From 8c9525b29eecf48bd140e24581f9091cd5bdc1ac Mon Sep 17 00:00:00 2001 From: Jerome Benoit Date: Fri, 26 Dec 2025 20:50:26 +0000 Subject: [PATCH] Import igraph_1.0.1+ds.orig.tar.xz [dgit import orig igraph_1.0.1+ds.orig.tar.xz] --- ACKNOWLEDGEMENTS.md | 293 + AUTHORS | 6 + CHANGELOG.md | 1827 + CMakeLists.txt | 195 + CONTRIBUTING.md | 246 + CONTRIBUTORS.md | 121 + CONTRIBUTORS.txt | 87 + COPYING | 340 + ChangeLog | 1 + IGRAPH_VERSION | 1 + INSTALL | 7 + NEWS | 5 + ONEWS | 1433 + README.md | 30 + SUPPORT.md | 13 + VERSIONING.md | 58 + doc/CMakeLists.txt | 362 + doc/adjlist.xxml | 51 + doc/attributes.xxml | 143 + doc/basicigraph.xxml | 241 + doc/bibdatabase.xml | 51 + doc/bipartite.xxml | 37 + doc/bitset.xxml | 63 + doc/c-docbook.re | 735 + doc/cliques.xxml | 45 + doc/coloring.xxml | 19 + doc/community.xxml | 66 + doc/cycles.xxml | 37 + doc/docbook.outlang | 36 + doc/doxrox.py | 567 + doc/dqueue.xxml | 24 + doc/embedding.xxml | 16 + doc/error.xxml | 88 + doc/fdl.xml | 420 + doc/flows.xxml | 48 + doc/foreign.xxml | 59 + doc/games.xxml | 85 + doc/generators.xxml | 93 + doc/glossary.md | 32 + doc/glossary.xml | 197 + doc/gpl.xml | 444 + doc/graphlets.xxml | 20 + doc/gtk-doc.xsl | 342 + doc/heap.xxml | 21 + doc/hrg.xxml | 45 + doc/html/home.png | Bin 0 -> 654 bytes doc/html/left.png | Bin 0 -> 459 bytes doc/html/right.png | Bin 0 -> 472 bytes doc/html/style.css | 279 + doc/html/toggle.js | 23 + doc/html/up.png | Bin 0 -> 406 bytes doc/igraph-docs.info | 65004 ++++++++++++++++ doc/igraph-docs.xml | 172 + doc/igraph.3 | 47 + doc/igraphlogo/igraph-white.svg.gz | Bin 0 -> 6138 bytes doc/igraphlogo/igraph.svg.gz | Bin 0 -> 6101 bytes doc/igraphlogo/igraph2.svg.gz | Bin 0 -> 1952 bytes doc/installation.xml | 634 + doc/introduction.xml | 109 + doc/isomorphism.xxml | 63 + doc/iterators.xxml | 101 + doc/layout.xxml | 57 + doc/licenses.xml | 14 + doc/licenses/Licence_CeCILL-B_V1-en.txt | 515 + doc/licenses/Licence_CeCILL-B_V1-fr.txt | 519 + doc/licenses/gpl-2.0.txt | 280 + doc/licenses/gpl-3.0.txt | 621 + doc/licenses/lgpl-2.1.txt | 458 + doc/licenses/lgpl-3.0.txt | 165 + doc/linalg.xxml | 54 + doc/matrix.xxml | 131 + doc/memory.xxml | 22 + doc/motifs.xxml | 33 + doc/nongraph.xxml | 40 + doc/operators.xxml | 42 + doc/pmt.xml | 146 + doc/processes.xxml | 16 + doc/progress.xxml | 39 + doc/psumtree.xxml | 20 + doc/random.xxml | 55 + doc/separators.xxml | 16 + doc/sparsemat.xxml | 108 + doc/spatial.xxml | 32 + doc/stack.xxml | 20 + doc/status.xxml | 27 + doc/structural.xxml | 256 + doc/strvector.xxml | 34 + doc/threading.xxml | 45 + doc/tutorial.xml | 327 + doc/vector.xxml | 176 + doc/vectorlist.xxml | 61 + doc/version-greater-or-equal.xsl | 54 + doc/visitors.xxml | 25 + etc/cmake/BuildType.cmake | 12 + etc/cmake/CheckTLSSupport.cmake | 36 + etc/cmake/CodeCoverage.cmake | 750 + etc/cmake/FindARPACK.cmake | 85 + etc/cmake/FindGLPK.cmake | 81 + etc/cmake/FindGMP.cmake | 36 + etc/cmake/FindPLFIT.cmake | 63 + etc/cmake/GetGitRevisionDescription.cmake | 166 + etc/cmake/GetGitRevisionDescription.cmake.in | 41 + etc/cmake/JoinPaths.cmake | 26 + etc/cmake/PadString.cmake | 39 + etc/cmake/PreventInSourceBuilds.cmake | 34 + etc/cmake/UseCCacheWhenInstalled.cmake | 7 + etc/cmake/attribute_support.cmake | 27 + etc/cmake/benchmark_helpers.cmake | 43 + etc/cmake/bit_operations_support.cmake | 92 + etc/cmake/compilers.cmake | 131 + etc/cmake/cpack_install_script.cmake | 87 + etc/cmake/create_igraph_version_file.cmake | 8 + etc/cmake/debugging.cmake | 7 + etc/cmake/dependencies.cmake | 171 + etc/cmake/features.cmake | 26 + etc/cmake/fuzz_helpers.cmake | 16 + etc/cmake/generate_tags_file.cmake | 46 + etc/cmake/helpers.cmake | 6 + etc/cmake/ieee754_endianness.cmake | 54 + etc/cmake/ieee754_endianness_check.c | 24 + etc/cmake/igraph-config.cmake.in | 40 + etc/cmake/lto.cmake | 23 + etc/cmake/packaging.cmake | 70 + etc/cmake/pkgconfig_helpers.cmake | 75 + etc/cmake/run_legacy_test.cmake | 73 + etc/cmake/safe_math_support.cmake | 18 + etc/cmake/sanitizers.cmake | 88 + etc/cmake/summary.cmake | 108 + etc/cmake/test_helpers.cmake | 120 + etc/cmake/tls.cmake | 25 + etc/cmake/uint128_support.cmake | 43 + etc/cmake/version.cmake | 90 + examples/simple/adjlist.c | 49 + examples/simple/ak-4102.max | 24623 ++++++ examples/simple/bellman_ford.c | 94 + examples/simple/bellman_ford.out | 16 + examples/simple/blas.c | 45 + examples/simple/blas.out | 2 + examples/simple/blas_dgemm.c | 30 + examples/simple/blas_dgemm.out | 2 + examples/simple/cattributes.c | 135 + examples/simple/cattributes.out | 6 + examples/simple/cattributes2.c | 70 + examples/simple/cattributes2.out | 337 + examples/simple/cattributes3.c | 99 + examples/simple/cattributes3.out | 243 + examples/simple/cattributes4.c | 84 + examples/simple/cattributes4.out | 81 + examples/simple/celegansneural.gml | 15644 ++++ examples/simple/centralization.c | 79 + examples/simple/centralization.out | 7 + examples/simple/cohesive_blocks.c | 65 + examples/simple/cohesive_blocks.out | 21 + examples/simple/coloring.c | 50 + examples/simple/creation.c | 33 + examples/simple/distances.c | 69 + examples/simple/distances.out | 51 + examples/simple/dominator_tree.c | 60 + examples/simple/dominator_tree.out | 9 + examples/simple/dot.c | 53 + examples/simple/dot.out | 196 + examples/simple/dqueue.c | 119 + examples/simple/dqueue.out | 2 + examples/simple/edgelist1.dl | 10 + examples/simple/edgelist2.dl | 9 + examples/simple/edgelist3.dl | 11 + examples/simple/edgelist4.dl | 10 + examples/simple/edgelist5.dl | 9 + examples/simple/edgelist6.dl | 11 + examples/simple/edgelist7.dl | 6 + examples/simple/eigenvector_centrality.c | 56 + examples/simple/eigenvector_centrality.out | 3 + examples/simple/even_tarjan.c | 70 + examples/simple/flow.c | 108 + examples/simple/flow2.c | 76 + examples/simple/flow2.out | 5 + examples/simple/foreign.c | 53 + examples/simple/foreign.out | 11 + examples/simple/fullmatrix1.dl | 7 + examples/simple/fullmatrix2.dl | 10 + examples/simple/fullmatrix3.dl | 12 + examples/simple/fullmatrix4.dl | 10 + examples/simple/gml.c | 54 + examples/simple/gml.out | 614 + examples/simple/graphml.c | 86 + examples/simple/igraph_all_st_mincuts.c | 68 + examples/simple/igraph_assortativity_degree.c | 37 + .../simple/igraph_assortativity_degree.out | 17 + .../simple/igraph_assortativity_nominal.c | 46 + .../simple/igraph_assortativity_nominal.out | 17 + examples/simple/igraph_atlas.c | 47 + examples/simple/igraph_atlas.out | 31 + .../simple/igraph_attribute_combination.c | 31 + .../simple/igraph_attribute_combination.out | 17 + examples/simple/igraph_average_path_length.c | 25 + .../igraph_avg_nearest_neighbor_degree.c | 65 + .../igraph_avg_nearest_neighbor_degree.out | 4 + examples/simple/igraph_barabasi_game.c | 96 + examples/simple/igraph_barabasi_game2.c | 110 + examples/simple/igraph_bfs.c | 72 + examples/simple/igraph_bfs.out | 6 + examples/simple/igraph_bfs_callback.c | 64 + examples/simple/igraph_bfs_callback.out | 1 + examples/simple/igraph_bfs_simple.c | 50 + examples/simple/igraph_bfs_simple.out | 3 + .../simple/igraph_biconnected_components.c | 71 + .../simple/igraph_biconnected_components.out | 17 + examples/simple/igraph_bipartite_create.c | 45 + examples/simple/igraph_bipartite_create.out | 8 + examples/simple/igraph_bipartite_projection.c | 54 + examples/simple/igraph_cliques.c | 147 + examples/simple/igraph_cliques.out | 50 + examples/simple/igraph_cocitation.c | 50 + examples/simple/igraph_cocitation.out | 11 + .../igraph_community_edge_betweenness.c | 62 + .../igraph_community_edge_betweenness.out | 11 + examples/simple/igraph_community_fastgreedy.c | 43 + .../simple/igraph_community_fastgreedy.out | 5 + .../igraph_community_label_propagation.c | 59 + .../igraph_community_label_propagation.out | 2 + .../igraph_community_leading_eigenvector.c | 92 + .../igraph_community_leading_eigenvector.out | 7 + examples/simple/igraph_community_leiden.c | 126 + examples/simple/igraph_community_leiden.out | 12 + examples/simple/igraph_community_multilevel.c | 115 + .../simple/igraph_community_multilevel.out | 14 + .../igraph_community_optimal_modularity.c | 57 + .../igraph_community_optimal_modularity.out | 2 + examples/simple/igraph_complementer.c | 111 + examples/simple/igraph_complementer.out | 87 + examples/simple/igraph_compose.c | 119 + examples/simple/igraph_compose.out | 11 + examples/simple/igraph_contract_vertices.c | 84 + examples/simple/igraph_copy.c | 58 + examples/simple/igraph_create.c | 72 + examples/simple/igraph_decompose.c | 50 + examples/simple/igraph_decompose.out | 18 + examples/simple/igraph_degree.c | 153 + examples/simple/igraph_degree.out | 13 + examples/simple/igraph_degree_sequence_game.c | 114 + .../simple/igraph_degree_sequence_game.out | 7 + examples/simple/igraph_delete_edges.c | 45 + examples/simple/igraph_delete_vertices.c | 50 + examples/simple/igraph_diameter.c | 56 + examples/simple/igraph_diameter.out | 3 + examples/simple/igraph_difference.c | 139 + examples/simple/igraph_difference.out | 24 + examples/simple/igraph_disjoint_union.c | 75 + examples/simple/igraph_disjoint_union.out | 30 + examples/simple/igraph_eccentricity.c | 49 + examples/simple/igraph_eccentricity.out | 3 + examples/simple/igraph_erdos_renyi_game_gnm.c | 52 + examples/simple/igraph_erdos_renyi_game_gnp.c | 52 + examples/simple/igraph_es_pairs.c | 81 + examples/simple/igraph_feedback_arc_set.c | 95 + examples/simple/igraph_feedback_arc_set.out | 3 + examples/simple/igraph_feedback_arc_set_ip.c | 127 + .../simple/igraph_feedback_arc_set_ip.out | 5 + examples/simple/igraph_fisher_yates_shuffle.c | 105 + examples/simple/igraph_full.c | 61 + examples/simple/igraph_full.out | 4 + .../igraph_get_all_shortest_paths_dijkstra.c | 83 + ...igraph_get_all_shortest_paths_dijkstra.out | 18 + examples/simple/igraph_get_eid.c | 63 + examples/simple/igraph_get_eid.out | 2 + examples/simple/igraph_get_eids.c | 96 + examples/simple/igraph_get_laplacian.c | 41 + examples/simple/igraph_get_laplacian.out | 5 + examples/simple/igraph_get_laplacian_sparse.c | 133 + .../simple/igraph_get_laplacian_sparse.out | 200 + examples/simple/igraph_get_shortest_paths.c | 116 + examples/simple/igraph_get_shortest_paths.out | 7 + .../igraph_get_shortest_paths_dijkstra.c | 84 + .../igraph_get_shortest_paths_dijkstra.out | 21 + examples/simple/igraph_girth.c | 60 + examples/simple/igraph_grg_game.c | 53 + examples/simple/igraph_grg_game.out | 1 + examples/simple/igraph_has_multiple.c | 87 + examples/simple/igraph_independent_sets.c | 77 + examples/simple/igraph_independent_sets.out | 41 + examples/simple/igraph_intersection.c | 126 + examples/simple/igraph_intersection.out | 7 + examples/simple/igraph_is_biconnected.c | 36 + examples/simple/igraph_is_biconnected.out | 1 + examples/simple/igraph_is_directed.c | 45 + examples/simple/igraph_is_loop.c | 57 + examples/simple/igraph_is_loop.out | 8 + examples/simple/igraph_is_minimal_separator.c | 76 + examples/simple/igraph_is_multiple.c | 58 + examples/simple/igraph_is_multiple.out | 2 + examples/simple/igraph_is_separator.c | 88 + examples/simple/igraph_isomorphic_vf2.c | 98 + examples/simple/igraph_join.c | 52 + examples/simple/igraph_join.out | 44 + examples/simple/igraph_kary_tree.c | 45 + examples/simple/igraph_kary_tree.out | 2 + examples/simple/igraph_lapack_dgeev.c | 64 + examples/simple/igraph_lapack_dgeev.out | 8 + examples/simple/igraph_lapack_dgeevx.c | 96 + examples/simple/igraph_lapack_dgeevx.out | 13 + examples/simple/igraph_lapack_dgesv.c | 153 + examples/simple/igraph_lapack_dgesv.out | 1 + examples/simple/igraph_lapack_dsyevr.c | 70 + examples/simple/igraph_lapack_dsyevr.out | 13 + .../simple/igraph_layout_reingold_tilford.c | 45 + .../simple/igraph_layout_reingold_tilford.in | 44 + examples/simple/igraph_lcf.c | 54 + examples/simple/igraph_lcf.out | 3 + examples/simple/igraph_list_triangles.c | 41 + examples/simple/igraph_list_triangles.out | 4 + examples/simple/igraph_maximal_cliques.c | 55 + .../igraph_maximum_bipartite_matching.c | 75 + examples/simple/igraph_mincut.c | 114 + examples/simple/igraph_mincut.out | 12 + examples/simple/igraph_minimal_separators.c | 55 + .../simple/igraph_minimum_size_separators.c | 45 + .../simple/igraph_minimum_spanning_tree.c | 67 + .../simple/igraph_minimum_spanning_tree.out | 7 + examples/simple/igraph_motifs_randesu.c | 55 + examples/simple/igraph_motifs_randesu.out | 17 + examples/simple/igraph_neighbors.c | 50 + examples/simple/igraph_neighbors.out | 3 + examples/simple/igraph_pagerank.c | 39 + examples/simple/igraph_power_law_fit.c | 66 + examples/simple/igraph_power_law_fit.out | 3 + examples/simple/igraph_radius.c | 56 + examples/simple/igraph_random_sample.c | 30 + examples/simple/igraph_read_graph_dl.c | 61 + examples/simple/igraph_read_graph_dl.out | 104 + examples/simple/igraph_read_graph_graphdb.c | 42 + examples/simple/igraph_read_graph_graphdb.out | 1500 + examples/simple/igraph_read_graph_lgl-1.lgl | 7 + examples/simple/igraph_read_graph_lgl-2.lgl | 7 + examples/simple/igraph_read_graph_lgl-3.lgl | 4 + examples/simple/igraph_read_graph_lgl.c | 80 + examples/simple/igraph_read_graph_lgl.out | 12 + .../simple/igraph_realize_degree_sequence.c | 41 + .../simple/igraph_realize_degree_sequence.out | 8 + examples/simple/igraph_reciprocity.c | 67 + examples/simple/igraph_regular_tree.c | 30 + examples/simple/igraph_regular_tree.out | 3 + examples/simple/igraph_ring.c | 49 + examples/simple/igraph_ring.out | 16 + examples/simple/igraph_similarity.c | 86 + examples/simple/igraph_similarity.out | 25 + examples/simple/igraph_simplify.c | 94 + examples/simple/igraph_simplify.out | 10 + examples/simple/igraph_small.c | 37 + examples/simple/igraph_small.out | 5 + examples/simple/igraph_sparsemat.c | 175 + examples/simple/igraph_sparsemat.out | 295 + examples/simple/igraph_sparsemat3.c | 310 + examples/simple/igraph_sparsemat3.out | 0 examples/simple/igraph_sparsemat4.c | 313 + examples/simple/igraph_sparsemat4.out | 0 examples/simple/igraph_sparsemat6.c | 67 + examples/simple/igraph_sparsemat7.c | 78 + examples/simple/igraph_sparsemat8.c | 210 + examples/simple/igraph_star.c | 38 + examples/simple/igraph_star.out | 6 + examples/simple/igraph_strvector.c | 88 + examples/simple/igraph_strvector.out | 45 + examples/simple/igraph_subisomorphic_lad.c | 127 + examples/simple/igraph_subisomorphic_lad.out | 30 + examples/simple/igraph_symmetric_tree.c | 30 + examples/simple/igraph_to_undirected.c | 54 + examples/simple/igraph_to_undirected.out | 48 + examples/simple/igraph_topological_sorting.c | 51 + .../simple/igraph_topological_sorting.out | 3 + examples/simple/igraph_transitivity.c | 96 + examples/simple/igraph_union.c | 53 + examples/simple/igraph_union.out | 9 + examples/simple/igraph_vector_int_list_sort.c | 44 + examples/simple/igraph_version.c | 42 + examples/simple/igraph_vs_nonadj.c | 67 + examples/simple/igraph_vs_nonadj.out | 2 + examples/simple/igraph_vs_range.c | 52 + examples/simple/igraph_vs_range.out | 1 + examples/simple/igraph_vs_vector.c | 88 + examples/simple/igraph_vs_vector.out | 1 + examples/simple/igraph_weighted_adjacency.c | 68 + examples/simple/igraph_weighted_adjacency.out | 6 + examples/simple/igraph_write_graph_lgl.c | 50 + examples/simple/igraph_write_graph_lgl.out | 37 + examples/simple/igraph_write_graph_pajek.c | 76 + examples/simple/igraph_write_graph_pajek.out | 56 + examples/simple/iso_b03_m1000.A00 | Bin 0 -> 5002 bytes examples/simple/karate.gml | 530 + examples/simple/links.net | 16 + examples/simple/nodelist1.dl | 9 + examples/simple/nodelist2.dl | 8 + examples/simple/random_seed.c | 37 + examples/simple/safelocale.c | 52 + examples/simple/safelocale.out | 21 + examples/simple/test.graphml | 55 + examples/simple/walktrap.c | 61 + examples/simple/weighted.gml | 17 + examples/tutorial/tutorial1.c | 33 + examples/tutorial/tutorial2.c | 45 + examples/tutorial/tutorial3.c | 53 + igraph.pc.in | 13 + include/igraph.h | 97 + include/igraph_adjlist.h | 220 + include/igraph_arpack.h | 395 + include/igraph_attributes.h | 931 + include/igraph_bipartite.h | 112 + include/igraph_bitset.h | 265 + include/igraph_bitset_list.h | 46 + include/igraph_blas.h | 67 + include/igraph_centrality.h | 207 + include/igraph_cliques.h | 141 + include/igraph_cocitation.h | 62 + include/igraph_cohesive_blocks.h | 38 + include/igraph_coloring.h | 57 + include/igraph_community.h | 280 + include/igraph_complex.h | 104 + include/igraph_components.h | 74 + include/igraph_config.h.in | 51 + include/igraph_constants.h | 278 + include/igraph_constructors.h | 105 + include/igraph_conversion.h | 67 + include/igraph_cycles.h | 103 + include/igraph_datatype.h | 122 + include/igraph_decls.h | 87 + include/igraph_dqueue.h | 66 + include/igraph_dqueue_pmt.h | 44 + include/igraph_eigen.h | 98 + include/igraph_embedding.h | 63 + include/igraph_epidemics.h | 63 + include/igraph_error.h | 932 + include/igraph_eulerian.h | 34 + include/igraph_flow.h | 153 + include/igraph_foreign.h | 98 + include/igraph_games.h | 229 + include/igraph_graph_list.h | 55 + include/igraph_graphicality.h | 71 + include/igraph_graphlets.h | 48 + include/igraph_heap.h | 80 + include/igraph_heap_pmt.h | 34 + include/igraph_hrg.h | 126 + include/igraph_interface.h | 155 + include/igraph_interrupt.h | 123 + include/igraph_isomorphism.h | 282 + include/igraph_iterators.h | 416 + include/igraph_lapack.h | 107 + include/igraph_layout.h | 293 + include/igraph_lsap.h | 35 + include/igraph_matching.h | 48 + include/igraph_matrix.h | 95 + include/igraph_matrix_list.h | 55 + include/igraph_matrix_pmt.h | 251 + include/igraph_memory.h | 45 + include/igraph_mixing.h | 67 + include/igraph_motifs.h | 98 + include/igraph_neighborhood.h | 44 + include/igraph_nongraph.h | 95 + include/igraph_operators.h | 118 + include/igraph_paths.h | 301 + include/igraph_pmt.h | 205 + include/igraph_pmt_off.h | 181 + include/igraph_progress.h | 197 + include/igraph_psumtree.h | 47 + include/igraph_qsort.h | 35 + include/igraph_random.h | 165 + include/igraph_reachability.h | 49 + include/igraph_sampling.h | 45 + include/igraph_scan.h | 67 + include/igraph_separators.h | 47 + include/igraph_setup.h | 31 + include/igraph_sparsemat.h | 287 + include/igraph_spatial.h | 91 + include/igraph_stack.h | 66 + include/igraph_stack_pmt.h | 43 + include/igraph_statusbar.h | 119 + include/igraph_structural.h | 173 + include/igraph_strvector.h | 112 + include/igraph_threading.h.in | 42 + include/igraph_transitivity.h | 54 + include/igraph_typed_list_pmt.h | 116 + include/igraph_types.h | 142 + include/igraph_vector.h | 158 + include/igraph_vector_list.h | 63 + include/igraph_vector_pmt.h | 313 + include/igraph_vector_ptr.h | 98 + include/igraph_vector_type.h | 28 + include/igraph_version.h.in | 39 + include/igraph_visitor.h | 134 + interfaces/CMakeLists.txt | 28 + interfaces/functions.yaml | 2817 + interfaces/types.yaml | 652 + src/CMakeLists.txt | 511 + src/centrality/betweenness.c | 1404 + src/centrality/centrality_internal.h | 37 + src/centrality/centrality_other.c | 72 + src/centrality/centralization.c | 723 + src/centrality/closeness.c | 805 + src/centrality/coreness.c | 157 + src/centrality/eigenvector.c | 646 + src/centrality/hub_authority.c | 505 + src/centrality/pagerank.c | 721 + src/centrality/prpack.cpp | 134 + src/centrality/prpack/CMakeLists.txt | 44 + src/centrality/prpack/prpack.h | 11 + src/centrality/prpack/prpack_base_graph.cpp | 345 + src/centrality/prpack/prpack_base_graph.h | 46 + src/centrality/prpack/prpack_csc.h | 30 + src/centrality/prpack/prpack_csr.h | 16 + src/centrality/prpack/prpack_edge_list.h | 16 + src/centrality/prpack/prpack_igraph_graph.cpp | 176 + src/centrality/prpack/prpack_igraph_graph.h | 33 + .../prpack/prpack_preprocessed_ge_graph.cpp | 65 + .../prpack/prpack_preprocessed_ge_graph.h | 26 + .../prpack/prpack_preprocessed_graph.h | 17 + .../prpack/prpack_preprocessed_gs_graph.cpp | 80 + .../prpack/prpack_preprocessed_gs_graph.h | 30 + .../prpack/prpack_preprocessed_scc_graph.cpp | 201 + .../prpack/prpack_preprocessed_scc_graph.h | 39 + .../prpack_preprocessed_schur_graph.cpp | 120 + .../prpack/prpack_preprocessed_schur_graph.h | 33 + src/centrality/prpack/prpack_result.cpp | 11 + src/centrality/prpack/prpack_result.h | 30 + src/centrality/prpack/prpack_solver.cpp | 886 + src/centrality/prpack/prpack_solver.h | 180 + src/centrality/prpack/prpack_utils.cpp | 58 + src/centrality/prpack/prpack_utils.h | 33 + src/centrality/prpack_internal.h | 43 + src/centrality/truss.cpp | 286 + src/cliques/cliquer/CMakeLists.txt | 27 + src/cliques/cliquer/README | 61 + src/cliques/cliquer/cliquer.c | 1868 + src/cliques/cliquer/cliquer.h | 78 + src/cliques/cliquer/cliquer_graph.c | 764 + src/cliques/cliquer/cliquerconf.h | 68 + src/cliques/cliquer/graph.h | 75 + src/cliques/cliquer/misc.h | 59 + src/cliques/cliquer/reorder.c | 424 + src/cliques/cliquer/reorder.h | 26 + src/cliques/cliquer/set.h | 386 + src/cliques/cliquer_internal.h | 51 + src/cliques/cliquer_wrapper.c | 472 + src/cliques/cliques.c | 1119 + src/cliques/glet.c | 881 + src/cliques/maximal_cliques.c | 619 + src/cliques/maximal_cliques_template.h | 378 + src/community/community_internal.h | 34 + src/community/community_misc.c | 1019 + src/community/edge_betweenness.c | 809 + src/community/fast_modularity.c | 1072 + src/community/fluid.c | 247 + src/community/infomap.cpp | 275 + src/community/label_propagation.c | 765 + src/community/leading_eigenvector.c | 866 + src/community/leiden.c | 1503 + src/community/louvain.c | 735 + src/community/modularity.c | 393 + src/community/optimal_modularity.c | 316 + src/community/spinglass/NetDataTypes.cpp | 101 + src/community/spinglass/NetDataTypes.h | 567 + src/community/spinglass/NetRoutines.cpp | 80 + src/community/spinglass/NetRoutines.h | 54 + src/community/spinglass/clustertool.cpp | 632 + src/community/spinglass/pottsmodel_2.cpp | 1708 + src/community/spinglass/pottsmodel_2.h | 164 + src/community/voronoi.c | 645 + src/community/walktrap/walktrap.cpp | 233 + .../walktrap/walktrap_communities.cpp | 785 + src/community/walktrap/walktrap_communities.h | 160 + src/community/walktrap/walktrap_graph.cpp | 228 + src/community/walktrap/walktrap_graph.h | 100 + src/community/walktrap/walktrap_heap.cpp | 141 + src/community/walktrap/walktrap_heap.h | 106 + src/config.h.in | 40 + src/connectivity/cohesive_blocks.c | 568 + src/connectivity/components.c | 1608 + src/connectivity/percolation.c | 404 + src/connectivity/reachability.c | 257 + src/connectivity/separators.c | 887 + src/constructors/adjacency.c | 1533 + src/constructors/atlas-edges.h | 1291 + src/constructors/atlas.c | 87 + src/constructors/basic_constructors.c | 143 + src/constructors/circulant.c | 112 + src/constructors/de_bruijn.c | 113 + src/constructors/famous.c | 495 + src/constructors/full.c | 376 + src/constructors/generalized_petersen.c | 95 + src/constructors/kautz.c | 210 + src/constructors/lattices.c | 601 + src/constructors/lcf.c | 156 + src/constructors/linegraph.c | 174 + src/constructors/mycielskian.c | 244 + src/constructors/prufer.c | 125 + src/constructors/regular.c | 1032 + src/constructors/trees.c | 161 + src/core/bitset.c | 817 + src/core/bitset_list.c | 50 + src/core/buckets.c | 192 + src/core/buckets.h | 71 + src/core/cutheap.c | 168 + src/core/cutheap.h | 52 + src/core/dqueue.c | 48 + src/core/dqueue.pmt | 430 + src/core/error.c | 601 + src/core/estack.c | 66 + src/core/estack.h | 51 + src/core/exceptions.h | 34 + src/core/fixed_vectorlist.c | 58 + src/core/fixed_vectorlist.h | 49 + src/core/genheap.c | 307 + src/core/genheap.h | 73 + src/core/grid.c | 275 + src/core/grid.h | 77 + src/core/heap.c | 63 + src/core/heap.pmt | 377 + src/core/indheap.c | 1025 + src/core/indheap.h | 154 + src/core/interruption.c | 40 + src/core/interruption.h | 80 + src/core/marked_queue.c | 114 + src/core/marked_queue.h | 74 + src/core/math.h | 81 + src/core/matrix.c | 290 + src/core/matrix.pmt | 1838 + src/core/matrix_list.c | 53 + src/core/memory.c | 123 + src/core/printing.c | 230 + src/core/progress.c | 150 + src/core/psumtree.c | 267 + src/core/set.c | 323 + src/core/set.h | 63 + src/core/setup.c | 67 + src/core/sparsemat.c | 3251 + src/core/stack.c | 48 + src/core/stack.pmt | 305 + src/core/statusbar.c | 130 + src/core/strvector.c | 738 + src/core/trie.c | 434 + src/core/trie.h | 71 + src/core/typed_list.pmt | 1123 + src/core/vector.c | 651 + src/core/vector.pmt | 3464 + src/core/vector_list.c | 170 + src/core/vector_ptr.c | 839 + src/cycles/cycle_bases.c | 542 + src/cycles/feedback_sets.c | 1326 + src/cycles/feedback_sets.h | 52 + src/cycles/order_cycle.cpp | 100 + src/cycles/order_cycle.h | 35 + src/cycles/simple_cycles.c | 651 + src/flow/flow.c | 2615 + src/flow/flow_conversion.c | 92 + src/flow/flow_internal.h | 43 + src/flow/st-cuts.c | 1575 + src/games/barabasi.c | 841 + src/games/callaway_traits.c | 197 + src/games/chung_lu.c | 321 + src/games/citations.c | 498 + src/games/correlated.c | 338 + src/games/degree_sequence.c | 864 + .../degree_sequence_vl/degree_sequence_vl.h | 34 + .../degree_sequence_vl/gengraph_definitions.h | 195 + .../gengraph_degree_sequence.cpp | 140 + .../gengraph_degree_sequence.h | 88 + .../gengraph_graph_molloy_hash.cpp | 732 + .../gengraph_graph_molloy_hash.h | 188 + .../gengraph_graph_molloy_optimized.cpp | 1212 + .../gengraph_graph_molloy_optimized.h | 231 + src/games/degree_sequence_vl/gengraph_hash.h | 315 + .../degree_sequence_vl/gengraph_header.h | 109 + .../gengraph_mr-connected.cpp | 182 + src/games/degree_sequence_vl/gengraph_qsort.h | 307 + .../degree_sequence_vl/gengraph_random.cpp | 275 + .../degree_sequence_vl/gengraph_random.h | 213 + src/games/dotproduct.c | 102 + src/games/erdos_renyi.c | 851 + src/games/establishment.c | 180 + src/games/forestfire.c | 257 + src/games/grg.c | 174 + src/games/growing_random.c | 105 + src/games/islands.c | 170 + src/games/k_regular.c | 83 + src/games/preference.c | 611 + src/games/recent_degree.c | 381 + src/games/sbm.c | 648 + src/games/static_fitness.c | 464 + src/games/tree.c | 190 + src/games/watts_strogatz.c | 118 + src/graph/adjlist.c | 1298 + src/graph/attributes.c | 1148 + src/graph/attributes.h | 111 + src/graph/basic_query.c | 63 + src/graph/caching.c | 214 + src/graph/caching.h | 52 + src/graph/cattributes.c | 2909 + src/graph/graph_list.c | 60 + src/graph/internal.h | 32 + src/graph/iterators.c | 2048 + src/graph/type_common.c | 243 + src/graph/type_indexededgelist.c | 2008 + src/graph/visitors.c | 661 + src/hrg/dendro.h | 296 + src/hrg/graph.h | 156 + src/hrg/graph_simp.h | 142 + src/hrg/hrg.cc | 1074 + src/hrg/hrg_types.cc | 3589 + src/hrg/rbtree.h | 144 + src/hrg/splittree_eq.h | 168 + src/internal/glpk_support.c | 169 + src/internal/glpk_support.h | 147 + src/internal/gmp_internal.h | 32 + src/internal/hacks.c | 61 + src/internal/hacks.h | 72 + src/internal/lsap.c | 689 + src/internal/qsort.c | 267 + src/internal/qsort_r.c | 8 + src/internal/utils.c | 224 + src/internal/utils.h | 39 + src/io/dimacs.c | 363 + src/io/dl-header.h | 53 + src/io/dl.c | 207 + src/io/dot.c | 322 + src/io/edgelist.c | 160 + src/io/gml-header.h | 42 + src/io/gml-tree.c | 279 + src/io/gml-tree.h | 87 + src/io/gml.c | 1290 + src/io/graphdb.c | 146 + src/io/graphml.c | 2029 + src/io/leda.c | 309 + src/io/lgl-header.h | 37 + src/io/lgl.c | 462 + src/io/ncol-header.h | 36 + src/io/ncol.c | 442 + src/io/pajek-header.h | 64 + src/io/pajek.c | 746 + src/io/parse_utils.c | 377 + src/io/parse_utils.h | 41 + src/io/parsers/dl-lexer.c | 2532 + src/io/parsers/dl-lexer.h | 731 + src/io/parsers/dl-parser.c | 2192 + src/io/parsers/dl-parser.h | 109 + src/io/parsers/gml-lexer.c | 2346 + src/io/parsers/gml-lexer.h | 730 + src/io/parsers/gml-parser.c | 1859 + src/io/parsers/gml-parser.h | 94 + src/io/parsers/lgl-lexer.c | 2284 + src/io/parsers/lgl-lexer.h | 730 + src/io/parsers/lgl-parser.c | 1691 + src/io/parsers/lgl-parser.h | 89 + src/io/parsers/ncol-lexer.c | 2279 + src/io/parsers/ncol-lexer.h | 730 + src/io/parsers/ncol-parser.c | 1683 + src/io/parsers/ncol-parser.h | 87 + src/io/parsers/pajek-lexer.c | 2683 + src/io/parsers/pajek-lexer.h | 734 + src/io/parsers/pajek-parser.c | 2919 + src/io/parsers/pajek-parser.h | 180 + src/isomorphism/bliss.cc | 764 + src/isomorphism/bliss/CMakeLists.txt | 43 + src/isomorphism/bliss/bignum.hh | 103 + src/isomorphism/bliss/defs.cc | 32 + src/isomorphism/bliss/defs.hh | 90 + src/isomorphism/bliss/graph.cc | 5035 ++ src/isomorphism/bliss/graph.hh | 876 + src/isomorphism/bliss/heap.cc | 111 + src/isomorphism/bliss/heap.hh | 89 + src/isomorphism/bliss/igraph-changes.md | 34 + src/isomorphism/bliss/kqueue.hh | 168 + src/isomorphism/bliss/kstack.hh | 145 + src/isomorphism/bliss/orbit.cc | 151 + src/isomorphism/bliss/orbit.hh | 112 + src/isomorphism/bliss/partition.cc | 1127 + src/isomorphism/bliss/partition.hh | 299 + src/isomorphism/bliss/stats.hh | 87 + src/isomorphism/bliss/uintseqhash.cc | 117 + src/isomorphism/bliss/uintseqhash.hh | 63 + src/isomorphism/bliss/utils.cc | 60 + src/isomorphism/bliss/utils.hh | 46 + src/isomorphism/isoclasses.c | 2936 + src/isomorphism/isoclasses.h | 45 + src/isomorphism/isomorphism_misc.c | 114 + src/isomorphism/lad.c | 1646 + src/isomorphism/queries.c | 215 + src/isomorphism/vf2.c | 1741 + src/layout/align.c | 320 + src/layout/circular.c | 186 + src/layout/davidson_harel.c | 454 + src/layout/drl/DensityGrid.cpp | 258 + src/layout/drl/DensityGrid.h | 81 + src/layout/drl/DensityGrid_3d.cpp | 282 + src/layout/drl/DensityGrid_3d.h | 81 + src/layout/drl/drl_Node.h | 70 + src/layout/drl/drl_Node_3d.h | 70 + src/layout/drl/drl_graph.cpp | 1270 + src/layout/drl/drl_graph.h | 132 + src/layout/drl/drl_graph_3d.cpp | 837 + src/layout/drl/drl_graph_3d.h | 124 + src/layout/drl/drl_layout.cpp | 481 + src/layout/drl/drl_layout.h | 65 + src/layout/drl/drl_layout_3d.cpp | 132 + src/layout/drl/drl_layout_3d.h | 65 + src/layout/drl/drl_parse.cpp | 197 + src/layout/drl/drl_parse.h | 68 + src/layout/fruchterman_reingold.c | 676 + src/layout/gem.c | 249 + src/layout/graphopt.c | 436 + src/layout/kamada_kawai.c | 702 + src/layout/large_graph.c | 389 + src/layout/layout_bipartite.c | 78 + src/layout/layout_grid.c | 111 + src/layout/layout_internal.h | 75 + src/layout/layout_random.c | 272 + src/layout/mds.c | 295 + src/layout/merge_dla.c | 301 + src/layout/merge_grid.c | 203 + src/layout/merge_grid.h | 55 + src/layout/reingold_tilford.c | 1010 + src/layout/sugiyama.c | 1309 + src/layout/umap.c | 1285 + src/linalg/arpack.c | 1634 + src/linalg/arpack_internal.h | 232 + src/linalg/blas.c | 261 + src/linalg/blas_internal.h | 92 + src/linalg/eigen.c | 1466 + src/linalg/lapack.c | 1057 + src/linalg/lapack_internal.h | 184 + src/math/complex.c | 375 + src/math/safe_intop.c | 186 + src/math/safe_intop.h | 139 + src/math/utils.c | 161 + src/misc/bipartite.c | 1784 + src/misc/chordality.c | 477 + src/misc/cocitation.c | 767 + src/misc/coloring.c | 519 + src/misc/conversion.c | 980 + src/misc/degree_sequence.cpp | 1373 + src/misc/embedding.c | 1195 + src/misc/graphicality.c | 928 + src/misc/graphicality.h | 36 + src/misc/matching.c | 1013 + src/misc/mixing.c | 1008 + src/misc/motifs.c | 1223 + src/misc/other.c | 261 + src/misc/power_law_fit.c | 319 + src/misc/scan.c | 766 + src/misc/sir.c | 265 + src/misc/spanning_trees.c | 620 + src/operators/add_edge.c | 61 + src/operators/complementer.c | 100 + src/operators/compose.c | 131 + src/operators/connect_neighborhood.c | 318 + src/operators/contract.c | 148 + src/operators/difference.c | 181 + src/operators/disjoint_union.c | 195 + src/operators/intersection.c | 286 + src/operators/join.c | 106 + src/operators/misc_internal.c | 239 + src/operators/misc_internal.h | 46 + src/operators/permute.c | 127 + src/operators/products.c | 652 + src/operators/reverse.c | 101 + src/operators/rewire.c | 267 + src/operators/rewire_edges.c | 385 + src/operators/rewire_internal.h | 17 + src/operators/simplify.c | 208 + src/operators/subgraph.c | 615 + src/operators/subgraph.h | 40 + src/operators/union.c | 245 + src/paths/all_shortest_paths.c | 356 + src/paths/astar.c | 273 + src/paths/bellman_ford.c | 591 + src/paths/dijkstra.c | 1235 + src/paths/distances.c | 864 + src/paths/eulerian.c | 681 + src/paths/floyd_warshall.c | 365 + src/paths/histogram.c | 141 + src/paths/johnson.c | 254 + src/paths/paths_internal.h | 119 + src/paths/random_walk.c | 340 + src/paths/shortest_paths.c | 1658 + src/paths/simple_paths.c | 189 + src/paths/sparsifier.c | 455 + src/paths/unweighted.c | 703 + src/paths/voronoi.c | 419 + src/paths/widest_paths.c | 741 + src/properties/basic_properties.c | 406 + src/properties/complete.c | 279 + src/properties/constraint.c | 288 + src/properties/convergence_degree.c | 206 + src/properties/dag.c | 214 + src/properties/degrees.c | 738 + src/properties/ecc.c | 385 + src/properties/girth.c | 216 + src/properties/loops.c | 161 + src/properties/multiplicity.c | 563 + src/properties/neighborhood.c | 458 + src/properties/perfect.c | 190 + src/properties/properties_internal.h | 33 + src/properties/rich_club.c | 166 + src/properties/spectral.c | 373 + src/properties/trees.c | 762 + src/properties/triangles.c | 923 + src/properties/triangles_template.h | 137 + src/properties/triangles_template1.h | 93 + src/random/random.c | 2229 + src/random/random_device.cpp | 43 + src/random/random_internal.h | 35 + src/random/rng_glibc2.c | 144 + src/random/rng_mt19937.c | 179 + src/random/rng_pcg32.c | 122 + src/random/rng_pcg64.c | 138 + src/random/sampling.c | 192 + src/spatial/beta_skeleton.cpp | 816 + src/spatial/convex_hull.c | 188 + src/spatial/delaunay.c | 280 + src/spatial/edge_lengths.c | 119 + src/spatial/nanoflann_internal.hpp | 121 + src/spatial/nearest_neighbor.cpp | 189 + src/spatial/spatial_internal.h | 34 + src/version.c | 59 + tests/CMakeLists.txt | 1067 + tests/benchmarks/bench.h | 61 + tests/benchmarks/beta_skeletons.c | 150 + tests/benchmarks/coloring.c | 56 + tests/benchmarks/community.c | 109 + tests/benchmarks/connectivity.c | 110 + tests/benchmarks/erdos_renyi.c | 136 + tests/benchmarks/graphicality.c | 144 + tests/benchmarks/igraph_adjacency.c | 100 + .../igraph_average_path_length_unweighted.c | 95 + tests/benchmarks/igraph_betweenness.c | 84 + .../benchmarks/igraph_betweenness_weighted.c | 164 + tests/benchmarks/igraph_cliques.c | 54 + tests/benchmarks/igraph_closeness_weighted.c | 164 + tests/benchmarks/igraph_decompose.c | 56 + tests/benchmarks/igraph_degree.c | 142 + .../benchmarks/igraph_degree_sequence_game.c | 120 + tests/benchmarks/igraph_delaunay_graph.c | 64 + tests/benchmarks/igraph_distances.c | 168 + tests/benchmarks/igraph_ecc.c | 175 + tests/benchmarks/igraph_induced_subgraph.c | 125 + .../igraph_induced_subgraph_edges.c | 100 + tests/benchmarks/igraph_layout_umap.c | 85 + tests/benchmarks/igraph_matrix_transpose.c | 57 + tests/benchmarks/igraph_maximal_cliques.c | 54 + .../igraph_nearest_neighbor_graph.c | 77 + tests/benchmarks/igraph_neighborhood.c | 473 + tests/benchmarks/igraph_pagerank.c | 137 + tests/benchmarks/igraph_pagerank_weighted.c | 156 + tests/benchmarks/igraph_power_law_fit.c | 168 + tests/benchmarks/igraph_qsort.c | 63 + tests/benchmarks/igraph_random_walk.c | 201 + .../igraph_realize_degree_sequence.c | 85 + tests/benchmarks/igraph_rewire.c | 77 + tests/benchmarks/igraph_strength.c | 145 + tests/benchmarks/igraph_transitivity.c | 252 + tests/benchmarks/igraph_tree_game.c | 104 + tests/benchmarks/igraph_vertex_connectivity.c | 56 + tests/benchmarks/igraph_voronoi.c | 165 + tests/benchmarks/inc_vs_adj.c | 456 + tests/benchmarks/intersection.c | 87 + tests/benchmarks/lad.c | 67 + tests/benchmarks/modularity.c | 71 + tests/benchmarks/spanning_tree.c | 164 + tests/regression/bug-1033045.c | 43 + tests/regression/bug-1033045.out | 11 + tests/regression/bug-1149658.c | 47 + tests/regression/bug_1760.c | 159 + tests/regression/bug_1760.out | 67 + tests/regression/bug_1814.c | 75 + tests/regression/bug_1814.out | 135 + tests/regression/bug_1970.c | 44 + tests/regression/bug_1970.graphml | 1 + tests/regression/bug_2150.c | 46 + tests/regression/bug_2497.c | 48 + tests/regression/bug_2497.gml | 4 + tests/regression/bug_2497.out | 13 + tests/regression/bug_2506.c | 68 + tests/regression/bug_2506_1.graphml | 4 + tests/regression/bug_2506_2.graphml | 1 + tests/regression/bug_2506_3.graphml | 1 + tests/regression/bug_2517.c | 44 + tests/regression/bug_2517.out | 4 + tests/regression/bug_2608.c | 49 + tests/regression/bug_2608.out | 0 tests/regression/cattr_bool_bug.c | 73 + tests/regression/cattr_bool_bug.graphml | 76 + tests/regression/cattr_bool_bug2.c | 63 + tests/regression/cattr_bool_bug2.graphml | 7 + .../igraph_layout_kamada_kawai_3d_bug_1462.c | 66 + ...igraph_layout_kamada_kawai_3d_bug_1462.out | 2 + .../igraph_layout_reingold_tilford_bug_879.c | 55 + .../igraph_layout_reingold_tilford_bug_879.in | 15 + ...igraph_layout_reingold_tilford_bug_879.out | 16 + .../igraph_read_graph_gml_invalid_inputs.c | 74 + ...igraph_read_graph_graphml_invalid_inputs.c | 81 + ...raph_read_graph_graphml_invalid_inputs.out | 0 .../igraph_read_graph_pajek_invalid_inputs.c | 70 + tests/regression/invalid1.gml | 463 + tests/regression/invalid1.graphml | 18 + tests/regression/invalid2.gml | 1 + tests/regression/invalid2.graphml | 26 + tests/regression/invalid3.graphml | 0 tests/regression/invalid4.gml | 1 + tests/regression/invalid4.graphml | Bin 0 -> 92 bytes tests/regression/invalid5.gml | 4 + tests/regression/invalid5.graphml | 30 + tests/regression/invalid6.gml | 1 + tests/regression/invalid6.graphml | 1 + tests/regression/invalid_pajek1.net | 1 + tests/regression/invalid_pajek2.net | 3 + tests/regression/invalid_pajek3.net | 2 + tests/unit/2wheap.c | 167 + tests/unit/VF2-compat.c | 257 + tests/unit/adj.c | 305 + tests/unit/adj.out | 285 + tests/unit/adjlist.c | 348 + tests/unit/adjlist.out | 692 + tests/unit/ak-4102.max | 24623 ++++++ tests/unit/all_almost_e.c | 87 + tests/unit/all_shortest_paths.c | 162 + tests/unit/all_shortest_paths.out | 125 + tests/unit/assortativity.c | 357 + tests/unit/assortativity.out | 33 + tests/unit/beta_skeletons.c | 182 + tests/unit/beta_skeletons.out | 338 + tests/unit/bfs.c | 149 + tests/unit/bfs.out | 13 + tests/unit/bfs_simple.c | 77 + tests/unit/bfs_simple.out | 9 + tests/unit/bipartite.net | 26 + tests/unit/bitset.c | 403 + tests/unit/bitset.out | 137 + tests/unit/bliss_automorphisms.c | 57 + tests/unit/bliss_automorphisms.out | 8 + tests/unit/cattributes5.c | 97 + tests/unit/cattributes5.out | 243 + tests/unit/cattributes6.c | 322 + tests/unit/cattributes6.out | 152 + tests/unit/centralization.c | 73 + tests/unit/centralization.out | 5 + tests/unit/cmp_epsilon.c | 77 + tests/unit/coloring.c | 229 + tests/unit/community_indexing.c | 109 + tests/unit/community_label_propagation.c | 157 + tests/unit/community_label_propagation2.c | 86 + tests/unit/community_label_propagation2.out | 3 + tests/unit/community_label_propagation3.c | 73 + tests/unit/community_leiden.c | 313 + tests/unit/community_leiden.out | 60 + tests/unit/community_walktrap.c | 187 + tests/unit/community_walktrap.out | 43 + tests/unit/components.c | 97 + tests/unit/components.out | 90 + tests/unit/constructor-failure.c | 202 + tests/unit/coreness.c | 224 + tests/unit/coreness.out | 24 + tests/unit/cutheap.c | 49 + tests/unit/cutheap.out | 1 + tests/unit/cycle_bases.c | 184 + tests/unit/cycle_bases.out | 78 + tests/unit/d_indheap.c | 102 + tests/unit/d_indheap.out | 12 + tests/unit/dgemv.c | 103 + tests/unit/dgemv.out | 23 + tests/unit/edge_selectors.c | 143 + tests/unit/edge_selectors.out | 36 + tests/unit/efficiency.c | 152 + tests/unit/efficiency.out | 90 + tests/unit/eigen_stress.c | 106 + tests/unit/empty | 0 tests/unit/erdos_renyi_game_gnm.c | 328 + tests/unit/erdos_renyi_game_gnp.c | 260 + tests/unit/error_macros.c | 71 + tests/unit/error_macros.out | 4 + tests/unit/expand_path_to_pairs.c | 61 + tests/unit/expand_path_to_pairs.out | 4 + tests/unit/fatal_handler.c | 39 + tests/unit/fatal_handler.out | 3 + tests/unit/foreign_empty.c | 91 + tests/unit/full.c | 67 + tests/unit/full.out | 326 + tests/unit/gen2wheap.c | 123 + tests/unit/gen2wheap.out | 37 + tests/unit/global_transitivity.c | 113 + tests/unit/global_transitivity.out | 17 + tests/unit/glpk_error.c | 72 + tests/unit/gml.c | 56 + tests/unit/gml.out | 79 + tests/unit/graph1.gml | 44 + tests/unit/graph2.gml | 11 + tests/unit/graph3.gml | 4 + tests/unit/graphlets.c | 149 + tests/unit/graphlets.out | 111 + tests/unit/graphml-default-attrs.xml | 25 + tests/unit/graphml-hsa05010.xml | 379 + tests/unit/graphml-lenient.xml | 10 + tests/unit/graphml-namespace.xml | 11 + tests/unit/graphml-whitespace.xml | 51 + tests/unit/graphml-yed.xml | 177 + tests/unit/harmonic_centrality.c | 138 + tests/unit/harmonic_centrality.out | 33 + tests/unit/heap.c | 145 + tests/unit/heap.out | 16 + tests/unit/hub_and_authority.c | 148 + tests/unit/hub_and_authority.out | 96 + tests/unit/igraph_add_edges.c | 73 + tests/unit/igraph_add_edges.out | 3 + tests/unit/igraph_add_vertices.c | 60 + tests/unit/igraph_adhesion.c | 49 + tests/unit/igraph_adjacency.c | 257 + tests/unit/igraph_adjacency.out | 468 + .../igraph_adjacency_spectral_embedding.c | 72 + .../igraph_adjacency_spectral_embedding.out | 29 + tests/unit/igraph_adjlist_init_complementer.c | 75 + .../unit/igraph_adjlist_init_complementer.out | 63 + tests/unit/igraph_adjlist_simplify.c | 47 + tests/unit/igraph_adjlist_simplify.out | 19 + tests/unit/igraph_all_st_cuts.c | 403 + tests/unit/igraph_all_st_cuts.out | 98 + tests/unit/igraph_all_st_mincuts.c | 191 + tests/unit/igraph_all_st_mincuts.out | 132 + tests/unit/igraph_almost_equals.c | 191 + tests/unit/igraph_are_adjacent.c | 68 + tests/unit/igraph_arpack_rnsolve.c | 232 + tests/unit/igraph_arpack_rnsolve.out | 41 + tests/unit/igraph_arpack_unpack_complex.c | 103 + tests/unit/igraph_arpack_unpack_complex.out | 125 + tests/unit/igraph_atlas.c | 30 + .../igraph_attribute_combination_remove.c | 71 + .../igraph_attribute_combination_remove.out | 18 + tests/unit/igraph_average_path_length.c | 80 + tests/unit/igraph_average_path_length.out | 35 + .../igraph_average_path_length_dijkstra.c | 99 + .../igraph_average_path_length_dijkstra.out | 34 + tests/unit/igraph_barabasi_aging_game.c | 146 + tests/unit/igraph_barabasi_aging_game.out | 72 + tests/unit/igraph_barabasi_game.c | 70 + tests/unit/igraph_betweenness.c | 317 + tests/unit/igraph_betweenness.out | 111 + tests/unit/igraph_betweenness_subset.c | 361 + tests/unit/igraph_betweenness_subset.out | 72 + tests/unit/igraph_biadjacency.c | 129 + tests/unit/igraph_biadjacency.out | 170 + tests/unit/igraph_biconnected_components.c | 63 + tests/unit/igraph_biconnected_components.out | 23 + tests/unit/igraph_bipartite_create.c | 40 + tests/unit/igraph_bipartite_game.c | 346 + tests/unit/igraph_bipartite_projection.c | 182 + tests/unit/igraph_blas_dgemm.c | 81 + tests/unit/igraph_blas_dgemm.out | 26 + tests/unit/igraph_bridges.c | 59 + tests/unit/igraph_bridges.out | 6 + tests/unit/igraph_callaway_traits_game.c | 99 + tests/unit/igraph_chung_lu_game.c | 178 + tests/unit/igraph_circulant.c | 178 + tests/unit/igraph_cited_type_game.c | 112 + tests/unit/igraph_cited_type_game.out | 40 + tests/unit/igraph_citing_cited_type_game.c | 87 + tests/unit/igraph_citing_cited_type_game.out | 14 + tests/unit/igraph_clique_size_hist.c | 57 + tests/unit/igraph_clique_size_hist.out | 10 + tests/unit/igraph_closeness.c | 345 + tests/unit/igraph_closeness.out | 152 + tests/unit/igraph_cohesion.c | 49 + tests/unit/igraph_cohesive_blocks.c | 125 + tests/unit/igraph_cohesive_blocks.out | 46 + tests/unit/igraph_coloring.out | 94 + tests/unit/igraph_community_eb_get_merges.c | 127 + tests/unit/igraph_community_eb_get_merges.out | 47 + .../unit/igraph_community_edge_betweenness.c | 208 + .../igraph_community_edge_betweenness.out | 4 + tests/unit/igraph_community_fastgreedy.c | 218 + tests/unit/igraph_community_fastgreedy.out | 35 + .../unit/igraph_community_fluid_communities.c | 90 + .../igraph_community_fluid_communities.out | 0 tests/unit/igraph_community_infomap.c | 313 + tests/unit/igraph_community_infomap.out | 55 + .../igraph_community_leading_eigenvector2.c | 98 + .../igraph_community_leading_eigenvector2.out | 7 + .../igraph_community_optimal_modularity.c | 169 + tests/unit/igraph_community_voronoi.c | 120 + tests/unit/igraph_community_voronoi.out | 27 + tests/unit/igraph_compare_communities.c | 101 + tests/unit/igraph_compare_communities.out | 27 + tests/unit/igraph_complex.c | 183 + tests/unit/igraph_connect_neighborhood.c | 74 + tests/unit/igraph_connect_neighborhood.out | 124 + tests/unit/igraph_constraint.c | 104 + tests/unit/igraph_constraint.out | 27 + tests/unit/igraph_contract_vertices.c | 117 + tests/unit/igraph_contract_vertices.out | 73 + tests/unit/igraph_convergence_degree.c | 56 + tests/unit/igraph_convergence_degree.out | 2 + tests/unit/igraph_convex_hull_2d.c | 158 + tests/unit/igraph_convex_hull_2d.out | 39 + tests/unit/igraph_correlated_game.c | 47 + tests/unit/igraph_correlated_pair_game.c | 46 + tests/unit/igraph_correlated_pair_game.out | 8 + tests/unit/igraph_count_adjacent_triangles.c | 85 + .../unit/igraph_count_adjacent_triangles.out | 15 + tests/unit/igraph_count_multiple.c | 37 + tests/unit/igraph_count_multiple.out | 2 + tests/unit/igraph_create.c | 40 + tests/unit/igraph_decompose_strong.c | 62 + tests/unit/igraph_decompose_strong.out | 21 + tests/unit/igraph_degree.c | 130 + tests/unit/igraph_degree.out | 9 + tests/unit/igraph_degree_sequence_game.c | 265 + tests/unit/igraph_delaunay_graph.c | 217 + tests/unit/igraph_delaunay_graph.out | 713 + tests/unit/igraph_delete_edges.c | 56 + tests/unit/igraph_delete_vertices.c | 32 + tests/unit/igraph_density.c | 214 + tests/unit/igraph_density.out | 29 + tests/unit/igraph_diameter.c | 90 + tests/unit/igraph_diameter.out | 17 + tests/unit/igraph_diameter_dijkstra.c | 112 + tests/unit/igraph_diameter_dijkstra.out | 79 + tests/unit/igraph_disjoint_union.c | 68 + tests/unit/igraph_disjoint_union.out | 42 + tests/unit/igraph_distances_floyd_warshall.c | 119 + .../unit/igraph_distances_floyd_warshall.out | 77 + .../igraph_distances_floyd_warshall_speedup.c | 126 + ...graph_distances_floyd_warshall_speedup.out | 77 + tests/unit/igraph_distances_johnson.c | 104 + tests/unit/igraph_distances_johnson.out | 36 + tests/unit/igraph_diversity.c | 86 + tests/unit/igraph_diversity.out | 8 + tests/unit/igraph_dominator_tree.c | 132 + tests/unit/igraph_dominator_tree.out | 40 + tests/unit/igraph_dot_product_game.c | 54 + tests/unit/igraph_dot_product_game.out | 16 + tests/unit/igraph_dyad_census.c | 63 + tests/unit/igraph_dyad_census.out | 15 + tests/unit/igraph_ecc.c | 219 + tests/unit/igraph_ecc.out | 79 + tests/unit/igraph_eccentricity.c | 86 + tests/unit/igraph_eccentricity.out | 23 + tests/unit/igraph_eccentricity_dijkstra.c | 117 + tests/unit/igraph_eccentricity_dijkstra.out | 33 + tests/unit/igraph_edge_betweenness.c | 278 + tests/unit/igraph_edge_betweenness.out | 36 + tests/unit/igraph_edge_betweenness_subset.c | 313 + tests/unit/igraph_edge_betweenness_subset.out | 27 + tests/unit/igraph_edge_disjoint_paths.c | 52 + tests/unit/igraph_edges.c | 60 + tests/unit/igraph_edges.out | 9 + tests/unit/igraph_eigen_matrix.c | 133 + tests/unit/igraph_eigen_matrix.out | 11 + tests/unit/igraph_eigen_matrix2.c | 116 + tests/unit/igraph_eigen_matrix2.out | 11 + tests/unit/igraph_eigen_matrix3.c | 97 + tests/unit/igraph_eigen_matrix3.out | 0 tests/unit/igraph_eigen_matrix4.c | 99 + tests/unit/igraph_eigen_matrix4.out | 2 + tests/unit/igraph_eigen_matrix_symmetric.c | 126 + tests/unit/igraph_eigen_matrix_symmetric.out | 4 + .../igraph_eigen_matrix_symmetric_arpack.c | 127 + .../igraph_eigen_matrix_symmetric_arpack.out | 4 + tests/unit/igraph_eigenvector_centrality.c | 165 + tests/unit/igraph_eigenvector_centrality.out | 86 + tests/unit/igraph_empty.c | 60 + tests/unit/igraph_es_all_between.c | 201 + tests/unit/igraph_es_path.c | 74 + tests/unit/igraph_establishment_game.c | 88 + tests/unit/igraph_eulerian_cycle.c | 183 + tests/unit/igraph_eulerian_cycle.out | 32 + tests/unit/igraph_eulerian_path.c | 443 + tests/unit/igraph_eulerian_path.out | 95 + tests/unit/igraph_extended_chordal_ring.c | 74 + tests/unit/igraph_feedback_arc_set.c | 255 + tests/unit/igraph_feedback_vertex_set.c | 210 + tests/unit/igraph_find_cycle.c | 83 + tests/unit/igraph_find_cycle.out | 29 + tests/unit/igraph_forest_fire_game.c | 67 + tests/unit/igraph_forest_fire_game.out | 29 + tests/unit/igraph_from_prufer.c | 42 + tests/unit/igraph_from_prufer.out | 25 + tests/unit/igraph_full_bipartite.c | 175 + tests/unit/igraph_full_bipartite.out | 11 + tests/unit/igraph_full_citation.c | 57 + tests/unit/igraph_full_multipartite.c | 158 + tests/unit/igraph_full_multipartite.out | 197 + tests/unit/igraph_generalized_petersen.c | 55 + tests/unit/igraph_get_adjacency.c | 200 + tests/unit/igraph_get_adjacency.out | 168 + tests/unit/igraph_get_adjacency_sparse.c | 211 + tests/unit/igraph_get_adjacency_sparse.out | 168 + .../igraph_get_all_shortest_paths_dijkstra.c | 184 + ...igraph_get_all_shortest_paths_dijkstra.out | 37 + tests/unit/igraph_get_all_simple_paths.c | 70 + tests/unit/igraph_get_all_simple_paths.out | 83 + tests/unit/igraph_get_biadjacency.c | 89 + tests/unit/igraph_get_biadjacency.out | 37 + tests/unit/igraph_get_eid.c | 41 + tests/unit/igraph_get_isomorphisms_vf2.c | 137 + tests/unit/igraph_get_isomorphisms_vf2.out | 94 + tests/unit/igraph_get_k_shortest_paths.c | 159 + tests/unit/igraph_get_k_shortest_paths.out | 219 + tests/unit/igraph_get_laplacian.c | 115 + tests/unit/igraph_get_laplacian.out | 113 + tests/unit/igraph_get_shortest_path_astar.c | 233 + tests/unit/igraph_get_shortest_path_astar.out | 18 + .../igraph_get_shortest_path_bellman_ford.c | 61 + .../igraph_get_shortest_path_bellman_ford.out | 5 + tests/unit/igraph_get_shortest_paths2.c | 72 + tests/unit/igraph_get_shortest_paths2.out | 16 + .../igraph_get_shortest_paths_bellman_ford.c | 214 + ...igraph_get_shortest_paths_bellman_ford.out | 32 + .../unit/igraph_get_shortest_paths_dijkstra.c | 171 + .../igraph_get_shortest_paths_dijkstra.out | 12 + tests/unit/igraph_get_stochastic.c | 98 + tests/unit/igraph_get_stochastic.out | 64 + tests/unit/igraph_get_stochastic_sparse.c | 50 + tests/unit/igraph_get_stochastic_sparse.out | 19 + tests/unit/igraph_get_subisomorphisms_vf2.c | 151 + tests/unit/igraph_get_subisomorphisms_vf2.out | 96 + tests/unit/igraph_gomory_hu_tree.c | 221 + tests/unit/igraph_graph_center.c | 277 + tests/unit/igraph_graph_center.out | 99 + tests/unit/igraph_graph_power.c | 120 + tests/unit/igraph_graph_power.out | 210 + tests/unit/igraph_grg_game.c | 42 + tests/unit/igraph_growing_random_game.c | 66 + tests/unit/igraph_has_mutual.c | 82 + tests/unit/igraph_hexagonal_lattice.c | 102 + tests/unit/igraph_hexagonal_lattice.out | 374 + tests/unit/igraph_hrg.c | 103 + tests/unit/igraph_hrg2.c | 98 + tests/unit/igraph_hrg3.c | 99 + tests/unit/igraph_hrg_create.c | 79 + tests/unit/igraph_hrg_create.out | 17 + tests/unit/igraph_hsbm_game.c | 74 + tests/unit/igraph_hsbm_game.out | 79 + tests/unit/igraph_hsbm_list_game.c | 95 + tests/unit/igraph_hsbm_list_game.out | 79 + tests/unit/igraph_i_layout_sphere.c | 82 + tests/unit/igraph_i_umap_fit_ab.c | 48 + tests/unit/igraph_i_umap_fit_ab.out | 8 + tests/unit/igraph_incident.c | 108 + tests/unit/igraph_incident.out | 34 + tests/unit/igraph_induced_subgraph.c | 111 + tests/unit/igraph_induced_subgraph.out | 43 + tests/unit/igraph_induced_subgraph_edges.c | 54 + tests/unit/igraph_induced_subgraph_edges.out | 4 + tests/unit/igraph_induced_subgraph_map.c | 65 + tests/unit/igraph_induced_subgraph_map.out | 20 + tests/unit/igraph_intersection.c | 62 + tests/unit/igraph_intersection.out | 29 + tests/unit/igraph_is_acyclic.c | 111 + tests/unit/igraph_is_biconnected.c | 135 + tests/unit/igraph_is_bigraphical.c | 54 + tests/unit/igraph_is_bigraphical.out | 32 + tests/unit/igraph_is_bipartite.c | 79 + tests/unit/igraph_is_chordal.c | 99 + tests/unit/igraph_is_chordal.out | 129 + tests/unit/igraph_is_clique.c | 145 + tests/unit/igraph_is_complete.c | 207 + tests/unit/igraph_is_connected.c | 102 + tests/unit/igraph_is_dag.c | 126 + tests/unit/igraph_is_eulerian.c | 207 + tests/unit/igraph_is_eulerian.out | 34 + tests/unit/igraph_is_forest.c | 145 + tests/unit/igraph_is_forest.out | 36 + tests/unit/igraph_is_forest2.c | 105 + tests/unit/igraph_is_graphical.c | 284 + tests/unit/igraph_is_graphical.out | 168 + tests/unit/igraph_is_mutual.c | 69 + tests/unit/igraph_is_mutual.out | 16 + tests/unit/igraph_is_same_graph.c | 84 + tests/unit/igraph_is_separator.c | 293 + tests/unit/igraph_is_tree.c | 140 + tests/unit/igraph_isomorphic.c | 72 + tests/unit/igraph_isomorphic.out | 7 + tests/unit/igraph_isomorphic_bliss.c | 149 + tests/unit/igraph_isomorphic_bliss.out | 42 + tests/unit/igraph_isomorphic_vf2.c | 229 + tests/unit/igraph_join.c | 77 + tests/unit/igraph_join.out | 54 + tests/unit/igraph_joint_degree_distribution.c | 315 + .../unit/igraph_joint_degree_distribution.out | 26 + tests/unit/igraph_joint_type_distribution.c | 163 + tests/unit/igraph_joint_type_distribution.out | 26 + tests/unit/igraph_k_regular_game.c | 199 + tests/unit/igraph_k_regular_game.out | 16 + tests/unit/igraph_k_shortest_paths.out | 0 tests/unit/igraph_kautz.c | 61 + tests/unit/igraph_lapack_dgeev.c | 224 + tests/unit/igraph_lapack_dgeevx.c | 206 + tests/unit/igraph_lapack_dgehrd.c | 80 + tests/unit/igraph_lapack_dgehrd.out | 0 tests/unit/igraph_lapack_dgetrf.c | 74 + tests/unit/igraph_lapack_dgetrf.out | 63 + tests/unit/igraph_lapack_dgetrs.c | 122 + tests/unit/igraph_lapack_dgetrs.out | 86 + tests/unit/igraph_lapack_dsyevr.c | 215 + tests/unit/igraph_lastcit_game.c | 86 + tests/unit/igraph_lastcit_game.out | 24 + tests/unit/igraph_layout_align.c | 194 + tests/unit/igraph_layout_bipartite.c | 110 + tests/unit/igraph_layout_bipartite.out | 42 + tests/unit/igraph_layout_davidson_harel.c | 180 + tests/unit/igraph_layout_davidson_harel.out | 3 + tests/unit/igraph_layout_drl.c | 99 + tests/unit/igraph_layout_drl_3d.c | 64 + tests/unit/igraph_layout_drl_3d.out | 2 + .../unit/igraph_layout_fruchterman_reingold.c | 128 + .../igraph_layout_fruchterman_reingold.out | 14 + .../igraph_layout_fruchterman_reingold_3d.c | 130 + .../igraph_layout_fruchterman_reingold_3d.out | 14 + tests/unit/igraph_layout_gem.c | 120 + tests/unit/igraph_layout_gem.out | 13 + tests/unit/igraph_layout_graphopt.c | 98 + tests/unit/igraph_layout_graphopt.out | 17 + tests/unit/igraph_layout_grid.c | 71 + tests/unit/igraph_layout_grid.out | 95 + tests/unit/igraph_layout_kamada_kawai.c | 249 + tests/unit/igraph_layout_kamada_kawai.out | 20 + tests/unit/igraph_layout_lgl.c | 108 + tests/unit/igraph_layout_lgl.out | 4 + tests/unit/igraph_layout_mds.c | 96 + tests/unit/igraph_layout_mds.out | 11 + tests/unit/igraph_layout_merge.c | 82 + tests/unit/igraph_layout_merge2.c | 86 + tests/unit/igraph_layout_merge2.out | 13 + tests/unit/igraph_layout_merge3.c | 45 + tests/unit/igraph_layout_random_3d.c | 64 + .../igraph_layout_reingold_tilford_circular.c | 116 + ...graph_layout_reingold_tilford_circular.out | 39 + .../igraph_layout_reingold_tilford_extended.c | 55 + ...igraph_layout_reingold_tilford_extended.in | 4 + tests/unit/igraph_layout_sphere.c | 47 + tests/unit/igraph_layout_sphere.out | 22 + tests/unit/igraph_layout_star.c | 70 + tests/unit/igraph_layout_star.out | 24 + tests/unit/igraph_layout_sugiyama.c | 153 + tests/unit/igraph_layout_sugiyama.out | 169 + tests/unit/igraph_layout_umap.c | 337 + tests/unit/igraph_layout_umap.out | 26 + tests/unit/igraph_lcf.c | 69 + .../unit/igraph_le_community_to_membership.c | 111 + .../igraph_le_community_to_membership.out | 17 + tests/unit/igraph_linegraph.c | 81 + tests/unit/igraph_list_triangles.c | 61 + tests/unit/igraph_list_triangles.out | 12 + tests/unit/igraph_local_scan_k_ecount.c | 107 + tests/unit/igraph_local_scan_k_ecount.out | 40 + tests/unit/igraph_local_scan_k_ecount_them.c | 135 + .../unit/igraph_local_scan_k_ecount_them.out | 58 + tests/unit/igraph_local_scan_subset_ecount.c | 101 + .../unit/igraph_local_scan_subset_ecount.out | 24 + tests/unit/igraph_local_transitivity.c | 245 + tests/unit/igraph_local_transitivity.out | 37 + tests/unit/igraph_maxflow.c | 240 + tests/unit/igraph_maxflow.out | 5 + tests/unit/igraph_maximal_cliques.c | 159 + tests/unit/igraph_maximal_cliques.out | 15 + tests/unit/igraph_maximal_cliques2.c | 82 + tests/unit/igraph_maximal_cliques2.out | 20 + tests/unit/igraph_maximal_cliques3.c | 60 + tests/unit/igraph_maximal_cliques3.out | 6 + tests/unit/igraph_maximal_cliques4.c | 92 + tests/unit/igraph_maximal_cliques4.out | 108 + tests/unit/igraph_maximal_cliques_file.c | 45 + tests/unit/igraph_maximal_cliques_file.out | 13 + .../unit/igraph_maximum_bipartite_matching.c | 180 + tests/unit/igraph_mean_degree.c | 59 + tests/unit/igraph_minimum_size_separators.c | 173 + tests/unit/igraph_minimum_size_separators.out | 55 + tests/unit/igraph_modularity.c | 128 + tests/unit/igraph_modularity.out | 9 + tests/unit/igraph_modularity_matrix.c | 133 + tests/unit/igraph_modularity_matrix.out | 53 + tests/unit/igraph_motifs_randesu.c | 68 + tests/unit/igraph_motifs_randesu.out | 47 + tests/unit/igraph_motifs_randesu_estimate.c | 91 + tests/unit/igraph_motifs_randesu_estimate.out | 27 + tests/unit/igraph_motifs_randesu_no.c | 73 + tests/unit/igraph_motifs_randesu_no.out | 19 + tests/unit/igraph_nearest_neighbor_graph.c | 278 + tests/unit/igraph_nearest_neighbor_graph.out | 809 + tests/unit/igraph_neighborhood.c | 95 + tests/unit/igraph_neighborhood.out | 85 + tests/unit/igraph_neighborhood_graphs.c | 112 + tests/unit/igraph_neighborhood_graphs.out | 246 + tests/unit/igraph_neighborhood_size.c | 97 + tests/unit/igraph_neighborhood_size.out | 21 + tests/unit/igraph_neighbors.c | 182 + tests/unit/igraph_neighbors.out | 66 + tests/unit/igraph_pagerank.c | 400 + tests/unit/igraph_pagerank.out | 52 + tests/unit/igraph_path_length_hist.c | 68 + tests/unit/igraph_path_length_hist.out | 24 + tests/unit/igraph_perfect.c | 134 + tests/unit/igraph_permute_vertices.c | 66 + tests/unit/igraph_permute_vertices.out | 23 + tests/unit/igraph_power_law_fit.c | 330 + tests/unit/igraph_power_law_fit.out | 37 + tests/unit/igraph_preference_game.c | 250 + tests/unit/igraph_product.c | 336 + tests/unit/igraph_progress_handler_stderr.c | 27 + tests/unit/igraph_pseudo_diameter.c | 131 + tests/unit/igraph_pseudo_diameter.out | 108 + tests/unit/igraph_pseudo_diameter_dijkstra.c | 153 + .../unit/igraph_pseudo_diameter_dijkstra.out | 128 + tests/unit/igraph_psumtree.c | 213 + tests/unit/igraph_qsort.c | 61 + tests/unit/igraph_qsort.out | 1 + tests/unit/igraph_qsort_r.c | 71 + tests/unit/igraph_qsort_r.out | 1 + tests/unit/igraph_random_sample.c | 65 + tests/unit/igraph_random_walk.c | 235 + tests/unit/igraph_random_walk.out | 42 + tests/unit/igraph_read_graph_graphdb.c | 57 + tests/unit/igraph_read_graph_graphdb.out | 8 + tests/unit/igraph_read_graph_graphml.c | 212 + tests/unit/igraph_read_graph_graphml.out | 67 + ...igraph_realize_bipartite_degree_sequence.c | 323 + ...raph_realize_bipartite_degree_sequence.out | 148 + tests/unit/igraph_realize_degree_sequence.c | 138 + tests/unit/igraph_realize_degree_sequence.out | 974 + tests/unit/igraph_recent_degree_aging_game.c | 93 + .../unit/igraph_recent_degree_aging_game.out | 43 + tests/unit/igraph_recent_degree_game.c | 73 + tests/unit/igraph_recent_degree_game.out | 33 + tests/unit/igraph_reindex_membership.c | 86 + tests/unit/igraph_residual_graph.c | 62 + tests/unit/igraph_reverse_edges.c | 46 + tests/unit/igraph_rewire.c | 81 + tests/unit/igraph_rewire_directed_edges.c | 88 + tests/unit/igraph_rewire_directed_edges.out | 14 + tests/unit/igraph_rng_get_integer.c | 208 + tests/unit/igraph_rng_get_integer.out | 33 + tests/unit/igraph_rng_sample_dirichlet.c | 83 + tests/unit/igraph_rng_sample_dirichlet.out | 10 + tests/unit/igraph_rng_sample_sphere.c | 66 + tests/unit/igraph_rooted_product.c | 115 + tests/unit/igraph_running_mean.c | 60 + tests/unit/igraph_running_mean.out | 8 + tests/unit/igraph_sbm_game.c | 169 + tests/unit/igraph_sbm_game.out | 68 + tests/unit/igraph_set_progress_handler.c | 46 + tests/unit/igraph_set_progress_handler.out | 7 + tests/unit/igraph_similarity.c | 208 + tests/unit/igraph_similarity.out | 40 + tests/unit/igraph_simple_cycles.c | 472 + tests/unit/igraph_simple_cycles.out | 1889 + ...graph_simple_interconnected_islands_game.c | 93 + ...aph_simple_interconnected_islands_game.out | 63 + tests/unit/igraph_sir.c | 96 + tests/unit/igraph_sir.out | 40 + tests/unit/igraph_solve_lsap.c | 67 + tests/unit/igraph_solve_lsap.out | 9 + tests/unit/igraph_spanner.c | 203 + tests/unit/igraph_sparsemat2.c | 147 + tests/unit/igraph_sparsemat2.out | 0 tests/unit/igraph_sparsemat5.c | 400 + tests/unit/igraph_sparsemat5.out | 159 + tests/unit/igraph_sparsemat9.c | 87 + tests/unit/igraph_sparsemat_droptol.c | 63 + tests/unit/igraph_sparsemat_droptol.out | 16 + tests/unit/igraph_sparsemat_fkeep.c | 80 + tests/unit/igraph_sparsemat_fkeep.out | 15 + .../igraph_sparsemat_getelements_sorted.c | 88 + .../igraph_sparsemat_getelements_sorted.out | 19 + tests/unit/igraph_sparsemat_is_symmetric.c | 71 + tests/unit/igraph_sparsemat_iterator_idx.c | 56 + tests/unit/igraph_sparsemat_minmax.c | 247 + tests/unit/igraph_sparsemat_minmax.out | 32 + tests/unit/igraph_sparsemat_nonzero_storage.c | 69 + tests/unit/igraph_sparsemat_normalize.c | 106 + tests/unit/igraph_sparsemat_normalize.out | 41 + tests/unit/igraph_sparsemat_view.out | 6 + tests/unit/igraph_sparsemat_which_minmax.c | 282 + tests/unit/igraph_sparsemat_which_minmax.out | 23 + tests/unit/igraph_spatial_edge_lengths.c | 64 + tests/unit/igraph_spatial_edge_lengths.out | 7 + tests/unit/igraph_split_join_distance.c | 72 + tests/unit/igraph_split_join_distance.out | 12 + tests/unit/igraph_square_lattice.c | 231 + tests/unit/igraph_st_edge_connectivity.c | 39 + tests/unit/igraph_st_mincut.c | 70 + tests/unit/igraph_st_mincut.out | 6 + tests/unit/igraph_st_mincut_value.c | 42 + tests/unit/igraph_st_vertex_connectivity.c | 86 + tests/unit/igraph_st_vertex_connectivity.out | 12 + tests/unit/igraph_static_power_law_game.c | 113 + tests/unit/igraph_static_power_law_game.out | 15 + tests/unit/igraph_strvector.c | 193 + tests/unit/igraph_strvector.out | 106 + tests/unit/igraph_subcomponent.c | 81 + tests/unit/igraph_subcomponent.out | 57 + tests/unit/igraph_subisomorphic.c | 64 + tests/unit/igraph_subisomorphic_lad.c | 195 + tests/unit/igraph_to_directed.c | 43 + tests/unit/igraph_to_directed.out | 61 + tests/unit/igraph_to_prufer.c | 166 + tests/unit/igraph_transitive_closure.c | 88 + tests/unit/igraph_transitive_closure.out | 92 + .../igraph_transitivity_avglocal_undirected.c | 77 + ...graph_transitivity_avglocal_undirected.out | 14 + tests/unit/igraph_transitivity_barrat.c | 132 + tests/unit/igraph_transitivity_barrat.out | 45 + tests/unit/igraph_tree_from_parent_vector.c | 77 + tests/unit/igraph_tree_from_parent_vector.out | 50 + tests/unit/igraph_triangular_lattice.c | 102 + tests/unit/igraph_triangular_lattice.out | 228 + tests/unit/igraph_trussness.c | 112 + tests/unit/igraph_trussness.out | 69 + tests/unit/igraph_turan.c | 167 + tests/unit/igraph_turan.out | 156 + tests/unit/igraph_unfold_tree.c | 82 + tests/unit/igraph_unfold_tree.out | 80 + tests/unit/igraph_union.c | 155 + tests/unit/igraph_union.out | 133 + tests/unit/igraph_vector_floor.c | 41 + tests/unit/igraph_vector_floor.out | 4 + tests/unit/igraph_vector_lex_cmp.c | 54 + tests/unit/igraph_vector_lex_cmp.out | 19 + tests/unit/igraph_vertex_disjoint_paths.c | 53 + tests/unit/igraph_voronoi.c | 134 + tests/unit/igraph_voronoi.out | 48 + tests/unit/igraph_weighted_adjacency.c | 331 + tests/unit/igraph_weighted_adjacency.out | 376 + tests/unit/igraph_weighted_biadjacency.c | 108 + tests/unit/igraph_weighted_biadjacency.out | 75 + tests/unit/igraph_weighted_cliques.c | 249 + tests/unit/igraph_weighted_cliques.out | 204 + tests/unit/igraph_wheel.c | 56 + tests/unit/igraph_wheel.out | 104 + tests/unit/igraph_write_graph_dimacs_flow.c | 69 + tests/unit/igraph_write_graph_dimacs_flow.out | 19 + tests/unit/igraph_write_graph_dot.c | 56 + tests/unit/igraph_write_graph_dot.out | 74 + tests/unit/igraph_write_graph_leda.c | 112 + tests/unit/igraph_write_graph_leda.out | 133 + tests/unit/inclist.c | 208 + tests/unit/inclist.out | 287 + tests/unit/input.dl | 104 + tests/unit/is_coloring.c | 294 + tests/unit/isoclasses.c | 67 + tests/unit/isoclasses.out | 4 + tests/unit/isoclasses2.c | 180 + tests/unit/isomorphism_test.c | 237 + tests/unit/isomorphism_test.out | 0 tests/unit/jdm.c | 322 + tests/unit/jdm.out | 139 + tests/unit/kary_tree.c | 57 + tests/unit/kary_tree.out | 61 + tests/unit/knn.c | 198 + tests/unit/knn.out | 61 + tests/unit/levc-stress.c | 70 + tests/unit/lineendings.c | 70 + tests/unit/lineendings.out | 44 + tests/unit/links.net | 16 + tests/unit/marked_queue.c | 65 + tests/unit/matrix.c | 177 + tests/unit/matrix.out | 44 + tests/unit/matrix2.c | 344 + tests/unit/matrix2.out | 167 + tests/unit/matrix3.c | 47 + tests/unit/matrix_complex.c | 93 + tests/unit/matrix_complex.out | 19 + tests/unit/max_results.c | 240 + tests/unit/maximal_cliques_callback.c | 106 + tests/unit/maximal_cliques_hist.c | 42 + tests/unit/maximal_cliques_hist.out | 1 + tests/unit/minimum_spanning_tree.c | 88 + tests/unit/minimum_spanning_tree.out | 14 + tests/unit/mycielskian.c | 179 + tests/unit/mycielskian.out | 111 + tests/unit/ncol.c | 73 + tests/unit/ncol.out | 11 + tests/unit/null_communities.c | 204 + tests/unit/overflow.c | 73 + tests/unit/pajek.c | 105 + tests/unit/pajek1.net | 21 + tests/unit/pajek2.c | 54 + tests/unit/pajek2.net | 1 + tests/unit/pajek2.out | 1 + tests/unit/pajek3.net | 21 + tests/unit/pajek4.net | 21 + tests/unit/pajek5.net | 21 + tests/unit/pajek6.net | 21 + tests/unit/pajek_arcslist.net | 40 + tests/unit/pajek_bip.net | 27 + tests/unit/pajek_bip2.net | 27 + tests/unit/pajek_bipartite.c | 46 + tests/unit/pajek_bipartite.out | 22 + tests/unit/pajek_bipartite2.c | 63 + tests/unit/pajek_bipartite2.out | 83 + tests/unit/pajek_edgeslist.net | 8 + tests/unit/pajek_signed.c | 47 + tests/unit/pajek_signed.net | 23 + tests/unit/pajek_signed.out | 56 + tests/unit/paths.c | 96 + tests/unit/paths.out | 23 + tests/unit/percolation.c | 364 + tests/unit/percolation.out | 49 + tests/unit/prop_caching.c | 168 + tests/unit/random_sampling.c | 306 + tests/unit/random_spanning_tree.c | 75 + tests/unit/reachability.c | 129 + tests/unit/reachability.out | 225 + tests/unit/rich_club.c | 288 + tests/unit/rich_club.out | 35 + tests/unit/ring.c | 51 + tests/unit/ring.out | 237 + ...g_init_destroy_max_bits_name_set_default.c | 132 + ...init_destroy_max_bits_name_set_default.out | 39 + tests/unit/rng_reproducibility.c | 38 + tests/unit/rng_reproducibility.out | 64 + tests/unit/set.c | 85 + tests/unit/set.out | 1 + tests/unit/si2_b06m_s20-bad1.A98 | Bin 0 -> 18 bytes tests/unit/si2_b06m_s20-bad2.A98 | Bin 0 -> 17 bytes tests/unit/si2_b06m_s20.A98 | Bin 0 -> 18 bytes tests/unit/simplify_and_colorize.c | 78 + tests/unit/simplify_and_colorize.out | 70 + tests/unit/single_target_shortest_path.c | 78 + tests/unit/single_target_shortest_path.out | 10 + tests/unit/spinglass.c | 344 + tests/unit/spinglass.out | 49 + tests/unit/stack.c | 93 + tests/unit/strvector_set_len_remove_print.c | 53 + tests/unit/strvector_set_len_remove_print.out | 8 + tests/unit/symmetric_tree.c | 111 + tests/unit/symmetric_tree.out | 220 + tests/unit/test.graphml | 55 + tests/unit/test_utilities.c | 646 + tests/unit/test_utilities.h | 184 + tests/unit/tls1.c | 52 + tests/unit/tls2.c | 243 + tests/unit/tls2.out | 23 + tests/unit/topological_sorting.c | 99 + tests/unit/topological_sorting.out | 2 + tests/unit/tree_game.c | 88 + tests/unit/triad_census.c | 49 + tests/unit/triad_census.out | 2 + tests/unit/trie.c | 137 + tests/unit/trie.out | 38 + tests/unit/utf8_with_bom.net | 18 + tests/unit/vector.c | 374 + tests/unit/vector.out | 64 + tests/unit/vector2.c | 161 + tests/unit/vector2.out | 28 + tests/unit/vector3.c | 52 + tests/unit/vector4.c | 96 + tests/unit/vector4.out | 22 + tests/unit/vector_list.c | 227 + tests/unit/vector_list.out | 157 + tests/unit/vector_ptr.c | 284 + tests/unit/vector_ptr_qsort_ind.out | 12 + tests/unit/vector_ptr_sort_ind.c | 80 + tests/unit/vector_sort_ind.c | 63 + tests/unit/vector_sort_ind.out | 4 + tests/unit/vertex_selectors.c | 116 + tests/unit/vertex_selectors.out | 39 + tests/unit/watts_strogatz_game.c | 125 + tests/unit/widest_paths.c | 579 + tests/unit/widest_paths.out | 281 + tests/unit/wikti_en_V_syn.elist | 8293 ++ tests/unit/zapsmall.c | 49 + tests/unit/zapsmall.out | 6 + tests/unit/zero_allocs.c | 62 + tools/removeexamples.py | 37 + tools/strip_licenses_from_examples.py | 67 + vendor/CMakeLists.txt | 9 + vendor/cs/CMakeLists.txt | 93 + vendor/cs/License.txt | 19 + vendor/cs/cs.h | 315 + vendor/cs/cs_add.c | 28 + vendor/cs/cs_amd.c | 364 + vendor/cs/cs_chol.c | 59 + vendor/cs/cs_cholsol.c | 26 + vendor/cs/cs_compress.c | 22 + vendor/cs/cs_counts.c | 61 + vendor/cs/cs_cumsum.c | 17 + vendor/cs/cs_dfs.c | 36 + vendor/cs/cs_dmperm.c | 144 + vendor/cs/cs_droptol.c | 9 + vendor/cs/cs_dropzeros.c | 9 + vendor/cs/cs_dupl.c | 34 + vendor/cs/cs_entry.c | 13 + vendor/cs/cs_ereach.c | 23 + vendor/cs/cs_etree.c | 30 + vendor/cs/cs_fkeep.c | 25 + vendor/cs/cs_gaxpy.c | 17 + vendor/cs/cs_happly.c | 19 + vendor/cs/cs_house.c | 30 + vendor/cs/cs_ipvec.c | 9 + vendor/cs/cs_leaf.c | 22 + vendor/cs/cs_load.c | 26 + vendor/cs/cs_lsolve.c | 18 + vendor/cs/cs_ltsolve.c | 18 + vendor/cs/cs_lu.c | 88 + vendor/cs/cs_lusol.c | 26 + vendor/cs/cs_malloc.c | 35 + vendor/cs/cs_maxtrans.c | 92 + vendor/cs/cs_multiply.c | 35 + vendor/cs/cs_norm.c | 16 + vendor/cs/cs_permute.c | 25 + vendor/cs/cs_pinv.c | 11 + vendor/cs/cs_post.c | 24 + vendor/cs/cs_print.c | 55 + vendor/cs/cs_pvec.c | 9 + vendor/cs/cs_qr.c | 74 + vendor/cs/cs_qrsol.c | 53 + vendor/cs/cs_randperm.c | 26 + vendor/cs/cs_reach.c | 19 + vendor/cs/cs_scatter.c | 22 + vendor/cs/cs_scc.c | 41 + vendor/cs/cs_schol.c | 26 + vendor/cs/cs_spsolve.c | 28 + vendor/cs/cs_sqr.c | 87 + vendor/cs/cs_symperm.c | 39 + vendor/cs/cs_tdfs.c | 24 + vendor/cs/cs_transpose.c | 25 + vendor/cs/cs_updown.c | 48 + vendor/cs/cs_usolve.c | 18 + vendor/cs/cs_util.c | 120 + vendor/cs/cs_utsolve.c | 18 + vendor/infomap/CITATION.cff | 16 + vendor/infomap/CMakeLists.txt | 48 + vendor/infomap/LICENSE_GPLv3.txt | 674 + vendor/infomap/README.rst | 162 + vendor/infomap/src/Infomap.h | 103 + vendor/infomap/src/core/BiasedMapEquation.cpp | 240 + vendor/infomap/src/core/BiasedMapEquation.h | 180 + vendor/infomap/src/core/FlowData.h | 165 + vendor/infomap/src/core/InfoEdge.cpp | 26 + vendor/infomap/src/core/InfoEdge.h | 47 + vendor/infomap/src/core/InfoNode.cpp | 405 + vendor/infomap/src/core/InfoNode.h | 374 + vendor/infomap/src/core/InfomapBase.cpp | 2182 + vendor/infomap/src/core/InfomapBase.h | 586 + vendor/infomap/src/core/InfomapConfig.h | 137 + vendor/infomap/src/core/InfomapOptimizer.h | 766 + .../infomap/src/core/InfomapOptimizerBase.h | 113 + vendor/infomap/src/core/MapEquation.h | 339 + vendor/infomap/src/core/MemMapEquation.cpp | 451 + vendor/infomap/src/core/MemMapEquation.h | 174 + vendor/infomap/src/core/MetaMapEquation.cpp | 271 + vendor/infomap/src/core/MetaMapEquation.h | 185 + vendor/infomap/src/core/StateNetwork.cpp | 329 + vendor/infomap/src/core/StateNetwork.h | 216 + .../src/core/iterators/InfomapIterator.cpp | 238 + .../src/core/iterators/InfomapIterator.h | 341 + .../infomap/src/core/iterators/IterWrapper.h | 32 + .../src/core/iterators/infomapIterators.h | 369 + .../src/core/iterators/treeIterators.h | 628 + vendor/infomap/src/io/ClusterMap.cpp | 189 + vendor/infomap/src/io/ClusterMap.h | 52 + vendor/infomap/src/io/Config.cpp | 264 + vendor/infomap/src/io/Config.h | 268 + vendor/infomap/src/io/Network.cpp | 990 + vendor/infomap/src/io/Network.h | 214 + vendor/infomap/src/io/Output.cpp | 629 + vendor/infomap/src/io/Output.h | 36 + vendor/infomap/src/io/ProgramInterface.cpp | 362 + vendor/infomap/src/io/ProgramInterface.h | 423 + vendor/infomap/src/io/SafeFile.h | 87 + vendor/infomap/src/utils/Date.h | 69 + vendor/infomap/src/utils/FileURI.cpp | 50 + vendor/infomap/src/utils/FileURI.h | 54 + vendor/infomap/src/utils/FlowCalculator.cpp | 898 + vendor/infomap/src/utils/FlowCalculator.h | 75 + vendor/infomap/src/utils/Log.cpp | 17 + vendor/infomap/src/utils/Log.h | 110 + vendor/infomap/src/utils/MetaCollection.h | 153 + vendor/infomap/src/utils/Random.h | 45 + vendor/infomap/src/utils/Stopwatch.h | 103 + vendor/infomap/src/utils/VectorMap.h | 82 + vendor/infomap/src/utils/convert.h | 216 + vendor/infomap/src/utils/infomath.h | 57 + vendor/infomap/src/version.h | 19 + vendor/nanoflann/nanoflann.hpp | 2813 + vendor/pcg/CMakeLists.txt | 21 + vendor/pcg/LICENSE.txt | 19 + vendor/pcg/pcg-advance-128.c | 61 + vendor/pcg/pcg-advance-64.c | 59 + vendor/pcg/pcg-output-128.c | 63 + vendor/pcg/pcg-output-32.c | 63 + vendor/pcg/pcg-output-64.c | 73 + vendor/pcg/pcg-rngs-128.c | 378 + vendor/pcg/pcg-rngs-64.c | 255 + vendor/pcg/pcg_variants.h | 2557 + vendor/qhull/CHANGES.md | 12 + vendor/qhull/CMakeLists.txt | 48 + vendor/qhull/COPYING.txt | 39 + vendor/qhull/README.txt | 720 + vendor/qhull/libqhull_r/accessors_r.c | 69 + vendor/qhull/libqhull_r/geom2_r.c | 2302 + vendor/qhull/libqhull_r/geom_r.c | 1284 + vendor/qhull/libqhull_r/geom_r.h | 189 + vendor/qhull/libqhull_r/global_r.c | 2268 + vendor/qhull/libqhull_r/io_r.c | 4128 + vendor/qhull/libqhull_r/io_r.h | 166 + vendor/qhull/libqhull_r/libqhull_r.c | 1754 + vendor/qhull/libqhull_r/libqhull_r.h | 1281 + vendor/qhull/libqhull_r/mem_r.c | 566 + vendor/qhull/libqhull_r/mem_r.h | 235 + vendor/qhull/libqhull_r/merge_r.c | 5589 ++ vendor/qhull/libqhull_r/merge_r.h | 238 + vendor/qhull/libqhull_r/poly2_r.c | 3959 + vendor/qhull/libqhull_r/poly_r.c | 1448 + vendor/qhull/libqhull_r/poly_r.h | 310 + vendor/qhull/libqhull_r/qhull_ra.h | 161 + vendor/qhull/libqhull_r/qset_r.c | 1383 + vendor/qhull/libqhull_r/qset_r.h | 515 + vendor/qhull/libqhull_r/random_r.c | 249 + vendor/qhull/libqhull_r/random_r.h | 41 + vendor/qhull/libqhull_r/rboxlib_r.c | 854 + vendor/qhull/libqhull_r/stat_r.c | 727 + vendor/qhull/libqhull_r/stat_r.h | 563 + vendor/qhull/libqhull_r/user_r.c | 617 + vendor/qhull/libqhull_r/user_r.h | 1061 + vendor/qhull/libqhull_r/usermem_r.c | 102 + vendor/qhull/libqhull_r/userprintf_r.c | 68 + vendor/qhull/libqhull_r/userprintf_rbox_r.c | 53 + 1921 files changed, 538896 insertions(+) create mode 100644 ACKNOWLEDGEMENTS.md create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTORS.md create mode 100644 CONTRIBUTORS.txt create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 IGRAPH_VERSION create mode 100644 INSTALL create mode 100644 NEWS create mode 100644 ONEWS create mode 100644 README.md create mode 100644 SUPPORT.md create mode 100644 VERSIONING.md create mode 100644 doc/CMakeLists.txt create mode 100644 doc/adjlist.xxml create mode 100644 doc/attributes.xxml create mode 100644 doc/basicigraph.xxml create mode 100644 doc/bibdatabase.xml create mode 100644 doc/bipartite.xxml create mode 100644 doc/bitset.xxml create mode 100644 doc/c-docbook.re create mode 100644 doc/cliques.xxml create mode 100644 doc/coloring.xxml create mode 100644 doc/community.xxml create mode 100644 doc/cycles.xxml create mode 100644 doc/docbook.outlang create mode 100755 doc/doxrox.py create mode 100644 doc/dqueue.xxml create mode 100644 doc/embedding.xxml create mode 100644 doc/error.xxml create mode 100644 doc/fdl.xml create mode 100644 doc/flows.xxml create mode 100644 doc/foreign.xxml create mode 100644 doc/games.xxml create mode 100644 doc/generators.xxml create mode 100644 doc/glossary.md create mode 100644 doc/glossary.xml create mode 100644 doc/gpl.xml create mode 100644 doc/graphlets.xxml create mode 100644 doc/gtk-doc.xsl create mode 100644 doc/heap.xxml create mode 100644 doc/hrg.xxml create mode 100644 doc/html/home.png create mode 100644 doc/html/left.png create mode 100644 doc/html/right.png create mode 100644 doc/html/style.css create mode 100644 doc/html/toggle.js create mode 100644 doc/html/up.png create mode 100644 doc/igraph-docs.info create mode 100644 doc/igraph-docs.xml create mode 100644 doc/igraph.3 create mode 100644 doc/igraphlogo/igraph-white.svg.gz create mode 100644 doc/igraphlogo/igraph.svg.gz create mode 100644 doc/igraphlogo/igraph2.svg.gz create mode 100644 doc/installation.xml create mode 100644 doc/introduction.xml create mode 100644 doc/isomorphism.xxml create mode 100644 doc/iterators.xxml create mode 100644 doc/layout.xxml create mode 100644 doc/licenses.xml create mode 100644 doc/licenses/Licence_CeCILL-B_V1-en.txt create mode 100644 doc/licenses/Licence_CeCILL-B_V1-fr.txt create mode 100644 doc/licenses/gpl-2.0.txt create mode 100644 doc/licenses/gpl-3.0.txt create mode 100644 doc/licenses/lgpl-2.1.txt create mode 100644 doc/licenses/lgpl-3.0.txt create mode 100644 doc/linalg.xxml create mode 100644 doc/matrix.xxml create mode 100644 doc/memory.xxml create mode 100644 doc/motifs.xxml create mode 100644 doc/nongraph.xxml create mode 100644 doc/operators.xxml create mode 100644 doc/pmt.xml create mode 100644 doc/processes.xxml create mode 100644 doc/progress.xxml create mode 100644 doc/psumtree.xxml create mode 100644 doc/random.xxml create mode 100644 doc/separators.xxml create mode 100644 doc/sparsemat.xxml create mode 100644 doc/spatial.xxml create mode 100644 doc/stack.xxml create mode 100644 doc/status.xxml create mode 100644 doc/structural.xxml create mode 100644 doc/strvector.xxml create mode 100644 doc/threading.xxml create mode 100644 doc/tutorial.xml create mode 100644 doc/vector.xxml create mode 100644 doc/vectorlist.xxml create mode 100644 doc/version-greater-or-equal.xsl create mode 100644 doc/visitors.xxml create mode 100644 etc/cmake/BuildType.cmake create mode 100644 etc/cmake/CheckTLSSupport.cmake create mode 100644 etc/cmake/CodeCoverage.cmake create mode 100644 etc/cmake/FindARPACK.cmake create mode 100644 etc/cmake/FindGLPK.cmake create mode 100644 etc/cmake/FindGMP.cmake create mode 100644 etc/cmake/FindPLFIT.cmake create mode 100644 etc/cmake/GetGitRevisionDescription.cmake create mode 100644 etc/cmake/GetGitRevisionDescription.cmake.in create mode 100644 etc/cmake/JoinPaths.cmake create mode 100644 etc/cmake/PadString.cmake create mode 100644 etc/cmake/PreventInSourceBuilds.cmake create mode 100644 etc/cmake/UseCCacheWhenInstalled.cmake create mode 100644 etc/cmake/attribute_support.cmake create mode 100644 etc/cmake/benchmark_helpers.cmake create mode 100644 etc/cmake/bit_operations_support.cmake create mode 100644 etc/cmake/compilers.cmake create mode 100644 etc/cmake/cpack_install_script.cmake create mode 100644 etc/cmake/create_igraph_version_file.cmake create mode 100644 etc/cmake/debugging.cmake create mode 100644 etc/cmake/dependencies.cmake create mode 100644 etc/cmake/features.cmake create mode 100644 etc/cmake/fuzz_helpers.cmake create mode 100644 etc/cmake/generate_tags_file.cmake create mode 100644 etc/cmake/helpers.cmake create mode 100644 etc/cmake/ieee754_endianness.cmake create mode 100644 etc/cmake/ieee754_endianness_check.c create mode 100644 etc/cmake/igraph-config.cmake.in create mode 100644 etc/cmake/lto.cmake create mode 100644 etc/cmake/packaging.cmake create mode 100644 etc/cmake/pkgconfig_helpers.cmake create mode 100644 etc/cmake/run_legacy_test.cmake create mode 100644 etc/cmake/safe_math_support.cmake create mode 100644 etc/cmake/sanitizers.cmake create mode 100644 etc/cmake/summary.cmake create mode 100644 etc/cmake/test_helpers.cmake create mode 100644 etc/cmake/tls.cmake create mode 100644 etc/cmake/uint128_support.cmake create mode 100644 etc/cmake/version.cmake create mode 100644 examples/simple/adjlist.c create mode 100644 examples/simple/ak-4102.max create mode 100644 examples/simple/bellman_ford.c create mode 100644 examples/simple/bellman_ford.out create mode 100644 examples/simple/blas.c create mode 100644 examples/simple/blas.out create mode 100644 examples/simple/blas_dgemm.c create mode 100644 examples/simple/blas_dgemm.out create mode 100644 examples/simple/cattributes.c create mode 100644 examples/simple/cattributes.out create mode 100644 examples/simple/cattributes2.c create mode 100644 examples/simple/cattributes2.out create mode 100644 examples/simple/cattributes3.c create mode 100644 examples/simple/cattributes3.out create mode 100644 examples/simple/cattributes4.c create mode 100644 examples/simple/cattributes4.out create mode 100644 examples/simple/celegansneural.gml create mode 100644 examples/simple/centralization.c create mode 100644 examples/simple/centralization.out create mode 100644 examples/simple/cohesive_blocks.c create mode 100644 examples/simple/cohesive_blocks.out create mode 100644 examples/simple/coloring.c create mode 100644 examples/simple/creation.c create mode 100644 examples/simple/distances.c create mode 100644 examples/simple/distances.out create mode 100644 examples/simple/dominator_tree.c create mode 100644 examples/simple/dominator_tree.out create mode 100644 examples/simple/dot.c create mode 100644 examples/simple/dot.out create mode 100644 examples/simple/dqueue.c create mode 100644 examples/simple/dqueue.out create mode 100644 examples/simple/edgelist1.dl create mode 100644 examples/simple/edgelist2.dl create mode 100644 examples/simple/edgelist3.dl create mode 100644 examples/simple/edgelist4.dl create mode 100644 examples/simple/edgelist5.dl create mode 100644 examples/simple/edgelist6.dl create mode 100644 examples/simple/edgelist7.dl create mode 100644 examples/simple/eigenvector_centrality.c create mode 100644 examples/simple/eigenvector_centrality.out create mode 100644 examples/simple/even_tarjan.c create mode 100644 examples/simple/flow.c create mode 100644 examples/simple/flow2.c create mode 100644 examples/simple/flow2.out create mode 100644 examples/simple/foreign.c create mode 100644 examples/simple/foreign.out create mode 100644 examples/simple/fullmatrix1.dl create mode 100644 examples/simple/fullmatrix2.dl create mode 100644 examples/simple/fullmatrix3.dl create mode 100644 examples/simple/fullmatrix4.dl create mode 100644 examples/simple/gml.c create mode 100644 examples/simple/gml.out create mode 100644 examples/simple/graphml.c create mode 100644 examples/simple/igraph_all_st_mincuts.c create mode 100644 examples/simple/igraph_assortativity_degree.c create mode 100644 examples/simple/igraph_assortativity_degree.out create mode 100644 examples/simple/igraph_assortativity_nominal.c create mode 100644 examples/simple/igraph_assortativity_nominal.out create mode 100644 examples/simple/igraph_atlas.c create mode 100644 examples/simple/igraph_atlas.out create mode 100644 examples/simple/igraph_attribute_combination.c create mode 100644 examples/simple/igraph_attribute_combination.out create mode 100644 examples/simple/igraph_average_path_length.c create mode 100644 examples/simple/igraph_avg_nearest_neighbor_degree.c create mode 100644 examples/simple/igraph_avg_nearest_neighbor_degree.out create mode 100644 examples/simple/igraph_barabasi_game.c create mode 100644 examples/simple/igraph_barabasi_game2.c create mode 100644 examples/simple/igraph_bfs.c create mode 100644 examples/simple/igraph_bfs.out create mode 100644 examples/simple/igraph_bfs_callback.c create mode 100644 examples/simple/igraph_bfs_callback.out create mode 100644 examples/simple/igraph_bfs_simple.c create mode 100644 examples/simple/igraph_bfs_simple.out create mode 100644 examples/simple/igraph_biconnected_components.c create mode 100644 examples/simple/igraph_biconnected_components.out create mode 100644 examples/simple/igraph_bipartite_create.c create mode 100644 examples/simple/igraph_bipartite_create.out create mode 100644 examples/simple/igraph_bipartite_projection.c create mode 100644 examples/simple/igraph_cliques.c create mode 100644 examples/simple/igraph_cliques.out create mode 100644 examples/simple/igraph_cocitation.c create mode 100644 examples/simple/igraph_cocitation.out create mode 100644 examples/simple/igraph_community_edge_betweenness.c create mode 100644 examples/simple/igraph_community_edge_betweenness.out create mode 100644 examples/simple/igraph_community_fastgreedy.c create mode 100644 examples/simple/igraph_community_fastgreedy.out create mode 100644 examples/simple/igraph_community_label_propagation.c create mode 100644 examples/simple/igraph_community_label_propagation.out create mode 100644 examples/simple/igraph_community_leading_eigenvector.c create mode 100644 examples/simple/igraph_community_leading_eigenvector.out create mode 100644 examples/simple/igraph_community_leiden.c create mode 100644 examples/simple/igraph_community_leiden.out create mode 100644 examples/simple/igraph_community_multilevel.c create mode 100644 examples/simple/igraph_community_multilevel.out create mode 100644 examples/simple/igraph_community_optimal_modularity.c create mode 100644 examples/simple/igraph_community_optimal_modularity.out create mode 100644 examples/simple/igraph_complementer.c create mode 100644 examples/simple/igraph_complementer.out create mode 100644 examples/simple/igraph_compose.c create mode 100644 examples/simple/igraph_compose.out create mode 100644 examples/simple/igraph_contract_vertices.c create mode 100644 examples/simple/igraph_copy.c create mode 100644 examples/simple/igraph_create.c create mode 100644 examples/simple/igraph_decompose.c create mode 100644 examples/simple/igraph_decompose.out create mode 100644 examples/simple/igraph_degree.c create mode 100644 examples/simple/igraph_degree.out create mode 100644 examples/simple/igraph_degree_sequence_game.c create mode 100644 examples/simple/igraph_degree_sequence_game.out create mode 100644 examples/simple/igraph_delete_edges.c create mode 100644 examples/simple/igraph_delete_vertices.c create mode 100644 examples/simple/igraph_diameter.c create mode 100644 examples/simple/igraph_diameter.out create mode 100644 examples/simple/igraph_difference.c create mode 100644 examples/simple/igraph_difference.out create mode 100644 examples/simple/igraph_disjoint_union.c create mode 100644 examples/simple/igraph_disjoint_union.out create mode 100644 examples/simple/igraph_eccentricity.c create mode 100644 examples/simple/igraph_eccentricity.out create mode 100644 examples/simple/igraph_erdos_renyi_game_gnm.c create mode 100644 examples/simple/igraph_erdos_renyi_game_gnp.c create mode 100644 examples/simple/igraph_es_pairs.c create mode 100644 examples/simple/igraph_feedback_arc_set.c create mode 100644 examples/simple/igraph_feedback_arc_set.out create mode 100644 examples/simple/igraph_feedback_arc_set_ip.c create mode 100644 examples/simple/igraph_feedback_arc_set_ip.out create mode 100644 examples/simple/igraph_fisher_yates_shuffle.c create mode 100644 examples/simple/igraph_full.c create mode 100644 examples/simple/igraph_full.out create mode 100644 examples/simple/igraph_get_all_shortest_paths_dijkstra.c create mode 100644 examples/simple/igraph_get_all_shortest_paths_dijkstra.out create mode 100644 examples/simple/igraph_get_eid.c create mode 100644 examples/simple/igraph_get_eid.out create mode 100644 examples/simple/igraph_get_eids.c create mode 100644 examples/simple/igraph_get_laplacian.c create mode 100644 examples/simple/igraph_get_laplacian.out create mode 100644 examples/simple/igraph_get_laplacian_sparse.c create mode 100644 examples/simple/igraph_get_laplacian_sparse.out create mode 100644 examples/simple/igraph_get_shortest_paths.c create mode 100644 examples/simple/igraph_get_shortest_paths.out create mode 100644 examples/simple/igraph_get_shortest_paths_dijkstra.c create mode 100644 examples/simple/igraph_get_shortest_paths_dijkstra.out create mode 100644 examples/simple/igraph_girth.c create mode 100644 examples/simple/igraph_grg_game.c create mode 100644 examples/simple/igraph_grg_game.out create mode 100644 examples/simple/igraph_has_multiple.c create mode 100644 examples/simple/igraph_independent_sets.c create mode 100644 examples/simple/igraph_independent_sets.out create mode 100644 examples/simple/igraph_intersection.c create mode 100644 examples/simple/igraph_intersection.out create mode 100644 examples/simple/igraph_is_biconnected.c create mode 100644 examples/simple/igraph_is_biconnected.out create mode 100644 examples/simple/igraph_is_directed.c create mode 100644 examples/simple/igraph_is_loop.c create mode 100644 examples/simple/igraph_is_loop.out create mode 100644 examples/simple/igraph_is_minimal_separator.c create mode 100644 examples/simple/igraph_is_multiple.c create mode 100644 examples/simple/igraph_is_multiple.out create mode 100644 examples/simple/igraph_is_separator.c create mode 100644 examples/simple/igraph_isomorphic_vf2.c create mode 100644 examples/simple/igraph_join.c create mode 100644 examples/simple/igraph_join.out create mode 100644 examples/simple/igraph_kary_tree.c create mode 100644 examples/simple/igraph_kary_tree.out create mode 100644 examples/simple/igraph_lapack_dgeev.c create mode 100644 examples/simple/igraph_lapack_dgeev.out create mode 100644 examples/simple/igraph_lapack_dgeevx.c create mode 100644 examples/simple/igraph_lapack_dgeevx.out create mode 100644 examples/simple/igraph_lapack_dgesv.c create mode 100644 examples/simple/igraph_lapack_dgesv.out create mode 100644 examples/simple/igraph_lapack_dsyevr.c create mode 100644 examples/simple/igraph_lapack_dsyevr.out create mode 100644 examples/simple/igraph_layout_reingold_tilford.c create mode 100644 examples/simple/igraph_layout_reingold_tilford.in create mode 100644 examples/simple/igraph_lcf.c create mode 100644 examples/simple/igraph_lcf.out create mode 100644 examples/simple/igraph_list_triangles.c create mode 100644 examples/simple/igraph_list_triangles.out create mode 100644 examples/simple/igraph_maximal_cliques.c create mode 100644 examples/simple/igraph_maximum_bipartite_matching.c create mode 100644 examples/simple/igraph_mincut.c create mode 100644 examples/simple/igraph_mincut.out create mode 100644 examples/simple/igraph_minimal_separators.c create mode 100644 examples/simple/igraph_minimum_size_separators.c create mode 100644 examples/simple/igraph_minimum_spanning_tree.c create mode 100644 examples/simple/igraph_minimum_spanning_tree.out create mode 100644 examples/simple/igraph_motifs_randesu.c create mode 100644 examples/simple/igraph_motifs_randesu.out create mode 100644 examples/simple/igraph_neighbors.c create mode 100644 examples/simple/igraph_neighbors.out create mode 100644 examples/simple/igraph_pagerank.c create mode 100644 examples/simple/igraph_power_law_fit.c create mode 100644 examples/simple/igraph_power_law_fit.out create mode 100644 examples/simple/igraph_radius.c create mode 100644 examples/simple/igraph_random_sample.c create mode 100644 examples/simple/igraph_read_graph_dl.c create mode 100644 examples/simple/igraph_read_graph_dl.out create mode 100644 examples/simple/igraph_read_graph_graphdb.c create mode 100644 examples/simple/igraph_read_graph_graphdb.out create mode 100644 examples/simple/igraph_read_graph_lgl-1.lgl create mode 100644 examples/simple/igraph_read_graph_lgl-2.lgl create mode 100644 examples/simple/igraph_read_graph_lgl-3.lgl create mode 100644 examples/simple/igraph_read_graph_lgl.c create mode 100644 examples/simple/igraph_read_graph_lgl.out create mode 100644 examples/simple/igraph_realize_degree_sequence.c create mode 100644 examples/simple/igraph_realize_degree_sequence.out create mode 100644 examples/simple/igraph_reciprocity.c create mode 100644 examples/simple/igraph_regular_tree.c create mode 100644 examples/simple/igraph_regular_tree.out create mode 100644 examples/simple/igraph_ring.c create mode 100644 examples/simple/igraph_ring.out create mode 100644 examples/simple/igraph_similarity.c create mode 100644 examples/simple/igraph_similarity.out create mode 100644 examples/simple/igraph_simplify.c create mode 100644 examples/simple/igraph_simplify.out create mode 100644 examples/simple/igraph_small.c create mode 100644 examples/simple/igraph_small.out create mode 100644 examples/simple/igraph_sparsemat.c create mode 100644 examples/simple/igraph_sparsemat.out create mode 100644 examples/simple/igraph_sparsemat3.c create mode 100644 examples/simple/igraph_sparsemat3.out create mode 100644 examples/simple/igraph_sparsemat4.c create mode 100644 examples/simple/igraph_sparsemat4.out create mode 100644 examples/simple/igraph_sparsemat6.c create mode 100644 examples/simple/igraph_sparsemat7.c create mode 100644 examples/simple/igraph_sparsemat8.c create mode 100644 examples/simple/igraph_star.c create mode 100644 examples/simple/igraph_star.out create mode 100644 examples/simple/igraph_strvector.c create mode 100644 examples/simple/igraph_strvector.out create mode 100644 examples/simple/igraph_subisomorphic_lad.c create mode 100644 examples/simple/igraph_subisomorphic_lad.out create mode 100644 examples/simple/igraph_symmetric_tree.c create mode 100644 examples/simple/igraph_to_undirected.c create mode 100644 examples/simple/igraph_to_undirected.out create mode 100644 examples/simple/igraph_topological_sorting.c create mode 100644 examples/simple/igraph_topological_sorting.out create mode 100644 examples/simple/igraph_transitivity.c create mode 100644 examples/simple/igraph_union.c create mode 100644 examples/simple/igraph_union.out create mode 100644 examples/simple/igraph_vector_int_list_sort.c create mode 100644 examples/simple/igraph_version.c create mode 100644 examples/simple/igraph_vs_nonadj.c create mode 100644 examples/simple/igraph_vs_nonadj.out create mode 100644 examples/simple/igraph_vs_range.c create mode 100644 examples/simple/igraph_vs_range.out create mode 100644 examples/simple/igraph_vs_vector.c create mode 100644 examples/simple/igraph_vs_vector.out create mode 100644 examples/simple/igraph_weighted_adjacency.c create mode 100644 examples/simple/igraph_weighted_adjacency.out create mode 100644 examples/simple/igraph_write_graph_lgl.c create mode 100644 examples/simple/igraph_write_graph_lgl.out create mode 100644 examples/simple/igraph_write_graph_pajek.c create mode 100644 examples/simple/igraph_write_graph_pajek.out create mode 100644 examples/simple/iso_b03_m1000.A00 create mode 100644 examples/simple/karate.gml create mode 100644 examples/simple/links.net create mode 100644 examples/simple/nodelist1.dl create mode 100644 examples/simple/nodelist2.dl create mode 100644 examples/simple/random_seed.c create mode 100644 examples/simple/safelocale.c create mode 100644 examples/simple/safelocale.out create mode 100644 examples/simple/test.graphml create mode 100644 examples/simple/walktrap.c create mode 100644 examples/simple/weighted.gml create mode 100644 examples/tutorial/tutorial1.c create mode 100644 examples/tutorial/tutorial2.c create mode 100644 examples/tutorial/tutorial3.c create mode 100644 igraph.pc.in create mode 100644 include/igraph.h create mode 100644 include/igraph_adjlist.h create mode 100644 include/igraph_arpack.h create mode 100644 include/igraph_attributes.h create mode 100644 include/igraph_bipartite.h create mode 100644 include/igraph_bitset.h create mode 100644 include/igraph_bitset_list.h create mode 100644 include/igraph_blas.h create mode 100644 include/igraph_centrality.h create mode 100644 include/igraph_cliques.h create mode 100644 include/igraph_cocitation.h create mode 100644 include/igraph_cohesive_blocks.h create mode 100644 include/igraph_coloring.h create mode 100644 include/igraph_community.h create mode 100644 include/igraph_complex.h create mode 100644 include/igraph_components.h create mode 100644 include/igraph_config.h.in create mode 100644 include/igraph_constants.h create mode 100644 include/igraph_constructors.h create mode 100644 include/igraph_conversion.h create mode 100644 include/igraph_cycles.h create mode 100644 include/igraph_datatype.h create mode 100644 include/igraph_decls.h create mode 100644 include/igraph_dqueue.h create mode 100644 include/igraph_dqueue_pmt.h create mode 100644 include/igraph_eigen.h create mode 100644 include/igraph_embedding.h create mode 100644 include/igraph_epidemics.h create mode 100644 include/igraph_error.h create mode 100644 include/igraph_eulerian.h create mode 100644 include/igraph_flow.h create mode 100644 include/igraph_foreign.h create mode 100644 include/igraph_games.h create mode 100644 include/igraph_graph_list.h create mode 100644 include/igraph_graphicality.h create mode 100644 include/igraph_graphlets.h create mode 100644 include/igraph_heap.h create mode 100644 include/igraph_heap_pmt.h create mode 100644 include/igraph_hrg.h create mode 100644 include/igraph_interface.h create mode 100644 include/igraph_interrupt.h create mode 100644 include/igraph_isomorphism.h create mode 100644 include/igraph_iterators.h create mode 100644 include/igraph_lapack.h create mode 100644 include/igraph_layout.h create mode 100644 include/igraph_lsap.h create mode 100644 include/igraph_matching.h create mode 100644 include/igraph_matrix.h create mode 100644 include/igraph_matrix_list.h create mode 100644 include/igraph_matrix_pmt.h create mode 100644 include/igraph_memory.h create mode 100644 include/igraph_mixing.h create mode 100644 include/igraph_motifs.h create mode 100644 include/igraph_neighborhood.h create mode 100644 include/igraph_nongraph.h create mode 100644 include/igraph_operators.h create mode 100644 include/igraph_paths.h create mode 100644 include/igraph_pmt.h create mode 100644 include/igraph_pmt_off.h create mode 100644 include/igraph_progress.h create mode 100644 include/igraph_psumtree.h create mode 100644 include/igraph_qsort.h create mode 100644 include/igraph_random.h create mode 100644 include/igraph_reachability.h create mode 100644 include/igraph_sampling.h create mode 100644 include/igraph_scan.h create mode 100644 include/igraph_separators.h create mode 100644 include/igraph_setup.h create mode 100644 include/igraph_sparsemat.h create mode 100644 include/igraph_spatial.h create mode 100644 include/igraph_stack.h create mode 100644 include/igraph_stack_pmt.h create mode 100644 include/igraph_statusbar.h create mode 100644 include/igraph_structural.h create mode 100644 include/igraph_strvector.h create mode 100644 include/igraph_threading.h.in create mode 100644 include/igraph_transitivity.h create mode 100644 include/igraph_typed_list_pmt.h create mode 100644 include/igraph_types.h create mode 100644 include/igraph_vector.h create mode 100644 include/igraph_vector_list.h create mode 100644 include/igraph_vector_pmt.h create mode 100644 include/igraph_vector_ptr.h create mode 100644 include/igraph_vector_type.h create mode 100644 include/igraph_version.h.in create mode 100644 include/igraph_visitor.h create mode 100644 interfaces/CMakeLists.txt create mode 100644 interfaces/functions.yaml create mode 100644 interfaces/types.yaml create mode 100644 src/CMakeLists.txt create mode 100644 src/centrality/betweenness.c create mode 100644 src/centrality/centrality_internal.h create mode 100644 src/centrality/centrality_other.c create mode 100644 src/centrality/centralization.c create mode 100644 src/centrality/closeness.c create mode 100644 src/centrality/coreness.c create mode 100644 src/centrality/eigenvector.c create mode 100644 src/centrality/hub_authority.c create mode 100644 src/centrality/pagerank.c create mode 100644 src/centrality/prpack.cpp create mode 100644 src/centrality/prpack/CMakeLists.txt create mode 100644 src/centrality/prpack/prpack.h create mode 100644 src/centrality/prpack/prpack_base_graph.cpp create mode 100644 src/centrality/prpack/prpack_base_graph.h create mode 100644 src/centrality/prpack/prpack_csc.h create mode 100644 src/centrality/prpack/prpack_csr.h create mode 100644 src/centrality/prpack/prpack_edge_list.h create mode 100644 src/centrality/prpack/prpack_igraph_graph.cpp create mode 100644 src/centrality/prpack/prpack_igraph_graph.h create mode 100644 src/centrality/prpack/prpack_preprocessed_ge_graph.cpp create mode 100644 src/centrality/prpack/prpack_preprocessed_ge_graph.h create mode 100644 src/centrality/prpack/prpack_preprocessed_graph.h create mode 100644 src/centrality/prpack/prpack_preprocessed_gs_graph.cpp create mode 100644 src/centrality/prpack/prpack_preprocessed_gs_graph.h create mode 100644 src/centrality/prpack/prpack_preprocessed_scc_graph.cpp create mode 100644 src/centrality/prpack/prpack_preprocessed_scc_graph.h create mode 100644 src/centrality/prpack/prpack_preprocessed_schur_graph.cpp create mode 100644 src/centrality/prpack/prpack_preprocessed_schur_graph.h create mode 100644 src/centrality/prpack/prpack_result.cpp create mode 100644 src/centrality/prpack/prpack_result.h create mode 100644 src/centrality/prpack/prpack_solver.cpp create mode 100644 src/centrality/prpack/prpack_solver.h create mode 100644 src/centrality/prpack/prpack_utils.cpp create mode 100644 src/centrality/prpack/prpack_utils.h create mode 100644 src/centrality/prpack_internal.h create mode 100644 src/centrality/truss.cpp create mode 100644 src/cliques/cliquer/CMakeLists.txt create mode 100644 src/cliques/cliquer/README create mode 100644 src/cliques/cliquer/cliquer.c create mode 100644 src/cliques/cliquer/cliquer.h create mode 100644 src/cliques/cliquer/cliquer_graph.c create mode 100644 src/cliques/cliquer/cliquerconf.h create mode 100644 src/cliques/cliquer/graph.h create mode 100644 src/cliques/cliquer/misc.h create mode 100644 src/cliques/cliquer/reorder.c create mode 100644 src/cliques/cliquer/reorder.h create mode 100644 src/cliques/cliquer/set.h create mode 100644 src/cliques/cliquer_internal.h create mode 100644 src/cliques/cliquer_wrapper.c create mode 100644 src/cliques/cliques.c create mode 100644 src/cliques/glet.c create mode 100644 src/cliques/maximal_cliques.c create mode 100644 src/cliques/maximal_cliques_template.h create mode 100644 src/community/community_internal.h create mode 100644 src/community/community_misc.c create mode 100644 src/community/edge_betweenness.c create mode 100644 src/community/fast_modularity.c create mode 100644 src/community/fluid.c create mode 100644 src/community/infomap.cpp create mode 100644 src/community/label_propagation.c create mode 100644 src/community/leading_eigenvector.c create mode 100644 src/community/leiden.c create mode 100644 src/community/louvain.c create mode 100644 src/community/modularity.c create mode 100644 src/community/optimal_modularity.c create mode 100644 src/community/spinglass/NetDataTypes.cpp create mode 100644 src/community/spinglass/NetDataTypes.h create mode 100644 src/community/spinglass/NetRoutines.cpp create mode 100644 src/community/spinglass/NetRoutines.h create mode 100644 src/community/spinglass/clustertool.cpp create mode 100644 src/community/spinglass/pottsmodel_2.cpp create mode 100644 src/community/spinglass/pottsmodel_2.h create mode 100644 src/community/voronoi.c create mode 100644 src/community/walktrap/walktrap.cpp create mode 100644 src/community/walktrap/walktrap_communities.cpp create mode 100644 src/community/walktrap/walktrap_communities.h create mode 100644 src/community/walktrap/walktrap_graph.cpp create mode 100644 src/community/walktrap/walktrap_graph.h create mode 100644 src/community/walktrap/walktrap_heap.cpp create mode 100644 src/community/walktrap/walktrap_heap.h create mode 100644 src/config.h.in create mode 100644 src/connectivity/cohesive_blocks.c create mode 100644 src/connectivity/components.c create mode 100644 src/connectivity/percolation.c create mode 100644 src/connectivity/reachability.c create mode 100644 src/connectivity/separators.c create mode 100644 src/constructors/adjacency.c create mode 100644 src/constructors/atlas-edges.h create mode 100644 src/constructors/atlas.c create mode 100644 src/constructors/basic_constructors.c create mode 100644 src/constructors/circulant.c create mode 100644 src/constructors/de_bruijn.c create mode 100644 src/constructors/famous.c create mode 100644 src/constructors/full.c create mode 100644 src/constructors/generalized_petersen.c create mode 100644 src/constructors/kautz.c create mode 100644 src/constructors/lattices.c create mode 100644 src/constructors/lcf.c create mode 100644 src/constructors/linegraph.c create mode 100644 src/constructors/mycielskian.c create mode 100644 src/constructors/prufer.c create mode 100644 src/constructors/regular.c create mode 100644 src/constructors/trees.c create mode 100644 src/core/bitset.c create mode 100644 src/core/bitset_list.c create mode 100644 src/core/buckets.c create mode 100644 src/core/buckets.h create mode 100644 src/core/cutheap.c create mode 100644 src/core/cutheap.h create mode 100644 src/core/dqueue.c create mode 100644 src/core/dqueue.pmt create mode 100644 src/core/error.c create mode 100644 src/core/estack.c create mode 100644 src/core/estack.h create mode 100644 src/core/exceptions.h create mode 100644 src/core/fixed_vectorlist.c create mode 100644 src/core/fixed_vectorlist.h create mode 100644 src/core/genheap.c create mode 100644 src/core/genheap.h create mode 100644 src/core/grid.c create mode 100644 src/core/grid.h create mode 100644 src/core/heap.c create mode 100644 src/core/heap.pmt create mode 100644 src/core/indheap.c create mode 100644 src/core/indheap.h create mode 100644 src/core/interruption.c create mode 100644 src/core/interruption.h create mode 100644 src/core/marked_queue.c create mode 100644 src/core/marked_queue.h create mode 100644 src/core/math.h create mode 100644 src/core/matrix.c create mode 100644 src/core/matrix.pmt create mode 100644 src/core/matrix_list.c create mode 100644 src/core/memory.c create mode 100644 src/core/printing.c create mode 100644 src/core/progress.c create mode 100644 src/core/psumtree.c create mode 100644 src/core/set.c create mode 100644 src/core/set.h create mode 100644 src/core/setup.c create mode 100644 src/core/sparsemat.c create mode 100644 src/core/stack.c create mode 100644 src/core/stack.pmt create mode 100644 src/core/statusbar.c create mode 100644 src/core/strvector.c create mode 100644 src/core/trie.c create mode 100644 src/core/trie.h create mode 100644 src/core/typed_list.pmt create mode 100644 src/core/vector.c create mode 100644 src/core/vector.pmt create mode 100644 src/core/vector_list.c create mode 100644 src/core/vector_ptr.c create mode 100644 src/cycles/cycle_bases.c create mode 100644 src/cycles/feedback_sets.c create mode 100644 src/cycles/feedback_sets.h create mode 100644 src/cycles/order_cycle.cpp create mode 100644 src/cycles/order_cycle.h create mode 100644 src/cycles/simple_cycles.c create mode 100644 src/flow/flow.c create mode 100644 src/flow/flow_conversion.c create mode 100644 src/flow/flow_internal.h create mode 100644 src/flow/st-cuts.c create mode 100644 src/games/barabasi.c create mode 100644 src/games/callaway_traits.c create mode 100644 src/games/chung_lu.c create mode 100644 src/games/citations.c create mode 100644 src/games/correlated.c create mode 100644 src/games/degree_sequence.c create mode 100644 src/games/degree_sequence_vl/degree_sequence_vl.h create mode 100644 src/games/degree_sequence_vl/gengraph_definitions.h create mode 100644 src/games/degree_sequence_vl/gengraph_degree_sequence.cpp create mode 100644 src/games/degree_sequence_vl/gengraph_degree_sequence.h create mode 100644 src/games/degree_sequence_vl/gengraph_graph_molloy_hash.cpp create mode 100644 src/games/degree_sequence_vl/gengraph_graph_molloy_hash.h create mode 100644 src/games/degree_sequence_vl/gengraph_graph_molloy_optimized.cpp create mode 100644 src/games/degree_sequence_vl/gengraph_graph_molloy_optimized.h create mode 100644 src/games/degree_sequence_vl/gengraph_hash.h create mode 100644 src/games/degree_sequence_vl/gengraph_header.h create mode 100644 src/games/degree_sequence_vl/gengraph_mr-connected.cpp create mode 100644 src/games/degree_sequence_vl/gengraph_qsort.h create mode 100644 src/games/degree_sequence_vl/gengraph_random.cpp create mode 100644 src/games/degree_sequence_vl/gengraph_random.h create mode 100644 src/games/dotproduct.c create mode 100644 src/games/erdos_renyi.c create mode 100644 src/games/establishment.c create mode 100644 src/games/forestfire.c create mode 100644 src/games/grg.c create mode 100644 src/games/growing_random.c create mode 100644 src/games/islands.c create mode 100644 src/games/k_regular.c create mode 100644 src/games/preference.c create mode 100644 src/games/recent_degree.c create mode 100644 src/games/sbm.c create mode 100644 src/games/static_fitness.c create mode 100644 src/games/tree.c create mode 100644 src/games/watts_strogatz.c create mode 100644 src/graph/adjlist.c create mode 100644 src/graph/attributes.c create mode 100644 src/graph/attributes.h create mode 100644 src/graph/basic_query.c create mode 100644 src/graph/caching.c create mode 100644 src/graph/caching.h create mode 100644 src/graph/cattributes.c create mode 100644 src/graph/graph_list.c create mode 100644 src/graph/internal.h create mode 100644 src/graph/iterators.c create mode 100644 src/graph/type_common.c create mode 100644 src/graph/type_indexededgelist.c create mode 100644 src/graph/visitors.c create mode 100644 src/hrg/dendro.h create mode 100644 src/hrg/graph.h create mode 100644 src/hrg/graph_simp.h create mode 100644 src/hrg/hrg.cc create mode 100644 src/hrg/hrg_types.cc create mode 100644 src/hrg/rbtree.h create mode 100644 src/hrg/splittree_eq.h create mode 100644 src/internal/glpk_support.c create mode 100644 src/internal/glpk_support.h create mode 100644 src/internal/gmp_internal.h create mode 100644 src/internal/hacks.c create mode 100644 src/internal/hacks.h create mode 100644 src/internal/lsap.c create mode 100644 src/internal/qsort.c create mode 100644 src/internal/qsort_r.c create mode 100644 src/internal/utils.c create mode 100644 src/internal/utils.h create mode 100644 src/io/dimacs.c create mode 100644 src/io/dl-header.h create mode 100644 src/io/dl.c create mode 100644 src/io/dot.c create mode 100644 src/io/edgelist.c create mode 100644 src/io/gml-header.h create mode 100644 src/io/gml-tree.c create mode 100644 src/io/gml-tree.h create mode 100644 src/io/gml.c create mode 100644 src/io/graphdb.c create mode 100644 src/io/graphml.c create mode 100644 src/io/leda.c create mode 100644 src/io/lgl-header.h create mode 100644 src/io/lgl.c create mode 100644 src/io/ncol-header.h create mode 100644 src/io/ncol.c create mode 100644 src/io/pajek-header.h create mode 100644 src/io/pajek.c create mode 100644 src/io/parse_utils.c create mode 100644 src/io/parse_utils.h create mode 100644 src/io/parsers/dl-lexer.c create mode 100644 src/io/parsers/dl-lexer.h create mode 100644 src/io/parsers/dl-parser.c create mode 100644 src/io/parsers/dl-parser.h create mode 100644 src/io/parsers/gml-lexer.c create mode 100644 src/io/parsers/gml-lexer.h create mode 100644 src/io/parsers/gml-parser.c create mode 100644 src/io/parsers/gml-parser.h create mode 100644 src/io/parsers/lgl-lexer.c create mode 100644 src/io/parsers/lgl-lexer.h create mode 100644 src/io/parsers/lgl-parser.c create mode 100644 src/io/parsers/lgl-parser.h create mode 100644 src/io/parsers/ncol-lexer.c create mode 100644 src/io/parsers/ncol-lexer.h create mode 100644 src/io/parsers/ncol-parser.c create mode 100644 src/io/parsers/ncol-parser.h create mode 100644 src/io/parsers/pajek-lexer.c create mode 100644 src/io/parsers/pajek-lexer.h create mode 100644 src/io/parsers/pajek-parser.c create mode 100644 src/io/parsers/pajek-parser.h create mode 100644 src/isomorphism/bliss.cc create mode 100644 src/isomorphism/bliss/CMakeLists.txt create mode 100644 src/isomorphism/bliss/bignum.hh create mode 100644 src/isomorphism/bliss/defs.cc create mode 100644 src/isomorphism/bliss/defs.hh create mode 100644 src/isomorphism/bliss/graph.cc create mode 100644 src/isomorphism/bliss/graph.hh create mode 100644 src/isomorphism/bliss/heap.cc create mode 100644 src/isomorphism/bliss/heap.hh create mode 100644 src/isomorphism/bliss/igraph-changes.md create mode 100644 src/isomorphism/bliss/kqueue.hh create mode 100644 src/isomorphism/bliss/kstack.hh create mode 100644 src/isomorphism/bliss/orbit.cc create mode 100644 src/isomorphism/bliss/orbit.hh create mode 100644 src/isomorphism/bliss/partition.cc create mode 100644 src/isomorphism/bliss/partition.hh create mode 100644 src/isomorphism/bliss/stats.hh create mode 100644 src/isomorphism/bliss/uintseqhash.cc create mode 100644 src/isomorphism/bliss/uintseqhash.hh create mode 100644 src/isomorphism/bliss/utils.cc create mode 100644 src/isomorphism/bliss/utils.hh create mode 100644 src/isomorphism/isoclasses.c create mode 100644 src/isomorphism/isoclasses.h create mode 100644 src/isomorphism/isomorphism_misc.c create mode 100644 src/isomorphism/lad.c create mode 100644 src/isomorphism/queries.c create mode 100644 src/isomorphism/vf2.c create mode 100644 src/layout/align.c create mode 100644 src/layout/circular.c create mode 100644 src/layout/davidson_harel.c create mode 100644 src/layout/drl/DensityGrid.cpp create mode 100644 src/layout/drl/DensityGrid.h create mode 100644 src/layout/drl/DensityGrid_3d.cpp create mode 100644 src/layout/drl/DensityGrid_3d.h create mode 100644 src/layout/drl/drl_Node.h create mode 100644 src/layout/drl/drl_Node_3d.h create mode 100644 src/layout/drl/drl_graph.cpp create mode 100644 src/layout/drl/drl_graph.h create mode 100644 src/layout/drl/drl_graph_3d.cpp create mode 100644 src/layout/drl/drl_graph_3d.h create mode 100644 src/layout/drl/drl_layout.cpp create mode 100644 src/layout/drl/drl_layout.h create mode 100644 src/layout/drl/drl_layout_3d.cpp create mode 100644 src/layout/drl/drl_layout_3d.h create mode 100644 src/layout/drl/drl_parse.cpp create mode 100644 src/layout/drl/drl_parse.h create mode 100644 src/layout/fruchterman_reingold.c create mode 100644 src/layout/gem.c create mode 100644 src/layout/graphopt.c create mode 100644 src/layout/kamada_kawai.c create mode 100644 src/layout/large_graph.c create mode 100644 src/layout/layout_bipartite.c create mode 100644 src/layout/layout_grid.c create mode 100644 src/layout/layout_internal.h create mode 100644 src/layout/layout_random.c create mode 100644 src/layout/mds.c create mode 100644 src/layout/merge_dla.c create mode 100644 src/layout/merge_grid.c create mode 100644 src/layout/merge_grid.h create mode 100644 src/layout/reingold_tilford.c create mode 100644 src/layout/sugiyama.c create mode 100644 src/layout/umap.c create mode 100644 src/linalg/arpack.c create mode 100644 src/linalg/arpack_internal.h create mode 100644 src/linalg/blas.c create mode 100644 src/linalg/blas_internal.h create mode 100644 src/linalg/eigen.c create mode 100644 src/linalg/lapack.c create mode 100644 src/linalg/lapack_internal.h create mode 100644 src/math/complex.c create mode 100644 src/math/safe_intop.c create mode 100644 src/math/safe_intop.h create mode 100644 src/math/utils.c create mode 100644 src/misc/bipartite.c create mode 100644 src/misc/chordality.c create mode 100644 src/misc/cocitation.c create mode 100644 src/misc/coloring.c create mode 100644 src/misc/conversion.c create mode 100644 src/misc/degree_sequence.cpp create mode 100644 src/misc/embedding.c create mode 100644 src/misc/graphicality.c create mode 100644 src/misc/graphicality.h create mode 100644 src/misc/matching.c create mode 100644 src/misc/mixing.c create mode 100644 src/misc/motifs.c create mode 100644 src/misc/other.c create mode 100644 src/misc/power_law_fit.c create mode 100644 src/misc/scan.c create mode 100644 src/misc/sir.c create mode 100644 src/misc/spanning_trees.c create mode 100644 src/operators/add_edge.c create mode 100644 src/operators/complementer.c create mode 100644 src/operators/compose.c create mode 100644 src/operators/connect_neighborhood.c create mode 100644 src/operators/contract.c create mode 100644 src/operators/difference.c create mode 100644 src/operators/disjoint_union.c create mode 100644 src/operators/intersection.c create mode 100644 src/operators/join.c create mode 100644 src/operators/misc_internal.c create mode 100644 src/operators/misc_internal.h create mode 100644 src/operators/permute.c create mode 100644 src/operators/products.c create mode 100644 src/operators/reverse.c create mode 100644 src/operators/rewire.c create mode 100644 src/operators/rewire_edges.c create mode 100644 src/operators/rewire_internal.h create mode 100644 src/operators/simplify.c create mode 100644 src/operators/subgraph.c create mode 100644 src/operators/subgraph.h create mode 100644 src/operators/union.c create mode 100644 src/paths/all_shortest_paths.c create mode 100644 src/paths/astar.c create mode 100644 src/paths/bellman_ford.c create mode 100644 src/paths/dijkstra.c create mode 100644 src/paths/distances.c create mode 100644 src/paths/eulerian.c create mode 100644 src/paths/floyd_warshall.c create mode 100644 src/paths/histogram.c create mode 100644 src/paths/johnson.c create mode 100644 src/paths/paths_internal.h create mode 100644 src/paths/random_walk.c create mode 100644 src/paths/shortest_paths.c create mode 100644 src/paths/simple_paths.c create mode 100644 src/paths/sparsifier.c create mode 100644 src/paths/unweighted.c create mode 100644 src/paths/voronoi.c create mode 100644 src/paths/widest_paths.c create mode 100644 src/properties/basic_properties.c create mode 100644 src/properties/complete.c create mode 100644 src/properties/constraint.c create mode 100644 src/properties/convergence_degree.c create mode 100644 src/properties/dag.c create mode 100644 src/properties/degrees.c create mode 100644 src/properties/ecc.c create mode 100644 src/properties/girth.c create mode 100644 src/properties/loops.c create mode 100644 src/properties/multiplicity.c create mode 100644 src/properties/neighborhood.c create mode 100644 src/properties/perfect.c create mode 100644 src/properties/properties_internal.h create mode 100644 src/properties/rich_club.c create mode 100644 src/properties/spectral.c create mode 100644 src/properties/trees.c create mode 100644 src/properties/triangles.c create mode 100644 src/properties/triangles_template.h create mode 100644 src/properties/triangles_template1.h create mode 100644 src/random/random.c create mode 100644 src/random/random_device.cpp create mode 100644 src/random/random_internal.h create mode 100644 src/random/rng_glibc2.c create mode 100644 src/random/rng_mt19937.c create mode 100644 src/random/rng_pcg32.c create mode 100644 src/random/rng_pcg64.c create mode 100644 src/random/sampling.c create mode 100644 src/spatial/beta_skeleton.cpp create mode 100644 src/spatial/convex_hull.c create mode 100644 src/spatial/delaunay.c create mode 100644 src/spatial/edge_lengths.c create mode 100644 src/spatial/nanoflann_internal.hpp create mode 100644 src/spatial/nearest_neighbor.cpp create mode 100644 src/spatial/spatial_internal.h create mode 100644 src/version.c create mode 100644 tests/CMakeLists.txt create mode 100644 tests/benchmarks/bench.h create mode 100644 tests/benchmarks/beta_skeletons.c create mode 100644 tests/benchmarks/coloring.c create mode 100644 tests/benchmarks/community.c create mode 100644 tests/benchmarks/connectivity.c create mode 100644 tests/benchmarks/erdos_renyi.c create mode 100644 tests/benchmarks/graphicality.c create mode 100644 tests/benchmarks/igraph_adjacency.c create mode 100644 tests/benchmarks/igraph_average_path_length_unweighted.c create mode 100644 tests/benchmarks/igraph_betweenness.c create mode 100644 tests/benchmarks/igraph_betweenness_weighted.c create mode 100644 tests/benchmarks/igraph_cliques.c create mode 100644 tests/benchmarks/igraph_closeness_weighted.c create mode 100644 tests/benchmarks/igraph_decompose.c create mode 100644 tests/benchmarks/igraph_degree.c create mode 100644 tests/benchmarks/igraph_degree_sequence_game.c create mode 100644 tests/benchmarks/igraph_delaunay_graph.c create mode 100644 tests/benchmarks/igraph_distances.c create mode 100644 tests/benchmarks/igraph_ecc.c create mode 100644 tests/benchmarks/igraph_induced_subgraph.c create mode 100644 tests/benchmarks/igraph_induced_subgraph_edges.c create mode 100644 tests/benchmarks/igraph_layout_umap.c create mode 100644 tests/benchmarks/igraph_matrix_transpose.c create mode 100644 tests/benchmarks/igraph_maximal_cliques.c create mode 100644 tests/benchmarks/igraph_nearest_neighbor_graph.c create mode 100644 tests/benchmarks/igraph_neighborhood.c create mode 100644 tests/benchmarks/igraph_pagerank.c create mode 100644 tests/benchmarks/igraph_pagerank_weighted.c create mode 100644 tests/benchmarks/igraph_power_law_fit.c create mode 100644 tests/benchmarks/igraph_qsort.c create mode 100644 tests/benchmarks/igraph_random_walk.c create mode 100644 tests/benchmarks/igraph_realize_degree_sequence.c create mode 100644 tests/benchmarks/igraph_rewire.c create mode 100644 tests/benchmarks/igraph_strength.c create mode 100644 tests/benchmarks/igraph_transitivity.c create mode 100644 tests/benchmarks/igraph_tree_game.c create mode 100644 tests/benchmarks/igraph_vertex_connectivity.c create mode 100644 tests/benchmarks/igraph_voronoi.c create mode 100644 tests/benchmarks/inc_vs_adj.c create mode 100644 tests/benchmarks/intersection.c create mode 100644 tests/benchmarks/lad.c create mode 100644 tests/benchmarks/modularity.c create mode 100644 tests/benchmarks/spanning_tree.c create mode 100644 tests/regression/bug-1033045.c create mode 100644 tests/regression/bug-1033045.out create mode 100644 tests/regression/bug-1149658.c create mode 100644 tests/regression/bug_1760.c create mode 100644 tests/regression/bug_1760.out create mode 100644 tests/regression/bug_1814.c create mode 100644 tests/regression/bug_1814.out create mode 100644 tests/regression/bug_1970.c create mode 100644 tests/regression/bug_1970.graphml create mode 100644 tests/regression/bug_2150.c create mode 100644 tests/regression/bug_2497.c create mode 100644 tests/regression/bug_2497.gml create mode 100644 tests/regression/bug_2497.out create mode 100644 tests/regression/bug_2506.c create mode 100644 tests/regression/bug_2506_1.graphml create mode 100644 tests/regression/bug_2506_2.graphml create mode 100644 tests/regression/bug_2506_3.graphml create mode 100644 tests/regression/bug_2517.c create mode 100644 tests/regression/bug_2517.out create mode 100644 tests/regression/bug_2608.c create mode 100644 tests/regression/bug_2608.out create mode 100644 tests/regression/cattr_bool_bug.c create mode 100644 tests/regression/cattr_bool_bug.graphml create mode 100644 tests/regression/cattr_bool_bug2.c create mode 100644 tests/regression/cattr_bool_bug2.graphml create mode 100644 tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.c create mode 100644 tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out create mode 100644 tests/regression/igraph_layout_reingold_tilford_bug_879.c create mode 100644 tests/regression/igraph_layout_reingold_tilford_bug_879.in create mode 100644 tests/regression/igraph_layout_reingold_tilford_bug_879.out create mode 100644 tests/regression/igraph_read_graph_gml_invalid_inputs.c create mode 100644 tests/regression/igraph_read_graph_graphml_invalid_inputs.c create mode 100644 tests/regression/igraph_read_graph_graphml_invalid_inputs.out create mode 100644 tests/regression/igraph_read_graph_pajek_invalid_inputs.c create mode 100644 tests/regression/invalid1.gml create mode 100644 tests/regression/invalid1.graphml create mode 100644 tests/regression/invalid2.gml create mode 100644 tests/regression/invalid2.graphml create mode 100644 tests/regression/invalid3.graphml create mode 100644 tests/regression/invalid4.gml create mode 100644 tests/regression/invalid4.graphml create mode 100644 tests/regression/invalid5.gml create mode 100644 tests/regression/invalid5.graphml create mode 100644 tests/regression/invalid6.gml create mode 100644 tests/regression/invalid6.graphml create mode 100644 tests/regression/invalid_pajek1.net create mode 100644 tests/regression/invalid_pajek2.net create mode 100644 tests/regression/invalid_pajek3.net create mode 100644 tests/unit/2wheap.c create mode 100644 tests/unit/VF2-compat.c create mode 100644 tests/unit/adj.c create mode 100644 tests/unit/adj.out create mode 100644 tests/unit/adjlist.c create mode 100644 tests/unit/adjlist.out create mode 100644 tests/unit/ak-4102.max create mode 100644 tests/unit/all_almost_e.c create mode 100644 tests/unit/all_shortest_paths.c create mode 100644 tests/unit/all_shortest_paths.out create mode 100644 tests/unit/assortativity.c create mode 100644 tests/unit/assortativity.out create mode 100644 tests/unit/beta_skeletons.c create mode 100644 tests/unit/beta_skeletons.out create mode 100644 tests/unit/bfs.c create mode 100644 tests/unit/bfs.out create mode 100644 tests/unit/bfs_simple.c create mode 100644 tests/unit/bfs_simple.out create mode 100644 tests/unit/bipartite.net create mode 100644 tests/unit/bitset.c create mode 100644 tests/unit/bitset.out create mode 100644 tests/unit/bliss_automorphisms.c create mode 100644 tests/unit/bliss_automorphisms.out create mode 100644 tests/unit/cattributes5.c create mode 100644 tests/unit/cattributes5.out create mode 100644 tests/unit/cattributes6.c create mode 100644 tests/unit/cattributes6.out create mode 100644 tests/unit/centralization.c create mode 100644 tests/unit/centralization.out create mode 100644 tests/unit/cmp_epsilon.c create mode 100644 tests/unit/coloring.c create mode 100644 tests/unit/community_indexing.c create mode 100644 tests/unit/community_label_propagation.c create mode 100644 tests/unit/community_label_propagation2.c create mode 100644 tests/unit/community_label_propagation2.out create mode 100644 tests/unit/community_label_propagation3.c create mode 100644 tests/unit/community_leiden.c create mode 100644 tests/unit/community_leiden.out create mode 100644 tests/unit/community_walktrap.c create mode 100644 tests/unit/community_walktrap.out create mode 100644 tests/unit/components.c create mode 100644 tests/unit/components.out create mode 100644 tests/unit/constructor-failure.c create mode 100644 tests/unit/coreness.c create mode 100644 tests/unit/coreness.out create mode 100644 tests/unit/cutheap.c create mode 100644 tests/unit/cutheap.out create mode 100644 tests/unit/cycle_bases.c create mode 100644 tests/unit/cycle_bases.out create mode 100644 tests/unit/d_indheap.c create mode 100644 tests/unit/d_indheap.out create mode 100644 tests/unit/dgemv.c create mode 100644 tests/unit/dgemv.out create mode 100644 tests/unit/edge_selectors.c create mode 100644 tests/unit/edge_selectors.out create mode 100644 tests/unit/efficiency.c create mode 100644 tests/unit/efficiency.out create mode 100644 tests/unit/eigen_stress.c create mode 100644 tests/unit/empty create mode 100644 tests/unit/erdos_renyi_game_gnm.c create mode 100644 tests/unit/erdos_renyi_game_gnp.c create mode 100644 tests/unit/error_macros.c create mode 100644 tests/unit/error_macros.out create mode 100644 tests/unit/expand_path_to_pairs.c create mode 100644 tests/unit/expand_path_to_pairs.out create mode 100644 tests/unit/fatal_handler.c create mode 100644 tests/unit/fatal_handler.out create mode 100644 tests/unit/foreign_empty.c create mode 100644 tests/unit/full.c create mode 100644 tests/unit/full.out create mode 100644 tests/unit/gen2wheap.c create mode 100644 tests/unit/gen2wheap.out create mode 100644 tests/unit/global_transitivity.c create mode 100644 tests/unit/global_transitivity.out create mode 100644 tests/unit/glpk_error.c create mode 100644 tests/unit/gml.c create mode 100644 tests/unit/gml.out create mode 100644 tests/unit/graph1.gml create mode 100644 tests/unit/graph2.gml create mode 100644 tests/unit/graph3.gml create mode 100644 tests/unit/graphlets.c create mode 100644 tests/unit/graphlets.out create mode 100644 tests/unit/graphml-default-attrs.xml create mode 100644 tests/unit/graphml-hsa05010.xml create mode 100644 tests/unit/graphml-lenient.xml create mode 100644 tests/unit/graphml-namespace.xml create mode 100644 tests/unit/graphml-whitespace.xml create mode 100644 tests/unit/graphml-yed.xml create mode 100644 tests/unit/harmonic_centrality.c create mode 100644 tests/unit/harmonic_centrality.out create mode 100644 tests/unit/heap.c create mode 100644 tests/unit/heap.out create mode 100644 tests/unit/hub_and_authority.c create mode 100644 tests/unit/hub_and_authority.out create mode 100644 tests/unit/igraph_add_edges.c create mode 100644 tests/unit/igraph_add_edges.out create mode 100644 tests/unit/igraph_add_vertices.c create mode 100644 tests/unit/igraph_adhesion.c create mode 100644 tests/unit/igraph_adjacency.c create mode 100644 tests/unit/igraph_adjacency.out create mode 100644 tests/unit/igraph_adjacency_spectral_embedding.c create mode 100644 tests/unit/igraph_adjacency_spectral_embedding.out create mode 100644 tests/unit/igraph_adjlist_init_complementer.c create mode 100644 tests/unit/igraph_adjlist_init_complementer.out create mode 100644 tests/unit/igraph_adjlist_simplify.c create mode 100644 tests/unit/igraph_adjlist_simplify.out create mode 100644 tests/unit/igraph_all_st_cuts.c create mode 100644 tests/unit/igraph_all_st_cuts.out create mode 100644 tests/unit/igraph_all_st_mincuts.c create mode 100644 tests/unit/igraph_all_st_mincuts.out create mode 100644 tests/unit/igraph_almost_equals.c create mode 100644 tests/unit/igraph_are_adjacent.c create mode 100644 tests/unit/igraph_arpack_rnsolve.c create mode 100644 tests/unit/igraph_arpack_rnsolve.out create mode 100644 tests/unit/igraph_arpack_unpack_complex.c create mode 100644 tests/unit/igraph_arpack_unpack_complex.out create mode 100644 tests/unit/igraph_atlas.c create mode 100644 tests/unit/igraph_attribute_combination_remove.c create mode 100644 tests/unit/igraph_attribute_combination_remove.out create mode 100644 tests/unit/igraph_average_path_length.c create mode 100644 tests/unit/igraph_average_path_length.out create mode 100644 tests/unit/igraph_average_path_length_dijkstra.c create mode 100644 tests/unit/igraph_average_path_length_dijkstra.out create mode 100644 tests/unit/igraph_barabasi_aging_game.c create mode 100644 tests/unit/igraph_barabasi_aging_game.out create mode 100644 tests/unit/igraph_barabasi_game.c create mode 100644 tests/unit/igraph_betweenness.c create mode 100644 tests/unit/igraph_betweenness.out create mode 100644 tests/unit/igraph_betweenness_subset.c create mode 100644 tests/unit/igraph_betweenness_subset.out create mode 100644 tests/unit/igraph_biadjacency.c create mode 100644 tests/unit/igraph_biadjacency.out create mode 100644 tests/unit/igraph_biconnected_components.c create mode 100644 tests/unit/igraph_biconnected_components.out create mode 100644 tests/unit/igraph_bipartite_create.c create mode 100644 tests/unit/igraph_bipartite_game.c create mode 100644 tests/unit/igraph_bipartite_projection.c create mode 100644 tests/unit/igraph_blas_dgemm.c create mode 100644 tests/unit/igraph_blas_dgemm.out create mode 100644 tests/unit/igraph_bridges.c create mode 100644 tests/unit/igraph_bridges.out create mode 100644 tests/unit/igraph_callaway_traits_game.c create mode 100644 tests/unit/igraph_chung_lu_game.c create mode 100644 tests/unit/igraph_circulant.c create mode 100644 tests/unit/igraph_cited_type_game.c create mode 100644 tests/unit/igraph_cited_type_game.out create mode 100644 tests/unit/igraph_citing_cited_type_game.c create mode 100644 tests/unit/igraph_citing_cited_type_game.out create mode 100644 tests/unit/igraph_clique_size_hist.c create mode 100644 tests/unit/igraph_clique_size_hist.out create mode 100644 tests/unit/igraph_closeness.c create mode 100644 tests/unit/igraph_closeness.out create mode 100644 tests/unit/igraph_cohesion.c create mode 100644 tests/unit/igraph_cohesive_blocks.c create mode 100644 tests/unit/igraph_cohesive_blocks.out create mode 100644 tests/unit/igraph_coloring.out create mode 100644 tests/unit/igraph_community_eb_get_merges.c create mode 100644 tests/unit/igraph_community_eb_get_merges.out create mode 100644 tests/unit/igraph_community_edge_betweenness.c create mode 100644 tests/unit/igraph_community_edge_betweenness.out create mode 100644 tests/unit/igraph_community_fastgreedy.c create mode 100644 tests/unit/igraph_community_fastgreedy.out create mode 100644 tests/unit/igraph_community_fluid_communities.c create mode 100644 tests/unit/igraph_community_fluid_communities.out create mode 100644 tests/unit/igraph_community_infomap.c create mode 100644 tests/unit/igraph_community_infomap.out create mode 100644 tests/unit/igraph_community_leading_eigenvector2.c create mode 100644 tests/unit/igraph_community_leading_eigenvector2.out create mode 100644 tests/unit/igraph_community_optimal_modularity.c create mode 100644 tests/unit/igraph_community_voronoi.c create mode 100644 tests/unit/igraph_community_voronoi.out create mode 100644 tests/unit/igraph_compare_communities.c create mode 100644 tests/unit/igraph_compare_communities.out create mode 100644 tests/unit/igraph_complex.c create mode 100644 tests/unit/igraph_connect_neighborhood.c create mode 100644 tests/unit/igraph_connect_neighborhood.out create mode 100644 tests/unit/igraph_constraint.c create mode 100644 tests/unit/igraph_constraint.out create mode 100644 tests/unit/igraph_contract_vertices.c create mode 100644 tests/unit/igraph_contract_vertices.out create mode 100644 tests/unit/igraph_convergence_degree.c create mode 100644 tests/unit/igraph_convergence_degree.out create mode 100644 tests/unit/igraph_convex_hull_2d.c create mode 100644 tests/unit/igraph_convex_hull_2d.out create mode 100644 tests/unit/igraph_correlated_game.c create mode 100644 tests/unit/igraph_correlated_pair_game.c create mode 100644 tests/unit/igraph_correlated_pair_game.out create mode 100644 tests/unit/igraph_count_adjacent_triangles.c create mode 100644 tests/unit/igraph_count_adjacent_triangles.out create mode 100644 tests/unit/igraph_count_multiple.c create mode 100644 tests/unit/igraph_count_multiple.out create mode 100644 tests/unit/igraph_create.c create mode 100644 tests/unit/igraph_decompose_strong.c create mode 100644 tests/unit/igraph_decompose_strong.out create mode 100644 tests/unit/igraph_degree.c create mode 100644 tests/unit/igraph_degree.out create mode 100644 tests/unit/igraph_degree_sequence_game.c create mode 100644 tests/unit/igraph_delaunay_graph.c create mode 100644 tests/unit/igraph_delaunay_graph.out create mode 100644 tests/unit/igraph_delete_edges.c create mode 100644 tests/unit/igraph_delete_vertices.c create mode 100644 tests/unit/igraph_density.c create mode 100644 tests/unit/igraph_density.out create mode 100644 tests/unit/igraph_diameter.c create mode 100644 tests/unit/igraph_diameter.out create mode 100644 tests/unit/igraph_diameter_dijkstra.c create mode 100644 tests/unit/igraph_diameter_dijkstra.out create mode 100644 tests/unit/igraph_disjoint_union.c create mode 100644 tests/unit/igraph_disjoint_union.out create mode 100644 tests/unit/igraph_distances_floyd_warshall.c create mode 100644 tests/unit/igraph_distances_floyd_warshall.out create mode 100644 tests/unit/igraph_distances_floyd_warshall_speedup.c create mode 100644 tests/unit/igraph_distances_floyd_warshall_speedup.out create mode 100644 tests/unit/igraph_distances_johnson.c create mode 100644 tests/unit/igraph_distances_johnson.out create mode 100644 tests/unit/igraph_diversity.c create mode 100644 tests/unit/igraph_diversity.out create mode 100644 tests/unit/igraph_dominator_tree.c create mode 100644 tests/unit/igraph_dominator_tree.out create mode 100644 tests/unit/igraph_dot_product_game.c create mode 100644 tests/unit/igraph_dot_product_game.out create mode 100644 tests/unit/igraph_dyad_census.c create mode 100644 tests/unit/igraph_dyad_census.out create mode 100644 tests/unit/igraph_ecc.c create mode 100644 tests/unit/igraph_ecc.out create mode 100644 tests/unit/igraph_eccentricity.c create mode 100644 tests/unit/igraph_eccentricity.out create mode 100644 tests/unit/igraph_eccentricity_dijkstra.c create mode 100644 tests/unit/igraph_eccentricity_dijkstra.out create mode 100644 tests/unit/igraph_edge_betweenness.c create mode 100644 tests/unit/igraph_edge_betweenness.out create mode 100644 tests/unit/igraph_edge_betweenness_subset.c create mode 100644 tests/unit/igraph_edge_betweenness_subset.out create mode 100644 tests/unit/igraph_edge_disjoint_paths.c create mode 100644 tests/unit/igraph_edges.c create mode 100644 tests/unit/igraph_edges.out create mode 100644 tests/unit/igraph_eigen_matrix.c create mode 100644 tests/unit/igraph_eigen_matrix.out create mode 100644 tests/unit/igraph_eigen_matrix2.c create mode 100644 tests/unit/igraph_eigen_matrix2.out create mode 100644 tests/unit/igraph_eigen_matrix3.c create mode 100644 tests/unit/igraph_eigen_matrix3.out create mode 100644 tests/unit/igraph_eigen_matrix4.c create mode 100644 tests/unit/igraph_eigen_matrix4.out create mode 100644 tests/unit/igraph_eigen_matrix_symmetric.c create mode 100644 tests/unit/igraph_eigen_matrix_symmetric.out create mode 100644 tests/unit/igraph_eigen_matrix_symmetric_arpack.c create mode 100644 tests/unit/igraph_eigen_matrix_symmetric_arpack.out create mode 100644 tests/unit/igraph_eigenvector_centrality.c create mode 100644 tests/unit/igraph_eigenvector_centrality.out create mode 100644 tests/unit/igraph_empty.c create mode 100644 tests/unit/igraph_es_all_between.c create mode 100644 tests/unit/igraph_es_path.c create mode 100644 tests/unit/igraph_establishment_game.c create mode 100644 tests/unit/igraph_eulerian_cycle.c create mode 100644 tests/unit/igraph_eulerian_cycle.out create mode 100644 tests/unit/igraph_eulerian_path.c create mode 100644 tests/unit/igraph_eulerian_path.out create mode 100644 tests/unit/igraph_extended_chordal_ring.c create mode 100644 tests/unit/igraph_feedback_arc_set.c create mode 100644 tests/unit/igraph_feedback_vertex_set.c create mode 100644 tests/unit/igraph_find_cycle.c create mode 100644 tests/unit/igraph_find_cycle.out create mode 100644 tests/unit/igraph_forest_fire_game.c create mode 100644 tests/unit/igraph_forest_fire_game.out create mode 100644 tests/unit/igraph_from_prufer.c create mode 100644 tests/unit/igraph_from_prufer.out create mode 100644 tests/unit/igraph_full_bipartite.c create mode 100644 tests/unit/igraph_full_bipartite.out create mode 100644 tests/unit/igraph_full_citation.c create mode 100644 tests/unit/igraph_full_multipartite.c create mode 100644 tests/unit/igraph_full_multipartite.out create mode 100644 tests/unit/igraph_generalized_petersen.c create mode 100644 tests/unit/igraph_get_adjacency.c create mode 100644 tests/unit/igraph_get_adjacency.out create mode 100644 tests/unit/igraph_get_adjacency_sparse.c create mode 100644 tests/unit/igraph_get_adjacency_sparse.out create mode 100644 tests/unit/igraph_get_all_shortest_paths_dijkstra.c create mode 100644 tests/unit/igraph_get_all_shortest_paths_dijkstra.out create mode 100644 tests/unit/igraph_get_all_simple_paths.c create mode 100644 tests/unit/igraph_get_all_simple_paths.out create mode 100644 tests/unit/igraph_get_biadjacency.c create mode 100644 tests/unit/igraph_get_biadjacency.out create mode 100644 tests/unit/igraph_get_eid.c create mode 100644 tests/unit/igraph_get_isomorphisms_vf2.c create mode 100644 tests/unit/igraph_get_isomorphisms_vf2.out create mode 100644 tests/unit/igraph_get_k_shortest_paths.c create mode 100644 tests/unit/igraph_get_k_shortest_paths.out create mode 100644 tests/unit/igraph_get_laplacian.c create mode 100644 tests/unit/igraph_get_laplacian.out create mode 100644 tests/unit/igraph_get_shortest_path_astar.c create mode 100644 tests/unit/igraph_get_shortest_path_astar.out create mode 100644 tests/unit/igraph_get_shortest_path_bellman_ford.c create mode 100644 tests/unit/igraph_get_shortest_path_bellman_ford.out create mode 100644 tests/unit/igraph_get_shortest_paths2.c create mode 100644 tests/unit/igraph_get_shortest_paths2.out create mode 100644 tests/unit/igraph_get_shortest_paths_bellman_ford.c create mode 100644 tests/unit/igraph_get_shortest_paths_bellman_ford.out create mode 100644 tests/unit/igraph_get_shortest_paths_dijkstra.c create mode 100644 tests/unit/igraph_get_shortest_paths_dijkstra.out create mode 100644 tests/unit/igraph_get_stochastic.c create mode 100644 tests/unit/igraph_get_stochastic.out create mode 100644 tests/unit/igraph_get_stochastic_sparse.c create mode 100644 tests/unit/igraph_get_stochastic_sparse.out create mode 100644 tests/unit/igraph_get_subisomorphisms_vf2.c create mode 100644 tests/unit/igraph_get_subisomorphisms_vf2.out create mode 100644 tests/unit/igraph_gomory_hu_tree.c create mode 100644 tests/unit/igraph_graph_center.c create mode 100644 tests/unit/igraph_graph_center.out create mode 100644 tests/unit/igraph_graph_power.c create mode 100644 tests/unit/igraph_graph_power.out create mode 100644 tests/unit/igraph_grg_game.c create mode 100644 tests/unit/igraph_growing_random_game.c create mode 100644 tests/unit/igraph_has_mutual.c create mode 100644 tests/unit/igraph_hexagonal_lattice.c create mode 100644 tests/unit/igraph_hexagonal_lattice.out create mode 100644 tests/unit/igraph_hrg.c create mode 100644 tests/unit/igraph_hrg2.c create mode 100644 tests/unit/igraph_hrg3.c create mode 100644 tests/unit/igraph_hrg_create.c create mode 100644 tests/unit/igraph_hrg_create.out create mode 100644 tests/unit/igraph_hsbm_game.c create mode 100644 tests/unit/igraph_hsbm_game.out create mode 100644 tests/unit/igraph_hsbm_list_game.c create mode 100644 tests/unit/igraph_hsbm_list_game.out create mode 100644 tests/unit/igraph_i_layout_sphere.c create mode 100644 tests/unit/igraph_i_umap_fit_ab.c create mode 100644 tests/unit/igraph_i_umap_fit_ab.out create mode 100644 tests/unit/igraph_incident.c create mode 100644 tests/unit/igraph_incident.out create mode 100644 tests/unit/igraph_induced_subgraph.c create mode 100644 tests/unit/igraph_induced_subgraph.out create mode 100644 tests/unit/igraph_induced_subgraph_edges.c create mode 100644 tests/unit/igraph_induced_subgraph_edges.out create mode 100644 tests/unit/igraph_induced_subgraph_map.c create mode 100644 tests/unit/igraph_induced_subgraph_map.out create mode 100644 tests/unit/igraph_intersection.c create mode 100644 tests/unit/igraph_intersection.out create mode 100644 tests/unit/igraph_is_acyclic.c create mode 100644 tests/unit/igraph_is_biconnected.c create mode 100644 tests/unit/igraph_is_bigraphical.c create mode 100644 tests/unit/igraph_is_bigraphical.out create mode 100644 tests/unit/igraph_is_bipartite.c create mode 100644 tests/unit/igraph_is_chordal.c create mode 100644 tests/unit/igraph_is_chordal.out create mode 100644 tests/unit/igraph_is_clique.c create mode 100644 tests/unit/igraph_is_complete.c create mode 100644 tests/unit/igraph_is_connected.c create mode 100644 tests/unit/igraph_is_dag.c create mode 100644 tests/unit/igraph_is_eulerian.c create mode 100644 tests/unit/igraph_is_eulerian.out create mode 100644 tests/unit/igraph_is_forest.c create mode 100644 tests/unit/igraph_is_forest.out create mode 100644 tests/unit/igraph_is_forest2.c create mode 100644 tests/unit/igraph_is_graphical.c create mode 100644 tests/unit/igraph_is_graphical.out create mode 100644 tests/unit/igraph_is_mutual.c create mode 100644 tests/unit/igraph_is_mutual.out create mode 100644 tests/unit/igraph_is_same_graph.c create mode 100644 tests/unit/igraph_is_separator.c create mode 100644 tests/unit/igraph_is_tree.c create mode 100644 tests/unit/igraph_isomorphic.c create mode 100644 tests/unit/igraph_isomorphic.out create mode 100644 tests/unit/igraph_isomorphic_bliss.c create mode 100644 tests/unit/igraph_isomorphic_bliss.out create mode 100644 tests/unit/igraph_isomorphic_vf2.c create mode 100644 tests/unit/igraph_join.c create mode 100644 tests/unit/igraph_join.out create mode 100644 tests/unit/igraph_joint_degree_distribution.c create mode 100644 tests/unit/igraph_joint_degree_distribution.out create mode 100644 tests/unit/igraph_joint_type_distribution.c create mode 100644 tests/unit/igraph_joint_type_distribution.out create mode 100644 tests/unit/igraph_k_regular_game.c create mode 100644 tests/unit/igraph_k_regular_game.out create mode 100644 tests/unit/igraph_k_shortest_paths.out create mode 100644 tests/unit/igraph_kautz.c create mode 100644 tests/unit/igraph_lapack_dgeev.c create mode 100644 tests/unit/igraph_lapack_dgeevx.c create mode 100644 tests/unit/igraph_lapack_dgehrd.c create mode 100644 tests/unit/igraph_lapack_dgehrd.out create mode 100644 tests/unit/igraph_lapack_dgetrf.c create mode 100644 tests/unit/igraph_lapack_dgetrf.out create mode 100644 tests/unit/igraph_lapack_dgetrs.c create mode 100644 tests/unit/igraph_lapack_dgetrs.out create mode 100644 tests/unit/igraph_lapack_dsyevr.c create mode 100644 tests/unit/igraph_lastcit_game.c create mode 100644 tests/unit/igraph_lastcit_game.out create mode 100644 tests/unit/igraph_layout_align.c create mode 100644 tests/unit/igraph_layout_bipartite.c create mode 100644 tests/unit/igraph_layout_bipartite.out create mode 100644 tests/unit/igraph_layout_davidson_harel.c create mode 100644 tests/unit/igraph_layout_davidson_harel.out create mode 100644 tests/unit/igraph_layout_drl.c create mode 100644 tests/unit/igraph_layout_drl_3d.c create mode 100644 tests/unit/igraph_layout_drl_3d.out create mode 100644 tests/unit/igraph_layout_fruchterman_reingold.c create mode 100644 tests/unit/igraph_layout_fruchterman_reingold.out create mode 100644 tests/unit/igraph_layout_fruchterman_reingold_3d.c create mode 100644 tests/unit/igraph_layout_fruchterman_reingold_3d.out create mode 100644 tests/unit/igraph_layout_gem.c create mode 100644 tests/unit/igraph_layout_gem.out create mode 100644 tests/unit/igraph_layout_graphopt.c create mode 100644 tests/unit/igraph_layout_graphopt.out create mode 100644 tests/unit/igraph_layout_grid.c create mode 100644 tests/unit/igraph_layout_grid.out create mode 100644 tests/unit/igraph_layout_kamada_kawai.c create mode 100644 tests/unit/igraph_layout_kamada_kawai.out create mode 100644 tests/unit/igraph_layout_lgl.c create mode 100644 tests/unit/igraph_layout_lgl.out create mode 100644 tests/unit/igraph_layout_mds.c create mode 100644 tests/unit/igraph_layout_mds.out create mode 100644 tests/unit/igraph_layout_merge.c create mode 100644 tests/unit/igraph_layout_merge2.c create mode 100644 tests/unit/igraph_layout_merge2.out create mode 100644 tests/unit/igraph_layout_merge3.c create mode 100644 tests/unit/igraph_layout_random_3d.c create mode 100644 tests/unit/igraph_layout_reingold_tilford_circular.c create mode 100644 tests/unit/igraph_layout_reingold_tilford_circular.out create mode 100644 tests/unit/igraph_layout_reingold_tilford_extended.c create mode 100644 tests/unit/igraph_layout_reingold_tilford_extended.in create mode 100644 tests/unit/igraph_layout_sphere.c create mode 100644 tests/unit/igraph_layout_sphere.out create mode 100644 tests/unit/igraph_layout_star.c create mode 100644 tests/unit/igraph_layout_star.out create mode 100644 tests/unit/igraph_layout_sugiyama.c create mode 100644 tests/unit/igraph_layout_sugiyama.out create mode 100644 tests/unit/igraph_layout_umap.c create mode 100644 tests/unit/igraph_layout_umap.out create mode 100644 tests/unit/igraph_lcf.c create mode 100644 tests/unit/igraph_le_community_to_membership.c create mode 100644 tests/unit/igraph_le_community_to_membership.out create mode 100644 tests/unit/igraph_linegraph.c create mode 100644 tests/unit/igraph_list_triangles.c create mode 100644 tests/unit/igraph_list_triangles.out create mode 100644 tests/unit/igraph_local_scan_k_ecount.c create mode 100644 tests/unit/igraph_local_scan_k_ecount.out create mode 100644 tests/unit/igraph_local_scan_k_ecount_them.c create mode 100644 tests/unit/igraph_local_scan_k_ecount_them.out create mode 100644 tests/unit/igraph_local_scan_subset_ecount.c create mode 100644 tests/unit/igraph_local_scan_subset_ecount.out create mode 100644 tests/unit/igraph_local_transitivity.c create mode 100644 tests/unit/igraph_local_transitivity.out create mode 100644 tests/unit/igraph_maxflow.c create mode 100644 tests/unit/igraph_maxflow.out create mode 100644 tests/unit/igraph_maximal_cliques.c create mode 100644 tests/unit/igraph_maximal_cliques.out create mode 100644 tests/unit/igraph_maximal_cliques2.c create mode 100644 tests/unit/igraph_maximal_cliques2.out create mode 100644 tests/unit/igraph_maximal_cliques3.c create mode 100644 tests/unit/igraph_maximal_cliques3.out create mode 100644 tests/unit/igraph_maximal_cliques4.c create mode 100644 tests/unit/igraph_maximal_cliques4.out create mode 100644 tests/unit/igraph_maximal_cliques_file.c create mode 100644 tests/unit/igraph_maximal_cliques_file.out create mode 100644 tests/unit/igraph_maximum_bipartite_matching.c create mode 100644 tests/unit/igraph_mean_degree.c create mode 100644 tests/unit/igraph_minimum_size_separators.c create mode 100644 tests/unit/igraph_minimum_size_separators.out create mode 100644 tests/unit/igraph_modularity.c create mode 100644 tests/unit/igraph_modularity.out create mode 100644 tests/unit/igraph_modularity_matrix.c create mode 100644 tests/unit/igraph_modularity_matrix.out create mode 100644 tests/unit/igraph_motifs_randesu.c create mode 100644 tests/unit/igraph_motifs_randesu.out create mode 100644 tests/unit/igraph_motifs_randesu_estimate.c create mode 100644 tests/unit/igraph_motifs_randesu_estimate.out create mode 100644 tests/unit/igraph_motifs_randesu_no.c create mode 100644 tests/unit/igraph_motifs_randesu_no.out create mode 100644 tests/unit/igraph_nearest_neighbor_graph.c create mode 100644 tests/unit/igraph_nearest_neighbor_graph.out create mode 100644 tests/unit/igraph_neighborhood.c create mode 100644 tests/unit/igraph_neighborhood.out create mode 100644 tests/unit/igraph_neighborhood_graphs.c create mode 100644 tests/unit/igraph_neighborhood_graphs.out create mode 100644 tests/unit/igraph_neighborhood_size.c create mode 100644 tests/unit/igraph_neighborhood_size.out create mode 100644 tests/unit/igraph_neighbors.c create mode 100644 tests/unit/igraph_neighbors.out create mode 100644 tests/unit/igraph_pagerank.c create mode 100644 tests/unit/igraph_pagerank.out create mode 100644 tests/unit/igraph_path_length_hist.c create mode 100644 tests/unit/igraph_path_length_hist.out create mode 100644 tests/unit/igraph_perfect.c create mode 100644 tests/unit/igraph_permute_vertices.c create mode 100644 tests/unit/igraph_permute_vertices.out create mode 100644 tests/unit/igraph_power_law_fit.c create mode 100644 tests/unit/igraph_power_law_fit.out create mode 100644 tests/unit/igraph_preference_game.c create mode 100644 tests/unit/igraph_product.c create mode 100644 tests/unit/igraph_progress_handler_stderr.c create mode 100644 tests/unit/igraph_pseudo_diameter.c create mode 100644 tests/unit/igraph_pseudo_diameter.out create mode 100644 tests/unit/igraph_pseudo_diameter_dijkstra.c create mode 100644 tests/unit/igraph_pseudo_diameter_dijkstra.out create mode 100644 tests/unit/igraph_psumtree.c create mode 100644 tests/unit/igraph_qsort.c create mode 100644 tests/unit/igraph_qsort.out create mode 100644 tests/unit/igraph_qsort_r.c create mode 100644 tests/unit/igraph_qsort_r.out create mode 100644 tests/unit/igraph_random_sample.c create mode 100644 tests/unit/igraph_random_walk.c create mode 100644 tests/unit/igraph_random_walk.out create mode 100644 tests/unit/igraph_read_graph_graphdb.c create mode 100644 tests/unit/igraph_read_graph_graphdb.out create mode 100644 tests/unit/igraph_read_graph_graphml.c create mode 100644 tests/unit/igraph_read_graph_graphml.out create mode 100644 tests/unit/igraph_realize_bipartite_degree_sequence.c create mode 100644 tests/unit/igraph_realize_bipartite_degree_sequence.out create mode 100644 tests/unit/igraph_realize_degree_sequence.c create mode 100644 tests/unit/igraph_realize_degree_sequence.out create mode 100644 tests/unit/igraph_recent_degree_aging_game.c create mode 100644 tests/unit/igraph_recent_degree_aging_game.out create mode 100644 tests/unit/igraph_recent_degree_game.c create mode 100644 tests/unit/igraph_recent_degree_game.out create mode 100644 tests/unit/igraph_reindex_membership.c create mode 100644 tests/unit/igraph_residual_graph.c create mode 100644 tests/unit/igraph_reverse_edges.c create mode 100644 tests/unit/igraph_rewire.c create mode 100644 tests/unit/igraph_rewire_directed_edges.c create mode 100644 tests/unit/igraph_rewire_directed_edges.out create mode 100644 tests/unit/igraph_rng_get_integer.c create mode 100644 tests/unit/igraph_rng_get_integer.out create mode 100644 tests/unit/igraph_rng_sample_dirichlet.c create mode 100644 tests/unit/igraph_rng_sample_dirichlet.out create mode 100644 tests/unit/igraph_rng_sample_sphere.c create mode 100644 tests/unit/igraph_rooted_product.c create mode 100644 tests/unit/igraph_running_mean.c create mode 100644 tests/unit/igraph_running_mean.out create mode 100644 tests/unit/igraph_sbm_game.c create mode 100644 tests/unit/igraph_sbm_game.out create mode 100644 tests/unit/igraph_set_progress_handler.c create mode 100644 tests/unit/igraph_set_progress_handler.out create mode 100644 tests/unit/igraph_similarity.c create mode 100644 tests/unit/igraph_similarity.out create mode 100644 tests/unit/igraph_simple_cycles.c create mode 100644 tests/unit/igraph_simple_cycles.out create mode 100644 tests/unit/igraph_simple_interconnected_islands_game.c create mode 100644 tests/unit/igraph_simple_interconnected_islands_game.out create mode 100644 tests/unit/igraph_sir.c create mode 100644 tests/unit/igraph_sir.out create mode 100644 tests/unit/igraph_solve_lsap.c create mode 100644 tests/unit/igraph_solve_lsap.out create mode 100644 tests/unit/igraph_spanner.c create mode 100644 tests/unit/igraph_sparsemat2.c create mode 100644 tests/unit/igraph_sparsemat2.out create mode 100644 tests/unit/igraph_sparsemat5.c create mode 100644 tests/unit/igraph_sparsemat5.out create mode 100644 tests/unit/igraph_sparsemat9.c create mode 100644 tests/unit/igraph_sparsemat_droptol.c create mode 100644 tests/unit/igraph_sparsemat_droptol.out create mode 100644 tests/unit/igraph_sparsemat_fkeep.c create mode 100644 tests/unit/igraph_sparsemat_fkeep.out create mode 100644 tests/unit/igraph_sparsemat_getelements_sorted.c create mode 100644 tests/unit/igraph_sparsemat_getelements_sorted.out create mode 100644 tests/unit/igraph_sparsemat_is_symmetric.c create mode 100644 tests/unit/igraph_sparsemat_iterator_idx.c create mode 100644 tests/unit/igraph_sparsemat_minmax.c create mode 100644 tests/unit/igraph_sparsemat_minmax.out create mode 100644 tests/unit/igraph_sparsemat_nonzero_storage.c create mode 100644 tests/unit/igraph_sparsemat_normalize.c create mode 100644 tests/unit/igraph_sparsemat_normalize.out create mode 100644 tests/unit/igraph_sparsemat_view.out create mode 100644 tests/unit/igraph_sparsemat_which_minmax.c create mode 100644 tests/unit/igraph_sparsemat_which_minmax.out create mode 100644 tests/unit/igraph_spatial_edge_lengths.c create mode 100644 tests/unit/igraph_spatial_edge_lengths.out create mode 100644 tests/unit/igraph_split_join_distance.c create mode 100644 tests/unit/igraph_split_join_distance.out create mode 100644 tests/unit/igraph_square_lattice.c create mode 100644 tests/unit/igraph_st_edge_connectivity.c create mode 100644 tests/unit/igraph_st_mincut.c create mode 100644 tests/unit/igraph_st_mincut.out create mode 100644 tests/unit/igraph_st_mincut_value.c create mode 100644 tests/unit/igraph_st_vertex_connectivity.c create mode 100644 tests/unit/igraph_st_vertex_connectivity.out create mode 100644 tests/unit/igraph_static_power_law_game.c create mode 100644 tests/unit/igraph_static_power_law_game.out create mode 100644 tests/unit/igraph_strvector.c create mode 100644 tests/unit/igraph_strvector.out create mode 100644 tests/unit/igraph_subcomponent.c create mode 100644 tests/unit/igraph_subcomponent.out create mode 100644 tests/unit/igraph_subisomorphic.c create mode 100644 tests/unit/igraph_subisomorphic_lad.c create mode 100644 tests/unit/igraph_to_directed.c create mode 100644 tests/unit/igraph_to_directed.out create mode 100644 tests/unit/igraph_to_prufer.c create mode 100644 tests/unit/igraph_transitive_closure.c create mode 100644 tests/unit/igraph_transitive_closure.out create mode 100644 tests/unit/igraph_transitivity_avglocal_undirected.c create mode 100644 tests/unit/igraph_transitivity_avglocal_undirected.out create mode 100644 tests/unit/igraph_transitivity_barrat.c create mode 100644 tests/unit/igraph_transitivity_barrat.out create mode 100644 tests/unit/igraph_tree_from_parent_vector.c create mode 100644 tests/unit/igraph_tree_from_parent_vector.out create mode 100644 tests/unit/igraph_triangular_lattice.c create mode 100644 tests/unit/igraph_triangular_lattice.out create mode 100644 tests/unit/igraph_trussness.c create mode 100644 tests/unit/igraph_trussness.out create mode 100644 tests/unit/igraph_turan.c create mode 100644 tests/unit/igraph_turan.out create mode 100644 tests/unit/igraph_unfold_tree.c create mode 100644 tests/unit/igraph_unfold_tree.out create mode 100644 tests/unit/igraph_union.c create mode 100644 tests/unit/igraph_union.out create mode 100644 tests/unit/igraph_vector_floor.c create mode 100644 tests/unit/igraph_vector_floor.out create mode 100644 tests/unit/igraph_vector_lex_cmp.c create mode 100644 tests/unit/igraph_vector_lex_cmp.out create mode 100644 tests/unit/igraph_vertex_disjoint_paths.c create mode 100644 tests/unit/igraph_voronoi.c create mode 100644 tests/unit/igraph_voronoi.out create mode 100644 tests/unit/igraph_weighted_adjacency.c create mode 100644 tests/unit/igraph_weighted_adjacency.out create mode 100644 tests/unit/igraph_weighted_biadjacency.c create mode 100644 tests/unit/igraph_weighted_biadjacency.out create mode 100644 tests/unit/igraph_weighted_cliques.c create mode 100644 tests/unit/igraph_weighted_cliques.out create mode 100644 tests/unit/igraph_wheel.c create mode 100644 tests/unit/igraph_wheel.out create mode 100644 tests/unit/igraph_write_graph_dimacs_flow.c create mode 100644 tests/unit/igraph_write_graph_dimacs_flow.out create mode 100644 tests/unit/igraph_write_graph_dot.c create mode 100644 tests/unit/igraph_write_graph_dot.out create mode 100644 tests/unit/igraph_write_graph_leda.c create mode 100644 tests/unit/igraph_write_graph_leda.out create mode 100644 tests/unit/inclist.c create mode 100644 tests/unit/inclist.out create mode 100644 tests/unit/input.dl create mode 100644 tests/unit/is_coloring.c create mode 100644 tests/unit/isoclasses.c create mode 100644 tests/unit/isoclasses.out create mode 100644 tests/unit/isoclasses2.c create mode 100644 tests/unit/isomorphism_test.c create mode 100644 tests/unit/isomorphism_test.out create mode 100644 tests/unit/jdm.c create mode 100644 tests/unit/jdm.out create mode 100644 tests/unit/kary_tree.c create mode 100644 tests/unit/kary_tree.out create mode 100644 tests/unit/knn.c create mode 100644 tests/unit/knn.out create mode 100644 tests/unit/levc-stress.c create mode 100644 tests/unit/lineendings.c create mode 100644 tests/unit/lineendings.out create mode 100644 tests/unit/links.net create mode 100644 tests/unit/marked_queue.c create mode 100644 tests/unit/matrix.c create mode 100644 tests/unit/matrix.out create mode 100644 tests/unit/matrix2.c create mode 100644 tests/unit/matrix2.out create mode 100644 tests/unit/matrix3.c create mode 100644 tests/unit/matrix_complex.c create mode 100644 tests/unit/matrix_complex.out create mode 100644 tests/unit/max_results.c create mode 100644 tests/unit/maximal_cliques_callback.c create mode 100644 tests/unit/maximal_cliques_hist.c create mode 100644 tests/unit/maximal_cliques_hist.out create mode 100644 tests/unit/minimum_spanning_tree.c create mode 100644 tests/unit/minimum_spanning_tree.out create mode 100644 tests/unit/mycielskian.c create mode 100644 tests/unit/mycielskian.out create mode 100644 tests/unit/ncol.c create mode 100644 tests/unit/ncol.out create mode 100644 tests/unit/null_communities.c create mode 100644 tests/unit/overflow.c create mode 100644 tests/unit/pajek.c create mode 100644 tests/unit/pajek1.net create mode 100644 tests/unit/pajek2.c create mode 100644 tests/unit/pajek2.net create mode 100644 tests/unit/pajek2.out create mode 100644 tests/unit/pajek3.net create mode 100644 tests/unit/pajek4.net create mode 100644 tests/unit/pajek5.net create mode 100644 tests/unit/pajek6.net create mode 100644 tests/unit/pajek_arcslist.net create mode 100644 tests/unit/pajek_bip.net create mode 100644 tests/unit/pajek_bip2.net create mode 100644 tests/unit/pajek_bipartite.c create mode 100644 tests/unit/pajek_bipartite.out create mode 100644 tests/unit/pajek_bipartite2.c create mode 100644 tests/unit/pajek_bipartite2.out create mode 100644 tests/unit/pajek_edgeslist.net create mode 100644 tests/unit/pajek_signed.c create mode 100644 tests/unit/pajek_signed.net create mode 100644 tests/unit/pajek_signed.out create mode 100644 tests/unit/paths.c create mode 100644 tests/unit/paths.out create mode 100644 tests/unit/percolation.c create mode 100644 tests/unit/percolation.out create mode 100644 tests/unit/prop_caching.c create mode 100644 tests/unit/random_sampling.c create mode 100644 tests/unit/random_spanning_tree.c create mode 100644 tests/unit/reachability.c create mode 100644 tests/unit/reachability.out create mode 100644 tests/unit/rich_club.c create mode 100644 tests/unit/rich_club.out create mode 100644 tests/unit/ring.c create mode 100644 tests/unit/ring.out create mode 100644 tests/unit/rng_init_destroy_max_bits_name_set_default.c create mode 100644 tests/unit/rng_init_destroy_max_bits_name_set_default.out create mode 100644 tests/unit/rng_reproducibility.c create mode 100644 tests/unit/rng_reproducibility.out create mode 100644 tests/unit/set.c create mode 100644 tests/unit/set.out create mode 100644 tests/unit/si2_b06m_s20-bad1.A98 create mode 100644 tests/unit/si2_b06m_s20-bad2.A98 create mode 100644 tests/unit/si2_b06m_s20.A98 create mode 100644 tests/unit/simplify_and_colorize.c create mode 100644 tests/unit/simplify_and_colorize.out create mode 100644 tests/unit/single_target_shortest_path.c create mode 100644 tests/unit/single_target_shortest_path.out create mode 100644 tests/unit/spinglass.c create mode 100644 tests/unit/spinglass.out create mode 100644 tests/unit/stack.c create mode 100644 tests/unit/strvector_set_len_remove_print.c create mode 100644 tests/unit/strvector_set_len_remove_print.out create mode 100644 tests/unit/symmetric_tree.c create mode 100644 tests/unit/symmetric_tree.out create mode 100644 tests/unit/test.graphml create mode 100644 tests/unit/test_utilities.c create mode 100644 tests/unit/test_utilities.h create mode 100644 tests/unit/tls1.c create mode 100644 tests/unit/tls2.c create mode 100644 tests/unit/tls2.out create mode 100644 tests/unit/topological_sorting.c create mode 100644 tests/unit/topological_sorting.out create mode 100644 tests/unit/tree_game.c create mode 100644 tests/unit/triad_census.c create mode 100644 tests/unit/triad_census.out create mode 100644 tests/unit/trie.c create mode 100644 tests/unit/trie.out create mode 100644 tests/unit/utf8_with_bom.net create mode 100644 tests/unit/vector.c create mode 100644 tests/unit/vector.out create mode 100644 tests/unit/vector2.c create mode 100644 tests/unit/vector2.out create mode 100644 tests/unit/vector3.c create mode 100644 tests/unit/vector4.c create mode 100644 tests/unit/vector4.out create mode 100644 tests/unit/vector_list.c create mode 100644 tests/unit/vector_list.out create mode 100644 tests/unit/vector_ptr.c create mode 100644 tests/unit/vector_ptr_qsort_ind.out create mode 100644 tests/unit/vector_ptr_sort_ind.c create mode 100644 tests/unit/vector_sort_ind.c create mode 100644 tests/unit/vector_sort_ind.out create mode 100644 tests/unit/vertex_selectors.c create mode 100644 tests/unit/vertex_selectors.out create mode 100644 tests/unit/watts_strogatz_game.c create mode 100644 tests/unit/widest_paths.c create mode 100644 tests/unit/widest_paths.out create mode 100644 tests/unit/wikti_en_V_syn.elist create mode 100644 tests/unit/zapsmall.c create mode 100644 tests/unit/zapsmall.out create mode 100644 tests/unit/zero_allocs.c create mode 100644 tools/removeexamples.py create mode 100644 tools/strip_licenses_from_examples.py create mode 100644 vendor/CMakeLists.txt create mode 100644 vendor/cs/CMakeLists.txt create mode 100644 vendor/cs/License.txt create mode 100644 vendor/cs/cs.h create mode 100644 vendor/cs/cs_add.c create mode 100644 vendor/cs/cs_amd.c create mode 100644 vendor/cs/cs_chol.c create mode 100644 vendor/cs/cs_cholsol.c create mode 100644 vendor/cs/cs_compress.c create mode 100644 vendor/cs/cs_counts.c create mode 100644 vendor/cs/cs_cumsum.c create mode 100644 vendor/cs/cs_dfs.c create mode 100644 vendor/cs/cs_dmperm.c create mode 100644 vendor/cs/cs_droptol.c create mode 100644 vendor/cs/cs_dropzeros.c create mode 100644 vendor/cs/cs_dupl.c create mode 100644 vendor/cs/cs_entry.c create mode 100644 vendor/cs/cs_ereach.c create mode 100644 vendor/cs/cs_etree.c create mode 100644 vendor/cs/cs_fkeep.c create mode 100644 vendor/cs/cs_gaxpy.c create mode 100644 vendor/cs/cs_happly.c create mode 100644 vendor/cs/cs_house.c create mode 100644 vendor/cs/cs_ipvec.c create mode 100644 vendor/cs/cs_leaf.c create mode 100644 vendor/cs/cs_load.c create mode 100644 vendor/cs/cs_lsolve.c create mode 100644 vendor/cs/cs_ltsolve.c create mode 100644 vendor/cs/cs_lu.c create mode 100644 vendor/cs/cs_lusol.c create mode 100644 vendor/cs/cs_malloc.c create mode 100644 vendor/cs/cs_maxtrans.c create mode 100644 vendor/cs/cs_multiply.c create mode 100644 vendor/cs/cs_norm.c create mode 100644 vendor/cs/cs_permute.c create mode 100644 vendor/cs/cs_pinv.c create mode 100644 vendor/cs/cs_post.c create mode 100644 vendor/cs/cs_print.c create mode 100644 vendor/cs/cs_pvec.c create mode 100644 vendor/cs/cs_qr.c create mode 100644 vendor/cs/cs_qrsol.c create mode 100644 vendor/cs/cs_randperm.c create mode 100644 vendor/cs/cs_reach.c create mode 100644 vendor/cs/cs_scatter.c create mode 100644 vendor/cs/cs_scc.c create mode 100644 vendor/cs/cs_schol.c create mode 100644 vendor/cs/cs_spsolve.c create mode 100644 vendor/cs/cs_sqr.c create mode 100644 vendor/cs/cs_symperm.c create mode 100644 vendor/cs/cs_tdfs.c create mode 100644 vendor/cs/cs_transpose.c create mode 100644 vendor/cs/cs_updown.c create mode 100644 vendor/cs/cs_usolve.c create mode 100644 vendor/cs/cs_util.c create mode 100644 vendor/cs/cs_utsolve.c create mode 100644 vendor/infomap/CITATION.cff create mode 100644 vendor/infomap/CMakeLists.txt create mode 100644 vendor/infomap/LICENSE_GPLv3.txt create mode 100644 vendor/infomap/README.rst create mode 100644 vendor/infomap/src/Infomap.h create mode 100644 vendor/infomap/src/core/BiasedMapEquation.cpp create mode 100644 vendor/infomap/src/core/BiasedMapEquation.h create mode 100644 vendor/infomap/src/core/FlowData.h create mode 100644 vendor/infomap/src/core/InfoEdge.cpp create mode 100644 vendor/infomap/src/core/InfoEdge.h create mode 100644 vendor/infomap/src/core/InfoNode.cpp create mode 100644 vendor/infomap/src/core/InfoNode.h create mode 100644 vendor/infomap/src/core/InfomapBase.cpp create mode 100644 vendor/infomap/src/core/InfomapBase.h create mode 100644 vendor/infomap/src/core/InfomapConfig.h create mode 100644 vendor/infomap/src/core/InfomapOptimizer.h create mode 100644 vendor/infomap/src/core/InfomapOptimizerBase.h create mode 100644 vendor/infomap/src/core/MapEquation.h create mode 100644 vendor/infomap/src/core/MemMapEquation.cpp create mode 100644 vendor/infomap/src/core/MemMapEquation.h create mode 100644 vendor/infomap/src/core/MetaMapEquation.cpp create mode 100644 vendor/infomap/src/core/MetaMapEquation.h create mode 100644 vendor/infomap/src/core/StateNetwork.cpp create mode 100644 vendor/infomap/src/core/StateNetwork.h create mode 100644 vendor/infomap/src/core/iterators/InfomapIterator.cpp create mode 100644 vendor/infomap/src/core/iterators/InfomapIterator.h create mode 100644 vendor/infomap/src/core/iterators/IterWrapper.h create mode 100644 vendor/infomap/src/core/iterators/infomapIterators.h create mode 100644 vendor/infomap/src/core/iterators/treeIterators.h create mode 100644 vendor/infomap/src/io/ClusterMap.cpp create mode 100644 vendor/infomap/src/io/ClusterMap.h create mode 100644 vendor/infomap/src/io/Config.cpp create mode 100644 vendor/infomap/src/io/Config.h create mode 100644 vendor/infomap/src/io/Network.cpp create mode 100644 vendor/infomap/src/io/Network.h create mode 100644 vendor/infomap/src/io/Output.cpp create mode 100644 vendor/infomap/src/io/Output.h create mode 100644 vendor/infomap/src/io/ProgramInterface.cpp create mode 100644 vendor/infomap/src/io/ProgramInterface.h create mode 100644 vendor/infomap/src/io/SafeFile.h create mode 100644 vendor/infomap/src/utils/Date.h create mode 100644 vendor/infomap/src/utils/FileURI.cpp create mode 100644 vendor/infomap/src/utils/FileURI.h create mode 100644 vendor/infomap/src/utils/FlowCalculator.cpp create mode 100644 vendor/infomap/src/utils/FlowCalculator.h create mode 100644 vendor/infomap/src/utils/Log.cpp create mode 100644 vendor/infomap/src/utils/Log.h create mode 100644 vendor/infomap/src/utils/MetaCollection.h create mode 100644 vendor/infomap/src/utils/Random.h create mode 100644 vendor/infomap/src/utils/Stopwatch.h create mode 100644 vendor/infomap/src/utils/VectorMap.h create mode 100644 vendor/infomap/src/utils/convert.h create mode 100644 vendor/infomap/src/utils/infomath.h create mode 100644 vendor/infomap/src/version.h create mode 100644 vendor/nanoflann/nanoflann.hpp create mode 100644 vendor/pcg/CMakeLists.txt create mode 100644 vendor/pcg/LICENSE.txt create mode 100644 vendor/pcg/pcg-advance-128.c create mode 100644 vendor/pcg/pcg-advance-64.c create mode 100644 vendor/pcg/pcg-output-128.c create mode 100644 vendor/pcg/pcg-output-32.c create mode 100644 vendor/pcg/pcg-output-64.c create mode 100644 vendor/pcg/pcg-rngs-128.c create mode 100644 vendor/pcg/pcg-rngs-64.c create mode 100644 vendor/pcg/pcg_variants.h create mode 100644 vendor/qhull/CHANGES.md create mode 100644 vendor/qhull/CMakeLists.txt create mode 100644 vendor/qhull/COPYING.txt create mode 100644 vendor/qhull/README.txt create mode 100644 vendor/qhull/libqhull_r/accessors_r.c create mode 100644 vendor/qhull/libqhull_r/geom2_r.c create mode 100644 vendor/qhull/libqhull_r/geom_r.c create mode 100644 vendor/qhull/libqhull_r/geom_r.h create mode 100644 vendor/qhull/libqhull_r/global_r.c create mode 100644 vendor/qhull/libqhull_r/io_r.c create mode 100644 vendor/qhull/libqhull_r/io_r.h create mode 100644 vendor/qhull/libqhull_r/libqhull_r.c create mode 100644 vendor/qhull/libqhull_r/libqhull_r.h create mode 100644 vendor/qhull/libqhull_r/mem_r.c create mode 100644 vendor/qhull/libqhull_r/mem_r.h create mode 100644 vendor/qhull/libqhull_r/merge_r.c create mode 100644 vendor/qhull/libqhull_r/merge_r.h create mode 100644 vendor/qhull/libqhull_r/poly2_r.c create mode 100644 vendor/qhull/libqhull_r/poly_r.c create mode 100644 vendor/qhull/libqhull_r/poly_r.h create mode 100644 vendor/qhull/libqhull_r/qhull_ra.h create mode 100644 vendor/qhull/libqhull_r/qset_r.c create mode 100644 vendor/qhull/libqhull_r/qset_r.h create mode 100644 vendor/qhull/libqhull_r/random_r.c create mode 100644 vendor/qhull/libqhull_r/random_r.h create mode 100644 vendor/qhull/libqhull_r/rboxlib_r.c create mode 100644 vendor/qhull/libqhull_r/stat_r.c create mode 100644 vendor/qhull/libqhull_r/stat_r.h create mode 100644 vendor/qhull/libqhull_r/user_r.c create mode 100644 vendor/qhull/libqhull_r/user_r.h create mode 100644 vendor/qhull/libqhull_r/usermem_r.c create mode 100644 vendor/qhull/libqhull_r/userprintf_r.c create mode 100644 vendor/qhull/libqhull_r/userprintf_rbox_r.c diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md new file mode 100644 index 0000000..9702108 --- /dev/null +++ b/ACKNOWLEDGEMENTS.md @@ -0,0 +1,293 @@ +# Acknowledgements + +[igraph](https://igraph.org) includes or links to code from the following sources. + + +#### [ARPACK-NG 3.7.0](https://github.com/opencollab/arpack-ng) + +BSD Software License + +Pertains to ARPACK and P_ARPACK + +Copyright (c) 1996-2008 Rice University. +Developed by D.C. Sorensen, R.B. Lehoucq, C. Yang, and K. Maschhoff. +All rights reserved. + +Arpack has been renamed to arpack-ng. + +Copyright (c) 2001-2011 - Scilab Enterprises +Updated by Allan Cornet, Sylvestre Ledru. + +Copyright (c) 2010 - Jordi Gutiérrez Hermoso (Octave patch) + +Copyright (c) 2007 - Sébastien Fabbro (gentoo patch) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#### [bliss 0.75](https://users.aalto.fi/~tjunttil/bliss/) + +Copyright (c) 2003-2021 Tommi Junttila. + +License: [GNU LGPLv3][lgpl3] + + +#### [Cliquer 1.21](https://users.aalto.fi/~pat/cliquer.html) + +Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + +License: [GNU GPLv2][gpl2] or later + + +#### [PRPACK](https://github.com/dgleich/prpack) + +Copyright (C) David Kurokawa, David Gleich, Chen Greif. + + +#### [gengraph](https://www-complexnetworks.lip6.fr/~latapy/FV/generation.html) + +Algorithm by Fabien Viger and Matthieu Latapy. + +Implementation Copyright (C) Fabien Viger. + +License: [GNU GPLv2][gpl2] or later + + +#### [Walktrap 0.2](https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html) + +Algorithm by Pascal Pons and Matthieu Latapy. + +Implementation Copyright (C) 2004-2005 Pascal Pons. + +License: [GNU GPLv2][gpl2] or later + + +#### [plfit](https://github.com/ntamas/plfit) + +Copyright (C) 2010-2011 Tamás Nepusz. + +License: [GNU GPLv2][gpl2] or later + + +#### DrL + +Copyright 2007 Sandia Corporation. Under the terms of Contract +DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains +certain rights in this software. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * Neither the name of Sandia National Laboratories nor the names of +its contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#### [Hierarchical Random Graphs](https://aaronclauset.github.io/hierarchy/) + +Copyright (C) 2006-2008 Aaron Clauset. + +License: [GNU GPLv2][gpl2] or later + + +#### Spinglass community detection + +Copyright (C) 2004 by Joerg Reichardt. + +License: [GNU GPLv2][gpl2] or later + + +#### [LAD version 1](http://liris.cnrs.fr/csolnon/LAD.html) + +Copyright (C) Christine Solnon. + +License: [CeCILL-B license](https://cecill.info/licences.en.html) + + +#### [LAPACK 3.5.0](http://www.netlib.org/lapack/) and [BLAS 3.12.0](http://www.netlib.org/blas/) + +Copyright (c) 1992-2013 The University of Tennessee and The University of Tennessee Research Foundation. All rights reserved. + +Copyright (c) 2000-2013 The University of California Berkeley. All rights reserved. + +Copyright (c) 2006-2013 The University of Colorado Denver. All rights reserved. + +License: [New BSD license](http://www.netlib.org/lapack/LICENSE.txt) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +The copyright holders provide no reassurances that the source code +provided does not infringe any patent, copyright, or any other +intellectual property rights of third parties. The copyright holders +disclaim any liability to any recipient for claims brought against +recipient by any third party for infringement of that parties +intellectual property rights. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#### [f2c](http://www.netlib.org/f2c/) + +Copyright 1990 - 1997 by AT&T, Lucent Technologies and Bellcore. + +Permission to use, copy, modify, and distribute this software +and its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the names of AT&T, Bell Laboratories, +Lucent or Bellcore or any of their entities not be used in +advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +AT&T, Lucent and Bellcore disclaim all warranties with regard to +this software, including all implied warranties of +merchantability and fitness. In no event shall AT&T, Lucent or +Bellcore be liable for any special, indirect or consequential +damages or any damages whatsoever resulting from loss of use, +data or profits, whether in an action of contract, negligence or +other tortious action, arising out of or in connection with the +use or performance of this software. + + +#### [SuiteSparse](http://www.suitesparse.com) + +CXSPARSE: a Concise Sparse Matrix package - Extended. Copyright (c) 2006-2017, Timothy A. Davis. + +License: [GNU LGPLv2.1][lgpl2] or later + + +#### [Infomap](https://www.mapequation.org/) + +Infomap software package for multi-level network clustering +Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + +License: [GNU GPLv3][gpl3] or later + + +#### [GLPK (GNU Linear Programming Kit) Version 5.0](https://www.gnu.org/software/glpk/) + +Copyright (C) 2000-2020 Free Software Foundation, Inc. + +Written by Andrew Makhorin, Department for Applied Informatics, +Moscow Aviation Institute, Moscow, Russia. E-mail: . + +License: [GNU GPLv3][gpl3] or later + + +#### [GMP (GNU Multiple Precision Arithmetic Library) and mini-gmp](https://gmplib.org/) + +Copyright (C) Free Software Foundation, Inc. + +License: [GNU LGPLv3][lgpl3] or later; or [GNU GPLv2][gpl2] or later + + +#### [libxml2](http://xmlsoft.org/) + +Copyright (C) 1998-2012 Daniel Veillard. + +License: [MIT license][mit] + + +#### [nanoflann](https://github.com/jlblancoc/nanoflann) + +Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). +Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). +Copyright 2011 Jose L. Blanco (joseluisblancoc@gmail.com). + +License: [BSD license][bsd] + + +#### [Qhull](http://www.qhull.org/) + +Qhull, Copyright (c) 1993-2020 + +C.B. Barber, Arlington, MA + +and + +The National Science and Technology Research Center for +Computation and Visualization of Geometric Structures +(The Geometry Center) +University of Minnesota + +License: [Qhull license][qhull] + + + [bsd]: https://opensource.org/license/BSD-2-Clause + [mit]: https://opensource.org/licenses/mit-license.html + [qhull]: http://www.qhull.org/COPYING.txt + [gpl2]: https://www.gnu.org/licenses/gpl-2.0.html + [lgpl2]: https://www.gnu.org/licenses/lgpl-2.1.html + [gpl3]: https://www.gnu.org/licenses/gpl-3.0.html + [lgpl3]: https://www.gnu.org/licenses/lgpl-3.0.html diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..446955d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Gabor Csardi +Tamas Nepusz +Szabolcs Horvat +Vincent Traag +Fabio Zanini +Daniel Noom diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..094ca27 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1827 @@ +# igraph C library changelog + +## [1.0.1] + +### Fixed + +- Eliminated references to `exit()` from the igraph shared library. These were accidentally introduced into igraph 1.0.0 through Qhull and Infomap. +- Eliminated references to `std::cout` from the igraph shared library, as required by CRAN. +- Fixed a bug in `igraph_hub_and_authority_scores()` that printed a warning about zero entries in the result even when the number of zeros was below the threshold used in the corresponding checks. +- Fixed compilation and tests when Infomap support is disabled. +- Fixed rare compilation issues on some Apple systems with some non-standard compilers due to incompatibilities between defining the `_POSIX_C_SOURCE` feature test macro and standard C++ headers. Now `_DARWIN_C_SOURCE` is defined when compiling igraph on Apple systems. +- Fixed inconsistent libf2c prototypes for `s_copy()` and `s_cat()`. This restores compatibility with emscripten. + +### Other + +- Documentation improvements. +- nanoflann was updated to version 1.9.0 + +## [1.0.0] + +Nearly twenty years after the first igraph release, igraph 1.0 has finally arrived. This release focuses on providing a stable and more consistent interface that users and downstream maintainers can rely on with confidence, as well as adding new features that required API-breaking changes. There is now an official versioning policy, see [`VERSIONING.md`](VERSIONING.md). + +### Highlights + +- A more consistent and more predictable API. +- Explicit versioning policy. +- Several random graph generators, including the Erdős-Rényi generators, can now produce graphs with multi-edges. +- Several functions that can generate a large number of results (cliques, cycles, etc.) now have a feature to limit the number of returned results, or to return a single result only. +- Functionality for generating several kinds of spatial networks. + +### Breaking changes + +This section lists API-breaking changes in this version, and provides guidance on adapting code written for igraph 0.10.x. + +#### General changes + +- igraph now requires a C++ compiler that supports the C++14 standard. +- `igraph_setup()` is now recommended to be called before using the library. This function may gain essential functions in the future. See the "Added" section below for details. +- `igraph_integer_t` was renamed to `igraph_int_t`, but `igraph_integer_t` is kept as an alias and it will remain available for at least the next major version. Library headers and source code uses `igraph_int_t` from now on. +- `igraph_rng_set_default()` now returns a pointer to the previous default RNG. Furthermore, this function now only stores a pointer to the `igraph_rng_t` struct passed to it, instead of copying the struct. Thus the `igraph_rng_t` object must continue to exist for as long as it is used as the default RNG. +- Interruption handlers do not take a `void *` argument anymore; this is relevant to maintainers of higher-level interfaces only. +- Interruption handlers now return an `igraph_bool_t` instead of an `igraph_error_t`; the returned value must be true if the calculation has to be interrupted and false otherwise. +- `igraph_status()`, `igraph_statusf()` and their macro versions (`IGRAPH_STATUS()` and `IGRAPH_STATUSF()`) do not convert error codes to `IGRAPH_INTERRUPTED` any more. Any error code returned from the status handler function is forwarded intact to the caller. If you want to trigger the interruption of the current calculation from the status handler without reporting an error, report `IGRAPH_INTERRUPTED` explicitly. It is the responsibility of higher-level interfaces to handle this error code appropriately. +- The `RNG_BEGIN()` and `RNG_END()` macros were removed. You are now responsible for seeding the RNG before using any igraph function that may use random numbers by calling `igraph_rng_seed(igraph_rng_default(), ...)`, or by simply ensuring that `igraph_setup()` was called before the first use of the library. +- Projects that depend on igraph must only include the `` header. While an igraph installation includes several sub-headers, these are for organizational purposes only, and their contents may change without notice. Only `#include ` is supported. + +#### Error codes + +- The `IGRAPH_EINVEVECTOR` error code was removed; `igraph_create()` and `igraph_add_edges()` that used to return this error code for invalid edge vectors will now return `IGRAPH_EINVAL` instead. +- The `IGRAPH_NONSQUARE` error code was removed; functions that used this error code now return `IGRAPH_EINVAL` instead when encountering a non-square matrix. +- The `IGRAPH_EGLP` error code and all other GLP-specific error codes (starting with `IGRAPH_GLP_`) were removed; functions that used this error code now return `IGRAPH_FAILURE` instead, providing more details in the message associated to the error code. +- The `IGRAPH_ELAPACK` error code was removed; functions that used this error code now return `IGRAPH_FAILURE` instead, providing more details in the message associated to the error code. +- The `IGRAPH_CPUTIME` error code was removed in favour of the interruption mechanism built into igraph. +- The unused `IGRAPH_EDIVZERO` and `IGRAPH_EATTRIBUTES` error codes were removed with no replacement. +- The deprecated error code `IGRAPH_ENEGLOOP` was removed. Use `IGRAPH_ENEGCYCLE` instead. The underlying numerical value is the same as it was for `IGRAPH_ENEGLOOP`. +- ARPACK-specific error codes (starting with `IGRAPH_ARPACK_...`) were replaced with a single `IGRAPH_EARPACK` error code. Details about the underlying ARPACK failure are provided in the error message. +- A new error code called `IGRAPH_EINVEID` was added for cases when an invalid edge ID was encountered in an edge ID vector. + +#### Core data structures + +- `igraph_strvector_push_back_len()` now takes a length parameter of `size_t` instead of `igraph_int_t`. +- `igraph_strvector_print()` no longer takes a file parameter. Use `igraph_strvector_fprint()` to print to a file. +- `igraph_vector_reverse()` no longer returns an error code. +- `igraph_vector_shuffle()` no longer returns an error code. +- `igraph_vector_swap()` and `igraph_matrix_swap()` no longer return an error code. +- `igraph_vector_list_swap()` and `igraph_graph_list_swap()` no longer return an error code. +- `igraph_vector_swap_elements()` no longer returns an error code. +- `igraph_vector_list_swap_elements()` and `igraph_graph_list_swap_elements()` no longer return an error code. +- `igraph_matrix_copy_to()` gained an `igraph_matrix_storage_t storage` parameter that specifies whether the data should be written in column-major or row-major format. +- `igraph_vector_view()`, `igraph_matrix_view()`, `igraph_matrix_view_from_vector()`, and `igraph_vector_ptr_view()` now return their result as a return value instead of an output parameter. This allows assigning the value to a `const` variable without triggering warnings with modern compilers. + +#### Attribute handling + +- `igraph_attribute_handler_t` members that formerly took an untyped `igraph_vector_ptr_t` argument are now taking a typed `igraph_attribute_record_list_t` argument instead. +- The deprecated `IGRAPH_ATTRIBUTE_DEFAULT` value of the `igraph_attribute_type_t` enum was removed. +- The `gettype` member of `igraph_attribute_table_t` was renamed to `get_type` for consistency with the naming scheme of other struct members. +- Attribute table members that retrieve graph, vertex or edge attributes must not clear the incoming result vector any more; results must be appended to the end of the provided result vector instead. +- The `value` member of `igraph_attribute_record_t` is now a union that can be used to formally treat the associated pointer as an `igraph_vector_t *`, `igraph_strvector_t *` or `igraph_vector_bool_t *`. + +#### Core graph manipulation + +- `igraph_delete_vertices_map()` (formerly called `igraph_delete_vertices_idx()`) and `igraph_induced_subgraph_map()` now use `-1` to represent unmapped vertices in the returned forward mapping vector and they do not offset vertex indices by 1 any more. Note that the inverse map always behaved this way, this change makes the two mappings consistent. +- `igraph_edges()` gained a new `igraph_bool_t bycol` argument that determines the order in which the edges are returned. `bycol = false` reproduces the existing behaviour, while `bycol = true` returns the edges suitable for a matrix stored in column-wise order. +- `igraph_neighbors()` and `igraph_vs_adj()` gained two extra arguments, `igraph_loops_t loops` and `igraph_bool_t multiple` to specify what to do with loop and multiple edges. This makes their interfaces consistent with `igraph_adjlist_init()`. Use `loops = IGRAPH_LOOPS_TWICE` and `multiple = true` to reproduce the previous behavior. +- `igraph_incident()` and `igraph_es_incident()` gained an extra `igraph_loops_t loops` argument to specify what to do with loop edges. This makes their interfaces consistent with `igraph_inclist_init()`. Use `loops = IGRAPH_LOOPS_TWICE` to reproduce the previous behavior. +- The `igraph_multiple_t` enum type was removed from the public API as it was essentially a Boolean. The symbolic constants `IGRAPH_MULTIPLE` (same as `true`) and `IGRAPH_NO_MULTIPLE` (same as `false`) were kept to improve readability of code written directly in C. + +#### Basic graph properties + +- `igraph_density()` now takes an optional `weights` parameter. +- `igraph_is_simple()` gained an extra `igraph_bool_t` argument that decides whether edge directions should be considered. Directed graphs with a mutual edge pair are treated as non-simple if this argument is set to `IGRAPH_UNDIRECTED` (which treats the graph as if it was undirected). +- The type of the `loops` argument of `igraph_adjlist_init_complementer()`, `igraph_centralization_degree()`, `igraph_centralization_degree_tmax()`, `igraph_degree()`, `igraph_maxdegree()`, `igraph_sort_vertex_ids_by_degree()` and `igraph_strength()` was changed to `igraph_loops_t` from `igraph_bool_t`, allowing finer-grained control about how loop edges are treated. Pass `IGRAPH_LOOPS_TWICE` and `IGRAPH_NO_LOOPS` to reproduce the previous behaviour of `true` and `false`. +- `igraph_get_biadjacency()` now takes a `weights` parameter, and can optionally create weighted biadjacency matrices. +- `igraph_adjacency()` now treats `IGRAPH_LOOPS_TWICE` as `IGRAPH_LOOPS_ONCE` when the mode is `IGRAPH_ADJ_DIRECTED`, `IGRAPH_ADJ_UPPER` or `IGRAPH_ADJ_LOWER`. For directed graphs, this is for the sake of consistency with the rest of the library where `IGRAPH_LOOPS_TWICE` is considered for undirected graphs only. For the "upper" and "lower" modes, double-counting the diagonal makes no sense because the double-counting artifact appears when you add the _transpose_ of an upper (or lower) diagonal matrix on top of the matrix itself. See Github issue #2501 for more context. + +#### Graph generators + +- `igraph_barabasi_game()`, `igraph_barabasi_aging_game()`, `igraph_recent_degree_game()` and `igraph_recent_degree_aging_game()` no longer interprets an empty `outseq` vector as a missing out-degree sequence. Pass `NULL` if you don't wish to specify an out-degree sequence. +- `igraph_degree_sequence_game()` no longer interprets an empty in-degree vector as a request for generating undirected graphs. To generate undirected graphs, pass `NULL` for in-degrees. +- `igraph_erdos_renyi_game_gnm()` uses a `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `igraph_bool_t loops`, and can now uniformly sample not only simple graphs but also multigraphs. It also gained an `edge_labeled` Boolean parameter which controls whether to sample from the set of ordered edge lists (equivalent to `igraph_iea_game()` for multigraphs). +- `igraph_erdos_renyi_game_gnp()` uses a `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `igraph_bool_t loops`, and can now sample multigraphs from a maximum entropy model with a prescribed _expected_ edge multiplicity. It also gained an `edge_labeled` Boolean parameter which controls whether to sample from the set of ordered edge lists. +- `igraph_bipartite_game_gnm()` gained an `igraph_edge_type_sw_t allowed_edge_types` parameter, and can now uniformly sample not only simple graphs but also multigraphs. It also gained an `edge_labeled` Boolean parameter which controls whether to sample from the set of ordered edge lists (equivalent to `igraph_bipartite_iea_game()` for multigraphs). +- `igraph_bipartite_game_gnp()` gained an `igraph_edge_type_sw_t allowed_edge_types` parameter, and can now sample multigraphs from a maximum entropy model with a prescribed _expected_ edge multiplicity. It also gained an `edge_labeled` Boolean parameter which controls whether to sample from the set of ordered edge lists. +- `igraph_lcf()` was renamed to `igraph_lcf_small()` and `igraph_lcf_vector()` was renamed to `igraph_lcf()`. Now `igraph_lcf()` takes shifts as a vector input, while `igraph_lcf_small()` accepts a shorthand notation where shifts are given as a variable number of function arguments. +- `igraph_sbm_game()` uses an `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `igraph_bool_t loops` and now supports generating graphs with multi-edges. The parameter determining the total number of vertices (`n`) was removed as it was redundant. +- `igraph_rewire_edges()` uses an `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `loops` and `multiple`. +- `igraph_rewire()` now takes an `igraph_edge_type_sw_t allowed_edge_types` parameter to specify whether to create self-loops. The `igraph_rewiring_t` enum type was removed. Instead of the old `IGRAPH_REWIRING_SIMPLE`, use `IGRAPH_SIMPLE_SW`. Instead of the old `IGRAPH_REWIRING_SIMPLE_LOOPS`, use `IGRAPH_LOOPS_SW`. +- `igraph_rewire()` now takes an optional `igraph_rewiring_stats_t *` output argument. You can pass the appropriate struct there to find out the number of successful swaps during the rewiring operation. +- `igraph_watts_strogatz_game()` uses an `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `loops` and `multiple`. +- `igraph_static_fitness_game()` uses an `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `loops` and `multiple`. +- `igraph_static_power_law_game()` uses an `igraph_edge_type_sw_t allowed_edge_types` parameter instead of `loops` and `multiple`. +- `igraph_correlated_game()` now takes the graph being constructed as the _first_ argument to remain consistent with other graph constructors. Earlier versions used to take the original graph as the first argument. +- The semantics of the `permutation` argument of `igraph_correlated_game()` and `igraph_correlated_pair_game()` has changed: the i-th element of the vector now contains the index of the _original_ vertex that will be mapped to the i-th vertex in the new graph. This is consistent with how `igraph_permute_vertices()` operates (which has also changed in igraph 1.0). + +#### Paths and cycles + +- `igraph_distances()`, `igraph_distances_cutoff()`, `igraph_get_shortest_path()`, `igraph_get_shortest_paths()` and `igraph_get_all_shortest_paths()` gained a `weights` argument. The functions now automatically select the appropriate implementation (unweighted, Dijkstra, Bellman-Ford or Johnson) algorithm based on whether weights are present and whether there are negative weights or not. You can still call the individual methods by their more specific names. +- `igraph_distances_johnson()` now takes an `igraph_neimode_t mode` parameter to determine in which direction paths should be followed. +- The weighted variants of `igraph_diameter()`, `igraph_pseudo_diameter()`, `igraph_radius()`, `igraph_graph_center()`, `igraph_eccentricity()` and `igraph_average_path_length()` were merged into the undirected ones by adding a new argument named `weights` in the second position. +- The `weights` parameter of `igraph_average_path_length()`, `igraph_global_efficiency()`, `igraph_local_efficiency()` and `igraph_average_local_efficiency()` were moved to the second position, after the `graph` itself, for consistency with other functions. +- `igraph_get_all_simple_paths()` returns its results in an integer vector list (`igraph_vector_int_list_t`) instead of a single integer vector. +- `igraph_get_all_simple_paths()` now has an additional parameter that allows restricting paths by minimum length as well, and its `mode` parameter was moved to before the path length limit parameters. +- `igraph_get_all_simple_paths()` gained a `max_results` parameter to limit the number of returned results. Pass `1` to return a single result, or `IGRAPH_UNLIMITED` to return all results. +- `igraph_simple_cycles()` gained a `max_results` parameter to limit the number of returned results. Pass `1` to return a single result, or `IGRAPH_UNLIMITED` to return all results. + +#### Community detection + +- `igraph_community_edge_betweenness()` now takes both a `weights` and a `lengths` parameter. Edge weights (interpreted as connection strengths) are used to divide betweenness scores before selecting them for removal as well as for the modularity computation. Edge lengths are used for defining shortest path lengths during the betweenness computation. This fixes issues #2229 and #1040. +- `igraph_community_infomap()` now supports regularization and gained the `is_regularized` and `regularization_strength` parameters. +- `igraph_community_label_propagation()` gained the `igraph_lpa_variant_t lpa_variant` parameter to allow specification of label propagation algorithm (LPA) variants. A new fast label propagation variant was added. Set `lpa_variant=DOMINANCE` to select the algorithm used up to igraph 0.10. +- `igraph_community_leiden()` now takes two `vertex_out_weights` and `vertex_in_weights` parameters in order to support directed graphs, instead of the previous single `node_weights` parameter. To obtain the old behavior for undirected graphs, pass the vertex weights as `vertex_out_weights` and set `vertex_in_weights` to `NULL`. +- The `history` parameter of `igraph_community_leading_eigenvector()` is now a pointer to an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_community_optimal_modularity()` now takes a `resolution` parameter and its `weight` parameter was moved to the second place. +- `igraph_community_spinglass_single()` now uses `igraph_real_t` for its `inner_links` and `outer_links` output parameters, as these return not simply edge counts, but the sum of the weights of some edges. + +#### Isomorphism functions and permutations + +- `igraph_count_automorphisms()` has been renamed to `igraph_count_automorphisms_bliss()` because it has a BLISS-specific interface. A new `igraph_count_automorphisms()` function was added with a simplified interface that does not depend on BLISS. +- `igraph_automorphism_group()` has been renamed to `igraph_automorphism_group_bliss()` because it has a BLISS-specific interface. A new `igraph_automorphism_group()` function was added with a simplified interface that does not depend on BLISS. +- `igraph_canonical_permutation()` has been renamed to `igraph_canonical_permutation_bliss()` because it has a BLISS-specific interface. A new `igraph_canonical_permutation()` function was added with a simplified interface that does not depend on BLISS. +- `igraph_subisomorphic_lad()` does not have a CPU time limit parameter any more. If you wish to stop the calculation from another thread or a higher level interface, use igraph's interruption mechanism. +- The semantics of the `igraph_permute_vertices()` permutation argument has changed: the i-th element of the vector now contains the index of the _original_ vertex that will be mapped to the i-th vertex in the new graph. This is now consistent with how other igraph functions treat permutations and vertex index vectors; for instance, you can now pass the result of `igraph_topological_sorting()` directly to `igraph_permute_vertices()` to obtain a new graph where the vertices are sorted topologically. +- As a consequence to the change in the semantics of the `igraph_permute_vertices()` permutation argument, the semantics of the permutations returned from `igraph_canonical_permutation()` and `igraph_canonical_permutation_bliss()` have also been inverted to maintain the invariant that the output of these functions can be fed into `igraph_permute_vertices()` directly. +- `igraph_isoclass_subgraph()` now takes a parameter of type `igraph_vs_t vids` instead of `igraph_vector_int_t vids`. Apply `igraph_vss_vector()` to the vector of vertex IDs to convert it to an `igraph_vs_t`. + +#### Centralities + +- All betweenness functions got a `normalized` parameter to support normalizing the result by the number of vertex pairs in the graph. At the same time, their parameter ordering was standardized. The following functions are affected: `igraph_betweenness()`, `igraph_betweenness_cutoff()`, `igraph_edge_betweenness()`, `igraph_edge_betweenness_cutoff()`, `igraph_betweenness_subset()`, `igraph_edge_betweenness_subset()`. +- `igraph_edge_betweenness()` and `igraph_edge_betweenness_cutoff()` now have an `eids` parameter to return only a subset of results. This makes their interface consistent with other betweenness functions. +- `igraph_eigenvector_centrality()`, `igraph_centralization_eigenvector_centrality()` and `igraph_centralization_eigenvector_centrality_tmax()` now use a `mode` parameter with possible values `IGRAPH_OUT`, `IGRAPH_IN` or `IGRAPH_ALL` to control how edge directions are considered. Previously they used a boolean `directed` parameter. +- `igraph_eigenvector_centrality()`, `igraph_centralization_eigenvector_centrality()` and `igraph_centralization_eigenvector_centrality_tmax()` no longer have a `scale` parameter. The result is now always scaled so that the largest centrality value is 1. +- `igraph_hub_and_authority_scores()` no longer has a `scale` parameter. The result is now always scaled so that the largest hub and authority scores are each 1. +- `igraph_pagerank()`, `igraph_personalized_pagerank()` and `igraph_personalized_pagerank_vs()` had their parameter ordering standardized. + +#### Cliques and independent sets + +- `igraph_cliques()`, `igraph_weighed_cliques()`, `igraph_maximal_cliques()`, `igraph_maximal_cliques_file()`, `igraph_maximal_cliques_subset()`, `igraph_independent_sets()` and `igraph_maximal_independent_sets()` received a `max_results` parameter to limit the number of returned results. Pass `1` to return a single result, or `IGRAPH_UNLIMITED` to return all results. +- `igraph_maximal_independent_sets()` received `min_size` and `max_size` parameters that control the range of independent set sizes that are returned. +- `igraph_weighted_cliques()` had its parameter ordering standardized: the `igraph_bool_t maximal` parameter now comes before the `min_weight` / `max_weight` parameters. +- `igraph_maximal_cliques_callback()` had its parameter ordering standardized: the `igraph_clique_handler_t *cliquehandler_fn, void *arg` parameter pair now comes at the end, making this function consistent with `igraph_cliques_callback()`. + +#### Layouts + +- `igraph_layout_sugiyama()` does not return an "extended graph" anymore. The bends in the edges of the layout are encoded in a list of matrices instead; each item in the list belongs to an edge of the original graph and contains the control points of the edge in a row-wise fashion. The matrix will have no rows if no control points are needed on the edge. + +#### Other network analysis + +- `igraph_similarity_jaccard()` and `igraph_similarity_dice()` now take two sets of vertices (`to` and `from` parameters) to create vertex pairs of, instead of one. Pass the same vertex set to both parameters to recover the previous behaviour. +- `igraph_minimum_spanning_tree()` takes a new `method` parameter that controls the algorithm used for finding the spanning tree. Kruskal's algorithm was added. +- `igraph_motifs_randesu_no()` and `igraph_motifs_randesu_estimate()` now take an `igraph_real_t` as their `result` argument to prevent overflows when igraph is compiled with 32-bit integers. +- The `igraph_motifs_handler_t` callback type now takes a `const igraph_vector_int_t *vids` parameter. Previously this was not formally `const`, even though it was not allowed to modify `vids`. This affects uses of `igraph_motifs_randesu_callback()`. +- The experimental functions `igraph_fundamental_cycles()` and `igraph_minimum_cycle_basis()` now use the type `igraph_real_t` for their `bfs_cutoff` parameter, and had their `weights` parameter moved to the 2nd position. + +#### Foreign formats + +- `igraph_read_graph_ncol()` and `igraph_read_graph_lgl()` now uses a default edge weight of 1 instead of 0 for files that do not contain edge weights for at least some of the edges. + +### Added + +- `igraph_setup()` performs all initialization tasks that are recommended before using the igraph library. Right now this function only initializes igraph's internal random number generator with a practically random seed, but it may also perform other tasks in the future. It is recommended to call this function before using any other function from the library (although most of the functions will work fine now even if this function is not called). +- `igraph_iea_game()` samples random multigraphs through independent edge assignment. +- `igraph_bipartite_iea_game()` samples random bipartite multigraph through independent edge assignment. +- `igraph_weighted_biadjacency()` creates a weighted graph from a bipartite adjacency matrix. +- `igraph_vector_ptr_capacity()` returns the allocated capacity of a pointer vector. +- `igraph_vector_ptr_resize_min()` deallocates unused capacity of a pointer vector. +- `igraph_strvector_fprint()` prints a string vector to a file. +- `igraph_rng_sample_dirichlet()`, `igraph_rng_sample_sphere_volume()` and `igraph_rng_sample_sphere_surface()` samples vectors from a Dirichlet distribution or from the volume or surface of a sphere while allowing the user to specify the random number generator to use. +- `igraph_nearest_neighbor_graph()` computes a neighborhood graph of spatial points based on a neighbor count, cutoff distance, and chosen metric (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2788! +- `igraph_delaunay_graph()` computes a Delaunay graph of a spatial point set (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2806! +- `igraph_gabriel_graph()` computes the Gabriel graph of a spatial point set (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2827! +- `igraph_relative_neighborhood_graph()` computes the relative neighborhood graph of a spatial point set (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2827! +- `igraph_lune_beta_skeleton()` and `igraph_circle_beta_skeleton()` compute the lune and circle based β-skeletons of a spatial point set (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2827! +- `igraph_beta_weighted_gabriel_graph()` computes a Gabriel graph of a spatial point set, along with a threshold β value for each edge, at which the edge ceases to be part of the lune-based β-skeleton (experimental function). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2827! +- `igraph_spatial_edge_lengths()` computes edge lengths based on spatial vertex coordinates (experimental function). +- `igraph_community_leiden_simple()` is a simplified interface to `igraph_community_leiden()` that allows selecting the objective function to maximize directly. +- `igraph_vector_difference_and_intersection_sorted()` calculates the intersection and the differences of two vectors simultaneously. +- `IGRAPH_UNLIMITED`, defined to `-1`, is a convenience constant for use with various "size limit" parameters, such as number of cliques returned, maximum path length, number of results returned, etc. It indicates that no limit should be used. + +### Changed + +- The Pajek format reader and writer now map vertex labels to the `name` vertex attribute in igraph. The `id` attribute is no longer used. +- `igraph_minimum_size_separators()` no longer returns any separating vertex sets for complete graphs. Prior to igraph 1.0, it would return all `n - 1` size vertex subsets where `n` is the vertex count. +- `igraph_community_edge_betweenness()` now treats edges with large weights as strong connections. +- `igraph_biadjacency()` now truncates non-integer matrix entries to their integer part instead of rounding them up. This brings consistency with related functions such as `igraph_adjacency()`. +- The order of edges in the graph returned by `igraph_(weighted_)adjacency()` and `igraph_biadjacency()` has changed. Note that these functions do not guarantee any specific edge order. +- `igraph_eigenvector_centrality()` now warns about eigenvector centralities equal to zero, as these indicate a disconnected graph, for which eigenvector centrality is not meaningful. +- `igraph_hub_and_authority_scores()` now warns when a large fraction of centrality scores are zero, as this indicates a non-unique solution, and thus the returned result may not be meaningful. +- `igraph_hub_and_authority_scores()` now warns when providing an undirected graph as input, and falls back to the equivalent eigenvector centrality computation. +- `igraph_get_stochastic_sparse()` no longer throws an error when some row or column sums are zero. This brings its behaviour in line with `igraph_get_stochastic()`. +- `igraph_vector_append()`, `igraph_strvector_append()` and `igraph_vector_ptr_append()` now use a different allocation strategy: if the `to` vector has insufficient capacity, they double its capacity. Previously they reserved precisely as much capacity as needed for appending the `from` vector. +- The implementation of the Infomap algorithm behind `igraph_community_infomap()` has been updated with a more recent version (2.8.0). Isolated vertices are now supported. +- `igraph_vector_difference_sorted()` now handles multisets properly (and documents how the multiplicities are handled). + +### Finalized experimental functions + +- The following functions are not experimental any more: `igraph_count_loops()`, `igraph_count_reachable()`, `igraph_degree_correlation_vector`, `igraph_distances_cutoff()`, `igraph_distances_floyd_warshall()`, `igraph_distances_dijkstra_cutoff()`, `igraph_ecc()`, `igraph_enter_safelocale()`, `igraph_exit_safelocale()`, `igraph_feedback_vertex_set()`, `igraph_find_cycle()`, `igraph_get_shortest_path_astar()`, `igraph_graph_power()`, `igraph_hexagonal_lattice()`, `igraph_hypercube()`, `igraph_is_bipartite_coloring()`, `igraph_is_clique()`, `igraph_is_complete()`, `igraph_is_edge_coloring()`, `igraph_is_vertex_coloring()`, `igraph_is_independent_vertex_set()`, `igraph_join()`,`igraph_joint_degree_distribution()`, `igraph_joint_degree_matrix()`, `igraph_joint_type_distribution()`, `igraph_layout_align()`, `igraph_layout_merge_dla()`, `igraph_mean_degree()`, `igraph_radius()`, `igraph_realize_bipartite_degree_sequence()`, `igraph_reachability()`, `igraph_transitive_closure()`, `igraph_tree_from_parent_vector()`, `igraph_triangular_lattice()`, `igraph_vector_intersection_size_sorted()`, `igraph_voronoi()`. + +### Fixed + +- `igraph_community_spinglass_single()` now uses `igraph_real_t` for its `inner_links` and `outer_links` output parameters, as these return not simply edge counts, but the sum of the weights of some edges. Thus these results are no longer incorrectly rounded. +- `igraph_correlated_game()` and `igraph_correlated_pair_game()` validate their `permutation` argument. + +### Removed + +- Removed `igraph_Calloc()`, `igraph_Realloc()` and `igraph_Free()`. Use `IGRAPH_CALLOC()`, `IGRAPH_REALLOC()` and `IGRAPH_FREE()` instead. +- The deprecated `igraph_adjacent_triangles()` was removed. Use `igraph_count_adjacent_triangles()` instead. +- The deprecated `igraph_are_connected()` was removed. Use `igraph_are_adjacent()` instead. +- The deprecated `igraph_automorphisms()` was removed. Use `igraph_count_automorphisms()` or `igraph_count_automorphisms_bliss()` instead. +- The deprecated `igraph_convex_hull()` was removed. Use `igraph_convex_hull_2d()` instead. +- The deprecated `igraph_decompose_destroy()` was removed. +- The deprecated `igraph_hub_score()` and `igraph_authority_score()` were removed. +- The deprecated `igraph_vs_seq()`, `igraph_vss_seq()`, `igraph_es_seq()`, `igraph_ess_range()`, and `igraph_vector_init_seq()` were removed. Use the `range` alternatives instead of the old `seq` ones. +- The deprecated `igraph_erdos_renyi_game()` and `igraph_bipartite_game()` were removed. Use the corresponding functions with `_gnm()` and `_gnp()` in the name instead. +- The deprecated `igraph_tree()` was removed. Use `igraph_kary_tree()` instead. +- The deprecated `igraph_lattice()` was removed. Use `igraph_square_lattice()` instead. +- The deprecated `igraph_minimum_spanning_tree_prim()` was removed. Use `igraph_minimum_spanning_tree()` in conjunction with `igraph_subgraph_from_edges()` instead. +- The deprecated `igraph_minimum_spanning_tree_unweighted()` was removed. Use `igraph_minimum_spanning_tree()` in conjunction with `igraph_subgraph_from_edges()` instead. +- The deprecated `igraph_get_sparsemat()` was removed. Use `igraph_get_adjacency_sparse()` instead. +- The deprecated `igraph_get_stochastic_sparsemat()` was removed. Use `igraph_get_stochastic_sparse()` instead. +- The deprecated `igraph_laplacian()` was removed. Use `igraph_get_laplacian()` or `igraph_get_laplacian_sparse()` instead. +- The deprecated `igraph_subgraph_edges()` was removed. Use `igraph_subgraph_from_edges()` instead. +- The deprecated `igraph_read_graph_dimacs()` and `igraph_write_graph_dimacs()` were removed. These names may be re-used in the future. Use `igraph_read_graph_dimacs_flow()` and `igraph_write_graph_dimacs_flow()` instead. +- The deprecated `igraph_isomorphic_function_vf2()` was removed. Use `igraph_get_isomorphisms_vf2_callback()` instead. +- The deprecated `igraph_subisomorphic_function_vf2()` was removed. Use `igraph_get_subisomorphisms_vf2_callback()` instead. +- The deprecated `igraph_isomorphic_34()` was removed. Its functionality is accessible through `igraph_isomorphic()`. +- The deprecated `igraph_transitive_closure_dag()` was removed. Use `igraph_transitive_closure()` instead, which works for all graphs, not just DAGs. +- The deprecated `igraph_sparsemat_copy()` was removed. Use `igraph_sparsemat_init_copy()` instead. +- The deprecated `igraph_sparsemat_eye()` was removed. Use `igraph_sparsemat_init_eye()` instead. +- The deprecated `igraph_sparsemat_diag()` was removed. Use `igraph_sparsemat_init_diag()` instead. +- The deprecated `igraph_sparsemat()` and `igraph_weighted_sparsemat()` functions were removed; use `igraph_get_adjacency_sparse()` instead. +- The deprecated `igraph_random_edge_walk()` was removed. Its functionality is incorporated in `igraph_random_walk()`. +- The deprecated `igraph_vector_qsort_ind()` was removed. Use `igraph_vector_sort_ind()` instead. +- The deprecated `igraph_vector_binsearch2()` was removed. Use `igraph_vector_contains_sorted()` instead. +- The deprecated `igraph_vector_copy()` and `igraph_matrix_copy()` were removed. Use `igraph_vector_init_copy()` and `igraph_matrix_init_copy()` instead. +- The deprecated `igraph_vector_e()`, `igraph_vector_e_ptr()`, `igraph_matrix_e()` and `igraph_matrix_e_ptr()` were removed. Use the alternatives ending in `_get()` and `_get_ptr()` instead. +- The deprecated `igraph_vector_move_interval2()` was removed. +- The deprecated `igraph_rng_get_dirichlet()` function was removed. +- The deprecated `igraph_zeroin()` was removed. +- The deprecated `igraph_deterministic_optimal_imitation()`, `igraph_moran_process()`, `igraph_roulette_wheel_imitation()` and `igraph_stochastic_imitation()` functions were removed. +- `igraph_sample_dirichlet()`, `igraph_sample_sphere_surface()` and `igraph_sample_sphere_volume()` were removed in favour of `igraph_rng_sample_dirichlet()`, `igraph_rng_sample_sphere_surface()` and `igraph_rng_sample_sphere_volume()`, which allow the user to specify the random number generator to use. +- The unused enum type `igraph_fileformat_type_t` was removed. +- The macros `IGRAPH_POSINFINITY` and `IGRAPH_NEGINFINITY` were removed. Use `IGRAPH_INFINITY` and `-IGRAPH_INFINITY` instead. +- Removed `igraph_sparsemat_view()` as its design was broken and required the user to reach into the internals of `igraph_sparmsemat_t` to destroy it properly. + +### Deprecated + +- `igraph_delete_vertices_idx()` is now deprecated in favour of `igraph_delete_vertices_map()`, which is functionally equivalent but has a name that is consistent with `igraph_induced_subgraph_map()`. + +### Other + +- The documentation was reorganized. +- Various documentation improvements. +- Improved performance when creating graphs from dense adjacency matrices (`igraph_adjacency()` and `igraph_weighted_adjacency()`). + +## [0.10.17] - 2025-09-19 + +### Added + +- `igraph_layout_align()` attempts to align a graph layout with the coordinate axes in a visually pleasing manner (experimental function). +- `igraph_product()` supports the lexicographic, strong and modular graph products. Thanks to Gulshan Kumar @gulshan-123 for contributing this functionality in #2772 and #2793! +- `igraph_rooted_product()` computes the rooted graph product (experimental function). Thanks to Gulshan Kumar @gulshan-123 for contributing this functionality in #2793! +- `igraph_mycielskian()` (experimental function) and `igraph_mycielski_graph()` compute a Mycielski transformation of a graph, and a Mycielski graph, respectively. Thanks to Gulshan Kumar @gulshan-123 for contributing this functionality in #2741! +- `igraph_path_graph()` is a convenience wrapper for `igraph_ring()` with `circular=false`. +- `igraph_cycle_graph()` is a convenience wrapper for `igraph_ring()` with `circular=true`. +- `igraph_site_percolation()`, `igraph_bond_percolation()` and `igraph_edgelist_percolation()` compute the evolution of the size of the giant component of a graph when vertices (site percolation) or edges (bond percolation) are added one by one in a given order (experimental functions). Thanks to Arnór Friðriksson @Zepeacedust for implementing this in #2778! +- `igraph_invert_permutation()` inverts a permutation stored in an integer vector. +- `igraph_is_vertex_coloring()` and `igraph_is_edge_coloring()` check if a vertex or edge coloring is valid, i.e. whether adjacent vertices/edges always have distinct colors (experimental functions). Thanks to Sarah Rashidi @its-serah for contributing this in #2807! +- `igraph_is_bipartite_coloring()` checks if a bipartite type assignment is valid, i.e. whether adjacent vertices always have different types (experimental function). Thanks to Sarah Rashidi @its-serah for contributing this in #2807! +- `igraph_rich_club_sequence()` calculates how the density of a graph changes as vertices are removed (experimental function). Thanks to Zara Zong @minifinity for contributing this in #2740! + +### Changed + +- `igraph_bipartite_game_gnp()` can now generate graphs with more than a hundred million vertices. Thanks to Dev Lohani @devlohani99 for implementing this in #2767! +- `igraph_reindex_membership()` now supports arbitrary cluster indices. Previously, it would error when indices are not within `0 .. n-1` where `n` is the membership vector length. +- `igraph_modularity()` now supports arbitrary cluster indices. However, ensuring that cluster indices are within the range `0 .. n-1`, where `n` is the vertex count, allows for better performance. + +### Fixed + +- Fix failure in SIR simulation due to roundoff errors creating slightly negative rates. +- Fix infinite coordinates for certain path graphs with `igraph_layout_kamada_kawai_3d()`. +- `igraph_community_leiden()` did not iterate until the partition ceased to change when `n_iterations < 0`. Thanks to Lucas Lopes Felipe @lucaslopes for fixing this in #2799! +- The widest path functions `igraph_widest_path_widths_floyd_warshall()`, `igraph_widest_path_widths_dijkstra()`, `igraph_get_widest_paths()`, and `igraph_get_widest_path()` incorrectly ignored edges with positive infinite width. Now they ignore edges with negative infinite width. +- `igraph_cliques_callback()` would sometimes fail to respect a request to stop (i.e. returning `IGRAPH_STOP`) from the callback. This is now corrected. +- `igraph_hypercube()` now validates the hypercube dimension and prevents negative values. +- `igraph_sparsemat_view()` checks for out-of-memory conditions. +- Fix assertion error when stopping search early in `igraph_simple_cycles_callback()` by returning `IGRAPH_STOP` from the callback. + +### Deprecated + +- `igraph_sparsemat()` and `igraph_weighted_sparsemat()` are now deprecated; their functionality is duplicated by `igraph_get_adjacency_sparse()`. They will be removed in version 1.0. +- `igraph_convex_hull()` is deprecated in favour of `igraph_convex_hull_2d()` and scheduled for removal in 1.0. + +### Other + +- Documentation improvements, including a new glossary. +- Simple cycle search (`igraph_simple_cycles()` and `igraph_simple_cycles_callback()`) is sped up by skipping cycle search from some redundant start vertices. Thanks to Tim Bernhard @GenieTim for contributing this improvement in #2714! +- `igraph_realize_degree_sequence()` is significantly sped up for simple undirected graphs, and now has near-linear complexity for this case. Thanks to Zara Zong @ minifinity for implementing this in #2786! + +## [0.10.16] - 2025-06-10 + +### Added + +- `igraph_count_triangles()` counts undirected triangles in a graph. +- `igraph_count_adjacent_triangles()` (rename of `igraph_adjacent_triangles()`). +- `igraph_rng_get_bool()` and `RNG_BOOL()` produce a single random boolean. +- `igraph_product()` computes various kinds of graph products of two graphs. Thanks to Gulshan Kumar @gulshan-123 for contributing this functionality in #2748! + +### Changed + +- `igraph_neighborhood_size()`, `igraph_neighborhood()` and `igraph_neighborhood_graphs()` now accept a negative `order` value and interpret it as infinite order. Previously, a negative `order` value was disallowed. +- `igraph_famous()` now accepts `Groetzsch` as an alias of `Grotzsch`. +- `igraph_vertex_path_from_edge_path()` can now determine the start vertex automatically. + +### Fixed + +- `igraph_largest_independent_vertex_sets()` and `igraph_maximal_independent_vertex_sets()` would sometimes return incorrect results for graphs with self-loops. This is now corrected. +- `igraph_vertex_path_from_edge_path()` now validates the start vertex. +- Fixed a memory leak in the GraphML parser for cases when the `id` attribute was specified multiple times within the same XML tag. + +### Deprecated + +- The undocumented function `igraph_vector_sumsq()` is deprecated. Use `igraph_blas_dnrm2()` to compute the Euclidean norm of real vectors. +- `igraph_adjacent_triangles()` is deprecated and scheduled for removal in 1.0. +- `igraph_deterministic_optimal_imitation()`, `igraph_moran_process()`, `igraph_roulette_wheel_imitation()` and `igraph_stochastic_imitation()` are now deprecated and scheduled for removal in 1.0. +- `igraph_rng_get_dirichlet()` is deprecated and scheduled for removal in 1.0. Its interface is inconsistent with the other `igraph_rng_get_...()` functions and we have a replacemenet for it in `igraph_sample_dirichlet()`. igraph 1.0 will gain an `igraph_rng_sample_dirichlet()` function that lets the caller pass in an `igraph_rng_t` instance as well. + +### Other + +- Workaround for bug in CMake 3.31.0, see +- Updated the vendored `plfit` library to version 1.0.0. This works around a bug in some MSVC / Windows SDK versions that define a `NAN` macro that is not a compile-time constant. +- Updated vendored BLAS to 3.12.0 and vendored ARPACK to ARPACK-NG 3.7.0. +- Re-translated vendored BLAS/LAPACK/ARPACK sources with f2c version 20240504. +- The performance of `igraph_transitivity_undirected()` is improved by a factor of about 2.5. +- The performance of `igraph_degree_sequence_game()` is improved when using `IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE`. +- Documentation improvements and fixes. + +## [0.10.15] + +### Added + +- `igraph_bitset_update()` copies the contents of one bitset into another (experimental function). +- `igraph_vector_sort_ind()` (rename of `igraph_vector_qsort_ind()`). +- `igraph_vector_contains_sorted()` (rename of `igraph_vector_binsearch2()`). +- `igraph_vector_reverse_section()` reverses a contiguous section of a vector. +- `igraph_vector_rotate_left()` applies a cyclic permutation to a vector. +- `igraph_strvector_swap_elements()` swaps two strings in an `igraph_strvector_t`. +- `igraph_find_cycle()` finds a single cycle in a graph, if it exists (experimental function). +- `igraph_feedback_vertex_set()` finds a minimum feedback vertex set in a directed or undirected graph (experimental function). +- `igraph_simple_cycles()` and `igraph_simple_cycles_callback()` find all simple cycles in a graph, optionally with an upper bound on the cycle length (experimental functions). Many thanks to Tim Bernhard @GenieTim for contributing this functionality in #2181. + +### Changed + +- `igraph_feedback_arc_set()` uses a much faster method for solving the exact minimum feedback arc set problem. The new method (`IGRAPH_FAS_EXACT_IP_CG`) is used by default (i.e. with `IGRAPH_FAS_EXACT_IP`), but the previous method is also kept available (`IGRAPH_FAS_EXACT_IP_TI`). +- `igraph_motifs_randesu()`, `igraph_motifs_randesu_callback()`, `igraph_motifs_randesu_estimate()` and `igraph_motifs_randesu_no()` now accept `NULL` for their `cut_prob` parameter, signifying that a complete search should be performed. +- `igraph_centralization_eigenvector_centrality_tmax()` and `igraph_centralization_eigenvector_centrality()` cannot produce meaningful results without normalizing vertex-level eigenvector centrality in a well-defined way. This was not the case when using `scale=false`. These functions now ignore the value of the `scale` parameter and always scale vertex-level centrality scores to have a maximum of 1. If you require a different type of normalization for the vertex-level eigenvector centrality scores, perform this normalization manually, and call `igraph_centralization()` to compute the centralization. +- When `igraph_eigenvector_centrality()` receives a directed acyclic graph as input, it now produces an eigenvector which has 1s in sink vertices and 0s everywhere else. Previously, it would return an all-zero vector. Note that eigenvector centrality is not uniquely defined for graphs that are not (strongly) connected, and both of these results can be considered valid. This change is to ensure consistency with the definition of the theoretical maximum of eigenvector centralization, which assumes the in-star to be the most centralized directed network. + +### Fixed + +- `igraph_layout_drl()` and `igraph_layout_drl_3d()` would crash with an assertion failure when interrupted. This is now fixed. +- Removed broken interruption support from `igraph_community_spinglass_single()`. +- In rare cases `igraph_community_multilevel()` could enter an infinite loop. This is now corrected. +- Fixed null-dereference in `igraph_community_voronoi()` when requesting `modularity` but not `membership`. +- Fixed null-dereference in `igraph_community_optimal_modularity()` when requesting `modularity` but not `membership` and passing a null graph or singleton graph. +- `igraph_layout_umap()` and `igraph_layout_umap_3d()` would crash when passing `distances=NULL` and `distances_are_weights=true`. This is now fixed. +- `igraph_layout_umap()` and `igraph_layout_umap_3d()` would crash on interruption. This is now fixed. +- `igraph_read_graph_pajek()` now warns about duplicate vertex IDs in input files. +- The documented `igraph_strvector_resize_min()` was missing from headers. +- `igraph_feedback_arc_set()` now validates the edge weights. +- `igraph_layout_lgl()` was not working correctly since igraph 0.10.0 due to a poor choice of initial coordinates. This is now fixed. +- `igraph_centralization_degree_tmax()`, `igraph_centralization_betweenness_tmax()`, `igraph_centralization_closeness_tmax()`, and `igraph_centralization_eigenvector_centrality_tmax()` now validate their `nodes` parameter. +- `igraph_centralization_degree_tmax()`, `igraph_centralization_betweenness_tmax()`, `igraph_centralization_closeness_tmax()`, and `igraph_centralization_eigenvector_centrality_tmax()` now return NaN for zero-vertex graphs. Previously they would return invalid values. +- `igraph_centralization_eigenvector_centrality_tmax()` now returns 0 for the undirected singleton graph. Previous it would return an invalid value. +- `igraph_motifs_randesu_estimate()` now validates the sample size. +- `igraph_bipartite_projection_size()` now validates the bipartite `types` vector. + +### Deprecated + +- `igraph_minimum_spanning_tree_prim()` and `igraph_minimum_spanning_tree_unweighted()` are deprecated. Use `igraph_minimum_spanning_tree()` in conjunction with `igraph_subgraph_from_edges()` instead. +- `igraph_array3_t` and all associated functions are deprecated and scheduled for removal in igraph 1.0. +- `igraph_vector_qsort_ind()` is deprecated in favour of `igraph_vector_sort_ind()`. +- `igraph_vector_binsearch2()` is deprecated in favour of `igraph_vector_contains_sorted()`. +- The error code `IGRAPH_ENEGLOOP` is deprecated in favour of the newly introduced `IGRAPH_ENEGCYCLE`. The underlying integer code did not change. + +### Other + +- Fixed multiple memory leaks in benchmark programs. +- Documentation improvements. + +## [0.10.13] + +### Added + +- `igraph_bitset_fill()` sets all elements of a bitset to the same value (experimental function). +- `igraph_bitset_null()` clears all elements of a bitset (experimental function). +- `igraph_bitset_is_all_zero()`, `igraph_bitset_is_all_one()`, `igraph_bitset_is_any_zero()`, `igraph_bitset_is_any_one()` check if any/all elements of a bitset are zeros/ones (experimental functions). +- `igraph_chung_lu_game()` implements the classic Chung-Lu model, as well as a number of its variants (experimental function). +- `igraph_mean_degree()` computes the average of vertex degrees (experimental function). +- `igraph_count_loops()` counts self-loops in the graph (experimental function). +- `igraph_is_clique()` checks if all pairs within a set of vertices are connected (experimental function). +- `igraph_is_independent_vertex_set()` checks if no pairs within a set of vertices are connected (experimental function). +- `igraph_hypercube()` creates a hypercube graph (experimental function). +- `igraph_vector_intersection_size_sorted()` counts elements common to two sorted vectors (experimental function). +- `igraph_stack_capacity()` returns the allocated capacity of a stack. +- `igraph_vector_is_all_finite()` checks if all elements in a vector are finite (i.e. neither NaN nor Inf). + +### Fixed + +- Fixed a bug that incorrectly cached that a graph has no multiple edges when `igraph_init_adjlist()` was called with `IGRAPH_NO_LOOPS` and `IGRAPH_NO_MULTIPLE` and all the multi-edges were loop edges. +- `igraph_is_forest()` would fail to set the result variable when testing for a directed forest, and it was already cached that the graph was not an undirected forest. +- `igraph_hub_and_authority_scores()` no longer clips negative results to zeros when negative weights are present. +- Fixed an assertion failure in `igraph_realize_bipartite_degree_sequence()` with some non-graphical degree sequences when requesting simple bipartite graphs. +- `igraph_static_fitness_game()` checks the input more carefully, and avoids an infinite loop in rare edge cases, such as when (almost) all fitness scores are zero. +- `igraph_arpack_rnsolve()` used the incorrect error message text for some errors. This is now corrected. +- Corrected the detection of some MSVC-specific bitset intrinsics during configuration. +- Corrected a bug in the fallback implementation of `igraph_bitset_countl_zero()` when `IGRAPH_INTEGER_SIZE` was set to 32. This fallback implementation was _not_ used with GCC, Clang, or MSVC. + +### Changed + +- `igraph_is_graphical()` and `igraph_is_bigraphical()` are now linear-time in all cases, and generally several times faster than before (thanks to @gendelpiekel, contributed in #2605). +- `igraph_erdos_renyi_game_gnp()` can now generate graphs with more than a hundred million vertices. +- `igraph_hub_and_authority_scores()` now warns when negative edge weights are present. +- `igraph_layout_lgl()` now uses a BFS tree rooted in the vertex specified as `proot` to guide the layout. Previously it used an unspecified (arbitrary) spanning tree. +- Updated the internal heuristics used by igraph's ARPACK interface, `igraph_arpack_rssolve()` and `igraph_arpack_rnsolve()`, to improve the robustness of calculations. +- Updated the initial vector construction in `igraph_hub_and_authority_scores()`, `igraph_eigenvector_centrality()` and `igraph_(personalized_)pagerank()` with `IGRAPH_PAGERANK_ALGO_ARPACK`. This improves the robustness and convergence of calculations. + +### Other + +- Documentation improvements. +- Reduced the memory usage of several functions by using bitsets instead of boolean vectors. +- `igraph_vector_intersect_sorted()` has better performance when the input vector sizes are similar. + +## [0.10.12] - 2024-05-06 + +### Added + +- `igraph_transitive_closure()` computes the transitive closure of a graph (experimental function). +- `igraph_reachability()` determines which vertices are reachable from each other in a graph (experimental function). +- `igraph_count_reachable()` counts how many vertices are reachable from each vertex (experimental function). +- Added a bitset data structure, `igraph_bitset_t`, and a set of corresponding functions (experimental functionality). + +### Fixed + +- `igraph_community_label_propagation()` is now interruptible. +- `igraph_is_bipartite()` would on rare occasions return invalid results when the cache was employed. +- `igraph_weighted_adjacency()` correctly passes through NaN values with `IGRAPH_ADJ_MAX`, and correctly recognizes symmetric adjacency matrices containing NaN values with `IGRAPH_ADJ_UNDIRECTED`. +- `igraph_read_graph_gml()` can now read GML files that use ids larger than what is representable on 32 bits, provided that igraph was configured with a 64-bit `igraph_integer_t` size. +- Fixed a performance issue in `igraph_read_graph_graphml()` with files containing a very large number of entities, such as `>`. +- `igraph_read_graph_pajek()` has improved vertex ID validation that better matches that of Pajek's own behavior. + +### Changed + +- `igraph_eigenvector_centrality()` no longer issues a warning when the input is directed and weighted. When using this function, keep in mind that eigenvector centrality is well-defined only for (strongly) connected graphs, and edges with zero weights are effectively treated as absent. + +### Deprecated + +- `igraph_transitive_closure_dag()` is deprecated in favour of `igraph_transitive_closure()` + +### Other + +- Documentation improvements. +- `igraph_strength()` and `igraph_degree(loops=false)` are now faster when calculating values for all vertices (contributed by @gendelpiekel in #2602) + +## [0.10.11] - 2024-04-02 + +### Added + +- `igraph_is_complete()` checks whether there is a connection between all pairs of vertices (experimental function, contributed by Aymeric Agon-Rambosson @aagon in #2510). +- `igraph_join()` creates the _join_ of two graphs (experimental function, contributed by Quinn Buratynski @GanzuraTheConsumer in #2508). + +### Fixed + +- Fixed a corruption of the "finally" stack in `igraph_write_graph_gml()` for certain invalid GML files. +- Fixed a memory leak in `igraph_write_graph_lgl()` when vertex names were present but edge weights were not. +- Fixed the handling of duplicate edge IDs in `igraph_subgraph_from_edges()`. +- Fixed conversion of sparse matrices to dense with `igraph_sparsemat_as_matrix()` when sparse matrix object did not make use of its full allocated capacity. +- `igraph_write_graph_ncol()` and `igraph_write_graph_lgl()` now refuse to write vertex names which would result in an invalid file that cannot be read back in. +- `igraph_write_graph_gml()` now ignores graph attributes called `edge` or `node` with a warning. Writing these would create an invalid GML file that igraph couldn't read back. +- `igraph_disjoint_union()` and `igraph_disjoint_union_many()` now check for overflow. +- `igraph_read_graph_graphml()` now correctly compares attribute values with certain expected values, meaning that prefixes of valid values of `attr.type` are not accepted anymore. +- Empty IDs are not allowed any more in `` tags of GraphML files as this is a violation of the GraphML specification. +- `igraph_is_separator()` and `igraph_is_minimal_separator()` now work correctly with disconnected graphs. +- `igraph_linegraph()` now considers self-loops to be self-adjacent in undirected graphs, bringing consistency with how directed graphs were already handled in previous versions. +- `igraph_all_st_mincuts()` now correctly returns all minimum cuts. This also fixes a problem with `igraph_minimum_size_separators()`. +- Corrected minor error in `igraph_community_label_propagation()` when adding labels to isolated nodes with some fixed labels present. +- `igraph_community_spinglass()` no longer crashes when passing an edgeless graph and an empty weight vector. +- `igraph_rewire()` no longer crashes on graphs with more than three vertices but fewer than two edges. + +### Changed + +- `igraph_rewire()` on longer throws an error on graphs with fewer than four vertices. These graphs are now returned unchanged, just like other graphs which are the unique realization of their degree sequence. + +### Other + +- Performance: `igraph_is_simple()` now makes more granular use of the cache. +- Performance: `igraph_degree()` now makes use of the cache when checking for self-loops. +- The performance of `igraph_is_minimal_separator()` was improved. +- `igraph_is_graphical()` now performs graphicality checks for degree sequences of simple directed graphs in linear time, an improvement from the previously used quadratic algorithm (contributed by Arnar Bjarni Arnarson @Tagl in #2537). +- Documentation improvements. + +## [0.10.10] - 2024-02-13 + +### Fixed + +- When `igraph_is_forest()` determined that a graph is not a directed forest, and the `roots` output parameter was set to `NULL`, it would incorrectly cache that the graph is also not an undirected forest. +- `igraph_spanner()` now correctly ignores edge directions, and no longer crashes on directed graphs. + +### Deprecated + +- `igraph_are_connected()` is renamed to `igraph_are_adjacent()`; the old name is kept available until at least igraph 1.0. + +### Other + +- Documentation improvements. + +## [0.10.9] - 2024-02-02 + +### Added + +- `igraph_is_biconnected()` checks if a graph is biconnected. +- `igraph_realize_bipartite_degree_sequence()` constructs a bipartite graph that has the given bidegree sequence, optionally ensuring that it is connected (PR #2425 by Lára Margrét Hólmfríðardóttir @larah19). + +### Fixed + +- More robust error handling in HRG code. +- Fixed infinite loop in `igraph_hrg_sample_many()`. +- `igraph_community_fastgreedy()` no longer crashes when providing a modularity vector only, but not a merges matrix of membership vector. +- The graph property cache was not initialized correctly on systems where the size of `bool` was not 1 byte (#2477). +- Compatibility with libxml2 version 2.12 (#2442). + +### Deprecated + +- The macro `STR()` is deprecated; use the function `igraph_strvector_get()` instead. + +### Other + +- Performance: Reduced memory usage and improved initialization performance for `igraph_strvector_t`. +- Performance: Improved cache use by `igraph_is_bipartite()`. +- The documentation is now also generated in Texinfo format. +- Documentation improvements. + +## [0.10.8] - 2023-11-17 + +### Added + +- `igraph_joint_degree_matrix()` computes the joint degree matrix, i.e. counts connections between vertices of different degrees (PR #2407 by Lára Margrét Hólmfríðardóttir @larah19). +- `igraph_joint_degree_distribution()` computes the joint distribution of degrees at either end of edges. +- `igraph_joint_type_distribution()` computes the joint distribution of vertex categories at either end of edges, i.e. the mixing matrix. +- `igraph_degree_correlation_vector()` computes the degree correlation function and its various directed generalizations. + +### Changed + +- The behaviour of the Pajek format reader and writer is now more closely aligned with the Pajek software and the reader is more tolerant of input it cannot interpret. Only those vertex and edge parameters are treated as valid which Pajek itself understands, therefore support for `size` is now dropped, and support for the `font` edge parameter is added. See for more information. Invalid/unrecognized parameters are now converted to igraph attributes by the reader, but just as before, they are not output by the writer. +- The Pajek format writer now encodes newline and quotation mark characters in a Pajek-compatible manner (`\n` and `"`, respectively). +- `igraph_avg_nearest_neighbor_degree()` now supports non-simple graphs. + +### Fixed + +- Resolved "ignoring duplicate libraries" warning when building tests with Xcode 15 on macOS. +- Fixed the handling of duplicate vertex IDs in `igraph_induced_subgraph()`. +- `igraph_vector_which_min()` and `igraph_vector_which_max()` no longer allow zero-length input, which makes them consistent with other similar functions, and was the originally intended behaviour. Passing zero-length input is invalid use and currently triggers an assertion failure. +- `igraph_erdos_renyi_game_gnm()` and `igraph_erdos_renyi_game_gnp()` are now interruptible. +- `igraph_de_bruijn()` and `igraph_kautz()` are now interruptible. +- `igraph_full()`, `igraph_full_citation()`, `igraph_full_multipartite()` and `igraph_turan()` are now interruptible. +- `igraph_avg_nearest_neighbor_degree()` did not compute `knnk` correctly in the weighted case. +- Fixed variadic arguments of invalid types, which could cause incorrect behaviour with `igraph_matrix_print()`, as well as test suite failures, on some platforms. 32-bit x86 was affected when setting `IGRAPH_INTEGER_SIZE` to 64. +- `igraph_subisomorphic_lad()` now returns a single null map when the pattern is the null graph. +- `igraph_community_spinglass()` now checks its parameters more carefully. +- `igraph_similarity_dice_pairs()` and `igraph_similarity_jaccard_pairs()` now validate vertex IDs. +- `igraph_maxflow()` now returns an error code if the source and target vertices are the same. It used to get stuck in an infinite loop in earlier versions when the `flow` argument was non-NULL. + +### Other + +- Updated vendored mini-gmp to 6.3.0. +- `igraph_connected_components()` makes better use of the cache, improving overall performance. +- Documentation improvements. + +## [0.10.7] - 2023-09-04 + +### Added + +- `igraph_radius_dijkstra()` computes the graph radius with weighted edges (experimental function). +- `igraph_graph_center_dijkstra()` computes the graph center, i.e. the set of minimum eccentricity vertices, with weighted edges (experimental function). + +### Fixed + +- `igraph_full_bipartite()` now checks for overflow. +- `igraph_bipartite_game_gnm()` and `igraph_bipartite_game_gnp()` are now more robust to overflow. +- Bipartite graph creation functions now check input arguments. +- `igraph_write_graph_dot()` now quotes real numbers written in exponential notation as necessary. +- Independent vertex set finding functions could trigger the fatal error "Finally stack too large" when called on large graphs. + +### Deprecated + +- `igraph_bipartite_game()` is now deprecated; use `igraph_bipartite_game_gnm()` and `igraph_bipartite_game_gnp()` instead. + +### Other + +- Documentation improvements. + +## [0.10.6] - 2023-07-13 + +### Fixed + +- Compatibility with libxml2 2.11. +- Fixed some converge failures in `igraph_community_voronoi()`. +- `IGRAPH_CALLOC()` and `IGRAPH_REALLOC()` now check for overflow. +- CMake packages created with the `install` target of the CMake build system are now relocatable, i.e. the generated `igraph-targets.cmake` file does not contain absolute paths any more. + +## [0.10.5] - 2023-06-29 + +### Added + +- `igraph_graph_power()` computes the kth power of a graph (experimental function). +- `igraph_community_voronoi()` for detecting communities using Voronoi partitioning (experimental function). + +### Changed + +- `igraph_community_walktrap()` no longer requires `modularity` and `merges` to be non-NULL when `membership` is non-NULL. +- `igraph_isomorphic()` now supports multigraphs. +- Shortest path related functions now consistently ignore edges with positive infinite weights. + +### Fixed + +- `igraph_hub_and_authority_scores()`, `igraph_hub_score()` and `igraph_authority_score()` considered self-loops only once on the diagonal of the adjacency matrix of undirected graphs, thus the result was not identical to that obtained by `igraph_eigenvector_centrality()` on loopy undirected graphs. This is now corrected. +- `igraph_community_infomap()` now checks edge and vertex weights for validity. +- `igraph_minimum_spanning_tree()` and `igraph_minimum_spanning_tree_prim()` now check that edge weights are not NaN. +- Fixed an initialization error in the string attribute combiner of the C attribute handler. +- Fixed an issue with the weighted clique number calculation when all the weights were the same. +- HRG functions now require a graph with at least 3 vertices; previous versions crashed with smaller graphs. +- `igraph_arpack_rssolve()` and `igraph_arpack_rnsolve()`, i.e. the ARPACK interface in igraph, are now interruptible. As a result, several other functions that rely on ARPACK (eigenvector centrality, hub and authority scores, etc.) also became interruptible. +- `igraph_get_shortest_paths_dijkstra()`, `igraph_get_all_shortest_paths_dijkstra()` and `igraph_get_shortest_paths_bellman_ford()` now validate the `from` vertex. +- Fixed bugs in `igraph_local_scan_1_ecount()` for weighted undirected graphs which would miscount loops and multi-edges. + +### Deprecated + +- `igraph_automorphisms()` is now deprecated; its new name is `igraph_count_automorphisms()`. The old name is kept available until at least igraph 0.11. +- `igraph_hub_score()` and `igraph_authority_score()` are now deprecated. Use `igraph_hub_and_authority_scores()` instead. +- `igraph_get_incidence()` is now deprecated; its new name is `igraph_get_biadjacency()` to reflect that the returned matrix is an _adjacency_ matrix between pairs of vertices and not an _incidence_ matrix between vertices and edges. The new name is kept available until at least igraph 0.11. We plan to re-use the name in later versions to provide a proper incidence matrix where the rows are vertices and the columns are edges. +- `igraph_hrg_dendrogram()` is deprecated because it requires an attribute handler and it goes against the convention of returning attributes in vectors where possible. Use `igraph_from_hrg_dendrogram()` instead, which constructs the dendrogram as an igraph graph _and_ returns the associated probabilities in a vector. + +### Other + +- Improved performance for `igraph_vertex_connectivity()`. +- `igraph_simplify()` makes use of the cache, and avoids simplification when the graph is already known to be simple. +- Documentation improvements. + +## [0.10.4] - 2023-01-26 + +### Added + +- `igraph_get_shortest_path_astar()` finds a shortest path with the A\* algorithm. +- `igraph_vertex_coloring_greedy()` now supports the DSatur heuristics through `IGRAPH_COLORING_GREEDY_DSATUR` (#2284, thanks to @professorcode1). + +### Changed + +- The `test` build target now only _runs_ the unit tests, but it does not _build_ them. In order to both build and run tests, use the `check` target, which continues to behave as before (PR #2291). +- The experimental function `igraph_distances_floyd_warshall()` now has `from` and `to` parameters for choosing source and target vertices. +- The experimental function `igraph_distances_floyd_warshall()` now has an additional `method` parameter to select a specific algorithm. A faster "Tree" variant of the Floyd-Warshall algorithm is now available (#2267, thanks to @rfulekjames). + +### Fixed + +- The Bellman-Ford shortest path finder is now interruptible. +- The Floyd-Warshall shortest path finder is now interruptible. +- Running CTest no longer builds the tests automatically, as this interfered with VSCode, which would invoke the `ctest` executable after configuring a project in order to determine test executables. Use the `build_tests` target to build the tests first, or use the `check` target to both _build_ and _run_ all unit tests (PR #2291). + +### Other + +- Improved the performance and memory usage of `igraph_widest_path_widths_floyd_warshall()`. +- Documentation improvements. + +## [0.10.3] - 2022-12-30 + +### Added + +- `igraph_matrix_init_array()` to initialize an igraph matrix by copying an existing C array in column-major or row-major order. +- `igraph_layout_umap_compute_weights()` computes weights for the UMAP layout algorithm from distances. This used to be part of `igraph_layout_umap()`, but it is now in a separate function to allow the user to experiment with different weighting schemes. +- `igraph_triangular_lattice()` to generate triangular lattices of various kinds (#2235, thanks to @rfulekjames). +- `igraph_hexagonal_lattice()` to generate hexagonal lattices of various kinds (#2262, thanks to @rfulekjames). +- `igraph_tree_from_parent_vector()` to create a tree or a forest from a parent vector (i.e. a vector that encodes the parent vertex of each vertex). +- `igraph_induced_subgraph_edges()` produces the IDs of edges contained within a subgraph induced by the given vertices. + +### Changed + +- The signature of the experimental `igraph_layout_umap()` function changed; the last argument is now a Boolean that specifies whether distances should already be treated as weights, and the sampling probability argument was removed. + +### Fixed + +- `igraph_transitivity_barrat()`, `igraph_community_fluid_communities()`, `igraph_sir()`, `igraph_trussness()` and graphlet functions did not correctly detect when a directed input graph had effective multi-edges due to ignoring edge directions. Such graphs are now rejected by these functions. +- Fixed a bug in `igraph_2dgrid_move()` that sometimes crashed the Large Graph Layout function when a grid cell became empty. +- `igraph_pagerank()` and `igraph_personalized_pagerank()` would fail to converge when the ARPACK implementation was used and a vertex had more than one outgoing edge but all these edges had zero weights. +- `igraph_pagerank()` and `igraph_personalized_pagerank()` no longer allow negative weights. Previously, edges with negative weights were silently ignored when using the PRPACK implementation. The ARPACK implementation would issue a warning saying that they are ignored, but in fact it computed an incorrect result. +- `igraph_all_st_cuts()` and `igraph_all_st_mincuts()` no longer trigger the "Finally stack too large" fatal error when called on certain large graphs. This was a regression in igraph 0.10. +- `igraph_community_label_propagation()` no longer rounds weights to integers. This was a regression in igraph 0.10. +- `igraph_read_graph_graphdb()` does more thorough checks on the input file. +- `igraph_calloc()` did not zero-initialize the allocated memory. This is now corrected. Note that the macro `IGRAPH_CALLOC()` was _not_ affected. +- Fixed new warnings issued by the Xcode 14.1 toolchain. + +### Deprecated + +- `igraph_subgraph_edges()` is now deprecated to avoid confusion with `igraph_induced_subgraph_edges()`; its new name is `igraph_subgraph_from_edges()`. The old name is kept available until at least igraph 0.11. + +### Other + +- Significantly improved performance for `igraph_matrix_transpose()`. +- Documentation improvements. + +## [0.10.2] - 2022-10-14 + +### Added + +- `igraph_distances_cutoff()` and `igraph_distances_dijkstra_cutoff()` calculate shortest paths with an upper limit on the path length (experimental functions). +- `igraph_distances_floyd_warshall()` for computing all-pairs shortest path lengths in dense graphs (experimental function). +- `igraph_ecc()` computes the edge clustering coefficient of some edges (experimental function). +- `igraph_voronoi()` computes a Voronoi partitioning of vertices (experimental function). +- `igraph_count_multiple_1()` determines the multiplicity of a single edge in the graph. +- `igraph_dqueue_get()` accesses an element in a queue by index. +- `igraph_degree_1()` efficiently retrieves the degee of a single vertex. +- `igraph_lazy_adjlist_has()` and `igraph_lazy_inclist_has()` to check if adjacent vertices / incident edges have already been computed and stored for a given vertex in a lazy adjlist / inclist. + +### Changed + +- `igraph_edge()` now verifies that the input edge ID is valid. +- `igraph_community_leading_eigenvector()`, `igraph_adjacency_spectral_embedding()`, `igraph_laplacian_spectral_embedding()`, `igraph_arpack_rssolve()` and `igraph_arpack_rnsolve()` now generate a random starting vector using igraph's own RNG if needed instead of relying on LAPACK or ARPACK to do so. This makes sure that the results obtained from these functions remain the same if igraph's RNG is seeded with the same value. +- `igraph_community_leading_eigenvector()` does not stop the splitting process any more when there are multiple equally likely splits (indicated by the multiplicity of the leading eigenvector being larger than 1). The algorithm picks an arbitrary split instead and proceeds normally. + +### Fixed + +- Fixed a bug in `igraph_get_k_shortest_paths()` that sometimes yielded incorrect results on undirected graphs when the `mode` argument was set to `IGRAPH_OUT` or `IGRAPH_IN`. +- `igraph_trussness()` is now interruptible. +- `igraph_spanner()` is now interruptible. +- `igraph_layout_umap()` and `igraph_layout_umap3d()` are now interruptible. +- In some rare cases, roundoff errors would cause `igraph_distance_johnson()` to fail on graphs with negative weights. +- `igraph_eulerian_cycle()` and `igraph_eulerian_path()` now returns a more specific error code (`IGRAPH_ENOSOL`) when the graph contains no Eulerian cycle or path. +- `igraph_heap_init_array()` did not copy the array data correctly for non-real specializations. +- `igraph_layout_umap_3d()` now actually uses three dimensions. +- `igraph_layout_umap()` and `igraph_layout_umap_3d()` are now interruptible. +- `igraph_vit_create()` and `igraph_eit_create()` no longer fails when trying to create an iterator for the null graph or edgeless graph from an empty range-based vertex or edge selector. +- `igraph_write_graph_leda()` did not correctly print attribute names in some warning messages. +- Addressed new warnings introduced by Clang 15. +- In the generated pkg-config file, libxml2 is now placed in the `Requires.private` section instead of the `Libs.private` one. + +### Removed + +- Removed unused and undocumented `igraph_bfgs()` function. +- Removed the undocumented function `igraph_complex_mod()`. Use `igraph_complex_abs()` instead, as it has identical functionality. + +### Deprecated + +- The `IGRAPH_EDRL` error code was deprecated; the DrL algorithm now returns `IGRAPH_FAILURE` when it used to return `IGRAPH_EDRL` (not likely to happen in practice). +- The undocumented function `igraph_dqueue_e()` is now deprecated and replaced by `igraph_dqueue_get()`. +- `igraph_finite()`, `igraph_is_nan()`, `igraph_is_inf()`, `igraph_is_posinf()` and `igraph_is_neginf()` are now deprecated. They were relics from a time when no standard alternatives existed. Use the C99 standard `isfinite()`, `isnan()` and `isinf()` instead. + +### Other + +- Documentation improvements. + +## [0.10.1] - 2022-09-08 + +### Fixed + +- Corrected a regression (compared to igraph 0.9) in weighted clique search functions. +- `igraph_girth()` no longer fails when the graph has no cycles and the `girth` parameter is set to `NULL`. +- `igraph_write_graph_gml()` did not respect entity encoding options when writing the `Creator` line. +- Fixed potential memory leak on out-of-memory condition in `igraph_asymmetric_preference_game()`, `igraph_vs_copy()` and `igraph_es_copy()`. +- Fixed an assertion failure in `igraph_barabasi_game()` and `igraph_barabasi_aging_game()` when passing in negative degree exponents. +- Fixed a compilation failure with some old Clang versions. + +### Changed + +- `igraph_write_graph_leda()` can now write boolean attributes. + +### Other + +- Support for ARM64 on Windows. +- Documentation improvements. + +## [0.10.0] - 2022-09-05 + +### Release notes + +This release focuses on infrastructural improvements, stability, and making the igraph interface more consistent, more predictable and easier to use. It contains many API-breaking changes and function renamings, in preparation for a future 1.0 release, at which point the API will become stable. Changes in this direction are likely to continue through a 0.11 release. It is recommended that you migrate your code from 0.9 to 0.10 soon, to make the eventual transition to 1.0 easier. + +Some of the highlights are: + +- A consistent use of `igraph_integer_t` for all indices and most integer quantities, both in the API and internally. This type is 64-bit by default on all 64-bit systems, bringing support for very large graphs with more than 2 billion vertices. Previously, vertex and edge indices were often represented as `igraph_real_t`. The move to an `igraph_integer_t` also implies a change from `igraph_vector_t` to `igraph_vector_int_t` in many functions. +- The random number generation framework has been overhauled. Sampling from the full range of `igraph_integer_t` is now possible. Similarly, the sampling of random reals has been improved to utilize almost the full range of the mantissa of an `igraph_real_t`. +- There is a new fully memory-managed container type for lists of vectors (`igraph_vector_list_t`), replacing most previous uses of the non-managed `igraph_vector_ptr_t`. Functions that previously used `igraph_vector_ptr_t` to return results and relied on the user to manage memory appropriately are now using `igraph_vector_list_t`, `igraph_graph_list_t` or similar and manage memory on their own. +- Some simple graph properties, such as whether a graph contains self-loops or multi-edges, or whether it is connected, are now cached in the graph data structure. Querying these properties for a second time will take constant computational time. The `igraph_invalidate_cache()` function is provided for debugging purposes. It will invaidate all cache entries. +- File format readers are much more robust and more tolerant of invalid input. +- igraph is much more resilient to overflow errors. +- Many improvements to robustness and reliability, made possible by internal refactorings. + +### Breaking changes + +- igraph now requires CMake 3.18 or later. +- In order to facilitate the usage of graphs with more than 2 billion vertices and edges, we have made the size of the `igraph_integer_t` data type to be 32 bits on 32-bit platforms and 64 bits on 64-bit platforms by default. You also have the option to compile a 32-bit igraph variant on a 64-bit platform by changing the `IGRAPH_INTEGER_SIZE` build variable in CMake to 32. +- `igraph_bool_t` is now a C99 `bool` and not an `int`. Similarly, `igraph_vector_bool_t` now consumes `sizeof(bool)` bytes per entry only, not `sizeof(int)`. The standard constants `true` and `false` may be used for Boolean values for readability. +- The random number generator interface, `igraph_rng_type_t`, has been overhauled. Check the declaration of the type for details. +- The default random number generator has been changed from Mersenne Twister to PCG32. +- Functions related to spectral coarse graining (i.e. all functions starting with `igraph_scg_...`) were separated into a project of its own. If you wish to keep on using these functions, please refer to the repository hosting the spectral coarse graining code at . The spectral coarse graining code was updated to support igraph 0.10. +- Since `igraph_integer_t` aims to be the largest integer size that is feasible on a particular platform, there is no need for generic data types based on `long int` anymore. The `long` variants of generic data types (e.g., `igraph_vector_long_t`) are therefore removed; you should use the corresponding `int` variant instead, whose elements are of type `igraph_integer_t`. +- Generic data types based on `float` were removed as they were not used anywhere in the library. +- Several igraph functions that used to take a `long int` or return a `long int` now takes or returns an `igraph_integer_t` instead to make the APIs more consistent. Similarly, igraph functions that used `igraph_vector_t` for arguments that take or return _integral_ vectors (e.g., vertex or edge indices) now take `igraph_vector_int_t` instead. Graph-related functions where the API was changed due to this reason are listed below, one by one. +- Similarly, igraph functions that used to accept the `long` variant of a generic igraph data type (e.g., `igraph_vector_long_t`) now take the `int` variant of the same data type. +- The type `igraph_stack_ptr_t` and its associated functions were removed. Use `igraph_vector_ptr_t` and associated functions instead. +- Error handlers should no longer perform a `longjmp()`. Doing so will introduce memory leaks, as resource cleanup is now done in multiple stages, through multiple calls to the error handler. Thus, the error handler should either abort execution immediately (as the default handler does), or report the error, call `IGRAPH_FINALLY_FREE()`, and return normally. +- Most callback functions now return an error code. In previous versions they returned a boolean value indicating whether to terminate the search. A request to stop the search is now indicated with the special return code `IGRAPH_STOP`. +- `igraph_add_edges()` now uses an `igraph_vector_int_t` for its `edges` parameter. +- `igraph_adjacency()` no longer accepts a negative number of edges in its adjacency matrix. When negative entries are found, an error is generated. +- `igraph_adjacency()` gained an additional `loops` argument that lets you specify whether the diagonal entries should be ignored or should be interpreted as raw edge counts or _twice_ the number of edges (which is common in linear algebra contexts). +- `igraph_all_minimal_st_separators()` now returns the separators in an `igraph_vector_int_list_t` containing `igraph_vector_int_t` vectors. +- `igraph_all_st_cuts()` and `igraph_all_st_mincuts()` now return the cuts in an `igraph_vector_int_list_t` containing `igraph_vector_int_t` vectors. +- `igraph_arpack_unpack_complex()` now uses `igraph_integer_t` for its `nev` argument instead of `long int`. +- `igraph_articulation_points()` now uses an `igraph_vector_int_t` to return the list of articulation points, not an `igraph_vector_t`. +- `igraph_assortativity_nominal()` now accepts vertex types in an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_asymmetric_preferennce_game()` now uses an `igraph_vector_int_t` to return the types of the nodes in the generated graph. +- `igraph_atlas()` now uses `igraph_integer_t` for its `number` argument. +- `igraph_automorphism_group()` now returns the generators in an `igraph_vector_int_list_t` instead of a pointer vector containing `igraph_vector_t` objects. +- `igraph_barabasi_game()`, `igraph_barabasi_aging_game()`, `igraph_recent_degree_game()` and `igraph_recent_degree_aging_game()` now use an `igraph_vector_int_t` for the out-degree sequence of the nodes being generated instead of an `igraph_vector_t`. +- `igraph_bfs()` now takes an `igraph_vector_int_t` for its `roots`, `restricted`, `order`, `father`, `pred`, `succ` and `dist` arguments instead of an `igraph_vector_t`. +- `igraph_bfs_simple()` now takes `igraph_vector_int_t` for its `vids`, `layers` and `parents` arguments instead of an `igraph_vector_t`. +- `igraph_bfs_simple()` now returns -1 in `parents` for the root node of the traversal, and -2 for unreachable vertices. This is now consistent with other functions that return a parent vector. +- `igraph_biconnected_components()` now uses an `igraph_vector_int_t` to return the list of articulation points, not an `igraph_vector_t`. Also, the container used for the edges and vertices of the components is now an `igraph_vector_int_list_t` instead of a pointer vector containing `igraph_vector_t` objects. +- `igraph_bipartite_projection()` now uses `igraph_vector_int_t` to return `multiplicity1` and `multiplicity2`, not `igraph_vector_t`. +- `igraph_bridges()` now uses an `igraph_vector_int_t` to return the list of bridges, not an `igraph_vector_t`. +- `igraph_callaway_traits_game()` returns the node types in an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_canonical_permutation()` now uses an `igraph_vector_int_t` for its labeling parameter. +- `igraph_cattribute_list()` now uses `igraph_vector_int_t` to return `gtypes`, `vtypes` and `etypes`. +- `igraph_cited_type_game()` now uses an `igraph_vector_int_t` for its types parameter. +- `igraph_citing_cited_type_game()` now uses an `igraph_vector_int_t` for its + types parameter. +- `igraph_clique_handler_t` now uses an `igraph_vector_int_t` for its `clique` parameter, and must return an `igraph_error_t`. Use `IGRAPH_STOP` as the return code to terminate the search prematurely. The vector that the handler receives is owned by the clique search routine. If you want to hold on to the vector for a longer period of time, you need to make a copy of it in the handler. Cliques passed to the callback are marked as `const` as a reminder to this change. +- The `res` parameter of `igraph_cliques()` is now an `igraph_vector_int_list_t`. +- Callbacks used by `igraph_cliques_callback()` need to be updated to account for the fact that the callback does not own the clique passed to it any more; the callback needs to make a copy if it wants to hold on to the clique for a longer period of time. If the callback does not need to store the clique, it does not need to do anything any more, and it must not destroy or free the clique. +- `igraph_closeness()` and `igraph_closeness_cutoff()` now use an `igraph_vector_int_t` to return `reachable_count`, not an `igraph_vector_t`. +- `igraph_cohesive_blocks()` now uses an `igraph_vector_int_t` to return the mapping from block indices to parent block indices, and the `cohesion`; also, it uses an `igraph_vector_int_list_t` to return the blocks themselves instead of a pointer vector of `igraph_vector_t`. +- The `igraph_community_eb_get_merges()` bridges parameter now starts the indices into the edge removal vector at 0, not 1. +- The `igraph_community_eb_get_merges()` now reports an error when not all edges in the graph are removed, instead of a nonsensical result. +- `igraph_community_edge_betweenness()` now uses an `igraph_vector_int_t` to return the edge IDs in the order of their removal as well as the list of edge IDs whose removal broke a single component into two. +- `igraph_community_fluid_communities()` does not provide the modularity in a separate output argument any more; use `igraph_modularity()` to retrieve the modularity if you need it. +- `igraph_community_infomap()` now uses `igraph_integer_t` for its `nb_trials` argument. +- `igraph_community_label_propagation()` now uses an `igraph_vector_int_t` for its `initial` parameter. It also takes a `mode` argument that specifies how labels should be propagated along edges (forward, backward or ignoring edge directions). +- `igraph_community_label_propagation()` does not provide the modularity in a separate output argument any more; use `igraph_modularity()` to retrieve the modularity if you need it. +- `igraph_community_leiden()` has an additional parameter to indicate the number of iterations to perform (PR #2177). +- `igraph_community_walktrap()`, `igraph_community_edge_betweenness()`, `igraph_community_eb_get_merges()`, `igraph_community_fastgreedy()`, `igraph_community_to_membership()`, `igraph_le_community_to_membership()`, `igraph_community_leading_eigenvector()` now use an `igraph_vector_int_t` for their `merges` parameter. +- `igraph_community_walktrap()` now uses `igraph_integer_t` for its `steps` argument. +- `igraph_coreness()` now uses an `igraph_vector_int_t` to return the coreness + values. +- `igraph_create()` now uses an `igraph_vector_int_t` for its `edges` parameter. +- `igraph_create_bipartite()` now uses an `igraph_vector_int_t` for its `edges` parameter. +- `igraph_compose()` now returns the edge maps in an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_count_multiple()` now returns the multiplicities in an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_decompose()` now uses an `igraph_integer_t` for its `maxcompno` and `minelements` arguments instead of a `long int`. +- `igraph_degree()` now uses an `igraph_vector_int_t` to return the degrees. If you need the degrees in a vector containing floating-point numbers instead (e.g., because you want to pass them on to some other function that takes an `igraph_vector_t`), use `igraph_strength()` instead with a null weight vector. +- `igraph_degree_sequence_game()` now takes degree sequences represented as `igraph_vector_int_t` instead of `igraph_vector_t`. +- `igraph_degseq_t`, used by `igraph_degree_sequence_game()`, uses new names for its constants. The old names are deprecated, but retained for compatibility. See `igraph_constants.h` to see which new name corresponds to which old one. +- `igraph_delete_vertices_idx()` now uses `igraph_vector_int_t` vectors to return the mapping and the inverse mapping of old vertex IDs to new ones. +- `igraph_deterministic_optimal_imitation()` now expects the list of strategies in an `igraph_vector_int_t` instead of an `igraph_int_t`. +- `igraph_dfs()` now takes an `igraph_vector_int_t` for its `order`, `order_out`, `father` and `dist` arguments instead of an `igraph_vector_t`. Furthermore, these vectors will contain -2 for vertices that have not been visited; in earlier versions, they used to contain NaN instead. Note that -1 is still used in the `father` vector to indicate the root of a DFS tree. +- `igraph_diameter()` and `igraph_diameter_dijkstra()` now use `igraph_vector_int_t` vectors to return the list of vertex and edge IDs in the diameter. +- `igraph_dominator_tree()` now takes an `igraph_vector_int_t` for its `dom` and `leftout` arguments instead of an `igraph_vector_t`. +- `igraph_dyad_census()` now uses `igraph_real_t` instead of `igraph_integer_t` for its output arguments, and it no longer returns -1 when overflow occurs. +- `igraph_edges()` now takes an `igraph_vector_int_t` for its `edges` argument instead of an `igraph_vector_t`. +- `igraph_es_multipairs()` was removed; you can use the newly added `igraph_es_all_between()` instead. +- `igraph_establishment_game()` now takes an `igraph_vector_int_t` for its `node_type_vec` argument instead of an `igraph_vector_t`. +- `igraph_eulerian_path()` and `igraph_eulerian_cycle()` now use `igraph_vector_int_t` to return the list of edge and vertex IDs participating in an Eulerian path or cycle instead of an `igraph_vector_t`. +- `igraph_feedback_arc_set()` now uses an `igraph_vector_int_t` to return the IDs of the edges in the feedback arc set instead of an `igraph_vector_t`. +- `igraph_get_adjacency()` no longer has the `eids` argument, which would produce an adjacency matrix where non-zero values were 1-based (not 0-based) edge IDs. If you need a matrix with edge IDs, create it manually. +- `igraph_get_adjacency_sparse()` now returns the sparse adjacency matrix in an `igraph_sparsemat_t` structure, and it assumes that the input matrix is _initialized_ for sake of consistency with other igraph functions. +- `igraph_get_adjacency()` and `igraph_get_adjacency_sparse()` now has a `loops` argument that lets the user specify how loop edges should be handled. +- `igraph_get_edgelist()` now uses an `igraph_vector_int_t` for its `res` parameter. +- `igraph_get_eids()` now uses `igraph_vector_int_t` to return lists of edge IDs and to receive lists of vertex IDs. +- The `path` argument of `igraph_get_eids()` was removed. You can replicate the old behaviour by constructing the list of vertex IDs explicitly from the path by duplicating each vertex in the path except the first and last ones. A helper function called `igraph_expand_path_to_pairs()` is provided to ease the transition. +- `igraph_get_eids_multi()` was removed as its design was fundamentally broken; there was no way to retrieve the IDs of all edges between a specific pair of vertices without knowing in advance how many such edges there are in the graph. Use `igraph_get_all_eids_between()` instead. +- `igraph_get_incidence()` now returns the vertex IDs corresponding to the rows and columns of the incidence matrix as `igraph_vector_int_t`. +- `igraph_get_shortest_path()`, `igraph_get_shortest_path_bellman_ford()` and `igraph_get_shortest_path_dijkstra()` now use `igraph_vector_int_t` vectors to return the list of vertex and edge IDs in the shortest path. +- `igraph_get_shortest_paths()`, `igraph_get_shortest_paths_dijkstra()` and `igraph_get_shortest_paths_bellman_ford()` now use an `igraph_vector_int_t` to return the predecessors and inbound edges instead of an `igraph_vector_long_t`. +- The functions `igraph_get_all_shortest_paths()`, `igraph_get_all_shortest_paths_dijkstra()`, `igraph_get_shortest_paths()`, `igraph_get_shortest_paths_bellman_ford()` and `igraph_get_shortest_paths_dijkstra()` now return paths in an `igraph_vector_int_list_t` instead of a pointer vector containing `igraph_vector_t` objects. +- The vector of parents in `igraph_get_shortest_paths()`, `igraph_get_shortest_paths_bellman_ford()` and `igraph_get_shortest_paths_dijkstra()` now use -1 to represent the starting vertex, and -2 for unreachable vertices. +- The `maps` parameters in `igraph_get_isomorphisms_vf2()` and `igraph_get_subisomorphisms_vf2()` are now of type `igraph_vector_int_list_t`. +- `igraph_get_stochastic()` now has an additional `weights` argument for edge weights. +- `igraph_get_stochastic_sparse()` now returns the sparse adjacency matrix in an `igraph_sparsemat_t` structure, and it assumes that the input matrix is _initialized_ for sake of consistency with other igraph functions. It also received an additional `weights` argument for edge weights. +- `igraph_girth()` now uses an `igraph_vector_int_t` for its `circle` parameter. +- `igraph_girth()` now uses `igraph_real_t` as the return value so we can return infinity for graphs with no cycles (instead of zero). +- The `cliques` parameters of type `igraph_vector_ptr_t` in `igraph_graphlets()`, `igraph_graphlets_candidate_basis()` and `igraph_graphlets_project()` were changed to an `igraph_vector_int_list_t`. +- `igraph_hrg_init()` and `igraph_hrg_resize()` now takes an `igraph_integer_t` as their size arguments instead of an `int`. +- `igraph_hrg_consensus()` now returns the parent vector in an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_hrg_create()` now takes a vector of probabilities corresponding to the internal nodes of the dendogram. It used to also take probabilities for the leaf nodes and then ignore them. +- `igraph_hrg_predict()` now uses an `igraph_vector_int_t` for its `edges` parameter. +- `igraph_hrg_sample()` now always samples a single graph only. Use `igraph_hrg_sample_many()` if you need more than one sample, and call `igraph_hrg_fit()` beforehand if you do not have a HRG model but only a single input graph. +- `igraph_hrg_size()` now returns an `igraph_integer_t` instead of an `int`. +- `igraph_incidence()` does not accept negative incidence counts any more. +- `igraph_incident()` now uses an `igraph_vector_int_t` for its `eids` parameter. +- The `res` parameter in `igraph_independent_vertex_sets()` is now an `igraph_vector_int_list_t`. +- `igraph_induced_subgraph_map()` now uses `igraph_vector_int_t` vectors to return the mapping and the inverse mapping of old vertex IDs to new ones. +- `igraph_intersection()` now uses an `igraph_vector_int_t` for its `edge_map1` and `edge_map2` parameters. +- The `edgemaps` parameter of `igraph_intersection_many()` is now an `igraph_vector_int_list_t` instead of a pointer vector. +- `igraph_is_chordal()` now uses an `igraph_vector_int_t` for its `alpha`, `alpham1` and `fill_in` parameters. +- `igraph_is_graphical()` and `igraph_is_bigraphical()` now take degree sequences represented as `igraph_vector_int_t` instead of `igraph_vector_t`. +- `igraph_is_matching()`, `igraph_is_maximal_matching()` and `igraph_maximum_bipartite_matching` now use an `igraph_vector_int_t` to return the matching instead of an `igraph_vector_long_t`. +- `igraph_is_mutual()` has an additional parameter which controls whether directed self-loops are considered mutual. +- The `vids` parameter for `igraph_isoclass_subgraph()` is now an `igraph_vector_int_t` instead of `igraph_vector_t`. +- `igraph_isomorphic_vf2()`, `igraph_get_isomorphisms_vf2_callback()` (which used to be called `igraph_isomorphic_function_vf2()`) and `igraph_isohandler_t` now all use `igraph_vector_int_t` for their `map12` and `map21` parameters. +- The `cliques` parameter of type `igraph_vector_ptr_t` in `igraph_largest_cliques()` was changed to an `igraph_vector_int_list_t`. +- The `res` parameters of type `igraph_vector_ptr_t` in `igraph_largest_independent_vertex_sets()` and `igraph_largest_weighted_cliques()` were changed to an `igraph_vector_int_list_t`. +- The dimension vector parameter for `igraph_square_lattice()` (used to be `igraph_lattice()`) is now an `igraph_vector_int_t` instead of `igraph_vector_t`. +- The maxiter parameter of `igraph_layout_bipartite()` is now an `igraph_integer_t` instead of `long int`. +- The fixed parameter of `igraph_layout_drl()` and `igraph_layout_drl_3d()` was removed as it has never been implemented properly. +- The width parameter of `igraph_layout_grid()` is now an `igraph_integer_t` instead of `long int`. +- The width and height parameters of `igraph_layout_grid_3d()` are now `igraph_integer_t` instead of `long int`. +- The dimension parameter of `igraph_layout_mds()` is now an `igraph_integer_t` instead of `long int`. +- The `roots` and `rootlevel` parameters of `igraph_layout_reingold_tilford()` are now `igraph_vector_int_t` instead of `igraph_vector_t`. +- The `roots` and `rootlevel` parameters of `igraph_layout_reingold_tilford_circular()` are now `igraph_vector_int_t` instead of `igraph_vector_t`. +- The order parameter of `igraph_layout_star()` is now an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- The maxiter parameter of `igraph_layout_sugiyama()` is now an `igraph_integer_t` instead of `long int`. Also, the function now uses an `igraph_vector_int_t` for its `extd_to_orig_eids` parameter. +- The shifts parameter of `igraph_lcf_vector()` is now an `igraph_vector_int_t` instead of an `igraph_vector_t`. +- `igraph_matrix_minmax()`, `igraph_matrix_which_minmax()`, `igraph_matrix_which_min()` and `igraph_matrix_which_max()` no longer return an error code. The return type is now `void`. These functions never fail. +- `igraph_maxflow()` now uses an `igraph_vector_int_t` for its `cut`, `partition` and `partition2` parameters. +- The `igraph_maxflow_stats_t` struct now contains `igraph_integer_t` values instead of `int` ones. +- The `res` parameters in `igraph_maximal_cliques()` and `igraph_maximal_cliques_subset()` are now of type `igraph_vector_int_list_t`. +- Callbacks used by `igraph_maximal_cliques_callback()` need to be updated to account for the fact that the callback does not own the clique passed to it any more; the callback needs to make a copy if it wants to hold on to the clique for a longer period of time. If the callback does not need to store the clique, it does not need to do anything any more, and it must not destroy or free the clique. +- The `res` parameter in `igraph_maximal_independent_vertex_sets()` is now an `igraph_vector_int_list_t`. +- `igraph_maximum_cardinality_search()` now uses an `igraph_vector_int_t` for its `alpha` and `alpham1` arguments. +- `igraph_mincut()` now uses an `igraph_vector_int_t` for its `cut`, `partition` and `partition2` parameters. +- `igraph_moran_process()` now expects the list of strategies in an `igraph_vector_int_t` instead of an `igraph_int_t`. +- Motif callbacks of type `igraph_motifs_handler_t` now take an `igraph_vector_int_t` with the vertex IDs instead of an `igraph_vector_t`, and use `igraph_integer_t` for the isoclass parameter. +- Motif functions now use `igraph_integer_t` instead of `int` for their `size` parameter. +- `igraph_neighborhood_size()` now uses an `igraph_vector_int_t` for its `res` parameter. +- The `res` parameter of `igraph_neighborhood()` is now an `igraph_vector_int_list_t`. +- `igraph_neighbors()` now uses an `igraph_vector_int_t` for its `neis` parameter. +- `igraph_permute_vertices()` now takes an `igraph_vector_int_t` as the permutation vector. +- `igraph_power_law_fit()` does not calculate the p-value automatically any more because the previous estimation method did not match the results from the original paper of Clauset, Shalizi and Newman (2009) and the implementation of the method outlined in the paper runs slower than the previous naive estimate. A separate function named `igraph_plfit_result_calculate_p_value()` is now provided for calculating the p-value. The automatic selection of the `x_min` cutoff also uses a different method than earlier versions. As a consequence, results might be slightly different if you used tests where the `x_min` cutoff was selected automatically. The new behaviour is now consistent with the defaults of the underlying `plfit` library. +- `igraph_preference_game()` now uses an `igraph_vector_int_t` to return the types of the nodes in the generated graph. +- `igraph_random_walk()` now uses an `igraph_vector_int_t` for its results. Also, the function now takes both vertices and edges as parameters. It can return IDs of vertices and/or edges on the walk. The function now takes weights as a parameter to support weighted graphs. +- `igraph_random_edge_walk()` now uses an `igraph_vector_int_t` for its `edgewalk` parameter. +- `igraph_read_graph_dimacs_flow()` now uses an `igraph_vector_int_t` for its label parameter. +- `igraph_read_graph_graphml()` now uses `igraph_integer_t` for its `index` argument. +- `igraph_read_graph_pajek()` now creates a Boolean `type` attribute for bipartite graphs. Previously it created a numeric attribute. +- `igraph_realize_degree_sequence()` now uses an `igraph_vector_int_t` for its `outdeg` and `indeg` parameters. +- `igraph_reindex_membership()` now uses an `igraph_vector_int_t` for its `new_to_old` parameter. +- `igraph_rng_seed()` now requires an `igraph_uint_t` as its seed arguments. RNG implementations are free to use only the lower bits of the seed if they do not support 64-bit seeds. +- `igraph_rngtype_rand` (i.e. the RNG that is based on BSD `rand()`) was removed due to poor statistical properties that sometimes resulted in weird artifacts like all-even "random" numbers when igraph's usage patterns happened to line up with the shortcomings of the `rand()` generator in a certain way. +- `igraph_roulette_wheel_imitation()` now expects the list of strategies in an `igraph_vector_int_t` instead of an `igraph_int_t`. +- `igraph_similarity_dice_pairs()` now uses an `igraph_vector_int_t` for its `pairs` parameter. +- `igraph_similarity_jaccard_pairs()` now uses an `igraph_vector_int_t` for its `pairs` parameter. +- `igraph_simple_interconnected_islands_game()` does not generate multi-edges between islands any more. +- `igraph_sort_vertex_ids_by_degree()` and `igraph_topological_sorting()` now use an `igraph_vector_int_t` to return the vertex IDs instead of an `igraph_vector_t`. +- `igraph_spanning_tree()`, `igraph_minimum_spanning_tree()` and `igraph_random_spanning_tree()` now all use an `igraph_vector_int_t` to return the vector of edge IDs in the spanning tree instead of an `igraph_vector_t`. +- `igraph_sparsemat_cholsol()`, `igraph_sparsemat_lusol()`, `igraph_sparsemat_symbqr()` and `igraph_sparsemat_symblu()` now take an `igraph_integer_t` as their `order` parameter. +- `igraph_sparsemat_count_nonzero()` and `igraph_sparsemat_count_nonzerotol()` now return an `igraph_integer_t`. +- `igraph_sparsemat_is_symmetric()` now returns an error code and the result itself is provided in an output argument. +- The `values` argument of `igraph_sparsemat_transpose()` was removed; now the function always copies the values over to the transposed matrix. +- `igraph_spmatrix_t` and related functions were removed as they mostly duplicated functionality that was already present in `igraph_sparsemat_t`. Functions that used `igraph_spmatrix_t` in the library now use `igraph_sparsemat_t`. +- `igraph_stochastic_imitation()` now expects the list of strategies in an `igraph_vector_int_t` instead of an `igraph_int_t`. +- `igraph_st_mincut()` now uses an `igraph_vector_int_t` for its `cut`, `partition` and `partition2` parameters. +- `igraph_st_vertex_connectivity()` now ignores edges between source and target for `IGRAPH_VCONN_NEI_IGNORE` +- `igraph_strvector_get()` now returns strings in the return value, not in an output argument. +- `igraph_subcomponent()` now uses an `igraph_integer_t` for the seed vertex instead of an `igraph_real_t`. It also uses an `igraph_vector_int_t` to return the list of vertices in the same component as the seed vertex instead of an `igraph_vector_t`. +- `igraph_subisomorphic_vf2()`, `igraph_get_subisomorphisms_vf2_callback()` (which used to be called `igraph_subisomorphic_function_vf2()`) and `igraph_isomorphic_bliss()` now all use `igraph_vector_int_t` for their `map12` and `map21` parameters. +- The `maps` parameters in `igraph_subisomorphic_lad()`, `igraph_get_isomorphisms_vf2()` and `igraph_get_subisomorphisms_vf2()` are now of type `igraph_vector_int_list_t`. +- `igraph_subisomorphic_lad()` now uses an `igraph_vector_int_t` for its `map` parameter. Also, its `domains` parameter is now an `igraph_vector_int_list_t` instead of a pointer vector containing `igraph_vector_t` objects. +- `igraph_unfold_tree()` now uses an `igraph_vector_int_t` for its `vertex_index` and `roots` parameters. +- `igraph_union()` now uses an `igraph_vector_int_t` for its `edge_map1` and `edge_map2` parameters. +- The `edgemaps` parameter of `igraph_union_many()` is now an `igraph_vector_int_list_t` instead of a pointer vector. +- `igraph_vector_init_copy()` was refactored to take _another_ vector that the newly initialized vector should copy. The old array-based initialization function is now called `igraph_vector_init_array()`. +- `igraph_vector_ptr_init_copy()` was renamed to `igraph_vector_ptr_init_array()` for sake of consistency. +- `igraph_vs_vector()`, `igraph_vss_vector()` and `igraph_vs_vector_copy()` now all take an `igraph_vector_int_t` as the vector of vertex IDs, not an `igraph_vector_t`. Similarly, `igraph_vs_as_vector()` now returns the vector of matched vertex IDs in an `igraph_vector_int_t`, not an `igraph_vector_t`. +- The `res` parameter of `igraph_weighted_cliques()` is now an `igraph_vector_int_list_t`. +- `igraph_write_graph_dimacs_flow()` now uses `igraph_integer_t` for the source and target vertex index instead of a `long int`. +- `igraph_vector_*()`, `igraph_matrix_*()`, `igraph_stack_*()`, `igraph_array_*()` and several other generic igraph data types now use `igraph_integer_t` for indexing, _not_ `long int`. Please refer to the headers for the exact details; the list of affected functions is too large to include here. +- `igraph_vector_minmax()` and `igraph_vector_which_minmax()` no longer return an error code. The return type is now `void`. These functions never fail. +- `igraph_vector_order()` was removed; use `igraph_vector_int_pair_order()` instead. (The original function worked for vectors containing integers only). +- `igraph_vector_resize_min()` and `igraph_matrix_resize_min()` no longer return an error code (return type is now `void`). The vector or matrix is always left in a consistent state by these functions, with all data intact, even if releasing unused storage is not successful. +- `igraph_vector_qsort_ind()` and its variants now take an `igraph_order_t` enum instead of a boolean to denote whether the order should be ascending or descending. +- `igraph_weighted_adjacency()` now returns the weights in a separate vector instead of storing it in a vertex attribute. The reason is twofold: first, the previous solution worked only with the C attribute handler (not the ones from the higher-level interfaces), and second, it wasn't consistent with other igraph functions that use weights provided as separate arguments. +- The `loops` argument of `igraph_weighted_adjacency()` was converted to an `igraph_loops_t` for sake of consistency with `igraph_adjacency()` and `igraph_get_adjacency()`. +- `igraph_write_graph_gml()` takes an additional bitfield parameter controlling some aspects of writing the GML file. +- The `add_edges()` function in the attribute handler now takes an `igraph_vector_int_t` for its `edges` parameter instead of an `igraph_vector_t`. The `add_vertices()` function now takes an `igraph_integer_t` for the vertex count instead of a `long int`. The `combine_vertices()` and `combine_edges()` functions now take an `igraph_vector_ptr_t` containing vectors of type `igraph_vector_int_t` in their `merges` parameters. The `get_info()` function now uses `igraph_vector_int_t` to return the types of the graph, vertex and edge attribute types. The `permute_vertices()` and `permute_edges()` functions in the attribute handler tables now take an `igraph_vector_int_t` instead of an `igraph_vector_t` for the index vectors. These are relevant only to maintainers of higher level interfaces to igraph; they should update their attribute handlers accordingly. +- igraph functions that interface with external libraries such as BLAS or LAPACK may now fail if the underlying BLAS or LAPACK implementation cannot handle the size of input vectors or matrices (BLAS and LAPACK are usually limited to vectors whose size fits in an `int`). `igraph_blas_dgemv()` and `igraph_blas_dgemv_array()` thus now return an `igraph_error_t`, which may be set to `IGRAPH_EOVERFLOW` if the input vectors or matrices are too large. +- Functions that used an `igraph_vector_t` to represent cluster size and cluster membership now use an `igraph_vector_int_t` instead. These are: + - `igraph_connected_components()` (used to be `igraph_clusters()` in 0.9 and before) + - `igraph_community_eb_get_merges()` + - `igraph_community_edge_betweenness()` + - `igraph_community_fastgreedy()` + - `igraph_community_fluid_communities()` + - `igraph_community_infomap()` + - `igraph_community_label_propagation()` + - `igraph_community_leading_eigenvector()` + - `igraph_community_leiden()` + - `igraph_community_multilevel()` + - `igraph_community_optimal_modularity()` + - `igraph_community_spinglass()` + - `igraph_community_spinglass_single()` + - `igraph_community_to_membership()` + - `igraph_community_walktrap()` + - `igraph_compare_communities()` + - `igraph_le_community_to_membership()` + - `igraph_modularity()` + - `igraph_reindex_membership()` + - `igraph_split_join_distance()` + - `igraph_community_multilevel()` additionally uses a `igraph_matrix_int_t` instead of `igraph_matrix_t()` for its memberships parameter. +- `IGRAPH_TOTAL` was removed from the `igraph_neimode_t` enum; use the equivalent `IGRAPH_ALL` instead. + +### Added + +- A new integer type, `igraph_uint_t` has been added. This is the unsigned pair of `igraph_integer_t` and they are always consistent in size. +- A new container type, `igraph_vector_list_t` has been added, replacing most uses of `igraph_vector_ptr_t` in the API where it was used to hold a variable-length list of vectors. The type contains `igraph_vector_t` objects, and it is fully memory managed (i.e. its contents do not need to be allocated and destroyed manually). There is also a variant named `igraph_vector_int_list_t` for vectors of `igraph_vector_int_t` objects. +- A new container type, `igraph_matrix_list_t` has been added, replacing most uses of `igraph_vector_ptr_t` in the API where it was used to hold a variable-length list of matrices. The type contains `igraph_matrix_t` objects, and it is fully memory managed (i.e. its contents do not need to be allocated and destroyed manually). +- A new container type, `igraph_graph_list_t` has been added, replacing most uses of `igraph_vector_ptr_t` in the API where it was used to hold a variable-length list of graphs. The type contains `igraph_t` objects, and it is fully memory managed (i.e. its contents do not need to be allocated and destroyed manually). +- The vector container type, `igraph_vector_t`, has been extended with a new variant whose functions all start with `igraph_vector_fortran_int_...`. This vector container can be used for interfacing with Fortran code as it guarantees that the integers in the vector are compatible with Fortran integers. Note that `igraph_vector_int_t` is not suitable any more, as the elements of `igraph_vector_int_t` are of type `igraph_integer_t`, whose size may differ on 32-bit and 64-bit platforms, depending on how igraph was compiled. +- `igraph_adjlist_init_from_inclist()` to create an adjacency list from an already existing incidence list by resolving edge IDs to their corresponding endpoints. This function is useful for algorithms when both an adjacency and an incidence list is needed and they should be in the same order. +- `igraph_almost_equals()` and `igraph_cmp_epsilon()` to compare floating point numbers with a relative tolerance. +- `igraph_betweenness_subset()` and `igraph_edge_betweenness_subset()` calculates betweenness and edge betweenness scores using shortest paths between a subset of vertices only (#1711, thanks to @guyroznb) +- `igraph_blas_dgemm()` to multiply two matrices. +- `igraph_calloc()` and `igraph_realloc()` are now publicly exposed; these functions provide variants of `calloc()` and `realloc()` that can safely be deallocated within igraph functions. +- `igraph_circulant()` to create circulant graphs (#1856, thanks to @Gomango999). +- `igraph_complex_almost_equals()` to compare complex numbers with a relative tolerance. +- `igraph_eccentricity_dijkstra()` finds the longest weighted path length among all shortest paths between a set of vertices. +- `igraph_enter_safelocale()` and `igraph_exit_safelocale()` for temporarily setting the locale to C. Foreign format readers and writers require a locale which uses a decimal point instead of decimal comma. +- `igraph_es_all_between()` to create an edge selector that selects all edges between a pair of vertices. +- `igraph_full_multipartite()` generates full multipartite graphs (a generalization of bipartite graphs to multiple groups). +- `igraph_fundamental_cycles()` computes a fundamental cycle basis (experimental). +- `igraph_generalized_petersen()` to create generalized Petersen graphs (#1844, thanks to @alexsyou). +- `igraph_get_all_eids_between()` returns the IDs of all edges between a pair of vertices. +- `igraph_get_k_shortest_paths()` finds the k shortest paths between a source and a target vertex. +- `igraph_get_laplacian()` and `igraph_get_laplacian_sparse()` return the Laplacian matrix of the graph as a dense or sparse matrix, with various kinds of normalizations. They replace the now-deprecated `igraph_laplacian()` function. This makes the API consistent with `igraph_get_adjacency()` and `igraph_get_adjacency_sparse()`. +- `igraph_get_widest_path()`, `igraph_get_widest_paths()`, `igraph_widest_path_widths_dijkstra()` and `igraph_widest_path_widths_floyd_warshall()` to find widest paths (#1893, thanks to @Gomango999). +- `igraph_graph_center()` finds the central vertices of the graph. The central vertices are the ones having a minimum eccentricity (PR #2084, thanks to @pradkrish). +- `igraph_graph_count()` returns the number of unlabelled graphs on a given number of vertices. It is meant to find the maximum isoclass value. +- `igraph_has_mutual()` checks if a directed graph has any mutual edges. +- `igraph_heap_clear()` and `igraph_heap_min_clear()` remove all elements from an `igraph_heap_t` or an `igraph_heap_min_t`, respectively. +- `igraph_invalidate_cache()` invalidates all cached graph properties, forcing their recomputation next time they are requested. This function should not be needed in everyday usage, but may be useful in debugging and benchmarking. +- `igraph_is_forest()` to check whether a graph is a forest (#1888, thanks to @rohitt28). +- `igraph_is_acyclic()` to check whether a graph is acyclic (#1945, thanks to @borsgeorgica). +- `igraph_is_perfect()` to check whether a graph is a perfect graph (#1730, thanks to @guyroznb). +- `igraph_hub_and_authority_scores()` calculates the hub and authority scores of a graph as a matching pair. +- `igraph_layout_umap()` and `igraph_layout_umap_3d()` to lay out a graph in 2D or 3D space using the UMAP dimensionality reduction algorithm. +- `igraph_local_scan_subset_ecount()` counts the number of edges in induced sugraphs from a subset of vertices. +- `igraph_matrix_view_from_vector()` allows interpreting the data stored in a vector as a matrix of the specified size. +- `igraph_minimum_cycle_basis()` computes an unweighted minimum cycle basis (experimental). +- `igraph_pseudo_diameter()` and `igraph_pseudo_diameter_dijkstra()` to determine a lower bound for the diameter of a graph (unweighted or weighted). +- `igraph_regular_tree()` creates a regular tree where all internal vertices have the same total degree. +- `igraph_rngtype_pcg32` and `igraph_rngtype_pcg64` implement 32-bit and 64-bit variants of the PCG random number generator. +- `igraph_rng_get_pois()` generates random variates from the Poisson distribution. +- `igraph_roots_for_tree_layout()` computes a set of roots suitable for a nice tree layout. +- `igraph_spanner()` calculates a spanner of a graph with a given stretch factor (#1752, thanks to @guyroznb) +- `igraph_sparse_adjacency()` and `igraph_sparse_weighted_adjacency()` constructs graphs from (weighted) sparse matrices. +- `igraph_sparsemat_get()` to retrieve a single element of a sparse matrix. +- `igraph_sparsemat_normalize_rows()` and `igraph_sparsemat_normalize_cols()` to normalize sparse matrices row-wise or column-wise. +- `igraph_stack_capacity()` to query the capacity of a stack. +- `igraph_strvector_capacity()` returns the maximum number of strings that can be stored in a string vector without reallocating the memory block holding the pointers to the individual strings. +- `igraph_strvector_merge()` moves all strings from one string vectors to the end of another without re-allocating them. +- `igraph_strvector_push_back_len()` adds a new string to the end of a string vector and allows the user to specify the length of the string being added. +- `igraph_strvector_reserve()` reserves space for a given number of string pointers in a string vector. +- `igraph_symmetric_tree()` to create a tree with the specified number of branches at each level (#1859, thanks to @YuliYudith and @DoruntinaM). +- `igraph_trussness()` calculates the trussness of each edge in the graph (#1034, thanks to @alexperrone) +- `igraph_turan()` generates Turán graphs (#2088, thanks to @pradkrish) +- `igraph_vector_all_almost_e()`, `igraph_vector_complex_all_almost_e()`, `igraph_matrix_all_almost_e()`, `igraph_matrix_complex_all_almost_e()` for elementwise comparisons of floating point vector and matrices with a relative tolerance. +- `igraph_vector_complex_zapsmall()` and `igraph_matrix_complex_zapsmall()` for replacing small components of complex vector or matrix elements with exact zeros. +- `igraph_vector_lex_cmp_untyped()` and `igraph_vector_colex_cmp_untyped()` for lexicographic and colexicographic comparison of vectors, similarly to `igraph_vector_lex_cmp()` and `igraph_vector_colex_cmp()`. The difference between the two variants is that the untyped versions declare the vectors as `const void*`, making the functions suitable as comparators for `qsort()`. +- `igraph_vector_permute()` functions to permute a vector based on an index vector. +- `igraph_vector_ptr_sort_ind()` to obtain an index vector that would sort a vector of pointers based on some comparison function. +- `igraph_vector_range()` to fill an existing vector with a range of increasing numbers. +- `igraph_vector_remove_fast()` functions to remove an item from a vector by swapping it with the last element and then popping it off. It allows one to remove an item from a vector in constant time if the order of items does not matter. +- `igraph_vertex_path_from_edge_path()` converts a sequence of edge IDs representing a path to an equivalent sequence of vertex IDs that represent the vertices the path travelled through. +- `igraph_vs_range()`, `igraph_vss_range()`, `igraph_es_range()` and `igraph_ess_range()` creates vertex and edge sequences from C-style intervals (closed from the left, open from the right). +- `igraph_wheel()` to create a wheel graph (#1938, thanks to @kwofach). + +### Removed + +- `igraph_adjlist_remove_duplicate()`, `igraph_betweenness_estimate()`, `igraph_closeness_estimate()`, `igraph_edge_betweenness_estimate()`, `igraph_inclist_remove_duplicate()`, `igraph_is_degree_sequence()` and `igraph_is_graphical_degree_sequence()` were deprecated earlier in 0.9.0 and are now removed in this release. +- `igraph_dnorm()`, `igraph_strvector_move_interval()`, `igraph_strvector_permdelete()` and `igraph_strvector_remove_negidx()` were removed. These are not breaking changes as the functions were never documented, they were only exposed from one of the headers. +- `igraph_eigen_laplacian()`, `igraph_es_fromto()` and `igraph_maximum_matching()` were removed. These are not breaking changes either as the functions were never implemented, they returned an error code unconditionally. + +### Changed + +- `igraph_degree_sequence_game()` now supports an additional method, `IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE`, an edge-switching MCMC sampler. +- `igraph_get_adjacency()` and `igraph_get_adjacency_sparse()` now count loop edges _twice_ in undirected graphs when using `IGRAPH_GET_ADJACENCY_BOTH`. This is to ensure consistency with `IGRAPH_GET_ADJACENCY_UPPER` and `IGRAPH_GET_ADJACENCY_LOWER` such that the sum of the upper and the lower triangle matrix is equal to the full adjacency matrix even in the presence of loop edges. +- `igraph_matrix_print()` and `igraph_matrix_fprint()` functions now align columns when priting. +- `igraph_read_graph_gml()` now supports graph attributes (in addition to vertex and edge attributes). +- `igraph_read_graph_gml()` now uses NaN as the default numerical attribute values instead of 0. +- The Pajek parser in `igraph_read_graph_pajek()` is now less strict and accepts more files. +- `igraph_ring()` no longer simplifies its result when generating a one- or two-vertex graph. The one-cycle has a self-loop and the undirected two-cycle has parallel edges. +- `igraph_vector_view()` now allows `data` to be `NULL` in the special case when `length == 0`. +- `igraph_version()` no longer returns an error code. +- `igraph_write_graph_gml()` uses the `creator` parameter in a different way: the supplied string is now written into the Creator line as-is instead of being appended to a default value. +- `igraph_write_graph_gml()` skips writing NaN values. These two changes ensure consistent round-tripping. +- `igraph_write_graph_gml()` and `igraph_read_graph_gml()` now have limited support for entity encoding. +- `igraph_write_graph_ncol()` now preserves the edge ordering of the graph when writing an NCOL file. +- igraph functions that take an ARPACK options object now also accept `NULL` in place of an options object, and they will fall back to using a default object provided by `igraph_arpack_options_get_default()`. +- Foreign format readers now present more informative error messages. +- The default tolerance of the zapsmall functions is now `eps^(2/3)` instead of `eps^(1/2)` where eps is the machine epsilon of `igraph_real_t`. +- It is now possible to override the uniform integer and the Poisson samplers in the random number generator interface. + +### Fixed + +- When an error occurs during parsing DL, GML, NCOL, LGL or Pajek files, line numbers are now reported correctly. +- The GraphML parser does not print to stderr any more in case of encoding errors and other error conditions originating from the underlying `libxml2` library. +- The GraphML parser would omit some edges and vertices when reading files with custom attribute types, such as those produced by yEd. This is now corrected. +- The GML parser no longer mixes up Inf and NaN and -Inf now works. +- The GML parser now supports nodes with no id field. +- The GML parser now performs more stringent checks on the input file, such as verifying that `id`, `source`, `target` and `directed` fields are not duplicated. +- The core data structures (vector, etc.) have overflow checks now. +- Deterministic graph generators, as well as most random ones, have overflow checks now. +- Graphs no longer lose all their attributes after calling `igraph_contract_vertices()`. +- `igraph_hrg_init()` does not throw an assertion error anymore for zero vertices. +- `igraph_matrix_complex_create()` and `igraph_matrix_complex_create_polar()` now set their sizes correctly. +- `igraph_random_walk()` took one fewer steps than specified. +- `igraph_sparsemat_getelements_sorted()` did not sort the elements for triplet matrices correctly; this is fixed now. +- `igraph_write_graph_gml()` no longer produces corrupt output when some string attribute values contain `"` characters. + +### Deprecated + +- `igraph_clusters()` has been renamed to `igraph_connected_components()`; the old name is deprecated and will be removed in 0.11. +- `igraph_complex_eq_tol()` is now deprecated in favour of `igraph_complex_almost_equals()`. +- `igraph_get_sparsemat()` is deprecated in favour of `igraph_get_adjacency_sparse()`, and will be removed in 0.11. Note that `igraph_get_adjacency_sparse()` takes an _initialized_ sparse matrix as input, unlike `igraph_get_sparsemat()` which takes an uninitialized one. +- `igraph_get_stochastic_sparsemat()` is deprecated in favour of `igraph_get_stochastic_sparse()`, and will be removed in 0.11. Note that `igraph_get_stochastic_sparse()` takes an _initialized_ sparse matrix as input, unlike `igraph_get_stochastic_sparsemat()`, which takes an uninitialized one. +- `igraph_isomorphic_34()` has been deprecated in favour of `igraph_isomorphic()`. Note that `igraph_isomorphic()` calls an optimized version for directed graphs of size 3 and 4, and undirected graphs with 3-6 vertices, so there is no need for a separate function. +- `igraph_laplacian()` is now deprecated; use `igraph_get_laplacian()` or `igraph_get_laplacian_sparse()` depending on whether you need a dense or a sparse matrix. +- `igraph_lattice()` has been renamed to `igraph_square_lattice()` to indicate that this function generates square lattices only. The old name is deprecated and will either be removed in 0.11 or will be changed to become a generic lattice generator that also supports other types of lattices. +- `igraph_local_scan_neighborhood_ecount()` is now deprecated in favour of `igraph_local_scan_subset_ecount()`. +- `igraph_matrix_all_e_tol()` is now deprecated in favour of `igraph_matrix_all_almost_e()`. +- `igraph_matrix_copy()` is now deprecated; use `igraph_matrix_init_copy()` instead. The new name emphasizes that the function _initializes_ the first argument instead of expecting an already-initialized target matrix. The old name will be removed in 0.11. +- `igraph_matrix_e()` and `igraph_matrix_e_ptr()` have been renamed to `igraph_matrix_get()` and `igraph_matrix_get_ptr()`. The old names are deprecated and will be removed in 0.11. +- `igraph_random_edge_walk()` has been deprecated by `igraph_random_walk()` to support edges and/or vertices for the random walk in a single function. It will be removed in 0.11. +- `igraph_sparsemat_copy()`, `igraph_sparsemat_diag()` and `igraph_sparsemat_eye()` have been renamed to `igraph_sparsemat_init_copy()`, `igraph_sparsemat_init_diag()` and `igraph_sparsemat_init_eye()` to indicate that they _initialize_ a new sparse matrix. The old names are deprecated and will be removed in 0.11. +- `igraph_strvector_add()` has been renamed to `igraph_strvector_push_back()` for sake of consistency with other vector-like data structures; the old name is deprecated and will be removed in 0.11. +- `igraph_strvector_copy()` has been renamed to `igraph_strvector_init_copy()` for sake of consistency with other vector-like data structures; the old name is deprecated and will be removed in 0.11. +- `igraph_strvector_get()` now returns a `const char*` and not a `char*` to indicate that you are not supposed to modify the string in the vector directly. If you do want to modify it and you are aware of the implications (i.e. the new string must not be longer than the original one), you can cast away the constness of the return value before modifying it. +- `igraph_strvector_set2()` has been renamed to `igraph_strvector_set_len()`; the old name is deprecated and will be removed in 0.11. +- `igraph_tree()` has been renamed to `igraph_kary_tree()`; the old name is deprecated and will be removed in 0.11. +- `igraph_vector_e()` and `igraph_vector_e_ptr()` have been renamed to `igraph_vector_get()` and `igraph_vector_get_ptr()`. The old names are deprecated and will be removed in 0.11. +- `igraph_vector_e_tol()` is now deprecated in favour of `igraph_vector_all_almost_e()`. +- `igraph_vector_copy()` is now deprecated; use `igraph_vector_init_copy()` instead. The new name emphasizes that the function _initializes_ the first argument instead of expecting an already-initialized target vector. The old name will be removed in 0.11. +- `igraph_vector_init_seq()` is now deprecated in favour of `igraph_vector_init_range()`, which uses C-style intervals (closed from the left and open from the right). +- `igraph_vs_seq()`, `igraph_vss_seq()`, `igraph_es_seq()` and `igraph_ess_seq()` are now deprecated in favour of `igraph_vs_range()`, `igraph_vss_range()`, `igraph_es_range()` and `igraph_ess_range()` because these use C-style intervals (closed from the left, open from the right). +- `igraph_write_graph_dimacs()` has been renamed to `igraph_write_graph_dimacs_flow()`; the old name is deprecated and might be re-used as a generic DIMACS writer in the future. Also, the function now uses `igraph_integer_t` as the source and target vertex IDs instead of a `long int`. +- `igraph_zeroin()` is deprecated and will be removed in 0.11, with no replacement. The function is not graph-related and was never part of the public API. +- The macros `igraph_Calloc`, `igraph_Realloc` and `igraph_Free` have been deprecated in favour of `IGRAPH_CALLOC`, `IGRAPH_REALLOC` and `IGRAPH_FREE` to simplify the API. The deprecated variants will be removed in 0.11. + +### Other + +- Documentation improvements. +- Support for Intel's LLVM-based compiler. + +## [0.9.10] - 2022-09-02 + +### Added + +- `igraph_reverse_edges()` reverses the specified edges in the graph while preserving all attributes. + +### Changed + +- The `IGRAPH_ARPACK_PROD` error code is no longer used. Instead, the specific error encountered while doing matrix multiplication is reported. +- XML external entities are not resolved any more when parsing GraphML files to prevent XML external entity injection (XXE) attacks. Standard XML entities like `<` or `"` still work. + +### Fixed + +- Fixed incorrect results from `igraph_local_scan_1_ecount()` when the graph was directed but the mode was `IGRAPH_ALL` and some nodes had loop edges. See issue #2092. +- Fixed incorrect counting of self-loops in `igraph_local_scan_neighborhood_ecount()` when the graph was undirected. +- In some rare edge cases, `igraph_pagerank()` with the ARPACK method and `igraph_hub_score()` / `igraph_authority_score()` could return incorrect results. The problem could be detected by checking that the returned eigenvalue is not negative. See issue #2090. +- `igraph_permute_vertices()` now checks for out-of-range indices and duplicates in the permutation vector. +- `igraph_create()` now checks for non-finite vertex indices in the edges vector. +- `igraph_eigenvector_centrality()` would return incorrect scores when some weights were negative. +- `igraph_es_seq()` and `igraph_ess_seq()` did not include the `to` vertex in the sequence. +- `igraph_eit_create()` and `igraph_vit_create()` now check that all edge/vertex indices are in range when creating iterators from sequence-type selectors. +- `igraph_grg_game()` now validates its arguments. +- `igraph_layout_drl()` and its 3D version now validate their inputs. +- `igraph_layout_kamada_kawai()`, `igraph_layout_fruchterman_reingold()`, `igraph_layout_drl()`, as well as their 3D versions now check for non-positive weights. +- `igraph_asymmetric_preference_game()` interpreted its `type_dist_matrix` argument incorrectly. +- Fixed incorrect result of `igraph_community_spinglass()` for null and singleton graphs. +- `igraph_layout_gem()` does not crash any more for graphs with only a single vertex. +- `igraph_bridges()` no longer uses recursion and thus is no longer prone to stack overflow. +- Include paths of dependent packages would be specified incorrectly in some environments. + +### Other + +- Documentation improvements. + +## [0.9.9] - 2022-06-04 + +### Changed + +- `igraph_community_walktrap()` now uses double precision floating point operations internally instead of single precision. +- In `igraph_community_leiden()`, the `nb_clusters` output parameter is now optional (i.e. it can be `NULL`). +- `igraph_read_graph_graphml()` no longer attempts to temporarily set the C locale, and will therefore not work correctly if the current locale uses a decimal comma. + +### Fixed + +- `igraph_community_walktrap()` would return an invalid `modularity` vector when the `merges` matrix was not requested. +- `igraph_community_walktrap()` would return a `modularity` vector that was too long for disconnected graphs. This would cause a failure in some weighted graphs when the `membership` vector was requested. +- `igraph_community_walktrap()` now checks the weight vector: only non-negative weights are accepted, and all vertices must have non-zero strength. +- `igraph_community_walktrap()` now returns a modularity score of NaN for graphs with no edges. +- `igraph_community_fast_greedy()` now returns a modularity score of NaN for graphs with no edges. +- `igraph_community_edge_betweenness()` now returns a modularity vector with a single NaN entry for graph with no edges. Previously it returned a zero-length vector. +- `igraph_community_leading_eigenvector()` does not ignore non-ARPACK-related errors from `igraph_arpack_rssolve()` any more. +- `igraph_preference_game()` now works correctly when `fixed_size` is true and + `type_dist` is not given; earlier versions had a bug where more than half of + the vertices mistakenly ended up in group 0. +- Fixed a memory leak in `igraph_hrg_fit()` when using `start=1`. +- `igraph_write_graph_dot()` now outputs NaN values unchanged. +- `igraph_write_graph_dot()` no longer produces invalid DOT files when empty string attributes are present. +- `igraph_layout_fruchterman_reingold()` and `igraph_layout_kamada_kawai()`, as well as their 3D versions, did not respect vertex coordinate bounds (`xmin`, `xmax`, etc.) when minimum values were large or maximum values were small. This is now fixed. +- The initial coordinates of the Kamada-Kawai layout (`igraph_layout_kamada_kawai()` and `igraph_layout_kamada_kawai_3d()`) are chosen to be more in line with the original publication, improving the stability of the result. See isse #963. This changes the output of the function for the same graph, compared with previous versions. To obtain the same layout, initialize coordinates with `igraph_layout_circle()` (in 2D) or `igraph_layout_sphere()` (in 3D). +- Improved numerical stability in Kamada-Kawai layout. +- Corrected a problem in the calculation of displacements in `igraph_layout_fruchterman_reingold()` and its 3D version. This fixes using the "grid" variant of the algorithm on disconnected graphs. +- `igraph_sumtree_search()` would consider search intervals open on the left and closed on the right, contrary to the documentation. This is now corrected to closed on the left and open on the right. In some cases this lead to a zero-weight element being returned for a zero search value. See issue #2080. + +### Other + +- Greatly improved error reporting from foregin format parsers. +- Documentation improvements. + +## [0.9.8] - 2022-04-08 + +### Fixed + +- Assertion failure in `igraph_bfs()` when an empty `roots` or `restricted` vector was provided. +- `igraph_diversity()` now returns 0 for degree-1 vertices. Previously it incorrectly returned NaN or +-Inf depending on roundoff errors. +- `igraph_community_walktrap()` does not crash any more when provided with + `modularity=NULL` and `membership=NULL`. + +### Other + +- Documentation improvements. + +## [0.9.7] - 2022-03-16 + +### Changed + +- `igraph_get_all_shortest_paths_dijsktra()` now uses tolerances when comparing path + lengths, and is thus robust to numerical roundoff errors. +- `igraph_vector_*_swap` and `igraph_matrix_swap` now take O(1) instead of O(n) and accept all sizes. + +### Fixed + +- NCOL and LGL format writers no longer accept "name" and "weight" attributes + of invalid types. +- The LGL writer could not access numerical weight attributes, potentially leading + to crashes. +- External PLFIT libraries and their headers are now detected at their standard + installation location. +- `igraph_vector_init()` no longer accepts negative vector sizes. +- `igraph_assortativity_nominal()` crashed on the null graph. +- Label propagation now ensures that all labels are dominant. +- Fixed incorrect partition results for walktrap algorithm (issue #1927) +- Negative values returned by `igraph_rng_get_integer()` and `RNG_INTEGER()` were incorrect, + one larger than they should have been. +- `igraph_community_walktrap()` now checks its `steps` input argument. +- The first modularity value reported by `igraph_community_walktrap()` was + incorrect (it was always zero). This is now fixed. +- `igraph_correlated_game()` would return incorrect results, or exhaust the memory, + for most input graphs that were not generated with `igraph_erdos_renyi_game_gnp()`. +- `igraph_community_label_propagation` incorrectly did not result in all labels being dominant (issue #1963, fixed in PR #1966). + +### Other + +- The C attribute handler now verifies attribute types when retrieving attributes. +- Documentation improvements. + +## [0.9.6] - 2022-01-05 + +- Isomorphism class functions (`igraph_isoclass()`, `igraph_isoclass_subgraph()`, + `igraph_isoclass_create`) and motif finder functions (`igraph_motifs_randesu()`, + `igraph_motifs_randesu_estimate()`, `igraph_motifs_randesu_callback()`) now + support undirected (sub)graphs of sizes 5 and 6. Previsouly only sizes 3 and 4 + were supported. + +### Fixed + +- igraph would not build with MinGW when using the vendored GLPK and enabling TLS. +- Removed some uses of `abort()` from vendored libraries, which could unexpectedly + shut down the host language of igraph's high-level interfaces. +- `igraph_community_label_propagation()` no longer leaves any vertices unlabeled + when they were not reachable from any labeled ones, i.e. the returned membership + vector is guaranteed not to contain negative values (#1853). +- The Kamada-Kawai layout is now interruptible. +- The Fruchterman-Reingold layout is now interruptible. +- Fixed a bug in `igraph_cmp_epsilon()` that resulted in incorrect results for + edge betweenness calculations in certain rare cases with x87 floating point + math when LTO was also enabled (#1894). +- Weighted clique related functions now fall back to the unweighted variants + when a null vertex weight vector is given to them. +- `igraph_erdos_renyi_game_(gnm|gnp)` would not produce self-loops for the singleton + graph. +- Fixed a bug in `igraph_local_efficiency()` that sometimes erroneously + reported zero as the local efficiency of a vertex in directed graphs. +- `igraph_vector_update()` (and its type-specific variants) did not check for + memory allocation failure. +- Fixed a potential crash in the GraphML reader that would be triggered by some + invalid GraphML files. + +### Other + +- `igraph_is_tree()` has improved performance and memory usage. +- `igraph_is_connected()` has improved performance when checking weak connectedness. +- Improved error handling in `igraph_maximal_cliques()` and related functions. +- The build system now checks that GLPK is of a compatible version (4.57 or later). +- The vendored `plfit` package was updated to 0.9.3. +- You can now build igraph with an external `plfit` instead of the vendored one. +- Documentation improvements. + +## [0.9.5] - 2021-11-11 + +### Fixed + +- `igraph_reindex_membership()` does not allow negative membership indices any more. + +- `igraph_rewire_directed_edges()` now generates multigraphs when edge directions + are ignored, to make it consistent with the directed case. + +- Fixed a bug in `igraph_gomory_hu_tree()` that returned only the equivalent flow + tree instead of the cut tree (#1810). + +- Fixed a bug in the `IGRAPH_TO_UNDIRECTED_COLLAPSE` mode of + `igraph_to_undirected()` that provided an incorrect merge vector to the + attribute handler, leading to problems when edge attributes were merged + using an attribute combination (#1814). + +- Fixed the behaviour of the `IGRAPH_ENABLE_LTO` option when it was set to + `AUTO`; earlier versions had a bug where `AUTO` simply checked whether LTO + is supported but then did not use LTO even if it was supported. + +- When using igraph from a CMake project, it is now checked that the project has + the C++ language enabled. This is necessary for linking to igraph with CMake. + +### Other + +- Improved the root selection method for disconnected graphs in the + Reingold-Tilford layout (#1836). The new root selection method provides + niceer results if the graph is not a tree, although it is still recommended + to use the Sugiyama layout instead, unless the input graph is _almost_ a + tree, in which case Reingold-Tilfold may still be preferred. + +- `igraph_decompose()` is now much faster for large graphs containing many + isolates or small components (#960). + +- `igraph_largest_cliques()` and `igraph_clique_number()` were re-written to + use `igraph_maximal_cliques_callback()` so they are much faster now (#804). + +- The vendored GLPK has been upgraded to GLPK 5.0. + +- Documentation improvements. + +## [0.9.4] - 2021-05-31 + +### Changed + +- Unweighted transitivity (i.e. clustering coefficient) calculations now ignore multi-edges and edge directions instead of rejecting multigraphs and directed graphs. +- `igraph_transitivity_barrat()` now returns an error code if the input graph has multiple edges (which is not handled correctly by the implementation yet). + +### Fixed + +- `igraph_local_scan_k_ecount()` now handles loops correctly. +- `igraph_transitivity_avglocal_undirected()` is no longer slower than `igraph_transitivity_local_undirected()`. +- Worked around an invalid warning issued by Clang 9.0 when compiling with OpenMP. + +### Other + +- Documentation improvements. + +## [0.9.3] - 2021-05-05 + +### Added + +- OpenMP is now enabled and used by certain functions (notably PageRank calculation) when the compiler supports it. Set `IGRAPH_OPENMP_SUPPORT=OFF` at configuration time to disable this. + +### Fixed + +- `igraph_get_incidence()` no longer reads and writes out of bounds when given a non-bipartite graph, but gives a warning and ignores edges within a part. +- `igraph_dyad_census()` no longer reports an overflow on singleton graphs, and handles loops and multigraphs correctly. Undirected graphs are handled consistently and will no longer give a warning. +- `igraph_vector_lex_cmp()` and `igraph_vector_colex_cmp()` dereferenced their arguments only once instead of twice, and therefore did not work with `igraph_vector_ptr_sort()`. +- `igraph_maximal_cliques_subset()` and `igraph_transitivity_barrat()` corrupted the error handling stack ("finally stack") under some circumstances. +- CMake package files did not respect `CMAKE_INSTALL_LIBDIR`. This only affected Linux distributions which install into `lib64` or other locations instead of `lib`. +- The parser sources could not be generated when igraph was in a location that contained spaces in its path. +- igraph no longer links to the math library (`libm`) when this is not necessary. +- `_CRT_SECURE_NO_WARNINGS` is now defined during compilation to enable compatibility with UWP. +- Fixed a compilation issue on MSYS / MinGW when link-time optimization was enabled and the `MSYS Makefiles` CMake generator was used. Some source files in igraph were renamed as a consequence, but these should not affect users of the library. + +### Deprecated + +- `igraph_rng_min()` is now deprecated; assume a constant zero as its return value if you used this function in your own code. + +### Other + +- Updated the vendored CXSparse library to version 3.2.0 + +## [0.9.2] - 2021-04-14 + +### Added + +- CMake package files are now installed with igraph. This allows `find_package(igraph)` to find igraph and detect the appropriate compilation options for projects that link to it. + +### Fixed + +- igraph can now be used as a CMake subproject in other CMake-based projects. +- The documentaton can now be built from the release tarball. +- Configuration will no longer fail when the release tarball is extracted into a subdirectory of an unrelated git repository. +- The generated pkg-config file was incorrect when `CMAKE_INSTALL_` variables were absolute paths. +- On Unix-like systems, the library name is now `libigraph.so.0.0.0`, as it used to be for igraph 0.8 and earlier. +- Fixed a return type mismatch in parser sources, and fixed some warnings with recent versions of gcc. +- Fixed a bug in `igraph_get_shortest_paths_dijkstra()` and `igraph_get_shortest_paths_bellman_ford()` that returned incorrect results for unreachable vertices. + +### Other + +- Improved installation instructions and tutorial. + +## [0.9.1] - 2021-03-23 + +### Added + +- `igraph_vector_lex_cmp()` and `igraph_vector_colex_cmp()` for lexicographic + and colexicographic comparison of vectors. These functions may also be used + for sorting. + +### Changed + +- `igraph_community_multilevel()` is now randomized (PR #1696, thanks to Daniel Noom). + +### Fixed + +- CMake settings that controlled the library installation directory name, such as `CMAKE_INSTALL_LIBDIR`, were not respected. +- Under some conditions, the generated pkg-config file contained an incorrect include directory path. +- The following functions were not exported from the shared library: `igraph_subcomponent()`, `igraph_stack_ptr_free_all()`, `igraph_stack_ptr_destroy_all()`, `igraph_status_handler_stderr()`, `igraph_progress_handler_stderr()`. +- Built-in random number generators (`igraph_rngtype_mt19937`, `igraph_rngtype_rand`, `igraph_rngtype_glibc2`) were not exported from the shared library. +- `igraph_layout_graphopt()` no longer rounds the `spring_length` parameter to an integer. +- `igraph_get_all_shortest_paths_dijkstra()` no longer modifies the `res` vector's item destructor. +- `igraph_get_shortest_path_bellman_ford()` did not work correctly when calculating paths to all vertices. +- `igraph_arpack_rnsolve()` checks its parameters more carefully. +- `igraph_community_to_membership()` does not crash anymore when `csize` is requested but `membership` is not. +- `igraph_citing_cited_type_game()`: fixed memory leaks (PR #1700, thanks to Daniel Noom). +- `igraph_transitivity_undirected()`, `igraph_transitivity_avglocal_undirected()` and `igraph_transitivity_barrat()` no longer trigger an assertion failure when used with the null graph (PRs #1709, #1710). +- `igraph_(personalized_)pagerank()` would return incorrect results for weighted multigraphs with fewer than 128 vertices when using `IGRAPH_PAGERANK_ALGO_PRPACK`. +- `igraph_diversity()` now checks its input more carefully, and throws an error when the input graph has multi-edges or is directed. +- `igraph_shortest_paths_johnson()` would return incorrect results when the `to` argument differed from `from` (thanks to Daniel Noom). +- `igraph_is_graphical()` would fail to set the result variable for certain special degree sequences in the undirected simple graph case. +- Non-maximal clique finding functions would sometimes return incomplete results when finding more than 2147483647 (i.e. 2^31 - 1) cliques. +- GLPK internal errors no longer crash igraph. +- Fixed some potential memory leaks that could happen on error conditions or when certain functions were interrupted. +- When testing a DLL build on Windows, the `PATH` was sometimes not set correctly, causing the tests to fail (PR #1692). +- When compiling from the git repository (as opposed to the release tarball), the build would fail with recent versions of `bison` and `flex`. + +### Other + +- Documentation improvements. +- Much faster documentation builds. +- Allow using a pre-generated `arith.h` header for f2c when cross-compiling; see the Installation section of the documentation. +- The `IGRAPH_ENABLE_LTO` build option now supports the `AUTO` value, which uses LTO only if the compiler supports it. Warning: CMake may not always be able to detect that LTO is not fully supported. Therefore, the default setting is `OFF`. +- The following functions are now interruptible: `igraph_grg_game()`, `igraph_sbm_game()`, `igraph_barabasi_game()`, `igraph_barabasi_aging_game()`. +- Functions that use GLPK, such as `igraph_feedback_arc_set()` and `igraph_community_optimal_modularity()` are now interruptible. +- Add support for older versions of Clang that do not recognize the `-Wno-varargs` flag. + +### Acknowledgments + +- Big thanks to Daniel Noom for continuing to expand the test suite and discovering and fixing several bugs in the process! + +## [0.9.0] - 2021-02-16 + +### Added + +- Eulerian paths/cycles (PR #1346): + - `igraph_is_eulerian()` finds out whether an Eulerian path/cycle exists. + - `igraph_eulerian_path()` returns an Eulerian path. + - `igraph_eulerian_cycle()` returns an Eulerian cycle. +- Efficiency (PR #1344): + - `igraph_global_efficiency()` computes the global efficiency of a network. + - `igraph_local_efficiency()` computes the local efficiency around each vertex. + - `igraph_average_local_efficiency()` computes the mean local efficiency. +- Degree sequences (PR #1445): + - `igraph_is_graphical()` checks if a degree sequence has a realization as a simple or multigraph, with or without self-loops. + - `igraph_is_bigraphical()` checks if two degree sequences have a realization as a bipartite graph. + - `igraph_realize_degree_sequence()` now supports constructing non-simple graphs as well. +- There is a new fatal error handling mechanism (PR #1548): + - `igraph_set_fatal_handler()` sets the fatal error handler. It is the only function in this functionality group that is relevant to end users. + - The macro `IGRAPH_FATAL()` and the functions `igraph_fatal()` and `igraph_fatalf()` raise a fatal error. These are for internal use. + - `IGRAPH_ASSERT()` is a replacement for the `assert()` macro. It is for internal use. + - `igraph_fatal_handler_abort()` is the default fatal error handler. +- The new `IGRAPH_WARNINGF`, `IGRAPH_ERRORF` and `IGRAPH_FATALF` macros provide warning/error reporting with `printf`-like syntax. (PR #1627, thanks to Daniel Noom!) +- `igraph_average_path_length_dijkstra()` computes the mean shortest path length in weighted graphs (PR #1344). +- `igraph_get_shortest_paths_bellman_ford()` computes the shortest paths (including the vertex and edge IDs along the paths) using the Bellman-Ford algorithm (PR #1642, thanks to Guy Rozenberg). This makes it possible to calculate the shortest paths on graphs with negative edge weights, which was not possible before with Dijkstra's algorithm. +- `igraph_get_shortest_path_bellman_ford()` is a wrapper for `igraph_get_shortest_paths_bellman_ford()` for the single path case. +- `igraph_is_same_graph()` cheks that two labelled graphs are the same (PR #1604). +- Harmonic centrality (PR #1583): + - `igraph_harmonic_centrality()` computes the harmonic centrality of vertices. + - `igraph_harmonic_centrality_cutoff()` computes the range-limited harmonic centrality. +- Range-limited centralities, currently equivalent to the old functions with names ending in `_estimate` (PR #1583): + - `igraph_closeness_cutoff()`. + - `igraph_betweenness_cutoff()`. + - `igraph_edge_betweenness_cutoff()`. +- `igraph_vector_is_any_nan()` checks if any elements of an `igraph_vector_t` is NaN. +- `igraph_inclist_size()` returns the number of vertices in an incidence list. +- `igraph_lazy_adjlist_size()` returns the number of vertices in a lazy adjacency list. +- `igraph_lazy_inclist_size()` returns the number of vertices in a lazy incidence list. +- `igraph_bfs_simple()` now provides a simpler interface to the breadth-first search functionality. + +### Changed + +- igraph now uses a CMake-based build sysyem. +- GMP support can no longer be disabled. When GMP is not present on the system, igraph will use an embedded copy of Mini-GMP (PR #1549). +- Bliss has been updated to version 0.75. Bliss functions are now interruptible. Thanks to Tommi Junttila for making this possible! +- Adjacency and incidence lists: + - `igraph_adjlist_init()` and `igraph_lazy_adjlist_init()` now require the caller to specify what to do with loop and multiple edges. + - `igraph_inclist_init()` and `igraph_lazy_inclist_init()` now require the caller to specify what to do with loop edges. + - Adjacency and incidence lists now use `igraph_vector_int_t` consistently. +- Community detection: + - `igraph_community_multilevel()`: added resolution parameter. + - `igraph_community_fluid_communities()`: graphs with no vertices or with one vertex only are now supported; they return a trivial partition. +- Modularity: + - `igraph_modularity()` and `igraph_modularity_matrix()`: added resolution parameter. + - `igraph_modularity()` and `igraph_modularity_matrix()` now support the directed version of modularity. + - `igraph_modularity()` returns NaN for graphs with no edges to indicate that the modularity is not well-defined for such graphs. +- Centralities: + - `cutoff=0` is no longer interpreted as infinity (i.e. no cutoff) in `betweenness`, `edge_betweenness` and `closeness`. If no cutoff is desired, use a negative value such as `cutoff=-1`. + - The `nobigint` argument has been removed from `igraph_betweenness()`, `igraph_betweenness_estimate()` and `igraph_centralization_betweenness()`, as it is not longer needed. The current implementation is more accurate than the old one using big integers. + - `igraph_closeness()` now considers only reachable vertices during the calculation (i.e. the closeness is calculated per-component in the undirected case) (PR #1630). + - `igraph_closeness()` gained two additional output parameters, `reachable_count` and `all_reachable`, returning the number of reached vertices from each vertex, as well as whether all vertices were reachable. This allows for computing various generalizations of closeness for disconnected graphs (PR #1630). + - `igraph_pagerank()`, `igraph_personalized_pagerank()` and `igraph_personalized_pagerank_vs()` no longer support the `IGRAPH_PAGERANK_ALGO_POWER` method. Their `options` argument now has type `igraph_arpack_options_t *` instead of `void *`. +- Shortest paths (PR #1344): + - `igraph_average_path_length()` now returns the number of disconnected vertex pairs in the new `unconn_pairs` output argument. + - `igraph_diameter()` now return the result as an `igraph_real_t` instead of an `igraph_integer_t`. + - `igraph_average_path_length()` and `igraph_diameter()` now return `IGRAPH_INFINITY` when `unconn=FALSE` and the graph is not connected. Previously they returned the number of vertices. +- Trait-based random graph generators: + - `igraph_callaway_traits_game()` and `igraph_establishment_game()` now have an optional output argument to retrieve the generated vertex types. + - `igraph_callaway_traits_game()` and `igraph_establishment_game()` now allow omitting the type distribution vector, in which case they assume a uniform distribution. + - `igraph_asymmetric_preference_game()` now accept a different number of in-types and out-types. +- `igraph_subisomorphic_lad()` now supports graphs with self-loops. +- `igraph_is_chordal()` and `igraph_maximum_cardinality_search()` now support non-simple graphs and directed graphs. +- `igraph_realize_degree_sequence()` has an additional argument controlling whether multi-edges or self-loops are allowed. +- `igraph_is_connected()` now returns false for the null graph; see for the reasoning behind this decision. +- `igraph_lapack_ddot()` is renamed to `igraph_blas_ddot()`. +- `igraph_to_directed()`: added RANDOM and ACYCLIC modes (PR #1511). +- `igraph_topological_sorting()` now issues an error if the input graph is not acyclic. Previously it issued a warning. +- `igraph_vector_(which_)(min|max|minmax)()` now handles NaN elements. +- `igraph_i_set_attribute_table()` is renamed to `igraph_set_attribute_table()`. +- `igraph_i_sparsemat_view()` is renamed to `igraph_sparsemat_view()`. + +### Deprecated + +- `igraph_is_degree_sequence()` and `igraph_is_graphical_degree_sequence()` are deprecated in favour of the newly added `igraph_is_graphical()`. +- `igraph_closeness_estimate()` is deprecated in favour of the newly added `igraph_closeness_cutoff()`. +- `igraph_betweenness_estimate()` and `igraph_edge_betweenness_estimate()` are deprecated in favour of the newly added `igraph_betweenness_cutoff()` and `igraph_edge_betweenness_cutoff()`. +- `igraph_adjlist_remove_duplicate()` and `igraph_inclist_remove_duplicate()` are now deprecated in favour of the new constructor arguments in `igraph_adjlist_init()` and `igraph_inclist_init()`. + +### Removed + +- The following functions, all deprecated in igraph 0.6, have been removed (PR #1562): + - `igraph_adjedgelist_init()`, `igraph_adjedgelist_destroy()`, `igraph_adjedgelist_get()`, `igraph_adjedgelist_print()`, `igraph_adjedgelist_remove_duplicate()`. + - `igraph_lazy_adjedgelist_init()`, `igraph_lazy_adjedgelist_destroy()`, `igraph_lazy_adjedgelist_get()`, `igraph_lazy_adjedgelist_get_real()`. + - `igraph_adjacent()`. + - `igraph_es_adj()`. + - `igraph_subgraph()`. +- `igraph_pagerank_old()`, deprecated in 0.7, has been removed. +- `igraph_vector_bool` and `igraph_matrix_bool` functions that relied on inequality-comparing `igraph_bool_t` values are removed. + +### Fixed + +- Betweenness calculations are no longer at risk from integer overflow. +- The actual cutoff distance used in closeness calculation was one smaller than the `cutoff` parameter. This is corrected (PR #1630). +- `igraph_layout_gem()` was not interruptible; now it is. +- `igraph_barabasi_aging_game()` now checks its parameters more carefully. +- `igraph_callaway_traits_game()` and `igraph_establishment_game()` now check their parameters. +- `igraph_lastcit_game()` checks its parameters more carefully, and no longer crashes with zero vertices (PR #1625). +- `igraph_cited_type_game()` incorrectly rounded the attractivity vector entries to integers. +- `igraph_residual_graph()` now returns the correct _residual_ capacities; previously it wrongly returned the original capacities (PR #1598). +- `igraph_psumtree_update()` now checks for negative values and NaN. +- `igraph_communities_spinglass()`: fixed several memory leaks in the `IGRAPH_SPINCOMM_IMP_NEG` implementation. +- `igraph_incident()` now returns edges in the same order as `igraph_neighbors()`. +- `igraph_modularity_matrix()` returned incorrect results for weighted graphs. This is now fixed. (PR #1649, thanks to Daniel Noom!) +- `igraph_lapack_dgetrf()` would crash when passing `NULL` for its `ipiv` argument (thanks for the fix to Daniel Noom). +- Some `igraph_matrix` functions would fail to report errors on out-of-memory conditions. +- `igraph_maxdegree()` now returns 0 for the null graph or empty vector set. Previously, it did not handle this case. +- `igraph_vector_bool_all_e()` now considers all nonzero (i.e. "true") values to be the same. +- PageRank (PR #1640): + - `igraph_(personalized_)pagerank(_vs)()` now check their parameters more carefully. + - `igraph_personalized_pagerank()` no longer modifies its `reset` parameter. + - `igraph_(personalized_)pagerank(_vs)`: the `IGRAPH_PAGERANK_ALGO_ARPACK` method now handles self-loops correctly. + - `igraph_personalized_pagerank(_vs)()`: the result retuned for edgeless or all-zero-weight graphs with the `IGRAPH_PAGERANK_ALGO_ARPACK` ignored the personalization vector. This is now corrected. + - `igraph_personalized_pagerank(_vs)()` with a non-uniform personalization vector, a disconnected graph and the `IGRAPH_PAGERANK_ALGO_PRPACK` method would return results that were inconsistent with `IGRAPH_PAGERANK_ALGO_ARPACK`. This happened because PRPACK always used a uniform reset distribution when the random walk got stuck in a sink vertex. Now it uses the user-specified reset distribution for this case as well. +- Fixed crashes in several functions when passing a weighted graph with zero edges (due to `vector_min` being called on the zero-length weight vector). +- Fixed problems in several functions when passing in a graph with zero vertices. +- Weighted betweenness, closeness, PageRank, shortest path calculations and random walk functions now check if any weights are NaN. +- Many functions now reject input arguments containing NaN values. +- Compatibility with the PGI compiler. + +### Other + +- Documentation improvements. +- Improved error and warning messages. +- More robust error handling. +- General code cleanup to reduce the number of compiler warnings. +- igraph's source files have been re-organized for better maintainability. +- Debugging aid: When igraph is build with AddressSanitizer, the default error handler prints a stack trace before exiting. +- igraph can now be built with an external CXSparse library. +- The references to igraph source files in error and warning messages are now always relative to igraph's base directory. +- When igraph is built as a shared library, only public symbols are exported even on Linux and macOS. + +### Acknowledgments + +- Thanks to Daniel Noom for significantly expanding igraph's test coverage and exposing several issues in the process! + +## [0.8.5] - 2020-12-07 + +### Changed + +- `igraph_write_graph_pajek()`: the function now always uses the platform-native line endings (CRLF on Windows, LF on Unix and macOS). Earlier versions tried to enforce Windows line endings, but this was error-prone, and since all recent versions of Pajek support both line endings, enforcing Windows line endings is not necessary any more. + +### Fixed + +- Fixed several compilation issues with MINGW32/64 (PR #1554) +- `igraph_layout_davidson_harel()` was not interruptible; now it is. +- Added a missing memory cleanup call in `igraph_i_cattribute_combine_vertices()`. +- Fixed a few memory leaks in test cases. + +## [0.8.4] - 2020-11-24 + +### Fixed + +- `igraph_i_cattribute_combine_vertices()`: fixed invalid cleanup code that eventually filled up the "finally" stack when combining vertices with attributes extensively. +- `igraph_hrg_sample()`: fixed incorrect function prototype +- `igraph_is_posinf()` and `igraph_is_neginf()`: fixed incorrect result on platforms where the sign of the result of `isinf()` is not indicative of the sign of the input. +- Fixed building with vendored LAPACK and external BLAS +- Fixed building with XCode 12.2 on macOS + +### Other + +- Documentation improvements +- General code cleanup to reduce the number of compiler warnings + +## [0.8.3] - 2020-10-02 + +### Added + +- `igraph_vector_binsearch_slice()` performs binary search on a sorted slice of a vector. + +### Changed + +- `igraph_eigenvector_centrality()` assumes the adjacency matrix of undirected graphs to have twice the number of self-loops for each vertex on the diagonal. This makes the results consistent between an undirected graph and its directed equivalent when each edge is replaced by a mutual edge pair. + +### Fixed + +- `igraph_isomorphic()` now verifies that the input graphs have no multi-edges (PR #1464). +- `igraph_difference()` was creating superfluous self loops (#597). +- `igraph_count_multiple()` was giving incorrect results for self-loops in directed graph (PR #1399). +- `igraph_betweenness_estimate()`: fixed incorrect results with finite cutoff (PR #1392). +- `igraph_count_multiple()` was giving incorrect results for self-loops in directed graph (PR #1399). +- `igraph_eigen_matrix_symmetric()`: fixed incorrect matrix multiplication (PR #1379). +- Corrected several issues that could arise during an error condition (PRs #1405, #1406, #1438). +- `igraph_realize_degree_sequence()` did not correctly detect some non-graphical inputs. +- `igraph_is_graphical_degree_sequence()`: fixed incorrect results in undirected case (PR #1441). +- `igraph_community_leiden()`: fixed incorrect result when self-loops are present (PR #1476). +- `igraph_eigenvector_centrality()`: fixed incorrect value for isolated vertices in weighted graphs. +- `igraph_eigenvector_centrality()`: corrected the handling of self-loops. +- `igraph_layout_reingold_tilford()`: fixed an issue where branches of the tree would sometimes overlap. + +### Other + +- `igraph_degree_sequence_game()`: improved performance with `IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE_UNIFORM` method. +- Improved the robustness of the test suite. +- Documentation improvements. +- Improved error and warning messages. +- Improved compatibility with recent versions of Microsoft Visual C. + +## [0.8.2] - 2020-04-28 + +### Changed + +- Improved argument checking: `igraph_all_st_mincuts()` and `igraph_sir()` +- Improved interruptibility: `igraph_sir()` + +### Fixed + +- `igraph_community_leiden()`: fixed crash when interrupting +- The tests are now more robust. Some incorrect test failures were fixed when + running on i386 architecture, or when using different versions of external + dependencies. + +### Other + +- Improved error messages from `igraph_sir()`. +- Improved compatibility with more recent versions of Microsoft Visual C. + +## [0.8.1] - 2020-03-13 + +### Changed + +- Improved interruptability: `igraph_degree_sequence_game()` +- Improved argument checking: `igraph_forest_fire_game()` +- Updated the plfit library to version 0.8.1 + +### Fixed + +- `igraph_community_edge_betweenness()`: fix for graphs with no edges (PR #1312) +- `igraph_bridges()` now handles multigraphs correctly (PR #1335) +- `igraph_avg_nearest_neighbor_degree()`: fix for memory leak in weighted case (PR #1339) +- `igraph_community_leiden()`: fix crash bug (PR #1357) + +### Other + +- Included `ACKOWLEDGEMENTS.md` +- Documentation improvements + +## [0.8.0] - 2020-01-29 + +### Added + +- Trees + - `igraph_to_prufer()` and `igraph_from_prufer()` convert labelled trees to/from Prüfer sequences + - `igraph_tree_game()` samples uniformly from the set of labelled trees + - `igraph_is_tree()` checks if a graph is a tree + - `igraph_random_spanning_tree()` picks a spanning tree of a graph uniformly at random + - `igraph_random_edge_walk()` returns the indices of edges traversed by a random walk; useful for multigraphs + +- Community detection + - `igraph_community_fluid_communities()` detects communities based on interacting fluids + - `igraph_community_leiden()` detects communities with the Leiden method + +- Cliques + - `igraph_maximal_cliques_hist()` counts maximal cliques of each size + - `igraph_maximal_cliques_callback()` calls a function for each maximal clique + - `igraph_clique_size_hist()` counts cliques of each size + - `igraph_cliques_callback()` calls a function for each clique + - `igraph_weighted_cliques()` finds weighted cliques in graphs with integer vertex weights + - `igraph_weighted_clique_number()` computes the weighted clique number + - `igraph_largest_weighted_cliques()` finds the largest weighted cliques + +- Graph generators + - `igraph_hsbm_game()` for a hierarchical stochastic block model + - `igraph_hsbm_list_game()` for a more general hierarchical stochastic block model + - `igraph_correlated_game()` generates pairs of correlated random graphs by perturbing existing adjacency matrix + - `igraph_correlated_pair_game()` generates pairs of correlated random graphs + - `igraph_tree_game()` samples uniformly from the set of labelled trees + - `igraph_dot_product_game()` generates a random dot product graph + - `igraph_realize_degree_sequence()` creates a single graph with a given degree sequence (Havel-Hakimi algorithm) + +- Graph embeddings + - `igraph_adjacency_spectral_embedding()` and `igraph_laplacian_spectral_embedding()` provide graph embedddings + - `igraph_dim_select()` provides dimensionality selection for singular values using profile likelihood + +- Isomorphism + - `igraph_automorphism_group()` computes the generators of the automorphism group of a simple graph + - `igraph_simplify_and_colorize()` encodes edge and self-loop multiplicities into edge and vertex colors; use in conjunction with VF2 to test isomorphism of non-simple graphs + +- Other + - `igraph_bridges()` finds edges whose removal would disconnect a graph + - `igraph_vertex_coloring_greedy()` computes a vertex coloring using a greedy algorithm + - `igraph_rewire_directed_edges()` randomly rewires only the starting points or only the endpoints of directed edges + - Various `igraph_local_scan_*` functions provide local counts and statistics of neighborhoods + - `igraph_sample_sphere_surface()` samples points uniformly from the surface of a sphere + - `igraph_sample_sphere_volume()` samples points uniformly from the volume of a sphere + - `igraph_sample_dirichlet()` samples points from a Dirichlet distribution + - `igraph_malloc()`, to be paired with the existing `igraph_free()` + +### Changed + +- `igraph_degree_sequence_game()`: new method added for uniform sampling: `IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE_UNIFORM` +- `igraph_modularity_matrix()`: removed `membership` argument (PR #1194) +- `igraph_avg_nearest_neighbor_degree()`: added `mode` and `neighbor_degree_mode` arguments (PR #1214). +- `igraph_get_all_simple_paths()`: added `cutoff` argument (PR #1232). +- `igraph_unfold_tree()`: no longer preserves edge ordering of original graph +- `igraph_decompose()`: support strongly connected components +- `igraph_isomorphic_bliss()`, `igraph_canonical_permutation()`, `igraph_automorphisms()`: added additional arguments to support vertex colored graphs (PR #873) +- `igraph_extended_chordal_ring`: added argument to support direction (PR #1096), and fixed issue #1093. + +### Other + +- The [Bliss isomorphism library](http://www.tcs.hut.fi/Software/bliss/) was updated to version 0.73. This version adds support for vertex colored and directed graphs. +- igraph now uses the high-performance [Cliquer library](https://users.aalto.fi/~pat/cliquer.html) to find (non-maximal) cliques +- Provide proper support for Windows, using `__declspec(dllexport)` and `__declspec(dllimport)` for `DLL`s and static usage by using `#define IGRAPH_STATIC 1`. +- Provided integer versions of `dqueue` and `stack` data types. + +[1.0.1]: https://github.com/igraph/igraph/compare/1.0.0..1.0.1 +[1.0.0]: https://github.com/igraph/igraph/compare/0.10.17..1.0.0 +[0.10.17]: https://github.com/igraph/igraph/compare/0.10.16..0.10.17 +[0.10.16]: https://github.com/igraph/igraph/compare/0.10.15..0.10.16 +[0.10.15]: https://github.com/igraph/igraph/compare/0.10.13..0.10.15 +[0.10.13]: https://github.com/igraph/igraph/compare/0.10.12..0.10.13 +[0.10.12]: https://github.com/igraph/igraph/compare/0.10.11..0.10.12 +[0.10.11]: https://github.com/igraph/igraph/compare/0.10.10..0.10.11 +[0.10.10]: https://github.com/igraph/igraph/compare/0.10.9..0.10.10 +[0.10.9]: https://github.com/igraph/igraph/compare/0.10.8..0.10.9 +[0.10.8]: https://github.com/igraph/igraph/compare/0.10.7..0.10.8 +[0.10.7]: https://github.com/igraph/igraph/compare/0.10.6..0.10.7 +[0.10.6]: https://github.com/igraph/igraph/compare/0.10.5..0.10.6 +[0.10.5]: https://github.com/igraph/igraph/compare/0.10.4..0.10.5 +[0.10.4]: https://github.com/igraph/igraph/compare/0.10.3..0.10.4 +[0.10.3]: https://github.com/igraph/igraph/compare/0.10.2..0.10.3 +[0.10.2]: https://github.com/igraph/igraph/compare/0.10.1..0.10.2 +[0.10.1]: https://github.com/igraph/igraph/compare/0.10.0..0.10.1 +[0.10.0]: https://github.com/igraph/igraph/compare/0.9.10..0.10.0 +[0.9.10]: https://github.com/igraph/igraph/compare/0.9.9...0.9.10 +[0.9.9]: https://github.com/igraph/igraph/compare/0.9.8...0.9.9 +[0.9.8]: https://github.com/igraph/igraph/compare/0.9.7...0.9.8 +[0.9.7]: https://github.com/igraph/igraph/compare/0.9.6...0.9.7 +[0.9.6]: https://github.com/igraph/igraph/compare/0.9.5...0.9.6 +[0.9.5]: https://github.com/igraph/igraph/compare/0.9.4...0.9.5 +[0.9.4]: https://github.com/igraph/igraph/compare/0.9.3...0.9.4 +[0.9.3]: https://github.com/igraph/igraph/compare/0.9.2...0.9.3 +[0.9.2]: https://github.com/igraph/igraph/compare/0.9.1...0.9.2 +[0.9.1]: https://github.com/igraph/igraph/compare/0.9.0...0.9.1 +[0.9.0]: https://github.com/igraph/igraph/compare/0.8.5...0.9.0 +[0.8.5]: https://github.com/igraph/igraph/compare/0.8.4...0.8.5 +[0.8.4]: https://github.com/igraph/igraph/compare/0.8.3...0.8.4 +[0.8.3]: https://github.com/igraph/igraph/compare/0.8.2...0.8.3 +[0.8.2]: https://github.com/igraph/igraph/compare/0.8.1...0.8.2 +[0.8.1]: https://github.com/igraph/igraph/compare/0.8.0...0.8.1 +[0.8.0]: https://github.com/igraph/igraph/releases/tag/0.8.0 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..13a2d47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,195 @@ +# Minimum CMake that we require is 3.18. +# Some of the recent features we use: +# * --ignore-eol when comparing unit test results with expected outcomes (3.14) +# * CROSSCOMPILING_EMULATOR can be a semicolon-separated list to pass arguments (3.15) +# * SKIP_REGULAR_EXPRESSION to handle skipped tests properly (3.16) +# * CheckLinkerFlag for HAVE_NEW_DTAGS test (3.18) +# * cmake -E cat (3.18) +cmake_minimum_required(VERSION 3.18...3.31) + +# CMake 3.31.0 issues warnings due to the following bug: +# https://gitlab.kitware.com/cmake/cmake/-/issues/26449 +# Setting policy CMP0175 to OLD avoids the warnings. +if(CMAKE_VERSION VERSION_EQUAL "3.31.0") + cmake_policy(SET CMP0175 OLD) +endif() + +# Add etc/cmake to CMake's search path so we can put our private stuff there +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/etc/cmake) + +# Set a default build type if none was specified +# This must precede the project() line, which would set the CMAKE_BUILD_TYPE +# to 'Debug' with single-config generators on Windows. +# Note that we must do this only if PROJECT_NAME is not set at this point. If +# it is set, it means that igraph is being used as a subproject of another +# project. +if(NOT PROJECT_NAME) + include(BuildType) +endif() + +# Prevent in-source builds +include(PreventInSourceBuilds) + +# Make use of ccache if it is present on the host system -- unless explicitly +# asked to disable it +include(UseCCacheWhenInstalled) + +# Figure out the version number from Git +include(version) + +# Declare the project, its version number and language +project( + igraph + VERSION ${PACKAGE_VERSION_BASE} + DESCRIPTION "A library for creating and manipulating graphs" + HOMEPAGE_URL https://igraph.org + LANGUAGES C CXX +) + +# Include some compiler-related helpers and set global compiler options +include(compilers) + +# Detect is certain attributes are supported by the compiler +include(attribute_support) + +# Set default symbol visibility to hidden +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +# Set C and C++ standard version +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED True) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Expose the BUILD_SHARED_LIBS option in the ccmake UI +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +# Add switches to use sanitizers and debugging helpers if needed +include(debugging) +include(sanitizers) + +# Enable fuzzer instrumentation if needed +# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION is a conventional +# macro used to adapt code for fuzzability, for example by +# reducing largest allowed graph sizes when reading various +# file formats. +if(BUILD_FUZZING) + add_compile_options(-fsanitize=fuzzer-no-link) + add_compile_definitions(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +endif() + +# Add version information +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_version.h +) + +# Create configuration options for optional features +include(features) + +# Handle dependencies and dependency-related configuration options +include(dependencies) +find_dependencies() + +# Run compile-time checks, generate config.h and igraph_threading.h +include(CheckSymbolExists) +include(CheckIncludeFiles) +include(CMakePushCheckState) + +# First we check for some functions and symbols +cmake_push_check_state() +if(NEED_LINKING_AGAINST_LIBM) + list(APPEND CMAKE_REQUIRED_LIBRARIES m) +endif() +check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP) +check_symbol_exists(strncasecmp strings.h HAVE_STRNCASECMP) +check_symbol_exists(_stricmp string.h HAVE__STRICMP) +check_symbol_exists(_strnicmp string.h HAVE__STRNICMP) +check_symbol_exists(strdup string.h HAVE_STRDUP) +check_symbol_exists(strndup string.h HAVE_STRNDUP) +check_include_files(xlocale.h HAVE_XLOCALE) +if(HAVE_XLOCALE) + # On BSD, uselocale() is in xlocale.h instead of locale.h. + # Some systems provide xlocale.h, but uselocale() is still in locale.h, + # thus we try both. + check_symbol_exists(uselocale "xlocale.h;locale.h" HAVE_USELOCALE) +else() + check_symbol_exists(uselocale locale.h HAVE_USELOCALE) +endif() +check_symbol_exists(_configthreadlocale locale.h HAVE__CONFIGTHREADLOCALE) +cmake_pop_check_state() + +# Check for 128-bit integer multiplication support, floating-point endianness, +# support for built-in overflow detection and fast bit operation support. +include(ieee754_endianness) +include(uint128_support) +include(bit_operations_support) +include(safe_math_support) + +if(NOT HAVE_USELOCALE AND NOT HAVE__CONFIGTHREADLOCALE) + message(WARNING "igraph cannot set per-thread locale on this platform. igraph_enter_safelocale() and igraph_exit_safelocale() will not be safe to use in multithreaded programs.") +endif() + +# Check for code coverage support +option(IGRAPH_ENABLE_CODE_COVERAGE "Enable code coverage calculation" OFF) +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND IGRAPH_ENABLE_CODE_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags() + setup_target_for_coverage_lcov( + NAME coverage + EXECUTABLE "${CMAKE_COMMAND}" "--build" "${PROJECT_BINARY_DIR}" "--target" "check" + # The base directory is changed to allow finding the generated parser sources. + # The following works with 'Ninja'. For 'Unix Makefiles', this requires ${PROJECT_BINARY_DIR}/src. + BASE_DIRECTORY "${PROJECT_BINARY_DIR}" + # /Applications and /Library/Developer are for macOS -- they exclude files from the macOS SDK. + EXCLUDE "/Applications/Xcode*" "/Library/Developer/*" "examples/*" "interfaces/*" "tests/*" "vendor/infomap/*" "vendor/pcg/*" + # These errors, present with lcov 2.x, are likely due to incompatibility with llvm-cov on macOS. + LCOV_ARGS --ignore-errors inconsistent,format,unused + GENHTML_ARGS --ignore-errors inconsistent,corrupt,category,unmapped + ) +endif() + +# Generate configuration headers +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/src/config.h +) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_config.h +) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_threading.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_threading.h +) + +# Enable unit tests. Behave nicely and do this only if we are not being +# included as a sub-project in another CMake project +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include(CTest) +endif() + +# Traverse subdirectories. vendor/ should come first because code in +# src/CMakeLists.txt depends on targets in vendor/ +add_subdirectory(vendor) +add_subdirectory(src) +add_subdirectory(interfaces) +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + add_subdirectory(tests) +endif() +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_FUZZING) + add_subdirectory(fuzzing) +endif() +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + add_subdirectory(doc) +endif() + +# Configure packaging -- only if igraph is the top-level project and not a +# subproject +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include(packaging) +endif() + +# Show result of configuration +include(summary) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8933621 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,246 @@ +# Contributing to this project + +Thank you for being interested in contributing to `igraph`! We need the help of +volunteers to keep the package going, so every little bit is welcome. You can help out +the project in several different ways. + +This repository only hosts the C code of the `igraph` project. Even if you are not so +experienced with C, you can contribute in a number of ways: + +1. Respond to user questions on our [support forum](https://igraph.discourse.group/). +2. Correct or improve our [documentation](https://igraph.org/c/html/latest/). +3. Go over [open issues](https://github.com/igraph/igraph/issues): + - Are some older issues still relevant in the most recent version? If not, write a + comment to the issue stating that you feel that the issue is not relevant any more. + - Can you reproduce some of the bugs that are reported? If so, write a comment to + the issue stating that this is still a problem in version X. + - Some [issues point out problems with the documentation](https://github.com/igraph/igraph/labels/documentation); + perhaps you could help correct these? + - Some [issues require clarifying a mathematical problem, or some literature research](https://github.com/igraph/igraph/labels/theory), + before any programming can begin. Can you contribute through your theoretical expertise? + - Looking to contribute code? Take a look at some [good first issues](https://github.com/igraph/igraph/labels/good%20first%20issue). + +## Using the issue tracker + +- The issue tracker is the preferred channel for [bug reports](#bugs), + [feature requests](#features) and [submitting pull requests](#pull-requests). + +- Do you have a question? Please use our [igraph support forum](https://igraph.discourse.group) + for support requests. + +- Please keep the discussion on topic and respect the opinions of others, and + adhere to our [Code of Conduct](https://igraph.org/code-of-conduct.html). + + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful — thank you for reporting! + +Guidelines for bug reports: + +1. **Make sure that the bug is in the C code of igraph and not in one of the + higher level interfaces** — if you are using igraph from R, Python + or Mathematica, consider submitting your issue in + [igraph/rigraph](https://github.com/igraph/rigraph/issues/new), + [igraph/python-igraph](https://github.com/igraph/python-igraph/issues/new) + or [szhorvat/IGraphM](https://github.com/szhorvat/IGraphM/issues/new) + instead. If you are unsure whether your issue is in the C layer, submit + a bug report in the repository of the higher level interface — + we will transfer the issue here if it indeed affects the C layer. + +2. **Use the GitHub issue search** — check if the issue has already been + reported. + +3. **Check if the issue has been fixed** — try to reproduce it using the + latest `main` or development branch in the repository. + +4. **Isolate the problem** — create a [short, self-contained, correct + example](http://sscce.org/). + +Please try to be as detailed as possible in your report and provide all +necessary information. What is your environment? What steps will reproduce the +issue? What would you expect to be the outcome? All these details will help us +to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the compiler/OS environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + + + +## Feature requests + +Feature requests are always welcome. First, take a moment to find out whether your +idea fits with the scope and aims of the project. Please provide as much detail +and context as possible, and where possible, references to relevant literature. +Having said that, implementing new features can be quite time consuming, and as +such they might not be implemented quickly. In addition, the development team +might decide not to implement a certain feature. It is up to you to make a case +to convince the project's developers of the merits of this feature. + + +## Pull requests + +_**Note:** The wiki has a lot of useful information for newcomers, as well as a +[quick start guide](https://github.com/igraph/igraph/wiki/Quickstart-for-new-contributors)!_ + +Good pull requests—patches, improvements, new features—are a fantastic help. +They should remain focused in scope and avoid containing unrelated commits. +Please also take a look at our [tips on writing igraph code](#tips) before +getting your hands dirty. + +**Please ask first before embarking on any significant pull request** (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). + +Follow the following steps if you would like to make a new pull request: + +1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, + and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com// + # Navigate to the newly cloned directory + cd + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com// + ``` + +2. Please checkout the section on [branching](#branching) to see whether you + need to branch off from the `main` branch or the `develop` branch. + + If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout + git pull --rebase upstream + ``` + +3. Create a new topic branch (off the targeted branch, see + [branching](#branching) section) to contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Please commit your changes in logical chunks, and try to provide clear commit + messages. It helps us during the review process if we can follow your thought + process during the implementation. If you hit a dead end, use `git revert` + to revert your commits or just go back to an earlier commit with `git checkout` + and continue your work from there. + +5. We have a [checklist for new igraph functions](https://github.com/igraph/igraph/wiki/Checklist-for-new-(and-old)-functions). + If you have added any new functions to igraph, please go through the + checklist to ensure that your functions play nicely with the rest of the + library. + +6. Make sure that your PR is based off the latest code and locally merge (or + rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream + ``` + + Rebasing is preferable over merging as you do not need to deal with merge + conflicts; however, if you already have many commits, merging the upstream + development branch may be faster. + +7. When your topic branch is up-to-date with the upstream development branch, you can + push your topic branch up to your fork: + + ```bash + git push origin + ``` + +8. [Open a pull request](https://help.github.com/articles/using-pull-requests/) + with a clear title and description. + +**IMPORTANT**: By submitting a pull request, you agree to allow the project +owner to license your work under the same license as that used by the project, +see also [Legal Stuff](#legal). + + + +### Branching + +In short, always ask whether your contribution should target the `main` or +`develop` branch _before_ starting any work. Read on for more details on how +this is decided. + +`igraph` is committed to [semantic versioning](https://semver.org/). We are +currently still in the development release (0.x), which in principle is a mark +that the public API is not yet stable. Regardless, we try to maintain semantic +versioning also for the development releases. We do so as follows. Any released +minor version (0.x.z) will be API backwards-compatible with any previous release +of the _same_ minor version (0.x.y, with y < z). This means that _if_ there is +an API incompatible change, we will increase the minor version. For example, +release 0.8.1 is API backwards-compatible with release 0.8.0, while release +0.9.0 might be API incompatible with version 0.8.1. Note that this only concerns +the _public_ API, internal functions may change also within a minor version. + +There will always be two versions of `igraph`: the most recent released version, +and the next upcoming minor release, which is by definition not yet released. +The most recent release version is in the `main` branch, while the next +upcoming minor release is in the `develop` branch. If you make a change that is +API incompatible with the most recent release, it **must** be merged to +the `develop` branch. If the change is API backwards-compatible, it **can** be +merged to the `main` branch. It is possible that you build on recent +improvements in the `develop` branch, in which case your change should of course +target the `develop` branch. If you only add new functionality, but do not +change anything of the existing API, this should be backwards-compatible, and +can be merged in the `main` branch. + +When you make a new pull request, please specify the correct target branch. The +maintainers of `igraph` may decide to retarget your pull request to the correct +branch. Retargeting you pull request may result in merge conflicts, so it is +always good to decide **before** starting to work on something whether you +should start from the `main` branch or from the `develop` branch. In most +cases, changes in the `main` branch will also be merged to the `develop` +branch by the maintainers. + + +## Writing igraph Code + +[Some tips on writing igraph code](https://github.com/igraph/igraph/wiki/Tips-on-writing-igraph-code). + +## Ask Us! + +In general, if you are not sure about something, please ask! You can +open an issue on GitHub, open a thread in our +[igraph support forum](https://igraph.discourse.group), or write to +[@ntamas](https://github.com/ntamas), [@vtraag](https://github.com/vtraag), +[@szhorvat](https://github.com/szhorvat), [@iosonofabio](https://github.com/iosonofabio) or +[@gaborcsardi](https://github.com/gaborcsardi). +We prefer open communication channels, because others can then learn from it +too. + + +## Legal Stuff + +This is a pain to deal with, but we can't avoid it, unfortunately. + +`igraph` is licensed under the "General Public License (GPL) version 2, or +later". The igraph manual is licensed under the "GNU Free Documentation +License". By submitting a patch or pull request, you agree to allow the project +owner to license your work under the same license as that used by the project. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..faec64d --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,121 @@ +# Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Gábor Csárdi
Gábor Csárdi

💻
Tamás Nepusz
Tamás Nepusz

💻
Szabolcs Horvát
Szabolcs Horvát

💻
Vincent Traag
Vincent Traag

💻
GroteGnoom
GroteGnoom

💻
Fabio Zanini
Fabio Zanini

💻
Jan Katins
Jan Katins

💻
Sancar Adali
Sancar Adali

💻
Ferran Parés
Ferran Parés

💻
mvngu
mvngu

💻
Dr. Nick
Dr. Nick

💻
jannick0
jannick0

💻
Jérôme Benoit
Jérôme Benoit

💻
Frederik Harwath
Frederik Harwath

💻
AdamKorcz
AdamKorcz

💻
Antonio Rojas
Antonio Rojas

💻
Árpád Horváth
Árpád Horváth

💻
Peter Scott
Peter Scott

💻
Navid Dianati
Navid Dianati

💻
YasirKusay
YasirKusay

💻
Andreas Beham
Andreas Beham

💻
Bart Kastermans
Bart Kastermans

💻
Erik Welch
Erik Welch

💻
Hong Xu
Hong Xu

💻
Hosseinazari
Hosseinazari

💻
Jean Monlong
Jean Monlong

💻
Keivin98
Keivin98

💻
Leonardo
Leonardo

💻
Min Kim
Min Kim

💻
Nikolay Khitrin
Nikolay Khitrin

💻
Peter Schmiedeskamp
Peter Schmiedeskamp

💻
Philipp A.
Philipp A.

💻
Ramy Saied
Ramy Saied

💻
Robert Schütz
Robert Schütz

💻
Ryan Duffin
Ryan Duffin

💻
Shlomi Fish
Shlomi Fish

💻
Tomasz Kłoczko
Tomasz Kłoczko

💻
Watal M. Iwasaki
Watal M. Iwasaki

💻
Aman Verma
Aman Verma

💻
guy rozenberg
guy rozenberg

💻
Artem V L
Artem V L

💻
Kateřina Č.
Kateřina Č.

💻
valdaarhun
valdaarhun

💻
YuliYudith
YuliYudith

💻
alexsyou
alexsyou

💻
Rohit Tawde
Rohit Tawde

💻
alexperrone
alexperrone

💻
Georgica Bors
Georgica Bors

💻
MEET PATEL
MEET PATEL

💻
kwofach
kwofach

💻
Kevin Zhu
Kevin Zhu

💻
Pradeep Krishnamurthy
Pradeep Krishnamurthy

💻
flange-ipb
flange-ipb

💻
Juan Julián Merelo Guervós
Juan Julián Merelo Guervós

💻
Radoslav Fulek
Radoslav Fulek

💻
professorcode1
professorcode1

💻
larah19
larah19

💻
Biswapriyo Nath
Biswapriyo Nath

💻
Gwyn Ciesla
Gwyn Ciesla

💻
aagon
aagon

💻
Quinn Buratynski
Quinn Buratynski

💻
Arnar Bjarni Arnarson
Arnar Bjarni Arnarson

💻
David Seifert
David Seifert

💻
Kirill Müller
Kirill Müller

💻
Michael
Michael

💻
Tim Bernhard
Tim Bernhard

💻
Maëlle Salmon
Maëlle Salmon

💻
Gulshan Kumar
Gulshan Kumar

💻
Carlos Planelles
Carlos Planelles

💻
Dev_Lohani
Dev_Lohani

💻
Lucas Lopes Felipe
Lucas Lopes Felipe

💻
Sarah Rashidi
Sarah Rashidi

💻
Zara Zong
Zara Zong

💻
Arnór Friðriksson
Arnór Friðriksson

💻
Varun Rajesh
Varun Rajesh

💻
Steve Sajeev
Steve Sajeev

💻
Rohith Sudheer
Rohith Sudheer

💻
Grisha
Grisha

💻
Michael Antonov
Michael Antonov

💻
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 0000000..016f6a9 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,87 @@ +Thanks goes to these wonderful people: + +Gábor Csárdi (@gaborcsardi) +Tamás Nepusz (@ntamas) +Szabolcs Horvát (@szhorvat) +Vincent Traag (@vtraag) +GroteGnoom (@GroteGnoom) +Fabio Zanini (@iosonofabio) +Jan Katins (@jankatins) +Sancar Adali (@adalisan) +Ferran Parés (@FerranPares) +mvngu (@mvngu) +Dr. Nick (@das-intensity) +jannick0 (@jannick0) +Jérôme Benoit (@jgmbenoit) +Frederik Harwath (@frederik-h) +AdamKorcz (@AdamKorcz) +Antonio Rojas (@antonio-rojas) +Árpád Horváth (@horvatha) +Peter Scott (@PeterScott) +Navid Dianati (@naviddianati) +YasirKusay (@YasirKusay) +Andreas Beham (@abeham) +Bart Kastermans (@kasterma) +Erik Welch (@eriknw) +Hong Xu (@xuhdev) +Hosseinazari (@Hosseinazari) +Jean Monlong (@jmonlong) +Keivin98 (@Keivin98) +Leonardo (@leo-aa88) +Min Kim (@msk) +Nikolay Khitrin (@khitrin) +Peter Schmiedeskamp (@pschmied) +Philipp A. (@flying-sheep) +Ramy Saied (@RamySaied1) +Robert Schütz (@dotlambda) +Ryan Duffin (@ryanduffin) +Shlomi Fish (@shlomif) +Tomasz Kłoczko (@kloczek) +Watal M. Iwasaki (@heavywatal) +Aman Verma (@nograpes) +guy rozenberg (@guyroznb) +Artem V L (@luav) +Kateřina Č. (@Katterrina) +valdaarhun (@valdaarhun) +YuliYudith (@YuliYudith) +alexsyou (@alexsyou) +Rohit Tawde (@rohitt28) +alexperrone (@alexperrone) +Georgica Bors (@borsgeorgica) +MEET PATEL (@meetpatel0963) +kwofach (@kwofach) +Kevin Zhu (@Gomango999) +Pradeep Krishnamurthy (@pradkrish) +flange-ipb (@flange-ipb) +Juan Julián Merelo Guervós (@JJ) +Radoslav Fulek (@rfulekjames) +professorcode1 (@professorcode1) +larah19 (@larah19) +Biswapriyo Nath (@Biswa96) +Gwyn Ciesla (@limburgher) +aagon (@aagon) +Quinn Buratynski (@GanzuraTheConsumer) +Arnar Bjarni Arnarson (@Tagl) +David Seifert (@SoapGentoo) +Kirill Müller (@krlmlr) +Michael (@gendelpiekel) +Tim Bernhard (@GenieTim) +Maëlle Salmon (@maelle) +Gulshan Kumar (@gulshan-123) +Carlos Planelles (@carlos-planelles) +Dev_Lohani (@devlohani99) +Lucas Lopes Felipe (@lucaslopes) +Sarah Rashidi (@its-serah) +Zara Zong (@minifinity) +Arnór Friðriksson (@Zepeacedust) +Varun Rajesh (@VRajesh7649) +Steve Sajeev (@stevesajeev1) +Rohith Sudheer (@RohithS98) +Grisha (@GrishaVar) +Michael Antonov (@Antonov548) + +This project follows the [all-contributors][1] specification. Contributions of any kind welcome! + +This file is an automatically generated, plain-text version of CONTRIBUTORS.md. + +[1]: https://github.com/all-contributors/all-contributors diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..6d45519 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..04bd5c9 --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +See CHANGELOG.md for a list of changes between versions. diff --git a/IGRAPH_VERSION b/IGRAPH_VERSION new file mode 100644 index 0000000..7f20734 --- /dev/null +++ b/IGRAPH_VERSION @@ -0,0 +1 @@ +1.0.1 \ No newline at end of file diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..307dd76 --- /dev/null +++ b/INSTALL @@ -0,0 +1,7 @@ +Instructions for installation are provided in Chapter 2 of the manual; see +`doc/html` in the distributed tarball. + +An online version of the installation instructions for the most recent version +can be found here: + +https://igraph.org/c/doc/igraph-Installation.html diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..ec318ba --- /dev/null +++ b/NEWS @@ -0,0 +1,5 @@ +News about each release of igraph from version 0.8 onwards can be found in +CHANGELOG.md. + +Archived news items before version 0.7 are to be found in ONEWS -- these are +most likely of historical interest only. diff --git a/ONEWS b/ONEWS new file mode 100644 index 0000000..4a262ca --- /dev/null +++ b/ONEWS @@ -0,0 +1,1433 @@ + +igraph 0.6.5 +============ + +Released February 24, 2013 + +The version number is not a mistake, we jump to 0.6.5 from 0.6, +for technical reasons. + +R: new features and bug fixes +----------------------------- + +- Added a vertex shape API for defining new vertex shapes, and also + a couple of new vertex shapes. +- Added the get.data.frame() function, opposite of graph.data.frame(). +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- `degree.sequence.game()` has a new method now: "simple_no_multiple". +- Added the is.degree.sequence() and is.graphical.degree.sequence() + functions. +- rewire() has a new method: "loops", that can create loop edges. +- Walktrap community detection now handles isolates. +- layout.mds() returns a layout matrix now. +- layout.mds() uses LAPACK instead of ARPACK. +- Handle the '~' character in write.graph and read.graph. Bug + \#1066986. +- Added k.regular.game(). +- Use vertex names to plot if no labels are specified in the function + call or as vetex attributes. Fixes issue \#1085431. +- power.law.fit() can now use a C implementation. + +- Fixed a bug in barabasi.game() when out.seq was an empty vector. +- Fixed a bug that made functions with a progress bar fail if called + from another package. +- Fixed a bug when creating graphs from a weighted integer adjacency + matrix via graph.adjacency(). Bug \#1019624. +- Fixed overflow issues in centralization calculations. +- Fixed a minimal.st.separators() bug, some vertex sets were incorrectly + reported as separators. Bug \#1033045. +- Fixed a bug that mishandled vertex colors in VF2 isomorphism + functions. Bug \#1032819. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in shortest paths + calculation in shortest.paths() (Dijkstra's algorithm.) Thanks to + Martin J Reed. +- Weighted transitivity uses V(graph) as 'vids' if it is NULL. +- Fixed a bug when 'pie' vertices were drawn together with other + vertex shapes. +- Speed up printing graphs. +- Speed up attribute queries and other basic operations, by avoiding + copying of the graph. Bug \#1043616. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- layout.merge()'s DLA mode has better defaults now. +- Fixed a bug in layout.mds() that resulted vertices on top of each + other. +- Fixed a bug in layout.spring(), it was not working properly. +- Fixed layout.svd(), which was completely defunct. +- Fixed a bug in layout.graphopt() that caused warnings and on + some platforms crashes. +- Fixed community.to.membership(). Bug \#1022850. +- Fixed a graph.incidence() crash if it was called with a non-matrix + argument. +- Fixed a get.shortest.paths bug, when output was set to "both". +- Motif finding functions return NA for isomorphism classes that are + not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed get.adjacency() when attr is given, and the attribute has some + complex type. Bug \#1025799. +- Fixed attribute name in graph.adjacency() for dense matrices. Bug + \#1066952. +- Fixed erratic behavior of alpha.centrality(). +- Fixed igraph indexing, when attr is given. Bug \#1073705. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Fixed a bug in the maximal clique search, closes \#1074402. +- Warn for negative weights when calculating PageRank. +- Fixed dense, unweighted graph.adjacency when diag=FALSE. Closes + issue \#1077425. +- Fixed a bug in eccentricity() and radius(), the results were often + simply wrong. +- Fixed a bug in get.all.shortest.paths() when some edges had zero weight. +- graph.data.frame() is more careful when vertex names are numbers, to + avoid their scientific notation. Fixes issue \#1082221. +- Better check for NAs in vertex names. Fixes issue \#1087215 +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Eliminate gap between vertex and edge when plotting an edge without an arrow. + Fixes \#1118448. +- Fixed a bug in has.multiple() that resulted in false negatives for + some undirected graphs. +- Fixed a crash in weighted betweenness calculation. +- R plotting: fixed a bug that caused misplaced arrows at rectangle + vertex shapes. + +Python news and fixes +--------------------- + +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- Graph.Degree_Sequence() has a new method now: "no_multiple". +- Added the is_degree_sequence() and is_graphical_degree_sequence() + functions. +- rewire() has a new mode: "loops", that can create loop edges. +- Walktrap community detection now handles isolates. +- Added Graph.K_Regular(). +- power_law_fit() now uses a C implementation. +- Added support for setting the frame (stroke) width of vertices using the + frame_width attribute or the vertex_frame_width keyword argument in plot() +- Improved Inkscape-friendly SVG output from Graph.write_svg(), thanks to drlog +- Better handling of named vertices in Graph.delete_vertices() +- Added experimental Gephi graph streaming support; see igraph.remote.gephi and + igraph.drawing.graph.GephiGraphStreamingDrawer +- Nicer __repr__ output for Flow and Cut instances +- Arrows are now placed correctly around diamond-shaped nodes on plots +- Added Graph.TupleList, a function that allows one to create graphs with + edge attributes quickly from a list of tuples. +- plot() now also supports .eps as an extension, not only .ps + +- Fixed overflow issues in centralization calculations. +- Fixed a bug that mishandled vertex colors in VF2 isomorphism + functions. Bug \#1032819. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in shortest paths + calculation in Graph.shortest_paths() (Dijkstra's algorithm.) Thanks to + Martin J Reed. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- Fixed a bug in Graph.layout_mds() that resulted vertices on top of each + other. +- Motif finding functions return nan for isomorphism classes that are + not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Warn for negative weights when calculating PageRank. +- Fixed a bug in Graph.eccentricity() and Graph.radius(), the results were often + simply wrong. +- Fixed a bug in Graph.get.all.shortest.paths() when some edges had zero weight. +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Fixed a bug in Graph.layout_sugiyama() when the graph had no edges. +- Fixed a bug in Graph.community_label_propagation() when initial labels + contained -1 entries. Issue \#1105460. +- Repaired the DescartesCoordinateSystem class (which is not used too frequently + anyway) +- Fixed a bug that caused segfaults when an igraph Graph was used in a thread + forked from the main Python interpreter thread +- Fixed a bug that affected file handles created from Python strings in the + C layer +- Fixed a bug in has_multiple() that resulted in false negatives + for some undirected graphs. +- Fixed a crash in weighted betweenness calculation. + +C library news and changes +-------------------------- + +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- igraph_layout_mds() uses LAPACK instead of ARPACK. +- igraph_degree_sequence_game has a new method: + IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE. +- Added the igraph_is_degree_sequence() and + igraph_is_graphical_degree_sequence() functions. +- igraph_rewire() has a new method: IGRAPH_REWIRING_SIMPLE_LOOPS, + that can create loops. +- Walktrap community detection now handles isolates. +- Added igraph_k_regular_game(). +- Added igraph_power_law_fit. + +- Fixed a bug in igraph_barabasi_game when outseq was an empty vector. +- Fixed overflow issues in centralization calculations. +- Fixed an invalid return value of igraph_vector_ptr_pop_back. +- Fixed a igraph_all_minimal_st_separators() bug, some vertex sets + were incorrectly reported as separators. Bug \#1033045. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in + igraph_shortest_paths_dijkstra(), thanks to Martin J Reed. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- igraph_layout_merge_dla uses better default parameter values now. +- Fixed a bug in igraph_layout_mds() that resulted vertices on top of + each other. +- Attribute handler table is not thread-local any more. +- Motif finding functions return IGRAPH_NAN for isomorphism classes + that are not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Fix a bug in degree_sequence_game(), in_seq can be an empty vector as + well instead of NULL, for an undirected graph. +- Fixed a bug in the maximal clique search, closes \#1074402. +- Warn for negative weights when calculating PageRank. +- Fixed a bug in igraph_eccentricity() (and also igraph_radius()), + the results were often simply wrong. +- Fixed a bug in igraph_get_all_shortest_paths_dijkstra() when edges + had zero weight. +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Fixed a bug in igraph_has_multiple() that resulted in false negatives + for some undirected graphs. +- Fixed a crash in weighted betweenness calculation. + +igraph 0.6 +========== + +Released June 11, 2012 + +See also the release notes at +http://igraph.sf.net/relnotes-0.6.html + +R: Major new features +--------------------- + +- Vertices and edges are numbered from 1 instead of 0. + Note that this makes most of the old R igraph code incompatible + with igraph 0.6. If you want to use your old code, please use + the igraph0 package. See more at http://igraph.sf.net/relnotes-0.6.html. +- The '\[' and '\[\[' operators can now be used on igraph graphs, + for '\[' the graph behaves as an adjacency matrix, for '[[' is + is treated as an adjacency list. It is also much simpler to + manipulate the graph structure, i.e. add/remove edges and vertices, + with some new operators. See more at ?graph.structure. +- In all functions that take a vector or list of vertices or edges, + vertex/edge names can be given instead of the numeric ids. +- New package 'igraphdata', contains a number of data sets that can + be used directly in igraph. +- Igraph now supports loading graphs from the Nexus online data + repository, see nexus.get(), nexus.info(), nexus.list() and + nexus.search(). +- All the community structure finding algorithm return a 'communities' + object now, which has a bunch of useful operations, see + ?communities for details. +- Vertex and edge attributes are handled much better now. They + are kept whenever possible, and can be combined via a flexible API. + See ?attribute.combination. +- R now prints igraph graphs to the screen in a more structured and + informative way. The output of summary() was also updated + accordingly. + +R: Other new features +--------------------- + +- It is possible to mark vertex groups on plots, via + shading. Communities and cohesive blocks are plotted using this by + default. +- Some igraph demos are now available, see a list via + 'demo(package="igraph")'. +- igraph now tries to select the optimal layout algorithm, when + plotting a graph. +- Added a simple console, using Tcl/Tk. It contains a text area + for status messages and also a status bar. See igraph.console(). +- Reimplemented igraph options support, see igraph.options() and + getIgraphOpt(). +- Igraph functions can now print status messages. + +R: New or updated functions +--------------------------- + +Community detection +------------------- +- The multi-level modularity optimization community structure detection + algorithm by Blondel et al. was added, see multilevel.community(). +- Distance between two community structures: compare.communities(). +- Community structure via exact modularity optimization, + optimal.community(). +- Hierarchical random graphs and community finding, porting the code + from Aaron Clauset. See hrg.game(), hrg.fit(), etc. +- Added the InfoMAP community finding method, thanks to Emmanuel + Navarro for the code. See infomap.community(). + +Shortest paths +-------------- +- Eccentricity (eccentricity()), and radius (radius()) calculations. +- Shortest path calculations with get.shortest.paths() can now + return the edges along the shortest paths. +- get.all.shortest.paths() now supports edge weights. + +Centrality +---------- +- Centralization scores for degree, closeness, betweenness and + eigenvector centrality. See centralization.scores(). +- Personalized Page-Rank scores, see page.rank(). +- Subgraph centrality, subgraph.centrality(). +- Authority (authority.score()) and hub (hub.score()) scores support + edge weights now. +- Support edge weights in betweenness and closeness calculations. +- bonpow(), Bonacich's power centrality and alpha.centrality(), + Alpha centrality calculations now use sparse matrices by default. +- Eigenvector centrality calculation, evcent() now works for + directed graphs. +- Betweenness calculation can now use arbitrarily large integers, + this is required for some lattice-like graphs to avoid overflow. + +Input/output and file formats +----------------------------- +- Support the DL file format in graph.read(). See + http://www.analytictech.com/networks/dataentry.htm. +- Support writing the LEDA file format in write.graph(). + +Plotting and layouts +-------------------- +- Star layout: layout.star(). +- Layout based on multidimensional scaling, layout.mds(). +- New layouts layout.grid() and layout.grid.3d(). +- Sugiyama layout algorithm for layered directed acyclic graphs, + layout.sugiyama(). + +Graph generators +---------------- +- New graph generators: static.fitness.game(), static.power.law.game(). +- barabasi.game() was rewritten and it supports three algorithms now, + the default algorithm does not generate multiple or loop edges. + The graph generation process can now start from a supplied graph. +- The Watts-Strogatz graph generator, igraph_watts_strogatz() can + now create graphs without loop edges. + +Others +------ +- Added the Spectral Coarse Graining algorithm, see scg(). +- The cohesive.blocks() function was rewritten in C, it is much faster + now. It has a nicer API, too. See demo("cohesive"). +- Added generic breadth-first and depth-first search implementations + with many callbacks, graph.bfs() and graph_dfs(). +- Support vertex and edge coloring in the VF2 (sub)graph isomorphism + functions (graph.isomorphic.vf2(), graph.count.isomorphisms.vf2(), + graph.get.isomorphisms.vf2(), graph.subisomorphic.vf2(), + graph.count.subisomorphisms.vf2(), graph.get.subisomorphisms.vf2()). +- Assortativity coefficient, assortativity(), assortativity.nominal() + and assortativity.degree(). +- Vertex operators that work by vertex names: + graph.intersection.by.name(), graph.union.by.name(), + graph.difference.by.name(). Thanks to Magnus Torfason for + contributing his code! +- Function to calculate a non-induced subraph: subgraph.edges(). +- More comprehensive maximum flow and minimum cut calculation, + see functions graph.maxflow(), graph.mincut(), stCuts(), stMincuts(). +- Check whether a directed graph is a DAG, is.dag(). +- has.multiple() to decide whether a graph has multiple edges. +- Added a function to calculate a diversity score for the vertices, + graph.diversity(). +- Graph Laplacian calculation (graph.laplacian()) supports edge + weights now. +- Biconnected component calculation, biconnected.components() + now returns the components themselves. +- bipartite.projection() calculates multiplicity of edges. +- Maximum cardinality search: maximum.cardinality.search() and + chordality test: is.chordal() +- Convex hull computation, convex.hull(). +- Contract vertices, contract.vertices(). + +New in the Python interface +--------------------------- + +TODO + +Major changes in the Python interface +------------------------------------- + +TODO + +New in the C layer +------------------ + +- Maximum cardinality search: igraph_maximum_cardinality_search() and + chordality test: igraph_is_chordal(). +- Support the DL file format, igraph_read_graph_dl(). See + http://www.analytictech.com/networks/dataentry.htm. +- Added generic breadth-first and depth-first search implementations + with many callbacks (igraph_bfs(), igraph_dfs()). +- Centralization scores for degree, closeness, betweenness and + eigenvector centrality, see igraph_centralization(). +- Added igraph_sparsemat_t, a type that implements sparse + matrices based on the CXSparse library by Tim Davis. + See http://www.cise.ufl.edu/research/sparse/CXSparse/. +- Personalized Page-Rank scores, igraph_personalized_pagerank() and + igraph_personalized_pagerank_vs(). +- Assortativity coefficient, igraph_assortativity(), + igraph_assortativity_nominal(), and igraph_assortativity_degree(). +- The multi-level modularity optimization community structure detection + algorithm by Blondel et al. was added, see igraph_community_multilevel(). +- Added the igraph_version() function. +- Star layout: igraph_layout_star(). +- Function to calculate a non-induced subraph: igraph_subgraph_edges(). +- Distance between two community structures: igraph_compare_communities(). +- Community structure via exact modularity optimization, + igraph_community_optimal_community(). +- More comprehensive maximum flow and minimum cut calculation, + see functions igraph_maxflow(), igraph_mincut(), + igraph_all_st_cuts(), igraph_all_st_mincuts(). +- Layout based on multidimensional scaling, igraph_layout_mds(). +- It is now possible to access the random number generator(s) via an + API. Multiple RNGs can be used, from external sources as well. + The default RNG is MT19937. +- Added igraph_get_all_shortest_paths_dijkstra, for calculating all + non-negatively weighted shortest paths. +- Check whether a directed graph is a DAG, igraph_is_dag(). +- Cohesive blocking, a'la Moody & White, igraph_cohesive_blocks(). +- Igraph functions can now print status messages, see igraph_status() + and related functions. +- Support writing the LEDA file format, igraph_write_graph_leda(). +- Contract vertices, igraph_contract_vertices(). +- The C reference manual has now a lot of example programs. +- Hierarchical random graphs and community finding, porting the code + from Aaron Clauset. See igraph_hrg_game(), igraph_hrg_fit(), etc. +- igraph_has_multiple() to decide whether a graph has multiple edges. +- New layouts igraph_layout_grid() and igraph_layout_grid_3d(). +- igraph_integer_t is really an integer now, it used to be a double. +- igraph_minimum_spanning_tree(), calls either the weighted or + the unweighted implementation. +- Eccentricity (igraph_eccentricity()), and radius (igraph_radius()) + calculations. +- Several game theory update rules, written by Minh Van Nguyen. See + igraph_deterministic_optimal_imitation(), + igraph_stochastic_imitation(), igraph_roulette_wheel_imitation(), + igraph_moran_process(), +- Sugiyama layout algorithm for layered directed acyclic graphs, + igraph_layout_sugiyama(). +- New graph generators: igraph_static_fitness_game(), + igraph_static_power_law_game(). +- Added the InfoMAP community finding method, thanks to Emmanuel + Navarro for the code. See igraph_community_infomap(). +- Added the Spectral Coarse Graining algorithm, see igraph_scg(). +- Added a function to calculate a diversity score for the vertices, + igraph_diversity(). + +Major changes in the C layer +---------------------------- + +- Authority (igraph_authority_score()) and hub (igraph_hub_score()) scores + support edge weights now. +- Graph Laplacian calculation (igraph_laplacian()) supports edge + weights now. +- Support edge weights in betweenness (igraph_betweenness()) and closeness + (igraph_closeness()) calculations. +- Support vertex and edge coloring in the VF2 graph isomorphism + algorithm (igraph_isomorphic_vf2(), igraph_count_isomorphisms_vf2(), + igraph_get_isomorphisms_vf2(), igraph_subisomorphic_vf2(), + igraph_count_subisomorphisms_vf2(), igraph_get_subisomorphisms_vf2()). +- Added print operations for the igraph_vector*_t, igraph_matrix*_t and + igraph_strvector_t types. +- Biconnected component calculation (igraph_biconnected_components()) + can now return the components themselves. +- Eigenvector centrality calculation, igraph_eigenvector_centrality() + now works for directed graphs. +- Shortest path calculations with get_shortest_paths() and + get_shortest_paths_dijkstra() can now return the edges along the paths. +- Betweenness calculation can now use arbitrarily large integers, + this is required for some lattice-like graphs to avoid overflow. +- igraph_bipartite_projection() calculates multiplicity of edges. +- igraph_barabasi_game() was rewritten and it supports three + algorithms now, the default algorithm does not generate multiple or + loop edges. +- The Watts-Strogatz graph generator, igraph_watts_strogatz() can + now create graphs without loop edges. +- igraph should be now thread-safe, on architectures that support + thread-local storage (Linux and Windows: yes, Mac OSX: no). + +We also fixed numerous bugs, too many to include them here, sorry. +You may look at our bug tracker at https://bugs.launchpad.net/igraph +to check whether a bug was fixed or not. Thanks for all the people +reporting bugs. Special thanks to Minh Van Nguyen for a lot of bug +reports, documentation fixes and contributed code! + +igraph 0.5.3 +============ + +Released November 22, 2009 + +Bugs corrected in the R interface +--------------------------------- +- Some small changes to make 'R CMD check' clean +- Fixed a bug in graph.incidence, the 'directed' and 'mode' arguments + were not handled correctly +- Betweenness and edge betweenness functions work for graphs with + many shortest paths now (up to the limit of long long int) +- When compiling the package, the configure script fails if there is + no C compiler available +- igraph.from.graphNEL creates the right number of loop edges now +- Fixed a bug in bipartite.projection() that caused occasional crashes + on some systems + +New in the Python interface +--------------------------- +- Added support for weighted diameter +- get_eid() considers edge directions by default from now on +- Fixed a memory leak in the attribute handler +- 'NaN' and 'inf' are treated correctly now + +Bugs corrected in the C layer +----------------------------- +- Betweenness and edge betweenness functions work for graphs with + many shortest paths now (up to the limit of long long int) +- The configure script fails if there is no C compiler available +- Fixed a bug in igraph_community_spinglass, when csize was a NULL + pointer, but membership was not +- Fixed a bug in igraph_bipartite_projection that caused occasional + crashes on some systems + +igraph 0.5.2 +============ + +Released April 10, 2009 + +See also the release notes at +http://igraph.sf.net/relnotes-0.5.2.html + +New in the R interface +---------------------- + +- Added progress bar support to beweenness() and + betweenness.estimate(), layout.drl() +- Speeded up betweenness estimation +- Speeded up are.connected() +- Johnson's shortest paths algorithm added +- shortest.paths() has now an 'algorithm' argument to choose from the + various implementations manually +- Always quote symbolic vertex names when printing graphs or edges +- Average nearest neighbor degree calculation, graph.knn() +- Weighted degree (also called strength) calculation, graph.strength() +- Some new functions to support bipartite graphs: graph.bipartite(), + is.bipartite(), get.indicence(), graph.incidence(), + bipartite.projection(), bipartite.projection.size() +- Support for plotting curved edges with plot.igraph() and tkplot() +- Added support for weighted graphs in alpha.centrality() +- Added the label propagation community detection algorithm by + Raghavan et al., label.propagation.community() +- cohesive.blocks() now has a 'cutsetHeuristic' argument to choose + between two cutset algorithms +- Added a function to "unfold" a tree, unfold.tree() +- New tkplot() arguments to change the drawing area +- Added a minimal GUI, invoke it with tkigraph() +- The DrL layout generator, layout.drl() has a three dimensional mode + now. + +Bugs corrected in the R interface +--------------------------------- + +- Fixed a bug in VF2 graph isomorphism functions +- Fixed a bug when a sparse adjacency matrix was requested in + get.adjacency() and the graph was named +- VL graph generator in degree.sequence.game() checks now that + the sum of the degrees is even +- Many fixes for supporting various compilers, e.g. GCC 4.4 and Sun's + C compiler +- Fixed memory leaks in graph.automorphisms(), Bellman-Ford + shortest.paths(), independent.vertex.sets() +- Fix a bug when a graph was imported from LGL and exported to NCOL + format (\#289596) +- cohesive.blocks() creates its temporary file in the session + temporary directory +- write.graph() and read.graph() now give error messages when unknown + arguments are given +- The GraphML reader checks the name of the attributes to avoid adding + a duplicate 'id' attribute +- It is possible to change the 'ncv' ARPACK parameter for + leading.eigenvector.community() +- Fixed a bug in path.length.hist(), 'unconnected' was wrong + for unconnected and undirected graphs +- Better handling of attribute assingment via iterators, this is now + also clarified in the manual +- Better error messages for unknown vertex shapes +- Make R package unload cleanly if unloadNamespace() is used +- Fixed a bug in plotting square shaped vertices (\#325244) +- Fixed a bug in graph.adjacency() when the matrix is a sparse matrix + of class "dgTMatrix" + +New in the Python interface +--------------------------- + +- Speeded up betweenness estimation +- Johnson's shortest paths algorithm added (selected automatically + by Graph.shortest_paths() if needed) +- Weighted degree (also called strength) calculation, Graph.strength() +- Some new methods to support bipartite graphs: Graph.Bipartite(), + Graph.is_bipartite(), Graph.get_indicence(), Graph.Incidence(), + Graph.bipartite_projection(), Graph.bipartite_projection_size() +- Added the label propagation community detection algorithm by + Raghavan et al., Graph.community_label_propagation() +- Added a function to "unfold" a tree, Graph.unfold_tree() +- setup.py script improvements +- Graph plotting now supports edge_arrow_size and edge_arrow_width +- Added Graph.Formula to create small graphs from a simple notation +- VertexSeq and EdgeSeq objects can now be indexed by slices + +New in the C layer +------------------ + +- Added progress bar support to igraph_betweenness() and + igraph_betweenness_estimate(), igraph_layout_drl() +- Speeded up igraph_betweenness_estimate(), igraph_get_eid(), + igraph_are_connected(), igraph_get_eids() +- Added igraph_get_eid2() +- Johnson's shortest path algorithm added: + igraph_shortest_paths_johnson() +- Average nearest neighbor degree calculation, + igraph_avg_nearest_neighbor_degree() +- Weighted degree (also called strength) calculation, + igraph_strength() +- Some functions to support bipartite graphs: igraph_full_bipartite(), + igraph_bipartite_projection(), igraph_create_bipartite(), + igraph_incidence(), igraph_get_incidence(), + igraph_bipartite_projection_size(), igraph_is_bipartite() +- Added the label propagation community detection algorithm by + Raghavan et al., igraph_community_label_propagation() +- Added an example that shows how to set the random number generator's + seed from C (examples/simple/random_seed.c) +- Added a function to "unfold" a tree, igraph_unfold_tree() +- C attribute handler updates: added functions to query many + vertices/edges at once +- Three dimensional DrL layout, igraph_layout_drl_3d() + +Bugs corrected in the C layer +----------------------------- + +- Fixed a bug in igraph_isomorphic_function_vf2(), affecting all VF2 + graph isomorphism functions +- VL graph generator in igraph_degree_sequence_game() checks now that + the sum of the degrees is even +- Many small corrections to make igraph compile with Microsoft Visual + Studio 2003, 2005 and 2008 +- Many fixes for supporting various compilers, e.g. GCC 4.4 and Sun's + C compiler +- Fix a bug when a graph was imported from LGL and exported to NCOL + format (\#289596) +- Fixed memory leaks in igraph_automorphisms(), + igraph_shortest_paths_bellman_ford(), + igraph_independent_vertex_sets() +- The GraphML reader checks the name of the attributes to avoid adding + a duplicate 'id' attribute +- It is possible to change the 'ncv' ARPACK parameter for + igraph_community_leading_eigenvector() +- Fixed a bug in igraph_path_length_hist(), 'unconnected' was wrong + for unconnected and undirected graphs. + +igraph 0.5.1 +============ + +Released July 14, 2008 + +See also the release notes at +http://igraph.sf.net/relnotes-0.5.1.html + +New in the R interface +---------------------- + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Edge labels are plotted at 1/3 of the edge, this is better if + the graph has mutual edges. +- Initial and experimental vertex shape support in 'plot'. +- New function, 'graph.adjlist' creates igraph graphs from + adjacency lists. +- Conversion to/from graphNEL graphs, from the 'graph' R package. +- Fastgreedy community detection can utilize edge weights now, this + was missing from the R interface. +- The 'arrow.width' graphical parameter was added. +- graph.data.frame has a new argument 'vertices'. +- graph.adjacency and get.adjacency support sparse matrices, + the 'Matrix' package is required to use this functionality. +- graph.adjacency adds column/row names as 'name' attribute. +- Weighted shortest paths using Dijkstra's or the Belmann-Ford + algorithm. +- Shortest path functions return 'Inf' for unreachable vertices. +- New function 'is.mutual' to find mutual edges in a directed graph. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- preference.game and asymmetric.preference.game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- Edge weight support in function 'get.shortest.paths', it uses + Dijkstra's algorithm. + +Bugs corrected in the R interface +--------------------------------- + +- A bug was corrected in write.pajek.bgraph. +- Several bugs were corrected in graph.adjacency. +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The basic igraph constructor (igraph_empty_attrs, all functions + are expected to call this internally) now checks whether the number + of vertices is finite. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now. +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +New in the Python interface +--------------------------- + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Methods parameters accepting igraph.IN, igraph.OUT and igraph.ALL + constants now also accept these as strings ("in", "out" and "all"). + Prefix matches also allowed as long as the prefix match is unique. +- Graph.shortest_paths() now supports edge weights (Dijkstra's and + Bellman-Ford algorithm implemented) +- Graph.get_shortest_paths() also supports edge weights + (only Dijkstra's algorithm yet) +- Added Graph.is_mutual() to find mutual edges in a directed graph. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- preference.game and asymmetric.preference.game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- ARPACK options can now be modified from the Python interface + (thanks to Kurt Jacobson) +- Layout.to_radial() added -- now you can create a top-down tree + layout by the Reingold-Tilford algorithm and then turn it to a + radial tree layout +- Added Graph.write_pajek() to save graphs in Pajek format +- Some vertex and edge related methods can now also be accessed via + the methods of VertexSeq and EdgeSeq, restricted to the current + vertex/edge sequence of course +- Visualisations now support triangle shaped vertices +- Added Graph.mincut() +- Added Graph.Weighted_Adjacency() to create graphs from weighted + adjacency matrices +- Kamada-Kawai and Fruchterman-Reingold layouts now accept initial + vertex positions +- Graph.Preference() and Graph.Asymmetric_Preference() were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). + +Bugs corrected in the Python interface +-------------------------------------- + +- Graph.constraint() now properly returns floats instead of integers + (thanks to Eytan Bakshy) +- Graphs given by adjacency matrices are now finally loaded and saved + properly +- Graph.Preference() now accepts floats in type distributions +- A small bug in Graph.community_edge_betweenness() corrected +- Some bugs in numeric attribute handling resolved +- VertexSeq and EdgeSeq objects can now be subsetted by lists and + tuples as well +- Fixed a bug when dealing with extremely small layout sizes +- Eigenvector centality now always return positive values +- Graph.authority_score() now really returns the authority scores + instead of the hub scores (blame copypasting) +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now. +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +New in the C layer +------------------ + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Some stochastic test results are ignored (for spinglass community + detection, some Erdos-Renyi generator tests) +- Weighted shortest paths, Dijkstra's algorithm. +- The unweighted shortest path routine returns 'Inf' for unreachable + vertices. +- New function, igraph_adjlist can create igraph graphs from + adjacency lists. +- New function, igraph_weighted_adjacency can create weighted graphs + from weight matrices. +- New function, igraph_is_mutual to search for mutual edges. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- igraph_preference_game and igraph_asymmetric_preference_game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- The Bellman-Ford shortest path algorithm was added. +- Added weighted variant of igraph_get_shortest_paths, based on + Dijkstra's algorithm. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +Bugs corrected in the C layer +----------------------------- + +- Several bugs were corrected in the (still experimental) C attribute + handler. +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Some code polish to make igraph compile with GCC 4.3 +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The basic igraph constructor (igraph_empty_attrs, all functions + are expected to call this internally) now checks whether the number + of vertices is finite. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now.x +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. + +igraph 0.5 +========= + +Released February 14, 2008 + +See also the release notes at http://igraph.sf.net/relnotes-0.5.html + +New in the R interface +---------------------- + +- The 'rescale', 'asp' and 'frame' graphical parameters were added +- Create graphs from a formula notation (graph.formula) +- Handle graph attributes properly +- Calculate the actual minimum cut for undirected graphs +- Adjacency lists, get.adjlist and get.adjedgelist added +- Eigenvector centrality computation is much faster now +- Proper R warnings, instead of writing the warning to the terminal +- R checks graphical parameters now, the unknown ones are not just + ignored, but an error message is given +- plot.igraph has an 'add' argument now to compose plots with multiple + graphs +- plot.igraph supports the 'main' and 'sub' arguments +- layout.norm is public now, it can normalize a layout +- It is possible to supply startup positions to layout generators +- Always free memory when CTRL+C/ESC is pressed, in all operating + systems +- plot.igraph can plot square vertices now, see the 'shape' parameter +- graph.adjacency rewritten when creating weighted graphs +- We use match.arg whenever possible. This means that character scalar + options can be abbreviated and they are always case insensitive + +- VF2 graph isomorphism routines can check subgraph isomorphism now, + and they are able to return matching(s) +- The BLISS graph isomorphism algorithm is included in igraph now. See + canonical.permutation, graph.isomorphic.bliss +- We use ARPACK for eigenvalue/eigenvector calculation. This means that the + following functions were rewritten: page.rank, + leading.eigenvector.community.\*, evcent. New functions based on + ARPACK: hub.score, authority.score, arpack. +- Edge weights for Fruchterman-Reingold layout (layout.fruchterman.reingold). +- Line graph calculation (line.graph) +- Kautz and de Bruijn graph generators (graph.kautz, graph.de.bruijn) +- Support for writing graphs in DOT format +- Jaccard and Dice similarity coefficients added (similarity.jaccard, + similarity.dice) +- Counting the multiplicity of edges (count.multiple) +- The graphopt layout algorithm was added, layout.graphopt +- Generation of "famous" graphs (graph.famous). +- Create graphs from LCF notation (graph.cf). +- Dyad census and triad cencus functions (dyad.census, triad.census) +- Cheking for simple graphs (is.simple) +- Create full citation networks (graph.full.citation) +- Create a histogram of path lengths (path.length.hist) +- Forest fire model added (forest.fire.game) +- DIMACS reader can handle different file types now +- Biconnected components and articulation points (biconnected.components, + articulation.points) +- Kleinberg's hub and authority scores (hub.score, authority.score) +- as.undirected handles attributes now +- Geometric random graph generator (grg.game) can return the + coordinates of the vertices +- Function added to convert leading eigenvector community structure result to + a membership vector (community.le.to.membership) +- Weighted fast greedy community detection +- Weighted page rank calculation +- Functions for estimating closeness, betweenness, edge betweenness by + introducing a cutoff for path lengths (closeness.estimate, + betweenness.estimate, edge.betweenness.estimate) +- Weighted modularity calculation +- Function for permuting vertices (permute.vertices) +- Betweenness and closeness calculations are speeded up +- read.graph can handle all possible line terminators now (\r, \n, \r\n, \n\r) +- Error handling was rewritten for walktrap community detection, + the calculation can be interrupted now +- The maxflow/mincut functions allow to supply NULL pointer for edge + capacities, implying unit capacities for all edges + +Bugs corrected in the R interface +--------------------------------- + +- Fixed a bug in cohesive.blocks, cohesive blocks were sometimes not + calculated correctly + +New in the Python interface +--------------------------- + +- Added shell interface: igraph can now be invoked by calling the script called + igraph from the command line. The script launches the Python interpreter and + automatically imports igraph functions into the main namespace +- Pickling (serialization) support for Graph objects +- Plotting functionality based on the Cairo graphics library (so you need to + install python-cairo if you want to use it). Currently the following + objects can be plotted: graphs, adjacency matrices and dendrograms. Some + crude support for plotting histograms is also implemented. Plots can be + saved in PNG, SVG and PDF formats. +- Unified Graph.layout method for accessing layout algorithms +- Added interfaces to walktrap community detection and the BLISS isomorphism + algorithm +- Added dyad and triad census functionality and motif counting +- VertexSeq and EdgeSeq objects can now be restricted to subsets of the + whole network (e.g., you can select vertices/edges based on attributes, + degree, centrality and so on) + +New in the C library +-------------------- + +- Many types (stack, matrix, dqueue, etc.) are templates now + They were also rewritten to provide a better organized interface +- VF2 graph isomorphism routines can check subgraph isomorphism now, + and they are able to return matching(s) +- The BLISS graph isomorphism algorithm is included in igraph now. See + igraph_canonical_permutation, igraph_isomorphic_bliss +- We use ARPACK for eigenvalue/eigenvector calculation. This means that the + following functions were rewritten: igraph_pagerank, + igraph_community_leading_eigenvector_\*. New functions based on + ARPACK: igraph_eigenvector_centrality, igraph_hub_score, + igraph_authority_score, igraph_arpack_rssolve, igraph_arpack_rnsolve +- Experimental C attribute interface added. I.e. it is possible to use + graph/vertex/edge attributes from C code now. + +- Edge weights for Fruchterman-Reingold layout. +- Line graph calculation. +- Kautz and de Bruijn graph generators +- Support for writing graphs in DOT format +- Jaccard and Dice similarity coefficients added +- igraph_count_multiple added +- igraph_is_loop and igraph_is_multiple "return" boolean vectors +- The graphopt layout algorithm was added, igraph_layout_graphopt +- Generation of "famous" graphs, igraph_famous +- Create graphs from LCF notation, igraph_lcf, igraph_lcf_vector +- igraph_add_edge adds a single edge to the graph +- Dyad census and triad cencus functions added +- igraph_is_simple added +- progress handlers are allowed to stop calculation +- igraph_full_citation to create full citation networks +- igraph_path_length_hist, create a histogram of path lengths +- forest fire model added +- DIMACS reader can handle different file types now +- Adjacency list types made public now (igraph_adjlist_t, igraph_adjedgelist_t) +- Biconnected components and articulation points can be computed +- Eigenvector centrality computation +- Kleinberg's hub and authority scores +- igraph_to_undirected handles attributes now +- Geometric random graph generator can return the coordinates of the vertices +- Function added to convert leading eigenvector community structure result to + a membership vector (igraph_le_community_to_membership) +- Weighted fast greedy community detection +- Weighted page rank calculation +- Functions for estimating closeness, betweenness, edge betweenness by + introducing a cutoff for path lengths +- Weighted modularity calculation +- igraph_permute_vertices added +- Betweenness ans closeness calculations are speeded up +- Startup positions can be supplied to the Kamada-Kawai layout + algorithms +- igraph_read_graph_\* functions can handle all possible line + terminators now (\r, \n, \r\n, \n\r) +- Error handling was rewritten for walktrap community detection, + the calculation can be interrupted now +- The maxflow/mincut functions allow to supply a null pointer for edge + capacities, implying unit capacities for all edges + +Bugs corrected in the C library +------------------------------- + +- Memory leak fixed in adjacency list handling +- Memory leak fixed in maximal independent vertex set calculation +- Fixed a bug when rewiring undirected graphs with igraph_rewire +- Fixed edge betweenness community structure detection for unconnected graphs +- Make igraph compile with Sun Studio +- Betweenness bug fixed, when not computing for all vertices +- memory usage of clique finding reduced +- Corrected bugs for motif counts when not all motifs were counted, + but a 'cut' vector was used +- Bugs fixed in trait games and cited type game +- Accept underscore as letter in GML files +- GML file directedness notation reversed, more logical this way + +igraph 0.4.5 +========= + +Released January 1, 2008 + +New: +- Cohesive block finding in the R interface, thanks to Peter McMahan + for contributing his code. See James Moody and Douglas R. White, + 2003, in Structural Cohesion and Embeddedness: A Hierarchical + Conception of Social Groups American Sociological Review 68(1):1-25 +- Biconnected components and articulation points. +- R interface: better printing of attributes. +- R interface: graph attributes can be used via '$'. + +New in the C library: +- igraph_vector_bool_t data type. + +Bug fixed: +- Erdos-Renyi random graph generators rewritten. + +igraph 0.4.4 +========= + +Released October 3, 2007 + +This release should work seemlessly with the new R 2.6.0 version. +Some other bugs were also fixed: +- A bug was fixed in the Erdos-Renyi graph generator, which sometimes + added an extra vertex. +- MSVC compilation issues were fixed. +- MinGW compilation fixes. + +igraph 0.4.3 +========= + +Released August 13, 2007 + +The next one in the sequence of bugfix releases. Thanks to many people +sending bug reports. Here are the changes: +- Some memory leaks removed when using attributes from R or Python. +- GraphML parser: entities and character data in multiple chunks are now handled correctly. +- A bug corrected in edge betweenness community structure detection, + it failed if called many times from the same program/session. +- Bug corrected in 'adjacent edges' edge iterator. +- Python interface: edge and vertex attribute deletion bug corrected. +- Edge betweeness community structure: handle unconnected graphs properly. +- Fixed bug related to fast greedy community detection in unconnected graphs. +- Use a different kind of parser (Push) for reading GraphML files. This is almost + invisible for users but fixed a nondeterministic bug when reading in GraphML + files. +- R interface: plot now handles properly if called with a vector as the edge.width + argument for directed graphs. +- R interface: bug (typo) corrected for walktrap.community and weighted graphs. +- Test suite should run correctly on Cygwin now. + +igraph 0.4.2 +========= + +Released June 7, 2007 + +This is another bugfix release, as there was a serious bug in the +R package of the previous version: it could not read and write graphs +to files in any format under MS Windows. + +Some other bits added: +- circular Reingold-Tilford layout generator for trees +- corrected a bug, Pajek files are written properly under MS Windows now. +- arrow.size graphical edge parameter added in the R interface. + +igraph 0.4.1 +========= + +Released May 23, 2007 + +This is a minor release, it corrects a number of bugs, mostly in the +R package. + +igraph 0.4 +========= + +Released May 21, 2007 + +The major new additions in this release is a bunch of community +detection algorithms and support for the GML file format. Here +is the complete list of changes: + + +New in the C library +-------------------- + +- internal representation changed +- neighbors always returns an ordered list +- igraph_is_loop and igraph_is_multiple added + +- topological sorting +- VF2 isomorphism algorithm +- support for reading the file format of the Graph Database for isomorphism +- igraph_mincut cat calculate the actual minimum cut +- girth calculation added, thanks to Keith Briggs +- support for reading and writing GML files + +- Walktrap community detection algorithm added, thanks to Matthieu Latapy + and Pascal Pons +- edge betweenness based community detection algorithm added +- fast greedy algorithm for community detection by Clauset et al. added + thanks to Aaron Clauset for sharing his code +- leading eigenvector community detection algorithm by Mark Newman added +- igraph_community_to_membership supporting function added, creates + a membership vector from a community structure merge tree +- modularity calculation added + +New in the R interface +---------------------- + +- as the internal representation changed, graphs stored with 'save' + with an older igraph version cannot be read back with the new + version reliably. +- neighbors returns ordered lists + +- topological sorting +- VF2 isomorphism algorithm +- support for reading graphs from the Graph Database for isomorphism +- girth calculation added, thanks to Keith Briggs +- support for reading and writing GML files + +- Walktrap community detection algorithm added, thanks to Matthieu Latapy + and Pascal Pons +- edge betweenness based community detection algorithm added +- fast greedy algorithm for community detection by Clauset et al. added + thanks to Aaron Clauset for sharing his code +- leading eigenvector community detection algorithm by Mark Newman added +- functions for creating denrdograms from the output of the + community detection algorithms added +- community.membership supporting function added, creates + a membership vector from a community structure merge tree +- modularity calculation added + +- graphics parameter handling is completely rewritten, uniform handling + of colors and fonts, make sure you read ?igraph.plotting +- new plotting parameter for edges: arrow.mode +- a bug corrected when playing a nonlinear barabasi.game +- better looking plotting in 3d using rglplot: edges are 3d too +- rglplot layout is allowed to be two dimensional now +- rglplot suspends updates while drawing, this makes it faster +- loop edges are correctly plotted by all three plotting functions + +- better printing of attributes when printing graphs +- summary of a graph prints attribute names +- is.igraph rewritten to make it possible to inherit from the 'igraph' class +- somewhat better looking progress meter for functions which support it + +Others +------ + +- proper support for Debian packages (re)added +- many functions benefit from the new internal representation and are + faster now: transitivity, reciprocity, graph operator functions like + intersection and union, etc. +- igraph compiles with Microsoft Visual C++ now +- there were some internal changes to make igraph a real graph algorithm + platform in the near future, but these are undocumented now + +Bugs corrected +-------------- + +- corrected a bug when reading Pajek files: directed graphs were read as undirected + +Debian package repository available +================================== + +Debian Linux users can now install and update the C interface +using the standard package manager. Just add the following two +lines to /etc/apt/sources.list and install the libigraph and +libigraph-dev packages. Packages for the Python interface are +coming soon. + +deb http://cneurocvs.rmki.kfki.hu /packages/binary/ + +deb-src http://cneurocvs.rmki.kfki.hu /packages/source/ + +igraph 0.3.3 +============ + +Released February 28, 2007 + +New in the C library +-------------------- + +* igraph_connect_neighborhood, nomen est omen +* igraph_watts_strogatz_game and igraph_rewire_edges +* K-core decomposition: igraph_coreness +* Clique and independent vertex set related functions: + igraph_cliques, igraph_independent_vertex_sets, + igraph_maximal_cliques, igraph_maximal_independent_vertex_sets, + igraph_independence_number, igraph_clique_number, + Some of these function were ported from the very_nauty library + of Keith Briggs, thanks Keith! +* The GraphML file format now supports graph attributes +* Transitivity calculation speeded up +* Correct transitivity calculation for multigraphs (ie. non-simple graphs) + +New in the R interface +---------------------- + +* connect.neighborhood +* watts.strogatz.game and rewire.edges +* K-core decomposition: graph.coreness +* added the 'innei' and 'outnei' shorthands for vertex sequence indexing + see help(iterators) +* Clique and independent vertex set related functions: + cliques, largest.cliques, maximal.cliques, clique.number, + independent.vertex.sets, largest.independent.vertex.sets, + maximal.independent.vertex.sets, independence.number +* The GraphML file format now supports graph attributes +* edge.lty argument added to plot.igraph and tkplot +* Transitivity calculation speeded up +* Correct transitivity calculation for multigraphs (ie. non-simple graphs) +* alpha.centrality added, calculates Bonacich alpha centrality, see docs. + +Bugs corrected +-------------- + +* 'make install' installs the library correctly on Cygwin now +* Pajek parser corrected to read files with MacOS newline characters correctly +* overflow bug in transitivity calculation for large graphs corrected +* an internal memcpy/memmove bug causing some segfaults removed +* R interface: tkplot bug with graphs containing a 'name' attribute +* R interface: attribute handling bug when adding vertices +* R interface: color selection bug corrected +* R interface: plot.igraph when plotting loops + +Python interface documentation +==================== + +Jan 8, 2007 + +The documentation of the Python interface is available. +See section 'documentation' in the menu on the left. + +igraph 0.3.2 +========= + +Released Dec 19, 2006 + +This is a new major release, it contains many new things: + +Changes in the C library +------------------------ + +- igraph_maxdegree added, calculates the maximum degree in the graph +- igraph_grg_game, geometric random graphs +- igraph_density, graph density calculation +- push-relabel maximum flow algorithm added, igraph_maxflow_value +- minimum cut functions added based on maximum flow: + igraph_st_mincut_value, igraph_mincut_value, the Stoer-Wagner + algorithm is implemented for undirected graphs +- vertex connectivity functions, usually based on maximum flow: + igraph_st_vertex_connectivity, igraph_vertex_connectivity +- edge connectivity functions, usually based on maximum flow: + igraph_st_edge_connectivity, igraph_edge_connectivity +- other functions based on maximum flow: igraph_edge_disjoint_paths, + igraph_vertex_disjoint_paths, igraph_adhesion, igraph_cohesion +- dimacs file format added +- igraph_to_directed handles attributes +- igraph_constraint calculation corrected, it handles weighted graphs +- spinglass-based community structure detection, the Joerg Reichardt -- + Stefan Bornholdt algorithm added: igraph_spinglass_community, + igraph_spinglass_my_community +- igraph_extended_chordal_rings, it creates extended chordal rings +- 'no' argument added to igraph_clusters, it is possible to calculate + the number of clusters without calculating the clusters themselves +- minimum spanning tree functions keep attributes now and also the + direction of the edges is kept in directed graphs +- there are separate functions to calculate different types of + transitivity now +- igraph_delete_vertices rewritten to allocate less memory for the new + graph +- neighborhood related functions added: igraph_neighborhood, + igraph_neighborhood_size, igraph_neighborhood_graphs +- two new games added based on different node types: + igraph_preference_game and igraph_asymmetric_preference_game +- Laplacian of a graph can be calculated by the igraph_laplacian function + +Changes in the R interface +-------------------------- + +- bonpow function ported from SNA to calculate Bonacich power centrality +- get.adjacency supports attributes now, this means that it sets the + colnames and rownames attributes and can return attribute values in + the matrix instead of 0/1 +- grg.game, geometric random graphs +- graph.density, graph density calculation +- edge and vertex attributes can be added easily now when added new + edges with add.edges or new vertices with add.vertices +- graph.data.frame creates graph from data frames, this can be used to + create graphs with edge attributes easily +- plot.igraph and tkplot can plot self-loop edges now +- graph.edgelist to create a graph from an edge list, can also handle + edge lists with symbolic names +- get.edgelist has now a 'names' argument and can return symbolic + vertex names instead of vertex IDs, by default id uses the 'name' + vertex attribute is returned +- printing graphs on screen also prints symbolic symbolic names + (the 'name' attribute if present) +- maximum flow and minimum cut functions: graph.maxflow, graph.mincut +- vertex and edge connectivity: edge.connectivity, vertex.connectivity +- edge and vertex disjoint paths: edge.disjoint.paths, + vertex.disjoint.paths +- White's cohesion and adhesion measure: graph.adhesion, graph.cohesion +- dimacs file format added +- as.directed handles attributes now +- constraint corrected, it handles weighted graphs as well now +- weighted attribute to graph.adjacency +- spinglass-based community structure detection, the Joerg Reichardt -- + Stefan Bornholdt algorithm added: spinglass.community +- graph.extended.chordal.ring, extended chordal ring generation +- no.clusters calculates the number of clusters without calculating + the clusters themselves +- minimum spanning tree functions updated to keep attributes +- transitivity can calculate local transitivity as well +- neighborhood related functions added: neighborhood, + neighborhood.size, graph.neighborhood +- new graph generators based on vertex types: preference.game and + asymmetric.preference.game + +Bugs corrected +-------------- + +- attribute handling bug when deleting edges corrected +- GraphML escaping and NaN handling corrected +- bug corrected to make it possible compile the R package without the + libxml2 library +- a bug in Erdos-Renyi graph generation corrected: it had problems + with generating large directed graphs +- bug in constraint calculation corrected, it works well now +- fixed memory leaks in igraph_read_graph_graphml +- error handling bug corrected in igraph_read_graph_graphml +- bug corrected in R version of graph.laplacian when normalized + Laplacian is requested +- memory leak corrected in get.all.shortest.paths in the R package + +igraph 0.2.1 +========= + +Released Aug 23, 2006 + +This is a bug-fix release. Bugs fixed: +- igraph_reciprocity (reciprocity in R) corrected to avoid segfaults +- some docs updates +- various R package updated to make it conform to the CRAN rules + +igraph 0.2 +========= + +Released Aug 18, 2006 + +Release time at last! There are many new things in igraph 0.2, the +most important ones: +- reading writing Pajek and GraphML formats with attributes + (not all Pajek and GraphML files are supported, see documentation + for details) +- iterators totally rewritten, it is much faster and cleaner now +- the RANDEDU fast motif search algorithm is implemented +- many new graph generators, both games and regular graphs +- many new structural properties: transitivity, reciprocity, etc. +- graph operators: union, intersection, difference, structural holes, etc. +- conversion between directed and undirected graphs +- new layout algorithms for trees and large graphs, 3D layouts + +and many more. + +New things in the R package: +- support for CTRL+C +- new functions: Graph Laplacian, Burt's constraint, etc. +- vertex/edge sequences totally rewritten, smart indexing (see manual) +- new R manual and tutorial: 'Network Analysis with igraph', still + under development but useful +- very basic 3D plotting using OpenGL + +Although this release was somewhat tested on Linux, MS Windows, Mac +OSX, Solaris 8 and FreeBSD, no heavy testing was done, so it might +contain bugs, and we kindly ask you to send bug reports to make igraph +better. + +igraph mailing lists +==================== + +Aug 18, 2006 + +I've set up two igraph mailing lists: igraph-help for +general igraph questions and discussion and +igraph-anonunce for announcements. See +http://lists.nongnu.org/mailman/listinfo/igraph-help and +http://lists.nongnu.org/mailman/listinfo/igraph-announce +for subscription information, archives, etc. + +igraph 0.1 +========= + +Released Jan 30, 2006 + +After about a year of development this is the first "official" release +of the igraph library. This release should be considered as beta +software, but it should be useful in general. Please send your +questions and comments. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8eadfd7 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +[![Build Status on Azure Pipelines](https://dev.azure.com/igraph-team/igraph/_apis/build/status/igraph.igraph?branchName=main)](https://dev.azure.com/igraph-team/igraph/_build/latest?definitionId=1&branchName=main) +![Build Status on Github Actions](https://github.com/igraph/igraph/workflows/MINGW/badge.svg?branch=main) +[![codecov](https://codecov.io/gh/igraph/igraph/branch/main/graph/badge.svg?token=xGFabHJE2I)](https://codecov.io/gh/igraph/igraph) +[![DOI](https://zenodo.org/badge/8546198.svg)](https://zenodo.org/badge/latestdoi/8546198) + +The igraph library +------------------ + +igraph is a C library for complex network analysis and graph theory, with +emphasis on efficiency, portability and ease of use. + +See https://igraph.org for installation instructions and documentation. + +igraph can also be used from: + + - R — https://github.com/igraph/rigraph + - Python — https://github.com/igraph/python-igraph + - Mathematica — https://github.com/szhorvat/IGraphM + +igraph is a collaborative work of many people from all around the world — +see the [list of contributors here](./CONTRIBUTORS.md). If you would like +to contribute yourself, [click here to see how you can +help](./CONTRIBUTING.md). + +Citation +-------- + +If you use igraph in your research, please cite + +> Csardi, G., & Nepusz, T. (2006). The igraph software package for complex network research. InterJournal, Complex Systems, 1695. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..bdcb7c7 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,13 @@ +# Need help with the igraph C library? + +_This repository is **only** about the C library of `igraph`. Do you use `igraph` from a different language? Then please see the repositories for the [R interface](https://github.com/igraph/rigraph/), the [Python interface](https://github.com/igraph/python-igraph/) or the [Mathematica interface](https://github.com/szhorvat/IGraphM)._ + +Having problems with igraph? + + - First, check our [documentation](https://igraph.org/c/html/latest/) for answers. + * Problems with installing `igraph`? Please check our [installation instructions](https://igraph.org/c/html/latest/igraph-Installation.html). + * Problems compiling your own code? Please check our [tutorial](https://igraph.org/c/html/latest/igraph-Tutorial.html) on writing your first `igraph` program. + - Do you have a question about `igraph`? Please post your question on our [support forum](https://igraph.discourse.group/). + - If you **found a bug**, please go ahead and [open a new issue](https://github.com/igraph/igraph/issues). + + We use the [issue tracker](https://github.com/igraph/igraph/issues) for bug reports and feature requests, and the [support forum](https://igraph.discourse.group/) for questions. diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..b29b233 --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,58 @@ +# Versioning and stability + +This document is provided for informational purposes only, to help igraph users understand how igraph's programming interface is evolving, and what stability guaratees you can rely on. It concerns the igraph C library only. igraph's high level interfaces (R, Python, Mathematica) have their own separate versioning schemes and compatibility policies. + +## Versioning + +Starting with version 1.0, igraph follows the spirit of semantic versioning, with some differences, as described below. The version number consists of three parts in the `MAJOR.MINOR.PATCH` format. + +- `MAJOR` is incremented after making _incompatible changes_ to the stable programming interface. Major releases are intended to be infrequent, and are accompanied by release notes where we make the effort to provide detailed guidance on adapting to incompatible changes. +- `MINOR` is incremented after making _additions_ to the stable programming interface. Minor releases are issued regularly. +- `PATCH` is incremented when making changes that do not affect compatibility (usually bug fixes, documentation improvements, or build systems changes). + +The three version parts are available at compile time as the macros `IGRAPH_VERSION_MAJOR`, `IGRAPH_VERSION_MINOR` and `IGRAPH_VERSION_PATCH`, or at runtime through the `igraph_version()` function. + +The majority of public, documented functions are considered to be part of the _stable programming interface_, but there are some notable exceptions: + +- **Experimental functions** may change at any time without notice. These are clearly marked in their documentation ([example](https://igraph.org/c/html/0.10.13/igraph-Generators.html#igraph_chung_lu_game)). They are also marked in igraph's header files with the `IGRAPH_EXPERIMENTAL` macro ([example](https://github.com/igraph/igraph/blob/3629c46b2784cb10fc27fc6e9fab4404a13d031c/include/igraph_cycles.h#L49-L53)). Most newly added functions start out as _experimental_, and stay in this state until we are confident in their design, typically for one or two minor releases. User feedback about experimental functions is particularly welcome. We make the effort to avoid changes to experimental functions in patch releases, but do not guarantee this. +- **Internal and undocumented functions** are not part of the stable programming interface, not even if they are present in public headers. They may change at any time. The names of internal functions usually start with the prefix `igraph_i_` (capitalized for macros), while public functions start with `igraph_`. + +## Symbol lifecycle + +Most new symbols start out as _experimental_ in minor releases. Eventually, their API is declared stable, and the experimental marker is removed from their declaration and documentation in an upcoming minor release. + +Symbols go through a deprecation phase before they are removed. Deprecated symbols are marked in their documentation ([example](https://igraph.org/c/html/0.10.13/igraph-Structural.html#igraph_clusters)), and functions are prefixed with `IGRAPH_DEPRECATED` in the public headers ([example](https://github.com/igraph/igraph/blob/997f59ad742892fff199824a248fab382b40f526/include/igraph_components.h#L45-L47)). With GCC-compatible compilers, use the `-Wdeprecated` flag to get a warning for the use of deprecated functions, but keep in mind that deprecation warnings are not supported for all symbol types (e.g. macros) with all compilers. The ultimate reference for deprecations is the [changelog][1]. + +## Stability of behaviour + +Whether changes in function behaviour are considered breaking is somewhat subjective, and is decided on a case-by-case basis. Expect some changes within minor releases. For example, a function that ignored edge multiplicities may gain support for multigraphs in a new minor release. Do not rely on details of behaviour that are not explicitly documented. + +A notable case is stochastic functions: we do not guarantee that the same output is returned across different releases (even patch releases) for the same random seed. We only guarantee the same statistical properties. + +## Advice to users and package maintainers + +**Users:** + +For as long as you don't use _experimental_ functions, you can be confident that your code will continue to work with future releases having the same major version. If you do use _experimental_ functions, it is your responsibility to check the [release notes][1] of each igraph release and adapt accordingly. The use of _internal_ functions is completely unsupported: if you feel you need them, please talk to us. + +If you do use _experimental_ functions, make this clear in your README file for the benefit of package maintainers. + +While igraph comes with multiple header files, only `#include ` is supported. The rest of the headers exist for internal organizational purposes only, and may change without notice. + +**Package maintainers:** + +Software that does not use experimental functions from igraph can safely link to future igraph versions with the same major version. Ask the developer of any software you are packaging if they are using experimental igraph functions. + +The high-level interfaces of igraph do use both experimental and internal functions. Each high-level interface release is only guaranteed to be compatible with one specific release of C/igraph. As of this writing, this is a concern only for the Python interface, as the other interfaces (R and Mathematica) cannot link dynamically to C/igraph. + +We provide the `IGRAPH_WARN_EXPERIMENTAL` compile-time macro to help maintainers in determining whether a piece of software uses experimental igraph functions or not. Compilers that support `__attribute__((__warning(...)))` clauses will issue a warning when `IGRAPH_WARN_EXPERIMENTAL` is defined to a non-zero value at compile-time and an experimental function is used somewhere in the code. + +## Notes + +For the purposes of this document, "API compatibility" means that the same sources can be compiled using headers from different igraph versions. "ABI compatibility" means that a program that only uses stable API can be linked to a different version of the igraph shared library than what it was compiled with. + +We strive to maintain both API and ABI compatibility. + +However, it must be pointed out that we do not support manipulating the same in-memory igraph data structures with different igraph versions (for example, if two libraries that exchange data are each statically linked to different igraph versions). + + [1]: https://github.com/igraph/igraph/blob/main/CHANGELOG.md diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..f7f2a10 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,362 @@ +# Specify the list of .xml files that are used as-is +set( + DOCBOOK_SOURCES + fdl.xml + gpl.xml + igraph-docs.xml + installation.xml + introduction.xml + licenses.xml + glossary.xml + pmt.xml + tutorial.xml +) + +# Specify the list of .xxml files that have to be piped through doxrox to +# obtain the final set of .xml files that serve as an input to DocBook +set( + DOXROX_SOURCES + adjlist.xxml + attributes.xxml + basicigraph.xxml + bipartite.xxml + bitset.xxml + cliques.xxml + coloring.xxml + community.xxml + cycles.xxml + dqueue.xxml + embedding.xxml + error.xxml + flows.xxml + foreign.xxml + games.xxml + generators.xxml + graphlets.xxml + heap.xxml + hrg.xxml + isomorphism.xxml + iterators.xxml + layout.xxml + linalg.xxml + matrix.xxml + memory.xxml + motifs.xxml + nongraph.xxml + operators.xxml + progress.xxml + processes.xxml + psumtree.xxml + random.xxml + separators.xxml + sparsemat.xxml + spatial.xxml + stack.xxml + status.xxml + structural.xxml + strvector.xxml + threading.xxml + vector.xxml + vectorlist.xxml + visitors.xxml +) + +# Specify the igraph source files that may contain documentation chunks +file( + GLOB_RECURSE IGRAPH_SOURCES_FOR_DOXROX + LIST_DIRECTORIES FALSE + ${CMAKE_SOURCE_DIR}/include/*.h + ${CMAKE_BINARY_DIR}/include/*.h + ${CMAKE_SOURCE_DIR}/src/*.c + ${CMAKE_SOURCE_DIR}/src/*.cc + ${CMAKE_SOURCE_DIR}/src/*.cpp + ${CMAKE_SOURCE_DIR}/src/*.h + ${CMAKE_SOURCE_DIR}/src/*.pmt +) + +# Specify the igraph source files that are used as examples in the +# documentation +file( + GLOB DOCBOOK_EXAMPLES + LIST_DIRECTORIES FALSE + RELATIVE ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/examples/simple/*.c + ${CMAKE_SOURCE_DIR}/examples/tutorial/*.c +) + +# You should not need to change anything below this line if you are simply +# trying to add new files to produce documentation from + +# Documentation build requires Python and source-highlight +find_package(Python3) +find_program(SOURCE_HIGHLIGHT_COMMAND source-highlight) + +# HTML documentation additionally requires xmlto from DocBook +find_program(XMLTO_COMMAND xmlto) + +# PDF documentation additionally requires xsltproc, xmllint and Apache FOP +find_program(FOP_COMMAND fop) +find_program(XMLLINT_COMMAND xmllint) +find_program(XSLTPROC_COMMAND xsltproc) + +# GNU Texinfo documentation additionally requires the docbook2X package, +# makeinfo (and xmllint as well). The docbook2texi command from docbook2X +# is renamed to docbook2x-texi by many Linux distros to avoid conflict with +# a command of the same name from the incompatible docbook-tools package. +# We look for both command names, and prefer docbook2x-texi if found. +# At the moment we do not validate that docbook2texi is from docbook2X +# instead of docbook-tools. Such validation will be possible with CMake >= 3.25. +find_program(DOCBOOK2XTEXI_COMMAND NAMES docbook2x-texi docbook2texi) +find_program(MAKEINFO_COMMAND makeinfo) + +if(Python3_FOUND AND SOURCE_HIGHLIGHT_COMMAND) + set(DOC_BUILD_SUPPORTED TRUE) +else() + set(DOC_BUILD_SUPPORTED FALSE) +endif() + +if(DOC_BUILD_SUPPORTED AND XMLTO_COMMAND) + set(HTML_DOC_BUILD_SUPPORTED TRUE) +else() + set(HTML_DOC_BUILD_SUPPORTED FALSE) +endif() + +if(DOC_BUILD_SUPPORTED AND XMLLINT_COMMAND AND XSLTPROC_COMMAND AND FOP_COMMAND) + set(PDF_DOC_BUILD_SUPPORTED TRUE) +else() + set(PDF_DOC_BUILD_SUPPORTED FALSE) +endif() + +if(DOC_BUILD_SUPPORTED AND XMLLINT_COMMAND AND DOCBOOK2XTEXI_COMMAND AND MAKEINFO_COMMAND) + set(INFO_DOC_BUILD_SUPPORTED TRUE) +else() + set(INFO_DOC_BUILD_SUPPORTED FALSE) +endif() + +if(DOC_BUILD_SUPPORTED) + set(DOXROX_COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/doxrox.py) + set(DOXROX_RULES ${CMAKE_CURRENT_SOURCE_DIR}/c-docbook.re) + set(DOXROX_CHUNKS ${CMAKE_CURRENT_BINARY_DIR}/chunks.pickle) + set(DOXROX_CACHE ${CMAKE_CURRENT_BINARY_DIR}/doxrox.cache) + + set(DOCBOOK_INPUTS "") + set(DOCBOOK_GENERATED_INPUTS "") + + # Specify that each DocBook .xml file is to be copied to the build folder + # TODO(ntamas): currently this works with out-of-tree builds only + set(IGRAPH_VERSION ${PACKAGE_VERSION}) # for replacement in igraph-docs.xml + foreach(DOCBOOK_SOURCE ${DOCBOOK_SOURCES}) + set(DOCBOOK_INPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOCBOOK_SOURCE}") + list(APPEND DOCBOOK_INPUTS "${DOCBOOK_INPUT}") + configure_file(${DOCBOOK_SOURCE} ${DOCBOOK_INPUT}) + endforeach() + + # Specify that .xxml files should be piped through doxrox.py to get a + # DocBook-compatible .xml file. This step inserts the documentation chunks + # extracted from the igraph source to the DocBook sources + foreach(DOXROX_SOURCE ${DOXROX_SOURCES}) + string(REGEX REPLACE "[.]xxml$" ".xml" DOXROX_OUTPUT ${DOXROX_SOURCE}) + set(COMMENT "Generating ${DOXROX_OUTPUT} from ${DOXROX_SOURCE}") + + string(PREPEND DOXROX_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/") + list(APPEND DOCBOOK_INPUTS "${DOXROX_OUTPUT}") + list(APPEND DOCBOOK_GENERATED_INPUTS "${DOXROX_OUTPUT}") + + add_custom_command( + OUTPUT ${DOXROX_OUTPUT} + COMMAND ${DOXROX_COMMAND} + ARGS + -t ${CMAKE_CURRENT_SOURCE_DIR}/${DOXROX_SOURCE} + --chunks ${DOXROX_CHUNKS} + -o ${DOXROX_OUTPUT} + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${DOXROX_SOURCE} + DEPENDS ${DOXROX_CHUNKS} + COMMENT ${COMMENT} + ) + endforeach() + + # When all .xxml and .xml files have been processed, we have to send them + # through a custom Python script that extracts the ID references and produces + # a ctags-compatible "tags" file. This will then be used later by + # source-highlight to cross-reference the known tokens from the source code + # of the examples + list(JOIN DOCBOOK_GENERATED_INPUTS ";" DOCBOOK_GENERATED_INPUTS_AS_STRING) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/tags" + COMMAND ${CMAKE_COMMAND} + ARGS + -DINPUT_FILES="${DOCBOOK_GENERATED_INPUTS_AS_STRING}" + -DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/tags + -P ${CMAKE_SOURCE_DIR}/etc/cmake/generate_tags_file.cmake + DEPENDS ${DOCBOOK_GENERATED_INPUTS} + COMMENT "Creating tags file from DocBook xmls" + ) + + # Specify that each example source file is to be piped through source-higlight + # to produce an .xml representation that can be used in the DocBook + # documentation + foreach(DOCBOOK_EXAMPLE_SOURCE ${DOCBOOK_EXAMPLES}) + string(REGEX REPLACE "[.]c$" ".c.xml" DOCBOOK_EXAMPLE_OUTPUT ${DOCBOOK_EXAMPLE_SOURCE}) + set(COMMENT "Highlighting source code in ${DOCBOOK_EXAMPLE_SOURCE}") + + set(DOCBOOK_EXAMPLE_OUTPUT "${CMAKE_BINARY_DIR}/${DOCBOOK_EXAMPLE_SOURCE}.xml") + list(APPEND DOCBOOK_INPUTS "${DOCBOOK_EXAMPLE_OUTPUT}") + + get_filename_component(DOCBOOK_EXAMPLE_OUTPUT_DIR "${DOCBOOK_EXAMPLE_OUTPUT}" DIRECTORY) + + add_custom_command( + OUTPUT ${DOCBOOK_EXAMPLE_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E make_directory ${DOCBOOK_EXAMPLE_OUTPUT_DIR} + COMMAND ${Python3_EXECUTABLE} + ARGS + ${CMAKE_SOURCE_DIR}/tools/strip_licenses_from_examples.py + ${CMAKE_SOURCE_DIR}/${DOCBOOK_EXAMPLE_SOURCE} + ${CMAKE_BINARY_DIR}/${DOCBOOK_EXAMPLE_SOURCE} + COMMAND ${SOURCE_HIGHLIGHT_COMMAND} + ARGS + --src-lang c + --out-format docbook + --input ${CMAKE_BINARY_DIR}/${DOCBOOK_EXAMPLE_SOURCE} + --output ${DOCBOOK_EXAMPLE_OUTPUT} + --gen-references inline + --ctags="" + --outlang-def ${CMAKE_SOURCE_DIR}/doc/docbook.outlang + MAIN_DEPENDENCY ${CMAKE_SOURCE_DIR}/${DOCBOOK_EXAMPLE_SOURCE} + DEPENDS tags + COMMENT ${COMMENT} + ) + endforeach() + + add_custom_command( + OUTPUT ${DOXROX_CHUNKS} ${DOXROX_CACHE} + COMMAND ${DOXROX_COMMAND} + ARGS + -e ${DOXROX_RULES} + -o ${DOXROX_CHUNKS} + --cache ${DOXROX_CACHE} + ${IGRAPH_SOURCES_FOR_DOXROX} + MAIN_DEPENDENCY ${DOXROX_RULES} + DEPENDS ${IGRAPH_SOURCES_FOR_DOXROX} + COMMENT "Parsing documentation chunks from source code" + ) + + set(DOCXML_STAMP ${CMAKE_CURRENT_BINARY_DIR}/xmlstamp) + add_custom_command( + OUTPUT ${DOCXML_STAMP} + COMMAND ${CMAKE_COMMAND} -E touch ${DOCXML_STAMP} + MAIN_DEPENDENCY igraph-docs.xml + DEPENDS ${DOCBOOK_INPUTS} + ) + add_custom_target(docxml DEPENDS ${DOCXML_STAMP}) + + if(HTML_DOC_BUILD_SUPPORTED) + set(HTML_STAMP ${CMAKE_CURRENT_BINARY_DIR}/html/stamp) + + add_custom_command( + OUTPUT ${HTML_STAMP} + COMMAND ${XMLTO_COMMAND} -x ${CMAKE_CURRENT_SOURCE_DIR}/gtk-doc.xsl -o html xhtml igraph-docs.xml + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/html/*.css ${CMAKE_CURRENT_BINARY_DIR}/html + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/html/*.js ${CMAKE_CURRENT_BINARY_DIR}/html + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/html/*.png ${CMAKE_CURRENT_BINARY_DIR}/html + COMMAND ${CMAKE_COMMAND} -E touch ${HTML_STAMP} + MAIN_DEPENDENCY igraph-docs.xml + # The DEPENDS clause below needs to list both the xmlstamp file and the + # target that creates it. The former is needed to make Ninja rebuild the + # HTML files if the source is modified. The latter is needed to make the + # XCode build system happy. + DEPENDS ${DOCXML_STAMP} docxml + COMMENT "Generating HTML documentation with xmlto" + ) + + add_custom_target(html DEPENDS ${HTML_STAMP}) + set(HTML_TARGET html) + endif() + + add_custom_command( + OUTPUT igraph-docs-with-resolved-includes.xml + COMMAND ${XMLLINT_COMMAND} + ARGS + --xinclude + --output igraph-docs-with-resolved-includes-tmp.xml + igraph-docs.xml + COMMAND ${Python3_EXECUTABLE} + ARGS + ${CMAKE_SOURCE_DIR}/tools/removeexamples.py + igraph-docs-with-resolved-includes-tmp.xml + igraph-docs-with-resolved-includes.xml + COMMAND ${CMAKE_COMMAND} + ARGS + -E remove igraph-docs-with-resolved-includes-tmp.xml + MAIN_DEPENDENCY igraph-docs.xml + # The DEPENDS clause below needs to list both the xmlstamp file and the + # target that creates it. The former is needed to make Ninja rebuild the + # PDF file if the source is modified. The latter is needed to make the + # XCode build system happy. + DEPENDS ${DOCXML_STAMP} docxml + ) + + # Intermediate custom target because Xcode projects cannot have commands that + # depend on intermediate files from other commands + add_custom_target( + _generate-resolved-docbook-xml DEPENDS igraph-docs-with-resolved-includes.xml + COMMENT "Resolving includes in DocBook XML source" + ) + + if(PDF_DOC_BUILD_SUPPORTED) + add_custom_command( + OUTPUT igraph-docs.fo + COMMAND ${XSLTPROC_COMMAND} + ARGS + --output igraph-docs.fo + --stringparam paper.type A4 + http://docbook.sourceforge.net/release/xsl/current/fo/docbook.xsl + igraph-docs-with-resolved-includes.xml + DEPENDS _generate-resolved-docbook-xml + COMMENT "Converting DocBook XML to Apache FOP format" + ) + + add_custom_command( + OUTPUT igraph-docs.pdf + COMMAND ${FOP_COMMAND} + ARGS -fo igraph-docs.fo -pdf igraph-docs.pdf + MAIN_DEPENDENCY igraph-docs.fo + COMMENT "Generating PDF documentation with Apache FOP" + ) + + add_custom_target(pdf DEPENDS igraph-docs.pdf) + set(PDF_TARGET pdf) + endif() + + if(INFO_DOC_BUILD_SUPPORTED) + add_custom_command( + OUTPUT igraph-docs.texi + COMMAND ${DOCBOOK2XTEXI_COMMAND} + ARGS + --encoding=utf-8//TRANSLIT + --string-param output-file=igraph-docs + --string-param directory-category=Libraries + --string-param directory-description='A fast graph library \(C\)' + igraph-docs-with-resolved-includes.xml + DEPENDS _generate-resolved-docbook-xml + COMMENT "Converting DocBook XML to GNU Texinfo format" + ) + + add_custom_command( + OUTPUT igraph-docs.info + COMMAND ${MAKEINFO_COMMAND} + ARGS --no-split igraph-docs.texi + MAIN_DEPENDENCY igraph-docs.texi + COMMENT "Generating info documentation with GNU Makeinfo" + ) + + add_custom_target(info DEPENDS igraph-docs.info) + set(INFO_TARGET info) + endif() + + + add_custom_target(doc DEPENDS ${HTML_TARGET} ${PDF_TARGET} ${INFO_TARGET}) +endif() + +set(HTML_DOC_BUILD_SUPPORTED ${HTML_DOC_BUILD_SUPPORTED} PARENT_SCOPE) +set(PDF_DOC_BUILD_SUPPORTED ${PDF_DOC_BUILD_SUPPORTED} PARENT_SCOPE) +set(INFO_DOC_BUILD_SUPPORTED ${INFO_DOC_BUILD_SUPPORTED} PARENT_SCOPE) diff --git a/doc/adjlist.xxml b/doc/adjlist.xxml new file mode 100644 index 0000000..7e0717d --- /dev/null +++ b/doc/adjlist.xxml @@ -0,0 +1,51 @@ + + +]> + +
+Adjacency lists + + + +
Adjacent vertices + + + + + + + + + + +
+ +
Incident edges + + + + + +
+ +
Lazy adjacency list for vertices + + + + + + +
+ +
Lazy incidence list for edges + + + + + + +
+ +
diff --git a/doc/attributes.xxml b/doc/attributes.xxml new file mode 100644 index 0000000..b659686 --- /dev/null +++ b/doc/attributes.xxml @@ -0,0 +1,143 @@ + + +]> + + +Graph, vertex and edge attributes + + + +
+The attribute handler interface + + + + + +
+ +
+Attribute records + + + + + + + + + + + + +
+ +
+Handling attribute combination lists + + + + + + + +
+ +
+Accessing attributes from C + + +
Query attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+Set attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
Remove attributes + + + + + + + + + + + +
+ +
Custom attribute combination functions + + + +
+ +
+ +
diff --git a/doc/basicigraph.xxml b/doc/basicigraph.xxml new file mode 100644 index 0000000..137160c --- /dev/null +++ b/doc/basicigraph.xxml @@ -0,0 +1,241 @@ + + +]> + + +Basic data types and interface + +
The &igraph; data model + +The &igraph; library can handle directed and +undirected graphs. The &igraph; graphs are multisets +of ordered (if directed) or unordered (if undirected) labeled pairs. +The labels of the pairs plus the number of vertices always starts with +zero and ends with the number of edges minus one. In addition to that, +a table of metadata is also attached to every graph, its most +important entries being the number of vertices in the graph and whether +the graph is directed or undirected. + + + +Like the edges, the &igraph; vertices are also +labeled by numbers between zero and the number of vertices minus one. +So, to summarize, a directed graph can be imagined like this: + + + ( vertices: 6, + directed: yes, + { + (0,2), + (2,2), + (3,2), + (3,3), + (3,4), + (3,4), + (4,3), + (4,1) + } + ) + + +Here the edges are ordered pairs or vertex ids, and the graph is a multiset +of edges plus some metadata. + + + +An undirected graph is like this: + + + ( vertices: 6, + directed: no, + { + (0,2), + (2,2), + (2,3), + (3,3), + (3,4), + (3,4), + (3,4), + (1,4) + } + ) + + +Here, an edge is an unordered pair of two vertex IDs. A graph is a multiset +of edges plus metadata, just like in the directed case. + + +It is possible to convert between directed and undirected graphs, +see the +igraph_to_directed() +and +igraph_to_undirected() functions. + + +&igraph; aims to robustly support multigraphs, i.e. graphs which +have more than one edge between some pairs of vertices, as well as +graphs with self-loops. Most functions which do not support such graphs +will check their input and issue an error if it is not valid. Those +rare functions which do not perform this check clearly indicate this +in their documentation. To eliminate multiple edges from a graph, you can use + + igraph_simplify(). + +
+ +
General conventions of &igraph; functions + +&igraph; has a simple and consistent interface. Most functions check +their input for validity and display an informative error message +when something goes wrong. In order to support this, the majority of functions +return an error code. In basic usage, this code can be ignored, as the +default behaviour is to abort the program immediately upon error. See +the section on error handling for +more information on this topic. + + + +Results are typically returned through output arguments, +i.e. pointers to a data structure into which the result will be written. +In almost all cases, this data structure is expected to be pre-initialized. +A few simple functions communicate their result directly through their return +value—these functions can never encounter an error. + +
+ +
Atomic data types + +igraph_int_t + + +&igraph; introduces a few aliases to standard C data types that are then used +throughout the library. The most important of these types is +igraph_int_t, which is an alias to either a 32-bit or a 64-bit +signed integer, depending on whether &igraph; was compiled +in 32-bit or 64-bit mode. The size of igraph_int_t also +influences the maximum number of vertices that an &igraph; graph can represent +as the number of vertices is stored in a variable of type +igraph_int_t. + + + +Before igraph 1.0, igraph_int_t was called igraph_integer_t. +This is still available as an alias to igraph_int_t and will remain +accessible until at least version 2.0 of the library. + + +Since the size of a variable of type igraph_int_t may +change depending on how &igraph; is compiled, you cannot simply use +%d or %ld as a placeholder for &igraph; integers in +printf format strings. &igraph; provides the +IGRAPH_PRId macro, which maps to d, ld +or lld depending on the size of igraph_int_t, and +you must use this macro in printf format strings to avoid compiler +warnings. + + +igraph_uint_t + +Similarly to how igraph_int_t maps to the standard size +signed integer in the library, igraph_uint_t maps to a 32-bit or +a 64-bit unsigned integer. It is guaranteed that the size of +igraph_int_t is the same as the size of igraph_uint_t. +&igraph; provides IGRAPH_PRIu as a format string placeholder for +variables of type igraph_uint_t. + + +igraph_real_t + +Real numbers (i.e. quantities that can potentially be fractional or +infinite) are represented with a type named igraph_real_t. Currently +igraph_real_t is always aliased to double, but it is +still good practice to use igraph_real_t in your own code for sake +of consistency. + +igraph_bool_t + +Boolean values are represented with a type named igraph_bool_t. +It tries to be as small as possible since it only needs to represent a truth +value. For printing purposes, you can treat it as an integer and use +%d in format strings as a placeholder for an igraph_bool_t. + + +IGRAPH_INTEGER_MAX +IGRAPH_INTEGER_MIN +IGRAPH_UINT_MAX +IGRAPH_UINT_MIN + +Upper and lower limits of igraph_int_t and +igraph_uint_t are provided by the constants named +IGRAPH_INTEGER_MIN, IGRAPH_INTEGER_MAX, +IGRAPH_UINT_MIN and IGRAPH_UINT_MAX. + + +
+ +
Setup and initialization + +Certain parts of &igraph; must be initialized before first use, which can be +accomplished using the setup functions below. As of igraph 1.0, most functions +will work correctly even if setup is not performed, as currently the only setup +action is seeding the random number generator. That said, it is strongly +recommended to call +igraph_setup() +before using any other function, as future &igraph; versions may add critical +initialization steps. + + + +
+ +
The basic interface + + +
Graph constructors and destructors + + + + +
+ +
Basic query operations + + + + + + + + + + + + + + + +
+ +
Adding and deleting vertices and edges + + + + + + +
+ +
+ +
Miscellaneous macros and helper functions + + + + + + +
+ +
diff --git a/doc/bibdatabase.xml b/doc/bibdatabase.xml new file mode 100644 index 0000000..1479cd4 --- /dev/null +++ b/doc/bibdatabase.xml @@ -0,0 +1,51 @@ + + + + + + + + Albert-László + Barabási + RékaAlbert + + Emergence of scaling in random networks + Science + 1999 + 286 + 509-512 + + + + + LászlóZalányi + GáborCsárdi + TamásKiss + MátéLengyel + RebeccaWarner + JanTobochnik + PéterÉrdi + + Properties of a random attachment growing network + Phyisical Review E + 2003 + 68 + 066104 + + + + + L. R.Ford Jr. + D. R.Fulkerson + + Maximal ow through a network + Canadian J. Math. + 1956 + 8 + 399--404 + + + + diff --git a/doc/bipartite.xxml b/doc/bipartite.xxml new file mode 100644 index 0000000..f836156 --- /dev/null +++ b/doc/bipartite.xxml @@ -0,0 +1,37 @@ + + +]> + + +Bipartite, i.e. two-mode graphs + +
+ +
+ +
Create two-mode networks + + + + + +
+ +
Bipartite adjacency matrices + + + +
+ +
Project two-mode graphs + + +
+ +
Other operations on bipartite graphs + +
+ +
diff --git a/doc/bitset.xxml b/doc/bitset.xxml new file mode 100644 index 0000000..b29fa22 --- /dev/null +++ b/doc/bitset.xxml @@ -0,0 +1,63 @@ + + +]> + +
+Bitsets + +
+ +
+ +
+ + + + +
+ +
+ + + + + + + +
+ +
Bitset operations + + + + + + + + + + + + + + + +
+ +
Bitset properties + + +
+ +
Resizing operations + + +
+ +
Copying bitsets + +
+ +
diff --git a/doc/c-docbook.re b/doc/c-docbook.re new file mode 100644 index 0000000..dacf1a4 --- /dev/null +++ b/doc/c-docbook.re @@ -0,0 +1,735 @@ +REPLACE ----- remove the " * " prefix first -----------------*- mode:python -*- +^[ ]\*[ ] +WITH -------------------------------------------------------------------------- +REPLACE ----- remove the " *" lines ------------------------------------------- +^[ ]\*\s*\n +WITH -------------------------------------------------------------------------- +\n +REPLACE IN typed_list.pmt ----- for the typed list template functions --------- + +FUNCTION\( +(?P[^\)]*) +\)\s* + +WITH + +igraph_vector_list_\g + +REPLACE IN typed_list.pmt ----- typed list template item type ----------------- + +ITEM_TYPE + +WITH + +igraph_vector_t + +REPLACE IN typed_list.pmt ----- typed list template type ---------------------- + +TYPE + +WITH + +igraph_vector_list_t + +REPLACE IN *.pmt ----- for the template functions ----------------------------- + +FUNCTION\( +(?P[^, \)]*)\s*,\s* +(?P[^\)]*) +\)\s* + +WITH + +\g_\g + +REPLACE IN *.pmt ----- template type ------------------------------------------ + +TYPE\( +(?P[^\)]*) +\) + +WITH + +\g_t + +REPLACE IN *.pmt ----- template base type, we cowardly assume real number ----- + +BASE + +WITH + +igraph_real_t + +REPLACE ----- function object, extract its signature -------------------------- + +(?P\A.*?) # head of the comment +\\function\s+ # \function keyword +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+)) # the keyword, remove igraph_ prefix
+[\s]*(?P[^\n]*?)\n        # brief description
+(?P.*?)\*\/               # tail of the comment
+\s*
+(IGRAPH_EXPORT\s+)?              # strip IGRAPH_EXPORT from prototype
+(?P.*?\))                   # function head
+(?=(\s*;)|(\s*\{))               # prototype ends with ; function head with {
+.*\Z                             # and the remainder
+
+WITH --------------------------------------------------------------------------
+
+
+<function>\g<name></function> — \g<brief> +\g + + +\g; + + + +\g +\g + +
+ +REPLACE ----- for functions (not used currently) ------------------- + +(?P[^<]*)\n + +RUN --------------------------------------------------------------------------- + +dr_params=string.split(matched.group("params"), ',') +dr_out="" +for dr_i in dr_params: + dr_i=string.strip(dr_i) + if dr_i=="...": + dr_out=dr_out+"" + else: + dr_words=re.match(r"([\w\*\&\s]+)(\b\w+)$", dr_i).groups() + dr_out=dr_out+""+dr_words[0]+""+dr_words[1]+ \ + "\n" +actch=actch[0:matched.start()]+dr_out+actch[matched.end():] + +REPLACE ----- function parameter descriptions, head --------------------------- + +(?P\A.*?) # head of the comment +\\param\b # first \param commant + +WITH -------------------------------------------------------------------------- + +\g +Arguments: + +\\param + +REPLACE ----- function parameter descriptions, tail --------------------------- + +# the end of the params is either an empty line after the last \param +# command or a \return or \sa statement (others might be added later) +# or the end of the comment + +\\param\b # the last \param command +(?P.*?) # the text of the \param command +(?P # this marks the end of the \param text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (\n\s*?\n)| # (at least) one empty line or + (\*\/)) # the end of the comment +(?P.*?\Z) # remaining part + +WITH + +\\param\g +\g\g + +REPLACE ----- function parameter descriptions --------------------------------- + +\\param\b\s* # \param command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \param command +(?=(\\param)|()| + (\n\s*\n)) + + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \return command ------------------------------------------------- + +# a return statement ends with an empty line or the end of the comment +\\return\b\s* # \return command +(?P.*?) # the text +(?=(\n\s*?\n)| # empty line or + (\*\/)| # the end of the comment or + (\\sa\b)) # \sa command + +WITH ----------------------------------------------------------------------TODO + +Returns: + + + \g + + + +REPLACE ----- variables ------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\var\s+ # \var keyword + argument +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)\*\/                # tail of the comment
+\s*
+(IGRAPH_EXPORT\s+)?               # strip IGRAPH_EXPORT
+(?P[^;]*;)                   # the definition of the variable
+.*\Z                              # and the remainder
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + +\g\g + +
+ +REPLACE ----- \define --------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\define\s+ # \define command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)\*\/                # tail of the comment
+\s*                               # whitespace
+(?P\#define\s+[\w0-9,]+\s*   # macro name
+(\([\w0-9,. ]+\))?)               # macro args (optional)
+.*\Z                              # drop the remainder
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + +\g\g + +
+ +REPLACE ----- \section without title ------------------------------------------ + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+)\s*$ # \section + argument +(?P.*?)\*\/ # tail of the comment +.*\Z # and the remainder, this is dropped + +WITH + +\g +\g + +REPLACE ----- \section with title --------------------------------------------- + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+) # \section + argument +(?P.*?) # section title +\n\s*?\n # empty line +(?P<after>.*?)\*\/ # tail of the comment +.*\Z # and the remainder, this is dropped + +WITH + +<title>\g<title> +\g +\g + +REPLACE ----- \section with title --------------------------------------------- + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+) # \section + argument +(?P.*?)\s*\*\/ # section title +.*\Z # and the remainder, this is dropped + +WITH + +<title>\g<title> +\g + +REPLACE ----- an enumeration typedef ------------------------------------------ + +(?P\A.*?) # head of the comment +\\typedef\s+ # \typedef command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)                    # tail of the comment
+ \*\/\s*                          # closing the comment
+(?Ptypedef\s*enum\s*\{       # typedef enum
+ [^\}]*\}\s*\w+\s*;)                  # rest of the definition
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + + +\g\g + +
+ +REPLACE ----- enumeration value descriptions, head ---------------------------- + +(?P\A.*?) # head of the comment +\\enumval\b # first \param commant + +WITH -------------------------------------------------------------------------- + +\g +Values: + +\\enumval + +REPLACE ----- enumeration value descriptions, tail ---------------------------- + +\\enumval\b # the last \enumval command +(?P.*?) # the text of the \enumval command +(?P # this marks the end of the \enumval text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (\n\s*?\n)| # (at least) one empty line or + (\*\/)) # the end of the comment +(?P.*?\Z) # remaining part + +WITH + +\\enumval\g +\g\g + +REPLACE ----- enumeration value descriptions ---------------------------------- + +\\enumval\b\s* # \enumval command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \enumval command +(?=(\\enumval)|()| + (\n\s*\n)) + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \struct --------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\struct\s+ # \struct command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P[\w_]+))
+[\s]*(?P[^\n]*?)(?=\n)     # brief description
+(?P.*?)                    # tail of the command
+\*\/\s*                           # closing the comment
+(?Ptypedef \s*struct\s*\w+\s*\{
+ .*\}\s*\w+\s*;)
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + + +\g\g + +
+ +REPLACE IN *.h ----- structure member descriptions, one block ----------------- + +^[\s]*\n +(?P.*?) # empty line+text +(?P\\member\b.*?) # member commands +(?= # this marks the end of the \member text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (^[\s]*\n)| # (at least) one empty line or + (\*\/)) # the end of the comment + +WITH -------------------------------------------------------------------------- + + +\g +Values: + +\g + + +REPLACE IN *.h ----- structure member descriptions ---------------------------- + +\\member\b\s* # \enumval command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \enumval command +(?=(\\member)|()| + (\n\s*\n)) + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \typedef function ----------------------------------------------- + +(?P\A.*?) # comment head +\\typedef\s+ # \typedef command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)                    # comment tail
+\*\/                              # end of comment block
+\s*
+(?Ptypedef\s+[^;]*;)         # the typedef definition
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + +\g + + +\g\g + +
+ +REPLACE ----- ignore doxygen \ingroup command --------------------------------- + +\\ingroup\s+\w+ + +WITH -------------------------------------------------------------------------- + +REPLACE ----- ignore doxygen \defgroup command -------------------------------- + +\\defgroup\s+\w+ + +WITH -------------------------------------------------------------------------- + +REPLACE ----- add the contents of \brief to the description ------------------- + +\\brief\b + +WITH -------------------------------------------------------------------------- + +REPLACE ----- \varname command ------------------------------------------------ + +\\varname\b\s* +(?P\w+\b) + +WITH + +\g + +REPLACE ----- references, \ref command, special case for igraph_vector_int ---- + +\\ref\b\s* +igraph_vector_int_(?P\w+)(?P([\(][\)])?) + +WITH -------------------------------------------------------------------------- + +igraph_vector_int_\g\g + +REPLACE ----- references, \ref command ---------------------------------------- + +\\ref\b\s* +(?P\w+)(?P([\(][\)])?) + +WITH -------------------------------------------------------------------------- + +\g\g + +REPLACE ----- \sa command ----------------------------------------------------- + +\\sa\b +\s* +(?P.*?) +(?=(\n\s*?\n)|(\*\/)) + +WITH ----------------------------------------------------------------------TODO + +See also: + + + \g + + + +REPLACE ----- \em command ----------------------------------------------------- + +\\em\b +\s* +(?P[^\s]+) + +WITH + +\g + +REPLACE ----- \emb command ---------------------------------------------------- + +\\emb\b + +WITH + + + +REPLACE ----- \eme command ---------------------------------------------------- + +\\eme\b + +WITH + + + +REPLACE ----- \verbatim ------------------------------------------------------- + +\\verbatim\b + +WITH + + + +REPLACE ----- \endverbatim ---------------------------------------------------- + +\\endverbatim\b + +WITH + + + +REPLACE ----- \clist ---------------------------------------------------------- + +\\clist\b + +WITH + + + +REPLACE ----- \cli ------------------------------------------------------------ + +\\cli\s+(?P.*?)$ +(?P.*?) +(?=(\\cli)|(\\endclist)) + +WITH -------------------------------------------------------------------------- + +\g + +\g + + +REPLACE ----- \endclist ------------------------------------------------------- + +\\endclist\b + +WITH + + + +REPLACE ----- \olist ---------------------------------------------------------- + +\\olist\b + +WITH + + + +REPLACE ----- \oli ------------------------------------------------------------ + +\\oli\s+(?P.*?) +(?=(\\oli)|(\\endolist)) + +WITH + + +\g + + +REPLACE ----- \endolist ------------------------------------------------------- + +\\endolist\b + +WITH + + + +REPLACE ----- \ilist ---------------------------------------------------------- + +\\ilist\b + +WITH + + + +REPLACE ----- \ili ------------------------------------------------------------ + +\\ili\s+(?P.*?) +(?=(\\ili)|(\\endilist)) + +WITH + + +\g + + +REPLACE ----- \endilist ------------------------------------------------------- + +\\endilist\b + +WITH + + + +REPLACE ----- doxygen \c command is for ---------------------------- + +\\c\s+(?P[\w\-^\']+)\b + +WITH + +\g + +REPLACE ----- doxygen \p command is for --------------------------- + +\\p\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \type command is for ----------------------------- + +\\type\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \a command is for ----------------------------- + +\\a\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \quote command is for --------------------------- + +\\quote\s+ + +WITH + + + +REPLACE ----- doxygen \endquote command is for ----------------------- + +\s*\\endquote\b + +WITH + + + +REPLACE ----- replace with ----------------------------------- + +<(?P/?)code> + +WITH -------------------------------------------------------------------------- + +<\gliteral> + +REPLACE ----- add http:// and https:// links ---------------------------------- + +(?Phttps?:\/\/[-\+=&;%@.:/~()?'\w_]*[-\+=&;%@/~'\w_]) + +WITH -------------------------------------------------------------------------- + +\g + +REPLACE ----- blockquote ------------------------------------------------------ + +\\blockquote + +WITH -------------------------------------------------------------------------- + +
+ +REPLACE ----- blockquote ------------------------------------------------------ + +\\endblockquote + +WITH -------------------------------------------------------------------------- + +
+ +REPLACE ----- example file --------------------------------------------------- + +\\example\b\s* +(?P[^\n]*?)\n + +WITH -------------------------------------------------------------------------- + + + File <code>\g<filename></code> + + + + +REPLACE ----- \deprecated-by -------------------------------------------------- + +\\deprecated-by\b\s* +(?P[^ \n]+)\s* +(?P[^\n]+)\n + +WITH -------------------------------------------------------------------------- + +
+ +Deprecated since version \g. Please do not use this function in new +code; use \g() +instead. + + + +REPLACE ----- \deprecated ----------------------------------------------------- + +\\deprecated\b\s* +(?P[^\n]*?)\n + +WITH -------------------------------------------------------------------------- + + + +Deprecated since version \g. Please do not use this function in new +code. + + + +REPLACE ----- \experimental --------------------------------------------------- + +\\experimental\b\s*\n + +WITH -------------------------------------------------------------------------- + + + +This function is experimental and its signature is not considered final yet. +We reserve the right to change the function signature without changing the +major version of igraph. Use it at your own risk. + + diff --git a/doc/cliques.xxml b/doc/cliques.xxml new file mode 100644 index 0000000..22e8b74 --- /dev/null +++ b/doc/cliques.xxml @@ -0,0 +1,45 @@ + +]> + + +Cliques and independent vertex sets + + +These functions calculate various graph properties related +to cliques and independent vertex sets. + + +
Cliques + + + + + + + + + + + + + + +
+ +
Weighted cliques + + + +
+ +
Independent vertex sets + + + + + +
+ +
diff --git a/doc/coloring.xxml b/doc/coloring.xxml new file mode 100644 index 0000000..212b759 --- /dev/null +++ b/doc/coloring.xxml @@ -0,0 +1,19 @@ + + +]> + + +Graph coloring + + + + + + + + + + + diff --git a/doc/community.xxml b/doc/community.xxml new file mode 100644 index 0000000..50f853b --- /dev/null +++ b/doc/community.xxml @@ -0,0 +1,66 @@ + + +]> + + +Detecting community structure + + + + + +
Community structure based on statistical mechanics + + +
+ +
Community structure based on eigenvectors of matrices + + + + +
+ +
Walktrap: Community structure based on random walks + +
+ +
Edge betweenness based community detection + + +
+ +
Community structure based on the optimization of modularity + + + + +
+ +
Fluid communities + +
+ +
Label propagation + +
+ +
The InfoMAP algorithm + +
+ +
Voronoi communities + +
+ +
diff --git a/doc/cycles.xxml b/doc/cycles.xxml new file mode 100644 index 0000000..02b3196 --- /dev/null +++ b/doc/cycles.xxml @@ -0,0 +1,37 @@ + + +]> + + +Graph cycles + +
Finding cycles + + + + +
+ +
Acyclic graphs and feedback sets + + + + + +
+ +
Eulerian cycles and paths + + + + +
+ +
Cycle bases + + +
+ +
diff --git a/doc/docbook.outlang b/doc/docbook.outlang new file mode 100644 index 0000000..44e14af --- /dev/null +++ b/doc/docbook.outlang @@ -0,0 +1,36 @@ +# by Stuart Rackham +# http://www.methods.co.nz/asciidoc/source-highlight-filter.html + +extension "xml" + +bold "$text" +italics "$text" + +anchor "$text" +postline_reference "$text -> $linenum" +postdoc_reference "$text -> $linenum" +reference "$text" + +doctemplate +" +
+ +$title + +" +" +
+" +end + +nodoctemplate +"" +" +" +end + +translations +"&" "&" +"<" "<" +">" ">" +end diff --git a/doc/doxrox.py b/doc/doxrox.py new file mode 100755 index 0000000..753a018 --- /dev/null +++ b/doc/doxrox.py @@ -0,0 +1,567 @@ +#! /usr/bin/env python3 + +# igraph library +# Copyright (C) 2005-2021 The igraph development team +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +################################################################### + +"""DocBook XML generator for igraph. + +The generator parses one or more input files for documentation chunks +(embedded in the source code as Doxygen-style comments), and processes +them with a set of regex-based rules. The processed chunks are then +substituted into a template file containing +directives. + +When a template file is not provided, the generator will read the input +files, process them with the ruleset and save a dictionary mapping chunk +names to the corresponding processed chunks into a Python pickle. This +can be used to speed up the processing of multiple input files as you can +generate the chunks once and then re-use them for multiple input files. +""" + +import os +import re +import sys + +from argparse import ArgumentParser +from collections import defaultdict +from contextlib import contextmanager +from dataclasses import dataclass +from enum import Enum +from fnmatch import fnmatch +from hashlib import sha1 +from operator import itemgetter +from pathlib import Path +from pickle import dump, load +from time import time +from typing import Any, Callable, Dict, Iterator, List, Optional, Pattern + +#: Constant indicating the start of a comment that doxrox.py will process +DOXHEAD: str = r"/\*\*" + +#: Stores whether we want verbose output +verbose: bool = False + + +def fatal(message: str, code: int = 1): + """Prints an error message and exits the program with the given error code.""" + print(message, file=sys.stderr) + sys.exit(code) + + +######################################################################### +# The main function +######################################################################### + + +def main(): + """Main entry point of the script.""" + + global verbose + + # get command line arguments + parser = create_argument_parser() + arguments = parser.parse_args() + + outputfile: str = arguments.output_file + inputs: List[str] = arguments.inputs + + verbose = arguments.verbose + + if ( + arguments.template_file in inputs + or arguments.rules_file in inputs + or outputfile in inputs + ): + fatal("Special file is also used as an input file", 2) + + # open the cache file if needed + cache = ChunkCache(arguments.cache_file) if arguments.cache_file else None + + # get all regular expressions + rules: List[Rule] + if arguments.rules_file: + with operation("Reading regular expressions...") as op: + rules = read_regex_rules_file(arguments.rules_file) + op("{0} rules read".format(len(rules))) + else: + rules = [] + + # parse all input files and extract chunks, apply rules + if arguments.chunk_file: + with operation("Reading pickled chunks...") as op: + try: + with open(arguments.chunk_file, "rb") as f: + all_chunks = load(f) + except IOError: + fatal("Error reading chunk file: " + arguments.chunk_file, 9) + op("{0} chunks read".format(len(all_chunks))) + else: + all_chunks = {} + + rule_timings = defaultdict(list) + + for ifile in inputs: + with operation("Parsing input file {0}...".format(ifile)) as op: + try: + with open(ifile, "r") as f: + contents = f.read() + except IOError: + fatal("Error reading input file: " + ifile, 3) + + if cache: + key = cache.key_of(contents) + chunks = cache.get(key) + else: + key, chunks = None, None + + if chunks is not None: + op("{0} chunks read from cache".format(len(chunks))) + else: + chunks = collect_chunks_from_input_file( + ifile, contents, rules, rule_timings + ) + op("{0} chunks parsed".format(len(chunks))) + if key and cache: + cache.put(key, chunks) + + for name, chunk in chunks.items(): + if name in all_chunks: + fatal( + "Multiple files provide chunks for {0!r}".format(name), code=4 + ) + all_chunks[name] = chunk + + if arguments.timing_stats and rule_timings: + rule_timings = {name: sum(dts) / len(dts) for name, dts in rule_timings.items()} + for name, dt in sorted(rule_timings.items(), key=itemgetter(1), reverse=True): + print("{0}: {1:.3f}us".format(name, dt)) + print("======") + + if cache: + cache.close() + + if arguments.template_file: + # substitute the template file + with operation("Reading template file..."): + try: + with open(arguments.template_file, "r") as tfile: + tstring = tfile.read() + except IOError: + fatal("Error reading the template file: " + arguments.template_file, 7) + + with operation("Substituting template file..."): + chunk_iterator = re.finditer( + r"", tstring + ) + outstring = [] + last = 0 + for match in chunk_iterator: + try: + chunk = all_chunks[match.group(1)] + except KeyError: + fatal("Chunk not found: {0}".format(match.group(1)), code=4) + outstring.append(tstring[last : match.start()]) + outstring.append(chunk) + last = match.end() + outstring.append(tstring[last:]) + outstring = "".join(outstring) + + # write output file + with operation("Writing output file..."): + try: + with open(outputfile, "w") as ofile: + ofile.write(outstring) + except IOError: + fatal("Error writing output file:" + outputfile, 8) + else: + # no template file given so just save the chunks as a pickle into the + # output file + with operation("Writing output file..."): + try: + with open(outputfile, "wb") as ofile: + dump(all_chunks, ofile) + except IOError: + fatal("Error writing output file:" + outputfile, 5) + + +######################################################################### +# Argument parser +######################################################################### + + +def create_argument_parser() -> ArgumentParser: + """Creates the command line argument parser that the script uses.""" + parser = ArgumentParser(description=(sys.modules[__name__].__doc__ or "").strip()) + + parser.add_argument( + "--cache", + metavar="FILE", + dest="cache_file", + help="optional cache file to store chunks from already processed files", + ) + parser.add_argument( + "-t", + "--template", + metavar="FILE", + dest="template_file", + help="template file to process", + ) + parser.add_argument( + "-e", + "--rules", + metavar="FILE", + dest="rules_file", + help="file containing matching and replacement rules", + ) + parser.add_argument( + "-o", + "--output", + metavar="FILE", + dest="output_file", + required=True, + help="name of the output file", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="enable verbose output", + ) + parser.add_argument( + "--chunks", + dest="chunk_file", + metavar="FILE", + help="name of a previously saved chunk file", + ) + parser.add_argument( + "--timing-stats", + dest="timing_stats", + action="store_true", + default=False, + help="print the average time it takes to process regex rules from the rules file", + ) + parser.add_argument( + "inputs", metavar="INPUT", nargs="*", help="input files to process" + ) + + return parser + + +################# +# classes and functions to read the regular expression rules +################# + + +class RuleType(Enum): + REPLACE = "replace" + RUN = "run" + + +@dataclass +class Rule: + regex: Pattern[str] + """The regular expression that the rule will attempt to match.""" + + replacement: str + """The replacement string for the match, or the code to execute on the + match. + """ + + type: RuleType + """Type of the rule""" + + name: Optional[str] + """Name of the rule, for debugging purposes.""" + + glob: Optional[str] = None + """Optional glob pattern that specifies which input files the rule + applies to. + """ + + def applies_to_filename(self, filename: str) -> bool: + """Returns whether the rule applies to files with the given name.""" + if self.glob: + return fnmatch(filename, self.glob) + else: + return True + + +def read_regex_rules_file(filename) -> List[Rule]: + """Parses the file containing the regex-based rules that we use to chop + up the input source files into chunks that can later be fed into a + DocBook document. + + Parameters: + filename: name of the input file + + Returns: + the rules that were parsed from the input file + """ + + rules: List[Rule] = [] + + def parse_error(lineno): + """Helper function to indicate a parse error at the given line.""" + fatal( + "Parse error in regex file ({0}), line {1}".format(filename, lineno), code=4 + ) + + def store( + rule: List[str], + replacement: List[str], + rule_name: Optional[str], + rule_type: RuleType, + glob: Optional[str], + ) -> None: + """Helper function to append the current rule to the result.""" + regex = re.compile("".join(rule), re.VERBOSE | re.MULTILINE | re.DOTALL) + replacement_str = "".join(replacement)[:-1] + rules.append(Rule(regex, replacement_str, rule_type, rule_name, glob)) + + mode = "empty" + regex, replacement = [], [] + rule_name: Optional[str] = None + rule_type: Optional[RuleType] = None + glob: Optional[str] = None + + try: + with open(filename, "r") as f: + for lineno, line in enumerate(f, 1): + if line.startswith("REPLACE"): + # a new pattern block starts + if mode not in ("empty", "with"): + parse_error(lineno) + else: + if regex and rule_type: + store(regex, replacement, rule_name, rule_type, glob) + regex.clear() + replacement.clear() + mode = "replace" + + match = re.match( + r"^REPLACE( IN (?P[^\s]+))?\s+-+\s+(?P.*)\s+-", + line, + ) + rule_name = match.group("name") if match else None + glob = match.group("glob") if match else None + + elif line.startswith("WITH") or line.startswith("RUN"): + # the second half of the pattern block starts + if mode != "replace": + parse_error(lineno) + else: + mode = "with" + rule_type = ( + RuleType.REPLACE if line.startswith("WITH") else RuleType.RUN + ) + + elif re.match(r"^\s*$", line): + # empty line, do nothing + pass + + else: + # normal line, append + if mode == "replace": + regex.append(line) + elif mode == "with": + replacement.append(line) + else: + parse_error(lineno) + + if regex != "" and rule_type: + store(regex, replacement, rule_name, rule_type, glob) + + except IOError: + fatal("Error reading regex file: " + filename, code=4) + + return rules + + +################# +# parse an input file string +################# +def collect_chunks_from_input_file( + path: str, strinput: str, rules: List[Rule], rule_timings +) -> Dict[str, str]: + result: Dict[str, str] = {} + + # split the file + chunks = re.split(DOXHEAD, strinput) + chunks = chunks[1:] + + # get the filename part of the path + filename = os.path.basename(path) + + # apply all rules to the chunks + for chunk in chunks: + name: Optional[str] = None + + for rule in rules: + start = time() + + if not name and "name" in rule.regex.groupindex: + # The regex might provide us with a chunk name so try figuring + # out what the "name" group might match to + matched = rule.regex.search(chunk) + if matched: + try: + name = matched.group("name") + except IndexError: + name = "" + + if rule.applies_to_filename(filename): + if rule.type is RuleType.REPLACE: + # This is a simple regex replacement rule + try: + chunk = rule.regex.sub(rule.replacement, chunk) + except IndexError: + print("Index error:" + chunk[0:60] + "...") + print("Pattern:\n" + rule.regex.pattern) + print("Current state:" + chunk[0:60] + "...") + fatal("Parsing error", code=6) + elif rule.type is RuleType.RUN: + # This is a piece of Python code that has to be executed on + # the part that matched + matched = rule.regex.search(chunk) + if matched: + exec(rule.replacement) + else: + fatal("Invalid rule type: {0!r}".format(rule.type), code=6) + + rule_timings[rule.name].append((time() - start) * 1000000) + + if not name: + # print("Chunk without a name ignored:" + ch[0:60] + "...") + continue + + result[name] = chunk.strip() + + return result + + +@contextmanager +def operation(message: str) -> Iterator[Callable[[Any], None]]: + """Helper function to show progress messages for a potentially long-running + operation in verbose mode. + + Parameters: + message (str): the message to show + """ + global verbose + + if verbose: + print(message, end="") + + result = [None] + + def set_result(obj: Any) -> None: + result[0] = obj + + success = False + try: + yield set_result + success = True + finally: + if verbose and success: + if result[0] is None: + print(" done.") + else: + print(" done, {0}.".format(result[0])) + + +class ChunkCache: + """Simple on-disk cache that stores SHA256 hashes of files along with the + DocBook documentation chunks that were parsed from them. + """ + + _data: Optional[Dict[str, Dict[str, str]]] + _dirty: bool + _path: Path + + def __init__(self, filename: str, hash=sha1): + """Constructor. + + Parameters: + filename: name of the file on the disk where the cache resides + hash: the hash function to use + """ + self._data = None + self._dirty = False + self._hash = hash + self._path = Path(filename) + + def _load(self) -> None: + """Populates the in-memory copy of the cache from the disk.""" + if self._path.exists(): + try: + with self._path.open("rb") as fp: + self._data = load(fp) + except (IOError, EOFError): + # cache corrupted + self._data = {} + else: + self._data = {} + self._dirty = False + + def close(self) -> None: + """Closes the cache and flushes its contents back to the disk if it + changed recently. + """ + if self._dirty: + self.flush() + + def flush(self) -> None: + """Flushes the contents of the cache back to the disk.""" + with self._path.open("wb") as fp: + dump(self._data, fp) + self._dirty = False + + def get(self, key: str) -> Optional[Dict[str, str]]: + """Returns the chunks associated to the file with the given key, or + `None` if the key is not in the cache. + """ + if self._data is None: + self._load() + + assert self._data is not None + return self._data.get(key) + + def key_of(self, contents, encoding: str = "utf-8") -> str: + """Returns the hash key corresponding to the file with the given + contents. + """ + if not isinstance(contents, bytes): + contents = contents.encode(encoding) + + key = self._hash() + key.update(contents) + return key.hexdigest() + + def put(self, key: str, chunks: Dict[str, str]) -> None: + """Stores some chunks associated to the file with the given key.""" + assert self._data is not None + self._data[key] = chunks + self._dirty = True + + +if __name__ == "__main__": + main() diff --git a/doc/dqueue.xxml b/doc/dqueue.xxml new file mode 100644 index 0000000..4a39443 --- /dev/null +++ b/doc/dqueue.xxml @@ -0,0 +1,24 @@ + + +]> + +
+Double-ended queues + + + + + + + + + + + + + + + +
diff --git a/doc/embedding.xxml b/doc/embedding.xxml new file mode 100644 index 0000000..96af489 --- /dev/null +++ b/doc/embedding.xxml @@ -0,0 +1,16 @@ + + +]> + + +Embedding of graphs + +
Spectral embedding + + + +
+ +
diff --git a/doc/error.xxml b/doc/error.xxml new file mode 100644 index 0000000..184fdac --- /dev/null +++ b/doc/error.xxml @@ -0,0 +1,88 @@ + + +]> + + +Error handling + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ + + + + + + + + +
+ +
+Advanced topics + +
+ + +
+ +
+ + + + + + + +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + + + + + + +
+ +
+ +
+ +
+ +
diff --git a/doc/fdl.xml b/doc/fdl.xml new file mode 100644 index 0000000..8fbd700 --- /dev/null +++ b/doc/fdl.xml @@ -0,0 +1,420 @@ + + +
+ + Version 1.2, November 2002 + 200020012002 + Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + + +The GNU Free Documentation License + +
0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +
1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +
2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +
3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +
4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + + + + Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. + + List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. + + State on the Title page the name of the publisher of the + Modified Version, as the publisher. + + Preserve all the copyright notices of the Document. + + Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. + + Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. + + Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. + + Include an unaltered copy of this License. + + Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. + + Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. + + For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. + + Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. + + Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. + + Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. + + Preserve any Warranty Disclaimers. + + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +
5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + +
6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +
7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +
8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +
9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +
10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + +
G.1.1 ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +
+
diff --git a/doc/flows.xxml b/doc/flows.xxml new file mode 100644 index 0000000..293ff71 --- /dev/null +++ b/doc/flows.xxml @@ -0,0 +1,48 @@ + + +]> + + +Maximum flows, minimum cuts and related measures + +
Maximum flows + + + + +
+ +
Cuts and minimum cuts + + + + + + + +
+ +
Connectivity + + + + +
+ +
Edge- and vertex-disjoint paths + + +
+ +
Graph adhesion and cohesion + + +
+ +
Cohesive blocks + +
+ +
diff --git a/doc/foreign.xxml b/doc/foreign.xxml new file mode 100644 index 0000000..249a9bb --- /dev/null +++ b/doc/foreign.xxml @@ -0,0 +1,59 @@ + + +]> + + +Reading and writing graphs from and to files + + + +
Simple edge list and similar formats + + + + + + + + +
+ +
Binary formats + +
+ +
GraphML format + + +
+ +
GML format + + +
+ +
Pajek format + + +
+ +
UCINET's DL file format + +
+ +
Graphviz format + +
+ +
LEDA format + +
+ +
Convenience functions for locale change + + +
+ +
diff --git a/doc/games.xxml b/doc/games.xxml new file mode 100644 index 0000000..efe5f8a --- /dev/null +++ b/doc/games.xxml @@ -0,0 +1,85 @@ + + +]> + + +Stochastic graph generators ("games") + +"Games" are random graph generators, i.e. they generate a different +graph every time they are called. igraph includes many such generators. +Some implement stochastic graph construction processes inspired by real-world +mechanics, such as preferential attachment, while others are designed to +produce graphs with certain used properties (e.g. fixed number of edges, +fixed degrees, etc.) + +
The Erdős-Rényi and related models + + + + + + + + + + + + +
+ +
Preferential attachment and related models +Preferential attachment models are growing random graphs where vertices are added iteratively, +and connected to previously added vertices based on dynamically changing vertex properties, such as +degree or time since the vertex was added. + + + + + + +
+ +
Growing random graph models +In growing random graphs, vertices are added iteratively, and connected based on various rules. +Preferential attachment models are documented in their +own section. + + + + + + + + +
+ +
Degree-constrained models +Random graph models with hard or soft degree constraints. + + + + + + +
+ +
Edge rewiring models + + + +
+ +
Other random graphs + + + + +
+ +
Common types and constants + +
+ +
diff --git a/doc/generators.xxml b/doc/generators.xxml new file mode 100644 index 0000000..5056af3 --- /dev/null +++ b/doc/generators.xxml @@ -0,0 +1,93 @@ + + +]> + + +Deterministic graph generators + +
About generators + +Most functions that create graphs in a deterministic manner are documented here. See also +stochastic generators, +spatial graph generators, +bipartite graph generators, +and operators that transform graphs. + +
+ +
Basic graph creation + + +
+ +
Graphs from adjacency matrices and adjacency lists +These functions create graphs from weighted or unweighted adjacency matrices, or an adjacency list. + + + + + + +
+ +
Regular structures +These functions produce various basic regular graph structures, such as paths, cycles or lattices. + + + + + + + + + + + + + + +
+ +
Tree generators +These functions generate tree graphs. + + + + + + +
+ +
Graphs with given degrees +These functions generate graphs with the specified degrees. + + + +
+ +
Complete graphs +These functions produce single and multipartite complete graphs, as well as related graphs. + + + + + +
+ +
Pre-defined graphs +These functions return graphs from various graph collections. + + + +
+ +
Other well-known graphs from graph theory + + + + +
+ +
diff --git a/doc/glossary.md b/doc/glossary.md new file mode 100644 index 0000000..3468789 --- /dev/null +++ b/doc/glossary.md @@ -0,0 +1,32 @@ + + +# Glossary + +This glossary defines common terms used throughout the igraph documentation. + + - **attribute**: A piece of data associated with a vertex, an edge, or the graph itself. The igraph C library currently supports numeric, string and Boolean attribute values, and provides a means for implementing attribute handlers that support custom types. + - **adjacent**: Two vertices are called **adjacent** if there is an edge connecting them. This term describes a vertex-to-vertex relation. + - **adjacency list**: A data structure that associates a list of neighbours (i.e. adjacent vertices) to each vertex. + - **adjacency matrix**: A representation of a graph as a square matrix. `A_ij` gives the number of edge endpoints connecting from the `i`th vertex to the `j`th vertex. Conventionally, the diagonal of the adjacency matrix of an undirected graph contains _twice_ the number of self-loops. All igraph functions follow this convention unless noted otherwise. + - **biadjacency matrix**: Analogous to the adjacency matrix, but used for bipartite graphs. Element `B_ij` gives the number of edges from the `i`th vertex of the first group to the `j`th vertex of the second group. + - **bipartite graph**: A graph whose vertices can be partitioned into two groups in such a way that connections are present only between members of different groups. + - **complete graph**: Also called **full graph** within the context of igraph, a graph in which all pairs of vertices are connected to each other. + - **connected graph**: A connected graph consists of a single component, in which any vertex is reachable from any other. In igraph, the null graph is not considered connected, as it has not one, but zero components. + - **edge**: A **connection** between two vertices, also called a **link**. In igraph, edges are referred to by integer indices called **edge IDs**. + - **finalizer stack**: A global stack used internally by igraph to keep track of currently allocated objects and their destructors, so that they can be automatically destroyed in case of an error. + - **game**: Within igraph, this term is used for stochastic graph generators, i.e. functions that sample from random graph models. + - **graph** or **network**: A set of vertices with connections between them. In igraph, graphs may carry associated data in the form of vertex, edge or graph attributes. + - **incident**: An edge is called **incident** to the vertices that are its endpoints. This term describes a vertex-to-edge relation. + - **incidence list**: A data structure that associates a list of incident edges to each vertex. + - **incidence matrix**: A matrix describing the incidence relation between vertices (rows) and edges (columns). + - **membership vector**: Membership vectors are a means of encoding a partitioning of items, usually vertices, into several groups. The `i`th element of the vector gives an integer identifier of the group the `i`th vertex belongs to. Membership vectors are typically used to describe a vertex clustering obtained through community detection, or by identifying the connected components of a graph. + - **multi-edges** or **parallel edges**: More than one edge connecting the same two vertices. In a directed graph, `a -> b, a -> b` are considered parallel edges, but `a -> b, a <- b` are not. + - **null graph**: A graph with no vertices (and no edges). + - **self-loop**, **self-edge**, or simply **loop**: An edge that connects a vertex to itself. + - **simple graph**: A graph that does not have self-loops or multi-edges. + - **singleton graph**: A graph having a single vertex. This term usually refers to a single vertex with no edges, but note that self-loops may in principle be present. + - **vertex**: Graphs consist of vertices, also called **nodes**, that are connected to each other. In igraph, vertices are referred to by integer indices called **vertex IDs**. diff --git a/doc/glossary.xml b/doc/glossary.xml new file mode 100644 index 0000000..16d981f --- /dev/null +++ b/doc/glossary.xml @@ -0,0 +1,197 @@ + + + + + + + Glossary + + This glossary defines common terms used throughout the igraph + documentation. + + + + + attribute: A piece of data + associated with a vertex, an edge, or the graph itself. The + igraph C library currently supports numeric, string and Boolean + attribute values, and provides a means for implementing + attribute handlers that support custom types. + + + + + adjacent: Two vertices are + called adjacent if there is + an edge connecting them. This term describes a vertex-to-vertex + relation. + + + + + adjacency list: A data + structure that associates a list of neighbours (i.e. adjacent + vertices) to each vertex. + + + + + adjacency matrix: A + representation of a graph as a square matrix. + A_ij gives the number of edge endpoints + connecting from the ith vertex to the + jth vertex. Conventionally, the diagonal of + the adjacency matrix of an undirected graph contains + twice the number of self-loops. All igraph + functions follow this convention unless noted otherwise. + + + + + biadjacency matrix: Analogous + to the adjacency matrix, but used for bipartite graphs. Element + B_ij gives the number of edges from the + ith vertex of the first group to the + jth vertex of the second group. + + + + + bipartite graph: A graph + whose vertices can be partitioned into two groups in such a way + that connections are present only between members of different + groups. + + + + + complete graph: Also called + full graph within the context + of igraph, a graph in which all pairs of vertices are connected + to each other. + + + + + connected graph: A connected + graph consists of a single component, in which any vertex is + reachable from any other. In igraph, the null graph is not + considered connected, as it has not one, but zero components. + + + + + edge: A + connection between two + vertices, also called a link. + In igraph, edges are referred to by integer indices called + edge IDs. + + + + + finalizer stack: A global + stack used internally by igraph to keep track of currently + allocated objects and their destructors, so that they can be + automatically destroyed in case of an error. + + + + + game: Within igraph, this + term is used for stochastic graph generators, i.e. functions + that sample from random graph models. + + + + + graph or + network: A set of vertices + with connections between them. In igraph, graphs may carry + associated data in the form of vertex, edge or graph attributes. + + + + + incident: An edge is called + incident to the vertices that + are its endpoints. This term describes a vertex-to-edge + relation. + + + + + incidence list: A data + structure that associates a list of incident edges to each + vertex. + + + + + incidence matrix: A matrix + describing the incidence relation between vertices (rows) and + edges (columns). + + + + + membership vector: Membership + vectors are a means of encoding a partitioning of items, usually + vertices, into several groups. The ith + element of the vector gives an integer identifier of the group + the ith vertex belongs to. Membership vectors + are typically used to describe a vertex clustering obtained + through community detection, or by identifying the connected + components of a graph. + + + + + multi-edges or + parallel edges: More than one + edge connecting the same two vertices. In a directed graph, + a -> b, a -> b are considered parallel + edges, but a -> b, a <- b are not. + + + + + null graph: A graph with no + vertices (and no edges). + + + + + self-loop, + self-edge, or simply + loop: An edge that connects a + vertex to itself. + + + + + simple graph: A graph that + does not have self-loops or multi-edges. + + + + + singleton graph: A graph + having a single vertex. This term usually refers to a single + vertex with no edges, but note that self-loops may in principle + be present. + + + + + vertex: Graphs consist of + vertices, also called nodes, + that are connected to each other. In igraph, vertices are + referred to by integer indices called + vertex IDs. + + + + diff --git a/doc/gpl.xml b/doc/gpl.xml new file mode 100644 index 0000000..1ded18f --- /dev/null +++ b/doc/gpl.xml @@ -0,0 +1,444 @@ + + +
+ + Version 2, June 1991 + 19891991 + Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + +THE GNU GENERAL PUBLIC LICENSE +
Preamble + + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + + + The precise terms and conditions for copying, distribution and +modification follow. + + +
+
GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + + + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + + + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + + You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + + + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + + + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + + + Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + + + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + + + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + + + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + + + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + + NO WARRANTY + + + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + + + END OF TERMS AND CONDITIONS + + +
+
How to Apply These Terms to Your New Programs + + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + +Also add information on how to contact you by electronic and paper mail. + + + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + + + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + + + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + +
+
diff --git a/doc/graphlets.xxml b/doc/graphlets.xxml new file mode 100644 index 0000000..d666a9d --- /dev/null +++ b/doc/graphlets.xxml @@ -0,0 +1,20 @@ + + +]> + + +Graphlets + +
+ +
+ +
Performing graphlet decomposition + + + +
+ +
diff --git a/doc/gtk-doc.xsl b/doc/gtk-doc.xsl new file mode 100644 index 0000000..14d252e --- /dev/null +++ b/doc/gtk-doc.xsl @@ -0,0 +1,342 @@ + + + + + + + + + bibdatabase.xml + 1 + 0 + 2 + + book toc + chapter toc + section toc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.36 + + + + +FATAL-ERROR: You need the DocBook XSL Stylesheets version 1.36 or higher +to build the documentation. +Get a newer version at http://docbook.sourceforge.net/projects/xsl/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.66 + + + + + + + + + + + + 1.66 + + + + + + + + + + +
+ + + +
+
+ + + +
+
+ + + + + + + +
+ + + +
+ + + +

+ + + +

+
+ +

+ + + + + + + + +

+
+
+

+ +

+
+ + + +
+
+
+ + + +
+ +
+
+ +
diff --git a/doc/heap.xxml b/doc/heap.xxml new file mode 100644 index 0000000..1046915 --- /dev/null +++ b/doc/heap.xxml @@ -0,0 +1,21 @@ + + +]> + +
+Maximum and minimum heaps + + + + + + + + + + + + +
diff --git a/doc/hrg.xxml b/doc/hrg.xxml new file mode 100644 index 0000000..15a0810 --- /dev/null +++ b/doc/hrg.xxml @@ -0,0 +1,45 @@ + + +]> + + +Hierarchical random graphs + +
+ +
+ +
Representing HRGs + + + + + +
+ +
Fitting HRGs + + +
+ +
HRG sampling + + +
+ +
Conversion to and from igraph graphs + + +
+ +
Predicting missing edges + +
+ +
Deprecated functions + +
+ +
diff --git a/doc/html/home.png b/doc/html/home.png new file mode 100644 index 0000000000000000000000000000000000000000..17003611d9df2b066afc682cbde962f3a575002d GIT binary patch literal 654 zcmV;90&)F`P)~yY zO1cF+0vxb!W?!x?K+*#62Jq)nA4q`)5S6sgX4ao{=)(Mgq+YMr)7sjak|a^9)zS!j zlk{-n29mabXYF=7SYBQx&vO8xC}MYams+hxqtO7sImhPaCf@rq;I^3!#u*2aUP)55 zT2&N90xmEJ0s&fGT~(T<3d2xYmK9C>IP*x-M@ib*+0pFm>>uW37N2Wzaq-fCnIZE9 zpb8}0+uN+KuQM2oZVHfP8U6kQdo3?>Wo2dT)WeM9So8DqhLi#T0 z-i(>mfjhvbsYV`;4sgfJ-p>G-SqJ!fjR6BQYs1h*y9xaN0l{VB;o%`08yiy@)$8@~ z2PD1gcDuiy;j1tR0v#V8OH%W)25-YKyx(j#IXO9*YWf0mb8}QG6@b@;cHxh9{t7+@ o!Yd`f8L$sLH?yBt^q3C6015TtIu@BS5dZ)H07*qoM6N<$f*igdr~m)} literal 0 HcmV?d00001 diff --git a/doc/html/left.png b/doc/html/left.png new file mode 100644 index 0000000000000000000000000000000000000000..2d05b3d5b4aeec9384bbfe404bfc4ed0897051c4 GIT binary patch literal 459 zcmV;+0W|)JP)40xL?wO*>WZ(J#ML5j2<9jD6A%Q&kC}jOeEc;X{s;`zcnxLeZR6?6h#^ihmNF6NpGdilO$m<82oD9WQ|6nVv1`? z>KufRi{?QPXg;4;wroQu4?mN1Ydd@|kaQ|ZyWLK!)yi7Wb%=0{}lD)tfliHAUyWRQ+fD_;aV6j->y6!O_8bENg6P)Cd4HCN^TYHBC0dz3r5|}*T3c5!K}0^NPTey!^rYo;W&eW{b1SE%dR-1ljcju- zJITo5P_e{cPDWDszO|97o#m$fni3V4d%~7^?0HU4-k!+X`e~w55Q}HA=c?CM9`EK` z^o5GF_RsnG`ey+9wOf8O4bzg>7W*;jU~M?g`OZAA$mNp|Lz<$s+~N9!2`ir8RcClo$(Q~19INM~9}j;&*|enC yGd}kJak0wj?aUKd8;%}`i}SSew>!A-2iw}^5}Rh(M>+vRkipZ{&t;ucLK6U4uc96R literal 0 HcmV?d00001 diff --git a/doc/igraph-docs.info b/doc/igraph-docs.info new file mode 100644 index 0000000..3c2b7fc --- /dev/null +++ b/doc/igraph-docs.info @@ -0,0 +1,65004 @@ +This is igraph-docs.info, produced by makeinfo version 7.2 from +igraph-docs.texi. + +INFO-DIR-SECTION Libraries +START-INFO-DIR-ENTRY +* igraph Reference Manual: (igraph-docs). A fast graph library (C) +END-INFO-DIR-ENTRY + + +File: igraph-docs.info, Node: Top, Next: Introduction, Up: (dir) + +igraph Reference Manual +*********************** + +* Menu: + +* Introduction:: +* Installation:: +* Tutorial:: +* Basic data types and interface:: +* Error handling:: +* Memory (de)allocation: Memory [de]allocation. +* Data structure library; vector, matrix, other data types: Data structure library; vector; matrix; other data types. +* Random numbers:: +* Vertex and edge selectors and sequences, iterators: Vertex and edge selectors and sequences; iterators. +* Graph, vertex and edge attributes: Graph; vertex and edge attributes. +* Deterministic graph generators:: +* Stochastic graph generators ("games"): Stochastic graph generators ["games"]. +* Bipartite, i.e. two-mode graphs: Bipartite; i_e_ two-mode graphs. +* Spatial graphs:: +* Graph operators:: +* Graph visitors:: +* Structural properties of graphs:: +* Graph cycles:: +* Cliques and independent vertex sets:: +* Graph motifs, dyad census and triad census: Graph motifs; dyad census and triad census. +* Graph isomorphism:: +* Graph coloring:: +* Maximum flows, minimum cuts and related measures: Maximum flows; minimum cuts and related measures. +* Vertex separators:: +* Detecting community structure:: +* Graphlets:: +* Hierarchical random graphs:: +* Embedding of graphs:: +* Generating layouts for graph drawing:: +* Processes on graphs:: +* Reading and writing graphs from and to files:: +* Using BLAS, LAPACK and ARPACK for igraph matrices and graphs: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs. +* Non-graph related functions : Non-graph related functions. +* Advanced igraph programming:: +* Glossary:: +* Licenses for igraph and this manual:: +* Index: Concept index. + +-- The Detailed Node Listing -- + +Introduction + +* igraph is free software:: +* Citing igraph:: + +Installation + +* Prerequisites:: +* Installation: Installation <1>. +* Building the documentation:: +* Notes for package maintainers:: + +Tutorial + +* Compiling programs using igraph:: +* Creating your first graphs:: +* Calculating various properties of graphs:: + +Basic data types and interface + +* The igraph data model:: +* General conventions of igraph functions:: +* Atomic data types:: +* Setup and initialization:: +* The basic interface:: +* Miscellaneous macros and helper functions:: + +Error handling + +* Error handling basics:: +* Error handlers:: +* Error codes:: +* Warning messages:: +* Advanced topics:: + +Memory (de)allocation + +* About allocation functions:: +* Available allocation functions:: + +Data structure library: vector, matrix, other data types + +* About template types:: +* Vectors:: +* Matrices:: +* Sparse matrices:: +* Stacks:: +* Double-ended queues:: +* Maximum and minimum heaps:: +* String vectors:: +* Lists of vectors, matrices and graphs: Lists of vectors; matrices and graphs. +* Adjacency lists:: +* Partial prefix sum trees:: +* Bitsets:: + +Random numbers + +* About random numbers in igraph:: +* The default random number generator:: +* Creating random number generators:: +* Generating random numbers:: +* Supported random number generators:: +* Use cases:: + +Vertex and edge selectors and sequences, iterators + +* About selectors, iterators: About selectors; iterators. +* Vertex selector constructors:: +* Generic vertex selector operations:: +* Immediate vertex selectors:: +* Vertex iterators:: +* Edge selector constructors:: +* Immediate edge selectors:: +* Generic edge selector operations:: +* Edge iterators:: + +Graph, vertex and edge attributes + +* The attribute handler interface:: +* Attribute records:: +* Handling attribute combination lists:: +* Accessing attributes from C:: + +Deterministic graph generators + +* About generators:: +* Basic graph creation:: +* Graphs from adjacency matrices and adjacency lists:: +* Regular structures:: +* Tree generators:: +* Graphs with given degrees:: +* Complete graphs:: +* Pre-defined graphs:: +* Other well-known graphs from graph theory:: + +Stochastic graph generators ("games") + +* The Erdős-Rényi and related models:: +* Preferential attachment and related models:: +* Growing random graph models:: +* Degree-constrained models:: +* Edge rewiring models:: +* Other random graphs:: +* Common types and constants:: + +Bipartite, i.e. two-mode graphs + +* Bipartite networks in igraph:: +* Create two-mode networks:: +* Bipartite adjacency matrices:: +* Project two-mode graphs:: +* Other operations on bipartite graphs:: + +Spatial graphs + +* Metrics:: +* Spatial graph generators:: +* Properties of spatial graphs:: +* Non-graph related spatial processing:: + +Graph operators + +* Union and intersection:: +* Other set-like operators:: +* Miscellaneous operators:: + +Graph visitors + +* Breadth-first search:: +* Depth-first search:: +* Random walks:: + +Structural properties of graphs + +* Basic properties:: +* Sparsifiers:: +* (Shortest)-path related functions: [Shortest]-path related functions. +* Widest-path related functions:: +* Efficiency measures:: +* Neighborhood of a vertex:: +* Local scan statistics:: +* Graph components:: +* Percolation:: +* Degree sequences:: +* Centrality measures:: +* Range-limited centrality measures:: +* Subset-limited centrality measures:: +* Centralization:: +* Similarity measures:: +* Trees and forests:: +* Transitivity or clustering coefficient:: +* Directedness conversion:: +* Spectral properties:: +* Non-simple graphs; Multiple and loop edges:: +* Mixing patterns and degree correlations:: +* K-cores and k-trusses:: +* Maximum cardinality search and chordal graphs:: +* Matchings:: +* Unfolding a graph into a tree:: +* Other operations:: +* Common types and constants: Common types and constants <1>. + +Graph cycles + +* Finding cycles:: +* Acyclic graphs and feedback sets:: +* Eulerian cycles and paths:: +* Cycle bases:: + +Cliques and independent vertex sets + +* Cliques:: +* Weighted cliques:: +* Independent vertex sets:: + +Graph motifs, dyad census and triad census + +* igraph_dyad_census -- Dyad census, as defined by Holland and Leinhardt.: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_. +* igraph_triad_census -- Triad census, as defined by Davis and Leinhardt.: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_. +* Finding triangles:: +* Graph motifs:: + +Graph isomorphism + +* The simple interface:: +* The BLISS algorithm:: +* The VF2 algorithm:: +* The LAD algorithm:: +* Functions for small graphs:: +* Utility functions:: + +Graph coloring + +* igraph_vertex_coloring_greedy -- Computes a vertex coloring using a greedy algorithm.: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_. +* igraph_coloring_greedy_t -- Ordering heuristics for greedy graph coloring.: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_. +* igraph_is_vertex_coloring -- Checks whether a vertex coloring is valid.: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_. +* igraph_is_bipartite_coloring -- Checks whether a bipartite vertex coloring is valid.: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_. +* igraph_is_edge_coloring -- Checks whether an edge coloring is valid.: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_. +* igraph_is_perfect -- Checks if the graph is perfect.: igraph_is_perfect --- Checks if the graph is perfect_. + +Maximum flows, minimum cuts and related measures + +* Maximum flows:: +* Cuts and minimum cuts:: +* Connectivity:: +* Edge- and vertex-disjoint paths:: +* Graph adhesion and cohesion:: +* Cohesive blocks:: + +Vertex separators + +* igraph_is_separator --- Would removing this set of vertices disconnect the graph?:: +* igraph_is_minimal_separator -- Decides whether a set of vertices is a minimal separator.: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_. +* igraph_all_minimal_st_separators -- List all vertex sets that are minimal (s,t) separators for some s and t.: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_. +* igraph_minimum_size_separators -- Find all minimum size separating vertex sets.: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_. +* igraph_even_tarjan_reduction -- Even-Tarjan reduction of a graph.: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_. + +Detecting community structure + +* Common functions related to community structure:: +* Community structure based on statistical mechanics:: +* Community structure based on eigenvectors of matrices:: +* Walktrap; Community structure based on random walks:: +* Edge betweenness based community detection:: +* Community structure based on the optimization of modularity:: +* Fluid communities:: +* Label propagation:: +* The InfoMAP algorithm:: +* Voronoi communities:: + +Graphlets + +* Introduction: Introduction <1>. +* Performing graphlet decomposition:: + +Hierarchical random graphs + +* Introduction: Introduction <2>. +* Representing HRGs:: +* Fitting HRGs:: +* HRG sampling:: +* Conversion to and from igraph graphs:: +* Predicting missing edges:: +* Deprecated functions:: + +Embedding of graphs + +* Spectral embedding:: + +Generating layouts for graph drawing + +* 2D layout generators:: +* Layouts for trees and acyclic graphs:: +* 3D layout generators:: +* Post-processing layouts:: + +Processes on graphs + +* Epidemic models:: + +Reading and writing graphs from and to files + +* Simple edge list and similar formats:: +* Binary formats:: +* GraphML format:: +* GML format:: +* Pajek format:: +* UCINET's DL file format:: +* Graphviz format:: +* LEDA format:: +* Convenience functions for locale change:: + +Using BLAS, LAPACK and ARPACK for igraph matrices and graphs + +* BLAS interface in igraph:: +* LAPACK interface in igraph:: +* ARPACK interface in igraph:: + +Non-graph related functions + +* igraph version number:: +* Running mean of a time series:: +* Random sampling from very long sequences:: +* Random sampling of spatial points:: +* Fitting power-law distributions to empirical data:: +* Comparing floats with a tolerance:: + +Advanced igraph programming + +* Using igraph in multi-threaded programs:: +* Progress handlers:: +* Status handlers:: + +Licenses for igraph and this manual + +* THE GNU GENERAL PUBLIC LICENSE:: +* The GNU Free Documentation License:: + + +File: igraph-docs.info, Node: Introduction, Next: Installation, Prev: Top, Up: Top + +1 Introduction +************** + +igraph is a library for creating and manipulating graphs. You can look +at it in two ways: first, igraph contains the implementation of quite a +lot of graph algorithms. These include classic graph algorithms like +graph isomorphism, graph girth and connectivity and also the new wave +graph algorithms like transitivity, graph motifs and community structure +detection. Skim through the table of contents or the index of this book +to get an impression of what is available. + + Second, igraph provides a platform for developing and/or implementing +graph algorithms. It has an efficient data structure for representing +graphs, and a number of other data structures like flexible vectors, +stacks, heaps, queues, adjacency lists that are useful for implementing +graph algorithms. In fact these data structures evolved along with the +implementation of the classic and non-classic graph algorithms which +make up the major part of the igraph library. This way, they were +fine-tuned and checked for correctness several times. + + Our main goal with developing igraph was to create a graph library +which is efficient on large, but not extremely large graphs. More +precisely, it is assumed that the graph(s) fit into the physical memory +of the computer. Nowadays this means graphs with several million +vertices and/or edges. Our definition of efficient is that it runs +fast, both in theory and (more importantly) in practice. + + We believe that one of the big strengths of igraph is that it can be +embedded into a higher-level language or environment. Three such +embeddings (or interfaces if you look at them another way) are currently +being developed by us: an R package, a Python extension module, and a +Mathematica (Wolfram Language) package. Others are likely to come. +High level languages such as R or Python make it possible to use graph +routines with much greater comfort, without actually writing a single +line of C code. They have some, usually very small, speed penalty +compared to the C version, but add ease of use and much flexibility. +This manual, however, covers only the C library. If you want to use +Python, R or the Wolfram Language, please see the documentation written +specifically for these interfaces and come back here only if you are +interested in some detail which is not covered in those documents. + + We still consider igraph as a child project. It has much room for +development and we are sure that it will improve a lot in the near +future. Any feedback we can get from the users is very important for +us, as most of the time these questions and comments guide us in what to +add and what to improve. + + igraph is open source and distributed under the terms of the GNU GPL +version 2 or (at your option) any later version. We strongly believe +that all the algorithms used in science, let that be graph theory or +not, should have an efficient open-source implementation allowing use +and modification for anyone. + +* Menu: + +* igraph is free software:: +* Citing igraph:: + + +File: igraph-docs.info, Node: igraph is free software, Next: Citing igraph, Up: Introduction + +1.1 igraph is free software +=========================== + +igraph library + + Copyright (C) 2003-2004 Gábor Csárdi + + Copyright (C) 2005-2019 Gábor Csárdi and +Tamás Nepusz + + Copyright (C) 2020-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2 of the License, or (at your +option) any later version. + + This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +Public License for more details. + + You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc. + + +File: igraph-docs.info, Node: Citing igraph, Prev: igraph is free software, Up: Introduction + +1.2 Citing igraph +================= + +To cite igraph in publications, please use the following reference: + + Gábor Csárdi, Tamás Nepusz: The igraph software package for complex +network research. InterJournal Complex Systems, 1695, 2006. + + The igraph C library is assigned the DOI 10.5281/zenodo.3630268 +(https://doi.org/10.5281/zenodo.3630268) on Zenodo. + + +File: igraph-docs.info, Node: Installation, Next: Tutorial, Prev: Introduction, Up: Top + +2 Installation +************** + +This chapter describes building igraph from source code and installing +it. The source archive of the latest stable release is always available +from the igraph website (https://igraph.org/c/#downloads). igraph is +also included in many Linux distributions, as well as several package +managers such as vcpkg (https://vcpkg.io/) (convenient on Windows), +MacPorts (https://www.macports.org/) (macOS) and Homebrew +(https://brew.sh/) (macOS), which provide an easier means of +installation. If you decide to use them, please consult their +documentation on how to install packages. + +* Menu: + +* Prerequisites:: +* Installation: Installation <1>. +* Building the documentation:: +* Notes for package maintainers:: + + +File: igraph-docs.info, Node: Prerequisites, Next: Installation <1>, Up: Installation + +2.1 Prerequisites +================= + +To build igraph from sources, you will need at least: + + • CMake (https://cmake.org) 3.18 or later + + • C and C++ compilers + + Visual Studio 2015 and later are supported. Earlier Visual Studio +versions may or may not work. + + Certain features also require the following libraries: + + • libxml2 (http://www.xmlsoft.org/), required for GraphML support + + igraph bundles a number of libraries for convenience. However, it is +preferable to use external versions of these libraries, which may +improve performance. These are: + + • GMP (https://gmplib.org/) (the bundled alternative is Mini-GMP) + + • GLPK (https://www.gnu.org/software/glpk/) (version 4.57 or later) + + • ARPACK (https://github.com/opencollab/arpack-ng) + + • plfit (https://github.com/ntamas/plfit) + + • A library providing a BLAS (https://www.netlib.org/blas/) API + (available by default on macOS; OpenBLAS + (http://www.openmathlib.org/OpenBLAS/) is one option on other + systems) + + • A library providing a LAPACK (https://www.netlib.org/lapack/) API + (available by default on macOS; OpenBLAS + (http://www.openmathlib.org/OpenBLAS/) is one option on other + systems) + + When building the development version of igraph, ‘bison’, ‘flex’ and +‘git’ are also required. Released versions do not require these tools. + + To run the tests, ‘diff’ is also required. + + +File: igraph-docs.info, Node: Installation <1>, Next: Building the documentation, Prev: Prerequisites, Up: Installation + +2.2 Installation +================ + +* Menu: + +* General build instructions:: +* Specific instructions for Windows:: +* Notable configuration options:: + + +File: igraph-docs.info, Node: General build instructions, Next: Specific instructions for Windows, Up: Installation <1> + +2.2.1 General build instructions +-------------------------------- + +igraph uses a CMake-based build system +(https://cmake.org/cmake/help/latest/guide/user-interaction/index.html). +To compile it, + + • Enter the directory where the igraph sources are: + + + $ cd igraph + + • Create a new directory. This is where igraph will be built: + + + $ mkdir build + $ cd build + + • Run CMake, which will automatically configure igraph, and report + the configuration: + + + $ cmake .. + + To set a non-default installation location, such as ‘/opt/local’, + use: + + cmake .. -DCMAKE_INSTALL_PREFIX=/opt/local + + • Check the output carefully, and ensure that all features you need + are enabled. If CMake could not find certain libraries, some + features such as GraphML support may have been automatically + disabled. + + • There are several ways to adjust the configuration: + + • Run ‘ccmake .’ on Unix-like systems or ‘cmake-gui’ on Windows + for a convenient interface. + + • Simply edit the ‘CMakeCache.txt’ file. Some of the relevant + options are listed below. + + • Once the configuration has been adjusted, run ‘cmake ..’ again. + + • Once igraph has been successfully configured, it can be built, + tested and installed using: + + + $ cmake --build . + $ cmake --build . --target check + $ cmake --install . + + +File: igraph-docs.info, Node: Specific instructions for Windows, Next: Notable configuration options, Prev: General build instructions, Up: Installation <1> + +2.2.2 Specific instructions for Windows +--------------------------------------- + +* Menu: + +* Microsoft Visual Studio:: +* MSYS2:: + + +File: igraph-docs.info, Node: Microsoft Visual Studio, Next: MSYS2, Up: Specific instructions for Windows + +2.2.2.1 Microsoft Visual Studio +............................... + +With Visual Studio, the steps to build igraph are generally the same as +above. However, since the Visual Studio CMake generator is a +multi-configuration one, we must specify the configuration (typically +Release or Debug) with each build command using the ‘--config’ option: + + + mkdir build + cd build + cmake .. + cmake --build . --config Release + cmake --build . --target check --config Release + + When building the development version, ‘bison’ and ‘flex’ must be +available on the system. ‘winflexbison’ +(https://github.com/lexxmark/winflexbison) for Bison version 3.x can be +useful for this purpose--make sure that the executables are in the +system ‘PATH’. The easiest installation option is probably by +installing ‘winflexbison3’ from the Chocolatey package manager +(https://chocolatey.org/packages/winflexbison3). + +* Menu: + +* vcpkg:: + + +File: igraph-docs.info, Node: vcpkg, Up: Microsoft Visual Studio + +vcpkg +..... + +Most external dependencies can be conveniently installed using ‘vcpkg’ +(https://github.com/microsoft/vcpkg#quick-start-windows). Note that +‘igraph’ bundles all dependencies except ‘libxml2’, which is needed for +GraphML support. + + In order to use vcpkg integrate it in the build environment by +executing ‘vcpkg.exe integrate install’ on the command line. When +configuring igraph, point CMake to the correct ‘vcpkg.cmake’ file using +‘-DCMAKE_TOOLCHAIN_FILE=...’, as instructed. + + Additionally, it might be that you need to set the appropriate +so-called triplet using ‘-DVCPKG_TARGET_TRIPLET’ when running ‘cmake’, +for exampling, setting it to ‘x64-windows’ when using shared builds of +packages or ‘x64-windows-static’ when using static builds. Similarly, +you also need to specify this target triplet when installing packages. +For example, to install ‘libxml2’ as a shared library, use ‘vcpkg.exe +install libxml2:x64-windows’ and to install ‘libxml2’ as a static +library, use ‘vcpkg.exe install libxml2:x64-windows-static’. In +addition, there is the possibility to use a static library with dynamic +runtime linking using the ‘x64-windows-static-md’ triplet. + + +File: igraph-docs.info, Node: MSYS2, Prev: Microsoft Visual Studio, Up: Specific instructions for Windows + +2.2.2.2 MSYS2 +............. + +MSYS2 can be installed from msys2.org (https://www.msys2.org/). After +installing MSYS2, ensure that it is up to date by opening a terminal and +running ‘pacman -Syuu’. + + The instructions below assume that you want to compile for a 64-bit +target. + + Install the following packages using ‘pacman -S’. + + • Minimal requirements: ‘mingw-w64-x86_64-toolchain’, + ‘mingw-w64-x86_64-cmake’. + + • Optional dependencies that enable certain features: + ‘mingw-w64-x86_64-gmp’, ‘mingw-w64-x86_64-libxml2’ + + • Optional external libraries for better performance: + ‘mingw-w64-x86_64-openblas’, ‘mingw-w64-x86_64-arpack’, + ‘mingw-w64-x86_64-glpk’ + + • Only needed for running the tests: ‘diffutils’ + + • Required only when building the development version: ‘git’, + ‘bison’, ‘flex’ + + The following command will install of these at once: + + + pacman -S \ + mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake \ + mingw-w64-x86_64-gmp mingw-w64-x86_64-libxml2 \ + mingw-w64-x86_64-openblas mingw-w64-x86_64-arpack \ + mingw-w64-x86_64-glpk diffutils git bison flex + + In order to build igraph, follow the *General build instructions* +above, paying attention to the following: + + • When using MSYS2, start the 'MSYS2 MinGW 64-bit' terminal, and + _not_ the 'MSYS2 MSYS' one. + + • Be sure to install the ‘mingw-w64-x86_64-cmake’ package and not the + ‘cmake’ one. The latter will not work. + + • When running ‘cmake’, pass the option ‘-G"MSYS Makefiles"’. + + • Note that ‘ccmake’ is not currently available. ‘cmake-gui’ can be + used only if the ‘mingw-w64-x86_64-qt5’ package is installed. + + +File: igraph-docs.info, Node: Notable configuration options, Prev: Specific instructions for Windows, Up: Installation <1> + +2.2.3 Notable configuration options +----------------------------------- + +The following options may be set to ‘ON’ or ‘OFF’. Some of them have an +‘AUTO’ setting, which chooses a reasonable default based on what +libraries are available on the current system. + + • igraph bundles some of its dependencies for convenience. The + ‘IGRAPH_USE_INTERNAL_XXX’ flags control whether these should be + used instead of external versions. Set them to ‘ON’ to use the + bundled ('vendored') versions. Generally, external versions are + preferable as they may be newer and usually provide better + performance. + + • ‘IGRAPH_GLPK_SUPPORT’: whether to make use of the GLPK + (https://www.gnu.org/software/glpk/) library. Some features, such + as finding a minimum feedback arc set or finding communities + through exact modularity optimization, require this. + + • ‘IGRAPH_GRAPHML_SUPPORT’: whether to enable support for reading and + writing GraphML (http://graphml.graphdrawing.org/) files. Requires + the libxml2 (http://xmlsoft.org/) library. + + • ‘IGRAPH_INFOMAP_SUPPORT’: whether to enable the Infomap community + detection algorithm. The Infomap library is licensed under the + GPLv3+. Compiling it into igraph causes GPLv3+ to apply to the + resulting binary, instead of igraph's GPLv2+ license. + + • ‘IGRAPH_OPENMP_SUPPORT’: whether to use OpenMP parallelization to + accelerate certain functions such as PageRank calculation. + Compiler support is required. + + • ‘IGRAPH_ENABLE_LTO’: whether to build igraph with link-time + optimization, which improves performance. Not supported with all + compilers. + + • ‘IGRAPH_ENABLE_TLS’: whether to enable thread-local storage. + Required when using igraph from multiple threads. + + • ‘IGRAPH_WARNINGS_AS_ERRORS’: whether to treat compiler warnings as + errors. We strive to eliminate all compiler warnings during + development so this switch is turned on by default. If your + compiler prints warnings for some parts of the code that we did not + anticipate, you can turn off this option to prevent the warnings + from stopping the compilation. + + • ‘BUILD_SHARED_LIBS’ + (https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html): + whether to build a shared library instead of a static one. + + • ‘BLA_VENDOR’: controls which library to use for BLAS + (https://cmake.org/cmake/help/latest/module/FindBLAS.html) and + LAPACK (https://cmake.org/cmake/help/latest/module/FindLAPACK.html) + functionality. + + • ‘CMAKE_INSTALL_PREFIX’ + (https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html): + the location where igraph will be installed. + + +File: igraph-docs.info, Node: Building the documentation, Next: Notes for package maintainers, Prev: Installation <1>, Up: Installation + +2.3 Building the documentation +============================== + +Most users will not need to build the documentation, as the release +tarball contains pre-built HTML documentation in the ‘doc’ directory. + + To build the documentation for the development version, simply build +the ‘html’, ‘pdf’ or ‘info’ targets for the HTML, PDF and Info versions +of the documentation, respectively. + + + $ cmake --build . --target html + + Building the HTML documentation requires Python 3, ‘xmlto’ and +‘source-highlight’. On some platforms, it is necessary to explicitly +install the docbook-xsl package as well. Building the PDF documentation +also requires ‘xsltproc’, ‘xmllint’ and ‘fop’. Building the Texinfo +documentation also requires the docbook2X package, ‘xmllint’ and +‘makeinfo’. + + +File: igraph-docs.info, Node: Notes for package maintainers, Prev: Building the documentation, Up: Installation + +2.4 Notes for package maintainers +================================= + +This section is for people who package igraph for Linux distros or other +package managers. Please read it carefully before packaging igraph. + +* Menu: + +* Auto-detection of dependencies:: +* Shared and static builds:: +* Cross-compiling:: +* Additional notes:: + + +File: igraph-docs.info, Node: Auto-detection of dependencies, Next: Shared and static builds, Up: Notes for package maintainers + +2.4.1 Auto-detection of dependencies +------------------------------------ + +igraph bundles several of its dependencies (or simplified versions of +its dependencies). During configuration time, it checks whether each +dependency is present on the system. If yes, it uses it. Otherwise, it +falls back to the bundled ('vendored') version. In order to make +configuration as deterministic as possible, you may want to disable this +auto-detection. To do so, set each of the ‘IGRAPH_USE_INTERNAL_XXX’ +options described above. Additionally, set ‘BLA_VENDOR’ to use the BLAS +and LAPACK implementations of your choice. This should be the same BLAS +and LAPACK library that igraph's other dependencies (e.g., ARPACK) are +linked against. + + For example, to force igraph to use external versions of all +dependencies except plfit, and to use OpenBLAS for BLAS/LAPACK, use + + + $ cmake .. \ + -DIGRAPH_USE_INTERNAL_BLAS=OFF \ + -DIGRAPH_USE_INTERNAL_LAPACK=OFF \ + -DIGRAPH_USE_INTERNAL_ARPACK=OFF \ + -DIGRAPH_USE_INTERNAL_GLPK=OFF \ + -DIGRAPH_USE_INTERNAL_GMP=OFF \ + -DIGRAPH_USE_INTERNAL_PLFIT=ON \ + -DBLA_VENDOR=OpenBLAS \ + -DIGRAPH_GRAPHML_SUPPORT=ON + + +File: igraph-docs.info, Node: Shared and static builds, Next: Cross-compiling, Prev: Auto-detection of dependencies, Up: Notes for package maintainers + +2.4.2 Shared and static builds +------------------------------ + +On Windows, shared and static builds should not be installed in the same +location. If you decide to do so anyway, keep in mind the following: +Both builds contain an ‘igraph.lib’ file. The static one should be +renamed to avoid conflict. The headers from the static build are +incompatible with the shared library. The headers from the shared build +may be used with the static library, but ‘IGRAPH_STATIC’ must be defined +when compiling programs that will link to igraph statically. + + These issues do not affect Unix-like systems. + + +File: igraph-docs.info, Node: Cross-compiling, Next: Additional notes, Prev: Shared and static builds, Up: Notes for package maintainers + +2.4.3 Cross-compiling +--------------------- + +When building igraph with an internal ARPACK, LAPACK or BLAS, it makes +use of f2c, which compiles and runs the ‘arithchk’ program at build time +to detect the floating point characteristics of the current system. It +writes the results into the ‘arith.h’ header. However, running this +program is not possible when cross-compiling without providing a +userspace emulator that can run executables of the target platform on +the host system. Therefore, when cross-compiling, you either need to +provide such an emulator with the ‘CMAKE_CROSSCOMPILING_EMULATOR’ +option, or you need to specify a pre-generated version of the ‘arith.h’ +header file through the ‘F2C_EXTERNAL_ARITH_HEADER’ CMake option. An +example version of this header follows for the x86_64 and arm64 target +architectures on macOS. Warning: Do not use this version of ‘arith.h’ on +other systems or architectures. + + + #define IEEE_8087 + #define Arith_Kind_ASL 1 + #define Long int + #define Intcast (int)(long) + #define Double_Align + #define X64_bit_pointers + #define NANCHECK + #define QNaN0 0x0 + #define QNaN1 0x7ff80000 + + igraph also checks whether the endianness of ‘uint64_t’ matches the +endianness of ‘double’ on the platform being compiled. This is needed +to ensure that certain functions in igraph's random number generator +work properly. However, it is not possible to execute this check when +cross-compiling without an emulator, so in this case igraph simply +assumes that the endianness matches (which is the case for the vast +majority of platforms anyway). The only case where you might run into +problems is when you cross-compile for Apple Silicon (‘arm64’) from an +Intel-based Mac, in which case CMake might not realize that you are +cross-compiling and will try to execute the check anyway. You can work +around this by setting ‘IEEE754_DOUBLE_ENDIANNESS_MATCHES’ to ‘ON’ +explicitly before invoking CMake. + + Providing an emulator in ‘CMAKE_CROSSCOMPILING_EMULATOR’ has the +added benefit that you can run the compiled unit tests on the host +platform. We have experimented with cross-compiling to 64-bit ARM CPUs +(‘aarch64’) on 64-bit Intel CPUs (‘amd64’), and we can confirm that +using ‘qemu-aarch64’ works as a cross-compiling emulator in this setup. + + +File: igraph-docs.info, Node: Additional notes, Prev: Cross-compiling, Up: Notes for package maintainers + +2.4.4 Additional notes +---------------------- + + • As of igraph 0.10, there is no tangible benefit to using an + external GMP, as igraph does not yet use GMP in any + performance-critical way. The bundled Mini-GMP is sufficient. + + • Link-time optimization noticeably improves the performance of some + igraph functions. To enable it, use ‘-DIGRAPH_ENABLE_LTO=ON’. The + ‘AUTO’ setting is also supported, and will enable link-time + optimization only if the current compiler supports it. Note that + this is detected by CMake, and the detection is not always + accurate. + + • We saw occasional hangs on Windows when igraph was built for a + 32-bit target with MinGW and linked to OpenBLAS. We believe this to + be an issue with OpenBLAS, not igraph. On this platform, you may + want to opt for a different BLAS/LAPACK or the bundled BLAS/LAPACK. + + +File: igraph-docs.info, Node: Tutorial, Next: Basic data types and interface, Prev: Installation, Up: Top + +3 Tutorial +********** + +* Menu: + +* Compiling programs using igraph:: +* Creating your first graphs:: +* Calculating various properties of graphs:: + + +File: igraph-docs.info, Node: Compiling programs using igraph, Next: Creating your first graphs, Up: Tutorial + +3.1 Compiling programs using igraph +=================================== + +The following short example program demonstrates the basic usage of the +‘igraph’ library. Save it into a file named ‘igraph_test.c’. + + #include + + int main(void) { + igraph_int_t num_vertices = 1000; + igraph_int_t num_edges = 1000; + igraph_real_t diameter, mean_degree; + igraph_t graph; + + /* Initialize the library. */ + igraph_setup(); + + /* Ensure identical results across runs. */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_erdos_renyi_game_gnm( + &graph, num_vertices, num_edges, + IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_diameter( + &graph, /* weights = */ NULL, + &diameter, + /* from = */ NULL, /* to = */ NULL, + /* vertex_path = */ NULL, /* edge_path = */ NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + + igraph_mean_degree(&graph, &mean_degree, IGRAPH_LOOPS); + printf("Diameter of a random graph with average degree %g: %g\n", + mean_degree, diameter); + + igraph_destroy(&graph); + + return 0; + } + + This example illustrates a couple of points: + + • First, programs using the ‘igraph’ library should include the + ‘igraph.h’ header file. Note that while igraph installs several + sub-headers, the organization of these may change without notice. + Only use ‘igraph.h’ in your projects, not any of the sub-headers. + + • Second, the library must be initialized using ‘igraph_setup()’ + (*note igraph_setup --- Initializes the igraph library_::) before + use. + + • Third, ‘igraph’ uses the ‘igraph_int_t’ type for integers instead + of ‘int’ or ‘long int’, and it also uses the ‘igraph_real_t’ type + for real numbers instead of ‘double’. Depending on how ‘igraph’ + was compiled, and whether you are using a 32-bit or 64-bit system, + ‘igraph_int_t’ may be a 32-bit or 64-bit integer. + + • Fourth, ‘igraph’ graph objects are represented by the ‘igraph_t’ + data type. + + • Fifth, the ‘igraph_erdos_renyi_game_gnm()’ (*note + igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] + graph with a fixed number of edges_::) creates a graph and + ‘igraph_destroy()’ (*note igraph_destroy --- Frees the memory + allocated for a graph object_::) destroys it, i.e. deallocates the + memory associated to it. + + For compiling this program you need a C compiler. Optionally, CMake +(https://cmake.org) can be used to automate the compilation. + +* Menu: + +* Compiling with CMake:: +* Compiling without CMake:: +* Running the program:: + + +File: igraph-docs.info, Node: Compiling with CMake, Next: Compiling without CMake, Up: Compiling programs using igraph + +3.1.1 Compiling with CMake +-------------------------- + +It is convenient to use CMake because it can automatically discover the +necessary compilation flags on all operating systems. Many IDEs support +CMake, and can work with CMake projects directly. To create a CMake +project for this example program, create a file name ‘CMakeLists.txt’ +with the following contents: + + + cmake_minimum_required(VERSION 3.18) + project(igraph_test) + + find_package(igraph REQUIRED) + + add_executable(igraph_test igraph_test.c) + target_link_libraries(igraph_test PUBLIC igraph::igraph) + + To compile the project, create a new directory called ‘build’ in the +root of the ‘igraph’ source tree, and switch to it: + + + mkdir build + cd build + + Run CMake to configure the project: + + + cmake .. + + If ‘igraph’ was installed at a non-standard location, specify its +prefix using the ‘-DCMAKE_PREFIX_PATH=...’ option. The prefix must be +the same directory that was specified as the ‘CMAKE_INSTALL_PREFIX’ when +compiling igraph. + + If configuration has succeeded, build the program using + + + cmake --build . + + *C++ must be enabled in igraph projects* + + Parts of ‘igraph’ are implemented in C++; therefore, any CMake + target that depends on ‘igraph’ should use the C++ linker. + Furthermore, OpenMP support in igraph works correctly only if C++ + is enabled in the CMake project. The script that finds ‘igraph’ on + the host machine will throw an error if C++ support is not enabled + in the CMake project. + + C++ support is enabled by default when no languages are explicitly + specified in CMake's ‘project’ + (https://cmake.org/cmake/help/latest/command/project.html) command, + e.g. ‘project(igraph_test)’. If you do specify some languages + explicitly, make sure to also include ‘CXX’, e.g. + ‘project(igraph_test C CXX)’. + + +File: igraph-docs.info, Node: Compiling without CMake, Next: Running the program, Prev: Compiling with CMake, Up: Compiling programs using igraph + +3.1.2 Compiling without CMake +----------------------------- + +On most Unix-like systems, the default C compiler is called ‘cc’. To +compile the test program, you will need a command similar to the +following: + + + cc igraph_test.c -I/usr/local/include/igraph -L/usr/local/lib -ligraph -o igraph_test + + The exact form depends on where ‘igraph’ was installed on your +system, whether it was compiled as a shared or static library, and the +external libraries it was linked to. The directory after the ‘-I’ +switch is the one containing the ‘igraph.h’ file, while the one +following ‘-L’ should contain the library file itself, usually a file +called ‘libigraph.a’ (static library on macOS and Linux), ‘libigraph.so’ +(shared library on Linux), ‘libigraph.dylib’ (shared library on macOS), +‘igraph.lib’ (static library on Windows) or ‘igraph.dll’ (shared library +on Windows). If ‘igraph’ was compiled as a static library, it is also +necessary to manually link to all of its dependencies. + + If your system has the ‘pkg-config’ utility you are likely to get the +necessary compile options by issuing the command + + + pkg-config --libs --cflags igraph + +(if ‘igraph’ was built as a shared library) or + + + pkg-config --static --libs --cflags igraph + +(if ‘igraph’ was built as a static library). + + +File: igraph-docs.info, Node: Running the program, Prev: Compiling without CMake, Up: Compiling programs using igraph + +3.1.3 Running the program +------------------------- + +On most systems, the executable can be run by simply typing its name +like this: + + + ./igraph_test + +If you use dynamic linking and the ‘igraph’ library is not installed in +a standard place, you may need to add its location to the +‘LD_LIBRARY_PATH’ (Linux), ‘DYLD_LIBRARY_PATH’ (macOS) or ‘PATH’ +(Windows) environment variables. This is typically necessary on Windows +systems. + + +File: igraph-docs.info, Node: Creating your first graphs, Next: Calculating various properties of graphs, Prev: Compiling programs using igraph, Up: Tutorial + +3.2 Creating your first graphs +============================== + +The functions generating graph objects are called graph generators. +Stochastic (i.e. randomized) graph generators are called 'games'. + + ‘igraph’ can handle directed and undirected graphs. Most graph +generators are able to create both types of graphs and most other +functions are usually also capable of handling both. E.g., +‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- +Shortest paths from a vertex_::), which calculates shortest paths from a +vertex to other vertices, can calculate directed or undirected paths. + + ‘igraph’ has sophisticated ways for creating graphs. The simplest +graphs are deterministic regular structures like star graphs +(‘igraph_star()’ (*note igraph_star --- Creates a star graph; every +vertex connects only to the center_::)), cycle graphs +(‘igraph_cycle_graph()’ (*note igraph_cycle_graph --- A cycle graph +C_n_::)), lattices (‘igraph_square_lattice()’ (*note +igraph_square_lattice --- Arbitrary dimensional square lattices_::)) or +trees (‘igraph_kary_tree()’ (*note igraph_kary_tree --- Creates a k-ary +tree in which almost all vertices have k children_::)), and many more. + + The following example creates an undirected regular circular lattice, +adds some random edges to it and calculates the average length of +shortest paths between all pairs of vertices in the graph before and +after adding the random edges. (The message is that some random edges +can reduce path lengths a lot.) + + #include + + int main(void) { + igraph_t graph; + igraph_vector_int_t dimvector; + igraph_vector_int_t edges; + igraph_vector_bool_t periodic; + igraph_real_t avg_path_len; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&dimvector, 2); + VECTOR(dimvector)[0] = 30; + VECTOR(dimvector)[1] = 30; + + igraph_vector_bool_init(&periodic, 2); + igraph_vector_bool_fill(&periodic, true); + igraph_square_lattice(&graph, &dimvector, 0, IGRAPH_UNDIRECTED, + /* mutual= */ false, &periodic); + + igraph_average_path_length(&graph, NULL, &avg_path_len, NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + printf("Average path length (lattice): %g\n", (double) avg_path_len); + + /* Seed the RNG to ensure identical results across runs. */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&edges, 20); + for (igraph_int_t i = 0; i < igraph_vector_int_size(&edges); i++) { + VECTOR(edges)[i] = RNG_INTEGER(0, igraph_vcount(&graph) - 1); + } + + igraph_add_edges(&graph, &edges, NULL); + igraph_average_path_length(&graph, NULL, &avg_path_len, NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + printf("Average path length (randomized lattice): %g\n", (double) avg_path_len); + + igraph_vector_bool_destroy(&periodic); + igraph_vector_int_destroy(&dimvector); + igraph_vector_int_destroy(&edges); + igraph_destroy(&graph); + + return 0; + } + + This example illustrates some new points. ‘igraph’ uses +‘igraph_vector_t’ (*note About igraph_vector_t objects::) and its +related types (‘igraph_vector_int_t’, ‘igraph_vector_bool_t’ and so on) +instead of plain C arrays. ‘igraph_vector_t’ is superior to regular +arrays in almost every sense. Vectors are created by the +‘igraph_vector_init()’ (*note igraph_vector_init --- Initializes a +vector object [constructor]_::) function and, like graphs, they should +be destroyed if not needed any more by calling ‘igraph_vector_destroy()’ +(*note igraph_vector_destroy --- Destroys a vector object_::) on them. +A vector can be indexed by the ‘VECTOR()’ (*note VECTOR --- Accessing an +element of a vector_::) function (right now it is a macro). The +elements of a vector are of type ‘igraph_real_t’ for ‘igraph_vector_t’ +(*note About igraph_vector_t objects::), and of type ‘igraph_int_t’ for +‘igraph_vector_int_t’. As you might expect, ‘igraph_vector_bool_t’ +holds ‘igraph_bool_t’ values. Vectors can be resized and most ‘igraph’ +functions returning the result in a vector automatically resize it to +the size they need. + + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- Arbitrary +dimensional square lattices_::) takes an integer vector argument +specifying the dimensions of the lattice. In this example we generate a +30x30 two dimensional periodic lattice. See the documentation of +‘igraph_square_lattice()’ (*note igraph_square_lattice --- Arbitrary +dimensional square lattices_::) in the reference manual for the other +arguments. + + The vertices in a graph are identified by a _vertex ID_, an integer +between ‘0’ and ‘n - 1’, where ‘n’ is the number of vertices in the +graph. The vertex count can be retrieved using ‘igraph_vcount()’ (*note +igraph_vcount --- The number of vertices in a graph_::), as in the +example. + + The ‘igraph_add_edges()’ (*note igraph_add_edges --- Adds edges to a +graph object_::) function simply takes a graph and a vector of vertex +IDs defining the new edges. The first edge is between the first two +vertex IDs in the vector, the second edge is between the second two, +etc. This way we add ten random edges to the lattice. + + Note that this example program may add _loop edges_, edges pointing a +vertex to itself, or _multiple edges_, more than one edge between the +same pair of vertices. ‘igraph_t’ can of course represent loops and +multiple edges, although some routines expect simple graphs, i.e. +graphs which contain neither of these. This is because some structural +properties are ill-defined for non-simple graphs. Loop and multi-edges +can be removed by calling ‘igraph_simplify()’ (*note igraph_simplify --- +Removes loop and/or multiple edges from the graph_::). + + +File: igraph-docs.info, Node: Calculating various properties of graphs, Prev: Creating your first graphs, Up: Tutorial + +3.3 Calculating various properties of graphs +============================================ + +In our next example we will calculate various centrality measures in a +friendship graph. The friendship graph is from the famous Zachary +karate club study. (Do a web search on "Zachary karate" if you want to +know more about this.) Centrality measures quantify how central is the +position of individual vertices in the graph. + + #include + + int main(void) { + igraph_t graph; + igraph_vector_int_t result; + igraph_vector_t result_real; + igraph_int_t edges_array[] = { + 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, + 0,10, 0,11, 0,12, 0,13, 0,17, 0,19, 0,21, 0,31, + 1, 2, 1, 3, 1, 7, 1,13, 1,17, 1,19, 1,21, 1,30, + 2, 3, 2, 7, 2,27, 2,28, 2,32, 2, 9, 2, 8, 2,13, + 3, 7, 3,12, 3,13, 4, 6, 4,10, 5, 6, 5,10, 5,16, + 6,16, 8,30, 8,32, 8,33, 9,33, 13,33, 14,32, 14,33, + 15,32, 15,33, 18,32, 18,33, 19,33, 20,32, 20,33, + 22,32, 22,33, 23,25, 23,27, 23,32, 23,33, 23,29, + 24,25, 24,27, 24,31, 25,31, 26,29, 26,33, 27,33, + 28,31, 28,33, 29,32, 29,33, 30,32, 30,33, 31,32, + 31,33, 32,33 + }; + igraph_vector_int_t edges = + igraph_vector_int_view(edges_array, sizeof(edges_array) / sizeof(edges_array[0])); + + /* Initialize the library. */ + igraph_setup(); + + igraph_create(&graph, &edges, 0, IGRAPH_UNDIRECTED); + + igraph_vector_int_init(&result, 0); + igraph_vector_init(&result_real, 0); + + igraph_degree(&graph, &result, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + printf("Maximum degree is %10" IGRAPH_PRId ", vertex %2" IGRAPH_PRId ".\n", + igraph_vector_int_max(&result), + igraph_vector_int_which_max(&result)); + + igraph_closeness(&graph, &result_real, NULL, NULL, igraph_vss_all(), + IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ false); + printf("Maximum closeness is %10g, vertex %2" IGRAPH_PRId ".\n", + (double) igraph_vector_max(&result_real), + igraph_vector_which_max(&result_real)); + + igraph_betweenness(&graph, /* weights= */ NULL, &result_real, igraph_vss_all(), + IGRAPH_UNDIRECTED, /* normalized= */ false); + printf("Maximum betweenness is %10g, vertex %2" IGRAPH_PRId ".\n", + (double) igraph_vector_max(&result_real), + igraph_vector_which_max(&result_real)); + + igraph_vector_int_destroy(&result); + igraph_vector_destroy(&result_real); + igraph_destroy(&graph); + + return 0; + } + + This example demonstrates some new operations. First of all, it +shows a way to create a graph a list of edges stored in a plain C array. +Function ‘igraph_vector_view()’ (*note igraph_vector_view --- Handle a +regular C array as a igraph_vector_t_::) creates a _view_ of a C array. +It does not copy any data, which means that you must not call +‘igraph_vector_destroy()’ (*note igraph_vector_destroy --- Destroys a +vector object_::) on a vector created this way. This vector is then +used to create the undirected graph. + + Then the degree, closeness and betweenness centrality of the vertices +is calculated and the highest values are printed. Note that the vector +‘result’, into which these functions will write their result, must be +initialized first, and also that the functions resize it to be able to +hold the result. + + Notice that in order to print values of type ‘igraph_int_t’, we used +the ‘IGRAPH_PRId’ format macro constant. This macro is similar to the +standard ‘PRI’ constants defined in ‘stdint.h’, and expands to the +correct ‘printf’ format specifier on each platform that ‘igraph’ +supports. + + The ‘igraph_vss_all()’ (*note igraph_vss_all --- All vertices of a +graph [immediate version]_::) argument tells the functions to calculate +the property for every vertex in the graph. It is shorthand for a +_vertex selector_, represented by type ‘igraph_vs_t’. Vertex selectors +help perform operations on a subset of vertices. You can read more +about them in one of the following chapters (*note Vertex and edge +selectors and sequences; iterators::). + + +File: igraph-docs.info, Node: Basic data types and interface, Next: Error handling, Prev: Tutorial, Up: Top + +4 Basic data types and interface +******************************** + +* Menu: + +* The igraph data model:: +* General conventions of igraph functions:: +* Atomic data types:: +* Setup and initialization:: +* The basic interface:: +* Miscellaneous macros and helper functions:: + + +File: igraph-docs.info, Node: The igraph data model, Next: General conventions of igraph functions, Up: Basic data types and interface + +4.1 The igraph data model +========================= + +The igraph library can handle directed and undirected graphs. The +igraph graphs are multisets of ordered (if directed) or unordered (if +undirected) labeled pairs. The labels of the pairs plus the number of +vertices always starts with zero and ends with the number of edges minus +one. In addition to that, a table of metadata is also attached to every +graph, its most important entries being the number of vertices in the +graph and whether the graph is directed or undirected. + + Like the edges, the igraph vertices are also labeled by numbers +between zero and the number of vertices minus one. So, to summarize, a +directed graph can be imagined like this: + + + ( vertices: 6, + directed: yes, + { + (0,2), + (2,2), + (3,2), + (3,3), + (3,4), + (3,4), + (4,3), + (4,1) + } + ) + +Here the edges are ordered pairs or vertex ids, and the graph is a +multiset of edges plus some metadata. + + An undirected graph is like this: + + + ( vertices: 6, + directed: no, + { + (0,2), + (2,2), + (2,3), + (3,3), + (3,4), + (3,4), + (3,4), + (1,4) + } + ) + +Here, an edge is an unordered pair of two vertex IDs. A graph is a +multiset of edges plus metadata, just like in the directed case. + + It is possible to convert between directed and undirected graphs, see +the ‘igraph_to_directed()’ (*note igraph_to_directed --- Convert an +undirected graph to a directed one_::) and ‘igraph_to_undirected()’ +(*note igraph_to_undirected --- Convert a directed graph to an +undirected one_::) functions. + + igraph aims to robustly support multigraphs, i.e. graphs which have +more than one edge between some pairs of vertices, as well as graphs +with self-loops. Most functions which do not support such graphs will +check their input and issue an error if it is not valid. Those rare +functions which do not perform this check clearly indicate this in their +documentation. To eliminate multiple edges from a graph, you can use +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::). + + +File: igraph-docs.info, Node: General conventions of igraph functions, Next: Atomic data types, Prev: The igraph data model, Up: Basic data types and interface + +4.2 General conventions of igraph functions +=========================================== + +igraph has a simple and consistent interface. Most functions check +their input for validity and display an informative error message when +something goes wrong. In order to support this, the majority of +functions return an error code. In basic usage, this code can be +ignored, as the default behaviour is to abort the program immediately +upon error. See the section on error handling (*note Error handling::) +for more information on this topic. + + Results are typically returned through _output arguments_, i.e. +pointers to a data structure into which the result will be written. In +almost all cases, this data structure is expected to be pre-initialized. +A few simple functions communicate their result directly through their +return value--these functions can never encounter an error. + + +File: igraph-docs.info, Node: Atomic data types, Next: Setup and initialization, Prev: General conventions of igraph functions, Up: Basic data types and interface + +4.3 Atomic data types +===================== + +igraph introduces a few aliases to standard C data types that are then +used throughout the library. The most important of these types is +‘igraph_int_t’, which is an alias to either a 32-bit or a 64-bit +_signed_ integer, depending on whether igraph was compiled in 32-bit or +64-bit mode. The size of ‘igraph_int_t’ also influences the maximum +number of vertices that an igraph graph can represent as the number of +vertices is stored in a variable of type ‘igraph_int_t’. + + Before igraph 1.0, ‘igraph_int_t’ was called ‘igraph_integer_t’. +This is still available as an alias to ‘igraph_int_t’ and will remain +accessible until at least version 2.0 of the library. + + Since the size of a variable of type ‘igraph_int_t’ may change +depending on how igraph is compiled, you cannot simply use ‘%d’ or ‘%ld’ +as a placeholder for igraph integers in ‘printf’ format strings. igraph +provides the ‘IGRAPH_PRId’ macro, which maps to ‘d’, ‘ld’ or ‘lld’ +depending on the size of ‘igraph_int_t’, and you must use this macro in +‘printf’ format strings to avoid compiler warnings. + + Similarly to how ‘igraph_int_t’ maps to the standard size signed +integer in the library, ‘igraph_uint_t’ maps to a 32-bit or a 64-bit +_unsigned_ integer. It is guaranteed that the size of ‘igraph_int_t’ is +the same as the size of ‘igraph_uint_t’. igraph provides ‘IGRAPH_PRIu’ +as a format string placeholder for variables of type ‘igraph_uint_t’. + + Real numbers (i.e. quantities that can potentially be fractional or +infinite) are represented with a type named ‘igraph_real_t’. Currently +‘igraph_real_t’ is always aliased to ‘double’, but it is still good +practice to use ‘igraph_real_t’ in your own code for sake of +consistency. + + Boolean values are represented with a type named ‘igraph_bool_t’. It +tries to be as small as possible since it only needs to represent a +truth value. For printing purposes, you can treat it as an integer and +use ‘%d’ in format strings as a placeholder for an ‘igraph_bool_t’. + + Upper and lower limits of ‘igraph_int_t’ and ‘igraph_uint_t’ are +provided by the constants named ‘IGRAPH_INTEGER_MIN’, +‘IGRAPH_INTEGER_MAX’, ‘IGRAPH_UINT_MIN’ and ‘IGRAPH_UINT_MAX’. + + +File: igraph-docs.info, Node: Setup and initialization, Next: The basic interface, Prev: Atomic data types, Up: Basic data types and interface + +4.4 Setup and initialization +============================ + +Certain parts of igraph must be initialized before first use, which can +be accomplished using the setup functions below. As of igraph 1.0, most +functions will work correctly even if setup is not performed, as +currently the only setup action is seeding the random number generator. +That said, it is strongly recommended to call ‘igraph_setup()’ (*note +igraph_setup --- Initializes the igraph library_::) before using any +other function, as future igraph versions may add critical +initialization steps. + +* Menu: + +* igraph_setup -- Initializes the igraph library.: igraph_setup --- Initializes the igraph library_. + + +File: igraph-docs.info, Node: igraph_setup --- Initializes the igraph library_, Up: Setup and initialization + +4.4.1 igraph_setup -- Initializes the igraph library. +----------------------------------------------------- + + + igraph_error_t igraph_setup(void); + + This function is a convenience function to call all setup functions +that are provided by the igraph library. + + Most of the library functions will work even if this function is not +called, but it is recommended to call it before using any igraph +functions that may use random numbers, such as graph generators or +random sampling functions. This function initializes the random number +generator with a seed based on the current time, ensuring that the +random numbers generated by igraph are different each time the program +is run. + + *Returns:. * + +‘’ + Error code; currently always ‘IGRAPH_SUCCESS’. + + +File: igraph-docs.info, Node: The basic interface, Next: Miscellaneous macros and helper functions, Prev: Setup and initialization, Up: Basic data types and interface + +4.5 The basic interface +======================= + +This is the very minimal API in ‘igraph’. All the other functions use +this minimal set for creating and manipulating graphs. + + This is a very important principle since it makes possible to +implement other data representations by implementing only this minimal +set. + + This section lists all the functions and macros that are considered +as part of the core API from the point of view of the _users_ of igraph. +Some of these functions and macros have sensible default implementations +that simply call some other core function (e.g., ‘igraph_empty()’ (*note +igraph_empty --- Creates an empty graph with some vertices and no +edges_::) calls ‘igraph_empty_attrs()’ (*note igraph_empty_attrs --- +Creates an empty graph with some vertices; no edges and some graph +attributes_::) with a null attribute table pointer). If you wish to +experiment with implementing an alternative data type, the actual number +of functions that you need to replace is lower as you can rely on the +same default implementations in most cases. + +* Menu: + +* Graph constructors and destructors:: +* Basic query operations:: +* Adding and deleting vertices and edges:: + + +File: igraph-docs.info, Node: Graph constructors and destructors, Next: Basic query operations, Up: The basic interface + +4.5.1 Graph constructors and destructors +---------------------------------------- + +* Menu: + +* igraph_empty -- Creates an empty graph with some vertices and no edges.: igraph_empty --- Creates an empty graph with some vertices and no edges_. +* igraph_empty_attrs -- Creates an empty graph with some vertices, no edges and some graph attributes.: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_. +* igraph_copy -- Creates an exact (deep) copy of a graph.: igraph_copy --- Creates an exact [deep] copy of a graph_. +* igraph_destroy -- Frees the memory allocated for a graph object.: igraph_destroy --- Frees the memory allocated for a graph object_. + + +File: igraph-docs.info, Node: igraph_empty --- Creates an empty graph with some vertices and no edges_, Next: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_, Up: Graph constructors and destructors + +4.5.1.1 igraph_empty -- Creates an empty graph with some vertices and no edges. +............................................................................... + + + igraph_error_t igraph_empty(igraph_t *graph, igraph_int_t n, igraph_bool_t directed); + + The most basic constructor, all the other constructors should call +this to create a minimal graph object. Our use of the term "empty +graph" in the above description should be distinguished from the +mathematical definition of the empty or null graph. Strictly speaking, +the empty or null graph in graph theory is the graph with no vertices +and no edges. However by "empty graph" as used in ‘igraph’ we mean a +graph having zero or more vertices, but no edges. + + *Arguments:. * + +‘graph’: + Pointer to a not-yet initialized graph object. + +‘n’: + The number of vertices in the graph, a non-negative integer number + is expected. + +‘directed’: + Boolean; whether the graph is directed or not. Supported values + are: + + ‘IGRAPH_DIRECTED’ + The graph will be _directed._ + + ‘IGRAPH_UNDIRECTED’ + The graph will be _undirected._ + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of vertices. + + Time complexity: O(|V|) for a graph with |V| vertices (and no edges). + + * File examples/simple/creation.c* + + +File: igraph-docs.info, Node: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_, Next: igraph_copy --- Creates an exact [deep] copy of a graph_, Prev: igraph_empty --- Creates an empty graph with some vertices and no edges_, Up: Graph constructors and destructors + +4.5.1.2 igraph_empty_attrs -- Creates an empty graph with some vertices, no edges and some graph attributes. +............................................................................................................ + + + igraph_error_t igraph_empty_attrs( + igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + const igraph_attribute_record_list_t *attr + ); + + Use this instead of ‘igraph_empty()’ (*note igraph_empty --- Creates +an empty graph with some vertices and no edges_::) if you wish to add +some graph attributes right after initialization. This function is +currently not very interesting for the ordinary user. Just supply 0 +here or use ‘igraph_empty()’ (*note igraph_empty --- Creates an empty +graph with some vertices and no edges_::). + + This function does not set any vertex attributes. To create a graph +which has vertex attributes, call this function specifying 0 vertices, +then use ‘igraph_add_vertices()’ (*note igraph_add_vertices --- Adds +vertices to a graph_::) to add vertices and their attributes. + + *Arguments:. * + +‘graph’: + Pointer to a not-yet initialized graph object. + +‘n’: + The number of vertices in the graph; a non-negative integer number + is expected. + +‘directed’: + Boolean; whether the graph is directed or not. Supported values + are: + + ‘IGRAPH_DIRECTED’ + Create a _directed_ graph. + + ‘IGRAPH_UNDIRECTED’ + Create an _undirected_ graph. + +‘attr’: + The graph attributes. Supply ‘NULL’ if not graph attributes are to + be set. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of vertices. + + *See also:. * + +‘’ + ‘igraph_empty()’ (*note igraph_empty --- Creates an empty graph + with some vertices and no edges_::) to create an empty graph + without attributes; ‘igraph_add_vertices()’ (*note + igraph_add_vertices --- Adds vertices to a graph_::) and + ‘igraph_add_edges()’ (*note igraph_add_edges --- Adds edges to a + graph object_::) to add vertices and edges, possibly with + associated attributes. + + Time complexity: O(|V|) for a graph with |V| vertices (and no edges). + + +File: igraph-docs.info, Node: igraph_copy --- Creates an exact [deep] copy of a graph_, Next: igraph_destroy --- Frees the memory allocated for a graph object_, Prev: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_, Up: Graph constructors and destructors + +4.5.1.3 igraph_copy -- Creates an exact (deep) copy of a graph. +............................................................... + + + igraph_error_t igraph_copy(igraph_t *to, const igraph_t *from); + + This function deeply copies a graph object to create an exact replica +of it. The new replica should be destroyed by calling +‘igraph_destroy()’ (*note igraph_destroy --- Frees the memory allocated +for a graph object_::) on it when not needed any more. + + You can also create a shallow copy of a graph by simply using the +standard assignment operator, but be careful and do _not_ destroy a +shallow replica. To avoid this mistake, creating shallow copies is not +recommended. + + *Arguments:. * + +‘to’: + Pointer to an uninitialized graph object. + +‘from’: + Pointer to the graph object to copy. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|) for a graph with |V| vertices and |E| +edges. + + * File examples/simple/igraph_copy.c* + + +File: igraph-docs.info, Node: igraph_destroy --- Frees the memory allocated for a graph object_, Prev: igraph_copy --- Creates an exact [deep] copy of a graph_, Up: Graph constructors and destructors + +4.5.1.4 igraph_destroy -- Frees the memory allocated for a graph object. +........................................................................ + + + void igraph_destroy(igraph_t *graph); + + This function should be called for every graph object exactly once. + + This function invalidates all iterators (of course), but the +iterators of a graph should be destroyed before the graph itself anyway. + + *Arguments:. * + +‘graph’: + Pointer to the graph to free. + + Time complexity: operating system specific. + + +File: igraph-docs.info, Node: Basic query operations, Next: Adding and deleting vertices and edges, Prev: Graph constructors and destructors, Up: The basic interface + +4.5.2 Basic query operations +---------------------------- + +* Menu: + +* igraph_vcount -- The number of vertices in a graph.: igraph_vcount --- The number of vertices in a graph_. +* igraph_ecount -- The number of edges in a graph.: igraph_ecount --- The number of edges in a graph_. +* igraph_is_directed --- Is this a directed graph?:: +* igraph_edge -- Returns the head and tail vertices of an edge.: igraph_edge --- Returns the head and tail vertices of an edge_. +* igraph_edges -- Gives the head and tail vertices of a series of edges.: igraph_edges --- Gives the head and tail vertices of a series of edges_. +* IGRAPH_FROM -- The source vertex of an edge.: IGRAPH_FROM --- The source vertex of an edge_. +* IGRAPH_TO -- The target vertex of an edge.: IGRAPH_TO --- The target vertex of an edge_. +* IGRAPH_OTHER -- The other endpoint of an edge.: IGRAPH_OTHER --- The other endpoint of an edge_. +* igraph_get_eid -- Get the edge ID from the endpoints of an edge.: igraph_get_eid --- Get the edge ID from the endpoints of an edge_. +* igraph_get_eids -- Return edge IDs based on the adjacent vertices.: igraph_get_eids --- Return edge IDs based on the adjacent vertices_. +* igraph_get_all_eids_between -- Returns all edge IDs between a pair of vertices.: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_. +* igraph_neighbors -- Adjacent vertices to a vertex.: igraph_neighbors --- Adjacent vertices to a vertex_. +* igraph_incident -- Gives the incident edges of a vertex.: igraph_incident --- Gives the incident edges of a vertex_. +* igraph_degree -- The degree of some vertices in a graph.: igraph_degree --- The degree of some vertices in a graph_. +* igraph_degree_1 -- The degree of of a single vertex in the graph.: igraph_degree_1 --- The degree of of a single vertex in the graph_. + + +File: igraph-docs.info, Node: igraph_vcount --- The number of vertices in a graph_, Next: igraph_ecount --- The number of edges in a graph_, Up: Basic query operations + +4.5.2.1 igraph_vcount -- The number of vertices in a graph. +........................................................... + + + igraph_int_t igraph_vcount(const igraph_t *graph); + + *Arguments:. * + +‘graph’: + The graph. + + *Returns:. * + +‘’ + Number of vertices. + + Time complexity: O(1) + + +File: igraph-docs.info, Node: igraph_ecount --- The number of edges in a graph_, Next: igraph_is_directed --- Is this a directed graph?, Prev: igraph_vcount --- The number of vertices in a graph_, Up: Basic query operations + +4.5.2.2 igraph_ecount -- The number of edges in a graph. +........................................................ + + + igraph_int_t igraph_ecount(const igraph_t *graph); + + *Arguments:. * + +‘graph’: + The graph. + + *Returns:. * + +‘’ + Number of edges. + + Time complexity: O(1) + + +File: igraph-docs.info, Node: igraph_is_directed --- Is this a directed graph?, Next: igraph_edge --- Returns the head and tail vertices of an edge_, Prev: igraph_ecount --- The number of edges in a graph_, Up: Basic query operations + +4.5.2.3 igraph_is_directed -- Is this a directed graph? +....................................................... + + + igraph_bool_t igraph_is_directed(const igraph_t *graph); + + *Arguments:. * + +‘graph’: + The graph. + + *Returns:. * + +‘’ + Boolean value, ‘true’ if the graph is directed, ‘false’ otherwise. + + Time complexity: O(1) + + * File examples/simple/igraph_is_directed.c* + + +File: igraph-docs.info, Node: igraph_edge --- Returns the head and tail vertices of an edge_, Next: igraph_edges --- Gives the head and tail vertices of a series of edges_, Prev: igraph_is_directed --- Is this a directed graph?, Up: Basic query operations + +4.5.2.4 igraph_edge -- Returns the head and tail vertices of an edge. +..................................................................... + + + igraph_error_t igraph_edge( + const igraph_t *graph, igraph_int_t eid, + igraph_int_t *from, igraph_int_t *to + ); + + *Arguments:. * + +‘graph’: + The graph object. + +‘eid’: + The edge ID. + +‘from’: + Pointer to an ‘igraph_int_t’. The tail (source) of the edge will + be placed here. + +‘to’: + Pointer to an ‘igraph_int_t’. The head (target) of the edge will + be placed here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_get_eid()’ (*note igraph_get_eid --- Get the edge ID from + the endpoints of an edge_::) for the opposite operation; + ‘igraph_edges()’ (*note igraph_edges --- Gives the head and tail + vertices of a series of edges_::) to get the endpoints of several + edges; ‘IGRAPH_TO()’ (*note IGRAPH_TO --- The target vertex of an + edge_::), ‘IGRAPH_FROM()’ (*note IGRAPH_FROM --- The source vertex + of an edge_::) and ‘IGRAPH_OTHER()’ (*note IGRAPH_OTHER --- The + other endpoint of an edge_::) for a faster but non-error-checked + version. + + Added in version 0.2. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_edges --- Gives the head and tail vertices of a series of edges_, Next: IGRAPH_FROM --- The source vertex of an edge_, Prev: igraph_edge --- Returns the head and tail vertices of an edge_, Up: Basic query operations + +4.5.2.5 igraph_edges -- Gives the head and tail vertices of a series of edges. +.............................................................................. + + + igraph_error_t igraph_edges( + const igraph_t *graph, igraph_es_t eids, igraph_vector_int_t *edges, + igraph_bool_t bycol + ); + + *Arguments:. * + +‘graph’: + The graph object. + +‘eids’: + Edge selector, the series of edges. + +‘edges’: + Pointer to an initialized vector. The start and endpoints of each + edge will be placed here. + +‘bycol’: + Boolean constant. If true, the edges will be returned columnwise, + e.g. the first edge is ‘res[0]->res[|E|]’, the second is + ‘res[1]->res[|E|+1]’, etc. Supply false to get the edge list in a + format compatible with ‘igraph_add_edges()’ (*note igraph_add_edges + --- Adds edges to a graph object_::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_get_eids()’ (*note igraph_get_eids --- Return edge IDs + based on the adjacent vertices_::) for the opposite operation; + ‘igraph_edge()’ (*note igraph_edge --- Returns the head and tail + vertices of an edge_::) for getting the endpoints of a single edge; + ‘IGRAPH_TO()’ (*note IGRAPH_TO --- The target vertex of an + edge_::), ‘IGRAPH_FROM()’ (*note IGRAPH_FROM --- The source vertex + of an edge_::) and ‘IGRAPH_OTHER()’ (*note IGRAPH_OTHER --- The + other endpoint of an edge_::) for a faster but non-error-checked + method. + + Time complexity: O(k) where k is the number of edges in the selector. + + +File: igraph-docs.info, Node: IGRAPH_FROM --- The source vertex of an edge_, Next: IGRAPH_TO --- The target vertex of an edge_, Prev: igraph_edges --- Gives the head and tail vertices of a series of edges_, Up: Basic query operations + +4.5.2.6 IGRAPH_FROM -- The source vertex of an edge. +.................................................... + + + #define IGRAPH_FROM(graph,eid) + + Faster than ‘igraph_edge()’ (*note igraph_edge --- Returns the head +and tail vertices of an edge_::), but no error checking is done: ‘eid’ +is assumed to be valid. + + *Arguments:. * + +‘graph’: + The graph. + +‘eid’: + The edge ID. + + *Returns:. * + +‘’ + The source vertex of the edge. + + *See also:. * + +‘’ + ‘igraph_edge()’ (*note igraph_edge --- Returns the head and tail + vertices of an edge_::) if error checking is desired. + + +File: igraph-docs.info, Node: IGRAPH_TO --- The target vertex of an edge_, Next: IGRAPH_OTHER --- The other endpoint of an edge_, Prev: IGRAPH_FROM --- The source vertex of an edge_, Up: Basic query operations + +4.5.2.7 IGRAPH_TO -- The target vertex of an edge. +.................................................. + + + #define IGRAPH_TO(graph,eid) + + Faster than ‘igraph_edge()’ (*note igraph_edge --- Returns the head +and tail vertices of an edge_::), but no error checking is done: ‘eid’ +is assumed to be valid. + + *Arguments:. * + +‘graph’: + The graph object. + +‘eid’: + The edge ID. + + *Returns:. * + +‘’ + The target vertex of the edge. + + *See also:. * + +‘’ + ‘igraph_edge()’ (*note igraph_edge --- Returns the head and tail + vertices of an edge_::) if error checking is desired. + + +File: igraph-docs.info, Node: IGRAPH_OTHER --- The other endpoint of an edge_, Next: igraph_get_eid --- Get the edge ID from the endpoints of an edge_, Prev: IGRAPH_TO --- The target vertex of an edge_, Up: Basic query operations + +4.5.2.8 IGRAPH_OTHER -- The other endpoint of an edge. +...................................................... + + + #define IGRAPH_OTHER(graph,eid,vid) + + Typically used with undirected edges when one endpoint of the edge is +known, and the other endpoint is needed. No error checking is done: +‘eid’ and ‘vid’ are assumed to be valid. + + *Arguments:. * + +‘graph’: + The graph object. + +‘eid’: + The edge ID. + +‘vid’: + The vertex ID of one endpoint of an edge. + + *Returns:. * + +‘’ + The other endpoint of the edge. + + *See also:. * + +‘’ + ‘IGRAPH_TO()’ (*note IGRAPH_TO --- The target vertex of an edge_::) + and ‘IGRAPH_FROM()’ (*note IGRAPH_FROM --- The source vertex of an + edge_::) to get the source and target of directed edges. + + +File: igraph-docs.info, Node: igraph_get_eid --- Get the edge ID from the endpoints of an edge_, Next: igraph_get_eids --- Return edge IDs based on the adjacent vertices_, Prev: IGRAPH_OTHER --- The other endpoint of an edge_, Up: Basic query operations + +4.5.2.9 igraph_get_eid -- Get the edge ID from the endpoints of an edge. +........................................................................ + + + igraph_error_t igraph_get_eid(const igraph_t *graph, igraph_int_t *eid, + igraph_int_t from, igraph_int_t to, + igraph_bool_t directed, igraph_bool_t error); + + For undirected graphs ‘from’ and ‘to’ are exchangeable. + + *Arguments:. * + +‘graph’: + The graph object. + +‘eid’: + Pointer to an integer, the edge ID will be stored here. If ‘error’ + is false and no edge was found, ‘-1’ will be returned. + +‘from’: + The starting point of the edge. + +‘to’: + The end point of the edge. + +‘directed’: + Boolean, whether to search for directed edges in a directed graph. + Ignored for undirected graphs. + +‘error’: + Boolean, whether to report an error if the edge was not found. If + it is false, then ‘-1’ will be assigned to ‘eid’. Note that + invalid vertex IDs in input arguments (‘from’ or ‘to’) always + trigger an error, regardless of this setting. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_edge()’ (*note igraph_edge --- Returns the head and tail + vertices of an edge_::) for the opposite operation, + ‘igraph_get_all_eids_between()’ (*note igraph_get_all_eids_between + --- Returns all edge IDs between a pair of vertices_::) to retrieve + all edge IDs between a pair of vertices. + + Time complexity: O(log (d)), where d is smaller of the out-degree of +‘from’ and in-degree of ‘to’ if ‘directed’ is true. If ‘directed’ is +false, then it is O(log(d)+log(d2)), where d is the same as before and +d2 is the minimum of the out-degree of ‘to’ and the in-degree of ‘from’. + + * File examples/simple/igraph_get_eid.c* + + +File: igraph-docs.info, Node: igraph_get_eids --- Return edge IDs based on the adjacent vertices_, Next: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_, Prev: igraph_get_eid --- Get the edge ID from the endpoints of an edge_, Up: Basic query operations + +4.5.2.10 igraph_get_eids -- Return edge IDs based on the adjacent vertices. +........................................................................... + + + igraph_error_t igraph_get_eids(const igraph_t *graph, igraph_vector_int_t *eids, + const igraph_vector_int_t *pairs, + igraph_bool_t directed, igraph_bool_t error); + + The pairs of vertex IDs for which the edges are looked up are taken +consecutively from the ‘pairs’ vector, i.e. ‘VECTOR(pairs)[0]’ and +‘VECTOR(pairs)[1]’ specify the first pair, ‘VECTOR(pairs)[2]’ and +‘VECTOR(pairs)[3]’ the second pair, etc. + + If you have a sequence of vertex IDs that describe a _path_ on the +graph, use ‘igraph_expand_path_to_pairs()’ (*note +igraph_expand_path_to_pairs --- Helper function to convert a sequence of +vertex IDs describing a path into a "pairs" vector_::) to convert them +to a list of vertex pairs along the path. + + If the ‘error’ argument is true, then it is an error to specify pairs +of vertices that are not connected. Otherwise -1 is reported for vertex +pairs without at least one edge between them. + + If there are multiple edges in the graph, then these are ignored; +i.e. for a given pair of vertex IDs, igraph always returns the same +edge ID, even if the pair appears multiple times in ‘pairs’. + + *Arguments:. * + +‘graph’: + The input graph. + +‘eids’: + Pointer to an initialized vector, the result is stored here. It + will be resized as needed. + +‘pairs’: + Vector giving pairs of vertices to fetch the edges for. + +‘directed’: + Boolean, whether to consider edge directions in directed graphs. + This is ignored for undirected graphs. + +‘error’: + Boolean, whether it is an error to supply non-connected vertices. + If false, then -1 is returned for non-connected pairs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n log(d)), where n is the number of queried edges +and d is the average degree of the vertices. + + *See also:. * + +‘’ + ‘igraph_get_eid()’ (*note igraph_get_eid --- Get the edge ID from + the endpoints of an edge_::) for a single edge. + + * File examples/simple/igraph_get_eids.c* + + +File: igraph-docs.info, Node: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_, Next: igraph_neighbors --- Adjacent vertices to a vertex_, Prev: igraph_get_eids --- Return edge IDs based on the adjacent vertices_, Up: Basic query operations + +4.5.2.11 igraph_get_all_eids_between -- Returns all edge IDs between a pair of vertices. +........................................................................................ + + + igraph_error_t igraph_get_all_eids_between( + const igraph_t *graph, igraph_vector_int_t *eids, + igraph_int_t source, igraph_int_t target, igraph_bool_t directed + ); + + For undirected graphs ‘source’ and ‘target’ are exchangeable. + + *Arguments:. * + +‘graph’: + The input graph. + +‘eids’: + Pointer to an initialized vector, the result is stored here. It + will be resized as needed. + +‘source’: + The ID of the source vertex + +‘target’: + The ID of the target vertex + +‘directed’: + Boolean, whether to consider edge directions in directed graphs. + This is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO + + *See also:. * + +‘’ + ‘igraph_get_eid()’ (*note igraph_get_eid --- Get the edge ID from + the endpoints of an edge_::) for a single edge. + + +File: igraph-docs.info, Node: igraph_neighbors --- Adjacent vertices to a vertex_, Next: igraph_incident --- Gives the incident edges of a vertex_, Prev: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_, Up: Basic query operations + +4.5.2.12 igraph_neighbors -- Adjacent vertices to a vertex. +........................................................... + + + igraph_error_t igraph_neighbors( + const igraph_t *graph, igraph_vector_int_t *neis, igraph_int_t pnode, + igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple + ); + + *Arguments:. * + +‘graph’: + The graph to work on. + +‘neis’: + This vector will contain the result. The vector should be + initialized beforehand and will be resized. Starting from igraph + version 0.4 this vector is always sorted, the vertex IDs are in + increasing order. If one neighbor is connected with multiple + edges, the neighbor will be returned multiple times. + +‘pnode’: + The id of the node for which the adjacent vertices are to be + searched. + +‘mode’: + Defines the way adjacent vertices are searched in directed graphs. + It can have the following values: ‘IGRAPH_OUT’, vertices reachable + by an edge from the specified vertex are searched; ‘IGRAPH_IN’, + vertices from which the specified vertex is reachable are searched; + ‘IGRAPH_ALL’, both kinds of vertices are searched. This parameter + is ignored for undirected graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the result. ‘IGRAPH_LOOPS_ONCE’ makes each loop edge + appear only once in the result. ‘IGRAPH_LOOPS_TWICE’ makes loop + edges appear _twice_ in the result if the graph is undirected or + ‘mode’ is set to ‘IGRAPH_ALL’ (and once otherwise as returning them + twice does not make sense for directed graphs). + +‘multiple’: + Specifies how to treat multiple (parallel) edges. + ‘IGRAPH_NO_MULTIPLE’ collapses parallel edges into a single one; + ‘IGRAPH_MULTIPLE’ keeps the multiplicities of parallel edges so the + same neighbor will appear as many times in the result as the number + of parallel edges going between the two vertices. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. ‘IGRAPH_EINVMODE’: + invalid mode argument. ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: O(d), d is the number of adjacent vertices to the +queried vertex. + + * File examples/simple/igraph_neighbors.c* + + +File: igraph-docs.info, Node: igraph_incident --- Gives the incident edges of a vertex_, Next: igraph_degree --- The degree of some vertices in a graph_, Prev: igraph_neighbors --- Adjacent vertices to a vertex_, Up: Basic query operations + +4.5.2.13 igraph_incident -- Gives the incident edges of a vertex. +................................................................. + + + igraph_error_t igraph_incident( + const igraph_t *graph, igraph_vector_int_t *eids, igraph_int_t pnode, + igraph_neimode_t mode, igraph_loops_t loops + ); + + *Arguments:. * + +‘graph’: + The graph object. + +‘eids’: + An initialized vector. It will be resized to hold the result. + +‘pnode’: + A vertex ID. + +‘mode’: + Specifies what kind of edges to include for directed graphs. + ‘IGRAPH_OUT’ means only outgoing edges, ‘IGRAPH_IN’ means only + incoming edges, ‘IGRAPH_ALL’ means both. This parameter is ignored + for undirected graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the result. ‘IGRAPH_LOOPS_ONCE’ makes each loop edge + appear only once in the result. ‘IGRAPH_LOOPS_TWICE’ makes loop + edges appear _twice_ in the result if the graph is undirected or + ‘mode’ is set to ‘IGRAPH_ALL’ (and once otherwise as returning them + twice does not make sense for directed graphs). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. ‘IGRAPH_EINVMODE’: + invalid mode argument. ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: O(d), the number of incident edges to ‘pnode’. + + +File: igraph-docs.info, Node: igraph_degree --- The degree of some vertices in a graph_, Next: igraph_degree_1 --- The degree of of a single vertex in the graph_, Prev: igraph_incident --- Gives the incident edges of a vertex_, Up: Basic query operations + +4.5.2.14 igraph_degree -- The degree of some vertices in a graph. +................................................................. + + + igraph_error_t igraph_degree( + const igraph_t *graph, igraph_vector_int_t *res, const igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops + ); + + This function calculates the in-, out- or total degree of the +specified vertices. + + This function returns the result as a vector of ‘igraph_int_t’ +values. In applications where ‘igraph_real_t’ is desired, use +‘igraph_strength()’ (*note igraph_strength --- Strength of the vertices; +also called weighted vertex degree_::) with ‘NULL’ weights. + + *Arguments:. * + +‘graph’: + The graph. + +‘res’: + Integer vector, this will contain the result. It should be + initialized and will be resized to be the appropriate size. + +‘vids’: + Vertex selector, giving the vertex IDs of which the degree will be + calculated. + +‘mode’: + Defines the type of the degree for directed graphs. Valid modes + are: ‘IGRAPH_OUT’, out-degree; ‘IGRAPH_IN’, in-degree; + ‘IGRAPH_ALL’, total degree (sum of the in- and out-degree). This + parameter is ignored for undirected graphs. + +‘loops’: + Constant of type ‘igraph_loops_t’ (*note igraph_loops_t --- How to + interpret self-loops in undirected graphs?::), specifies how to + treat loop edges when calculating the degree. ‘IGRAPH_NO_LOOPS’ + ignores loop edges; ‘IGRAPH_LOOPS_ONCE’ counts each loop edge only + once; ‘IGRAPH_LOOPS_TWICE’ counts each loop edge twice in + undirected graphs and once in directed graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. ‘IGRAPH_EINVMODE’: + invalid mode argument. + + Time complexity: O(v) if ‘loops’ is ‘true’, and O(v*d) otherwise. v +is the number of vertices for which the degree will be calculated, and d +is their (average) degree. + + *See also:. * + +‘’ + ‘igraph_strength()’ (*note igraph_strength --- Strength of the + vertices; also called weighted vertex degree_::) for the version + that takes into account edge weights; ‘igraph_degree_1()’ (*note + igraph_degree_1 --- The degree of of a single vertex in the + graph_::) to efficiently compute the degree of a single vertex; + ‘igraph_maxdegree()’ (*note igraph_maxdegree --- The maximum degree + in a graph [or set of vertices]_::) if you only need the largest + degree. + + * File examples/simple/igraph_degree.c* + + +File: igraph-docs.info, Node: igraph_degree_1 --- The degree of of a single vertex in the graph_, Prev: igraph_degree --- The degree of some vertices in a graph_, Up: Basic query operations + +4.5.2.15 igraph_degree_1 -- The degree of of a single vertex in the graph. +.......................................................................... + + + igraph_error_t igraph_degree_1( + const igraph_t *graph, igraph_int_t *deg, igraph_int_t vid, + igraph_neimode_t mode, igraph_loops_t loops + ); + + This function calculates the in-, out- or total degree of a single +vertex. For a single vertex, it is more efficient than calling +‘igraph_degree()’ (*note igraph_degree --- The degree of some vertices +in a graph_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘deg’: + Pointer to the integer where the computed degree will be stored. + +‘vid’: + The vertex for which the degree will be calculated. + +‘mode’: + Defines the type of the degree for directed graphs. Valid modes + are: ‘IGRAPH_OUT’, out-degree; ‘IGRAPH_IN’, in-degree; + ‘IGRAPH_ALL’, total degree (sum of the in- and out-degree). This + parameter is ignored for undirected graphs. + +‘loops’: + Boolean, gives whether the self-loops should be counted. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_degree()’ (*note igraph_degree --- The degree of some + vertices in a graph_::) to compute the degree of several vertices + at once. + + Time complexity: O(1) if ‘loops’ is ‘true’, and O(d) otherwise, where +d is the degree. + + +File: igraph-docs.info, Node: Adding and deleting vertices and edges, Prev: Basic query operations, Up: The basic interface + +4.5.3 Adding and deleting vertices and edges +-------------------------------------------- + +* Menu: + +* igraph_add_edge -- Adds a single edge to a graph.: igraph_add_edge --- Adds a single edge to a graph_. +* igraph_add_edges -- Adds edges to a graph object.: igraph_add_edges --- Adds edges to a graph object_. +* igraph_add_vertices -- Adds vertices to a graph.: igraph_add_vertices --- Adds vertices to a graph_. +* igraph_delete_edges -- Removes edges from a graph.: igraph_delete_edges --- Removes edges from a graph_. +* igraph_delete_vertices -- Removes some vertices (with all their edges) from the graph.: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_. +* igraph_delete_vertices_map -- Removes some vertices (with all their edges) from the graph.: igraph_delete_vertices_map --- Removes some vertices [with all their edges] from the graph_. + + +File: igraph-docs.info, Node: igraph_add_edge --- Adds a single edge to a graph_, Next: igraph_add_edges --- Adds edges to a graph object_, Up: Adding and deleting vertices and edges + +4.5.3.1 igraph_add_edge -- Adds a single edge to a graph. +......................................................... + + + igraph_error_t igraph_add_edge(igraph_t *graph, igraph_int_t from, igraph_int_t to); + + For directed graphs the edge points from ‘from’ to ‘to’. + + Note that if you want to add many edges to a big graph, then it is +inefficient to add them one by one, it is better to collect them into a +vector and add all of them via a single ‘igraph_add_edges()’ (*note +igraph_add_edges --- Adds edges to a graph object_::) call. + + *Arguments:. * + +‘graph’: + The graph. + +‘from’: + The id of the first vertex of the edge. + +‘to’: + The id of the second vertex of the edge. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_add_edges()’ (*note igraph_add_edges --- Adds edges to a + graph object_::) to add many edges, ‘igraph_delete_edges()’ (*note + igraph_delete_edges --- Removes edges from a graph_::) to remove + edges and ‘igraph_add_vertices()’ (*note igraph_add_vertices --- + Adds vertices to a graph_::) to add vertices. + + Time complexity: O(|V|+|E|), the number of edges plus the number of +vertices. + + +File: igraph-docs.info, Node: igraph_add_edges --- Adds edges to a graph object_, Next: igraph_add_vertices --- Adds vertices to a graph_, Prev: igraph_add_edge --- Adds a single edge to a graph_, Up: Adding and deleting vertices and edges + +4.5.3.2 igraph_add_edges -- Adds edges to a graph object. +......................................................... + + + igraph_error_t igraph_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr + ); + + The edges are given in a vector, the first two elements define the +first edge (the order is ‘from’, ‘to’ for directed graphs). The vector +should contain even number of integer numbers between zero and the +number of vertices in the graph minus one (inclusive). If you also want +to add new vertices, call ‘igraph_add_vertices()’ (*note +igraph_add_vertices --- Adds vertices to a graph_::) first. + + *Arguments:. * + +‘graph’: + The graph to which the edges will be added. + +‘edges’: + The edges themselves. + +‘attr’: + The attributes of the new edges. You can supply a null pointer + here if you do not need edge attributes. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid (odd) edges vector length, + ‘IGRAPH_EINVVID’: invalid vertex ID in edges vector. + + This function invalidates all iterators. + + Time complexity: O(|V|+|E|) where |V| is the number of vertices and +|E| is the number of edges in the _new,_ extended graph. + + * File examples/simple/creation.c* + + +File: igraph-docs.info, Node: igraph_add_vertices --- Adds vertices to a graph_, Next: igraph_delete_edges --- Removes edges from a graph_, Prev: igraph_add_edges --- Adds edges to a graph object_, Up: Adding and deleting vertices and edges + +4.5.3.3 igraph_add_vertices -- Adds vertices to a graph. +........................................................ + + + igraph_error_t igraph_add_vertices( + igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr + ); + + This function invalidates all iterators. + + *Arguments:. * + +‘graph’: + The graph object to extend. + +‘nv’: + Non-negative integer specifying the number of vertices to add. + +‘attr’: + The attributes of the new vertices. You can supply a null pointer + here if you do not need vertex attributes. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of new vertices. + + Time complexity: O(|V|) where |V| is the number of vertices in the +_new,_ extended graph. + + * File examples/simple/creation.c* + + +File: igraph-docs.info, Node: igraph_delete_edges --- Removes edges from a graph_, Next: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_, Prev: igraph_add_vertices --- Adds vertices to a graph_, Up: Adding and deleting vertices and edges + +4.5.3.4 igraph_delete_edges -- Removes edges from a graph. +.......................................................... + + + igraph_error_t igraph_delete_edges(igraph_t *graph, igraph_es_t edges); + + The edges to remove are specified as an edge selector. + + This function cannot remove vertices; vertices will be kept even if +they lose all their edges. + + This function invalidates all iterators. + + *Arguments:. * + +‘graph’: + The graph to work on. + +‘edges’: + The edges to remove. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|) where |V| and |E| are the number of +vertices and edges in the _original_ graph, respectively. + + * File examples/simple/igraph_delete_edges.c* + + +File: igraph-docs.info, Node: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_, Next: igraph_delete_vertices_map --- Removes some vertices [with all their edges] from the graph_, Prev: igraph_delete_edges --- Removes edges from a graph_, Up: Adding and deleting vertices and edges + +4.5.3.5 igraph_delete_vertices -- Removes some vertices (with all their edges) from the graph. +.............................................................................................. + + + igraph_error_t igraph_delete_vertices(igraph_t *graph, const igraph_vs_t vertices); + + This function changes the IDs of the vertices (except in some very +special cases, but these should not be relied on anyway). + + This function invalidates all iterators. + + *Arguments:. * + +‘graph’: + The graph to work on. + +‘vertices’: + The IDs of the vertices to remove, in a vector. The vector may + contain the same ID more than once. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. + + Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices +and edges in the original graph. + + * File examples/simple/igraph_delete_vertices.c* + + +File: igraph-docs.info, Node: igraph_delete_vertices_map --- Removes some vertices [with all their edges] from the graph_, Prev: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_, Up: Adding and deleting vertices and edges + +4.5.3.6 igraph_delete_vertices_map -- Removes some vertices (with all their edges) from the graph. +.................................................................................................. + + + igraph_error_t igraph_delete_vertices_map( + igraph_t *graph, const igraph_vs_t vertices, igraph_vector_int_t *map, + igraph_vector_int_t *invmap + ); + + This function changes the IDs of the vertices (except in some very +special cases, but these should not be relied on anyway). You can use +the ‘map’ argument to obtain the mapping from old vertex IDs to the new +ones, and the ‘newmap’ argument to obtain the reverse mapping. + + This function invalidates all iterators. + + *Arguments:. * + +‘graph’: + The graph to work on. + +‘vertices’: + The IDs of the vertices to remove, in a vector. The vector may + contain the same ID more than once. + +‘map’: + An optional pointer to a vector that provides the mapping from the + vertex IDs _before_ the removal to the vertex IDs _after_ the + removal. You can supply ‘NULL’ here if you are not interested. + +‘invmap’: + An optional pointer to a vector that provides the mapping from the + vertex IDs _after_ the removal to the vertex IDs _before_ the + removal. You can supply ‘NULL’ here if you are not interested. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. + + Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices +and edges in the original graph. + + +File: igraph-docs.info, Node: Miscellaneous macros and helper functions, Prev: The basic interface, Up: Basic data types and interface + +4.6 Miscellaneous macros and helper functions +============================================= + +* Menu: + +* IGRAPH_VCOUNT_MAX -- The maximum number of vertices supported in igraph graphs.: IGRAPH_VCOUNT_MAX --- The maximum number of vertices supported in igraph graphs_. +* IGRAPH_ECOUNT_MAX -- The maximum number of edges supported in igraph graphs.: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_. +* IGRAPH_UNLIMITED -- Constant for "do not limit results".: IGRAPH_UNLIMITED --- Constant for "do not limit results"_. +* igraph_expand_path_to_pairs -- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector.: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_. +* igraph_invalidate_cache -- Invalidates the internal cache of an igraph graph.: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_. +* igraph_is_same_graph --- Are two graphs identical as labelled graphs?:: + + +File: igraph-docs.info, Node: IGRAPH_VCOUNT_MAX --- The maximum number of vertices supported in igraph graphs_, Next: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_, Up: Miscellaneous macros and helper functions + +4.6.1 IGRAPH_VCOUNT_MAX -- The maximum number of vertices supported in igraph graphs. +------------------------------------------------------------------------------------- + + + #define IGRAPH_VCOUNT_MAX + + The value of this constant is one less than ‘IGRAPH_INTEGER_MAX’ . +When igraph is compiled in 32-bit mode, this means that you are limited +to 2^31 - 2 (about 2.1 billion) vertices. In 64-bit mode, the limit is +2^63 - 2 so you are much more likely to hit out-of-memory issues due to +other reasons before reaching this limit. + + +File: igraph-docs.info, Node: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_, Next: IGRAPH_UNLIMITED --- Constant for "do not limit results"_, Prev: IGRAPH_VCOUNT_MAX --- The maximum number of vertices supported in igraph graphs_, Up: Miscellaneous macros and helper functions + +4.6.2 IGRAPH_ECOUNT_MAX -- The maximum number of edges supported in igraph graphs. +---------------------------------------------------------------------------------- + + + #define IGRAPH_ECOUNT_MAX + + The value of this constant is half of ‘IGRAPH_INTEGER_MAX’ . When +igraph is compiled in 32-bit mode, this means that you are limited to +approximately 2^30 (about 1.07 billion) vertices. In 64-bit mode, the +limit is approximately 2^62 so you are much more likely to hit +out-of-memory issues due to other reasons before reaching this limit. + + +File: igraph-docs.info, Node: IGRAPH_UNLIMITED --- Constant for "do not limit results"_, Next: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_, Prev: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_, Up: Miscellaneous macros and helper functions + +4.6.3 IGRAPH_UNLIMITED -- Constant for "do not limit results". +-------------------------------------------------------------- + + + #define IGRAPH_UNLIMITED + + A constant signifying that no limitation should be used with various +cutoff, size limit or result set size parameters, such as minimum or +maximum clique size, number of results returned, cutoff for path +lengths, etc. Currently defined to ‘-1’. + + +File: igraph-docs.info, Node: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_, Next: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_, Prev: IGRAPH_UNLIMITED --- Constant for "do not limit results"_, Up: Miscellaneous macros and helper functions + +4.6.4 igraph_expand_path_to_pairs -- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector. +--------------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_expand_path_to_pairs(igraph_vector_int_t* path); + + This function is useful when you have a sequence of vertex IDs in a +graph and you would like to retrieve the IDs of the edges between them. +The function duplicates all but the first and the last elements in the +vector, effectively converting the path into a vector of vertex IDs that +can be passed to ‘igraph_get_eids()’ (*note igraph_get_eids --- Return +edge IDs based on the adjacent vertices_::). + + *Arguments:. * + +‘path’: + the input vector. It will be modified in-place and it will be + resized as needed. When the vector contains less than two vertex + IDs, it will be cleared. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory to expand + the vector. + + +File: igraph-docs.info, Node: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_, Next: igraph_is_same_graph --- Are two graphs identical as labelled graphs?, Prev: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_, Up: Miscellaneous macros and helper functions + +4.6.5 igraph_invalidate_cache -- Invalidates the internal cache of an igraph graph. +----------------------------------------------------------------------------------- + + + void igraph_invalidate_cache(const igraph_t* graph); + + igraph graphs cache some basic properties about themselves in an +internal data structure. This function invalidates the contents of the +cache and forces a recalculation of the cached properties the next time +they are needed. + + You should not need to call this function during normal usage; +however, we might ask you to call this function explicitly if we suspect +that you are running into a bug in igraph's cache handling. A tell-tale +sign of an invalid cache entry is that the result of a cached igraph +function (such as ‘igraph_is_dag()’ (*note igraph_is_dag --- Checks +whether a graph is a directed acyclic graph [DAG]_::) or +‘igraph_is_simple()’ (*note igraph_is_simple --- Decides whether the +input graph is a simple graph_::)) is different before and after a cache +invalidation. + + *Arguments:. * + +‘graph’: + The graph whose cache is to be invalidated. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_is_same_graph --- Are two graphs identical as labelled graphs?, Prev: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_, Up: Miscellaneous macros and helper functions + +4.6.6 igraph_is_same_graph -- Are two graphs identical as labelled graphs? +-------------------------------------------------------------------------- + + + igraph_error_t igraph_is_same_graph(const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *res); + + Two graphs are considered to be the same if they have the same vertex +and edge sets. Graphs which are the same may have multiple different +representations in igraph, hence the need for this function. + + This function verifies that the two graphs have the same +directedness, the same number of vertices, and that they contain +precisely the same edges (regardless of their ordering) when written in +terms of vertex indices. Graph attributes are not taken into account. + + This concept is different from isomorphism. For example, the graphs +‘0-1, 2-1’ and ‘1-2, 0-1’ are considered the same because they only +differ in the ordering of their edge lists and the ordering of vertices +in an undirected edge. However, they are not the same as ‘0-2, 1-2’, +even though they are isomorphic to it. Note that this latter graph +contains the edge ‘0-2’ while the former two do not -- thus their edge +sets differ. + + *Arguments:. * + +‘graph1’: + The first graph object. + +‘graph2’: + The second graph object. + +‘res’: + The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(E), the number of edges in the graphs. + + *See also:. * + +‘’ + ‘igraph_isomorphic()’ (*note igraph_isomorphic --- Are two graphs + isomorphic?::) to test if two graphs are isomorphic. + + +File: igraph-docs.info, Node: Error handling, Next: Memory [de]allocation, Prev: Basic data types and interface, Up: Top + +5 Error handling +**************** + +* Menu: + +* Error handling basics:: +* Error handlers:: +* Error codes:: +* Warning messages:: +* Advanced topics:: + + +File: igraph-docs.info, Node: Error handling basics, Next: Error handlers, Up: Error handling + +5.1 Error handling basics +========================= + +‘igraph’ functions can run into various problems preventing them from +normal operation. The user might have supplied invalid arguments, e.g. +a non-square matrix when a square-matrix was expected, or the program +has run out of memory while some more memory allocation is required, +etc. + + By default ‘igraph’ aborts the program when it runs into an error. +While this behavior might be good enough for smaller programs, it is +without doubt avoidable in larger projects. Please read further if your +project requires more sophisticated error handling. You can safely skip +the rest of this chapter otherwise. + + +File: igraph-docs.info, Node: Error handlers, Next: Error codes, Prev: Error handling basics, Up: Error handling + +5.2 Error handlers +================== + +If ‘igraph’ runs into an error - an invalid argument was supplied to a +function, or we've ran out of memory - the control is transferred to the +_ error handler _ function. + + The default error handler is ‘igraph_error_handler_abort’ (*note +igraph_error_handler_abort --- Abort program in case of error_::) which +prints an error message and aborts the program. + + The ‘igraph_set_error_handler()’ (*note igraph_set_error_handler --- +Sets a new error handler_::) function can be used to set a new error +handler function of type ‘igraph_error_handler_t’ (*note +igraph_error_handler_t --- The type of error handler functions_::); see +the documentation of this type for details. + + There are two other predefined error handler functions, +‘igraph_error_handler_ignore’ (*note igraph_error_handler_ignore --- +Ignore errors_::) and ‘igraph_error_handler_printignore’ (*note +igraph_error_handler_printignore --- Print and ignore errors_::). These +deallocate the temporarily allocated memory (more about this later) and +return with the error code. The latter also prints an error message. +If you use these error handlers you need to take care about possible +errors yourself by checking the return value of (almost) every non-void +‘igraph’ function. + + Independently of the error handler installed, all functions in the +library do their best to leave their arguments _semantically_ unchanged +if an error happens. By semantically we mean that the implementation of +an object supplied as an argument might change, but its 'meaning' in +most cases does not. The rare occasions when this rule is violated are +documented in this manual. + +* Menu: + +* igraph_error_handler_t -- The type of error handler functions.: igraph_error_handler_t --- The type of error handler functions_. +* igraph_error_handler_abort -- Abort program in case of error.: igraph_error_handler_abort --- Abort program in case of error_. +* igraph_error_handler_ignore -- Ignore errors.: igraph_error_handler_ignore --- Ignore errors_. +* igraph_error_handler_printignore -- Print and ignore errors.: igraph_error_handler_printignore --- Print and ignore errors_. + + +File: igraph-docs.info, Node: igraph_error_handler_t --- The type of error handler functions_, Next: igraph_error_handler_abort --- Abort program in case of error_, Up: Error handlers + +5.2.1 igraph_error_handler_t -- The type of error handler functions. +-------------------------------------------------------------------- + + + typedef void igraph_error_handler_t(const char *reason, const char *file, + int line, igraph_error_t igraph_errno); + + This is the type of the error handler functions. + + *Arguments:. * + +‘reason’: + Textual description of the error. + +‘file’: + The source file in which the error is noticed. + +‘line’: + The number of the line in the source file which triggered the error + +‘igraph_errno’: + The ‘igraph’ error code. + + +File: igraph-docs.info, Node: igraph_error_handler_abort --- Abort program in case of error_, Next: igraph_error_handler_ignore --- Ignore errors_, Prev: igraph_error_handler_t --- The type of error handler functions_, Up: Error handlers + +5.2.2 igraph_error_handler_abort -- Abort program in case of error. +------------------------------------------------------------------- + + + IGRAPH_FUNCATTR_NORETURN igraph_error_handler_t igraph_error_handler_abort; + + The default error handler, prints an error message and aborts the +program. + + +File: igraph-docs.info, Node: igraph_error_handler_ignore --- Ignore errors_, Next: igraph_error_handler_printignore --- Print and ignore errors_, Prev: igraph_error_handler_abort --- Abort program in case of error_, Up: Error handlers + +5.2.3 igraph_error_handler_ignore -- Ignore errors. +--------------------------------------------------- + + + igraph_error_handler_t igraph_error_handler_ignore; + + This error handler frees the temporarily allocated memory and returns +with the error code. + + +File: igraph-docs.info, Node: igraph_error_handler_printignore --- Print and ignore errors_, Prev: igraph_error_handler_ignore --- Ignore errors_, Up: Error handlers + +5.2.4 igraph_error_handler_printignore -- Print and ignore errors. +------------------------------------------------------------------ + + + igraph_error_handler_t igraph_error_handler_printignore; + + Frees temporarily allocated memory, prints an error message to the +standard error and returns with the error code. + + +File: igraph-docs.info, Node: Error codes, Next: Warning messages, Prev: Error handlers, Up: Error handling + +5.3 Error codes +=============== + +Every ‘igraph’ function which can fail return a single integer error +code. Some functions are very simple and cannot run into any error, +these may return other types, or ‘void’ as well. The error codes are +defined by the ‘igraph_error_type_t’ (*note igraph_error_type_t --- +Error code type_::) enumeration. + +* Menu: + +* igraph_error_t -- Return type for functions returning an error code.: igraph_error_t --- Return type for functions returning an error code_. +* igraph_error_type_t -- Error code type.: igraph_error_type_t --- Error code type_. +* igraph_strerror -- Textual description of an error.: igraph_strerror --- Textual description of an error_. + + +File: igraph-docs.info, Node: igraph_error_t --- Return type for functions returning an error code_, Next: igraph_error_type_t --- Error code type_, Up: Error codes + +5.3.1 igraph_error_t -- Return type for functions returning an error code. +-------------------------------------------------------------------------- + + + typedef igraph_error_type_t igraph_error_t; + + This type is used as the return type of igraph functions that return +an error code. It is a type alias because ‘igraph_error_t’ used to be +an ‘int’, and was used slightly differenly than ‘igraph_error_type_t’. + + +File: igraph-docs.info, Node: igraph_error_type_t --- Error code type_, Next: igraph_strerror --- Textual description of an error_, Prev: igraph_error_t --- Return type for functions returning an error code_, Up: Error codes + +5.3.2 igraph_error_type_t -- Error code type. +--------------------------------------------- + + + typedef enum { + IGRAPH_SUCCESS = 0, + IGRAPH_FAILURE = 1, + IGRAPH_ENOMEM = 2, + IGRAPH_PARSEERROR = 3, + IGRAPH_EINVAL = 4, + IGRAPH_EXISTS = 5, + /* IGRAPH_EINVEVECTOR = 6, */ /* removed in 1.0 */ + IGRAPH_EINVVID = 7, + IGRAPH_EINVEID = 8, /* used to be IGRAPH_NONSQUARE before 1.0 */ + IGRAPH_EINVMODE = 9, + IGRAPH_EFILE = 10, + IGRAPH_UNIMPLEMENTED = 12, + IGRAPH_INTERRUPTED = 13, + IGRAPH_DIVERGED = 14, + IGRAPH_EARPACK = 15, + /* ARPACK error codes from 15 to 36 were moved to igraph_arpack_error_t in 1.0 */ + IGRAPH_ENEGCYCLE = 37, + IGRAPH_EINTERNAL = 38, + /* ARPACK error codes from 39 to 41 were moved to igraph_arpack_error_t in 1.0 */ + /* IGRAPH_EDIVZERO = 42, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EBOUND = 43, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EROOT = 44, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ENOPFS = 45, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ENODFS = 46, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EFAIL = 47, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EMIPGAP = 48, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ETMLIM = 49, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ESTOP = 50, */ /* removed in 1.0 */ + /* IGRAPH_EATTRIBUTES = 51, */ /* removed in 1.0 */ + IGRAPH_EATTRCOMBINE = 52, + /* IGRAPH_ELAPACK = 53, */ /* removed in 1.0 */ + /* IGRAPH_EDRL = 54, */ /* deprecated in 0.10.2, removed in 1.0 */ + IGRAPH_EOVERFLOW = 55, + /* IGRAPH_EGLP = 56, */ /* removed in 1.0 */ + /* IGRAPH_CPUTIME = 57, */ /* removed in 1.0 */ + IGRAPH_EUNDERFLOW = 58, + IGRAPH_ERWSTUCK = 59, + IGRAPH_STOP = 60, + IGRAPH_ERANGE = 61, + IGRAPH_ENOSOL = 62 + } igraph_error_type_t; + + These are the possible values returned by ‘igraph’ functions. Note +that these are interesting only if you defined an error handler with +‘igraph_set_error_handler()’ (*note igraph_set_error_handler --- Sets a +new error handler_::). Otherwise the program is aborted and the +function causing the error never returns. + + *Values:. * + +‘IGRAPH_SUCCESS’: + The function successfully completed its task. + +‘IGRAPH_FAILURE’: + Something went wrong. You'll almost never meet this error as + normally more specific error codes are used. + +‘IGRAPH_ENOMEM’: + There wasn't enough memory to allocate on the heap. + +‘IGRAPH_PARSEERROR’: + A parse error was found in a file. + +‘IGRAPH_EINVAL’: + A parameter's value is invalid. E.g. negative number was + specified as the number of vertices. + +‘IGRAPH_EXISTS’: + A graph/vertex/edge attribute is already installed with the given + name. + +‘IGRAPH_EINVVID’: + Invalid vertex ID, negative or too big. + +‘IGRAPH_EINVEID’: + Invalid edge ID, negative or too big. + +‘IGRAPH_EINVMODE’: + Invalid mode parameter. + +‘IGRAPH_EFILE’: + A file operation failed. E.g. a file doesn't exist, or the user + has no rights to open it. + +‘IGRAPH_UNIMPLEMENTED’: + Attempted to call an unimplemented or disabled (at compile-time) + function. + +‘IGRAPH_DIVERGED’: + A numeric algorithm failed to converge. + +‘IGRAPH_ARPACK’: + An error happened inside a calculation implemented in ARPACK. The + calculation involved is most likely an eigenvector-related + calculation. + +‘IGRAPH_ENEGCYCLE’: + Negative cycle detected while calculating shortest paths. + +‘IGRAPH_EINTERNAL’: + Internal error, likely a bug in igraph. + +‘IGRAPH_EATTRCOMBINE’: + Unimplemented attribute combination method for the given attribute + type. + +‘IGRAPH_EOVERFLOW’: + Integer or double overflow. + +‘IGRAPH_EUNDERFLOW’: + Integer or double underflow. + +‘IGRAPH_ERWSTUCK’: + Random walk got stuck. + +‘IGRAPH_ERANGE’: + Maximum vertex or edge count exceeded. + +‘IGRAPH_ENOSOL’: + Input problem has no solution. + + +File: igraph-docs.info, Node: igraph_strerror --- Textual description of an error_, Prev: igraph_error_type_t --- Error code type_, Up: Error codes + +5.3.3 igraph_strerror -- Textual description of an error. +--------------------------------------------------------- + + + const char *igraph_strerror(const igraph_error_t igraph_errno); + + This is a simple utility function, it gives a short general textual +description for an ‘igraph’ error code. + + *Arguments:. * + +‘igraph_errno’: + The ‘igraph’ error code. + + *Returns:. * + +‘’ + pointer to the textual description of the error code. + + +File: igraph-docs.info, Node: Warning messages, Next: Advanced topics, Prev: Error codes, Up: Error handling + +5.4 Warning messages +==================== + +‘igraph’ also supports warning messages in addition to error messages. +Warning messages typically do not terminate the program, but they are +usually crucial to the user. + + ‘igraph’ warnings are handled similarly to errors. There is a +separate warning handler function that is called whenever an ‘igraph’ +function triggers a warning. This handler can be set by the +‘igraph_set_warning_handler()’ (*note igraph_set_warning_handler --- +Installs a warning handler_::) function. There are two predefined +simple warning handlers, ‘igraph_warning_handler_ignore()’ (*note +igraph_warning_handler_ignore --- Ignores all warnings_::) and +‘igraph_warning_handler_print()’ (*note igraph_warning_handler_print --- +Prints all warnings to the standard error_::), the latter being the +default. + + To trigger a warning, ‘igraph’ functions typically use the +‘IGRAPH_WARNING()’ (*note IGRAPH_WARNING --- Triggers a warning_::) +macro, the ‘igraph_warning()’ (*note igraph_warning --- Reports a +warning_::) function, or if more flexibility is needed, +‘igraph_warningf()’ (*note igraph_warningf --- Reports a warning; +printf-like version_::). + +* Menu: + +* igraph_warning_handler_t -- The type of igraph warning handler functions.: igraph_warning_handler_t --- The type of igraph warning handler functions_. +* igraph_set_warning_handler -- Installs a warning handler.: igraph_set_warning_handler --- Installs a warning handler_. +* IGRAPH_WARNING -- Triggers a warning.: IGRAPH_WARNING --- Triggers a warning_. +* IGRAPH_WARNINGF -- Triggers a warning, with printf-like syntax.: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_. +* igraph_warning -- Reports a warning.: igraph_warning --- Reports a warning_. +* igraph_warningf -- Reports a warning, printf-like version.: igraph_warningf --- Reports a warning; printf-like version_. +* igraph_warning_handler_ignore -- Ignores all warnings.: igraph_warning_handler_ignore --- Ignores all warnings_. +* igraph_warning_handler_print -- Prints all warnings to the standard error.: igraph_warning_handler_print --- Prints all warnings to the standard error_. + + +File: igraph-docs.info, Node: igraph_warning_handler_t --- The type of igraph warning handler functions_, Next: igraph_set_warning_handler --- Installs a warning handler_, Up: Warning messages + +5.4.1 igraph_warning_handler_t -- The type of igraph warning handler functions. +------------------------------------------------------------------------------- + + + typedef void igraph_warning_handler_t(const char *reason, + const char *file, int line); + + Currently it is defined to have the same type as +‘igraph_error_handler_t’ (*note igraph_error_handler_t --- The type of +error handler functions_::), although the last (error code) argument is +not used. + + +File: igraph-docs.info, Node: igraph_set_warning_handler --- Installs a warning handler_, Next: IGRAPH_WARNING --- Triggers a warning_, Prev: igraph_warning_handler_t --- The type of igraph warning handler functions_, Up: Warning messages + +5.4.2 igraph_set_warning_handler -- Installs a warning handler. +--------------------------------------------------------------- + + + igraph_warning_handler_t *igraph_set_warning_handler(igraph_warning_handler_t *new_handler); + + Install the supplied warning handler function. + + *Arguments:. * + +‘new_handler’: + The new warning handler function to install. Supply a null pointer + here to uninstall the current warning handler, without installing a + new one. + + *Returns:. * + +‘’ + The current warning handler function. + + +File: igraph-docs.info, Node: IGRAPH_WARNING --- Triggers a warning_, Next: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_, Prev: igraph_set_warning_handler --- Installs a warning handler_, Up: Warning messages + +5.4.3 IGRAPH_WARNING -- Triggers a warning. +------------------------------------------- + + + #define IGRAPH_WARNING(reason) + + This is the usual way of triggering a warning from an igraph +function. It calls ‘igraph_warning()’ (*note igraph_warning --- Reports +a warning_::). + + *Arguments:. * + +‘reason’: + The warning message. + + +File: igraph-docs.info, Node: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_, Next: igraph_warning --- Reports a warning_, Prev: IGRAPH_WARNING --- Triggers a warning_, Up: Warning messages + +5.4.4 IGRAPH_WARNINGF -- Triggers a warning, with printf-like syntax. +--------------------------------------------------------------------- + + + #define IGRAPH_WARNINGF(reason, ...) + + ‘igraph’ functions can use this macro when they notice a warning and +want to pass on extra information to the user about what went wrong. It +calls ‘igraph_warningf()’ (*note igraph_warningf --- Reports a warning; +printf-like version_::) with the proper parameters and no error code. + + *Arguments:. * + +‘reason’: + Textual description of the warning, a template string with the same + syntax as the standard printf C library function. + +‘...’: + The additional arguments to be substituted into the template + string. + + +File: igraph-docs.info, Node: igraph_warning --- Reports a warning_, Next: igraph_warningf --- Reports a warning; printf-like version_, Prev: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_, Up: Warning messages + +5.4.5 igraph_warning -- Reports a warning. +------------------------------------------ + + + void igraph_warning(const char *reason, const char *file, int line); + + Call this function if you want to trigger a warning from within a +function that uses ‘igraph’. + + *Arguments:. * + +‘reason’: + Textual description of the warning. + +‘file’: + The source file in which the warning was noticed. + +‘line’: + The number of line in the source file which triggered the warning. + + +File: igraph-docs.info, Node: igraph_warningf --- Reports a warning; printf-like version_, Next: igraph_warning_handler_ignore --- Ignores all warnings_, Prev: igraph_warning --- Reports a warning_, Up: Warning messages + +5.4.6 igraph_warningf -- Reports a warning, printf-like version. +---------------------------------------------------------------- + + + void igraph_warningf(const char *reason, const char *file, int line, ...); + + This function is similar to ‘igraph_warning()’ (*note igraph_warning +--- Reports a warning_::), but uses a printf-like syntax. It +substitutes the additional arguments into the ‘reason’ template string +and calls ‘igraph_warning()’ (*note igraph_warning --- Reports a +warning_::). + + *Arguments:. * + +‘reason’: + Textual description of the warning, a template string with the same + syntax as the standard printf C library function. + +‘file’: + The source file in which the warning was noticed. + +‘line’: + The number of line in the source file which triggered the warning. + +‘...’: + The additional arguments to be substituted into the template + string. + + +File: igraph-docs.info, Node: igraph_warning_handler_ignore --- Ignores all warnings_, Next: igraph_warning_handler_print --- Prints all warnings to the standard error_, Prev: igraph_warningf --- Reports a warning; printf-like version_, Up: Warning messages + +5.4.7 igraph_warning_handler_ignore -- Ignores all warnings. +------------------------------------------------------------ + + + void igraph_warning_handler_ignore(const char *reason, const char *file, int line); + + This warning handler function simply ignores all warnings. + + *Arguments:. * + +‘reason’: + Textual description of the warning. + +‘file’: + The source file in which the warning was noticed. + +‘line’: + The number of line in the source file which triggered the warning.. + + +File: igraph-docs.info, Node: igraph_warning_handler_print --- Prints all warnings to the standard error_, Prev: igraph_warning_handler_ignore --- Ignores all warnings_, Up: Warning messages + +5.4.8 igraph_warning_handler_print -- Prints all warnings to the standard error. +-------------------------------------------------------------------------------- + + + void igraph_warning_handler_print(const char *reason, const char *file, int line); + + This warning handler function simply prints all warnings to the +standard error. + + *Arguments:. * + +‘reason’: + Textual description of the warning. + +‘file’: + The source file in which the warning was noticed. + +‘line’: + The number of line in the source file which triggered the warning.. + + +File: igraph-docs.info, Node: Advanced topics, Prev: Warning messages, Up: Error handling + +5.5 Advanced topics +=================== + +* Menu: + +* Writing error handlers:: +* Error handling internals:: +* Deallocating memory:: +* Writing igraph functions with proper error handling:: +* Fatal errors:: +* Error handling and threads:: + + +File: igraph-docs.info, Node: Writing error handlers, Next: Error handling internals, Up: Advanced topics + +5.5.1 Writing error handlers +---------------------------- + +The contents of the rest of this chapter might be useful only for those +who want to create an interface to ‘igraph’ from another language, or +use igraph from a GUI application. Most readers can safely skip to the +next chapter. + + You can write and install error handlers simply by defining a +function of type ‘igraph_error_handler_t’ (*note igraph_error_handler_t +--- The type of error handler functions_::) and calling +‘igraph_set_error_handler()’ (*note igraph_set_error_handler --- Sets a +new error handler_::). This feature is useful for interface writers, as +‘igraph’ will have the chance to signal errors the appropriate way. For +example, the R interface uses R's native printing facilities to +communicate errors, while the Python interface converts them into Python +exceptions. + + The two main tasks of the error handler are to report the error (i.e. +print the error message) and ensure proper resource cleanup. This is +ensured by calling ‘IGRAPH_FINALLY_FREE()’ (*note IGRAPH_FINALLY_FREE +--- Deallocates objects registered at the current level_::), which +deallocates some of the temporary memory to avoid memory leaks. Note +that this may invalidate the error message buffer ‘reason’ passed to the +error handler. Do not access it after having called +‘IGRAPH_FINALLY_FREE()’ (*note IGRAPH_FINALLY_FREE --- Deallocates +objects registered at the current level_::). + + As of ‘igraph’ 0.10, temporary memory is dellocated in stages, +through multiple calls to the error handler (and indirectly to +‘IGRAPH_FINALLY_FREE()’ (*note IGRAPH_FINALLY_FREE --- Deallocates +objects registered at the current level_::)). Therefore, error handlers +that do not abort the program immediately are expected to return. The +error handler should not perform a ‘longjmp’, as this may lead to some +of the memory not getting freed. + +* Menu: + +* igraph_set_error_handler -- Sets a new error handler.: igraph_set_error_handler --- Sets a new error handler_. + + +File: igraph-docs.info, Node: igraph_set_error_handler --- Sets a new error handler_, Up: Writing error handlers + +5.5.1.1 igraph_set_error_handler -- Sets a new error handler. +............................................................. + + + igraph_error_handler_t *igraph_set_error_handler(igraph_error_handler_t *new_handler); + + Installs a new error handler. If called with ‘NULL’, it installs the +default error handler (which is currently ‘igraph_error_handler_abort’ +(*note igraph_error_handler_abort --- Abort program in case of +error_::)). + + *Arguments:. * + +‘new_handler’: + The error handler function to install. + + *Returns:. * + +‘’ + The old error handler function. This should be saved and restored + if ‘new_handler’ is not needed any more. + + +File: igraph-docs.info, Node: Error handling internals, Next: Deallocating memory, Prev: Writing error handlers, Up: Advanced topics + +5.5.2 Error handling internals +------------------------------ + +If an error happens, the functions in the library call the +‘IGRAPH_ERROR()’ (*note IGRAPH_ERROR --- Triggers an error_::) macro +with a textual description of the error and an ‘igraph’ error code. +This macro calls (through the ‘igraph_error()’ (*note igraph_error --- +Reports an error_::) function) the installed error handler. Another +useful macro is ‘IGRAPH_CHECK()’ (*note IGRAPH_CHECK --- Checks the +return value of a function call_::). This checks the return value of +its argument, which is normally a function call, and calls +‘IGRAPH_ERROR()’ (*note IGRAPH_ERROR --- Triggers an error_::) if it is +not ‘IGRAPH_SUCCESS’. + +* Menu: + +* IGRAPH_ERROR -- Triggers an error.: IGRAPH_ERROR --- Triggers an error_. +* IGRAPH_ERRORF -- Triggers an error, with printf-like syntax.: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_. +* igraph_error -- Reports an error.: igraph_error --- Reports an error_. +* igraph_errorf -- Reports an error, printf-like version.: igraph_errorf --- Reports an error; printf-like version_. +* IGRAPH_CHECK -- Checks the return value of a function call.: IGRAPH_CHECK --- Checks the return value of a function call_. +* IGRAPH_CHECK_CALLBACK -- Checks the return value of a callback.: IGRAPH_CHECK_CALLBACK --- Checks the return value of a callback_. + + +File: igraph-docs.info, Node: IGRAPH_ERROR --- Triggers an error_, Next: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_, Up: Error handling internals + +5.5.2.1 IGRAPH_ERROR -- Triggers an error. +.......................................... + + + #define IGRAPH_ERROR(reason, igraph_errno) + + ‘igraph’ functions usually use this macro when they notice an error. +It calls ‘igraph_error()’ (*note igraph_error --- Reports an error_::) +with the proper parameters and if that returns the macro returns the +"calling" function as well, with the error code. If for some +(suspicious) reason you want to call the error handler without returning +from the current function, call ‘igraph_error()’ (*note igraph_error --- +Reports an error_::) directly. + + *Arguments:. * + +‘reason’: + Textual description of the error. This should be something more + descriptive than the text associated with the error code. E.g. if + the error code is ‘IGRAPH_EINVAL’, its associated text (see + ‘igraph_strerror()’ (*note igraph_strerror --- Textual description + of an error_::)) is "Invalid value" and this string should explain + which parameter was invalid and maybe why. + +‘igraph_errno’: + The ‘igraph’ error code. + + +File: igraph-docs.info, Node: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_, Next: igraph_error --- Reports an error_, Prev: IGRAPH_ERROR --- Triggers an error_, Up: Error handling internals + +5.5.2.2 IGRAPH_ERRORF -- Triggers an error, with printf-like syntax. +.................................................................... + + + #define IGRAPH_ERRORF(reason, igraph_errno, ...) + + ‘igraph’ functions can use this macro when they notice an error and +want to pass on extra information to the user about what went wrong. It +calls ‘igraph_errorf()’ (*note igraph_errorf --- Reports an error; +printf-like version_::) with the proper parameters and if that returns +the macro returns the "calling" function as well, with the error code. +If for some (suspicious) reason you want to call the error handler +without returning from the current function, call ‘igraph_errorf()’ +(*note igraph_errorf --- Reports an error; printf-like version_::) +directly. + + *Arguments:. * + +‘reason’: + Textual description of the error, a template string with the same + syntax as the standard printf C library function. This should be + something more descriptive than the text associated with the error + code. E.g. if the error code is ‘IGRAPH_EINVAL’, its associated + text (see ‘igraph_strerror()’ (*note igraph_strerror --- Textual + description of an error_::)) is "Invalid value" and this string + should explain which parameter was invalid and maybe what was + expected and what was recieved. + +‘igraph_errno’: + The ‘igraph’ error code. + +‘...’: + The additional arguments to be substituted into the template + string. + + +File: igraph-docs.info, Node: igraph_error --- Reports an error_, Next: igraph_errorf --- Reports an error; printf-like version_, Prev: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_, Up: Error handling internals + +5.5.2.3 igraph_error -- Reports an error. +......................................... + + + igraph_error_t igraph_error(const char *reason, const char *file, int line, + igraph_error_t igraph_errno); + + ‘igraph’ functions usually call this function (most often via the +‘IGRAPH_ERROR’ (*note IGRAPH_ERROR --- Triggers an error_::) macro) if +they notice an error. It calls the currently installed error handler +function with the supplied arguments. + + *Arguments:. * + +‘reason’: + Textual description of the error. + +‘file’: + The source file in which the error was noticed. + +‘line’: + The number of line in the source file which triggered the error. + +‘igraph_errno’: + The ‘igraph’ error code. + + *Returns:. * + +‘’ + The error code (if it returns). + + *See also:. * + +‘’ + ‘igraph_errorf()’ (*note igraph_errorf --- Reports an error; + printf-like version_::) + + +File: igraph-docs.info, Node: igraph_errorf --- Reports an error; printf-like version_, Next: IGRAPH_CHECK --- Checks the return value of a function call_, Prev: igraph_error --- Reports an error_, Up: Error handling internals + +5.5.2.4 igraph_errorf -- Reports an error, printf-like version. +............................................................... + + + igraph_error_t igraph_errorf(const char *reason, const char *file, int line, + igraph_error_t igraph_errno, ...); + + *Arguments:. * + +‘reason’: + Textual description of the error, interpreted as a ‘printf’ format + string. + +‘file’: + The source file in which the error was noticed. + +‘line’: + The line in the source file which triggered the error. + +‘igraph_errno’: + The ‘igraph’ error code. + +‘...’: + Additional parameters, the values to substitute into the format + string. + + *Returns:. * + +‘’ + The error code (if it returns). + + *See also:. * + +‘’ + ‘igraph_error()’ (*note igraph_error --- Reports an error_::) + + +File: igraph-docs.info, Node: IGRAPH_CHECK --- Checks the return value of a function call_, Next: IGRAPH_CHECK_CALLBACK --- Checks the return value of a callback_, Prev: igraph_errorf --- Reports an error; printf-like version_, Up: Error handling internals + +5.5.2.5 IGRAPH_CHECK -- Checks the return value of a function call. +................................................................... + + + #define IGRAPH_CHECK(expr) + + *Arguments:. * + +‘expr’: + An expression, usually a function call. It is guaranteed to be + evaluated only once. + + Executes the expression and checks its value. If this is not +‘IGRAPH_SUCCESS’, it calls ‘IGRAPH_ERROR’ (*note IGRAPH_ERROR --- +Triggers an error_::) with the value as the error code. Here is an +example usage: + + IGRAPH_CHECK(vector_push_back(&v, 100)); + + There is only one reason to use this macro when writing ‘igraph’ +functions. If the user installs an error handler which returns to the +auxiliary calling code (like ‘igraph_error_handler_ignore’ (*note +igraph_error_handler_ignore --- Ignore errors_::) and +‘igraph_error_handler_printignore’ (*note +igraph_error_handler_printignore --- Print and ignore errors_::)), and +the ‘igraph’ function signalling the error is called from another +‘igraph’ function then we need to make sure that the error is propagated +back to the auxiliary (i.e. non-igraph) calling function. This is +achieved by using ‘IGRAPH_CHECK’ on every ‘igraph’ call which can return +an error code. + + +File: igraph-docs.info, Node: IGRAPH_CHECK_CALLBACK --- Checks the return value of a callback_, Prev: IGRAPH_CHECK --- Checks the return value of a function call_, Up: Error handling internals + +5.5.2.6 IGRAPH_CHECK_CALLBACK -- Checks the return value of a callback. +....................................................................... + + + #define IGRAPH_CHECK_CALLBACK(expr, code) + + Identical to ‘IGRAPH_CHECK’ (*note IGRAPH_CHECK --- Checks the return +value of a function call_::), but treats ‘IGRAPH_STOP’ as a normal +(non-erroneous) return code. This macro is used in some igraph +functions that allow the user to hook into a long-running calculation +with a callback function. When the user-defined callback function +returns ‘IGRAPH_SUCCESS’, the calculation will proceed normally. +Returning ‘IGRAPH_STOP’ from the callback will terminate the calculation +without reporting an error. Returning any other value from the callback +is treated as an error code, and igraph will trigger the necessary +cleanup functions before exiting the function. + + Note that ‘IGRAPH_CHECK_CALLBACK’ does not handle ‘IGRAPH_STOP’ by +any means except returning it in the variable pointed to by ‘code’. It +is the responsibility of the caller to handle ‘IGRAPH_STOP’ accordingly. + + *Arguments:. * + +‘expr’: + An expression, usually a call to a user-defined callback function. + It is guaranteed to be evaluated only once. + +‘code’: + Pointer to an optional variable of type ‘igraph_error_t’; the value + of this variable will be set to the error code if it is not a null + pointer. + + +File: igraph-docs.info, Node: Deallocating memory, Next: Writing igraph functions with proper error handling, Prev: Error handling internals, Up: Advanced topics + +5.5.3 Deallocating memory +------------------------- + +If a function runs into an error (and the program is not aborted) the +error handler should deallocate all temporary memory. This is done by +storing the address and the destroy function of all temporary objects in +a stack. The ‘IGRAPH_FINALLY’ (*note IGRAPH_FINALLY --- Registers an +object for deallocation_::) function declares an object as temporary by +placing its address in the stack. If an ‘igraph’ function returns with +success it calls ‘IGRAPH_FINALLY_CLEAN()’ (*note IGRAPH_FINALLY_CLEAN +--- Signals clean deallocation of objects_::) with the number of objects +to remove from the stack. If an error happens however, the error +handler should call ‘IGRAPH_FINALLY_FREE()’ (*note IGRAPH_FINALLY_FREE +--- Deallocates objects registered at the current level_::) to +deallocate each object added to the stack. This means that the +temporary objects allocated in the calling function (and etc.) will be +freed as well. + +* Menu: + +* IGRAPH_FINALLY -- Registers an object for deallocation.: IGRAPH_FINALLY --- Registers an object for deallocation_. +* IGRAPH_FINALLY_CLEAN -- Signals clean deallocation of objects.: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_. +* IGRAPH_FINALLY_FREE -- Deallocates objects registered at the current level.: IGRAPH_FINALLY_FREE --- Deallocates objects registered at the current level_. + + +File: igraph-docs.info, Node: IGRAPH_FINALLY --- Registers an object for deallocation_, Next: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_, Up: Deallocating memory + +5.5.3.1 IGRAPH_FINALLY -- Registers an object for deallocation. +............................................................... + + + #define IGRAPH_FINALLY(func, ptr) + + This macro places the address of an object, together with the address +of its destructor on a stack. This stack is used if an error happens to +deallocate temporarily allocated objects to prevent memory leaks. After +manual deallocation, objects are removed from the stack using +‘IGRAPH_FINALLY_CLEAN()’ (*note IGRAPH_FINALLY_CLEAN --- Signals clean +deallocation of objects_::). + + The typical usage is just after an initialization: + + + IGRAPH_CHECK(igraph_vector_init(&vector, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &vector); + +The most commonly used data structures, such as ‘igraph_vector_t’ (*note +About igraph_vector_t objects::), have associated convenience macros +that initialize the object and register it on this stack in one step. +Thus the pattern above can be replaced with a single line: + + + IGRAPH_VECTOR_INIT_FINALLY(&vector, 0); + + *Arguments:. * + +‘func’: + The function which is normally called to destroy the object. + +‘ptr’: + Pointer to the object itself. + + +File: igraph-docs.info, Node: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_, Next: IGRAPH_FINALLY_FREE --- Deallocates objects registered at the current level_, Prev: IGRAPH_FINALLY --- Registers an object for deallocation_, Up: Deallocating memory + +5.5.3.2 IGRAPH_FINALLY_CLEAN -- Signals clean deallocation of objects. +...................................................................... + + + void IGRAPH_FINALLY_CLEAN(int num); + + Removes the specified number of objects from the stack of temporarily +allocated objects. It is typically called immediately after manually +destroying the objects: + + + igraph_vector_t vector; + igraph_vector_init(&vector, 10); + IGRAPH_FINALLY(igraph_vector_destroy, &vector); + // use vector + igraph_vector_destroy(&vector); + IGRAPH_FINALLY_CLEAN(1); + + *Arguments:. * + +‘num’: + The number of objects to remove from the bookkeeping stack. + + +File: igraph-docs.info, Node: IGRAPH_FINALLY_FREE --- Deallocates objects registered at the current level_, Prev: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_, Up: Deallocating memory + +5.5.3.3 IGRAPH_FINALLY_FREE -- Deallocates objects registered at the current level. +................................................................................... + + + void IGRAPH_FINALLY_FREE(void); + + Calls the destroy function for all objects in the current level of +the stack of temporarily allocated objects, i.e. up to the nearest mark +set by ‘IGRAPH_FINALLY_ENTER()’. This function must only be called from +an error handler. It is _not_ appropriate to use it instead of +destroying each unneeded object of a function, as it destroys the +temporary objects of the caller function (and so on) as well. + + +File: igraph-docs.info, Node: Writing igraph functions with proper error handling, Next: Fatal errors, Prev: Deallocating memory, Up: Advanced topics + +5.5.4 Writing igraph functions with proper error handling +--------------------------------------------------------- + +There are some simple rules to keep in order to have functions behaving +well in erroneous situations. First, check the arguments of the +functions and call ‘IGRAPH_ERROR()’ (*note IGRAPH_ERROR --- Triggers an +error_::) if they are invalid. Second, call ‘IGRAPH_FINALLY’ (*note +IGRAPH_FINALLY --- Registers an object for deallocation_::) on each +dynamically allocated object and call ‘IGRAPH_FINALLY_CLEAN()’ (*note +IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_::) with +the proper argument before returning. Third, use ‘IGRAPH_CHECK’ (*note +IGRAPH_CHECK --- Checks the return value of a function call_::) on all +‘igraph’ function calls which can generate errors. + + The size of the stack used for this bookkeeping is fixed, and small. +If you want to allocate several objects, write a destroy function which +can deallocate all of these. See the ‘adjlist.c’ file in the ‘igraph’ +source for an example. + + For some functions these mechanisms are simply not flexible enough. +These functions should define their own error handlers and restore the +error handler before they return. + + * File examples/simple/igraph_contract_vertices.c* + + +File: igraph-docs.info, Node: Fatal errors, Next: Error handling and threads, Prev: Writing igraph functions with proper error handling, Up: Advanced topics + +5.5.5 Fatal errors +------------------ + +In some rare situations, ‘igraph’ may encounter an internal error that +cannot be fully handled. In this case, it will call the current fatal +error handler. The default fatal error handler simply prints the error +and aborts the program. + + Fatal error handlers do not return. Typically, they might abort the +the program immediately, or in the case of the high-level ‘igraph’ +interfaces, they might return to the top level using a ‘longjmp()’. The +fatal error handler is only called when a serious error has occurred, +and as a result igraph may be in an inconsistent state. The purpose of +returning to the top level is to give the user a chance to save their +work instead of aborting immediately. However, the program session +should be restarted as soon as possible. + + Most projects that use ‘igraph’ will use the default fatal error +handler. + +* Menu: + +* igraph_fatal_handler_t -- The type of igraph fatal error handler functions.: igraph_fatal_handler_t --- The type of igraph fatal error handler functions_. +* igraph_set_fatal_handler -- Installs a fatal error handler.: igraph_set_fatal_handler --- Installs a fatal error handler_. +* igraph_fatal_handler_abort -- Abort program in case of fatal error.: igraph_fatal_handler_abort --- Abort program in case of fatal error_. +* IGRAPH_FATAL -- Triggers a fatal error.: IGRAPH_FATAL --- Triggers a fatal error_. +* IGRAPH_FATALF -- Triggers a fatal error, with printf-like syntax.: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_. +* IGRAPH_ASSERT -- igraph-specific replacement for assert().: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_. +* igraph_fatal -- Triggers a fatal error.: igraph_fatal --- Triggers a fatal error_. +* igraph_fatalf -- Triggers a fatal error, printf-like syntax.: igraph_fatalf --- Triggers a fatal error; printf-like syntax_. + + +File: igraph-docs.info, Node: igraph_fatal_handler_t --- The type of igraph fatal error handler functions_, Next: igraph_set_fatal_handler --- Installs a fatal error handler_, Up: Fatal errors + +5.5.5.1 igraph_fatal_handler_t -- The type of igraph fatal error handler functions. +................................................................................... + + + typedef void igraph_fatal_handler_t(const char *reason, const char *file, int line); + + Functions of this type _must_ not return. Typically they call +‘abort()’ or do a ‘longjmp()’. + + *Arguments:. * + +‘reason’: + Textual description of the error. + +‘file’: + The source file in which the error is noticed. + +‘line’: + The number of the line in the source file which triggered the + error. + + +File: igraph-docs.info, Node: igraph_set_fatal_handler --- Installs a fatal error handler_, Next: igraph_fatal_handler_abort --- Abort program in case of fatal error_, Prev: igraph_fatal_handler_t --- The type of igraph fatal error handler functions_, Up: Fatal errors + +5.5.5.2 igraph_set_fatal_handler -- Installs a fatal error handler. +................................................................... + + + igraph_fatal_handler_t *igraph_set_fatal_handler(igraph_fatal_handler_t *new_handler); + + Installs the supplied fatal error handler function. + + Fatal error handler functions _must_ not return. Typically, the +fatal error handler would either call ‘abort()’ or ‘longjmp()’. + + *Arguments:. * + +‘new_handler’: + The new fatal error handler function to install. Supply a null + pointer here to uninstall the current fatal error handler, without + installing a new one. + + *Returns:. * + +‘’ + The current fatal error handler function. + + +File: igraph-docs.info, Node: igraph_fatal_handler_abort --- Abort program in case of fatal error_, Next: IGRAPH_FATAL --- Triggers a fatal error_, Prev: igraph_set_fatal_handler --- Installs a fatal error handler_, Up: Fatal errors + +5.5.5.3 igraph_fatal_handler_abort -- Abort program in case of fatal error. +........................................................................... + + + IGRAPH_FUNCATTR_NORETURN igraph_fatal_handler_t igraph_fatal_handler_abort; + + The default fatal error handler, prints an error message and aborts +the program. + + +File: igraph-docs.info, Node: IGRAPH_FATAL --- Triggers a fatal error_, Next: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_, Prev: igraph_fatal_handler_abort --- Abort program in case of fatal error_, Up: Fatal errors + +5.5.5.4 IGRAPH_FATAL -- Triggers a fatal error. +............................................... + + + #define IGRAPH_FATAL(reason) + + This is the usual way of triggering a fatal error from an igraph +function. It calls ‘igraph_fatal()’ (*note igraph_fatal --- Triggers a +fatal error_::). + + Use this macro only in situations where the error cannot be handled. +The normal way to handle errors is ‘IGRAPH_ERROR()’ (*note IGRAPH_ERROR +--- Triggers an error_::). + + *Arguments:. * + +‘reason’: + The error message. + + +File: igraph-docs.info, Node: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_, Next: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_, Prev: IGRAPH_FATAL --- Triggers a fatal error_, Up: Fatal errors + +5.5.5.5 IGRAPH_FATALF -- Triggers a fatal error, with printf-like syntax. +......................................................................... + + + #define IGRAPH_FATALF(reason, ...) + + ‘igraph’ functions can use this macro when a fatal error occurs and +want to pass on extra information to the user about what went wrong. It +calls ‘igraph_fatalf()’ (*note igraph_fatalf --- Triggers a fatal error; +printf-like syntax_::) with the proper parameters. + + *Arguments:. * + +‘reason’: + Textual description of the error, a template string with the same + syntax as the standard printf C library function. + +‘...’: + The additional arguments to be substituted into the template + string. + + +File: igraph-docs.info, Node: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_, Next: igraph_fatal --- Triggers a fatal error_, Prev: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_, Up: Fatal errors + +5.5.5.6 IGRAPH_ASSERT -- igraph-specific replacement for assert(). +.................................................................. + + + #define IGRAPH_ASSERT(condition) + + This macro is like the standard ‘assert()’, but instead of calling +‘abort()’, it calls ‘igraph_fatal()’ (*note igraph_fatal --- Triggers a +fatal error_::). This allows for returning the control to the calling +program, e.g. returning to the top level in a high-level ‘igraph’ +interface. + + Unlike ‘assert()’, ‘IGRAPH_ASSERT()’ is not disabled when the +‘NDEBUG’ macro is defined. + + This macro is meant for internal use by ‘igraph’. + + Since a typical fatal error handler does a ‘longjmp()’, avoid using +this macro in C++ code. With most compilers, destructor will not be +called when ‘longjmp()’ leaves the current scope. + + *Arguments:. * + +‘condition’: + The condition to be checked. + + +File: igraph-docs.info, Node: igraph_fatal --- Triggers a fatal error_, Next: igraph_fatalf --- Triggers a fatal error; printf-like syntax_, Prev: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_, Up: Fatal errors + +5.5.5.7 igraph_fatal -- Triggers a fatal error. +............................................... + + + void igraph_fatal(const char *reason, const char *file, int line); + + This function triggers a fatal error. Typically it is called +indirectly through ‘IGRAPH_FATAL()’ (*note IGRAPH_FATAL --- Triggers a +fatal error_::) or ‘IGRAPH_ASSERT()’ (*note IGRAPH_ASSERT --- +igraph-specific replacement for assert[]_::). + + *Arguments:. * + +‘reason’: + Textual description of the error. + +‘file’: + The source file in which the error was noticed. + +‘line’: + The number of line in the source file which triggered the error. + + +File: igraph-docs.info, Node: igraph_fatalf --- Triggers a fatal error; printf-like syntax_, Prev: igraph_fatal --- Triggers a fatal error_, Up: Fatal errors + +5.5.5.8 igraph_fatalf -- Triggers a fatal error, printf-like syntax. +.................................................................... + + + void igraph_fatalf(const char *reason, const char *file, int line, ...); + + This function is similar to ‘igraph_fatal()’ (*note igraph_fatal --- +Triggers a fatal error_::), but uses a printf-like syntax. It +substitutes the additional arguments into the ‘reason’ template string +and calls ‘igraph_fatal()’ (*note igraph_fatal --- Triggers a fatal +error_::). + + *Arguments:. * + +‘reason’: + Textual description of the error. + +‘file’: + The source file in which the error was noticed. + +‘line’: + The number of line in the source file which triggered the error. + +‘...’: + The additional arguments to be substituted into the template + string. + + +File: igraph-docs.info, Node: Error handling and threads, Prev: Fatal errors, Up: Advanced topics + +5.5.6 Error handling and threads +-------------------------------- + +It is likely that the ‘igraph’ error handling method is _not_ +thread-safe, mainly because of the static global stack which is used to +store the address of the temporarily allocated objects. This issue +might be addressed in a later version of ‘igraph’. + + +File: igraph-docs.info, Node: Memory [de]allocation, Next: Data structure library; vector; matrix; other data types, Prev: Error handling, Up: Top + +6 Memory (de)allocation +*********************** + +* Menu: + +* About allocation functions:: +* Available allocation functions:: + + +File: igraph-docs.info, Node: About allocation functions, Next: Available allocation functions, Up: Memory [de]allocation + +6.1 About allocation functions +============================== + +Some igraph functions return a pointer vector (‘igraph_vector_ptr_t’) +containing pointers to other igraph or other data types. These data +types are dynamically allocated and have to be deallocated manually when +the user does not need them any more. ‘igraph_vector_ptr_t’ has +functions to deallocate the contained pointers on its own, but in this +case it has to be ensured that these pointers are allocated by a +function that corresponds to the deallocator function that igraph uses. + + To this end, igraph exports the memory allocation functions that are +used internally so the user of the library can ensure that the proper +functions are used when pointers are moved between the code written by +the user and the code of the igraph library. + + Additionally, the memory allocator functions used by igraph work +around the quirks of classical ‘malloc’(), ‘realloc’() and ‘calloc’() +implementations where the behaviour of allocating zero bytes is +undefined. igraph allocator functions will always allocate at least one +byte. + + +File: igraph-docs.info, Node: Available allocation functions, Prev: About allocation functions, Up: Memory [de]allocation + +6.2 Available allocation functions +================================== + +* Menu: + +* igraph_malloc -- Allocates memory that can be safely deallocated by igraph functions.: igraph_malloc --- Allocates memory that can be safely deallocated by igraph functions_. +* igraph_calloc -- Allocates memory that can be safely deallocated by igraph functions.: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_. +* igraph_realloc -- Reallocate memory that can be safely deallocated by igraph functions.: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_. +* igraph_free -- Deallocates memory that was allocated by igraph functions.: igraph_free --- Deallocates memory that was allocated by igraph functions_. + + +File: igraph-docs.info, Node: igraph_malloc --- Allocates memory that can be safely deallocated by igraph functions_, Next: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_, Up: Available allocation functions + +6.2.1 igraph_malloc -- Allocates memory that can be safely deallocated by igraph functions. +------------------------------------------------------------------------------------------- + + + void *igraph_malloc(size_t size); + + This function behaves like ‘malloc’(), but it ensures that at least +one byte is allocated even when the caller asks for zero bytes. + + *Arguments:. * + +‘size’: + Number of bytes to be allocated. Zero is treated as one byte. + + *Returns:. * + +‘’ + Pointer to the piece of allocated memory; ‘NULL’ if the allocation + failed. + + *See also:. * + +‘’ + ‘igraph_calloc()’ (*note igraph_calloc --- Allocates memory that + can be safely deallocated by igraph functions_::), + ‘igraph_realloc()’ (*note igraph_realloc --- Reallocate memory that + can be safely deallocated by igraph functions_::), ‘igraph_free()’ + (*note igraph_free --- Deallocates memory that was allocated by + igraph functions_::) + + +File: igraph-docs.info, Node: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_, Next: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_, Prev: igraph_malloc --- Allocates memory that can be safely deallocated by igraph functions_, Up: Available allocation functions + +6.2.2 igraph_calloc -- Allocates memory that can be safely deallocated by igraph functions. +------------------------------------------------------------------------------------------- + + + void *igraph_calloc(size_t count, size_t size); + + This function behaves like ‘calloc’(), but it ensures that at least +one byte is allocated even when the caller asks for zero bytes. + + *Arguments:. * + +‘count’: + Number of items to be allocated. + +‘size’: + Size of a single item to be allocated. + + *Returns:. * + +‘’ + Pointer to the piece of allocated memory; ‘NULL’ if the allocation + failed. + + *See also:. * + +‘’ + ‘igraph_malloc()’ (*note igraph_malloc --- Allocates memory that + can be safely deallocated by igraph functions_::), + ‘igraph_realloc()’ (*note igraph_realloc --- Reallocate memory that + can be safely deallocated by igraph functions_::), ‘igraph_free()’ + (*note igraph_free --- Deallocates memory that was allocated by + igraph functions_::) + + +File: igraph-docs.info, Node: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_, Next: igraph_free --- Deallocates memory that was allocated by igraph functions_, Prev: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_, Up: Available allocation functions + +6.2.3 igraph_realloc -- Reallocate memory that can be safely deallocated by igraph functions. +--------------------------------------------------------------------------------------------- + + + void *igraph_realloc(void *ptr, size_t size); + + This function behaves like ‘realloc’(), but it ensures that at least +one byte is allocated even when the caller asks for zero bytes. + + *Arguments:. * + +‘ptr’: + The pointer to reallocate. + +‘size’: + Number of bytes to be allocated. + + *Returns:. * + +‘’ + Pointer to the piece of allocated memory; ‘NULL’ if the allocation + failed. + + *See also:. * + +‘’ + ‘igraph_free()’ (*note igraph_free --- Deallocates memory that was + allocated by igraph functions_::), ‘igraph_malloc()’ (*note + igraph_malloc --- Allocates memory that can be safely deallocated + by igraph functions_::) + + +File: igraph-docs.info, Node: igraph_free --- Deallocates memory that was allocated by igraph functions_, Prev: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_, Up: Available allocation functions + +6.2.4 igraph_free -- Deallocates memory that was allocated by igraph functions. +------------------------------------------------------------------------------- + + + void igraph_free(void *ptr); + + This function exposes the ‘free’() function used internally by +igraph. + + *Arguments:. * + +‘ptr’: + Pointer to the piece of memory to be deallocated. + + Time complexity: platform dependent, ideally it should be O(1). + + *See also:. * + +‘’ + ‘igraph_calloc()’ (*note igraph_calloc --- Allocates memory that + can be safely deallocated by igraph functions_::), + ‘igraph_malloc()’ (*note igraph_malloc --- Allocates memory that + can be safely deallocated by igraph functions_::), + ‘igraph_realloc()’ (*note igraph_realloc --- Reallocate memory that + can be safely deallocated by igraph functions_::) + + +File: igraph-docs.info, Node: Data structure library; vector; matrix; other data types, Next: Random numbers, Prev: Memory [de]allocation, Up: Top + +7 Data structure library: vector, matrix, other data types +********************************************************** + +* Menu: + +* About template types:: +* Vectors:: +* Matrices:: +* Sparse matrices:: +* Stacks:: +* Double-ended queues:: +* Maximum and minimum heaps:: +* String vectors:: +* Lists of vectors, matrices and graphs: Lists of vectors; matrices and graphs. +* Adjacency lists:: +* Partial prefix sum trees:: +* Bitsets:: + + +File: igraph-docs.info, Node: About template types, Next: Vectors, Up: Data structure library; vector; matrix; other data types + +7.1 About template types +======================== + +Some of the container types listed in this section are defined for many +base types. This is similar to templates in C++ and generics in Ada, +but it is implemented via preprocessor macros since the C language +cannot handle it. Here is the list of template types and the all base +types they currently support: + +vector + Vector is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char), ‘igraph_bool_t’ (bool), ‘igraph_complex_t’ + (complex) and and ‘void *’ (ptr). The default is ‘igraph_real_t’. + +matrix + Matrix is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char), ‘igraph_bool_t’ (bool) and ‘igraph_complex_t’ + (complex). The default is ‘igraph_real_t’. + +array3 + Array3 is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char) and ‘igraph_bool_t’ (bool). The default is + ‘igraph_real_t’. + +stack + Stack is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char) and ‘igraph_bool_t’ (bool). The default is + ‘igraph_real_t’. + +double-ended queue + Dqueue is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char) and ‘igraph_bool_t’ (bool). The default is + ‘igraph_real_t’. + +heap + Heap is currently defined for ‘igraph_real_t’, ‘igraph_int_t’ + (int), ‘char’ (char). In addition both maximum and minimum heaps + are available. The default is the ‘igraph_real_t’ maximum heap. + +list of vectors + Lists of vectors are currently defined for vectors holding + ‘igraph_real_t’ and ‘igraph_int_t’ (int). The default is + ‘igraph_real_t’. + +list of matrices + Lists of matrices are currently defined for matrices holding + ‘igraph_real_t’ only. + + The name of the base element (in parentheses) is added to the +function names, except for the default type. + + Some examples: + + • ‘igraph_vector_t’ is a vector of ‘igraph_real_t’ elements. Its + functions are ‘igraph_vector_init’, ‘igraph_vector_destroy’, + ‘igraph_vector_sort’, etc. + + • ‘igraph_vector_bool_t’ is a vector of ‘igraph_bool_t’ elements; + initialize it with ‘igraph_vector_bool_init’, destroy it with + ‘igraph_vector_bool_destroy’, etc. + + • ‘igraph_heap_t’ is a maximum heap with ‘igraph_real_t’ elements. + The corresponding functions are ‘igraph_heap_init’, + ‘igraph_heap_pop’, etc. + + • ‘igraph_heap_min_t’ is a minimum heap with ‘igraph_real_t’ + elements. The corresponding functions are called + ‘igraph_heap_min_init’, ‘igraph_heap_min_pop’, etc. + + • ‘igraph_heap_int_t’ is a maximum heap with ‘igraph_int_t’ elements. + Its functions have the ‘igraph_heap_int_’ prefix. + + • ‘igraph_heap_min_int_t’ is a minimum heap containing ‘igraph_int_t’ + elements. Its functions have the ‘igraph_heap_min_int_’ prefix. + + • ‘igraph_vector_list_t’ is a list of (floating-point) vectors; each + element in this data structure is an ‘igraph_vector_t’. Similarly, + ‘igraph_matrix_list_t’ is a list of (floating-point) matrices; each + element in this data structure is an ‘igraph_matrix_t’. + + • ‘igraph_vector_int_list_t’ is a list of integer vectors; each + element in this data structure is an ‘igraph_vector_int_t’. + + Note that the VECTOR (*note VECTOR --- Accessing an element of a +vector_::) and the MATRIX (*note MATRIX --- Accessing an element of a +matrix_::) macros can be used on _all_ vector and matrix types. VECTOR +(*note VECTOR --- Accessing an element of a vector_::) cannot be used on +_lists_ of vectors, though, only on the individial vectors in the list. + + +File: igraph-docs.info, Node: Vectors, Next: Matrices, Prev: About template types, Up: Data structure library; vector; matrix; other data types + +7.2 Vectors +=========== + +* Menu: + +* About igraph_vector_t objects:: +* Constructors and destructors:: +* Initializing elements:: +* Accessing elements:: +* Vector views:: +* Copying vectors:: +* Exchanging elements:: +* Vector operations:: +* Vector comparisons:: +* Finding minimum and maximum:: +* Vector properties:: +* Searching for elements:: +* Resizing operations:: +* Complex vector operations:: +* Sorting:: +* Set operations on sorted vectors:: +* Pointer vectors (igraph_vector_ptr_t): Pointer vectors [igraph_vector_ptr_t]. + + +File: igraph-docs.info, Node: About igraph_vector_t objects, Next: Constructors and destructors, Up: Vectors + +7.2.1 About igraph_vector_t objects +----------------------------------- + +The ‘igraph_vector_t’ data type is a simple and efficient interface to +arrays containing numbers. It is something similar to (but much simpler +than) the ‘vector’ template in the C++ standard library. + + There are multiple variants of ‘igraph_vector_t’; the basic variant +stores doubles, but there is also ‘igraph_vector_int_t’ for integers (of +type ‘igraph_int_t’), ‘igraph_vector_bool_t’ for booleans (of type +‘igraph_bool_t’) and so on. Vectors are used extensively in ‘igraph’; +all functions that expect or return a list of numbers use +‘igraph_vector_t’ or ‘igraph_vector_int_t’ to achieve this. Integer +vectors are typically used when the vector is supposed to hold vertex or +edge identifiers, while ‘igraph_vector_t’ is used when the vector is +expected to hold fractional numbers or infinities. + + The ‘igraph_vector_t’ type and its variants usually use O(n) space to +store n elements. Sometimes they use more, this is because vectors can +shrink, but even if they shrink, the current implementation does not +free a single bit of memory. + + The elements in an ‘igraph_vector_t’ object and its variants are +indexed from zero, we follow the usual C convention here. + + The elements of a vector always occupy a single block of memory, the +starting address of this memory block can be queried with the ‘VECTOR’ +(*note VECTOR --- Accessing an element of a vector_::) macro. This way, +vector objects can be used with standard mathematical libraries, like +the GNU Scientific Library. + + Almost all of the functions described below for ‘igraph_vector_t’ +also exist for all the other vector type variants. These variants are +not documented separately; you can simply replace ‘vector’ with +‘vector_int’, ‘vector_bool’ or something similar if you need a function +for another variant. For instance, to initialize a vector of type +‘igraph_vector_int_t’, you need to use ‘igraph_vector_int_init()’ (*note +igraph_vector_init --- Initializes a vector object [constructor]_::) and +not ‘igraph_vector_init()’ (*note igraph_vector_init --- Initializes a +vector object [constructor]_::). + + +File: igraph-docs.info, Node: Constructors and destructors, Next: Initializing elements, Prev: About igraph_vector_t objects, Up: Vectors + +7.2.2 Constructors and destructors +---------------------------------- + +‘igraph_vector_t’ objects have to be initialized before using them, this +is analogous to calling a constructor on them. There are a number of +‘igraph_vector_t’ constructors, for your convenience. +‘igraph_vector_init()’ (*note igraph_vector_init --- Initializes a +vector object [constructor]_::) is the basic constructor, it creates a +vector of the given length, filled with zeros. +‘igraph_vector_init_copy()’ (*note igraph_vector_init_copy --- +Initializes a vector from another vector object [constructor]_::) +creates a new identical copy of an already existing and initialized +vector. ‘igraph_vector_init_array()’ (*note igraph_vector_init_array +--- Initializes a vector from an ordinary C array [constructor]_::) +creates a vector by copying a regular C array. +‘igraph_vector_init_range()’ (*note igraph_vector_init_range --- +Initializes a vector with a range_::) creates a vector containing a +regular sequence with increment one. + + ‘igraph_vector_view()’ (*note igraph_vector_view --- Handle a regular +C array as a igraph_vector_t_::) is a special constructor, it allows you +to handle a regular C array as a ‘vector’ without copying its elements. + + If a ‘igraph_vector_t’ object is not needed any more, it should be +destroyed to free its allocated memory by calling the ‘igraph_vector_t’ +destructor, ‘igraph_vector_destroy()’ (*note igraph_vector_destroy --- +Destroys a vector object_::). + + Note that vectors created by ‘igraph_vector_view()’ (*note +igraph_vector_view --- Handle a regular C array as a igraph_vector_t_::) +are special, you must not call ‘igraph_vector_destroy()’ (*note +igraph_vector_destroy --- Destroys a vector object_::) on these. + +* Menu: + +* igraph_vector_init -- Initializes a vector object (constructor).: igraph_vector_init --- Initializes a vector object [constructor]_. +* igraph_vector_init_array -- Initializes a vector from an ordinary C array (constructor).: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_. +* igraph_vector_init_copy -- Initializes a vector from another vector object (constructor).: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_. +* igraph_vector_init_range -- Initializes a vector with a range.: igraph_vector_init_range --- Initializes a vector with a range_. +* igraph_vector_destroy -- Destroys a vector object.: igraph_vector_destroy --- Destroys a vector object_. + + +File: igraph-docs.info, Node: igraph_vector_init --- Initializes a vector object [constructor]_, Next: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_, Up: Constructors and destructors + +7.2.2.1 igraph_vector_init -- Initializes a vector object (constructor). +........................................................................ + + + igraph_error_t igraph_vector_init(igraph_vector_t *v, igraph_int_t size); + + Every vector needs to be initialized before it can be used, and there +are a number of initialization functions or otherwise called +constructors. This function constructs a vector of the given size and +initializes each entry to 0. Note that ‘igraph_vector_null()’ (*note +igraph_vector_null --- Sets each element in the vector to zero_::) can +be used to set each element of a vector to zero. However, if you want a +vector of zeros, it is much faster to use this function than to create a +vector and then invoke ‘igraph_vector_null()’ (*note igraph_vector_null +--- Sets each element in the vector to zero_::). + + Every vector object initialized by this function should be destroyed +(ie. the memory allocated for it should be freed) when it is not needed +anymore, the ‘igraph_vector_destroy()’ (*note igraph_vector_destroy --- +Destroys a vector object_::) function is responsible for this. + + *Arguments:. * + +‘v’: + Pointer to a not yet initialized vector object. + +‘size’: + The size of the vector. + + *Returns:. * + +‘’ + error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, the amount of 'time' +required to allocate O(n) elements, n is the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_, Next: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_, Prev: igraph_vector_init --- Initializes a vector object [constructor]_, Up: Constructors and destructors + +7.2.2.2 igraph_vector_init_array -- Initializes a vector from an ordinary C array (constructor). +................................................................................................ + + + igraph_error_t igraph_vector_init_array( + igraph_vector_t *v, const igraph_real_t *data, igraph_int_t length); + + *Arguments:. * + +‘v’: + Pointer to an uninitialized vector object. + +‘data’: + A regular C array. + +‘length’: + The length of the C array. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system specific, usually O(‘length’). + + +File: igraph-docs.info, Node: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_, Next: igraph_vector_init_range --- Initializes a vector with a range_, Prev: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_, Up: Constructors and destructors + +7.2.2.3 igraph_vector_init_copy -- Initializes a vector from another vector object (constructor). +................................................................................................. + + + igraph_error_t igraph_vector_init_copy( + igraph_vector_t *to, const igraph_vector_t *from + ); + + The contents of the existing vector object will be copied to the new +one. + + *Arguments:. * + +‘to’: + Pointer to a not yet initialized vector object. + +‘from’: + The original vector object to copy. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, usually O(n), n is the +size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_init_range --- Initializes a vector with a range_, Next: igraph_vector_destroy --- Destroys a vector object_, Prev: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_, Up: Constructors and destructors + +7.2.2.4 igraph_vector_init_range -- Initializes a vector with a range. +...................................................................... + + + igraph_error_t igraph_vector_init_range(igraph_vector_t *v, igraph_real_t start, igraph_real_t end); + + The vector will contain the numbers ‘start’, ‘start’+1, ..., ‘end’-1. +Note that the range is closed from the left and open from the right, +according to C conventions. + + *Arguments:. * + +‘v’: + Pointer to an uninitialized vector object. + +‘start’: + The lower limit in the range (inclusive). + +‘end’: + The upper limit in the range (exclusive). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: out of memory. + + Time complexity: O(n), the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_destroy --- Destroys a vector object_, Prev: igraph_vector_init_range --- Initializes a vector with a range_, Up: Constructors and destructors + +7.2.2.5 igraph_vector_destroy -- Destroys a vector object. +.......................................................... + + + void igraph_vector_destroy(igraph_vector_t *v); + + All vectors initialized by ‘igraph_vector_init()’ (*note +igraph_vector_init --- Initializes a vector object [constructor]_::) +should be properly destroyed by this function. A destroyed vector needs +to be reinitialized by ‘igraph_vector_init()’ (*note igraph_vector_init +--- Initializes a vector object [constructor]_::), +‘igraph_vector_init_array()’ (*note igraph_vector_init_array --- +Initializes a vector from an ordinary C array [constructor]_::) or +another constructor. + + *Arguments:. * + +‘v’: + Pointer to the (previously initialized) vector object to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: Initializing elements, Next: Accessing elements, Prev: Constructors and destructors, Up: Vectors + +7.2.3 Initializing elements +--------------------------- + +* Menu: + +* igraph_vector_null -- Sets each element in the vector to zero.: igraph_vector_null --- Sets each element in the vector to zero_. +* igraph_vector_fill -- Fill a vector with a constant element.: igraph_vector_fill --- Fill a vector with a constant element_. +* igraph_vector_range -- Updates a vector to store a range.: igraph_vector_range --- Updates a vector to store a range_. + + +File: igraph-docs.info, Node: igraph_vector_null --- Sets each element in the vector to zero_, Next: igraph_vector_fill --- Fill a vector with a constant element_, Up: Initializing elements + +7.2.3.1 igraph_vector_null -- Sets each element in the vector to zero. +...................................................................... + + + void igraph_vector_null(igraph_vector_t *v); + + Note that ‘igraph_vector_init()’ (*note igraph_vector_init --- +Initializes a vector object [constructor]_::) sets the elements to zero +as well, so it makes no sense to call this function on a just +initialized vector. Thus if you want to construct a vector of zeros, +then you should use ‘igraph_vector_init()’ (*note igraph_vector_init --- +Initializes a vector object [constructor]_::). + + *Arguments:. * + +‘v’: + The vector object. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_fill --- Fill a vector with a constant element_, Next: igraph_vector_range --- Updates a vector to store a range_, Prev: igraph_vector_null --- Sets each element in the vector to zero_, Up: Initializing elements + +7.2.3.2 igraph_vector_fill -- Fill a vector with a constant element. +.................................................................... + + + void igraph_vector_fill(igraph_vector_t *v, igraph_real_t e); + + Sets each element of the vector to the supplied constant. + + *Arguments:. * + +‘vector’: + The vector to work on. + +‘e’: + The element to fill with. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_range --- Updates a vector to store a range_, Prev: igraph_vector_fill --- Fill a vector with a constant element_, Up: Initializing elements + +7.2.3.3 igraph_vector_range -- Updates a vector to store a range. +................................................................. + + + igraph_error_t igraph_vector_range(igraph_vector_t *v, igraph_real_t start, igraph_real_t end); + + Sets the elements of the vector to contain the numbers ‘start’, +‘start’+1, ..., ‘end’-1. Note that the range is closed from the left +and open from the right, according to C conventions. The vector will be +resized to fit the range. + + *Arguments:. * + +‘v’: + The vector to update. + +‘start’: + The lower limit in the range (inclusive). + +‘end’: + The upper limit in the range (exclusive). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: out of memory. + + Time complexity: O(n), the number of elements in the vector. + + +File: igraph-docs.info, Node: Accessing elements, Next: Vector views, Prev: Initializing elements, Up: Vectors + +7.2.4 Accessing elements +------------------------ + +The simplest and most performant way to access an element of a vector is +to use the ‘VECTOR’ (*note VECTOR --- Accessing an element of a +vector_::) macro. This macro can be used both for querying and setting +‘igraph_vector_t’ elements. If you need a function, +‘igraph_vector_get()’ (*note igraph_vector_get --- Access an element of +a vector_::) queries and ‘igraph_vector_set()’ (*note igraph_vector_set +--- Assignment to an element of a vector_::) sets an element of a +vector. ‘igraph_vector_get_ptr()’ (*note igraph_vector_get_ptr --- Get +the address of an element of a vector_::) returns the address of an +element. + + ‘igraph_vector_tail()’ (*note igraph_vector_tail --- Returns the last +element in a vector_::) returns the last element of a non-empty vector. +There is no ‘igraph_vector_head()’ function however, as it is easy to +write ‘VECTOR(v)[0]’ instead. + +* Menu: + +* VECTOR -- Accessing an element of a vector.: VECTOR --- Accessing an element of a vector_. +* igraph_vector_get -- Access an element of a vector.: igraph_vector_get --- Access an element of a vector_. +* igraph_vector_get_ptr -- Get the address of an element of a vector.: igraph_vector_get_ptr --- Get the address of an element of a vector_. +* igraph_vector_set -- Assignment to an element of a vector.: igraph_vector_set --- Assignment to an element of a vector_. +* igraph_vector_tail -- Returns the last element in a vector.: igraph_vector_tail --- Returns the last element in a vector_. +* igraph_vector_index -- Extract elements from a vector at specific indices.: igraph_vector_index --- Extract elements from a vector at specific indices_. +* igraph_vector_index_in_place -- Extract elements from a vector at specific indices in-place.: igraph_vector_index_in_place --- Extract elements from a vector at specific indices in-place_. + + +File: igraph-docs.info, Node: VECTOR --- Accessing an element of a vector_, Next: igraph_vector_get --- Access an element of a vector_, Up: Accessing elements + +7.2.4.1 VECTOR -- Accessing an element of a vector. +................................................... + + + #define VECTOR(v) + + Usage: + + VECTOR(v)[0] + +to access the first element of the vector, you can also use this in +assignments, like: + + VECTOR(v)[10] = 5; + +Note that there are no range checks right now. + + *Arguments:. * + +‘v’: + The vector object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_get --- Access an element of a vector_, Next: igraph_vector_get_ptr --- Get the address of an element of a vector_, Prev: VECTOR --- Accessing an element of a vector_, Up: Accessing elements + +7.2.4.2 igraph_vector_get -- Access an element of a vector. +........................................................... + + + igraph_real_t igraph_vector_get(const igraph_vector_t *v, igraph_int_t pos); + + Unless you need a function, consider using the ‘VECTOR’ (*note VECTOR +--- Accessing an element of a vector_::) macro instead for better +performance. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object. + +‘pos’: + The position of the element, the index of the first element is + zero. + + *Returns:. * + +‘’ + The desired element. + + *See also:. * + +‘’ + ‘igraph_vector_get_ptr()’ (*note igraph_vector_get_ptr --- Get the + address of an element of a vector_::) and the ‘VECTOR’ (*note + VECTOR --- Accessing an element of a vector_::) macro. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_get_ptr --- Get the address of an element of a vector_, Next: igraph_vector_set --- Assignment to an element of a vector_, Prev: igraph_vector_get --- Access an element of a vector_, Up: Accessing elements + +7.2.4.3 igraph_vector_get_ptr -- Get the address of an element of a vector. +........................................................................... + + + igraph_real_t* igraph_vector_get_ptr(const igraph_vector_t *v, igraph_int_t pos); + + Unless you need a function, consider using the ‘VECTOR’ (*note VECTOR +--- Accessing an element of a vector_::) macro instead for better +performance. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object. + +‘pos’: + The position of the element, the position of the first element is + zero. + + *Returns:. * + +‘’ + Pointer to the desired element. + + *See also:. * + +‘’ + ‘igraph_vector_get()’ (*note igraph_vector_get --- Access an + element of a vector_::) and the ‘VECTOR’ (*note VECTOR --- + Accessing an element of a vector_::) macro. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_set --- Assignment to an element of a vector_, Next: igraph_vector_tail --- Returns the last element in a vector_, Prev: igraph_vector_get_ptr --- Get the address of an element of a vector_, Up: Accessing elements + +7.2.4.4 igraph_vector_set -- Assignment to an element of a vector. +.................................................................. + + + void igraph_vector_set(igraph_vector_t *v, igraph_int_t pos, igraph_real_t value); + + Unless you need a function, consider using the ‘VECTOR’ (*note VECTOR +--- Accessing an element of a vector_::) macro instead for better +performance. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ element. + +‘pos’: + Position of the element to set. + +‘value’: + New value of the element. + + *See also:. * + +‘’ + ‘igraph_vector_get()’ (*note igraph_vector_get --- Access an + element of a vector_::). + + +File: igraph-docs.info, Node: igraph_vector_tail --- Returns the last element in a vector_, Next: igraph_vector_index --- Extract elements from a vector at specific indices_, Prev: igraph_vector_set --- Assignment to an element of a vector_, Up: Accessing elements + +7.2.4.5 igraph_vector_tail -- Returns the last element in a vector. +................................................................... + + + igraph_real_t igraph_vector_tail(const igraph_vector_t *v); + + It is an error to call this function on an empty vector, the result +is undefined. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The last element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_index --- Extract elements from a vector at specific indices_, Next: igraph_vector_index_in_place --- Extract elements from a vector at specific indices in-place_, Prev: igraph_vector_tail --- Returns the last element in a vector_, Up: Accessing elements + +7.2.4.6 igraph_vector_index -- Extract elements from a vector at specific indices. +.................................................................................. + + + igraph_error_t igraph_vector_index(const igraph_vector_t *v, + igraph_vector_t *newv, + const igraph_vector_int_t *idx); + + *Arguments:. * + +‘v’: + the vector to extract elements from + +‘newv’: + the result vector + +‘idx’: + vector containing the indices of the elements to extract + + *See also:. * + +‘’ + ‘igraph_vector_index_in_place’ (*note igraph_vector_index_in_place + --- Extract elements from a vector at specific indices in-place_::) + for the in-place variant + + +File: igraph-docs.info, Node: igraph_vector_index_in_place --- Extract elements from a vector at specific indices in-place_, Prev: igraph_vector_index --- Extract elements from a vector at specific indices_, Up: Accessing elements + +7.2.4.7 igraph_vector_index_in_place -- Extract elements from a vector at specific indices in-place. +.................................................................................................... + + + igraph_error_t igraph_vector_index_in_place(igraph_vector_t *v, + const igraph_vector_int_t *idx); + + *Arguments:. * + +‘v’: + the vector to extract elements from. This will be modified + in-place. + +‘idx’: + vector containing the indices of the elements to extract + + *See also:. * + +‘’ + ‘igraph_vector_index’ (*note igraph_vector_index --- Extract + elements from a vector at specific indices_::) for a function that + does not modify the original vector + + +File: igraph-docs.info, Node: Vector views, Next: Copying vectors, Prev: Accessing elements, Up: Vectors + +7.2.5 Vector views +------------------ + +* Menu: + +* igraph_vector_view -- Handle a regular C array as a igraph_vector_t.: igraph_vector_view --- Handle a regular C array as a igraph_vector_t_. + + +File: igraph-docs.info, Node: igraph_vector_view --- Handle a regular C array as a igraph_vector_t_, Up: Vector views + +7.2.5.1 igraph_vector_view -- Handle a regular C array as a igraph_vector_t. +............................................................................ + + + igraph_vector_t igraph_vector_view( + const igraph_real_t *data, igraph_int_t length); + + This function lets you treat an existing C array as an +‘igraph_vector_t’ (*note About igraph_vector_t objects::). + + Since this function creates a view into an existing array, you must +_not_ destroy the ‘igraph_vector_t’ object when you are done with it. +Similarly, you must _not_ call any function on it that may attempt to +modify the size of the vector. Modifying an element in the vector will +modify the underlying array as the two share the same memory block. + + Typical usage pattern: + + + igraph_real_t array[] = { 1.0, 1.5, 2.0 }; + const igraph_vector_t v = igraph_vector_view(array, sizeof(array) / sizeof(array[0])); + printf("The sum of vector elements is %g.\n", igraph_vector_sum(&v)); + + *Arguments:. * + +‘data’: + The raw array that the vector provides a view into. + +‘length’: + The length of the C array. + + *Returns:. * + +‘’ + The vector object providing the view into the array. + + Time complexity: O(1) + + +File: igraph-docs.info, Node: Copying vectors, Next: Exchanging elements, Prev: Vector views, Up: Vectors + +7.2.6 Copying vectors +--------------------- + +* Menu: + +* igraph_vector_copy_to -- Copies the contents of a vector to a C array.: igraph_vector_copy_to --- Copies the contents of a vector to a C array_. +* igraph_vector_update -- Update a vector from another one.: igraph_vector_update --- Update a vector from another one_. +* igraph_vector_append -- Append a vector to another one.: igraph_vector_append --- Append a vector to another one_. +* igraph_vector_swap -- Swap all elements of two vectors.: igraph_vector_swap --- Swap all elements of two vectors_. + + +File: igraph-docs.info, Node: igraph_vector_copy_to --- Copies the contents of a vector to a C array_, Next: igraph_vector_update --- Update a vector from another one_, Up: Copying vectors + +7.2.6.1 igraph_vector_copy_to -- Copies the contents of a vector to a C array. +.............................................................................. + + + void igraph_vector_copy_to(const igraph_vector_t *v, igraph_real_t *to); + + The C array should have sufficient length. + + *Arguments:. * + +‘v’: + The vector object. + +‘to’: + The C array. + + Time complexity: O(n), n is the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_update --- Update a vector from another one_, Next: igraph_vector_append --- Append a vector to another one_, Prev: igraph_vector_copy_to --- Copies the contents of a vector to a C array_, Up: Copying vectors + +7.2.6.2 igraph_vector_update -- Update a vector from another one. +................................................................. + + + igraph_error_t igraph_vector_update(igraph_vector_t *to, + const igraph_vector_t *from); + + After this operation the contents of ‘to’ will be exactly the same as +that of ‘from’. The vector ‘to’ will be resized if it was originally +shorter or longer than ‘from’. + + *Arguments:. * + +‘to’: + The vector to update. + +‘from’: + The vector to update from. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of elements in ‘from’. + + +File: igraph-docs.info, Node: igraph_vector_append --- Append a vector to another one_, Next: igraph_vector_swap --- Swap all elements of two vectors_, Prev: igraph_vector_update --- Update a vector from another one_, Up: Copying vectors + +7.2.6.3 igraph_vector_append -- Append a vector to another one. +............................................................... + + + igraph_error_t igraph_vector_append(igraph_vector_t *to, + const igraph_vector_t *from); + + The target vector will be resized (except when ‘from’ is empty). + + *Arguments:. * + +‘to’: + The vector to append to. + +‘from’: + The vector to append, it is kept unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of elements in the new vector. + + +File: igraph-docs.info, Node: igraph_vector_swap --- Swap all elements of two vectors_, Prev: igraph_vector_append --- Append a vector to another one_, Up: Copying vectors + +7.2.6.4 igraph_vector_swap -- Swap all elements of two vectors. +............................................................... + + + void igraph_vector_swap(igraph_vector_t *v1, igraph_vector_t *v2); + + *Arguments:. * + +‘v1’: + The first vector. + +‘v2’: + The second vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Exchanging elements, Next: Vector operations, Prev: Copying vectors, Up: Vectors + +7.2.7 Exchanging elements +------------------------- + +* Menu: + +* igraph_vector_swap_elements -- Swap two elements in a vector.: igraph_vector_swap_elements --- Swap two elements in a vector_. +* igraph_vector_reverse -- Reverse the elements of a vector.: igraph_vector_reverse --- Reverse the elements of a vector_. +* igraph_vector_reverse_section -- Reverse the elements in a section of a vector.: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_. +* igraph_vector_rotate_left -- Rotates the elements of a vector to the left.: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_. +* igraph_vector_shuffle -- Shuffles a vector in-place using the Fisher-Yates method.: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_. +* igraph_vector_permute -- Permutes the elements of a vector in place according to an index vector.: igraph_vector_permute --- Permutes the elements of a vector in place according to an index vector_. + + +File: igraph-docs.info, Node: igraph_vector_swap_elements --- Swap two elements in a vector_, Next: igraph_vector_reverse --- Reverse the elements of a vector_, Up: Exchanging elements + +7.2.7.1 igraph_vector_swap_elements -- Swap two elements in a vector. +..................................................................... + + + void igraph_vector_swap_elements(igraph_vector_t *v, + igraph_int_t i, igraph_int_t j); + + Note that currently no range checking is performed. + + *Arguments:. * + +‘v’: + The input vector. + +‘i’: + Index of the first element. + +‘j’: + Index of the second element (may be the same as the first one). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_reverse --- Reverse the elements of a vector_, Next: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_, Prev: igraph_vector_swap_elements --- Swap two elements in a vector_, Up: Exchanging elements + +7.2.7.2 igraph_vector_reverse -- Reverse the elements of a vector. +.................................................................. + + + void igraph_vector_reverse(igraph_vector_t *v); + + The first element will be last, the last element will be first, etc. + + *Arguments:. * + +‘v’: + The input vector. + + *See also:. * + +‘’ + igraph_vector_reverse_section() to reverse only a section of a + vector. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_, Next: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_, Prev: igraph_vector_reverse --- Reverse the elements of a vector_, Up: Exchanging elements + +7.2.7.3 igraph_vector_reverse_section -- Reverse the elements in a section of a vector. +....................................................................................... + + + void igraph_vector_reverse_section(igraph_vector_t *v, igraph_int_t from, igraph_int_t to); + + *Arguments:. * + +‘v’: + The input vector. + +‘from’: + Index of the first element to include in the reversal. + +‘to’: + Index of the first element _not_ to include in the reversal. + + *See also:. * + +‘’ + igraph_vector_reverse() to reverse the entire vector. + + Time complexity: O(to - from), the number of elements to reverse. + + +File: igraph-docs.info, Node: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_, Next: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_, Prev: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_, Up: Exchanging elements + +7.2.7.4 igraph_vector_rotate_left -- Rotates the elements of a vector to the left. +.................................................................................. + + + void igraph_vector_rotate_left(igraph_vector_t *v, igraph_int_t n); + + Rotates the elements of a vector to the left by the given number of +steps. Element index ‘n’ will have index 0 after the rotation. For +example, rotating ‘(0, 1, 2, 3, 4, 5)’ by 2 yields ‘(2, 3, 4, 5, 0, 1)’. + + *Arguments:. * + +‘v’: + The input vector. + +‘n’: + The number of steps to rotate by. Passing a negative value rotates + to the right. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_, Next: igraph_vector_permute --- Permutes the elements of a vector in place according to an index vector_, Prev: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_, Up: Exchanging elements + +7.2.7.5 igraph_vector_shuffle -- Shuffles a vector in-place using the Fisher-Yates method. +.......................................................................................... + + + void igraph_vector_shuffle(igraph_vector_t *v); + + The Fisher-Yates shuffle ensures that every permutation is equally +probable when using a proper randomness source. Of course this does not +apply to pseudo-random generators as the cycle of these generators is +less than the number of possible permutations of the vector if the +vector is long enough. + + *Arguments:. * + +‘v’: + The vector object. + + Time complexity: O(n), n is the number of elements in the vector. + + References: + +‘(Fisher & Yates 1963)’ + R. A. Fisher and F. Yates. _ Statistical Tables for Biological, + Agricultural and Medical Research. _ Oliver and Boyd, 6th + edition, 1963, page 37. + +‘(Knuth 1998)’ + D. E. Knuth. _ Seminumerical Algorithms, _ volume 2 of _ The Art + of Computer Programming. _ Addison-Wesley, 3rd edition, 1998, + page 145. + + * File examples/simple/igraph_fisher_yates_shuffle.c* + + +File: igraph-docs.info, Node: igraph_vector_permute --- Permutes the elements of a vector in place according to an index vector_, Prev: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_, Up: Exchanging elements + +7.2.7.6 igraph_vector_permute -- Permutes the elements of a vector in place according to an index vector. +......................................................................................................... + + + igraph_error_t igraph_vector_permute(igraph_vector_t *v, const igraph_vector_int_t *index); + + This function takes a vector ‘v’ and a corresponding index vector +‘ind’, and permutes the elements of ‘v’ such that ‘v’[ind[i]] is moved +to become ‘v’[i] after the function is executed. + + It is an error to call this function with an index vector that does +not represent a valid permutation. Each element in the index vector +must be between 0 and the length of the vector minus one (inclusive), +and each such element must appear only once. The function does not +attempt to validate the index vector. + + The index vector that this function takes is compatible with the +index vector returned from ‘igraph_vector_sort_ind()’ (*note +igraph_vector_sort_ind --- Returns a permutation of indices that sorts a +vector_::); passing in the index vector from ‘igraph_vector_sort_ind()’ +(*note igraph_vector_sort_ind --- Returns a permutation of indices that +sorts a vector_::) will sort the original vector. + + As a special case, this function allows the index vector to be +_shorter_ than the vector being permuted, in which case the elements +whose indices do not occur in the index vector will be removed from the +vector. + + *Arguments:. * + +‘v’: + the vector to permute + +‘ind’: + the index vector + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: Vector operations, Next: Vector comparisons, Prev: Exchanging elements, Up: Vectors + +7.2.8 Vector operations +----------------------- + +* Menu: + +* igraph_vector_add_constant -- Add a constant to the vector.: igraph_vector_add_constant --- Add a constant to the vector_. +* igraph_vector_scale -- Multiplies all elements of a vector by a constant.: igraph_vector_scale --- Multiplies all elements of a vector by a constant_. +* igraph_vector_add -- Add two vectors.: igraph_vector_add --- Add two vectors_. +* igraph_vector_sub -- Subtract a vector from another one.: igraph_vector_sub --- Subtract a vector from another one_. +* igraph_vector_mul -- Multiply two vectors.: igraph_vector_mul --- Multiply two vectors_. +* igraph_vector_div -- Divide a vector by another one.: igraph_vector_div --- Divide a vector by another one_. +* igraph_vector_floor -- Transform a real vector to an integer vector by flooring each element.: igraph_vector_floor --- Transform a real vector to an integer vector by flooring each element_. + + +File: igraph-docs.info, Node: igraph_vector_add_constant --- Add a constant to the vector_, Next: igraph_vector_scale --- Multiplies all elements of a vector by a constant_, Up: Vector operations + +7.2.8.1 igraph_vector_add_constant -- Add a constant to the vector. +................................................................... + + + void igraph_vector_add_constant(igraph_vector_t *v, igraph_real_t plus); + + ‘plus’ is added to every element of ‘v’. Note that overflow might +happen. + + *Arguments:. * + +‘v’: + The input vector. + +‘plus’: + The constant to add. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_scale --- Multiplies all elements of a vector by a constant_, Next: igraph_vector_add --- Add two vectors_, Prev: igraph_vector_add_constant --- Add a constant to the vector_, Up: Vector operations + +7.2.8.2 igraph_vector_scale -- Multiplies all elements of a vector by a constant. +................................................................................. + + + void igraph_vector_scale(igraph_vector_t *v, igraph_real_t by); + + *Arguments:. * + +‘v’: + The vector. + +‘by’: + The constant. + + *Returns:. * + +‘’ + Error code. The current implementation always returns with + success. + + Added in version 0.2. + + Time complexity: O(n), the number of elements in a vector. + + +File: igraph-docs.info, Node: igraph_vector_add --- Add two vectors_, Next: igraph_vector_sub --- Subtract a vector from another one_, Prev: igraph_vector_scale --- Multiplies all elements of a vector by a constant_, Up: Vector operations + +7.2.8.3 igraph_vector_add -- Add two vectors. +............................................. + + + igraph_error_t igraph_vector_add(igraph_vector_t *v1, + const igraph_vector_t *v2); + + Add the elements of ‘v2’ to ‘v1’, the result is stored in ‘v1’. The +two vectors must have the same length. + + *Arguments:. * + +‘v1’: + The first vector, the result will be stored here. + +‘v2’: + The second vector, its contents will be unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_sub --- Subtract a vector from another one_, Next: igraph_vector_mul --- Multiply two vectors_, Prev: igraph_vector_add --- Add two vectors_, Up: Vector operations + +7.2.8.4 igraph_vector_sub -- Subtract a vector from another one. +................................................................ + + + igraph_error_t igraph_vector_sub(igraph_vector_t *v1, + const igraph_vector_t *v2); + + Subtract the elements of ‘v2’ from ‘v1’, the result is stored in +‘v1’. The two vectors must have the same length. + + *Arguments:. * + +‘v1’: + The first vector, to subtract from. The result is stored here. + +‘v2’: + The vector to subtract, it will be unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_mul --- Multiply two vectors_, Next: igraph_vector_div --- Divide a vector by another one_, Prev: igraph_vector_sub --- Subtract a vector from another one_, Up: Vector operations + +7.2.8.5 igraph_vector_mul -- Multiply two vectors. +.................................................. + + + igraph_error_t igraph_vector_mul(igraph_vector_t *v1, + const igraph_vector_t *v2); + + ‘v1’ will be multiplied by ‘v2’, elementwise. The two vectors must +have the same length. + + *Arguments:. * + +‘v1’: + The first vector, the result will be stored here. + +‘v2’: + The second vector, it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_div --- Divide a vector by another one_, Next: igraph_vector_floor --- Transform a real vector to an integer vector by flooring each element_, Prev: igraph_vector_mul --- Multiply two vectors_, Up: Vector operations + +7.2.8.6 igraph_vector_div -- Divide a vector by another one. +............................................................ + + + igraph_error_t igraph_vector_div(igraph_vector_t *v1, + const igraph_vector_t *v2); + + ‘v1’ is divided by ‘v2’, elementwise. They must have the same +length. If the base type of the vector can generate divide by zero +errors then please make sure that ‘v2’ contains no zero if you want to +avoid trouble. + + *Arguments:. * + +‘v1’: + The dividend. The result is also stored here. + +‘v2’: + The divisor, it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_floor --- Transform a real vector to an integer vector by flooring each element_, Prev: igraph_vector_div --- Divide a vector by another one_, Up: Vector operations + +7.2.8.7 igraph_vector_floor -- Transform a real vector to an integer vector by flooring each element. +..................................................................................................... + + + igraph_error_t igraph_vector_floor(const igraph_vector_t *from, igraph_vector_int_t *to); + + Flooring means rounding down to the nearest integer. + + *Arguments:. * + +‘from’: + The original real vector object. + +‘to’: + Pointer to an initialized integer vector. The result will be + stored here. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: out of memory + + Time complexity: O(n), where n is the number of elements in the +vector. + + +File: igraph-docs.info, Node: Vector comparisons, Next: Finding minimum and maximum, Prev: Vector operations, Up: Vectors + +7.2.9 Vector comparisons +------------------------ + +* Menu: + +* igraph_vector_all_e --- Are all elements equal?:: +* igraph_vector_all_almost_e --- Are all elements almost equal?:: +* igraph_vector_all_l --- Are all elements less?:: +* igraph_vector_all_g --- Are all elements greater?:: +* igraph_vector_all_le --- Are all elements less or equal?:: +* igraph_vector_all_ge --- Are all elements greater or equal?:: +* igraph_vector_is_equal --- Are all elements equal?:: +* igraph_vector_zapsmall -- Replaces small elements of a vector by exact zeros.: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_. +* igraph_vector_lex_cmp -- Lexicographical comparison of two vectors (type-safe variant).: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_. +* igraph_vector_lex_cmp_untyped -- Lexicographical comparison of two vectors (non-type-safe).: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_. +* igraph_vector_colex_cmp -- Colexicographical comparison of two vectors.: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_. +* igraph_vector_colex_cmp_untyped -- Colexicographical comparison of two vectors.: igraph_vector_colex_cmp_untyped --- Colexicographical comparison of two vectors_. + + +File: igraph-docs.info, Node: igraph_vector_all_e --- Are all elements equal?, Next: igraph_vector_all_almost_e --- Are all elements almost equal?, Up: Vector comparisons + +7.2.9.1 igraph_vector_all_e -- Are all elements equal? +...................................................... + + + igraph_bool_t igraph_vector_all_e(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + Checks element-wise equality of two vectors. For vectors containing +floating point values, consider using ‘igraph_matrix_all_almost_e()’ +(*note igraph_matrix_all_almost_e --- Are all elements almost equal?::). + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all equal to the + corresponding elements in ‘rhs’. Returns false if the lengths of + the vectors don't match. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_all_almost_e --- Are all elements almost equal?, Next: igraph_vector_all_l --- Are all elements less?, Prev: igraph_vector_all_e --- Are all elements equal?, Up: Vector comparisons + +7.2.9.2 igraph_vector_all_almost_e -- Are all elements almost equal? +.................................................................... + + + igraph_bool_t igraph_vector_all_almost_e(const igraph_vector_t *lhs, + const igraph_vector_t *rhs, + igraph_real_t eps); + + Checks if the elements of two vectors are equal within a relative +tolerance. + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + +‘eps’: + Relative tolerance, see ‘igraph_almost_equals()’ (*note + igraph_almost_equals --- Compare two double-precision floats with a + tolerance_::) for details. + + *Returns:. * + +‘’ + True if the two vectors are almost equal, false if there is at + least one differing element or if the vectors are not of the same + size. + + +File: igraph-docs.info, Node: igraph_vector_all_l --- Are all elements less?, Next: igraph_vector_all_g --- Are all elements greater?, Prev: igraph_vector_all_almost_e --- Are all elements almost equal?, Up: Vector comparisons + +7.2.9.3 igraph_vector_all_l -- Are all elements less? +..................................................... + + + igraph_bool_t igraph_vector_all_l(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all less than the + corresponding elements in ‘rhs’. Returns false if the lengths of + the vectors don't match. If any element is NaN, it will return + false. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_all_g --- Are all elements greater?, Next: igraph_vector_all_le --- Are all elements less or equal?, Prev: igraph_vector_all_l --- Are all elements less?, Up: Vector comparisons + +7.2.9.4 igraph_vector_all_g -- Are all elements greater? +........................................................ + + + igraph_bool_t igraph_vector_all_g(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all greater than the + corresponding elements in ‘rhs’. Returns false if the lengths of + the vectors don't match. If any element is NaN, it will return + false. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_all_le --- Are all elements less or equal?, Next: igraph_vector_all_ge --- Are all elements greater or equal?, Prev: igraph_vector_all_g --- Are all elements greater?, Up: Vector comparisons + +7.2.9.5 igraph_vector_all_le -- Are all elements less or equal? +............................................................... + + + igraph_bool_t igraph_vector_all_le(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all less than or equal to the + corresponding elements in ‘rhs’. Returns false if the lengths of + the vectors don't match. If any element is NaN, it will return + false. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_all_ge --- Are all elements greater or equal?, Next: igraph_vector_is_equal --- Are all elements equal?, Prev: igraph_vector_all_le --- Are all elements less or equal?, Up: Vector comparisons + +7.2.9.6 igraph_vector_all_ge -- Are all elements greater or equal? +.................................................................. + + + igraph_bool_t igraph_vector_all_ge(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all greater than or equal to + the corresponding elements in ‘rhs’. Returns false if the lengths + of the vectors don't match. If any element is NaN, it will return + false. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_is_equal --- Are all elements equal?, Next: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_, Prev: igraph_vector_all_ge --- Are all elements greater or equal?, Up: Vector comparisons + +7.2.9.7 igraph_vector_is_equal -- Are all elements equal? +......................................................... + + + igraph_bool_t igraph_vector_is_equal(const igraph_vector_t *lhs, + const igraph_vector_t *rhs); + + This is an alias of ‘igraph_vector_all_e()’ (*note +igraph_vector_all_e --- Are all elements equal?::) with a more intuitive +name. + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all equal to the + corresponding elements in ‘rhs’. Returns false if the lengths of + the vectors don't match. + + Time complexity: O(n), the length of the vectors. + + +File: igraph-docs.info, Node: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_, Next: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_, Prev: igraph_vector_is_equal --- Are all elements equal?, Up: Vector comparisons + +7.2.9.8 igraph_vector_zapsmall -- Replaces small elements of a vector by exact zeros. +..................................................................................... + + + igraph_error_t igraph_vector_zapsmall(igraph_vector_t *v, igraph_real_t tol); + + Vector elements which are smaller in magnitude than the given +absolute tolerance will be replaced by exact zeros. The default +tolerance corresponds to two-thirds of the representable digits of +‘igraph_real_t’, i.e. ‘DBL_EPSILON^(2/3)’ which is approximately +‘10^-10’. + + *Arguments:. * + +‘v’: + The vector to process, it will be changed in-place. + +‘tol’: + Tolerance value. Numbers smaller than this in magnitude will be + replaced by zeros. Pass in zero to use the default tolerance. + Must not be negative. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vector_all_almost_e()’ (*note igraph_vector_all_almost_e + --- Are all elements almost equal?::) and ‘igraph_almost_equals()’ + (*note igraph_almost_equals --- Compare two double-precision floats + with a tolerance_::) to perform comparisons with relative + tolerances. + + +File: igraph-docs.info, Node: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_, Next: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_, Prev: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_, Up: Vector comparisons + +7.2.9.9 igraph_vector_lex_cmp -- Lexicographical comparison of two vectors (type-safe variant). +............................................................................................... + + + int igraph_vector_lex_cmp( + const igraph_vector_t *lhs, const igraph_vector_t *rhs + ); + + If the elements of two vectors match but one is shorter, the shorter +one comes first. Thus {1, 3} comes after {1, 2, 3}, but before {1, 3, +4}. + + This function is typically used together with +‘igraph_vector_list_sort()’ (*note igraph_vector_list_sort --- Sorts the +elements of the list into ascending order_::). + + *Arguments:. * + +‘lhs’: + Pointer to the first vector. + +‘rhs’: + Pointer to the second vector. + + *Returns:. * + +‘’ + -1 if ‘lhs’ is lexicographically smaller, 0 if ‘lhs’ and ‘rhs’ are + equal, else 1. + + *See also:. * + +‘’ + ‘igraph_vector_lex_cmp_untyped()’ (*note + igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two + vectors [non-type-safe]_::) for an untyped variant of this + function, or ‘igraph_vector_colex_cmp()’ (*note + igraph_vector_colex_cmp --- Colexicographical comparison of two + vectors_::) to compare vectors starting from the last element. + + Time complexity: O(n), the number of elements in the smaller vector. + + * File examples/simple/igraph_vector_int_list_sort.c* + + +File: igraph-docs.info, Node: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_, Next: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_, Prev: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_, Up: Vector comparisons + +7.2.9.10 igraph_vector_lex_cmp_untyped -- Lexicographical comparison of two vectors (non-type-safe). +.................................................................................................... + + + int igraph_vector_lex_cmp_untyped(const void *lhs, const void *rhs); + + If the elements of two vectors match but one is shorter, the shorter +one comes first. Thus {1, 3} comes after {1, 2, 3}, but before {1, 3, +4}. + + This function is typically used together with +‘igraph_vector_ptr_sort()’ (*note igraph_vector_ptr_sort --- Sorts the +pointer vector based on an external comparison function_::). + + *Arguments:. * + +‘lhs’: + Pointer to a pointer to the first vector (interpreted as an + ‘igraph_vector_t **’). + +‘rhs’: + Pointer to a pointer to the second vector (interpreted as an + ‘igraph_vector_t **’). + + *Returns:. * + +‘’ + -1 if ‘lhs’ is lexicographically smaller, 0 if ‘lhs’ and ‘rhs’ are + equal, else 1. + + *See also:. * + +‘’ + ‘igraph_vector_lex_cmp()’ (*note igraph_vector_lex_cmp --- + Lexicographical comparison of two vectors [type-safe variant]_::) + for a type-safe variant of this function, or + ‘igraph_vector_colex_cmp_untyped()’ (*note + igraph_vector_colex_cmp_untyped --- Colexicographical comparison of + two vectors_::) to compare vectors starting from the last element. + + Time complexity: O(n), the number of elements in the smaller vector. + + +File: igraph-docs.info, Node: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_, Next: igraph_vector_colex_cmp_untyped --- Colexicographical comparison of two vectors_, Prev: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_, Up: Vector comparisons + +7.2.9.11 igraph_vector_colex_cmp -- Colexicographical comparison of two vectors. +................................................................................ + + + int igraph_vector_colex_cmp( + const igraph_vector_t *lhs, const igraph_vector_t *rhs + ); + + This comparison starts from the last element of both vectors and +moves backward. If the elements of two vectors match but one is +shorter, the shorter one comes first. Thus {1, 2} comes after {3, 2, +1}, but before {0, 1, 2}. + + This function is typically used together with +‘igraph_vector_list_sort()’ (*note igraph_vector_list_sort --- Sorts the +elements of the list into ascending order_::). + + *Arguments:. * + +‘lhs’: + Pointer to a pointer to the first vector. + +‘rhs’: + Pointer to a pointer to the second vector. + + *Returns:. * + +‘’ + -1 if ‘lhs’ in reverse order is lexicographically smaller than the + reverse of ‘rhs’, 0 if ‘lhs’ and ‘rhs’ are equal, else 1. + + *See also:. * + +‘’ + ‘igraph_vector_colex_cmp_untyped()’ (*note + igraph_vector_colex_cmp_untyped --- Colexicographical comparison of + two vectors_::) for an untyped variant of this function, or + ‘igraph_vector_lex_cmp()’ (*note igraph_vector_lex_cmp --- + Lexicographical comparison of two vectors [type-safe variant]_::) + to compare vectors starting from the first element. + + Time complexity: O(n), the number of elements in the smaller vector. + + * File examples/simple/igraph_vector_int_list_sort.c* + + +File: igraph-docs.info, Node: igraph_vector_colex_cmp_untyped --- Colexicographical comparison of two vectors_, Prev: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_, Up: Vector comparisons + +7.2.9.12 igraph_vector_colex_cmp_untyped -- Colexicographical comparison of two vectors. +........................................................................................ + + + int igraph_vector_colex_cmp_untyped(const void *lhs, const void *rhs); + + This comparison starts from the last element of both vectors and +moves backward. If the elements of two vectors match but one is +shorter, the shorter one comes first. Thus {1, 2} comes after {3, 2, +1}, but before {0, 1, 2}. + + This function is typically used together with +‘igraph_vector_ptr_sort()’ (*note igraph_vector_ptr_sort --- Sorts the +pointer vector based on an external comparison function_::). + + *Arguments:. * + +‘lhs’: + Pointer to a pointer to the first vector (interpreted as an + ‘igraph_vector_t **’). + +‘rhs’: + Pointer to a pointer to the second vector (interpreted as an + ‘igraph_vector_t **’). + + *Returns:. * + +‘’ + -1 if ‘lhs’ in reverse order is lexicographically smaller than the + reverse of ‘rhs’, 0 if ‘lhs’ and ‘rhs’ are equal, else 1. + + *See also:. * + +‘’ + ‘igraph_vector_colex_cmp()’ (*note igraph_vector_colex_cmp --- + Colexicographical comparison of two vectors_::) for a type-safe + variant of this function, ‘igraph_vector_lex_cmp_untyped()’ (*note + igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two + vectors [non-type-safe]_::) to compare vectors starting from the + first element. + + Time complexity: O(n), the number of elements in the smaller vector. + + +File: igraph-docs.info, Node: Finding minimum and maximum, Next: Vector properties, Prev: Vector comparisons, Up: Vectors + +7.2.10 Finding minimum and maximum +---------------------------------- + +* Menu: + +* igraph_vector_min -- Smallest element of a vector.: igraph_vector_min --- Smallest element of a vector_. +* igraph_vector_max -- Largest element of a vector.: igraph_vector_max --- Largest element of a vector_. +* igraph_vector_which_min -- Index of the smallest element.: igraph_vector_which_min --- Index of the smallest element_. +* igraph_vector_which_max -- Gives the index of the maximum element of the vector.: igraph_vector_which_max --- Gives the index of the maximum element of the vector_. +* igraph_vector_minmax -- Minimum and maximum elements of a vector.: igraph_vector_minmax --- Minimum and maximum elements of a vector_. +* igraph_vector_which_minmax -- Index of the minimum and maximum elements.: igraph_vector_which_minmax --- Index of the minimum and maximum elements_. + + +File: igraph-docs.info, Node: igraph_vector_min --- Smallest element of a vector_, Next: igraph_vector_max --- Largest element of a vector_, Up: Finding minimum and maximum + +7.2.10.1 igraph_vector_min -- Smallest element of a vector. +........................................................... + + + igraph_real_t igraph_vector_min(const igraph_vector_t *v); + + The vector must not be empty. + + *Arguments:. * + +‘v’: + The input vector. + + *Returns:. * + +‘’ + The smallest element of ‘v’, or NaN if any element is NaN. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_max --- Largest element of a vector_, Next: igraph_vector_which_min --- Index of the smallest element_, Prev: igraph_vector_min --- Smallest element of a vector_, Up: Finding minimum and maximum + +7.2.10.2 igraph_vector_max -- Largest element of a vector. +.......................................................... + + + igraph_real_t igraph_vector_max(const igraph_vector_t *v); + + The vector must not be empty. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The maximum element of ‘v’, or NaN if any element is NaN. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_which_min --- Index of the smallest element_, Next: igraph_vector_which_max --- Gives the index of the maximum element of the vector_, Prev: igraph_vector_max --- Largest element of a vector_, Up: Finding minimum and maximum + +7.2.10.3 igraph_vector_which_min -- Index of the smallest element. +.................................................................. + + + igraph_int_t igraph_vector_which_min(const igraph_vector_t* v); + + The vector must not be empty. If the smallest element is not unique, +then the index of the first is returned. If the vector contains NaN +values, the index of the first NaN value is returned. + + *Arguments:. * + +‘v’: + The input vector. + + *Returns:. * + +‘’ + Index of the smallest element. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_which_max --- Gives the index of the maximum element of the vector_, Next: igraph_vector_minmax --- Minimum and maximum elements of a vector_, Prev: igraph_vector_which_min --- Index of the smallest element_, Up: Finding minimum and maximum + +7.2.10.4 igraph_vector_which_max -- Gives the index of the maximum element of the vector. +......................................................................................... + + + igraph_int_t igraph_vector_which_max(const igraph_vector_t *v); + + The vector must not be empty. If the largest element is not unique, +then the index of the first is returned. If the vector contains NaN +values, the index of the first NaN value is returned. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The index of the first maximum element. + + Time complexity: O(n), n is the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_minmax --- Minimum and maximum elements of a vector_, Next: igraph_vector_which_minmax --- Index of the minimum and maximum elements_, Prev: igraph_vector_which_max --- Gives the index of the maximum element of the vector_, Up: Finding minimum and maximum + +7.2.10.5 igraph_vector_minmax -- Minimum and maximum elements of a vector. +.......................................................................... + + + void igraph_vector_minmax(const igraph_vector_t *v, + igraph_real_t *min, igraph_real_t *max); + + Handy if you want to have both the smallest and largest element of a +vector. The vector is only traversed once. The vector must be +non-empty. If a vector contains at least one NaN, both ‘min’ and ‘max’ +will be NaN. + + *Arguments:. * + +‘v’: + The input vector. It must contain at least one element. + +‘min’: + Pointer to a base type variable, the minimum is stored here. + +‘max’: + Pointer to a base type variable, the maximum is stored here. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_which_minmax --- Index of the minimum and maximum elements_, Prev: igraph_vector_minmax --- Minimum and maximum elements of a vector_, Up: Finding minimum and maximum + +7.2.10.6 igraph_vector_which_minmax -- Index of the minimum and maximum elements. +................................................................................. + + + void igraph_vector_which_minmax(const igraph_vector_t *v, + igraph_int_t *which_min, igraph_int_t *which_max); + + Handy if you need the indices of the smallest and largest elements. +The vector is traversed only once. The vector must be non-empty. If +the minimum or maximum is not unique, the index of the first minimum or +the first maximum is returned, respectively. If a vector contains at +least one NaN, both ‘which_min’ and ‘which_max’ will point to the first +NaN value. + + *Arguments:. * + +‘v’: + The input vector. It must contain at least one element. + +‘which_min’: + The index of the minimum element will be stored here. + +‘which_max’: + The index of the maximum element will be stored here. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: Vector properties, Next: Searching for elements, Prev: Finding minimum and maximum, Up: Vectors + +7.2.11 Vector properties +------------------------ + +* Menu: + +* igraph_vector_empty -- Decides whether the size of the vector is zero.: igraph_vector_empty --- Decides whether the size of the vector is zero_. +* igraph_vector_size -- The size of the vector.: igraph_vector_size --- The size of the vector_. +* igraph_vector_capacity -- Returns the allocated capacity of the vector.: igraph_vector_capacity --- Returns the allocated capacity of the vector_. +* igraph_vector_sum -- Calculates the sum of the elements in the vector.: igraph_vector_sum --- Calculates the sum of the elements in the vector_. +* igraph_vector_prod -- Calculates the product of the elements in the vector.: igraph_vector_prod --- Calculates the product of the elements in the vector_. +* igraph_vector_isininterval -- Checks if all elements of a vector are in the given interval.: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_. +* igraph_vector_maxdifference -- The maximum absolute difference of m1 and m2.: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_. +* igraph_vector_is_nan -- Check for each element if it is NaN.: igraph_vector_is_nan --- Check for each element if it is NaN_. +* igraph_vector_is_any_nan -- Check if any element is NaN.: igraph_vector_is_any_nan --- Check if any element is NaN_. +* igraph_vector_is_all_finite -- Check if all elements are finite.: igraph_vector_is_all_finite --- Check if all elements are finite_. + + +File: igraph-docs.info, Node: igraph_vector_empty --- Decides whether the size of the vector is zero_, Next: igraph_vector_size --- The size of the vector_, Up: Vector properties + +7.2.11.1 igraph_vector_empty -- Decides whether the size of the vector is zero. +............................................................................... + + + igraph_bool_t igraph_vector_empty(const igraph_vector_t *v); + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + True if the size of the vector is zero and false otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_size --- The size of the vector_, Next: igraph_vector_capacity --- Returns the allocated capacity of the vector_, Prev: igraph_vector_empty --- Decides whether the size of the vector is zero_, Up: Vector properties + +7.2.11.2 igraph_vector_size -- The size of the vector. +...................................................... + + + igraph_int_t igraph_vector_size(const igraph_vector_t *v); + + Returns the number of elements stored in the vector. + + *Arguments:. * + +‘v’: + The vector object + + *Returns:. * + +‘’ + The size of the vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_capacity --- Returns the allocated capacity of the vector_, Next: igraph_vector_sum --- Calculates the sum of the elements in the vector_, Prev: igraph_vector_size --- The size of the vector_, Up: Vector properties + +7.2.11.3 igraph_vector_capacity -- Returns the allocated capacity of the vector. +................................................................................ + + + igraph_int_t igraph_vector_capacity(const igraph_vector_t *v); + + Note that this might be different from the size of the vector (as +queried by ‘igraph_vector_size()’ (*note igraph_vector_size --- The size +of the vector_::)), and specifies how many elements the vector can hold, +without reallocation. + + *Arguments:. * + +‘v’: + Pointer to the (previously initialized) vector object to query. + + *Returns:. * + +‘’ + The allocated capacity. + + *See also:. * + +‘’ + ‘igraph_vector_size()’ (*note igraph_vector_size --- The size of + the vector_::). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_sum --- Calculates the sum of the elements in the vector_, Next: igraph_vector_prod --- Calculates the product of the elements in the vector_, Prev: igraph_vector_capacity --- Returns the allocated capacity of the vector_, Up: Vector properties + +7.2.11.4 igraph_vector_sum -- Calculates the sum of the elements in the vector. +............................................................................... + + + igraph_real_t igraph_vector_sum(const igraph_vector_t *v); + + For the empty vector 0 is returned. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The sum of the elements. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_prod --- Calculates the product of the elements in the vector_, Next: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_, Prev: igraph_vector_sum --- Calculates the sum of the elements in the vector_, Up: Vector properties + +7.2.11.5 igraph_vector_prod -- Calculates the product of the elements in the vector. +.................................................................................... + + + igraph_real_t igraph_vector_prod(const igraph_vector_t *v); + + For the empty vector one (1) is returned. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The product of the elements. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_, Next: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_, Prev: igraph_vector_prod --- Calculates the product of the elements in the vector_, Up: Vector properties + +7.2.11.6 igraph_vector_isininterval -- Checks if all elements of a vector are in the given interval. +.................................................................................................... + + + igraph_bool_t igraph_vector_isininterval(const igraph_vector_t *v, + igraph_real_t low, + igraph_real_t high); + + *Arguments:. * + +‘v’: + The vector object. + +‘low’: + The lower limit of the interval (inclusive). + +‘high’: + The higher limit of the interval (inclusive). + + *Returns:. * + +‘’ + True if the vector is empty or all vector elements are in the + interval, false otherwise. If any element is NaN, it will return + false. + + Time complexity: O(n), the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_, Next: igraph_vector_is_nan --- Check for each element if it is NaN_, Prev: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_, Up: Vector properties + +7.2.11.7 igraph_vector_maxdifference -- The maximum absolute difference of m1 and m2. +..................................................................................... + + + igraph_real_t igraph_vector_maxdifference(const igraph_vector_t *m1, + const igraph_vector_t *m2); + + The element with the largest absolute value in ‘m1’ - ‘m2’ is +returned. Both vectors must be non-empty, but they not need to have the +same length, the extra elements in the longer vector are ignored. If +any value is NaN in the shorter vector, the result will be NaN. + + *Arguments:. * + +‘m1’: + The first vector. + +‘m2’: + The second vector. + + *Returns:. * + +‘’ + The maximum absolute difference of ‘m1’ and ‘m2’. + + Time complexity: O(n), the number of elements in the shorter vector. + + +File: igraph-docs.info, Node: igraph_vector_is_nan --- Check for each element if it is NaN_, Next: igraph_vector_is_any_nan --- Check if any element is NaN_, Prev: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_, Up: Vector properties + +7.2.11.8 igraph_vector_is_nan -- Check for each element if it is NaN. +..................................................................... + + + igraph_error_t igraph_vector_is_nan(const igraph_vector_t *v, igraph_vector_bool_t *is_nan); + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object to check. + +‘is_nan’: + The resulting boolean vector indicating for each element whether it + is NaN or not. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory. Note + that this function _never_ returns an error if the vector ‘is_nan’ + will already be large enough. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_is_any_nan --- Check if any element is NaN_, Next: igraph_vector_is_all_finite --- Check if all elements are finite_, Prev: igraph_vector_is_nan --- Check for each element if it is NaN_, Up: Vector properties + +7.2.11.9 igraph_vector_is_any_nan -- Check if any element is NaN. +................................................................. + + + igraph_bool_t igraph_vector_is_any_nan(const igraph_vector_t *v); + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object to check. + + *Returns:. * + +‘’ + True if any element is NaN, false otherwise. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_is_all_finite --- Check if all elements are finite_, Prev: igraph_vector_is_any_nan --- Check if any element is NaN_, Up: Vector properties + +7.2.11.10 igraph_vector_is_all_finite -- Check if all elements are finite. +.......................................................................... + + + igraph_bool_t igraph_vector_is_all_finite(const igraph_vector_t *v); + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object to check. + + *Returns:. * + +‘’ + True if none of the elements are infinite or NaN. + + Time complexity: O(n), the number of elements. + + +File: igraph-docs.info, Node: Searching for elements, Next: Resizing operations, Prev: Vector properties, Up: Vectors + +7.2.12 Searching for elements +----------------------------- + +* Menu: + +* igraph_vector_contains -- Linear search in a vector.: igraph_vector_contains --- Linear search in a vector_. +* igraph_vector_search -- Searches in a vector from a given position.: igraph_vector_search --- Searches in a vector from a given position_. +* igraph_vector_binsearch -- Finds an element by binary searching a sorted vector.: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_. +* igraph_vector_binsearch_slice -- Finds an element by binary searching a sorted slice of a vector.: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_. +* igraph_vector_contains_sorted -- Binary search in a sorted vector.: igraph_vector_contains_sorted --- Binary search in a sorted vector_. + + +File: igraph-docs.info, Node: igraph_vector_contains --- Linear search in a vector_, Next: igraph_vector_search --- Searches in a vector from a given position_, Up: Searching for elements + +7.2.12.1 igraph_vector_contains -- Linear search in a vector. +............................................................. + + + igraph_bool_t igraph_vector_contains(const igraph_vector_t *v, + igraph_real_t what); + + Check whether the supplied element is included in the vector, by +linear search. + + *Arguments:. * + +‘v’: + The input vector. + +‘what’: + The element to look for. + + *Returns:. * + +‘’ + ‘true’ if the element is found and ‘false’ otherwise. + + Time complexity: O(n), the length of the vector. + + +File: igraph-docs.info, Node: igraph_vector_search --- Searches in a vector from a given position_, Next: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_, Prev: igraph_vector_contains --- Linear search in a vector_, Up: Searching for elements + +7.2.12.2 igraph_vector_search -- Searches in a vector from a given position. +............................................................................ + + + igraph_bool_t igraph_vector_search(const igraph_vector_t *v, + igraph_int_t from, igraph_real_t what, igraph_int_t *pos); + + The supplied element ‘what’ is searched in vector ‘v’, starting from +element index ‘from’. If found then the index of the first instance +(after ‘from’) is stored in ‘pos’. + + *Arguments:. * + +‘v’: + The input vector. + +‘from’: + The index to start searching from. No range checking is performed. + +‘what’: + The element to find. + +‘pos’: + If not ‘NULL’ then the index of the found element is stored here. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the element was found, ‘false’ otherwise. + + Time complexity: O(m), the number of elements to search, the length +of the vector minus the ‘from’ argument. + + +File: igraph-docs.info, Node: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_, Next: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_, Prev: igraph_vector_search --- Searches in a vector from a given position_, Up: Searching for elements + +7.2.12.3 igraph_vector_binsearch -- Finds an element by binary searching a sorted vector. +......................................................................................... + + + igraph_bool_t igraph_vector_binsearch(const igraph_vector_t *v, + igraph_real_t what, igraph_int_t *pos); + + It is assumed that the vector is sorted. If the specified element +(‘what’) is not in the vector, then the position of where it should be +inserted (to keep the vector sorted) is returned. If the vector +contains any NaN values, the returned value is undefined and ‘pos’ may +point to any position. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object. + +‘what’: + The element to search for. + +‘pos’: + Pointer to an ‘igraph_int_t’. This is set to the position of an + instance of ‘what’ in the vector if it is present. If ‘v’ does not + contain ‘what’ then ‘pos’ is set to the position to which it should + be inserted (to keep the vector sorted of course). + + *Returns:. * + +‘’ + True if ‘what’ is found in the vector, false otherwise. + + Time complexity: O(log(n)), n is the number of elements in ‘v’. + + +File: igraph-docs.info, Node: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_, Next: igraph_vector_contains_sorted --- Binary search in a sorted vector_, Prev: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_, Up: Searching for elements + +7.2.12.4 igraph_vector_binsearch_slice -- Finds an element by binary searching a sorted slice of a vector. +.......................................................................................................... + + + igraph_bool_t igraph_vector_binsearch_slice(const igraph_vector_t *v, + igraph_real_t what, igraph_int_t *pos, igraph_int_t start, igraph_int_t end); + + It is assumed that the indicated slice of the vector, from ‘start’ to +‘end’, is sorted. If the specified element (‘what’) is not in the slice +of the vector, then the position of where it should be inserted (to keep +the _slice_ sorted) is returned. Note that this means that the returned +index will point _inside_ the slice (including its endpoints), but will +not evaluate values _outside_ the slice. If the indicated slice +contains any NaN values, the returned value is undefined and ‘pos’ may +point to any position within the slice. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object. + +‘what’: + The element to search for. + +‘pos’: + Pointer to an ‘igraph_int_t’. This is set to the position of an + instance of ‘what’ in the slice of the vector if it is present. If + ‘v’ does not contain ‘what’ then ‘pos’ is set to the position to + which it should be inserted (to keep the vector sorted). + +‘start’: + The start position of the slice to search (inclusive). + +‘end’: + The end position of the slice to search (exclusive). + + *Returns:. * + +‘’ + True if ‘what’ is found in the vector, false otherwise. + + Time complexity: O(log(n)), n is the number of elements in the slice +of ‘v’, i.e. ‘end’ - ‘start’. + + +File: igraph-docs.info, Node: igraph_vector_contains_sorted --- Binary search in a sorted vector_, Prev: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_, Up: Searching for elements + +7.2.12.5 igraph_vector_contains_sorted -- Binary search in a sorted vector. +........................................................................... + + + igraph_bool_t igraph_vector_contains_sorted(const igraph_vector_t *v, igraph_real_t what); + + It is assumed that the vector is sorted. + + *Arguments:. * + +‘v’: + The ‘igraph_vector_t’ object. + +‘what’: + The element to search for. + + *Returns:. * + +‘’ + True if ‘what’ is found in the vector, false otherwise. + + Time complexity: O(log(n)), n is the number of elements in ‘v’. + + +File: igraph-docs.info, Node: Resizing operations, Next: Complex vector operations, Prev: Searching for elements, Up: Vectors + +7.2.13 Resizing operations +-------------------------- + +* Menu: + +* igraph_vector_clear -- Removes all elements from a vector.: igraph_vector_clear --- Removes all elements from a vector_. +* igraph_vector_reserve -- Reserves memory for a vector.: igraph_vector_reserve --- Reserves memory for a vector_. +* igraph_vector_resize -- Resize the vector.: igraph_vector_resize --- Resize the vector_. +* igraph_vector_resize_min -- Deallocate the unused memory of a vector.: igraph_vector_resize_min --- Deallocate the unused memory of a vector_. +* igraph_vector_push_back -- Appends one element to a vector.: igraph_vector_push_back --- Appends one element to a vector_. +* igraph_vector_pop_back -- Removes and returns the last element of a vector.: igraph_vector_pop_back --- Removes and returns the last element of a vector_. +* igraph_vector_insert -- Inserts a single element into a vector.: igraph_vector_insert --- Inserts a single element into a vector_. +* igraph_vector_remove -- Removes a single element from a vector.: igraph_vector_remove --- Removes a single element from a vector_. +* igraph_vector_remove_section -- Deletes a section from a vector.: igraph_vector_remove_section --- Deletes a section from a vector_. + + +File: igraph-docs.info, Node: igraph_vector_clear --- Removes all elements from a vector_, Next: igraph_vector_reserve --- Reserves memory for a vector_, Up: Resizing operations + +7.2.13.1 igraph_vector_clear -- Removes all elements from a vector. +................................................................... + + + void igraph_vector_clear(igraph_vector_t* v); + + This function simply sets the size of the vector to zero, it does not +free any allocated memory. For that you have to call +‘igraph_vector_destroy()’ (*note igraph_vector_destroy --- Destroys a +vector object_::). + + *Arguments:. * + +‘v’: + The vector object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_reserve --- Reserves memory for a vector_, Next: igraph_vector_resize --- Resize the vector_, Prev: igraph_vector_clear --- Removes all elements from a vector_, Up: Resizing operations + +7.2.13.2 igraph_vector_reserve -- Reserves memory for a vector. +............................................................... + + + igraph_error_t igraph_vector_reserve(igraph_vector_t *v, igraph_int_t capacity); + + ‘igraph’ vectors are flexible, they can grow and shrink. Growing +however occasionally needs the data in the vector to be copied. In +order to avoid this, you can call this function to reserve space for +future growth of the vector. + + Note that this function does _not_ change the size of the vector. +Let us see a small example to clarify things: if you reserve space for +100 elements and the size of your vector was (and still is) 60, then you +can surely add additional 40 elements to your vector before it will be +copied. + + *Arguments:. * + +‘v’: + The vector object. + +‘capacity’: + The new _allocated_ size of the vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, should be around O(n), n +is the new allocated size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_resize --- Resize the vector_, Next: igraph_vector_resize_min --- Deallocate the unused memory of a vector_, Prev: igraph_vector_reserve --- Reserves memory for a vector_, Up: Resizing operations + +7.2.13.3 igraph_vector_resize -- Resize the vector. +................................................... + + + igraph_error_t igraph_vector_resize(igraph_vector_t* v, igraph_int_t new_size); + + Note that this function does not free any memory, just sets the size +of the vector to the given one. It can on the other hand allocate more +memory if the new size is larger than the previous one. In this case +the newly appeared elements in the vector are _not_ set to zero, they +are uninitialized. + + *Arguments:. * + +‘v’: + The vector object + +‘new_size’: + The new size of the vector. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory. Note + that this function _never_ returns an error if the vector is made + smaller. + + *See also:. * + +‘’ + ‘igraph_vector_reserve()’ (*note igraph_vector_reserve --- Reserves + memory for a vector_::) for allocating memory for future extensions + of a vector. ‘igraph_vector_resize_min()’ (*note + igraph_vector_resize_min --- Deallocate the unused memory of a + vector_::) for deallocating the unnneded memory for a vector. + + Time complexity: O(1) if the new size is smaller, operating system +dependent if it is larger. In the latter case it is usually around +O(n), n is the new size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_resize_min --- Deallocate the unused memory of a vector_, Next: igraph_vector_push_back --- Appends one element to a vector_, Prev: igraph_vector_resize --- Resize the vector_, Up: Resizing operations + +7.2.13.4 igraph_vector_resize_min -- Deallocate the unused memory of a vector. +.............................................................................. + + + void igraph_vector_resize_min(igraph_vector_t *v); + + This function attempts to deallocate the unused reserved storage of a +vector. If it succeeds, ‘igraph_vector_size()’ (*note +igraph_vector_size --- The size of the vector_::) and +‘igraph_vector_capacity()’ (*note igraph_vector_capacity --- Returns the +allocated capacity of the vector_::) will be the same. The data in the +vector is always preserved, even if deallocation is not successful. + + *Arguments:. * + +‘v’: + Pointer to an initialized vector. + + *See also:. * + +‘’ + ‘igraph_vector_resize()’ (*note igraph_vector_resize --- Resize the + vector_::), ‘igraph_vector_reserve()’ (*note igraph_vector_reserve + --- Reserves memory for a vector_::). + + Time complexity: operating system dependent, O(n) at worst. + + +File: igraph-docs.info, Node: igraph_vector_push_back --- Appends one element to a vector_, Next: igraph_vector_pop_back --- Removes and returns the last element of a vector_, Prev: igraph_vector_resize_min --- Deallocate the unused memory of a vector_, Up: Resizing operations + +7.2.13.5 igraph_vector_push_back -- Appends one element to a vector. +.................................................................... + + + igraph_error_t igraph_vector_push_back(igraph_vector_t *v, igraph_real_t e); + + This function resizes the vector to be one element longer and sets +the very last element in the vector to ‘e’. + + *Arguments:. * + +‘v’: + The vector object. + +‘e’: + The element to append to the vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: operating system dependent. What is important is +that a sequence of n subsequent calls to this function has time +complexity O(n), even if there hadn't been any space reserved for the +new elements by ‘igraph_vector_reserve()’ (*note igraph_vector_reserve +--- Reserves memory for a vector_::). This is implemented by a trick +similar to the C++ ‘vector’ class: each time more memory is allocated +for a vector, the size of the additionally allocated memory is the same +as the vector's current length. (We assume here that the time +complexity of memory allocation is at most linear.) + + +File: igraph-docs.info, Node: igraph_vector_pop_back --- Removes and returns the last element of a vector_, Next: igraph_vector_insert --- Inserts a single element into a vector_, Prev: igraph_vector_push_back --- Appends one element to a vector_, Up: Resizing operations + +7.2.13.6 igraph_vector_pop_back -- Removes and returns the last element of a vector. +.................................................................................... + + + igraph_real_t igraph_vector_pop_back(igraph_vector_t *v); + + It is an error to call this function with an empty vector. + + *Arguments:. * + +‘v’: + The vector object. + + *Returns:. * + +‘’ + The removed last element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_insert --- Inserts a single element into a vector_, Next: igraph_vector_remove --- Removes a single element from a vector_, Prev: igraph_vector_pop_back --- Removes and returns the last element of a vector_, Up: Resizing operations + +7.2.13.7 igraph_vector_insert -- Inserts a single element into a vector. +........................................................................ + + + igraph_error_t igraph_vector_insert( + igraph_vector_t *v, igraph_int_t pos, igraph_real_t value); + + Note that this function does not do range checking. Insertion will +shift the elements from the position given to the end of the vector one +position to the right, and the new element will be inserted in the empty +space created at the given position. The size of the vector will +increase by one. + + *Arguments:. * + +‘v’: + The vector object. + +‘pos’: + The position where the new element is to be inserted. + +‘value’: + The new element to be inserted. + + +File: igraph-docs.info, Node: igraph_vector_remove --- Removes a single element from a vector_, Next: igraph_vector_remove_section --- Deletes a section from a vector_, Prev: igraph_vector_insert --- Inserts a single element into a vector_, Up: Resizing operations + +7.2.13.8 igraph_vector_remove -- Removes a single element from a vector. +........................................................................ + + + void igraph_vector_remove(igraph_vector_t *v, igraph_int_t elem); + + Note that this function does not do range checking. + + *Arguments:. * + +‘v’: + The vector object. + +‘elem’: + The position of the element to remove. + + Time complexity: O(n-elem), n is the number of elements in the +vector. + + +File: igraph-docs.info, Node: igraph_vector_remove_section --- Deletes a section from a vector_, Prev: igraph_vector_remove --- Removes a single element from a vector_, Up: Resizing operations + +7.2.13.9 igraph_vector_remove_section -- Deletes a section from a vector. +......................................................................... + + + void igraph_vector_remove_section( + igraph_vector_t *v, igraph_int_t from, igraph_int_t to); + + *Arguments:. * + +‘v’: + The vector object. + +‘from’: + The position of the first element to remove. + +‘to’: + The position of the first element _not_ to remove. + + Time complexity: O(n-from), n is the number of elements in the +vector. + + +File: igraph-docs.info, Node: Complex vector operations, Next: Sorting, Prev: Resizing operations, Up: Vectors + +7.2.14 Complex vector operations +-------------------------------- + +* Menu: + +* igraph_vector_complex_real -- Gives the real part of a complex vector.: igraph_vector_complex_real --- Gives the real part of a complex vector_. +* igraph_vector_complex_imag -- Gives the imaginary part of a complex vector.: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_. +* igraph_vector_complex_realimag -- Gives the real and imaginary parts of a complex vector.: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_. +* igraph_vector_complex_create -- Creates a complex vector from a real and imaginary part.: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_. +* igraph_vector_complex_create_polar -- Creates a complex matrix from a magnitude and an angle.: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_. +* igraph_vector_complex_all_almost_e --- Are all elements almost equal?:: +* igraph_vector_complex_zapsmall -- Replaces small elements of a complex vector by exact zeros.: igraph_vector_complex_zapsmall --- Replaces small elements of a complex vector by exact zeros_. + + +File: igraph-docs.info, Node: igraph_vector_complex_real --- Gives the real part of a complex vector_, Next: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_, Up: Complex vector operations + +7.2.14.1 igraph_vector_complex_real -- Gives the real part of a complex vector. +............................................................................... + + + igraph_error_t igraph_vector_complex_real(const igraph_vector_complex_t *v, + igraph_vector_t *real); + + *Arguments:. * + +‘v’: + Pointer to a complex vector. + +‘real’: + Pointer to an initialized vector. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_, Next: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_, Prev: igraph_vector_complex_real --- Gives the real part of a complex vector_, Up: Complex vector operations + +7.2.14.2 igraph_vector_complex_imag -- Gives the imaginary part of a complex vector. +.................................................................................... + + + igraph_error_t igraph_vector_complex_imag(const igraph_vector_complex_t *v, + igraph_vector_t *imag); + + *Arguments:. * + +‘v’: + Pointer to a complex vector. + +‘imag’: + Pointer to an initialized vector. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_, Next: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_, Prev: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_, Up: Complex vector operations + +7.2.14.3 igraph_vector_complex_realimag -- Gives the real and imaginary parts of a complex vector. +.................................................................................................. + + + igraph_error_t igraph_vector_complex_realimag(const igraph_vector_complex_t *v, + igraph_vector_t *real, + igraph_vector_t *imag); + + *Arguments:. * + +‘v’: + Pointer to a complex vector. + +‘real’: + Pointer to an initialized vector. The real part will be stored + here. + +‘imag’: + Pointer to an initialized vector. The imaginary part will be + stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_, Next: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Prev: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_, Up: Complex vector operations + +7.2.14.4 igraph_vector_complex_create -- Creates a complex vector from a real and imaginary part. +................................................................................................. + + + igraph_error_t igraph_vector_complex_create(igraph_vector_complex_t *v, + const igraph_vector_t *real, + const igraph_vector_t *imag); + + *Arguments:. * + +‘v’: + Pointer to an uninitialized complex vector. + +‘real’: + Pointer to the real part of the complex vector. + +‘imag’: + Pointer to the imaginary part of the complex vector. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Next: igraph_vector_complex_all_almost_e --- Are all elements almost equal?, Prev: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_, Up: Complex vector operations + +7.2.14.5 igraph_vector_complex_create_polar -- Creates a complex matrix from a magnitude and an angle. +...................................................................................................... + + + igraph_error_t igraph_vector_complex_create_polar(igraph_vector_complex_t *v, + const igraph_vector_t *r, + const igraph_vector_t *theta); + + *Arguments:. * + +‘v’: + Pointer to an uninitialized complex vector. + +‘r’: + Pointer to a real vector containing magnitudes. + +‘theta’: + Pointer to a real vector containing arguments (phase angles). + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the vector. + + +File: igraph-docs.info, Node: igraph_vector_complex_all_almost_e --- Are all elements almost equal?, Next: igraph_vector_complex_zapsmall --- Replaces small elements of a complex vector by exact zeros_, Prev: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Up: Complex vector operations + +7.2.14.6 igraph_vector_complex_all_almost_e -- Are all elements almost equal? +............................................................................. + + + igraph_bool_t igraph_vector_complex_all_almost_e(const igraph_vector_complex_t *lhs, + const igraph_vector_complex_t *rhs, + igraph_real_t eps); + + Checks if the elements of two complex vectors are equal within a +relative tolerance. + + *Arguments:. * + +‘lhs’: + The first vector. + +‘rhs’: + The second vector. + +‘eps’: + Relative tolerance, see ‘igraph_complex_almost_equals()’ (*note + igraph_complex_almost_equals --- Compare two complex numbers with a + tolerance_::) for details. + + *Returns:. * + +‘’ + True if the two vectors are almost equal, false if there is at + least one differing element or if the vectors are not of the same + size. + + +File: igraph-docs.info, Node: igraph_vector_complex_zapsmall --- Replaces small elements of a complex vector by exact zeros_, Prev: igraph_vector_complex_all_almost_e --- Are all elements almost equal?, Up: Complex vector operations + +7.2.14.7 igraph_vector_complex_zapsmall -- Replaces small elements of a complex vector by exact zeros. +...................................................................................................... + + + igraph_error_t igraph_vector_complex_zapsmall(igraph_vector_complex_t *v, igraph_real_t tol); + + Similarly to ‘igraph_vector_zapsmall()’ (*note igraph_vector_zapsmall +--- Replaces small elements of a vector by exact zeros_::), small +elements will be replaced by zeros. The operation is performed +separately on the real and imaginary parts of the numbers. This way, +complex numbers with a large real part and tiny imaginary part will +effectively be transformed to real numbers. The default tolerance +corresponds to two-thirds of the representable digits of +‘igraph_real_t’, i.e. ‘DBL_EPSILON^(2/3)’ which is approximately +‘10^-10’. + + *Arguments:. * + +‘v’: + The vector to process, it will be changed in-place. + +‘tol’: + Tolerance value. Real and imaginary parts smaller than this in + magnitude will be replaced by zeros. Pass in zero to use the + default tolerance. Must not be negative. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vector_complex_all_almost_e()’ (*note + igraph_vector_complex_all_almost_e --- Are all elements almost + equal?::) and ‘igraph_complex_almost_equals()’ (*note + igraph_complex_almost_equals --- Compare two complex numbers with a + tolerance_::) to perform comparisons with relative tolerances. + + +File: igraph-docs.info, Node: Sorting, Next: Set operations on sorted vectors, Prev: Complex vector operations, Up: Vectors + +7.2.15 Sorting +-------------- + +* Menu: + +* igraph_vector_sort -- Sorts the elements of the vector into ascending order.: igraph_vector_sort --- Sorts the elements of the vector into ascending order_. +* igraph_vector_reverse_sort -- Sorts the elements of the vector into descending order.: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_. +* igraph_vector_sort_ind -- Returns a permutation of indices that sorts a vector.: igraph_vector_sort_ind --- Returns a permutation of indices that sorts a vector_. + + +File: igraph-docs.info, Node: igraph_vector_sort --- Sorts the elements of the vector into ascending order_, Next: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_, Up: Sorting + +7.2.15.1 igraph_vector_sort -- Sorts the elements of the vector into ascending order. +..................................................................................... + + + void igraph_vector_sort(igraph_vector_t *v); + + If the vector contains any NaN values, the resulting ordering of NaN +values is undefined and may appear anywhere in the vector. + + *Arguments:. * + +‘v’: + Pointer to an initialized vector object. + + Time complexity: O(n log n) for n elements. + + +File: igraph-docs.info, Node: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_, Next: igraph_vector_sort_ind --- Returns a permutation of indices that sorts a vector_, Prev: igraph_vector_sort --- Sorts the elements of the vector into ascending order_, Up: Sorting + +7.2.15.2 igraph_vector_reverse_sort -- Sorts the elements of the vector into descending order. +.............................................................................................. + + + void igraph_vector_reverse_sort(igraph_vector_t *v); + + If the vector contains any NaN values, the resulting ordering of NaN +values is undefined and may appear anywhere in the vector. + + *Arguments:. * + +‘v’: + Pointer to an initialized vector object. + + Time complexity: O(n log n) for n elements. + + +File: igraph-docs.info, Node: igraph_vector_sort_ind --- Returns a permutation of indices that sorts a vector_, Prev: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_, Up: Sorting + +7.2.15.3 igraph_vector_sort_ind -- Returns a permutation of indices that sorts a vector. +........................................................................................ + + + igraph_error_t igraph_vector_sort_ind( + const igraph_vector_t *v, + igraph_vector_int_t *inds, + igraph_order_t order); + + Takes an unsorted array ‘v’ as input and computes an array of indices +‘inds’ such that ‘v[ inds[i] ]’, with ‘i’ increasing from 0, is an +ordered array (either ascending or descending, depending on ‘order’). +The order of indices for identical elements is not defined. If the +vector contains any NaN values, the ordering of NaN values is undefined. + + *Arguments:. * + +‘v’: + the array to be sorted + +‘inds’: + the output array of indices. This must be initialized, but will be + resized + +‘order’: + whether the output array should be sorted in ascending or + descending order. Use ‘IGRAPH_ASCENDING’ for ascending and + ‘IGRAPH_DESCENDING’ for descending order. + + *Returns:. * + +‘’ + Error code. + + This routine uses igraph's built-in qsort routine. Algorithm: 1) +create an array of pointers to the elements of v. 2) Pass this array to +qsort. 3) after sorting the difference between the pointer value and +the first pointer value gives its original position in the array. Use +this to set the values of inds. + + +File: igraph-docs.info, Node: Set operations on sorted vectors, Next: Pointer vectors [igraph_vector_ptr_t], Prev: Sorting, Up: Vectors + +7.2.16 Set operations on sorted vectors +--------------------------------------- + +* Menu: + +* igraph_vector_intersect_sorted -- Set intersection of two sorted vectors.: igraph_vector_intersect_sorted --- Set intersection of two sorted vectors_. +* igraph_vector_intersection_size_sorted -- Intersection size of two sorted vectors.: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_. +* igraph_vector_difference_sorted -- Set difference of two sorted vectors.: igraph_vector_difference_sorted --- Set difference of two sorted vectors_. +* igraph_vector_difference_and_intersection_sorted -- Simultaneous difference and intersection of two sorted vectors.: igraph_vector_difference_and_intersection_sorted --- Simultaneous difference and intersection of two sorted vectors_. + + +File: igraph-docs.info, Node: igraph_vector_intersect_sorted --- Set intersection of two sorted vectors_, Next: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_, Up: Set operations on sorted vectors + +7.2.16.1 igraph_vector_intersect_sorted -- Set intersection of two sorted vectors. +.................................................................................. + + + igraph_error_t igraph_vector_intersect_sorted(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_vector_t *result); + + The elements that are contained in both vectors are stored in the +result vector. All three vectors must be initialized. + + For similar-size vectors, this function uses a straightforward linear +scan. When the vector sizes differ substantially, it uses the set +intersection method of Ricardo Baeza-Yates, which takes logarithmic time +in the length of the larger vector. + + The algorithm keeps the multiplicities of the elements: if an element +appears ‘k1’ times in the first vector and ‘k2’ times in the second, the +result will include that element ‘min(k1, k2)’ times. + + Reference: + + Baeza-Yates R: A fast set intersection algorithm for sorted +sequences. In: Lecture Notes in Computer Science, vol. 3109/2004, pp. +400-408, 2004. Springer Berlin/Heidelberg. +https://doi.org/10.1007/978-3-540-27801-6_30 +(https://doi.org/10.1007/978-3-540-27801-6_30) + + *Arguments:. * + +‘v1’: + The first vector + +‘v2’: + The second vector + +‘result’: + The result vector, which will also be sorted. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(m log(n)) where m is the size of the smaller +vector and n is the size of the larger one. + + +File: igraph-docs.info, Node: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_, Next: igraph_vector_difference_sorted --- Set difference of two sorted vectors_, Prev: igraph_vector_intersect_sorted --- Set intersection of two sorted vectors_, Up: Set operations on sorted vectors + +7.2.16.2 igraph_vector_intersection_size_sorted -- Intersection size of two sorted vectors. +........................................................................................... + + + igraph_int_t igraph_vector_intersection_size_sorted( + const igraph_vector_t *v1, + const igraph_vector_t *v2); + + Counts elements that are present in both vectors. This is +particularly useful for counting common neighbours of two vertices. + + For similar-size vectors, this function uses a straightforward linear +scan. When the vector sizes differ substantially, it uses the set +intersection method of Ricardo Baeza-Yates, which takes logarithmic time +in the length of the larger vector. + + The algorithm keeps the multiplicities of the elements: if an element +appears ‘k1’ times in the first vector and ‘k2’ times in the second, the +result will include that element ‘min(k1, k2)’ times. + + Reference: + + Baeza-Yates R: A fast set intersection algorithm for sorted +sequences. In: Lecture Notes in Computer Science, vol. 3109/2004, pp. +400-408, 2004. Springer Berlin/Heidelberg. +https://doi.org/10.1007/978-3-540-27801-6_30 +(https://doi.org/10.1007/978-3-540-27801-6_30) + + *Arguments:. * + +‘v1’: + The first vector + +‘v2’: + The second vector + + *Returns:. * + +‘’ + The number of common elements. + + Time complexity: O(m log(n)) where m is the size of the smaller +vector and n is the size of the larger one. + + +File: igraph-docs.info, Node: igraph_vector_difference_sorted --- Set difference of two sorted vectors_, Next: igraph_vector_difference_and_intersection_sorted --- Simultaneous difference and intersection of two sorted vectors_, Prev: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_, Up: Set operations on sorted vectors + +7.2.16.3 igraph_vector_difference_sorted -- Set difference of two sorted vectors. +................................................................................. + + + igraph_error_t igraph_vector_difference_sorted(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_vector_t *result); + + The elements that are contained in only the first vector but not the +second are stored in the result vector. All three vectors must be +initialized. + + The algorithm keeps the multiplicities of the elements: if an element +appears ‘k1’ times in the first vector and ‘k2’ times in the second, the +result will include that element ‘max(0, k1-k2)’ times. + + *Arguments:. * + +‘v1’: + the first vector + +‘v2’: + the second vector + +‘result’: + the result vector + + +File: igraph-docs.info, Node: igraph_vector_difference_and_intersection_sorted --- Simultaneous difference and intersection of two sorted vectors_, Prev: igraph_vector_difference_sorted --- Set difference of two sorted vectors_, Up: Set operations on sorted vectors + +7.2.16.4 igraph_vector_difference_and_intersection_sorted -- Simultaneous difference and intersection of two sorted vectors. +............................................................................................................................ + + + igraph_error_t igraph_vector_difference_and_intersection_sorted( + const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_vector_t *vdiff12, igraph_vector_t*vdiff21, + igraph_vector_t*vint); + + This function iterates over all the elements of the two input vectors +and sorts them into three other vectors: elements that are in the first +vector but not in the second, elements that are in the second vector but +not in the first, and the intersection of the two vectors. The input +vectors must be initialized. The output arguments can be ‘NULL’, but +they must be initialized if they are not ‘NULL’ and will be resized +accordingly. + + The multiplicities of the individual elements are treated +consistently with ‘igraph_vector_difference_sorted()’ (*note +igraph_vector_difference_sorted --- Set difference of two sorted +vectors_::) and ‘igraph_vector_intersect_sorted()’ (*note +igraph_vector_intersect_sorted --- Set intersection of two sorted +vectors_::): The algorithm keeps the multiplicities of the elements: if +an element appears ‘k1’ times in the first vector and ‘k2’ times in the +second, the intersection vector will include that element ‘min(k1, k2)’ +times, while the difference vectors will include that element ‘max(0, +k1-k2)’ and ‘max(0, k2-k1)’ times, respectively. + + *Arguments:. * + +‘v1’: + the first vector + +‘v2’: + the second vector + +‘vdiff12’: + output vector containing the elements that are in the first vector + but not the second one, or ‘NULL’ if not needed + +‘vdiff21’: + output vector containing the elements that are in the second vector + but not the first one, or ‘NULL’ if not needed + +‘vint’: + output vector containing the intersection, or ‘NULL’ if not needed + + +File: igraph-docs.info, Node: Pointer vectors [igraph_vector_ptr_t], Prev: Set operations on sorted vectors, Up: Vectors + +7.2.17 Pointer vectors (igraph_vector_ptr_t) +-------------------------------------------- + +The ‘igraph_vector_ptr_t’ data type is very similar to the +‘igraph_vector_t’ (*note About igraph_vector_t objects::) type, but it +stores generic pointers instead of real numbers. + + This type has the same space complexity as ‘igraph_vector_t’ (*note +About igraph_vector_t objects::), and most implemented operations work +the same way as for ‘igraph_vector_t’ (*note About igraph_vector_t +objects::). + + The same ‘VECTOR’ (*note VECTOR --- Accessing an element of a +vector_::) macro used for ordinary vectors can be used for pointer +vectors as well, please note that a typeless generic pointer will be +provided by this macro and you may need to cast it to a specific pointer +before starting to work with it. + + Pointer vectors may have an associated item destructor function which +takes a pointer and returns nothing. The item destructor will be called +on each item in the pointer vector when it is destroyed by +‘igraph_vector_ptr_destroy()’ (*note igraph_vector_ptr_destroy --- +Destroys a pointer vector_::) or ‘igraph_vector_ptr_destroy_all()’ +(*note igraph_vector_ptr_destroy_all --- Frees all the elements and +destroys the pointer vector_::), or when its elements are freed by +‘igraph_vector_ptr_free_all()’ (*note igraph_vector_ptr_free_all --- +Frees all the elements of a pointer vector_::). Note that the semantics +of an item destructor does not coincide with C++ destructors; for +instance, when a pointer vector is resized to a smaller size, the extra +items will _not_ be destroyed automatically! Nevertheless, item +destructors may become handy in many cases; for instance, a vector of +graphs generated by some function can be destroyed with a single call to +‘igraph_vector_ptr_destroy_all()’ (*note igraph_vector_ptr_destroy_all +--- Frees all the elements and destroys the pointer vector_::) if the +item destructor is set to ‘igraph_destroy()’ (*note igraph_destroy --- +Frees the memory allocated for a graph object_::). + +* Menu: + +* igraph_vector_ptr_init -- Initialize a pointer vector (constructor).: igraph_vector_ptr_init --- Initialize a pointer vector [constructor]_. +* igraph_vector_ptr_init_copy -- Initializes a pointer vector from another one (constructor).: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_. +* igraph_vector_ptr_destroy -- Destroys a pointer vector.: igraph_vector_ptr_destroy --- Destroys a pointer vector_. +* igraph_vector_ptr_free_all -- Frees all the elements of a pointer vector.: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_. +* igraph_vector_ptr_destroy_all -- Frees all the elements and destroys the pointer vector.: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_. +* igraph_vector_ptr_size -- Gives the number of elements in the pointer vector.: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_. +* igraph_vector_ptr_capacity -- Returns the allocated capacity of the pointer vector.: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_. +* igraph_vector_ptr_clear -- Removes all elements from a pointer vector.: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_. +* igraph_vector_ptr_reserve -- Reserves memory for a pointer vector for later use.: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_. +* igraph_vector_ptr_resize -- Resizes a pointer vector.: igraph_vector_ptr_resize --- Resizes a pointer vector_. +* igraph_vector_ptr_resize_min -- Deallocate the unused memory of a pointer vector.: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_. +* igraph_vector_ptr_push_back -- Appends an element to the back of a pointer vector.: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_. +* igraph_vector_ptr_pop_back -- Removes and returns the last element of a pointer vector.: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_. +* igraph_vector_ptr_insert -- Inserts a single element into a pointer vector.: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_. +* igraph_vector_ptr_get -- Access an element of a pointer vector.: igraph_vector_ptr_get --- Access an element of a pointer vector_. +* igraph_vector_ptr_set -- Assign to an element of a pointer vector.: igraph_vector_ptr_set --- Assign to an element of a pointer vector_. +* igraph_vector_ptr_sort -- Sorts the pointer vector based on an external comparison function.: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_. +* igraph_vector_ptr_sort_ind -- Returns a permutation of indices that sorts a vector of pointers.: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_. +* igraph_vector_ptr_permute -- Permutes the elements of a pointer vector in place according to an index vector.: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_. +* igraph_vector_ptr_get_item_destructor -- Gets the current item destructor for this pointer vector.: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_. +* igraph_vector_ptr_set_item_destructor -- Sets the item destructor for this pointer vector.: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_. +* IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR -- Sets the item destructor for this pointer vector (macro version).: IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR --- Sets the item destructor for this pointer vector [macro version]_. + + +File: igraph-docs.info, Node: igraph_vector_ptr_init --- Initialize a pointer vector [constructor]_, Next: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.1 igraph_vector_ptr_init -- Initialize a pointer vector (constructor). +............................................................................. + + + igraph_error_t igraph_vector_ptr_init(igraph_vector_ptr_t* v, igraph_int_t size); + + This is the constructor of the pointer vector data type. All pointer +vectors constructed this way should be destroyed via calling +‘igraph_vector_ptr_destroy()’ (*note igraph_vector_ptr_destroy --- +Destroys a pointer vector_::). + + *Arguments:. * + +‘v’: + Pointer to an uninitialized ‘igraph_vector_ptr_t’ object, to be + created. + +‘size’: + Integer, the size of the pointer vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if out of memory + + Time complexity: operating system dependent, the amount of 'time' +required to allocate ‘size’ elements. + + +File: igraph-docs.info, Node: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_, Next: igraph_vector_ptr_destroy --- Destroys a pointer vector_, Prev: igraph_vector_ptr_init --- Initialize a pointer vector [constructor]_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.2 igraph_vector_ptr_init_copy -- Initializes a pointer vector from another one (constructor). +.................................................................................................... + + + igraph_error_t igraph_vector_ptr_init_copy(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from); + + This function creates a pointer vector by copying another one. This +is shallow copy, only the pointers in the vector will be copied. + + It is potentially dangerous to copy a pointer vector with an +associated item destructor. The copied vector will inherit the item +destructor, which may cause problems when both vectors are destroyed as +the items might get destroyed twice. Make sure you know what you are +doing when copying a pointer vector with an item destructor, or unset +the item destructor on one of the vectors later. + + *Arguments:. * + +‘to’: + Pointer to an uninitialized pointer vector object. + +‘from’: + A pointer vector object. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if out of memory + + Time complexity: O(n) if allocating memory for n elements can be done +in O(n) time. + + +File: igraph-docs.info, Node: igraph_vector_ptr_destroy --- Destroys a pointer vector_, Next: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_, Prev: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.3 igraph_vector_ptr_destroy -- Destroys a pointer vector. +................................................................ + + + void igraph_vector_ptr_destroy(igraph_vector_ptr_t* v); + + The destructor for pointer vectors. + + *Arguments:. * + +‘v’: + Pointer to the pointer vector to destroy. + + Time complexity: operating system dependent, the 'time' required to +deallocate O(n) bytes, n is the number of elements allocated for the +pointer vector (not necessarily the number of elements in the vector). + + +File: igraph-docs.info, Node: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_, Next: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_, Prev: igraph_vector_ptr_destroy --- Destroys a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.4 igraph_vector_ptr_free_all -- Frees all the elements of a pointer vector. +.................................................................................. + + + void igraph_vector_ptr_free_all(igraph_vector_ptr_t* v); + + If an item destructor is set for this pointer vector, this function +will first call the destructor on all elements of the vector and then +free all the elements using ‘igraph_free()’ (*note igraph_free --- +Deallocates memory that was allocated by igraph functions_::). If an +item destructor is not set, the elements will simply be freed. + + *Arguments:. * + +‘v’: + Pointer to the pointer vector whose elements will be freed. + + Time complexity: operating system dependent, the 'time' required to +call the destructor n times and then deallocate O(n) pointers, each +pointing to a memory area of arbitrary size. n is the number of +elements in the pointer vector. + + +File: igraph-docs.info, Node: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_, Next: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_, Prev: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.5 igraph_vector_ptr_destroy_all -- Frees all the elements and destroys the pointer vector. +................................................................................................. + + + void igraph_vector_ptr_destroy_all(igraph_vector_ptr_t* v); + + This function is equivalent to ‘igraph_vector_ptr_free_all()’ (*note +igraph_vector_ptr_free_all --- Frees all the elements of a pointer +vector_::) followed by ‘igraph_vector_ptr_destroy()’ (*note +igraph_vector_ptr_destroy --- Destroys a pointer vector_::). + + *Arguments:. * + +‘v’: + Pointer to the pointer vector to destroy. + + Time complexity: operating system dependent, the 'time' required to +deallocate O(n) pointers, each pointing to a memory area of arbitrary +size, plus the 'time' required to deallocate O(n) bytes, n being the +number of elements allocated for the pointer vector (not necessarily the +number of elements in the vector). + + +File: igraph-docs.info, Node: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_, Next: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_, Prev: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.6 igraph_vector_ptr_size -- Gives the number of elements in the pointer vector. +...................................................................................... + + + igraph_int_t igraph_vector_ptr_size(const igraph_vector_ptr_t* v); + + *Arguments:. * + +‘v’: + The pointer vector object. + + *Returns:. * + +‘’ + The size of the object, i.e. the number of pointers stored. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_, Next: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_, Prev: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.7 igraph_vector_ptr_capacity -- Returns the allocated capacity of the pointer vector. +............................................................................................ + + + igraph_int_t igraph_vector_ptr_capacity(const igraph_vector_ptr_t* v); + + *Arguments:. * + +‘v’: + The pointer vector object. + + *Returns:. * + +‘’ + The allocated capacity. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_, Next: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_, Prev: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.8 igraph_vector_ptr_clear -- Removes all elements from a pointer vector. +............................................................................... + + + void igraph_vector_ptr_clear(igraph_vector_ptr_t* v); + + This function resizes a pointer to vector to zero length. Note that +the pointed objects are _not_ deallocated, you should call +‘igraph_free()’ (*note igraph_free --- Deallocates memory that was +allocated by igraph functions_::) on them, or make sure that their +allocated memory is freed in some other way, you'll get memory leaks +otherwise. If you have set up an item destructor earlier, the +destructor will be called on every element. + + Note that the current implementation of this function does _not_ +deallocate the memory required for storing the pointers, so making a +pointer vector smaller this way does not give back any memory. This +behavior might change in the future. + + *Arguments:. * + +‘v’: + The pointer vector to clear. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_, Next: igraph_vector_ptr_resize --- Resizes a pointer vector_, Prev: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.9 igraph_vector_ptr_reserve -- Reserves memory for a pointer vector for later use. +......................................................................................... + + + igraph_error_t igraph_vector_ptr_reserve(igraph_vector_ptr_t* v, igraph_int_t capacity); + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_vector_ptr_resize --- Resizes a pointer vector_, Next: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_, Prev: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.10 igraph_vector_ptr_resize -- Resizes a pointer vector. +............................................................... + + + igraph_error_t igraph_vector_ptr_resize(igraph_vector_ptr_t* v, igraph_int_t newsize); + + Note that if a vector is made smaller the pointed object are not +deallocated by this function and the item destructor is not called on +the extra elements. + + *Arguments:. * + +‘v’: + A pointer vector. + +‘newsize’: + The new size of the pointer vector. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) if the vector if made smaller. Operating +system dependent otherwise, the amount of 'time' needed to allocate the +memory for the vector elements. + + +File: igraph-docs.info, Node: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_, Next: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_, Prev: igraph_vector_ptr_resize --- Resizes a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.11 igraph_vector_ptr_resize_min -- Deallocate the unused memory of a pointer vector. +........................................................................................... + + + void igraph_vector_ptr_resize_min(igraph_vector_ptr_t* v); + + This function attempts to deallocate the unused reserved storage of a +pointer vector. If it succeeds, ‘igraph_vector_ptr_size()’ (*note +igraph_vector_ptr_size --- Gives the number of elements in the pointer +vector_::) and ‘igraph_vector_ptr_capacity()’ (*note +igraph_vector_ptr_capacity --- Returns the allocated capacity of the +pointer vector_::) will be the same. The data in the pointer vector is +always preserved, even if deallocation is not successful. + + *Arguments:. * + +‘v’: + Pointer to an initialized pointer vector. + + *See also:. * + +‘’ + ‘igraph_vector_ptr_resize()’ (*note igraph_vector_ptr_resize --- + Resizes a pointer vector_::), ‘igraph_vector_ptr_reserve()’ (*note + igraph_vector_ptr_reserve --- Reserves memory for a pointer vector + for later use_::). + + Time complexity: operating system dependent, O(n) at worst. + + +File: igraph-docs.info, Node: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_, Next: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_, Prev: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.12 igraph_vector_ptr_push_back -- Appends an element to the back of a pointer vector. +............................................................................................ + + + igraph_error_t igraph_vector_ptr_push_back(igraph_vector_ptr_t* v, void* e); + + *Arguments:. * + +‘v’: + The pointer vector. + +‘e’: + The new element to include in the pointer vector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vector_push_back()’ (*note igraph_vector_push_back --- + Appends one element to a vector_::) for the corresponding operation + of the ordinary vector type. + + Time complexity: O(1) or O(n), n is the number of elements in the +vector. The pointer vector implementation ensures that n subsequent +push_back operations need O(n) time to complete. + + +File: igraph-docs.info, Node: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_, Next: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_, Prev: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.13 igraph_vector_ptr_pop_back -- Removes and returns the last element of a pointer vector. +................................................................................................. + + + void *igraph_vector_ptr_pop_back(igraph_vector_ptr_t *v); + + It is an error to call this function with an empty vector. + + *Arguments:. * + +‘v’: + The pointer vector. + + *Returns:. * + +‘’ + The removed last element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_, Next: igraph_vector_ptr_get --- Access an element of a pointer vector_, Prev: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.14 igraph_vector_ptr_insert -- Inserts a single element into a pointer vector. +..................................................................................... + + + igraph_error_t igraph_vector_ptr_insert(igraph_vector_ptr_t* v, igraph_int_t pos, void* e); + + Note that this function does not do range checking. Insertion will +shift the elements from the position given to the end of the vector one +position to the right, and the new element will be inserted in the empty +space created at the given position. The size of the vector will +increase by one. + + *Arguments:. * + +‘v’: + The pointer vector object. + +‘pos’: + The position where the new element is inserted. + +‘e’: + The inserted element + + +File: igraph-docs.info, Node: igraph_vector_ptr_get --- Access an element of a pointer vector_, Next: igraph_vector_ptr_set --- Assign to an element of a pointer vector_, Prev: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.15 igraph_vector_ptr_get -- Access an element of a pointer vector. +......................................................................... + + + void *igraph_vector_ptr_get(const igraph_vector_ptr_t* v, igraph_int_t pos); + + *Arguments:. * + +‘v’: + Pointer to a pointer vector. + +‘pos’: + The index of the pointer to return. + + *Returns:. * + +‘’ + The pointer at ‘pos’ position. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_set --- Assign to an element of a pointer vector_, Next: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_, Prev: igraph_vector_ptr_get --- Access an element of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.16 igraph_vector_ptr_set -- Assign to an element of a pointer vector. +............................................................................ + + + void igraph_vector_ptr_set(igraph_vector_ptr_t* v, igraph_int_t pos, void* value); + + *Arguments:. * + +‘v’: + Pointer to a pointer vector. + +‘pos’: + The index of the pointer to update. + +‘value’: + The new pointer to set in the vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_, Next: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_, Prev: igraph_vector_ptr_set --- Assign to an element of a pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.17 igraph_vector_ptr_sort -- Sorts the pointer vector based on an external comparison function. +...................................................................................................... + + + void igraph_vector_ptr_sort(igraph_vector_ptr_t *v, int (*compar)(const void*, const void*)); + + Sometimes it is necessary to sort the pointers in the vector based on +the property of the element being referenced by the pointer. This +function allows us to sort the vector based on an arbitrary external +comparison function which accepts two ‘void *’ pointers ‘p1’ and ‘p2’ +and returns an integer less than, equal to or greater than zero if the +first argument is considered to be respectively less than, equal to, or +greater than the second. ‘p1’ and ‘p2’ will point to the pointer in the +vector, so they have to be double-dereferenced if one wants to get +access to the underlying object the address of which is stored in ‘v’. + + *Arguments:. * + +‘v’: + The pointer vector to be sorted. + +‘compar’: + A qsort-compatible comparison function. It must take pointers to + the elements of the pointer vector. For example, if the pointer + vector contains ‘igraph_vector_t *’ pointers, then the comparison + function must interpret its arguments as ‘igraph_vector_t **’. + + +File: igraph-docs.info, Node: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_, Next: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_, Prev: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.18 igraph_vector_ptr_sort_ind -- Returns a permutation of indices that sorts a vector of pointers. +......................................................................................................... + + + igraph_error_t igraph_vector_ptr_sort_ind(igraph_vector_ptr_t *v, + igraph_vector_int_t *inds, cmp_t *cmp); + + Takes an unsorted array ‘v’ as input and computes an array of indices +inds such that v[ inds[i] ], with i increasing from 0, is an ordered +array (either ascending or descending, depending on \v order). The +order of indices for identical elements is not defined. + + *Arguments:. * + +‘v’: + the array to be sorted + +‘inds’: + the output array of indices. This must be initialized, but will be + resized + +‘cmp’: + a comparator function that takes two elements of the pointer vector + being sorted (these are constant pointers on their own) and returns + a negative value if the item _"pointed_ to" by the first pointer is + smaller than the item _"pointed_ to" by the second pointer, a + positive value if it is larger, or zero if the two items are equal + + *Returns:. * + +‘’ + Error code. + + This routine uses the C library qsort routine. Algorithm: 1) create +an array of pointers to the elements of v. 2) Pass this array to qsort. +3) after sorting the difference between the pointer value and the first +pointer value gives its original position in the array. Use this to set +the values of inds. + + +File: igraph-docs.info, Node: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_, Next: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_, Prev: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.19 igraph_vector_ptr_permute -- Permutes the elements of a pointer vector in place according to an index vector. +....................................................................................................................... + + + igraph_error_t igraph_vector_ptr_permute(igraph_vector_ptr_t* v, const igraph_vector_int_t* index); + + This function takes a vector ‘v’ and a corresponding index vector +‘ind’, and permutes the elements of ‘v’ such that ‘v’[ind[i]] is moved +to become ‘v’[i] after the function is executed. + + It is an error to call this function with an index vector that does +not represent a valid permutation. Each element in the index vector +must be between 0 and the length of the vector minus one (inclusive), +and each such element must appear only once. The function does not +attempt to validate the index vector. + + The index vector that this function takes is compatible with the +index vector returned from ‘igraph_vector_ptr_sort_ind()’ (*note +igraph_vector_ptr_sort_ind --- Returns a permutation of indices that +sorts a vector of pointers_::); passing in the index vector from +‘igraph_vector_ptr_sort_ind()’ (*note igraph_vector_ptr_sort_ind --- +Returns a permutation of indices that sorts a vector of pointers_::) +will sort the original vector. + + As a special case, this function allows the index vector to be +_shorter_ than the vector being permuted, in which case the elements +whose indices do not occur in the index vector will be removed from the +vector. + + *Arguments:. * + +‘v’: + the vector to permute + +‘ind’: + the index vector + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: O(n), the size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_, Next: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_, Prev: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.20 igraph_vector_ptr_get_item_destructor -- Gets the current item destructor for this pointer vector. +............................................................................................................ + + + igraph_finally_func_t* igraph_vector_ptr_get_item_destructor(const igraph_vector_ptr_t *v); + + The item destructor is a function which will be called on every +non-null pointer stored in this vector when +‘igraph_vector_ptr_destroy()’ (*note igraph_vector_ptr_destroy --- +Destroys a pointer vector_::), igraph_vector_ptr_destroy_all() or +‘igraph_vector_ptr_free_all()’ (*note igraph_vector_ptr_free_all --- +Frees all the elements of a pointer vector_::) is called. + + *Returns:. * + +‘’ + The current item destructor. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_, Next: IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR --- Sets the item destructor for this pointer vector [macro version]_, Prev: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.21 igraph_vector_ptr_set_item_destructor -- Sets the item destructor for this pointer vector. +.................................................................................................... + + + igraph_finally_func_t* igraph_vector_ptr_set_item_destructor( + igraph_vector_ptr_t *v, igraph_finally_func_t *func); + + The item destructor is a function which will be called on every +non-null pointer stored in this vector when +‘igraph_vector_ptr_destroy()’ (*note igraph_vector_ptr_destroy --- +Destroys a pointer vector_::), igraph_vector_ptr_destroy_all() or +‘igraph_vector_ptr_free_all()’ (*note igraph_vector_ptr_free_all --- +Frees all the elements of a pointer vector_::) is called. + + *Returns:. * + +‘’ + The old item destructor. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR --- Sets the item destructor for this pointer vector [macro version]_, Prev: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_, Up: Pointer vectors [igraph_vector_ptr_t] + +7.2.17.22 IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR -- Sets the item destructor for this pointer vector (macro version). +.................................................................................................................... + + + #define IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(v, func) + + This macro is expanded to ‘igraph_vector_ptr_set_item_destructor()’ +(*note igraph_vector_ptr_set_item_destructor --- Sets the item +destructor for this pointer vector_::), the only difference is that the +second argument is automatically cast to an ‘igraph_finally_func_t’*. +The cast is necessary in most cases as the destructor functions we use +(such as ‘igraph_vector_destroy()’ (*note igraph_vector_destroy --- +Destroys a vector object_::)) take a pointer to some concrete igraph +data type, while ‘igraph_finally_func_t’ expects ‘void’* + + +File: igraph-docs.info, Node: Matrices, Next: Sparse matrices, Prev: Vectors, Up: Data structure library; vector; matrix; other data types + +7.3 Matrices +============ + +* Menu: + +* About igraph_matrix_t objects:: +* Matrix constructors and destructors:: +* Initializing elements: Initializing elements <1>. +* Accessing elements of a matrix:: +* Matrix views:: +* Copying matrices:: +* Operations on rows and columns:: +* Matrix operations:: +* Matrix comparisons:: +* Combining matrices:: +* Finding minimum and maximum: Finding minimum and maximum <1>. +* Matrix properties:: +* Searching for elements: Searching for elements <1>. +* Resizing operations: Resizing operations <1>. +* Complex matrix operations:: + + +File: igraph-docs.info, Node: About igraph_matrix_t objects, Next: Matrix constructors and destructors, Up: Matrices + +7.3.1 About igraph_matrix_t objects +----------------------------------- + +This type is just an interface to ‘igraph_vector_t’. + + The ‘igraph_matrix_t’ type usually stores n elements in O(n) space, +but not always. See the documentation of the vector type. + + +File: igraph-docs.info, Node: Matrix constructors and destructors, Next: Initializing elements <1>, Prev: About igraph_matrix_t objects, Up: Matrices + +7.3.2 Matrix constructors and destructors +----------------------------------------- + +* Menu: + +* igraph_matrix_init -- Initializes a matrix.: igraph_matrix_init --- Initializes a matrix_. +* igraph_matrix_init_array -- Initializes a matrix from an ordinary C array (constructor).: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_. +* igraph_matrix_init_copy -- Copies a matrix.: igraph_matrix_init_copy --- Copies a matrix_. +* igraph_matrix_destroy -- Destroys a matrix object.: igraph_matrix_destroy --- Destroys a matrix object_. + + +File: igraph-docs.info, Node: igraph_matrix_init --- Initializes a matrix_, Next: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_, Up: Matrix constructors and destructors + +7.3.2.1 igraph_matrix_init -- Initializes a matrix. +................................................... + + + igraph_error_t igraph_matrix_init( + igraph_matrix_t *m, igraph_int_t nrow, igraph_int_t ncol); + + Every matrix needs to be initialized before using it. This is done +by calling this function. A matrix has to be destroyed if it is not +needed any more; see ‘igraph_matrix_destroy()’ (*note +igraph_matrix_destroy --- Destroys a matrix object_::). + + *Arguments:. * + +‘m’: + Pointer to a not yet initialized matrix object to be initialized. + +‘nrow’: + The number of rows in the matrix. + +‘ncol’: + The number of columns in the matrix. + + *Returns:. * + +‘’ + Error code. + + Time complexity: usually O(n), n is the number of elements in the +matrix. + + +File: igraph-docs.info, Node: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_, Next: igraph_matrix_init_copy --- Copies a matrix_, Prev: igraph_matrix_init --- Initializes a matrix_, Up: Matrix constructors and destructors + +7.3.2.2 igraph_matrix_init_array -- Initializes a matrix from an ordinary C array (constructor). +................................................................................................ + + + igraph_error_t igraph_matrix_init_array( + igraph_matrix_t *m, const igraph_real_t *data, + igraph_int_t nrow, igraph_int_t ncol, + igraph_matrix_storage_t storage); + + The array is assumed to store the matrix data contiguously, either in +a column-major or row-major format. In other words, ‘data’ may store +concatenated matrix columns or concatenated matrix rows. Constructing a +matrix from column-major data is faster, as this is igraph's native +storage format. + + *Arguments:. * + +‘v’: + Pointer to an uninitialized matrix object. + +‘data’: + A regular C array, storing the elements of the matrix in + column-major order, i.e. the elements of the first column are + stored first, followed by the second column and so on. + +‘nrow’: + The number of rows in the matrix. + +‘ncol’: + The number of columns in the matrix. + +‘storage’: + ‘IGRAPH_ROW_MAJOR’ if the array is in row-major format, + ‘IGRAPH_COLUMN_MAJOR’ if the array is in column-major format. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system specific, usually O(‘nrow’ ‘ncol’). + + +File: igraph-docs.info, Node: igraph_matrix_init_copy --- Copies a matrix_, Next: igraph_matrix_destroy --- Destroys a matrix object_, Prev: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_, Up: Matrix constructors and destructors + +7.3.2.3 igraph_matrix_init_copy -- Copies a matrix. +................................................... + + + igraph_error_t igraph_matrix_init_copy(igraph_matrix_t *to, const igraph_matrix_t *from); + + Creates a matrix object by copying from an existing matrix. + + *Arguments:. * + +‘to’: + Pointer to an uninitialized matrix object. + +‘from’: + The initialized matrix object to copy. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there isn't enough memory to + allocate the new matrix. + + Time complexity: O(n), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_destroy --- Destroys a matrix object_, Prev: igraph_matrix_init_copy --- Copies a matrix_, Up: Matrix constructors and destructors + +7.3.2.4 igraph_matrix_destroy -- Destroys a matrix object. +.......................................................... + + + void igraph_matrix_destroy(igraph_matrix_t *m); + + This function frees all the memory allocated for a matrix object. +The destroyed object needs to be reinitialized before using it again. + + *Arguments:. * + +‘m’: + The matrix to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: Initializing elements <1>, Next: Accessing elements of a matrix, Prev: Matrix constructors and destructors, Up: Matrices + +7.3.3 Initializing elements +--------------------------- + +* Menu: + +* igraph_matrix_null -- Sets all elements in a matrix to zero.: igraph_matrix_null --- Sets all elements in a matrix to zero_. +* igraph_matrix_fill -- Fill with an element.: igraph_matrix_fill --- Fill with an element_. + + +File: igraph-docs.info, Node: igraph_matrix_null --- Sets all elements in a matrix to zero_, Next: igraph_matrix_fill --- Fill with an element_, Up: Initializing elements <1> + +7.3.3.1 igraph_matrix_null -- Sets all elements in a matrix to zero. +.................................................................... + + + void igraph_matrix_null(igraph_matrix_t *m); + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix object. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_fill --- Fill with an element_, Prev: igraph_matrix_null --- Sets all elements in a matrix to zero_, Up: Initializing elements <1> + +7.3.3.2 igraph_matrix_fill -- Fill with an element. +................................................... + + + void igraph_matrix_fill(igraph_matrix_t *m, igraph_real_t e); + + Set the matrix to a constant matrix. + + *Arguments:. * + +‘m’: + The input matrix. + +‘e’: + The element to set. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: Accessing elements of a matrix, Next: Matrix views, Prev: Initializing elements <1>, Up: Matrices + +7.3.4 Accessing elements of a matrix +------------------------------------ + +* Menu: + +* MATRIX -- Accessing an element of a matrix.: MATRIX --- Accessing an element of a matrix_. +* igraph_matrix_get -- Extract an element from a matrix.: igraph_matrix_get --- Extract an element from a matrix_. +* igraph_matrix_get_ptr -- Pointer to an element of a matrix.: igraph_matrix_get_ptr --- Pointer to an element of a matrix_. +* igraph_matrix_set -- Set an element.: igraph_matrix_set --- Set an element_. + + +File: igraph-docs.info, Node: MATRIX --- Accessing an element of a matrix_, Next: igraph_matrix_get --- Extract an element from a matrix_, Up: Accessing elements of a matrix + +7.3.4.1 MATRIX -- Accessing an element of a matrix. +................................................... + + + #define MATRIX(m,i,j) + + Note that there are no range checks right now. This functionality +might be redefined as a proper function later. + + *Arguments:. * + +‘m’: + The matrix object. + +‘i’: + The index of the row, starting with zero. + +‘j’: + The index of the column, starting with zero. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_get --- Extract an element from a matrix_, Next: igraph_matrix_get_ptr --- Pointer to an element of a matrix_, Prev: MATRIX --- Accessing an element of a matrix_, Up: Accessing elements of a matrix + +7.3.4.2 igraph_matrix_get -- Extract an element from a matrix. +.............................................................. + + + igraph_real_t igraph_matrix_get(const igraph_matrix_t *m, + igraph_int_t row, igraph_int_t col); + + Use this if you need a function for some reason and cannot use the +‘MATRIX’ (*note MATRIX --- Accessing an element of a matrix_::) macro. +Note that no range checking is performed. + + *Arguments:. * + +‘m’: + The input matrix. + +‘row’: + The row index. + +‘col’: + The column index. + + *Returns:. * + +‘’ + The element in the given row and column. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_get_ptr --- Pointer to an element of a matrix_, Next: igraph_matrix_set --- Set an element_, Prev: igraph_matrix_get --- Extract an element from a matrix_, Up: Accessing elements of a matrix + +7.3.4.3 igraph_matrix_get_ptr -- Pointer to an element of a matrix. +................................................................... + + + igraph_real_t* igraph_matrix_get_ptr(const igraph_matrix_t *m, + igraph_int_t row, igraph_int_t col); + + The function returns a pointer to an element. No range checking is +performed. + + *Arguments:. * + +‘m’: + The input matrix. + +‘row’: + The row index. + +‘col’: + The column index. + + *Returns:. * + +‘’ + Pointer to the element in the given row and column. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_set --- Set an element_, Prev: igraph_matrix_get_ptr --- Pointer to an element of a matrix_, Up: Accessing elements of a matrix + +7.3.4.4 igraph_matrix_set -- Set an element. +............................................ + + + void igraph_matrix_set( + igraph_matrix_t* m, igraph_int_t row, igraph_int_t col, + igraph_real_t value); + + Set an element of a matrix. No range checking is performed. + + *Arguments:. * + +‘m’: + The input matrix. + +‘row’: + The row index. + +‘col’: + The column index. + +‘value’: + The new value of the element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Matrix views, Next: Copying matrices, Prev: Accessing elements of a matrix, Up: Matrices + +7.3.5 Matrix views +------------------ + +* Menu: + +* igraph_matrix_view -- Creates a matrix view into an existing array.: igraph_matrix_view --- Creates a matrix view into an existing array_. +* igraph_matrix_view_from_vector -- Creates a matrix view that treats an existing vector as a matrix.: igraph_matrix_view_from_vector --- Creates a matrix view that treats an existing vector as a matrix_. + + +File: igraph-docs.info, Node: igraph_matrix_view --- Creates a matrix view into an existing array_, Next: igraph_matrix_view_from_vector --- Creates a matrix view that treats an existing vector as a matrix_, Up: Matrix views + +7.3.5.1 igraph_matrix_view -- Creates a matrix view into an existing array. +........................................................................... + + + igraph_matrix_t igraph_matrix_view( + const igraph_real_t *data, + igraph_int_t nrow, igraph_int_t ncol); + + This function lets you treat an existing C array as a matrix. The +elements of the matrix are assumed to be stored in column-major order in +the array, i.e. the elements of the first column are stored first, +followed by the second column and so on. + + Since this function creates a view into an existing array, you must +_not_ destroy the ‘igraph_matrix_t’ object when you are done with it. +Similarly, you must _not_ call any function on it that may attempt to +modify the size of the matrix. Modifying an element in the matrix will +modify the underlying array as the two share the same memory block. + + *Arguments:. * + +‘data’: + The raw array that the matrix provides a view into. + +‘nrow’: + The number of rows in the matrix. + +‘ncol’: + The number of columns in the matrix. + + *Returns:. * + +‘’ + The matrix object providing the view into the array. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_view_from_vector --- Creates a matrix view that treats an existing vector as a matrix_, Prev: igraph_matrix_view --- Creates a matrix view into an existing array_, Up: Matrix views + +7.3.5.2 igraph_matrix_view_from_vector -- Creates a matrix view that treats an existing vector as a matrix. +........................................................................................................... + + + igraph_matrix_t igraph_matrix_view_from_vector( + const igraph_vector_t *v, + igraph_int_t nrow + ); + + This function lets you treat an existing igraph vector as a matrix. +The elements of the matrix are assumed to be stored in column-major +order in the vector, i.e. the elements of the first column are stored +first, followed by the second column and so on. + + Since this function creates a view into an existing vector, you must +_not_ destroy the ‘igraph_matrix_t’ object when you are done with it. +Similarly, you must _not_ call any function on it that may attempt to +modify the size of the vector. Modifying an element in the matrix will +modify the underlying vector as the two share the same memory block. + + Additionally, you must _not_ attempt to grow the underlying vector by +any vector operation as that may result in a re-allocation of the +backing memory block of the vector, and the matrix view will not be +informed about the re-allocation so it will point to an invalid memory +area afterwards. + + *Arguments:. * + +‘v’: + The vector that the matrix will provide a view into. + +‘nrow’: + The number of rows in the matrix. The number of columns will be + derived implicitly from the size of the vector. If the number of + items in the vector is not divisible by the number of rows, the + last few elements of the vector will not be covered by the view. + + *Returns:. * + +‘’ + The matrix object providing the view into the vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Copying matrices, Next: Operations on rows and columns, Prev: Matrix views, Up: Matrices + +7.3.6 Copying matrices +---------------------- + +* Menu: + +* igraph_matrix_copy_to -- Copies a matrix to a regular C array.: igraph_matrix_copy_to --- Copies a matrix to a regular C array_. +* igraph_matrix_update -- Update from another matrix.: igraph_matrix_update --- Update from another matrix_. +* igraph_matrix_swap -- Swap two matrices.: igraph_matrix_swap --- Swap two matrices_. + + +File: igraph-docs.info, Node: igraph_matrix_copy_to --- Copies a matrix to a regular C array_, Next: igraph_matrix_update --- Update from another matrix_, Up: Copying matrices + +7.3.6.1 igraph_matrix_copy_to -- Copies a matrix to a regular C array. +...................................................................... + + + void igraph_matrix_copy_to(const igraph_matrix_t *m, igraph_real_t *to, igraph_matrix_storage_t storage); + + The C array should be of sufficient size; there are (of course) no +range checks. + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix object. + +‘to’: + Pointer to a C array; the place to copy the data to. + +‘storage’: + ‘IGRAPH_ROW_MAJOR’ to write the data in row-major format, + ‘IGRAPH_COLUMN_MAJOR’ to write it in column-major format. + Currently igraph uses column-major storage internally, thus + ‘IGRAPH_COLUMN_MAJOR’ is much faster. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_update --- Update from another matrix_, Next: igraph_matrix_swap --- Swap two matrices_, Prev: igraph_matrix_copy_to --- Copies a matrix to a regular C array_, Up: Copying matrices + +7.3.6.2 igraph_matrix_update -- Update from another matrix. +........................................................... + + + igraph_error_t igraph_matrix_update(igraph_matrix_t *to, + const igraph_matrix_t *from); + + This function replicates ‘from’ in the matrix ‘to’. Note that ‘to’ +must be already initialized. + + *Arguments:. * + +‘to’: + The result matrix. + +‘from’: + The matrix to replicate; it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_swap --- Swap two matrices_, Prev: igraph_matrix_update --- Update from another matrix_, Up: Copying matrices + +7.3.6.3 igraph_matrix_swap -- Swap two matrices. +................................................ + + + void igraph_matrix_swap(igraph_matrix_t *m1, igraph_matrix_t *m2); + + The contents of the two matrices will be swapped. + + *Arguments:. * + +‘m1’: + The first matrix. + +‘m2’: + The second matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Operations on rows and columns, Next: Matrix operations, Prev: Copying matrices, Up: Matrices + +7.3.7 Operations on rows and columns +------------------------------------ + +* Menu: + +* igraph_matrix_get_row -- Extract a row.: igraph_matrix_get_row --- Extract a row_. +* igraph_matrix_get_col -- Select a column.: igraph_matrix_get_col --- Select a column_. +* igraph_matrix_set_row -- Set a row from a vector.: igraph_matrix_set_row --- Set a row from a vector_. +* igraph_matrix_set_col -- Set a column from a vector.: igraph_matrix_set_col --- Set a column from a vector_. +* igraph_matrix_swap_rows -- Swap two rows.: igraph_matrix_swap_rows --- Swap two rows_. +* igraph_matrix_swap_cols -- Swap two columns.: igraph_matrix_swap_cols --- Swap two columns_. +* igraph_matrix_select_rows -- Select some rows of a matrix.: igraph_matrix_select_rows --- Select some rows of a matrix_. +* igraph_matrix_select_cols -- Select some columns of a matrix.: igraph_matrix_select_cols --- Select some columns of a matrix_. +* igraph_matrix_select_rows_cols -- Select some rows and columns of a matrix.: igraph_matrix_select_rows_cols --- Select some rows and columns of a matrix_. + + +File: igraph-docs.info, Node: igraph_matrix_get_row --- Extract a row_, Next: igraph_matrix_get_col --- Select a column_, Up: Operations on rows and columns + +7.3.7.1 igraph_matrix_get_row -- Extract a row. +............................................... + + + igraph_error_t igraph_matrix_get_row(const igraph_matrix_t *m, + igraph_vector_t *res, igraph_int_t index); + + Extract a row from a matrix and return it as a vector. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + Pointer to an initialized vector; it will be resized if needed. + +‘index’: + The index of the row to select. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of columns in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_get_col --- Select a column_, Next: igraph_matrix_set_row --- Set a row from a vector_, Prev: igraph_matrix_get_row --- Extract a row_, Up: Operations on rows and columns + +7.3.7.2 igraph_matrix_get_col -- Select a column. +................................................. + + + igraph_error_t igraph_matrix_get_col(const igraph_matrix_t *m, + igraph_vector_t *res, + igraph_int_t index); + + Extract a column of a matrix and return it as a vector. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + The result will we stored in this vector. It should be initialized + and will be resized as needed. + +‘index’: + The index of the column to select. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of rows in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_set_row --- Set a row from a vector_, Next: igraph_matrix_set_col --- Set a column from a vector_, Prev: igraph_matrix_get_col --- Select a column_, Up: Operations on rows and columns + +7.3.7.3 igraph_matrix_set_row -- Set a row from a vector. +......................................................... + + + igraph_error_t igraph_matrix_set_row(igraph_matrix_t *m, + const igraph_vector_t *v, igraph_int_t index); + + Sets the elements of a row with the given vector. This has the +effect of setting row ‘index’ to have the elements in the vector ‘v’. +The length of the vector and the number of columns in the matrix must +match, otherwise an error is triggered. + + *Arguments:. * + +‘m’: + The input matrix. + +‘v’: + The vector containing the new elements of the row. + +‘index’: + Index of the row to set. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of columns in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_set_col --- Set a column from a vector_, Next: igraph_matrix_swap_rows --- Swap two rows_, Prev: igraph_matrix_set_row --- Set a row from a vector_, Up: Operations on rows and columns + +7.3.7.4 igraph_matrix_set_col -- Set a column from a vector. +............................................................ + + + igraph_error_t igraph_matrix_set_col(igraph_matrix_t *m, + const igraph_vector_t *v, igraph_int_t index); + + Sets the elements of a column with the given vector. In effect, +column ‘index’ will be set with elements from the vector ‘v’. The +length of the vector and the number of rows in the matrix must match, +otherwise an error is triggered. + + *Arguments:. * + +‘m’: + The input matrix. + +‘v’: + The vector containing the new elements of the column. + +‘index’: + Index of the column to set. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(m), the number of rows in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_swap_rows --- Swap two rows_, Next: igraph_matrix_swap_cols --- Swap two columns_, Prev: igraph_matrix_set_col --- Set a column from a vector_, Up: Operations on rows and columns + +7.3.7.5 igraph_matrix_swap_rows -- Swap two rows. +................................................. + + + igraph_error_t igraph_matrix_swap_rows(igraph_matrix_t *m, + igraph_int_t i, igraph_int_t j); + + Swap two rows in the matrix. + + *Arguments:. * + +‘m’: + The input matrix. + +‘i’: + The index of the first row. + +‘j’: + The index of the second row. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of columns. + + +File: igraph-docs.info, Node: igraph_matrix_swap_cols --- Swap two columns_, Next: igraph_matrix_select_rows --- Select some rows of a matrix_, Prev: igraph_matrix_swap_rows --- Swap two rows_, Up: Operations on rows and columns + +7.3.7.6 igraph_matrix_swap_cols -- Swap two columns. +.................................................... + + + igraph_error_t igraph_matrix_swap_cols(igraph_matrix_t *m, + igraph_int_t i, igraph_int_t j); + + Swap two columns in the matrix. + + *Arguments:. * + +‘m’: + The input matrix. + +‘i’: + The index of the first column. + +‘j’: + The index of the second column. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(m), the number of rows. + + +File: igraph-docs.info, Node: igraph_matrix_select_rows --- Select some rows of a matrix_, Next: igraph_matrix_select_cols --- Select some columns of a matrix_, Prev: igraph_matrix_swap_cols --- Swap two columns_, Up: Operations on rows and columns + +7.3.7.7 igraph_matrix_select_rows -- Select some rows of a matrix. +.................................................................. + + + igraph_error_t igraph_matrix_select_rows(const igraph_matrix_t *m, + igraph_matrix_t *res, + const igraph_vector_int_t *rows); + + This function selects some rows of a matrix and returns them in a new +matrix. The result matrix should be initialized before calling the +function. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + The result matrix. It should be initialized and will be resized as + needed. + +‘rows’: + Vector; it contains the row indices (starting with zero) to + extract. Note that no range checking is performed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nm), n is the number of rows, m the number of +columns of the result matrix. + + +File: igraph-docs.info, Node: igraph_matrix_select_cols --- Select some columns of a matrix_, Next: igraph_matrix_select_rows_cols --- Select some rows and columns of a matrix_, Prev: igraph_matrix_select_rows --- Select some rows of a matrix_, Up: Operations on rows and columns + +7.3.7.8 igraph_matrix_select_cols -- Select some columns of a matrix. +..................................................................... + + + igraph_error_t igraph_matrix_select_cols(const igraph_matrix_t *m, + igraph_matrix_t *res, + const igraph_vector_int_t *cols); + + This function selects some columns of a matrix and returns them in a +new matrix. The result matrix should be initialized before calling the +function. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + The result matrix. It should be initialized and will be resized as + needed. + +‘cols’: + Vector; it contains the column indices (starting with zero) to + extract. Note that no range checking is performed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nm), n is the number of rows, m the number of +columns of the result matrix. + + +File: igraph-docs.info, Node: igraph_matrix_select_rows_cols --- Select some rows and columns of a matrix_, Prev: igraph_matrix_select_cols --- Select some columns of a matrix_, Up: Operations on rows and columns + +7.3.7.9 igraph_matrix_select_rows_cols -- Select some rows and columns of a matrix. +................................................................................... + + + igraph_error_t igraph_matrix_select_rows_cols(const igraph_matrix_t *m, + igraph_matrix_t *res, + const igraph_vector_int_t *rows, + const igraph_vector_int_t *cols); + + This function selects some rows and columns of a matrix and returns +them in a new matrix. The result matrix should be initialized before +calling the function. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + The result matrix. It should be initialized and will be resized as + needed. + +‘rows’: + Vector; it contains the row indices (starting with zero) to + extract. Note that no range checking is performed. + +‘cols’: + Vector; it contains the column indices (starting with zero) to + extract. Note that no range checking is performed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nm), n is the number of rows, m the number of +columns of the result matrix. + + +File: igraph-docs.info, Node: Matrix operations, Next: Matrix comparisons, Prev: Operations on rows and columns, Up: Matrices + +7.3.8 Matrix operations +----------------------- + +* Menu: + +* igraph_matrix_add_constant -- Add a constant to every element.: igraph_matrix_add_constant --- Add a constant to every element_. +* igraph_matrix_scale -- Multiplies each element of the matrix by a constant.: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_. +* igraph_matrix_add -- Add two matrices.: igraph_matrix_add --- Add two matrices_. +* igraph_matrix_sub -- Difference of two matrices.: igraph_matrix_sub --- Difference of two matrices_. +* igraph_matrix_mul_elements -- Elementwise matrix multiplication.: igraph_matrix_mul_elements --- Elementwise matrix multiplication_. +* igraph_matrix_div_elements -- Elementwise division.: igraph_matrix_div_elements --- Elementwise division_. +* igraph_matrix_sum -- Sum of elements.: igraph_matrix_sum --- Sum of elements_. +* igraph_matrix_prod -- Product of all matrix elements.: igraph_matrix_prod --- Product of all matrix elements_. +* igraph_matrix_rowsum -- Rowwise sum.: igraph_matrix_rowsum --- Rowwise sum_. +* igraph_matrix_colsum -- Columnwise sum.: igraph_matrix_colsum --- Columnwise sum_. +* igraph_matrix_transpose -- Transpose of a matrix.: igraph_matrix_transpose --- Transpose of a matrix_. + + +File: igraph-docs.info, Node: igraph_matrix_add_constant --- Add a constant to every element_, Next: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_, Up: Matrix operations + +7.3.8.1 igraph_matrix_add_constant -- Add a constant to every element. +...................................................................... + + + void igraph_matrix_add_constant(igraph_matrix_t *m, igraph_real_t plus); + + *Arguments:. * + +‘m’: + The input matrix. + +‘plud’: + The constant to add. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_, Next: igraph_matrix_add --- Add two matrices_, Prev: igraph_matrix_add_constant --- Add a constant to every element_, Up: Matrix operations + +7.3.8.2 igraph_matrix_scale -- Multiplies each element of the matrix by a constant. +................................................................................... + + + void igraph_matrix_scale(igraph_matrix_t *m, igraph_real_t by); + + *Arguments:. * + +‘m’: + The matrix. + +‘by’: + The constant. + + Added in version 0.2. + + Time complexity: O(n), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_add --- Add two matrices_, Next: igraph_matrix_sub --- Difference of two matrices_, Prev: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_, Up: Matrix operations + +7.3.8.3 igraph_matrix_add -- Add two matrices. +.............................................. + + + igraph_error_t igraph_matrix_add(igraph_matrix_t *m1, + const igraph_matrix_t *m2); + + Add ‘m2’ to ‘m1’, and store the result in ‘m1’. The dimensions of +the matrices must match. + + *Arguments:. * + +‘m1’: + The first matrix; the result will be stored here. + +‘m2’: + The second matrix; it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_sub --- Difference of two matrices_, Next: igraph_matrix_mul_elements --- Elementwise matrix multiplication_, Prev: igraph_matrix_add --- Add two matrices_, Up: Matrix operations + +7.3.8.4 igraph_matrix_sub -- Difference of two matrices. +........................................................ + + + igraph_error_t igraph_matrix_sub(igraph_matrix_t *m1, + const igraph_matrix_t *m2); + + Subtract ‘m2’ from ‘m1’ and store the result in ‘m1’. The dimensions +of the two matrices must match. + + *Arguments:. * + +‘m1’: + The first matrix; the result is stored here. + +‘m2’: + The second matrix; it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_mul_elements --- Elementwise matrix multiplication_, Next: igraph_matrix_div_elements --- Elementwise division_, Prev: igraph_matrix_sub --- Difference of two matrices_, Up: Matrix operations + +7.3.8.5 igraph_matrix_mul_elements -- Elementwise matrix multiplication. +........................................................................ + + + igraph_error_t igraph_matrix_mul_elements(igraph_matrix_t *m1, + const igraph_matrix_t *m2); + + Multiply ‘m1’ by ‘m2’ elementwise and store the result in ‘m1’. The +dimensions of the two matrices must match. + + *Arguments:. * + +‘m1’: + The first matrix; the result is stored here. + +‘m2’: + The second matrix; it is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_div_elements --- Elementwise division_, Next: igraph_matrix_sum --- Sum of elements_, Prev: igraph_matrix_mul_elements --- Elementwise matrix multiplication_, Up: Matrix operations + +7.3.8.6 igraph_matrix_div_elements -- Elementwise division. +........................................................... + + + igraph_error_t igraph_matrix_div_elements(igraph_matrix_t *m1, + const igraph_matrix_t *m2); + + Divide ‘m1’ by ‘m2’ elementwise and store the result in ‘m1’. The +dimensions of the two matrices must match. + + *Arguments:. * + +‘m1’: + The dividend. The result is store here. + +‘m2’: + The divisor. It is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_sum --- Sum of elements_, Next: igraph_matrix_prod --- Product of all matrix elements_, Prev: igraph_matrix_div_elements --- Elementwise division_, Up: Matrix operations + +7.3.8.7 igraph_matrix_sum -- Sum of elements. +............................................. + + + igraph_real_t igraph_matrix_sum(const igraph_matrix_t *m); + + Returns the sum of the elements of a matrix. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + The sum of the elements. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_prod --- Product of all matrix elements_, Next: igraph_matrix_rowsum --- Rowwise sum_, Prev: igraph_matrix_sum --- Sum of elements_, Up: Matrix operations + +7.3.8.8 igraph_matrix_prod -- Product of all matrix elements. +............................................................. + + + igraph_real_t igraph_matrix_prod(const igraph_matrix_t *m); + + Note that this function can result in overflow easily, even for not +too big matrices. Overflow is not checked. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + The product of the elements. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_rowsum --- Rowwise sum_, Next: igraph_matrix_colsum --- Columnwise sum_, Prev: igraph_matrix_prod --- Product of all matrix elements_, Up: Matrix operations + +7.3.8.9 igraph_matrix_rowsum -- Rowwise sum. +............................................ + + + igraph_error_t igraph_matrix_rowsum(const igraph_matrix_t *m, + igraph_vector_t *res); + + Calculate the sum of the elements in each row. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + Pointer to an initialized vector; the result is stored here. It + will be resized if necessary. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_colsum --- Columnwise sum_, Next: igraph_matrix_transpose --- Transpose of a matrix_, Prev: igraph_matrix_rowsum --- Rowwise sum_, Up: Matrix operations + +7.3.8.10 igraph_matrix_colsum -- Columnwise sum. +................................................ + + + igraph_error_t igraph_matrix_colsum(const igraph_matrix_t *m, + igraph_vector_t *res); + + Calculate the sum of the elements in each column. + + *Arguments:. * + +‘m’: + The input matrix. + +‘res’: + Pointer to an initialized vector; the result is stored here. It + will be resized if necessary. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_transpose --- Transpose of a matrix_, Prev: igraph_matrix_colsum --- Columnwise sum_, Up: Matrix operations + +7.3.8.11 igraph_matrix_transpose -- Transpose of a matrix. +.......................................................... + + + igraph_error_t igraph_matrix_transpose(igraph_matrix_t *m); + + Calculates the transpose of a matrix. When the matrix is non-square, +this function reallocates the storage used for the matrix. + + *Arguments:. * + +‘m’: + The input (and output) matrix. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: Matrix comparisons, Next: Combining matrices, Prev: Matrix operations, Up: Matrices + +7.3.9 Matrix comparisons +------------------------ + +* Menu: + +* igraph_matrix_all_e --- Are all elements equal?:: +* igraph_matrix_all_almost_e --- Are all elements almost equal?:: +* igraph_matrix_all_l --- Are all elements less?:: +* igraph_matrix_all_g --- Are all elements greater?:: +* igraph_matrix_all_le --- Are all elements less or equal?:: +* igraph_matrix_all_ge --- Are all elements greater or equal?:: +* igraph_matrix_zapsmall -- Replaces small elements of a matrix by exact zeros.: igraph_matrix_zapsmall --- Replaces small elements of a matrix by exact zeros_. + + +File: igraph-docs.info, Node: igraph_matrix_all_e --- Are all elements equal?, Next: igraph_matrix_all_almost_e --- Are all elements almost equal?, Up: Matrix comparisons + +7.3.9.1 igraph_matrix_all_e -- Are all elements equal? +...................................................... + + + igraph_bool_t igraph_matrix_all_e(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs); + + Checks element-wise equality of two matrices. For matrices +containing floating point values, consider using +‘igraph_matrix_all_almost_e()’ (*note igraph_matrix_all_almost_e --- Are +all elements almost equal?::). + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all equal to the + corresponding elements in ‘rhs’. Returns false if the dimensions + of the matrices don't match. + + Time complexity: O(nm), the size of the matrices. + + +File: igraph-docs.info, Node: igraph_matrix_all_almost_e --- Are all elements almost equal?, Next: igraph_matrix_all_l --- Are all elements less?, Prev: igraph_matrix_all_e --- Are all elements equal?, Up: Matrix comparisons + +7.3.9.2 igraph_matrix_all_almost_e -- Are all elements almost equal? +.................................................................... + + + igraph_bool_t igraph_matrix_all_almost_e(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs, + igraph_real_t eps); + + Checks if the elements of two matrices are equal within a relative +tolerance. + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + +‘eps’: + Relative tolerance, see ‘igraph_almost_equals()’ (*note + igraph_almost_equals --- Compare two double-precision floats with a + tolerance_::) for details. + + *Returns:. * + +‘’ + True if the two matrices are almost equal, false if there is at + least one differing element or if the matrices are not of the same + dimensions. + + +File: igraph-docs.info, Node: igraph_matrix_all_l --- Are all elements less?, Next: igraph_matrix_all_g --- Are all elements greater?, Prev: igraph_matrix_all_almost_e --- Are all elements almost equal?, Up: Matrix comparisons + +7.3.9.3 igraph_matrix_all_l -- Are all elements less? +..................................................... + + + igraph_bool_t igraph_matrix_all_l(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs); + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all less than the + corresponding elements in ‘rhs’. Returns false if the dimensions + of the matrices don't match. + + Time complexity: O(nm), the size of the matrices. + + +File: igraph-docs.info, Node: igraph_matrix_all_g --- Are all elements greater?, Next: igraph_matrix_all_le --- Are all elements less or equal?, Prev: igraph_matrix_all_l --- Are all elements less?, Up: Matrix comparisons + +7.3.9.4 igraph_matrix_all_g -- Are all elements greater? +........................................................ + + + igraph_bool_t igraph_matrix_all_g(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs); + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all greater than the + corresponding elements in ‘rhs’. Returns false if the dimensions + of the matrices don't match. + + Time complexity: O(nm), the size of the matrices. + + +File: igraph-docs.info, Node: igraph_matrix_all_le --- Are all elements less or equal?, Next: igraph_matrix_all_ge --- Are all elements greater or equal?, Prev: igraph_matrix_all_g --- Are all elements greater?, Up: Matrix comparisons + +7.3.9.5 igraph_matrix_all_le -- Are all elements less or equal? +............................................................... + + + igraph_bool_t + igraph_matrix_all_le(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs); + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all less than or equal to the + corresponding elements in ‘rhs’. Returns false if the dimensions + of the matrices don't match. + + Time complexity: O(nm), the size of the matrices. + + +File: igraph-docs.info, Node: igraph_matrix_all_ge --- Are all elements greater or equal?, Next: igraph_matrix_zapsmall --- Replaces small elements of a matrix by exact zeros_, Prev: igraph_matrix_all_le --- Are all elements less or equal?, Up: Matrix comparisons + +7.3.9.6 igraph_matrix_all_ge -- Are all elements greater or equal? +.................................................................. + + + igraph_bool_t + igraph_matrix_all_ge(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs); + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + + *Returns:. * + +‘’ + True if the elements in the ‘lhs’ are all greater than or equal to + the corresponding elements in ‘rhs’. Returns false if the + dimensions of the matrices don't match. + + Time complexity: O(nm), the size of the matrices. + + +File: igraph-docs.info, Node: igraph_matrix_zapsmall --- Replaces small elements of a matrix by exact zeros_, Prev: igraph_matrix_all_ge --- Are all elements greater or equal?, Up: Matrix comparisons + +7.3.9.7 igraph_matrix_zapsmall -- Replaces small elements of a matrix by exact zeros. +..................................................................................... + + + igraph_error_t igraph_matrix_zapsmall(igraph_matrix_t *m, igraph_real_t tol); + + Matrix elements which are smaller in magnitude than the given +absolute tolerance will be replaced by exact zeros. The default +tolerance corresponds to two-thirds of the representable digits of +‘igraph_real_t’, i.e. ‘DBL_EPSILON^(2/3)’ which is approximately +‘10^-10’. + + *Arguments:. * + +‘m’: + The matrix to process, it will be changed in-place. + +‘tol’: + Tolerance value. Numbers smaller than this in magnitude will be + replaced by zeros. Pass in zero to use the default tolerance. + Must not be negative. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_matrix_all_almost_e()’ (*note igraph_matrix_all_almost_e + --- Are all elements almost equal?::) and ‘igraph_almost_equals()’ + (*note igraph_almost_equals --- Compare two double-precision floats + with a tolerance_::) to perform comparisons with relative + tolerances. + + +File: igraph-docs.info, Node: Combining matrices, Next: Finding minimum and maximum <1>, Prev: Matrix comparisons, Up: Matrices + +7.3.10 Combining matrices +------------------------- + +* Menu: + +* igraph_matrix_rbind -- Combine two matrices rowwise.: igraph_matrix_rbind --- Combine two matrices rowwise_. +* igraph_matrix_cbind -- Combine matrices columnwise.: igraph_matrix_cbind --- Combine matrices columnwise_. + + +File: igraph-docs.info, Node: igraph_matrix_rbind --- Combine two matrices rowwise_, Next: igraph_matrix_cbind --- Combine matrices columnwise_, Up: Combining matrices + +7.3.10.1 igraph_matrix_rbind -- Combine two matrices rowwise. +............................................................. + + + igraph_error_t igraph_matrix_rbind(igraph_matrix_t *to, + const igraph_matrix_t *from); + + This function places the rows of ‘from’ below the rows of ‘to’ and +stores the result in ‘to’. The number of columns in the two matrices +must match. + + *Arguments:. * + +‘to’: + The upper matrix; the result is also stored here. + +‘from’: + The lower matrix. It is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements in the newly created +matrix. + + +File: igraph-docs.info, Node: igraph_matrix_cbind --- Combine matrices columnwise_, Prev: igraph_matrix_rbind --- Combine two matrices rowwise_, Up: Combining matrices + +7.3.10.2 igraph_matrix_cbind -- Combine matrices columnwise. +............................................................ + + + igraph_error_t igraph_matrix_cbind(igraph_matrix_t *to, + const igraph_matrix_t *from); + + This function places the columns of ‘from’ on the right of ‘to’, and +stores the result in ‘to’. + + *Arguments:. * + +‘to’: + The left matrix; the result is stored here too. + +‘from’: + The right matrix. It is left unchanged. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements on the new matrix. + + +File: igraph-docs.info, Node: Finding minimum and maximum <1>, Next: Matrix properties, Prev: Combining matrices, Up: Matrices + +7.3.11 Finding minimum and maximum +---------------------------------- + +* Menu: + +* igraph_matrix_min -- Smallest element of a matrix.: igraph_matrix_min --- Smallest element of a matrix_. +* igraph_matrix_max -- Largest element of a matrix.: igraph_matrix_max --- Largest element of a matrix_. +* igraph_matrix_which_min -- Indices of the smallest element.: igraph_matrix_which_min --- Indices of the smallest element_. +* igraph_matrix_which_max -- Indices of the largest element.: igraph_matrix_which_max --- Indices of the largest element_. +* igraph_matrix_minmax -- Minimum and maximum elements of a matrix.: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_. +* igraph_matrix_which_minmax -- Indices of the minimum and maximum elements.: igraph_matrix_which_minmax --- Indices of the minimum and maximum elements_. + + +File: igraph-docs.info, Node: igraph_matrix_min --- Smallest element of a matrix_, Next: igraph_matrix_max --- Largest element of a matrix_, Up: Finding minimum and maximum <1> + +7.3.11.1 igraph_matrix_min -- Smallest element of a matrix. +........................................................... + + + igraph_real_t igraph_matrix_min(const igraph_matrix_t *m); + + The matrix must be non-empty. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + The smallest element of ‘m’, or NaN if any element is NaN. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_max --- Largest element of a matrix_, Next: igraph_matrix_which_min --- Indices of the smallest element_, Prev: igraph_matrix_min --- Smallest element of a matrix_, Up: Finding minimum and maximum <1> + +7.3.11.2 igraph_matrix_max -- Largest element of a matrix. +.......................................................... + + + igraph_real_t igraph_matrix_max(const igraph_matrix_t *m); + + If the matrix is empty, an arbitrary number is returned. + + *Arguments:. * + +‘m’: + The matrix object. + + *Returns:. * + +‘’ + The maximum element of ‘m’, or NaN if any element is NaN. + + Added in version 0.2. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_which_min --- Indices of the smallest element_, Next: igraph_matrix_which_max --- Indices of the largest element_, Prev: igraph_matrix_max --- Largest element of a matrix_, Up: Finding minimum and maximum <1> + +7.3.11.3 igraph_matrix_which_min -- Indices of the smallest element. +.................................................................... + + + void igraph_matrix_which_min( + const igraph_matrix_t *m, igraph_int_t *i, igraph_int_t *j); + + The matrix must be non-empty. If the smallest element is not unique, +then the indices of the first such element are returned. If the matrix +contains NaN values, the indices of the first NaN value are returned. + + *Arguments:. * + +‘m’: + The matrix. + +‘i’: + Pointer to an ‘igraph_int_t’. The row index of the minimum is + stored here. + +‘j’: + Pointer to an ‘igraph_int_t’. The column index of the minimum is + stored here. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_which_max --- Indices of the largest element_, Next: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_, Prev: igraph_matrix_which_min --- Indices of the smallest element_, Up: Finding minimum and maximum <1> + +7.3.11.4 igraph_matrix_which_max -- Indices of the largest element. +................................................................... + + + void igraph_matrix_which_max( + const igraph_matrix_t *m, igraph_int_t *i, igraph_int_t *j); + + The matrix must be non-empty. If the largest element is not unique, +then the indices of the first such element are returned. If the matrix +contains NaN values, the indices of the first NaN value are returned. + + *Arguments:. * + +‘m’: + The matrix. + +‘i’: + Pointer to an ‘igraph_int_t’. The row index of the maximum is + stored here. + +‘j’: + Pointer to an ‘igraph_int_t’. The column index of the maximum is + stored here. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_, Next: igraph_matrix_which_minmax --- Indices of the minimum and maximum elements_, Prev: igraph_matrix_which_max --- Indices of the largest element_, Up: Finding minimum and maximum <1> + +7.3.11.5 igraph_matrix_minmax -- Minimum and maximum elements of a matrix. +.......................................................................... + + + void igraph_matrix_minmax(const igraph_matrix_t *m, + igraph_real_t *min, igraph_real_t *max); + + Handy if you want to have both the smallest and largest element of a +matrix. The matrix is only traversed once. The matrix must be +non-empty. If a matrix contains at least one NaN, both ‘min’ and ‘max’ +will be NaN. + + *Arguments:. * + +‘m’: + The input matrix. It must contain at least one element. + +‘min’: + Pointer to a base type variable. The minimum is stored here. + +‘max’: + Pointer to a base type variable. The maximum is stored here. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_which_minmax --- Indices of the minimum and maximum elements_, Prev: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_, Up: Finding minimum and maximum <1> + +7.3.11.6 igraph_matrix_which_minmax -- Indices of the minimum and maximum elements. +................................................................................... + + + void igraph_matrix_which_minmax(const igraph_matrix_t *m, + igraph_int_t *imin, igraph_int_t *jmin, + igraph_int_t *imax, igraph_int_t *jmax); + + Handy if you need the indices of the smallest and largest elements. +The matrix is traversed only once. The matrix must be non-empty. If +the minimum or maximum is not unique, the index of the first minimum or +the first maximum is returned, respectively. If a matrix contains at +least one NaN, both ‘which_min’ and ‘which_max’ will point to the first +NaN value. + + *Arguments:. * + +‘m’: + The input matrix. + +‘imin’: + Pointer to an ‘igraph_int_t’, the row index of the minimum is + stored here. + +‘jmin’: + Pointer to an ‘igraph_int_t’, the column index of the minimum is + stored here. + +‘imax’: + Pointer to an ‘igraph_int_t’, the row index of the maximum is + stored here. + +‘jmax’: + Pointer to an ‘igraph_int_t’, the column index of the maximum is + stored here. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: Matrix properties, Next: Searching for elements <1>, Prev: Finding minimum and maximum <1>, Up: Matrices + +7.3.12 Matrix properties +------------------------ + +* Menu: + +* igraph_matrix_empty --- Is the matrix empty?:: +* igraph_matrix_isnull -- Checks for a null matrix.: igraph_matrix_isnull --- Checks for a null matrix_. +* igraph_matrix_size -- The number of elements in a matrix.: igraph_matrix_size --- The number of elements in a matrix_. +* igraph_matrix_capacity -- Returns the number of elements allocated for a matrix.: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_. +* igraph_matrix_nrow -- The number of rows in a matrix.: igraph_matrix_nrow --- The number of rows in a matrix_. +* igraph_matrix_ncol -- The number of columns in a matrix.: igraph_matrix_ncol --- The number of columns in a matrix_. +* igraph_matrix_is_symmetric --- Is the matrix symmetric?:: +* igraph_matrix_maxdifference -- Maximum absolute difference between two matrices.: igraph_matrix_maxdifference --- Maximum absolute difference between two matrices_. + + +File: igraph-docs.info, Node: igraph_matrix_empty --- Is the matrix empty?, Next: igraph_matrix_isnull --- Checks for a null matrix_, Up: Matrix properties + +7.3.12.1 igraph_matrix_empty -- Is the matrix empty? +.................................................... + + + igraph_bool_t igraph_matrix_empty(const igraph_matrix_t *m); + + It is possible to have a matrix with zero rows or zero columns, or +even both. This functions checks for these. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the matrix contains zero elements, and ‘false’ + otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_isnull --- Checks for a null matrix_, Next: igraph_matrix_size --- The number of elements in a matrix_, Prev: igraph_matrix_empty --- Is the matrix empty?, Up: Matrix properties + +7.3.12.2 igraph_matrix_isnull -- Checks for a null matrix. +.......................................................... + + + igraph_bool_t igraph_matrix_isnull(const igraph_matrix_t *m); + + Checks whether all elements are zero. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + Boolean, ‘true’ is ‘m’ contains only zeros and ‘false’ otherwise. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_size --- The number of elements in a matrix_, Next: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_, Prev: igraph_matrix_isnull --- Checks for a null matrix_, Up: Matrix properties + +7.3.12.3 igraph_matrix_size -- The number of elements in a matrix. +.................................................................. + + + igraph_int_t igraph_matrix_size(const igraph_matrix_t *m); + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix object. + + *Returns:. * + +‘’ + The size of the matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_, Next: igraph_matrix_nrow --- The number of rows in a matrix_, Prev: igraph_matrix_size --- The number of elements in a matrix_, Up: Matrix properties + +7.3.12.4 igraph_matrix_capacity -- Returns the number of elements allocated for a matrix. +......................................................................................... + + + igraph_int_t igraph_matrix_capacity(const igraph_matrix_t *m); + + Note that this might be different from the size of the matrix (as +queried by ‘igraph_matrix_size()’ (*note igraph_matrix_size --- The +number of elements in a matrix_::), and specifies how many elements the +matrix can hold, without reallocation. + + *Arguments:. * + +‘v’: + Pointer to the (previously initialized) matrix object to query. + + *Returns:. * + +‘’ + The allocated capacity. + + *See also:. * + +‘’ + ‘igraph_matrix_size()’ (*note igraph_matrix_size --- The number of + elements in a matrix_::), ‘igraph_matrix_nrow()’ (*note + igraph_matrix_nrow --- The number of rows in a matrix_::), + ‘igraph_matrix_ncol()’ (*note igraph_matrix_ncol --- The number of + columns in a matrix_::). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_nrow --- The number of rows in a matrix_, Next: igraph_matrix_ncol --- The number of columns in a matrix_, Prev: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_, Up: Matrix properties + +7.3.12.5 igraph_matrix_nrow -- The number of rows in a matrix. +.............................................................. + + + igraph_int_t igraph_matrix_nrow(const igraph_matrix_t *m); + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix object. + + *Returns:. * + +‘’ + The number of rows in the matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_ncol --- The number of columns in a matrix_, Next: igraph_matrix_is_symmetric --- Is the matrix symmetric?, Prev: igraph_matrix_nrow --- The number of rows in a matrix_, Up: Matrix properties + +7.3.12.6 igraph_matrix_ncol -- The number of columns in a matrix. +................................................................. + + + igraph_int_t igraph_matrix_ncol(const igraph_matrix_t *m); + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix object. + + *Returns:. * + +‘’ + The number of columns in the matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_matrix_is_symmetric --- Is the matrix symmetric?, Next: igraph_matrix_maxdifference --- Maximum absolute difference between two matrices_, Prev: igraph_matrix_ncol --- The number of columns in a matrix_, Up: Matrix properties + +7.3.12.7 igraph_matrix_is_symmetric -- Is the matrix symmetric? +............................................................... + + + igraph_bool_t igraph_matrix_is_symmetric(const igraph_matrix_t *m); + + A non-square matrix is not symmetric by definition. + + *Arguments:. * + +‘m’: + The input matrix. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the matrix is square and symmetric, ‘false’ + otherwise. + + Time complexity: O(mn), the number of elements. O(1) for non-square +matrices. + + +File: igraph-docs.info, Node: igraph_matrix_maxdifference --- Maximum absolute difference between two matrices_, Prev: igraph_matrix_is_symmetric --- Is the matrix symmetric?, Up: Matrix properties + +7.3.12.8 igraph_matrix_maxdifference -- Maximum absolute difference between two matrices. +......................................................................................... + + + igraph_real_t igraph_matrix_maxdifference(const igraph_matrix_t *m1, + const igraph_matrix_t *m2); + + Calculate the maximum absolute difference of two matrices. Both +matrices must be non-empty. If their dimensions differ then a warning +is given and the comparison is performed by vectors columnwise from both +matrices. The remaining elements in the larger vector are ignored. + + *Arguments:. * + +‘m1’: + The first matrix. + +‘m2’: + The second matrix. + + *Returns:. * + +‘’ + The element with the largest absolute value in ‘m1’ - ‘m2’. + + Time complexity: O(mn), the elements in the smaller matrix. + + +File: igraph-docs.info, Node: Searching for elements <1>, Next: Resizing operations <1>, Prev: Matrix properties, Up: Matrices + +7.3.13 Searching for elements +----------------------------- + +* Menu: + +* igraph_matrix_contains -- Search for an element.: igraph_matrix_contains --- Search for an element_. +* igraph_matrix_search -- Search from a given position.: igraph_matrix_search --- Search from a given position_. + + +File: igraph-docs.info, Node: igraph_matrix_contains --- Search for an element_, Next: igraph_matrix_search --- Search from a given position_, Up: Searching for elements <1> + +7.3.13.1 igraph_matrix_contains -- Search for an element. +......................................................... + + + igraph_bool_t igraph_matrix_contains(const igraph_matrix_t *m, + igraph_real_t e); + + Search for the given element in the matrix. + + *Arguments:. * + +‘m’: + The input matrix. + +‘e’: + The element to search for. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the matrix contains ‘e’, ‘false’ otherwise. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: igraph_matrix_search --- Search from a given position_, Prev: igraph_matrix_contains --- Search for an element_, Up: Searching for elements <1> + +7.3.13.2 igraph_matrix_search -- Search from a given position. +.............................................................. + + + igraph_bool_t igraph_matrix_search(const igraph_matrix_t *m, + igraph_int_t from, igraph_real_t what, igraph_int_t *pos, + igraph_int_t *row, igraph_int_t *col); + + Search for an element in a matrix and start the search from the given +position. The search is performed columnwise. + + *Arguments:. * + +‘m’: + The input matrix. + +‘from’: + The position to search from, the positions are enumerated + columnwise. + +‘what’: + The element to search for. + +‘pos’: + Pointer to an ‘igraph_int_t’. If the element is found, then this + is set to the position of its first appearance. + +‘row’: + Pointer to an ‘igraph_int_t’. If the element is found, then this + is set to its row index. + +‘col’: + Pointer to an ‘igraph_int_t’. If the element is found, then this + is set to its column index. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the element is found, ‘false’ otherwise. + + Time complexity: O(mn), the number of elements. + + +File: igraph-docs.info, Node: Resizing operations <1>, Next: Complex matrix operations, Prev: Searching for elements <1>, Up: Matrices + +7.3.14 Resizing operations +-------------------------- + +* Menu: + +* igraph_matrix_resize -- Resizes a matrix.: igraph_matrix_resize --- Resizes a matrix_. +* igraph_matrix_resize_min -- Deallocates unused memory for a matrix.: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_. +* igraph_matrix_add_rows -- Adds rows to a matrix.: igraph_matrix_add_rows --- Adds rows to a matrix_. +* igraph_matrix_add_cols -- Adds columns to a matrix.: igraph_matrix_add_cols --- Adds columns to a matrix_. +* igraph_matrix_remove_row -- Remove a row.: igraph_matrix_remove_row --- Remove a row_. +* igraph_matrix_remove_col -- Removes a column from a matrix.: igraph_matrix_remove_col --- Removes a column from a matrix_. + + +File: igraph-docs.info, Node: igraph_matrix_resize --- Resizes a matrix_, Next: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_, Up: Resizing operations <1> + +7.3.14.1 igraph_matrix_resize -- Resizes a matrix. +.................................................. + + + igraph_error_t igraph_matrix_resize(igraph_matrix_t *m, igraph_int_t nrow, igraph_int_t ncol); + + This function resizes a matrix by adding more elements to it. The +matrix contains arbitrary data after resizing it. That is, after +calling this function you cannot expect that element (i,j) in the matrix +remains the same as before. + + *Arguments:. * + +‘m’: + Pointer to an already initialized matrix object. + +‘nrow’: + The number of rows in the resized matrix. + +‘ncol’: + The number of columns in the resized matrix. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) if the matrix gets smaller, usually O(n) if it +gets larger, n is the number of elements in the resized matrix. + + +File: igraph-docs.info, Node: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_, Next: igraph_matrix_add_rows --- Adds rows to a matrix_, Prev: igraph_matrix_resize --- Resizes a matrix_, Up: Resizing operations <1> + +7.3.14.2 igraph_matrix_resize_min -- Deallocates unused memory for a matrix. +............................................................................ + + + void igraph_matrix_resize_min(igraph_matrix_t *m); + + This function attempts to deallocate the unused reserved storage of a +matrix. + + *Arguments:. * + +‘m’: + Pointer to an initialized matrix. + + *See also:. * + +‘’ + ‘igraph_matrix_resize()’ (*note igraph_matrix_resize --- Resizes a + matrix_::). + + Time complexity: operating system dependent, O(n) at worst. + + +File: igraph-docs.info, Node: igraph_matrix_add_rows --- Adds rows to a matrix_, Next: igraph_matrix_add_cols --- Adds columns to a matrix_, Prev: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_, Up: Resizing operations <1> + +7.3.14.3 igraph_matrix_add_rows -- Adds rows to a matrix. +......................................................... + + + igraph_error_t igraph_matrix_add_rows(igraph_matrix_t *m, igraph_int_t n); + + *Arguments:. * + +‘m’: + The matrix object. + +‘n’: + The number of rows to add. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there isn't enough memory for the + operation. + + Time complexity: linear with the number of elements of the new, +resized matrix. + + +File: igraph-docs.info, Node: igraph_matrix_add_cols --- Adds columns to a matrix_, Next: igraph_matrix_remove_row --- Remove a row_, Prev: igraph_matrix_add_rows --- Adds rows to a matrix_, Up: Resizing operations <1> + +7.3.14.4 igraph_matrix_add_cols -- Adds columns to a matrix. +............................................................ + + + igraph_error_t igraph_matrix_add_cols(igraph_matrix_t *m, igraph_int_t n); + + *Arguments:. * + +‘m’: + The matrix object. + +‘n’: + The number of columns to add. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory to + perform the operation. + + Time complexity: linear with the number of elements of the new, +resized matrix. + + +File: igraph-docs.info, Node: igraph_matrix_remove_row --- Remove a row_, Next: igraph_matrix_remove_col --- Removes a column from a matrix_, Prev: igraph_matrix_add_cols --- Adds columns to a matrix_, Up: Resizing operations <1> + +7.3.14.5 igraph_matrix_remove_row -- Remove a row. +.................................................. + + + igraph_error_t igraph_matrix_remove_row(igraph_matrix_t *m, igraph_int_t row); + + A row is removed from the matrix. + + *Arguments:. * + +‘m’: + The input matrix. + +‘row’: + The index of the row to remove. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(mn), the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_remove_col --- Removes a column from a matrix_, Prev: igraph_matrix_remove_row --- Remove a row_, Up: Resizing operations <1> + +7.3.14.6 igraph_matrix_remove_col -- Removes a column from a matrix. +.................................................................... + + + igraph_error_t igraph_matrix_remove_col(igraph_matrix_t *m, igraph_int_t col); + + *Arguments:. * + +‘m’: + The matrix object. + +‘col’: + The column to remove. + + *Returns:. * + +‘’ + Error code, always returns with success. + + Time complexity: linear with the number of elements of the new, +resized matrix. + + +File: igraph-docs.info, Node: Complex matrix operations, Prev: Resizing operations <1>, Up: Matrices + +7.3.15 Complex matrix operations +-------------------------------- + +* Menu: + +* igraph_matrix_complex_real -- Gives the real part of a complex matrix.: igraph_matrix_complex_real --- Gives the real part of a complex matrix_. +* igraph_matrix_complex_imag -- Gives the imaginary part of a complex matrix.: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_. +* igraph_matrix_complex_realimag -- Gives the real and imaginary parts of a complex matrix.: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_. +* igraph_matrix_complex_create -- Creates a complex matrix from a real and imaginary part.: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_. +* igraph_matrix_complex_create_polar -- Creates a complex matrix from a magnitude and an angle.: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_. +* igraph_matrix_complex_all_almost_e --- Are all elements almost equal?:: +* igraph_matrix_complex_zapsmall -- Replaces small elements of a complex matrix by exact zeros.: igraph_matrix_complex_zapsmall --- Replaces small elements of a complex matrix by exact zeros_. + + +File: igraph-docs.info, Node: igraph_matrix_complex_real --- Gives the real part of a complex matrix_, Next: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_, Up: Complex matrix operations + +7.3.15.1 igraph_matrix_complex_real -- Gives the real part of a complex matrix. +............................................................................... + + + igraph_error_t igraph_matrix_complex_real(const igraph_matrix_complex_t *m, + igraph_matrix_t *real); + + *Arguments:. * + +‘m’: + Pointer to a complex matrix. + +‘real’: + Pointer to an initialized matrix. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_, Next: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_, Prev: igraph_matrix_complex_real --- Gives the real part of a complex matrix_, Up: Complex matrix operations + +7.3.15.2 igraph_matrix_complex_imag -- Gives the imaginary part of a complex matrix. +.................................................................................... + + + igraph_error_t igraph_matrix_complex_imag(const igraph_matrix_complex_t *m, + igraph_matrix_t *imag); + + *Arguments:. * + +‘m’: + Pointer to a complex matrix. + +‘imag’: + Pointer to an initialized matrix. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_, Next: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_, Prev: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_, Up: Complex matrix operations + +7.3.15.3 igraph_matrix_complex_realimag -- Gives the real and imaginary parts of a complex matrix. +.................................................................................................. + + + igraph_error_t igraph_matrix_complex_realimag(const igraph_matrix_complex_t *m, + igraph_matrix_t *real, + igraph_matrix_t *imag); + + *Arguments:. * + +‘m’: + Pointer to a complex matrix. + +‘real’: + Pointer to an initialized matrix. The real part will be stored + here. + +‘imag’: + Pointer to an initialized matrix. The imaginary part will be + stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_, Next: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Prev: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_, Up: Complex matrix operations + +7.3.15.4 igraph_matrix_complex_create -- Creates a complex matrix from a real and imaginary part. +................................................................................................. + + + igraph_error_t igraph_matrix_complex_create(igraph_matrix_complex_t *m, + const igraph_matrix_t *real, + const igraph_matrix_t *imag); + + *Arguments:. * + +‘m’: + Pointer to an uninitialized complex matrix. + +‘real’: + Pointer to the real part of the complex matrix. + +‘imag’: + Pointer to the imaginary part of the complex matrix. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Next: igraph_matrix_complex_all_almost_e --- Are all elements almost equal?, Prev: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_, Up: Complex matrix operations + +7.3.15.5 igraph_matrix_complex_create_polar -- Creates a complex matrix from a magnitude and an angle. +...................................................................................................... + + + igraph_error_t igraph_matrix_complex_create_polar(igraph_matrix_complex_t *m, + const igraph_matrix_t *r, + const igraph_matrix_t *theta); + + *Arguments:. * + +‘m’: + Pointer to an uninitialized complex matrix. + +‘r’: + Pointer to a real matrix containing magnitudes. + +‘theta’: + Pointer to a real matrix containing arguments (phase angles). + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of elements in the matrix. + + +File: igraph-docs.info, Node: igraph_matrix_complex_all_almost_e --- Are all elements almost equal?, Next: igraph_matrix_complex_zapsmall --- Replaces small elements of a complex matrix by exact zeros_, Prev: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_, Up: Complex matrix operations + +7.3.15.6 igraph_matrix_complex_all_almost_e -- Are all elements almost equal? +............................................................................. + + + igraph_bool_t igraph_matrix_complex_all_almost_e(igraph_matrix_complex_t *lhs, + igraph_matrix_complex_t *rhs, + igraph_real_t eps); + + Checks if the elements of two complex matrices are equal within a +relative tolerance. + + *Arguments:. * + +‘lhs’: + The first matrix. + +‘rhs’: + The second matrix. + +‘eps’: + Relative tolerance, see ‘igraph_complex_almost_equals()’ (*note + igraph_complex_almost_equals --- Compare two complex numbers with a + tolerance_::) for details. + + *Returns:. * + +‘’ + True if the two matrices are almost equal, false if there is at + least one differing element or if the matrices are not of the same + dimensions. + + +File: igraph-docs.info, Node: igraph_matrix_complex_zapsmall --- Replaces small elements of a complex matrix by exact zeros_, Prev: igraph_matrix_complex_all_almost_e --- Are all elements almost equal?, Up: Complex matrix operations + +7.3.15.7 igraph_matrix_complex_zapsmall -- Replaces small elements of a complex matrix by exact zeros. +...................................................................................................... + + + igraph_error_t igraph_matrix_complex_zapsmall(igraph_matrix_complex_t *m, igraph_real_t tol); + + Similarly to ‘igraph_matrix_zapsmall()’ (*note igraph_matrix_zapsmall +--- Replaces small elements of a matrix by exact zeros_::), small +elements will be replaced by zeros. The operation is performed +separately on the real and imaginary parts of the numbers. This way, +complex numbers with a large real part and tiny imaginary part will +effectively be transformed to real numbers. The default tolerance +corresponds to two-thirds of the representable digits of +‘igraph_real_t’, i.e. ‘DBL_EPSILON^(2/3)’ which is approximately +‘10^-10’. + + *Arguments:. * + +‘m’: + The matrix to process, it will be changed in-place. + +‘tol’: + Tolerance value. Real and imaginary parts smaller than this in + magnitude will be replaced by zeros. Pass in zero to use the + default tolerance. Must not be negative. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_matrix_complex_all_almost_e()’ (*note + igraph_matrix_complex_all_almost_e --- Are all elements almost + equal?::) and ‘igraph_complex_almost_equals()’ (*note + igraph_complex_almost_equals --- Compare two complex numbers with a + tolerance_::) to perform comparisons with relative tolerances. + + +File: igraph-docs.info, Node: Sparse matrices, Next: Stacks, Prev: Matrices, Up: Data structure library; vector; matrix; other data types + +7.4 Sparse matrices +=================== + +* Menu: + +* About sparse matrices:: +* Creating sparse matrix objects:: +* Query properties of a sparse matrix:: +* Operations on sparse matrices:: +* Operations on sparse matrix iterators:: +* Operations that change the internal representation:: +* Decompositions and solving linear systems:: +* Eigenvalues and eigenvectors:: +* Conversion to other data types:: +* Writing to a file, or to the screen: Writing to a file; or to the screen. + + +File: igraph-docs.info, Node: About sparse matrices, Next: Creating sparse matrix objects, Up: Sparse matrices + +7.4.1 About sparse matrices +--------------------------- + +The ‘igraph_sparsemat_t’ data type stores sparse matrices, i.e. +matrices in which the majority of the elements are zero. + + The data type is essentially a wrapper to some of the functions in +the CXSparse library, by Tim Davis, see +http://faculty.cse.tamu.edu/davis/suitesparse.html +(http://faculty.cse.tamu.edu/davis/suitesparse.html) + + Matrices can be stored in two formats: triplet and column-compressed. +The triplet format is intended for sparse matrix initialization, as it +is easy to add new (non-zero) elements to it. Most of the computations +are done on sparse matrices in column-compressed format, after the user +has converted the triplet matrix to column-compressed, via +‘igraph_sparsemat_compress()’ (*note igraph_sparsemat_compress --- +Converts a sparse matrix to column-compressed format_::). + + Both formats are dynamic, in the sense that new elements can be added +to them, possibly resulting the allocation of more memory. + + Row and column indices follow the C convention and are zero-based. + + * File examples/simple/igraph_sparsemat.c* + + * File examples/simple/igraph_sparsemat3.c* + + * File examples/simple/igraph_sparsemat4.c* + + * File examples/simple/igraph_sparsemat6.c* + + * File examples/simple/igraph_sparsemat7.c* + + * File examples/simple/igraph_sparsemat8.c* + + +File: igraph-docs.info, Node: Creating sparse matrix objects, Next: Query properties of a sparse matrix, Prev: About sparse matrices, Up: Sparse matrices + +7.4.2 Creating sparse matrix objects +------------------------------------ + +* Menu: + +* igraph_sparsemat_init -- Initializes a sparse matrix, in triplet format.: igraph_sparsemat_init --- Initializes a sparse matrix; in triplet format_. +* igraph_sparsemat_init_copy -- Copies a sparse matrix.: igraph_sparsemat_init_copy --- Copies a sparse matrix_. +* igraph_sparsemat_init_diag -- Creates a sparse diagonal matrix.: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_. +* igraph_sparsemat_init_eye -- Creates a sparse identity matrix.: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_. +* igraph_sparsemat_realloc -- Allocates more (or less) memory for a sparse matrix.: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_. +* igraph_sparsemat_destroy -- Deallocates memory used by a sparse matrix.: igraph_sparsemat_destroy --- Deallocates memory used by a sparse matrix_. + + +File: igraph-docs.info, Node: igraph_sparsemat_init --- Initializes a sparse matrix; in triplet format_, Next: igraph_sparsemat_init_copy --- Copies a sparse matrix_, Up: Creating sparse matrix objects + +7.4.2.1 igraph_sparsemat_init -- Initializes a sparse matrix, in triplet format. +................................................................................ + + + igraph_error_t igraph_sparsemat_init(igraph_sparsemat_t *A, igraph_int_t rows, + igraph_int_t cols, igraph_int_t nzmax); + + This is the most common way to create a sparse matrix, together with +the ‘igraph_sparsemat_entry()’ (*note igraph_sparsemat_entry --- Adds an +element to a sparse matrix_::) function, which can be used to add the +non-zero elements one by one. Once done, the user can call +‘igraph_sparsemat_compress()’ (*note igraph_sparsemat_compress --- +Converts a sparse matrix to column-compressed format_::) to convert the +matrix to column-compressed, to allow computations with it. + + The user must call ‘igraph_sparsemat_destroy()’ (*note +igraph_sparsemat_destroy --- Deallocates memory used by a sparse +matrix_::) on the matrix to deallocate the memory, once the matrix is no +more needed. + + *Arguments:. * + +‘A’: + Pointer to a not yet initialized sparse matrix. + +‘rows’: + The number of rows in the matrix. + +‘cols’: + The number of columns. + +‘nzmax’: + The maximum number of non-zero elements in the matrix. It is not + compulsory to get this right, but it is useful for the allocation + of the proper amount of memory. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_init_copy --- Copies a sparse matrix_, Next: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_, Prev: igraph_sparsemat_init --- Initializes a sparse matrix; in triplet format_, Up: Creating sparse matrix objects + +7.4.2.2 igraph_sparsemat_init_copy -- Copies a sparse matrix. +............................................................. + + + igraph_error_t igraph_sparsemat_init_copy( + igraph_sparsemat_t *to, const igraph_sparsemat_t *from + ); + + Create a sparse matrix object, by copying another one. The source +matrix can be either in triplet or column-compressed format. + + Exactly the same amount of memory will be allocated to the copy +matrix, as it is currently for the original one. + + *Arguments:. * + +‘to’: + Pointer to an uninitialized sparse matrix, the copy will be created + here. + +‘from’: + The sparse matrix to copy. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n+nzmax), the number of columns plus the maximum +number of non-zero elements. + + +File: igraph-docs.info, Node: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_, Next: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_, Prev: igraph_sparsemat_init_copy --- Copies a sparse matrix_, Up: Creating sparse matrix objects + +7.4.2.3 igraph_sparsemat_init_diag -- Creates a sparse diagonal matrix. +....................................................................... + + + igraph_error_t igraph_sparsemat_init_diag( + igraph_sparsemat_t *A, igraph_int_t nzmax, const igraph_vector_t *values, + igraph_bool_t compress + ); + + *Arguments:. * + +‘A’: + An uninitialized sparse matrix, the result is stored here. + +‘nzmax’: + The maximum number of non-zero elements, this essentially gives the + amount of memory that will be allocated for matrix elements. + +‘values’: + The values to store in the diagonal, the size of the matrix defined + by the length of this vector. + +‘compress’: + Whether to create a column-compressed matrix. If false, then a + triplet matrix is created. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the length of the diagonal vector. + + +File: igraph-docs.info, Node: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_, Next: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_, Prev: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_, Up: Creating sparse matrix objects + +7.4.2.4 igraph_sparsemat_init_eye -- Creates a sparse identity matrix. +...................................................................... + + + igraph_error_t igraph_sparsemat_init_eye( + igraph_sparsemat_t *A, igraph_int_t n, igraph_int_t nzmax, + igraph_real_t value, igraph_bool_t compress + ); + + *Arguments:. * + +‘A’: + An uninitialized sparse matrix, the result is stored here. + +‘n’: + The number of rows and number of columns in the matrix. + +‘nzmax’: + The maximum number of non-zero elements, this essentially gives the + amount of memory that will be allocated for matrix elements. + +‘value’: + The value to store in the diagonal. + +‘compress’: + Whether to create a column-compressed matrix. If false, then a + triplet matrix is created. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n). + + +File: igraph-docs.info, Node: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_, Next: igraph_sparsemat_destroy --- Deallocates memory used by a sparse matrix_, Prev: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_, Up: Creating sparse matrix objects + +7.4.2.5 igraph_sparsemat_realloc -- Allocates more (or less) memory for a sparse matrix. +........................................................................................ + + + igraph_error_t igraph_sparsemat_realloc(igraph_sparsemat_t *A, igraph_int_t nzmax); + + Sparse matrices automatically allocate more memory, as needed. To +control memory allocation, the user can call this function, to allocate +memory for a given number of non-zero elements. + + *Arguments:. * + +‘A’: + The sparse matrix, it can be in triplet or column-compressed + format. + +‘nzmax’: + The new maximum number of non-zero elements. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_destroy --- Deallocates memory used by a sparse matrix_, Prev: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_, Up: Creating sparse matrix objects + +7.4.2.6 igraph_sparsemat_destroy -- Deallocates memory used by a sparse matrix. +............................................................................... + + + void igraph_sparsemat_destroy(igraph_sparsemat_t *A); + + One destroyed, the sparse matrix must be initialized again, before +calling any other operation on it. + + *Arguments:. * + +‘A’: + The sparse matrix to destroy. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Query properties of a sparse matrix, Next: Operations on sparse matrices, Prev: Creating sparse matrix objects, Up: Sparse matrices + +7.4.3 Query properties of a sparse matrix +----------------------------------------- + +* Menu: + +* igraph_sparsemat_index -- Extracts a submatrix or a single element.: igraph_sparsemat_index --- Extracts a submatrix or a single element_. +* igraph_sparsemat_nrow -- Number of rows.: igraph_sparsemat_nrow --- Number of rows_. +* igraph_sparsemat_ncol -- Number of columns.: igraph_sparsemat_ncol --- Number of columns_. +* igraph_sparsemat_type -- Type of a sparse matrix (triplet or column-compressed).: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_. +* igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?:: +* igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?:: +* igraph_sparsemat_is_symmetric -- Returns whether a sparse matrix is symmetric.: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_. +* igraph_sparsemat_get -- Return the value of a single element from a sparse matrix.: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_. +* igraph_sparsemat_getelements -- Returns all elements of a sparse matrix.: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_. +* igraph_sparsemat_getelements_sorted -- Returns all elements of a sparse matrix, sorted by row and column indices.: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_. +* igraph_sparsemat_min -- Minimum of a sparse matrix.: igraph_sparsemat_min --- Minimum of a sparse matrix_. +* igraph_sparsemat_max -- Maximum of a sparse matrix.: igraph_sparsemat_max --- Maximum of a sparse matrix_. +* igraph_sparsemat_minmax -- Minimum and maximum of a sparse matrix.: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_. +* igraph_sparsemat_count_nonzero -- Counts nonzero elements of a sparse matrix.: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_. +* igraph_sparsemat_count_nonzerotol -- Counts nonzero elements of a sparse matrix, ignoring elements close to zero.: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_. +* igraph_sparsemat_rowsums -- Row-wise sums.: igraph_sparsemat_rowsums --- Row-wise sums_. +* igraph_sparsemat_colsums -- Column-wise sums.: igraph_sparsemat_colsums --- Column-wise sums_. +* igraph_sparsemat_nonzero_storage -- Returns number of stored entries of a sparse matrix.: igraph_sparsemat_nonzero_storage --- Returns number of stored entries of a sparse matrix_. + + +File: igraph-docs.info, Node: igraph_sparsemat_index --- Extracts a submatrix or a single element_, Next: igraph_sparsemat_nrow --- Number of rows_, Up: Query properties of a sparse matrix + +7.4.3.1 igraph_sparsemat_index -- Extracts a submatrix or a single element. +........................................................................... + + + igraph_error_t igraph_sparsemat_index(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres); + + This function indexes into a sparse matrix. It serves two purposes. +First, it can extract submatrices from a sparse matrix. Second, as a +special case, it can extract a single element from a sparse matrix. + + *Arguments:. * + +‘A’: + The input matrix, it must be in column-compressed format. + +‘p’: + An integer vector, or a null pointer. The selected row index or + indices. A null pointer selects all rows. + +‘q’: + An integer vector, or a null pointer. The selected column index or + indices. A null pointer selects all columns. + +‘res’: + Pointer to an uninitialized sparse matrix, or a null pointer. If + not a null pointer, then the selected submatrix is stored here. + +‘constres’: + Pointer to a real variable or a null pointer. If not a null + pointer, then the first non-zero element in the selected submatrix + is stored here, if there is one. Otherwise zero is stored here. + This behavior is handy if one wants to select a single entry from + the matrix. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_nrow --- Number of rows_, Next: igraph_sparsemat_ncol --- Number of columns_, Prev: igraph_sparsemat_index --- Extracts a submatrix or a single element_, Up: Query properties of a sparse matrix + +7.4.3.2 igraph_sparsemat_nrow -- Number of rows. +................................................ + + + igraph_int_t igraph_sparsemat_nrow(const igraph_sparsemat_t *A); + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + + *Returns:. * + +‘’ + The number of rows in the ‘A’ matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_ncol --- Number of columns_, Next: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_, Prev: igraph_sparsemat_nrow --- Number of rows_, Up: Query properties of a sparse matrix + +7.4.3.3 igraph_sparsemat_ncol -- Number of columns. +................................................... + + + igraph_int_t igraph_sparsemat_ncol(const igraph_sparsemat_t *A); + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + + *Returns:. * + +‘’ + The number of columns in the ‘A’ matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_, Next: igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?, Prev: igraph_sparsemat_ncol --- Number of columns_, Up: Query properties of a sparse matrix + +7.4.3.4 igraph_sparsemat_type -- Type of a sparse matrix (triplet or column-compressed). +........................................................................................ + + + igraph_sparsemat_type_t igraph_sparsemat_type(const igraph_sparsemat_t *A); + + Gives whether a sparse matrix is stored in the triplet format or in +column-compressed format. + + *Arguments:. * + +‘A’: + The input matrix. + + *Returns:. * + +‘’ + Either ‘IGRAPH_SPARSEMAT_CC’ or ‘IGRAPH_SPARSEMAT_TRIPLET’. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?, Next: igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?, Prev: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_, Up: Query properties of a sparse matrix + +7.4.3.5 igraph_sparsemat_is_triplet -- Is this sparse matrix in triplet format? +............................................................................... + + + igraph_bool_t igraph_sparsemat_is_triplet(const igraph_sparsemat_t *A); + + Decides whether a sparse matrix is in triplet format. + + *Arguments:. * + +‘A’: + The input matrix. + + *Returns:. * + +‘’ + One if the input matrix is in triplet format, zero otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?, Next: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_, Prev: igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?, Up: Query properties of a sparse matrix + +7.4.3.6 igraph_sparsemat_is_cc -- Is this sparse matrix in column-compressed format? +.................................................................................... + + + igraph_bool_t igraph_sparsemat_is_cc(const igraph_sparsemat_t *A); + + Decides whether a sparse matrix is in column-compressed format. + + *Arguments:. * + +‘A’: + The input matrix. + + *Returns:. * + +‘’ + One if the input matrix is in column-compressed format, zero + otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_, Next: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_, Prev: igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?, Up: Query properties of a sparse matrix + +7.4.3.7 igraph_sparsemat_is_symmetric -- Returns whether a sparse matrix is symmetric. +...................................................................................... + + + igraph_error_t igraph_sparsemat_is_symmetric(const igraph_sparsemat_t *A, igraph_bool_t *result); + + *Arguments:. * + +‘A’: + The input matrix + +‘result’: + Pointer to an ‘igraph_bool_t’ ; the result is provided here. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_, Next: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_, Prev: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_, Up: Query properties of a sparse matrix + +7.4.3.8 igraph_sparsemat_get -- Return the value of a single element from a sparse matrix. +.......................................................................................... + + + igraph_real_t igraph_sparsemat_get( + const igraph_sparsemat_t *A, igraph_int_t row, igraph_int_t col + ); + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + +‘row’: + The row index + +‘col’: + The column index + + *Returns:. * + +‘’ + The value of the cell with the given row and column indices in the + matrix; zero if the indices are out of bounds. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_, Next: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_, Prev: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.9 igraph_sparsemat_getelements -- Returns all elements of a sparse matrix. +................................................................................ + + + igraph_error_t igraph_sparsemat_getelements(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); + + This function will return the elements of a sparse matrix in three +vectors. Two vectors will indicate where the elements are located, and +one will specify the elements themselves. + + *Arguments:. * + +‘A’: + A sparse matrix in either triplet or compressed form. + +‘i’: + An initialized integer vector. This will store the rows of the + returned elements. + +‘j’: + An initialized integer vector. For a triplet matrix this will + store the columns of the returned elements. For a compressed + matrix, if the column index is ‘k’, then ‘j[k]’ is the index in ‘x’ + of the start of the ‘k-th’ column, and the last element of ‘j’ is + the total number of elements. The total number of elements in the + ‘k-th’ column is therefore ‘j[k+1] - j[k]’. For example, if there + is one element in the first column, and five in the second, ‘j’ + will be set to ‘{0, 1, 6}’. + +‘x’: + An initialized vector. The elements will be placed here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of stored elements in the sparse +matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_, Next: igraph_sparsemat_min --- Minimum of a sparse matrix_, Prev: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.10 igraph_sparsemat_getelements_sorted -- Returns all elements of a sparse matrix, sorted by row and column indices. +.......................................................................................................................... + + + igraph_error_t igraph_sparsemat_getelements_sorted(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); + + This function will sort a sparse matrix and return the elements in +three vectors. Two vectors will indicate where the elements are +located, and one will specify the elements themselves. + + Sorting is done based on the _indices_ of the elements, not their +numeric values. The returned entries will be sorted by column indices; +entries in the same column are then sorted by row indices. + + *Arguments:. * + +‘A’: + A sparse matrix in either triplet or compressed form. + +‘i’: + An initialized integer vector. This will store the rows of the + returned elements. + +‘j’: + An initialized integer vector. For a triplet matrix this will + store the columns of the returned elements. For a compressed + matrix, if the column index is ‘k’, then ‘j[k]’ is the index in ‘x’ + of the start of the ‘k-th’ column, and the last element of ‘j’ is + the total number of elements. The total number of elements in the + ‘k-th’ column is therefore ‘j[k+1] - j[k]’. For example, if there + is one element in the first column, and five in the second, ‘j’ + will be set to ‘{0, 1, 6}’. + +‘x’: + An initialized vector. The elements will be placed here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_min --- Minimum of a sparse matrix_, Next: igraph_sparsemat_max --- Maximum of a sparse matrix_, Prev: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_, Up: Query properties of a sparse matrix + +7.4.3.11 igraph_sparsemat_min -- Minimum of a sparse matrix. +............................................................ + + + igraph_real_t igraph_sparsemat_min(igraph_sparsemat_t *A); + + *Arguments:. * + +‘A’: + The input matrix, column-compressed. + + *Returns:. * + +‘’ + The minimum in the input matrix, or ‘IGRAPH_INFINITY’ if the matrix + has zero elements. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_max --- Maximum of a sparse matrix_, Next: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_, Prev: igraph_sparsemat_min --- Minimum of a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.12 igraph_sparsemat_max -- Maximum of a sparse matrix. +............................................................ + + + igraph_real_t igraph_sparsemat_max(igraph_sparsemat_t *A); + + *Arguments:. * + +‘A’: + The input matrix, column-compressed. + + *Returns:. * + +‘’ + The maximum in the input matrix, or ‘-IGRAPH_INFINITY’ if the + matrix has zero elements. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_, Next: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_, Prev: igraph_sparsemat_max --- Maximum of a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.13 igraph_sparsemat_minmax -- Minimum and maximum of a sparse matrix. +........................................................................... + + + igraph_error_t igraph_sparsemat_minmax(igraph_sparsemat_t *A, + igraph_real_t *min, igraph_real_t *max); + + *Arguments:. * + +‘A’: + The input matrix, column-compressed. + +‘min’: + The minimum in the input matrix is stored here, or + ‘IGRAPH_INFINITY’ if the matrix has zero elements. + +‘max’: + The maximum in the input matrix is stored here, or + ‘-IGRAPH_INFINITY’ if the matrix has zero elements. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_, Next: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_, Prev: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.14 igraph_sparsemat_count_nonzero -- Counts nonzero elements of a sparse matrix. +...................................................................................... + + + igraph_int_t igraph_sparsemat_count_nonzero(igraph_sparsemat_t *A); + + *Arguments:. * + +‘A’: + The input matrix, column-compressed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_, Next: igraph_sparsemat_rowsums --- Row-wise sums_, Prev: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_, Up: Query properties of a sparse matrix + +7.4.3.15 igraph_sparsemat_count_nonzerotol -- Counts nonzero elements of a sparse matrix, ignoring elements close to zero. +.......................................................................................................................... + + + igraph_int_t igraph_sparsemat_count_nonzerotol(igraph_sparsemat_t *A, + igraph_real_t tol); + + Count the number of matrix entries that are closer to zero than +‘tol’. + + *Arguments:. * + +‘A’: + The input matrix, column-compressed. + +‘tol’: + The tolerance for zero comparisons. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_rowsums --- Row-wise sums_, Next: igraph_sparsemat_colsums --- Column-wise sums_, Prev: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_, Up: Query properties of a sparse matrix + +7.4.3.16 igraph_sparsemat_rowsums -- Row-wise sums. +................................................... + + + igraph_error_t igraph_sparsemat_rowsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + +‘res’: + An initialized vector, the result is stored here. It will be + resized as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nz), the number of non-zero elements. + + +File: igraph-docs.info, Node: igraph_sparsemat_colsums --- Column-wise sums_, Next: igraph_sparsemat_nonzero_storage --- Returns number of stored entries of a sparse matrix_, Prev: igraph_sparsemat_rowsums --- Row-wise sums_, Up: Query properties of a sparse matrix + +7.4.3.17 igraph_sparsemat_colsums -- Column-wise sums. +...................................................... + + + igraph_error_t igraph_sparsemat_colsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + +‘res’: + An initialized vector, the result is stored here. It will be + resized as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nz) for triplet matrices, O(nz+n) for +column-compressed ones, nz is the number of non-zero elements, n is the +number of columns. + + +File: igraph-docs.info, Node: igraph_sparsemat_nonzero_storage --- Returns number of stored entries of a sparse matrix_, Prev: igraph_sparsemat_colsums --- Column-wise sums_, Up: Query properties of a sparse matrix + +7.4.3.18 igraph_sparsemat_nonzero_storage -- Returns number of stored entries of a sparse matrix. +................................................................................................. + + + igraph_int_t igraph_sparsemat_nonzero_storage(const igraph_sparsemat_t *A); + + This function will return the number of stored entries of a sparse +matrix. These entries can be zero, and multiple entries can be at the +same position. Use ‘igraph_sparsemat_dupl()’ (*note +igraph_sparsemat_dupl --- Removes duplicate elements from a sparse +matrix_::) to sum duplicate entries, and ‘igraph_sparsemat_dropzeros()’ +(*note igraph_sparsemat_dropzeros --- Drops the zero elements from a +sparse matrix_::) to remove zeros. + + *Arguments:. * + +‘A’: + A sparse matrix in either triplet or compressed form. + + *Returns:. * + +‘’ + Number of stored entries. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Operations on sparse matrices, Next: Operations on sparse matrix iterators, Prev: Query properties of a sparse matrix, Up: Sparse matrices + +7.4.4 Operations on sparse matrices +----------------------------------- + +* Menu: + +* igraph_sparsemat_entry -- Adds an element to a sparse matrix.: igraph_sparsemat_entry --- Adds an element to a sparse matrix_. +* igraph_sparsemat_fkeep -- Filters the elements of a sparse matrix.: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_. +* igraph_sparsemat_dropzeros -- Drops the zero elements from a sparse matrix.: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_. +* igraph_sparsemat_droptol -- Drops the almost zero elements from a sparse matrix.: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_. +* igraph_sparsemat_scale -- Scales a sparse matrix.: igraph_sparsemat_scale --- Scales a sparse matrix_. +* igraph_sparsemat_permute -- Permutes the rows and columns of a sparse matrix.: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_. +* igraph_sparsemat_transpose -- Transposes a sparse matrix.: igraph_sparsemat_transpose --- Transposes a sparse matrix_. +* igraph_sparsemat_add -- Sum of two sparse matrices.: igraph_sparsemat_add --- Sum of two sparse matrices_. +* igraph_sparsemat_multiply -- Matrix multiplication.: igraph_sparsemat_multiply --- Matrix multiplication_. +* igraph_sparsemat_gaxpy -- Matrix-vector product, added to another vector.: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_. +* igraph_sparsemat_add_rows -- Adds rows to a sparse matrix.: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_. +* igraph_sparsemat_add_cols -- Adds columns to a sparse matrix.: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_. +* igraph_sparsemat_resize -- Resizes a sparse matrix and clears all the elements.: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_. +* igraph_sparsemat_sort -- Sorts all elements of a sparse matrix by row and column indices.: igraph_sparsemat_sort --- Sorts all elements of a sparse matrix by row and column indices_. + + +File: igraph-docs.info, Node: igraph_sparsemat_entry --- Adds an element to a sparse matrix_, Next: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.1 igraph_sparsemat_entry -- Adds an element to a sparse matrix. +..................................................................... + + + igraph_error_t igraph_sparsemat_entry(igraph_sparsemat_t *A, + igraph_int_t row, igraph_int_t col, igraph_real_t elem); + + This function can be used to add the entries to a sparse matrix, +after initializing it with ‘igraph_sparsemat_init()’ (*note +igraph_sparsemat_init --- Initializes a sparse matrix; in triplet +format_::). If you add multiple entries in the same position, they will +all be saved, and the resulting value is the sum of all entries in that +position. + + *Arguments:. * + +‘A’: + The input matrix, it must be in triplet format. + +‘row’: + The row index of the entry to add. + +‘col’: + The column index of the entry to add. + +‘elem’: + The value of the entry. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) on average. + + +File: igraph-docs.info, Node: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_, Next: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_, Prev: igraph_sparsemat_entry --- Adds an element to a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.2 igraph_sparsemat_fkeep -- Filters the elements of a sparse matrix. +.......................................................................... + + + igraph_error_t igraph_sparsemat_fkeep( + igraph_sparsemat_t *A, + igraph_int_t (*fkeep)(igraph_int_t, igraph_int_t, igraph_real_t, void*), + void *other + ); + + This function can be used to filter the (non-zero) elements of a +sparse matrix. For all entries, it calls the supplied function and +depending on the return values either keeps, or deleted the element from +the matrix. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘fkeep’: + The filter function. It must take four arguments: the first is an + ‘igraph_int_t’, the row index of the entry, the second is another + ‘igraph_int_t’, the column index. The third is ‘igraph_real_t’, + the value of the entry. The fourth element is a ‘void’ pointer, + the ‘other’ argument is passed here. The function must return an + ‘int’. If this is zero, then the entry is deleted, otherwise it is + kept. + +‘other’: + A ‘void’ pointer that is passed to the filtering function. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_, Next: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_, Prev: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.3 igraph_sparsemat_dropzeros -- Drops the zero elements from a sparse matrix. +................................................................................... + + + igraph_error_t igraph_sparsemat_dropzeros(igraph_sparsemat_t *A); + + As a result of matrix operations, some of the entries in a sparse +matrix might be zero. This function removes these entries. + + *Arguments:. * + +‘A’: + The input matrix, it must be in column-compressed format. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_, Next: igraph_sparsemat_scale --- Scales a sparse matrix_, Prev: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.4 igraph_sparsemat_droptol -- Drops the almost zero elements from a sparse matrix. +........................................................................................ + + + igraph_error_t igraph_sparsemat_droptol(igraph_sparsemat_t *A, igraph_real_t tol); + + This function is similar to ‘igraph_sparsemat_dropzeros()’ (*note +igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse +matrix_::), but it also drops entries that are closer to zero than the +given tolerance threshold. + + *Arguments:. * + +‘A’: + The input matrix, it must be in column-compressed format. + +‘tol’: + Real number, giving the tolerance threshold. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_scale --- Scales a sparse matrix_, Next: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_, Prev: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.5 igraph_sparsemat_scale -- Scales a sparse matrix. +......................................................... + + + igraph_error_t igraph_sparsemat_scale(igraph_sparsemat_t *A, igraph_real_t by); + + Multiplies all elements of a sparse matrix, by the given factor. + + *Arguments:. * + +‘A’: + The input matrix. + +‘by’: + The scaling factor. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nz), the number of non-zero elements in the +matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_, Next: igraph_sparsemat_transpose --- Transposes a sparse matrix_, Prev: igraph_sparsemat_scale --- Scales a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.6 igraph_sparsemat_permute -- Permutes the rows and columns of a sparse matrix. +..................................................................................... + + + igraph_error_t igraph_sparsemat_permute(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res); + + *Arguments:. * + +‘A’: + The input matrix, it must be in column-compressed format. + +‘p’: + Integer vector, giving the permutation of the rows. + +‘q’: + Integer vector, the permutation of the columns. + +‘res’: + Pointer to an uninitialized sparse matrix, the result is stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(m+n+nz), the number of rows plus the number of +columns plus the number of non-zero elements in the matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_transpose --- Transposes a sparse matrix_, Next: igraph_sparsemat_add --- Sum of two sparse matrices_, Prev: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.7 igraph_sparsemat_transpose -- Transposes a sparse matrix. +................................................................. + + + igraph_error_t igraph_sparsemat_transpose( + const igraph_sparsemat_t *A, igraph_sparsemat_t *res + ); + + *Arguments:. * + +‘A’: + The input matrix, column-compressed or triple format. + +‘res’: + Pointer to an uninitialized sparse matrix, the result is stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_add --- Sum of two sparse matrices_, Next: igraph_sparsemat_multiply --- Matrix multiplication_, Prev: igraph_sparsemat_transpose --- Transposes a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.8 igraph_sparsemat_add -- Sum of two sparse matrices. +........................................................... + + + igraph_error_t igraph_sparsemat_add(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_real_t alpha, + igraph_real_t beta, + igraph_sparsemat_t *res); + + *Arguments:. * + +‘A’: + The first input matrix, in column-compressed format. + +‘B’: + The second input matrix, in column-compressed format. + +‘alpha’: + Real value, ‘A’ is multiplied by ‘alpha’ before the addition. + +‘beta’: + Real value, ‘B’ is multiplied by ‘beta’ before the addition. + +‘res’: + Pointer to an uninitialized sparse matrix, the result is stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_multiply --- Matrix multiplication_, Next: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_, Prev: igraph_sparsemat_add --- Sum of two sparse matrices_, Up: Operations on sparse matrices + +7.4.4.9 igraph_sparsemat_multiply -- Matrix multiplication. +........................................................... + + + igraph_error_t igraph_sparsemat_multiply(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_sparsemat_t *res); + + Multiplies two sparse matrices. + + *Arguments:. * + +‘A’: + The first input matrix (left hand side), in column-compressed + format. + +‘B’: + The second input matrix (right hand side), in column-compressed + format. + +‘res’: + Pointer to an uninitialized sparse matrix, the result is stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_, Next: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_, Prev: igraph_sparsemat_multiply --- Matrix multiplication_, Up: Operations on sparse matrices + +7.4.4.10 igraph_sparsemat_gaxpy -- Matrix-vector product, added to another vector. +.................................................................................. + + + igraph_error_t igraph_sparsemat_gaxpy(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + igraph_vector_t *res); + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘x’: + The input vector, its size must match the number of columns in ‘A’. + +‘res’: + This vector is added to the matrix-vector product and it is + overwritten by the result. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_, Next: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_, Prev: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_, Up: Operations on sparse matrices + +7.4.4.11 igraph_sparsemat_add_rows -- Adds rows to a sparse matrix. +................................................................... + + + igraph_error_t igraph_sparsemat_add_rows(igraph_sparsemat_t *A, igraph_int_t n); + + The current matrix elements are retained and all elements in the new +rows are zero. + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + +‘n’: + The number of rows to add. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_, Next: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_, Prev: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.12 igraph_sparsemat_add_cols -- Adds columns to a sparse matrix. +...................................................................... + + + igraph_error_t igraph_sparsemat_add_cols(igraph_sparsemat_t *A, igraph_int_t n); + + The current matrix elements are retained, and all elements in the new +columns are zero. + + *Arguments:. * + +‘A’: + The input matrix, in triplet or column-compressed format. + +‘n’: + The number of columns to add. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_, Next: igraph_sparsemat_sort --- Sorts all elements of a sparse matrix by row and column indices_, Prev: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_, Up: Operations on sparse matrices + +7.4.4.13 igraph_sparsemat_resize -- Resizes a sparse matrix and clears all the elements. +........................................................................................ + + + igraph_error_t igraph_sparsemat_resize(igraph_sparsemat_t *A, igraph_int_t nrow, + igraph_int_t ncol, igraph_int_t nzmax); + + This function resizes a sparse matrix. The resized sparse matrix +will become empty, even if it contained nonzero entries. + + *Arguments:. * + +‘A’: + The initialized sparse matrix to resize. + +‘nrow’: + The new number of rows. + +‘ncol’: + The new number of columns. + +‘nzmax’: + The new maximum number of elements. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nzmax), the maximum number of non-zero elements. + + +File: igraph-docs.info, Node: igraph_sparsemat_sort --- Sorts all elements of a sparse matrix by row and column indices_, Prev: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_, Up: Operations on sparse matrices + +7.4.4.14 igraph_sparsemat_sort -- Sorts all elements of a sparse matrix by row and column indices. +.................................................................................................. + + + igraph_error_t igraph_sparsemat_sort(const igraph_sparsemat_t *A, + igraph_sparsemat_t *sorted); + + This function will sort the elements of a sparse matrix such that +iterating over the entries will return them sorted by column indices; +elements in the same column are then sorted by row indices. + + *Arguments:. * + +‘A’: + A sparse matrix in either triplet or compressed form. + +‘sorted’: + An uninitialized sparse matrix; the result will be returned here. + The result will be in triplet form if the input was in triplet + form, otherwise it will be in compressed form. Note that sorting + is more efficient when the matrix is already in compressed form. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO + + +File: igraph-docs.info, Node: Operations on sparse matrix iterators, Next: Operations that change the internal representation, Prev: Operations on sparse matrices, Up: Sparse matrices + +7.4.5 Operations on sparse matrix iterators +------------------------------------------- + +* Menu: + +* igraph_sparsemat_iterator_init -- Initialize a sparse matrix iterator.: igraph_sparsemat_iterator_init --- Initialize a sparse matrix iterator_. +* igraph_sparsemat_iterator_reset -- Reset a sparse matrix iterator to the first element.: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_. +* igraph_sparsemat_iterator_end -- Query if the iterator is past the last element.: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_. +* igraph_sparsemat_iterator_row -- Return the row of the iterator.: igraph_sparsemat_iterator_row --- Return the row of the iterator_. +* igraph_sparsemat_iterator_col -- Return the column of the iterator.: igraph_sparsemat_iterator_col --- Return the column of the iterator_. +* igraph_sparsemat_iterator_get -- Return the element at the current iterator position.: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_. +* igraph_sparsemat_iterator_next -- Let a sparse matrix iterator go to the next element.: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_. +* igraph_sparsemat_iterator_idx -- Returns the element vector index of a sparse matrix iterator.: igraph_sparsemat_iterator_idx --- Returns the element vector index of a sparse matrix iterator_. + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_init --- Initialize a sparse matrix iterator_, Next: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_, Up: Operations on sparse matrix iterators + +7.4.5.1 igraph_sparsemat_iterator_init -- Initialize a sparse matrix iterator. +.............................................................................. + + + igraph_error_t igraph_sparsemat_iterator_init( + igraph_sparsemat_iterator_t *it, const igraph_sparsemat_t *sparsemat + ); + + *Arguments:. * + +‘it’: + A pointer to an uninitialized sparse matrix iterator. + +‘sparsemat’: + Pointer to the sparse matrix. + + *Returns:. * + +‘’ + Error code. This will always return ‘IGRAPH_SUCCESS’ + + Time complexity: O(n), the number of columns of the sparse matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_, Next: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_, Prev: igraph_sparsemat_iterator_init --- Initialize a sparse matrix iterator_, Up: Operations on sparse matrix iterators + +7.4.5.2 igraph_sparsemat_iterator_reset -- Reset a sparse matrix iterator to the first element. +............................................................................................... + + + igraph_error_t igraph_sparsemat_iterator_reset(igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + Error code. This will always return ‘IGRAPH_SUCCESS’ + + Time complexity: O(n), the number of columns of the sparse matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_, Next: igraph_sparsemat_iterator_row --- Return the row of the iterator_, Prev: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_, Up: Operations on sparse matrix iterators + +7.4.5.3 igraph_sparsemat_iterator_end -- Query if the iterator is past the last element. +........................................................................................ + + + igraph_bool_t + igraph_sparsemat_iterator_end(const igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + true if the iterator is past the last element, false if it points + to an element in a sparse matrix. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_row --- Return the row of the iterator_, Next: igraph_sparsemat_iterator_col --- Return the column of the iterator_, Prev: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_, Up: Operations on sparse matrix iterators + +7.4.5.4 igraph_sparsemat_iterator_row -- Return the row of the iterator. +........................................................................ + + + igraph_int_t igraph_sparsemat_iterator_row(const igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + The row of the element at the current iterator position. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_col --- Return the column of the iterator_, Next: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_, Prev: igraph_sparsemat_iterator_row --- Return the row of the iterator_, Up: Operations on sparse matrix iterators + +7.4.5.5 igraph_sparsemat_iterator_col -- Return the column of the iterator. +........................................................................... + + + igraph_int_t igraph_sparsemat_iterator_col(const igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + The column of the element at the current iterator position. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_, Next: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_, Prev: igraph_sparsemat_iterator_col --- Return the column of the iterator_, Up: Operations on sparse matrix iterators + +7.4.5.6 igraph_sparsemat_iterator_get -- Return the element at the current iterator position. +............................................................................................. + + + igraph_real_t + igraph_sparsemat_iterator_get(const igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + The value of the element at the current iterator position. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_, Next: igraph_sparsemat_iterator_idx --- Returns the element vector index of a sparse matrix iterator_, Prev: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_, Up: Operations on sparse matrix iterators + +7.4.5.7 igraph_sparsemat_iterator_next -- Let a sparse matrix iterator go to the next element. +.............................................................................................. + + + igraph_int_t igraph_sparsemat_iterator_next(igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + The position of the iterator in the element vector. + + Time complexity: O(n), the number of columns of the sparse matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_iterator_idx --- Returns the element vector index of a sparse matrix iterator_, Prev: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_, Up: Operations on sparse matrix iterators + +7.4.5.8 igraph_sparsemat_iterator_idx -- Returns the element vector index of a sparse matrix iterator. +...................................................................................................... + + + igraph_int_t igraph_sparsemat_iterator_idx(const igraph_sparsemat_iterator_t *it); + + *Arguments:. * + +‘it’: + A pointer to the sparse matrix iterator. + + *Returns:. * + +‘’ + The position of the iterator in the element vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Operations that change the internal representation, Next: Decompositions and solving linear systems, Prev: Operations on sparse matrix iterators, Up: Sparse matrices + +7.4.6 Operations that change the internal representation +-------------------------------------------------------- + +* Menu: + +* igraph_sparsemat_compress -- Converts a sparse matrix to column-compressed format.: igraph_sparsemat_compress --- Converts a sparse matrix to column-compressed format_. +* igraph_sparsemat_dupl -- Removes duplicate elements from a sparse matrix.: igraph_sparsemat_dupl --- Removes duplicate elements from a sparse matrix_. + + +File: igraph-docs.info, Node: igraph_sparsemat_compress --- Converts a sparse matrix to column-compressed format_, Next: igraph_sparsemat_dupl --- Removes duplicate elements from a sparse matrix_, Up: Operations that change the internal representation + +7.4.6.1 igraph_sparsemat_compress -- Converts a sparse matrix to column-compressed format. +.......................................................................................... + + + igraph_error_t igraph_sparsemat_compress(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res); + + Converts a sparse matrix from triplet format to column-compressed +format. Almost all sparse matrix operations require that the matrix is +in column-compressed format. + + *Arguments:. * + +‘A’: + The input matrix, it must be in triplet format. + +‘res’: + Pointer to an uninitialized sparse matrix object, the compressed + version of ‘A’ is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nz) where ‘nz’ is the number of non-zero elements. + + +File: igraph-docs.info, Node: igraph_sparsemat_dupl --- Removes duplicate elements from a sparse matrix_, Prev: igraph_sparsemat_compress --- Converts a sparse matrix to column-compressed format_, Up: Operations that change the internal representation + +7.4.6.2 igraph_sparsemat_dupl -- Removes duplicate elements from a sparse matrix. +................................................................................. + + + igraph_error_t igraph_sparsemat_dupl(igraph_sparsemat_t *A); + + It is possible that a column-compressed sparse matrix stores a single +matrix entry in multiple pieces. The entry is then the sum of all its +pieces. (Some functions create matrices like this.) This function +eliminates the multiple pieces. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Decompositions and solving linear systems, Next: Eigenvalues and eigenvectors, Prev: Operations that change the internal representation, Up: Sparse matrices + +7.4.7 Decompositions and solving linear systems +----------------------------------------------- + +* Menu: + +* igraph_sparsemat_symblu -- Symbolic LU decomposition.: igraph_sparsemat_symblu --- Symbolic LU decomposition_. +* igraph_sparsemat_symbqr -- Symbolic QR decomposition.: igraph_sparsemat_symbqr --- Symbolic QR decomposition_. +* igraph_sparsemat_lsolve -- Solves a lower-triangular linear system.: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_. +* igraph_sparsemat_ltsolve -- Solves an upper-triangular linear system.: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_. +* igraph_sparsemat_usolve -- Solves an upper-triangular linear system.: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_. +* igraph_sparsemat_utsolve -- Solves a lower-triangular linear system.: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_. +* igraph_sparsemat_cholsol -- Solves a symmetric linear system via Cholesky decomposition.: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_. +* igraph_sparsemat_lusol -- Solves a linear system via LU decomposition.: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_. +* igraph_sparsemat_lu -- LU decomposition of a sparse matrix.: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_. +* igraph_sparsemat_qr -- QR decomposition of a sparse matrix.: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_. +* igraph_sparsemat_luresol -- Solves a linear system using a precomputed LU decomposition.: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_. +* igraph_sparsemat_qrresol -- Solves a linear system using a precomputed QR decomposition.: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_. +* igraph_sparsemat_symbolic_destroy -- Deallocates memory after a symbolic decomposition.: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_. +* igraph_sparsemat_numeric_destroy -- Deallocates memory after a numeric decomposition.: igraph_sparsemat_numeric_destroy --- Deallocates memory after a numeric decomposition_. + + +File: igraph-docs.info, Node: igraph_sparsemat_symblu --- Symbolic LU decomposition_, Next: igraph_sparsemat_symbqr --- Symbolic QR decomposition_, Up: Decompositions and solving linear systems + +7.4.7.1 igraph_sparsemat_symblu -- Symbolic LU decomposition. +............................................................. + + + igraph_error_t igraph_sparsemat_symblu(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + + LU decomposition of sparse matrices involves two steps, the first is +calling this function, and then ‘igraph_sparsemat_lu()’ (*note +igraph_sparsemat_lu --- LU decomposition of a sparse matrix_::). + + *Arguments:. * + +‘order’: + The ordering to use: 0 means natural ordering, 1 means minimum + degree ordering of A+A', 2 is minimum degree ordering of A'A after + removing the dense rows from A, and 3 is the minimum degree + ordering of A'A. + +‘A’: + The input matrix, in column-compressed format. + +‘dis’: + The result of the symbolic analysis is stored here. Once not + needed anymore, it must be destroyed by calling + ‘igraph_sparsemat_symbolic_destroy()’ (*note + igraph_sparsemat_symbolic_destroy --- Deallocates memory after a + symbolic decomposition_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_symbqr --- Symbolic QR decomposition_, Next: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_, Prev: igraph_sparsemat_symblu --- Symbolic LU decomposition_, Up: Decompositions and solving linear systems + +7.4.7.2 igraph_sparsemat_symbqr -- Symbolic QR decomposition. +............................................................. + + + igraph_error_t igraph_sparsemat_symbqr(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + + QR decomposition of sparse matrices involves two steps, the first is +calling this function, and then ‘igraph_sparsemat_qr()’ (*note +igraph_sparsemat_qr --- QR decomposition of a sparse matrix_::). + + *Arguments:. * + +‘order’: + The ordering to use: 0 means natural ordering, 1 means minimum + degree ordering of A+A', 2 is minimum degree ordering of A'A after + removing the dense rows from A, and 3 is the minimum degree + ordering of A'A. + +‘A’: + The input matrix, in column-compressed format. + +‘dis’: + The result of the symbolic analysis is stored here. Once not + needed anymore, it must be destroyed by calling + ‘igraph_sparsemat_symbolic_destroy()’ (*note + igraph_sparsemat_symbolic_destroy --- Deallocates memory after a + symbolic decomposition_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_, Next: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_, Prev: igraph_sparsemat_symbqr --- Symbolic QR decomposition_, Up: Decompositions and solving linear systems + +7.4.7.3 igraph_sparsemat_lsolve -- Solves a lower-triangular linear system. +........................................................................... + + + igraph_error_t igraph_sparsemat_lsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res); + + Solve the Lx=b linear equation system, where the L coefficient matrix +is square and lower-triangular, with a zero-free diagonal. + + *Arguments:. * + +‘L’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side of the linear system. + +‘res’: + An initialized vector, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_, Next: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_, Prev: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_, Up: Decompositions and solving linear systems + +7.4.7.4 igraph_sparsemat_ltsolve -- Solves an upper-triangular linear system. +............................................................................. + + + igraph_error_t igraph_sparsemat_ltsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res); + + Solve the L'x=b linear equation system, where the L matrix is square +and lower-triangular, with a zero-free diagonal. + + *Arguments:. * + +‘L’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side of the linear system. + +‘res’: + An initialized vector, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_, Next: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_, Prev: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_, Up: Decompositions and solving linear systems + +7.4.7.5 igraph_sparsemat_usolve -- Solves an upper-triangular linear system. +............................................................................ + + + igraph_error_t igraph_sparsemat_usolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res); + + Solves the Ux=b upper triangular system. + + *Arguments:. * + +‘U’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side of the linear system. + +‘res’: + An initialized vector, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_, Next: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_, Prev: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_, Up: Decompositions and solving linear systems + +7.4.7.6 igraph_sparsemat_utsolve -- Solves a lower-triangular linear system. +............................................................................ + + + igraph_error_t igraph_sparsemat_utsolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res); + + This is the same as ‘igraph_sparsemat_usolve()’ (*note +igraph_sparsemat_usolve --- Solves an upper-triangular linear +system_::), but U'x=b is solved, where the apostrophe denotes the +transpose. + + *Arguments:. * + +‘U’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side of the linear system. + +‘res’: + An initialized vector, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_, Next: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_, Prev: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_, Up: Decompositions and solving linear systems + +7.4.7.7 igraph_sparsemat_cholsol -- Solves a symmetric linear system via Cholesky decomposition. +................................................................................................ + + + igraph_error_t igraph_sparsemat_cholsol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order); + + Solve Ax=b, where A is a symmetric positive definite matrix. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side. + +‘res’: + An initialized vector, the result is stored here. + +‘order’: + An integer giving the ordering method to use for the factorization. + Zero is the natural ordering; if it is one, then the fill-reducing + minimum-degree ordering of A+A' is used. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_, Next: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_, Prev: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_, Up: Decompositions and solving linear systems + +7.4.7.8 igraph_sparsemat_lusol -- Solves a linear system via LU decomposition. +.............................................................................. + + + igraph_error_t igraph_sparsemat_lusol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order, + igraph_real_t tol); + + Solve Ax=b, via LU factorization of A. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘b’: + The right hand side of the equation. + +‘res’: + An initialized vector, the result is stored here. + +‘order’: + The ordering method to use, zero means the natural ordering, one + means the fill-reducing minimum-degree ordering of A+A', two means + the ordering of A'*A, after removing the dense rows from A. Three + means the ordering of A'*A. + +‘tol’: + Real number, the tolerance limit to use for the numeric LU + factorization. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_, Next: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_, Prev: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_, Up: Decompositions and solving linear systems + +7.4.7.9 igraph_sparsemat_lu -- LU decomposition of a sparse matrix. +................................................................... + + + igraph_error_t igraph_sparsemat_lu(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din, double tol); + + Performs numeric sparse LU decomposition of a matrix. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘dis’: + The symbolic analysis for LU decomposition, coming from a call to + the ‘igraph_sparsemat_symblu()’ (*note igraph_sparsemat_symblu --- + Symbolic LU decomposition_::) function. + +‘din’: + The numeric decomposition, the result is stored here. It can be + used to solve linear systems with changing right hand side vectors, + by calling ‘igraph_sparsemat_luresol()’ (*note + igraph_sparsemat_luresol --- Solves a linear system using a + precomputed LU decomposition_::). Once not needed any more, it + must be destroyed by calling ‘igraph_sparsemat_symbolic_destroy()’ + (*note igraph_sparsemat_symbolic_destroy --- Deallocates memory + after a symbolic decomposition_::) on it. + +‘tol’: + The tolerance for the numeric LU decomposition. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_, Next: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_, Prev: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_, Up: Decompositions and solving linear systems + +7.4.7.10 igraph_sparsemat_qr -- QR decomposition of a sparse matrix. +.................................................................... + + + igraph_error_t igraph_sparsemat_qr(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din); + + Numeric QR decomposition of a sparse matrix. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed format. + +‘dis’: + The result of the symbolic QR analysis, from the function + ‘igraph_sparsemat_symbqr()’ (*note igraph_sparsemat_symbqr --- + Symbolic QR decomposition_::). + +‘din’: + The result of the decomposition is stored here, it can be used to + solve many linear systems with the same coefficient matrix and + changing right hand sides, using the ‘igraph_sparsemat_qrresol()’ + (*note igraph_sparsemat_qrresol --- Solves a linear system using a + precomputed QR decomposition_::) function. Once not needed any + more, one should call ‘igraph_sparsemat_numeric_destroy()’ (*note + igraph_sparsemat_numeric_destroy --- Deallocates memory after a + numeric decomposition_::) on it to free the allocated memory. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_, Next: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_, Prev: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_, Up: Decompositions and solving linear systems + +7.4.7.11 igraph_sparsemat_luresol -- Solves a linear system using a precomputed LU decomposition. +................................................................................................. + + + igraph_error_t igraph_sparsemat_luresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + + Uses the LU decomposition of a matrix to solve linear systems. + + *Arguments:. * + +‘dis’: + The symbolic analysis of the coefficient matrix, the result of + ‘igraph_sparsemat_symblu()’ (*note igraph_sparsemat_symblu --- + Symbolic LU decomposition_::). + +‘din’: + The LU decomposition, the result of a call to + ‘igraph_sparsemat_lu()’ (*note igraph_sparsemat_lu --- LU + decomposition of a sparse matrix_::). + +‘b’: + A vector that defines the right hand side of the linear equation + system. + +‘res’: + An initialized vector, the solution of the linear system is stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_, Next: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_, Prev: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_, Up: Decompositions and solving linear systems + +7.4.7.12 igraph_sparsemat_qrresol -- Solves a linear system using a precomputed QR decomposition. +................................................................................................. + + + igraph_error_t igraph_sparsemat_qrresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + + Solves a linear system using a QR decomposition of its coefficient +matrix. + + *Arguments:. * + +‘dis’: + Symbolic analysis of the coefficient matrix, the result of + ‘igraph_sparsemat_symbqr()’ (*note igraph_sparsemat_symbqr --- + Symbolic QR decomposition_::). + +‘din’: + The QR decomposition of the coefficient matrix, the result of + ‘igraph_sparsemat_qr()’ (*note igraph_sparsemat_qr --- QR + decomposition of a sparse matrix_::). + +‘b’: + Vector, giving the right hand side of the linear equation system. + +‘res’: + An initialized vector, the solution is stored here. It is resized + as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_, Next: igraph_sparsemat_numeric_destroy --- Deallocates memory after a numeric decomposition_, Prev: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_, Up: Decompositions and solving linear systems + +7.4.7.13 igraph_sparsemat_symbolic_destroy -- Deallocates memory after a symbolic decomposition. +................................................................................................ + + + void igraph_sparsemat_symbolic_destroy(igraph_sparsemat_symbolic_t *dis); + + Frees the memory allocated by ‘igraph_sparsemat_symbqr()’ (*note +igraph_sparsemat_symbqr --- Symbolic QR decomposition_::) or +‘igraph_sparsemat_symblu()’ (*note igraph_sparsemat_symblu --- Symbolic +LU decomposition_::). + + *Arguments:. * + +‘dis’: + The symbolic analysis. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_sparsemat_numeric_destroy --- Deallocates memory after a numeric decomposition_, Prev: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_, Up: Decompositions and solving linear systems + +7.4.7.14 igraph_sparsemat_numeric_destroy -- Deallocates memory after a numeric decomposition. +.............................................................................................. + + + void igraph_sparsemat_numeric_destroy(igraph_sparsemat_numeric_t *din); + + Frees the memoty allocated by ‘igraph_sparsemat_qr()’ (*note +igraph_sparsemat_qr --- QR decomposition of a sparse matrix_::) or +‘igraph_sparsemat_lu()’ (*note igraph_sparsemat_lu --- LU decomposition +of a sparse matrix_::). + + *Arguments:. * + +‘din’: + The LU or QR decomposition. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Eigenvalues and eigenvectors, Next: Conversion to other data types, Prev: Decompositions and solving linear systems, Up: Sparse matrices + +7.4.8 Eigenvalues and eigenvectors +---------------------------------- + +* Menu: + +* igraph_sparsemat_arpack_rssolve -- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK.: igraph_sparsemat_arpack_rssolve --- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK_. +* igraph_sparsemat_arpack_rnsolve -- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK.: igraph_sparsemat_arpack_rnsolve --- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK_. + + +File: igraph-docs.info, Node: igraph_sparsemat_arpack_rssolve --- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK_, Next: igraph_sparsemat_arpack_rnsolve --- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK_, Up: Eigenvalues and eigenvectors + +7.4.8.1 igraph_sparsemat_arpack_rssolve -- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK. +................................................................................................................ + + + igraph_error_t igraph_sparsemat_arpack_rssolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_sparsemat_solve_t solvemethod); + + *Arguments:. * + +‘A’: + The input matrix, must be column-compressed. + +‘options’: + It is passed to ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::). + Supply ‘NULL’ here to use the defaults. See + ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- + Options for ARPACK_::) for the details. If ‘mode’ is 1, then + ARPACK uses regular mode, if ‘mode’ is 3, then shift and invert + mode is used and the ‘sigma’ structure member defines the shift. + +‘storage’: + Storage for ARPACK. See ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::) + and ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::) for details. + +‘values’: + An initialized vector or a null pointer, the eigenvalues are stored + here. + +‘vectors’: + An initialised matrix, or a null pointer, the eigenvectors are + stored here, in the columns. + +‘solvemethod’: + The method to solve the linear system, if ‘mode’ is 3, i.e. the + shift and invert mode is used. Possible values: + + ‘IGRAPH_SPARSEMAT_SOLVE_LU’ + The linear system is solved using LU decomposition. + + ‘IGRAPH_SPARSEMAT_SOLVE_QR’ + The linear system is solved using QR decomposition. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_sparsemat_arpack_rnsolve --- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK_, Prev: igraph_sparsemat_arpack_rssolve --- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK_, Up: Eigenvalues and eigenvectors + +7.4.8.2 igraph_sparsemat_arpack_rnsolve -- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK. +................................................................................................................... + + + igraph_error_t igraph_sparsemat_arpack_rnsolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, + igraph_matrix_t *vectors); + + Eigenvalues and/or eigenvectors of a nonsymmetric sparse matrix. + + *Arguments:. * + +‘A’: + The input matrix, in column-compressed mode. + +‘options’: + ARPACK options, it is passed to ‘igraph_arpack_rnsolve()’ (*note + igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::). Supply ‘NULL’ here to use the defaults. See also + ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- + Options for ARPACK_::) for details. + +‘storage’: + Storage for ARPACK, this is passed to ‘igraph_arpack_rnsolve()’ + (*note igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::). See ‘igraph_arpack_storage_t’ (*note + igraph_arpack_storage_t --- Storage for ARPACK_::) for details. + +‘values’: + An initialized matrix, or a null pointer. If not a null pointer, + then the eigenvalues are stored here, the first column is the real + part, the second column is the imaginary part. + +‘vectors’: + An initialized matrix, or a null pointer. If not a null pointer, + then the eigenvectors are stored here, please see + ‘igraph_arpack_rnsolve()’ (*note igraph_arpack_rnsolve --- ARPACK + solver for non-symmetric matrices_::) for the format. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Conversion to other data types, Next: Writing to a file; or to the screen, Prev: Eigenvalues and eigenvectors, Up: Sparse matrices + +7.4.9 Conversion to other data types +------------------------------------ + +* Menu: + +* igraph_matrix_as_sparsemat -- Converts a dense matrix to a sparse matrix.: igraph_matrix_as_sparsemat --- Converts a dense matrix to a sparse matrix_. +* igraph_sparsemat_as_matrix -- Converts a sparse matrix to a dense matrix.: igraph_sparsemat_as_matrix --- Converts a sparse matrix to a dense matrix_. + + +File: igraph-docs.info, Node: igraph_matrix_as_sparsemat --- Converts a dense matrix to a sparse matrix_, Next: igraph_sparsemat_as_matrix --- Converts a sparse matrix to a dense matrix_, Up: Conversion to other data types + +7.4.9.1 igraph_matrix_as_sparsemat -- Converts a dense matrix to a sparse matrix. +................................................................................. + + + igraph_error_t igraph_matrix_as_sparsemat(igraph_sparsemat_t *res, + const igraph_matrix_t *mat, + igraph_real_t tol); + + *Arguments:. * + +‘res’: + An uninitialized sparse matrix, the result is stored here. + +‘mat’: + The dense input matrix. + +‘tol’: + The tolerance for zero comparisons. Values closer than ‘tol’ to + zero are considered as zero, and will not be included in the sparse + matrix. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_sparsemat_as_matrix()’ (*note igraph_sparsemat_as_matrix + --- Converts a sparse matrix to a dense matrix_::) for the reverse + conversion. + + Time complexity: O(mn), the number of elements in the dense matrix. + + +File: igraph-docs.info, Node: igraph_sparsemat_as_matrix --- Converts a sparse matrix to a dense matrix_, Prev: igraph_matrix_as_sparsemat --- Converts a dense matrix to a sparse matrix_, Up: Conversion to other data types + +7.4.9.2 igraph_sparsemat_as_matrix -- Converts a sparse matrix to a dense matrix. +................................................................................. + + + igraph_error_t igraph_sparsemat_as_matrix(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat); + + *Arguments:. * + +‘res’: + Pointer to an initialized matrix, the result is stored here. It + will be resized to the required size. + +‘spmat’: + The input sparse matrix, in triplet or column-compressed format. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_matrix_as_sparsemat()’ (*note igraph_matrix_as_sparsemat + --- Converts a dense matrix to a sparse matrix_::) for the reverse + conversion. + + Time complexity: O(mn), the number of elements in the dense matrix. + + +File: igraph-docs.info, Node: Writing to a file; or to the screen, Prev: Conversion to other data types, Up: Sparse matrices + +7.4.10 Writing to a file, or to the screen +------------------------------------------ + +* Menu: + +* igraph_sparsemat_print -- Prints a sparse matrix to a file.: igraph_sparsemat_print --- Prints a sparse matrix to a file_. + + +File: igraph-docs.info, Node: igraph_sparsemat_print --- Prints a sparse matrix to a file_, Up: Writing to a file; or to the screen + +7.4.10.1 igraph_sparsemat_print -- Prints a sparse matrix to a file. +.................................................................... + + + igraph_error_t igraph_sparsemat_print(const igraph_sparsemat_t *A, + FILE *outstream); + + Only the non-zero entries are printed. This function serves more as +a debugging utility, as currently there is no function that could read +back the printed matrix from the file. + + *Arguments:. * + +‘A’: + The input matrix, triplet or column-compressed format. + +‘outstream’: + The stream to print it to. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(nz) for triplet matrices, O(n+nz) for +column-compressed matrices. nz is the number of non-zero elements, n is +the number columns in the matrix. + + +File: igraph-docs.info, Node: Stacks, Next: Double-ended queues, Prev: Sparse matrices, Up: Data structure library; vector; matrix; other data types + +7.5 Stacks +========== + +* Menu: + +* igraph_stack_init -- Initializes a stack.: igraph_stack_init --- Initializes a stack_. +* igraph_stack_destroy -- Destroys a stack object.: igraph_stack_destroy --- Destroys a stack object_. +* igraph_stack_reserve -- Reserve memory.: igraph_stack_reserve --- Reserve memory_. +* igraph_stack_empty -- Decides whether a stack object is empty.: igraph_stack_empty --- Decides whether a stack object is empty_. +* igraph_stack_size -- Returns the number of elements in a stack.: igraph_stack_size --- Returns the number of elements in a stack_. +* igraph_stack_clear -- Removes all elements from a stack.: igraph_stack_clear --- Removes all elements from a stack_. +* igraph_stack_push -- Places an element on the top of a stack.: igraph_stack_push --- Places an element on the top of a stack_. +* igraph_stack_pop -- Removes and returns an element from the top of a stack.: igraph_stack_pop --- Removes and returns an element from the top of a stack_. +* igraph_stack_top -- Query top element.: igraph_stack_top --- Query top element_. + + +File: igraph-docs.info, Node: igraph_stack_init --- Initializes a stack_, Next: igraph_stack_destroy --- Destroys a stack object_, Up: Stacks + +7.5.1 igraph_stack_init -- Initializes a stack. +----------------------------------------------- + + + igraph_error_t igraph_stack_init(igraph_stack_t* s, igraph_int_t capacity); + + The initialized stack is always empty. + + *Arguments:. * + +‘s’: + Pointer to an uninitialized stack. + +‘capacity’: + The number of elements to allocate memory for. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(‘size’). + + +File: igraph-docs.info, Node: igraph_stack_destroy --- Destroys a stack object_, Next: igraph_stack_reserve --- Reserve memory_, Prev: igraph_stack_init --- Initializes a stack_, Up: Stacks + +7.5.2 igraph_stack_destroy -- Destroys a stack object. +------------------------------------------------------ + + + void igraph_stack_destroy(igraph_stack_t* s); + + Deallocate the memory used for a stack. It is possible to +reinitialize a destroyed stack again by ‘igraph_stack_init()’ (*note +igraph_stack_init --- Initializes a stack_::). + + *Arguments:. * + +‘s’: + The stack to destroy. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_stack_reserve --- Reserve memory_, Next: igraph_stack_empty --- Decides whether a stack object is empty_, Prev: igraph_stack_destroy --- Destroys a stack object_, Up: Stacks + +7.5.3 igraph_stack_reserve -- Reserve memory. +--------------------------------------------- + + + igraph_error_t igraph_stack_reserve(igraph_stack_t* s, igraph_int_t capacity); + + Reserve memory for future use. The actual size of the stack is +unchanged. + + *Arguments:. * + +‘s’: + The stack object. + +‘size’: + The number of elements to reserve memory for. If it is not bigger + than the current size then nothing happens. + + *Returns:. * + +‘’ + Error code. + + Time complexity: should be around O(n), the new allocated size of the +stack. + + +File: igraph-docs.info, Node: igraph_stack_empty --- Decides whether a stack object is empty_, Next: igraph_stack_size --- Returns the number of elements in a stack_, Prev: igraph_stack_reserve --- Reserve memory_, Up: Stacks + +7.5.4 igraph_stack_empty -- Decides whether a stack object is empty. +-------------------------------------------------------------------- + + + igraph_bool_t igraph_stack_empty(igraph_stack_t* s); + + *Arguments:. * + +‘s’: + The stack object. + + *Returns:. * + +‘’ + Boolean, ‘true’ if the stack is empty, ‘false’ otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_stack_size --- Returns the number of elements in a stack_, Next: igraph_stack_clear --- Removes all elements from a stack_, Prev: igraph_stack_empty --- Decides whether a stack object is empty_, Up: Stacks + +7.5.5 igraph_stack_size -- Returns the number of elements in a stack. +--------------------------------------------------------------------- + + + igraph_int_t igraph_stack_size(const igraph_stack_t* s); + + *Arguments:. * + +‘s’: + The stack object. + + *Returns:. * + +‘’ + The number of elements in the stack. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_stack_clear --- Removes all elements from a stack_, Next: igraph_stack_push --- Places an element on the top of a stack_, Prev: igraph_stack_size --- Returns the number of elements in a stack_, Up: Stacks + +7.5.6 igraph_stack_clear -- Removes all elements from a stack. +-------------------------------------------------------------- + + + void igraph_stack_clear(igraph_stack_t* s); + + *Arguments:. * + +‘s’: + The stack object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_stack_push --- Places an element on the top of a stack_, Next: igraph_stack_pop --- Removes and returns an element from the top of a stack_, Prev: igraph_stack_clear --- Removes all elements from a stack_, Up: Stacks + +7.5.7 igraph_stack_push -- Places an element on the top of a stack. +------------------------------------------------------------------- + + + igraph_error_t igraph_stack_push(igraph_stack_t* s, igraph_real_t elem); + + The capacity of the stack is increased, if needed. + + *Arguments:. * + +‘s’: + The stack object. + +‘elem’: + The element to push. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) is no reallocation is needed, O(n) otherwise, +but it is ensured that n push operations are performed in O(n) time. + + +File: igraph-docs.info, Node: igraph_stack_pop --- Removes and returns an element from the top of a stack_, Next: igraph_stack_top --- Query top element_, Prev: igraph_stack_push --- Places an element on the top of a stack_, Up: Stacks + +7.5.8 igraph_stack_pop -- Removes and returns an element from the top of a stack. +--------------------------------------------------------------------------------- + + + igraph_real_t igraph_stack_pop(igraph_stack_t* s); + + The stack must contain at least one element, call +‘igraph_stack_empty()’ (*note igraph_stack_empty --- Decides whether a +stack object is empty_::) to make sure of this. + + *Arguments:. * + +‘s’: + The stack object. + + *Returns:. * + +‘’ + The removed top element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_stack_top --- Query top element_, Prev: igraph_stack_pop --- Removes and returns an element from the top of a stack_, Up: Stacks + +7.5.9 igraph_stack_top -- Query top element. +-------------------------------------------- + + + igraph_real_t igraph_stack_top(const igraph_stack_t* s); + + Returns the top element of the stack, without removing it. The stack +must be non-empty. + + *Arguments:. * + +‘s’: + The stack. + + *Returns:. * + +‘’ + The top element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Double-ended queues, Next: Maximum and minimum heaps, Prev: Stacks, Up: Data structure library; vector; matrix; other data types + +7.6 Double-ended queues +======================= + +This is the classic data type of the double ended queue. Most of the +time it is used if a First-In-First-Out (FIFO) behavior is needed. See +the operations below. + + * File examples/simple/dqueue.c* + +* Menu: + +* igraph_dqueue_init -- Initialize a double ended queue (deque).: igraph_dqueue_init --- Initialize a double ended queue [deque]_. +* igraph_dqueue_destroy -- Destroy a double ended queue.: igraph_dqueue_destroy --- Destroy a double ended queue_. +* igraph_dqueue_empty -- Decide whether the queue is empty.: igraph_dqueue_empty --- Decide whether the queue is empty_. +* igraph_dqueue_full -- Check whether the queue is full.: igraph_dqueue_full --- Check whether the queue is full_. +* igraph_dqueue_clear -- Remove all elements from the queue.: igraph_dqueue_clear --- Remove all elements from the queue_. +* igraph_dqueue_size -- Number of elements in the queue.: igraph_dqueue_size --- Number of elements in the queue_. +* igraph_dqueue_head -- Head of the queue.: igraph_dqueue_head --- Head of the queue_. +* igraph_dqueue_back -- Tail of the queue.: igraph_dqueue_back --- Tail of the queue_. +* igraph_dqueue_get -- Access an element in a queue.: igraph_dqueue_get --- Access an element in a queue_. +* igraph_dqueue_pop -- Remove the head.: igraph_dqueue_pop --- Remove the head_. +* igraph_dqueue_pop_back -- Removes the tail.: igraph_dqueue_pop_back --- Removes the tail_. +* igraph_dqueue_push -- Appends an element.: igraph_dqueue_push --- Appends an element_. + + +File: igraph-docs.info, Node: igraph_dqueue_init --- Initialize a double ended queue [deque]_, Next: igraph_dqueue_destroy --- Destroy a double ended queue_, Up: Double-ended queues + +7.6.1 igraph_dqueue_init -- Initialize a double ended queue (deque). +-------------------------------------------------------------------- + + + igraph_error_t igraph_dqueue_init(igraph_dqueue_t* q, igraph_int_t capacity); + + The queue will be always empty. + + *Arguments:. * + +‘q’: + Pointer to an uninitialized deque. + +‘capacity’: + How many elements to allocate memory for. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(‘capacity’). + + +File: igraph-docs.info, Node: igraph_dqueue_destroy --- Destroy a double ended queue_, Next: igraph_dqueue_empty --- Decide whether the queue is empty_, Prev: igraph_dqueue_init --- Initialize a double ended queue [deque]_, Up: Double-ended queues + +7.6.2 igraph_dqueue_destroy -- Destroy a double ended queue. +------------------------------------------------------------ + + + void igraph_dqueue_destroy(igraph_dqueue_t* q); + + *Arguments:. * + +‘q’: + The queue to destroy. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_empty --- Decide whether the queue is empty_, Next: igraph_dqueue_full --- Check whether the queue is full_, Prev: igraph_dqueue_destroy --- Destroy a double ended queue_, Up: Double-ended queues + +7.6.3 igraph_dqueue_empty -- Decide whether the queue is empty. +--------------------------------------------------------------- + + + igraph_bool_t igraph_dqueue_empty(const igraph_dqueue_t* q); + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + Boolean, true if ‘q’ contains at least one element, false + otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_full --- Check whether the queue is full_, Next: igraph_dqueue_clear --- Remove all elements from the queue_, Prev: igraph_dqueue_empty --- Decide whether the queue is empty_, Up: Double-ended queues + +7.6.4 igraph_dqueue_full -- Check whether the queue is full. +------------------------------------------------------------ + + + igraph_bool_t igraph_dqueue_full(igraph_dqueue_t* q); + + If a queue is full the next ‘igraph_dqueue_push()’ (*note +igraph_dqueue_push --- Appends an element_::) operation will allocate +more memory. + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + ‘true’ if ‘q’ is full, ‘false’ otherwise. + + Time complecity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_clear --- Remove all elements from the queue_, Next: igraph_dqueue_size --- Number of elements in the queue_, Prev: igraph_dqueue_full --- Check whether the queue is full_, Up: Double-ended queues + +7.6.5 igraph_dqueue_clear -- Remove all elements from the queue. +---------------------------------------------------------------- + + + void igraph_dqueue_clear(igraph_dqueue_t* q); + + *Arguments:. * + +‘q’: + The queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_size --- Number of elements in the queue_, Next: igraph_dqueue_head --- Head of the queue_, Prev: igraph_dqueue_clear --- Remove all elements from the queue_, Up: Double-ended queues + +7.6.6 igraph_dqueue_size -- Number of elements in the queue. +------------------------------------------------------------ + + + igraph_int_t igraph_dqueue_size(const igraph_dqueue_t* q); + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + Integer, the number of elements currently in the queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_head --- Head of the queue_, Next: igraph_dqueue_back --- Tail of the queue_, Prev: igraph_dqueue_size --- Number of elements in the queue_, Up: Double-ended queues + +7.6.7 igraph_dqueue_head -- Head of the queue. +---------------------------------------------- + + + igraph_real_t igraph_dqueue_head(const igraph_dqueue_t* q); + + The queue must contain at least one element. + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + The first element in the queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_back --- Tail of the queue_, Next: igraph_dqueue_get --- Access an element in a queue_, Prev: igraph_dqueue_head --- Head of the queue_, Up: Double-ended queues + +7.6.8 igraph_dqueue_back -- Tail of the queue. +---------------------------------------------- + + + igraph_real_t igraph_dqueue_back(const igraph_dqueue_t* q); + + The queue must contain at least one element. + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + The last element in the queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_get --- Access an element in a queue_, Next: igraph_dqueue_pop --- Remove the head_, Prev: igraph_dqueue_back --- Tail of the queue_, Up: Double-ended queues + +7.6.9 igraph_dqueue_get -- Access an element in a queue. +-------------------------------------------------------- + + + igraph_real_t igraph_dqueue_get(const igraph_dqueue_t *q, igraph_int_t idx); + + *Arguments:. * + +‘q’: + The queue. + +‘idx’: + The index of the element within the queue. + + *Returns:. * + +‘’ + The desired element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_pop --- Remove the head_, Next: igraph_dqueue_pop_back --- Removes the tail_, Prev: igraph_dqueue_get --- Access an element in a queue_, Up: Double-ended queues + +7.6.10 igraph_dqueue_pop -- Remove the head. +-------------------------------------------- + + + igraph_real_t igraph_dqueue_pop(igraph_dqueue_t* q); + + Removes and returns the first element in the queue. The queue must +be non-empty. + + *Arguments:. * + +‘q’: + The input queue. + + *Returns:. * + +‘’ + The first element in the queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_pop_back --- Removes the tail_, Next: igraph_dqueue_push --- Appends an element_, Prev: igraph_dqueue_pop --- Remove the head_, Up: Double-ended queues + +7.6.11 igraph_dqueue_pop_back -- Removes the tail. +-------------------------------------------------- + + + igraph_real_t igraph_dqueue_pop_back(igraph_dqueue_t* q); + + Removes and returns the last element in the queue. The queue must be +non-empty. + + *Arguments:. * + +‘q’: + The queue. + + *Returns:. * + +‘’ + The last element in the queue. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_dqueue_push --- Appends an element_, Prev: igraph_dqueue_pop_back --- Removes the tail_, Up: Double-ended queues + +7.6.12 igraph_dqueue_push -- Appends an element. +------------------------------------------------ + + + igraph_error_t igraph_dqueue_push(igraph_dqueue_t* q, igraph_real_t elem); + + Append an element to the end of the queue. + + *Arguments:. * + +‘q’: + The queue. + +‘elem’: + The element to append. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) if no memory allocation is needed, O(n), the +number of elements in the queue otherwise. But note that by allocating +always twice as much memory as the current size of the queue we ensure +that n push operations can always be done in at most O(n) time. +(Assuming memory allocation is at most linear.) + + +File: igraph-docs.info, Node: Maximum and minimum heaps, Next: String vectors, Prev: Double-ended queues, Up: Data structure library; vector; matrix; other data types + +7.7 Maximum and minimum heaps +============================= + +* Menu: + +* igraph_heap_init -- Initializes an empty heap object.: igraph_heap_init --- Initializes an empty heap object_. +* igraph_heap_init_array -- Build a heap from an array.: igraph_heap_init_array --- Build a heap from an array_. +* igraph_heap_destroy -- Destroys an initialized heap object.: igraph_heap_destroy --- Destroys an initialized heap object_. +* igraph_heap_clear -- Removes all elements from a heap.: igraph_heap_clear --- Removes all elements from a heap_. +* igraph_heap_empty -- Decides whether a heap object is empty.: igraph_heap_empty --- Decides whether a heap object is empty_. +* igraph_heap_push -- Add an element.: igraph_heap_push --- Add an element_. +* igraph_heap_top -- Top element.: igraph_heap_top --- Top element_. +* igraph_heap_delete_top -- Removes and returns the top element.: igraph_heap_delete_top --- Removes and returns the top element_. +* igraph_heap_size -- Number of elements in the heap.: igraph_heap_size --- Number of elements in the heap_. +* igraph_heap_reserve -- Reserves memory for a heap.: igraph_heap_reserve --- Reserves memory for a heap_. + + +File: igraph-docs.info, Node: igraph_heap_init --- Initializes an empty heap object_, Next: igraph_heap_init_array --- Build a heap from an array_, Up: Maximum and minimum heaps + +7.7.1 igraph_heap_init -- Initializes an empty heap object. +----------------------------------------------------------- + + + igraph_error_t igraph_heap_init(igraph_heap_t* h, igraph_int_t capacity); + + Creates an _empty_ heap, and also allocates memory for some elements. + + *Arguments:. * + +‘h’: + Pointer to an uninitialized heap object. + +‘capacity’: + Number of elements to allocate memory for. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(‘alloc_size’), assuming memory allocation is a +linear operation. + + +File: igraph-docs.info, Node: igraph_heap_init_array --- Build a heap from an array_, Next: igraph_heap_destroy --- Destroys an initialized heap object_, Prev: igraph_heap_init --- Initializes an empty heap object_, Up: Maximum and minimum heaps + +7.7.2 igraph_heap_init_array -- Build a heap from an array. +----------------------------------------------------------- + + + igraph_error_t igraph_heap_init_array(igraph_heap_t *h, const igraph_real_t *data, igraph_int_t len); + + Initializes a heap object from an array. The heap is also built of +course (constructor). + + *Arguments:. * + +‘h’: + Pointer to an uninitialized heap object. + +‘data’: + Pointer to an array of base data type. + +‘len’: + The length of the array at ‘data’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of elements in the heap. + + +File: igraph-docs.info, Node: igraph_heap_destroy --- Destroys an initialized heap object_, Next: igraph_heap_clear --- Removes all elements from a heap_, Prev: igraph_heap_init_array --- Build a heap from an array_, Up: Maximum and minimum heaps + +7.7.3 igraph_heap_destroy -- Destroys an initialized heap object. +----------------------------------------------------------------- + + + void igraph_heap_destroy(igraph_heap_t* h); + + *Arguments:. * + +‘h’: + The heap object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_heap_clear --- Removes all elements from a heap_, Next: igraph_heap_empty --- Decides whether a heap object is empty_, Prev: igraph_heap_destroy --- Destroys an initialized heap object_, Up: Maximum and minimum heaps + +7.7.4 igraph_heap_clear -- Removes all elements from a heap. +------------------------------------------------------------ + + + void igraph_heap_clear(igraph_heap_t* h); + + This function simply sets the size of the heap to zero, it does not +free any allocated memory. For that you have to call +‘igraph_heap_destroy()’ (*note igraph_heap_destroy --- Destroys an +initialized heap object_::). + + *Arguments:. * + +‘h’: + The heap object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_heap_empty --- Decides whether a heap object is empty_, Next: igraph_heap_push --- Add an element_, Prev: igraph_heap_clear --- Removes all elements from a heap_, Up: Maximum and minimum heaps + +7.7.5 igraph_heap_empty -- Decides whether a heap object is empty. +------------------------------------------------------------------ + + + igraph_bool_t igraph_heap_empty(const igraph_heap_t* h); + + *Arguments:. * + +‘h’: + The heap object. + + *Returns:. * + +‘’ + ‘true’ if the heap is empty, ‘false’ otherwise. + + TIme complexity: O(1). + + +File: igraph-docs.info, Node: igraph_heap_push --- Add an element_, Next: igraph_heap_top --- Top element_, Prev: igraph_heap_empty --- Decides whether a heap object is empty_, Up: Maximum and minimum heaps + +7.7.6 igraph_heap_push -- Add an element. +----------------------------------------- + + + igraph_error_t igraph_heap_push(igraph_heap_t* h, igraph_real_t elem); + + Adds an element to the heap. + + *Arguments:. * + +‘h’: + The heap object. + +‘elem’: + The element to add. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(log n), n is the number of elements in the heap if +no reallocation is needed, O(n) otherwise. It is ensured that n push +operations are performed in O(n log n) time. + + +File: igraph-docs.info, Node: igraph_heap_top --- Top element_, Next: igraph_heap_delete_top --- Removes and returns the top element_, Prev: igraph_heap_push --- Add an element_, Up: Maximum and minimum heaps + +7.7.7 igraph_heap_top -- Top element. +------------------------------------- + + + igraph_real_t igraph_heap_top(const igraph_heap_t* h); + + For maximum heaps this is the largest, for minimum heaps the smallest +element of the heap. + + *Arguments:. * + +‘h’: + The heap object. + + *Returns:. * + +‘’ + The top element. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_heap_delete_top --- Removes and returns the top element_, Next: igraph_heap_size --- Number of elements in the heap_, Prev: igraph_heap_top --- Top element_, Up: Maximum and minimum heaps + +7.7.8 igraph_heap_delete_top -- Removes and returns the top element. +-------------------------------------------------------------------- + + + igraph_real_t igraph_heap_delete_top(igraph_heap_t* h); + + Removes and returns the top element of the heap. For maximum heaps +this is the largest, for minimum heaps the smallest element. + + *Arguments:. * + +‘h’: + The heap object. + + *Returns:. * + +‘’ + The top element. + + Time complexity: O(log n), n is the number of elements in the heap. + + +File: igraph-docs.info, Node: igraph_heap_size --- Number of elements in the heap_, Next: igraph_heap_reserve --- Reserves memory for a heap_, Prev: igraph_heap_delete_top --- Removes and returns the top element_, Up: Maximum and minimum heaps + +7.7.9 igraph_heap_size -- Number of elements in the heap. +--------------------------------------------------------- + + + igraph_int_t igraph_heap_size(const igraph_heap_t* h); + + Gives the number of elements in a heap. + + *Arguments:. * + +‘h’: + The heap object. + + *Returns:. * + +‘’ + The number of elements in the heap. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_heap_reserve --- Reserves memory for a heap_, Prev: igraph_heap_size --- Number of elements in the heap_, Up: Maximum and minimum heaps + +7.7.10 igraph_heap_reserve -- Reserves memory for a heap. +--------------------------------------------------------- + + + igraph_error_t igraph_heap_reserve(igraph_heap_t* h, igraph_int_t capacity); + + Allocates memory for future use. The size of the heap is unchanged. +If the heap is larger than the ‘capacity’ parameter then nothing +happens. + + *Arguments:. * + +‘h’: + The heap object. + +‘capacity’: + The number of elements to allocate memory for. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(‘capacity’) if ‘capacity’ is larger than the +current number of elements. O(1) otherwise. + + +File: igraph-docs.info, Node: String vectors, Next: Lists of vectors; matrices and graphs, Prev: Maximum and minimum heaps, Up: Data structure library; vector; matrix; other data types + +7.8 String vectors +================== + +The ‘igraph_strvector_t’ type is a vector of null-terminated strings. +It is used internally for storing graph attribute names as well as +string attributes in the C attribute handler. + + This container automatically manages the memory of its elements. The +strings within an ‘igraph_strvector_t’ should be considered constant, +and not modified directly. Functions that add new elements always make +copies of the string passed to them. + + * File examples/simple/igraph_strvector.c* + +* Menu: + +* igraph_strvector_init -- Initializes a string vector.: igraph_strvector_init --- Initializes a string vector_. +* igraph_strvector_init_copy -- Initialization by copying.: igraph_strvector_init_copy --- Initialization by copying_. +* igraph_strvector_destroy -- Frees the memory allocated for the string vector.: igraph_strvector_destroy --- Frees the memory allocated for the string vector_. +* STR -- Indexing string vectors.: STR --- Indexing string vectors_. +* igraph_strvector_get -- Retrieves an element of a string vector.: igraph_strvector_get --- Retrieves an element of a string vector_. +* igraph_strvector_set -- Sets an element of the string vector from a string.: igraph_strvector_set --- Sets an element of the string vector from a string_. +* igraph_strvector_set_len -- Sets an element of the string vector given a buffer and its size.: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_. +* igraph_strvector_push_back -- Adds an element to the back of a string vector.: igraph_strvector_push_back --- Adds an element to the back of a string vector_. +* igraph_strvector_push_back_len -- Adds a string of the given length to the back of a string vector.: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_. +* igraph_strvector_swap_elements -- Swap two elements in a string vector.: igraph_strvector_swap_elements --- Swap two elements in a string vector_. +* igraph_strvector_remove -- Removes a single element from a string vector.: igraph_strvector_remove --- Removes a single element from a string vector_. +* igraph_strvector_remove_section -- Removes a section from a string vector.: igraph_strvector_remove_section --- Removes a section from a string vector_. +* igraph_strvector_append -- Concatenates two string vectors.: igraph_strvector_append --- Concatenates two string vectors_. +* igraph_strvector_merge -- Moves the contents of a string vector to the end of another.: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_. +* igraph_strvector_swap -- Swaps all elements of two string vectors.: igraph_strvector_swap --- Swaps all elements of two string vectors_. +* igraph_strvector_update -- Updates a string vector from another one.: igraph_strvector_update --- Updates a string vector from another one_. +* igraph_strvector_clear -- Removes all elements from a string vector.: igraph_strvector_clear --- Removes all elements from a string vector_. +* igraph_strvector_resize -- Resizes a string vector.: igraph_strvector_resize --- Resizes a string vector_. +* igraph_strvector_reserve -- Reserves memory for a string vector.: igraph_strvector_reserve --- Reserves memory for a string vector_. +* igraph_strvector_resize_min -- Deallocates the unused memory of a string vector.: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_. +* igraph_strvector_size -- Returns the size of a string vector.: igraph_strvector_size --- Returns the size of a string vector_. +* igraph_strvector_capacity -- Returns the capacity of a string vector.: igraph_strvector_capacity --- Returns the capacity of a string vector_. + + +File: igraph-docs.info, Node: igraph_strvector_init --- Initializes a string vector_, Next: igraph_strvector_init_copy --- Initialization by copying_, Up: String vectors + +7.8.1 igraph_strvector_init -- Initializes a string vector. +----------------------------------------------------------- + + + igraph_error_t igraph_strvector_init(igraph_strvector_t *sv, igraph_int_t size); + + Reserves memory for the string vector, a string vector must be first +initialized before calling other functions on it. All elements of the +string vector are set to the empty string. + + *Arguments:. * + +‘sv’: + Pointer to an initialized string vector. + +‘size’: + The (initial) length of the string vector. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(‘len’). + + +File: igraph-docs.info, Node: igraph_strvector_init_copy --- Initialization by copying_, Next: igraph_strvector_destroy --- Frees the memory allocated for the string vector_, Prev: igraph_strvector_init --- Initializes a string vector_, Up: String vectors + +7.8.2 igraph_strvector_init_copy -- Initialization by copying. +-------------------------------------------------------------- + + + igraph_error_t igraph_strvector_init_copy(igraph_strvector_t *to, + const igraph_strvector_t *from); + + Initializes a string vector by copying another string vector. + + *Arguments:. * + +‘to’: + Pointer to an uninitialized string vector. + +‘from’: + The other string vector, to be copied. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(l), the total length of the strings in ‘from’. + + +File: igraph-docs.info, Node: igraph_strvector_destroy --- Frees the memory allocated for the string vector_, Next: STR --- Indexing string vectors_, Prev: igraph_strvector_init_copy --- Initialization by copying_, Up: String vectors + +7.8.3 igraph_strvector_destroy -- Frees the memory allocated for the string vector. +----------------------------------------------------------------------------------- + + + void igraph_strvector_destroy(igraph_strvector_t *sv); + + Destroy a string vector. It may be reinitialized with +‘igraph_strvector_init()’ (*note igraph_strvector_init --- Initializes a +string vector_::) later. + + *Arguments:. * + +‘sv’: + The string vector. + + Time complexity: O(l), the total length of the strings, maybe less +depending on the memory manager. + + +File: igraph-docs.info, Node: STR --- Indexing string vectors_, Next: igraph_strvector_get --- Retrieves an element of a string vector_, Prev: igraph_strvector_destroy --- Frees the memory allocated for the string vector_, Up: String vectors + +7.8.4 STR -- Indexing string vectors. +------------------------------------- + + + #define STR(sv,i) + + This is a macro that allows to query the elements of a string vector, +just like ‘igraph_strvector_get()’ (*note igraph_strvector_get --- +Retrieves an element of a string vector_::). Note this macro cannot be +used to set an element. Use ‘igraph_strvector_set()’ (*note +igraph_strvector_set --- Sets an element of the string vector from a +string_::) to set an element instead. + + *Arguments:. * + +‘sv’: + The string vector + +‘i’: + The index of the element. + + *Returns:. * + +‘’ + The element at position ‘i’. + + Time complexity: O(1). + + *Warning* + + Deprecated since version 0.10.9. Please do not use this function + in new code; use ‘igraph_strvector_get()’ (*note + igraph_strvector_get --- Retrieves an element of a string + vector_::) instead. + + +File: igraph-docs.info, Node: igraph_strvector_get --- Retrieves an element of a string vector_, Next: igraph_strvector_set --- Sets an element of the string vector from a string_, Prev: STR --- Indexing string vectors_, Up: String vectors + +7.8.5 igraph_strvector_get -- Retrieves an element of a string vector. +---------------------------------------------------------------------- + + + const char *igraph_strvector_get(const igraph_strvector_t *sv, igraph_int_t idx); + + Query an element of a string vector. The returned string must not be +modified. + + *Arguments:. * + +‘sv’: + The input string vector. + +‘idx’: + The index of the element to query. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_strvector_set --- Sets an element of the string vector from a string_, Next: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_, Prev: igraph_strvector_get --- Retrieves an element of a string vector_, Up: String vectors + +7.8.6 igraph_strvector_set -- Sets an element of the string vector from a string. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_set(igraph_strvector_t *sv, igraph_int_t idx, + const char *value); + + The provided ‘value’ is copied into the ‘idx’ position in the string +vector. + + *Arguments:. * + +‘sv’: + The string vector. + +‘idx’: + The position to set. + +‘value’: + The new value. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(l), the length of the new string. Maybe more, +depending on the memory management, if reallocation is needed. + + +File: igraph-docs.info, Node: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_, Next: igraph_strvector_push_back --- Adds an element to the back of a string vector_, Prev: igraph_strvector_set --- Sets an element of the string vector from a string_, Up: String vectors + +7.8.7 igraph_strvector_set_len -- Sets an element of the string vector given a buffer and its size. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_set_len(igraph_strvector_t *sv, igraph_int_t idx, + const char *value, size_t len); + + This is almost the same as ‘igraph_strvector_set’ (*note +igraph_strvector_set --- Sets an element of the string vector from a +string_::), but the new value is not a zero terminated string, but its +length is given. + + *Arguments:. * + +‘sv’: + The string vector. + +‘idx’: + The position to set. + +‘value’: + The new value. + +‘len’: + The length of the new value. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(l), the length of the new string. Maybe more, +depending on the memory management, if reallocation is needed. + + +File: igraph-docs.info, Node: igraph_strvector_push_back --- Adds an element to the back of a string vector_, Next: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_, Prev: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_, Up: String vectors + +7.8.8 igraph_strvector_push_back -- Adds an element to the back of a string vector. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_push_back(igraph_strvector_t *sv, const char *value); + + *Arguments:. * + +‘sv’: + The string vector. + +‘value’: + The string to add; it will be copied. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n+l), n is the total number of strings, l is the +length of the new string. + + +File: igraph-docs.info, Node: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_, Next: igraph_strvector_swap_elements --- Swap two elements in a string vector_, Prev: igraph_strvector_push_back --- Adds an element to the back of a string vector_, Up: String vectors + +7.8.9 igraph_strvector_push_back_len -- Adds a string of the given length to the back of a string vector. +--------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_push_back_len( + igraph_strvector_t *sv, + const char *value, size_t len); + + *Arguments:. * + +‘sv’: + The string vector. + +‘value’: + The start of the string to add. At most ‘len’ characters will be + copied. + +‘len’: + The length of the string. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n+l), n is the total number of strings, l is the +length of the new string. + + +File: igraph-docs.info, Node: igraph_strvector_swap_elements --- Swap two elements in a string vector_, Next: igraph_strvector_remove --- Removes a single element from a string vector_, Prev: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_, Up: String vectors + +7.8.10 igraph_strvector_swap_elements -- Swap two elements in a string vector. +------------------------------------------------------------------------------ + + + void igraph_strvector_swap_elements(igraph_strvector_t *sv, igraph_int_t i, igraph_int_t j); + + Note that currently no range checking is performed. + + *Arguments:. * + +‘sv’: + The string vector. + +‘i’: + Index of the first element. + +‘j’: + Index of the second element (may be the same as the first one). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_strvector_remove --- Removes a single element from a string vector_, Next: igraph_strvector_remove_section --- Removes a section from a string vector_, Prev: igraph_strvector_swap_elements --- Swap two elements in a string vector_, Up: String vectors + +7.8.11 igraph_strvector_remove -- Removes a single element from a string vector. +-------------------------------------------------------------------------------- + + + void igraph_strvector_remove(igraph_strvector_t *sv, igraph_int_t elem); + + The string will be one shorter. + + *Arguments:. * + +‘sv’: + The string vector. + +‘elem’: + The index of the element to remove. + + Time complexity: O(n), the length of the string. + + +File: igraph-docs.info, Node: igraph_strvector_remove_section --- Removes a section from a string vector_, Next: igraph_strvector_append --- Concatenates two string vectors_, Prev: igraph_strvector_remove --- Removes a single element from a string vector_, Up: String vectors + +7.8.12 igraph_strvector_remove_section -- Removes a section from a string vector. +--------------------------------------------------------------------------------- + + + void igraph_strvector_remove_section( + igraph_strvector_t *sv, igraph_int_t from, igraph_int_t to); + + This function removes the range ‘[from, to)’ from the string vector. + + *Arguments:. * + +‘sv’: + The string vector. + +‘from’: + The position of the first element to remove. + +‘to’: + The position of the first element _not_ to remove. + + +File: igraph-docs.info, Node: igraph_strvector_append --- Concatenates two string vectors_, Next: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_, Prev: igraph_strvector_remove_section --- Removes a section from a string vector_, Up: String vectors + +7.8.13 igraph_strvector_append -- Concatenates two string vectors. +------------------------------------------------------------------ + + + igraph_error_t igraph_strvector_append(igraph_strvector_t *to, + const igraph_strvector_t *from); + + Appends the contents of the ‘from’ vector to the ‘to’ vector. If the +‘from’ vector is no longer needed after this operation, use +‘igraph_strvector_merge()’ (*note igraph_strvector_merge --- Moves the +contents of a string vector to the end of another_::) for better +performance. + + *Arguments:. * + +‘to’: + The first string vector, the result is stored here. + +‘from’: + The second string vector, it is kept unchanged. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_strvector_merge()’ (*note igraph_strvector_merge --- Moves + the contents of a string vector to the end of another_::) + + Time complexity: O(n+l2), n is the number of strings in the new +string vector, l2 is the total length of strings in the ‘from’ string +vector. + + +File: igraph-docs.info, Node: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_, Next: igraph_strvector_swap --- Swaps all elements of two string vectors_, Prev: igraph_strvector_append --- Concatenates two string vectors_, Up: String vectors + +7.8.14 igraph_strvector_merge -- Moves the contents of a string vector to the end of another. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_merge(igraph_strvector_t *to, igraph_strvector_t *from); + + Transfers the contents of the ‘from’ vector to the end of ‘to’, +clearing ‘from’ in the process. If this operation fails, both vectors +are left intact. This function does not copy or reallocate individual +strings, therefore it performs better than ‘igraph_strvector_append()’ +(*note igraph_strvector_append --- Concatenates two string vectors_::). + + *Arguments:. * + +‘to’: + The target vector. The contents of ‘from’ will be appended to it. + +‘from’: + The source vector. It will be cleared. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_strvector_append()’ (*note igraph_strvector_append --- + Concatenates two string vectors_::) + + Time complexity: O(l2) if ‘to’ has sufficient capacity, O(2*l1+l2) +otherwise, where l1 and l2 are the lengths of ‘to’ and \from +respectively. + + +File: igraph-docs.info, Node: igraph_strvector_swap --- Swaps all elements of two string vectors_, Next: igraph_strvector_update --- Updates a string vector from another one_, Prev: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_, Up: String vectors + +7.8.15 igraph_strvector_swap -- Swaps all elements of two string vectors. +------------------------------------------------------------------------- + + + void igraph_strvector_swap(igraph_strvector_t *v1, igraph_strvector_t *v2); + + *Arguments:. * + +‘v1’: + The first string vector. + +‘v2’: + The second string vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_strvector_update --- Updates a string vector from another one_, Next: igraph_strvector_clear --- Removes all elements from a string vector_, Prev: igraph_strvector_swap --- Swaps all elements of two string vectors_, Up: String vectors + +7.8.16 igraph_strvector_update -- Updates a string vector from another one. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_update( + igraph_strvector_t *to, const igraph_strvector_t *from + ); + + After this operation the contents of ‘to’ will be exactly the same as +that of ‘from’. The vector ‘to’ will be resized if it was originally +shorter or longer than ‘from’. + + *Arguments:. * + +‘to’: + The string vector to update. + +‘from’: + The string vector to update from. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_strvector_clear --- Removes all elements from a string vector_, Next: igraph_strvector_resize --- Resizes a string vector_, Prev: igraph_strvector_update --- Updates a string vector from another one_, Up: String vectors + +7.8.17 igraph_strvector_clear -- Removes all elements from a string vector. +--------------------------------------------------------------------------- + + + void igraph_strvector_clear(igraph_strvector_t *sv); + + After this operation the string vector will be empty. + + *Arguments:. * + +‘sv’: + The string vector. + + Time complexity: O(l), the total length of strings, maybe less, +depending on the memory manager. + + +File: igraph-docs.info, Node: igraph_strvector_resize --- Resizes a string vector_, Next: igraph_strvector_reserve --- Reserves memory for a string vector_, Prev: igraph_strvector_clear --- Removes all elements from a string vector_, Up: String vectors + +7.8.18 igraph_strvector_resize -- Resizes a string vector. +---------------------------------------------------------- + + + igraph_error_t igraph_strvector_resize(igraph_strvector_t *sv, igraph_int_t newsize); + + If the new size is bigger then empty strings are added, if it is +smaller then the unneeded elements are removed. + + *Arguments:. * + +‘sv’: + The string vector. + +‘newsize’: + The new size. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of strings if the vector is made +bigger, O(l), the total length of the deleted strings if it is made +smaller, maybe less, depending on memory management. + + +File: igraph-docs.info, Node: igraph_strvector_reserve --- Reserves memory for a string vector_, Next: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_, Prev: igraph_strvector_resize --- Resizes a string vector_, Up: String vectors + +7.8.19 igraph_strvector_reserve -- Reserves memory for a string vector. +----------------------------------------------------------------------- + + + igraph_error_t igraph_strvector_reserve(igraph_strvector_t *sv, igraph_int_t capacity); + + ‘igraph’ string vectors are flexible, they can grow and shrink. +Growing however occasionally needs the data in the vector to be copied. +In order to avoid this, you can call this function to reserve space for +future growth of the vector. + + Note that this function does _not_ change the size of the string +vector. Let us see a small example to clarify things: if you reserve +space for 100 strings and the size of your vector was (and still is) 60, +then you can surely add additional 40 strings to your vector before it +will be copied. + + *Arguments:. * + +‘sv’: + The string vector object. + +‘capacity’: + The new _allocated_ size of the string vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, should be around O(n), n +is the new allocated size of the vector. + + +File: igraph-docs.info, Node: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_, Next: igraph_strvector_size --- Returns the size of a string vector_, Prev: igraph_strvector_reserve --- Reserves memory for a string vector_, Up: String vectors + +7.8.20 igraph_strvector_resize_min -- Deallocates the unused memory of a string vector. +--------------------------------------------------------------------------------------- + + + void igraph_strvector_resize_min(igraph_strvector_t *sv); + + This function attempts to deallocate the unused reserved storage of a +string vector. If it succeeds, ‘igraph_strvector_size()’ (*note +igraph_strvector_size --- Returns the size of a string vector_::) and +‘igraph_strvector_capacity()’ (*note igraph_strvector_capacity --- +Returns the capacity of a string vector_::) will be the same. The data +in the string vector is always preserved, even if deallocation is not +successful. + + *Arguments:. * + +‘sv’: + The string vector. + + Time complexity: Operating system dependent, at most O(n). + + +File: igraph-docs.info, Node: igraph_strvector_size --- Returns the size of a string vector_, Next: igraph_strvector_capacity --- Returns the capacity of a string vector_, Prev: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_, Up: String vectors + +7.8.21 igraph_strvector_size -- Returns the size of a string vector. +-------------------------------------------------------------------- + + + igraph_int_t igraph_strvector_size(const igraph_strvector_t *sv); + + *Arguments:. * + +‘sv’: + The string vector. + + *Returns:. * + +‘’ + The length of the string vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_strvector_capacity --- Returns the capacity of a string vector_, Prev: igraph_strvector_size --- Returns the size of a string vector_, Up: String vectors + +7.8.22 igraph_strvector_capacity -- Returns the capacity of a string vector. +---------------------------------------------------------------------------- + + + igraph_int_t igraph_strvector_capacity(const igraph_strvector_t *sv); + + *Arguments:. * + +‘sv’: + The string vector. + + *Returns:. * + +‘’ + The capacity of the string vector. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Lists of vectors; matrices and graphs, Next: Adjacency lists, Prev: String vectors, Up: Data structure library; vector; matrix; other data types + +7.9 Lists of vectors, matrices and graphs +========================================= + +* Menu: + +* About igraph_vector_list_t objects:: +* Constructors and destructors: Constructors and destructors <1>. +* Accessing elements: Accessing elements <1>. +* Vector properties: Vector properties <1>. +* Resizing operations: Resizing operations <2>. +* Sorting and reordering:: + + +File: igraph-docs.info, Node: About igraph_vector_list_t objects, Next: Constructors and destructors <1>, Up: Lists of vectors; matrices and graphs + +7.9.1 About igraph_vector_list_t objects +---------------------------------------- + +The ‘igraph_vector_list_t’ data type is essentially a list of +‘igraph_vector_t’ objects with automatic memory management. It is +something similar to (but much simpler than) the ‘vector’ template in +the C++ standard library where the elements are vectors themselves. + + There are multiple variants of ‘igraph_vector_list_t’; the basic +variant stores vectors of doubles (i.e. each item is an +‘igraph_vector_t’ (*note About igraph_vector_t objects::)), but there is +also ‘igraph_vector_int_list_t’ for integers (where each item is an +‘igraph_vector_int_t’), ‘igraph_matrix_list_t’ for matrices of doubles +and so on. The following list summarizes the variants that are +currently available in the library: + + • ‘igraph_vector_list_t’ for lists of vectors of floating-point + numbers (‘igraph_vector_t’) + + • ‘igraph_vector_int_list_t’ for lists of integer vectors + (‘igraph_vector_int_t’) + + • ‘igraph_matrix_list_t’ for lists of matrices of floating-point + numbers (‘igraph_matrix_t’) + + • ‘igraph_graph_list_t’ for lists of graphs (‘igraph_t’) + + Lists of vectors are used in ‘igraph’ in many cases, e.g., when +returning lists of paths, cliques or vertex sets. Functions that expect +or return a list of numeric vectors typically use ‘igraph_vector_list_t’ +or ‘igraph_vector_int_list_t’ to achieve this. Lists of integer vectors +are used when the vectors in the list are supposed to hold vertex or +edge identifiers, while lists of floating-point vectors are used when +the vectors are expected to hold fractional numbers or infinities. + + The elements in an ‘igraph_vector_list_t’ object and its variants are +indexed from zero, we follow the usual C convention here. + + Almost all of the functions described below for +‘igraph_vector_list_t’ also exist for all the other vector list +variants. These variants are not documented separately; you can simply +replace ‘vector_list’ with, say, ‘vector_int_list’ if you need a +function for another variant. For instance, to initialize a list of +integer vectors, you need to use ‘igraph_vector_int_list_init’() and not +‘igraph_vector_list_init()’ (*note igraph_vector_list_init --- +Initializes a list of vectors [constructor]_::). + + Before diving into a detailed description of the functions related to +lists of vectors, we must also talk about the _ownership_ rules of these +objects. The most important rule is that the vectors in the list are +owned by the list itself, meaning that the user is _not_ responsible for +allocating memory for the vectors or for freeing the memory associated +to the vectors. It is the responsibility of the list to allocate and +initialize the vectors when new items are created in the list, and it is +also the responsibility of the list to destroy the items when they are +removed from the list without passing on their ownership to the user. +As a consequence, the list may not contain "uninitialized" or "null" +items; each item is initialized when it comes to existence. If you +create a list containing one million vectors, you are not only +allocating memory for one million ‘igraph_vector_t’ (*note About +igraph_vector_t objects::) object but you are also initializing one +million vectors. Also, if you have a list containing one million +vectors and you clear the list by calling ‘igraph_vector_list_clear()’ +(*note igraph_vector_list_clear --- Removes all elements from a list of +vectors_::), the list will implicitly destroy these lists, and any +pointers that you may hold to the items become invalid at once. + + Speaking about pointers, the typical way of working with vectors in a +list is to obtain a pointer to one of the items via the +‘igraph_vector_list_get_ptr()’ (*note igraph_vector_list_get_ptr --- The +address of a vector in the vector list_::) method and then passing this +pointer onwards to functions that manipulate ‘igraph_vector_t’ (*note +About igraph_vector_t objects::) objects. However, note that the +pointers are _ephemeral_ in the sense that they may be invalidated any +time when the list is modified because a modification may involve the +re-allocation of the internal storage of the list if more space is +needed, and the pointers that you obtained will not follow the +reallocation. This limitation does not appear often in real-world usage +of ‘igraph_vector_list_t’ and in general, the advantages of the +automatic memory management outweigh this limitation. + + +File: igraph-docs.info, Node: Constructors and destructors <1>, Next: Accessing elements <1>, Prev: About igraph_vector_list_t objects, Up: Lists of vectors; matrices and graphs + +7.9.2 Constructors and destructors +---------------------------------- + +‘igraph_vector_list_t’ objects have to be initialized before using them, +this is analogous to calling a constructor on them. +‘igraph_vector_list_init()’ (*note igraph_vector_list_init --- +Initializes a list of vectors [constructor]_::) is the basic +constructor; it creates a list of the given length and also initializes +each vector in the newly created list to zero length. + + If an ‘igraph_vector_list_t’ object is not needed any more, it should +be destroyed to free its allocated memory by calling the +‘igraph_vector_list_t’ destructor, ‘igraph_vector_list_destroy()’ (*note +igraph_vector_list_destroy --- Destroys a list of vectors object_::). +Calling the destructor also destroys all the vectors inside the vector +list due to the ownership rules. If you want to keep a few of the +vectors in the vector list, you need to copy them with +‘igraph_vector_init_copy()’ (*note igraph_vector_init_copy --- +Initializes a vector from another vector object [constructor]_::) or +‘igraph_vector_update()’ (*note igraph_vector_update --- Update a vector +from another one_::), or you need to remove them from the list and take +ownership by calling ‘igraph_vector_list_pop_back()’ (*note +igraph_vector_list_pop_back --- Removes the last item from the vector +list and transfer ownership to the caller_::), +‘igraph_vector_list_remove()’ (*note igraph_vector_list_remove --- +Removes the item at the given index from the vector list and transfer +ownership to the caller_::) or ‘igraph_vector_list_remove_fast()’ (*note +igraph_vector_list_remove_fast --- Removes the item at the given index +in the vector list; move the last item to its place and transfer +ownership to the caller_::) . + +* Menu: + +* igraph_vector_list_init -- Initializes a list of vectors (constructor).: igraph_vector_list_init --- Initializes a list of vectors [constructor]_. +* igraph_vector_list_init_copy -- Initializes a list of vectors from another list of vectors (constructor).: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_. +* igraph_vector_list_destroy -- Destroys a list of vectors object.: igraph_vector_list_destroy --- Destroys a list of vectors object_. + + +File: igraph-docs.info, Node: igraph_vector_list_init --- Initializes a list of vectors [constructor]_, Next: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_, Up: Constructors and destructors <1> + +7.9.2.1 igraph_vector_list_init -- Initializes a list of vectors (constructor). +............................................................................... + + + igraph_error_t igraph_vector_list_init(igraph_vector_list_t *v, igraph_int_t size); + + This function constructs a list of vectors of the given size, and +initializes each vector in the newly created list to become an empty +vector. + + Vector objects initialized by this function are _owned_ by the list, +and they will be destroyed automatically when the list is destroyed with +‘igraph_vector_list_destroy()’ (*note igraph_vector_list_destroy --- +Destroys a list of vectors object_::). + + *Arguments:. * + +‘v’: + Pointer to a not yet initialized list of vectors. + +‘size’: + The size of the list. + + *Returns:. * + +‘’ + error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, the amount of 'time' +required to allocate O(n) elements and initialize the corresponding +vectors; n is the number of elements. + + +File: igraph-docs.info, Node: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_, Next: igraph_vector_list_destroy --- Destroys a list of vectors object_, Prev: igraph_vector_list_init --- Initializes a list of vectors [constructor]_, Up: Constructors and destructors <1> + +7.9.2.2 igraph_vector_list_init_copy -- Initializes a list of vectors from another list of vectors (constructor). +................................................................................................................. + + + igraph_error_t igraph_vector_list_init_copy(igraph_vector_list_t* to, const igraph_vector_list_t* from); + + The contents of the existing list will be copied to the new one. + + *Arguments:. * + +‘to’: + Pointer to a not yet initialized list of vectors. + +‘from’: + The original list of vectors to copy. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, usually O(nm), n is the +size of the list, m is the size of each element in the list, assuming +that copying a single item takes O(m) time. + + +File: igraph-docs.info, Node: igraph_vector_list_destroy --- Destroys a list of vectors object_, Prev: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_, Up: Constructors and destructors <1> + +7.9.2.3 igraph_vector_list_destroy -- Destroys a list of vectors object. +........................................................................ + + + void igraph_vector_list_destroy(igraph_vector_list_t *v); + + All lists initialized by ‘igraph_vector_list_init()’ (*note +igraph_vector_list_init --- Initializes a list of vectors +[constructor]_::) should be properly destroyed by this function. A +destroyed list of vectors needs to be reinitialized by +‘igraph_vector_list_init()’ (*note igraph_vector_list_init --- +Initializes a list of vectors [constructor]_::) if you want to use it +again. + + Vectors that are in the list when it is destroyed are also destroyed +implicitly. + + *Arguments:. * + +‘v’: + Pointer to the (previously initialized) list object to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: Accessing elements <1>, Next: Vector properties <1>, Prev: Constructors and destructors <1>, Up: Lists of vectors; matrices and graphs + +7.9.3 Accessing elements +------------------------ + +Elements of a vector list may be accessed with the +‘igraph_vector_list_get_ptr()’ (*note igraph_vector_list_get_ptr --- The +address of a vector in the vector list_::) function. The function +returns a _pointer_ to the vector with a given index inside the list, +and you may then pass this pointer onwards to other functions that can +query or manipulate vectors. The pointer itself is guaranteed to stay +valid as long as the list itself is not modified; however, _any_ +modification to the list will invalidate the pointer, even modifications +that are seemingly unrelated to the vector that the pointer points to +(such as adding a new vector at the end of the list). This is because +the list data structure may be forced to re-allocate its internal +storage if a new element does not fit into the already allocated space, +and there are no guarantees that the re-allocated block remains at the +same memory location (typically it gets moved elsewhere). + + Note that the standard ‘VECTOR’ (*note VECTOR --- Accessing an +element of a vector_::) macro that works for ordinary vectors does not +work for lists of vectors to access the i-th element (but of course you +can use it to index into an existing vector that you retrieved from the +vector list with ‘igraph_vector_list_get_ptr()’ (*note +igraph_vector_list_get_ptr --- The address of a vector in the vector +list_::) ). This is because the macro notation would allow one to +overwrite the vector in the list with another one without the list +knowing about it, so the list would not be able to destroy the vector +that was overwritten by a new one. + + ‘igraph_vector_list_tail_ptr()’ (*note igraph_vector_list_tail_ptr +--- The address of the last vector in the vector list_::) returns a +pointer to the last vector in the list, or ‘NULL’ if the list is empty. +There is no ‘igraph_vector_list_head_ptr()’, however, as it is easy to +write ‘igraph_vector_list_get_ptr(v, 0)’ instead. + +* Menu: + +* igraph_vector_list_get_ptr -- The address of a vector in the vector list.: igraph_vector_list_get_ptr --- The address of a vector in the vector list_. +* igraph_vector_list_tail_ptr -- The address of the last vector in the vector list.: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_. +* igraph_vector_list_set -- Sets the vector at the given index in the list.: igraph_vector_list_set --- Sets the vector at the given index in the list_. +* igraph_vector_list_replace -- Replaces the vector at the given index in the list with another one.: igraph_vector_list_replace --- Replaces the vector at the given index in the list with another one_. + + +File: igraph-docs.info, Node: igraph_vector_list_get_ptr --- The address of a vector in the vector list_, Next: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_, Up: Accessing elements <1> + +7.9.3.1 igraph_vector_list_get_ptr -- The address of a vector in the vector list. +................................................................................. + + + igraph_vector_t *igraph_vector_list_get_ptr(const igraph_vector_list_t *v, igraph_int_t pos); + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The position of the vector in the list. The position of the first + vector is zero. + + *Returns:. * + +‘’ + A pointer to the vector. It remains valid as long as the + underlying list of vectors is not modified. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_, Next: igraph_vector_list_set --- Sets the vector at the given index in the list_, Prev: igraph_vector_list_get_ptr --- The address of a vector in the vector list_, Up: Accessing elements <1> + +7.9.3.2 igraph_vector_list_tail_ptr -- The address of the last vector in the vector list. +......................................................................................... + + + igraph_vector_t *igraph_vector_list_tail_ptr(const igraph_vector_list_t *v); + + *Arguments:. * + +‘v’: + The list object. + + *Returns:. * + +‘’ + A pointer to the last vector in the list, or ‘NULL’ if the list is + empty. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_set --- Sets the vector at the given index in the list_, Next: igraph_vector_list_replace --- Replaces the vector at the given index in the list with another one_, Prev: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_, Up: Accessing elements <1> + +7.9.3.3 igraph_vector_list_set -- Sets the vector at the given index in the list. +................................................................................. + + + void igraph_vector_list_set(igraph_vector_list_t *v, igraph_int_t pos, igraph_vector_t *e); + + This function destroys the vector that is already at the given index +‘pos’ in the list, and replaces it with the vector pointed to by ‘e’. +The ownership of the vector pointed to by ‘e’ is taken by the list so +the user is not responsible for destroying ‘e’ any more; it will be +destroyed when the list itself is destroyed or if ‘e’ gets removed from +the list without passing on the ownership to somewhere else. + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The index to modify in the list. + +‘e’: + The vector to set in the list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_replace --- Replaces the vector at the given index in the list with another one_, Prev: igraph_vector_list_set --- Sets the vector at the given index in the list_, Up: Accessing elements <1> + +7.9.3.4 igraph_vector_list_replace -- Replaces the vector at the given index in the list with another one. +.......................................................................................................... + + + void igraph_vector_list_replace(igraph_vector_list_t *v, igraph_int_t pos, igraph_vector_t *e); + + This function replaces the vector that is already at the given index +‘pos’ in the list with the vector pointed to by ‘e’. The ownership of +the vector pointed to by ‘e’ is taken by the list so the user is not +responsible for destroying ‘e’ any more. At the same time, the +ownership of the vector that _was_ in the list at position ‘pos’ will be +transferred to the caller and ‘e’ will be updated to point to it, so the +caller becomes responsible for destroying it when it does not need the +vector any more. + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The index to modify in the list. + +‘e’: + The vector to swap with the one already in the list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Vector properties <1>, Next: Resizing operations <2>, Prev: Accessing elements <1>, Up: Lists of vectors; matrices and graphs + +7.9.4 Vector properties +----------------------- + +* Menu: + +* igraph_vector_list_empty -- Decides whether the size of the list is zero.: igraph_vector_list_empty --- Decides whether the size of the list is zero_. +* igraph_vector_list_size -- The size of the vector list.: igraph_vector_list_size --- The size of the vector list_. +* igraph_vector_list_capacity -- Returns the allocated capacity of the list.: igraph_vector_list_capacity --- Returns the allocated capacity of the list_. + + +File: igraph-docs.info, Node: igraph_vector_list_empty --- Decides whether the size of the list is zero_, Next: igraph_vector_list_size --- The size of the vector list_, Up: Vector properties <1> + +7.9.4.1 igraph_vector_list_empty -- Decides whether the size of the list is zero. +................................................................................. + + + igraph_bool_t igraph_vector_list_empty(const igraph_vector_list_t *v); + + *Arguments:. * + +‘v’: + The list object. + + *Returns:. * + +‘’ + True if the size of the list is zero and false otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_size --- The size of the vector list_, Next: igraph_vector_list_capacity --- Returns the allocated capacity of the list_, Prev: igraph_vector_list_empty --- Decides whether the size of the list is zero_, Up: Vector properties <1> + +7.9.4.2 igraph_vector_list_size -- The size of the vector list. +............................................................... + + + igraph_int_t igraph_vector_list_size(const igraph_vector_list_t *v); + + Returns the number of vectors stored in the list. + + *Arguments:. * + +‘v’: + The list object + + *Returns:. * + +‘’ + The size of the list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_capacity --- Returns the allocated capacity of the list_, Prev: igraph_vector_list_size --- The size of the vector list_, Up: Vector properties <1> + +7.9.4.3 igraph_vector_list_capacity -- Returns the allocated capacity of the list. +.................................................................................. + + + igraph_int_t igraph_vector_list_capacity(const igraph_vector_list_t *v); + + Note that this might be different from the size of the list (as +queried by ‘igraph_vector_list_size()’ (*note igraph_vector_list_size +--- The size of the vector list_::)), and specifies how many vectors the +list can hold, without reallocation. + + *Arguments:. * + +‘v’: + Pointer to the (previously initialized) list object to query. + + *Returns:. * + +‘’ + The allocated capacity. + + *See also:. * + +‘’ + ‘igraph_vector_list_size()’ (*note igraph_vector_list_size --- The + size of the vector list_::). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Resizing operations <2>, Next: Sorting and reordering, Prev: Vector properties <1>, Up: Lists of vectors; matrices and graphs + +7.9.5 Resizing operations +------------------------- + +* Menu: + +* igraph_vector_list_clear -- Removes all elements from a list of vectors.: igraph_vector_list_clear --- Removes all elements from a list of vectors_. +* igraph_vector_list_reserve -- Reserves memory for a list.: igraph_vector_list_reserve --- Reserves memory for a list_. +* igraph_vector_list_resize -- Resizes the list of vectors.: igraph_vector_list_resize --- Resizes the list of vectors_. +* igraph_vector_list_push_back -- Appends an existing vector to the list, transferring ownership.: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_. +* igraph_vector_list_push_back_copy -- Appends the copy of a vector to the list.: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_. +* igraph_vector_list_push_back_new -- Appends a new vector to the list.: igraph_vector_list_push_back_new --- Appends a new vector to the list_. +* igraph_vector_list_pop_back -- Removes the last item from the vector list and transfer ownership to the caller.: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_. +* igraph_vector_list_insert -- Inserts an existing vector into the list, transferring ownership.: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_. +* igraph_vector_list_insert_copy -- Inserts the copy of a vector to the list.: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_. +* igraph_vector_list_insert_new -- Inserts a new vector into the list.: igraph_vector_list_insert_new --- Inserts a new vector into the list_. +* igraph_vector_list_remove -- Removes the item at the given index from the vector list and transfer ownership to the caller.: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_. +* igraph_vector_list_remove_fast -- Removes the item at the given index in the vector list, move the last item to its place and transfer ownership to the caller.: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_. +* igraph_vector_list_discard -- Discards the item at the given index in the vector list.: igraph_vector_list_discard --- Discards the item at the given index in the vector list_. +* igraph_vector_list_discard_back -- Discards the last item in the vector list.: igraph_vector_list_discard_back --- Discards the last item in the vector list_. +* igraph_vector_list_discard_fast -- Discards the item at the given index in the vector list and moves the last item to its place.: igraph_vector_list_discard_fast --- Discards the item at the given index in the vector list and moves the last item to its place_. + + +File: igraph-docs.info, Node: igraph_vector_list_clear --- Removes all elements from a list of vectors_, Next: igraph_vector_list_reserve --- Reserves memory for a list_, Up: Resizing operations <2> + +7.9.5.1 igraph_vector_list_clear -- Removes all elements from a list of vectors. +................................................................................ + + + void igraph_vector_list_clear(igraph_vector_list_t *v); + + This function sets the size of the list to zero, and it also destroys +all the vectors that were placed in the list before clearing it. + + *Arguments:. * + +‘v’: + The list object. + + Time complexity: O(n), n is the number of items being deleted. + + +File: igraph-docs.info, Node: igraph_vector_list_reserve --- Reserves memory for a list_, Next: igraph_vector_list_resize --- Resizes the list of vectors_, Prev: igraph_vector_list_clear --- Removes all elements from a list of vectors_, Up: Resizing operations <2> + +7.9.5.2 igraph_vector_list_reserve -- Reserves memory for a list. +................................................................. + + + igraph_error_t igraph_vector_list_reserve(igraph_vector_list_t *v, igraph_int_t capacity); + + ‘igraph’ lists are flexible, they can grow and shrink. Growing +however occasionally needs the data in the list to be copied. In order +to avoid this, you can call this function to reserve space for future +growth of the list. + + Note that this function does _not_ change the size of the list, +neither does it initialize any new vectors. Let us see a small example +to clarify things: if you reserve space for 100 elements and the size of +your list was (and still is) 60, then you can surely add additional 40 +new vectors to your list before it will be copied. + + *Arguments:. * + +‘v’: + The list object. + +‘capacity’: + The new _allocated_ size of the list. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, should be around O(n), n +is the new allocated size of the list. + + +File: igraph-docs.info, Node: igraph_vector_list_resize --- Resizes the list of vectors_, Next: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_, Prev: igraph_vector_list_reserve --- Reserves memory for a list_, Up: Resizing operations <2> + +7.9.5.3 igraph_vector_list_resize -- Resizes the list of vectors. +................................................................. + + + igraph_error_t igraph_vector_list_resize(igraph_vector_list_t *v, igraph_int_t new_size); + + Note that this function does not free any memory, just sets the size +of the list to the given one. It can on the other hand allocate more +memory if the new size is larger than the previous one. + + When the new size is larger than the current size, the newly added +vectors in the list are initialized to empty vectors. When the new size +is smaller than the current size, the vectors that were removed from the +end of the list are destroyed automatically. + + *Arguments:. * + +‘v’: + The list object + +‘new_size’: + The new size of the list. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory. Note + that this function _never_ returns an error if the list is made + smaller. + + *See also:. * + +‘’ + ‘igraph_vector_list_reserve()’ (*note igraph_vector_list_reserve + --- Reserves memory for a list_::) for allocating memory for future + extensions of a list. + + Time complexity: O(m) if the new size is smaller (m is the number of +items that were removed from the list), operating system dependent if +the new size is larger. In the latter case it is usually around O(n), +where n is the new size of the vector. + + +File: igraph-docs.info, Node: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_, Next: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_, Prev: igraph_vector_list_resize --- Resizes the list of vectors_, Up: Resizing operations <2> + +7.9.5.4 igraph_vector_list_push_back -- Appends an existing vector to the list, transferring ownership. +....................................................................................................... + + + igraph_error_t igraph_vector_list_push_back(igraph_vector_list_t *v, igraph_vector_t *e); + + This function resizes the list to be one element longer, and sets the +very last element in the list to the specified vector ‘e’ . The list +takes ownership of the vector so the user is not responsible for freeing +‘e’ any more; the vector will be destroyed when the list itself is +destroyed or if ‘e’ gets removed from the list without passing on the +ownership to somewhere else. + + *Arguments:. * + +‘v’: + The list object. + +‘e’: + Pointer to the vector to append to the list. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: operating system dependent. What is important is +that a sequence of n subsequent calls to this function has time +complexity O(n), even if there hadn't been any space reserved for the +new elements by ‘igraph_vector_list_reserve()’ (*note +igraph_vector_list_reserve --- Reserves memory for a list_::). This is +implemented by a trick similar to the C++ ‘vector’ class: each time more +memory is allocated for a vector, the size of the additionally allocated +memory is the same as the vector's current length. (We assume here that +the time complexity of memory allocation is at most linear). + + +File: igraph-docs.info, Node: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_, Next: igraph_vector_list_push_back_new --- Appends a new vector to the list_, Prev: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_, Up: Resizing operations <2> + +7.9.5.5 igraph_vector_list_push_back_copy -- Appends the copy of a vector to the list. +...................................................................................... + + + igraph_error_t igraph_vector_list_push_back_copy(igraph_vector_list_t *v, const igraph_vector_t *e); + + This function resizes the list to be one element longer, and copies +the specified vector given as an argument to the last element. The +newly added element is owned by the list, but the ownership of the +original vector is retained at the caller. + + *Arguments:. * + +‘v’: + The list object. + +‘e’: + Pointer to the vector to copy to the end of the list. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: same as ‘igraph_vector_list_push_back()’ (*note +igraph_vector_list_push_back --- Appends an existing vector to the list; +transferring ownership_::) plus the time needed to copy the vector +(which is O(n) for n elements in the vector). + + +File: igraph-docs.info, Node: igraph_vector_list_push_back_new --- Appends a new vector to the list_, Next: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_, Prev: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_, Up: Resizing operations <2> + +7.9.5.6 igraph_vector_list_push_back_new -- Appends a new vector to the list. +............................................................................. + + + igraph_error_t igraph_vector_list_push_back_new(igraph_vector_list_t *v, igraph_vector_t** e); + + This function resizes the list to be one element longer. The newly +added element will be an empty vector that is owned by the list. A +pointer to the newly added element is returned in the last argument if +it is not ‘NULL’ . + + *Arguments:. * + +‘v’: + The list object. + +‘result’: + Pointer to a vector pointer; this will be updated to point to the + newly added vector. May be ‘NULL’ if you do not need a pointer to + the newly added vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: same as ‘igraph_vector_list_push_back()’ (*note +igraph_vector_list_push_back --- Appends an existing vector to the list; +transferring ownership_::). + + +File: igraph-docs.info, Node: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_, Next: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_, Prev: igraph_vector_list_push_back_new --- Appends a new vector to the list_, Up: Resizing operations <2> + +7.9.5.7 igraph_vector_list_pop_back -- Removes the last item from the vector list and transfer ownership to the caller. +....................................................................................................................... + + + igraph_vector_t igraph_vector_list_pop_back(igraph_vector_list_t *v); + + This function removes the last vector from the list. The vector that +was removed from the list is returned and its ownership is passed back +to the caller; in other words, the caller becomes responsible for +destroying the vector when it is not needed any more. + + It is an error to call this function with an empty vector. + + *Arguments:. * + +‘v’: + The list object. + +‘result’: + Pointer to an ‘igraph_vector_t’ (*note About igraph_vector_t + objects::) object; it will be updated to the item that was removed + from the list. Ownership of this vector is passed on to the + caller. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_, Next: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_, Prev: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_, Up: Resizing operations <2> + +7.9.5.8 igraph_vector_list_insert -- Inserts an existing vector into the list, transferring ownership. +...................................................................................................... + + + igraph_error_t igraph_vector_list_insert(igraph_vector_list_t *v, igraph_int_t pos, igraph_vector_t *e); + + This function inserts ‘e’ into the list at the given index, moving +other items towards the end of the list as needed. The list takes +ownership of the vector so the user is not responsible for freeing ‘e’ +any more; the vector will be destroyed when the list itself is destroyed +or if ‘e’ gets removed from the list without passing on the ownership to +somewhere else. + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The position where the new element is to be inserted. + +‘e’: + Pointer to the vector to insert into the list. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: O(n). + + +File: igraph-docs.info, Node: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_, Next: igraph_vector_list_insert_new --- Inserts a new vector into the list_, Prev: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_, Up: Resizing operations <2> + +7.9.5.9 igraph_vector_list_insert_copy -- Inserts the copy of a vector to the list. +................................................................................... + + + igraph_error_t igraph_vector_list_insert_copy(igraph_vector_list_t *v, igraph_int_t pos, const igraph_vector_t *e); + + This function inserts a copy of ‘e’ into the list at the given index, +moving other items towards the end of the list as needed. The newly +added element is owned by the list, but the ownership of the original +vector is retained at the caller. + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The position where the new element is to be inserted. + +‘e’: + Pointer to the vector to copy to the end of the list. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: same as ‘igraph_vector_list_insert()’ (*note +igraph_vector_list_insert --- Inserts an existing vector into the list; +transferring ownership_::) plus the time needed to copy the vector +(which is O(n) for n elements in the vector). + + +File: igraph-docs.info, Node: igraph_vector_list_insert_new --- Inserts a new vector into the list_, Next: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_, Prev: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_, Up: Resizing operations <2> + +7.9.5.10 igraph_vector_list_insert_new -- Inserts a new vector into the list. +............................................................................. + + + igraph_error_t igraph_vector_list_insert_new(igraph_vector_list_t *v, igraph_int_t pos, igraph_vector_t** e); + + This function inserts a newly created empty vector into the list at +the given index, moving other items towards the end of the list as +needed. The newly added vector is owned by the list. A pointer to the +new element is returned in the last argument if it is not ‘NULL’ . + + *Arguments:. * + +‘v’: + The list object. + +‘pos’: + The position where the new element is to be inserted. + +‘result’: + Pointer to a vector pointer; this will be updated to point to the + newly added vector. May be ‘NULL’ if you do not need a pointer to + the newly added vector. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory. + + Time complexity: same as ‘igraph_vector_list_push_back()’ (*note +igraph_vector_list_push_back --- Appends an existing vector to the list; +transferring ownership_::). + + +File: igraph-docs.info, Node: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_, Next: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_, Prev: igraph_vector_list_insert_new --- Inserts a new vector into the list_, Up: Resizing operations <2> + +7.9.5.11 igraph_vector_list_remove -- Removes the item at the given index from the vector list and transfer ownership to the caller. +.................................................................................................................................... + + + igraph_error_t igraph_vector_list_remove(igraph_vector_list_t *v, igraph_int_t index, igraph_vector_t *result); + + This function removes the vector at the given index from the list, +and moves all subsequent items in the list by one slot to the left to +fill the gap. The vector that was removed from the list is returned in +‘e’ and its ownership is passed back to the caller; in other words, the +caller becomes responsible for destroying the vector when it is not +needed any more. + + *Arguments:. * + +‘v’: + The list object. + +‘index’: + Index of the item to be removed. + +‘result’: + Pointer to an ‘igraph_vector_t’ (*note About igraph_vector_t + objects::) object; it will be updated to the item that was removed + from the list. Ownership of this vector is passed on to the + caller. It is an error to supply a null pointer here. + + *See also:. * + +‘’ + ‘igraph_vector_list_discard()’ (*note igraph_vector_list_discard + --- Discards the item at the given index in the vector list_::) if + you are not interested in the item that was removed, + ‘igraph_vector_list_remove_fast()’ (*note + igraph_vector_list_remove_fast --- Removes the item at the given + index in the vector list; move the last item to its place and + transfer ownership to the caller_::) if you do not care about the + order of the items in the list. + + Time complexity: O(n), where n is the number of items in the list. + + +File: igraph-docs.info, Node: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_, Next: igraph_vector_list_discard --- Discards the item at the given index in the vector list_, Prev: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_, Up: Resizing operations <2> + +7.9.5.12 igraph_vector_list_remove_fast -- Removes the item at the given index in the vector list, move the last item to its place and transfer ownership to the caller. +........................................................................................................................................................................ + + + igraph_error_t igraph_vector_list_remove_fast(igraph_vector_list_t *v, igraph_int_t index, igraph_vector_t *result); + + This function removes the vector at the given index from the list, +moves the last item in the list to ‘index’ to fill the gap, and then +transfers ownership of the removed vector back to the caller; in other +words, the caller becomes responsible for destroying the vector when it +is not needed any more. + + *Arguments:. * + +‘v’: + The list object. + +‘index’: + Index of the item to be removed. + +‘result’: + Pointer to an ‘igraph_vector_t’ (*note About igraph_vector_t + objects::) object; it will be updated to the item that was removed + from the list. Ownership of this vector is passed on to the + caller. It is an error to supply a null pointer here. + + *See also:. * + +‘’ + ‘igraph_vector_list_remove()’ (*note igraph_vector_list_remove --- + Removes the item at the given index from the vector list and + transfer ownership to the caller_::) if you want to preserve the + order of the items in the list, ‘igraph_vector_list_discard_fast()’ + (*note igraph_vector_list_discard_fast --- Discards the item at the + given index in the vector list and moves the last item to its + place_::) if you are not interested in the item that was removed. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_discard --- Discards the item at the given index in the vector list_, Next: igraph_vector_list_discard_back --- Discards the last item in the vector list_, Prev: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_, Up: Resizing operations <2> + +7.9.5.13 igraph_vector_list_discard -- Discards the item at the given index in the vector list. +............................................................................................... + + + void igraph_vector_list_discard(igraph_vector_list_t *v, igraph_int_t index); + + This function removes the vector at the given index from the list, +and moves all subsequent items in the list by one slot to the left to +fill the gap. The vector that was removed from the list is destroyed +automatically. + + *Arguments:. * + +‘v’: + The list object. + +‘index’: + Index of the item to be discarded and destroyed. + + *See also:. * + +‘’ + ‘igraph_vector_list_discard_fast()’ (*note + igraph_vector_list_discard_fast --- Discards the item at the given + index in the vector list and moves the last item to its place_::) + if you do not care about the order of the items in the list, + ‘igraph_vector_list_remove()’ (*note igraph_vector_list_remove --- + Removes the item at the given index from the vector list and + transfer ownership to the caller_::) if you want to gain ownership + of the item that was removed instead of destroying it. + + Time complexity: O(n), where n is the number of items in the list. + + +File: igraph-docs.info, Node: igraph_vector_list_discard_back --- Discards the last item in the vector list_, Next: igraph_vector_list_discard_fast --- Discards the item at the given index in the vector list and moves the last item to its place_, Prev: igraph_vector_list_discard --- Discards the item at the given index in the vector list_, Up: Resizing operations <2> + +7.9.5.14 igraph_vector_list_discard_back -- Discards the last item in the vector list. +...................................................................................... + + + void igraph_vector_list_discard_back(igraph_vector_list_t *v); + + This function removes the last vector from the list and destroys it. + + *Arguments:. * + +‘v’: + The list object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_discard_fast --- Discards the item at the given index in the vector list and moves the last item to its place_, Prev: igraph_vector_list_discard_back --- Discards the last item in the vector list_, Up: Resizing operations <2> + +7.9.5.15 igraph_vector_list_discard_fast -- Discards the item at the given index in the vector list and moves the last item to its place. +......................................................................................................................................... + + + void igraph_vector_list_discard_fast(igraph_vector_list_t *v, igraph_int_t index); + + This function removes the vector at the given index from the list, +and moves the last item in the list to ‘index’ to fill the gap. The +vector that was removed from the list is destroyed automatically. + + *Arguments:. * + +‘v’: + The list object. + +‘index’: + Index of the item to be discarded and destroyed. + + *See also:. * + +‘’ + ‘igraph_vector_list_discard()’ (*note igraph_vector_list_discard + --- Discards the item at the given index in the vector list_::) if + you want to preserve the order of the items in the list, + ‘igraph_vector_list_remove_fast()’ (*note + igraph_vector_list_remove_fast --- Removes the item at the given + index in the vector list; move the last item to its place and + transfer ownership to the caller_::) if you want to gain ownership + of the item that was removed instead of destroying it. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Sorting and reordering, Prev: Resizing operations <2>, Up: Lists of vectors; matrices and graphs + +7.9.6 Sorting and reordering +---------------------------- + +* Menu: + +* igraph_vector_list_permute -- Permutes the elements of a list in place according to an index vector.: igraph_vector_list_permute --- Permutes the elements of a list in place according to an index vector_. +* igraph_vector_list_sort -- Sorts the elements of the list into ascending order.: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_. +* igraph_vector_list_sort_ind -- Returns a permutation of indices that sorts the list.: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_. +* igraph_vector_list_swap -- Swaps all elements of two vector lists.: igraph_vector_list_swap --- Swaps all elements of two vector lists_. +* igraph_vector_list_swap_elements -- Swap two elements in a vector list.: igraph_vector_list_swap_elements --- Swap two elements in a vector list_. + + +File: igraph-docs.info, Node: igraph_vector_list_permute --- Permutes the elements of a list in place according to an index vector_, Next: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_, Up: Sorting and reordering + +7.9.6.1 igraph_vector_list_permute -- Permutes the elements of a list in place according to an index vector. +............................................................................................................ + + + igraph_error_t igraph_vector_list_permute(igraph_vector_list_t *v, const igraph_vector_int_t* index); + + This function takes a list ‘v’ and a corresponding index vector +‘index’, and permutes the elements of ‘v’ such that ‘v’[index[i]] is +moved to become ‘v’[i] after the function is executed. + + It is an error to call this function with an index vector that does +not represent a valid permutation. Each element in the index vector +must be between 0 and the length of the list minus one (inclusive), and +each such element must appear only once. The function does not attempt +to validate the index vector. Memory may be leaked if the index vector +does not satisfy these conditions. + + The index vector that this function takes is compatible with the +index vector returned from ‘igraph_vector_list_sort_ind()’ (*note +igraph_vector_list_sort_ind --- Returns a permutation of indices that +sorts the list_::); passing in the index vector from +‘igraph_vector_list_sort_ind()’ (*note igraph_vector_list_sort_ind --- +Returns a permutation of indices that sorts the list_::) will sort the +original vector. + + *Arguments:. * + +‘v’: + the list to permute + +‘index’: + the index vector + + Time complexity: O(n), the number of items in the list. + + +File: igraph-docs.info, Node: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_, Next: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_, Prev: igraph_vector_list_permute --- Permutes the elements of a list in place according to an index vector_, Up: Sorting and reordering + +7.9.6.2 igraph_vector_list_sort -- Sorts the elements of the list into ascending order. +....................................................................................... + + + void igraph_vector_list_sort(igraph_vector_list_t *v, int (*cmp)(const igraph_vector_t*, const igraph_vector_t*)); + + *Arguments:. * + +‘v’: + Pointer to an initialized list object. + +‘cmp’: + A comparison function that takes pointers to two vectors and + returns zero if the two vectors are considered equal, any negative + number if the first vector is smaller and any positive number if + the second vector is smaller. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n log n) for n elements. + + +File: igraph-docs.info, Node: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_, Next: igraph_vector_list_swap --- Swaps all elements of two vector lists_, Prev: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_, Up: Sorting and reordering + +7.9.6.3 igraph_vector_list_sort_ind -- Returns a permutation of indices that sorts the list. +............................................................................................ + + + igraph_error_t igraph_vector_list_sort_ind( + igraph_vector_list_t *v, igraph_vector_int_t *inds, + int (*cmp)(const igraph_vector_t*, const igraph_vector_t*) + ); + + Takes an unsorted list ‘v’ as input and computes an array of indices +‘inds’ such that v[ inds[i] ], with i increasing from 0, is an ordered +array according to the comparison function ‘cmp’. The order of indices +for identical elements is not defined. + + *Arguments:. * + +‘v’: + the list to be sorted + +‘inds’: + the output array of indices. This must be initialized, but will be + resized + +‘cmp’: + A comparison function that takes pointers to two vectors and + returns zero if the two vectors are considered equal, any negative + number if the first vector is smaller and any positive number if + the second vector is smaller. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n log n) for n elements. + + +File: igraph-docs.info, Node: igraph_vector_list_swap --- Swaps all elements of two vector lists_, Next: igraph_vector_list_swap_elements --- Swap two elements in a vector list_, Prev: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_, Up: Sorting and reordering + +7.9.6.4 igraph_vector_list_swap -- Swaps all elements of two vector lists. +.......................................................................... + + + void igraph_vector_list_swap(igraph_vector_list_t *v1, igraph_vector_list_t *v2); + + *Arguments:. * + +‘v1’: + The first list. + +‘v2’: + The second list. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vector_list_swap_elements --- Swap two elements in a vector list_, Prev: igraph_vector_list_swap --- Swaps all elements of two vector lists_, Up: Sorting and reordering + +7.9.6.5 igraph_vector_list_swap_elements -- Swap two elements in a vector list. +............................................................................... + + + void igraph_vector_list_swap_elements(igraph_vector_list_t *v1, igraph_int_t i, igraph_int_t j); + + Note that currently no range checking is performed. + + *Arguments:. * + +‘v’: + The input list. + +‘i’: + Index of the first element. + +‘j’: + Index of the second element (may be the same as the first one). + + *Returns:. * + +‘’ + Error code, currently always ‘IGRAPH_SUCCESS’. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Adjacency lists, Next: Partial prefix sum trees, Prev: Lists of vectors; matrices and graphs, Up: Data structure library; vector; matrix; other data types + +7.10 Adjacency lists +==================== + +Sometimes it is easier to work with a graph which is in adjacency list +format: a list of vectors; each vector contains the neighbor vertices or +incident edges of a given vertex. Typically, this representation is +good if we need to iterate over the neighbors of all vertices many +times. E.g. when finding the shortest paths between all pairs of +vertices or calculating closeness centrality for all the vertices. + + The ‘igraph_adjlist_t’ stores the adjacency lists of a graph. After +creation it is independent of the original graph, it can be modified +freely with the usual vector operations, the graph is not affected. +E.g. the adjacency list can be used to rewire the edges of a graph +efficiently. If one used the straightforward ‘igraph_delete_edges()’ +(*note igraph_delete_edges --- Removes edges from a graph_::) and +‘igraph_add_edges()’ (*note igraph_add_edges --- Adds edges to a graph +object_::) combination for this that needs O(|V|+|E|) time for every +single deletion and insertion operation, it is thus very slow if many +edges are rewired. Extracting the graph into an adjacency list, do all +the rewiring operations on the vectors of the adjacency list and then +creating a new graph needs (depending on how exactly the rewiring is +done) typically O(|V|+|E|) time for the whole rewiring process. + + Lazy adjacency lists are a bit different. When creating a lazy +adjacency list, the neighbors of the vertices are not queried, only some +memory is allocated for the vectors. When ‘igraph_lazy_adjlist_get()’ +(*note igraph_lazy_adjlist_get --- Query neighbor vertices_::) is called +for vertex v the first time, the neighbors of v are queried and stored +in a vector of the adjacency list, so they don't need to be queried +again. Lazy adjacency lists are handy if you have an at least linear +operation (because initialization is generally linear in terms of the +number of vertices), but you don't know how many vertices you will visit +during the computation. + + * File examples/simple/adjlist.c* + +* Menu: + +* Adjacent vertices:: +* Incident edges:: +* Lazy adjacency list for vertices:: +* Lazy incidence list for edges:: + + +File: igraph-docs.info, Node: Adjacent vertices, Next: Incident edges, Up: Adjacency lists + +7.10.1 Adjacent vertices +------------------------ + +* Menu: + +* igraph_adjlist_init -- Constructs an adjacency list of vertices from a given graph.: igraph_adjlist_init --- Constructs an adjacency list of vertices from a given graph_. +* igraph_adjlist_init_empty -- Initializes an empty adjacency list.: igraph_adjlist_init_empty --- Initializes an empty adjacency list_. +* igraph_adjlist_init_complementer -- Adjacency lists for the complementer graph.: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_. +* igraph_adjlist_init_from_inclist -- Constructs an adjacency list of vertices from an incidence list.: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_. +* igraph_adjlist_destroy -- Deallocates an adjacency list.: igraph_adjlist_destroy --- Deallocates an adjacency list_. +* igraph_adjlist_get -- Query a vector in an adjacency list.: igraph_adjlist_get --- Query a vector in an adjacency list_. +* igraph_adjlist_size -- Returns the number of vertices in an adjacency list.: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_. +* igraph_adjlist_clear -- Removes all edges from an adjacency list.: igraph_adjlist_clear --- Removes all edges from an adjacency list_. +* igraph_adjlist_sort -- Sorts each vector in an adjacency list.: igraph_adjlist_sort --- Sorts each vector in an adjacency list_. +* igraph_adjlist_simplify -- Simplifies an adjacency list.: igraph_adjlist_simplify --- Simplifies an adjacency list_. + + +File: igraph-docs.info, Node: igraph_adjlist_init --- Constructs an adjacency list of vertices from a given graph_, Next: igraph_adjlist_init_empty --- Initializes an empty adjacency list_, Up: Adjacent vertices + +7.10.1.1 igraph_adjlist_init -- Constructs an adjacency list of vertices from a given graph. +............................................................................................ + + + igraph_error_t igraph_adjlist_init(const igraph_t *graph, igraph_adjlist_t *al, + igraph_neimode_t mode, igraph_loops_t loops, + igraph_bool_t multiple); + + Creates a list of vectors containing the neighbors of all vertices in +a graph. The adjacency list is independent of the graph after creation, +e.g. the graph can be destroyed and modified, the adjacency list +contains the state of the graph at the time of its initialization. + + This function returns each neighbor list in sorted order, just like +‘igraph_neighbors()’ (*note igraph_neighbors --- Adjacent vertices to a +vertex_::). However, adjacency lists _"in_ general" are not guaranteed +to be sorted, and we reserve the right to change the ordering of +vertices in the result in the future without considering this a breaking +change. If you need to ensure that the adjacency lists are sorted, you +can use ‘igraph_adjlist_sort()’ (*note igraph_adjlist_sort --- Sorts +each vector in an adjacency list_::) to sort all the adjacency lists, or +call ‘igraph_vector_int_sort()’ (*note igraph_vector_sort --- Sorts the +elements of the vector into ascending order_::) on the individual +adjacency vectors after the initialization. + + As of igraph 0.10, there is a small performance cost to setting +‘loops’ to a different value than ‘IGRAPH_LOOPS_TWICE’ or setting +‘multiple’ to a different value from ‘IGRAPH_MULTIPLE’. + + *Arguments:. * + +‘graph’: + The input graph. + +‘al’: + Pointer to an uninitialized ‘igraph_adjlist_t’ object. + +‘mode’: + Constant specifying whether to include only outgoing + (‘IGRAPH_OUT’), only incoming (‘IGRAPH_IN’), or both (‘IGRAPH_ALL’) + types of neighbors in the adjacency list. It is ignored for + undirected graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the adjacency list. ‘IGRAPH_LOOPS_ONCE’ makes each loop + edge appear only once in the adjacency list of the corresponding + vertex. ‘IGRAPH_LOOPS_TWICE’ makes loop edges appear _twice_ in + the adjacency list of the corresponding vertex, but only if the + graph is undirected or ‘mode’ is set to ‘IGRAPH_ALL’. + +‘multiple’: + Specifies how to treat multiple (parallel) edges. + ‘IGRAPH_NO_MULTIPLE’ collapses parallel edges into a single one; + ‘IGRAPH_MULTIPLE’ keeps the multiplicities of parallel edges so the + same vertex will appear as many times in the adjacency list of + another vertex as the number of parallel edges going between the + two vertices. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_neighbors()’ (*note igraph_neighbors --- Adjacent vertices + to a vertex_::) for getting the neighbor lists of individual + vertices. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + +File: igraph-docs.info, Node: igraph_adjlist_init_empty --- Initializes an empty adjacency list_, Next: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_, Prev: igraph_adjlist_init --- Constructs an adjacency list of vertices from a given graph_, Up: Adjacent vertices + +7.10.1.2 igraph_adjlist_init_empty -- Initializes an empty adjacency list. +.......................................................................... + + + igraph_error_t igraph_adjlist_init_empty(igraph_adjlist_t *al, igraph_int_t no_of_nodes); + + Creates a list of vectors, one for each vertex. This is useful when +you are _constructing_ a graph using an adjacency list representation as +it does not require your graph to exist yet. + + *Arguments:. * + +‘al’: + Pointer to an uninitialized ‘igraph_adjlist_t’ object. + +‘no_of_nodes’: + The number of vertices + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), linear in the number of vertices. + + +File: igraph-docs.info, Node: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_, Next: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_, Prev: igraph_adjlist_init_empty --- Initializes an empty adjacency list_, Up: Adjacent vertices + +7.10.1.3 igraph_adjlist_init_complementer -- Adjacency lists for the complementer graph. +........................................................................................ + + + igraph_error_t igraph_adjlist_init_complementer(const igraph_t *graph, + igraph_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops); + + This function creates adjacency lists for the complementer of the +input graph. In the complementer graph all edges are present which are +not present in the original graph. Multiple edges in the input graph +are ignored. + + This function returns each neighbor list in sorted order. + + *Arguments:. * + +‘graph’: + The input graph. + +‘al’: + Pointer to a not yet initialized adjacency list. + +‘mode’: + Constant specifying whether outgoing (‘IGRAPH_OUT’), incoming + (‘IGRAPH_IN’), or both (‘IGRAPH_ALL’) types of neighbors (in the + complementer graph) to include in the adjacency list. It is + ignored for undirected networks. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ will not + include loops edges in the returned adjacency list. + ‘IGRAPH_LOOPS_ONCE’ will include vertex ‘i’ in the adjacency list + of vetex ‘i’ _once_ if the original graph did not have a loop edge + incident on vertex ‘i’, while ‘IGRAPH_LOOPS_TWICE’ will include + vertex ‘i’ _twice_ _if_ ‘mode’ is set to ‘IGRAPH_ALL’ (otherwise it + is treated the same way as ‘IGRAPH_LOOPS_ONCE’ ). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_adjlist_init()’ (*note igraph_adjlist_init --- Constructs + an adjacency list of vertices from a given graph_::), + ‘igraph_complementer()’ (*note igraph_complementer --- Creates the + complementer of a graph_::) + + Time complexity: O(|V|^2+|E|), quadratic in the number of vertices. + + +File: igraph-docs.info, Node: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_, Next: igraph_adjlist_destroy --- Deallocates an adjacency list_, Prev: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_, Up: Adjacent vertices + +7.10.1.4 igraph_adjlist_init_from_inclist -- Constructs an adjacency list of vertices from an incidence list. +............................................................................................................. + + + igraph_error_t igraph_adjlist_init_from_inclist( + const igraph_t *graph, igraph_adjlist_t *al, const igraph_inclist_t *il); + + In some algorithms it is useful to have an adjacency list _and_ an +incidence list representation of the same graph, and in many cases it is +the most useful if they are consistent with each other, i.e. if can be +guaranteed that the vertex ID in the i-th entry of the adjacency list of +vertex v is the _other_ endpoint of the edge in the i-th entry of the +incidence list of the same vertex. This function creates such an +adjacency list from the corresponding incidence list by looking up the +endpoints of each edge in the incidence list and constructing the +corresponding adjacenecy vectors. + + The adjacency list is independent of the graph or the incidence list +after creation; in other words, modifications that are made to the graph +or the incidence list are not reflected in the adjacency list. + + *Arguments:. * + +‘graph’: + The input graph. + +‘al’: + Pointer to an uninitialized ‘igraph_adjlist_t’ object. + +‘il’: + Pointer to an _initialized_ ‘igraph_inclist_t’ object that will be + converted into an adjacency list. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + +File: igraph-docs.info, Node: igraph_adjlist_destroy --- Deallocates an adjacency list_, Next: igraph_adjlist_get --- Query a vector in an adjacency list_, Prev: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_, Up: Adjacent vertices + +7.10.1.5 igraph_adjlist_destroy -- Deallocates an adjacency list. +................................................................. + + + void igraph_adjlist_destroy(igraph_adjlist_t *al); + + Free all memory allocated for an adjacency list. + + *Arguments:. * + +‘al’: + The adjacency list to destroy. + + Time complexity: O(n), where n is the size of the adjacency list. + + +File: igraph-docs.info, Node: igraph_adjlist_get --- Query a vector in an adjacency list_, Next: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_, Prev: igraph_adjlist_destroy --- Deallocates an adjacency list_, Up: Adjacent vertices + +7.10.1.6 igraph_adjlist_get -- Query a vector in an adjacency list. +................................................................... + + + #define igraph_adjlist_get(al,no) + + Returns a pointer to an ‘igraph_vector_int_t’ (*note About +igraph_vector_t objects::) object from an adjacency list. The vector +can be modified as desired. + + *Arguments:. * + +‘al’: + The adjacency list object. + +‘no’: + The vertex whose adjacent vertices will be returned. + + *Returns:. * + +‘’ + Pointer to the ‘igraph_vector_int_t’ (*note About igraph_vector_t + objects::) object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_, Next: igraph_adjlist_clear --- Removes all edges from an adjacency list_, Prev: igraph_adjlist_get --- Query a vector in an adjacency list_, Up: Adjacent vertices + +7.10.1.7 igraph_adjlist_size -- Returns the number of vertices in an adjacency list. +.................................................................................... + + + igraph_int_t igraph_adjlist_size(const igraph_adjlist_t *al); + + *Arguments:. * + +‘al’: + The adjacency list. + + *Returns:. * + +‘’ + The number of vertices in the adjacency list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_adjlist_clear --- Removes all edges from an adjacency list_, Next: igraph_adjlist_sort --- Sorts each vector in an adjacency list_, Prev: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_, Up: Adjacent vertices + +7.10.1.8 igraph_adjlist_clear -- Removes all edges from an adjacency list. +.......................................................................... + + + void igraph_adjlist_clear(igraph_adjlist_t *al); + + The size of the adjacency list stays unchanged, but all adjacent +vertices will be removed. + + *Arguments:. * + +‘al’: + The adjacency list. + + Time complexity: O(n), where n is the size of the adjacency list. + + +File: igraph-docs.info, Node: igraph_adjlist_sort --- Sorts each vector in an adjacency list_, Next: igraph_adjlist_simplify --- Simplifies an adjacency list_, Prev: igraph_adjlist_clear --- Removes all edges from an adjacency list_, Up: Adjacent vertices + +7.10.1.9 igraph_adjlist_sort -- Sorts each vector in an adjacency list. +....................................................................... + + + void igraph_adjlist_sort(igraph_adjlist_t *al); + + Sorts every vector of the adjacency list. Note that +‘igraph_adjlist_init()’ (*note igraph_adjlist_init --- Constructs an +adjacency list of vertices from a given graph_::) already produces +sorted neighbor lists. This function is useful when the adjacency list +is produced in a different manner, or is modified in a way that does not +preserve the sorted order. + + *Arguments:. * + +‘al’: + The adjacency list. + + Time complexity: O(m log m), m is the total number of neighbors +stored in the adjacency list. + + +File: igraph-docs.info, Node: igraph_adjlist_simplify --- Simplifies an adjacency list_, Prev: igraph_adjlist_sort --- Sorts each vector in an adjacency list_, Up: Adjacent vertices + +7.10.1.10 igraph_adjlist_simplify -- Simplifies an adjacency list. +.................................................................. + + + igraph_error_t igraph_adjlist_simplify(igraph_adjlist_t *al); + + Simplifies an adjacency list, i.e. removes loop and multiple edges. + + When the adjacency list is created with ‘igraph_adjlist_init()’ +(*note igraph_adjlist_init --- Constructs an adjacency list of vertices +from a given graph_::), use the ‘loops’ and ‘multiple’ parameters of +that function instead. + + *Arguments:. * + +‘al’: + The adjacency list. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of edges and +vertices. + + +File: igraph-docs.info, Node: Incident edges, Next: Lazy adjacency list for vertices, Prev: Adjacent vertices, Up: Adjacency lists + +7.10.2 Incident edges +--------------------- + +* Menu: + +* igraph_inclist_init -- Initializes an incidence list.: igraph_inclist_init --- Initializes an incidence list_. +* igraph_inclist_destroy -- Frees all memory allocated for an incidence list.: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_. +* igraph_inclist_get -- Query a vector in an incidence list.: igraph_inclist_get --- Query a vector in an incidence list_. +* igraph_inclist_size -- Returns the number of vertices in an incidence list.: igraph_inclist_size --- Returns the number of vertices in an incidence list_. +* igraph_inclist_clear -- Removes all edges from an incidence list.: igraph_inclist_clear --- Removes all edges from an incidence list_. + + +File: igraph-docs.info, Node: igraph_inclist_init --- Initializes an incidence list_, Next: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_, Up: Incident edges + +7.10.2.1 igraph_inclist_init -- Initializes an incidence list. +.............................................................. + + + igraph_error_t igraph_inclist_init(const igraph_t *graph, + igraph_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops); + + Creates a list of vectors containing the incident edges for all +vertices. The incidence list is independent of the graph after +creation, subsequent changes of the graph object do not update the +incidence list, and changes to the incidence list do not update the +graph. + + When ‘mode’ is ‘IGRAPH_IN’ or ‘IGRAPH_OUT’, each edge ID will appear +in the incidence list _once._ When ‘mode’ is ‘IGRAPH_ALL’, each edge ID +will appear in the incidence list _twice,_ once for the source vertex +and once for the target edge. It also means that the edge IDs of loop +edges may potentially appear _twice_ for the _same_ vertex. Use the +‘loops’ argument to control whether this will be the case +(‘IGRAPH_LOOPS_TWICE’ ) or not (‘IGRAPH_LOOPS_ONCE’ or +‘IGRAPH_NO_LOOPS’). + + As of igraph 0.10, there is a small performance cost to setting +‘loops’ to a different value than ‘IGRAPH_LOOPS_TWICE’. + + *Arguments:. * + +‘graph’: + The input graph. + +‘il’: + Pointer to an uninitialized incidence list. + +‘mode’: + Constant specifying whether incoming edges (‘IGRAPH_IN’), outgoing + edges (‘IGRAPH_OUT’) or both (‘IGRAPH_ALL’) to include in the + incidence lists of directed graphs. It is ignored for undirected + graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the incidence list. ‘IGRAPH_LOOPS_ONCE’ makes each loop + edge appear only once in the incidence list of the corresponding + vertex. ‘IGRAPH_LOOPS_TWICE’ makes loop edges appear _twice_ in + the incidence list of the corresponding vertex, but only if the + graph is undirected or ‘mode’ is set to ‘IGRAPH_ALL’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + +File: igraph-docs.info, Node: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_, Next: igraph_inclist_get --- Query a vector in an incidence list_, Prev: igraph_inclist_init --- Initializes an incidence list_, Up: Incident edges + +7.10.2.2 igraph_inclist_destroy -- Frees all memory allocated for an incidence list. +.................................................................................... + + + void igraph_inclist_destroy(igraph_inclist_t *il); + + *Arguments:. * + +‘il’: + The incidence list to destroy. + + Time complexity: O(n), where n is the size of the incidence list. + + +File: igraph-docs.info, Node: igraph_inclist_get --- Query a vector in an incidence list_, Next: igraph_inclist_size --- Returns the number of vertices in an incidence list_, Prev: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_, Up: Incident edges + +7.10.2.3 igraph_inclist_get -- Query a vector in an incidence list. +................................................................... + + + #define igraph_inclist_get(il,no) + + Returns a pointer to an ‘igraph_vector_int_t’ object from an +incidence list containing edge IDs. The vector can be modified, +resized, etc. as desired. + + *Arguments:. * + +‘il’: + Pointer to the incidence list. + +‘no’: + The vertex for which the incident edges are returned. + + *Returns:. * + +‘’ + Pointer to an ‘igraph_vector_int_t’ object. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_inclist_size --- Returns the number of vertices in an incidence list_, Next: igraph_inclist_clear --- Removes all edges from an incidence list_, Prev: igraph_inclist_get --- Query a vector in an incidence list_, Up: Incident edges + +7.10.2.4 igraph_inclist_size -- Returns the number of vertices in an incidence list. +.................................................................................... + + + igraph_int_t igraph_inclist_size(const igraph_inclist_t *il); + + *Arguments:. * + +‘il’: + The incidence list. + + *Returns:. * + +‘’ + The number of vertices in the incidence list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_inclist_clear --- Removes all edges from an incidence list_, Prev: igraph_inclist_size --- Returns the number of vertices in an incidence list_, Up: Incident edges + +7.10.2.5 igraph_inclist_clear -- Removes all edges from an incidence list. +.......................................................................... + + + void igraph_inclist_clear(igraph_inclist_t *il); + + The size of the incidence list stays unchanged, but all incident +edges will be removed. + + *Arguments:. * + +‘il’: + The incidence list. + + Time complexity: O(n), where n is the size of the incidence list. + + +File: igraph-docs.info, Node: Lazy adjacency list for vertices, Next: Lazy incidence list for edges, Prev: Incident edges, Up: Adjacency lists + +7.10.3 Lazy adjacency list for vertices +--------------------------------------- + +* Menu: + +* igraph_lazy_adjlist_init -- Initializes a lazy adjacency list.: igraph_lazy_adjlist_init --- Initializes a lazy adjacency list_. +* igraph_lazy_adjlist_destroy -- Deallocate a lazt adjacency list.: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_. +* igraph_lazy_adjlist_get -- Query neighbor vertices.: igraph_lazy_adjlist_get --- Query neighbor vertices_. +* igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?:: +* igraph_lazy_adjlist_size -- Returns the number of vertices in a lazy adjacency list.: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_. +* igraph_lazy_adjlist_clear -- Removes all edges from a lazy adjacency list.: igraph_lazy_adjlist_clear --- Removes all edges from a lazy adjacency list_. + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_init --- Initializes a lazy adjacency list_, Next: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_, Up: Lazy adjacency list for vertices + +7.10.3.1 igraph_lazy_adjlist_init -- Initializes a lazy adjacency list. +....................................................................... + + + igraph_error_t igraph_lazy_adjlist_init(const igraph_t *graph, + igraph_lazy_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops, + igraph_bool_t multiple); + + Create a lazy adjacency list for vertices. This function only +allocates some memory for storing the vectors of an adjacency list, but +the neighbor vertices are not queried, only at the +‘igraph_lazy_adjlist_get()’ (*note igraph_lazy_adjlist_get --- Query +neighbor vertices_::) calls. Neighbor lists will be returned in sorted +order. + + As of igraph 0.10, there is a small performance cost to setting +‘loops’ to a different value than ‘IGRAPH_LOOPS_TWICE’ or setting +‘multiple’ to a different value from ‘IGRAPH_MULTIPLE’. + + *Arguments:. * + +‘graph’: + The input graph. + +‘al’: + Pointer to an uninitialized adjacency list object. + +‘mode’: + Constant specifying whether to include only outgoing + (‘IGRAPH_OUT’), only incoming (‘IGRAPH_IN’), or both (‘IGRAPH_ALL’) + types of neighbors in the adjacency list. It is ignored for + undirected graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the adjacency list. ‘IGRAPH_LOOPS_ONCE’ makes each loop + edge appear only once in the adjacency list of the corresponding + vertex. ‘IGRAPH_LOOPS_TWICE’ makes loop edges appear _twice_ in + the adjacency list of the corresponding vertex, but only if the + graph is undirected or ‘mode’ is set to ‘IGRAPH_ALL’. + +‘multiple’: + Specifies how to treat multiple (parallel) edges. + ‘IGRAPH_NO_MULTIPLE’ collapses parallel edges into a single one; + ‘IGRAPH_MULTIPLE’ keeps the multiplicities of parallel edges so the + same vertex will appear as many times in the adjacency list of + another vertex as the number of parallel edges going between the + two vertices. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_neighbors()’ (*note igraph_neighbors --- Adjacent vertices + to a vertex_::) for getting the neighbor lists of individual + vertices. + + Time complexity: O(|V|), the number of vertices, possibly, but +depends on the underlying memory management too. + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_, Next: igraph_lazy_adjlist_get --- Query neighbor vertices_, Prev: igraph_lazy_adjlist_init --- Initializes a lazy adjacency list_, Up: Lazy adjacency list for vertices + +7.10.3.2 igraph_lazy_adjlist_destroy -- Deallocate a lazt adjacency list. +......................................................................... + + + void igraph_lazy_adjlist_destroy(igraph_lazy_adjlist_t *al); + + Free all allocated memory for a lazy adjacency list. + + *Arguments:. * + +‘al’: + The adjacency list to deallocate. + + Time complexity: depends on the memory management. + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_get --- Query neighbor vertices_, Next: igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?, Prev: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_, Up: Lazy adjacency list for vertices + +7.10.3.3 igraph_lazy_adjlist_get -- Query neighbor vertices. +............................................................ + + + #define igraph_lazy_adjlist_get(al,no) + + If the function is called for the first time for a vertex then the +result is stored in the adjacency list and no further query operations +are needed when the neighbors of the same vertex are queried again. + + *Arguments:. * + +‘al’: + The lazy adjacency list. + +‘no’: + The vertex ID to query. + + *Returns:. * + +‘’ + Pointer to a vector, or ‘NULL’ upon error. Errors can only occur + the first time this function is called for a given vertex. It is + safe to modify this vector, modification does not affect the + original graph. + + *See also:. * + +‘’ + ‘igraph_lazy_adjlist_has()’ (*note igraph_lazy_adjlist_has --- Are + adjacenct vertices already stored in a lazy adjacency list?::) to + check if this function has already been called for a vertex. + + Time complexity: O(d), the number of neighbor vertices for the first +time, O(1) for subsequent calls. + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?, Next: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_, Prev: igraph_lazy_adjlist_get --- Query neighbor vertices_, Up: Lazy adjacency list for vertices + +7.10.3.4 igraph_lazy_adjlist_has -- Are adjacenct vertices already stored in a lazy adjacency list? +................................................................................................... + + + #define igraph_lazy_adjlist_has(al,no) + + *Arguments:. * + +‘al’: + The lazy adjacency list. + +‘no’: + The vertex ID to query. + + *Returns:. * + +‘’ + True if the adjacent vertices of this vertex are already computed + and stored, false otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_, Next: igraph_lazy_adjlist_clear --- Removes all edges from a lazy adjacency list_, Prev: igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?, Up: Lazy adjacency list for vertices + +7.10.3.5 igraph_lazy_adjlist_size -- Returns the number of vertices in a lazy adjacency list. +............................................................................................. + + + igraph_int_t igraph_lazy_adjlist_size(const igraph_lazy_adjlist_t *al); + + *Arguments:. * + +‘al’: + The lazy adjacency list. + + *Returns:. * + +‘’ + The number of vertices in the lazy adjacency list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_lazy_adjlist_clear --- Removes all edges from a lazy adjacency list_, Prev: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_, Up: Lazy adjacency list for vertices + +7.10.3.6 igraph_lazy_adjlist_clear -- Removes all edges from a lazy adjacency list. +................................................................................... + + + void igraph_lazy_adjlist_clear(igraph_lazy_adjlist_t *al); + + *Arguments:. * + +‘al’: + The lazy adjacency list. Time complexity: depends on memory + management, typically O(n), where n is the total number of elements + in the adjacency list. + + +File: igraph-docs.info, Node: Lazy incidence list for edges, Prev: Lazy adjacency list for vertices, Up: Adjacency lists + +7.10.4 Lazy incidence list for edges +------------------------------------ + +* Menu: + +* igraph_lazy_inclist_init -- Initializes a lazy incidence list of edges.: igraph_lazy_inclist_init --- Initializes a lazy incidence list of edges_. +* igraph_lazy_inclist_destroy -- Deallocates a lazy incidence list.: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_. +* igraph_lazy_inclist_get -- Query incident edges.: igraph_lazy_inclist_get --- Query incident edges_. +* igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?:: +* igraph_lazy_inclist_size -- Returns the number of vertices in a lazy incidence list.: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_. +* igraph_lazy_inclist_clear -- Removes all edges from a lazy incidence list.: igraph_lazy_inclist_clear --- Removes all edges from a lazy incidence list_. + + +File: igraph-docs.info, Node: igraph_lazy_inclist_init --- Initializes a lazy incidence list of edges_, Next: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_, Up: Lazy incidence list for edges + +7.10.4.1 igraph_lazy_inclist_init -- Initializes a lazy incidence list of edges. +................................................................................ + + + igraph_error_t igraph_lazy_inclist_init(const igraph_t *graph, + igraph_lazy_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops); + + Create a lazy incidence list for edges. This function only allocates +some memory for storing the vectors of an incidence list, but the +incident edges are not queried, only when ‘igraph_lazy_inclist_get()’ +(*note igraph_lazy_inclist_get --- Query incident edges_::) is called. + + When ‘mode’ is ‘IGRAPH_IN’ or ‘IGRAPH_OUT’, each edge ID will appear +in the incidence list _once._ When ‘mode’ is ‘IGRAPH_ALL’, each edge ID +will appear in the incidence list _twice,_ once for the source vertex +and once for the target edge. It also means that the edge IDs of loop +edges will appear _twice_ for the _same_ vertex. + + As of igraph 0.10, there is a small performance cost to setting +‘loops’ to a different value than ‘IGRAPH_LOOPS_TWICE’. + + *Arguments:. * + +‘graph’: + The input graph. + +‘il’: + Pointer to an uninitialized incidence list. + +‘mode’: + Constant, it gives whether incoming edges (‘IGRAPH_IN’), outgoing + edges (‘IGRAPH_OUT’) or both types of edges (‘IGRAPH_ALL’) are + considered. It is ignored for undirected graphs. + +‘loops’: + Specifies how to treat loop edges. ‘IGRAPH_NO_LOOPS’ removes loop + edges from the incidence list. ‘IGRAPH_LOOPS_ONCE’ makes each loop + edge appear only once in the incidence list of the corresponding + vertex. ‘IGRAPH_LOOPS_TWICE’ makes loop edges appear _twice_ in + the incidence list of the corresponding vertex, but only if the + graph is undirected or ‘mode’ is set to ‘IGRAPH_ALL’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), the number of vertices, possibly. But it +also depends on the underlying memory management. + + +File: igraph-docs.info, Node: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_, Next: igraph_lazy_inclist_get --- Query incident edges_, Prev: igraph_lazy_inclist_init --- Initializes a lazy incidence list of edges_, Up: Lazy incidence list for edges + +7.10.4.2 igraph_lazy_inclist_destroy -- Deallocates a lazy incidence list. +.......................................................................... + + + void igraph_lazy_inclist_destroy(igraph_lazy_inclist_t *il); + + Frees all allocated memory for a lazy incidence list. + + *Arguments:. * + +‘il’: + The incidence list to deallocate. + + Time complexity: depends on memory management. + + +File: igraph-docs.info, Node: igraph_lazy_inclist_get --- Query incident edges_, Next: igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?, Prev: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_, Up: Lazy incidence list for edges + +7.10.4.3 igraph_lazy_inclist_get -- Query incident edges. +......................................................... + + + #define igraph_lazy_inclist_get(il,no) + + If the function is called for the first time for a vertex, then the +result is stored in the incidence list and no further query operations +are needed when the incident edges of the same vertex are queried again. + + *Arguments:. * + +‘il’: + The lazy incidence list object. + +‘no’: + The vertex ID to query. + + *Returns:. * + +‘’ + Pointer to a vector, or ‘NULL’ upon error. Errors can only occur + the first time this function is called for a given vertex. It is + safe to modify this vector, modification does not affect the + original graph. + + *See also:. * + +‘’ + ‘igraph_lazy_inclist_has()’ (*note igraph_lazy_inclist_has --- Are + incident edges already stored in a lazy inclist?::) to check if + this function has already been called for a vertex. + + Time complexity: O(d), the number of incident edges for the first +time, O(1) for subsequent calls with the same ‘no’ argument. + + +File: igraph-docs.info, Node: igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?, Next: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_, Prev: igraph_lazy_inclist_get --- Query incident edges_, Up: Lazy incidence list for edges + +7.10.4.4 igraph_lazy_inclist_has -- Are incident edges already stored in a lazy inclist? +........................................................................................ + + + #define igraph_lazy_inclist_has(il,no) + + *Arguments:. * + +‘il’: + The lazy incidence list. + +‘no’: + The vertex ID to query. + + *Returns:. * + +‘’ + True if the incident edges of this vertex are already computed and + stored, false otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_, Next: igraph_lazy_inclist_clear --- Removes all edges from a lazy incidence list_, Prev: igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?, Up: Lazy incidence list for edges + +7.10.4.5 igraph_lazy_inclist_size -- Returns the number of vertices in a lazy incidence list. +............................................................................................. + + + igraph_int_t igraph_lazy_inclist_size(const igraph_lazy_inclist_t *il); + + *Arguments:. * + +‘il’: + The lazy incidence list. + + *Returns:. * + +‘’ + The number of vertices in the lazy incidence list. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_lazy_inclist_clear --- Removes all edges from a lazy incidence list_, Prev: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_, Up: Lazy incidence list for edges + +7.10.4.6 igraph_lazy_inclist_clear -- Removes all edges from a lazy incidence list. +................................................................................... + + + void igraph_lazy_inclist_clear(igraph_lazy_inclist_t *il); + + *Arguments:. * + +‘il’: + The lazy incidence list. + + Time complexity: depends on memory management, typically O(n), where +n is the total number of elements in the incidence list. + + +File: igraph-docs.info, Node: Partial prefix sum trees, Next: Bitsets, Prev: Adjacency lists, Up: Data structure library; vector; matrix; other data types + +7.11 Partial prefix sum trees +============================= + +The ‘igraph_psumtree_t’ data type represents a partial prefix sum tree. +A partial prefix sum tree is a data structure that can be used to draw +samples from a discrete probability distribution with dynamic +probabilities that are updated frequently. This is achieved by creating +a binary tree where the leaves are the items. Each leaf contains the +probability corresponding to the items. Intermediate nodes of the tree +always contain the sum of its two children. When the value of a leaf +node is updated, the values of its ancestors are also updated +accordingly. + + Samples can be drawn from the probability distribution represented by +the tree by generating a uniform random number between 0 (inclusive) and +the value of the root of the tree (exclusive), and then following the +branches of the tree as follows. In each step, the value in the current +node is compared with the generated number. If the value in the node is +larger, the left branch of the tree is taken; otherwise the generated +number is decreased by the value in the node and the right branch of the +tree is taken, until a leaf node is reached. + + Note that the sampling process works only if all the values in the +tree are non-negative. This is enforced by the object; in particular, +trying to set a negative value for an item will produce an igraph error. + +* Menu: + +* igraph_psumtree_init -- Initializes a partial prefix sum tree.: igraph_psumtree_init --- Initializes a partial prefix sum tree_. +* igraph_psumtree_destroy -- Destroys a partial prefix sum tree.: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_. +* igraph_psumtree_size -- Returns the size of the tree.: igraph_psumtree_size --- Returns the size of the tree_. +* igraph_psumtree_get -- Retrieves the value corresponding to an item in the tree.: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_. +* igraph_psumtree_sum -- Returns the sum of the values of the leaves in the tree.: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_. +* igraph_psumtree_search -- Finds an item in the tree, given a value.: igraph_psumtree_search --- Finds an item in the tree; given a value_. +* igraph_psumtree_update -- Updates the value associated to an item in the tree.: igraph_psumtree_update --- Updates the value associated to an item in the tree_. +* igraph_psumtree_reset -- Resets all the values in the tree to zero.: igraph_psumtree_reset --- Resets all the values in the tree to zero_. + + +File: igraph-docs.info, Node: igraph_psumtree_init --- Initializes a partial prefix sum tree_, Next: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_, Up: Partial prefix sum trees + +7.11.1 igraph_psumtree_init -- Initializes a partial prefix sum tree. +--------------------------------------------------------------------- + + + igraph_error_t igraph_psumtree_init(igraph_psumtree_t *t, igraph_int_t size); + + The tree is initialized with a fixed number of elements. After +initialization, the value corresponding to each element is zero. + + *Arguments:. * + +‘t’: + The tree to initialize. + +‘size’: + The number of elements in the tree. It must be at least one. + + *Returns:. * + +‘’ + Error code, typically ‘IGRAPH_ENOMEM’ if there is not enough + memory. + + Time complexity: O(n) for a tree containing n elements + + +File: igraph-docs.info, Node: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_, Next: igraph_psumtree_size --- Returns the size of the tree_, Prev: igraph_psumtree_init --- Initializes a partial prefix sum tree_, Up: Partial prefix sum trees + +7.11.2 igraph_psumtree_destroy -- Destroys a partial prefix sum tree. +--------------------------------------------------------------------- + + + void igraph_psumtree_destroy(igraph_psumtree_t *t); + + All partial prefix sum trees initialized by ‘igraph_psumtree_init()’ +(*note igraph_psumtree_init --- Initializes a partial prefix sum +tree_::) should be properly destroyed by this function. A destroyed +tree needs to be reinitialized by ‘igraph_psumtree_init()’ (*note +igraph_psumtree_init --- Initializes a partial prefix sum tree_::) if +you want to use it again. + + *Arguments:. * + +‘t’: + Pointer to the (previously initialized) tree to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: igraph_psumtree_size --- Returns the size of the tree_, Next: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_, Prev: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_, Up: Partial prefix sum trees + +7.11.3 igraph_psumtree_size -- Returns the size of the tree. +------------------------------------------------------------ + + + igraph_int_t igraph_psumtree_size(const igraph_psumtree_t *t); + + *Arguments:. * + +‘t’: + The tree object + + *Returns:. * + +‘’ + The number of discrete items in the tree. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_, Next: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_, Prev: igraph_psumtree_size --- Returns the size of the tree_, Up: Partial prefix sum trees + +7.11.4 igraph_psumtree_get -- Retrieves the value corresponding to an item in the tree. +--------------------------------------------------------------------------------------- + + + igraph_real_t igraph_psumtree_get(const igraph_psumtree_t *t, igraph_int_t idx); + + *Arguments:. * + +‘t’: + The tree to query. + +‘idx’: + The index of the item whose value is to be retrieved. + + *Returns:. * + +‘’ + The value corresponding to the item with the given index. + + Time complexity: O(1) + + +File: igraph-docs.info, Node: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_, Next: igraph_psumtree_search --- Finds an item in the tree; given a value_, Prev: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_, Up: Partial prefix sum trees + +7.11.5 igraph_psumtree_sum -- Returns the sum of the values of the leaves in the tree. +-------------------------------------------------------------------------------------- + + + igraph_real_t igraph_psumtree_sum(const igraph_psumtree_t *t); + + *Arguments:. * + +‘t’: + The tree object + + *Returns:. * + +‘’ + The sum of the values of the leaves in the tree. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_psumtree_search --- Finds an item in the tree; given a value_, Next: igraph_psumtree_update --- Updates the value associated to an item in the tree_, Prev: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_, Up: Partial prefix sum trees + +7.11.6 igraph_psumtree_search -- Finds an item in the tree, given a value. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_psumtree_search(const igraph_psumtree_t *t, igraph_int_t *idx, + igraph_real_t search); + + This function finds the item with the lowest index where it holds +that the sum of all the items with a _lower_ index is less than or equal +to the given value and that the sum of all the items with a lower index +plus the item itself is larger than the given value. + + If you think about the partial prefix sum tree as a tool to sample +from a discrete probability distribution, then calling this function +repeatedly with uniformly distributed random numbers in the range 0 +(inclusive) to the sum of all values in the tree (exclusive) will sample +the items in the tree with a probability that is proportional to their +associated values. + + *Arguments:. * + +‘t’: + The tree to query. + +‘idx’: + The index of the item is returned here. + +‘search’: + The value to use for the search. Must be in the interval ‘[0, + sum)’, where ‘sum’ is the sum of all elements (leaves) in the tree. + + *Returns:. * + +‘’ + Error code; currently the search always succeeds. + + Time complexity: O(log n), where n is the number of items in the +tree. + + +File: igraph-docs.info, Node: igraph_psumtree_update --- Updates the value associated to an item in the tree_, Next: igraph_psumtree_reset --- Resets all the values in the tree to zero_, Prev: igraph_psumtree_search --- Finds an item in the tree; given a value_, Up: Partial prefix sum trees + +7.11.7 igraph_psumtree_update -- Updates the value associated to an item in the tree. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_psumtree_update(igraph_psumtree_t *t, igraph_int_t idx, + igraph_real_t new_value); + + *Arguments:. * + +‘t’: + The tree to query. + +‘idx’: + The index of the item to update. + +‘new_value’: + The new value of the item. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_EINVAL’ if the new value is negative or NaN, + ‘IGRAPH_SUCCESS’ if the operation was successful. + + Time complexity: O(log n), where n is the number of items in the +tree. + + +File: igraph-docs.info, Node: igraph_psumtree_reset --- Resets all the values in the tree to zero_, Prev: igraph_psumtree_update --- Updates the value associated to an item in the tree_, Up: Partial prefix sum trees + +7.11.8 igraph_psumtree_reset -- Resets all the values in the tree to zero. +-------------------------------------------------------------------------- + + + void igraph_psumtree_reset(igraph_psumtree_t *t); + + *Arguments:. * + +‘t’: + The tree to reset. + + +File: igraph-docs.info, Node: Bitsets, Prev: Partial prefix sum trees, Up: Data structure library; vector; matrix; other data types + +7.12 Bitsets +============ + +* Menu: + +* About igraph_bitset_t objects:: +* Constructors and destructors: Constructors and destructors <2>. +* Accessing elements: Accessing elements <2>. +* Bitset operations:: +* Bitset properties:: +* Resizing operations: Resizing operations <3>. +* Copying bitsets:: + + +File: igraph-docs.info, Node: About igraph_bitset_t objects, Next: Constructors and destructors <2>, Up: Bitsets + +7.12.1 About igraph_bitset_t objects +------------------------------------ + +The ‘igraph_bitset_t’ data type is a simple and efficient interface to +arrays containing boolean values. It is similar to the ‘bitset’ +template in the C++ standard library, although the main difference being +the C++ version's size is initialized at compile time. + + The ‘igraph_bitset_t’ type and use O(n/w) space to store n elements, +where w is the bit width of ‘igraph_int_t’, the integer type used +throughout the library (either 32 or 64). Sometimes they use more, this +is because bitsets can shrink, but even if they shrink, the current +implementation does not free a single bit of memory. + + The elements in an ‘igraph_bitset_t’ object and its variants are +indexed from zero, we follow the usual C convention here. Bitsets are +indexed from right to left, meaning index 0 is the least significant bit +and index ‘n - 1’ is the most significant bit. + + The elements of a bitset always occupy a single block of memory, the +starting address of this memory block can be queried with the ‘VECTOR()’ +(*note VECTOR --- Accessing an element of a vector_::) macro. This way, +bitset objects can be used with standard mathematical libraries, like +the GNU Scientific Library. + + Note that while the interface of bitset functions is similar to +igraph's vector functions, there is one major difference: bitset +functions such as ‘igraph_bitset_and()’ (*note igraph_bitset_and --- +Bitwise AND of two bitsets_::) do not verify that that sizes of input +parameters are compatible, and do not automatically resize the output +parameter. Doing so is the responsibility of the user. + + +File: igraph-docs.info, Node: Constructors and destructors <2>, Next: Accessing elements <2>, Prev: About igraph_bitset_t objects, Up: Bitsets + +7.12.2 Constructors and destructors +----------------------------------- + +‘igraph_bitset_t’ objects have to be initialized before using them, this +is analogous to calling a constructor on them. There are two +‘igraph_bitset_t’ constructors, for your convenience. +‘igraph_bitset_init()’ (*note igraph_bitset_init --- Initializes a +bitset object [constructor]_::) is the basic constructor, it creates a +bitset of the given length, filled with zeros. +‘igraph_bitset_init_copy()’ (*note igraph_bitset_init_copy --- +Initializes a bitset from another bitset object [constructor]_::) +creates a new identical copy of an already existing and initialized +bitset. + + If a ‘igraph_bitset_t’ object is not needed any more, it should be +destroyed to free its allocated memory by calling the ‘igraph_bitset_t’ +destructor, ‘igraph_bitset_destroy()’ (*note igraph_bitset_destroy --- +Destroys a bitset object_::). + +* Menu: + +* igraph_bitset_init -- Initializes a bitset object (constructor).: igraph_bitset_init --- Initializes a bitset object [constructor]_. +* igraph_bitset_init_copy -- Initializes a bitset from another bitset object (constructor).: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_. +* igraph_bitset_destroy -- Destroys a bitset object.: igraph_bitset_destroy --- Destroys a bitset object_. + + +File: igraph-docs.info, Node: igraph_bitset_init --- Initializes a bitset object [constructor]_, Next: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_, Up: Constructors and destructors <2> + +7.12.2.1 igraph_bitset_init -- Initializes a bitset object (constructor). +......................................................................... + + + igraph_error_t igraph_bitset_init(igraph_bitset_t *bitset, igraph_int_t size); + + Every bitset needs to be initialized before it can be used, and there +are a number of initialization functions or otherwise called +constructors. This function constructs a bitset of the given size and +initializes each entry to 0. + + Every bitset object initialized by this function should be destroyed +(ie. the memory allocated for it should be freed) when it is not needed +anymore, the ‘igraph_bitset_destroy()’ (*note igraph_bitset_destroy --- +Destroys a bitset object_::) function is responsible for this. + + *Arguments:. * + +‘bitset’: + Pointer to a not yet initialized bitset object. + +‘size’: + The size of the bitset. + + *Returns:. * + +‘’ + error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, the amount of 'time' +required to allocate O(n/w) elements, n is the number of elements. w is +the word size of the machine (32 or 64). + + +File: igraph-docs.info, Node: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_, Next: igraph_bitset_destroy --- Destroys a bitset object_, Prev: igraph_bitset_init --- Initializes a bitset object [constructor]_, Up: Constructors and destructors <2> + +7.12.2.2 igraph_bitset_init_copy -- Initializes a bitset from another bitset object (constructor). +.................................................................................................. + + + igraph_error_t igraph_bitset_init_copy(igraph_bitset_t *dest, const igraph_bitset_t *src); + + The contents of the existing bitset object will be copied to the new +one. + + *Arguments:. * + +‘dest’: + Pointer to a not yet initialized bitset object. + +‘src’: + The original bitset object to copy. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, usually O(n/w), n is the +size of the bitset, w is the word size of the machine (32 or 64). + + +File: igraph-docs.info, Node: igraph_bitset_destroy --- Destroys a bitset object_, Prev: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_, Up: Constructors and destructors <2> + +7.12.2.3 igraph_bitset_destroy -- Destroys a bitset object. +........................................................... + + + void igraph_bitset_destroy(igraph_bitset_t *bitset); + + All bitsets initialized by ‘igraph_bitset_init()’ (*note +igraph_bitset_init --- Initializes a bitset object [constructor]_::) +should be properly destroyed by this function. A destroyed bitset needs +to be reinitialized by ‘igraph_bitset_init()’ (*note igraph_bitset_init +--- Initializes a bitset object [constructor]_::) or another +constructor. + + *Arguments:. * + +‘bitset’: + Pointer to the (previously initialized) bitset object to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: Accessing elements <2>, Next: Bitset operations, Prev: Constructors and destructors <2>, Up: Bitsets + +7.12.3 Accessing elements +------------------------- + +The simplest way to access an element of a bitset is to use the +‘IGRAPH_BIT_TEST()’ (*note IGRAPH_BIT_TEST --- Tests whether a bit is +set in a bitset_::), ‘IGRAPH_BIT_SET()’ (*note IGRAPH_BIT_SET --- Sets a +specific bit in a bitset to 1 without altering other bits_::) and +‘IGRAPH_BIT_CLEAR()’ (*note IGRAPH_BIT_CLEAR --- Sets a specific bit in +a bitset to 0 without altering other bits_::) macros. + + There are a few other macros which allow manual manipulation of +bitsets. Those are ‘VECTOR()’ (*note VECTOR --- Accessing an element of +a vector_::), ‘IGRAPH_BIT_SLOT()’ (*note IGRAPH_BIT_SLOT --- Computes +index used to access a specific slot of a bitset_::), +‘IGRAPH_BIT_MASK()’ (*note IGRAPH_BIT_MASK --- Computes mask used to +access a specific bit of an integer_::) and ‘IGRAPH_BIT_NSLOTS()’ (*note +IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a +specified number of bits_::). + +* Menu: + +* IGRAPH_BIT_MASK -- Computes mask used to access a specific bit of an integer.: IGRAPH_BIT_MASK --- Computes mask used to access a specific bit of an integer_. +* IGRAPH_BIT_SLOT -- Computes index used to access a specific slot of a bitset.: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_. +* IGRAPH_BIT_SET -- Sets a specific bit in a bitset to 1 without altering other bits.: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_. +* IGRAPH_BIT_CLEAR -- Sets a specific bit in a bitset to 0 without altering other bits.: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_. +* IGRAPH_BIT_TEST -- Tests whether a bit is set in a bitset.: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_. +* IGRAPH_BIT_NSLOTS -- Computes the number of slots required to store a specified number of bits.: IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a specified number of bits_. + + +File: igraph-docs.info, Node: IGRAPH_BIT_MASK --- Computes mask used to access a specific bit of an integer_, Next: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_, Up: Accessing elements <2> + +7.12.3.1 IGRAPH_BIT_MASK -- Computes mask used to access a specific bit of an integer. +...................................................................................... + + + #define IGRAPH_BIT_MASK(i) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Used in combination with ‘IGRAPH_BIT_SLOT()’ (*note IGRAPH_BIT_SLOT +--- Computes index used to access a specific slot of a bitset_::) to +access an element of a bitset. Usage: + + IGRAPH_BIT_MASK(10) + +to obtain an integer where only the 11th least significant bit is set. +Note that passing negative values here results in undefined behaviour. + + *Arguments:. * + +‘b’: + The only bit index that should have its bit set. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_, Next: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_, Prev: IGRAPH_BIT_MASK --- Computes mask used to access a specific bit of an integer_, Up: Accessing elements <2> + +7.12.3.2 IGRAPH_BIT_SLOT -- Computes index used to access a specific slot of a bitset. +...................................................................................... + + + #define IGRAPH_BIT_SLOT(i) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Used in combination with ‘IGRAPH_BIT_MASK’ (*note IGRAPH_BIT_MASK --- +Computes mask used to access a specific bit of an integer_::) to access +an element of a bitset. Usage: + + IGRAPH_BIT_SLOT(70) + +will return 1 if using 64-bit words or 2 if using 32-bit words. + + *Arguments:. * + +‘i’: + The bit index whose slot should be determined. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_, Next: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_, Prev: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_, Up: Accessing elements <2> + +7.12.3.3 IGRAPH_BIT_SET -- Sets a specific bit in a bitset to 1 without altering other bits. +............................................................................................ + + + #define IGRAPH_BIT_SET(bitset, i) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Usage: + + IGRAPH_BIT_SET(bitset, 3) + +will set the fourth least significant bit in the bitset to 1. + + *Arguments:. * + +‘bitset’: + The bitset + +‘i’: + The bit index that should have its bit set to 1 after the + operation. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_, Next: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_, Prev: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_, Up: Accessing elements <2> + +7.12.3.4 IGRAPH_BIT_CLEAR -- Sets a specific bit in a bitset to 0 without altering other bits. +.............................................................................................. + + + #define IGRAPH_BIT_CLEAR(bitset, i) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Usage: + + IGRAPH_BIT_CLEAR(bitset, 4) + +will set the fifth least significant bit in the bitset to 0. + + *Arguments:. * + +‘bitset’: + The bitset + +‘i’: + The bit index that should have its bit set to 0 after the + operation. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_, Next: IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a specified number of bits_, Prev: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_, Up: Accessing elements <2> + +7.12.3.5 IGRAPH_BIT_TEST -- Tests whether a bit is set in a bitset. +................................................................... + + + #define IGRAPH_BIT_TEST(bitset, i) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Returns 0 if the bit at the specified bit index is not set, otherwise +returns a non-zero value. Usage: + + IGRAPH_BIT_TEST(bitset, 7) + +will test the eighth least significant bit in the bitset. + + *Arguments:. * + +‘bitset’: + The bitset + +‘i’: + The bit index that should have its bit tested. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a specified number of bits_, Prev: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_, Up: Accessing elements <2> + +7.12.3.6 IGRAPH_BIT_NSLOTS -- Computes the number of slots required to store a specified number of bits. +........................................................................................................ + + + #define IGRAPH_BIT_NSLOTS(nbits) + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Usage: + + IGRAPH_BIT_NSLOTS(70) + +will return 2 if using 64-bit words and 3 if using 32-bit words. + + IGRAPH_BIT_NSLOTS(128) + +will return 2 if using 64-bit words and 4 if using 32-bit words. + + *Arguments:. * + +‘nbits’: + The specified number of bits. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Bitset operations, Next: Bitset properties, Prev: Accessing elements <2>, Up: Bitsets + +7.12.4 Bitset operations +------------------------ + +* Menu: + +* igraph_bitset_fill -- Fills a bitset with a constant value.: igraph_bitset_fill --- Fills a bitset with a constant value_. +* igraph_bitset_null -- Clears all bits in a bitset.: igraph_bitset_null --- Clears all bits in a bitset_. +* igraph_bitset_or -- Bitwise OR of two bitsets.: igraph_bitset_or --- Bitwise OR of two bitsets_. +* igraph_bitset_and -- Bitwise AND of two bitsets.: igraph_bitset_and --- Bitwise AND of two bitsets_. +* igraph_bitset_xor -- Bitwise XOR of two bitsets.: igraph_bitset_xor --- Bitwise XOR of two bitsets_. +* igraph_bitset_not -- Bitwise negation of a bitset.: igraph_bitset_not --- Bitwise negation of a bitset_. +* igraph_bitset_popcount -- The population count of the bitset.: igraph_bitset_popcount --- The population count of the bitset_. +* igraph_bitset_countl_zero -- The number of leading zeros in the bitset.: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_. +* igraph_bitset_countl_one -- The number of leading ones in the bitset.: igraph_bitset_countl_one --- The number of leading ones in the bitset_. +* igraph_bitset_countr_zero -- The number of trailing zeros in the bitset.: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_. +* igraph_bitset_countr_one -- The number of trailing ones in the bitset.: igraph_bitset_countr_one --- The number of trailing ones in the bitset_. +* igraph_bitset_is_all_zero --- Are all bits zeros?:: +* igraph_bitset_is_all_one --- Are all bits ones?:: +* igraph_bitset_is_any_zero --- Are any bits zeros?:: +* igraph_bitset_is_any_one --- Are any bits ones?:: + + +File: igraph-docs.info, Node: igraph_bitset_fill --- Fills a bitset with a constant value_, Next: igraph_bitset_null --- Clears all bits in a bitset_, Up: Bitset operations + +7.12.4.1 igraph_bitset_fill -- Fills a bitset with a constant value. +.................................................................... + + + void igraph_bitset_fill(igraph_bitset_t *bitset, igraph_bool_t value); + + Sets all bits of a bitset to the same value. + + *Arguments:. * + +‘bitset’: + The bitset object to modify. + +‘value’: + The value to set for all bits. + + *See also:. * + +‘’ + ‘igraph_bitset_null()’ (*note igraph_bitset_null --- Clears all + bits in a bitset_::) + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_null --- Clears all bits in a bitset_, Next: igraph_bitset_or --- Bitwise OR of two bitsets_, Prev: igraph_bitset_fill --- Fills a bitset with a constant value_, Up: Bitset operations + +7.12.4.2 igraph_bitset_null -- Clears all bits in a bitset. +........................................................... + + + void igraph_bitset_null(igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object to clear all bits in. + + *See also:. * + +‘’ + ‘igraph_bitset_fill()’ (*note igraph_bitset_fill --- Fills a bitset + with a constant value_::) + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_or --- Bitwise OR of two bitsets_, Next: igraph_bitset_and --- Bitwise AND of two bitsets_, Prev: igraph_bitset_null --- Clears all bits in a bitset_, Up: Bitset operations + +7.12.4.3 igraph_bitset_or -- Bitwise OR of two bitsets. +....................................................... + + + void igraph_bitset_or(igraph_bitset_t *dest, + const igraph_bitset_t *src1, const igraph_bitset_t *src2); + + Applies a bitwise or to the contents of two bitsets and stores it in +an already initialized bitset. The destination bitset may be equal to +one (or even both) of the sources. When working with bitsets, it is +common that those created are of the same size fixed size. Therefore, +this function does not check the sizes of the bitsets passed to it, the +caller must do so if necessary. + + *Arguments:. * + +‘dest’: + The bitset object where the result is stored + +‘src1’: + A bitset. Must have have same size as ‘dest’. + +‘src2’: + A bitset. Must have have same size as ‘dest’. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_and --- Bitwise AND of two bitsets_, Next: igraph_bitset_xor --- Bitwise XOR of two bitsets_, Prev: igraph_bitset_or --- Bitwise OR of two bitsets_, Up: Bitset operations + +7.12.4.4 igraph_bitset_and -- Bitwise AND of two bitsets. +......................................................... + + + void igraph_bitset_and(igraph_bitset_t *dest, const igraph_bitset_t *src1, const igraph_bitset_t *src2); + + Applies a bitwise and to the contents of two bitsets and stores it in +an already initialized bitset. The destination bitset may be equal to +one (or even both) of the sources. When working with bitsets, it is +common that those created are of the same size fixed size. Therefore, +this function does not check the sizes of the bitsets passed to it, the +caller must do so if necessary. + + *Arguments:. * + +‘dest’: + The bitset object where the result is stored + +‘src1’: + A bitset. Must have have same size as ‘dest’. + +‘src2’: + A bitset. Must have have same size as ‘dest’. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_xor --- Bitwise XOR of two bitsets_, Next: igraph_bitset_not --- Bitwise negation of a bitset_, Prev: igraph_bitset_and --- Bitwise AND of two bitsets_, Up: Bitset operations + +7.12.4.5 igraph_bitset_xor -- Bitwise XOR of two bitsets. +......................................................... + + + void igraph_bitset_xor(igraph_bitset_t *dest, + const igraph_bitset_t *src1, const igraph_bitset_t *src2); + + Applies a bitwise xor to the contents of two bitsets and stores it in +an already initialized bitset. The destination bitset may be equal to +one (or even both) of the sources. When working with bitsets, it is +common that those created are of the same size fixed size. Therefore, +this function does not check the sizes of the bitsets passed to it, the +caller must do so if necessary. + + *Arguments:. * + +‘dest’: + The bitset object where the result is stored + +‘src1’: + A bitset. Must have have same size as ‘dest’. + +‘src2’: + A bitset. Must have have same size as ‘dest’. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_not --- Bitwise negation of a bitset_, Next: igraph_bitset_popcount --- The population count of the bitset_, Prev: igraph_bitset_xor --- Bitwise XOR of two bitsets_, Up: Bitset operations + +7.12.4.6 igraph_bitset_not -- Bitwise negation of a bitset. +........................................................... + + + void igraph_bitset_not(igraph_bitset_t *dest, const igraph_bitset_t *src); + + Applies a bitwise not to the contents of a bitset and stores it in an +already initialized bitset. The destination bitset may be equal to the +source. When working with bitsets, it is common that those created are +of the same size fixed size. Therefore, this function does not check +the sizes of the bitsets passed to it, the caller must do so if +necessary. + + *Arguments:. * + +‘dest’: + The bitset object where the result is stored + +‘src’: + A bitset. Must have have same size as ‘dest’. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_popcount --- The population count of the bitset_, Next: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_, Prev: igraph_bitset_not --- Bitwise negation of a bitset_, Up: Bitset operations + +7.12.4.7 igraph_bitset_popcount -- The population count of the bitset. +...................................................................... + + + igraph_int_t igraph_bitset_popcount(const igraph_bitset_t *bitset); + + Returns the number of set bits, also called the population count, of +the bitset. + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The population count of the bitset. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_, Next: igraph_bitset_countl_one --- The number of leading ones in the bitset_, Prev: igraph_bitset_popcount --- The population count of the bitset_, Up: Bitset operations + +7.12.4.8 igraph_bitset_countl_zero -- The number of leading zeros in the bitset. +................................................................................ + + + igraph_int_t igraph_bitset_countl_zero(const igraph_bitset_t *bitset); + + Returns the number of leading (starting at the most significant bit) +zeros in the bitset before the first one is encountered. If the bitset +is all zeros, then its size is returned. + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The number of leading zeros in the bitset. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_countl_one --- The number of leading ones in the bitset_, Next: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_, Prev: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_, Up: Bitset operations + +7.12.4.9 igraph_bitset_countl_one -- The number of leading ones in the bitset. +.............................................................................. + + + igraph_int_t igraph_bitset_countl_one(const igraph_bitset_t *bitset); + + Returns the number of leading ones (starting at the most significant +bit) in the bitset before the first zero is encountered. If the bitset +is all ones, then its size is returned. + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The number of leading ones in the bitset. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_, Next: igraph_bitset_countr_one --- The number of trailing ones in the bitset_, Prev: igraph_bitset_countl_one --- The number of leading ones in the bitset_, Up: Bitset operations + +7.12.4.10 igraph_bitset_countr_zero -- The number of trailing zeros in the bitset. +.................................................................................. + + + igraph_int_t igraph_bitset_countr_zero(const igraph_bitset_t *bitset); + + Returns the number of trailing (starting at the least significant +bit) zeros in the bitset before the first one is encountered. If the +bitset is all zeros, then its size is returned. + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The number of trailing zeros in the bitset. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_countr_one --- The number of trailing ones in the bitset_, Next: igraph_bitset_is_all_zero --- Are all bits zeros?, Prev: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_, Up: Bitset operations + +7.12.4.11 igraph_bitset_countr_one -- The number of trailing ones in the bitset. +................................................................................ + + + igraph_int_t igraph_bitset_countr_one(const igraph_bitset_t *bitset); + + Returns the number of trailing ones (starting at the least +significant bit) in the bitset before the first zero is encountered. If +the bitset is all ones, then its size is returned. + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The number of trailing ones in the bitset. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_is_all_zero --- Are all bits zeros?, Next: igraph_bitset_is_all_one --- Are all bits ones?, Prev: igraph_bitset_countr_one --- The number of trailing ones in the bitset_, Up: Bitset operations + +7.12.4.12 igraph_bitset_is_all_zero -- Are all bits zeros? +.......................................................... + + + igraph_bool_t igraph_bitset_is_all_zero(const igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object to test. + + *Returns:. * + +‘’ + True if none of the bits are set. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_is_all_one --- Are all bits ones?, Next: igraph_bitset_is_any_zero --- Are any bits zeros?, Prev: igraph_bitset_is_all_zero --- Are all bits zeros?, Up: Bitset operations + +7.12.4.13 igraph_bitset_is_all_one -- Are all bits ones? +........................................................ + + + igraph_bool_t igraph_bitset_is_all_one(const igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object to test. + + *Returns:. * + +‘’ + True if all of the bits are set. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_is_any_zero --- Are any bits zeros?, Next: igraph_bitset_is_any_one --- Are any bits ones?, Prev: igraph_bitset_is_all_one --- Are all bits ones?, Up: Bitset operations + +7.12.4.14 igraph_bitset_is_any_zero -- Are any bits zeros? +.......................................................... + + + igraph_bool_t igraph_bitset_is_any_zero(const igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object to test. + + *Returns:. * + +‘’ + True if at least one bit is zero. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: igraph_bitset_is_any_one --- Are any bits ones?, Prev: igraph_bitset_is_any_zero --- Are any bits zeros?, Up: Bitset operations + +7.12.4.15 igraph_bitset_is_any_one -- Are any bits ones? +........................................................ + + + igraph_bool_t igraph_bitset_is_any_one(const igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object to test. + + *Returns:. * + +‘’ + True if at least one bit is one. + + Time complexity: O(n/w). + + +File: igraph-docs.info, Node: Bitset properties, Next: Resizing operations <3>, Prev: Bitset operations, Up: Bitsets + +7.12.5 Bitset properties +------------------------ + +* Menu: + +* igraph_bitset_size -- Returns the length of the bitset.: igraph_bitset_size --- Returns the length of the bitset_. +* igraph_bitset_capacity -- Returns the allocated capacity of the bitset.: igraph_bitset_capacity --- Returns the allocated capacity of the bitset_. + + +File: igraph-docs.info, Node: igraph_bitset_size --- Returns the length of the bitset_, Next: igraph_bitset_capacity --- Returns the allocated capacity of the bitset_, Up: Bitset properties + +7.12.5.1 igraph_bitset_size -- Returns the length of the bitset. +................................................................ + + + igraph_int_t igraph_bitset_size(const igraph_bitset_t *bitset); + + *Arguments:. * + +‘bitset’: + The bitset object + + *Returns:. * + +‘’ + The size of the bitset. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_bitset_capacity --- Returns the allocated capacity of the bitset_, Prev: igraph_bitset_size --- Returns the length of the bitset_, Up: Bitset properties + +7.12.5.2 igraph_bitset_capacity -- Returns the allocated capacity of the bitset. +................................................................................ + + + igraph_int_t igraph_bitset_capacity(const igraph_bitset_t *bitset); + + Note that this might be different from the size of the bitset (as +queried by ‘igraph_bitset_size()’ (*note igraph_bitset_size --- Returns +the length of the bitset_::)), and specifies how many elements the +bitset can hold, without reallocation. + + *Arguments:. * + +‘bitset’: + Pointer to the (previously initialized) bitset object to query. + + *Returns:. * + +‘’ + The allocated capacity. + + *See also:. * + +‘’ + ‘igraph_bitset_size()’ (*note igraph_bitset_size --- Returns the + length of the bitset_::). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Resizing operations <3>, Next: Copying bitsets, Prev: Bitset properties, Up: Bitsets + +7.12.6 Resizing operations +-------------------------- + +* Menu: + +* igraph_bitset_reserve -- Reserves memory for a bitset.: igraph_bitset_reserve --- Reserves memory for a bitset_. +* igraph_bitset_resize -- Resizes the bitset.: igraph_bitset_resize --- Resizes the bitset_. + + +File: igraph-docs.info, Node: igraph_bitset_reserve --- Reserves memory for a bitset_, Next: igraph_bitset_resize --- Resizes the bitset_, Up: Resizing operations <3> + +7.12.6.1 igraph_bitset_reserve -- Reserves memory for a bitset. +............................................................... + + + igraph_error_t igraph_bitset_reserve(igraph_bitset_t *bitset, igraph_int_t capacity); + + ‘igraph’ bitsets are flexible, they can grow and shrink. Growing +however occasionally needs the data in the bitset to be copied. In +order to avoid this, you can call this function to reserve space for +future growth of the bitset. + + Note that this function does _not_ change the size of the bitset. +Let us see a small example to clarify things: if you reserve space for +100 elements and the size of your bitset was (and still is) 60, then you +can surely add additional 40 elements to your bitset before it will be +copied. + + *Arguments:. * + +‘bitset’: + The bitset object. + +‘capacity’: + The new _allocated_ size of the bitset. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, should be around O(n/w), +n is the new allocated size of the bitset, w is the word size of the +machine (32 or 64). + + +File: igraph-docs.info, Node: igraph_bitset_resize --- Resizes the bitset_, Prev: igraph_bitset_reserve --- Reserves memory for a bitset_, Up: Resizing operations <3> + +7.12.6.2 igraph_bitset_resize -- Resizes the bitset. +.................................................... + + + igraph_error_t igraph_bitset_resize(igraph_bitset_t *bitset, igraph_int_t new_size); + + Note that this function does not free any memory, just sets the size +of the bitset to the given one. It may, on the other hand, allocate +more memory if the new size is larger than the previous one. In this +case the newly appeared elements in the bitset are set to zero. + + *Arguments:. * + +‘bitset’: + The bitset object + +‘new_size’: + The new size of the bitset. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory. Note + that this function _never_ returns an error if the bitset is made + smaller. + + *See also:. * + +‘’ + ‘igraph_bitset_reserve()’ (*note igraph_bitset_reserve --- Reserves + memory for a bitset_::) for allocating memory for future extensions + of a bitset. + + Time complexity: O(1) if the new size is smaller, operating system +dependent if it is larger. In the latter case it is usually around +O(n/w), n is the new size of the bitset, w is the word size of the +machine (32 or 64). + + +File: igraph-docs.info, Node: Copying bitsets, Prev: Resizing operations <3>, Up: Bitsets + +7.12.7 Copying bitsets +---------------------- + +* Menu: + +* igraph_bitset_update -- Update a bitset from another one.: igraph_bitset_update --- Update a bitset from another one_. + + +File: igraph-docs.info, Node: igraph_bitset_update --- Update a bitset from another one_, Up: Copying bitsets + +7.12.7.1 igraph_bitset_update -- Update a bitset from another one. +.................................................................. + + + igraph_error_t igraph_bitset_update(igraph_bitset_t *dest, const igraph_bitset_t *src); + + The size and contents of ‘dest’ will be identical to that of ‘src’. + + *Arguments:. * + +‘dest’: + Pointer to an initialized bitset object. This will be updated. + +‘src’: + The bitset to update from. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, usually O(n/w), n is the +size of the bitset, w is the word size of the machine (32 or 64). + + +File: igraph-docs.info, Node: Random numbers, Next: Vertex and edge selectors and sequences; iterators, Prev: Data structure library; vector; matrix; other data types, Up: Top + +8 Random numbers +**************** + +* Menu: + +* About random numbers in igraph:: +* The default random number generator:: +* Creating random number generators:: +* Generating random numbers:: +* Supported random number generators:: +* Use cases:: + + +File: igraph-docs.info, Node: About random numbers in igraph, Next: The default random number generator, Up: Random numbers + +8.1 About random numbers in igraph +================================== + +Some algorithms in igraph, such as sampling from random graph models, +require random number generators (RNGs). igraph includes a flexible RNG +framework that allows hooking up arbitrary random number generators, and +comes with several ready-to-use generators. This framework is used in +igraph's high-level interfaces to integrate with the host language's own +RNG. + + +File: igraph-docs.info, Node: The default random number generator, Next: Creating random number generators, Prev: About random numbers in igraph, Up: Random numbers + +8.2 The default random number generator +======================================= + +* Menu: + +* igraph_rng_default -- Query the default random number generator.: igraph_rng_default --- Query the default random number generator_. +* igraph_rng_set_default -- Set the default igraph random number generator.: igraph_rng_set_default --- Set the default igraph random number generator_. + + +File: igraph-docs.info, Node: igraph_rng_default --- Query the default random number generator_, Next: igraph_rng_set_default --- Set the default igraph random number generator_, Up: The default random number generator + +8.2.1 igraph_rng_default -- Query the default random number generator. +---------------------------------------------------------------------- + + + igraph_rng_t *igraph_rng_default(void); + + *Returns:. * + +‘’ + A pointer to the default random number generator. + + *See also:. * + +‘’ + ‘igraph_rng_set_default()’ (*note igraph_rng_set_default --- Set + the default igraph random number generator_::) + + +File: igraph-docs.info, Node: igraph_rng_set_default --- Set the default igraph random number generator_, Prev: igraph_rng_default --- Query the default random number generator_, Up: The default random number generator + +8.2.2 igraph_rng_set_default -- Set the default igraph random number generator. +------------------------------------------------------------------------------- + + + igraph_rng_t *igraph_rng_set_default(igraph_rng_t *rng); + + This function updates the default RNG used by igraph to be the one +pointed to by ‘rng’, and returns a pointer to the previous default RNG. +Future calls to ‘igraph_rng_default()’ (*note igraph_rng_default --- +Query the default random number generator_::) will return the same +pointer as ‘rng’. The RNG pointed to by ‘rng’ must not be destroyed for +as long as it is used as the default. + + *Arguments:. * + +‘rng’: + The random number generator to use as default from now on. Calling + ‘igraph_rng_destroy()’ (*note igraph_rng_destroy --- Deallocates + memory associated with a random number generator_::) on it, while + it is still being used as the default will result in crashes and/or + unpredictable results. + + *Returns:. * + +‘’ + Pointer the previous default RNG. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Creating random number generators, Next: Generating random numbers, Prev: The default random number generator, Up: Random numbers + +8.3 Creating random number generators +===================================== + +* Menu: + +* igraph_rng_init -- Initializes a random number generator.: igraph_rng_init --- Initializes a random number generator_. +* igraph_rng_destroy -- Deallocates memory associated with a random number generator.: igraph_rng_destroy --- Deallocates memory associated with a random number generator_. +* igraph_rng_seed -- Seeds a random number generator.: igraph_rng_seed --- Seeds a random number generator_. +* igraph_rng_bits -- The number of random bits that a random number generator can produces in a single round.: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_. +* igraph_rng_max -- The maximum possible integer for a random number generator.: igraph_rng_max --- The maximum possible integer for a random number generator_. +* igraph_rng_name -- The type of a random number generator.: igraph_rng_name --- The type of a random number generator_. + + +File: igraph-docs.info, Node: igraph_rng_init --- Initializes a random number generator_, Next: igraph_rng_destroy --- Deallocates memory associated with a random number generator_, Up: Creating random number generators + +8.3.1 igraph_rng_init -- Initializes a random number generator. +--------------------------------------------------------------- + + + igraph_error_t igraph_rng_init(igraph_rng_t *rng, const igraph_rng_type_t *type); + + This function allocates memory for a random number generator, with +the given type, and sets its seed to the default. + + *Arguments:. * + +‘rng’: + Pointer to an uninitialized RNG. + +‘type’: + The type of the RNG, such as ‘igraph_rngtype_mt19937’ (*note + igraph_rngtype_mt19937 --- The MT19937 random number generator_::), + ‘igraph_rngtype_glibc2’ (*note igraph_rngtype_glibc2 --- The random + number generator introduced in GNU libc 2_::), + ‘igraph_rngtype_pcg32’ (*note igraph_rngtype_pcg32 --- The PCG + random number generator [32-bit version]_::) or + ‘igraph_rngtype_pcg64’ (*note igraph_rngtype_pcg64 --- The PCG + random number generator [64-bit version]_::). + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_rng_destroy --- Deallocates memory associated with a random number generator_, Next: igraph_rng_seed --- Seeds a random number generator_, Prev: igraph_rng_init --- Initializes a random number generator_, Up: Creating random number generators + +8.3.2 igraph_rng_destroy -- Deallocates memory associated with a random number generator. +----------------------------------------------------------------------------------------- + + + void igraph_rng_destroy(igraph_rng_t *rng); + + *Arguments:. * + +‘rng’: + The RNG to destroy. Do not destroy an RNG that is used as the + default igraph RNG. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_rng_seed --- Seeds a random number generator_, Next: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_, Prev: igraph_rng_destroy --- Deallocates memory associated with a random number generator_, Up: Creating random number generators + +8.3.3 igraph_rng_seed -- Seeds a random number generator. +--------------------------------------------------------- + + + igraph_error_t igraph_rng_seed(igraph_rng_t *rng, igraph_uint_t seed); + + *Arguments:. * + +‘rng’: + The RNG. + +‘seed’: + The new seed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: usually O(1), but may depend on the type of the RNG. + + +File: igraph-docs.info, Node: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_, Next: igraph_rng_max --- The maximum possible integer for a random number generator_, Prev: igraph_rng_seed --- Seeds a random number generator_, Up: Creating random number generators + +8.3.4 igraph_rng_bits -- The number of random bits that a random number generator can produces in a single round. +----------------------------------------------------------------------------------------------------------------- + + + igraph_int_t igraph_rng_bits(const igraph_rng_t* rng); + + *Arguments:. * + +‘rng’: + The RNG. + + *Returns:. * + +‘’ + The number of random bits that can be generated in a single round + with the RNG. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_rng_max --- The maximum possible integer for a random number generator_, Next: igraph_rng_name --- The type of a random number generator_, Prev: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_, Up: Creating random number generators + +8.3.5 igraph_rng_max -- The maximum possible integer for a random number generator. +----------------------------------------------------------------------------------- + + + igraph_uint_t igraph_rng_max(const igraph_rng_t *rng); + + Note that this number is only for informational purposes; it returns +the maximum possible integer that can be generated with the RNG with a +single call to its internals. It is derived directly from the number of +random _bits_ that the RNG can generate in a single round. When this is +smaller than what would be needed by other RNG functions like +‘igraph_rng_get_integer()’ (*note igraph_rng_get_integer --- Generate an +integer random number from an interval_::), igraph will call the RNG +multiple times to generate more random bits. + + *Arguments:. * + +‘rng’: + The RNG. + + *Returns:. * + +‘’ + The largest possible integer that can be generated in a single + round with the RNG. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_rng_name --- The type of a random number generator_, Prev: igraph_rng_max --- The maximum possible integer for a random number generator_, Up: Creating random number generators + +8.3.6 igraph_rng_name -- The type of a random number generator. +--------------------------------------------------------------- + + + const char *igraph_rng_name(const igraph_rng_t *rng); + + *Arguments:. * + +‘rng’: + The RNG. + + *Returns:. * + +‘’ + The name of the type of the generator. Do not deallocate or change + the returned string. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Generating random numbers, Next: Supported random number generators, Prev: Creating random number generators, Up: Random numbers + +8.4 Generating random numbers +============================= + +* Menu: + +* igraph_rng_get_bool -- Generate a random boolean.: igraph_rng_get_bool --- Generate a random boolean_. +* igraph_rng_get_integer -- Generate an integer random number from an interval.: igraph_rng_get_integer --- Generate an integer random number from an interval_. +* igraph_rng_get_unif01 -- Samples uniformly from the unit interval.: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_. +* igraph_rng_get_unif -- Samples real numbers from a given interval.: igraph_rng_get_unif --- Samples real numbers from a given interval_. +* igraph_rng_get_normal -- Samples from a normal distribution.: igraph_rng_get_normal --- Samples from a normal distribution_. +* igraph_rng_get_exp -- Samples from an exponential distribution.: igraph_rng_get_exp --- Samples from an exponential distribution_. +* igraph_rng_get_gamma -- Samples from a gamma distribution.: igraph_rng_get_gamma --- Samples from a gamma distribution_. +* igraph_rng_get_binom -- Samples from a binomial distribution.: igraph_rng_get_binom --- Samples from a binomial distribution_. +* igraph_rng_get_geom -- Samples from a geometric distribution.: igraph_rng_get_geom --- Samples from a geometric distribution_. +* igraph_rng_get_pois -- Samples from a Poisson distribution.: igraph_rng_get_pois --- Samples from a Poisson distribution_. + + +File: igraph-docs.info, Node: igraph_rng_get_bool --- Generate a random boolean_, Next: igraph_rng_get_integer --- Generate an integer random number from an interval_, Up: Generating random numbers + +8.4.1 igraph_rng_get_bool -- Generate a random boolean. +------------------------------------------------------- + + + igraph_bool_t igraph_rng_get_bool(igraph_rng_t *rng); + + Use this function only when a single random boolean, i.e. a single +bit is needed at a time. It is not efficient for generating multiple +bits. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use for the generation. Use + ‘igraph_rng_default()’ (*note igraph_rng_default --- Query the + default random number generator_::) here to use the default igraph + RNG. + + *Returns:. * + +‘’ + The generated bit, as a truth value. + + +File: igraph-docs.info, Node: igraph_rng_get_integer --- Generate an integer random number from an interval_, Next: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_, Prev: igraph_rng_get_bool --- Generate a random boolean_, Up: Generating random numbers + +8.4.2 igraph_rng_get_integer -- Generate an integer random number from an interval. +----------------------------------------------------------------------------------- + + + igraph_int_t igraph_rng_get_integer( + igraph_rng_t *rng, igraph_int_t l, igraph_int_t h + ); + + Generate uniformly distributed integers from the interval ‘[l, h]’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use for the generation. Use + ‘igraph_rng_default()’ (*note igraph_rng_default --- Query the + default random number generator_::) here to use the default igraph + RNG. + +‘l’: + Lower limit, inclusive, it can be negative as well. + +‘h’: + Upper limit, inclusive, it can be negative as well, but it must be + at least ‘l’. + + *Returns:. * + +‘’ + The generated random integer. + + Time complexity: O(log2(h-l+1) / bits) where bits is the value of +‘igraph_rng_bits’ (*note igraph_rng_bits --- The number of random bits +that a random number generator can produces in a single round_::)(rng). + + +File: igraph-docs.info, Node: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_, Next: igraph_rng_get_unif --- Samples real numbers from a given interval_, Prev: igraph_rng_get_integer --- Generate an integer random number from an interval_, Up: Generating random numbers + +8.4.3 igraph_rng_get_unif01 -- Samples uniformly from the unit interval. +------------------------------------------------------------------------ + + + igraph_real_t igraph_rng_get_unif01(igraph_rng_t *rng); + + Generates uniformly distributed real numbers from the ‘[0, 1)’ +half-open interval. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + + *Returns:. * + +‘’ + The generated uniformly distributed random number. + + Time complexity: depends on the type of the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_unif --- Samples real numbers from a given interval_, Next: igraph_rng_get_normal --- Samples from a normal distribution_, Prev: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_, Up: Generating random numbers + +8.4.4 igraph_rng_get_unif -- Samples real numbers from a given interval. +------------------------------------------------------------------------ + + + igraph_real_t igraph_rng_get_unif(igraph_rng_t *rng, + igraph_real_t l, igraph_real_t h); + + Generates uniformly distributed real numbers from the ‘[l, h)’ +half-open interval. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘l’: + The lower bound, it can be negative. + +‘h’: + The upper bound, it can be negative, but it has to be larger than + the lower bound. + + *Returns:. * + +‘’ + The generated uniformly distributed random number. + + Time complexity: depends on the type of the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_normal --- Samples from a normal distribution_, Next: igraph_rng_get_exp --- Samples from an exponential distribution_, Prev: igraph_rng_get_unif --- Samples real numbers from a given interval_, Up: Generating random numbers + +8.4.5 igraph_rng_get_normal -- Samples from a normal distribution. +------------------------------------------------------------------ + + + igraph_real_t igraph_rng_get_normal(igraph_rng_t *rng, + igraph_real_t m, igraph_real_t s); + + Generates random variates from a normal distribution with probability +density + + ‘exp( -(x - m)^2 / (2 s^2) )’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘m’: + The mean. + +‘s’: + The standard deviation. + + *Returns:. * + +‘’ + The generated normally distributed random number. + + Time complexity: depends on the type of the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_exp --- Samples from an exponential distribution_, Next: igraph_rng_get_gamma --- Samples from a gamma distribution_, Prev: igraph_rng_get_normal --- Samples from a normal distribution_, Up: Generating random numbers + +8.4.6 igraph_rng_get_exp -- Samples from an exponential distribution. +--------------------------------------------------------------------- + + + igraph_real_t igraph_rng_get_exp(igraph_rng_t *rng, igraph_real_t rate); + + Generates random variates from an exponential distribution with +probability density proportional to + + ‘exp(-rate x)’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘rate’: + Rate parameter. + + *Returns:. * + +‘’ + The generated sample. + + Time complexity: depends on the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_gamma --- Samples from a gamma distribution_, Next: igraph_rng_get_binom --- Samples from a binomial distribution_, Prev: igraph_rng_get_exp --- Samples from an exponential distribution_, Up: Generating random numbers + +8.4.7 igraph_rng_get_gamma -- Samples from a gamma distribution. +---------------------------------------------------------------- + + + igraph_real_t igraph_rng_get_gamma(igraph_rng_t *rng, igraph_real_t shape, + igraph_real_t scale); + + Generates random variates from a gamma distribution with probability +density proportional to + + ‘x^(shape-1) exp(-x / scale)’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘shape’: + Shape parameter. + +‘scale’: + Scale parameter. + + *Returns:. * + +‘’ + The generated sample. + + Time complexity: depends on the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_binom --- Samples from a binomial distribution_, Next: igraph_rng_get_geom --- Samples from a geometric distribution_, Prev: igraph_rng_get_gamma --- Samples from a gamma distribution_, Up: Generating random numbers + +8.4.8 igraph_rng_get_binom -- Samples from a binomial distribution. +------------------------------------------------------------------- + + + igraph_real_t igraph_rng_get_binom(igraph_rng_t *rng, igraph_int_t n, igraph_real_t p); + + Generates random variates from a binomial distribution. The number +‘k’ is generated with probability + + ‘(n \choose k) p^k (1-p)^(n-k)’, ‘k = 0, 1, ..., n’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘n’: + Number of observations. + +‘p’: + Probability of an event. + + *Returns:. * + +‘’ + The generated binomially distributed random number. + + Time complexity: depends on the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_geom --- Samples from a geometric distribution_, Next: igraph_rng_get_pois --- Samples from a Poisson distribution_, Prev: igraph_rng_get_binom --- Samples from a binomial distribution_, Up: Generating random numbers + +8.4.9 igraph_rng_get_geom -- Samples from a geometric distribution. +------------------------------------------------------------------- + + + igraph_real_t igraph_rng_get_geom(igraph_rng_t *rng, igraph_real_t p); + + Generates random variates from a geometric distribution. The number +‘k’ is generated with probability + + ‘(1 - p)^k p’, ‘k = 0, 1, 2, ...’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘p’: + The probability of success in each trial. Must be larger than zero + and smaller or equal to 1. + + *Returns:. * + +‘’ + The generated geometrically distributed random number. + + Time complexity: depends on the RNG. + + +File: igraph-docs.info, Node: igraph_rng_get_pois --- Samples from a Poisson distribution_, Prev: igraph_rng_get_geom --- Samples from a geometric distribution_, Up: Generating random numbers + +8.4.10 igraph_rng_get_pois -- Samples from a Poisson distribution. +------------------------------------------------------------------ + + + igraph_real_t igraph_rng_get_pois(igraph_rng_t *rng, igraph_real_t rate); + + Generates random variates from a Poisson distribution. The number +‘k’ is generated with probability + + ‘rate^k * exp(-rate) / k!’, ‘k = 0, 1, 2, ...’. + + *Arguments:. * + +‘rng’: + Pointer to the RNG to use. Use ‘igraph_rng_default()’ (*note + igraph_rng_default --- Query the default random number + generator_::) here to use the default igraph RNG. + +‘rate’: + The rate parameter of the Poisson distribution. Must not be + negative. + + *Returns:. * + +‘’ + The generated geometrically distributed random number. + + Time complexity: depends on the RNG. + + +File: igraph-docs.info, Node: Supported random number generators, Next: Use cases, Prev: Generating random numbers, Up: Random numbers + +8.5 Supported random number generators +====================================== + +By default igraph uses the MT19937 generator. Prior to igraph version +0.6, the generator supplied by the standard C library was used. This +means the GLIBC2 generator on GNU libc 2 systems, and maybe the BSD RAND +generator on others. The RAND generator was removed due to poor +statistical properties in version 0.10. The PCG32 generator was added +in version 0.10. + +* Menu: + +* igraph_rngtype_mt19937 -- The MT19937 random number generator.: igraph_rngtype_mt19937 --- The MT19937 random number generator_. +* igraph_rngtype_glibc2 -- The random number generator introduced in GNU libc 2.: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_. +* igraph_rngtype_pcg32 -- The PCG random number generator (32-bit version).: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_. +* igraph_rngtype_pcg64 -- The PCG random number generator (64-bit version).: igraph_rngtype_pcg64 --- The PCG random number generator [64-bit version]_. + + +File: igraph-docs.info, Node: igraph_rngtype_mt19937 --- The MT19937 random number generator_, Next: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_, Up: Supported random number generators + +8.5.1 igraph_rngtype_mt19937 -- The MT19937 random number generator. +-------------------------------------------------------------------- + + + const igraph_rng_type_t igraph_rngtype_mt19937 = { + /* name= */ "MT19937", + /* bits= */ 32, + /* init= */ igraph_rng_mt19937_init, + /* destroy= */ igraph_rng_mt19937_destroy, + /* seed= */ igraph_rng_mt19937_seed, + /* get= */ igraph_rng_mt19937_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL + }; + + The MT19937 generator of Makoto Matsumoto and Takuji Nishimura is a +variant of the twisted generalized feedback shift-register algorithm, +and is known as the "Mersenne Twister" generator. It has a Mersenne +prime period of 2^19937 - 1 (about 10^6000) and is equi-distributed in +623 dimensions. It has passed the diehard statistical tests. It uses +624 words of state per generator and is comparable in speed to the other +generators. The original generator used a default seed of 4357 and +choosing ‘s’ equal to zero in ‘igraph_rng_mt19937_seed’() reproduces +this. Later versions switched to 5489 as the default seed, you can +choose this explicitly via ‘igraph_rng_seed()’ (*note igraph_rng_seed +--- Seeds a random number generator_::) instead if you require it. + + For more information see, Makoto Matsumoto and Takuji Nishimura, +"Mersenne Twister: A 623-dimensionally equidistributed uniform +pseudorandom number generator". ACM Transactions on Modeling and +Computer Simulation, Vol. 8, No. 1 (Jan. 1998), Pages 3-30 + + The generator ‘igraph_rngtype_mt19937’ uses the second revision of +the seeding procedure published by the two authors above in 2002. The +original seeding procedures could cause spurious artifacts for some seed +values. + + This generator was ported from the GNU Scientific Library. + + +File: igraph-docs.info, Node: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_, Next: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_, Prev: igraph_rngtype_mt19937 --- The MT19937 random number generator_, Up: Supported random number generators + +8.5.2 igraph_rngtype_glibc2 -- The random number generator introduced in GNU libc 2. +------------------------------------------------------------------------------------ + + + const igraph_rng_type_t igraph_rngtype_glibc2 = { + /* name= */ "LIBC", + /* bits= */ 31, + /* init= */ igraph_rng_glibc2_init, + /* destroy= */ igraph_rng_glibc2_destroy, + /* seed= */ igraph_rng_glibc2_seed, + /* get= */ igraph_rng_glibc2_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL + }; + + This is a linear feedback shift register generator with a 128-byte +buffer. This generator was the default prior to igraph version 0.6, at +least on systems relying on GNU libc. This generator was ported from +the GNU Scientific Library. It is a reimplementation and does not call +the system glibc generator. + + +File: igraph-docs.info, Node: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_, Next: igraph_rngtype_pcg64 --- The PCG random number generator [64-bit version]_, Prev: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_, Up: Supported random number generators + +8.5.3 igraph_rngtype_pcg32 -- The PCG random number generator (32-bit version). +------------------------------------------------------------------------------- + + + const igraph_rng_type_t igraph_rngtype_pcg32 = { + /* name= */ "PCG32", + /* bits= */ 32, + /* init= */ igraph_rng_pcg32_init, + /* destroy= */ igraph_rng_pcg32_destroy, + /* seed= */ igraph_rng_pcg32_seed, + /* get= */ igraph_rng_pcg32_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL + }; + + This is an implementation of the PCG random number generator; see +https://www.pcg-random.org (https://www.pcg-random.org) for more +details. This implementation returns 32 random bits in a single +iteration. + + The generator was ported from the original source code published by +the authors at https://github.com/imneme/pcg-c +(https://github.com/imneme/pcg-c). + + +File: igraph-docs.info, Node: igraph_rngtype_pcg64 --- The PCG random number generator [64-bit version]_, Prev: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_, Up: Supported random number generators + +8.5.4 igraph_rngtype_pcg64 -- The PCG random number generator (64-bit version). +------------------------------------------------------------------------------- + + + const igraph_rng_type_t igraph_rngtype_pcg64 = { + /* name= */ "PCG64", + /* bits= */ 64, + /* init= */ igraph_rng_pcg64_init, + /* destroy= */ igraph_rng_pcg64_destroy, + /* seed= */ igraph_rng_pcg64_seed, + /* get= */ igraph_rng_pcg64_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL + }; + + This is an implementation of the PCG random number generator; see +https://www.pcg-random.org (https://www.pcg-random.org) for more +details. This implementation returns 64 random bits in a single +iteration. It is only available on 64-bit plaforms with compilers that +provide the __uint128_t type. + + PCG64 typically provides better performance than PCG32 when sampling +floating point numbers or very large integers, as it can provide twice +as many random bits in a single generation round. + + The generator was ported from the original source code published by +the authors at https://github.com/imneme/pcg-c +(https://github.com/imneme/pcg-c). + + +File: igraph-docs.info, Node: Use cases, Prev: Supported random number generators, Up: Random numbers + +8.6 Use cases +============= + +* Menu: + +* Normal (default) use: Normal [default] use. +* Reproducible simulations:: +* Changing the default generator:: +* Using multiple generators:: +* Example:: + + +File: igraph-docs.info, Node: Normal [default] use, Next: Reproducible simulations, Up: Use cases + +8.6.1 Normal (default) use +-------------------------- + +If the user does not use any of the RNG functions explicitly, but calls +some of the randomized igraph functions, then a default RNG is set up +the first time an igraph function needs random numbers. The seed of +this RNG is the output of the ‘time(0)’ function call, using the ‘time’ +function from the standard C library. This ensures that igraph creates +a different random graph, each time the C program is called. + + The created default generator is stored internally and can be queried +with the ‘igraph_rng_default()’ (*note igraph_rng_default --- Query the +default random number generator_::) function. + + +File: igraph-docs.info, Node: Reproducible simulations, Next: Changing the default generator, Prev: Normal [default] use, Up: Use cases + +8.6.2 Reproducible simulations +------------------------------ + +If reproducible results are needed, then the user should set the seed of +the default random number generator explicitly, using the +‘igraph_rng_seed()’ (*note igraph_rng_seed --- Seeds a random number +generator_::) function on the default generator, ‘igraph_rng_default()’ +(*note igraph_rng_default --- Query the default random number +generator_::). When setting the seed to the same number, igraph +generates exactly the same random graph (or series of random graphs). + + +File: igraph-docs.info, Node: Changing the default generator, Next: Using multiple generators, Prev: Reproducible simulations, Up: Use cases + +8.6.3 Changing the default generator +------------------------------------ + +By default igraph uses the ‘igraph_rng_default()’ (*note +igraph_rng_default --- Query the default random number generator_::) +random number generator. This can be changed any time by calling +‘igraph_rng_set_default()’ (*note igraph_rng_set_default --- Set the +default igraph random number generator_::), with an already initialized +random number generator. Note that the old (replaced) generator is not +destroyed, so no memory is deallocated. + + +File: igraph-docs.info, Node: Using multiple generators, Next: Example, Prev: Changing the default generator, Up: Use cases + +8.6.4 Using multiple generators +------------------------------- + +igraph also provides functions to set up multiple random number +generators, using the ‘igraph_rng_init()’ (*note igraph_rng_init --- +Initializes a random number generator_::) function, and then generating +random numbers from them, e.g. with ‘igraph_rng_get_integer()’ (*note +igraph_rng_get_integer --- Generate an integer random number from an +interval_::) and/or ‘igraph_rng_get_unif()’ (*note igraph_rng_get_unif +--- Samples real numbers from a given interval_::) calls. + + Note that initializing a new random number generator is independent +of the generator that the igraph functions themselves use. If you want +to replace that, then please use ‘igraph_rng_set_default()’ (*note +igraph_rng_set_default --- Set the default igraph random number +generator_::). + + +File: igraph-docs.info, Node: Example, Prev: Using multiple generators, Up: Use cases + +8.6.5 Example +------------- + +* File examples/simple/random_seed.c* + + +File: igraph-docs.info, Node: Vertex and edge selectors and sequences; iterators, Next: Graph; vertex and edge attributes, Prev: Random numbers, Up: Top + +9 Vertex and edge selectors and sequences, iterators +**************************************************** + +* Menu: + +* About selectors, iterators: About selectors; iterators. +* Vertex selector constructors:: +* Generic vertex selector operations:: +* Immediate vertex selectors:: +* Vertex iterators:: +* Edge selector constructors:: +* Immediate edge selectors:: +* Generic edge selector operations:: +* Edge iterators:: + + +File: igraph-docs.info, Node: About selectors; iterators, Next: Vertex selector constructors, Up: Vertex and edge selectors and sequences; iterators + +9.1 About selectors, iterators +============================== + +Everything about vertices and vertex selectors also applies to edges and +edge selectors unless explicitly noted otherwise. + + The vertex (and edge) selector notion was introduced in igraph 0.2. +It is a way to reference a sequence of vertices or edges independently +of the graph. + + While this might sound quite mysterious, it is actually very simple. +For example, all vertices of a graph can be selected by +‘igraph_vs_all()’ (*note igraph_vs_all --- Vertex set; all vertices of a +graph_::) and the graph independence means that ‘igraph_vs_all()’ (*note +igraph_vs_all --- Vertex set; all vertices of a graph_::) is not +parametrized by a graph object. That is, ‘igraph_vs_all()’ (*note +igraph_vs_all --- Vertex set; all vertices of a graph_::) is the general +_concept_ of selecting all vertices of a graph. A vertex selector is +then a way to specify the class of vertices to be visited. The selector +might specify that all vertices of a graph or all the neighbours of a +vertex are to be visited. A vertex selector is a way of saying that you +want to visit a bunch of vertices, as opposed to a vertex iterator which +is a concrete plan for visiting each of the chosen vertices of a +specific graph. + + To determine the actual vertex IDs implied by a vertex selector, you +need to apply the concept of selecting vertices to a specific graph +object. This can be accomplished by instantiating a vertex iterator +using a specific vertex selection concept and a specific graph object. +The notion of vertex iterators can be thought of in the following way. +Given a specific graph object and the class of vertices to be visited, a +vertex iterator is a road map, plan or route for how to visit the chosen +vertices. + + Some vertex selectors have _immediate_ versions. These have the +prefix ‘igraph_vss’ instead of ‘igraph_vs’, e.g. ‘igraph_vss_all()’ +(*note igraph_vss_all --- All vertices of a graph [immediate +version]_::) instead of ‘igraph_vs_all()’ (*note igraph_vs_all --- +Vertex set; all vertices of a graph_::). The immediate versions are to +be used in the parameter list of the igraph functions, such as +‘igraph_degree()’ (*note igraph_degree --- The degree of some vertices +in a graph_::). These functions are not associated with any +‘igraph_vs_t’ object, so they have no separate constructors and +destructors (destroy functions). + + +File: igraph-docs.info, Node: Vertex selector constructors, Next: Generic vertex selector operations, Prev: About selectors; iterators, Up: Vertex and edge selectors and sequences; iterators + +9.2 Vertex selector constructors +================================ + +Vertex selectors are created by vertex selector constructors, can be +instantiated with ‘igraph_vit_create()’ (*note igraph_vit_create --- +Creates a vertex iterator from a vertex selector_::), and are destroyed +with ‘igraph_vs_destroy()’ (*note igraph_vs_destroy --- Destroy a vertex +set_::). + +* Menu: + +* igraph_vs_all -- Vertex set, all vertices of a graph.: igraph_vs_all --- Vertex set; all vertices of a graph_. +* igraph_vs_adj -- Adjacent vertices of a vertex.: igraph_vs_adj --- Adjacent vertices of a vertex_. +* igraph_vs_nonadj -- Non-adjacent vertices of a vertex.: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_. +* igraph_vs_none -- Empty vertex set.: igraph_vs_none --- Empty vertex set_. +* igraph_vs_1 -- Vertex set with a single vertex.: igraph_vs_1 --- Vertex set with a single vertex_. +* igraph_vs_vector -- Vertex set based on a vector.: igraph_vs_vector --- Vertex set based on a vector_. +* igraph_vs_vector_small -- Create a vertex set by giving its elements.: igraph_vs_vector_small --- Create a vertex set by giving its elements_. +* igraph_vs_vector_copy -- Vertex set based on a vector, with copying.: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_. +* igraph_vs_range -- Vertex set, an interval of vertices.: igraph_vs_range --- Vertex set; an interval of vertices_. + + +File: igraph-docs.info, Node: igraph_vs_all --- Vertex set; all vertices of a graph_, Next: igraph_vs_adj --- Adjacent vertices of a vertex_, Up: Vertex selector constructors + +9.2.1 igraph_vs_all -- Vertex set, all vertices of a graph. +----------------------------------------------------------- + + + igraph_error_t igraph_vs_all(igraph_vs_t *vs); + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized ‘igraph_vs_t’ object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vss_all()’ (*note igraph_vss_all --- All vertices of a + graph [immediate version]_::), ‘igraph_vs_destroy()’ (*note + igraph_vs_destroy --- Destroy a vertex set_::) + + This selector includes all vertices of a given graph in increasing +vertex ID order. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_adj --- Adjacent vertices of a vertex_, Next: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_, Prev: igraph_vs_all --- Vertex set; all vertices of a graph_, Up: Vertex selector constructors + +9.2.2 igraph_vs_adj -- Adjacent vertices of a vertex. +----------------------------------------------------- + + + igraph_error_t igraph_vs_adj( + igraph_vs_t *vs, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops, igraph_bool_t multiple + ); + + All neighboring vertices of a given vertex are selected by this +selector. The ‘mode’ argument controls the type of the neighboring +vertices to be selected. The vertices are visited in increasing vertex +ID order, as of igraph version 0.4. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + +‘vid’: + Vertex ID, the center of the neighborhood. + +‘mode’: + Decides the type of the neighborhood for directed graphs. This + parameter is ignored for undirected graphs. Possible values: + + ‘IGRAPH_OUT’ + All vertices to which there is a directed edge from ‘vid’. + That is, all the out-neighbors of ‘vid’. + + ‘IGRAPH_IN’ + All vertices from which there is a directed edge to ‘vid’. In + other words, all the in-neighbors of ‘vid’. + + ‘IGRAPH_ALL’ + All vertices to which or from which there is a directed edge + from/to ‘vid’. That is, all the neighbors of ‘vid’ considered + as if the graph is undirected. + +‘loops’: + Whether to include the vertex itself in the neighborhood if the + vertex has a loop edge. If ‘IGRAPH_NO_LOOPS’, loop edges are + excluded. If ‘IGRAPH_LOOPS_ONCE’, the vertex is included in its + own neighborhood once for every loop edge that it has. If + ‘IGRAPH_LOOPS_TWICE’, the vertex is included twice in its own + neighborhood for every loop edge that it has, but only if the graph + is undirected or ‘mode’ is set to ‘IGRAPH_ALL’. + +‘multiple’: + Whether to include multiple edges. If ‘IGRAPH_NO_MULTIPLE’, + multiple edges are not included in the neighborhood. If + ‘IGRAPH_MULTIPLE’, multiple edges are included in the neighborhood. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vs_destroy()’ (*note igraph_vs_destroy --- Destroy a vertex + set_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_, Next: igraph_vs_none --- Empty vertex set_, Prev: igraph_vs_adj --- Adjacent vertices of a vertex_, Up: Vertex selector constructors + +9.2.3 igraph_vs_nonadj -- Non-adjacent vertices of a vertex. +------------------------------------------------------------ + + + igraph_error_t igraph_vs_nonadj(igraph_vs_t *vs, igraph_int_t vid, + igraph_neimode_t mode); + + All non-neighboring vertices of a given vertex. The ‘mode’ argument +controls the type of neighboring vertices _not_ to select. Instead of +selecting immediate neighbors of ‘vid’ as is done by ‘igraph_vs_adj()’ +(*note igraph_vs_adj --- Adjacent vertices of a vertex_::), the current +function selects vertices that are _not_ immediate neighbors of ‘vid’. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + +‘vid’: + Vertex ID, the 'center' of the non-neighborhood. + +‘mode’: + The type of neighborhood not to select in directed graphs. + Possible values: + + ‘IGRAPH_OUT’ + All vertices will be selected except those to which there is a + directed edge from ‘vid’. That is, we select all vertices + excluding the out-neighbors of ‘vid’. + + ‘IGRAPH_IN’ + All vertices will be selected except those from which there is + a directed edge to ‘vid’. In other words, we select all + vertices but the in-neighbors of ‘vid’. + + ‘IGRAPH_ALL’ + All vertices will be selected except those from or to which + there is a directed edge to or from ‘vid’. That is, we select + all vertices of ‘vid’ except for its immediate neighbors. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vs_destroy()’ (*note igraph_vs_destroy --- Destroy a vertex + set_::) + + Time complexity: O(1). + + * File examples/simple/igraph_vs_nonadj.c* + + +File: igraph-docs.info, Node: igraph_vs_none --- Empty vertex set_, Next: igraph_vs_1 --- Vertex set with a single vertex_, Prev: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_, Up: Vertex selector constructors + +9.2.4 igraph_vs_none -- Empty vertex set. +----------------------------------------- + + + igraph_error_t igraph_vs_none(igraph_vs_t *vs); + + Creates an empty vertex selector. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vss_none()’ (*note igraph_vss_none --- Empty vertex set + [immediate version]_::), ‘igraph_vs_destroy()’ (*note + igraph_vs_destroy --- Destroy a vertex set_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_1 --- Vertex set with a single vertex_, Next: igraph_vs_vector --- Vertex set based on a vector_, Prev: igraph_vs_none --- Empty vertex set_, Up: Vertex selector constructors + +9.2.5 igraph_vs_1 -- Vertex set with a single vertex. +----------------------------------------------------- + + + igraph_error_t igraph_vs_1(igraph_vs_t *vs, igraph_int_t vid); + + This vertex selector selects a single vertex. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + +‘vid’: + The vertex ID to be selected. + + *Returns:. * + +‘’ + Error Code. + + *See also:. * + +‘’ + ‘igraph_vss_1()’ (*note igraph_vss_1 --- Vertex set with a single + vertex [immediate version]_::), ‘igraph_vs_destroy()’ (*note + igraph_vs_destroy --- Destroy a vertex set_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_vector --- Vertex set based on a vector_, Next: igraph_vs_vector_small --- Create a vertex set by giving its elements_, Prev: igraph_vs_1 --- Vertex set with a single vertex_, Up: Vertex selector constructors + +9.2.6 igraph_vs_vector -- Vertex set based on a vector. +------------------------------------------------------- + + + igraph_error_t igraph_vs_vector(igraph_vs_t *vs, + const igraph_vector_int_t *v); + + This function makes it possible to handle an ‘igraph_vector_int_t’ +temporarily as a vertex selector. The vertex selector should be thought +of as a _view_ into the vector. If you make changes to the vector that +also affects the vertex selector. Destroying the vertex selector does +not destroy the vector. Do not destroy the vector before destroying the +vertex selector, or you might get strange behavior. Since selectors are +not tied to any specific graph, this function does not check whether the +vertex IDs in the vector are valid. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector. + +‘v’: + Pointer to a ‘igraph_vector_int_t’ object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vss_vector()’ (*note igraph_vss_vector --- Vertex set based + on a vector [immediate version]_::), ‘igraph_vs_destroy()’ (*note + igraph_vs_destroy --- Destroy a vertex set_::) + + Time complexity: O(1). + + * File examples/simple/igraph_vs_vector.c* + + +File: igraph-docs.info, Node: igraph_vs_vector_small --- Create a vertex set by giving its elements_, Next: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_, Prev: igraph_vs_vector --- Vertex set based on a vector_, Up: Vertex selector constructors + +9.2.7 igraph_vs_vector_small -- Create a vertex set by giving its elements. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_vs_vector_small(igraph_vs_t *vs, ...); + + This function can be used to create a vertex selector with a few of +vertices. Do not forget to include a ‘-1’ after the last vertex ID. The +behavior of the function is undefined if you don't use a ‘-1’ properly. + + Note that the vertex IDs supplied will be parsed as value of type +‘int’ so you cannot supply arbitrarily large (too large for ‘int’) +vertex IDs here. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + +‘...’: + Additional parameters, these will be the vertex IDs to be included + in the vertex selector. Supply a ‘-1’ after the last vertex ID. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vs_destroy()’ (*note igraph_vs_destroy --- Destroy a vertex + set_::) + + Time complexity: O(n), the number of vertex IDs supplied. + + +File: igraph-docs.info, Node: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_, Next: igraph_vs_range --- Vertex set; an interval of vertices_, Prev: igraph_vs_vector_small --- Create a vertex set by giving its elements_, Up: Vertex selector constructors + +9.2.8 igraph_vs_vector_copy -- Vertex set based on a vector, with copying. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_vs_vector_copy(igraph_vs_t *vs, const igraph_vector_int_t *v); + + This function makes it possible to handle an ‘igraph_vector_int_t’ +permanently as a vertex selector. The vertex selector creates a copy of +the original vector, so the vector can safely be destroyed after +creating the vertex selector. Changing the original vector will not +affect the vertex selector. The vertex selector is responsible for +deleting the copy made by itself. Since selectors are not tied to any +specific graph, this function does not check whether the vertex IDs in +the vector are valid. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector. + +‘v’: + Pointer to a ‘igraph_vector_int_t’ object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vs_destroy()’ (*note igraph_vs_destroy --- Destroy a vertex + set_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_range --- Vertex set; an interval of vertices_, Prev: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_, Up: Vertex selector constructors + +9.2.9 igraph_vs_range -- Vertex set, an interval of vertices. +------------------------------------------------------------- + + + igraph_error_t igraph_vs_range(igraph_vs_t *vs, igraph_int_t start, igraph_int_t end); + + Creates a vertex selector containing all vertices with vertex ID +equal to or bigger than ‘from’ and smaller than ‘to’. Note that the +interval is closed from the left and open from the right, following C +conventions. + + *Arguments:. * + +‘vs’: + Pointer to an uninitialized vertex selector object. + +‘start’: + The first vertex ID to be included in the vertex selector. + +‘end’: + The first vertex ID _not_ to be included in the vertex selector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vss_range()’ (*note igraph_vss_range --- An interval of + vertices [immediate version]_::), ‘igraph_vs_destroy()’ (*note + igraph_vs_destroy --- Destroy a vertex set_::) + + Time complexity: O(1). + + * File examples/simple/igraph_vs_range.c* + + +File: igraph-docs.info, Node: Generic vertex selector operations, Next: Immediate vertex selectors, Prev: Vertex selector constructors, Up: Vertex and edge selectors and sequences; iterators + +9.3 Generic vertex selector operations +====================================== + +* Menu: + +* igraph_vs_copy -- Creates a copy of a vertex selector.: igraph_vs_copy --- Creates a copy of a vertex selector_. +* igraph_vs_destroy -- Destroy a vertex set.: igraph_vs_destroy --- Destroy a vertex set_. +* igraph_vs_is_all -- Check whether all vertices are included.: igraph_vs_is_all --- Check whether all vertices are included_. +* igraph_vs_size -- Returns the size of the vertex selector.: igraph_vs_size --- Returns the size of the vertex selector_. +* igraph_vs_type -- Returns the type of the vertex selector.: igraph_vs_type --- Returns the type of the vertex selector_. + + +File: igraph-docs.info, Node: igraph_vs_copy --- Creates a copy of a vertex selector_, Next: igraph_vs_destroy --- Destroy a vertex set_, Up: Generic vertex selector operations + +9.3.1 igraph_vs_copy -- Creates a copy of a vertex selector. +------------------------------------------------------------ + + + igraph_error_t igraph_vs_copy(igraph_vs_t* dest, const igraph_vs_t* src); + + *Arguments:. * + +‘dest’: + An uninitialized selector that will contain the copy. + +‘src’: + The selector being copied. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_vs_destroy --- Destroy a vertex set_, Next: igraph_vs_is_all --- Check whether all vertices are included_, Prev: igraph_vs_copy --- Creates a copy of a vertex selector_, Up: Generic vertex selector operations + +9.3.2 igraph_vs_destroy -- Destroy a vertex set. +------------------------------------------------ + + + void igraph_vs_destroy(igraph_vs_t *vs); + + This function should be called for all vertex selectors when they are +not needed. The memory allocated for the vertex selector will be +deallocated. Do not call this function on vertex selectors created with +the immediate versions of the vertex selector constructors (starting +with ‘igraph_vss’). + + *Arguments:. * + +‘vs’: + Pointer to a vertex selector object. + + Time complexity: operating system dependent, usually O(1). + + +File: igraph-docs.info, Node: igraph_vs_is_all --- Check whether all vertices are included_, Next: igraph_vs_size --- Returns the size of the vertex selector_, Prev: igraph_vs_destroy --- Destroy a vertex set_, Up: Generic vertex selector operations + +9.3.3 igraph_vs_is_all -- Check whether all vertices are included. +------------------------------------------------------------------ + + + igraph_bool_t igraph_vs_is_all(const igraph_vs_t *vs); + + This function checks whether the vertex selector object was created +by ‘igraph_vs_all()’ (*note igraph_vs_all --- Vertex set; all vertices +of a graph_::) or ‘igraph_vss_all()’ (*note igraph_vss_all --- All +vertices of a graph [immediate version]_::). Note that the vertex +selector might contain all vertices in a given graph but if it wasn't +created by the two constructors mentioned here the return value will be +‘false’. + + *Arguments:. * + +‘vs’: + Pointer to a vertex selector object. + + *Returns:. * + +‘’ + ‘true’ if the vertex selector contains all vertices and ‘false’ + otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vs_size --- Returns the size of the vertex selector_, Next: igraph_vs_type --- Returns the type of the vertex selector_, Prev: igraph_vs_is_all --- Check whether all vertices are included_, Up: Generic vertex selector operations + +9.3.4 igraph_vs_size -- Returns the size of the vertex selector. +---------------------------------------------------------------- + + + igraph_error_t igraph_vs_size(const igraph_t *graph, const igraph_vs_t *vs, + igraph_int_t *result); + + The size of the vertex selector is the number of vertices it will +yield when it is iterated over. + + *Arguments:. * + +‘graph’: + The graph over which we will iterate. + +‘vs’: + the vertex selector. + +‘result’: + The result will be returned here. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_vs_type --- Returns the type of the vertex selector_, Prev: igraph_vs_size --- Returns the size of the vertex selector_, Up: Generic vertex selector operations + +9.3.5 igraph_vs_type -- Returns the type of the vertex selector. +---------------------------------------------------------------- + + + igraph_vs_type_t igraph_vs_type(const igraph_vs_t *vs); + + +File: igraph-docs.info, Node: Immediate vertex selectors, Next: Vertex iterators, Prev: Generic vertex selector operations, Up: Vertex and edge selectors and sequences; iterators + +9.4 Immediate vertex selectors +============================== + +* Menu: + +* igraph_vss_all -- All vertices of a graph (immediate version).: igraph_vss_all --- All vertices of a graph [immediate version]_. +* igraph_vss_none -- Empty vertex set (immediate version).: igraph_vss_none --- Empty vertex set [immediate version]_. +* igraph_vss_1 -- Vertex set with a single vertex (immediate version).: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_. +* igraph_vss_vector -- Vertex set based on a vector (immediate version).: igraph_vss_vector --- Vertex set based on a vector [immediate version]_. +* igraph_vss_range -- An interval of vertices (immediate version).: igraph_vss_range --- An interval of vertices [immediate version]_. + + +File: igraph-docs.info, Node: igraph_vss_all --- All vertices of a graph [immediate version]_, Next: igraph_vss_none --- Empty vertex set [immediate version]_, Up: Immediate vertex selectors + +9.4.1 igraph_vss_all -- All vertices of a graph (immediate version). +-------------------------------------------------------------------- + + + igraph_vs_t igraph_vss_all(void); + + Immediate vertex selector for all vertices in a graph. It can be +used conveniently when some vertex property (e.g. betweenness, degree, +etc.) should be calculated for all vertices. + + *Returns:. * + +‘’ + A vertex selector for all vertices in a graph. + + *See also:. * + +‘’ + ‘igraph_vs_all()’ (*note igraph_vs_all --- Vertex set; all vertices + of a graph_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vss_none --- Empty vertex set [immediate version]_, Next: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_, Prev: igraph_vss_all --- All vertices of a graph [immediate version]_, Up: Immediate vertex selectors + +9.4.2 igraph_vss_none -- Empty vertex set (immediate version). +-------------------------------------------------------------- + + + igraph_vs_t igraph_vss_none(void); + + The immediate version of the empty vertex selector. + + *Returns:. * + +‘’ + An empty vertex selector. + + *See also:. * + +‘’ + ‘igraph_vs_none()’ (*note igraph_vs_none --- Empty vertex set_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_, Next: igraph_vss_vector --- Vertex set based on a vector [immediate version]_, Prev: igraph_vss_none --- Empty vertex set [immediate version]_, Up: Immediate vertex selectors + +9.4.3 igraph_vss_1 -- Vertex set with a single vertex (immediate version). +-------------------------------------------------------------------------- + + + igraph_vs_t igraph_vss_1(igraph_int_t vid); + + The immediate version of the single-vertex selector. + + *Arguments:. * + +‘vid’: + The vertex to be selected. + + *Returns:. * + +‘’ + A vertex selector containing a single vertex. + + *See also:. * + +‘’ + ‘igraph_vs_1()’ (*note igraph_vs_1 --- Vertex set with a single + vertex_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vss_vector --- Vertex set based on a vector [immediate version]_, Next: igraph_vss_range --- An interval of vertices [immediate version]_, Prev: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_, Up: Immediate vertex selectors + +9.4.4 igraph_vss_vector -- Vertex set based on a vector (immediate version). +---------------------------------------------------------------------------- + + + igraph_vs_t igraph_vss_vector(const igraph_vector_int_t *v); + + This is the immediate version of ‘igraph_vs_vector’ (*note +igraph_vs_vector --- Vertex set based on a vector_::). + + *Arguments:. * + +‘v’: + Pointer to a ‘igraph_vector_int_t’ object. + + *Returns:. * + +‘’ + A vertex selector object containing the vertices in the vector. + + *See also:. * + +‘’ + ‘igraph_vs_vector()’ (*note igraph_vs_vector --- Vertex set based + on a vector_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_vss_range --- An interval of vertices [immediate version]_, Prev: igraph_vss_vector --- Vertex set based on a vector [immediate version]_, Up: Immediate vertex selectors + +9.4.5 igraph_vss_range -- An interval of vertices (immediate version). +---------------------------------------------------------------------- + + + igraph_vs_t igraph_vss_range(igraph_int_t start, igraph_int_t end); + + The immediate version of ‘igraph_vs_range()’ (*note igraph_vs_range +--- Vertex set; an interval of vertices_::). + + *Arguments:. * + +‘start’: + The first vertex ID to be included in the vertex selector. + +‘end’: + The first vertex ID _not_ to be included in the vertex selector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vs_range()’ (*note igraph_vs_range --- Vertex set; an + interval of vertices_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Vertex iterators, Next: Edge selector constructors, Prev: Immediate vertex selectors, Up: Vertex and edge selectors and sequences; iterators + +9.5 Vertex iterators +==================== + +* Menu: + +* igraph_vit_create -- Creates a vertex iterator from a vertex selector.: igraph_vit_create --- Creates a vertex iterator from a vertex selector_. +* igraph_vit_destroy -- Destroys a vertex iterator.: igraph_vit_destroy --- Destroys a vertex iterator_. +* Stepping over the vertices:: +* IGRAPH_VIT_NEXT -- Next vertex.: IGRAPH_VIT_NEXT --- Next vertex_. +* IGRAPH_VIT_END --- Are we at the end?:: +* IGRAPH_VIT_SIZE -- Size of a vertex iterator.: IGRAPH_VIT_SIZE --- Size of a vertex iterator_. +* IGRAPH_VIT_RESET -- Reset a vertex iterator.: IGRAPH_VIT_RESET --- Reset a vertex iterator_. +* IGRAPH_VIT_GET -- Query the current position.: IGRAPH_VIT_GET --- Query the current position_. + + +File: igraph-docs.info, Node: igraph_vit_create --- Creates a vertex iterator from a vertex selector_, Next: igraph_vit_destroy --- Destroys a vertex iterator_, Up: Vertex iterators + +9.5.1 igraph_vit_create -- Creates a vertex iterator from a vertex selector. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_vit_create(const igraph_t *graph, igraph_vs_t vs, igraph_vit_t *vit); + + This function instantiates a vertex selector object with a given +graph. This is the step when the actual vertex IDs are created from the +_logical_ notion of the vertex selector based on the graph. E.g. a +vertex selector created with ‘igraph_vs_all()’ (*note igraph_vs_all --- +Vertex set; all vertices of a graph_::) contains knowledge that _all_ +vertices are included in a (yet indefinite) graph. When instantiating +it a vertex iterator object is created, this contains the actual vertex +IDs in the graph supplied as a parameter. + + The same vertex selector object can be used to instantiate any number +vertex iterators. + + *Arguments:. * + +‘graph’: + An ‘igraph_t’ object, a graph. + +‘vs’: + A vertex selector object. + +‘vit’: + Pointer to an uninitialized vertex iterator object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_vit_destroy()’ (*note igraph_vit_destroy --- Destroys a + vertex iterator_::). + + Time complexity: it depends on the vertex selector type. O(1) for +vertex selectors created with ‘igraph_vs_all()’ (*note igraph_vs_all --- +Vertex set; all vertices of a graph_::), ‘igraph_vs_none()’ (*note +igraph_vs_none --- Empty vertex set_::), ‘igraph_vs_1’ (*note +igraph_vs_1 --- Vertex set with a single vertex_::), ‘igraph_vs_vector’ +(*note igraph_vs_vector --- Vertex set based on a vector_::), +‘igraph_vs_range()’ (*note igraph_vs_range --- Vertex set; an interval +of vertices_::), ‘igraph_vs_vector()’ (*note igraph_vs_vector --- Vertex +set based on a vector_::), ‘igraph_vs_vector_small()’ (*note +igraph_vs_vector_small --- Create a vertex set by giving its +elements_::). O(d) for ‘igraph_vs_adj()’ (*note igraph_vs_adj --- +Adjacent vertices of a vertex_::), d is the number of vertex IDs to be +included in the iterator. O(|V|) for ‘igraph_vs_nonadj()’ (*note +igraph_vs_nonadj --- Non-adjacent vertices of a vertex_::), |V| is the +number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_vit_destroy --- Destroys a vertex iterator_, Next: Stepping over the vertices, Prev: igraph_vit_create --- Creates a vertex iterator from a vertex selector_, Up: Vertex iterators + +9.5.2 igraph_vit_destroy -- Destroys a vertex iterator. +------------------------------------------------------- + + + void igraph_vit_destroy(const igraph_vit_t *vit); + + Deallocates memory allocated for a vertex iterator. + + *Arguments:. * + +‘vit’: + Pointer to an initialized vertex iterator object. + + *See also:. * + +‘’ + ‘igraph_vit_create()’ (*note igraph_vit_create --- Creates a vertex + iterator from a vertex selector_::) + + Time complexity: operating system dependent, usually O(1). + + +File: igraph-docs.info, Node: Stepping over the vertices, Next: IGRAPH_VIT_NEXT --- Next vertex_, Prev: igraph_vit_destroy --- Destroys a vertex iterator_, Up: Vertex iterators + +9.5.3 Stepping over the vertices +-------------------------------- + +After creating an iterator with ‘igraph_vit_create()’ (*note +igraph_vit_create --- Creates a vertex iterator from a vertex +selector_::), it points to the first vertex in the vertex determined by +the vertex selector (if there is any). The ‘IGRAPH_VIT_NEXT()’ (*note +IGRAPH_VIT_NEXT --- Next vertex_::) macro steps to the next vertex, +‘IGRAPH_VIT_END()’ (*note IGRAPH_VIT_END --- Are we at the end?::) +checks whether there are more vertices to visit, ‘IGRAPH_VIT_SIZE()’ +(*note IGRAPH_VIT_SIZE --- Size of a vertex iterator_::) gives the total +size of the vertices visited so far and to be visited. +‘IGRAPH_VIT_RESET()’ (*note IGRAPH_VIT_RESET --- Reset a vertex +iterator_::) resets the iterator, it will point to the first vertex +again. Finally ‘IGRAPH_VIT_GET()’ (*note IGRAPH_VIT_GET --- Query the +current position_::) gives the current vertex pointed to by the iterator +(call this only if ‘IGRAPH_VIT_END()’ (*note IGRAPH_VIT_END --- Are we +at the end?::) is false). + + Here is an example on how to step over the neighbors of vertex 0: + + + igraph_vs_t vs; + igraph_vit_t vit; + ... + igraph_vs_adj(&vs, 0, IGRAPH_ALL); + igraph_vit_create(&graph, vs, &vit); + while (!IGRAPH_VIT_END(vit)) { + printf(" %" IGRAPH_PRId, IGRAPH_VIT_GET(vit)); + IGRAPH_VIT_NEXT(vit); + } + printf("\n"); + ... + igraph_vit_destroy(&vit); + igraph_vs_destroy(&vs); + + +File: igraph-docs.info, Node: IGRAPH_VIT_NEXT --- Next vertex_, Next: IGRAPH_VIT_END --- Are we at the end?, Prev: Stepping over the vertices, Up: Vertex iterators + +9.5.4 IGRAPH_VIT_NEXT -- Next vertex. +------------------------------------- + + + #define IGRAPH_VIT_NEXT(vit) + + Steps the iterator to the next vertex. Only call this function if +‘IGRAPH_VIT_END()’ (*note IGRAPH_VIT_END --- Are we at the end?::) +returns false. + + *Arguments:. * + +‘vit’: + The vertex iterator to step. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_VIT_END --- Are we at the end?, Next: IGRAPH_VIT_SIZE --- Size of a vertex iterator_, Prev: IGRAPH_VIT_NEXT --- Next vertex_, Up: Vertex iterators + +9.5.5 IGRAPH_VIT_END -- Are we at the end? +------------------------------------------ + + + #define IGRAPH_VIT_END(vit) + + Checks whether there are more vertices to step to. + + *Arguments:. * + +‘vit’: + The vertex iterator to check. + + *Returns:. * + +‘’ + Logical value, if true there are no more vertices to step to. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_VIT_SIZE --- Size of a vertex iterator_, Next: IGRAPH_VIT_RESET --- Reset a vertex iterator_, Prev: IGRAPH_VIT_END --- Are we at the end?, Up: Vertex iterators + +9.5.6 IGRAPH_VIT_SIZE -- Size of a vertex iterator. +--------------------------------------------------- + + + #define IGRAPH_VIT_SIZE(vit) + + Gives the number of vertices in a vertex iterator. + + *Arguments:. * + +‘vit’: + The vertex iterator. + + *Returns:. * + +‘’ + The number of vertices. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_VIT_RESET --- Reset a vertex iterator_, Next: IGRAPH_VIT_GET --- Query the current position_, Prev: IGRAPH_VIT_SIZE --- Size of a vertex iterator_, Up: Vertex iterators + +9.5.7 IGRAPH_VIT_RESET -- Reset a vertex iterator. +-------------------------------------------------- + + + #define IGRAPH_VIT_RESET(vit) + + Resets a vertex iterator. After calling this macro the iterator will +point to the first vertex. + + *Arguments:. * + +‘vit’: + The vertex iterator. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_VIT_GET --- Query the current position_, Prev: IGRAPH_VIT_RESET --- Reset a vertex iterator_, Up: Vertex iterators + +9.5.8 IGRAPH_VIT_GET -- Query the current position. +--------------------------------------------------- + + + #define IGRAPH_VIT_GET(vit) + + Gives the vertex ID of the current vertex pointed to by the iterator. + + *Arguments:. * + +‘vit’: + The vertex iterator. + + *Returns:. * + +‘’ + The vertex ID of the current vertex. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Edge selector constructors, Next: Immediate edge selectors, Prev: Vertex iterators, Up: Vertex and edge selectors and sequences; iterators + +9.6 Edge selector constructors +============================== + +* Menu: + +* igraph_es_all -- Edge set, all edges.: igraph_es_all --- Edge set; all edges_. +* igraph_es_incident -- Edges incident on a given vertex.: igraph_es_incident --- Edges incident on a given vertex_. +* igraph_es_none -- Empty edge selector.: igraph_es_none --- Empty edge selector_. +* igraph_es_1 -- Edge selector containing a single edge.: igraph_es_1 --- Edge selector containing a single edge_. +* igraph_es_all_between -- Edge selector, all edge IDs between a pair of vertices.: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_. +* igraph_es_vector -- Handle a vector as an edge selector.: igraph_es_vector --- Handle a vector as an edge selector_. +* igraph_es_range -- Edge selector, a sequence of edge IDs.: igraph_es_range --- Edge selector; a sequence of edge IDs_. +* igraph_es_pairs -- Edge selector, multiple edges defined by their endpoints in a vector.: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_. +* igraph_es_pairs_small -- Edge selector, multiple edges defined by their endpoints as arguments.: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_. +* igraph_es_path -- Edge selector, edge IDs on a path.: igraph_es_path --- Edge selector; edge IDs on a path_. +* igraph_es_vector_copy -- Edge set, based on a vector, with copying.: igraph_es_vector_copy --- Edge set; based on a vector; with copying_. + + +File: igraph-docs.info, Node: igraph_es_all --- Edge set; all edges_, Next: igraph_es_incident --- Edges incident on a given vertex_, Up: Edge selector constructors + +9.6.1 igraph_es_all -- Edge set, all edges. +------------------------------------------- + + + igraph_error_t igraph_es_all(igraph_es_t *es, + igraph_edgeorder_type_t order); + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘order’: + Constant giving the order in which the edges will be included in + the selector. Possible values: + + ‘IGRAPH_EDGEORDER_ID’ + Edge ID order; currently performs the fastest. + + ‘IGRAPH_EDGEORDER_FROM’ + Vertex ID order, the id of the _source_ vertex counts for + directed graphs. The order of the incident edges of a given + vertex is arbitrary. + + ‘IGRAPH_EDGEORDER_TO’ + Vertex ID order, the ID of the _target_ vertex counts for + directed graphs. The order of the incident edges of a given + vertex is arbitrary. + + For undirected graph the latter two is the same. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ess_all()’ (*note igraph_ess_all --- Edge set; all edges + [immediate version]_::), ‘igraph_es_destroy()’ (*note + igraph_es_destroy --- Destroys an edge selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_incident --- Edges incident on a given vertex_, Next: igraph_es_none --- Empty edge selector_, Prev: igraph_es_all --- Edge set; all edges_, Up: Edge selector constructors + +9.6.2 igraph_es_incident -- Edges incident on a given vertex. +------------------------------------------------------------- + + + igraph_error_t igraph_es_incident( + igraph_es_t *es, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops + ); + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘vid’: + Vertex ID, of which the incident edges will be selected. + +‘mode’: + Constant giving the type of the incident edges to select. This is + ignored for undirected graphs. Possible values: ‘IGRAPH_OUT’, + outgoing edges; ‘IGRAPH_IN’, incoming edges; ‘IGRAPH_ALL’, all + edges. + +‘loops’: + Whether to include loop edges in the result. If ‘IGRAPH_NO_LOOPS’, + loop edges are excluded. If ‘IGRAPH_LOOPS_ONCE’, loop edges are + included once. If ‘IGRAPH_LOOPS_TWICE’, loop edges are included + twice, but only if the graph is undirected or ‘mode’ is set to + ‘IGRAPH_ALL’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_none --- Empty edge selector_, Next: igraph_es_1 --- Edge selector containing a single edge_, Prev: igraph_es_incident --- Edges incident on a given vertex_, Up: Edge selector constructors + +9.6.3 igraph_es_none -- Empty edge selector. +-------------------------------------------- + + + igraph_error_t igraph_es_none(igraph_es_t *es); + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object to initialize. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ess_none()’ (*note igraph_ess_none --- Immediate empty edge + selector_::), ‘igraph_es_destroy()’ (*note igraph_es_destroy --- + Destroys an edge selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_1 --- Edge selector containing a single edge_, Next: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_, Prev: igraph_es_none --- Empty edge selector_, Up: Edge selector constructors + +9.6.4 igraph_es_1 -- Edge selector containing a single edge. +------------------------------------------------------------ + + + igraph_error_t igraph_es_1(igraph_es_t *es, igraph_int_t eid); + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘eid’: + Edge ID of the edge to select. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ess_1()’ (*note igraph_ess_1 --- Immediate version of the + single edge edge selector_::), ‘igraph_es_destroy()’ (*note + igraph_es_destroy --- Destroys an edge selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_, Next: igraph_es_vector --- Handle a vector as an edge selector_, Prev: igraph_es_1 --- Edge selector containing a single edge_, Up: Edge selector constructors + +9.6.5 igraph_es_all_between -- Edge selector, all edge IDs between a pair of vertices. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_es_all_between( + igraph_es_t *es, igraph_int_t from, igraph_int_t to, + igraph_bool_t directed + ); + + This function takes a pair of vertices and creates a selector that +matches all edges between those vertices. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘directed’: + If edge directions should be taken into account. This will be + ignored if the graph to select from is undirected. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_vector --- Handle a vector as an edge selector_, Next: igraph_es_range --- Edge selector; a sequence of edge IDs_, Prev: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_, Up: Edge selector constructors + +9.6.6 igraph_es_vector -- Handle a vector as an edge selector. +-------------------------------------------------------------- + + + igraph_error_t igraph_es_vector(igraph_es_t *es, const igraph_vector_int_t *v); + + Creates an edge selector which serves as a view into a vector +containing edge IDs. Do not destroy the vector before destroying the +edge selector. Since selectors are not tied to any specific graph, this +function does not check whether the edge IDs in the vector are valid. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector. + +‘v’: + Vector containing edge IDs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ess_vector()’ (*note igraph_ess_vector --- Immediate vector + view edge selector_::), ‘igraph_es_destroy()’ (*note + igraph_es_destroy --- Destroys an edge selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_range --- Edge selector; a sequence of edge IDs_, Next: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_, Prev: igraph_es_vector --- Handle a vector as an edge selector_, Up: Edge selector constructors + +9.6.7 igraph_es_range -- Edge selector, a sequence of edge IDs. +--------------------------------------------------------------- + + + igraph_error_t igraph_es_range(igraph_es_t *es, igraph_int_t start, igraph_int_t end); + + Creates an edge selector containing all edges with edge ID equal to +or bigger than ‘from’ and smaller than ‘to’. Note that the interval is +closed from the left and open from the right, following C conventions. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘start’: + The first edge ID to be included in the edge selector. + +‘end’: + The first edge ID _not_ to be included in the edge selector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ess_range()’ (*note igraph_ess_range --- Immediate version + of the sequence edge selector_::), ‘igraph_es_destroy()’ (*note + igraph_es_destroy --- Destroys an edge selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_, Next: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_, Prev: igraph_es_range --- Edge selector; a sequence of edge IDs_, Up: Edge selector constructors + +9.6.8 igraph_es_pairs -- Edge selector, multiple edges defined by their endpoints in a vector. +---------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_es_pairs(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed); + + The edges between the given pairs of vertices will be included in the +edge selection. The vertex pairs must be defined in the vector ‘v’, the +first element of the vector is the first vertex of the first edge to be +selected, the second element is the second vertex of the first edge, the +third element is the first vertex of the second edge and so on. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘v’: + The vector containing the endpoints of the edges. + +‘directed’: + Whether the graph is directed or not. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_pairs_small()’ (*note igraph_es_pairs_small --- Edge + selector; multiple edges defined by their endpoints as + arguments_::), ‘igraph_es_destroy()’ (*note igraph_es_destroy --- + Destroys an edge selector object_::) + + Time complexity: O(n), the number of edges being selected. + + * File examples/simple/igraph_es_pairs.c* + + +File: igraph-docs.info, Node: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_, Next: igraph_es_path --- Edge selector; edge IDs on a path_, Prev: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_, Up: Edge selector constructors + +9.6.9 igraph_es_pairs_small -- Edge selector, multiple edges defined by their endpoints as arguments. +----------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_es_pairs_small(igraph_es_t *es, igraph_bool_t directed, int first, ...); + + The edges between the given pairs of vertices will be included in the +edge selection. The vertex pairs must be given as the arguments of the +function call, the third argument is the first vertex of the first edge, +the fourth argument is the second vertex of the first edge, the fifth is +the first vertex of the second edge and so on. The last element of the +argument list must be -1 to denote the end of the argument list. + + Note that the vertex IDs supplied will be parsed as ‘int’'s so you +cannot supply arbitrarily large (too large for int) vertex IDs here. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘directed’: + Whether the graph is directed or not. + +‘...’: + The additional arguments give the edges to be included in the + selector, as pairs of vertex IDs. The last argument must be -1. + The ‘first’ parameter is present for technical reasons and + represents the first variadic argument. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_pairs()’ (*note igraph_es_pairs --- Edge selector; + multiple edges defined by their endpoints in a vector_::), + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + Time complexity: O(n), the number of edges being selected. + + +File: igraph-docs.info, Node: igraph_es_path --- Edge selector; edge IDs on a path_, Next: igraph_es_vector_copy --- Edge set; based on a vector; with copying_, Prev: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_, Up: Edge selector constructors + +9.6.10 igraph_es_path -- Edge selector, edge IDs on a path. +----------------------------------------------------------- + + + igraph_error_t igraph_es_path(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed); + + This function takes a vector of vertices and creates a selector of +edges between those vertices. Vector {0, 3, 4, 7} will select edges (0 +-> 3), (3 -> 4), (4 -> 7). If these edges don't exist then trying to +create an iterator using this selector will fail. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector object. + +‘v’: + Pointer to a vector of vertex IDs along the path. + +‘directed’: + If edge directions should be taken into account. This will be + ignored if the graph to select from is undirected. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + Time complexity: O(n), the number of vertices. + + +File: igraph-docs.info, Node: igraph_es_vector_copy --- Edge set; based on a vector; with copying_, Prev: igraph_es_path --- Edge selector; edge IDs on a path_, Up: Edge selector constructors + +9.6.11 igraph_es_vector_copy -- Edge set, based on a vector, with copying. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_es_vector_copy(igraph_es_t *es, const igraph_vector_int_t *v); + + This function makes it possible to handle an ‘igraph_vector_int_t’ +permanently as an edge selector. The edge selector creates a copy of +the original vector, so the vector can safely be destroyed after +creating the edge selector. Changing the original vector will not +affect the edge selector. The edge selector is responsible for deleting +the copy made by itself. Since selectors are not tied to any specific +graph, this function does not check whether the edge IDs in the vector +are valid. + + *Arguments:. * + +‘es’: + Pointer to an uninitialized edge selector. + +‘v’: + Pointer to a ‘igraph_vector_int_t’ object. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Immediate edge selectors, Next: Generic edge selector operations, Prev: Edge selector constructors, Up: Vertex and edge selectors and sequences; iterators + +9.7 Immediate edge selectors +============================ + +* Menu: + +* igraph_ess_all -- Edge set, all edges (immediate version).: igraph_ess_all --- Edge set; all edges [immediate version]_. +* igraph_ess_none -- Immediate empty edge selector.: igraph_ess_none --- Immediate empty edge selector_. +* igraph_ess_1 -- Immediate version of the single edge edge selector.: igraph_ess_1 --- Immediate version of the single edge edge selector_. +* igraph_ess_vector -- Immediate vector view edge selector.: igraph_ess_vector --- Immediate vector view edge selector_. +* igraph_ess_range -- Immediate version of the sequence edge selector.: igraph_ess_range --- Immediate version of the sequence edge selector_. + + +File: igraph-docs.info, Node: igraph_ess_all --- Edge set; all edges [immediate version]_, Next: igraph_ess_none --- Immediate empty edge selector_, Up: Immediate edge selectors + +9.7.1 igraph_ess_all -- Edge set, all edges (immediate version). +---------------------------------------------------------------- + + + igraph_es_t igraph_ess_all(igraph_edgeorder_type_t order); + + The immediate version of the all-edges selector. + + *Arguments:. * + +‘order’: + Constant giving the order of the edges in the edge selector. See + ‘igraph_es_all()’ (*note igraph_es_all --- Edge set; all edges_::) + for the possible values. + + *Returns:. * + +‘’ + The edge selector. + + *See also:. * + +‘’ + ‘igraph_es_all()’ (*note igraph_es_all --- Edge set; all edges_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_ess_none --- Immediate empty edge selector_, Next: igraph_ess_1 --- Immediate version of the single edge edge selector_, Prev: igraph_ess_all --- Edge set; all edges [immediate version]_, Up: Immediate edge selectors + +9.7.2 igraph_ess_none -- Immediate empty edge selector. +------------------------------------------------------- + + + igraph_es_t igraph_ess_none(void); + + Immediate version of the empty edge selector. + + *Returns:. * + +‘’ + Initialized empty edge selector. + + *See also:. * + +‘’ + ‘igraph_es_none()’ (*note igraph_es_none --- Empty edge + selector_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_ess_1 --- Immediate version of the single edge edge selector_, Next: igraph_ess_vector --- Immediate vector view edge selector_, Prev: igraph_ess_none --- Immediate empty edge selector_, Up: Immediate edge selectors + +9.7.3 igraph_ess_1 -- Immediate version of the single edge edge selector. +------------------------------------------------------------------------- + + + igraph_es_t igraph_ess_1(igraph_int_t eid); + + *Arguments:. * + +‘eid’: + The ID of the edge. + + *Returns:. * + +‘’ + The edge selector. + + *See also:. * + +‘’ + ‘igraph_es_1()’ (*note igraph_es_1 --- Edge selector containing a + single edge_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_ess_vector --- Immediate vector view edge selector_, Next: igraph_ess_range --- Immediate version of the sequence edge selector_, Prev: igraph_ess_1 --- Immediate version of the single edge edge selector_, Up: Immediate edge selectors + +9.7.4 igraph_ess_vector -- Immediate vector view edge selector. +--------------------------------------------------------------- + + + igraph_es_t igraph_ess_vector(const igraph_vector_int_t *v); + + This is the immediate version of the vector of edge IDs edge +selector. + + *Arguments:. * + +‘v’: + The vector of edge IDs. + + *Returns:. * + +‘’ + Edge selector, initialized. + + *See also:. * + +‘’ + ‘igraph_es_vector()’ (*note igraph_es_vector --- Handle a vector as + an edge selector_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_ess_range --- Immediate version of the sequence edge selector_, Prev: igraph_ess_vector --- Immediate vector view edge selector_, Up: Immediate edge selectors + +9.7.5 igraph_ess_range -- Immediate version of the sequence edge selector. +-------------------------------------------------------------------------- + + + igraph_es_t igraph_ess_range(igraph_int_t start, igraph_int_t end); + + *Arguments:. * + +‘start’: + The first edge ID to be included in the edge selector. + +‘end’: + The first edge ID _not_ to be included in the edge selector. + + *Returns:. * + +‘’ + The initialized edge selector. + + *See also:. * + +‘’ + ‘igraph_es_range()’ (*note igraph_es_range --- Edge selector; a + sequence of edge IDs_::) + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Generic edge selector operations, Next: Edge iterators, Prev: Immediate edge selectors, Up: Vertex and edge selectors and sequences; iterators + +9.8 Generic edge selector operations +==================================== + +* Menu: + +* igraph_es_as_vector -- Transform edge selector into vector.: igraph_es_as_vector --- Transform edge selector into vector_. +* igraph_es_copy -- Creates a copy of an edge selector.: igraph_es_copy --- Creates a copy of an edge selector_. +* igraph_es_destroy -- Destroys an edge selector object.: igraph_es_destroy --- Destroys an edge selector object_. +* igraph_es_is_all -- Check whether an edge selector includes all edges.: igraph_es_is_all --- Check whether an edge selector includes all edges_. +* igraph_es_size -- Returns the size of the edge selector.: igraph_es_size --- Returns the size of the edge selector_. +* igraph_es_type -- Returns the type of the edge selector.: igraph_es_type --- Returns the type of the edge selector_. + + +File: igraph-docs.info, Node: igraph_es_as_vector --- Transform edge selector into vector_, Next: igraph_es_copy --- Creates a copy of an edge selector_, Up: Generic edge selector operations + +9.8.1 igraph_es_as_vector -- Transform edge selector into vector. +----------------------------------------------------------------- + + + igraph_error_t igraph_es_as_vector(const igraph_t *graph, igraph_es_t es, + igraph_vector_int_t *v); + + Call this function on an edge selector to transform it into a vector. +This is only implemented for sequence and vector selectors. If the +edges do not exist in the graph, this will result in an error. + + *Arguments:. * + +‘graph’: + Pointer to a graph to check if the edges in the selector exist. + +‘es’: + An edge selector object. + +‘v’: + Pointer to initialized vector. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of edges in the selector. + + +File: igraph-docs.info, Node: igraph_es_copy --- Creates a copy of an edge selector_, Next: igraph_es_destroy --- Destroys an edge selector object_, Prev: igraph_es_as_vector --- Transform edge selector into vector_, Up: Generic edge selector operations + +9.8.2 igraph_es_copy -- Creates a copy of an edge selector. +----------------------------------------------------------- + + + igraph_error_t igraph_es_copy(igraph_es_t* dest, const igraph_es_t* src); + + *Arguments:. * + +‘dest’: + An uninitialized selector that will contain the copy. + +‘src’: + The selector being copied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_es_destroy()’ (*note igraph_es_destroy --- Destroys an edge + selector object_::) + + +File: igraph-docs.info, Node: igraph_es_destroy --- Destroys an edge selector object_, Next: igraph_es_is_all --- Check whether an edge selector includes all edges_, Prev: igraph_es_copy --- Creates a copy of an edge selector_, Up: Generic edge selector operations + +9.8.3 igraph_es_destroy -- Destroys an edge selector object. +------------------------------------------------------------ + + + void igraph_es_destroy(igraph_es_t *es); + + Call this function on an edge selector when it is not needed any +more. Do _not_ call this function on edge selectors created by +immediate constructors, those don't need to be destroyed. + + *Arguments:. * + +‘es’: + Pointer to an edge selector object. + + Time complexity: operating system dependent, usually O(1). + + +File: igraph-docs.info, Node: igraph_es_is_all --- Check whether an edge selector includes all edges_, Next: igraph_es_size --- Returns the size of the edge selector_, Prev: igraph_es_destroy --- Destroys an edge selector object_, Up: Generic edge selector operations + +9.8.4 igraph_es_is_all -- Check whether an edge selector includes all edges. +---------------------------------------------------------------------------- + + + igraph_bool_t igraph_es_is_all(const igraph_es_t *es); + + *Arguments:. * + +‘es’: + Pointer to an edge selector object. + + *Returns:. * + +‘’ + ‘true’ if ‘es’ was created with ‘igraph_es_all()’ (*note + igraph_es_all --- Edge set; all edges_::) or ‘igraph_ess_all()’ + (*note igraph_ess_all --- Edge set; all edges [immediate + version]_::), and ‘false’ otherwise. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_es_size --- Returns the size of the edge selector_, Next: igraph_es_type --- Returns the type of the edge selector_, Prev: igraph_es_is_all --- Check whether an edge selector includes all edges_, Up: Generic edge selector operations + +9.8.5 igraph_es_size -- Returns the size of the edge selector. +-------------------------------------------------------------- + + + igraph_error_t igraph_es_size(const igraph_t *graph, const igraph_es_t *es, + igraph_int_t *result); + + The size of the edge selector is the number of edges it will yield +when it is iterated over. + + *Arguments:. * + +‘graph’: + The graph over which we will iterate. + +‘es’: + The edge selector. + +‘result’: + The result will be returned here. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_es_type --- Returns the type of the edge selector_, Prev: igraph_es_size --- Returns the size of the edge selector_, Up: Generic edge selector operations + +9.8.6 igraph_es_type -- Returns the type of the edge selector. +-------------------------------------------------------------- + + + igraph_es_type_t igraph_es_type(const igraph_es_t *es); + + +File: igraph-docs.info, Node: Edge iterators, Prev: Generic edge selector operations, Up: Vertex and edge selectors and sequences; iterators + +9.9 Edge iterators +================== + +* Menu: + +* igraph_eit_create -- Creates an edge iterator from an edge selector.: igraph_eit_create --- Creates an edge iterator from an edge selector_. +* igraph_eit_destroy -- Destroys an edge iterator.: igraph_eit_destroy --- Destroys an edge iterator_. +* Stepping over the edges:: +* IGRAPH_EIT_NEXT -- Next edge.: IGRAPH_EIT_NEXT --- Next edge_. +* IGRAPH_EIT_END --- Are we at the end?:: +* IGRAPH_EIT_SIZE -- Number of edges in the iterator.: IGRAPH_EIT_SIZE --- Number of edges in the iterator_. +* IGRAPH_EIT_RESET -- Reset an edge iterator.: IGRAPH_EIT_RESET --- Reset an edge iterator_. +* IGRAPH_EIT_GET -- Query an edge iterator.: IGRAPH_EIT_GET --- Query an edge iterator_. + + +File: igraph-docs.info, Node: igraph_eit_create --- Creates an edge iterator from an edge selector_, Next: igraph_eit_destroy --- Destroys an edge iterator_, Up: Edge iterators + +9.9.1 igraph_eit_create -- Creates an edge iterator from an edge selector. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_eit_create(const igraph_t *graph, igraph_es_t es, igraph_eit_t *eit); + + This function creates an edge iterator based on an edge selector and +a graph. + + The same edge selector can be used to create many edge iterators, +also for different graphs. + + *Arguments:. * + +‘graph’: + An ‘igraph_t’ object for which the edge selector will be + instantiated. + +‘es’: + The edge selector to instantiate. + +‘eit’: + Pointer to an uninitialized edge iterator. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_eit_destroy()’ (*note igraph_eit_destroy --- Destroys an + edge iterator_::) + + Time complexity: depends on the type of the edge selector. For edge +selectors created by ‘igraph_es_all()’ (*note igraph_es_all --- Edge +set; all edges_::), ‘igraph_es_none()’ (*note igraph_es_none --- Empty +edge selector_::), ‘igraph_es_1()’ (*note igraph_es_1 --- Edge selector +containing a single edge_::), ‘igraph_es_vector()’ (*note +igraph_es_vector --- Handle a vector as an edge selector_::), +‘igraph_es_range()’ (*note igraph_es_range --- Edge selector; a sequence +of edge IDs_::) it is O(1). For ‘igraph_es_incident()’ (*note +igraph_es_incident --- Edges incident on a given vertex_::) it is O(d) +where d is the number of incident edges of the vertex. + + +File: igraph-docs.info, Node: igraph_eit_destroy --- Destroys an edge iterator_, Next: Stepping over the edges, Prev: igraph_eit_create --- Creates an edge iterator from an edge selector_, Up: Edge iterators + +9.9.2 igraph_eit_destroy -- Destroys an edge iterator. +------------------------------------------------------ + + + void igraph_eit_destroy(const igraph_eit_t *eit); + + *Arguments:. * + +‘eit’: + Pointer to an edge iterator to destroy. + + *See also:. * + +‘’ + ‘igraph_eit_create()’ (*note igraph_eit_create --- Creates an edge + iterator from an edge selector_::) + + Time complexity: operating system dependent, usually O(1). + + +File: igraph-docs.info, Node: Stepping over the edges, Next: IGRAPH_EIT_NEXT --- Next edge_, Prev: igraph_eit_destroy --- Destroys an edge iterator_, Up: Edge iterators + +9.9.3 Stepping over the edges +----------------------------- + +Just like for vertex iterators, macros are provided for stepping over a +sequence of edges: ‘IGRAPH_EIT_NEXT()’ (*note IGRAPH_EIT_NEXT --- Next +edge_::) goes to the next edge, ‘IGRAPH_EIT_END()’ (*note IGRAPH_EIT_END +--- Are we at the end?::) checks whether there are more edges to visit, +‘IGRAPH_EIT_SIZE()’ (*note IGRAPH_EIT_SIZE --- Number of edges in the +iterator_::) gives the number of edges in the edge sequence, +‘IGRAPH_EIT_RESET()’ (*note IGRAPH_EIT_RESET --- Reset an edge +iterator_::) resets the iterator to the first edge and +‘IGRAPH_EIT_GET()’ (*note IGRAPH_EIT_GET --- Query an edge iterator_::) +returns the id of the current edge. + + +File: igraph-docs.info, Node: IGRAPH_EIT_NEXT --- Next edge_, Next: IGRAPH_EIT_END --- Are we at the end?, Prev: Stepping over the edges, Up: Edge iterators + +9.9.4 IGRAPH_EIT_NEXT -- Next edge. +----------------------------------- + + + #define IGRAPH_EIT_NEXT(eit) + + Steps the iterator to the next edge. Call this function only if +‘IGRAPH_EIT_END()’ (*note IGRAPH_EIT_END --- Are we at the end?::) +returns false. + + *Arguments:. * + +‘eit’: + The edge iterator to step. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_EIT_END --- Are we at the end?, Next: IGRAPH_EIT_SIZE --- Number of edges in the iterator_, Prev: IGRAPH_EIT_NEXT --- Next edge_, Up: Edge iterators + +9.9.5 IGRAPH_EIT_END -- Are we at the end? +------------------------------------------ + + + #define IGRAPH_EIT_END(eit) + + Checks whether there are more edges to step to. + + *Arguments:. * + +‘wit’: + The edge iterator to check. + + *Returns:. * + +‘’ + Logical value, if true there are no more edges to step to. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_EIT_SIZE --- Number of edges in the iterator_, Next: IGRAPH_EIT_RESET --- Reset an edge iterator_, Prev: IGRAPH_EIT_END --- Are we at the end?, Up: Edge iterators + +9.9.6 IGRAPH_EIT_SIZE -- Number of edges in the iterator. +--------------------------------------------------------- + + + #define IGRAPH_EIT_SIZE(eit) + + Gives the number of edges in an edge iterator. + + *Arguments:. * + +‘eit’: + The edge iterator. + + *Returns:. * + +‘’ + The number of edges. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_EIT_RESET --- Reset an edge iterator_, Next: IGRAPH_EIT_GET --- Query an edge iterator_, Prev: IGRAPH_EIT_SIZE --- Number of edges in the iterator_, Up: Edge iterators + +9.9.7 IGRAPH_EIT_RESET -- Reset an edge iterator. +------------------------------------------------- + + + #define IGRAPH_EIT_RESET(eit) + + Resets an edge iterator. After calling this macro the iterator will +point to the first edge. + + *Arguments:. * + +‘eit’: + The edge iterator. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: IGRAPH_EIT_GET --- Query an edge iterator_, Prev: IGRAPH_EIT_RESET --- Reset an edge iterator_, Up: Edge iterators + +9.9.8 IGRAPH_EIT_GET -- Query an edge iterator. +----------------------------------------------- + + + #define IGRAPH_EIT_GET(eit) + + Gives the edge ID of the current edge pointed to by an iterator. + + *Arguments:. * + +‘eit’: + The edge iterator. + + *Returns:. * + +‘’ + The id of the current edge. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Graph; vertex and edge attributes, Next: Deterministic graph generators, Prev: Vertex and edge selectors and sequences; iterators, Up: Top + +10 Graph, vertex and edge attributes +************************************ + +Attributes are numbers, boolean values or strings associated with the +vertices or edges of a graph, or with the graph itself. E.g. you may +label vertices with symbolic names or attach numeric weights to the +edges of a graph. In addition to these three basic types, a custom +object type is supported as well. + + igraph attributes are designed to be flexible and extensible. In +igraph attributes are implemented via an interface abstraction: any type +implementing the functions in the interface can be used for storing +vertex, edge and graph attributes. This means that different attribute +implementations can be used together with igraph. This is reasonable: +if igraph is used from Python attributes can be of any Python type, from +R all R types are allowed. There is also an experimental attribute +implementation to be used when programming in C, but by default it is +currently turned off. + + First we briefly look over how attribute handlers can be implemented. +This is not something a user does every day. It is rather typically the +job of the high level interface writers. (But it is possible to write +an interface without implementing attributes.) Then we show the +experimental C attribute handler. + +* Menu: + +* The attribute handler interface:: +* Attribute records:: +* Handling attribute combination lists:: +* Accessing attributes from C:: + + +File: igraph-docs.info, Node: The attribute handler interface, Next: Attribute records, Up: Graph; vertex and edge attributes + +10.1 The attribute handler interface +==================================== + +It is possible to attach an attribute handling interface to ‘igraph’. +This is simply a table of functions, of type ‘igraph_attribute_table_t’ +(*note igraph_attribute_table_t --- Table of functions to perform +operations on attributes_::). These functions are invoked to notify the +attribute handling code about the structural changes in a graph. See +the documentation of this type for details. + + By default there is no attribute interface attached to ‘igraph’. To +attach one, call ‘igraph_set_attribute_table’ (*note +igraph_set_attribute_table --- Attach an attribute table_::) with your +new table. This is normally done on program startup, and is kept +untouched for the program's lifetime. It must be done before any graph +object is created, as graphs created with a given attribute handler +cannot be manipulated while a different attribute handler is active. + +* Menu: + +* igraph_attribute_table_t -- Table of functions to perform operations on attributes.: igraph_attribute_table_t --- Table of functions to perform operations on attributes_. +* igraph_set_attribute_table -- Attach an attribute table.: igraph_set_attribute_table --- Attach an attribute table_. +* igraph_attribute_type_t -- The possible types of the attributes.: igraph_attribute_type_t --- The possible types of the attributes_. +* igraph_attribute_elemtype_t -- Types of objects to which attributes can be attached.: igraph_attribute_elemtype_t --- Types of objects to which attributes can be attached_. + + +File: igraph-docs.info, Node: igraph_attribute_table_t --- Table of functions to perform operations on attributes_, Next: igraph_set_attribute_table --- Attach an attribute table_, Up: The attribute handler interface + +10.1.1 igraph_attribute_table_t -- Table of functions to perform operations on attributes. +------------------------------------------------------------------------------------------ + + + typedef struct igraph_attribute_table_t { + igraph_error_t (*init)(igraph_t *graph, const igraph_attribute_record_list_t *attr); + void (*destroy)(igraph_t *graph); + igraph_error_t (*copy)(igraph_t *to, const igraph_t *from, igraph_bool_t ga, + igraph_bool_t va, igraph_bool_t ea); + igraph_error_t (*add_vertices)( + igraph_t *graph, igraph_int_t nv, + const igraph_attribute_record_list_t *attr + ); + igraph_error_t (*permute_vertices)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx); + igraph_error_t (*combine_vertices)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); + igraph_error_t (*add_edges)( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr + ); + igraph_error_t (*permute_edges)(const igraph_t *graph, + igraph_t *newgraph, const igraph_vector_int_t *idx); + igraph_error_t (*combine_edges)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); + igraph_error_t (*get_info)(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, igraph_vector_int_t *etypes); + igraph_bool_t (*has_attr)(const igraph_t *graph, igraph_attribute_elemtype_t type, + const char *name); + igraph_error_t (*get_type)(const igraph_t *graph, igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, const char *name); + igraph_error_t (*get_numeric_graph_attr)(const igraph_t *graph, const char *name, + igraph_vector_t *value); + igraph_error_t (*get_string_graph_attr)(const igraph_t *graph, const char *name, + igraph_strvector_t *value); + igraph_error_t (*get_bool_graph_attr)(const igraph_t *igraph, const char *name, + igraph_vector_bool_t *value); + igraph_error_t (*get_numeric_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_vector_t *value); + igraph_error_t (*get_string_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_strvector_t *value); + igraph_error_t (*get_bool_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value); + igraph_error_t (*get_numeric_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_vector_t *value); + igraph_error_t (*get_string_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_strvector_t *value); + igraph_error_t (*get_bool_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_vector_bool_t *value); + } igraph_attribute_table_t; + + This type collects the functions defining an attribute handler. It +has the following members: + + *Values:. * + +‘init’: + This function is called whenever a new graph object is created, + right after it is created but before any vertices or edges are + added. It is supposed to set the ‘attr’ member of the ‘igraph_t’ + object, which is guaranteed to be set to a null pointer before this + function is called. It is expected to set the ‘attr’ member to a + non-null value _or_ return an error code. Leaving the ‘attr’ + member at a null value while returning success is invalid and will + trigger an error in the C core of igraph itself. + +‘destroy’: + This function is called whenever the graph object is destroyed, + right before freeing the allocated memory. It is supposed to do + any cleanup operations that are need to dispose of the ‘attr’ + member of the ‘igraph_t’ object properly. The caller will set the + ‘attr’ member to a null pointer after this function returns. + +‘copy’: + This function is called when the C core wants to populate the + attributes of a graph from another graph. The structure of the + target graph is already initialized by the time this function is + called, and the ‘attr’ member of the graph is set to a null + pointer. The function is supposed to populate the ‘attr’ member of + the target ‘igraph_t’ object to a non-null value _or_ return an + error code. Leaving the ‘attr’ member at a null value while + returning success is invalid and will trigger an error in the C + core of igraph itself. + +‘add_vertices’: + Called when vertices are added to a graph, after the base data + structure was modified. The number of vertices that were added is + supplied as an argument. The function is supposed to set up + default values for each vertex attribute that is currently + registered on the graph, for all the newly added vertices. + Expected to return an error code. + +‘permute_vertices’: + Called when a new graph is created based on an existing one such + that there is a mapping from the vertices of the new graph back to + the vertices of the old graph (e.g. if vertices are removed from a + graph). The supplied index vector defines which old vertex a new + vertex corresponds to. Its length is the same as the number of + vertices in the new graph, and for each new vertex it provides the + ID of the corresponding vertex in the old graph. The function is + supposed to set up the values of the vertex attributes of the new + graph based on the attributes of the old graph and the provided + index vector. Note that the old and the new graph _may_ be the + same, in which case it is the responsibility of the function to + ensure that the operation can safely be performed in-place. If the + two graph instances are _not_ the same, implementors may safely + assume that the new graph has no vertex attributes yet (but it may + already have graph or edge attributes by the time this function is + called). + +‘combine_vertices’: + This function is called when the creation of a new graph involves a + merge (contraction, etc.) of vertices from another graph. The + function is called after the new graph was created. An argument + specifies how several vertices from the old graph map to a single + vertex in the new graph. It is guaranteed that the old and the new + graph instances are different when this callback is called. + Implementors may safely assume that the new graph has no vertex + attributes yet (but it may already have graph or edge attributes by + the time this function is called). + +‘add_edges’: + Called when new edges are added to a graph, after the base data + structure was modified. A vector containing the endpoints of the + new edges are supplied as an argument. The function is supposed to + set up default values for each edge attribute that is currently + registered on the graph, for all the newly added edges. Expected + to return an error code. + +‘permute_edges’: + Called when a new graph is created based on an existing one such + that some of the edges in the new graph should copy the attributes + of some edges from the old graph (this also includes the deletion + of edges). The supplied index vector defines which old edge a new + edge corresponds to. Its length is the same as the number of edges + in the new graph, and for each edge it provides the ID of the + corresponding edge in the old graph. The function is supposed to + set up the values of the edge attributes of the new graph based on + the attributes of the old graph and the provided index vector. + Note that the old and the new graph _may_ be the same, in which + case it is the responsibility of the function to ensure that the + operation can safely be performed in-place. If the two graph + instances are _not_ the same, implementors may safely assume that + the new graph has no edge attributes yet (but it may already have + graph or vertex attributes by the time this function is called). + +‘combine_edges’: + This function is called when the creation of a new graph involves a + merge (contraction, etc.) of edges from another graph. The + function is after the new graph was created. An argument specifies + how several edges from the old graph map to a single edge in the + new graph. It is guaranteed that the old and the new graph + instances are different when this callback is called. Implementors + may safely assume that the new graph has no edge attributes yet + (but it may already have graph or vertex attributes by the time + this function is called). + +‘get_info’: + Query the attributes of a graph, the names and types should be + returned. + +‘has_attr’: + Check whether a graph has the named graph/vertex/edge attribute. + +‘get_type’: + Query the type of a graph/vertex/edge attribute. + +‘get_numeric_graph_attr’: + Query a numeric graph attribute. The value should be appended to + the provided ‘value’ vector. No assumptions should be made about + the initial contents of the ‘value’ vector and it is not guaranteed + to be empty. + +‘get_string_graph_attr’: + Query a string graph attribute. The value should be appended to + the provided ‘value’ vector. No assumptions should be made about + the initial contents of the ‘value’ vector and it is not guaranteed + to be empty. + +‘get_bool_graph_attr’: + Query a boolean graph attribute. The value should be appended to + the provided ‘value’ vector. No assumptions should be made about + the initial contents of the ‘value’ vector and it is not guaranteed + to be empty. + +‘get_numeric_vertex_attr’: + Query a numeric vertex attribute, for the vertices included in + ‘vs’. The attribute values should be appended to the provided + ‘value’ vector. No assumptions should be made about the initial + contents of the ‘value’ vector and it is not guaranteed to be + empty. + +‘get_string_vertex_attr’: + Query a string vertex attribute, for the vertices included in ‘vs’. + The attribute values should be appended to the provided ‘value’ + vector. No assumptions should be made about the initial contents + of the ‘value’ vector and it is not guaranteed to be empty. + +‘get_bool_vertex_attr’: + Query a boolean vertex attribute, for the vertices included in + ‘vs’. The attribute values should be appended to the provided + ‘value’ vector. No assumptions should be made about the initial + contents of the ‘value’ vector and it is not guaranteed to be + empty. + +‘get_numeric_edge_attr’: + Query a numeric edge attribute, for the edges included in ‘es’. + The attribute values should be appended to the provided ‘value’ + vector. No assumptions should be made about the initial contents + of the ‘value’ vector and it is not guaranteed to be empty. + +‘get_string_edge_attr’: + Query a string edge attribute, for the the edges included in ‘es’. + The attribute values should be appended to the provided ‘value’ + vector. No assumptions should be made about the initial contents + of the ‘value’ vector and it is not guaranteed to be empty. + +‘get_bool_edge_attr’: + Query a boolean edge attribute, for the the edges included in ‘es’. + The attribute values should be appended to the provided ‘value’ + vector. No assumptions should be made about the initial contents + of the ‘value’ vector and it is not guaranteed to be empty. + + +File: igraph-docs.info, Node: igraph_set_attribute_table --- Attach an attribute table_, Next: igraph_attribute_type_t --- The possible types of the attributes_, Prev: igraph_attribute_table_t --- Table of functions to perform operations on attributes_, Up: The attribute handler interface + +10.1.2 igraph_set_attribute_table -- Attach an attribute table. +--------------------------------------------------------------- + + + igraph_attribute_table_t * + igraph_set_attribute_table(const igraph_attribute_table_t * table); + + This function attaches attribute handling code to the igraph library. +Note that the attribute handler table is _not_ thread-local even if +igraph is compiled in thread-local mode. In the vast majority of cases, +this is not a significant restriction. + + Attribute handlers are normally attached on program startup, and are +left active for the program's lifetime. This is because a graph object +created with a given attribute handler must not be manipulated while a +different attribute handler is active. + + *Arguments:. * + +‘table’: + Pointer to an ‘igraph_attribute_table_t’ (*note + igraph_attribute_table_t --- Table of functions to perform + operations on attributes_::) object containing the functions for + attribute manipulation. Supply ‘NULL’ here if you don't want + attributes. + + *Returns:. * + +‘’ + Pointer to the old attribute handling table. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_attribute_type_t --- The possible types of the attributes_, Next: igraph_attribute_elemtype_t --- Types of objects to which attributes can be attached_, Prev: igraph_set_attribute_table --- Attach an attribute table_, Up: The attribute handler interface + +10.1.3 igraph_attribute_type_t -- The possible types of the attributes. +----------------------------------------------------------------------- + + + typedef enum { + IGRAPH_ATTRIBUTE_UNSPECIFIED = 0, + IGRAPH_ATTRIBUTE_NUMERIC = 1, + IGRAPH_ATTRIBUTE_BOOLEAN = 2, + IGRAPH_ATTRIBUTE_STRING = 3, + IGRAPH_ATTRIBUTE_OBJECT = 127 + } igraph_attribute_type_t; + + Values of this enum are used by the attribute interface to +communicate the type of an attribute to igraph's C core. When igraph is +integrated in a high-level language, the attribute type reported by the +interface may not necessarily have to match the exact data type in the +high-level language as long as the attribute interface can provide a +conversion from the native high-level attribute value to one of the data +types listed here. When the high-level data type is complex and has no +suitable conversion to one of the atomic igraph attribute types +(numeric, string or Boolean), the attribute interface should report the +attribute as having an "object" type, which is ignored by the C core. +See also ‘igraph_attribute_table_t’ (*note igraph_attribute_table_t --- +Table of functions to perform operations on attributes_::). + + *Values:. * + +‘IGRAPH_ATTRIBUTE_UNSPECIFIED’: + Currently used internally as a "null value" or "placeholder value" + in some algorithms. Attribute records with this type must not be + passed to igraph functions. + +‘IGRAPH_ATTRIBUTE_NUMERIC’: + Numeric attribute. + +‘IGRAPH_ATTRIBUTE_BOOLEAN’: + Logical values, true or false. + +‘IGRAPH_ATTRIBUTE_STRING’: + String attribute. + +‘IGRAPH_ATTRIBUTE_OBJECT’: + Custom attribute type, to be used for special data types by client + applications. The R and Python interfaces use this for attributes + that hold R or Python objects. Usually ignored by igraph + functions. + + +File: igraph-docs.info, Node: igraph_attribute_elemtype_t --- Types of objects to which attributes can be attached_, Prev: igraph_attribute_type_t --- The possible types of the attributes_, Up: The attribute handler interface + +10.1.4 igraph_attribute_elemtype_t -- Types of objects to which attributes can be attached. +------------------------------------------------------------------------------------------- + + + typedef enum { + IGRAPH_ATTRIBUTE_GRAPH = 0, + IGRAPH_ATTRIBUTE_VERTEX, + IGRAPH_ATTRIBUTE_EDGE + } igraph_attribute_elemtype_t; + + *Values:. * + +‘IGRAPH_ATTRIBUTE_GRAPH’: + Denotes that an attribute belongs to the entire graph. + +‘IGRAPH_ATTRIBUTE_VERTEX’: + Denotes that an attribute belongs to the vertices of a graph. + +‘IGRAPH_ATTRIBUTE_EDGE’: + Denotes that an attribute belongs to the edges of a graph. + + +File: igraph-docs.info, Node: Attribute records, Next: Handling attribute combination lists, Prev: The attribute handler interface, Up: Graph; vertex and edge attributes + +10.2 Attribute records +====================== + +Functions in the attribute handler interface may refer to _"attribute_ +records" or _"attribute_ record lists". An attribute record is simply a +triplet consisting of an attribute name, an attribute type and a vector +containing the values of the attribute. Attribute record lists are +typed containers that contain a sequence of attribute records. +Attribute record lists own the attribute records that they contain, and +similarly, attribute records own the vectors contained in them. +Destroying an attribute record destroys the vector of values inside it, +and destroying an attribute record list destroys all attribute records +in the list. + +* Menu: + +* igraph_attribute_record_t -- An attribute record holding the name, type and values of an attribute.: igraph_attribute_record_t --- An attribute record holding the name; type and values of an attribute_. +* igraph_attribute_record_init -- Initializes an attribute record with a given name and type.: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_. +* igraph_attribute_record_init_copy -- Initializes an attribute record by copying another record.: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_. +* igraph_attribute_record_size -- Returns the size of the value vector in an attribute record.: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_. +* igraph_attribute_record_resize -- Resizes the value vector in an attribute record.: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_. +* igraph_attribute_record_set_name -- Sets the attribute name in an attribute record.: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_. +* igraph_attribute_record_set_type -- Sets the type of an attribute record.: igraph_attribute_record_set_type --- Sets the type of an attribute record_. +* igraph_attribute_record_set_default_numeric -- Sets the default value of the attribute to the given number.: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_. +* igraph_attribute_record_set_default_string -- Sets the default value of the attribute to the given string.: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_. +* igraph_attribute_record_set_default_boolean -- Sets the default value of the attribute to the given logical value.: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_. +* igraph_attribute_record_destroy -- Destroys an attribute record.: igraph_attribute_record_destroy --- Destroys an attribute record_. + + +File: igraph-docs.info, Node: igraph_attribute_record_t --- An attribute record holding the name; type and values of an attribute_, Next: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_, Up: Attribute records + +10.2.1 igraph_attribute_record_t -- An attribute record holding the name, type and values of an attribute. +---------------------------------------------------------------------------------------------------------- + + + typedef struct igraph_attribute_record_t { + char *name; + + This composite data type is used in the attribute interface to +specify a name-type-value triplet where the name is the name of a graph, +vertex or edge attribute, the type is the corresponding igraph type of +the attribute and the value is a _vector_ of attribute values. Note +that for graph attributes we use a vector of length 1. The type of the +vector depends on the attribute type: it is ‘igraph_vector_t’ (*note +About igraph_vector_t objects::) for numeric attributes, +‘igraph_strvector_t’ for string attributes and ‘igraph_vector_bool_t’ +for Boolean attributes. + + The record also stores default values for the attribute. The default +values are used when the value vector of the record is resized with +‘igraph_attribute_record_resize()’ (*note igraph_attribute_record_resize +--- Resizes the value vector in an attribute record_::). It is +important that the record stores _one_ default value only, corresponding +to the type of the attribute record. The default value is _cleared_ +when the type of the record is changed. + + +File: igraph-docs.info, Node: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_, Next: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_, Prev: igraph_attribute_record_t --- An attribute record holding the name; type and values of an attribute_, Up: Attribute records + +10.2.2 igraph_attribute_record_init -- Initializes an attribute record with a given name and type. +-------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_record_init( + igraph_attribute_record_t *attr, const char *name, igraph_attribute_type_t type + ); + + *Arguments:. * + +‘attr’: + the attribute record to initialize + +‘name’: + name of the attribute + +‘type’: + type of the attribute + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_, Next: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_, Prev: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_, Up: Attribute records + +10.2.3 igraph_attribute_record_init_copy -- Initializes an attribute record by copying another record. +------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_attribute_record_init_copy( + igraph_attribute_record_t *to, const igraph_attribute_record_t *from + ); + + Copies made by this function are deep copies: a full copy of the +value vector contained in the record is placed in the new record so they +become independent of each other. + + *Arguments:. * + +‘to’: + the attribute record to initialize + +‘from’: + the attribute record to copy data from + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + Time complexity: operating system dependent, usually O(n), where n is +the size of the value vector in the attribute record. + + +File: igraph-docs.info, Node: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_, Next: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_, Prev: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_, Up: Attribute records + +10.2.4 igraph_attribute_record_size -- Returns the size of the value vector in an attribute record. +--------------------------------------------------------------------------------------------------- + + + igraph_int_t igraph_attribute_record_size(const igraph_attribute_record_t *attr); + + *Arguments:. * + +‘attr’: + the attribute record to query + + *Returns:. * + +‘’ + the number of elements in the value vector of the attribute record + + +File: igraph-docs.info, Node: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_, Next: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_, Prev: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_, Up: Attribute records + +10.2.5 igraph_attribute_record_resize -- Resizes the value vector in an attribute record. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_record_resize( + igraph_attribute_record_t *attr, igraph_int_t new_size + ); + + When the value vector is shorter than the desired length, it will be +expanded with ‘IGRAPH_NAN’ for numeric vectors, ‘false’ for Boolean +vectors and empty strings for string vectors. + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘new_size’: + the new size of the value vector + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + ‘IGRAPH_EINVAL’ if the type of the attribute record is not + specified yet. + + +File: igraph-docs.info, Node: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_, Next: igraph_attribute_record_set_type --- Sets the type of an attribute record_, Prev: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_, Up: Attribute records + +10.2.6 igraph_attribute_record_set_name -- Sets the attribute name in an attribute record. +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_attribute_record_set_name( + igraph_attribute_record_t *attr, const char *name + ); + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘name’: + the new name + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + +File: igraph-docs.info, Node: igraph_attribute_record_set_type --- Sets the type of an attribute record_, Next: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_, Prev: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_, Up: Attribute records + +10.2.7 igraph_attribute_record_set_type -- Sets the type of an attribute record. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_record_set_type( + igraph_attribute_record_t *attr, igraph_attribute_type_t type + ); + + When the new type being set is different from the old type, any +values already stored in the attribute record will be destroyed and a +new, empty attribute value vector will be allocated. When the new type +is the same as the old type, this function is a no-op. + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘type’: + the new type + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory. + + +File: igraph-docs.info, Node: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_, Next: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_, Prev: igraph_attribute_record_set_type --- Sets the type of an attribute record_, Up: Attribute records + +10.2.8 igraph_attribute_record_set_default_numeric -- Sets the default value of the attribute to the given number. +------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_attribute_record_set_default_numeric( + igraph_attribute_record_t *attr, igraph_real_t value + ); + + This function must be called for numeric attribute records only. +When not specified, the default value of numeric attributes is NaN. + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘value’: + the new default value + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’ if the attribute record has a + non-numeric type + + +File: igraph-docs.info, Node: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_, Next: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_, Prev: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_, Up: Attribute records + +10.2.9 igraph_attribute_record_set_default_string -- Sets the default value of the attribute to the given string. +----------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_record_set_default_string( + igraph_attribute_record_t *attr, const char* value + ); + + This function must be called for string attribute records only. When +not specified, the default value of string attributes is an empty +string. + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘value’: + the new default value. ‘NULL’ means an empty string. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory + ‘IGRAPH_EINVAL’ if the attribute record is not of string type + + +File: igraph-docs.info, Node: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_, Next: igraph_attribute_record_destroy --- Destroys an attribute record_, Prev: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_, Up: Attribute records + +10.2.10 igraph_attribute_record_set_default_boolean -- Sets the default value of the attribute to the given logical value. +-------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_record_set_default_boolean( + igraph_attribute_record_t *attr, igraph_bool_t value + ); + + This function must be called for Boolean attribute records only. +When not specified, the default value of Boolean attributes is ‘false’. + + *Arguments:. * + +‘attr’: + the attribute record to update + +‘value’: + the new default value + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’ if the attribute record is not of + Boolean type + + +File: igraph-docs.info, Node: igraph_attribute_record_destroy --- Destroys an attribute record_, Prev: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_, Up: Attribute records + +10.2.11 igraph_attribute_record_destroy -- Destroys an attribute record. +------------------------------------------------------------------------ + + + void igraph_attribute_record_destroy(igraph_attribute_record_t *attr); + + *Arguments:. * + +‘attr’: + the previously initialized attribute record to destroy. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: Handling attribute combination lists, Next: Accessing attributes from C, Prev: Attribute records, Up: Graph; vertex and edge attributes + +10.3 Handling attribute combination lists +========================================= + +Several graph operations may collapse multiple vertices or edges into a +single one. Attribute combination lists are used to indicate to the +attribute handler how to combine the attributes of the original vertices +or edges and how to derive the final attribute value that is to be +assigned to the collapsed vertex or edge. For example, +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::) removes loops and combines multiple +edges into a single one; in case of a graph with an edge attribute named +‘weight’ the attribute combination list can tell the attribute handler +whether the weight of a collapsed edge should be the sum, the mean or +some other function of the weights of the original edges that were +collapsed into one. + + One attribute combination list may contain several attribute +combination records, one for each vertex or edge attribute that is to be +handled during the operation. + +* Menu: + +* igraph_attribute_combination_init -- Initialize attribute combination list.: igraph_attribute_combination_init --- Initialize attribute combination list_. +* igraph_attribute_combination_add -- Add combination record to attribute combination list.: igraph_attribute_combination_add --- Add combination record to attribute combination list_. +* igraph_attribute_combination_remove -- Remove a record from an attribute combination list.: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_. +* igraph_attribute_combination_destroy -- Destroy attribute combination list.: igraph_attribute_combination_destroy --- Destroy attribute combination list_. +* igraph_attribute_combination_type_t -- The possible types of attribute combinations.: igraph_attribute_combination_type_t --- The possible types of attribute combinations_. +* igraph_attribute_combination -- Initialize attribute combination list and add records.: igraph_attribute_combination --- Initialize attribute combination list and add records_. + + +File: igraph-docs.info, Node: igraph_attribute_combination_init --- Initialize attribute combination list_, Next: igraph_attribute_combination_add --- Add combination record to attribute combination list_, Up: Handling attribute combination lists + +10.3.1 igraph_attribute_combination_init -- Initialize attribute combination list. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_combination_init(igraph_attribute_combination_t *comb); + + *Arguments:. * + +‘comb’: + The uninitialized attribute combination list. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) + + +File: igraph-docs.info, Node: igraph_attribute_combination_add --- Add combination record to attribute combination list_, Next: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_, Prev: igraph_attribute_combination_init --- Initialize attribute combination list_, Up: Handling attribute combination lists + +10.3.2 igraph_attribute_combination_add -- Add combination record to attribute combination list. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_attribute_combination_add(igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t type, + igraph_function_pointer_t func); + + *Arguments:. * + +‘comb’: + The attribute combination list. + +‘name’: + The name of the attribute. If the name already exists the + attribute combination record will be replaced. Use NULL to add a + default combination record for all atributes not in the list. + +‘type’: + The type of the attribute combination. See + ‘igraph_attribute_combination_type_t’ (*note + igraph_attribute_combination_type_t --- The possible types of + attribute combinations_::) for the options. + +‘func’: + Function to be used if ‘type’ is + ‘IGRAPH_ATTRIBUTE_COMBINE_FUNCTION’. This function is called by + the concrete attribute handler attached to igraph, and its calling + signature depends completely on the attribute handler. For + instance, if you are using attributes from C and you have attached + the C attribute handler, you need to follow the documentation of + the C attribute handler (*note Custom attribute combination + functions::) for more details. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), where n is the number of current attribute +combinations. + + +File: igraph-docs.info, Node: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_, Next: igraph_attribute_combination_destroy --- Destroy attribute combination list_, Prev: igraph_attribute_combination_add --- Add combination record to attribute combination list_, Up: Handling attribute combination lists + +10.3.3 igraph_attribute_combination_remove -- Remove a record from an attribute combination list. +------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_combination_remove(igraph_attribute_combination_t *comb, + const char *name); + + *Arguments:. * + +‘comb’: + The attribute combination list. + +‘name’: + The attribute name of the attribute combination record to remove. + It will be ignored if the named attribute does not exist. It can + be NULL to remove the default combination record. + + *Returns:. * + +‘’ + Error code. This currently always returns IGRAPH_SUCCESS. + + Time complexity: O(n), where n is the number of records in the +attribute combination list. + + +File: igraph-docs.info, Node: igraph_attribute_combination_destroy --- Destroy attribute combination list_, Next: igraph_attribute_combination_type_t --- The possible types of attribute combinations_, Prev: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_, Up: Handling attribute combination lists + +10.3.4 igraph_attribute_combination_destroy -- Destroy attribute combination list. +---------------------------------------------------------------------------------- + + + void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb); + + *Arguments:. * + +‘comb’: + The attribute combination list. + + Time complexity: O(n), where n is the number of records in the +attribute combination list. + + +File: igraph-docs.info, Node: igraph_attribute_combination_type_t --- The possible types of attribute combinations_, Next: igraph_attribute_combination --- Initialize attribute combination list and add records_, Prev: igraph_attribute_combination_destroy --- Destroy attribute combination list_, Up: Handling attribute combination lists + +10.3.5 igraph_attribute_combination_type_t -- The possible types of attribute combinations. +------------------------------------------------------------------------------------------- + + + typedef enum { + IGRAPH_ATTRIBUTE_COMBINE_IGNORE = 0, + IGRAPH_ATTRIBUTE_COMBINE_DEFAULT = 1, + IGRAPH_ATTRIBUTE_COMBINE_FUNCTION = 2, + IGRAPH_ATTRIBUTE_COMBINE_SUM = 3, + IGRAPH_ATTRIBUTE_COMBINE_PROD = 4, + IGRAPH_ATTRIBUTE_COMBINE_MIN = 5, + IGRAPH_ATTRIBUTE_COMBINE_MAX = 6, + IGRAPH_ATTRIBUTE_COMBINE_RANDOM = 7, + IGRAPH_ATTRIBUTE_COMBINE_FIRST = 8, + IGRAPH_ATTRIBUTE_COMBINE_LAST = 9, + IGRAPH_ATTRIBUTE_COMBINE_MEAN = 10, + IGRAPH_ATTRIBUTE_COMBINE_MEDIAN = 11, + IGRAPH_ATTRIBUTE_COMBINE_CONCAT = 12 + } igraph_attribute_combination_type_t; + + *Values:. * + +‘IGRAPH_ATTRIBUTE_COMBINE_IGNORE’: + Ignore old attributes, use an empty value. + +‘IGRAPH_ATTRIBUTE_COMBINE_DEFAULT’: + Use the default way to combine attributes (decided by the attribute + handler implementation). + +‘IGRAPH_ATTRIBUTE_COMBINE_FUNCTION’: + Supply your own function to combine attributes. + +‘IGRAPH_ATTRIBUTE_COMBINE_SUM’: + Take the sum of the attributes. + +‘IGRAPH_ATTRIBUTE_COMBINE_PROD’: + Take the product of the attributes. + +‘IGRAPH_ATTRIBUTE_COMBINE_MIN’: + Take the minimum attribute. + +‘IGRAPH_ATTRIBUTE_COMBINE_MAX’: + Take the maximum attribute. + +‘IGRAPH_ATTRIBUTE_COMBINE_RANDOM’: + Take a random attribute. + +‘IGRAPH_ATTRIBUTE_COMBINE_FIRST’: + Take the first attribute. + +‘IGRAPH_ATTRIBUTE_COMBINE_LAST’: + Take the last attribute. + +‘IGRAPH_ATTRIBUTE_COMBINE_MEAN’: + Take the mean of the attributes. + +‘IGRAPH_ATTRIBUTE_COMBINE_MEDIAN’: + Take the median of the attributes. + +‘IGRAPH_ATTRIBUTE_COMBINE_CONCAT’: + Concatenate the attributes. + + +File: igraph-docs.info, Node: igraph_attribute_combination --- Initialize attribute combination list and add records_, Prev: igraph_attribute_combination_type_t --- The possible types of attribute combinations_, Up: Handling attribute combination lists + +10.3.6 igraph_attribute_combination -- Initialize attribute combination list and add records. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_attribute_combination( + igraph_attribute_combination_t *comb, ...); + + *Arguments:. * + +‘comb’: + The uninitialized attribute combination list. + +‘...’: + A list of 'name, type[, func]', where: + +‘name’: + The name of the attribute. If the name already exists the + attribute combination record will be replaced. Use NULL to add a + default combination record for all atributes not in the list. + +‘type’: + The type of the attribute combination. See + ‘igraph_attribute_combination_type_t’ (*note + igraph_attribute_combination_type_t --- The possible types of + attribute combinations_::) for the options. + +‘func’: + Function to be used if ‘type’ is + ‘IGRAPH_ATTRIBUTE_COMBINE_FUNCTION’. The list is closed by setting + the name to ‘IGRAPH_NO_MORE_ATTRIBUTES’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n^2), where n is the number attribute combinations +records to add. + + * File examples/simple/igraph_attribute_combination.c* + + +File: igraph-docs.info, Node: Accessing attributes from C, Prev: Handling attribute combination lists, Up: Graph; vertex and edge attributes + +10.4 Accessing attributes from C +================================ + +There is an experimental attribute handler that can be used from C code. +In this section we show how this works. This attribute handler is by +default not attached (the default is no attribute handler), so we first +need to attach it: + + + igraph_set_attribute_table(&igraph_cattribute_table); + + Now the attribute functions are available. Please note that the +attribute handler must be attached before you call any other igraph +functions, otherwise you might end up with graphs without attributes and +an active attribute handler, which might cause unexpected program +behaviour. The rule is that you attach the attribute handler in the +beginning of your ‘main()’ and never touch it again. Detaching the +attribute handler might lead to memory leaks. + + It is not currently possible to have attribute handlers on a +per-graph basis. All graphs in an application must be managed with the +same attribute handler. This also applies to the default case when +there is no attribute handler at all. + + The C attribute handler supports attaching real numbers, boolean +values and character strings as attributes. No vector values are +allowed. For example, vertices have a ‘name’ attribute holding a single +string value for each vertex, but it is not possible to have a ‘coords’ +attribute which is a vector of numbers per vertex. + + The functions documented in this section are specific to the C +attribute handler. Code using these functions will not function when a +different attribute handler is attached. + + * File examples/simple/cattributes.c* + + * File examples/simple/cattributes2.c* + + * File examples/simple/cattributes3.c* + + * File examples/simple/cattributes4.c* + +* Menu: + +* Query attributes:: +* Set attributes:: +* Remove attributes:: +* Custom attribute combination functions:: + + +File: igraph-docs.info, Node: Query attributes, Next: Set attributes, Up: Accessing attributes from C + +10.4.1 Query attributes +----------------------- + +* Menu: + +* igraph_cattribute_list -- List all attributes.: igraph_cattribute_list --- List all attributes_. +* igraph_cattribute_has_attr -- Checks whether a (graph, vertex or edge) attribute exists.: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_. +* igraph_cattribute_GAN -- Query a numeric graph attribute.: igraph_cattribute_GAN --- Query a numeric graph attribute_. +* GAN -- Query a numeric graph attribute.: GAN --- Query a numeric graph attribute_. +* igraph_cattribute_GAB -- Query a boolean graph attribute.: igraph_cattribute_GAB --- Query a boolean graph attribute_. +* GAB -- Query a boolean graph attribute.: GAB --- Query a boolean graph attribute_. +* igraph_cattribute_GAS -- Query a string graph attribute.: igraph_cattribute_GAS --- Query a string graph attribute_. +* GAS -- Query a string graph attribute.: GAS --- Query a string graph attribute_. +* igraph_cattribute_VAN -- Query a numeric vertex attribute.: igraph_cattribute_VAN --- Query a numeric vertex attribute_. +* VAN -- Query a numeric vertex attribute.: VAN --- Query a numeric vertex attribute_. +* igraph_cattribute_VANV -- Query a numeric vertex attribute for many vertices.: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_. +* VANV -- Query a numeric vertex attribute for all vertices.: VANV --- Query a numeric vertex attribute for all vertices_. +* igraph_cattribute_VAB -- Query a boolean vertex attribute.: igraph_cattribute_VAB --- Query a boolean vertex attribute_. +* VAB -- Query a boolean vertex attribute.: VAB --- Query a boolean vertex attribute_. +* igraph_cattribute_VABV -- Query a boolean vertex attribute for many vertices.: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_. +* VABV -- Query a boolean vertex attribute for all vertices.: VABV --- Query a boolean vertex attribute for all vertices_. +* igraph_cattribute_VAS -- Query a string vertex attribute.: igraph_cattribute_VAS --- Query a string vertex attribute_. +* VAS -- Query a string vertex attribute.: VAS --- Query a string vertex attribute_. +* igraph_cattribute_VASV -- Query a string vertex attribute for many vertices.: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_. +* VASV -- Query a string vertex attribute for all vertices.: VASV --- Query a string vertex attribute for all vertices_. +* igraph_cattribute_EAN -- Query a numeric edge attribute.: igraph_cattribute_EAN --- Query a numeric edge attribute_. +* EAN -- Query a numeric edge attribute.: EAN --- Query a numeric edge attribute_. +* igraph_cattribute_EANV -- Query a numeric edge attribute for many edges.: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_. +* EANV -- Query a numeric edge attribute for all edges.: EANV --- Query a numeric edge attribute for all edges_. +* igraph_cattribute_EAB -- Query a boolean edge attribute.: igraph_cattribute_EAB --- Query a boolean edge attribute_. +* EAB -- Query a boolean edge attribute.: EAB --- Query a boolean edge attribute_. +* igraph_cattribute_EABV -- Query a boolean edge attribute for many edges.: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_. +* EABV -- Query a boolean edge attribute for all edges.: EABV --- Query a boolean edge attribute for all edges_. +* igraph_cattribute_EAS -- Query a string edge attribute.: igraph_cattribute_EAS --- Query a string edge attribute_. +* EAS -- Query a string edge attribute.: EAS --- Query a string edge attribute_. +* igraph_cattribute_EASV -- Query a string edge attribute for many edges.: igraph_cattribute_EASV --- Query a string edge attribute for many edges_. +* EASV -- Query a string edge attribute for all edges.: EASV --- Query a string edge attribute for all edges_. + + +File: igraph-docs.info, Node: igraph_cattribute_list --- List all attributes_, Next: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_, Up: Query attributes + +10.4.1.1 igraph_cattribute_list -- List all attributes. +....................................................... + + + igraph_error_t igraph_cattribute_list(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, igraph_vector_int_t *etypes); + + See ‘igraph_attribute_type_t’ (*note igraph_attribute_type_t --- The +possible types of the attributes_::) for the various attribute types. + + *Arguments:. * + +‘graph’: + The input graph. + +‘gnames’: + String vector, the names of the graph attributes. + +‘gtypes’: + Numeric vector, the types of the graph attributes. + +‘vnames’: + String vector, the names of the vertex attributes. + +‘vtypes’: + Numeric vector, the types of the vertex attributes. + +‘enames’: + String vector, the names of the edge attributes. + +‘etypes’: + Numeric vector, the types of the edge attributes. + + *Returns:. * + +‘’ + Error code. + + Naturally, the string vector with the attribute names and the numeric +vector with the attribute types are in the right order, i.e. the first +name corresponds to the first type, etc. Time complexity: O(Ag+Av+Ae), +the number of all attributes. + + +File: igraph-docs.info, Node: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_, Next: igraph_cattribute_GAN --- Query a numeric graph attribute_, Prev: igraph_cattribute_list --- List all attributes_, Up: Query attributes + +10.4.1.2 igraph_cattribute_has_attr -- Checks whether a (graph, vertex or edge) attribute exists. +................................................................................................. + + + igraph_bool_t igraph_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name); + + *Arguments:. * + +‘graph’: + The graph. + +‘type’: + The type of the attribute, ‘IGRAPH_ATTRIBUTE_GRAPH’, + ‘IGRAPH_ATTRIBUTE_VERTEX’ or ‘IGRAPH_ATTRIBUTE_EDGE’. + +‘name’: + Character constant, the name of the attribute. + + *Returns:. * + +‘’ + Boolean value, ‘true’ if the attribute exists, ‘false’ otherwise. + + Time complexity: O(A), the number of (graph, vertex or edge) +attributes, assuming attribute names are not too long. + + +File: igraph-docs.info, Node: igraph_cattribute_GAN --- Query a numeric graph attribute_, Next: GAN --- Query a numeric graph attribute_, Prev: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_, Up: Query attributes + +10.4.1.3 igraph_cattribute_GAN -- Query a numeric graph attribute. +.................................................................. + + + igraph_real_t igraph_cattribute_GAN(const igraph_t *graph, const char *name); + + Returns the value of the given numeric graph attribute. If the +attribute does not exist, a warning is issued and NaN is returned. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute to query. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘GAN’ (*note GAN --- Query a numeric graph attribute_::) for a + simpler interface. + + Time complexity: O(Ag), the number of graph attributes. + + +File: igraph-docs.info, Node: GAN --- Query a numeric graph attribute_, Next: igraph_cattribute_GAB --- Query a boolean graph attribute_, Prev: igraph_cattribute_GAN --- Query a numeric graph attribute_, Up: Query attributes + +10.4.1.4 GAN -- Query a numeric graph attribute. +................................................ + + + #define GAN(graph,n) + + This is shorthand for ‘igraph_cattribute_GAN()’ (*note +igraph_cattribute_GAN --- Query a numeric graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_GAB --- Query a boolean graph attribute_, Next: GAB --- Query a boolean graph attribute_, Prev: GAN --- Query a numeric graph attribute_, Up: Query attributes + +10.4.1.5 igraph_cattribute_GAB -- Query a boolean graph attribute. +.................................................................. + + + igraph_bool_t igraph_cattribute_GAB(const igraph_t *graph, const char *name); + + Returns the value of the given boolean graph attribute. If the +attribute does not exist, a warning is issued and false is returned. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute to query. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘GAB’ (*note GAB --- Query a boolean graph attribute_::) for a + simpler interface. + + Time complexity: O(Ag), the number of graph attributes. + + +File: igraph-docs.info, Node: GAB --- Query a boolean graph attribute_, Next: igraph_cattribute_GAS --- Query a string graph attribute_, Prev: igraph_cattribute_GAB --- Query a boolean graph attribute_, Up: Query attributes + +10.4.1.6 GAB -- Query a boolean graph attribute. +................................................ + + + #define GAB(graph,n) + + This is shorthand for ‘igraph_cattribute_GAB()’ (*note +igraph_cattribute_GAB --- Query a boolean graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_GAS --- Query a string graph attribute_, Next: GAS --- Query a string graph attribute_, Prev: GAB --- Query a boolean graph attribute_, Up: Query attributes + +10.4.1.7 igraph_cattribute_GAS -- Query a string graph attribute. +................................................................. + + + const char *igraph_cattribute_GAS(const igraph_t *graph, const char *name); + + Returns a ‘const’ pointer to the string graph attribute specified in +‘name’. The value must not be modified. If the attribute does not +exist, a warning is issued and an empty string is returned. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute to query. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘GAS’ (*note GAS --- Query a string graph attribute_::) for a + simpler interface. + + Time complexity: O(Ag), the number of graph attributes. + + +File: igraph-docs.info, Node: GAS --- Query a string graph attribute_, Next: igraph_cattribute_VAN --- Query a numeric vertex attribute_, Prev: igraph_cattribute_GAS --- Query a string graph attribute_, Up: Query attributes + +10.4.1.8 GAS -- Query a string graph attribute. +............................................... + + + #define GAS(graph,n) + + This is shorthand for ‘igraph_cattribute_GAS()’ (*note +igraph_cattribute_GAS --- Query a string graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_VAN --- Query a numeric vertex attribute_, Next: VAN --- Query a numeric vertex attribute_, Prev: GAS --- Query a string graph attribute_, Up: Query attributes + +10.4.1.9 igraph_cattribute_VAN -- Query a numeric vertex attribute. +................................................................... + + + igraph_real_t igraph_cattribute_VAN(const igraph_t *graph, const char *name, + igraph_int_t vid); + + If the attribute does not exist, a warning is issued and NaN is +returned. See ‘igraph_cattribute_VANV()’ (*note igraph_cattribute_VANV +--- Query a numeric vertex attribute for many vertices_::) for an +error-checked version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vid’: + The id of the queried vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘VAN’ (*note VAN --- Query a numeric vertex attribute_::) macro for + a simpler interface. + + Time complexity: O(Av), the number of vertex attributes. + + +File: igraph-docs.info, Node: VAN --- Query a numeric vertex attribute_, Next: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_, Prev: igraph_cattribute_VAN --- Query a numeric vertex attribute_, Up: Query attributes + +10.4.1.10 VAN -- Query a numeric vertex attribute. +.................................................. + + + #define VAN(graph,n,v) + + This is shorthand for ‘igraph_cattribute_VAN()’ (*note +igraph_cattribute_VAN --- Query a numeric vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + The id of the vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_, Next: VANV --- Query a numeric vertex attribute for all vertices_, Prev: VAN --- Query a numeric vertex attribute_, Up: Query attributes + +10.4.1.11 igraph_cattribute_VANV -- Query a numeric vertex attribute for many vertices. +....................................................................................... + + + igraph_error_t igraph_cattribute_VANV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vids’: + The vertices to query. + +‘result’: + Pointer to an initialized vector, the result is stored here. It + will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(v), where v is the number of vertices in 'vids'. + + +File: igraph-docs.info, Node: VANV --- Query a numeric vertex attribute for all vertices_, Next: igraph_cattribute_VAB --- Query a boolean vertex attribute_, Prev: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_, Up: Query attributes + +10.4.1.12 VANV -- Query a numeric vertex attribute for all vertices. +.................................................................... + + + #define VANV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_VANV()’ (*note +igraph_cattribute_VANV --- Query a numeric vertex attribute for many +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized vector, the result is stored here. It + will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAB --- Query a boolean vertex attribute_, Next: VAB --- Query a boolean vertex attribute_, Prev: VANV --- Query a numeric vertex attribute for all vertices_, Up: Query attributes + +10.4.1.13 igraph_cattribute_VAB -- Query a boolean vertex attribute. +.................................................................... + + + igraph_bool_t igraph_cattribute_VAB(const igraph_t *graph, const char *name, + igraph_int_t vid); + + If the vertex attribute does not exist, a warning is issued and false +is returned. See ‘igraph_cattribute_VABV()’ (*note +igraph_cattribute_VABV --- Query a boolean vertex attribute for many +vertices_::) for an error-checked version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vid’: + The id of the queried vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘VAB’ (*note VAB --- Query a boolean vertex attribute_::) macro for + a simpler interface. + + Time complexity: O(Av), the number of vertex attributes. + + +File: igraph-docs.info, Node: VAB --- Query a boolean vertex attribute_, Next: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_, Prev: igraph_cattribute_VAB --- Query a boolean vertex attribute_, Up: Query attributes + +10.4.1.14 VAB -- Query a boolean vertex attribute. +.................................................. + + + #define VAB(graph,n,v) + + This is shorthand for ‘igraph_cattribute_VAB()’ (*note +igraph_cattribute_VAB --- Query a boolean vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + The id of the vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_, Next: VABV --- Query a boolean vertex attribute for all vertices_, Prev: VAB --- Query a boolean vertex attribute_, Up: Query attributes + +10.4.1.15 igraph_cattribute_VABV -- Query a boolean vertex attribute for many vertices. +....................................................................................... + + + igraph_error_t igraph_cattribute_VABV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_bool_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vids’: + The vertices to query. + +‘result’: + Pointer to an initialized boolean vector, the result is stored + here. It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(v), where v is the number of vertices in 'vids'. + + +File: igraph-docs.info, Node: VABV --- Query a boolean vertex attribute for all vertices_, Next: igraph_cattribute_VAS --- Query a string vertex attribute_, Prev: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_, Up: Query attributes + +10.4.1.16 VABV -- Query a boolean vertex attribute for all vertices. +.................................................................... + + + #define VABV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_VABV()’ (*note +igraph_cattribute_VABV --- Query a boolean vertex attribute for many +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized boolean vector, the result is stored + here. It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAS --- Query a string vertex attribute_, Next: VAS --- Query a string vertex attribute_, Prev: VABV --- Query a boolean vertex attribute for all vertices_, Up: Query attributes + +10.4.1.17 igraph_cattribute_VAS -- Query a string vertex attribute. +................................................................... + + + const char *igraph_cattribute_VAS(const igraph_t *graph, const char *name, + igraph_int_t vid); + + Returns a ‘const’ pointer to the string vertex attribute specified in +‘name’. The value must not be modified. If the vertex attribute does +not exist, a warning is issued and an empty string is returned. See +‘igraph_cattribute_VASV()’ (*note igraph_cattribute_VASV --- Query a +string vertex attribute for many vertices_::) for an error-checked +version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vid’: + The id of the queried vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + The macro ‘VAS’ (*note VAS --- Query a string vertex attribute_::) + for a simpler interface. + + Time complexity: O(Av), the number of vertex attributes. + + +File: igraph-docs.info, Node: VAS --- Query a string vertex attribute_, Next: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_, Prev: igraph_cattribute_VAS --- Query a string vertex attribute_, Up: Query attributes + +10.4.1.18 VAS -- Query a string vertex attribute. +................................................. + + + #define VAS(graph,n,v) + + This is shorthand for ‘igraph_cattribute_VAS()’ (*note +igraph_cattribute_VAS --- Query a string vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + The id of the vertex. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_, Next: VASV --- Query a string vertex attribute for all vertices_, Prev: VAS --- Query a string vertex attribute_, Up: Query attributes + +10.4.1.19 igraph_cattribute_VASV -- Query a string vertex attribute for many vertices. +...................................................................................... + + + igraph_error_t igraph_cattribute_VASV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_strvector_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘vids’: + The vertices to query. + +‘result’: + Pointer to an initialized string vector, the result is stored here. + It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(v), where v is the number of vertices in 'vids'. +(We assume that the string attributes have a bounded length.) + + +File: igraph-docs.info, Node: VASV --- Query a string vertex attribute for all vertices_, Next: igraph_cattribute_EAN --- Query a numeric edge attribute_, Prev: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_, Up: Query attributes + +10.4.1.20 VASV -- Query a string vertex attribute for all vertices. +................................................................... + + + #define VASV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_VASV()’ (*note +igraph_cattribute_VASV --- Query a string vertex attribute for many +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized string vector, the result is stored here. + It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAN --- Query a numeric edge attribute_, Next: EAN --- Query a numeric edge attribute_, Prev: VASV --- Query a string vertex attribute for all vertices_, Up: Query attributes + +10.4.1.21 igraph_cattribute_EAN -- Query a numeric edge attribute. +.................................................................. + + + igraph_real_t igraph_cattribute_EAN(const igraph_t *graph, const char *name, + igraph_int_t eid); + + If the attribute does not exist, a warning is issued and NaN is +returned. See ‘igraph_cattribute_EANV()’ (*note igraph_cattribute_EANV +--- Query a numeric edge attribute for many edges_::) for an +error-checked version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eid’: + The id of the queried edge. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘EAN’ (*note EAN --- Query a numeric edge attribute_::) for an + easier interface. + + Time complexity: O(Ae), the number of edge attributes. + + +File: igraph-docs.info, Node: EAN --- Query a numeric edge attribute_, Next: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_, Prev: igraph_cattribute_EAN --- Query a numeric edge attribute_, Up: Query attributes + +10.4.1.22 EAN -- Query a numeric edge attribute. +................................................ + + + #define EAN(graph,n,e) + + This is shorthand for ‘igraph_cattribute_EAN()’ (*note +igraph_cattribute_EAN --- Query a numeric edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘e’: + The id of the edge. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_, Next: EANV --- Query a numeric edge attribute for all edges_, Prev: EAN --- Query a numeric edge attribute_, Up: Query attributes + +10.4.1.23 igraph_cattribute_EANV -- Query a numeric edge attribute for many edges. +.................................................................................. + + + igraph_error_t igraph_cattribute_EANV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eids’: + The edges to query. + +‘result’: + Pointer to an initialized vector, the result is stored here. It + will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(e), where e is the number of edges in 'eids'. + + +File: igraph-docs.info, Node: EANV --- Query a numeric edge attribute for all edges_, Next: igraph_cattribute_EAB --- Query a boolean edge attribute_, Prev: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_, Up: Query attributes + +10.4.1.24 EANV -- Query a numeric edge attribute for all edges. +............................................................... + + + #define EANV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_EANV()’ (*note +igraph_cattribute_EANV --- Query a numeric edge attribute for many +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized vector, the result is stored here. It + will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAB --- Query a boolean edge attribute_, Next: EAB --- Query a boolean edge attribute_, Prev: EANV --- Query a numeric edge attribute for all edges_, Up: Query attributes + +10.4.1.25 igraph_cattribute_EAB -- Query a boolean edge attribute. +.................................................................. + + + igraph_bool_t igraph_cattribute_EAB(const igraph_t *graph, const char *name, + igraph_int_t eid); + + If the edge attribute does not exist, a warning is issued and false +is returned. See ‘igraph_cattribute_EABV()’ (*note +igraph_cattribute_EABV --- Query a boolean edge attribute for many +edges_::) for an error-checked version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eid’: + The id of the queried edge. + + *Returns:. * + +‘’ + The value of the attribute. + + *See also:. * + +‘’ + ‘EAB’ (*note EAB --- Query a boolean edge attribute_::) for an + easier interface. + + Time complexity: O(Ae), the number of edge attributes. + + +File: igraph-docs.info, Node: EAB --- Query a boolean edge attribute_, Next: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_, Prev: igraph_cattribute_EAB --- Query a boolean edge attribute_, Up: Query attributes + +10.4.1.26 EAB -- Query a boolean edge attribute. +................................................ + + + #define EAB(graph,n,e) + + This is shorthand for ‘igraph_cattribute_EAB()’ (*note +igraph_cattribute_EAB --- Query a boolean edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘e’: + The id of the edge. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_, Next: EABV --- Query a boolean edge attribute for all edges_, Prev: EAB --- Query a boolean edge attribute_, Up: Query attributes + +10.4.1.27 igraph_cattribute_EABV -- Query a boolean edge attribute for many edges. +.................................................................................. + + + igraph_error_t igraph_cattribute_EABV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_bool_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eids’: + The edges to query. + +‘result’: + Pointer to an initialized boolean vector, the result is stored + here. It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(e), where e is the number of edges in 'eids'. + + +File: igraph-docs.info, Node: EABV --- Query a boolean edge attribute for all edges_, Next: igraph_cattribute_EAS --- Query a string edge attribute_, Prev: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_, Up: Query attributes + +10.4.1.28 EABV -- Query a boolean edge attribute for all edges. +............................................................... + + + #define EABV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_EABV()’ (*note +igraph_cattribute_EABV --- Query a boolean edge attribute for many +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized vector, the result is stored here. It + will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAS --- Query a string edge attribute_, Next: EAS --- Query a string edge attribute_, Prev: EABV --- Query a boolean edge attribute for all edges_, Up: Query attributes + +10.4.1.29 igraph_cattribute_EAS -- Query a string edge attribute. +................................................................. + + + const char *igraph_cattribute_EAS(const igraph_t *graph, const char *name, + igraph_int_t eid); + + Returns a ‘const’ pointer to the string edge attribute specified in +‘name’. The value must not be modified. If the edge attribute does not +exist, a warning is issued and an empty string is returned. See +‘igraph_cattribute_EASV()’ (*note igraph_cattribute_EASV --- Query a +string edge attribute for many edges_::) for an error-checked version. + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eid’: + The id of the queried edge. + + *Returns:. * + +‘’ + The value of the attribute. + + \se ‘EAS’ (*note EAS --- Query a string edge attribute_::) if you +want to type less. Time complexity: O(Ae), the number of edge +attributes. + + +File: igraph-docs.info, Node: EAS --- Query a string edge attribute_, Next: igraph_cattribute_EASV --- Query a string edge attribute for many edges_, Prev: igraph_cattribute_EAS --- Query a string edge attribute_, Up: Query attributes + +10.4.1.30 EAS -- Query a string edge attribute. +............................................... + + + #define EAS(graph,n,e) + + This is shorthand for ‘igraph_cattribute_EAS()’ (*note +igraph_cattribute_EAS --- Query a string edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘e’: + The id of the edge. + + *Returns:. * + +‘’ + The value of the attribute. + + +File: igraph-docs.info, Node: igraph_cattribute_EASV --- Query a string edge attribute for many edges_, Next: EASV --- Query a string edge attribute for all edges_, Prev: EAS --- Query a string edge attribute_, Up: Query attributes + +10.4.1.31 igraph_cattribute_EASV -- Query a string edge attribute for many edges. +................................................................................. + + + igraph_error_t igraph_cattribute_EASV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_strvector_t *result); + + *Arguments:. * + +‘graph’: + The input graph. + +‘name’: + The name of the attribute. + +‘eids’: + The edges to query. + +‘result’: + Pointer to an initialized string vector, the result is stored here. + It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(e), where e is the number of edges in 'eids'. (We +assume that the string attributes have a bounded length.) + + +File: igraph-docs.info, Node: EASV --- Query a string edge attribute for all edges_, Prev: igraph_cattribute_EASV --- Query a string edge attribute for many edges_, Up: Query attributes + +10.4.1.32 EASV -- Query a string edge attribute for all edges. +.............................................................. + + + #define EASV(graph,n,vec) + + This is a shorthand for ‘igraph_cattribute_EASV()’ (*note +igraph_cattribute_EASV --- Query a string edge attribute for many +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vec’: + Pointer to an initialized string vector, the result is stored here. + It will be resized, if needed. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: Set attributes, Next: Remove attributes, Prev: Query attributes, Up: Accessing attributes from C + +10.4.2 Set attributes +--------------------- + +* Menu: + +* igraph_cattribute_GAN_set -- Set a numeric graph attribute.: igraph_cattribute_GAN_set --- Set a numeric graph attribute_. +* SETGAN --- Set a numeric graph attribute:: +* igraph_cattribute_GAB_set -- Set a boolean graph attribute.: igraph_cattribute_GAB_set --- Set a boolean graph attribute_. +* SETGAB --- Set a boolean graph attribute:: +* igraph_cattribute_GAS_set -- Set a string graph attribute.: igraph_cattribute_GAS_set --- Set a string graph attribute_. +* SETGAS --- Set a string graph attribute:: +* igraph_cattribute_VAN_set -- Set a numeric vertex attribute.: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_. +* SETVAN --- Set a numeric vertex attribute:: +* igraph_cattribute_VAB_set -- Set a boolean vertex attribute.: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_. +* SETVAB --- Set a boolean vertex attribute:: +* igraph_cattribute_VAS_set -- Set a string vertex attribute.: igraph_cattribute_VAS_set --- Set a string vertex attribute_. +* SETVAS --- Set a string vertex attribute:: +* igraph_cattribute_EAN_set -- Set a numeric edge attribute.: igraph_cattribute_EAN_set --- Set a numeric edge attribute_. +* SETEAN --- Set a numeric edge attribute:: +* igraph_cattribute_EAB_set -- Set a boolean edge attribute.: igraph_cattribute_EAB_set --- Set a boolean edge attribute_. +* SETEAB --- Set a boolean edge attribute:: +* igraph_cattribute_EAS_set -- Set a string edge attribute.: igraph_cattribute_EAS_set --- Set a string edge attribute_. +* SETEAS --- Set a string edge attribute:: +* igraph_cattribute_VAN_setv -- Set a numeric vertex attribute for all vertices.: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_. +* SETVANV --- Set a numeric vertex attribute for all vertices:: +* igraph_cattribute_VAB_setv -- Set a boolean vertex attribute for all vertices.: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_. +* SETVABV --- Set a boolean vertex attribute for all vertices:: +* igraph_cattribute_VAS_setv -- Set a string vertex attribute for all vertices.: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_. +* SETVASV --- Set a string vertex attribute for all vertices:: +* igraph_cattribute_EAN_setv -- Set a numeric edge attribute for all edges.: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_. +* SETEANV --- Set a numeric edge attribute for all edges:: +* igraph_cattribute_EAB_setv -- Set a boolean edge attribute for all edges.: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_. +* SETEABV --- Set a boolean edge attribute for all edges:: +* igraph_cattribute_EAS_setv -- Set a string edge attribute for all edges.: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_. +* SETEASV --- Set a string edge attribute for all edges:: + + +File: igraph-docs.info, Node: igraph_cattribute_GAN_set --- Set a numeric graph attribute_, Next: SETGAN --- Set a numeric graph attribute, Up: Set attributes + +10.4.2.1 igraph_cattribute_GAN_set -- Set a numeric graph attribute. +.................................................................... + + + igraph_error_t igraph_cattribute_GAN_set(igraph_t *graph, const char *name, + igraph_real_t value); + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the graph attribute. If there is no such attribute yet, + then it will be added. + +‘value’: + The (new) value of the graph attribute. + + *Returns:. * + +‘’ + Error code. + + \se ‘SETGAN’ (*note SETGAN --- Set a numeric graph attribute::) if +you want to type less. Time complexity: O(1). + + +File: igraph-docs.info, Node: SETGAN --- Set a numeric graph attribute, Next: igraph_cattribute_GAB_set --- Set a boolean graph attribute_, Prev: igraph_cattribute_GAN_set --- Set a numeric graph attribute_, Up: Set attributes + +10.4.2.2 SETGAN -- Set a numeric graph attribute +................................................ + + + #define SETGAN(graph,n,value) + + This is a shorthand for ‘igraph_cattribute_GAN_set()’ (*note +igraph_cattribute_GAN_set --- Set a numeric graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_GAB_set --- Set a boolean graph attribute_, Next: SETGAB --- Set a boolean graph attribute, Prev: SETGAN --- Set a numeric graph attribute, Up: Set attributes + +10.4.2.3 igraph_cattribute_GAB_set -- Set a boolean graph attribute. +.................................................................... + + + igraph_error_t igraph_cattribute_GAB_set(igraph_t *graph, const char *name, + igraph_bool_t value); + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the graph attribute. If there is no such attribute yet, + then it will be added. + +‘value’: + The (new) value of the graph attribute. + + *Returns:. * + +‘’ + Error code. + + \se ‘SETGAN’ (*note SETGAN --- Set a numeric graph attribute::) if +you want to type less. Time complexity: O(1). + + +File: igraph-docs.info, Node: SETGAB --- Set a boolean graph attribute, Next: igraph_cattribute_GAS_set --- Set a string graph attribute_, Prev: igraph_cattribute_GAB_set --- Set a boolean graph attribute_, Up: Set attributes + +10.4.2.4 SETGAB -- Set a boolean graph attribute +................................................ + + + #define SETGAB(graph,n,value) + + This is a shorthand for ‘igraph_cattribute_GAB_set()’ (*note +igraph_cattribute_GAB_set --- Set a boolean graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_GAS_set --- Set a string graph attribute_, Next: SETGAS --- Set a string graph attribute, Prev: SETGAB --- Set a boolean graph attribute, Up: Set attributes + +10.4.2.5 igraph_cattribute_GAS_set -- Set a string graph attribute. +................................................................... + + + igraph_error_t igraph_cattribute_GAS_set(igraph_t *graph, const char *name, + const char *value); + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the graph attribute. If there is no such attribute yet, + then it will be added. + +‘value’: + The (new) value of the graph attribute. It will be copied. + + *Returns:. * + +‘’ + Error code. + + \se ‘SETGAS’ (*note SETGAS --- Set a string graph attribute::) if you +want to type less. Time complexity: O(1). + + +File: igraph-docs.info, Node: SETGAS --- Set a string graph attribute, Next: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_, Prev: igraph_cattribute_GAS_set --- Set a string graph attribute_, Up: Set attributes + +10.4.2.6 SETGAS -- Set a string graph attribute +............................................... + + + #define SETGAS(graph,n,value) + + This is a shorthand for ‘igraph_cattribute_GAS_set()’ (*note +igraph_cattribute_GAS_set --- Set a string graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_, Next: SETVAN --- Set a numeric vertex attribute, Prev: SETGAS --- Set a string graph attribute, Up: Set attributes + +10.4.2.7 igraph_cattribute_VAN_set -- Set a numeric vertex attribute. +..................................................................... + + + igraph_error_t igraph_cattribute_VAN_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_real_t value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all vertices included +in ‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘vid’: + Vertices for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVAN’ (*note SETVAN --- Set a numeric vertex attribute::) for a + simpler way. + + Time complexity: O(n), the number of vertices if the attribute is +new, O(|vid|) otherwise. + + +File: igraph-docs.info, Node: SETVAN --- Set a numeric vertex attribute, Next: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_, Prev: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_, Up: Set attributes + +10.4.2.8 SETVAN -- Set a numeric vertex attribute +................................................. + + + #define SETVAN(graph,n,vid,value) + + This is a shorthand for ‘igraph_cattribute_VAN_set()’ (*note +igraph_cattribute_VAN_set --- Set a numeric vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vid’: + Ids of the vertices to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_, Next: SETVAB --- Set a boolean vertex attribute, Prev: SETVAN --- Set a numeric vertex attribute, Up: Set attributes + +10.4.2.9 igraph_cattribute_VAB_set -- Set a boolean vertex attribute. +..................................................................... + + + igraph_error_t igraph_cattribute_VAB_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_bool_t value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all vertices included +in ‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘vid’: + Vertices for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVAB’ (*note SETVAB --- Set a boolean vertex attribute::) for a + simpler way. + + Time complexity: O(n), the number of vertices if the attribute is +new, O(|vid|) otherwise. + + +File: igraph-docs.info, Node: SETVAB --- Set a boolean vertex attribute, Next: igraph_cattribute_VAS_set --- Set a string vertex attribute_, Prev: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_, Up: Set attributes + +10.4.2.10 SETVAB -- Set a boolean vertex attribute +.................................................. + + + #define SETVAB(graph,n,vid,value) + + This is a shorthand for ‘igraph_cattribute_VAB_set()’ (*note +igraph_cattribute_VAB_set --- Set a boolean vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vid’: + Ids of the vertices to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAS_set --- Set a string vertex attribute_, Next: SETVAS --- Set a string vertex attribute, Prev: SETVAB --- Set a boolean vertex attribute, Up: Set attributes + +10.4.2.11 igraph_cattribute_VAS_set -- Set a string vertex attribute. +..................................................................... + + + igraph_error_t igraph_cattribute_VAS_set(igraph_t *graph, const char *name, + igraph_int_t vid, const char *value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all vertices included +in ‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘vid’: + Vertices for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVAS’ (*note SETVAS --- Set a string vertex attribute::) for a + simpler way. + + Time complexity: O(n*l), n is the number of vertices, l is the length +of the string to set. If the attribute if not new then only O(|vid|*l). + + +File: igraph-docs.info, Node: SETVAS --- Set a string vertex attribute, Next: igraph_cattribute_EAN_set --- Set a numeric edge attribute_, Prev: igraph_cattribute_VAS_set --- Set a string vertex attribute_, Up: Set attributes + +10.4.2.12 SETVAS -- Set a string vertex attribute +................................................. + + + #define SETVAS(graph,n,vid,value) + + This is a shorthand for ‘igraph_cattribute_VAS_set()’ (*note +igraph_cattribute_VAS_set --- Set a string vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘vid’: + Ids of the vertices to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAN_set --- Set a numeric edge attribute_, Next: SETEAN --- Set a numeric edge attribute, Prev: SETVAS --- Set a string vertex attribute, Up: Set attributes + +10.4.2.13 igraph_cattribute_EAN_set -- Set a numeric edge attribute. +.................................................................... + + + igraph_error_t igraph_cattribute_EAN_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_real_t value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all edges included in +‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘eid’: + Edges for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEAN’ (*note SETEAN --- Set a numeric edge attribute::) for a + simpler way. + + Time complexity: O(e), the number of edges if the attribute is new, +O(|eid|) otherwise. + + +File: igraph-docs.info, Node: SETEAN --- Set a numeric edge attribute, Next: igraph_cattribute_EAB_set --- Set a boolean edge attribute_, Prev: igraph_cattribute_EAN_set --- Set a numeric edge attribute_, Up: Set attributes + +10.4.2.14 SETEAN -- Set a numeric edge attribute +................................................ + + + #define SETEAN(graph,n,eid,value) + + This is a shorthand for ‘igraph_cattribute_EAN_set()’ (*note +igraph_cattribute_EAN_set --- Set a numeric edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘eid’: + Ids of the edges to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAB_set --- Set a boolean edge attribute_, Next: SETEAB --- Set a boolean edge attribute, Prev: SETEAN --- Set a numeric edge attribute, Up: Set attributes + +10.4.2.15 igraph_cattribute_EAB_set -- Set a boolean edge attribute. +.................................................................... + + + igraph_error_t igraph_cattribute_EAB_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_bool_t value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all edges included in +‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘eid’: + Edges for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEAB’ (*note SETEAB --- Set a boolean edge attribute::) for a + simpler way. + + Time complexity: O(e), the number of edges if the attribute is new, +O(|eid|) otherwise. + + +File: igraph-docs.info, Node: SETEAB --- Set a boolean edge attribute, Next: igraph_cattribute_EAS_set --- Set a string edge attribute_, Prev: igraph_cattribute_EAB_set --- Set a boolean edge attribute_, Up: Set attributes + +10.4.2.16 SETEAB -- Set a boolean edge attribute +................................................ + + + #define SETEAB(graph,n,eid,value) + + This is a shorthand for ‘igraph_cattribute_EAB_set()’ (*note +igraph_cattribute_EAB_set --- Set a boolean edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘eid’: + Ids of the edges to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAS_set --- Set a string edge attribute_, Next: SETEAS --- Set a string edge attribute, Prev: SETEAB --- Set a boolean edge attribute, Up: Set attributes + +10.4.2.17 igraph_cattribute_EAS_set -- Set a string edge attribute. +................................................................... + + + igraph_error_t igraph_cattribute_EAS_set(igraph_t *graph, const char *name, + igraph_int_t eid, const char *value); + + The attribute will be added if not present already. If present it +will be overwritten. The same ‘value’ is set for all edges included in +‘vid’. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘eid’: + Edges for which to set the attribute. + +‘value’: + The (new) value of the attribute. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEAS’ (*note SETEAS --- Set a string edge attribute::) for a + simpler way. + + Time complexity: O(e*l), n is the number of edges, l is the length of +the string to set. If the attribute if not new then only O(|eid|*l). + + +File: igraph-docs.info, Node: SETEAS --- Set a string edge attribute, Next: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_, Prev: igraph_cattribute_EAS_set --- Set a string edge attribute_, Up: Set attributes + +10.4.2.18 SETEAS -- Set a string edge attribute +............................................... + + + #define SETEAS(graph,n,eid,value) + + This is a shorthand for ‘igraph_cattribute_EAS_set()’ (*note +igraph_cattribute_EAS_set --- Set a string edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘eid’: + Ids of the edges to set. + +‘value’: + The new value of the attribute. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_, Next: SETVANV --- Set a numeric vertex attribute for all vertices, Prev: SETEAS --- Set a string edge attribute, Up: Set attributes + +10.4.2.19 igraph_cattribute_VAN_setv -- Set a numeric vertex attribute for all vertices. +........................................................................................ + + + igraph_error_t igraph_cattribute_VAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘v’: + The new attribute values. The length of this vector must match the + number of vertices. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVANV’ (*note SETVANV --- Set a numeric vertex attribute for all + vertices::) for a simpler way. + + Time complexity: O(n), the number of vertices. + + +File: igraph-docs.info, Node: SETVANV --- Set a numeric vertex attribute for all vertices, Next: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_, Prev: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_, Up: Set attributes + +10.4.2.20 SETVANV -- Set a numeric vertex attribute for all vertices +.................................................................... + + + #define SETVANV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_VAN_setv()’ (*note +igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_, Next: SETVABV --- Set a boolean vertex attribute for all vertices, Prev: SETVANV --- Set a numeric vertex attribute for all vertices, Up: Set attributes + +10.4.2.21 igraph_cattribute_VAB_setv -- Set a boolean vertex attribute for all vertices. +........................................................................................ + + + igraph_error_t igraph_cattribute_VAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘v’: + The new attribute values. The length of this boolean vector must + match the number of vertices. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVANV’ (*note SETVANV --- Set a numeric vertex attribute for all + vertices::) for a simpler way. + + Time complexity: O(n), the number of vertices. + + +File: igraph-docs.info, Node: SETVABV --- Set a boolean vertex attribute for all vertices, Next: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_, Prev: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_, Up: Set attributes + +10.4.2.22 SETVABV -- Set a boolean vertex attribute for all vertices +.................................................................... + + + #define SETVABV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_VAB_setv()’ (*note +igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_, Next: SETVASV --- Set a string vertex attribute for all vertices, Prev: SETVABV --- Set a boolean vertex attribute for all vertices, Up: Set attributes + +10.4.2.23 igraph_cattribute_VAS_setv -- Set a string vertex attribute for all vertices. +....................................................................................... + + + igraph_error_t igraph_cattribute_VAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘sv’: + String vector, the new attribute values. The length of this vector + must match the number of vertices. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETVASV’ (*note SETVASV --- Set a string vertex attribute for all + vertices::) for a simpler way. + + Time complexity: O(n+l), n is the number of vertices, l is the total +length of the strings. + + +File: igraph-docs.info, Node: SETVASV --- Set a string vertex attribute for all vertices, Next: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_, Prev: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_, Up: Set attributes + +10.4.2.24 SETVASV -- Set a string vertex attribute for all vertices +................................................................... + + + #define SETVASV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_VAS_setv()’ (*note +igraph_cattribute_VAS_setv --- Set a string vertex attribute for all +vertices_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_, Next: SETEANV --- Set a numeric edge attribute for all edges, Prev: SETVASV --- Set a string vertex attribute for all vertices, Up: Set attributes + +10.4.2.25 igraph_cattribute_EAN_setv -- Set a numeric edge attribute for all edges. +................................................................................... + + + igraph_error_t igraph_cattribute_EAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘v’: + The new attribute values. The length of this vector must match the + number of edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEANV’ (*note SETEANV --- Set a numeric edge attribute for all + edges::) for a simpler way. + + Time complexity: O(e), the number of edges. + + +File: igraph-docs.info, Node: SETEANV --- Set a numeric edge attribute for all edges, Next: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_, Prev: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_, Up: Set attributes + +10.4.2.26 SETEANV -- Set a numeric edge attribute for all edges +............................................................... + + + #define SETEANV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_EAN_setv()’ (*note +igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + +File: igraph-docs.info, Node: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_, Next: SETEABV --- Set a boolean edge attribute for all edges, Prev: SETEANV --- Set a numeric edge attribute for all edges, Up: Set attributes + +10.4.2.27 igraph_cattribute_EAB_setv -- Set a boolean edge attribute for all edges. +................................................................................... + + + igraph_error_t igraph_cattribute_EAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘v’: + The new attribute values. The length of this vector must match the + number of edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEABV’ (*note SETEABV --- Set a boolean edge attribute for all + edges::) for a simpler way. + + Time complexity: O(e), the number of edges. + + +File: igraph-docs.info, Node: SETEABV --- Set a boolean edge attribute for all edges, Next: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_, Prev: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_, Up: Set attributes + +10.4.2.28 SETEABV -- Set a boolean edge attribute for all edges +............................................................... + + + #define SETEABV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_EAB_setv()’ (*note +igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + +File: igraph-docs.info, Node: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_, Next: SETEASV --- Set a string edge attribute for all edges, Prev: SETEABV --- Set a boolean edge attribute for all edges, Up: Set attributes + +10.4.2.29 igraph_cattribute_EAS_setv -- Set a string edge attribute for all edges. +.................................................................................. + + + igraph_error_t igraph_cattribute_EAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv); + + The attribute will be added if not present yet. + + *Arguments:. * + +‘graph’: + The graph. + +‘name’: + Name of the attribute. + +‘sv’: + String vector, the new attribute values. The length of this vector + must match the number of edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘SETEASV’ (*note SETEASV --- Set a string edge attribute for all + edges::) for a simpler way. + + Time complexity: O(e+l), e is the number of edges, l is the total +length of the strings. + + +File: igraph-docs.info, Node: SETEASV --- Set a string edge attribute for all edges, Prev: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_, Up: Set attributes + +10.4.2.30 SETEASV -- Set a string edge attribute for all edges +.............................................................. + + + #define SETEASV(graph,n,v) + + This is a shorthand for ‘igraph_cattribute_EAS_setv()’ (*note +igraph_cattribute_EAS_setv --- Set a string edge attribute for all +edges_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute. + +‘v’: + Vector containing the new values of the attributes. + + +File: igraph-docs.info, Node: Remove attributes, Next: Custom attribute combination functions, Prev: Set attributes, Up: Accessing attributes from C + +10.4.3 Remove attributes +------------------------ + +* Menu: + +* igraph_cattribute_remove_g -- Remove a graph attribute.: igraph_cattribute_remove_g --- Remove a graph attribute_. +* DELGA -- Remove a graph attribute.: DELGA --- Remove a graph attribute_. +* igraph_cattribute_remove_v -- Remove a vertex attribute.: igraph_cattribute_remove_v --- Remove a vertex attribute_. +* DELVA -- Remove a vertex attribute.: DELVA --- Remove a vertex attribute_. +* igraph_cattribute_remove_e -- Remove an edge attribute.: igraph_cattribute_remove_e --- Remove an edge attribute_. +* DELEA -- Remove an edge attribute.: DELEA --- Remove an edge attribute_. +* igraph_cattribute_remove_all -- Remove all graph/vertex/edge attributes.: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_. +* DELGAS -- Remove all graph attributes.: DELGAS --- Remove all graph attributes_. +* DELVAS -- Remove all vertex attributes.: DELVAS --- Remove all vertex attributes_. +* DELEAS -- Remove all edge attributes.: DELEAS --- Remove all edge attributes_. +* DELALL -- Remove all attributes.: DELALL --- Remove all attributes_. + + +File: igraph-docs.info, Node: igraph_cattribute_remove_g --- Remove a graph attribute_, Next: DELGA --- Remove a graph attribute_, Up: Remove attributes + +10.4.3.1 igraph_cattribute_remove_g -- Remove a graph attribute. +................................................................ + + + void igraph_cattribute_remove_g(igraph_t *graph, const char *name); + + *Arguments:. * + +‘graph’: + The graph object. + +‘name’: + Name of the graph attribute to remove. + + *See also:. * + +‘’ + ‘DELGA’ (*note DELGA --- Remove a graph attribute_::) for a simpler + way. + + +File: igraph-docs.info, Node: DELGA --- Remove a graph attribute_, Next: igraph_cattribute_remove_v --- Remove a vertex attribute_, Prev: igraph_cattribute_remove_g --- Remove a graph attribute_, Up: Remove attributes + +10.4.3.2 DELGA -- Remove a graph attribute. +........................................... + + + #define DELGA(graph,n) + + A shorthand for ‘igraph_cattribute_remove_g()’ (*note +igraph_cattribute_remove_g --- Remove a graph attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute to remove. + + +File: igraph-docs.info, Node: igraph_cattribute_remove_v --- Remove a vertex attribute_, Next: DELVA --- Remove a vertex attribute_, Prev: DELGA --- Remove a graph attribute_, Up: Remove attributes + +10.4.3.3 igraph_cattribute_remove_v -- Remove a vertex attribute. +................................................................. + + + void igraph_cattribute_remove_v(igraph_t *graph, const char *name); + + *Arguments:. * + +‘graph’: + The graph object. + +‘name’: + Name of the vertex attribute to remove. + + *See also:. * + +‘’ + ‘DELVA’ (*note DELVA --- Remove a vertex attribute_::) for a + simpler way. + + +File: igraph-docs.info, Node: DELVA --- Remove a vertex attribute_, Next: igraph_cattribute_remove_e --- Remove an edge attribute_, Prev: igraph_cattribute_remove_v --- Remove a vertex attribute_, Up: Remove attributes + +10.4.3.4 DELVA -- Remove a vertex attribute. +............................................ + + + #define DELVA(graph,n) + + A shorthand for ‘igraph_cattribute_remove_v()’ (*note +igraph_cattribute_remove_v --- Remove a vertex attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute to remove. + + +File: igraph-docs.info, Node: igraph_cattribute_remove_e --- Remove an edge attribute_, Next: DELEA --- Remove an edge attribute_, Prev: DELVA --- Remove a vertex attribute_, Up: Remove attributes + +10.4.3.5 igraph_cattribute_remove_e -- Remove an edge attribute. +................................................................ + + + void igraph_cattribute_remove_e(igraph_t *graph, const char *name); + + *Arguments:. * + +‘graph’: + The graph object. + +‘name’: + Name of the edge attribute to remove. + + *See also:. * + +‘’ + ‘DELEA’ (*note DELEA --- Remove an edge attribute_::) for a simpler + way. + + +File: igraph-docs.info, Node: DELEA --- Remove an edge attribute_, Next: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_, Prev: igraph_cattribute_remove_e --- Remove an edge attribute_, Up: Remove attributes + +10.4.3.6 DELEA -- Remove an edge attribute. +........................................... + + + #define DELEA(graph,n) + + A shorthand for ‘igraph_cattribute_remove_e()’ (*note +igraph_cattribute_remove_e --- Remove an edge attribute_::). + + *Arguments:. * + +‘graph’: + The graph. + +‘n’: + The name of the attribute to remove. + + +File: igraph-docs.info, Node: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_, Next: DELGAS --- Remove all graph attributes_, Prev: DELEA --- Remove an edge attribute_, Up: Remove attributes + +10.4.3.7 igraph_cattribute_remove_all -- Remove all graph/vertex/edge attributes. +................................................................................. + + + void igraph_cattribute_remove_all(igraph_t *graph, igraph_bool_t g, + igraph_bool_t v, igraph_bool_t e); + + *Arguments:. * + +‘graph’: + The graph object. + +‘g’: + Boolean, whether to remove graph attributes. + +‘v’: + Boolean, whether to remove vertex attributes. + +‘e’: + Boolean, whether to remove edge attributes. + + *See also:. * + +‘’ + ‘DELGAS’ (*note DELGAS --- Remove all graph attributes_::), + ‘DELVAS’ (*note DELVAS --- Remove all vertex attributes_::), + ‘DELEAS’ (*note DELEAS --- Remove all edge attributes_::), ‘DELALL’ + (*note DELALL --- Remove all attributes_::) for simpler ways. + + +File: igraph-docs.info, Node: DELGAS --- Remove all graph attributes_, Next: DELVAS --- Remove all vertex attributes_, Prev: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_, Up: Remove attributes + +10.4.3.8 DELGAS -- Remove all graph attributes. +............................................... + + + #define DELGAS(graph) + + Calls ‘igraph_cattribute_remove_all()’ (*note +igraph_cattribute_remove_all --- Remove all graph/vertex/edge +attributes_::). + + *Arguments:. * + +‘graph’: + The graph. + + +File: igraph-docs.info, Node: DELVAS --- Remove all vertex attributes_, Next: DELEAS --- Remove all edge attributes_, Prev: DELGAS --- Remove all graph attributes_, Up: Remove attributes + +10.4.3.9 DELVAS -- Remove all vertex attributes. +................................................ + + + #define DELVAS(graph) + + Calls ‘igraph_cattribute_remove_all()’ (*note +igraph_cattribute_remove_all --- Remove all graph/vertex/edge +attributes_::). + + *Arguments:. * + +‘graph’: + The graph. + + +File: igraph-docs.info, Node: DELEAS --- Remove all edge attributes_, Next: DELALL --- Remove all attributes_, Prev: DELVAS --- Remove all vertex attributes_, Up: Remove attributes + +10.4.3.10 DELEAS -- Remove all edge attributes. +............................................... + + + #define DELEAS(graph) + + Calls ‘igraph_cattribute_remove_all()’ (*note +igraph_cattribute_remove_all --- Remove all graph/vertex/edge +attributes_::). + + *Arguments:. * + +‘graph’: + The graph. + + +File: igraph-docs.info, Node: DELALL --- Remove all attributes_, Prev: DELEAS --- Remove all edge attributes_, Up: Remove attributes + +10.4.3.11 DELALL -- Remove all attributes. +.......................................... + + + #define DELALL(graph) + + All graph, vertex and edges attributes will be removed. Calls +‘igraph_cattribute_remove_all()’ (*note igraph_cattribute_remove_all --- +Remove all graph/vertex/edge attributes_::). + + *Arguments:. * + +‘graph’: + The graph. + + +File: igraph-docs.info, Node: Custom attribute combination functions, Prev: Remove attributes, Up: Accessing attributes from C + +10.4.4 Custom attribute combination functions +--------------------------------------------- + +The C attribute handler supports combining the attributes of multiple +vertices of edges into a single attribute during a vertex or edge +contraction operation via a user-defined function. This is achieved by +setting the type of the attribute combination to +‘IGRAPH_ATTRIBUTE_COMBINE_FUNCTION’ and passing in a pointer to the +custom combination function when specifying attribute combinations in +‘igraph_attribute_combination()’ (*note igraph_attribute_combination --- +Initialize attribute combination list and add records_::) or +‘igraph_attribute_combination_add()’ (*note +igraph_attribute_combination_add --- Add combination record to attribute +combination list_::) . For the C attribute handler, the signature of +the function depends on the type of the underlying attribute. For +numeric attributes, use: + + igraph_error_t function(const igraph_vector_t *input, igraph_real_t *output); + +where ‘input’ will receive a vector containing the value of the +attribute for all the vertices or edges being combined, and ‘output’ +must be filled by the function to the combined value. Similarly, for +Boolean attributes, the function takes a boolean vector in ‘input’ and +must return the combined Boolean value in ‘output’: + + igraph_error_t function(const igraph_vector_bool_t *input, igraph_bool_t *output); + +For string attributes, the signature is slightly different: + + igraph_error_t function(const igraph_strvector_t *input, char **output); + +In case of strings, all strings in the input vector are _owned_ by +igraph and must not be modified or freed in the combination handler. +The string returned to the caller in ‘output’ remains owned by the +caller; igraph will make a copy it and store the copy in the appropriate +part of the data structure holding the vertex or edge attributes. + + +File: igraph-docs.info, Node: Deterministic graph generators, Next: Stochastic graph generators ["games"], Prev: Graph; vertex and edge attributes, Up: Top + +11 Deterministic graph generators +********************************* + +* Menu: + +* About generators:: +* Basic graph creation:: +* Graphs from adjacency matrices and adjacency lists:: +* Regular structures:: +* Tree generators:: +* Graphs with given degrees:: +* Complete graphs:: +* Pre-defined graphs:: +* Other well-known graphs from graph theory:: + + +File: igraph-docs.info, Node: About generators, Next: Basic graph creation, Up: Deterministic graph generators + +11.1 About generators +===================== + +Most functions that create graphs in a deterministic manner are +documented here. See also stochastic generators (*note Stochastic graph +generators ["games"]::), spatial graph generators (*note Spatial graph +generators::), bipartite graph generators (*note Create two-mode +networks::), and operators that transform graphs (*note Graph +operators::). + + +File: igraph-docs.info, Node: Basic graph creation, Next: Graphs from adjacency matrices and adjacency lists, Prev: About generators, Up: Deterministic graph generators + +11.2 Basic graph creation +========================= + +* Menu: + +* igraph_create -- Creates a graph with the specified edges.: igraph_create --- Creates a graph with the specified edges_. +* igraph_small -- Shorthand to create a small graph, giving the edges as arguments.: igraph_small --- Shorthand to create a small graph; giving the edges as arguments_. + + +File: igraph-docs.info, Node: igraph_create --- Creates a graph with the specified edges_, Next: igraph_small --- Shorthand to create a small graph; giving the edges as arguments_, Up: Basic graph creation + +11.2.1 igraph_create -- Creates a graph with the specified edges. +----------------------------------------------------------------- + + + igraph_error_t igraph_create(igraph_t *graph, const igraph_vector_int_t *edges, + igraph_int_t n, igraph_bool_t directed); + + *Arguments:. * + +‘graph’: + An uninitialized graph object. + +‘edges’: + The edges to add, the first two elements are the first edge, etc. + +‘n’: + The number of vertices in the graph, if smaller or equal to the + highest vertex ID in the ‘edges’ vector it will be increased + automatically. So it is safe to give 0 here. + +‘directed’: + Boolean, whether to create a directed graph or not. If yes, then + the first edge points from the first vertex ID in ‘edges’ to the + second, etc. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid edges vector (odd number of + vertices). ‘IGRAPH_EINVVID’: invalid (negative) vertex ID. + + Time complexity: O(|V|+|E|), |V| is the number of vertices, |E| the +number of edges in the graph. + + * File examples/simple/igraph_create.c* + + +File: igraph-docs.info, Node: igraph_small --- Shorthand to create a small graph; giving the edges as arguments_, Prev: igraph_create --- Creates a graph with the specified edges_, Up: Basic graph creation + +11.2.2 igraph_small -- Shorthand to create a small graph, giving the edges as arguments. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_small(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + int first, ...); + + This function is handy when a relatively small graph needs to be +created. Instead of giving the edges as a vector, they are given simply +as arguments and a ‘-1’ needs to be given after the last meaningful edge +argument. + + This function is intended to be used with vertex IDs that are entered +as literal integers. If you use a variable instead of a literal, make +sure that it is of type ‘int’, as this is the type that this function +assumes for all variadic arguments. Using a different integer type is +undefined behaviour and likely to cause platform-specific issues. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. The result will be + stored here. + +‘n’: + The number of vertices in the graph; a non-negative integer. + +‘directed’: + Boolean constant; gives whether the graph should be directed. + Supported values are: + + ‘IGRAPH_DIRECTED’ + The graph to be created will be _directed._ + + ‘IGRAPH_UNDIRECTED’ + The graph to be created will be _undirected._ + +‘...’: + The additional arguments giving the edges of the graph, and _must_ + be of type ‘int’. Don't forget to supply an additional ‘-1’ after + the last (meaningful) argument. The ‘first’ parameter is present + for technical reasons and represents the first variadic argument. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph to create. + + * File examples/simple/igraph_small.c* + + +File: igraph-docs.info, Node: Graphs from adjacency matrices and adjacency lists, Next: Regular structures, Prev: Basic graph creation, Up: Deterministic graph generators + +11.3 Graphs from adjacency matrices and adjacency lists +======================================================= + +These functions create graphs from weighted or unweighted adjacency +matrices, or an adjacency list. + +* Menu: + +* igraph_adjacency -- Creates a graph from an adjacency matrix.: igraph_adjacency --- Creates a graph from an adjacency matrix_. +* igraph_weighted_adjacency -- Creates a graph from a weighted adjacency matrix.: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_. +* igraph_sparse_adjacency -- Creates a graph from a sparse adjacency matrix.: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_. +* igraph_sparse_weighted_adjacency -- Creates a graph from a weighted sparse adjacency matrix.: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_. +* igraph_adjlist -- Creates a graph from an adjacency list.: igraph_adjlist --- Creates a graph from an adjacency list_. + + +File: igraph-docs.info, Node: igraph_adjacency --- Creates a graph from an adjacency matrix_, Next: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_, Up: Graphs from adjacency matrices and adjacency lists + +11.3.1 igraph_adjacency -- Creates a graph from an adjacency matrix. +-------------------------------------------------------------------- + + + igraph_error_t igraph_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_loops_t loops + ); + + The order of the vertices in the matrix is preserved, i.e. the +vertex corresponding to the first row/column will be vertex with id 0, +the next row is for vertex 1, etc. No guarantees are given about the +ordering of edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘adjmatrix’: + The adjacency matrix. How it is interpreted depends on the ‘mode’ + argument. + +‘mode’: + Constant to specify how the given matrix is interpreted as an + adjacency matrix. Possible values (A(i,j) is the element in row i + and column j in the adjacency matrix ‘adjmatrix’): + + ‘IGRAPH_ADJ_DIRECTED’ + The graph will be directed and an element gives the number of + edges between two vertices. + + ‘IGRAPH_ADJ_UNDIRECTED’ + The graph will be undirected and an element gives the number + of edges between two vertices. If the input matrix is not + symmetric, an error is thrown. + + ‘IGRAPH_ADJ_MAX’ + An undirected graph will be created and the number of edges + between vertices i and j is max(A(i,j), A(j,i)). + + ‘IGRAPH_ADJ_MIN’ + An undirected graph will be created with min(A(i,j), A(j,i)) + edges between vertices i and j. + + ‘IGRAPH_ADJ_PLUS’ + An undirected graph will be created with A(i,j)+A(j,i) edges + between vertices i and j. + + ‘IGRAPH_ADJ_UPPER’ + An undirected graph will be created. Only the upper right + triangle (including the diagonal) is used for the number of + edges. + + ‘IGRAPH_ADJ_LOWER’ + An undirected graph will be created. Only the lower left + triangle (including the diagonal) is used for the number of + edges. + +‘loops’: + Constant of type ‘igraph_loops_t’ (*note igraph_loops_t --- How to + interpret self-loops in undirected graphs?::) to specify how the + diagonal of the matrix should be treated when creating loop edges. + Ignored for modes ‘IGRAPH_ADJ_DIRECTED’, ‘IGRAPH_ADJ_UPPER’ and + ‘IGRAPH_ADJ_LOWER’. + + ‘IGRAPH_NO_LOOPS’ + Ignore the diagonal of the input matrix and do not create + loops. + + ‘IGRAPH_LOOPS_ONCE’ + Treat the diagonal entries as the number of loop edges + incident on the corresponding vertex. + + ‘IGRAPH_LOOPS_TWICE’ + Treat the diagonal entries as _twice_ the number of loop edges + incident on the corresponding vertex. Odd numbers in the + diagonal will return an error code. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_EINVAL’: Non-square adjacency matrix, negative + entry in adjacency matrix, or an odd number was found in the + diagonal with ‘IGRAPH_LOOPS_TWICE’ + + Time complexity: O(|V||V|), |V| is the number of vertices in the +graph. + + +File: igraph-docs.info, Node: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_, Next: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_, Prev: igraph_adjacency --- Creates a graph from an adjacency matrix_, Up: Graphs from adjacency matrices and adjacency lists + +11.3.2 igraph_weighted_adjacency -- Creates a graph from a weighted adjacency matrix. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_weighted_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_vector_t *weights, igraph_loops_t loops + ); + + The order of the vertices in the matrix is preserved, i.e. the +vertex corresponding to the first row/column will be vertex with id 0, +the next row is for vertex 1, etc. No guarantees are given for the +ordering of edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘adjmatrix’: + The weighted adjacency matrix. How it is interpreted depends on + the ‘mode’ argument. The common feature is that edges with zero + weights are considered nonexistent (however, negative weights are + permitted). + +‘mode’: + Constant to specify how the given matrix is interpreted as an + adjacency matrix. Possible values (A(i,j) is the element in row i + and column j in the adjacency matrix ‘adjmatrix’): + + ‘IGRAPH_ADJ_DIRECTED’ + The graph will be directed and an element specifies the weight + of the edge between two vertices. + + ‘IGRAPH_ADJ_UNDIRECTED’ + This is the same as ‘IGRAPH_ADJ_MAX’, for convenience. + + ‘IGRAPH_ADJ_MAX’ + An undirected graph will be created and the weight of the edge + between vertices i and j is max(A(i,j), A(j,i)). + + ‘IGRAPH_ADJ_MIN’ + An undirected graph will be created and the weight of the edge + between vertices i and j is min(A(i,j), A(j,i)). + + ‘IGRAPH_ADJ_PLUS’ + An undirected graph will be created and the weight of the edge + between vertices i and j is A(i,j)+A(j,i). + + ‘IGRAPH_ADJ_UPPER’ + An undirected graph will be created. Only the upper right + triangle (including the diagonal) is used for the edge + weights. + + ‘IGRAPH_ADJ_LOWER’ + An undirected graph will be created. Only the lower left + triangle (including the diagonal) is used for the edge + weights. + +‘weights’: + Pointer to an initialized vector, the weights will be stored here. + +‘loops’: + Constant to specify how the diagonal of the matrix should be + treated when creating loop edges. Ignored for modes + ‘IGRAPH_ADJ_DIRECTED’, ‘IGRAPH_ADJ_UPPER’ and ‘IGRAPH_ADJ_LOWER’. + + ‘IGRAPH_NO_LOOPS’ + Ignore the diagonal of the input matrix and do not create + loops. + + ‘IGRAPH_LOOPS_ONCE’ + Treat the diagonal entries as the weight of the loop edge + incident on the corresponding vertex. + + ‘IGRAPH_LOOPS_TWICE’ + Treat the diagonal entries as _twice_ the weight of the loop + edge incident on the corresponding vertex. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_EINVAL’: non-square matrix. + + Time complexity: O(|V||V|), |V| is the number of vertices in the +graph. + + * File examples/simple/igraph_weighted_adjacency.c* + + +File: igraph-docs.info, Node: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_, Next: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_, Prev: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_, Up: Graphs from adjacency matrices and adjacency lists + +11.3.3 igraph_sparse_adjacency -- Creates a graph from a sparse adjacency matrix. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_sparse_adjacency(igraph_t *graph, igraph_sparsemat_t *adjmatrix, + igraph_adjacency_t mode, igraph_loops_t loops); + + This has the same functionality as ‘igraph_adjacency()’ (*note +igraph_adjacency --- Creates a graph from an adjacency matrix_::), but +uses a column-compressed adjacency matrix. + + Time complexity: O(|E|), where |E| is the number of edges in the +graph. + + +File: igraph-docs.info, Node: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_, Next: igraph_adjlist --- Creates a graph from an adjacency list_, Prev: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_, Up: Graphs from adjacency matrices and adjacency lists + +11.3.4 igraph_sparse_weighted_adjacency -- Creates a graph from a weighted sparse adjacency matrix. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_sparse_weighted_adjacency( + igraph_t *graph, igraph_sparsemat_t *adjmatrix, igraph_adjacency_t mode, + igraph_vector_t *weights, igraph_loops_t loops + ); + + This has the same functionality as ‘igraph_weighted_adjacency()’ +(*note igraph_weighted_adjacency --- Creates a graph from a weighted +adjacency matrix_::), but uses a column-compressed adjacency matrix. + + Time complexity: O(|E|), where |E| is the number of edges in the +graph. + + +File: igraph-docs.info, Node: igraph_adjlist --- Creates a graph from an adjacency list_, Prev: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_, Up: Graphs from adjacency matrices and adjacency lists + +11.3.5 igraph_adjlist -- Creates a graph from an adjacency list. +---------------------------------------------------------------- + + + igraph_error_t igraph_adjlist(igraph_t *graph, const igraph_adjlist_t *adjlist, + igraph_neimode_t mode, igraph_bool_t duplicate); + + An adjacency list is a list of vectors, containing the neighbors of +all vertices. For operations that involve many changes to the graph +structure, it is recommended that you convert the graph into an +adjacency list via ‘igraph_adjlist_init()’ (*note igraph_adjlist_init +--- Constructs an adjacency list of vertices from a given graph_::), +perform the modifications (these are cheap for an adjacency list) and +then recreate the igraph graph via this function. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘adjlist’: + The adjacency list. + +‘mode’: + Whether or not to create a directed graph. ‘IGRAPH_ALL’ means an + undirected graph, ‘IGRAPH_OUT’ means a directed graph from an + out-adjacency list (i.e. each list contains the successors of the + corresponding vertices), ‘IGRAPH_IN’ means a directed graph from an + in-adjacency list + +‘duplicate’: + Boolean constant. For undirected graphs this specifies whether + each edge is included twice, in the vectors of both adjacent + vertices. If this is ‘false’, then it is assumed that every edge + is included only once. This argument is ignored for directed + graphs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_adjlist_init()’ (*note igraph_adjlist_init --- Constructs + an adjacency list of vertices from a given graph_::) for the + opposite operation. + + Time complexity: O(|V|+|E|). + + +File: igraph-docs.info, Node: Regular structures, Next: Tree generators, Prev: Graphs from adjacency matrices and adjacency lists, Up: Deterministic graph generators + +11.4 Regular structures +======================= + +These functions produce various basic regular graph structures, such as +paths, cycles or lattices. + +* Menu: + +* igraph_star -- Creates a star graph, every vertex connects only to the center.: igraph_star --- Creates a star graph; every vertex connects only to the center_. +* igraph_wheel -- Creates a wheel graph, a union of a star and a cycle graph.: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_. +* igraph_hypercube -- The n-dimensional hypercube graph.: igraph_hypercube --- The n-dimensional hypercube graph_. +* igraph_square_lattice -- Arbitrary dimensional square lattices.: igraph_square_lattice --- Arbitrary dimensional square lattices_. +* igraph_triangular_lattice -- A triangular lattice with the given shape.: igraph_triangular_lattice --- A triangular lattice with the given shape_. +* igraph_hexagonal_lattice -- A hexagonal lattice with the given shape.: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_. +* igraph_ring -- Creates a cycle graph or a path graph.: igraph_ring --- Creates a cycle graph or a path graph_. +* igraph_path_graph -- A path graph P_n.: igraph_path_graph --- A path graph P_n_. +* igraph_cycle_graph -- A cycle graph C_n.: igraph_cycle_graph --- A cycle graph C_n_. +* igraph_lcf -- Creates a graph from LCF notation.: igraph_lcf --- Creates a graph from LCF notation_. +* igraph_lcf_small -- Shorthand to create a graph from LCF notation, giving shifts as the arguments.: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_. +* igraph_circulant -- Creates a circulant graph.: igraph_circulant --- Creates a circulant graph_. +* igraph_extended_chordal_ring -- Create an extended chordal ring.: igraph_extended_chordal_ring --- Create an extended chordal ring_. + + +File: igraph-docs.info, Node: igraph_star --- Creates a star graph; every vertex connects only to the center_, Next: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_, Up: Regular structures + +11.4.1 igraph_star -- Creates a star graph, every vertex connects only to the center. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_star(igraph_t *graph, igraph_int_t n, igraph_star_mode_t mode, + igraph_int_t center); + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, this will be the result. + +‘n’: + Integer constant, the number of vertices in the graph. + +‘mode’: + Constant, gives the type of the star graph to create. Possible + values: + + ‘IGRAPH_STAR_OUT’ + directed star graph, edges point _from_ the center to the + other vertices. + + ‘IGRAPH_STAR_IN’ + directed star graph, edges point _to_ the center from the + other vertices. + + ‘IGRAPH_STAR_MUTUAL’ + directed star graph with mutual edges. + + ‘IGRAPH_STAR_UNDIRECTED’ + an undirected star graph is created. + +‘center’: + Id of the vertex which will be the center of the graph. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_EINVVID’ + invalid number of vertices. + + ‘IGRAPH_EINVAL’ + invalid center vertex. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|V|), the number of vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_wheel()’ (*note igraph_wheel --- Creates a wheel graph; a + union of a star and a cycle graph_::), ‘igraph_square_lattice()’ + (*note igraph_square_lattice --- Arbitrary dimensional square + lattices_::), ‘igraph_ring()’ (*note igraph_ring --- Creates a + cycle graph or a path graph_::), ‘igraph_kary_tree()’ (*note + igraph_kary_tree --- Creates a k-ary tree in which almost all + vertices have k children_::) for creating other regular structures. + + * File examples/simple/igraph_star.c* + + +File: igraph-docs.info, Node: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_, Next: igraph_hypercube --- The n-dimensional hypercube graph_, Prev: igraph_star --- Creates a star graph; every vertex connects only to the center_, Up: Regular structures + +11.4.2 igraph_wheel -- Creates a wheel graph, a union of a star and a cycle graph. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_wheel(igraph_t *graph, igraph_int_t n, igraph_wheel_mode_t mode, + igraph_int_t center); + + A wheel graph on ‘n’ vertices can be thought of as a wheel with ‘n - +1’ spokes. The cycle graph part makes up the rim, while the star graph +part adds the spokes. + + Note that the two and three-vertex wheel graphs are non-simple: The +two-vertex wheel graph contains a self-loop, while the three-vertex +wheel graph contains parallel edges (a 1-cycle and a 2-cycle, +respectively). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, this will be the result. + +‘n’: + Integer constant, the number of vertices in the graph. + +‘mode’: + Constant, gives the type of the star graph to create. Possible + values: + + ‘IGRAPH_WHEEL_OUT’ + directed wheel graph, edges point _from_ the center to the + other vertices. + + ‘IGRAPH_WHEEL_IN’ + directed wheel graph, edges point _to_ the center from the + other vertices. + + ‘IGRAPH_WHEEL_MUTUAL’ + directed wheel graph with mutual edges. + + ‘IGRAPH_WHEEL_UNDIRECTED’ + an undirected wheel graph is created. + +‘center’: + Id of the vertex which will be the center of the graph. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_EINVVID’ + invalid number of vertices. + + ‘IGRAPH_EINVAL’ + invalid center vertex. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|V|), the number of vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::), ‘igraph_ring()’ (*note + igraph_ring --- Creates a cycle graph or a path graph_::), + ‘igraph_star()’ (*note igraph_star --- Creates a star graph; every + vertex connects only to the center_::), ‘igraph_kary_tree()’ (*note + igraph_kary_tree --- Creates a k-ary tree in which almost all + vertices have k children_::) for creating other regular structures. + + +File: igraph-docs.info, Node: igraph_hypercube --- The n-dimensional hypercube graph_, Next: igraph_square_lattice --- Arbitrary dimensional square lattices_, Prev: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_, Up: Regular structures + +11.4.3 igraph_hypercube -- The n-dimensional hypercube graph. +------------------------------------------------------------- + + + igraph_error_t igraph_hypercube(igraph_t *graph, + igraph_int_t n, igraph_bool_t directed); + + The hypercube graph ‘Q_n’ has ‘2^n’ vertices and ‘2^(n-1) n’ edges. +Two vertices are connected when the binary representations of their +zero-based vertex IDs differs in precisely one bit. + + *Arguments:. * + +‘graph’: + An uninitialized graph object. + +‘n’: + The dimension of the hypercube graph. + +‘directed’: + Whether the graph should be directed. Edges will point from lower + index vertices towards higher index ones. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::) + + Time complexity: O(2^n) + + +File: igraph-docs.info, Node: igraph_square_lattice --- Arbitrary dimensional square lattices_, Next: igraph_triangular_lattice --- A triangular lattice with the given shape_, Prev: igraph_hypercube --- The n-dimensional hypercube graph_, Up: Regular structures + +11.4.4 igraph_square_lattice -- Arbitrary dimensional square lattices. +---------------------------------------------------------------------- + + + igraph_error_t igraph_square_lattice( + igraph_t *graph, const igraph_vector_int_t *dimvector, igraph_int_t nei, + igraph_bool_t directed, igraph_bool_t mutual, const igraph_vector_bool_t *periodic + ); + + Creates d-dimensional square lattices of the given size. Optionally, +the lattice can be made periodic, and the neighbors within a given graph +distance can be connected. + + In the zero-dimensional case, the singleton graph is returned. + + The vertices of the resulting graph are ordered such that the index +of the vertex at position ‘(i_1, i_2, i_3, ..., i_d)’ in a lattice of +size ‘(n_1, n_2, ..., n_d)’ will be ‘i_1 + n_1 * i_2 + n_1 * n_2 * i_3 + +...’. + + *Arguments:. * + +‘graph’: + An uninitialized graph object. + +‘dimvector’: + Vector giving the sizes of the lattice in each of its dimensions. + The dimension of the lattice will be the same as the length of this + vector. + +‘nei’: + Integer value giving the distance (number of steps) within which + two vertices will be connected. + +‘directed’: + Boolean, whether to create a directed graph. If the ‘mutual’ and + ‘circular’ arguments are not set to true, edges will be directed + from lower-index vertices towards higher-index ones. + +‘mutual’: + Boolean, if the graph is directed this gives whether to create all + connections as mutual. + +‘periodic’: + Boolean vector, defines whether the generated lattice is periodic + along each dimension. The length of this vector must match the + length of ‘dimvector’. This parameter may also be ‘NULL’, which + implies that the lattice will not be periodic. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid (negative) dimension vector or + mismatch between the length of the dimension vector and the + periodicity vector. + + *See also:. * + +‘’ + ‘igraph_hypercube()’ (*note igraph_hypercube --- The n-dimensional + hypercube graph_::) to create a hypercube graph; ‘igraph_ring()’ + (*note igraph_ring --- Creates a cycle graph or a path graph_::) to + create a cycle graph or path graph; ‘igraph_triangular_lattice()’ + (*note igraph_triangular_lattice --- A triangular lattice with the + given shape_::) and ‘igraph_hexagonal_lattice()’ (*note + igraph_hexagonal_lattice --- A hexagonal lattice with the given + shape_::) to create other types of lattices; + ‘igraph_regular_tree()’ (*note igraph_regular_tree --- Creates a + regular tree_::) to create a Bethe lattice. + + Time complexity: If ‘nei’ is less than two then it is O(|V|+|E|) (as +far as I remember), |V| and |E| are the number of vertices and edges in +the generated graph. Otherwise it is O(|V|*d^k+|E|), d is the average +degree of the graph, k is the ‘nei’ argument. + + +File: igraph-docs.info, Node: igraph_triangular_lattice --- A triangular lattice with the given shape_, Next: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_, Prev: igraph_square_lattice --- Arbitrary dimensional square lattices_, Up: Regular structures + +11.4.5 igraph_triangular_lattice -- A triangular lattice with the given shape. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_triangular_lattice( + igraph_t *graph, const igraph_vector_int_t *dims, + igraph_bool_t directed, igraph_bool_t mutual); + + Creates a triangular lattice whose vertices have the form (i, j) for +non-negative integers i and j and (i, j) is generally connected with (i ++ 1, j), (i, j + 1), and (i - 1, j + 1). The function constructs a +planar dual of the graph constructed by ‘igraph_hexagonal_lattice()’ +(*note igraph_hexagonal_lattice --- A hexagonal lattice with the given +shape_::). In particular, there a one-to-one correspondence between the +vertices in the constructed graph and the cycles of length 6 in the +graph constructed by ‘igraph_hexagonal_lattice()’ (*note +igraph_hexagonal_lattice --- A hexagonal lattice with the given +shape_::) with the same ‘dims’ parameter. + + The vertices of the resulting graph are ordered lexicographically +with the 2nd coordinate being more significant, e.g., (i, j) < (i + 1, +j) and (i + 1, j) < (i, j + 1) + + *Arguments:. * + +‘graph’: + An uninitialized graph object. + +‘dims’: + Integer vector, defines the shape of the lattice. If ‘dims’ is of + length 1, the resulting lattice has a triangular shape where each + side of the triangle contains ‘dims[0]’ vertices. If ‘dims’ is of + length 2, the resulting lattice has a "quasi rectangular" shape + with the sides containing ‘dims[0]’ and ‘dims[1]’ vertices, + respectively. If ‘dims’ is of length 3, the resulting lattice has + a hexagonal shape where the sides of the hexagon contain ‘dims[0]’, + ‘dims[1]’ and ‘dims[2]’ vertices. All dimensions must be + non-negative. + +‘directed’: + Boolean, whether to create a directed graph. If the ‘mutual’ + argument is not set to true, edges will be directed from + lower-index vertices towards higher-index ones. + +‘mutual’: + Boolean, if the graph is directed this gives whether to create all + connections as mutual. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: The size of ‘dims’ must be either 1, + 2, or 3 with all the components at least 1. + + *See also:. * + +‘’ + ‘igraph_hexagonal_lattice()’ (*note igraph_hexagonal_lattice --- A + hexagonal lattice with the given shape_::) and + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::) for creating other types + of lattices; ‘igraph_regular_tree()’ (*note igraph_regular_tree --- + Creates a regular tree_::) to create a Bethe lattice. + + Time complexity: O(|V|), where |V| is the number of vertices in the +generated graph. + + +File: igraph-docs.info, Node: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_, Next: igraph_ring --- Creates a cycle graph or a path graph_, Prev: igraph_triangular_lattice --- A triangular lattice with the given shape_, Up: Regular structures + +11.4.6 igraph_hexagonal_lattice -- A hexagonal lattice with the given shape. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_hexagonal_lattice( + igraph_t *graph, const igraph_vector_int_t *dims, + igraph_bool_t directed, igraph_bool_t mutual); + + Creates a hexagonal lattice whose vertices have the form (i, j) for +non-negative integers i and j and (i, j) is generally connected with (i ++ 1, j), and if i is odd also with (i - 1, j + 1). The function +constructs a planar dual of the graph constructed by +‘igraph_triangular_lattice()’ (*note igraph_triangular_lattice --- A +triangular lattice with the given shape_::). In particular, there a +one-to-one correspondence between the cycles of length 6 in the +constructed graph and the vertices of the graph constructed by +‘igraph_triangular_lattice()’ (*note igraph_triangular_lattice --- A +triangular lattice with the given shape_::) function with the same +‘dims’ parameter. + + The vertices of the resulting graph are ordered lexicographically +with the 2nd coordinate being more significant, e.g., (i, j) < (i + 1, +j) and (i + 1, j) < (i, j + 1) + + *Arguments:. * + +‘graph’: + An uninitialized graph object. + +‘dims’: + Integer vector, defines the shape of the lattice. If ‘dims’ is of + length 1, the resulting lattice has a triangular shape where each + side of the triangle contains ‘dims[0]’ vertices. If ‘dims’ is of + length 2, the resulting lattice has a "quasi rectangular" shape + with the sides containing ‘dims[0]’ and ‘dims[1]’ vertices, + respectively. If ‘dims’ is of length 3, the resulting lattice has + a hexagonal shape where the sides of the hexagon contain ‘dims[0]’, + ‘dims[1]’ and ‘dims[2]’ vertices. All coordinates must be + non-negative. + +‘directed’: + Boolean, whether to create a directed graph. If the ‘mutual’ + argument is not set to true, edges will be directed from + lower-index vertices towards higher-index ones. + +‘mutual’: + Boolean, if the graph is directed this gives whether to create all + connections as mutual. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: The size of ‘dims’ must be either 1, + 2, or 3 with all the components at least 1. + + *See also:. * + +‘’ + ‘igraph_triangular_lattice()’ (*note igraph_triangular_lattice --- + A triangular lattice with the given shape_::) and + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::) for creating other types + of lattices; ; ‘igraph_regular_tree()’ (*note igraph_regular_tree + --- Creates a regular tree_::) to create a Bethe lattice. + + Time complexity: O(|V|), where |V| is the number of vertices in the +generated graph. + + +File: igraph-docs.info, Node: igraph_ring --- Creates a cycle graph or a path graph_, Next: igraph_path_graph --- A path graph P_n_, Prev: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_, Up: Regular structures + +11.4.7 igraph_ring -- Creates a cycle graph or a path graph. +------------------------------------------------------------ + + + igraph_error_t igraph_ring(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_bool_t mutual, igraph_bool_t circular); + + A circular ring on ‘n’ vertices is commonly known in graph theory as +the cycle graph, and often denoted by ‘C_n’. Removing a single edge +from the cycle graph ‘C_n’ results in the path graph ‘P_n’. This +function can generate both. + + When ‘n’ is 1 or 2, the result may not be a simple graph: the +one-cycle contains a self-loop and the undirected or reciprocally +connected directed two-cycle contains parallel edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘directed’: + Whether to create a directed graph. All edges will be oriented in + the same direction along the cycle or path. + +‘mutual’: + Whether to create mutual edges in directed graphs. It is ignored + for undirected graphs. + +‘circular’: + Whether to create a closed ring (a cycle) or an open path. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of vertices. + + Time complexity: O(|V|), the number of vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::) for generating more + general (periodic or non-periodic) lattices. + + * File examples/simple/igraph_ring.c* + + +File: igraph-docs.info, Node: igraph_path_graph --- A path graph P_n_, Next: igraph_cycle_graph --- A cycle graph C_n_, Prev: igraph_ring --- Creates a cycle graph or a path graph_, Up: Regular structures + +11.4.8 igraph_path_graph -- A path graph P_n. +--------------------------------------------- + + + igraph_error_t igraph_path_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual); + + Creates the path graph ‘P_n’ on ‘n’ vertices. + + This is a convenience wrapper to ‘igraph_ring()’ (*note igraph_ring +--- Creates a cycle graph or a path graph_::). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘directed’: + Whether to create a directed graph. + +‘mutual’: + Whether to create mutual edges in directed graphs. It is ignored + for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_cycle_graph --- A cycle graph C_n_, Next: igraph_lcf --- Creates a graph from LCF notation_, Prev: igraph_path_graph --- A path graph P_n_, Up: Regular structures + +11.4.9 igraph_cycle_graph -- A cycle graph C_n. +----------------------------------------------- + + + igraph_error_t igraph_cycle_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual); + + Creates the cycle graph ‘C_n’ on ‘n’ vertices. + + When ‘n’ is 1 or 2, the result may not be a simple graph: the +one-cycle contains a self-loop and the undirected or reciprocally +connected directed two-cycle contains parallel edges. + + This is a convenience wrapper to ‘igraph_ring()’ (*note igraph_ring +--- Creates a cycle graph or a path graph_::). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘directed’: + Whether to create a directed graph. + +‘mutual’: + Whether to create mutual edges in directed graphs. It is ignored + for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_lcf --- Creates a graph from LCF notation_, Next: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_, Prev: igraph_cycle_graph --- A cycle graph C_n_, Up: Regular structures + +11.4.10 igraph_lcf -- Creates a graph from LCF notation. +-------------------------------------------------------- + + + igraph_error_t igraph_lcf(igraph_t *graph, igraph_int_t n, + const igraph_vector_int_t *shifts, + igraph_int_t repeats); + + LCF notation (named after Lederberg, Coxeter and Frucht) is a concise +notation for 3-regular Hamiltonian graphs. It consists of three +parameters: the number of vertices in the graph, a list of shifts giving +additional edges to a cycle backbone, and another integer giving how +many times the shifts should be performed. See +https://mathworld.wolfram.com/LCFNotation.html +(https://mathworld.wolfram.com/LCFNotation.html) for details. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + Integer constant giving the number of vertices. This is normally + set to the number of shifts multiplied by the number of repeats. + +‘shifts’: + An integer vector giving the shifts. + +‘repeats’: + The number of repeats for the shifts. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_lcf_small()’ (*note igraph_lcf_small --- Shorthand to + create a graph from LCF notation; giving shifts as the + arguments_::), ‘igraph_extended_chordal_ring()’ (*note + igraph_extended_chordal_ring --- Create an extended chordal + ring_::) + + Time complexity: O(|V|+|E|), linear in the number of vertices plus +the number of edges. + + +File: igraph-docs.info, Node: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_, Next: igraph_circulant --- Creates a circulant graph_, Prev: igraph_lcf --- Creates a graph from LCF notation_, Up: Regular structures + +11.4.11 igraph_lcf_small -- Shorthand to create a graph from LCF notation, giving shifts as the arguments. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_lcf_small(igraph_t *graph, igraph_int_t n, ...); + + This function provides a shorthand to give the shifts of the LCF +notation directly as function arguments. See ‘igraph_lcf()’ (*note +igraph_lcf --- Creates a graph from LCF notation_::) for an explanation +of LCF notation. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + Integer, the number of vertices in the graph. + +‘...’: + The shifts and the number of repeats for the shifts, plus an + additional 0 to mark the end of the arguments. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + See ‘igraph_lcf()’ (*note igraph_lcf --- Creates a graph from LCF + notation_::) for a similar function using an ‘igraph_vector_t’ + (*note About igraph_vector_t objects::) instead of the variable + length argument list; ‘igraph_circulant()’ (*note igraph_circulant + --- Creates a circulant graph_::) to create circulant graphs. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + * File examples/simple/igraph_lcf.c* + + +File: igraph-docs.info, Node: igraph_circulant --- Creates a circulant graph_, Next: igraph_extended_chordal_ring --- Create an extended chordal ring_, Prev: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_, Up: Regular structures + +11.4.12 igraph_circulant -- Creates a circulant graph. +------------------------------------------------------ + + + igraph_error_t igraph_circulant(igraph_t *graph, igraph_int_t n, const igraph_vector_int_t *shifts, igraph_bool_t directed); + + A circulant graph ‘G(n, shifts)’ consists of ‘n’ vertices ‘v_0’, ..., +‘v_(n-1)’ such that for each ‘s_i’ in the list of offsets ‘shifts’, +‘v_j’ is connected to ‘v_((j + s_i) mod n)’ for all j. + + The function can generate either directed or undirected graphs. It +does not generate multi-edges or self-loops. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘n’: + Integer, the number of vertices in the circulant graph. + +‘shifts’: + Integer vector, a list of the offsets within the circulant graph. + +‘directed’: + Boolean, whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ring()’ (*note igraph_ring --- Creates a cycle graph or a + path graph_::), ‘igraph_generalized_petersen()’ (*note + igraph_generalized_petersen --- Creates a Generalized Petersen + graph_::), ‘igraph_extended_chordal_ring()’ (*note + igraph_extended_chordal_ring --- Create an extended chordal + ring_::), ‘igraph_lcf()’ (*note igraph_lcf --- Creates a graph from + LCF notation_::) + + Time complexity: O(|V| |shifts|), the number of vertices in the graph +times the number of shifts. + + +File: igraph-docs.info, Node: igraph_extended_chordal_ring --- Create an extended chordal ring_, Prev: igraph_circulant --- Creates a circulant graph_, Up: Regular structures + +11.4.13 igraph_extended_chordal_ring -- Create an extended chordal ring. +------------------------------------------------------------------------ + + + igraph_error_t igraph_extended_chordal_ring( + igraph_t *graph, igraph_int_t nodes, const igraph_matrix_int_t *W, + igraph_bool_t directed); + + An extended chordal ring is a cycle graph with additional chords +connecting its vertices. Each row ‘L’ of the matrix ‘W’ specifies a set +of chords to be inserted, in the following way: vertex ‘i’ will connect +to a vertex ‘L[(i mod p)]’ steps ahead of it along the cycle, where ‘p’ +is the length of ‘L’. In other words, vertex ‘i’ will be connected to +vertex ‘(i + L[(i mod p)]) mod nodes’. If multiple edges are defined in +this way, this will output a non-simple graph. The result can be +simplified using ‘igraph_simplify()’ (*note igraph_simplify --- Removes +loop and/or multiple edges from the graph_::). + + See also Kotsis, G: Interconnection Topologies for Parallel +Processing Systems, PARS Mitteilungen 11, 1-6, 1993. The igraph +extended chordal rings are not identical to the ones in the paper. In +igraph the matrix specifies which edges to add. In the paper, a +condition is specified which should simultaneously hold between two +endpoints and the reverse endpoints. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘nodes’: + Integer constant, the number of vertices in the graph. It must be + at least 3. + +‘W’: + The matrix specifying the extra edges. The number of columns + should divide the number of total vertices. The elements are + allowed to be negative. + +‘directed’: + Whether the graph should be directed. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_ring()’ (*note igraph_ring --- Creates a cycle graph or a + path graph_::), ‘igraph_lcf()’ (*note igraph_lcf --- Creates a + graph from LCF notation_::), ‘igraph_circulant()’ (*note + igraph_circulant --- Creates a circulant graph_::) + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: Tree generators, Next: Graphs with given degrees, Prev: Regular structures, Up: Deterministic graph generators + +11.5 Tree generators +==================== + +These functions generate tree graphs. + +* Menu: + +* igraph_kary_tree -- Creates a k-ary tree in which almost all vertices have k children.: igraph_kary_tree --- Creates a k-ary tree in which almost all vertices have k children_. +* igraph_symmetric_tree -- Creates a symmetric tree with the specified number of branches at each level.: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_. +* igraph_regular_tree -- Creates a regular tree.: igraph_regular_tree --- Creates a regular tree_. +* igraph_tree_from_parent_vector -- Constructs a tree or forest from a vector encoding the parent of each vertex.: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_. +* igraph_from_prufer -- Generates a tree from a Prüfer sequence.: igraph_from_prufer --- Generates a tree from a Prüfer sequence_. + + +File: igraph-docs.info, Node: igraph_kary_tree --- Creates a k-ary tree in which almost all vertices have k children_, Next: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_, Up: Tree generators + +11.5.1 igraph_kary_tree -- Creates a k-ary tree in which almost all vertices have k children. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_kary_tree(igraph_t *graph, igraph_int_t n, igraph_int_t children, + igraph_tree_mode_t type); + + To obtain a completely symmetric tree with ‘l’ layers, where each +vertex has precisely ‘children’ descendants, use ‘n = (children^(l+1) - +1) / (children - 1)’. Such trees are often called ‘k’-ary trees, where +‘k’ refers to the number of children. + + Note that for ‘n=0’, the null graph is returned, which is not +considered to be a tree by ‘igraph_is_tree()’ (*note igraph_is_tree --- +Decides whether the graph is a tree_::). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + Integer, the number of vertices in the graph. + +‘children’: + Integer, the number of children of a vertex in the tree. + +‘type’: + Constant, gives whether to create a directed tree, and if this is + the case, also its orientation. Possible values: + + ‘IGRAPH_TREE_OUT’ + directed tree, the edges point from the parents to their + children. + + ‘IGRAPH_TREE_IN’ + directed tree, the edges point from the children to their + parents. + + ‘IGRAPH_TREE_UNDIRECTED’ + undirected tree. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of vertices. + ‘IGRAPH_INVMODE’: invalid mode argument. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_regular_tree()’ (*note igraph_regular_tree --- Creates a + regular tree_::), ‘igraph_symmetric_tree()’ (*note + igraph_symmetric_tree --- Creates a symmetric tree with the + specified number of branches at each level_::) and ‘igraph_star()’ + (*note igraph_star --- Creates a star graph; every vertex connects + only to the center_::) for creating other regular structures; + ‘igraph_from_prufer()’ (*note igraph_from_prufer --- Generates a + tree from a Prüfer sequence_::) and + ‘igraph_tree_from_parent_vector()’ (*note + igraph_tree_from_parent_vector --- Constructs a tree or forest from + a vector encoding the parent of each vertex_::) for creating + arbitrary trees; ‘igraph_tree_game()’ (*note igraph_tree_game --- + Generates a random tree with the given number of nodes_::) for + uniform random sampling of trees; + ‘igraph_realize_degree_sequence()’ (*note + igraph_realize_degree_sequence --- Generates a graph with the given + degree sequence_::) with ‘IGRAPH_REALIZE_DEGSEQ_SMALLEST’ to create + a tree with given degrees. + + * File examples/simple/igraph_kary_tree.c* + + +File: igraph-docs.info, Node: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_, Next: igraph_regular_tree --- Creates a regular tree_, Prev: igraph_kary_tree --- Creates a k-ary tree in which almost all vertices have k children_, Up: Tree generators + +11.5.2 igraph_symmetric_tree -- Creates a symmetric tree with the specified number of branches at each level. +------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_symmetric_tree(igraph_t *graph, const igraph_vector_int_t *branches, + igraph_tree_mode_t type); + + This function creates a tree in which all vertices at distance ‘d’ +from the root have ‘branching_counts’[d] children. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘branches’: + Vector detailing the number of branches at each level. + +‘type’: + Constant, gives whether to create a directed tree, and if this is + the case, also its orientation. Possible values: + + ‘IGRAPH_TREE_OUT’ + directed tree, the edges point from the parents to their + children. + + ‘IGRAPH_TREE_IN’ + directed tree, the edges point from the children to their + parents. + + ‘IGRAPH_TREE_UNDIRECTED’ + undirected tree. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_INVMODE’: invalid mode argument. + ‘IGRAPH_EINVAL’: invalid number of children. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_kary_tree()’ (*note igraph_kary_tree --- Creates a k-ary + tree in which almost all vertices have k children_::), + ‘igraph_regular_tree()’ (*note igraph_regular_tree --- Creates a + regular tree_::) and ‘igraph_star()’ (*note igraph_star --- Creates + a star graph; every vertex connects only to the center_::) for + creating other regular tree structures; ‘igraph_from_prufer()’ + (*note igraph_from_prufer --- Generates a tree from a Prüfer + sequence_::) for creating arbitrary trees; ‘igraph_tree_game()’ + (*note igraph_tree_game --- Generates a random tree with the given + number of nodes_::) for uniform random sampling of trees. + + * File examples/simple/igraph_symmetric_tree.c* + + +File: igraph-docs.info, Node: igraph_regular_tree --- Creates a regular tree_, Next: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_, Prev: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_, Up: Tree generators + +11.5.3 igraph_regular_tree -- Creates a regular tree. +----------------------------------------------------- + + + igraph_error_t igraph_regular_tree(igraph_t *graph, igraph_int_t h, igraph_int_t k, igraph_tree_mode_t type); + + All vertices of a regular tree, except its leaves, have the same +total degree ‘k’. This is different from a k-ary tree +(‘igraph_kary_tree()’ (*note igraph_kary_tree --- Creates a k-ary tree +in which almost all vertices have k children_::)), where all vertices +have the same number of children, thus the degre of the root is one less +than the degree of the other internal vertices. Regular trees are also +referred to as Bethe lattices. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘h’: + The height of the tree, i.e. the distance between the root and the + leaves. + +‘k’: + The degree of the regular tree. + +‘type’: + Constant, gives whether to create a directed tree, and if this is + the case, also its orientation. Possible values: + + ‘IGRAPH_TREE_OUT’ + directed tree, the edges point from the parents to their + children. + + ‘IGRAPH_TREE_IN’ + directed tree, the edges point from the children to their + parents. + + ‘IGRAPH_TREE_UNDIRECTED’ + undirected tree. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_kary_tree()’ (*note igraph_kary_tree --- Creates a k-ary + tree in which almost all vertices have k children_::) to create + k-ary tree where each vertex has the same number of children, i.e. + out-degree, instead of the same total degree. + ‘igraph_symmetric_tree()’ (*note igraph_symmetric_tree --- Creates + a symmetric tree with the specified number of branches at each + level_::) to use a different number of children at each level. + + * File examples/simple/igraph_regular_tree.c* + + +File: igraph-docs.info, Node: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_, Next: igraph_from_prufer --- Generates a tree from a Prüfer sequence_, Prev: igraph_regular_tree --- Creates a regular tree_, Up: Tree generators + +11.5.4 igraph_tree_from_parent_vector -- Constructs a tree or forest from a vector encoding the parent of each vertex. +---------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_tree_from_parent_vector( + igraph_t *graph, + const igraph_vector_int_t *parents, + igraph_tree_mode_t type); + + Rooted trees and forests are conveniently represented using a +‘parents’ vector where the ID of the parent of vertex ‘v’ is stored in +‘parents[v]’. This function serves to construct an igraph graph from a +parent vector representation. The result is guaranteed to be a forest +or a tree. If the ‘parents’ vector is found to encode a cycle or a +self-loop, an error is raised. + + Several igraph functions produce such vectors, such as graph +traversal functions (‘igraph_bfs()’ (*note igraph_bfs --- Breadth-first +search_::) and ‘igraph_dfs()’ (*note igraph_dfs --- Depth-first +search_::)), shortest path functions that construct a shortest path +tree, as well as some other specialized functions like +‘igraph_dominator_tree()’ (*note igraph_dominator_tree --- Calculates +the dominator tree of a flowgraph_::) or ‘igraph_cohesive_blocks()’ +(*note igraph_cohesive_blocks --- Identifies the hierarchical cohesive +block structure of a graph_::). Vertices which do not have parents +(i.e. roots) get a negative entry in the ‘parents’ vector. + + Use ‘igraph_bfs()’ (*note igraph_bfs --- Breadth-first search_::) or +‘igraph_dfs()’ (*note igraph_dfs --- Depth-first search_::) to convert a +forest into a parent vector representation. For trees, i.e. forests +with a single root, it is more convenient to use ‘igraph_bfs_simple()’ +(*note igraph_bfs_simple --- Breadth-first search; single-source +version::). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘parents’: + The parent vector. ‘parents[v]’ is the ID of the parent vertex of + ‘v’. ‘parents[v] < 0’ indicates that ‘v’ does not have a parent. + +‘type’: + Constant, gives whether to create a directed tree, and if this is + the case, also its orientation. Possible values: + + ‘IGRAPH_TREE_OUT’ + directed tree, the edges point from the parents to their + children. + + ‘IGRAPH_TREE_IN’ + directed tree, the edges point from the children to their + parents. + + ‘IGRAPH_TREE_UNDIRECTED undirected tree.’ + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_bfs()’ (*note igraph_bfs --- Breadth-first search_::), + ‘igraph_bfs_simple()’ (*note igraph_bfs_simple --- Breadth-first + search; single-source version::) for back-conversion; + ‘igraph_from_prufer()’ (*note igraph_from_prufer --- Generates a + tree from a Prüfer sequence_::) for creating trees from Prüfer + sequences; ‘igraph_is_tree()’ (*note igraph_is_tree --- Decides + whether the graph is a tree_::) and ‘igraph_is_forest()’ (*note + igraph_is_forest --- Decides whether the graph is a forest_::) to + check if a graph is a tree or forest. + + Time complexity: O(n) where n is the length of ‘parents’. + + +File: igraph-docs.info, Node: igraph_from_prufer --- Generates a tree from a Prüfer sequence_, Prev: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_, Up: Tree generators + +11.5.5 igraph_from_prufer -- Generates a tree from a Prüfer sequence. +--------------------------------------------------------------------- + + + igraph_error_t igraph_from_prufer(igraph_t *graph, const igraph_vector_int_t *prufer); + + A Prüfer sequence is a unique sequence of integers associated with a +labelled tree. A tree on ‘n’ vertices can be represented by a sequence +of ‘n-2’ integers, each between ‘0’ and ‘n-1’ (inclusive). The +algorithm used by this function is based on Paulius Micikevičius, +Saverio Caminiti, Narsingh Deo: Linear-time Algorithms for Encoding +Trees as Sequences of Node Labels + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘prufer’: + The Prüfer sequence + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + there is not enough memory to perform the operation. + + ‘IGRAPH_EINVAL’ + invalid Prüfer sequence given + + *See also:. * + +‘’ + ‘igraph_to_prufer()’ (*note igraph_to_prufer --- Converts a tree to + its Prüfer sequence_::), ‘igraph_kary_tree()’ (*note + igraph_kary_tree --- Creates a k-ary tree in which almost all + vertices have k children_::), ‘igraph_tree_game()’ (*note + igraph_tree_game --- Generates a random tree with the given number + of nodes_::) + + Time complexity: O(|V|), where |V| is the number of vertices in the +tree. + + +File: igraph-docs.info, Node: Graphs with given degrees, Next: Complete graphs, Prev: Tree generators, Up: Deterministic graph generators + +11.6 Graphs with given degrees +============================== + +These functions generate graphs with the specified degrees. + +* Menu: + +* igraph_realize_degree_sequence -- Generates a graph with the given degree sequence.: igraph_realize_degree_sequence --- Generates a graph with the given degree sequence_. +* igraph_realize_bipartite_degree_sequence -- Generates a bipartite graph with the given bidegree sequence.: igraph_realize_bipartite_degree_sequence --- Generates a bipartite graph with the given bidegree sequence_. + + +File: igraph-docs.info, Node: igraph_realize_degree_sequence --- Generates a graph with the given degree sequence_, Next: igraph_realize_bipartite_degree_sequence --- Generates a bipartite graph with the given bidegree sequence_, Up: Graphs with given degrees + +11.6.1 igraph_realize_degree_sequence -- Generates a graph with the given degree sequence. +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_realize_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, + igraph_edge_type_sw_t allowed_edge_types, + igraph_realize_degseq_t method); + + This function generates an undirected graph that realizes a given +degree sequence, or a directed graph that realizes a given pair of out- +and in-degree sequences. + + Simple undirected graphs are constructed using the Havel-Hakimi +algorithm (undirected case), or the analogous Kleitman-Wang algorithm +(directed case). These algorithms work by choosing an arbitrary vertex +and connecting all its stubs to other vertices of highest degree. In +the directed case, the "highest" (in, out) degree pairs are determined +based on lexicographic ordering. This step is repeated until all +degrees have been connected up. + + Loopless multigraphs are generated using an analogous algorithm: an +arbitrary vertex is chosen, and it is connected with a single connection +to a highest remaining degee vertex. If self-loops are also allowed, +the same algorithm is used, but if a non-zero vertex remains at the end +of the procedure, the graph is completed by adding self-loops to it. +Thus, the result will contain at most one vertex with self-loops. + + The ‘method’ parameter controls the order in which the vertices to be +connected are chosen. In the undirected case, +‘IGRAPH_REALIZE_DEGSEQ_SMALLEST’ produces a connected graph when one +exists. This makes this method suitable for constructing trees with a +given degree sequence. + + For a undirected simple graph, the time complexity is O(V + alpha(V) +* E). For an undirected multi graph, the time complexity is O(V * E + V +log V). For a directed graph, the time complexity is O(E + V^2 log V). + + References: + + V. Havel: Poznámka o existenci konečných grafů (A remark on the +existence of finite graphs), Časopis pro pěstování matematiky 80, +477-480 (1955). http://eudml.org/doc/19050 (http://eudml.org/doc/19050) + + S. L. Hakimi: On Realizability of a Set of Integers as Degrees of the +Vertices of a Linear Graph, Journal of the SIAM 10, 3 (1962). +https://www.jstor.org/stable/2098770 +(https://www.jstor.org/stable/2098770) + + D. J. Kleitman and D. L. Wang: Algorithms for Constructing Graphs and +Digraphs with Given Valences and Factors, Discrete Mathematics 6, 1 +(1973). https://doi.org/10.1016/0012-365X%2873%2990037-X +(https://doi.org/10.1016/0012-365X%2873%2990037-X) P. L. Erdős, I. +Miklós, Z. Toroczkai: A simple Havel-Hakimi type algorithm to realize +graphical degree sequences of directed graphs, The Electronic Journal of +Combinatorics 17.1 (2010). http://eudml.org/doc/227072 +(http://eudml.org/doc/227072) + + Sz. Horvát and C. D. Modes: Connectedness matters: construction and +exact random sampling of connected networks (2021). +https://doi.org/10.1088/2632-072X/abced5 +(https://doi.org/10.1088/2632-072X/abced5) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘outdeg’: + The degree sequence of an undirected graph (if ‘indeg’ is NULL), or + the out-degree sequence of a directed graph (if ‘indeg’ is given). + +‘indeg’: + The in-degree sequence of a directed graph. Pass ‘NULL’ to + generate an undirected graph. + +‘allowed_edge_types’: + The types of edges to allow in the graph. See + ‘igraph_edge_type_sw_t’ (*note igraph_edge_type_sw_t --- What types + of non-simple edges to allow?::). For directed graphs, only + ‘IGRAPH_SIMPLE_SW’ is implemented at this moment. For undirected + graphs, the following values are valid: + + ‘IGRAPH_SIMPLE_SW’ + simple graphs (i.e. no self-loops or multi-edges allowed). + + ‘IGRAPH_LOOPS_SW’ + single self-loops are allowed, but not multi-edges; currently + not implemented. + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed, but not self-loops. + + ‘IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW’ + both self-loops and multi-edges are allowed. + +‘method’: + The method to generate the graph. Possible values: + + ‘IGRAPH_REALIZE_DEGSEQ_SMALLEST’ + The vertex with smallest remaining degree is selected first. + The result is usually a graph with high negative degree + assortativity. In the undirected case, this method is + guaranteed to generate a connected graph, regardless of + whether multi-edges are allowed, provided that a connected + realization exists (see Horvát and Modes, 2021, as well as + http://szhorvat.net/pelican/hh-connected-graphs.html + (http://szhorvat.net/pelican/hh-connected-graphs.html)). This + method can be used to construct a tree from its degrees. In + the directed case it tends to generate weakly connected + graphs, but this is not guaranteed. + + ‘IGRAPH_REALIZE_DEGSEQ_LARGEST’ + The vertex with the largest remaining degree is selected + first. The result is usually a graph with high positive + degree assortativity, and is often disconnected. + + ‘IGRAPH_REALIZE_DEGSEQ_INDEX’ + The vertices are selected in order of their index (i.e. their + position in the degree vector). Note that sorting the degree + vector and using the ‘INDEX’ method is not equivalent to the + ‘SMALLEST’ method above, as ‘SMALLEST’ uses the smallest + _remaining_ degree for selecting vertices, not the smallest + _initial_ degree. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_UNIMPLEMENTED’ + The requested method is not implemented. + + ‘IGRAPH_ENOMEM’ + There is not enough memory to perform the operation. + + ‘IGRAPH_EINVAL’ + Invalid method parameter, or invalid in- and/or out-degree + vectors. The degree vectors should be non-negative, the + length and sum of ‘outdeg’ and ‘indeg’ should match for + directed graphs. + + *See also:. * + +‘’ + ‘igraph_is_graphical()’ (*note igraph_is_graphical --- Is there a + graph with the given degree sequence?::) to test graphicality + without generating a graph; + ‘igraph_realize_bipartite_degree_sequence()’ (*note + igraph_realize_bipartite_degree_sequence --- Generates a bipartite + graph with the given bidegree sequence_::) to create bipartite + graphs from two degree sequence; ‘igraph_degree_sequence_game()’ + (*note igraph_degree_sequence_game --- Generates a random graph + with a given degree sequence_::) to generate random graphs with a + given degree sequence; ‘igraph_k_regular_game()’ (*note + igraph_k_regular_game --- Generates a random graph where each + vertex has the same degree_::) to generate random regular graphs; + ‘igraph_rewire()’ (*note igraph_rewire --- Randomly rewires a graph + while preserving its degree sequence_::) to randomly rewire the + edges of a graph while preserving its degree sequence. + + * File examples/simple/igraph_realize_degree_sequence.c* + + +File: igraph-docs.info, Node: igraph_realize_bipartite_degree_sequence --- Generates a bipartite graph with the given bidegree sequence_, Prev: igraph_realize_degree_sequence --- Generates a graph with the given degree sequence_, Up: Graphs with given degrees + +11.6.2 igraph_realize_bipartite_degree_sequence -- Generates a bipartite graph with the given bidegree sequence. +---------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_realize_bipartite_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, + const igraph_edge_type_sw_t allowed_edge_types, const igraph_realize_degseq_t method + ); + + This function generates a bipartite graph with the given bidegree +sequence, using a Havel-Hakimi-like construction algorithm. The order +in which vertices are connected up is controlled by the ‘method’ +parameter. When using the ‘IGRAPH_REALIZE_DEGSEQ_SMALLEST’ method, it +is ensured that the graph will be connected if and only if the given +bidegree sequence is potentially connected. + + The vertices of the graph will be ordered so that those having +‘degrees1’ come first, followed by ‘degrees2’. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘degrees1’: + The degree sequence of the first partition. + +‘degrees2’: + The degree sequence of the second partition. + +‘allowed_edge_types’: + The types of edges to allow in the graph. + + ‘IGRAPH_SIMPLE_SW’ + simple graph (i.e. no multi-edges allowed). + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed + +‘method’: + Controls the order in which vertices are selected for connection. + Possible values: + + ‘IGRAPH_REALIZE_DEGSEQ_SMALLEST’ + The vertex with smallest remaining degree is selected first, + from either partition. The result is usually a graph with + high negative degree assortativity. This method is guaranteed + to generate a connected graph, if one exists. + + ‘IGRAPH_REALIZE_DEGSEQ_LARGEST’ + The vertex with the largest remaining degree is selected + first, from either parition. The result is usually a graph + with high positive degree assortativity, and is often + disconnected. + + ‘IGRAPH_REALIZE_DEGSEQ_INDEX’ + The vertices are selected in order of their index. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_bigraphical()’ (*note igraph_is_bigraphical --- Is there + a bipartite graph with the given bi-degree-sequence?::) to test + bigraphicality without generating a graph. + + +File: igraph-docs.info, Node: Complete graphs, Next: Pre-defined graphs, Prev: Graphs with given degrees, Up: Deterministic graph generators + +11.7 Complete graphs +==================== + +These functions produce single and multipartite complete graphs, as well +as related graphs. + +* Menu: + +* igraph_full -- Creates a full graph (complete graph).: igraph_full --- Creates a full graph [complete graph]_. +* igraph_full_citation -- Creates a full citation graph (a complete directed acyclic graph).: igraph_full_citation --- Creates a full citation graph [a complete directed acyclic graph]_. +* igraph_full_multipartite -- Creates a full multipartite graph.: igraph_full_multipartite --- Creates a full multipartite graph_. +* igraph_turan -- Creates a Turán graph.: igraph_turan --- Creates a Turán graph_. + + +File: igraph-docs.info, Node: igraph_full --- Creates a full graph [complete graph]_, Next: igraph_full_citation --- Creates a full citation graph [a complete directed acyclic graph]_, Up: Complete graphs + +11.7.1 igraph_full -- Creates a full graph (complete graph). +------------------------------------------------------------ + + + igraph_error_t igraph_full(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_bool_t loops); + + In a full graph every possible edge is present: every vertex is +connected to every other vertex. ‘igraph’ generalizes the usual concept +of complete graphs in graph theory to graphs with self-loops as well as +to directed graphs. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + Integer, the number of vertices in the graph. + +‘directed’: + Whether to create a directed graph. + +‘loops’: + Whether to include self-loops. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid number of vertices. + + Time complexity: O(|V|^2) = O(|E|), where |V| is the number of +vertices and |E| is the number of edges. + + *See also:. * + +‘’ + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::), ‘igraph_star()’ (*note + igraph_star --- Creates a star graph; every vertex connects only to + the center_::), ‘igraph_kary_tree()’ (*note igraph_kary_tree --- + Creates a k-ary tree in which almost all vertices have k + children_::) for creating other regular structures. + + * File examples/simple/igraph_full.c* + + +File: igraph-docs.info, Node: igraph_full_citation --- Creates a full citation graph [a complete directed acyclic graph]_, Next: igraph_full_multipartite --- Creates a full multipartite graph_, Prev: igraph_full --- Creates a full graph [complete graph]_, Up: Complete graphs + +11.7.2 igraph_full_citation -- Creates a full citation graph (a complete directed acyclic graph). +------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_full_citation(igraph_t *graph, igraph_int_t n, + igraph_bool_t directed); + + This is a directed graph, where every ‘i->j’ edge is present if and +only if ‘j1 and + n>2. The Chvatal graph is an example for m=4 and n=12. It has 24 + edges. + +‘Coxeter’ + A non-Hamiltonian cubic symmetric graph with 28 vertices and 42 + edges. + +‘Cubical’ + The Platonic graph of the cube. A convex regular polyhedron with 8 + vertices and 12 edges. + +‘Diamond’ + A graph with 4 vertices and 5 edges, resembles a schematic diamond + if drawn properly. + +‘Dodecahedral, Dodecahedron’ + Another Platonic solid with 20 vertices and 30 edges. + +‘Folkman’ + The semisymmetric graph with minimum number of vertices, 20 and 40 + edges. A semisymmetric graph is regular, edge transitive and not + vertex transitive. + +‘Franklin’ + This is a graph whose embedding to the Klein bottle can be colored + with six colors, it is a counterexample to the necessity of the + Heawood conjecture on a Klein bottle. It has 12 vertices and 18 + edges. + +‘Frucht’ + The Frucht Graph is the smallest cubical graph whose automorphism + group consists only of the identity element. It has 12 vertices + and 18 edges. + +‘Grotzsch, Groetzsch’ + The Grötzsch graph is a triangle-free graph with 11 vertices, 20 + edges, and chromatic number 4. It is named after German + mathematician Herbert Grötzsch, and its existence demonstrates that + the assumption of planarity is necessary in Grötzsch's theorem that + every triangle-free planar graph is 3-colorable. + +‘Heawood’ + The Heawood graph is an undirected graph with 14 vertices and 21 + edges. The graph is cubic, and all cycles in the graph have six or + more edges. Every smaller cubic graph has shorter cycles, so this + graph is the 6-cage, the smallest cubic graph of girth 6. + +‘Herschel’ + The Herschel graph is the smallest nonhamiltonian polyhedral graph. + It is the unique such graph on 11 nodes, and has 18 edges. + +‘House’ + The house graph is a 5-vertex, 6-edge graph, the schematic draw of + a house if drawn properly, basically a triangle on top of a square. + +‘HouseX’ + The same as the house graph with an X in the square. 5 vertices + and 8 edges. + +‘Icosahedral, Icosahedron’ + A Platonic solid with 12 vertices and 30 edges. + +‘Krackhardt_Kite’ + A social network with 10 vertices and 18 edges. Krackhardt, D. + Assessing the Political Landscape: Structure, Cognition, and Power + in Organizations. Admin. Sci. Quart. 35, 342-369, 1990. + +‘Levi’ + The graph is a 4-arc transitive cubic graph, it has 30 vertices and + 45 edges. + +‘McGee’ + The McGee graph is the unique 3-regular 7-cage graph, it has 24 + vertices and 36 edges. + +‘Meredith’ + The Meredith graph is a quartic graph on 70 nodes and 140 edges + that is a counterexample to the conjecture that every 4-regular + 4-connected graph is Hamiltonian. + +‘Noperfectmatching’ + A connected graph with 16 vertices and 27 edges containing no + perfect matching. A matching in a graph is a set of pairwise + non-incident edges; that is, no two edges share a common vertex. A + perfect matching is a matching which covers all vertices of the + graph. + +‘Nonline’ + A graph whose connected components are the 9 graphs whose presence + as a vertex-induced subgraph in a graph makes a nonline graph. It + has 50 vertices and 72 edges. + +‘Octahedral, Octahedron’ + Platonic solid with 6 vertices and 12 edges. + +‘Petersen’ + A 3-regular graph with 10 vertices and 15 edges. It is the + smallest hypohamiltonian graph, i.e. it is non-hamiltonian but + removing any single vertex from it makes it Hamiltonian. + +‘Robertson’ + The unique (4,5)-cage graph, i.e. a 4-regular graph of girth 5. + It has 19 vertices and 38 edges. + +‘Smallestcyclicgroup’ + A smallest nontrivial graph whose automorphism group is cyclic. It + has 9 vertices and 15 edges. + +‘Tetrahedral, Tetrahedron’ + Platonic solid with 4 vertices and 6 edges. + +‘Thomassen’ + The smallest hypotraceable graph, on 34 vertices and 52 edges. A + hypotracable graph does not contain a Hamiltonian path but after + removing any single vertex from it the remainder always contains a + Hamiltonian path. A graph containing a Hamiltonian path is called + traceable. + +‘Tutte’ + Tait's Hamiltonian graph conjecture states that every 3-connected + 3-regular planar graph is Hamiltonian. This graph is a + counterexample. It has 46 vertices and 69 edges. + +‘Uniquely3colorable’ + Returns a 12-vertex, triangle-free graph with chromatic number 3 + that is uniquely 3-colorable. + +‘Walther’ + An identity graph with 25 vertices and 31 edges. An identity graph + has a single graph automorphism, the trivial one. + +‘Zachary’ + Social network of friendships between 34 members of a karate club + at a US university in the 1970s. See W. W. Zachary, An information + flow model for conflict and fission in small groups, Journal of + Anthropological Research 33, 452-473 (1977). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘name’: + Character constant, the name of the graph to be created, it is case + insensitive. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_EINVAL’ if there is no graph with the given + name. + + *See also:. * + +‘’ + Other functions for creating graph structures: ‘igraph_ring()’ + (*note igraph_ring --- Creates a cycle graph or a path graph_::), + ‘igraph_kary_tree()’ (*note igraph_kary_tree --- Creates a k-ary + tree in which almost all vertices have k children_::), + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::), ‘igraph_full()’ (*note + igraph_full --- Creates a full graph [complete graph]_::). + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + +File: igraph-docs.info, Node: igraph_atlas --- Create a small graph from the Graph Atlas_, Prev: igraph_famous --- Create a famous graph by simply providing its name_, Up: Pre-defined graphs + +11.8.2 igraph_atlas -- Create a small graph from the Graph Atlas. +----------------------------------------------------------------- + + + igraph_error_t igraph_atlas(igraph_t *graph, igraph_int_t number); + + The graph atlas contains all simple undirected unlabeled graphs on +between 0 and 7 vertices. The number of the graph is given as a +parameter. The graphs are listed: + + 1. in increasing order of number of vertices; + + 2. for a fixed number of vertices, in increasing order of the number + of edges; + + 3. for fixed numbers of vertices and edges, in lexicographically + increasing order of the degree sequence, for example 111223 < + 112222; + + 4. for fixed degree sequence, in increasing number of automorphisms. + + The data was converted from the NetworkX software package, see +https://networkx.org/ (https://networkx.org/). + + See _ An Atlas of Graphs _ by Ronald C. Read and Robin J. Wilson, +Oxford University Press, 1998. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘number’: + The number of the graph to generate. Must be between 0 and 1252 + (inclusive). Graphs on 0-7 vertices start at numbers 0, 1, 2, 4, + 8, 19, 53, and 209, respectively. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + * File examples/simple/igraph_atlas.c* + + +File: igraph-docs.info, Node: Other well-known graphs from graph theory, Prev: Pre-defined graphs, Up: Deterministic graph generators + +11.9 Other well-known graphs from graph theory +============================================== + +* Menu: + +* igraph_de_bruijn -- Generate a de Bruijn graph.: igraph_de_bruijn --- Generate a de Bruijn graph_. +* igraph_kautz -- Generate a Kautz graph.: igraph_kautz --- Generate a Kautz graph_. +* igraph_generalized_petersen -- Creates a Generalized Petersen graph.: igraph_generalized_petersen --- Creates a Generalized Petersen graph_. +* igraph_mycielski_graph -- The Mycielski graph of order k.: igraph_mycielski_graph --- The Mycielski graph of order k_. + + +File: igraph-docs.info, Node: igraph_de_bruijn --- Generate a de Bruijn graph_, Next: igraph_kautz --- Generate a Kautz graph_, Up: Other well-known graphs from graph theory + +11.9.1 igraph_de_bruijn -- Generate a de Bruijn graph. +------------------------------------------------------ + + + igraph_error_t igraph_de_bruijn(igraph_t *graph, igraph_int_t m, igraph_int_t n); + + A de Bruijn graph represents relationships between strings. An +alphabet of ‘m’ letters are used and strings of length ‘n’ are +considered. A vertex corresponds to every possible string and there is +a directed edge from vertex ‘v’ to vertex ‘w’ if the string of ‘v’ can +be transformed into the string of ‘w’ by removing its first letter and +appending a letter to it. + + Please note that the graph will have ‘m’ to the power ‘n’ vertices +and even more edges, so probably you don't want to supply too big +numbers for ‘m’ and ‘n’. + + De Bruijn graphs have some interesting properties, please see another +source, e.g. Wikipedia for details. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘m’: + Integer, the number of letters in the alphabet. + +‘n’: + Integer, the length of the strings. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_kautz()’ (*note igraph_kautz --- Generate a Kautz + graph_::). + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_kautz --- Generate a Kautz graph_, Next: igraph_generalized_petersen --- Creates a Generalized Petersen graph_, Prev: igraph_de_bruijn --- Generate a de Bruijn graph_, Up: Other well-known graphs from graph theory + +11.9.2 igraph_kautz -- Generate a Kautz graph. +---------------------------------------------- + + + igraph_error_t igraph_kautz(igraph_t *graph, igraph_int_t m, igraph_int_t n); + + A Kautz graph is a labeled graph, vertices are labeled by strings of +length ‘n’+1 above an alphabet with ‘m’+1 letters, with the restriction +that every two consecutive letters in the string must be different. +There is a directed edge from a vertex ‘v’ to another vertex ‘w’ if it +is possible to transform the string of ‘v’ into the string of ‘w’ by +removing the first letter and appending a letter to it. For string +length 1 the new letter cannot equal the old letter, so there are no +loops. + + Kautz graphs have some interesting properties, see e.g. Wikipedia +for details. + + Vincent Matossian wrote the first version of this function in R, +thanks. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘m’: + Integer, ‘m’+1 is the number of letters in the alphabet. + +‘n’: + Integer, ‘n’+1 is the length of the strings. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_de_bruijn()’ (*note igraph_de_bruijn --- Generate a de + Bruijn graph_::). + + Time complexity: O(|V|* [(m+1)/m]^n +|E|), in practice it is more +like O(|V|+|E|). |V| is the number of vertices, |E| is the number of +edges and ‘m’ and ‘n’ are the corresponding arguments. + + +File: igraph-docs.info, Node: igraph_generalized_petersen --- Creates a Generalized Petersen graph_, Next: igraph_mycielski_graph --- The Mycielski graph of order k_, Prev: igraph_kautz --- Generate a Kautz graph_, Up: Other well-known graphs from graph theory + +11.9.3 igraph_generalized_petersen -- Creates a Generalized Petersen graph. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_generalized_petersen(igraph_t *graph, igraph_int_t n, igraph_int_t k); + + The generalized Petersen graph ‘G(n, k)’ consists of ‘n’ vertices +‘v_0’, ..., ‘v_n’ forming an "outer" cycle graph, and ‘n’ additional +vertices ‘u_0’, ..., ‘u_n’ forming an "inner" circulant graph where +‘u_i’ is connected to ‘u_(i + k mod n)’. Additionally, all ‘v_i’ are +connected to ‘u_i’. + + ‘G(n, k)’ has ‘2n’ vertices and ‘3n’ edges. The Petersen graph +itself is ‘G(5, 2)’. + + Reference: + + M. E. Watkins, A Theorem on Tait Colorings with an Application to the +Generalized Petersen Graphs, Journal of Combinatorial Theory 6, 152-164 +(1969). https://doi.org/10.1016%2FS0021-9800%2869%2980116-X +(https://doi.org/10.1016%2FS0021-9800%2869%2980116-X) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘n’: + Integer, ‘n’ is the number of vertices in the inner and outer + cycle/circulant graphs. It must be at least 3. + +‘k’: + Integer, ‘k’ is the shift of the circulant graph. It must be + positive and less than ‘n/2’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_famous()’ (*note igraph_famous --- Create a famous graph by + simply providing its name_::) for the original Petersen graph. + + Time complexity: O(|V|), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_mycielski_graph --- The Mycielski graph of order k_, Prev: igraph_generalized_petersen --- Creates a Generalized Petersen graph_, Up: Other well-known graphs from graph theory + +11.9.4 igraph_mycielski_graph -- The Mycielski graph of order k. +---------------------------------------------------------------- + + + igraph_error_t igraph_mycielski_graph(igraph_t *graph, igraph_int_t k); + + The Mycielski graph of order ‘k’, denoted ‘M_k’, is a triangle-free +graph on ‘k’ vertices with chromatic number ‘k’. It is defined through +the Mycielski construction described in the documentation of +‘igraph_mycielskian()’ (*note igraph_mycielskian --- Generate the +Mycielskian of a graph with k iterations_::). + + Some authors define Mycielski graphs only for ‘k > 1’. igraph +extends this to all ‘k >= 0’. The first few Mycielski graphs are: + + 1. M_0: Null graph + + 2. M_1: Single vertex + + 3. M_2: Path graph with 2 vertices + + 4. M_3: Cycle graph with 5 vertices + + 5. M_4: Grötzsch graph (a triangle-free graph with chromatic number 4) + +The vertex count of ‘M_k’ is ‘n_k = 3 * 2^(k-2) - 1’ for ‘k > 1’ and ‘k’ +otherwise. The edge count is ‘m_k = (7 * 3^(k-2) + 1) / 2 - 3 * 2^(k - +2)’ for ‘k > 1’ and 0 otherwise. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. The generated Mycielski + graph will be stored here. + +‘k’: + Integer, the order of the Mycielski graph (must be non-negative). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_mycielskian()’ (*note igraph_mycielskian --- Generate the + Mycielskian of a graph with k iterations_::). + + Time complexity: O(3^k), i.e. exponential in ‘k’. + + +File: igraph-docs.info, Node: Stochastic graph generators ["games"], Next: Bipartite; i_e_ two-mode graphs, Prev: Deterministic graph generators, Up: Top + +12 Stochastic graph generators ("games") +**************************************** + +"Games" are random graph generators, i.e. they generate a different +graph every time they are called. igraph includes many such generators. +Some implement stochastic graph construction processes inspired by +real-world mechanics, such as preferential attachment, while others are +designed to produce graphs with certain used properties (e.g. fixed +number of edges, fixed degrees, etc.) + +* Menu: + +* The Erdős-Rényi and related models:: +* Preferential attachment and related models:: +* Growing random graph models:: +* Degree-constrained models:: +* Edge rewiring models:: +* Other random graphs:: +* Common types and constants:: + + +File: igraph-docs.info, Node: The Erdős-Rényi and related models, Next: Preferential attachment and related models, Up: Stochastic graph generators ["games"] + +12.1 The Erdős-Rényi and related models +======================================= + +There are two classic random graph models referred to as the Erdős-Rényi +random graph, or sometimes simply _the_ random graph. Both fix the +vertex count n, but while the G(n,m) model prescribes precisely m edges, +the G(n,p) model connects all vertex pairs independently with +probability p. While these models look superficially different, when n +is large they behave in a similar manner. G(n,m) graphs have a density +of exactly ‘p = m / m_max’, while G(n,p) graphs have ‘m = p m_max’ edges +on _average,_ where ‘m_max’ is the number of vertex pairs. Indeed, +these two models turns out to be two sides of the same coin: both can be +understood as maximum entropy models with a constraint on the number of +edges. The G(n,m) is obtained from a sharp constraint, while G(n,p) +from an average constraint (soft constraint). + + The maximum entropy framework allows for rigorous generalizations of +these models to various scenarios, of which igraph supports many, such +as models defined over directed graphs, bipartite graphs, multigraphs, +or even over edge-labelled graphs. Constraining edge counts between +various subsets of vertices yields further families of related models, +such as ‘igraph_sbm_game()’ (*note igraph_sbm_game --- Sample from a +stochastic block model_::) (given connection probabilities between +categories) or ‘igraph_degree_sequence_game()’ (*note +igraph_degree_sequence_game --- Generates a random graph with a given +degree sequence_::) (given incident edge counts, i.e. degrees, for each +vertex). + +* Menu: + +* igraph_erdos_renyi_game_gnm -- Generates a random (Erdős-Rényi) graph with a fixed number of edges.: igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] graph with a fixed number of edges_. +* igraph_erdos_renyi_game_gnp -- Generates a random (Erdős-Rényi) graph with fixed edge probabilities.: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_. +* igraph_iea_game -- Generates a random multigraph through independent edge assignment.: igraph_iea_game --- Generates a random multigraph through independent edge assignment_. +* igraph_sbm_game -- Sample from a stochastic block model.: igraph_sbm_game --- Sample from a stochastic block model_. +* igraph_hsbm_game -- Hierarchical stochastic block model.: igraph_hsbm_game --- Hierarchical stochastic block model_. +* igraph_hsbm_list_game -- Hierarchical stochastic block model, more general version.: igraph_hsbm_list_game --- Hierarchical stochastic block model; more general version_. +* igraph_preference_game -- Generates a graph with vertex types and connection preferences.: igraph_preference_game --- Generates a graph with vertex types and connection preferences_. +* igraph_asymmetric_preference_game -- Generates a graph with asymmetric vertex types and connection preferences.: igraph_asymmetric_preference_game --- Generates a graph with asymmetric vertex types and connection preferences_. +* igraph_correlated_game -- Generates a random graph correlated to an existing graph.: igraph_correlated_game --- Generates a random graph correlated to an existing graph_. +* igraph_correlated_pair_game -- Generates pairs of correlated random graphs.: igraph_correlated_pair_game --- Generates pairs of correlated random graphs_. + + +File: igraph-docs.info, Node: igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] graph with a fixed number of edges_, Next: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_, Up: The Erdős-Rényi and related models + +12.1.1 igraph_erdos_renyi_game_gnm -- Generates a random (Erdős-Rényi) graph with a fixed number of edges. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_erdos_renyi_game_gnm( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + + In the ‘G(n, m)’ Erdős-Rényi model, a graph with ‘n’ vertices and ‘m’ +edges is generated uniformly at random. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘m’: + The number of edges in the graph. + +‘directed’: + Whether to generate a directed graph. + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are generated. See + ‘igraph_edge_type_sw_t’ (*note igraph_edge_type_sw_t --- What types + of non-simple edges to allow?::). + +‘edge_labeled’: + If true, the sampling is done uniformly from the set of ordered + edge lists. See ‘igraph_iea_game()’ (*note igraph_iea_game --- + Generates a random multigraph through independent edge + assignment_::) for more information. Set this to ‘false’ to select + the classic Erdős-Rényi model. The constants + ‘IGRAPH_EDGE_UNLABELED’ and ‘IGRAPH_EDGE_LABELED’ may be used + instead of ‘false’ and ‘true’ for better readability. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid ‘n’ or ‘m’ parameter. + ‘IGRAPH_ENOMEM’: there is not enough memory for the operation. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_erdos_renyi_game_gnp()’ (*note igraph_erdos_renyi_game_gnp + --- Generates a random [Erdős-Rényi] graph with fixed edge + probabilities_::) to sample from the related ‘G(n, p)’ model, which + constrains the _expected_ edge count; ‘igraph_iea_game()’ (*note + igraph_iea_game --- Generates a random multigraph through + independent edge assignment_::) to generate multigraph by assigning + edges to vertex pairs uniformly and independently; + ‘igraph_degree_sequence_game()’ (*note igraph_degree_sequence_game + --- Generates a random graph with a given degree sequence_::) to + constrain the degree sequence; ‘igraph_bipartite_game_gnm()’ (*note + igraph_bipartite_game_gnm --- Generate a random bipartite graph + with a fixed number of edges_::) for the bipartite version of this + model; ‘igraph_barabasi_game()’ (*note igraph_barabasi_game --- + Generates a graph based on the Barabási-Albert model_::) and + ‘igraph_growing_random_game()’ (*note igraph_growing_random_game + --- Generates a growing random graph_::) for other commonly used + random graph models. + + * File examples/simple/igraph_erdos_renyi_game_gnm.c* + + +File: igraph-docs.info, Node: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_, Next: igraph_iea_game --- Generates a random multigraph through independent edge assignment_, Prev: igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] graph with a fixed number of edges_, Up: The Erdős-Rényi and related models + +12.1.2 igraph_erdos_renyi_game_gnp -- Generates a random (Erdős-Rényi) graph with fixed edge probabilities. +----------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_erdos_renyi_game_gnp( + igraph_t *graph, + igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + + In the ‘G(n, p)’ Erdős-Rényi model, also known as the Gilbert model, +or Bernoulli random graph, a graph with ‘n’ vertices is generated such +that every possible edge is included in the graph independently with +probability ‘p’. This is equivalent to a maximum entropy random graph +model model with a constraint on the _expected_ edge count. The maximum +entropy view allows for extending the model to multigraphs, as discussed +by Park and Newman (2004), section III.D. In this case, ‘p’ is +interpreted as the expected number of edges between any vertex pair. + + Setting ‘p = 1/2’ and ‘multiple = false’ generates all graphs without +multi-edges on ‘n’ vertices with the same probability. + + For both simple and multigraphs, the expected mean degree of the +graph is approximately ‘p n’; set ‘p = k/n’ when a mean degree of +approximately ‘k’ is desired. More precisely, the expected mean degree +is ‘p(n-1)’ in (undirected or directed) graphs without self-loops, +‘p(n+1)’ in undirected graphs with self-loops, and ‘p n’ in directed +graphs with self-loops. + + When generating multigraphs, the distribution of the edge +multiplicities is geometric, i.e. the probability of finding ‘m’ edges +between two vertices is ‘q (1-q)^m’, where ‘q = 1 / (1+p)’. + + This function uses the sequential geometric sampling technique +described in Batagelj and Brandes (2005), with a modification to handle +multigraphs. + + References: + + J. Park and M. E. J. Newman: "Statistical Mechanics of Networks". +Phys. Rev. E 70, 066117 (2004). +https://doi.org/10.1103/PhysRevE.70.066117 +(https://doi.org/10.1103/PhysRevE.70.066117) + + V. Batagelj and U. Brandes: "Efficient Generation of Large Random +Networks". Phys. Rev. E 71, 036113 (2005). +https://doi.org/10.1103/PhysRevE.71.036113 +(https://doi.org/10.1103/PhysRevE.71.036113) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘p’: + The expected number of edges between any vertex pair. When + multi-edges are disallowed, this is equivalent to the probability + of having a connection between any two vertices. + +‘directed’: + Whether to generate a directed graph. + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are generated. See + ‘igraph_edge_type_sw_t’ (*note igraph_edge_type_sw_t --- What types + of non-simple edges to allow?::). + +‘edge_labeled’: + If true, the model is defined over the set of ordered edge lists, + i.e. over the set of edge-labeled graphs. Set it to ‘false’ to + select the classic Erdős-Rényi model. The constants + ‘IGRAPH_EDGE_UNLABELED’ and ‘IGRAPH_EDGE_LABELED’ may be used + instead of ‘false’ and ‘true’ for better readability. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid ‘n’ or ‘p’ parameter. + ‘IGRAPH_ENOMEM’: there is not enough memory for the operation. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_erdos_renyi_game_gnm()’ (*note igraph_erdos_renyi_game_gnm + --- Generates a random [Erdős-Rényi] graph with a fixed number of + edges_::) to generate random graphs with a sharply fixed edge + count; ‘igraph_chung_lu_game()’ (*note igraph_chung_lu_game --- + Samples graphs from the Chung-Lu model_::) and + ‘igraph_static_fitness_game()’ (*note igraph_static_fitness_game + --- Non-growing random graph with edge probabilities proportional + to node fitness scores_::) to generate random graphs with a fixed + expected degree sequence; ‘igraph_bipartite_game_gnm()’ (*note + igraph_bipartite_game_gnm --- Generate a random bipartite graph + with a fixed number of edges_::) for the bipartite version of this + model; ‘igraph_barabasi_game()’ (*note igraph_barabasi_game --- + Generates a graph based on the Barabási-Albert model_::) and + ‘igraph_growing_random_game()’ (*note igraph_growing_random_game + --- Generates a growing random graph_::) for other commonly used + random graph models. + + * File examples/simple/igraph_erdos_renyi_game_gnp.c* + + +File: igraph-docs.info, Node: igraph_iea_game --- Generates a random multigraph through independent edge assignment_, Next: igraph_sbm_game --- Sample from a stochastic block model_, Prev: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_, Up: The Erdős-Rényi and related models + +12.1.3 igraph_iea_game -- Generates a random multigraph through independent edge assignment. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_iea_game( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This model generates random multigraphs on ‘n’ vertices with ‘m’ +edges through independent edge assignment (IEA). Each of the ‘m’ edges +is assigned uniformly at random to an _ordered_ vertex pair, +independently of each other. + + This model does not sample multigraphs uniformly. Undirected graphs +are generated with probability proportional to + + ‘(prod_(i 0). In each step a +new node is added, and it is connected to ‘m’ existing nodes. Existing +nodes to connect to are chosen with probability dependent on their +(in-)degree (‘k’) and age (‘l’). The degree-dependent part is ‘deg_coef +* k^pa_exp + zero_deg_appeal’, while the age-dependent part is ‘age_coef +* l^aging_exp + zero_age_appeal’, which are multiplied to obtain the +final weight. + + The age ‘l’ is based on the number of vertices in the network and the +‘aging_bins’ argument: the age of a node is incremented by 1 after each +‘floor(nodes / aging_bins) + 1’ time steps. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the graph. + +‘m’: + The number of edges to add in each time step. Ignored if ‘outseq’ + is a non-zero length vector. + +‘outseq’: + The number of edges to add in each time step. If it is ‘NULL’ or a + zero-length vector then it is ignored and the ‘m’ argument is used + instead. + +‘outpref’: + Boolean constant, whether the edges initiated by a vertex + contribute to the probability to gain a new edge. + +‘pa_exp’: + The exponent of the preferential attachment, a small positive + number usually, the value 1 yields the classic linear preferential + attachment. + +‘aging_exp’: + The exponent of the aging, this is a negative number usually. + +‘aging_bins’: + Integer constant, the number of age bins to use. + +‘zero_deg_appeal’: + The degree dependent part of the attractiveness of the zero degree + vertices. + +‘zero_age_appeal’: + The age dependent part of the attractiveness of the vertices of age + zero. This parameter is usually zero. + +‘deg_coef’: + The coefficient for the degree. + +‘age_coef’: + The coefficient for the age. + +‘directed’: + Boolean constant, whether to generate a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O((|V|+|V|/aging_bins)*log(|V|)+|E|). |V| is the +number of vertices, |E| the number of edges. + + +File: igraph-docs.info, Node: igraph_recent_degree_game --- Stochastic graph generator based on the number of incident edges a node has gained recently_, Next: igraph_recent_degree_aging_game --- Preferential attachment based on the number of edges gained recently; with aging of vertices_, Prev: igraph_barabasi_aging_game --- Preferential attachment with aging of vertices_, Up: Preferential attachment and related models + +12.2.3 igraph_recent_degree_game -- Stochastic graph generator based on the number of incident edges a node has gained recently. +-------------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_recent_degree_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t power, + igraph_int_t time_window, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t zero_appeal, + igraph_bool_t directed); + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the graph, this is the same as the number + of time steps. + +‘power’: + The exponent, the probability that a node gains a new edge is + proportional to the number of edges it has gained recently (in the + last ‘window’ time steps) to ‘power’. + +‘time_window’: + Integer constant, the size of the time window to use to count the + number of recent edges. + +‘m’: + Integer constant, the number of edges to add per time step if the + ‘outseq’ parameter is a null pointer or a zero-length vector. + +‘outseq’: + The number of edges to add in each time step. This argument is + ignored if it is a null pointer or a zero length vector. In this + case the constant ‘m’ parameter is used. + +‘outpref’: + Boolean constant, if true the edges originated by a vertex also + count as recent incident edges. For most applications it is + reasonable to set it to false. + +‘zero_appeal’: + Constant giving the attractiveness of the vertices which haven't + gained any edge recently. + +‘directed’: + Boolean constant, whether to generate a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|*log(|V|)+|E|), |V| is the number of vertices, +|E| is the number of edges in the graph. + + +File: igraph-docs.info, Node: igraph_recent_degree_aging_game --- Preferential attachment based on the number of edges gained recently; with aging of vertices_, Next: igraph_lastcit_game --- Simulates a citation network; based on time passed since the last citation_, Prev: igraph_recent_degree_game --- Stochastic graph generator based on the number of incident edges a node has gained recently_, Up: Preferential attachment and related models + +12.2.4 igraph_recent_degree_aging_game -- Preferential attachment based on the number of edges gained recently, with aging of vertices. +--------------------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_recent_degree_aging_game(igraph_t *graph, + igraph_int_t nodes, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_int_t aging_bins, + igraph_int_t time_window, + igraph_real_t zero_appeal, + igraph_bool_t directed); + + This game is very similar to ‘igraph_barabasi_aging_game()’ (*note +igraph_barabasi_aging_game --- Preferential attachment with aging of +vertices_::), except that instead of the total number of incident edges +the number of edges gained in the last ‘time_window’ time steps are +counted. + + The degree dependent part of the attractiveness is given by k to the +power of ‘pa_exp’ plus ‘zero_appeal’; the age dependent part is l to the +power to ‘aging_exp’. k is the number of edges gained in the last +‘time_window’ time steps, l is the age of the vertex. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the graph. + +‘m’: + The number of edges to add in each time step. If the ‘outseq’ + argument is not a null vector or a zero-length vector then it is + ignored. + +‘outseq’: + Vector giving the number of edges to add in each time step. If it + is a null pointer or a zero-length vector then it is ignored and + the ‘m’ argument is used. + +‘outpref’: + Boolean constant, if true the edges initiated by a vertex are also + counted. Normally it is false. + +‘pa_exp’: + The exponent for the preferential attachment. + +‘aging_exp’: + The exponent for the aging, normally it is negative: old vertices + gain edges with less probability. + +‘aging_bins’: + Integer constant, the number of age bins to use. + +‘time_window’: + The time window to use to count the number of incident edges for + the vertices. + +‘zero_appeal’: + The degree dependent part of the attractiveness for zero degree + vertices. + +‘directed’: + Boolean constant, whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O((|V|+|V|/aging_bins)*log(|V|)+|E|). |V| is the +number of vertices, |E| the number of edges. + + +File: igraph-docs.info, Node: igraph_lastcit_game --- Simulates a citation network; based on time passed since the last citation_, Prev: igraph_recent_degree_aging_game --- Preferential attachment based on the number of edges gained recently; with aging of vertices_, Up: Preferential attachment and related models + +12.2.5 igraph_lastcit_game -- Simulates a citation network, based on time passed since the last citation. +--------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_lastcit_game(igraph_t *graph, + igraph_int_t nodes, igraph_int_t edges_per_node, + igraph_int_t agebins, + const igraph_vector_t *preference, + igraph_bool_t directed); + + This is a quite special stochastic graph generator, it models an +evolving graph. In each time step a single vertex is added to the +network and it cites a number of other vertices (as specified by the +‘edges_per_step’ argument). The cited vertices are selected based on +the last time they were cited. Time is measured by the addition of +vertices and it is binned into ‘agebins’ bins. So if the current time +step is ‘t’ and the last citation to a given ‘i’ vertex was made in time +step ‘t0’, then ‘(t-t0) / binwidth’ is calculated where binwidth is +‘nodes/agebins + 1’, in the last expression '/' denotes integer +division, so the fraction part is omitted. + + The ‘preference’ argument specifies the preferences for the citation +lags, i.e. its first elements contains the attractivity of the very +recently cited vertices, etc. The last element is special, it contains +the attractivity of the vertices which were never cited. This element +should be bigger than zero. + + Note that this function generates networks with multiple edges if +‘edges_per_step’ is bigger than one, call ‘igraph_simplify()’ (*note +igraph_simplify --- Removes loop and/or multiple edges from the +graph_::) on the result to get rid of these edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result will be stored + here. + +‘nodes’: + The number of vertices in the network. + +‘edges_per_node’: + The number of edges to add in each time step. + +‘agebins’: + The number of age bins to use. + +‘preference’: + Pointer to an initialized vector of length ‘agebins + 1’. This + contains the "attractivity" of the various age bins, the last + element is the attractivity of the vertices which were never cited, + and it should be greater than zero. It is a good idea to have all + positive values in this vector. Preferences cannot be negative. + +‘directed’: + Boolean constant, whether to create directed networks. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_barabasi_aging_game()’ (*note igraph_barabasi_aging_game + --- Preferential attachment with aging of vertices_::). + + Time complexity: O(|V|*a+|E|*log|V|), |V| is the number of vertices, +|E| is the total number of edges, a is the ‘agebins’ parameter. + + +File: igraph-docs.info, Node: Growing random graph models, Next: Degree-constrained models, Prev: Preferential attachment and related models, Up: Stochastic graph generators ["games"] + +12.3 Growing random graph models +================================ + +In growing random graphs, vertices are added iteratively, and connected +based on various rules. Preferential attachment models are documented +in their own section (*note Preferential attachment and related +models::). + +* Menu: + +* igraph_growing_random_game -- Generates a growing random graph.: igraph_growing_random_game --- Generates a growing random graph_. +* igraph_callaway_traits_game -- Simulates a growing network with vertex types.: igraph_callaway_traits_game --- Simulates a growing network with vertex types_. +* igraph_establishment_game -- Generates a graph with a simple growing model with vertex types.: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_. +* igraph_cited_type_game -- Simulates a citation based on vertex types.: igraph_cited_type_game --- Simulates a citation based on vertex types_. +* igraph_citing_cited_type_game -- Simulates a citation network based on vertex types.: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_. +* igraph_forest_fire_game -- Generates a network according to the forest fire game.: igraph_forest_fire_game --- Generates a network according to the forest fire game_. + + +File: igraph-docs.info, Node: igraph_growing_random_game --- Generates a growing random graph_, Next: igraph_callaway_traits_game --- Simulates a growing network with vertex types_, Up: Growing random graph models + +12.3.1 igraph_growing_random_game -- Generates a growing random graph. +---------------------------------------------------------------------- + + + igraph_error_t igraph_growing_random_game(igraph_t *graph, igraph_int_t n, + igraph_int_t m, igraph_bool_t directed, + igraph_bool_t citation); + + This function simulates a growing random graph. We start out with +one vertex. In each step a new vertex is added and a number of new +edges are also added. These graphs are known to be different from +standard (not growing) random graphs. + + *Arguments:. * + +‘graph’: + Uninitialized graph object. + +‘n’: + The number of vertices in the graph. + +‘m’: + The number of edges to add in a time step (i.e. after adding a + vertex). + +‘directed’: + Boolean, whether to generate a directed graph. + +‘citation’: + Boolean, if ‘true’, the edges always originate from the most + recently added vertex and are connected to a previous vertex. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid ‘n’ or ‘m’ parameter. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_callaway_traits_game --- Simulates a growing network with vertex types_, Next: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_, Prev: igraph_growing_random_game --- Generates a growing random graph_, Up: Growing random graph models + +12.3.2 igraph_callaway_traits_game -- Simulates a growing network with vertex types. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_callaway_traits_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t edges_per_step, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec); + + The different types of vertices prefer to connect other types of +vertices with a given probability. + + The simulation goes like this: in each discrete time step a new +vertex is added to the graph. The type of this vertex is generated +based on ‘type_dist’. Then two vertices are selected uniformly randomly +from the graph. The probability that they will be connected depends on +the types of these vertices and is taken from ‘pref_matrix’. Then +another two vertices are selected and this is repeated ‘edges_per_step’ +times in each time step. + + References: + + D. S. Callaway, J. E. Hopcroft, J. M. Kleinberg, M. E. J. Newman, and +S. H. Strogatz, Are randomly grown graphs really random? Phys. Rev. E +64, 041902 (2001). https://doi.org/10.1103/PhysRevE.64.041902 +(https://doi.org/10.1103/PhysRevE.64.041902) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph. + +‘nodes’: + The number of nodes in the graph. + +‘types’: + Number of node types. + +‘edges_per_step’: + The number of connections tried in each time step. + +‘type_dist’: + Vector giving the distribution of the vertex types. If ‘NULL’, the + distribution is assumed to be uniform. + +‘pref_matrix’: + Matrix giving the connection probabilities for the vertex types. + +‘directed’: + Whether to generate a directed graph. + +‘node_type_vec’: + An initialized vector or ‘NULL’. If not ‘NULL’, the type of each + node will be stored here. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: O(|V|*k*log(|V|)), |V| is the number of vertices, k +is ‘edges_per_step’. + + +File: igraph-docs.info, Node: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_, Next: igraph_cited_type_game --- Simulates a citation based on vertex types_, Prev: igraph_callaway_traits_game --- Simulates a growing network with vertex types_, Up: Growing random graph models + +12.3.3 igraph_establishment_game -- Generates a graph with a simple growing model with vertex types. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_establishment_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t k, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec); + + The simulation goes like this: a single vertex is added at each time +step. This new vertex tries to connect to ‘k’ vertices in the graph. +The probability that such a connection is realized depends on the types +of the vertices involved. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph. + +‘nodes’: + The number of vertices in the graph. + +‘types’: + The number of vertex types. + +‘k’: + The number of connections tried in each time step. + +‘type_dist’: + Vector giving the distribution of vertex types. If ‘NULL’, the + distribution is assumed to be uniform. + +‘pref_matrix’: + Matrix giving the connection probabilities for different vertex + types. + +‘directed’: + Whether to generate a directed graph. + +‘node_type_vec’: + An initialized vector or ‘NULL’. If not ‘NULL’, the type of each + node will be stored here. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: O(|V|*k*log(|V|)), |V| is the number of vertices and +k is the ‘k’ parameter. + + +File: igraph-docs.info, Node: igraph_cited_type_game --- Simulates a citation based on vertex types_, Next: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_, Prev: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_, Up: Growing random graph models + +12.3.4 igraph_cited_type_game -- Simulates a citation based on vertex types. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_vector_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed); + + Function to create a network based on some vertex categories. This +function creates a citation network: in each step a single vertex and +‘edges_per_step’ citing edges are added. Nodes with different +categories may have different probabilities to get cited, as given by +the ‘pref’ vector. + + Note that this function might generate networks with multiple edges +if ‘edges_per_step’ is greater than one. You might want to call +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::) on the result to remove multiple +edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the network. + +‘types’: + Numeric vector giving the categories of the vertices, so it should + contain ‘nodes’ non-negative integer numbers. Types are numbered + from zero. + +‘pref’: + The attractivity of the different vertex categories in a vector. + Its length should be the maximum element in ‘types’ plus one (types + are numbered from zero). + +‘edges_per_step’: + Integer constant, the number of edges to add in each time step. + +‘directed’: + Boolean constant, whether to create a directed network. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_citing_cited_type_game()’ (*note + igraph_citing_cited_type_game --- Simulates a citation network + based on vertex types_::) for a bit more general game. + + Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of +vertices and edges, respectively. + + +File: igraph-docs.info, Node: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_, Next: igraph_forest_fire_game --- Generates a network according to the forest fire game_, Prev: igraph_cited_type_game --- Simulates a citation based on vertex types_, Up: Growing random graph models + +12.3.5 igraph_citing_cited_type_game -- Simulates a citation network based on vertex types. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_citing_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_matrix_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed); + + This game is similar to ‘igraph_cited_type_game()’ (*note +igraph_cited_type_game --- Simulates a citation based on vertex +types_::) but here the category of the citing vertex is also considered. + + An evolving citation network is modeled here, a single vertex and its +‘edges_per_step’ citation are added in each time step. The odds the a +given vertex is cited by the new vertex depends on the category of both +the citing and the cited vertex and is given in the ‘pref’ matrix. The +categories of the citing vertex correspond to the rows, the categories +of the cited vertex to the columns of this matrix. I.e. the element in +row ‘i’ and column ‘j’ gives the probability that a ‘j’ vertex is cited, +if the category of the citing vertex is ‘i’. + + Note that this function might generate networks with multiple edges +if ‘edges_per_step’ is greater than one. You might want to call +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::) on the result to remove multiple +edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the network. + +‘types’: + A numeric vector of length ‘nodes’, containing the categories of + the vertices. The categories are numbered from zero. + +‘pref’: + The preference matrix, a square matrix is required, both the number + of rows and columns should be the maximum element in ‘types’ plus + one (types are numbered from zero). + +‘edges_per_step’: + Integer constant, the number of edges to add in each time step. + +‘directed’: + Boolean constant, whether to create a directed network. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of +vertices and edges, respectively. + + +File: igraph-docs.info, Node: igraph_forest_fire_game --- Generates a network according to the forest fire game_, Prev: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_, Up: Growing random graph models + +12.3.6 igraph_forest_fire_game -- Generates a network according to the forest fire game. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_forest_fire_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t fw_prob, igraph_real_t bw_factor, + igraph_int_t pambs, igraph_bool_t directed); + + The forest fire model intends to reproduce the following network +characteristics, observed in real networks: + + • Heavy-tailed in- and out-degree distributions. + + • Community structure. + + • Densification power-law. The network is densifying in time, + according to a power-law rule. + + • Shrinking diameter. The diameter of the network decreases in time. + + The network is generated in the following way. One vertex is added +at a time. This vertex connects to (cites) ‘ambs’ vertices already +present in the network, chosen uniformly random. Now, for each cited +vertex ‘v’ we do the following procedure: + + 1. We generate two random numbers, ‘x’ and ‘y’, that are geometrically + distributed with means ‘p/(1-p)’ and ‘rp(1-rp)’. (‘p’ is + ‘fw_prob’, ‘r’ is ‘bw_factor’.) The new vertex cites ‘x’ outgoing + neighbors and ‘y’ incoming neighbors of ‘v’, from those which are + not yet cited by the new vertex. If there are less than ‘x’ or ‘y’ + such vertices available then we cite all of them. + + 2. The same procedure is applied to all the newly cited vertices. + + See also: Jure Leskovec, Jon Kleinberg and Christos Faloutsos. +Graphs over time: densification laws, shrinking diameters and possible +explanations. _ KDD '05: Proceeding of the eleventh ACM SIGKDD +international conference on Knowledge discovery in data mining _, +177-187, 2005. + + Note however, that the version of the model in the published paper is +incorrect in the sense that it cannot generate the kind of graphs the +authors claim. A corrected version is available from +https://www.cs.cmu.edu/~jure/pubs/powergrowth-tkdd.pdf +(https://www.cs.cmu.edu/~jure/pubs/powergrowth-tkdd.pdf), our +implementation is based on this. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the graph. + +‘fw_prob’: + The forward burning probability. + +‘bw_factor’: + The backward burning ratio. The backward burning probability is + calculated as ‘bw_factor * fw_prob’. + +‘pambs’: + The number of ambassador vertices. + +‘directed’: + Whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Degree-constrained models, Next: Edge rewiring models, Prev: Growing random graph models, Up: Stochastic graph generators ["games"] + +12.4 Degree-constrained models +============================== + +Random graph models with hard or soft degree constraints. + +* Menu: + +* igraph_degree_sequence_game -- Generates a random graph with a given degree sequence.: igraph_degree_sequence_game --- Generates a random graph with a given degree sequence_. +* igraph_k_regular_game -- Generates a random graph where each vertex has the same degree.: igraph_k_regular_game --- Generates a random graph where each vertex has the same degree_. +* igraph_rewire -- Randomly rewires a graph while preserving its degree sequence.: igraph_rewire --- Randomly rewires a graph while preserving its degree sequence_. +* igraph_chung_lu_game -- Samples graphs from the Chung-Lu model.: igraph_chung_lu_game --- Samples graphs from the Chung-Lu model_. +* igraph_static_fitness_game -- Non-growing random graph with edge probabilities proportional to node fitness scores.: igraph_static_fitness_game --- Non-growing random graph with edge probabilities proportional to node fitness scores_. +* igraph_static_power_law_game -- Generates a non-growing random graph with expected power-law degree distributions.: igraph_static_power_law_game --- Generates a non-growing random graph with expected power-law degree distributions_. + + +File: igraph-docs.info, Node: igraph_degree_sequence_game --- Generates a random graph with a given degree sequence_, Next: igraph_k_regular_game --- Generates a random graph where each vertex has the same degree_, Up: Degree-constrained models + +12.4.1 igraph_degree_sequence_game -- Generates a random graph with a given degree sequence. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_degree_sequence_game( + igraph_t *graph, + const igraph_vector_int_t *out_degrees, + const igraph_vector_int_t *in_degrees, + igraph_degseq_t method); + + This function generates random graphs with a prescribed degree +sequence. Several sampling methods are available, which respect +different constraints (simple graph or multigraphs, connected graphs, +etc.), and provide different tradeoffs between performance and unbiased +sampling. See Section 2.1 of Horvát and Modes (2021) for an overview of +sampling techniques for graphs with fixed degrees. + + References: + + Fabien Viger, and Matthieu Latapy: Efficient and Simple Generation of +Random Simple Connected Graphs with Prescribed Degree Sequence, Journal +of Complex Networks 4, no. 1, pp. 15-37 (2015). +https://doi.org/10.1093/comnet/cnv013 +(https://doi.org/10.1093/comnet/cnv013). + + Szabolcs Horvát, and Carl D Modes: Connectedness Matters: +Construction and Exact Random Sampling of Connected Networks, Journal of +Physics: Complexity 2, no. 1, pp. 015008 (2021). +https://doi.org/10.1088/2632-072x/abced5 +(https://doi.org/10.1088/2632-072x/abced5). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘out_degrees’: + A vector of integers specifying the degree sequence for undirected + graphs or the out-degree sequence for directed graphs. + +‘in_degrees’: + A vector of integers specifying the in-degree sequence for directed + graphs. For undirected graphs, it must be ‘NULL’. + +‘method’: + The method to generate the graph. Possible values: + + ‘IGRAPH_DEGSEQ_CONFIGURATION’ + This method implements the configuration model. For + undirected graphs, it puts all vertex IDs in a bag such that + the multiplicity of a vertex in the bag is the same as its + degree. Then it draws pairs from the bag until the bag + becomes empty. This method may generate both loop (self) + edges and multiple edges. For directed graphs, the algorithm + is basically the same, but two separate bags are used for the + in- and out-degrees. Undirected graphs are generated with + probability proportional to ‘(\prod_{i 1’, this function simply issues a warning and creates a +connection between ‘i’ and ‘j’. However, in this case the expected +degrees will no longer relate to the weights in the manner stated above. +Thus the original Chung-Lu model cannot produce certain (large) expected +degrees. + + The overcome this limitation, this function implements additional +variants of the model, with modified expressions for the connection +probability ‘p_ij’ between vertices ‘i’ and ‘j’. Let ‘q_ij = w_i w_j / +S’, or ‘q_ij = w^out_i w^in_j / S’ in the directed case. All model +variants become equivalent in the limit of sparse graphs where ‘q_ij’ +approaches zero. In the original Chung-Lu model, selectable by setting +‘variant’ to ‘IGRAPH_CHUNG_LU_ORIGINAL’, ‘p_ij = min(q_ij, 1)’. The +‘IGRAPH_CHUNG_LU_MAXENT’ variant, sometiems referred to a the +generalized random graph, uses ‘p_ij = q_ij / (1 + q_ij)’, and is +equivalent to a maximum entropy model (i.e. exponential random graph +model) with a constraint on expected degrees; see Park and Newman +(2004), Section B, setting ‘exp(-Theta_ij) = w_i w_j / S’. This model +is also discussed by Britton, Deijfen and Martin-Löf (2006). By virtue +of being a degree-constrained maximum entropy model, it produces graphs +with the same degree sequence with the same probability. A third +variant can be requested with ‘IGRAPH_CHUNG_LU_NR’, and uses ‘p_ij = 1 - +exp(-q_ij)’. This is the underlying simple graph of a multigraph model +introduced by Norros and Reittu (2006). For a discussion of these three +model variants, see Section 16.4 of Bollobás, Janson, Riordan (2007), as +well as Van Der Hofstad (2013). + + References: + + Chung F and Lu L: Connected components in a random graph with given +degree sequences. Annals of Combinatorics 6, 125-145 (2002). +https://doi.org/10.1007/PL00012580 (https://doi.org/10.1007/PL00012580) + + Miller JC and Hagberg A: Efficient Generation of Networks with Given +Expected Degrees (2011). https://doi.org/10.1007/978-3-642-21286-4_10 +(https://doi.org/10.1007/978-3-642-21286-4_10) + + Park J and Newman MEJ: Statistical mechanics of networks. Physical +Review E 70, 066117 (2004). https://doi.org/10.1103/PhysRevE.70.066117 +(https://doi.org/10.1103/PhysRevE.70.066117) + + Britton T, Deijfen M, Martin-Löf A: Generating Simple Random Graphs +with Prescribed Degree Distribution. J Stat Phys 124, 1377-1397 (2006). +https://doi.org/10.1007/s10955-006-9168-x +(https://doi.org/10.1007/s10955-006-9168-x) + + Norros I and Reittu H: On a conditionally Poissonian graph process. +Advances in Applied Probability 38, 59-75 (2006). +https://doi.org/10.1239/aap/1143936140 +(https://doi.org/10.1239/aap/1143936140) + + Bollobás B, Janson S, Riordan O: The phase transition in +inhomogeneous random graphs. Random Struct Algorithms 31, 3-122 (2007). +https://doi.org/10.1002/rsa.20168 (https://doi.org/10.1002/rsa.20168) + + Van Der Hofstad R: Critical behavior in inhomogeneous random graphs. +Random Struct Algorithms 42, 480-508 (2013). +https://doi.org/10.1002/rsa.20450 (https://doi.org/10.1002/rsa.20450) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘out_weights’: + A vector of non-negative vertex weights (or out-weights). In + sparse graphs these will be approximately equal to the expected + (out-)degrees. + +‘in_weights’: + A vector of non-negative in-weights, approximately equal to the + expected in-degrees in sparse graphs. May be set to ‘NULL’, in + which case undirected graphs are generated. + +‘loops’: + Whether to allow the creation of self-loops. Since vertex pairs + are connected independently, setting this to false is equivalent to + simply discarding self-loops from an existing loopy Chung-Lu graph. + +‘variant’: + The model variant to sample from, with different definitions of the + connection probability between vertices ‘i’ and ‘j’. Given ‘q_ij = + w_i w_j / S’, the following formulations are available: + + ‘IGRAPH_CHUNG_LU_ORIGINAL’ + the original Chung-Lu model, ‘p_ij = min(q_ij, 1)’. + + ‘IGRAPH_CHUNG_LU_MAXENT’ + maximum entropy model with fixed expected degrees, ‘p_ij = + q_ij / (1 + q_ij)’. + + ‘IGRAPH_CHUNG_LU_NR’ + Norros and Reittu's model, ‘p_ij = 1 - exp(-q_ij)’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_static_fitness_game()’ (*note igraph_static_fitness_game + --- Non-growing random graph with edge probabilities proportional + to node fitness scores_::) implements a similar model with a sharp + constraint on the number of edges; ‘igraph_degree_sequence_game()’ + (*note igraph_degree_sequence_game --- Generates a random graph + with a given degree sequence_::) samples random graphs with sharply + specified degrees; ‘igraph_erdos_renyi_game_gnp()’ (*note + igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] + graph with fixed edge probabilities_::) creates random graphs with + a fixed connection probability ‘p’ between all vertex pairs. + + Time complexity: O(|E| + |V|), linear in the number of edges. + + +File: igraph-docs.info, Node: igraph_static_fitness_game --- Non-growing random graph with edge probabilities proportional to node fitness scores_, Next: igraph_static_power_law_game --- Generates a non-growing random graph with expected power-law degree distributions_, Prev: igraph_chung_lu_game --- Samples graphs from the Chung-Lu model_, Up: Degree-constrained models + +12.4.5 igraph_static_fitness_game -- Non-growing random graph with edge probabilities proportional to node fitness scores. +-------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_static_fitness_game(igraph_t *graph, igraph_int_t no_of_edges, + const igraph_vector_t *fitness_out, const igraph_vector_t *fitness_in, + igraph_edge_type_sw_t allowed_edge_types); + + This game generates a directed or undirected random graph where the +probability of an edge between vertices ‘i’ and ‘j’ depends on the +fitness scores of the two vertices involved. For undirected graphs, +each vertex has a single fitness score. For directed graphs, each +vertex has an out- and an in-fitness, and the probability of an edge +from ‘i’ to ‘j’ depends on the out-fitness of vertex ‘i’ and the +in-fitness of vertex ‘j’. + + The generation process goes as follows. We start from ‘N’ +disconnected nodes (where ‘N’ is given by the length of the fitness +vector). Then we randomly select two vertices ‘i’ and ‘j’, with +probabilities proportional to their fitnesses. (When the generated +graph is directed, ‘i’ is selected according to the out-fitnesses and +‘j’ is selected according to the in-fitnesses). If the vertices are not +connected yet (or if multiple edges are allowed), we connect them; +otherwise we select a new pair. This is repeated until the desired +number of links are created. + + The _expected_ degree (though not the actual degree) of each vertex +will be proportional to its fitness. This is exactly true when +self-loops and multi-edges are allowed, and approximately true +otherwise. If you need to generate a graph with an exact degree +sequence, consider ‘igraph_degree_sequence_game()’ (*note +igraph_degree_sequence_game --- Generates a random graph with a given +degree sequence_::) and ‘igraph_realize_degree_sequence()’ (*note +igraph_realize_degree_sequence --- Generates a graph with the given +degree sequence_::) instead. + + To generate random undirected graphs with a given expected degree +sequence, set ‘fitness_out’ (and in the directed case ‘fitness_out’) to +the desired expected degrees, and ‘no_of_edges’ to the corresponding +edge count, i.e. half the sum of expected degrees in the undirected +case, and the sum of out- or in-degrees in the directed case. + + This model is similar to the better-known Chung-Lu model, implemented +in igraph as ‘igraph_chung_lu_game()’ (*note igraph_chung_lu_game --- +Samples graphs from the Chung-Lu model_::), but with a sharply fixed +edge count. + + This model is commonly used to generate static scale-free networks. +To achieve this, you have to draw the fitness scores from the desired +power-law distribution. Alternatively, you may use +‘igraph_static_power_law_game()’ (*note igraph_static_power_law_game --- +Generates a non-growing random graph with expected power-law degree +distributions_::) which generates the fitnesses for you with a given +exponent. + + Reference: + + Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in +scale-free networks. Phys Rev Lett 87(27):278701, 2001 +https://doi.org/10.1103/PhysRevLett.87.278701 +(https://doi.org/10.1103/PhysRevLett.87.278701). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘no_of_edges’: + The number of edges in the generated graph. + +‘fitness_out’: + A numeric vector containing the fitness of each vertex. For + directed graphs, this specifies the out-fitness of each vertex. + +‘fitness_in’: + If ‘NULL’, the generated graph will be undirected. If not ‘NULL’, + this argument specifies the in-fitness of each vertex. + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are allowed in the + generated graph. See ‘igraph_edge_type_sw_t’ (*note + igraph_edge_type_sw_t --- What types of non-simple edges to + allow?::). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid parameter ‘IGRAPH_ENOMEM’: + there is not enough memory for the operation. + + *See also:. * + +‘’ + ‘igraph_static_power_law_game()’ (*note + igraph_static_power_law_game --- Generates a non-growing random + graph with expected power-law degree distributions_::), + ‘igraph_chung_lu_game()’ (*note igraph_chung_lu_game --- Samples + graphs from the Chung-Lu model_::), ‘igraph_degree_sequence_game()’ + (*note igraph_degree_sequence_game --- Generates a random graph + with a given degree sequence_::) + + Time complexity: O(|V| + |E| log |E|). + + +File: igraph-docs.info, Node: igraph_static_power_law_game --- Generates a non-growing random graph with expected power-law degree distributions_, Prev: igraph_static_fitness_game --- Non-growing random graph with edge probabilities proportional to node fitness scores_, Up: Degree-constrained models + +12.4.6 igraph_static_power_law_game -- Generates a non-growing random graph with expected power-law degree distributions. +------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_static_power_law_game(igraph_t *graph, + igraph_int_t no_of_nodes, igraph_int_t no_of_edges, + igraph_real_t exponent_out, igraph_real_t exponent_in, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t finite_size_correction); + + This game generates a directed or undirected random graph where the +degrees of vertices follow power-law distributions with prescribed +exponents. For directed graphs, the exponents of the in- and out-degree +distributions may be specified separately. + + The game simply uses ‘igraph_static_fitness_game()’ (*note +igraph_static_fitness_game --- Non-growing random graph with edge +probabilities proportional to node fitness scores_::) with appropriately +constructed fitness vectors. In particular, the fitness of vertex ‘i’ +is ‘i^(-alpha)’, where ‘alpha = 1/(gamma-1)’ and ‘gamma’ is the exponent +given in the arguments. + + To remove correlations between in- and out-degrees in case of +directed graphs, the in-fitness vector will be shuffled after it has +been set up and before ‘igraph_static_fitness_game()’ (*note +igraph_static_fitness_game --- Non-growing random graph with edge +probabilities proportional to node fitness scores_::) is called. + + Note that significant finite size effects may be observed for +exponents smaller than 3 in the original formulation of the game. This +function provides an argument that lets you remove the finite size +effects by assuming that the fitness of vertex ‘i’ is +‘(i+i0-1)^(-alpha)’, where ‘i0’ is a constant chosen appropriately to +ensure that the maximum degree is less than the square root of the +number of edges times the average degree; see the paper of Chung and Lu, +and Cho et al for more details. + + References: + + Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in +scale-free networks. Phys Rev Lett 87(27):278701, 2001. +https://doi.org/10.1103/PhysRevLett.87.278701 +(https://doi.org/10.1103/PhysRevLett.87.278701) + + Chung F and Lu L: Connected components in a random graph with given +degree sequences. Annals of Combinatorics 6, 125-145, 2002. +https://doi.org/10.1007/PL00012580 (https://doi.org/10.1007/PL00012580) + + Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in +scale-free networks under the Achlioptas process. Phys Rev Lett +103:135702, 2009. https://doi.org/10.1103/PhysRevLett.103.135702 +(https://doi.org/10.1103/PhysRevLett.103.135702) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘no_of_nodes’: + The number of nodes in the generated graph. + +‘no_of_edges’: + The number of edges in the generated graph. + +‘exponent_out’: + The power law exponent of the degree distribution. For directed + graphs, this specifies the exponent of the out-degree distribution. + It must be greater than or equal to 2. If you pass + ‘IGRAPH_INFINITY’ here, you will get back an Erdős-Rényi random + network. + +‘exponent_in’: + If negative, the generated graph will be undirected. If greater + than or equal to 2, this argument specifies the exponent of the + in-degree distribution. If non-negative but less than 2, an error + will be generated. + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are allowed in the + generated graph. See ‘igraph_edge_type_sw_t’ (*note + igraph_edge_type_sw_t --- What types of non-simple edges to + allow?::). + +‘finite_size_correction’: + Whether to use the proposed finite size correction of Cho et al. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid parameter ‘IGRAPH_ENOMEM’: + there is not enough memory for the operation. + + Time complexity: O(|V| + |E| log |E|). + + +File: igraph-docs.info, Node: Edge rewiring models, Next: Other random graphs, Prev: Degree-constrained models, Up: Stochastic graph generators ["games"] + +12.5 Edge rewiring models +========================= + +* Menu: + +* igraph_watts_strogatz_game -- The Watts-Strogatz small-world model.: igraph_watts_strogatz_game --- The Watts-Strogatz small-world model_. +* igraph_rewire_edges -- Rewires the edges of a graph with constant probability.: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_. +* igraph_rewire_directed_edges -- Rewires the chosen endpoint of directed edges.: igraph_rewire_directed_edges --- Rewires the chosen endpoint of directed edges_. + + +File: igraph-docs.info, Node: igraph_watts_strogatz_game --- The Watts-Strogatz small-world model_, Next: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_, Up: Edge rewiring models + +12.5.1 igraph_watts_strogatz_game -- The Watts-Strogatz small-world model. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_watts_strogatz_game( + igraph_t *graph, igraph_int_t dim, + igraph_int_t size, igraph_int_t nei, + igraph_real_t p, + igraph_edge_type_sw_t allowed_edge_types); + + This function generates networks with the small-world property based +on a variant of the Watts-Strogatz model. The network is obtained by +first creating a periodic undirected lattice, then rewiring both +endpoints of each edge with probability ‘p’, while avoiding the creation +of multi-edges. + + This process differs from the original model of Watts and Strogatz +(see reference) in that it rewires _both_ endpoints of edges. Thus in +the limit of ‘p=1’, we obtain a G(n,m) random graph with the same number +of vertices and edges as the original lattice. In comparison, the +original Watts-Strogatz model only rewires a single endpoint of each +edge, thus the network does not become fully random even for ‘p=1’. For +appropriate choices of ‘p’, both models exhibit the property of +simultaneously having short path lengths and high clustering. + + Reference: + + Duncan J Watts and Steven H Strogatz: Collective dynamics of 'small +world' networks, Nature 393, 440-442, 1998. +https://doi.org/10.1038/30918 (https://doi.org/10.1038/30918) + + *Arguments:. * + +‘graph’: + The graph to initialize. + +‘dim’: + The dimension of the lattice. + +‘size’: + The size of the lattice along each dimension. + +‘nei’: + The size of the neighborhood for each vertex. This is the same as + the ‘order’ argument of ‘igraph_connect_neighborhood()’ (*note + igraph_connect_neighborhood --- Connects each vertex to its + neighborhood_::). + +‘p’: + The rewiring probability. A real number between zero and one + (inclusive). + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are allowed in the + generated graph. See ‘igraph_edge_type_sw_t’ (*note + igraph_edge_type_sw_t --- What types of non-simple edges to + allow?::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_square_lattice()’ (*note igraph_square_lattice --- + Arbitrary dimensional square lattices_::), + ‘igraph_connect_neighborhood()’ (*note igraph_connect_neighborhood + --- Connects each vertex to its neighborhood_::) and + ‘igraph_rewire_edges()’ (*note igraph_rewire_edges --- Rewires the + edges of a graph with constant probability_::) can be used if more + flexibility is needed, e.g. a different type of lattice. + + Time complexity: O(|V|*d^o+|E|), |V| and |E| are the number of +vertices and edges, d is the average degree, o is the ‘nei’ argument. + + +File: igraph-docs.info, Node: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_, Next: igraph_rewire_directed_edges --- Rewires the chosen endpoint of directed edges_, Prev: igraph_watts_strogatz_game --- The Watts-Strogatz small-world model_, Up: Edge rewiring models + +12.5.2 igraph_rewire_edges -- Rewires the edges of a graph with constant probability. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_rewire_edges(igraph_t *graph, igraph_real_t prob, + igraph_edge_type_sw_t allowed_edge_types); + + This function rewires the edges of a graph with a constant +probability. More precisely each end point of each edge is rewired to a +uniformly randomly chosen vertex with constant probability ‘prob’. + + Note that this function modifies the input ‘graph’, call +‘igraph_copy()’ (*note igraph_copy --- Creates an exact [deep] copy of a +graph_::) if you want to keep it. + + *Arguments:. * + +‘graph’: + The input graph, this will be rewired, it can be directed or + undirected. + +‘prob’: + The rewiring probability a constant between zero and one + (inclusive). + +‘allowed_edge_types’: + Controls whether multi-edges and self-loops are allowed in the new + graph. See ‘igraph_edge_type_sw_t’ (*note igraph_edge_type_sw_t + --- What types of non-simple edges to allow?::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_watts_strogatz_game()’ (*note igraph_watts_strogatz_game + --- The Watts-Strogatz small-world model_::) uses this function for + the rewiring. + + Time complexity: O(|V|+|E|). + + +File: igraph-docs.info, Node: igraph_rewire_directed_edges --- Rewires the chosen endpoint of directed edges_, Prev: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_, Up: Edge rewiring models + +12.5.3 igraph_rewire_directed_edges -- Rewires the chosen endpoint of directed edges. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_rewire_directed_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_neimode_t mode); + + This function rewires either the start or end of directed edges in a +graph with a constant probability. Correspondingly, either the +in-degree sequence or the out-degree sequence of the graph will be +preserved. + + Note that this function modifies the input ‘graph’, call +‘igraph_copy()’ (*note igraph_copy --- Creates an exact [deep] copy of a +graph_::) if you want to keep it. + + This function can produce multiple edges between two vertices. + + *Arguments:. * + +‘graph’: + The input graph, this will be rewired, it can be directed or + undirected. If it is undirected or ‘mode’ is set to IGRAPH_ALL, + ‘igraph_rewire_edges()’ (*note igraph_rewire_edges --- Rewires the + edges of a graph with constant probability_::) will be called. + +‘prob’: + The rewiring probability, a constant between zero and one + (inclusive). + +‘loops’: + Boolean, whether loop edges are allowed in the new graph, or not. + +‘mode’: + The endpoints of directed edges to rewire. It is ignored for + undirected graphs. Possible values: + + ‘IGRAPH_OUT’ + rewire the end of each directed edge + + ‘IGRAPH_IN’ + rewire the start of each directed edge + + ‘IGRAPH_ALL’ + rewire both endpoints of each edge + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_rewire_edges()’ (*note igraph_rewire_edges --- Rewires the + edges of a graph with constant probability_::), ‘igraph_rewire()’ + (*note igraph_rewire --- Randomly rewires a graph while preserving + its degree sequence_::) + + Time complexity: O(|E|). + + +File: igraph-docs.info, Node: Other random graphs, Next: Common types and constants, Prev: Edge rewiring models, Up: Stochastic graph generators ["games"] + +12.6 Other random graphs +======================== + +* Menu: + +* igraph_grg_game -- Generates a geometric random graph.: igraph_grg_game --- Generates a geometric random graph_. +* igraph_dot_product_game -- Generates a random dot product graph.: igraph_dot_product_game --- Generates a random dot product graph_. +* igraph_simple_interconnected_islands_game -- Generates a random graph made of several interconnected islands, each island being a random graph.: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_. +* igraph_tree_game -- Generates a random tree with the given number of nodes.: igraph_tree_game --- Generates a random tree with the given number of nodes_. + + +File: igraph-docs.info, Node: igraph_grg_game --- Generates a geometric random graph_, Next: igraph_dot_product_game --- Generates a random dot product graph_, Up: Other random graphs + +12.6.1 igraph_grg_game -- Generates a geometric random graph. +------------------------------------------------------------- + + + igraph_error_t igraph_grg_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t radius, igraph_bool_t torus, + igraph_vector_t *x, igraph_vector_t *y); + + A geometric random graph is created by dropping points (i.e. +vertices) randomly on the unit square and then connecting all those +pairs which are strictly less than ‘radius’ apart in Euclidean distance. + + Original code contributed by Keith Briggs, thanks Keith. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘nodes’: + The number of vertices in the graph. + +‘radius’: + The radius within which the vertices will be connected. + +‘torus’: + Boolean constant. If true, periodic boundary conditions will be + used, i.e. the vertices are assumed to be on a torus instead of a + square. + +‘x’: + An initialized vector or ‘NULL’. If not ‘NULL’, the points' x + coordinates will be returned here. + +‘y’: + An initialized vector or ‘NULL’. If not ‘NULL’, the points' y + coordinates will be returned here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO, less than O(|V|^2+|E|). + + * File examples/simple/igraph_grg_game.c* + + +File: igraph-docs.info, Node: igraph_dot_product_game --- Generates a random dot product graph_, Next: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_, Prev: igraph_grg_game --- Generates a geometric random graph_, Up: Other random graphs + +12.6.2 igraph_dot_product_game -- Generates a random dot product graph. +----------------------------------------------------------------------- + + + igraph_error_t igraph_dot_product_game(igraph_t *graph, const igraph_matrix_t *vecs, + igraph_bool_t directed); + + In this model, each vertex is represented by a latent position +vector. Probability of an edge between two vertices are given by the +dot product of their latent position vectors. + + See also Christine Leigh Myers Nickel: Random dot product graphs, a +model for social networks. Dissertation, Johns Hopkins University, +Maryland, USA, 2006. + + *Arguments:. * + +‘graph’: + The output graph is stored here. + +‘vecs’: + A matrix in which each latent position vector is a column. The dot + product of the latent position vectors should be in the [0,1] + interval, otherwise a warning is given. For negative dot products, + no edges are added; dot products that are larger than one always + add an edge. + +‘directed’: + Should the generated graph be directed? + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n*n*m), where n is the number of vertices, and m +is the length of the latent vectors. + + *See also:. * + +‘’ + ‘igraph_rng_sample_dirichlet()’ (*note igraph_rng_sample_dirichlet + --- Sample points from a Dirichlet distribution_::), + ‘igraph_rng_sample_sphere_volume()’ (*note + igraph_rng_sample_sphere_volume --- Sample points uniformly from + the volume of a sphere_::), ‘igraph_rng_sample_sphere_surface()’ + (*note igraph_rng_sample_sphere_surface --- Sample points uniformly + from the surface of a sphere_::) for functions to generate the + latent vectors. + + +File: igraph-docs.info, Node: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_, Next: igraph_tree_game --- Generates a random tree with the given number of nodes_, Prev: igraph_dot_product_game --- Generates a random dot product graph_, Up: Other random graphs + +12.6.3 igraph_simple_interconnected_islands_game -- Generates a random graph made of several interconnected islands, each island being a random graph. +------------------------------------------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_simple_interconnected_islands_game( + igraph_t *graph, + igraph_int_t islands_n, + igraph_int_t islands_size, + igraph_real_t islands_pin, + igraph_int_t n_inter); + + All islands are of the same size. Within an island, each edge is +generated with the same probability. A fixed number of additional edges +are then generated for each unordered pair of islands to connect them. +The generated graph is guaranteed to be simple. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘islands_n’: + The number of islands in the graph. + +‘islands_size’: + The size of islands in the graph. + +‘islands_pin’: + The probability to create each possible edge within islands. + +‘n_inter’: + The number of edges to create between two islands. It may be + larger than ‘islands_size’ squared, but in this case it is assumed + to be ‘islands_size’ squared. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid parameter ‘IGRAPH_ENOMEM’: + there is not enough memory for the operation. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + +File: igraph-docs.info, Node: igraph_tree_game --- Generates a random tree with the given number of nodes_, Prev: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_, Up: Other random graphs + +12.6.4 igraph_tree_game -- Generates a random tree with the given number of nodes. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_tree_game(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, igraph_random_tree_t method); + + This function samples uniformly from the set of labelled trees, i.e. +it generates each labelled tree with the same probability. + + Note that for ‘n=0’, the null graph is returned, which is not +considered to be a tree by ‘igraph_is_tree()’ (*note igraph_is_tree --- +Decides whether the graph is a tree_::). + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘n’: + The number of nodes in the tree. + +‘directed’: + Whether to create a directed tree. The edges are oriented away + from the root. + +‘method’: + The algorithm to use to generate the tree. Possible values: + + ‘IGRAPH_RANDOM_TREE_PRUFER’ + This algorithm samples Prüfer sequences uniformly, then + converts them to trees. Directed trees are not currently + supported. + + ‘IGRAPH_RANDOM_LERW’ + This algorithm effectively performs a loop-erased random walk + on the complete graph to uniformly sample its spanning trees + (Wilson's algorithm). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: there is not enough memory to perform + the operation. ‘IGRAPH_EINVAL’: invalid tree size + + *See also:. * + +‘’ + ‘igraph_from_prufer()’ (*note igraph_from_prufer --- Generates a + tree from a Prüfer sequence_::) + + +File: igraph-docs.info, Node: Common types and constants, Prev: Other random graphs, Up: Stochastic graph generators ["games"] + +12.7 Common types and constants +=============================== + +* Menu: + +* igraph_edge_type_sw_t --- What types of non-simple edges to allow?:: + + +File: igraph-docs.info, Node: igraph_edge_type_sw_t --- What types of non-simple edges to allow?, Up: Common types and constants + +12.7.1 igraph_edge_type_sw_t -- What types of non-simple edges to allow? +------------------------------------------------------------------------ + + + typedef unsigned int igraph_edge_type_sw_t; + + This type is used with multiple functions to specify what types of +non-simple edges to allow, create or consider a graph. The constants +below are treated as "switches" that can be turned on individually and +combined using the bitwise-or operator. For example, ‘IGRAPH_LOOPS_SW’ +allows only self-loops but not multi-edges, while ‘IGRAPH_LOOPS_SW | +IGRAPH_MULTI_SW’ allows both. + + *Values:. * + +‘IGRAPH_SIMPLE_SW’: + A shorthand for simple graphs only, which is the default + assumption. + +‘IGRAPH_LOOPS_SW’: + Allow or consider self-loops. + +‘IGRAPH_MULTI_SW’: + Allow or consider multi-edges. + + +File: igraph-docs.info, Node: Bipartite; i_e_ two-mode graphs, Next: Spatial graphs, Prev: Stochastic graph generators ["games"], Up: Top + +13 Bipartite, i.e. two-mode graphs +********************************** + +* Menu: + +* Bipartite networks in igraph:: +* Create two-mode networks:: +* Bipartite adjacency matrices:: +* Project two-mode graphs:: +* Other operations on bipartite graphs:: + + +File: igraph-docs.info, Node: Bipartite networks in igraph, Next: Create two-mode networks, Up: Bipartite; i_e_ two-mode graphs + +13.1 Bipartite networks in igraph +================================= + +A bipartite network contains two kinds of vertices and connections are +only possible between two vertices of different kinds. There are many +natural examples, e.g. movies and actors as vertices and a movie is +connected to all participating actors, etc. + + igraph does not have direct support for bipartite networks, at least +not at the C language level. In other words the igraph_t structure does +not contain information about the vertex types. The C functions for +bipartite networks usually have an additional input argument to graph, +called ‘types’, a boolean vector giving the vertex types. + + Most functions creating bipartite networks are able to create this +extra vector, you just need to supply an initialized boolean vector to +them. + + +File: igraph-docs.info, Node: Create two-mode networks, Next: Bipartite adjacency matrices, Prev: Bipartite networks in igraph, Up: Bipartite; i_e_ two-mode graphs + +13.2 Create two-mode networks +============================= + +* Menu: + +* igraph_create_bipartite -- Create a bipartite graph.: igraph_create_bipartite --- Create a bipartite graph_. +* igraph_full_bipartite -- Creates a complete bipartite graph.: igraph_full_bipartite --- Creates a complete bipartite graph_. +* igraph_bipartite_game_gnm -- Generate a random bipartite graph with a fixed number of edges.: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_. +* igraph_bipartite_game_gnp -- Generates a random bipartite graph with a fixed connection probability.: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_. +* igraph_bipartite_iea_game -- Generates a random bipartite multigraph through independent edge assignment.: igraph_bipartite_iea_game --- Generates a random bipartite multigraph through independent edge assignment_. + + +File: igraph-docs.info, Node: igraph_create_bipartite --- Create a bipartite graph_, Next: igraph_full_bipartite --- Creates a complete bipartite graph_, Up: Create two-mode networks + +13.2.1 igraph_create_bipartite -- Create a bipartite graph. +----------------------------------------------------------- + + + igraph_error_t igraph_create_bipartite(igraph_t *graph, const igraph_vector_bool_t *types, + const igraph_vector_int_t *edges, + igraph_bool_t directed); + + This is a simple wrapper function to create a bipartite graph. It +does a little more than ‘igraph_create()’ (*note igraph_create --- +Creates a graph with the specified edges_::), e.g. it checks that the +graph is indeed bipartite with respect to the given ‘types’ vector. If +there is an edge connecting two vertices of the same kind, then an error +is reported. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the result is created + here. + +‘types’: + Boolean vector giving the vertex types. The length of the vector + defines the number of vertices in the graph. + +‘edges’: + Vector giving the edges of the graph. The highest vertex ID in + this vector must be smaller than the length of the ‘types’ vector. + +‘directed’: + Boolean, whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + * File examples/simple/igraph_bipartite_create.c* + + +File: igraph-docs.info, Node: igraph_full_bipartite --- Creates a complete bipartite graph_, Next: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_, Prev: igraph_create_bipartite --- Create a bipartite graph_, Up: Create two-mode networks + +13.2.2 igraph_full_bipartite -- Creates a complete bipartite graph. +------------------------------------------------------------------- + + + igraph_error_t igraph_full_bipartite(igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, + igraph_bool_t directed, + igraph_neimode_t mode); + + A bipartite network contains two kinds of vertices and connections +are only possible between two vertices of different kind. There are +many natural examples, e.g. movies and actors as vertices and a movie +is connected to all participating actors, etc. + + igraph does not have direct support for bipartite networks, at least +not at the C language level. In other words the ‘igraph_t’ structure +does not contain information about the vertex types. The C functions +for bipartite networks usually have an additional input argument to +graph, called ‘types’, a boolean vector giving the vertex types. + + Most functions creating bipartite networks are able to create this +extra vector, you just need to supply an initialized boolean vector to +them. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object, the graph will be created + here. + +‘types’: + Pointer to a boolean vector. If not a null pointer, then the + vertex types will be stored here. + +‘n1’: + Integer, the number of vertices of the first kind. + +‘n2’: + Integer, the number of vertices of the second kind. + +‘directed’: + Boolean, whether to create a directed graph. + +‘mode’: + A constant that gives the type of connections for directed graphs. + If ‘IGRAPH_OUT’, then edges point from vertices of the first kind + to vertices of the second kind; if ‘IGRAPH_IN’, then the opposite + direction is realized; if ‘IGRAPH_ALL’, then mutual edges will be + created. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + *See also:. * + +‘’ + ‘igraph_full()’ (*note igraph_full --- Creates a full graph + [complete graph]_::) for non-bipartite complete graphs, + ‘igraph_full_multipartite()’ (*note igraph_full_multipartite --- + Creates a full multipartite graph_::) for complete multipartite + graphs. + + +File: igraph-docs.info, Node: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_, Next: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_, Prev: igraph_full_bipartite --- Creates a complete bipartite graph_, Up: Create two-mode networks + +13.2.3 igraph_bipartite_game_gnm -- Generate a random bipartite graph with a fixed number of edges. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_bipartite_game_gnm( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + + The ‘G(n1, n2, m)’ model uniformly samples bipartite graphs with ‘n1’ +bottom vertices and ‘n2’ top vertices, and precisely ‘m’ edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized igraph graph, the result is stored + here. + +‘types’: + Pointer to an initialized boolean vector, or a null pointer. If + not a null pointer, then the vertex types are stored here. Bottom + vertices come first, ‘n1’ of them, then ‘n2’ top vertices. + +‘n1’: + The number of bottom vertices. + +‘n2’: + The number of top vertices. + +‘m’: + The number of edges. + +‘directed’: + Boolean, whether to generate a directed graph. See also the ‘mode’ + argument. + +‘mode’: + Specifies how to direct the edges in directed graphs. If it is + ‘IGRAPH_OUT’, then directed edges point from bottom vertices to top + vertices. If it is ‘IGRAPH_IN’, edges point from top vertices to + bottom vertices. ‘IGRAPH_OUT’ and ‘IGRAPH_IN’ do not generate + mutual edges. If this argument is ‘IGRAPH_ALL’, then each edge + direction is considered independently and mutual edges might be + generated. This argument is ignored for undirected graphs. * + +‘allowed_edge_types’: + The types of edges to allow in the graph. + + ‘IGRAPH_SIMPLE_SW’ + simple graph (i.e. no multi-edges allowed). + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed + +‘edge_labeled’: + If true, the sampling is done uniformly from the set of ordered + edge lists. See ‘igraph_bipartite_iea_game()’ (*note + igraph_bipartite_iea_game --- Generates a random bipartite + multigraph through independent edge assignment_::) for more + information. Set this to ‘false’ to select the classic Erdős-Rényi + model. The constants ‘IGRAPH_EDGE_UNLABELED’ and + ‘IGRAPH_EDGE_LABELED’ may be used instead of ‘false’ and ‘true’ for + better readability. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_erdos_renyi_game_gnm()’ (*note igraph_erdos_renyi_game_gnm + --- Generates a random [Erdős-Rényi] graph with a fixed number of + edges_::) for the unipartite version, ‘igraph_bipartite_game_gnp()’ + (*note igraph_bipartite_game_gnp --- Generates a random bipartite + graph with a fixed connection probability_::) for the ‘G(n1, n2, + p)’ model. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + +File: igraph-docs.info, Node: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_, Next: igraph_bipartite_iea_game --- Generates a random bipartite multigraph through independent edge assignment_, Prev: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_, Up: Create two-mode networks + +13.2.4 igraph_bipartite_game_gnp -- Generates a random bipartite graph with a fixed connection probability. +----------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_bipartite_game_gnp( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + + In the ‘G(n1, n2, p)’ model, every possible edge between the ‘n1’ +bottom vertices and ‘n2’ top vertices is realized independently with +probability ‘p’. This is equivalent to a maximum entropy model with a +constraint on the _expected_ total edge count. This view allows a +multigraph extension, in which case ‘is’ interpreted as the expected +number of edges between any vertex pair. See +‘igraph_erdos_renyi_game_gnp()’ (*note igraph_erdos_renyi_game_gnp --- +Generates a random [Erdős-Rényi] graph with fixed edge probabilities_::) +for more details. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized igraph graph, the result is stored + here. + +‘types’: + Pointer to an initialized boolean vector, or a null pointer. If + not ‘NULL’, then the vertex types are stored here. Bottom vertices + come first, ‘n1’ of them, then ‘n2’ top vertices. + +‘n1’: + The number of bottom vertices. + +‘n2’: + The number of top vertices. + +‘p’: + The expected number of edges between any vertex pair. When + multi-edges are disallowed, this is equivalent to the probability + of having a connection between any two vertices. + +‘directed’: + Whether to generate a directed graph. See also the ‘mode’ + argument. + +‘mode’: + Specifies how to direct the edges in directed graphs. If it is + ‘IGRAPH_OUT’, then directed edges point from bottom vertices to top + vertices. If it is ‘IGRAPH_IN’, edges point from top vertices to + bottom vertices. ‘IGRAPH_OUT’ and ‘IGRAPH_IN’ do not generate + mutual edges. If this argument is ‘IGRAPH_ALL’, then each edge + direction is considered independently and mutual edges might be + generated. This argument is ignored for undirected graphs. * + +‘allowed_edge_types’: + The types of edges to allow in the graph. + + ‘IGRAPH_SIMPLE_SW’ + simple graph (i.e. no multi-edges allowed). + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed + +‘edge_labeled’: + If true, the model is defined over the set of ordered edge lists, + i.e. over the set of edge-labeled graphs. Set it to ‘false’ to + select the classic bipartite Erdős-Rényi model. The constants + ‘IGRAPH_EDGE_UNLABELED’ and ‘IGRAPH_EDGE_LABELED’ may be used + instead of ‘false’ and ‘true’ for better readability. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_erdos_renyi_game_gnp()’ (*note igraph_erdos_renyi_game_gnp + --- Generates a random [Erdős-Rényi] graph with fixed edge + probabilities_::) for the unipartite version, + ‘igraph_bipartite_game_gnm()’ (*note igraph_bipartite_game_gnm --- + Generate a random bipartite graph with a fixed number of edges_::) + for the ‘G(n1, n2, m)’ model. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + +File: igraph-docs.info, Node: igraph_bipartite_iea_game --- Generates a random bipartite multigraph through independent edge assignment_, Prev: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_, Up: Create two-mode networks + +13.2.5 igraph_bipartite_iea_game -- Generates a random bipartite multigraph through independent edge assignment. +---------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_bipartite_iea_game( + igraph_t *graph, igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This model generates random multigraphs with ‘n1’ bottom vertices, +‘n2’ top vertices and ‘m’ edges through independent edge assignment +(IEA). Each of the ‘m’ edges is assigned uniformly at random to a vertex +pair, independently of each other. + + This model does not sample multigraphs uniformly. Undirected graphs +are generated with probability proportional to + + ‘(prod_(i. + + +File: igraph-docs.info, Node: Basic properties, Next: Sparsifiers, Up: Structural properties of graphs + +17.1 Basic properties +===================== + +* Menu: + +* igraph_are_adjacent -- Decides whether two vertices are adjacent.: igraph_are_adjacent --- Decides whether two vertices are adjacent_. + + +File: igraph-docs.info, Node: igraph_are_adjacent --- Decides whether two vertices are adjacent_, Up: Basic properties + +17.1.1 igraph_are_adjacent -- Decides whether two vertices are adjacent. +------------------------------------------------------------------------ + + + igraph_error_t igraph_are_adjacent(const igraph_t *graph, + igraph_int_t v1, igraph_int_t v2, + igraph_bool_t *res); + + Decides whether there are any edges that have ‘v1’ and ‘v2’ as +endpoints. This function is of course symmetric for undirected graphs. + + *Arguments:. * + +‘graph’: + The graph object. + +‘v1’: + The first vertex. + +‘v2’: + The second vertex. + +‘res’: + Boolean, ‘true’ if there is an edge from ‘v1’ to ‘v2’, ‘false’ + otherwise. + + *Returns:. * + +‘’ + The error code ‘IGRAPH_EINVVID’ is returned if an invalid vertex ID + is given. + + Time complexity: O( min(log(d1), log(d2)) ), d1 is the (out-)degree +of ‘v1’ and d2 is the (in-)degree of ‘v2’. + + +File: igraph-docs.info, Node: Sparsifiers, Next: [Shortest]-path related functions, Prev: Basic properties, Up: Structural properties of graphs + +17.2 Sparsifiers +================ + +* Menu: + +* igraph_spanner -- Calculates a spanner of a graph with a given stretch factor.: igraph_spanner --- Calculates a spanner of a graph with a given stretch factor_. + + +File: igraph-docs.info, Node: igraph_spanner --- Calculates a spanner of a graph with a given stretch factor_, Up: Sparsifiers + +17.2.1 igraph_spanner -- Calculates a spanner of a graph with a given stretch factor. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_spanner(const igraph_t *graph, igraph_vector_int_t *spanner, + igraph_real_t stretch, const igraph_vector_t *weights); + + A spanner of a graph ‘G = (V,E)’ with a stretch ‘t’ is a subgraph ‘H += (V,Es)’ such that ‘Es’ is a subset of ‘E’ and the distance between any +pair of nodes in ‘H’ is at most ‘t’ times the distance in ‘G’. The +returned graph is always a spanner of the given graph with the specified +stretch. For weighted graphs the number of edges in the spanner is ‘O(k +n^(1 + 1 / k))’, where ‘k’ is ‘k = (t + 1) / 2’, ‘m’ is the number of +edges and ‘n’ is the number of nodes in ‘G’. For unweighted graphs the +number of edges is ‘O(n^(1 + 1 / k) + kn)’. + + This function is based on the algorithm of Baswana and Sen: "A Simple +and Linear Time Randomized Algorithm for Computing Sparse Spanners in +Weighted Graphs". https://doi.org/10.1002/rsa.20130 +(https://doi.org/10.1002/rsa.20130) + + *Arguments:. * + +‘graph’: + An undirected connected graph object. If the graph is directed, + the directions of the edges will be ignored. + +‘spanner’: + An initialized vector, the IDs of the edges that constitute the + calculated spanner will be returned here. Use + ‘igraph_subgraph_from_edges()’ (*note igraph_subgraph_from_edges + --- Creates a subgraph with the specified edges and their + endpoints_::) to extract the spanner as a separate graph object. + +‘stretch’: + The stretch factor ‘t’ of the spanner. + +‘weights’: + The edge weights or ‘NULL’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: The algorithm is a randomized Las Vegas algorithm. +The expected running time is O(km) where k is the value mentioned above +and m is the number of edges. + + +File: igraph-docs.info, Node: [Shortest]-path related functions, Next: Widest-path related functions, Prev: Sparsifiers, Up: Structural properties of graphs + +17.3 (Shortest)-path related functions +====================================== + +* Menu: + +* igraph_distances -- Length of the shortest paths between vertices.: igraph_distances --- Length of the shortest paths between vertices_. +* igraph_distances_cutoff -- Length of the shortest paths between vertices, with cutoff.: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_. +* igraph_distances_dijkstra -- Weighted shortest path lengths between vertices.: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_. +* igraph_distances_dijkstra_cutoff -- Weighted shortest path lengths between vertices, with cutoff.: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_. +* igraph_distances_bellman_ford -- Weighted shortest path lengths between vertices, allowing negative weights.: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_. +* igraph_distances_johnson -- Weighted shortest path lengths between vertices, using Johnson's algorithm.: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_. +* igraph_distances_floyd_warshall -- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm.: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_. +* igraph_get_shortest_paths -- Shortest paths from a vertex.: igraph_get_shortest_paths --- Shortest paths from a vertex_. +* igraph_get_shortest_path -- Shortest path from one vertex to another one.: igraph_get_shortest_path --- Shortest path from one vertex to another one_. +* igraph_get_shortest_paths_dijkstra -- Weighted shortest paths from a vertex.: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_. +* igraph_get_shortest_path_dijkstra -- Weighted shortest path from one vertex to another one (Dijkstra).: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_. +* igraph_get_shortest_paths_bellman_ford -- Weighted shortest paths from a vertex, allowing negative weights.: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_. +* igraph_get_shortest_path_bellman_ford -- Weighted shortest path from one vertex to another one (Bellman-Ford).: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_. +* igraph_get_shortest_path_astar -- A* gives the shortest path from one vertex to another, with heuristic.: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_. +* igraph_astar_heuristic_func_t -- Distance estimator for A* algorithm.: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_. +* igraph_get_all_shortest_paths -- All shortest paths (geodesics) from a vertex.: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_. +* igraph_get_all_shortest_paths_dijkstra -- All weighted shortest paths (geodesics) from a vertex.: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_. +* igraph_get_k_shortest_paths -- k shortest paths between two vertices.: igraph_get_k_shortest_paths --- k shortest paths between two vertices_. +* igraph_get_all_simple_paths -- List all simple paths from one source.: igraph_get_all_simple_paths --- List all simple paths from one source_. +* igraph_average_path_length -- The average shortest path length between all vertex pairs.: igraph_average_path_length --- The average shortest path length between all vertex pairs_. +* igraph_path_length_hist -- Create a histogram of all shortest path lengths.: igraph_path_length_hist --- Create a histogram of all shortest path lengths_. +* igraph_diameter -- Calculates the weighted diameter of a graph using Dijkstra's algorithm.: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_. +* igraph_girth -- The girth of a graph is the length of the shortest cycle in it.: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_. +* igraph_eccentricity -- Eccentricity of some vertices.: igraph_eccentricity --- Eccentricity of some vertices_. +* igraph_radius -- Radius of a graph, using weighted edges.: igraph_radius --- Radius of a graph; using weighted edges_. +* igraph_graph_center -- Central vertices of a graph.: igraph_graph_center --- Central vertices of a graph_. +* igraph_pseudo_diameter -- Approximation and lower bound of the diameter of a graph.: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_. +* igraph_voronoi -- Voronoi partitioning of a graph.: igraph_voronoi --- Voronoi partitioning of a graph_. +* igraph_vertex_path_from_edge_path -- Converts a walk of edge IDs to the traversed vertex IDs.: igraph_vertex_path_from_edge_path --- Converts a walk of edge IDs to the traversed vertex IDs_. + + +File: igraph-docs.info, Node: igraph_distances --- Length of the shortest paths between vertices_, Next: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_, Up: [Shortest]-path related functions + +17.3.1 igraph_distances -- Length of the shortest paths between vertices. +------------------------------------------------------------------------- + + + igraph_error_t igraph_distances( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode); + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights. If ‘NULL’, the graph is considered + unweighted, i.e. all edge weights are 1. + +‘res’: + The result of the calculation, a matrix. A pointer to an + initialized matrix, to be more precise. The matrix will be resized + if needed. It will have the same number of rows as the length of + the ‘from’ argument, and its number of columns is the number of + vertices in the ‘to’ argument. One row of the matrix shows the + distances from/to a given vertex to the ones in ‘to’. For the + unreachable vertices ‘IGRAPH_INFINITY’ is returned. + +‘from’: + The source vertices. + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(n(|V|+|E|)), n is the number of vertices to +calculate, |V| and |E| are the number of vertices and edges in the +graph. + + *See also:. * + +‘’ + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::) to get the paths themselves, + ‘igraph_distances_dijkstra()’ (*note igraph_distances_dijkstra --- + Weighted shortest path lengths between vertices_::) for the + weighted version with non-negative weights, + ‘igraph_distances_bellman_ford()’ (*note + igraph_distances_bellman_ford --- Weighted shortest path lengths + between vertices; allowing negative weights_::) if you also have + negative weights. + + * File examples/simple/distances.c* + + +File: igraph-docs.info, Node: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_, Next: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_, Prev: igraph_distances --- Length of the shortest paths between vertices_, Up: [Shortest]-path related functions + +17.3.2 igraph_distances_cutoff -- Length of the shortest paths between vertices, with cutoff. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_distances_cutoff( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_real_t cutoff); + + This function is similar to ‘igraph_distances()’ (*note +igraph_distances --- Length of the shortest paths between vertices_::), +but paths longer than ‘cutoff’ will not be considered. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights. If ‘NULL’, the graph is considered + unweighted, i.e. all edge weights are equal to 1. + +‘res’: + The result of the calculation, a matrix. A pointer to an + initialized matrix, to be more precise. The matrix will be resized + if needed. It will have the same number of rows as the length of + the ‘from’ argument, and its number of columns is the number of + vertices in the ‘to’ argument. One row of the matrix shows the + distances from/to a given vertex to the ones in ‘to’. For the + unreachable vertices ‘IGRAPH_INFINITY’ is returned. + +‘from’: + The source vertices._d + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘cutoff’: + The maximal length of paths that will be considered. When the + distance of two vertices is greater than this value, it will be + returned as ‘IGRAPH_INFINITY’. Negative cutoffs and + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::) are treated as infinity. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(s |E| + |V|), where s is the number of source +vertices to use, and |V| and |E| are the number of vertices and edges in +the graph. + + *See also:. * + +‘’ + ‘igraph_distances_dijkstra_cutoff()’ (*note + igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths + between vertices; with cutoff_::) for the weighted version with + non-negative weights. + + * File examples/simple/distances.c* + + +File: igraph-docs.info, Node: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_, Next: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_, Prev: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_, Up: [Shortest]-path related functions + +17.3.3 igraph_distances_dijkstra -- Weighted shortest path lengths between vertices. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_distances_dijkstra( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + This function implements Dijkstra's algorithm, which can find the +weighted shortest path lengths from a source vertex to all other +vertices. This function allows specifying a set of source and target +vertices. The algorithm is run independently for each source and the +results are retained only for the specified targets. This +implementation uses a binary heap for efficiency. + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘res’: + The result, a matrix. A pointer to an initialized matrix should be + passed here. The matrix will be resized as needed. Each row + contains the distances from a single source, to the vertices given + in the ‘to’ argument. Unreachable vertices have distance + ‘IGRAPH_INFINITY’. + +‘from’: + The source vertices. + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) is called. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(s*|E|log|V|+|V|), where |V| is the number of +vertices, |E| the number of edges and s the number of sources. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) for a non-algorithm-specific + interface or ‘igraph_distances_bellman_ford()’ (*note + igraph_distances_bellman_ford --- Weighted shortest path lengths + between vertices; allowing negative weights_::) for a weighted + variant that works in the presence of negative edge weights (but no + negative loops) + + * File examples/simple/distances.c* + + +File: igraph-docs.info, Node: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_, Next: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_, Prev: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_, Up: [Shortest]-path related functions + +17.3.4 igraph_distances_dijkstra_cutoff -- Weighted shortest path lengths between vertices, with cutoff. +-------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_distances_dijkstra_cutoff( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_real_t cutoff); + + This function is similar to ‘igraph_distances_dijkstra()’ (*note +igraph_distances_dijkstra --- Weighted shortest path lengths between +vertices_::), but paths longer than ‘cutoff’ will not be considered. + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘res’: + The result, a matrix. A pointer to an initialized matrix should be + passed here. The matrix will be resized as needed. Each row + contains the distances from a single source, to the vertices given + in the ‘to’ argument. Vertices that are not reachable within + distance ‘cutoff’ will be assigned distance ‘IGRAPH_INFINITY’. + +‘from’: + The source vertices. + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) is called. Edges with positive + infinite weights are ignored. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + +‘cutoff’: + The maximal length of paths that will be considered. When the + distance of two vertices is greater than this value, it will be + returned as ‘IGRAPH_INFINITY’. Negative cutoffs and + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::) are treated as infinity. + + *Returns:. * + +‘’ + Error code. + + Time complexity: at most O(s |E| log|V| + |V|), where |V| is the +number of vertices, |E| the number of edges and s the number of sources. +The ‘cutoff’ parameter will limit the number of edges traversed from +each source vertex, which reduces the computation time. + + *See also:. * + +‘’ + ‘igraph_distances_cutoff()’ (*note igraph_distances_cutoff --- + Length of the shortest paths between vertices; with cutoff_::) for + a (slightly) faster unweighted version. + + * File examples/simple/distances.c* + + +File: igraph-docs.info, Node: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_, Next: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_, Prev: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_, Up: [Shortest]-path related functions + +17.3.5 igraph_distances_bellman_ford -- Weighted shortest path lengths between vertices, allowing negative weights. +------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_distances_bellman_ford( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + This function implements the Bellman-Ford algorithm to find the +weighted shortest paths to all vertices from a single source, allowing +negative weights. It is run independently for the given sources. If +there are no negative weights, you are better off with +‘igraph_distances_dijkstra()’ (*note igraph_distances_dijkstra --- +Weighted shortest path lengths between vertices_::) . + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘res’: + The result, a matrix. A pointer to an initialized matrix should be + passed here, the matrix will be resized if needed. Each row + contains the distances from a single source, to all vertices in the + graph, in the order of vertex IDs. For unreachable vertices the + matrix contains ‘IGRAPH_INFINITY’. + +‘from’: + The source vertices. + +‘to’: + The target vertices. + +‘weights’: + The edge weights. There must not be any cycle in the graph that + has a negative total weight (since this would allow us to decrease + the weight of any path containing at least a single vertex of this + cycle infinitely). Additionally, no edge weight may be NaN. If + either case does not hold, an error is returned. If this is a null + pointer, then the unweighted version, ‘igraph_distances()’ (*note + igraph_distances --- Length of the shortest paths between + vertices_::) is called. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(s*|E|*|V|), where |V| is the number of vertices, +|E| the number of edges and s the number of sources. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) for a non-algorithm-specific + interface; ‘igraph_distances_dijkstra()’ (*note + igraph_distances_dijkstra --- Weighted shortest path lengths + between vertices_::) if you do not have negative edge weights. + + * File examples/simple/bellman_ford.c* + + +File: igraph-docs.info, Node: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_, Next: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_, Prev: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_, Up: [Shortest]-path related functions + +17.3.6 igraph_distances_johnson -- Weighted shortest path lengths between vertices, using Johnson's algorithm. +-------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_distances_johnson( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + This algorithm supports directed graphs with negative edge weights, +and performs better than the Bellman-Ford method when distances are +calculated from many different sources, the typical use case being +all-pairs distance calculations. It works by using a single-source +Bellman-Ford run to transform all edge weights to non-negative ones, +then invoking Dijkstra's algorithm with the new weights. See the +Wikipedia page for more details: +http://en.wikipedia.org/wiki/Johnson's_algorithm +(http://en.wikipedia.org/wiki/Johnson's_algorithm). + + If no edge weights are supplied, then the unweighted version, +‘igraph_distances()’ (*note igraph_distances --- Length of the shortest +paths between vertices_::) is called. If none of the supplied edge +weights are negative, then Dijkstra's algorithm is used by calling +‘igraph_distances_dijkstra()’ (*note igraph_distances_dijkstra --- +Weighted shortest path lengths between vertices_::). + + Note that Johnson's algorithm applies only to directed graphs. This +function rejects undirected graphs with _any_ negative edge weights, +even when the ‘from’ and ‘to’ vertices are all in connected components +that are free of negative weights. + + References: + + Donald B. Johnson: Efficient Algorithms for Shortest Paths in Sparse +Networks. J. ACM 24, 1 (1977), 1-13. +https://doi.org/10.1145/321992.321993 +(https://doi.org/10.1145/321992.321993) + + *Arguments:. * + +‘graph’: + The input graph. If negative weights are present, it should be + directed. + +‘res’: + Pointer to an initialized matrix, the result will be stored here, + one line for each source vertex, one column for each target vertex. + +‘from’: + The source vertices. + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘weights’: + Optional edge weights. If it is a null-pointer, then the + unweighted breadth-first search based ‘igraph_distances()’ (*note + igraph_distances --- Length of the shortest paths between + vertices_::) will be called. Edges with positive infinite weights + are ignored. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. ‘IGRAPH_ALL’ should not be used with negative weights. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(s|V|log|V|+|V||E|), |V| and |E| are the number of +vertices and edges, s is the number of source vertices. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) for a faster unweighted + version, ‘igraph_distances_dijkstra()’ (*note + igraph_distances_dijkstra --- Weighted shortest path lengths + between vertices_::) if you do not have negative edge weights, + ‘igraph_distances_bellman_ford()’ (*note + igraph_distances_bellman_ford --- Weighted shortest path lengths + between vertices; allowing negative weights_::) if you only need to + calculate shortest paths from a couple of sources. + + +File: igraph-docs.info, Node: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_, Next: igraph_get_shortest_paths --- Shortest paths from a vertex_, Prev: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_, Up: [Shortest]-path related functions + +17.3.7 igraph_distances_floyd_warshall -- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm. +--------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_distances_floyd_warshall( + const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, igraph_neimode_t mode, + const igraph_floyd_warshall_algorithm_t method); + + The Floyd-Warshall algorithm computes weighted shortest path lengths +between all pairs of vertices at the same time. It is useful with very +dense weighted graphs, as its running time is primarily determined by +the vertex count, and is not sensitive to the graph density. In sparse +graphs, other methods such as the Dijkstra or Bellman-Ford algorithms +will perform significantly better. + + In addition to the original Floyd-Warshall algorithm, igraph contains +implementations of variants that offer better asymptotic complexity as +well as better practical running times for most instances. See the +reference below for more information. + + Note that internally this function always computes the distance +matrix for all pairs of vertices. The ‘from’ and ‘to’ parameters only +serve to subset this matrix, but do not affect the time or memory taken +by the calculation. + + Reference: + + Brodnik, A., Grgurovič, M., Požar, R.: Modifications of the +Floyd-Warshall algorithm with nearly quadratic expected-time, Ars +Mathematica Contemporanea, vol. 22, issue 1, p. #P1.01 (2021). +https://doi.org/10.26493/1855-3974.2467.497 +(https://doi.org/10.26493/1855-3974.2467.497) + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + An intialized matrix, the distances will be stored here. + +‘from’: + The source vertices. + +‘to’: + The target vertices. + +‘weights’: + The edge weights. If ‘NULL’, all weights are assumed to be 1. + Negative weights are allowed, but the graph must not contain + negative cycles. Edges with positive infinite weights are ignored. + +‘mode’: + The type of shortest paths to be use for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘method’: + The type of the algorithm used. + + ‘IGRAPH_FLOYD_WARSHALL_AUTOMATIC’ + tries to select the best performing variant for the current + graph; presently this option always uses the "Tree" method. + + ‘IGRAPH_FLOYD_WARSHALL_ORIGINAL’ + the basic Floyd-Warshall algorithm. + + ‘IGRAPH_FLOYD_WARSHALL_TREE’ + the "Tree" speedup of Brodnik et al., faster than the original + algorithm in most cases. + + *Returns:. * + +‘’ + Error code. ‘IGRAPH_ENEGCYCLE’ is returned if a negative-weight + cycle is found. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::), ‘igraph_distances_dijkstra()’ + (*note igraph_distances_dijkstra --- Weighted shortest path lengths + between vertices_::), ‘igraph_distances_bellman_ford()’ (*note + igraph_distances_bellman_ford --- Weighted shortest path lengths + between vertices; allowing negative weights_::), + ‘igraph_distances_johnson()’ (*note igraph_distances_johnson --- + Weighted shortest path lengths between vertices; using Johnson's + algorithm_::) + + Time complexity: The original variant has complexity O(|V|^3 + |E|). +The "Tree" variant has expected-case complexity of O(|V|^2 log^2 |V|) +according to Brodnik et al., while its worst-time complexity remains +O(|V|^3). Here |V| denotes the number of vertices and |E| is the number +of edges. + + +File: igraph-docs.info, Node: igraph_get_shortest_paths --- Shortest paths from a vertex_, Next: igraph_get_shortest_path --- Shortest path from one vertex to another one_, Prev: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_, Up: [Shortest]-path related functions + +17.3.8 igraph_get_shortest_paths -- Shortest paths from a vertex. +----------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_paths( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + + Finds unweighted shortest paths from a single source vertex to the +specified sets of target vertices. If there is more than one geodesic +between two vertices, this function gives only one of them. Use +‘igraph_get_all_shortest_paths()’ (*note igraph_get_all_shortest_paths +--- All shortest paths [geodesics] from a vertex_::) to find _all_ +shortest paths. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights. If ‘NULL’, the graph is considered + unweighted, i.e. all edge weights are equal to 1. + +‘vertices’: + The result, the IDs of the vertices along the paths. This is a + list of integer vectors where each element is an + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + object. The list will be resized as needed. Supply a null pointer + here if you don't need these vectors. + +‘edges’: + The result, the IDs of the edges along the paths. This is a list + of integer vectors where each element is an ‘igraph_vector_int_t’ + (*note About igraph_vector_t objects::) object. The list will be + resized as needed. Supply a null pointer here if you don't need + these vectors. + +‘from’: + The ID of the vertex from/to which the geodesics are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + shortest paths will be calculated. A vertex might be given + multiple times. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘parents’: + A pointer to an initialized igraph vector or ‘NULL’. If not + ‘NULL’, a vector containing the parent of each vertex in the single + source shortest path tree is returned here. The parent of vertex + ‘i’ in the tree is the vertex from which vertex ‘i’ was reached. + The parent of the start vertex (in the ‘from’ argument) is -1. If + the parent is -2, it means that the given vertex was not reached + from the source during the search. Note that the search terminates + if all the vertices in ‘to’ are reached. + +‘inbound_edges’: + A pointer to an initialized igraph vector or ‘NULL’. If not + ‘NULL’, a vector containing the inbound edge of each vertex in the + single source shortest path tree is returned here. The inbound + edge of vertex ‘i’ in the tree is the edge via which vertex ‘i’ was + reached. The start vertex and vertices that were not reached + during the search will have -1 in the corresponding entry of the + vector. Note that the search terminates if all the vertices in + ‘to’ are reached. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ is invalid vertex ID + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|V|+|E|), |V| is the number of vertices, |E| the +number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) if you only need the path + lengths but not the paths themselves; + ‘igraph_get_shortest_paths_dijkstra()’ (*note + igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from + a vertex_::) for the weighted version; + ‘igraph_get_all_shortest_paths()’ (*note + igraph_get_all_shortest_paths --- All shortest paths [geodesics] + from a vertex_::) to return all shortest paths between (source, + target) pairs. + + * File examples/simple/igraph_get_shortest_paths.c* + + +File: igraph-docs.info, Node: igraph_get_shortest_path --- Shortest path from one vertex to another one_, Next: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_, Prev: igraph_get_shortest_paths --- Shortest paths from a vertex_, Up: [Shortest]-path related functions + +17.3.9 igraph_get_shortest_path -- Shortest path from one vertex to another one. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_path( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, igraph_int_t to, + igraph_neimode_t mode); + + Calculates and returns a single unweighted shortest path from a given +vertex to another one. If there is more than one shortest path between +the two vertices, then an arbitrary one is returned. + + This function is a wrapper to ‘igraph_get_shortest_paths()’ (*note +igraph_get_shortest_paths --- Shortest paths from a vertex_::) for the +special case when only one target vertex is considered. + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. Directed paths + are considered in directed graphs. + +‘weights’: + Optional edge weights. If ‘NULL’, the graph is considered + unweighted, i.e. all edge weights are equal to 1. + +‘vertices’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the vertex IDs along the path are stored here, + including the source and target vertices. + +‘edges’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the edge IDs along the path are stored here. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. Valid modes are: ‘IGRAPH_OUT’, follows edge + directions; ‘IGRAPH_IN’, follows the opposite directions; and + ‘IGRAPH_ALL’, ignores edge directions. This argument is ignored + for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges in the graph. + + *See also:. * + +‘’ + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::) for the version with more target + vertices. + + +File: igraph-docs.info, Node: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_, Next: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_, Prev: igraph_get_shortest_path --- Shortest path from one vertex to another one_, Up: [Shortest]-path related functions + +17.3.10 igraph_get_shortest_paths_dijkstra -- Weighted shortest paths from a vertex. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_get_shortest_paths_dijkstra( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + + Finds weighted shortest paths from a single source vertex to the +specified sets of target vertices using Dijkstra's algorithm. If there +is more than one path with the smallest weight between two vertices, +this function gives only one of them. To find all such paths, use +‘igraph_get_all_shortest_paths_dijkstra()’ (*note +igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths +[geodesics] from a vertex_::). + + *Arguments:. * + +‘graph’: + The graph object. + +‘vertices’: + The result, the IDs of the vertices along the paths. This is a + list of integer vectors where each element is an + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + object. The list will be resized as needed. Supply a null pointer + here if you don't need these vectors. + +‘edges’: + The result, the IDs of the edges along the paths. This is a list + of integer vectors where each element is an ‘igraph_vector_int_t’ + (*note About igraph_vector_t objects::) object. The list will be + resized as needed. Supply a null pointer here if you don't need + these vectors. + +‘from’: + The id of the vertex from/to which the geodesics are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + shortest paths will be calculated. A vertex might be given + multiple times. * + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::) is called. + +‘mode’: + The type of shortest paths to be use for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘parents’: + A pointer to an initialized igraph vector or null. If not null, a + vector containing the parent of each vertex in the single source + shortest path tree is returned here. The parent of vertex i in the + tree is the vertex from which vertex i was reached. The parent of + the start vertex (in the ‘from’ argument) is -1. If the parent is + -2, it means that the given vertex was not reached from the source + during the search. Note that the search terminates if all the + vertices in ‘to’ are reached. + +‘inbound_edges’: + A pointer to an initialized igraph vector or null. If not null, a + vector containing the inbound edge of each vertex in the single + source shortest path tree is returned here. The inbound edge of + vertex i in the tree is the edge via which vertex i was reached. + The start vertex and vertices that were not reached during the + search will have -1 in the corresponding entry of the vector. Note + that the search terminates if all the vertices in ‘to’ are reached. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ is invalid vertex ID + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|E|log|V|+|V|), where |V| is the number of +vertices and |E| is the number of edges + + *See also:. * + +‘’ + ‘igraph_distances_dijkstra()’ (*note igraph_distances_dijkstra --- + Weighted shortest path lengths between vertices_::) if you only + need the path lengths but not the paths themselves; + ‘igraph_get_all_shortest_paths_dijkstra()’ (*note + igraph_get_all_shortest_paths_dijkstra --- All weighted shortest + paths [geodesics] from a vertex_::) to find all shortest paths + between (source, target) pairs; ‘igraph_get_shortest_paths()’ + (*note igraph_get_shortest_paths --- Shortest paths from a + vertex_::) for a non-algorithm-specific interface. + + * File examples/simple/igraph_get_shortest_paths_dijkstra.c* + + +File: igraph-docs.info, Node: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_, Next: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_, Prev: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_, Up: [Shortest]-path related functions + +17.3.11 igraph_get_shortest_path_dijkstra -- Weighted shortest path from one vertex to another one (Dijkstra). +-------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_path_dijkstra(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + Finds a weighted shortest path from a single source vertex to a +single target, using Dijkstra's algorithm. If more than one shortest +path exists, an arbitrary one is returned. + + This function is a special case (and a wrapper) to +‘igraph_get_shortest_paths_dijkstra()’ (*note +igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a +vertex_::). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘vertices’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the vertex IDs along the path are stored here, + including the source and target vertices. + +‘edges’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the edge IDs along the path are stored here. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::) is called. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. ‘IGRAPH_OUT’ follows edge directions, ‘IGRAPH_IN’ + follows the opposite directions, and ‘IGRAPH_ALL’ ignores edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|log|V|+|V|), |V| is the number of vertices, |E| +is the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_get_shortest_paths_dijkstra()’ (*note + igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from + a vertex_::) for the version with more target vertices. + + +File: igraph-docs.info, Node: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_, Next: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_, Prev: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_, Up: [Shortest]-path related functions + +17.3.12 igraph_get_shortest_paths_bellman_ford -- Weighted shortest paths from a vertex, allowing negative weights. +------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_paths_bellman_ford( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + + This function calculates weighted shortest paths from or to a single +vertex using the Bellman-Ford algorithm, whihc can handle negative +weights. When there is more than one shortest path between two +vertices, only one of them is returned. When there are no negative +weights, ‘igraph_get_shortest_paths_dijkstra()’ (*note +igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a +vertex_::) is likely to be faster. + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘vertices’: + The result, the IDs of the vertices along the paths. This is a + list of integer vectors where each element is an + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + object. The list will be resized as needed. Supply a null pointer + here if you don't need these vectors. + +‘edges’: + The result, the IDs of the edges along the paths. This is a list + of integer vectors where each element is an ‘igraph_vector_int_t’ + (*note About igraph_vector_t objects::) object. The list will be + resized as needed. Supply a null pointer here if you don't need + these vectors. + +‘from’: + The id of the vertex from/to which the geodesics are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + shortest paths will be calculated. A vertex might be given + multiple times. + +‘weights’: + The edge weights. There must not be any cycle in the graph that + has a negative total weight (since this would allow us to decrease + the weight of any path containing at least a single vertex of this + cycle infinitely). If this is a null pointer, then the unweighted + version, ‘igraph_get_shortest_paths()’ (*note + igraph_get_shortest_paths --- Shortest paths from a vertex_::) is + called. Edges with positive infinite weights are ignored. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + +‘parents’: + A pointer to an initialized igraph vector or null. If not null, a + vector containing the parent of each vertex in the single source + shortest path tree is returned here. The parent of vertex i in the + tree is the vertex from which vertex i was reached. The parent of + the start vertex (in the ‘from’ argument) is -1. If the parent is + -2, it means that the given vertex was not reached from the source + during the search. Note that the search terminates if all the + vertices in ‘to’ are reached. + +‘inbound_edges’: + A pointer to an initialized igraph vector or null. If not null, a + vector containing the inbound edge of each vertex in the single + source shortest path tree is returned here. The inbound edge of + vertex i in the tree is the edge via which vertex i was reached. + The start vertex and vertices that were not reached during the + search will have -1 in the corresponding entry of the vector. Note + that the search terminates if all the vertices in ‘to’ are reached. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + Not enough memory for temporary data. + + ‘IGRAPH_EINVAL’ + The weight vector doesn't math the number of edges. + + ‘IGRAPH_EINVVID’ + ‘from’ is invalid vertex ID + + ‘IGRAPH_ENEGCYCLE’ + Bellman-ford algorithm encounted a negative cycle. + + Time complexity: O(|E|*|V|), where |V| is the number of vertices, |E| +the number of edges. + + *See also:. * + +‘’ + ‘igraph_distances_bellman_ford()’ (*note + igraph_distances_bellman_ford --- Weighted shortest path lengths + between vertices; allowing negative weights_::) to compute only + shortest path lengths, but not the paths themselves; + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::) for a non-algorithm-specific + interface. + + +File: igraph-docs.info, Node: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_, Next: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_, Prev: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_, Up: [Shortest]-path related functions + +17.3.13 igraph_get_shortest_path_bellman_ford -- Weighted shortest path from one vertex to another one (Bellman-Ford). +---------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_path_bellman_ford(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + Finds a weighted shortest path from a single source vertex to a +single target using the Bellman-Ford algorithm. + + This function is a special case (and a wrapper) to +‘igraph_get_shortest_paths_bellman_ford()’ (*note +igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from +a vertex; allowing negative weights_::). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘vertices’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the vertex IDs along the path are stored here, + including the source and target vertices. + +‘edges’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the edge IDs along the path are stored here. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘weights’: + The edge weights. There must not be any cycle in the graph that + has a negative total weight (since this would allow us to decrease + the weight of any path containing at least a single vertex of this + cycle infinitely). If this is a null pointer, then the unweighted + version is called. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. ‘IGRAPH_OUT’ follows edge directions, ‘IGRAPH_IN’ + follows the opposite directions, and ‘IGRAPH_ALL’ ignores edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|log|E|+|V|), |V| is the number of vertices, |E| +is the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_get_shortest_paths_bellman_ford()’ (*note + igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths + from a vertex; allowing negative weights_::) for the version with + more target vertices. + + +File: igraph-docs.info, Node: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_, Next: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_, Prev: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_, Up: [Shortest]-path related functions + +17.3.14 igraph_get_shortest_path_astar -- A* gives the shortest path from one vertex to another, with heuristic. +---------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_shortest_path_astar(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_astar_heuristic_func_t *heuristic, + void *extra); + + Calculates a shortest path from a single source vertex to a single +target, using the A* algorithm. A* tries to find a shortest path by +starting at ‘from’ and moving to vertices that lie on a path with the +lowest estimated length. This length estimate is the sum of two +numbers: the distance from the source (‘from’) to the intermediate +vertex, and the value returned by the heuristic function. The heuristic +function provides an estimate the distance between intermediate +candidate vertices and the target vertex ‘to’. The A* algorithm is +guaranteed to give the correct shortest path (if one exists) only if the +heuristic does not overestimate distances, i.e. if the heuristic +function is _admissible._ + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘vertices’: + Pointer to an initialized vector or the ‘NULL’ pointer. If not + ‘NULL’, then the vertex IDs along the path are stored here, + including the source and target vertices. + +‘edges’: + Pointer to an initialized vector or the ‘NULL’ pointer. If not + ‘NULL’, then the edge IDs along the path are stored here. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘weights’: + Optional edge weights. Supply ‘NULL’ for unweighted graphs. All + edge weights must be non-negative. Additionally, no edge weight + may be NaN. If either case does not hold, an error is returned. + Edges with positive infinite weights are ignored. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. ‘IGRAPH_OUT’ follows edge directions, ‘IGRAPH_IN’ + follows the opposite directions, and ‘IGRAPH_ALL’ ignores edge + directions. This argument is ignored for undirected graphs. + +‘heuristic’: + A function that provides distance estimates to the target vertex. + See ‘igraph_astar_heuristic_func_t’ (*note + igraph_astar_heuristic_func_t --- Distance estimator for A* + algorithm_::) for more information. + +‘extra’: + This is passed on to the heuristic function. + + *Returns:. * + +‘’ + Error code. + + Time complexity: In the worst case, O(|E|log|V|+|V|), where |V| is +the number of vertices and |E| is the number of edges in the graph. The +running time depends on the accuracy of the distance estimates returned +by the heuristic function. Assuming that the heuristic is admissible, +the better the estimates, the shortert the running time. + + +File: igraph-docs.info, Node: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_, Next: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_, Prev: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_, Up: [Shortest]-path related functions + +17.3.15 igraph_astar_heuristic_func_t -- Distance estimator for A* algorithm. +----------------------------------------------------------------------------- + + + typedef igraph_error_t igraph_astar_heuristic_func_t( + igraph_real_t *result, + igraph_int_t from, igraph_int_t to, + void *extra); + + ‘igraph_get_shortest_path_astar()’ (*note +igraph_get_shortest_path_astar --- A* gives the shortest path from one +vertex to another; with heuristic_::) uses a heuristic based on a +distance estimate to the target vertex to guide its search, and +determine which vertex to try next. The heuristic function is expected +to compute an estimate of the distance between ‘from’ and ‘to’. In +order for ‘igraph_get_shortest_path_astar()’ (*note +igraph_get_shortest_path_astar --- A* gives the shortest path from one +vertex to another; with heuristic_::) to find an exact shortest path, +the distance must not be overestimated, i.e. the heuristic function +must be _admissible._ + + *Arguments:. * + +‘result’: + The result of the heuristic, i.e. the estimated distance. A lower + value will mean this vertex will be a better candidate for + exploration. + +‘from’: + The vertex ID of the candidate vertex will be passed here. + +‘to’: + The vertex ID of the endpoint of the path, i.e. the ‘to’ parameter + given to ‘igraph_get_shortest_path_astar()’ (*note + igraph_get_shortest_path_astar --- A* gives the shortest path from + one vertex to another; with heuristic_::), will be passed here. + +‘extra’: + The ‘extra’ argument that was passed to + ‘igraph_get_shortest_path_astar()’ (*note + igraph_get_shortest_path_astar --- A* gives the shortest path from + one vertex to another; with heuristic_::). + + *Returns:. * + +‘’ + Error code. Must return ‘IGRAPH_SUCCESS’ if there were no errors. + This can be used to break off the algorithm if something unexpected + happens, like a failed memory allocation (‘IGRAPH_ENOMEM’). + + *See also:. * + +‘’ + ‘igraph_get_shortest_path_astar()’ (*note + igraph_get_shortest_path_astar --- A* gives the shortest path from + one vertex to another; with heuristic_::) + + +File: igraph-docs.info, Node: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_, Next: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_, Prev: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_, Up: [Shortest]-path related functions + +17.3.16 igraph_get_all_shortest_paths -- All shortest paths (geodesics) from a vertex. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_all_shortest_paths( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode); + + When there is more than one shortest path between two vertices, all +of them will be returned. Every edge is considered separately, +therefore in graphs with multi-edges, this function may produce a very +large number of results. + + *Arguments:. * + +‘graph’: + The graph object. + +‘vertices’: + The result, the IDs of the vertices along the paths. This is a + list of integer vectors where each element is an + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + object. Each vector object contains the vertices along a shortest + path from ‘from’ to another vertex. The vectors are ordered + according to their target vertex: first the shortest paths to + vertex 0, then to vertex 1, etc. No data is included for + unreachable vertices. The list will be resized as needed. Supply + a null pointer here if you don't need these vectors. + +‘edges’: + The result, the IDs of the edges along the paths. This is a list + of integer vectors where each element is an ‘igraph_vector_int_t’ + (*note About igraph_vector_t objects::) object. Each vector object + contains the edges along a shortest path from ‘from’ to another + vertex. The vectors are ordered according to their target vertex: + first the shortest paths to vertex 0, then to vertex 1, etc. No + data is included for unreachable vertices. The list will be + resized as needed. Supply a null pointer here if you don't need + these vectors. + +‘nrgeo’: + Pointer to an initialized ‘igraph_vector_int_t’ (*note About + igraph_vector_t objects::) object or ‘NULL’. If not ‘NULL’ the + number of shortest paths from ‘from’ are stored here for every + vertex in the graph. Note that the values will be accurate only + for those vertices that are in the target vertex sequence (see + ‘to’), since the search terminates as soon as all the target + vertices have been found. + +‘from’: + The id of the vertex from/to which the geodesics are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + shortest paths will be calculated. A vertex might be given + multiple times. + +‘mode’: + The type of shortest paths to be use for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ is invalid vertex ID. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Added in version 0.2. + + Time complexity: O(|V|+|E|) for most graphs, O(|V|^2) in the worst +case. + + +File: igraph-docs.info, Node: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_, Next: igraph_get_k_shortest_paths --- k shortest paths between two vertices_, Prev: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_, Up: [Shortest]-path related functions + +17.3.17 igraph_get_all_shortest_paths_dijkstra -- All weighted shortest paths (geodesics) from a vertex. +-------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_all_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + *Arguments:. * + +‘graph’: + The graph object. + +‘vertices’: + Pointer to an initialized integer vector list or NULL. If not NULL, + then each vector object contains the vertices along a shortest path + from ‘from’ to another vertex. The vectors are ordered according + to their target vertex: first the shortest paths to vertex 0, then + to vertex 1, etc. No data is included for unreachable vertices. + +‘edges’: + Pointer to an initialized integer vector list or NULL. If not NULL, + then each vector object contains the edges along a shortest path + from ‘from’ to another vertex. The vectors are ordered according + to their target vertex: first the shortest paths to vertex 0, then + to vertex 1, etc. No data is included for unreachable vertices. + +‘nrgeo’: + Pointer to an initialized igraph_vector_int_t object or NULL. If + not NULL the number of shortest paths from ‘from’ are stored here + for every vertex in the graph. Note that the values will be + accurate only for those vertices that are in the target vertex + sequence (see ‘to’), since the search terminates as soon as all the + target vertices have been found. + +‘from’: + The id of the vertex from/to which the geodesics are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + shortest paths will be calculated. A vertex might be given + multiple times. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_get_all_shortest_paths()’ (*note + igraph_get_all_shortest_paths --- All shortest paths [geodesics] + from a vertex_::) is called. + +‘mode’: + The type of shortest paths to be use for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ is an invalid vertex ID + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|E|log|V|+|V|), where |V| is the number of +vertices and |E| is the number of edges + + *See also:. * + +‘’ + ‘igraph_distances_dijkstra()’ (*note igraph_distances_dijkstra --- + Weighted shortest path lengths between vertices_::) if you only + need the path lengths but not the paths themselves, + ‘igraph_get_all_shortest_paths()’ (*note + igraph_get_all_shortest_paths --- All shortest paths [geodesics] + from a vertex_::) if all edge weights are equal. + + * File examples/simple/igraph_get_all_shortest_paths_dijkstra.c* + + +File: igraph-docs.info, Node: igraph_get_k_shortest_paths --- k shortest paths between two vertices_, Next: igraph_get_all_simple_paths --- List all simple paths from one source_, Prev: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_, Up: [Shortest]-path related functions + +17.3.18 igraph_get_k_shortest_paths -- k shortest paths between two vertices. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_get_k_shortest_paths( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *vertex_paths, + igraph_vector_int_list_t *edge_paths, + igraph_int_t k, igraph_int_t from, igraph_int_t to, + igraph_neimode_t mode + ); + + This function returns the ‘k’ shortest paths between two vertices, in +order of increasing lengths. + + Reference: + + Yen, Jin Y.: An algorithm for finding shortest routes from all source +nodes to a given destination in general networks. Quarterly of Applied +Mathematics. 27 (4): 526-530. (1970) +https://doi.org/10.1090/qam/253822 (https://doi.org/10.1090/qam/253822) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + The edge weights of the graph. Can be ‘NULL’ for an unweighted + graph. Infinite weights will be treated as missing edges. + +‘vertex_paths’: + Pointer to an initialized list of integer vectors, the result will + be stored here in ‘igraph_vector_int_t’ (*note About + igraph_vector_t objects::) objects. Each vector object contains + the vertex IDs along the ‘k’th shortest path between ‘from’ and + ‘to’, where ‘k’ is the vector list index. May be ‘NULL’ if the + vertex paths are not needed. + +‘edge_paths’: + Pointer to an initialized list of integer vectors, the result will + be stored here in ‘igraph_vector_int_t’ (*note About + igraph_vector_t objects::) objects. Each vector object contains + the edge IDs along the ‘k’th shortest path between ‘from’ and ‘to’, + where ‘k’ is the vector list index. May be ‘NULL’ if the edge + paths are not needed. + +‘k’: + The number of paths. + +‘from’: + The ID of the vertex from which the paths are calculated. + +‘to’: + The ID of the vertex to which the paths are calculated. + +‘mode’: + The type of paths to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + The outgoing paths of ‘from’ are calculated. + + ‘IGRAPH_IN’ + The incoming paths of ‘from’ are calculated. + + ‘IGRAPH_ALL’ + The directed graph is considered as an undirected one for the + computation. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + Not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ or ‘to’ is an invalid vertex id. + + ‘IGRAPH_EINVMODE’ + Invalid mode argument. + + ‘IGRAPH_EINVAL’ + Invalid argument. + + Time complexity: k |V| (|V| log|V| + |E|), where |V| is the number of +vertices, and |E| is the number of edges. + + *See also:. * + +‘’ + ‘igraph_get_all_simple_paths()’ (*note igraph_get_all_simple_paths + --- List all simple paths from one source_::), + ‘igraph_get_shortest_paths()’ (*note igraph_get_shortest_paths --- + Shortest paths from a vertex_::), + ‘igraph_get_shortest_paths_dijkstra()’ (*note + igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from + a vertex_::) + + +File: igraph-docs.info, Node: igraph_get_all_simple_paths --- List all simple paths from one source_, Next: igraph_average_path_length --- The average shortest path length between all vertex pairs_, Prev: igraph_get_k_shortest_paths --- k shortest paths between two vertices_, Up: [Shortest]-path related functions + +17.3.19 igraph_get_all_simple_paths -- List all simple paths from one source. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_get_all_simple_paths( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_int_t minlen, igraph_int_t maxlen, + igraph_int_t max_results); + + A path is simple if its vertices are unique, i.e. no vertex is +visited more than once. This function returns paths in terms of their +vertices and ignores multi-edges. + + Note that potentially there are exponentially many paths between two +vertices of a graph, and you may run out of memory when using this +function when the graph has many cycles. Consider using the ‘minlen’ +and ‘maxlen’ parameters to restrict the paths that are returned. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Initialized integer vector list. The paths are returned here in + terms of their vertices. The paths are included in arbitrary + order, as they are found. + +‘from’: + The start vertex. + +‘to’: + The target vertices. + +‘mode’: + The type of paths to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + outgoing paths are calculated. + + ‘IGRAPH_IN’ + incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘minlen’: + Minimum length of paths that is considered. If negative or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no lower bound is used on the path lengths. + +‘maxlen’: + Maximum length of paths that is considered. If negative or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no upper bound is used on the path lengths. + +‘max_results’: + At most this many paths will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_get_k_shortest_paths()’ (*note igraph_get_k_shortest_paths + --- k shortest paths between two vertices_::) + + Time complexity: O(n!) in the worst case, n is the number of +vertices. + + +File: igraph-docs.info, Node: igraph_average_path_length --- The average shortest path length between all vertex pairs_, Next: igraph_path_length_hist --- Create a histogram of all shortest path lengths_, Prev: igraph_get_all_simple_paths --- List all simple paths from one source_, Up: [Shortest]-path related functions + +17.3.20 igraph_average_path_length -- The average shortest path length between all vertex pairs. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_average_path_length( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_real_t *unconn_pairs, igraph_bool_t directed, igraph_bool_t unconn + ); + + If no vertex pairs can be included in the calculation, for example +because the graph has fewer than two vertices, or if the graph has no +edges and ‘unconn’ is set to ‘true’, NaN is returned. + + All distinct ordered vertex pairs are taken into account. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. Passa a + null pointer here if the graph is unweighted. Edges with positive + infinite weight are ignored. + +‘res’: + Pointer to a real number, this will contain the result. + +‘unconn_pairs’: + Pointer to a real number. If not a null pointer, the number of + ordered vertex pairs where the second vertex is unreachable from + the first one will be stored here. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + +‘unconn’: + If ‘true’, only those pairs are considered for the calculation + between which there is a path. If ‘false’, ‘IGRAPH_INFINITY’ is + returned for disconnected graphs. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for data structures + + ‘IGRAPH_EINVAL’ + invalid weight vector + + Time complexity: O(|V| |E| log|E| + |V|), where |V| is the number of +vertices and |E| is the number of edges. + + * File examples/simple/igraph_grg_game.c* + + +File: igraph-docs.info, Node: igraph_path_length_hist --- Create a histogram of all shortest path lengths_, Next: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_, Prev: igraph_average_path_length --- The average shortest path length between all vertex pairs_, Up: [Shortest]-path related functions + +17.3.21 igraph_path_length_hist -- Create a histogram of all shortest path lengths. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_path_length_hist(const igraph_t *graph, igraph_vector_t *res, + igraph_real_t *unconnected, igraph_bool_t directed); + + This function calculates a histogram by calculating the shortest path +length between all pairs of vertices. In directed graphs, both +directions are considered, meaning that each vertex pair appears twice +in the histogram. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized vector, the result is stored here. The + first (i.e. index 0) element contains the number of shortest paths + of length 1, the second of length 2, etc. The supplied vector is + resized as needed. + +‘unconnected’: + Pointer to a real number, the number of vertex pairs for which the + second vertex is not reachable from the first is stored here. + +‘directed’: + Whether to consider directed paths in a directed graph. This + argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V||E|), the number of vertices times the number +of edges. + + *See also:. * + +‘’ + ‘igraph_average_path_length()’ (*note igraph_average_path_length + --- The average shortest path length between all vertex pairs_::) + and ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::) + + +File: igraph-docs.info, Node: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_, Next: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_, Prev: igraph_path_length_hist --- Create a histogram of all shortest path lengths_, Up: [Shortest]-path related functions + +17.3.22 igraph_diameter -- Calculates the weighted diameter of a graph using Dijkstra's algorithm. +-------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_diameter( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_int_t *from, igraph_int_t *to, igraph_vector_int_t *vertex_path, + igraph_vector_int_t *edge_path, igraph_bool_t directed, igraph_bool_t unconn + ); + + This function computes the weighted diameter of a graph, defined as +the longest weighted shortest path, or the maximum weighted eccentricity +of the graph's vertices. A corresponding shortest path, as well as its +endpoints, can also be optionally computed. + + If the graph has no vertices, ‘IGRAPH_NAN’ is returned. + + *Arguments:. * + +‘graph’: + The input graph, can be directed or undirected. + +‘weights’: + The edge weights of the graph. Can be ‘NULL’ for an unweighted + graph. Edges with positive infinite weight are ignored. + +‘res’: + Pointer to a real number, if not ‘NULL’ then it will contain the + diameter (the actual distance). + +‘from’: + Pointer to an integer, if not ‘NULL’ it will be set to the source + vertex of the diameter path. If the graph has no diameter path, it + will be set to -1. + +‘to’: + Pointer to an integer, if not ‘NULL’ it will be set to the target + vertex of the diameter path. If the graph has no diameter path, it + will be set to -1. + +‘vertex_path’: + Pointer to an initialized vector. If not ‘NULL’ the actual longest + geodesic path in terms of vertices will be stored here. The vector + will be resized as needed. + +‘edge_path’: + Pointer to an initialized vector. If not ‘NULL’ the actual longest + geodesic path in terms of edges will be stored here. The vector + will be resized as needed. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + +‘unconn’: + What to do if the graph is not connected. If ‘true’ the longest + geodesic within a component will be returned, otherwise + ‘IGRAPH_INFINITY’ is returned. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V||E|*log|E|), |V| is the number of vertices, |E| +is the number of edges. + + *See also:. * + +‘’ + ‘igraph_radius()’ (*note igraph_radius --- Radius of a graph; using + weighted edges_::) for the minimum eccentricity. + + * File examples/simple/igraph_diameter.c* + + +File: igraph-docs.info, Node: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_, Next: igraph_eccentricity --- Eccentricity of some vertices_, Prev: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_, Up: [Shortest]-path related functions + +17.3.23 igraph_girth -- The girth of a graph is the length of the shortest cycle in it. +--------------------------------------------------------------------------------------- + + + igraph_error_t igraph_girth(const igraph_t *graph, + igraph_real_t *girth, + igraph_vector_int_t *cycle); + + The current implementation works for undirected graphs only, directed +graphs are treated as undirected graphs. Self-loops and multiple edges +are ignored, i.e. cycles of length 1 or 2 are not considered. + + For graphs that contain no cycles, and only for such graphs, infinity +is returned. + + The first implementation of this function was done by Keith Briggs, +thanks Keith. + + Reference: + + Alon Itai and Michael Rodeh: Finding a minimum circuit in a graph _ +Proceedings of the ninth annual ACM symposium on Theory of computing _, +1-10, 1977. https://doi.org/10.1145/800105.803390 +(https://doi.org/10.1145/800105.803390) + + *Arguments:. * + +‘graph’: + The input graph. Edge directions will be ignored. + +‘girth’: + Pointer to an ‘igraph_real_t’, if not ‘NULL’ then the result will + be stored here. + +‘cycle’: + Pointer to an initialized vector, the vertex IDs in the shortest + cycle of length at least 3 will be stored here. If ‘NULL’ then it + is ignored. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O((|V|+|E|)^2), |V| is the number of vertices, |E| +is the number of edges in the general case. If the graph has no cycles +at all then the function needs O(|V|+|E|) time to realize this and then +it stops. + + * File examples/simple/igraph_girth.c* + + +File: igraph-docs.info, Node: igraph_eccentricity --- Eccentricity of some vertices_, Next: igraph_radius --- Radius of a graph; using weighted edges_, Prev: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_, Up: [Shortest]-path related functions + +17.3.24 igraph_eccentricity -- Eccentricity of some vertices. +------------------------------------------------------------- + + + igraph_error_t igraph_eccentricity( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + igraph_vs_t vids, igraph_neimode_t mode + ); + + The eccentricity of a vertex is calculated by measuring the shortest +distance from (or to) the vertex, to (or from) all vertices in the +graph, and taking the maximum. + + This implementation ignores vertex pairs that are in different +components. Isolated vertices have eccentricity zero. + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. Use a + null pointer to calculate the unweighted eccentricities. Edges + with positive infinite weights are ignored. + +‘res’: + Pointer to an initialized vector, the result is stored here. + +‘vids’: + The vertices for which the eccentricity is calculated. + +‘mode’: + What kind of paths to consider for the calculation: ‘IGRAPH_OUT’, + paths that follow edge directions; ‘IGRAPH_IN’, paths that follow + the opposite directions; and ‘IGRAPH_ALL’, paths that ignore edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of +vertices, |E| the number of edges. + + * File examples/simple/igraph_eccentricity.c* + + +File: igraph-docs.info, Node: igraph_radius --- Radius of a graph; using weighted edges_, Next: igraph_graph_center --- Central vertices of a graph_, Prev: igraph_eccentricity --- Eccentricity of some vertices_, Up: [Shortest]-path related functions + +17.3.25 igraph_radius -- Radius of a graph, using weighted edges. +----------------------------------------------------------------- + + + igraph_error_t igraph_radius( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *radius, igraph_neimode_t mode + ); + + The radius of a graph is the defined as the minimum eccentricity of +its vertices, see ‘igraph_eccentricity()’ (*note igraph_eccentricity --- +Eccentricity of some vertices_::). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, ‘igraph_radius()’ + (*note igraph_radius --- Radius of a graph; using weighted + edges_::) is called. Edges with positive infinite weights are + ignored. + +‘radius’: + Pointer to a real variable, the result is stored here. + +‘mode’: + What kind of paths to consider for the calculation: ‘IGRAPH_OUT’, + paths that follow edge directions; ‘IGRAPH_IN’, paths that follow + the opposite directions; and ‘IGRAPH_ALL’, paths that ignore edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of +vertices, |E| the number of edges. + + *See also:. * + +‘’ + ‘igraph_diameter()’ (*note igraph_diameter --- Calculates the + weighted diameter of a graph using Dijkstra's algorithm_::) for the + maximum eccentricity, ‘igraph_eccentricity()’ (*note + igraph_eccentricity --- Eccentricity of some vertices_::) for + eccentricities of all vertices. + + +File: igraph-docs.info, Node: igraph_graph_center --- Central vertices of a graph_, Next: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_, Prev: igraph_radius --- Radius of a graph; using weighted edges_, Up: [Shortest]-path related functions + +17.3.26 igraph_graph_center -- Central vertices of a graph. +----------------------------------------------------------- + + + igraph_error_t igraph_graph_center( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_int_t *res, + igraph_neimode_t mode + ); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + The central vertices of a graph are calculated by finding the +vertices with the minimum eccentricity. The concept of the graph center +is typically applied to (strongly) connected graphs. In disconnected +graphs, the smallest eccentricity is taken across all components. + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. Pass a + null pointer here if all edges have equal weight. Edges with + positive infinite weights are ignored. + +‘res’: + Pointer to an initialized vector, the result is stored here. + +‘mode’: + What kind of paths to consider for the calculation: ‘IGRAPH_OUT’, + paths that follow edge directions; ‘IGRAPH_IN’, paths that follow + the opposite directions; and ‘IGRAPH_ALL’, paths that ignore edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of +vertices, |E| the number of edges. + + *See also:. * + +‘’ + ‘igraph_graph_center()’ (*note igraph_graph_center --- Central + vertices of a graph_::), ‘igraph_eccentricity()’ (*note + igraph_eccentricity --- Eccentricity of some vertices_::), + ‘igraph_radius()’ (*note igraph_radius --- Radius of a graph; using + weighted edges_::) + + +File: igraph-docs.info, Node: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_, Next: igraph_voronoi --- Voronoi partitioning of a graph_, Prev: igraph_graph_center --- Central vertices of a graph_, Up: [Shortest]-path related functions + +17.3.27 igraph_pseudo_diameter -- Approximation and lower bound of the diameter of a graph. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_pseudo_diameter( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *diameter, igraph_int_t vid_start, + igraph_int_t *from, igraph_int_t *to, + igraph_bool_t directed, igraph_bool_t unconn + ); + + This algorithm finds a pseudo-peripheral vertex and returns its +eccentricity. This value can be used as an approximation and lower +bound of the diameter of a graph. + + A pseudo-peripheral vertex is a vertex v, such that for every vertex +u which is as far away from v as possible, v is also as far away from u +as possible. The process of finding one depends on where the search +starts, and for a disconnected graph the maximum diameter found will be +that of the component ‘vid_start’ is in. + + If the graph has no vertices, ‘IGRAPH_NAN’ is returned. + + *Arguments:. * + +‘graph’: + The input graph, can be directed or undirected. + +‘weights’: + The edge weights of the graph. Can be ‘NULL’ for an unweighted + graph. All weights should be non-negative. Edges with positive + infinite weights are ignored. + +‘diameter’: + This will contain the pseudo-diameter. + +‘vid_start’: + Id of the starting vertex. If this is negative, a random starting + vertex is chosen. + +‘from’: + If not ‘NULL’ this will be set to the source vertex of the diameter + path. If the graph has no diameter path, it will be set to -1. + +‘to’: + If not ‘NULL’ this will be set to the target vertex of the diameter + path. If the graph has no diameter path, it will be set to -1. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + +‘unconn’: + What to do if the graph is not connected. If ‘true’ the longest + geodesic within a component will be returned, otherwise + ‘IGRAPH_INFINITY’ is returned. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V| |E| log|E|), |V| is the number of vertices, +|E| is the number of edges. + + *See also:. * + +‘’ + ‘igraph_diameter()’ (*note igraph_diameter --- Calculates the + weighted diameter of a graph using Dijkstra's algorithm_::) + + +File: igraph-docs.info, Node: igraph_voronoi --- Voronoi partitioning of a graph_, Next: igraph_vertex_path_from_edge_path --- Converts a walk of edge IDs to the traversed vertex IDs_, Prev: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_, Up: [Shortest]-path related functions + +17.3.28 igraph_voronoi -- Voronoi partitioning of a graph. +---------------------------------------------------------- + + + igraph_error_t igraph_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_t *distances, + const igraph_vector_int_t *generators, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_voronoi_tiebreaker_t tiebreaker); + + To obtain a Voronoi partitioning of a graph, we start with a set of +generator vertices, which will define the partitions. Each vertex is +assigned to the generator vertex from (or to) which it is closest. + + This function uses a BFS search for unweighted graphs and Dijkstra's +algorithm for weights ones. + + *Arguments:. * + +‘graph’: + The graph to partition. + +‘membership’: + If not ‘NULL’, the Voronoi partition of each vertex will be stored + here. ‘membership[v]’ will be set to the index in ‘generators’ of + the generator vertex that ‘v’ belongs to. For vertices that are + not reachable from any generator, ‘-1’ is returned. + +‘distances’: + If not ‘NULL’, the distance of each vertex to its respective + generator will be stored here. For vertices which are not + reachable from any generator, ‘IGRAPH_INFINITY’ is returned. + +‘generators’: + Vertex IDs of the generator vertices. + +‘weights’: + The edge weights, interpreted as lengths in the shortest path + calculation. All weights must be non-negative. + +‘mode’: + In directed graphs, whether to compute distances _from_ generator + vertices to other vertices (‘IGRAPH_OUT’), _to_ generator vertices + from other vertices (‘IGRAPH_IN’), or ignore edge directions + entirely (‘IGRAPH_ALL’). + +‘tiebreaker’: + Controls which generator vertex to assign a vertex to when it is at + equal distance from/to multiple generator vertices. + + ‘IGRAPH_VORONOI_FIRST assign the vertex to the first generator vertex.’ + + ‘IGRAPH_VORONOI_LAST assign the vertex to the last generator vertex.’ + + ‘IGRAPH_VORONOI_RANDOM assign the vertex to a random generator vertex.’ + + Note that ‘IGRAPH_VORONOI_RANDOM’ does not guarantee that all + partitions will be contiguous. For example, if 1 and 2 are chosen + as generators for the graph ‘1-3, 2-3, 3-4’, then 3 and 4 are at + equal distance from both generators. If 3 is assigned to 2 but 4 + is assigned to 1, then the partition {1, 4} will not induce a + connected subgraph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: In weighted graphs, O((log |S|) |E| (log |V|) + +|V|), and in unweighted graphs O((log |S|) |E| + |V|), where |S| is the +number of generator vertices, and |V| and |E| are the number of vertices +and edges in the graph. + + *See also:. * + +‘’ + ‘igraph_distances()’ (*note igraph_distances --- Length of the + shortest paths between vertices_::), ‘igraph_distances_dijkstra()’ + (*note igraph_distances_dijkstra --- Weighted shortest path lengths + between vertices_::). + + +File: igraph-docs.info, Node: igraph_vertex_path_from_edge_path --- Converts a walk of edge IDs to the traversed vertex IDs_, Prev: igraph_voronoi --- Voronoi partitioning of a graph_, Up: [Shortest]-path related functions + +17.3.29 igraph_vertex_path_from_edge_path -- Converts a walk of edge IDs to the traversed vertex IDs. +----------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_vertex_path_from_edge_path( + const igraph_t *graph, igraph_int_t start, + const igraph_vector_int_t *edge_path, igraph_vector_int_t *vertex_path, + igraph_neimode_t mode + ); + + This function is useful when you have a sequence of edge IDs +representing a continuous walk in a graph and you would like to obtain +the vertex IDs that the walk traverses. The function is used implicitly +by several shortest path related functions to convert a path of edge IDs +to the corresponding representation that describes the path in terms of +vertex IDs instead. + + The result will always contain one more vertex than the number of +provided edges. If no edges are given, the output will contain only the +start vertex. + + The walk is allowed to traverse the same vertex more than once. It +is suitable for use on paths, cycles, or arbitrary walks. + + *Arguments:. * + +‘graph’: + The graph that the edge IDs refer to. + +‘start’: + The start vertex of the path. If negative, it is determined + automatically. This is only possible if the walk contains at least + one edge. If only one edge is present in an undirected walk, one + of its endpoints will be selected arbitrarily. + +‘edge_path’: + The sequence of edge IDs that describe the path. + +‘vertex_path’: + The sequence of vertex IDs traversed will be returned here. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. ‘IGRAPH_OUT’ follows edge directions, ‘IGRAPH_IN’ + follows the opposite directions, and ‘IGRAPH_ALL’ ignores edge + directions. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory, + ‘IGRAPH_EINVVID’ if the start vertex is invalid, ‘IGRAPH_EINVAL’ if + the edge walk does not start at the given vertex or if there is at + least one edge whose start vertex does not match the end vertex of + the previous edge. + + Time complexity: O(n) where n is the length of the walk. + + +File: igraph-docs.info, Node: Widest-path related functions, Next: Efficiency measures, Prev: [Shortest]-path related functions, Up: Structural properties of graphs + +17.4 Widest-path related functions +================================== + +* Menu: + +* igraph_get_widest_path -- Widest path from one vertex to another one.: igraph_get_widest_path --- Widest path from one vertex to another one_. +* igraph_get_widest_paths -- Widest paths from a single vertex.: igraph_get_widest_paths --- Widest paths from a single vertex_. +* igraph_widest_path_widths_dijkstra -- Widths of widest paths between vertices.: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_. +* igraph_widest_path_widths_floyd_warshall -- Widths of widest paths between vertices.: igraph_widest_path_widths_floyd_warshall --- Widths of widest paths between vertices_. + + +File: igraph-docs.info, Node: igraph_get_widest_path --- Widest path from one vertex to another one_, Next: igraph_get_widest_paths --- Widest paths from a single vertex_, Up: Widest-path related functions + +17.4.1 igraph_get_widest_path -- Widest path from one vertex to another one. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_get_widest_path(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + Calculates a single widest path from a single vertex to another one, +using Dijkstra's algorithm. + + This function is a special case (and a wrapper) to +‘igraph_get_widest_paths()’ (*note igraph_get_widest_paths --- Widest +paths from a single vertex_::). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘vertices’: + Pointer to an initialized vector or ‘NULL’. If not ‘NULL’, then + the vertex IDs along the path are stored here, including the source + and target vertices. + +‘edges’: + Pointer to an initialized vector or ‘NULL’. If not ‘NULL’, then + the edge IDs along the path are stored here. + +‘from’: + The ID of the source vertex. + +‘to’: + The ID of the target vertex. + +‘weights’: + The edge weights, interpreted as widths. Edge weights can be + negative, but must not be NaN. Edges with negative infinite weight + are ignored. The weight vector is required: if ‘NULL’ is passed, + an error is raised. + +‘mode’: + The type of widest paths to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|log|E|+|V|), |V| is the number of vertices, |E| +is the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_get_widest_paths()’ (*note igraph_get_widest_paths --- + Widest paths from a single vertex_::) for the version with more + target vertices. + + +File: igraph-docs.info, Node: igraph_get_widest_paths --- Widest paths from a single vertex_, Next: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_, Prev: igraph_get_widest_path --- Widest path from one vertex to another one_, Up: Widest-path related functions + +17.4.2 igraph_get_widest_paths -- Widest paths from a single vertex. +-------------------------------------------------------------------- + + + igraph_error_t igraph_get_widest_paths(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + + Calculates the widest paths from a single vertex to all other +specified vertices, using a modified Dijkstra's algorithm. The width of +a path is defined as the width of the narrowest edge in the path.If +there is more than one path with the largest width between two vertices, +this function gives only one of them. + + *Arguments:. * + +‘graph’: + The graph object. + +‘vertices’: + The result, the IDs of the vertices along the paths. This is a + list of integer vectors where each element is an + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + object. The list will be resized as needed. Supply a null pointer + here if you don't need these vectors. + +‘edges’: + The result, the IDs of the edges along the paths. This is a list + of integer vectors where each element is an ‘igraph_vector_int_t’ + (*note About igraph_vector_t objects::) object. The vector list + will be resized as needed. Supply a null pointer here if you don't + need these vectors. + +‘from’: + The ID of the vertex from/to which the widest paths are calculated. + +‘to’: + Vertex sequence with the IDs of the vertices to/from which the + widest paths will be calculated. A vertex may be given multiple + times. + +‘weights’: + The edge weights, interpreted as widths. Edge weights can be + negative, but must not be NaN. Edges with negative infinite weight + are ignored. The weight vector is required: if ‘NULL’ is passed, + an error is raised. + +‘mode’: + The type of widest paths to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘parents’: + A pointer to an initialized igraph vector or null. If not null, a + vector containing the parent of each vertex in the single source + widest path tree is returned here. The parent of vertex ‘i’ in the + tree is the vertex from which vertex ‘i’ was reached. The parent + of the start vertex (in the ‘from’ argument) is -1. If the parent + is -2, it means that the given vertex was not reached from the + source during the search. The search terminates when all the + vertices in ‘to’ have been reached. + +‘inbound_edges’: + A pointer to an initialized igraph vector or ‘NULL’. If not + ‘NULL’, a vector containing the inbound edge of each vertex in the + single source widest path tree is returned here. The inbound edge + of vertex ‘i’ in the tree is the edge via which vertex ‘i’ was + reached. The start vertex and vertices that were not reached + during the search will have -1 in the corresponding entry of the + vector. The search terminates when all the vertices in ‘to’ have + been reached. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘from’ is invalid vertex ID + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|E|log|E|+|V|), where |V| is the number of +vertices in the graph and |E| is the number of edges + + *See also:. * + +‘’ + ‘igraph_widest_path_widths_dijkstra()’ (*note + igraph_widest_path_widths_dijkstra --- Widths of widest paths + between vertices_::) or + ‘igraph_widest_path_widths_floyd_warshall()’ (*note + igraph_widest_path_widths_floyd_warshall --- Widths of widest paths + between vertices_::) if you only need the widths of the paths but + not the paths themselves. + + +File: igraph-docs.info, Node: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_, Next: igraph_widest_path_widths_floyd_warshall --- Widths of widest paths between vertices_, Prev: igraph_get_widest_paths --- Widest paths from a single vertex_, Up: Widest-path related functions + +17.4.3 igraph_widest_path_widths_dijkstra -- Widths of widest paths between vertices. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_widest_path_widths_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + This function implements a modified Dijkstra's algorithm, which can +find the widest path widths from a source vertex to all other vertices. +The width of a path is defined as the width of the narrowest edge in the +path. + + This function allows specifying a set of source and target vertices. +The algorithm is run independently for each source and the results are +retained only for the specified targets. This implementation uses a +binary heap for efficiency. + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘res’: + An initialized matrix, the result will be written here. The matrix + will be resized as needed. Each row will contain the widths from a + single source to the vertices given in the ‘to’ argument. + Unreachable vertices have width ‘-IGRAPH_INFINITY’, and vertices + have a width of ‘IGRAPH_INFINITY’ to themselves. + +‘from’: + The source vertices. + +‘to’: + The target vertices. It is not allowed to include a vertex twice + or more. + +‘weights’: + The edge weights, interpreted as widths. Edge weights can be + negative, but must not be NaN. Edges with negative infinite weight + are ignored. The weight vector is required: if ‘NULL’ is passed, + an error is raised. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(s*(|E|log|E|+|V|)), where |V| is the number of +vertices in the graph, |E| the number of edges and s the number of +sources. + + *See also:. * + +‘’ + ‘igraph_widest_path_widths_floyd_warshall()’ (*note + igraph_widest_path_widths_floyd_warshall --- Widths of widest paths + between vertices_::) for a variant that runs faster on dense + graphs. + + +File: igraph-docs.info, Node: igraph_widest_path_widths_floyd_warshall --- Widths of widest paths between vertices_, Prev: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_, Up: Widest-path related functions + +17.4.4 igraph_widest_path_widths_floyd_warshall -- Widths of widest paths between vertices. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_widest_path_widths_floyd_warshall(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + This function implements a modified Floyd-Warshall algorithm, to find +the widest path widths between a set of source and target vertices. The +width of a path is defined as the width of the narrowest edge in the +path. + + This algorithm is primarily useful for all-pairs path widths in very +dense graphs, as its running time is manily determined by the vertex +count, and is not sensitive to the graph density. In sparse graphs, +other methods such as Dijkstra's algorithm, implemented in +‘igraph_widest_path_widths_dijkstra()’ (*note +igraph_widest_path_widths_dijkstra --- Widths of widest paths between +vertices_::) will perform better. + + Note that internally this function always computes the path width +matrix for all pairs of vertices. The ‘from’ and ‘to’ parameters only +serve to subset this matrix, but do not affect the time taken by the +calculation. + + *Arguments:. * + +‘graph’: + The input graph, can be directed. + +‘res’: + An initialized matrix, the result will be written here. The matrix + will be resized as needed. Each row will contain the widths from a + single source to the vertices given in the ‘to’ argument. + Unreachable vertices have width ‘-IGRAPH_INFINITY’, and vertices + have a width of ‘IGRAPH_INFINITY’ to themselves. + +‘from’: + The source vertices. + +‘to’: + The target vertices. + +‘weights’: + The edge weights, interpreted as widths. Edge weights can be + negative, but must not be NaN. Edges with negative infinite weight + are ignored. The weight vector is required: if ‘NULL’ is passed, + an error is raised. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3), where |V| is the number of vertices in the +graph. + + *See also:. * + +‘’ + ‘igraph_widest_path_widths_dijkstra()’ (*note + igraph_widest_path_widths_dijkstra --- Widths of widest paths + between vertices_::) for a variant that runs faster on sparse + graphs. + + +File: igraph-docs.info, Node: Efficiency measures, Next: Neighborhood of a vertex, Prev: Widest-path related functions, Up: Structural properties of graphs + +17.5 Efficiency measures +======================== + +* Menu: + +* igraph_global_efficiency -- Calculates the global efficiency of a network.: igraph_global_efficiency --- Calculates the global efficiency of a network_. +* igraph_local_efficiency -- Calculates the local efficiency around each vertex in a network.: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_. +* igraph_average_local_efficiency -- Calculates the average local efficiency in a network.: igraph_average_local_efficiency --- Calculates the average local efficiency in a network_. + + +File: igraph-docs.info, Node: igraph_global_efficiency --- Calculates the global efficiency of a network_, Next: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_, Up: Efficiency measures + +17.5.1 igraph_global_efficiency -- Calculates the global efficiency of a network. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_global_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *res, igraph_bool_t directed + ); + + The global efficiency of a network is defined as the average of +inverse distances between all pairs of vertices: ‘E_g = 1/(N*(N-1)) +sum_{i!=j} 1/d_ij’, where ‘N’ is the number of vertices. The inverse +distance between pairs that are not reachable from each other is +considered to be zero. For graphs with fewer than 2 vertices, NaN is +returned. + + Reference: + + V. Latora and M. Marchiori, Efficient Behavior of Small-World +Networks, Phys. Rev. Lett. 87, 198701 (2001). +https://dx.doi.org/10.1103/PhysRevLett.87.198701 +(https://dx.doi.org/10.1103/PhysRevLett.87.198701) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + The edge weights. All edge weights must be non-negative for + Dijkstra's algorithm to work. Additionally, no edge weight may be + NaN. If either case does not hold, an error is returned. If this + is a null pointer, then the unweighted version, + ‘igraph_average_path_length()’ (*note igraph_average_path_length + --- The average shortest path length between all vertex pairs_::) + is used in calculating the global efficiency. Edges with positive + infinite weights are ignored. + +‘res’: + Pointer to a real number, this will contain the result. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for data structures + + ‘IGRAPH_EINVAL’ + invalid weight vector + + Time complexity: O(|V| |E| log|E| + |V|) for weighted graphs and +O(|V| |E|) for unweighted ones. |V| denotes the number of vertices and +|E| denotes the number of edges. + + *See also:. * + +‘’ + ‘igraph_local_efficiency()’ (*note igraph_local_efficiency --- + Calculates the local efficiency around each vertex in a + network_::), ‘igraph_average_local_efficiency()’ (*note + igraph_average_local_efficiency --- Calculates the average local + efficiency in a network_::) + + +File: igraph-docs.info, Node: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_, Next: igraph_average_local_efficiency --- Calculates the average local efficiency in a network_, Prev: igraph_global_efficiency --- Calculates the global efficiency of a network_, Up: Efficiency measures + +17.5.2 igraph_local_efficiency -- Calculates the local efficiency around each vertex in a network. +-------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + const igraph_vs_t vids, igraph_bool_t directed, igraph_neimode_t mode + ); + + The local efficiency of a network around a vertex is defined as +follows: We remove the vertex and compute the distances (shortest path +lengths) between its neighbours through the rest of the network. The +local efficiency around the removed vertex is the average of the inverse +of these distances. + + The inverse distance between two vertices which are not reachable +from each other is considered to be zero. The local efficiency around a +vertex with fewer than two neighbours is taken to be zero by convention. + + Reference: + + I. Vragović, E. Louis, and A. Díaz-Guilera, Efficiency of +informational transfer in regular and complex networks, Phys. Rev. E +71, 1 (2005). http://dx.doi.org/10.1103/PhysRevE.71.036122 +(http://dx.doi.org/10.1103/PhysRevE.71.036122) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + The edge weights. All edge weights must be non-negative. + Additionally, no edge weight may be NaN. If either case does not + hold, an error is returned. If this is a null pointer, then the + unweighted version, ‘igraph_average_path_length()’ (*note + igraph_average_path_length --- The average shortest path length + between all vertex pairs_::) is called. Edges with positive + infinite weights are ignored. + +‘res’: + Pointer to an initialized vector, this will contain the result. + +‘vids’: + The vertices around which the local efficiency will be calculated. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + +‘mode’: + How to determine the local neighborhood of each vertex in directed + graphs. Ignored in undirected graphs. + + ‘IGRAPH_ALL’ + take both in- and out-neighbours; this is a reasonable default + for high-level interfaces. + + ‘IGRAPH_OUT’ + take only out-neighbours + + ‘IGRAPH_IN’ + take only in-neighbours + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for data structures + + ‘IGRAPH_EINVAL’ + invalid weight vector + + Time complexity: O(|E|^2 log|E|) for weighted graphs and O(|E|^2) for +unweighted ones. |E| denotes the number of edges. + + *See also:. * + +‘’ + ‘igraph_average_local_efficiency()’ (*note + igraph_average_local_efficiency --- Calculates the average local + efficiency in a network_::), ‘igraph_global_efficiency()’ (*note + igraph_global_efficiency --- Calculates the global efficiency of a + network_::) + + +File: igraph-docs.info, Node: igraph_average_local_efficiency --- Calculates the average local efficiency in a network_, Prev: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_, Up: Efficiency measures + +17.5.3 igraph_average_local_efficiency -- Calculates the average local efficiency in a network. +----------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_average_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_bool_t directed, igraph_neimode_t mode + ); + + For the null graph, zero is returned by convention. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + The edge weights. They must be all non-negative. If a null + pointer is given, all weights are assumed to be 1. Edges with + positive infinite weight are ignored. + +‘res’: + Pointer to a real number, this will contain the result. + +‘directed’: + Boolean, whether to consider directed paths. Ignored for + undirected graphs. + +‘mode’: + How to determine the local neighborhood of each vertex in directed + graphs. Ignored in undirected graphs. + + ‘IGRAPH_ALL’ + take both in- and out-neighbours; this is a reasonable default + for high-level interfaces. + + ‘IGRAPH_OUT’ + take only out-neighbours + + ‘IGRAPH_IN’ + take only in-neighbours + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for data structures + + ‘IGRAPH_EINVAL’ + invalid weight vector + + Time complexity: O(|E|^2 log|E|) for weighted graphs and O(|E|^2) for +unweighted ones. |E| denotes the number of edges. + + *See also:. * + +‘’ + ‘igraph_local_efficiency()’ (*note igraph_local_efficiency --- + Calculates the local efficiency around each vertex in a network_::) + + +File: igraph-docs.info, Node: Neighborhood of a vertex, Next: Local scan statistics, Prev: Efficiency measures, Up: Structural properties of graphs + +17.6 Neighborhood of a vertex +============================= + +* Menu: + +* igraph_neighborhood_size -- Calculates the size of the neighborhood of a given vertex.: igraph_neighborhood_size --- Calculates the size of the neighborhood of a given vertex_. +* igraph_neighborhood -- Calculate the neighborhood of vertices.: igraph_neighborhood --- Calculate the neighborhood of vertices_. +* igraph_neighborhood_graphs -- Create graphs from the neighborhood(s) of some vertex/vertices.: igraph_neighborhood_graphs --- Create graphs from the neighborhood[s] of some vertex/vertices_. + + +File: igraph-docs.info, Node: igraph_neighborhood_size --- Calculates the size of the neighborhood of a given vertex_, Next: igraph_neighborhood --- Calculate the neighborhood of vertices_, Up: Neighborhood of a vertex + +17.6.1 igraph_neighborhood_size -- Calculates the size of the neighborhood of a given vertex. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_neighborhood_size(const igraph_t *graph, igraph_vector_int_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, + igraph_int_t mindist); + + The neighborhood of a given order of a vertex includes all vertices +which are closer to the vertex than the order. I.e., order 0 is always +the vertex itself, order 1 is the vertex plus its immediate neighbors, +order 2 is order 1 plus the immediate neighbors of the vertices in order +1, etc. + + This function calculates the size of the neighborhood of the given +order for the given vertices. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized vector, the result will be stored here. + It will be resized as needed. + +‘vids’: + The vertices for which the calculation is performed. + +‘order’: + Integer giving the order of the neighborhood. Negative values are + treated as infinity. + +‘mode’: + Specifies how to use the direction of the edges if a directed graph + is analyzed. For ‘IGRAPH_OUT’ only the outgoing edges are + followed, so all vertices reachable from the source vertex in at + most ‘order’ steps are counted. For ‘IGRAPH_IN’ all vertices from + which the source vertex is reachable in at most ‘order’ steps are + counted. ‘IGRAPH_ALL’ ignores the direction of the edges. This + argument is ignored for undirected graphs. + +‘mindist’: + The minimum distance to include a vertex in the counting. Vertices + reachable with a path shorter than this value are excluded. If + this is one, then the starting vertex is not counted. If this is + two, then its neighbors are not counted either, etc. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_neighborhood()’ (*note igraph_neighborhood --- Calculate + the neighborhood of vertices_::) for calculating the actual + neighborhood; ‘igraph_neighborhood_graphs()’ (*note + igraph_neighborhood_graphs --- Create graphs from the + neighborhood[s] of some vertex/vertices_::) for creating separate + graphs from the neighborhoods. + + Time complexity: O(n*d*o), where n is the number vertices for which +the calculation is performed, d is the average degree, o is the order. + + +File: igraph-docs.info, Node: igraph_neighborhood --- Calculate the neighborhood of vertices_, Next: igraph_neighborhood_graphs --- Create graphs from the neighborhood[s] of some vertex/vertices_, Prev: igraph_neighborhood_size --- Calculates the size of the neighborhood of a given vertex_, Up: Neighborhood of a vertex + +17.6.2 igraph_neighborhood -- Calculate the neighborhood of vertices. +--------------------------------------------------------------------- + + + igraph_error_t igraph_neighborhood(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist); + + The neighborhood of a given order of a vertex includes all vertices +which are closer to the vertex than the order. I.e., order 0 is always +the vertex itself, order 1 is the vertex plus its immediate neighbors, +order 2 is order 1 plus the immediate neighbors of the vertices in order +1, etc. + + This function calculates the vertices within the neighborhood of the +specified vertices. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + An initialized list of integer vectors. The result of the + calculation will be stored here. The list will be resized as + needed. + +‘vids’: + The vertices for which the calculation is performed. + +‘order’: + Integer giving the order of the neighborhood. Negative values are + treated as infinity. + +‘mode’: + Specifies how to use the direction of the edges if a directed graph + is analyzed. For ‘IGRAPH_OUT’ only the outgoing edges are + followed, so all vertices reachable from the source vertex in at + most ‘order’ steps are included. For ‘IGRAPH_IN’ all vertices from + which the source vertex is reachable in at most ‘order’ steps are + included. ‘IGRAPH_ALL’ ignores the direction of the edges. This + argument is ignored for undirected graphs. + +‘mindist’: + The minimum distance to include a vertex in the counting. Vertices + reachable with a path shorter than this value are excluded. If + this is one, then the starting vertex is not counted. If this is + two, then its neighbors are not counted either, etc. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_neighborhood_size()’ (*note igraph_neighborhood_size --- + Calculates the size of the neighborhood of a given vertex_::) to + calculate the size of the neighborhood; + ‘igraph_neighborhood_graphs()’ (*note igraph_neighborhood_graphs + --- Create graphs from the neighborhood[s] of some + vertex/vertices_::) for creating graphs from the neighborhoods; + ‘igraph_subcomponent()’ (*note igraph_subcomponent --- The vertices + reachable from a given vertex_::) to find vertices reachable from a + single vertex. + + Time complexity: O(n*d*o), n is the number of vertices for which the +calculation is performed, d is the average degree, o is the order. + + +File: igraph-docs.info, Node: igraph_neighborhood_graphs --- Create graphs from the neighborhood[s] of some vertex/vertices_, Prev: igraph_neighborhood --- Calculate the neighborhood of vertices_, Up: Neighborhood of a vertex + +17.6.3 igraph_neighborhood_graphs -- Create graphs from the neighborhood(s) of some vertex/vertices. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_neighborhood_graphs(const igraph_t *graph, igraph_graph_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, + igraph_int_t mindist); + + The neighborhood of a given order of a vertex includes all vertices +which are closer to the vertex than the order. Ie. order 0 is always +the vertex itself, order 1 is the vertex plus its immediate neighbors, +order 2 is order 1 plus the immediate neighbors of the vertices in order +1, etc. + + This function finds every vertex in the neighborhood of a given +parameter vertex and creates the induced subgraph from these vertices. + + The first version of this function was written by Vincent Matossian, +thanks Vincent. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a list of graphs, the result will be stored here. Each + item in the list is an ‘igraph_t’ object. The list will be resized + as needed. + +‘vids’: + The vertices for which the calculation is performed. + +‘order’: + Integer giving the order of the neighborhood. Negative values are + treated as infinity. + +‘mode’: + Specifies how to use the direction of the edges if a directed graph + is analyzed. For ‘IGRAPH_OUT’ only the outgoing edges are + followed, so all vertices reachable from the source vertex in at + most ‘order’ steps are counted. For ‘IGRAPH_IN’ all vertices from + which the source vertex is reachable in at most ‘order’ steps are + counted. ‘IGRAPH_ALL’ ignores the direction of the edges. This + argument is ignored for undirected graphs. + +‘mindist’: + The minimum distance to include a vertex in the counting. Vertices + reachable with a path shorter than this value are excluded. If + this is one, then the starting vertex is not counted. If this is + two, then its neighbors are not counted either, etc. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_neighborhood_size()’ (*note igraph_neighborhood_size --- + Calculates the size of the neighborhood of a given vertex_::) for + calculating the neighborhood sizes only; ‘igraph_neighborhood()’ + (*note igraph_neighborhood --- Calculate the neighborhood of + vertices_::) for calculating the neighborhoods (but not creating + graphs). + + Time complexity: O(n*(|V|+|E|)), where n is the number vertices for +which the calculation is performed, |V| and |E| are the number of +vertices and edges in the original input graph. + + +File: igraph-docs.info, Node: Local scan statistics, Next: Graph components, Prev: Neighborhood of a vertex, Up: Structural properties of graphs + +17.7 Local scan statistics +========================== + +The scan statistic is a summary of the locality statistics that is +computed from the local neighborhood of each vertex. For details, see +Priebe, C. E., Conroy, J. M., Marchette, D. J., Park, Y. (2005). Scan +Statistics on Enron Graphs. Computational and Mathematical Organization +Theory. + +* Menu: + +* "Us" statistics:: +* "Them" statistics:: +* Pre-calculated subsets:: + + +File: igraph-docs.info, Node: "Us" statistics, Next: "Them" statistics, Up: Local scan statistics + +17.7.1 "Us" statistics +---------------------- + +* Menu: + +* igraph_local_scan_0 -- Local scan-statistics, k=0: igraph_local_scan_0 --- Local scan-statistics; k=0. +* igraph_local_scan_1_ecount -- Local scan-statistics, k=1, edge count and sum of weights: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights. +* igraph_local_scan_k_ecount -- Sum the number of edges or the weights in k-neighborhood of every vertex.: igraph_local_scan_k_ecount --- Sum the number of edges or the weights in k-neighborhood of every vertex_. + + +File: igraph-docs.info, Node: igraph_local_scan_0 --- Local scan-statistics; k=0, Next: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights, Up: "Us" statistics + +17.7.1.1 igraph_local_scan_0 -- Local scan-statistics, k=0 +.......................................................... + + + igraph_error_t igraph_local_scan_0(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + K=0 scan-statistics is arbitrarily defined as the vertex degree for +unweighted, and the vertex strength for weighted graphs. See +‘igraph_degree()’ (*note igraph_degree --- The degree of some vertices +in a graph_::) and ‘igraph_strength()’ (*note igraph_strength --- +Strength of the vertices; also called weighted vertex degree_::). + + *Arguments:. * + +‘graph’: + The input graph + +‘res’: + An initialized vector, the results are stored here. + +‘weights’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights, Next: igraph_local_scan_k_ecount --- Sum the number of edges or the weights in k-neighborhood of every vertex_, Prev: igraph_local_scan_0 --- Local scan-statistics; k=0, Up: "Us" statistics + +17.7.1.2 igraph_local_scan_1_ecount -- Local scan-statistics, k=1, edge count and sum of weights +................................................................................................ + + + igraph_error_t igraph_local_scan_1_ecount(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + Count the number of edges or the sum the edge weights in the +1-neighborhood of vertices. + + *Arguments:. * + +‘graph’: + The input graph + +‘res’: + An initialized vector, the results are stored here. + +‘weights’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_local_scan_k_ecount --- Sum the number of edges or the weights in k-neighborhood of every vertex_, Prev: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights, Up: "Us" statistics + +17.7.1.3 igraph_local_scan_k_ecount -- Sum the number of edges or the weights in k-neighborhood of every vertex. +................................................................................................................ + + + igraph_error_t igraph_local_scan_k_ecount(const igraph_t *graph, igraph_int_t k, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + + *Arguments:. * + +‘graph’: + The input graph. + +‘k’: + The size of the neighborhood, non-negative integer. The k=0 case + is special, see ‘igraph_local_scan_0()’ (*note igraph_local_scan_0 + --- Local scan-statistics; k=0::). + +‘res’: + An initialized vector, the results are stored here. + +‘weights’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: "Them" statistics, Next: Pre-calculated subsets, Prev: "Us" statistics, Up: Local scan statistics + +17.7.2 "Them" statistics +------------------------ + +* Menu: + +* igraph_local_scan_0_them -- Local THEM scan-statistics, k=0: igraph_local_scan_0_them --- Local THEM scan-statistics; k=0. +* igraph_local_scan_1_ecount_them -- Local THEM scan-statistics, k=1, edge count and sum of weights: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights. +* igraph_local_scan_k_ecount_them -- Local THEM scan-statistics, edge count or sum of weights.: igraph_local_scan_k_ecount_them --- Local THEM scan-statistics; edge count or sum of weights_. + + +File: igraph-docs.info, Node: igraph_local_scan_0_them --- Local THEM scan-statistics; k=0, Next: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights, Up: "Them" statistics + +17.7.2.1 igraph_local_scan_0_them -- Local THEM scan-statistics, k=0 +.................................................................... + + + igraph_error_t igraph_local_scan_0_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + + K=0 scan-statistics is arbitrarily defined as the vertex degree for +unweighted, and the vertex strength for weighted graphs. See +‘igraph_degree()’ (*note igraph_degree --- The degree of some vertices +in a graph_::) and ‘igraph_strength()’ (*note igraph_strength --- +Strength of the vertices; also called weighted vertex degree_::). + + *Arguments:. * + +‘us’: + The input graph, to use to extract the neighborhoods. + +‘them’: + The input graph to use for the actually counting. + +‘res’: + An initialized vector, the results are stored here. + +‘weights_them’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights, Next: igraph_local_scan_k_ecount_them --- Local THEM scan-statistics; edge count or sum of weights_, Prev: igraph_local_scan_0_them --- Local THEM scan-statistics; k=0, Up: "Them" statistics + +17.7.2.2 igraph_local_scan_1_ecount_them -- Local THEM scan-statistics, k=1, edge count and sum of weights +.......................................................................................................... + + + igraph_error_t igraph_local_scan_1_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + + Count the number of edges or the sum the edge weights in the +1-neighborhood of vertices. + + *Arguments:. * + +‘us’: + The input graph to extract the neighborhoods. + +‘them’: + The input graph to perform the counting. + +‘weights_them’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_local_scan_1_ecount()’ (*note igraph_local_scan_1_ecount + --- Local scan-statistics; k=1; edge count and sum of weights::) + for the US statistics. + + +File: igraph-docs.info, Node: igraph_local_scan_k_ecount_them --- Local THEM scan-statistics; edge count or sum of weights_, Prev: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights, Up: "Them" statistics + +17.7.2.3 igraph_local_scan_k_ecount_them -- Local THEM scan-statistics, edge count or sum of weights. +..................................................................................................... + + + igraph_error_t igraph_local_scan_k_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_int_t k, igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + + Count the number of edges or the sum the edge weights in the +k-neighborhood of vertices. + + *Arguments:. * + +‘us’: + The input graph to extract the neighborhoods. + +‘them’: + The input graph to perform the counting. + +‘k’: + The size of the neighborhood, non-negative integer. The k=0 case + is special, see ‘igraph_local_scan_0_them()’ (*note + igraph_local_scan_0_them --- Local THEM scan-statistics; k=0::). + +‘res’: + An initialized vector, the results are stored here. + +‘weights_them’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘mode’: + Type of the neighborhood, ‘IGRAPH_OUT’ means outgoing, ‘IGRAPH_IN’ + means incoming and ‘IGRAPH_ALL’ means all edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_local_scan_1_ecount()’ (*note igraph_local_scan_1_ecount + --- Local scan-statistics; k=1; edge count and sum of weights::) + for the US statistics. + + +File: igraph-docs.info, Node: Pre-calculated subsets, Prev: "Them" statistics, Up: Local scan statistics + +17.7.3 Pre-calculated subsets +----------------------------- + +* Menu: + +* igraph_local_scan_neighborhood_ecount --- Local scan-statistics with pre-calculated neighborhoods:: +* igraph_local_scan_subset_ecount -- Local scan-statistics of subgraphs induced by subsets of vertices.: igraph_local_scan_subset_ecount --- Local scan-statistics of subgraphs induced by subsets of vertices_. + + +File: igraph-docs.info, Node: igraph_local_scan_neighborhood_ecount --- Local scan-statistics with pre-calculated neighborhoods, Next: igraph_local_scan_subset_ecount --- Local scan-statistics of subgraphs induced by subsets of vertices_, Up: Pre-calculated subsets + +17.7.3.1 igraph_local_scan_neighborhood_ecount -- Local scan-statistics with pre-calculated neighborhoods +......................................................................................................... + + + igraph_error_t igraph_local_scan_neighborhood_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *neighborhoods); + + Count the number of edges, or sum the edge weights in neighborhoods +given as a parameter. + + *Warning* + + Deprecated since version 0.10.0. Please do not use this function + in new code; use ‘igraph_local_scan_subset_ecount()’ (*note + igraph_local_scan_subset_ecount --- Local scan-statistics of + subgraphs induced by subsets of vertices_::) instead. + + *Arguments:. * + +‘graph’: + The graph to perform the counting/summing in. + +‘res’: + Initialized vector, the result is stored here. + +‘weights’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘neighborhoods’: + List of ‘igraph_vector_int_t’ objects, the neighborhoods, one for + each vertex in the graph. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_local_scan_subset_ecount --- Local scan-statistics of subgraphs induced by subsets of vertices_, Prev: igraph_local_scan_neighborhood_ecount --- Local scan-statistics with pre-calculated neighborhoods, Up: Pre-calculated subsets + +17.7.3.2 igraph_local_scan_subset_ecount -- Local scan-statistics of subgraphs induced by subsets of vertices. +.............................................................................................................. + + + igraph_error_t igraph_local_scan_subset_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *subsets); + + Count the number of edges, or sum the edge weights in induced +subgraphs from vertices given as a parameter. + + *Arguments:. * + +‘graph’: + The graph to perform the counting/summing in. + +‘res’: + Initialized vector, the result is stored here. + +‘weights’: + Weight vector for weighted graphs, null pointer for unweighted + graphs. + +‘subsets’: + List of ‘igraph_vector_int_t’ objects, the vertex subsets. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: Graph components, Next: Percolation, Prev: Local scan statistics, Up: Structural properties of graphs + +17.8 Graph components +===================== + +* Menu: + +* igraph_subcomponent -- The vertices reachable from a given vertex.: igraph_subcomponent --- The vertices reachable from a given vertex_. +* igraph_connected_components -- Calculates the (weakly or strongly) connected components in a graph.: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_. +* igraph_is_connected -- Decides whether the graph is (weakly or strongly) connected.: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_. +* igraph_decompose -- Decomposes a graph into connected components.: igraph_decompose --- Decomposes a graph into connected components_. +* igraph_reachability -- Calculates which vertices are reachable from each vertex in the graph.: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_. +* igraph_count_reachable -- The number of vertices reachable from each vertex in the graph.: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_. +* igraph_transitive_closure -- Computes the transitive closure of a graph.: igraph_transitive_closure --- Computes the transitive closure of a graph_. +* igraph_biconnected_components -- Calculates biconnected components.: igraph_biconnected_components --- Calculates biconnected components_. +* igraph_articulation_points -- Finds the articulation points in a graph.: igraph_articulation_points --- Finds the articulation points in a graph_. +* igraph_bridges -- Finds all bridges in a graph.: igraph_bridges --- Finds all bridges in a graph_. +* igraph_is_biconnected -- Checks whether a graph is biconnected.: igraph_is_biconnected --- Checks whether a graph is biconnected_. + + +File: igraph-docs.info, Node: igraph_subcomponent --- The vertices reachable from a given vertex_, Next: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_, Up: Graph components + +17.8.1 igraph_subcomponent -- The vertices reachable from a given vertex. +------------------------------------------------------------------------- + + + igraph_error_t igraph_subcomponent( + const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t vertex, + igraph_neimode_t mode + ); + + This function returns the set of vertices reachable from a specified +vertex. In undirected graphs, this is simple the set of vertices within +the same component. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result, vector with the IDs of the vertices reachable from + ‘vertex’. + +‘vertex’: + The id of the vertex of which the component is searched. + +‘mode’: + Type of the component for directed graphs, possible values: + + ‘IGRAPH_OUT’ + the set of vertices reachable _from_ the ‘vertex’, + + ‘IGRAPH_IN’ + the set of vertices from which the ‘vertex’ is reachable. + + ‘IGRAPH_ALL’ + the graph is considered as an undirected graph. Note that + this is _not_ the same as the union of the previous two. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + ‘vertex’ is an invalid vertex ID + + ‘IGRAPH_EINVMODE’ + invalid mode argument passed. + + Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices +and edges in the graph. + + *See also:. * + +‘’ + ‘igraph_induced_subgraph()’ (*note igraph_induced_subgraph --- + Creates a subgraph induced by the specified vertices_::) if you + want a graph object consisting only a given set of vertices and the + edges between them; ‘igraph_reachability()’ (*note + igraph_reachability --- Calculates which vertices are reachable + from each vertex in the graph_::) to efficiently compute the + reachable set from _all_ vertices; ‘igraph_neighborhood()’ (*note + igraph_neighborhood --- Calculate the neighborhood of vertices_::) + to find vertices within a given distance. + + +File: igraph-docs.info, Node: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_, Next: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_, Prev: igraph_subcomponent --- The vertices reachable from a given vertex_, Up: Graph components + +17.8.2 igraph_connected_components -- Calculates the (weakly or strongly) connected components in a graph. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_connected_components( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no, igraph_connectedness_t mode + ); + + When computing strongly connected components, the components will be +indexed in topological order. In other words, vertex ‘v’ is reachable +from vertex ‘u’ precisely when ‘membership[u] <= membership[v]’. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘membership’: + For every vertex the ID of its component is given. The vector has + to be preinitialized and will be resized as needed. Alternatively + this argument can be ‘NULL’, in which case it is ignored. + +‘csize’: + For every component it gives its size, the order being defined by + the component IDs. The vector must be preinitialized and will be + resized as needed. Alternatively this argument can be ‘NULL’, in + which case it is ignored. + +‘no’: + Pointer to an integer, if not ‘NULL’ then the number of components + will be stored here. + +‘mode’: + For directed graph this specifies whether to calculate weakly or + strongly connected components. Possible values: + + ‘IGRAPH_WEAK’ + Compute weakly connected components, i.e. ignore edge + directions. + + ‘IGRAPH_STRONG’ + Compute strongly connnected components, i.e. consider edge + directions. + + This parameter is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), where |V| and |E| are the number of +vertices and edges in the graph. + + * File examples/simple/igraph_contract_vertices.c* + + +File: igraph-docs.info, Node: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_, Next: igraph_decompose --- Decomposes a graph into connected components_, Prev: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_, Up: Graph components + +17.8.3 igraph_is_connected -- Decides whether the graph is (weakly or strongly) connected. +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_is_connected(const igraph_t *graph, igraph_bool_t *res, + igraph_connectedness_t mode); + + A graph is considered connected when any of its vertices is reachable +from any other. A directed graph with this property is called +_strongly_ connected. A directed graph that would be connected when +ignoring the directions of its edges is called _weakly_ connected. + + A graph with zero vertices (i.e. the null graph) is _not_ connected +by definition. This behaviour changed in igraph 0.9; earlier versions +assumed that the null graph is connected. See the following issue on +Github for the argument that led us to change the definition: +https://github.com/igraph/igraph/issues/1539 +(https://github.com/igraph/igraph/issues/1539) + + The return value of this function is cached in the graph itself, +separately for weak and strong connectivity. Calling the function +multiple times with no modifications to the graph in between will return +a cached value in O(1) time. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a Boolean variable, the result will be stored here. + +‘mode’: + For a directed graph this specifies whether to calculate weak or + strong connectedness. Possible values: ‘IGRAPH_WEAK’, + ‘IGRAPH_STRONG’. This argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid mode argument. + + *See also:. * + +‘’ + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::) to find the connected components, + ‘igraph_is_biconnected()’ (*note igraph_is_biconnected --- Checks + whether a graph is biconnected_::) to check if the graph is + 2-vertex-connected. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph. + + +File: igraph-docs.info, Node: igraph_decompose --- Decomposes a graph into connected components_, Next: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_, Prev: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_, Up: Graph components + +17.8.4 igraph_decompose -- Decomposes a graph into connected components. +------------------------------------------------------------------------ + + + igraph_error_t igraph_decompose(const igraph_t *graph, igraph_graph_list_t *components, + igraph_connectedness_t mode, + igraph_int_t maxcompno, igraph_int_t minelements); + + Creates a separate graph for each component of a graph. Note that +the vertex IDs in the new graphs will be different than in the original +graph, except when there is only a single component in the original +graph. + + *Arguments:. * + +‘graph’: + The original graph. + +‘components’: + This list of graphs will contain the individual components. It + should be initialized before calling this function and will be + resized to hold the graphs. + +‘mode’: + Either ‘IGRAPH_WEAK’ or ‘IGRAPH_STRONG’ for weakly and strongly + connected components respectively. + +‘maxcompno’: + The maximum number of components to return. The first ‘maxcompno’ + components will be returned (which hold at least ‘minelements’ + vertices, see the next parameter), the others will be ignored. + Supply ‘-1’ here if you don't want to limit the number of + components. + +‘minelements’: + The minimum number of vertices a component should contain in order + to place it in the ‘components’ vector. For example, supplying 2 + here ignores isolated vertices. + + *Returns:. * + +‘’ + Error code, ‘IGRAPH_ENOMEM’ if there is not enough memory to + perform the operation. + + Added in version 0.2. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + * File examples/simple/igraph_decompose.c* + + +File: igraph-docs.info, Node: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_, Next: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_, Prev: igraph_decompose --- Decomposes a graph into connected components_, Up: Graph components + +17.8.5 igraph_reachability -- Calculates which vertices are reachable from each vertex in the graph. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_reachability( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t *no_of_components, + igraph_bitset_list_t *reach, + igraph_neimode_t mode); + + The resulting list will contain one bitset for each strongly +connected component. The bitset for component i will have its j-th bit +set, if vertex j is reachable from some vertex in component i in 0 or +more steps. In particular, a vertex is always reachable from itself. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘membership’: + Pointer to an integer vector. For every vertex, the ID of its + component is given. The vector will be resized as needed. This + parameter must not be ‘NULL’. + +‘csize’: + Pointer to an integer vector or ‘NULL’. For every component, it + gives its size (vertex count), the order being defined by the + component IDs. The vector will be resized as needed. + +‘no_of_components’: + Pointer to an integer or ‘NULL’. The number of components will be + stored here. + +‘reach’: + A list of bitsets representing the result. It will be resized as + needed. ‘reach[membership[u]][v]’ is set to ‘true’ if vertex ‘v’ + is reachable from vertex ‘u’. + +‘mode’: + In directed graphs, controls the treatment of edge directions. + Ignored in undirected graphs. With ‘IGRAPH_OUT’, reachability is + computed by traversing edges along their direction. With + ‘IGRAPH_IN’, edges are traversed opposite to their direction. With + ‘IGRAPH_ALL’, edge directions are ignored and the graph is treated + as undirected. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory to + perform the operation. + + *See also:. * + +‘’ + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::) to find the connnected components of a graph; + ‘igraph_count_reachable()’ (*note igraph_count_reachable --- The + number of vertices reachable from each vertex in the graph_::) to + count how many vertices are reachable from each vertex; + ‘igraph_subcomponent()’ (*note igraph_subcomponent --- The vertices + reachable from a given vertex_::) to find which vertices are + rechable from a single vertex. + + Time complexity: O(|C||V|/w + |V| + |E|), where |C| is the number of +strongly connected components (at most |V|), |V| is the number of +vertices, and |E| is the number of edges respectively, and w is the bit +width of ‘igraph_int_t’, typically the word size of the machine (32 or +64). + + +File: igraph-docs.info, Node: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_, Next: igraph_transitive_closure --- Computes the transitive closure of a graph_, Prev: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_, Up: Graph components + +17.8.6 igraph_count_reachable -- The number of vertices reachable from each vertex in the graph. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_count_reachable(const igraph_t *graph, + igraph_vector_int_t *counts, + igraph_neimode_t mode); + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘counts’: + Integer vector. ‘counts[v]’ will store the number of vertices + reachable from vertex ‘v’, including ‘v’ itself. + +‘mode’: + In directed graphs, controls the treatment of edge directions. + Ignored in undirected graphs. With ‘IGRAPH_OUT’, reachability is + computed by traversing edges along their direction. With + ‘IGRAPH_IN’, edges are traversed opposite to their direction. With + ‘IGRAPH_ALL’, edge directions are ignored and the graph is treated + as undirected. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory to + perform the operation. + + *See also:. * + +‘’ + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::), ‘igraph_transitive_closure()’ (*note + igraph_transitive_closure --- Computes the transitive closure of a + graph_::) + + Time complexity: O(|C||V|/w + |V| + |E|), where |C| is the number of +strongly connected components (at most |V|), |V| is the number of +vertices, and |E| is the number of edges respectively, and w is the bit +width of ‘igraph_int_t’, typically the word size of the machine (32 or +64). + + +File: igraph-docs.info, Node: igraph_transitive_closure --- Computes the transitive closure of a graph_, Next: igraph_biconnected_components --- Calculates biconnected components_, Prev: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_, Up: Graph components + +17.8.7 igraph_transitive_closure -- Computes the transitive closure of a graph. +------------------------------------------------------------------------------- + + + igraph_error_t igraph_transitive_closure(const igraph_t *graph, igraph_t *closure); + + The resulting graph will have an edge from vertex ‘i’ to vertex ‘j’ +if ‘j’ is reachable from ‘i’. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘closure’: + The resulting graph representing the transitive closure. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’ if there is not enough memory to + perform the operation. + + *See also:. * + +‘’ + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::), ‘igraph_count_reachable()’ (*note igraph_count_reachable + --- The number of vertices reachable from each vertex in the + graph_::) + + Time complexity: O(|V|^2 + |E|), where |V| is the number of vertices, +and |E| is the number of edges, respectively. + + +File: igraph-docs.info, Node: igraph_biconnected_components --- Calculates biconnected components_, Next: igraph_articulation_points --- Finds the articulation points in a graph_, Prev: igraph_transitive_closure --- Computes the transitive closure of a graph_, Up: Graph components + +17.8.8 igraph_biconnected_components -- Calculates biconnected components. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_biconnected_components(const igraph_t *graph, + igraph_int_t *no, + igraph_vector_int_list_t *tree_edges, + igraph_vector_int_list_t *component_edges, + igraph_vector_int_list_t *components, + igraph_vector_int_t *articulation_points); + + A graph is biconnected if the removal of any single vertex (and its +incident edges) does not disconnect it. + + A biconnected component of a graph is a maximal biconnected subgraph +of it. The biconnected components of a graph can be given by a +partition of its edges: every edge is a member of exactly one +biconnected component. Note that this is not true for vertices: the +same vertex can be part of many biconnected components, while isolated +vertices are part of none at all. + + Note that some authors do not consider the graph consisting of two +connected vertices as biconnected, however, igraph does. + + igraph does not consider components containing a single vertex only +as being biconnected. Isolated vertices will not be part of any of the +biconnected components. This means that checking whether there is a +single biconnected component is not sufficient for determining if a +graph is biconnected. Use ‘igraph_is_biconnected()’ (*note +igraph_is_biconnected --- Checks whether a graph is biconnected_::) for +this purpose. + + *Arguments:. * + +‘graph’: + The input graph. It will be treated as undirected. + +‘no’: + If not a ‘NULL’ pointer, the number of biconnected components will + be stored here. + +‘tree_edges’: + If not a ‘NULL’ pointer, then the found components are stored here, + in a list of vectors. Every vector in the list is a biconnected + component, represented by its edges. More precisely, a spanning + tree of the biconnected component is returned. + +‘component_edges’: + If not a ‘NULL’ pointer, then the edges of the biconnected + components are stored here, in the same form as for ‘tree_edges’. + +‘components’: + If not a ‘NULL’ pointer, then the vertices of the biconnected + components are stored here, in the same format as for the previous + two arguments. + +‘articulation_points’: + If not a NULL pointer, then the articulation points of the graph + are stored in this vector. A vertex is an articulation point if + its removal increases the number of (weakly) connected components + in the graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges, but only if you do not calculate ‘components’ and +‘component_edges’. If you calculate ‘components’, then it is quadratic +in the number of vertices. If you calculate ‘component_edges’ as well, +then it is cubic in the number of vertices. + + *See also:. * + +‘’ + ‘igraph_articulation_points()’ (*note igraph_articulation_points + --- Finds the articulation points in a graph_::), + ‘igraph_is_biconnected()’ (*note igraph_is_biconnected --- Checks + whether a graph is biconnected_::), ‘igraph_connected_components()’ + (*note igraph_connected_components --- Calculates the [weakly or + strongly] connected components in a graph_::). + + * File examples/simple/igraph_biconnected_components.c* + + +File: igraph-docs.info, Node: igraph_articulation_points --- Finds the articulation points in a graph_, Next: igraph_bridges --- Finds all bridges in a graph_, Prev: igraph_biconnected_components --- Calculates biconnected components_, Up: Graph components + +17.8.9 igraph_articulation_points -- Finds the articulation points in a graph. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_articulation_points(const igraph_t *graph, igraph_vector_int_t *res); + + A vertex is an articulation point if its removal increases the number +of (weakly) connected components in the graph. + + Note that a graph without any articulation points is not necessarily +biconnected. Counterexamples are the two-vertex complete graph as well +as empty graphs. Use ‘igraph_is_biconnected()’ (*note +igraph_is_biconnected --- Checks whether a graph is biconnected_::) to +check whether a graph is biconnected. + + *Arguments:. * + +‘graph’: + The input graph. It will be treated as undirected. + +‘res’: + Pointer to an initialized vector, the articulation points will be + stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + *See also:. * + +‘’ + ‘igraph_biconnected_components()’ (*note + igraph_biconnected_components --- Calculates biconnected + components_::), ‘igraph_is_bipartite()’ (*note igraph_is_bipartite + --- Check whether a graph is bipartite_::), + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::), ‘igraph_bridges()’ (*note igraph_bridges --- Finds all + bridges in a graph_::) + + +File: igraph-docs.info, Node: igraph_bridges --- Finds all bridges in a graph_, Next: igraph_is_biconnected --- Checks whether a graph is biconnected_, Prev: igraph_articulation_points --- Finds the articulation points in a graph_, Up: Graph components + +17.8.10 igraph_bridges -- Finds all bridges in a graph. +------------------------------------------------------- + + + igraph_error_t igraph_bridges(const igraph_t *graph, igraph_vector_int_t *bridges); + + An edge is a bridge if its removal increases the number of (weakly) +connected components in the graph. + + *Arguments:. * + +‘graph’: + The input graph. It will be treated as undirected. + +‘bridges’: + Pointer to an initialized vector, the bridges will be stored here + as edge indices. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + *See also:. * + +‘’ + ‘igraph_articulation_points()’ (*note igraph_articulation_points + --- Finds the articulation points in a graph_::), + ‘igraph_biconnected_components()’ (*note + igraph_biconnected_components --- Calculates biconnected + components_::), ‘igraph_connected_components()’ (*note + igraph_connected_components --- Calculates the [weakly or strongly] + connected components in a graph_::) + + +File: igraph-docs.info, Node: igraph_is_biconnected --- Checks whether a graph is biconnected_, Prev: igraph_bridges --- Finds all bridges in a graph_, Up: Graph components + +17.8.11 igraph_is_biconnected -- Checks whether a graph is biconnected. +----------------------------------------------------------------------- + + + igraph_error_t igraph_is_biconnected(const igraph_t *graph, igraph_bool_t *res); + + A graph is biconnected if the removal of any single vertex (and its +incident edges) does not disconnect it. + + igraph does not consider single-vertex graphs biconnected. + + Note that some authors do not consider the graph consisting of two +connected vertices as biconnected, however, igraph does. + + *Arguments:. * + +‘graph’: + The input graph. It will be treated as undirected. + +‘res’: + If not a ‘NULL’ pointer, the result will be returned here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + *See also:. * + +‘’ + ‘igraph_articulation_points()’ (*note igraph_articulation_points + --- Finds the articulation points in a graph_::), + ‘igraph_biconnected_components()’ (*note + igraph_biconnected_components --- Calculates biconnected + components_::). + + * File examples/simple/igraph_is_biconnected.c* + + +File: igraph-docs.info, Node: Percolation, Next: Degree sequences, Prev: Graph components, Up: Structural properties of graphs + +17.9 Percolation +================ + +* Menu: + +* igraph_site_percolation -- The size of the largest component as vertices are added to a graph.: igraph_site_percolation --- The size of the largest component as vertices are added to a graph_. +* igraph_bond_percolation -- The size of the largest component as edges are added to a graph.: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_. +* igraph_edgelist_percolation -- The size of the largest component as vertex pairs are connected.: igraph_edgelist_percolation --- The size of the largest component as vertex pairs are connected_. + + +File: igraph-docs.info, Node: igraph_site_percolation --- The size of the largest component as vertices are added to a graph_, Next: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_, Up: Percolation + +17.9.1 igraph_site_percolation -- The size of the largest component as vertices are added to a graph. +----------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_site_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *edge_count, + const igraph_vector_int_t *vertex_order); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Calculates the site percolation curve, i.e. the size of the largest +connected component as vertices are added in the given order. If both +‘giant_size’ and ‘vertex_order’ are reversed, it is the size of the +largest component as vertices are removed from the graph. If no vertex +order is given, a random one will be used. + + *Arguments:. * + +‘graph’: + The graph that vertices are assumed to be in. Edge directions are + ignored. + +‘giant_size’: + ‘giant_size[i]’ will contain the size of the largest component + after having added the vertex with index ‘vertex_order[i]’. + +‘edge_count’: + ‘edge_count[i]’ will contain the numer of edges in the graph having + added the vertex with index ‘vertex_order[i]’. + +‘vertex_order’: + The order the vertices are added in. Must not contain duplicates. + If ‘NULL’, a random order will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_bond_percolation()’ (*note igraph_bond_percolation --- The + size of the largest component as edges are added to a graph_::) to + compute the edge percolation curve; ‘igraph_connected_components()’ + (*note igraph_connected_components --- Calculates the [weakly or + strongly] connected components in a graph_::) to find the size of + connected components. + + Time complexity: O(|V| + |E| a(|E|)) where a is the inverse Ackermann +function, for all practical purposes it is not above 5. + + +File: igraph-docs.info, Node: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_, Next: igraph_edgelist_percolation --- The size of the largest component as vertex pairs are connected_, Prev: igraph_site_percolation --- The size of the largest component as vertices are added to a graph_, Up: Percolation + +17.9.2 igraph_bond_percolation -- The size of the largest component as edges are added to a graph. +-------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_bond_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count, + const igraph_vector_int_t *edge_order); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Calculates the bond percolation curve, i.e. the size of the largest +connected component as edges are added to the graph in the order given. +If both ‘giant_size’ and ‘edge_order’ are reversed, it is the size of +the largest component as edges are removed from the graph. If no edge +order is given, a random one will be used. + + *Arguments:. * + +‘graph’: + The graph that edges are assumed to be in. Edge directions are + ignored. + +‘giant_size’: + ‘giant_size[i]’ will contain the size of the largest component + after having added the edge with index ‘edge_order[i]’. + +‘vertex_count’: + ‘vertex_count[i]’ will contain the number of vertices that have at + least one incident edge after adding the edge with index + ‘edge_order[i]’. + +‘edge_order’: + The order the edges are added in. Must not contain duplicates. If + ‘NULL’, a random order will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_edgelist_percolation()’ (*note igraph_edgelist_percolation + --- The size of the largest component as vertex pairs are + connected_::) to specify the edges to be added by their endpoints; + ‘igraph_site_percolation()’ (*note igraph_site_percolation --- The + size of the largest component as vertices are added to a graph_::) + to compute the vertex percolation curve; + ‘igraph_connected_components()’ (*note igraph_connected_components + --- Calculates the [weakly or strongly] connected components in a + graph_::) to find the size of connected components. + + Time complexity: O(|V| + |E| a(|E|)) where a is the inverse Ackermann +function, for all practical purposes it is not above 5. + + +File: igraph-docs.info, Node: igraph_edgelist_percolation --- The size of the largest component as vertex pairs are connected_, Prev: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_, Up: Percolation + +17.9.3 igraph_edgelist_percolation -- The size of the largest component as vertex pairs are connected. +------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_edgelist_percolation( + const igraph_vector_int_t *edges, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + Calculates the size of the largest connected component as edges are +added to a graph in the given order. This function differs from +‘igraph_bond_percolation()’ (*note igraph_bond_percolation --- The size +of the largest component as edges are added to a graph_::) in that it +take a list of vertex pairs as input. + + *Arguments:. * + +‘edges’: + Vector of edges, where the i-th edge has endpoints ‘edges[2i]’ and + ‘edges[2i+1]’. + +‘giant_size’: + ‘giant_size[i]’ will contain the size of the largest connected + component after edge ‘i’ is added. + +‘vertex_count’: + ‘vertex_count[i]’ will contain the number of vertices with at least + one edge after edge ‘i’ is added. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_bond_percolation()’ (*note igraph_bond_percolation --- The + size of the largest component as edges are added to a graph_::) to + specify edges by their ID in a graph object. + + Time complexity: O(|E| a(|E|)) where a is the inverse Ackermann +function, for all practical purposes it is not above 5. + + +File: igraph-docs.info, Node: Degree sequences, Next: Centrality measures, Prev: Percolation, Up: Structural properties of graphs + +17.10 Degree sequences +====================== + +* Menu: + +* igraph_is_graphical --- Is there a graph with the given degree sequence?:: +* igraph_is_bigraphical --- Is there a bipartite graph with the given bi-degree-sequence?:: + + +File: igraph-docs.info, Node: igraph_is_graphical --- Is there a graph with the given degree sequence?, Next: igraph_is_bigraphical --- Is there a bipartite graph with the given bi-degree-sequence?, Up: Degree sequences + +17.10.1 igraph_is_graphical -- Is there a graph with the given degree sequence? +------------------------------------------------------------------------------- + + + igraph_error_t igraph_is_graphical(const igraph_vector_int_t *out_degrees, + const igraph_vector_int_t *in_degrees, + const igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res); + + Determines whether a sequence of integers can be the degree sequence +of some graph. The classical concept of graphicality assumes simple +graphs. This function can perform the check also when either +self-loops, multi-edge, or both are allowed in the graph. + + For simple undirected graphs, the Erdős-Gallai conditions are checked +using the linear-time algorithm of Cloteaux. If both self-loops and +multi-edges are allowed, it is sufficient to chek that that sum of +degrees is even. If only multi-edges are allowed, but not self-loops, +there is an additional condition that the sum of degrees be no smaller +than twice the maximum degree. If at most one self-loop is allowed per +vertex, but no multi-edges, a modified version of the Erdős-Gallai +conditions are used (see Cairns & Mendan). + + For simple directed graphs, the Fulkerson-Chen-Anstee theorem is used +with the relaxation by Berger. If both self-loops and multi-edges are +allowed, then it is sufficient to check that the sum of in- and +out-degrees is the same. If only multi-edges are allowed, but not self +loops, there is an additional condition that the sum of out-degrees (or +equivalently, in-degrees) is no smaller than the maximum total degree. +If single self-loops are allowed, but not multi-edges, the problem is +equivalent to realizability as a simple bipartite graph, thus the +Gale-Ryser theorem can be used; see ‘igraph_is_bigraphical()’ (*note +igraph_is_bigraphical --- Is there a bipartite graph with the given +bi-degree-sequence?::) for more information. + + References: + + P. Erdős and T. Gallai, Gráfok előírt fokú pontokkal, Matematikai +Lapok 11, pp. 264-274 (1960). +https://users.renyi.hu/~p_erdos/1961-05.pdf +(https://users.renyi.hu/~p_erdos/1961-05.pdf) + + Z. Király, Recognizing graphic degree sequences and generating all +realizations. TR-2011-11, Egerváry Research Group, H-1117, Budapest, +Hungary. ISSN 1587-4451 (2012). +https://egres.elte.hu/tr/egres-11-11.pdf +(https://egres.elte.hu/tr/egres-11-11.pdf) + + B. Cloteaux, Is This for Real? Fast Graphicality Testing, Comput. +Sci. Eng. 17, 91 (2015). https://dx.doi.org/10.1109/MCSE.2015.125 +(https://dx.doi.org/10.1109/MCSE.2015.125) + + A. Berger, A note on the characterization of digraphic sequences, +Discrete Math. 314, 38 (2014). +https://dx.doi.org/10.1016/j.disc.2013.09.010 +(https://dx.doi.org/10.1016/j.disc.2013.09.010) + + G. Cairns and S. Mendan, Degree Sequence for Graphs with Loops +(2013). https://arxiv.org/abs/1303.2145v1 +(https://arxiv.org/abs/1303.2145v1) + + *Arguments:. * + +‘out_degrees’: + A vector of integers specifying the degree sequence for undirected + graphs or the out-degree sequence for directed graphs. + +‘in_degrees’: + A vector of integers specifying the in-degree sequence for directed + graphs. For undirected graphs, it must be ‘NULL’. + +‘allowed_edge_types’: + The types of edges to allow in the graph. See + ‘igraph_edge_type_sw_t’ (*note igraph_edge_type_sw_t --- What types + of non-simple edges to allow?::) for details. + + ‘IGRAPH_SIMPLE_SW’ + simple graphs (i.e. no self-loops or multi-edges allowed). + + ‘IGRAPH_LOOPS_SW’ + single self-loops are allowed, but not multi-edges. + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed, but not self-loops. + + ‘IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW’ + both self-loops and multi-edges are allowed. + +‘res’: + Pointer to a Boolean. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_bigraphical()’ (*note igraph_is_bigraphical --- Is there + a bipartite graph with the given bi-degree-sequence?::) to check if + a bi-degree-sequence can be realized as a bipartite graph; + ‘igraph_realize_degree_sequence()’ (*note + igraph_realize_degree_sequence --- Generates a graph with the given + degree sequence_::) to construct a graph with a given degree + sequence. + + Time complexity: O(n), where n is the length of the degree +sequence(s). + + +File: igraph-docs.info, Node: igraph_is_bigraphical --- Is there a bipartite graph with the given bi-degree-sequence?, Prev: igraph_is_graphical --- Is there a graph with the given degree sequence?, Up: Degree sequences + +17.10.2 igraph_is_bigraphical -- Is there a bipartite graph with the given bi-degree-sequence? +---------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_is_bigraphical(const igraph_vector_int_t *degrees1, + const igraph_vector_int_t *degrees2, + const igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res); + + Determines whether two sequences of integers can be the degree +sequences of a bipartite graph. Such a pair of degree sequence is +called _bigraphical._ + + When multi-edges are allowed, it is sufficient to check that the sum +of degrees is the same in the two partitions. For simple graphs, the +Gale-Ryser theorem is used with Berger's relaxation. + + References: + + H. J. Ryser, Combinatorial Properties of Matrices of Zeros and Ones, +Can. J. Math. 9, 371 (1957). +https://dx.doi.org/10.4153/cjm-1957-044-3 +(https://dx.doi.org/10.4153/cjm-1957-044-3) + + D. Gale, A theorem on flows in networks, Pacific J. Math. 7, 1073 +(1957). https://dx.doi.org/10.2140/pjm.1957.7.1073 +(https://dx.doi.org/10.2140/pjm.1957.7.1073) + + A. Berger, A note on the characterization of digraphic sequences, +Discrete Math. 314, 38 (2014). +https://dx.doi.org/10.1016/j.disc.2013.09.010 +(https://dx.doi.org/10.1016/j.disc.2013.09.010) + + *Arguments:. * + +‘degrees1’: + A vector of integers specifying the degrees in the first partition + +‘degrees2’: + A vector of integers specifying the degrees in the second partition + +‘allowed_edge_types’: + The types of edges to allow in the graph: + + ‘IGRAPH_SIMPLE_SW’ + simple graphs (i.e. no multi-edges allowed). + + ‘IGRAPH_MULTI_SW’ + multi-edges are allowed. + +‘res’: + Pointer to a Boolean. The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_graphical()’ (*note igraph_is_graphical --- Is there a + graph with the given degree sequence?::) + + Time complexity: O(n), where n is the length of the larger degree +sequence. + + +File: igraph-docs.info, Node: Centrality measures, Next: Range-limited centrality measures, Prev: Degree sequences, Up: Structural properties of graphs + +17.11 Centrality measures +========================= + +* Menu: + +* igraph_closeness -- Closeness centrality calculations for some vertices.: igraph_closeness --- Closeness centrality calculations for some vertices_. +* igraph_harmonic_centrality -- Harmonic centrality for some vertices.: igraph_harmonic_centrality --- Harmonic centrality for some vertices_. +* igraph_betweenness -- Betweenness centrality of some vertices.: igraph_betweenness --- Betweenness centrality of some vertices_. +* igraph_edge_betweenness -- Betweenness centrality of the edges.: igraph_edge_betweenness --- Betweenness centrality of the edges_. +* igraph_pagerank_algo_t -- PageRank algorithm implementation.: igraph_pagerank_algo_t --- PageRank algorithm implementation_. +* igraph_pagerank -- Calculates the Google PageRank for the specified vertices.: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_. +* igraph_personalized_pagerank -- Calculates the personalized Google PageRank for the specified vertices.: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_. +* igraph_personalized_pagerank_vs -- Calculates the personalized Google PageRank for the specified vertices.: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_. +* igraph_constraint -- Burt's constraint scores.: igraph_constraint --- Burt's constraint scores_. +* igraph_maxdegree -- The maximum degree in a graph (or set of vertices).: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_. +* igraph_strength -- Strength of the vertices, also called weighted vertex degree.: igraph_strength --- Strength of the vertices; also called weighted vertex degree_. +* igraph_eigenvector_centrality -- Eigenvector centrality of the vertices.: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_. +* igraph_hub_and_authority_scores -- Kleinberg's hub and authority scores (HITS).: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_. +* igraph_convergence_degree -- Calculates the convergence degree of each edge in a graph.: igraph_convergence_degree --- Calculates the convergence degree of each edge in a graph_. + + +File: igraph-docs.info, Node: igraph_closeness --- Closeness centrality calculations for some vertices_, Next: igraph_harmonic_centrality --- Harmonic centrality for some vertices_, Up: Centrality measures + +17.11.1 igraph_closeness -- Closeness centrality calculations for some vertices. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_closeness(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized); + + The closeness centrality of a vertex measures how easily other +vertices can be reached from it (or the other way: how easily it can be +reached from the other vertices). It is defined as the inverse of the +mean distance to (or from) all other vertices. + + Closeness centrality is meaningful only for connected graphs. If the +graph is not connected, igraph computes the inverse of the mean distance +to (or from) all _reachable_ vertices. In undirected graphs, this is +equivalent to computing the closeness separately in each connected +component. The optional ‘all_reachable’ output parameter is provided to +help detect when the graph is disconnected. + + While there is no universally adopted definition of closeness +centrality for disconnected graphs, there have been some attempts for +generalizing the concept to the disconnected case. One type of approach +considers the mean distance only to reachable vertices, then re-scales +the obtained certrality score by a factor that depends on the number of +reachable vertices (i.e. the size of the component in the undirected +case). To facilitate computing these generalizations of closeness +centrality, the number of reachable vertices (not including the starting +vertex) is returned in ‘reachable_count’. + + In disconnected graphs, consider using the harmonic centrality, +computable using ‘igraph_harmonic_centrality()’ (*note +igraph_harmonic_centrality --- Harmonic centrality for some +vertices_::). + + For isolated vertices, i.e. those having no associated paths, NaN is +returned. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result of the computation, a vector containing the closeness + centrality scores for the given vertices. + +‘reachable_count’: + If not ‘NULL’, this vector will contain the number of vertices + reachable from each vertex for which the closeness is calculated + (not including that vertex). + +‘all_reachable’: + Pointer to a Boolean. If not ‘NULL’, it indicates if all vertices + of the graph were reachable from each vertex in ‘vids’. If false, + the graph is non-connected. If true, and the graph is undirected, + or if the graph is directed and ‘vids’ contains all vertices, then + the graph is connected. + +‘vids’: + The vertices for which the closeness centrality will be computed. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘weights’: + An optional vector containing edge weights for weighted closeness. + NaN values re not allowed as weights. Supply a null pointer here + for traditional, unweighted closeness. + +‘normalized’: + If true, the inverse of the mean distance to reachable vertices is + returned. If false, the inverse of the sum of distances is + returned. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(n|E|) for the unweighted case and +O(n|E|log|V|+|V|) for the weighted case, where n is the number of +vertices for which the calculation is done, |V| is the number of +vertices and |E| is the number of edges in the graph. + + *See also:. * + +‘’ + Other centrality types: ‘igraph_degree()’ (*note igraph_degree --- + The degree of some vertices in a graph_::), ‘igraph_betweenness()’ + (*note igraph_betweenness --- Betweenness centrality of some + vertices_::), ‘igraph_harmonic_centrality()’ (*note + igraph_harmonic_centrality --- Harmonic centrality for some + vertices_::). See ‘igraph_closeness_cutoff()’ (*note + igraph_closeness_cutoff --- Range limited closeness centrality_::) + for the range-limited closeness centrality. + + +File: igraph-docs.info, Node: igraph_harmonic_centrality --- Harmonic centrality for some vertices_, Next: igraph_betweenness --- Betweenness centrality of some vertices_, Prev: igraph_closeness --- Closeness centrality calculations for some vertices_, Up: Centrality measures + +17.11.2 igraph_harmonic_centrality -- Harmonic centrality for some vertices. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_harmonic_centrality(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized); + + The harmonic centrality of a vertex is the mean inverse distance to +all other vertices. The inverse distance to an unreachable vertex is +considered to be zero. + + References: + + M. Marchiori and V. Latora, Harmony in the small-world, Physica A +285, pp. 539-546 (2000). +https://doi.org/10.1016/S0378-4371%2800%2900311-3 +(https://doi.org/10.1016/S0378-4371%2800%2900311-3) + + Y. Rochat, Closeness Centrality Extended to Unconnected Graphs: the +Harmonic Centrality Index, ASNA 2009. +https://infoscience.epfl.ch/record/200525 +(https://infoscience.epfl.ch/record/200525) + + S. Vigna and P. Boldi, Axioms for Centrality, Internet Mathematics +10, (2014). https://doi.org/10.1080/15427951.2013.865686 +(https://doi.org/10.1080/15427951.2013.865686) + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result of the computation, a vector containing the harmonic + centrality scores for the given vertices. + +‘vids’: + The vertices for which the harmonic centrality will be computed. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘weights’: + An optional vector containing edge weights for weighted harmonic + centrality. No edge weight may be NaN. If ‘NULL’, all weights are + considered to be one. + +‘normalized’: + Boolean, whether to normalize the result. If true, the result is + the mean inverse path length to other vertices, i.e. it is + normalized by the number of vertices minus one. If false, the + result is the sum of inverse path lengths to other vertices. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(n|E|) for the unweighted case and +O(n*|E|log|V|+|V|) for the weighted case, where n is the number of +vertices for which the calculation is done, |V| is the number of +vertices and |E| is the number of edges in the graph. + + *See also:. * + +‘’ + Other centrality types: ‘igraph_closeness()’ (*note + igraph_closeness --- Closeness centrality calculations for some + vertices_::), ‘igraph_degree()’ (*note igraph_degree --- The degree + of some vertices in a graph_::), ‘igraph_betweenness()’ (*note + igraph_betweenness --- Betweenness centrality of some vertices_::). + + +File: igraph-docs.info, Node: igraph_betweenness --- Betweenness centrality of some vertices_, Next: igraph_edge_betweenness --- Betweenness centrality of the edges_, Prev: igraph_harmonic_centrality --- Harmonic centrality for some vertices_, Up: Centrality measures + +17.11.3 igraph_betweenness -- Betweenness centrality of some vertices. +---------------------------------------------------------------------- + + + igraph_error_t igraph_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized); + + The betweenness centrality of a vertex ‘v’ is the number of shortest +paths passing through it. If there is more than one shortest path +between two vertices, the fraction of these passing through ‘v’ is +counted. + + Reference: + + Ulrik Brandes: A faster algorithm for betweenness centrality. The +Journal of Mathematical Sociology, 25(2), 163-177 (2001). +https://doi.org/10.1080/0022250X.2001.9990249 +(https://doi.org/10.1080/0022250X.2001.9990249) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional vector containing edge weights for calculating weighted + betweenness. No edge weight may be NaN. Supply a null pointer here + for unweighted betweenness. + +‘res’: + The result of the computation, a vector containing the betweenness + scores for the specified vertices. + +‘vids’: + The vertices for which the range-limited betweenness centrality + scores will be returned. This paramerer is for convenience only + and does not affect performance. Internally, the betewenness of + all vertices is calculated. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores by the number of vertex + pairs. In directed graphs, the number of ordered vertex pairs, in + undirected graphs the number of unordered vertex pairs is used. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID passed in ‘vids’. + + Time complexity: O(|V||E|), |V| and |E| are the number of vertices +and edges in the graph. Note that the time complexity is independent of +the number of vertices for which the score is calculated. + + *See also:. * + +‘’ + ‘igraph_edge_betweenness()’ (*note igraph_edge_betweenness --- + Betweenness centrality of the edges_::) for calculating the + betweenness score of the edges in a graph; + ‘igraph_betweenness_cutoff()’ (*note igraph_betweenness_cutoff --- + Range-limited betweenness centrality_::) to calculate the + range-limited betweenness of the vertices in a graph; + ‘igraph_betweenness_subset()’ (*note igraph_betweenness_subset --- + Betweenness centrality for a subset of source and target + vertices_::) to consider shortest paths only between two vertex + subsets for calculating betweenness. + + +File: igraph-docs.info, Node: igraph_edge_betweenness --- Betweenness centrality of the edges_, Next: igraph_pagerank_algo_t --- PageRank algorithm implementation_, Prev: igraph_betweenness --- Betweenness centrality of some vertices_, Up: Centrality measures + +17.11.4 igraph_edge_betweenness -- Betweenness centrality of the edges. +----------------------------------------------------------------------- + + + igraph_error_t igraph_edge_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized); + + The betweenness centrality of an edge ‘e’ is the number of shortest +paths passing through it. If there is more than one shortest path +between two vertices, the fraction of these passing through ‘e’ is +counted. + + Reference: + + Ulrik Brandes: A faster algorithm for betweenness centrality. The +Journal of Mathematical Sociology, 25(2), 163-177 (2001). +https://doi.org/10.1080/0022250X.2001.9990249 +(https://doi.org/10.1080/0022250X.2001.9990249) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional weight vector for weighted edge betweenness. No edge + weight may be NaN. Supply a null pointer here for the unweighted + version. + +‘res’: + The result of the computation, vector containing the betweenness + scores for the edges. + +‘eids’: + The edges for which the betweenness centrality will be returned. + This parameter is for convenience only, and does not affect + performance. Internally, the betweenness is be calculated for all + edges. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores by the number of vertex + pairs. In directed graphs, the number of ordered vertex pairs, in + undirected graphs the number of unordered vertex pairs is used. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + + Time complexity: O(|V||E|), |V| and |E| are the number of vertices +and edges in the graph. + + *See also:. * + +‘’ + ‘igraph_betweenness()’ (*note igraph_betweenness --- Betweenness + centrality of some vertices_::) for calculating the betweenness + score of the vertices in a graph; + ‘igraph_edge_betweenness_cutoff()’ (*note + igraph_edge_betweenness_cutoff --- Range-limited betweenness + centrality of the edges_::) to compute the range-limited + betweenness score of the edges in a graph; + ‘igraph_edge_betweenness_subset()’ (*note + igraph_edge_betweenness_subset --- Edge betweenness centrality for + a subset of source and target vertices_::) to consider shortest + paths only between two vertex subsets for calculating betweenness. + + +File: igraph-docs.info, Node: igraph_pagerank_algo_t --- PageRank algorithm implementation_, Next: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_, Prev: igraph_edge_betweenness --- Betweenness centrality of the edges_, Up: Centrality measures + +17.11.5 igraph_pagerank_algo_t -- PageRank algorithm implementation. +-------------------------------------------------------------------- + + + typedef enum { + IGRAPH_PAGERANK_ALGO_ARPACK = 1, + IGRAPH_PAGERANK_ALGO_PRPACK = 2 + } igraph_pagerank_algo_t; + + Algorithms to calculate PageRank. + + *Values:. * + +‘IGRAPH_PAGERANK_ALGO_ARPACK’: + Use the ARPACK library, this was the PageRank implementation in + igraph from version 0.5, until version 0.7. + +‘IGRAPH_PAGERANK_ALGO_PRPACK’: + Use the PRPACK library. Currently this implementation is + recommended. + + +File: igraph-docs.info, Node: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_, Next: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_, Prev: igraph_pagerank_algo_t --- PageRank algorithm implementation_, Up: Centrality measures + +17.11.6 igraph_pagerank -- Calculates the Google PageRank for the specified vertices. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + + The PageRank centrality of a vertex is the fraction of time a random +walker traversing the graph would spend on that vertex. The walker +follows the out-edges with probabilities proportional to their weights. +Additionally, in each step, it restarts the walk from a random vertex +with probability ‘1 - damping’. If the random walker gets stuck in a +sink vertex, it will also restart from a random vertex. + + The PageRank centrality is mainly useful for directed graphs. In +undirected graphs it converges to trivial values proportional to degrees +as the damping factor approaches 1. + + Starting from version 0.9, igraph has two PageRank implementations, +and the user can choose between them. The first implementation is +‘IGRAPH_PAGERANK_ALGO_ARPACK’, which phrases the PageRank calculation as +an eigenvalue problem, which is then solved using the ARPACK library. +This was the default before igraph version 0.7. The second and +recommended implementation is ‘IGRAPH_PAGERANK_ALGO_PRPACK’. This is +using the PRPACK package, see https://github.com/dgleich/prpack +(https://github.com/dgleich/prpack). PRPACK uses an algebraic method, +i.e. solves a linear system to obtain the PageRank scores. + + Note that the PageRank of a given vertex depends on the PageRank of +all other vertices, so even if you want to calculate the PageRank for +only some of the vertices, all of them must be calculated. Requesting +the PageRank for only some of the vertices does not result in any +performance increase at all. + + References: + + Sergey Brin and Larry Page: The Anatomy of a Large-Scale Hypertextual +Web Search Engine. Proceedings of the 7th World-Wide Web Conference, +Brisbane, Australia, April 1998. +https://doi.org/10.1016/S0169-7552(98)00110-X +(https://doi.org/10.1016/S0169-7552(98)00110-X) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights. May be a ‘NULL’ pointer, meaning unweighted + edges, or a vector of non-negative values of the same length as the + number of edges. + +‘vector’: + Pointer to an initialized vector, the result is stored here. It is + resized as needed. + +‘value’: + Pointer to a real variable. When using + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, the eigenvalue corresponding to the + PageRank vector is stored here. It is expected to be exactly one. + Checking this value can be used to diagnose cases when ARPACK + failed to converge to the leading eigenvector. When using + ‘IGRAPH_PAGERANK_ALGO_PRPACK’, this is always set to 1.0. + +‘damping’: + The damping factor ("d" in the original paper). Must be a + probability in the range [0, 1]. A commonly used value is 0.85. + +‘directed’: + Boolean, whether to consider the directedness of the edges. This + is ignored for undirected graphs. + +‘vids’: + The vertex IDs for which the PageRank is returned. This parameter + is only for convenience. Computing PageRank for fewer than all + vertices will not speed up the calculation. + +‘algo’: + The PageRank implementation to use. Possible values: + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, ‘IGRAPH_PAGERANK_ALGO_PRPACK’. + +‘options’: + Options for the ARPACK method. See ‘igraph_arpack_options_t’ + (*note igraph_arpack_options_t --- Options for ARPACK_::) for + details. Supply ‘NULL’ here to use the defaults. Note that the + function overwrites the ‘n’ (number of vertices), ‘nev’ (1), ‘ncv’ + (3) and ‘which’ (LM) parameters and it always starts the + calculation from a non-random vector calculated based on the degree + of the vertices. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID in ‘vids’. + + Time complexity: depends on the input graph, usually it is O(|E|), +the number of edges. + + *See also:. * + +‘’ + ‘igraph_personalized_pagerank()’ (*note + igraph_personalized_pagerank --- Calculates the personalized Google + PageRank for the specified vertices_::) and + ‘igraph_personalized_pagerank_vs()’ (*note + igraph_personalized_pagerank_vs --- Calculates the personalized + Google PageRank for the specified vertices_::) for the personalized + PageRank measure. See ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::) + and ‘igraph_arpack_rnsolve()’ (*note igraph_arpack_rnsolve --- + ARPACK solver for non-symmetric matrices_::) for the underlying + machinery used by ‘IGRAPH_PAGERANK_ALGO_ARPACK’. + + * File examples/simple/igraph_pagerank.c* + + +File: igraph-docs.info, Node: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_, Next: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_, Prev: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_, Up: Centrality measures + +17.11.7 igraph_personalized_pagerank -- Calculates the personalized Google PageRank for the specified vertices. +--------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_personalized_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + const igraph_vector_t *reset, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + + The personalized PageRank is similar to the original PageRank +measure, but when the random walk is restarted, a new starting vertex is +chosen non-uniformly, according to the distribution specified in ‘reset’ +(instead of the uniform distribution in the original PageRank measure). +The ‘reset’ distribution is used both when restarting randomly with +probability ‘1 - damping’, and when the walker is forced to restart due +to being stuck in a sink vertex (a vertex with no outgoing edges). + + Note that the personalized PageRank of a given vertex depends on the +personalized PageRank of all other vertices, so even if you want to +calculate the personalized PageRank for only some of the vertices, all +of them must be calculated. Requesting the personalized PageRank for +only some of the vertices does not result in any performance increase at +all. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights. May be a ‘NULL’ pointer, meaning unweighted + edges, or a vector of non-negative values of the same length as the + number of edges. + +‘vector’: + Pointer to an initialized vector, the result is stored here. It is + resized as needed. + +‘value’: + Pointer to a real variable. When using + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, the eigenvalue corresponding to the + PageRank vector is stored here. It is expected to be exactly one. + Checking this value can be used to diagnose cases when ARPACK + failed to converge to the leading eigenvector. When using + ‘IGRAPH_PAGERANK_ALGO_PRPACK’, this is always set to 1.0. + +‘reset’: + The probability distribution over the vertices used when resetting + the random walk. It is either a ‘NULL’ pointer (denoting a uniform + choice that results in the original PageRank measure) or a vector + of the same length as the number of vertices. + +‘damping’: + The damping factor ("d" in the original paper). Must be a + probability in the range [0, 1]. A commonly used value is 0.85. + +‘directed’: + Boolean, whether to consider the directedness of the edges. This + is ignored for undirected graphs. + +‘vids’: + The vertex IDs for which the PageRank is returned. This parameter + is only for convenience. Computing PageRank for fewer than all + vertices will not speed up the calculation. + +‘algo’: + The PageRank implementation to use. Possible values: + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, ‘IGRAPH_PAGERANK_ALGO_PRPACK’. + +‘options’: + Options for the ARPACK method. See ‘igraph_arpack_options_t’ + (*note igraph_arpack_options_t --- Options for ARPACK_::) for + details. Supply ‘NULL’ here to use the defaults. Note that the + function overwrites the ‘n’ (number of vertices), ‘nev’ (1), ‘ncv’ + (3) and ‘which’ (LM) parameters and it always starts the + calculation from a non-random vector calculated based on the degree + of the vertices. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID in ‘vids’ or an invalid reset + vector in ‘reset’. + + Time complexity: depends on the input graph, usually it is O(|E|), +the number of edges. + + *See also:. * + +‘’ + ‘igraph_pagerank()’ (*note igraph_pagerank --- Calculates the + Google PageRank for the specified vertices_::) for the + non-personalized implementation, + ‘igraph_personalized_pagerank_vs()’ (*note + igraph_personalized_pagerank_vs --- Calculates the personalized + Google PageRank for the specified vertices_::) for a personalized + implementation with resetting to specific vertices. + + +File: igraph-docs.info, Node: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_, Next: igraph_constraint --- Burt's constraint scores_, Prev: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_, Up: Centrality measures + +17.11.8 igraph_personalized_pagerank_vs -- Calculates the personalized Google PageRank for the specified vertices. +------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_personalized_pagerank_vs( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_vs_t reset_vids, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + + The personalized PageRank is similar to the original PageRank +measure, but when the random walk is restarted, a new starting vertex is +chosen according to a specified distribution. This distribution is used +both when restarting randomly with probability ‘1 - damping’, and when +the walker is forced to restart due to being stuck in a sink vertex (a +vertex with no outgoing edges). + + This simplified interface takes a vertex sequence and resets the +random walk to one of the vertices in the specified vertex sequence, +chosen uniformly. A typical application of personalized PageRank is +when the random walk is reset to the same vertex every time: this can +easily be achieved using ‘igraph_vss_1()’ (*note igraph_vss_1 --- Vertex +set with a single vertex [immediate version]_::) which generates a +vertex sequence containing only a single vertex. + + Note that the personalized PageRank of a given vertex depends on the +personalized PageRank of all other vertices, so even if you want to +calculate the personalized PageRank for only some of the vertices, all +of them must be calculated. Requesting the personalized PageRank for +only some of the vertices does not result in any performance increase at +all. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Optional edge weights, it is either a null pointer, then the edges + are not weighted, or a vector of the same length as the number of + edges. + +‘vector’: + Pointer to an initialized vector, the result is stored here. It is + resized as needed. + +‘value’: + Pointer to a real variable. When using + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, the eigenvalue corresponding to the + PageRank vector is stored here. It is expected to be exactly one. + Checking this value can be used to diagnose cases when ARPACK + failed to converge to the leading eigenvector. When using + ‘IGRAPH_PAGERANK_ALGO_PRPACK’, this is always set to 1.0. + +‘reset_vids’: + IDs of the vertices used when resetting the random walk. The walk + will be restarted from a vertex in this set, chosen uniformly at + random. Duplicate vertices are allowed. + +‘damping’: + The damping factor ("d" in the original paper). Must be a + probability in the range [0, 1]. A commonly used value is 0.85. + +‘directed’: + Boolean, whether to consider the directedness of the edges. This + is ignored for undirected graphs. + +‘vids’: + The vertex IDs for which the PageRank is returned. This parameter + is only for convenience. Computing PageRank for fewer than all + vertices will not speed up the calculation. + +‘algo’: + The PageRank implementation to use. Possible values: + ‘IGRAPH_PAGERANK_ALGO_ARPACK’, ‘IGRAPH_PAGERANK_ALGO_PRPACK’. + +‘options’: + Options for the ARPACK method. See ‘igraph_arpack_options_t’ + (*note igraph_arpack_options_t --- Options for ARPACK_::) for + details. Supply ‘NULL’ here to use the defaults. Note that the + function overwrites the ‘n’ (number of vertices), ‘nev’ (1), ‘ncv’ + (3) and ‘which’ (LM) parameters and it always starts the + calculation from a non-random vector calculated based on the degree + of the vertices. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID in ‘vids’ or an empty reset + vertex sequence in ‘vids_reset’. + + Time complexity: depends on the input graph, usually it is O(|E|), +the number of edges. + + *See also:. * + +‘’ + ‘igraph_pagerank()’ (*note igraph_pagerank --- Calculates the + Google PageRank for the specified vertices_::) for the + non-personalized implementation. + + +File: igraph-docs.info, Node: igraph_constraint --- Burt's constraint scores_, Next: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_, Prev: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_, Up: Centrality measures + +17.11.9 igraph_constraint -- Burt's constraint scores. +------------------------------------------------------ + + + igraph_error_t igraph_constraint(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, const igraph_vector_t *weights); + + This function calculates Burt's constraint scores for the given +vertices, also known as structural holes. + + Burt's constraint is higher if ego has less, or mutually stronger +related (i.e. more redundant) contacts. Burt's measure of constraint, +C[i], of vertex i's ego network V[i], is defined for directed and valued +graphs, + + C[i] = sum( sum( (p[i,q] p[q,j])^2, q in V[i], q != i,j ), j in + V[], j != i) + +for a graph of order (i.e. number of vertices) N, where proportional +tie strengths are defined as + + p[i,j]=(a[i,j]+a[j,i]) / sum(a[i,k]+a[k,i], k in V[i], k != i), + +a[i,j] are elements of A and the latter being the graph adjacency +matrix. For isolated vertices, constraint is undefined. + + Burt, R.S. (2004). Structural holes and good ideas. American +Journal of Sociology 110, 349-399. + + The first R version of this function was contributed by Jeroen +Bruggeman. + + *Arguments:. * + +‘graph’: + A graph object. + +‘res’: + Pointer to an initialized vector, the result will be stored here. + The vector will be resized to have the appropriate size for holding + the result. + +‘vids’: + Vertex selector containing the vertices for which the constraint + should be calculated. + +‘weights’: + Vector giving the weights of the edges. If it is ‘NULL’ then each + edge is supposed to have the same weight. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+E|+n*d^2), n is the number of vertices for +which the constraint is calculated and d is the average degree, |V| is +the number of vertices, |E| the number of edges in the graph. If the +weights argument is ‘NULL’ then the time complexity is O(|V|+n*d^2). + + +File: igraph-docs.info, Node: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_, Next: igraph_strength --- Strength of the vertices; also called weighted vertex degree_, Prev: igraph_constraint --- Burt's constraint scores_, Up: Centrality measures + +17.11.10 igraph_maxdegree -- The maximum degree in a graph (or set of vertices). +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_maxdegree( + const igraph_t *graph, igraph_int_t *res, igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops + ); + + The largest in-, out- or total degree of the specified vertices is +calculated. If the graph has no vertices, or ‘vids’ is empty, 0 is +returned, as this is the smallest possible value for degrees. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer (‘igraph_int_t’), the result will be stored + here. + +‘vids’: + Vector giving the vertex IDs for which the maximum degree will be + calculated. + +‘mode’: + Defines the type of the degree. ‘IGRAPH_OUT’, out-degree, + ‘IGRAPH_IN’, in-degree, ‘IGRAPH_ALL’, total degree (sum of the in- + and out-degree). This parameter is ignored for undirected graphs. + +‘loops’: + Specifies how to treat loop edges when calculating the degree. + ‘IGRAPH_NO_LOOPS’ ignores loop edges; ‘IGRAPH_LOOPS_ONCE’ counts + each loop edge only once; ‘IGRAPH_LOOPS_TWICE’ counts each loop + edge twice in undirected graphs and once in directed graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. ‘IGRAPH_EINVMODE’: + invalid mode argument. + + Time complexity: O(v) if ‘loops’ is ‘true’, and O(v*d) otherwise. v +is the number of vertices for which the degree will be calculated, and d +is their (average) degree. + + *See also:. * + +‘’ + ‘igraph_degree()’ (*note igraph_degree --- The degree of some + vertices in a graph_::) to retrieve the degrees for several + vertices. + + +File: igraph-docs.info, Node: igraph_strength --- Strength of the vertices; also called weighted vertex degree_, Next: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_, Prev: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_, Up: Centrality measures + +17.11.11 igraph_strength -- Strength of the vertices, also called weighted vertex degree. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_strength( + const igraph_t *graph, igraph_vector_t *res, const igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops, const igraph_vector_t *weights + ); + + In a weighted network the strength of a vertex is the sum of the +weights of all incident edges. In a non-weighted network this is +exactly the vertex degree. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized vector, the result is stored here. It + will be resized as needed. + +‘vids’: + The vertices for which the calculation is performed. + +‘mode’: + Gives whether to count only outgoing (‘IGRAPH_OUT’), incoming + (‘IGRAPH_IN’) edges or both (‘IGRAPH_ALL’). This parameter is + ignored for undirected graphs. + +‘loops’: + Constant of type ‘igraph_loops_t’ (*note igraph_loops_t --- How to + interpret self-loops in undirected graphs?::). Specifies how to + treat loop edges when calculating the strength. ‘IGRAPH_NO_LOOPS’ + ignores loop edges; ‘IGRAPH_LOOPS_ONCE’ counts each loop edge only + once; ‘IGRAPH_LOOPS_TWICE’ counts each loop edge twice in + undirected graphs and once in directed graphs. + +‘weights’: + A vector giving the edge weights. If this is a ‘NULL’ pointer, + then ‘igraph_degree()’ (*note igraph_degree --- The degree of some + vertices in a graph_::) is called to perform the calculation. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number vertices and edges. + + *See also:. * + +‘’ + ‘igraph_degree()’ (*note igraph_degree --- The degree of some + vertices in a graph_::) for the traditional, non-weighted version. + + +File: igraph-docs.info, Node: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_, Next: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_, Prev: igraph_strength --- Strength of the vertices; also called weighted vertex degree_, Up: Centrality measures + +17.11.12 igraph_eigenvector_centrality -- Eigenvector centrality of the vertices. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_eigenvector_centrality(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_arpack_options_t *options); + + Eigenvector centrality is a measure of the importance of a node in a +network. It assigns relative scores to all nodes in the network based +on the principle that connections from high-scoring nodes contribute +more to the score of the node in question than equal connections from +low-scoring nodes. Specifically, the eigenvector centrality of each +vertex is proportional to the sum of eigenvector centralities of its +neighbors. In practice, the centralities are determined by calculating +the eigenvector corresponding to the largest positive eigenvalue of the +adjacency matrix. This is motivated by the fact that the principal +eigenvector is guaranteed to be non-negative, assuming that edge weights +are also non-negative. In fact, in connected undirected graphs, this is +the _only_ non-negative eigenvector. + + In the undirected case, this function considers the diagonal entries +of the adjacency matrix to be _twice_ the number of self-loops on the +corresponding vertex. + + In the weighted case, the eigenvector centrality of a vertex is +proportional to the weighted sum of centralities of its neighbours, i.e. +‘c_j = sum_i w_ij c_i’, where ‘w_ij’ is the weight of the edge +connecting vertex ‘i’ to ‘j’. The weights of parallel edges are added +up. + + The centrality scores returned by igraph are normalized such that the +largest eigenvector centrality score is 1, unless all scores are zeros. + + Eigenvector centrality is meaningful only for (strongly) connected +graphs. Undirected graphs that are not connected should be decomposed +into connected components, and the eigenvector centrality calculated for +each separately. This function does not directly verify that the graph +is connected. If it is not, in the undirected case the scores of all +but one component will typically be zeros. When zeros are detected, a +warning is issued. + + Also note that the adjacency matrix of a directed acyclic graph or +the adjacency matrix of an empty graph does not possess positive +eigenvalues, therefore the eigenvector centrality is not meaningful for +these graphs. igraph will return an eigenvalue of zero in such cases. +The returned eigenvector centralities will all be equal for vertices +with zero out-degree or zero in-degrees (depending on whether ‘mode’ is +‘IGRAPH_OUT’ or ‘IGRAPH_IN’) and zeros for other vertices. Such +pathological cases can be detected by asking igraph to calculate the +eigenvalue as well (using the ‘value’ parameter, see below) and checking +whether the eigenvalue is very close to zero. + + Eigenvector centrality was developed for networks with non-negative +edge weights. While igraph does not refuse to carry out the calculation +with negative weights, it will issue a warning. + + When working with directed graphs, consider using hub and authority +scores instead, see ‘igraph_hub_and_authority_scores()’ (*note +igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores +[HITS]_::). + + *Arguments:. * + +‘graph’: + The input graph. It may be directed. + +‘vector’: + Pointer to an initialized vector, it will be resized as needed. + The result of the computation is stored here. It can be a null + pointer, then it is ignored. + +‘value’: + If not a null pointer, then the eigenvalue corresponding to the + found eigenvector is stored here. + +‘mode’: + How to consider edge directions in directed graphs. It is ignored + for undirected graphs. Possible values: + + ‘IGRAPH_OUT’ + the left eigenvector of the adjacency matrix is calculated, + i.e. the centrality of a vertex is proportional to the sum of + centralities of vertices pointing to it. This is the standard + eigenvector centrality. + + ‘IGRAPH_IN’ + the right eigenvector of the adjacency matrix is calculated, + i.e. the centrality of a vertex is proportional to the sum of + centralities of vertices it points to. + + ‘IGRAPH_ALL’ + edge directions are ignored, and the unweighted eigenvector + centrality is calculated. + +‘weights’: + A null pointer (indicating no edge weights), or a vector giving the + weights of the edges. Weights should be positive to guarantee a + meaningful result. The algorithm might produce complex numbers + when some weights are negative and the graph is directed. In this + case only the real part is reported. + +‘options’: + Options to ARPACK. See ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) for details. + Supply ‘NULL’ here to use the defaults. Note that the function + overwrites the ‘n’ (number of vertices) parameter and it always + starts the calculation from a non-random vector calculated based on + the degree of the vertices. + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on the input graph, usually it is +O(|V|+|E|). + + *See also:. * + +‘’ + ‘igraph_pagerank’ (*note igraph_pagerank --- Calculates the Google + PageRank for the specified vertices_::) and + ‘igraph_personalized_pagerank’ (*note igraph_personalized_pagerank + --- Calculates the personalized Google PageRank for the specified + vertices_::) for modifications of eigenvector centrality. + ‘igraph_hub_and_authority_scores()’ (*note + igraph_hub_and_authority_scores --- Kleinberg's hub and authority + scores [HITS]_::) for a similar pair of measures intended for + directed graphs. + + * File examples/simple/eigenvector_centrality.c* + + +File: igraph-docs.info, Node: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_, Next: igraph_convergence_degree --- Calculates the convergence degree of each edge in a graph_, Prev: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_, Up: Centrality measures + +17.11.13 igraph_hub_and_authority_scores -- Kleinberg's hub and authority scores (HITS). +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_hub_and_authority_scores(const igraph_t *graph, + igraph_vector_t *hub_vector, igraph_vector_t *authority_vector, + igraph_real_t *value, + const igraph_vector_t *weights, igraph_arpack_options_t *options); + + Hub and authority scores are a generalization of the ideas behind +eigenvector centrality to directed graphs. The authority score of a +vertex is proportional to the sum of the hub scores of vertices that +point to it. Conversely, the hub score of a vertex is proportional to +the sum of authority scores of vertices that it points to. These +concepts are also known under the name Hyperlink-Induced Topic Search +(HITS). + + The hub and authority scores of the vertices are defined as the +principal eigenvectors of ‘A A^T’ and ‘A^T A’, respectively, where ‘A’ +is the adjacency matrix of the graph and ‘A^T’ is its transpose. The +motivation for choosing the principal eigenvector is that it is +guaranteed to be non-negative when edge weights are also non-negative. + + If vectors ‘h’ and ‘a’ contain hub and authority scores, then the two +scores are related by ‘h = A a’ and ‘a = A^T h’. When the principal +eigenvalue of ‘A A^T’ is degenerate, there is no unique solution to the +hub- and authority-score problem. igraph guarantees that the scores +that are returned are matching, i.e. are related by these formulas, +even in this situation. + + Note that hub and authority scores are not well behaved in extremely +sparse graphs where no single connected component dominates the +undirected graphs corresponding to ‘A A^T’ and ‘A^T A’. In these cases, +there are many different non-negative eigenvectors, all reasonable +solutions to the HITS equations. The symptom of such a situation is +that a large fraction of the scores are zeros. igraph issues a warning +when this is detected. + + Results are scaled so that the largest hub and authority scores are +both 1. + + The concept of hub and authority scores were developed for _directed_ +graphs. In undirected graphs, both the hub and authority scores are +equal to the eigenvector centrality, which can be computed using +‘igraph_eigenvector_centrality()’ (*note igraph_eigenvector_centrality +--- Eigenvector centrality of the vertices_::). + + HITS scores were developed for networks with non-negative edge +weights. While igraph does not refuse to carry out the calculation with +negative weights, it will issue a warning. + + See the following reference on the meaning of this score: J. +Kleinberg. Authoritative sources in a hyperlinked environment. _ +Proc. 9th ACM-SIAM Symposium on Discrete Algorithms, _ 1998. Extended +version in _ Journal of the ACM _ 46 (1999). +https://doi.org/10.1145/324133.324140 +(https://doi.org/10.1145/324133.324140) Also appears as IBM Research +Report RJ 10076, May 1997. + + *Arguments:. * + +‘graph’: + The input graph. Can be directed and undirected. + +‘hub_vector’: + Pointer to an initialized vector, the hub scores are stored here. + If a null pointer then it is ignored. + +‘authority_vector’: + Pointer to an initialized vector, the authority scores are stored + here. If a null pointer then it is ignored. + +‘value’: + If not a null pointer then the eigenvalue corresponding to the + calculated eigenvectors is stored here. + +‘weights’: + A null pointer (meaning no edge weights), or a vector giving the + weights of the edges. + +‘options’: + Options to ARPACK. See ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) for details. + Supply ‘NULL’ here to use the defaults. Note that the function + overwrites the ‘n’ (number of vertices) parameter and it always + starts the calculation from a vector calculated based on the degree + of the vertices. + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on the input graph, usually it is O(|V|), +the number of vertices. + + *See also:. * + +‘’ + ‘igraph_pagerank()’ (*note igraph_pagerank --- Calculates the + Google PageRank for the specified vertices_::), + ‘igraph_personalized_pagerank()’ (*note + igraph_personalized_pagerank --- Calculates the personalized Google + PageRank for the specified vertices_::); + ‘igraph_eigenvector_centrality()’ (*note + igraph_eigenvector_centrality --- Eigenvector centrality of the + vertices_::) for a similar measure intended for undirected graphs. + + +File: igraph-docs.info, Node: igraph_convergence_degree --- Calculates the convergence degree of each edge in a graph_, Prev: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_, Up: Centrality measures + +17.11.14 igraph_convergence_degree -- Calculates the convergence degree of each edge in a graph. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_convergence_degree(const igraph_t *graph, igraph_vector_t *result, + igraph_vector_t *ins, igraph_vector_t *outs); + + Let us define the input set of an edge (i, j) as the set of vertices +where the shortest paths passing through (i, j) originate, and +similarly, let us defined the output set of an edge (i, j) as the set of +vertices where the shortest paths passing through (i, j) terminate. The +convergence degree of an edge is defined as the normalized value of the +difference between the size of the input set and the output set, i.e. +the difference of them divided by the sum of them. Convergence degrees +are in the range (-1, 1); a positive value indicates that the edge is +_convergent_ since the shortest paths passing through it originate from +a larger set and terminate in a smaller set, while a negative value +indicates that the edge is _divergent_ since the paths originate from a +small set and terminate in a larger set. + + Note that the convergence degree as defined above does not make sense +in undirected graphs as there is no distinction between the input and +output set. Therefore, for undirected graphs, the input and output sets +of an edge are determined by orienting the edge arbitrarily while +keeping the remaining edges undirected, and then taking the absolute +value of the convergence degree. + + *Arguments:. * + +‘graph’: + The input graph, it can be either directed or undirected. + +‘result’: + Pointer to an initialized vector; the convergence degrees of each + edge will be stored here. May be ‘NULL’ if we are not interested + in the exact convergence degrees. + +‘ins’: + Pointer to an initialized vector; the size of the input set of each + edge will be stored here. May be ‘NULL’ if we are not interested + in the sizes of the input sets. + +‘outs’: + Pointer to an initialized vector; the size of the output set of + each edge will be stored here. May be ‘NULL’ if we are not + interested in the sizes of the output sets. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V||E|), the number of vertices times the number +of edges. + + +File: igraph-docs.info, Node: Range-limited centrality measures, Next: Subset-limited centrality measures, Prev: Centrality measures, Up: Structural properties of graphs + +17.12 Range-limited centrality measures +======================================= + +* Menu: + +* igraph_closeness_cutoff -- Range limited closeness centrality.: igraph_closeness_cutoff --- Range limited closeness centrality_. +* igraph_harmonic_centrality_cutoff -- Range limited harmonic centrality.: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_. +* igraph_betweenness_cutoff -- Range-limited betweenness centrality.: igraph_betweenness_cutoff --- Range-limited betweenness centrality_. +* igraph_edge_betweenness_cutoff -- Range-limited betweenness centrality of the edges.: igraph_edge_betweenness_cutoff --- Range-limited betweenness centrality of the edges_. + + +File: igraph-docs.info, Node: igraph_closeness_cutoff --- Range limited closeness centrality_, Next: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_, Up: Range-limited centrality measures + +17.12.1 igraph_closeness_cutoff -- Range limited closeness centrality. +---------------------------------------------------------------------- + + + igraph_error_t igraph_closeness_cutoff(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff); + + This function computes a range-limited version of closeness +centrality by considering only those shortest paths whose length is no +greater then the given cutoff value. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result of the computation, a vector containing the + range-limited closeness centrality scores for the given vertices. + +‘reachable_count’: + If not ‘NULL’, this vector will contain the number of vertices + reachable within the cutoff distance from each vertex for which the + range-limited closeness is calculated (not including that vertex). + +‘all_reachable’: + Pointer to a Boolean. If not ‘NULL’, it indicates if all vertices + of the graph were reachable from each vertex in ‘vids’ within the + given cutoff distance. + +‘vids’: + The vertices for which the range limited closeness centrality will + be computed. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘weights’: + An optional vector containing edge weights for weighted closeness. + No edge weight may be NaN. Supply a null pointer here for + traditional, unweighted closeness. + +‘normalized’: + If true, the inverse of the mean distance to vertices reachable + within the cutoff is returned. If false, the inverse of the sum of + distances is returned. + +‘cutoff’: + The maximal length of paths that will be considered. If negative + or ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do + not limit results"_::), the exact closeness will be calculated (no + upper limit on path lengths). + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: At most O(n|E|) for the unweighted case and +O(n|E|log|V|+|V|) for the weighted case, where n is the number of +vertices for which the calculation is done, |V| is the number of +vertices and |E| is the number of edges in the graph. The timing +decreases with smaller cutoffs in a way that depends on the graph +structure. + + *See also:. * + +‘’ + ‘igraph_closeness()’ (*note igraph_closeness --- Closeness + centrality calculations for some vertices_::) to calculate the + exact closeness centrality. + + +File: igraph-docs.info, Node: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_, Next: igraph_betweenness_cutoff --- Range-limited betweenness centrality_, Prev: igraph_closeness_cutoff --- Range limited closeness centrality_, Up: Range-limited centrality measures + +17.12.2 igraph_harmonic_centrality_cutoff -- Range limited harmonic centrality. +------------------------------------------------------------------------------- + + + igraph_error_t igraph_harmonic_centrality_cutoff(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff); + + This function computes the range limited version of harmonic +centrality: only those shortest paths are considered whose length is not +above the given cutoff. The inverse distance to vertices not reachable +within the cutoff is considered to be zero. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result of the computation, a vector containing the range + limited harmonic centrality scores for the given vertices. + +‘vids’: + The vertices for which the harmonic centrality will be computed. + +‘mode’: + The type of shortest paths to be used for the calculation in + directed graphs. Possible values: + + ‘IGRAPH_OUT’ + the lengths of the outgoing paths are calculated. + + ‘IGRAPH_IN’ + the lengths of the incoming paths are calculated. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘weights’: + An optional vector containing edge weights for weighted harmonic + centrality. No edge weight may be NaN. If ‘NULL’, all weights are + considered to be one. + +‘normalized’: + Boolean, whether to normalize the result. If true, the result is + the mean inverse path length to other vertices. i.e. it is + normalized by the number of vertices minus one. If false, the + result is the sum of inverse path lengths to other vertices. + +‘cutoff’: + The maximal length of paths that will be considered. The inverse + distance to vertices that are not reachable within the cutoff path + length is considered to be zero. Supply a negative value to + compute the exact harmonic centrality, without any upper limit on + the length of paths. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: At most O(n|E|) for the unweighted case and +O(n|E|log|V|+|V|) for the weighted case, where n is the number of +vertices for which the calculation is done, |V| is the number of +vertices and |E| is the number of edges in the graph. The timing +decreases with smaller cutoffs in a way that depends on the graph +structure. + + *See also:. * + +‘’ + ‘igraph_harmonic_centrality()’ (*note igraph_harmonic_centrality + --- Harmonic centrality for some vertices_::) to calculate the + exact harmonic centrality. Other centrality types: + ‘igraph_closeness()’ (*note igraph_closeness --- Closeness + centrality calculations for some vertices_::), + ‘igraph_betweenness()’ (*note igraph_betweenness --- Betweenness + centrality of some vertices_::). + + +File: igraph-docs.info, Node: igraph_betweenness_cutoff --- Range-limited betweenness centrality_, Next: igraph_edge_betweenness_cutoff --- Range-limited betweenness centrality of the edges_, Prev: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_, Up: Range-limited centrality measures + +17.12.3 igraph_betweenness_cutoff -- Range-limited betweenness centrality. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff); + + This function computes a range-limited version of betweenness +centrality by considering only those shortest paths whose length is no +greater then the given cutoff value. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional vector containing edge weights for calculating weighted + betweenness. No edge weight may be NaN. Supply a null pointer here + for unweighted betweenness. + +‘res’: + The result of the computation, a vector containing the + range-limited betweenness scores for the specified vertices. + +‘vids’: + The vertices for which the range-limited betweenness centrality + scores will be returned. This paramerer is for convenience only + and does not affect performance. Internally, the betewenness of + all vertices is calculated. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores by the number of vertex + pairs. In directed graphs, the number of ordered vertex pairs, in + undirected graphs the number of unordered vertex pairs is used. + +‘cutoff’: + The maximal length of paths that will be considered. If negative + or ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do + not limit results"_::), the exact betweenness will be calculated, + and there will be no upper limit on path lengths. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID passed in ‘vids’. + + Time complexity: O(|V||E|), |V| and |E| are the number of vertices +and edges in the graph. Note that the time complexity is independent of +the number of vertices for which the score is calculated. + + *See also:. * + +‘’ + ‘igraph_betweenness()’ (*note igraph_betweenness --- Betweenness + centrality of some vertices_::) to calculate the exact betweenness + and ‘igraph_edge_betweenness_cutoff()’ (*note + igraph_edge_betweenness_cutoff --- Range-limited betweenness + centrality of the edges_::) to calculate the range-limited edge + betweenness. + + +File: igraph-docs.info, Node: igraph_edge_betweenness_cutoff --- Range-limited betweenness centrality of the edges_, Prev: igraph_betweenness_cutoff --- Range-limited betweenness centrality_, Up: Range-limited centrality measures + +17.12.4 igraph_edge_betweenness_cutoff -- Range-limited betweenness centrality of the edges. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_edge_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff); + + This function computes a range-limited version of edge betweenness +centrality by considering only those shortest paths whose length is no +greater then the given cutoff value. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional weight vector for weighted betweenness. No edge weight + may be NaN. Supply a null pointer here for unweighted betweenness. + +‘res’: + The result of the computation, vector containing the betweenness + scores for the edges. + +‘eids’: + The edges for which the betweenness centrality will be returned. + This parameter is for convenience only, and does not affect + performance. Internally, the betweenness is be calculated for all + edges. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores by the number of vertex + pairs. In directed graphs, the number of ordered vertex pairs, in + undirected graphs the number of unordered vertex pairs is used. + +‘cutoff’: + The maximal length of paths that will be considered. If negative + of ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do + not limit results"_::), the exact betweenness will be calculated + (no upper limit on path lengths). + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + + Time complexity: O(|V||E|), |V| and |E| are the number of vertices +and edges in the graph. + + *See also:. * + +‘’ + ‘igraph_edge_betweenness()’ (*note igraph_edge_betweenness --- + Betweenness centrality of the edges_::) to compute the exact edge + betweenness and ‘igraph_betweenness_cutoff()’ (*note + igraph_betweenness_cutoff --- Range-limited betweenness + centrality_::) to compute the range-limited vertex betweenness. + + +File: igraph-docs.info, Node: Subset-limited centrality measures, Next: Centralization, Prev: Range-limited centrality measures, Up: Structural properties of graphs + +17.13 Subset-limited centrality measures +======================================== + +* Menu: + +* igraph_betweenness_subset -- Betweenness centrality for a subset of source and target vertices.: igraph_betweenness_subset --- Betweenness centrality for a subset of source and target vertices_. +* igraph_edge_betweenness_subset -- Edge betweenness centrality for a subset of source and target vertices.: igraph_edge_betweenness_subset --- Edge betweenness centrality for a subset of source and target vertices_. + + +File: igraph-docs.info, Node: igraph_betweenness_subset --- Betweenness centrality for a subset of source and target vertices_, Next: igraph_edge_betweenness_subset --- Edge betweenness centrality for a subset of source and target vertices_, Up: Subset-limited centrality measures + +17.13.1 igraph_betweenness_subset -- Betweenness centrality for a subset of source and target vertices. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized); + + This function computes the subset-limited version of betweenness +centrality by considering only those shortest paths that lie between +vertices in a given source and target subset. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional vector containing edge weights for calculating weighted + betweenness. No edge weight may be NaN. Supply a null pointer here + for unweighted betweenness. + +‘res’: + The result of the computation, a vector containing the betweenness + score for the subset of vertices. + +‘sources’: + A vertex selector for the sources of the shortest paths taken into + considuration in the betweenness calculation. + +‘targets’: + A vertex selector for the targets of the shortest paths taken into + considuration in the betweenness calculation. + +‘vids’: + The vertices for which the subset-limited betweenness centrality + scores will be computed. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores. Normalization is + currently unimplemented, and setting this to ‘true’ raises an + error. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID passed in ‘vids’, ‘sources’ or + ‘targets’ + + Time complexity: O(|S||E|), where |S| is the number of vertices in +the subset and |E| is the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_betweenness()’ (*note igraph_betweenness --- Betweenness + centrality of some vertices_::) to calculate the exact vertex + betweenness and ‘igraph_betweenness_cutoff()’ (*note + igraph_betweenness_cutoff --- Range-limited betweenness + centrality_::) to calculate the range-limited vertex betweenness. + + +File: igraph-docs.info, Node: igraph_edge_betweenness_subset --- Edge betweenness centrality for a subset of source and target vertices_, Prev: igraph_betweenness_subset --- Betweenness centrality for a subset of source and target vertices_, Up: Subset-limited centrality measures + +17.13.2 igraph_edge_betweenness_subset -- Edge betweenness centrality for a subset of source and target vertices. +----------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_edge_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized); + + This function computes the subset-limited version of edge betweenness +centrality by considering only those shortest paths that lie between +vertices in a given source and target subset. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + An optional weight vector for weighted betweenness. No edge weight + may be NaN. Supply a null pointer here for unweighted betweenness. + +‘res’: + The result of the computation, vector containing the betweenness + scores for the edges. + +‘sources’: + A vertex selector for the sources of the shortest paths taken into + considuration in the betweenness calculation. + +‘targets’: + A vertex selector for the targets of the shortest paths taken into + considuration in the betweenness calculation. + +‘eids’: + The edges for which the subset-limited betweenness centrality + scores will be returned. This parameter is for convenience only. + Internally, the betweenness will be calculated for all edges. + +‘directed’: + If true directed paths will be considered for directed graphs. It + is ignored for undirected graphs. + +‘normalized’: + Whether to normalize betweenness scores. Normalization is + currently unimplemented, and setting this to ‘true’ raises an + error. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + ‘IGRAPH_EINVVID’, invalid vertex ID passed in ‘sources’ or + ‘targets’ + + Time complexity: O(|S||E|), where |S| is the number of vertices in +the subset and |E| is the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_edge_betweenness()’ (*note igraph_edge_betweenness --- + Betweenness centrality of the edges_::) to compute the exact edge + betweenness and ‘igraph_edge_betweenness_cutoff()’ (*note + igraph_edge_betweenness_cutoff --- Range-limited betweenness + centrality of the edges_::) to compute the range-limited edge + betweenness. + + +File: igraph-docs.info, Node: Centralization, Next: Similarity measures, Prev: Subset-limited centrality measures, Up: Structural properties of graphs + +17.14 Centralization +==================== + +* Menu: + +* igraph_centralization -- Calculate the centralization score from the node level scores.: igraph_centralization --- Calculate the centralization score from the node level scores_. +* igraph_centralization_degree -- Calculate vertex degree and graph centralization.: igraph_centralization_degree --- Calculate vertex degree and graph centralization_. +* igraph_centralization_betweenness -- Calculate vertex betweenness and graph centralization.: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_. +* igraph_centralization_closeness -- Calculate vertex closeness and graph centralization.: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_. +* igraph_centralization_eigenvector_centrality -- Calculate eigenvector centrality scores and graph centralization.: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_. +* igraph_centralization_degree_tmax -- Theoretical maximum for graph centralization based on degree.: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_. +* igraph_centralization_betweenness_tmax -- Theoretical maximum for graph centralization based on betweenness.: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_. +* igraph_centralization_closeness_tmax -- Theoretical maximum for graph centralization based on closeness.: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_. +* igraph_centralization_eigenvector_centrality_tmax -- Theoretical maximum centralization for eigenvector centrality.: igraph_centralization_eigenvector_centrality_tmax --- Theoretical maximum centralization for eigenvector centrality_. + + +File: igraph-docs.info, Node: igraph_centralization --- Calculate the centralization score from the node level scores_, Next: igraph_centralization_degree --- Calculate vertex degree and graph centralization_, Up: Centralization + +17.14.1 igraph_centralization -- Calculate the centralization score from the node level scores. +----------------------------------------------------------------------------------------------- + + + igraph_real_t igraph_centralization(const igraph_vector_t *scores, + igraph_real_t theoretical_max, + igraph_bool_t normalized); + + For a centrality score defined on the vertices of a graph, it is +possible to define a graph level centralization index, by calculating +the sum of the deviations from the maximum centrality score. +Consequently, the higher the centralization index of the graph, the more +centralized the structure is. + + In order to make graphs of different sizes comparable, the +centralization index is usually normalized to a number between zero and +one, by dividing the (unnormalized) centralization score of the most +centralized structure with the same number of vertices. + + For most centrality indices, the most centralized structure is the +star graph, a single center connected to all other nodes in the network. +There is some variation depending on whether the graph is directed or +not, whether loop edges are allowed, etc. + + This function simply calculates the graph level index, if the node +level scores and the theoretical maximum are given. It is called by all +the measure-specific centralization functions. It uses the calculation + + ‘C = sum_v ((max_u c_u) - c_v)’ + + where ‘c’ are the centrality scores passed in ‘scores’. If +‘normalized’ is ‘true’, then ‘C/theoretical_max’ is returned. + + *Arguments:. * + +‘scores’: + A vector containing the node-level centrality scores. + +‘theoretical_max’: + The graph level centrality score of the most centralized graph with + the same number of vertices. Only used if ‘normalized’ set to + true. + +‘normalized’: + Boolean, whether to normalize the centralization by dividing the + supplied theoretical maximum. + + *Returns:. * + +‘’ + The graph level index. + + *See also:. * + +‘’ + ‘igraph_centralization_degree()’ (*note + igraph_centralization_degree --- Calculate vertex degree and graph + centralization_::), ‘igraph_centralization_betweenness()’ (*note + igraph_centralization_betweenness --- Calculate vertex betweenness + and graph centralization_::), ‘igraph_centralization_closeness()’ + (*note igraph_centralization_closeness --- Calculate vertex + closeness and graph centralization_::), and + ‘igraph_centralization_eigenvector_centrality()’ (*note + igraph_centralization_eigenvector_centrality --- Calculate + eigenvector centrality scores and graph centralization_::) for + specific centralization functions. + + Time complexity: O(n), the length of the score vector. + + * File examples/simple/centralization.c* + + +File: igraph-docs.info, Node: igraph_centralization_degree --- Calculate vertex degree and graph centralization_, Next: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_, Prev: igraph_centralization --- Calculate the centralization score from the node level scores_, Up: Centralization + +17.14.2 igraph_centralization_degree -- Calculate vertex degree and graph centralization. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_degree( + const igraph_t *graph, igraph_vector_t *res, igraph_neimode_t mode, + igraph_loops_t loops, igraph_real_t *centralization, + igraph_real_t *theoretical_max, igraph_bool_t normalized + ); + + This function calculates the degree of the vertices by passing its +arguments to ‘igraph_degree()’ (*note igraph_degree --- The degree of +some vertices in a graph_::); and it calculates the graph level +centralization index based on the results by calling +‘igraph_centralization()’ (*note igraph_centralization --- Calculate the +centralization score from the node level scores_::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + A vector if you need the node-level degree scores, or a null + pointer otherwise. + +‘mode’: + Constant the specifies the type of degree for directed graphs. + Possible values: ‘IGRAPH_IN’, ‘IGRAPH_OUT’ and ‘IGRAPH_ALL’. This + argument is ignored for undirected graphs. + +‘loops’: + Specifies how to treat loop edges when calculating the degree (and + the centralization). ‘IGRAPH_NO_LOOPS’ ignores loop edges; + ‘IGRAPH_LOOPS_ONCE’ counts each loop edge only once; + ‘IGRAPH_LOOPS_TWICE’ counts each loop edge twice in undirected + graphs and once in directed graphs. + +‘centralization’: + Pointer to a real number, the centralization score is placed here. + +‘theoretical_max’: + Pointer to real number or a null pointer. If not a null pointer, + then the theoretical maximum graph centrality score for a graph + with the same number vertices is stored here. + +‘normalized’: + Boolean, whether to calculate a normalized centralization score. + See ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::) + for how the normalization is done. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::), + ‘igraph_degree()’ (*note igraph_degree --- The degree of some + vertices in a graph_::). + + Time complexity: the complexity of ‘igraph_degree()’ (*note +igraph_degree --- The degree of some vertices in a graph_::) plus O(n), +the number of vertices queried, for calculating the centralization +score. + + +File: igraph-docs.info, Node: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_, Next: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_, Prev: igraph_centralization_degree --- Calculate vertex degree and graph centralization_, Up: Centralization + +17.14.3 igraph_centralization_betweenness -- Calculate vertex betweenness and graph centralization. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_betweenness(const igraph_t *graph, + igraph_vector_t *res, + igraph_bool_t directed, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); + + This function calculates the betweenness centrality of the vertices +by passing its arguments to ‘igraph_betweenness()’ (*note +igraph_betweenness --- Betweenness centrality of some vertices_::); and +it calculates the graph level centralization index based on the results +by calling ‘igraph_centralization()’ (*note igraph_centralization --- +Calculate the centralization score from the node level scores_::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + A vector if you need the node-level betweenness scores, or a null + pointer otherwise. + +‘directed’: + Boolean, whether to consider directed paths when calculating + betweenness. + +‘centralization’: + Pointer to a real number, the centralization score is placed here. + +‘theoretical_max’: + Pointer to real number or a null pointer. If not a null pointer, + then the theoretical maximum graph centrality score for a graph + with the same number vertices is stored here. + +‘normalized’: + Boolean, whether to calculate a normalized centralization score. + See ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::) + for how the normalization is done. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::), + ‘igraph_betweenness()’ (*note igraph_betweenness --- Betweenness + centrality of some vertices_::). + + Time complexity: the complexity of ‘igraph_betweenness()’ (*note +igraph_betweenness --- Betweenness centrality of some vertices_::) plus +O(n), the number of vertices queried, for calculating the centralization +score. + + +File: igraph-docs.info, Node: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_, Next: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_, Prev: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_, Up: Centralization + +17.14.4 igraph_centralization_closeness -- Calculate vertex closeness and graph centralization. +----------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_closeness(const igraph_t *graph, + igraph_vector_t *res, + igraph_neimode_t mode, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); + + This function calculates the closeness centrality of the vertices by +passing its arguments to ‘igraph_closeness()’ (*note igraph_closeness +--- Closeness centrality calculations for some vertices_::); and it +calculates the graph level centralization index based on the results by +calling ‘igraph_centralization()’ (*note igraph_centralization --- +Calculate the centralization score from the node level scores_::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + A vector if you need the node-level closeness scores, or a null + pointer otherwise. + +‘mode’: + Constant the specifies the type of closeness for directed graphs. + Possible values: ‘IGRAPH_IN’, ‘IGRAPH_OUT’ and ‘IGRAPH_ALL’. This + argument is ignored for undirected graphs. See + ‘igraph_closeness()’ (*note igraph_closeness --- Closeness + centrality calculations for some vertices_::) argument with the + same name for more. + +‘centralization’: + Pointer to a real number, the centralization score is placed here. + +‘theoretical_max’: + Pointer to real number or a null pointer. If not a null pointer, + then the theoretical maximum graph centrality score for a graph + with the same number vertices is stored here. + +‘normalized’: + Boolean, whether to calculate a normalized centralization score. + See ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::) + for how the normalization is done. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::), + ‘igraph_closeness()’ (*note igraph_closeness --- Closeness + centrality calculations for some vertices_::). + + Time complexity: the complexity of ‘igraph_closeness()’ (*note +igraph_closeness --- Closeness centrality calculations for some +vertices_::) plus O(n), the number of vertices queried, for calculating +the centralization score. + + +File: igraph-docs.info, Node: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_, Next: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_, Prev: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_, Up: Centralization + +17.14.5 igraph_centralization_eigenvector_centrality -- Calculate eigenvector centrality scores and graph centralization. +------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_eigenvector_centrality( + const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + igraph_arpack_options_t *options, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); + + This function calculates the eigenvector centrality of the vertices +by passing its arguments to ‘igraph_eigenvector_centrality()’ (*note +igraph_eigenvector_centrality --- Eigenvector centrality of the +vertices_::); and it calculates the graph level centralization index +based on the results by calling ‘igraph_centralization()’ (*note +igraph_centralization --- Calculate the centralization score from the +node level scores_::). + + Note that vertex-level eigenvector centrality scores do not have a +natural scale. As with any eigenvector, their interpretation is +invariant to scaling by a constant factor. However, due to how +graph-level _centralization_ is defined, its value depends on the +specific scale/normalization used for vertex-level scores. Which of two +graphs will have a higher eigenvector _centralization_ depends on the +choice of normalization for centralities. This function makes the +specific choice of scaling vertex-level centrality scores by their +maximum (i.e. it uses the ∞-norm). Other normalization choices, +such as the 1-norm or 2-norm are not currently implemented. + + *Arguments:. * + +‘graph’: + The input graph. + +‘vector’: + A vector if you need the node-level eigenvector centrality scores, + or a null pointer otherwise. + +‘value’: + If not a null pointer, then the leading eigenvalue is stored here. + +‘mode’: + How to consider edge directions in directed graphs. See + ‘igraph_eigenvector_centrality()’ (*note + igraph_eigenvector_centrality --- Eigenvector centrality of the + vertices_::) for details. Ignored for directed graphs. + +‘options’: + Options to ARPACK. See ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) for details. + Note that the function overwrites the ‘n’ (number of vertices) + parameter and it always starts the calculation from a non-random + vector calculated based on the degree of the vertices. + +‘centralization’: + Pointer to a real number, the centralization score is placed here. + +‘theoretical_max’: + Pointer to real number or a null pointer. If not a null pointer, + then the theoretical maximum graph centrality score for a graph + with the same number vertices is stored here. + +‘normalized’: + Boolean, whether to calculate a normalized centralization score. + See ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::) + for how the normalization is done. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_centralization()’ (*note igraph_centralization --- + Calculate the centralization score from the node level scores_::), + ‘igraph_eigenvector_centrality()’ (*note + igraph_eigenvector_centrality --- Eigenvector centrality of the + vertices_::). + + Time complexity: the complexity of ‘igraph_eigenvector_centrality()’ +(*note igraph_eigenvector_centrality --- Eigenvector centrality of the +vertices_::) plus O(|V|), the number of vertices for the calculating the +centralization. + + +File: igraph-docs.info, Node: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_, Next: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_, Prev: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_, Up: Centralization + +17.14.6 igraph_centralization_degree_tmax -- Theoretical maximum for graph centralization based on degree. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_degree_tmax( + const igraph_t *graph, igraph_int_t nodes, igraph_neimode_t mode, + igraph_loops_t loops, igraph_real_t *res + ); + + This function returns the theoretical maximum graph centrality based +on vertex degree. + + There are two ways to call this function, the first is to supply a +graph as the ‘graph’ argument, and then the number of vertices is taken +from this object, and its directedness is considered as well. The +‘nodes’ argument is ignored in this case. The ‘mode’ argument is also +ignored if the supplied graph is undirected. + + The other way is to supply a null pointer as the ‘graph’ argument. +In this case the ‘nodes’ and ‘mode’ arguments are considered. + + The most centralized structure is the star. More specifically, for +undirected graphs it is the star, for directed graphs it is the in-star +or the out-star. + + *Arguments:. * + +‘graph’: + A graph object or a null pointer, see the description above. + +‘nodes’: + The number of nodes. This is ignored if the ‘graph’ argument is + not a null pointer. + +‘mode’: + Constant, whether the calculation is based on in-degree + (‘IGRAPH_IN’), out-degree (‘IGRAPH_OUT’) or total degree + (‘IGRAPH_ALL’). This is ignored if the ‘graph’ argument is not a + null pointer and the given graph is undirected. + +‘loops’: + Specifies how to treat loop edges when calculating the degree (and + the centralization). ‘IGRAPH_NO_LOOPS’ ignores loop edges; + ‘IGRAPH_LOOPS_ONCE’ counts each loop edge only once; + ‘IGRAPH_LOOPS_TWICE’ counts each loop edge twice in undirected + graphs and once in directed graphs. + +‘res’: + Pointer to a real variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + *See also:. * + +‘’ + ‘igraph_centralization_degree()’ (*note + igraph_centralization_degree --- Calculate vertex degree and graph + centralization_::) and ‘igraph_centralization()’ (*note + igraph_centralization --- Calculate the centralization score from + the node level scores_::). + + +File: igraph-docs.info, Node: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_, Next: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_, Prev: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_, Up: Centralization + +17.14.7 igraph_centralization_betweenness_tmax -- Theoretical maximum for graph centralization based on betweenness. +-------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_betweenness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_bool_t directed, + igraph_real_t *res); + + This function returns the theoretical maximum graph centrality based +on vertex betweenness. + + There are two ways to call this function, the first is to supply a +graph as the ‘graph’ argument, and then the number of vertices is taken +from this object, and its directedness is considered as well. The +‘nodes’ argument is ignored in this case. The ‘directed’ argument is +also ignored if the supplied graph is undirected. + + The other way is to supply a null pointer as the ‘graph’ argument. +In this case the ‘nodes’ and ‘directed’ arguments are considered. + + The most centralized structure is the star. + + *Arguments:. * + +‘graph’: + A graph object or a null pointer, see the description above. + +‘nodes’: + The number of nodes. This is ignored if the ‘graph’ argument is + not a null pointer. + +‘directed’: + Boolean, whether to use directed paths in the betweenness + calculation. This argument is ignored if ‘graph’ is not a null + pointer and it is undirected. + +‘res’: + Pointer to a real variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + *See also:. * + +‘’ + ‘igraph_centralization_betweenness()’ (*note + igraph_centralization_betweenness --- Calculate vertex betweenness + and graph centralization_::) and ‘igraph_centralization()’ (*note + igraph_centralization --- Calculate the centralization score from + the node level scores_::). + + +File: igraph-docs.info, Node: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_, Next: igraph_centralization_eigenvector_centrality_tmax --- Theoretical maximum centralization for eigenvector centrality_, Prev: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_, Up: Centralization + +17.14.8 igraph_centralization_closeness_tmax -- Theoretical maximum for graph centralization based on closeness. +---------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_closeness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res); + + This function returns the theoretical maximum graph centrality based +on vertex closeness. + + There are two ways to call this function, the first is to supply a +graph as the ‘graph’ argument, and then the number of vertices is taken +from this object, and its directedness is considered as well. The +‘nodes’ argument is ignored in this case. The ‘mode’ argument is also +ignored if the supplied graph is undirected. + + The other way is to supply a null pointer as the ‘graph’ argument. +In this case the ‘nodes’ and ‘mode’ arguments are considered. + + The most centralized structure is the star. + + *Arguments:. * + +‘graph’: + A graph object or a null pointer, see the description above. + +‘nodes’: + The number of nodes. This is ignored if the ‘graph’ argument is + not a null pointer. + +‘mode’: + Constant, specifies what kind of distances to consider to calculate + closeness. See the ‘mode’ argument of ‘igraph_closeness()’ (*note + igraph_closeness --- Closeness centrality calculations for some + vertices_::) for details. This argument is ignored if ‘graph’ is + not a null pointer and it is undirected. + +‘res’: + Pointer to a real variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + *See also:. * + +‘’ + ‘igraph_centralization_closeness()’ (*note + igraph_centralization_closeness --- Calculate vertex closeness and + graph centralization_::) and ‘igraph_centralization()’ (*note + igraph_centralization --- Calculate the centralization score from + the node level scores_::). + + +File: igraph-docs.info, Node: igraph_centralization_eigenvector_centrality_tmax --- Theoretical maximum centralization for eigenvector centrality_, Prev: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_, Up: Centralization + +17.14.9 igraph_centralization_eigenvector_centrality_tmax -- Theoretical maximum centralization for eigenvector centrality. +--------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_centralization_eigenvector_centrality_tmax( + const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res); + + This function returns the theoretical maximum graph centrality based +on vertex eigenvector centrality. + + There are two ways to call this function, the first is to supply a +graph as the ‘graph’ argument, and then the number of vertices is taken +from this object, and its directedness is considered as well. The +‘nodes’ argument is ignored in this case. The ‘mode’ argument is also +ignored if the supplied graph is undirected. + + The other way is to supply a null pointer as the ‘graph’. argument. +In this case the ‘nodes’ and ‘mode’ arguments are considered. + + The most centralized directed structure is the in-star with ‘mode’ +set to ‘IGRAPH_OUT’, and the out-star with ‘mode’ set to ‘IGRAPH_IN’. +The most centralized undirected structure is the graph with a single +edge. + + *Arguments:. * + +‘graph’: + A graph object or a null pointer, see the description above. + +‘nodes’: + The number of nodes. This is ignored if the ‘graph’ argument is + not a null pointer. + +‘mode’: + How to consider edge directions in directed graphs. See + ‘igraph_eigenvector_centrality()’ (*note + igraph_eigenvector_centrality --- Eigenvector centrality of the + vertices_::) for details. This argument is ignored if ‘graph’ is + not a null pointer and it is undirected. + +‘res’: + Pointer to a real variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + *See also:. * + +‘’ + ‘igraph_centralization_closeness()’ (*note + igraph_centralization_closeness --- Calculate vertex closeness and + graph centralization_::) and ‘igraph_centralization()’ (*note + igraph_centralization --- Calculate the centralization score from + the node level scores_::). + + +File: igraph-docs.info, Node: Similarity measures, Next: Trees and forests, Prev: Centralization, Up: Structural properties of graphs + +17.15 Similarity measures +========================= + +* Menu: + +* igraph_bibcoupling -- Bibliographic coupling.: igraph_bibcoupling --- Bibliographic coupling_. +* igraph_cocitation -- Cocitation coupling.: igraph_cocitation --- Cocitation coupling_. +* igraph_similarity_jaccard -- Jaccard similarity coefficient for the given vertices.: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_. +* igraph_similarity_jaccard_pairs -- Jaccard similarity coefficient for given vertex pairs.: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_. +* igraph_similarity_jaccard_es -- Jaccard similarity coefficient for a given edge selector.: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_. +* igraph_similarity_dice -- Dice similarity coefficient.: igraph_similarity_dice --- Dice similarity coefficient_. +* igraph_similarity_dice_pairs -- Dice similarity coefficient for given vertex pairs.: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_. +* igraph_similarity_dice_es -- Dice similarity coefficient for a given edge selector.: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_. +* igraph_similarity_inverse_log_weighted -- Vertex similarity based on the inverse logarithm of vertex degrees.: igraph_similarity_inverse_log_weighted --- Vertex similarity based on the inverse logarithm of vertex degrees_. + + +File: igraph-docs.info, Node: igraph_bibcoupling --- Bibliographic coupling_, Next: igraph_cocitation --- Cocitation coupling_, Up: Similarity measures + +17.15.1 igraph_bibcoupling -- Bibliographic coupling. +----------------------------------------------------- + + + igraph_error_t igraph_bibcoupling(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids); + + The bibliographic coupling of two vertices is the number of other +vertices they both cite, ‘igraph_bibcoupling()’ (*note +igraph_bibcoupling --- Bibliographic coupling_::) calculates this. The +bibliographic coupling score for each given vertex and all other +vertices in the graph will be calculated. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a matrix, the result of the calculation will be stored + here. The number of its rows is the same as the number of vertex + IDs in ‘vids’, the number of columns is the number of vertices in + the graph. + +‘vids’: + The vertex IDs of the vertices for which the calculation will be + done. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. + + Time complexity: O(|V|d^2), |V| is the number of vertices in the +graph, d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_cocitation()’ (*note igraph_cocitation --- Cocitation + coupling_::) + + * File examples/simple/igraph_cocitation.c* + + +File: igraph-docs.info, Node: igraph_cocitation --- Cocitation coupling_, Next: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_, Prev: igraph_bibcoupling --- Bibliographic coupling_, Up: Similarity measures + +17.15.2 igraph_cocitation -- Cocitation coupling. +------------------------------------------------- + + + igraph_error_t igraph_cocitation(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids); + + Two vertices are cocited if there is another vertex citing both of +them. ‘igraph_cocitation()’ (*note igraph_cocitation --- Cocitation +coupling_::) simply counts how many times two vertices are cocited. The +cocitation score for each given vertex and all other vertices in the +graph will be calculated. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a matrix, the result of the calculation will be stored + here. The number of its rows is the same as the number of vertex + IDs in ‘vids’, the number of columns is the number of vertices in + the graph. + +‘vids’: + The vertex IDs of the vertices for which the calculation will be + done. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. + + Time complexity: O(|V|d^2), |V| is the number of vertices in the +graph, d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_bibcoupling()’ (*note igraph_bibcoupling --- Bibliographic + coupling_::) + + * File examples/simple/igraph_cocitation.c* + + +File: igraph-docs.info, Node: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_, Next: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_, Prev: igraph_cocitation --- Cocitation coupling_, Up: Similarity measures + +17.15.3 igraph_similarity_jaccard -- Jaccard similarity coefficient for the given vertices. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_jaccard(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, igraph_neimode_t mode, igraph_bool_t loops); + + The Jaccard similarity coefficient of two vertices is the number of +common neighbors divided by the number of vertices that are neighbors of +at least one of the two vertices being considered. This function +calculates the pairwise Jaccard similarities for some (or all) of the +vertices. + + *Arguments:. * + +‘graph’: + The graph object to analyze + +‘res’: + Pointer to a matrix, the result of the calculation will be stored + here. The number of its rows and columns is the same as the number + of vertex IDs in ‘from’ and ‘to’, respectively. + +‘from’: + The vertex IDs of the first set of vertices of the pairs for which + the calculation will be done. + +‘to’: + The vertex IDs of the second set of vertices of the pairs for which + the calculation will be done. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves in the neighbor sets. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|V|^2 d), |V| is the number of vertices in the +vertex iterator given, d is the (maximum) degree of the vertices in the +graph. + + *See also:. * + +‘’ + ‘igraph_similarity_dice()’ (*note igraph_similarity_dice --- Dice + similarity coefficient_::), a measure very similar to the Jaccard + coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_, Next: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_, Prev: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_, Up: Similarity measures + +17.15.4 igraph_similarity_jaccard_pairs -- Jaccard similarity coefficient for given vertex pairs. +------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_jaccard_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops); + + The Jaccard similarity coefficient of two vertices is the number of +common neighbors divided by the number of vertices that are neighbors of +at least one of the two vertices being considered. This function +calculates the pairwise Jaccard similarities for a list of vertex pairs. + + *Arguments:. * + +‘graph’: + The graph object to analyze + +‘res’: + Pointer to a vector, the result of the calculation will be stored + here. The number of elements is the same as the number of pairs in + ‘pairs’. + +‘pairs’: + A vector that contains the pairs for which the similarity will be + calculated. Each pair is defined by two consecutive elements, i.e. + the first and second element of the vector specifies the first + pair, the third and fourth element specifies the second pair and so + on. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves in the neighbor sets. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(nd), n is the number of pairs in the given vector, +d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_similarity_jaccard()’ (*note igraph_similarity_jaccard --- + Jaccard similarity coefficient for the given vertices_::) to + calculate the Jaccard similarity between all pairs of a vertex set, + or ‘igraph_similarity_dice()’ (*note igraph_similarity_dice --- + Dice similarity coefficient_::) and + ‘igraph_similarity_dice_pairs()’ (*note + igraph_similarity_dice_pairs --- Dice similarity coefficient for + given vertex pairs_::) for a measure very similar to the Jaccard + coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_, Next: igraph_similarity_dice --- Dice similarity coefficient_, Prev: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_, Up: Similarity measures + +17.15.5 igraph_similarity_jaccard_es -- Jaccard similarity coefficient for a given edge selector. +------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_jaccard_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops); + + The Jaccard similarity coefficient of two vertices is the number of +common neighbors divided by the number of vertices that are neighbors of +at least one of the two vertices being considered. This function +calculates the pairwise Jaccard similarities for the endpoints of edges +in a given edge selector. + + *Arguments:. * + +‘graph’: + The graph object to analyze + +‘res’: + Pointer to a vector, the result of the calculation will be stored + here. The number of elements is the same as the number of edges in + ‘es’. + +‘es’: + An edge selector that specifies the edges to be included in the + result. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves in the neighbor sets. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(nd), n is the number of edges in the edge +selector, d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_similarity_jaccard()’ (*note igraph_similarity_jaccard --- + Jaccard similarity coefficient for the given vertices_::) and + ‘igraph_similarity_jaccard_pairs()’ (*note + igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient + for given vertex pairs_::) to calculate the Jaccard similarity + between all pairs of a vertex set or some selected vertex pairs, or + ‘igraph_similarity_dice()’ (*note igraph_similarity_dice --- Dice + similarity coefficient_::), ‘igraph_similarity_dice_pairs()’ (*note + igraph_similarity_dice_pairs --- Dice similarity coefficient for + given vertex pairs_::) and ‘igraph_similarity_dice_es()’ (*note + igraph_similarity_dice_es --- Dice similarity coefficient for a + given edge selector_::) for a measure very similar to the Jaccard + coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_dice --- Dice similarity coefficient_, Next: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_, Prev: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_, Up: Similarity measures + +17.15.6 igraph_similarity_dice -- Dice similarity coefficient. +-------------------------------------------------------------- + + + igraph_error_t igraph_similarity_dice(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode, igraph_bool_t loops); + + The Dice similarity coefficient of two vertices is twice the number +of common neighbors divided by the sum of the degrees of the vertices. +This function calculates the pairwise Dice similarities for some (or +all) of the vertices. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a matrix, the result of the calculation will be stored + here. The number of its rows and columns is the same as the number + of vertex IDs in ‘from’ and ‘to’, respectively. + +‘from’: + The vertex IDs of the first vertices of the pairs for which the + calculation will be done. + +‘to’: + The vertex IDs of the second vertices of the pairs for which the + calculation will be done. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves as their own neighbors. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(|V|^2 d), where |V| is the number of vertices in +the vertex iterator given, and d is the (maximum) degree of the vertices +in the graph. + + *See also:. * + +‘’ + ‘igraph_similarity_jaccard()’ (*note igraph_similarity_jaccard --- + Jaccard similarity coefficient for the given vertices_::), a + measure very similar to the Dice coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_, Next: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_, Prev: igraph_similarity_dice --- Dice similarity coefficient_, Up: Similarity measures + +17.15.7 igraph_similarity_dice_pairs -- Dice similarity coefficient for given vertex pairs. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_dice_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops); + + The Dice similarity coefficient of two vertices is twice the number +of common neighbors divided by the sum of the degrees of the vertices. +This function calculates the pairwise Dice similarities for a list of +vertex pairs. + + *Arguments:. * + +‘graph’: + The graph object to analyze + +‘res’: + Pointer to a vector, the result of the calculation will be stored + here. The number of elements is the same as the number of pairs in + ‘pairs’. + +‘pairs’: + A vector that contains the pairs for which the similarity will be + calculated. Each pair is defined by two consecutive elements, i.e. + the first and second element of the vector specifies the first + pair, the third and fourth element specifies the second pair and so + on. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves as their own neighbors. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(nd), n is the number of pairs in the given vector, +d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_similarity_dice()’ (*note igraph_similarity_dice --- Dice + similarity coefficient_::) to calculate the Dice similarity between + all pairs of a vertex set, or ‘igraph_similarity_jaccard()’ (*note + igraph_similarity_jaccard --- Jaccard similarity coefficient for + the given vertices_::), ‘igraph_similarity_jaccard_pairs()’ (*note + igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient + for given vertex pairs_::) and ‘igraph_similarity_jaccard_es()’ + (*note igraph_similarity_jaccard_es --- Jaccard similarity + coefficient for a given edge selector_::) for a measure very + similar to the Dice coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_, Next: igraph_similarity_inverse_log_weighted --- Vertex similarity based on the inverse logarithm of vertex degrees_, Prev: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_, Up: Similarity measures + +17.15.8 igraph_similarity_dice_es -- Dice similarity coefficient for a given edge selector. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_dice_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops); + + The Dice similarity coefficient of two vertices is twice the number +of common neighbors divided by the sum of the degrees of the vertices. +This function calculates the pairwise Dice similarities for the +endpoints of edges in a given edge selector. + + *Arguments:. * + +‘graph’: + The graph object to analyze + +‘res’: + Pointer to a vector, the result of the calculation will be stored + here. The number of elements is the same as the number of edges in + ‘es’. + +‘es’: + An edge selector that specifies the edges to be included in the + result. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. + +‘loops’: + Whether to include the vertices themselves as their own neighbors. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_EINVVID’ + invalid vertex ID passed. + + ‘IGRAPH_EINVMODE’ + invalid mode argument. + + Time complexity: O(nd), n is the number of pairs in the given vector, +d is the (maximum) degree of the vertices in the graph. + + *See also:. * + +‘’ + ‘igraph_similarity_dice()’ (*note igraph_similarity_dice --- Dice + similarity coefficient_::) and ‘igraph_similarity_dice_pairs()’ + (*note igraph_similarity_dice_pairs --- Dice similarity coefficient + for given vertex pairs_::) to calculate the Dice similarity between + all pairs of a vertex set or some selected vertex pairs, or + ‘igraph_similarity_jaccard()’ (*note igraph_similarity_jaccard --- + Jaccard similarity coefficient for the given vertices_::), + ‘igraph_similarity_jaccard_pairs()’ (*note + igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient + for given vertex pairs_::) and ‘igraph_similarity_jaccard_es()’ + (*note igraph_similarity_jaccard_es --- Jaccard similarity + coefficient for a given edge selector_::) for a measure very + similar to the Dice coefficient + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: igraph_similarity_inverse_log_weighted --- Vertex similarity based on the inverse logarithm of vertex degrees_, Prev: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_, Up: Similarity measures + +17.15.9 igraph_similarity_inverse_log_weighted -- Vertex similarity based on the inverse logarithm of vertex degrees. +--------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_similarity_inverse_log_weighted(const igraph_t *graph, + igraph_matrix_t *res, const igraph_vs_t vids, igraph_neimode_t mode); + + The inverse log-weighted similarity of two vertices is the number of +their common neighbors, weighted by the inverse logarithm of their +degrees. It is based on the assumption that two vertices should be +considered more similar if they share a low-degree common neighbor, +since high-degree common neighbors are more likely to appear even by +pure chance. + + Isolated vertices will have zero similarity to any other vertex. +Self-similarities are not calculated. + + Note that the presence of loop edges may yield counter-intuitive +results. A node with a loop edge is considered to be a neighbor of +itself _twice_ (because there are two edge stems incident on the node). +Adding a loop edge to a node may decrease its similarity to other nodes, +but it may also _increase_ it. For instance, if nodes A and B are +connected but share no common neighbors, their similarity is zero. +However, if a loop edge is added to B, then B itself becomes a common +neighbor of A and B and thus the similarity of A and B will be +increased. Consider removing loop edges explicitly before invoking this +function using ‘igraph_simplify()’ (*note igraph_simplify --- Removes +loop and/or multiple edges from the graph_::). + + See the following paper for more details: Lada A. Adamic and Eytan +Adar: Friends and neighbors on the Web. Social Networks, 25(3):211-230, +2003. https://doi.org/10.1016/S0378-8733(03)00009-1 +(https://doi.org/10.1016/S0378-8733(03)00009-1) + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a matrix, the result of the calculation will be stored + here. The number of its rows is the same as the number of vertex + IDs in ‘vids’, the number of columns is the number of vertices in + the graph. + +‘vids’: + The vertex IDs of the vertices for which the calculation will be + done. + +‘mode’: + The type of neighbors to be used for the calculation in directed + graphs. Possible values: + + ‘IGRAPH_OUT’ + the outgoing edges will be considered for each node. Nodes + will be weighted according to their in-degree. + + ‘IGRAPH_IN’ + the incoming edges will be considered for each node. Nodes + will be weighted according to their out-degree. + + ‘IGRAPH_ALL’ + the directed graph is considered as an undirected one for the + computation. Every node is weighted according to its + undirected degree. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVVID’: invalid vertex ID. + + Time complexity: O(|V|d^2), |V| is the number of vertices in the +graph, d is the (maximum) degree of the vertices in the graph. + + * File examples/simple/igraph_similarity.c* + + +File: igraph-docs.info, Node: Trees and forests, Next: Transitivity or clustering coefficient, Prev: Similarity measures, Up: Structural properties of graphs + +17.16 Trees and forests +======================= + +* Menu: + +* igraph_minimum_spanning_tree -- Calculates a minimum spanning tree of a graph.: igraph_minimum_spanning_tree --- Calculates a minimum spanning tree of a graph_. +* igraph_random_spanning_tree -- Uniformly samples the spanning trees of a graph.: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_. +* igraph_is_tree -- Decides whether the graph is a tree.: igraph_is_tree --- Decides whether the graph is a tree_. +* igraph_is_forest -- Decides whether the graph is a forest.: igraph_is_forest --- Decides whether the graph is a forest_. +* igraph_to_prufer -- Converts a tree to its Prüfer sequence.: igraph_to_prufer --- Converts a tree to its Prüfer sequence_. + + +File: igraph-docs.info, Node: igraph_minimum_spanning_tree --- Calculates a minimum spanning tree of a graph_, Next: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_, Up: Trees and forests + +17.16.1 igraph_minimum_spanning_tree -- Calculates a minimum spanning tree of a graph. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_minimum_spanning_tree( + const igraph_t *graph, igraph_vector_int_t *res, + const igraph_vector_t *weights, igraph_mst_algorithm_t method); + + Finds a minimum weight spanning tree of the graph. If the graph is +not connected then its minimum spanning forest is returned, i.e. the +set of the minimum spanning trees of each component. + + Directed graphs are treated as undirected for this computation. + + This function is deterministic, i.e. it always returns the same +spanning tree. See ‘igraph_random_spanning_tree()’ (*note +igraph_random_spanning_tree --- Uniformly samples the spanning trees of +a graph_::) for the uniform random sampling of spanning trees of a +graph. + + References: + + Prim, R.C.: Shortest connection networks and some generalizations, +Bell System Technical Journal, Vol. 36, 1957, 1389-1401. +https://doi.org/10.1002/j.1538-7305.1957.tb01515.x +(https://doi.org/10.1002/j.1538-7305.1957.tb01515.x) + + Kruskal, J. B.: On the shortest spanning subtree of a graph and the +traveling salesman problem, Proc. Amer. Math. Soc. 7 (1956), 48-50 +https://doi.org/10.1090%2FS0002-9939-1956-0078686-7 +(https://doi.org/10.1090%2FS0002-9939-1956-0078686-7) + + *Arguments:. * + +‘graph’: + The graph object. Edge directions will be ignored. + +‘res’: + An initialized vector, the IDs of the edges that constitute a + spanning tree will be returned here. Use + ‘igraph_subgraph_from_edges()’ (*note igraph_subgraph_from_edges + --- Creates a subgraph with the specified edges and their + endpoints_::) to extract the spanning tree as a separate graph + object. + +‘weights’: + A vector containing the weights of the edges in the order of edge + IDs. Weights must not be NaN. Supply ‘NULL’ to treat all edges as + having the same weight. + +‘method’: + The type of the algorithm used. + + ‘IGRAPH_MST_AUTOMATIC’ + tries to select the best performing algorithm for the current + graph. + + ‘IGRAPH_MST_UNWEIGHTED’ + ignores edge weights and produces an arbitrary spanning tree. + + ‘IGRAPH_MST_PRIM’ + uses Prim's algorithm. + + ‘IGRAPH_MST_KRUSKAL’ + uses Kruskal's algorithm. + + *Returns:. * + +‘’ + Error code. + + Time complexity: See the functions implementing the specific +algorithms. + + *See also:. * + +‘’ + ‘igraph_random_spanning_tree()’ (*note igraph_random_spanning_tree + --- Uniformly samples the spanning trees of a graph_::) to compute + a random spanning tree instead of a minimum one. + + * File examples/simple/igraph_minimum_spanning_tree.c* + + +File: igraph-docs.info, Node: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_, Next: igraph_is_tree --- Decides whether the graph is a tree_, Prev: igraph_minimum_spanning_tree --- Calculates a minimum spanning tree of a graph_, Up: Trees and forests + +17.16.2 igraph_random_spanning_tree -- Uniformly samples the spanning trees of a graph. +--------------------------------------------------------------------------------------- + + + igraph_error_t igraph_random_spanning_tree(const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t vid); + + Performs a loop-erased random walk on the graph to uniformly sample +its spanning trees. Edge directions are ignored. + + Multi-graphs are supported, and edge multiplicities will affect the +sampling frequency. For example, consider the 3-cycle graph ‘1=2-3-1’, +with two edges between vertices 1 and 2. Due to these parallel edges, +the trees ‘1-2-3’ and ‘3-1-2’ will be sampled with multiplicity 2, while +the tree ‘2-3-1’ will be sampled with multiplicity 1. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘res’: + An initialized vector, the IDs of the edges that constitute a + spanning tree will be returned here. Use + ‘igraph_subgraph_from_edges()’ (*note igraph_subgraph_from_edges + --- Creates a subgraph with the specified edges and their + endpoints_::) to extract the spanning tree as a separate graph + object. + +‘vid’: + This parameter is relevant if the graph is not connected. If + negative, a random spanning forest of all components will be + generated. Otherwise, it should be the ID of a vertex. A random + spanning tree of the component containing the vertex will be + generated. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_minimum_spanning_tree()’ (*note + igraph_minimum_spanning_tree --- Calculates a minimum spanning tree + of a graph_::), ‘igraph_random_walk()’ (*note igraph_random_walk + --- Performs a random walk on a graph_::) + + +File: igraph-docs.info, Node: igraph_is_tree --- Decides whether the graph is a tree_, Next: igraph_is_forest --- Decides whether the graph is a forest_, Prev: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_, Up: Trees and forests + +17.16.3 igraph_is_tree -- Decides whether the graph is a tree. +-------------------------------------------------------------- + + + igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_int_t *root, igraph_neimode_t mode); + + An undirected graph is a tree if it is connected and has no cycles. + + In the directed case, an additional requirement is that all edges are +oriented away from a root (out-tree or arborescence) or all edges are +oriented towards a root (in-tree or anti-arborescence). This test can +be controlled using the ‘mode’ parameter. + + By convention, the null graph (i.e. the graph with no vertices) is +considered not to be connected, and therefore not a tree. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a Boolean variable, the result will be stored here. + +‘root’: + If not ‘NULL’, the root node will be stored here. When ‘mode’ is + ‘IGRAPH_ALL’ or the graph is undirected, any vertex can be the root + and ‘root’ is set to 0 (the first vertex). When ‘mode’ is + ‘IGRAPH_OUT’ or ‘IGRAPH_IN’, the root is set to the vertex with + zero in- or out-degree, respectively. + +‘mode’: + For a directed graph this specifies whether to test for an + out-tree, an in-tree or ignore edge directions. The respective + possible values are: ‘IGRAPH_OUT’, ‘IGRAPH_IN’, ‘IGRAPH_ALL’. This + argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid mode argument. + + Time complexity: At most O(|V|+|E|), the number of vertices plus the +number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_is_forest()’ (*note igraph_is_forest --- Decides whether + the graph is a forest_::) to check if all components are trees, + which is equivalent to the graph lacking undirected cycles; + ‘igraph_is_connected()’ (*note igraph_is_connected --- Decides + whether the graph is [weakly or strongly] connected_::), + ‘igraph_is_acyclic()’ (*note igraph_is_acyclic --- Checks whether a + graph is acyclic or not_::) + + * File examples/simple/igraph_kary_tree.c* + + +File: igraph-docs.info, Node: igraph_is_forest --- Decides whether the graph is a forest_, Next: igraph_to_prufer --- Converts a tree to its Prüfer sequence_, Prev: igraph_is_tree --- Decides whether the graph is a tree_, Up: Trees and forests + +17.16.4 igraph_is_forest -- Decides whether the graph is a forest. +------------------------------------------------------------------ + + + igraph_error_t igraph_is_forest(const igraph_t *graph, igraph_bool_t *res, + igraph_vector_int_t *roots, igraph_neimode_t mode); + + An undirected graph is a forest if it has no cycles. Equivalently, a +graph is a forest if all connected components are trees. + + In the directed case, an additional requirement is that edges in each +tree are oriented away from the root (out-trees or arborescences) or all +edges are oriented towards the root (in-trees or anti-arborescences). +This test can be controlled using the ‘mode’ parameter. + + By convention, the null graph (i.e. the graph with no vertices) is +considered to be a forest. + + The ‘res’ return value of this function is cached in the graph itself +if ‘mode’ is set to ‘IGRAPH_ALL’ or if the graph is undirected. Calling +the function multiple times with no modifications to the graph in +between will return a cached value in O(1) time if the roots are not +requested. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a Boolean variable. If not ‘NULL’, then the result will + be stored here. + +‘roots’: + If not ‘NULL’, the root nodes will be stored here. When ‘mode’ is + ‘IGRAPH_ALL’ or the graph is undirected, any one vertex from each + component can be the root. When ‘mode’ is ‘IGRAPH_OUT’ or + ‘IGRAPH_IN’, all the vertices with zero in- or out-degree, + respectively are considered as root nodes. + +‘mode’: + For a directed graph this specifies whether to test for an + out-forest, an in-forest or ignore edge directions. The respective + possible values are: ‘IGRAPH_OUT’, ‘IGRAPH_IN’, ‘IGRAPH_ALL’. This + argument is ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVMODE’: invalid mode argument. + + Time complexity: At most O(|V|+|E|), the number of vertices plus the +number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_is_tree()’ (*note igraph_is_tree --- Decides whether the + graph is a tree_::) to check if a graph is a tree, i.e. a forest + with a single component; ‘igraph_is_acyclic()’ (*note + igraph_is_acyclic --- Checks whether a graph is acyclic or not_::) + to check if a graph lacks (undirected or directed) cycles. + + +File: igraph-docs.info, Node: igraph_to_prufer --- Converts a tree to its Prüfer sequence_, Prev: igraph_is_forest --- Decides whether the graph is a forest_, Up: Trees and forests + +17.16.5 igraph_to_prufer -- Converts a tree to its Prüfer sequence. +------------------------------------------------------------------- + + + igraph_error_t igraph_to_prufer(const igraph_t *graph, igraph_vector_int_t* prufer); + + A Prüfer sequence is a unique sequence of integers associated with a +labelled tree. A tree on n >= 2 vertices can be represented by a +sequence of n-2 integers, each between 0 and n-1 (inclusive). + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object which must be a tree on n >= + 2 vertices. + +‘prufer’: + A pointer to the integer vector that should hold the Prüfer + sequence; the vector must be initialized and will be resized to n - + 2. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + there is not enough memory to perform the operation. + + ‘IGRAPH_EINVAL’ + the graph is not a tree or it is has less than vertices + + *See also:. * + +‘’ + ‘igraph_from_prufer()’ (*note igraph_from_prufer --- Generates a + tree from a Prüfer sequence_::) + + +File: igraph-docs.info, Node: Transitivity or clustering coefficient, Next: Directedness conversion, Prev: Trees and forests, Up: Structural properties of graphs + +17.17 Transitivity or clustering coefficient +============================================ + +* Menu: + +* igraph_transitivity_undirected -- Calculates the transitivity (clustering coefficient) of a graph.: igraph_transitivity_undirected --- Calculates the transitivity [clustering coefficient] of a graph_. +* igraph_transitivity_local_undirected -- The local transitivity (clustering coefficient) of some vertices.: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_. +* igraph_transitivity_avglocal_undirected -- Average local transitivity (clustering coefficient).: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_. +* igraph_transitivity_barrat -- Weighted local transitivity of some vertices, as defined by A. Barrat.: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_. +* igraph_ecc -- Edge clustering coefficient of some edges.: igraph_ecc --- Edge clustering coefficient of some edges_. + + +File: igraph-docs.info, Node: igraph_transitivity_undirected --- Calculates the transitivity [clustering coefficient] of a graph_, Next: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_, Up: Transitivity or clustering coefficient + +17.17.1 igraph_transitivity_undirected -- Calculates the transitivity (clustering coefficient) of a graph. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_transitivity_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); + + The transitivity measures the probability that two neighbors of a +vertex are connected. More precisely, this is the ratio of the +triangles and connected triples in the graph, the result is a single +real number. Directed graphs are considered as undirected ones and +multi-edges are ignored. + + Note that this measure is different from the local transitivity +measure (see ‘igraph_transitivity_local_undirected()’ (*note +igraph_transitivity_local_undirected --- The local transitivity +[clustering coefficient] of some vertices_::) ) as it calculates a +single value for the whole graph. + + Clustering coefficient is an alternative name for transitivity. + + References: + + S. Wasserman and K. Faust: Social Network Analysis: Methods and +Applications. Cambridge: Cambridge University Press, 1994. + + *Arguments:. * + +‘graph’: + The graph object. Edge directions and multiplicites are ignored. + +‘res’: + Pointer to a real variable, the result will be stored here. + +‘mode’: + Defines how to treat graphs with no connected triples. + ‘IGRAPH_TRANSITIVITY_NAN’ returns ‘NaN’ in this case, + ‘IGRAPH_TRANSITIVITY_ZERO’ returns zero. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory for temporary data. + + *See also:. * + +‘’ + ‘igraph_transitivity_local_undirected()’ (*note + igraph_transitivity_local_undirected --- The local transitivity + [clustering coefficient] of some vertices_::), + ‘igraph_transitivity_avglocal_undirected()’ (*note + igraph_transitivity_avglocal_undirected --- Average local + transitivity [clustering coefficient]_::). + + Time complexity: O(|V|*d^2), |V| is the number of vertices in the +graph, d is the average node degree. + + * File examples/simple/igraph_transitivity.c* + + +File: igraph-docs.info, Node: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_, Next: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_, Prev: igraph_transitivity_undirected --- Calculates the transitivity [clustering coefficient] of a graph_, Up: Transitivity or clustering coefficient + +17.17.2 igraph_transitivity_local_undirected -- The local transitivity (clustering coefficient) of some vertices. +----------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_transitivity_local_undirected(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode); + + The transitivity measures the probability that two neighbors of a +vertex are connected. In case of the local transitivity, this +probability is calculated separately for each vertex. + + Note that this measure is different from the global transitivity +measure (see ‘igraph_transitivity_undirected()’ (*note +igraph_transitivity_undirected --- Calculates the transitivity +[clustering coefficient] of a graph_::) ) as it calculates a +transitivity value for each vertex individually. + + Clustering coefficient is an alternative name for transitivity. + + References: + + D. J. Watts and S. Strogatz: Collective dynamics of small-world +networks. Nature 393(6684):440-442 (1998). + + *Arguments:. * + +‘graph’: + The input graph. Edge directions and multiplicities are ignored. + +‘res’: + Pointer to an initialized vector, the result will be stored here. + It will be resized as needed. + +‘vids’: + Vertex set, the vertices for which the local transitivity will be + calculated. + +‘mode’: + Defines how to treat vertices with degree less than two. + ‘IGRAPH_TRANSITIVITY_NAN’ returns ‘NaN’ for these vertices, + ‘IGRAPH_TRANSITIVITY_ZERO’ returns zero. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_transitivity_undirected()’ (*note + igraph_transitivity_undirected --- Calculates the transitivity + [clustering coefficient] of a graph_::), + ‘igraph_transitivity_avglocal_undirected()’ (*note + igraph_transitivity_avglocal_undirected --- Average local + transitivity [clustering coefficient]_::). + + Time complexity: O(n*d^2), n is the number of vertices for which the +transitivity is calculated, d is the average vertex degree. + + +File: igraph-docs.info, Node: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_, Next: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_, Prev: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_, Up: Transitivity or clustering coefficient + +17.17.3 igraph_transitivity_avglocal_undirected -- Average local transitivity (clustering coefficient). +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_transitivity_avglocal_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); + + The transitivity measures the probability that two neighbors of a +vertex are connected. In case of the average local transitivity, this +probability is calculated for each vertex and then the average is taken. +Vertices with less than two neighbors require special treatment, they +will either be left out from the calculation or they will be considered +as having zero transitivity, depending on the ‘mode’ argument. Edge +directions and edge multiplicities are ignored. + + Note that this measure is different from the global transitivity +measure (see ‘igraph_transitivity_undirected()’ (*note +igraph_transitivity_undirected --- Calculates the transitivity +[clustering coefficient] of a graph_::) ) as it simply takes the average +local transitivity across the whole network. + + Clustering coefficient is an alternative name for transitivity. + + References: + + D. J. Watts and S. Strogatz: Collective dynamics of small-world +networks. Nature 393(6684):440-442 (1998). + + *Arguments:. * + +‘graph’: + The input graph. Edge directions and multiplicites are ignored. + +‘res’: + Pointer to a real variable, the result will be stored here. + +‘mode’: + Defines how to treat vertices with degree less than two. + ‘IGRAPH_TRANSITIVITY_NAN’ leaves them out from averaging, + ‘IGRAPH_TRANSITIVITY_ZERO’ includes them with zero transitivity. + The result will be ‘NaN’ if the mode is ‘IGRAPH_TRANSITIVITY_NAN’ + and there are no vertices with more than one neighbor. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_transitivity_undirected()’ (*note + igraph_transitivity_undirected --- Calculates the transitivity + [clustering coefficient] of a graph_::), + ‘igraph_transitivity_local_undirected()’ (*note + igraph_transitivity_local_undirected --- The local transitivity + [clustering coefficient] of some vertices_::). + + Time complexity: O(|V|*d^2), |V| is the number of vertices in the +graph and d is the average degree. + + +File: igraph-docs.info, Node: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_, Next: igraph_ecc --- Edge clustering coefficient of some edges_, Prev: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_, Up: Transitivity or clustering coefficient + +17.17.4 igraph_transitivity_barrat -- Weighted local transitivity of some vertices, as defined by A. Barrat. +------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_transitivity_barrat(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode); + + This is a local transitivity, i.e. a vertex-level index. For a +given vertex ‘i’, from all triangles in which it participates we +consider the weight of the edges incident on ‘i’. The transitivity is +the sum of these weights divided by twice the strength of the vertex +(see ‘igraph_strength()’ (*note igraph_strength --- Strength of the +vertices; also called weighted vertex degree_::)) and the degree of the +vertex minus one. See equation (5) in Alain Barrat, Marc Barthelemy, +Romualdo Pastor-Satorras, Alessandro Vespignani: The architecture of +complex weighted networks, Proc. Natl. Acad. Sci. USA 101, 3747 +(2004) at https://doi.org/10.1073/pnas.0400087101 +(https://doi.org/10.1073/pnas.0400087101) for the exact formula. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored for directed graphs. + Note that the function does _not_ work for non-simple graphs. + +‘res’: + Pointer to an initialized vector, the result will be stored here. + It will be resized as needed. + +‘vids’: + The vertices for which the calculation is performed. + +‘weights’: + Edge weights. If this is a null pointer, then a warning is given + and ‘igraph_transitivity_local_undirected()’ (*note + igraph_transitivity_local_undirected --- The local transitivity + [clustering coefficient] of some vertices_::) is called. + +‘mode’: + Defines how to treat vertices with zero strength. + ‘IGRAPH_TRANSITIVITY_NAN’ says that the transitivity of these + vertices is ‘NaN’, ‘IGRAPH_TRANSITIVITY_ZERO’ says it is zero. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|*d^2), |V| is the number of vertices in the +graph, d is the average node degree. + + *See also:. * + +‘’ + ‘igraph_transitivity_undirected()’ (*note + igraph_transitivity_undirected --- Calculates the transitivity + [clustering coefficient] of a graph_::), + ‘igraph_transitivity_local_undirected()’ (*note + igraph_transitivity_local_undirected --- The local transitivity + [clustering coefficient] of some vertices_::) and + ‘igraph_transitivity_avglocal_undirected()’ (*note + igraph_transitivity_avglocal_undirected --- Average local + transitivity [clustering coefficient]_::) for other kinds of + (non-weighted) transitivity. + + +File: igraph-docs.info, Node: igraph_ecc --- Edge clustering coefficient of some edges_, Prev: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_, Up: Transitivity or clustering coefficient + +17.17.5 igraph_ecc -- Edge clustering coefficient of some edges. +---------------------------------------------------------------- + + + igraph_error_t igraph_ecc(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t eids, igraph_int_t k, + igraph_bool_t offset, igraph_bool_t normalize); + + The edge clustering coefficient ‘C^(k)_ij’ of an edge (i, j) is +defined based on the number of k-cycles the edge participates in, +‘z^(k)_ij’, and the largest number of such cycles it could participate +in given the degrees of its endpoints, ‘s^(k)_ij’. The original +definition given in the reference below is: + + ‘C^(k)_ij = (z^(k)_ij + 1) / s^(k)_ij’ + + For ‘k=3’, ‘s^(k)_ij = min(d_i - 1, d_j - 1)’, where ‘d_i’ and ‘d_j’ +are the edge endpoint degrees. For ‘k=4’, ‘s^(k)_ij = (d_i - 1) (d_j - +1)’. + + The ‘normalize’ and ‘offset’ parameters allow for skipping +normalization by ‘s^(k)’ and offsetting the cycle count ‘z^(k)’ by one +in the numerator of ‘C^(k)’. Set both to ‘true’ to compute the original +definition of this metric. + + This function ignores edge multiplicities when listing k-cycles (i.e. +‘z^(k)’), but not when computing the maximum number of cycles an edge +can participate in (‘s^(k)’). + + Reference: + + F. Radicchi, C. Castellano, F. Cecconi, V. Loreto, and D. Parisi, +PNAS 101, 2658 (2004). https://doi.org/10.1073/pnas.0400054101 +(https://doi.org/10.1073/pnas.0400054101) + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Initialized vector, the result will be stored here. + +‘eids’: + The edges for which the edge clustering coefficient will be + computed. + +‘k’: + Size of cycles to use in calculation. Must be at least 3. + Currently only values of 3 and 4 are supported. + +‘offset’: + Boolean, whether to add one to cycle counts. When ‘false’, ‘z^(k)’ + is used instead of ‘z^(k) + 1’. In this case the maximum value of + the normalized metric is 1. For ‘k=3’ this is achieved for all + edges in a complete graph. + +‘normalize’: + Boolean, whether to normalize cycle counts by the maximum possible + count ‘s^(k)’ given the degrees. + + *Returns:. * + +‘’ + Error code. + + Time complexity: When ‘k’ is 3, O(|V| d log d + |E| d). When ‘k’ is +4, O(|V| d log d + |E| d^2). d denotes the degree of vertices. + + +File: igraph-docs.info, Node: Directedness conversion, Next: Spectral properties, Prev: Transitivity or clustering coefficient, Up: Structural properties of graphs + +17.18 Directedness conversion +============================= + +* Menu: + +* igraph_to_directed -- Convert an undirected graph to a directed one.: igraph_to_directed --- Convert an undirected graph to a directed one_. +* igraph_to_undirected -- Convert a directed graph to an undirected one.: igraph_to_undirected --- Convert a directed graph to an undirected one_. + + +File: igraph-docs.info, Node: igraph_to_directed --- Convert an undirected graph to a directed one_, Next: igraph_to_undirected --- Convert a directed graph to an undirected one_, Up: Directedness conversion + +17.18.1 igraph_to_directed -- Convert an undirected graph to a directed one. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_to_directed(igraph_t *graph, + igraph_to_directed_t mode); + + If the supplied graph is directed, this function does nothing. + + *Arguments:. * + +‘graph’: + The graph object to convert. + +‘mode’: + Constant, specifies the details of how exactly the conversion is + done. Possible values: + + ‘IGRAPH_TO_DIRECTED_ARBITRARY’ + The number of edges in the graph stays the same, an + arbitrarily directed edge is created for each undirected edge. + + ‘IGRAPH_TO_DIRECTED_MUTUAL’ + Two directed edges are created for each undirected edge, one + in each direction. + + ‘IGRAPH_TO_DIRECTED_RANDOM’ + Each undirected edge is converted to a randomly oriented + directed one. + + ‘IGRAPH_TO_DIRECTED_ACYCLIC’ + Each undirected edge is converted to a directed edge oriented + from a lower index vertex to a higher index one. If no + self-loops were present, then the result is a directed acyclic + graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_to_undirected --- Convert a directed graph to an undirected one_, Prev: igraph_to_directed --- Convert an undirected graph to a directed one_, Up: Directedness conversion + +17.18.2 igraph_to_undirected -- Convert a directed graph to an undirected one. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_to_undirected(igraph_t *graph, + igraph_to_undirected_t mode, + const igraph_attribute_combination_t *edge_comb); + + If the supplied graph is undirected, this function does nothing. + + *Arguments:. * + +‘graph’: + The graph object to convert. + +‘mode’: + Constant, specifies the details of how exactly the conversion is + done. Possible values: ‘IGRAPH_TO_UNDIRECTED_EACH’: the number of + edges remains constant, an undirected edge is created for each + directed one, this version might create graphs with multiple edges; + ‘IGRAPH_TO_UNDIRECTED_COLLAPSE’: one undirected edge will be + created for each pair of vertices that are connected with at least + one directed edge, no multiple edges will be created. + ‘IGRAPH_TO_UNDIRECTED_MUTUAL’ creates an undirected edge for each + pair of mutual edges in the directed graph. Non-mutual edges are + lost; loop edges are kept unconditionally. This mode might create + multiple edges. + +‘edge_comb’: + What to do with the edge attributes. See the igraph manual section + about attributes for details. ‘NULL’ means that the edge + attributes are lost during the conversion, _except_ when ‘mode’ is + ‘IGRAPH_TO_UNDIRECTED_EACH’, in which case the edge attributes are + kept intact. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + * File examples/simple/igraph_to_undirected.c* + + +File: igraph-docs.info, Node: Spectral properties, Next: Non-simple graphs; Multiple and loop edges, Prev: Directedness conversion, Up: Structural properties of graphs + +17.19 Spectral properties +========================= + +* Menu: + +* igraph_get_laplacian -- Returns the Laplacian matrix of a graph.: igraph_get_laplacian --- Returns the Laplacian matrix of a graph_. +* igraph_get_laplacian_sparse -- Returns the Laplacian of a graph in a sparse matrix format.: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_. +* igraph_laplacian_normalization_t -- Normalization methods for a Laplacian matrix.: igraph_laplacian_normalization_t --- Normalization methods for a Laplacian matrix_. + + +File: igraph-docs.info, Node: igraph_get_laplacian --- Returns the Laplacian matrix of a graph_, Next: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_, Up: Spectral properties + +17.19.1 igraph_get_laplacian -- Returns the Laplacian matrix of a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_get_laplacian( + const igraph_t *graph, igraph_matrix_t *res, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights + ); + + The Laplacian matrix ‘L’ of a graph is defined as ‘L_ij = - A_ij’ +when ‘i != j’ and ‘L_ii = d_i - A_ii’. Here ‘A’ denotes the (possibly +weighted) adjacency matrix and ‘d_i’ is the degree (or strength, if +weighted) of vertex ‘i’. In directed graphs, the ‘mode’ parameter +controls whether to use out- or in-degrees. Correspondingly, the rows +or columns will sum to zero. In undirected graphs, ‘A_ii’ is taken to +be _twice_ the number (or total weight) of self-loops, ensuring that +‘d_i = \sum_j A_ij’. Thus, the Laplacian of an undirected graph is the +same as the Laplacian of a directed one obtained by replacing each +undirected edge with two reciprocal directed ones. + + More compactly, ‘L = D - A’ where the ‘D’ is a diagonal matrix +containing the degrees. The Laplacian matrix can also be normalized, +with several conventional normalization methods. See +‘igraph_laplacian_normalization_t’ (*note +igraph_laplacian_normalization_t --- Normalization methods for a +Laplacian matrix_::) for the methods available in igraph. + + The first version of this function was written by Vincent Matossian. + + *Arguments:. * + +‘graph’: + Pointer to the graph to convert. + +‘res’: + Pointer to an initialized matrix object, the result is stored here. + It will be resized if needed. + +‘mode’: + Controls whether to use out- or in-degrees in directed graphs. If + set to ‘IGRAPH_ALL’, edge directions will be ignored. + +‘normalization’: + The normalization method to use when calculating the Laplacian + matrix. See ‘igraph_laplacian_normalization_t’ (*note + igraph_laplacian_normalization_t --- Normalization methods for a + Laplacian matrix_::) for possible values. + +‘weights’: + An optional vector containing non-negative edge weights, to + calculate the weighted Laplacian matrix. Set it to a null pointer + to calculate the unweighted Laplacian. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^2), |V| is the number of vertices in the +graph. + + * File examples/simple/igraph_get_laplacian.c* + + +File: igraph-docs.info, Node: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_, Next: igraph_laplacian_normalization_t --- Normalization methods for a Laplacian matrix_, Prev: igraph_get_laplacian --- Returns the Laplacian matrix of a graph_, Up: Spectral properties + +17.19.2 igraph_get_laplacian_sparse -- Returns the Laplacian of a graph in a sparse matrix format. +-------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_laplacian_sparse( + const igraph_t *graph, igraph_sparsemat_t *sparseres, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights + ); + + See ‘igraph_get_laplacian()’ (*note igraph_get_laplacian --- Returns +the Laplacian matrix of a graph_::) for the definition of the Laplacian +matrix. + + The first version of this function was written by Vincent Matossian. + + *Arguments:. * + +‘graph’: + Pointer to the graph to convert. + +‘sparseres’: + Pointer to an initialized sparse matrix object, the result is + stored here. + +‘mode’: + Controls whether to use out- or in-degrees in directed graphs. If + set to ‘IGRAPH_ALL’, edge directions will be ignored. + +‘normalization’: + The normalization method to use when calculating the Laplacian + matrix. See ‘igraph_laplacian_normalization_t’ (*note + igraph_laplacian_normalization_t --- Normalization methods for a + Laplacian matrix_::) for possible values. + +‘weights’: + An optional vector containing non-negative edge weights, to + calculate the weighted Laplacian matrix. Set it to a null pointer + to calculate the unweighted Laplacian. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), |E| is the number of edges in the graph. + + * File examples/simple/igraph_get_laplacian_sparse.c* + + +File: igraph-docs.info, Node: igraph_laplacian_normalization_t --- Normalization methods for a Laplacian matrix_, Prev: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_, Up: Spectral properties + +17.19.3 igraph_laplacian_normalization_t -- Normalization methods for a Laplacian matrix. +----------------------------------------------------------------------------------------- + + + typedef enum { + IGRAPH_LAPLACIAN_UNNORMALIZED = 0, + IGRAPH_LAPLACIAN_SYMMETRIC = 1, + IGRAPH_LAPLACIAN_LEFT = 2, + IGRAPH_LAPLACIAN_RIGHT = 3 + } igraph_laplacian_normalization_t; + + Normalization methods for ‘igraph_get_laplacian()’ (*note +igraph_get_laplacian --- Returns the Laplacian matrix of a graph_::) and +‘igraph_get_laplacian_sparse()’ (*note igraph_get_laplacian_sparse --- +Returns the Laplacian of a graph in a sparse matrix format_::). In the +following, ‘A’ refers to the (possibly weighted) adjacency matrix and +‘D’ is a diagonal matrix containing degrees (unweighted case) or +strengths (weighted case). Out-, in- or total degrees are used +according to the ‘mode’ parameter. + + *Values:. * + +‘IGRAPH_LAPLACIAN_UNNORMALIZED’: + Unnormalized Laplacian, ‘L = D - A’. + +‘IGRAPH_LAPLACIAN_SYMMETRIC’: + Symmetrically normalized Laplacian, ‘L = I - D^(-1/2) A D^(-1/2)’. + +‘IGRAPH_LAPLACIAN_LEFT’: + Left-stochastic normalized Laplacian, ‘L = I - D^-1 A’. + +‘IGRAPH_LAPLACIAN_RIGHT’: + Right-stochastic normalized Laplacian, ‘L = I - A D^-1’. + + +File: igraph-docs.info, Node: Non-simple graphs; Multiple and loop edges, Next: Mixing patterns and degree correlations, Prev: Spectral properties, Up: Structural properties of graphs + +17.20 Non-simple graphs: Multiple and loop edges +================================================ + +* Menu: + +* igraph_is_simple -- Decides whether the input graph is a simple graph.: igraph_is_simple --- Decides whether the input graph is a simple graph_. +* igraph_is_loop -- Find the loop edges in a graph.: igraph_is_loop --- Find the loop edges in a graph_. +* igraph_has_loop -- Returns whether the graph has at least one loop edge.: igraph_has_loop --- Returns whether the graph has at least one loop edge_. +* igraph_count_loops -- Counts the self-loops in the graph.: igraph_count_loops --- Counts the self-loops in the graph_. +* igraph_is_multiple -- Find the multiple edges in a graph.: igraph_is_multiple --- Find the multiple edges in a graph_. +* igraph_has_multiple -- Check whether the graph has at least one multiple edge.: igraph_has_multiple --- Check whether the graph has at least one multiple edge_. +* igraph_count_multiple -- The multiplicity of some edges in a graph.: igraph_count_multiple --- The multiplicity of some edges in a graph_. +* igraph_count_multiple_1 -- The multiplicity of a single edge in a graph.: igraph_count_multiple_1 --- The multiplicity of a single edge in a graph_. + + +File: igraph-docs.info, Node: igraph_is_simple --- Decides whether the input graph is a simple graph_, Next: igraph_is_loop --- Find the loop edges in a graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.1 igraph_is_simple -- Decides whether the input graph is a simple graph. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_is_simple(const igraph_t *graph, igraph_bool_t *res, igraph_bool_t directed); + + A graph is a simple graph if it does not contain loop edges and +multiple edges. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean constant, the result is stored here. + +‘directed’: + Whether to consider the directions of edges. ‘IGRAPH_UNDIRECTED’ + means that edge directions will be ignored and a directed graph + with at least one mutual edge pair will be considered non-simple. + ‘IGRAPH_DIRECTED’ means that edge directions will be considered. + Ignored for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_loop()’ (*note igraph_is_loop --- Find the loop edges in + a graph_::) and ‘igraph_is_multiple()’ (*note igraph_is_multiple + --- Find the multiple edges in a graph_::) to find the loops and + multiple edges, ‘igraph_simplify()’ (*note igraph_simplify --- + Removes loop and/or multiple edges from the graph_::) to get rid of + them, or ‘igraph_has_multiple()’ (*note igraph_has_multiple --- + Check whether the graph has at least one multiple edge_::) to + decide whether there is at least one multiple edge. + + Time complexity: O(|V|+|E|). + + +File: igraph-docs.info, Node: igraph_is_loop --- Find the loop edges in a graph_, Next: igraph_has_loop --- Returns whether the graph has at least one loop edge_, Prev: igraph_is_simple --- Decides whether the input graph is a simple graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.2 igraph_is_loop -- Find the loop edges in a graph. +--------------------------------------------------------- + + + igraph_error_t igraph_is_loop(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); + + A loop edge, also called a self-loop, is an edge from a vertex to +itself. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized boolean vector for storing the result, it + will be resized as needed. + +‘es’: + The edges to check, for all edges supply ‘igraph_ess_all()’ (*note + igraph_ess_all --- Edge set; all edges [immediate version]_::) + here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or + multiple edges from the graph_::) to get rid of loop edges. + + Time complexity: O(e), the number of edges to check. + + * File examples/simple/igraph_is_loop.c* + + +File: igraph-docs.info, Node: igraph_has_loop --- Returns whether the graph has at least one loop edge_, Next: igraph_count_loops --- Counts the self-loops in the graph_, Prev: igraph_is_loop --- Find the loop edges in a graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.3 igraph_has_loop -- Returns whether the graph has at least one loop edge. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_has_loop(const igraph_t *graph, igraph_bool_t *res); + + A loop edge is an edge from a vertex to itself. + + The return value of this function is cached in the graph itself; +calling the function multiple times with no modifications to the graph +in between will return a cached value in O(1) time. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized boolean vector for storing the result. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or + multiple edges from the graph_::) to get rid of loop edges. + + Time complexity: O(e), the number of edges to check. + + * File examples/simple/igraph_is_loop.c* + + +File: igraph-docs.info, Node: igraph_count_loops --- Counts the self-loops in the graph_, Next: igraph_is_multiple --- Find the multiple edges in a graph_, Prev: igraph_has_loop --- Returns whether the graph has at least one loop edge_, Up: Non-simple graphs; Multiple and loop edges + +17.20.4 igraph_count_loops -- Counts the self-loops in the graph. +----------------------------------------------------------------- + + + igraph_error_t igraph_count_loops(const igraph_t *graph, igraph_int_t *loop_count); + + Counts loop edges, i.e. edges whose two endpoints coincide. + + *Arguments:. * + +‘graph’: + The input graph. + +‘loop_count’: + Pointer to an integer, the number of self-loops will be stored + here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), linear in the number of edges. + + * File examples/simple/igraph_is_loop.c* + + +File: igraph-docs.info, Node: igraph_is_multiple --- Find the multiple edges in a graph_, Next: igraph_has_multiple --- Check whether the graph has at least one multiple edge_, Prev: igraph_count_loops --- Counts the self-loops in the graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.5 igraph_is_multiple -- Find the multiple edges in a graph. +----------------------------------------------------------------- + + + igraph_error_t igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); + + An edge is a multiple edge if there is another edge with the same +head and tail vertices in the graph. + + Note that this function returns true only for the second or more +appearances of the multiple edges. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean vector, the result will be stored here. It + will be resized as needed. + +‘es’: + The edges to check. Supply ‘igraph_ess_all()’ (*note + igraph_ess_all --- Edge set; all edges [immediate version]_::) if + you want to check all edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_count_multiple()’ (*note igraph_count_multiple --- The + multiplicity of some edges in a graph_::), + ‘igraph_count_multiple_1()’ (*note igraph_count_multiple_1 --- The + multiplicity of a single edge in a graph_::), + ‘igraph_has_multiple()’ (*note igraph_has_multiple --- Check + whether the graph has at least one multiple edge_::) and + ‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or + multiple edges from the graph_::). + + Time complexity: O(e*d), e is the number of edges to check and d is +the average degree (out-degree in directed graphs) of the vertices at +the tail of the edges. + + * File examples/simple/igraph_is_multiple.c* + + +File: igraph-docs.info, Node: igraph_has_multiple --- Check whether the graph has at least one multiple edge_, Next: igraph_count_multiple --- The multiplicity of some edges in a graph_, Prev: igraph_is_multiple --- Find the multiple edges in a graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.6 igraph_has_multiple -- Check whether the graph has at least one multiple edge. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res); + + An edge is a multiple edge if there is another edge with the same +head and tail vertices in the graph. + + The return value of this function is cached in the graph itself; +calling the function multiple times with no modifications to the graph +in between will return a cached value in O(1) time. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean variable, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_count_multiple()’ (*note igraph_count_multiple --- The + multiplicity of some edges in a graph_::), ‘igraph_is_multiple()’ + (*note igraph_is_multiple --- Find the multiple edges in a + graph_::) and ‘igraph_simplify()’ (*note igraph_simplify --- + Removes loop and/or multiple edges from the graph_::). + + Time complexity: O(e*d), e is the number of edges to check and d is +the average degree (out-degree in directed graphs) of the vertices at +the tail of the edges. + + * File examples/simple/igraph_has_multiple.c* + + +File: igraph-docs.info, Node: igraph_count_multiple --- The multiplicity of some edges in a graph_, Next: igraph_count_multiple_1 --- The multiplicity of a single edge in a graph_, Prev: igraph_has_multiple --- Check whether the graph has at least one multiple edge_, Up: Non-simple graphs; Multiple and loop edges + +17.20.7 igraph_count_multiple -- The multiplicity of some edges in a graph. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_count_multiple(const igraph_t *graph, igraph_vector_int_t *res, + igraph_es_t es); + + An edge is called a multiple edge when there is one or more other +edge between the same two vertices. The multiplicity of an edge is the +number of edges between its endpoints. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a vector, the result will be stored here. It will be + resized as needed. + +‘es’: + The edges to check. Supply ‘igraph_ess_all()’ (*note + igraph_ess_all --- Edge set; all edges [immediate version]_::) if + you want to check all edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_count_multiple_1()’ (*note igraph_count_multiple_1 --- The + multiplicity of a single edge in a graph_::) if you only need the + multiplicity of a single edge; ‘igraph_is_multiple()’ (*note + igraph_is_multiple --- Find the multiple edges in a graph_::) if + you are only interested in whether the graph has at least one edge + with multiplicity greater than one; ‘igraph_simplify()’ (*note + igraph_simplify --- Removes loop and/or multiple edges from the + graph_::) to ensure that the graph has no multiple edges. + + Time complexity: O(E d), E is the number of edges to check and d is +the average degree (out-degree in directed graphs) of the vertices at +the tail of the edges. + + +File: igraph-docs.info, Node: igraph_count_multiple_1 --- The multiplicity of a single edge in a graph_, Prev: igraph_count_multiple --- The multiplicity of some edges in a graph_, Up: Non-simple graphs; Multiple and loop edges + +17.20.8 igraph_count_multiple_1 -- The multiplicity of a single edge in a graph. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_count_multiple_1(const igraph_t *graph, igraph_int_t *res, + igraph_int_t eid); + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an iteger, the result will be stored here. + +‘eid’: + The ID of the edge to check. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_count_multiple()’ (*note igraph_count_multiple --- The + multiplicity of some edges in a graph_::) if you need the + multiplicity of multiple edges; ‘igraph_is_multiple()’ (*note + igraph_is_multiple --- Find the multiple edges in a graph_::) if + you are only interested in whether the graph has at least one edge + with multiplicity greater than one; ‘igraph_simplify()’ (*note + igraph_simplify --- Removes loop and/or multiple edges from the + graph_::) to ensure that the graph has no multiple edges. + + Time complexity: O(d), where d is the out-degree of the tail of the +edge. + + +File: igraph-docs.info, Node: Mixing patterns and degree correlations, Next: K-cores and k-trusses, Prev: Non-simple graphs; Multiple and loop edges, Up: Structural properties of graphs + +17.21 Mixing patterns and degree correlations +============================================= + +* Menu: + +* igraph_assortativity_nominal -- Assortativity of a graph based on vertex categories.: igraph_assortativity_nominal --- Assortativity of a graph based on vertex categories_. +* igraph_assortativity -- Assortativity based on numeric properties of vertices.: igraph_assortativity --- Assortativity based on numeric properties of vertices_. +* igraph_assortativity_degree -- Assortativity of a graph based on vertex degree.: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_. +* igraph_avg_nearest_neighbor_degree -- Average neighbor degree.: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_. +* igraph_degree_correlation_vector -- Degree correlation function.: igraph_degree_correlation_vector --- Degree correlation function_. +* igraph_joint_type_distribution -- Mixing matrix for vertex categories.: igraph_joint_type_distribution --- Mixing matrix for vertex categories_. +* igraph_joint_degree_distribution -- The joint degree distribution of a graph.: igraph_joint_degree_distribution --- The joint degree distribution of a graph_. +* igraph_joint_degree_matrix -- The joint degree matrix of a graph.: igraph_joint_degree_matrix --- The joint degree matrix of a graph_. +* igraph_rich_club_sequence -- Density sequence of subgraphs formed by sequential vertex removal.: igraph_rich_club_sequence --- Density sequence of subgraphs formed by sequential vertex removal_. + + +File: igraph-docs.info, Node: igraph_assortativity_nominal --- Assortativity of a graph based on vertex categories_, Next: igraph_assortativity --- Assortativity based on numeric properties of vertices_, Up: Mixing patterns and degree correlations + +17.21.1 igraph_assortativity_nominal -- Assortativity of a graph based on vertex categories. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_assortativity_nominal( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_int_t *types, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized); + + Assuming the vertices of the input graph belong to different +categories, this function calculates the assortativity coefficient of +the graph. The assortativity coefficient is between minus one and one +and it is one if all connections stay within categories, it is minus +one, if the network is perfectly disassortative. For a randomly +connected network it is (asymptotically) zero. + + The unnormalized version, computed when ‘normalized’ is set to false, +is identical to the modularity, and is defined as follows for directed +networks: + + ‘1/m sum_ij (A_ij - k^out_i k^in_j / m) d(i,j)’, + + where ‘m’ denotes the number of edges, ‘A_ij’ is the adjacency +matrix, ‘k^out’ and ‘k^in’ are the out- and in-degrees, and ‘d(i,j)’ is +one if vertices ‘i’ and ‘j’ are in the same category and zero otherwise. + + The normalized assortativity coefficient is obtained by dividing the +previous expression by + + ‘1/m sum_ij (m - k^out_i k^in_j d(i,j) / m)’. + + It can take any value within the interval [-1, 1]. + + Undirected graphs are effectively treated as directed ones with +all-reciprocal edges. Thus, self-loops are taken into account twice in +undirected graphs. + + References: + + M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, +026126 (2003) https://doi.org/10.1103/PhysRevE.67.026126 +(https://doi.org/10.1103/PhysRevE.67.026126). See section II and +equation (2) for the definition of the concept. + + For an educational overview of assortativity, see M. E. J. Newman, +Networks: An Introduction, Oxford University Press (2010). +https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001 +(https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘weights’: + Weighted nominal assortativity is not currently implemented. Pass + ‘NULL’ to ignore. + +‘types’: + Integer vector giving the vertex categories. The types are + represented by integers starting at zero. + +‘res’: + Pointer to a real variable, the result is stored here. + +‘directed’: + Boolean, it gives whether to consider edge directions in a directed + graph. It is ignored for undirected graphs. + +‘normalized’: + Boolean, whether to compute the usual normalized assortativity. + The unnormalized version is identical to modularity. Supply true + here to compute the standard assortativity. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|+t), |E| is the number of edges, t is the +number of vertex types. + + *See also:. * + +‘’ + ‘igraph_assortativity()’ (*note igraph_assortativity --- + Assortativity based on numeric properties of vertices_::) for + computing the assortativity based on continuous vertex values + instead of discrete categories. ‘igraph_modularity()’ (*note + igraph_modularity --- Calculates the modularity of a graph with + respect to some clusters or vertex types_::) to compute generalized + modularity. ‘igraph_joint_type_distribution()’ (*note + igraph_joint_type_distribution --- Mixing matrix for vertex + categories_::) to obtain the mixing matrix. + + * File examples/simple/igraph_assortativity_nominal.c* + + +File: igraph-docs.info, Node: igraph_assortativity --- Assortativity based on numeric properties of vertices_, Next: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_, Prev: igraph_assortativity_nominal --- Assortativity of a graph based on vertex categories_, Up: Mixing patterns and degree correlations + +17.21.2 igraph_assortativity -- Assortativity based on numeric properties of vertices. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_assortativity( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_t *values, const igraph_vector_t *values_in, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized); + + This function calculates the assortativity coefficient of a graph +based on given values ‘x_i’ for each vertex ‘i’. This type of +assortativity coefficient equals the Pearson correlation of the values +at the two ends of the edges. + + The unnormalized covariance of values, computed when ‘normalized’ is +set to false, is defined as follows in a directed graph: + + ‘cov(x_out, x_in) = 1/m sum_ij (A_ij - k^out_i k^in_j / m) x_i x_j’, + + where ‘m’ denotes the number of edges, ‘A_ij’ is the adjacency +matrix, and ‘k^out’ and ‘k^in’ are the out- and in-degrees. ‘x_out’ and +‘x_in’ refer to the sets of vertex values at the start and end of the +directed edges. + + The normalized covariance, i.e. Pearson correlation, is obtained by +dividing the previous expression by ‘sqrt(var(x_out)) sqrt(var(x_in))’, +where + + ‘var(x_out) = 1/m sum_i k^out_i x_i^2 - (1/m sum_i k^out_i x_i^2)^2’ + + ‘var(x_in) = 1/m sum_j k^in_j x_j^2 - (1/m sum_j k^in_j x_j^2)^2’ + + Undirected graphs are effectively treated as directed graphs where +all edges are reciprocal. Therefore, self-loops are effectively +considered twice in undirected graphs. + + When edge weights are given, they are effectively treated as edge +multiplicities. The above formulas are valid for weighted graph as well +when ‘m’ is interpreted as the total edge weight (instead of the edge +count) and ‘k’ as vertex strengths (instead of degrees). + + References: + + M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, +026126 (2003) https://doi.org/10.1103/PhysRevE.67.026126 +(https://doi.org/10.1103/PhysRevE.67.026126). See section III and +equation (21) for the definition, and equation (26) for performing the +calculation in directed graphs with the degrees as values. + + M. E. J. Newman: Assortative mixing in networks, Phys. Rev. Lett. +89, 208701 (2002) https://doi.org/10.1103/PhysRevLett.89.208701 +(https://doi.org/10.1103/PhysRevLett.89.208701). See equation (4) for +performing the calculation in undirected graphs with the degrees as +values. + + For an educational overview of the concept of assortativity, see M. +E. J. Newman, Networks: An Introduction, Oxford University Press (2010). +https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001 +(https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001). + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘weights’: + The edge weights. Pass ‘NULL’ to compute unweighed assortativity, + which in effect assumes all weights to be 1. + +‘values’: + The vertex values, these can be arbitrary numeric values. + +‘values_in’: + A second value vector to be used for the incoming edges when + calculating assortativity for a directed graph. Supply ‘NULL’ here + if you want to use the same values for outgoing and incoming edges. + This argument is ignored (with a warning) if it is not a null + pointer and the undirected assortativity coefficient is being + calculated. + +‘res’: + Pointer to a real variable, the result is stored here. + +‘directed’: + Boolean, whether to consider edge directions for directed graphs. + It is ignored for undirected graphs. + +‘normalized’: + Boolean, whether to compute the normalized covariance, i.e. + Pearson correlation. Supply true here to compute the standard + assortativity. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), linear in the number of edges of the graph. + + *See also:. * + +‘’ + ‘igraph_assortativity_nominal()’ (*note + igraph_assortativity_nominal --- Assortativity of a graph based on + vertex categories_::) if you have discrete vertex categories + instead of numeric labels, and ‘igraph_assortativity_degree()’ + (*note igraph_assortativity_degree --- Assortativity of a graph + based on vertex degree_::) for the special case of assortativity + based on vertex degrees. + + +File: igraph-docs.info, Node: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_, Next: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_, Prev: igraph_assortativity --- Assortativity based on numeric properties of vertices_, Up: Mixing patterns and degree correlations + +17.21.3 igraph_assortativity_degree -- Assortativity of a graph based on vertex degree. +--------------------------------------------------------------------------------------- + + + igraph_error_t igraph_assortativity_degree(const igraph_t *graph, + igraph_real_t *res, + igraph_bool_t directed); + + Assortativity based on vertex degree, please see the discussion at +the documentation of ‘igraph_assortativity()’ (*note +igraph_assortativity --- Assortativity based on numeric properties of +vertices_::) for details. This function simply calls +‘igraph_assortativity()’ (*note igraph_assortativity --- Assortativity +based on numeric properties of vertices_::) with the degrees as the +vertex values and normalization enabled. In the directed case, it uses +out-degrees as out-values and in-degrees as in-values. + + For regular graphs, i.e. graphs in which all vertices have the same +degree, computing degree correlations is not meaningful, and this +function returns NaN. + + *Arguments:. * + +‘graph’: + The input graph, it can be directed or undirected. + +‘res’: + Pointer to a real variable, the result is stored here. + +‘directed’: + Boolean, whether to consider edge directions for directed graphs. + This argument is ignored for undirected graphs. Supply true here + to do the natural thing, i.e. use directed version of the measure + for directed graphs and the undirected version for undirected + graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|+|V|), |E| is the number of edges, |V| is the +number of vertices. + + *See also:. * + +‘’ + ‘igraph_assortativity()’ (*note igraph_assortativity --- + Assortativity based on numeric properties of vertices_::) for the + general function calculating assortativity for any kind of numeric + vertex values, and ‘igraph_joint_degree_distribution()’ (*note + igraph_joint_degree_distribution --- The joint degree distribution + of a graph_::) to get the complete joint degree distribution. + + * File examples/simple/igraph_assortativity_degree.c* + + +File: igraph-docs.info, Node: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_, Next: igraph_degree_correlation_vector --- Degree correlation function_, Prev: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_, Up: Mixing patterns and degree correlations + +17.21.4 igraph_avg_nearest_neighbor_degree -- Average neighbor degree. +---------------------------------------------------------------------- + + + igraph_error_t igraph_avg_nearest_neighbor_degree(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights); + + Calculates the average degree of the neighbors for each vertex +(‘knn’), and optionally, the same quantity as a function of the vertex +degree (‘knnk’). + + For isolated vertices ‘knn’ is set to NaN. The same is done in ‘knnk’ +for vertex degrees that don't appear in the graph. + + The weighted version computes a weighted average of the neighbor +degrees as + + ‘k_nn_u = 1/s_u sum_v w_uv k_v’, + + where ‘s_u = sum_v w_uv’ is the sum of the incident edge weights of +vertex ‘u’, i.e. its strength. The sum runs over the neighbors ‘v’ of +vertex ‘u’ as indicated by ‘mode’. ‘w_uv’ denotes the weighted +adjacency matrix and ‘k_v’ is the neighbors' degree, specified by +‘neighbor_degree_mode’. This is equation (6) in the reference below. + + When only the ‘k_nn(k)’ degree correlation function is needed, +‘igraph_degree_correlation_vector()’ (*note +igraph_degree_correlation_vector --- Degree correlation function_::) can +be used as well. This function provides more flexible control over how +degree at each end of directed edges are computed. + + Reference: + + A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, The +architecture of complex weighted networks, Proc. Natl. Acad. Sci. +USA 101, 3747 (2004). https://dx.doi.org/10.1073/pnas.0400087101 +(https://dx.doi.org/10.1073/pnas.0400087101) + + *Arguments:. * + +‘graph’: + The input graph. It may be directed. + +‘vids’: + The vertices for which the calculation is performed. + +‘mode’: + The type of neighbors to consider in directed graphs. ‘IGRAPH_OUT’ + considers out-neighbors, ‘IGRAPH_IN’ in-neighbors and ‘IGRAPH_ALL’ + ignores edge directions. + +‘neighbor_degree_mode’: + The type of degree to average in directed graphs. ‘IGRAPH_OUT’ + averages out-degrees, ‘IGRAPH_IN’ averages in-degrees and + ‘IGRAPH_ALL’ ignores edge directions for the degree calculation. + +‘knn’: + Pointer to an initialized vector, the result will be stored here. + It will be resized as needed. Supply a ‘NULL’ pointer here if you + only want to calculate ‘knnk’. + +‘knnk’: + Pointer to an initialized vector, the average neighbor degree as a + function of the vertex degree is stored here. This is sometimes + referred to as the ‘k_nn(k)’ degree correlation function. The + first (zeroth) element is for degree one vertices, etc. The + calculation is done based only on the vertices ‘vids’. Supply a + ‘NULL’ pointer here if you don't want to calculate this. + +‘weights’: + Optional edge weights. Supply a null pointer here for the + non-weighted version. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_degree_correlation_vector()’ (*note + igraph_degree_correlation_vector --- Degree correlation + function_::) for computing only the degree correlation function, + with more flexible control over degree computations. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + * File examples/simple/igraph_avg_nearest_neighbor_degree.c* + + +File: igraph-docs.info, Node: igraph_degree_correlation_vector --- Degree correlation function_, Next: igraph_joint_type_distribution --- Mixing matrix for vertex categories_, Prev: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_, Up: Mixing patterns and degree correlations + +17.21.5 igraph_degree_correlation_vector -- Degree correlation function. +------------------------------------------------------------------------ + + + igraph_error_t igraph_degree_correlation_vector( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *knnk, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors); + + Computes the degree correlation function ‘k_nn(k)’, defined as the +mean degree of the targets of directed edges whose source has degree +‘k’. The averaging is done over all directed edges. The ‘from_mode’ +and ‘to_mode’ parameters control how the source and target vertex +degrees are computed. This way the out-in, out-out, in-in and in-out +degree correlation functions can all be computed. + + In undirected graphs, edges are treated as if they were a pair of +reciprocal directed ones. + + If P_ij is the joint degree distribution of the graph, computable +with ‘igraph_joint_degree_distribution()’ (*note +igraph_joint_degree_distribution --- The joint degree distribution of a +graph_::), then ‘k_nn(k) = (sum_j j P_kj) / (sum_j P_kj)’. + + The function ‘igraph_avg_nearest_neighbor_degree()’ (*note +igraph_avg_nearest_neighbor_degree --- Average neighbor degree_::), +whose main purpose is to calculate the average neighbor degree for each +vertex separately, can also compute ‘k_nn(k)’. It differs from this +function in that it can take a subset of vertices to base the +calculation on, but it does not allow the same fine-grained control over +how degrees are computed. + + References: + + R. Pastor-Satorras, A. Vazquez, A. Vespignani: Dynamical and +Correlation Properties of the Internet, Phys. Rev. Lett., vol. 87, +pp. 258701 (2001). https://doi.org/10.1103/PhysRevLett.87.258701 +(https://doi.org/10.1103/PhysRevLett.87.258701) + + A. Vazquez, R. Pastor-Satorras, A. Vespignani: Large-scale +topological and dynamical properties of the Internet, Phys. Rev. E, +vol. 65, pp. 066130 (2002). +https://doi.org/10.1103/PhysRevE.65.066130 +(https://doi.org/10.1103/PhysRevE.65.066130) + + A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, The +architecture of complex weighted networks, Proc. Natl. Acad. Sci. +USA 101, 3747 (2004). https://dx.doi.org/10.1073/pnas.0400087101 +(https://dx.doi.org/10.1073/pnas.0400087101) + + *Arguments:. * + +‘graph’: + The input graph. + +‘weights’: + An optional weight vector. If not ‘NULL’, weighted averages will + be computed. + +‘knnk’: + An initialized vector, the result will be written here. ‘knnk[d]’ + will contain the mean degree of vertices connected to by vertices + of degree ‘d’. Note that in contrast to + ‘igraph_avg_nearest_neighbor_degree()’ (*note + igraph_avg_nearest_neighbor_degree --- Average neighbor degree_::), + ‘d=0’ is also included. + +‘from_mode’: + How to compute the degree of sources? Can be ‘IGRAPH_OUT’ for + out-degree, ‘IGRAPH_IN’ for in-degree, or ‘IGRAPH_ALL’ for total + degree. Ignored in undirected graphs. + +‘to_mode’: + How to compute the degree of sources? Can be ‘IGRAPH_OUT’ for + out-degree, ‘IGRAPH_IN’ for in-degree, or ‘IGRAPH_ALL’ for total + degree. Ignored in undirected graphs. + +‘directed_neighbors’: + Whether to consider ‘u -> v’ connections to be directed. + Undirected connections are treated as reciprocal directed ones, + i.e. both ‘u -> v’ and ‘v -> u’ will be considered. Ignored in + undirected graphs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_avg_nearest_neighbor_degree()’ (*note + igraph_avg_nearest_neighbor_degree --- Average neighbor degree_::) + for computing the average neighbour degree of a set of vertices, + ‘igraph_joint_degree_distribution()’ (*note + igraph_joint_degree_distribution --- The joint degree distribution + of a graph_::) to get the complete joint degree distribution, and + ‘igraph_assortativity_degree()’ (*note igraph_assortativity_degree + --- Assortativity of a graph based on vertex degree_::) to compute + the degree assortativity. + + Time complexity: O(|E| + |V|) + + +File: igraph-docs.info, Node: igraph_joint_type_distribution --- Mixing matrix for vertex categories_, Next: igraph_joint_degree_distribution --- The joint degree distribution of a graph_, Prev: igraph_degree_correlation_vector --- Degree correlation function_, Up: Mixing patterns and degree correlations + +17.21.6 igraph_joint_type_distribution -- Mixing matrix for vertex categories. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_joint_type_distribution( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, + igraph_bool_t directed, igraph_bool_t normalized); + + Computes the mixing matrix M_ij, i.e. the joint distribution of +vertex types at the endpoints directed of edges. Categories are +represented by non-negative integer indices, passed in ‘from_types’ and +‘to_types’. The row and column counts of ‘m’ will be one larger than +the largest source and target type, respectively. Re-index type vectors +using ‘igraph_reindex_membership()’ (*note igraph_reindex_membership --- +Makes the IDs in a membership vector contiguous_::) if they are not +contiguous integers, to avoid producing a very large matrix. + + M_ij is proportional to the probability that a randomly chosen +ordered pair of vertices have types ‘i’ and ‘j’. + + When there is a single categorization of vertices, i.e. ‘from_types’ +and ‘to_types’ are the same, M_ij is related to the modularity +(‘igraph_modularity()’ (*note igraph_modularity --- Calculates the +modularity of a graph with respect to some clusters or vertex types_::)) +and nominal assortativity (‘igraph_assortativity_nominal()’ (*note +igraph_assortativity_nominal --- Assortativity of a graph based on +vertex categories_::)). Let ‘a_i = sum_j M_ij’ and ‘b_j = sum_i M_ij’. +If M_ij is normalized, i.e. ‘sum_ij M_ij = 1’, and the types represent +membership in vertex partitions, then the modularity of the partitioning +can be computed as + + ‘Q = sum_ii M_ii - sum_i a_i b_i’ + + The normalized nominal assortativity is + + ‘Q / (1 - sum_i a_i b_i)’ + + ‘igraph_joint_degree_distribution()’ (*note +igraph_joint_degree_distribution --- The joint degree distribution of a +graph_::) is a special case of this function, with categories consisting +vertices of the same degree. + + References: + + M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, +026126 (2003) https://doi.org/10.1103/PhysRevE.67.026126 +(https://doi.org/10.1103/PhysRevE.67.026126). + + *Arguments:. * + +‘graph’: + The input graph. + +‘weights’: + A vector containing the weights of the edges. If passing a ‘NULL’ + pointer, edges will be assumed to have unit weights. + +‘p’: + The mixing matrix ‘M_ij’ will be stored here. + +‘from_types’: + Vertex types for source vertices. These must be non-negative + integers. + +‘to_types’: + Vertex types for target vertices. These must be non-negative + integers. If ‘NULL’, it is assumed to be the same as ‘from_types’. + +‘directed’: + Whether to treat edges are directed. Ignored for undirected + graphs. + +‘normalized’: + Whether to normalize the matrix so that entries sum to 1.0. If + false, matrix entries will be connection counts. Normalization is + not meaningful if some edge weights are negative. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_joint_degree_distribution()’ (*note + igraph_joint_degree_distribution --- The joint degree distribution + of a graph_::) to compute the joint distribution of vertex degrees; + ‘igraph_modularity()’ (*note igraph_modularity --- Calculates the + modularity of a graph with respect to some clusters or vertex + types_::) to compute the modularity of a vertex partitioning; + ‘igraph_assortativity_nominal()’ (*note + igraph_assortativity_nominal --- Assortativity of a graph based on + vertex categories_::) to compute assortativity based on vertex + categories. + + Time complexity: O(E), where E is the number of edges in the input +graph. + + +File: igraph-docs.info, Node: igraph_joint_degree_distribution --- The joint degree distribution of a graph_, Next: igraph_joint_degree_matrix --- The joint degree matrix of a graph_, Prev: igraph_joint_type_distribution --- Mixing matrix for vertex categories_, Up: Mixing patterns and degree correlations + +17.21.7 igraph_joint_degree_distribution -- The joint degree distribution of a graph. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_joint_degree_distribution( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors, + igraph_bool_t normalized, + igraph_int_t max_from_degree, igraph_int_t max_to_degree); + + Computes the joint degree distribution ‘P_ij’ of a graph, used in the +study of degree correlations. ‘P_ij’ is the probability that a randomly +chosen ordered pair of _connected_ vertices have degrees ‘i’ and ‘j’. + + In directed graphs, directionally connected ‘u -> v’ pairs are +considered. The joint degree distribution of an undirected graph is the +same as that of the corresponding directed graph in which all connection +are bidirectional, assuming that ‘from_mode’ is ‘IGRAPH_OUT’, ‘to_mode’ +is ‘IGRAPH_IN’ and ‘directed_neighbors’ is true. + + When ‘normalized’ is false, ‘sum_ij P_ij’ gives the total number of +connections in a directed graph, or twice that value in an undirected +graph. The sum is taken over ordered ‘(i,j)’ degree pairs. + + The joint degree distribution relates to other concepts used in the +study of degree correlations. If ‘P_ij’ is normalized then the degree +correlation function ‘k_nn(k)’ is obtained as + + ‘k_nn(k) = (sum_j j P_kj) / (sum_j P_kj)’. + + The non-normalized degree assortativity is obtained as + + ‘a = sum_ij i j (P_ij - q_i r_j)’, + + where ‘q_i = sum_k P_ik’ and ‘r_j = sum_k P_kj’. + + Note that the joint degree distribution ‘P_ij’ is similar, but not +identical to the joint degree matrix ‘J_ij’ computed by +‘igraph_joint_degree_matrix()’ (*note igraph_joint_degree_matrix --- The +joint degree matrix of a graph_::). If the graph is undirected, then +the diagonal entries of an unnormalized ‘P_ij’ are double that of +‘J_ij’, as any undirected connection between same-degree vertices is +counted in both directions. In contrast to +‘igraph_joint_degree_matrix()’ (*note igraph_joint_degree_matrix --- The +joint degree matrix of a graph_::), this function returns matrices which +include the row and column corresponding to zero degrees. In directed +graphs, this row and column is not necessarily zero when ‘from_mode’ is +different from ‘IGRAPH_OUT’ or ‘to_mode’ is different from ‘IGRAPH_IN’. + + References: + + M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, +026126 (2003) https://doi.org/10.1103/PhysRevE.67.026126 +(https://doi.org/10.1103/PhysRevE.67.026126). + + *Arguments:. * + +‘graph’: + A pointer to an initialized graph object. + +‘weights’: + A vector containing the weights of the edges. If passing a ‘NULL’ + pointer, edges will be assumed to have unit weights. + +‘p’: + A pointer to an initialized matrix that will be resized. The + ‘P_ij’ value will be written into ‘p[i,j]’. + +‘from_mode’: + How to compute the degree of sources? Can be ‘IGRAPH_OUT’ for + out-degree, ‘IGRAPH_IN’ for in-degree, or ‘IGRAPH_ALL’ for total + degree. Ignored in undirected graphs. + +‘to_mode’: + How to compute the degree of targets? Can be ‘IGRAPH_OUT’ for + out-degree, ‘IGRAPH_IN’ for in-degree, or ‘IGRAPH_ALL’ for total + degree. Ignored in undirected graphs. + +‘directed_neighbors’: + Whether to consider ‘u -> v’ connections to be directed. + Undirected connections are treated as reciprocal directed ones, + i.e. both ‘u -> v’ and ‘v -> u’ will be considered. Ignored in + undirected graphs. + +‘normalized’: + Whether to normalize the matrix so that entries sum to 1.0. If + false, matrix entries will be connection counts. Normalization is + not meaningful if some edge weights are negative. + +‘max_from_degree’: + The largest source vertex degree to consider. If negative or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), the largest source degree will be used. The + row count of the result matrix is one larger than this value. + +‘max_to_degree’: + The largest target vertex degree to consider. If negative or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), the largest target degree will be used. The + column count of the result matrix is one larger than this value. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_joint_degree_matrix()’ (*note igraph_joint_degree_matrix + --- The joint degree matrix of a graph_::) for computing the joint + degree matrix; ‘igraph_assortativity_degree()’ (*note + igraph_assortativity_degree --- Assortativity of a graph based on + vertex degree_::) and ‘igraph_assortativity()’ (*note + igraph_assortativity --- Assortativity based on numeric properties + of vertices_::) for degree correlations coefficients, and + ‘igraph_degree_correlation_vector()’ (*note + igraph_degree_correlation_vector --- Degree correlation + function_::) for the degree correlation function. + + Time complexity: O(E), where E is the number of edges in the input +graph. + + +File: igraph-docs.info, Node: igraph_joint_degree_matrix --- The joint degree matrix of a graph_, Next: igraph_rich_club_sequence --- Density sequence of subgraphs formed by sequential vertex removal_, Prev: igraph_joint_degree_distribution --- The joint degree distribution of a graph_, Up: Mixing patterns and degree correlations + +17.21.8 igraph_joint_degree_matrix -- The joint degree matrix of a graph. +------------------------------------------------------------------------- + + + igraph_error_t igraph_joint_degree_matrix( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *jdm, + igraph_int_t max_out_degree, igraph_int_t max_in_degree); + + In graph theory, the joint degree matrix ‘J_ij’ of a graph gives the +number of edges, or sum of edge weights, between vertices of degree ‘i’ +and degree ‘j’. This function stores ‘J_ij’ into ‘jdm[i-1, j-1]’. Each +edge, including self-loops, is counted precisely once, both in +undirected and directed graphs. + + ‘sum_(i,j) J_ij’ is the total number of edges (or total edge weight) +‘m’ in the graph, where ‘(i,j)’ refers to ordered or unordered pairs in +directed and undirected graphs, respectively. Thus ‘J_ij / m’ is the +probability that an edge chosen at random (with probability proportional +to its weight) connects vertices with degrees ‘i’ and ‘j’. + + Note that ‘J_ij’ is similar, but not identical to the joint degree +_distribution,_ computed by ‘igraph_joint_degree_distribution()’ (*note +igraph_joint_degree_distribution --- The joint degree distribution of a +graph_::), which is defined for _ordered_ ‘(i, j)’ degree pairs even in +the undirected case. When considering undirected graphs, the diagonal +of the joint degree distribution is twice that of the joint degree +matrix. + + References: + + Isabelle Stanton and Ali Pinar: Constructing and sampling graphs with +a prescribed joint degree distribution. ACM J. Exp. Algorithmics 17, +Article 3.5 (2012). https://doi.org/10.1145/2133803.2330086 +(https://doi.org/10.1145/2133803.2330086) + + *Arguments:. * + +‘graph’: + A pointer to an initialized graph object. + +‘weights’: + A vector containing the weights of the edges. If passing a ‘NULL’ + pointer, edges will be assumed to have unit weights, i.e. the + matrix entries will be connection counts. + +‘jdm’: + A pointer to an initialized matrix that will be resized. The + values will be written here. + +‘max_out_degree’: + Number of rows in the result, i.e. the largest (out-)degree to + consider. If negative or ‘IGRAPH_UNLIMITED’ (*note + IGRAPH_UNLIMITED --- Constant for "do not limit results"_::), the + largest (out-)degree of the graph will be used. + +‘max_in_degree’: + Number of columns in the result, i.e. the largest (in-)degree to + consider. If negative or ‘IGRAPH_UNLIMITED’ (*note + IGRAPH_UNLIMITED --- Constant for "do not limit results"_::), the + largest (in-)degree of the graph will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_joint_degree_distribution()’ (*note + igraph_joint_degree_distribution --- The joint degree distribution + of a graph_::) to count ordered vertex pairs instead of edges, or + to obtain a normalized matrix. + + Time complexity: O(E), where E is the number of edges in input graph. + + +File: igraph-docs.info, Node: igraph_rich_club_sequence --- Density sequence of subgraphs formed by sequential vertex removal_, Prev: igraph_joint_degree_matrix --- The joint degree matrix of a graph_, Up: Mixing patterns and degree correlations + +17.21.9 igraph_rich_club_sequence -- Density sequence of subgraphs formed by sequential vertex removal. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_rich_club_sequence( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + const igraph_vector_int_t *vertex_order, + igraph_bool_t normalized, + igraph_bool_t loops, igraph_bool_t directed); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function takes a graph and a vertex ordering as input, +sequentially removes the vertices in the given order, and calculates the +density of the remaining subgraph after each removal. + + Density is calculated as the ratio of the number of edges (or total +edge weight, if weighted) to the number of total possible edges in the +graph. The latter is dependent on whether the graph is directed and +whether self-loops are assumed to be possible: for undirected graphs +without self-loops, this total is given by ‘n(n-1)/2’, and for directed +graphs by ‘n(n-1)’. When self-loops are allowed, these are adjusted to +‘n(n+1)/2’ for undirected and ‘n^2’ for directed graphs. + + Vertex order can be sorted by degree so that the resulting density +sequence helps reveal how interconnected a graph is across different +degree levels, or the presence of a "rich-club" effect. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘weights’: + Vector of edge weights. If ‘NULL’ all weights are assumed to be 1. + +‘res’: + Initialized vector, the result will be written here. ‘res[i]’ + contain the density of the remaining graph after ‘i’ vertices have + been removed. If ‘normalized’ is set to ‘false’, it contains the + remaining edge count (or remaining total edge weights if weights + were given). + +‘vertex_order’: + Vector giving the order in which vertices are removed. + +‘normalized’: + If ‘false’, return edge counts (or total edge weights). If ‘true’, + divide by the largest possible edge count to obtain densities. + +‘loops’: + Whether self-loops are assumed to be possible. Ignored when + normalized is not requested. + +‘directed’: + If false, directed graphs will be treated as undirected. Ignored + with undirected graphs. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: invalid vertex_order vector and/or + weight vector lengths + + Time complexity: O(|V| + |E|) where |V| is the number of vertices and +|E| the number of edges in the graph given. + + *See also:. * + +‘’ + ‘igraph_density()’ (*note igraph_density --- Calculate the density + of a graph_::), which uses the same calculation of total possible + edges. + + +File: igraph-docs.info, Node: K-cores and k-trusses, Next: Maximum cardinality search and chordal graphs, Prev: Mixing patterns and degree correlations, Up: Structural properties of graphs + +17.22 K-cores and k-trusses +=========================== + +* Menu: + +* igraph_coreness -- The coreness of the vertices in a graph.: igraph_coreness --- The coreness of the vertices in a graph_. +* igraph_trussness -- Finding the "trussness" of the edges in a network.: igraph_trussness --- Finding the "trussness" of the edges in a network_. + + +File: igraph-docs.info, Node: igraph_coreness --- The coreness of the vertices in a graph_, Next: igraph_trussness --- Finding the "trussness" of the edges in a network_, Up: K-cores and k-trusses + +17.22.1 igraph_coreness -- The coreness of the vertices in a graph. +------------------------------------------------------------------- + + + igraph_error_t igraph_coreness(const igraph_t *graph, + igraph_vector_int_t *cores, igraph_neimode_t mode); + + The k-core of a graph is a maximal subgraph in which each vertex has +at least degree k. (Degree here means the degree in the subgraph of +course.). The coreness of a vertex is the highest order of a k-core +containing the vertex. + + This function implements the algorithm presented in Vladimir +Batagelj, Matjaz Zaversnik: An O(m) Algorithm for Cores Decomposition of +Networks. https://arxiv.org/abs/cs/0310049 +(https://arxiv.org/abs/cs/0310049) + + *Arguments:. * + +‘graph’: + The input graph. + +‘cores’: + Pointer to an initialized vector, the result of the computation + will be stored here. It will be resized as needed. For each + vertex it contains the highest order of a core containing the + vertex. + +‘mode’: + For directed graph it specifies whether to calculate in-cores, + out-cores or the undirected version. It is ignored for undirected + graphs. Possible values: ‘IGRAPH_ALL’ undirected version, + ‘IGRAPH_IN’ in-cores, ‘IGRAPH_OUT’ out-cores. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), the number of edges. + + +File: igraph-docs.info, Node: igraph_trussness --- Finding the "trussness" of the edges in a network_, Prev: igraph_coreness --- The coreness of the vertices in a graph_, Up: K-cores and k-trusses + +17.22.2 igraph_trussness -- Finding the "trussness" of the edges in a network. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_trussness(const igraph_t* graph, igraph_vector_int_t* trussness); + + A k-truss is a subgraph in which every edge occurs in at least ‘k-2’ +triangles in the subgraph. The trussness of an edge indicates the +highest k-truss that the edge occurs in. + + This function returns the highest ‘k’ for each edge. If you are +interested in a particular k-truss subgraph, you can subset the graph to +those edges which are ‘>= k’ because each k-truss is a subgraph of a +‘(k--1)’-truss Thus, to get all 4-trusses, take ‘k >= 4’ because the +5-trusses, 6-trusses, etc. need to be included. + + The current implementation of this function iteratively decrements +support of each edge using O(|E|) space and O(|E|^1.5) time. The +implementation does not support multigraphs; use ‘igraph_simplify()’ +(*note igraph_simplify --- Removes loop and/or multiple edges from the +graph_::) to collapse edges before calling this function. + + Reference: + + See Algorithm 2 in: Wang, Jia, and James Cheng. "Truss decomposition +in massive networks." Proceedings of the VLDB Endowment 5.9 (2012): +812-823. https://doi.org/10.14778/2311906.2311909 +(https://doi.org/10.14778/2311906.2311909) + + *Arguments:. * + +‘graph’: + The input graph. Loop edges are allowed; multigraphs are not. + +‘truss’: + Pointer to initialized vector of truss values that will indicate + the highest k-truss each edge occurs in. It will be resized as + needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: It should be O(|E|^1.5) according to the reference. + + +File: igraph-docs.info, Node: Maximum cardinality search and chordal graphs, Next: Matchings, Prev: K-cores and k-trusses, Up: Structural properties of graphs + +17.23 Maximum cardinality search and chordal graphs +=================================================== + +* Menu: + +* igraph_maximum_cardinality_search -- Maximum cardinality search.: igraph_maximum_cardinality_search --- Maximum cardinality search_. +* igraph_is_chordal -- Decides whether a graph is chordal.: igraph_is_chordal --- Decides whether a graph is chordal_. + + +File: igraph-docs.info, Node: igraph_maximum_cardinality_search --- Maximum cardinality search_, Next: igraph_is_chordal --- Decides whether a graph is chordal_, Up: Maximum cardinality search and chordal graphs + +17.23.1 igraph_maximum_cardinality_search -- Maximum cardinality search. +------------------------------------------------------------------------ + + + igraph_error_t igraph_maximum_cardinality_search(const igraph_t *graph, + igraph_vector_int_t *alpha, + igraph_vector_int_t *alpham1); + + This function implements the maximum cardinality search algorithm. +It computes a rank ‘alpha’ for each vertex, such that visiting vertices +in decreasing rank order corresponds to always choosing the vertex with +the most already visited neighbors as the next one to visit. + + Maximum cardinality search is useful in deciding the chordality of a +graph. A graph is chordal if and only if any two neighbors of a vertex +which are higher in rank than it are connected to each other. + + References: + + Robert E Tarjan and Mihalis Yannakakis: Simple linear-time algorithms +to test chordality of graphs, test acyclicity of hypergraphs, and +selectively reduce acyclic hypergraphs. SIAM Journal of Computation 13, +566-579, 1984. https://doi.org/10.1137/0213035 +(https://doi.org/10.1137/0213035) + + *Arguments:. * + +‘graph’: + The input graph. Edge directions will be ignored. + +‘alpha’: + Pointer to an initialized vector, the result is stored here. It + will be resized, as needed. Upon return it contains the rank of + the each vertex in the range 0 to ‘n - 1’, where ‘n’ is the number + of vertices. + +‘alpham1’: + Pointer to an initialized vector or a ‘NULL’ pointer. If not + ‘NULL’, then the inverse of ‘alpha’ is stored here. In other + words, the elements of ‘alpham1’ are vertex IDs in reverse maximum + cardinality search order. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in terms of the number of +vertices and edges. + + *See also:. * + +‘’ + ‘igraph_is_chordal()’ (*note igraph_is_chordal --- Decides whether + a graph is chordal_::). + + +File: igraph-docs.info, Node: igraph_is_chordal --- Decides whether a graph is chordal_, Prev: igraph_maximum_cardinality_search --- Maximum cardinality search_, Up: Maximum cardinality search and chordal graphs + +17.23.2 igraph_is_chordal -- Decides whether a graph is chordal. +---------------------------------------------------------------- + + + igraph_error_t igraph_is_chordal(const igraph_t *graph, + const igraph_vector_int_t *alpha, + const igraph_vector_int_t *alpham1, + igraph_bool_t *chordal, + igraph_vector_int_t *fill_in, + igraph_t *newgraph); + + A graph is chordal if each of its cycles of four or more nodes has a +chord, i.e. an edge joining two nodes that are not adjacent in the +cycle. An equivalent definition is that any chordless cycles have at +most three nodes. If either ‘alpha’ or ‘alpham1’ is given, then the +other is calculated by taking simply the inverse. If neither are given, +then ‘igraph_maximum_cardinality_search()’ (*note +igraph_maximum_cardinality_search --- Maximum cardinality search_::) is +called to calculate them. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions will be ignored. + +‘alpha’: + Either an alpha vector coming from + ‘igraph_maximum_cardinality_search()’ (*note + igraph_maximum_cardinality_search --- Maximum cardinality + search_::) (on the same graph), or a ‘NULL’ pointer. + +‘alpham1’: + Either an inverse alpha vector coming from + ‘igraph_maximum_cardinality_search()’ (*note + igraph_maximum_cardinality_search --- Maximum cardinality + search_::) (on the same graph) or a ‘NULL’ pointer. + +‘chordal’: + Pointer to a boolean. If not NULL the result is stored here. + +‘fill_in’: + Pointer to an initialized vector, or a ‘NULL’ pointer. If not a + ‘NULL’ pointer, then the fill-in, also called the chordal + completion of the graph is stored here. The chordal completion is + a set of edges that are needed to make the graph chordal. The + vector is resized as needed. Note that the chordal completion + returned by this function may not be minimal, i.e. some of the + returned fill-in edges may not be needed to make the graph chordal. + +‘newgraph’: + Pointer to an uninitialized graph, or a ‘NULL’ pointer. If not a + null pointer, then a new triangulated graph is created here. This + essentially means adding the fill-in edges to the original graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n). + + *See also:. * + +‘’ + ‘igraph_maximum_cardinality_search()’ (*note + igraph_maximum_cardinality_search --- Maximum cardinality + search_::). + + +File: igraph-docs.info, Node: Matchings, Next: Unfolding a graph into a tree, Prev: Maximum cardinality search and chordal graphs, Up: Structural properties of graphs + +17.24 Matchings +=============== + +* Menu: + +* igraph_is_matching -- Checks whether the given matching is valid for the given graph.: igraph_is_matching --- Checks whether the given matching is valid for the given graph_. +* igraph_is_maximal_matching -- Checks whether a matching in a graph is maximal.: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_. +* igraph_maximum_bipartite_matching -- Calculates a maximum matching in a bipartite graph.: igraph_maximum_bipartite_matching --- Calculates a maximum matching in a bipartite graph_. + + +File: igraph-docs.info, Node: igraph_is_matching --- Checks whether the given matching is valid for the given graph_, Next: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_, Up: Matchings + +17.24.1 igraph_is_matching -- Checks whether the given matching is valid for the given graph. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_is_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, const igraph_vector_int_t *matching, + igraph_bool_t *result); + + This function checks a matching vector and verifies whether its +length matches the number of vertices in the given graph, its values are +between -1 (inclusive) and the number of vertices (exclusive), and +whether there exists a corresponding edge in the graph for every matched +vertex pair. For bipartite graphs, it also verifies whether the matched +vertices are in different parts of the graph. + + *Arguments:. * + +‘graph’: + The input graph. It can be directed but the edge directions will + be ignored. + +‘types’: + If the graph is bipartite and you are interested in bipartite + matchings only, pass the vertex types here. If the graph is + non-bipartite, simply pass ‘NULL’. + +‘matching’: + The matching itself. It must be a vector where element i contains + the ID of the vertex that vertex i is matched to, or -1 if vertex i + is unmatched. + +‘result’: + Pointer to a boolean variable, the result will be returned here. + + *See also:. * + +‘’ + ‘igraph_is_maximal_matching()’ (*note igraph_is_maximal_matching + --- Checks whether a matching in a graph is maximal_::) if you are + also interested in whether the matching is maximal (i.e. + non-extendable). + + Time complexity: O(|V|+|E|) where |V| is the number of vertices and +|E| is the number of edges. + + * File examples/simple/igraph_maximum_bipartite_matching.c* + + +File: igraph-docs.info, Node: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_, Next: igraph_maximum_bipartite_matching --- Calculates a maximum matching in a bipartite graph_, Prev: igraph_is_matching --- Checks whether the given matching is valid for the given graph_, Up: Matchings + +17.24.2 igraph_is_maximal_matching -- Checks whether a matching in a graph is maximal. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_is_maximal_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, const igraph_vector_int_t *matching, + igraph_bool_t *result); + + A matching is maximal if and only if there exists no unmatched vertex +in a graph such that one of its neighbors is also unmatched. + + *Arguments:. * + +‘graph’: + The input graph. It can be directed but the edge directions will + be ignored. + +‘types’: + If the graph is bipartite and you are interested in bipartite + matchings only, pass the vertex types here. If the graph is + non-bipartite, simply pass ‘NULL’. + +‘matching’: + The matching itself. It must be a vector where element i contains + the ID of the vertex that vertex i is matched to, or -1 if vertex i + is unmatched. + +‘result’: + Pointer to a boolean variable, the result will be returned here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_matching()’ (*note igraph_is_matching --- Checks whether + the given matching is valid for the given graph_::) if you are only + interested in whether a matching vector is valid for a given graph. + + Time complexity: O(|V|+|E|) where |V| is the number of vertices and +|E| is the number of edges. + + * File examples/simple/igraph_maximum_bipartite_matching.c* + + +File: igraph-docs.info, Node: igraph_maximum_bipartite_matching --- Calculates a maximum matching in a bipartite graph_, Prev: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_, Up: Matchings + +17.24.3 igraph_maximum_bipartite_matching -- Calculates a maximum matching in a bipartite graph. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_maximum_bipartite_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_real_t *matching_weight, igraph_vector_int_t *matching, + const igraph_vector_t *weights, igraph_real_t eps); + + A matching in a bipartite graph is a partial assignment of vertices +of the first kind to vertices of the second kind such that each vertex +of the first kind is matched to at most one vertex of the second kind +and vice versa, and matched vertices must be connected by an edge in the +graph. The size (or cardinality) of a matching is the number of edges. +A matching is a maximum matching if there exists no other matching with +larger cardinality. For weighted graphs, a maximum matching is a +matching whose edges have the largest possible total weight among all +possible matchings. + + Maximum matchings in bipartite graphs are found by the push-relabel +algorithm with greedy initialization and a global relabeling after every +n/2 steps where n is the number of vertices in the graph. + + References: Cherkassky BV, Goldberg AV, Martin P, Setubal JC and +Stolfi J: Augment or push: A computational study of bipartite matching +and unit-capacity flow algorithms. ACM Journal of Experimental +Algorithmics 3, 1998. + + Kaya K, Langguth J, Manne F and Ucar B: Experiments on +push-relabel-based maximum cardinality matching algorithms for bipartite +graphs. Technical Report TR/PA/11/33 of the Centre Europeen de +Recherche et de Formation Avancee en Calcul Scientifique, 2011. + + *Arguments:. * + +‘graph’: + The input graph. It can be directed but the edge directions will + be ignored. + +‘types’: + Boolean vector giving the vertex types of the graph. + +‘matching_size’: + The size of the matching (i.e. the number of matched vertex pairs + will be returned here). It may be ‘NULL’ if you don't need this. + +‘matching_weight’: + The weight of the matching if the edges are weighted, or the size + of the matching again if the edges are unweighted. It may be + ‘NULL’ if you don't need this. + +‘matching’: + The matching itself. It must be a vector where element i contains + the ID of the vertex that vertex i is matched to, or -1 if vertex i + is unmatched. + +‘weights’: + A null pointer (=no edge weights), or a vector giving the weights + of the edges. Note that the algorithm is stable only for integer + weights. + +‘eps’: + A small real number used in equality tests in the weighted + bipartite matching algorithm. Two real numbers are considered + equal in the algorithm if their difference is smaller than ‘eps’. + This is required to avoid the accumulation of numerical errors. It + is advised to pass a value derived from the ‘DBL_EPSILON’ constant + in ‘float’.h here. If you are running the algorithm with no + ‘weights’ vector, this argument is ignored. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(sqrt(|V|) |E|) for unweighted graphs (according to +the technical report referenced above), O(|V||E|) for weighted graphs. + + * File examples/simple/igraph_maximum_bipartite_matching.c* + + +File: igraph-docs.info, Node: Unfolding a graph into a tree, Next: Other operations, Prev: Matchings, Up: Structural properties of graphs + +17.25 Unfolding a graph into a tree +=================================== + +* Menu: + +* igraph_unfold_tree -- Unfolding a graph into a tree, by possibly multiplicating its vertices.: igraph_unfold_tree --- Unfolding a graph into a tree; by possibly multiplicating its vertices_. + + +File: igraph-docs.info, Node: igraph_unfold_tree --- Unfolding a graph into a tree; by possibly multiplicating its vertices_, Up: Unfolding a graph into a tree + +17.25.1 igraph_unfold_tree -- Unfolding a graph into a tree, by possibly multiplicating its vertices. +----------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_unfold_tree(const igraph_t *graph, igraph_t *tree, + igraph_neimode_t mode, const igraph_vector_int_t *roots, + igraph_vector_int_t *vertex_index); + + A graph is converted into a tree (or forest, if it is unconnected), +by performing a breadth-first search on it, and replicating vertices +that were found a second, third, etc. time. + + *Arguments:. * + +‘graph’: + The input graph, it can be either directed or undirected. + +‘tree’: + Pointer to an uninitialized graph object, the result is stored + here. + +‘mode’: + For directed graphs; whether to follow paths along edge directions + (‘IGRAPH_OUT’), or the opposite (‘IGRAPH_IN’), or ignore edge + directions completely (‘IGRAPH_ALL’). It is ignored for undirected + graphs. + +‘roots’: + A numeric vector giving the root vertex, or vertices (if the graph + is not connected), to start from. + +‘vertex_index’: + Pointer to an initialized vector, or a null pointer. If not a null + pointer, then a mapping from the vertices in the new graph to the + ones in the original is created here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n+m), linear in the number vertices and edges. + + +File: igraph-docs.info, Node: Other operations, Next: Common types and constants <1>, Prev: Unfolding a graph into a tree, Up: Structural properties of graphs + +17.26 Other operations +====================== + +* Menu: + +* igraph_density -- Calculate the density of a graph.: igraph_density --- Calculate the density of a graph_. +* igraph_mean_degree -- The mean degree of a graph.: igraph_mean_degree --- The mean degree of a graph_. +* igraph_reciprocity -- Calculates the reciprocity of a directed graph.: igraph_reciprocity --- Calculates the reciprocity of a directed graph_. +* igraph_diversity -- Structural diversity index of the vertices.: igraph_diversity --- Structural diversity index of the vertices_. +* igraph_is_mutual -- Check whether some edges of a directed graph are mutual.: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_. +* igraph_has_mutual -- Check whether a directed graph has any mutual edges.: igraph_has_mutual --- Check whether a directed graph has any mutual edges_. +* igraph_get_adjacency -- The adjacency matrix of a graph.: igraph_get_adjacency --- The adjacency matrix of a graph_. +* igraph_get_adjacency_sparse -- Returns the adjacency matrix of a graph in a sparse matrix format.: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_. +* igraph_get_stochastic -- Stochastic adjacency matrix of a graph.: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_. +* igraph_get_stochastic_sparse -- The stochastic adjacency matrix of a graph.: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_. +* igraph_get_edgelist -- The list of edges in a graph.: igraph_get_edgelist --- The list of edges in a graph_. + + +File: igraph-docs.info, Node: igraph_density --- Calculate the density of a graph_, Next: igraph_mean_degree --- The mean degree of a graph_, Up: Other operations + +17.26.1 igraph_density -- Calculate the density of a graph. +----------------------------------------------------------- + + + igraph_error_t igraph_density( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *res, + igraph_bool_t loops); + + The density of a graph is simply the ratio of the actual number of +its edges and the largest possible number of edges it could have. The +maximum number of edges depends on interpretation: are vertices allowed +to have a connection to themselves? This is controlled by the ‘loops’ +parameter. + + The classic definition of the density is formulated for unweighted +graphs without multi-edges. This function allows multigraphs and +weighted graphs as well. In this case, it computes the ratio of the +total edge weight to the largest possible number of adjacent vertex +pairs the graph could have. This value may be larger than 1. + + If you need the density concept for simple graphs, make sure to +eliminate any multi-edges appropriately. This can be done using +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::). + + *Arguments:. * + +‘graph’: + The input graph object. It must not have parallel edges. + +‘res’: + Pointer to a real number, the result will be stored here. + +‘weights’: + Vector of edge weights. Pass ‘NULL’ to to perform an unweighted + density calculation. + +‘loops’: + Boolean constant, whether to include self-loops in the calculation. + If this constant is ‘true’ then loop edges are thought to be + possible in the graph (this does not necessarily mean that the + graph really contains any loops). If this is ‘false’ then the + result is only correct if the graph does not contain loops. This + function does not check if loops are actually present. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_mean_degree --- The mean degree of a graph_, Next: igraph_reciprocity --- Calculates the reciprocity of a directed graph_, Prev: igraph_density --- Calculate the density of a graph_, Up: Other operations + +17.26.2 igraph_mean_degree -- The mean degree of a graph. +--------------------------------------------------------- + + + igraph_error_t igraph_mean_degree(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t loops); + + This is a convenience function that computes the average of all +vertex degrees. In directed graphs, the average of out-degrees and +in-degrees is the same; this is the number that is returned. For the +null graph, which has no vertices, NaN is returned. + + *Arguments:. * + +‘graph’: + The input graph object. + +‘res’: + Pointer to a real number, the result will be stored here. + +‘loops’: + Whether to consider self-loops during the calculation. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1) if self-loops are considered, O(|E|) where |E| +is the number of edges if self-loops are ignored. + + +File: igraph-docs.info, Node: igraph_reciprocity --- Calculates the reciprocity of a directed graph_, Next: igraph_diversity --- Structural diversity index of the vertices_, Prev: igraph_mean_degree --- The mean degree of a graph_, Up: Other operations + +17.26.3 igraph_reciprocity -- Calculates the reciprocity of a directed graph. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_reciprocity(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t ignore_loops, + igraph_reciprocity_t mode); + + In a directed graph, the measure of reciprocity defines the +proportion of mutual connections. It is most commonly defined as the +probability that the opposite counterpart of a randomly chosen directed +edge is also included in the graph. In adjacency matrix notation: ‘1 - +(sum_ij |A_ij - A_ji|) / (2 sum_ij A_ij)’. In multigraphs, each +parallel edge between two vertices must have its own separate reciprocal +edge, in accordance with the above formula. This measure is calculated +if the ‘mode’ argument is ‘IGRAPH_RECIPROCITY_DEFAULT’. + + For directed graphs with no edges, NaN is returned. For undirected +graphs, 1 is returned unconditionally. + + Prior to igraph version 0.6, another measure was implemented, defined +as the probability of having mutual connections between a vertex pair if +we know that there is a (possibly non-mutual) connection between them. +In other words, (unordered) vertex pairs are classified into three +groups: (1) disconnected, (2) non-reciprocally connected, (3) +reciprocally connected. The result is the size of group (3), divided by +the sum of group sizes (2)+(3). This measure is calculated if ‘mode’ is +‘IGRAPH_RECIPROCITY_RATIO’. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + Pointer to an ‘igraph_real_t’ which will contain the result. + +‘ignore_loops’: + Whether to ignore self-loops when counting edges. Self-loops are + considered as a mutual connection. + +‘mode’: + Type of reciprocity to calculate, possible values are + ‘IGRAPH_RECIPROCITY_DEFAULT’ and ‘IGRAPH_RECIPROCITY_RATIO’, please + see their description above. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’: graph has no edges ‘IGRAPH_ENOMEM’: + not enough memory for temporary data. + + Time complexity: O(|V|+|E|), |V| is the number of vertices, |E| is +the number of edges. + + * File examples/simple/igraph_reciprocity.c* + + +File: igraph-docs.info, Node: igraph_diversity --- Structural diversity index of the vertices_, Next: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_, Prev: igraph_reciprocity --- Calculates the reciprocity of a directed graph_, Up: Other operations + +17.26.4 igraph_diversity -- Structural diversity index of the vertices. +----------------------------------------------------------------------- + + + igraph_error_t igraph_diversity(const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, const igraph_vs_t vids); + + This measure was defined in Nathan Eagle, Michael Macy and Rob +Claxton: Network Diversity and Economic Development, Science 328, +1029-1031, 2010. + + It is simply the (normalized) Shannon entropy of the incident edges' +weights. ‘D(i) = H(i) / log(k[i])’, and ‘H(i) = -sum(p[i,j] +log(p[i,j]), j=1..k[i])’, where ‘p[i,j] = w[i,j] / sum(w[i,l], +l=1..k[i])’, ‘k[i]’ is the (total) degree of vertex ‘i’, and ‘w[i,j]’ is +the weight of the edge(s) between vertex ‘i’ and ‘j’. The diversity of +isolated vertices will be NaN (not-a-number), while that of vertices +with a single connection will be zero. + + The measure works only if the graph is undirected and has no multiple +edges. If the graph has multiple edges, simplify it first using +‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or +multiple edges from the graph_::). If the graph is directed, convert it +into an undirected graph with ‘igraph_to_undirected()’ (*note +igraph_to_undirected --- Convert a directed graph to an undirected +one_::) . + + *Arguments:. * + +‘graph’: + The undirected input graph. + +‘weights’: + The edge weights, in the order of the edge IDs, must have + appropriate length. Weights must be non-negative. + +‘res’: + An initialized vector, the results are stored here. + +‘vids’: + Vertex selector that specifies the vertices which to calculate the + measure. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear. + + +File: igraph-docs.info, Node: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_, Next: igraph_has_mutual --- Check whether a directed graph has any mutual edges_, Prev: igraph_diversity --- Structural diversity index of the vertices_, Up: Other operations + +17.26.5 igraph_is_mutual -- Check whether some edges of a directed graph are mutual. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_is_mutual(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es, igraph_bool_t loops); + + An (A,B) non-loop directed edge is mutual if the graph contains the +(B,A) edge too. Whether directed self-loops are considered mutual is +controlled by the ‘loops’ parameter. + + An undirected graph only has mutual edges, by definition. + + Edge multiplicity is not considered here, e.g. if there are two +(A,B) edges and one (B,A) edge, then all three are considered to be +mutual. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized vector, the result is stored here. + +‘es’: + The sequence of edges to check. Supply ‘igraph_ess_all()’ (*note + igraph_ess_all --- Edge set; all edges [immediate version]_::) to + check all edges. + +‘loops’: + Boolean, whether to consider directed self-loops to be mutual. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n log(d)), n is the number of edges supplied, d is +the maximum in-degree of the vertices that are targets of the supplied +edges. An upper limit of the time complexity is O(n log(|E|)), |E| is +the number of edges in the graph. + + +File: igraph-docs.info, Node: igraph_has_mutual --- Check whether a directed graph has any mutual edges_, Next: igraph_get_adjacency --- The adjacency matrix of a graph_, Prev: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_, Up: Other operations + +17.26.6 igraph_has_mutual -- Check whether a directed graph has any mutual edges. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_has_mutual(const igraph_t *graph, igraph_bool_t *res, + igraph_bool_t loops); + + An (A,B) non-loop directed edge is mutual if the graph contains the +(B,A) edge too. Whether directed self-loops are considered mutual is +controlled by the ‘loops’ parameter. + + In undirected graphs, all edges are considered mutual by definition. +Thus for undirected graph, this function returns false only when there +are no edges. + + To check whether a graph is an oriented graph, use this function in +conjunction with ‘igraph_is_directed()’ (*note igraph_is_directed --- Is +this a directed graph?::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean, the result will be stored here. + +‘loops’: + Boolean, whether to consider directed self-loops to be mutual. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E| log(d)) where d is the maximum in-degree. + + +File: igraph-docs.info, Node: igraph_get_adjacency --- The adjacency matrix of a graph_, Next: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_, Prev: igraph_has_mutual --- Check whether a directed graph has any mutual edges_, Up: Other operations + +17.26.7 igraph_get_adjacency -- The adjacency matrix of a graph. +---------------------------------------------------------------- + + + igraph_error_t igraph_get_adjacency( + const igraph_t *graph, igraph_matrix_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops + ); + + The result is an adjacency matrix. Entry i, j of the matrix contains +the number of edges connecting vertex i to vertex j in the unweighted +case, or the total weight of edges connecting vertex i to vertex j in +the weighted case. + + *Arguments:. * + +‘graph’: + Pointer to the graph to convert + +‘res’: + Pointer to an initialized matrix object, it will be resized if + needed. + +‘type’: + Constant specifying the type of the adjacency matrix to create for + undirected graphs. It is ignored for directed graphs. Possible + values: + + ‘IGRAPH_GET_ADJACENCY_UPPER’ + the upper right triangle of the matrix is used. + + ‘IGRAPH_GET_ADJACENCY_LOWER’ + the lower left triangle of the matrix is used. + + ‘IGRAPH_GET_ADJACENCY_BOTH’ + the whole matrix is used, a symmetric matrix is returned if + the graph is undirected. + +‘weights’: + An optional vector containing the weight of each edge in the graph. + Supply a null pointer here to make all edges have the same weight + of 1. + +‘loops’: + Constant specifying how loop edges should be handled. Possible + values: + + ‘IGRAPH_NO_LOOPS’ + loop edges are ignored and the diagonal of the matrix will + contain zeros only + + ‘IGRAPH_LOOPS_ONCE’ + loop edges are counted once, i.e. a vertex with a single + unweighted loop edge will have 1 in the corresponding diagonal + entry + + ‘IGRAPH_LOOPS_TWICE’ + loop edges are counted twice in _undirected_ graphs, i.e. a + vertex with a single unweighted loop edge in an undirected + graph will have 2 in the corresponding diagonal entry. Loop + edges in directed graphs are still counted as 1. Essentially, + this means that the function is counting the incident edge + _stems_ , which makes more sense when using the adjacency + matrix in linear algebra. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’ invalid type argument. + + *See also:. * + +‘’ + ‘igraph_get_adjacency_sparse()’ (*note igraph_get_adjacency_sparse + --- Returns the adjacency matrix of a graph in a sparse matrix + format_::) if you want a sparse matrix representation + + Time complexity: O(|V||V|), |V| is the number of vertices in the +graph. + + +File: igraph-docs.info, Node: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_, Next: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_, Prev: igraph_get_adjacency --- The adjacency matrix of a graph_, Up: Other operations + +17.26.8 igraph_get_adjacency_sparse -- Returns the adjacency matrix of a graph in a sparse matrix format. +--------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_adjacency_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops + ); + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an _initialized_ sparse matrix. The result will be + stored here. The matrix will be resized as needed. + +‘type’: + Constant specifying the type of the adjacency matrix to create for + undirected graphs. It is ignored for directed graphs. Possible + values: + + ‘IGRAPH_GET_ADJACENCY_UPPER’ + the upper right triangle of the matrix is used. + + ‘IGRAPH_GET_ADJACENCY_LOWER’ + the lower left triangle of the matrix is used. + + ‘IGRAPH_GET_ADJACENCY_BOTH’ + the whole matrix is used, a symmetric matrix is returned if + the graph is undirected. + +‘weights’: + An optional vector containing the weight of each edge in the graph. + Supply a null pointer here to make all edges have the same weight + of 1. + +‘loops’: + Constant specifying how loop edges should be handled. Possible + values: + + ‘IGRAPH_NO_LOOPS’ + loop edges are ignored and the diagonal of the matrix will + contain zeros only + + ‘IGRAPH_LOOPS_ONCE’ + loop edges are counted once, i.e. a vertex with a single + unweighted loop edge will have 1 in the corresponding diagonal + entry + + ‘IGRAPH_LOOPS_TWICE’ + loop edges are counted twice in _undirected_ graphs, i.e. a + vertex with a single unweighted loop edge in an undirected + graph will have 2 in the corresponding diagonal entry. Loop + edges in directed graphs are still counted as 1. Essentially, + this means that the function is counting the incident edge + _stems_ , which makes more sense when using the adjacency + matrix in linear algebra. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’ invalid type argument. + + *See also:. * + +‘’ + ‘igraph_get_adjacency()’ (*note igraph_get_adjacency --- The + adjacency matrix of a graph_::), the dense version of this + function. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_, Next: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_, Prev: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_, Up: Other operations + +17.26.9 igraph_get_stochastic -- Stochastic adjacency matrix of a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_get_stochastic( + const igraph_t *graph, igraph_matrix_t *res, igraph_bool_t column_wise, + const igraph_vector_t *weights + ); + + Stochastic matrix of a graph. The stochastic matrix of a graph is +its adjacency matrix, normalized row-wise (or column-wise), such that +the sum of each row (or column) is one. The row-wise normalized matrix +is also called a _right-stochastic_ and contains the transition +probabilities of a random walk that follows edge directions in a +directed graph. The column-wise normalized matrix is called +_left-stochastic_ and is related to random walks moving against edge +directions. + + When the out-degree (or in-degree) of a vertex is zero, the +corresponding row (or column) of the row-wise (or column-wise) +normalized stochastic matrix will be zero. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized matrix, the result is stored here. It + will be resized as needed. + +‘column_wise’: + If ‘false’, row-wise normalization is used. If ‘true’, column-wise + normalization is used. + +‘weights’: + An optional vector containing the weight of each edge in the graph. + Supply a null pointer here to make all edges have the same weight + of 1. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^2), |V| is the number of vertices in the +graph. + + *See also:. * + +‘’ + ‘igraph_get_stochastic_sparse()’ (*note + igraph_get_stochastic_sparse --- The stochastic adjacency matrix of + a graph_::), the sparse version of this function. + + +File: igraph-docs.info, Node: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_, Next: igraph_get_edgelist --- The list of edges in a graph_, Prev: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_, Up: Other operations + +17.26.10 igraph_get_stochastic_sparse -- The stochastic adjacency matrix of a graph. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_get_stochastic_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_bool_t column_wise, + const igraph_vector_t *weights + ); + + Stochastic matrix of a graph in sparse format. See +‘igraph_get_stochastic()’ (*note igraph_get_stochastic --- Stochastic +adjacency matrix of a graph_::) for the information on stochastic +matrices. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an _initialized_ sparse matrix, the result is stored + here. The matrix will be resized as needed. + +‘column_wise’: + If ‘false’, row-wise normalization is used. If ‘true’, column-wise + normalization is used. + +‘weights’: + An optional vector containing the weight of each edge in the graph. + Supply a null pointer here to make all edges have the same weight + of 1. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number of vertices and +edges. + + *See also:. * + +‘’ + ‘igraph_get_stochastic()’ (*note igraph_get_stochastic --- + Stochastic adjacency matrix of a graph_::), the dense version of + this function. + + +File: igraph-docs.info, Node: igraph_get_edgelist --- The list of edges in a graph_, Prev: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_, Up: Other operations + +17.26.11 igraph_get_edgelist -- The list of edges in a graph. +------------------------------------------------------------- + + + igraph_error_t igraph_get_edgelist(const igraph_t *graph, igraph_vector_int_t *res, igraph_bool_t bycol); + + The order of the edges is given by the edge IDs. + + *Arguments:. * + +‘graph’: + Pointer to the graph object + +‘res’: + Pointer to an initialized vector object, it will be resized. + +‘bycol’: + Boolean constant. If true, the edges will be returned columnwise, + e.g. the first edge is ‘res[0]->res[|E|]’, the second is + ‘res[1]->res[|E|+1]’, etc. Supply false to get the edge list in a + format compatible with ‘igraph_add_edges()’ (*note igraph_add_edges + --- Adds edges to a graph object_::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_edges()’ (*note igraph_edges --- Gives the head and tail + vertices of a series of edges_::) to return the result only for + some edge IDs. + + Time complexity: O(|E|), the number of edges in the graph. + + +File: igraph-docs.info, Node: Common types and constants <1>, Prev: Other operations, Up: Structural properties of graphs + +17.27 Common types and constants +================================ + +* Menu: + +* igraph_loops_t --- How to interpret self-loops in undirected graphs?:: +* igraph_neimode_t --- How to interpret edge directions in directed graphs?:: + + +File: igraph-docs.info, Node: igraph_loops_t --- How to interpret self-loops in undirected graphs?, Next: igraph_neimode_t --- How to interpret edge directions in directed graphs?, Up: Common types and constants <1> + +17.27.1 igraph_loops_t -- How to interpret self-loops in undirected graphs? +--------------------------------------------------------------------------- + + + typedef enum { + IGRAPH_NO_LOOPS = 0, + IGRAPH_LOOPS_TWICE = 1, + IGRAPH_LOOPS_ONCE = 2, + IGRAPH_LOOPS = IGRAPH_LOOPS_TWICE + } igraph_loops_t; + + Controls the interpretation of self-loops in undirected graphs, +typically in the context of adjacency matrices or degrees. + + These constants are also used to improve readability in boolean +contexts, with ‘IGRAPH_NO_LOOPS’, equivalent to ‘false’, signifying that +loops should be ignored and ‘IGRAPH_LOOPS’, equivalent to ‘true’, that +loops should be considered. + + *Values:. * + +‘IGRAPH_NO_LOOPS’: + Self-loops are ignored. + +‘IGRAPH_LOOPS_TWICE’: + Self-loops are considered, and counted twice in undirected graphs. + For example, a self-loop contributes two to the degree of a vertex + and to diagonal entries of adjacency matrices. This is the + standard interpretation in graph theory, thus ‘IGRAPH_LOOPS’ serves + as an alias for this option. + +‘IGRAPH_LOOPS_ONCE’: + Self-loops are considered, and counted only once in undirected + graphs. + + +File: igraph-docs.info, Node: igraph_neimode_t --- How to interpret edge directions in directed graphs?, Prev: igraph_loops_t --- How to interpret self-loops in undirected graphs?, Up: Common types and constants <1> + +17.27.2 igraph_neimode_t -- How to interpret edge directions in directed graphs? +-------------------------------------------------------------------------------- + + + typedef enum { + IGRAPH_OUT = 1, + IGRAPH_IN = 2, + IGRAPH_ALL = 3 + } igraph_neimode_t; + + These "neighbor mode" constants are typically used to specify the +treatment of edge directions in directed graphs, or which vertices to +consider as adjacent to (i.e. neighbor of) a vertex. It is typically +ignored in undirected graphs. + + *Values:. * + +‘IGRAPH_OUT’: + Follow edge directions in directed graphs, or consider + out-neighbors of vertices. + +‘IGRAPH_IN’: + Follow edges in the reverse direction in directed graphs, or + consider in-neighbors of vertices. + +‘IGRAPH_ALL’: + Ignore edge directions in directed graphs, or consider all + neighbours (both out and in-neighbors) of vertices. + + +File: igraph-docs.info, Node: Graph cycles, Next: Cliques and independent vertex sets, Prev: Structural properties of graphs, Up: Top + +18 Graph cycles +*************** + +* Menu: + +* Finding cycles:: +* Acyclic graphs and feedback sets:: +* Eulerian cycles and paths:: +* Cycle bases:: + + +File: igraph-docs.info, Node: Finding cycles, Next: Acyclic graphs and feedback sets, Up: Graph cycles + +18.1 Finding cycles +=================== + +* Menu: + +* igraph_find_cycle -- Finds a single cycle in the graph.: igraph_find_cycle --- Finds a single cycle in the graph_. +* igraph_simple_cycles -- Finds all simple cycles.: igraph_simple_cycles --- Finds all simple cycles_. +* igraph_simple_cycles_callback -- Finds all simple cycles (callback version).: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_. +* igraph_cycle_handler_t -- Type of cycle handler functions.: igraph_cycle_handler_t --- Type of cycle handler functions_. + + +File: igraph-docs.info, Node: igraph_find_cycle --- Finds a single cycle in the graph_, Next: igraph_simple_cycles --- Finds all simple cycles_, Up: Finding cycles + +18.1.1 igraph_find_cycle -- Finds a single cycle in the graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_find_cycle(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_neimode_t mode); + + This function returns a cycle of the graph, if there is one. If the +graph is acyclic, it returns empty vectors. + + *Arguments:. * + +‘graph’: + The input graph. + +‘vertices’: + Pointer to an integer vector. If a cycle is found, its vertices + will be stored here. Otherwise the vector will be empty. + +‘edges’: + Pointer to an integer vector. If a cycle is found, its edges will + be stored here. Otherwise the vector will be empty. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. Valid modes are: ‘IGRAPH_OUT’, follows edge + directions; ‘IGRAPH_IN’, follows the opposite directions; and + ‘IGRAPH_ALL’, ignores edge directions. This argument is ignored + for undirected graphs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), where |V| and |E| are the number of +vertices and edges in the original input graph. + + *See also:. * + +‘’ + ‘igraph_is_acyclic()’ (*note igraph_is_acyclic --- Checks whether a + graph is acyclic or not_::) to determine if a graph is acyclic, + without returning a specific cycle; ‘igraph_simple_cycles()’ (*note + igraph_simple_cycles --- Finds all simple cycles_::) to list all + cycles in a graph. + + +File: igraph-docs.info, Node: igraph_simple_cycles --- Finds all simple cycles_, Next: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_, Prev: igraph_find_cycle --- Finds a single cycle in the graph_, Up: Finding cycles + +18.1.2 igraph_simple_cycles -- Finds all simple cycles. +------------------------------------------------------- + + + igraph_error_t igraph_simple_cycles( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, igraph_int_t max_cycle_length, + igraph_int_t max_results); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function searches for all simple cycles using Johnson's cycle +detection algorithm, and stores them in the provided vector lists. A +simple cycle is a cycle (i.e. closed path) without repeated vertices. + + Reference: + + Johnson DB: Finding all the elementary circuits of a directed graph. +SIAM J. Comput. 4(1):77-84. https://doi.org/10.1137/0204007 +(https://doi.org/10.1137/0204007) + + *Arguments:. * + +‘graph’: + The graph to search for cycles in. + +‘vertices’: + The vertex IDs of each cycle will be stored here. + +‘edges’: + The edge IDs of each cycle will be stored here. + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. Valid modes are: ‘IGRAPH_OUT’, follows edge + directions; ‘IGRAPH_IN’, follows the opposite directions; and + ‘IGRAPH_ALL’, ignores edge directions. This argument is ignored + for undirected graphs. + +‘min_cycle_length’: + Limit the minimum length of cycles to search for. Pass a negative + value to search for all cycles. + +‘max_cycle_length’: + Limit the maximum length of cycles to search for. Pass a negative + value to search for all cycles. + +‘max_results’: + At most this many cycles will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_simple_cycles_callback()’ (*note + igraph_simple_cycles_callback --- Finds all simple cycles [callback + version]_::) to call a function for each found cycle; + ‘igraph_find_cycle()’ (*note igraph_find_cycle --- Finds a single + cycle in the graph_::) to find a single cycle; + ‘igraph_fundamental_cycles()’ (*note igraph_fundamental_cycles --- + Finds a fundamental cycle basis_::) and + ‘igraph_minimum_cycle_basis()’ (*note igraph_minimum_cycle_basis + --- Computes a minimum weight cycle basis_::) to find a cycle + basis, a compact representation of the cycle structure of the + graph. + + +File: igraph-docs.info, Node: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_, Next: igraph_cycle_handler_t --- Type of cycle handler functions_, Prev: igraph_simple_cycles --- Finds all simple cycles_, Up: Finding cycles + +18.1.3 igraph_simple_cycles_callback -- Finds all simple cycles (callback version). +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_simple_cycles_callback( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, + igraph_int_t max_cycle_length, + igraph_cycle_handler_t *callback, + void *arg); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function searches for all simple cycles using Johnson's cycle +detection algorithm, and calls a function for each. A simple cycle is a +cycle (i.e. closed path) without repeated vertices. + + Reference: + + Johnson DB: Finding all the elementary circuits of a directed graph. +SIAM J. Comput. 4(1):77-84. https://doi.org/10.1137/0204007 +(https://doi.org/10.1137/0204007) + + *Arguments:. * + +‘graph’: + The graph to search for + +‘mode’: + A constant specifying how edge directions are considered in + directed graphs. Valid modes are: ‘IGRAPH_OUT’, follows edge + directions; ‘IGRAPH_IN’, follows the opposite directions; and + ‘IGRAPH_ALL’, ignores edge directions. This argument is ignored + for undirected graphs. + +‘min_cycle_length’: + Limit the minimum length of cycles to search for. Pass a negative + value to search for all cycles. + +‘max_cycle_length’: + Limit the maximum length of cycles to search for. Pass a negative + value to search for all cycles. + +‘callback’: + A function to call for each cycle that is found. See also + ‘igraph_cycle_handler_t’ (*note igraph_cycle_handler_t --- Type of + cycle handler functions_::) + +‘arg’: + This parameter will be passed to ‘callback’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_simple_cycles()’ (*note igraph_simple_cycles --- Finds all + simple cycles_::) to store the found cycles; ‘igraph_find_cycle()’ + (*note igraph_find_cycle --- Finds a single cycle in the graph_::) + to find a single cycle; ‘igraph_fundamental_cycles()’ (*note + igraph_fundamental_cycles --- Finds a fundamental cycle basis_::) + and igraph_minimum_cycle_basis() to find a cycle basis, a compact + representation of the cycle structure of the graph. + + +File: igraph-docs.info, Node: igraph_cycle_handler_t --- Type of cycle handler functions_, Prev: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_, Up: Finding cycles + +18.1.4 igraph_cycle_handler_t -- Type of cycle handler functions. +----------------------------------------------------------------- + + + typedef igraph_error_t igraph_cycle_handler_t( + const igraph_vector_int_t *vertices, + const igraph_vector_int_t *edges, + void *arg); + + Callback type, called by ‘igraph_simple_cycles_callback()’ (*note +igraph_simple_cycles_callback --- Finds all simple cycles [callback +version]_::) when a cycle is found. + + *Arguments:. * + +‘vertices’: + The vertices of the current cycle. Must not be modified. + +‘edges’: + The edges of the current cycle. Must not be modified. + +‘arg’: + The extra parameter passed to ‘igraph_simple_cycles_callback()’ + (*note igraph_simple_cycles_callback --- Finds all simple cycles + [callback version]_::) + + *Returns:. * + +‘’ + Error code; ‘IGRAPH_SUCCESS’ to continue the search or + ‘IGRAPH_STOP’ to stop the search without signaling an error. + + +File: igraph-docs.info, Node: Acyclic graphs and feedback sets, Next: Eulerian cycles and paths, Prev: Finding cycles, Up: Graph cycles + +18.2 Acyclic graphs and feedback sets +===================================== + +* Menu: + +* igraph_is_dag -- Checks whether a graph is a directed acyclic graph (DAG).: igraph_is_dag --- Checks whether a graph is a directed acyclic graph [DAG]_. +* igraph_is_acyclic -- Checks whether a graph is acyclic or not.: igraph_is_acyclic --- Checks whether a graph is acyclic or not_. +* igraph_topological_sorting -- Calculate a possible topological sorting of the graph.: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_. +* igraph_feedback_arc_set -- Feedback arc set of a graph using exact or heuristic methods.: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_. +* igraph_feedback_vertex_set -- Feedback vertex set of a graph.: igraph_feedback_vertex_set --- Feedback vertex set of a graph_. + + +File: igraph-docs.info, Node: igraph_is_dag --- Checks whether a graph is a directed acyclic graph [DAG]_, Next: igraph_is_acyclic --- Checks whether a graph is acyclic or not_, Up: Acyclic graphs and feedback sets + +18.2.1 igraph_is_dag -- Checks whether a graph is a directed acyclic graph (DAG). +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_is_dag(const igraph_t* graph, igraph_bool_t *res); + + A directed acyclic graph (DAG) is a directed graph with no cycles. + + This function returns ‘false’ for undirected graphs. + + The return value of this function is cached in the graph itself; +calling the function multiple times with no modifications to the graph +in between will return a cached value in O(1) time. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean constant, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), where |V| and |E| are the number of +vertices and edges in the original input graph. + + *See also:. * + +‘’ + ‘igraph_topological_sorting()’ (*note igraph_topological_sorting + --- Calculate a possible topological sorting of the graph_::) to + get a possible topological sorting of a DAG. + + +File: igraph-docs.info, Node: igraph_is_acyclic --- Checks whether a graph is acyclic or not_, Next: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_, Prev: igraph_is_dag --- Checks whether a graph is a directed acyclic graph [DAG]_, Up: Acyclic graphs and feedback sets + +18.2.2 igraph_is_acyclic -- Checks whether a graph is acyclic or not. +--------------------------------------------------------------------- + + + igraph_error_t igraph_is_acyclic(const igraph_t *graph, igraph_bool_t *res); + + This function checks whether a graph has any cycles. Edge directions +are considered, i.e. in directed graphs, only directed cycles are +searched for. + + The result of this function is cached in the graph itself; calling +the function multiple times with no modifications to the graph in +between will return a cached value in O(1) time. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a boolean constant, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_find_cycle()’ (*note igraph_find_cycle --- Finds a single + cycle in the graph_::) to find a cycle that demonstrates that the + graph is not acyclic; ‘igraph_is_forest()’ (*note igraph_is_forest + --- Decides whether the graph is a forest_::) to look for + undirected cycles even in directed graphs; ‘igraph_is_dag()’ (*note + igraph_is_dag --- Checks whether a graph is a directed acyclic + graph [DAG]_::) to test specifically for directed acyclic graphs. + + Time complexity: O(|V|+|E|), where |V| and |E| are the number of +vertices and edges in the original input graph. + + +File: igraph-docs.info, Node: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_, Next: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_, Prev: igraph_is_acyclic --- Checks whether a graph is acyclic or not_, Up: Acyclic graphs and feedback sets + +18.2.3 igraph_topological_sorting -- Calculate a possible topological sorting of the graph. +------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_topological_sorting( + const igraph_t* graph, igraph_vector_int_t *res, igraph_neimode_t mode); + + A topological sorting of a directed acyclic graph (DAG) is a linear +ordering of its vertices where each vertex comes before all nodes to +which it has edges. Every DAG has at least one topological sort, and +may have many. This function returns one possible topological sort +among them. If the graph contains any cycles that are not self-loops, +an error is raised. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a vector, the result will be stored here. It will be + resized if needed. + +‘mode’: + Specifies how to use the direction of the edges. For ‘IGRAPH_OUT’, + the sorting order ensures that each vertex comes before all + vertices to which it has edges, so vertices with no incoming edges + go first. For ‘IGRAPH_IN’, it is quite the opposite: each vertex + comes before all vertices from which it receives edges. Vertices + with no outgoing edges go first. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), where |V| and |E| are the number of +vertices and edges in the original input graph. + + *See also:. * + +‘’ + ‘igraph_is_dag()’ (*note igraph_is_dag --- Checks whether a graph + is a directed acyclic graph [DAG]_::) if you are only interested in + whether a given graph is a DAG or not, or + ‘igraph_feedback_arc_set()’ (*note igraph_feedback_arc_set --- + Feedback arc set of a graph using exact or heuristic methods_::) to + find a set of edges whose removal makes the graph acyclic. + + * File examples/simple/igraph_topological_sorting.c* + + +File: igraph-docs.info, Node: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_, Next: igraph_feedback_vertex_set --- Feedback vertex set of a graph_, Prev: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_, Up: Acyclic graphs and feedback sets + +18.2.4 igraph_feedback_arc_set -- Feedback arc set of a graph using exact or heuristic methods. +----------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_feedback_arc_set( + const igraph_t *graph, + igraph_vector_int_t *result, + const igraph_vector_t *weights, + igraph_fas_algorithm_t algo); + + A feedback arc set is a set of edges whose removal makes the graph +acyclic. We are usually interested in _minimum_ feedback arc sets, i.e. +sets of edges whose total weight is the smallest among all the feedback +arc sets. + + For undirected graphs, the solution is simple: one has to find a +maximum weight spanning tree and then remove all the edges not in the +spanning tree. For directed graphs, this is an NP-complete problem, and +various heuristics are usually used to find an approximate solution to +the problem. This function implements both exact methods and +heuristics, selectable with the ‘algo’ parameter. + + References: + + Eades P, Lin X and Smyth WF: A fast and effective heuristic for the +feedback arc set problem. Information Processing Letters 47(6), pp +319-323 (1993). https://doi.org/10.1016/0020-0190(93)90079-O +(https://doi.org/10.1016/0020-0190(93)90079-O) + + Baharev A, Hermann S, Arnold N and Tobias A: An Exact Method for the +Minimum Feedback Arc Set Problem. ACM Journal of Experimental +Algorithmics 26, 1-28 (2021). https://doi.org/10.1145/3446429 +(https://doi.org/10.1145/3446429). + + *Arguments:. * + +‘graph’: + The graph object. + +‘result’: + An initialized vector, the result will be written here. + +‘weights’: + Weight vector or ‘NULL’ if no weights are specified. + +‘algo’: + The algorithm to use to solve the problem if the graph is directed. + Possible values: + + ‘IGRAPH_FAS_EXACT_IP’ + Finds a _minimum_ feedback arc set using integer programming + (IP), automatically selecting the best method of this type + (currently always ‘IGRAPH_FAS_EXACT_IP_CG’). The complexity + is of course at least exponential. + + ‘IGRAPH_FAS_EXACT_IP_CG’ + This is an integer programming approach based on a minimum set + cover formulation and using incremental constraint generation + (CG), added in igraph 0.10.14. We minimize ‘sum_e w_e b_e’ + subject to the constraints ‘sum_e c_e b_e >= 1’ for all cycles + ‘c’. Here ‘w_e’ is the weight of edge ‘e’, ‘b_e’ is a binary + variable (0 or 1) indicating whether edge ‘e’ is in the + feedback set, and ‘c_e’ is a binary coefficient indicating + whether edge ‘e’ is in cycle ‘c’. The constraint expresses + the requirement that all cycles must intersect with (be broken + by) the edge set represented by ‘b’. Since there are a very + large number of cycles in the graph, constraints are generated + incrementally, iteratively adding some cycles that do not + intersect with the current edge set ‘b’, then solving for ‘b’ + again, until finally no unbroken cycles remain. This approach + is similar to that described by Baharev et al (though with a + simpler cycle generation scheme), and to what is implemented + by SageMath's. ‘feedback_edge_set’ function. + + ‘IGRAPH_FAS_EXACT_IP_TI’ + This is another integer programming approach based on finding + a maximum (largest weight) edge set that adheres to a + topological order. It uses the common formulation through + triangle inequalities (TI), see Section 3.1 of Baharev et al + (2021) for an overview. This method was used before igraph + 0.10.14, and is typically much slower than + ‘IGRAPH_FAS_EXACT_IP_CG’. + + ‘IGRAPH_FAS_APPROX_EADES’ + Finds a feedback arc set using the heuristic of Eades, Lin and + Smyth (1993). This is guaranteed to be smaller than |E|/2 - + |V|/6, and it is linear in the number of edges (i.e. O(|E|)) + to compute. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EINVAL’ if an unknown method was specified or + the weight vector is invalid. + + * File examples/simple/igraph_feedback_arc_set.c* + + * File examples/simple/igraph_feedback_arc_set_ip.c* + +Time complexity: depends on ‘algo’, see the time complexities there. + + +File: igraph-docs.info, Node: igraph_feedback_vertex_set --- Feedback vertex set of a graph_, Prev: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_, Up: Acyclic graphs and feedback sets + +18.2.5 igraph_feedback_vertex_set -- Feedback vertex set of a graph. +-------------------------------------------------------------------- + + + igraph_error_t igraph_feedback_vertex_set( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *vertex_weights, igraph_fvs_algorithm_t algo); + + A feedback vertex set is a set of vertices whose removal makes the +graph acyclic. Finding a _minimum_ feedback vertex set is an +NP-complete problem, both on directed and undirected graphs. + + *Arguments:. * + +‘graph’: + The graph. + +‘result’: + An initialized vector, the result will be written here. + +‘vertex_weights’: + Vertex weight vector or ‘NULL’ if no weights are specified. + +‘algo’: + Algorithm to use. Possible values: + + ‘IGRAPH_FVS_EXACT_IP’ + Finds a _miniumum_ feedback vertex set using integer + programming (IP). The complexity is of course at least + exponential. Currently this method uses an approach analogous + to that of the ‘IGRAPH_FAS_EXACT_IP_CG’ algorithm of + ‘igraph_feedback_arc_set()’ (*note igraph_feedback_arc_set --- + Feedback arc set of a graph using exact or heuristic + methods_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on ‘algo’, see the time complexities there. + + +File: igraph-docs.info, Node: Eulerian cycles and paths, Next: Cycle bases, Prev: Acyclic graphs and feedback sets, Up: Graph cycles + +18.3 Eulerian cycles and paths +============================== + +These functions calculate whether an Eulerian path or cycle exists and +if so, can find them. + +* Menu: + +* igraph_is_eulerian -- Checks whether an Eulerian path or cycle exists.: igraph_is_eulerian --- Checks whether an Eulerian path or cycle exists_. +* igraph_eulerian_cycle -- Finds an Eulerian cycle.: igraph_eulerian_cycle --- Finds an Eulerian cycle_. +* igraph_eulerian_path -- Finds an Eulerian path.: igraph_eulerian_path --- Finds an Eulerian path_. + + +File: igraph-docs.info, Node: igraph_is_eulerian --- Checks whether an Eulerian path or cycle exists_, Next: igraph_eulerian_cycle --- Finds an Eulerian cycle_, Up: Eulerian cycles and paths + +18.3.1 igraph_is_eulerian -- Checks whether an Eulerian path or cycle exists. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_is_eulerian(const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle); + + An Eulerian path traverses each edge of the graph precisely once. A +closed Eulerian path is referred to as an Eulerian cycle. + + *Arguments:. * + +‘graph’: + The graph object. + +‘has_path’: + Pointer to a Boolean, will be set to true if an Eulerian path + exists. Must not be ‘NULL’. + +‘has_cycle’: + Pointer to a Boolean, will be set to true if an Eulerian cycle + exists. Must not be ‘NULL’. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’, not enough memory for temporary data. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_eulerian_cycle --- Finds an Eulerian cycle_, Next: igraph_eulerian_path --- Finds an Eulerian path_, Prev: igraph_is_eulerian --- Checks whether an Eulerian path or cycle exists_, Up: Eulerian cycles and paths + +18.3.2 igraph_eulerian_cycle -- Finds an Eulerian cycle. +-------------------------------------------------------- + + + igraph_error_t igraph_eulerian_cycle( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res); + + Finds an Eulerian cycle, if it exists. An Eulerian cycle is a closed +path that traverses each edge precisely once. + + If the graph has no edges, a zero-length cycle is returned. + + This function uses Hierholzer's algorithm. + + *Arguments:. * + +‘graph’: + The graph object. + +‘edge_res’: + Pointer to an initialised vector. The indices of edges belonging + to the cycle will be stored here. May be ‘NULL’ if it is not + needed by the caller. + +‘vertex_res’: + Pointer to an initialised vector. The indices of vertices + belonging to the cycle will be stored here. The first and last + vertex in the vector will be the same. May be ‘NULL’ if it is not + needed by the caller. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_ENOSOL’ + graph does not have an Eulerian cycle. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_eulerian_path --- Finds an Eulerian path_, Prev: igraph_eulerian_cycle --- Finds an Eulerian cycle_, Up: Eulerian cycles and paths + +18.3.3 igraph_eulerian_path -- Finds an Eulerian path. +------------------------------------------------------ + + + igraph_error_t igraph_eulerian_path( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res); + + Finds an Eulerian path, if it exists. An Eulerian path traverses +each edge precisely once. + + If the graph has no edges, a zero-length path is returned. + + This function uses Hierholzer's algorithm. + + *Arguments:. * + +‘graph’: + The graph object. + +‘edge_res’: + Pointer to an initialised vector. The indices of edges belonging + to the path will be stored here. May be ‘NULL’ if it is not needed + by the caller. + +‘vertex_res’: + Pointer to an initialised vector. The indices of vertices + belonging to the path will be stored here. May be ‘NULL’ if it is + not needed by the caller. + + *Returns:. * + +‘’ + Error code: + + ‘IGRAPH_ENOMEM’ + not enough memory for temporary data. + + ‘IGRAPH_ENOSOL’ + graph does not have an Eulerian path. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: Cycle bases, Prev: Eulerian cycles and paths, Up: Graph cycles + +18.4 Cycle bases +================ + +* Menu: + +* igraph_fundamental_cycles -- Finds a fundamental cycle basis.: igraph_fundamental_cycles --- Finds a fundamental cycle basis_. +* igraph_minimum_cycle_basis -- Computes a minimum weight cycle basis.: igraph_minimum_cycle_basis --- Computes a minimum weight cycle basis_. + + +File: igraph-docs.info, Node: igraph_fundamental_cycles --- Finds a fundamental cycle basis_, Next: igraph_minimum_cycle_basis --- Computes a minimum weight cycle basis_, Up: Cycle bases + +18.4.1 igraph_fundamental_cycles -- Finds a fundamental cycle basis. +-------------------------------------------------------------------- + + + igraph_error_t igraph_fundamental_cycles( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_int_t start_vid, igraph_real_t bfs_cutoff); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function computes a fundamental cycle basis associated with a +breadth-first search tree of the graph. + + Edge directions are ignored. Multi-edges and self-loops are +supported. + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Currently unused. + +‘result’: + An initialized integer vector list. The result will be stored + here, each vector containing the edge IDs of a basis element. + +‘start_vid’: + If negative, a complete fundamental cycle basis is returned. If a + vertex ID, the fundamental cycles associated with the BFS tree + rooted in that vertex will be returned, only for the weakly + connected component containing that vertex. + +‘bfs_cutoff’: + If negative, a complete cycle basis is returned. Otherwise, only + cycles of length ‘2*bfs_cutoff + 1’ or shorter are included. + ‘bfs_cutoff’ is used to limit the depth of the BFS tree when + searching for cycle edges. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_minimum_cycle_basis()’ (*note igraph_minimum_cycle_basis + --- Computes a minimum weight cycle basis_::) + + Time complexity: O(|V| + |E|). + + +File: igraph-docs.info, Node: igraph_minimum_cycle_basis --- Computes a minimum weight cycle basis_, Prev: igraph_fundamental_cycles --- Finds a fundamental cycle basis_, Up: Cycle bases + +18.4.2 igraph_minimum_cycle_basis -- Computes a minimum weight cycle basis. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_minimum_cycle_basis( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_real_t bfs_cutoff, + igraph_bool_t complete, igraph_bool_t use_cycle_order); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function computes a minimum weight cycle basis of a graph. +Currently, a modified version of Horton's algorithm is used that allows +for cutoffs. + + Edge directions are ignored. Multi-edges and self-loops are +supported. + + References: + + Horton, J. D. (1987) A polynomial-time algorithm to find the shortest +cycle basis of a graph, SIAM Journal on Computing, 16 (2): 358-366. +https://doi.org/10.1137%2F0216026 (https://doi.org/10.1137%2F0216026) + + *Arguments:. * + +‘graph’: + The graph object. + +‘weights’: + Currently unused. + +‘result’: + An initialized integer vector list, the elements of the cycle basis + will be stored here as vectors of edge IDs. + +‘bfs_cutoff’: + If negative, an exact minimum cycle basis is returned. Otherwise + only those cycles in the result will be part of some minimum cycle + basis which are of size ‘2*bfs_cutoff + 1’ or smaller. Cycles + longer than this limit may not be of the smallest possible size. + ‘bfs_cutoff’ is used to limit the depth of the BFS tree when + computing candidate cycles. Specifying a bfs_cutoff can speed up + the computation substantially. + +‘complete’: + Boolean value. Used only when ‘bfs_cutoff’ was given. If true, a + complete basis is returned. If false, only cycles not greater than + ‘2*bfs_cutoff + 1’ are returned. This may save computation time, + however, the result will not span the entire cycle space. + +‘use_cycle_order’: + If true, each cycle is returned in natural order: the edge IDs will + appear ordered along the cycle. This comes at a small performance + cost. If false, no guarantees are given about the ordering of edge + IDs within cycles. This parameter exists solely to control + performance tradeoffs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_fundamental_cycles()’ (*note igraph_fundamental_cycles --- + Finds a fundamental cycle basis_::) + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Cliques and independent vertex sets, Next: Graph motifs; dyad census and triad census, Prev: Graph cycles, Up: Top + +19 Cliques and independent vertex sets +************************************** + +These functions calculate various graph properties related to cliques +and independent vertex sets. + +* Menu: + +* Cliques:: +* Weighted cliques:: +* Independent vertex sets:: + + +File: igraph-docs.info, Node: Cliques, Next: Weighted cliques, Up: Cliques and independent vertex sets + +19.1 Cliques +============ + +* Menu: + +* igraph_is_complete -- Decides whether the graph is complete.: igraph_is_complete --- Decides whether the graph is complete_. +* igraph_is_clique --- Does a set of vertices form a clique?:: +* igraph_cliques -- Finds all or some cliques in a graph.: igraph_cliques --- Finds all or some cliques in a graph_. +* igraph_clique_size_hist -- Counts cliques of each size in the graph.: igraph_clique_size_hist --- Counts cliques of each size in the graph_. +* igraph_cliques_callback -- Calls a function for each clique in the graph.: igraph_cliques_callback --- Calls a function for each clique in the graph_. +* igraph_clique_handler_t -- Type of clique handler functions.: igraph_clique_handler_t --- Type of clique handler functions_. +* igraph_largest_cliques -- Finds the largest clique(s) in a graph.: igraph_largest_cliques --- Finds the largest clique[s] in a graph_. +* igraph_maximal_cliques -- Finds all maximal cliques in a graph.: igraph_maximal_cliques --- Finds all maximal cliques in a graph_. +* igraph_maximal_cliques_count -- Count the number of maximal cliques in a graph.: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_. +* igraph_maximal_cliques_file -- Find maximal cliques and write them to a file.: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_. +* igraph_maximal_cliques_subset -- Maximal cliques for a subset of initial vertices.: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_. +* igraph_maximal_cliques_hist -- Counts the number of maximal cliques of each size in a graph.: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_. +* igraph_maximal_cliques_callback -- Finds maximal cliques in a graph and calls a function for each one.: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_. +* igraph_clique_number -- Finds the clique number of the graph.: igraph_clique_number --- Finds the clique number of the graph_. + + +File: igraph-docs.info, Node: igraph_is_complete --- Decides whether the graph is complete_, Next: igraph_is_clique --- Does a set of vertices form a clique?, Up: Cliques + +19.1.1 igraph_is_complete -- Decides whether the graph is complete. +------------------------------------------------------------------- + + + igraph_error_t igraph_is_complete(const igraph_t *graph, igraph_bool_t *res); + + A graph is considered complete if all pairs of different vertices are +adjacent. + + The null graph and the singleton graph are considered complete. + + *Arguments:. * + +‘graph’: + The graph object to analyze. + +‘res’: + Pointer to a Boolean variable, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V| + |E|) at worst. + + +File: igraph-docs.info, Node: igraph_is_clique --- Does a set of vertices form a clique?, Next: igraph_cliques --- Finds all or some cliques in a graph_, Prev: igraph_is_complete --- Decides whether the graph is complete_, Up: Cliques + +19.1.2 igraph_is_clique -- Does a set of vertices form a clique? +---------------------------------------------------------------- + + + igraph_error_t igraph_is_clique(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t directed, igraph_bool_t *res); + + Tests if all pairs within a set of vertices are adjacent, i.e. +whether they form a clique. An empty set and singleton set are +considered to be a clique. + + *Arguments:. * + +‘graph’: + The input graph. + +‘candidate’: + The vertex set to test for being a clique. + +‘directed’: + Whether to take edge directions into account in directed graphs. + +‘res’: + The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_complete()’ (*note igraph_is_complete --- Decides + whether the graph is complete_::) to test if a graph is complete; + ‘igraph_is_independent_vertex_set()’ (*note + igraph_is_independent_vertex_set --- Does a set of vertices form an + independent set?::) to test for independent vertex sets; + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::), ‘igraph_maximal_cliques()’ (*note + igraph_maximal_cliques --- Finds all maximal cliques in a graph_::) + and ‘igraph_largest_cliques()’ (*note igraph_largest_cliques --- + Finds the largest clique[s] in a graph_::) to find cliques. + + Time complexity: O(n^2 log(d)) where n is the number of vertices in +the candidate set and d is the typical vertex degree. + + +File: igraph-docs.info, Node: igraph_cliques --- Finds all or some cliques in a graph_, Next: igraph_clique_size_hist --- Counts cliques of each size in the graph_, Prev: igraph_is_clique --- Does a set of vertices form a clique?, Up: Cliques + +19.1.3 igraph_cliques -- Finds all or some cliques in a graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_cliques( + const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + Cliques are fully connected subgraphs of a graph. + + If you are only interested in the size of the largest clique in the +graph, use ‘igraph_clique_number()’ (*note igraph_clique_number --- +Finds the clique number of the graph_::) instead. + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + +‘min_size’: + Integer specifying the minimum size of the cliques to be returned. + If negative or zero, no lower bound will be used. + +‘max_size’: + Integer specifying the maximum size of the cliques to be returned. + If negative or zero, no upper bound will be used. + +‘max_results’: + At most this many cliques will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_largest_cliques()’ (*note igraph_largest_cliques --- Finds + the largest clique[s] in a graph_::) and ‘igraph_clique_number()’ + (*note igraph_clique_number --- Finds the clique number of the + graph_::). + + Time complexity: Exponential + + * File examples/simple/igraph_cliques.c* + + +File: igraph-docs.info, Node: igraph_clique_size_hist --- Counts cliques of each size in the graph_, Next: igraph_cliques_callback --- Calls a function for each clique in the graph_, Prev: igraph_cliques --- Finds all or some cliques in a graph_, Up: Cliques + +19.1.4 igraph_clique_size_hist -- Counts cliques of each size in the graph. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_clique_size_hist(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size); + + Cliques are fully connected subgraphs of a graph. + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘hist’: + Pointer to an initialized vector. The result will be stored here. + The first element will store the number of size-1 cliques, the + second element the number of size-2 cliques, etc. For cliques + smaller than ‘min_size’, zero counts will be returned. + +‘min_size’: + Integer specifying the minimum size of the cliques to be returned. + If negative or zero, no lower bound will be used. + +‘max_size’: + Integer specifying the maximum size of the cliques to be returned. + If negative or zero, no upper bound will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::) and ‘igraph_cliques_callback()’ (*note + igraph_cliques_callback --- Calls a function for each clique in the + graph_::) + + Time complexity: Exponential + + +File: igraph-docs.info, Node: igraph_cliques_callback --- Calls a function for each clique in the graph_, Next: igraph_clique_handler_t --- Type of clique handler functions_, Prev: igraph_clique_size_hist --- Counts cliques of each size in the graph_, Up: Cliques + +19.1.5 igraph_cliques_callback -- Calls a function for each clique in the graph. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_cliques_callback(const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + + Cliques are fully connected subgraphs of a graph. This function +enumerates all cliques within the given size range and calls +‘cliquehandler_fn’ for each of them. The cliques are passed to the +callback function as a pointer to an ‘igraph_vector_int_t’ (*note About +igraph_vector_t objects::). Destroying and freeing this vector is left +up to the user. Use ‘igraph_vector_int_destroy()’ (*note +igraph_vector_destroy --- Destroys a vector object_::) to destroy it +first, then free it using ‘igraph_free()’ (*note igraph_free --- +Deallocates memory that was allocated by igraph functions_::). + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘min_size’: + Integer specifying the minimum size of the cliques to be returned. + If negative or zero, no lower bound will be used. + +‘max_size’: + Integer specifying the maximum size of the cliques to be returned. + If negative or zero, no upper bound will be used. + +‘cliquehandler_fn’: + Callback function to be called for each clique. See also + ‘igraph_clique_handler_t’ (*note igraph_clique_handler_t --- Type + of clique handler functions_::). + +‘arg’: + Extra argument to supply to ‘cliquehandler_fn’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::) + + Time complexity: Exponential + + +File: igraph-docs.info, Node: igraph_clique_handler_t --- Type of clique handler functions_, Next: igraph_largest_cliques --- Finds the largest clique[s] in a graph_, Prev: igraph_cliques_callback --- Calls a function for each clique in the graph_, Up: Cliques + +19.1.6 igraph_clique_handler_t -- Type of clique handler functions. +------------------------------------------------------------------- + + + typedef igraph_error_t igraph_clique_handler_t(const igraph_vector_int_t *clique, void *arg); + + Callback type, called when a clique was found. See the details at +the documentation of ‘igraph_cliques_callback()’ (*note +igraph_cliques_callback --- Calls a function for each clique in the +graph_::). + + *Arguments:. * + +‘clique’: + The current clique. The clique is owned by the clique search + routine. You do not need to destroy or free it if you do not want + to store it; however, if you want to hold on to it for a longer + period of time, you need to make a copy of it on your own and store + the copy itself. + +‘arg’: + This extra argument was passed to ‘igraph_cliques_callback()’ + (*note igraph_cliques_callback --- Calls a function for each clique + in the graph_::) when it was called. + + *Returns:. * + +‘’ + Error code; ‘IGRAPH_SUCCESS’ to continue the search or + ‘IGRAPH_STOP’ to stop the search without signaling an error. + + +File: igraph-docs.info, Node: igraph_largest_cliques --- Finds the largest clique[s] in a graph_, Next: igraph_maximal_cliques --- Finds all maximal cliques in a graph_, Prev: igraph_clique_handler_t --- Type of clique handler functions_, Up: Cliques + +19.1.7 igraph_largest_cliques -- Finds the largest clique(s) in a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_largest_cliques(const igraph_t *graph, igraph_vector_int_list_t *res); + + A clique is largest (quite intuitively) if there is no other clique +in the graph which contains more vertices. + + Note that this is not necessarily the same as a maximal clique, i.e. +the largest cliques are always maximal but a maximal clique is not +always largest. + + The current implementation of this function searches for maximal +cliques using ‘igraph_maximal_cliques_callback()’ (*note +igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and +calls a function for each one_::) and drops those that are not the +largest. + + The implementation of this function changed between igraph 0.5 and +0.6, so the order of the cliques and the order of vertices within the +cliques will almost surely be different between these two versions. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::), ‘igraph_maximal_cliques()’ (*note + igraph_maximal_cliques --- Finds all maximal cliques in a graph_::) + + Time complexity: O(3^(|V|/3)) worst case. + + +File: igraph-docs.info, Node: igraph_maximal_cliques --- Finds all maximal cliques in a graph_, Next: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_, Prev: igraph_largest_cliques --- Finds the largest clique[s] in a graph_, Up: Cliques + +19.1.8 igraph_maximal_cliques -- Finds all maximal cliques in a graph. +---------------------------------------------------------------------- + + + igraph_error_t igraph_maximal_cliques( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + This function lists maximal cliques within a size range, ignoring +edge directions. A clique is a subset of vertices in which all vertex +pairs are connected. A _maximal_ clique is a clique which is not a +strict subset of any larger clique. + + No guarantees are given about the order in which cliques are +returned. + + The current implementation uses a modified Bron-Kerbosch algorithm +due to Eppstein, Löffler and Strash. + + Reference: + + David Eppstein, Maarten Löffler, Darren Strash: Listing All Maximal +Cliques in Sparse Graphs in Near-Optimal Time. Algorithms and +Computation, Lecture Notes in Computer Science, volume 6506, pp 403-414 +(2010). https://doi.org/10.1007/978-3-642-17517-6_36 +(https://doi.org/10.1007/978-3-642-17517-6_36) +https://arxiv.org/abs/1006.5440 (https://arxiv.org/abs/1006.5440) + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘res’: + Pointer to list of integer vectors. The maximal cliques will be + returned here as vectors of vertex IDs. Note that vertices of a + clique may be returned in arbitrary order. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + +‘max_results’: + At most this many cliques will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_independent_vertex_sets()’ (*note + igraph_maximal_independent_vertex_sets --- Finds all maximal + independent vertex sets of a graph_::) to find maximal independent + sets, which are cliques of the complement graph; + ‘igraph_clique_number()’ (*note igraph_clique_number --- Finds the + clique number of the graph_::) to find the size of the largest + clique; ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or + some cliques in a graph_::) to find all cliques. + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs. + + * File examples/simple/igraph_maximal_cliques.c* + + +File: igraph-docs.info, Node: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_, Next: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_, Prev: igraph_maximal_cliques --- Finds all maximal cliques in a graph_, Up: Cliques + +19.1.9 igraph_maximal_cliques_count -- Count the number of maximal cliques in a graph. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_maximal_cliques_count( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t min_size, igraph_int_t max_size); + + See ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- +Finds all maximal cliques in a graph_::) for details. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘res’: + Pointer to an ‘igraph_int_t’; the number of maximal cliques will be + stored here. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::). + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs. + + * File examples/simple/igraph_maximal_cliques.c* + + +File: igraph-docs.info, Node: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_, Next: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_, Prev: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_, Up: Cliques + +19.1.10 igraph_maximal_cliques_file -- Find maximal cliques and write them to a file. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_maximal_cliques_file( + const igraph_t *graph, + FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + This function enumerates all maximal cliques within a size range and +writes them to file. See ‘igraph_maximal_cliques()’ (*note +igraph_maximal_cliques --- Finds all maximal cliques in a graph_::) for +details + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘outfile’: + Pointer to the output file, it should be writable. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + +‘max_results’: + At most this many cliques will be output. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::). + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs.* + + +File: igraph-docs.info, Node: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_, Next: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_, Prev: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_, Up: Cliques + +19.1.11 igraph_maximal_cliques_subset -- Maximal cliques for a subset of initial vertices. +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_maximal_cliques_subset( + const igraph_t *graph, const igraph_vector_int_t *subset, + igraph_vector_int_list_t *res, igraph_int_t *no, FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + This function enumerates all maximal cliques for a subset of initial +vertices and writes them to file. See ‘igraph_maximal_cliques()’ (*note +igraph_maximal_cliques --- Finds all maximal cliques in a graph_::) for +details. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘subset’: + Pointer to an ‘igraph_vector_int_t’ containing the subset of + initial vertices. + +‘res’: + Pointer to a list of integer vectors; the cliques will be stored + here. + +‘no’: + Pointer to an ‘igraph_int_t’; the number of maximal cliques will be + stored here. + +‘outfile’: + Pointer to an output file or ‘NULL’. When not ‘NULL’, the file + should be writable. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + +‘max_results’: + At most this many cliques will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::). + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs. + + +File: igraph-docs.info, Node: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_, Next: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_, Prev: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_, Up: Cliques + +19.1.12 igraph_maximal_cliques_hist -- Counts the number of maximal cliques of each size in a graph. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_maximal_cliques_hist( + const igraph_t *graph, + igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size); + + This function counts how many maximal cliques of each size are +present in the graph. Maximal cliques of size one are simply isolated +vertices. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘hist’: + Pointer to an initialized vector. The result will be stored here. + The first element will store the number of size-1 maximal cliques, + the second element the number of size-2 maximal cliques, etc. For + cliques smaller than ‘min_size’, zero counts will be returned. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::), ‘igraph_clique_size_hist()’ + (*note igraph_clique_size_hist --- Counts cliques of each size in + the graph_::). + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs. + + +File: igraph-docs.info, Node: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_, Next: igraph_clique_number --- Finds the clique number of the graph_, Prev: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_, Up: Cliques + +19.1.13 igraph_maximal_cliques_callback -- Finds maximal cliques in a graph and calls a function for each one. +-------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_maximal_cliques_callback( + const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + + This function enumerates all maximal cliques within the given size +range and calls ‘cliquehandler_fn’ for each of them. The cliques are +passed to the callback function as a pointer to an ‘igraph_vector_int_t’ +(*note About igraph_vector_t objects::). The vector is owned by the +maximal clique search routine so users are expected to make a copy of +the vector using ‘igraph_vector_int_init_copy()’ (*note +igraph_vector_init_copy --- Initializes a vector from another vector +object [constructor]_::) if they want to hold on to it. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored. + +‘cliquehandler_fn’: + Callback function to be called for each clique. See also + ‘igraph_clique_handler_t’ (*note igraph_clique_handler_t --- Type + of clique handler functions_::). + +‘arg’: + Extra argument to supply to ‘cliquehandler_fn’. + +‘min_size’: + Integer giving the minimum size of the cliques to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer giving the maximum size of the cliques to be returned. If + negative or zero, no upper bound will be used. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::), ‘igraph_cliques_callback()’ + (*note igraph_cliques_callback --- Calls a function for each clique + in the graph_::). + + Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy of +the graph, this is typically small for sparse graphs. + + +File: igraph-docs.info, Node: igraph_clique_number --- Finds the clique number of the graph_, Prev: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_, Up: Cliques + +19.1.14 igraph_clique_number -- Finds the clique number of the graph. +--------------------------------------------------------------------- + + + igraph_error_t igraph_clique_number(const igraph_t *graph, igraph_int_t *no); + + The clique number of a graph is the size of the largest clique. + + The current implementation of this function searches for maximal +cliques using ‘igraph_maximal_cliques_callback()’ (*note +igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and +calls a function for each one_::) and keeps track of the size of the +largest clique that was found. + + *Arguments:. * + +‘graph’: + The input graph. + +‘no’: + The clique number will be returned to the ‘igraph_int_t’ pointed by + this variable. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::), ‘igraph_largest_cliques()’ (*note + igraph_largest_cliques --- Finds the largest clique[s] in a + graph_::). + + Time complexity: O(3^(|V|/3)) worst case. + + +File: igraph-docs.info, Node: Weighted cliques, Next: Independent vertex sets, Prev: Cliques, Up: Cliques and independent vertex sets + +19.2 Weighted cliques +===================== + +* Menu: + +* igraph_weighted_cliques -- Finds all cliques in a given weight range in a vertex weighted graph.: igraph_weighted_cliques --- Finds all cliques in a given weight range in a vertex weighted graph_. +* igraph_largest_weighted_cliques -- Finds the largest weight clique(s) in a graph.: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_. +* igraph_weighted_clique_number -- Finds the weight of the largest weight clique in the graph.: igraph_weighted_clique_number --- Finds the weight of the largest weight clique in the graph_. + + +File: igraph-docs.info, Node: igraph_weighted_cliques --- Finds all cliques in a given weight range in a vertex weighted graph_, Next: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_, Up: Weighted cliques + +19.2.1 igraph_weighted_cliques -- Finds all cliques in a given weight range in a vertex weighted graph. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_weighted_cliques( + const igraph_t *graph, const igraph_vector_t *vertex_weights, + igraph_vector_int_list_t *res, + igraph_bool_t maximal, + igraph_real_t min_weight, igraph_real_t max_weight, + igraph_int_t max_results); + + Cliques are fully connected subgraphs of a graph. The weight of a +clique is the sum of the weights of individual vertices within the +clique. + + Only positive integer vertex weights are supported. + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘vertex_weights’: + A vector of vertex weights. The current implementation will + truncate all weights to their integer parts. You may pass ‘NULL’ + here to make each vertex have a weight of 1. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + +‘maximal’: + If true, only maximal cliques will be returned + +‘min_weight’: + Integer specifying the minimum weight of the cliques to be + returned. If negative or zero, no lower bound will be used. + +‘max_weight’: + Integer specifying the maximum weight of the cliques to be + returned. If negative or zero, no upper bound will be used. + +‘max_results’: + At most this many cliques will be recorded. If negative, or + ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- Constant for "do not + limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_cliques()’ (*note igraph_cliques --- Finds all or some + cliques in a graph_::), ‘igraph_maximal_cliques()’ (*note + igraph_maximal_cliques --- Finds all maximal cliques in a graph_::) + + Time complexity: Exponential + + +File: igraph-docs.info, Node: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_, Next: igraph_weighted_clique_number --- Finds the weight of the largest weight clique in the graph_, Prev: igraph_weighted_cliques --- Finds all cliques in a given weight range in a vertex weighted graph_, Up: Weighted cliques + +19.2.2 igraph_largest_weighted_cliques -- Finds the largest weight clique(s) in a graph. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res); + + The weight of a clique is the sum of the weights of its vertices. +This function finds the clique(s) having the largest weight in the +graph. + + Only positive integer vertex weights are supported. + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘vertex_weights’: + A vector of vertex weights. The current implementation will + truncate all weights to their integer parts. You may pass ‘NULL’ + here to make each vertex have a weight of 1. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_weighted_cliques()’ (*note igraph_weighted_cliques --- + Finds all cliques in a given weight range in a vertex weighted + graph_::), ‘igraph_weighted_clique_number()’ (*note + igraph_weighted_clique_number --- Finds the weight of the largest + weight clique in the graph_::), ‘igraph_largest_cliques()’ (*note + igraph_largest_cliques --- Finds the largest clique[s] in a + graph_::) + + Time complexity: TODO + + +File: igraph-docs.info, Node: igraph_weighted_clique_number --- Finds the weight of the largest weight clique in the graph_, Prev: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_, Up: Weighted cliques + +19.2.3 igraph_weighted_clique_number -- Finds the weight of the largest weight clique in the graph. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res); + + The weight of a clique is the sum of the weights of its vertices. +This function finds the weight of the largest weight clique. + + Only positive integer vertex weights are supported. + + The current implementation of this function uses version 1.21 of the +Cliquer library by Sampo Niskanen and Patric R. J. ÖstergÃ¥rd, +http://users.aalto.fi/~pat/cliquer.html +(http://users.aalto.fi/~pat/cliquer.html) + + *Arguments:. * + +‘graph’: + The input graph. + +‘vertex_weights’: + A vector of vertex weights. The current implementation will + truncate all weights to their integer parts. You may pass ‘NULL’ + here to make each vertex have a weight of 1. + +‘res’: + The largest weight will be returned to the ‘igraph_real_t’ pointed + to by this variable. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_weighted_cliques()’ (*note igraph_weighted_cliques --- + Finds all cliques in a given weight range in a vertex weighted + graph_::), ‘igraph_largest_weighted_cliques()’ (*note + igraph_largest_weighted_cliques --- Finds the largest weight + clique[s] in a graph_::), ‘igraph_clique_number()’ (*note + igraph_clique_number --- Finds the clique number of the graph_::) + + Time complexity: TODO + + +File: igraph-docs.info, Node: Independent vertex sets, Prev: Weighted cliques, Up: Cliques and independent vertex sets + +19.3 Independent vertex sets +============================ + +* Menu: + +* igraph_is_independent_vertex_set --- Does a set of vertices form an independent set?:: +* igraph_independent_vertex_sets -- Finds all independent vertex sets in a graph.: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_. +* igraph_largest_independent_vertex_sets -- Finds the largest independent vertex set(s) in a graph.: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_. +* igraph_maximal_independent_vertex_sets -- Finds all maximal independent vertex sets of a graph.: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_. +* igraph_independence_number -- Finds the independence number of the graph.: igraph_independence_number --- Finds the independence number of the graph_. + + +File: igraph-docs.info, Node: igraph_is_independent_vertex_set --- Does a set of vertices form an independent set?, Next: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_, Up: Independent vertex sets + +19.3.1 igraph_is_independent_vertex_set -- Does a set of vertices form an independent set? +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_is_independent_vertex_set(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t *res); + + Tests if no pairs within a set of vertices are adjacenct, i.e. +whether they form an independent set. An empty set and singleton set +are both considered to be an independent set. + + *Arguments:. * + +‘graph’: + The input graph. + +‘candidate’: + The vertex set to test for being an independent set. + +‘res’: + The result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_clique()’ (*note igraph_is_clique --- Does a set of + vertices form a clique?::) to test for cliques; + ‘igraph_independent_vertex_sets()’ (*note + igraph_independent_vertex_sets --- Finds all independent vertex + sets in a graph_::), ‘igraph_maximal_independent_vertex_sets()’ + (*note igraph_maximal_independent_vertex_sets --- Finds all maximal + independent vertex sets of a graph_::) and + ‘igraph_largest_independent_vertex_sets()’ (*note + igraph_largest_independent_vertex_sets --- Finds the largest + independent vertex set[s] in a graph_::) to find independent vertex + sets. + + Time complexity: O(n^2 log(d)) where n is the number of vertices in +the candidate set and d is the typical vertex degree. + + +File: igraph-docs.info, Node: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_, Next: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_, Prev: igraph_is_independent_vertex_set --- Does a set of vertices form an independent set?, Up: Independent vertex sets + +19.3.2 igraph_independent_vertex_sets -- Finds all independent vertex sets in a graph. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + A vertex set is considered independent if there are no edges between +them. + + If you are interested in the size of the largest independent vertex +set, use ‘igraph_independence_number()’ (*note +igraph_independence_number --- Finds the independence number of the +graph_::) instead. + + The current implementation was ported to igraph from the Very Nauty +Graph Library by Keith Briggs and uses the algorithm from the paper S. +Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for +generating all the maximal independent sets. SIAM J Computing, +6:505-517, 1977. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + +‘min_size’: + Integer specifying the minimum size of the sets to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer specifying the maximum size of the sets to be returned. If + negative or zero, no upper bound will be used. + +‘max_results’: + At most this many independent vertex sets will be recorded. If + negative, or ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- + Constant for "do not limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_largest_independent_vertex_sets()’ (*note + igraph_largest_independent_vertex_sets --- Finds the largest + independent vertex set[s] in a graph_::), + ‘igraph_independence_number()’ (*note igraph_independence_number + --- Finds the independence number of the graph_::). + + Time complexity: TODO + + * File examples/simple/igraph_independent_sets.c* + + +File: igraph-docs.info, Node: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_, Next: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_, Prev: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_, Up: Independent vertex sets + +19.3.3 igraph_largest_independent_vertex_sets -- Finds the largest independent vertex set(s) in a graph. +-------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_largest_independent_vertex_sets(const igraph_t *graph, + igraph_vector_int_list_t *res); + + An independent vertex set is largest if there is no other independent +vertex set with more vertices in the graph. + + The current implementation was ported to igraph from the Very Nauty +Graph Library by Keith Briggs and uses the algorithm from the paper S. +Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for +generating all the maximal independent sets. SIAM J Computing, +6:505-517, 1977. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_independent_vertex_sets()’ (*note + igraph_independent_vertex_sets --- Finds all independent vertex + sets in a graph_::), ‘igraph_maximal_independent_vertex_sets()’ + (*note igraph_maximal_independent_vertex_sets --- Finds all maximal + independent vertex sets of a graph_::). + + Time complexity: TODO + + +File: igraph-docs.info, Node: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_, Next: igraph_independence_number --- Finds the independence number of the graph_, Prev: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_, Up: Independent vertex sets + +19.3.4 igraph_maximal_independent_vertex_sets -- Finds all maximal independent vertex sets of a graph. +------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_maximal_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + + A maximal independent vertex set is an independent vertex set which +can't be extended any more by adding a new vertex to it. + + The algorithm used here is based on the following paper: S. +Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for +generating all the maximal independent sets. SIAM J Computing, +6:505-517, 1977. + + The implementation was originally written by Kevin O'Neill and +modified by K M Briggs in the Very Nauty Graph Library. I simply +re-wrote it to use igraph's data structures. + + If you are interested in the size of the largest independent vertex +set, use ‘igraph_independence_number()’ (*note +igraph_independence_number --- Finds the independence number of the +graph_::) instead. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized list of integer vectors. The cliques + will be stored here as vectors of vertex IDs. + +‘min_size’: + Integer specifying the minimum size of the sets to be returned. If + negative or zero, no lower bound will be used. + +‘max_size’: + Integer specifying the maximum size of the sets to be returned. If + negative or zero, no upper bound will be used. + +‘max_results’: + At most this many independent vertex sets will be recorded. If + negative, or ‘IGRAPH_UNLIMITED’ (*note IGRAPH_UNLIMITED --- + Constant for "do not limit results"_::), no limit is applied. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maximal_cliques()’ (*note igraph_maximal_cliques --- Finds + all maximal cliques in a graph_::), ‘igraph_independence_number()’ + (*note igraph_independence_number --- Finds the independence number + of the graph_::) + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_independence_number --- Finds the independence number of the graph_, Prev: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_, Up: Independent vertex sets + +19.3.5 igraph_independence_number -- Finds the independence number of the graph. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_independence_number(const igraph_t *graph, igraph_int_t *no); + + The independence number of a graph is the cardinality of the largest +independent vertex set. + + The current implementation was ported to igraph from the Very Nauty +Graph Library by Keith Briggs and uses the algorithm from the paper S. +Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for +generating all the maximal independent sets. SIAM J Computing, +6:505-517, 1977. + + *Arguments:. * + +‘graph’: + The input graph. + +‘no’: + The independence number will be returned to the ‘igraph_int_t’ + pointed by this variable. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_independent_vertex_sets()’ (*note + igraph_independent_vertex_sets --- Finds all independent vertex + sets in a graph_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Graph motifs; dyad census and triad census, Next: Graph isomorphism, Prev: Cliques and independent vertex sets, Up: Top + +20 Graph motifs, dyad census and triad census +********************************************* + +This section deals with functions which find small induced subgraphs in +a graph. These were first defined for subgraphs of two and three +vertices by Holland and Leinhardt, and named dyad census and triad +census. + +* Menu: + +* igraph_dyad_census -- Dyad census, as defined by Holland and Leinhardt.: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_. +* igraph_triad_census -- Triad census, as defined by Davis and Leinhardt.: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_. +* Finding triangles:: +* Graph motifs:: + + +File: igraph-docs.info, Node: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_, Next: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_, Up: Graph motifs; dyad census and triad census + +20.1 igraph_dyad_census -- Dyad census, as defined by Holland and Leinhardt. +============================================================================ + + + igraph_error_t igraph_dyad_census(const igraph_t *graph, igraph_real_t *mut, + igraph_real_t *asym, igraph_real_t *null); + + Dyad census means classifying each pair of vertices of a directed +graph into three categories: mutual (there is at least one edge from ‘a’ +to ‘b’ and also from ‘b’ to ‘a’); asymmetric (there is at least one edge +either from ‘a’ to ‘b’ or from ‘b’ to ‘a’, but not the other way) and +null (no edges between ‘a’ and ‘b’ in either direction). + + Holland, P.W. and Leinhardt, S. (1970). A Method for Detecting +Structure in Sociometric Data. American Journal of Sociology, 70, +492-513. + + *Arguments:. * + +‘graph’: + The input graph. For an undirected graph, there are no asymmetric + connections. + +‘mut’: + Pointer to a real, the number of mutual dyads is stored here. + +‘asym’: + Pointer to a real, the number of asymmetric dyads is stored here. + +‘null’: + Pointer to a real, the number of null dyads is stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_reciprocity()’ (*note igraph_reciprocity --- Calculates the + reciprocity of a directed graph_::), ‘igraph_triad_census()’ (*note + igraph_triad_census --- Triad census; as defined by Davis and + Leinhardt_::). + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + +File: igraph-docs.info, Node: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_, Next: Finding triangles, Prev: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_, Up: Graph motifs; dyad census and triad census + +20.2 igraph_triad_census -- Triad census, as defined by Davis and Leinhardt. +============================================================================ + + + igraph_error_t igraph_triad_census(const igraph_t *graph, igraph_vector_t *res); + + Calculating the triad census means classifying every triple of +vertices in a directed graph based on the type of pairwise connections +it contains, i.e. mutual, asymmetric or no connection. A triple can be +in one of 16 states, commonly described using Davis and Leinhardt's "MAN +labels". The ‘res’ vector will contain the counts of these in the +following order: + +‘ 0: 003’ + A, B, C, the empty graph. + +‘ 1: 012’ + A->B, C, a graph with a single directed edge. + +‘ 2: 102’ + A<->B, C, a graph with a mutual connection between two vertices. + +‘ 3: 021D’ + A<-B->C, the binary out-tree. + +‘ 4: 021U’ + A->B<-C, the binary in-tree. + +‘ 5: 021C’ + A->B->C, the directed line. + +‘ 6: 111D’ + A<->B<-C. + +‘ 7: 111U’ + A<->B->C. + +‘ 8: 030T’ + A->B<-C, A->C. + +‘ 9: 030C’ + A<-B<-C, A->C. + +‘10: 201’ + A<->B<->C. + +‘11: 120D’ + A<-B->C, A<->C. + +‘12: 120U’ + A->B<-C, A<->C. + +‘13: 120C’ + A->B->C, A<->C. + +‘14: 210’ + A->B<->C, A<->C. + +‘15: 300’ + A<->B<->C, A<->C, the complete graph. + + This function is intended for directed graphs. If the input is +undirected, a warning is shown, and undirected edges will be interpreted +as mutual. + + This function calls ‘igraph_motifs_randesu()’ (*note +igraph_motifs_randesu --- Count the number of motifs in a graph_::) +which is an implementation of the FANMOD motif finder tool, see +‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu --- Count the +number of motifs in a graph_::) for details. Note that the order of the +triads is not the same for ‘igraph_triad_census()’ (*note +igraph_triad_census --- Triad census; as defined by Davis and +Leinhardt_::) and ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu +--- Count the number of motifs in a graph_::). + + References: + + Davis, J.A. and Leinhardt, S. (1972). The Structure of Positive +Interpersonal Relations in Small Groups. In J. Berger (Ed.), +Sociological Theories in Progress, Volume 2, 218-251. Boston: Houghton +Mifflin. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized vector, the result is stored here in the + same order as given in the list above. Note that this order is + different than the one used by ‘igraph_motifs_randesu()’ (*note + igraph_motifs_randesu --- Count the number of motifs in a + graph_::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu --- Count + the number of motifs in a graph_::), ‘igraph_dyad_census()’ (*note + igraph_dyad_census --- Dyad census; as defined by Holland and + Leinhardt_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Finding triangles, Next: Graph motifs, Prev: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_, Up: Graph motifs; dyad census and triad census + +20.3 Finding triangles +====================== + +* Menu: + +* igraph_count_adjacent_triangles -- Count the number of triangles a vertex is part of.: igraph_count_adjacent_triangles --- Count the number of triangles a vertex is part of_. +* igraph_count_triangles -- Counts triangles in a graph.: igraph_count_triangles --- Counts triangles in a graph_. +* igraph_list_triangles -- Find all triangles in a graph.: igraph_list_triangles --- Find all triangles in a graph_. + + +File: igraph-docs.info, Node: igraph_count_adjacent_triangles --- Count the number of triangles a vertex is part of_, Next: igraph_count_triangles --- Counts triangles in a graph_, Up: Finding triangles + +20.3.1 igraph_count_adjacent_triangles -- Count the number of triangles a vertex is part of. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_count_adjacent_triangles(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids); + + *Arguments:. * + +‘graph’: + The input graph. Edge directions and multiplicities are ignored. + +‘res’: + Initiliazed vector, the results are stored here. + +‘vids’: + The vertices to perform the calculation for. + + *Returns:. * + +‘’ + Error mode. + + *See also:. * + +‘’ + ‘igraph_list_triangles()’ (*note igraph_list_triangles --- Find all + triangles in a graph_::) to list triangles, + ‘igraph_count_triangles()’ (*note igraph_count_triangles --- Counts + triangles in a graph_::) to count all triangles at once. + + Time complexity: O(d^2 n), d is the average vertex degree of the +queried vertices, n is their number. + + +File: igraph-docs.info, Node: igraph_count_triangles --- Counts triangles in a graph_, Next: igraph_list_triangles --- Find all triangles in a graph_, Prev: igraph_count_adjacent_triangles --- Count the number of triangles a vertex is part of_, Up: Finding triangles + +20.3.2 igraph_count_triangles -- Counts triangles in a graph. +------------------------------------------------------------- + + + igraph_error_t igraph_count_triangles(const igraph_t *graph, igraph_real_t *res); + + This function computes the total number of triangles, i.e. fully +connected vertex triples, in a graph. Edge directions, edge +multiplicities, and self-loops are ignored. + + *Arguments:. * + +‘graph’: + The graph object. Edge directions and multiplicites are ignored. + +‘res’: + Pointer to a real variable, the result will be stored here. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory for temporary data. + + *See also:. * + +‘’ + ‘igraph_list_triangles()’ (*note igraph_list_triangles --- Find all + triangles in a graph_::), ‘igraph_count_adjacent_triangles()’ + (*note igraph_count_adjacent_triangles --- Count the number of + triangles a vertex is part of_::), + ‘igraph_transitivity_undirected()’ (*note + igraph_transitivity_undirected --- Calculates the transitivity + [clustering coefficient] of a graph_::). + + Time complexity: O(|V|*d^2), |V| is the number of vertices in the +graph, d is the average node degree. + + +File: igraph-docs.info, Node: igraph_list_triangles --- Find all triangles in a graph_, Prev: igraph_count_triangles --- Counts triangles in a graph_, Up: Finding triangles + +20.3.3 igraph_list_triangles -- Find all triangles in a graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_list_triangles(const igraph_t *graph, + igraph_vector_int_t *res); + + The triangles are reported as a long list of vertex ID triplets. Use +the ‘int’ variant of ‘igraph_matrix_view_from_vector()’ (*note +igraph_matrix_view_from_vector --- Creates a matrix view that treats an +existing vector as a matrix_::) to create a matrix view into the vector +where each triangle is stored in a column of the matrix (see the +example). + + *Arguments:. * + +‘graph’: + The input graph, edge directions are ignored. Multiple edges are + ignored. + +‘res’: + Pointer to an initialized integer vector, the result is stored + here, in a long list of triples of vertex IDs. Each triple is a + triangle in the graph. Each triangle is listed exactly once. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_count_triangles()’ (*note igraph_count_triangles --- Counts + triangles in a graph_::) to count the triangles, + ‘igraph_count_adjacent_triangles()’ (*note + igraph_count_adjacent_triangles --- Count the number of triangles a + vertex is part of_::) to count the triangles a vertex participates + in, ‘igraph_transitivity_undirected()’ (*note + igraph_transitivity_undirected --- Calculates the transitivity + [clustering coefficient] of a graph_::) to compute the global + clustering coefficient. + + Time complexity: O(d^2 n), d is the average degree, n is the number +of vertices. + + * File examples/simple/igraph_list_triangles.c* + + +File: igraph-docs.info, Node: Graph motifs, Prev: Finding triangles, Up: Graph motifs; dyad census and triad census + +20.4 Graph motifs +================= + +* Menu: + +* igraph_motifs_randesu -- Count the number of motifs in a graph.: igraph_motifs_randesu --- Count the number of motifs in a graph_. +* igraph_motifs_randesu_no -- Count the total number of motifs in a graph.: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_. +* igraph_motifs_randesu_estimate -- Estimate the total number of motifs in a graph.: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_. +* igraph_motifs_randesu_callback -- Finds motifs in a graph and calls a function for each of them.: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_. +* igraph_motifs_handler_t -- Callback type for igraph_motifs_randesu_callback.: igraph_motifs_handler_t --- Callback type for igraph_motifs_randesu_callback_. + + +File: igraph-docs.info, Node: igraph_motifs_randesu --- Count the number of motifs in a graph_, Next: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_, Up: Graph motifs + +20.4.1 igraph_motifs_randesu -- Count the number of motifs in a graph. +---------------------------------------------------------------------- + + + igraph_error_t igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t size, const igraph_vector_t *cut_prob); + + Motifs are small weakly connected induced subgraphs of a given +structure in a graph. It is argued that the motif profile (i.e. the +number of different motifs in the graph) is characteristic for different +types of networks and network function is related to the motifs in the +graph. + + This function is able to find directed motifs of sizes three and four +and undirected motifs of sizes three to six (i.e. the number of +different subgraphs with three to six vertices in the network). + + In a big network the total number of motifs can be very large, so it +takes a lot of time to find all of them. In this case, a sampling +method can be used. This function is capable of doing sampling via the +‘cut_prob’ argument. This argument gives the probability that a branch +of the motif search tree will not be explored. See S. Wernicke and F. +Rasche: FANMOD: a tool for fast network motif detection, Bioinformatics +22(9), 1152-1153, 2006 for details. +https://doi.org/10.1093/bioinformatics/btl038 +(https://doi.org/10.1093/bioinformatics/btl038) + + Set the ‘cut_prob’ argument to a zero vector for finding all motifs. + + Directed motifs will be counted in directed graphs and undirected +motifs in undirected graphs. + + *Arguments:. * + +‘graph’: + The graph to find the motifs in. + +‘hist’: + The result of the computation, it gives the number of motifs found + for each isomorphism class. See ‘igraph_isoclass()’ (*note + igraph_isoclass --- Determine the isomorphism class of small + graphs_::) for help about isomorphism classes. Note that this + function does _not_ count isomorphism classes that are not + connected and will report NaN (more precisely ‘IGRAPH_NAN’) for + them. + +‘size’: + The size of the motifs to search for. For directed graphs, only 3 + and 4 are implemented, for undirected, 3 to 6. The limitation is + not in the motif finding code, but the graph isomorphism code. + +‘cut_prob’: + Vector of probabilities for cutting the search tree at a given + level. The first element is the first level, etc. To perform a + complete search and find all motifs, supply either an all-zero + vector of length ‘size’, or (since igraph 0.10.14) a ‘NULL’ + pointer. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_motifs_randesu_estimate()’ (*note + igraph_motifs_randesu_estimate --- Estimate the total number of + motifs in a graph_::) for estimating the number of motifs in a + graph, this can help to set the ‘cut_prob’ parameter; + ‘igraph_motifs_randesu_no()’ (*note igraph_motifs_randesu_no --- + Count the total number of motifs in a graph_::) to calculate the + total number of motifs of a given size in a graph; + ‘igraph_motifs_randesu_callback()’ (*note + igraph_motifs_randesu_callback --- Finds motifs in a graph and + calls a function for each of them_::) for calling a callback + function for every motif found; ‘igraph_subisomorphic_lad()’ (*note + igraph_subisomorphic_lad --- Check subgraph isomorphism with the + LAD algorithm::) for finding subgraphs on more than 4 (directed) or + 6 (undirected) vertices; ‘igraph_graph_count()’ (*note + igraph_graph_count --- The number of unlabelled graphs on the given + number of vertices_::) to find the number of graph on a given + number of vertices, i.e. the length of the ‘hist’ vector. + + Time complexity: TODO. + + * File examples/simple/igraph_motifs_randesu.c* + + +File: igraph-docs.info, Node: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_, Next: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_, Prev: igraph_motifs_randesu --- Count the number of motifs in a graph_, Up: Graph motifs + +20.4.2 igraph_motifs_randesu_no -- Count the total number of motifs in a graph. +------------------------------------------------------------------------------- + + + igraph_error_t igraph_motifs_randesu_no( + const igraph_t *graph, igraph_real_t *no, igraph_int_t size, + const igraph_vector_t *cut_prob + ); + + This function counts the total number of (weakly) connected induced +subgraphs on ‘size’ vertices, without assigning isomorphism classes to +them. Arbitrarily large motif sizes are supported. + + *Arguments:. * + +‘graph’: + The graph object to study. + +‘no’: + Pointer to an ‘igraph_real_t’, the result will be stored here. + Note that even though the result is an integer, we need to use + ‘igraph_real_t’ to avoid overflow when igraph is compiled with + 32-bit integers. + +‘size’: + The size of the motifs to count. + +‘cut_prob’: + Vector of probabilities for cutting the search tree at a given + level. The first element is the first level, etc. To perform a + complete search and find all connected subgraphs, supply either an + all-zero vector of length ‘size’, or (since igraph 0.10.14) a + ‘NULL’ pointer. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu --- Count + the number of motifs in a graph_::), + ‘igraph_motifs_randesu_estimate()’ (*note + igraph_motifs_randesu_estimate --- Estimate the total number of + motifs in a graph_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_, Next: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_, Prev: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_, Up: Graph motifs + +20.4.3 igraph_motifs_randesu_estimate -- Estimate the total number of motifs in a graph. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_real_t *est, + igraph_int_t size, const igraph_vector_t *cut_prob, + igraph_int_t sample_size, + const igraph_vector_int_t *parsample); + + This function estimates the total number of (weakly) connected +induced subgraphs on ‘size’ vertices. For example, an undirected +complete graph on ‘n’ vertices will have one motif of size ‘n’, and ‘n’ +motifs of ‘size’ ‘n - 1’. As another example, one triangle and a +separate vertex will have zero motifs of size four. + + This function is useful for large graphs for which it is not feasible +to count all connected subgraphs, as there are too many of them. + + The estimate is made by taking a sample of vertices and counting all +connected subgraphs in which these vertices are included. There is also +a ‘cut_prob’ parameter which gives the probabilities to cut a branch of +the search tree. + + *Arguments:. * + +‘graph’: + The graph object to study. + +‘est’: + Pointer to an ‘igraph_real_t’, the result will be stored here. + Note that even though the result is an integer, we need to use + ‘igraph_real_t’ to avoid overflow when igraph is compiled with + 32-bit integers. + +‘size’: + The size of the subgraphs to look for. + +‘cut_prob’: + Vector of probabilities for cutting the search tree at a given + level. The first element is the first level, etc. To perform a + complete search and find all motifs, supply either an all-zero + vector of length ‘size’, or (since igraph 0.10.14) a ‘NULL’ + pointer. + +‘sample_size’: + The number of vertices to use as the sample. This parameter is + only used if the ‘parsample’ argument is a null pointer. + +‘parsample’: + Either pointer to an initialized vector or a null pointer. If a + vector then the vertex IDs in the vector are used as a sample. If + a null pointer then the ‘sample_size’ argument is used to create a + sample of vertices drawn with uniform probability. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu --- Count + the number of motifs in a graph_::), ‘igraph_motifs_randesu_no()’ + (*note igraph_motifs_randesu_no --- Count the total number of + motifs in a graph_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_, Next: igraph_motifs_handler_t --- Callback type for igraph_motifs_randesu_callback_, Prev: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_, Up: Graph motifs + +20.4.4 igraph_motifs_randesu_callback -- Finds motifs in a graph and calls a function for each of them. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_motifs_randesu_callback( + const igraph_t *graph, + igraph_int_t size, const igraph_vector_t *cut_prob, + igraph_motifs_handler_t *callback, void* extra); + + Similarly to ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu +--- Count the number of motifs in a graph_::), this function is able to +find directed motifs of sizes three and four and undirected motifs of +sizes three to six (i.e. the number of different subgraphs with three +to six vertices in the network). However, instead of counting them, the +function will call a callback function for each motif found to allow +further tests or post-processing. + + The ‘cut_prob’ argument also allows sampling the motifs, just like +for ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu --- Count the +number of motifs in a graph_::). Set the ‘cut_prob’ argument to a zero +vector for finding all motifs. + + *Arguments:. * + +‘graph’: + The graph to find the motifs in. + +‘size’: + The size of the motifs to search for. Only three and four are + implemented currently. The limitation is not in the motif finding + code, but the graph isomorphism code. + +‘cut_prob’: + Vector of probabilities for cutting the search tree at a given + level. The first element is the first level, etc. To perform a + complete search and find all motifs, supply either an all-zero + vector of length ‘size’, or (since igraph 0.10.14) a ‘NULL’ + pointer. + +‘callback’: + A pointer to a function of type ‘igraph_motifs_handler_t’ (*note + igraph_motifs_handler_t --- Callback type for + igraph_motifs_randesu_callback_::). This function will be called + whenever a new motif is found. + +‘extra’: + Extra argument to pass to the callback function. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/igraph_motifs_randesu.c* + + +File: igraph-docs.info, Node: igraph_motifs_handler_t --- Callback type for igraph_motifs_randesu_callback_, Prev: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_, Up: Graph motifs + +20.4.5 igraph_motifs_handler_t -- Callback type for igraph_motifs_randesu_callback. +----------------------------------------------------------------------------------- + + + typedef igraph_error_t igraph_motifs_handler_t(const igraph_t *graph, + const igraph_vector_int_t *vids, + igraph_int_t isoclass, + void *extra); + + ‘igraph_motifs_randesu_callback()’ (*note +igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a +function for each of them_::) calls a specified callback function +whenever a new motif is found during a motif search. This callback +function must be of type ‘igraph_motifs_handler_t’. It has the +following arguments: + + *Arguments:. * + +‘graph’: + The graph that that algorithm is working on. Of course this must + not be modified. + +‘vids’: + The IDs of the vertices in the motif that has just been found. + This vector is owned by the motif search algorithm, so do not + modify or destroy it; make a copy of it if you need it later. + +‘isoclass’: + The isomorphism class of the motif that has just been found. Use + ‘igraph_graph_count()’ (*note igraph_graph_count --- The number of + unlabelled graphs on the given number of vertices_::) to find the + maximum possible isoclass for graphs of a given size. See + ‘igraph_isoclass’ (*note igraph_isoclass --- Determine the + isomorphism class of small graphs_::) and + ‘igraph_isoclass_subgraph’ (*note igraph_isoclass_subgraph --- The + isomorphism class of a subgraph of a graph_::) for more + information. + +‘extra’: + The extra argument that was passed to + ‘igraph_motifs_randesu_callback()’ (*note + igraph_motifs_randesu_callback --- Finds motifs in a graph and + calls a function for each of them_::). + + *Returns:. * + +‘’ + ‘IGRAPH_SUCCESS’ to continue the motif search, ‘IGRAPH_STOP’ to + stop the motif search and return to the caller normally. Any other + return value is interpreted as an igraph error code, which will + terminate the search and return the same error code to the caller. + + *See also:. * + +‘’ + ‘igraph_motifs_randesu_callback()’ (*note + igraph_motifs_randesu_callback --- Finds motifs in a graph and + calls a function for each of them_::) + + +File: igraph-docs.info, Node: Graph isomorphism, Next: Graph coloring, Prev: Graph motifs; dyad census and triad census, Up: Top + +21 Graph isomorphism +******************** + +* Menu: + +* The simple interface:: +* The BLISS algorithm:: +* The VF2 algorithm:: +* The LAD algorithm:: +* Functions for small graphs:: +* Utility functions:: + + +File: igraph-docs.info, Node: The simple interface, Next: The BLISS algorithm, Up: Graph isomorphism + +21.1 The simple interface +========================= + +igraph provides four set of functions to deal with graph isomorphism +problems. + + The ‘igraph_isomorphic()’ (*note igraph_isomorphic --- Are two graphs +isomorphic?::) and ‘igraph_subisomorphic()’ (*note igraph_subisomorphic +--- Decide subgraph isomorphism_::) functions make up the first set (in +addition with the ‘igraph_permute_vertices()’ (*note +igraph_permute_vertices --- Permute the vertices_::) function). These +functions choose the algorithm which is best for the supplied input +graph. (The choice is not very sophisticated though, see their +documentation for details.) + + The VF2 graph (and subgraph) isomorphism algorithm is implemented in +igraph, these functions are the second set. See +‘igraph_isomorphic_vf2()’ (*note igraph_isomorphic_vf2 --- Isomorphism +via VF2_::) and ‘igraph_subisomorphic_vf2()’ (*note +igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2::) +for starters. + + Functions for the Bliss algorithm constitute the third set, see +‘igraph_isomorphic_bliss()’ (*note igraph_isomorphic_bliss --- Graph +isomorphism via Bliss_::). + + Finally, the isomorphism classes of all directed graphs with three +and four vertices and all undirected graphs with 3-6 vertices are +precomputed and stored in igraph, so for these small graphs there is a +separate fast path in the code that does not use more complex, generic +isomorphism algorithms. + +* Menu: + +* igraph_isomorphic --- Are two graphs isomorphic?:: +* igraph_subisomorphic -- Decide subgraph isomorphism.: igraph_subisomorphic --- Decide subgraph isomorphism_. +* igraph_count_automorphisms -- Number of automorphisms of a graph.: igraph_count_automorphisms --- Number of automorphisms of a graph_. +* igraph_automorphism_group -- Automorphism group generators of a graph.: igraph_automorphism_group --- Automorphism group generators of a graph_. +* igraph_canonical_permutation -- Canonical permutation of a graph.: igraph_canonical_permutation --- Canonical permutation of a graph_. + + +File: igraph-docs.info, Node: igraph_isomorphic --- Are two graphs isomorphic?, Next: igraph_subisomorphic --- Decide subgraph isomorphism_, Up: The simple interface + +21.1.1 igraph_isomorphic -- Are two graphs isomorphic? +------------------------------------------------------ + + + igraph_error_t igraph_isomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); + + In simple terms, two graphs are isomorphic if they become +indistinguishable from each other once their vertex labels are removed +(rendering the vertices within each graph indistiguishable). More +precisely, two graphs are isomorphic if there is a one-to-one mapping +from the vertices of the first one to the vertices of the second such +that it transforms the edge set of the first graph into the edge set of +the second. This mapping is called an _isomorphism._ + + This function decides which graph isomorphism algorithm to be used +based on the input graphs. Right now it does the following: + + 1. If one graph is directed and the other undirected then an error is + triggered. + + 2. If one of the graphs has multi-edges then both graphs are + simplified and colorized using ‘igraph_simplify_and_colorize()’ + (*note igraph_simplify_and_colorize --- Simplify the graph and + compute self-loop and edge multiplicities_::) and sent to VF2. + + 3. If the two graphs does not have the same number of vertices and + edges it returns with ‘false’. + + 4. Otherwise, if the ‘igraph_isoclass()’ (*note igraph_isoclass --- + Determine the isomorphism class of small graphs_::) function + supports both graphs (which is true for directed graphs with 3 and + 4 vertices, and undirected graphs with 3-6 vertices), an O(1) + algorithm is used with precomputed data. + + 5. Otherwise Bliss is used, see ‘igraph_isomorphic_bliss()’ (*note + igraph_isomorphic_bliss --- Graph isomorphism via Bliss_::). + + Please call the VF2 and Bliss functions directly if you need +something more sophisticated, e.g. you need the isomorphic mapping. + + *Arguments:. * + +‘graph1’: + The first graph. + +‘graph2’: + The second graph. + +‘iso’: + Pointer to a Boolean variable, will be set to ‘true’ if the two + graphs are isomorphic, and ‘false’ otherwise. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_isoclass()’ (*note igraph_isoclass --- Determine the + isomorphism class of small graphs_::), ‘igraph_isoclass_subgraph()’ + (*note igraph_isoclass_subgraph --- The isomorphism class of a + subgraph of a graph_::), ‘igraph_isoclass_create()’ (*note + igraph_isoclass_create --- Creates a graph from the given + isomorphism class_::). + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_subisomorphic --- Decide subgraph isomorphism_, Next: igraph_count_automorphisms --- Number of automorphisms of a graph_, Prev: igraph_isomorphic --- Are two graphs isomorphic?, Up: The simple interface + +21.1.2 igraph_subisomorphic -- Decide subgraph isomorphism. +----------------------------------------------------------- + + + igraph_error_t igraph_subisomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); + + Check whether ‘graph2’ is isomorphic to a subgraph of ‘graph1’. +Currently this function just calls ‘igraph_subisomorphic_vf2()’ (*note +igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2::) +for all graphs. + + Currently this function does not support non-simple graphs. + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. This is + supposed to be the bigger graph. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph2’, or an error is triggered. This is supposed to be the + smaller graph. + +‘iso’: + Pointer to a boolean, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_count_automorphisms --- Number of automorphisms of a graph_, Next: igraph_automorphism_group --- Automorphism group generators of a graph_, Prev: igraph_subisomorphic --- Decide subgraph isomorphism_, Up: The simple interface + +21.1.3 igraph_count_automorphisms -- Number of automorphisms of a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_count_automorphisms( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_real_t *result + ); + + This function computes the number of automorphisms of a graph. Since +the number of automorphisms may be very large, the result is returned as +an ‘igraph_real_t’ instead of an integer. If the number of +automorphisms is larger than what can be represented in an +‘igraph_real_t’ and you need the exact number, use +‘igraph_count_automorphisms_bliss()’ (*note +igraph_count_automorphisms_bliss --- Number of automorphisms using +Bliss_::), which can return the number as a string. + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘result’: + Pointer to an ‘igraph_real_t’, the number of automorphisms will be + returned here. + + *Returns:. * + +‘’ + Error code. ‘IGRAPH_EOVERFLOW’ if the number of automorphisms is + too large to be represented in an ‘igraph_real_t’ . + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: igraph_automorphism_group --- Automorphism group generators of a graph_, Next: igraph_canonical_permutation --- Canonical permutation of a graph_, Prev: igraph_count_automorphisms --- Number of automorphisms of a graph_, Up: The simple interface + +21.1.4 igraph_automorphism_group -- Automorphism group generators of a graph. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_automorphism_group( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators + ); + + This function computes the generators of the automorphism group of a +graph. The generator set may not be minimal and may depend on the +specific parameters of the algorithm under the hood. The generators are +permutations represented using zero-based indexing. + + The current implementation uses BLISS behind the scenes and the +result may be dependent on the splitting heuristics. Use +‘igraph_automorphism_group_bliss()’ (*note +igraph_automorphism_group_bliss --- Automorphism group generators using +Bliss_::) if you want to fine-tune the splitting heuristics. + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘generators’: + Must be an initialized interger vector list. The generators of the + automorphism group will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: igraph_canonical_permutation --- Canonical permutation of a graph_, Prev: igraph_automorphism_group --- Automorphism group generators of a graph_, Up: The simple interface + +21.1.5 igraph_canonical_permutation -- Canonical permutation of a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_canonical_permutation( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling + ); + + This function computes the vertex permutation which transforms the +graph into a canonical form. Two graphs have the same canonical form if +and only if they are isomorphic. Use ‘igraph_is_same_graph()’ (*note +igraph_is_same_graph --- Are two graphs identical as labelled graphs?::) +to compare two canonical forms. + + The current implementation uses the BLISS isomorphism algorithms with +sensible defaults. Use ‘igraph_canonical_permutation_bliss()’ (*note +igraph_canonical_permutation_bliss --- Canonical permutation using +Bliss_::) to fine-tune the parameters. + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘labeling’: + Pointer to a vector, the result is stored here. The permutation + takes vertex 0 to the first element of the vector, vertex 1 to the + second, etc. The vector will be resized as needed. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_same_graph()’ (*note igraph_is_same_graph --- Are two + graphs identical as labelled graphs?::) + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: The BLISS algorithm, Next: The VF2 algorithm, Prev: The simple interface, Up: Graph isomorphism + +21.2 The BLISS algorithm +======================== + +Bliss is a successor of the famous NAUTY algorithm and implementation. +While using the same ideas in general, with better heuristics and data +structures Bliss outperforms NAUTY on most graphs. + + Bliss was developed and implemented by Tommi Junttila and Petteri +Kaski at Helsinki University of Technology, Finland. For more +information, see the Bliss homepage at +https://users.aalto.fi/~tjunttil/bliss/ +(https://users.aalto.fi/~tjunttil/bliss/) and the following publication: + + Tommi Junttila and Petteri Kaski: "Engineering an Efficient Canonical +Labeling Tool for Large and Sparse Graphs" In ALENEX 2007, pages +135-149, 2007 https://doi.org/10.1137/1.9781611972870.13 +(https://doi.org/10.1137/1.9781611972870.13) + + Tommi Junttila and Petteri Kaski: "Conflict Propagation and Component +Recursion for Canonical Labeling" in TAPAS 2011, pages 151-162, 2011. +https://doi.org/10.1007/978-3-642-19754-3_16 +(https://doi.org/10.1007/978-3-642-19754-3_16) + + Bliss works with both directed graphs and undirected graphs. It +supports graphs with self-loops, but not graphs with multi-edges. + + Bliss version 0.75 is included in igraph. + +* Menu: + +* igraph_bliss_sh_t -- Splitting heuristics for Bliss.: igraph_bliss_sh_t --- Splitting heuristics for Bliss_. +* igraph_bliss_info_t -- Information about a Bliss run.: igraph_bliss_info_t --- Information about a Bliss run_. +* igraph_isomorphic_bliss -- Graph isomorphism via Bliss.: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_. +* igraph_count_automorphisms_bliss -- Number of automorphisms using Bliss.: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_. +* igraph_automorphism_group_bliss -- Automorphism group generators using Bliss.: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_. +* igraph_canonical_permutation_bliss -- Canonical permutation using Bliss.: igraph_canonical_permutation_bliss --- Canonical permutation using Bliss_. + + +File: igraph-docs.info, Node: igraph_bliss_sh_t --- Splitting heuristics for Bliss_, Next: igraph_bliss_info_t --- Information about a Bliss run_, Up: The BLISS algorithm + +21.2.1 igraph_bliss_sh_t -- Splitting heuristics for Bliss. +----------------------------------------------------------- + + + typedef enum { IGRAPH_BLISS_F = 0, IGRAPH_BLISS_FL, + IGRAPH_BLISS_FS, IGRAPH_BLISS_FM, + IGRAPH_BLISS_FLM, IGRAPH_BLISS_FSM + } igraph_bliss_sh_t; + + ‘IGRAPH_BLISS_FL’ provides good performance for many graphs, and is a +reasonable default choice. ‘IGRAPH_BLISS_FSM’ is recommended for graphs +that have some combinatorial structure, and is the default of the Bliss +library's command line tool. + + *Values:. * + +‘IGRAPH_BLISS_F’: + First non-singleton cell. + +‘IGRAPH_BLISS_FL’: + First largest non-singleton cell. + +‘IGRAPH_BLISS_FS’: + First smallest non-singleton cell. + +‘IGRAPH_BLISS_FM’: + First maximally non-trivially connected non-singleton cell. + +‘IGRAPH_BLISS_FLM’: + Largest maximally non-trivially connected non-singleton cell. + +‘IGRAPH_BLISS_FSM’: + Smallest maximally non-trivially connected non-singleton cell. + + +File: igraph-docs.info, Node: igraph_bliss_info_t --- Information about a Bliss run_, Next: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_, Prev: igraph_bliss_sh_t --- Splitting heuristics for Bliss_, Up: The BLISS algorithm + +21.2.2 igraph_bliss_info_t -- Information about a Bliss run. +------------------------------------------------------------ + + + typedef struct igraph_bliss_info_t { + unsigned long nof_nodes; + unsigned long nof_leaf_nodes; + unsigned long nof_bad_nodes; + unsigned long nof_canupdates; + unsigned long nof_generators; + unsigned long max_level; + char *group_size; + } igraph_bliss_info_t; + + Some secondary information found by the Bliss algorithm is stored +here. It is useful if you wany to study the internal working of the +algorithm. + + *Values:. * + +‘nof_nodes’: + The number of nodes in the search tree. + +‘nof_leaf_nodes’: + The number of leaf nodes in the search tree. + +‘nof_bad_nodes’: + Number of bad nodes. + +‘nof_canupdates’: + Number of canrep updates. + +‘nof_generators’: + Number of generators of the automorphism group. + +‘max_level’: + Maximum level. + +‘group_size’: + The size of the automorphism group of the graph, given as a string. + It should be deallocated via ‘igraph_free()’ (*note igraph_free --- + Deallocates memory that was allocated by igraph functions_::) if + not needed any more. + + See https://users.aalto.fi/~tjunttil/bliss/ +(https://users.aalto.fi/~tjunttil/bliss/) for details about the +algorithm and these parameters. + + +File: igraph-docs.info, Node: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_, Next: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_, Prev: igraph_bliss_info_t --- Information about a Bliss run_, Up: The BLISS algorithm + +21.2.3 igraph_isomorphic_bliss -- Graph isomorphism via Bliss. +-------------------------------------------------------------- + + + igraph_error_t igraph_isomorphic_bliss(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *colors1, const igraph_vector_int_t *colors2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info1, igraph_bliss_info_t *info2); + + This function uses the Bliss graph isomorphism algorithm, a successor +of the famous NAUTY algorithm and implementation. Bliss is open source +and licensed according to the GNU LGPL. See +https://users.aalto.fi/~tjunttil/bliss/ +(https://users.aalto.fi/~tjunttil/bliss/) for details. Currently the +0.75 version of Bliss is included in igraph. + + Isomorphism testing is implemented by producing the canonical form of +both graphs using ‘igraph_canonical_permutation_bliss()’ (*note +igraph_canonical_permutation_bliss --- Canonical permutation using +Bliss_::) and comparing them. + + *Arguments:. * + +‘graph1’: + The first input graph. Multiple edges between the same nodes are + not supported and will cause an incorrect result to be returned. + +‘graph2’: + The second input graph. Multiple edges between the same nodes are + not supported and will cause an incorrect result to be returned. + +‘colors1’: + An optional vertex color vector for the first graph. Supply a null + pointer if your graph is not colored. + +‘colors2’: + An optional vertex color vector for the second graph. Supply a + null pointer if your graph is not colored. + +‘iso’: + Pointer to a boolean, the result is stored here. + +‘map12’: + A vector or ‘NULL’ pointer. If not ‘NULL’ then an isomorphic + mapping from ‘graph1’ to ‘graph2’ is stored here. If the input + graphs are not isomorphic then this vector is cleared, i.e. it + will have length zero. + +‘map21’: + Similar to ‘map12’, but for the mapping from ‘graph2’ to ‘graph1’. + +‘sh’: + Splitting heuristics to be used for the graphs. See + ‘igraph_bliss_sh_t’ (*note igraph_bliss_sh_t --- Splitting + heuristics for Bliss_::). + +‘info1’: + If not ‘NULL’, information about the canonization of the first + input graph is stored here. Note that if the two graphs have + different number of vertices or edges, then this is only partially + filled. The memory used by this structure should be released when + no longer needed, see ‘igraph_bliss_info_t’ (*note + igraph_bliss_info_t --- Information about a Bliss run_::) for + details. + +‘info2’: + Same as ‘info1’, but for the second graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential, but in practice it is quite fast. + + +File: igraph-docs.info, Node: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_, Next: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_, Prev: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_, Up: The BLISS algorithm + +21.2.4 igraph_count_automorphisms_bliss -- Number of automorphisms using Bliss. +------------------------------------------------------------------------------- + + + igraph_error_t igraph_count_automorphisms_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info + ); + + The number of automorphisms of a graph is computed using Bliss. The +result is returned as part of the ‘info’ structure, in tag ‘group_size’. +It is returned as a string, as it can be very high even for relatively +small graphs. See also ‘igraph_bliss_info_t’ (*note igraph_bliss_info_t +--- Information about a Bliss run_::). + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘sh’: + The splitting heuristics to be used in Bliss. See + ‘igraph_bliss_sh_t’ (*note igraph_bliss_sh_t --- Splitting + heuristics for Bliss_::). + +‘info’: + The result is stored here, in particular in the ‘group_size’ tag of + ‘info’. The memory used by this structure must be released when no + longer needed, see ‘igraph_bliss_info_t’ (*note igraph_bliss_info_t + --- Information about a Bliss run_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_, Next: igraph_canonical_permutation_bliss --- Canonical permutation using Bliss_, Prev: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_, Up: The BLISS algorithm + +21.2.5 igraph_automorphism_group_bliss -- Automorphism group generators using Bliss. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_automorphism_group_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info + ); + + The generators of the automorphism group of a graph are computed +using Bliss. The generator set may not be minimal and may depend on the +splitting heuristics. The generators are permutations represented using +zero-based indexing. + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘generators’: + Must be an initialized interger vector list. The generators of the + automorphism group will be stored here. + +‘sh’: + The splitting heuristics to be used in Bliss. See + ‘igraph_bliss_sh_t’ (*note igraph_bliss_sh_t --- Splitting + heuristics for Bliss_::). + +‘info’: + If not ‘NULL’ then information on Bliss internals is stored here. + The memory used by this structure must to be freed when no longer + needed, see ‘igraph_bliss_info_t’ (*note igraph_bliss_info_t --- + Information about a Bliss run_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: igraph_canonical_permutation_bliss --- Canonical permutation using Bliss_, Prev: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_, Up: The BLISS algorithm + +21.2.6 igraph_canonical_permutation_bliss -- Canonical permutation using Bliss. +------------------------------------------------------------------------------- + + + igraph_error_t igraph_canonical_permutation_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info + ); + + This function computes the vertex permutation which transforms the +graph into a canonical form, using the Bliss algorithm. Two graphs have +the same canonical form if and only if they are isomorphic. Use +‘igraph_is_same_graph()’ (*note igraph_is_same_graph --- Are two graphs +identical as labelled graphs?::) to compare two canonical forms. + + *Arguments:. * + +‘graph’: + The input graph. Multiple edges between the same nodes are not + supported and will cause an incorrect result to be returned. + +‘colors’: + An optional vertex color vector for the graph. Supply a null + pointer is the graph is not colored. + +‘labeling’: + Pointer to a vector, the result is stored here. The permutation + takes vertex 0 to the first element of the vector, vertex 1 to the + second, etc. The vector will be resized as needed. + +‘sh’: + The splitting heuristics to be used in Bliss. See + ‘igraph_bliss_sh_t’ (*note igraph_bliss_sh_t --- Splitting + heuristics for Bliss_::). + +‘info’: + If not ‘NULL’ then information on Bliss internals is stored here. + The memory used by this structure must to be freed when no longer + needed, see ‘igraph_bliss_info_t’ (*note igraph_bliss_info_t --- + Information about a Bliss run_::). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_is_same_graph()’ (*note igraph_is_same_graph --- Are two + graphs identical as labelled graphs?::) + + Time complexity: exponential, in practice it is fast for many graphs. + + +File: igraph-docs.info, Node: The VF2 algorithm, Next: The LAD algorithm, Prev: The BLISS algorithm, Up: Graph isomorphism + +21.3 The VF2 algorithm +====================== + +The VF2 algorithm can search for a subgraph in a larger graph, or check +if two graphs are isomorphic. See P. Foggia, C. Sansone, M. Vento, An +Improved algorithm for matching large graphs, Proc. of the 3rd +IAPR-TC-15 International Workshop on Graph-based Representations, Italy, +2001. + + VF2 supports both vertex and edge-colored graphs, as well as custom +vertex or edge compatibility functions. + + VF2 works with both directed and undirected graphs. Only simple +graphs are supported. Self-loops or multi-edges must not be present in +the graphs. Currently, the VF2 functions do not check that the input +graph is simple: it is the responsibility of the user to pass in valid +input. + +* Menu: + +* igraph_isomorphic_vf2 -- Isomorphism via VF2.: igraph_isomorphic_vf2 --- Isomorphism via VF2_. +* igraph_count_isomorphisms_vf2 -- Number of isomorphisms via VF2.: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_. +* igraph_get_isomorphisms_vf2 -- Collect all isomorphic mappings of two graphs.: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_. +* igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface:: +* igraph_isohandler_t -- Callback type, called when an isomorphism was found: igraph_isohandler_t --- Callback type; called when an isomorphism was found. +* igraph_isocompat_t -- Callback type, called to check whether two vertices or edges are compatible: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible. +* igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2:: +* igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF2:: +* igraph_get_subisomorphisms_vf2 -- Return all subgraph isomorphic mappings.: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_. +* igraph_get_subisomorphisms_vf2_callback -- Generic VF2 function for subgraph isomorphism problems.: igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for subgraph isomorphism problems_. + + +File: igraph-docs.info, Node: igraph_isomorphic_vf2 --- Isomorphism via VF2_, Next: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_, Up: The VF2 algorithm + +21.3.1 igraph_isomorphic_vf2 -- Isomorphism via VF2. +---------------------------------------------------- + + + igraph_error_t igraph_isomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + This function performs the VF2 algorithm via calling +‘igraph_get_isomorphisms_vf2_callback()’ (*note +igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface::). + + Note that this function cannot be used for deciding subgraph +isomorphism, use ‘igraph_subisomorphic_vf2()’ (*note +igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2::) +for that. + + *Arguments:. * + +‘graph1’: + The first graph, may be directed or undirected. + +‘graph2’: + The second graph. It must have the same directedness as ‘graph1’, + otherwise an error is reported. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the isomorphism is calculated on the + colored graphs; i.e. two vertices can match only if their color + also matches. Supply a null pointer here if your graphs are not + colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘iso’: + Pointer to a Boolean constant, the result of the algorithm will be + placed here. + +‘map12’: + Pointer to an initialized vector or a NULL pointer. If not a NULL + pointer then the mapping from ‘graph1’ to ‘graph2’ is stored here. + If the graphs are not isomorphic then the vector is cleared (i.e. + has zero elements). + +‘map21’: + Pointer to an initialized vector or a NULL pointer. If not a NULL + pointer then the mapping from ‘graph2’ to ‘graph1’ is stored here. + If the graphs are not isomorphic then the vector is cleared (i.e. + has zero elements). + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_subisomorphic_vf2()’ (*note igraph_subisomorphic_vf2 --- + Decide subgraph isomorphism using VF2::), + ‘igraph_count_isomorphisms_vf2()’ (*note + igraph_count_isomorphisms_vf2 --- Number of isomorphisms via + VF2_::), ‘igraph_get_isomorphisms_vf2()’ (*note + igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of + two graphs_::), + + Time complexity: exponential, what did you expect? + + * File examples/simple/igraph_isomorphic_vf2.c* + + +File: igraph-docs.info, Node: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_, Next: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_, Prev: igraph_isomorphic_vf2 --- Isomorphism via VF2_, Up: The VF2 algorithm + +21.3.2 igraph_count_isomorphisms_vf2 -- Number of isomorphisms via VF2. +----------------------------------------------------------------------- + + + igraph_error_t igraph_count_isomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + This function counts the number of isomorphic mappings between two +graphs. It uses the generic ‘igraph_get_isomorphisms_vf2_callback()’ +(*note igraph_get_isomorphisms_vf2_callback --- The generic VF2 +interface::) function. + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’, or an error will be reported. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the isomorphism is calculated on the + colored graphs; i.e. two vertices can match only if their color + also matches. Supply a null pointer here if your graphs are not + colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘count’: + Point to an integer, the result will be stored here. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + igraph_count_automorphisms_bliss() + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_, Next: igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface, Prev: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_, Up: The VF2 algorithm + +21.3.3 igraph_get_isomorphisms_vf2 -- Collect all isomorphic mappings of two graphs. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_get_isomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + This function finds all the isomorphic mappings between two simple +graphs. It uses the ‘igraph_get_isomorphisms_vf2_callback()’ (*note +igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface::) +function. Call the function with the same graph as ‘graph1’ and +‘graph2’ to get automorphisms. + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’, or an error will be reported. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the isomorphism is calculated on the + colored graphs; i.e. two vertices can match only if their color + also matches. Supply a null pointer here if your graphs are not + colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘maps’: + Pointer to a list of integer vectors. On return it is empty if the + input graphs are not isomorphic. Otherwise it contains pointers to + ‘igraph_vector_int_t’ (*note About igraph_vector_t objects::) + objects, each vector is an isomorphic mapping of ‘graph2’ to + ‘graph1’. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface, Next: igraph_isohandler_t --- Callback type; called when an isomorphism was found, Prev: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_, Up: The VF2 algorithm + +21.3.4 igraph_get_isomorphisms_vf2_callback -- The generic VF2 interface +------------------------------------------------------------------------ + + + igraph_error_t igraph_get_isomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg + ); + + This function is an implementation of the VF2 isomorphism algorithm, +see P. Foggia, C. Sansone, M. Vento, An Improved algorithm for matching +large graphs, Proc. of the 3rd IAPR-TC-15 International Workshop on +Graph-based Representations, Italy, 2001. + + For using it you need to define a callback function of type +‘igraph_isohandler_t’ (*note igraph_isohandler_t --- Callback type; +called when an isomorphism was found::). This function will be called +whenever VF2 finds an isomorphism between the two graphs. The mapping +between the two graphs will be also provided to this function. If the +callback returns ‘IGRAPH_SUCCESS’, then the search is continued, +otherwise it stops. ‘IGRAPH_STOP’ as a return value can be used to +indicate normal premature termination; any other return value will be +treated as an igraph error code, making the caller function return the +same error code as well. The callback function must not destroy the +mapping vectors that are passed to it. + + *Arguments:. * + +‘graph1’: + The first input graph. + +‘graph2’: + The second input graph. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the isomorphism is calculated on the + colored graphs; i.e. two vertices can match only if their color + also matches. Supply a null pointer here if your graphs are not + colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘map12’: + Pointer to an initialized vector or ‘NULL’. If not ‘NULL’ and the + supplied graphs are isomorphic then the permutation taking ‘graph1’ + to ‘graph’ is stored here. If not ‘NULL’ and the graphs are not + isomorphic then a zero-length vector is returned. + +‘map21’: + This is the same as ‘map12’, but for the permutation taking + ‘graph2’ to ‘graph1’. + +‘isohandler_fn’: + The callback function to be called if an isomorphism is found. See + also ‘igraph_isohandler_t’ (*note igraph_isohandler_t --- Callback + type; called when an isomorphism was found::). + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘isohandler_fn’, + ‘node_compat_fn’ and ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_isohandler_t --- Callback type; called when an isomorphism was found, Next: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible, Prev: igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface, Up: The VF2 algorithm + +21.3.5 igraph_isohandler_t -- Callback type, called when an isomorphism was found +--------------------------------------------------------------------------------- + + + typedef igraph_error_t igraph_isohandler_t(const igraph_vector_int_t *map12, + const igraph_vector_int_t *map21, void *arg); + + See the details at the documentation of +‘igraph_get_isomorphisms_vf2_callback()’ (*note +igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface::). + + *Arguments:. * + +‘map12’: + The mapping from the first graph to the second. + +‘map21’: + The mapping from the second graph to the first, the inverse of + ‘map12’ basically. + +‘arg’: + This extra argument was passed to + ‘igraph_get_isomorphisms_vf2_callback()’ (*note + igraph_get_isomorphisms_vf2_callback --- The generic VF2 + interface::) when it was called. + + *Returns:. * + +‘’ + ‘IGRAPH_SUCCESS’ to continue the search, ‘IGRAPH_STOP’ to terminate + the search. Any other return value is interpreted as an igraph + error code, which will then abort the search and return the same + error code from the caller function. + + +File: igraph-docs.info, Node: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible, Next: igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2, Prev: igraph_isohandler_t --- Callback type; called when an isomorphism was found, Up: The VF2 algorithm + +21.3.6 igraph_isocompat_t -- Callback type, called to check whether two vertices or edges are compatible +-------------------------------------------------------------------------------------------------------- + + + typedef igraph_bool_t igraph_isocompat_t(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg); + + VF2 (subgraph) isomorphism functions can be restricted by defining +relations on the vertices and/or edges of the graphs, and then checking +whether the vertices (edges) match according to these relations. + + This feature is implemented by two callbacks, one for vertices, one +for edges. Every time igraph tries to match a vertex (edge) of the +first (sub)graph to a vertex of the second graph, the vertex (edge) +compatibility callback is called. The callback returns a logical value, +giving whether the two vertices match. + + Both callback functions are of type ‘igraph_isocompat_t’. + + *Arguments:. * + +‘graph1’: + The first graph. + +‘graph2’: + The second graph. + +‘g1_num’: + The id of a vertex or edge in the first graph. + +‘g2_num’: + The id of a vertex or edge in the second graph. + +‘arg’: + Extra argument to pass to the callback functions. + + *Returns:. * + +‘’ + Logical scalar, whether vertex (or edge) ‘g1_num’ in ‘graph1’ is + compatible with vertex (or edge) ‘g2_num’ in ‘graph2’. + + +File: igraph-docs.info, Node: igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2, Next: igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF2, Prev: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible, Up: The VF2 algorithm + +21.3.7 igraph_subisomorphic_vf2 -- Decide subgraph isomorphism using VF2 +------------------------------------------------------------------------ + + + igraph_error_t igraph_subisomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + Decides whether a subgraph of ‘graph1’ is isomorphic to ‘graph2’. It +uses ‘igraph_get_subisomorphisms_vf2_callback()’ (*note +igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for +subgraph isomorphism problems_::). + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. This is + supposed to be the larger graph. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’. This is supposed to be the smaller graph. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the subgraph isomorphism is calculated + on the colored graphs; i.e. two vertices can match only if their + color also matches. Supply a null pointer here if your graphs are + not colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘iso’: + Pointer to a boolean. The result of the decision problem is stored + here. + +‘map12’: + Pointer to a vector or ‘NULL’. If not ‘NULL’, then an isomorphic + mapping from ‘graph1’ to ‘graph2’ is stored here. + +‘map21’: + Pointer to a vector ot ‘NULL’. If not ‘NULL’, then an isomorphic + mapping from ‘graph2’ to ‘graph1’ is stored here. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF2, Next: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_, Prev: igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2, Up: The VF2 algorithm + +21.3.8 igraph_count_subisomorphisms_vf2 -- Number of subgraph isomorphisms using VF2 +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_count_subisomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + Count the number of isomorphisms between subgraphs of ‘graph1’ and +‘graph2’. This function uses +‘igraph_get_subisomorphisms_vf2_callback()’ (*note +igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for +subgraph isomorphism problems_::). + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. This is + supposed to be the larger graph. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’. This is supposed to be the smaller graph. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the subgraph isomorphism is calculated + on the colored graphs; i.e. two vertices can match only if their + color also matches. Supply a null pointer here if your graphs are + not colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘count’: + Pointer to an integer. The number of subgraph isomorphisms is + stored here. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_, Next: igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for subgraph isomorphism problems_, Prev: igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF2, Up: The VF2 algorithm + +21.3.9 igraph_get_subisomorphisms_vf2 -- Return all subgraph isomorphic mappings. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_subisomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + + This function collects all isomorphic mappings of ‘graph2’ to a +subgraph of ‘graph1’. It uses the +‘igraph_get_subisomorphisms_vf2_callback()’ (*note +igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for +subgraph isomorphism problems_::) function. The graphs should be +simple. + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. This is + supposed to be the larger graph. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’. This is supposed to be the smaller graph. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the subgraph isomorphism is calculated + on the colored graphs; i.e. two vertices can match only if their + color also matches. Supply a null pointer here if your graphs are + not colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘maps’: + Pointer to a list of integer vectors. On return it contains + pointers to ‘igraph_vector_int_t’ (*note About igraph_vector_t + objects::) objects, each vector is an isomorphic mapping of + ‘graph2’ to a subgraph of ‘graph1’. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘node_compat_fn’ and + ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for subgraph isomorphism problems_, Prev: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_, Up: The VF2 algorithm + +21.3.10 igraph_get_subisomorphisms_vf2_callback -- Generic VF2 function for subgraph isomorphism problems. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_get_subisomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg + ); + + This function is the pair of ‘igraph_get_isomorphisms_vf2_callback()’ +(*note igraph_get_isomorphisms_vf2_callback --- The generic VF2 +interface::), for subgraph isomorphism problems. It searches for +subgraphs of ‘graph1’ which are isomorphic to ‘graph2’. When it founds +an isomorphic mapping it calls the supplied callback ‘isohandler_fn’. +The mapping (and its inverse) and the additional ‘arg’ argument are +supplied to the callback. + + *Arguments:. * + +‘graph1’: + The first input graph, may be directed or undirected. This is + supposed to be the larger graph. + +‘graph2’: + The second input graph, it must have the same directedness as + ‘graph1’. This is supposed to be the smaller graph. + +‘vertex_color1’: + An optional color vector for the first graph. If color vectors are + given for both graphs, then the subgraph isomorphism is calculated + on the colored graphs; i.e. two vertices can match only if their + color also matches. Supply a null pointer here if your graphs are + not colored. + +‘vertex_color2’: + An optional color vector for the second graph. See the previous + argument for explanation. + +‘edge_color1’: + An optional edge color vector for the first graph. The matching + edges in the two graphs must have matching colors as well. Supply + a null pointer here if your graphs are not edge-colored. + +‘edge_color2’: + The edge color vector for the second graph. + +‘map12’: + Pointer to a vector or ‘NULL’. If not ‘NULL’, then an isomorphic + mapping from ‘graph1’ to ‘graph2’ is stored here. + +‘map21’: + Pointer to a vector ot ‘NULL’. If not ‘NULL’, then an isomorphic + mapping from ‘graph2’ to ‘graph1’ is stored here. + +‘isohandler_fn’: + A pointer to a function of type ‘igraph_isohandler_t’ (*note + igraph_isohandler_t --- Callback type; called when an isomorphism + was found::). This will be called whenever a subgraph isomorphism + is found. If the function returns ‘IGRAPH_SUCCESS’, then the + search is continued. If the function returns ‘IGRAPH_STOP’, the + search is terminated normally. Any other value is treated as an + igraph error code. + +‘node_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two nodes are compatible. + +‘edge_compat_fn’: + A pointer to a function of type ‘igraph_isocompat_t’ (*note + igraph_isocompat_t --- Callback type; called to check whether two + vertices or edges are compatible::). This function will be called + by the algorithm to determine whether two edges are compatible. + +‘arg’: + Extra argument to supply to functions ‘isohandler_fn’, + ‘node_compat_fn’ and ‘edge_compat_fn’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: exponential. + + +File: igraph-docs.info, Node: The LAD algorithm, Next: Functions for small graphs, Prev: The VF2 algorithm, Up: Graph isomorphism + +21.4 The LAD algorithm +====================== + +The LAD algorithm can search for a subgraph in a larger graph, or check +if two graphs are isomorphic. See Christine Solnon: AllDifferent-based +Filtering for Subgraph Isomorphism. Artificial Intelligence, +174(12-13):850-864, 2010. https://doi.org/10.1016/j.artint.2010.05.002 +(https://doi.org/10.1016/j.artint.2010.05.002) as well as the homepage +of the LAD library at http://liris.cnrs.fr/csolnon/LAD.html +(http://liris.cnrs.fr/csolnon/LAD.html) The implementation in igraph is +based on LADv1, but it is modified to use igraph's own memory allocation +and error handling. + + LAD uses the concept of domains to indicate vertex compatibility when +matching the pattern graph. Domains can be used to implement matching +of colored vertices. + + LAD works with both directed and undirected graphs. Graphs with +multi-edges are not supported. + +* Menu: + +* igraph_subisomorphic_lad --- Check subgraph isomorphism with the LAD algorithm:: + + +File: igraph-docs.info, Node: igraph_subisomorphic_lad --- Check subgraph isomorphism with the LAD algorithm, Up: The LAD algorithm + +21.4.1 igraph_subisomorphic_lad -- Check subgraph isomorphism with the LAD algorithm +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_subisomorphic_lad(const igraph_t *pattern, const igraph_t *target, + const igraph_vector_int_list_t *domains, + igraph_bool_t *iso, igraph_vector_int_t *map, + igraph_vector_int_list_t *maps, + igraph_bool_t induced); + + Check whether ‘pattern’ is isomorphic to a subgraph os ‘target’. The +original LAD implementation by Christine Solnon was used as the basis of +this code. + + See more about LAD at http://liris.cnrs.fr/csolnon/LAD.html +(http://liris.cnrs.fr/csolnon/LAD.html) and in Christine Solnon: +AllDifferent-based Filtering for Subgraph Isomorphism. Artificial +Intelligence, 174(12-13):850-864, 2010. +https://doi.org/10.1016/j.artint.2010.05.002 +(https://doi.org/10.1016/j.artint.2010.05.002) + + *Arguments:. * + +‘pattern’: + The smaller graph, it can be directed or undirected. + +‘target’: + The bigger graph, it can be directed or undirected. + +‘domains’: + An integer vector list of ‘NULL’. The length of each vector must + match the number of vertices in the ‘pattern’ graph. For each + vertex, the IDs of the compatible vertices in the target graph are + listed. + +‘iso’: + Pointer to a boolean, or a null pointer. If not a null pointer, + then the boolean is set to ‘true’ if a subgraph isomorphism is + found, and to ‘false’ otherwise. + +‘map’: + Pointer to a vector or a null pointer. If not a null pointer and a + subgraph isomorphism is found, the matching vertices from the + target graph are listed here, for each vertex (in vertex ID order) + from the pattern graph. + +‘maps’: + Pointer to a list of integer vectors or a null pointer. If not a + null pointer, then all subgraph isomorphisms are stored in the + vector list, in ‘igraph_vector_int_t’ (*note About igraph_vector_t + objects::) objects. + +‘induced’: + Boolean, whether to search for induced matching subgraphs. + +‘time_limit’: + Processor time limit in seconds. Supply zero here for no limit. + If the time limit is over, then the function signals an error. + + *Returns:. * + +‘’ + Error code + + *See also:. * + +‘’ + ‘igraph_subisomorphic_vf2()’ (*note igraph_subisomorphic_vf2 --- + Decide subgraph isomorphism using VF2::) for the VF2 algorithm. + + Time complexity: exponential. + + * File examples/simple/igraph_subisomorphic_lad.c* + + +File: igraph-docs.info, Node: Functions for small graphs, Next: Utility functions, Prev: The LAD algorithm, Up: Graph isomorphism + +21.5 Functions for small graphs +=============================== + +* Menu: + +* igraph_isoclass -- Determine the isomorphism class of small graphs.: igraph_isoclass --- Determine the isomorphism class of small graphs_. +* igraph_isoclass_subgraph -- The isomorphism class of a subgraph of a graph.: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_. +* igraph_isoclass_create -- Creates a graph from the given isomorphism class.: igraph_isoclass_create --- Creates a graph from the given isomorphism class_. +* igraph_graph_count -- The number of unlabelled graphs on the given number of vertices.: igraph_graph_count --- The number of unlabelled graphs on the given number of vertices_. + + +File: igraph-docs.info, Node: igraph_isoclass --- Determine the isomorphism class of small graphs_, Next: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_, Up: Functions for small graphs + +21.5.1 igraph_isoclass -- Determine the isomorphism class of small graphs. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_isoclass(const igraph_t *graph, igraph_int_t *isoclass); + + All graphs with a given number of vertices belong to a number of +isomorphism classes, with every graph in a given class being isomorphic +to each other. + + This function gives the isomorphism class (a number) of a graph. Two +graphs have the same isomorphism class if and only if they are +isomorphic. + + The first isomorphism class is numbered zero and it contains the +edgeless graph. The last isomorphism class contains the full graph. +The number of isomorphism classes for directed graphs with three +vertices is 16 (between 0 and 15), for undirected graph it is only 4. +For graphs with four vertices it is 218 (directed) and 11 (undirected). +For 5 and 6 vertex undirected graphs, it is 34 and 156, respectively. +These values can also be retrieved using ‘igraph_graph_count()’ (*note +igraph_graph_count --- The number of unlabelled graphs on the given +number of vertices_::). For more information, see +https://oeis.org/A000273 (https://oeis.org/A000273) and +https://oeis.org/A000088 (https://oeis.org/A000088). + + At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex +undirected graphs are supported. + + Multi-edges and self-loops are ignored by this function. + + *Arguments:. * + +‘graph’: + The graph object. + +‘isoclass’: + Pointer to an integer, the isomorphism class will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_isomorphic()’ (*note igraph_isomorphic --- Are two graphs + isomorphic?::), ‘igraph_isoclass_subgraph()’ (*note + igraph_isoclass_subgraph --- The isomorphism class of a subgraph of + a graph_::), ‘igraph_isoclass_create()’ (*note + igraph_isoclass_create --- Creates a graph from the given + isomorphism class_::), ‘igraph_motifs_randesu()’ (*note + igraph_motifs_randesu --- Count the number of motifs in a + graph_::). + + Because of some limitations this function works only for graphs with +three of four vertices. + + Time complexity: O(|E|), the number of edges in the graph. + + +File: igraph-docs.info, Node: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_, Next: igraph_isoclass_create --- Creates a graph from the given isomorphism class_, Prev: igraph_isoclass --- Determine the isomorphism class of small graphs_, Up: Functions for small graphs + +21.5.2 igraph_isoclass_subgraph -- The isomorphism class of a subgraph of a graph. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_isoclass_subgraph(const igraph_t *graph, igraph_vs_t vids, + igraph_int_t *isoclass); + + This function identifies the isomorphism class of the subgraph +induced the vertices specified in ‘vids’. + + At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex +undirected graphs are supported. + + Multi-edges and self-loops are ignored by this function. + + *Arguments:. * + +‘graph’: + The graph object. + +‘vids’: + The vertices of the subgraph. Each vertex must be included at most + once. + +‘isoclass’: + Pointer to an integer, this will be set to the isomorphism class. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_isoclass()’ (*note igraph_isoclass --- Determine the + isomorphism class of small graphs_::), ‘igraph_isomorphic()’ (*note + igraph_isomorphic --- Are two graphs isomorphic?::), + ‘igraph_isoclass_create()’ (*note igraph_isoclass_create --- + Creates a graph from the given isomorphism class_::). + + Time complexity: O((d+n)*n), d is the average degree in the network, +and n is the number of vertices in ‘vids’. + + +File: igraph-docs.info, Node: igraph_isoclass_create --- Creates a graph from the given isomorphism class_, Next: igraph_graph_count --- The number of unlabelled graphs on the given number of vertices_, Prev: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_, Up: Functions for small graphs + +21.5.3 igraph_isoclass_create -- Creates a graph from the given isomorphism class. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_isoclass_create(igraph_t *graph, igraph_int_t size, + igraph_int_t number, igraph_bool_t directed); + + This function creates the canonical representative graph of the given +isomorphism class. + + The isomorphism class is an integer between 0 and the number of +unique unlabeled (i.e. non-isomorphic) graphs on the given number of +vertices and give directedness. See https://oeis.org/A000273 +(https://oeis.org/A000273) and https://oeis.org/A000088 +(https://oeis.org/A000088) for the number of directed and undirected +graphs on ‘size’ nodes. + + At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex +undirected graphs are supported. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘size’: + The number of vertices to add to the graph. + +‘number’: + The isomorphism class. + +‘directed’: + Boolean constant, whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_isoclass()’ (*note igraph_isoclass --- Determine the + isomorphism class of small graphs_::), ‘igraph_isoclass_subgraph()’ + (*note igraph_isoclass_subgraph --- The isomorphism class of a + subgraph of a graph_::), ‘igraph_isomorphic()’ (*note + igraph_isomorphic --- Are two graphs isomorphic?::). + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges in the graph to create. + + +File: igraph-docs.info, Node: igraph_graph_count --- The number of unlabelled graphs on the given number of vertices_, Prev: igraph_isoclass_create --- Creates a graph from the given isomorphism class_, Up: Functions for small graphs + +21.5.4 igraph_graph_count -- The number of unlabelled graphs on the given number of vertices. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_graph_count(igraph_int_t n, igraph_bool_t directed, igraph_int_t *count); + + Gives the number of unlabelled _simple_ graphs on the specified +number of vertices. The "isoclass" of a graph of this size is at most +one less than this value. + + This function is meant to be used in conjunction with isoclass and +motif finder functions. It will only work for small ‘n’ values for +which the result is represetable in an ‘igraph_int_t’. For larger ‘n’ +values, an overflow error is raised. + + *Arguments:. * + +‘n’: + The number of vertices. + +‘directed’: + Boolean, whether to consider directed graphs. + +‘count’: + Pointer to an integer, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_isoclass()’ (*note igraph_isoclass --- Determine the + isomorphism class of small graphs_::), + ‘igraph_motifs_randesu_callback()’ (*note + igraph_motifs_randesu_callback --- Finds motifs in a graph and + calls a function for each of them_::). + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Utility functions, Prev: Functions for small graphs, Up: Graph isomorphism + +21.6 Utility functions +====================== + +* Menu: + +* igraph_invert_permutation -- Inverts a permutation.: igraph_invert_permutation --- Inverts a permutation_. +* igraph_permute_vertices -- Permute the vertices.: igraph_permute_vertices --- Permute the vertices_. +* igraph_simplify_and_colorize -- Simplify the graph and compute self-loop and edge multiplicities.: igraph_simplify_and_colorize --- Simplify the graph and compute self-loop and edge multiplicities_. + + +File: igraph-docs.info, Node: igraph_invert_permutation --- Inverts a permutation_, Next: igraph_permute_vertices --- Permute the vertices_, Up: Utility functions + +21.6.1 igraph_invert_permutation -- Inverts a permutation. +---------------------------------------------------------- + + + igraph_error_t igraph_invert_permutation(const igraph_vector_int_t *permutation, igraph_vector_int_t *inverse); + + Produces the inverse of ‘permutation’ into ‘inverse’ and at the same +time it checks that the permutation vector is valid, i.e. all indices +are within range and there are no duplicate entries. + + *Arguments:. * + +‘permutation’: + A permutation vector containing 0-based integer indices. + +‘inverse’: + An initialized vector. The inverse of ‘permutation’ will be stored + here. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_permute_vertices --- Permute the vertices_, Next: igraph_simplify_and_colorize --- Simplify the graph and compute self-loop and edge multiplicities_, Prev: igraph_invert_permutation --- Inverts a permutation_, Up: Utility functions + +21.6.2 igraph_permute_vertices -- Permute the vertices. +------------------------------------------------------- + + + igraph_error_t igraph_permute_vertices(const igraph_t *graph, igraph_t *res, + const igraph_vector_int_t *permutation); + + This function creates a new graph from the input graph by permuting +its vertices according to the specified mapping. Call this function +with the output of ‘igraph_canonical_permutation()’ (*note +igraph_canonical_permutation --- Canonical permutation of a graph_::) to +create the canonical form of a graph. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an uninitialized graph object. The new graph is created + here. + +‘permutation’: + The permutation to apply. The i-th element of the vector specifies + the index of the vertex in the original graph that will become + vertex i in the new graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in terms of the number of +vertices and edges. + + +File: igraph-docs.info, Node: igraph_simplify_and_colorize --- Simplify the graph and compute self-loop and edge multiplicities_, Prev: igraph_permute_vertices --- Permute the vertices_, Up: Utility functions + +21.6.3 igraph_simplify_and_colorize -- Simplify the graph and compute self-loop and edge multiplicities. +-------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_simplify_and_colorize( + const igraph_t *graph, igraph_t *res, + igraph_vector_int_t *vertex_color, igraph_vector_int_t *edge_color); + + This function creates a vertex and edge colored simple graph from the +input graph. The vertex colors are computed as the number of incident +self-loops to each vertex in the input graph. The edge colors are +computed as the number of parallel edges in the input graph that were +merged to create each edge in the simple graph. + + The resulting colored simple graph is suitable for use by isomorphism +checking algorithms such as VF2, which only support simple graphs, but +can consider vertex and edge colors. + + *Arguments:. * + +‘graph’: + The graph object, typically having self-loops or multi-edges. + +‘res’: + An uninitialized graph object. The result will be stored here + +‘vertex_color’: + Computed vertex colors corresponding to self-loop multiplicities. + +‘edge_color’: + Computed edge colors corresponding to edge multiplicities + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_simplify()’ (*note igraph_simplify --- Removes loop and/or + multiple edges from the graph_::), ‘igraph_isomorphic_vf2()’ (*note + igraph_isomorphic_vf2 --- Isomorphism via VF2_::), + ‘igraph_subisomorphic_vf2()’ (*note igraph_subisomorphic_vf2 --- + Decide subgraph isomorphism using VF2::) + + +File: igraph-docs.info, Node: Graph coloring, Next: Maximum flows; minimum cuts and related measures, Prev: Graph isomorphism, Up: Top + +22 Graph coloring +***************** + +* Menu: + +* igraph_vertex_coloring_greedy -- Computes a vertex coloring using a greedy algorithm.: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_. +* igraph_coloring_greedy_t -- Ordering heuristics for greedy graph coloring.: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_. +* igraph_is_vertex_coloring -- Checks whether a vertex coloring is valid.: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_. +* igraph_is_bipartite_coloring -- Checks whether a bipartite vertex coloring is valid.: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_. +* igraph_is_edge_coloring -- Checks whether an edge coloring is valid.: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_. +* igraph_is_perfect -- Checks if the graph is perfect.: igraph_is_perfect --- Checks if the graph is perfect_. + + +File: igraph-docs.info, Node: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_, Next: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_, Up: Graph coloring + +22.1 igraph_vertex_coloring_greedy -- Computes a vertex coloring using a greedy algorithm. +========================================================================================== + + + igraph_error_t igraph_vertex_coloring_greedy(const igraph_t *graph, igraph_vector_int_t *colors, igraph_coloring_greedy_t heuristic); + + This function assigns a "color"--represented as a non-negative +integer--to each vertex of the graph in such a way that neighboring +vertices never have the same color. The obtained coloring is not +necessarily minimal. + + Vertices are colored greedily, one by one, always choosing the +smallest color index that differs from that of already colored +neighbors. Vertices are picked in an order determined by the speified +heuristic. Colors are represented by non-negative integers 0, 1, 2, ... + + *Arguments:. * + +‘graph’: + The input graph. + +‘colors’: + Pointer to an initialized integer vector. The vertex colors will + be stored here. + +‘heuristic’: + The vertex ordering heuristic to use during greedy coloring. See + ‘igraph_coloring_greedy_t’ (*note igraph_coloring_greedy_t --- + Ordering heuristics for greedy graph coloring_::) for more + information. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + igraph_is_vertex_coloring() to check if a coloring is valid, i.e. + if all edges connect vertices of different colors. + + * File examples/simple/coloring.c* + + +File: igraph-docs.info, Node: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_, Next: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_, Prev: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_, Up: Graph coloring + +22.2 igraph_coloring_greedy_t -- Ordering heuristics for greedy graph coloring. +=============================================================================== + + + typedef enum { + IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS = 0, + IGRAPH_COLORING_GREEDY_DSATUR = 1 + } igraph_coloring_greedy_t; + + Ordering heuristics for ‘igraph_vertex_coloring_greedy()’ (*note +igraph_vertex_coloring_greedy --- Computes a vertex coloring using a +greedy algorithm_::). + + *Values:. * + +‘IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS’: + Choose the vertex with largest number of already colored neighbors. + +‘IGRAPH_COLORING_GREEDY_DSATUR’: + Choose the vertex with largest number of unique colors in its + neighborhood, i.e. its "saturation degree". When multiple + vertices have the same saturation degree, choose the one with the + most not yet colored neighbors. Added in igraph 0.10.4. This + heuristic is known as "DSatur", and was proposed in Daniel Brélaz: + New methods to color the vertices of a graph, Commun. ACM 22, 4 + (1979), 251-256. https://doi.org/10.1145/359094.359101 + (https://doi.org/10.1145/359094.359101) + + +File: igraph-docs.info, Node: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_, Next: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_, Prev: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_, Up: Graph coloring + +22.3 igraph_is_vertex_coloring -- Checks whether a vertex coloring is valid. +============================================================================ + + + igraph_error_t igraph_is_vertex_coloring( + const igraph_t *graph, + const igraph_vector_int_t *types, + igraph_bool_t *res); + + This function checks whether the given vertex type/color assignment +is a valid vertex coloring, i.e., no two adjacent vertices have the same +color. Self-loops are ignored. + + *Arguments:. * + +‘graph’: + The input graph. + +‘types’: + The vertex types/colors as an integer vector. + +‘res’: + Pointer to a boolean, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), linear in the number of edges. + + * File examples/simple/coloring.c* + + +File: igraph-docs.info, Node: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_, Next: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_, Prev: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_, Up: Graph coloring + +22.4 igraph_is_bipartite_coloring -- Checks whether a bipartite vertex coloring is valid. +========================================================================================= + + + igraph_error_t igraph_is_bipartite_coloring( + const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_bool_t *res, + igraph_neimode_t *mode); + + This function checks whether the given vertex type assignment is a +valid bipartite coloring, i.e., no two adjacent vertices have the same +type. Additionally, for directed graphs, it determines the mode of edge +directions. Self-loops are ignored. + + *Arguments:. * + +‘graph’: + The input graph. + +‘types’: + The vertex types as a boolean vector. + +‘res’: + Pointer to a boolean, the result is stored here. + +‘mode’: + Pointer to store the edge direction mode. Can be ‘NULL’ if not + needed. If all edges go from false to true vertices, ‘IGRAPH_OUT’ + is returned. If all edges go from true to false vertices, + ‘IGRAPH_IN’ is returned. If edges go in both directions or graph + is undirected, ‘IGRAPH_ALL’ is returned. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), linear in the number of edges. + + *See also:. * + +‘’ + igraph_is_bipartite() to determine whether a graph is bipartite, + i.e. 2-colorable, and find such a coloring. + + +File: igraph-docs.info, Node: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_, Next: igraph_is_perfect --- Checks if the graph is perfect_, Prev: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_, Up: Graph coloring + +22.5 igraph_is_edge_coloring -- Checks whether an edge coloring is valid. +========================================================================= + + + igraph_error_t igraph_is_edge_coloring( + const igraph_t *graph, + const igraph_vector_int_t *types, + igraph_bool_t *res); + + This function checks whether the given edge color assignment is a +valid edge coloring, i.e., no two adjacent edges have the same color. +Note that this function does not consider self-edges (loops) as being +adjacent to themselves, so graphs with self-loops may still be +considered to have a valid edge coloring. + + *Arguments:. * + +‘graph’: + The input graph. + +‘types’: + The edge colors as an integer vector. + +‘res’: + Pointer to a boolean, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|*d*log(d)), where d is the maximum degree. + + +File: igraph-docs.info, Node: igraph_is_perfect --- Checks if the graph is perfect_, Prev: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_, Up: Graph coloring + +22.6 igraph_is_perfect -- Checks if the graph is perfect. +========================================================= + + + igraph_error_t igraph_is_perfect(const igraph_t *graph, igraph_bool_t *perfect); + + A perfect graph is an undirected graph in which the chromatic number +of every induced subgraph equals the order of the largest clique of that +subgraph. The chromatic number of a graph G is the smallest number of +colors needed to color the vertices of G so that no two adjacent +vertices share the same color. + + Warning: This function may create the complement of the graph +internally, which consumes a lot of memory. For moderately sized +graphs, consider decomposing them into biconnected components and +running the check separately on each component. + + This implementation is based on the strong perfect graph theorem +which was conjectured by Claude Berge and proved by Maria Chudnovsky, +Neil Robertson, Paul Seymour, and Robin Thomas. + + *Arguments:. * + +‘graph’: + The input graph. It is expected to be undirected and simple. + +‘perfect’: + Pointer to an integer, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: worst case exponenital, often faster in practice. + + +File: igraph-docs.info, Node: Maximum flows; minimum cuts and related measures, Next: Vertex separators, Prev: Graph coloring, Up: Top + +23 Maximum flows, minimum cuts and related measures +*************************************************** + +* Menu: + +* Maximum flows:: +* Cuts and minimum cuts:: +* Connectivity:: +* Edge- and vertex-disjoint paths:: +* Graph adhesion and cohesion:: +* Cohesive blocks:: + + +File: igraph-docs.info, Node: Maximum flows, Next: Cuts and minimum cuts, Up: Maximum flows; minimum cuts and related measures + +23.1 Maximum flows +================== + +* Menu: + +* igraph_maxflow -- Maximum network flow between a pair of vertices.: igraph_maxflow --- Maximum network flow between a pair of vertices_. +* igraph_maxflow_value -- Maximum flow in a network with the push/relabel algorithm.: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_. +* igraph_dominator_tree -- Calculates the dominator tree of a flowgraph.: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_. +* igraph_maxflow_stats_t -- Data structure holding statistics from the push-relabel maximum flow solver.: igraph_maxflow_stats_t --- Data structure holding statistics from the push-relabel maximum flow solver_. + + +File: igraph-docs.info, Node: igraph_maxflow --- Maximum network flow between a pair of vertices_, Next: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_, Up: Maximum flows + +23.1.1 igraph_maxflow -- Maximum network flow between a pair of vertices. +------------------------------------------------------------------------- + + + igraph_error_t igraph_maxflow(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *flow, igraph_vector_int_t *cut, + igraph_vector_int_t *partition, igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); + + This function implements the Goldberg-Tarjan algorithm for +calculating value of the maximum flow in a directed or undirected graph. +The algorithm was given in Andrew V. Goldberg, Robert E. Tarjan: A New +Approach to the Maximum-Flow Problem, Journal of the ACM, 35(4), +921-940, 1988 https://doi.org/10.1145/48014.61051 +(https://doi.org/10.1145/48014.61051). + + The input of the function is a graph, a vector of real numbers giving +the capacity of the edges and two vertices of the graph, the source and +the target. A flow is a function assigning positive real numbers to the +edges and satisfying two requirements: (1) the flow value is less than +the capacity of the edge and (2) at each vertex except the source and +the target, the incoming flow (i.e. the sum of the flow on the incoming +edges) is the same as the outgoing flow (i.e. the sum of the flow on +the outgoing edges). The value of the flow is the incoming flow at the +target vertex. The maximum flow is the flow with the maximum value. + + *Arguments:. * + +‘graph’: + The input graph, either directed or undirected. + +‘value’: + Pointer to a real number, the value of the maximum will be placed + here, unless it is a null pointer. + +‘flow’: + If not a null pointer, then it must be a pointer to an initialized + vector. The vector will be resized, and the flow on each edge will + be placed in it, in the order of the edge IDs. For undirected + graphs this argument is bit trickier, since for these the flow + direction is not predetermined by the edge direction. For these + graphs the elements of the ‘flow’ vector can be negative, this + means that the flow goes from the bigger vertex ID to the smaller + one. Positive values mean that the flow goes from the smaller + vertex ID to the bigger one. + +‘cut’: + A null pointer or a pointer to an initialized vector. If not a + null pointer, then the minimum cut corresponding to the maximum + flow is stored here, i.e. all edge IDs that are part of the + minimum cut are stored in the vector. + +‘partition’: + A null pointer or a pointer to an initialized vector. If not a + null pointer, then the first partition of the minimum cut that + corresponds to the maximum flow will be placed here. The first + partition is always the one that contains the source vertex. + +‘partition2’: + A null pointer or a pointer to an initialized vector. If not a + null pointer, then the second partition of the minimum cut that + corresponds to the maximum flow will be placed here. The second + partition is always the one that contains the target vertex. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + +‘capacity’: + Vector containing the capacity of the edges. If ‘NULL’, then every + edge is considered to have capacity 1.0. + +‘stats’: + Counts of the number of different operations performed by the + algorithm are stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3). In practice it is much faster, but I +cannot prove a better lower bound for the data structure I've used. In +fact, this implementation runs much faster than the ‘hi_pr’ +implementation discussed in B. V. Cherkassky and A. V. Goldberg: On +implementing the push-relabel method for the maximum flow problem, +(Algorithmica, 19:390-410, 1997) on all the graph classes I've tried. + + *See also:. * + +‘’ + ‘igraph_mincut_value()’ (*note igraph_mincut_value --- The minimum + edge cut in a graph_::), ‘igraph_edge_connectivity()’ (*note + igraph_edge_connectivity --- The minimum edge connectivity in a + graph_::), ‘igraph_vertex_connectivity()’ (*note + igraph_vertex_connectivity --- The vertex connectivity of a + graph_::) for properties based on the maximum flow. + + * File examples/simple/flow.c* + + * File examples/simple/flow2.c* + + +File: igraph-docs.info, Node: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_, Next: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_, Prev: igraph_maxflow --- Maximum network flow between a pair of vertices_, Up: Maximum flows + +23.1.2 igraph_maxflow_value -- Maximum flow in a network with the push/relabel algorithm. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_maxflow_value(const igraph_t *graph, igraph_real_t *value, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); + + This function implements the Goldberg-Tarjan algorithm for +calculating value of the maximum flow in a directed or undirected graph. +The algorithm was given in Andrew V. Goldberg, Robert E. Tarjan: A New +Approach to the Maximum-Flow Problem, Journal of the ACM, 35(4), +921-940, 1988 https://doi.org/10.1145/48014.61051 +(https://doi.org/10.1145/48014.61051). + + The input of the function is a graph, a vector of real numbers giving +the capacity of the edges and two vertices of the graph, the source and +the target. A flow is a function assigning positive real numbers to the +edges and satisfying two requirements: (1) the flow value is less than +the capacity of the edge and (2) at each vertex except the source and +the target, the incoming flow (i.e. the sum of the flow on the incoming +edges) is the same as the outgoing flow (i.e. the sum of the flow on +the outgoing edges). The value of the flow is the incoming flow at the +target vertex. The maximum flow is the flow with the maximum value. + + According to a theorem by Ford and Fulkerson (L. R. Ford Jr. and D. +R. Fulkerson. Maximal flow through a network. Canadian J. Math., +8:399-404, 1956.) the maximum flow between two vertices is the same as +the minimum cut between them (also called the minimum s-t cut). So +‘igraph_st_mincut_value()’ (*note igraph_st_mincut_value --- The minimum +s-t cut in a graph_::) gives the same result in all cases as +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + Note that the value of the maximum flow is the same as the minimum +cut in the graph. + + *Arguments:. * + +‘graph’: + The input graph, either directed or undirected. + +‘value’: + Pointer to a real number, the result will be placed here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + +‘capacity’: + Vector containing the capacity of the edges. If NULL, then every + edge is considered to have capacity 1.0. + +‘stats’: + Counts of the number of different operations preformed by the + algorithm are stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3). + + *See also:. * + +‘’ + ‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network flow + between a pair of vertices_::) to calculate the actual flow. + ‘igraph_mincut_value()’ (*note igraph_mincut_value --- The minimum + edge cut in a graph_::), ‘igraph_edge_connectivity()’ (*note + igraph_edge_connectivity --- The minimum edge connectivity in a + graph_::), ‘igraph_vertex_connectivity()’ (*note + igraph_vertex_connectivity --- The vertex connectivity of a + graph_::) for properties based on the maximum flow. + + +File: igraph-docs.info, Node: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_, Next: igraph_maxflow_stats_t --- Data structure holding statistics from the push-relabel maximum flow solver_, Prev: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_, Up: Maximum flows + +23.1.3 igraph_dominator_tree -- Calculates the dominator tree of a flowgraph. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_dominator_tree(const igraph_t *graph, + igraph_int_t root, + igraph_vector_int_t *dom, + igraph_t *domtree, + igraph_vector_int_t *leftout, + igraph_neimode_t mode); + + A flowgraph is a directed graph with a distinguished start (or root) +vertex r, such that for any vertex v, there is a path from r to v. A +vertex v dominates another vertex w (not equal to v), if every path from +r to w contains v. Vertex v is the immediate dominator or w, v=idom(w), +if v dominates w and every other dominator of w dominates v. The edges +{(idom(w), w)| w is not r} form a directed tree, rooted at r, called the +dominator tree of the graph. Vertex v dominates vertex w if and only if +v is an ancestor of w in the dominator tree. + + This function implements the Lengauer-Tarjan algorithm to construct +the dominator tree of a directed graph. For details please see Thomas +Lengauer, Robert Endre Tarjan: A fast algorithm for finding dominators +in a flowgraph, ACM Transactions on Programming Languages and Systems +(TOPLAS) I/1, 121-141, 1979. https://doi.org/10.1145/357062.357071 +(https://doi.org/10.1145/357062.357071) + + *Arguments:. * + +‘graph’: + A directed graph. If it is not a flowgraph, and it contains some + vertices not reachable from the root vertex, then these vertices + will be collected in the ‘leftout’ vector. + +‘root’: + The ID of the root (or source) vertex, this will be the root of the + tree. + +‘dom’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the immediate dominator of each vertex will be stored + here. For vertices that are not reachable from the root, ‘-2’ is + stored here. For the root vertex itself, ‘-1’ is added. + +‘domtree’: + Pointer to an _uninitialized_ ‘igraph_t’, or ‘NULL’. If not a null + pointer, then the dominator tree is returned here. The graph + contains the vertices that are unreachable from the root (if any), + these will be isolates. Graph and vertex attributes are preserved, + but edge attributes are discarded. + +‘leftout’: + Pointer to an initialized vector object, or ‘NULL’. If not ‘NULL’, + then the IDs of the vertices that are unreachable from the root + vertex (and thus not part of the dominator tree) are stored here. + +‘mode’: + Constant, must be ‘IGRAPH_IN’ or ‘IGRAPH_OUT’. If it is + ‘IGRAPH_IN’, then all directions are considered as opposite to the + original one in the input graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: very close to O(|E|+|V|), linear in the number of +edges and vertices. More precisely, it is O(|V|+|E|alpha(|E|,|V|)), +where alpha(|E|,|V|) is a functional inverse of Ackermann's function. + + * File examples/simple/dominator_tree.c* + + +File: igraph-docs.info, Node: igraph_maxflow_stats_t --- Data structure holding statistics from the push-relabel maximum flow solver_, Prev: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_, Up: Maximum flows + +23.1.4 igraph_maxflow_stats_t -- Data structure holding statistics from the push-relabel maximum flow solver. +------------------------------------------------------------------------------------------------------------- + + + typedef struct { + igraph_int_t nopush, norelabel, nogap, nogapnodes, nobfs; + + *Arguments:. * + +‘nopush’: + The number of push operations performed. + +‘norelabel’: + The number of relabel operarions performed. + +‘nogap’: + The number of times the gap heuristics was used. + +‘nogapnodes’: + The total number of vertices that were omitted form further + calculations because of the gap heuristics. + +‘nobfs’: + The number of times the reverse BFS was run to assign good values + to the height function. This includes an initial run before the + whole algorithm, so it is always at least one. + + +File: igraph-docs.info, Node: Cuts and minimum cuts, Next: Connectivity, Prev: Maximum flows, Up: Maximum flows; minimum cuts and related measures + +23.2 Cuts and minimum cuts +========================== + +* Menu: + +* igraph_st_mincut -- Minimum cut between a source and a target vertex.: igraph_st_mincut --- Minimum cut between a source and a target vertex_. +* igraph_st_mincut_value -- The minimum s-t cut in a graph.: igraph_st_mincut_value --- The minimum s-t cut in a graph_. +* igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph:: +* igraph_all_st_mincuts -- All minimum s-t cuts of a directed graph.: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_. +* igraph_mincut -- Calculates the minimum cut in a graph.: igraph_mincut --- Calculates the minimum cut in a graph_. +* igraph_mincut_value -- The minimum edge cut in a graph.: igraph_mincut_value --- The minimum edge cut in a graph_. +* igraph_gomory_hu_tree -- Gomory-Hu tree of a graph.: igraph_gomory_hu_tree --- Gomory-Hu tree of a graph_. + + +File: igraph-docs.info, Node: igraph_st_mincut --- Minimum cut between a source and a target vertex_, Next: igraph_st_mincut_value --- The minimum s-t cut in a graph_, Up: Cuts and minimum cuts + +23.2.1 igraph_st_mincut -- Minimum cut between a source and a target vertex. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_st_mincut(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_t *cut, igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); + + Finds the edge set that has the smallest total capacity among all +edge sets that disconnect the source and target vertices. + + The calculation is performed using maximum flow techniques, by +calling ‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network +flow between a pair of vertices_::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘value’: + Pointer to a real variable, the value of the cut is stored here. + +‘cut’: + Pointer to an initialized vector, the edge IDs that are included in + the cut are stored here. This argument is ignored if it is a null + pointer. + +‘partition’: + Pointer to an initialized vector, the vertex IDs of the vertices in + the first partition of the cut are stored here. The first + partition is always the one that contains the source vertex. This + argument is ignored if it is a null pointer. + +‘partition2’: + Pointer to an initialized vector, the vertex IDs of the vertices in + the second partition of the cut are stored here. The second + partition is always the one that contains the target vertex. This + argument is ignored if it is a null pointer. + +‘source’: + Integer, the id of the source vertex. + +‘target’: + Integer, the id of the target vertex. + +‘capacity’: + Vector containing the capacity of the edges. If a null pointer, + then every edge is considered to have capacity 1.0. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network flow + between a pair of vertices_::). + + Time complexity: see ‘igraph_maxflow()’ (*note igraph_maxflow --- +Maximum network flow between a pair of vertices_::). + + +File: igraph-docs.info, Node: igraph_st_mincut_value --- The minimum s-t cut in a graph_, Next: igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph, Prev: igraph_st_mincut --- Minimum cut between a source and a target vertex_, Up: Cuts and minimum cuts + +23.2.2 igraph_st_mincut_value -- The minimum s-t cut in a graph. +---------------------------------------------------------------- + + + igraph_error_t igraph_st_mincut_value(const igraph_t *graph, igraph_real_t *value, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); + + The minimum s-t cut in a weighted (=valued) graph is the total +minimum edge weight needed to remove from the graph to eliminate all +paths from a given vertex (‘source’) to another vertex (‘target’). +Directed paths are considered in directed graphs, and undirected paths +in undirected graphs. + + The minimum s-t cut between two vertices is known to be same as the +maximum flow between these two vertices. So this function calls +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::) to do the calculation. + + *Arguments:. * + +‘graph’: + The input graph. + +‘value’: + Pointer to a real variable, the result will be stored here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + +‘capacity’: + Pointer to the capacity vector, it should contain non-negative + numbers and its length should be the same the the number of edges + in the graph. It can be a null pointer, then every edge has unit + capacity. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3), see also the discussion for +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::), |V| is the number of +vertices. + + +File: igraph-docs.info, Node: igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph, Next: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_, Prev: igraph_st_mincut_value --- The minimum s-t cut in a graph_, Up: Cuts and minimum cuts + +23.2.3 igraph_all_st_cuts -- List all edge-cuts between two vertices in a directed graph +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_all_st_cuts(const igraph_t *graph, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target); + + This function lists all edge-cuts between a source and a target +vertex. Every cut is listed exactly once. The implemented algorithm is +described in JS Provan and DR Shier: A Paradigm for listing (s,t)-cuts +in graphs, Algorithmica 15, 351-372, 1996. + + *Arguments:. * + +‘graph’: + The input graph, is must be directed. + +‘cuts’: + An initialized list of integer vectors, the cuts are stored here. + Each vector will contain the IDs of the edges in the cut. This + argument is ignored if it is a null pointer. + +‘partition1s’: + An initialized list of integer vectors, the list of vertex sets + generating the actual edge cuts are stored here. Each vector + contains a set of vertex IDs. If X is such a set, then all edges + going from X to the complement of X form an (s, t) edge-cut in the + graph. This argument is ignored if it is a null pointer. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n(|V|+|E|)), where |V| is the number of vertices, +|E| is the number of edges, and n is the number of cuts. + + +File: igraph-docs.info, Node: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_, Next: igraph_mincut --- Calculates the minimum cut in a graph_, Prev: igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph, Up: Cuts and minimum cuts + +23.2.4 igraph_all_st_mincuts -- All minimum s-t cuts of a directed graph. +------------------------------------------------------------------------- + + + igraph_error_t igraph_all_st_mincuts(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target, + const igraph_vector_t *capacity); + + This function lists all edge cuts between two vertices, in a directed +graph, with minimum total capacity. Possibly, multiple cuts may have +the same total capacity, although there is often only one minimum cut in +weighted graphs. It is recommended to supply integer-values capacities. +Otherwise, not all minimum cuts may be detected because of numerical +roundoff errors. The implemented algorithm is described in JS Provan +and DR Shier: A Paradigm for listing (s,t)-cuts in graphs, Algorithmica +15, 351-372, 1996. + + *Arguments:. * + +‘graph’: + The input graph, it must be directed. + +‘value’: + Pointer to a real number or ‘NULL’. The value of the minimum cut + is stored here, unless it is a null pointer. + +‘cuts’: + Pointer to initialized list of integer vectors or ‘NULL’. The cuts + are stored here as lists of vertex IDs. + +‘partition1s’: + Pointer to an initialized list of integer vectors or ‘NULL’. The + list of vertex sets, generating the actual edge cuts, are stored + here. Each vector contains a set of vertex IDs. If X is such a + set, then all edges going from X to the complement of X form an + (s,t) edge-cut in the graph. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + +‘capacity’: + Vector of edge capacities. All capacities must be strictly + positive. If this is a null pointer, then all edges are assumed to + have capacity one. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n(|V|+|E|))+O(F), where |V| is the number of +vertices, |E| is the number of edges, and n is the number of cuts; O(F) +is the time complexity of the maximum flow algorithm, see +‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network flow +between a pair of vertices_::). + + * File examples/simple/igraph_all_st_mincuts.c* + + +File: igraph-docs.info, Node: igraph_mincut --- Calculates the minimum cut in a graph_, Next: igraph_mincut_value --- The minimum edge cut in a graph_, Prev: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_, Up: Cuts and minimum cuts + +23.2.5 igraph_mincut -- Calculates the minimum cut in a graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_mincut(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_vector_int_t *cut, + const igraph_vector_t *capacity); + + This function calculates the minimum cut in a graph. The minimum cut +is the minimum set of edges which needs to be removed to disconnect the +graph. The minimum is calculated using the weights (‘capacity’) of the +edges, so the cut with the minimum total capacity is calculated. + + For directed graphs an implementation based on calculating 2|V|-2 +maximum flows is used. For undirected graphs we use the Stoer-Wagner +algorithm, as described in M. Stoer and F. Wagner: A simple min-cut +algorithm, Journal of the ACM, 44 585-591, 1997. + + The first implementation of the actual cut calculation for undirected +graphs was made by Gregory Benison, thanks Greg. + + *Arguments:. * + +‘graph’: + The input graph. + +‘value’: + Pointer to a float, the value of the cut will be stored here. + +‘partition’: + Pointer to an initialized vector, the ids of the vertices in the + first partition after separating the graph will be stored here. + The vector will be resized as needed. This argument is ignored if + it is a NULL pointer. + +‘partition2’: + Pointer to an initialized vector the ids of the vertices in the + second partition will be stored here. The vector will be resized + as needed. This argument is ignored if it is a NULL pointer. + +‘cut’: + Pointer to an initialized vector, the IDs of the edges in the cut + will be stored here. This argument is ignored if it is a NULL + pointer. + +‘capacity’: + A numeric vector giving the capacities of the edges. If a null + pointer then all edges have unit capacity. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_mincut_value()’ (*note igraph_mincut_value --- The minimum + edge cut in a graph_::), a simpler interface for calculating the + value of the cut only. + + Time complexity: for directed graphs it is O(|V|^4), but see the +remarks at ‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network +flow between a pair of vertices_::). For undirected graphs it is +O(|V||E|+|V|^2 log|V|). |V| and |E| are the number of vertices and +edges respectively. + + * File examples/simple/igraph_mincut.c* + + +File: igraph-docs.info, Node: igraph_mincut_value --- The minimum edge cut in a graph_, Next: igraph_gomory_hu_tree --- Gomory-Hu tree of a graph_, Prev: igraph_mincut --- Calculates the minimum cut in a graph_, Up: Cuts and minimum cuts + +23.2.6 igraph_mincut_value -- The minimum edge cut in a graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_mincut_value(const igraph_t *graph, igraph_real_t *res, + const igraph_vector_t *capacity); + + The minimum edge cut in a graph is the total minimum weight of the +edges needed to remove from the graph to make the graph _not_ strongly +connected. (If the original graph is not strongly connected then this +is zero.) Note that in undirected graphs strong connectedness is the +same as weak connectedness. + + The minimum cut can be calculated with maximum flow techniques, +although the current implementation does this only for directed graphs +and a separate non-flow based implementation is used for undirected +graphs. See Mechthild Stoer and Frank Wagner: A simple min-cut +algorithm, Journal of the ACM 44 585-591, 1997. For directed graphs the +maximum flow is calculated between a fixed vertex and all the other +vertices in the graph and this is done in both directions. Then the +minimum is taken to get the minimum cut. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to a real variable, the result will be stored here. + +‘capacity’: + Pointer to the capacity vector, it should contain the same number + of non-negative numbers as the number of edges in the graph. If a + null pointer then all edges will have unit capacity. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_mincut()’ (*note igraph_mincut --- Calculates the minimum + cut in a graph_::), ‘igraph_maxflow_value()’ (*note + igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::), ‘igraph_st_mincut_value()’ (*note + igraph_st_mincut_value --- The minimum s-t cut in a graph_::). + + Time complexity: O(log(|V|)*|V|^2) for undirected graphs and O(|V|^4) +for directed graphs, but see also the discussion at the documentation of +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + +File: igraph-docs.info, Node: igraph_gomory_hu_tree --- Gomory-Hu tree of a graph_, Prev: igraph_mincut_value --- The minimum edge cut in a graph_, Up: Cuts and minimum cuts + +23.2.7 igraph_gomory_hu_tree -- Gomory-Hu tree of a graph. +---------------------------------------------------------- + + + igraph_error_t igraph_gomory_hu_tree(const igraph_t *graph, + igraph_t *tree, + igraph_vector_t *flows, + const igraph_vector_t *capacity); + + The Gomory-Hu tree is a concise representation of the value of all +the maximum flows (or minimum cuts) in a graph. The vertices of the +tree correspond exactly to the vertices of the original graph in the +same order. Edges of the Gomory-Hu tree are annotated by flow values. +The value of the maximum flow (or minimum cut) between an arbitrary +(u,v) vertex pair in the original graph is then given by the minimum +flow value (i.e. edge annotation) along the shortest path between u and +v in the Gomory-Hu tree. + + This implementation uses Gusfield's algorithm to construct the +Gomory-Hu tree. See the following paper for more details: + + Reference: + + Gusfield D: Very simple methods for all pairs network flow analysis. +SIAM J Comput 19(1):143-155, 1990 https://doi.org/10.1137/0219009 +(https://doi.org/10.1137/0219009). + + *Arguments:. * + +‘graph’: + The input graph. + +‘tree’: + Pointer to an uninitialized graph; the result will be stored here. + +‘flows’: + Pointer to an uninitialized vector; the flow values corresponding + to each edge in the Gomory-Hu tree will be returned here. You may + pass a NULL pointer here if you are not interested in the flow + values. + +‘capacity’: + Vector containing the capacity of the edges. If NULL, then every + edge is considered to have capacity 1.0. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^4) since it performs a max-flow calculation +between vertex zero and every other vertex and max-flow is O(|V|^3). + + *See also:. * + +‘’ + ‘igraph_maxflow()’ (*note igraph_maxflow --- Maximum network flow + between a pair of vertices_::) + + +File: igraph-docs.info, Node: Connectivity, Next: Edge- and vertex-disjoint paths, Prev: Cuts and minimum cuts, Up: Maximum flows; minimum cuts and related measures + +23.3 Connectivity +================= + +* Menu: + +* igraph_st_edge_connectivity -- Edge connectivity of a pair of vertices.: igraph_st_edge_connectivity --- Edge connectivity of a pair of vertices_. +* igraph_edge_connectivity -- The minimum edge connectivity in a graph.: igraph_edge_connectivity --- The minimum edge connectivity in a graph_. +* igraph_st_vertex_connectivity -- The vertex connectivity of a pair of vertices.: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_. +* igraph_vertex_connectivity -- The vertex connectivity of a graph.: igraph_vertex_connectivity --- The vertex connectivity of a graph_. + + +File: igraph-docs.info, Node: igraph_st_edge_connectivity --- Edge connectivity of a pair of vertices_, Next: igraph_edge_connectivity --- The minimum edge connectivity in a graph_, Up: Connectivity + +23.3.1 igraph_st_edge_connectivity -- Edge connectivity of a pair of vertices. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_st_edge_connectivity(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); + + The edge connectivity of two vertices (‘source’ and ‘target’) is the +minimum number of edges that have to be deleted from the graph to +eliminate all paths from ‘source’ to ‘target’. + + This function uses the maximum flow algorithm to calculate the edge +connectivity. + + *Arguments:. * + +‘graph’: + The input graph, it has to be directed. + +‘res’: + Pointer to an integer, the result will be stored here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3). + + *See also:. * + +‘’ + ‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum + flow in a network with the push/relabel algorithm_::), + ‘igraph_edge_disjoint_paths()’ (*note igraph_edge_disjoint_paths + --- The maximum number of edge-disjoint paths between two + vertices_::), ‘igraph_edge_connectivity()’ (*note + igraph_edge_connectivity --- The minimum edge connectivity in a + graph_::), ‘igraph_st_vertex_connectivity()’ (*note + igraph_st_vertex_connectivity --- The vertex connectivity of a pair + of vertices_::), ‘igraph_vertex_connectivity()’ (*note + igraph_vertex_connectivity --- The vertex connectivity of a + graph_::). + + +File: igraph-docs.info, Node: igraph_edge_connectivity --- The minimum edge connectivity in a graph_, Next: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_, Prev: igraph_st_edge_connectivity --- Edge connectivity of a pair of vertices_, Up: Connectivity + +23.3.2 igraph_edge_connectivity -- The minimum edge connectivity in a graph. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_edge_connectivity(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks); + + This is the minimum of the edge connectivity over all pairs of +vertices in the graph. + + The edge connectivity of a graph is the same as group adhesion as +defined in Douglas R. White and Frank Harary: The cohesiveness of blocks +in social networks: node connectivity and conditional density, +Sociological Methodology 31:305-359, 2001 +https://doi.org/10.1111/0081-1750.00098 +(https://doi.org/10.1111/0081-1750.00098). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer, the result will be stored here. + +‘checks’: + Boolean constant. Whether to check that the graph is connected and + also the degree of the vertices. If the graph is not (strongly) + connected then the connectivity is obviously zero. Otherwise if + the minimum degree is one then the edge connectivity is also one. + It is a good idea to perform these checks, as they can be done + quickly compared to the connectivity calculation itself. They were + suggested by Peter McMahan, thanks Peter. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(log(|V|)*|V|^2) for undirected graphs and O(|V|^4) +for directed graphs, but see also the discussion at the documentation of +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + *See also:. * + +‘’ + ‘igraph_st_edge_connectivity()’ (*note igraph_st_edge_connectivity + --- Edge connectivity of a pair of vertices_::), + ‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum + flow in a network with the push/relabel algorithm_::), + ‘igraph_vertex_connectivity()’ (*note igraph_vertex_connectivity + --- The vertex connectivity of a graph_::). + + +File: igraph-docs.info, Node: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_, Next: igraph_vertex_connectivity --- The vertex connectivity of a graph_, Prev: igraph_edge_connectivity --- The minimum edge connectivity in a graph_, Up: Connectivity + +23.3.3 igraph_st_vertex_connectivity -- The vertex connectivity of a pair of vertices. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_st_vertex_connectivity( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors); + + The vertex connectivity of two vertices (‘source’ and ‘target’) is +the minimum number of vertices that must be deleted to eliminate all +paths from ‘source’ to ‘target’. Directed paths are considered in +directed graphs. + + The vertex connectivity of a pair is the same as the number of +different (i.e. node-independent) paths from source to target, assuming +no direct edges between them. + + The current implementation uses maximum flow calculations to obtain +the result. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer, the result will be stored here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + +‘neighbors’: + A constant giving what to do if the two vertices are connected. + Possible values: ‘IGRAPH_VCONN_NEI_ERROR’, stop with an error + message, ‘IGRAPH_VCONN_NEI_NEGATIVE’, return -1. + ‘IGRAPH_VCONN_NEI_NUMBER_OF_NODES’, return the number of nodes. + ‘IGRAPH_VCONN_NEI_IGNORE’, ignore the fact that the two vertices + are connected and calculate the number of vertices needed to + eliminate all paths except for the trivial (direct) paths between + ‘source’ and ‘vertex’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3), but see the discussion at +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + *See also:. * + +‘’ + ‘igraph_vertex_connectivity()’ (*note igraph_vertex_connectivity + --- The vertex connectivity of a graph_::), + ‘igraph_edge_connectivity()’ (*note igraph_edge_connectivity --- + The minimum edge connectivity in a graph_::), + ‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum + flow in a network with the push/relabel algorithm_::). + + +File: igraph-docs.info, Node: igraph_vertex_connectivity --- The vertex connectivity of a graph_, Prev: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_, Up: Connectivity + +23.3.4 igraph_vertex_connectivity -- The vertex connectivity of a graph. +------------------------------------------------------------------------ + + + igraph_error_t igraph_vertex_connectivity( + const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks); + + The vertex connectivity of a graph is the minimum vertex connectivity +along each pairs of vertices in the graph. + + The vertex connectivity of a graph is the same as group cohesion as +defined in Douglas R. White and Frank Harary: The cohesiveness of blocks +in social networks: node connectivity and conditional density, +Sociological Methodology 31:305-359, 2001 +https://doi.org/10.1111/0081-1750.00098 +(https://doi.org/10.1111/0081-1750.00098). + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer, the result will be stored here. + +‘checks’: + Boolean constant. Whether to check if the graph is connected or + complete and also the degree of the vertices. If the graph is not + (strongly) connected then the connectivity is obviously zero. + Otherwise if the minimum degree is 1 then the vertex connectivity + is also 1. If the graph is complete, the connectivity is the + vertex count minus one. It is a good idea to perform these checks, + as they can be done quickly compared to the connectivity + calculation itself. They were suggested by Peter McMahan, thanks + Peter. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^5). + + *See also:. * + +‘’ + ‘igraph_st_vertex_connectivity()’ (*note + igraph_st_vertex_connectivity --- The vertex connectivity of a pair + of vertices_::), ‘igraph_maxflow_value()’ (*note + igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::), and ‘igraph_edge_connectivity()’ (*note + igraph_edge_connectivity --- The minimum edge connectivity in a + graph_::). + + +File: igraph-docs.info, Node: Edge- and vertex-disjoint paths, Next: Graph adhesion and cohesion, Prev: Connectivity, Up: Maximum flows; minimum cuts and related measures + +23.4 Edge- and vertex-disjoint paths +==================================== + +* Menu: + +* igraph_edge_disjoint_paths -- The maximum number of edge-disjoint paths between two vertices.: igraph_edge_disjoint_paths --- The maximum number of edge-disjoint paths between two vertices_. +* igraph_vertex_disjoint_paths -- Maximum number of vertex-disjoint paths between two vertices.: igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint paths between two vertices_. + + +File: igraph-docs.info, Node: igraph_edge_disjoint_paths --- The maximum number of edge-disjoint paths between two vertices_, Next: igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint paths between two vertices_, Up: Edge- and vertex-disjoint paths + +23.4.1 igraph_edge_disjoint_paths -- The maximum number of edge-disjoint paths between two vertices. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_edge_disjoint_paths(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); + + A set of paths between two vertices is called edge-disjoint if they +do not share any edges. The maximum number of edge-disjoint paths are +calculated by this function using maximum flow techniques. Directed +paths are considered in directed graphs. + + Note that the number of disjoint paths is the same as the edge +connectivity of the two vertices using uniform edge weights. + + *Arguments:. * + +‘graph’: + The input graph, can be directed or undirected. + +‘res’: + Pointer to an integer variable, the result will be stored here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3), but see the discussion at +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + *See also:. * + +‘’ + ‘igraph_vertex_disjoint_paths()’ (*note + igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint + paths between two vertices_::), ‘igraph_st_edge_connectivity()’ + (*note igraph_st_edge_connectivity --- Edge connectivity of a pair + of vertices_::), ‘igraph_maxflow_value()’ (*note + igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::). + + +File: igraph-docs.info, Node: igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint paths between two vertices_, Prev: igraph_edge_disjoint_paths --- The maximum number of edge-disjoint paths between two vertices_, Up: Edge- and vertex-disjoint paths + +23.4.2 igraph_vertex_disjoint_paths -- Maximum number of vertex-disjoint paths between two vertices. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_vertex_disjoint_paths(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); + + A set of paths between two vertices is called vertex-disjoint if they +share no vertices, other than the endpoints. This function computes the +largest number of such paths that can be constructed between a source +and a target vertex. The calculation is performed by using maximum flow +techniques. + + When there are no edges from the source to the target, the number of +vertex-disjoint paths is the same as the vertex connectivity of the two +vertices. When some edges are present, each one of them contributes one +extra path. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer variable, the result will be stored here. + +‘source’: + The id of the source vertex. + +‘target’: + The id of the target vertex. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^3). + + *See also:. * + +‘’ + ‘igraph_edge_disjoint_paths()’ (*note igraph_edge_disjoint_paths + --- The maximum number of edge-disjoint paths between two + vertices_::), ‘igraph_st_vertex_connectivity()’ (*note + igraph_st_vertex_connectivity --- The vertex connectivity of a pair + of vertices_::), ‘igraph_maxflow_value()’ (*note + igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::). + + +File: igraph-docs.info, Node: Graph adhesion and cohesion, Next: Cohesive blocks, Prev: Edge- and vertex-disjoint paths, Up: Maximum flows; minimum cuts and related measures + +23.5 Graph adhesion and cohesion +================================ + +* Menu: + +* igraph_adhesion -- Graph adhesion, this is (almost) the same as edge connectivity.: igraph_adhesion --- Graph adhesion; this is [almost] the same as edge connectivity_. +* igraph_cohesion -- Graph cohesion, this is the same as vertex connectivity.: igraph_cohesion --- Graph cohesion; this is the same as vertex connectivity_. + + +File: igraph-docs.info, Node: igraph_adhesion --- Graph adhesion; this is [almost] the same as edge connectivity_, Next: igraph_cohesion --- Graph cohesion; this is the same as vertex connectivity_, Up: Graph adhesion and cohesion + +23.5.1 igraph_adhesion -- Graph adhesion, this is (almost) the same as edge connectivity. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_adhesion(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks); + + This quantity is defined by White and Harary in The cohesiveness of +blocks in social networks: node connectivity and conditional density, +(Sociological Methodology 31:305-359, 2001) and basically it is the edge +connectivity of the graph with uniform edge weights. + + *Arguments:. * + +‘graph’: + The input graph, either directed or undirected. + +‘res’: + Pointer to an integer, the result will be stored here. + +‘checks’: + Boolean constant. Whether to check that the graph is connected and + also the degree of the vertices. If the graph is not (strongly) + connected then the adhesion is obviously zero. Otherwise if the + minimum degree is one then the adhesion is also one. It is a good + idea to perform these checks, as they can be done quickly compared + to the edge connectivity calculation itself. They were suggested + by Peter McMahan, thanks Peter. * + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(log(|V|)*|V|^2) for undirected graphs and O(|V|^4) +for directed graphs, but see also the discussion at the documentation of +‘igraph_maxflow_value()’ (*note igraph_maxflow_value --- Maximum flow in +a network with the push/relabel algorithm_::). + + *See also:. * + +‘’ + ‘igraph_cohesion()’ (*note igraph_cohesion --- Graph cohesion; this + is the same as vertex connectivity_::), ‘igraph_maxflow_value()’ + (*note igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::), ‘igraph_edge_connectivity()’ (*note + igraph_edge_connectivity --- The minimum edge connectivity in a + graph_::), ‘igraph_mincut_value()’ (*note igraph_mincut_value --- + The minimum edge cut in a graph_::). + + +File: igraph-docs.info, Node: igraph_cohesion --- Graph cohesion; this is the same as vertex connectivity_, Prev: igraph_adhesion --- Graph adhesion; this is [almost] the same as edge connectivity_, Up: Graph adhesion and cohesion + +23.5.2 igraph_cohesion -- Graph cohesion, this is the same as vertex connectivity. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_cohesion(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks); + + This quantity was defined by White and Harary in 'The cohesiveness of +blocks in social networks: node connectivity and conditional density', +(Sociological Methodology 31:305-359, 2001) and it is the same as the +vertex connectivity of a graph. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an integer variable, the result will be stored here. + +‘checks’: + Boolean constant. Whether to check that the graph is connected and + also the degree of the vertices. If the graph is not (strongly) + connected then the cohesion is obviously zero. Otherwise if the + minimum degree is one then the cohesion is also one. It is a good + idea to perform these checks, as they can be done quickly compared + to the vertex connectivity calculation itself. They were suggested + by Peter McMahan, thanks Peter. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^4), |V| is the number of vertices. In +practice it is more like O(|V|^2), see ‘igraph_maxflow_value()’ (*note +igraph_maxflow_value --- Maximum flow in a network with the push/relabel +algorithm_::). + + *See also:. * + +‘’ + ‘igraph_vertex_connectivity()’ (*note igraph_vertex_connectivity + --- The vertex connectivity of a graph_::), ‘igraph_adhesion()’ + (*note igraph_adhesion --- Graph adhesion; this is [almost] the + same as edge connectivity_::), ‘igraph_maxflow_value()’ (*note + igraph_maxflow_value --- Maximum flow in a network with the + push/relabel algorithm_::). + + +File: igraph-docs.info, Node: Cohesive blocks, Prev: Graph adhesion and cohesion, Up: Maximum flows; minimum cuts and related measures + +23.6 Cohesive blocks +==================== + +* Menu: + +* igraph_cohesive_blocks -- Identifies the hierarchical cohesive block structure of a graph.: igraph_cohesive_blocks --- Identifies the hierarchical cohesive block structure of a graph_. + + +File: igraph-docs.info, Node: igraph_cohesive_blocks --- Identifies the hierarchical cohesive block structure of a graph_, Up: Cohesive blocks + +23.6.1 igraph_cohesive_blocks -- Identifies the hierarchical cohesive block structure of a graph. +------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_cohesive_blocks(const igraph_t *graph, + igraph_vector_int_list_t *blocks, + igraph_vector_int_t *cohesion, + igraph_vector_int_t *parent, + igraph_t *block_tree); + + Cohesive blocking is a method of determining hierarchical subsets of +graph vertices based on their structural cohesion (or vertex +connectivity). For a given graph G, a subset of its vertices S is said +to be maximally k-cohesive if there is no superset of S with vertex +connectivity greater than or equal to k. Cohesive blocking is a process +through which, given a k-cohesive set of vertices, maximally l-cohesive +subsets are recursively identified with l>k. Thus a hiearchy of vertex +subsets is found, with the entire graph G at its root. + + This function implements cohesive blocking and calculates the +complete cohesive block hierarchy of a graph. + + See the following reference for details: + + J. Moody and D. R. White. Structural cohesion and embeddedness: A +hierarchical concept of social groups. American Sociological Review, +68(1):103-127, Feb 2003. https://doi.org/10.2307/3088904 +(https://doi.org/10.2307/3088904) + + *Arguments:. * + +‘graph’: + The input graph. It must be undirected and simple. See + ‘igraph_is_simple()’ (*note igraph_is_simple --- Decides whether + the input graph is a simple graph_::). + +‘blocks’: + If not a null pointer, then it must be an initialized list of + integers vectors; the cohesive blocks will be stored here. Each + block is encoded with a vector of type ‘igraph_vector_int_t’ (*note + About igraph_vector_t objects::) that contains the vertex IDs of + the block. + +‘cohesion’: + If not a null pointer, then it must be an initialized vector and + the cohesion of the blocks is stored here, in the same order as the + blocks in the ‘blocks’ vector list. + +‘parent’: + If not a null pointer, then it must be an initialized vector and + the block hierarchy is stored here. For each block, the ID (i.e. + the position in the ‘blocks’ vector list) of its parent block is + stored. For the top block in the hierarchy, ‘-1’ is stored. + +‘block_tree’: + If not a null pointer, then it must be a pointer to an + uninitialized graph, and the block hierarchy is stored here as an + igraph graph. The vertex IDs correspond to the order of the blocks + in the ‘blocks’ vector. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/cohesive_blocks.c* + + +File: igraph-docs.info, Node: Vertex separators, Next: Detecting community structure, Prev: Maximum flows; minimum cuts and related measures, Up: Top + +24 Vertex separators +******************** + +* Menu: + +* igraph_is_separator --- Would removing this set of vertices disconnect the graph?:: +* igraph_is_minimal_separator -- Decides whether a set of vertices is a minimal separator.: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_. +* igraph_all_minimal_st_separators -- List all vertex sets that are minimal (s,t) separators for some s and t.: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_. +* igraph_minimum_size_separators -- Find all minimum size separating vertex sets.: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_. +* igraph_even_tarjan_reduction -- Even-Tarjan reduction of a graph.: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_. + + +File: igraph-docs.info, Node: igraph_is_separator --- Would removing this set of vertices disconnect the graph?, Next: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_, Up: Vertex separators + +24.1 igraph_is_separator -- Would removing this set of vertices disconnect the graph? +===================================================================================== + + + igraph_error_t igraph_is_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res); + + A vertex set ‘S’ is a separator if there are vertices ‘u’ and ‘v’ in +the graph such that all paths between ‘u’ and ‘v’ pass through some +vertices in ‘S’. + + *Arguments:. * + +‘graph’: + The input graph. It may be directed, but edge directions are + ignored. + +‘candidate’: + The candidate separator. + +‘res’: + Pointer to a boolean variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number vertices and edges. + + * File examples/simple/igraph_is_separator.c* + + +File: igraph-docs.info, Node: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_, Next: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_, Prev: igraph_is_separator --- Would removing this set of vertices disconnect the graph?, Up: Vertex separators + +24.2 igraph_is_minimal_separator -- Decides whether a set of vertices is a minimal separator. +============================================================================================= + + + igraph_error_t igraph_is_minimal_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res); + + A vertex separator ‘S’ is minimal is no proper subset of ‘S’ is also +a separator. + + *Arguments:. * + +‘graph’: + The input graph. It may be directed, but edge directions are + ignored. + +‘candidate’: + The candidate minimal separators. + +‘res’: + Pointer to a boolean variable, the result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), linear in the number vertices and edges. + + * File examples/simple/igraph_is_minimal_separator.c* + + +File: igraph-docs.info, Node: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_, Next: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_, Prev: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_, Up: Vertex separators + +24.3 igraph_all_minimal_st_separators -- List all vertex sets that are minimal (s,t) separators for some s and t. +================================================================================================================= + + + igraph_error_t igraph_all_minimal_st_separators( + const igraph_t *graph, igraph_vector_int_list_t *separators + ); + + This function lists all vertex sets that are minimal (s,t) separators +for some (s,t) vertex pair. + + Note that some vertex sets returned by this function may not be +minimal with respect to disconnecting the graph (or increasing the +number of connected components). Take for example the 5-vertex graph +with edges ‘0-1-2-3-4-1’. This function returns the vertex sets ‘{1}’, +‘{2,4}’ and ‘{1,3}’. Notice that ‘{1,3}’ is not minimal with respect to +disconnecting the graph, as ‘{1}’ would be sufficient for that. +However, it is minimal with respect to separating vertices ‘2’ and ‘4’. + + See more about the implemented algorithm in Anne Berry, Jean-Paul +Bordat and Olivier Cogis: Generating All the Minimal Separators of a +Graph, In: Peter Widmayer, Gabriele Neyer and Stephan Eidenbenz +(editors): Graph-theoretic concepts in computer science, 1665, 167-172, +1999. Springer. https://doi.org/10.1007/3-540-46784-X_17 +(https://doi.org/10.1007/3-540-46784-X_17) + + *Arguments:. * + +‘graph’: + The input graph. It may be directed, but edge directions are + ignored. + +‘separators’: + Pointer to a list of integer vectors, the separators will be stored + here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_minimum_size_separators()’ (*note + igraph_minimum_size_separators --- Find all minimum size separating + vertex sets_::) + + Time complexity: O(n|V|^3), |V| is the number of vertices, n is the +number of separators. + + * File examples/simple/igraph_minimal_separators.c* + + +File: igraph-docs.info, Node: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_, Next: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_, Prev: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_, Up: Vertex separators + +24.4 igraph_minimum_size_separators -- Find all minimum size separating vertex sets. +==================================================================================== + + + igraph_error_t igraph_minimum_size_separators( + const igraph_t *graph, igraph_vector_int_list_t *separators + ); + + This function lists all separator vertex sets of minimum size. A +vertex set is a separator if its removal disconnects the graph. + + If the graph is already disconnected, no separators are returned. +Note that this convention differs from that used by some other funtions +such as ‘igraph_all_minimal_st_separators()’ (*note +igraph_all_minimal_st_separators --- List all vertex sets that are +minimal [s;t] separators for some s and t_::). + + Complete graphs have no vertex separators. + + The implementation is based on the following paper: Arkady Kanevsky: +Finding all minimum-size separating vertex sets in a graph, Networks 23, +533-541, 1993. https://doi.org/10.1002/net.3230230604 +(https://doi.org/10.1002/net.3230230604) + + *Arguments:. * + +‘graph’: + The input graph, which must be undirected. + +‘separators’: + An initialized list of integer vectors, the separators are stored + here. It is a list of pointers to ‘igraph_vector_int_t’ objects. + Each vector will contain the IDs of the vertices in the separator. + The separators are returned in an arbitrary order. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/igraph_minimum_size_separators.c* + + +File: igraph-docs.info, Node: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_, Prev: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_, Up: Vertex separators + +24.5 igraph_even_tarjan_reduction -- Even-Tarjan reduction of a graph. +====================================================================== + + + igraph_error_t igraph_even_tarjan_reduction(const igraph_t *graph, igraph_t *graphbar, + igraph_vector_t *capacity); + + A digraph is created with twice as many vertices and edges. For each +original vertex ‘i’, two vertices ‘i' = i’ and ‘i'' = i' + n’ are +created, with a directed edge from ‘i'’ to ‘i''’. For each original +directed edge from ‘i’ to ‘j’, two new edges are created, from ‘i'’ to +‘j''’ and from ‘i''’ to ‘j'’. + + This reduction is used in the paper (observation 2): Arkady Kanevsky: +Finding all minimum-size separating vertex sets in a graph, Networks 23, +533-541, 1993. + + The original paper where this reduction was conceived is Shimon Even +and R. Endre Tarjan: Network Flow and Testing Graph Connectivity, SIAM +J. Comput., 4(4), 507-518. https://doi.org/10.1137/0204043 +(https://doi.org/10.1137/0204043) + + *Arguments:. * + +‘graph’: + A graph. Although directness is not checked, this function is + commonly used only on directed graphs. + +‘graphbar’: + Pointer to a new directed graph that will contain the reduction, + with twice as many vertices and edges. + +‘capacity’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then it will be filled the capacity from the reduction: + the first |E| elements are 1, the remaining |E| are equal to |V| + (which is used to indicate infinity). + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|+|V|). + + * File examples/simple/even_tarjan.c* + + +File: igraph-docs.info, Node: Detecting community structure, Next: Graphlets, Prev: Vertex separators, Up: Top + +25 Detecting community structure +******************************** + +Community detection is concerned with clustering the vertices of +networks into tightly connected subgraphs called "communities". The +following references provide a good introduction to the topic of +community detection: + + S. Fortunato: "Community Detection in Graphs". Physics Reports 486, +no. 3-5 (2010): 75-174. https://doi.org/10.1016/j.physrep.2009.11.002 +(https://doi.org/10.1016/j.physrep.2009.11.002). + + S. Fortunato and D. Hric: "Community Detection in Networks: A User +Guide". Physics Reports 659 (2016): 1-44. +https://doi.org/10.1016/j.physrep.2016.09.002 +(https://doi.org/10.1016/j.physrep.2016.09.002). + +* Menu: + +* Common functions related to community structure:: +* Community structure based on statistical mechanics:: +* Community structure based on eigenvectors of matrices:: +* Walktrap; Community structure based on random walks:: +* Edge betweenness based community detection:: +* Community structure based on the optimization of modularity:: +* Fluid communities:: +* Label propagation:: +* The InfoMAP algorithm:: +* Voronoi communities:: + + +File: igraph-docs.info, Node: Common functions related to community structure, Next: Community structure based on statistical mechanics, Up: Detecting community structure + +25.1 Common functions related to community structure +==================================================== + +* Menu: + +* igraph_modularity -- Calculates the modularity of a graph with respect to some clusters or vertex types.: igraph_modularity --- Calculates the modularity of a graph with respect to some clusters or vertex types_. +* igraph_modularity_matrix -- Calculates the modularity matrix.: igraph_modularity_matrix --- Calculates the modularity matrix_. +* igraph_community_optimal_modularity -- Calculate the community structure with the highest modularity value.: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_. +* igraph_community_to_membership -- Cut a dendrogram after a given number of merges.: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_. +* igraph_reindex_membership -- Makes the IDs in a membership vector contiguous.: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_. +* igraph_compare_communities -- Compares community structures using various metrics.: igraph_compare_communities --- Compares community structures using various metrics_. +* igraph_split_join_distance -- Calculates the split-join distance of two community structures.: igraph_split_join_distance --- Calculates the split-join distance of two community structures_. + + +File: igraph-docs.info, Node: igraph_modularity --- Calculates the modularity of a graph with respect to some clusters or vertex types_, Next: igraph_modularity_matrix --- Calculates the modularity matrix_, Up: Common functions related to community structure + +25.1.1 igraph_modularity -- Calculates the modularity of a graph with respect to some clusters or vertex types. +--------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_modularity(const igraph_t *graph, + const igraph_vector_int_t *membership, + const igraph_vector_t *weights, + const igraph_real_t resolution, + const igraph_bool_t directed, + igraph_real_t *modularity); + + The modularity of a graph with respect to some clustering of the +vertices (or assignment of vertex types) measures how strongly separated +the different clusters are from each other compared to a random null +model. It is defined as + + ‘Q = 1/(2m) sum_ij (A_ij - γ k_i k_j / (2m)) δ(c_i,c_j)’, + + where ‘m’ is the number of edges, ‘A_ij’ is the adjacency matrix, +‘k_i’ is the degree of vertex ‘i’, ‘c_i’ is the cluster that vertex ‘i’ +belongs to (or its vertex type), ‘δ(i,j)=1’ if ‘i=j’ and 0 otherwise, +and the sum goes over all ‘i’, ‘j’ pairs of vertices. Note that in this +formula, the diagonal of the adjacency matrix contains twice the number +of self-loops. + + The resolution parameter ‘γ’ allows weighting the random null model, +which might be useful when finding partitions with a high modularity. +Maximizing modularity with higher values of the resolution parameter +typically results in more, smaller clusters when finding partitions with +a high modularity. Lower values typically results in fewer, larger +clusters. The original definition of modularity is retrieved when +setting ‘γ = 1’. + + Modularity can also be calculated on directed graphs. This only +requires a relatively modest change, + + ‘Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i,c_j)’, + + where ‘k^out_i’ is the out-degree of node ‘i’ and ‘k^in_j’ is the +in-degree of node ‘j’. + + Modularity on weighted graphs is also meaningful. When taking edge +weights into account, ‘A_ij’ equals the weight of the corresponding edge +(or 0 if there is no edge), ‘k_i’ is the strength (i.e. the weighted +degree) of vertex ‘i’, with similar counterparts for a directed graph, +and ‘m’ is the total weight of all edges. + + Note that the modularity is not well-defined for graphs with no +edges. igraph returns ‘NaN’ for graphs with no edges; see +https://github.com/igraph/igraph/issues/1539 +(https://github.com/igraph/igraph/issues/1539) for a detailed +discussion. + + For the original definition of modularity, see Newman, M. E. J., and +Girvan, M. (2004). Finding and evaluating community structure in +networks. Physical Review E 69, 026113. +https://doi.org/10.1103/PhysRevE.69.026113 +(https://doi.org/10.1103/PhysRevE.69.026113) + + For the directed definition of modularity, see Leicht, E. A., and +Newman, M. E. J. (2008). Community Structure in Directed Networks. +Physical Review Letters 100, 118703. +https://doi.org/10.1103/PhysRevLett.100.118703 +(https://doi.org/10.1103/PhysRevLett.100.118703) + + For the introduction of the resolution parameter ‘γ’, see Reichardt, +J., and Bornholdt, S. (2006). Statistical mechanics of community +detection. Physical Review E 74, 016110. +https://doi.org/10.1103/PhysRevE.74.016110 +(https://doi.org/10.1103/PhysRevE.74.016110) + + *Arguments:. * + +‘graph’: + The input graph. + +‘membership’: + Numeric vector of integer values which gives the type of each + vertex, i.e. the cluster to which it belongs. It does not have to + be consecutive, i.e. empty communities are allowed. For better + performance, ensure that community indices are nonnegative and + smaller than the vertex count. This can be ensured using + ‘igraph_reindex_membership()’ (*note igraph_reindex_membership --- + Makes the IDs in a membership vector contiguous_::). + +‘weights’: + Weight vector or ‘NULL’ if no weights are specified. + +‘resolution’: + The resolution parameter ‘γ’. Must not be negative. Set it to 1 + to use the classical definition of modularity. + +‘directed’: + Whether to use the directed or undirected version of modularity. + Ignored for undirected graphs. + +‘modularity’: + Pointer to a real number, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_modularity_matrix()’ (*note igraph_modularity_matrix --- + Calculates the modularity matrix_::) + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges, assuming that community indices are nonnegative and smaller +than the vertex count. Otherwise, O(|V| log |V| + |E|). + + +File: igraph-docs.info, Node: igraph_modularity_matrix --- Calculates the modularity matrix_, Next: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_, Prev: igraph_modularity --- Calculates the modularity of a graph with respect to some clusters or vertex types_, Up: Common functions related to community structure + +25.1.2 igraph_modularity_matrix -- Calculates the modularity matrix. +-------------------------------------------------------------------- + + + igraph_error_t igraph_modularity_matrix(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_matrix_t *modmat, + igraph_bool_t directed); + + This function returns the modularity matrix, which is defined as + + ‘B_ij = A_ij - γ k_i k_j / (2m)’ + + for undirected graphs, where ‘A_ij’ is the adjacency matrix, ‘γ’ is +the resolution parameter, ‘k_i’ is the degree of vertex ‘i’, and ‘m’ is +the number of edges in the graph. When there are no edges, or the +weights add up to zero, the result is undefined. + + For directed graphs the modularity matrix is changed to + + ‘B_ij = A_ij - γ k^out_i k^in_j / m’ + + where ‘k^out_i’ is the out-degree of node ‘i’ and ‘k^in_j’ is the +in-degree of node ‘j’. + + Note that self-loops in undirected graphs are multiplied by 2 in this +implementation. If weights are specified, the weighted counterparts of +the adjacency matrix and degrees are used. + + *Arguments:. * + +‘graph’: + The input graph. + +‘weights’: + Edge weights, pointer to a vector. If this is a null pointer then + every edge is assumed to have a weight of 1. + +‘resolution’: + The resolution parameter ‘γ’. Must not be negative. Default is 1. + Lower values favor fewer, larger communities; higher values favor + more, smaller communities. + +‘modmat’: + Pointer to an initialized matrix in which the modularity matrix is + stored. + +‘directed’: + For directed graphs: if the edges should be treated as undirected. + For undirected graphs this is ignored. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_modularity()’ (*note igraph_modularity --- Calculates the + modularity of a graph with respect to some clusters or vertex + types_::) + + +File: igraph-docs.info, Node: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_, Next: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_, Prev: igraph_modularity_matrix --- Calculates the modularity matrix_, Up: Common functions related to community structure + +25.1.3 igraph_community_optimal_modularity -- Calculate the community structure with the highest modularity value. +------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_community_optimal_modularity(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_real_t *modularity, + igraph_vector_int_t *membership); + + This function calculates the optimal community structure for a graph, +in terms of maximal modularity score. Both undirected and directed +graphs are supported. + + The calculation is done by transforming the modularity maximization +into an integer programming problem, and then calling the GLPK library +to solve that. Please see Ulrik Brandes et al.: On Modularity +Clustering, IEEE Transactions on Knowledge and Data Engineering +20(2):172-188, 2008 https://doi.org/10.1109/TKDE.2007.190689 +(https://doi.org/10.1109/TKDE.2007.190689). + + Note that exact modularity optimization is an NP-complete problem, +and all known algorithms for it have exponential time complexity. This +means that you probably don't want to run this function on larger +graphs. Graphs with up to fifty vertices should be fine, graphs with a +couple of hundred vertices might be possible. + + *Arguments:. * + +‘graph’: + The input graph. It may be undirected or directed. + +‘weights’: + Vector giving the weights of the edges. If it is ‘NULL’ then each + edge is supposed to have the same weight. + +‘resolution’: + Resolution parameter. Must be greater than or equal to 0. Lower + values favor fewer, larger communities; higher values favor more, + smaller communities. Set it to 1 to use the classical definition + of modularity. + +‘modularity’: + Pointer to a real number, or a null pointer. If it is not a null + pointer, then a optimal modularity value is returned here. + +‘membership’: + Pointer to a vector, or a null pointer. If not a null pointer, + then the membership vector of the optimal community structure is + stored here. + + *Returns:. * + +‘’ + Error code. When GLPK is not available, ‘IGRAPH_UNIMPLEMENTED’ is + returned. + + *See also:. * + +‘’ + ‘igraph_modularity()’ (*note igraph_modularity --- Calculates the + modularity of a graph with respect to some clusters or vertex + types_::), ‘igraph_community_fastgreedy()’ (*note + igraph_community_fastgreedy --- Finding community structure by + greedy optimization of modularity_::) for an algorithm that finds a + local optimum in a greedy way. + + Time complexity: exponential in the number of vertices. + + * File examples/simple/igraph_community_optimal_modularity.c* + + +File: igraph-docs.info, Node: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_, Next: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_, Prev: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_, Up: Common functions related to community structure + +25.1.4 igraph_community_to_membership -- Cut a dendrogram after a given number of merges. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t nodes, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize); + + This function creates a membership vector from a dendrogram whose +leaves are individual vertices by cutting it at the specified level. It +produces a membership vector that contains for each vertex its cluster +ID, numbered from zero. This is the same membership vector format that +is produced by ‘igraph_connected_components()’ (*note +igraph_connected_components --- Calculates the [weakly or strongly] +connected components in a graph_::), as well as all community detection +functions in igraph. + + It takes as input the number of vertices ‘n’, and a ‘merges’ matrix +encoding the dendrogram, in the format produced by hierarchical +clustering functions such as ‘igraph_community_edge_betweenness()’ +(*note igraph_community_edge_betweenness --- Community finding based on +edge betweenness_::), ‘igraph_community_walktrap()’ (*note +igraph_community_walktrap --- Community finding using a random walk +based similarity measure_::) or ‘igraph_community_fastgreedy()’ (*note +igraph_community_fastgreedy --- Finding community structure by greedy +optimization of modularity_::). The matrix must have two columns and up +to ‘n - 1’ rows. Each row represents merging two dendrogram nodes into +their parent node. The leaf nodes of the dendrogram are indexed from 0 +to ‘n - 1’ and are identical to the vertices of the graph that is being +partitioned into communities. Row ‘i’ contains the children of +dendrogram node with index ‘n + i’. + + This function performs ‘steps’ merge operations as prescribed by the +‘merges’ matrix and returns the resulting partitioning into ‘n - steps’ +communities. + + If ‘merges’ is not a complete dendrogram, it is possible to take +‘steps’ steps if ‘steps’ is not bigger than the number lines in +‘merges’. + + *Arguments:. * + +‘merges’: + The two-column matrix containing the merge operations. + +‘nodes’: + The number of leaf nodes in the dendrogram. + +‘steps’: + Integer constant, the number of steps to take. + +‘membership’: + Pointer to an initialized vector, the membership results will be + stored here, if not ‘NULL’. The vector will be resized as needed. + +‘csize’: + Pointer to an initialized vector, or ‘NULL’. If not ‘NULL’ then + the sizes of the components will be stored here, the vector will be + resized as needed. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_walktrap()’ (*note igraph_community_walktrap --- + Community finding using a random walk based similarity measure_::), + ‘igraph_community_edge_betweenness()’ (*note + igraph_community_edge_betweenness --- Community finding based on + edge betweenness_::), ‘igraph_community_fastgreedy()’ (*note + igraph_community_fastgreedy --- Finding community structure by + greedy optimization of modularity_::) for community structure + detection algorithms producing merge matrices in this format; + ‘igraph_le_community_to_membership()’ (*note + igraph_le_community_to_membership --- Cut an incomplete dendrogram + after a given number of merges; starting with an initial cluster + assignment_::) to perform merges starting from a given cluster + assignment. + + Time complexity: O(|V|), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_, Next: igraph_compare_communities --- Compares community structures using various metrics_, Prev: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_, Up: Common functions related to community structure + +25.1.5 igraph_reindex_membership -- Makes the IDs in a membership vector contiguous. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_reindex_membership( + igraph_vector_int_t *membership, + igraph_vector_int_t *new_to_old, + igraph_int_t *nb_clusters); + + This function reindexes component IDs in a membership vector in a way +that the new IDs start from zero and go up to ‘C-1’, where ‘C’ is the +number of unique component IDs in the original vector. + + *Arguments:. * + +‘membership’: + Numeric vector which gives the type of each vertex, i.e. the + component to which it belongs. The vector will be altered + in-place. + +‘new_to_old’: + Pointer to a vector which will contain the old component ID for + each new one, or ‘NULL’, in which case it is not returned. The + vector will be resized as needed. + +‘nb_clusters’: + Pointer to an integer for the number of distinct clusters. If not + ‘NULL’, this will be updated to reflect the number of distinct + clusters found in membership. + + *Returns:. * + +‘’ + Error code. + + Time complexity: Let n be the length of the membership vector. O(n) +if cluster indices are within 0..n-1, and O(n log(n)) otherwise. + + +File: igraph-docs.info, Node: igraph_compare_communities --- Compares community structures using various metrics_, Next: igraph_split_join_distance --- Calculates the split-join distance of two community structures_, Prev: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_, Up: Common functions related to community structure + +25.1.6 igraph_compare_communities -- Compares community structures using various metrics. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_compare_communities(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, igraph_real_t* result, + igraph_community_comparison_t method); + + This function assesses the distance between two community structures +using the variation of information (VI) metric of Meila (2003), the +normalized mutual information (NMI) of Danon et al (2005), the +split-join distance of van Dongen (2000), the Rand index of Rand (1971) +or the adjusted Rand index of Hubert and Arabie (1985). + + Some of these measures are defined based on the entropy of a discrete +random variable associated with a given clustering ‘C’ of vertices. Let +‘p_i’ be the probability that a randomly picked vertex would be part of +cluster ‘i’. Then the entropy of the clustering is + + ‘H(C) = - sum_i p_i log p_i’ + + Similarly, we can define the joint entropy of two clusterings ‘C_1’ +and ‘C_2’ based on the probability ‘p_ij’ that a random vertex is part +of cluster ‘i’ in the first clustering and cluster ‘j’ in the second +one: + + ‘H(C_1, C_2) = - sum_ii p_ij log p_ij’ + + The mutual information of ‘C_1’ and ‘C_2’ is then ‘MI(C_1, C_2) = +H(C_1) + H(C_2) - H(C_1, C_2) >= 0 ’. A large mutual information +indicates a high overlap between the two clusterings. The normalized +mutual information, as computed by igraph, is + + ‘NMI(C_1, C_2) = 2 MI(C_1, C_2) / (H(C_1) + H(C_2))’. + + It takes its value from the interval (0, 1], with 1 achieved when the +two clusterings coincide. + + The variation of information is defined as ‘VI(C_1, C_2) = [H(C_1) - +MI(C_1, C_2)] + [H(C_2) - MI(C_1, C_2)]’. Lower values of the variation +of information indicate a smaller difference between the two +clusterings, with ‘VI = 0’ achieved precisely when they coincide. +igraph uses natural units for the variation of information, i.e. it +uses the natural logarithm when computing entropies. + + The Rand index is defined as the probability that the two clusterings +agree about the cluster memberships of a randomly chosen vertex _pair._ +All vertex pairs are considered, and the two clusterings are considered +to be in agreement about the memberships of a vertex pair if either the +two vertices are in the same cluster in both clusterings, or they are in +different clusters in both clusterings. The Rand index is then the +number of vertex pairs in agreement, divided by the total number of +vertex pairs. A Rand index of zero means that the two clusterings +disagree about the membership of all vertex pairs, while 1 means that +the two clusterings are identical. + + The adjusted Rand index is similar to the Rand index, but it takes +into account that agreement between the two clusterings may also occur +by chance even if the two clusterings are chosen completely randomly. +The adjusted Rand index therefore subtracts the expected fraction of +agreements from the value of the Rand index, and divides the result by +one minus the expected fraction of agreements. The maximum value of the +adjusted Rand index is still 1 (similarly to the Rand index), indicating +maximum agreement, but the value may be less than zero if there is +_less_ agreement between the two clusterings than what would be expected +by chance. + + For an explanation of the split-join distance, see +‘igraph_split_join_distance()’ (*note igraph_split_join_distance --- +Calculates the split-join distance of two community structures_::). + + References: + + Meilă M: Comparing clusterings by the variation of information. In: +Schölkopf B, Warmuth MK (eds.). Learning Theory and Kernel Machines: +16th Annual Conference on Computational Learning Theory and 7th Kernel +Workshop, COLT/Kernel 2003, Washington, DC, USA. Lecture Notes in +Computer Science, vol. 2777, Springer, 2003. ISBN: 978-3-540-40720-1. +https://doi.org/10.1007/978-3-540-45167-9_14 +(https://doi.org/10.1007/978-3-540-45167-9_14) + + Danon L, Diaz-Guilera A, Duch J, Arenas A: Comparing community +structure identification. J Stat Mech P09008, 2005. +https://doi.org/10.1088/1742-5468/2005/09/P09008 +(https://doi.org/10.1088/1742-5468/2005/09/P09008) + + van Dongen S: Performance criteria for graph clustering and Markov +cluster experiments. Technical Report INS-R0012, National Research +Institute for Mathematics and Computer Science in the Netherlands, +Amsterdam, May 2000. https://ir.cwi.nl/pub/4461 +(https://ir.cwi.nl/pub/4461) + + Rand WM: Objective criteria for the evaluation of clustering methods. +J Am Stat Assoc 66(336):846-850, 1971. https://doi.org/10.2307/2284239 +(https://doi.org/10.2307/2284239) + + Hubert L and Arabie P: Comparing partitions. Journal of +Classification 2:193-218, 1985. https://doi.org/10.1007/BF01908075 +(https://doi.org/10.1007/BF01908075) + + *Arguments:. * + +‘comm1’: + the membership vector of the first community structure + +‘comm2’: + the membership vector of the second community structure + +‘result’: + the result is stored here. + +‘method’: + the comparison method to use. ‘IGRAPH_COMMCMP_VI’ selects the + variation of information (VI) metric of Meila (2003), + ‘IGRAPH_COMMCMP_NMI’ selects the normalized mutual information + measure proposed by Danon et al (2005), ‘IGRAPH_COMMCMP_SPLIT_JOIN’ + selects the split-join distance of van Dongen (2000), + ‘IGRAPH_COMMCMP_RAND’ selects the unadjusted Rand index (1971) and + ‘IGRAPH_COMMCMP_ADJUSTED_RAND’ selects the adjusted Rand index. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_split_join_distance()’ (*note igraph_split_join_distance + --- Calculates the split-join distance of two community + structures_::). + + Time complexity: O(n log(n)). + + +File: igraph-docs.info, Node: igraph_split_join_distance --- Calculates the split-join distance of two community structures_, Prev: igraph_compare_communities --- Compares community structures using various metrics_, Up: Common functions related to community structure + +25.1.7 igraph_split_join_distance -- Calculates the split-join distance of two community structures. +---------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_split_join_distance(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, igraph_int_t *distance12, + igraph_int_t *distance21); + + The split-join distance between partitions A and B is the sum of the +projection distance of A from B and the projection distance of B from A. +The projection distance is an asymmetric measure and it is defined as +follows: + + First, each set in partition A is evaluated against all sets in +partition B. For each set in partition A, the best matching set in +partition B is found and the overlap size is calculated. (Matching is +quantified by the size of the overlap between the two sets). Then, the +maximal overlap sizes for each set in A are summed together and +subtracted from the number of elements in A. + + The split-join distance will be returned in two arguments, +‘distance12’ will contain the projection distance of the first partition +from the second, while ‘distance21’ will be the projection distance of +the second partition from the first. This makes it easier to detect +whether a partition is a subpartition of the other, since in this case, +the corresponding distance will be zero. + + Reference: + + van Dongen S: Performance criteria for graph clustering and Markov +cluster experiments. Technical Report INS-R0012, National Research +Institute for Mathematics and Computer Science in the Netherlands, +Amsterdam, May 2000. + + *Arguments:. * + +‘comm1’: + the membership vector of the first community structure + +‘comm2’: + the membership vector of the second community structure + +‘distance12’: + pointer to an ‘igraph_int_t’, the projection distance of the first + community structure from the second one will be returned here. + +‘distance21’: + pointer to an ‘igraph_int_t’, the projection distance of the second + community structure from the first one will be returned here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_compare_communities()’ (*note igraph_compare_communities + --- Compares community structures using various metrics_::) with + the ‘IGRAPH_COMMCMP_SPLIT_JOIN’ method if you are not interested in + the individual distances but only the sum of them. + + Time complexity: O(n log(n)). + + +File: igraph-docs.info, Node: Community structure based on statistical mechanics, Next: Community structure based on eigenvectors of matrices, Prev: Common functions related to community structure, Up: Detecting community structure + +25.2 Community structure based on statistical mechanics +======================================================= + +* Menu: + +* igraph_community_spinglass -- Community detection based on statistical mechanics.: igraph_community_spinglass --- Community detection based on statistical mechanics_. +* igraph_community_spinglass_single -- Community of a single node based on statistical mechanics.: igraph_community_spinglass_single --- Community of a single node based on statistical mechanics_. + + +File: igraph-docs.info, Node: igraph_community_spinglass --- Community detection based on statistical mechanics_, Next: igraph_community_spinglass_single --- Community of a single node based on statistical mechanics_, Up: Community structure based on statistical mechanics + +25.2.1 igraph_community_spinglass -- Community detection based on statistical mechanics. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_spinglass(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + igraph_spinglass_implementation_t implementation, + igraph_real_t gamma_minus); + + This function implements the community structure detection algorithm +proposed by Joerg Reichardt and Stefan Bornholdt. The algorithm is +described in their paper: Statistical Mechanics of Community Detection, +http://arxiv.org/abs/cond-mat/0603718 +(http://arxiv.org/abs/cond-mat/0603718) . + + From version 0.6, igraph also supports an extension to the algorithm +that allows negative edge weights. This is described in V. A. Traag and +Jeroen Bruggeman: Community detection in networks with positive and +negative links, http://arxiv.org/abs/0811.2329 +(http://arxiv.org/abs/0811.2329) . + + *Arguments:. * + +‘graph’: + The input graph, it may be directed but the direction of the edges + is ignored by the algorithm. + +‘weights’: + The vector giving the edge weights, it may be ‘NULL’, in which case + all edges are weighted equally. The edge weights must be positive + unless using the ‘IGRAPH_SPINCOMM_IMP_NEG’ implementation. + +‘modularity’: + Pointer to a real number, if not ‘NULL’ then the modularity score + of the solution will be stored here. This is the gereralized + modularity, taking into account the resolution parameter ‘gamma’. + See ‘igraph_modularity()’ (*note igraph_modularity --- Calculates + the modularity of a graph with respect to some clusters or vertex + types_::) for details. + +‘temperature’: + Pointer to a real number, if not ‘NULL’ then the temperature at the + end of the algorithm will be stored here. + +‘membership’: + Pointer to an initialized vector or ‘NULL’. If not ‘NULL’ then the + result of the clustering will be stored here. For each vertex, the + number of its cluster is given, with the first cluster numbered + zero. The vector will be resized as needed. + +‘csize’: + Pointer to an initialized vector or ‘NULL’. If not ‘NULL’ then the + sizes of the clusters will stored here in cluster number order. + The vector will be resized as needed. + +‘spins’: + Integer giving the number of spins, i.e. the maximum number of + clusters. Even if the number of spins is high the number of + clusters in the result might be small. + +‘parupdate’: + A Boolean constant, whether to update all spins in parallel. It is + not implemented in the ‘IGRAPH_SPINCOMM_INP_NEG’ implementation. + +‘starttemp’: + Real number, the temperature at the start. A reasonable default is + 1.0. + +‘stoptemp’: + Real number, the algorithm stops at this temperature. A reasonable + default is 0.01. + +‘coolfact’: + Real number, the cooling factor for the simulated annealing. A + reasonable default is 0.99. + +‘update_rule’: + The type of the update rule. Possible values: + ‘IGRAPH_SPINCOMM_UPDATE_SIMPLE’ and + ‘IGRAPH_SPINCOMM_UPDATE_CONFIG’. Basically this parameter defines + the null model based on which the actual clustering is done. If + this is ‘IGRAPH_SPINCOMM_UPDATE_SIMPLE’ then the random graph (i.e. + G(n,p)), if it is ‘IGRAPH_SPINCOMM_UPDATE’ then the configuration + model is used. The configuration means that the baseline for the + clustering is a random graph with the same degree distribution as + the input graph. + +‘gamma’: + Real number. The gamma parameter of the algorithm, acting as a + resolution parameter. Smaller values typically lead to larger + clusters, larger values typically lead to smaller clusters. + +‘implementation’: + Constant, chooses between the two implementations of the spin-glass + algorithm that are included in igraph. ‘IGRAPH_SPINCOMM_IMP_ORIG’ + selects the original implementation, this is faster, + ‘IGRAPH_SPINCOMM_INP_NEG’ selects an implementation that allows + negative edge weights. + +‘gamma_minus’: + Real number. Parameter for the ‘IGRAPH_SPINCOMM_IMP_NEG’ + implementation. This acts as a resolution parameter for the + negative part of the network. Smaller values of ‘gamma_minus’ + leads to fewer negative edges within clusters. If this argument is + set to zero, the algorithm reduces to a graph coloring algorithm + when all edges have negative weights, using the number of spins as + the number of colors. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_spinglass_single()’ (*note + igraph_community_spinglass_single --- Community of a single node + based on statistical mechanics_::) for calculating the community of + a single vertex. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_community_spinglass_single --- Community of a single node based on statistical mechanics_, Prev: igraph_community_spinglass --- Community detection based on statistical mechanics_, Up: Community structure based on statistical mechanics + +25.2.2 igraph_community_spinglass_single -- Community of a single node based on statistical mechanics. +------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_community_spinglass_single(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t vertex, + igraph_vector_int_t *community, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_real_t *inner_links, + igraph_real_t *outer_links, + igraph_int_t spins, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma); + + This function implements the community structure detection algorithm +proposed by Joerg Reichardt and Stefan Bornholdt. It is described in +their paper: Statistical Mechanics of Community Detection, +http://arxiv.org/abs/cond-mat/0603718 +(http://arxiv.org/abs/cond-mat/0603718) . + + This function calculates the community of a single vertex without +calculating all the communities in the graph. + + *Arguments:. * + +‘graph’: + The input graph, it may be directed but the direction of the edges + is not used in the algorithm. + +‘weights’: + Pointer to a vector with the weights of the edges. Alternatively + ‘NULL’ can be supplied to have the same weight for every edge. + +‘vertex’: + The vertex ID of the vertex of which this community is calculated. + +‘community’: + Pointer to an initialized vector, the result, the IDs of the + vertices in the community of the input vertex will be stored here. + The vector will be resized as needed. + +‘cohesion’: + Pointer to a real variable, if not ‘NULL’ the cohesion index of the + community will be stored here. + +‘adhesion’: + Pointer to a real variable, if not ‘NULL’ the adhesion index of the + community will be stored here. + +‘inner_links’: + Pointer to a real, if not ‘NULL’ the number of edges within the + community (or the sum of their weights) is stored here. + +‘outer_links’: + Pointer to a real, if not ‘NULL’ the number of edges between the + community and the rest of the graph (or the sum of their weights) + will be stored here. + +‘spins’: + The number of spins to use, this can be higher than the actual + number of clusters in the network, in which case some clusters will + contain zero vertices. + +‘update_rule’: + The type of the update rule. Possible values: + ‘IGRAPH_SPINCOMM_UPDATE_SIMPLE’ and + ‘IGRAPH_SPINCOMM_UPDATE_CONFIG’. Basically this parameter defined + the null model based on which the actual clustering is done. If + this is ‘IGRAPH_SPINCOMM_UPDATE_SIMPLE’ then the random graph (ie. + G(n,p)), if it is ‘IGRAPH_SPINCOMM_UPDATE’ then the configuration + model is used. The configuration means that the baseline for the + clustering is a random graph with the same degree distribution as + the input graph. + +‘gamma’: + Real number. The gamma parameter of the algorithm. This defined + the weight of the missing and existing links in the quality + function for the clustering. The default value in the original + code was 1.0, which is equal weight to missing and existing edges. + Smaller values make the existing links contibute more to the energy + function which is minimized in the algorithm. Bigger values make + the missing links more important. (If my understanding is + correct.) + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + igraph_community_spinglass() for the traditional version of the + algorithm. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Community structure based on eigenvectors of matrices, Next: Walktrap; Community structure based on random walks, Prev: Community structure based on statistical mechanics, Up: Detecting community structure + +25.3 Community structure based on eigenvectors of matrices +========================================================== + +The function documented in these section implements the 'leading +eigenvector' method developed by Mark Newman and published in MEJ +Newman: Finding community structure using the eigenvectors of matrices, +Phys Rev E 74:036104 (2006). + + The heart of the method is the definition of the modularity matrix ‘B += A - P’, ‘A’ being the adjacency matrix of the (undirected) network, +and ‘P’ contains the probability that certain edges are present +according to the 'configuration model'. In other words, a ‘P_ij’ +element of ‘P’ is the probability that there is an edge between vertices +‘i’ and ‘j’ in a random network in which the degrees of all vertices are +the same as in the input graph. See ‘igraph_modularity_matrix()’ (*note +igraph_modularity_matrix --- Calculates the modularity matrix_::) for +more details. + + The leading eigenvector method works by calculating the eigenvector +of the modularity matrix for the largest positive eigenvalue and then +separating vertices into two community based on the sign of the +corresponding element in the eigenvector. If all elements in the +eigenvector are of the same sign that means that the network has no +underlying community structure. Check Newman's paper to understand why +this is a good method for detecting community structure. + + The leading eigenvector community structure detection method is +implemented in ‘igraph_community_leading_eigenvector()’ (*note +igraph_community_leading_eigenvector --- Leading eigenvector community +finding [proper version]_::). After the initial split, the following +splits are done in a way to optimize modularity regarding to the +original network. Note that any further refinement, for example using +Kernighan-Lin, as proposed in Section V.A of Newman (2006), is not +implemented here. + + * File examples/simple/igraph_community_leading_eigenvector.c* + +* Menu: + +* igraph_community_leading_eigenvector -- Leading eigenvector community finding (proper version).: igraph_community_leading_eigenvector --- Leading eigenvector community finding [proper version]_. +* igraph_community_leading_eigenvector_callback_t -- Callback for the leading eigenvector community finding method.: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_. +* igraph_le_community_to_membership -- Cut an incomplete dendrogram after a given number of merges, starting with an initial cluster assignment.: igraph_le_community_to_membership --- Cut an incomplete dendrogram after a given number of merges; starting with an initial cluster assignment_. + + +File: igraph-docs.info, Node: igraph_community_leading_eigenvector --- Leading eigenvector community finding [proper version]_, Next: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_, Up: Community structure based on eigenvectors of matrices + +25.3.1 igraph_community_leading_eigenvector -- Leading eigenvector community finding (proper version). +------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_community_leading_eigenvector( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_int_t *membership, + igraph_int_t steps, + igraph_arpack_options_t *options, + igraph_real_t *modularity, + igraph_bool_t start, + igraph_vector_t *eigenvalues, + igraph_vector_list_t *eigenvectors, + igraph_vector_int_t *history, + igraph_community_leading_eigenvector_callback_t *callback, + void *callback_extra); + + Newman's leading eigenvector method for detecting community +structure. This is the proper implementation of the recursive, divisive +algorithm: each split is done by maximizing the modularity regarding the +original network, see MEJ Newman: Finding community structure in +networks using the eigenvectors of matrices, Phys Rev E 74:036104 +(2006). https://doi.org/10.1103/PhysRevE.74.036104 +(https://doi.org/10.1103/PhysRevE.74.036104) + + *Arguments:. * + +‘graph’: + The input graph. Edge directions will be ignored. + +‘weights’: + The weights of the edges, or ‘NULL’ for unweighted graphs. + +‘merges’: + The result of the algorithm, a matrix containing the information + about the splits performed. The matrix is built in the opposite + way however, it is like the result of an agglomerative algorithm. + Unlike with most other hierarchical community detection functions + in igraph, the integers in this matrix represent community indices, + not vertex indices. If at the end of the algorithm (after ‘steps’ + steps was done) there are 'p' communities, then these are numbered + from zero to ‘p-1’. The first line of the matrix contains the + first 'merge' (which is in reality the last split) of two + communities into community ‘p’, the merge in the second line forms + community ‘p+1’, etc. The matrix should be initialized before + calling and will be resized as needed. This argument is ignored if + it is ‘NULL’. + +‘membership’: + The membership of the vertices after all the splits were performed + will be stored here. The vector must be initialized before calling + and will be resized as needed. This argument is ignored if it is + ‘NULL’. This argument can also be used to supply a starting + configuration for the community finding, in the format of a + membership vector. In this case the ‘start’ argument must be set + to ‘true’. + +‘steps’: + The maximum number of steps to perform. It might happen that some + component (or the whole network) has no underlying community + structure and no further steps can be done. If you want as many + steps as possible then supply the number of vertices in the network + here. + +‘options’: + The options for ARPACK. Supply ‘NULL’ here to use the defaults. + ‘n’ is always overwritten. ‘ncv’ is set to at least 4. + +‘modularity’: + If not a null pointer, then it must be a pointer to a real number + and the modularity score of the final division is stored here. + +‘start’: + Boolean, whether to use the community structure given in the + ‘membership’ argument as a starting point. + +‘eigenvalues’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then the eigenvalues calculated along the community + structure detection are stored here. The non-positive eigenvalues, + that do not result a split, are stored as well. + +‘eigenvectors’: + If not a null pointer, then the eigenvectors that are calculated in + each step of the algorithm are stored here, in a list of vectors. + Each eigenvector is stored in an ‘igraph_vector_t’ (*note About + igraph_vector_t objects::) object. + +‘history’: + Pointer to an initialized vector or a null pointer. If not a null + pointer, then a trace of the algorithm is stored here, encoded + numerically. The various operations: + + ‘IGRAPH_LEVC_HIST_START_FULL’ + Start the algorithm from an initial state where each connected + component is a separate community. + + ‘IGRAPH_LEVC_HIST_START_GIVEN’ + Start the algorithm from a given community structure. The + next value in the vector contains the initial number of + communities. + + ‘IGRAPH_LEVC_HIST_SPLIT’ + Split a community into two communities. The id of the + splitted community is given in the next element of the history + vector. The id of the first new community is the same as the + id of the splitted community. The id of the second community + equals to the number of communities before the split. + + ‘IGRAPH_LEVC_HIST_FAILED’ + Tried to split a community, but it was not worth it, as it + does not result in a bigger modularity value. The id of the + community is given in the next element of the vector. + +‘callback’: + A null pointer or a function of type + ‘igraph_community_leading_eigenvector_callback_t’ (*note + igraph_community_leading_eigenvector_callback_t --- Callback for + the leading eigenvector community finding method_::). If given, + this callback function is called after each eigenvector/eigenvalue + calculation. If the callback returns ‘IGRAPH_STOP’, then the + community finding algorithm stops. If it returns ‘IGRAPH_SUCCESS’, + the algorithm continues normally. Any other return value is + considered an igraph error code and will terminete the algorithm + with the same error code. See the arguments passed to the callback + at the documentation of + ‘igraph_community_leading_eigenvector_callback_t’ (*note + igraph_community_leading_eigenvector_callback_t --- Callback for + the leading eigenvector community finding method_::). + +‘callback_extra’: + Extra argument to pass to the callback function. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_walktrap()’ (*note igraph_community_walktrap --- + Community finding using a random walk based similarity measure_::) + and ‘igraph_community_spinglass()’ (*note + igraph_community_spinglass --- Community detection based on + statistical mechanics_::) for other community structure detection + methods. + + Time complexity: O(|E|+|V|^2*steps), |V| is the number of vertices, +|E| the number of edges, 'steps' the number of splits performed. + + +File: igraph-docs.info, Node: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_, Next: igraph_le_community_to_membership --- Cut an incomplete dendrogram after a given number of merges; starting with an initial cluster assignment_, Prev: igraph_community_leading_eigenvector --- Leading eigenvector community finding [proper version]_, Up: Community structure based on eigenvectors of matrices + +25.3.2 igraph_community_leading_eigenvector_callback_t -- Callback for the leading eigenvector community finding method. +------------------------------------------------------------------------------------------------------------------------ + + + typedef igraph_error_t igraph_community_leading_eigenvector_callback_t( + const igraph_vector_int_t *membership, + igraph_int_t comm, + igraph_real_t eigenvalue, + const igraph_vector_t *eigenvector, + igraph_arpack_function_t *arpack_multiplier, + void *arpack_extra, + void *extra); + + The leading eigenvector community finding implementation in igraph is +able to call a callback function, after each eigenvalue calculation. +This callback function must be of +‘igraph_community_leading_eigenvector_callback_t’ type. The following +arguments are passed to the callback: + + *Arguments:. * + +‘membership’: + The actual membership vector, before recording the potential change + implied by the newly found eigenvalue. + +‘comm’: + The id of the community that the algorithm tried to split in the + last iteration. The community IDs are indexed from zero here! + +‘eigenvalue’: + The eigenvalue the algorithm has just found. + +‘eigenvector’: + The eigenvector corresponding to the eigenvalue the algorithm just + found. + +‘arpack_multiplier’: + A function that was passed to ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::) + to solve the last eigenproblem. + +‘arpack_extra’: + The extra argument that was passed to the ARPACK solver. + +‘extra’: + Extra argument that as passed to + ‘igraph_community_leading_eigenvector()’ (*note + igraph_community_leading_eigenvector --- Leading eigenvector + community finding [proper version]_::). + + *See also:. * + +‘’ + ‘igraph_community_leading_eigenvector()’ (*note + igraph_community_leading_eigenvector --- Leading eigenvector + community finding [proper version]_::), ‘igraph_arpack_function_t’ + (*note igraph_arpack_function_t --- Type of the ARPACK callback + function_::), ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::). + + +File: igraph-docs.info, Node: igraph_le_community_to_membership --- Cut an incomplete dendrogram after a given number of merges; starting with an initial cluster assignment_, Prev: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_, Up: Community structure based on eigenvectors of matrices + +25.3.3 igraph_le_community_to_membership -- Cut an incomplete dendrogram after a given number of merges, starting with an initial cluster assignment. +----------------------------------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_le_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize); + + This function takes a dendrogram whose leaves are cluster IDs given +in an initial cluster assignment provided in ‘membership’. Then it +updates the cluster assignment by performing the specified number of +mergers, as given by the dendrogram encoded in ‘merges’. It is a more +general version of ‘igraph_community_to_membership()’ (*note +igraph_community_to_membership --- Cut a dendrogram after a given number +of merges_::), which assumes that the dendrogram leaves are singleton +clusters corresponding to individual vertices. + + This dendrogram format is suitable for divise hierarchical community +detection algorithms that stop before dividing the graph into individual +vertices, such as ‘igraph_community_leading_eigenvector()’ (*note +igraph_community_leading_eigenvector --- Leading eigenvector community +finding [proper version]_::). + + Initially, ‘membership’ is expected to contain ‘m’ contiguous cluster +indices, numbered from zero. These correspond to the leaf nodes of the +dendrogram. Row ‘i’ of the two-column ‘merges’ matrix contains the IDs +of clusters that are merged together into dendrogram node ‘m + i’. It +may have up to ‘m - 1’ rows. + + This function performs ‘steps’ merge operations as prescribed by the +‘merges’ matrix and updates ‘membership’ to the resulting partitioning +into ‘m - steps’ communities. + + *Arguments:. * + +‘merges’: + The two-column matrix containing the merge operations. See + ‘igraph_community_leading_eigenvector()’ (*note + igraph_community_leading_eigenvector --- Leading eigenvector + community finding [proper version]_::) for the detailed syntax. + This is usually from the output of the leading eigenvector + community structure detection routines. + +‘steps’: + The number of steps to make according to ‘merges’. + +‘membership’: + Initially the starting membership vector, on output the resulting + membership vector, after performing ‘steps’ merges. + +‘csize’: + Optionally the sizes of the communities are stored here, if this is + not a null pointer, but an initialized vector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_to_membership()’ (*note + igraph_community_to_membership --- Cut a dendrogram after a given + number of merges_::) for a simpler interface that starts by merging + individual vertices. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: Walktrap; Community structure based on random walks, Next: Edge betweenness based community detection, Prev: Community structure based on eigenvectors of matrices, Up: Detecting community structure + +25.4 Walktrap: Community structure based on random walks +======================================================== + +* Menu: + +* igraph_community_walktrap -- Community finding using a random walk based similarity measure.: igraph_community_walktrap --- Community finding using a random walk based similarity measure_. + + +File: igraph-docs.info, Node: igraph_community_walktrap --- Community finding using a random walk based similarity measure_, Up: Walktrap; Community structure based on random walks + +25.4.1 igraph_community_walktrap -- Community finding using a random walk based similarity measure. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_walktrap(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t steps, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + + This function is the implementation of the Walktrap community finding +algorithm, see Pascal Pons, Matthieu Latapy: Computing communities in +large networks using random walks, https://arxiv.org/abs/physics/0512106 +(https://arxiv.org/abs/physics/0512106) + + Currently the original C++ implementation is used in igraph, see +https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html +(https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html) We are +grateful to Matthieu Latapy and Pascal Pons for providing this source +code. + + In contrast to the original implementation, isolated vertices are +allowed in the graph and they are assumed to have a single incident loop +edge with weight 1. + + *Arguments:. * + +‘graph’: + The input graph, edge directions are ignored. + +‘weights’: + Numeric vector giving the weights of the edges. If it is a NULL + pointer then all edges will have equal weights. The weights are + expected to be positive. + +‘steps’: + Integer constant, the length of the random walks. Typically, good + results are obtained with values between 3-8 with 4-5 being a + reasonable default. + +‘merges’: + Pointer to a matrix, the merges performed by the algorithm will be + stored here (if not ‘NULL’). Each merge is a row in a two-column + matrix and contains the IDs of the merged clusters. Clusters are + numbered from zero and cluster numbers smaller than the number of + nodes in the network belong to the individual vertices as singleton + clusters. In each step a new cluster is created from two other + clusters and its id will be one larger than the largest cluster id + so far. This means that before the first merge we have ‘n’ + clusters (the number of vertices in the graph) numbered from zero + to ‘n - 1’. The first merge creates cluster ‘n’, the second + cluster ‘n + 1’, etc. + +‘modularity’: + Pointer to a vector. If not ‘NULL’ then the modularity score of + the current clustering is stored here after each merge operation. + +‘membership’: + Pointer to a vector. If not a ‘NULL’ pointer, then the membership + vector corresponding to the maximal modularity score is stored + here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_spinglass()’ (*note igraph_community_spinglass + --- Community detection based on statistical mechanics_::), + ‘igraph_community_edge_betweenness()’ (*note + igraph_community_edge_betweenness --- Community finding based on + edge betweenness_::). + + Time complexity: O(|E||V|^2) in the worst case, O(|V|^2 log|V|) +typically, |V| is the number of vertices, |E| is the number of edges. + + * File examples/simple/walktrap.c* + + +File: igraph-docs.info, Node: Edge betweenness based community detection, Next: Community structure based on the optimization of modularity, Prev: Walktrap; Community structure based on random walks, Up: Detecting community structure + +25.5 Edge betweenness based community detection +=============================================== + +* Menu: + +* igraph_community_edge_betweenness -- Community finding based on edge betweenness.: igraph_community_edge_betweenness --- Community finding based on edge betweenness_. +* igraph_community_eb_get_merges -- Calculating the merges, i.e. the dendrogram for an edge betweenness community structure.: igraph_community_eb_get_merges --- Calculating the merges; i_e_ the dendrogram for an edge betweenness community structure_. + + +File: igraph-docs.info, Node: igraph_community_edge_betweenness --- Community finding based on edge betweenness_, Next: igraph_community_eb_get_merges --- Calculating the merges; i_e_ the dendrogram for an edge betweenness community structure_, Up: Edge betweenness based community detection + +25.5.1 igraph_community_edge_betweenness -- Community finding based on edge betweenness. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_edge_betweenness(const igraph_t *graph, + igraph_vector_int_t *removed_edges, + igraph_vector_t *edge_betweenness, + igraph_matrix_int_t *merges, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership, + igraph_bool_t directed, + const igraph_vector_t *weights, + const igraph_vector_t *lengths); + + Community structure detection based on the betweenness of the edges +in the network. This method is also known as the Girvan-Newman +algorithm. + + The idea behind this method is that the betweenness of the edges +connecting two communities is typically high, as many of the shortest +paths between vertices in separate communities pass through them. The +algorithm successively removes edges with the highest betweenness, +recalculating betweenness values after each removal. This way +eventually the network splits into two components, then one of these +components splits again, and so on, until all edges are removed. The +resulting hierarhical partitioning of the vertices can be encoded as a +dendrogram. + + In directed graphs, when ‘directed’ is set to true, the directed +version of betweenness and modularity are used, however, only splits +into _weakly_ connected components are detected. + + When edge weights are given, the ratio of betweenness and weight +values is used to choose which edges to remove first, as described in M. +E. J. Newman: Analysis of Weighted Networks (2004), Section C. Thus, +edges with large weights are treated as strong connections, and will be +removed later than weak connections having similar betweenness. Weights +are also used for calculating modularity. + + If lengths are given, they will be considered for shortest path +length calculations while computing betweenness values. + + Note: In igraph 0.10, this function interpreted weights in a +different, erroneous way, and issued a warning when weights were used. +Please see https://github.com/igraph/igraph/issues/2229 +(https://github.com/igraph/igraph/issues/2229) for additional details. + + References: + + M. Girvan and M. E. J. Newman, Community Structure in Social and +Biological Networks, PNAS 99, 7821 (2002). +https://doi.org/10.1073/pnas.122653799 +(https://doi.org/10.1073/pnas.122653799) + + M. E. J. Newman, Analysis of Weighted Networks, Phys. Rev. E 70, 9 +(2004). https://doi.org/10.1103/PhysRevE.70.056131 +(https://doi.org/10.1103/PhysRevE.70.056131) + + *Arguments:. * + +‘graph’: + The input graph. + +‘removed_edges’: + Pointer to an initialized integer vector, which will be resized as + needed. The IDs of the removed edges in the order of their removal + will be stored here. This vector is suitable as input to + ‘igraph_community_eb_get_merges()’ (*note + igraph_community_eb_get_merges --- Calculating the merges; i_e_ the + dendrogram for an edge betweenness community structure_::). This + parameter may be ‘NULL’ if the edge IDs are not needed by the + caller. + +‘edge_betweenness’: + Pointer to an initialized vector or ‘NULL’. In the former case the + edge betweenness of the removed edges is stored here. The vector + will be resized as needed. Note that the betweenness values stored + here are _not_ divided by weights. + +‘merges’: + Pointer to an initialized matrix or ‘NULL’. If not ‘NULL’ then + merges performed by the algorithm are stored here. Even if this is + a divisive algorithm, we can replay it backwards and note which two + clusters were merged. Clusters are numbered from zero. See + ‘igraph_community_to_membership()’ (*note + igraph_community_to_membership --- Cut a dendrogram after a given + number of merges_::) for details. The matrix will be resized as + needed. + +‘bridges’: + Pointer to an initialized vector of ‘NULL’. If not ‘NULL’ then the + indices into ‘result’ of all edges which caused one of the ‘merges’ + will be put here. This is equivalent to all edge removals which + separated the network into more components, in reverse order. + +‘modularity’: + If not a null pointer, then the modularity values of the different + divisions are stored here, in the order corresponding to the merge + matrix. The modularity values will take weights into account if + ‘weights’ is not null. + +‘membership’: + If not a null pointer, then the membership vector, corresponding to + the highest modularity value, is stored here. + +‘directed’: + Boolean constant. Controls whether to calculate directed + betweenness (i.e. directed paths) for directed graphs, and whether + to use the directed version of modularity. It is ignored for + undirected graphs. + +‘weights’: + An optional vector containing edge weights. If not ‘NULL’, the + weights will be used to divide the edge betweenness scores, as well + as for the calculation of modularity. + +‘lengths’: + An optional vector containing edge lengths. If not ‘NULL’, path + lengths used in the betweenness calculation will take these lengths + into account. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_eb_get_merges()’ (*note + igraph_community_eb_get_merges --- Calculating the merges; i_e_ the + dendrogram for an edge betweenness community structure_::), + ‘igraph_community_spinglass()’ (*note igraph_community_spinglass + --- Community detection based on statistical mechanics_::), + ‘igraph_community_walktrap()’ (*note igraph_community_walktrap --- + Community finding using a random walk based similarity measure_::). + + Time complexity: O(|V||E|^2), as the betweenness calculation requires +O(|V||E|) and we do it |E|-1 times. + + * File examples/simple/igraph_community_edge_betweenness.c* + + +File: igraph-docs.info, Node: igraph_community_eb_get_merges --- Calculating the merges; i_e_ the dendrogram for an edge betweenness community structure_, Prev: igraph_community_edge_betweenness --- Community finding based on edge betweenness_, Up: Edge betweenness based community detection + +25.5.2 igraph_community_eb_get_merges -- Calculating the merges, i.e. the dendrogram for an edge betweenness community structure. +--------------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_eb_get_merges(const igraph_t *graph, + const igraph_bool_t directed, + const igraph_vector_int_t *edges, + const igraph_vector_t *weights, + igraph_matrix_int_t *res, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + + This function is handy if you have a sequence of edges which are +gradually removed from the network and you would like to know how the +network falls apart into separate components. The edge sequence may +come from the ‘igraph_community_edge_betweenness()’ (*note +igraph_community_edge_betweenness --- Community finding based on edge +betweenness_::) function, but this is not necessary. Note that +‘igraph_community_edge_betweenness()’ (*note +igraph_community_edge_betweenness --- Community finding based on edge +betweenness_::) can also calculate the dendrogram, via its ‘merges’ +argument. Merges happen when the edge removal process is run backwards +and two components become connected. + + *Arguments:. * + +‘graph’: + The input graph. + +‘directed’: + Whether to use the directed or undirected version of modularity. + Will be ignored for undirected graphs. + +‘edges’: + Vector containing the edges to be removed from the network, all + edges are expected to appear exactly once in the vector. + +‘weights’: + An optional vector containing edge weights. If null, the + unweighted modularity scores will be calculated. If not null, the + weighted modularity scores will be calculated. Ignored if both + ‘modularity’ and ‘membership’ are ‘NULL’ pointers. + +‘res’: + Pointer to an initialized matrix, if not ‘NULL’ then the dendrogram + will be stored here, in the same form as for the + ‘igraph_community_walktrap()’ (*note igraph_community_walktrap --- + Community finding using a random walk based similarity measure_::) + function: the matrix has two columns and each line is a merge given + by the IDs of the merged components. The component IDs are + numbered from zero and component IDs smaller than the number of + vertices in the graph belong to individual vertices. The + non-trivial components containing at least two vertices are + numbered from ‘n’, where ‘n’ is the number of vertices in the + graph. So if the first line contains ‘a’ and ‘b’ that means that + components ‘a’ and ‘b’ are merged into component ‘n’, the second + line creates component ‘n + 1’, etc. The matrix will be resized as + needed. + +‘bridges’: + Pointer to an initialized vector of ‘NULL’. If not ‘NULL’ then the + indices into ‘edges’ of all edges which caused one of the merges + will be put here. This is equal to all edge removals which + separated the network into more components, in reverse order. + +‘modularity’: + If not a null pointer, then the modularity values for the different + divisions, corresponding to the merges matrix, will be stored here. + +‘membership’: + If not a null pointer, then the membership vector for the best + division (in terms of modularity) will be stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_edge_betweenness()’ (*note + igraph_community_edge_betweenness --- Community finding based on + edge betweenness_::). + + Time complexity: O(|E|+|V|log|V|), |V| is the number of vertices, |E| +is the number of edges. + + +File: igraph-docs.info, Node: Community structure based on the optimization of modularity, Next: Fluid communities, Prev: Edge betweenness based community detection, Up: Detecting community structure + +25.6 Community structure based on the optimization of modularity +================================================================ + +* Menu: + +* igraph_community_fastgreedy -- Finding community structure by greedy optimization of modularity.: igraph_community_fastgreedy --- Finding community structure by greedy optimization of modularity_. +* igraph_community_multilevel -- Finding community structure by multi-level optimization of modularity (Louvain).: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_. +* igraph_community_leiden -- Finding community structure using the Leiden algorithm.: igraph_community_leiden --- Finding community structure using the Leiden algorithm_. +* igraph_community_leiden_simple -- Finding community structure using the Leiden algorithm, simple interface.: igraph_community_leiden_simple --- Finding community structure using the Leiden algorithm; simple interface_. + + +File: igraph-docs.info, Node: igraph_community_fastgreedy --- Finding community structure by greedy optimization of modularity_, Next: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_, Up: Community structure based on the optimization of modularity + +25.6.1 igraph_community_fastgreedy -- Finding community structure by greedy optimization of modularity. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_fastgreedy(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + + This function implements the fast greedy modularity optimization +algorithm for finding community structure, see A Clauset, MEJ Newman, C +Moore: Finding community structure in very large networks, +http://www.arxiv.org/abs/cond-mat/0408187 +(http://www.arxiv.org/abs/cond-mat/0408187) for the details. + + Some improvements proposed in K Wakita, T Tsurumi: Finding community +structure in mega-scale social networks, +http://www.arxiv.org/abs/cs.CY/0702048v1 +(http://www.arxiv.org/abs/cs.CY/0702048v1) have also been implemented. + + *Arguments:. * + +‘graph’: + The input graph. It must be a graph without multiple edges. This + is checked and an error message is given for graphs with multiple + edges. + +‘weights’: + Potentially a numeric vector containing edge weights. Supply a + null pointer here for unweighted graphs. The weights are expected + to be non-negative. + +‘merges’: + Pointer to an initialized matrix or ‘NULL’, the result of the + computation is stored here as a merges matrix representing a + dendrogram. The matrix has two columns and each merge corresponds + to one merge, the IDs of the two merged components are stored. The + component IDs are numbered from zero and the first ‘n’ components + are the individual vertices, ‘n’ is the number of vertices in the + graph. Component ‘n’ is created in the first merge, component + ‘n+1’ in the second merge, etc. The matrix will be resized as + needed. If this argument is ‘NULL’ then it is ignored completely. + +‘modularity’: + Pointer to an initialized vector or ‘NULL’ pointer, in the former + case the modularity scores along the stages of the computation are + recorded here. The vector will be resized as needed. + +‘membership’: + Pointer to a vector. If not a null pointer, then the membership + vector corresponding to the best split (in terms of modularity) is + stored here. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_community_to_membership()’ (*note + igraph_community_to_membership --- Cut a dendrogram after a given + number of merges_::) to cut the dendrogram at an arbitrary number + of steps. + + Time complexity: O(|E||V|log|V|) in the worst case, +O(|E|+|V|log^2|V|) typically, |V| is the number of vertices, |E| is the +number of edges. + + * File examples/simple/igraph_community_fastgreedy.c* + + +File: igraph-docs.info, Node: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_, Next: igraph_community_leiden --- Finding community structure using the Leiden algorithm_, Prev: igraph_community_fastgreedy --- Finding community structure by greedy optimization of modularity_, Up: Community structure based on the optimization of modularity + +25.6.2 igraph_community_multilevel -- Finding community structure by multi-level optimization of modularity (Louvain). +---------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_multilevel(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_vector_int_t *membership, + igraph_matrix_int_t *memberships, + igraph_vector_t *modularity); + + This function implements a multi-level modularity optimization +algorithm for finding community structure, sometimes known as the +Louvain algorithm. + + The algorithm is based on the modularity measure and a hierarchical +approach. Initially, each vertex is assigned to a community on its own. +In every step, vertices are re-assigned to communities in a local, +greedy way: in a random order, each vertex is moved to the community +with which it achieves the highest contribution to modularity. When no +vertices can be reassigned, each community is considered a vertex on its +own, and the process starts again with the merged communities. The +process stops when there is only a single vertex left or when the +modularity cannot be increased any more in a step. + + The resolution parameter ‘γ’ allows finding communities at different +resolutions. Higher values of the resolution parameter typically result +in more, smaller communities. Lower values typically result in fewer, +larger communities. The original definition of modularity is retrieved +when setting ‘γ=1’. Note that the returned modularity value is +calculated using the indicated resolution parameter. See +‘igraph_modularity()’ (*note igraph_modularity --- Calculates the +modularity of a graph with respect to some clusters or vertex types_::) +for more details. + + The original version of this function was contributed by Tom +Gregorovic. + + Reference: + + Blondel, V. D., Guillaume, J.-L., Lambiotte, R., & Lefebvre, E.: Fast +unfolding of communities in large networks. Journal of Statistical +Mechanics: Theory and Experiment, 10008(10), 6 (2008). +https://doi.org/10.1088/1742-5468/2008/10/P10008 +(https://doi.org/10.1088/1742-5468/2008/10/P10008) + + *Arguments:. * + +‘graph’: + The input graph. It must be an undirected graph. + +‘weights’: + Numeric vector containing edge weights. If ‘NULL’, every edge has + equal weight. The weights are expected to be non-negative. + +‘resolution’: + Resolution parameter. Must be greater than or equal to 0. Lower + values favor fewer, larger communities; higher values favor more, + smaller communities. Set it to 1 to use the classical definition + of modularity. + +‘membership’: + The membership vector, the result is returned here. For each + vertex it gives the ID of its community. The vector must be + initialized and it will be resized accordingly. + +‘memberships’: + Numeric matrix that will contain the membership vector after each + level, if not ‘NULL’. It must be initialized and it will be + resized accordingly. + +‘modularity’: + Numeric vector that will contain the modularity score after each + level, if not ‘NULL’. It must be initialized and it will be + resized accordingly. + + *Returns:. * + +‘’ + Error code. + + Time complexity: in average near linear on sparse graphs. + + * File examples/simple/igraph_community_multilevel.c* + + +File: igraph-docs.info, Node: igraph_community_leiden --- Finding community structure using the Leiden algorithm_, Next: igraph_community_leiden_simple --- Finding community structure using the Leiden algorithm; simple interface_, Prev: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_, Up: Community structure based on the optimization of modularity + +25.6.3 igraph_community_leiden -- Finding community structure using the Leiden algorithm. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_leiden( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality); + + This function implements the Leiden algorithm for finding community +structure. + + It is similar to the multilevel algorithm, often called the Louvain +algorithm, but it is faster and yields higher quality solutions. It can +optimize both modularity and the Constant Potts Model, which does not +suffer from the resolution-limit (see Traag, Van Dooren & Nesterov). + + The Leiden algorithm consists of three phases: (1) local moving of +vertices, (2) refinement of the partition and (3) aggregation of the +network based on the refined partition, using the non-refined partition +to create an initial partition for the aggregate network. In the local +move procedure in the Leiden algorithm, only vertices whose neighborhood +has changed are visited. Only moves that strictly improve the quality +function are made. The refinement is done by restarting from a +singleton partition within each cluster and gradually merging the +subclusters. When aggregating, a single cluster may then be represented +by several vertices (which are the subclusters identified in the +refinement). + + The Leiden algorithm provides several guarantees. The Leiden +algorithm is typically iterated: the output of one iteration is used as +the input for the next iteration. At each iteration all clusters are +guaranteed to be (weakly) connected and well-separated. After an +iteration in which nothing has changed, all vertices and some parts are +guaranteed to be locally optimally assigned. Note that even if a single +iteration did not result in any change, it is still possible that a +subsequent iteration might find some improvement. Each iteration +explores different subsets of vertices to consider for moving from one +cluster to another. Finally, asymptotically, all subsets of all +clusters are guaranteed to be locally optimally assigned. For more +details, please see Traag, Waltman & van Eck (2019). + + The objective function being optimized is + + ‘1 / 2m sum_ij (A_ij - γ n_i n_j) δ(s_i, s_j)’ + + in the undirected case and + + ‘1 / m sum_ij (A_ij - γ n^out_i n^in_j) δ(s_i, s_j)’ + + in the directed case. Here ‘m’ is the total edge weight, ‘A_ij’ is +the weight of edge (i, j), ‘γ’ is the so-called resolution parameter, +‘n_i’ is the vertex weight of vertex ‘i’ (separate out- and in-weights +are used with directed graphs), ‘s_i’ is the cluster of vertex ‘i’ and +‘δ(x, y) = 1’ if and only if ‘x = y’ and 0 otherwise. + + By setting ‘n_i = k_i’, the degree of vertex ‘i’, and dividing ‘γ’ by +‘2m’ (by ‘m’ in the directed case), we effectively obtain an expression +for modularity. Hence, the standard modularity will be optimized when +you supply the degrees (out- and in-degrees with directed graphs) as the +vertex weights and by supplying as a resolution parameter ‘1/(2m)’ +(‘1/m’ with directed graphs). Use the +‘igraph_community_leiden_simple()’ (*note igraph_community_leiden_simple +--- Finding community structure using the Leiden algorithm; simple +interface_::) convenience function to compute vertex weights +automatically for modularity maximization. + + References: + + V. A. Traag, L. Waltman, N. J. van Eck: From Louvain to Leiden: +guaranteeing well-connected communities. Scientific Reports, 9(1), 5233 +(2019). http://dx.doi.org/10.1038/s41598-019-41695-z +(http://dx.doi.org/10.1038/s41598-019-41695-z) + + V. A. Traag, P. Van Dooren, and Y. Nesterov: Narrow scope for +resolution-limit-free community detection. Phys. Rev. E 84, 016114 +(2011). https://doi.org/10.1103/PhysRevE.84.016114 +(https://doi.org/10.1103/PhysRevE.84.016114) + + *Arguments:. * + +‘graph’: + The input graph. + +‘edge_weights’: + Numeric vector containing edge weights. If ‘NULL’, every edge has + equal weight of 1. The weights need not be non-negative. + +‘vertex_out_weights’: + Numeric vector containing vertex weights, or vertex out-weights for + directed graphs. If ‘NULL’, every vertex has equal weight of 1. + +‘vertex_in_weights’: + Numeric vector containing vertex in-weights for directed graphs. + If set to ‘NULL’, in-weights are assumed to be the same as + out-weights, which effectively ignores edge directions. Must be + ‘NULL’ for undirected graphs. + +‘n_iterations’: + Iterate the core Leiden algorithm the indicated number of times. + If this is a negative number, it will continue iterating until an + iteration did not change the clustering. Two iterations are often + sufficient, thus 2 is a reasonable default. + +‘beta’: + The randomness used in the refinement step when merging. A small + amount of randomness (‘beta’ = 0.01) typically works well. + +‘start’: + Start from membership vector. If this is true, the optimization + will start from the provided membership vector. If this is false, + the optimization will start from a singleton partition. + +‘n_iterations’: + Iterate the core Leiden algorithm for the indicated number of + times. If this is a negative number, it will continue iterating + until an iteration did not change the clustering. + +‘membership’: + The membership vector. This is both used as the initial membership + from which optimisation starts and is updated in place. It must + hence be properly initialized. When finding clusters from scratch + it is typically started using a singleton clustering. This can be + achieved using ‘igraph_vector_int_init_range()’ (*note + igraph_vector_init_range --- Initializes a vector with a range_::). + +‘nb_clusters’: + The number of clusters contained in the final ‘membership’. If + ‘NULL’, the number of clusters will not be returned. + +‘quality’: + The quality of the partition, in terms of the objective function as + included in the documentation. If ‘NULL’ the quality will not be + calculated. + + *Returns:. * + +‘’ + Error code. + + Time complexity: near linear on sparse graphs. + + *See also:. * + +‘’ + ‘igraph_community_leiden_simple()’ (*note + igraph_community_leiden_simple --- Finding community structure + using the Leiden algorithm; simple interface_::) for a simplified + interface that allows specifying an objective function directly and + does not require vertex weights. + + * File examples/simple/igraph_community_leiden.c* + + +File: igraph-docs.info, Node: igraph_community_leiden_simple --- Finding community structure using the Leiden algorithm; simple interface_, Prev: igraph_community_leiden --- Finding community structure using the Leiden algorithm_, Up: Community structure based on the optimization of modularity + +25.6.4 igraph_community_leiden_simple -- Finding community structure using the Leiden algorithm, simple interface. +------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_community_leiden_simple( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_leiden_objective_t objective, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality); + + This is a simplified interface to ‘igraph_community_leiden()’ (*note +igraph_community_leiden --- Finding community structure using the Leiden +algorithm_::) for convenience purposes. Instead of requiring vertex +weights, it allows choosing from a set of objective functions to +maximize. It implements these objective functions by passing suitable +vertex weights to ‘igraph_community_leiden()’ (*note +igraph_community_leiden --- Finding community structure using the Leiden +algorithm_::), as explained in the documentation of that function. + + *Arguments:. * + +‘graph’: + The input graph. May be directed or undirected. + +‘weights’: + The edge weights. If ‘NULL’, all weights are assumed to be 1. + +‘objective’: + The objective function to maximize. + + ‘IGRAPH_LEIDEN_OBJECTIVE_MODULARITY’ + Use the generalized modularity, defined as ‘Q = 1/(2m) sum_ij + (A_ij - γ k_i k_j / (2m)) δ(c_i, c_j)’ for undirected graphs + and as ‘Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i, + c_j)’ for directed graphs. This effectively uses a multigraph + configuration model as the null model. Edge weights must not + be negative. + + ‘IGRAPH_LEIDEN_OBJECTIVE_CPM’ + Use the constant Potts model, whose objective function is + defined as ‘Q = 1/(2m) sum_ij (A_ij - γ) δ(c_i, c_j)’ for + undirected graphs and as ‘Q = 1/m sum_ij (A_ij - γ) δ(c_i, + c_j)’ for directed graphs. Edge weights are allowed to be + negative. Edge directions have no impact on the result. + + ‘IGRAPH_LEIDEN_OBJECTIVE_ER’ + Use an objective function based on the multigraph Erdős-Rényi + G(n,p) null model, defined as ‘Q = 1/(2m) sum_ij (A_ij - γ p) + δ(c_i, c_j)’ for undirected graphs and as ‘Q = 1/m sum_ij + (A_ij - γ p) δ(c_i, c_j)’ for directed graphs. ‘p’ is the + weighted density, i.e. the average link strength between all + vertex pairs (whether adjacent or not). Edge weights must not + be negative. Edge directions have no impact on the result. + + In the above formulas, ‘A’ is the adjacency matrix, ‘m’ is the + total edge weight, ‘k’ are the (out- and in-) degrees, ‘γ’ is the + resolution parameter, and ‘δ(c_i, c_j)’ is 1 if vertices ‘i’ and + ‘j’ are in the same community and 0 otherwise. Edge directions are + only relevant with ‘IGRAPH_LEIDEN_OBJECTIVE_MODULARITY’. The other + two objective functions are equivalent between directed and + undirected graphs: the formal difference is due to each edge being + included twice in undirected (symmetric) adjacency matrices. + +‘resolution’: + The resolution parameter, which is represented by γ in the + objective functions detailed above. + +‘beta’: + The randomness used in the refinement step when merging. A small + amount of randomness (‘beta’ = 0.01) typically works well. + +‘start’: + Start from membership vector. If this is true, the optimization + will start from the provided membership vector. If this is false, + the optimization will start from a singleton partition. + +‘n_iterations’: + Iterate the core Leiden algorithm the indicated number of times. + If this is a negative number, it will continue iterating until an + iteration did not change the clustering. Two iterations are often + sufficient, thus 2 is a reasonable default. + +‘membership’: + The membership vector. If ‘start’ is set to ‘false’, it will be + resized appropriately. If ‘start’ is ‘true’, it must be a valid + membership vector for the given ‘graph’. + +‘nb_clusters’: + The number of clusters contained in the final ‘membership’. If + ‘NULL’, the number of clusters will not be returned. + +‘quality’: + The quality of the partition, in terms of the objective function + selected by ‘objective’. If ‘NULL’ the quality will not be + calculated. + + *Returns:. * + +‘’ + Error code. + + Time complexity: near linear on sparse graphs. + + *See also:. * + +‘’ + ‘igraph_community_leiden()’ (*note igraph_community_leiden --- + Finding community structure using the Leiden algorithm_::) for a + more flexible interface that allows specifying raw vertex weights. + + +File: igraph-docs.info, Node: Fluid communities, Next: Label propagation, Prev: Community structure based on the optimization of modularity, Up: Detecting community structure + +25.7 Fluid communities +====================== + +* Menu: + +* igraph_community_fluid_communities -- Community detection based on fluids interacting on the graph.: igraph_community_fluid_communities --- Community detection based on fluids interacting on the graph_. + + +File: igraph-docs.info, Node: igraph_community_fluid_communities --- Community detection based on fluids interacting on the graph_, Up: Fluid communities + +25.7.1 igraph_community_fluid_communities -- Community detection based on fluids interacting on the graph. +---------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_fluid_communities( + const igraph_t *graph, + igraph_int_t no_of_communities, + igraph_vector_int_t *membership); + + The algorithm is based on the simple idea of several fluids +interacting in a non-homogeneous environment (the graph topology), +expanding and contracting based on their interaction and density. +Weighted graphs are not supported. + + This function implements the community detection method described in: +Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive, +Scalable and Diverse Community Detection Algorithm. In: Complex +Networks & Their Applications VI: Proceedings of Complex Networks 2017 +(The Sixth International Conference on Complex Networks and Their +Applications), Springer, vol 689, p 229. +https://doi.org/10.1007/978-3-319-72150-7_19 +(https://doi.org/10.1007/978-3-319-72150-7_19) + + *Arguments:. * + +‘graph’: + The input graph. The graph must be simple and connected. Edge + directions will be ignored. + +‘no_of_communities’: + The number of communities to be found. Must be greater than 0 and + fewer than number of vertices in the graph. + +‘membership’: + The result vector mapping vertices to the communities they are + assigned to. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|) + + +File: igraph-docs.info, Node: Label propagation, Next: The InfoMAP algorithm, Prev: Fluid communities, Up: Detecting community structure + +25.8 Label propagation +====================== + +* Menu: + +* igraph_community_label_propagation -- Community detection based on label propagation.: igraph_community_label_propagation --- Community detection based on label propagation_. + + +File: igraph-docs.info, Node: igraph_community_label_propagation --- Community detection based on label propagation_, Up: Label propagation + +25.8.1 igraph_community_label_propagation -- Community detection based on label propagation. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_label_propagation(const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_neimode_t mode, + const igraph_vector_t *weights, + const igraph_vector_int_t *initial, + const igraph_vector_bool_t *fixed, + igraph_lpa_variant_t lpa_variant); + + This function implements the label propagation-based community +detection algorithm described by Raghavan, Albert and Kumara (2007). +This version extends the original method by the ability to take edge +weights into consideration and also by allowing some labels to be fixed. +In addition, it implements the fast label propagation alternative +introduced by Traag and Å ubelj (2023). + + The algorithm works by iterating over nodes and updating the label of +a node based on the labels of its neighbors. The labels that are most +frequent among the neighbors are said to be dominant labels. The label +of a node is always updated to a dominant label. The algorithm +guarantees that the label for each is dominant when it terminates. + + There are several variants implemented, which work slightly +differently with the dominance of labels. Nodes with a dominant label +might no longer have a dominant label if one of their neighbors change +label. In ‘IGRAPH_LPA_DOMINANCE’ an additional iteration over all nodes +is made after updating all labels to double check whether all nodes +indeed have a dominant label. When updating the label of a node, labels +are always sampled from among all dominant labels. The algorithm stops +when all nodes have dominant labels. In ‘IGRAPH_LPA_RETENTION’ instead +labels are only updated when they are not dominant. That is, they +retain their current label whenever the current label is already +dominant. The algorithm does not make an additional iteration to check +for dominance. Instead, it simply keeps track whether a label has been +updated, and terminates if no updates have been made. In +‘IGRAPH_LPA_FAST’ labels are sampled from among all dominant labels, +similar to ‘IGRAPH_LPA_DOMINANCE’. Instead of iterating over all nodes, +it keeps track of a queue of nodes that should be considered. Nodes are +popped from the queue when they are considered for update. When the +label of a node is updated, the node's neighbors are added to the queue +again (if they weren't already in the queue). The algorithm terminates +when the queue is empty. All variants guarantee that the labels for all +nodes are dominant. + + Weights are taken into account as follows: when the new label of node +‘i’ is determined, the algorithm iterates over all edges incident on +node ‘i’ and calculate the total weight of edges leading to other nodes +with label 0, 1, 2, ..., ‘k’ - 1 (where ‘k’ is the number of possible +labels). The new label of node ‘i’ will then be the label whose edges +(among the ones incident on node ‘i’) have the highest total weight. + + For directed graphs, it is important to know that labels can +circulate freely only within the strongly connected components of the +graph and may propagate in only one direction (or not at all) _between_ +strongly connected components. You should treat directed edges as +directed only if you are aware of the consequences. + + References: + + Raghavan, U.N. and Albert, R. and Kumara, S.: Near linear time +algorithm to detect community structures in large-scale networks. Phys +Rev E 76, 036106 (2007). https://doi.org/10.1103/PhysRevE.76.036106 +(https://doi.org/10.1103/PhysRevE.76.036106) + + Å ubelj, L.: Label propagation for clustering. Chapter in "Advances +in Network Clustering and Blockmodeling" edited by P. Doreian, V. +Batagelj & A. Ferligoj (Wiley, New York, 2018). +https://doi.org/10.1002/9781119483298.ch5 +(https://doi.org/10.1002/9781119483298.ch5) +https://arxiv.org/abs/1709.05634 (https://arxiv.org/abs/1709.05634) + + Traag, V. A., and Å ubelj, L.: Large network community detection by +fast label propagation. Scientific Reports, 13:1, (2023). +https://doi.org/10.1038/s41598-023-29610-z +(https://doi.org/10.1038/s41598-023-29610-z) +https://arxiv.org/abs/2209.13338 (https://arxiv.org/abs/2209.13338) + + *Arguments:. * + +‘graph’: + The input graph. Note that the algorithm was originally defined + for undirected graphs. You are advised to set ‘mode’ to + ‘IGRAPH_ALL’ if you pass a directed graph here to treat it as + undirected. + +‘membership’: + The membership vector, the result is returned here. For each + vertex it gives the ID of its community (label). + +‘mode’: + Whether to consider edge directions for the label propagation, and + if so, which direction the labels should propagate. Ignored for + undirected graphs. ‘IGRAPH_ALL’ means to ignore edge directions + (even in directed graphs). ‘IGRAPH_OUT’ means to propagate labels + along the natural direction of the edges. ‘IGRAPH_IN’ means to + propagate labels _backwards_ (i.e. from head to tail). It is + advised to set this to ‘IGRAPH_ALL’ unless you are specifically + interested in the effect of edge directions. + +‘weights’: + The weight vector, it should contain a positive weight for all the + edges. + +‘initial’: + The initial state. If ‘NULL’, every vertex will have a different + label at the beginning. Otherwise it must be a vector with an + entry for each vertex. Non-negative values denote different + labels, negative entries denote vertices without labels. Unlabeled + vertices which are not reachable from any labeled ones will remain + unlabeled at the end of the label propagation process, and will be + labeled in an additional step to avoid returning negative values in + ‘membership’. In undirected graphs, this happens when entire + connected components are unlabeled. Then, each unlabeled component + will receive its own separate label. In directed graphs, the + outcome of the additional labeling should be considered undefined + and may change in the future; please do not rely on it. + +‘fixed’: + Boolean vector denoting which labels are fixed. Of course this + makes sense only if you provided an initial state, otherwise this + element will be ignored. Note that vertices without labels cannot + be fixed. The fixed status will be ignored for these with a + warning. Also note that label numbers by themselves have no + meaning, and igraph may renumber labels. However, co-membership + constraints will be respected: two vertices can be fixed to be in + the same or in different communities. + +‘lpa_variant’: + Which variant of the label propagation algorithm to run. + + ‘IGRAPH_LPA_DOMINANCE’ + check for dominance of all nodes after each iteration. + + ‘IGRAPH_LPA_RETENTION’ + keep current label if among dominant labels, only check if + labels changed. + + ‘IGRAPH_LPA_FAST’ + sample from dominant labels, only check neighbors. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(m+n) + + * File examples/simple/igraph_community_label_propagation.c* + + +File: igraph-docs.info, Node: The InfoMAP algorithm, Next: Voronoi communities, Prev: Label propagation, Up: Detecting community structure + +25.9 The InfoMAP algorithm +========================== + +* Menu: + +* igraph_community_infomap -- Community structure that minimizes the expected description length of a random walker trajectory.: igraph_community_infomap --- Community structure that minimizes the expected description length of a random walker trajectory_. + + +File: igraph-docs.info, Node: igraph_community_infomap --- Community structure that minimizes the expected description length of a random walker trajectory_, Up: The InfoMAP algorithm + +25.9.1 igraph_community_infomap -- Community structure that minimizes the expected description length of a random walker trajectory. +------------------------------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_community_infomap( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_weights, + igraph_int_t nb_trials, + igraph_bool_t is_regularized, + igraph_real_t regularization_strength, + igraph_vector_int_t *membership, + igraph_real_t *codelength); + + Implementation of the Infomap community detection algorithm of Martin +Rosvall and Carl T. Bergstrom. This algorithm takes edge directions +into account. For more details, see the visualization of the math and +the map generator at https://www.mapequation.org +(https://www.mapequation.org). + + Infomap is based on a random walker model similar to PageRank: the +walker either chooses out-edges to follow with probabilities +proportional to edge weights, or teleports to a random vertex with +probability 0.15. Vertex weights can be given to control the +probability of choosing different vertices as the target of the +teleportation. In addition, Infomap can be regularized to account for +potential missing links. + + As of igraph 1.0, the Infomap library written by Daniel Edler, Anton +Holmgren and Martin Rosvall is used. See +https://github.com/mapequation/infomap/ +(https://github.com/mapequation/infomap/). + + If you want to specify a random seed (as in the original +implementation) you can use ‘igraph_rng_seed()’ (*note igraph_rng_seed +--- Seeds a random number generator_::). + + References: + + M. Rosvall and C. T. Bergstrom: Maps of information flow reveal +community structure in complex networks, PNAS 105, 1118 (2008). +https://dx.doi.org/10.1073/pnas.0706851105 +(https://dx.doi.org/10.1073/pnas.0706851105), +https://arxiv.org/abs/0707.0609 (https://arxiv.org/abs/0707.0609) + + M. Rosvall, D. Axelsson, and C. T. Bergstrom: The map equation, Eur. +Phys. J. Special Topics 178, 13 (2009). +https://dx.doi.org/10.1140/epjst/e2010-01179-1 +(https://dx.doi.org/10.1140/epjst/e2010-01179-1), +https://arxiv.org/abs/0906.1405 (https://arxiv.org/abs/0906.1405) + + Smiljanić, Jelena, Daniel Edler, and Martin Rosvall: Mapping Flows on +Sparse Networks with Missing Links. Phys Rev E 102 (1-1): 012302 +(2020). https://doi.org/10.1103/PhysRevE.102.012302 +(https://doi.org/10.1103/PhysRevE.102.012302), +https://arxiv.org/abs/2106.14798 (https://arxiv.org/abs/2106.14798) + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are taken into account. + +‘edge_weights’: + Numeric vector giving the weights of the edges. The random walker + will favour edges with high weights over edges with low weights; + the probability of picking a particular outbound edge from a node + is directly proportional to its weight. If it is ‘NULL’ then all + edges will have equal weights. The weights are expected to be + non-negative. + +‘vertex_weights’: + Numeric vector giving the weights of the vertices. Vertices with + higher weights are favoured by the random walker when it teleports + to a new vertex. The probability of picking a vertex when the + random walker teleports is directly proportional to the weight of + the vertex. If this argument is ‘NULL’ then all vertices will have + equal weights. Weights are expected to be positive. + +‘nb_trials’: + The number of attempts to partition the network (can be any integer + value equal to or larger than 1). + +‘is_regularized’: + If true, adds a fully connected Bayesian prior network to avoid + overfitting due to missing links. + +‘regularization_strength’: + Adjust relative strength of the Bayesian prior network used for + regularization. This multiplies the default strength, a parameter + of 1 hence uses the default regularization strength. Ignored when + ‘is_regularized’ is set to ‘false’. + +‘membership’: + Pointer to a vector. The membership vector is stored here. ‘NULL’ + means that the caller is not interested in the membership vector. + +‘codelength’: + Pointer to a real. If not ‘NULL’ the code length of the partition + is stored here. + + *Returns:. * + +‘’ + Error code. When Infomap is not available, ‘IGRAPH_UNIMPLEMENTED’ + is returned. + + *See also:. * + +‘’ + ‘igraph_community_spinglass()’ (*note igraph_community_spinglass + --- Community detection based on statistical mechanics_::), + ‘igraph_community_edge_betweenness()’ (*note + igraph_community_edge_betweenness --- Community finding based on + edge betweenness_::), ‘igraph_community_walktrap()’ (*note + igraph_community_walktrap --- Community finding using a random walk + based similarity measure_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Voronoi communities, Prev: The InfoMAP algorithm, Up: Detecting community structure + +25.10 Voronoi communities +========================= + +* Menu: + +* igraph_community_voronoi -- Finds communities using Voronoi partitioning.: igraph_community_voronoi --- Finds communities using Voronoi partitioning_. + + +File: igraph-docs.info, Node: igraph_community_voronoi --- Finds communities using Voronoi partitioning_, Up: Voronoi communities + +25.10.1 igraph_community_voronoi -- Finds communities using Voronoi partitioning. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_community_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, igraph_vector_int_t *generators, + igraph_real_t *modularity, + const igraph_vector_t *lengths, const igraph_vector_t *weights, + igraph_neimode_t mode, igraph_real_t r); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This function finds communities using a Voronoi partitioning of +vertices based on the given edge lengths divided by the edge clustering +coefficient (‘igraph_ecc()’ (*note igraph_ecc --- Edge clustering +coefficient of some edges_::)). The generator vertices are chosen to be +those with the largest local relative density within a radius ‘r’, with +the local relative density of a vertex defined as ‘s m / (m + k)’, where +‘s’ is the strength of the vertex, ‘m’ is the number of edges within the +vertex's first order neighborhood, while ‘k’ is the number of edges with +only one endpoint within this neighborhood. + + References: + + Deritei et al., Community detection by graph Voronoi diagrams, New +Journal of Physics 16, 063007 (2014) +https://doi.org/10.1088/1367-2630/16/6/063007 +(https://doi.org/10.1088/1367-2630/16/6/063007) + + Molnár et al., Community Detection in Directed Weighted Networks +using Voronoi Partitioning, Scientific Reports 14, 8124 (2024) +https://doi.org/10.1038/s41598-024-58624-4 +(https://doi.org/10.1038/s41598-024-58624-4) + + *Arguments:. * + +‘graph’: + The input graph. It must be simple. + +‘membership’: + If not ‘NULL’, the membership of each vertex is returned here. + +‘generators’: + If not ‘NULL’, the generator points used for Voronoi partitioning + are returned here. + +‘modularity’: + If not ‘NULL’, the modularity score of the partitioning is returned + here. + +‘lengths’: + Edge lengths, or ‘NULL’ to consider all edges as having unit + length. Voronoi partitioning will use edge lengths equal to + lengths / ECC where ECC is the edge clustering coefficient. + +‘weights’: + Edge weights, or ‘NULL’ to consider all edges as having unit + weight. Weights are used when selecting generator points, as well + as for computing modularity. + +‘mode’: + If ‘IGRAPH_OUT’, distances from generator points to all other nodes + are considered. If ‘IGRAPH_IN’, the reverse distances are used. + If ‘IGRAPH_ALL’, edge directions are ignored. This parameter is + ignored for undirected graphs. + +‘r’: + The radius/resolution to use when selecting generator points. The + larger this value, the fewer partitions there will be. Pass in a + negative value to automatically select the radius that maximizes + modularity. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_voronoi()’ (*note igraph_voronoi --- Voronoi partitioning + of a graph_::), ‘igraph_ecc()’ (*note igraph_ecc --- Edge + clustering coefficient of some edges_::). + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Graphlets, Next: Hierarchical random graphs, Prev: Detecting community structure, Up: Top + +26 Graphlets +************ + +* Menu: + +* Introduction: Introduction <1>. +* Performing graphlet decomposition:: + + +File: igraph-docs.info, Node: Introduction <1>, Next: Performing graphlet decomposition, Up: Graphlets + +26.1 Introduction +================= + +Graphlet decomposition models a weighted undirected graph via the union +of potentially overlapping dense social groups. This is done by a +two-step algorithm. In the first step, a candidate set of groups (a +candidate basis) is created by finding cliques in the thresholded input +graph. In the second step, the graph is projected onto the candidate +basis, resulting in a weight coefficient for each clique in the +candidate basis. + + For more information on graphlet decomposition, see Hossein Azari +Soufiani and Edoardo M Airoldi: "Graphlet decomposition of a weighted +network", https://arxiv.org/abs/1203.2821 +(https://arxiv.org/abs/1203.2821) and +http://proceedings.mlr.press/v22/azari12/azari12.pdf +(http://proceedings.mlr.press/v22/azari12/azari12.pdf) + + igraph contains three functions for performing the graphlet +decomponsition of a graph. The first is ‘igraph_graphlets()’ (*note +igraph_graphlets --- Calculate graphlets basis and project the graph on +it_::), which performs both steps of the method and returns a list of +subgraphs with their corresponding weights. The other two functions +correspond to the first and second steps of the algorithm, and they are +useful if the user wishes to perform them individually: +‘igraph_graphlets_candidate_basis()’ (*note +igraph_graphlets_candidate_basis --- Calculate a candidate graphlets +basis::) and ‘igraph_graphlets_project()’ (*note +igraph_graphlets_project --- Project a graph on a graphlets basis_::). + + [Remark: Note: The term "graphlet" is used for several unrelated +concepts in the literature. If you are looking to count induced +subgraphs, see ‘igraph_motifs_randesu()’ (*note igraph_motifs_randesu +--- Count the number of motifs in a graph_::) and +‘igraph_subisomorphic_lad()’ (*note igraph_subisomorphic_lad --- Check +subgraph isomorphism with the LAD algorithm::). ] + + +File: igraph-docs.info, Node: Performing graphlet decomposition, Prev: Introduction <1>, Up: Graphlets + +26.2 Performing graphlet decomposition +====================================== + +* Menu: + +* igraph_graphlets -- Calculate graphlets basis and project the graph on it.: igraph_graphlets --- Calculate graphlets basis and project the graph on it_. +* igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis:: +* igraph_graphlets_project -- Project a graph on a graphlets basis.: igraph_graphlets_project --- Project a graph on a graphlets basis_. + + +File: igraph-docs.info, Node: igraph_graphlets --- Calculate graphlets basis and project the graph on it_, Next: igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis, Up: Performing graphlet decomposition + +26.2.1 igraph_graphlets -- Calculate graphlets basis and project the graph on it. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_int_t niter); + + This function simply calls ‘igraph_graphlets_candidate_basis()’ +(*note igraph_graphlets_candidate_basis --- Calculate a candidate +graphlets basis::) and ‘igraph_graphlets_project()’ (*note +igraph_graphlets_project --- Project a graph on a graphlets basis_::), +and then orders the graphlets according to decreasing weights. + + *Arguments:. * + +‘graph’: + The input graph, it must be a simple graph, edge directions are + ignored. + +‘weights’: + Weights of the edges, a vector. + +‘cliques’: + An initialized list of integer vectors. The graphlet basis is + stored here. Each element of the list is an integer vector of + vertex IDs, encoding a single basis subgraph. + +‘Mu’: + An initialized vector, the weights of the graphlets will be stored + here. + +‘niter’: + The number of iterations to perform for the projection step. + + *Returns:. * + +‘’ + Error code. + + See also: ‘igraph_graphlets_candidate_basis()’ (*note +igraph_graphlets_candidate_basis --- Calculate a candidate graphlets +basis::) and ‘igraph_graphlets_project()’ (*note +igraph_graphlets_project --- Project a graph on a graphlets basis_::). + + +File: igraph-docs.info, Node: igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis, Next: igraph_graphlets_project --- Project a graph on a graphlets basis_, Prev: igraph_graphlets --- Calculate graphlets basis and project the graph on it_, Up: Performing graphlet decomposition + +26.2.2 igraph_graphlets_candidate_basis -- Calculate a candidate graphlets basis +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_graphlets_candidate_basis(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *thresholds); + + *Arguments:. * + +‘graph’: + The input graph, it must be a simple graph, edge directions are + ignored. + +‘weights’: + Weights of the edges, a vector. + +‘cliques’: + An initialized list of integer vectors. The graphlet basis is + stored here. Each element of the list is an integer vector of + vertex IDs, encoding a single basis subgraph. + +‘thresholds’: + An initialized vector, the (highest possible) weight thresholds for + finding the basis subgraphs are stored here. + + *Returns:. * + +‘’ + Error code. + + See also: ‘igraph_graphlets()’ (*note igraph_graphlets --- Calculate +graphlets basis and project the graph on it_::) and +‘igraph_graphlets_project()’ (*note igraph_graphlets_project --- Project +a graph on a graphlets basis_::). + + +File: igraph-docs.info, Node: igraph_graphlets_project --- Project a graph on a graphlets basis_, Prev: igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis, Up: Performing graphlet decomposition + +26.2.3 igraph_graphlets_project -- Project a graph on a graphlets basis. +------------------------------------------------------------------------ + + + igraph_error_t igraph_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + igraph_int_t niter); + + Note that the graph projected does not have to be the same that was +used to calculate the graphlet basis, but it is assumed that it has the +same number of vertices, and the vertex IDs of the two graphs match. + + *Arguments:. * + +‘graph’: + The input graph, it must be a simple graph, edge directions are + ignored. + +‘weights’: + Weights of the edges in the input graph, a vector. + +‘cliques’: + An initialized list of integer vectors. The graphlet basis is + stored here. Each element of the list is an integer vector of + vertex IDs, encoding a single basis subgraph. + +‘Mu’: + An initialized vector, the weights of the graphlets will be stored + here. This vector is also used to initialize the the weight vector + for the iterative algorithm, if the ‘startMu’ argument is true. + +‘startMu’: + If true, then the supplied Mu vector is used as the starting point + of the iteration. Otherwise a constant 1 vector is used. + +‘niter’: + The number of iterations to perform. + + *Returns:. * + +‘’ + Error code. + + See also: ‘igraph_graphlets()’ (*note igraph_graphlets --- Calculate +graphlets basis and project the graph on it_::) and +‘igraph_graphlets_candidate_basis()’ (*note +igraph_graphlets_candidate_basis --- Calculate a candidate graphlets +basis::). + + +File: igraph-docs.info, Node: Hierarchical random graphs, Next: Embedding of graphs, Prev: Graphlets, Up: Top + +27 Hierarchical random graphs +***************************** + +* Menu: + +* Introduction: Introduction <2>. +* Representing HRGs:: +* Fitting HRGs:: +* HRG sampling:: +* Conversion to and from igraph graphs:: +* Predicting missing edges:: +* Deprecated functions:: + + +File: igraph-docs.info, Node: Introduction <2>, Next: Representing HRGs, Up: Hierarchical random graphs + +27.1 Introduction +================= + +A hierarchical random graph is an ensemble of undirected graphs with ‘n’ +vertices. It is defined via a binary tree with ‘n’ leaf and ‘n-1’ +internal vertices, where the internal vertices are labeled with +probabilities. The probability that two vertices are connected in the +random graph is given by the probability label at their closest common +ancestor. + + Please read the following two articles for more about hierarchical +random graphs: A. Clauset, C. Moore, and M.E.J. Newman. Hierarchical +structure and the prediction of missing links in networks. Nature 453, +98 - 101 (2008); and A. Clauset, C. Moore, and M.E.J. Newman. +Structural Inference of Hierarchies in Networks. In E. M. Airoldi et +al. (Eds.): ICML 2006 Ws, Lecture Notes in Computer Science 4503, 1-13. +Springer-Verlag, Berlin Heidelberg (2007). + + igraph contains functions for fitting HRG models to a given network +(‘igraph_hrg_fit’ (*note igraph_hrg_fit --- Fit a hierarchical random +graph model to a network_::)), for generating networks from a given HRG +ensemble (‘igraph_hrg_game’ (*note igraph_hrg_game --- Generate a +hierarchical random graph_::), ‘igraph_hrg_sample’ (*note +igraph_hrg_sample --- Sample from a hierarchical random graph +model_::)), converting an igraph graph to a HRG and back +(‘igraph_hrg_create’ (*note igraph_hrg_create --- Create a HRG from an +igraph graph_::), ‘igraph_hrg_dendrogram’ (*note igraph_hrg_dendrogram +--- Create a dendrogram from a hierarchical random graph_::)), for +calculating a consensus tree from a set of sampled HRGs +(‘igraph_hrg_consensus’ (*note igraph_hrg_consensus --- Calculate a +consensus tree for a HRG_::)) and for predicting missing edges in a +network based on its HRG models (‘igraph_hrg_predict’ (*note +igraph_hrg_predict --- Predict missing edges in a graph; based on HRG +models_::)). + + The igraph HRG implementation is heavily based on the code published +by Aaron Clauset, at his website, +https://aaronclauset.github.io/hierarchy/ +(https://aaronclauset.github.io/hierarchy/) + + +File: igraph-docs.info, Node: Representing HRGs, Next: Fitting HRGs, Prev: Introduction <2>, Up: Hierarchical random graphs + +27.2 Representing HRGs +====================== + +* Menu: + +* igraph_hrg_t -- Data structure to store a hierarchical random graph.: igraph_hrg_t --- Data structure to store a hierarchical random graph_. +* igraph_hrg_init -- Allocate memory for a HRG.: igraph_hrg_init --- Allocate memory for a HRG_. +* igraph_hrg_destroy -- Deallocate memory for an HRG.: igraph_hrg_destroy --- Deallocate memory for an HRG_. +* igraph_hrg_size -- Returns the size of the HRG, the number of leaf nodes.: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_. +* igraph_hrg_resize -- Resize a HRG.: igraph_hrg_resize --- Resize a HRG_. + + +File: igraph-docs.info, Node: igraph_hrg_t --- Data structure to store a hierarchical random graph_, Next: igraph_hrg_init --- Allocate memory for a HRG_, Up: Representing HRGs + +27.2.1 igraph_hrg_t -- Data structure to store a hierarchical random graph. +--------------------------------------------------------------------------- + + + typedef struct igraph_hrg_t { + igraph_vector_int_t left; + igraph_vector_int_t right; + igraph_vector_t prob; + igraph_vector_int_t vertices; + igraph_vector_int_t edges; + } igraph_hrg_t; + + A hierarchical random graph (HRG) can be given as a binary tree, +where the internal vertices are labeled with real numbers. + + Note that you don't necessarily have to know this internal +representation for using the HRG functions, just pass the HRG objects +created by one igraph function, to another igraph function. + + It has the following members: + + *Values:. * + +‘left’: + Vector that contains the left children of the internal tree + vertices. The first vertex is always the root vertex, so the first + element of the vector is the left child of the root vertex. + Internal vertices are denoted with negative numbers, starting from + -1 and going down, i.e. the root vertex is -1. Leaf vertices are + denoted by non-negative number, starting from zero and up. + +‘right’: + Vector that contains the right children of the vertices, with the + same encoding as the ‘left’ vector. + +‘prob’: + The connection probabilities attached to the internal vertices, the + first number belongs to the root vertex (i.e. internal vertex -1), + the second to internal vertex -2, etc. + +‘edges’: + The number of edges in the subtree below the given internal vertex. + +‘vertices’: + The number of vertices in the subtree below the given internal + vertex, including itself. + + +File: igraph-docs.info, Node: igraph_hrg_init --- Allocate memory for a HRG_, Next: igraph_hrg_destroy --- Deallocate memory for an HRG_, Prev: igraph_hrg_t --- Data structure to store a hierarchical random graph_, Up: Representing HRGs + +27.2.2 igraph_hrg_init -- Allocate memory for a HRG. +---------------------------------------------------- + + + igraph_error_t igraph_hrg_init(igraph_hrg_t *hrg, igraph_int_t n); + + This function must be called before passing an ‘igraph_hrg_t’ (*note +igraph_hrg_t --- Data structure to store a hierarchical random graph_::) +to an igraph function. + + *Arguments:. * + +‘hrg’: + Pointer to the HRG data structure to initialize. + +‘n’: + The number of vertices in the graph that is modeled by this HRG. It + can be zero, if this is not yet known. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_hrg_destroy --- Deallocate memory for an HRG_, Next: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_, Prev: igraph_hrg_init --- Allocate memory for a HRG_, Up: Representing HRGs + +27.2.3 igraph_hrg_destroy -- Deallocate memory for an HRG. +---------------------------------------------------------- + + + void igraph_hrg_destroy(igraph_hrg_t *hrg); + + The HRG data structure can be reinitialized again with an +‘igraph_hrg_destroy’ (*note igraph_hrg_destroy --- Deallocate memory for +an HRG_::) call. + + *Arguments:. * + +‘hrg’: + Pointer to the HRG data structure to deallocate. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_, Next: igraph_hrg_resize --- Resize a HRG_, Prev: igraph_hrg_destroy --- Deallocate memory for an HRG_, Up: Representing HRGs + +27.2.4 igraph_hrg_size -- Returns the size of the HRG, the number of leaf nodes. +-------------------------------------------------------------------------------- + + + igraph_int_t igraph_hrg_size(const igraph_hrg_t *hrg); + + *Arguments:. * + +‘hrg’: + Pointer to the HRG. + + *Returns:. * + +‘’ + The number of leaf nodes in the HRG. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_hrg_resize --- Resize a HRG_, Prev: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_, Up: Representing HRGs + +27.2.5 igraph_hrg_resize -- Resize a HRG. +----------------------------------------- + + + igraph_error_t igraph_hrg_resize(igraph_hrg_t *hrg, igraph_int_t newsize); + + *Arguments:. * + +‘hrg’: + Pointer to an initialized (see ‘igraph_hrg_init’ (*note + igraph_hrg_init --- Allocate memory for a HRG_::)) HRG. + +‘newsize’: + The new size, i.e. the number of leaf nodes. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the new size. + + +File: igraph-docs.info, Node: Fitting HRGs, Next: HRG sampling, Prev: Representing HRGs, Up: Hierarchical random graphs + +27.3 Fitting HRGs +================= + +* Menu: + +* igraph_hrg_fit -- Fit a hierarchical random graph model to a network.: igraph_hrg_fit --- Fit a hierarchical random graph model to a network_. +* igraph_hrg_consensus -- Calculate a consensus tree for a HRG.: igraph_hrg_consensus --- Calculate a consensus tree for a HRG_. + + +File: igraph-docs.info, Node: igraph_hrg_fit --- Fit a hierarchical random graph model to a network_, Next: igraph_hrg_consensus --- Calculate a consensus tree for a HRG_, Up: Fitting HRGs + +27.3.1 igraph_hrg_fit -- Fit a hierarchical random graph model to a network. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_hrg_fit(const igraph_t *graph, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t steps); + + *Arguments:. * + +‘graph’: + The igraph graph to fit the model to. Edge directions are ignored + in directed graphs. + +‘hrg’: + Pointer to an initialized HRG, the result of the fitting is stored + here. It can also be used to pass a HRG to the function, that can + be used as the starting point of the Markov Chain Monte Carlo + fitting, if the ‘start’ argument is true. + +‘start’: + Whether to start the fitting from the given HRG model. + +‘steps’: + Integer, the number of MCMC steps to take in the fitting procedure. + If this is zero, then the fitting stops if a convergence criteria + is fulfilled. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_hrg_consensus --- Calculate a consensus tree for a HRG_, Prev: igraph_hrg_fit --- Fit a hierarchical random graph model to a network_, Up: Fitting HRGs + +27.3.2 igraph_hrg_consensus -- Calculate a consensus tree for a HRG. +-------------------------------------------------------------------- + + + igraph_error_t igraph_hrg_consensus(const igraph_t *graph, + igraph_vector_int_t *parents, + igraph_vector_t *weights, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples); + + The calculation can be started from the given HRG (‘hrg’), or (if +‘start’ is false), a HRG is first fitted to the given graph. + + *Arguments:. * + +‘graph’: + The input graph. + +‘parents’: + An initialized vector, the results are stored here. For each + vertex, the id of its parent vertex is stored, or -1, if the vertex + is the root vertex in the tree. The first n vertex IDs (from 0) + refer to the original vertices of the graph, the other IDs refer to + vertex groups. + +‘weights’: + Numeric vector, counts the number of times a given tree split + occured in the generated network samples, for each internal + vertices. The order is the same as in ‘parents’. + +‘hrg’: + A hierarchical random graph. It is used as a starting point for + the sampling, if the ‘start’ argument is true. It is modified + along the MCMC. + +‘start’: + Whether to use the supplied HRG (in ‘hrg’) as a starting point for + the MCMC. + +‘num_samples’: + The number of samples to generate for creating the consensus tree. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: HRG sampling, Next: Conversion to and from igraph graphs, Prev: Fitting HRGs, Up: Hierarchical random graphs + +27.4 HRG sampling +================= + +* Menu: + +* igraph_hrg_sample -- Sample from a hierarchical random graph model.: igraph_hrg_sample --- Sample from a hierarchical random graph model_. +* igraph_hrg_game -- Generate a hierarchical random graph.: igraph_hrg_game --- Generate a hierarchical random graph_. + + +File: igraph-docs.info, Node: igraph_hrg_sample --- Sample from a hierarchical random graph model_, Next: igraph_hrg_game --- Generate a hierarchical random graph_, Up: HRG sampling + +27.4.1 igraph_hrg_sample -- Sample from a hierarchical random graph model. +-------------------------------------------------------------------------- + + + igraph_error_t igraph_hrg_sample(const igraph_hrg_t *hrg, igraph_t *sample); + + This function draws a single sample from a hierarchical random graph +model. + + *Arguments:. * + +‘hrg’: + A HRG model to sample from + +‘sample’: + Pointer to an uninitialized graph; the sample is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_hrg_game --- Generate a hierarchical random graph_, Prev: igraph_hrg_sample --- Sample from a hierarchical random graph model_, Up: HRG sampling + +27.4.2 igraph_hrg_game -- Generate a hierarchical random graph. +--------------------------------------------------------------- + + + igraph_error_t igraph_hrg_game(igraph_t *graph, + const igraph_hrg_t *hrg); + + This function is a simple shortcut to ‘igraph_hrg_sample’ (*note +igraph_hrg_sample --- Sample from a hierarchical random graph model_::). +It creates a single graph from the given HRG. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph, the new graph is created here. + +‘hrg’: + The hierarchical random graph model to sample from. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Conversion to and from igraph graphs, Next: Predicting missing edges, Prev: HRG sampling, Up: Hierarchical random graphs + +27.5 Conversion to and from igraph graphs +========================================= + +* Menu: + +* igraph_from_hrg_dendrogram -- Create a graph representation of the dendrogram of a hierarchical random graph model.: igraph_from_hrg_dendrogram --- Create a graph representation of the dendrogram of a hierarchical random graph model_. +* igraph_hrg_create -- Create a HRG from an igraph graph.: igraph_hrg_create --- Create a HRG from an igraph graph_. + + +File: igraph-docs.info, Node: igraph_from_hrg_dendrogram --- Create a graph representation of the dendrogram of a hierarchical random graph model_, Next: igraph_hrg_create --- Create a HRG from an igraph graph_, Up: Conversion to and from igraph graphs + +27.5.1 igraph_from_hrg_dendrogram -- Create a graph representation of the dendrogram of a hierarchical random graph model. +-------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_from_hrg_dendrogram( + igraph_t *graph, const igraph_hrg_t *hrg, igraph_vector_t *prob + ); + + Creates the igraph graph equivalent of the dendrogram encoded in an +‘igraph_hrg_t’ (*note igraph_hrg_t --- Data structure to store a +hierarchical random graph_::) data structure. The probabilities +associated to the nodes are returned in a vector so this function works +without an attribute handler. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph, the result is stored here. + +‘hrg’: + The hierarchical random graph to convert. + +‘prob’: + Pointer to an _initialized_ vector; the probabilities associated to + the nodes of the dendrogram will be stored here. Leaf nodes will + have an associated probability of ‘IGRAPH_NAN’ . You may set this + to ‘NULL’ if you do not need the probabilities. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_hrg_create --- Create a HRG from an igraph graph_, Prev: igraph_from_hrg_dendrogram --- Create a graph representation of the dendrogram of a hierarchical random graph model_, Up: Conversion to and from igraph graphs + +27.5.2 igraph_hrg_create -- Create a HRG from an igraph graph. +-------------------------------------------------------------- + + + igraph_error_t igraph_hrg_create(igraph_hrg_t *hrg, + const igraph_t *graph, + const igraph_vector_t *prob); + + *Arguments:. * + +‘hrg’: + Pointer to an initialized ‘igraph_hrg_t’ (*note igraph_hrg_t --- + Data structure to store a hierarchical random graph_::). The + result is stored here. + +‘graph’: + The igraph graph to convert. It must be a directed binary tree, + with n-1 internal and n leaf vertices. The root vertex must have + in-degree zero. + +‘prob’: + The vector of probabilities, this is used to label the internal + nodes of the hierarchical random graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of vertices in the tree. + + +File: igraph-docs.info, Node: Predicting missing edges, Next: Deprecated functions, Prev: Conversion to and from igraph graphs, Up: Hierarchical random graphs + +27.6 Predicting missing edges +============================= + +* Menu: + +* igraph_hrg_predict -- Predict missing edges in a graph, based on HRG models.: igraph_hrg_predict --- Predict missing edges in a graph; based on HRG models_. + + +File: igraph-docs.info, Node: igraph_hrg_predict --- Predict missing edges in a graph; based on HRG models_, Up: Predicting missing edges + +27.6.1 igraph_hrg_predict -- Predict missing edges in a graph, based on HRG models. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_hrg_predict(const igraph_t *graph, + igraph_vector_int_t *edges, + igraph_vector_t *prob, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples, + igraph_int_t num_bins); + + Samples HRG models for a network, and estimated the probability that +an edge was falsely observed as non-existent in the network. + + *Arguments:. * + +‘graph’: + The input graph. + +‘edges’: + The list of missing edges is stored here, the first two elements + are the first edge, the next two the second edge, etc. + +‘prob’: + Vector of probabilies for the existence of missing edges, in the + order corresponding to ‘edges’. + +‘hrg’: + A HRG, it is used as a starting point if ‘start’ is true. It is + also modified during the MCMC sampling. + +‘start’: + Whether to start the MCMC from the given HRG. + +‘num_samples’: + The number of samples to generate. + +‘num_bins’: + Controls the resolution of the edge probabilities. Higher numbers + result higher resolution. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: Deprecated functions, Prev: Predicting missing edges, Up: Hierarchical random graphs + +27.7 Deprecated functions +========================= + +* Menu: + +* igraph_hrg_dendrogram -- Create a dendrogram from a hierarchical random graph.: igraph_hrg_dendrogram --- Create a dendrogram from a hierarchical random graph_. + + +File: igraph-docs.info, Node: igraph_hrg_dendrogram --- Create a dendrogram from a hierarchical random graph_, Up: Deprecated functions + +27.7.1 igraph_hrg_dendrogram -- Create a dendrogram from a hierarchical random graph. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_hrg_dendrogram(igraph_t *graph, const igraph_hrg_t *hrg); + + Creates the igraph graph equivalent of an ‘igraph_hrg_t’ (*note +igraph_hrg_t --- Data structure to store a hierarchical random graph_::) +data structure. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph, the result is stored here. + +‘hrg’: + The hierarchical random graph to convert. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), the number of vertices in the graph. + + *Warning* + + Deprecated since version 0.10.5. Please do not use this function + in new code; use ‘igraph_from_hrg_dendrogram()’ (*note + igraph_from_hrg_dendrogram --- Create a graph representation of the + dendrogram of a hierarchical random graph model_::) instead. + + +File: igraph-docs.info, Node: Embedding of graphs, Next: Generating layouts for graph drawing, Prev: Hierarchical random graphs, Up: Top + +28 Embedding of graphs +********************** + +* Menu: + +* Spectral embedding:: + + +File: igraph-docs.info, Node: Spectral embedding, Up: Embedding of graphs + +28.1 Spectral embedding +======================= + +* Menu: + +* igraph_adjacency_spectral_embedding --- Adjacency spectral embedding:: +* igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph:: +* igraph_dim_select -- Dimensionality selection.: igraph_dim_select --- Dimensionality selection_. + + +File: igraph-docs.info, Node: igraph_adjacency_spectral_embedding --- Adjacency spectral embedding, Next: igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph, Up: Spectral embedding + +28.1.1 igraph_adjacency_spectral_embedding -- Adjacency spectral embedding +-------------------------------------------------------------------------- + + + igraph_error_t igraph_adjacency_spectral_embedding(const igraph_t *graph, + igraph_int_t n, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + igraph_arpack_options_t *options); + + Spectral decomposition of the adjacency matrices of graphs. This +function computes an ‘n’-dimensional Euclidean representation of the +graph based on its adjacency matrix, A. This representation is computed +via the singular value decomposition of the adjacency matrix, A=U D V^T. +In the case, where the graph is a random dot product graph generated +using latent position vectors in R^n for each vertex, the embedding will +provide an estimate of these latent vectors. + + For undirected graphs, the latent positions are calculated as X = U^n +D^(1/2) where U^n equals to the first no columns of U, and D^(1/2) is a +diagonal matrix containing the square root of the selected singular +values on the diagonal. + + For directed graphs, the embedding is defined as the pair X = U^n +D^(1/2), Y = V^n D^(1/2). (For undirected graphs U=V, so it is +sufficient to keep one of them.) + + *Arguments:. * + +‘graph’: + The input graph, can be directed or undirected. + +‘n’: + An integer scalar. This value is the embedding dimension of the + spectral embedding. Should be smaller than the number of vertices. + The largest n-dimensional non-zero singular values are used for the + spectral embedding. + +‘weights’: + Optional edge weights. Supply a null pointer for unweighted + graphs. + +‘which’: + Which eigenvalues (or singular values, for directed graphs) to use, + possible values: + + ‘IGRAPH_EIGEN_LM’ + the ones with the largest magnitude + + ‘IGRAPH_EIGEN_LA’ + the (algebraic) largest ones + + ‘IGRAPH_EIGEN_SA’ + the (algebraic) smallest ones. + + For directed graphs, ‘IGRAPH_EIGEN_LM’ and ‘IGRAPH_EIGEN_LA’ are + the same because singular values are used for the ordering instead + of eigenvalues. + +‘scaled’: + Whether to return X and Y (if ‘scaled’ is true), or U and V. + +‘X’: + Initialized matrix, the estimated latent positions are stored here. + +‘Y’: + Initialized matrix or a null pointer. If not a null pointer, then + the second half of the latent positions are stored here. (For + undirected graphs, this always equals X.) + +‘D’: + Initialized vector or a null pointer. If not a null pointer, then + the eigenvalues (for undirected graphs) or the singular values (for + directed graphs) are stored here. + +‘cvec’: + A numeric vector, its length is the number vertices in the graph. + This vector is added to the diagonal of the adjacency matrix, + before performing the SVD. + +‘options’: + Options to ARPACK. See ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) for details. + Supply ‘NULL’ to use the defaults. Note that the function + overwrites the ‘n’ (number of vertices), ‘nev’ and ‘which’ + parameters and it always starts the calculation from a random start + vector. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph, Next: igraph_dim_select --- Dimensionality selection_, Prev: igraph_adjacency_spectral_embedding --- Adjacency spectral embedding, Up: Spectral embedding + +28.1.2 igraph_laplacian_spectral_embedding -- Spectral embedding of the Laplacian of a graph +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_laplacian_spectral_embedding(const igraph_t *graph, + igraph_int_t n, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options); + + This function essentially does the same as +‘igraph_adjacency_spectral_embedding’ (*note +igraph_adjacency_spectral_embedding --- Adjacency spectral embedding::), +but works on the Laplacian of the graph, instead of the adjacency +matrix. + + *Arguments:. * + +‘graph’: + The input graph. + +‘n’: + The number of eigenvectors (or singular vectors if the graph is + directed) to use for the embedding. + +‘weights’: + Optional edge weights. Supply a null pointer for unweighted + graphs. + +‘which’: + Which eigenvalues (or singular values, for directed graphs) to use, + possible values: + + ‘IGRAPH_EIGEN_LM’ + the ones with the largest magnitude + + ‘IGRAPH_EIGEN_LA’ + the (algebraic) largest ones + + ‘IGRAPH_EIGEN_SA’ + the (algebraic) smallest ones. + + For directed graphs, ‘IGRAPH_EIGEN_LM’ and ‘IGRAPH_EIGEN_LA’ are + the same because singular values are used for the ordering instead + of eigenvalues. + +‘type’: + The type of the Laplacian to use. Various definitions exist for + the Laplacian of a graph, and one can choose between them with this + argument. Possible values: + + ‘IGRAPH_EMBEDDING_D_A’ + means D - A where D is the degree matrix and A is the + adjacency matrix + + ‘IGRAPH_EMBEDDING_DAD’ + means Di times A times Di, where Di is the inverse of the + square root of the degree matrix; + + ‘IGRAPH_EMBEDDING_I_DAD’ + means I - Di A Di, where I is the identity matrix. + +‘scaled’: + Whether to return X and Y (if ‘scaled’ is true), or U and V. + +‘X’: + Initialized matrix, the estimated latent positions are stored here. + +‘Y’: + Initialized matrix or a null pointer. If not a null pointer, then + the second half of the latent positions are stored here. (For + undirected graphs, this always equals X.) + +‘D’: + Initialized vector or a null pointer. If not a null pointer, then + the eigenvalues (for undirected graphs) or the singular values (for + directed graphs) are stored here. + +‘options’: + Options to ARPACK. See ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) for details. + Supply ‘NULL’ to use the defaults. Note that the function + overwrites the ‘n’ (number of vertices), ‘nev’ and ‘which’ + parameters and it always starts the calculation from a random start + vector. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_adjacency_spectral_embedding’ (*note + igraph_adjacency_spectral_embedding --- Adjacency spectral + embedding::) to embed the adjacency matrix. + + +File: igraph-docs.info, Node: igraph_dim_select --- Dimensionality selection_, Prev: igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph, Up: Spectral embedding + +28.1.3 igraph_dim_select -- Dimensionality selection. +----------------------------------------------------- + + + igraph_error_t igraph_dim_select(const igraph_vector_t *sv, igraph_int_t *dim); + + Dimensionality selection for singular values using profile +likelihood. + + The input of the function is a numeric vector which contains the +measure of "importance" for each dimension. + + For spectral embedding, these are the singular values of the +adjacency matrix. The singular values are assumed to be generated from +a Gaussian mixture distribution with two components that have different +means and same variance. The dimensionality d is chosen to maximize the +likelihood when the d largest singular values are assigned to one +component of the mixture and the rest of the singular values assigned to +the other component. + + This function can also be used for the general separation problem, +where we assume that the left and the right of the vector are coming +from two normal distributions, with different means, and we want to know +their border. + + *Arguments:. * + +‘sv’: + A numeric vector, the ordered singular values. + +‘dim’: + The result is stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the number of values in sv. + + *See also:. * + +‘’ + ‘igraph_adjacency_spectral_embedding()’ (*note + igraph_adjacency_spectral_embedding --- Adjacency spectral + embedding::). + + +File: igraph-docs.info, Node: Generating layouts for graph drawing, Next: Processes on graphs, Prev: Embedding of graphs, Up: Top + +29 Generating layouts for graph drawing +*************************************** + +* Menu: + +* 2D layout generators:: +* Layouts for trees and acyclic graphs:: +* 3D layout generators:: +* Post-processing layouts:: + + +File: igraph-docs.info, Node: 2D layout generators, Next: Layouts for trees and acyclic graphs, Up: Generating layouts for graph drawing + +29.1 2D layout generators +========================= + +Layout generator functions (or at least most of them) try to place the +vertices and edges of a graph on a 2D plane or in 3D space in a way +which visually pleases the human eye. + + They take a graph object and a number of parameters as arguments and +return an ‘igraph_matrix_t’, in which each row gives the coordinates of +a vertex. + +* Menu: + +* igraph_layout_random -- Places the vertices uniformly randomly within a square.: igraph_layout_random --- Places the vertices uniformly randomly within a square_. +* igraph_layout_circle -- Places the vertices uniformly on a circle in arbitrary order.: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_. +* igraph_layout_star -- Generates a star-like layout.: igraph_layout_star --- Generates a star-like layout_. +* igraph_layout_grid -- Places the vertices on a regular grid on the plane.: igraph_layout_grid --- Places the vertices on a regular grid on the plane_. +* igraph_layout_graphopt -- Optimizes vertex layout via the graphopt algorithm.: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_. +* igraph_layout_bipartite -- Simple layout for bipartite graphs.: igraph_layout_bipartite --- Simple layout for bipartite graphs_. +* The DrL layout generator:: +* igraph_layout_fruchterman_reingold -- Places the vertices on a plane according to the Fruchterman-Reingold algorithm.: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_. +* igraph_layout_kamada_kawai -- Places the vertices on a plane according to the Kamada-Kawai algorithm.: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_. +* igraph_layout_gem -- Layout graph according to GEM algorithm.: igraph_layout_gem --- Layout graph according to GEM algorithm_. +* igraph_layout_davidson_harel -- Davidson-Harel layout algorithm.: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_. +* igraph_layout_mds -- Place the vertices on a plane using multidimensional scaling.: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_. +* igraph_layout_lgl -- Force based layout algorithm for large graphs.: igraph_layout_lgl --- Force based layout algorithm for large graphs_. + + +File: igraph-docs.info, Node: igraph_layout_random --- Places the vertices uniformly randomly within a square_, Next: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_, Up: 2D layout generators + +29.1.1 igraph_layout_random -- Places the vertices uniformly randomly within a square. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res); + + Vertex coordinates range from -1 to 1, and are placed in two columns +of a matrix, with a row for each vertex. + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_, Next: igraph_layout_star --- Generates a star-like layout_, Prev: igraph_layout_random --- Places the vertices uniformly randomly within a square_, Up: 2D layout generators + +29.1.2 igraph_layout_circle -- Places the vertices uniformly on a circle in arbitrary order. +-------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t order); + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘order’: + The order of the vertices on the circle. The vertices not included + here, will be placed at (0,0). Supply ‘igraph_vss_all()’ (*note + igraph_vss_all --- All vertices of a graph [immediate version]_::) + here to place vertices in the order of their vertex IDs. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: igraph_layout_star --- Generates a star-like layout_, Next: igraph_layout_grid --- Places the vertices on a regular grid on the plane_, Prev: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_, Up: 2D layout generators + +29.1.3 igraph_layout_star -- Generates a star-like layout. +---------------------------------------------------------- + + + igraph_error_t igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t center, const igraph_vector_int_t *order); + + *Arguments:. * + +‘graph’: + The input graph. Its edges are ignored by this function. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘center’: + The id of the vertex to put in the center. You can set it to any + arbitrary value for the special case when the input graph has no + vertices; otherwise it must be between 0 and the number of vertices + minus 1. + +‘order’: + A numeric vector giving the order of the vertices (including the + center vertex!). If a null pointer, then the vertices are placed + in increasing vertex ID order. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|), linear in the number of vertices. + + *See also:. * + +‘’ + ‘igraph_layout_circle()’ (*note igraph_layout_circle --- Places the + vertices uniformly on a circle in arbitrary order_::) and other + layout generators. + + +File: igraph-docs.info, Node: igraph_layout_grid --- Places the vertices on a regular grid on the plane_, Next: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_, Prev: igraph_layout_star --- Generates a star-like layout_, Up: 2D layout generators + +29.1.4 igraph_layout_grid -- Places the vertices on a regular grid on the plane. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, igraph_int_t width); + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘width’: + The number of vertices in a single row of the grid. When zero or + negative, the width of the grid will be the square root of the + number of vertices, rounded up if needed. + + *Returns:. * + +‘’ + Error code. The current implementation always returns with + success. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_, Next: igraph_layout_bipartite --- Simple layout for bipartite graphs_, Prev: igraph_layout_grid --- Places the vertices on a regular grid on the plane_, Up: 2D layout generators + +29.1.5 igraph_layout_graphopt -- Optimizes vertex layout via the graphopt algorithm. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_layout_graphopt(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t niter, + igraph_real_t node_charge, igraph_real_t node_mass, + igraph_real_t spring_length, + igraph_real_t spring_constant, + igraph_real_t max_sa_movement, + igraph_bool_t use_seed); + + This is a port of the graphopt layout algorithm by Michael Schmuhl. +graphopt version 0.4.1 was rewritten in C, the support for layers was +removed and the code was reorganized to avoid some unnecessary steps +when the node charge (see below) is zero. + + Graphopt uses physical analogies for defining attracting and +repelling forces among the vertices and then the physical system is +simulated until it reaches an equilibrium. (There is no simulated +annealing or anything like that, so a stable fixed point is not +guaranteed.) + + See also +https://web.archive.org/web/20220611030748/http://www.schmuhl.org/graphopt/ +(https://web.archive.org/web/20220611030748/http://www.schmuhl.org/graphopt/) +and https://sourceforge.net/projects/graphopt/ +(https://sourceforge.net/projects/graphopt/) for the original graphopt. + + *Arguments:. * + +‘graph’: + The input graph. + +‘res’: + Pointer to an initialized matrix, the result will be stored here + and its initial contents are used as the starting point of the + simulation if the ‘use_seed’ argument is true. Note that in this + case the matrix should have the proper size, otherwise a warning is + issued and the supplied values are ignored. If no starting + positions are given (or they are invalid) then a random starting + position is used. The matrix will be resized if needed. + +‘niter’: + Integer constant, the number of iterations to perform. Should be a + couple of hundred in general. If you have a large graph then you + might want to only do a few iterations and then check the result. + If it is not good enough you can feed it in again in the ‘res’ + argument. The original graphopt default is 500. + +‘node_charge’: + The charge of the vertices, used to calculate electric repulsion. + The original graphopt default is 0.001. + +‘node_mass’: + The mass of the vertices, used for the spring forces. The original + graphopt defaults to 30. + +‘spring_length’: + The length of the springs. The original graphopt defaults to zero. + +‘spring_constant’: + The spring constant, the original graphopt defaults to one. + +‘max_sa_movement’: + Real constant, it gives the maximum amount of movement allowed in a + single step along a single axis. The original graphopt default is + 5. + +‘use_seed’: + Boolean, whether to use the positions in ‘res’ as a starting + configuration. See also ‘res’ above. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n (|V|^2+|E|) ), n is the number of iterations, +|V| is the number of vertices, |E| the number of edges. If +‘node_charge’ is zero then it is only O(n|E|). + + +File: igraph-docs.info, Node: igraph_layout_bipartite --- Simple layout for bipartite graphs_, Next: The DrL layout generator, Prev: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_, Up: 2D layout generators + +29.1.6 igraph_layout_bipartite -- Simple layout for bipartite graphs. +--------------------------------------------------------------------- + + + igraph_error_t igraph_layout_bipartite(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, igraph_real_t hgap, + igraph_real_t vgap, igraph_int_t maxiter); + + The layout is created by first placing the vertices in two rows, +according to their types. Then the positions within the rows are +optimized to minimize edge crossings, by calling +‘igraph_layout_sugiyama()’ (*note igraph_layout_sugiyama --- Sugiyama +layout algorithm for layered directed acyclic graphs_::). + + *Arguments:. * + +‘graph’: + The input graph. + +‘types’: + A boolean vector containing ones and zeros, the vertex types. Its + length must match the number of vertices in the graph. + +‘res’: + Pointer to an initialized matrix, the result, the x and y + coordinates are stored here. + +‘hgap’: + The preferred minimum horizontal gap between vertices in the same + layer (i.e. vertices of the same type). + +‘vgap’: + The distance between layers. + +‘maxiter’: + Maximum number of iterations in the crossing minimization stage. + 100 is a reasonable default; if you feel that you have too many + edge crossings, increase this. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_layout_sugiyama()’ (*note igraph_layout_sugiyama --- + Sugiyama layout algorithm for layered directed acyclic graphs_::). + + +File: igraph-docs.info, Node: The DrL layout generator, Next: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_, Prev: igraph_layout_bipartite --- Simple layout for bipartite graphs_, Up: 2D layout generators + +29.1.7 The DrL layout generator +------------------------------- + +DrL is a sophisticated layout generator developed and implemented by +Shawn Martin et al. As of October 2012 the original DrL homepage is +unfortunately not available. You can read more about this algorithm in +the following technical report: Martin, S., Brown, W.M., Klavans, R., +Boyack, K.W., DrL: Distributed Recursive (Graph) Layout. SAND Reports, +2008. 2936: p. 1-10. + + Only a subset of the complete DrL functionality is included in +igraph, parallel runs and recursive, multi-level layouting is not +supported. + + The parameters of the layout are stored in an +‘igraph_layout_drl_options_t’ (*note igraph_layout_drl_options_t --- +Parameters for the DrL layout generator::) structure, this can be +initialized by calling the function ‘igraph_layout_drl_options_init()’ +(*note igraph_layout_drl_options_init --- Initialize parameters for the +DrL layout generator::). The fields of this structure can then be +adjusted by hand if needed. The layout is calculated by an +‘igraph_layout_drl()’ (*note igraph_layout_drl --- The DrL layout +generator::) call. + +* Menu: + +* igraph_layout_drl_options_t --- Parameters for the DrL layout generator:: +* igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator:: +* igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator:: +* igraph_layout_drl --- The DrL layout generator:: +* igraph_layout_drl_3d -- The DrL layout generator, 3d version.: igraph_layout_drl_3d --- The DrL layout generator; 3d version_. + + +File: igraph-docs.info, Node: igraph_layout_drl_options_t --- Parameters for the DrL layout generator, Next: igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator, Up: The DrL layout generator + +29.1.7.1 igraph_layout_drl_options_t -- Parameters for the DrL layout generator +............................................................................... + + + typedef struct igraph_layout_drl_options_t { + igraph_real_t edge_cut; + igraph_int_t init_iterations; + igraph_real_t init_temperature; + igraph_real_t init_attraction; + igraph_real_t init_damping_mult; + igraph_int_t liquid_iterations; + igraph_real_t liquid_temperature; + igraph_real_t liquid_attraction; + igraph_real_t liquid_damping_mult; + igraph_int_t expansion_iterations; + igraph_real_t expansion_temperature; + igraph_real_t expansion_attraction; + igraph_real_t expansion_damping_mult; + igraph_int_t cooldown_iterations; + igraph_real_t cooldown_temperature; + igraph_real_t cooldown_attraction; + igraph_real_t cooldown_damping_mult; + igraph_int_t crunch_iterations; + igraph_real_t crunch_temperature; + igraph_real_t crunch_attraction; + igraph_real_t crunch_damping_mult; + igraph_int_t simmer_iterations; + igraph_real_t simmer_temperature; + igraph_real_t simmer_attraction; + igraph_real_t simmer_damping_mult; + } igraph_layout_drl_options_t; + + *Values:. * + +‘edge_cut’: + The edge cutting parameter. Edge cutting is done in the late + stages of the algorithm in order to achieve less dense layouts. + Edges are cut if there is a lot of stress on them (a large value in + the objective function sum). The edge cutting parameter is a value + between 0 and 1 with 0 representing no edge cutting and 1 + representing maximal edge cutting. The default value is 32/40. + +‘init_iterations’: + Number of iterations, initial phase. + +‘init_temperature’: + Start temperature, initial phase. + +‘init_attraction’: + Attraction, initial phase. + +‘init_damping_mult’: + Damping factor, initial phase. + +‘liquid_iterations’: + Number of iterations in the liquid phase. + +‘liquid_temperature’: + Start temperature in the liquid phase. + +‘liquid_attraction’: + Attraction in the liquid phase. + +‘liquid_damping_mult’: + Multiplicatie damping factor, liquid phase. + +‘expansion_iterations’: + Number of iterations in the expansion phase. + +‘expansion_temperature’: + Start temperature in the expansion phase. + +‘expansion_attraction’: + Attraction, expansion phase. + +‘expansion_damping_mult’: + Damping factor, expansion phase. + +‘cooldown_iterations’: + Number of iterations in the cooldown phase. + +‘cooldown_temperature’: + Start temperature in the cooldown phase. + +‘cooldown_attraction’: + Attraction in the cooldown phase. + +‘cooldown_damping_mult’: + Damping fact int the cooldown phase. + +‘crunch_iterations’: + Number of iterations in the crunch phase. + +‘crunch_temperature’: + Start temperature in the crunch phase. + +‘crunch_attraction’: + Attraction in the crunch phase. + +‘crunch_damping_mult’: + Damping factor in the crunch phase. + +‘simmer_iterations’: + Number of iterations in the simmer phase. + +‘simmer_temperature’: + Start temperature in te simmer phase. + +‘simmer_attraction’: + Attraction in the simmer phase. + +‘simmer_damping_mult’: + Multiplicative damping factor in the simmer phase. + + +File: igraph-docs.info, Node: igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator, Next: igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator, Prev: igraph_layout_drl_options_t --- Parameters for the DrL layout generator, Up: The DrL layout generator + +29.1.7.2 igraph_layout_drl_default_t -- Predefined parameter templates for the DrL layout generator +................................................................................................... + + + typedef enum { IGRAPH_LAYOUT_DRL_DEFAULT = 0, + IGRAPH_LAYOUT_DRL_COARSEN, + IGRAPH_LAYOUT_DRL_COARSEST, + IGRAPH_LAYOUT_DRL_REFINE, + IGRAPH_LAYOUT_DRL_FINAL + } igraph_layout_drl_default_t; + + These constants can be used to initialize a set of DrL parameters. +These can then be modified according to the user's needs. + + *Values:. * + +‘IGRAPH_LAYOUT_DRL_DEFAULT’: + The deafult parameters. + +‘IGRAPH_LAYOUT_DRL_COARSEN’: + Slightly modified parameters to get a coarser layout. + +‘IGRAPH_LAYOUT_DRL_COARSEST’: + An even coarser layout. + +‘IGRAPH_LAYOUT_DRL_REFINE’: + Refine an already calculated layout. + +‘IGRAPH_LAYOUT_DRL_FINAL’: + Finalize an already refined layout. + + +File: igraph-docs.info, Node: igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator, Next: igraph_layout_drl --- The DrL layout generator, Prev: igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator, Up: The DrL layout generator + +29.1.7.3 igraph_layout_drl_options_init -- Initialize parameters for the DrL layout generator +............................................................................................. + + + igraph_error_t igraph_layout_drl_options_init(igraph_layout_drl_options_t *options, + igraph_layout_drl_default_t templ); + + This function can be used to initialize the struct holding the +parameters for the DrL layout generator. There are a number of +predefined templates available, it is a good idea to start from one of +these by modifying some parameters. + + *Arguments:. * + +‘options’: + The struct to initialize. + +‘templ’: + The template to use. Currently the following templates are + supplied: ‘IGRAPH_LAYOUT_DRL_DEFAULT’, ‘IGRAPH_LAYOUT_DRL_COARSEN’, + ‘IGRAPH_LAYOUT_DRL_COARSEST’, ‘IGRAPH_LAYOUT_DRL_REFINE’ and + ‘IGRAPH_LAYOUT_DRL_FINAL’. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_layout_drl --- The DrL layout generator, Next: igraph_layout_drl_3d --- The DrL layout generator; 3d version_, Prev: igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator, Up: The DrL layout generator + +29.1.7.4 igraph_layout_drl -- The DrL layout generator +...................................................... + + + igraph_error_t igraph_layout_drl(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + + This function implements the force-directed DrL layout generator. +Please see more in the following technical report: Martin, S., Brown, +W.M., Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) +Layout. SAND Reports, 2008. 2936: p. 1-10. + + *Arguments:. * + +‘graph’: + The input graph. + +‘use_seed’: + Boolean, if true, then the coordinates supplied in the ‘res’ + argument are used as starting points. + +‘res’: + Pointer to a matrix, the result layout is stored here. It will be + resized as needed. + +‘options’: + The parameters to pass to the layout generator. + +‘weights’: + Edge weights, pointer to a vector. If this is a null pointer then + every edge will have the same weight. + + *Returns:. * + +‘’ + Error code. + + Time complexity: ???. + + +File: igraph-docs.info, Node: igraph_layout_drl_3d --- The DrL layout generator; 3d version_, Prev: igraph_layout_drl --- The DrL layout generator, Up: The DrL layout generator + +29.1.7.5 igraph_layout_drl_3d -- The DrL layout generator, 3d version. +...................................................................... + + + igraph_error_t igraph_layout_drl_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + + This function implements the force-directed DrL layout generator. +Please see more in the technical report: Martin, S., Brown, W.M., +Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) Layout. +SAND Reports, 2008. 2936: p. 1-10. + + This function uses a modified DrL generator that does the layout in +three dimensions. + + *Arguments:. * + +‘graph’: + The input graph. + +‘use_seed’: + Boolean, if true, then the coordinates supplied in the ‘res’ + argument are used as starting points. + +‘res’: + Pointer to a matrix, the result layout is stored here. It will be + resized as needed. + +‘options’: + The parameters to pass to the layout generator. + +‘weights’: + Edge weights, pointer to a vector. If this is a null pointer then + every edge will have the same weight. + + *Returns:. * + +‘’ + Error code. + + Time complexity: ???. + + *See also:. * + +‘’ + ‘igraph_layout_drl()’ (*note igraph_layout_drl --- The DrL layout + generator::) for the standard 2d version. + + +File: igraph-docs.info, Node: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_, Next: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_, Prev: The DrL layout generator, Up: 2D layout generators + +29.1.8 igraph_layout_fruchterman_reingold -- Places the vertices on a plane according to the Fruchterman-Reingold algorithm. +---------------------------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_fruchterman_reingold(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + igraph_layout_grid_t grid, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy); + + This is a force-directed layout that simulates an attractive force +‘f_a’ between connected vertex pairs and a repulsive force ‘f_r’ between +all vertex pairs. The forces are computed as a function of the distance +‘d’ between the two vertices as + + ‘f_a(d) = -w * d^2’ and ‘f_r(d) = 1/d’, + + where ‘w’ represents the edge weight. The equilibrium distance of +two connected vertices is thus ‘1/w^3’, assuming no other forces acting +on them. + + In disconnected graphs, igraph effectively inserts a weak connection +of weight ‘n^(-3/2)’ between all pairs of vertices, where ‘n’ is the +vertex count. This ensures that components are kept near each other. + + Reference: + + Fruchterman, T.M.J. and Reingold, E.M.: Graph Drawing by +Force-directed Placement. Software - Practice and Experience, 21/11, +1129-1164, 1991. https://doi.org/10.1002/spe.4380211102 +(https://doi.org/10.1002/spe.4380211102) + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘use_seed’: + If true the supplied values in the ‘res’ argument are used as an + initial layout, if false a random initial layout is used. + +‘niter’: + The number of iterations to do. A reasonable default value is 500. + +‘start_temp’: + Start temperature. This is the maximum amount of movement allowed + along one axis, within one step, for a vertex. Currently it is + decreased linearly to zero during the iteration. + +‘grid’: + Whether to use the (fast but less accurate) grid based version of + the algorithm. Possible values: ‘IGRAPH_LAYOUT_GRID’, + ‘IGRAPH_LAYOUT_NOGRID’, ‘IGRAPH_LAYOUT_AUTOGRID’. The last one + uses the grid based version only for large graphs, currently the + ones with more than 1000 vertices. + +‘weights’: + Pointer to a vector containing edge weights. Weights must be + positive. If ‘NULL’, all edges are assumed to have weight 1. The + attraction along the edges will be multiplied by the weights, + resulting in vertices connected by a high-weight edge being placed + closer together. + +‘minx’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'x' coordinate for every vertex. + +‘maxx’: + Same as ‘minx’, but the maximum 'x' coordinates. + +‘miny’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'y' coordinate for every vertex. + +‘maxy’: + Same as ‘miny’, but the maximum 'y' coordinates. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|^2) in each iteration, |V| is the number of +vertices in the graph. + + +File: igraph-docs.info, Node: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_, Next: igraph_layout_gem --- Layout graph according to GEM algorithm_, Prev: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_, Up: 2D layout generators + +29.1.9 igraph_layout_kamada_kawai -- Places the vertices on a plane according to the Kamada-Kawai algorithm. +------------------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_layout_kamada_kawai(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy); + + This is a force-directed layout. A spring is inserted between all +pairs of vertices, both those which are directly connected and those +that are not. The unstretched length of springs is chosen based on the +undirected graph distance between the corresponding pair of vertices. +Thus, in a weighted graph, increasing the weight between two vertices +pushes them apart. The Young modulus of springs is inversely +proportional to the graph distance, ensuring that springs between +far-apart veritces will have a smaller effect on the layout. + + Disconnected graphs are handled by assuming that the graph distance +between vertices in different components is the same as the graph +diameter. + + This layout works particularly well for locally connected spatial +networks such as lattices. + + This layout algorithm is not suitable for large graphs. The memory +requirements are of the order O(|V|^2). + + Reference: + + Kamada, T. and Kawai, S.: An Algorithm for Drawing General Undirected +Graphs. Information Processing Letters, 31/1, 7-15, 1989. +https://doi.org/10.1016/0020-0190(89)90102-6 +(https://doi.org/10.1016/0020-0190(89)90102-6) + + *Arguments:. * + +‘graph’: + A graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result (x-positions in column zero and y-positions in column one) + and will be resized if needed. + +‘use_seed’: + Boolean, whether to use the values supplied in the ‘res’ argument + as the initial configuration. If zero and there are any limits on + the X or Y coordinates, then a random initial configuration is + used. Otherwise the vertices are placed on a circle of radius 1 as + the initial configuration. + +‘maxiter’: + The maximum number of iterations to perform. A reasonable default + value is at least ten (or more) times the number of vertices. + +‘epsilon’: + Stop the iteration, if the maximum delta value of the algorithm is + smaller than this. It is safe to leave it at zero, and then + ‘maxiter’ iterations are performed. + +‘kkconst’: + The Kamada-Kawai vertex attraction constant. Typical value: number + of vertices. + +‘weights’: + A vector of edge weights. Weights are interpreted as edge + _lengths_ in the shortest path calculation used by the Kamada-Kawai + algorithm. Therefore, vertices connected by high-weight edges will + be placed further apart. Pass ‘NULL’ to assume unit weights for + all edges. + +‘minx’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'x' coordinate for every vertex. + +‘maxx’: + Same as ‘minx’, but the maximum 'x' coordinates. + +‘miny’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'y' coordinate for every vertex. + +‘maxy’: + Same as ‘miny’, but the maximum 'y' coordinates. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|) for each iteration, after an O(|V|^2 log|V|) +initialization step. |V| is the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_layout_gem --- Layout graph according to GEM algorithm_, Next: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_, Prev: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_, Up: 2D layout generators + +29.1.10 igraph_layout_gem -- Layout graph according to GEM algorithm. +--------------------------------------------------------------------- + + + igraph_error_t igraph_layout_gem(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t temp_max, igraph_real_t temp_min, + igraph_real_t temp_init); + + The GEM layout algorithm, as described in Arne Frick, Andreas Ludwig, +Heiko Mehldau: A Fast Adaptive Layout Algorithm for Undirected Graphs, +Proc. Graph Drawing 1994, LNCS 894, pp. 388-403, 1995. + + *Arguments:. * + +‘graph’: + The input graph. Edge directions are ignored in directed graphs. + +‘res’: + The result is stored here. If the ‘use_seed’ argument is true, + then this matrix is also used as the starting point of the + algorithm. + +‘use_seed’: + Boolean, whether to use the supplied coordinates in ‘res’ as the + starting point. If false (zero), then a uniform random starting + point is used. + +‘maxiter’: + The maximum number of iterations to perform. Updating a single + vertex counts as an iteration. A reasonable default is 40 * n * n, + where n is the number of vertices. The original paper suggests 4 * + n * n, but this usually only works if the other parameters are set + up carefully. + +‘temp_max’: + The maximum allowed local temperature. A reasonable default is the + number of vertices. + +‘temp_min’: + The global temperature at which the algorithm terminates (even + before reaching ‘maxiter’ iterations). A reasonable default is + 1/10. + +‘temp_init’: + Initial local temperature of all vertices. A reasonable default is + the square root of the number of vertices. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(t * n * (n+e)), where n is the number of vertices, +e is the number of edges and t is the number of time steps performed. + + +File: igraph-docs.info, Node: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_, Next: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_, Prev: igraph_layout_gem --- Layout graph according to GEM algorithm_, Up: 2D layout generators + +29.1.11 igraph_layout_davidson_harel -- Davidson-Harel layout algorithm. +------------------------------------------------------------------------ + + + igraph_error_t igraph_layout_davidson_harel(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_int_t fineiter, igraph_real_t cool_fact, + igraph_real_t weight_node_dist, igraph_real_t weight_border, + igraph_real_t weight_edge_lengths, + igraph_real_t weight_edge_crossings, + igraph_real_t weight_node_edge_dist); + + This function implements the algorithm by Davidson and Harel, see Ron +Davidson, David Harel: Drawing Graphs Nicely Using Simulated Annealing. +ACM Transactions on Graphics 15(4), pp. 301-331, 1996. +https://doi.org/10.1145/234535.234538 +(https://doi.org/10.1145/234535.234538) + + The algorithm uses simulated annealing and a sophisticated energy +function, which is unfortunately hard to parameterize for different +graphs. The original publication did not disclose any parameter values, +and the ones below were determined by experimentation. + + The algorithm consists of two phases, an annealing phase, and a +fine-tuning phase. There is no simulated annealing in the second phase. + + Our implementation tries to follow the original publication, as much +as possible. The only major difference is that coordinates are +explicitly kept within the bounds of the rectangle of the layout. + + *Arguments:. * + +‘graph’: + The input graph, edge directions are ignored. + +‘res’: + A matrix, the result is stored here. It can be used to supply + start coordinates, see ‘use_seed’. + +‘use_seed’: + Boolean, whether to use the supplied ‘res’ as start coordinates. + +‘maxiter’: + The maximum number of annealing iterations. A reasonable value for + smaller graphs is 10. + +‘fineiter’: + The number of fine tuning iterations. A reasonable value is + ‘max(10, log2(n))’ where ‘n’ is the number of vertices. + +‘cool_fact’: + Cooling factor. A reasonable value is 0.75. + +‘weight_node_dist’: + Weight for the node-node distances component of the energy + function. Reasonable value: 1.0. + +‘weight_border’: + Weight for the distance from the border component of the energy + function. It can be set to zero, if vertices are allowed to sit on + the border. + +‘weight_edge_lengths’: + Weight for the edge length component of the energy function, a + reasonable value is the density of the graph divided by 10. + +‘weight_edge_crossings’: + Weight for the edge crossing component of the energy function, a + reasonable default is 1 minus the square root of the density of the + graph. + +‘weight_node_edge_dist’: + Weight for the node-edge distance component of the energy function. + A reasonable value is 1 minus the density, divided by 5. + + *Returns:. * + +‘’ + Error code. + + Time complexity: one first phase iteration has time complexity +O(n^2+m^2), one fine tuning iteration has time complexity O(mn). Time +complexity might be smaller if some of the weights of the components of +the energy function are set to zero. + + +File: igraph-docs.info, Node: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_, Next: igraph_layout_lgl --- Force based layout algorithm for large graphs_, Prev: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_, Up: 2D layout generators + +29.1.12 igraph_layout_mds -- Place the vertices on a plane using multidimensional scaling. +------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_layout_mds(const igraph_t *graph, igraph_matrix_t *res, + const igraph_matrix_t *dist, igraph_int_t dim); + + This layout requires a distance matrix, where the intersection of row +i and column j specifies the desired distance between vertex i and +vertex j. The algorithm will try to place the vertices in a space +having a given number of dimensions in a way that approximates the +distance relations prescribed in the distance matrix. igraph uses the +classical multidimensional scaling by Torgerson; for more details, see +Cox & Cox: Multidimensional Scaling (1994), Chapman and Hall, London. + + If the input graph is disconnected, igraph will decompose it first +into its subgraphs, lay out the subgraphs one by one using the +appropriate submatrices of the distance matrix, and then merge the +layouts using ‘igraph_layout_merge_dla()’ (*note igraph_layout_merge_dla +--- Merges multiple layouts by using a DLA algorithm_::). Since +‘igraph_layout_merge_dla()’ (*note igraph_layout_merge_dla --- Merges +multiple layouts by using a DLA algorithm_::) works for 2D layouts only, +you cannot run the MDS layout on disconnected graphs for more than two +dimensions. + + Warning: if the graph is symmetric to the exchange of two vertices +(as is the case with leaves of a tree connecting to the same parent), +classical multidimensional scaling may assign the same coordinates to +these vertices. + + *Arguments:. * + +‘graph’: + A graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized if needed. + +‘dist’: + The distance matrix. It must be symmetric and this function does + not check whether the matrix is indeed symmetric. Results are + unspecified if you pass a non-symmetric matrix here. You can set + this parameter to null; in this case, the undirected shortest path + lengths between vertices will be used as distances. + +‘dim’: + The number of dimensions in the embedding space. For 2D layouts, + supply 2 here. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.6. + + Time complexity: usually around O(|V|^2 dim). + + +File: igraph-docs.info, Node: igraph_layout_lgl --- Force based layout algorithm for large graphs_, Prev: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_, Up: 2D layout generators + +29.1.13 igraph_layout_lgl -- Force based layout algorithm for large graphs. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t maxit, igraph_real_t maxdelta, + igraph_real_t area, igraph_real_t coolexp, + igraph_real_t repulserad, igraph_real_t cellsize, + igraph_int_t proot); + + This is a layout generator similar to the Large Graph Layout +algorithm and program (http://lgl.sourceforge.net/ +(http://lgl.sourceforge.net/)). But unlike LGL, this version uses a +Fruchterman-Reingold style simulated annealing algorithm for placing the +vertices. The speedup is achieved by placing the vertices on a grid and +calculating the repulsion only for vertices which are closer to each +other than a limit. + + *Arguments:. * + +‘graph’: + The (initialized) graph object to place. It must be connnected; + disconnected graphs are not handled by the algorithm. + +‘res’: + Pointer to an initialized matrix object to hold the result. It + will be resized if needed. + +‘maxit’: + The maximum number of cooling iterations to perform for each layout + step. A reasonable default is 150. + +‘maxdelta’: + The maximum length of the move allowed for a vertex in a single + iteration. A reasonable default is the number of vertices. + +‘area’: + This parameter gives the area of the square on which the vertices + will be placed. A reasonable default value is the number of + vertices squared. + +‘coolexp’: + The cooling exponent. A reasonable default value is 1.5. + +‘repulserad’: + Determines the radius at which vertex-vertex repulsion cancels out + attraction of adjacent vertices. A reasonable default value is + ‘area’ times the number of vertices. + +‘cellsize’: + The size of the grid cells, one side of the square. A reasonable + default value is the fourth root of ‘area’ (or the square root of + the number of vertices if ‘area’ is also left at its default + value). + +‘proot’: + The root vertex, this is placed first, its neighbors in the first + iteration, second neighbors in the second, etc. If negative then a + random vertex is chosen. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: ideally O(dia*maxit*(|V|+|E|)), |V| is the number of +vertices, dia is the diameter of the graph, worst case complexity is +still O(dia*maxit*(|V|^2+|E|)), this is the case when all vertices +happen to be in the same grid cell. + + +File: igraph-docs.info, Node: Layouts for trees and acyclic graphs, Next: 3D layout generators, Prev: 2D layout generators, Up: Generating layouts for graph drawing + +29.2 Layouts for trees and acyclic graphs +========================================= + +* Menu: + +* igraph_layout_reingold_tilford -- Reingold-Tilford layout for tree graphs.: igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree graphs_. +* igraph_layout_reingold_tilford_circular -- Circular Reingold-Tilford layout for trees.: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_. +* igraph_roots_for_tree_layout -- Roots suitable for a nice tree layout.: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_. +* igraph_layout_sugiyama -- Sugiyama layout algorithm for layered directed acyclic graphs.: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_. +* igraph_layout_umap -- Layout using Uniform Manifold Approximation and Projection (UMAP).: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_. +* igraph_layout_umap_compute_weights -- Compute weights for a UMAP layout starting from distances.: igraph_layout_umap_compute_weights --- Compute weights for a UMAP layout starting from distances_. + + +File: igraph-docs.info, Node: igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree graphs_, Next: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_, Up: Layouts for trees and acyclic graphs + +29.2.1 igraph_layout_reingold_tilford -- Reingold-Tilford layout for tree graphs. +--------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_reingold_tilford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel); + + Arranges the nodes in a tree where the given node is used as the +root. The tree is directed downwards and the parents are centered above +its children. For the exact algorithm, see: + + Reingold, E and Tilford, J: Tidier drawing of trees. IEEE Trans. +Softw. Eng., SE-7(2):223-228, 1981. +https://doi.org/10.1109/TSE.1981.234519 +(https://doi.org/10.1109/TSE.1981.234519) + + If the given graph is not a tree, a breadth-first search is executed +first to obtain a possible spanning tree. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result, the coordinates in a matrix. The parameter should + point to an initialized matrix object and will be resized. + +‘mode’: + Specifies which edges to consider when building the tree. If it is + ‘IGRAPH_OUT’ then only the outgoing, if it is ‘IGRAPH_IN’ then only + the incoming edges of a parent are considered. If it is + ‘IGRAPH_ALL’ then all edges are used (this was the behavior in + igraph 0.5 and before). This parameter also influences how the + root vertices are calculated, if they are not given. See the + ‘roots’ parameter. + +‘roots’: + The index of the root vertex or root vertices. The set of roots + should be specified so that all vertices of the graph are reachable + from them. Simply put, in the undirected case, one root should be + given from each connected component. If ‘roots’ is ‘NULL’ or a + pointer to an empty vector, then the roots will be selected + automatically. Currently, automatic root selection prefers low + eccentricity vertices in graphs with fewer than 500 vertices, and + high degree vertices (according to ‘mode’) in larger graphs. The + root selection heuristic may change without notice. To ensure a + consistent output, please specify the roots manually. The + ‘igraph_roots_for_tree_layout()’ (*note + igraph_roots_for_tree_layout --- Roots suitable for a nice tree + layout_::) function gives more control over automatic root + selection. + +‘rootlevel’: + This argument can be useful when drawing forests which are not + trees (i.e. they are unconnected and have tree components). It + specifies the level of the root vertices for every tree in the + forest. It is only considered if not a null pointer and the + ‘roots’ argument is also given (and it is not a null pointer of an + empty vector). + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + *See also:. * + +‘’ + ‘igraph_layout_reingold_tilford_circular()’ (*note + igraph_layout_reingold_tilford_circular --- Circular + Reingold-Tilford layout for trees_::), + ‘igraph_roots_for_tree_layout()’ (*note + igraph_roots_for_tree_layout --- Roots suitable for a nice tree + layout_::) + + * File examples/simple/igraph_layout_reingold_tilford.c* + + +File: igraph-docs.info, Node: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_, Next: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_, Prev: igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree graphs_, Up: Layouts for trees and acyclic graphs + +29.2.2 igraph_layout_reingold_tilford_circular -- Circular Reingold-Tilford layout for trees. +--------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_reingold_tilford_circular(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel); + + This layout is almost the same as ‘igraph_layout_reingold_tilford()’ +(*note igraph_layout_reingold_tilford --- Reingold-Tilford layout for +tree graphs_::), but the tree is drawn in a circular way, with the root +vertex in the center. + + *Arguments:. * + +‘graph’: + The graph object. + +‘res’: + The result, the coordinates in a matrix. The parameter should + point to an initialized matrix object and will be resized. + +‘mode’: + Specifies which edges to consider when building the tree. If it is + ‘IGRAPH_OUT’ then only the outgoing, if it is ‘IGRAPH_IN’ then only + the incoming edges of a parent are considered. If it is + ‘IGRAPH_ALL’ then all edges are used (this was the behavior in + igraph 0.5 and before). This parameter also influences how the + root vertices are calculated, if they are not given. See the + ‘roots’ parameter. + +‘roots’: + The index of the root vertex or root vertices. The set of roots + should be specified so that all vertices of the graph are reachable + from them. Simply put, in the undirected case, one root should be + given from each connected component. If ‘roots’ is ‘NULL’ or a + pointer to an empty vector, then the roots will be selected + automatically. Currently, automatic root selection prefers low + eccentricity vertices in graphs with fewer than 500 vertices, and + high degree vertices (according to ‘mode’) in larger graphs. The + root selection heuristic may change without notice. To ensure a + consistent output, please specify the roots manually. + +‘rootlevel’: + This argument can be useful when drawing forests which are not + trees (i.e. they are unconnected and have tree components). It + specifies the level of the root vertices for every tree in the + forest. It is only considered if not a null pointer and the + ‘roots’ argument is also given (and it is not a null pointer or an + empty vector). + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_layout_reingold_tilford()’ (*note + igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree + graphs_::). + + +File: igraph-docs.info, Node: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_, Next: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_, Prev: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_, Up: Layouts for trees and acyclic graphs + +29.2.3 igraph_roots_for_tree_layout -- Roots suitable for a nice tree layout. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_roots_for_tree_layout( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_vector_int_t *roots, + igraph_root_choice_t heuristic); + + This function chooses a root, or a set of roots suitable for +visualizing a tree, or a tree-like graph. It is typically used with +‘igraph_layout_reingold_tilford()’ (*note igraph_layout_reingold_tilford +--- Reingold-Tilford layout for tree graphs_::). The principle is to +select a minimal set of roots so that all other vertices will be +reachable from them. + + In the undirected case, one root is chosen from each connected +component. In the directed case, one root is chosen from each strongly +connected component that has no incoming (or outgoing) edges (depending +on 'mode'). When more than one root choice is possible, vertices are +prioritized based on the given ‘heuristic’. + + *Arguments:. * + +‘graph’: + The graph, typically a tree, but any graph is accepted. + +‘mode’: + Whether to interpret the input as undirected, a directed out-tree + or in-tree. + +‘roots’: + An initialized integer vector, the roots will be returned here. + +‘heuristic’: + The heuristic to use for breaking ties when multiple root choices + are possible. + + ‘IGRAPH_ROOT_CHOICE_DEGREE’ + Choose the vertices with the highest degree (out- or in-degree + in directed mode). This simple heuristic is fast even in + large graphs. + + ‘IGRAPH_ROOT_CHOICE_ECCENTRICITY’ + Choose the vertices with the lowest eccentricity. This + usually results in a "wide and shallow" tree layout. While + this heuristic produces high-quality results, it is slow for + large graphs: computing the eccentricities has quadratic + complexity in the number of vertices. + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on the heuristic. + + +File: igraph-docs.info, Node: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_, Next: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_, Prev: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_, Up: Layouts for trees and acyclic graphs + +29.2.4 igraph_layout_sugiyama -- Sugiyama layout algorithm for layered directed acyclic graphs. +----------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_sugiyama( + const igraph_t *graph, igraph_matrix_t *res, igraph_matrix_list_t *routing, + const igraph_vector_int_t* layers, igraph_real_t hgap, igraph_real_t vgap, + igraph_int_t maxiter, const igraph_vector_t *weights + ); + + This layout algorithm is designed for directed acyclic graphs where +each vertex is assigned to a layer. Layers are indexed from zero, and +vertices of the same layer will be placed on the same horizontal line. +The X coordinates of vertices within each layer are decided by the +heuristic proposed by Sugiyama et al to minimize edge crossings. + + You can also try to lay out undirected graphs, graphs containing +cycles, or graphs without an a priori layered assignment with this +algorithm. igraph will try to eliminate cycles and assign vertices to +layers, but there is no guarantee on the quality of the layout in such +cases. + + The Sugiyama layout may introduce "bends" on the edges in order to +obtain a visually more pleasing layout. The additional control points +of the edges are returned in a separate list of matrices, one matrix per +edge in the original graph. If an edge requires no additional control +points, the corresponding matrix will be empty, otherwise the matrix +will contain the coordinates of the control points, one point per row. +When drawing the graph, edges should be drawn in a way that the curve +representing the edge passes through the control points. + + For more details, see K. Sugiyama, S. Tagawa and M. Toda, "Methods +for Visual Understanding of Hierarchical Systems". IEEE Transactions on +Systems, Man and Cybernetics 11(2):109-125, 1981. + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. The coordinates of the + vertices in the layout will be stored in the rows of the matrix, + one row per vertex. + +‘routing’: + Pointer to an uninitialized list of matrices or ‘NULL’. When not + ‘NULL’, the list will be resized as needed such that there will be + one matrix for each edge of the graph, and the matrix will hold the + additional control points that the edge must pass through, starting + from the source vertex of the edge and ending at the target vertex. + The matrix will have zero rows if an edge does not require control + points. + +‘layers’: + The layer index for each vertex or ‘NULL’ if the layers should be + determined automatically by igraph. + +‘hgap’: + The preferred minimum horizontal gap between vertices in the same + layer. + +‘vgap’: + The distance between layers. + +‘maxiter’: + Maximum number of iterations in the crossing minimization stage. + 100 is a reasonable default; if you feel that you have too many + edge crossings, increase this. + +‘weights’: + Weights of the edges. These are used only if the graph contains + cycles; igraph will tend to reverse edges with smaller weights when + breaking the cycles. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_, Next: igraph_layout_umap_compute_weights --- Compute weights for a UMAP layout starting from distances_, Prev: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_, Up: Layouts for trees and acyclic graphs + +29.2.5 igraph_layout_umap -- Layout using Uniform Manifold Approximation and Projection (UMAP). +----------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_umap(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + UMAP is mostly used to embed high-dimensional vectors in a +low-dimensional space (most commonly 2D). The algorithm is probabilistic +and introduces nonlinearities, unlike e.g. PCA and similar to +T-distributed Stochastic Neighbor Embedding (t-SNE). Nonlinearity helps +"cluster" very similar vectors together without imposing a global +geometry on the embedded space (e.g. a rigid rotation + compression in +PCA). UMAP uses graphs as intermediate data structures, hence it can be +used as a graph layout algorithm as well. + + The general UMAP workflow is to start from vectors, compute a sparse +distance graph that only contains edges between simiar points (e.g. a +k-nearest neighbors graph), and then convert these distances into +exponentially decaying weights between 0 and 1 that are larger for +points that are closest neighbors in the distance graph. If a graph +without any distances associated to the edges is used, all weights will +be set to 1. + + If you are trying to use this function to embed high-dimensional +vectors, you should first compute a k-nearest neighbors graph between +your vectors and compute the associated distances, and then call this +function on that graph. If you already have a distance graph, or you +have a graph with no distances, you can call this function directly. If +you already have a graph with meaningful weights associated to each +edge, you can also call this function, but set the argument +‘distances_are_weights’ to true. To compute weights from distances +without computing the layout, see ‘igraph_layout_umap_compute_weights()’ +(*note igraph_layout_umap_compute_weights --- Compute weights for a UMAP +layout starting from distances_::). + + References: + + Leland McInnes, John Healy, and James Melville: UMAP: Uniform +Manifold Approximation and Projection for Dimension Reduction (2020) +https://arxiv.org/abs/1802.03426 (https://arxiv.org/abs/1802.03426) + + *Arguments:. * + +‘graph’: + Pointer to the graph to find a layout for (i.e. to embed). This + is typically a sparse graph with only edges for the shortest + distances stored, e.g. a k-nearest neighbors graph. + +‘res’: + Pointer to the n by 2 matrix where the layout coordinates will be + stored. + +‘use_seed’: + If ‘true’ the supplied values in the ‘res’ argument are used as an + initial layout, if ‘false’ a random initial layout is used. + +‘distances’: + Pointer to a vector of distances associated with the graph edges. + If this argument is ‘NULL’, all weights will be set to 1. + +‘min_dist’: + A fudge parameter that decides how close two unconnected vertices + can be in the embedding before feeling a repulsive force. It must + not be negative. Typical values are between 0 and 1. + +‘epochs’: + Number of iterations of the main stochastic gradient descent loop + on the cross-entropy. Typical values are between 30 and 500. + +‘distances_are_weights’: + Whether to use precomputed weights. If true, the ‘distances’ + vector contains precomputed weights. If ‘false’ (the typical use + case), this function will compute weights from distances and then + use them to compute the layout. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_layout_umap_3d()’ (*note igraph_layout_umap_3d --- 3D + layout using UMAP_::) + + +File: igraph-docs.info, Node: igraph_layout_umap_compute_weights --- Compute weights for a UMAP layout starting from distances_, Prev: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_, Up: Layouts for trees and acyclic graphs + +29.2.6 igraph_layout_umap_compute_weights -- Compute weights for a UMAP layout starting from distances. +------------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_umap_compute_weights( + const igraph_t *graph, + const igraph_vector_t *distances, + igraph_vector_t *weights); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + UMAP is used to embed high-dimensional vectors in a low-dimensional +space (most commonly 2D). It uses a distance graph as an intermediate +data structure, making it also a useful graph layout algorithm. See +‘igraph_layout_umap()’ (*note igraph_layout_umap --- Layout using +Uniform Manifold Approximation and Projection [UMAP]_::) for more +information. + + An early step in UMAP is to compute exponentially decaying "weights" +from the distance graph. Connectivities can also be viewed as edge +weights that quantify similarity between two vertices. This function +computes weights from the distance graph. To compute the layout from +precomputed weights, call ‘igraph_layout_umap()’ (*note +igraph_layout_umap --- Layout using Uniform Manifold Approximation and +Projection [UMAP]_::) with the ‘distances_are_weights’ argument set to +‘true’. + + While the distance graph can be directed (e.g. in a k-nearest +neighbors, it is clear _whom_ you are a neighbor of), the weights are +usually undirected. Whenever two vertices are doubly connected in the +distance graph, the resulting weight ‘W’ is set as: + + ‘W = W1 + W2 - W1 * W2’ Because UMAP weights are interpreted as +probabilities, this is just the probability that either edge is present, +without double counting. It is called "fuzzy union" in the original +UMAP implementation and is the default. One could also require that +both edges are there, i.e. W = W1 * W2: this would represent the fuzzy +intersection and is not implemented in igraph. As a consequence of this +symmetrization, information is lost, i.e. one needs fewer weights than +one had distances. To keep things efficient, here we set the weight for +one of the two edges as above and the weight for its opposite edge as 0, +so that it will be skipped in the UMAP gradient descent later on. + + Technical note: For each vertex, this function computes its scale +factor (sigma), its connectivity correction (rho), and finally the +weights themselves. + + References: + + Leland McInnes, John Healy, and James Melville: UMAP: Uniform +Manifold Approximation and Projection for Dimension Reduction (2020) +https://arxiv.org/abs/1802.03426 (https://arxiv.org/abs/1802.03426) + + *Arguments:. * + +‘graph’: + Pointer to the distance graph. This can be directed (e.g. + connecting each vertex to its neighbors in a k-nearest neighbor) or + undirected, but must have no loops nor parallel edges. The only + exception is: if the graph is directed, having pairs of edges with + opposite direction is accepted. + +‘distances’: + Pointer to the vector with the vertex-to-vertex distance associated + with each edge. This argument can be NULL, in which case all edges + are assumed to have the same distance. + +‘weights’: + Pointer to an initialized vector where the result will be stored. + If the input graph is directed, the weights represent a symmetrized + version which contains less information. Therefore, whenever two + edges between the same vertices and opposite direction are present + in the input graph, only one of the weights is set and the other is + fixed to zero. That format is accepted by ‘igraph_layout_umap()’ + (*note igraph_layout_umap --- Layout using Uniform Manifold + Approximation and Projection [UMAP]_::), which skips all + zero-weight edges from the layout optimization. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_layout_umap()’ (*note igraph_layout_umap --- Layout using + Uniform Manifold Approximation and Projection [UMAP]_::), + ‘igraph_layout_umap_3d()’ (*note igraph_layout_umap_3d --- 3D + layout using UMAP_::) + + +File: igraph-docs.info, Node: 3D layout generators, Next: Post-processing layouts, Prev: Layouts for trees and acyclic graphs, Up: Generating layouts for graph drawing + +29.3 3D layout generators +========================= + +* Menu: + +* igraph_layout_random_3d -- Places the vertices uniformly randomly in a cube.: igraph_layout_random_3d --- Places the vertices uniformly randomly in a cube_. +* igraph_layout_sphere -- Places vertices (more or less) uniformly on a sphere.: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_. +* igraph_layout_grid_3d -- Places the vertices on a regular grid in the 3D space.: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_. +* igraph_layout_fruchterman_reingold_3d -- 3D Fruchterman-Reingold algorithm.: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_. +* igraph_layout_kamada_kawai_3d -- 3D version of the Kamada-Kawai layout generator.: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_. +* igraph_layout_umap_3d -- 3D layout using UMAP.: igraph_layout_umap_3d --- 3D layout using UMAP_. + + +File: igraph-docs.info, Node: igraph_layout_random_3d --- Places the vertices uniformly randomly in a cube_, Next: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_, Up: 3D layout generators + +29.3.1 igraph_layout_random_3d -- Places the vertices uniformly randomly in a cube. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res); + + Vertex coordinates range from -1 to 1, and are placed in three +columns of a matrix, with a row for each vertex. + + *Arguments:. * + +‘graph’: + The graph to place. + +‘res’: + Pointer to an initialized matrix object. It will be resized to + hold the result. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_, Next: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_, Prev: igraph_layout_random_3d --- Places the vertices uniformly randomly in a cube_, Up: 3D layout generators + +29.3.2 igraph_layout_sphere -- Places vertices (more or less) uniformly on a sphere. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res); + + The vertices are placed with approximately equal spacing on a spiral +wrapped around a sphere, in the order of their vertex IDs. Vertices +with consecutive vertex IDs are placed near each other. + + The algorithm was described in the following paper: + + Distributing many points on a sphere by E.B. Saff and A.B.J. +Kuijlaars, _ Mathematical Intelligencer _ 19.1 (1997) 5-11. +https://doi.org/10.1007/BF03024331 (https://doi.org/10.1007/BF03024331) + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + + *Returns:. * + +‘’ + Error code. The current implementation always returns with + success. + + Added in version 0.2. + + Time complexity: O(|V|), the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_, Next: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_, Prev: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_, Up: 3D layout generators + +29.3.3 igraph_layout_grid_3d -- Places the vertices on a regular grid in the 3D space. +-------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t width, igraph_int_t height); + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘width’: + The number of vertices in a single row of the grid. When zero or + negative, the width is determined automatically. + +‘height’: + The number of vertices in a single column of the grid. When zero + or negative, the height is determined automatically. + + *Returns:. * + +‘’ + Error code. The current implementation always returns with + success. + + Time complexity: O(|V|), the number of vertices. + + +File: igraph-docs.info, Node: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_, Next: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_, Prev: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_, Up: 3D layout generators + +29.3.4 igraph_layout_fruchterman_reingold_3d -- 3D Fruchterman-Reingold algorithm. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_fruchterman_reingold_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy, + const igraph_vector_t *minz, + const igraph_vector_t *maxz); + + This is the 3D version of the force based Fruchterman-Reingold +layout. See ‘igraph_layout_fruchterman_reingold()’ (*note +igraph_layout_fruchterman_reingold --- Places the vertices on a plane +according to the Fruchterman-Reingold algorithm_::) for the 2D version. + + *Arguments:. * + +‘graph’: + Pointer to an initialized graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result and will be resized as needed. + +‘use_seed’: + If true the supplied values in the ‘res’ argument are used as an + initial layout, if false a random initial layout is used. + +‘niter’: + The number of iterations to do. A reasonable default value is 500. + +‘start_temp’: + Start temperature. This is the maximum amount of movement alloved + along one axis, within one step, for a vertex. Currently it is + decreased linearly to zero during the iteration. + +‘weights’: + Pointer to a vector containing edge weights. Weights must be + positive. If ‘NULL’, all edges are assumed to have weight 1. The + attraction along the edges will be multiplied by the weights, + resulting in vertices connected by a high-weight edge being placed + closer together. + +‘minx’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'x' coordinate for every vertex. + +‘maxx’: + Same as ‘minx’, but the maximum 'x' coordinates. + +‘miny’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'y' coordinate for every vertex. + +‘maxy’: + Same as ‘miny’, but the maximum 'y' coordinates. + +‘minz’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'z' coordinate for every vertex. + +‘maxz’: + Same as ‘minz’, but the maximum 'z' coordinates. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: O(|V|^2) in each iteration, |V| is the number of +vertices in the graph. + + +File: igraph-docs.info, Node: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_, Next: igraph_layout_umap_3d --- 3D layout using UMAP_, Prev: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_, Up: 3D layout generators + +29.3.5 igraph_layout_kamada_kawai_3d -- 3D version of the Kamada-Kawai layout generator. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_kamada_kawai_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz); + + This is the 3D version of ‘igraph_layout_kamada_kawai()’ (*note +igraph_layout_kamada_kawai --- Places the vertices on a plane according +to the Kamada-Kawai algorithm_::). See the documentation of that +function for more information. + + This layout algorithm is not suitable for large graphs. The memory +requirements are of the order O(|V|^2). + + *Arguments:. * + +‘graph’: + A graph object. + +‘res’: + Pointer to an initialized matrix object. This will contain the + result (x-, y- and z-positions in columns one through three) and + will be resized if needed. + +‘use_seed’: + Boolean, whether to use the values supplied in the ‘res’ argument + as the initial configuration. If zero and there are any limits on + the z, y or z coordinates, then a random initial configuration is + used. Otherwise the vertices are placed uniformly on a sphere of + radius 1 as the initial configuration. + +‘maxiter’: + The maximum number of iterations to perform. A reasonable default + value is at least ten (or more) times the number of vertices. + +‘epsilon’: + Stop the iteration, if the maximum delta value of the algorithm is + smaller than this. It is safe to leave it at zero, and then + ‘maxiter’ iterations are performed. + +‘kkconst’: + The Kamada-Kawai vertex attraction constant. Typical value: number + of vertices. + +‘weights’: + A vector of edge weights. Weights are interpreted as edge + _lengths_ in the shortest path calculation used by the Kamada-Kawai + algorithm. Therefore, vertices connected by high-weight edges will + be placed further apart. Pass ‘NULL’ to assume unit weights for + all edges. + +‘minx’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'x' coordinate for every vertex. + +‘maxx’: + Same as ‘minx’, but the maximum 'x' coordinates. + +‘miny’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'y' coordinate for every vertex. + +‘maxy’: + Same as ‘miny’, but the maximum 'y' coordinates. + +‘minz’: + Pointer to a vector, or a ‘NULL’ pointer. If not a ‘NULL’ pointer + then the vector gives the minimum 'z' coordinate for every vertex. + +‘maxz’: + Same as ‘minz’, but the maximum 'z' coordinates. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|) for each iteration, after an O(|V|^2 log|V|) +initialization step. |V| is the number of vertices in the graph. + + +File: igraph-docs.info, Node: igraph_layout_umap_3d --- 3D layout using UMAP_, Prev: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_, Up: 3D layout generators + +29.3.6 igraph_layout_umap_3d -- 3D layout using UMAP. +----------------------------------------------------- + + + igraph_error_t igraph_layout_umap_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights); + + *Warning* + + This function is experimental and its signature is not considered + final yet. We reserve the right to change the function signature + without changing the major version of igraph. Use it at your own + risk. + + This is the 3D version of the UMAP algorithm (see +‘igraph_layout_umap()’ (*note igraph_layout_umap --- Layout using +Uniform Manifold Approximation and Projection [UMAP]_::) for the 2D +version). + + *Arguments:. * + +‘graph’: + Pointer to the graph to find a layout for (i.e. to embed). This + is typically a directed, sparse graph with only edges for the + shortest distances stored, e.g. a k-nearest neighbors graph with + the edges going from each focal vertex to its neighbors. However, + it can also be an undirected graph. If the ‘distances_are_weights’ + is ‘true’, this is treated as an undirected graph. + +‘res’: + Pointer to the n by 3 matrix where the layout coordinates will be + stored. + +‘use_seed’: + If true the supplied values in the ‘res’ argument are used as an + initial layout, if false a random initial layout is used. + +‘distances’: + Pointer to a vector of distances associated with the graph edges. + If this argument is ‘NULL’, all edges are assumed to have the same + distance. + +‘min_dist’: + A fudge parameter that decides how close two unconnected vertices + can be in the embedding before feeling a repulsive force. It must + not be negative. Typical values are between 0 and 1. + +‘epochs’: + Number of iterations of the main stochastic gradient descent loop + on the cross-entropy. Typical values are between 30 and 500. + +‘distances_are_weights’: + Whether to use precomputed weights. If ‘false’ (the typical use + case), this function will compute weights from distances and then + use them to compute the layout. If ‘true’, the ‘distances’ vector + contains precomputed weights, including possibly some weights equal + to zero that are inconsequential for the layout optimization. + + *Returns:. * + +‘’ + Error code. + + *See also:. * + +‘’ + ‘igraph_layout_umap()’ (*note igraph_layout_umap --- Layout using + Uniform Manifold Approximation and Projection [UMAP]_::) + + +File: igraph-docs.info, Node: Post-processing layouts, Prev: 3D layout generators, Up: Generating layouts for graph drawing + +29.4 Post-processing layouts +============================ + +* Menu: + +* igraph_layout_merge_dla -- Merges multiple layouts by using a DLA algorithm.: igraph_layout_merge_dla --- Merges multiple layouts by using a DLA algorithm_. +* igraph_layout_align -- Aligns a graph layout with the coordinate axes.: igraph_layout_align --- Aligns a graph layout with the coordinate axes_. + + +File: igraph-docs.info, Node: igraph_layout_merge_dla --- Merges multiple layouts by using a DLA algorithm_, Next: igraph_layout_align --- Aligns a graph layout with the coordinate axes_, Up: Post-processing layouts + +29.4.1 igraph_layout_merge_dla -- Merges multiple layouts by using a DLA algorithm. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_merge_dla( + const igraph_vector_ptr_t *thegraphs, const igraph_matrix_list_t *coords, + igraph_matrix_t *res + ); + + First each layout is covered by a circle. Then the layout of the +largest graph is placed at the origin. Then the other layouts are +placed by the DLA algorithm, larger ones first and smaller ones last. + + *Arguments:. * + +‘thegraphs’: + Pointer vector containing the graph objects of which the layouts + will be merged. + +‘coords’: + List of matrices with the 2D layouts of the graphs in ‘thegraphs’. + +‘res’: + Pointer to an initialized matrix object, the result will be stored + here. It will be resized if needed. + + *Returns:. * + +‘’ + Error code. + + Added in version 0.2. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_layout_align --- Aligns a graph layout with the coordinate axes_, Prev: igraph_layout_merge_dla --- Merges multiple layouts by using a DLA algorithm_, Up: Post-processing layouts + +29.4.2 igraph_layout_align -- Aligns a graph layout with the coordinate axes. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_layout_align(const igraph_t *graph, igraph_matrix_t *layout); + + This function centers a vertex layout on the coordinate system origin +and rotates the layout to achieve a visually pleasing alignment with the +coordinate axes. Doing this is particularly useful with force-directed +layouts such as ‘igraph_layout_fruchterman_reingold()’ (*note +igraph_layout_fruchterman_reingold --- Places the vertices on a plane +according to the Fruchterman-Reingold algorithm_::). Layouts in +arbitrary dimensional spaces are supported. + + *Arguments:. * + +‘graph’: + The graph whose layout is to be aligned. + +‘layout’: + A matrix whose rows are the coordinates of vertices. It will be + modified in-place. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E| + |V|), linear in the number of edges and +vertices. + + +File: igraph-docs.info, Node: Processes on graphs, Next: Reading and writing graphs from and to files, Prev: Generating layouts for graph drawing, Up: Top + +30 Processes on graphs +********************** + +* Menu: + +* Epidemic models:: + + +File: igraph-docs.info, Node: Epidemic models, Up: Processes on graphs + +30.1 Epidemic models +==================== + +* Menu: + +* igraph_sir -- Performs a number of SIR epidemics model runs on a graph.: igraph_sir --- Performs a number of SIR epidemics model runs on a graph_. +* igraph_sir_t -- The result of one SIR model simulation.: igraph_sir_t --- The result of one SIR model simulation_. +* igraph_sir_destroy -- Deallocates memory associated with a SIR simulation run.: igraph_sir_destroy --- Deallocates memory associated with a SIR simulation run_. + + +File: igraph-docs.info, Node: igraph_sir --- Performs a number of SIR epidemics model runs on a graph_, Next: igraph_sir_t --- The result of one SIR model simulation_, Up: Epidemic models + +30.1.1 igraph_sir -- Performs a number of SIR epidemics model runs on a graph. +------------------------------------------------------------------------------ + + + igraph_error_t igraph_sir(const igraph_t *graph, igraph_real_t beta, + igraph_real_t gamma, igraph_int_t no_sim, + igraph_vector_ptr_t *result); + + The SIR model is a simple model from epidemiology. The individuals +of the population might be in three states: susceptible, infected and +recovered. Recovered people are assumed to be immune to the disease. +Susceptibles become infected with a rate that depends on their number of +infected neighbors. Infected people become recovered with a constant +rate. See these parameters below. + + This function runs multiple simulations, all starting with a single +uniformly randomly chosen infected individual. A simulation is stopped +when no infected individuals are left. + + *Arguments:. * + +‘graph’: + The graph to perform the model on. For directed graphs edge + directions are ignored and a warning is given. + +‘beta’: + The rate of infection of an individual that is susceptible and has + a single infected neighbor. The infection rate of a susceptible + individual with n infected neighbors is n times beta. Formally + this is the rate parameter of an exponential distribution. + +‘gamma’: + The rate of recovery of an infected individual. Formally, this is + the rate parameter of an exponential distribution. + +‘no_sim’: + The number of simulation runs to perform. + +‘result’: + The result of the simulation is stored here, in a list of + ‘igraph_sir_t’ (*note igraph_sir_t --- The result of one SIR model + simulation_::) objects. To deallocate memory, the user needs to + call ‘igraph_sir_destroy’ (*note igraph_sir_destroy --- Deallocates + memory associated with a SIR simulation run_::) on each element, + before destroying the pointer vector itself using + ‘igraph_vector_ptr_destroy_all()’ (*note + igraph_vector_ptr_destroy_all --- Frees all the elements and + destroys the pointer vector_::). + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(no_sim * (|V| + |E| log(|V|))). + + +File: igraph-docs.info, Node: igraph_sir_t --- The result of one SIR model simulation_, Next: igraph_sir_destroy --- Deallocates memory associated with a SIR simulation run_, Prev: igraph_sir --- Performs a number of SIR epidemics model runs on a graph_, Up: Epidemic models + +30.1.2 igraph_sir_t -- The result of one SIR model simulation. +-------------------------------------------------------------- + + + typedef struct igraph_sir_t { + igraph_vector_t times; + igraph_vector_int_t no_s, no_i, no_r; + } igraph_sir_t; + + Data structure to store the results of one simulation of the SIR +(susceptible-infected-recovered) model on a graph. It has the following +members. They are all (real or integer) vectors, and they are of the +same length. + + *Values:. * + +‘times’: + A vector, the times of the events are stored here. + +‘no_s’: + An integer vector, the number of susceptibles in each time step is + stored here. + +‘no_i’: + An integer vector, the number of infected individuals at each time + step, is stored here. + +‘no_r’: + An integer vector, the number of recovered individuals is stored + here at each time step. + + +File: igraph-docs.info, Node: igraph_sir_destroy --- Deallocates memory associated with a SIR simulation run_, Prev: igraph_sir_t --- The result of one SIR model simulation_, Up: Epidemic models + +30.1.3 igraph_sir_destroy -- Deallocates memory associated with a SIR simulation run. +------------------------------------------------------------------------------------- + + + void igraph_sir_destroy(igraph_sir_t *sir); + + *Arguments:. * + +‘sir’: + The ‘igraph_sir_t’ (*note igraph_sir_t --- The result of one SIR + model simulation_::) object storing the simulation. + + +File: igraph-docs.info, Node: Reading and writing graphs from and to files, Next: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs, Prev: Processes on graphs, Up: Top + +31 Reading and writing graphs from and to files +*********************************************** + +These functions can write a graph to a file, or read a graph from a +file. + + They assume that the current locale uses a decimal point and not a +decimal comma. See ‘igraph_enter_safelocale()’ (*note +igraph_enter_safelocale --- Temporarily set the C locale_::) and +‘igraph_exit_safelocale()’ (*note igraph_exit_safelocale --- Temporarily +set the C locale_::) for more information. + + Note that as ‘igraph’ uses the traditional C streams, it is possible +to read/write files from/to memory, at least on GNU operating systems +supporting 'non-standard' streams. + +* Menu: + +* Simple edge list and similar formats:: +* Binary formats:: +* GraphML format:: +* GML format:: +* Pajek format:: +* UCINET's DL file format:: +* Graphviz format:: +* LEDA format:: +* Convenience functions for locale change:: + + +File: igraph-docs.info, Node: Simple edge list and similar formats, Next: Binary formats, Up: Reading and writing graphs from and to files + +31.1 Simple edge list and similar formats +========================================= + +* Menu: + +* igraph_read_graph_edgelist -- Reads an edge list from a file and creates a graph.: igraph_read_graph_edgelist --- Reads an edge list from a file and creates a graph_. +* igraph_write_graph_edgelist -- Writes the edge list of a graph to a file.: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_. +* igraph_read_graph_ncol -- Reads an .ncol file used by LGL.: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_. +* igraph_write_graph_ncol -- Writes the graph to a file in .ncol format.: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_. +* igraph_read_graph_lgl -- Reads a graph from an .lgl file.: igraph_read_graph_lgl --- Reads a graph from an _lgl file_. +* igraph_write_graph_lgl -- Writes the graph to a file in .lgl format.: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_. +* igraph_read_graph_dimacs_flow -- Read a graph in DIMACS format.: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_. +* igraph_write_graph_dimacs_flow -- Write a graph in DIMACS format.: igraph_write_graph_dimacs_flow --- Write a graph in DIMACS format_. + + +File: igraph-docs.info, Node: igraph_read_graph_edgelist --- Reads an edge list from a file and creates a graph_, Next: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_, Up: Simple edge list and similar formats + +31.1.1 igraph_read_graph_edgelist -- Reads an edge list from a file and creates a graph. +---------------------------------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_edgelist(igraph_t *graph, FILE *instream, + igraph_int_t n, igraph_bool_t directed); + + This format is simply a series of an even number of non-negative +integers separated by whitespace. The integers represent vertex IDs. +Placing each edge (i.e. pair of integers) on a separate line is not +required, but it is recommended for readability. Edges of directed +graphs are assumed to be in "from, to" order. + + The largest vertex ID plus one, or the parameter ‘n’ determines the +vertex count, whichever is larger. See ‘igraph_read_graph_ncol()’ +(*note igraph_read_graph_ncol --- Reads an _ncol file used by LGL_::) +for reading files where vertices are specified by name instead of by a +numerical vertex ID. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + Pointer to a stream, it should be readable. + +‘n’: + The number of vertices in the graph. If smaller than the largest + integer in the file it will be ignored. It is thus safe to supply + zero here. + +‘directed’: + If true the graph is directed, if false it will be undirected. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_PARSEERROR’: if there is a problem reading the + file, or the file is syntactically incorrect. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. It is assumed that reading an integer requires O(1) time. + + +File: igraph-docs.info, Node: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_, Next: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_, Prev: igraph_read_graph_edgelist --- Reads an edge list from a file and creates a graph_, Up: Simple edge list and similar formats + +31.1.2 igraph_write_graph_edgelist -- Writes the edge list of a graph to a file. +-------------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_edgelist(const igraph_t *graph, FILE *outstream); + + Edges are represented as pairs of 0-based vertex indices. One edge +is written per line, separated by a single space. For directed graphs +edges are written in from, to order. + + *Arguments:. * + +‘graph’: + The graph object to write. + +‘outstream’: + Pointer to a stream, it should be writable. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EFILE’ if there is an error writing the file. + + Time complexity: O(|E|), the number of edges in the graph. It is +assumed that writing an integer to the file requires O(1) time. + + +File: igraph-docs.info, Node: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_, Next: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_, Prev: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_, Up: Simple edge list and similar formats + +31.1.3 igraph_read_graph_ncol -- Reads an .ncol file used by LGL. +----------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_ncol(igraph_t *graph, FILE *instream, + const igraph_strvector_t *predefnames, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed); + + Also useful for creating graphs from 'named' (and optionally +weighted) edge lists. + + This format is used by the Large Graph Layout program +(https://lgl.sourceforge.net (https://lgl.sourceforge.net)), and it is +simply a symbolic weighted edge list. It is a simple text file with one +edge per line. An edge is defined by two symbolic vertex names +separated by whitespace. The vertex names themselves cannot contain +whitespace. They may be followed by an optional number, the weight of +the edge; the number can be negative and can be in scientific notation. +If there is no weight specified to an edge it is assumed to be zero. + + The resulting graph is always undirected. LGL cannot deal with files +which contain multiple or loop edges, this is however not checked here, +as ‘igraph’ is happy with these. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + Pointer to a stream, it should be readable. + +‘predefnames’: + Pointer to the symbolic names of the vertices in the file. If + ‘NULL’ is given here then vertex IDs will be assigned to vertex + names in the order of their appearance in the ‘.ncol’ file. If it + is not ‘NULL’ and some unknown vertex names are found in the + ‘.ncol’ file then new vertex ids will be assigned to them. + +‘names’: + Boolean value. If ‘true’, the symbolic names of the vertices will + be added to the graph as a vertex attribute called 'name'. + +‘weights’: + Whether to add the weights of the edges to the graph as an edge + attribute called 'weight'. ‘IGRAPH_ADD_WEIGHTS_YES’ adds the + weights (even if they are not present in the file, in this case + they are assumed to be 1). ‘IGRAPH_ADD_WEIGHTS_NO’ does not add + any edge attribute. ‘IGRAPH_ADD_WEIGHTS_IF_PRESENT’ adds the + attribute if and only if there is at least one explicit edge weight + in the input file, and edges without an explicit weight are assumed + to have a weight of 1. + +‘directed’: + Whether to create a directed graph. As this format was originally + used only for undirected graphs there is no information in the file + about the directedness of the graph. Set this parameter to + ‘IGRAPH_DIRECTED’ or ‘IGRAPH_UNDIRECTED’ to create a directed or + undirected graph. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_PARSEERROR’: if there is a problem reading the + file, or the file is syntactically incorrect. + + Time complexity: O(|V|+|E|log(|V|)) if we neglect the time required +by the parsing. As usual |V| is the number of vertices, while |E| is +the number of edges. + + *See also:. * + +‘’ + ‘igraph_read_graph_lgl()’ (*note igraph_read_graph_lgl --- Reads a + graph from an _lgl file_::), ‘igraph_write_graph_ncol()’ (*note + igraph_write_graph_ncol --- Writes the graph to a file in _ncol + format_::) + + +File: igraph-docs.info, Node: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_, Next: igraph_read_graph_lgl --- Reads a graph from an _lgl file_, Prev: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_, Up: Simple edge list and similar formats + +31.1.4 igraph_write_graph_ncol -- Writes the graph to a file in .ncol format. +----------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_ncol(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights); + + ‘.ncol’ is a format used by LGL, see ‘igraph_read_graph_ncol()’ +(*note igraph_read_graph_ncol --- Reads an _ncol file used by LGL_::) +for details. + + Note that having multiple or loop edges in an ‘.ncol’ file breaks the +LGL software but ‘igraph’ does not check for this condition. + + This format cannot represent zero-degree vertices. + + *Arguments:. * + +‘graph’: + The graph to write. + +‘outstream’: + The stream object to write to, it should be writable. + +‘names’: + The name of a string vertex attribute, if symbolic names are to be + written to the file. Supply ‘NULL’ to write vertex ids instead. + +‘weights’: + The name of a numerical edge attribute, which will be written as + weights to the file. Supply ‘NULL’ to skip writing edge weights. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EFILE’ if there is an error writing the file. + + Time complexity: O(|E|), the number of edges. All file operations +are expected to have time complexity O(1). + + *See also:. * + +‘’ + ‘igraph_read_graph_ncol()’ (*note igraph_read_graph_ncol --- Reads + an _ncol file used by LGL_::), ‘igraph_write_graph_lgl()’ (*note + igraph_write_graph_lgl --- Writes the graph to a file in _lgl + format_::) + + +File: igraph-docs.info, Node: igraph_read_graph_lgl --- Reads a graph from an _lgl file_, Next: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_, Prev: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_, Up: Simple edge list and similar formats + +31.1.5 igraph_read_graph_lgl -- Reads a graph from an .lgl file. +---------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_lgl(igraph_t *graph, FILE *instream, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed); + + The ‘.lgl’ format is used by the Large Graph Layout visualization +software (https://lgl.sourceforge.net (https://lgl.sourceforge.net)), it +can describe undirected optionally weighted graphs. From the LGL +manual: + + The second format is the LGL file format (‘.lgl’ file suffix). + This is yet another graph file format that tries to be as stingy as + possible with space, yet keeping the edge file in a human readable + (not binary) format. The format itself is like the following: + + # vertex1name + vertex2name [optionalWeight] + vertex3name [optionalWeight] + + Here, the first vertex of an edge is preceded with a pound sign + '#'. Then each vertex that shares an edge with that vertex is + listed one per line on subsequent lines. + + LGL cannot handle loop and multiple edges or directed graphs, but in +‘igraph’ it is not an error to have multiple and loop edges. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + A stream, it should be readable. + +‘names’: + Boolean value, if ‘true’ the symbolic names of the vertices will be + added to the graph as a vertex attribute called 'name'. + +‘weights’: + Whether to add the weights of the edges to the graph as an edge + attribute called 'weight'. ‘IGRAPH_ADD_WEIGHTS_YES’ adds the + weights (even if they are not present in the file, in this case + they are assumed to be 1). ‘IGRAPH_ADD_WEIGHTS_NO’ does not add + any edge attribute. ‘IGRAPH_ADD_WEIGHTS_IF_PRESENT’ adds the + attribute if and only if there is at least one explicit edge weight + in the input file, and edges without an explicit weight are assumed + to have a weight of 1. + +‘directed’: + Whether to create a directed graph. As this format was originally + used only for undirected graphs there is no information in the file + about the directedness of the graph. Set this parameter to + ‘IGRAPH_DIRECTED’ or ‘IGRAPH_UNDIRECTED’ to create a directed or + undirected graph. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_PARSEERROR’: if there is a problem reading the + file, or the file is syntactically incorrect. + + Time complexity: O(|V|+|E|log(|V|)) if we neglect the time required +by the parsing. As usual |V| is the number of vertices, while |E| is +the number of edges. + + *See also:. * + +‘’ + ‘igraph_read_graph_ncol()’ (*note igraph_read_graph_ncol --- Reads + an _ncol file used by LGL_::), ‘igraph_write_graph_lgl()’ (*note + igraph_write_graph_lgl --- Writes the graph to a file in _lgl + format_::) + + * File examples/simple/igraph_read_graph_lgl.c* + + +File: igraph-docs.info, Node: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_, Next: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_, Prev: igraph_read_graph_lgl --- Reads a graph from an _lgl file_, Up: Simple edge list and similar formats + +31.1.6 igraph_write_graph_lgl -- Writes the graph to a file in .lgl format. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_lgl(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights, + igraph_bool_t isolates); + + ‘.lgl’ is a format used by LGL, see ‘igraph_read_graph_lgl()’ (*note +igraph_read_graph_lgl --- Reads a graph from an _lgl file_::) for +details. + + Note that having multiple or loop edges in an ‘.lgl’ file breaks the +LGL software but ‘igraph’ does not check for this condition. + + *Arguments:. * + +‘graph’: + The graph to write. + +‘outstream’: + The stream object to write to, it should be writable. + +‘names’: + The name of a string vertex attribute, if symbolic names are to be + written to the file. Supply ‘NULL’ to write vertex ids instead. + +‘weights’: + The name of a numerical edge attribute, which will be written as + weights to the file. Supply ‘NULL’ to skip writing edge weights. + +‘isolates’: + If ‘true’, isolated vertices are also written to the file. If + ‘false’, they will be omitted. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EFILE’ if there is an error writing the file. + + Time complexity: O(|E|), the number of edges if ‘isolates’ is +‘false’, O(|V|+|E|) otherwise. All file operations are expected to have +time complexity O(1). + + *See also:. * + +‘’ + ‘igraph_read_graph_lgl()’ (*note igraph_read_graph_lgl --- Reads a + graph from an _lgl file_::), ‘igraph_write_graph_ncol()’ (*note + igraph_write_graph_ncol --- Writes the graph to a file in _ncol + format_::) + + * File examples/simple/igraph_write_graph_lgl.c* + + +File: igraph-docs.info, Node: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_, Next: igraph_write_graph_dimacs_flow --- Write a graph in DIMACS format_, Prev: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_, Up: Simple edge list and similar formats + +31.1.7 igraph_read_graph_dimacs_flow -- Read a graph in DIMACS format. +---------------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_dimacs_flow( + igraph_t *graph, FILE *instream, + igraph_strvector_t *problem, + igraph_vector_int_t *label, + igraph_int_t *source, + igraph_int_t *target, + igraph_vector_t *capacity, + igraph_bool_t directed); + + This function reads the DIMACS file format, more specifically the +version for network flow problems, see the files at +http://archive.dimacs.rutgers.edu/pub/netflow/general-info/ +(http://archive.dimacs.rutgers.edu/pub/netflow/general-info/) + + This is a line-oriented text file (ASCII) format. The first +character of each line defines the type of the line. If the first +character is ‘c’ the line is a comment line and it is ignored. There is +one problem line (‘p’ in the file), it must appear before any node and +arc descriptor lines. The problem line has three fields separated by +spaces: the problem type (‘max’ or ‘edge’), the number of vertices, and +number of edges in the graph. In MAX problems, exactly two node +identification lines are expected (‘n’), one for the source, and one for +the target vertex. These have two fields: the ID of the vertex and the +type of the vertex, either ‘s’ ( = source) or ‘t’ ( = target). Arc +lines start with ‘a’ and have three fields: the source vertex, the +target vertex and the edge capacity. In EDGE problems, there may be a +node line (‘n’) for each node. It specifies the node index and an +integer node label. Nodes for which no explicit label was specified +will use their index as label. In EDGE problems, each edge is specified +as an edge line (‘e’). + + Within DIMACS files, vertex IDs are numbered from 1. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + The file to read from. + +‘problem’: + If not ‘NULL’, it will contain the problem type. + +‘label’: + If not ‘NULL’, node labels will be stored here for ‘edge’ problems. + Ignored for ‘max’ problems. + +‘source’: + Pointer to an integer, the ID of the source node will be stored + here. (The igraph vertex ID, which is one less than the actual + number in the file.) It is ignored if ‘NULL’. + +‘target’: + Pointer to an integer, the (igraph) ID of the target node will be + stored here. It is ignored if ‘NULL’. + +‘capacity’: + Pointer to an initialized vector, the capacity of the edges will be + stored here if not \ NULL. + +‘directed’: + Boolean, whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|+c), the number of vertices plus the number +of edges, plus the size of the file in characters. + + *See also:. * + +‘’ + ‘igraph_write_graph_dimacs_flow()’ (*note + igraph_write_graph_dimacs_flow --- Write a graph in DIMACS + format_::) + + +File: igraph-docs.info, Node: igraph_write_graph_dimacs_flow --- Write a graph in DIMACS format_, Prev: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_, Up: Simple edge list and similar formats + +31.1.8 igraph_write_graph_dimacs_flow -- Write a graph in DIMACS format. +------------------------------------------------------------------------ + + + igraph_error_t igraph_write_graph_dimacs_flow(const igraph_t *graph, FILE *outstream, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); + + This function writes a graph to an output stream in DIMACS format, +describing a maximum flow problem. See +ftp://dimacs.rutgers.edu/pub/netflow/general-info/ + + This file format is discussed in the documentation of +‘igraph_read_graph_dimacs_flow()’ (*note igraph_read_graph_dimacs_flow +--- Read a graph in DIMACS format_::), see that for more information. + + *Arguments:. * + +‘graph’: + The graph to write to the stream. + +‘outstream’: + The stream. + +‘source’: + Integer, the id of the source vertex for the maximum flow. + +‘target’: + Integer, the id of the target vertex. + +‘capacity’: + Pointer to an initialized vector containing the edge capacity + values. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|E|), the number of edges in the graph. + + *See also:. * + +‘’ + ‘igraph_read_graph_dimacs_flow()’ (*note + igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_::) + + +File: igraph-docs.info, Node: Binary formats, Next: GraphML format, Prev: Simple edge list and similar formats, Up: Reading and writing graphs from and to files + +31.2 Binary formats +=================== + +* Menu: + +* igraph_read_graph_graphdb -- Read a graph in the binary graph database format.: igraph_read_graph_graphdb --- Read a graph in the binary graph database format_. + + +File: igraph-docs.info, Node: igraph_read_graph_graphdb --- Read a graph in the binary graph database format_, Up: Binary formats + +31.2.1 igraph_read_graph_graphdb -- Read a graph in the binary graph database format. +------------------------------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_graphdb(igraph_t *graph, FILE *instream, + igraph_bool_t directed); + + This is a binary format, used in the ARG Graph Database for +isomorphism testing. For more information, see +https://mivia.unisa.it/datasets/graph-database/arg-database/ +(https://mivia.unisa.it/datasets/graph-database/arg-database/) + + From the graph database homepage: + + The graphs are stored in a compact binary format, one graph per + file. The file is composed of 16 bit words, which are represented + using the so-called little-endian convention, i.e. the least + significant byte of the word is stored first. + + Then, for each node, the file contains the list of edges coming out + of the node itself. The list is represented by a word encoding its + length, followed by a word for each edge, representing the + destination node of the edge. Node numeration is 0-based, so the + first node of the graph has index 0. + + As of igraph 0.10, only unlabelled graphs are implemented. + + References: + + M. De Santo, P. Foggia, C. Sansone, and M. Vento: A large database of +graphs and its use for benchmarking graph isomorphism algorithms. +Pattern Recognition Letters, 24(8), 1067-1079 (2003). +https://doi.org/10.1016/S0167-8655(02)00253-2 +(https://doi.org/10.1016/S0167-8655(02)00253-2) + + MIVIA ARG Dataset, https://zenodo.org/records/11204020 +(https://zenodo.org/records/11204020), +https://mivia.unisa.it/datasets/graph-database/arg-database/ +(https://mivia.unisa.it/datasets/graph-database/arg-database/) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + The stream to read from. It should be opened in binary mode. + +‘directed’: + Whether to create a directed graph. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices plus the number +of edges. + + * File examples/simple/igraph_read_graph_graphdb.c* + + +File: igraph-docs.info, Node: GraphML format, Next: GML format, Prev: Binary formats, Up: Reading and writing graphs from and to files + +31.3 GraphML format +=================== + +* Menu: + +* igraph_read_graph_graphml -- Reads a graph from a GraphML file.: igraph_read_graph_graphml --- Reads a graph from a GraphML file_. +* igraph_write_graph_graphml -- Writes the graph to a file in GraphML format.: igraph_write_graph_graphml --- Writes the graph to a file in GraphML format_. + + +File: igraph-docs.info, Node: igraph_read_graph_graphml --- Reads a graph from a GraphML file_, Next: igraph_write_graph_graphml --- Writes the graph to a file in GraphML format_, Up: GraphML format + +31.3.1 igraph_read_graph_graphml -- Reads a graph from a GraphML file. +---------------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_graphml(igraph_t *graph, FILE *instream, igraph_int_t index); + + GraphML is an XML-based file format for representing various types of +graphs. Currently only the most basic import functionality is +implemented in igraph: it can read GraphML files without nested graphs +and hyperedges. Attributes of the graph are loaded only if an attribute +interface is attached, see ‘igraph_set_attribute_table()’ (*note +igraph_set_attribute_table --- Attach an attribute table_::). String +attrribute values are returned in UTF-8 encoding. + + Graph attribute names are taken from the ‘attr.name’ attributes of +the ‘key’ tags in the GraphML file. Since ‘attr.name’ is not mandatory, +igraph will fall back to the ‘id’ attribute of the ‘key’ tag if +‘attr.name’ is missing. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + A stream, it should be readable. + +‘index’: + If the GraphML file contains more than one graph, the one specified + by this index will be loaded. Indices start from zero, so supply + zero here if your GraphML file contains only a single graph. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_PARSEERROR’: if there is a problem reading the + file, or the file is syntactically incorrect. + ‘IGRAPH_UNIMPLEMENTED’: the GraphML functionality was disabled at + compile-time + + * File examples/simple/graphml.c* + + +File: igraph-docs.info, Node: igraph_write_graph_graphml --- Writes the graph to a file in GraphML format_, Prev: igraph_read_graph_graphml --- Reads a graph from a GraphML file_, Up: GraphML format + +31.3.2 igraph_write_graph_graphml -- Writes the graph to a file in GraphML format. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_graphml(const igraph_t *graph, FILE *outstream, + igraph_bool_t prefixattr); + + GraphML is an XML-based file format for representing various types of +graphs. See the GraphML Primer +(http://graphml.graphdrawing.org/primer/graphml-primer.html +(http://graphml.graphdrawing.org/primer/graphml-primer.html)) for the +detailed format description. + + When a numerical attribute value is NaN, it will be omitted from the +file. + + This function assumes that non-ASCII characters in attribute names +and string attribute values are UTF-8 encoded. If this is not the case, +the resulting XML file will be invalid. Control characters, i.e. +character codes up to and including 31 (with the exception of tab, cr +and lf), are not allowed. + + *Arguments:. * + +‘graph’: + The graph to write. + +‘outstream’: + The stream object to write to, it should be writable. + +‘prefixattr’: + Boolean value. Whether to put a prefix in front of the attribute + names to ensure uniqueness if the graph has vertex and edge (or + graph) attributes with the same name. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_EFILE’ if there is an error writing the file. + + Time complexity: O(|V|+|E|) otherwise. All file operations are +expected to have time complexity O(1). + + * File examples/simple/graphml.c* + + +File: igraph-docs.info, Node: GML format, Next: Pajek format, Prev: GraphML format, Up: Reading and writing graphs from and to files + +31.4 GML format +=============== + +* Menu: + +* igraph_read_graph_gml -- Read a graph in GML format.: igraph_read_graph_gml --- Read a graph in GML format_. +* igraph_write_graph_gml -- Write the graph to a stream in GML format.: igraph_write_graph_gml --- Write the graph to a stream in GML format_. + + +File: igraph-docs.info, Node: igraph_read_graph_gml --- Read a graph in GML format_, Next: igraph_write_graph_gml --- Write the graph to a stream in GML format_, Up: GML format + +31.4.1 igraph_read_graph_gml -- Read a graph in GML format. +----------------------------------------------------------- + + + igraph_error_t igraph_read_graph_gml(igraph_t *graph, FILE *instream); + + GML is a simple textual format, see +https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1 +(https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1) +for details. + + Although all syntactically correct GML can be parsed, we implement +only a subset of this format. Some attributes might be ignored. Here +is a list of all the differences: + + 1. Only attributes with a simple type are used: integer, real or + string. If an attribute is composite, i.e. an array or a record, + then it is ignored. When some values of the attribute are simple + and some compound, the composite ones are replaced with a default + value (NaN for numeric, ‘""’ for string). + + 2. ‘comment’ fields are not ignored. They are treated as any other + field and converted to attributes. + + 3. Top level attributes except for ‘Version’ and the first ‘graph’ + attribute are completely ignored. + + 4. There is no maximum line length or maximum keyword length. + + 5. Only the ‘quot’, ‘amp’, ‘apos’, ‘lt’ and ‘gt’ character entities + are supported. Any other entity is passed through unchanged by the + reader after issuing a warning, and is expected to be decoded by + the user. + + 6. We allow ‘inf’, ‘-inf’ and ‘nan’ (not a number) as a real number. + This is case insensitive, so ‘nan’, ‘NaN’ and ‘NAN’ are equivalent. + + Please contact us if you cannot live with these limitations of the +GML parser. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + The stream to read the GML file from. + + *Returns:. * + +‘’ + Error code. + + Time complexity: should be proportional to the length of the file. + + *See also:. * + +‘’ + ‘igraph_read_graph_graphml()’ (*note igraph_read_graph_graphml --- + Reads a graph from a GraphML file_::) for a more modern format, + ‘igraph_write_graph_gml()’ (*note igraph_write_graph_gml --- Write + the graph to a stream in GML format_::) for writing GML files. + + * File examples/simple/gml.c* + + +File: igraph-docs.info, Node: igraph_write_graph_gml --- Write the graph to a stream in GML format_, Prev: igraph_read_graph_gml --- Read a graph in GML format_, Up: GML format + +31.4.2 igraph_write_graph_gml -- Write the graph to a stream in GML format. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_gml(const igraph_t *graph, FILE *outstream, + igraph_write_gml_sw_t options, + const igraph_vector_t *id, const char *creator); + + GML is a quite general textual format, see +https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1 +(https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1) +for details. + + The graph, vertex and edges attributes are written to the file as +well, if they are numeric or string. Boolean attributes are converted +to numeric, with 0 and 1 used for false and true, respectively. NaN +values of numeric attributes are skipped, as NaN is not part of the GML +specification and other software may not be able to read files +containing them. This is consistent with ‘igraph_read_graph_gml()’ +(*note igraph_read_graph_gml --- Read a graph in GML format_::), which +produces NaN when an attribute value is missing. In contrast with NaN, +infinite values are retained. Ensure that none of the numeric +attributes values are infinite to produce a conformant GML file that can +be read by other software. + + As igraph is more forgiving about attribute names, it might be +necessary to simplify the them before writing to the GML file. This way +we'll have a syntactically correct GML file. The following simple +procedure is performed on each attribute name: first the alphanumeric +characters are extracted, the others are ignored. Then if the first +character is not a letter then the attribute name is prefixed with +'igraph'. Note that this might result identical names for two +attributes, igraph does not check this. + + The 'id' vertex attribute is treated specially. If the ‘id’ argument +is not ‘NULL’ then it should be a numeric vector with the vertex IDs and +the 'id' vertex attribute is ignored (if there is one). If ‘id’ is +‘NULL’ and there is a numeric 'id' vertex attribute, it will be used +instead. If ids are not specified in either way then the regular igraph +vertex IDs are used. If some of the supplied id values are invalid +(non-integer or NaN), all supplied id are ignored and igraph vertex IDs +are used instead. + + Note that whichever way vertex IDs are specified, their uniqueness is +not checked. + + If the graph has edge attributes that become 'source' or 'target' +after encoding, or the graph has an attribute that becomes 'directed', +they will be ignored with a warning. GML uses these attributes to +specify the edge endpoints, and the graph directedness, so we cannot +write them to the file. Rename them before calling this function if you +want to preserve them. + + *Arguments:. * + +‘graph’: + The graph to write to the stream. + +‘outstream’: + The stream to write the file to. + +‘options’: + Set of ‘|’-combinable boolean flags for writing the GML file. + + ‘0’ + All options turned off. + + ‘IGRAPH_WRITE_GML_DEFAULT_SW’ + Default options, currently equivalent to 0. May change in + future versions. + + ‘IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW’ + Do not encode any other characters than " as entities. + Specifically, this option prevents the encoding of &. Useful + when re-exporting a graph that was read from a GML file in + which igraph could not interpret all entities, and thus passed + them through without decoding. + +‘id’: + Either ‘NULL’ or a numeric vector with the vertex IDs. See details + above. + +‘creator’: + An optional string to write to the stream in the creator line. If + ‘NULL’, the igraph version with the current date and time is added. + If ‘""’, the creator line is omitted. Otherwise, the supplied + string is used verbatim. + + *Returns:. * + +‘’ + Error code. + + Time complexity: should be proportional to the number of characters +written to the file. + + *See also:. * + +‘’ + ‘igraph_read_graph_gml()’ (*note igraph_read_graph_gml --- Read a + graph in GML format_::) for reading GML files, + ‘igraph_read_graph_graphml()’ (*note igraph_read_graph_graphml --- + Reads a graph from a GraphML file_::) for a more modern format. + + * File examples/simple/gml.c* + + +File: igraph-docs.info, Node: Pajek format, Next: UCINET's DL file format, Prev: GML format, Up: Reading and writing graphs from and to files + +31.5 Pajek format +================= + +* Menu: + +* igraph_read_graph_pajek -- Reads a file in Pajek format.: igraph_read_graph_pajek --- Reads a file in Pajek format_. +* igraph_write_graph_pajek -- Writes a graph to a file in Pajek format.: igraph_write_graph_pajek --- Writes a graph to a file in Pajek format_. + + +File: igraph-docs.info, Node: igraph_read_graph_pajek --- Reads a file in Pajek format_, Next: igraph_write_graph_pajek --- Writes a graph to a file in Pajek format_, Up: Pajek format + +31.5.1 igraph_read_graph_pajek -- Reads a file in Pajek format. +--------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_pajek(igraph_t *graph, FILE *instream); + + Only a subset of the Pajek format is implemented. This is partially +because there is no formal specification for this format, but also +because ‘igraph’ does not support some Pajek features, like mixed +graphs. + + Starting from version 0.6.1 igraph reads bipartite (two-mode) graphs +from Pajek files and adds the ‘type’ Boolean vertex attribute for them. +Warnings are given for invalid edges, i.e. edges connecting vertices of +the same type. + + The list of the current limitations: + + 1. Only ‘.net’ files are supported, Pajek project files (‘.paj’) are + not. + + 2. Temporal networks (i.e. with time events) are not supported. + + 3. Graphs with both directed and non-directed edges are not supported, + as they cannot be represented in ‘igraph’. + + 4. Only Pajek networks are supported; permutations, hierarchies, + clusters and vectors are not. + + 5. Multi-relational networks (i.e. networks with multiple edge types) + are not supported. + + 6. Unicode characters encoded as ‘&#dddd;’, or newlines encoded as + ‘\n’ will not be decoded. + + If an attribute handler is installed, ‘igraph’ also reads the vertex +and edge attributes from the file. Most attributes are renamed to be +more informative: ‘color’ instead of ‘c’, ‘xfact’ instead of ‘x_fact’, +‘yfact’ instead of y_fact, ‘labeldist’ instead of ‘lr’, ‘labeldegree2’ +instead of ‘lphi’, ‘framewidth’ instead of ‘bw’, ‘fontsize’ instead of +‘fos’, ‘rotation’ instead of ‘phi’, ‘radius’ instead of ‘r’, +‘diamondratio’ instead of ‘q’, ‘labeldegree’ instead of ‘la’, ‘color’ +instead of ‘ic’, ‘framecolor’ instead of ‘bc’, ‘labelcolor’ instead of +‘lc’; these belong to vertices. + + Edge attributes are also renamed, ‘s’ to ‘arrowsize’, ‘w’ to +‘edgewidth’, ‘h1’ to ‘hook1’, ‘h2’ to ‘hook2’, ‘a1’ to ‘angle1’, ‘a2’ to +‘angle2’, ‘k1’ to ‘velocity1’, ‘k2’ to ‘velocity2’, ‘ap’ to ‘arrowpos’, +‘lp’ to ‘labelpos’, ‘lr’ to ‘labelangle’, ‘lphi’ to ‘labelangle2’, ‘la’ +to ‘labeldegree’, ‘fos’ to ‘fontsize’, ‘a’ to ‘arrowtype’, ‘p’ to +‘linepattern’, ‘l’ to ‘label’, ‘lc’ to ‘labelcolor’, ‘c’ to ‘color’. + + Unknown vertex or edge parameters are read as string vertex or edge +attributes. If the parameter name conflicts with one the standard +attribute names mentioned above, a ‘_’ character is appended to it to +avoid conflict. + + In addition the following vertex attributes might be added: ‘name’ is +added (with the same value) if there are vertex IDs in the file. ‘x’ +and ‘y’, and potentially ‘z’ are also added if there are vertex +coordinates in the file. + + The ‘weight’ edge attribute will be added if there are edge weights +present. + + See the Pajek homepage: +http://vlado.fmf.uni-lj.si/pub/networks/pajek/ +(http://vlado.fmf.uni-lj.si/pub/networks/pajek/) for more info on Pajek. +The Pajek manual, +http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf +(http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf), and +http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm +(http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm) have information on the +Pajek file format. There is additional useful information and sample +files at http://mrvar.fdv.uni-lj.si/pajek/history.htm +(http://mrvar.fdv.uni-lj.si/pajek/history.htm) + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + An already opened file handler. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| +the number of edges, |A| the number of attributes (vertex + edge) in the +graph if there are attribute handlers installed. + + *See also:. * + +‘’ + ‘igraph_write_graph_pajek()’ (*note igraph_write_graph_pajek --- + Writes a graph to a file in Pajek format_::) for writing Pajek + files, ‘igraph_read_graph_graphml()’ (*note + igraph_read_graph_graphml --- Reads a graph from a GraphML file_::) + for reading GraphML files. + + * File examples/simple/foreign.c* + + +File: igraph-docs.info, Node: igraph_write_graph_pajek --- Writes a graph to a file in Pajek format_, Prev: igraph_read_graph_pajek --- Reads a file in Pajek format_, Up: Pajek format + +31.5.2 igraph_write_graph_pajek -- Writes a graph to a file in Pajek format. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_pajek(const igraph_t *graph, FILE *outstream); + + Writes files in the native format of the Pajek software. This format +is not recommended for data exchange or archival. It is meant solely +for interoperability with Pajek. + + The Pajek vertex and edge parameters (like color) are determined by +the attributes of the vertices and edges. Of course this requires an +attribute handler to be installed. The names of the corresponding +vertex and edge attributes are listed at ‘igraph_read_graph_pajek()’ +(*note igraph_read_graph_pajek --- Reads a file in Pajek format_::), +e.g. the ‘color’ vertex attributes determines the color (‘c’ in Pajek) +parameter. + + Vertex and edge attributes that do not correspond to any documented +Pajek parameter are discarded. + + As of version 0.6.1 igraph writes bipartite graphs into Pajek files +correctly, i.e. they will be also bipartite when read into Pajek. As +Pajek is less flexible for bipartite graphs (the numeric IDs of the +vertices must be sorted according to vertex type), igraph might need to +reorder the vertices when writing a bipartite Pajek file. This +effectively means that numeric vertex IDs usually change when a +bipartite graph is written to a Pajek file, and then read back into +igraph. + + Early versions of Pajek supported only Windows-style line endings in +Pajek files, but recent versions support both Windows and Unix line +endings. igraph therefore uses the platform-native line endings when +the input file is opened in text mode, and uses Unix-style line endings +when the input file is opened in binary mode. If you are using an old +version of Pajek, you are on Unix and you are having problems reading +files written by igraph on a Windows machine, convert the line endings +manually with a text editor or with ‘unix2dos’ or ‘iconv’ from the +command line). + + Pajek will only interpret UTF-8 encoded files if they contain a +byte-order mark (BOM) at the beginning. igraph is agnostic of string +attribute encodings and therefore it will never write a BOM. You need to +add this manually if/when necessary. + + *Arguments:. * + +‘graph’: + The graph object to write. + +‘outstream’: + The file to write to. It should be opened and writable. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| +is the number of edges, |A| the number of attributes (vertex + edge) in +the graph if there are attribute handlers installed. + + *See also:. * + +‘’ + ‘igraph_read_graph_pajek()’ (*note igraph_read_graph_pajek --- + Reads a file in Pajek format_::) for reading Pajek graphs, + ‘igraph_write_graph_graphml()’ (*note igraph_write_graph_graphml + --- Writes the graph to a file in GraphML format_::) for writing a + graph in GraphML format, this suites ‘igraph’ graphs better. + + * File examples/simple/igraph_write_graph_pajek.c* + + +File: igraph-docs.info, Node: UCINET's DL file format, Next: Graphviz format, Prev: Pajek format, Up: Reading and writing graphs from and to files + +31.6 UCINET's DL file format +============================ + +* Menu: + +* igraph_read_graph_dl -- Reads a file in the DL format of UCINET.: igraph_read_graph_dl --- Reads a file in the DL format of UCINET_. + + +File: igraph-docs.info, Node: igraph_read_graph_dl --- Reads a file in the DL format of UCINET_, Up: UCINET's DL file format + +31.6.1 igraph_read_graph_dl -- Reads a file in the DL format of UCINET. +----------------------------------------------------------------------- + + + igraph_error_t igraph_read_graph_dl(igraph_t *graph, FILE *instream, + igraph_bool_t directed); + + This is a simple textual file format used by UCINET. See +http://www.analytictech.com/networks/dataentry.htm +(http://www.analytictech.com/networks/dataentry.htm) for examples. All +the forms described here are supported by igraph. Vertex names and edge +weights are also supported and they are added as attributes. (If an +attribute handler is attached.) + + Note the specification does not mention whether the format is case +sensitive or not. For igraph DL files are case sensitive, i.e. ‘Larry’ +and ‘larry’ are not the same. + + *Arguments:. * + +‘graph’: + Pointer to an uninitialized graph object. + +‘instream’: + The stream to read the DL file from. + +‘directed’: + Boolean, whether to create a directed file. + + *Returns:. * + +‘’ + Error code. + + Time complexity: linear in terms of the number of edges and vertices, +except for the matrix format, which is quadratic in the number of +vertices. + + * File examples/simple/igraph_read_graph_dl.c* + + +File: igraph-docs.info, Node: Graphviz format, Next: LEDA format, Prev: UCINET's DL file format, Up: Reading and writing graphs from and to files + +31.7 Graphviz format +==================== + +* Menu: + +* igraph_write_graph_dot -- Write the graph to a stream in DOT format.: igraph_write_graph_dot --- Write the graph to a stream in DOT format_. + + +File: igraph-docs.info, Node: igraph_write_graph_dot --- Write the graph to a stream in DOT format_, Up: Graphviz format + +31.7.1 igraph_write_graph_dot -- Write the graph to a stream in DOT format. +--------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_dot(const igraph_t *graph, FILE* outstream); + + DOT is the format used by the widely known GraphViz software, see +http://www.graphviz.org (http://www.graphviz.org) for details. The +grammar of the DOT format can be found here: +http://www.graphviz.org/doc/info/lang.html +(http://www.graphviz.org/doc/info/lang.html) + + This is only a preliminary implementation, no visualization +information is written. + + This format is meant solely for interoperability with Graphviz. It +is not recommended for data exchange or archival. + + *Arguments:. * + +‘graph’: + The graph to write to the stream. + +‘outstream’: + The stream to write the file to. + + *Returns:. * + +‘’ + Error code. + + Time complexity: should be proportional to the number of characters +written to the file. + + *See also:. * + +‘’ + ‘igraph_write_graph_graphml()’ (*note igraph_write_graph_graphml + --- Writes the graph to a file in GraphML format_::) for a more + modern format. + + * File examples/simple/dot.c* + + +File: igraph-docs.info, Node: LEDA format, Next: Convenience functions for locale change, Prev: Graphviz format, Up: Reading and writing graphs from and to files + +31.8 LEDA format +================ + +* Menu: + +* igraph_write_graph_leda -- Write a graph in LEDA native graph format.: igraph_write_graph_leda --- Write a graph in LEDA native graph format_. + + +File: igraph-docs.info, Node: igraph_write_graph_leda --- Write a graph in LEDA native graph format_, Up: LEDA format + +31.8.1 igraph_write_graph_leda -- Write a graph in LEDA native graph format. +---------------------------------------------------------------------------- + + + igraph_error_t igraph_write_graph_leda(const igraph_t *graph, FILE *outstream, + const char *vertex_attr_name, + const char *edge_attr_name); + + This function writes a graph to an output stream in LEDA format. See +http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html +(http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html) + + The support for the LEDA format is very basic at the moment; igraph +writes only the LEDA graph section which supports one selected vertex +and edge attribute and no layout information or visual attributes. + + *Arguments:. * + +‘graph’: + The graph to write to the stream. + +‘outstream’: + The stream. + +‘vertex_attr_name’: + The name of the vertex attribute whose values are to be stored in + the output, or ‘NULL’ if no vertex attribute should be stored. + +‘edge_attr_name’: + The name of the edge attribute whose values are to be stored in the + output, or ‘NULL’ if no edge attribute should be stored. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(|V|+|E|), the number of vertices and edges in the +graph. + + +File: igraph-docs.info, Node: Convenience functions for locale change, Prev: LEDA format, Up: Reading and writing graphs from and to files + +31.9 Convenience functions for locale change +============================================ + +* Menu: + +* igraph_enter_safelocale -- Temporarily set the C locale.: igraph_enter_safelocale --- Temporarily set the C locale_. +* igraph_exit_safelocale -- Temporarily set the C locale.: igraph_exit_safelocale --- Temporarily set the C locale_. + + +File: igraph-docs.info, Node: igraph_enter_safelocale --- Temporarily set the C locale_, Next: igraph_exit_safelocale --- Temporarily set the C locale_, Up: Convenience functions for locale change + +31.9.1 igraph_enter_safelocale -- Temporarily set the C locale. +--------------------------------------------------------------- + + + igraph_error_t igraph_enter_safelocale(igraph_safelocale_t *loc); + + igraph's foreign format readers and writers require a locale that +uses a decimal point instead of a decimal comma. This is a convenience +function that temporarily sets the C locale so that readers and writers +would work correctly. It _must_ be paired with a call to +‘igraph_exit_safelocale()’ (*note igraph_exit_safelocale --- Temporarily +set the C locale_::), otherwise a memory leak will occur. + + This function tries to set the locale for the current thread only on +a best-effort basis. Restricting the locale change to a single thread +is not supported on all platforms. In these cases, this function falls +back to using the standard ‘setlocale()’ function, which affects the +entire process and is not safe to use from concurrent threads. + + It is generally recommended to run igraph within a thread that has +been permanently set to the C locale using system-specific means. This +is a convenience function for situations when this is not easily +possible because the programmer is not in control of the process, such +as when developing plugins/extensions. Note that processes start up in +the C locale by default, thus nothing needs to be done unless the locale +has been changed away from the default. + + *Arguments:. * + +‘loc’: + Pointer to a variable of type ‘igraph_safelocale_t’. The current + locale will be stored here, so that it can be restored using + ‘igraph_exit_safelocale()’ (*note igraph_exit_safelocale --- + Temporarily set the C locale_::). + + *Returns:. * + +‘’ + Error code. + + * File examples/simple/safelocale.c* + + +File: igraph-docs.info, Node: igraph_exit_safelocale --- Temporarily set the C locale_, Prev: igraph_enter_safelocale --- Temporarily set the C locale_, Up: Convenience functions for locale change + +31.9.2 igraph_exit_safelocale -- Temporarily set the C locale. +-------------------------------------------------------------- + + + void igraph_exit_safelocale(igraph_safelocale_t *loc); + + Restores a locale saved by ‘igraph_enter_safelocale()’ (*note +igraph_enter_safelocale --- Temporarily set the C locale_::) and +deallocates all associated data. This function _must_ be paired with a +call to ‘igraph_enter_safelocale()’ (*note igraph_enter_safelocale --- +Temporarily set the C locale_::). + + *Arguments:. * + +‘loc’: + A variable of type ‘igraph_safelocale_t’, originally set by + ‘igraph_enter_safelocale()’ (*note igraph_enter_safelocale --- + Temporarily set the C locale_::). + + +File: igraph-docs.info, Node: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs, Next: Non-graph related functions, Prev: Reading and writing graphs from and to files, Up: Top + +32 Using BLAS, LAPACK and ARPACK for igraph matrices and graphs +*************************************************************** + +* Menu: + +* BLAS interface in igraph:: +* LAPACK interface in igraph:: +* ARPACK interface in igraph:: + + +File: igraph-docs.info, Node: BLAS interface in igraph, Next: LAPACK interface in igraph, Up: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs + +32.1 BLAS interface in igraph +============================= + +BLAS is a highly optimized library for basic linear algebra operations +such as vector-vector, matrix-vector and matrix-matrix product. Please +see http://www.netlib.org/blas/ (http://www.netlib.org/blas/) for +details and a reference implementation in Fortran. igraph contains some +wrapper functions that can be used to call BLAS routines in a somewhat +more user-friendly way. Not all BLAS routines are included in igraph, +and even those which are included might not have wrappers; the extension +of the set of wrapped functions will probably be driven by igraph's +internal requirements. The wrapper functions usually substitute +double-precision floating point arrays used by BLAS with +‘igraph_vector_t’ and ‘igraph_matrix_t’ instances and also remove those +parameters (such as the number of rows/columns) that can be inferred +from the passed arguments directly. + +* Menu: + +* igraph_blas_ddot -- Dot product of two vectors.: igraph_blas_ddot --- Dot product of two vectors_. +* igraph_blas_dnrm2 -- Euclidean norm of a vector.: igraph_blas_dnrm2 --- Euclidean norm of a vector_. +* igraph_blas_dgemv -- Matrix-vector multiplication using BLAS, vector version.: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_. +* igraph_blas_dgemm -- Matrix-matrix multiplication using BLAS.: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_. +* igraph_blas_dgemv_array -- Matrix-vector multiplication using BLAS, array version.: igraph_blas_dgemv_array --- Matrix-vector multiplication using BLAS; array version_. + + +File: igraph-docs.info, Node: igraph_blas_ddot --- Dot product of two vectors_, Next: igraph_blas_dnrm2 --- Euclidean norm of a vector_, Up: BLAS interface in igraph + +32.1.1 igraph_blas_ddot -- Dot product of two vectors. +------------------------------------------------------ + + + igraph_error_t igraph_blas_ddot(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *res); + + *Arguments:. * + +‘v1’: + The first vector. + +‘v2’: + The second vector. + +‘res’: + Pointer to a real, the result will be stored here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n) where n is the length of the vectors. + + * File examples/simple/blas.c* + + +File: igraph-docs.info, Node: igraph_blas_dnrm2 --- Euclidean norm of a vector_, Next: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_, Prev: igraph_blas_ddot --- Dot product of two vectors_, Up: BLAS interface in igraph + +32.1.2 igraph_blas_dnrm2 -- Euclidean norm of a vector. +------------------------------------------------------- + + + igraph_real_t igraph_blas_dnrm2(const igraph_vector_t *v); + + *Arguments:. * + +‘v’: + The vector. + + *Returns:. * + +‘’ + Real value, the norm of ‘v’. + + Time complexity: O(n) where n is the length of the vector. + + +File: igraph-docs.info, Node: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_, Next: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_, Prev: igraph_blas_dnrm2 --- Euclidean norm of a vector_, Up: BLAS interface in igraph + +32.1.3 igraph_blas_dgemv -- Matrix-vector multiplication using BLAS, vector version. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_blas_dgemv(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t *a, const igraph_vector_t *x, + igraph_real_t beta, igraph_vector_t *y); + + This function is a somewhat more user-friendly interface to the +‘dgemv’ function in BLAS. ‘dgemv’ performs the operation ‘y = alpha*A*x ++ beta*y’, where ‘x’ and ‘y’ are vectors and ‘A’ is an appropriately +sized matrix (symmetric or non-symmetric). + + *Arguments:. * + +‘transpose’: + Whether to transpose the matrix ‘A’. + +‘alpha’: + The constant ‘alpha’. + +‘a’: + The matrix ‘A’. + +‘x’: + The vector ‘x’. + +‘beta’: + The constant ‘beta’. + +‘y’: + The vector ‘y’ (which will be modified in-place). It must always + have the correct length, but its elements need not be set when + ‘beta=0’. + + Time complexity: O(nk) if the matrix is of size n x k + + *Returns:. * + +‘’ + ‘IGRAPH_EOVERFLOW’ if the matrix is too large for BLAS, + ‘IGRAPH_SUCCESS’ otherwise. + + *See also:. * + +‘’ + ‘igraph_blas_dgemv_array’ (*note igraph_blas_dgemv_array --- + Matrix-vector multiplication using BLAS; array version_::) if you + have arrays instead of vectors. + + * File examples/simple/blas.c* + + +File: igraph-docs.info, Node: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_, Next: igraph_blas_dgemv_array --- Matrix-vector multiplication using BLAS; array version_, Prev: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_, Up: BLAS interface in igraph + +32.1.4 igraph_blas_dgemm -- Matrix-matrix multiplication using BLAS. +-------------------------------------------------------------------- + + + igraph_error_t igraph_blas_dgemm(igraph_bool_t transpose_a, igraph_bool_t transpose_b, + igraph_real_t alpha, const igraph_matrix_t *a, const igraph_matrix_t *b, + igraph_real_t beta, igraph_matrix_t *c); + + This function is a somewhat more user-friendly interface to the +‘dgemm’ function in BLAS. ‘dgemm’ calculates alpha*a*b + beta*c, where +a, b and c are matrices, of which a and b can be transposed. + + *Arguments:. * + +‘transpose_a’: + whether to transpose the matrix ‘a’ + +‘transpose_b’: + whether to transpose the matrix ‘b’ + +‘alpha’: + the constant ‘alpha’ + +‘a’: + the matrix ‘a’ + +‘b’: + the matrix ‘b’ + +‘beta’: + the constant ‘beta’ + +‘c’: + the matrix ‘c’. The result will also be stored here. If beta is + zero, c will be resized to fit the result. + + Time complexity: O(n m k) where matrix a is of size n × k, and matrix +b is of size k × m. + + *Returns:. * + +‘’ + ‘IGRAPH_EOVERFLOW’ if the matrix is too large for BLAS, + ‘IGRAPH_EINVAL’ if the matrices have incompatible sizes, + ‘IGRAPH_SUCCESS’ otherwise. + + * File examples/simple/blas_dgemm.c* + + +File: igraph-docs.info, Node: igraph_blas_dgemv_array --- Matrix-vector multiplication using BLAS; array version_, Prev: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_, Up: BLAS interface in igraph + +32.1.5 igraph_blas_dgemv_array -- Matrix-vector multiplication using BLAS, array version. +----------------------------------------------------------------------------------------- + + + igraph_error_t igraph_blas_dgemv_array(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_real_t* x, + igraph_real_t beta, igraph_real_t* y); + + This function is a somewhat more user-friendly interface to the +‘dgemv’ function in BLAS. ‘dgemv’ performs the operation y = alpha*A*x + +beta*y, where x and y are vectors and A is an appropriately sized matrix +(symmetric or non-symmetric). + + *Arguments:. * + +‘transpose’: + whether to transpose the matrix ‘A’ + +‘alpha’: + the constant ‘alpha’ + +‘a’: + the matrix ‘A’ + +‘x’: + the vector ‘x’ as a regular C array + +‘beta’: + the constant ‘beta’ + +‘y’: + the vector ‘y’ as a regular C array (which will be modified + in-place) + + Time complexity: O(nk) if the matrix is of size n x k + + *Returns:. * + +‘’ + ‘IGRAPH_EOVERFLOW’ if the matrix is too large for BLAS, + ‘IGRAPH_SUCCESS’ otherwise. + + *See also:. * + +‘’ + ‘igraph_blas_dgemv’ (*note igraph_blas_dgemv --- Matrix-vector + multiplication using BLAS; vector version_::) if you have vectors + instead of arrays. + + +File: igraph-docs.info, Node: LAPACK interface in igraph, Next: ARPACK interface in igraph, Prev: BLAS interface in igraph, Up: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs + +32.2 LAPACK interface in igraph +=============================== + +LAPACK is written in Fortran90 and provides routines for solving systems +of simultaneous linear equations, least-squares solutions of linear +systems of equations, eigenvalue problems, and singular value problems. +The associated matrix factorizations (LU, Cholesky, QR, SVD, Schur, +generalized Schur) are also provided, as are related computations such +as reordering of the Schur factorizations and estimating condition +numbers. Dense and banded matrices are handled, but not general sparse +matrices. In all areas, similar functionality is provided for real and +complex matrices, in both single and double precision. + + igraph provides an interface to a very limited set of LAPACK +functions, using the regular igraph data structures. + + See more about LAPACK at http://www.netlib.org/lapack/ +(http://www.netlib.org/lapack/) + +* Menu: + +* Matrix factorization, solving linear systems: Matrix factorization; solving linear systems. +* Eigenvalues and eigenvectors of matrices:: + + +File: igraph-docs.info, Node: Matrix factorization; solving linear systems, Next: Eigenvalues and eigenvectors of matrices, Up: LAPACK interface in igraph + +32.2.1 Matrix factorization, solving linear systems +--------------------------------------------------- + +* Menu: + +* igraph_lapack_dgetrf -- LU factorization of a general M-by-N matrix.: igraph_lapack_dgetrf --- LU factorization of a general M-by-N matrix_. +* igraph_lapack_dgetrs -- Solve general system of linear equations using LU factorization.: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_. +* igraph_lapack_dgesv -- Solve system of linear equations with LU factorization.: igraph_lapack_dgesv --- Solve system of linear equations with LU factorization_. + + +File: igraph-docs.info, Node: igraph_lapack_dgetrf --- LU factorization of a general M-by-N matrix_, Next: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_, Up: Matrix factorization; solving linear systems + +32.2.1.1 igraph_lapack_dgetrf -- LU factorization of a general M-by-N matrix. +............................................................................. + + + igraph_error_t igraph_lapack_dgetrf(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + int *info); + + The factorization has the form A = P * L * U where P is a permutation +matrix, L is lower triangular with unit diagonal elements (lower +trapezoidal if m > n), and U is upper triangular (upper trapezoidal if m +< n). + + *Arguments:. * + +‘a’: + The input/output matrix. On entry, the M-by-N matrix to be + factored. On exit, the factors L and U from the factorization A = + P * L * U; the unit diagonal elements of L are not stored. + +‘ipiv’: + An integer vector, the pivot indices are stored here, unless it is + a null pointer. Row ‘i’ of the matrix was interchanged with row + ‘ipiv[i]’. + +‘info’: + LAPACK error code. Zero on successful exit. If its value is a + positive number i, it indicates that U(i,i) is exactly zero. The + factorization has been completed, but the factor U is exactly + singular, and division by zero will occur if it is used to solve a + system of equations. If LAPACK returns an error, i.e. a negative + info value, then an igraph error is generated as well. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_, Next: igraph_lapack_dgesv --- Solve system of linear equations with LU factorization_, Prev: igraph_lapack_dgetrf --- LU factorization of a general M-by-N matrix_, Up: Matrix factorization; solving linear systems + +32.2.1.2 igraph_lapack_dgetrs -- Solve general system of linear equations using LU factorization. +................................................................................................. + + + igraph_error_t igraph_lapack_dgetrs(igraph_bool_t transpose, const igraph_matrix_t *a, + const igraph_vector_int_t *ipiv, igraph_matrix_t *b); + + This function calls LAPACK to solve a system of linear equations A * +X = B or A' * X = B with a general N-by-N matrix A using the LU +factorization computed by ‘igraph_lapack_dgetrf’ (*note +igraph_lapack_dgetrf --- LU factorization of a general M-by-N +matrix_::). + + *Arguments:. * + +‘transpose’: + Boolean, whether to transpose the input matrix. + +‘a’: + A matrix containing the L and U factors from the factorization A = + P*L*U. L is expected to be unitriangular, diagonal entries are + those of U. If A is singular, no warning or error wil be given and + random output will be returned. + +‘ipiv’: + An integer vector, the pivot indices from ‘igraph_lapack_dgetrf()’ + (*note igraph_lapack_dgetrf --- LU factorization of a general + M-by-N matrix_::) must be given here. Row ‘i’ of A was + interchanged with row ‘ipiv[i]’. + +‘b’: + The right hand side matrix must be given here. The solution will + also be placed here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + +File: igraph-docs.info, Node: igraph_lapack_dgesv --- Solve system of linear equations with LU factorization_, Prev: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_, Up: Matrix factorization; solving linear systems + +32.2.1.3 igraph_lapack_dgesv -- Solve system of linear equations with LU factorization. +....................................................................................... + + + igraph_error_t igraph_lapack_dgesv(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + igraph_matrix_t *b, int *info); + + This function computes the solution to a real system of linear +equations A * X = B, where A is an N-by-N matrix and X and B are +N-by-NRHS matrices. + + The LU decomposition with partial pivoting and row interchanges is +used to factor A as A = P * L * U, where P is a permutation matrix, L is +unit lower triangular, and U is upper triangular. The factored form of +A is then used to solve the system of equations A * X = B. + + *Arguments:. * + +‘a’: + Matrix. On entry the N-by-N coefficient matrix, on exit, the + factors L and U from the factorization A=P*L*U; the unit diagonal + elements of L are not stored. + +‘ipiv’: + An integer vector or a null pointer. If not a null pointer, then + the pivot indices that define the permutation matrix P, are stored + here. Row i of the matrix was interchanged with row IPIV(i). + +‘b’: + Matrix, on entry the right hand side matrix should be stored here. + On exit, if there was no error, and the info argument is zero, then + it contains the solution matrix X. + +‘info’: + The LAPACK info code. If it is positive, then U(info,info) is + exactly zero. In this case the factorization has been completed, + but the factor U is exactly singular, so the solution could not be + computed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/igraph_lapack_dgesv.c* + + +File: igraph-docs.info, Node: Eigenvalues and eigenvectors of matrices, Prev: Matrix factorization; solving linear systems, Up: LAPACK interface in igraph + +32.2.2 Eigenvalues and eigenvectors of matrices +----------------------------------------------- + +* Menu: + +* igraph_lapack_dsyevr -- Selected eigenvalues and optionally eigenvectors of a symmetric matrix.: igraph_lapack_dsyevr --- Selected eigenvalues and optionally eigenvectors of a symmetric matrix_. +* igraph_lapack_dgeev -- Eigenvalues and optionally eigenvectors of a non-symmetric matrix.: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_. +* igraph_lapack_dgeevx -- Eigenvalues/vectors of nonsymmetric matrices, expert mode.: igraph_lapack_dgeevx --- Eigenvalues/vectors of nonsymmetric matrices; expert mode_. + + +File: igraph-docs.info, Node: igraph_lapack_dsyevr --- Selected eigenvalues and optionally eigenvectors of a symmetric matrix_, Next: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_, Up: Eigenvalues and eigenvectors of matrices + +32.2.2.1 igraph_lapack_dsyevr -- Selected eigenvalues and optionally eigenvectors of a symmetric matrix. +........................................................................................................ + + + igraph_error_t igraph_lapack_dsyevr(const igraph_matrix_t *A, + igraph_lapack_dsyev_which_t which, + igraph_real_t vl, igraph_real_t vu, int vestimate, + int il, int iu, igraph_real_t abstol, + igraph_vector_t *values, igraph_matrix_t *vectors, + igraph_vector_int_t *support); + + Calls the DSYEVR LAPACK function to compute selected eigenvalues and, +optionally, eigenvectors of a real symmetric matrix ‘A’. Eigenvalues +and eigenvectors can be selected by specifying either a range of values +or a range of indices for the desired eigenvalues. + + See more in the LAPACK documentation. + + *Arguments:. * + +‘A’: + Matrix, on entry it contains the symmetric input matrix. Only the + leading N-by-N upper triangular part is used for the computation. + +‘which’: + Constant that gives which eigenvalues (and possibly the + corresponding eigenvectors) to calculate. Possible values are + ‘IGRAPH_LAPACK_DSYEV_ALL’, all eigenvalues; + ‘IGRAPH_LAPACK_DSYEV_INTERVAL’, all eigenvalues in the half-open + interval ‘(vl, vu]’; ‘IGRAPH_LAPACK_DSYEV_SELECT’, the il-th + through iu-th eigenvalues. + +‘vl’: + If ‘which’ is ‘IGRAPH_LAPACK_DSYEV_INTERVAL’, then this is the + lower bound of the interval to be searched for eigenvalues. See + also the ‘vestimate’ argument. + +‘vu’: + If ‘which’ is ‘IGRAPH_LAPACK_DSYEV_INTERVAL’, then this is the + upper bound of the interval to be searched for eigenvalues. See + also the ‘vestimate’ argument. + +‘vestimate’: + An upper bound for the number of eigenvalues in the ‘(vl, vu]’ + interval, if ‘which’ is ‘IGRAPH_LAPACK_DSYEV_INTERVAL’. Memory is + allocated only for the given number of eigenvalues (and + eigenvectors), so this upper bound must be correct. + +‘il’: + The index of the smallest eigenvalue to return, if ‘which’ is + ‘IGRAPH_LAPACK_DSYEV_SELECT’. + +‘iu’: + The index of the largets eigenvalue to return, if ‘which’ is + ‘IGRAPH_LAPACK_DSYEV_SELECT’. + +‘abstol’: + The absolute error tolerance for the eigevalues. An approximate + eigenvalue is accepted as converged when it is determined to lie in + an interval ‘[a,b]’ of width less than or equal to ‘abstol + EPS * + max(|a|,|b|)’, where ‘EPS’ is the machine precision. + +‘values’: + An initialized vector, the eigenvalues are stored here, unless it + is a null pointer. It will be resized as needed. + +‘vectors’: + An initialized matrix. A set of orthonormal eigenvectors are + stored in its columns, unless it is a null pointer. It will be + resized as needed. + +‘support’: + An integer vector. If not a null pointer, then it will be resized + to (2*max(1,M)) (M is a the total number of eigenvalues found). + Then the support of the eigenvectors in ‘vectors’ is stored here, + i.e., the indices indicating the nonzero elements in ‘vectors’. + The i-th eigenvector is nonzero only in elements support(2*i-1) + through support(2*i). + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/igraph_lapack_dsyevr.c* + + +File: igraph-docs.info, Node: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_, Next: igraph_lapack_dgeevx --- Eigenvalues/vectors of nonsymmetric matrices; expert mode_, Prev: igraph_lapack_dsyevr --- Selected eigenvalues and optionally eigenvectors of a symmetric matrix_, Up: Eigenvalues and eigenvectors of matrices + +32.2.2.2 igraph_lapack_dgeev -- Eigenvalues and optionally eigenvectors of a non-symmetric matrix. +.................................................................................................. + + + igraph_error_t igraph_lapack_dgeev(const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *info); + + This function calls LAPACK to compute, for an N-by-N real +nonsymmetric matrix A, the eigenvalues and, optionally, the left and/or +right eigenvectors. + + The right eigenvector v(j) of A satisfies A * v(j) = lambda(j) * v(j) +where lambda(j) is its eigenvalue. The left eigenvector u(j) of A +satisfies u(j)^H * A = lambda(j) * u(j)^H where u(j)^H denotes the +conjugate transpose of u(j). + + The computed eigenvectors are normalized to have Euclidean norm equal +to 1 and largest component real. + + *Arguments:. * + +‘A’: + matrix. On entry it contains the N-by-N input matrix. + +‘valuesreal’: + Pointer to an initialized vector, or a null pointer. If not a null + pointer, then the real parts of the eigenvalues are stored here. + The vector will be resized as needed. + +‘valuesimag’: + Pointer to an initialized vector, or a null pointer. If not a null + pointer, then the imaginary parts of the eigenvalues are stored + here. The vector will be resized as needed. + +‘vectorsleft’: + Pointer to an initialized matrix, or a null pointer. If not a null + pointer, then the left eigenvectors are stored in the columns of + the matrix. The matrix will be resized as needed. + +‘vectorsright’: + Pointer to an initialized matrix, or a null pointer. If not a null + pointer, then the right eigenvectors are stored in the columns of + the matrix. The matrix will be resized as needed. + +‘info’: + This argument is used for two purposes. As an input argument it + gives whether an igraph error should be generated if the QR + algorithm fails to compute all eigenvalues. If ‘info’ is non-zero, + then an error is generated, otherwise only a warning is given. On + exit it contains the LAPACK error code. Zero means successful + exit. A negative values means that some of the arguments had an + illegal value, this always triggers an igraph error. An i positive + value means that the QR algorithm failed to compute all the + eigenvalues, and no eigenvectors have been computed; element i+1:N + of ‘valuesreal’ and ‘valuesimag’ contain eigenvalues which have + converged. This case only generates an igraph error, if ‘info’ was + non-zero on entry. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO. + + * File examples/simple/igraph_lapack_dgeev.c* + + +File: igraph-docs.info, Node: igraph_lapack_dgeevx --- Eigenvalues/vectors of nonsymmetric matrices; expert mode_, Prev: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_, Up: Eigenvalues and eigenvectors of matrices + +32.2.2.3 igraph_lapack_dgeevx -- Eigenvalues/vectors of nonsymmetric matrices, expert mode. +........................................................................................... + + + igraph_error_t igraph_lapack_dgeevx(igraph_lapack_dgeevx_balance_t balance, + const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *ilo, int *ihi, igraph_vector_t *scale, + igraph_real_t *abnrm, + igraph_vector_t *rconde, + igraph_vector_t *rcondv, + int *info); + + This function calculates the eigenvalues and optionally the left +and/or right eigenvectors of a nonsymmetric N-by-N real matrix. + + Optionally also, it computes a balancing transformation to improve +the conditioning of the eigenvalues and eigenvectors (‘ilo’, ‘ihi’, +‘scale’, and ‘abnrm’), reciprocal condition numbers for the eigenvalues +(‘rconde’), and reciprocal condition numbers for the right eigenvectors +(‘rcondv’). + + The right eigenvector v(j) of A satisfies A * v(j) = lambda(j) * v(j) +where lambda(j) is its eigenvalue. The left eigenvector u(j) of A +satisfies u(j)^H * A = lambda(j) * u(j)^H where u(j)^H denotes the +conjugate transpose of u(j). + + The computed eigenvectors are normalized to have Euclidean norm equal +to 1 and largest component real. + + Balancing a matrix means permuting the rows and columns to make it +more nearly upper triangular, and applying a diagonal similarity +transformation D * A * D^(-1), where D is a diagonal matrix, to make its +rows and columns closer in norm and the condition numbers of its +eigenvalues and eigenvectors smaller. The computed reciprocal condition +numbers correspond to the balanced matrix. Permuting rows and columns +will not change the condition numbers (in exact arithmetic) but diagonal +scaling will. For further explanation of balancing, see section 4.10.2 +of the LAPACK Users' Guide. Note that the eigenvectors obtained for the +balanced matrix are backtransformed to those of ‘A’. + + *Arguments:. * + +‘balance’: + Indicates whether the input matrix should be balanced. Possible + values: + + ‘IGRAPH_LAPACK_DGEEVX_BALANCE_NONE’ + no not diagonally scale or permute. + + ‘IGRAPH_LAPACK_DGEEVX_BALANCE_PERM’ + perform permutations to make the matrix more nearly upper + triangular. Do not diagonally scale. + + ‘IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE’ + diagonally scale the matrix, i.e. replace A by D*A*D^(-1), + where D is a diagonal matrix, chosen to make the rows and + columns of A more equal in norm. Do not permute. + + ‘IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH’ + both diagonally scale and permute A. + +‘A’: + The input matrix, must be square. + +‘valuesreal’: + An initialized vector, or a ‘NULL’ pointer. If not a ‘NULL’ + pointer, then the real parts of the eigenvalues are stored here. + The vector will be resized, as needed. + +‘valuesimag’: + An initialized vector, or a ‘NULL’ pointer. If not a ‘NULL’ + pointer, then the imaginary parts of the eigenvalues are stored + here. The vector will be resized, as needed. + +‘vectorsleft’: + An initialized matrix or a ‘NULL’ pointer. If not a null pointer, + then the left eigenvectors are stored here. The order corresponds + to the eigenvalues and the eigenvectors are stored in a compressed + form. If the j-th eigenvalue is real then column j contains the + corresponding eigenvector. If the j-th and (j+1)-th eigenvalues + form a complex conjugate pair, then the j-th and (j+1)-th columns + contain the real and imaginary parts of the corresponding + eigenvectors. + +‘vectorsright’: + An initialized matrix or a ‘NULL’ pointer. If not a null pointer, + then the right eigenvectors are stored here. The format is the + same, as for the ‘vectorsleft’ argument. + +‘ilo’: + +‘ihi’: + if not NULL, ‘ilo’ and ‘ihi’ point to integer values determined + when A was balanced. The balanced A(i,j) = 0 if I>J and + J=1,...,ilo-1 or I=ihi+1,...,N. + +‘scale’: + Pointer to an initialized vector or a NULL pointer. If not a NULL + pointer, then details of the permutations and scaling factors + applied when balancing ‘A’, are stored here. If P(j) is the index + of the row and column interchanged with row and column j, and D(j) + is the scaling factor applied to row and column j, then + + ‘scale(J) = P(J), for J = 1,...,ilo-1’ + + ‘scale(J) = D(J), for J = ilo,...,ihi’ + + ‘scale(J) = P(J) for J = ihi+1,...,N.’ + + The order in which the interchanges are made is N to ‘ihi’+1, then + 1 to ‘ilo’-1. + +‘abnrm’: + Pointer to a real variable, the one-norm of the balanced matrix is + stored here. (The one-norm is the maximum of the sum of absolute + values of elements in any column.) + +‘rconde’: + An initialized vector or a NULL pointer. If not a null pointer, + then the reciprocal condition numbers of the eigenvalues are stored + here. + +‘rcondv’: + An initialized vector or a NULL pointer. If not a null pointer, + then the reciprocal condition numbers of the right eigenvectors are + stored here. + +‘info’: + This argument is used for two purposes. As an input argument it + gives whether an igraph error should be generated if the QR + algorithm fails to compute all eigenvalues. If ‘info’ is non-zero, + then an error is generated, otherwise only a warning is given. On + exit it contains the LAPACK error code. Zero means successful + exit. A negative values means that some of the arguments had an + illegal value, this always triggers an igraph error. An i positive + value means that the QR algorithm failed to compute all the + eigenvalues, and no eigenvectors have been computed; element i+1:N + of ‘valuesreal’ and ‘valuesimag’ contain eigenvalues which have + converged. This case only generated an igraph error, if ‘info’ was + non-zero on entry. + + *Returns:. * + +‘’ + Error code. + + Time complexity: TODO + + * File examples/simple/igraph_lapack_dgeevx.c* + + +File: igraph-docs.info, Node: ARPACK interface in igraph, Prev: LAPACK interface in igraph, Up: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs + +32.3 ARPACK interface in igraph +=============================== + +ARPACK is a library for solving large scale eigenvalue problems. The +package is designed to compute a few eigenvalues and corresponding +eigenvectors of a general ‘n’ by ‘n’ matrix ‘A’. It is most appropriate +for large sparse or structured matrices ‘A’ where structured means that +a matrix-vector product ‘w <- Av’ requires order ‘n’ rather than the +usual order ‘n^2’ floating point operations. Please see +https://github.com/opencollab/arpack-ng +(https://github.com/opencollab/arpack-ng) for details. + + The eigenvalue calculation in ARPACK (in the simplest case) involves +the calculation of the ‘Av’ product where ‘A’ is the matrix we work with +and ‘v’ is an arbitrary vector. A user-defined function of type +‘igraph_arpack_function_t’ (*note igraph_arpack_function_t --- Type of +the ARPACK callback function_::) is expected to perform this product. +If the product can be done efficiently, e.g. if the matrix is sparse, +then ARPACK is usually able to calculate the eigenvalues very quickly. + + In igraph, eigenvalue/eigenvector calculations usually involve the +following steps: + + 1. Initialization of an ‘igraph_arpack_options_t’ (*note + igraph_arpack_options_t --- Options for ARPACK_::) data structure + using ‘igraph_arpack_options_init’ (*note + igraph_arpack_options_init --- Initialize ARPACK options_::). + + 2. Setting some options in the initialized ‘igraph_arpack_options_t’ + (*note igraph_arpack_options_t --- Options for ARPACK_::) object. + + 3. Defining a function of type ‘igraph_arpack_function_t’ (*note + igraph_arpack_function_t --- Type of the ARPACK callback + function_::). The input of this function is a vector, and the + output should be the output matrix multiplied by the input vector. + + 4. Calling ‘igraph_arpack_rssolve()’ (*note igraph_arpack_rssolve --- + ARPACK solver for symmetric matrices_::) (is the matrix is + symmetric), or ‘igraph_arpack_rnsolve()’ (*note + igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::). + +The ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- Options +for ARPACK_::) object can be used multiple times. + + If we have many eigenvalue problems to solve, then it might worth to +create an ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- +Storage for ARPACK_::) object, and initialize it via +‘igraph_arpack_storage_init()’ (*note igraph_arpack_storage_init --- +Initialize ARPACK storage_::). This structure contains all memory +needed for ARPACK (with the given upper limit regerding to the size of +the eigenvalue problem). Then many problems can be solved using the +same ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- +Storage for ARPACK_::) object, without always reallocating the required +memory. The ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t +--- Storage for ARPACK_::) object needs to be destroyed by calling +‘igraph_arpack_storage_destroy()’ (*note igraph_arpack_storage_destroy +--- Deallocate ARPACK storage_::) on it, when it is not needed any more. + + igraph does not contain all ARPACK routines, only the ones dealing +with symmetric and non-symmetric eigenvalue problems using double +precision real numbers. + +* Menu: + +* Data structures:: +* ARPACK solvers:: + + +File: igraph-docs.info, Node: Data structures, Next: ARPACK solvers, Up: ARPACK interface in igraph + +32.3.1 Data structures +---------------------- + +* Menu: + +* igraph_arpack_options_t -- Options for ARPACK.: igraph_arpack_options_t --- Options for ARPACK_. +* igraph_arpack_storage_t -- Storage for ARPACK.: igraph_arpack_storage_t --- Storage for ARPACK_. +* igraph_arpack_function_t -- Type of the ARPACK callback function.: igraph_arpack_function_t --- Type of the ARPACK callback function_. +* igraph_arpack_options_init -- Initialize ARPACK options.: igraph_arpack_options_init --- Initialize ARPACK options_. +* igraph_arpack_storage_init -- Initialize ARPACK storage.: igraph_arpack_storage_init --- Initialize ARPACK storage_. +* igraph_arpack_storage_destroy -- Deallocate ARPACK storage.: igraph_arpack_storage_destroy --- Deallocate ARPACK storage_. + + +File: igraph-docs.info, Node: igraph_arpack_options_t --- Options for ARPACK_, Next: igraph_arpack_storage_t --- Storage for ARPACK_, Up: Data structures + +32.3.1.1 igraph_arpack_options_t -- Options for ARPACK. +....................................................... + + + typedef struct igraph_arpack_options_t { + /* INPUT */ + char bmat[1]; /* I-standard problem, G-generalized */ + int n; /* Dimension of the eigenproblem */ + char which[2]; /* LA, SA, LM, SM, BE */ + int nev; /* Number of eigenvalues to be computed */ + igraph_real_t tol; /* Stopping criterion */ + int ncv; /* Number of columns in V */ + int ldv; /* Leading dimension of V */ + int ishift; /* 0-reverse comm., 1-exact with tridiagonal */ + int mxiter; /* Maximum number of update iterations to take */ + int nb; /* Block size on the recurrence, only 1 works */ + int mode; /* The kind of problem to be solved (1-5) + 1: A*x=l*x, A symmetric + 2: A*x=l*M*x, A symm. M pos. def. + 3: K*x = l*M*x, K symm., M pos. semidef. + 4: K*x = l*KG*x, K s. pos. semidef. KG s. indef. + 5: A*x = l*M*x, A symm., M symm. pos. semidef. */ + int start; /* 0: random, 1: use the supplied vector */ + int lworkl; /* Size of temporary storage, default is fine */ + igraph_real_t sigma; /* The shift for modes 3,4,5 */ + igraph_real_t sigmai; /* The imaginary part of shift for rnsolve */ + /* OUTPUT */ + int info; /* What happened, see docs */ + int ierr; /* What happened in the dseupd call */ + int noiter; /* The number of iterations taken */ + int nconv; + int numop; /* Number of OP*x operations */ + int numopb; /* Number of B*x operations if BMAT='G' */ + int numreo; /* Number of steps of re-orthogonalizations */ + /* INTERNAL */ + int iparam[11]; + int ipntr[14]; + } igraph_arpack_options_t; + + This data structure contains the options of the ARPACK eigenvalue +solver routines. It must be initialized by calling +‘igraph_arpack_options_init()’ (*note igraph_arpack_options_init --- +Initialize ARPACK options_::) on it. Then it can be used for multiple +ARPACK calls, as the ARPACK solvers do not modify it. Input options: + + *Values:. * + +‘bmat’: + Character. Whether to solve a standard ('I') ot a generalized + problem ('B'). + +‘n’: + Dimension of the eigenproblem. + +‘which’: + Specifies which eigenvalues/vectors to compute. Possible values + for symmetric matrices: + + ‘LA’ + Compute ‘nev’ largest (algebraic) eigenvalues. + + ‘SA’ + Compute ‘nev’ smallest (algebraic) eigenvalues. + + ‘LM’ + Compute ‘nev’ largest (in magnitude) eigenvalues. + + ‘SM’ + Compute ‘nev’ smallest (in magnitude) eigenvalues. + + ‘BE’ + Compute ‘nev’ eigenvalues, half from each end of the spectrum. + When ‘nev’ is odd, compute one more from the high en than from + the low end. + + Possible values for non-symmetric matrices: + + ‘LM’ + Compute ‘nev’ largest (in magnitude) eigenvalues. + + ‘SM’ + Compute ‘nev’ smallest (in magnitude) eigenvalues. + + ‘LR’ + Compute ‘nev’ eigenvalues of largest real part. + + ‘SR’ + Compute ‘nev’ eigenvalues of smallest real part. + + ‘LI’ + Compute ‘nev’ eigenvalues of largest imaginary part. + + ‘SI’ + Compute ‘nev’ eigenvalues of smallest imaginary part. + +‘nev’: + The number of eigenvalues to be computed. + +‘tol’: + Stopping criterion: the relative accuracy of the Ritz value is + considered acceptable if its error is less than ‘tol’ times its + estimated value. If this is set to zero then machine precision is + used. + +‘ncv’: + Number of Lanczos vectors to be generated. Setting this to zero + means that ‘igraph_arpack_rssolve’ (*note igraph_arpack_rssolve --- + ARPACK solver for symmetric matrices_::) and + ‘igraph_arpack_rnsolve’ (*note igraph_arpack_rnsolve --- ARPACK + solver for non-symmetric matrices_::) will determine a suitable + value for ‘ncv’ automatically. + +‘ldv’: + Numberic scalar. It should be set to zero in the current igraph + implementation. + +‘ishift’: + Either zero or one. If zero then the shifts are provided by the + user via reverse communication. If one then exact shifts with + respect to the reduced tridiagonal matrix ‘T’. Please always set + this to one. + +‘mxiter’: + Maximum number of Arnoldi update iterations allowed. + +‘nb’: + Blocksize to be used in the recurrence. Please always leave this + on the default value, one. + +‘mode’: + The type of the eigenproblem to be solved. Possible values if the + input matrix is symmetric: + + 1. A*x=lambda*x, A is symmetric. + + 2. A*x=lambda*M*x, A is symmetric, M is symmetric positive + definite. + + 3. K*x=lambda*M*x, K is symmetric, M is symmetric positive + semi-definite. + + 4. K*x=lambda*KG*x, K is symmetric positive semi-definite, KG is + symmetric indefinite. + + 5. A*x=lambda*M*x, A is symmetric, M is symmetric positive + semi-definite. (Cayley transformed mode.) + + Please note that only ‘mode’ ==1 was tested and other values might + not work properly. Possible values if the input matrix is not + symmetric: + + 1. A*x=lambda*x. + + 2. A*x=lambda*M*x, M is symmetric positive definite. + + 3. A*x=lambda*M*x, M is symmetric semi-definite. + + 4. A*x=lambda*M*x, M is symmetric semi-definite. + + Please note that only ‘mode’ == 1 was tested and other values might + not work properly. + +‘start’: + Whether to use the supplied starting vector (1), or use a random + starting vector (0). The starting vector must be supplied in the + first column of the ‘vectors’ argument of the + ‘igraph_arpack_rssolve()’ (*note igraph_arpack_rssolve --- ARPACK + solver for symmetric matrices_::) of ‘igraph_arpack_rnsolve()’ + (*note igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::) call. + + Output options: + + *Values:. * + +‘info’: + Error flag of ARPACK. Possible values: + + ‘0’ + Normal exit. + + ‘1’ + Maximum number of iterations taken. + + ‘3’ + No shifts could be applied during a cycle of the Implicitly + restarted Arnoldi iteration. One possibility is to increase + the size of ‘ncv’ relative to ‘nev’. + + ARPACK can return other error flags as well, but these are + converted to igraph errors, see ‘igraph_error_type_t’ (*note + igraph_error_type_t --- Error code type_::). + +‘ierr’: + Error flag of the second ARPACK call (one eigenvalue computation + usually involves two calls to ARPACK). This is always zero, as + other error codes are converted to igraph errors. + +‘noiter’: + Number of Arnoldi iterations taken. + +‘nconv’: + Number of converged Ritz values. This represents the number of + Ritz values that satisfy the convergence critetion. + +‘numop’: + Total number of matrix-vector multiplications. + +‘numopb’: + Not used currently. + +‘numreo’: + Total number of steps of re-orthogonalization. + + Internal options: + + *Values:. * + +‘lworkl’: + Do not modify this option. + +‘sigma’: + The shift for the shift-invert mode. + +‘sigmai’: + The imaginary part of the shift, for the non-symmetric or complex + shift-invert mode. + +‘iparam’: + Do not modify this option. + +‘ipntr’: + Do not modify this option. + + +File: igraph-docs.info, Node: igraph_arpack_storage_t --- Storage for ARPACK_, Next: igraph_arpack_function_t --- Type of the ARPACK callback function_, Prev: igraph_arpack_options_t --- Options for ARPACK_, Up: Data structures + +32.3.1.2 igraph_arpack_storage_t -- Storage for ARPACK. +....................................................... + + + typedef struct igraph_arpack_storage_t { + int maxn, maxncv, maxldv; + igraph_real_t *v; + igraph_real_t *workl; + igraph_real_t *workd; + igraph_real_t *d; + igraph_real_t *resid; + igraph_real_t *ax; + int *select; + /* The following two are only used for non-symmetric problems: */ + igraph_real_t *di; + igraph_real_t *workev; + } igraph_arpack_storage_t; + + Public members, do not modify them directly, these are considered to +be read-only. + + *Values:. * + +‘maxn’: + Maximum rank of matrix. + +‘maxncv’: + Maximum NCV. + +‘maxldv’: + Maximum LDV. + + These members are considered to be private: + + *Values:. * + +‘workl’: + Working memory. + +‘workd’: + Working memory. + +‘d’: + Memory for eigenvalues. + +‘resid’: + Memory for residuals. + +‘ax’: + Working memory. + +‘select’: + Working memory. + +‘di’: + Memory for eigenvalues, non-symmetric case only. + +‘workev’: + Working memory, non-symmetric case only. + + +File: igraph-docs.info, Node: igraph_arpack_function_t --- Type of the ARPACK callback function_, Next: igraph_arpack_options_init --- Initialize ARPACK options_, Prev: igraph_arpack_storage_t --- Storage for ARPACK_, Up: Data structures + +32.3.1.3 igraph_arpack_function_t -- Type of the ARPACK callback function. +.......................................................................... + + + typedef igraph_error_t igraph_arpack_function_t(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra); + + *Arguments:. * + +‘to’: + Pointer to an ‘igraph_real_t’, the result of the matrix-vector + product is expected to be stored here. + +‘from’: + Pointer to an ‘igraph_real_t’, the input matrix should be + multiplied by the vector stored here. + +‘n’: + The length of the vector (which is the same as the order of the + input matrix). + +‘extra’: + Extra argument to the matrix-vector calculation function. This is + coming from the ‘igraph_arpack_rssolve()’ (*note + igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::) + or ‘igraph_arpack_rnsolve()’ (*note igraph_arpack_rnsolve --- + ARPACK solver for non-symmetric matrices_::) function. + + *Returns:. * + +‘’ + Error code. If not ‘IGRAPH_SUCCESS’, then the ARPACK solver + considers this as an error, stops and calls the igraph error + handler. + + +File: igraph-docs.info, Node: igraph_arpack_options_init --- Initialize ARPACK options_, Next: igraph_arpack_storage_init --- Initialize ARPACK storage_, Prev: igraph_arpack_function_t --- Type of the ARPACK callback function_, Up: Data structures + +32.3.1.4 igraph_arpack_options_init -- Initialize ARPACK options. +................................................................. + + + void igraph_arpack_options_init(igraph_arpack_options_t *o); + + Initializes ARPACK options, set them to default values. You can +always pass the initialized ‘igraph_arpack_options_t’ (*note +igraph_arpack_options_t --- Options for ARPACK_::) object to built-in +igraph functions without any modification. The built-in igraph +functions modify the options to perform their calculation, e.g. +‘igraph_pagerank()’ (*note igraph_pagerank --- Calculates the Google +PageRank for the specified vertices_::) always searches for the +eigenvalue with the largest magnitude, regardless of the supplied value. + + If you want to implement your own function involving eigenvalue +calculation using ARPACK, however, you will likely need to set up the +fields for yourself. + + *Arguments:. * + +‘o’: + The ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- + Options for ARPACK_::) object to initialize. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_arpack_storage_init --- Initialize ARPACK storage_, Next: igraph_arpack_storage_destroy --- Deallocate ARPACK storage_, Prev: igraph_arpack_options_init --- Initialize ARPACK options_, Up: Data structures + +32.3.1.5 igraph_arpack_storage_init -- Initialize ARPACK storage. +................................................................. + + + igraph_error_t igraph_arpack_storage_init(igraph_arpack_storage_t *s, igraph_int_t maxn, + igraph_int_t maxncv, igraph_int_t maxldv, + igraph_bool_t symm); + + You only need this function if you want to run multiple eigenvalue +calculations using ARPACK, and want to spare the memory +allocation/deallocation between each two runs. Otherwise it is safe to +supply a null pointer as the ‘storage’ argument of both +‘igraph_arpack_rssolve()’ (*note igraph_arpack_rssolve --- ARPACK solver +for symmetric matrices_::) and ‘igraph_arpack_rnsolve()’ (*note +igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_::) +to make memory allocated and deallocated automatically. + + Don't forget to call the ‘igraph_arpack_storage_destroy()’ (*note +igraph_arpack_storage_destroy --- Deallocate ARPACK storage_::) function +on the storage object if you don't need it any more. + + *Arguments:. * + +‘s’: + The ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::) object to initialize. + +‘maxn’: + The maximum order of the matrices. + +‘maxncv’: + The maximum NCV parameter intended to use. + +‘maxldv’: + The maximum LDV parameter intended to use. + +‘symm’: + Whether symmetric or non-symmetric problems will be solved using + this ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::). (You cannot use the same storage both with + symmetric and non-symmetric solvers.) + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(maxncv*(maxldv+maxn)). + + +File: igraph-docs.info, Node: igraph_arpack_storage_destroy --- Deallocate ARPACK storage_, Prev: igraph_arpack_storage_init --- Initialize ARPACK storage_, Up: Data structures + +32.3.1.6 igraph_arpack_storage_destroy -- Deallocate ARPACK storage. +.................................................................... + + + void igraph_arpack_storage_destroy(igraph_arpack_storage_t *s); + + *Arguments:. * + +‘s’: + The ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::) object for which the memory will be + deallocated. + + Time complexity: operating system dependent. + + +File: igraph-docs.info, Node: ARPACK solvers, Prev: Data structures, Up: ARPACK interface in igraph + +32.3.2 ARPACK solvers +--------------------- + +* Menu: + +* igraph_arpack_rssolve -- ARPACK solver for symmetric matrices.: igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_. +* igraph_arpack_rnsolve -- ARPACK solver for non-symmetric matrices.: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_. +* igraph_arpack_unpack_complex -- Makes the result of the non-symmetric ARPACK solver more readable.: igraph_arpack_unpack_complex --- Makes the result of the non-symmetric ARPACK solver more readable_. + + +File: igraph-docs.info, Node: igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_, Next: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_, Up: ARPACK solvers + +32.3.2.1 igraph_arpack_rssolve -- ARPACK solver for symmetric matrices. +....................................................................... + + + igraph_error_t igraph_arpack_rssolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, igraph_matrix_t *vectors); + + This is the ARPACK solver for symmetric matrices. Please use +‘igraph_arpack_rnsolve()’ (*note igraph_arpack_rnsolve --- ARPACK solver +for non-symmetric matrices_::) for non-symmetric matrices. + + *Arguments:. * + +‘fun’: + Pointer to an ‘igraph_arpack_function_t’ (*note + igraph_arpack_function_t --- Type of the ARPACK callback + function_::) object, the function that performs the matrix-vector + multiplication. + +‘extra’: + An extra argument to be passed to ‘fun’. + +‘options’: + An ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- + Options for ARPACK_::) object. + +‘storage’: + An ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::) object, or a null pointer. In the latter + case memory allocation and deallocation is performed automatically. + Either this or the ‘vectors’ argument must be non-null if the + ARPACK iteration is started from a given starting vector. If both + are given ‘vectors’ take precedence. + +‘values’: + If not a null pointer, then it should be a pointer to an + initialized vector. The eigenvalues will be stored here. The + vector will be resized as needed. + +‘vectors’: + If not a null pointer, then it must be a pointer to an initialized + matrix. The eigenvectors will be stored in the columns of the + matrix. The matrix will be resized as needed. Either this or the + ‘storage’ argument must be non-null if the ARPACK iteration is + started from a given starting vector. If both are given ‘vectors’ + take precedence. + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on the matrix-vector multiplication. +Usually a small number of iterations is enough, so if the matrix is +sparse and the matrix-vector multiplication can be done in O(n) time +(the number of vertices), then the eigenvalues are found in O(n) time as +well. + + +File: igraph-docs.info, Node: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_, Next: igraph_arpack_unpack_complex --- Makes the result of the non-symmetric ARPACK solver more readable_, Prev: igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_, Up: ARPACK solvers + +32.3.2.2 igraph_arpack_rnsolve -- ARPACK solver for non-symmetric matrices. +........................................................................... + + + igraph_error_t igraph_arpack_rnsolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, igraph_matrix_t *vectors); + + Please always consider calling ‘igraph_arpack_rssolve()’ (*note +igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_::) if +your matrix is symmetric, it is much faster. ‘igraph_arpack_rnsolve()’ +(*note igraph_arpack_rnsolve --- ARPACK solver for non-symmetric +matrices_::) for non-symmetric matrices. + + Note that ARPACK is not called for 2x2 matrices as an exact algebraic +solution exists in these cases. + + *Arguments:. * + +‘fun’: + Pointer to an ‘igraph_arpack_function_t’ (*note + igraph_arpack_function_t --- Type of the ARPACK callback + function_::) object, the function that performs the matrix-vector + multiplication. + +‘extra’: + An extra argument to be passed to ‘fun’. + +‘options’: + An ‘igraph_arpack_options_t’ (*note igraph_arpack_options_t --- + Options for ARPACK_::) object. + +‘storage’: + An ‘igraph_arpack_storage_t’ (*note igraph_arpack_storage_t --- + Storage for ARPACK_::) object, or a null pointer. In the latter + case memory allocation and deallocation is performed automatically. + +‘values’: + If not a null pointer, then it should be a pointer to an + initialized matrix. The (possibly complex) eigenvalues will be + stored here. The matrix will have two columns, the first column + contains the real, the second the imaginary parts of the + eigenvalues. The matrix will be resized as needed. + +‘vectors’: + If not a null pointer, then it must be a pointer to an initialized + matrix. The eigenvectors will be stored in the columns of the + matrix. The matrix will be resized as needed. Note that real + eigenvalues will have real eigenvectors in a single column in this + matrix; however, complex eigenvalues come in conjugate pairs and + the result matrix will store the eigenvector corresponding to the + eigenvalue with _positive_ imaginary part only. Since in this case + the eigenvector is also complex, it will occupy _two_ columns in + the eigenvector matrix (the real and the imaginary parts, in this + order). Caveat: if the eigenvalue vector returns only the + eigenvalue with the _negative_ imaginary part for a complex + conjugate eigenvalue pair, the result vector will _still_ store the + eigenvector corresponding to the eigenvalue with the positive + imaginary part (since this is how ARPACK works). + + *Returns:. * + +‘’ + Error code. + + Time complexity: depends on the matrix-vector multiplication. +Usually a small number of iterations is enough, so if the matrix is +sparse and the matrix-vector multiplication can be done in O(n) time +(the number of vertices), then the eigenvalues are found in O(n) time as +well. + + +File: igraph-docs.info, Node: igraph_arpack_unpack_complex --- Makes the result of the non-symmetric ARPACK solver more readable_, Prev: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_, Up: ARPACK solvers + +32.3.2.3 igraph_arpack_unpack_complex -- Makes the result of the non-symmetric ARPACK solver more readable. +........................................................................................................... + + + igraph_error_t igraph_arpack_unpack_complex(igraph_matrix_t *vectors, igraph_matrix_t *values, + igraph_int_t nev); + + This function works on the output of ‘igraph_arpack_rnsolve’ (*note +igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_::) +and brushes it up a bit: it only keeps ‘nev’ eigenvalues/vectors and +every eigenvector is stored in two columns of the ‘vectors’ matrix. + + The output of the non-symmetric ARPACK solver is somewhat hard to +parse, as real eigenvectors occupy only one column in the matrix, and +the complex conjugate eigenvectors are not stored at all (usually). The +other problem is that the solver might return more eigenvalues than +requested. The common use of this function is to call it directly after +‘igraph_arpack_rnsolve’ (*note igraph_arpack_rnsolve --- ARPACK solver +for non-symmetric matrices_::) with its ‘vectors’ and ‘values’ argument +and ‘options’->nev as ‘nev’. This will add the vectors for eigenvalues +with a negative imaginary part and return all vectors as 2 columns, a +real and imaginary part. + + *Arguments:. * + +‘vectors’: + The eigenvector matrix, as returned by ‘igraph_arpack_rnsolve’ + (*note igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::). It will be resized, typically it will be larger. + +‘values’: + The eigenvalue matrix, as returned by ‘igraph_arpack_rnsolve’ + (*note igraph_arpack_rnsolve --- ARPACK solver for non-symmetric + matrices_::). It will be resized, typically extra, unneeded rows + (=eigenvalues) will be removed. + +‘nev’: + The number of eigenvalues/vectors to keep. Can be less or equal + than the number originally requested from ARPACK. + + *Returns:. * + +‘’ + Error code. + + Time complexity: linear in the number of elements in the ‘vectors’ +matrix. + + +File: igraph-docs.info, Node: Non-graph related functions, Next: Advanced igraph programming, Prev: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs, Up: Top + +33 Non-graph related functions +****************************** + +* Menu: + +* igraph version number:: +* Running mean of a time series:: +* Random sampling from very long sequences:: +* Random sampling of spatial points:: +* Fitting power-law distributions to empirical data:: +* Comparing floats with a tolerance:: + + +File: igraph-docs.info, Node: igraph version number, Next: Running mean of a time series, Up: Non-graph related functions + +33.1 igraph version number +========================== + +* Menu: + +* igraph_version -- The version of the igraph C library.: igraph_version --- The version of the igraph C library_. + + +File: igraph-docs.info, Node: igraph_version --- The version of the igraph C library_, Up: igraph version number + +33.1.1 igraph_version -- The version of the igraph C library. +------------------------------------------------------------- + + + void igraph_version(const char **version_string, + int *major, + int *minor, + int *patch); + + *Arguments:. * + +‘version_string’: + Pointer to a string pointer. If not ‘NULL’, it is set to the + igraph version string, e.g. "0.10.13", "1.2.0", or + "0.10.13-14-g997f59ad7". It consists of three dot-separated + numerical parts and potentially of a dash-separated suffix, used in + prerelease versions. This string must not be modified or + deallocated. + +‘major’: + If not a ‘NULL’ pointer, then it is set to the major igraph + version. E.g. for version "0.10.13" this is 0. + +‘minor’: + If not a ‘NULL’ pointer, then it is set to the minor igraph + version. E.g. for version "0.10.13" this is 10. + +‘patch’: + If not a ‘NULL’ pointer, then it is set to the subminor igraph + version. E.g. for version "0.10.13" this is 13. + + * File examples/simple/igraph_version.c* + + +File: igraph-docs.info, Node: Running mean of a time series, Next: Random sampling from very long sequences, Prev: igraph version number, Up: Non-graph related functions + +33.2 Running mean of a time series +================================== + +* Menu: + +* igraph_running_mean -- Calculates the running mean of a vector.: igraph_running_mean --- Calculates the running mean of a vector_. + + +File: igraph-docs.info, Node: igraph_running_mean --- Calculates the running mean of a vector_, Up: Running mean of a time series + +33.2.1 igraph_running_mean -- Calculates the running mean of a vector. +---------------------------------------------------------------------- + + + igraph_error_t igraph_running_mean(const igraph_vector_t *data, igraph_vector_t *res, + igraph_int_t binwidth); + + The running mean is defined by the mean of the previous ‘binwidth’ +values. + + *Arguments:. * + +‘data’: + The vector containing the data. + +‘res’: + The vector containing the result. This should be initialized + before calling this function and will be resized. + +‘binwidth’: + Integer giving the width of the bin for the running mean + calculation. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n), n is the length of the data vector. + + +File: igraph-docs.info, Node: Random sampling from very long sequences, Next: Random sampling of spatial points, Prev: Running mean of a time series, Up: Non-graph related functions + +33.3 Random sampling from very long sequences +============================================= + +* Menu: + +* igraph_random_sample -- Generates an increasing random sequence of integers.: igraph_random_sample --- Generates an increasing random sequence of integers_. + + +File: igraph-docs.info, Node: igraph_random_sample --- Generates an increasing random sequence of integers_, Up: Random sampling from very long sequences + +33.3.1 igraph_random_sample -- Generates an increasing random sequence of integers. +----------------------------------------------------------------------------------- + + + igraph_error_t igraph_random_sample(igraph_vector_int_t *res, igraph_int_t l, igraph_int_t h, + igraph_int_t length); + + This function generates an increasing sequence of random integer +numbers from a given interval. The algorithm is taken literally from +(Vitter 1987). This method can be used for generating numbers from a +_very_ large interval. It is primarily created for randomly selecting +some edges from the sometimes huge set of possible edges in a large +graph. + + Reference: + + J. S. Vitter. An efficient algorithm for sequential random sampling. +ACM Transactions on Mathematical Software, 13(1):58-67, 1987. +https://doi.org/10.1145/23002.23003 +(https://doi.org/10.1145/23002.23003) + + *Arguments:. * + +‘res’: + Pointer to an initialized vector. This will hold the result. It + will be resized to the proper size. + +‘l’: + The lower limit of the generation interval (inclusive). This must + be less than or equal to the upper limit, and it must be integral. + +‘h’: + The upper limit of the generation interval (inclusive). This must + be greater than or equal to the lower limit, and it must be + integral. + +‘length’: + The number of random integers to generate. + + *Returns:. * + +‘’ + The error code ‘IGRAPH_EINVAL’ is returned in each of the following + cases: (1) The given lower limit is greater than the given upper + limit, i.e. ‘l’ > ‘h’. (2) Assuming that ‘l’ < ‘h’ and N is the + sample size, the above error code is returned if N > |‘h’ - ‘l’|, + i.e. the sample size exceeds the size of the candidate pool. + + Time complexity: according to (Vitter 1987), the expected running +time is O(length). + + * File examples/simple/igraph_random_sample.c* + + +File: igraph-docs.info, Node: Random sampling of spatial points, Next: Fitting power-law distributions to empirical data, Prev: Random sampling from very long sequences, Up: Non-graph related functions + +33.4 Random sampling of spatial points +====================================== + +* Menu: + +* igraph_rng_sample_sphere_surface -- Sample points uniformly from the surface of a sphere.: igraph_rng_sample_sphere_surface --- Sample points uniformly from the surface of a sphere_. +* igraph_rng_sample_sphere_volume -- Sample points uniformly from the volume of a sphere.: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_. +* igraph_rng_sample_dirichlet -- Sample points from a Dirichlet distribution.: igraph_rng_sample_dirichlet --- Sample points from a Dirichlet distribution_. + + +File: igraph-docs.info, Node: igraph_rng_sample_sphere_surface --- Sample points uniformly from the surface of a sphere_, Next: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_, Up: Random sampling of spatial points + +33.4.1 igraph_rng_sample_sphere_surface -- Sample points uniformly from the surface of a sphere. +------------------------------------------------------------------------------------------------ + + + igraph_error_t igraph_rng_sample_sphere_surface( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res + ); + + The center of the sphere is at the origin. + + *Arguments:. * + +‘rng’: + The random number generator to use. + +‘dim’: + The dimension of the random vectors. + +‘n’: + The number of vectors to sample. + +‘radius’: + Radius of the sphere, it must be positive. + +‘positive’: + Whether to restrict sampling to the positive orthant. + +‘res’: + Pointer to an initialized matrix, the result is stored here, each + column will be a sampled vector. The matrix is resized, as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n*dim*g), where g is the time complexity of +generating a standard normal random number. + + *See also:. * + +‘’ + ‘igraph_rng_sample_sphere_volume()’ (*note + igraph_rng_sample_sphere_volume --- Sample points uniformly from + the volume of a sphere_::), ‘igraph_rng_sample_dirichlet()’ (*note + igraph_rng_sample_dirichlet --- Sample points from a Dirichlet + distribution_::) for other similar samplers. + + +File: igraph-docs.info, Node: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_, Next: igraph_rng_sample_dirichlet --- Sample points from a Dirichlet distribution_, Prev: igraph_rng_sample_sphere_surface --- Sample points uniformly from the surface of a sphere_, Up: Random sampling of spatial points + +33.4.2 igraph_rng_sample_sphere_volume -- Sample points uniformly from the volume of a sphere. +---------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_rng_sample_sphere_volume( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res + ); + + The center of the sphere is at the origin. + + *Arguments:. * + +‘rng’: + The random number generator to use. + +‘dim’: + The dimension of the random vectors. + +‘n’: + The number of vectors to sample. + +‘radius’: + Radius of the sphere, it must be positive. + +‘positive’: + Whether to restrict sampling to the positive orthant. + +‘res’: + Pointer to an initialized matrix, the result is stored here, each + column will be a sampled vector. The matrix is resized, as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n*dim*g), where g is the time complexity of +generating a standard normal random number. + + *See also:. * + +‘’ + ‘igraph_rng_sample_sphere_surface()’ (*note + igraph_rng_sample_sphere_surface --- Sample points uniformly from + the surface of a sphere_::), ‘igraph_rng_sample_dirichlet()’ (*note + igraph_rng_sample_dirichlet --- Sample points from a Dirichlet + distribution_::) for other similar samplers. + + +File: igraph-docs.info, Node: igraph_rng_sample_dirichlet --- Sample points from a Dirichlet distribution_, Prev: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_, Up: Random sampling of spatial points + +33.4.3 igraph_rng_sample_dirichlet -- Sample points from a Dirichlet distribution. +---------------------------------------------------------------------------------- + + + igraph_error_t igraph_rng_sample_dirichlet( + igraph_rng_t* rng, igraph_int_t n, const igraph_vector_t *alpha, + igraph_matrix_t *res + ); + + *Arguments:. * + +‘rng’: + The random number generator to use. + +‘n’: + The number of vectors to sample. + +‘alpha’: + The parameters of the Dirichlet distribution. They must be + positive. The length of this vector gives the dimension of the + generated samples. + +‘res’: + Pointer to an initialized matrix, the result is stored here, one + sample in each column. It will be resized, as needed. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(n * dim * g), where dim is the dimension of the +sample vectors, set by the length of alpha, and g is the time complexity +of sampling from a Gamma distribution. + + *See also:. * + +‘’ + ‘igraph_rng_sample_sphere_surface()’ (*note + igraph_rng_sample_sphere_surface --- Sample points uniformly from + the surface of a sphere_::) and ‘igraph_rng_sample_sphere_volume()’ + (*note igraph_rng_sample_sphere_volume --- Sample points uniformly + from the volume of a sphere_::) for other methods to sample latent + vectors. + + +File: igraph-docs.info, Node: Fitting power-law distributions to empirical data, Next: Comparing floats with a tolerance, Prev: Random sampling of spatial points, Up: Non-graph related functions + +33.5 Fitting power-law distributions to empirical data +====================================================== + +* Menu: + +* igraph_plfit_result_t -- Result of fitting a power-law distribution to a vector.: igraph_plfit_result_t --- Result of fitting a power-law distribution to a vector_. +* igraph_power_law_fit -- Fits a power-law distribution to a vector of numbers.: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_. +* igraph_plfit_result_calculate_p_value -- Calculates the p-value of a fitted power-law model.: igraph_plfit_result_calculate_p_value --- Calculates the p-value of a fitted power-law model_. + + +File: igraph-docs.info, Node: igraph_plfit_result_t --- Result of fitting a power-law distribution to a vector_, Next: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_, Up: Fitting power-law distributions to empirical data + +33.5.1 igraph_plfit_result_t -- Result of fitting a power-law distribution to a vector. +--------------------------------------------------------------------------------------- + + + typedef struct igraph_plfit_result_t { + igraph_bool_t continuous; + igraph_real_t alpha; + igraph_real_t xmin; + igraph_real_t L; + igraph_real_t D; + const igraph_vector_t* data; + } igraph_plfit_result_t; + + This data structure contains the result of ‘igraph_power_law_fit()’ +(*note igraph_power_law_fit --- Fits a power-law distribution to a +vector of numbers_::), which tries to fit a power-law distribution to a +vector of numbers. The structure contains the following members: + + *Values:. * + +‘continuous’: + Whether the fitted power-law distribution was continuous or + discrete. + +‘alpha’: + The exponent of the fitted power-law distribution. + +‘xmin’: + The minimum value from which the power-law distribution was fitted. + In other words, only the values larger than ‘xmin’ were used from + the input vector. + +‘L’: + The log-likelihood of the fitted parameters; in other words, the + probability of observing the input vector given the parameters. + +‘D’: + The test statistic of a Kolmogorov-Smirnov test that compares the + fitted distribution with the input vector. Smaller scores denote + better fit. + +‘p’: + The p-value of the Kolmogorov-Smirnov test; ‘NaN’ if it has not + been calculated yet. Small p-values (less than 0.05) indicate that + the test rejected the hypothesis that the original data could have + been drawn from the fitted power-law distribution. + +‘data’: + The vector containing the original input data. May not be valid + any more if the caller already destroyed the vector. + + +File: igraph-docs.info, Node: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_, Next: igraph_plfit_result_calculate_p_value --- Calculates the p-value of a fitted power-law model_, Prev: igraph_plfit_result_t --- Result of fitting a power-law distribution to a vector_, Up: Fitting power-law distributions to empirical data + +33.5.2 igraph_power_law_fit -- Fits a power-law distribution to a vector of numbers. +------------------------------------------------------------------------------------ + + + igraph_error_t igraph_power_law_fit( + const igraph_vector_t* data, igraph_plfit_result_t* result, + igraph_real_t xmin, igraph_bool_t force_continuous + ); + + This function fits a power-law distribution to a vector containing +samples from a distribution (that is assumed to follow a power-law of +course). In a power-law distribution, it is generally assumed that +P(X=x) is proportional to x^-alpha, where x is a positive number and +alpha is greater than 1. In many real-world cases, the power-law +behaviour kicks in only above a threshold value _xmin._ The goal of +this functions is to determine _alpha_ if _xmin_ is given, or to +determine _xmin_ and the corresponding value of _alpha._ + + The function uses the maximum likelihood principle to determine +_alpha_ for a given _xmin;_ in other words, the function will return the +_alpha_ value for which the probability of drawing the given sample is +the highest. When _xmin_ is not given in advance, the algorithm will +attempt to find the optimal _xmin_ value for which the p-value of a +Kolmogorov-Smirnov test between the fitted distribution and the original +sample is the largest. The function uses the method of Clauset, Shalizi +and Newman to calculate the parameters of the fitted distribution. See +the following reference for details: + + Aaron Clauset, Cosma R. Shalizi and Mark E.J. Newman: Power-law +distributions in empirical data. SIAM Review 51(4):661-703, 2009. +https://doi.org/10.1137/070710111 (https://doi.org/10.1137/070710111) + + *Arguments:. * + +‘data’: + vector containing the samples for which a power-law distribution is + to be fitted. Note that you have to provide the _samples,_ not the + probability density function or the cumulative distribution + function. For example, if you wish to fit a power-law to the + degrees of a graph, you can use the output of ‘igraph_degree’ + (*note igraph_degree --- The degree of some vertices in a graph_::) + directly as an input argument to ‘igraph_power_law_fit’ (*note + igraph_power_law_fit --- Fits a power-law distribution to a vector + of numbers_::) + +‘result’: + the result of the fitting algorithm. See ‘igraph_plfit_result_t’ + (*note igraph_plfit_result_t --- Result of fitting a power-law + distribution to a vector_::) for more details. Note that the + p-value of the fit is _not_ calculated by default as it is + time-consuming; you need to call + ‘igraph_plfit_result_calculate_p_value()’ (*note + igraph_plfit_result_calculate_p_value --- Calculates the p-value of + a fitted power-law model_::) to calculate the p-value itself + +‘xmin’: + the minimum value in the sample vector where the power-law + behaviour is expected to kick in. Samples smaller than ‘xmin’ will + be ignored by the algorithm. Pass zero here if you want to include + all the samples. If ‘xmin’ is negative, the algorithm will attempt + to determine its best value automatically. + +‘force_continuous’: + assume that the samples in the ‘data’ argument come from a + continuous distribution even if the sample vector contains integer + values only (by chance). If this argument is false, igraph will + assume a continuous distribution if at least one sample is + non-integer and assume a discrete distribution otherwise. + + *Returns:. * + +‘’ + Error code: ‘IGRAPH_ENOMEM’: not enough memory ‘IGRAPH_EINVAL’: one + of the arguments is invalid ‘IGRAPH_EOVERFLOW’: overflow during the + fitting process ‘IGRAPH_EUNDERFLOW’: underflow during the fitting + process ‘IGRAPH_FAILURE’: the underlying algorithm signaled a + failure without returning a more specific error code + + Time complexity: in the continuous case, O(n log(n)) if ‘xmin’ is +given. In the discrete case, the time complexity is dominated by the +complexity of the underlying L-BFGS algorithm that is used to optimize +alpha. If ‘xmin’ is not given, the time complexity is multiplied by the +number of unique samples in the input vector (although it should be +faster in practice). + + * File examples/simple/igraph_power_law_fit.c* + + +File: igraph-docs.info, Node: igraph_plfit_result_calculate_p_value --- Calculates the p-value of a fitted power-law model_, Prev: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_, Up: Fitting power-law distributions to empirical data + +33.5.3 igraph_plfit_result_calculate_p_value -- Calculates the p-value of a fitted power-law model. +--------------------------------------------------------------------------------------------------- + + + igraph_error_t igraph_plfit_result_calculate_p_value( + const igraph_plfit_result_t* model, igraph_real_t* result, igraph_real_t precision + ); + + The p-value is calculated by resampling the input data many times in +a way that the part below the fitted ‘x_min’ threshold is resampled from +the input data itself, while the part above the fitted ‘x_min’ threshold +is drawn from the fitted power-law function. A Kolmogorov-Smirnov test +is then performed for each resampled dataset and its test statistic is +compared with the observed test statistic from the original dataset. +The fraction of resampled datasets that have a _higher_ test statistic +is the returned p-value. + + Note that the precision of the returned p-value depends on the number +of resampling attempts. The number of resampling trials is determined +by 0.25 divided by the square of the required precision. For instance, +a required precision of 0.01 means that 2500 samples will be drawn. + + If igraph is compiled with OpenMP support, this function will use +parallel OpenMP threads for the resampling. Each OpenMP thread gets its +own instance of a random number generator. However, since the +scheduling of OpenMP threads is outside our control, we cannot guarantee +how many resampling instances the threads are asked to execute, thus it +may happen that the random number generators are used differently +between runs. If you want to obtain reproducible results, seed igraph's +master RNG appropriately, and force the number of OpenMP threads to 1 +early in your program, either by calling ‘omp_set_num_threads(1)’ or by +setting the value of the ‘OMP_NUM_THREADS’ environment variable to 1. + + *Arguments:. * + +‘model’: + The fitted power-law model from the ‘igraph_power_law_fit()’ (*note + igraph_power_law_fit --- Fits a power-law distribution to a vector + of numbers_::) function + +‘result’: + The calculated p-value is returned here + +‘precision’: + The desired precision of the p-value. Higher values correspond to + longer calculation time. + + *Returns:. * + +‘’ + Error code. + + +File: igraph-docs.info, Node: Comparing floats with a tolerance, Prev: Fitting power-law distributions to empirical data, Up: Non-graph related functions + +33.6 Comparing floats with a tolerance +====================================== + +* Menu: + +* igraph_cmp_epsilon -- Compare two double-precision floats with a tolerance.: igraph_cmp_epsilon --- Compare two double-precision floats with a tolerance_. +* igraph_almost_equals -- Compare two double-precision floats with a tolerance.: igraph_almost_equals --- Compare two double-precision floats with a tolerance_. +* igraph_complex_almost_equals -- Compare two complex numbers with a tolerance.: igraph_complex_almost_equals --- Compare two complex numbers with a tolerance_. + + +File: igraph-docs.info, Node: igraph_cmp_epsilon --- Compare two double-precision floats with a tolerance_, Next: igraph_almost_equals --- Compare two double-precision floats with a tolerance_, Up: Comparing floats with a tolerance + +33.6.1 igraph_cmp_epsilon -- Compare two double-precision floats with a tolerance. +---------------------------------------------------------------------------------- + + + int igraph_cmp_epsilon(double a, double b, double eps); + + Determines whether two double-precision floats are "almost equal" to +each other with a given level of tolerance on the relative error. + + The function supports infinities and NaN values. NaN values are +considered not equal to any other value (even another NaN), but the +ordering is arbitrary; in other words, we only guarantee that comparing +a NaN with any other value will not return zero. Positive infinity is +considered to be greater than any finite value with any tolerance. +Negative infinity is considered to be smaller than any finite value with +any tolerance. Positive infinity is considered to be equal to another +positive infinity with any tolerance. Negative infinity is considered +to be equal to another negative infinity with any tolerance. + + *Arguments:. * + +‘a’: + The first float. + +‘b’: + The second float. + +‘eps’: + The level of tolerance on the relative error. The relative error + is defined as ‘abs(a-b) / (abs(a) + abs(b))’. The two numbers are + considered equal if this is less than ‘eps’. Negative epsilon + values are not allowed; the returned value will be undefined in + this case. Zero means to do an exact comparison without tolerance. + + *Returns:. * + +‘’ + Zero if the two floats are nearly equal to each other within the + given level of tolerance, positive number if the first float is + larger, negative number if the second float is larger. + + +File: igraph-docs.info, Node: igraph_almost_equals --- Compare two double-precision floats with a tolerance_, Next: igraph_complex_almost_equals --- Compare two complex numbers with a tolerance_, Prev: igraph_cmp_epsilon --- Compare two double-precision floats with a tolerance_, Up: Comparing floats with a tolerance + +33.6.2 igraph_almost_equals -- Compare two double-precision floats with a tolerance. +------------------------------------------------------------------------------------ + + + igraph_bool_t igraph_almost_equals(double a, double b, double eps); + + Determines whether two double-precision floats are "almost equal" to +each other with a given level of tolerance on the relative error. + + *Arguments:. * + +‘a’: + The first float. + +‘b’: + The second float. + +‘eps’: + The level of tolerance on the relative error. The relative error + is defined as ‘abs(a-b) / (abs(a) + abs(b))’. The two numbers are + considered equal if this is less than ‘eps’. + + *Returns:. * + +‘’ + True if the two floats are nearly equal to each other within the + given level of tolerance, false otherwise. + + +File: igraph-docs.info, Node: igraph_complex_almost_equals --- Compare two complex numbers with a tolerance_, Prev: igraph_almost_equals --- Compare two double-precision floats with a tolerance_, Up: Comparing floats with a tolerance + +33.6.3 igraph_complex_almost_equals -- Compare two complex numbers with a tolerance. +------------------------------------------------------------------------------------ + + + igraph_bool_t igraph_complex_almost_equals(igraph_complex_t a, + igraph_complex_t b, + igraph_real_t eps); + + Determines whether two complex numbers are "almost equal" to each +other with a given level of tolerance on the relative error. + + *Arguments:. * + +‘a’: + The first complex number. + +‘b’: + The second complex number. + +‘eps’: + The level of tolerance on the relative error. The relative error + is defined as ‘abs(a-b) / (abs(a) + abs(b))’. The two numbers are + considered equal if this is less than ‘eps’. + + *Returns:. * + +‘’ + True if the two complex numbers are nearly equal to each other + within the given level of tolerance, false otherwise. + + +File: igraph-docs.info, Node: Advanced igraph programming, Next: Glossary, Prev: Non-graph related functions, Up: Top + +34 Advanced igraph programming +****************************** + +* Menu: + +* Using igraph in multi-threaded programs:: +* Progress handlers:: +* Status handlers:: + + +File: igraph-docs.info, Node: Using igraph in multi-threaded programs, Next: Progress handlers, Up: Advanced igraph programming + +34.1 Using igraph in multi-threaded programs +============================================ + +The igraph library is considered thread-safe if it has been compiled +with thread-local storage enabled, i.e. the ‘IGRAPH_ENABLE_TLS’ setting +was toggled to ‘ON’ and the current platform supports this feature. To +check whether an igraph build is thread-safe, use the +‘IGRAPH_THREAD_SAFE’ (*note IGRAPH_THREAD_SAFE --- Specifies whether +igraph was built in thread-safe mode_::) macro. When linking to +external versions of igraph's dependencies, it is the responsibility of +the user to check that these dependencies were also compiled to be +thread-safe. + +* Menu: + +* IGRAPH_THREAD_SAFE -- Specifies whether igraph was built in thread-safe mode.: IGRAPH_THREAD_SAFE --- Specifies whether igraph was built in thread-safe mode_. +* Thread-safe ARPACK library:: +* Thread-safety of random number generators:: + + +File: igraph-docs.info, Node: IGRAPH_THREAD_SAFE --- Specifies whether igraph was built in thread-safe mode_, Next: Thread-safe ARPACK library, Up: Using igraph in multi-threaded programs + +34.1.1 IGRAPH_THREAD_SAFE -- Specifies whether igraph was built in thread-safe mode. +------------------------------------------------------------------------------------ + + + #define IGRAPH_THREAD_SAFE + + This macro is defined to 1 if the current build of the igraph library +is built in thread-safe mode, and 0 if it is not. A thread-safe igraph +library attempts to use thread-local data structures instead of global +ones, but note that this is not (and can not) be guaranteed for +third-party libraries that igraph links to. + + +File: igraph-docs.info, Node: Thread-safe ARPACK library, Next: Thread-safety of random number generators, Prev: IGRAPH_THREAD_SAFE --- Specifies whether igraph was built in thread-safe mode_, Up: Using igraph in multi-threaded programs + +34.1.2 Thread-safe ARPACK library +--------------------------------- + +Note that igraph is only thread-safe if it was built with the internal +ARPACK library, i.e. the one that comes with igraph. The standard +ARPACK library is not thread-safe. + + +File: igraph-docs.info, Node: Thread-safety of random number generators, Prev: Thread-safe ARPACK library, Up: Using igraph in multi-threaded programs + +34.1.3 Thread-safety of random number generators +------------------------------------------------ + +The default random number generator that igraph uses is _not_ guaranteed +to be thread-safe. You need to set a different random number generator +instance for every thread that you want to use igraph from. This is +especially important if you set the seed of the random number generator +to ensure reproducibility; sharing a random number generator between +threads would break reproducibility as the order in which the various +threads are scheduled is random, and therefore they would still receive +random numbers in an unpredictable order from the shared random number +generator. + + +File: igraph-docs.info, Node: Progress handlers, Next: Status handlers, Prev: Using igraph in multi-threaded programs, Up: Advanced igraph programming + +34.2 Progress handlers +====================== + +* Menu: + +* About progress handlers:: +* Setting up progress handlers:: +* Invoking the progress handler:: +* Writing progress handlers:: +* Writing igraph functions with progress reporting:: +* Multi-threaded programs:: + + +File: igraph-docs.info, Node: About progress handlers, Next: Setting up progress handlers, Up: Progress handlers + +34.2.1 About progress handlers +------------------------------ + +It is often useful to report the progress of some long calculation, to +allow the user to follow the computation and guess the total running +time. A couple of igraph functions support this at the time of writing, +hopefully more will support it in the future. + + To see the progress of a computation, the user has to install a +progress handler, as there is none installed by default. If an igraph +function supports progress reporting, then it calls the installed +progress handler periodically, and passes a percentage value to it, the +percentage of computation already performed. To install a progress +handler, you need to call ‘igraph_set_progress_handler()’ (*note +igraph_set_progress_handler --- Install a progress handler; or remove +the current handler_::). Currently there is a single pre-defined +progress handler, called ‘igraph_progress_handler_stderr()’ (*note +igraph_progress_handler_stderr --- A simple predefined progress +handler_::). + + +File: igraph-docs.info, Node: Setting up progress handlers, Next: Invoking the progress handler, Prev: About progress handlers, Up: Progress handlers + +34.2.2 Setting up progress handlers +----------------------------------- + +* Menu: + +* igraph_progress_handler_t --- Type of progress handler functions:: +* igraph_set_progress_handler -- Install a progress handler, or remove the current handler.: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_. +* igraph_progress_handler_stderr -- A simple predefined progress handler.: igraph_progress_handler_stderr --- A simple predefined progress handler_. + + +File: igraph-docs.info, Node: igraph_progress_handler_t --- Type of progress handler functions, Next: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_, Up: Setting up progress handlers + +34.2.2.1 igraph_progress_handler_t -- Type of progress handler functions +........................................................................ + + + typedef igraph_error_t igraph_progress_handler_t(const char *message, igraph_real_t percent, + void *data); + + This is the type of the igraph progress handler functions. There is +currently one such predefined function, +‘igraph_progress_handler_stderr()’ (*note igraph_progress_handler_stderr +--- A simple predefined progress handler_::), but the user can write and +set up more sophisticated ones. + + *Arguments:. * + +‘message’: + A string describing the function or algorithm that is reporting the + progress. Current igraph functions always use the name ‘message’ + argument if reporting from the same function. + +‘percent’: + Numeric, the percentage that was completed by the algorithm or + function. + +‘data’: + User-defined data. Current igraph functions that report progress + pass a null pointer here. Users can write their own progress + handlers and functions with progress reporting, and then pass some + meaningfull context here. + + *Returns:. * + +‘’ + If the return value of the progress handler is not + ‘IGRAPH_SUCCESS’, then ‘igraph_progress()’ (*note igraph_progress + --- Report the progress of a calculation from an igraph + function_::) returns the error code from the progress handler + intact. The ‘IGRAPH_PROGRESS()’ (*note IGRAPH_PROGRESS --- Report + the progress of a calculation from an igraph function [macro + variant]_::) macro also frees all allocated memory. + + +File: igraph-docs.info, Node: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_, Next: igraph_progress_handler_stderr --- A simple predefined progress handler_, Prev: igraph_progress_handler_t --- Type of progress handler functions, Up: Setting up progress handlers + +34.2.2.2 igraph_set_progress_handler -- Install a progress handler, or remove the current handler. +.................................................................................................. + + + igraph_progress_handler_t * + igraph_set_progress_handler(igraph_progress_handler_t new_handler); + + There is a single simple predefined progress handler: +‘igraph_progress_handler_stderr()’ (*note igraph_progress_handler_stderr +--- A simple predefined progress handler_::). + + *Arguments:. * + +‘new_handler’: + Pointer to a function of type ‘igraph_progress_handler_t’ (*note + igraph_progress_handler_t --- Type of progress handler + functions::), the progress handler function to install. To + uninstall the current progress handler, this argument can be a null + pointer. + + *Returns:. * + +‘’ + Pointer to the previously installed progress handler function. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_progress_handler_stderr --- A simple predefined progress handler_, Prev: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_, Up: Setting up progress handlers + +34.2.2.3 igraph_progress_handler_stderr -- A simple predefined progress handler. +................................................................................ + + + igraph_error_t igraph_progress_handler_stderr(const char *message, igraph_real_t percent, + void* data); + + This simple progress handler first prints ‘message’, and then the +percentage complete value in a short message to standard error. + + *Arguments:. * + +‘message’: + A string describing the function or algorithm that is reporting the + progress. Current igraph functions always use the same ‘message’ + argument if reporting from the same function. + +‘percent’: + Numeric, the percentage that was completed by the algorithm or + function. + +‘data’: + User-defined data. Current igraph functions that report progress + pass a null pointer here. Users can write their own progress + handlers and functions with progress reporting, and then pass some + meaningfull context here. + + *Returns:. * + +‘’ + This function always returns with ‘IGRAPH_SUCCESS’. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Invoking the progress handler, Next: Writing progress handlers, Prev: Setting up progress handlers, Up: Progress handlers + +34.2.3 Invoking the progress handler +------------------------------------ + +* Menu: + +* IGRAPH_PROGRESS -- Report the progress of a calculation from an igraph function (macro variant).: IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph function [macro variant]_. +* IGRAPH_PROGRESSF -- Report the progress of a calculation from an igraph function, printf-like (macro variant).: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_. +* igraph_progress -- Report the progress of a calculation from an igraph function.: igraph_progress --- Report the progress of a calculation from an igraph function_. +* igraph_progressf -- Report the progress of a calculation from an igraph function, printf-like.: igraph_progressf --- Report the progress of a calculation from an igraph function; printf-like_. + + +File: igraph-docs.info, Node: IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph function [macro variant]_, Next: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_, Up: Invoking the progress handler + +34.2.3.1 IGRAPH_PROGRESS -- Report the progress of a calculation from an igraph function (macro variant). +......................................................................................................... + + + #define IGRAPH_PROGRESS(message, percent, data) + + The standard way to report progress from an igraph function + + *Arguments:. * + +‘message’: + A string, a textual message that references the calculation under + progress. + +‘percent’: + Numeric scalar, the percentage that is complete. + +‘data’: + User-defined data, this can be used in user-defined progress + handler functions, from user-written igraph functions. + + *Returns:. * + +‘’ + If the return value of the progress handler is not + ‘IGRAPH_SUCCESS’, then ‘igraph_progress()’ (*note igraph_progress + --- Report the progress of a calculation from an igraph + function_::) returns the error code from the progress handler + intact. The ‘IGRAPH_PROGRESS()’ (*note IGRAPH_PROGRESS --- Report + the progress of a calculation from an igraph function [macro + variant]_::) macro also frees all allocated memory. + + +File: igraph-docs.info, Node: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_, Next: igraph_progress --- Report the progress of a calculation from an igraph function_, Prev: IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph function [macro variant]_, Up: Invoking the progress handler + +34.2.3.2 IGRAPH_PROGRESSF -- Report the progress of a calculation from an igraph function, printf-like (macro variant). +....................................................................................................................... + + + #define IGRAPH_PROGRESSF(args) + + This is the more flexible version of ‘IGRAPH_PROGRESS()’ (*note +IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph +function [macro variant]_::), having a printf-like syntax. As this +macro takes variable number of arguments, they must be all supplied as a +single argument, enclosed in parentheses. ‘igraph_progressf()’ (*note +igraph_progressf --- Report the progress of a calculation from an igraph +function; printf-like_::) is then called with the given arguments. + + *Arguments:. * + +‘args’: + The arguments to pass to ‘igraph_progressf()’ (*note + igraph_progressf --- Report the progress of a calculation from an + igraph function; printf-like_::). + + *Returns:. * + +‘’ + If the progress handler returns with a value other than + ‘IGRAPH_SUCCESS’, then the function that called this macro returns + as well, with the same error code, after cleaning up all allocated + memory as needed. + + +File: igraph-docs.info, Node: igraph_progress --- Report the progress of a calculation from an igraph function_, Next: igraph_progressf --- Report the progress of a calculation from an igraph function; printf-like_, Prev: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_, Up: Invoking the progress handler + +34.2.3.3 igraph_progress -- Report the progress of a calculation from an igraph function. +......................................................................................... + + + igraph_error_t igraph_progress(const char *message, igraph_real_t percent, void *data); + + Note that the usual way to report progress is the ‘IGRAPH_PROGRESS’ +(*note IGRAPH_PROGRESS --- Report the progress of a calculation from an +igraph function [macro variant]_::) macro, as that takes care of the +return value of the progress handler. + + *Arguments:. * + +‘message’: + A string describing the function or algorithm that is reporting the + progress. Current igraph functions always use the name ‘message’ + argument if reporting from the same function. + +‘percent’: + Numeric, the percentage that was completed by the algorithm or + function. + +‘data’: + User-defined data. Current igraph functions that report progress + pass a null pointer here. Users can write their own progress + handlers and functions with progress reporting, and then pass some + meaningfull context here. + + *Returns:. * + +‘’ + Error code from the progress handler function, or ‘IGRAPH_SUCCESS’ + if no progress handler function was registered. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_progressf --- Report the progress of a calculation from an igraph function; printf-like_, Prev: igraph_progress --- Report the progress of a calculation from an igraph function_, Up: Invoking the progress handler + +34.2.3.4 igraph_progressf -- Report the progress of a calculation from an igraph function, printf-like. +....................................................................................................... + + + igraph_error_t igraph_progressf(const char *message, igraph_real_t percent, void *data, + ...); + + This is a more flexible version of ‘igraph_progress()’ (*note +igraph_progress --- Report the progress of a calculation from an igraph +function_::), with a printf-like template string. First the template +string is filled with the additional arguments and then +‘igraph_progress()’ (*note igraph_progress --- Report the progress of a +calculation from an igraph function_::) is called. + + Note that there is an upper limit for the length of the ‘message’ +string, currently 1000 characters. + + *Arguments:. * + +‘message’: + A string describing the function or algorithm that is reporting the + progress. For this function this is a template string, using the + same syntax as the standard ‘libc’ ‘printf’ function. + +‘percent’: + Numeric, the percentage that was completed by the algorithm or + function. + +‘data’: + User-defined data. Current igraph functions that report progress + pass a null pointer here. Users can write their own progress + handlers and functions with progress reporting, and then pass some + meaningfull context here. + +‘...’: + Additional argument that were specified in the ‘message’ argument. + + *Returns:. * + +‘’ + Error code from the progress handler function, or ‘IGRAPH_SUCCESS’ + if no progress handler function was registered. \return + + +File: igraph-docs.info, Node: Writing progress handlers, Next: Writing igraph functions with progress reporting, Prev: Invoking the progress handler, Up: Progress handlers + +34.2.4 Writing progress handlers +-------------------------------- + +To write a new progress handler, one needs to create a function of type +‘igraph_progress_handler_t’ (*note igraph_progress_handler_t --- Type of +progress handler functions::). The new progress handler can then be +installed with the ‘igraph_set_progress_handler()’ (*note +igraph_set_progress_handler --- Install a progress handler; or remove +the current handler_::) function. + + One can assume that the first progress handler call from a +calculation will be call with zero as the ‘percentage’ argument, and the +last call from a function will have 100 as the ‘percentage’ argument. +Note, however, that if an error happens in the middle of a computation, +then the 100 percent call might be omitted. + + +File: igraph-docs.info, Node: Writing igraph functions with progress reporting, Next: Multi-threaded programs, Prev: Writing progress handlers, Up: Progress handlers + +34.2.5 Writing igraph functions with progress reporting +------------------------------------------------------- + +If you want to write a function that uses igraph and supports progress +reporting, you need to include ‘igraph_progress()’ (*note +igraph_progress --- Report the progress of a calculation from an igraph +function_::) calls in your function, usually via the ‘IGRAPH_PROGRESS()’ +(*note IGRAPH_PROGRESS --- Report the progress of a calculation from an +igraph function [macro variant]_::) macro. + + It is good practice to always include a call to ‘igraph_progress()’ +(*note igraph_progress --- Report the progress of a calculation from an +igraph function_::) with a zero ‘percentage’ argument, before the +computation; and another call with 100 ‘percentage’ value after the +computation is completed. + + It is also good practice _not_ to call ‘igraph_progress()’ (*note +igraph_progress --- Report the progress of a calculation from an igraph +function_::) too often, as this would slow down the computation. It +might not be worth to support progress reporting in functions with +linear or log-linear time complexity, as these are fast, even with a +large amount of data. For functions with quadratic or higher time +complexity make sure that the time complexity of the progress reporting +is constant or at least linear. In practice this means having at most +O(n) progress checks and at most 100 ‘igraph_progress()’ (*note +igraph_progress --- Report the progress of a calculation from an igraph +function_::) calls. + + +File: igraph-docs.info, Node: Multi-threaded programs, Prev: Writing igraph functions with progress reporting, Up: Progress handlers + +34.2.6 Multi-threaded programs +------------------------------ + +In multi-threaded programs, each thread has its own progress handler, if +thread-local storage is supported and igraph is thread-safe. See the +‘IGRAPH_THREAD_SAFE’ (*note IGRAPH_THREAD_SAFE --- Specifies whether +igraph was built in thread-safe mode_::) macro for checking whether an +igraph build is thread-safe. + + +File: igraph-docs.info, Node: Status handlers, Prev: Progress handlers, Up: Advanced igraph programming + +34.3 Status handlers +==================== + +* Menu: + +* Status reporting:: +* Setting up status handlers:: +* Invoking the status handler:: + + +File: igraph-docs.info, Node: Status reporting, Next: Setting up status handlers, Up: Status handlers + +34.3.1 Status reporting +----------------------- + +In addition to the possibility of reporting the progress of an igraph +computation via ‘igraph_progress()’ (*note igraph_progress --- Report +the progress of a calculation from an igraph function_::), it is also +possible to report simple status messages from within igraph functions, +without having to judge how much of the computation was performed +already. For this one needs to install a status handler function. + + Status handler functions must be of type ‘igraph_status_handler_t’ +(*note igraph_status_handler_t --- The type of the igraph status handler +functions::) and they can be installed by a call to +‘igraph_set_status_handler()’ (*note igraph_set_status_handler --- +Install of uninstall a status handler function_::). Currently there is +a simple predefined status handler function, called +‘igraph_status_handler_stderr()’ (*note igraph_status_handler_stderr --- +A simple predefined status handler function_::), but the user can define +new ones. + + igraph functions report their status via a call to the +‘IGRAPH_STATUS()’ (*note IGRAPH_STATUS --- Report the status of an +igraph function_::) or the ‘IGRAPH_STATUSF()’ (*note IGRAPH_STATUSF --- +Report the status from an igraph function::) macro. + + +File: igraph-docs.info, Node: Setting up status handlers, Next: Invoking the status handler, Prev: Status reporting, Up: Status handlers + +34.3.2 Setting up status handlers +--------------------------------- + +* Menu: + +* igraph_status_handler_t --- The type of the igraph status handler functions:: +* igraph_set_status_handler -- Install of uninstall a status handler function.: igraph_set_status_handler --- Install of uninstall a status handler function_. +* igraph_status_handler_stderr -- A simple predefined status handler function.: igraph_status_handler_stderr --- A simple predefined status handler function_. + + +File: igraph-docs.info, Node: igraph_status_handler_t --- The type of the igraph status handler functions, Next: igraph_set_status_handler --- Install of uninstall a status handler function_, Up: Setting up status handlers + +34.3.2.1 igraph_status_handler_t -- The type of the igraph status handler functions +................................................................................... + + + typedef igraph_error_t igraph_status_handler_t(const char *message, void *data); + + *Arguments:. * + +‘message’: + The status message. + +‘data’: + Additional context, with user-defined semantics. Existing igraph + functions pass a null pointer here. + + *Returns:. * + +‘’ + Error code. The current calculation will abort if you return + anything else than ‘IGRAPH_SUCCESS’ here. + + +File: igraph-docs.info, Node: igraph_set_status_handler --- Install of uninstall a status handler function_, Next: igraph_status_handler_stderr --- A simple predefined status handler function_, Prev: igraph_status_handler_t --- The type of the igraph status handler functions, Up: Setting up status handlers + +34.3.2.2 igraph_set_status_handler -- Install of uninstall a status handler function. +..................................................................................... + + + igraph_status_handler_t * + igraph_set_status_handler(igraph_status_handler_t new_handler); + + To uninstall the currently installed status handler, call this +function with a null pointer. + + *Arguments:. * + +‘new_handler’: + The status handler function to install. + + *Returns:. * + +‘’ + The previously installed status handler function. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_status_handler_stderr --- A simple predefined status handler function_, Prev: igraph_set_status_handler --- Install of uninstall a status handler function_, Up: Setting up status handlers + +34.3.2.3 igraph_status_handler_stderr -- A simple predefined status handler function. +..................................................................................... + + + igraph_error_t igraph_status_handler_stderr(const char *message, void *data); + + A simple status handler function that writes the status message to +the standard error. + + *Arguments:. * + +‘message’: + The status message. + +‘data’: + Additional context, with user-defined semantics. Existing igraph + functions pass a null pointer here. + + *Returns:. * + +‘’ + Error code. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: Invoking the status handler, Prev: Setting up status handlers, Up: Status handlers + +34.3.3 Invoking the status handler +---------------------------------- + +* Menu: + +* IGRAPH_STATUS -- Report the status of an igraph function.: IGRAPH_STATUS --- Report the status of an igraph function_. +* IGRAPH_STATUSF --- Report the status from an igraph function:: +* igraph_status -- Reports status from an igraph function.: igraph_status --- Reports status from an igraph function_. +* igraph_statusf -- Report status, more flexible printf-like version.: igraph_statusf --- Report status; more flexible printf-like version_. + + +File: igraph-docs.info, Node: IGRAPH_STATUS --- Report the status of an igraph function_, Next: IGRAPH_STATUSF --- Report the status from an igraph function, Up: Invoking the status handler + +34.3.3.1 IGRAPH_STATUS -- Report the status of an igraph function. +.................................................................. + + + #define IGRAPH_STATUS(message, data) + + Typically this function is called only a handful of times from an +igraph function. E.g. if an algorithm has three major steps, then it +is logical to call it three times, to signal the three major steps. + + *Arguments:. * + +‘message’: + The status message. + +‘data’: + Additional context, with user-defined semantics. Existing igraph + functions pass a null pointer here. + + *Returns:. * + +‘’ + If the status handler returns with a value other than + ‘IGRAPH_SUCCESS’, then the function that called this macro returns + as well, with the same error code, after cleaning up all allocated + memory as needed. + + +File: igraph-docs.info, Node: IGRAPH_STATUSF --- Report the status from an igraph function, Next: igraph_status --- Reports status from an igraph function_, Prev: IGRAPH_STATUS --- Report the status of an igraph function_, Up: Invoking the status handler + +34.3.3.2 IGRAPH_STATUSF -- Report the status from an igraph function +.................................................................... + + + #define IGRAPH_STATUSF(args) + + This is the more flexible version of ‘IGRAPH_STATUS()’ (*note +IGRAPH_STATUS --- Report the status of an igraph function_::), having a +printf-like syntax. As this macro takes variable number of arguments, +they must be all supplied as a single argument, enclosed in parentheses. +‘igraph_statusf()’ (*note igraph_statusf --- Report status; more +flexible printf-like version_::) is then called with the given +arguments. + + *Arguments:. * + +‘args’: + The arguments to pass to ‘igraph_statusf()’ (*note igraph_statusf + --- Report status; more flexible printf-like version_::). + + *Returns:. * + +‘’ + If the status handler returns with a value other than + ‘IGRAPH_SUCCESS’, then the function that called this macro returns + as well, with the same error code, after cleaning up all allocated + memory as needed. + + +File: igraph-docs.info, Node: igraph_status --- Reports status from an igraph function_, Next: igraph_statusf --- Report status; more flexible printf-like version_, Prev: IGRAPH_STATUSF --- Report the status from an igraph function, Up: Invoking the status handler + +34.3.3.3 igraph_status -- Reports status from an igraph function. +................................................................. + + + igraph_error_t igraph_status(const char *message, void *data); + + It calls the installed status handler function, if there is one. +Otherwise it does nothing. Note that the standard way to report the +status from an igraph function is the ‘IGRAPH_STATUS’ (*note +IGRAPH_STATUS --- Report the status of an igraph function_::) or +‘IGRAPH_STATUSF’ (*note IGRAPH_STATUSF --- Report the status from an +igraph function::) macro, as these take care of cleaning up allocated +memory from the calling function if the status handler returns with an +error code. + + *Arguments:. * + +‘message’: + The status message. + +‘data’: + Additional context, with user-defined semantics. Existing igraph + functions pass a null pointer here. + + *Returns:. * + +‘’ + Error code from the status handler function, or ‘IGRAPH_SUCCESS’ if + no status handler function was registered. + + Time complexity: O(1). + + +File: igraph-docs.info, Node: igraph_statusf --- Report status; more flexible printf-like version_, Prev: igraph_status --- Reports status from an igraph function_, Up: Invoking the status handler + +34.3.3.4 igraph_statusf -- Report status, more flexible printf-like version. +............................................................................ + + + igraph_error_t igraph_statusf(const char *message, void *data, ...); + + This is the more flexible version of ‘igraph_status()’ (*note +igraph_status --- Reports status from an igraph function_::), that has a +syntax similar to the ‘printf’ standard C library function. It +substitutes the values of the additional arguments into the ‘message’ +template string and calls ‘igraph_status()’ (*note igraph_status --- +Reports status from an igraph function_::). + + *Arguments:. * + +‘message’: + Status message template string, the syntax is the same as for the + ‘printf’ function. + +‘data’: + Additional context, with user-defined semantics. Existing igraph + functions pass a null pointer here. + +‘...’: + The additional arguments to fill the template given in the + ‘message’ argument. + + *Returns:. * + +‘’ + Error code from the status handler function, or ‘IGRAPH_SUCCESS’ if + no status handler function was registered. + + +File: igraph-docs.info, Node: Glossary, Next: Licenses for igraph and this manual, Prev: Advanced igraph programming, Up: Top + +35 Glossary +*********** + +This glossary defines common terms used throughout the igraph +documentation. + + • *attribute*: A piece of data associated with a vertex, an edge, or + the graph itself. The igraph C library currently supports numeric, + string and Boolean attribute values, and provides a means for + implementing attribute handlers that support custom types. + + • *adjacent*: Two vertices are called *adjacent* if there is an edge + connecting them. This term describes a vertex-to-vertex relation. + + • *adjacency list*: A data structure that associates a list of + neighbours (i.e. adjacent vertices) to each vertex. + + • *adjacency matrix*: A representation of a graph as a square matrix. + ‘A_ij’ gives the number of edge endpoints connecting from the ‘i’th + vertex to the ‘j’th vertex. Conventionally, the diagonal of the + adjacency matrix of an undirected graph contains _twice_ the number + of self-loops. All igraph functions follow this convention unless + noted otherwise. + + • *biadjacency matrix*: Analogous to the adjacency matrix, but used + for bipartite graphs. Element ‘B_ij’ gives the number of edges + from the ‘i’th vertex of the first group to the ‘j’th vertex of the + second group. + + • *bipartite graph*: A graph whose vertices can be partitioned into + two groups in such a way that connections are present only between + members of different groups. + + • *complete graph*: Also called *full graph* within the context of + igraph, a graph in which all pairs of vertices are connected to + each other. + + • *connected graph*: A connected graph consists of a single + component, in which any vertex is reachable from any other. In + igraph, the null graph is not considered connected, as it has not + one, but zero components. + + • *edge*: A *connection* between two vertices, also called a *link*. + In igraph, edges are referred to by integer indices called *edge + IDs*. + + • *finalizer stack*: A global stack used internally by igraph to keep + track of currently allocated objects and their destructors, so that + they can be automatically destroyed in case of an error. + + • *game*: Within igraph, this term is used for stochastic graph + generators, i.e. functions that sample from random graph models. + + • *graph* or *network*: A set of vertices with connections between + them. In igraph, graphs may carry associated data in the form of + vertex, edge or graph attributes. + + • *incident*: An edge is called *incident* to the vertices that are + its endpoints. This term describes a vertex-to-edge relation. + + • *incidence list*: A data structure that associates a list of + incident edges to each vertex. + + • *incidence matrix*: A matrix describing the incidence relation + between vertices (rows) and edges (columns). + + • *membership vector*: Membership vectors are a means of encoding a + partitioning of items, usually vertices, into several groups. The + ‘i’th element of the vector gives an integer identifier of the + group the ‘i’th vertex belongs to. Membership vectors are + typically used to describe a vertex clustering obtained through + community detection, or by identifying the connected components of + a graph. + + • *multi-edges* or *parallel edges*: More than one edge connecting + the same two vertices. In a directed graph, ‘a -> b, a -> b’ are + considered parallel edges, but ‘a -> b, a <- b’ are not. + + • *null graph*: A graph with no vertices (and no edges). + + • *self-loop*, *self-edge*, or simply *loop*: An edge that connects a + vertex to itself. + + • *simple graph*: A graph that does not have self-loops or + multi-edges. + + • *singleton graph*: A graph having a single vertex. This term + usually refers to a single vertex with no edges, but note that + self-loops may in principle be present. + + • *vertex*: Graphs consist of vertices, also called *nodes*, that are + connected to each other. In igraph, vertices are referred to by + integer indices called *vertex IDs*. + + +File: igraph-docs.info, Node: Licenses for igraph and this manual, Next: Concept index, Prev: Glossary, Up: Top + +36 Licenses for igraph and this manual +************************************** + +* Menu: + +* THE GNU GENERAL PUBLIC LICENSE:: +* The GNU Free Documentation License:: + + +File: igraph-docs.info, Node: THE GNU GENERAL PUBLIC LICENSE, Next: The GNU Free Documentation License, Up: Licenses for igraph and this manual + +36.1 THE GNU GENERAL PUBLIC LICENSE +=================================== + +* Menu: + +* Preamble:: +* GNU GENERAL PUBLIC LICENSE:: +* How to Apply These Terms to Your New Programs:: + + +File: igraph-docs.info, Node: Preamble, Next: GNU GENERAL PUBLIC LICENSE, Up: THE GNU GENERAL PUBLIC LICENSE + +36.1.1 Preamble +--------------- + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software-to +make sure the software is free for all its users. This General Public +License applies to most of the Free Software Foundation's software and +to any other program whose authors commit to using it. (Some other Free +Software Foundation software is covered by the GNU Library General +Public License instead.) You can apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it if +you want it, that you can change the software or use pieces of it in new +free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, +and (2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + +File: igraph-docs.info, Node: GNU GENERAL PUBLIC LICENSE, Next: How to Apply These Terms to Your New Programs, Prev: Preamble, Up: THE GNU GENERAL PUBLIC LICENSE + +36.1.2 GNU GENERAL PUBLIC LICENSE +--------------------------------- + +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed under +the terms of this General Public License. The "Program", below, refers +to any such program or work, and a "work based on the Program" means +either the Program or any derivative work under copyright law: that is +to say, a work containing the Program or a portion of it, either +verbatim or with modifications and/or translated into another language. +(Hereinafter, translation is included without limitation in the term +"modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of running +the Program is not restricted, and the output from the Program is +covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the notices +that refer to this License and to the absence of any warranty; and give +any other recipients of the Program a copy of this License along with +the Program. + + You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and distribute +such modifications or work under the terms of Section 1 above, provided +that you also meet all of these conditions: + + 1. You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + 2. You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + 3. If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive + use in the most ordinary way, to print or display an announcement + including an appropriate copyright notice and a notice that there + is no warranty (or else, saying that you provide a warranty) and + that users may redistribute the program under these conditions, and + telling the user how to view a copy of this License. (Exception: + if the Program itself is interactive but does not normally print + such an announcement, your work based on the Program is not + required to print an announcement.) + + These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, and +can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based on +the Program, the distribution of the whole must be on the terms of this +License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + + In addition, mere aggregation of another work not based on the +Program with the Program (or with a work based on the Program) on a +volume of a storage or distribution medium does not bring the other work +under the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + 1. Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software + interchange; or, + + 2. Accompany it with a written offer, valid for at least three years, + to give any third party, for a charge no more than your cost of + physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + 3. Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed + only for noncommercial distribution and only if you received the + program in object code or executable form with such an offer, in + accord with Subsection b above.) + + The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to control +compilation and installation of the executable. However, as a special +exception, the source code distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies the +executable. + + If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent access +to copy the source code from the same place counts as distribution of +the source code, even though third parties are not compelled to copy the +source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this License +will not have their licenses terminated so long as such parties remain +in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and all +its terms and conditions for copying, distributing or modifying the +Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further restrictions +on the recipients' exercise of the rights granted herein. You are not +responsible for enforcing compliance by third parties to this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent license +would not permit royalty-free redistribution of the Program by all those +who receive copies directly or indirectly through you, then the only way +you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + + If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + + It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is implemented +by public license practices. Many people have made generous +contributions to the wide range of software distributed through that +system in reliance on consistent application of that system; it is up to +the author/donor to decide if he or she is willing to distribute +software through any other system and a licensee cannot impose that +choice. + + This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License may +add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among countries +not thus excluded. In such case, this License incorporates the +limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies a version number of this License which applies to it +and "any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a version +number of this License, you may choose any version ever published by the +Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the +author to ask for permission. For software which is copyrighted by the +Free Software Foundation, write to the Free Software Foundation; we +sometimes make exceptions for this. Our decision will be guided by the +two goals of preserving the free status of all derivatives of our free +software and of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH +YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR +DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM +(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR +OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + +File: igraph-docs.info, Node: How to Apply These Terms to Your New Programs, Prev: GNU GENERAL PUBLIC LICENSE, Up: THE GNU GENERAL PUBLIC LICENSE + +36.1.3 How to Apply These Terms to Your New Programs +---------------------------------------------------- + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Also add information on how to contact you by electronic and paper +mail. + + If the program is interactive, make it output a short notice like +this when it starts in an interactive mode: + + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + + The hypothetical commands 'show w' and 'show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than 'show w' and 'show +c'; they could even be mouse-clicks or menu items-whatever suits your +program. + + You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the program, +if necessary. Here is a sample; alter the names: + + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Library General Public License instead of this License. + + +File: igraph-docs.info, Node: The GNU Free Documentation License, Prev: THE GNU GENERAL PUBLIC LICENSE, Up: Licenses for igraph and this manual + +36.2 The GNU Free Documentation License +======================================= + +* Menu: + +* 0. PREAMBLE: 0_ PREAMBLE. +* 1. APPLICABILITY AND DEFINITIONS: 1_ APPLICABILITY AND DEFINITIONS. +* 2. VERBATIM COPYING: 2_ VERBATIM COPYING. +* 3. COPYING IN QUANTITY: 3_ COPYING IN QUANTITY. +* 4. MODIFICATIONS: 4_ MODIFICATIONS. +* 5. COMBINING DOCUMENTS: 5_ COMBINING DOCUMENTS. +* 6. COLLECTIONS OF DOCUMENTS: 6_ COLLECTIONS OF DOCUMENTS. +* 7. AGGREGATION WITH INDEPENDENT WORKS: 7_ AGGREGATION WITH INDEPENDENT WORKS. +* 8. TRANSLATION: 8_ TRANSLATION. +* 9. TERMINATION: 9_ TERMINATION. +* 10. FUTURE REVISIONS OF THIS LICENSE: 10_ FUTURE REVISIONS OF THIS LICENSE. +* G.1.1 ADDENDUM; How to use this License for your documents: G_1_1 ADDENDUM; How to use this License for your documents. + + +File: igraph-docs.info, Node: 0_ PREAMBLE, Next: 1_ APPLICABILITY AND DEFINITIONS, Up: The GNU Free Documentation License + +36.2.1 0. PREAMBLE +------------------ + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to assure +everyone the effective freedom to copy and redistribute it, with or +without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible for +modifications made by others. + + This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft license +designed for free software. + + We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free program +should come with manuals providing the same freedoms that the software +does. But this License is not limited to software manuals; it can be +used for any textual work, regardless of subject matter or whether it is +published as a printed book. We recommend this License principally for +works whose purpose is instruction or reference. + + +File: igraph-docs.info, Node: 1_ APPLICABILITY AND DEFINITIONS, Next: 2_ VERBATIM COPYING, Prev: 0_ PREAMBLE, Up: The GNU Free Documentation License + +36.2.2 1. APPLICABILITY AND DEFINITIONS +--------------------------------------- + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, refers +to any such manual or work. Any member of the public is a licensee, and +is addressed as "you". You accept the license if you copy, modify or +distribute the work in a way requiring permission under copyright law. + + A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + + A "Secondary Section" is a named appendix or a front-matter section +of the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding them. + + The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice that +says that the Document is released under this License. If a section +does not fit the above definition of Secondary then it is not allowed to +be designated as Invariant. The Document may contain zero Invariant +Sections. If the Document does not identify any Invariant Sections then +there are none. + + The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may be +at most 5 words, and a Back-Cover Text may be at most 25 words. + + A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the general +public, that is suitable for revising the document straightforwardly +with generic text editors or (for images composed of pixels) generic +paint programs or (for drawings) some widely available drawing editor, +and that is suitable for input to text formatters or for automatic +translation to a variety of formats suitable for input to text +formatters. A copy made in an otherwise Transparent file format whose +markup, or absence of markup, has been arranged to thwart or discourage +subsequent modification by readers is not Transparent. An image format +is not Transparent if used for any substantial amount of text. A copy +that is not "Transparent" is called "Opaque". + + Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML or +XML using a publicly available DTD, and standard-conforming simple HTML, +PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the machine-generated +HTML, PostScript or PDF produced by some word processors for output +purposes only. + + The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in formats +which do not have any title page as such, "Title Page" means the text +near the most prominent appearance of the work's title, preceding the +beginning of the body of the text. + + A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + + The Document may include Warranty Disclaimers next to the notice +which states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this License, +but only as regards disclaiming warranties: any other implication that +these Warranty Disclaimers may have is void and has no effect on the +meaning of this License. + + +File: igraph-docs.info, Node: 2_ VERBATIM COPYING, Next: 3_ COPYING IN QUANTITY, Prev: 1_ APPLICABILITY AND DEFINITIONS, Up: The GNU Free Documentation License + +36.2.3 2. VERBATIM COPYING +-------------------------- + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies to +the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further copying +of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + + You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +File: igraph-docs.info, Node: 3_ COPYING IN QUANTITY, Next: 4_ MODIFICATIONS, Prev: 2_ VERBATIM COPYING, Up: The GNU Free Documentation License + +36.2.4 3. COPYING IN QUANTITY +----------------------------- + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover Texts: +Front-Cover Texts on the front cover, and Back-Cover Texts on the back +cover. Both covers must also clearly and legibly identify you as the +publisher of these copies. The front cover must present the full title +with all words of the title equally prominent and visible. You may add +other material on the covers in addition. Copying with changes limited +to the covers, as long as they preserve the title of the Document and +satisfy these conditions, can be treated as verbatim copying in other +respects. + + If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + + If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy a +computer-network location from which the general network-using public +has access to download using public-standard network protocols a +complete Transparent copy of the Document, free of added material. If +you use the latter option, you must take reasonably prudent steps, when +you begin distribution of Opaque copies in quantity, to ensure that this +Transparent copy will remain thus accessible at the stated location +until at least one year after the last time you distribute an Opaque +copy (directly or through your agents or retailers) of that edition to +the public. + + It is requested, but not required, that you contact the authors of +the Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +File: igraph-docs.info, Node: 4_ MODIFICATIONS, Next: 5_ COMBINING DOCUMENTS, Prev: 3_ COPYING IN QUANTITY, Up: The GNU Free Documentation License + +36.2.5 4. MODIFICATIONS +----------------------- + +You may copy and distribute a Modified Version of the Document under the +conditions of sections 2 and 3 above, provided that you release the +Modified Version under precisely this License, with the Modified Version +filling the role of the Document, thus licensing distribution and +modification of the Modified Version to whoever possesses a copy of it. +In addition, you must do these things in the Modified Version: + + 1. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. + + 2. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of + the Document (all of its principal authors, if it has fewer than + five), unless they release you from this requirement. + + 3. State on the Title page the name of the publisher of the Modified + Version, as the publisher. + + 4. Preserve all the copyright notices of the Document. + + 5. Add an appropriate copyright notice for your modifications adjacent + to the other copyright notices. + + 6. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. + + 7. Preserve in that license notice the full lists of Invariant + Sections and required Cover Texts given in the Document's license + notice. + + 8. Include an unaltered copy of this License. + + 9. Preserve the section Entitled "History", Preserve its Title, and + add to it an item stating at least the title, year, new authors, + and publisher of the Modified Version as given on the Title Page. + If there is no section Entitled "History" in the Document, create + one stating the title, year, authors, and publisher of the Document + as given on its Title Page, then add an item describing the + Modified Version as stated in the previous sentence. + + 10. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. + + 11. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. + + 12. Preserve all the Invariant Sections of the Document, unaltered in + their text and in their titles. Section numbers or the equivalent + are not considered part of the section titles. + + 13. Delete any section Entitled "Endorsements". Such a section may + not be included in the Modified Version. + + 14. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. + + 15. Preserve any Warranty Disclaimers. + + If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + + You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various parties-for +example, statements of peer review or that the text has been approved by +an organization as the authoritative definition of a standard. + + You may add a passage of up to five words as a Front-Cover Text, and +a passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of Front-Cover +Text and one of Back-Cover Text may be added by (or through arrangements +made by) any one entity. If the Document already includes a cover text +for the same cover, previously added by you or by arrangement made by +the same entity you are acting on behalf of, you may not add another; +but you may replace the old one, on explicit permission from the +previous publisher that added the old one. + + The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +File: igraph-docs.info, Node: 5_ COMBINING DOCUMENTS, Next: 6_ COLLECTIONS OF DOCUMENTS, Prev: 4_ MODIFICATIONS, Up: The GNU Free Documentation License + +36.2.6 5. COMBINING DOCUMENTS +----------------------------- + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its license +notice, and that you preserve all their Warranty Disclaimers. + + The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by adding +at the end of it, in parentheses, the name of the original author or +publisher of that section if known, or else a unique number. Make the +same adjustment to the section titles in the list of Invariant Sections +in the license notice of the combined work. + + In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +File: igraph-docs.info, Node: 6_ COLLECTIONS OF DOCUMENTS, Next: 7_ AGGREGATION WITH INDEPENDENT WORKS, Prev: 5_ COMBINING DOCUMENTS, Up: The GNU Free Documentation License + +36.2.7 6. COLLECTIONS OF DOCUMENTS +---------------------------------- + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + + You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +File: igraph-docs.info, Node: 7_ AGGREGATION WITH INDEPENDENT WORKS, Next: 8_ TRANSLATION, Prev: 6_ COLLECTIONS OF DOCUMENTS, Up: The GNU Free Documentation License + +36.2.8 7. AGGREGATION WITH INDEPENDENT WORKS +-------------------------------------------- + +A compilation of the Document or its derivatives with other separate and +independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright resulting +from the compilation is not used to limit the legal rights of the +compilation's users beyond what the individual works permit. When the +Document is included in an aggregate, this License does not apply to the +other works in the aggregate which are not themselves derivative works +of the Document. + + If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on covers +that bracket the Document within the aggregate, or the electronic +equivalent of covers if the Document is in electronic form. Otherwise +they must appear on printed covers that bracket the whole aggregate. + + +File: igraph-docs.info, Node: 8_ TRANSLATION, Next: 9_ TERMINATION, Prev: 7_ AGGREGATION WITH INDEPENDENT WORKS, Up: The GNU Free Documentation License + +36.2.9 8. TRANSLATION +--------------------- + +Translation is considered a kind of modification, so you may distribute +translations of the Document under the terms of section 4. Replacing +Invariant Sections with translations requires special permission from +their copyright holders, but you may include translations of some or all +Invariant Sections in addition to the original versions of these +Invariant Sections. You may include a translation of this License, and +all the license notices in the Document, and any Warranty Disclaimers, +provided that you also include the original English version of this +License and the original versions of those notices and disclaimers. In +case of a disagreement between the translation and the original version +of this License or a notice or disclaimer, the original version will +prevail. + + If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve its +Title (section 1) will typically require changing the actual title. + + +File: igraph-docs.info, Node: 9_ TERMINATION, Next: 10_ FUTURE REVISIONS OF THIS LICENSE, Prev: 8_ TRANSLATION, Up: The GNU Free Documentation License + +36.2.10 9. TERMINATION +---------------------- + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this License +will not have their licenses terminated so long as such parties remain +in full compliance. + + +File: igraph-docs.info, Node: 10_ FUTURE REVISIONS OF THIS LICENSE, Next: G_1_1 ADDENDUM; How to use this License for your documents, Prev: 9_ TERMINATION, Up: The GNU Free Documentation License + +36.2.11 10. FUTURE REVISIONS OF THIS LICENSE +-------------------------------------------- + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + + Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +File: igraph-docs.info, Node: G_1_1 ADDENDUM; How to use this License for your documents, Prev: 10_ FUTURE REVISIONS OF THIS LICENSE, Up: The GNU Free Documentation License + +36.2.12 G.1.1 ADDENDUM: How to use this License for your documents +------------------------------------------------------------------ + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and license +notices just after the title page: + + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + If you have Invariant Sections, Front-Cover Texts and Back-Cover +Texts, replace the "with...Texts." line with this: + + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + + If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + + If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of free +software license, such as the GNU General Public License, to permit +their use in free software. + + +File: igraph-docs.info, Node: Concept index, Prev: Licenses for igraph and this manual, Up: Top + +Index +***** + +[index] +* Menu: + +* add_edge: igraph_add_edge --- Adds a single edge to a graph_. + (line 6) +* add_edges: igraph_add_edges --- Adds edges to a graph object_. + (line 6) +* add_vertices: igraph_add_vertices --- Adds vertices to a graph_. + (line 6) +* adhesion: igraph_adhesion --- Graph adhesion; this is [almost] the same as edge connectivity_. + (line 6) +* adjacency: igraph_adjacency --- Creates a graph from an adjacency matrix_. + (line 6) +* adjacency_spectral_embedding: igraph_adjacency_spectral_embedding --- Adjacency spectral embedding. + (line 6) +* adjlist: igraph_adjlist --- Creates a graph from an adjacency list_. + (line 6) +* adjlist_clear: igraph_adjlist_clear --- Removes all edges from an adjacency list_. + (line 6) +* adjlist_destroy: igraph_adjlist_destroy --- Deallocates an adjacency list_. + (line 6) +* adjlist_get: igraph_adjlist_get --- Query a vector in an adjacency list_. + (line 6) +* adjlist_init: igraph_adjlist_init --- Constructs an adjacency list of vertices from a given graph_. + (line 6) +* adjlist_init_complementer: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_. + (line 6) +* adjlist_init_empty: igraph_adjlist_init_empty --- Initializes an empty adjacency list_. + (line 6) +* adjlist_init_from_inclist: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_. + (line 6) +* adjlist_simplify: igraph_adjlist_simplify --- Simplifies an adjacency list_. + (line 6) +* adjlist_size: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_. + (line 6) +* adjlist_sort: igraph_adjlist_sort --- Sorts each vector in an adjacency list_. + (line 6) +* all_minimal_st_separators: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_. + (line 6) +* all_st_cuts: igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph. + (line 6) +* all_st_mincuts: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_. + (line 6) +* almost_equals: igraph_almost_equals --- Compare two double-precision floats with a tolerance_. + (line 6) +* are_adjacent: igraph_are_adjacent --- Decides whether two vertices are adjacent_. + (line 6) +* arpack_function_t: igraph_arpack_function_t --- Type of the ARPACK callback function_. + (line 6) +* arpack_options_init: igraph_arpack_options_init --- Initialize ARPACK options_. + (line 6) +* arpack_options_t: igraph_arpack_options_t --- Options for ARPACK_. + (line 6) +* arpack_rnsolve: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_. + (line 6) +* arpack_rssolve: igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_. + (line 6) +* arpack_storage_destroy: igraph_arpack_storage_destroy --- Deallocate ARPACK storage_. + (line 6) +* arpack_storage_init: igraph_arpack_storage_init --- Initialize ARPACK storage_. + (line 6) +* arpack_storage_t: igraph_arpack_storage_t --- Storage for ARPACK_. + (line 6) +* arpack_unpack_complex: igraph_arpack_unpack_complex --- Makes the result of the non-symmetric ARPACK solver more readable_. + (line 6) +* articulation_points: igraph_articulation_points --- Finds the articulation points in a graph_. + (line 6) +* ASSERT: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_. + (line 6) +* assortativity: igraph_assortativity --- Assortativity based on numeric properties of vertices_. + (line 6) +* assortativity_degree: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_. + (line 6) +* assortativity_nominal: igraph_assortativity_nominal --- Assortativity of a graph based on vertex categories_. + (line 6) +* astar_heuristic_func_t: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_. + (line 6) +* asymmetric_preference_game: igraph_asymmetric_preference_game --- Generates a graph with asymmetric vertex types and connection preferences_. + (line 6) +* atlas: igraph_atlas --- Create a small graph from the Graph Atlas_. + (line 6) +* attribute_combination: igraph_attribute_combination --- Initialize attribute combination list and add records_. + (line 6) +* attribute_combination_add: igraph_attribute_combination_add --- Add combination record to attribute combination list_. + (line 6) +* attribute_combination_destroy: igraph_attribute_combination_destroy --- Destroy attribute combination list_. + (line 6) +* attribute_combination_init: igraph_attribute_combination_init --- Initialize attribute combination list_. + (line 6) +* attribute_combination_remove: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_. + (line 6) +* attribute_combination_type_t: igraph_attribute_combination_type_t --- The possible types of attribute combinations_. + (line 6) +* attribute_elemtype_t: igraph_attribute_elemtype_t --- Types of objects to which attributes can be attached_. + (line 6) +* attribute_record_destroy: igraph_attribute_record_destroy --- Destroys an attribute record_. + (line 6) +* attribute_record_init: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_. + (line 6) +* attribute_record_init_copy: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_. + (line 6) +* attribute_record_resize: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_. + (line 6) +* attribute_record_set_default_boolean: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_. + (line 6) +* attribute_record_set_default_numeric: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_. + (line 6) +* attribute_record_set_default_string: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_. + (line 6) +* attribute_record_set_name: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_. + (line 6) +* attribute_record_set_type: igraph_attribute_record_set_type --- Sets the type of an attribute record_. + (line 6) +* attribute_record_size: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_. + (line 6) +* attribute_record_t: igraph_attribute_record_t --- An attribute record holding the name; type and values of an attribute_. + (line 6) +* attribute_table_t: igraph_attribute_table_t --- Table of functions to perform operations on attributes_. + (line 6) +* attribute_type_t: igraph_attribute_type_t --- The possible types of the attributes_. + (line 6) +* automorphism_group: igraph_automorphism_group --- Automorphism group generators of a graph_. + (line 6) +* automorphism_group_bliss: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_. + (line 6) +* average_local_efficiency: igraph_average_local_efficiency --- Calculates the average local efficiency in a network_. + (line 6) +* average_path_length: igraph_average_path_length --- The average shortest path length between all vertex pairs_. + (line 6) +* avg_nearest_neighbor_degree: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_. + (line 6) +* barabasi_aging_game: igraph_barabasi_aging_game --- Preferential attachment with aging of vertices_. + (line 6) +* barabasi_game: igraph_barabasi_game --- Generates a graph based on the Barabási-Albert model_. + (line 6) +* beta_weighted_gabriel_graph: igraph_beta_weighted_gabriel_graph --- A Gabriel graph; with edges weighted by the β value at which it disappears_. + (line 6) +* betweenness: igraph_betweenness --- Betweenness centrality of some vertices_. + (line 6) +* betweenness_cutoff: igraph_betweenness_cutoff --- Range-limited betweenness centrality_. + (line 6) +* betweenness_subset: igraph_betweenness_subset --- Betweenness centrality for a subset of source and target vertices_. + (line 6) +* bfs: igraph_bfs --- Breadth-first search_. + (line 6) +* bfs_simple: igraph_bfs_simple --- Breadth-first search; single-source version. + (line 6) +* bfshandler_t: igraph_bfshandler_t --- Callback type for BFS function_. + (line 6) +* biadjacency: igraph_biadjacency --- Creates a bipartite graph from a bipartite adjacency matrix_. + (line 6) +* bibcoupling: igraph_bibcoupling --- Bibliographic coupling_. + (line 6) +* biconnected_components: igraph_biconnected_components --- Calculates biconnected components_. + (line 6) +* bipartite_game_gnm: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_. + (line 6) +* bipartite_game_gnp: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_. + (line 6) +* bipartite_iea_game: igraph_bipartite_iea_game --- Generates a random bipartite multigraph through independent edge assignment_. + (line 6) +* bipartite_projection: igraph_bipartite_projection --- Create one or both projections of a bipartite [two-mode] network_. + (line 6) +* bipartite_projection_size: igraph_bipartite_projection_size --- Calculate the number of vertices and edges in the bipartite projections_. + (line 6) +* BIT_CLEAR: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_. + (line 6) +* BIT_MASK: IGRAPH_BIT_MASK --- Computes mask used to access a specific bit of an integer_. + (line 6) +* BIT_NSLOTS: IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a specified number of bits_. + (line 6) +* BIT_SET: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_. + (line 6) +* BIT_SLOT: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_. + (line 6) +* BIT_TEST: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_. + (line 6) +* bitset_and: igraph_bitset_and --- Bitwise AND of two bitsets_. + (line 6) +* bitset_capacity: igraph_bitset_capacity --- Returns the allocated capacity of the bitset_. + (line 6) +* bitset_countl_one: igraph_bitset_countl_one --- The number of leading ones in the bitset_. + (line 6) +* bitset_countl_zero: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_. + (line 6) +* bitset_countr_one: igraph_bitset_countr_one --- The number of trailing ones in the bitset_. + (line 6) +* bitset_countr_zero: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_. + (line 6) +* bitset_destroy: igraph_bitset_destroy --- Destroys a bitset object_. + (line 6) +* bitset_fill: igraph_bitset_fill --- Fills a bitset with a constant value_. + (line 6) +* bitset_init: igraph_bitset_init --- Initializes a bitset object [constructor]_. + (line 6) +* bitset_init_copy: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_. + (line 6) +* bitset_is_all_one: igraph_bitset_is_all_one --- Are all bits ones?. + (line 6) +* bitset_is_all_zero: igraph_bitset_is_all_zero --- Are all bits zeros?. + (line 6) +* bitset_is_any_one: igraph_bitset_is_any_one --- Are any bits ones?. + (line 6) +* bitset_is_any_zero: igraph_bitset_is_any_zero --- Are any bits zeros?. + (line 6) +* bitset_not: igraph_bitset_not --- Bitwise negation of a bitset_. + (line 6) +* bitset_null: igraph_bitset_null --- Clears all bits in a bitset_. + (line 6) +* bitset_or: igraph_bitset_or --- Bitwise OR of two bitsets_. + (line 6) +* bitset_popcount: igraph_bitset_popcount --- The population count of the bitset_. + (line 6) +* bitset_reserve: igraph_bitset_reserve --- Reserves memory for a bitset_. + (line 6) +* bitset_resize: igraph_bitset_resize --- Resizes the bitset_. + (line 6) +* bitset_size: igraph_bitset_size --- Returns the length of the bitset_. + (line 6) +* bitset_update: igraph_bitset_update --- Update a bitset from another one_. + (line 6) +* bitset_xor: igraph_bitset_xor --- Bitwise XOR of two bitsets_. + (line 6) +* blas_ddot: igraph_blas_ddot --- Dot product of two vectors_. + (line 6) +* blas_dgemm: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_. + (line 6) +* blas_dgemv: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_. + (line 6) +* blas_dgemv_array: igraph_blas_dgemv_array --- Matrix-vector multiplication using BLAS; array version_. + (line 6) +* blas_dnrm2: igraph_blas_dnrm2 --- Euclidean norm of a vector_. + (line 6) +* bliss_info_t: igraph_bliss_info_t --- Information about a Bliss run_. + (line 6) +* bliss_sh_t: igraph_bliss_sh_t --- Splitting heuristics for Bliss_. + (line 6) +* bond_percolation: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_. + (line 6) +* bridges: igraph_bridges --- Finds all bridges in a graph_. + (line 6) +* callaway_traits_game: igraph_callaway_traits_game --- Simulates a growing network with vertex types_. + (line 6) +* calloc: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_. + (line 6) +* canonical_permutation: igraph_canonical_permutation --- Canonical permutation of a graph_. + (line 6) +* canonical_permutation_bliss: igraph_canonical_permutation_bliss --- Canonical permutation using Bliss_. + (line 6) +* cattribute_EAB: igraph_cattribute_EAB --- Query a boolean edge attribute_. + (line 6) +* cattribute_EAB_set: igraph_cattribute_EAB_set --- Set a boolean edge attribute_. + (line 6) +* cattribute_EAB_setv: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_. + (line 6) +* cattribute_EABV: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_. + (line 6) +* cattribute_EAN: igraph_cattribute_EAN --- Query a numeric edge attribute_. + (line 6) +* cattribute_EAN_set: igraph_cattribute_EAN_set --- Set a numeric edge attribute_. + (line 6) +* cattribute_EAN_setv: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_. + (line 6) +* cattribute_EANV: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_. + (line 6) +* cattribute_EAS: igraph_cattribute_EAS --- Query a string edge attribute_. + (line 6) +* cattribute_EAS_set: igraph_cattribute_EAS_set --- Set a string edge attribute_. + (line 6) +* cattribute_EAS_setv: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_. + (line 6) +* cattribute_EASV: igraph_cattribute_EASV --- Query a string edge attribute for many edges_. + (line 6) +* cattribute_GAB: igraph_cattribute_GAB --- Query a boolean graph attribute_. + (line 6) +* cattribute_GAB_set: igraph_cattribute_GAB_set --- Set a boolean graph attribute_. + (line 6) +* cattribute_GAN: igraph_cattribute_GAN --- Query a numeric graph attribute_. + (line 6) +* cattribute_GAN_set: igraph_cattribute_GAN_set --- Set a numeric graph attribute_. + (line 6) +* cattribute_GAS: igraph_cattribute_GAS --- Query a string graph attribute_. + (line 6) +* cattribute_GAS_set: igraph_cattribute_GAS_set --- Set a string graph attribute_. + (line 6) +* cattribute_has_attr: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_. + (line 6) +* cattribute_list: igraph_cattribute_list --- List all attributes_. + (line 6) +* cattribute_remove_all: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_. + (line 6) +* cattribute_remove_e: igraph_cattribute_remove_e --- Remove an edge attribute_. + (line 6) +* cattribute_remove_g: igraph_cattribute_remove_g --- Remove a graph attribute_. + (line 6) +* cattribute_remove_v: igraph_cattribute_remove_v --- Remove a vertex attribute_. + (line 6) +* cattribute_VAB: igraph_cattribute_VAB --- Query a boolean vertex attribute_. + (line 6) +* cattribute_VAB_set: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_. + (line 6) +* cattribute_VAB_setv: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_. + (line 6) +* cattribute_VABV: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_. + (line 6) +* cattribute_VAN: igraph_cattribute_VAN --- Query a numeric vertex attribute_. + (line 6) +* cattribute_VAN_set: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_. + (line 6) +* cattribute_VAN_setv: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_. + (line 6) +* cattribute_VANV: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_. + (line 6) +* cattribute_VAS: igraph_cattribute_VAS --- Query a string vertex attribute_. + (line 6) +* cattribute_VAS_set: igraph_cattribute_VAS_set --- Set a string vertex attribute_. + (line 6) +* cattribute_VAS_setv: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_. + (line 6) +* cattribute_VASV: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_. + (line 6) +* centralization: igraph_centralization --- Calculate the centralization score from the node level scores_. + (line 6) +* centralization_betweenness: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_. + (line 6) +* centralization_betweenness_tmax: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_. + (line 6) +* centralization_closeness: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_. + (line 6) +* centralization_closeness_tmax: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_. + (line 6) +* centralization_degree: igraph_centralization_degree --- Calculate vertex degree and graph centralization_. + (line 6) +* centralization_degree_tmax: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_. + (line 6) +* centralization_eigenvector_centrality: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_. + (line 6) +* centralization_eigenvector_centrality_tmax: igraph_centralization_eigenvector_centrality_tmax --- Theoretical maximum centralization for eigenvector centrality_. + (line 6) +* CHECK: IGRAPH_CHECK --- Checks the return value of a function call_. + (line 6) +* CHECK_CALLBACK: IGRAPH_CHECK_CALLBACK --- Checks the return value of a callback_. + (line 6) +* chung_lu_game: igraph_chung_lu_game --- Samples graphs from the Chung-Lu model_. + (line 6) +* circle_beta_skeleton: igraph_circle_beta_skeleton --- The circle based β-skeleton of a 2D spatial point set_. + (line 6) +* circulant: igraph_circulant --- Creates a circulant graph_. + (line 6) +* cited_type_game: igraph_cited_type_game --- Simulates a citation based on vertex types_. + (line 6) +* citing_cited_type_game: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_. + (line 6) +* clique_handler_t: igraph_clique_handler_t --- Type of clique handler functions_. + (line 6) +* clique_number: igraph_clique_number --- Finds the clique number of the graph_. + (line 6) +* clique_size_hist: igraph_clique_size_hist --- Counts cliques of each size in the graph_. + (line 6) +* cliques: igraph_cliques --- Finds all or some cliques in a graph_. + (line 6) +* cliques_callback: igraph_cliques_callback --- Calls a function for each clique in the graph_. + (line 6) +* closeness: igraph_closeness --- Closeness centrality calculations for some vertices_. + (line 6) +* closeness_cutoff: igraph_closeness_cutoff --- Range limited closeness centrality_. + (line 6) +* cmp_epsilon: igraph_cmp_epsilon --- Compare two double-precision floats with a tolerance_. + (line 6) +* cocitation: igraph_cocitation --- Cocitation coupling_. + (line 6) +* cohesion: igraph_cohesion --- Graph cohesion; this is the same as vertex connectivity_. + (line 6) +* cohesive_blocks: igraph_cohesive_blocks --- Identifies the hierarchical cohesive block structure of a graph_. + (line 6) +* coloring_greedy_t: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_. + (line 6) +* community_eb_get_merges: igraph_community_eb_get_merges --- Calculating the merges; i_e_ the dendrogram for an edge betweenness community structure_. + (line 6) +* community_edge_betweenness: igraph_community_edge_betweenness --- Community finding based on edge betweenness_. + (line 6) +* community_fastgreedy: igraph_community_fastgreedy --- Finding community structure by greedy optimization of modularity_. + (line 6) +* community_fluid_communities: igraph_community_fluid_communities --- Community detection based on fluids interacting on the graph_. + (line 6) +* community_infomap: igraph_community_infomap --- Community structure that minimizes the expected description length of a random walker trajectory_. + (line 6) +* community_label_propagation: igraph_community_label_propagation --- Community detection based on label propagation_. + (line 6) +* community_leading_eigenvector: igraph_community_leading_eigenvector --- Leading eigenvector community finding [proper version]_. + (line 6) +* community_leading_eigenvector_callback_t: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_. + (line 6) +* community_leiden: igraph_community_leiden --- Finding community structure using the Leiden algorithm_. + (line 6) +* community_leiden_simple: igraph_community_leiden_simple --- Finding community structure using the Leiden algorithm; simple interface_. + (line 6) +* community_multilevel: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_. + (line 6) +* community_optimal_modularity: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_. + (line 6) +* community_spinglass: igraph_community_spinglass --- Community detection based on statistical mechanics_. + (line 6) +* community_spinglass_single: igraph_community_spinglass_single --- Community of a single node based on statistical mechanics_. + (line 6) +* community_to_membership: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_. + (line 6) +* community_voronoi: igraph_community_voronoi --- Finds communities using Voronoi partitioning_. + (line 6) +* community_walktrap: igraph_community_walktrap --- Community finding using a random walk based similarity measure_. + (line 6) +* compare_communities: igraph_compare_communities --- Compares community structures using various metrics_. + (line 6) +* complementer: igraph_complementer --- Creates the complementer of a graph_. + (line 6) +* complex_almost_equals: igraph_complex_almost_equals --- Compare two complex numbers with a tolerance_. + (line 6) +* compose: igraph_compose --- Calculates the composition of two graphs_. + (line 6) +* connect_neighborhood: igraph_connect_neighborhood --- Connects each vertex to its neighborhood_. + (line 6) +* connected_components: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_. + (line 6) +* constraint: igraph_constraint --- Burt's constraint scores_. + (line 6) +* contract_vertices: igraph_contract_vertices --- Replace multiple vertices with a single one_. + (line 6) +* convergence_degree: igraph_convergence_degree --- Calculates the convergence degree of each edge in a graph_. + (line 6) +* convex_hull_2d: igraph_convex_hull_2d --- Determines the convex hull of a given set of points in the 2D plane_. + (line 6) +* copy: igraph_copy --- Creates an exact [deep] copy of a graph_. + (line 6) +* coreness: igraph_coreness --- The coreness of the vertices in a graph_. + (line 6) +* correlated_game: igraph_correlated_game --- Generates a random graph correlated to an existing graph_. + (line 6) +* correlated_pair_game: igraph_correlated_pair_game --- Generates pairs of correlated random graphs_. + (line 6) +* count_adjacent_triangles: igraph_count_adjacent_triangles --- Count the number of triangles a vertex is part of_. + (line 6) +* count_automorphisms: igraph_count_automorphisms --- Number of automorphisms of a graph_. + (line 6) +* count_automorphisms_bliss: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_. + (line 6) +* count_isomorphisms_vf2: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_. + (line 6) +* count_loops: igraph_count_loops --- Counts the self-loops in the graph_. + (line 6) +* count_multiple: igraph_count_multiple --- The multiplicity of some edges in a graph_. + (line 6) +* count_multiple_1: igraph_count_multiple_1 --- The multiplicity of a single edge in a graph_. + (line 6) +* count_reachable: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_. + (line 6) +* count_subisomorphisms_vf2: igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF2. + (line 6) +* count_triangles: igraph_count_triangles --- Counts triangles in a graph_. + (line 6) +* create: igraph_create --- Creates a graph with the specified edges_. + (line 6) +* create_bipartite: igraph_create_bipartite --- Create a bipartite graph_. + (line 6) +* cycle_graph: igraph_cycle_graph --- A cycle graph C_n_. + (line 6) +* cycle_handler_t: igraph_cycle_handler_t --- Type of cycle handler functions_. + (line 6) +* de_bruijn: igraph_de_bruijn --- Generate a de Bruijn graph_. + (line 6) +* decompose: igraph_decompose --- Decomposes a graph into connected components_. + (line 6) +* degree: igraph_degree --- The degree of some vertices in a graph_. + (line 6) +* degree_1: igraph_degree_1 --- The degree of of a single vertex in the graph_. + (line 6) +* degree_correlation_vector: igraph_degree_correlation_vector --- Degree correlation function_. + (line 6) +* degree_sequence_game: igraph_degree_sequence_game --- Generates a random graph with a given degree sequence_. + (line 6) +* DELALL: DELALL --- Remove all attributes_. + (line 6) +* delaunay_graph: igraph_delaunay_graph --- Computes the Delaunay graph of a spatial point set_. + (line 6) +* DELEA: DELEA --- Remove an edge attribute_. + (line 6) +* DELEAS: DELEAS --- Remove all edge attributes_. + (line 6) +* delete_edges: igraph_delete_edges --- Removes edges from a graph_. + (line 6) +* delete_vertices: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_. + (line 6) +* delete_vertices_map: igraph_delete_vertices_map --- Removes some vertices [with all their edges] from the graph_. + (line 6) +* DELGA: DELGA --- Remove a graph attribute_. + (line 6) +* DELGAS: DELGAS --- Remove all graph attributes_. + (line 6) +* DELVA: DELVA --- Remove a vertex attribute_. + (line 6) +* DELVAS: DELVAS --- Remove all vertex attributes_. + (line 6) +* density: igraph_density --- Calculate the density of a graph_. + (line 6) +* destroy: igraph_destroy --- Frees the memory allocated for a graph object_. + (line 6) +* dfs: igraph_dfs --- Depth-first search_. + (line 6) +* dfshandler_t: igraph_dfshandler_t --- Callback type for the DFS function_. + (line 6) +* diameter: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_. + (line 6) +* difference: igraph_difference --- Calculates the difference of two graphs_. + (line 6) +* dim_select: igraph_dim_select --- Dimensionality selection_. + (line 6) +* disjoint_union: igraph_disjoint_union --- Creates the union of two disjoint graphs_. + (line 6) +* disjoint_union_many: igraph_disjoint_union_many --- The disjoint union of many graphs_. + (line 6) +* distances: igraph_distances --- Length of the shortest paths between vertices_. + (line 6) +* distances_bellman_ford: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_. + (line 6) +* distances_cutoff: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_. + (line 6) +* distances_dijkstra: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_. + (line 6) +* distances_dijkstra_cutoff: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_. + (line 6) +* distances_floyd_warshall: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_. + (line 6) +* distances_johnson: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_. + (line 6) +* diversity: igraph_diversity --- Structural diversity index of the vertices_. + (line 6) +* dominator_tree: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_. + (line 6) +* dot_product_game: igraph_dot_product_game --- Generates a random dot product graph_. + (line 6) +* dqueue_back: igraph_dqueue_back --- Tail of the queue_. + (line 6) +* dqueue_clear: igraph_dqueue_clear --- Remove all elements from the queue_. + (line 6) +* dqueue_destroy: igraph_dqueue_destroy --- Destroy a double ended queue_. + (line 6) +* dqueue_empty: igraph_dqueue_empty --- Decide whether the queue is empty_. + (line 6) +* dqueue_full: igraph_dqueue_full --- Check whether the queue is full_. + (line 6) +* dqueue_get: igraph_dqueue_get --- Access an element in a queue_. + (line 6) +* dqueue_head: igraph_dqueue_head --- Head of the queue_. + (line 6) +* dqueue_init: igraph_dqueue_init --- Initialize a double ended queue [deque]_. + (line 6) +* dqueue_pop: igraph_dqueue_pop --- Remove the head_. + (line 6) +* dqueue_pop_back: igraph_dqueue_pop_back --- Removes the tail_. + (line 6) +* dqueue_push: igraph_dqueue_push --- Appends an element_. + (line 6) +* dqueue_size: igraph_dqueue_size --- Number of elements in the queue_. + (line 6) +* dyad_census: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_. + (line 6) +* EAB: EAB --- Query a boolean edge attribute_. + (line 6) +* EABV: EABV --- Query a boolean edge attribute for all edges_. + (line 6) +* EAN: EAN --- Query a numeric edge attribute_. + (line 6) +* EANV: EANV --- Query a numeric edge attribute for all edges_. + (line 6) +* EAS: EAS --- Query a string edge attribute_. + (line 6) +* EASV: EASV --- Query a string edge attribute for all edges_. + (line 6) +* ecc: igraph_ecc --- Edge clustering coefficient of some edges_. + (line 6) +* eccentricity: igraph_eccentricity --- Eccentricity of some vertices_. + (line 6) +* ecount: igraph_ecount --- The number of edges in a graph_. + (line 6) +* ECOUNT_MAX: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_. + (line 6) +* edge: igraph_edge --- Returns the head and tail vertices of an edge_. + (line 6) +* edge_betweenness: igraph_edge_betweenness --- Betweenness centrality of the edges_. + (line 6) +* edge_betweenness_cutoff: igraph_edge_betweenness_cutoff --- Range-limited betweenness centrality of the edges_. + (line 6) +* edge_betweenness_subset: igraph_edge_betweenness_subset --- Edge betweenness centrality for a subset of source and target vertices_. + (line 6) +* edge_connectivity: igraph_edge_connectivity --- The minimum edge connectivity in a graph_. + (line 6) +* edge_disjoint_paths: igraph_edge_disjoint_paths --- The maximum number of edge-disjoint paths between two vertices_. + (line 6) +* edge_type_sw_t: igraph_edge_type_sw_t --- What types of non-simple edges to allow?. + (line 6) +* edgelist_percolation: igraph_edgelist_percolation --- The size of the largest component as vertex pairs are connected_. + (line 6) +* edges: igraph_edges --- Gives the head and tail vertices of a series of edges_. + (line 6) +* eigenvector_centrality: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_. + (line 6) +* eit_create: igraph_eit_create --- Creates an edge iterator from an edge selector_. + (line 6) +* eit_destroy: igraph_eit_destroy --- Destroys an edge iterator_. + (line 6) +* EIT_END: IGRAPH_EIT_END --- Are we at the end?. + (line 6) +* EIT_GET: IGRAPH_EIT_GET --- Query an edge iterator_. + (line 6) +* EIT_NEXT: IGRAPH_EIT_NEXT --- Next edge_. + (line 6) +* EIT_RESET: IGRAPH_EIT_RESET --- Reset an edge iterator_. + (line 6) +* EIT_SIZE: IGRAPH_EIT_SIZE --- Number of edges in the iterator_. + (line 6) +* empty: igraph_empty --- Creates an empty graph with some vertices and no edges_. + (line 6) +* empty_attrs: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_. + (line 6) +* enter_safelocale: igraph_enter_safelocale --- Temporarily set the C locale_. + (line 6) +* erdos_renyi_game_gnm: igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] graph with a fixed number of edges_. + (line 6) +* erdos_renyi_game_gnp: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_. + (line 6) +* ERROR: IGRAPH_ERROR --- Triggers an error_. + (line 6) +* error: igraph_error --- Reports an error_. + (line 6) +* error_handler_abort: igraph_error_handler_abort --- Abort program in case of error_. + (line 6) +* error_handler_ignore: igraph_error_handler_ignore --- Ignore errors_. + (line 6) +* error_handler_printignore: igraph_error_handler_printignore --- Print and ignore errors_. + (line 6) +* error_handler_t: igraph_error_handler_t --- The type of error handler functions_. + (line 6) +* error_t: igraph_error_t --- Return type for functions returning an error code_. + (line 6) +* error_type_t: igraph_error_type_t --- Error code type_. + (line 6) +* ERRORF: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_. + (line 6) +* errorf: igraph_errorf --- Reports an error; printf-like version_. + (line 6) +* es_1: igraph_es_1 --- Edge selector containing a single edge_. + (line 6) +* es_all: igraph_es_all --- Edge set; all edges_. + (line 6) +* es_all_between: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_. + (line 6) +* es_as_vector: igraph_es_as_vector --- Transform edge selector into vector_. + (line 6) +* es_copy: igraph_es_copy --- Creates a copy of an edge selector_. + (line 6) +* es_destroy: igraph_es_destroy --- Destroys an edge selector object_. + (line 6) +* es_incident: igraph_es_incident --- Edges incident on a given vertex_. + (line 6) +* es_is_all: igraph_es_is_all --- Check whether an edge selector includes all edges_. + (line 6) +* es_none: igraph_es_none --- Empty edge selector_. + (line 6) +* es_pairs: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_. + (line 6) +* es_pairs_small: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_. + (line 6) +* es_path: igraph_es_path --- Edge selector; edge IDs on a path_. + (line 6) +* es_range: igraph_es_range --- Edge selector; a sequence of edge IDs_. + (line 6) +* es_size: igraph_es_size --- Returns the size of the edge selector_. + (line 6) +* es_type: igraph_es_type --- Returns the type of the edge selector_. + (line 6) +* es_vector: igraph_es_vector --- Handle a vector as an edge selector_. + (line 6) +* es_vector_copy: igraph_es_vector_copy --- Edge set; based on a vector; with copying_. + (line 6) +* ess_1: igraph_ess_1 --- Immediate version of the single edge edge selector_. + (line 6) +* ess_all: igraph_ess_all --- Edge set; all edges [immediate version]_. + (line 6) +* ess_none: igraph_ess_none --- Immediate empty edge selector_. + (line 6) +* ess_range: igraph_ess_range --- Immediate version of the sequence edge selector_. + (line 6) +* ess_vector: igraph_ess_vector --- Immediate vector view edge selector_. + (line 6) +* establishment_game: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_. + (line 6) +* eulerian_cycle: igraph_eulerian_cycle --- Finds an Eulerian cycle_. + (line 6) +* eulerian_path: igraph_eulerian_path --- Finds an Eulerian path_. + (line 6) +* even_tarjan_reduction: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_. + (line 6) +* exit_safelocale: igraph_exit_safelocale --- Temporarily set the C locale_. + (line 6) +* expand_path_to_pairs: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_. + (line 6) +* extended_chordal_ring: igraph_extended_chordal_ring --- Create an extended chordal ring_. + (line 6) +* famous: igraph_famous --- Create a famous graph by simply providing its name_. + (line 6) +* FATAL: IGRAPH_FATAL --- Triggers a fatal error_. + (line 6) +* fatal: igraph_fatal --- Triggers a fatal error_. + (line 6) +* fatal_handler_abort: igraph_fatal_handler_abort --- Abort program in case of fatal error_. + (line 6) +* fatal_handler_t: igraph_fatal_handler_t --- The type of igraph fatal error handler functions_. + (line 6) +* FATALF: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_. + (line 6) +* fatalf: igraph_fatalf --- Triggers a fatal error; printf-like syntax_. + (line 6) +* feedback_arc_set: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_. + (line 6) +* feedback_vertex_set: igraph_feedback_vertex_set --- Feedback vertex set of a graph_. + (line 6) +* FINALLY: IGRAPH_FINALLY --- Registers an object for deallocation_. + (line 6) +* FINALLY_CLEAN: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_. + (line 6) +* FINALLY_FREE: IGRAPH_FINALLY_FREE --- Deallocates objects registered at the current level_. + (line 6) +* find_cycle: igraph_find_cycle --- Finds a single cycle in the graph_. + (line 6) +* forest_fire_game: igraph_forest_fire_game --- Generates a network according to the forest fire game_. + (line 6) +* free: igraph_free --- Deallocates memory that was allocated by igraph functions_. + (line 6) +* FROM: IGRAPH_FROM --- The source vertex of an edge_. + (line 6) +* from_hrg_dendrogram: igraph_from_hrg_dendrogram --- Create a graph representation of the dendrogram of a hierarchical random graph model_. + (line 6) +* from_prufer: igraph_from_prufer --- Generates a tree from a Prüfer sequence_. + (line 6) +* full: igraph_full --- Creates a full graph [complete graph]_. + (line 6) +* full_bipartite: igraph_full_bipartite --- Creates a complete bipartite graph_. + (line 6) +* full_citation: igraph_full_citation --- Creates a full citation graph [a complete directed acyclic graph]_. + (line 6) +* full_multipartite: igraph_full_multipartite --- Creates a full multipartite graph_. + (line 6) +* fundamental_cycles: igraph_fundamental_cycles --- Finds a fundamental cycle basis_. + (line 6) +* GAB: GAB --- Query a boolean graph attribute_. + (line 6) +* gabriel_graph: igraph_gabriel_graph --- The Gabriel graph of a point set_. + (line 6) +* GAN: GAN --- Query a numeric graph attribute_. + (line 6) +* GAS: GAS --- Query a string graph attribute_. + (line 6) +* generalized_petersen: igraph_generalized_petersen --- Creates a Generalized Petersen graph_. + (line 6) +* get_adjacency: igraph_get_adjacency --- The adjacency matrix of a graph_. + (line 6) +* get_adjacency_sparse: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_. + (line 6) +* get_all_eids_between: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_. + (line 6) +* get_all_shortest_paths: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_. + (line 6) +* get_all_shortest_paths_dijkstra: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_. + (line 6) +* get_all_simple_paths: igraph_get_all_simple_paths --- List all simple paths from one source_. + (line 6) +* get_biadjacency: igraph_get_biadjacency --- Converts a bipartite graph into a bipartite adjacency matrix_. + (line 6) +* get_edgelist: igraph_get_edgelist --- The list of edges in a graph_. + (line 6) +* get_eid: igraph_get_eid --- Get the edge ID from the endpoints of an edge_. + (line 6) +* get_eids: igraph_get_eids --- Return edge IDs based on the adjacent vertices_. + (line 6) +* get_isomorphisms_vf2: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_. + (line 6) +* get_isomorphisms_vf2_callback: igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface. + (line 6) +* get_k_shortest_paths: igraph_get_k_shortest_paths --- k shortest paths between two vertices_. + (line 6) +* get_laplacian: igraph_get_laplacian --- Returns the Laplacian matrix of a graph_. + (line 6) +* get_laplacian_sparse: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_. + (line 6) +* get_shortest_path: igraph_get_shortest_path --- Shortest path from one vertex to another one_. + (line 6) +* get_shortest_path_astar: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_. + (line 6) +* get_shortest_path_bellman_ford: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_. + (line 6) +* get_shortest_path_dijkstra: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_. + (line 6) +* get_shortest_paths: igraph_get_shortest_paths --- Shortest paths from a vertex_. + (line 6) +* get_shortest_paths_bellman_ford: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_. + (line 6) +* get_shortest_paths_dijkstra: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_. + (line 6) +* get_stochastic: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_. + (line 6) +* get_stochastic_sparse: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_. + (line 6) +* get_subisomorphisms_vf2: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_. + (line 6) +* get_subisomorphisms_vf2_callback: igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for subgraph isomorphism problems_. + (line 6) +* get_widest_path: igraph_get_widest_path --- Widest path from one vertex to another one_. + (line 6) +* get_widest_paths: igraph_get_widest_paths --- Widest paths from a single vertex_. + (line 6) +* girth: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_. + (line 6) +* global_efficiency: igraph_global_efficiency --- Calculates the global efficiency of a network_. + (line 6) +* gomory_hu_tree: igraph_gomory_hu_tree --- Gomory-Hu tree of a graph_. + (line 6) +* graph_center: igraph_graph_center --- Central vertices of a graph_. + (line 6) +* graph_count: igraph_graph_count --- The number of unlabelled graphs on the given number of vertices_. + (line 6) +* graph_power: igraph_graph_power --- The k-th power of a graph_. + (line 6) +* graphlets: igraph_graphlets --- Calculate graphlets basis and project the graph on it_. + (line 6) +* graphlets_candidate_basis: igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis. + (line 6) +* graphlets_project: igraph_graphlets_project --- Project a graph on a graphlets basis_. + (line 6) +* grg_game: igraph_grg_game --- Generates a geometric random graph_. + (line 6) +* growing_random_game: igraph_growing_random_game --- Generates a growing random graph_. + (line 6) +* harmonic_centrality: igraph_harmonic_centrality --- Harmonic centrality for some vertices_. + (line 6) +* harmonic_centrality_cutoff: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_. + (line 6) +* has_loop: igraph_has_loop --- Returns whether the graph has at least one loop edge_. + (line 6) +* has_multiple: igraph_has_multiple --- Check whether the graph has at least one multiple edge_. + (line 6) +* has_mutual: igraph_has_mutual --- Check whether a directed graph has any mutual edges_. + (line 6) +* heap_clear: igraph_heap_clear --- Removes all elements from a heap_. + (line 6) +* heap_delete_top: igraph_heap_delete_top --- Removes and returns the top element_. + (line 6) +* heap_destroy: igraph_heap_destroy --- Destroys an initialized heap object_. + (line 6) +* heap_empty: igraph_heap_empty --- Decides whether a heap object is empty_. + (line 6) +* heap_init: igraph_heap_init --- Initializes an empty heap object_. + (line 6) +* heap_init_array: igraph_heap_init_array --- Build a heap from an array_. + (line 6) +* heap_push: igraph_heap_push --- Add an element_. + (line 6) +* heap_reserve: igraph_heap_reserve --- Reserves memory for a heap_. + (line 6) +* heap_size: igraph_heap_size --- Number of elements in the heap_. + (line 6) +* heap_top: igraph_heap_top --- Top element_. + (line 6) +* hexagonal_lattice: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_. + (line 6) +* hrg_consensus: igraph_hrg_consensus --- Calculate a consensus tree for a HRG_. + (line 6) +* hrg_create: igraph_hrg_create --- Create a HRG from an igraph graph_. + (line 6) +* hrg_dendrogram: igraph_hrg_dendrogram --- Create a dendrogram from a hierarchical random graph_. + (line 6) +* hrg_destroy: igraph_hrg_destroy --- Deallocate memory for an HRG_. + (line 6) +* hrg_fit: igraph_hrg_fit --- Fit a hierarchical random graph model to a network_. + (line 6) +* hrg_game: igraph_hrg_game --- Generate a hierarchical random graph_. + (line 6) +* hrg_init: igraph_hrg_init --- Allocate memory for a HRG_. + (line 6) +* hrg_predict: igraph_hrg_predict --- Predict missing edges in a graph; based on HRG models_. + (line 6) +* hrg_resize: igraph_hrg_resize --- Resize a HRG_. + (line 6) +* hrg_sample: igraph_hrg_sample --- Sample from a hierarchical random graph model_. + (line 6) +* hrg_size: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_. + (line 6) +* hrg_t: igraph_hrg_t --- Data structure to store a hierarchical random graph_. + (line 6) +* hsbm_game: igraph_hsbm_game --- Hierarchical stochastic block model_. + (line 6) +* hsbm_list_game: igraph_hsbm_list_game --- Hierarchical stochastic block model; more general version_. + (line 6) +* hub_and_authority_scores: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_. + (line 6) +* hypercube: igraph_hypercube --- The n-dimensional hypercube graph_. + (line 6) +* iea_game: igraph_iea_game --- Generates a random multigraph through independent edge assignment_. + (line 6) +* igraph_bool_t: Atomic data types. (line 35) +* igraph_int_t: Atomic data types. (line 6) +* IGRAPH_INTEGER_MAX: Atomic data types. (line 40) +* IGRAPH_INTEGER_MIN: Atomic data types. (line 40) +* igraph_real_t: Atomic data types. (line 29) +* IGRAPH_UINT_MAX: Atomic data types. (line 40) +* IGRAPH_UINT_MIN: Atomic data types. (line 40) +* igraph_uint_t: Atomic data types. (line 23) +* incident: igraph_incident --- Gives the incident edges of a vertex_. + (line 6) +* inclist_clear: igraph_inclist_clear --- Removes all edges from an incidence list_. + (line 6) +* inclist_destroy: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_. + (line 6) +* inclist_get: igraph_inclist_get --- Query a vector in an incidence list_. + (line 6) +* inclist_init: igraph_inclist_init --- Initializes an incidence list_. + (line 6) +* inclist_size: igraph_inclist_size --- Returns the number of vertices in an incidence list_. + (line 6) +* independence_number: igraph_independence_number --- Finds the independence number of the graph_. + (line 6) +* independent_vertex_sets: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_. + (line 6) +* induced_subgraph: igraph_induced_subgraph --- Creates a subgraph induced by the specified vertices_. + (line 6) +* induced_subgraph_edges: igraph_induced_subgraph_edges --- The edges contained within an induced sugraph_. + (line 6) +* induced_subgraph_map: igraph_induced_subgraph_map --- Creates an induced subraph and returns the mapping from the original_. + (line 6) +* intersection: igraph_intersection --- Collect the common edges from two graphs_. + (line 6) +* intersection_many: igraph_intersection_many --- The intersection of more than two graphs_. + (line 6) +* invalidate_cache: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_. + (line 6) +* invert_permutation: igraph_invert_permutation --- Inverts a permutation_. + (line 6) +* is_acyclic: igraph_is_acyclic --- Checks whether a graph is acyclic or not_. + (line 6) +* is_biconnected: igraph_is_biconnected --- Checks whether a graph is biconnected_. + (line 6) +* is_bigraphical: igraph_is_bigraphical --- Is there a bipartite graph with the given bi-degree-sequence?. + (line 6) +* is_bipartite: igraph_is_bipartite --- Check whether a graph is bipartite_. + (line 6) +* is_bipartite_coloring: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_. + (line 6) +* is_chordal: igraph_is_chordal --- Decides whether a graph is chordal_. + (line 6) +* is_clique: igraph_is_clique --- Does a set of vertices form a clique?. + (line 6) +* is_complete: igraph_is_complete --- Decides whether the graph is complete_. + (line 6) +* is_connected: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_. + (line 6) +* is_dag: igraph_is_dag --- Checks whether a graph is a directed acyclic graph [DAG]_. + (line 6) +* is_directed: igraph_is_directed --- Is this a directed graph?. + (line 6) +* is_edge_coloring: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_. + (line 6) +* is_eulerian: igraph_is_eulerian --- Checks whether an Eulerian path or cycle exists_. + (line 6) +* is_forest: igraph_is_forest --- Decides whether the graph is a forest_. + (line 6) +* is_graphical: igraph_is_graphical --- Is there a graph with the given degree sequence?. + (line 6) +* is_independent_vertex_set: igraph_is_independent_vertex_set --- Does a set of vertices form an independent set?. + (line 6) +* is_loop: igraph_is_loop --- Find the loop edges in a graph_. + (line 6) +* is_matching: igraph_is_matching --- Checks whether the given matching is valid for the given graph_. + (line 6) +* is_maximal_matching: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_. + (line 6) +* is_minimal_separator: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_. + (line 6) +* is_multiple: igraph_is_multiple --- Find the multiple edges in a graph_. + (line 6) +* is_mutual: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_. + (line 6) +* is_perfect: igraph_is_perfect --- Checks if the graph is perfect_. + (line 6) +* is_same_graph: igraph_is_same_graph --- Are two graphs identical as labelled graphs?. + (line 6) +* is_separator: igraph_is_separator --- Would removing this set of vertices disconnect the graph?. + (line 6) +* is_simple: igraph_is_simple --- Decides whether the input graph is a simple graph_. + (line 6) +* is_tree: igraph_is_tree --- Decides whether the graph is a tree_. + (line 6) +* is_vertex_coloring: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_. + (line 6) +* isoclass: igraph_isoclass --- Determine the isomorphism class of small graphs_. + (line 6) +* isoclass_create: igraph_isoclass_create --- Creates a graph from the given isomorphism class_. + (line 6) +* isoclass_subgraph: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_. + (line 6) +* isocompat_t: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible. + (line 6) +* isohandler_t: igraph_isohandler_t --- Callback type; called when an isomorphism was found. + (line 6) +* isomorphic: igraph_isomorphic --- Are two graphs isomorphic?. + (line 6) +* isomorphic_bliss: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_. + (line 6) +* isomorphic_vf2: igraph_isomorphic_vf2 --- Isomorphism via VF2_. + (line 6) +* join: igraph_join --- Creates the join of two disjoint graphs_. + (line 6) +* joint_degree_distribution: igraph_joint_degree_distribution --- The joint degree distribution of a graph_. + (line 6) +* joint_degree_matrix: igraph_joint_degree_matrix --- The joint degree matrix of a graph_. + (line 6) +* joint_type_distribution: igraph_joint_type_distribution --- Mixing matrix for vertex categories_. + (line 6) +* k_regular_game: igraph_k_regular_game --- Generates a random graph where each vertex has the same degree_. + (line 6) +* kary_tree: igraph_kary_tree --- Creates a k-ary tree in which almost all vertices have k children_. + (line 6) +* kautz: igraph_kautz --- Generate a Kautz graph_. + (line 6) +* lapack_dgeev: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_. + (line 6) +* lapack_dgeevx: igraph_lapack_dgeevx --- Eigenvalues/vectors of nonsymmetric matrices; expert mode_. + (line 6) +* lapack_dgesv: igraph_lapack_dgesv --- Solve system of linear equations with LU factorization_. + (line 6) +* lapack_dgetrf: igraph_lapack_dgetrf --- LU factorization of a general M-by-N matrix_. + (line 6) +* lapack_dgetrs: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_. + (line 6) +* lapack_dsyevr: igraph_lapack_dsyevr --- Selected eigenvalues and optionally eigenvectors of a symmetric matrix_. + (line 6) +* laplacian_normalization_t: igraph_laplacian_normalization_t --- Normalization methods for a Laplacian matrix_. + (line 6) +* laplacian_spectral_embedding: igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph. + (line 6) +* largest_cliques: igraph_largest_cliques --- Finds the largest clique[s] in a graph_. + (line 6) +* largest_independent_vertex_sets: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_. + (line 6) +* largest_weighted_cliques: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_. + (line 6) +* lastcit_game: igraph_lastcit_game --- Simulates a citation network; based on time passed since the last citation_. + (line 6) +* layout_align: igraph_layout_align --- Aligns a graph layout with the coordinate axes_. + (line 6) +* layout_bipartite: igraph_layout_bipartite --- Simple layout for bipartite graphs_. + (line 6) +* layout_circle: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_. + (line 6) +* layout_davidson_harel: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_. + (line 6) +* layout_drl: igraph_layout_drl --- The DrL layout generator. + (line 6) +* layout_drl_3d: igraph_layout_drl_3d --- The DrL layout generator; 3d version_. + (line 6) +* layout_drl_default_t: igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator. + (line 6) +* layout_drl_options_init: igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator. + (line 6) +* layout_drl_options_t: igraph_layout_drl_options_t --- Parameters for the DrL layout generator. + (line 6) +* layout_fruchterman_reingold: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_. + (line 6) +* layout_fruchterman_reingold_3d: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_. + (line 6) +* layout_gem: igraph_layout_gem --- Layout graph according to GEM algorithm_. + (line 6) +* layout_graphopt: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_. + (line 6) +* layout_grid: igraph_layout_grid --- Places the vertices on a regular grid on the plane_. + (line 6) +* layout_grid_3d: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_. + (line 6) +* layout_kamada_kawai: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_. + (line 6) +* layout_kamada_kawai_3d: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_. + (line 6) +* layout_lgl: igraph_layout_lgl --- Force based layout algorithm for large graphs_. + (line 6) +* layout_mds: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_. + (line 6) +* layout_merge_dla: igraph_layout_merge_dla --- Merges multiple layouts by using a DLA algorithm_. + (line 6) +* layout_random: igraph_layout_random --- Places the vertices uniformly randomly within a square_. + (line 6) +* layout_random_3d: igraph_layout_random_3d --- Places the vertices uniformly randomly in a cube_. + (line 6) +* layout_reingold_tilford: igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree graphs_. + (line 6) +* layout_reingold_tilford_circular: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_. + (line 6) +* layout_sphere: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_. + (line 6) +* layout_star: igraph_layout_star --- Generates a star-like layout_. + (line 6) +* layout_sugiyama: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_. + (line 6) +* layout_umap: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_. + (line 6) +* layout_umap_3d: igraph_layout_umap_3d --- 3D layout using UMAP_. + (line 6) +* layout_umap_compute_weights: igraph_layout_umap_compute_weights --- Compute weights for a UMAP layout starting from distances_. + (line 6) +* lazy_adjlist_clear: igraph_lazy_adjlist_clear --- Removes all edges from a lazy adjacency list_. + (line 6) +* lazy_adjlist_destroy: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_. + (line 6) +* lazy_adjlist_get: igraph_lazy_adjlist_get --- Query neighbor vertices_. + (line 6) +* lazy_adjlist_has: igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?. + (line 6) +* lazy_adjlist_init: igraph_lazy_adjlist_init --- Initializes a lazy adjacency list_. + (line 6) +* lazy_adjlist_size: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_. + (line 6) +* lazy_inclist_clear: igraph_lazy_inclist_clear --- Removes all edges from a lazy incidence list_. + (line 6) +* lazy_inclist_destroy: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_. + (line 6) +* lazy_inclist_get: igraph_lazy_inclist_get --- Query incident edges_. + (line 6) +* lazy_inclist_has: igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?. + (line 6) +* lazy_inclist_init: igraph_lazy_inclist_init --- Initializes a lazy incidence list of edges_. + (line 6) +* lazy_inclist_size: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_. + (line 6) +* lcf: igraph_lcf --- Creates a graph from LCF notation_. + (line 6) +* lcf_small: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_. + (line 6) +* le_community_to_membership: igraph_le_community_to_membership --- Cut an incomplete dendrogram after a given number of merges; starting with an initial cluster assignment_. + (line 6) +* linegraph: igraph_linegraph --- Create the line graph of a graph_. + (line 6) +* list_triangles: igraph_list_triangles --- Find all triangles in a graph_. + (line 6) +* local_efficiency: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_. + (line 6) +* local_scan_0: igraph_local_scan_0 --- Local scan-statistics; k=0. + (line 6) +* local_scan_0_them: igraph_local_scan_0_them --- Local THEM scan-statistics; k=0. + (line 6) +* local_scan_1_ecount: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights. + (line 6) +* local_scan_1_ecount_them: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights. + (line 6) +* local_scan_k_ecount: igraph_local_scan_k_ecount --- Sum the number of edges or the weights in k-neighborhood of every vertex_. + (line 6) +* local_scan_k_ecount_them: igraph_local_scan_k_ecount_them --- Local THEM scan-statistics; edge count or sum of weights_. + (line 6) +* local_scan_neighborhood_ecount: igraph_local_scan_neighborhood_ecount --- Local scan-statistics with pre-calculated neighborhoods. + (line 6) +* local_scan_subset_ecount: igraph_local_scan_subset_ecount --- Local scan-statistics of subgraphs induced by subsets of vertices_. + (line 6) +* loops_t: igraph_loops_t --- How to interpret self-loops in undirected graphs?. + (line 6) +* lune_beta_skeleton: igraph_lune_beta_skeleton --- The lune based β-skeleton of a spatial point set_. + (line 6) +* malloc: igraph_malloc --- Allocates memory that can be safely deallocated by igraph functions_. + (line 6) +* MATRIX: MATRIX --- Accessing an element of a matrix_. + (line 6) +* matrix_add: igraph_matrix_add --- Add two matrices_. + (line 6) +* matrix_add_cols: igraph_matrix_add_cols --- Adds columns to a matrix_. + (line 6) +* matrix_add_constant: igraph_matrix_add_constant --- Add a constant to every element_. + (line 6) +* matrix_add_rows: igraph_matrix_add_rows --- Adds rows to a matrix_. + (line 6) +* matrix_all_almost_e: igraph_matrix_all_almost_e --- Are all elements almost equal?. + (line 6) +* matrix_all_e: igraph_matrix_all_e --- Are all elements equal?. + (line 6) +* matrix_all_g: igraph_matrix_all_g --- Are all elements greater?. + (line 6) +* matrix_all_ge: igraph_matrix_all_ge --- Are all elements greater or equal?. + (line 6) +* matrix_all_l: igraph_matrix_all_l --- Are all elements less?. + (line 6) +* matrix_all_le: igraph_matrix_all_le --- Are all elements less or equal?. + (line 6) +* matrix_as_sparsemat: igraph_matrix_as_sparsemat --- Converts a dense matrix to a sparse matrix_. + (line 6) +* matrix_capacity: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_. + (line 6) +* matrix_cbind: igraph_matrix_cbind --- Combine matrices columnwise_. + (line 6) +* matrix_colsum: igraph_matrix_colsum --- Columnwise sum_. + (line 6) +* matrix_complex_all_almost_e: igraph_matrix_complex_all_almost_e --- Are all elements almost equal?. + (line 6) +* matrix_complex_create: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_. + (line 6) +* matrix_complex_create_polar: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_. + (line 6) +* matrix_complex_imag: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_. + (line 6) +* matrix_complex_real: igraph_matrix_complex_real --- Gives the real part of a complex matrix_. + (line 6) +* matrix_complex_realimag: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_. + (line 6) +* matrix_complex_zapsmall: igraph_matrix_complex_zapsmall --- Replaces small elements of a complex matrix by exact zeros_. + (line 6) +* matrix_contains: igraph_matrix_contains --- Search for an element_. + (line 6) +* matrix_copy_to: igraph_matrix_copy_to --- Copies a matrix to a regular C array_. + (line 6) +* matrix_destroy: igraph_matrix_destroy --- Destroys a matrix object_. + (line 6) +* matrix_div_elements: igraph_matrix_div_elements --- Elementwise division_. + (line 6) +* matrix_empty: igraph_matrix_empty --- Is the matrix empty?. + (line 6) +* matrix_fill: igraph_matrix_fill --- Fill with an element_. + (line 6) +* matrix_get: igraph_matrix_get --- Extract an element from a matrix_. + (line 6) +* matrix_get_col: igraph_matrix_get_col --- Select a column_. + (line 6) +* matrix_get_ptr: igraph_matrix_get_ptr --- Pointer to an element of a matrix_. + (line 6) +* matrix_get_row: igraph_matrix_get_row --- Extract a row_. + (line 6) +* matrix_init: igraph_matrix_init --- Initializes a matrix_. + (line 6) +* matrix_init_array: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_. + (line 6) +* matrix_init_copy: igraph_matrix_init_copy --- Copies a matrix_. + (line 6) +* matrix_is_symmetric: igraph_matrix_is_symmetric --- Is the matrix symmetric?. + (line 6) +* matrix_isnull: igraph_matrix_isnull --- Checks for a null matrix_. + (line 6) +* matrix_max: igraph_matrix_max --- Largest element of a matrix_. + (line 6) +* matrix_maxdifference: igraph_matrix_maxdifference --- Maximum absolute difference between two matrices_. + (line 6) +* matrix_min: igraph_matrix_min --- Smallest element of a matrix_. + (line 6) +* matrix_minmax: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_. + (line 6) +* matrix_mul_elements: igraph_matrix_mul_elements --- Elementwise matrix multiplication_. + (line 6) +* matrix_ncol: igraph_matrix_ncol --- The number of columns in a matrix_. + (line 6) +* matrix_nrow: igraph_matrix_nrow --- The number of rows in a matrix_. + (line 6) +* matrix_null: igraph_matrix_null --- Sets all elements in a matrix to zero_. + (line 6) +* matrix_prod: igraph_matrix_prod --- Product of all matrix elements_. + (line 6) +* matrix_rbind: igraph_matrix_rbind --- Combine two matrices rowwise_. + (line 6) +* matrix_remove_col: igraph_matrix_remove_col --- Removes a column from a matrix_. + (line 6) +* matrix_remove_row: igraph_matrix_remove_row --- Remove a row_. + (line 6) +* matrix_resize: igraph_matrix_resize --- Resizes a matrix_. + (line 6) +* matrix_resize_min: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_. + (line 6) +* matrix_rowsum: igraph_matrix_rowsum --- Rowwise sum_. + (line 6) +* matrix_scale: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_. + (line 6) +* matrix_search: igraph_matrix_search --- Search from a given position_. + (line 6) +* matrix_select_cols: igraph_matrix_select_cols --- Select some columns of a matrix_. + (line 6) +* matrix_select_rows: igraph_matrix_select_rows --- Select some rows of a matrix_. + (line 6) +* matrix_select_rows_cols: igraph_matrix_select_rows_cols --- Select some rows and columns of a matrix_. + (line 6) +* matrix_set: igraph_matrix_set --- Set an element_. + (line 6) +* matrix_set_col: igraph_matrix_set_col --- Set a column from a vector_. + (line 6) +* matrix_set_row: igraph_matrix_set_row --- Set a row from a vector_. + (line 6) +* matrix_size: igraph_matrix_size --- The number of elements in a matrix_. + (line 6) +* matrix_sub: igraph_matrix_sub --- Difference of two matrices_. + (line 6) +* matrix_sum: igraph_matrix_sum --- Sum of elements_. + (line 6) +* matrix_swap: igraph_matrix_swap --- Swap two matrices_. + (line 6) +* matrix_swap_cols: igraph_matrix_swap_cols --- Swap two columns_. + (line 6) +* matrix_swap_rows: igraph_matrix_swap_rows --- Swap two rows_. + (line 6) +* matrix_transpose: igraph_matrix_transpose --- Transpose of a matrix_. + (line 6) +* matrix_update: igraph_matrix_update --- Update from another matrix_. + (line 6) +* matrix_view: igraph_matrix_view --- Creates a matrix view into an existing array_. + (line 6) +* matrix_view_from_vector: igraph_matrix_view_from_vector --- Creates a matrix view that treats an existing vector as a matrix_. + (line 6) +* matrix_which_max: igraph_matrix_which_max --- Indices of the largest element_. + (line 6) +* matrix_which_min: igraph_matrix_which_min --- Indices of the smallest element_. + (line 6) +* matrix_which_minmax: igraph_matrix_which_minmax --- Indices of the minimum and maximum elements_. + (line 6) +* matrix_zapsmall: igraph_matrix_zapsmall --- Replaces small elements of a matrix by exact zeros_. + (line 6) +* maxdegree: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_. + (line 6) +* maxflow: igraph_maxflow --- Maximum network flow between a pair of vertices_. + (line 6) +* maxflow_stats_t: igraph_maxflow_stats_t --- Data structure holding statistics from the push-relabel maximum flow solver_. + (line 6) +* maxflow_value: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_. + (line 6) +* maximal_cliques: igraph_maximal_cliques --- Finds all maximal cliques in a graph_. + (line 6) +* maximal_cliques_callback: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_. + (line 6) +* maximal_cliques_count: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_. + (line 6) +* maximal_cliques_file: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_. + (line 6) +* maximal_cliques_hist: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_. + (line 6) +* maximal_cliques_subset: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_. + (line 6) +* maximal_independent_vertex_sets: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_. + (line 6) +* maximum_bipartite_matching: igraph_maximum_bipartite_matching --- Calculates a maximum matching in a bipartite graph_. + (line 6) +* maximum_cardinality_search: igraph_maximum_cardinality_search --- Maximum cardinality search_. + (line 6) +* mean_degree: igraph_mean_degree --- The mean degree of a graph_. + (line 6) +* metric_t: igraph_metric_t --- Metric functions for use with spatial computation_. + (line 6) +* mincut: igraph_mincut --- Calculates the minimum cut in a graph_. + (line 6) +* mincut_value: igraph_mincut_value --- The minimum edge cut in a graph_. + (line 6) +* minimum_cycle_basis: igraph_minimum_cycle_basis --- Computes a minimum weight cycle basis_. + (line 6) +* minimum_size_separators: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_. + (line 6) +* minimum_spanning_tree: igraph_minimum_spanning_tree --- Calculates a minimum spanning tree of a graph_. + (line 6) +* modularity: igraph_modularity --- Calculates the modularity of a graph with respect to some clusters or vertex types_. + (line 6) +* modularity_matrix: igraph_modularity_matrix --- Calculates the modularity matrix_. + (line 6) +* motifs_handler_t: igraph_motifs_handler_t --- Callback type for igraph_motifs_randesu_callback_. + (line 6) +* motifs_randesu: igraph_motifs_randesu --- Count the number of motifs in a graph_. + (line 6) +* motifs_randesu_callback: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_. + (line 6) +* motifs_randesu_estimate: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_. + (line 6) +* motifs_randesu_no: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_. + (line 6) +* mycielski_graph: igraph_mycielski_graph --- The Mycielski graph of order k_. + (line 6) +* mycielskian: igraph_mycielskian --- Generate the Mycielskian of a graph with k iterations_. + (line 6) +* nearest_neighbor_graph: igraph_nearest_neighbor_graph --- Computes the nearest neighbor graph for a spatial point set_. + (line 6) +* neighborhood: igraph_neighborhood --- Calculate the neighborhood of vertices_. + (line 6) +* neighborhood_graphs: igraph_neighborhood_graphs --- Create graphs from the neighborhood[s] of some vertex/vertices_. + (line 6) +* neighborhood_size: igraph_neighborhood_size --- Calculates the size of the neighborhood of a given vertex_. + (line 6) +* neighbors: igraph_neighbors --- Adjacent vertices to a vertex_. + (line 6) +* neimode_t: igraph_neimode_t --- How to interpret edge directions in directed graphs?. + (line 6) +* OTHER: IGRAPH_OTHER --- The other endpoint of an edge_. + (line 6) +* pagerank: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_. + (line 6) +* pagerank_algo_t: igraph_pagerank_algo_t --- PageRank algorithm implementation_. + (line 6) +* path_graph: igraph_path_graph --- A path graph P_n_. + (line 6) +* path_length_hist: igraph_path_length_hist --- Create a histogram of all shortest path lengths_. + (line 6) +* permute_vertices: igraph_permute_vertices --- Permute the vertices_. + (line 6) +* personalized_pagerank: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_. + (line 6) +* personalized_pagerank_vs: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_. + (line 6) +* plfit_result_calculate_p_value: igraph_plfit_result_calculate_p_value --- Calculates the p-value of a fitted power-law model_. + (line 6) +* plfit_result_t: igraph_plfit_result_t --- Result of fitting a power-law distribution to a vector_. + (line 6) +* power_law_fit: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_. + (line 6) +* preference_game: igraph_preference_game --- Generates a graph with vertex types and connection preferences_. + (line 6) +* product: igraph_product --- The graph product of two graphs; according to the chosen product type_. + (line 6) +* PROGRESS: IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph function [macro variant]_. + (line 6) +* progress: igraph_progress --- Report the progress of a calculation from an igraph function_. + (line 6) +* progress_handler_stderr: igraph_progress_handler_stderr --- A simple predefined progress handler_. + (line 6) +* progress_handler_t: igraph_progress_handler_t --- Type of progress handler functions. + (line 6) +* PROGRESSF: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_. + (line 6) +* progressf: igraph_progressf --- Report the progress of a calculation from an igraph function; printf-like_. + (line 6) +* pseudo_diameter: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_. + (line 6) +* psumtree_destroy: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_. + (line 6) +* psumtree_get: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_. + (line 6) +* psumtree_init: igraph_psumtree_init --- Initializes a partial prefix sum tree_. + (line 6) +* psumtree_reset: igraph_psumtree_reset --- Resets all the values in the tree to zero_. + (line 6) +* psumtree_search: igraph_psumtree_search --- Finds an item in the tree; given a value_. + (line 6) +* psumtree_size: igraph_psumtree_size --- Returns the size of the tree_. + (line 6) +* psumtree_sum: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_. + (line 6) +* psumtree_update: igraph_psumtree_update --- Updates the value associated to an item in the tree_. + (line 6) +* radius: igraph_radius --- Radius of a graph; using weighted edges_. + (line 6) +* random_sample: igraph_random_sample --- Generates an increasing random sequence of integers_. + (line 6) +* random_spanning_tree: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_. + (line 6) +* random_walk: igraph_random_walk --- Performs a random walk on a graph_. + (line 6) +* reachability: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_. + (line 6) +* read_graph_dimacs_flow: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_. + (line 6) +* read_graph_dl: igraph_read_graph_dl --- Reads a file in the DL format of UCINET_. + (line 6) +* read_graph_edgelist: igraph_read_graph_edgelist --- Reads an edge list from a file and creates a graph_. + (line 6) +* read_graph_gml: igraph_read_graph_gml --- Read a graph in GML format_. + (line 6) +* read_graph_graphdb: igraph_read_graph_graphdb --- Read a graph in the binary graph database format_. + (line 6) +* read_graph_graphml: igraph_read_graph_graphml --- Reads a graph from a GraphML file_. + (line 6) +* read_graph_lgl: igraph_read_graph_lgl --- Reads a graph from an _lgl file_. + (line 6) +* read_graph_ncol: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_. + (line 6) +* read_graph_pajek: igraph_read_graph_pajek --- Reads a file in Pajek format_. + (line 6) +* realize_bipartite_degree_sequence: igraph_realize_bipartite_degree_sequence --- Generates a bipartite graph with the given bidegree sequence_. + (line 6) +* realize_degree_sequence: igraph_realize_degree_sequence --- Generates a graph with the given degree sequence_. + (line 6) +* realloc: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_. + (line 6) +* recent_degree_aging_game: igraph_recent_degree_aging_game --- Preferential attachment based on the number of edges gained recently; with aging of vertices_. + (line 6) +* recent_degree_game: igraph_recent_degree_game --- Stochastic graph generator based on the number of incident edges a node has gained recently_. + (line 6) +* reciprocity: igraph_reciprocity --- Calculates the reciprocity of a directed graph_. + (line 6) +* regular_tree: igraph_regular_tree --- Creates a regular tree_. + (line 6) +* reindex_membership: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_. + (line 6) +* relative_neighborhood_graph: igraph_relative_neighborhood_graph --- The relative neighborhood graph of a point set_. + (line 6) +* reverse_edges: igraph_reverse_edges --- Reverses some edges of a directed graph_. + (line 6) +* rewire: igraph_rewire --- Randomly rewires a graph while preserving its degree sequence_. + (line 6) +* rewire_directed_edges: igraph_rewire_directed_edges --- Rewires the chosen endpoint of directed edges_. + (line 6) +* rewire_edges: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_. + (line 6) +* rich_club_sequence: igraph_rich_club_sequence --- Density sequence of subgraphs formed by sequential vertex removal_. + (line 6) +* ring: igraph_ring --- Creates a cycle graph or a path graph_. + (line 6) +* rng_bits: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_. + (line 6) +* rng_default: igraph_rng_default --- Query the default random number generator_. + (line 6) +* rng_destroy: igraph_rng_destroy --- Deallocates memory associated with a random number generator_. + (line 6) +* rng_get_binom: igraph_rng_get_binom --- Samples from a binomial distribution_. + (line 6) +* rng_get_bool: igraph_rng_get_bool --- Generate a random boolean_. + (line 6) +* rng_get_exp: igraph_rng_get_exp --- Samples from an exponential distribution_. + (line 6) +* rng_get_gamma: igraph_rng_get_gamma --- Samples from a gamma distribution_. + (line 6) +* rng_get_geom: igraph_rng_get_geom --- Samples from a geometric distribution_. + (line 6) +* rng_get_integer: igraph_rng_get_integer --- Generate an integer random number from an interval_. + (line 6) +* rng_get_normal: igraph_rng_get_normal --- Samples from a normal distribution_. + (line 6) +* rng_get_pois: igraph_rng_get_pois --- Samples from a Poisson distribution_. + (line 6) +* rng_get_unif: igraph_rng_get_unif --- Samples real numbers from a given interval_. + (line 6) +* rng_get_unif01: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_. + (line 6) +* rng_init: igraph_rng_init --- Initializes a random number generator_. + (line 6) +* rng_max: igraph_rng_max --- The maximum possible integer for a random number generator_. + (line 6) +* rng_name: igraph_rng_name --- The type of a random number generator_. + (line 6) +* rng_sample_dirichlet: igraph_rng_sample_dirichlet --- Sample points from a Dirichlet distribution_. + (line 6) +* rng_sample_sphere_surface: igraph_rng_sample_sphere_surface --- Sample points uniformly from the surface of a sphere_. + (line 6) +* rng_sample_sphere_volume: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_. + (line 6) +* rng_seed: igraph_rng_seed --- Seeds a random number generator_. + (line 6) +* rng_set_default: igraph_rng_set_default --- Set the default igraph random number generator_. + (line 6) +* rngtype_glibc2: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_. + (line 6) +* rngtype_mt19937: igraph_rngtype_mt19937 --- The MT19937 random number generator_. + (line 6) +* rngtype_pcg32: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_. + (line 6) +* rngtype_pcg64: igraph_rngtype_pcg64 --- The PCG random number generator [64-bit version]_. + (line 6) +* rooted_product: igraph_rooted_product --- The rooted graph product of two graphs_. + (line 6) +* roots_for_tree_layout: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_. + (line 6) +* running_mean: igraph_running_mean --- Calculates the running mean of a vector_. + (line 6) +* sbm_game: igraph_sbm_game --- Sample from a stochastic block model_. + (line 6) +* set_attribute_table: igraph_set_attribute_table --- Attach an attribute table_. + (line 6) +* set_error_handler: igraph_set_error_handler --- Sets a new error handler_. + (line 6) +* set_fatal_handler: igraph_set_fatal_handler --- Installs a fatal error handler_. + (line 6) +* set_progress_handler: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_. + (line 6) +* set_status_handler: igraph_set_status_handler --- Install of uninstall a status handler function_. + (line 6) +* set_warning_handler: igraph_set_warning_handler --- Installs a warning handler_. + (line 6) +* SETEAB: SETEAB --- Set a boolean edge attribute. + (line 6) +* SETEABV: SETEABV --- Set a boolean edge attribute for all edges. + (line 6) +* SETEAN: SETEAN --- Set a numeric edge attribute. + (line 6) +* SETEANV: SETEANV --- Set a numeric edge attribute for all edges. + (line 6) +* SETEAS: SETEAS --- Set a string edge attribute. + (line 6) +* SETEASV: SETEASV --- Set a string edge attribute for all edges. + (line 6) +* SETGAB: SETGAB --- Set a boolean graph attribute. + (line 6) +* SETGAN: SETGAN --- Set a numeric graph attribute. + (line 6) +* SETGAS: SETGAS --- Set a string graph attribute. + (line 6) +* setup: igraph_setup --- Initializes the igraph library_. + (line 6) +* SETVAB: SETVAB --- Set a boolean vertex attribute. + (line 6) +* SETVABV: SETVABV --- Set a boolean vertex attribute for all vertices. + (line 6) +* SETVAN: SETVAN --- Set a numeric vertex attribute. + (line 6) +* SETVANV: SETVANV --- Set a numeric vertex attribute for all vertices. + (line 6) +* SETVAS: SETVAS --- Set a string vertex attribute. + (line 6) +* SETVASV: SETVASV --- Set a string vertex attribute for all vertices. + (line 6) +* similarity_dice: igraph_similarity_dice --- Dice similarity coefficient_. + (line 6) +* similarity_dice_es: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_. + (line 6) +* similarity_dice_pairs: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_. + (line 6) +* similarity_inverse_log_weighted: igraph_similarity_inverse_log_weighted --- Vertex similarity based on the inverse logarithm of vertex degrees_. + (line 6) +* similarity_jaccard: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_. + (line 6) +* similarity_jaccard_es: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_. + (line 6) +* similarity_jaccard_pairs: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_. + (line 6) +* simple_cycles: igraph_simple_cycles --- Finds all simple cycles_. + (line 6) +* simple_cycles_callback: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_. + (line 6) +* simple_interconnected_islands_game: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_. + (line 6) +* simplify: igraph_simplify --- Removes loop and/or multiple edges from the graph_. + (line 6) +* simplify_and_colorize: igraph_simplify_and_colorize --- Simplify the graph and compute self-loop and edge multiplicities_. + (line 6) +* sir: igraph_sir --- Performs a number of SIR epidemics model runs on a graph_. + (line 6) +* sir_destroy: igraph_sir_destroy --- Deallocates memory associated with a SIR simulation run_. + (line 6) +* sir_t: igraph_sir_t --- The result of one SIR model simulation_. + (line 6) +* site_percolation: igraph_site_percolation --- The size of the largest component as vertices are added to a graph_. + (line 6) +* small: igraph_small --- Shorthand to create a small graph; giving the edges as arguments_. + (line 6) +* spanner: igraph_spanner --- Calculates a spanner of a graph with a given stretch factor_. + (line 6) +* sparse_adjacency: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_. + (line 6) +* sparse_weighted_adjacency: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_. + (line 6) +* sparsemat_add: igraph_sparsemat_add --- Sum of two sparse matrices_. + (line 6) +* sparsemat_add_cols: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_. + (line 6) +* sparsemat_add_rows: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_. + (line 6) +* sparsemat_arpack_rnsolve: igraph_sparsemat_arpack_rnsolve --- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK_. + (line 6) +* sparsemat_arpack_rssolve: igraph_sparsemat_arpack_rssolve --- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK_. + (line 6) +* sparsemat_as_matrix: igraph_sparsemat_as_matrix --- Converts a sparse matrix to a dense matrix_. + (line 6) +* sparsemat_cholsol: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_. + (line 6) +* sparsemat_colsums: igraph_sparsemat_colsums --- Column-wise sums_. + (line 6) +* sparsemat_compress: igraph_sparsemat_compress --- Converts a sparse matrix to column-compressed format_. + (line 6) +* sparsemat_count_nonzero: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_. + (line 6) +* sparsemat_count_nonzerotol: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_. + (line 6) +* sparsemat_destroy: igraph_sparsemat_destroy --- Deallocates memory used by a sparse matrix_. + (line 6) +* sparsemat_droptol: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_. + (line 6) +* sparsemat_dropzeros: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_. + (line 6) +* sparsemat_dupl: igraph_sparsemat_dupl --- Removes duplicate elements from a sparse matrix_. + (line 6) +* sparsemat_entry: igraph_sparsemat_entry --- Adds an element to a sparse matrix_. + (line 6) +* sparsemat_fkeep: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_. + (line 6) +* sparsemat_gaxpy: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_. + (line 6) +* sparsemat_get: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_. + (line 6) +* sparsemat_getelements: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_. + (line 6) +* sparsemat_getelements_sorted: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_. + (line 6) +* sparsemat_index: igraph_sparsemat_index --- Extracts a submatrix or a single element_. + (line 6) +* sparsemat_init: igraph_sparsemat_init --- Initializes a sparse matrix; in triplet format_. + (line 6) +* sparsemat_init_copy: igraph_sparsemat_init_copy --- Copies a sparse matrix_. + (line 6) +* sparsemat_init_diag: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_. + (line 6) +* sparsemat_init_eye: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_. + (line 6) +* sparsemat_is_cc: igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?. + (line 6) +* sparsemat_is_symmetric: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_. + (line 6) +* sparsemat_is_triplet: igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?. + (line 6) +* sparsemat_iterator_col: igraph_sparsemat_iterator_col --- Return the column of the iterator_. + (line 6) +* sparsemat_iterator_end: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_. + (line 6) +* sparsemat_iterator_get: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_. + (line 6) +* sparsemat_iterator_idx: igraph_sparsemat_iterator_idx --- Returns the element vector index of a sparse matrix iterator_. + (line 6) +* sparsemat_iterator_init: igraph_sparsemat_iterator_init --- Initialize a sparse matrix iterator_. + (line 6) +* sparsemat_iterator_next: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_. + (line 6) +* sparsemat_iterator_reset: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_. + (line 6) +* sparsemat_iterator_row: igraph_sparsemat_iterator_row --- Return the row of the iterator_. + (line 6) +* sparsemat_lsolve: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_. + (line 6) +* sparsemat_ltsolve: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_. + (line 6) +* sparsemat_lu: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_. + (line 6) +* sparsemat_luresol: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_. + (line 6) +* sparsemat_lusol: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_. + (line 6) +* sparsemat_max: igraph_sparsemat_max --- Maximum of a sparse matrix_. + (line 6) +* sparsemat_min: igraph_sparsemat_min --- Minimum of a sparse matrix_. + (line 6) +* sparsemat_minmax: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_. + (line 6) +* sparsemat_multiply: igraph_sparsemat_multiply --- Matrix multiplication_. + (line 6) +* sparsemat_ncol: igraph_sparsemat_ncol --- Number of columns_. + (line 6) +* sparsemat_nonzero_storage: igraph_sparsemat_nonzero_storage --- Returns number of stored entries of a sparse matrix_. + (line 6) +* sparsemat_nrow: igraph_sparsemat_nrow --- Number of rows_. + (line 6) +* sparsemat_numeric_destroy: igraph_sparsemat_numeric_destroy --- Deallocates memory after a numeric decomposition_. + (line 6) +* sparsemat_permute: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_. + (line 6) +* sparsemat_print: igraph_sparsemat_print --- Prints a sparse matrix to a file_. + (line 6) +* sparsemat_qr: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_. + (line 6) +* sparsemat_qrresol: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_. + (line 6) +* sparsemat_realloc: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_. + (line 6) +* sparsemat_resize: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_. + (line 6) +* sparsemat_rowsums: igraph_sparsemat_rowsums --- Row-wise sums_. + (line 6) +* sparsemat_scale: igraph_sparsemat_scale --- Scales a sparse matrix_. + (line 6) +* sparsemat_sort: igraph_sparsemat_sort --- Sorts all elements of a sparse matrix by row and column indices_. + (line 6) +* sparsemat_symblu: igraph_sparsemat_symblu --- Symbolic LU decomposition_. + (line 6) +* sparsemat_symbolic_destroy: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_. + (line 6) +* sparsemat_symbqr: igraph_sparsemat_symbqr --- Symbolic QR decomposition_. + (line 6) +* sparsemat_transpose: igraph_sparsemat_transpose --- Transposes a sparse matrix_. + (line 6) +* sparsemat_type: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_. + (line 6) +* sparsemat_usolve: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_. + (line 6) +* sparsemat_utsolve: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_. + (line 6) +* spatial_edge_lengths: igraph_spatial_edge_lengths --- Edge lengths based on spatial vertex coordinates_. + (line 6) +* split_join_distance: igraph_split_join_distance --- Calculates the split-join distance of two community structures_. + (line 6) +* square_lattice: igraph_square_lattice --- Arbitrary dimensional square lattices_. + (line 6) +* st_edge_connectivity: igraph_st_edge_connectivity --- Edge connectivity of a pair of vertices_. + (line 6) +* st_mincut: igraph_st_mincut --- Minimum cut between a source and a target vertex_. + (line 6) +* st_mincut_value: igraph_st_mincut_value --- The minimum s-t cut in a graph_. + (line 6) +* st_vertex_connectivity: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_. + (line 6) +* stack_clear: igraph_stack_clear --- Removes all elements from a stack_. + (line 6) +* stack_destroy: igraph_stack_destroy --- Destroys a stack object_. + (line 6) +* stack_empty: igraph_stack_empty --- Decides whether a stack object is empty_. + (line 6) +* stack_init: igraph_stack_init --- Initializes a stack_. + (line 6) +* stack_pop: igraph_stack_pop --- Removes and returns an element from the top of a stack_. + (line 6) +* stack_push: igraph_stack_push --- Places an element on the top of a stack_. + (line 6) +* stack_reserve: igraph_stack_reserve --- Reserve memory_. + (line 6) +* stack_size: igraph_stack_size --- Returns the number of elements in a stack_. + (line 6) +* stack_top: igraph_stack_top --- Query top element_. + (line 6) +* star: igraph_star --- Creates a star graph; every vertex connects only to the center_. + (line 6) +* static_fitness_game: igraph_static_fitness_game --- Non-growing random graph with edge probabilities proportional to node fitness scores_. + (line 6) +* static_power_law_game: igraph_static_power_law_game --- Generates a non-growing random graph with expected power-law degree distributions_. + (line 6) +* STATUS: IGRAPH_STATUS --- Report the status of an igraph function_. + (line 6) +* status: igraph_status --- Reports status from an igraph function_. + (line 6) +* status_handler_stderr: igraph_status_handler_stderr --- A simple predefined status handler function_. + (line 6) +* status_handler_t: igraph_status_handler_t --- The type of the igraph status handler functions. + (line 6) +* STATUSF: IGRAPH_STATUSF --- Report the status from an igraph function. + (line 6) +* statusf: igraph_statusf --- Report status; more flexible printf-like version_. + (line 6) +* STR: STR --- Indexing string vectors_. + (line 6) +* strength: igraph_strength --- Strength of the vertices; also called weighted vertex degree_. + (line 6) +* strerror: igraph_strerror --- Textual description of an error_. + (line 6) +* strvector_append: igraph_strvector_append --- Concatenates two string vectors_. + (line 6) +* strvector_capacity: igraph_strvector_capacity --- Returns the capacity of a string vector_. + (line 6) +* strvector_clear: igraph_strvector_clear --- Removes all elements from a string vector_. + (line 6) +* strvector_destroy: igraph_strvector_destroy --- Frees the memory allocated for the string vector_. + (line 6) +* strvector_get: igraph_strvector_get --- Retrieves an element of a string vector_. + (line 6) +* strvector_init: igraph_strvector_init --- Initializes a string vector_. + (line 6) +* strvector_init_copy: igraph_strvector_init_copy --- Initialization by copying_. + (line 6) +* strvector_merge: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_. + (line 6) +* strvector_push_back: igraph_strvector_push_back --- Adds an element to the back of a string vector_. + (line 6) +* strvector_push_back_len: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_. + (line 6) +* strvector_remove: igraph_strvector_remove --- Removes a single element from a string vector_. + (line 6) +* strvector_remove_section: igraph_strvector_remove_section --- Removes a section from a string vector_. + (line 6) +* strvector_reserve: igraph_strvector_reserve --- Reserves memory for a string vector_. + (line 6) +* strvector_resize: igraph_strvector_resize --- Resizes a string vector_. + (line 6) +* strvector_resize_min: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_. + (line 6) +* strvector_set: igraph_strvector_set --- Sets an element of the string vector from a string_. + (line 6) +* strvector_set_len: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_. + (line 6) +* strvector_size: igraph_strvector_size --- Returns the size of a string vector_. + (line 6) +* strvector_swap: igraph_strvector_swap --- Swaps all elements of two string vectors_. + (line 6) +* strvector_swap_elements: igraph_strvector_swap_elements --- Swap two elements in a string vector_. + (line 6) +* strvector_update: igraph_strvector_update --- Updates a string vector from another one_. + (line 6) +* subcomponent: igraph_subcomponent --- The vertices reachable from a given vertex_. + (line 6) +* subgraph_from_edges: igraph_subgraph_from_edges --- Creates a subgraph with the specified edges and their endpoints_. + (line 6) +* subisomorphic: igraph_subisomorphic --- Decide subgraph isomorphism_. + (line 6) +* subisomorphic_lad: igraph_subisomorphic_lad --- Check subgraph isomorphism with the LAD algorithm. + (line 6) +* subisomorphic_vf2: igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF2. + (line 6) +* symmetric_tree: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_. + (line 6) +* THREAD_SAFE: IGRAPH_THREAD_SAFE --- Specifies whether igraph was built in thread-safe mode_. + (line 6) +* TO: IGRAPH_TO --- The target vertex of an edge_. + (line 6) +* to_directed: igraph_to_directed --- Convert an undirected graph to a directed one_. + (line 6) +* to_prufer: igraph_to_prufer --- Converts a tree to its Prüfer sequence_. + (line 6) +* to_undirected: igraph_to_undirected --- Convert a directed graph to an undirected one_. + (line 6) +* topological_sorting: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_. + (line 6) +* transitive_closure: igraph_transitive_closure --- Computes the transitive closure of a graph_. + (line 6) +* transitivity_avglocal_undirected: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_. + (line 6) +* transitivity_barrat: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_. + (line 6) +* transitivity_local_undirected: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_. + (line 6) +* transitivity_undirected: igraph_transitivity_undirected --- Calculates the transitivity [clustering coefficient] of a graph_. + (line 6) +* tree_from_parent_vector: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_. + (line 6) +* tree_game: igraph_tree_game --- Generates a random tree with the given number of nodes_. + (line 6) +* triad_census: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_. + (line 6) +* triangular_lattice: igraph_triangular_lattice --- A triangular lattice with the given shape_. + (line 6) +* trussness: igraph_trussness --- Finding the "trussness" of the edges in a network_. + (line 6) +* turan: igraph_turan --- Creates a Turán graph_. + (line 6) +* unfold_tree: igraph_unfold_tree --- Unfolding a graph into a tree; by possibly multiplicating its vertices_. + (line 6) +* union: igraph_union --- Calculates the union of two graphs_. + (line 6) +* union_many: igraph_union_many --- Creates the union of many graphs_. + (line 6) +* UNLIMITED: IGRAPH_UNLIMITED --- Constant for "do not limit results"_. + (line 6) +* VAB: VAB --- Query a boolean vertex attribute_. + (line 6) +* VABV: VABV --- Query a boolean vertex attribute for all vertices_. + (line 6) +* VAN: VAN --- Query a numeric vertex attribute_. + (line 6) +* VANV: VANV --- Query a numeric vertex attribute for all vertices_. + (line 6) +* VAS: VAS --- Query a string vertex attribute_. + (line 6) +* VASV: VASV --- Query a string vertex attribute for all vertices_. + (line 6) +* vcount: igraph_vcount --- The number of vertices in a graph_. + (line 6) +* VCOUNT_MAX: IGRAPH_VCOUNT_MAX --- The maximum number of vertices supported in igraph graphs_. + (line 6) +* VECTOR: VECTOR --- Accessing an element of a vector_. + (line 6) +* vector_add: igraph_vector_add --- Add two vectors_. + (line 6) +* vector_add_constant: igraph_vector_add_constant --- Add a constant to the vector_. + (line 6) +* vector_all_almost_e: igraph_vector_all_almost_e --- Are all elements almost equal?. + (line 6) +* vector_all_e: igraph_vector_all_e --- Are all elements equal?. + (line 6) +* vector_all_g: igraph_vector_all_g --- Are all elements greater?. + (line 6) +* vector_all_ge: igraph_vector_all_ge --- Are all elements greater or equal?. + (line 6) +* vector_all_l: igraph_vector_all_l --- Are all elements less?. + (line 6) +* vector_all_le: igraph_vector_all_le --- Are all elements less or equal?. + (line 6) +* vector_append: igraph_vector_append --- Append a vector to another one_. + (line 6) +* vector_binsearch: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_. + (line 6) +* vector_binsearch_slice: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_. + (line 6) +* vector_capacity: igraph_vector_capacity --- Returns the allocated capacity of the vector_. + (line 6) +* vector_clear: igraph_vector_clear --- Removes all elements from a vector_. + (line 6) +* vector_colex_cmp: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_. + (line 6) +* vector_colex_cmp_untyped: igraph_vector_colex_cmp_untyped --- Colexicographical comparison of two vectors_. + (line 6) +* vector_complex_all_almost_e: igraph_vector_complex_all_almost_e --- Are all elements almost equal?. + (line 6) +* vector_complex_create: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_. + (line 6) +* vector_complex_create_polar: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_. + (line 6) +* vector_complex_imag: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_. + (line 6) +* vector_complex_real: igraph_vector_complex_real --- Gives the real part of a complex vector_. + (line 6) +* vector_complex_realimag: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_. + (line 6) +* vector_complex_zapsmall: igraph_vector_complex_zapsmall --- Replaces small elements of a complex vector by exact zeros_. + (line 6) +* vector_contains: igraph_vector_contains --- Linear search in a vector_. + (line 6) +* vector_contains_sorted: igraph_vector_contains_sorted --- Binary search in a sorted vector_. + (line 6) +* vector_copy_to: igraph_vector_copy_to --- Copies the contents of a vector to a C array_. + (line 6) +* vector_destroy: igraph_vector_destroy --- Destroys a vector object_. + (line 6) +* vector_difference_and_intersection_sorted: igraph_vector_difference_and_intersection_sorted --- Simultaneous difference and intersection of two sorted vectors_. + (line 6) +* vector_difference_sorted: igraph_vector_difference_sorted --- Set difference of two sorted vectors_. + (line 6) +* vector_div: igraph_vector_div --- Divide a vector by another one_. + (line 6) +* vector_empty: igraph_vector_empty --- Decides whether the size of the vector is zero_. + (line 6) +* vector_fill: igraph_vector_fill --- Fill a vector with a constant element_. + (line 6) +* vector_floor: igraph_vector_floor --- Transform a real vector to an integer vector by flooring each element_. + (line 6) +* vector_get: igraph_vector_get --- Access an element of a vector_. + (line 6) +* vector_get_ptr: igraph_vector_get_ptr --- Get the address of an element of a vector_. + (line 6) +* vector_index: igraph_vector_index --- Extract elements from a vector at specific indices_. + (line 6) +* vector_index_in_place: igraph_vector_index_in_place --- Extract elements from a vector at specific indices in-place_. + (line 6) +* vector_init: igraph_vector_init --- Initializes a vector object [constructor]_. + (line 6) +* vector_init_array: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_. + (line 6) +* vector_init_copy: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_. + (line 6) +* vector_init_range: igraph_vector_init_range --- Initializes a vector with a range_. + (line 6) +* vector_insert: igraph_vector_insert --- Inserts a single element into a vector_. + (line 6) +* vector_intersect_sorted: igraph_vector_intersect_sorted --- Set intersection of two sorted vectors_. + (line 6) +* vector_intersection_size_sorted: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_. + (line 6) +* vector_is_all_finite: igraph_vector_is_all_finite --- Check if all elements are finite_. + (line 6) +* vector_is_any_nan: igraph_vector_is_any_nan --- Check if any element is NaN_. + (line 6) +* vector_is_equal: igraph_vector_is_equal --- Are all elements equal?. + (line 6) +* vector_is_nan: igraph_vector_is_nan --- Check for each element if it is NaN_. + (line 6) +* vector_isininterval: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_. + (line 6) +* vector_lex_cmp: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_. + (line 6) +* vector_lex_cmp_untyped: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_. + (line 6) +* vector_list_capacity: igraph_vector_list_capacity --- Returns the allocated capacity of the list_. + (line 6) +* vector_list_clear: igraph_vector_list_clear --- Removes all elements from a list of vectors_. + (line 6) +* vector_list_destroy: igraph_vector_list_destroy --- Destroys a list of vectors object_. + (line 6) +* vector_list_discard: igraph_vector_list_discard --- Discards the item at the given index in the vector list_. + (line 6) +* vector_list_discard_back: igraph_vector_list_discard_back --- Discards the last item in the vector list_. + (line 6) +* vector_list_discard_fast: igraph_vector_list_discard_fast --- Discards the item at the given index in the vector list and moves the last item to its place_. + (line 6) +* vector_list_empty: igraph_vector_list_empty --- Decides whether the size of the list is zero_. + (line 6) +* vector_list_get_ptr: igraph_vector_list_get_ptr --- The address of a vector in the vector list_. + (line 6) +* vector_list_init: igraph_vector_list_init --- Initializes a list of vectors [constructor]_. + (line 6) +* vector_list_init_copy: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_. + (line 6) +* vector_list_insert: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_. + (line 6) +* vector_list_insert_copy: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_. + (line 6) +* vector_list_insert_new: igraph_vector_list_insert_new --- Inserts a new vector into the list_. + (line 6) +* vector_list_permute: igraph_vector_list_permute --- Permutes the elements of a list in place according to an index vector_. + (line 6) +* vector_list_pop_back: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_. + (line 6) +* vector_list_push_back: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_. + (line 6) +* vector_list_push_back_copy: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_. + (line 6) +* vector_list_push_back_new: igraph_vector_list_push_back_new --- Appends a new vector to the list_. + (line 6) +* vector_list_remove: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_. + (line 6) +* vector_list_remove_fast: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_. + (line 6) +* vector_list_replace: igraph_vector_list_replace --- Replaces the vector at the given index in the list with another one_. + (line 6) +* vector_list_reserve: igraph_vector_list_reserve --- Reserves memory for a list_. + (line 6) +* vector_list_resize: igraph_vector_list_resize --- Resizes the list of vectors_. + (line 6) +* vector_list_set: igraph_vector_list_set --- Sets the vector at the given index in the list_. + (line 6) +* vector_list_size: igraph_vector_list_size --- The size of the vector list_. + (line 6) +* vector_list_sort: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_. + (line 6) +* vector_list_sort_ind: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_. + (line 6) +* vector_list_swap: igraph_vector_list_swap --- Swaps all elements of two vector lists_. + (line 6) +* vector_list_swap_elements: igraph_vector_list_swap_elements --- Swap two elements in a vector list_. + (line 6) +* vector_list_tail_ptr: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_. + (line 6) +* vector_max: igraph_vector_max --- Largest element of a vector_. + (line 6) +* vector_maxdifference: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_. + (line 6) +* vector_min: igraph_vector_min --- Smallest element of a vector_. + (line 6) +* vector_minmax: igraph_vector_minmax --- Minimum and maximum elements of a vector_. + (line 6) +* vector_mul: igraph_vector_mul --- Multiply two vectors_. + (line 6) +* vector_null: igraph_vector_null --- Sets each element in the vector to zero_. + (line 6) +* vector_permute: igraph_vector_permute --- Permutes the elements of a vector in place according to an index vector_. + (line 6) +* vector_pop_back: igraph_vector_pop_back --- Removes and returns the last element of a vector_. + (line 6) +* vector_prod: igraph_vector_prod --- Calculates the product of the elements in the vector_. + (line 6) +* vector_ptr_capacity: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_. + (line 6) +* vector_ptr_clear: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_. + (line 6) +* vector_ptr_destroy: igraph_vector_ptr_destroy --- Destroys a pointer vector_. + (line 6) +* vector_ptr_destroy_all: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_. + (line 6) +* vector_ptr_free_all: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_. + (line 6) +* vector_ptr_get: igraph_vector_ptr_get --- Access an element of a pointer vector_. + (line 6) +* vector_ptr_get_item_destructor: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_. + (line 6) +* vector_ptr_init: igraph_vector_ptr_init --- Initialize a pointer vector [constructor]_. + (line 6) +* vector_ptr_init_copy: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_. + (line 6) +* vector_ptr_insert: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_. + (line 6) +* vector_ptr_permute: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_. + (line 6) +* vector_ptr_pop_back: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_. + (line 6) +* vector_ptr_push_back: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_. + (line 6) +* vector_ptr_reserve: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_. + (line 6) +* vector_ptr_resize: igraph_vector_ptr_resize --- Resizes a pointer vector_. + (line 6) +* vector_ptr_resize_min: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_. + (line 6) +* vector_ptr_set: igraph_vector_ptr_set --- Assign to an element of a pointer vector_. + (line 6) +* vector_ptr_set_item_destructor: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_. + (line 6) +* VECTOR_PTR_SET_ITEM_DESTRUCTOR: IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR --- Sets the item destructor for this pointer vector [macro version]_. + (line 6) +* vector_ptr_size: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_. + (line 6) +* vector_ptr_sort: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_. + (line 6) +* vector_ptr_sort_ind: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_. + (line 6) +* vector_push_back: igraph_vector_push_back --- Appends one element to a vector_. + (line 6) +* vector_range: igraph_vector_range --- Updates a vector to store a range_. + (line 6) +* vector_remove: igraph_vector_remove --- Removes a single element from a vector_. + (line 6) +* vector_remove_section: igraph_vector_remove_section --- Deletes a section from a vector_. + (line 6) +* vector_reserve: igraph_vector_reserve --- Reserves memory for a vector_. + (line 6) +* vector_resize: igraph_vector_resize --- Resize the vector_. + (line 6) +* vector_resize_min: igraph_vector_resize_min --- Deallocate the unused memory of a vector_. + (line 6) +* vector_reverse: igraph_vector_reverse --- Reverse the elements of a vector_. + (line 6) +* vector_reverse_section: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_. + (line 6) +* vector_reverse_sort: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_. + (line 6) +* vector_rotate_left: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_. + (line 6) +* vector_scale: igraph_vector_scale --- Multiplies all elements of a vector by a constant_. + (line 6) +* vector_search: igraph_vector_search --- Searches in a vector from a given position_. + (line 6) +* vector_set: igraph_vector_set --- Assignment to an element of a vector_. + (line 6) +* vector_shuffle: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_. + (line 6) +* vector_size: igraph_vector_size --- The size of the vector_. + (line 6) +* vector_sort: igraph_vector_sort --- Sorts the elements of the vector into ascending order_. + (line 6) +* vector_sort_ind: igraph_vector_sort_ind --- Returns a permutation of indices that sorts a vector_. + (line 6) +* vector_sub: igraph_vector_sub --- Subtract a vector from another one_. + (line 6) +* vector_sum: igraph_vector_sum --- Calculates the sum of the elements in the vector_. + (line 6) +* vector_swap: igraph_vector_swap --- Swap all elements of two vectors_. + (line 6) +* vector_swap_elements: igraph_vector_swap_elements --- Swap two elements in a vector_. + (line 6) +* vector_tail: igraph_vector_tail --- Returns the last element in a vector_. + (line 6) +* vector_update: igraph_vector_update --- Update a vector from another one_. + (line 6) +* vector_view: igraph_vector_view --- Handle a regular C array as a igraph_vector_t_. + (line 6) +* vector_which_max: igraph_vector_which_max --- Gives the index of the maximum element of the vector_. + (line 6) +* vector_which_min: igraph_vector_which_min --- Index of the smallest element_. + (line 6) +* vector_which_minmax: igraph_vector_which_minmax --- Index of the minimum and maximum elements_. + (line 6) +* vector_zapsmall: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_. + (line 6) +* version: igraph_version --- The version of the igraph C library_. + (line 6) +* vertex_coloring_greedy: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_. + (line 6) +* vertex_connectivity: igraph_vertex_connectivity --- The vertex connectivity of a graph_. + (line 6) +* vertex_disjoint_paths: igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint paths between two vertices_. + (line 6) +* vertex_path_from_edge_path: igraph_vertex_path_from_edge_path --- Converts a walk of edge IDs to the traversed vertex IDs_. + (line 6) +* vit_create: igraph_vit_create --- Creates a vertex iterator from a vertex selector_. + (line 6) +* vit_destroy: igraph_vit_destroy --- Destroys a vertex iterator_. + (line 6) +* VIT_END: IGRAPH_VIT_END --- Are we at the end?. + (line 6) +* VIT_GET: IGRAPH_VIT_GET --- Query the current position_. + (line 6) +* VIT_NEXT: IGRAPH_VIT_NEXT --- Next vertex_. + (line 6) +* VIT_RESET: IGRAPH_VIT_RESET --- Reset a vertex iterator_. + (line 6) +* VIT_SIZE: IGRAPH_VIT_SIZE --- Size of a vertex iterator_. + (line 6) +* voronoi: igraph_voronoi --- Voronoi partitioning of a graph_. + (line 6) +* vs_1: igraph_vs_1 --- Vertex set with a single vertex_. + (line 6) +* vs_adj: igraph_vs_adj --- Adjacent vertices of a vertex_. + (line 6) +* vs_all: igraph_vs_all --- Vertex set; all vertices of a graph_. + (line 6) +* vs_copy: igraph_vs_copy --- Creates a copy of a vertex selector_. + (line 6) +* vs_destroy: igraph_vs_destroy --- Destroy a vertex set_. + (line 6) +* vs_is_all: igraph_vs_is_all --- Check whether all vertices are included_. + (line 6) +* vs_nonadj: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_. + (line 6) +* vs_none: igraph_vs_none --- Empty vertex set_. + (line 6) +* vs_range: igraph_vs_range --- Vertex set; an interval of vertices_. + (line 6) +* vs_size: igraph_vs_size --- Returns the size of the vertex selector_. + (line 6) +* vs_type: igraph_vs_type --- Returns the type of the vertex selector_. + (line 6) +* vs_vector: igraph_vs_vector --- Vertex set based on a vector_. + (line 6) +* vs_vector_copy: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_. + (line 6) +* vs_vector_small: igraph_vs_vector_small --- Create a vertex set by giving its elements_. + (line 6) +* vss_1: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_. + (line 6) +* vss_all: igraph_vss_all --- All vertices of a graph [immediate version]_. + (line 6) +* vss_none: igraph_vss_none --- Empty vertex set [immediate version]_. + (line 6) +* vss_range: igraph_vss_range --- An interval of vertices [immediate version]_. + (line 6) +* vss_vector: igraph_vss_vector --- Vertex set based on a vector [immediate version]_. + (line 6) +* WARNING: IGRAPH_WARNING --- Triggers a warning_. + (line 6) +* warning: igraph_warning --- Reports a warning_. + (line 6) +* warning_handler_ignore: igraph_warning_handler_ignore --- Ignores all warnings_. + (line 6) +* warning_handler_print: igraph_warning_handler_print --- Prints all warnings to the standard error_. + (line 6) +* warning_handler_t: igraph_warning_handler_t --- The type of igraph warning handler functions_. + (line 6) +* WARNINGF: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_. + (line 6) +* warningf: igraph_warningf --- Reports a warning; printf-like version_. + (line 6) +* watts_strogatz_game: igraph_watts_strogatz_game --- The Watts-Strogatz small-world model_. + (line 6) +* weighted_adjacency: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_. + (line 6) +* weighted_biadjacency: igraph_weighted_biadjacency --- Creates a bipartite graph from a weighted bipartite adjacency matrix_. + (line 6) +* weighted_clique_number: igraph_weighted_clique_number --- Finds the weight of the largest weight clique in the graph_. + (line 6) +* weighted_cliques: igraph_weighted_cliques --- Finds all cliques in a given weight range in a vertex weighted graph_. + (line 6) +* wheel: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_. + (line 6) +* widest_path_widths_dijkstra: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_. + (line 6) +* widest_path_widths_floyd_warshall: igraph_widest_path_widths_floyd_warshall --- Widths of widest paths between vertices_. + (line 6) +* write_graph_dimacs_flow: igraph_write_graph_dimacs_flow --- Write a graph in DIMACS format_. + (line 6) +* write_graph_dot: igraph_write_graph_dot --- Write the graph to a stream in DOT format_. + (line 6) +* write_graph_edgelist: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_. + (line 6) +* write_graph_gml: igraph_write_graph_gml --- Write the graph to a stream in GML format_. + (line 6) +* write_graph_graphml: igraph_write_graph_graphml --- Writes the graph to a file in GraphML format_. + (line 6) +* write_graph_leda: igraph_write_graph_leda --- Write a graph in LEDA native graph format_. + (line 6) +* write_graph_lgl: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_. + (line 6) +* write_graph_ncol: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_. + (line 6) +* write_graph_pajek: igraph_write_graph_pajek --- Writes a graph to a file in Pajek format_. + (line 6) + + +Tag Table: +Node: Top220 +Node: Introduction10355 +Node: igraph is free software13476 +Node: Citing igraph14505 +Node: Installation14966 +Node: Prerequisites15796 +Node: Installation <1>17318 +Node: General build instructions17593 +Node: Specific instructions for Windows19172 +Node: Microsoft Visual Studio19465 +Node: vcpkg20525 +Node: MSYS221832 +Node: Notable configuration options23707 +Node: Building the documentation26630 +Node: Notes for package maintainers27595 +Node: Auto-detection of dependencies28040 +Node: Shared and static builds29390 +Node: Cross-compiling30154 +Node: Additional notes32668 +Node: Tutorial33678 +Node: Compiling programs using igraph33936 +Node: Compiling with CMake36873 +Node: Compiling without CMake38928 +Node: Running the program40429 +Node: Creating your first graphs41002 +Node: Calculating various properties of graphs47225 +Node: Basic data types and interface51710 +Node: The igraph data model52093 +Node: General conventions of igraph functions54496 +Node: Atomic data types55545 +Node: Setup and initialization58078 +Node: igraph_setup --- Initializes the igraph library_58905 +Node: The basic interface59788 +Node: Graph constructors and destructors61160 +Node: igraph_empty --- Creates an empty graph with some vertices and no edges_61987 +Node: igraph_empty_attrs --- Creates an empty graph with some vertices; no edges and some graph attributes_63596 +Node: igraph_copy --- Creates an exact [deep] copy of a graph_66126 +Node: igraph_destroy --- Frees the memory allocated for a graph object_67420 +Node: Basic query operations68145 +Node: igraph_vcount --- The number of vertices in a graph_70134 +Node: igraph_ecount --- The number of edges in a graph_70614 +Node: igraph_is_directed --- Is this a directed graph?71142 +Node: igraph_edge --- Returns the head and tail vertices of an edge_71791 +Node: igraph_edges --- Gives the head and tail vertices of a series of edges_73350 +Node: IGRAPH_FROM --- The source vertex of an edge_75228 +Node: IGRAPH_TO --- The target vertex of an edge_76091 +Node: IGRAPH_OTHER --- The other endpoint of an edge_76931 +Node: igraph_get_eid --- Get the edge ID from the endpoints of an edge_77965 +Node: igraph_get_eids --- Return edge IDs based on the adjacent vertices_80116 +Node: igraph_get_all_eids_between --- Returns all edge IDs between a pair of vertices_82644 +Node: igraph_neighbors --- Adjacent vertices to a vertex_83991 +Node: igraph_incident --- Gives the incident edges of a vertex_86609 +Node: igraph_degree --- The degree of some vertices in a graph_88288 +Node: igraph_degree_1 --- The degree of of a single vertex in the graph_91122 +Node: Adding and deleting vertices and edges92741 +Node: igraph_add_edge --- Adds a single edge to a graph_93758 +Node: igraph_add_edges --- Adds edges to a graph object_95162 +Node: igraph_add_vertices --- Adds vertices to a graph_96724 +Node: igraph_delete_edges --- Removes edges from a graph_97777 +Node: igraph_delete_vertices --- Removes some vertices [with all their edges] from the graph_98783 +Node: igraph_delete_vertices_map --- Removes some vertices [with all their edges] from the graph_99996 +Node: Miscellaneous macros and helper functions101795 +Node: IGRAPH_VCOUNT_MAX --- The maximum number of vertices supported in igraph graphs_102970 +Node: IGRAPH_ECOUNT_MAX --- The maximum number of edges supported in igraph graphs_103758 +Node: IGRAPH_UNLIMITED --- Constant for "do not limit results"_104622 +Node: igraph_expand_path_to_pairs --- Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector_105393 +Node: igraph_invalidate_cache --- Invalidates the internal cache of an igraph graph_106822 +Node: igraph_is_same_graph --- Are two graphs identical as labelled graphs?108334 +Node: Error handling110184 +Node: Error handling basics110459 +Node: Error handlers111229 +Node: igraph_error_handler_t --- The type of error handler functions_113539 +Node: igraph_error_handler_abort --- Abort program in case of error_114363 +Node: igraph_error_handler_ignore --- Ignore errors_114907 +Node: igraph_error_handler_printignore --- Print and ignore errors_115409 +Node: Error codes115899 +Node: igraph_error_t --- Return type for functions returning an error code_116716 +Node: igraph_error_type_t --- Error code type_117316 +Node: igraph_strerror --- Textual description of an error_122059 +Node: Warning messages122677 +Node: igraph_warning_handler_t --- The type of igraph warning handler functions_124974 +Node: igraph_set_warning_handler --- Installs a warning handler_125681 +Node: IGRAPH_WARNING --- Triggers a warning_126476 +Node: IGRAPH_WARNINGF --- Triggers a warning; with printf-like syntax_127057 +Node: igraph_warning --- Reports a warning_128007 +Node: igraph_warningf --- Reports a warning; printf-like version_128738 +Node: igraph_warning_handler_ignore --- Ignores all warnings_129881 +Node: igraph_warning_handler_print --- Prints all warnings to the standard error_130653 +Node: Advanced topics131417 +Node: Writing error handlers131748 +Node: igraph_set_error_handler --- Sets a new error handler_133906 +Node: Error handling internals134703 +Node: IGRAPH_ERROR --- Triggers an error_136218 +Node: IGRAPH_ERRORF --- Triggers an error; with printf-like syntax_137488 +Node: igraph_error --- Reports an error_139192 +Node: igraph_errorf --- Reports an error; printf-like version_140382 +Node: IGRAPH_CHECK --- Checks the return value of a function call_141471 +Node: IGRAPH_CHECK_CALLBACK --- Checks the return value of a callback_143001 +Node: Deallocating memory144639 +Node: IGRAPH_FINALLY --- Registers an object for deallocation_146215 +Node: IGRAPH_FINALLY_CLEAN --- Signals clean deallocation of objects_147590 +Node: IGRAPH_FINALLY_FREE --- Deallocates objects registered at the current level_148523 +Node: Writing igraph functions with proper error handling149353 +Node: Fatal errors150809 +Node: igraph_fatal_handler_t --- The type of igraph fatal error handler functions_152869 +Node: igraph_set_fatal_handler --- Installs a fatal error handler_153666 +Node: igraph_fatal_handler_abort --- Abort program in case of fatal error_154651 +Node: IGRAPH_FATAL --- Triggers a fatal error_155212 +Node: IGRAPH_FATALF --- Triggers a fatal error; with printf-like syntax_155988 +Node: IGRAPH_ASSERT --- igraph-specific replacement for assert[]_156945 +Node: igraph_fatal --- Triggers a fatal error_158099 +Node: igraph_fatalf --- Triggers a fatal error; printf-like syntax_158978 +Node: Error handling and threads159973 +Node: Memory [de]allocation160406 +Node: About allocation functions160685 +Node: Available allocation functions161923 +Node: igraph_malloc --- Allocates memory that can be safely deallocated by igraph functions_162820 +Node: igraph_calloc --- Allocates memory that can be safely deallocated by igraph functions_164060 +Node: igraph_realloc --- Reallocate memory that can be safely deallocated by igraph functions_165439 +Node: igraph_free --- Deallocates memory that was allocated by igraph functions_166662 +Node: Data structure library; vector; matrix; other data types167755 +Node: About template types168333 +Node: Vectors172382 +Node: About igraph_vector_t objects173054 +Node: Constructors and destructors175411 +Node: igraph_vector_init --- Initializes a vector object [constructor]_178093 +Node: igraph_vector_init_array --- Initializes a vector from an ordinary C array [constructor]_179815 +Node: igraph_vector_init_copy --- Initializes a vector from another vector object [constructor]_180801 +Node: igraph_vector_init_range --- Initializes a vector with a range_181843 +Node: igraph_vector_destroy --- Destroys a vector object_182911 +Node: Initializing elements183919 +Node: igraph_vector_null --- Sets each element in the vector to zero_184499 +Node: igraph_vector_fill --- Fill a vector with a constant element_185394 +Node: igraph_vector_range --- Updates a vector to store a range_186082 +Node: Accessing elements187079 +Node: VECTOR --- Accessing an element of a vector_189095 +Node: igraph_vector_get --- Access an element of a vector_189664 +Node: igraph_vector_get_ptr --- Get the address of an element of a vector_190741 +Node: igraph_vector_set --- Assignment to an element of a vector_191864 +Node: igraph_vector_tail --- Returns the last element in a vector_192802 +Node: igraph_vector_index --- Extract elements from a vector at specific indices_193494 +Node: igraph_vector_index_in_place --- Extract elements from a vector at specific indices in-place_194558 +Node: Vector views195540 +Node: igraph_vector_view --- Handle a regular C array as a igraph_vector_t_195844 +Node: Copying vectors197196 +Node: igraph_vector_copy_to --- Copies the contents of a vector to a C array_197866 +Node: igraph_vector_update --- Update a vector from another one_198486 +Node: igraph_vector_append --- Append a vector to another one_199416 +Node: igraph_vector_swap --- Swap all elements of two vectors_200244 +Node: Exchanging elements200741 +Node: igraph_vector_swap_elements --- Swap two elements in a vector_201866 +Node: igraph_vector_reverse --- Reverse the elements of a vector_202590 +Node: igraph_vector_reverse_section --- Reverse the elements in a section of a vector_203345 +Node: igraph_vector_rotate_left --- Rotates the elements of a vector to the left_204273 +Node: igraph_vector_shuffle --- Shuffles a vector in-place using the Fisher-Yates method_205264 +Node: igraph_vector_permute --- Permutes the elements of a vector in place according to an index vector_206709 +Node: Vector operations208656 +Node: igraph_vector_add_constant --- Add a constant to the vector_209709 +Node: igraph_vector_scale --- Multiplies all elements of a vector by a constant_210358 +Node: igraph_vector_add --- Add two vectors_211114 +Node: igraph_vector_sub --- Subtract a vector from another one_211951 +Node: igraph_vector_mul --- Multiply two vectors_212814 +Node: igraph_vector_div --- Divide a vector by another one_213613 +Node: igraph_vector_floor --- Transform a real vector to an integer vector by flooring each element_214594 +Node: Vector comparisons215489 +Node: igraph_vector_all_e --- Are all elements equal?216927 +Node: igraph_vector_all_almost_e --- Are all elements almost equal?217911 +Node: igraph_vector_all_l --- Are all elements less?219028 +Node: igraph_vector_all_g --- Are all elements greater?219900 +Node: igraph_vector_all_le --- Are all elements less or equal?220776 +Node: igraph_vector_all_ge --- Are all elements greater or equal?221690 +Node: igraph_vector_is_equal --- Are all elements equal?222614 +Node: igraph_vector_zapsmall --- Replaces small elements of a vector by exact zeros_223617 +Node: igraph_vector_lex_cmp --- Lexicographical comparison of two vectors [type-safe variant]_225092 +Node: igraph_vector_lex_cmp_untyped --- Lexicographical comparison of two vectors [non-type-safe]_226831 +Node: igraph_vector_colex_cmp --- Colexicographical comparison of two vectors_228629 +Node: igraph_vector_colex_cmp_untyped --- Colexicographical comparison of two vectors_230486 +Node: Finding minimum and maximum232276 +Node: igraph_vector_min --- Smallest element of a vector_233274 +Node: igraph_vector_max --- Largest element of a vector_233872 +Node: igraph_vector_which_min --- Index of the smallest element_234535 +Node: igraph_vector_which_max --- Gives the index of the maximum element of the vector_235380 +Node: igraph_vector_minmax --- Minimum and maximum elements of a vector_236301 +Node: igraph_vector_which_minmax --- Index of the minimum and maximum elements_237419 +Node: Vector properties238602 +Node: igraph_vector_empty --- Decides whether the size of the vector is zero_240222 +Node: igraph_vector_size --- The size of the vector_240808 +Node: igraph_vector_capacity --- Returns the allocated capacity of the vector_241443 +Node: igraph_vector_sum --- Calculates the sum of the elements in the vector_242487 +Node: igraph_vector_prod --- Calculates the product of the elements in the vector_243211 +Node: igraph_vector_isininterval --- Checks if all elements of a vector are in the given interval_243976 +Node: igraph_vector_maxdifference --- The maximum absolute difference of m1 and m2_245062 +Node: igraph_vector_is_nan --- Check for each element if it is NaN_246191 +Node: igraph_vector_is_any_nan --- Check if any element is NaN_247152 +Node: igraph_vector_is_all_finite --- Check if all elements are finite_247823 +Node: Searching for elements248450 +Node: igraph_vector_contains --- Linear search in a vector_249405 +Node: igraph_vector_search --- Searches in a vector from a given position_250154 +Node: igraph_vector_binsearch --- Finds an element by binary searching a sorted vector_251412 +Node: igraph_vector_binsearch_slice --- Finds an element by binary searching a sorted slice of a vector_252936 +Node: igraph_vector_contains_sorted --- Binary search in a sorted vector_254976 +Node: Resizing operations255787 +Node: igraph_vector_clear --- Removes all elements from a vector_257142 +Node: igraph_vector_reserve --- Reserves memory for a vector_257818 +Node: igraph_vector_resize --- Resize the vector_259129 +Node: igraph_vector_resize_min --- Deallocate the unused memory of a vector_260709 +Node: igraph_vector_push_back --- Appends one element to a vector_261938 +Node: igraph_vector_pop_back --- Removes and returns the last element of a vector_263365 +Node: igraph_vector_insert --- Inserts a single element into a vector_264081 +Node: igraph_vector_remove --- Removes a single element from a vector_265104 +Node: igraph_vector_remove_section --- Deletes a section from a vector_265838 +Node: Complex vector operations266557 +Node: igraph_vector_complex_real --- Gives the real part of a complex vector_267884 +Node: igraph_vector_complex_imag --- Gives the imaginary part of a complex vector_268673 +Node: igraph_vector_complex_realimag --- Gives the real and imaginary parts of a complex vector_269571 +Node: igraph_vector_complex_create --- Creates a complex vector from a real and imaginary part_270692 +Node: igraph_vector_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_271790 +Node: igraph_vector_complex_all_almost_e --- Are all elements almost equal?272900 +Node: igraph_vector_complex_zapsmall --- Replaces small elements of a complex vector by exact zeros_274201 +Node: Sorting275985 +Node: igraph_vector_sort --- Sorts the elements of the vector into ascending order_276658 +Node: igraph_vector_reverse_sort --- Sorts the elements of the vector into descending order_277360 +Node: igraph_vector_sort_ind --- Returns a permutation of indices that sorts a vector_278177 +Node: Set operations on sorted vectors279826 +Node: igraph_vector_intersect_sorted --- Set intersection of two sorted vectors_280772 +Node: igraph_vector_intersection_size_sorted --- Intersection size of two sorted vectors_282508 +Node: igraph_vector_difference_sorted --- Set difference of two sorted vectors_284298 +Node: igraph_vector_difference_and_intersection_sorted --- Simultaneous difference and intersection of two sorted vectors_285467 +Node: Pointer vectors [igraph_vector_ptr_t]287818 +Node: igraph_vector_ptr_init --- Initialize a pointer vector [constructor]_293740 +Node: igraph_vector_ptr_init_copy --- Initializes a pointer vector from another one [constructor]_294840 +Node: igraph_vector_ptr_destroy --- Destroys a pointer vector_296297 +Node: igraph_vector_ptr_free_all --- Frees all the elements of a pointer vector_297139 +Node: igraph_vector_ptr_destroy_all --- Frees all the elements and destroys the pointer vector_298364 +Node: igraph_vector_ptr_size --- Gives the number of elements in the pointer vector_299631 +Node: igraph_vector_ptr_capacity --- Returns the allocated capacity of the pointer vector_300409 +Node: igraph_vector_ptr_clear --- Removes all elements from a pointer vector_301148 +Node: igraph_vector_ptr_reserve --- Reserves memory for a pointer vector for later use_302484 +Node: igraph_vector_ptr_resize --- Resizes a pointer vector_303107 +Node: igraph_vector_ptr_resize_min --- Deallocate the unused memory of a pointer vector_304128 +Node: igraph_vector_ptr_push_back --- Appends an element to the back of a pointer vector_305581 +Node: igraph_vector_ptr_pop_back --- Removes and returns the last element of a pointer vector_306759 +Node: igraph_vector_ptr_insert --- Inserts a single element into a pointer vector_307567 +Node: igraph_vector_ptr_get --- Access an element of a pointer vector_308627 +Node: igraph_vector_ptr_set --- Assign to an element of a pointer vector_309374 +Node: igraph_vector_ptr_sort --- Sorts the pointer vector based on an external comparison function_310141 +Node: igraph_vector_ptr_sort_ind --- Returns a permutation of indices that sorts a vector of pointers_311831 +Node: igraph_vector_ptr_permute --- Permutes the elements of a pointer vector in place according to an index vector_313718 +Node: igraph_vector_ptr_get_item_destructor --- Gets the current item destructor for this pointer vector_315892 +Node: igraph_vector_ptr_set_item_destructor --- Sets the item destructor for this pointer vector_317074 +Node: IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR --- Sets the item destructor for this pointer vector [macro version]_318266 +Node: Matrices319411 +Node: About igraph_matrix_t objects320114 +Node: Matrix constructors and destructors320503 +Node: igraph_matrix_init --- Initializes a matrix_321231 +Node: igraph_matrix_init_array --- Initializes a matrix from an ordinary C array [constructor]_322253 +Node: igraph_matrix_init_copy --- Copies a matrix_323947 +Node: igraph_matrix_destroy --- Destroys a matrix object_324818 +Node: Initializing elements <1>325420 +Node: igraph_matrix_null --- Sets all elements in a matrix to zero_325865 +Node: igraph_matrix_fill --- Fill with an element_326383 +Node: Accessing elements of a matrix326919 +Node: MATRIX --- Accessing an element of a matrix_327551 +Node: igraph_matrix_get --- Extract an element from a matrix_328181 +Node: igraph_matrix_get_ptr --- Pointer to an element of a matrix_329103 +Node: igraph_matrix_set --- Set an element_329946 +Node: Matrix views330611 +Node: igraph_matrix_view --- Creates a matrix view into an existing array_331132 +Node: igraph_matrix_view_from_vector --- Creates a matrix view that treats an existing vector as a matrix_332568 +Node: Copying matrices334547 +Node: igraph_matrix_copy_to --- Copies a matrix to a regular C array_335057 +Node: igraph_matrix_update --- Update from another matrix_336100 +Node: igraph_matrix_swap --- Swap two matrices_336916 +Node: Operations on rows and columns337419 +Node: igraph_matrix_get_row --- Extract a row_338618 +Node: igraph_matrix_get_col --- Select a column_339384 +Node: igraph_matrix_set_row --- Set a row from a vector_340296 +Node: igraph_matrix_set_col --- Set a column from a vector_341327 +Node: igraph_matrix_swap_rows --- Swap two rows_342358 +Node: igraph_matrix_swap_cols --- Swap two columns_343098 +Node: igraph_matrix_select_rows --- Select some rows of a matrix_343856 +Node: igraph_matrix_select_cols --- Select some columns of a matrix_344983 +Node: igraph_matrix_select_rows_cols --- Select some rows and columns of a matrix_346153 +Node: Matrix operations347478 +Node: igraph_matrix_add_constant --- Add a constant to every element_348851 +Node: igraph_matrix_scale --- Multiplies each element of the matrix by a constant_349425 +Node: igraph_matrix_add --- Add two matrices_350088 +Node: igraph_matrix_sub --- Difference of two matrices_350897 +Node: igraph_matrix_mul_elements --- Elementwise matrix multiplication_351720 +Node: igraph_matrix_div_elements --- Elementwise division_352582 +Node: igraph_matrix_sum --- Sum of elements_353396 +Node: igraph_matrix_prod --- Product of all matrix elements_354000 +Node: igraph_matrix_rowsum --- Rowwise sum_354679 +Node: igraph_matrix_colsum --- Columnwise sum_355441 +Node: igraph_matrix_transpose --- Transpose of a matrix_356210 +Node: Matrix comparisons356862 +Node: igraph_matrix_all_e --- Are all elements equal?357553 +Node: igraph_matrix_all_almost_e --- Are all elements almost equal?358517 +Node: igraph_matrix_all_l --- Are all elements less?359643 +Node: igraph_matrix_all_g --- Are all elements greater?360442 +Node: igraph_matrix_all_le --- Are all elements less or equal?361245 +Node: igraph_matrix_all_ge --- Are all elements greater or equal?362114 +Node: igraph_matrix_zapsmall --- Replaces small elements of a matrix by exact zeros_363021 +Node: Combining matrices364408 +Node: igraph_matrix_rbind --- Combine two matrices rowwise_364826 +Node: igraph_matrix_cbind --- Combine matrices columnwise_365691 +Node: Finding minimum and maximum <1>366489 +Node: igraph_matrix_min --- Smallest element of a matrix_367456 +Node: igraph_matrix_max --- Largest element of a matrix_368073 +Node: igraph_matrix_which_min --- Indices of the smallest element_368810 +Node: igraph_matrix_which_max --- Indices of the largest element_369838 +Node: igraph_matrix_minmax --- Minimum and maximum elements of a matrix_370879 +Node: igraph_matrix_which_minmax --- Indices of the minimum and maximum elements_371994 +Node: Matrix properties373452 +Node: igraph_matrix_empty --- Is the matrix empty?374556 +Node: igraph_matrix_isnull --- Checks for a null matrix_375210 +Node: igraph_matrix_size --- The number of elements in a matrix_375883 +Node: igraph_matrix_capacity --- Returns the number of elements allocated for a matrix_376508 +Node: igraph_matrix_nrow --- The number of rows in a matrix_377801 +Node: igraph_matrix_ncol --- The number of columns in a matrix_378431 +Node: igraph_matrix_is_symmetric --- Is the matrix symmetric?379044 +Node: igraph_matrix_maxdifference --- Maximum absolute difference between two matrices_379828 +Node: Searching for elements <1>380863 +Node: igraph_matrix_contains --- Search for an element_381284 +Node: igraph_matrix_search --- Search from a given position_381983 +Node: Resizing operations <1>383321 +Node: igraph_matrix_resize --- Resizes a matrix_384184 +Node: igraph_matrix_resize_min --- Deallocates unused memory for a matrix_385198 +Node: igraph_matrix_add_rows --- Adds rows to a matrix_385988 +Node: igraph_matrix_add_cols --- Adds columns to a matrix_386735 +Node: igraph_matrix_remove_row --- Remove a row_387473 +Node: igraph_matrix_remove_col --- Removes a column from a matrix_388148 +Node: Complex matrix operations388798 +Node: igraph_matrix_complex_real --- Gives the real part of a complex matrix_390114 +Node: igraph_matrix_complex_imag --- Gives the imaginary part of a complex matrix_390903 +Node: igraph_matrix_complex_realimag --- Gives the real and imaginary parts of a complex matrix_391801 +Node: igraph_matrix_complex_create --- Creates a complex matrix from a real and imaginary part_392922 +Node: igraph_matrix_complex_create_polar --- Creates a complex matrix from a magnitude and an angle_394020 +Node: igraph_matrix_complex_all_almost_e --- Are all elements almost equal?395130 +Node: igraph_matrix_complex_zapsmall --- Replaces small elements of a complex matrix by exact zeros_396430 +Node: Sparse matrices398214 +Node: About sparse matrices398832 +Node: Creating sparse matrix objects400315 +Node: igraph_sparsemat_init --- Initializes a sparse matrix; in triplet format_401405 +Node: igraph_sparsemat_init_copy --- Copies a sparse matrix_403051 +Node: igraph_sparsemat_init_diag --- Creates a sparse diagonal matrix_404132 +Node: igraph_sparsemat_init_eye --- Creates a sparse identity matrix_405314 +Node: igraph_sparsemat_realloc --- Allocates more [or less] memory for a sparse matrix_406494 +Node: igraph_sparsemat_destroy --- Deallocates memory used by a sparse matrix_407504 +Node: Query properties of a sparse matrix408158 +Node: igraph_sparsemat_index --- Extracts a submatrix or a single element_410909 +Node: igraph_sparsemat_nrow --- Number of rows_412671 +Node: igraph_sparsemat_ncol --- Number of columns_413283 +Node: igraph_sparsemat_type --- Type of a sparse matrix [triplet or column-compressed]_413917 +Node: igraph_sparsemat_is_triplet --- Is this sparse matrix in triplet format?414748 +Node: igraph_sparsemat_is_cc --- Is this sparse matrix in column-compressed format?415544 +Node: igraph_sparsemat_is_symmetric --- Returns whether a sparse matrix is symmetric_416368 +Node: igraph_sparsemat_get --- Return the value of a single element from a sparse matrix_417162 +Node: igraph_sparsemat_getelements --- Returns all elements of a sparse matrix_418134 +Node: igraph_sparsemat_getelements_sorted --- Returns all elements of a sparse matrix; sorted by row and column indices_420068 +Node: igraph_sparsemat_min --- Minimum of a sparse matrix_422238 +Node: igraph_sparsemat_max --- Maximum of a sparse matrix_422966 +Node: igraph_sparsemat_minmax --- Minimum and maximum of a sparse matrix_423648 +Node: igraph_sparsemat_count_nonzero --- Counts nonzero elements of a sparse matrix_424635 +Node: igraph_sparsemat_count_nonzerotol --- Counts nonzero elements of a sparse matrix; ignoring elements close to zero_425381 +Node: igraph_sparsemat_rowsums --- Row-wise sums_426342 +Node: igraph_sparsemat_colsums --- Column-wise sums_427178 +Node: igraph_sparsemat_nonzero_storage --- Returns number of stored entries of a sparse matrix_428086 +Node: Operations on sparse matrices429206 +Node: igraph_sparsemat_entry --- Adds an element to a sparse matrix_431417 +Node: igraph_sparsemat_fkeep --- Filters the elements of a sparse matrix_432572 +Node: igraph_sparsemat_dropzeros --- Drops the zero elements from a sparse matrix_434138 +Node: igraph_sparsemat_droptol --- Drops the almost zero elements from a sparse matrix_434986 +Node: igraph_sparsemat_scale --- Scales a sparse matrix_436017 +Node: igraph_sparsemat_permute --- Permutes the rows and columns of a sparse matrix_436795 +Node: igraph_sparsemat_transpose --- Transposes a sparse matrix_438028 +Node: igraph_sparsemat_add --- Sum of two sparse matrices_438809 +Node: igraph_sparsemat_multiply --- Matrix multiplication_439964 +Node: igraph_sparsemat_gaxpy --- Matrix-vector product; added to another vector_440954 +Node: igraph_sparsemat_add_rows --- Adds rows to a sparse matrix_441930 +Node: igraph_sparsemat_add_cols --- Adds columns to a sparse matrix_442732 +Node: igraph_sparsemat_resize --- Resizes a sparse matrix and clears all the elements_443553 +Node: igraph_sparsemat_sort --- Sorts all elements of a sparse matrix by row and column indices_444674 +Node: Operations on sparse matrix iterators445909 +Node: igraph_sparsemat_iterator_init --- Initialize a sparse matrix iterator_447521 +Node: igraph_sparsemat_iterator_reset --- Reset a sparse matrix iterator to the first element_448370 +Node: igraph_sparsemat_iterator_end --- Query if the iterator is past the last element_449225 +Node: igraph_sparsemat_iterator_row --- Return the row of the iterator_450071 +Node: igraph_sparsemat_iterator_col --- Return the column of the iterator_450811 +Node: igraph_sparsemat_iterator_get --- Return the element at the current iterator position_451565 +Node: igraph_sparsemat_iterator_next --- Let a sparse matrix iterator go to the next element_452382 +Node: igraph_sparsemat_iterator_idx --- Returns the element vector index of a sparse matrix iterator_453254 +Node: Operations that change the internal representation454008 +Node: igraph_sparsemat_compress --- Converts a sparse matrix to column-compressed format_454660 +Node: igraph_sparsemat_dupl --- Removes duplicate elements from a sparse matrix_455736 +Node: Decompositions and solving linear systems456624 +Node: igraph_sparsemat_symblu --- Symbolic LU decomposition_459027 +Node: igraph_sparsemat_symbqr --- Symbolic QR decomposition_460402 +Node: igraph_sparsemat_lsolve --- Solves a lower-triangular linear system_461854 +Node: igraph_sparsemat_ltsolve --- Solves an upper-triangular linear system_462899 +Node: igraph_sparsemat_usolve --- Solves an upper-triangular linear system_463955 +Node: igraph_sparsemat_utsolve --- Solves a lower-triangular linear system_464930 +Node: igraph_sparsemat_cholsol --- Solves a symmetric linear system via Cholesky decomposition_466084 +Node: igraph_sparsemat_lusol --- Solves a linear system via LU decomposition_467381 +Node: igraph_sparsemat_lu --- LU decomposition of a sparse matrix_468821 +Node: igraph_sparsemat_qr --- QR decomposition of a sparse matrix_470481 +Node: igraph_sparsemat_luresol --- Solves a linear system using a precomputed LU decomposition_472095 +Node: igraph_sparsemat_qrresol --- Solves a linear system using a precomputed QR decomposition_473614 +Node: igraph_sparsemat_symbolic_destroy --- Deallocates memory after a symbolic decomposition_475178 +Node: igraph_sparsemat_numeric_destroy --- Deallocates memory after a numeric decomposition_476137 +Node: Eigenvalues and eigenvectors477001 +Node: igraph_sparsemat_arpack_rssolve --- Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK_477692 +Node: igraph_sparsemat_arpack_rnsolve --- Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK_480130 +Node: Conversion to other data types482354 +Node: igraph_matrix_as_sparsemat --- Converts a dense matrix to a sparse matrix_482913 +Node: igraph_sparsemat_as_matrix --- Converts a sparse matrix to a dense matrix_484115 +Node: Writing to a file; or to the screen485184 +Node: igraph_sparsemat_print --- Prints a sparse matrix to a file_485537 +Node: Stacks486471 +Node: igraph_stack_init --- Initializes a stack_487689 +Node: igraph_stack_destroy --- Destroys a stack object_488278 +Node: igraph_stack_reserve --- Reserve memory_488906 +Node: igraph_stack_empty --- Decides whether a stack object is empty_489693 +Node: igraph_stack_size --- Returns the number of elements in a stack_490302 +Node: igraph_stack_clear --- Removes all elements from a stack_490905 +Node: igraph_stack_push --- Places an element on the top of a stack_491412 +Node: igraph_stack_pop --- Removes and returns an element from the top of a stack_492221 +Node: igraph_stack_top --- Query top element_493000 +Node: Double-ended queues493541 +Node: igraph_dqueue_init --- Initialize a double ended queue [deque]_495232 +Node: igraph_dqueue_destroy --- Destroy a double ended queue_495897 +Node: igraph_dqueue_empty --- Decide whether the queue is empty_496414 +Node: igraph_dqueue_full --- Check whether the queue is full_497040 +Node: igraph_dqueue_clear --- Remove all elements from the queue_497782 +Node: igraph_dqueue_size --- Number of elements in the queue_498287 +Node: igraph_dqueue_head --- Head of the queue_498870 +Node: igraph_dqueue_back --- Tail of the queue_499433 +Node: igraph_dqueue_get --- Access an element in a queue_499991 +Node: igraph_dqueue_pop --- Remove the head_500584 +Node: igraph_dqueue_pop_back --- Removes the tail_501175 +Node: igraph_dqueue_push --- Appends an element_501766 +Node: Maximum and minimum heaps502606 +Node: igraph_heap_init --- Initializes an empty heap object_503937 +Node: igraph_heap_init_array --- Build a heap from an array_504673 +Node: igraph_heap_destroy --- Destroys an initialized heap object_505545 +Node: igraph_heap_clear --- Removes all elements from a heap_506062 +Node: igraph_heap_empty --- Decides whether a heap object is empty_506800 +Node: igraph_heap_push --- Add an element_507402 +Node: igraph_heap_top --- Top element_508134 +Node: igraph_heap_delete_top --- Removes and returns the top element_508711 +Node: igraph_heap_size --- Number of elements in the heap_509450 +Node: igraph_heap_reserve --- Reserves memory for a heap_510070 +Node: String vectors510886 +Node: igraph_strvector_init --- Initializes a string vector_514803 +Node: igraph_strvector_init_copy --- Initialization by copying_515591 +Node: igraph_strvector_destroy --- Frees the memory allocated for the string vector_516453 +Node: STR --- Indexing string vectors_517245 +Node: igraph_strvector_get --- Retrieves an element of a string vector_518405 +Node: igraph_strvector_set --- Sets an element of the string vector from a string_519108 +Node: igraph_strvector_set_len --- Sets an element of the string vector given a buffer and its size_520109 +Node: igraph_strvector_push_back --- Adds an element to the back of a string vector_521355 +Node: igraph_strvector_push_back_len --- Adds a string of the given length to the back of a string vector_522216 +Node: igraph_strvector_swap_elements --- Swap two elements in a string vector_523230 +Node: igraph_strvector_remove --- Removes a single element from a string vector_524069 +Node: igraph_strvector_remove_section --- Removes a section from a string vector_524805 +Node: igraph_strvector_append --- Concatenates two string vectors_525634 +Node: igraph_strvector_merge --- Moves the contents of a string vector to the end of another_527024 +Node: igraph_strvector_swap --- Swaps all elements of two string vectors_528468 +Node: igraph_strvector_update --- Updates a string vector from another one_529127 +Node: igraph_strvector_clear --- Removes all elements from a string vector_530029 +Node: igraph_strvector_resize --- Resizes a string vector_530720 +Node: igraph_strvector_reserve --- Reserves memory for a string vector_531634 +Node: igraph_strvector_resize_min --- Deallocates the unused memory of a string vector_533030 +Node: igraph_strvector_size --- Returns the size of a string vector_534110 +Node: igraph_strvector_capacity --- Returns the capacity of a string vector_534755 +Node: Lists of vectors; matrices and graphs535332 +Node: About igraph_vector_list_t objects535879 +Node: Constructors and destructors <1>540628 +Node: igraph_vector_list_init --- Initializes a list of vectors [constructor]_543108 +Node: igraph_vector_list_init_copy --- Initializes a list of vectors from another list of vectors [constructor]_544421 +Node: igraph_vector_list_destroy --- Destroys a list of vectors object_545589 +Node: Accessing elements <1>546681 +Node: igraph_vector_list_get_ptr --- The address of a vector in the vector list_549547 +Node: igraph_vector_list_tail_ptr --- The address of the last vector in the vector list_550364 +Node: igraph_vector_list_set --- Sets the vector at the given index in the list_551134 +Node: igraph_vector_list_replace --- Replaces the vector at the given index in the list with another one_552344 +Node: Vector properties <1>553640 +Node: igraph_vector_list_empty --- Decides whether the size of the list is zero_554287 +Node: igraph_vector_list_size --- The size of the vector list_554900 +Node: igraph_vector_list_capacity --- Returns the allocated capacity of the list_555576 +Node: Resizing operations <2>556594 +Node: igraph_vector_list_clear --- Removes all elements from a list of vectors_559623 +Node: igraph_vector_list_reserve --- Reserves memory for a list_560312 +Node: igraph_vector_list_resize --- Resizes the list of vectors_561703 +Node: igraph_vector_list_push_back --- Appends an existing vector to the list; transferring ownership_563425 +Node: igraph_vector_list_push_back_copy --- Appends the copy of a vector to the list_565256 +Node: igraph_vector_list_push_back_new --- Appends a new vector to the list_566580 +Node: igraph_vector_list_pop_back --- Removes the last item from the vector list and transfer ownership to the caller_567917 +Node: igraph_vector_list_insert --- Inserts an existing vector into the list; transferring ownership_569238 +Node: igraph_vector_list_insert_copy --- Inserts the copy of a vector to the list_570601 +Node: igraph_vector_list_insert_new --- Inserts a new vector into the list_571997 +Node: igraph_vector_list_remove --- Removes the item at the given index from the vector list and transfer ownership to the caller_573476 +Node: igraph_vector_list_remove_fast --- Removes the item at the given index in the vector list; move the last item to its place and transfer ownership to the caller_575657 +Node: igraph_vector_list_discard --- Discards the item at the given index in the vector list_577826 +Node: igraph_vector_list_discard_back --- Discards the last item in the vector list_579491 +Node: igraph_vector_list_discard_fast --- Discards the item at the given index in the vector list and moves the last item to its place_580265 +Node: Sorting and reordering581825 +Node: igraph_vector_list_permute --- Permutes the elements of a list in place according to an index vector_582862 +Node: igraph_vector_list_sort --- Sorts the elements of the list into ascending order_584624 +Node: igraph_vector_list_sort_ind --- Returns a permutation of indices that sorts the list_585691 +Node: igraph_vector_list_swap --- Swaps all elements of two vector lists_587149 +Node: igraph_vector_list_swap_elements --- Swap two elements in a vector list_587850 +Node: Adjacency lists588665 +Node: Adjacent vertices591051 +Node: igraph_adjlist_init --- Constructs an adjacency list of vertices from a given graph_592677 +Node: igraph_adjlist_init_empty --- Initializes an empty adjacency list_596053 +Node: igraph_adjlist_init_complementer --- Adjacency lists for the complementer graph_597046 +Node: igraph_adjlist_init_from_inclist --- Constructs an adjacency list of vertices from an incidence list_599399 +Node: igraph_adjlist_destroy --- Deallocates an adjacency list_601256 +Node: igraph_adjlist_get --- Query a vector in an adjacency list_601931 +Node: igraph_adjlist_size --- Returns the number of vertices in an adjacency list_602827 +Node: igraph_adjlist_clear --- Removes all edges from an adjacency list_603505 +Node: igraph_adjlist_sort --- Sorts each vector in an adjacency list_604215 +Node: igraph_adjlist_simplify --- Simplifies an adjacency list_605201 +Node: Incident edges606086 +Node: igraph_inclist_init --- Initializes an incidence list_606966 +Node: igraph_inclist_destroy --- Frees all memory allocated for an incidence list_609370 +Node: igraph_inclist_get --- Query a vector in an incidence list_609999 +Node: igraph_inclist_size --- Returns the number of vertices in an incidence list_610866 +Node: igraph_inclist_clear --- Removes all edges from an incidence list_611541 +Node: Lazy adjacency list for vertices612173 +Node: igraph_lazy_adjlist_init --- Initializes a lazy adjacency list_613215 +Node: igraph_lazy_adjlist_destroy --- Deallocate a lazt adjacency list_615961 +Node: igraph_lazy_adjlist_get --- Query neighbor vertices_616632 +Node: igraph_lazy_adjlist_has --- Are adjacenct vertices already stored in a lazy adjacency list?618017 +Node: igraph_lazy_adjlist_size --- Returns the number of vertices in a lazy adjacency list_618843 +Node: igraph_lazy_adjlist_clear --- Removes all edges from a lazy adjacency list_619624 +Node: Lazy incidence list for edges620300 +Node: igraph_lazy_inclist_init --- Initializes a lazy incidence list of edges_621316 +Node: igraph_lazy_inclist_destroy --- Deallocates a lazy incidence list_623661 +Node: igraph_lazy_inclist_get --- Query incident edges_624335 +Node: igraph_lazy_inclist_has --- Are incident edges already stored in a lazy inclist?625729 +Node: igraph_lazy_inclist_size --- Returns the number of vertices in a lazy incidence list_626513 +Node: igraph_lazy_inclist_clear --- Removes all edges from a lazy incidence list_627280 +Node: Partial prefix sum trees627946 +Node: igraph_psumtree_init --- Initializes a partial prefix sum tree_630667 +Node: igraph_psumtree_destroy --- Destroys a partial prefix sum tree_631534 +Node: igraph_psumtree_size --- Returns the size of the tree_632516 +Node: igraph_psumtree_get --- Retrieves the value corresponding to an item in the tree_633142 +Node: igraph_psumtree_sum --- Returns the sum of the values of the leaves in the tree_633946 +Node: igraph_psumtree_search --- Finds an item in the tree; given a value_634662 +Node: igraph_psumtree_update --- Updates the value associated to an item in the tree_636341 +Node: igraph_psumtree_reset --- Resets all the values in the tree to zero_637344 +Node: Bitsets637828 +Node: About igraph_bitset_t objects638261 +Node: Constructors and destructors <2>640060 +Node: igraph_bitset_init --- Initializes a bitset object [constructor]_641572 +Node: igraph_bitset_init_copy --- Initializes a bitset from another bitset object [constructor]_642973 +Node: igraph_bitset_destroy --- Destroys a bitset object_644019 +Node: Accessing elements <2>644936 +Node: IGRAPH_BIT_MASK --- Computes mask used to access a specific bit of an integer_647065 +Node: IGRAPH_BIT_SLOT --- Computes index used to access a specific slot of a bitset_648209 +Node: IGRAPH_BIT_SET --- Sets a specific bit in a bitset to 1 without altering other bits_649364 +Node: IGRAPH_BIT_CLEAR --- Sets a specific bit in a bitset to 0 without altering other bits_650437 +Node: IGRAPH_BIT_TEST --- Tests whether a bit is set in a bitset_651498 +Node: IGRAPH_BIT_NSLOTS --- Computes the number of slots required to store a specified number of bits_652582 +Node: Bitset operations653600 +Node: igraph_bitset_fill --- Fills a bitset with a constant value_655361 +Node: igraph_bitset_null --- Clears all bits in a bitset_656080 +Node: igraph_bitset_or --- Bitwise OR of two bitsets_656737 +Node: igraph_bitset_and --- Bitwise AND of two bitsets_657850 +Node: igraph_bitset_xor --- Bitwise XOR of two bitsets_658940 +Node: igraph_bitset_not --- Bitwise negation of a bitset_660062 +Node: igraph_bitset_popcount --- The population count of the bitset_661047 +Node: igraph_bitset_countl_zero --- The number of leading zeros in the bitset_661766 +Node: igraph_bitset_countl_one --- The number of leading ones in the bitset_662635 +Node: igraph_bitset_countr_zero --- The number of trailing zeros in the bitset_663508 +Node: igraph_bitset_countr_one --- The number of trailing ones in the bitset_664394 +Node: igraph_bitset_is_all_zero --- Are all bits zeros?665252 +Node: igraph_bitset_is_all_one --- Are all bits ones?665855 +Node: igraph_bitset_is_any_zero --- Are any bits zeros?666430 +Node: igraph_bitset_is_any_one --- Are any bits ones?667009 +Node: Bitset properties667526 +Node: igraph_bitset_size --- Returns the length of the bitset_667977 +Node: igraph_bitset_capacity --- Returns the allocated capacity of the bitset_668515 +Node: Resizing operations <3>669519 +Node: igraph_bitset_reserve --- Reserves memory for a bitset_669914 +Node: igraph_bitset_resize --- Resizes the bitset_671220 +Node: Copying bitsets672582 +Node: igraph_bitset_update --- Update a bitset from another one_672856 +Node: Random numbers673661 +Node: About random numbers in igraph674085 +Node: The default random number generator674652 +Node: igraph_rng_default --- Query the default random number generator_675203 +Node: igraph_rng_set_default --- Set the default igraph random number generator_675852 +Node: Creating random number generators677151 +Node: igraph_rng_init --- Initializes a random number generator_678311 +Node: igraph_rng_destroy --- Deallocates memory associated with a random number generator_679522 +Node: igraph_rng_seed --- Seeds a random number generator_680193 +Node: igraph_rng_bits --- The number of random bits that a random number generator can produces in a single round_680920 +Node: igraph_rng_max --- The maximum possible integer for a random number generator_681731 +Node: igraph_rng_name --- The type of a random number generator_683034 +Node: Generating random numbers683642 +Node: igraph_rng_get_bool --- Generate a random boolean_685189 +Node: igraph_rng_get_integer --- Generate an integer random number from an interval_686025 +Node: igraph_rng_get_unif01 --- Samples uniformly from the unit interval_687353 +Node: igraph_rng_get_unif --- Samples real numbers from a given interval_688304 +Node: igraph_rng_get_normal --- Samples from a normal distribution_689464 +Node: igraph_rng_get_exp --- Samples from an exponential distribution_690551 +Node: igraph_rng_get_gamma --- Samples from a gamma distribution_691515 +Node: igraph_rng_get_binom --- Samples from a binomial distribution_692580 +Node: igraph_rng_get_geom --- Samples from a geometric distribution_693676 +Node: igraph_rng_get_pois --- Samples from a Poisson distribution_694778 +Node: Supported random number generators695800 +Node: igraph_rngtype_mt19937 --- The MT19937 random number generator_696999 +Node: igraph_rngtype_glibc2 --- The random number generator introduced in GNU libc 2_699289 +Node: igraph_rngtype_pcg32 --- The PCG random number generator [32-bit version]_700669 +Node: igraph_rngtype_pcg64 --- The PCG random number generator [64-bit version]_702098 +Node: Use cases703724 +Node: Normal [default] use704023 +Node: Reproducible simulations704803 +Node: Changing the default generator705487 +Node: Using multiple generators706164 +Node: Example707141 +Node: Vertex and edge selectors and sequences; iterators707301 +Node: About selectors; iterators707876 +Node: Vertex selector constructors710469 +Node: igraph_vs_all --- Vertex set; all vertices of a graph_712062 +Node: igraph_vs_adj --- Adjacent vertices of a vertex_712886 +Node: igraph_vs_nonadj --- Non-adjacent vertices of a vertex_715385 +Node: igraph_vs_none --- Empty vertex set_717403 +Node: igraph_vs_1 --- Vertex set with a single vertex_718181 +Node: igraph_vs_vector --- Vertex set based on a vector_719061 +Node: igraph_vs_vector_small --- Create a vertex set by giving its elements_720580 +Node: igraph_vs_vector_copy --- Vertex set based on a vector; with copying_721933 +Node: igraph_vs_range --- Vertex set; an interval of vertices_723303 +Node: Generic vertex selector operations724543 +Node: igraph_vs_copy --- Creates a copy of a vertex selector_725409 +Node: igraph_vs_destroy --- Destroy a vertex set_725975 +Node: igraph_vs_is_all --- Check whether all vertices are included_726817 +Node: igraph_vs_size --- Returns the size of the vertex selector_727930 +Node: igraph_vs_type --- Returns the type of the vertex selector_728777 +Node: Immediate vertex selectors729174 +Node: igraph_vss_all --- All vertices of a graph [immediate version]_730108 +Node: igraph_vss_none --- Empty vertex set [immediate version]_730902 +Node: igraph_vss_1 --- Vertex set with a single vertex [immediate version]_731588 +Node: igraph_vss_vector --- Vertex set based on a vector [immediate version]_732414 +Node: igraph_vss_range --- An interval of vertices [immediate version]_733377 +Node: Vertex iterators734307 +Node: igraph_vit_create --- Creates a vertex iterator from a vertex selector_735221 +Node: igraph_vit_destroy --- Destroys a vertex iterator_737668 +Node: Stepping over the vertices738412 +Node: IGRAPH_VIT_NEXT --- Next vertex_740084 +Node: IGRAPH_VIT_END --- Are we at the end?740618 +Node: IGRAPH_VIT_SIZE --- Size of a vertex iterator_741173 +Node: IGRAPH_VIT_RESET --- Reset a vertex iterator_741713 +Node: IGRAPH_VIT_GET --- Query the current position_742252 +Node: Edge selector constructors742777 +Node: igraph_es_all --- Edge set; all edges_744461 +Node: igraph_es_incident --- Edges incident on a given vertex_745884 +Node: igraph_es_none --- Empty edge selector_747313 +Node: igraph_es_1 --- Edge selector containing a single edge_748085 +Node: igraph_es_all_between --- Edge selector; all edge IDs between a pair of vertices_748978 +Node: igraph_es_vector --- Handle a vector as an edge selector_750210 +Node: igraph_es_range --- Edge selector; a sequence of edge IDs_751407 +Node: igraph_es_pairs --- Edge selector; multiple edges defined by their endpoints in a vector_752691 +Node: igraph_es_pairs_small --- Edge selector; multiple edges defined by their endpoints as arguments_754346 +Node: igraph_es_path --- Edge selector; edge IDs on a path_756328 +Node: igraph_es_vector_copy --- Edge set; based on a vector; with copying_757663 +Node: Immediate edge selectors758947 +Node: igraph_ess_all --- Edge set; all edges [immediate version]_759841 +Node: igraph_ess_none --- Immediate empty edge selector_760666 +Node: igraph_ess_1 --- Immediate version of the single edge edge selector_761333 +Node: igraph_ess_vector --- Immediate vector view edge selector_762050 +Node: igraph_ess_range --- Immediate version of the sequence edge selector_762876 +Node: Generic edge selector operations763695 +Node: igraph_es_as_vector --- Transform edge selector into vector_764698 +Node: igraph_es_copy --- Creates a copy of an edge selector_765698 +Node: igraph_es_destroy --- Destroys an edge selector object_766469 +Node: igraph_es_is_all --- Check whether an edge selector includes all edges_767238 +Node: igraph_es_size --- Returns the size of the edge selector_768110 +Node: igraph_es_type --- Returns the type of the edge selector_768950 +Node: Edge iterators769337 +Node: igraph_eit_create --- Creates an edge iterator from an edge selector_770205 +Node: igraph_eit_destroy --- Destroys an edge iterator_771904 +Node: Stepping over the edges772570 +Node: IGRAPH_EIT_NEXT --- Next edge_773473 +Node: IGRAPH_EIT_END --- Are we at the end?773992 +Node: IGRAPH_EIT_SIZE --- Number of edges in the iterator_774541 +Node: IGRAPH_EIT_RESET --- Reset an edge iterator_775087 +Node: IGRAPH_EIT_GET --- Query an edge iterator_775618 +Node: Graph; vertex and edge attributes776112 +Node: The attribute handler interface777720 +Node: igraph_attribute_table_t --- Table of functions to perform operations on attributes_779423 +Node: igraph_set_attribute_table --- Attach an attribute table_792925 +Node: igraph_attribute_type_t --- The possible types of the attributes_794383 +Node: igraph_attribute_elemtype_t --- Types of objects to which attributes can be attached_796579 +Node: Attribute records797457 +Node: igraph_attribute_record_t --- An attribute record holding the name; type and values of an attribute_800420 +Node: igraph_attribute_record_init --- Initializes an attribute record with a given name and type_802015 +Node: igraph_attribute_record_init_copy --- Initializes an attribute record by copying another record_803002 +Node: igraph_attribute_record_size --- Returns the size of the value vector in an attribute record_804236 +Node: igraph_attribute_record_resize --- Resizes the value vector in an attribute record_805041 +Node: igraph_attribute_record_set_name --- Sets the attribute name in an attribute record_806186 +Node: igraph_attribute_record_set_type --- Sets the type of an attribute record_807004 +Node: igraph_attribute_record_set_default_numeric --- Sets the default value of the attribute to the given number_808104 +Node: igraph_attribute_record_set_default_string --- Sets the default value of the attribute to the given string_809200 +Node: igraph_attribute_record_set_default_boolean --- Sets the default value of the attribute to the given logical value_810428 +Node: igraph_attribute_record_destroy --- Destroys an attribute record_811546 +Node: Handling attribute combination lists812162 +Node: igraph_attribute_combination_init --- Initialize attribute combination list_814422 +Node: igraph_attribute_combination_add --- Add combination record to attribute combination list_815090 +Node: igraph_attribute_combination_remove --- Remove a record from an attribute combination list_817103 +Node: igraph_attribute_combination_destroy --- Destroy attribute combination list_818278 +Node: igraph_attribute_combination_type_t --- The possible types of attribute combinations_819046 +Node: igraph_attribute_combination --- Initialize attribute combination list and add records_821302 +Node: Accessing attributes from C822820 +Node: Query attributes824841 +Node: igraph_cattribute_list --- List all attributes_828768 +Node: igraph_cattribute_has_attr --- Checks whether a [graph; vertex or edge] attribute exists_830337 +Node: igraph_cattribute_GAN --- Query a numeric graph attribute_831436 +Node: GAN --- Query a numeric graph attribute_832406 +Node: igraph_cattribute_GAB --- Query a boolean graph attribute_833042 +Node: GAB --- Query a boolean graph attribute_833965 +Node: igraph_cattribute_GAS --- Query a string graph attribute_834600 +Node: GAS --- Query a string graph attribute_835588 +Node: igraph_cattribute_VAN --- Query a numeric vertex attribute_836220 +Node: VAN --- Query a numeric vertex attribute_837347 +Node: igraph_cattribute_VANV --- Query a numeric vertex attribute for many vertices_838049 +Node: VANV --- Query a numeric vertex attribute for all vertices_839017 +Node: igraph_cattribute_VAB --- Query a boolean vertex attribute_839860 +Node: VAB --- Query a boolean vertex attribute_841018 +Node: igraph_cattribute_VABV --- Query a boolean vertex attribute for many vertices_841720 +Node: VABV --- Query a boolean vertex attribute for all vertices_842701 +Node: igraph_cattribute_VAS --- Query a string vertex attribute_843551 +Node: VAS --- Query a string vertex attribute_844833 +Node: igraph_cattribute_VASV --- Query a string vertex attribute for many vertices_845529 +Node: VASV --- Query a string vertex attribute for all vertices_846563 +Node: igraph_cattribute_EAN --- Query a numeric edge attribute_847405 +Node: EAN --- Query a numeric edge attribute_848528 +Node: igraph_cattribute_EANV --- Query a numeric edge attribute for many edges_849213 +Node: EANV --- Query a numeric edge attribute for all edges_850153 +Node: igraph_cattribute_EAB --- Query a boolean edge attribute_850969 +Node: EAB --- Query a boolean edge attribute_852095 +Node: igraph_cattribute_EABV --- Query a boolean edge attribute for many edges_852780 +Node: EABV --- Query a boolean edge attribute for all edges_853733 +Node: igraph_cattribute_EAS --- Query a string edge attribute_854548 +Node: EAS --- Query a string edge attribute_855761 +Node: igraph_cattribute_EASV --- Query a string edge attribute for many edges_856440 +Node: EASV --- Query a string edge attribute for all edges_857447 +Node: Set attributes858198 +Node: igraph_cattribute_GAN_set --- Set a numeric graph attribute_861219 +Node: SETGAN --- Set a numeric graph attribute862048 +Node: igraph_cattribute_GAB_set --- Set a boolean graph attribute_862738 +Node: SETGAB --- Set a boolean graph attribute863616 +Node: igraph_cattribute_GAS_set --- Set a string graph attribute_864305 +Node: SETGAS --- Set a string graph attribute865196 +Node: igraph_cattribute_VAN_set --- Set a numeric vertex attribute_865882 +Node: SETVAN --- Set a numeric vertex attribute867018 +Node: igraph_cattribute_VAB_set --- Set a boolean vertex attribute_867763 +Node: SETVAB --- Set a boolean vertex attribute868901 +Node: igraph_cattribute_VAS_set --- Set a string vertex attribute_869647 +Node: SETVAS --- Set a string vertex attribute870832 +Node: igraph_cattribute_EAN_set --- Set a numeric edge attribute_871572 +Node: SETEAN --- Set a numeric edge attribute872692 +Node: igraph_cattribute_EAB_set --- Set a boolean edge attribute_873424 +Node: SETEAB --- Set a boolean edge attribute874543 +Node: igraph_cattribute_EAS_set --- Set a string edge attribute_875274 +Node: SETEAS --- Set a string edge attribute876438 +Node: igraph_cattribute_VAN_setv --- Set a numeric vertex attribute for all vertices_877185 +Node: SETVANV --- Set a numeric vertex attribute for all vertices878246 +Node: igraph_cattribute_VAB_setv --- Set a boolean vertex attribute for all vertices_879066 +Node: SETVABV --- Set a boolean vertex attribute for all vertices880161 +Node: igraph_cattribute_VAS_setv --- Set a string vertex attribute for all vertices_880980 +Node: SETVASV --- Set a string vertex attribute for all vertices882122 +Node: igraph_cattribute_EAN_setv --- Set a numeric edge attribute for all edges_882932 +Node: SETEANV --- Set a numeric edge attribute for all edges883982 +Node: igraph_cattribute_EAB_setv --- Set a boolean edge attribute for all edges_884729 +Node: SETEABV --- Set a boolean edge attribute for all edges885780 +Node: igraph_cattribute_EAS_setv --- Set a string edge attribute for all edges_886526 +Node: SETEASV --- Set a string edge attribute for all edges887632 +Node: Remove attributes888291 +Node: igraph_cattribute_remove_g --- Remove a graph attribute_889559 +Node: DELGA --- Remove a graph attribute_890152 +Node: igraph_cattribute_remove_v --- Remove a vertex attribute_890721 +Node: DELVA --- Remove a vertex attribute_891364 +Node: igraph_cattribute_remove_e --- Remove an edge attribute_891937 +Node: DELEA --- Remove an edge attribute_892574 +Node: igraph_cattribute_remove_all --- Remove all graph/vertex/edge attributes_893159 +Node: DELGAS --- Remove all graph attributes_894248 +Node: DELVAS --- Remove all vertex attributes_894785 +Node: DELEAS --- Remove all edge attributes_895289 +Node: DELALL --- Remove all attributes_895785 +Node: Custom attribute combination functions896279 +Node: Deterministic graph generators898332 +Node: About generators898837 +Node: Basic graph creation899349 +Node: igraph_create --- Creates a graph with the specified edges_899880 +Node: igraph_small --- Shorthand to create a small graph; giving the edges as arguments_901237 +Node: Graphs from adjacency matrices and adjacency lists903330 +Node: igraph_adjacency --- Creates a graph from an adjacency matrix_904491 +Node: igraph_weighted_adjacency --- Creates a graph from a weighted adjacency matrix_907922 +Node: igraph_sparse_adjacency --- Creates a graph from a sparse adjacency matrix_911413 +Node: igraph_sparse_weighted_adjacency --- Creates a graph from a weighted sparse adjacency matrix_912351 +Node: igraph_adjlist --- Creates a graph from an adjacency list_913377 +Node: Regular structures915418 +Node: igraph_star --- Creates a star graph; every vertex connects only to the center_917435 +Node: igraph_wheel --- Creates a wheel graph; a union of a star and a cycle graph_919580 +Node: igraph_hypercube --- The n-dimensional hypercube graph_922145 +Node: igraph_square_lattice --- Arbitrary dimensional square lattices_923353 +Node: igraph_triangular_lattice --- A triangular lattice with the given shape_926623 +Node: igraph_hexagonal_lattice --- A hexagonal lattice with the given shape_929766 +Node: igraph_ring --- Creates a cycle graph or a path graph_932924 +Node: igraph_path_graph --- A path graph P_n_934767 +Node: igraph_cycle_graph --- A cycle graph C_n_935814 +Node: igraph_lcf --- Creates a graph from LCF notation_937052 +Node: igraph_lcf_small --- Shorthand to create a graph from LCF notation; giving shifts as the arguments_938842 +Node: igraph_circulant --- Creates a circulant graph_940462 +Node: igraph_extended_chordal_ring --- Create an extended chordal ring_942285 +Node: Tree generators944671 +Node: igraph_kary_tree --- Creates a k-ary tree in which almost all vertices have k children_945762 +Node: igraph_symmetric_tree --- Creates a symmetric tree with the specified number of branches at each level_948905 +Node: igraph_regular_tree --- Creates a regular tree_951317 +Node: igraph_tree_from_parent_vector --- Constructs a tree or forest from a vector encoding the parent of each vertex_953676 +Node: igraph_from_prufer --- Generates a tree from a Prüfer sequence_957258 +Node: Graphs with given degrees958921 +Node: igraph_realize_degree_sequence --- Generates a graph with the given degree sequence_959590 +Node: igraph_realize_bipartite_degree_sequence --- Generates a bipartite graph with the given bidegree sequence_967197 +Node: Complete graphs969971 +Node: igraph_full --- Creates a full graph [complete graph]_970781 +Node: igraph_full_citation --- Creates a full citation graph [a complete directed acyclic graph]_972414 +Node: igraph_full_multipartite --- Creates a full multipartite graph_973762 +Node: igraph_turan --- Creates a Turán graph_975756 +Node: Pre-defined graphs977414 +Node: igraph_famous --- Create a famous graph by simply providing its name_977966 +Node: igraph_atlas --- Create a small graph from the Graph Atlas_985063 +Node: Other well-known graphs from graph theory986679 +Node: igraph_de_bruijn --- Generate a de Bruijn graph_987374 +Node: igraph_kautz --- Generate a Kautz graph_988905 +Node: igraph_generalized_petersen --- Creates a Generalized Petersen graph_990651 +Node: igraph_mycielski_graph --- The Mycielski graph of order k_992547 +Node: Stochastic graph generators ["games"]994331 +Node: The Erdős-Rényi and related models995204 +Node: igraph_erdos_renyi_game_gnm --- Generates a random [Erdős-Rényi] graph with a fixed number of edges_998756 +Node: igraph_erdos_renyi_game_gnp --- Generates a random [Erdős-Rényi] graph with fixed edge probabilities_1002115 +Node: igraph_iea_game --- Generates a random multigraph through independent edge assignment_1007271 +Node: igraph_sbm_game --- Sample from a stochastic block model_1010122 +Node: igraph_hsbm_game --- Hierarchical stochastic block model_1012665 +Node: igraph_hsbm_list_game --- Hierarchical stochastic block model; more general version_1014382 +Node: igraph_preference_game --- Generates a graph with vertex types and connection preferences_1016204 +Node: igraph_asymmetric_preference_game --- Generates a graph with asymmetric vertex types and connection preferences_1019909 +Node: igraph_correlated_game --- Generates a random graph correlated to an existing graph_1023013 +Node: igraph_correlated_pair_game --- Generates pairs of correlated random graphs_1025035 +Node: Preferential attachment and related models1026979 +Node: igraph_barabasi_game --- Generates a graph based on the Barabási-Albert model_1028566 +Node: igraph_barabasi_aging_game --- Preferential attachment with aging of vertices_1034159 +Node: igraph_recent_degree_game --- Stochastic graph generator based on the number of incident edges a node has gained recently_1037671 +Node: igraph_recent_degree_aging_game --- Preferential attachment based on the number of edges gained recently; with aging of vertices_1040276 +Node: igraph_lastcit_game --- Simulates a citation network; based on time passed since the last citation_1043641 +Node: Growing random graph models1046849 +Node: igraph_growing_random_game --- Generates a growing random graph_1048312 +Node: igraph_callaway_traits_game --- Simulates a growing network with vertex types_1049757 +Node: igraph_establishment_game --- Generates a graph with a simple growing model with vertex types_1052370 +Node: igraph_cited_type_game --- Simulates a citation based on vertex types_1054416 +Node: igraph_citing_cited_type_game --- Simulates a citation network based on vertex types_1056847 +Node: igraph_forest_fire_game --- Generates a network according to the forest fire game_1059584 +Node: Degree-constrained models1062556 +Node: igraph_degree_sequence_game --- Generates a random graph with a given degree sequence_1063987 +Node: igraph_k_regular_game --- Generates a random graph where each vertex has the same degree_1071393 +Node: igraph_rewire --- Randomly rewires a graph while preserving its degree sequence_1073601 +Node: igraph_chung_lu_game --- Samples graphs from the Chung-Lu model_1075867 +Node: igraph_static_fitness_game --- Non-growing random graph with edge probabilities proportional to node fitness scores_1083765 +Node: igraph_static_power_law_game --- Generates a non-growing random graph with expected power-law degree distributions_1088890 +Node: Edge rewiring models1093355 +Node: igraph_watts_strogatz_game --- The Watts-Strogatz small-world model_1094046 +Node: igraph_rewire_edges --- Rewires the edges of a graph with constant probability_1097133 +Node: igraph_rewire_directed_edges --- Rewires the chosen endpoint of directed edges_1098863 +Node: Other random graphs1101073 +Node: igraph_grg_game --- Generates a geometric random graph_1101996 +Node: igraph_dot_product_game --- Generates a random dot product graph_1103565 +Node: igraph_simple_interconnected_islands_game --- Generates a random graph made of several interconnected islands; each island being a random graph_1105666 +Node: igraph_tree_game --- Generates a random tree with the given number of nodes_1107576 +Node: Common types and constants1109495 +Node: igraph_edge_type_sw_t --- What types of non-simple edges to allow?1109774 +Node: Bipartite; i_e_ two-mode graphs1110737 +Node: Bipartite networks in igraph1111127 +Node: Create two-mode networks1112082 +Node: igraph_create_bipartite --- Create a bipartite graph_1113177 +Node: igraph_full_bipartite --- Creates a complete bipartite graph_1114734 +Node: igraph_bipartite_game_gnm --- Generate a random bipartite graph with a fixed number of edges_1117415 +Node: igraph_bipartite_game_gnp --- Generates a random bipartite graph with a fixed connection probability_1120831 +Node: igraph_bipartite_iea_game --- Generates a random bipartite multigraph through independent edge assignment_1124727 +Node: Bipartite adjacency matrices1128089 +Node: igraph_biadjacency --- Creates a bipartite graph from a bipartite adjacency matrix_1128893 +Node: igraph_weighted_biadjacency --- Creates a bipartite graph from a weighted bipartite adjacency matrix_1131444 +Node: igraph_get_biadjacency --- Converts a bipartite graph into a bipartite adjacency matrix_1133661 +Node: Project two-mode graphs1136202 +Node: igraph_bipartite_projection_size --- Calculate the number of vertices and edges in the bipartite projections_1136871 +Node: igraph_bipartite_projection --- Create one or both projections of a bipartite [two-mode] network_1139223 +Node: Other operations on bipartite graphs1142674 +Node: igraph_is_bipartite --- Check whether a graph is bipartite_1143033 +Node: Spatial graphs1144674 +Node: Metrics1144959 +Node: igraph_metric_t --- Metric functions for use with spatial computation_1145237 +Node: Spatial graph generators1145911 +Node: igraph_delaunay_graph --- Computes the Delaunay graph of a spatial point set_1147344 +Node: igraph_nearest_neighbor_graph --- Computes the nearest neighbor graph for a spatial point set_1149065 +Node: igraph_gabriel_graph --- The Gabriel graph of a point set_1151095 +Node: igraph_relative_neighborhood_graph --- The relative neighborhood graph of a point set_1153027 +Node: igraph_lune_beta_skeleton --- The lune based β-skeleton of a spatial point set_1155274 +Node: igraph_circle_beta_skeleton --- The circle based β-skeleton of a 2D spatial point set_1156972 +Node: igraph_beta_weighted_gabriel_graph --- A Gabriel graph; with edges weighted by the β value at which it disappears_1158547 +Node: Properties of spatial graphs1161420 +Node: igraph_spatial_edge_lengths --- Edge lengths based on spatial vertex coordinates_1161828 +Node: Non-graph related spatial processing1164228 +Node: igraph_convex_hull_2d --- Determines the convex hull of a given set of points in the 2D plane_1164645 +Node: Graph operators1166265 +Node: Union and intersection1166504 +Node: igraph_disjoint_union --- Creates the union of two disjoint graphs_1167578 +Node: igraph_disjoint_union_many --- The disjoint union of many graphs_1169591 +Node: igraph_join --- Creates the join of two disjoint graphs_1171484 +Node: igraph_union --- Calculates the union of two graphs_1173399 +Node: igraph_union_many --- Creates the union of many graphs_1175587 +Node: igraph_intersection --- Collect the common edges from two graphs_1178011 +Node: igraph_intersection_many --- The intersection of more than two graphs_1180567 +Node: Other set-like operators1183094 +Node: igraph_difference --- Calculates the difference of two graphs_1183688 +Node: igraph_complementer --- Creates the complementer of a graph_1185106 +Node: igraph_compose --- Calculates the composition of two graphs_1186550 +Node: Miscellaneous operators1188551 +Node: igraph_connect_neighborhood --- Connects each vertex to its neighborhood_1190742 +Node: igraph_contract_vertices --- Replace multiple vertices with a single one_1192958 +Node: igraph_graph_power --- The k-th power of a graph_1194971 +Node: igraph_product --- The graph product of two graphs; according to the chosen product type_1196973 +Node: igraph_rooted_product --- The rooted graph product of two graphs_1203186 +Node: igraph_induced_subgraph --- Creates a subgraph induced by the specified vertices_1206091 +Node: igraph_induced_subgraph_map --- Creates an induced subraph and returns the mapping from the original_1209719 +Node: igraph_induced_subgraph_edges --- The edges contained within an induced sugraph_1213343 +Node: igraph_linegraph --- Create the line graph of a graph_1214529 +Node: igraph_mycielskian --- Generate the Mycielskian of a graph with k iterations_1216336 +Node: igraph_simplify --- Removes loop and/or multiple edges from the graph_1219667 +Node: igraph_subgraph_from_edges --- Creates a subgraph with the specified edges and their endpoints_1221215 +Node: igraph_reverse_edges --- Reverses some edges of a directed graph_1223421 +Node: Graph visitors1224630 +Node: Breadth-first search1224865 +Node: igraph_bfs --- Breadth-first search_1225358 +Node: igraph_bfs_simple --- Breadth-first search; single-source version1230089 +Node: igraph_bfshandler_t --- Callback type for BFS function_1232696 +Node: Depth-first search1234990 +Node: igraph_dfs --- Depth-first search_1235369 +Node: igraph_dfshandler_t --- Callback type for the DFS function_1238926 +Node: Random walks1240686 +Node: igraph_random_walk --- Performs a random walk on a graph_1240947 +Node: Structural properties of graphs1243977 +Node: Basic properties1245138 +Node: igraph_are_adjacent --- Decides whether two vertices are adjacent_1245439 +Node: Sparsifiers1246516 +Node: igraph_spanner --- Calculates a spanner of a graph with a given stretch factor_1246875 +Node: [Shortest]-path related functions1249010 +Node: igraph_distances --- Length of the shortest paths between vertices_1254220 +Node: igraph_distances_cutoff --- Length of the shortest paths between vertices; with cutoff_1257077 +Node: igraph_distances_dijkstra --- Weighted shortest path lengths between vertices_1260337 +Node: igraph_distances_dijkstra_cutoff --- Weighted shortest path lengths between vertices; with cutoff_1263379 +Node: igraph_distances_bellman_ford --- Weighted shortest path lengths between vertices; allowing negative weights_1266696 +Node: igraph_distances_johnson --- Weighted shortest path lengths between vertices; using Johnson's algorithm_1269847 +Node: igraph_distances_floyd_warshall --- Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm_1273941 +Node: igraph_get_shortest_paths --- Shortest paths from a vertex_1278319 +Node: igraph_get_shortest_path --- Shortest path from one vertex to another one_1283154 +Node: igraph_get_shortest_paths_dijkstra --- Weighted shortest paths from a vertex_1285695 +Node: igraph_get_shortest_path_dijkstra --- Weighted shortest path from one vertex to another one [Dijkstra]_1290914 +Node: igraph_get_shortest_paths_bellman_ford --- Weighted shortest paths from a vertex; allowing negative weights_1293978 +Node: igraph_get_shortest_path_bellman_ford --- Weighted shortest path from one vertex to another one [Bellman-Ford]_1299172 +Node: igraph_get_shortest_path_astar --- A* gives the shortest path from one vertex to another; with heuristic_1302249 +Node: igraph_astar_heuristic_func_t --- Distance estimator for A* algorithm_1306027 +Node: igraph_get_all_shortest_paths --- All shortest paths [geodesics] from a vertex_1308634 +Node: igraph_get_all_shortest_paths_dijkstra --- All weighted shortest paths [geodesics] from a vertex_1312487 +Node: igraph_get_k_shortest_paths --- k shortest paths between two vertices_1316485 +Node: igraph_get_all_simple_paths --- List all simple paths from one source_1320124 +Node: igraph_average_path_length --- The average shortest path length between all vertex pairs_1322971 +Node: igraph_path_length_hist --- Create a histogram of all shortest path lengths_1325288 +Node: igraph_diameter --- Calculates the weighted diameter of a graph using Dijkstra's algorithm_1327222 +Node: igraph_girth --- The girth of a graph is the length of the shortest cycle in it_1330151 +Node: igraph_eccentricity --- Eccentricity of some vertices_1332146 +Node: igraph_radius --- Radius of a graph; using weighted edges_1334122 +Node: igraph_graph_center --- Central vertices of a graph_1336262 +Node: igraph_pseudo_diameter --- Approximation and lower bound of the diameter of a graph_1338645 +Node: igraph_voronoi --- Voronoi partitioning of a graph_1341322 +Node: igraph_vertex_path_from_edge_path --- Converts a walk of edge IDs to the traversed vertex IDs_1344808 +Node: Widest-path related functions1347340 +Node: igraph_get_widest_path --- Widest path from one vertex to another one_1348205 +Node: igraph_get_widest_paths --- Widest paths from a single vertex_1350798 +Node: igraph_widest_path_widths_dijkstra --- Widths of widest paths between vertices_1355679 +Node: igraph_widest_path_widths_floyd_warshall --- Widths of widest paths between vertices_1358534 +Node: Efficiency measures1361614 +Node: igraph_global_efficiency --- Calculates the global efficiency of a network_1362365 +Node: igraph_local_efficiency --- Calculates the local efficiency around each vertex in a network_1364969 +Node: igraph_average_local_efficiency --- Calculates the average local efficiency in a network_1368269 +Node: Neighborhood of a vertex1370246 +Node: igraph_neighborhood_size --- Calculates the size of the neighborhood of a given vertex_1370975 +Node: igraph_neighborhood --- Calculate the neighborhood of vertices_1373792 +Node: igraph_neighborhood_graphs --- Create graphs from the neighborhood[s] of some vertex/vertices_1376841 +Node: Local scan statistics1379918 +Node: "Us" statistics1380495 +Node: igraph_local_scan_0 --- Local scan-statistics; k=01381155 +Node: igraph_local_scan_1_ecount --- Local scan-statistics; k=1; edge count and sum of weights1382439 +Node: igraph_local_scan_k_ecount --- Sum the number of edges or the weights in k-neighborhood of every vertex_1383681 +Node: "Them" statistics1385053 +Node: igraph_local_scan_0_them --- Local THEM scan-statistics; k=01385766 +Node: igraph_local_scan_1_ecount_them --- Local THEM scan-statistics; k=1; edge count and sum of weights1387273 +Node: igraph_local_scan_k_ecount_them --- Local THEM scan-statistics; edge count or sum of weights_1388847 +Node: Pre-calculated subsets1390639 +Node: igraph_local_scan_neighborhood_ecount --- Local scan-statistics with pre-calculated neighborhoods1391132 +Node: igraph_local_scan_subset_ecount --- Local scan-statistics of subgraphs induced by subsets of vertices_1392634 +Node: Graph components1393826 +Node: igraph_subcomponent --- The vertices reachable from a given vertex_1395727 +Node: igraph_connected_components --- Calculates the [weakly or strongly] connected components in a graph_1398074 +Node: igraph_is_connected --- Decides whether the graph is [weakly or strongly] connected_1400338 +Node: igraph_decompose --- Decomposes a graph into connected components_1402810 +Node: igraph_reachability --- Calculates which vertices are reachable from each vertex in the graph_1404901 +Node: igraph_count_reachable --- The number of vertices reachable from each vertex in the graph_1408228 +Node: igraph_transitive_closure --- Computes the transitive closure of a graph_1410305 +Node: igraph_biconnected_components --- Calculates biconnected components_1411704 +Node: igraph_articulation_points --- Finds the articulation points in a graph_1415596 +Node: igraph_bridges --- Finds all bridges in a graph_1417378 +Node: igraph_is_biconnected --- Checks whether a graph is biconnected_1418715 +Node: Percolation1420062 +Node: igraph_site_percolation --- The size of the largest component as vertices are added to a graph_1420822 +Node: igraph_bond_percolation --- The size of the largest component as edges are added to a graph_1423249 +Node: igraph_edgelist_percolation --- The size of the largest component as vertex pairs are connected_1426015 +Node: Degree sequences1428029 +Node: igraph_is_graphical --- Is there a graph with the given degree sequence?1428392 +Node: igraph_is_bigraphical --- Is there a bipartite graph with the given bi-degree-sequence?1433138 +Node: Centrality measures1435521 +Node: igraph_closeness --- Closeness centrality calculations for some vertices_1437933 +Node: igraph_harmonic_centrality --- Harmonic centrality for some vertices_1442860 +Node: igraph_betweenness --- Betweenness centrality of some vertices_1446369 +Node: igraph_edge_betweenness --- Betweenness centrality of the edges_1449494 +Node: igraph_pagerank_algo_t --- PageRank algorithm implementation_1452422 +Node: igraph_pagerank --- Calculates the Google PageRank for the specified vertices_1453305 +Node: igraph_personalized_pagerank --- Calculates the personalized Google PageRank for the specified vertices_1458844 +Node: igraph_personalized_pagerank_vs --- Calculates the personalized Google PageRank for the specified vertices_1463597 +Node: igraph_constraint --- Burt's constraint scores_1468347 +Node: igraph_maxdegree --- The maximum degree in a graph [or set of vertices]_1470625 +Node: igraph_strength --- Strength of the vertices; also called weighted vertex degree_1472723 +Node: igraph_eigenvector_centrality --- Eigenvector centrality of the vertices_1474968 +Node: igraph_hub_and_authority_scores --- Kleinberg's hub and authority scores [HITS]_1481451 +Node: igraph_convergence_degree --- Calculates the convergence degree of each edge in a graph_1486472 +Node: Range-limited centrality measures1489105 +Node: igraph_closeness_cutoff --- Range limited closeness centrality_1489967 +Node: igraph_harmonic_centrality_cutoff --- Range limited harmonic centrality_1493525 +Node: igraph_betweenness_cutoff --- Range-limited betweenness centrality_1497158 +Node: igraph_edge_betweenness_cutoff --- Range-limited betweenness centrality of the edges_1500124 +Node: Subset-limited centrality measures1502759 +Node: igraph_betweenness_subset --- Betweenness centrality for a subset of source and target vertices_1503438 +Node: igraph_edge_betweenness_subset --- Edge betweenness centrality for a subset of source and target vertices_1506174 +Node: Centralization1509011 +Node: igraph_centralization --- Calculate the centralization score from the node level scores_1511053 +Node: igraph_centralization_degree --- Calculate vertex degree and graph centralization_1514192 +Node: igraph_centralization_betweenness --- Calculate vertex betweenness and graph centralization_1517174 +Node: igraph_centralization_closeness --- Calculate vertex closeness and graph centralization_1519953 +Node: igraph_centralization_eigenvector_centrality --- Calculate eigenvector centrality scores and graph centralization_1523045 +Node: igraph_centralization_degree_tmax --- Theoretical maximum for graph centralization based on degree_1527174 +Node: igraph_centralization_betweenness_tmax --- Theoretical maximum for graph centralization based on betweenness_1529987 +Node: igraph_centralization_closeness_tmax --- Theoretical maximum for graph centralization based on closeness_1532306 +Node: igraph_centralization_eigenvector_centrality_tmax --- Theoretical maximum centralization for eigenvector centrality_1534783 +Node: Similarity measures1537333 +Node: igraph_bibcoupling --- Bibliographic coupling_1538952 +Node: igraph_cocitation --- Cocitation coupling_1540453 +Node: igraph_similarity_jaccard --- Jaccard similarity coefficient for the given vertices_1542046 +Node: igraph_similarity_jaccard_pairs --- Jaccard similarity coefficient for given vertex pairs_1544674 +Node: igraph_similarity_jaccard_es --- Jaccard similarity coefficient for a given edge selector_1547734 +Node: igraph_similarity_dice --- Dice similarity coefficient_1550880 +Node: igraph_similarity_dice_pairs --- Dice similarity coefficient for given vertex pairs_1553482 +Node: igraph_similarity_dice_es --- Dice similarity coefficient for a given edge selector_1556575 +Node: igraph_similarity_inverse_log_weighted --- Vertex similarity based on the inverse logarithm of vertex degrees_1559693 +Node: Trees and forests1563087 +Node: igraph_minimum_spanning_tree --- Calculates a minimum spanning tree of a graph_1564004 +Node: igraph_random_spanning_tree --- Uniformly samples the spanning trees of a graph_1567071 +Node: igraph_is_tree --- Decides whether the graph is a tree_1569178 +Node: igraph_is_forest --- Decides whether the graph is a forest_1571667 +Node: igraph_to_prufer --- Converts a tree to its Prüfer sequence_1574418 +Node: Transitivity or clustering coefficient1575687 +Node: igraph_transitivity_undirected --- Calculates the transitivity [clustering coefficient] of a graph_1576900 +Node: igraph_transitivity_local_undirected --- The local transitivity [clustering coefficient] of some vertices_1579432 +Node: igraph_transitivity_avglocal_undirected --- Average local transitivity [clustering coefficient]_1582006 +Node: igraph_transitivity_barrat --- Weighted local transitivity of some vertices; as defined by A_ Barrat_1584817 +Node: igraph_ecc --- Edge clustering coefficient of some edges_1588066 +Node: Directedness conversion1590792 +Node: igraph_to_directed --- Convert an undirected graph to a directed one_1591324 +Node: igraph_to_undirected --- Convert a directed graph to an undirected one_1592889 +Node: Spectral properties1594842 +Node: igraph_get_laplacian --- Returns the Laplacian matrix of a graph_1595571 +Node: igraph_get_laplacian_sparse --- Returns the Laplacian of a graph in a sparse matrix format_1598305 +Node: igraph_laplacian_normalization_t --- Normalization methods for a Laplacian matrix_1600255 +Node: Non-simple graphs; Multiple and loop edges1601835 +Node: igraph_is_simple --- Decides whether the input graph is a simple graph_1603235 +Node: igraph_is_loop --- Find the loop edges in a graph_1604938 +Node: igraph_has_loop --- Returns whether the graph has at least one loop edge_1606220 +Node: igraph_count_loops --- Counts the self-loops in the graph_1607443 +Node: igraph_is_multiple --- Find the multiple edges in a graph_1608325 +Node: igraph_has_multiple --- Check whether the graph has at least one multiple edge_1610241 +Node: igraph_count_multiple --- The multiplicity of some edges in a graph_1611870 +Node: igraph_count_multiple_1 --- The multiplicity of a single edge in a graph_1613817 +Node: Mixing patterns and degree correlations1615238 +Node: igraph_assortativity_nominal --- Assortativity of a graph based on vertex categories_1616945 +Node: igraph_assortativity --- Assortativity based on numeric properties of vertices_1620929 +Node: igraph_assortativity_degree --- Assortativity of a graph based on vertex degree_1625726 +Node: igraph_avg_nearest_neighbor_degree --- Average neighbor degree_1628212 +Node: igraph_degree_correlation_vector --- Degree correlation function_1632325 +Node: igraph_joint_type_distribution --- Mixing matrix for vertex categories_1636921 +Node: igraph_joint_degree_distribution --- The joint degree distribution of a graph_1641210 +Node: igraph_joint_degree_matrix --- The joint degree matrix of a graph_1647005 +Node: igraph_rich_club_sequence --- Density sequence of subgraphs formed by sequential vertex removal_1650461 +Node: K-cores and k-trusses1653750 +Node: igraph_coreness --- The coreness of the vertices in a graph_1654285 +Node: igraph_trussness --- Finding the "trussness" of the edges in a network_1655861 +Node: Maximum cardinality search and chordal graphs1657823 +Node: igraph_maximum_cardinality_search --- Maximum cardinality search_1658358 +Node: igraph_is_chordal --- Decides whether a graph is chordal_1660617 +Node: Matchings1663443 +Node: igraph_is_matching --- Checks whether the given matching is valid for the given graph_1664183 +Node: igraph_is_maximal_matching --- Checks whether a matching in a graph is maximal_1666213 +Node: igraph_maximum_bipartite_matching --- Calculates a maximum matching in a bipartite graph_1668122 +Node: Unfolding a graph into a tree1671868 +Node: igraph_unfold_tree --- Unfolding a graph into a tree; by possibly multiplicating its vertices_1672289 +Node: Other operations1673959 +Node: igraph_density --- Calculate the density of a graph_1675709 +Node: igraph_mean_degree --- The mean degree of a graph_1677847 +Node: igraph_reciprocity --- Calculates the reciprocity of a directed graph_1678993 +Node: igraph_diversity --- Structural diversity index of the vertices_1681548 +Node: igraph_is_mutual --- Check whether some edges of a directed graph are mutual_1683659 +Node: igraph_has_mutual --- Check whether a directed graph has any mutual edges_1685363 +Node: igraph_get_adjacency --- The adjacency matrix of a graph_1686793 +Node: igraph_get_adjacency_sparse --- Returns the adjacency matrix of a graph in a sparse matrix format_1689787 +Node: igraph_get_stochastic --- Stochastic adjacency matrix of a graph_1692557 +Node: igraph_get_stochastic_sparse --- The stochastic adjacency matrix of a graph_1694639 +Node: igraph_get_edgelist --- The list of edges in a graph_1696262 +Node: Common types and constants <1>1697539 +Node: igraph_loops_t --- How to interpret self-loops in undirected graphs?1697895 +Node: igraph_neimode_t --- How to interpret edge directions in directed graphs?1699360 +Node: Graph cycles1700498 +Node: Finding cycles1700784 +Node: igraph_find_cycle --- Finds a single cycle in the graph_1701446 +Node: igraph_simple_cycles --- Finds all simple cycles_1703296 +Node: igraph_simple_cycles_callback --- Finds all simple cycles [callback version]_1706346 +Node: igraph_cycle_handler_t --- Type of cycle handler functions_1709153 +Node: Acyclic graphs and feedback sets1710358 +Node: igraph_is_dag --- Checks whether a graph is a directed acyclic graph [DAG]_1711361 +Node: igraph_is_acyclic --- Checks whether a graph is acyclic or not_1712663 +Node: igraph_topological_sorting --- Calculate a possible topological sorting of the graph_1714363 +Node: igraph_feedback_arc_set --- Feedback arc set of a graph using exact or heuristic methods_1716604 +Node: igraph_feedback_vertex_set --- Feedback vertex set of a graph_1721426 +Node: Eulerian cycles and paths1723037 +Node: igraph_is_eulerian --- Checks whether an Eulerian path or cycle exists_1723697 +Node: igraph_eulerian_cycle --- Finds an Eulerian cycle_1724785 +Node: igraph_eulerian_path --- Finds an Eulerian path_1726303 +Node: Cycle bases1727644 +Node: igraph_fundamental_cycles --- Finds a fundamental cycle basis_1728060 +Node: igraph_minimum_cycle_basis --- Computes a minimum weight cycle basis_1730058 +Node: Cliques and independent vertex sets1732942 +Node: Cliques1733344 +Node: igraph_is_complete --- Decides whether the graph is complete_1735518 +Node: igraph_is_clique --- Does a set of vertices form a clique?1736304 +Node: igraph_cliques --- Finds all or some cliques in a graph_1738136 +Node: igraph_clique_size_hist --- Counts cliques of each size in the graph_1740252 +Node: igraph_cliques_callback --- Calls a function for each clique in the graph_1742079 +Node: igraph_clique_handler_t --- Type of clique handler functions_1744398 +Node: igraph_largest_cliques --- Finds the largest clique[s] in a graph_1745810 +Node: igraph_maximal_cliques --- Finds all maximal cliques in a graph_1747590 +Node: igraph_maximal_cliques_count --- Count the number of maximal cliques in a graph_1750605 +Node: igraph_maximal_cliques_file --- Find maximal cliques and write them to a file_1752218 +Node: igraph_maximal_cliques_subset --- Maximal cliques for a subset of initial vertices_1754092 +Node: igraph_maximal_cliques_hist --- Counts the number of maximal cliques of each size in a graph_1756443 +Node: igraph_maximal_cliques_callback --- Finds maximal cliques in a graph and calls a function for each one_1758429 +Node: igraph_clique_number --- Finds the clique number of the graph_1760821 +Node: Weighted cliques1762141 +Node: igraph_weighted_cliques --- Finds all cliques in a given weight range in a vertex weighted graph_1762896 +Node: igraph_largest_weighted_cliques --- Finds the largest weight clique[s] in a graph_1765371 +Node: igraph_weighted_clique_number --- Finds the weight of the largest weight clique in the graph_1767431 +Node: Independent vertex sets1769341 +Node: igraph_is_independent_vertex_set --- Does a set of vertices form an independent set?1770340 +Node: igraph_independent_vertex_sets --- Finds all independent vertex sets in a graph_1772137 +Node: igraph_largest_independent_vertex_sets --- Finds the largest independent vertex set[s] in a graph_1774623 +Node: igraph_maximal_independent_vertex_sets --- Finds all maximal independent vertex sets of a graph_1776327 +Node: igraph_independence_number --- Finds the independence number of the graph_1778899 +Node: Graph motifs; dyad census and triad census1780201 +Node: igraph_dyad_census --- Dyad census; as defined by Holland and Leinhardt_1781012 +Node: igraph_triad_census --- Triad census; as defined by Davis and Leinhardt_1782844 +Node: Finding triangles1786102 +Node: igraph_count_adjacent_triangles --- Count the number of triangles a vertex is part of_1786771 +Node: igraph_count_triangles --- Counts triangles in a graph_1788036 +Node: igraph_list_triangles --- Find all triangles in a graph_1789538 +Node: Graph motifs1791423 +Node: igraph_motifs_randesu --- Count the number of motifs in a graph_1792403 +Node: igraph_motifs_randesu_no --- Count the total number of motifs in a graph_1796467 +Node: igraph_motifs_randesu_estimate --- Estimate the total number of motifs in a graph_1798331 +Node: igraph_motifs_randesu_callback --- Finds motifs in a graph and calls a function for each of them_1801359 +Node: igraph_motifs_handler_t --- Callback type for igraph_motifs_randesu_callback_1803854 +Node: Graph isomorphism1806427 +Node: The simple interface1806762 +Node: igraph_isomorphic --- Are two graphs isomorphic?1808919 +Node: igraph_subisomorphic --- Decide subgraph isomorphism_1811721 +Node: igraph_count_automorphisms --- Number of automorphisms of a graph_1812991 +Node: igraph_automorphism_group --- Automorphism group generators of a graph_1814696 +Node: igraph_canonical_permutation --- Canonical permutation of a graph_1816424 +Node: The BLISS algorithm1818294 +Node: igraph_bliss_sh_t --- Splitting heuristics for Bliss_1820429 +Node: igraph_bliss_info_t --- Information about a Bliss run_1821662 +Node: igraph_isomorphic_bliss --- Graph isomorphism via Bliss_1823277 +Node: igraph_count_automorphisms_bliss --- Number of automorphisms using Bliss_1826511 +Node: igraph_automorphism_group_bliss --- Automorphism group generators using Bliss_1828351 +Node: igraph_canonical_permutation_bliss --- Canonical permutation using Bliss_1830281 +Node: The VF2 algorithm1832454 +Node: igraph_isomorphic_vf2 --- Isomorphism via VF2_1834661 +Node: igraph_count_isomorphisms_vf2 --- Number of isomorphisms via VF2_1838880 +Node: igraph_get_isomorphisms_vf2 --- Collect all isomorphic mappings of two graphs_1842095 +Node: igraph_get_isomorphisms_vf2_callback --- The generic VF2 interface1845652 +Node: igraph_isohandler_t --- Callback type; called when an isomorphism was found1849898 +Node: igraph_isocompat_t --- Callback type; called to check whether two vertices or edges are compatible1851382 +Node: igraph_subisomorphic_vf2 --- Decide subgraph isomorphism using VF21853186 +Node: igraph_count_subisomorphisms_vf2 --- Number of subgraph isomorphisms using VF21856852 +Node: igraph_get_subisomorphisms_vf2 --- Return all subgraph isomorphic mappings_1860216 +Node: igraph_get_subisomorphisms_vf2_callback --- Generic VF2 function for subgraph isomorphism problems_1863846 +Node: The LAD algorithm1867902 +Node: igraph_subisomorphic_lad --- Check subgraph isomorphism with the LAD algorithm1869020 +Node: Functions for small graphs1871851 +Node: igraph_isoclass --- Determine the isomorphism class of small graphs_1872697 +Node: igraph_isoclass_subgraph --- The isomorphism class of a subgraph of a graph_1875188 +Node: igraph_isoclass_create --- Creates a graph from the given isomorphism class_1876850 +Node: igraph_graph_count --- The number of unlabelled graphs on the given number of vertices_1878822 +Node: Utility functions1880353 +Node: igraph_invert_permutation --- Inverts a permutation_1880934 +Node: igraph_permute_vertices --- Permute the vertices_1881795 +Node: igraph_simplify_and_colorize --- Simplify the graph and compute self-loop and edge multiplicities_1883142 +Node: Graph coloring1885011 +Node: igraph_vertex_coloring_greedy --- Computes a vertex coloring using a greedy algorithm_1886110 +Node: igraph_coloring_greedy_t --- Ordering heuristics for greedy graph coloring_1887798 +Node: igraph_is_vertex_coloring --- Checks whether a vertex coloring is valid_1889280 +Node: igraph_is_bipartite_coloring --- Checks whether a bipartite vertex coloring is valid_1890413 +Node: igraph_is_edge_coloring --- Checks whether an edge coloring is valid_1892133 +Node: igraph_is_perfect --- Checks if the graph is perfect_1893333 +Node: Maximum flows; minimum cuts and related measures1894762 +Node: Maximum flows1895168 +Node: igraph_maxflow --- Maximum network flow between a pair of vertices_1896018 +Node: igraph_maxflow_value --- Maximum flow in a network with the push/relabel algorithm_1900824 +Node: igraph_dominator_tree --- Calculates the dominator tree of a flowgraph_1904399 +Node: igraph_maxflow_stats_t --- Data structure holding statistics from the push-relabel maximum flow solver_1907928 +Node: Cuts and minimum cuts1909034 +Node: igraph_st_mincut --- Minimum cut between a source and a target vertex_1910088 +Node: igraph_st_mincut_value --- The minimum s-t cut in a graph_1912584 +Node: igraph_all_st_cuts --- List all edge-cuts between two vertices in a directed graph1914577 +Node: igraph_all_st_mincuts --- All minimum s-t cuts of a directed graph_1916546 +Node: igraph_mincut --- Calculates the minimum cut in a graph_1919308 +Node: igraph_mincut_value --- The minimum edge cut in a graph_1922243 +Node: igraph_gomory_hu_tree --- Gomory-Hu tree of a graph_1924639 +Node: Connectivity1926869 +Node: igraph_st_edge_connectivity --- Edge connectivity of a pair of vertices_1927684 +Node: igraph_edge_connectivity --- The minimum edge connectivity in a graph_1929643 +Node: igraph_st_vertex_connectivity --- The vertex connectivity of a pair of vertices_1932058 +Node: igraph_vertex_connectivity --- The vertex connectivity of a graph_1934643 +Node: Edge- and vertex-disjoint paths1936807 +Node: igraph_edge_disjoint_paths --- The maximum number of edge-disjoint paths between two vertices_1937456 +Node: igraph_vertex_disjoint_paths --- Maximum number of vertex-disjoint paths between two vertices_1939524 +Node: Graph adhesion and cohesion1941578 +Node: igraph_adhesion --- Graph adhesion; this is [almost] the same as edge connectivity_1942164 +Node: igraph_cohesion --- Graph cohesion; this is the same as vertex connectivity_1944490 +Node: Cohesive blocks1946620 +Node: igraph_cohesive_blocks --- Identifies the hierarchical cohesive block structure of a graph_1947001 +Node: Vertex separators1950001 +Node: igraph_is_separator --- Would removing this set of vertices disconnect the graph?1951005 +Node: igraph_is_minimal_separator --- Decides whether a set of vertices is a minimal separator_1952198 +Node: igraph_all_minimal_st_separators --- List all vertex sets that are minimal [s;t] separators for some s and t_1953478 +Node: igraph_minimum_size_separators --- Find all minimum size separating vertex sets_1955775 +Node: igraph_even_tarjan_reduction --- Even-Tarjan reduction of a graph_1957650 +Node: Detecting community structure1959563 +Node: Common functions related to community structure1960808 +Node: igraph_modularity --- Calculates the modularity of a graph with respect to some clusters or vertex types_1962363 +Node: igraph_modularity_matrix --- Calculates the modularity matrix_1967432 +Node: igraph_community_optimal_modularity --- Calculate the community structure with the highest modularity value_1969923 +Node: igraph_community_to_membership --- Cut a dendrogram after a given number of merges_1973270 +Node: igraph_reindex_membership --- Makes the IDs in a membership vector contiguous_1977519 +Node: igraph_compare_communities --- Compares community structures using various metrics_1979195 +Node: igraph_split_join_distance --- Calculates the split-join distance of two community structures_1985561 +Node: Community structure based on statistical mechanics1988408 +Node: igraph_community_spinglass --- Community detection based on statistical mechanics_1989136 +Node: igraph_community_spinglass_single --- Community of a single node based on statistical mechanics_1995187 +Node: Community structure based on eigenvectors of matrices1999499 +Node: igraph_community_leading_eigenvector --- Leading eigenvector community finding [proper version]_2002467 +Node: igraph_community_leading_eigenvector_callback_t --- Callback for the leading eigenvector community finding method_2009632 +Node: igraph_le_community_to_membership --- Cut an incomplete dendrogram after a given number of merges; starting with an initial cluster assignment_2012386 +Node: Walktrap; Community structure based on random walks2015884 +Node: igraph_community_walktrap --- Community finding using a random walk based similarity measure_2016435 +Node: Edge betweenness based community detection2019995 +Node: igraph_community_edge_betweenness --- Community finding based on edge betweenness_2020763 +Node: igraph_community_eb_get_merges --- Calculating the merges; i_e_ the dendrogram for an edge betweenness community structure_2027485 +Node: Community structure based on the optimization of modularity2031826 +Node: igraph_community_fastgreedy --- Finding community structure by greedy optimization of modularity_2032994 +Node: igraph_community_multilevel --- Finding community structure by multi-level optimization of modularity [Louvain]_2036319 +Node: igraph_community_leiden --- Finding community structure using the Leiden algorithm_2040414 +Node: igraph_community_leiden_simple --- Finding community structure using the Leiden algorithm; simple interface_2047937 +Node: Fluid communities2053358 +Node: igraph_community_fluid_communities --- Community detection based on fluids interacting on the graph_2053802 +Node: Label propagation2055535 +Node: igraph_community_label_propagation --- Community detection based on label propagation_2055913 +Node: The InfoMAP algorithm2063436 +Node: igraph_community_infomap --- Community structure that minimizes the expected description length of a random walker trajectory_2063904 +Node: Voronoi communities2069128 +Node: igraph_community_voronoi --- Finds communities using Voronoi partitioning_2069464 +Node: Graphlets2073017 +Node: Introduction <1>2073253 +Node: Performing graphlet decomposition2075260 +Node: igraph_graphlets --- Calculate graphlets basis and project the graph on it_2075829 +Node: igraph_graphlets_candidate_basis --- Calculate a candidate graphlets basis2077656 +Node: igraph_graphlets_project --- Project a graph on a graphlets basis_2079226 +Node: Hierarchical random graphs2081281 +Node: Introduction <2>2081654 +Node: Representing HRGs2083854 +Node: igraph_hrg_t --- Data structure to store a hierarchical random graph_2084619 +Node: igraph_hrg_init --- Allocate memory for a HRG_2086522 +Node: igraph_hrg_destroy --- Deallocate memory for an HRG_2087440 +Node: igraph_hrg_size --- Returns the size of the HRG; the number of leaf nodes_2088150 +Node: igraph_hrg_resize --- Resize a HRG_2088765 +Node: Fitting HRGs2089423 +Node: igraph_hrg_fit --- Fit a hierarchical random graph model to a network_2089871 +Node: igraph_hrg_consensus --- Calculate a consensus tree for a HRG_2091140 +Node: HRG sampling2092976 +Node: igraph_hrg_sample --- Sample from a hierarchical random graph model_2093429 +Node: igraph_hrg_game --- Generate a hierarchical random graph_2094148 +Node: Conversion to and from igraph graphs2095007 +Node: igraph_from_hrg_dendrogram --- Create a graph representation of the dendrogram of a hierarchical random graph model_2095614 +Node: igraph_hrg_create --- Create a HRG from an igraph graph_2097116 +Node: Predicting missing edges2098278 +Node: igraph_hrg_predict --- Predict missing edges in a graph; based on HRG models_2098674 +Node: Deprecated functions2100263 +Node: igraph_hrg_dendrogram --- Create a dendrogram from a hierarchical random graph_2100610 +Node: Embedding of graphs2101730 +Node: Spectral embedding2101954 +Node: igraph_adjacency_spectral_embedding --- Adjacency spectral embedding2102355 +Node: igraph_laplacian_spectral_embedding --- Spectral embedding of the Laplacian of a graph2106415 +Node: igraph_dim_select --- Dimensionality selection_2110353 +Node: Generating layouts for graph drawing2112004 +Node: 2D layout generators2112351 +Node: igraph_layout_random --- Places the vertices uniformly randomly within a square_2114845 +Node: igraph_layout_circle --- Places the vertices uniformly on a circle in arbitrary order_2115759 +Node: igraph_layout_star --- Generates a star-like layout_2116976 +Node: igraph_layout_grid --- Places the vertices on a regular grid on the plane_2118515 +Node: igraph_layout_graphopt --- Optimizes vertex layout via the graphopt algorithm_2119630 +Node: igraph_layout_bipartite --- Simple layout for bipartite graphs_2123265 +Node: The DrL layout generator2125146 +Node: igraph_layout_drl_options_t --- Parameters for the DrL layout generator2127020 +Node: igraph_layout_drl_default_t --- Predefined parameter templates for the DrL layout generator2130753 +Node: igraph_layout_drl_options_init --- Initialize parameters for the DrL layout generator2132086 +Node: igraph_layout_drl --- The DrL layout generator2133393 +Node: igraph_layout_drl_3d --- The DrL layout generator; 3d version_2134865 +Node: igraph_layout_fruchterman_reingold --- Places the vertices on a plane according to the Fruchterman-Reingold algorithm_2136515 +Node: igraph_layout_kamada_kawai --- Places the vertices on a plane according to the Kamada-Kawai algorithm_2140736 +Node: igraph_layout_gem --- Layout graph according to GEM algorithm_2144981 +Node: igraph_layout_davidson_harel --- Davidson-Harel layout algorithm_2147302 +Node: igraph_layout_mds --- Place the vertices on a plane using multidimensional scaling_2150964 +Node: igraph_layout_lgl --- Force based layout algorithm for large graphs_2153643 +Node: Layouts for trees and acyclic graphs2156551 +Node: igraph_layout_reingold_tilford --- Reingold-Tilford layout for tree graphs_2157864 +Node: igraph_layout_reingold_tilford_circular --- Circular Reingold-Tilford layout for trees_2161546 +Node: igraph_roots_for_tree_layout --- Roots suitable for a nice tree layout_2164538 +Node: igraph_layout_sugiyama --- Sugiyama layout algorithm for layered directed acyclic graphs_2166991 +Node: igraph_layout_umap --- Layout using Uniform Manifold Approximation and Projection [UMAP]_2170678 +Node: igraph_layout_umap_compute_weights --- Compute weights for a UMAP layout starting from distances_2175326 +Node: 3D layout generators2179936 +Node: igraph_layout_random_3d --- Places the vertices uniformly randomly in a cube_2181084 +Node: igraph_layout_sphere --- Places vertices [more or less] uniformly on a sphere_2181971 +Node: igraph_layout_grid_3d --- Places the vertices on a regular grid in the 3D space_2183397 +Node: igraph_layout_fruchterman_reingold_3d --- 3D Fruchterman-Reingold algorithm_2184692 +Node: igraph_layout_kamada_kawai_3d --- 3D version of the Kamada-Kawai layout generator_2187822 +Node: igraph_layout_umap_3d --- 3D layout using UMAP_2191524 +Node: Post-processing layouts2194614 +Node: igraph_layout_merge_dla --- Merges multiple layouts by using a DLA algorithm_2195119 +Node: igraph_layout_align --- Aligns a graph layout with the coordinate axes_2196327 +Node: Processes on graphs2197572 +Node: Epidemic models2197811 +Node: igraph_sir --- Performs a number of SIR epidemics model runs on a graph_2198369 +Node: igraph_sir_t --- The result of one SIR model simulation_2200805 +Node: igraph_sir_destroy --- Deallocates memory associated with a SIR simulation run_2201990 +Node: Reading and writing graphs from and to files2202578 +Node: Simple edge list and similar formats2203662 +Node: igraph_read_graph_edgelist --- Reads an edge list from a file and creates a graph_2205028 +Node: igraph_write_graph_edgelist --- Writes the edge list of a graph to a file_2206935 +Node: igraph_read_graph_ncol --- Reads an _ncol file used by LGL_2208048 +Node: igraph_write_graph_ncol --- Writes the graph to a file in _ncol format_2211744 +Node: igraph_read_graph_lgl --- Reads a graph from an _lgl file_2213634 +Node: igraph_write_graph_lgl --- Writes the graph to a file in _lgl format_2217026 +Node: igraph_read_graph_dimacs_flow --- Read a graph in DIMACS format_2219148 +Node: igraph_write_graph_dimacs_flow --- Write a graph in DIMACS format_2222513 +Node: Binary formats2224079 +Node: igraph_read_graph_graphdb --- Read a graph in the binary graph database format_2224461 +Node: GraphML format2226761 +Node: igraph_read_graph_graphml --- Reads a graph from a GraphML file_2227244 +Node: igraph_write_graph_graphml --- Writes the graph to a file in GraphML format_2229064 +Node: GML format2230822 +Node: igraph_read_graph_gml --- Read a graph in GML format_2231259 +Node: igraph_write_graph_gml --- Write the graph to a stream in GML format_2233802 +Node: Pajek format2238480 +Node: igraph_read_graph_pajek --- Reads a file in Pajek format_2238940 +Node: igraph_write_graph_pajek --- Writes a graph to a file in Pajek format_2243586 +Node: UCINET's DL file format2246881 +Node: igraph_read_graph_dl --- Reads a file in the DL format of UCINET_2247239 +Node: Graphviz format2248635 +Node: igraph_write_graph_dot --- Write the graph to a stream in DOT format_2248984 +Node: LEDA format2250319 +Node: igraph_write_graph_leda --- Write a graph in LEDA native graph format_2250678 +Node: Convenience functions for locale change2252215 +Node: igraph_enter_safelocale --- Temporarily set the C locale_2252697 +Node: igraph_exit_safelocale --- Temporarily set the C locale_2254691 +Node: Using BLAS; LAPACK and ARPACK for igraph matrices and graphs2255611 +Node: BLAS interface in igraph2256035 +Node: igraph_blas_ddot --- Dot product of two vectors_2257805 +Node: igraph_blas_dnrm2 --- Euclidean norm of a vector_2258529 +Node: igraph_blas_dgemv --- Matrix-vector multiplication using BLAS; vector version_2259141 +Node: igraph_blas_dgemm --- Matrix-matrix multiplication using BLAS_2260936 +Node: igraph_blas_dgemv_array --- Matrix-vector multiplication using BLAS; array version_2262592 +Node: LAPACK interface in igraph2264226 +Node: Matrix factorization; solving linear systems2265464 +Node: igraph_lapack_dgetrf --- LU factorization of a general M-by-N matrix_2266229 +Node: igraph_lapack_dgetrs --- Solve general system of linear equations using LU factorization_2267893 +Node: igraph_lapack_dgesv --- Solve system of linear equations with LU factorization_2269670 +Node: Eigenvalues and eigenvectors of matrices2271678 +Node: igraph_lapack_dsyevr --- Selected eigenvalues and optionally eigenvectors of a symmetric matrix_2272499 +Node: igraph_lapack_dgeev --- Eigenvalues and optionally eigenvectors of a non-symmetric matrix_2276336 +Node: igraph_lapack_dgeevx --- Eigenvalues/vectors of nonsymmetric matrices; expert mode_2279635 +Node: ARPACK interface in igraph2286452 +Node: Data structures2290014 +Node: igraph_arpack_options_t --- Options for ARPACK_2290875 +Node: igraph_arpack_storage_t --- Storage for ARPACK_2299080 +Node: igraph_arpack_function_t --- Type of the ARPACK callback function_2300496 +Node: igraph_arpack_options_init --- Initialize ARPACK options_2301948 +Node: igraph_arpack_storage_init --- Initialize ARPACK storage_2303286 +Node: igraph_arpack_storage_destroy --- Deallocate ARPACK storage_2305326 +Node: ARPACK solvers2305954 +Node: igraph_arpack_rssolve --- ARPACK solver for symmetric matrices_2306588 +Node: igraph_arpack_rnsolve --- ARPACK solver for non-symmetric matrices_2309191 +Node: igraph_arpack_unpack_complex --- Makes the result of the non-symmetric ARPACK solver more readable_2312676 +Node: Non-graph related functions2315033 +Node: igraph version number2315518 +Node: igraph_version --- The version of the igraph C library_2315826 +Node: Running mean of a time series2317089 +Node: igraph_running_mean --- Calculates the running mean of a vector_2317480 +Node: Random sampling from very long sequences2318394 +Node: igraph_random_sample --- Generates an increasing random sequence of integers_2318845 +Node: Random sampling of spatial points2320975 +Node: igraph_rng_sample_sphere_surface --- Sample points uniformly from the surface of a sphere_2321796 +Node: igraph_rng_sample_sphere_volume --- Sample points uniformly from the volume of a sphere_2323469 +Node: igraph_rng_sample_dirichlet --- Sample points from a Dirichlet distribution_2325225 +Node: Fitting power-law distributions to empirical data2326850 +Node: igraph_plfit_result_t --- Result of fitting a power-law distribution to a vector_2327692 +Node: igraph_power_law_fit --- Fits a power-law distribution to a vector of numbers_2329782 +Node: igraph_plfit_result_calculate_p_value --- Calculates the p-value of a fitted power-law model_2334517 +Node: Comparing floats with a tolerance2337115 +Node: igraph_cmp_epsilon --- Compare two double-precision floats with a tolerance_2337843 +Node: igraph_almost_equals --- Compare two double-precision floats with a tolerance_2339756 +Node: igraph_complex_almost_equals --- Compare two complex numbers with a tolerance_2340908 +Node: Advanced igraph programming2342125 +Node: Using igraph in multi-threaded programs2342409 +Node: IGRAPH_THREAD_SAFE --- Specifies whether igraph was built in thread-safe mode_2343449 +Node: Thread-safe ARPACK library2344173 +Node: Thread-safety of random number generators2344661 +Node: Progress handlers2345497 +Node: About progress handlers2345918 +Node: Setting up progress handlers2347058 +Node: igraph_progress_handler_t --- Type of progress handler functions2347701 +Node: igraph_set_progress_handler --- Install a progress handler; or remove the current handler_2349608 +Node: igraph_progress_handler_stderr --- A simple predefined progress handler_2350857 +Node: Invoking the progress handler2352251 +Node: IGRAPH_PROGRESS --- Report the progress of a calculation from an igraph function [macro variant]_2353283 +Node: IGRAPH_PROGRESSF --- Report the progress of a calculation from an igraph function; printf-like [macro variant]_2354722 +Node: igraph_progress --- Report the progress of a calculation from an igraph function_2356342 +Node: igraph_progressf --- Report the progress of a calculation from an igraph function; printf-like_2358019 +Node: Writing progress handlers2359967 +Node: Writing igraph functions with progress reporting2360928 +Node: Multi-threaded programs2362649 +Node: Status handlers2363168 +Node: Status reporting2363415 +Node: Setting up status handlers2364805 +Node: igraph_status_handler_t --- The type of the igraph status handler functions2365426 +Node: igraph_set_status_handler --- Install of uninstall a status handler function_2366242 +Node: igraph_status_handler_stderr --- A simple predefined status handler function_2367122 +Node: Invoking the status handler2367958 +Node: IGRAPH_STATUS --- Report the status of an igraph function_2368604 +Node: IGRAPH_STATUSF --- Report the status from an igraph function2369630 +Node: igraph_status --- Reports status from an igraph function_2370923 +Node: igraph_statusf --- Report status; more flexible printf-like version_2372256 +Node: Glossary2373608 +Node: Licenses for igraph and this manual2377946 +Node: THE GNU GENERAL PUBLIC LICENSE2378228 +Node: Preamble2378555 +Node: GNU GENERAL PUBLIC LICENSE2381230 +Node: How to Apply These Terms to Your New Programs2393711 +Node: The GNU Free Documentation License2396901 +Node: 0_ PREAMBLE2397830 +Node: 1_ APPLICABILITY AND DEFINITIONS2399164 +Node: 2_ VERBATIM COPYING2404182 +Node: 3_ COPYING IN QUANTITY2405121 +Node: 4_ MODIFICATIONS2407329 +Node: 5_ COMBINING DOCUMENTS2412512 +Node: 6_ COLLECTIONS OF DOCUMENTS2413979 +Node: 7_ AGGREGATION WITH INDEPENDENT WORKS2414859 +Node: 8_ TRANSLATION2416052 +Node: 9_ TERMINATION2417246 +Node: 10_ FUTURE REVISIONS OF THIS LICENSE2417892 +Node: G_1_1 ADDENDUM; How to use this License for your documents2419010 +Node: Concept index2420629 + +End Tag Table + + +Local Variables: +coding: utf-8 +End: diff --git a/doc/igraph-docs.xml b/doc/igraph-docs.xml new file mode 100644 index 0000000..44cffe9 --- /dev/null +++ b/doc/igraph-docs.xml @@ -0,0 +1,172 @@ + + + + + + + + +]> + + + + + &igraph; Reference Manual + &version; + + GáborCsárdi + + Department of Statistics, Harvard University +
1 Oxford street, Cambridge, MA, 02138 USA
+
+
+ + TamásNepusz + + Department of Biological Physics, Eötvös Loránd University +
1/a Pázmány Péter sétány, 1117 Budapest, Hungary
+
+
+ + VincentTraag + + Centre for Science and Technology Studies, Leiden University +
Room B5.31, Kolffpad 1, 2333 BN Leiden, Netherlands
+
+
+ + SzabolcsHorvát + + Department of Computer Science, Reykjavik University +
Menntavegur 1, 102 Reykjavík, Iceland
+
+
+ + FabioZanini + + Lowy Cancer Research Centre, University of New South Wales +
Room 211, Botany and High St, Kensington, NSW, 2033, Australia
+
+
+ + DanielNoom + + jitjit software development +
Amsterdam, Netherlands
+
+
+
+ + + This manual is for &igraph;, version &version;. + + + Copyright (C) 2005-2019 Gábor Csárdi and Tamás Nepusz. + Copyright (C) 2020-2025 igraph development team. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled + GNU Free Documentation License. + + +
+ + + + + + + + + + + + + + + Data structure library: vector, matrix, other data types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Advanced igraph programming + + + + + + + + + + + +
diff --git a/doc/igraph.3 b/doc/igraph.3 new file mode 100644 index 0000000..bee9abe --- /dev/null +++ b/doc/igraph.3 @@ -0,0 +1,47 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.\" +.\" Copyright (C) 2006-2021 The igraph development team +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU General Public License as published by the Free +.\" Software Foundation; either version 2, or (at your option) any later +.\" version. +.\" +.\" This is distributed in the hope that it will be useful, but WITHOUT +.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +.\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +.\" for more details. +.\" +.\" You should have received a copy of the GNU General Public License with +.\" your Debian GNU/Linux system, in /usr/share/common-licenses/GPL, or with +.\" the dpkg source package as the file COPYING. If not, write to the Free +.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.TH IGRAPH 3 "May 2021" "igraph library" +.SH NAME +igraph \- a library for creating and manipulating graphs +.SH DESCRIPTION +.B igraph +is a C library for complex network analysis and graph theory, with emphasis on +efficiency, portability and ease of use. +.SH DOCUMENTATION +The full documentation can be downloaded from the homepage of the +library: +.RI < https://igraph.org/c/doc > +.SH BUGS +If you think you have found a bug in igraph, feel free to file a bug report +in the issue tracker at: +.RI < https://github.com/igraph/igraph/issues > + +.SH AUTHORS +Gabor Csardi , +.br +Tamas Nepusz , +.br +Vincent Traag , +.br +Szabolcs Horvat , +.br +Fabio Zanini , +.br +Daniel Noom diff --git a/doc/igraphlogo/igraph-white.svg.gz b/doc/igraphlogo/igraph-white.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..60b54058fef4f894642fda1310afb63dcb3f5706 GIT binary patch literal 6138 zcmVpy(Fy;*#}+CHo|cNZsS7*7_fyUWeh`tJJT={{q=7aiv^~=dpN(kyf~@!_wTniLU(m}wz^r}uI_dZXJ#;GCo}owOn%A9*Wa%$ zH@CN&y9eRCd-!cfyuErmQFAdLoDi8(%GqeotUU$A>BFbH-SXqt=8A?b{8kMeqUc*oM6RPzO?DjC#@IC9~7s1BCWHT0gHb)6R zDWQSp;ji&1%?}qR$@^g}-sjH`T9^<5* z_1C*kvvw>N*ITIm=4Q2B-d#fO6w&nP{rYOP_pCnHeD`;3;@Fc}K3L;4+c+G3{_t*j zwfO)mJAC-VW^)VJ4*u}TC63(Zp@it&qi5V#2!oAz%!dzQb?@miPv76IcW_JhA0M&5 z-)>28v;4Gz_^z!V+u=RD+kBv$E>7MqZ>D3II6tiKu#eN~LyR9E?Y2@hw^B-cf`S_T z#3RbFsm$$ax4c^Jma}>^BsE)u?>_(UKmO_Ks(!C8FVFvXv;9ZY#>IjJ%kSVvE>2Fq zp5WJ4m*?=dx69qv>sy==9rb_0yFyE+2YbXc*LhGKcDquyeKdAmUEZ$AboQ6s`sU`( zoTP3`hCHhat=l;3_VN0x?NYv7AIRxu`Q7T~;^Ym_Z?e1T>GgK={{D7zg+<8;pUkq8 z?~P#xm44txFHYp+4MOo3f`w3=TfFJfPzK`+h6L*35N+l}!3^2OiYA$esHLwsK*Q(; zm$NRaq8?}-0=#<`Eaj1y!Km2_2AAY4xs9W8D7POGL6p)D5i^0(yTM@d#KI7qjOf#l z<0#x=D5kEnL-R1Eiemg!XF86y5h&6;V95|EQAmPwS*+PB<_E9~<`DHBt={^IwjXbp7?!?L&jGt^XMvlgp?p{Co3vdMI4o-a=QVW5K07F zaFY_B@?*6*P_X?V40H7<&5?NyyKoP5O^6Sd2Y)6PwP)?~aB-gFiwpRy3&6Q#7!frN zXEj#ssHkceL`v<#Nr%x27GdLxLEFPf?vc^HaR=sZLg}bc9^N9t0=yjqFxVnMUe@(!T>U%T!w)00eRR*ByWyGXs!!ubjX{agV_yG)?j2WPS))c zM#d6m4BRQ;Q=WERDY;tAXJvx%4Xv0wZbXcTLA zzyivkhJ~zHf{iP;U^*tk!06-CXq3~b^Cvm+9_9R5Bx1zJI7c62N1Orn1S~<{a6&W2 z)9{lHc9eM>ise~yo{+p?k(2=tgcRi{3o1NY52HZvSf&-#qUc1L9>)S{7Dl6y8kiH- zAiJrUo(0qmXTDE~VW2!-Fj@pLBbut*rbQ~CiEeg6NjbuA$BMzji}vHhRq6$iqFG)- zoO5}99_pxSRzbw8wgYcml)FGzn+_WAWU(@=Z4}FcoP$x!Nt~N8+N~c7MpW*Z601YW@ManP%SdxnZAo{3J_jAVHiD zDPvuyWrl&}Fe8xzQ$Uu(pyq64RWgS?!M-B^JTx+^3lhm) zrrjCnAax@60t?E-aH{k+X~I;B&@=I09c4`L10q?kxq=s8MUO~s_|8(#ARyQ9^f$p< z@rlq}*tDKCSIH>4k0E<$+S8$yQ5}0kjx)d`aYV=hkYG4V(feQ%bBpT#P%I~*L?(cB z1Ert!R=hs)6_pS@bkKc6$fXLLPC7W*H_pn-e#lLY(!2*#*O=kOVEdD%G`#p(WJxI?Z{k=n%Fj7__@;KUS8S;eZk+PCN08f?_*}k& zQypsh+fg!`9MwP(mBniZ|nq-C(DalB6~tG;09HhEj!Ir6_vc$YbR@ zUgmtMXg*ibSbF%Bq~;71uX+}-s}x*AnRha_MN=`v9OlBTAdrx0z1!j{Y;WQ=%)m%V zTZ3_UvBaX;H8AL@a0{l~us?7dP_n(B#T;yx;t}f7SlBgP1&MZPP z$F{wOu)+(9>`N9VqYMdVHCIdtx(AZ8&_n%Yf4mB#rIWf6O7H-#BCG3KtN zu6}fl8!NS@c2k(BXTeHI(oHF~KhrRkoc2xLbZFmAnfd2#3b80Fn!71n5p`z2n?jWG z?7Jxz`CEZd`)-Ow>@}k~XxCXwZxd#el!(YE?J5>jvn?u**tnTFo{JGQ z9C)}#zVJx@Et=1@)I#~vQhRv-N^F_I=%lzdEeKT9NCS+?e*uOg%MKU|d$w(Wp}0m# z@3eDanwctQ;o<0%Enf*9IP~I_-H^-^0hnm539P}cKw`3m$(;+i-l7TQ4 zGYiK8!78trT$6+A3y3F0@)9QE@6^%~GBN`4rG zmBbWfG>U*Em=wb>CMWF*<7~s~ict3>LY0ovB6~tECRl6^RGKfcADE(VMk_KkPCIu+ z{Msa*DE*@jvKpLQ3NaJ9<`WBZ(&EH8jhZ3bOhHx+lM5|m6}>*Eo_dtAe4r|S164v+sIZz=`q!j5@+s4NqLH7 z+0r#KqPTqt3s8MyW$vj-$z8MYnVk4*Hl0;PLKa1yd_9%BZo7*L*d3Ho)pMdmP7+J8 z8Ud1YVx9CuLRGL(y9N$bFV(Y;#*XhVIN`$KW2NtH5-xoHmMB!hFvsvuZ$S4_B(htOE}DZR)t zY7a(k0-WQWfI2HU`eorV% z&FUx(#0%O8P<4oQ(U>1jubIS zT`(*CpDZqpW~V1H)_%Cd>A6TZ#SWSF2p|VI+aOq553M_t!f1CCL& zUM$konOkP*3~$3l%Y;WYs(B@1V~Q0^L62pw?RYG-g{(&yc!My9T>6zDHwZ@#^|NVU z_>u;`I1QvnLv>|B%kZm5oKU_W(R{Q+0)v7}J8Qf#N7V4_l10I!%4W<#O;Vy2?z!G_ ztV-&bbG_A}>>k$2&SBP);Cm!};yW1_l{raG$4!$De$1`O$uP)!Iz)inB`6g$+SCgz5Y3dZVC1#|5O$C)rsi!|QccgL z?s~qP;<`O7E*fnkh><$vsl|BH#{nc=E^*?$h!L4)qNd-Ygr`T@teAFc9JU&Y7w$Y& zYJ5d0B;`&ut;T-`XRWbauVg;0`OT0140+4bZdw}ZN)~M@)hMh zkr(~vk$YJaZ<(-qhm#DsuD&I-&a(#+g;GivQUXg(xku^v&Y0e{6t7WYMkN9D^O(v{ z#Wb0fGco7I`d~dpdJxFwVUkV0+5M8xyM;iPvz*%2N-GOKib3t7`YL3*{}OL8mj_ zr&mneMy_7)o1PL$3Ol!E9*{n#rsLDhedRSRQTj1f&IT+QXJeXcBL)`i>(Je?ryM*z zZAYn&(RO{g<{{Ki$5tfsWS~{r%}G*x%_lf_f{$pxmy3g+7O=fxpCC$+p6kWYn-1+? zM{KC(&Q6-ho;f&jK7FkrNLl0!gS?lleQrT}z82?V5o^%5t32QQgBo{*h3B=Z&%!&m z&wF%klry&CK%YV9_PJ-uiab=q4;>Na;;TX?7B%I%AHKYm;^EqnR5G*Ggf~i~9bO9w z&<{xxML7&({{Jh0pw+O5JoQQFN>R#dETTj>B)ZmceF?FbSqC4|zz zUFC`)7z1MlF&a5v+Xd*-Mr*Rj7Ah0;ok%mag(BK7K#M)z!O;_qyY7XvX9qpeBN&D5FIwUda_uEE*pL%pBt>n5u0aK0lF(b|Ae?#LIR z;-R&5=xkRak4HAxqn))mx_8QRe$CkPwG4yh%bN71Dg;t!RX(&f&%Uox8Yy;3iIf4W z&ms>iuLD=?(Ap++ZZdt-@pE@ct{y!TC&gx!K^o$fySp0d^u{K~S5NbnV3 z^}q4x25rO6KU#Tu2bM46yI6P7zMUfB&IYI9uo=FDg|s#I(;ZGYPigk34swT{xLz5R zlf#Gg7X@r_L89{z>5 z-gQAQa)QYTma|DoTx7Z3xAberl}MlvJml)@bR8 zp(g!a*f1>y9=qGMVxsJPoqmjoy{nGW$`!7Th#2Fnyd)2M;JY8RBZm}op+6ZY_*`U_ zD0Q6|3}$Ac>;@y489UhbOgRgPBjP|zFGzWdl6lOY$SoUCyj7i!hT#LJ9{Uq~eAbVY z;@_8bj`HV;tn;@8A)wfU#1qm=$i}_>1vAk3=T^_y-SJ;VJSl9-L-K49#`Zm=y!w6x z-m6I-HAml*hh>A2KaH_P|K$AR#UfvB*7)n(H|%Qp@NT)?EL}mBUT@+L z3O#Elq^!?5%xr$7eD!}2u-rZ=+b&9KZwO~S+NYxQkJRFq%Z}*g?c0ad?tJ{bJexpJ zV?1Nf{QWzBMVW0f?isRA{C`>Sck=l;vMopRsKJso_8GEI&ylGj@J=9k7hX-%F8*sV zF}<2TEyji6KfIw#g>8wK(H|NAhet~}S`DEPL|8{I_6h}hEY=3|)yp*Q_;9>N8F`0u z%Da__6xM$Aly_$%-IlbsNtCD|WXFzO`_wkc9}pyY%jjkFN5+3Fl)Yn|hIn2nUO@Ij z6?d^RO&4`NcGY;)( zvTD1IuW9jxtj-oErdQLa$)V)|!Y$>c^hd^jp+j^0@tpVRm0{c4$LxN*KbyN!h&RZP zT34!mJLM1XQN$Pb>QsI?mtBZYdbwYNq+WQoKUP!z$zJYA2kU1W_UI57k#04^F27bU zcf420yNVq74xZ!B50N#NFN*fF6)hbZ(8T&hXM8V*>!h)GJlyq)(`U6YyLm8zW&Ys M0Y{7LoH|kf0L?=s_y7O^ literal 0 HcmV?d00001 diff --git a/doc/igraphlogo/igraph.svg.gz b/doc/igraphlogo/igraph.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..b9f3d21c40c26c7723aa22f5b5fae35320ff5343 GIT binary patch literal 6101 zcmV;`7b@rn239g`&78istLoPMt{T7mXu z#Sg3P!+LXfabkw?WU;!t++3~it}jmh=da(M=99(4Zh3dLyxH8XE>7+?Cx86rcQ60r z^mOrRyISs6SBv-S-P^^V@BZ;{xx8O3e*bp2yFWiWd;k9Zu&&^+*)#cghW_7!|+dZ6_!JM7U3@me0{rGU%%a5oY?w(eRXky8C&_%hCiRQVjP@4YVYhH`MA$@wYlUh zE>70h+vWY+p-E!m-Mrh~zuWz7^$5tI`@8F-TpzCPu`T8JD?he}-{9%XtJRwaQmMT7&C3Hkni%gf z%lp+OHB(7D$?QII>-I^V?(bqxxc7hi0Ld=S7s1BCWHT0gHb)6RDxrbS;ji&1&5su+ z$@^g}-sjK1v`*`Evt3`WA#RuKF#2RY$XytRl#@GPg*3W#8@P}&5!e}JB%dZ3aD%RX zSg+pCg8Y8@u&VF6UtYtYZ#LVDlds;$pZehY&Gu@wZI7b-*?Y8s(yw5Kab04hqf~lW!*Y<<;dm{Os*=_s#ki zdqfxgpYW^D(&@n-G0k;;slIl*QipvsYF%C4uE})vm)-j2=Fc3YE=yi{Rwr7Qan`Nl z7pYwq7Z@0Yu`lN;N8yulD}HqfMp^^dD_sc6-=+QIb9AVYglFF5gZVPW}Y_6CYH512AUN)(ddSQc~kiunQTf;mLJN3*wn@Gc7Fv`!laGiGA5JSx^p z?sbQHm`=%clP7*3K$$vIvw}P}p^yksU48i~j1ZDpgdkMS_Cm8nyTEUMJk|)ZgxURIl^zpiowH+_G8CY>IIRaSzbb%b9sLp>Zoc~ zLBy-J18-cEyFgc)4kA;h;=;->w^1w)at=l@Cvk4ZXt#bSc%yR9l$eEDJ$A%Kwc9Dq zaD~L_D^9}vlU%@w@(Dtbh6!*`at2LZW;r@sl_icf^*!lrev zIZH;-eGJ)4)1D5sjOth;a-0DkiEo4~011Y@6ul2NF}JAx55;m4N@N07;{`bz`r*tcTpxD9w8?bd4Ea47T5CO2dnvW#(fv8=Iq$QBRqV z#;fnd_i}8_#XvYkk`P4dn7ig;-A*v865Zv@xo%Y;^{E=gio_Dih;|bN^Ex97>ou{% zlE`<81~a8|GIo@)xjWR#C}Rh)SnHG%lG3Pacg)j<+D1@z6%xENm#~e+y3Hs}- z-0&%gz*aE&AuIVfbVxAFTyugL=k;KfH7A&J4@5EK0fLQ^w>$AoL1zxT&drk`qwqC* zV9pRp2?;R{Imr`p%z`=5^VG1)J<1w=Pl?z>N@9rj6{E!{ckcteNFzwz-?*`cAbA~Z zgJEXIv^McIo7hLE3~Are_6GJQ6gwY6^TV_c2+txECnLE;if}N0sN2?(n zIRqk=gJuoDyP;H}S1F2K7xGy7j+Z%KDw@w# zG?pGdC8;?B#jBo0>?#Gb+r9 zA!HV{cQK=^&`kAeGe5I3jmyN!Sct)2kJ6*C0banH+K*_~jnQP4+D+jqNr`v|328S) zvp-7BId{-a>Cw4wVi9>$dJ3JpDTrB!l%{r5P^EEvL|Me1+D&1GO^mr~sk0wlRGT-l5|r_?awq!C8vFp*B#n-Q)d3Tn?fwgjOK0%XGERZ@1_u?Jo|2nMgCSG z)V`Zy5qr&O4%&6r(%Xa?B_$#78~iOfwVa zD#i$<5oH5}+|wsQIkO)}2+BJq^5!^|^-wdIj)Pug@s{kPU^rxkPBIXtVrJo3AXw!! zlWTHteF5>LNM6DwG$~m?IGce%y`pf#>FS!9oQ+nSMI8!7v|a;RMad6?u#%Xfj7AZV z1e0PI#^j`3VVrGPoe}DOM5xkHT4Ybi#RQAZflBj5)&o=Y&1glY#%brSh+mt;6QzID zK~{rvOCe@L*L-4OPFkE8r%^Lxn<>bwVRE5`tiodfA&hdCUPf`mpHSFx&d48Eds z9i>O`R!o{ng27qotTD1}X$zZUE(oPz@D)lU(>Ah{P45q~xmE_)JdxHJi?=A|Z<+PrjbYU6gVA}5KZSd9QlIxhp2z$U|tX`;=Z}8MOx^Cjrj! zOhBEU_6cP@Gs+%7H3srqNK^AS7L-}W8H*7t)yj)AqOsa8CwwJyhu;&*QnNZr1NoM$ zu~egT)`9siA!2i zqfZgG7}9qOsd5AvJ?kF+_O{X zo}0|8;Y#XOq}dq6tr<^KMtN;}#7gH|E40@g$|*KonYh{duJN@n#Wqf|diKrpc}m# zbcUzlqGiIP8r8fKu`$JprJ&m~*LK_%+CtVX3_L-YLoWSFkQ0O>oBG)_Fnmb^pPdHM zqoKMop=J2hBTgvqM>HSpkieke(#{%B%n>y_yJS%?sj?Y!P?MBsg?p~I9IKN0&bi*| zP<9V%W#=$zN$@?AKJiWl-pZUL!`t-{kDaxHk-&|x9MZT)$RjjZL6K=DBN%fLFU-uT zn&YO)2S4W4y2H z78i}S5yVIx^3-Cy>Ei&BE|)m)Uc`t@Gf~s;QNq)sY*tJ=H4aM+#S3?yDm7kF3Q4(B zO{?+W!C7l;*DINKYrgr>?;%fl+DS`8otzFJtA;;ytjRGtG35_3j&ZUc?`upZH*qf2cJ)MVA!7v1vzS$XQFaf&UNW>xJzcA>n5H0X4u^Yn^|+sM@m zzUnEFq_A^q<`>fE)O36rxv#vYDM~-a%HHr=EB3}T*G3F1Sl6MuV^2AFdfJXs9i#2~ za?L}ipN_3a=FUK?w40Np_?mZc?gSsvfG-CJKQCZ=!#+WjB0blGqt_kUUq@`H=FU!< z$euYkaz4G*5Tq>fhC$v-);_nOJ+H;NSi~Cic9rMNKd5n6Sa=?*`YgP2`@BczMmb|E zHuMQ}Zl8OmtjI$({Lm3$EM65dv8XB6_3-jmiidMYQpwC#6W%C|c6co$kYlmkb+Lx^ za(msOp4S$th0pw+Nr`kQQFN>W>lB)Zp@Ci(1A)RA(RI0Dpw4_7#K5% z(a8SVEaeE1(4{&v;g;P(-bO%0iYJhWW#fo;JT5>U?d$u<5 zceciZftZQW)}-iW>dfleNknPK;B54v-p}>AiE2BX?}$XSHXxKc@*-3`w6+eN?MmeF z$Oe0~vo=TfPI=DPj6JVq7))R0q?f7?NTF5v(AqrvzDjAN*d--W2FyN-Jgi&?uGpcq zP3YWYde!lBbxDpMJrgIzW|cu2;+4BQ8tV09RxY(HiFD2>gvF|<>WOJpOaxpe>~jm1 zy6UBM%WOho>=8%2cWSX)eb`x--5lty`^VSLYwm|T9B>}e>`@)$4n1)_GAbvB59==q*y4gj z;ky_;poqK#LB9;;o6r&#hL@$X948vN#9;xkan1W@bb%}oGnpN0H_Njw=tWL2Il*!^ zDT#~Bc$Z5z^=!B2OsbCBc=F_WSw6Xgn(iX z5>H4gAshGh-GGA_YJ#RKD=FSx66-vN&h6bPdE@5qFkHwY>m)e$`5c1141w_98cHcn8G9O=9_pO z@0n8lrQ$+hiX#l*-zhEz)GVc^~ErLfzI$B;h9`S_a9Aq2{h3(tK-w1k^L&!p(a)#ObM6v2`{+HWNinI%S5_sQWi%0uY z0`Fs;eZI(uZr;3kSnbZo&x^6i0oB42Rx3Ycx9rX)7zRM=p69{rK`|M+MsN9!7tbcod0 z+!rXdW3e`vub-!A$6v<-k&&k{r#vNzNJj0~pK{@BiQAI)7KvgrgzVURYoFR8`BQ)- z_l2HEf8_m-g|fGe(<`1`>VPy{>U<-Qu~bi`ZQU!RLAR8yhznq-o*4``ZT$(JlHUn@?82O?|-KI za{SSo_vwXUyV}R>e!BOXyGe*H$bMQkseU`<59d*Q7x&^+e({!Fh@bRnzXeG>b8mmD zru>tA+L6xFFSh5=Aqpa=`mOr39XFK_5dpP#J_KPP5BCkcEqSNsiSx1aDgWk@cKktZd*n>^Zh7&Tv>U3X_2 b^Oxyk{UCq6o8SE(Ye?eC(NO>Z{9O!T literal 0 HcmV?d00001 diff --git a/doc/igraphlogo/igraph2.svg.gz b/doc/igraphlogo/igraph2.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..69fbc409b2719e3cece4161726cb81da1a8b1a53 GIT binary patch literal 1952 zcmV;R2VeLfiwFo~^~^~C18HY+VQ^?NE^~Hg0PR@YZrex_ea}~LnU@$VQtTVMo3T7t zOb}pz*_WMJ?BjqYTM{P{DUg(9`Rn&I#hYx(v3HV5u)7uvkyX`~I#qSLTGv0^Z#T}J zDXXl=7cTJw*D-lgq*=aNxc~h1qo-V_s^dJ3H$`q1ZeF-Q+`PH|i|08%mL{%E>Kw9q z?fjH~s*-qToOkQG-bM5I;c)P?R_GVyYJTN-9!6Got2d70VA;Hi(q!Sb_Pc$#Y3$Nu zZZ>9X^17N6pUmCCJQ>UryLfhIl483p@~Rn`S8qqgWxDLG?PdEJwD^$g=Y-kNF`C#ITr@@D)YOY1elt7-GPHrZ-j5ARvJaIs^`iD=%t9YlR%1U_p` z+iAxt9yY48EIOMeEfTx4g`2I)c((neOJJHDp`vxauql31eeuu5?Okxw}WIftkeYA+4o3az^&Y8oz-|pe96FGv0K2apl zcqWsE$CDvJD&Q-aw0T&#l5;=MoU6}I+HT7$%50SZ9wwk}mP|Z`1f{jmlmzl$W=51V=cD7x-LZxkra^b#RHh-<+wkT6mcB-)XJ5v?N z#;iULN#QuF64_X6Od011khdW9s(qH4GhM4FZhwaxQ4@W=rkmc~l>&lx4PrPm|x{B9YUa+vaeL-dM>{4fI>Nt(- zcu12@f&>e?!UMBL|M>9nrp?9cB#Hi2l%KlPbR26C-=eG+u6xsquhS#~7jEPFCfg!h zmPY>y?nA=%iZf$dyThcXX-jh=)yb13O}3dGH2n_XB)h}2z%|SEcUx?k(xHV?f~CexNBs0WgT6(&Ep0K>^qMko23u| zoq3e|iqcSus}AZ_=l^Ida4NI=cVGxD7-xDG;4{2=giD_=O~lNTLi(Ih!mrMV17|@% z`p8`G;(9&c+%O9yT!z{m#E|~oA)ql9l910hVZM|~apy-!l`lgsd#M%*=V4^rtXDTr!hN)ag`)4sy2y-dwGrEpIW(GG46ft_Hg zHEbuuZiUc9kC($Y$+;G0E&`qI`{K-RzRt*flZ|Y7#cyUi=0D_ znP@2ZC^T4)Al~6Pf=^>TN{6aGZjkTG0@PMz4<;g<@9y89-mNamZAEWRlDoLf;=DeS z9U9U)6IFF->SW!^P{h&Op!sWFy!Hf$$a|O;pG@>t3r#g`Ur$sSp|5080Yi!%wVF+q z8x(7FyRYl9?DrzeBd~@kJL#}Sv!`Xe%{IsAV`zgpyj z*_e0E>uWfa!5EgUaBL*W1hm%s9Bui@^>PnP&7Hxh^jYr_TPZ@t6??t!&C4dkSHwG6 z8sl6o%lLTO%wv?EPy|X%s;0hu9B6OCYtTFSVbT|6#O=$wr>|0D~fAGlH9>rc{ut%gHi51v)Mo;IKp! zFVTFVX`@$9DUNh9d%z9NlCU&s)*kBc-iWzbdL&-$mHMwCxQp!G}bC+7bwjWBy-=UmRP7&s_Y>}4#{{we^X18bu*XnqwK-aPaR z8ka@QW?pDpaK%J8!7bkwk_vs&M~-~W^XJbvYe#MStD^h5^XK^%=t7V5U3796I6(W7 m$soj;K` + +]> + + +Installation + + + This chapter describes building igraph from source code and installing it. + The source archive of the latest stable release is always available + from the igraph website. + igraph is also included in many Linux distributions, as well as several package + managers such as vcpkg (convenient on Windows), + MacPorts (macOS) and + Homebrew (macOS), which provide an easier + means of installation. If you decide to use them, please consult their documentation + on how to install packages. + + +
+ Prerequisites + + To build igraph from sources, you will need at least: + + + + + CMake 3.18 or later + + + + + C and C++ compilers + + + + + Visual Studio 2015 and later are supported. Earlier Visual Studio + versions may or may not work. + + + Certain features also require the following libraries: + + + + + libxml2, + required for GraphML support + + + + + igraph bundles a number of libraries for convenience. However, it is + preferable to use external versions of these libraries, which may + improve performance. These are: + + + + + GMP (the bundled + alternative is Mini-GMP) + + + + + GLPK (version 4.57 or later) + + + + + ARPACK + + + + + plfit + + + + + A library providing a + BLAS API + (available by default on macOS; + OpenBLAS is one + option on other systems) + + + + + A library providing a + LAPACK + API (available by default on macOS; + OpenBLAS is one + option on other systems) + + + + + When building the development version of igraph, + bison, flex and + git are also required. Released versions do not + require these tools. + + + To run the tests, diff is also required. + +
+ +
+ Installation +
+ General build instructions + + igraph uses a + CMake-based + build system. To compile it, + + + + + Enter the directory where the igraph sources are: + +$ cd igraph + + + + + + Create a new directory. This is where igraph will be built: + +$ mkdir build +$ cd build + + + + + + Run CMake, which will automatically configure igraph, and + report the configuration: + +$ cmake .. + + To set a non-default installation location, such as + /opt/local, use: + cmake .. -DCMAKE_INSTALL_PREFIX=/opt/local + + + + + Check the output carefully, and ensure that all features you + need are enabled. If CMake could not find certain libraries, + some features such as GraphML support may have been + automatically disabled. + + + + + There are several ways to adjust the configuration: + + + + + Run ccmake . on Unix-like systems or + cmake-gui on Windows for a convenient + interface. + + + + + Simply edit the CMakeCache.txt file. + Some of the relevant options are listed below. + + + + + + + Once the configuration has been adjusted, run + cmake .. again. + + + + + Once igraph has been successfully configured, it can be built, + tested and installed using: + +$ cmake --build . +$ cmake --build . --target check +$ cmake --install . + + + + +
+ +
+ Specific instructions for Windows +
+ Microsoft Visual Studio + + With Visual Studio, the steps to build igraph are generally the + same as above. However, since the Visual Studio CMake generator is + a multi-configuration one, we must specify the configuration + (typically Release or Debug) with each build command using the + --config option: + + +mkdir build +cd build +cmake .. +cmake --build . --config Release +cmake --build . --target check --config Release + + + When building the development version, bison + and flex must be available on the system. + winflexbison + for Bison version 3.x can be useful for this purpose—make sure + that the executables are in the system PATH. + The easiest installation option is probably by installing + winflexbison3 from the + Chocolatey + package manager. + +
+ vcpkg + + Most external dependencies can be conveniently installed using + vcpkg. + Note that igraph bundles all dependencies + except libxml2, which is needed for GraphML + support. + + + In order to use vcpkg integrate it in the build environment by executing + vcpkg.exe integrate install on the command line. + When configuring igraph, point CMake to the correct + vcpkg.cmake file using -DCMAKE_TOOLCHAIN_FILE=..., + as instructed. + + + Additionally, it might be that you need to set the appropriate + so-called triplet using + -DVCPKG_TARGET_TRIPLET when running + cmake, for exampling, setting it to + x64-windows when using shared builds of packages or + x64-windows-static when using static builds. + Similarly, you also need to specify this target triplet when + installing packages. For example, to install + libxml2 as a shared library, use + vcpkg.exe install libxml2:x64-windows and to + install libxml2 as a static library, use + vcpkg.exe install libxml2:x64-windows-static. + In addition, there is the possibility to use a static library + with dynamic runtime linking using the + x64-windows-static-md triplet. + +
+
+
+ MSYS2 + + MSYS2 can be installed from msys2.org. After installing MSYS2, + ensure that it is up to date by opening a terminal and running + pacman -Syuu. + + + The instructions below assume that you want to compile for a 64-bit + target. + + + Install the following packages using pacman -S. + + + + + Minimal requirements: + mingw-w64-x86_64-toolchain, + mingw-w64-x86_64-cmake. + + + + + Optional dependencies that enable certain features: + mingw-w64-x86_64-gmp, + mingw-w64-x86_64-libxml2 + + + + + Optional external libraries for better performance: + mingw-w64-x86_64-openblas, + mingw-w64-x86_64-arpack, + mingw-w64-x86_64-glpk + + + + + Only needed for running the tests: diffutils + + + + + Required only when building the development version: + git, bison, + flex + + + + + The following command will install of these at once: + + +pacman -S \ + mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake \ + mingw-w64-x86_64-gmp mingw-w64-x86_64-libxml2 \ + mingw-w64-x86_64-openblas mingw-w64-x86_64-arpack \ + mingw-w64-x86_64-glpk diffutils git bison flex + + + In order to build igraph, follow the General + build instructions above, paying attention to the + following: + + + + + When using MSYS2, start the MSYS2 MinGW 64-bit + terminal, and not the MSYS2 + MSYS one. + + + + + Be sure to install the mingw-w64-x86_64-cmake + package and not the cmake one. The latter + will not work. + + + + + When running cmake, pass the option + -G"MSYS Makefiles". + + + + + Note that ccmake is not currently available. + cmake-gui can be used only if the + mingw-w64-x86_64-qt5 package is installed. + + + +
+
+ +
+ Notable configuration options + + The following options may be set to ON or + OFF. Some of them have an AUTO + setting, which chooses a reasonable default based on what libraries + are available on the current system. + + + + + igraph bundles some of its dependencies for convenience. The + IGRAPH_USE_INTERNAL_XXX flags control whether + these should be used instead of external versions. Set them to + ON to use the bundled + (vendored) versions. Generally, external versions + are preferable as they may be newer and usually provide better + performance. + + + + + IGRAPH_GLPK_SUPPORT: whether to make use of + the + GLPK + library. Some features, such as finding a minimum feedback arc + set or finding communities through exact modularity + optimization, require this. + + + + + IGRAPH_GRAPHML_SUPPORT: whether to enable + support for reading and writing + GraphML + files. Requires the + libxml2 library. + + + + + IGRAPH_INFOMAP_SUPPORT: whether to enable + the Infomap community detection algorithm. The Infomap library + is licensed under the GPLv3+. Compiling it into igraph causes + GPLv3+ to apply to the resulting binary, instead of igraph's + GPLv2+ license. + + + + + IGRAPH_OPENMP_SUPPORT: whether to use OpenMP + parallelization to accelerate certain functions such as PageRank + calculation. Compiler support is required. + + + + + IGRAPH_ENABLE_LTO: whether to build igraph + with link-time optimization, which improves performance. Not + supported with all compilers. + + + + + IGRAPH_ENABLE_TLS: whether to enable + thread-local storage. Required when using igraph from multiple + threads. + + + + + IGRAPH_WARNINGS_AS_ERRORS: whether to treat + compiler warnings as errors. We strive to eliminate all compiler + warnings during development so this switch is turned on by default. + If your compiler prints warnings for some parts of the code that we + did not anticipate, you can turn off this option to prevent the + warnings from stopping the compilation. + + + + + BUILD_SHARED_LIBS: + whether to build a shared library instead of a static one. + + + + + BLA_VENDOR: controls which library to use for + BLAS + and + LAPACK + functionality. + + + + + CMAKE_INSTALL_PREFIX: + the location where igraph will be installed. + + + +
+
+ +
+ Building the documentation + + Most users will not need to build the documentation, as the release + tarball contains pre-built HTML documentation in the doc + directory. + + + To build the documentation for the development version, simply build the + html, pdf or info + targets for the HTML, PDF and Info versions of the documentation, + respectively. + + +$ cmake --build . --target html + + + Building the HTML documentation requires Python 3, xmlto + and source-highlight. On some platforms, it is necessary + to explicitly install the docbook-xsl package as well. Building the PDF + documentation also requires xsltproc, + xmllint and fop. Building the Texinfo + documentation also requires the docbook2X package, xmllint + and makeinfo. + +
+ +
+ Notes for package maintainers + + This section is for people who package igraph for Linux distros or + other package managers. Please read it carefully before packaging + igraph. + +
+ Auto-detection of dependencies + + igraph bundles several of its dependencies (or simplified versions + of its dependencies). During configuration time, it checks whether + each dependency is present on the system. If yes, it uses it. + Otherwise, it falls back to the bundled (vendored) + version. In order to make configuration as deterministic as + possible, you may want to disable this auto-detection. To do so, set + each of the IGRAPH_USE_INTERNAL_XXX options + described above. Additionally, set BLA_VENDOR to + use the BLAS and LAPACK implementations of your choice. This should + be the same BLAS and LAPACK library that igraph's other dependencies + (e.g., ARPACK) are linked against. + + + For example, to force igraph to use external versions of all + dependencies except plfit, and to use OpenBLAS for BLAS/LAPACK, use + + + +$ cmake .. \ + -DIGRAPH_USE_INTERNAL_BLAS=OFF \ + -DIGRAPH_USE_INTERNAL_LAPACK=OFF \ + -DIGRAPH_USE_INTERNAL_ARPACK=OFF \ + -DIGRAPH_USE_INTERNAL_GLPK=OFF \ + -DIGRAPH_USE_INTERNAL_GMP=OFF \ + -DIGRAPH_USE_INTERNAL_PLFIT=ON \ + -DBLA_VENDOR=OpenBLAS \ + -DIGRAPH_GRAPHML_SUPPORT=ON + + +
+
+ Shared and static builds + + On Windows, shared and static builds should not be installed in the same + location. If you decide to do so anyway, keep in mind the following: + Both builds contain an igraph.lib file. The static one + should be renamed to avoid conflict. The headers from the static build + are incompatible with the shared library. The headers from the shared build + may be used with the static library, but IGRAPH_STATIC + must be defined when compiling programs that will link to igraph statically. + + + These issues do not affect Unix-like systems. + +
+
+ Cross-compiling + + When building igraph with an internal ARPACK, LAPACK or BLAS, it + makes use of f2c, which compiles and runs the arithchk + program at build time to detect the floating point characteristics of the + current system. It writes the results into the arith.h + header. However, running this program is not possible when cross-compiling + without providing a userspace emulator that can run executables of the + target platform on the host system. Therefore, when cross-compiling, you + either need to provide such an emulator with the + CMAKE_CROSSCOMPILING_EMULATOR option, or you need to + specify a pre-generated version of the arith.h header + file through the F2C_EXTERNAL_ARITH_HEADER + CMake option. An example version of this header follows for the + x86_64 and arm64 target architectures on macOS. Warning: Do not use this + version of arith.h on other systems or architectures. + + + +#define IEEE_8087 +#define Arith_Kind_ASL 1 +#define Long int +#define Intcast (int)(long) +#define Double_Align +#define X64_bit_pointers +#define NANCHECK +#define QNaN0 0x0 +#define QNaN1 0x7ff80000 + + + + igraph also checks whether the endianness of uint64_t + matches the endianness of double on the platform + being compiled. This is needed to ensure that certain functions in igraph's + random number generator work properly. However, it is not possible to + execute this check when cross-compiling without an emulator, so in this + case igraph simply assumes that the endianness matches (which is the case + for the vast majority of platforms anyway). The only case where you might + run into problems is when you cross-compile for Apple Silicon + (arm64) from an Intel-based Mac, in which case CMake + might not realize that you are cross-compiling and will try to execute + the check anyway. You can work around this by setting + IEEE754_DOUBLE_ENDIANNESS_MATCHES to ON + explicitly before invoking CMake. + + + Providing an emulator in CMAKE_CROSSCOMPILING_EMULATOR + has the added benefit that you can run the compiled unit tests on the + host platform. We have experimented with cross-compiling to 64-bit ARM + CPUs (aarch64) on 64-bit Intel CPUs (amd64), + and we can confirm that using qemu-aarch64 works as a + cross-compiling emulator in this setup. + +
+
+ Additional notes + + + + As of igraph 0.10, there is no tangible benefit to using an + external GMP, as igraph does not yet use GMP in any + performance-critical way. The bundled Mini-GMP is sufficient. + + + + + Link-time optimization noticeably improves the performance of + some igraph functions. To enable it, use + -DIGRAPH_ENABLE_LTO=ON. + The AUTO setting is also supported, and will + enable link-time optimization only if the current compiler + supports it. Note that this is detected by CMake, and the + detection is not always accurate. + + + + + We saw occasional hangs on Windows when igraph was built for a + 32-bit target with MinGW and linked to OpenBLAS. We believe this + to be an issue with OpenBLAS, not igraph. On this platform, you + may want to opt for a different BLAS/LAPACK or the bundled + BLAS/LAPACK. + + + +
+
+ +
diff --git a/doc/introduction.xml b/doc/introduction.xml new file mode 100644 index 0000000..18dc1a6 --- /dev/null +++ b/doc/introduction.xml @@ -0,0 +1,109 @@ + + +]> + + +Introduction + + +igraph is a library for creating and manipulating graphs. +You can look at it in two ways: first, igraph contains the implementation +of quite a lot of graph algorithms. These include classic graph +algorithms like graph isomorphism, graph girth and connectivity and +also the new wave graph algorithms like transitivity, graph motifs and +community structure detection. Skim through the table of contents +or the index of this book to get an impression of what is available. + + +Second, igraph provides a platform for developing and/or +implementing graph algorithms. It has an efficient data structure +for representing graphs, and a number of other data structures like +flexible vectors, stacks, heaps, queues, adjacency lists that are useful for implementing graph algorithms. In fact these data structures evolved along with the +implementation of the classic and non-classic graph algorithms which +make up the major part of the igraph library. This way, they were fine-tuned +and checked for correctness several times. + + + +Our main goal with developing igraph was to create a graph library +which is efficient on large, but not extremely large graphs. More +precisely, it is assumed that the graph(s) fit into the physical +memory of the computer. Nowadays this means graphs with +several million vertices and/or edges. Our definition of efficient is +that it runs fast, both in theory and (more importantly) in practice. + + + +We believe that one of the big strengths of igraph is that it can be +embedded into a higher-level language or environment. Three such +embeddings (or interfaces if you look at them another way) +are currently being developed by us: an R +package, a Python extension module, and a Mathematica (Wolfram Language) package. Others are +likely to come. High level languages such as R or Python make it +possible to use graph routines with much greater comfort, without +actually writing a single line of C code. They have some, usually very +small, speed penalty compared to the C version, but add ease of use and much +flexibility. This manual, however, covers only the C library. If you +want to use Python, R or the Wolfram Language, please see the documentation written +specifically for these interfaces and come back here only if you are +interested in some detail which is not covered in those documents. + + + +We still consider igraph as a child project. It has much room for +development and we are sure that it will improve a lot in the near +future. Any feedback we can get from the users is very important for +us, as most of the time these questions and comments guide us in what +to add and what to improve. + + + +igraph is open source and distributed under the terms of the GNU GPL +version 2 or (at your option) any later version. +We strongly believe that all the algorithms used in science, let that +be graph theory or not, should have an efficient open-source +implementation allowing use and modification for anyone. + + +
&igraph; is free software + + igraph library + + Copyright (C) 2003-2004 Gábor Csárdi <csardi.gabor@gmail.com> + + Copyright (C) 2005-2019 Gábor Csárdi <csardi.gabor@gmail.com> and Tamás Nepusz <ntamas@gmail.com> + + Copyright (C) 2020-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc. + +
+ +
Citing &igraph; + +To cite &igraph; in publications, please use the following +reference: + +Gábor Csárdi, Tamás Nepusz: The igraph software package for complex network +research. InterJournal Complex Systems, 1695, 2006. + +The igraph C library is assigned the DOI 10.5281/zenodo.3630268 on Zenodo. + + +
+ +
diff --git a/doc/isomorphism.xxml b/doc/isomorphism.xxml new file mode 100644 index 0000000..b3c1a95 --- /dev/null +++ b/doc/isomorphism.xxml @@ -0,0 +1,63 @@ + + +]> + + +Graph isomorphism + +
The simple interface + + + + + + +
+ +
The BLISS algorithm + + + + + + + + +
+ +
The VF2 algorithm + + + + + + + + + + + + +
+ +
The LAD algorithm + + +
+ +
Functions for small graphs + + + + +
+ +
Utility functions + + + +
+ +
diff --git a/doc/iterators.xxml b/doc/iterators.xxml new file mode 100644 index 0000000..56bec46 --- /dev/null +++ b/doc/iterators.xxml @@ -0,0 +1,101 @@ + + +]> + + +Vertex and edge selectors and sequences, iterators + +
+ +
+ +
Vertex selector constructors + + + + + + + + + + +
+ +
Generic vertex selector operations + + + + + +
+ +
Immediate vertex selectors + + + + + +
+ +
Vertex iterators + + +
+ + + + + +
+ +
Edge selector constructors + + + + + + + + + + + +
+ +
Immediate edge selectors + + + + + +
+ +
Generic edge selector operations + + + + + + +
+ +
Edge iterators + + +
+ + + + + +
+ + + +
diff --git a/doc/layout.xxml b/doc/layout.xxml new file mode 100644 index 0000000..3d11bc4 --- /dev/null +++ b/doc/layout.xxml @@ -0,0 +1,57 @@ + + +]> + + +Generating layouts for graph drawing + +
2D layout generators + + + + + + + +
The DrL layout generator + + + + + + +
+ + + + + + +
+ +
Layouts for trees and acyclic graphs + + + + + + +
+ +
3D layout generators + + + + + + +
+ +
Post-processing layouts + + +
+ +
diff --git a/doc/licenses.xml b/doc/licenses.xml new file mode 100644 index 0000000..45fa97b --- /dev/null +++ b/doc/licenses.xml @@ -0,0 +1,14 @@ + + +]> + + +Licenses for igraph and this manual + + + + + diff --git a/doc/licenses/Licence_CeCILL-B_V1-en.txt b/doc/licenses/Licence_CeCILL-B_V1-en.txt new file mode 100644 index 0000000..c02c70e --- /dev/null +++ b/doc/licenses/Licence_CeCILL-B_V1-en.txt @@ -0,0 +1,515 @@ + +CeCILL-B FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL-B (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial research establishment, having its principal place of +business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +This Agreement is an open source software license intended to give users +significant freedom to modify and redistribute the software licensed +hereunder. + +The exercising of this freedom is conditional upon a strong obligation +of giving credits for everybody that distributes a software +incorporating a software ruled by the current license so as all +contributions to be properly identified and acknowledged. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software at a minimum during the entire period of its +distribution of the Software, it being understood that the additional +cost of acquiring the Source Code shall not exceed the cost of +transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +If the Licensee makes any Contribution to the Software, the resulting +Modified Software may be distributed under a license agreement other +than this Agreement subject to compliance with the provisions of Article +5.3.4. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 CREDITS + +Any Licensee who may distribute a Modified Software hereby expressly +agrees to: + + 1. indicate in the related documentation that it is based on the + Software licensed hereunder, and reproduce the intellectual + property notice for the Software, + + 2. ensure that written indications of the Software intended use, + intellectual property notice and license hereunder are included in + easily accessible format from the Modified Software interface, + + 3. mention, on a freely accessible website describing the Modified + Software, at least throughout the distribution term thereof, that + it is based on the Software licensed hereunder, and reproduce the + Software intellectual property notice, + + 4. where it is distributed to a third party that may distribute a + Modified Software without having to make its source code + available, make its best efforts to ensure that said third party + agrees to comply with the obligations set forth in this Article . + +If the Software, whether or not modified, is distributed with an +External Module designed for use in connection with the Software, the +Licensee shall submit said External Module to the foregoing obligations. + + + 5.3.5 COMPATIBILITY WITH THE CeCILL AND CeCILL-C LICENSES + +Where a Modified Software contains a Contribution subject to the CeCILL +license, the provisions set forth in Article 5.3.4 shall be optional. + +A Modified Software may be distributed under the CeCILL-C license. In +such a case the provisions set forth in Article 5.3.4 shall be optional. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 1.0 dated 2006-09-05. diff --git a/doc/licenses/Licence_CeCILL-B_V1-fr.txt b/doc/licenses/Licence_CeCILL-B_V1-fr.txt new file mode 100644 index 0000000..d9bb943 --- /dev/null +++ b/doc/licenses/Licence_CeCILL-B_V1-fr.txt @@ -0,0 +1,519 @@ + +CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + Avertissement + +Ce contrat est une licence de logiciel libre issue d'une concertation +entre ses auteurs afin que le respect de deux grands principes préside à +sa rédaction: + + * d'une part, le respect des principes de diffusion des logiciels + libres: accès au code source, droits étendus conférés aux + utilisateurs, + * d'autre part, la désignation d'un droit applicable, le droit + français, auquel elle est conforme, tant au regard du droit de la + responsabilité civile que du droit de la propriété intellectuelle + et de la protection qu'il offre aux auteurs et titulaires des + droits patrimoniaux sur un logiciel. + +Les auteurs de la licence CeCILL-B (pour Ce[a] C[nrs] I[nria] L[ogiciel] +L[ibre]) sont: + +Commissariat à l'Energie Atomique - CEA, établissement public de +recherche à caractère scientifique, technique et industriel, dont le +siège est situé 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris. + +Centre National de la Recherche Scientifique - CNRS, établissement +public à caractère scientifique et technologique, dont le siège est +situé 3 rue Michel-Ange, 75794 Paris cedex 16. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, établissement public à caractère scientifique et technologique, +dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153 +Le Chesnay cedex. + + + Préambule + +Ce contrat est une licence de logiciel libre dont l'objectif est de +conférer aux utilisateurs une très large liberté de modification et de +redistribution du logiciel régi par cette licence. + +L'exercice de cette liberté est assorti d'une obligation forte de +citation à la charge de ceux qui distribueraient un logiciel incorporant +un logiciel régi par la présente licence afin d'assurer que les +contributions de tous soient correctement identifiées et reconnues. + +L'accessibilité au code source et les droits de copie, de modification +et de redistribution qui découlent de ce contrat ont pour contrepartie +de n'offrir aux utilisateurs qu'une garantie limitée et de ne faire +peser sur l'auteur du logiciel, le titulaire des droits patrimoniaux et +les concédants successifs qu'une responsabilité restreinte. + +A cet égard l'attention de l'utilisateur est attirée sur les risques +associés au chargement, à l'utilisation, à la modification et/ou au +développement et à la reproduction du logiciel par l'utilisateur étant +donné sa spécificité de logiciel libre, qui peut le rendre complexe à +manipuler et qui le réserve donc à des développeurs ou des +professionnels avertis possédant des connaissances informatiques +approfondies. Les utilisateurs sont donc invités à charger et tester +l'adéquation du logiciel à leurs besoins dans des conditions permettant +d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus +généralement, à l'utiliser et l'exploiter dans les mêmes conditions de +sécurité. Ce contrat peut être reproduit et diffusé librement, sous +réserve de le conserver en l'état, sans ajout ni suppression de clauses. + +Ce contrat est susceptible de s'appliquer à tout logiciel dont le +titulaire des droits patrimoniaux décide de soumettre l'exploitation aux +dispositions qu'il contient. + + + Article 1 - DEFINITIONS + +Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une +lettre capitale, auront la signification suivante: + +Contrat: désigne le présent contrat de licence, ses éventuelles versions +postérieures et annexes. + +Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code +Source et le cas échéant sa documentation, dans leur état au moment de +l'acceptation du Contrat par le Licencié. + +Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et +éventuellement de Code Objet et le cas échéant sa documentation, dans +leur état au moment de leur première diffusion sous les termes du Contrat. + +Logiciel Modifié: désigne le Logiciel modifié par au moins une +Contribution. + +Code Source: désigne l'ensemble des instructions et des lignes de +programme du Logiciel et auquel l'accès est nécessaire en vue de +modifier le Logiciel. + +Code Objet: désigne les fichiers binaires issus de la compilation du +Code Source. + +Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur +sur le Logiciel Initial. + +Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le +Contrat. + +Contributeur: désigne le Licencié auteur d'au moins une Contribution. + +Concédant: désigne le Titulaire ou toute personne physique ou morale +distribuant le Logiciel sous le Contrat. + +Contribution: désigne l'ensemble des modifications, corrections, +traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans +le Logiciel par tout Contributeur, ainsi que tout Module Interne. + +Module: désigne un ensemble de fichiers sources y compris leur +documentation qui permet de réaliser des fonctionnalités ou services +supplémentaires à ceux fournis par le Logiciel. + +Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce +Module et le Logiciel s'exécutent dans des espaces d'adressage +différents, l'un appelant l'autre au moment de leur exécution. + +Module Interne: désigne tout Module lié au Logiciel de telle sorte +qu'ils s'exécutent dans le même espace d'adressage. + +Parties: désigne collectivement le Licencié et le Concédant. + +Ces termes s'entendent au singulier comme au pluriel. + + + Article 2 - OBJET + +Le Contrat a pour objet la concession par le Concédant au Licencié d'une +licence non exclusive, cessible et mondiale du Logiciel telle que +définie ci-après à l'article 5 pour toute la durée de protection des droits +portant sur ce Logiciel. + + + Article 3 - ACCEPTATION + +3.1 L'acceptation par le Licencié des termes du Contrat est réputée +acquise du fait du premier des faits suivants: + + * (i) le chargement du Logiciel par tout moyen notamment par + téléchargement à partir d'un serveur distant ou par chargement à + partir d'un support physique; + * (ii) le premier exercice par le Licencié de l'un quelconque des + droits concédés par le Contrat. + +3.2 Un exemplaire du Contrat, contenant notamment un avertissement +relatif aux spécificités du Logiciel, à la restriction de garantie et à +la limitation à un usage par des utilisateurs expérimentés a été mis à +disposition du Licencié préalablement à son acceptation telle que +définie à l'article 3.1 ci dessus et le Licencié reconnaît en avoir pris +connaissance. + + + Article 4 - ENTREE EN VIGUEUR ET DUREE + + + 4.1 ENTREE EN VIGUEUR + +Le Contrat entre en vigueur à la date de son acceptation par le Licencié +telle que définie en 3.1. + + + 4.2 DUREE + +Le Contrat produira ses effets pendant toute la durée légale de +protection des droits patrimoniaux portant sur le Logiciel. + + + Article 5 - ETENDUE DES DROITS CONCEDES + +Le Concédant concède au Licencié, qui accepte, les droits suivants sur +le Logiciel pour toutes destinations et pour la durée du Contrat dans +les conditions ci-après détaillées. + +Par ailleurs, si le Concédant détient ou venait à détenir un ou +plusieurs brevets d'invention protégeant tout ou partie des +fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas +opposer les éventuels droits conférés par ces brevets aux Licenciés +successifs qui utiliseraient, exploiteraient ou modifieraient le +Logiciel. En cas de cession de ces brevets, le Concédant s'engage à +faire reprendre les obligations du présent alinéa aux cessionnaires. + + + 5.1 DROIT D'UTILISATION + +Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant +aux domaines d'application, étant ci-après précisé que cela comporte: + + 1. la reproduction permanente ou provisoire du Logiciel en tout ou + partie par tout moyen et sous toute forme. + + 2. le chargement, l'affichage, l'exécution, ou le stockage du + Logiciel sur tout support. + + 3. la possibilité d'en observer, d'en étudier, ou d'en tester le + fonctionnement afin de déterminer les idées et principes qui sont + à la base de n'importe quel élément de ce Logiciel; et ceci, + lorsque le Licencié effectue toute opération de chargement, + d'affichage, d'exécution, de transmission ou de stockage du + Logiciel qu'il est en droit d'effectuer en vertu du Contrat. + + + 5.2 DROIT D'APPORTER DES CONTRIBUTIONS + +Le droit d'apporter des Contributions comporte le droit de traduire, +d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel +et le droit de reproduire le logiciel en résultant. + +Le Licencié est autorisé à apporter toute Contribution au Logiciel sous +réserve de mentionner, de façon explicite, son nom en tant qu'auteur de +cette Contribution et la date de création de celle-ci. + + + 5.3 DROIT DE DISTRIBUTION + +Le droit de distribution comporte notamment le droit de diffuser, de +transmettre et de communiquer le Logiciel au public sur tout support et +par tout moyen ainsi que le droit de mettre sur le marché à titre +onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé. + +Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou +non, à des tiers dans les conditions ci-après détaillées. + + + 5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION + +Le Licencié est autorisé à distribuer des copies conformes du Logiciel, +sous forme de Code Source ou de Code Objet, à condition que cette +distribution respecte les dispositions du Contrat dans leur totalité et +soit accompagnée: + + 1. d'un exemplaire du Contrat, + + 2. d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + et 9, + +et que, dans le cas où seul le Code Objet du Logiciel est redistribué, +le Licencié permette un accès effectif au Code Source complet du +Logiciel pendant au moins toute la durée de sa distribution du Logiciel, +étant entendu que le coût additionnel d'acquisition du Code Source ne +devra pas excéder le simple coût de transfert des données. + + + 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE + +Lorsque le Licencié apporte une Contribution au Logiciel, le Logiciel +Modifié peut être distribué sous un contrat de licence autre que le +présent Contrat sous réserve du respect des dispositions de l'article +5.3.4. + + + 5.3.3 DISTRIBUTION DES MODULES EXTERNES + +Lorsque le Licencié a développé un Module Externe les conditions du +Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué +sous un contrat de licence différent. + + + 5.3.4 CITATIONS + +Le Licencié qui distribue un Logiciel Modifié s'engage expressément: + + 1. à indiquer dans sa documentation qu'il a été réalisé à partir du + Logiciel régi par le Contrat, en reproduisant les mentions de + propriété intellectuelle du Logiciel, + + 2. à faire en sorte que l'utilisation du Logiciel, ses mentions de + propriété intellectuelle et le fait qu'il est régi par le Contrat + soient indiqués dans un texte facilement accessible depuis + l'interface du Logiciel Modifié, + + 3. à mentionner, sur un site Web librement accessible décrivant le + Logiciel Modifié, et pendant au moins toute la durée de sa + distribution, qu'il a été réalisé à partir du Logiciel régi par le + Contrat, en reproduisant les mentions de propriété intellectuelle + du Logiciel, + + 4. lorsqu'il le distribue à un tiers susceptible de distribuer + lui-même un Logiciel Modifié, sans avoir à en distribuer le code + source, à faire ses meilleurs efforts pour que les obligations du + présent article 5.3.4 soient reprises par le dit tiers. + +Lorsque le Logiciel modifié ou non est distribué avec un Module Externe +qui a été conçu pour l'utiliser, le Licencié doit soumettre le dit +Module Externe aux obligations précédentes. + + + 5.3.5 COMPATIBILITE AVEC LES LICENCES CeCILL et CeCILL-C + +Lorsqu'un Logiciel Modifié contient une Contribution soumise au contrat +de licence CeCILL, les stipulations prévues à l'article 5.3.4 sont +facultatives. + +Un Logiciel Modifié peut être distribué sous le contrat de licence +CeCILL-C. Les stipulations prévues à l'article 5.3.4 sont alors +facultatives. + + + Article 6 - PROPRIETE INTELLECTUELLE + + + 6.1 SUR LE LOGICIEL INITIAL + +Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel +Initial. Toute utilisation du Logiciel Initial est soumise au respect +des conditions dans lesquelles le Titulaire a choisi de diffuser son +oeuvre et nul autre n'a la faculté de modifier les conditions de +diffusion de ce Logiciel Initial. + +Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi +par le Contrat et ce, pour la durée visée à l'article 4.2. + + + 6.2 SUR LES CONTRIBUTIONS + +Le Licencié qui a développé une Contribution est titulaire sur celle-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable. + + + 6.3 SUR LES MODULES EXTERNES + +Le Licencié qui a développé un Module Externe est titulaire sur celui-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable et reste libre du choix du contrat régissant +sa diffusion. + + + 6.4 DISPOSITIONS COMMUNES + +Le Licencié s'engage expressément: + + 1. à ne pas supprimer ou modifier de quelque manière que ce soit les + mentions de propriété intellectuelle apposées sur le Logiciel; + + 2. à reproduire à l'identique lesdites mentions de propriété + intellectuelle sur les copies du Logiciel modifié ou non. + +Le Licencié s'engage à ne pas porter atteinte, directement ou +indirectement, aux droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs sur le Logiciel et à prendre, le cas échéant, à +l'égard de son personnel toutes les mesures nécessaires pour assurer le +respect des dits droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs. + + + Article 7 - SERVICES ASSOCIES + +7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de +prestations d'assistance technique ou de maintenance du Logiciel. + +Cependant le Concédant reste libre de proposer ce type de services. Les +termes et conditions d'une telle assistance technique et/ou d'une telle +maintenance seront alors déterminés dans un acte séparé. Ces actes de +maintenance et/ou assistance technique n'engageront que la seule +responsabilité du Concédant qui les propose. + +7.2 De même, tout Concédant est libre de proposer, sous sa seule +responsabilité, à ses licenciés une garantie, qui n'engagera que lui, +lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce, +dans les conditions qu'il souhaite. Cette garantie et les modalités +financières de son application feront l'objet d'un acte séparé entre le +Concédant et le Licencié. + + + Article 8 - RESPONSABILITE + +8.1 Sous réserve des dispositions de l'article 8.2, le Licencié a la +faculté, sous réserve de prouver la faute du Concédant concerné, de +solliciter la réparation du préjudice direct qu'il subirait du fait du +Logiciel et dont il apportera la preuve. + +8.2 La responsabilité du Concédant est limitée aux engagements pris en +application du Contrat et ne saurait être engagée en raison notamment: +(i) des dommages dus à l'inexécution, totale ou partielle, de ses +obligations par le Licencié, (ii) des dommages directs ou indirects +découlant de l'utilisation ou des performances du Logiciel subis par le +Licencié et (iii) plus généralement d'un quelconque dommage indirect. En +particulier, les Parties conviennent expressément que tout préjudice +financier ou commercial (par exemple perte de données, perte de +bénéfices, perte d'exploitation, perte de clientèle ou de commandes, +manque à gagner, trouble commercial quelconque) ou toute action dirigée +contre le Licencié par un tiers, constitue un dommage indirect et +n'ouvre pas droit à réparation par le Concédant. + + + Article 9 - GARANTIE + +9.1 Le Licencié reconnaît que l'état actuel des connaissances +scientifiques et techniques au moment de la mise en circulation du +Logiciel ne permet pas d'en tester et d'en vérifier toutes les +utilisations ni de détecter l'existence d'éventuels défauts. L'attention +du Licencié a été attirée sur ce point sur les risques associés au +chargement, à l'utilisation, la modification et/ou au développement et à +la reproduction du Logiciel qui sont réservés à des utilisateurs avertis. + +Il relève de la responsabilité du Licencié de contrôler, par tous +moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et +de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens. + +9.2 Le Concédant déclare de bonne foi être en droit de concéder +l'ensemble des droits attachés au Logiciel (comprenant notamment les +droits visés à l'article 5). + +9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le +Concédant sans autre garantie, expresse ou tacite, que celle prévue à +l'article 9.2 et notamment sans aucune garantie sur sa valeur commerciale, +son caractère sécurisé, innovant ou pertinent. + +En particulier, le Concédant ne garantit pas que le Logiciel est exempt +d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible +avec l'équipement du Licencié et sa configuration logicielle ni qu'il +remplira les besoins du Licencié. + +9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le +Logiciel ne porte pas atteinte à un quelconque droit de propriété +intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout +autre droit de propriété. Ainsi, le Concédant exclut toute garantie au +profit du Licencié contre les actions en contrefaçon qui pourraient être +diligentées au titre de l'utilisation, de la modification, et de la +redistribution du Logiciel. Néanmoins, si de telles actions sont +exercées contre le Licencié, le Concédant lui apportera son aide +technique et juridique pour sa défense. Cette aide technique et +juridique est déterminée au cas par cas entre le Concédant concerné et +le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage +toute responsabilité quant à l'utilisation de la dénomination du +Logiciel par le Licencié. Aucune garantie n'est apportée quant à +l'existence de droits antérieurs sur le nom du Logiciel et sur +l'existence d'une marque. + + + Article 10 - RESILIATION + +10.1 En cas de manquement par le Licencié aux obligations mises à sa +charge par le Contrat, le Concédant pourra résilier de plein droit le +Contrat trente (30) jours après notification adressée au Licencié et +restée sans effet. + +10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à +utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les +licences qu'il aura concédées antérieurement à la résiliation du Contrat +resteront valides sous réserve qu'elles aient été effectuées en +conformité avec le Contrat. + + + Article 11 - DISPOSITIONS DIVERSES + + + 11.1 CAUSE EXTERIEURE + +Aucune des Parties ne sera responsable d'un retard ou d'une défaillance +d'exécution du Contrat qui serait dû à un cas de force majeure, un cas +fortuit ou une cause extérieure, telle que, notamment, le mauvais +fonctionnement ou les interruptions du réseau électrique ou de +télécommunication, la paralysie du réseau liée à une attaque +informatique, l'intervention des autorités gouvernementales, les +catastrophes naturelles, les dégâts des eaux, les tremblements de terre, +le feu, les explosions, les grèves et les conflits sociaux, l'état de +guerre... + +11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou +plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du +Contrat, ne pourra en aucun cas impliquer renonciation par la Partie +intéressée à s'en prévaloir ultérieurement. + +11.3 Le Contrat annule et remplace toute convention antérieure, écrite +ou orale, entre les Parties sur le même objet et constitue l'accord +entier entre les Parties sur cet objet. Aucune addition ou modification +aux termes du Contrat n'aura d'effet à l'égard des Parties à moins +d'être faite par écrit et signée par leurs représentants dûment habilités. + +11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat +s'avèrerait contraire à une loi ou à un texte applicable, existants ou +futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les +amendements nécessaires pour se conformer à cette loi ou à ce texte. +Toutes les autres dispositions resteront en vigueur. De même, la +nullité, pour quelque raison que ce soit, d'une des dispositions du +Contrat ne saurait entraîner la nullité de l'ensemble du Contrat. + + + 11.5 LANGUE + +Le Contrat est rédigé en langue française et en langue anglaise, ces +deux versions faisant également foi. + + + Article 12 - NOUVELLES VERSIONS DU CONTRAT + +12.1 Toute personne est autorisée à copier et distribuer des copies de +ce Contrat. + +12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé +et ne peut être modifié que par les auteurs de la licence, lesquels se +réservent le droit de publier périodiquement des mises à jour ou de +nouvelles versions du Contrat, qui posséderont chacune un numéro +distinct. Ces versions ultérieures seront susceptibles de prendre en +compte de nouvelles problématiques rencontrées par les logiciels libres. + +12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra +faire l'objet d'une diffusion ultérieure que sous la même version du +Contrat ou une version postérieure. + + + Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE + +13.1 Le Contrat est régi par la loi française. Les Parties conviennent +de tenter de régler à l'amiable les différends ou litiges qui +viendraient à se produire par suite ou à l'occasion du Contrat. + +13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter +de leur survenance et sauf situation relevant d'une procédure d'urgence, +les différends ou litiges seront portés par la Partie la plus diligente +devant les Tribunaux compétents de Paris. + + +Version 1.0 du 2006-09-05. diff --git a/doc/licenses/gpl-2.0.txt b/doc/licenses/gpl-2.0.txt new file mode 100644 index 0000000..d8cf7d4 --- /dev/null +++ b/doc/licenses/gpl-2.0.txt @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/doc/licenses/gpl-3.0.txt b/doc/licenses/gpl-3.0.txt new file mode 100644 index 0000000..810fce6 --- /dev/null +++ b/doc/licenses/gpl-3.0.txt @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/doc/licenses/lgpl-2.1.txt b/doc/licenses/lgpl-2.1.txt new file mode 100644 index 0000000..20fb9c7 --- /dev/null +++ b/doc/licenses/lgpl-2.1.txt @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/doc/licenses/lgpl-3.0.txt b/doc/licenses/lgpl-3.0.txt new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/doc/licenses/lgpl-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/doc/linalg.xxml b/doc/linalg.xxml new file mode 100644 index 0000000..d8db0ec --- /dev/null +++ b/doc/linalg.xxml @@ -0,0 +1,54 @@ + + + +]> + + +Using BLAS, LAPACK and ARPACK for igraph matrices and graphs + +
+ + + + + + +
+ +
+ +
Matrix factorization, solving linear systems + + + +
+
Eigenvalues and eigenvectors of matrices + + + +
+
+ +
+ +
Data structures + + + + + + +
+ +
ARPACK solvers + + + +
+ +
+ +
diff --git a/doc/matrix.xxml b/doc/matrix.xxml new file mode 100644 index 0000000..e2fdf6a --- /dev/null +++ b/doc/matrix.xxml @@ -0,0 +1,131 @@ + + +]> + +
+Matrices + +
+ +
+ +
+ + + + + +
+ +
Initializing elements + + +
+ +
+ + + + + +
+ +
Matrix views + + +
+ +
Copying matrices + + + +
+ +
Operations on rows and columns + + + + + + + + + +
+ +
Matrix operations + + + + + + + + + + + +
+ +
Matrix comparisons + + + + + + + +
+ +
Combining matrices + + +
+ +
Finding minimum and maximum + + + + + + +
+ +
Matrix properties + + + + + + + + +
+ +
Searching for elements + + +
+ +
Resizing operations + + + + + + +
+ +
Complex matrix operations + + + + + + + +
+ +
diff --git a/doc/memory.xxml b/doc/memory.xxml new file mode 100644 index 0000000..851c03e --- /dev/null +++ b/doc/memory.xxml @@ -0,0 +1,22 @@ + + +]> + + +Memory (de)allocation + +
+ +
+ +
+Available allocation functions + + + + +
+ +
diff --git a/doc/motifs.xxml b/doc/motifs.xxml new file mode 100644 index 0000000..d44d67c --- /dev/null +++ b/doc/motifs.xxml @@ -0,0 +1,33 @@ + + +]> + + +Graph motifs, dyad census and triad census + + +This section deals with functions which find small induced subgraphs in a +graph. These were first defined for subgraphs of two and three vertices +by Holland and Leinhardt, and named dyad census and triad census. + + + + + +
Finding triangles + + + +
+ +
Graph motifs + + + + + +
+ +
diff --git a/doc/nongraph.xxml b/doc/nongraph.xxml new file mode 100644 index 0000000..47e81c1 --- /dev/null +++ b/doc/nongraph.xxml @@ -0,0 +1,40 @@ + + +]> + + +Non-graph related functions + +
igraph version number + +
+ +
Running mean of a time series + +
+ +
Random sampling from very long sequences + +
+ +
Random sampling of spatial points + + + +
+ +
Fitting power-law distributions to empirical data + + + +
+ +
Comparing floats with a tolerance + + + +
+ +
diff --git a/doc/operators.xxml b/doc/operators.xxml new file mode 100644 index 0000000..da575f7 --- /dev/null +++ b/doc/operators.xxml @@ -0,0 +1,42 @@ + + +]> + + +Graph operators + +
Union and intersection + + + + + + + +
+ +
Other set-like operators + + + +
+ +
Miscellaneous operators + + + + + + + + + + + + + +
+ +
diff --git a/doc/pmt.xml b/doc/pmt.xml new file mode 100644 index 0000000..089d096 --- /dev/null +++ b/doc/pmt.xml @@ -0,0 +1,146 @@ + + +]> + +
+About template types + +Some of the container types listed in this section are defined for +many base types. This is similar to templates in C++ and generics in +Ada, but it is implemented via preprocessor macros since the C language +cannot handle it. Here is the list of template types and the all base +types they currently support: + + +vector + Vector is currently defined for igraph_real_t, + igraph_int_t (int), char (char), + igraph_bool_t (bool), igraph_complex_t + (complex) and and void * (ptr). The default is + igraph_real_t. + + +matrix + Matrix is currently defined for igraph_real_t, + igraph_int_t (int), char (char), + igraph_bool_t (bool) and igraph_complex_t + (complex). The default is igraph_real_t. + + +array3 + Array3 is currently defined for igraph_real_t, + igraph_int_t (int), char (char) and + igraph_bool_t (bool). The default is + igraph_real_t. + + +stack + Stack is currently defined for igraph_real_t, + igraph_int_t (int), char (char) and + igraph_bool_t (bool). + The default is igraph_real_t. + + +double-ended queue + Dqueue is currently defined for igraph_real_t, + igraph_int_t (int), char (char) and + igraph_bool_t (bool). The default is + igraph_real_t. + + +heap + Heap is currently defined for igraph_real_t, + igraph_int_t (int), char (char). + In addition both maximum and minimum heaps are available. + The default is the igraph_real_t maximum heap. + + +list of vectors + Lists of vectors are currently defined for vectors holding + igraph_real_t and igraph_int_t (int). + The default is igraph_real_t. + + +list of matrices + Lists of matrices are currently defined for matrices holding + igraph_real_t only. + + + + + + +The name of the base element (in parentheses) is added to the function +names, except for the default type. + + + +Some examples: + + + + igraph_vector_t is a vector of + igraph_real_t elements. Its functions are + igraph_vector_init, + igraph_vector_destroy, + igraph_vector_sort, etc. + + + + igraph_vector_bool_t is a vector of + igraph_bool_t elements; initialize it with + igraph_vector_bool_init, destroy it with + igraph_vector_bool_destroy, etc. + + + + igraph_heap_t is a maximum heap with + igraph_real_t elements. The corresponding functions are + igraph_heap_init, + igraph_heap_pop, etc. + + + + igraph_heap_min_t is a minimum heap with + igraph_real_t elements. The corresponding functions are + called igraph_heap_min_init, + igraph_heap_min_pop, etc. + + + + igraph_heap_int_t is a maximum heap with igraph_int_t + elements. Its functions have the igraph_heap_int_ prefix. + + + + igraph_heap_min_int_t is a minimum heap containing + igraph_int_t elements. Its functions have the + igraph_heap_min_int_ prefix. + + + + igraph_vector_list_t is a list of (floating-point) vectors; each + element in this data structure is an igraph_vector_t. + Similarly, igraph_matrix_list_t is a list of (floating-point) + matrices; each element in this data structure is an igraph_matrix_t. + + + + igraph_vector_int_list_t is a list of integer vectors; each + element in this data structure is an igraph_vector_int_t. + + + + + + +Note that the VECTOR and the MATRIX macros can be used on all +vector and matrix types. VECTOR cannot be used +on lists of vectors, though, only on the individial +vectors in the list. + + +
diff --git a/doc/processes.xxml b/doc/processes.xxml new file mode 100644 index 0000000..931a5b8 --- /dev/null +++ b/doc/processes.xxml @@ -0,0 +1,16 @@ + + +]> + + +Processes on graphs + +
Epidemic models + + + +
+ +
diff --git a/doc/progress.xxml b/doc/progress.xxml new file mode 100644 index 0000000..d173757 --- /dev/null +++ b/doc/progress.xxml @@ -0,0 +1,39 @@ + + +]> + +
+Progress handlers + +
+ +
+ +
Setting up progress handlers + + + +
+ +
Invoking the progress handler + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/doc/psumtree.xxml b/doc/psumtree.xxml new file mode 100644 index 0000000..8869d4c --- /dev/null +++ b/doc/psumtree.xxml @@ -0,0 +1,20 @@ + + +]> + +
+Partial prefix sum trees + + + + + + + + + + + +
diff --git a/doc/random.xxml b/doc/random.xxml new file mode 100644 index 0000000..0c15604 --- /dev/null +++ b/doc/random.xxml @@ -0,0 +1,55 @@ + + +]> + + +Random numbers + + + +
The default random number generator + + +
+ +
Creating random number generators + + + + + + +
+ +
Generating random numbers + + + + + + + + + + +
+ +
Supported random number generators + +By default igraph uses the MT19937 generator. Prior to igraph version +0.6, the generator supplied by the standard C library was used. This +means the GLIBC2 generator on GNU libc 2 systems, and maybe the BSD RAND +generator on others. The RAND generator was removed due to poor statistical +properties in version 0.10. The PCG32 generator was added in version 0.10. + + + + + +
+ + + +
diff --git a/doc/separators.xxml b/doc/separators.xxml new file mode 100644 index 0000000..f6f9e88 --- /dev/null +++ b/doc/separators.xxml @@ -0,0 +1,16 @@ + + +]> + + +Vertex separators + + + + + + + + diff --git a/doc/sparsemat.xxml b/doc/sparsemat.xxml new file mode 100644 index 0000000..c9ca01d --- /dev/null +++ b/doc/sparsemat.xxml @@ -0,0 +1,108 @@ + + +]> + +
+Sparse matrices + +
+ +
+ +
Creating sparse matrix objects + + + + + + +
+ +
Query properties of a sparse matrix + + + + + + + + + + + + + + + + + + +
+ +
Operations on sparse matrices + + + + + + + + + + + + + + +
+ +
Operations on sparse matrix iterators + + + + + + + + +
+ +
Operations that change the internal representation + + +
+ +
Decompositions and solving linear systems + + + + + + + + + + + + + + +
+ +
Eigenvalues and eigenvectors + + +
+ +
Conversion to other data types + + +
+ +
Writing to a file, or to the screen + +
+ +
diff --git a/doc/spatial.xxml b/doc/spatial.xxml new file mode 100644 index 0000000..fecffa8 --- /dev/null +++ b/doc/spatial.xxml @@ -0,0 +1,32 @@ + + +]> + + +Spatial graphs + +
Metrics + +
+ +
Spatial graph generators + + + + + + + +
+ +
Properties of spatial graphs + +
+ +
Non-graph related spatial processing + +
+ +
diff --git a/doc/stack.xxml b/doc/stack.xxml new file mode 100644 index 0000000..d62430d --- /dev/null +++ b/doc/stack.xxml @@ -0,0 +1,20 @@ + + +]> + +
+Stacks + + + + + + + + + + + +
diff --git a/doc/status.xxml b/doc/status.xxml new file mode 100644 index 0000000..bd0784b --- /dev/null +++ b/doc/status.xxml @@ -0,0 +1,27 @@ + + +]> + +
+Status handlers + +
+ +
+ +
Setting up status handlers + + + +
+ +
Invoking the status handler + + + + +
+ +
diff --git a/doc/structural.xxml b/doc/structural.xxml new file mode 100644 index 0000000..5dc12cf --- /dev/null +++ b/doc/structural.xxml @@ -0,0 +1,256 @@ + + +]> + + +Structural properties of graphs + + + +
Basic properties + +
+ +
Sparsifiers + +
+ + + + + +
Efficiency measures + + + +
+ +
Neighborhood of a vertex + + + +
+ +
Local scan statistics + +
"Us" statistics + + + +
+
"Them" statistics + + + +
+
Pre-calculated subsets + + +
+
+ +
Graph components + + + + + + + + + + + +
+ +
Percolation + + + +
+ +
Degree sequences + + +
+ +
Centrality measures + + + + + + + + + + + + + + +
+ +
Range-limited centrality measures + + + + +
+ +
Subset-limited centrality measures + + +
+ +
Centralization + + + + + + + + + +
+ +
Similarity measures + + + + + + + + + +
+ +
Trees and forests + + + + + +
+ +
Transitivity or clustering coefficient + + + + + +
+ +
Directedness conversion + + +
+ +
Spectral properties + + + +
+ +
Non-simple graphs: Multiple and loop edges + + + + + + + + +
+ +
Mixing patterns and degree correlations + + + + + + + + + +
+ +
K-cores and k-trusses + + +
+ +
Maximum cardinality search and chordal graphs + + +
+ +
Matchings + + + +
+ +
Unfolding a graph into a tree + +
+ +
Other operations + + + + + + + + + + + +
+ +
Common types and constants + + +
+ +
diff --git a/doc/strvector.xxml b/doc/strvector.xxml new file mode 100644 index 0000000..4eea2e7 --- /dev/null +++ b/doc/strvector.xxml @@ -0,0 +1,34 @@ + + +]> + +
+String vectors + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/doc/threading.xxml b/doc/threading.xxml new file mode 100644 index 0000000..7813e44 --- /dev/null +++ b/doc/threading.xxml @@ -0,0 +1,45 @@ + + +]> + +
Using igraph in multi-threaded programs + + The igraph library is considered thread-safe if it has been compiled + with thread-local storage enabled, i.e. the IGRAPH_ENABLE_TLS + setting was toggled to ON and the current platform + supports this feature. To check whether an igraph build is thread-safe, use the + + IGRAPH_THREAD_SAFE + + macro. When linking to external versions of igraph's dependencies, it is + the responsibility of the user to check that these dependencies were also + compiled to be thread-safe. + + + + +
Thread-safe ARPACK library + +Note that igraph is only thread-safe if it was built with the internal +ARPACK library, i.e. the one that comes with igraph. The standard +ARPACK library is not thread-safe. + +
+ +
Thread-safety of random number generators + +The default random number generator that igraph uses is not +guaranteed to be thread-safe. You need to set a different random number generator +instance for every thread that you want to use igraph from. This is especially +important if you set the seed of the random number generator to ensure +reproducibility; sharing a random number generator between threads would break +reproducibility as the order in which the various threads are scheduled is +random, and therefore they would still receive random numbers in an unpredictable +order from the shared random number generator. + +
+ + +
diff --git a/doc/tutorial.xml b/doc/tutorial.xml new file mode 100644 index 0000000..6efe18e --- /dev/null +++ b/doc/tutorial.xml @@ -0,0 +1,327 @@ + + +]> + + +Tutorial + +
Compiling programs using igraph + + +The following short example program demonstrates the basic usage of +the igraph library. Save it into a file named +igraph_test.c. + + + + +This example illustrates a couple of points: + + + + +First, programs +using the igraph library should include the +igraph.h header +file. Note that while igraph installs several sub-headers, the organization of these may change +without notice. Only use igraph.h in your projects, not any of the sub-headers. + + +Second, the library must be initialized using +igraph_setup() +before use. + + +Third, igraph uses the +igraph_int_t type for integers instead of +int or long int, and it also uses the +igraph_real_t type for real numbers instead of +double. Depending on how igraph was compiled, and whether you are +using a 32-bit or 64-bit system, igraph_int_t may be a 32-bit +or 64-bit integer. + + +Fourth, igraph graph objects are represented by the igraph_t data +type. + + +Fifth, the igraph_erdos_renyi_game_gnm() +creates a graph and igraph_destroy() +destroys it, i.e. deallocates the memory associated to it. + + + + +For compiling this program you need a C compiler. Optionally, +CMake can be used to automate the compilation. + + +
+Compiling with CMake + + +It is convenient to use CMake because it can automatically discover the +necessary compilation flags on all operating systems. Many IDEs support +CMake, and can work with CMake projects directly. To create a CMake project +for this example program, create a file name CMakeLists.txt with the +following contents: + + +cmake_minimum_required(VERSION 3.18) +project(igraph_test) + +find_package(igraph REQUIRED) + +add_executable(igraph_test igraph_test.c) +target_link_libraries(igraph_test PUBLIC igraph::igraph) + + + + +To compile the project, create a new directory called build in +the root of the igraph source tree, and switch to it: + +mkdir build +cd build + + + + +Run CMake to configure the project: + +cmake .. + + + + +If igraph was installed at a non-standard location, specify its prefix +using the option. The prefix must be +the same directory that was specified as the +when compiling igraph. + + + +If configuration has succeeded, build the program using + +cmake --build . + + + +C++ must be enabled in igraph projects +Parts of igraph are implemented in C++; therefore, any CMake target that +depends on igraph should use the C++ linker. Furthermore, OpenMP support in +igraph works correctly only if C++ is enabled in the CMake project. The script +that finds igraph on the host machine will throw an error if C++ support is +not enabled in the CMake project. + +C++ support is enabled by default when no languages are explicitly +specified in CMake's project +command, e.g. project(igraph_test). If you do specify some languages explicitly, +make sure to also include CXX, e.g. project(igraph_test C CXX). + + + +
+ +
+Compiling without CMake + + +On most Unix-like systems, the default C compiler is called cc. +To compile the test program, you will need a command similar to the following: + +cc igraph_test.c -I/usr/local/include/igraph -L/usr/local/lib -ligraph -o igraph_test + + + + +The exact form depends on where igraph was installed on your +system, whether it was compiled as a shared or static library, and the external +libraries it was linked to. The directory after the switch +is the one containing the igraph.h file, while the one +following should contain the library file itself, usually a +file called libigraph.a (static library on macOS and +Linux), libigraph.so (shared library on Linux), +libigraph.dylib (shared library on macOS), +igraph.lib (static library on Windows) or +igraph.dll (shared library on Windows). If +igraph was compiled as a static library, it is also +necessary to manually link to all of its dependencies. + + + +If your system has the pkg-config utility you are +likely to get the necessary compile options by issuing the command + +pkg-config --libs --cflags igraph + +(if igraph was built as a shared library) or + +pkg-config --static --libs --cflags igraph + +(if igraph was built as a static library). + + +
+ +
+Running the program + + +On most systems, the executable can be run by simply typing its name like this: + +./igraph_test + +If you use dynamic linking and the igraph +library is not installed in a standard place, you may need to add its location to the +LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (macOS) +or PATH (Windows) environment variables. This is typically necessary +on Windows systems. + + +
+ +
+
Creating your first graphs + + +The functions generating graph objects are called graph +generators. Stochastic (i.e. randomized) graph generators are called +games. + + + +igraph can handle directed and undirected graphs. Most graph +generators are able to create both types of graphs and most other +functions are usually also capable of handling +both. E.g., igraph_get_shortest_paths(), +which calculates shortest paths from a vertex to other vertices, can calculate +directed or undirected paths. + + + +igraph has sophisticated ways for creating graphs. The simplest +graphs are deterministic regular structures like star graphs +(igraph_star()), +cycle graphs (igraph_cycle_graph()), lattices +(igraph_square_lattice()) or trees +(igraph_kary_tree()), and many more. + + + +The following example creates an undirected regular circular lattice, +adds some random edges to it and calculates the average length of +shortest paths between all pairs of vertices in the graph before and +after adding the random edges. (The message is that some random edges +can reduce path lengths a lot.) + + + + +This example illustrates some new points. igraph uses +igraph_vector_t +and its related types (igraph_vector_int_t, igraph_vector_bool_t +and so on) instead of plain C arrays. igraph_vector_t is superior to +regular arrays in almost every sense. Vectors are created by the +igraph_vector_init() +function and, like graphs, they should be destroyed if not +needed any more by calling +igraph_vector_destroy() +on them. A vector can be indexed by the +VECTOR() function +(right now it is a macro). The elements of a vector are of type igraph_real_t +for igraph_vector_t, +and of type igraph_int_t for igraph_vector_int_t. +As you might expect, igraph_vector_bool_t holds +igraph_bool_t values. Vectors can be resized and most igraph +functions returning the result in a vector automatically resize it to the size they need. + + + +igraph_square_lattice() +takes an integer vector argument specifying the dimensions of +the lattice. In this example we generate a 30x30 two dimensional +periodic lattice. See the documentation of +igraph_square_lattice() in +the reference manual for the other arguments. + + + +The vertices in a graph are identified by a vertex ID, an integer between +0 and n - 1, where n is the number of vertices in the graph. +The vertex count can be retrieved using igraph_vcount(), +as in the example. + + + +The igraph_add_edges() +function simply takes a graph and a vector of +vertex IDs defining the new edges. The first edge is between the first +two vertex IDs in the vector, the second edge is between the second +two, etc. This way we add ten random edges to the lattice. + + + +Note that this example program may add loop edges, edges +pointing a vertex to itself, or multiple edges, more than one edge +between the same pair of vertices. +igraph_t can of course represent loops and multiple edges, although some +routines expect simple graphs, i.e. graphs which contain neither of these. This is because some +structural properties are ill-defined for non-simple graphs. Loop and multi-edges can be removed by calling +igraph_simplify(). + + +
+
Calculating various properties of graphs + +In our next example we will calculate various centrality measures in a +friendship graph. The friendship graph is from the famous Zachary karate +club study. (Do a web search on "Zachary karate" if you want to know more about +this.) Centrality measures quantify how central is the position of +individual vertices in the graph. + + + + +This example demonstrates some new operations. First of all, it shows a +way to create a graph a list of edges stored in a plain C array. +Function igraph_vector_view() +creates a view of a C array. It does not copy any data, +which means that you must not call +igraph_vector_destroy() +on a vector created this way. This vector is then used to create the +undirected graph. + + + +Then the degree, closeness and betweenness centrality of the vertices +is calculated and the highest values are printed. Note that the vector +result, into which these functions will write their +result, must be initialized first, and also that the functions resize +it to be able to hold the result. + + + +Notice that in order to print values of type igraph_int_t, +we used the IGRAPH_PRId format macro constant. This +macro is similar to the standard PRI constants defined +in stdint.h, and expands to the correct printf +format specifier on each platform that igraph supports. + + + +The igraph_vss_all() argument +tells the functions to calculate the property for every vertex in the graph. +It is shorthand for a vertex selector, represented by type +igraph_vs_t. +Vertex selectors help perform operations on a subset of vertices. +You can read more about them in one +of the following chapters. + +
+ +
diff --git a/doc/vector.xxml b/doc/vector.xxml new file mode 100644 index 0000000..2fe6553 --- /dev/null +++ b/doc/vector.xxml @@ -0,0 +1,176 @@ + + +]> + +
+Vectors + +
+ +
+ +
+ + + + + + +
+ +
Initializing elements + + + +
+ +
+ + + + + + + + +
+ +
Vector views + +
+ +
Copying vectors + + + + +
+ +
Exchanging elements + + + + + + +
+ +
Vector operations + + + + + + + +
+ +
Vector comparisons + + + + + + + + + + + + +
+ +
Finding minimum and maximum + + + + + + +
+ +
Vector properties + + + + + + + + + + +
+ +
Searching for elements + + + + + +
+ +
Resizing operations + + + + + + + + + +
+ +
Complex vector operations + + + + + + + +
+ +
Sorting + + + +
+ +
Set operations on sorted vectors + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/doc/vectorlist.xxml b/doc/vectorlist.xxml new file mode 100644 index 0000000..6934226 --- /dev/null +++ b/doc/vectorlist.xxml @@ -0,0 +1,61 @@ + + +]> + +
+Lists of vectors, matrices and graphs + +
+ +
+ +
+ + + + +
+ +
+ + + + + +
+ +
Vector properties + + + +
+ +
Resizing operations + + + + + + + + + + + + + + + +
+ +
Sorting and reordering + + + + + +
+ +
diff --git a/doc/version-greater-or-equal.xsl b/doc/version-greater-or-equal.xsl new file mode 100644 index 0000000..8edcd81 --- /dev/null +++ b/doc/version-greater-or-equal.xsl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 0 + + + + + + + + + + + 0 + + + 1 + + + + + + diff --git a/doc/visitors.xxml b/doc/visitors.xxml new file mode 100644 index 0000000..470e45b --- /dev/null +++ b/doc/visitors.xxml @@ -0,0 +1,25 @@ + + +]> + + +Graph visitors + + + + + +
Random walks + +
+ +
diff --git a/etc/cmake/BuildType.cmake b/etc/cmake/BuildType.cmake new file mode 100644 index 0000000..76dd902 --- /dev/null +++ b/etc/cmake/BuildType.cmake @@ -0,0 +1,12 @@ +# Taken from https://blog.kitware.com/cmake-and-the-default-build-type/ + +# Set the default build type to "Release" +set(default_build_type "Release") + +get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT isMultiConfig AND NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() diff --git a/etc/cmake/CheckTLSSupport.cmake b/etc/cmake/CheckTLSSupport.cmake new file mode 100644 index 0000000..5a72791 --- /dev/null +++ b/etc/cmake/CheckTLSSupport.cmake @@ -0,0 +1,36 @@ +include(CheckCSourceCompiles) +include(CMakePushCheckState) + +macro(check_tls_support VAR) + if(NOT DEFINED "${VAR}") + cmake_push_check_state() + set(CMAKE_REQUIRED_QUIET 1) + + check_c_source_compiles(" + __thread int tls; + + int main(void) { + return 0; + }" HAVE_GCC_TLS) + + if(HAVE_GCC_TLS) + message(STATUS "Thread-local storage: supported (__thread)") + set(${VAR} "__thread" CACHE INTERNAL "Thread-local storage support keyword in compiler") + else() + check_c_source_compiles(" + __declspec(thread) int tls; + + int main(void) { + return 0; + }" HAVE_MSVC_TLS) + if(HAVE_MSVC_TLS) + message(STATUS "Thread-local storage: supported (__declspec(thread))") + set(${VAR} "__declspec(thread)" CACHE INTERNAL "Thread-local storage keyword in compiler") + else() + message(STATUS "Thread-local storage: not supported") + set(${VAR} "" CACHE INTERNAL "Thread-local storage keyword in compiler") + endif() + endif() + cmake_pop_check_state() + endif() +endmacro() diff --git a/etc/cmake/CodeCoverage.cmake b/etc/cmake/CodeCoverage.cmake new file mode 100644 index 0000000..9b9e64d --- /dev/null +++ b/etc/cmake/CodeCoverage.cmake @@ -0,0 +1,750 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND true + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND true + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND true + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND true + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/etc/cmake/FindARPACK.cmake b/etc/cmake/FindARPACK.cmake new file mode 100644 index 0000000..26a9d2d --- /dev/null +++ b/etc/cmake/FindARPACK.cmake @@ -0,0 +1,85 @@ +# https://raw.githubusercontent.com/dune-project/dune-istl/master/cmake/modules/FindARPACK.cmake +# +# This file is taken from: +# +# DUNE, the Distributed and Unified Numerics Environment +# GPLv2 licensed +# +# .. cmake_module:: +# +# Module that checks whether ARPACK is available and usable. +# +# Variables used by this module which you may want to set: +# +# :ref:`ARPACK_ROOT` +# Path list to search for ARPACK. +# +# Sets the following variables: +# +# :code:`ARPACK_FOUND` +# True if ARPACK available. +# +# :code:`ARPACK_LIBRARIES` +# Link against these libraries to use ARPACK. +# +# .. cmake_variable:: ARPACK_ROOT +# +# You may set this variable to have :ref:`FindARPACK` look +# for the ARPACK package in the given path before inspecting +# system paths. +# + +# look for library, only at positions given by the user +find_library(ARPACK_LIBRARY + NAMES "arpack" + PATHS ${ARPACK_PREFIX} ${ARPACK_ROOT} + PATH_SUFFIXES "lib" "lib32" "lib64" + NO_DEFAULT_PATH +) + +# look for library files, including default paths +find_library(ARPACK_LIBRARY + NAMES "arpack" + PATH_SUFFIXES "lib" "lib32" "lib64" +) + +# check header usability +include(CMakePushCheckState) +cmake_push_check_state() + +# we need if clauses here because variable is set variable-NOTFOUND if the +# searches above were not successful; without them CMake print errors like: +# "CMake Error: The following variables are used in this project, but they +# are set to NOTFOUND. Please set them or make sure they are set and tested +# correctly in the CMake files." +if(ARPACK_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${ARPACK_LIBRARY}) +endif() + +# end of header usability check +cmake_pop_check_state() + +# behave like a CMake module is supposed to behave +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + "ARPACK" + DEFAULT_MSG + ARPACK_LIBRARY +) + +# hide the introduced cmake cached variables in cmake GUIs +mark_as_advanced(ARPACK_LIBRARY) + +# if headers are found, store results +if(ARPACK_FOUND) + set(ARPACK_LIBRARIES ${ARPACK_LIBRARY}) + # log result + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log + "Determing location of ARPACK succeeded:\n" + "Libraries to link against: ${ARPACK_LIBRARIES}\n\n") +else() + # log errornous result + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Determing location of ARPACK failed:\n" + "Libraries to link against: ${ARPACK_LIBRARIES}\n\n") +endif() diff --git a/etc/cmake/FindGLPK.cmake b/etc/cmake/FindGLPK.cmake new file mode 100644 index 0000000..c2e887a --- /dev/null +++ b/etc/cmake/FindGLPK.cmake @@ -0,0 +1,81 @@ +#[=======================================================================[.rst: +FindGLPK +-------- + +Finds the GLPK library. + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``GLPK_FOUND`` + True if the system has the GLPK library. +``GLPK_VERSION`` + The version of the GLPK library which was found. +``GLPK_INCLUDE_DIRS`` + Include directories needed to use Foo. +``GLPK_LIBRARIES`` + Libraries needed to link to Foo. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``GLPK_INCLUDE_DIR`` + The directory containing ``glpk.h``. +``GLPK_LIBRARY`` + The path to the GLPK library. + +#]=======================================================================] + +find_path(GLPK_INCLUDE_DIR + NAMES glpk.h +) + +find_library(GLPK_LIBRARY + NAMES glpk +) + +# parse version from header +if(GLPK_INCLUDE_DIR) + set(GLPK_VERSION_FILE ${GLPK_INCLUDE_DIR}/glpk.h) + file(READ ${GLPK_VERSION_FILE} GLPK_VERSION_FILE_CONTENTS) + + string(REGEX MATCH "#define[ ]+GLP_MAJOR_VERSION[ ]+[0-9]+" + GLPK_VERSION_MAJOR "${GLPK_VERSION_FILE_CONTENTS}") + string(REGEX REPLACE "#define[ ]+GLP_MAJOR_VERSION[ ]+([0-9]+)" "\\1" + GLPK_VERSION_MAJOR "${GLPK_VERSION_MAJOR}") + + string(REGEX MATCH "#define[ ]+GLP_MINOR_VERSION[ ]+[0-9]+" + GLPK_VERSION_MINOR "${GLPK_VERSION_FILE_CONTENTS}") + string(REGEX REPLACE "#define[ ]+GLP_MINOR_VERSION[ ]+([0-9]+)" "\\1" + GLPK_VERSION_MINOR "${GLPK_VERSION_MINOR}") + + set(GLPK_VERSION "${GLPK_VERSION_MAJOR}.${GLPK_VERSION_MINOR}") + + # compatibility variables + set(GLPK_VERSION_STRING "${GLPK_VERSION}") +endif() + +# behave like a CMake module is supposed to behave +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GLPK + FOUND_VAR GLPK_FOUND + REQUIRED_VARS + GLPK_LIBRARY + GLPK_INCLUDE_DIR + VERSION_VAR GLPK_VERSION +) + +# hide the introduced cmake cached variables in cmake GUIs +mark_as_advanced( + GLPK_INCLUDE_DIR + GLPK_LIBRARY +) + +if(GLPK_FOUND) + set(GLPK_LIBRARIES ${GLPK_LIBRARY}) + set(GLPK_INCLUDE_DIRS ${GLPK_INCLUDE_DIR}) +endif() diff --git a/etc/cmake/FindGMP.cmake b/etc/cmake/FindGMP.cmake new file mode 100644 index 0000000..f958a6c --- /dev/null +++ b/etc/cmake/FindGMP.cmake @@ -0,0 +1,36 @@ +# Inspired by http://code.google.com/p/origin/source/browse/trunk/cmake/FindGMP.cmake + +# Copyright (c) 2008-2010 Kent State University +# Copyright (c) 2011-2012 Texas A&M University +# +# This file is distributed under the MIT License. See +# http://www.opensource.org/licenses/mit-license.php for terms and conditions. +# +# Some modifications made by Tamas Nepusz to ensure that the module fits better +# with the de facto conventions of FindXXX.cmake scripts + +find_path(GMP_INCLUDE_DIR + NAMES gmp.h +) + +find_library(GMP_LIBRARY + NAMES gmp +) + +# behave like a CMake module is supposed to behave +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + "GMP" + DEFAULT_MSG + GMP_LIBRARY + GMP_INCLUDE_DIR +) + +# hide the introduced cmake cached variables in cmake GUIs +mark_as_advanced(GMP_INCLUDE_DIR) +mark_as_advanced(GMP_LIBRARY) + +if(GMP_FOUND) + set(GMP_LIBRARIES ${GMP_LIBRARY}) + set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR}) +endif() diff --git a/etc/cmake/FindPLFIT.cmake b/etc/cmake/FindPLFIT.cmake new file mode 100644 index 0000000..0136937 --- /dev/null +++ b/etc/cmake/FindPLFIT.cmake @@ -0,0 +1,63 @@ +# Inspired by http://code.google.com/p/origin/source/browse/trunk/cmake/FindGMP.cmake + +# Copyright (c) 2021 Tamas Nepusz +# +# This file is distributed under the MIT License. See +# http://www.opensource.org/licenses/mit-license.php for terms and conditions. +# +# Some modifications made by Tamas Nepusz to ensure that the module fits better +# with the de facto conventions of FindXXX.cmake scripts + +find_path(PLFIT_INCLUDE_DIR + NAMES plfit.h + PATH_SUFFIXES plfit +) + +find_library(PLFIT_LIBRARY + NAMES plfit +) + +# parse version from header +if(PLFIT_INCLUDE_DIR) + set(PLFIT_VERSION_FILE ${PLFIT_INCLUDE_DIR}/plfit_version.h) + file(READ ${PLFIT_VERSION_FILE} PLFIT_VERSION_FILE_CONTENTS) + + string(REGEX MATCH "#define[ ]+PLFIT_VERSION_MAJOR[ ]+[0-9]+" + PLFIT_VERSION_MAJOR "${PLFIT_VERSION_FILE_CONTENTS}") + string(REGEX REPLACE "#define[ ]+PLFIT_VERSION_MAJOR[ ]+([0-9]+)" "\\1" + PLFIT_VERSION_MAJOR "${PLFIT_VERSION_MAJOR}") + + string(REGEX MATCH "#define[ ]+PLFIT_VERSION_MINOR[ ]+[0-9]+" + PLFIT_VERSION_MINOR "${PLFIT_VERSION_FILE_CONTENTS}") + string(REGEX REPLACE "#define[ ]+PLFIT_VERSION_MINOR[ ]+([0-9]+)" "\\1" + PLFIT_VERSION_MINOR "${PLFIT_VERSION_MINOR}") + + string(REGEX MATCH "#define[ ]+PLFIT_VERSION_PATCH[ ]+[0-9]+" + PLFIT_VERSION_PATCH "${PLFIT_VERSION_FILE_CONTENTS}") + string(REGEX REPLACE "#define[ ]+PLFIT_VERSION_PATCH[ ]+([0-9]+)" "\\1" + PLFIT_VERSION_PATCH "${PLFIT_VERSION_PATCH}") + + set(PLFIT_VERSION "${PLFIT_VERSION_MAJOR}.${PLFIT_VERSION_MINOR}.${PLFIT_VERSION_PATCH}") + + # compatibility variables + set(PLFIT_VERSION_STRING "${PLFIT_VERSION}") +endif() + +# behave like a CMake module is supposed to behave +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PLFIT + FOUND_VAR PLFIT_FOUND + REQUIRED_VARS + PLFIT_LIBRARY + PLFIT_INCLUDE_DIR + VERSION_VAR PLFIT_VERSION +) + +# hide the introduced cmake cached variables in cmake GUIs +mark_as_advanced(PLFIT_INCLUDE_DIR) +mark_as_advanced(PLFIT_LIBRARY) + +if(PLFIT_FOUND) + set(PLFIT_LIBRARIES ${PLFIT_LIBRARY}) + set(PLFIT_INCLUDE_DIRS ${PLFIT_INCLUDE_DIR}) +endif() diff --git a/etc/cmake/GetGitRevisionDescription.cmake b/etc/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 0000000..2ebfd40 --- /dev/null +++ b/etc/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,166 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + diff-index --quiet HEAD -- + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} "CLEAN" PARENT_SCOPE) + else() + set(${_var} "DIRTY" PARENT_SCOPE) + endif() +endfunction() diff --git a/etc/cmake/GetGitRevisionDescription.cmake.in b/etc/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000..6d8b708 --- /dev/null +++ b/etc/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,41 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/etc/cmake/JoinPaths.cmake b/etc/cmake/JoinPaths.cmake new file mode 100644 index 0000000..32d6d66 --- /dev/null +++ b/etc/cmake/JoinPaths.cmake @@ -0,0 +1,26 @@ +# This module provides function for joining paths +# known from from most languages +# +# Original license: +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Explicit permission given to distribute this module under +# the terms of the project as described in /LICENSE.rst. +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/etc/cmake/PadString.cmake b/etc/cmake/PadString.cmake new file mode 100644 index 0000000..9c60dfc --- /dev/null +++ b/etc/cmake/PadString.cmake @@ -0,0 +1,39 @@ +# ------------------------------------------------------------------------------ +# Macro PAD_STRING +# +# This function pads a string on the left side with a specified character to +# reach the specified length. If the string length is already long enough or +# longer, the string will not be modified. +# +# PAD_STRING(OUT_VARIABLE DESIRED_LENGTH FILL_CHAR VALUE) +# +# OUT_VARIABLE: name of the resulting variable to create +# DESIRED_LENGTH: desired length of the generated string +# FILL_CHAR: character to use for padding +# VALUE: string to pad +# +# Copyright (C) 2011 by Johannes Wienke +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General +# Public License as published by the Free Software Foundation; +# either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# ------------------------------------------------------------------------------ +FUNCTION(PAD_STRING OUT_VARIABLE DESIRED_LENGTH FILL_CHAR VALUE) + STRING(LENGTH "${VALUE}" VALUE_LENGTH) + MATH(EXPR REQUIRED_PADS "${DESIRED_LENGTH} - ${VALUE_LENGTH}") + SET(PAD ${VALUE}) + IF(REQUIRED_PADS GREATER 0) + MATH(EXPR REQUIRED_MINUS_ONE "${REQUIRED_PADS} - 1") + FOREACH(FOO RANGE ${REQUIRED_MINUS_ONE}) + SET(PAD "${FILL_CHAR}${PAD}") + ENDFOREACH() + ENDIF() + SET(${OUT_VARIABLE} "${PAD}" PARENT_SCOPE) +ENDFUNCTION() diff --git a/etc/cmake/PreventInSourceBuilds.cmake b/etc/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 0000000..c157d75 --- /dev/null +++ b/etc/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,34 @@ +# Original source of this script: +# https://raw.githubusercontent.com/InsightSoftwareConsortium/ITK/master/CMake/PreventInSourceBuilds.cmake +# +# Thanks to the ITK project! +# +# This function will prevent in-source builds +function(AssureOutOfSourceBuilds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("##########################################################################") + message("# igraph should not be configured & built in the igraph source directory") + message("# You must run cmake in a build directory.") + message("#") + message("# Example:") + message("# mkdir build; cd build; cmake ..; make") + message("#") + message("# NOTE: Given that you already tried to make an in-source build") + message("# CMake have already created several files & directories") + message("# in your source tree. If you are using git, run 'git clean -dfx'") + message("# to start from scratch. If you don't have git, remove") + message("# CMakeCache.txt and the CMakeFiles/ folder from the top of") + message("# the source tree.") + message("#") + message("##########################################################################") + message("") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +AssureOutOfSourceBuilds() diff --git a/etc/cmake/UseCCacheWhenInstalled.cmake b/etc/cmake/UseCCacheWhenInstalled.cmake new file mode 100644 index 0000000..68a26d1 --- /dev/null +++ b/etc/cmake/UseCCacheWhenInstalled.cmake @@ -0,0 +1,7 @@ +option(USE_CCACHE "Use ccache to speed up compilation if it is installed" ON) +if(USE_CCACHE) + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + endif() +endif() diff --git a/etc/cmake/attribute_support.cmake b/etc/cmake/attribute_support.cmake new file mode 100644 index 0000000..4b1d392 --- /dev/null +++ b/etc/cmake/attribute_support.cmake @@ -0,0 +1,27 @@ + +# Detect if certain attributes are supported by the compiler +# The result will be used to set macros in include/igraph_config.h + +# GCC-style enum value deprecation + +include(CheckCSourceCompiles) +include(CMakePushCheckState) + +# Only check with Clang and GCC as we assume that the -Werror option is supported +# For other compilers, assume that the attribute is unsupported. +if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") + cmake_push_check_state() + # Require compiling with no warning: + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror") + check_c_source_compiles( + "enum { A __attribute__ ((deprecated)) = 0 }; int main(void) { return 0; }" + COMPILER_HAS_DEPRECATED_ENUMVAL_ATTR + ) + cmake_pop_check_state() +else() + set(COMPILER_HAS_DEPRECATED_ENUMVAL_ATTR FALSE) +endif() + +if(COMPILER_HAS_DEPRECATED_ENUMVAL_ATTR) + set(IGRAPH_DEPRECATED_ENUMVAL "__attribute__ ((deprecated))") +endif() diff --git a/etc/cmake/benchmark_helpers.cmake b/etc/cmake/benchmark_helpers.cmake new file mode 100644 index 0000000..fa55a53 --- /dev/null +++ b/etc/cmake/benchmark_helpers.cmake @@ -0,0 +1,43 @@ +include(CMakeParseArguments) + +function(add_benchmark NAME NAMESPACE) + set(TARGET_NAME ${NAMESPACE}_${NAME}) + + add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL ${PROJECT_SOURCE_DIR}/tests/benchmarks/${NAME}.c) + use_all_warnings(${TARGET_NAME}) + add_dependencies(build_benchmarks ${TARGET_NAME}) + target_link_libraries(${TARGET_NAME} PRIVATE igraph) + + # Some benchmarks include plfit_sampling.h from plfit. The following ensures + # that the correct version is included, depending on whether plfit is vendored + target_include_directories( + ${TARGET_NAME} PRIVATE + $<$:$> + $<$:${PLFIT_INCLUDE_DIR}> + ) + + if (MSVC) + # Add MSVC-specific include path for some headers that are missing on Windows + target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/msvc/include) + endif() + + add_custom_command( + TARGET benchmark + POST_BUILD + COMMAND ${TARGET_NAME} + COMMENT "Running benchmark: ${NAME}" + USES_TERMINAL + ) +endfunction() + +function(add_benchmarks) + cmake_parse_arguments( + PARSED "" "" "NAMES;LIBRARIES" ${ARGN} + ) + foreach(NAME ${PARSED_NAMES}) + add_benchmark(${NAME} benchmark) + if(PARSED_LIBRARIES) + target_link_libraries(benchmark_${NAME} PRIVATE ${PARSED_LIBRARIES}) + endif() + endforeach() +endfunction() diff --git a/etc/cmake/bit_operations_support.cmake b/etc/cmake/bit_operations_support.cmake new file mode 100644 index 0000000..5357519 --- /dev/null +++ b/etc/cmake/bit_operations_support.cmake @@ -0,0 +1,92 @@ +include(CheckCXXSourceCompiles) +include(CheckTypeSize) + +cmake_push_check_state(RESET) + +# Check whether the compiler supports the __popcnt64() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long long a = 0xDEADBEEF; + volatile unsigned long long b; + b = __popcnt64(a); + return 0; + } + " + HAVE__POPCNT64 +) + +# Check whether the compiler supports the __popcnt() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long a = 0xDEADBEEF; + volatile unsigned long long b; + b = __popcnt(a); + return 0; + } + " + HAVE__POPCNT +) + +# Check whether the compiler supports the _BitScanForward64() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long long a = 0xDEADBEEF; + unsigned long b; + volatile unsigned long c; + c = _BitScanForward64(&b, a) ? b : 64; + return 0; + } + " + HAVE__BITSCANFORWARD64 +) + +# Check whether the compiler supports the _BitScanForward() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long a = 0xDEADBEEF, b; + volatile unsigned long c; + c = _BitScanForward(&b, a) ? b : 64; + return 0; + } + " + HAVE__BITSCANFORWARD +) + +# Check whether the compiler supports the _BitScanReverse64() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long long a = 0xDEADBEEF; + unsigned long b; + volatile unsigned long c; + c = _BitScanReverse64(&b, a) ? b : 64; + return 0; + } + " + HAVE__BITSCANREVERSE64 +) + +# Check whether the compiler supports the _BitScanReverse() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long a = 0xDEADBEEF, b; + volatile unsigned long c; + c = _BitScanReverse(&b, a) ? b : 64; + return 0; + } + " + HAVE__BITSCANREVERSE +) + +cmake_pop_check_state() diff --git a/etc/cmake/compilers.cmake b/etc/cmake/compilers.cmake new file mode 100644 index 0000000..c25a1e7 --- /dev/null +++ b/etc/cmake/compilers.cmake @@ -0,0 +1,131 @@ +include(CheckCCompilerFlag) + +# Enable POSIX features. This needs to be set here instead of in source files so +# that it affects CMake-based feature tests. +# +# See: +# - https://pubs.opengroup.org/onlinepubs/007904875/functions/xsh_chap02_02.html +# - https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html +add_compile_definitions(_POSIX_C_SOURCE=200809L) + +# On some Apple systems, with some compilers, defining _POSIX_C_SOURCE causes compilation +# failures when including C++ headers. Defining _DARWIN_C_SOURCE explicitly usually fixes +# this. Refs: https://trac.macports.org/ticket/60655 and https://trac.macports.org/ticket/73145 +# This is claimed to be due to _POSIX_C_SOURCE disabling APIs that C++ headers rely on +# on some systems. _DARWIN_C_SOURCE re-enables these APIs, but _GNU_SOURCE alone does not +# necessarily do so. Using different global defined for C and C++ is problematic with CMake, +# so instead of restricting _POSIX_C_SOURCE to C, we define _DARWIN_C_SOURCE. +if(APPLE) + add_compile_definitions(_DARWIN_C_SOURCE) +endif() + +if(MSVC) + add_compile_options(/FS) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) # necessary to compile for UWP +endif() + +if(NOT MSVC) + # Even though we will later use 'no-unknown-warning-option', we perform the test for + # 'unknown-warning-option', without the 'no-' prefix. This is necessary because GCC + # will accept any warning option starting with 'no-', and will not error, yet it still + # prints a message about the unrecognized option. + check_c_compiler_flag("-Wunknown-warning-option" COMPILER_SUPPORTS_UNKNOWN_WARNING_OPTION_FLAG) +endif() + +set( + IGRAPH_WARNINGS_AS_ERRORS ON CACHE BOOL + "Treat warnings as errors with GCC-like compilers" +) + +option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." FALSE) +if(FORCE_COLORED_OUTPUT) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(-fdiagnostics-color=always) + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options(-fcolor-diagnostics) + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + add_compile_options(-fcolor-diagnostics) + endif() +endif() + +macro(use_all_warnings TARGET_NAME) + if(MSVC) + target_compile_options(${TARGET_NAME} PRIVATE + /W4 # enable most warnings, then disable: + /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data + /wd4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data + /wd4996 # deprecated functions, e.g. 'sprintf': This function or variable may be unsafe. Consider using sprintf_s instead. + /wd4456 # declaration of 'identifier' hides previous local declaration + /wd4800 # forcing value to 'true' or 'false' (performance warning) + /wd4204 # nonstandard extension used: non-constant aggregate initializer + /wd4701 # potentially uninitialized local variable + /wd4221 # nonstandard extension used: '...': cannot be initialized using address of automatic variable '...' + /wd4127 # conditional expression is constant + /wd4702 # unreachable code + ) + else() + # Notes: + # GCC does not complain when encountering an unsupported "no"-prefixed warning option such as -Wno-foo. + # Clang does complain, but these complaints can be silenced with -Wno-unknown-warning-option. + # Therefore it is generally safe to use -Wno-... options that are only supported by recent GCC/Clang. + target_compile_options(${TARGET_NAME} PRIVATE + # GCC-style compilers: + $<$: + $<$:-Werror> + -Wall -Wextra -pedantic + -Wstrict-prototypes + -Wno-unused-function -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-sign-compare -Wno-constant-logical-operand + > + $<$:-Wno-unknown-warning-option> + # Intel compiler: + $<$: + # disable #279: controlling expression is constant; affecting assert(condition && "message") + # disable #592: variable "var" is used before its value is set; affecting IGRAPH_UNUSED + # disable #10148: warning option not supported, as a replacement for -Wno-unknown-warning-option + -wd279 -wd592 -diag-disable=remark -diag-disable=10148 + > + # Intel LLVM: + $<$: + -fp-model=precise # The default 'fast' mode is not compatible with igraph's extensive use of NaN/Inf + > + ) + endif() +endmacro() + +# Helper function to add preprocesor definition of IGRAPH_FILE_BASENAME +# to pass the filename without directory path for debugging use. +# +# Example: +# +# define_file_basename_for_sources(my_target) +# +# Will add -DIGRAPH_FILE_BASENAME="filename" for each source file depended +# on by my_target, where filename is the name of the file. +# +# Source: https://stackoverflow.com/a/27990434/156771 +function(define_file_basename_for_sources targetname) + get_target_property(source_files "${targetname}" SOURCES) + get_target_property(source_dir "${targetname}" SOURCE_DIR) + foreach(sourcefile ${source_files}) + # Turn relative paths into absolute + get_filename_component(source_full_path "${sourcefile}" ABSOLUTE BASE_DIR "${source_dir}") + + # Figure out whether the relative path from the source or the build folder + # is shorter + file(RELATIVE_PATH source_rel_path "${PROJECT_SOURCE_DIR}" "${source_full_path}") + file(RELATIVE_PATH binary_rel_path "${PROJECT_BINARY_DIR}" "${source_full_path}") + string(LENGTH "${source_rel_path}" source_rel_path_length) + string(LENGTH "${binary_rel_path}" binary_rel_path_length) + if(binary_rel_path_length LESS source_rel_path_length) + set(basename "${binary_rel_path}") + else() + set(basename "${source_rel_path}") + endif() + + # Add the IGRAPH_FILE_BASENAME=filename compile definition to the source file + set_property( + SOURCE "${sourcefile}" APPEND + PROPERTY COMPILE_DEFINITIONS "IGRAPH_FILE_BASENAME=\"${basename}\"" + ) + endforeach() +endfunction() diff --git a/etc/cmake/cpack_install_script.cmake b/etc/cmake/cpack_install_script.cmake new file mode 100644 index 0000000..48af65c --- /dev/null +++ b/etc/cmake/cpack_install_script.cmake @@ -0,0 +1,87 @@ +# Custom CPack install script that allows us to whitelist files to be copied +# to the tarball from the root directory, instead of copying the entire root +# directory recursively + +if(CPACK_SOURCE_INSTALLED_DIRECTORIES) + # Make sure that the parser sources are built + execute_process( + COMMAND "${CMAKE_COMMAND}" + --build "${CPACK_PACKAGE_DIRECTORY}" + --target parsersources + RESULT_VARIABLE EXIT_CODE + ) + if(NOT EXIT_CODE EQUAL 0) + message(FATAL_ERROR "Failed to build the parser sources.") + endif() + + # Generate a version file in the build folder if we don't have one in the + # source folder + if(EXISTS "${SOURCE_DIR}/IGRAPH_VERSION") + set(IGRAPH_VERSION_FILE "${SOURCE_DIR}/IGRAPH_VERSION") + else() + execute_process( + COMMAND "${CMAKE_COMMAND}" + --build "${CPACK_PACKAGE_DIRECTORY}" + --target versionfile + RESULT_VARIABLE EXIT_CODE + ) + if(NOT EXIT_CODE EQUAL 0) + message(FATAL_ERROR "Failed to determine the version number of igraph that is being packaged.") + endif() + set(IGRAPH_VERSION_FILE "${CPACK_PACKAGE_DIRECTORY}/IGRAPH_VERSION") + endif() + + list(GET CPACK_BUILD_SOURCE_DIRS 0 SOURCE_DIR) + # This branch runs only if CPack generates the source package, and within + # this branch, CMAKE_CURRENT_BINARY_DIR refers to the root of the staging + # area where the tarball is assembled + file(GLOB FILES_TO_COPY "${SOURCE_DIR}/*.md") + file( + INSTALL ${FILES_TO_COPY} + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + ) + file( + INSTALL + "${SOURCE_DIR}/AUTHORS" + "${SOURCE_DIR}/CMakeLists.txt" + "${SOURCE_DIR}/CONTRIBUTORS.txt" + "${SOURCE_DIR}/COPYING" + "${SOURCE_DIR}/ChangeLog" + "${SOURCE_DIR}/INSTALL" + "${SOURCE_DIR}/NEWS" + "${SOURCE_DIR}/ONEWS" + "${SOURCE_DIR}/igraph.pc.in" + "${IGRAPH_VERSION_FILE}" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + ) + file( + INSTALL + "${SOURCE_DIR}/src/config.h.in" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/src" + ) + file( + INSTALL + "${CPACK_PACKAGE_DIRECTORY}/src/io/parsers" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/src/io" + ) + file( + INSTALL + "${SOURCE_DIR}/tools/removeexamples.py" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/tools" + ) + file( + INSTALL + "${SOURCE_DIR}/tools/strip_licenses_from_examples.py" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/tools" + ) + file( + INSTALL + "${CPACK_PACKAGE_DIRECTORY}/doc/html" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/doc" + ) + file( + INSTALL + "${CPACK_PACKAGE_DIRECTORY}/doc/igraph-docs.info" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/doc" + ) +endif() diff --git a/etc/cmake/create_igraph_version_file.cmake b/etc/cmake/create_igraph_version_file.cmake new file mode 100644 index 0000000..a2f5675 --- /dev/null +++ b/etc/cmake/create_igraph_version_file.cmake @@ -0,0 +1,8 @@ +# CMake script that generates the IGRAPH_VERSION file in the build folder +# +# Script variables that need to be set before calling it via "cmake -P": +# +# * IGRAPH_VERSION should be set to the exact version number +# * VERSION_FILE_PATH should be set to the name of the version file + +FILE(WRITE "${VERSION_FILE_PATH}" "${IGRAPH_VERSION}") diff --git a/etc/cmake/debugging.cmake b/etc/cmake/debugging.cmake new file mode 100644 index 0000000..4cb2a63 --- /dev/null +++ b/etc/cmake/debugging.cmake @@ -0,0 +1,7 @@ +set( + IGRAPH_VERIFY_FINALLY_STACK + "" + CACHE + BOOL + "Verify that the 'finally' stack is cleaned up properly. Useful only in debugging; do not use in production." +) diff --git a/etc/cmake/dependencies.cmake b/etc/cmake/dependencies.cmake new file mode 100644 index 0000000..f550308 --- /dev/null +++ b/etc/cmake/dependencies.cmake @@ -0,0 +1,171 @@ +include(helpers) + +include(CheckSymbolExists) +include(CMakePushCheckState) + +# The threading library is not needed for igraph itself, but might be needed +# for tests +include(FindThreads) + +macro(find_dependencies) + # Declare the list of dependencies that _may_ be vendored + set(VENDORABLE_DEPENDENCIES BLAS GLPK LAPACK ARPACK GMP PLFIT INFOMAP) + + # Declare optional dependencies associated with IGRAPH_..._SUPPORT flags + # Note that GLPK and INFOMAP are both vendorable and optional + set(OPTIONAL_DEPENDENCIES GLPK OpenMP INFOMAP) + + # Declare configuration options for dependencies + tristate(IGRAPH_USE_INTERNAL_GMP "Compile igraph with internal Mini-GMP" AUTO) + tristate(IGRAPH_USE_INTERNAL_ARPACK "Compile igraph with internal ARPACK" AUTO) + tristate(IGRAPH_USE_INTERNAL_BLAS "Compile igraph with internal BLAS" AUTO) + tristate(IGRAPH_USE_INTERNAL_GLPK "Compile igraph with internal GLPK" AUTO) + tristate(IGRAPH_USE_INTERNAL_LAPACK "Compile igraph with internal LAPACK" AUTO) + tristate(IGRAPH_USE_INTERNAL_PLFIT "Compile igraph with internal plfit" AUTO) + + # Infomap currently does not support being built as an external library, so will + # only be supported through the internal vendored build + set(IGRAPH_USE_INTERNAL_INFOMAP ON) + + # Declare dependencies + set(REQUIRED_DEPENDENCIES "") + set(OPTIONAL_DEPENDENCIES FLEX BISON OpenMP) + set(VENDORED_DEPENDENCIES "") + + # Declare minimum supported version for some dependencies + set(GLPK_VERSION_MIN "4.57") # 4.57 is the first version providing glp_on_error() + set(LIBXML2_VERSION_MIN "2.7.4") # 2.7.4 is the first version providing xmlStructuredErrorContext + set(PLFIT_VERSION_MIN "0.9.3") + + # Extend dependencies depending on whether we will be using the vendored + # copies or not + foreach(DEPENDENCY ${VENDORABLE_DEPENDENCIES}) + string(TOUPPER "${DEPENDENCY}" LIBNAME_UPPER) + + if(IGRAPH_USE_INTERNAL_${LIBNAME_UPPER} STREQUAL "AUTO") + find_package(${DEPENDENCY} ${${DEPENDENCY}_VERSION_MIN} QUIET) + if(${LIBNAME_UPPER}_FOUND) + set(IGRAPH_USE_INTERNAL_${LIBNAME_UPPER} OFF) + else() + set(IGRAPH_USE_INTERNAL_${LIBNAME_UPPER} ON) + endif() + endif() + if(IGRAPH_USE_INTERNAL_${LIBNAME_UPPER}) + list(APPEND VENDORED_DEPENDENCIES ${DEPENDENCY}) + else() + list(APPEND REQUIRED_DEPENDENCIES ${DEPENDENCY}) + endif() + endforeach() + + # For optional dependencies, figure out whether we should attempt to + # link to them based on the value of the IGRAPH_..._SUPPORT option + foreach(DEPENDENCY ${OPTIONAL_DEPENDENCIES}) + string(TOUPPER "${DEPENDENCY}" LIBNAME_UPPER) + + if(IGRAPH_${LIBNAME_UPPER}_SUPPORT STREQUAL "AUTO") + find_package(${DEPENDENCY} ${${DEPENDENCY}_VERSION_MIN} QUIET) + if(${LIBNAME_UPPER}_FOUND) + set(IGRAPH_${LIBNAME_UPPER}_SUPPORT ON) + else() + set(IGRAPH_${LIBNAME_UPPER}_SUPPORT OFF) + endif() + endif() + endforeach() + + # GraphML support is treated separately because the library name is different + if(IGRAPH_GRAPHML_SUPPORT STREQUAL "AUTO") + find_package(LibXml2 ${LIBXML2_VERSION_MIN} QUIET) + if(LibXml2_FOUND) + set(IGRAPH_GRAPHML_SUPPORT ON) + else() + set(IGRAPH_GRAPHML_SUPPORT OFF) + endif() + endif() + + if(NOT IGRAPH_GLPK_SUPPORT) + if(IGRAPH_USE_INTERNAL_GLPK) + list(REMOVE_ITEM VENDORED_DEPENDENCIES GLPK) + else() + list(REMOVE_ITEM REQUIRED_DEPENDENCIES GLPK) + endif() + endif() + + if(NOT IGRAPH_INFOMAP_SUPPORT) + list(REMOVE_ITEM VENDORED_DEPENDENCIES INFOMAP) + endif() + + if(IGRAPH_GRAPHML_SUPPORT) + list(APPEND REQUIRED_DEPENDENCIES LibXml2) + endif() + + # Find dependencies + foreach(DEPENDENCY ${REQUIRED_DEPENDENCIES} ${OPTIONAL_DEPENDENCIES}) + list(FIND REQUIRED_DEPENDENCIES "${DEPENDENCY}" INDEX) + set(NEED_THIS_DEPENDENCY NO) + + if(INDEX GREATER_EQUAL 0) + # This is a required dependency, search for it unconditionally. Do + # not use REQUIRED; we will report errors in a single batch at the end + # of the configuration process + set(NEED_THIS_DEPENDENCY YES) + else() + # This is an optional dependency, search for it only if the user did not + # turn it off explicitly + string(TOUPPER "${DEPENDENCY}" LIBNAME_UPPER) + if(NOT DEFINED IGRAPH_${LIBNAME_UPPER}_SUPPORT) + set(NEED_THIS_DEPENDENCY YES) + elseif(IGRAPH_${LIBNAME_UPPER}_SUPPORT) + set(NEED_THIS_DEPENDENCY YES) + endif() + endif() + + if(NEED_THIS_DEPENDENCY AND NOT DEFINED ${DEPENDENCY}_FOUND) + find_package(${DEPENDENCY} ${${DEPENDENCY}_VERSION_MIN}) + endif() + endforeach() + + # Override libraries of vendored dependencies even if they were somehow + # detected above + foreach(DEPENDENCY ${VENDORED_DEPENDENCIES}) + string(TOUPPER "${DEPENDENCY}" LIBNAME_UPPER) + string(TOLOWER "${DEPENDENCY}" LIBNAME_LOWER) + if(IGRAPH_USE_INTERNAL_${LIBNAME_UPPER}) + set(${LIBNAME_UPPER}_LIBRARIES "") + set(${LIBNAME_UPPER}_FOUND 1) + set(${LIBNAME_UPPER}_IS_VENDORED 1) + set(INTERNAL_${LIBNAME_UPPER} 1) + endif() + endforeach() + + # Export some aliases that will be used in config.h + set(HAVE_GLPK ${GLPK_FOUND}) + set(HAVE_INFOMAP ${INFOMAP_FOUND}) + set(HAVE_GMP ${GMP_FOUND}) + set(HAVE_LIBXML ${LIBXML2_FOUND}) + + # Check whether we need to link to the math library + if(NOT DEFINED CACHE{NEED_LINKING_AGAINST_LIBM}) + cmake_push_check_state() + set(CMAKE_REQUIRED_QUIET ON) + check_symbol_exists(sinh "math.h" SINH_FUNCTION_EXISTS) + if(NOT SINH_FUNCTION_EXISTS) + unset(SINH_FUNCTION_EXISTS CACHE) + list(APPEND CMAKE_REQUIRED_LIBRARIES m) + check_symbol_exists(sinh "math.h" SINH_FUNCTION_EXISTS) + if(SINH_FUNCTION_EXISTS) + set(NEED_LINKING_AGAINST_LIBM True CACHE BOOL "" FORCE) + else() + message(FATAL_ERROR "Failed to figure out how to link to the math library on this platform") + endif() + endif() + unset(SINH_FUNCTION_EXISTS CACHE) + cmake_pop_check_state() + endif() + + if(NEED_LINKING_AGAINST_LIBM) + find_library(MATH_LIBRARY m) + endif() + + mark_as_advanced(MATH_LIBRARY) + mark_as_advanced(NEED_LINKING_AGAINST_LIBM) +endmacro() diff --git a/etc/cmake/features.cmake b/etc/cmake/features.cmake new file mode 100644 index 0000000..0863f4f --- /dev/null +++ b/etc/cmake/features.cmake @@ -0,0 +1,26 @@ +include(helpers) + +include(tls) +include(lto) + +option(IGRAPH_GLPK_SUPPORT "Compile igraph with GLPK support" ON) +option(IGRAPH_INFOMAP_SUPPORT "Compile igraph with Infomap support" ON) +tristate(IGRAPH_GRAPHML_SUPPORT "Compile igraph with GraphML support" AUTO) +tristate(IGRAPH_OPENMP_SUPPORT "Use OpenMP for parallelization" AUTO) + +set(IGRAPH_INTEGER_SIZE AUTO CACHE STRING "Set size of igraph integers") +set_property(CACHE IGRAPH_INTEGER_SIZE PROPERTY STRINGS AUTO 32 64) + +if(IGRAPH_INTEGER_SIZE STREQUAL AUTO) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(IGRAPH_INTEGER_SIZE 64) + else() + set(IGRAPH_INTEGER_SIZE 32) + endif() +endif() + +option(FLEX_KEEP_LINE_NUMBERS "Keep references to the original line numbers in generated Flex/Bison parser files" OFF) +mark_as_advanced(FLEX_KEEP_LINE_NUMBERS) + +option(BUILD_FUZZING "Build fuzz targets and enable fuzzer instrumentation" OFF) +mark_as_advanced(BUILD_FUZZING) diff --git a/etc/cmake/fuzz_helpers.cmake b/etc/cmake/fuzz_helpers.cmake new file mode 100644 index 0000000..bc272e7 --- /dev/null +++ b/etc/cmake/fuzz_helpers.cmake @@ -0,0 +1,16 @@ + +function(add_fuzzer NAME) + set(TARGET_NAME fuzzer_${NAME}) + + add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL ${PROJECT_SOURCE_DIR}/fuzzing/${NAME}.cpp) + + add_dependencies(build_fuzzers ${TARGET_NAME}) + + target_link_libraries(${TARGET_NAME} PRIVATE igraph) + + # The -fsanitize=fuzzer-no-link is already added by the top-level CMakeLists.txt + # for general fuzzer instrumentation. Additionally, we need -fsanitize=fuzzer + # for the fuzz targets, which do not contain a main() function, to link in the + # fuzz driver. See https://llvm.org/docs/LibFuzzer.html + target_link_options(${TARGET_NAME} PRIVATE -fsanitize=fuzzer) +endfunction() diff --git a/etc/cmake/generate_tags_file.cmake b/etc/cmake/generate_tags_file.cmake new file mode 100644 index 0000000..dbb8484 --- /dev/null +++ b/etc/cmake/generate_tags_file.cmake @@ -0,0 +1,46 @@ +# Creates a ctags-compatible tags file from a set of XML files by extracting +# the IDs found in the XML files. +# +# Parameters of the script: +# +# - INPUT_FILES: list of input files to process, with absolute pathnames +# - OUTPUT_FILE: the output file to write the tags into + +string(REPLACE " " ";" INPUT_FILE_LIST "${INPUT_FILES}") + +set(EXTRACTED_IDS "") + +foreach(INPUT_FILE ${INPUT_FILE_LIST}) + file(READ "${INPUT_FILE}" CONTENTS) + + # Replace newlines with semicolons. This is a hack and we should escape + # semicolons first if we wanted to do this properly; however, here we are + # only interested in XML IDs and they don't have semicolons + string(REPLACE "\n" ";" LINES "${CONTENTS}") + foreach(_line ${LINES}) + string(REGEX MATCHALL "id=\"[^-\"]*\"" MATCH_RESULT "${_line}") + if(MATCH_RESULT) + foreach(MATCH ${MATCH_RESULT}) + string(REGEX REPLACE "id=\"(.*)\"" "\\1" EXTRACTED_ID "${MATCH}") + list(APPEND EXTRACTED_IDS "${EXTRACTED_ID}") + endforeach() + endif() + endforeach() +endforeach() + +list(SORT EXTRACTED_IDS) +string(REPLACE ";" "\t\t\n" TAGS_OUTPUT "${EXTRACTED_IDS}") +string(APPEND TAGS_OUTPUT "\t\t\n") +string(SHA1 TAGS_OUTPUT_HASH "${TAGS_OUTPUT}") + +# Update the output file only if it changed; this prevents CMake from calling +# source-highlight if there is no point in rebuilding the highlighted +# source files +if(EXISTS "${OUTPUT_FILE}") + file(SHA1 "${OUTPUT_FILE}" OUTPUT_FILE_HASH) + if(NOT "${OUTPUT_FILE_HASH}" STREQUAL "${TAGS_OUTPUT_HASH}") + file(WRITE "${OUTPUT_FILE}" "${TAGS_OUTPUT}") + endif() +else() + file(WRITE "${OUTPUT_FILE}" "${TAGS_OUTPUT}") +endif() diff --git a/etc/cmake/helpers.cmake b/etc/cmake/helpers.cmake new file mode 100644 index 0000000..df9ddaf --- /dev/null +++ b/etc/cmake/helpers.cmake @@ -0,0 +1,6 @@ +macro(tristate OPTION_NAME DESCRIPTION DEFAULT_VALUE) + set(${OPTION_NAME} "${DEFAULT_VALUE}" CACHE STRING "${DESCRIPTION}") + set_property(CACHE ${OPTION_NAME} PROPERTY STRINGS AUTO ON OFF) +endmacro() + +include(PadString) diff --git a/etc/cmake/ieee754_endianness.cmake b/etc/cmake/ieee754_endianness.cmake new file mode 100644 index 0000000..fc66751 --- /dev/null +++ b/etc/cmake/ieee754_endianness.cmake @@ -0,0 +1,54 @@ +include(CheckCSourceRuns) + +cmake_push_check_state(RESET) + +# Check whether IEEE754 doubles are laid out in little-endian order. We do this +# only when not cross-compiling; during cross-compilation, the host architecture +# might have different endianness conventions than the target, and we are running +# the test on the host here +if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR) + # If we are cross-compiling and we have no emulator, let's just assume that + # IEEE754 doubles use the same endianness as uint64_t + set(IEEE754_DOUBLE_ENDIANNESS_MATCHES YES) + message(WARNING "\ +igraph is being cross-compiled, therefore we cannot validate whether the \ +endianness of IEEE754 doubles is the same as the endianness of uint64_t. \ +Most likely it is, unless you are compiling for some esoteric platform, \ +in which case you need make sure that this is the case on your own.\ +") +else() + if(NOT DEFINED CACHE{IEEE754_DOUBLE_ENDIANNESS_MATCHES}) + try_run( + IEEE754_DOUBLE_ENDIANNESS_TEST_EXIT_CODE + IEEE754_DOUBLE_ENDIANNESS_TEST_COMPILES + ${CMAKE_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/etc/cmake/ieee754_endianness_check.c + RUN_OUTPUT_VARIABLE IEEE754_DOUBLE_ENDIANNESS_TEST_RESULT + ) + # Strip trailing newline, which is necessary on some platforms (such as node.js) + # to complete printing the output. + string(STRIP "${IEEE754_DOUBLE_ENDIANNESS_TEST_RESULT}" IEEE754_DOUBLE_ENDIANNESS_TEST_RESULT) + if(IEEE754_DOUBLE_ENDIANNESS_TEST_EXIT_CODE EQUAL 0) + if(IEEE754_DOUBLE_ENDIANNESS_TEST_RESULT STREQUAL "OK") + set(TEST_RESULT YES) + else() + set(TEST_RESULT NO) + endif() + else() + message(FATAL_ERROR "IEEE754 double endianness test terminated abnormally.") + endif() + + set( + IEEE754_DOUBLE_ENDIANNESS_MATCHES ${TEST_RESULT} CACHE BOOL + "Specifies whether the endianness of IEEE754 doubles is the same as the endianness of uint64_t." + FORCE + ) + mark_as_advanced(IEEE754_DOUBLE_ENDIANNESS_MATCHES) + endif() +endif() + +cmake_pop_check_state() + +if(NOT IEEE754_DOUBLE_ENDIANNESS_MATCHES) + message(FATAL_ERROR "igraph only supports platforms where IEEE754 doubles have the same endianness as uint64_t.") +endif() diff --git a/etc/cmake/ieee754_endianness_check.c b/etc/cmake/ieee754_endianness_check.c new file mode 100644 index 0000000..6296825 --- /dev/null +++ b/etc/cmake/ieee754_endianness_check.c @@ -0,0 +1,24 @@ +/* Checks whether the endianness of IEEE754 doubles matches the endianness of + * uint64_t on the target system. This is needed to ensure that the trick we + * employ in igraph_rng_get_unif01() works. */ + +#include +#include + +union { + uint64_t as_uint64_t; + double as_double; +} value; + +int main(void) { + value.as_uint64_t = 4841376218035192321ULL; + if (value.as_double == 4510218239279617.0) { + /* endianness of uint64_t and double match */ + printf("OK\n"); + } + /* We always return 0, even for a negative result, this is because we + * need to tell on the CMake side whether a compiler misconfiguration + * aborted our program, which can then be detected from a nonzero exit + * code. */ + return 0; +} diff --git a/etc/cmake/igraph-config.cmake.in b/etc/cmake/igraph-config.cmake.in new file mode 100644 index 0000000..27ceda2 --- /dev/null +++ b/etc/cmake/igraph-config.cmake.in @@ -0,0 +1,40 @@ +# igraph-config.cmake +# +# igraph CMake package +# +# The following variables are set: +# +# - IGRAPH_VERSION - The igraph version string. +# - IGRAPH_INTEGER_SIZE - The integer size igraph was configured with (32 or 64). +# - IGRAPH_GLPK_SUPPORT - Whether igraph was compiled with GLPK support. +# - IGRAPH_GRAPHML_SUPPORT - Whether igraph was compiled with GraphML support. +# + +set(IGRAPH_VERSION "@PACKAGE_VERSION_BASE@") +set(IGRAPH_INTEGER_SIZE @IGRAPH_INTEGER_SIZE@) +set(IGRAPH_GLPK_SUPPORT @IGRAPH_GLPK_SUPPORT@) +set(IGRAPH_GRAPHML_SUPPORT @IGRAPH_GRAPHML_SUPPORT@) +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/igraph-targets.cmake") + +# Check whether C++ support is enabled; this is needed to ensure that programs +# that are dependent on igraph will get linked with the C++ linker and not the +# "plain" C linker +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +if("CXX" IN_LIST LANGUAGES) + # This is okay +else() + message(FATAL_ERROR "Please enable C++ support in your project if you are linking to igraph.") +endif() + +# Turn on CMP0012 because the following if() conditionals will use "ON" and +# "OFF" verbatim and they must be evaluated as booleans +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +if(@IGRAPH_OPENMP_SUPPORT@) + find_package(OpenMP) +endif() +cmake_policy(POP) + +check_required_components(igraph) diff --git a/etc/cmake/lto.cmake b/etc/cmake/lto.cmake new file mode 100644 index 0000000..efcbd93 --- /dev/null +++ b/etc/cmake/lto.cmake @@ -0,0 +1,23 @@ +include(helpers) + +tristate(IGRAPH_ENABLE_LTO "Enable link-time optimization" OFF) + +include(CheckIPOSupported) + +if(IGRAPH_ENABLE_LTO) + # this matches both ON and AUTO + check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_NOT_SUPPORTED_REASON) + if(IGRAPH_ENABLE_LTO STREQUAL "AUTO") + # autodetection + set(IGRAPH_ENABLE_LTO ${IPO_SUPPORTED}) + if(IPO_SUPPORTED) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + elseif(IPO_SUPPORTED) + # user wanted LTO and the compiler supports it + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + # user wanted LTO and the compiler does not support it + message(FATAL_ERROR "Link-time optimization not supported on this compiler") + endif() +endif() diff --git a/etc/cmake/packaging.cmake b/etc/cmake/packaging.cmake new file mode 100644 index 0000000..ec52c93 --- /dev/null +++ b/etc/cmake/packaging.cmake @@ -0,0 +1,70 @@ +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "igraph library") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://igraph.org") +set(CPACK_PACKAGE_VENDOR "The igraph development team") + +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING") + +if(TARGET html AND TARGET info) + # Alias "dist" to "package_source" + add_custom_target(dist + COMMAND "${CMAKE_COMMAND}" + --build "${CMAKE_BINARY_DIR}" + --target package_source + VERBATIM + USES_TERMINAL + ) + + # Add dependencies to "dist" + add_dependencies(dist html info) +else() + add_custom_target(dist + COMMAND "${CMAKE_COMMAND}" -E false + COMMENT + "Cannot build source tarball since the HTML or the Texinfo documentation was not built." + VERBATIM + USES_TERMINAL + ) +endif() + +############################################################################# +## Configuration of the source package +############################################################################# + +# Set source package name and format +set(CPACK_SOURCE_PACKAGE_FILE_NAME "igraph-${PACKAGE_VERSION}") +set(CPACK_SOURCE_GENERATOR "TGZ") + +# Declare what to include in the source tarball. Unfortunately we can only +# declare full directories here, not individual files. +set( + CPACK_SOURCE_INSTALLED_DIRECTORIES + "${CMAKE_SOURCE_DIR}/doc;/doc" + "${CMAKE_SOURCE_DIR}/etc/cmake;/etc/cmake" + "${CMAKE_SOURCE_DIR}/examples;/examples" + "${CMAKE_SOURCE_DIR}/include;/include" + "${CMAKE_SOURCE_DIR}/interfaces;/interfaces" + "${CMAKE_SOURCE_DIR}/msvc/include;/msvc/include" + "${CMAKE_SOURCE_DIR}/src;/src" + "${CMAKE_SOURCE_DIR}/tests;/tests" + "${CMAKE_SOURCE_DIR}/vendor;/vendor" +) + +# CPack is pretty dumb as it can only copy full directories (sans the ignored +# files) to the target tarball by default. In some cases it is easier to +# whitelist files to be copied; we use CPACK_INSTALL_SCRIPT for that. +set(CPACK_INSTALL_SCRIPT "${CMAKE_SOURCE_DIR}/etc/cmake/cpack_install_script.cmake") + +# Ignore the build and all hidden folders +set( + CPACK_SOURCE_IGNORE_FILES + "\\\\..*/" + "\\\\.l$" + "\\\\.y$" + "${CMAKE_SOURCE_DIR}/build" +) + +############################################################################# +## Now we can include CPack +############################################################################# + +include(CPack) diff --git a/etc/cmake/pkgconfig_helpers.cmake b/etc/cmake/pkgconfig_helpers.cmake new file mode 100644 index 0000000..d0d9ecb --- /dev/null +++ b/etc/cmake/pkgconfig_helpers.cmake @@ -0,0 +1,75 @@ +# Helper functions for generating a nicely formatted igraph.pc file from +# igraph.pc.in + +include(JoinPaths) +include(CheckCXXSymbolExists) + +# Converts the name of a library file (or framework on macOS) into an +# appropriate linker flag (-lsomething or -framework something.framework). +# Returns the input intact if its extension does not look like a shared or +# static library extension. +function(convert_library_file_to_flags output_variable input) + get_filename_component(input_filename ${input} NAME_WE) + get_filename_component(input_extension ${input} LAST_EXT) + if(input_extension STREQUAL ${CMAKE_SHARED_LIBRARY_SUFFIX} OR input_extension STREQUAL ${CMAKE_STATIC_LIBRARY_SUFFIX}) + string(REGEX REPLACE "^${CMAKE_SHARED_LIBRARY_PREFIX}" "" input_stripped ${input_filename}) + set("${output_variable}" "-l${input_stripped}" PARENT_SCOPE) + elseif(APPLE AND input_extension STREQUAL ".framework") + set("${output_variable}" "-framework ${input_filename}" PARENT_SCOPE) + else() + set("${output_variable}" "${input}" PARENT_SCOPE) + endif() +endfunction() + +if(MATH_LIBRARY) + set(PKGCONFIG_LIBS_PRIVATE "-lm") +else() + set(PKGCONFIG_LIBS_PRIVATE "") +endif() +set(PKGCONFIG_REQUIRES_PRIVATE "") + +if(NOT MSVC) + check_cxx_symbol_exists(_LIBCPP_VERSION "vector" USING_LIBCXX) + check_cxx_symbol_exists(__GLIBCXX__ "vector" USING_LIBSTDCXX) + if(USING_LIBCXX) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lc++") + elseif(USING_LIBSTDCXX) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lstdc++") + endif() +endif() + +if(IGRAPH_GRAPHML_SUPPORT) + set(PKGCONFIG_REQUIRES_PRIVATE "${PKGCONFIG_REQUIRES_PRIVATE} libxml-2.0") +endif() +if(NOT IGRAPH_USE_INTERNAL_GMP) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lgmp") +endif() +if(NOT IGRAPH_USE_INTERNAL_BLAS) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lblas") +endif() +if(IGRAPH_GLPK_SUPPORT AND NOT IGRAPH_USE_INTERNAL_GLPK) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lglpk") +endif() +if(NOT IGRAPH_USE_INTERNAL_LAPACK) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -llapack") +endif() +if(NOT IGRAPH_USE_INTERNAL_ARPACK) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -larpack") +endif() +if(NOT IGRAPH_USE_INTERNAL_PLFIT) + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} -lplfit") +endif() +if(IGRAPH_OPENMP_SUPPORT AND OpenMP_FOUND) + foreach(CURRENT_LIB ${OpenMP_C_LIB_NAMES}) + convert_library_file_to_flags(CURRENT_LIB "${OpenMP_${CURRENT_LIB}_LIBRARY}") + set(PKGCONFIG_LIBS_PRIVATE "${PKGCONFIG_LIBS_PRIVATE} ${CURRENT_LIB}") + endforeach() +endif() + +join_paths(PKGCONFIG_LIBDIR "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") +join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") +configure_file( + ${PROJECT_SOURCE_DIR}/igraph.pc.in + ${PROJECT_BINARY_DIR}/igraph.pc + @ONLY +) diff --git a/etc/cmake/run_legacy_test.cmake b/etc/cmake/run_legacy_test.cmake new file mode 100644 index 0000000..ef09642 --- /dev/null +++ b/etc/cmake/run_legacy_test.cmake @@ -0,0 +1,73 @@ +# Runs a legacy autotools-based test with a file containing the expected output +# +# Parameters of the script: +# +# - TEST_EXECUTABLE: full path of the compiled test executable +# - EXPECTED_OUTPUT_FILE: full path of the file containing the expected output +# - OBSERVED_OUTPUT_FILE: full path of the file where the observed output +# can be written +# - DIFF_FILE: full path of the file where the differences between the expectd +# and the observed output should be written +# - DIFF_TOOL: full path to a "diff" tool on the system of the user, if present +# - FC_TOOL: full path to a "fc" tool on the system of the user, if present +# - IGRAPH_VERSION: version string of igraph that should be replaced in +# expected outputs + +get_filename_component(WORK_DIR ${EXPECTED_OUTPUT_FILE} DIRECTORY) + +execute_process( + COMMAND ${CROSSCOMPILING_EMULATOR} ${TEST_EXECUTABLE} + WORKING_DIRECTORY ${WORK_DIR} + RESULT_VARIABLE ERROR_CODE + OUTPUT_VARIABLE OBSERVED_OUTPUT +) + +if(ERROR_CODE EQUAL 77) + message(STATUS "Test skipped") +elseif(ERROR_CODE) + set(MESSAGE "Test exited abnormally with error: ${ERROR_CODE}") + file(WRITE ${OBSERVED_OUTPUT_FILE} "${MESSAGE}\n=========================================\n${OBSERVED_OUTPUT}") + execute_process(COMMAND "${CMAKE_COMMAND}" -E cat "${OBSERVED_OUTPUT_FILE}") + file(REMOVE ${DIFF_FILE}) + message(FATAL_ERROR "Exiting test.") +else() + string(REPLACE ${IGRAPH_VERSION} "\@VERSION\@" OBSERVED_OUTPUT "${OBSERVED_OUTPUT}") + file(WRITE ${OBSERVED_OUTPUT_FILE} "${OBSERVED_OUTPUT}") + + execute_process( + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${EXPECTED_OUTPUT_FILE} ${OBSERVED_OUTPUT_FILE} + RESULT_VARIABLE ARE_DIFFERENT + ) + + if(ARE_DIFFERENT) + if(DIFF_TOOL) + execute_process( + COMMAND ${DIFF_TOOL} -u ${EXPECTED_OUTPUT_FILE} ${OBSERVED_OUTPUT_FILE} + OUTPUT_FILE ${DIFF_FILE} + ) + elseif(FC_TOOL) + file(TO_NATIVE_PATH "${EXPECTED_OUTPUT_FILE}" REAL_EXPECTED_OUTPUT_FILE) + file(TO_NATIVE_PATH "${OBSERVED_OUTPUT_FILE}" REAL_OBSERVED_OUTPUT_FILE) + execute_process( + COMMAND ${FC_TOOL} /A ${REAL_EXPECTED_OUTPUT_FILE} ${REAL_OBSERVED_OUTPUT_FILE} + OUTPUT_FILE ${DIFF_FILE} + ) + endif() + + message(STATUS "Test case output differs from the expected output") + if(EXISTS ${DIFF_FILE}) + message(STATUS "See diff below:") + message(STATUS "-------------------------------------------------------") + execute_process(COMMAND "${CMAKE_COMMAND}" -E cat "${DIFF_FILE}") + message(STATUS "-------------------------------------------------------") + else() + message(STATUS "Diff omitted; no diff tool was installed.") + endif() + message(FATAL_ERROR "Exiting test.") + else() + file(REMOVE ${DIFF_FILE}) + endif() + + file(REMOVE ${OBSERVED_OUTPUT_FILE}) +endif() diff --git a/etc/cmake/safe_math_support.cmake b/etc/cmake/safe_math_support.cmake new file mode 100644 index 0000000..a9e0ef4 --- /dev/null +++ b/etc/cmake/safe_math_support.cmake @@ -0,0 +1,18 @@ +include(CheckCXXSourceCompiles) + +# Check whether the compiler supports the __builtin_add_overflow() and __builtin_mul_overflow() +# builtins. These are present in recent GCC-compatible compilers. +cmake_push_check_state(RESET) + +check_cxx_source_compiles(" + int main(void) { + long long a=1, b=2, c; + __builtin_add_overflow(a, b, &c); + __builtin_mul_overflow(a, b, &c); + return 0; + } + " + HAVE_BUILTIN_OVERFLOW +) + +cmake_pop_check_state() diff --git a/etc/cmake/sanitizers.cmake b/etc/cmake/sanitizers.cmake new file mode 100644 index 0000000..3e1163a --- /dev/null +++ b/etc/cmake/sanitizers.cmake @@ -0,0 +1,88 @@ +# +# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +set( + USE_SANITIZER + "" + CACHE + STRING + "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'" + ) + +function(append value) + foreach(variable ${ARGN}) + set(${variable} "${${variable}} ${value}" PARENT_SCOPE) + endforeach(variable) +endfunction() + +if(USE_SANITIZER) + + if(UNIX) + + append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + + if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") + append("-Og" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + endif() + + if(USE_SANITIZER MATCHES "([Aa]ddress);([Uu]ndefined)" + OR USE_SANITIZER MATCHES "([Uu]ndefined);([Aa]ddress)") + message(STATUS "Building with Address, Undefined sanitizers") + append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + elseif("${USE_SANITIZER}" MATCHES "([Aa]ddress)") + # Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope + message(STATUS "Building with Address sanitizer") + append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + elseif(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)") + # Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 + append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") + message(STATUS "Building with MemoryWithOrigins sanitizer") + append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + else() + message(STATUS "Building with Memory sanitizer") + endif() + elseif(USE_SANITIZER MATCHES "([Uu]ndefined)") + message(STATUS "Building with Undefined sanitizer") + append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + if(EXISTS "${BLACKLIST_FILE}") + append("-fsanitize-blacklist=${BLACKLIST_FILE}" CMAKE_C_FLAGS + CMAKE_CXX_FLAGS) + endif() + elseif(USE_SANITIZER MATCHES "([Tt]hread)") + message(STATUS "Building with Thread sanitizer") + append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + elseif(USE_SANITIZER MATCHES "([Ll]eak)") + message(STATUS "Building with Leak sanitizer") + append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + else() + message( + FATAL_ERROR "Unsupported value of USE_SANITIZER: ${USE_SANITIZER}") + endif() + elseif(MSVC) + if(USE_SANITIZER MATCHES "([Aa]ddress)") + message(STATUS "Building with Address sanitizer") + append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + else() + message( + FATAL_ERROR + "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}" + ) + endif() + else() + message(FATAL_ERROR "USE_SANITIZER is not supported on this platform.") + endif() + +endif() diff --git a/etc/cmake/summary.cmake b/etc/cmake/summary.cmake new file mode 100644 index 0000000..348b7d3 --- /dev/null +++ b/etc/cmake/summary.cmake @@ -0,0 +1,108 @@ +function(print_bool HEADING VAR) + if(${VAR}) + set(LABEL "yes") + else() + set(LABEL "no") + endif() + print_str(${HEADING} ${LABEL}) +endfunction() + +function(print_str HEADING LABEL) + string(LENGTH "${HEADING}" HEADING_LENGTH) + math(EXPR REMAINING_WIDTH "30 - ${HEADING_LENGTH}") + + if("${LABEL}" STREQUAL "") + pad_string(PADDED ${REMAINING_WIDTH} " " "${ARGN}") + else() + pad_string(PADDED ${REMAINING_WIDTH} " " "${LABEL}") + endif() + + message(STATUS "${HEADING}: ${PADDED}") +endfunction() + +############################################################################# + +set(ALL_DEPENDENCIES ${REQUIRED_DEPENDENCIES} ${OPTIONAL_DEPENDENCIES} ${VENDORED_DEPENDENCIES}) +list(SORT ALL_DEPENDENCIES CASE INSENSITIVE) + +message(STATUS " ") +message(STATUS "-----[ Build configuration ]----") +print_str("Version" "${PACKAGE_VERSION}") +print_str("CMake build type" "${CMAKE_BUILD_TYPE}" "default") +if(BUILD_SHARED_LIBS) + message(STATUS "Library type: shared") +else() + message(STATUS "Library type: static") +endif() +if(${IGRAPH_INTEGER_SIZE} STREQUAL "AUTO") + print_str("igraph_int_t size" "auto") +elseif(${IGRAPH_INTEGER_SIZE} STREQUAL 64) + print_str("igraph_int_t size" "64 bits") +elseif(${IGRAPH_INTEGER_SIZE} STREQUAL 32) + print_str("igraph_int_t size" "32 bits") +else() + print_str("igraph_int_t size" "INVALID") +endif() +if(USE_CCACHE) + if(CCACHE_PROGRAM) + message(STATUS "Compiler cache: ccache") + endif() +else() + message(STATUS "Compiler cache: disabled") +endif() + +message(STATUS " ") +message(STATUS "----------[ Features ]----------") +print_bool("GLPK for optimization" IGRAPH_GLPK_SUPPORT) +print_bool("Infomap community detection" IGRAPH_INFOMAP_SUPPORT) +print_bool("Reading GraphML files" IGRAPH_GRAPHML_SUPPORT) +print_bool("Thread-local storage" IGRAPH_ENABLE_TLS) +print_bool("Link-time optimization" IGRAPH_ENABLE_LTO) +message(STATUS " ") + +message(STATUS "--------[ Dependencies ]--------") +foreach(DEPENDENCY ${ALL_DEPENDENCIES}) + list(FIND VENDORED_DEPENDENCIES "${DEPENDENCY}" INDEX) + if(INDEX EQUAL -1) + print_bool("${DEPENDENCY}" ${DEPENDENCY}_FOUND) + # This is a hack to skip printing Infomap, until + # support for linking to an external version is added + elseif(NOT "${DEPENDENCY}" STREQUAL "INFOMAP") + print_str("${DEPENDENCY}" "vendored") + endif() +endforeach() +message(STATUS " ") + +message(STATUS "-----------[ Testing ]----------") +if(DIFF_TOOL) + print_str("Diff tool" "diff") +elseif(FC_TOOL) + print_str("Diff tool" "fc") +else() + print_str("Diff tool" "not found") +endif() +print_str("Sanitizers" "${USE_SANITIZER}" "none") +print_bool("Code coverage" IGRAPH_ENABLE_CODE_COVERAGE) +print_bool("Verify 'finally' stack" IGRAPH_VERIFY_FINALLY_STACK) +message(STATUS " ") + +message(STATUS "--------[ Documentation ]-------") +print_bool("HTML" HTML_DOC_BUILD_SUPPORTED) +print_bool("PDF" PDF_DOC_BUILD_SUPPORTED) +print_bool("INFO" INFO_DOC_BUILD_SUPPORTED) +message(STATUS " ") + +set(MISSING_DEPENDENCIES) +foreach(DEPENDENCY ${REQUIRED_DEPENDENCIES}) + if(NOT ${DEPENDENCY}_FOUND) + list(APPEND MISSING_DEPENDENCIES ${DEPENDENCY}) + endif() +endforeach() + +if(MISSING_DEPENDENCIES) + list(JOIN MISSING_DEPENDENCIES ", " GLUED) + message(FATAL_ERROR "The following dependencies are missing: ${GLUED}") +else() + message(STATUS "igraph configured successfully.") + message(STATUS " ") +endif() diff --git a/etc/cmake/test_helpers.cmake b/etc/cmake/test_helpers.cmake new file mode 100644 index 0000000..f83bb9d --- /dev/null +++ b/etc/cmake/test_helpers.cmake @@ -0,0 +1,120 @@ +include(CMakeParseArguments) + +find_program(DIFF_TOOL diff) +if(NOT DIFF_TOOL) + find_program(FC_TOOL fc) +endif() + +function(add_legacy_test FOLDER NAME NAMESPACE) + set(TARGET_NAME ${NAMESPACE}_${NAME}) + set(TEST_NAME "${NAMESPACE}::${NAME}") + + add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL ${PROJECT_SOURCE_DIR}/${FOLDER}/${NAME}.c) + use_all_warnings(${TARGET_NAME}) + add_dependencies(build_tests ${TARGET_NAME}) + # Specify linking with test_utilities *before* linking with igraph, to avoid + # duplicating libigraph.a. See https://github.com/igraph/igraph/issues/2394 + if (NAMESPACE STREQUAL "test") + target_link_libraries(${TARGET_NAME} PRIVATE test_utilities) + endif() + target_link_libraries(${TARGET_NAME} PRIVATE igraph) + + # Some tests depend on internal igraph headers so we also have to add src/ + # to the include path even though it's not part of the public API + target_include_directories( + ${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src + ) + + # Some tests include cs.h from CXSparse + target_include_directories( + ${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/vendor/cs + ) + + if (MSVC) + # Add MSVC-specific include path for some headers that are missing on Windows + target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/msvc/include) + endif() + + set(EXPECTED_OUTPUT_FILE ${CMAKE_SOURCE_DIR}/${FOLDER}/${NAME}.out) + set(OBSERVED_OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.out) + set(DIFF_FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.diff) + get_filename_component(WORK_DIR ${EXPECTED_OUTPUT_FILE} DIRECTORY) + + if(EXISTS ${EXPECTED_OUTPUT_FILE}) + get_property(CROSSCOMPILING_EMULATOR TARGET ${TARGET_NAME} PROPERTY CROSSCOMPILING_EMULATOR) + add_test( + NAME ${TEST_NAME} + COMMAND ${CMAKE_COMMAND} + -DTEST_EXECUTABLE=$ + -DEXPECTED_OUTPUT_FILE=${EXPECTED_OUTPUT_FILE} + -DOBSERVED_OUTPUT_FILE=${OBSERVED_OUTPUT_FILE} + -DDIFF_FILE=${DIFF_FILE} + -DDIFF_TOOL=${DIFF_TOOL} + -DFC_TOOL=${FC_TOOL} + -DIGRAPH_VERSION=${PACKAGE_VERSION} + "-DCROSSCOMPILING_EMULATOR=${CROSSCOMPILING_EMULATOR}" + -P ${CMAKE_SOURCE_DIR}/etc/cmake/run_legacy_test.cmake + ) + set_property(TEST ${TEST_NAME} PROPERTY SKIP_REGULAR_EXPRESSION "Test skipped") + else() + add_test( + NAME ${TEST_NAME} + COMMAND ${TARGET_NAME} + WORKING_DIRECTORY ${WORK_DIR} + ) + set_property(TEST ${TEST_NAME} PROPERTY SKIP_RETURN_CODE 77) + endif() + if (WIN32 AND BUILD_SHARED_LIBS) + # On Windows the built igraph.dll is not automatically found by the tests. We therefore + # add the dir that contains the built igraph.dll to the path environment variable + # so that igraph.dll is found when running the tests. + SET(IGRAPH_LIBDIR $) + + # The next line is necessitated by MinGW on Windows. MinGW uses forward slashes in + # IGRAPH_LIBDIR, but we need to supply CTest with backslashes because CTest is executed + # in a cmd.exe shell. We therefore explicitly ensure that that path is transformed to a + # native path. + file(TO_NATIVE_PATH "${IGRAPH_LIBDIR}" IGRAPH_LIBDIR) + + # Semicolons are used as list separators in CMake so we need to escape them in the PATH, + # otherwise the PATH envvar gets split by CMake before it passes the PATH on to CTest. + # We process each path separately to ensure it is a proper path. In particular, we need + # to ensure that a trailing backslash is not incorrectly interpreted as an escape + # character. Presumably, with cmake 3.20, this can be changed to using TO_NATIVE_PATH_LIST. + SET(TEST_PATHS) + foreach (PATH $ENV{PATH}) + file(TO_NATIVE_PATH "${PATH}" CORRECT_PATH) + # Remove trailing backslash + STRING(REGEX REPLACE "\\$" "" CORRECT_PATH ${CORRECT_PATH}) + list(APPEND TEST_PATHS ${CORRECT_PATH}) + endforeach() + + # Join all paths in a single string, separated by an escaped semi-colon. + string(JOIN "\;" CORRECT_PATHS ${TEST_PATHS}) + SET_TESTS_PROPERTIES(${TEST_NAME} PROPERTIES ENVIRONMENT "PATH=${IGRAPH_LIBDIR}\;${CORRECT_PATHS}" ) + endif() +endfunction() + +function(add_legacy_tests) + cmake_parse_arguments( + PARSED "" "FOLDER" "NAMES;LIBRARIES" ${ARGN} + ) + foreach(NAME ${PARSED_NAMES}) + add_legacy_test(${PARSED_FOLDER} ${NAME} test) + if(PARSED_LIBRARIES) + target_link_libraries(test_${NAME} PRIVATE ${PARSED_LIBRARIES}) + endif() + endforeach() +endfunction() + +function(add_examples) + cmake_parse_arguments( + PARSED "" "FOLDER" "NAMES;LIBRARIES" ${ARGN} + ) + foreach(NAME ${PARSED_NAMES}) + add_legacy_test(${PARSED_FOLDER} ${NAME} example) + if(PARSED_LIBRARIES) + target_link_libraries(example_${NAME} PRIVATE ${PARSED_LIBRARIES}) + endif() + endforeach() +endfunction() diff --git a/etc/cmake/tls.cmake b/etc/cmake/tls.cmake new file mode 100644 index 0000000..f0f4834 --- /dev/null +++ b/etc/cmake/tls.cmake @@ -0,0 +1,25 @@ +tristate(IGRAPH_ENABLE_TLS "Enable thread-local storage for igraph global variables" AUTO) + +include(CheckTLSSupport) +check_tls_support(TLS_KEYWORD) + +if(IGRAPH_ENABLE_TLS STREQUAL "AUTO") + if(TLS_KEYWORD) + set(IGRAPH_ENABLE_TLS ON) + else() + set(IGRAPH_ENABLE_TLS OFF) + endif() +endif() + +if(IGRAPH_ENABLE_TLS) + if(NOT TLS_KEYWORD) + message(FATAL_ERROR "Thread-local storage not supported on this compiler") + endif() + + # TODO: we should probably set this only if we are building igraph with + # internal-everything + set(IGRAPH_THREAD_SAFE YES) +else() + set(TLS_KEYWORD "") + set(IGRAPH_THREAD_SAFE NO) +endif() diff --git a/etc/cmake/uint128_support.cmake b/etc/cmake/uint128_support.cmake new file mode 100644 index 0000000..968c50a --- /dev/null +++ b/etc/cmake/uint128_support.cmake @@ -0,0 +1,43 @@ +include(CheckCXXSourceCompiles) +include(CheckTypeSize) + +cmake_push_check_state(RESET) + +# Check whether the compiler supports the _umul128() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long long a = 0, b = 0; + unsigned long long c; + volatile unsigned long long d; + d = _umul128(a, b, &c); + return 0; + } + " + HAVE__UMUL128 +) + +# Check whether the compiler supports the __umulh() intrinsic +check_cxx_source_compiles(" + #include + + int main(void) { + unsigned long long a = 0, b = 0; + volatile unsigned long long c; + c = __umulh(a, b); + return 0; + } + " + HAVE___UMULH +) + +# Check whether the compiler has __uint128_t +check_type_size("__uint128_t" UINT128 LANGUAGE CXX) +if(UINT128 EQUAL 16) + set(HAVE___UINT128_T ON) +else() + set(HAVE___UINT128_T OFF) +endif() + +cmake_pop_check_state() diff --git a/etc/cmake/version.cmake b/etc/cmake/version.cmake new file mode 100644 index 0000000..895441a --- /dev/null +++ b/etc/cmake/version.cmake @@ -0,0 +1,90 @@ +include(GetGitRevisionDescription) + +# At this point, igraph is either the main CMake project or a subproject of +# another project. CMAKE_SOURCE_DIR would point to the root of the main +# project if we are a subproject so we cannot use that; we need to use +# CMAKE_CURRENT_SOURCE_DIR to get the directory containing the CMakeLists.txt +# file that version.cmake was included from, which is the top-level +# CMakeLists.txt file of igraph itself +set(VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/IGRAPH_VERSION") +set(NEXT_VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/NEXT_VERSION") + +if(EXISTS "${VERSION_FILE}") + file(READ "${VERSION_FILE}" PACKAGE_VERSION) + string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) + message(STATUS "Version number: ${PACKAGE_VERSION}") +else() + find_package(Git QUIET) + if(Git_FOUND) + git_describe(PACKAGE_VERSION) + else() + set(PACKAGE_VERSION "NOTFOUND") + endif() + + if(PACKAGE_VERSION) + if(EXISTS "${NEXT_VERSION_FILE}") + file(READ "${NEXT_VERSION_FILE}" PACKAGE_VERSION) + string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) + get_git_head_revision(GIT_REFSPEC GIT_COMMIT_HASH) + string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 GIT_COMMIT_HASH_SHORT) + string(APPEND PACKAGE_VERSION "-dev+${GIT_COMMIT_HASH_SHORT}") + endif() + message(STATUS "Version number from Git: ${PACKAGE_VERSION}") + elseif(EXISTS "${NEXT_VERSION_FILE}") + file(READ "${NEXT_VERSION_FILE}" PACKAGE_VERSION) + string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) + string(APPEND PACKAGE_VERSION "-dev") + message(STATUS "Version number: ${PACKAGE_VERSION}") + else() + message(STATUS "Cannot find out the version number of this package; IGRAPH_VERSION is missing.") + message(STATUS "") + message(STATUS "The official igraph tarballs should contain this file, therefore you are") + message(STATUS "most likely trying to compile a development version yourself. The development") + message(STATUS "versions need Git to be able to determine the version number of igraph.") + message(STATUS "") + if(Git_FOUND) + message(STATUS "It seems like you do have Git but it failed to determine the package version number.") + message(STATUS "") + message(STATUS "Git was found at: ${GIT_EXECUTABLE}") + message(STATUS "The version number detection failed with: ${PACKAGE_VERSION}") + message(STATUS "") + message(STATUS "Most frequently this is caused by a shallow Git checkout that contains no tags in the history.") + else() + message(STATUS "Please install Git, make sure it is in your path, and then try again.") + endif() + message(STATUS "") + message(FATAL_ERROR "Configuration failed.") + endif() +endif() + +string(REGEX MATCH "^[^-]+" PACKAGE_VERSION_BASE "${PACKAGE_VERSION}") +string( + REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9+])" "\\1;\\2;\\3" + PACKAGE_VERSION_PARTS "${PACKAGE_VERSION_BASE}" +) +list(GET PACKAGE_VERSION_PARTS 0 PACKAGE_VERSION_MAJOR) +list(GET PACKAGE_VERSION_PARTS 1 PACKAGE_VERSION_MINOR) +list(GET PACKAGE_VERSION_PARTS 2 PACKAGE_VERSION_PATCH) + +if(PACKAGE_VERSION MATCHES "^[^-]+-") + string( + REGEX REPLACE "^[^-]+-([^+]*)" "\\1" PACKAGE_VERSION_PRERELEASE "${PACKAGE_VERSION}" + ) +else() + set(PACKAGE_VERSION_PRERELEASE "cmake-experimental") +endif() + +# Add a target that we can use to generate an IGRAPH_VERSION file in the build +# folder, for the sake of creating a tarball. This is needed only if igraph is +# the main project +if(NOT PROJECT_NAME) + add_custom_target( + versionfile + BYPRODUCTS "${CMAKE_BINARY_DIR}/IGRAPH_VERSION" + COMMAND "${CMAKE_COMMAND}" + -DIGRAPH_VERSION="${PACKAGE_VERSION}" + -DVERSION_FILE_PATH="${CMAKE_BINARY_DIR}/IGRAPH_VERSION" + -P "${CMAKE_SOURCE_DIR}/etc/cmake/create_igraph_version_file.cmake" + COMMENT "Generating IGRAPH_VERSION file in build folder" + ) +endif() diff --git a/examples/simple/adjlist.c b/examples/simple/adjlist.c new file mode 100644 index 0000000..fbd916e --- /dev/null +++ b/examples/simple/adjlist.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g, g2; + igraph_adjlist_t adjlist; + igraph_bool_t iso; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed out-tree, convert it into an adjacency list + * representation, then reconstruct the graph from the tree and check + * whether the two are isomorphic (they should be) */ + + igraph_kary_tree(&g, 42, 3, IGRAPH_TREE_OUT); + igraph_adjlist_init(&g, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + igraph_adjlist(&g2, &adjlist, IGRAPH_OUT, /* duplicate = */ 0); + igraph_isomorphic(&g, &g2, &iso); + if (!iso) { + return 1; + } + igraph_adjlist_destroy(&adjlist); + igraph_destroy(&g2); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/ak-4102.max b/examples/simple/ak-4102.max new file mode 100644 index 0000000..0ff4ab6 --- /dev/null +++ b/examples/simple/ak-4102.max @@ -0,0 +1,24623 @@ +c very bad maxflow problem +p max 16414 24619 +n 1 s +n 2 t +a 3 4 4103 +a 3 4106 1 +a 4 5 4102 +a 4 4106 1 +a 5 6 4101 +a 5 4106 1 +a 6 7 4100 +a 6 4106 1 +a 7 8 4099 +a 7 4106 1 +a 8 9 4098 +a 8 4106 1 +a 9 10 4097 +a 9 4106 1 +a 10 11 4096 +a 10 4106 1 +a 11 12 4095 +a 11 4106 1 +a 12 13 4094 +a 12 4106 1 +a 13 14 4093 +a 13 4106 1 +a 14 15 4092 +a 14 4106 1 +a 15 16 4091 +a 15 4106 1 +a 16 17 4090 +a 16 4106 1 +a 17 18 4089 +a 17 4106 1 +a 18 19 4088 +a 18 4106 1 +a 19 20 4087 +a 19 4106 1 +a 20 21 4086 +a 20 4106 1 +a 21 22 4085 +a 21 4106 1 +a 22 23 4084 +a 22 4106 1 +a 23 24 4083 +a 23 4106 1 +a 24 25 4082 +a 24 4106 1 +a 25 26 4081 +a 25 4106 1 +a 26 27 4080 +a 26 4106 1 +a 27 28 4079 +a 27 4106 1 +a 28 29 4078 +a 28 4106 1 +a 29 30 4077 +a 29 4106 1 +a 30 31 4076 +a 30 4106 1 +a 31 32 4075 +a 31 4106 1 +a 32 33 4074 +a 32 4106 1 +a 33 34 4073 +a 33 4106 1 +a 34 35 4072 +a 34 4106 1 +a 35 36 4071 +a 35 4106 1 +a 36 37 4070 +a 36 4106 1 +a 37 38 4069 +a 37 4106 1 +a 38 39 4068 +a 38 4106 1 +a 39 40 4067 +a 39 4106 1 +a 40 41 4066 +a 40 4106 1 +a 41 42 4065 +a 41 4106 1 +a 42 43 4064 +a 42 4106 1 +a 43 44 4063 +a 43 4106 1 +a 44 45 4062 +a 44 4106 1 +a 45 46 4061 +a 45 4106 1 +a 46 47 4060 +a 46 4106 1 +a 47 48 4059 +a 47 4106 1 +a 48 49 4058 +a 48 4106 1 +a 49 50 4057 +a 49 4106 1 +a 50 51 4056 +a 50 4106 1 +a 51 52 4055 +a 51 4106 1 +a 52 53 4054 +a 52 4106 1 +a 53 54 4053 +a 53 4106 1 +a 54 55 4052 +a 54 4106 1 +a 55 56 4051 +a 55 4106 1 +a 56 57 4050 +a 56 4106 1 +a 57 58 4049 +a 57 4106 1 +a 58 59 4048 +a 58 4106 1 +a 59 60 4047 +a 59 4106 1 +a 60 61 4046 +a 60 4106 1 +a 61 62 4045 +a 61 4106 1 +a 62 63 4044 +a 62 4106 1 +a 63 64 4043 +a 63 4106 1 +a 64 65 4042 +a 64 4106 1 +a 65 66 4041 +a 65 4106 1 +a 66 67 4040 +a 66 4106 1 +a 67 68 4039 +a 67 4106 1 +a 68 69 4038 +a 68 4106 1 +a 69 70 4037 +a 69 4106 1 +a 70 71 4036 +a 70 4106 1 +a 71 72 4035 +a 71 4106 1 +a 72 73 4034 +a 72 4106 1 +a 73 74 4033 +a 73 4106 1 +a 74 75 4032 +a 74 4106 1 +a 75 76 4031 +a 75 4106 1 +a 76 77 4030 +a 76 4106 1 +a 77 78 4029 +a 77 4106 1 +a 78 79 4028 +a 78 4106 1 +a 79 80 4027 +a 79 4106 1 +a 80 81 4026 +a 80 4106 1 +a 81 82 4025 +a 81 4106 1 +a 82 83 4024 +a 82 4106 1 +a 83 84 4023 +a 83 4106 1 +a 84 85 4022 +a 84 4106 1 +a 85 86 4021 +a 85 4106 1 +a 86 87 4020 +a 86 4106 1 +a 87 88 4019 +a 87 4106 1 +a 88 89 4018 +a 88 4106 1 +a 89 90 4017 +a 89 4106 1 +a 90 91 4016 +a 90 4106 1 +a 91 92 4015 +a 91 4106 1 +a 92 93 4014 +a 92 4106 1 +a 93 94 4013 +a 93 4106 1 +a 94 95 4012 +a 94 4106 1 +a 95 96 4011 +a 95 4106 1 +a 96 97 4010 +a 96 4106 1 +a 97 98 4009 +a 97 4106 1 +a 98 99 4008 +a 98 4106 1 +a 99 100 4007 +a 99 4106 1 +a 100 101 4006 +a 100 4106 1 +a 101 102 4005 +a 101 4106 1 +a 102 103 4004 +a 102 4106 1 +a 103 104 4003 +a 103 4106 1 +a 104 105 4002 +a 104 4106 1 +a 105 106 4001 +a 105 4106 1 +a 106 107 4000 +a 106 4106 1 +a 107 108 3999 +a 107 4106 1 +a 108 109 3998 +a 108 4106 1 +a 109 110 3997 +a 109 4106 1 +a 110 111 3996 +a 110 4106 1 +a 111 112 3995 +a 111 4106 1 +a 112 113 3994 +a 112 4106 1 +a 113 114 3993 +a 113 4106 1 +a 114 115 3992 +a 114 4106 1 +a 115 116 3991 +a 115 4106 1 +a 116 117 3990 +a 116 4106 1 +a 117 118 3989 +a 117 4106 1 +a 118 119 3988 +a 118 4106 1 +a 119 120 3987 +a 119 4106 1 +a 120 121 3986 +a 120 4106 1 +a 121 122 3985 +a 121 4106 1 +a 122 123 3984 +a 122 4106 1 +a 123 124 3983 +a 123 4106 1 +a 124 125 3982 +a 124 4106 1 +a 125 126 3981 +a 125 4106 1 +a 126 127 3980 +a 126 4106 1 +a 127 128 3979 +a 127 4106 1 +a 128 129 3978 +a 128 4106 1 +a 129 130 3977 +a 129 4106 1 +a 130 131 3976 +a 130 4106 1 +a 131 132 3975 +a 131 4106 1 +a 132 133 3974 +a 132 4106 1 +a 133 134 3973 +a 133 4106 1 +a 134 135 3972 +a 134 4106 1 +a 135 136 3971 +a 135 4106 1 +a 136 137 3970 +a 136 4106 1 +a 137 138 3969 +a 137 4106 1 +a 138 139 3968 +a 138 4106 1 +a 139 140 3967 +a 139 4106 1 +a 140 141 3966 +a 140 4106 1 +a 141 142 3965 +a 141 4106 1 +a 142 143 3964 +a 142 4106 1 +a 143 144 3963 +a 143 4106 1 +a 144 145 3962 +a 144 4106 1 +a 145 146 3961 +a 145 4106 1 +a 146 147 3960 +a 146 4106 1 +a 147 148 3959 +a 147 4106 1 +a 148 149 3958 +a 148 4106 1 +a 149 150 3957 +a 149 4106 1 +a 150 151 3956 +a 150 4106 1 +a 151 152 3955 +a 151 4106 1 +a 152 153 3954 +a 152 4106 1 +a 153 154 3953 +a 153 4106 1 +a 154 155 3952 +a 154 4106 1 +a 155 156 3951 +a 155 4106 1 +a 156 157 3950 +a 156 4106 1 +a 157 158 3949 +a 157 4106 1 +a 158 159 3948 +a 158 4106 1 +a 159 160 3947 +a 159 4106 1 +a 160 161 3946 +a 160 4106 1 +a 161 162 3945 +a 161 4106 1 +a 162 163 3944 +a 162 4106 1 +a 163 164 3943 +a 163 4106 1 +a 164 165 3942 +a 164 4106 1 +a 165 166 3941 +a 165 4106 1 +a 166 167 3940 +a 166 4106 1 +a 167 168 3939 +a 167 4106 1 +a 168 169 3938 +a 168 4106 1 +a 169 170 3937 +a 169 4106 1 +a 170 171 3936 +a 170 4106 1 +a 171 172 3935 +a 171 4106 1 +a 172 173 3934 +a 172 4106 1 +a 173 174 3933 +a 173 4106 1 +a 174 175 3932 +a 174 4106 1 +a 175 176 3931 +a 175 4106 1 +a 176 177 3930 +a 176 4106 1 +a 177 178 3929 +a 177 4106 1 +a 178 179 3928 +a 178 4106 1 +a 179 180 3927 +a 179 4106 1 +a 180 181 3926 +a 180 4106 1 +a 181 182 3925 +a 181 4106 1 +a 182 183 3924 +a 182 4106 1 +a 183 184 3923 +a 183 4106 1 +a 184 185 3922 +a 184 4106 1 +a 185 186 3921 +a 185 4106 1 +a 186 187 3920 +a 186 4106 1 +a 187 188 3919 +a 187 4106 1 +a 188 189 3918 +a 188 4106 1 +a 189 190 3917 +a 189 4106 1 +a 190 191 3916 +a 190 4106 1 +a 191 192 3915 +a 191 4106 1 +a 192 193 3914 +a 192 4106 1 +a 193 194 3913 +a 193 4106 1 +a 194 195 3912 +a 194 4106 1 +a 195 196 3911 +a 195 4106 1 +a 196 197 3910 +a 196 4106 1 +a 197 198 3909 +a 197 4106 1 +a 198 199 3908 +a 198 4106 1 +a 199 200 3907 +a 199 4106 1 +a 200 201 3906 +a 200 4106 1 +a 201 202 3905 +a 201 4106 1 +a 202 203 3904 +a 202 4106 1 +a 203 204 3903 +a 203 4106 1 +a 204 205 3902 +a 204 4106 1 +a 205 206 3901 +a 205 4106 1 +a 206 207 3900 +a 206 4106 1 +a 207 208 3899 +a 207 4106 1 +a 208 209 3898 +a 208 4106 1 +a 209 210 3897 +a 209 4106 1 +a 210 211 3896 +a 210 4106 1 +a 211 212 3895 +a 211 4106 1 +a 212 213 3894 +a 212 4106 1 +a 213 214 3893 +a 213 4106 1 +a 214 215 3892 +a 214 4106 1 +a 215 216 3891 +a 215 4106 1 +a 216 217 3890 +a 216 4106 1 +a 217 218 3889 +a 217 4106 1 +a 218 219 3888 +a 218 4106 1 +a 219 220 3887 +a 219 4106 1 +a 220 221 3886 +a 220 4106 1 +a 221 222 3885 +a 221 4106 1 +a 222 223 3884 +a 222 4106 1 +a 223 224 3883 +a 223 4106 1 +a 224 225 3882 +a 224 4106 1 +a 225 226 3881 +a 225 4106 1 +a 226 227 3880 +a 226 4106 1 +a 227 228 3879 +a 227 4106 1 +a 228 229 3878 +a 228 4106 1 +a 229 230 3877 +a 229 4106 1 +a 230 231 3876 +a 230 4106 1 +a 231 232 3875 +a 231 4106 1 +a 232 233 3874 +a 232 4106 1 +a 233 234 3873 +a 233 4106 1 +a 234 235 3872 +a 234 4106 1 +a 235 236 3871 +a 235 4106 1 +a 236 237 3870 +a 236 4106 1 +a 237 238 3869 +a 237 4106 1 +a 238 239 3868 +a 238 4106 1 +a 239 240 3867 +a 239 4106 1 +a 240 241 3866 +a 240 4106 1 +a 241 242 3865 +a 241 4106 1 +a 242 243 3864 +a 242 4106 1 +a 243 244 3863 +a 243 4106 1 +a 244 245 3862 +a 244 4106 1 +a 245 246 3861 +a 245 4106 1 +a 246 247 3860 +a 246 4106 1 +a 247 248 3859 +a 247 4106 1 +a 248 249 3858 +a 248 4106 1 +a 249 250 3857 +a 249 4106 1 +a 250 251 3856 +a 250 4106 1 +a 251 252 3855 +a 251 4106 1 +a 252 253 3854 +a 252 4106 1 +a 253 254 3853 +a 253 4106 1 +a 254 255 3852 +a 254 4106 1 +a 255 256 3851 +a 255 4106 1 +a 256 257 3850 +a 256 4106 1 +a 257 258 3849 +a 257 4106 1 +a 258 259 3848 +a 258 4106 1 +a 259 260 3847 +a 259 4106 1 +a 260 261 3846 +a 260 4106 1 +a 261 262 3845 +a 261 4106 1 +a 262 263 3844 +a 262 4106 1 +a 263 264 3843 +a 263 4106 1 +a 264 265 3842 +a 264 4106 1 +a 265 266 3841 +a 265 4106 1 +a 266 267 3840 +a 266 4106 1 +a 267 268 3839 +a 267 4106 1 +a 268 269 3838 +a 268 4106 1 +a 269 270 3837 +a 269 4106 1 +a 270 271 3836 +a 270 4106 1 +a 271 272 3835 +a 271 4106 1 +a 272 273 3834 +a 272 4106 1 +a 273 274 3833 +a 273 4106 1 +a 274 275 3832 +a 274 4106 1 +a 275 276 3831 +a 275 4106 1 +a 276 277 3830 +a 276 4106 1 +a 277 278 3829 +a 277 4106 1 +a 278 279 3828 +a 278 4106 1 +a 279 280 3827 +a 279 4106 1 +a 280 281 3826 +a 280 4106 1 +a 281 282 3825 +a 281 4106 1 +a 282 283 3824 +a 282 4106 1 +a 283 284 3823 +a 283 4106 1 +a 284 285 3822 +a 284 4106 1 +a 285 286 3821 +a 285 4106 1 +a 286 287 3820 +a 286 4106 1 +a 287 288 3819 +a 287 4106 1 +a 288 289 3818 +a 288 4106 1 +a 289 290 3817 +a 289 4106 1 +a 290 291 3816 +a 290 4106 1 +a 291 292 3815 +a 291 4106 1 +a 292 293 3814 +a 292 4106 1 +a 293 294 3813 +a 293 4106 1 +a 294 295 3812 +a 294 4106 1 +a 295 296 3811 +a 295 4106 1 +a 296 297 3810 +a 296 4106 1 +a 297 298 3809 +a 297 4106 1 +a 298 299 3808 +a 298 4106 1 +a 299 300 3807 +a 299 4106 1 +a 300 301 3806 +a 300 4106 1 +a 301 302 3805 +a 301 4106 1 +a 302 303 3804 +a 302 4106 1 +a 303 304 3803 +a 303 4106 1 +a 304 305 3802 +a 304 4106 1 +a 305 306 3801 +a 305 4106 1 +a 306 307 3800 +a 306 4106 1 +a 307 308 3799 +a 307 4106 1 +a 308 309 3798 +a 308 4106 1 +a 309 310 3797 +a 309 4106 1 +a 310 311 3796 +a 310 4106 1 +a 311 312 3795 +a 311 4106 1 +a 312 313 3794 +a 312 4106 1 +a 313 314 3793 +a 313 4106 1 +a 314 315 3792 +a 314 4106 1 +a 315 316 3791 +a 315 4106 1 +a 316 317 3790 +a 316 4106 1 +a 317 318 3789 +a 317 4106 1 +a 318 319 3788 +a 318 4106 1 +a 319 320 3787 +a 319 4106 1 +a 320 321 3786 +a 320 4106 1 +a 321 322 3785 +a 321 4106 1 +a 322 323 3784 +a 322 4106 1 +a 323 324 3783 +a 323 4106 1 +a 324 325 3782 +a 324 4106 1 +a 325 326 3781 +a 325 4106 1 +a 326 327 3780 +a 326 4106 1 +a 327 328 3779 +a 327 4106 1 +a 328 329 3778 +a 328 4106 1 +a 329 330 3777 +a 329 4106 1 +a 330 331 3776 +a 330 4106 1 +a 331 332 3775 +a 331 4106 1 +a 332 333 3774 +a 332 4106 1 +a 333 334 3773 +a 333 4106 1 +a 334 335 3772 +a 334 4106 1 +a 335 336 3771 +a 335 4106 1 +a 336 337 3770 +a 336 4106 1 +a 337 338 3769 +a 337 4106 1 +a 338 339 3768 +a 338 4106 1 +a 339 340 3767 +a 339 4106 1 +a 340 341 3766 +a 340 4106 1 +a 341 342 3765 +a 341 4106 1 +a 342 343 3764 +a 342 4106 1 +a 343 344 3763 +a 343 4106 1 +a 344 345 3762 +a 344 4106 1 +a 345 346 3761 +a 345 4106 1 +a 346 347 3760 +a 346 4106 1 +a 347 348 3759 +a 347 4106 1 +a 348 349 3758 +a 348 4106 1 +a 349 350 3757 +a 349 4106 1 +a 350 351 3756 +a 350 4106 1 +a 351 352 3755 +a 351 4106 1 +a 352 353 3754 +a 352 4106 1 +a 353 354 3753 +a 353 4106 1 +a 354 355 3752 +a 354 4106 1 +a 355 356 3751 +a 355 4106 1 +a 356 357 3750 +a 356 4106 1 +a 357 358 3749 +a 357 4106 1 +a 358 359 3748 +a 358 4106 1 +a 359 360 3747 +a 359 4106 1 +a 360 361 3746 +a 360 4106 1 +a 361 362 3745 +a 361 4106 1 +a 362 363 3744 +a 362 4106 1 +a 363 364 3743 +a 363 4106 1 +a 364 365 3742 +a 364 4106 1 +a 365 366 3741 +a 365 4106 1 +a 366 367 3740 +a 366 4106 1 +a 367 368 3739 +a 367 4106 1 +a 368 369 3738 +a 368 4106 1 +a 369 370 3737 +a 369 4106 1 +a 370 371 3736 +a 370 4106 1 +a 371 372 3735 +a 371 4106 1 +a 372 373 3734 +a 372 4106 1 +a 373 374 3733 +a 373 4106 1 +a 374 375 3732 +a 374 4106 1 +a 375 376 3731 +a 375 4106 1 +a 376 377 3730 +a 376 4106 1 +a 377 378 3729 +a 377 4106 1 +a 378 379 3728 +a 378 4106 1 +a 379 380 3727 +a 379 4106 1 +a 380 381 3726 +a 380 4106 1 +a 381 382 3725 +a 381 4106 1 +a 382 383 3724 +a 382 4106 1 +a 383 384 3723 +a 383 4106 1 +a 384 385 3722 +a 384 4106 1 +a 385 386 3721 +a 385 4106 1 +a 386 387 3720 +a 386 4106 1 +a 387 388 3719 +a 387 4106 1 +a 388 389 3718 +a 388 4106 1 +a 389 390 3717 +a 389 4106 1 +a 390 391 3716 +a 390 4106 1 +a 391 392 3715 +a 391 4106 1 +a 392 393 3714 +a 392 4106 1 +a 393 394 3713 +a 393 4106 1 +a 394 395 3712 +a 394 4106 1 +a 395 396 3711 +a 395 4106 1 +a 396 397 3710 +a 396 4106 1 +a 397 398 3709 +a 397 4106 1 +a 398 399 3708 +a 398 4106 1 +a 399 400 3707 +a 399 4106 1 +a 400 401 3706 +a 400 4106 1 +a 401 402 3705 +a 401 4106 1 +a 402 403 3704 +a 402 4106 1 +a 403 404 3703 +a 403 4106 1 +a 404 405 3702 +a 404 4106 1 +a 405 406 3701 +a 405 4106 1 +a 406 407 3700 +a 406 4106 1 +a 407 408 3699 +a 407 4106 1 +a 408 409 3698 +a 408 4106 1 +a 409 410 3697 +a 409 4106 1 +a 410 411 3696 +a 410 4106 1 +a 411 412 3695 +a 411 4106 1 +a 412 413 3694 +a 412 4106 1 +a 413 414 3693 +a 413 4106 1 +a 414 415 3692 +a 414 4106 1 +a 415 416 3691 +a 415 4106 1 +a 416 417 3690 +a 416 4106 1 +a 417 418 3689 +a 417 4106 1 +a 418 419 3688 +a 418 4106 1 +a 419 420 3687 +a 419 4106 1 +a 420 421 3686 +a 420 4106 1 +a 421 422 3685 +a 421 4106 1 +a 422 423 3684 +a 422 4106 1 +a 423 424 3683 +a 423 4106 1 +a 424 425 3682 +a 424 4106 1 +a 425 426 3681 +a 425 4106 1 +a 426 427 3680 +a 426 4106 1 +a 427 428 3679 +a 427 4106 1 +a 428 429 3678 +a 428 4106 1 +a 429 430 3677 +a 429 4106 1 +a 430 431 3676 +a 430 4106 1 +a 431 432 3675 +a 431 4106 1 +a 432 433 3674 +a 432 4106 1 +a 433 434 3673 +a 433 4106 1 +a 434 435 3672 +a 434 4106 1 +a 435 436 3671 +a 435 4106 1 +a 436 437 3670 +a 436 4106 1 +a 437 438 3669 +a 437 4106 1 +a 438 439 3668 +a 438 4106 1 +a 439 440 3667 +a 439 4106 1 +a 440 441 3666 +a 440 4106 1 +a 441 442 3665 +a 441 4106 1 +a 442 443 3664 +a 442 4106 1 +a 443 444 3663 +a 443 4106 1 +a 444 445 3662 +a 444 4106 1 +a 445 446 3661 +a 445 4106 1 +a 446 447 3660 +a 446 4106 1 +a 447 448 3659 +a 447 4106 1 +a 448 449 3658 +a 448 4106 1 +a 449 450 3657 +a 449 4106 1 +a 450 451 3656 +a 450 4106 1 +a 451 452 3655 +a 451 4106 1 +a 452 453 3654 +a 452 4106 1 +a 453 454 3653 +a 453 4106 1 +a 454 455 3652 +a 454 4106 1 +a 455 456 3651 +a 455 4106 1 +a 456 457 3650 +a 456 4106 1 +a 457 458 3649 +a 457 4106 1 +a 458 459 3648 +a 458 4106 1 +a 459 460 3647 +a 459 4106 1 +a 460 461 3646 +a 460 4106 1 +a 461 462 3645 +a 461 4106 1 +a 462 463 3644 +a 462 4106 1 +a 463 464 3643 +a 463 4106 1 +a 464 465 3642 +a 464 4106 1 +a 465 466 3641 +a 465 4106 1 +a 466 467 3640 +a 466 4106 1 +a 467 468 3639 +a 467 4106 1 +a 468 469 3638 +a 468 4106 1 +a 469 470 3637 +a 469 4106 1 +a 470 471 3636 +a 470 4106 1 +a 471 472 3635 +a 471 4106 1 +a 472 473 3634 +a 472 4106 1 +a 473 474 3633 +a 473 4106 1 +a 474 475 3632 +a 474 4106 1 +a 475 476 3631 +a 475 4106 1 +a 476 477 3630 +a 476 4106 1 +a 477 478 3629 +a 477 4106 1 +a 478 479 3628 +a 478 4106 1 +a 479 480 3627 +a 479 4106 1 +a 480 481 3626 +a 480 4106 1 +a 481 482 3625 +a 481 4106 1 +a 482 483 3624 +a 482 4106 1 +a 483 484 3623 +a 483 4106 1 +a 484 485 3622 +a 484 4106 1 +a 485 486 3621 +a 485 4106 1 +a 486 487 3620 +a 486 4106 1 +a 487 488 3619 +a 487 4106 1 +a 488 489 3618 +a 488 4106 1 +a 489 490 3617 +a 489 4106 1 +a 490 491 3616 +a 490 4106 1 +a 491 492 3615 +a 491 4106 1 +a 492 493 3614 +a 492 4106 1 +a 493 494 3613 +a 493 4106 1 +a 494 495 3612 +a 494 4106 1 +a 495 496 3611 +a 495 4106 1 +a 496 497 3610 +a 496 4106 1 +a 497 498 3609 +a 497 4106 1 +a 498 499 3608 +a 498 4106 1 +a 499 500 3607 +a 499 4106 1 +a 500 501 3606 +a 500 4106 1 +a 501 502 3605 +a 501 4106 1 +a 502 503 3604 +a 502 4106 1 +a 503 504 3603 +a 503 4106 1 +a 504 505 3602 +a 504 4106 1 +a 505 506 3601 +a 505 4106 1 +a 506 507 3600 +a 506 4106 1 +a 507 508 3599 +a 507 4106 1 +a 508 509 3598 +a 508 4106 1 +a 509 510 3597 +a 509 4106 1 +a 510 511 3596 +a 510 4106 1 +a 511 512 3595 +a 511 4106 1 +a 512 513 3594 +a 512 4106 1 +a 513 514 3593 +a 513 4106 1 +a 514 515 3592 +a 514 4106 1 +a 515 516 3591 +a 515 4106 1 +a 516 517 3590 +a 516 4106 1 +a 517 518 3589 +a 517 4106 1 +a 518 519 3588 +a 518 4106 1 +a 519 520 3587 +a 519 4106 1 +a 520 521 3586 +a 520 4106 1 +a 521 522 3585 +a 521 4106 1 +a 522 523 3584 +a 522 4106 1 +a 523 524 3583 +a 523 4106 1 +a 524 525 3582 +a 524 4106 1 +a 525 526 3581 +a 525 4106 1 +a 526 527 3580 +a 526 4106 1 +a 527 528 3579 +a 527 4106 1 +a 528 529 3578 +a 528 4106 1 +a 529 530 3577 +a 529 4106 1 +a 530 531 3576 +a 530 4106 1 +a 531 532 3575 +a 531 4106 1 +a 532 533 3574 +a 532 4106 1 +a 533 534 3573 +a 533 4106 1 +a 534 535 3572 +a 534 4106 1 +a 535 536 3571 +a 535 4106 1 +a 536 537 3570 +a 536 4106 1 +a 537 538 3569 +a 537 4106 1 +a 538 539 3568 +a 538 4106 1 +a 539 540 3567 +a 539 4106 1 +a 540 541 3566 +a 540 4106 1 +a 541 542 3565 +a 541 4106 1 +a 542 543 3564 +a 542 4106 1 +a 543 544 3563 +a 543 4106 1 +a 544 545 3562 +a 544 4106 1 +a 545 546 3561 +a 545 4106 1 +a 546 547 3560 +a 546 4106 1 +a 547 548 3559 +a 547 4106 1 +a 548 549 3558 +a 548 4106 1 +a 549 550 3557 +a 549 4106 1 +a 550 551 3556 +a 550 4106 1 +a 551 552 3555 +a 551 4106 1 +a 552 553 3554 +a 552 4106 1 +a 553 554 3553 +a 553 4106 1 +a 554 555 3552 +a 554 4106 1 +a 555 556 3551 +a 555 4106 1 +a 556 557 3550 +a 556 4106 1 +a 557 558 3549 +a 557 4106 1 +a 558 559 3548 +a 558 4106 1 +a 559 560 3547 +a 559 4106 1 +a 560 561 3546 +a 560 4106 1 +a 561 562 3545 +a 561 4106 1 +a 562 563 3544 +a 562 4106 1 +a 563 564 3543 +a 563 4106 1 +a 564 565 3542 +a 564 4106 1 +a 565 566 3541 +a 565 4106 1 +a 566 567 3540 +a 566 4106 1 +a 567 568 3539 +a 567 4106 1 +a 568 569 3538 +a 568 4106 1 +a 569 570 3537 +a 569 4106 1 +a 570 571 3536 +a 570 4106 1 +a 571 572 3535 +a 571 4106 1 +a 572 573 3534 +a 572 4106 1 +a 573 574 3533 +a 573 4106 1 +a 574 575 3532 +a 574 4106 1 +a 575 576 3531 +a 575 4106 1 +a 576 577 3530 +a 576 4106 1 +a 577 578 3529 +a 577 4106 1 +a 578 579 3528 +a 578 4106 1 +a 579 580 3527 +a 579 4106 1 +a 580 581 3526 +a 580 4106 1 +a 581 582 3525 +a 581 4106 1 +a 582 583 3524 +a 582 4106 1 +a 583 584 3523 +a 583 4106 1 +a 584 585 3522 +a 584 4106 1 +a 585 586 3521 +a 585 4106 1 +a 586 587 3520 +a 586 4106 1 +a 587 588 3519 +a 587 4106 1 +a 588 589 3518 +a 588 4106 1 +a 589 590 3517 +a 589 4106 1 +a 590 591 3516 +a 590 4106 1 +a 591 592 3515 +a 591 4106 1 +a 592 593 3514 +a 592 4106 1 +a 593 594 3513 +a 593 4106 1 +a 594 595 3512 +a 594 4106 1 +a 595 596 3511 +a 595 4106 1 +a 596 597 3510 +a 596 4106 1 +a 597 598 3509 +a 597 4106 1 +a 598 599 3508 +a 598 4106 1 +a 599 600 3507 +a 599 4106 1 +a 600 601 3506 +a 600 4106 1 +a 601 602 3505 +a 601 4106 1 +a 602 603 3504 +a 602 4106 1 +a 603 604 3503 +a 603 4106 1 +a 604 605 3502 +a 604 4106 1 +a 605 606 3501 +a 605 4106 1 +a 606 607 3500 +a 606 4106 1 +a 607 608 3499 +a 607 4106 1 +a 608 609 3498 +a 608 4106 1 +a 609 610 3497 +a 609 4106 1 +a 610 611 3496 +a 610 4106 1 +a 611 612 3495 +a 611 4106 1 +a 612 613 3494 +a 612 4106 1 +a 613 614 3493 +a 613 4106 1 +a 614 615 3492 +a 614 4106 1 +a 615 616 3491 +a 615 4106 1 +a 616 617 3490 +a 616 4106 1 +a 617 618 3489 +a 617 4106 1 +a 618 619 3488 +a 618 4106 1 +a 619 620 3487 +a 619 4106 1 +a 620 621 3486 +a 620 4106 1 +a 621 622 3485 +a 621 4106 1 +a 622 623 3484 +a 622 4106 1 +a 623 624 3483 +a 623 4106 1 +a 624 625 3482 +a 624 4106 1 +a 625 626 3481 +a 625 4106 1 +a 626 627 3480 +a 626 4106 1 +a 627 628 3479 +a 627 4106 1 +a 628 629 3478 +a 628 4106 1 +a 629 630 3477 +a 629 4106 1 +a 630 631 3476 +a 630 4106 1 +a 631 632 3475 +a 631 4106 1 +a 632 633 3474 +a 632 4106 1 +a 633 634 3473 +a 633 4106 1 +a 634 635 3472 +a 634 4106 1 +a 635 636 3471 +a 635 4106 1 +a 636 637 3470 +a 636 4106 1 +a 637 638 3469 +a 637 4106 1 +a 638 639 3468 +a 638 4106 1 +a 639 640 3467 +a 639 4106 1 +a 640 641 3466 +a 640 4106 1 +a 641 642 3465 +a 641 4106 1 +a 642 643 3464 +a 642 4106 1 +a 643 644 3463 +a 643 4106 1 +a 644 645 3462 +a 644 4106 1 +a 645 646 3461 +a 645 4106 1 +a 646 647 3460 +a 646 4106 1 +a 647 648 3459 +a 647 4106 1 +a 648 649 3458 +a 648 4106 1 +a 649 650 3457 +a 649 4106 1 +a 650 651 3456 +a 650 4106 1 +a 651 652 3455 +a 651 4106 1 +a 652 653 3454 +a 652 4106 1 +a 653 654 3453 +a 653 4106 1 +a 654 655 3452 +a 654 4106 1 +a 655 656 3451 +a 655 4106 1 +a 656 657 3450 +a 656 4106 1 +a 657 658 3449 +a 657 4106 1 +a 658 659 3448 +a 658 4106 1 +a 659 660 3447 +a 659 4106 1 +a 660 661 3446 +a 660 4106 1 +a 661 662 3445 +a 661 4106 1 +a 662 663 3444 +a 662 4106 1 +a 663 664 3443 +a 663 4106 1 +a 664 665 3442 +a 664 4106 1 +a 665 666 3441 +a 665 4106 1 +a 666 667 3440 +a 666 4106 1 +a 667 668 3439 +a 667 4106 1 +a 668 669 3438 +a 668 4106 1 +a 669 670 3437 +a 669 4106 1 +a 670 671 3436 +a 670 4106 1 +a 671 672 3435 +a 671 4106 1 +a 672 673 3434 +a 672 4106 1 +a 673 674 3433 +a 673 4106 1 +a 674 675 3432 +a 674 4106 1 +a 675 676 3431 +a 675 4106 1 +a 676 677 3430 +a 676 4106 1 +a 677 678 3429 +a 677 4106 1 +a 678 679 3428 +a 678 4106 1 +a 679 680 3427 +a 679 4106 1 +a 680 681 3426 +a 680 4106 1 +a 681 682 3425 +a 681 4106 1 +a 682 683 3424 +a 682 4106 1 +a 683 684 3423 +a 683 4106 1 +a 684 685 3422 +a 684 4106 1 +a 685 686 3421 +a 685 4106 1 +a 686 687 3420 +a 686 4106 1 +a 687 688 3419 +a 687 4106 1 +a 688 689 3418 +a 688 4106 1 +a 689 690 3417 +a 689 4106 1 +a 690 691 3416 +a 690 4106 1 +a 691 692 3415 +a 691 4106 1 +a 692 693 3414 +a 692 4106 1 +a 693 694 3413 +a 693 4106 1 +a 694 695 3412 +a 694 4106 1 +a 695 696 3411 +a 695 4106 1 +a 696 697 3410 +a 696 4106 1 +a 697 698 3409 +a 697 4106 1 +a 698 699 3408 +a 698 4106 1 +a 699 700 3407 +a 699 4106 1 +a 700 701 3406 +a 700 4106 1 +a 701 702 3405 +a 701 4106 1 +a 702 703 3404 +a 702 4106 1 +a 703 704 3403 +a 703 4106 1 +a 704 705 3402 +a 704 4106 1 +a 705 706 3401 +a 705 4106 1 +a 706 707 3400 +a 706 4106 1 +a 707 708 3399 +a 707 4106 1 +a 708 709 3398 +a 708 4106 1 +a 709 710 3397 +a 709 4106 1 +a 710 711 3396 +a 710 4106 1 +a 711 712 3395 +a 711 4106 1 +a 712 713 3394 +a 712 4106 1 +a 713 714 3393 +a 713 4106 1 +a 714 715 3392 +a 714 4106 1 +a 715 716 3391 +a 715 4106 1 +a 716 717 3390 +a 716 4106 1 +a 717 718 3389 +a 717 4106 1 +a 718 719 3388 +a 718 4106 1 +a 719 720 3387 +a 719 4106 1 +a 720 721 3386 +a 720 4106 1 +a 721 722 3385 +a 721 4106 1 +a 722 723 3384 +a 722 4106 1 +a 723 724 3383 +a 723 4106 1 +a 724 725 3382 +a 724 4106 1 +a 725 726 3381 +a 725 4106 1 +a 726 727 3380 +a 726 4106 1 +a 727 728 3379 +a 727 4106 1 +a 728 729 3378 +a 728 4106 1 +a 729 730 3377 +a 729 4106 1 +a 730 731 3376 +a 730 4106 1 +a 731 732 3375 +a 731 4106 1 +a 732 733 3374 +a 732 4106 1 +a 733 734 3373 +a 733 4106 1 +a 734 735 3372 +a 734 4106 1 +a 735 736 3371 +a 735 4106 1 +a 736 737 3370 +a 736 4106 1 +a 737 738 3369 +a 737 4106 1 +a 738 739 3368 +a 738 4106 1 +a 739 740 3367 +a 739 4106 1 +a 740 741 3366 +a 740 4106 1 +a 741 742 3365 +a 741 4106 1 +a 742 743 3364 +a 742 4106 1 +a 743 744 3363 +a 743 4106 1 +a 744 745 3362 +a 744 4106 1 +a 745 746 3361 +a 745 4106 1 +a 746 747 3360 +a 746 4106 1 +a 747 748 3359 +a 747 4106 1 +a 748 749 3358 +a 748 4106 1 +a 749 750 3357 +a 749 4106 1 +a 750 751 3356 +a 750 4106 1 +a 751 752 3355 +a 751 4106 1 +a 752 753 3354 +a 752 4106 1 +a 753 754 3353 +a 753 4106 1 +a 754 755 3352 +a 754 4106 1 +a 755 756 3351 +a 755 4106 1 +a 756 757 3350 +a 756 4106 1 +a 757 758 3349 +a 757 4106 1 +a 758 759 3348 +a 758 4106 1 +a 759 760 3347 +a 759 4106 1 +a 760 761 3346 +a 760 4106 1 +a 761 762 3345 +a 761 4106 1 +a 762 763 3344 +a 762 4106 1 +a 763 764 3343 +a 763 4106 1 +a 764 765 3342 +a 764 4106 1 +a 765 766 3341 +a 765 4106 1 +a 766 767 3340 +a 766 4106 1 +a 767 768 3339 +a 767 4106 1 +a 768 769 3338 +a 768 4106 1 +a 769 770 3337 +a 769 4106 1 +a 770 771 3336 +a 770 4106 1 +a 771 772 3335 +a 771 4106 1 +a 772 773 3334 +a 772 4106 1 +a 773 774 3333 +a 773 4106 1 +a 774 775 3332 +a 774 4106 1 +a 775 776 3331 +a 775 4106 1 +a 776 777 3330 +a 776 4106 1 +a 777 778 3329 +a 777 4106 1 +a 778 779 3328 +a 778 4106 1 +a 779 780 3327 +a 779 4106 1 +a 780 781 3326 +a 780 4106 1 +a 781 782 3325 +a 781 4106 1 +a 782 783 3324 +a 782 4106 1 +a 783 784 3323 +a 783 4106 1 +a 784 785 3322 +a 784 4106 1 +a 785 786 3321 +a 785 4106 1 +a 786 787 3320 +a 786 4106 1 +a 787 788 3319 +a 787 4106 1 +a 788 789 3318 +a 788 4106 1 +a 789 790 3317 +a 789 4106 1 +a 790 791 3316 +a 790 4106 1 +a 791 792 3315 +a 791 4106 1 +a 792 793 3314 +a 792 4106 1 +a 793 794 3313 +a 793 4106 1 +a 794 795 3312 +a 794 4106 1 +a 795 796 3311 +a 795 4106 1 +a 796 797 3310 +a 796 4106 1 +a 797 798 3309 +a 797 4106 1 +a 798 799 3308 +a 798 4106 1 +a 799 800 3307 +a 799 4106 1 +a 800 801 3306 +a 800 4106 1 +a 801 802 3305 +a 801 4106 1 +a 802 803 3304 +a 802 4106 1 +a 803 804 3303 +a 803 4106 1 +a 804 805 3302 +a 804 4106 1 +a 805 806 3301 +a 805 4106 1 +a 806 807 3300 +a 806 4106 1 +a 807 808 3299 +a 807 4106 1 +a 808 809 3298 +a 808 4106 1 +a 809 810 3297 +a 809 4106 1 +a 810 811 3296 +a 810 4106 1 +a 811 812 3295 +a 811 4106 1 +a 812 813 3294 +a 812 4106 1 +a 813 814 3293 +a 813 4106 1 +a 814 815 3292 +a 814 4106 1 +a 815 816 3291 +a 815 4106 1 +a 816 817 3290 +a 816 4106 1 +a 817 818 3289 +a 817 4106 1 +a 818 819 3288 +a 818 4106 1 +a 819 820 3287 +a 819 4106 1 +a 820 821 3286 +a 820 4106 1 +a 821 822 3285 +a 821 4106 1 +a 822 823 3284 +a 822 4106 1 +a 823 824 3283 +a 823 4106 1 +a 824 825 3282 +a 824 4106 1 +a 825 826 3281 +a 825 4106 1 +a 826 827 3280 +a 826 4106 1 +a 827 828 3279 +a 827 4106 1 +a 828 829 3278 +a 828 4106 1 +a 829 830 3277 +a 829 4106 1 +a 830 831 3276 +a 830 4106 1 +a 831 832 3275 +a 831 4106 1 +a 832 833 3274 +a 832 4106 1 +a 833 834 3273 +a 833 4106 1 +a 834 835 3272 +a 834 4106 1 +a 835 836 3271 +a 835 4106 1 +a 836 837 3270 +a 836 4106 1 +a 837 838 3269 +a 837 4106 1 +a 838 839 3268 +a 838 4106 1 +a 839 840 3267 +a 839 4106 1 +a 840 841 3266 +a 840 4106 1 +a 841 842 3265 +a 841 4106 1 +a 842 843 3264 +a 842 4106 1 +a 843 844 3263 +a 843 4106 1 +a 844 845 3262 +a 844 4106 1 +a 845 846 3261 +a 845 4106 1 +a 846 847 3260 +a 846 4106 1 +a 847 848 3259 +a 847 4106 1 +a 848 849 3258 +a 848 4106 1 +a 849 850 3257 +a 849 4106 1 +a 850 851 3256 +a 850 4106 1 +a 851 852 3255 +a 851 4106 1 +a 852 853 3254 +a 852 4106 1 +a 853 854 3253 +a 853 4106 1 +a 854 855 3252 +a 854 4106 1 +a 855 856 3251 +a 855 4106 1 +a 856 857 3250 +a 856 4106 1 +a 857 858 3249 +a 857 4106 1 +a 858 859 3248 +a 858 4106 1 +a 859 860 3247 +a 859 4106 1 +a 860 861 3246 +a 860 4106 1 +a 861 862 3245 +a 861 4106 1 +a 862 863 3244 +a 862 4106 1 +a 863 864 3243 +a 863 4106 1 +a 864 865 3242 +a 864 4106 1 +a 865 866 3241 +a 865 4106 1 +a 866 867 3240 +a 866 4106 1 +a 867 868 3239 +a 867 4106 1 +a 868 869 3238 +a 868 4106 1 +a 869 870 3237 +a 869 4106 1 +a 870 871 3236 +a 870 4106 1 +a 871 872 3235 +a 871 4106 1 +a 872 873 3234 +a 872 4106 1 +a 873 874 3233 +a 873 4106 1 +a 874 875 3232 +a 874 4106 1 +a 875 876 3231 +a 875 4106 1 +a 876 877 3230 +a 876 4106 1 +a 877 878 3229 +a 877 4106 1 +a 878 879 3228 +a 878 4106 1 +a 879 880 3227 +a 879 4106 1 +a 880 881 3226 +a 880 4106 1 +a 881 882 3225 +a 881 4106 1 +a 882 883 3224 +a 882 4106 1 +a 883 884 3223 +a 883 4106 1 +a 884 885 3222 +a 884 4106 1 +a 885 886 3221 +a 885 4106 1 +a 886 887 3220 +a 886 4106 1 +a 887 888 3219 +a 887 4106 1 +a 888 889 3218 +a 888 4106 1 +a 889 890 3217 +a 889 4106 1 +a 890 891 3216 +a 890 4106 1 +a 891 892 3215 +a 891 4106 1 +a 892 893 3214 +a 892 4106 1 +a 893 894 3213 +a 893 4106 1 +a 894 895 3212 +a 894 4106 1 +a 895 896 3211 +a 895 4106 1 +a 896 897 3210 +a 896 4106 1 +a 897 898 3209 +a 897 4106 1 +a 898 899 3208 +a 898 4106 1 +a 899 900 3207 +a 899 4106 1 +a 900 901 3206 +a 900 4106 1 +a 901 902 3205 +a 901 4106 1 +a 902 903 3204 +a 902 4106 1 +a 903 904 3203 +a 903 4106 1 +a 904 905 3202 +a 904 4106 1 +a 905 906 3201 +a 905 4106 1 +a 906 907 3200 +a 906 4106 1 +a 907 908 3199 +a 907 4106 1 +a 908 909 3198 +a 908 4106 1 +a 909 910 3197 +a 909 4106 1 +a 910 911 3196 +a 910 4106 1 +a 911 912 3195 +a 911 4106 1 +a 912 913 3194 +a 912 4106 1 +a 913 914 3193 +a 913 4106 1 +a 914 915 3192 +a 914 4106 1 +a 915 916 3191 +a 915 4106 1 +a 916 917 3190 +a 916 4106 1 +a 917 918 3189 +a 917 4106 1 +a 918 919 3188 +a 918 4106 1 +a 919 920 3187 +a 919 4106 1 +a 920 921 3186 +a 920 4106 1 +a 921 922 3185 +a 921 4106 1 +a 922 923 3184 +a 922 4106 1 +a 923 924 3183 +a 923 4106 1 +a 924 925 3182 +a 924 4106 1 +a 925 926 3181 +a 925 4106 1 +a 926 927 3180 +a 926 4106 1 +a 927 928 3179 +a 927 4106 1 +a 928 929 3178 +a 928 4106 1 +a 929 930 3177 +a 929 4106 1 +a 930 931 3176 +a 930 4106 1 +a 931 932 3175 +a 931 4106 1 +a 932 933 3174 +a 932 4106 1 +a 933 934 3173 +a 933 4106 1 +a 934 935 3172 +a 934 4106 1 +a 935 936 3171 +a 935 4106 1 +a 936 937 3170 +a 936 4106 1 +a 937 938 3169 +a 937 4106 1 +a 938 939 3168 +a 938 4106 1 +a 939 940 3167 +a 939 4106 1 +a 940 941 3166 +a 940 4106 1 +a 941 942 3165 +a 941 4106 1 +a 942 943 3164 +a 942 4106 1 +a 943 944 3163 +a 943 4106 1 +a 944 945 3162 +a 944 4106 1 +a 945 946 3161 +a 945 4106 1 +a 946 947 3160 +a 946 4106 1 +a 947 948 3159 +a 947 4106 1 +a 948 949 3158 +a 948 4106 1 +a 949 950 3157 +a 949 4106 1 +a 950 951 3156 +a 950 4106 1 +a 951 952 3155 +a 951 4106 1 +a 952 953 3154 +a 952 4106 1 +a 953 954 3153 +a 953 4106 1 +a 954 955 3152 +a 954 4106 1 +a 955 956 3151 +a 955 4106 1 +a 956 957 3150 +a 956 4106 1 +a 957 958 3149 +a 957 4106 1 +a 958 959 3148 +a 958 4106 1 +a 959 960 3147 +a 959 4106 1 +a 960 961 3146 +a 960 4106 1 +a 961 962 3145 +a 961 4106 1 +a 962 963 3144 +a 962 4106 1 +a 963 964 3143 +a 963 4106 1 +a 964 965 3142 +a 964 4106 1 +a 965 966 3141 +a 965 4106 1 +a 966 967 3140 +a 966 4106 1 +a 967 968 3139 +a 967 4106 1 +a 968 969 3138 +a 968 4106 1 +a 969 970 3137 +a 969 4106 1 +a 970 971 3136 +a 970 4106 1 +a 971 972 3135 +a 971 4106 1 +a 972 973 3134 +a 972 4106 1 +a 973 974 3133 +a 973 4106 1 +a 974 975 3132 +a 974 4106 1 +a 975 976 3131 +a 975 4106 1 +a 976 977 3130 +a 976 4106 1 +a 977 978 3129 +a 977 4106 1 +a 978 979 3128 +a 978 4106 1 +a 979 980 3127 +a 979 4106 1 +a 980 981 3126 +a 980 4106 1 +a 981 982 3125 +a 981 4106 1 +a 982 983 3124 +a 982 4106 1 +a 983 984 3123 +a 983 4106 1 +a 984 985 3122 +a 984 4106 1 +a 985 986 3121 +a 985 4106 1 +a 986 987 3120 +a 986 4106 1 +a 987 988 3119 +a 987 4106 1 +a 988 989 3118 +a 988 4106 1 +a 989 990 3117 +a 989 4106 1 +a 990 991 3116 +a 990 4106 1 +a 991 992 3115 +a 991 4106 1 +a 992 993 3114 +a 992 4106 1 +a 993 994 3113 +a 993 4106 1 +a 994 995 3112 +a 994 4106 1 +a 995 996 3111 +a 995 4106 1 +a 996 997 3110 +a 996 4106 1 +a 997 998 3109 +a 997 4106 1 +a 998 999 3108 +a 998 4106 1 +a 999 1000 3107 +a 999 4106 1 +a 1000 1001 3106 +a 1000 4106 1 +a 1001 1002 3105 +a 1001 4106 1 +a 1002 1003 3104 +a 1002 4106 1 +a 1003 1004 3103 +a 1003 4106 1 +a 1004 1005 3102 +a 1004 4106 1 +a 1005 1006 3101 +a 1005 4106 1 +a 1006 1007 3100 +a 1006 4106 1 +a 1007 1008 3099 +a 1007 4106 1 +a 1008 1009 3098 +a 1008 4106 1 +a 1009 1010 3097 +a 1009 4106 1 +a 1010 1011 3096 +a 1010 4106 1 +a 1011 1012 3095 +a 1011 4106 1 +a 1012 1013 3094 +a 1012 4106 1 +a 1013 1014 3093 +a 1013 4106 1 +a 1014 1015 3092 +a 1014 4106 1 +a 1015 1016 3091 +a 1015 4106 1 +a 1016 1017 3090 +a 1016 4106 1 +a 1017 1018 3089 +a 1017 4106 1 +a 1018 1019 3088 +a 1018 4106 1 +a 1019 1020 3087 +a 1019 4106 1 +a 1020 1021 3086 +a 1020 4106 1 +a 1021 1022 3085 +a 1021 4106 1 +a 1022 1023 3084 +a 1022 4106 1 +a 1023 1024 3083 +a 1023 4106 1 +a 1024 1025 3082 +a 1024 4106 1 +a 1025 1026 3081 +a 1025 4106 1 +a 1026 1027 3080 +a 1026 4106 1 +a 1027 1028 3079 +a 1027 4106 1 +a 1028 1029 3078 +a 1028 4106 1 +a 1029 1030 3077 +a 1029 4106 1 +a 1030 1031 3076 +a 1030 4106 1 +a 1031 1032 3075 +a 1031 4106 1 +a 1032 1033 3074 +a 1032 4106 1 +a 1033 1034 3073 +a 1033 4106 1 +a 1034 1035 3072 +a 1034 4106 1 +a 1035 1036 3071 +a 1035 4106 1 +a 1036 1037 3070 +a 1036 4106 1 +a 1037 1038 3069 +a 1037 4106 1 +a 1038 1039 3068 +a 1038 4106 1 +a 1039 1040 3067 +a 1039 4106 1 +a 1040 1041 3066 +a 1040 4106 1 +a 1041 1042 3065 +a 1041 4106 1 +a 1042 1043 3064 +a 1042 4106 1 +a 1043 1044 3063 +a 1043 4106 1 +a 1044 1045 3062 +a 1044 4106 1 +a 1045 1046 3061 +a 1045 4106 1 +a 1046 1047 3060 +a 1046 4106 1 +a 1047 1048 3059 +a 1047 4106 1 +a 1048 1049 3058 +a 1048 4106 1 +a 1049 1050 3057 +a 1049 4106 1 +a 1050 1051 3056 +a 1050 4106 1 +a 1051 1052 3055 +a 1051 4106 1 +a 1052 1053 3054 +a 1052 4106 1 +a 1053 1054 3053 +a 1053 4106 1 +a 1054 1055 3052 +a 1054 4106 1 +a 1055 1056 3051 +a 1055 4106 1 +a 1056 1057 3050 +a 1056 4106 1 +a 1057 1058 3049 +a 1057 4106 1 +a 1058 1059 3048 +a 1058 4106 1 +a 1059 1060 3047 +a 1059 4106 1 +a 1060 1061 3046 +a 1060 4106 1 +a 1061 1062 3045 +a 1061 4106 1 +a 1062 1063 3044 +a 1062 4106 1 +a 1063 1064 3043 +a 1063 4106 1 +a 1064 1065 3042 +a 1064 4106 1 +a 1065 1066 3041 +a 1065 4106 1 +a 1066 1067 3040 +a 1066 4106 1 +a 1067 1068 3039 +a 1067 4106 1 +a 1068 1069 3038 +a 1068 4106 1 +a 1069 1070 3037 +a 1069 4106 1 +a 1070 1071 3036 +a 1070 4106 1 +a 1071 1072 3035 +a 1071 4106 1 +a 1072 1073 3034 +a 1072 4106 1 +a 1073 1074 3033 +a 1073 4106 1 +a 1074 1075 3032 +a 1074 4106 1 +a 1075 1076 3031 +a 1075 4106 1 +a 1076 1077 3030 +a 1076 4106 1 +a 1077 1078 3029 +a 1077 4106 1 +a 1078 1079 3028 +a 1078 4106 1 +a 1079 1080 3027 +a 1079 4106 1 +a 1080 1081 3026 +a 1080 4106 1 +a 1081 1082 3025 +a 1081 4106 1 +a 1082 1083 3024 +a 1082 4106 1 +a 1083 1084 3023 +a 1083 4106 1 +a 1084 1085 3022 +a 1084 4106 1 +a 1085 1086 3021 +a 1085 4106 1 +a 1086 1087 3020 +a 1086 4106 1 +a 1087 1088 3019 +a 1087 4106 1 +a 1088 1089 3018 +a 1088 4106 1 +a 1089 1090 3017 +a 1089 4106 1 +a 1090 1091 3016 +a 1090 4106 1 +a 1091 1092 3015 +a 1091 4106 1 +a 1092 1093 3014 +a 1092 4106 1 +a 1093 1094 3013 +a 1093 4106 1 +a 1094 1095 3012 +a 1094 4106 1 +a 1095 1096 3011 +a 1095 4106 1 +a 1096 1097 3010 +a 1096 4106 1 +a 1097 1098 3009 +a 1097 4106 1 +a 1098 1099 3008 +a 1098 4106 1 +a 1099 1100 3007 +a 1099 4106 1 +a 1100 1101 3006 +a 1100 4106 1 +a 1101 1102 3005 +a 1101 4106 1 +a 1102 1103 3004 +a 1102 4106 1 +a 1103 1104 3003 +a 1103 4106 1 +a 1104 1105 3002 +a 1104 4106 1 +a 1105 1106 3001 +a 1105 4106 1 +a 1106 1107 3000 +a 1106 4106 1 +a 1107 1108 2999 +a 1107 4106 1 +a 1108 1109 2998 +a 1108 4106 1 +a 1109 1110 2997 +a 1109 4106 1 +a 1110 1111 2996 +a 1110 4106 1 +a 1111 1112 2995 +a 1111 4106 1 +a 1112 1113 2994 +a 1112 4106 1 +a 1113 1114 2993 +a 1113 4106 1 +a 1114 1115 2992 +a 1114 4106 1 +a 1115 1116 2991 +a 1115 4106 1 +a 1116 1117 2990 +a 1116 4106 1 +a 1117 1118 2989 +a 1117 4106 1 +a 1118 1119 2988 +a 1118 4106 1 +a 1119 1120 2987 +a 1119 4106 1 +a 1120 1121 2986 +a 1120 4106 1 +a 1121 1122 2985 +a 1121 4106 1 +a 1122 1123 2984 +a 1122 4106 1 +a 1123 1124 2983 +a 1123 4106 1 +a 1124 1125 2982 +a 1124 4106 1 +a 1125 1126 2981 +a 1125 4106 1 +a 1126 1127 2980 +a 1126 4106 1 +a 1127 1128 2979 +a 1127 4106 1 +a 1128 1129 2978 +a 1128 4106 1 +a 1129 1130 2977 +a 1129 4106 1 +a 1130 1131 2976 +a 1130 4106 1 +a 1131 1132 2975 +a 1131 4106 1 +a 1132 1133 2974 +a 1132 4106 1 +a 1133 1134 2973 +a 1133 4106 1 +a 1134 1135 2972 +a 1134 4106 1 +a 1135 1136 2971 +a 1135 4106 1 +a 1136 1137 2970 +a 1136 4106 1 +a 1137 1138 2969 +a 1137 4106 1 +a 1138 1139 2968 +a 1138 4106 1 +a 1139 1140 2967 +a 1139 4106 1 +a 1140 1141 2966 +a 1140 4106 1 +a 1141 1142 2965 +a 1141 4106 1 +a 1142 1143 2964 +a 1142 4106 1 +a 1143 1144 2963 +a 1143 4106 1 +a 1144 1145 2962 +a 1144 4106 1 +a 1145 1146 2961 +a 1145 4106 1 +a 1146 1147 2960 +a 1146 4106 1 +a 1147 1148 2959 +a 1147 4106 1 +a 1148 1149 2958 +a 1148 4106 1 +a 1149 1150 2957 +a 1149 4106 1 +a 1150 1151 2956 +a 1150 4106 1 +a 1151 1152 2955 +a 1151 4106 1 +a 1152 1153 2954 +a 1152 4106 1 +a 1153 1154 2953 +a 1153 4106 1 +a 1154 1155 2952 +a 1154 4106 1 +a 1155 1156 2951 +a 1155 4106 1 +a 1156 1157 2950 +a 1156 4106 1 +a 1157 1158 2949 +a 1157 4106 1 +a 1158 1159 2948 +a 1158 4106 1 +a 1159 1160 2947 +a 1159 4106 1 +a 1160 1161 2946 +a 1160 4106 1 +a 1161 1162 2945 +a 1161 4106 1 +a 1162 1163 2944 +a 1162 4106 1 +a 1163 1164 2943 +a 1163 4106 1 +a 1164 1165 2942 +a 1164 4106 1 +a 1165 1166 2941 +a 1165 4106 1 +a 1166 1167 2940 +a 1166 4106 1 +a 1167 1168 2939 +a 1167 4106 1 +a 1168 1169 2938 +a 1168 4106 1 +a 1169 1170 2937 +a 1169 4106 1 +a 1170 1171 2936 +a 1170 4106 1 +a 1171 1172 2935 +a 1171 4106 1 +a 1172 1173 2934 +a 1172 4106 1 +a 1173 1174 2933 +a 1173 4106 1 +a 1174 1175 2932 +a 1174 4106 1 +a 1175 1176 2931 +a 1175 4106 1 +a 1176 1177 2930 +a 1176 4106 1 +a 1177 1178 2929 +a 1177 4106 1 +a 1178 1179 2928 +a 1178 4106 1 +a 1179 1180 2927 +a 1179 4106 1 +a 1180 1181 2926 +a 1180 4106 1 +a 1181 1182 2925 +a 1181 4106 1 +a 1182 1183 2924 +a 1182 4106 1 +a 1183 1184 2923 +a 1183 4106 1 +a 1184 1185 2922 +a 1184 4106 1 +a 1185 1186 2921 +a 1185 4106 1 +a 1186 1187 2920 +a 1186 4106 1 +a 1187 1188 2919 +a 1187 4106 1 +a 1188 1189 2918 +a 1188 4106 1 +a 1189 1190 2917 +a 1189 4106 1 +a 1190 1191 2916 +a 1190 4106 1 +a 1191 1192 2915 +a 1191 4106 1 +a 1192 1193 2914 +a 1192 4106 1 +a 1193 1194 2913 +a 1193 4106 1 +a 1194 1195 2912 +a 1194 4106 1 +a 1195 1196 2911 +a 1195 4106 1 +a 1196 1197 2910 +a 1196 4106 1 +a 1197 1198 2909 +a 1197 4106 1 +a 1198 1199 2908 +a 1198 4106 1 +a 1199 1200 2907 +a 1199 4106 1 +a 1200 1201 2906 +a 1200 4106 1 +a 1201 1202 2905 +a 1201 4106 1 +a 1202 1203 2904 +a 1202 4106 1 +a 1203 1204 2903 +a 1203 4106 1 +a 1204 1205 2902 +a 1204 4106 1 +a 1205 1206 2901 +a 1205 4106 1 +a 1206 1207 2900 +a 1206 4106 1 +a 1207 1208 2899 +a 1207 4106 1 +a 1208 1209 2898 +a 1208 4106 1 +a 1209 1210 2897 +a 1209 4106 1 +a 1210 1211 2896 +a 1210 4106 1 +a 1211 1212 2895 +a 1211 4106 1 +a 1212 1213 2894 +a 1212 4106 1 +a 1213 1214 2893 +a 1213 4106 1 +a 1214 1215 2892 +a 1214 4106 1 +a 1215 1216 2891 +a 1215 4106 1 +a 1216 1217 2890 +a 1216 4106 1 +a 1217 1218 2889 +a 1217 4106 1 +a 1218 1219 2888 +a 1218 4106 1 +a 1219 1220 2887 +a 1219 4106 1 +a 1220 1221 2886 +a 1220 4106 1 +a 1221 1222 2885 +a 1221 4106 1 +a 1222 1223 2884 +a 1222 4106 1 +a 1223 1224 2883 +a 1223 4106 1 +a 1224 1225 2882 +a 1224 4106 1 +a 1225 1226 2881 +a 1225 4106 1 +a 1226 1227 2880 +a 1226 4106 1 +a 1227 1228 2879 +a 1227 4106 1 +a 1228 1229 2878 +a 1228 4106 1 +a 1229 1230 2877 +a 1229 4106 1 +a 1230 1231 2876 +a 1230 4106 1 +a 1231 1232 2875 +a 1231 4106 1 +a 1232 1233 2874 +a 1232 4106 1 +a 1233 1234 2873 +a 1233 4106 1 +a 1234 1235 2872 +a 1234 4106 1 +a 1235 1236 2871 +a 1235 4106 1 +a 1236 1237 2870 +a 1236 4106 1 +a 1237 1238 2869 +a 1237 4106 1 +a 1238 1239 2868 +a 1238 4106 1 +a 1239 1240 2867 +a 1239 4106 1 +a 1240 1241 2866 +a 1240 4106 1 +a 1241 1242 2865 +a 1241 4106 1 +a 1242 1243 2864 +a 1242 4106 1 +a 1243 1244 2863 +a 1243 4106 1 +a 1244 1245 2862 +a 1244 4106 1 +a 1245 1246 2861 +a 1245 4106 1 +a 1246 1247 2860 +a 1246 4106 1 +a 1247 1248 2859 +a 1247 4106 1 +a 1248 1249 2858 +a 1248 4106 1 +a 1249 1250 2857 +a 1249 4106 1 +a 1250 1251 2856 +a 1250 4106 1 +a 1251 1252 2855 +a 1251 4106 1 +a 1252 1253 2854 +a 1252 4106 1 +a 1253 1254 2853 +a 1253 4106 1 +a 1254 1255 2852 +a 1254 4106 1 +a 1255 1256 2851 +a 1255 4106 1 +a 1256 1257 2850 +a 1256 4106 1 +a 1257 1258 2849 +a 1257 4106 1 +a 1258 1259 2848 +a 1258 4106 1 +a 1259 1260 2847 +a 1259 4106 1 +a 1260 1261 2846 +a 1260 4106 1 +a 1261 1262 2845 +a 1261 4106 1 +a 1262 1263 2844 +a 1262 4106 1 +a 1263 1264 2843 +a 1263 4106 1 +a 1264 1265 2842 +a 1264 4106 1 +a 1265 1266 2841 +a 1265 4106 1 +a 1266 1267 2840 +a 1266 4106 1 +a 1267 1268 2839 +a 1267 4106 1 +a 1268 1269 2838 +a 1268 4106 1 +a 1269 1270 2837 +a 1269 4106 1 +a 1270 1271 2836 +a 1270 4106 1 +a 1271 1272 2835 +a 1271 4106 1 +a 1272 1273 2834 +a 1272 4106 1 +a 1273 1274 2833 +a 1273 4106 1 +a 1274 1275 2832 +a 1274 4106 1 +a 1275 1276 2831 +a 1275 4106 1 +a 1276 1277 2830 +a 1276 4106 1 +a 1277 1278 2829 +a 1277 4106 1 +a 1278 1279 2828 +a 1278 4106 1 +a 1279 1280 2827 +a 1279 4106 1 +a 1280 1281 2826 +a 1280 4106 1 +a 1281 1282 2825 +a 1281 4106 1 +a 1282 1283 2824 +a 1282 4106 1 +a 1283 1284 2823 +a 1283 4106 1 +a 1284 1285 2822 +a 1284 4106 1 +a 1285 1286 2821 +a 1285 4106 1 +a 1286 1287 2820 +a 1286 4106 1 +a 1287 1288 2819 +a 1287 4106 1 +a 1288 1289 2818 +a 1288 4106 1 +a 1289 1290 2817 +a 1289 4106 1 +a 1290 1291 2816 +a 1290 4106 1 +a 1291 1292 2815 +a 1291 4106 1 +a 1292 1293 2814 +a 1292 4106 1 +a 1293 1294 2813 +a 1293 4106 1 +a 1294 1295 2812 +a 1294 4106 1 +a 1295 1296 2811 +a 1295 4106 1 +a 1296 1297 2810 +a 1296 4106 1 +a 1297 1298 2809 +a 1297 4106 1 +a 1298 1299 2808 +a 1298 4106 1 +a 1299 1300 2807 +a 1299 4106 1 +a 1300 1301 2806 +a 1300 4106 1 +a 1301 1302 2805 +a 1301 4106 1 +a 1302 1303 2804 +a 1302 4106 1 +a 1303 1304 2803 +a 1303 4106 1 +a 1304 1305 2802 +a 1304 4106 1 +a 1305 1306 2801 +a 1305 4106 1 +a 1306 1307 2800 +a 1306 4106 1 +a 1307 1308 2799 +a 1307 4106 1 +a 1308 1309 2798 +a 1308 4106 1 +a 1309 1310 2797 +a 1309 4106 1 +a 1310 1311 2796 +a 1310 4106 1 +a 1311 1312 2795 +a 1311 4106 1 +a 1312 1313 2794 +a 1312 4106 1 +a 1313 1314 2793 +a 1313 4106 1 +a 1314 1315 2792 +a 1314 4106 1 +a 1315 1316 2791 +a 1315 4106 1 +a 1316 1317 2790 +a 1316 4106 1 +a 1317 1318 2789 +a 1317 4106 1 +a 1318 1319 2788 +a 1318 4106 1 +a 1319 1320 2787 +a 1319 4106 1 +a 1320 1321 2786 +a 1320 4106 1 +a 1321 1322 2785 +a 1321 4106 1 +a 1322 1323 2784 +a 1322 4106 1 +a 1323 1324 2783 +a 1323 4106 1 +a 1324 1325 2782 +a 1324 4106 1 +a 1325 1326 2781 +a 1325 4106 1 +a 1326 1327 2780 +a 1326 4106 1 +a 1327 1328 2779 +a 1327 4106 1 +a 1328 1329 2778 +a 1328 4106 1 +a 1329 1330 2777 +a 1329 4106 1 +a 1330 1331 2776 +a 1330 4106 1 +a 1331 1332 2775 +a 1331 4106 1 +a 1332 1333 2774 +a 1332 4106 1 +a 1333 1334 2773 +a 1333 4106 1 +a 1334 1335 2772 +a 1334 4106 1 +a 1335 1336 2771 +a 1335 4106 1 +a 1336 1337 2770 +a 1336 4106 1 +a 1337 1338 2769 +a 1337 4106 1 +a 1338 1339 2768 +a 1338 4106 1 +a 1339 1340 2767 +a 1339 4106 1 +a 1340 1341 2766 +a 1340 4106 1 +a 1341 1342 2765 +a 1341 4106 1 +a 1342 1343 2764 +a 1342 4106 1 +a 1343 1344 2763 +a 1343 4106 1 +a 1344 1345 2762 +a 1344 4106 1 +a 1345 1346 2761 +a 1345 4106 1 +a 1346 1347 2760 +a 1346 4106 1 +a 1347 1348 2759 +a 1347 4106 1 +a 1348 1349 2758 +a 1348 4106 1 +a 1349 1350 2757 +a 1349 4106 1 +a 1350 1351 2756 +a 1350 4106 1 +a 1351 1352 2755 +a 1351 4106 1 +a 1352 1353 2754 +a 1352 4106 1 +a 1353 1354 2753 +a 1353 4106 1 +a 1354 1355 2752 +a 1354 4106 1 +a 1355 1356 2751 +a 1355 4106 1 +a 1356 1357 2750 +a 1356 4106 1 +a 1357 1358 2749 +a 1357 4106 1 +a 1358 1359 2748 +a 1358 4106 1 +a 1359 1360 2747 +a 1359 4106 1 +a 1360 1361 2746 +a 1360 4106 1 +a 1361 1362 2745 +a 1361 4106 1 +a 1362 1363 2744 +a 1362 4106 1 +a 1363 1364 2743 +a 1363 4106 1 +a 1364 1365 2742 +a 1364 4106 1 +a 1365 1366 2741 +a 1365 4106 1 +a 1366 1367 2740 +a 1366 4106 1 +a 1367 1368 2739 +a 1367 4106 1 +a 1368 1369 2738 +a 1368 4106 1 +a 1369 1370 2737 +a 1369 4106 1 +a 1370 1371 2736 +a 1370 4106 1 +a 1371 1372 2735 +a 1371 4106 1 +a 1372 1373 2734 +a 1372 4106 1 +a 1373 1374 2733 +a 1373 4106 1 +a 1374 1375 2732 +a 1374 4106 1 +a 1375 1376 2731 +a 1375 4106 1 +a 1376 1377 2730 +a 1376 4106 1 +a 1377 1378 2729 +a 1377 4106 1 +a 1378 1379 2728 +a 1378 4106 1 +a 1379 1380 2727 +a 1379 4106 1 +a 1380 1381 2726 +a 1380 4106 1 +a 1381 1382 2725 +a 1381 4106 1 +a 1382 1383 2724 +a 1382 4106 1 +a 1383 1384 2723 +a 1383 4106 1 +a 1384 1385 2722 +a 1384 4106 1 +a 1385 1386 2721 +a 1385 4106 1 +a 1386 1387 2720 +a 1386 4106 1 +a 1387 1388 2719 +a 1387 4106 1 +a 1388 1389 2718 +a 1388 4106 1 +a 1389 1390 2717 +a 1389 4106 1 +a 1390 1391 2716 +a 1390 4106 1 +a 1391 1392 2715 +a 1391 4106 1 +a 1392 1393 2714 +a 1392 4106 1 +a 1393 1394 2713 +a 1393 4106 1 +a 1394 1395 2712 +a 1394 4106 1 +a 1395 1396 2711 +a 1395 4106 1 +a 1396 1397 2710 +a 1396 4106 1 +a 1397 1398 2709 +a 1397 4106 1 +a 1398 1399 2708 +a 1398 4106 1 +a 1399 1400 2707 +a 1399 4106 1 +a 1400 1401 2706 +a 1400 4106 1 +a 1401 1402 2705 +a 1401 4106 1 +a 1402 1403 2704 +a 1402 4106 1 +a 1403 1404 2703 +a 1403 4106 1 +a 1404 1405 2702 +a 1404 4106 1 +a 1405 1406 2701 +a 1405 4106 1 +a 1406 1407 2700 +a 1406 4106 1 +a 1407 1408 2699 +a 1407 4106 1 +a 1408 1409 2698 +a 1408 4106 1 +a 1409 1410 2697 +a 1409 4106 1 +a 1410 1411 2696 +a 1410 4106 1 +a 1411 1412 2695 +a 1411 4106 1 +a 1412 1413 2694 +a 1412 4106 1 +a 1413 1414 2693 +a 1413 4106 1 +a 1414 1415 2692 +a 1414 4106 1 +a 1415 1416 2691 +a 1415 4106 1 +a 1416 1417 2690 +a 1416 4106 1 +a 1417 1418 2689 +a 1417 4106 1 +a 1418 1419 2688 +a 1418 4106 1 +a 1419 1420 2687 +a 1419 4106 1 +a 1420 1421 2686 +a 1420 4106 1 +a 1421 1422 2685 +a 1421 4106 1 +a 1422 1423 2684 +a 1422 4106 1 +a 1423 1424 2683 +a 1423 4106 1 +a 1424 1425 2682 +a 1424 4106 1 +a 1425 1426 2681 +a 1425 4106 1 +a 1426 1427 2680 +a 1426 4106 1 +a 1427 1428 2679 +a 1427 4106 1 +a 1428 1429 2678 +a 1428 4106 1 +a 1429 1430 2677 +a 1429 4106 1 +a 1430 1431 2676 +a 1430 4106 1 +a 1431 1432 2675 +a 1431 4106 1 +a 1432 1433 2674 +a 1432 4106 1 +a 1433 1434 2673 +a 1433 4106 1 +a 1434 1435 2672 +a 1434 4106 1 +a 1435 1436 2671 +a 1435 4106 1 +a 1436 1437 2670 +a 1436 4106 1 +a 1437 1438 2669 +a 1437 4106 1 +a 1438 1439 2668 +a 1438 4106 1 +a 1439 1440 2667 +a 1439 4106 1 +a 1440 1441 2666 +a 1440 4106 1 +a 1441 1442 2665 +a 1441 4106 1 +a 1442 1443 2664 +a 1442 4106 1 +a 1443 1444 2663 +a 1443 4106 1 +a 1444 1445 2662 +a 1444 4106 1 +a 1445 1446 2661 +a 1445 4106 1 +a 1446 1447 2660 +a 1446 4106 1 +a 1447 1448 2659 +a 1447 4106 1 +a 1448 1449 2658 +a 1448 4106 1 +a 1449 1450 2657 +a 1449 4106 1 +a 1450 1451 2656 +a 1450 4106 1 +a 1451 1452 2655 +a 1451 4106 1 +a 1452 1453 2654 +a 1452 4106 1 +a 1453 1454 2653 +a 1453 4106 1 +a 1454 1455 2652 +a 1454 4106 1 +a 1455 1456 2651 +a 1455 4106 1 +a 1456 1457 2650 +a 1456 4106 1 +a 1457 1458 2649 +a 1457 4106 1 +a 1458 1459 2648 +a 1458 4106 1 +a 1459 1460 2647 +a 1459 4106 1 +a 1460 1461 2646 +a 1460 4106 1 +a 1461 1462 2645 +a 1461 4106 1 +a 1462 1463 2644 +a 1462 4106 1 +a 1463 1464 2643 +a 1463 4106 1 +a 1464 1465 2642 +a 1464 4106 1 +a 1465 1466 2641 +a 1465 4106 1 +a 1466 1467 2640 +a 1466 4106 1 +a 1467 1468 2639 +a 1467 4106 1 +a 1468 1469 2638 +a 1468 4106 1 +a 1469 1470 2637 +a 1469 4106 1 +a 1470 1471 2636 +a 1470 4106 1 +a 1471 1472 2635 +a 1471 4106 1 +a 1472 1473 2634 +a 1472 4106 1 +a 1473 1474 2633 +a 1473 4106 1 +a 1474 1475 2632 +a 1474 4106 1 +a 1475 1476 2631 +a 1475 4106 1 +a 1476 1477 2630 +a 1476 4106 1 +a 1477 1478 2629 +a 1477 4106 1 +a 1478 1479 2628 +a 1478 4106 1 +a 1479 1480 2627 +a 1479 4106 1 +a 1480 1481 2626 +a 1480 4106 1 +a 1481 1482 2625 +a 1481 4106 1 +a 1482 1483 2624 +a 1482 4106 1 +a 1483 1484 2623 +a 1483 4106 1 +a 1484 1485 2622 +a 1484 4106 1 +a 1485 1486 2621 +a 1485 4106 1 +a 1486 1487 2620 +a 1486 4106 1 +a 1487 1488 2619 +a 1487 4106 1 +a 1488 1489 2618 +a 1488 4106 1 +a 1489 1490 2617 +a 1489 4106 1 +a 1490 1491 2616 +a 1490 4106 1 +a 1491 1492 2615 +a 1491 4106 1 +a 1492 1493 2614 +a 1492 4106 1 +a 1493 1494 2613 +a 1493 4106 1 +a 1494 1495 2612 +a 1494 4106 1 +a 1495 1496 2611 +a 1495 4106 1 +a 1496 1497 2610 +a 1496 4106 1 +a 1497 1498 2609 +a 1497 4106 1 +a 1498 1499 2608 +a 1498 4106 1 +a 1499 1500 2607 +a 1499 4106 1 +a 1500 1501 2606 +a 1500 4106 1 +a 1501 1502 2605 +a 1501 4106 1 +a 1502 1503 2604 +a 1502 4106 1 +a 1503 1504 2603 +a 1503 4106 1 +a 1504 1505 2602 +a 1504 4106 1 +a 1505 1506 2601 +a 1505 4106 1 +a 1506 1507 2600 +a 1506 4106 1 +a 1507 1508 2599 +a 1507 4106 1 +a 1508 1509 2598 +a 1508 4106 1 +a 1509 1510 2597 +a 1509 4106 1 +a 1510 1511 2596 +a 1510 4106 1 +a 1511 1512 2595 +a 1511 4106 1 +a 1512 1513 2594 +a 1512 4106 1 +a 1513 1514 2593 +a 1513 4106 1 +a 1514 1515 2592 +a 1514 4106 1 +a 1515 1516 2591 +a 1515 4106 1 +a 1516 1517 2590 +a 1516 4106 1 +a 1517 1518 2589 +a 1517 4106 1 +a 1518 1519 2588 +a 1518 4106 1 +a 1519 1520 2587 +a 1519 4106 1 +a 1520 1521 2586 +a 1520 4106 1 +a 1521 1522 2585 +a 1521 4106 1 +a 1522 1523 2584 +a 1522 4106 1 +a 1523 1524 2583 +a 1523 4106 1 +a 1524 1525 2582 +a 1524 4106 1 +a 1525 1526 2581 +a 1525 4106 1 +a 1526 1527 2580 +a 1526 4106 1 +a 1527 1528 2579 +a 1527 4106 1 +a 1528 1529 2578 +a 1528 4106 1 +a 1529 1530 2577 +a 1529 4106 1 +a 1530 1531 2576 +a 1530 4106 1 +a 1531 1532 2575 +a 1531 4106 1 +a 1532 1533 2574 +a 1532 4106 1 +a 1533 1534 2573 +a 1533 4106 1 +a 1534 1535 2572 +a 1534 4106 1 +a 1535 1536 2571 +a 1535 4106 1 +a 1536 1537 2570 +a 1536 4106 1 +a 1537 1538 2569 +a 1537 4106 1 +a 1538 1539 2568 +a 1538 4106 1 +a 1539 1540 2567 +a 1539 4106 1 +a 1540 1541 2566 +a 1540 4106 1 +a 1541 1542 2565 +a 1541 4106 1 +a 1542 1543 2564 +a 1542 4106 1 +a 1543 1544 2563 +a 1543 4106 1 +a 1544 1545 2562 +a 1544 4106 1 +a 1545 1546 2561 +a 1545 4106 1 +a 1546 1547 2560 +a 1546 4106 1 +a 1547 1548 2559 +a 1547 4106 1 +a 1548 1549 2558 +a 1548 4106 1 +a 1549 1550 2557 +a 1549 4106 1 +a 1550 1551 2556 +a 1550 4106 1 +a 1551 1552 2555 +a 1551 4106 1 +a 1552 1553 2554 +a 1552 4106 1 +a 1553 1554 2553 +a 1553 4106 1 +a 1554 1555 2552 +a 1554 4106 1 +a 1555 1556 2551 +a 1555 4106 1 +a 1556 1557 2550 +a 1556 4106 1 +a 1557 1558 2549 +a 1557 4106 1 +a 1558 1559 2548 +a 1558 4106 1 +a 1559 1560 2547 +a 1559 4106 1 +a 1560 1561 2546 +a 1560 4106 1 +a 1561 1562 2545 +a 1561 4106 1 +a 1562 1563 2544 +a 1562 4106 1 +a 1563 1564 2543 +a 1563 4106 1 +a 1564 1565 2542 +a 1564 4106 1 +a 1565 1566 2541 +a 1565 4106 1 +a 1566 1567 2540 +a 1566 4106 1 +a 1567 1568 2539 +a 1567 4106 1 +a 1568 1569 2538 +a 1568 4106 1 +a 1569 1570 2537 +a 1569 4106 1 +a 1570 1571 2536 +a 1570 4106 1 +a 1571 1572 2535 +a 1571 4106 1 +a 1572 1573 2534 +a 1572 4106 1 +a 1573 1574 2533 +a 1573 4106 1 +a 1574 1575 2532 +a 1574 4106 1 +a 1575 1576 2531 +a 1575 4106 1 +a 1576 1577 2530 +a 1576 4106 1 +a 1577 1578 2529 +a 1577 4106 1 +a 1578 1579 2528 +a 1578 4106 1 +a 1579 1580 2527 +a 1579 4106 1 +a 1580 1581 2526 +a 1580 4106 1 +a 1581 1582 2525 +a 1581 4106 1 +a 1582 1583 2524 +a 1582 4106 1 +a 1583 1584 2523 +a 1583 4106 1 +a 1584 1585 2522 +a 1584 4106 1 +a 1585 1586 2521 +a 1585 4106 1 +a 1586 1587 2520 +a 1586 4106 1 +a 1587 1588 2519 +a 1587 4106 1 +a 1588 1589 2518 +a 1588 4106 1 +a 1589 1590 2517 +a 1589 4106 1 +a 1590 1591 2516 +a 1590 4106 1 +a 1591 1592 2515 +a 1591 4106 1 +a 1592 1593 2514 +a 1592 4106 1 +a 1593 1594 2513 +a 1593 4106 1 +a 1594 1595 2512 +a 1594 4106 1 +a 1595 1596 2511 +a 1595 4106 1 +a 1596 1597 2510 +a 1596 4106 1 +a 1597 1598 2509 +a 1597 4106 1 +a 1598 1599 2508 +a 1598 4106 1 +a 1599 1600 2507 +a 1599 4106 1 +a 1600 1601 2506 +a 1600 4106 1 +a 1601 1602 2505 +a 1601 4106 1 +a 1602 1603 2504 +a 1602 4106 1 +a 1603 1604 2503 +a 1603 4106 1 +a 1604 1605 2502 +a 1604 4106 1 +a 1605 1606 2501 +a 1605 4106 1 +a 1606 1607 2500 +a 1606 4106 1 +a 1607 1608 2499 +a 1607 4106 1 +a 1608 1609 2498 +a 1608 4106 1 +a 1609 1610 2497 +a 1609 4106 1 +a 1610 1611 2496 +a 1610 4106 1 +a 1611 1612 2495 +a 1611 4106 1 +a 1612 1613 2494 +a 1612 4106 1 +a 1613 1614 2493 +a 1613 4106 1 +a 1614 1615 2492 +a 1614 4106 1 +a 1615 1616 2491 +a 1615 4106 1 +a 1616 1617 2490 +a 1616 4106 1 +a 1617 1618 2489 +a 1617 4106 1 +a 1618 1619 2488 +a 1618 4106 1 +a 1619 1620 2487 +a 1619 4106 1 +a 1620 1621 2486 +a 1620 4106 1 +a 1621 1622 2485 +a 1621 4106 1 +a 1622 1623 2484 +a 1622 4106 1 +a 1623 1624 2483 +a 1623 4106 1 +a 1624 1625 2482 +a 1624 4106 1 +a 1625 1626 2481 +a 1625 4106 1 +a 1626 1627 2480 +a 1626 4106 1 +a 1627 1628 2479 +a 1627 4106 1 +a 1628 1629 2478 +a 1628 4106 1 +a 1629 1630 2477 +a 1629 4106 1 +a 1630 1631 2476 +a 1630 4106 1 +a 1631 1632 2475 +a 1631 4106 1 +a 1632 1633 2474 +a 1632 4106 1 +a 1633 1634 2473 +a 1633 4106 1 +a 1634 1635 2472 +a 1634 4106 1 +a 1635 1636 2471 +a 1635 4106 1 +a 1636 1637 2470 +a 1636 4106 1 +a 1637 1638 2469 +a 1637 4106 1 +a 1638 1639 2468 +a 1638 4106 1 +a 1639 1640 2467 +a 1639 4106 1 +a 1640 1641 2466 +a 1640 4106 1 +a 1641 1642 2465 +a 1641 4106 1 +a 1642 1643 2464 +a 1642 4106 1 +a 1643 1644 2463 +a 1643 4106 1 +a 1644 1645 2462 +a 1644 4106 1 +a 1645 1646 2461 +a 1645 4106 1 +a 1646 1647 2460 +a 1646 4106 1 +a 1647 1648 2459 +a 1647 4106 1 +a 1648 1649 2458 +a 1648 4106 1 +a 1649 1650 2457 +a 1649 4106 1 +a 1650 1651 2456 +a 1650 4106 1 +a 1651 1652 2455 +a 1651 4106 1 +a 1652 1653 2454 +a 1652 4106 1 +a 1653 1654 2453 +a 1653 4106 1 +a 1654 1655 2452 +a 1654 4106 1 +a 1655 1656 2451 +a 1655 4106 1 +a 1656 1657 2450 +a 1656 4106 1 +a 1657 1658 2449 +a 1657 4106 1 +a 1658 1659 2448 +a 1658 4106 1 +a 1659 1660 2447 +a 1659 4106 1 +a 1660 1661 2446 +a 1660 4106 1 +a 1661 1662 2445 +a 1661 4106 1 +a 1662 1663 2444 +a 1662 4106 1 +a 1663 1664 2443 +a 1663 4106 1 +a 1664 1665 2442 +a 1664 4106 1 +a 1665 1666 2441 +a 1665 4106 1 +a 1666 1667 2440 +a 1666 4106 1 +a 1667 1668 2439 +a 1667 4106 1 +a 1668 1669 2438 +a 1668 4106 1 +a 1669 1670 2437 +a 1669 4106 1 +a 1670 1671 2436 +a 1670 4106 1 +a 1671 1672 2435 +a 1671 4106 1 +a 1672 1673 2434 +a 1672 4106 1 +a 1673 1674 2433 +a 1673 4106 1 +a 1674 1675 2432 +a 1674 4106 1 +a 1675 1676 2431 +a 1675 4106 1 +a 1676 1677 2430 +a 1676 4106 1 +a 1677 1678 2429 +a 1677 4106 1 +a 1678 1679 2428 +a 1678 4106 1 +a 1679 1680 2427 +a 1679 4106 1 +a 1680 1681 2426 +a 1680 4106 1 +a 1681 1682 2425 +a 1681 4106 1 +a 1682 1683 2424 +a 1682 4106 1 +a 1683 1684 2423 +a 1683 4106 1 +a 1684 1685 2422 +a 1684 4106 1 +a 1685 1686 2421 +a 1685 4106 1 +a 1686 1687 2420 +a 1686 4106 1 +a 1687 1688 2419 +a 1687 4106 1 +a 1688 1689 2418 +a 1688 4106 1 +a 1689 1690 2417 +a 1689 4106 1 +a 1690 1691 2416 +a 1690 4106 1 +a 1691 1692 2415 +a 1691 4106 1 +a 1692 1693 2414 +a 1692 4106 1 +a 1693 1694 2413 +a 1693 4106 1 +a 1694 1695 2412 +a 1694 4106 1 +a 1695 1696 2411 +a 1695 4106 1 +a 1696 1697 2410 +a 1696 4106 1 +a 1697 1698 2409 +a 1697 4106 1 +a 1698 1699 2408 +a 1698 4106 1 +a 1699 1700 2407 +a 1699 4106 1 +a 1700 1701 2406 +a 1700 4106 1 +a 1701 1702 2405 +a 1701 4106 1 +a 1702 1703 2404 +a 1702 4106 1 +a 1703 1704 2403 +a 1703 4106 1 +a 1704 1705 2402 +a 1704 4106 1 +a 1705 1706 2401 +a 1705 4106 1 +a 1706 1707 2400 +a 1706 4106 1 +a 1707 1708 2399 +a 1707 4106 1 +a 1708 1709 2398 +a 1708 4106 1 +a 1709 1710 2397 +a 1709 4106 1 +a 1710 1711 2396 +a 1710 4106 1 +a 1711 1712 2395 +a 1711 4106 1 +a 1712 1713 2394 +a 1712 4106 1 +a 1713 1714 2393 +a 1713 4106 1 +a 1714 1715 2392 +a 1714 4106 1 +a 1715 1716 2391 +a 1715 4106 1 +a 1716 1717 2390 +a 1716 4106 1 +a 1717 1718 2389 +a 1717 4106 1 +a 1718 1719 2388 +a 1718 4106 1 +a 1719 1720 2387 +a 1719 4106 1 +a 1720 1721 2386 +a 1720 4106 1 +a 1721 1722 2385 +a 1721 4106 1 +a 1722 1723 2384 +a 1722 4106 1 +a 1723 1724 2383 +a 1723 4106 1 +a 1724 1725 2382 +a 1724 4106 1 +a 1725 1726 2381 +a 1725 4106 1 +a 1726 1727 2380 +a 1726 4106 1 +a 1727 1728 2379 +a 1727 4106 1 +a 1728 1729 2378 +a 1728 4106 1 +a 1729 1730 2377 +a 1729 4106 1 +a 1730 1731 2376 +a 1730 4106 1 +a 1731 1732 2375 +a 1731 4106 1 +a 1732 1733 2374 +a 1732 4106 1 +a 1733 1734 2373 +a 1733 4106 1 +a 1734 1735 2372 +a 1734 4106 1 +a 1735 1736 2371 +a 1735 4106 1 +a 1736 1737 2370 +a 1736 4106 1 +a 1737 1738 2369 +a 1737 4106 1 +a 1738 1739 2368 +a 1738 4106 1 +a 1739 1740 2367 +a 1739 4106 1 +a 1740 1741 2366 +a 1740 4106 1 +a 1741 1742 2365 +a 1741 4106 1 +a 1742 1743 2364 +a 1742 4106 1 +a 1743 1744 2363 +a 1743 4106 1 +a 1744 1745 2362 +a 1744 4106 1 +a 1745 1746 2361 +a 1745 4106 1 +a 1746 1747 2360 +a 1746 4106 1 +a 1747 1748 2359 +a 1747 4106 1 +a 1748 1749 2358 +a 1748 4106 1 +a 1749 1750 2357 +a 1749 4106 1 +a 1750 1751 2356 +a 1750 4106 1 +a 1751 1752 2355 +a 1751 4106 1 +a 1752 1753 2354 +a 1752 4106 1 +a 1753 1754 2353 +a 1753 4106 1 +a 1754 1755 2352 +a 1754 4106 1 +a 1755 1756 2351 +a 1755 4106 1 +a 1756 1757 2350 +a 1756 4106 1 +a 1757 1758 2349 +a 1757 4106 1 +a 1758 1759 2348 +a 1758 4106 1 +a 1759 1760 2347 +a 1759 4106 1 +a 1760 1761 2346 +a 1760 4106 1 +a 1761 1762 2345 +a 1761 4106 1 +a 1762 1763 2344 +a 1762 4106 1 +a 1763 1764 2343 +a 1763 4106 1 +a 1764 1765 2342 +a 1764 4106 1 +a 1765 1766 2341 +a 1765 4106 1 +a 1766 1767 2340 +a 1766 4106 1 +a 1767 1768 2339 +a 1767 4106 1 +a 1768 1769 2338 +a 1768 4106 1 +a 1769 1770 2337 +a 1769 4106 1 +a 1770 1771 2336 +a 1770 4106 1 +a 1771 1772 2335 +a 1771 4106 1 +a 1772 1773 2334 +a 1772 4106 1 +a 1773 1774 2333 +a 1773 4106 1 +a 1774 1775 2332 +a 1774 4106 1 +a 1775 1776 2331 +a 1775 4106 1 +a 1776 1777 2330 +a 1776 4106 1 +a 1777 1778 2329 +a 1777 4106 1 +a 1778 1779 2328 +a 1778 4106 1 +a 1779 1780 2327 +a 1779 4106 1 +a 1780 1781 2326 +a 1780 4106 1 +a 1781 1782 2325 +a 1781 4106 1 +a 1782 1783 2324 +a 1782 4106 1 +a 1783 1784 2323 +a 1783 4106 1 +a 1784 1785 2322 +a 1784 4106 1 +a 1785 1786 2321 +a 1785 4106 1 +a 1786 1787 2320 +a 1786 4106 1 +a 1787 1788 2319 +a 1787 4106 1 +a 1788 1789 2318 +a 1788 4106 1 +a 1789 1790 2317 +a 1789 4106 1 +a 1790 1791 2316 +a 1790 4106 1 +a 1791 1792 2315 +a 1791 4106 1 +a 1792 1793 2314 +a 1792 4106 1 +a 1793 1794 2313 +a 1793 4106 1 +a 1794 1795 2312 +a 1794 4106 1 +a 1795 1796 2311 +a 1795 4106 1 +a 1796 1797 2310 +a 1796 4106 1 +a 1797 1798 2309 +a 1797 4106 1 +a 1798 1799 2308 +a 1798 4106 1 +a 1799 1800 2307 +a 1799 4106 1 +a 1800 1801 2306 +a 1800 4106 1 +a 1801 1802 2305 +a 1801 4106 1 +a 1802 1803 2304 +a 1802 4106 1 +a 1803 1804 2303 +a 1803 4106 1 +a 1804 1805 2302 +a 1804 4106 1 +a 1805 1806 2301 +a 1805 4106 1 +a 1806 1807 2300 +a 1806 4106 1 +a 1807 1808 2299 +a 1807 4106 1 +a 1808 1809 2298 +a 1808 4106 1 +a 1809 1810 2297 +a 1809 4106 1 +a 1810 1811 2296 +a 1810 4106 1 +a 1811 1812 2295 +a 1811 4106 1 +a 1812 1813 2294 +a 1812 4106 1 +a 1813 1814 2293 +a 1813 4106 1 +a 1814 1815 2292 +a 1814 4106 1 +a 1815 1816 2291 +a 1815 4106 1 +a 1816 1817 2290 +a 1816 4106 1 +a 1817 1818 2289 +a 1817 4106 1 +a 1818 1819 2288 +a 1818 4106 1 +a 1819 1820 2287 +a 1819 4106 1 +a 1820 1821 2286 +a 1820 4106 1 +a 1821 1822 2285 +a 1821 4106 1 +a 1822 1823 2284 +a 1822 4106 1 +a 1823 1824 2283 +a 1823 4106 1 +a 1824 1825 2282 +a 1824 4106 1 +a 1825 1826 2281 +a 1825 4106 1 +a 1826 1827 2280 +a 1826 4106 1 +a 1827 1828 2279 +a 1827 4106 1 +a 1828 1829 2278 +a 1828 4106 1 +a 1829 1830 2277 +a 1829 4106 1 +a 1830 1831 2276 +a 1830 4106 1 +a 1831 1832 2275 +a 1831 4106 1 +a 1832 1833 2274 +a 1832 4106 1 +a 1833 1834 2273 +a 1833 4106 1 +a 1834 1835 2272 +a 1834 4106 1 +a 1835 1836 2271 +a 1835 4106 1 +a 1836 1837 2270 +a 1836 4106 1 +a 1837 1838 2269 +a 1837 4106 1 +a 1838 1839 2268 +a 1838 4106 1 +a 1839 1840 2267 +a 1839 4106 1 +a 1840 1841 2266 +a 1840 4106 1 +a 1841 1842 2265 +a 1841 4106 1 +a 1842 1843 2264 +a 1842 4106 1 +a 1843 1844 2263 +a 1843 4106 1 +a 1844 1845 2262 +a 1844 4106 1 +a 1845 1846 2261 +a 1845 4106 1 +a 1846 1847 2260 +a 1846 4106 1 +a 1847 1848 2259 +a 1847 4106 1 +a 1848 1849 2258 +a 1848 4106 1 +a 1849 1850 2257 +a 1849 4106 1 +a 1850 1851 2256 +a 1850 4106 1 +a 1851 1852 2255 +a 1851 4106 1 +a 1852 1853 2254 +a 1852 4106 1 +a 1853 1854 2253 +a 1853 4106 1 +a 1854 1855 2252 +a 1854 4106 1 +a 1855 1856 2251 +a 1855 4106 1 +a 1856 1857 2250 +a 1856 4106 1 +a 1857 1858 2249 +a 1857 4106 1 +a 1858 1859 2248 +a 1858 4106 1 +a 1859 1860 2247 +a 1859 4106 1 +a 1860 1861 2246 +a 1860 4106 1 +a 1861 1862 2245 +a 1861 4106 1 +a 1862 1863 2244 +a 1862 4106 1 +a 1863 1864 2243 +a 1863 4106 1 +a 1864 1865 2242 +a 1864 4106 1 +a 1865 1866 2241 +a 1865 4106 1 +a 1866 1867 2240 +a 1866 4106 1 +a 1867 1868 2239 +a 1867 4106 1 +a 1868 1869 2238 +a 1868 4106 1 +a 1869 1870 2237 +a 1869 4106 1 +a 1870 1871 2236 +a 1870 4106 1 +a 1871 1872 2235 +a 1871 4106 1 +a 1872 1873 2234 +a 1872 4106 1 +a 1873 1874 2233 +a 1873 4106 1 +a 1874 1875 2232 +a 1874 4106 1 +a 1875 1876 2231 +a 1875 4106 1 +a 1876 1877 2230 +a 1876 4106 1 +a 1877 1878 2229 +a 1877 4106 1 +a 1878 1879 2228 +a 1878 4106 1 +a 1879 1880 2227 +a 1879 4106 1 +a 1880 1881 2226 +a 1880 4106 1 +a 1881 1882 2225 +a 1881 4106 1 +a 1882 1883 2224 +a 1882 4106 1 +a 1883 1884 2223 +a 1883 4106 1 +a 1884 1885 2222 +a 1884 4106 1 +a 1885 1886 2221 +a 1885 4106 1 +a 1886 1887 2220 +a 1886 4106 1 +a 1887 1888 2219 +a 1887 4106 1 +a 1888 1889 2218 +a 1888 4106 1 +a 1889 1890 2217 +a 1889 4106 1 +a 1890 1891 2216 +a 1890 4106 1 +a 1891 1892 2215 +a 1891 4106 1 +a 1892 1893 2214 +a 1892 4106 1 +a 1893 1894 2213 +a 1893 4106 1 +a 1894 1895 2212 +a 1894 4106 1 +a 1895 1896 2211 +a 1895 4106 1 +a 1896 1897 2210 +a 1896 4106 1 +a 1897 1898 2209 +a 1897 4106 1 +a 1898 1899 2208 +a 1898 4106 1 +a 1899 1900 2207 +a 1899 4106 1 +a 1900 1901 2206 +a 1900 4106 1 +a 1901 1902 2205 +a 1901 4106 1 +a 1902 1903 2204 +a 1902 4106 1 +a 1903 1904 2203 +a 1903 4106 1 +a 1904 1905 2202 +a 1904 4106 1 +a 1905 1906 2201 +a 1905 4106 1 +a 1906 1907 2200 +a 1906 4106 1 +a 1907 1908 2199 +a 1907 4106 1 +a 1908 1909 2198 +a 1908 4106 1 +a 1909 1910 2197 +a 1909 4106 1 +a 1910 1911 2196 +a 1910 4106 1 +a 1911 1912 2195 +a 1911 4106 1 +a 1912 1913 2194 +a 1912 4106 1 +a 1913 1914 2193 +a 1913 4106 1 +a 1914 1915 2192 +a 1914 4106 1 +a 1915 1916 2191 +a 1915 4106 1 +a 1916 1917 2190 +a 1916 4106 1 +a 1917 1918 2189 +a 1917 4106 1 +a 1918 1919 2188 +a 1918 4106 1 +a 1919 1920 2187 +a 1919 4106 1 +a 1920 1921 2186 +a 1920 4106 1 +a 1921 1922 2185 +a 1921 4106 1 +a 1922 1923 2184 +a 1922 4106 1 +a 1923 1924 2183 +a 1923 4106 1 +a 1924 1925 2182 +a 1924 4106 1 +a 1925 1926 2181 +a 1925 4106 1 +a 1926 1927 2180 +a 1926 4106 1 +a 1927 1928 2179 +a 1927 4106 1 +a 1928 1929 2178 +a 1928 4106 1 +a 1929 1930 2177 +a 1929 4106 1 +a 1930 1931 2176 +a 1930 4106 1 +a 1931 1932 2175 +a 1931 4106 1 +a 1932 1933 2174 +a 1932 4106 1 +a 1933 1934 2173 +a 1933 4106 1 +a 1934 1935 2172 +a 1934 4106 1 +a 1935 1936 2171 +a 1935 4106 1 +a 1936 1937 2170 +a 1936 4106 1 +a 1937 1938 2169 +a 1937 4106 1 +a 1938 1939 2168 +a 1938 4106 1 +a 1939 1940 2167 +a 1939 4106 1 +a 1940 1941 2166 +a 1940 4106 1 +a 1941 1942 2165 +a 1941 4106 1 +a 1942 1943 2164 +a 1942 4106 1 +a 1943 1944 2163 +a 1943 4106 1 +a 1944 1945 2162 +a 1944 4106 1 +a 1945 1946 2161 +a 1945 4106 1 +a 1946 1947 2160 +a 1946 4106 1 +a 1947 1948 2159 +a 1947 4106 1 +a 1948 1949 2158 +a 1948 4106 1 +a 1949 1950 2157 +a 1949 4106 1 +a 1950 1951 2156 +a 1950 4106 1 +a 1951 1952 2155 +a 1951 4106 1 +a 1952 1953 2154 +a 1952 4106 1 +a 1953 1954 2153 +a 1953 4106 1 +a 1954 1955 2152 +a 1954 4106 1 +a 1955 1956 2151 +a 1955 4106 1 +a 1956 1957 2150 +a 1956 4106 1 +a 1957 1958 2149 +a 1957 4106 1 +a 1958 1959 2148 +a 1958 4106 1 +a 1959 1960 2147 +a 1959 4106 1 +a 1960 1961 2146 +a 1960 4106 1 +a 1961 1962 2145 +a 1961 4106 1 +a 1962 1963 2144 +a 1962 4106 1 +a 1963 1964 2143 +a 1963 4106 1 +a 1964 1965 2142 +a 1964 4106 1 +a 1965 1966 2141 +a 1965 4106 1 +a 1966 1967 2140 +a 1966 4106 1 +a 1967 1968 2139 +a 1967 4106 1 +a 1968 1969 2138 +a 1968 4106 1 +a 1969 1970 2137 +a 1969 4106 1 +a 1970 1971 2136 +a 1970 4106 1 +a 1971 1972 2135 +a 1971 4106 1 +a 1972 1973 2134 +a 1972 4106 1 +a 1973 1974 2133 +a 1973 4106 1 +a 1974 1975 2132 +a 1974 4106 1 +a 1975 1976 2131 +a 1975 4106 1 +a 1976 1977 2130 +a 1976 4106 1 +a 1977 1978 2129 +a 1977 4106 1 +a 1978 1979 2128 +a 1978 4106 1 +a 1979 1980 2127 +a 1979 4106 1 +a 1980 1981 2126 +a 1980 4106 1 +a 1981 1982 2125 +a 1981 4106 1 +a 1982 1983 2124 +a 1982 4106 1 +a 1983 1984 2123 +a 1983 4106 1 +a 1984 1985 2122 +a 1984 4106 1 +a 1985 1986 2121 +a 1985 4106 1 +a 1986 1987 2120 +a 1986 4106 1 +a 1987 1988 2119 +a 1987 4106 1 +a 1988 1989 2118 +a 1988 4106 1 +a 1989 1990 2117 +a 1989 4106 1 +a 1990 1991 2116 +a 1990 4106 1 +a 1991 1992 2115 +a 1991 4106 1 +a 1992 1993 2114 +a 1992 4106 1 +a 1993 1994 2113 +a 1993 4106 1 +a 1994 1995 2112 +a 1994 4106 1 +a 1995 1996 2111 +a 1995 4106 1 +a 1996 1997 2110 +a 1996 4106 1 +a 1997 1998 2109 +a 1997 4106 1 +a 1998 1999 2108 +a 1998 4106 1 +a 1999 2000 2107 +a 1999 4106 1 +a 2000 2001 2106 +a 2000 4106 1 +a 2001 2002 2105 +a 2001 4106 1 +a 2002 2003 2104 +a 2002 4106 1 +a 2003 2004 2103 +a 2003 4106 1 +a 2004 2005 2102 +a 2004 4106 1 +a 2005 2006 2101 +a 2005 4106 1 +a 2006 2007 2100 +a 2006 4106 1 +a 2007 2008 2099 +a 2007 4106 1 +a 2008 2009 2098 +a 2008 4106 1 +a 2009 2010 2097 +a 2009 4106 1 +a 2010 2011 2096 +a 2010 4106 1 +a 2011 2012 2095 +a 2011 4106 1 +a 2012 2013 2094 +a 2012 4106 1 +a 2013 2014 2093 +a 2013 4106 1 +a 2014 2015 2092 +a 2014 4106 1 +a 2015 2016 2091 +a 2015 4106 1 +a 2016 2017 2090 +a 2016 4106 1 +a 2017 2018 2089 +a 2017 4106 1 +a 2018 2019 2088 +a 2018 4106 1 +a 2019 2020 2087 +a 2019 4106 1 +a 2020 2021 2086 +a 2020 4106 1 +a 2021 2022 2085 +a 2021 4106 1 +a 2022 2023 2084 +a 2022 4106 1 +a 2023 2024 2083 +a 2023 4106 1 +a 2024 2025 2082 +a 2024 4106 1 +a 2025 2026 2081 +a 2025 4106 1 +a 2026 2027 2080 +a 2026 4106 1 +a 2027 2028 2079 +a 2027 4106 1 +a 2028 2029 2078 +a 2028 4106 1 +a 2029 2030 2077 +a 2029 4106 1 +a 2030 2031 2076 +a 2030 4106 1 +a 2031 2032 2075 +a 2031 4106 1 +a 2032 2033 2074 +a 2032 4106 1 +a 2033 2034 2073 +a 2033 4106 1 +a 2034 2035 2072 +a 2034 4106 1 +a 2035 2036 2071 +a 2035 4106 1 +a 2036 2037 2070 +a 2036 4106 1 +a 2037 2038 2069 +a 2037 4106 1 +a 2038 2039 2068 +a 2038 4106 1 +a 2039 2040 2067 +a 2039 4106 1 +a 2040 2041 2066 +a 2040 4106 1 +a 2041 2042 2065 +a 2041 4106 1 +a 2042 2043 2064 +a 2042 4106 1 +a 2043 2044 2063 +a 2043 4106 1 +a 2044 2045 2062 +a 2044 4106 1 +a 2045 2046 2061 +a 2045 4106 1 +a 2046 2047 2060 +a 2046 4106 1 +a 2047 2048 2059 +a 2047 4106 1 +a 2048 2049 2058 +a 2048 4106 1 +a 2049 2050 2057 +a 2049 4106 1 +a 2050 2051 2056 +a 2050 4106 1 +a 2051 2052 2055 +a 2051 4106 1 +a 2052 2053 2054 +a 2052 4106 1 +a 2053 2054 2053 +a 2053 4106 1 +a 2054 2055 2052 +a 2054 4106 1 +a 2055 2056 2051 +a 2055 4106 1 +a 2056 2057 2050 +a 2056 4106 1 +a 2057 2058 2049 +a 2057 4106 1 +a 2058 2059 2048 +a 2058 4106 1 +a 2059 2060 2047 +a 2059 4106 1 +a 2060 2061 2046 +a 2060 4106 1 +a 2061 2062 2045 +a 2061 4106 1 +a 2062 2063 2044 +a 2062 4106 1 +a 2063 2064 2043 +a 2063 4106 1 +a 2064 2065 2042 +a 2064 4106 1 +a 2065 2066 2041 +a 2065 4106 1 +a 2066 2067 2040 +a 2066 4106 1 +a 2067 2068 2039 +a 2067 4106 1 +a 2068 2069 2038 +a 2068 4106 1 +a 2069 2070 2037 +a 2069 4106 1 +a 2070 2071 2036 +a 2070 4106 1 +a 2071 2072 2035 +a 2071 4106 1 +a 2072 2073 2034 +a 2072 4106 1 +a 2073 2074 2033 +a 2073 4106 1 +a 2074 2075 2032 +a 2074 4106 1 +a 2075 2076 2031 +a 2075 4106 1 +a 2076 2077 2030 +a 2076 4106 1 +a 2077 2078 2029 +a 2077 4106 1 +a 2078 2079 2028 +a 2078 4106 1 +a 2079 2080 2027 +a 2079 4106 1 +a 2080 2081 2026 +a 2080 4106 1 +a 2081 2082 2025 +a 2081 4106 1 +a 2082 2083 2024 +a 2082 4106 1 +a 2083 2084 2023 +a 2083 4106 1 +a 2084 2085 2022 +a 2084 4106 1 +a 2085 2086 2021 +a 2085 4106 1 +a 2086 2087 2020 +a 2086 4106 1 +a 2087 2088 2019 +a 2087 4106 1 +a 2088 2089 2018 +a 2088 4106 1 +a 2089 2090 2017 +a 2089 4106 1 +a 2090 2091 2016 +a 2090 4106 1 +a 2091 2092 2015 +a 2091 4106 1 +a 2092 2093 2014 +a 2092 4106 1 +a 2093 2094 2013 +a 2093 4106 1 +a 2094 2095 2012 +a 2094 4106 1 +a 2095 2096 2011 +a 2095 4106 1 +a 2096 2097 2010 +a 2096 4106 1 +a 2097 2098 2009 +a 2097 4106 1 +a 2098 2099 2008 +a 2098 4106 1 +a 2099 2100 2007 +a 2099 4106 1 +a 2100 2101 2006 +a 2100 4106 1 +a 2101 2102 2005 +a 2101 4106 1 +a 2102 2103 2004 +a 2102 4106 1 +a 2103 2104 2003 +a 2103 4106 1 +a 2104 2105 2002 +a 2104 4106 1 +a 2105 2106 2001 +a 2105 4106 1 +a 2106 2107 2000 +a 2106 4106 1 +a 2107 2108 1999 +a 2107 4106 1 +a 2108 2109 1998 +a 2108 4106 1 +a 2109 2110 1997 +a 2109 4106 1 +a 2110 2111 1996 +a 2110 4106 1 +a 2111 2112 1995 +a 2111 4106 1 +a 2112 2113 1994 +a 2112 4106 1 +a 2113 2114 1993 +a 2113 4106 1 +a 2114 2115 1992 +a 2114 4106 1 +a 2115 2116 1991 +a 2115 4106 1 +a 2116 2117 1990 +a 2116 4106 1 +a 2117 2118 1989 +a 2117 4106 1 +a 2118 2119 1988 +a 2118 4106 1 +a 2119 2120 1987 +a 2119 4106 1 +a 2120 2121 1986 +a 2120 4106 1 +a 2121 2122 1985 +a 2121 4106 1 +a 2122 2123 1984 +a 2122 4106 1 +a 2123 2124 1983 +a 2123 4106 1 +a 2124 2125 1982 +a 2124 4106 1 +a 2125 2126 1981 +a 2125 4106 1 +a 2126 2127 1980 +a 2126 4106 1 +a 2127 2128 1979 +a 2127 4106 1 +a 2128 2129 1978 +a 2128 4106 1 +a 2129 2130 1977 +a 2129 4106 1 +a 2130 2131 1976 +a 2130 4106 1 +a 2131 2132 1975 +a 2131 4106 1 +a 2132 2133 1974 +a 2132 4106 1 +a 2133 2134 1973 +a 2133 4106 1 +a 2134 2135 1972 +a 2134 4106 1 +a 2135 2136 1971 +a 2135 4106 1 +a 2136 2137 1970 +a 2136 4106 1 +a 2137 2138 1969 +a 2137 4106 1 +a 2138 2139 1968 +a 2138 4106 1 +a 2139 2140 1967 +a 2139 4106 1 +a 2140 2141 1966 +a 2140 4106 1 +a 2141 2142 1965 +a 2141 4106 1 +a 2142 2143 1964 +a 2142 4106 1 +a 2143 2144 1963 +a 2143 4106 1 +a 2144 2145 1962 +a 2144 4106 1 +a 2145 2146 1961 +a 2145 4106 1 +a 2146 2147 1960 +a 2146 4106 1 +a 2147 2148 1959 +a 2147 4106 1 +a 2148 2149 1958 +a 2148 4106 1 +a 2149 2150 1957 +a 2149 4106 1 +a 2150 2151 1956 +a 2150 4106 1 +a 2151 2152 1955 +a 2151 4106 1 +a 2152 2153 1954 +a 2152 4106 1 +a 2153 2154 1953 +a 2153 4106 1 +a 2154 2155 1952 +a 2154 4106 1 +a 2155 2156 1951 +a 2155 4106 1 +a 2156 2157 1950 +a 2156 4106 1 +a 2157 2158 1949 +a 2157 4106 1 +a 2158 2159 1948 +a 2158 4106 1 +a 2159 2160 1947 +a 2159 4106 1 +a 2160 2161 1946 +a 2160 4106 1 +a 2161 2162 1945 +a 2161 4106 1 +a 2162 2163 1944 +a 2162 4106 1 +a 2163 2164 1943 +a 2163 4106 1 +a 2164 2165 1942 +a 2164 4106 1 +a 2165 2166 1941 +a 2165 4106 1 +a 2166 2167 1940 +a 2166 4106 1 +a 2167 2168 1939 +a 2167 4106 1 +a 2168 2169 1938 +a 2168 4106 1 +a 2169 2170 1937 +a 2169 4106 1 +a 2170 2171 1936 +a 2170 4106 1 +a 2171 2172 1935 +a 2171 4106 1 +a 2172 2173 1934 +a 2172 4106 1 +a 2173 2174 1933 +a 2173 4106 1 +a 2174 2175 1932 +a 2174 4106 1 +a 2175 2176 1931 +a 2175 4106 1 +a 2176 2177 1930 +a 2176 4106 1 +a 2177 2178 1929 +a 2177 4106 1 +a 2178 2179 1928 +a 2178 4106 1 +a 2179 2180 1927 +a 2179 4106 1 +a 2180 2181 1926 +a 2180 4106 1 +a 2181 2182 1925 +a 2181 4106 1 +a 2182 2183 1924 +a 2182 4106 1 +a 2183 2184 1923 +a 2183 4106 1 +a 2184 2185 1922 +a 2184 4106 1 +a 2185 2186 1921 +a 2185 4106 1 +a 2186 2187 1920 +a 2186 4106 1 +a 2187 2188 1919 +a 2187 4106 1 +a 2188 2189 1918 +a 2188 4106 1 +a 2189 2190 1917 +a 2189 4106 1 +a 2190 2191 1916 +a 2190 4106 1 +a 2191 2192 1915 +a 2191 4106 1 +a 2192 2193 1914 +a 2192 4106 1 +a 2193 2194 1913 +a 2193 4106 1 +a 2194 2195 1912 +a 2194 4106 1 +a 2195 2196 1911 +a 2195 4106 1 +a 2196 2197 1910 +a 2196 4106 1 +a 2197 2198 1909 +a 2197 4106 1 +a 2198 2199 1908 +a 2198 4106 1 +a 2199 2200 1907 +a 2199 4106 1 +a 2200 2201 1906 +a 2200 4106 1 +a 2201 2202 1905 +a 2201 4106 1 +a 2202 2203 1904 +a 2202 4106 1 +a 2203 2204 1903 +a 2203 4106 1 +a 2204 2205 1902 +a 2204 4106 1 +a 2205 2206 1901 +a 2205 4106 1 +a 2206 2207 1900 +a 2206 4106 1 +a 2207 2208 1899 +a 2207 4106 1 +a 2208 2209 1898 +a 2208 4106 1 +a 2209 2210 1897 +a 2209 4106 1 +a 2210 2211 1896 +a 2210 4106 1 +a 2211 2212 1895 +a 2211 4106 1 +a 2212 2213 1894 +a 2212 4106 1 +a 2213 2214 1893 +a 2213 4106 1 +a 2214 2215 1892 +a 2214 4106 1 +a 2215 2216 1891 +a 2215 4106 1 +a 2216 2217 1890 +a 2216 4106 1 +a 2217 2218 1889 +a 2217 4106 1 +a 2218 2219 1888 +a 2218 4106 1 +a 2219 2220 1887 +a 2219 4106 1 +a 2220 2221 1886 +a 2220 4106 1 +a 2221 2222 1885 +a 2221 4106 1 +a 2222 2223 1884 +a 2222 4106 1 +a 2223 2224 1883 +a 2223 4106 1 +a 2224 2225 1882 +a 2224 4106 1 +a 2225 2226 1881 +a 2225 4106 1 +a 2226 2227 1880 +a 2226 4106 1 +a 2227 2228 1879 +a 2227 4106 1 +a 2228 2229 1878 +a 2228 4106 1 +a 2229 2230 1877 +a 2229 4106 1 +a 2230 2231 1876 +a 2230 4106 1 +a 2231 2232 1875 +a 2231 4106 1 +a 2232 2233 1874 +a 2232 4106 1 +a 2233 2234 1873 +a 2233 4106 1 +a 2234 2235 1872 +a 2234 4106 1 +a 2235 2236 1871 +a 2235 4106 1 +a 2236 2237 1870 +a 2236 4106 1 +a 2237 2238 1869 +a 2237 4106 1 +a 2238 2239 1868 +a 2238 4106 1 +a 2239 2240 1867 +a 2239 4106 1 +a 2240 2241 1866 +a 2240 4106 1 +a 2241 2242 1865 +a 2241 4106 1 +a 2242 2243 1864 +a 2242 4106 1 +a 2243 2244 1863 +a 2243 4106 1 +a 2244 2245 1862 +a 2244 4106 1 +a 2245 2246 1861 +a 2245 4106 1 +a 2246 2247 1860 +a 2246 4106 1 +a 2247 2248 1859 +a 2247 4106 1 +a 2248 2249 1858 +a 2248 4106 1 +a 2249 2250 1857 +a 2249 4106 1 +a 2250 2251 1856 +a 2250 4106 1 +a 2251 2252 1855 +a 2251 4106 1 +a 2252 2253 1854 +a 2252 4106 1 +a 2253 2254 1853 +a 2253 4106 1 +a 2254 2255 1852 +a 2254 4106 1 +a 2255 2256 1851 +a 2255 4106 1 +a 2256 2257 1850 +a 2256 4106 1 +a 2257 2258 1849 +a 2257 4106 1 +a 2258 2259 1848 +a 2258 4106 1 +a 2259 2260 1847 +a 2259 4106 1 +a 2260 2261 1846 +a 2260 4106 1 +a 2261 2262 1845 +a 2261 4106 1 +a 2262 2263 1844 +a 2262 4106 1 +a 2263 2264 1843 +a 2263 4106 1 +a 2264 2265 1842 +a 2264 4106 1 +a 2265 2266 1841 +a 2265 4106 1 +a 2266 2267 1840 +a 2266 4106 1 +a 2267 2268 1839 +a 2267 4106 1 +a 2268 2269 1838 +a 2268 4106 1 +a 2269 2270 1837 +a 2269 4106 1 +a 2270 2271 1836 +a 2270 4106 1 +a 2271 2272 1835 +a 2271 4106 1 +a 2272 2273 1834 +a 2272 4106 1 +a 2273 2274 1833 +a 2273 4106 1 +a 2274 2275 1832 +a 2274 4106 1 +a 2275 2276 1831 +a 2275 4106 1 +a 2276 2277 1830 +a 2276 4106 1 +a 2277 2278 1829 +a 2277 4106 1 +a 2278 2279 1828 +a 2278 4106 1 +a 2279 2280 1827 +a 2279 4106 1 +a 2280 2281 1826 +a 2280 4106 1 +a 2281 2282 1825 +a 2281 4106 1 +a 2282 2283 1824 +a 2282 4106 1 +a 2283 2284 1823 +a 2283 4106 1 +a 2284 2285 1822 +a 2284 4106 1 +a 2285 2286 1821 +a 2285 4106 1 +a 2286 2287 1820 +a 2286 4106 1 +a 2287 2288 1819 +a 2287 4106 1 +a 2288 2289 1818 +a 2288 4106 1 +a 2289 2290 1817 +a 2289 4106 1 +a 2290 2291 1816 +a 2290 4106 1 +a 2291 2292 1815 +a 2291 4106 1 +a 2292 2293 1814 +a 2292 4106 1 +a 2293 2294 1813 +a 2293 4106 1 +a 2294 2295 1812 +a 2294 4106 1 +a 2295 2296 1811 +a 2295 4106 1 +a 2296 2297 1810 +a 2296 4106 1 +a 2297 2298 1809 +a 2297 4106 1 +a 2298 2299 1808 +a 2298 4106 1 +a 2299 2300 1807 +a 2299 4106 1 +a 2300 2301 1806 +a 2300 4106 1 +a 2301 2302 1805 +a 2301 4106 1 +a 2302 2303 1804 +a 2302 4106 1 +a 2303 2304 1803 +a 2303 4106 1 +a 2304 2305 1802 +a 2304 4106 1 +a 2305 2306 1801 +a 2305 4106 1 +a 2306 2307 1800 +a 2306 4106 1 +a 2307 2308 1799 +a 2307 4106 1 +a 2308 2309 1798 +a 2308 4106 1 +a 2309 2310 1797 +a 2309 4106 1 +a 2310 2311 1796 +a 2310 4106 1 +a 2311 2312 1795 +a 2311 4106 1 +a 2312 2313 1794 +a 2312 4106 1 +a 2313 2314 1793 +a 2313 4106 1 +a 2314 2315 1792 +a 2314 4106 1 +a 2315 2316 1791 +a 2315 4106 1 +a 2316 2317 1790 +a 2316 4106 1 +a 2317 2318 1789 +a 2317 4106 1 +a 2318 2319 1788 +a 2318 4106 1 +a 2319 2320 1787 +a 2319 4106 1 +a 2320 2321 1786 +a 2320 4106 1 +a 2321 2322 1785 +a 2321 4106 1 +a 2322 2323 1784 +a 2322 4106 1 +a 2323 2324 1783 +a 2323 4106 1 +a 2324 2325 1782 +a 2324 4106 1 +a 2325 2326 1781 +a 2325 4106 1 +a 2326 2327 1780 +a 2326 4106 1 +a 2327 2328 1779 +a 2327 4106 1 +a 2328 2329 1778 +a 2328 4106 1 +a 2329 2330 1777 +a 2329 4106 1 +a 2330 2331 1776 +a 2330 4106 1 +a 2331 2332 1775 +a 2331 4106 1 +a 2332 2333 1774 +a 2332 4106 1 +a 2333 2334 1773 +a 2333 4106 1 +a 2334 2335 1772 +a 2334 4106 1 +a 2335 2336 1771 +a 2335 4106 1 +a 2336 2337 1770 +a 2336 4106 1 +a 2337 2338 1769 +a 2337 4106 1 +a 2338 2339 1768 +a 2338 4106 1 +a 2339 2340 1767 +a 2339 4106 1 +a 2340 2341 1766 +a 2340 4106 1 +a 2341 2342 1765 +a 2341 4106 1 +a 2342 2343 1764 +a 2342 4106 1 +a 2343 2344 1763 +a 2343 4106 1 +a 2344 2345 1762 +a 2344 4106 1 +a 2345 2346 1761 +a 2345 4106 1 +a 2346 2347 1760 +a 2346 4106 1 +a 2347 2348 1759 +a 2347 4106 1 +a 2348 2349 1758 +a 2348 4106 1 +a 2349 2350 1757 +a 2349 4106 1 +a 2350 2351 1756 +a 2350 4106 1 +a 2351 2352 1755 +a 2351 4106 1 +a 2352 2353 1754 +a 2352 4106 1 +a 2353 2354 1753 +a 2353 4106 1 +a 2354 2355 1752 +a 2354 4106 1 +a 2355 2356 1751 +a 2355 4106 1 +a 2356 2357 1750 +a 2356 4106 1 +a 2357 2358 1749 +a 2357 4106 1 +a 2358 2359 1748 +a 2358 4106 1 +a 2359 2360 1747 +a 2359 4106 1 +a 2360 2361 1746 +a 2360 4106 1 +a 2361 2362 1745 +a 2361 4106 1 +a 2362 2363 1744 +a 2362 4106 1 +a 2363 2364 1743 +a 2363 4106 1 +a 2364 2365 1742 +a 2364 4106 1 +a 2365 2366 1741 +a 2365 4106 1 +a 2366 2367 1740 +a 2366 4106 1 +a 2367 2368 1739 +a 2367 4106 1 +a 2368 2369 1738 +a 2368 4106 1 +a 2369 2370 1737 +a 2369 4106 1 +a 2370 2371 1736 +a 2370 4106 1 +a 2371 2372 1735 +a 2371 4106 1 +a 2372 2373 1734 +a 2372 4106 1 +a 2373 2374 1733 +a 2373 4106 1 +a 2374 2375 1732 +a 2374 4106 1 +a 2375 2376 1731 +a 2375 4106 1 +a 2376 2377 1730 +a 2376 4106 1 +a 2377 2378 1729 +a 2377 4106 1 +a 2378 2379 1728 +a 2378 4106 1 +a 2379 2380 1727 +a 2379 4106 1 +a 2380 2381 1726 +a 2380 4106 1 +a 2381 2382 1725 +a 2381 4106 1 +a 2382 2383 1724 +a 2382 4106 1 +a 2383 2384 1723 +a 2383 4106 1 +a 2384 2385 1722 +a 2384 4106 1 +a 2385 2386 1721 +a 2385 4106 1 +a 2386 2387 1720 +a 2386 4106 1 +a 2387 2388 1719 +a 2387 4106 1 +a 2388 2389 1718 +a 2388 4106 1 +a 2389 2390 1717 +a 2389 4106 1 +a 2390 2391 1716 +a 2390 4106 1 +a 2391 2392 1715 +a 2391 4106 1 +a 2392 2393 1714 +a 2392 4106 1 +a 2393 2394 1713 +a 2393 4106 1 +a 2394 2395 1712 +a 2394 4106 1 +a 2395 2396 1711 +a 2395 4106 1 +a 2396 2397 1710 +a 2396 4106 1 +a 2397 2398 1709 +a 2397 4106 1 +a 2398 2399 1708 +a 2398 4106 1 +a 2399 2400 1707 +a 2399 4106 1 +a 2400 2401 1706 +a 2400 4106 1 +a 2401 2402 1705 +a 2401 4106 1 +a 2402 2403 1704 +a 2402 4106 1 +a 2403 2404 1703 +a 2403 4106 1 +a 2404 2405 1702 +a 2404 4106 1 +a 2405 2406 1701 +a 2405 4106 1 +a 2406 2407 1700 +a 2406 4106 1 +a 2407 2408 1699 +a 2407 4106 1 +a 2408 2409 1698 +a 2408 4106 1 +a 2409 2410 1697 +a 2409 4106 1 +a 2410 2411 1696 +a 2410 4106 1 +a 2411 2412 1695 +a 2411 4106 1 +a 2412 2413 1694 +a 2412 4106 1 +a 2413 2414 1693 +a 2413 4106 1 +a 2414 2415 1692 +a 2414 4106 1 +a 2415 2416 1691 +a 2415 4106 1 +a 2416 2417 1690 +a 2416 4106 1 +a 2417 2418 1689 +a 2417 4106 1 +a 2418 2419 1688 +a 2418 4106 1 +a 2419 2420 1687 +a 2419 4106 1 +a 2420 2421 1686 +a 2420 4106 1 +a 2421 2422 1685 +a 2421 4106 1 +a 2422 2423 1684 +a 2422 4106 1 +a 2423 2424 1683 +a 2423 4106 1 +a 2424 2425 1682 +a 2424 4106 1 +a 2425 2426 1681 +a 2425 4106 1 +a 2426 2427 1680 +a 2426 4106 1 +a 2427 2428 1679 +a 2427 4106 1 +a 2428 2429 1678 +a 2428 4106 1 +a 2429 2430 1677 +a 2429 4106 1 +a 2430 2431 1676 +a 2430 4106 1 +a 2431 2432 1675 +a 2431 4106 1 +a 2432 2433 1674 +a 2432 4106 1 +a 2433 2434 1673 +a 2433 4106 1 +a 2434 2435 1672 +a 2434 4106 1 +a 2435 2436 1671 +a 2435 4106 1 +a 2436 2437 1670 +a 2436 4106 1 +a 2437 2438 1669 +a 2437 4106 1 +a 2438 2439 1668 +a 2438 4106 1 +a 2439 2440 1667 +a 2439 4106 1 +a 2440 2441 1666 +a 2440 4106 1 +a 2441 2442 1665 +a 2441 4106 1 +a 2442 2443 1664 +a 2442 4106 1 +a 2443 2444 1663 +a 2443 4106 1 +a 2444 2445 1662 +a 2444 4106 1 +a 2445 2446 1661 +a 2445 4106 1 +a 2446 2447 1660 +a 2446 4106 1 +a 2447 2448 1659 +a 2447 4106 1 +a 2448 2449 1658 +a 2448 4106 1 +a 2449 2450 1657 +a 2449 4106 1 +a 2450 2451 1656 +a 2450 4106 1 +a 2451 2452 1655 +a 2451 4106 1 +a 2452 2453 1654 +a 2452 4106 1 +a 2453 2454 1653 +a 2453 4106 1 +a 2454 2455 1652 +a 2454 4106 1 +a 2455 2456 1651 +a 2455 4106 1 +a 2456 2457 1650 +a 2456 4106 1 +a 2457 2458 1649 +a 2457 4106 1 +a 2458 2459 1648 +a 2458 4106 1 +a 2459 2460 1647 +a 2459 4106 1 +a 2460 2461 1646 +a 2460 4106 1 +a 2461 2462 1645 +a 2461 4106 1 +a 2462 2463 1644 +a 2462 4106 1 +a 2463 2464 1643 +a 2463 4106 1 +a 2464 2465 1642 +a 2464 4106 1 +a 2465 2466 1641 +a 2465 4106 1 +a 2466 2467 1640 +a 2466 4106 1 +a 2467 2468 1639 +a 2467 4106 1 +a 2468 2469 1638 +a 2468 4106 1 +a 2469 2470 1637 +a 2469 4106 1 +a 2470 2471 1636 +a 2470 4106 1 +a 2471 2472 1635 +a 2471 4106 1 +a 2472 2473 1634 +a 2472 4106 1 +a 2473 2474 1633 +a 2473 4106 1 +a 2474 2475 1632 +a 2474 4106 1 +a 2475 2476 1631 +a 2475 4106 1 +a 2476 2477 1630 +a 2476 4106 1 +a 2477 2478 1629 +a 2477 4106 1 +a 2478 2479 1628 +a 2478 4106 1 +a 2479 2480 1627 +a 2479 4106 1 +a 2480 2481 1626 +a 2480 4106 1 +a 2481 2482 1625 +a 2481 4106 1 +a 2482 2483 1624 +a 2482 4106 1 +a 2483 2484 1623 +a 2483 4106 1 +a 2484 2485 1622 +a 2484 4106 1 +a 2485 2486 1621 +a 2485 4106 1 +a 2486 2487 1620 +a 2486 4106 1 +a 2487 2488 1619 +a 2487 4106 1 +a 2488 2489 1618 +a 2488 4106 1 +a 2489 2490 1617 +a 2489 4106 1 +a 2490 2491 1616 +a 2490 4106 1 +a 2491 2492 1615 +a 2491 4106 1 +a 2492 2493 1614 +a 2492 4106 1 +a 2493 2494 1613 +a 2493 4106 1 +a 2494 2495 1612 +a 2494 4106 1 +a 2495 2496 1611 +a 2495 4106 1 +a 2496 2497 1610 +a 2496 4106 1 +a 2497 2498 1609 +a 2497 4106 1 +a 2498 2499 1608 +a 2498 4106 1 +a 2499 2500 1607 +a 2499 4106 1 +a 2500 2501 1606 +a 2500 4106 1 +a 2501 2502 1605 +a 2501 4106 1 +a 2502 2503 1604 +a 2502 4106 1 +a 2503 2504 1603 +a 2503 4106 1 +a 2504 2505 1602 +a 2504 4106 1 +a 2505 2506 1601 +a 2505 4106 1 +a 2506 2507 1600 +a 2506 4106 1 +a 2507 2508 1599 +a 2507 4106 1 +a 2508 2509 1598 +a 2508 4106 1 +a 2509 2510 1597 +a 2509 4106 1 +a 2510 2511 1596 +a 2510 4106 1 +a 2511 2512 1595 +a 2511 4106 1 +a 2512 2513 1594 +a 2512 4106 1 +a 2513 2514 1593 +a 2513 4106 1 +a 2514 2515 1592 +a 2514 4106 1 +a 2515 2516 1591 +a 2515 4106 1 +a 2516 2517 1590 +a 2516 4106 1 +a 2517 2518 1589 +a 2517 4106 1 +a 2518 2519 1588 +a 2518 4106 1 +a 2519 2520 1587 +a 2519 4106 1 +a 2520 2521 1586 +a 2520 4106 1 +a 2521 2522 1585 +a 2521 4106 1 +a 2522 2523 1584 +a 2522 4106 1 +a 2523 2524 1583 +a 2523 4106 1 +a 2524 2525 1582 +a 2524 4106 1 +a 2525 2526 1581 +a 2525 4106 1 +a 2526 2527 1580 +a 2526 4106 1 +a 2527 2528 1579 +a 2527 4106 1 +a 2528 2529 1578 +a 2528 4106 1 +a 2529 2530 1577 +a 2529 4106 1 +a 2530 2531 1576 +a 2530 4106 1 +a 2531 2532 1575 +a 2531 4106 1 +a 2532 2533 1574 +a 2532 4106 1 +a 2533 2534 1573 +a 2533 4106 1 +a 2534 2535 1572 +a 2534 4106 1 +a 2535 2536 1571 +a 2535 4106 1 +a 2536 2537 1570 +a 2536 4106 1 +a 2537 2538 1569 +a 2537 4106 1 +a 2538 2539 1568 +a 2538 4106 1 +a 2539 2540 1567 +a 2539 4106 1 +a 2540 2541 1566 +a 2540 4106 1 +a 2541 2542 1565 +a 2541 4106 1 +a 2542 2543 1564 +a 2542 4106 1 +a 2543 2544 1563 +a 2543 4106 1 +a 2544 2545 1562 +a 2544 4106 1 +a 2545 2546 1561 +a 2545 4106 1 +a 2546 2547 1560 +a 2546 4106 1 +a 2547 2548 1559 +a 2547 4106 1 +a 2548 2549 1558 +a 2548 4106 1 +a 2549 2550 1557 +a 2549 4106 1 +a 2550 2551 1556 +a 2550 4106 1 +a 2551 2552 1555 +a 2551 4106 1 +a 2552 2553 1554 +a 2552 4106 1 +a 2553 2554 1553 +a 2553 4106 1 +a 2554 2555 1552 +a 2554 4106 1 +a 2555 2556 1551 +a 2555 4106 1 +a 2556 2557 1550 +a 2556 4106 1 +a 2557 2558 1549 +a 2557 4106 1 +a 2558 2559 1548 +a 2558 4106 1 +a 2559 2560 1547 +a 2559 4106 1 +a 2560 2561 1546 +a 2560 4106 1 +a 2561 2562 1545 +a 2561 4106 1 +a 2562 2563 1544 +a 2562 4106 1 +a 2563 2564 1543 +a 2563 4106 1 +a 2564 2565 1542 +a 2564 4106 1 +a 2565 2566 1541 +a 2565 4106 1 +a 2566 2567 1540 +a 2566 4106 1 +a 2567 2568 1539 +a 2567 4106 1 +a 2568 2569 1538 +a 2568 4106 1 +a 2569 2570 1537 +a 2569 4106 1 +a 2570 2571 1536 +a 2570 4106 1 +a 2571 2572 1535 +a 2571 4106 1 +a 2572 2573 1534 +a 2572 4106 1 +a 2573 2574 1533 +a 2573 4106 1 +a 2574 2575 1532 +a 2574 4106 1 +a 2575 2576 1531 +a 2575 4106 1 +a 2576 2577 1530 +a 2576 4106 1 +a 2577 2578 1529 +a 2577 4106 1 +a 2578 2579 1528 +a 2578 4106 1 +a 2579 2580 1527 +a 2579 4106 1 +a 2580 2581 1526 +a 2580 4106 1 +a 2581 2582 1525 +a 2581 4106 1 +a 2582 2583 1524 +a 2582 4106 1 +a 2583 2584 1523 +a 2583 4106 1 +a 2584 2585 1522 +a 2584 4106 1 +a 2585 2586 1521 +a 2585 4106 1 +a 2586 2587 1520 +a 2586 4106 1 +a 2587 2588 1519 +a 2587 4106 1 +a 2588 2589 1518 +a 2588 4106 1 +a 2589 2590 1517 +a 2589 4106 1 +a 2590 2591 1516 +a 2590 4106 1 +a 2591 2592 1515 +a 2591 4106 1 +a 2592 2593 1514 +a 2592 4106 1 +a 2593 2594 1513 +a 2593 4106 1 +a 2594 2595 1512 +a 2594 4106 1 +a 2595 2596 1511 +a 2595 4106 1 +a 2596 2597 1510 +a 2596 4106 1 +a 2597 2598 1509 +a 2597 4106 1 +a 2598 2599 1508 +a 2598 4106 1 +a 2599 2600 1507 +a 2599 4106 1 +a 2600 2601 1506 +a 2600 4106 1 +a 2601 2602 1505 +a 2601 4106 1 +a 2602 2603 1504 +a 2602 4106 1 +a 2603 2604 1503 +a 2603 4106 1 +a 2604 2605 1502 +a 2604 4106 1 +a 2605 2606 1501 +a 2605 4106 1 +a 2606 2607 1500 +a 2606 4106 1 +a 2607 2608 1499 +a 2607 4106 1 +a 2608 2609 1498 +a 2608 4106 1 +a 2609 2610 1497 +a 2609 4106 1 +a 2610 2611 1496 +a 2610 4106 1 +a 2611 2612 1495 +a 2611 4106 1 +a 2612 2613 1494 +a 2612 4106 1 +a 2613 2614 1493 +a 2613 4106 1 +a 2614 2615 1492 +a 2614 4106 1 +a 2615 2616 1491 +a 2615 4106 1 +a 2616 2617 1490 +a 2616 4106 1 +a 2617 2618 1489 +a 2617 4106 1 +a 2618 2619 1488 +a 2618 4106 1 +a 2619 2620 1487 +a 2619 4106 1 +a 2620 2621 1486 +a 2620 4106 1 +a 2621 2622 1485 +a 2621 4106 1 +a 2622 2623 1484 +a 2622 4106 1 +a 2623 2624 1483 +a 2623 4106 1 +a 2624 2625 1482 +a 2624 4106 1 +a 2625 2626 1481 +a 2625 4106 1 +a 2626 2627 1480 +a 2626 4106 1 +a 2627 2628 1479 +a 2627 4106 1 +a 2628 2629 1478 +a 2628 4106 1 +a 2629 2630 1477 +a 2629 4106 1 +a 2630 2631 1476 +a 2630 4106 1 +a 2631 2632 1475 +a 2631 4106 1 +a 2632 2633 1474 +a 2632 4106 1 +a 2633 2634 1473 +a 2633 4106 1 +a 2634 2635 1472 +a 2634 4106 1 +a 2635 2636 1471 +a 2635 4106 1 +a 2636 2637 1470 +a 2636 4106 1 +a 2637 2638 1469 +a 2637 4106 1 +a 2638 2639 1468 +a 2638 4106 1 +a 2639 2640 1467 +a 2639 4106 1 +a 2640 2641 1466 +a 2640 4106 1 +a 2641 2642 1465 +a 2641 4106 1 +a 2642 2643 1464 +a 2642 4106 1 +a 2643 2644 1463 +a 2643 4106 1 +a 2644 2645 1462 +a 2644 4106 1 +a 2645 2646 1461 +a 2645 4106 1 +a 2646 2647 1460 +a 2646 4106 1 +a 2647 2648 1459 +a 2647 4106 1 +a 2648 2649 1458 +a 2648 4106 1 +a 2649 2650 1457 +a 2649 4106 1 +a 2650 2651 1456 +a 2650 4106 1 +a 2651 2652 1455 +a 2651 4106 1 +a 2652 2653 1454 +a 2652 4106 1 +a 2653 2654 1453 +a 2653 4106 1 +a 2654 2655 1452 +a 2654 4106 1 +a 2655 2656 1451 +a 2655 4106 1 +a 2656 2657 1450 +a 2656 4106 1 +a 2657 2658 1449 +a 2657 4106 1 +a 2658 2659 1448 +a 2658 4106 1 +a 2659 2660 1447 +a 2659 4106 1 +a 2660 2661 1446 +a 2660 4106 1 +a 2661 2662 1445 +a 2661 4106 1 +a 2662 2663 1444 +a 2662 4106 1 +a 2663 2664 1443 +a 2663 4106 1 +a 2664 2665 1442 +a 2664 4106 1 +a 2665 2666 1441 +a 2665 4106 1 +a 2666 2667 1440 +a 2666 4106 1 +a 2667 2668 1439 +a 2667 4106 1 +a 2668 2669 1438 +a 2668 4106 1 +a 2669 2670 1437 +a 2669 4106 1 +a 2670 2671 1436 +a 2670 4106 1 +a 2671 2672 1435 +a 2671 4106 1 +a 2672 2673 1434 +a 2672 4106 1 +a 2673 2674 1433 +a 2673 4106 1 +a 2674 2675 1432 +a 2674 4106 1 +a 2675 2676 1431 +a 2675 4106 1 +a 2676 2677 1430 +a 2676 4106 1 +a 2677 2678 1429 +a 2677 4106 1 +a 2678 2679 1428 +a 2678 4106 1 +a 2679 2680 1427 +a 2679 4106 1 +a 2680 2681 1426 +a 2680 4106 1 +a 2681 2682 1425 +a 2681 4106 1 +a 2682 2683 1424 +a 2682 4106 1 +a 2683 2684 1423 +a 2683 4106 1 +a 2684 2685 1422 +a 2684 4106 1 +a 2685 2686 1421 +a 2685 4106 1 +a 2686 2687 1420 +a 2686 4106 1 +a 2687 2688 1419 +a 2687 4106 1 +a 2688 2689 1418 +a 2688 4106 1 +a 2689 2690 1417 +a 2689 4106 1 +a 2690 2691 1416 +a 2690 4106 1 +a 2691 2692 1415 +a 2691 4106 1 +a 2692 2693 1414 +a 2692 4106 1 +a 2693 2694 1413 +a 2693 4106 1 +a 2694 2695 1412 +a 2694 4106 1 +a 2695 2696 1411 +a 2695 4106 1 +a 2696 2697 1410 +a 2696 4106 1 +a 2697 2698 1409 +a 2697 4106 1 +a 2698 2699 1408 +a 2698 4106 1 +a 2699 2700 1407 +a 2699 4106 1 +a 2700 2701 1406 +a 2700 4106 1 +a 2701 2702 1405 +a 2701 4106 1 +a 2702 2703 1404 +a 2702 4106 1 +a 2703 2704 1403 +a 2703 4106 1 +a 2704 2705 1402 +a 2704 4106 1 +a 2705 2706 1401 +a 2705 4106 1 +a 2706 2707 1400 +a 2706 4106 1 +a 2707 2708 1399 +a 2707 4106 1 +a 2708 2709 1398 +a 2708 4106 1 +a 2709 2710 1397 +a 2709 4106 1 +a 2710 2711 1396 +a 2710 4106 1 +a 2711 2712 1395 +a 2711 4106 1 +a 2712 2713 1394 +a 2712 4106 1 +a 2713 2714 1393 +a 2713 4106 1 +a 2714 2715 1392 +a 2714 4106 1 +a 2715 2716 1391 +a 2715 4106 1 +a 2716 2717 1390 +a 2716 4106 1 +a 2717 2718 1389 +a 2717 4106 1 +a 2718 2719 1388 +a 2718 4106 1 +a 2719 2720 1387 +a 2719 4106 1 +a 2720 2721 1386 +a 2720 4106 1 +a 2721 2722 1385 +a 2721 4106 1 +a 2722 2723 1384 +a 2722 4106 1 +a 2723 2724 1383 +a 2723 4106 1 +a 2724 2725 1382 +a 2724 4106 1 +a 2725 2726 1381 +a 2725 4106 1 +a 2726 2727 1380 +a 2726 4106 1 +a 2727 2728 1379 +a 2727 4106 1 +a 2728 2729 1378 +a 2728 4106 1 +a 2729 2730 1377 +a 2729 4106 1 +a 2730 2731 1376 +a 2730 4106 1 +a 2731 2732 1375 +a 2731 4106 1 +a 2732 2733 1374 +a 2732 4106 1 +a 2733 2734 1373 +a 2733 4106 1 +a 2734 2735 1372 +a 2734 4106 1 +a 2735 2736 1371 +a 2735 4106 1 +a 2736 2737 1370 +a 2736 4106 1 +a 2737 2738 1369 +a 2737 4106 1 +a 2738 2739 1368 +a 2738 4106 1 +a 2739 2740 1367 +a 2739 4106 1 +a 2740 2741 1366 +a 2740 4106 1 +a 2741 2742 1365 +a 2741 4106 1 +a 2742 2743 1364 +a 2742 4106 1 +a 2743 2744 1363 +a 2743 4106 1 +a 2744 2745 1362 +a 2744 4106 1 +a 2745 2746 1361 +a 2745 4106 1 +a 2746 2747 1360 +a 2746 4106 1 +a 2747 2748 1359 +a 2747 4106 1 +a 2748 2749 1358 +a 2748 4106 1 +a 2749 2750 1357 +a 2749 4106 1 +a 2750 2751 1356 +a 2750 4106 1 +a 2751 2752 1355 +a 2751 4106 1 +a 2752 2753 1354 +a 2752 4106 1 +a 2753 2754 1353 +a 2753 4106 1 +a 2754 2755 1352 +a 2754 4106 1 +a 2755 2756 1351 +a 2755 4106 1 +a 2756 2757 1350 +a 2756 4106 1 +a 2757 2758 1349 +a 2757 4106 1 +a 2758 2759 1348 +a 2758 4106 1 +a 2759 2760 1347 +a 2759 4106 1 +a 2760 2761 1346 +a 2760 4106 1 +a 2761 2762 1345 +a 2761 4106 1 +a 2762 2763 1344 +a 2762 4106 1 +a 2763 2764 1343 +a 2763 4106 1 +a 2764 2765 1342 +a 2764 4106 1 +a 2765 2766 1341 +a 2765 4106 1 +a 2766 2767 1340 +a 2766 4106 1 +a 2767 2768 1339 +a 2767 4106 1 +a 2768 2769 1338 +a 2768 4106 1 +a 2769 2770 1337 +a 2769 4106 1 +a 2770 2771 1336 +a 2770 4106 1 +a 2771 2772 1335 +a 2771 4106 1 +a 2772 2773 1334 +a 2772 4106 1 +a 2773 2774 1333 +a 2773 4106 1 +a 2774 2775 1332 +a 2774 4106 1 +a 2775 2776 1331 +a 2775 4106 1 +a 2776 2777 1330 +a 2776 4106 1 +a 2777 2778 1329 +a 2777 4106 1 +a 2778 2779 1328 +a 2778 4106 1 +a 2779 2780 1327 +a 2779 4106 1 +a 2780 2781 1326 +a 2780 4106 1 +a 2781 2782 1325 +a 2781 4106 1 +a 2782 2783 1324 +a 2782 4106 1 +a 2783 2784 1323 +a 2783 4106 1 +a 2784 2785 1322 +a 2784 4106 1 +a 2785 2786 1321 +a 2785 4106 1 +a 2786 2787 1320 +a 2786 4106 1 +a 2787 2788 1319 +a 2787 4106 1 +a 2788 2789 1318 +a 2788 4106 1 +a 2789 2790 1317 +a 2789 4106 1 +a 2790 2791 1316 +a 2790 4106 1 +a 2791 2792 1315 +a 2791 4106 1 +a 2792 2793 1314 +a 2792 4106 1 +a 2793 2794 1313 +a 2793 4106 1 +a 2794 2795 1312 +a 2794 4106 1 +a 2795 2796 1311 +a 2795 4106 1 +a 2796 2797 1310 +a 2796 4106 1 +a 2797 2798 1309 +a 2797 4106 1 +a 2798 2799 1308 +a 2798 4106 1 +a 2799 2800 1307 +a 2799 4106 1 +a 2800 2801 1306 +a 2800 4106 1 +a 2801 2802 1305 +a 2801 4106 1 +a 2802 2803 1304 +a 2802 4106 1 +a 2803 2804 1303 +a 2803 4106 1 +a 2804 2805 1302 +a 2804 4106 1 +a 2805 2806 1301 +a 2805 4106 1 +a 2806 2807 1300 +a 2806 4106 1 +a 2807 2808 1299 +a 2807 4106 1 +a 2808 2809 1298 +a 2808 4106 1 +a 2809 2810 1297 +a 2809 4106 1 +a 2810 2811 1296 +a 2810 4106 1 +a 2811 2812 1295 +a 2811 4106 1 +a 2812 2813 1294 +a 2812 4106 1 +a 2813 2814 1293 +a 2813 4106 1 +a 2814 2815 1292 +a 2814 4106 1 +a 2815 2816 1291 +a 2815 4106 1 +a 2816 2817 1290 +a 2816 4106 1 +a 2817 2818 1289 +a 2817 4106 1 +a 2818 2819 1288 +a 2818 4106 1 +a 2819 2820 1287 +a 2819 4106 1 +a 2820 2821 1286 +a 2820 4106 1 +a 2821 2822 1285 +a 2821 4106 1 +a 2822 2823 1284 +a 2822 4106 1 +a 2823 2824 1283 +a 2823 4106 1 +a 2824 2825 1282 +a 2824 4106 1 +a 2825 2826 1281 +a 2825 4106 1 +a 2826 2827 1280 +a 2826 4106 1 +a 2827 2828 1279 +a 2827 4106 1 +a 2828 2829 1278 +a 2828 4106 1 +a 2829 2830 1277 +a 2829 4106 1 +a 2830 2831 1276 +a 2830 4106 1 +a 2831 2832 1275 +a 2831 4106 1 +a 2832 2833 1274 +a 2832 4106 1 +a 2833 2834 1273 +a 2833 4106 1 +a 2834 2835 1272 +a 2834 4106 1 +a 2835 2836 1271 +a 2835 4106 1 +a 2836 2837 1270 +a 2836 4106 1 +a 2837 2838 1269 +a 2837 4106 1 +a 2838 2839 1268 +a 2838 4106 1 +a 2839 2840 1267 +a 2839 4106 1 +a 2840 2841 1266 +a 2840 4106 1 +a 2841 2842 1265 +a 2841 4106 1 +a 2842 2843 1264 +a 2842 4106 1 +a 2843 2844 1263 +a 2843 4106 1 +a 2844 2845 1262 +a 2844 4106 1 +a 2845 2846 1261 +a 2845 4106 1 +a 2846 2847 1260 +a 2846 4106 1 +a 2847 2848 1259 +a 2847 4106 1 +a 2848 2849 1258 +a 2848 4106 1 +a 2849 2850 1257 +a 2849 4106 1 +a 2850 2851 1256 +a 2850 4106 1 +a 2851 2852 1255 +a 2851 4106 1 +a 2852 2853 1254 +a 2852 4106 1 +a 2853 2854 1253 +a 2853 4106 1 +a 2854 2855 1252 +a 2854 4106 1 +a 2855 2856 1251 +a 2855 4106 1 +a 2856 2857 1250 +a 2856 4106 1 +a 2857 2858 1249 +a 2857 4106 1 +a 2858 2859 1248 +a 2858 4106 1 +a 2859 2860 1247 +a 2859 4106 1 +a 2860 2861 1246 +a 2860 4106 1 +a 2861 2862 1245 +a 2861 4106 1 +a 2862 2863 1244 +a 2862 4106 1 +a 2863 2864 1243 +a 2863 4106 1 +a 2864 2865 1242 +a 2864 4106 1 +a 2865 2866 1241 +a 2865 4106 1 +a 2866 2867 1240 +a 2866 4106 1 +a 2867 2868 1239 +a 2867 4106 1 +a 2868 2869 1238 +a 2868 4106 1 +a 2869 2870 1237 +a 2869 4106 1 +a 2870 2871 1236 +a 2870 4106 1 +a 2871 2872 1235 +a 2871 4106 1 +a 2872 2873 1234 +a 2872 4106 1 +a 2873 2874 1233 +a 2873 4106 1 +a 2874 2875 1232 +a 2874 4106 1 +a 2875 2876 1231 +a 2875 4106 1 +a 2876 2877 1230 +a 2876 4106 1 +a 2877 2878 1229 +a 2877 4106 1 +a 2878 2879 1228 +a 2878 4106 1 +a 2879 2880 1227 +a 2879 4106 1 +a 2880 2881 1226 +a 2880 4106 1 +a 2881 2882 1225 +a 2881 4106 1 +a 2882 2883 1224 +a 2882 4106 1 +a 2883 2884 1223 +a 2883 4106 1 +a 2884 2885 1222 +a 2884 4106 1 +a 2885 2886 1221 +a 2885 4106 1 +a 2886 2887 1220 +a 2886 4106 1 +a 2887 2888 1219 +a 2887 4106 1 +a 2888 2889 1218 +a 2888 4106 1 +a 2889 2890 1217 +a 2889 4106 1 +a 2890 2891 1216 +a 2890 4106 1 +a 2891 2892 1215 +a 2891 4106 1 +a 2892 2893 1214 +a 2892 4106 1 +a 2893 2894 1213 +a 2893 4106 1 +a 2894 2895 1212 +a 2894 4106 1 +a 2895 2896 1211 +a 2895 4106 1 +a 2896 2897 1210 +a 2896 4106 1 +a 2897 2898 1209 +a 2897 4106 1 +a 2898 2899 1208 +a 2898 4106 1 +a 2899 2900 1207 +a 2899 4106 1 +a 2900 2901 1206 +a 2900 4106 1 +a 2901 2902 1205 +a 2901 4106 1 +a 2902 2903 1204 +a 2902 4106 1 +a 2903 2904 1203 +a 2903 4106 1 +a 2904 2905 1202 +a 2904 4106 1 +a 2905 2906 1201 +a 2905 4106 1 +a 2906 2907 1200 +a 2906 4106 1 +a 2907 2908 1199 +a 2907 4106 1 +a 2908 2909 1198 +a 2908 4106 1 +a 2909 2910 1197 +a 2909 4106 1 +a 2910 2911 1196 +a 2910 4106 1 +a 2911 2912 1195 +a 2911 4106 1 +a 2912 2913 1194 +a 2912 4106 1 +a 2913 2914 1193 +a 2913 4106 1 +a 2914 2915 1192 +a 2914 4106 1 +a 2915 2916 1191 +a 2915 4106 1 +a 2916 2917 1190 +a 2916 4106 1 +a 2917 2918 1189 +a 2917 4106 1 +a 2918 2919 1188 +a 2918 4106 1 +a 2919 2920 1187 +a 2919 4106 1 +a 2920 2921 1186 +a 2920 4106 1 +a 2921 2922 1185 +a 2921 4106 1 +a 2922 2923 1184 +a 2922 4106 1 +a 2923 2924 1183 +a 2923 4106 1 +a 2924 2925 1182 +a 2924 4106 1 +a 2925 2926 1181 +a 2925 4106 1 +a 2926 2927 1180 +a 2926 4106 1 +a 2927 2928 1179 +a 2927 4106 1 +a 2928 2929 1178 +a 2928 4106 1 +a 2929 2930 1177 +a 2929 4106 1 +a 2930 2931 1176 +a 2930 4106 1 +a 2931 2932 1175 +a 2931 4106 1 +a 2932 2933 1174 +a 2932 4106 1 +a 2933 2934 1173 +a 2933 4106 1 +a 2934 2935 1172 +a 2934 4106 1 +a 2935 2936 1171 +a 2935 4106 1 +a 2936 2937 1170 +a 2936 4106 1 +a 2937 2938 1169 +a 2937 4106 1 +a 2938 2939 1168 +a 2938 4106 1 +a 2939 2940 1167 +a 2939 4106 1 +a 2940 2941 1166 +a 2940 4106 1 +a 2941 2942 1165 +a 2941 4106 1 +a 2942 2943 1164 +a 2942 4106 1 +a 2943 2944 1163 +a 2943 4106 1 +a 2944 2945 1162 +a 2944 4106 1 +a 2945 2946 1161 +a 2945 4106 1 +a 2946 2947 1160 +a 2946 4106 1 +a 2947 2948 1159 +a 2947 4106 1 +a 2948 2949 1158 +a 2948 4106 1 +a 2949 2950 1157 +a 2949 4106 1 +a 2950 2951 1156 +a 2950 4106 1 +a 2951 2952 1155 +a 2951 4106 1 +a 2952 2953 1154 +a 2952 4106 1 +a 2953 2954 1153 +a 2953 4106 1 +a 2954 2955 1152 +a 2954 4106 1 +a 2955 2956 1151 +a 2955 4106 1 +a 2956 2957 1150 +a 2956 4106 1 +a 2957 2958 1149 +a 2957 4106 1 +a 2958 2959 1148 +a 2958 4106 1 +a 2959 2960 1147 +a 2959 4106 1 +a 2960 2961 1146 +a 2960 4106 1 +a 2961 2962 1145 +a 2961 4106 1 +a 2962 2963 1144 +a 2962 4106 1 +a 2963 2964 1143 +a 2963 4106 1 +a 2964 2965 1142 +a 2964 4106 1 +a 2965 2966 1141 +a 2965 4106 1 +a 2966 2967 1140 +a 2966 4106 1 +a 2967 2968 1139 +a 2967 4106 1 +a 2968 2969 1138 +a 2968 4106 1 +a 2969 2970 1137 +a 2969 4106 1 +a 2970 2971 1136 +a 2970 4106 1 +a 2971 2972 1135 +a 2971 4106 1 +a 2972 2973 1134 +a 2972 4106 1 +a 2973 2974 1133 +a 2973 4106 1 +a 2974 2975 1132 +a 2974 4106 1 +a 2975 2976 1131 +a 2975 4106 1 +a 2976 2977 1130 +a 2976 4106 1 +a 2977 2978 1129 +a 2977 4106 1 +a 2978 2979 1128 +a 2978 4106 1 +a 2979 2980 1127 +a 2979 4106 1 +a 2980 2981 1126 +a 2980 4106 1 +a 2981 2982 1125 +a 2981 4106 1 +a 2982 2983 1124 +a 2982 4106 1 +a 2983 2984 1123 +a 2983 4106 1 +a 2984 2985 1122 +a 2984 4106 1 +a 2985 2986 1121 +a 2985 4106 1 +a 2986 2987 1120 +a 2986 4106 1 +a 2987 2988 1119 +a 2987 4106 1 +a 2988 2989 1118 +a 2988 4106 1 +a 2989 2990 1117 +a 2989 4106 1 +a 2990 2991 1116 +a 2990 4106 1 +a 2991 2992 1115 +a 2991 4106 1 +a 2992 2993 1114 +a 2992 4106 1 +a 2993 2994 1113 +a 2993 4106 1 +a 2994 2995 1112 +a 2994 4106 1 +a 2995 2996 1111 +a 2995 4106 1 +a 2996 2997 1110 +a 2996 4106 1 +a 2997 2998 1109 +a 2997 4106 1 +a 2998 2999 1108 +a 2998 4106 1 +a 2999 3000 1107 +a 2999 4106 1 +a 3000 3001 1106 +a 3000 4106 1 +a 3001 3002 1105 +a 3001 4106 1 +a 3002 3003 1104 +a 3002 4106 1 +a 3003 3004 1103 +a 3003 4106 1 +a 3004 3005 1102 +a 3004 4106 1 +a 3005 3006 1101 +a 3005 4106 1 +a 3006 3007 1100 +a 3006 4106 1 +a 3007 3008 1099 +a 3007 4106 1 +a 3008 3009 1098 +a 3008 4106 1 +a 3009 3010 1097 +a 3009 4106 1 +a 3010 3011 1096 +a 3010 4106 1 +a 3011 3012 1095 +a 3011 4106 1 +a 3012 3013 1094 +a 3012 4106 1 +a 3013 3014 1093 +a 3013 4106 1 +a 3014 3015 1092 +a 3014 4106 1 +a 3015 3016 1091 +a 3015 4106 1 +a 3016 3017 1090 +a 3016 4106 1 +a 3017 3018 1089 +a 3017 4106 1 +a 3018 3019 1088 +a 3018 4106 1 +a 3019 3020 1087 +a 3019 4106 1 +a 3020 3021 1086 +a 3020 4106 1 +a 3021 3022 1085 +a 3021 4106 1 +a 3022 3023 1084 +a 3022 4106 1 +a 3023 3024 1083 +a 3023 4106 1 +a 3024 3025 1082 +a 3024 4106 1 +a 3025 3026 1081 +a 3025 4106 1 +a 3026 3027 1080 +a 3026 4106 1 +a 3027 3028 1079 +a 3027 4106 1 +a 3028 3029 1078 +a 3028 4106 1 +a 3029 3030 1077 +a 3029 4106 1 +a 3030 3031 1076 +a 3030 4106 1 +a 3031 3032 1075 +a 3031 4106 1 +a 3032 3033 1074 +a 3032 4106 1 +a 3033 3034 1073 +a 3033 4106 1 +a 3034 3035 1072 +a 3034 4106 1 +a 3035 3036 1071 +a 3035 4106 1 +a 3036 3037 1070 +a 3036 4106 1 +a 3037 3038 1069 +a 3037 4106 1 +a 3038 3039 1068 +a 3038 4106 1 +a 3039 3040 1067 +a 3039 4106 1 +a 3040 3041 1066 +a 3040 4106 1 +a 3041 3042 1065 +a 3041 4106 1 +a 3042 3043 1064 +a 3042 4106 1 +a 3043 3044 1063 +a 3043 4106 1 +a 3044 3045 1062 +a 3044 4106 1 +a 3045 3046 1061 +a 3045 4106 1 +a 3046 3047 1060 +a 3046 4106 1 +a 3047 3048 1059 +a 3047 4106 1 +a 3048 3049 1058 +a 3048 4106 1 +a 3049 3050 1057 +a 3049 4106 1 +a 3050 3051 1056 +a 3050 4106 1 +a 3051 3052 1055 +a 3051 4106 1 +a 3052 3053 1054 +a 3052 4106 1 +a 3053 3054 1053 +a 3053 4106 1 +a 3054 3055 1052 +a 3054 4106 1 +a 3055 3056 1051 +a 3055 4106 1 +a 3056 3057 1050 +a 3056 4106 1 +a 3057 3058 1049 +a 3057 4106 1 +a 3058 3059 1048 +a 3058 4106 1 +a 3059 3060 1047 +a 3059 4106 1 +a 3060 3061 1046 +a 3060 4106 1 +a 3061 3062 1045 +a 3061 4106 1 +a 3062 3063 1044 +a 3062 4106 1 +a 3063 3064 1043 +a 3063 4106 1 +a 3064 3065 1042 +a 3064 4106 1 +a 3065 3066 1041 +a 3065 4106 1 +a 3066 3067 1040 +a 3066 4106 1 +a 3067 3068 1039 +a 3067 4106 1 +a 3068 3069 1038 +a 3068 4106 1 +a 3069 3070 1037 +a 3069 4106 1 +a 3070 3071 1036 +a 3070 4106 1 +a 3071 3072 1035 +a 3071 4106 1 +a 3072 3073 1034 +a 3072 4106 1 +a 3073 3074 1033 +a 3073 4106 1 +a 3074 3075 1032 +a 3074 4106 1 +a 3075 3076 1031 +a 3075 4106 1 +a 3076 3077 1030 +a 3076 4106 1 +a 3077 3078 1029 +a 3077 4106 1 +a 3078 3079 1028 +a 3078 4106 1 +a 3079 3080 1027 +a 3079 4106 1 +a 3080 3081 1026 +a 3080 4106 1 +a 3081 3082 1025 +a 3081 4106 1 +a 3082 3083 1024 +a 3082 4106 1 +a 3083 3084 1023 +a 3083 4106 1 +a 3084 3085 1022 +a 3084 4106 1 +a 3085 3086 1021 +a 3085 4106 1 +a 3086 3087 1020 +a 3086 4106 1 +a 3087 3088 1019 +a 3087 4106 1 +a 3088 3089 1018 +a 3088 4106 1 +a 3089 3090 1017 +a 3089 4106 1 +a 3090 3091 1016 +a 3090 4106 1 +a 3091 3092 1015 +a 3091 4106 1 +a 3092 3093 1014 +a 3092 4106 1 +a 3093 3094 1013 +a 3093 4106 1 +a 3094 3095 1012 +a 3094 4106 1 +a 3095 3096 1011 +a 3095 4106 1 +a 3096 3097 1010 +a 3096 4106 1 +a 3097 3098 1009 +a 3097 4106 1 +a 3098 3099 1008 +a 3098 4106 1 +a 3099 3100 1007 +a 3099 4106 1 +a 3100 3101 1006 +a 3100 4106 1 +a 3101 3102 1005 +a 3101 4106 1 +a 3102 3103 1004 +a 3102 4106 1 +a 3103 3104 1003 +a 3103 4106 1 +a 3104 3105 1002 +a 3104 4106 1 +a 3105 3106 1001 +a 3105 4106 1 +a 3106 3107 1000 +a 3106 4106 1 +a 3107 3108 999 +a 3107 4106 1 +a 3108 3109 998 +a 3108 4106 1 +a 3109 3110 997 +a 3109 4106 1 +a 3110 3111 996 +a 3110 4106 1 +a 3111 3112 995 +a 3111 4106 1 +a 3112 3113 994 +a 3112 4106 1 +a 3113 3114 993 +a 3113 4106 1 +a 3114 3115 992 +a 3114 4106 1 +a 3115 3116 991 +a 3115 4106 1 +a 3116 3117 990 +a 3116 4106 1 +a 3117 3118 989 +a 3117 4106 1 +a 3118 3119 988 +a 3118 4106 1 +a 3119 3120 987 +a 3119 4106 1 +a 3120 3121 986 +a 3120 4106 1 +a 3121 3122 985 +a 3121 4106 1 +a 3122 3123 984 +a 3122 4106 1 +a 3123 3124 983 +a 3123 4106 1 +a 3124 3125 982 +a 3124 4106 1 +a 3125 3126 981 +a 3125 4106 1 +a 3126 3127 980 +a 3126 4106 1 +a 3127 3128 979 +a 3127 4106 1 +a 3128 3129 978 +a 3128 4106 1 +a 3129 3130 977 +a 3129 4106 1 +a 3130 3131 976 +a 3130 4106 1 +a 3131 3132 975 +a 3131 4106 1 +a 3132 3133 974 +a 3132 4106 1 +a 3133 3134 973 +a 3133 4106 1 +a 3134 3135 972 +a 3134 4106 1 +a 3135 3136 971 +a 3135 4106 1 +a 3136 3137 970 +a 3136 4106 1 +a 3137 3138 969 +a 3137 4106 1 +a 3138 3139 968 +a 3138 4106 1 +a 3139 3140 967 +a 3139 4106 1 +a 3140 3141 966 +a 3140 4106 1 +a 3141 3142 965 +a 3141 4106 1 +a 3142 3143 964 +a 3142 4106 1 +a 3143 3144 963 +a 3143 4106 1 +a 3144 3145 962 +a 3144 4106 1 +a 3145 3146 961 +a 3145 4106 1 +a 3146 3147 960 +a 3146 4106 1 +a 3147 3148 959 +a 3147 4106 1 +a 3148 3149 958 +a 3148 4106 1 +a 3149 3150 957 +a 3149 4106 1 +a 3150 3151 956 +a 3150 4106 1 +a 3151 3152 955 +a 3151 4106 1 +a 3152 3153 954 +a 3152 4106 1 +a 3153 3154 953 +a 3153 4106 1 +a 3154 3155 952 +a 3154 4106 1 +a 3155 3156 951 +a 3155 4106 1 +a 3156 3157 950 +a 3156 4106 1 +a 3157 3158 949 +a 3157 4106 1 +a 3158 3159 948 +a 3158 4106 1 +a 3159 3160 947 +a 3159 4106 1 +a 3160 3161 946 +a 3160 4106 1 +a 3161 3162 945 +a 3161 4106 1 +a 3162 3163 944 +a 3162 4106 1 +a 3163 3164 943 +a 3163 4106 1 +a 3164 3165 942 +a 3164 4106 1 +a 3165 3166 941 +a 3165 4106 1 +a 3166 3167 940 +a 3166 4106 1 +a 3167 3168 939 +a 3167 4106 1 +a 3168 3169 938 +a 3168 4106 1 +a 3169 3170 937 +a 3169 4106 1 +a 3170 3171 936 +a 3170 4106 1 +a 3171 3172 935 +a 3171 4106 1 +a 3172 3173 934 +a 3172 4106 1 +a 3173 3174 933 +a 3173 4106 1 +a 3174 3175 932 +a 3174 4106 1 +a 3175 3176 931 +a 3175 4106 1 +a 3176 3177 930 +a 3176 4106 1 +a 3177 3178 929 +a 3177 4106 1 +a 3178 3179 928 +a 3178 4106 1 +a 3179 3180 927 +a 3179 4106 1 +a 3180 3181 926 +a 3180 4106 1 +a 3181 3182 925 +a 3181 4106 1 +a 3182 3183 924 +a 3182 4106 1 +a 3183 3184 923 +a 3183 4106 1 +a 3184 3185 922 +a 3184 4106 1 +a 3185 3186 921 +a 3185 4106 1 +a 3186 3187 920 +a 3186 4106 1 +a 3187 3188 919 +a 3187 4106 1 +a 3188 3189 918 +a 3188 4106 1 +a 3189 3190 917 +a 3189 4106 1 +a 3190 3191 916 +a 3190 4106 1 +a 3191 3192 915 +a 3191 4106 1 +a 3192 3193 914 +a 3192 4106 1 +a 3193 3194 913 +a 3193 4106 1 +a 3194 3195 912 +a 3194 4106 1 +a 3195 3196 911 +a 3195 4106 1 +a 3196 3197 910 +a 3196 4106 1 +a 3197 3198 909 +a 3197 4106 1 +a 3198 3199 908 +a 3198 4106 1 +a 3199 3200 907 +a 3199 4106 1 +a 3200 3201 906 +a 3200 4106 1 +a 3201 3202 905 +a 3201 4106 1 +a 3202 3203 904 +a 3202 4106 1 +a 3203 3204 903 +a 3203 4106 1 +a 3204 3205 902 +a 3204 4106 1 +a 3205 3206 901 +a 3205 4106 1 +a 3206 3207 900 +a 3206 4106 1 +a 3207 3208 899 +a 3207 4106 1 +a 3208 3209 898 +a 3208 4106 1 +a 3209 3210 897 +a 3209 4106 1 +a 3210 3211 896 +a 3210 4106 1 +a 3211 3212 895 +a 3211 4106 1 +a 3212 3213 894 +a 3212 4106 1 +a 3213 3214 893 +a 3213 4106 1 +a 3214 3215 892 +a 3214 4106 1 +a 3215 3216 891 +a 3215 4106 1 +a 3216 3217 890 +a 3216 4106 1 +a 3217 3218 889 +a 3217 4106 1 +a 3218 3219 888 +a 3218 4106 1 +a 3219 3220 887 +a 3219 4106 1 +a 3220 3221 886 +a 3220 4106 1 +a 3221 3222 885 +a 3221 4106 1 +a 3222 3223 884 +a 3222 4106 1 +a 3223 3224 883 +a 3223 4106 1 +a 3224 3225 882 +a 3224 4106 1 +a 3225 3226 881 +a 3225 4106 1 +a 3226 3227 880 +a 3226 4106 1 +a 3227 3228 879 +a 3227 4106 1 +a 3228 3229 878 +a 3228 4106 1 +a 3229 3230 877 +a 3229 4106 1 +a 3230 3231 876 +a 3230 4106 1 +a 3231 3232 875 +a 3231 4106 1 +a 3232 3233 874 +a 3232 4106 1 +a 3233 3234 873 +a 3233 4106 1 +a 3234 3235 872 +a 3234 4106 1 +a 3235 3236 871 +a 3235 4106 1 +a 3236 3237 870 +a 3236 4106 1 +a 3237 3238 869 +a 3237 4106 1 +a 3238 3239 868 +a 3238 4106 1 +a 3239 3240 867 +a 3239 4106 1 +a 3240 3241 866 +a 3240 4106 1 +a 3241 3242 865 +a 3241 4106 1 +a 3242 3243 864 +a 3242 4106 1 +a 3243 3244 863 +a 3243 4106 1 +a 3244 3245 862 +a 3244 4106 1 +a 3245 3246 861 +a 3245 4106 1 +a 3246 3247 860 +a 3246 4106 1 +a 3247 3248 859 +a 3247 4106 1 +a 3248 3249 858 +a 3248 4106 1 +a 3249 3250 857 +a 3249 4106 1 +a 3250 3251 856 +a 3250 4106 1 +a 3251 3252 855 +a 3251 4106 1 +a 3252 3253 854 +a 3252 4106 1 +a 3253 3254 853 +a 3253 4106 1 +a 3254 3255 852 +a 3254 4106 1 +a 3255 3256 851 +a 3255 4106 1 +a 3256 3257 850 +a 3256 4106 1 +a 3257 3258 849 +a 3257 4106 1 +a 3258 3259 848 +a 3258 4106 1 +a 3259 3260 847 +a 3259 4106 1 +a 3260 3261 846 +a 3260 4106 1 +a 3261 3262 845 +a 3261 4106 1 +a 3262 3263 844 +a 3262 4106 1 +a 3263 3264 843 +a 3263 4106 1 +a 3264 3265 842 +a 3264 4106 1 +a 3265 3266 841 +a 3265 4106 1 +a 3266 3267 840 +a 3266 4106 1 +a 3267 3268 839 +a 3267 4106 1 +a 3268 3269 838 +a 3268 4106 1 +a 3269 3270 837 +a 3269 4106 1 +a 3270 3271 836 +a 3270 4106 1 +a 3271 3272 835 +a 3271 4106 1 +a 3272 3273 834 +a 3272 4106 1 +a 3273 3274 833 +a 3273 4106 1 +a 3274 3275 832 +a 3274 4106 1 +a 3275 3276 831 +a 3275 4106 1 +a 3276 3277 830 +a 3276 4106 1 +a 3277 3278 829 +a 3277 4106 1 +a 3278 3279 828 +a 3278 4106 1 +a 3279 3280 827 +a 3279 4106 1 +a 3280 3281 826 +a 3280 4106 1 +a 3281 3282 825 +a 3281 4106 1 +a 3282 3283 824 +a 3282 4106 1 +a 3283 3284 823 +a 3283 4106 1 +a 3284 3285 822 +a 3284 4106 1 +a 3285 3286 821 +a 3285 4106 1 +a 3286 3287 820 +a 3286 4106 1 +a 3287 3288 819 +a 3287 4106 1 +a 3288 3289 818 +a 3288 4106 1 +a 3289 3290 817 +a 3289 4106 1 +a 3290 3291 816 +a 3290 4106 1 +a 3291 3292 815 +a 3291 4106 1 +a 3292 3293 814 +a 3292 4106 1 +a 3293 3294 813 +a 3293 4106 1 +a 3294 3295 812 +a 3294 4106 1 +a 3295 3296 811 +a 3295 4106 1 +a 3296 3297 810 +a 3296 4106 1 +a 3297 3298 809 +a 3297 4106 1 +a 3298 3299 808 +a 3298 4106 1 +a 3299 3300 807 +a 3299 4106 1 +a 3300 3301 806 +a 3300 4106 1 +a 3301 3302 805 +a 3301 4106 1 +a 3302 3303 804 +a 3302 4106 1 +a 3303 3304 803 +a 3303 4106 1 +a 3304 3305 802 +a 3304 4106 1 +a 3305 3306 801 +a 3305 4106 1 +a 3306 3307 800 +a 3306 4106 1 +a 3307 3308 799 +a 3307 4106 1 +a 3308 3309 798 +a 3308 4106 1 +a 3309 3310 797 +a 3309 4106 1 +a 3310 3311 796 +a 3310 4106 1 +a 3311 3312 795 +a 3311 4106 1 +a 3312 3313 794 +a 3312 4106 1 +a 3313 3314 793 +a 3313 4106 1 +a 3314 3315 792 +a 3314 4106 1 +a 3315 3316 791 +a 3315 4106 1 +a 3316 3317 790 +a 3316 4106 1 +a 3317 3318 789 +a 3317 4106 1 +a 3318 3319 788 +a 3318 4106 1 +a 3319 3320 787 +a 3319 4106 1 +a 3320 3321 786 +a 3320 4106 1 +a 3321 3322 785 +a 3321 4106 1 +a 3322 3323 784 +a 3322 4106 1 +a 3323 3324 783 +a 3323 4106 1 +a 3324 3325 782 +a 3324 4106 1 +a 3325 3326 781 +a 3325 4106 1 +a 3326 3327 780 +a 3326 4106 1 +a 3327 3328 779 +a 3327 4106 1 +a 3328 3329 778 +a 3328 4106 1 +a 3329 3330 777 +a 3329 4106 1 +a 3330 3331 776 +a 3330 4106 1 +a 3331 3332 775 +a 3331 4106 1 +a 3332 3333 774 +a 3332 4106 1 +a 3333 3334 773 +a 3333 4106 1 +a 3334 3335 772 +a 3334 4106 1 +a 3335 3336 771 +a 3335 4106 1 +a 3336 3337 770 +a 3336 4106 1 +a 3337 3338 769 +a 3337 4106 1 +a 3338 3339 768 +a 3338 4106 1 +a 3339 3340 767 +a 3339 4106 1 +a 3340 3341 766 +a 3340 4106 1 +a 3341 3342 765 +a 3341 4106 1 +a 3342 3343 764 +a 3342 4106 1 +a 3343 3344 763 +a 3343 4106 1 +a 3344 3345 762 +a 3344 4106 1 +a 3345 3346 761 +a 3345 4106 1 +a 3346 3347 760 +a 3346 4106 1 +a 3347 3348 759 +a 3347 4106 1 +a 3348 3349 758 +a 3348 4106 1 +a 3349 3350 757 +a 3349 4106 1 +a 3350 3351 756 +a 3350 4106 1 +a 3351 3352 755 +a 3351 4106 1 +a 3352 3353 754 +a 3352 4106 1 +a 3353 3354 753 +a 3353 4106 1 +a 3354 3355 752 +a 3354 4106 1 +a 3355 3356 751 +a 3355 4106 1 +a 3356 3357 750 +a 3356 4106 1 +a 3357 3358 749 +a 3357 4106 1 +a 3358 3359 748 +a 3358 4106 1 +a 3359 3360 747 +a 3359 4106 1 +a 3360 3361 746 +a 3360 4106 1 +a 3361 3362 745 +a 3361 4106 1 +a 3362 3363 744 +a 3362 4106 1 +a 3363 3364 743 +a 3363 4106 1 +a 3364 3365 742 +a 3364 4106 1 +a 3365 3366 741 +a 3365 4106 1 +a 3366 3367 740 +a 3366 4106 1 +a 3367 3368 739 +a 3367 4106 1 +a 3368 3369 738 +a 3368 4106 1 +a 3369 3370 737 +a 3369 4106 1 +a 3370 3371 736 +a 3370 4106 1 +a 3371 3372 735 +a 3371 4106 1 +a 3372 3373 734 +a 3372 4106 1 +a 3373 3374 733 +a 3373 4106 1 +a 3374 3375 732 +a 3374 4106 1 +a 3375 3376 731 +a 3375 4106 1 +a 3376 3377 730 +a 3376 4106 1 +a 3377 3378 729 +a 3377 4106 1 +a 3378 3379 728 +a 3378 4106 1 +a 3379 3380 727 +a 3379 4106 1 +a 3380 3381 726 +a 3380 4106 1 +a 3381 3382 725 +a 3381 4106 1 +a 3382 3383 724 +a 3382 4106 1 +a 3383 3384 723 +a 3383 4106 1 +a 3384 3385 722 +a 3384 4106 1 +a 3385 3386 721 +a 3385 4106 1 +a 3386 3387 720 +a 3386 4106 1 +a 3387 3388 719 +a 3387 4106 1 +a 3388 3389 718 +a 3388 4106 1 +a 3389 3390 717 +a 3389 4106 1 +a 3390 3391 716 +a 3390 4106 1 +a 3391 3392 715 +a 3391 4106 1 +a 3392 3393 714 +a 3392 4106 1 +a 3393 3394 713 +a 3393 4106 1 +a 3394 3395 712 +a 3394 4106 1 +a 3395 3396 711 +a 3395 4106 1 +a 3396 3397 710 +a 3396 4106 1 +a 3397 3398 709 +a 3397 4106 1 +a 3398 3399 708 +a 3398 4106 1 +a 3399 3400 707 +a 3399 4106 1 +a 3400 3401 706 +a 3400 4106 1 +a 3401 3402 705 +a 3401 4106 1 +a 3402 3403 704 +a 3402 4106 1 +a 3403 3404 703 +a 3403 4106 1 +a 3404 3405 702 +a 3404 4106 1 +a 3405 3406 701 +a 3405 4106 1 +a 3406 3407 700 +a 3406 4106 1 +a 3407 3408 699 +a 3407 4106 1 +a 3408 3409 698 +a 3408 4106 1 +a 3409 3410 697 +a 3409 4106 1 +a 3410 3411 696 +a 3410 4106 1 +a 3411 3412 695 +a 3411 4106 1 +a 3412 3413 694 +a 3412 4106 1 +a 3413 3414 693 +a 3413 4106 1 +a 3414 3415 692 +a 3414 4106 1 +a 3415 3416 691 +a 3415 4106 1 +a 3416 3417 690 +a 3416 4106 1 +a 3417 3418 689 +a 3417 4106 1 +a 3418 3419 688 +a 3418 4106 1 +a 3419 3420 687 +a 3419 4106 1 +a 3420 3421 686 +a 3420 4106 1 +a 3421 3422 685 +a 3421 4106 1 +a 3422 3423 684 +a 3422 4106 1 +a 3423 3424 683 +a 3423 4106 1 +a 3424 3425 682 +a 3424 4106 1 +a 3425 3426 681 +a 3425 4106 1 +a 3426 3427 680 +a 3426 4106 1 +a 3427 3428 679 +a 3427 4106 1 +a 3428 3429 678 +a 3428 4106 1 +a 3429 3430 677 +a 3429 4106 1 +a 3430 3431 676 +a 3430 4106 1 +a 3431 3432 675 +a 3431 4106 1 +a 3432 3433 674 +a 3432 4106 1 +a 3433 3434 673 +a 3433 4106 1 +a 3434 3435 672 +a 3434 4106 1 +a 3435 3436 671 +a 3435 4106 1 +a 3436 3437 670 +a 3436 4106 1 +a 3437 3438 669 +a 3437 4106 1 +a 3438 3439 668 +a 3438 4106 1 +a 3439 3440 667 +a 3439 4106 1 +a 3440 3441 666 +a 3440 4106 1 +a 3441 3442 665 +a 3441 4106 1 +a 3442 3443 664 +a 3442 4106 1 +a 3443 3444 663 +a 3443 4106 1 +a 3444 3445 662 +a 3444 4106 1 +a 3445 3446 661 +a 3445 4106 1 +a 3446 3447 660 +a 3446 4106 1 +a 3447 3448 659 +a 3447 4106 1 +a 3448 3449 658 +a 3448 4106 1 +a 3449 3450 657 +a 3449 4106 1 +a 3450 3451 656 +a 3450 4106 1 +a 3451 3452 655 +a 3451 4106 1 +a 3452 3453 654 +a 3452 4106 1 +a 3453 3454 653 +a 3453 4106 1 +a 3454 3455 652 +a 3454 4106 1 +a 3455 3456 651 +a 3455 4106 1 +a 3456 3457 650 +a 3456 4106 1 +a 3457 3458 649 +a 3457 4106 1 +a 3458 3459 648 +a 3458 4106 1 +a 3459 3460 647 +a 3459 4106 1 +a 3460 3461 646 +a 3460 4106 1 +a 3461 3462 645 +a 3461 4106 1 +a 3462 3463 644 +a 3462 4106 1 +a 3463 3464 643 +a 3463 4106 1 +a 3464 3465 642 +a 3464 4106 1 +a 3465 3466 641 +a 3465 4106 1 +a 3466 3467 640 +a 3466 4106 1 +a 3467 3468 639 +a 3467 4106 1 +a 3468 3469 638 +a 3468 4106 1 +a 3469 3470 637 +a 3469 4106 1 +a 3470 3471 636 +a 3470 4106 1 +a 3471 3472 635 +a 3471 4106 1 +a 3472 3473 634 +a 3472 4106 1 +a 3473 3474 633 +a 3473 4106 1 +a 3474 3475 632 +a 3474 4106 1 +a 3475 3476 631 +a 3475 4106 1 +a 3476 3477 630 +a 3476 4106 1 +a 3477 3478 629 +a 3477 4106 1 +a 3478 3479 628 +a 3478 4106 1 +a 3479 3480 627 +a 3479 4106 1 +a 3480 3481 626 +a 3480 4106 1 +a 3481 3482 625 +a 3481 4106 1 +a 3482 3483 624 +a 3482 4106 1 +a 3483 3484 623 +a 3483 4106 1 +a 3484 3485 622 +a 3484 4106 1 +a 3485 3486 621 +a 3485 4106 1 +a 3486 3487 620 +a 3486 4106 1 +a 3487 3488 619 +a 3487 4106 1 +a 3488 3489 618 +a 3488 4106 1 +a 3489 3490 617 +a 3489 4106 1 +a 3490 3491 616 +a 3490 4106 1 +a 3491 3492 615 +a 3491 4106 1 +a 3492 3493 614 +a 3492 4106 1 +a 3493 3494 613 +a 3493 4106 1 +a 3494 3495 612 +a 3494 4106 1 +a 3495 3496 611 +a 3495 4106 1 +a 3496 3497 610 +a 3496 4106 1 +a 3497 3498 609 +a 3497 4106 1 +a 3498 3499 608 +a 3498 4106 1 +a 3499 3500 607 +a 3499 4106 1 +a 3500 3501 606 +a 3500 4106 1 +a 3501 3502 605 +a 3501 4106 1 +a 3502 3503 604 +a 3502 4106 1 +a 3503 3504 603 +a 3503 4106 1 +a 3504 3505 602 +a 3504 4106 1 +a 3505 3506 601 +a 3505 4106 1 +a 3506 3507 600 +a 3506 4106 1 +a 3507 3508 599 +a 3507 4106 1 +a 3508 3509 598 +a 3508 4106 1 +a 3509 3510 597 +a 3509 4106 1 +a 3510 3511 596 +a 3510 4106 1 +a 3511 3512 595 +a 3511 4106 1 +a 3512 3513 594 +a 3512 4106 1 +a 3513 3514 593 +a 3513 4106 1 +a 3514 3515 592 +a 3514 4106 1 +a 3515 3516 591 +a 3515 4106 1 +a 3516 3517 590 +a 3516 4106 1 +a 3517 3518 589 +a 3517 4106 1 +a 3518 3519 588 +a 3518 4106 1 +a 3519 3520 587 +a 3519 4106 1 +a 3520 3521 586 +a 3520 4106 1 +a 3521 3522 585 +a 3521 4106 1 +a 3522 3523 584 +a 3522 4106 1 +a 3523 3524 583 +a 3523 4106 1 +a 3524 3525 582 +a 3524 4106 1 +a 3525 3526 581 +a 3525 4106 1 +a 3526 3527 580 +a 3526 4106 1 +a 3527 3528 579 +a 3527 4106 1 +a 3528 3529 578 +a 3528 4106 1 +a 3529 3530 577 +a 3529 4106 1 +a 3530 3531 576 +a 3530 4106 1 +a 3531 3532 575 +a 3531 4106 1 +a 3532 3533 574 +a 3532 4106 1 +a 3533 3534 573 +a 3533 4106 1 +a 3534 3535 572 +a 3534 4106 1 +a 3535 3536 571 +a 3535 4106 1 +a 3536 3537 570 +a 3536 4106 1 +a 3537 3538 569 +a 3537 4106 1 +a 3538 3539 568 +a 3538 4106 1 +a 3539 3540 567 +a 3539 4106 1 +a 3540 3541 566 +a 3540 4106 1 +a 3541 3542 565 +a 3541 4106 1 +a 3542 3543 564 +a 3542 4106 1 +a 3543 3544 563 +a 3543 4106 1 +a 3544 3545 562 +a 3544 4106 1 +a 3545 3546 561 +a 3545 4106 1 +a 3546 3547 560 +a 3546 4106 1 +a 3547 3548 559 +a 3547 4106 1 +a 3548 3549 558 +a 3548 4106 1 +a 3549 3550 557 +a 3549 4106 1 +a 3550 3551 556 +a 3550 4106 1 +a 3551 3552 555 +a 3551 4106 1 +a 3552 3553 554 +a 3552 4106 1 +a 3553 3554 553 +a 3553 4106 1 +a 3554 3555 552 +a 3554 4106 1 +a 3555 3556 551 +a 3555 4106 1 +a 3556 3557 550 +a 3556 4106 1 +a 3557 3558 549 +a 3557 4106 1 +a 3558 3559 548 +a 3558 4106 1 +a 3559 3560 547 +a 3559 4106 1 +a 3560 3561 546 +a 3560 4106 1 +a 3561 3562 545 +a 3561 4106 1 +a 3562 3563 544 +a 3562 4106 1 +a 3563 3564 543 +a 3563 4106 1 +a 3564 3565 542 +a 3564 4106 1 +a 3565 3566 541 +a 3565 4106 1 +a 3566 3567 540 +a 3566 4106 1 +a 3567 3568 539 +a 3567 4106 1 +a 3568 3569 538 +a 3568 4106 1 +a 3569 3570 537 +a 3569 4106 1 +a 3570 3571 536 +a 3570 4106 1 +a 3571 3572 535 +a 3571 4106 1 +a 3572 3573 534 +a 3572 4106 1 +a 3573 3574 533 +a 3573 4106 1 +a 3574 3575 532 +a 3574 4106 1 +a 3575 3576 531 +a 3575 4106 1 +a 3576 3577 530 +a 3576 4106 1 +a 3577 3578 529 +a 3577 4106 1 +a 3578 3579 528 +a 3578 4106 1 +a 3579 3580 527 +a 3579 4106 1 +a 3580 3581 526 +a 3580 4106 1 +a 3581 3582 525 +a 3581 4106 1 +a 3582 3583 524 +a 3582 4106 1 +a 3583 3584 523 +a 3583 4106 1 +a 3584 3585 522 +a 3584 4106 1 +a 3585 3586 521 +a 3585 4106 1 +a 3586 3587 520 +a 3586 4106 1 +a 3587 3588 519 +a 3587 4106 1 +a 3588 3589 518 +a 3588 4106 1 +a 3589 3590 517 +a 3589 4106 1 +a 3590 3591 516 +a 3590 4106 1 +a 3591 3592 515 +a 3591 4106 1 +a 3592 3593 514 +a 3592 4106 1 +a 3593 3594 513 +a 3593 4106 1 +a 3594 3595 512 +a 3594 4106 1 +a 3595 3596 511 +a 3595 4106 1 +a 3596 3597 510 +a 3596 4106 1 +a 3597 3598 509 +a 3597 4106 1 +a 3598 3599 508 +a 3598 4106 1 +a 3599 3600 507 +a 3599 4106 1 +a 3600 3601 506 +a 3600 4106 1 +a 3601 3602 505 +a 3601 4106 1 +a 3602 3603 504 +a 3602 4106 1 +a 3603 3604 503 +a 3603 4106 1 +a 3604 3605 502 +a 3604 4106 1 +a 3605 3606 501 +a 3605 4106 1 +a 3606 3607 500 +a 3606 4106 1 +a 3607 3608 499 +a 3607 4106 1 +a 3608 3609 498 +a 3608 4106 1 +a 3609 3610 497 +a 3609 4106 1 +a 3610 3611 496 +a 3610 4106 1 +a 3611 3612 495 +a 3611 4106 1 +a 3612 3613 494 +a 3612 4106 1 +a 3613 3614 493 +a 3613 4106 1 +a 3614 3615 492 +a 3614 4106 1 +a 3615 3616 491 +a 3615 4106 1 +a 3616 3617 490 +a 3616 4106 1 +a 3617 3618 489 +a 3617 4106 1 +a 3618 3619 488 +a 3618 4106 1 +a 3619 3620 487 +a 3619 4106 1 +a 3620 3621 486 +a 3620 4106 1 +a 3621 3622 485 +a 3621 4106 1 +a 3622 3623 484 +a 3622 4106 1 +a 3623 3624 483 +a 3623 4106 1 +a 3624 3625 482 +a 3624 4106 1 +a 3625 3626 481 +a 3625 4106 1 +a 3626 3627 480 +a 3626 4106 1 +a 3627 3628 479 +a 3627 4106 1 +a 3628 3629 478 +a 3628 4106 1 +a 3629 3630 477 +a 3629 4106 1 +a 3630 3631 476 +a 3630 4106 1 +a 3631 3632 475 +a 3631 4106 1 +a 3632 3633 474 +a 3632 4106 1 +a 3633 3634 473 +a 3633 4106 1 +a 3634 3635 472 +a 3634 4106 1 +a 3635 3636 471 +a 3635 4106 1 +a 3636 3637 470 +a 3636 4106 1 +a 3637 3638 469 +a 3637 4106 1 +a 3638 3639 468 +a 3638 4106 1 +a 3639 3640 467 +a 3639 4106 1 +a 3640 3641 466 +a 3640 4106 1 +a 3641 3642 465 +a 3641 4106 1 +a 3642 3643 464 +a 3642 4106 1 +a 3643 3644 463 +a 3643 4106 1 +a 3644 3645 462 +a 3644 4106 1 +a 3645 3646 461 +a 3645 4106 1 +a 3646 3647 460 +a 3646 4106 1 +a 3647 3648 459 +a 3647 4106 1 +a 3648 3649 458 +a 3648 4106 1 +a 3649 3650 457 +a 3649 4106 1 +a 3650 3651 456 +a 3650 4106 1 +a 3651 3652 455 +a 3651 4106 1 +a 3652 3653 454 +a 3652 4106 1 +a 3653 3654 453 +a 3653 4106 1 +a 3654 3655 452 +a 3654 4106 1 +a 3655 3656 451 +a 3655 4106 1 +a 3656 3657 450 +a 3656 4106 1 +a 3657 3658 449 +a 3657 4106 1 +a 3658 3659 448 +a 3658 4106 1 +a 3659 3660 447 +a 3659 4106 1 +a 3660 3661 446 +a 3660 4106 1 +a 3661 3662 445 +a 3661 4106 1 +a 3662 3663 444 +a 3662 4106 1 +a 3663 3664 443 +a 3663 4106 1 +a 3664 3665 442 +a 3664 4106 1 +a 3665 3666 441 +a 3665 4106 1 +a 3666 3667 440 +a 3666 4106 1 +a 3667 3668 439 +a 3667 4106 1 +a 3668 3669 438 +a 3668 4106 1 +a 3669 3670 437 +a 3669 4106 1 +a 3670 3671 436 +a 3670 4106 1 +a 3671 3672 435 +a 3671 4106 1 +a 3672 3673 434 +a 3672 4106 1 +a 3673 3674 433 +a 3673 4106 1 +a 3674 3675 432 +a 3674 4106 1 +a 3675 3676 431 +a 3675 4106 1 +a 3676 3677 430 +a 3676 4106 1 +a 3677 3678 429 +a 3677 4106 1 +a 3678 3679 428 +a 3678 4106 1 +a 3679 3680 427 +a 3679 4106 1 +a 3680 3681 426 +a 3680 4106 1 +a 3681 3682 425 +a 3681 4106 1 +a 3682 3683 424 +a 3682 4106 1 +a 3683 3684 423 +a 3683 4106 1 +a 3684 3685 422 +a 3684 4106 1 +a 3685 3686 421 +a 3685 4106 1 +a 3686 3687 420 +a 3686 4106 1 +a 3687 3688 419 +a 3687 4106 1 +a 3688 3689 418 +a 3688 4106 1 +a 3689 3690 417 +a 3689 4106 1 +a 3690 3691 416 +a 3690 4106 1 +a 3691 3692 415 +a 3691 4106 1 +a 3692 3693 414 +a 3692 4106 1 +a 3693 3694 413 +a 3693 4106 1 +a 3694 3695 412 +a 3694 4106 1 +a 3695 3696 411 +a 3695 4106 1 +a 3696 3697 410 +a 3696 4106 1 +a 3697 3698 409 +a 3697 4106 1 +a 3698 3699 408 +a 3698 4106 1 +a 3699 3700 407 +a 3699 4106 1 +a 3700 3701 406 +a 3700 4106 1 +a 3701 3702 405 +a 3701 4106 1 +a 3702 3703 404 +a 3702 4106 1 +a 3703 3704 403 +a 3703 4106 1 +a 3704 3705 402 +a 3704 4106 1 +a 3705 3706 401 +a 3705 4106 1 +a 3706 3707 400 +a 3706 4106 1 +a 3707 3708 399 +a 3707 4106 1 +a 3708 3709 398 +a 3708 4106 1 +a 3709 3710 397 +a 3709 4106 1 +a 3710 3711 396 +a 3710 4106 1 +a 3711 3712 395 +a 3711 4106 1 +a 3712 3713 394 +a 3712 4106 1 +a 3713 3714 393 +a 3713 4106 1 +a 3714 3715 392 +a 3714 4106 1 +a 3715 3716 391 +a 3715 4106 1 +a 3716 3717 390 +a 3716 4106 1 +a 3717 3718 389 +a 3717 4106 1 +a 3718 3719 388 +a 3718 4106 1 +a 3719 3720 387 +a 3719 4106 1 +a 3720 3721 386 +a 3720 4106 1 +a 3721 3722 385 +a 3721 4106 1 +a 3722 3723 384 +a 3722 4106 1 +a 3723 3724 383 +a 3723 4106 1 +a 3724 3725 382 +a 3724 4106 1 +a 3725 3726 381 +a 3725 4106 1 +a 3726 3727 380 +a 3726 4106 1 +a 3727 3728 379 +a 3727 4106 1 +a 3728 3729 378 +a 3728 4106 1 +a 3729 3730 377 +a 3729 4106 1 +a 3730 3731 376 +a 3730 4106 1 +a 3731 3732 375 +a 3731 4106 1 +a 3732 3733 374 +a 3732 4106 1 +a 3733 3734 373 +a 3733 4106 1 +a 3734 3735 372 +a 3734 4106 1 +a 3735 3736 371 +a 3735 4106 1 +a 3736 3737 370 +a 3736 4106 1 +a 3737 3738 369 +a 3737 4106 1 +a 3738 3739 368 +a 3738 4106 1 +a 3739 3740 367 +a 3739 4106 1 +a 3740 3741 366 +a 3740 4106 1 +a 3741 3742 365 +a 3741 4106 1 +a 3742 3743 364 +a 3742 4106 1 +a 3743 3744 363 +a 3743 4106 1 +a 3744 3745 362 +a 3744 4106 1 +a 3745 3746 361 +a 3745 4106 1 +a 3746 3747 360 +a 3746 4106 1 +a 3747 3748 359 +a 3747 4106 1 +a 3748 3749 358 +a 3748 4106 1 +a 3749 3750 357 +a 3749 4106 1 +a 3750 3751 356 +a 3750 4106 1 +a 3751 3752 355 +a 3751 4106 1 +a 3752 3753 354 +a 3752 4106 1 +a 3753 3754 353 +a 3753 4106 1 +a 3754 3755 352 +a 3754 4106 1 +a 3755 3756 351 +a 3755 4106 1 +a 3756 3757 350 +a 3756 4106 1 +a 3757 3758 349 +a 3757 4106 1 +a 3758 3759 348 +a 3758 4106 1 +a 3759 3760 347 +a 3759 4106 1 +a 3760 3761 346 +a 3760 4106 1 +a 3761 3762 345 +a 3761 4106 1 +a 3762 3763 344 +a 3762 4106 1 +a 3763 3764 343 +a 3763 4106 1 +a 3764 3765 342 +a 3764 4106 1 +a 3765 3766 341 +a 3765 4106 1 +a 3766 3767 340 +a 3766 4106 1 +a 3767 3768 339 +a 3767 4106 1 +a 3768 3769 338 +a 3768 4106 1 +a 3769 3770 337 +a 3769 4106 1 +a 3770 3771 336 +a 3770 4106 1 +a 3771 3772 335 +a 3771 4106 1 +a 3772 3773 334 +a 3772 4106 1 +a 3773 3774 333 +a 3773 4106 1 +a 3774 3775 332 +a 3774 4106 1 +a 3775 3776 331 +a 3775 4106 1 +a 3776 3777 330 +a 3776 4106 1 +a 3777 3778 329 +a 3777 4106 1 +a 3778 3779 328 +a 3778 4106 1 +a 3779 3780 327 +a 3779 4106 1 +a 3780 3781 326 +a 3780 4106 1 +a 3781 3782 325 +a 3781 4106 1 +a 3782 3783 324 +a 3782 4106 1 +a 3783 3784 323 +a 3783 4106 1 +a 3784 3785 322 +a 3784 4106 1 +a 3785 3786 321 +a 3785 4106 1 +a 3786 3787 320 +a 3786 4106 1 +a 3787 3788 319 +a 3787 4106 1 +a 3788 3789 318 +a 3788 4106 1 +a 3789 3790 317 +a 3789 4106 1 +a 3790 3791 316 +a 3790 4106 1 +a 3791 3792 315 +a 3791 4106 1 +a 3792 3793 314 +a 3792 4106 1 +a 3793 3794 313 +a 3793 4106 1 +a 3794 3795 312 +a 3794 4106 1 +a 3795 3796 311 +a 3795 4106 1 +a 3796 3797 310 +a 3796 4106 1 +a 3797 3798 309 +a 3797 4106 1 +a 3798 3799 308 +a 3798 4106 1 +a 3799 3800 307 +a 3799 4106 1 +a 3800 3801 306 +a 3800 4106 1 +a 3801 3802 305 +a 3801 4106 1 +a 3802 3803 304 +a 3802 4106 1 +a 3803 3804 303 +a 3803 4106 1 +a 3804 3805 302 +a 3804 4106 1 +a 3805 3806 301 +a 3805 4106 1 +a 3806 3807 300 +a 3806 4106 1 +a 3807 3808 299 +a 3807 4106 1 +a 3808 3809 298 +a 3808 4106 1 +a 3809 3810 297 +a 3809 4106 1 +a 3810 3811 296 +a 3810 4106 1 +a 3811 3812 295 +a 3811 4106 1 +a 3812 3813 294 +a 3812 4106 1 +a 3813 3814 293 +a 3813 4106 1 +a 3814 3815 292 +a 3814 4106 1 +a 3815 3816 291 +a 3815 4106 1 +a 3816 3817 290 +a 3816 4106 1 +a 3817 3818 289 +a 3817 4106 1 +a 3818 3819 288 +a 3818 4106 1 +a 3819 3820 287 +a 3819 4106 1 +a 3820 3821 286 +a 3820 4106 1 +a 3821 3822 285 +a 3821 4106 1 +a 3822 3823 284 +a 3822 4106 1 +a 3823 3824 283 +a 3823 4106 1 +a 3824 3825 282 +a 3824 4106 1 +a 3825 3826 281 +a 3825 4106 1 +a 3826 3827 280 +a 3826 4106 1 +a 3827 3828 279 +a 3827 4106 1 +a 3828 3829 278 +a 3828 4106 1 +a 3829 3830 277 +a 3829 4106 1 +a 3830 3831 276 +a 3830 4106 1 +a 3831 3832 275 +a 3831 4106 1 +a 3832 3833 274 +a 3832 4106 1 +a 3833 3834 273 +a 3833 4106 1 +a 3834 3835 272 +a 3834 4106 1 +a 3835 3836 271 +a 3835 4106 1 +a 3836 3837 270 +a 3836 4106 1 +a 3837 3838 269 +a 3837 4106 1 +a 3838 3839 268 +a 3838 4106 1 +a 3839 3840 267 +a 3839 4106 1 +a 3840 3841 266 +a 3840 4106 1 +a 3841 3842 265 +a 3841 4106 1 +a 3842 3843 264 +a 3842 4106 1 +a 3843 3844 263 +a 3843 4106 1 +a 3844 3845 262 +a 3844 4106 1 +a 3845 3846 261 +a 3845 4106 1 +a 3846 3847 260 +a 3846 4106 1 +a 3847 3848 259 +a 3847 4106 1 +a 3848 3849 258 +a 3848 4106 1 +a 3849 3850 257 +a 3849 4106 1 +a 3850 3851 256 +a 3850 4106 1 +a 3851 3852 255 +a 3851 4106 1 +a 3852 3853 254 +a 3852 4106 1 +a 3853 3854 253 +a 3853 4106 1 +a 3854 3855 252 +a 3854 4106 1 +a 3855 3856 251 +a 3855 4106 1 +a 3856 3857 250 +a 3856 4106 1 +a 3857 3858 249 +a 3857 4106 1 +a 3858 3859 248 +a 3858 4106 1 +a 3859 3860 247 +a 3859 4106 1 +a 3860 3861 246 +a 3860 4106 1 +a 3861 3862 245 +a 3861 4106 1 +a 3862 3863 244 +a 3862 4106 1 +a 3863 3864 243 +a 3863 4106 1 +a 3864 3865 242 +a 3864 4106 1 +a 3865 3866 241 +a 3865 4106 1 +a 3866 3867 240 +a 3866 4106 1 +a 3867 3868 239 +a 3867 4106 1 +a 3868 3869 238 +a 3868 4106 1 +a 3869 3870 237 +a 3869 4106 1 +a 3870 3871 236 +a 3870 4106 1 +a 3871 3872 235 +a 3871 4106 1 +a 3872 3873 234 +a 3872 4106 1 +a 3873 3874 233 +a 3873 4106 1 +a 3874 3875 232 +a 3874 4106 1 +a 3875 3876 231 +a 3875 4106 1 +a 3876 3877 230 +a 3876 4106 1 +a 3877 3878 229 +a 3877 4106 1 +a 3878 3879 228 +a 3878 4106 1 +a 3879 3880 227 +a 3879 4106 1 +a 3880 3881 226 +a 3880 4106 1 +a 3881 3882 225 +a 3881 4106 1 +a 3882 3883 224 +a 3882 4106 1 +a 3883 3884 223 +a 3883 4106 1 +a 3884 3885 222 +a 3884 4106 1 +a 3885 3886 221 +a 3885 4106 1 +a 3886 3887 220 +a 3886 4106 1 +a 3887 3888 219 +a 3887 4106 1 +a 3888 3889 218 +a 3888 4106 1 +a 3889 3890 217 +a 3889 4106 1 +a 3890 3891 216 +a 3890 4106 1 +a 3891 3892 215 +a 3891 4106 1 +a 3892 3893 214 +a 3892 4106 1 +a 3893 3894 213 +a 3893 4106 1 +a 3894 3895 212 +a 3894 4106 1 +a 3895 3896 211 +a 3895 4106 1 +a 3896 3897 210 +a 3896 4106 1 +a 3897 3898 209 +a 3897 4106 1 +a 3898 3899 208 +a 3898 4106 1 +a 3899 3900 207 +a 3899 4106 1 +a 3900 3901 206 +a 3900 4106 1 +a 3901 3902 205 +a 3901 4106 1 +a 3902 3903 204 +a 3902 4106 1 +a 3903 3904 203 +a 3903 4106 1 +a 3904 3905 202 +a 3904 4106 1 +a 3905 3906 201 +a 3905 4106 1 +a 3906 3907 200 +a 3906 4106 1 +a 3907 3908 199 +a 3907 4106 1 +a 3908 3909 198 +a 3908 4106 1 +a 3909 3910 197 +a 3909 4106 1 +a 3910 3911 196 +a 3910 4106 1 +a 3911 3912 195 +a 3911 4106 1 +a 3912 3913 194 +a 3912 4106 1 +a 3913 3914 193 +a 3913 4106 1 +a 3914 3915 192 +a 3914 4106 1 +a 3915 3916 191 +a 3915 4106 1 +a 3916 3917 190 +a 3916 4106 1 +a 3917 3918 189 +a 3917 4106 1 +a 3918 3919 188 +a 3918 4106 1 +a 3919 3920 187 +a 3919 4106 1 +a 3920 3921 186 +a 3920 4106 1 +a 3921 3922 185 +a 3921 4106 1 +a 3922 3923 184 +a 3922 4106 1 +a 3923 3924 183 +a 3923 4106 1 +a 3924 3925 182 +a 3924 4106 1 +a 3925 3926 181 +a 3925 4106 1 +a 3926 3927 180 +a 3926 4106 1 +a 3927 3928 179 +a 3927 4106 1 +a 3928 3929 178 +a 3928 4106 1 +a 3929 3930 177 +a 3929 4106 1 +a 3930 3931 176 +a 3930 4106 1 +a 3931 3932 175 +a 3931 4106 1 +a 3932 3933 174 +a 3932 4106 1 +a 3933 3934 173 +a 3933 4106 1 +a 3934 3935 172 +a 3934 4106 1 +a 3935 3936 171 +a 3935 4106 1 +a 3936 3937 170 +a 3936 4106 1 +a 3937 3938 169 +a 3937 4106 1 +a 3938 3939 168 +a 3938 4106 1 +a 3939 3940 167 +a 3939 4106 1 +a 3940 3941 166 +a 3940 4106 1 +a 3941 3942 165 +a 3941 4106 1 +a 3942 3943 164 +a 3942 4106 1 +a 3943 3944 163 +a 3943 4106 1 +a 3944 3945 162 +a 3944 4106 1 +a 3945 3946 161 +a 3945 4106 1 +a 3946 3947 160 +a 3946 4106 1 +a 3947 3948 159 +a 3947 4106 1 +a 3948 3949 158 +a 3948 4106 1 +a 3949 3950 157 +a 3949 4106 1 +a 3950 3951 156 +a 3950 4106 1 +a 3951 3952 155 +a 3951 4106 1 +a 3952 3953 154 +a 3952 4106 1 +a 3953 3954 153 +a 3953 4106 1 +a 3954 3955 152 +a 3954 4106 1 +a 3955 3956 151 +a 3955 4106 1 +a 3956 3957 150 +a 3956 4106 1 +a 3957 3958 149 +a 3957 4106 1 +a 3958 3959 148 +a 3958 4106 1 +a 3959 3960 147 +a 3959 4106 1 +a 3960 3961 146 +a 3960 4106 1 +a 3961 3962 145 +a 3961 4106 1 +a 3962 3963 144 +a 3962 4106 1 +a 3963 3964 143 +a 3963 4106 1 +a 3964 3965 142 +a 3964 4106 1 +a 3965 3966 141 +a 3965 4106 1 +a 3966 3967 140 +a 3966 4106 1 +a 3967 3968 139 +a 3967 4106 1 +a 3968 3969 138 +a 3968 4106 1 +a 3969 3970 137 +a 3969 4106 1 +a 3970 3971 136 +a 3970 4106 1 +a 3971 3972 135 +a 3971 4106 1 +a 3972 3973 134 +a 3972 4106 1 +a 3973 3974 133 +a 3973 4106 1 +a 3974 3975 132 +a 3974 4106 1 +a 3975 3976 131 +a 3975 4106 1 +a 3976 3977 130 +a 3976 4106 1 +a 3977 3978 129 +a 3977 4106 1 +a 3978 3979 128 +a 3978 4106 1 +a 3979 3980 127 +a 3979 4106 1 +a 3980 3981 126 +a 3980 4106 1 +a 3981 3982 125 +a 3981 4106 1 +a 3982 3983 124 +a 3982 4106 1 +a 3983 3984 123 +a 3983 4106 1 +a 3984 3985 122 +a 3984 4106 1 +a 3985 3986 121 +a 3985 4106 1 +a 3986 3987 120 +a 3986 4106 1 +a 3987 3988 119 +a 3987 4106 1 +a 3988 3989 118 +a 3988 4106 1 +a 3989 3990 117 +a 3989 4106 1 +a 3990 3991 116 +a 3990 4106 1 +a 3991 3992 115 +a 3991 4106 1 +a 3992 3993 114 +a 3992 4106 1 +a 3993 3994 113 +a 3993 4106 1 +a 3994 3995 112 +a 3994 4106 1 +a 3995 3996 111 +a 3995 4106 1 +a 3996 3997 110 +a 3996 4106 1 +a 3997 3998 109 +a 3997 4106 1 +a 3998 3999 108 +a 3998 4106 1 +a 3999 4000 107 +a 3999 4106 1 +a 4000 4001 106 +a 4000 4106 1 +a 4001 4002 105 +a 4001 4106 1 +a 4002 4003 104 +a 4002 4106 1 +a 4003 4004 103 +a 4003 4106 1 +a 4004 4005 102 +a 4004 4106 1 +a 4005 4006 101 +a 4005 4106 1 +a 4006 4007 100 +a 4006 4106 1 +a 4007 4008 99 +a 4007 4106 1 +a 4008 4009 98 +a 4008 4106 1 +a 4009 4010 97 +a 4009 4106 1 +a 4010 4011 96 +a 4010 4106 1 +a 4011 4012 95 +a 4011 4106 1 +a 4012 4013 94 +a 4012 4106 1 +a 4013 4014 93 +a 4013 4106 1 +a 4014 4015 92 +a 4014 4106 1 +a 4015 4016 91 +a 4015 4106 1 +a 4016 4017 90 +a 4016 4106 1 +a 4017 4018 89 +a 4017 4106 1 +a 4018 4019 88 +a 4018 4106 1 +a 4019 4020 87 +a 4019 4106 1 +a 4020 4021 86 +a 4020 4106 1 +a 4021 4022 85 +a 4021 4106 1 +a 4022 4023 84 +a 4022 4106 1 +a 4023 4024 83 +a 4023 4106 1 +a 4024 4025 82 +a 4024 4106 1 +a 4025 4026 81 +a 4025 4106 1 +a 4026 4027 80 +a 4026 4106 1 +a 4027 4028 79 +a 4027 4106 1 +a 4028 4029 78 +a 4028 4106 1 +a 4029 4030 77 +a 4029 4106 1 +a 4030 4031 76 +a 4030 4106 1 +a 4031 4032 75 +a 4031 4106 1 +a 4032 4033 74 +a 4032 4106 1 +a 4033 4034 73 +a 4033 4106 1 +a 4034 4035 72 +a 4034 4106 1 +a 4035 4036 71 +a 4035 4106 1 +a 4036 4037 70 +a 4036 4106 1 +a 4037 4038 69 +a 4037 4106 1 +a 4038 4039 68 +a 4038 4106 1 +a 4039 4040 67 +a 4039 4106 1 +a 4040 4041 66 +a 4040 4106 1 +a 4041 4042 65 +a 4041 4106 1 +a 4042 4043 64 +a 4042 4106 1 +a 4043 4044 63 +a 4043 4106 1 +a 4044 4045 62 +a 4044 4106 1 +a 4045 4046 61 +a 4045 4106 1 +a 4046 4047 60 +a 4046 4106 1 +a 4047 4048 59 +a 4047 4106 1 +a 4048 4049 58 +a 4048 4106 1 +a 4049 4050 57 +a 4049 4106 1 +a 4050 4051 56 +a 4050 4106 1 +a 4051 4052 55 +a 4051 4106 1 +a 4052 4053 54 +a 4052 4106 1 +a 4053 4054 53 +a 4053 4106 1 +a 4054 4055 52 +a 4054 4106 1 +a 4055 4056 51 +a 4055 4106 1 +a 4056 4057 50 +a 4056 4106 1 +a 4057 4058 49 +a 4057 4106 1 +a 4058 4059 48 +a 4058 4106 1 +a 4059 4060 47 +a 4059 4106 1 +a 4060 4061 46 +a 4060 4106 1 +a 4061 4062 45 +a 4061 4106 1 +a 4062 4063 44 +a 4062 4106 1 +a 4063 4064 43 +a 4063 4106 1 +a 4064 4065 42 +a 4064 4106 1 +a 4065 4066 41 +a 4065 4106 1 +a 4066 4067 40 +a 4066 4106 1 +a 4067 4068 39 +a 4067 4106 1 +a 4068 4069 38 +a 4068 4106 1 +a 4069 4070 37 +a 4069 4106 1 +a 4070 4071 36 +a 4070 4106 1 +a 4071 4072 35 +a 4071 4106 1 +a 4072 4073 34 +a 4072 4106 1 +a 4073 4074 33 +a 4073 4106 1 +a 4074 4075 32 +a 4074 4106 1 +a 4075 4076 31 +a 4075 4106 1 +a 4076 4077 30 +a 4076 4106 1 +a 4077 4078 29 +a 4077 4106 1 +a 4078 4079 28 +a 4078 4106 1 +a 4079 4080 27 +a 4079 4106 1 +a 4080 4081 26 +a 4080 4106 1 +a 4081 4082 25 +a 4081 4106 1 +a 4082 4083 24 +a 4082 4106 1 +a 4083 4084 23 +a 4083 4106 1 +a 4084 4085 22 +a 4084 4106 1 +a 4085 4086 21 +a 4085 4106 1 +a 4086 4087 20 +a 4086 4106 1 +a 4087 4088 19 +a 4087 4106 1 +a 4088 4089 18 +a 4088 4106 1 +a 4089 4090 17 +a 4089 4106 1 +a 4090 4091 16 +a 4090 4106 1 +a 4091 4092 15 +a 4091 4106 1 +a 4092 4093 14 +a 4092 4106 1 +a 4093 4094 13 +a 4093 4106 1 +a 4094 4095 12 +a 4094 4106 1 +a 4095 4096 11 +a 4095 4106 1 +a 4096 4097 10 +a 4096 4106 1 +a 4097 4098 9 +a 4097 4106 1 +a 4098 4099 8 +a 4098 4106 1 +a 4099 4100 7 +a 4099 4106 1 +a 4100 4101 6 +a 4100 4106 1 +a 4101 4102 5 +a 4101 4106 1 +a 4102 4103 4 +a 4102 4106 1 +a 4103 4104 3 +a 4103 4106 1 +a 4104 4105 2 +a 4104 4106 1 +a 4105 8208 1 +a 4105 4106 1 +a 4106 4107 4103 +a 4107 4108 4103 +a 4108 4109 4103 +a 4109 4110 4103 +a 4110 4111 4103 +a 4111 4112 4103 +a 4112 4113 4103 +a 4113 4114 4103 +a 4114 4115 4103 +a 4115 4116 4103 +a 4116 4117 4103 +a 4117 4118 4103 +a 4118 4119 4103 +a 4119 4120 4103 +a 4120 4121 4103 +a 4121 4122 4103 +a 4122 4123 4103 +a 4123 4124 4103 +a 4124 4125 4103 +a 4125 4126 4103 +a 4126 4127 4103 +a 4127 4128 4103 +a 4128 4129 4103 +a 4129 4130 4103 +a 4130 4131 4103 +a 4131 4132 4103 +a 4132 4133 4103 +a 4133 4134 4103 +a 4134 4135 4103 +a 4135 4136 4103 +a 4136 4137 4103 +a 4137 4138 4103 +a 4138 4139 4103 +a 4139 4140 4103 +a 4140 4141 4103 +a 4141 4142 4103 +a 4142 4143 4103 +a 4143 4144 4103 +a 4144 4145 4103 +a 4145 4146 4103 +a 4146 4147 4103 +a 4147 4148 4103 +a 4148 4149 4103 +a 4149 4150 4103 +a 4150 4151 4103 +a 4151 4152 4103 +a 4152 4153 4103 +a 4153 4154 4103 +a 4154 4155 4103 +a 4155 4156 4103 +a 4156 4157 4103 +a 4157 4158 4103 +a 4158 4159 4103 +a 4159 4160 4103 +a 4160 4161 4103 +a 4161 4162 4103 +a 4162 4163 4103 +a 4163 4164 4103 +a 4164 4165 4103 +a 4165 4166 4103 +a 4166 4167 4103 +a 4167 4168 4103 +a 4168 4169 4103 +a 4169 4170 4103 +a 4170 4171 4103 +a 4171 4172 4103 +a 4172 4173 4103 +a 4173 4174 4103 +a 4174 4175 4103 +a 4175 4176 4103 +a 4176 4177 4103 +a 4177 4178 4103 +a 4178 4179 4103 +a 4179 4180 4103 +a 4180 4181 4103 +a 4181 4182 4103 +a 4182 4183 4103 +a 4183 4184 4103 +a 4184 4185 4103 +a 4185 4186 4103 +a 4186 4187 4103 +a 4187 4188 4103 +a 4188 4189 4103 +a 4189 4190 4103 +a 4190 4191 4103 +a 4191 4192 4103 +a 4192 4193 4103 +a 4193 4194 4103 +a 4194 4195 4103 +a 4195 4196 4103 +a 4196 4197 4103 +a 4197 4198 4103 +a 4198 4199 4103 +a 4199 4200 4103 +a 4200 4201 4103 +a 4201 4202 4103 +a 4202 4203 4103 +a 4203 4204 4103 +a 4204 4205 4103 +a 4205 4206 4103 +a 4206 4207 4103 +a 4207 4208 4103 +a 4208 4209 4103 +a 4209 4210 4103 +a 4210 4211 4103 +a 4211 4212 4103 +a 4212 4213 4103 +a 4213 4214 4103 +a 4214 4215 4103 +a 4215 4216 4103 +a 4216 4217 4103 +a 4217 4218 4103 +a 4218 4219 4103 +a 4219 4220 4103 +a 4220 4221 4103 +a 4221 4222 4103 +a 4222 4223 4103 +a 4223 4224 4103 +a 4224 4225 4103 +a 4225 4226 4103 +a 4226 4227 4103 +a 4227 4228 4103 +a 4228 4229 4103 +a 4229 4230 4103 +a 4230 4231 4103 +a 4231 4232 4103 +a 4232 4233 4103 +a 4233 4234 4103 +a 4234 4235 4103 +a 4235 4236 4103 +a 4236 4237 4103 +a 4237 4238 4103 +a 4238 4239 4103 +a 4239 4240 4103 +a 4240 4241 4103 +a 4241 4242 4103 +a 4242 4243 4103 +a 4243 4244 4103 +a 4244 4245 4103 +a 4245 4246 4103 +a 4246 4247 4103 +a 4247 4248 4103 +a 4248 4249 4103 +a 4249 4250 4103 +a 4250 4251 4103 +a 4251 4252 4103 +a 4252 4253 4103 +a 4253 4254 4103 +a 4254 4255 4103 +a 4255 4256 4103 +a 4256 4257 4103 +a 4257 4258 4103 +a 4258 4259 4103 +a 4259 4260 4103 +a 4260 4261 4103 +a 4261 4262 4103 +a 4262 4263 4103 +a 4263 4264 4103 +a 4264 4265 4103 +a 4265 4266 4103 +a 4266 4267 4103 +a 4267 4268 4103 +a 4268 4269 4103 +a 4269 4270 4103 +a 4270 4271 4103 +a 4271 4272 4103 +a 4272 4273 4103 +a 4273 4274 4103 +a 4274 4275 4103 +a 4275 4276 4103 +a 4276 4277 4103 +a 4277 4278 4103 +a 4278 4279 4103 +a 4279 4280 4103 +a 4280 4281 4103 +a 4281 4282 4103 +a 4282 4283 4103 +a 4283 4284 4103 +a 4284 4285 4103 +a 4285 4286 4103 +a 4286 4287 4103 +a 4287 4288 4103 +a 4288 4289 4103 +a 4289 4290 4103 +a 4290 4291 4103 +a 4291 4292 4103 +a 4292 4293 4103 +a 4293 4294 4103 +a 4294 4295 4103 +a 4295 4296 4103 +a 4296 4297 4103 +a 4297 4298 4103 +a 4298 4299 4103 +a 4299 4300 4103 +a 4300 4301 4103 +a 4301 4302 4103 +a 4302 4303 4103 +a 4303 4304 4103 +a 4304 4305 4103 +a 4305 4306 4103 +a 4306 4307 4103 +a 4307 4308 4103 +a 4308 4309 4103 +a 4309 4310 4103 +a 4310 4311 4103 +a 4311 4312 4103 +a 4312 4313 4103 +a 4313 4314 4103 +a 4314 4315 4103 +a 4315 4316 4103 +a 4316 4317 4103 +a 4317 4318 4103 +a 4318 4319 4103 +a 4319 4320 4103 +a 4320 4321 4103 +a 4321 4322 4103 +a 4322 4323 4103 +a 4323 4324 4103 +a 4324 4325 4103 +a 4325 4326 4103 +a 4326 4327 4103 +a 4327 4328 4103 +a 4328 4329 4103 +a 4329 4330 4103 +a 4330 4331 4103 +a 4331 4332 4103 +a 4332 4333 4103 +a 4333 4334 4103 +a 4334 4335 4103 +a 4335 4336 4103 +a 4336 4337 4103 +a 4337 4338 4103 +a 4338 4339 4103 +a 4339 4340 4103 +a 4340 4341 4103 +a 4341 4342 4103 +a 4342 4343 4103 +a 4343 4344 4103 +a 4344 4345 4103 +a 4345 4346 4103 +a 4346 4347 4103 +a 4347 4348 4103 +a 4348 4349 4103 +a 4349 4350 4103 +a 4350 4351 4103 +a 4351 4352 4103 +a 4352 4353 4103 +a 4353 4354 4103 +a 4354 4355 4103 +a 4355 4356 4103 +a 4356 4357 4103 +a 4357 4358 4103 +a 4358 4359 4103 +a 4359 4360 4103 +a 4360 4361 4103 +a 4361 4362 4103 +a 4362 4363 4103 +a 4363 4364 4103 +a 4364 4365 4103 +a 4365 4366 4103 +a 4366 4367 4103 +a 4367 4368 4103 +a 4368 4369 4103 +a 4369 4370 4103 +a 4370 4371 4103 +a 4371 4372 4103 +a 4372 4373 4103 +a 4373 4374 4103 +a 4374 4375 4103 +a 4375 4376 4103 +a 4376 4377 4103 +a 4377 4378 4103 +a 4378 4379 4103 +a 4379 4380 4103 +a 4380 4381 4103 +a 4381 4382 4103 +a 4382 4383 4103 +a 4383 4384 4103 +a 4384 4385 4103 +a 4385 4386 4103 +a 4386 4387 4103 +a 4387 4388 4103 +a 4388 4389 4103 +a 4389 4390 4103 +a 4390 4391 4103 +a 4391 4392 4103 +a 4392 4393 4103 +a 4393 4394 4103 +a 4394 4395 4103 +a 4395 4396 4103 +a 4396 4397 4103 +a 4397 4398 4103 +a 4398 4399 4103 +a 4399 4400 4103 +a 4400 4401 4103 +a 4401 4402 4103 +a 4402 4403 4103 +a 4403 4404 4103 +a 4404 4405 4103 +a 4405 4406 4103 +a 4406 4407 4103 +a 4407 4408 4103 +a 4408 4409 4103 +a 4409 4410 4103 +a 4410 4411 4103 +a 4411 4412 4103 +a 4412 4413 4103 +a 4413 4414 4103 +a 4414 4415 4103 +a 4415 4416 4103 +a 4416 4417 4103 +a 4417 4418 4103 +a 4418 4419 4103 +a 4419 4420 4103 +a 4420 4421 4103 +a 4421 4422 4103 +a 4422 4423 4103 +a 4423 4424 4103 +a 4424 4425 4103 +a 4425 4426 4103 +a 4426 4427 4103 +a 4427 4428 4103 +a 4428 4429 4103 +a 4429 4430 4103 +a 4430 4431 4103 +a 4431 4432 4103 +a 4432 4433 4103 +a 4433 4434 4103 +a 4434 4435 4103 +a 4435 4436 4103 +a 4436 4437 4103 +a 4437 4438 4103 +a 4438 4439 4103 +a 4439 4440 4103 +a 4440 4441 4103 +a 4441 4442 4103 +a 4442 4443 4103 +a 4443 4444 4103 +a 4444 4445 4103 +a 4445 4446 4103 +a 4446 4447 4103 +a 4447 4448 4103 +a 4448 4449 4103 +a 4449 4450 4103 +a 4450 4451 4103 +a 4451 4452 4103 +a 4452 4453 4103 +a 4453 4454 4103 +a 4454 4455 4103 +a 4455 4456 4103 +a 4456 4457 4103 +a 4457 4458 4103 +a 4458 4459 4103 +a 4459 4460 4103 +a 4460 4461 4103 +a 4461 4462 4103 +a 4462 4463 4103 +a 4463 4464 4103 +a 4464 4465 4103 +a 4465 4466 4103 +a 4466 4467 4103 +a 4467 4468 4103 +a 4468 4469 4103 +a 4469 4470 4103 +a 4470 4471 4103 +a 4471 4472 4103 +a 4472 4473 4103 +a 4473 4474 4103 +a 4474 4475 4103 +a 4475 4476 4103 +a 4476 4477 4103 +a 4477 4478 4103 +a 4478 4479 4103 +a 4479 4480 4103 +a 4480 4481 4103 +a 4481 4482 4103 +a 4482 4483 4103 +a 4483 4484 4103 +a 4484 4485 4103 +a 4485 4486 4103 +a 4486 4487 4103 +a 4487 4488 4103 +a 4488 4489 4103 +a 4489 4490 4103 +a 4490 4491 4103 +a 4491 4492 4103 +a 4492 4493 4103 +a 4493 4494 4103 +a 4494 4495 4103 +a 4495 4496 4103 +a 4496 4497 4103 +a 4497 4498 4103 +a 4498 4499 4103 +a 4499 4500 4103 +a 4500 4501 4103 +a 4501 4502 4103 +a 4502 4503 4103 +a 4503 4504 4103 +a 4504 4505 4103 +a 4505 4506 4103 +a 4506 4507 4103 +a 4507 4508 4103 +a 4508 4509 4103 +a 4509 4510 4103 +a 4510 4511 4103 +a 4511 4512 4103 +a 4512 4513 4103 +a 4513 4514 4103 +a 4514 4515 4103 +a 4515 4516 4103 +a 4516 4517 4103 +a 4517 4518 4103 +a 4518 4519 4103 +a 4519 4520 4103 +a 4520 4521 4103 +a 4521 4522 4103 +a 4522 4523 4103 +a 4523 4524 4103 +a 4524 4525 4103 +a 4525 4526 4103 +a 4526 4527 4103 +a 4527 4528 4103 +a 4528 4529 4103 +a 4529 4530 4103 +a 4530 4531 4103 +a 4531 4532 4103 +a 4532 4533 4103 +a 4533 4534 4103 +a 4534 4535 4103 +a 4535 4536 4103 +a 4536 4537 4103 +a 4537 4538 4103 +a 4538 4539 4103 +a 4539 4540 4103 +a 4540 4541 4103 +a 4541 4542 4103 +a 4542 4543 4103 +a 4543 4544 4103 +a 4544 4545 4103 +a 4545 4546 4103 +a 4546 4547 4103 +a 4547 4548 4103 +a 4548 4549 4103 +a 4549 4550 4103 +a 4550 4551 4103 +a 4551 4552 4103 +a 4552 4553 4103 +a 4553 4554 4103 +a 4554 4555 4103 +a 4555 4556 4103 +a 4556 4557 4103 +a 4557 4558 4103 +a 4558 4559 4103 +a 4559 4560 4103 +a 4560 4561 4103 +a 4561 4562 4103 +a 4562 4563 4103 +a 4563 4564 4103 +a 4564 4565 4103 +a 4565 4566 4103 +a 4566 4567 4103 +a 4567 4568 4103 +a 4568 4569 4103 +a 4569 4570 4103 +a 4570 4571 4103 +a 4571 4572 4103 +a 4572 4573 4103 +a 4573 4574 4103 +a 4574 4575 4103 +a 4575 4576 4103 +a 4576 4577 4103 +a 4577 4578 4103 +a 4578 4579 4103 +a 4579 4580 4103 +a 4580 4581 4103 +a 4581 4582 4103 +a 4582 4583 4103 +a 4583 4584 4103 +a 4584 4585 4103 +a 4585 4586 4103 +a 4586 4587 4103 +a 4587 4588 4103 +a 4588 4589 4103 +a 4589 4590 4103 +a 4590 4591 4103 +a 4591 4592 4103 +a 4592 4593 4103 +a 4593 4594 4103 +a 4594 4595 4103 +a 4595 4596 4103 +a 4596 4597 4103 +a 4597 4598 4103 +a 4598 4599 4103 +a 4599 4600 4103 +a 4600 4601 4103 +a 4601 4602 4103 +a 4602 4603 4103 +a 4603 4604 4103 +a 4604 4605 4103 +a 4605 4606 4103 +a 4606 4607 4103 +a 4607 4608 4103 +a 4608 4609 4103 +a 4609 4610 4103 +a 4610 4611 4103 +a 4611 4612 4103 +a 4612 4613 4103 +a 4613 4614 4103 +a 4614 4615 4103 +a 4615 4616 4103 +a 4616 4617 4103 +a 4617 4618 4103 +a 4618 4619 4103 +a 4619 4620 4103 +a 4620 4621 4103 +a 4621 4622 4103 +a 4622 4623 4103 +a 4623 4624 4103 +a 4624 4625 4103 +a 4625 4626 4103 +a 4626 4627 4103 +a 4627 4628 4103 +a 4628 4629 4103 +a 4629 4630 4103 +a 4630 4631 4103 +a 4631 4632 4103 +a 4632 4633 4103 +a 4633 4634 4103 +a 4634 4635 4103 +a 4635 4636 4103 +a 4636 4637 4103 +a 4637 4638 4103 +a 4638 4639 4103 +a 4639 4640 4103 +a 4640 4641 4103 +a 4641 4642 4103 +a 4642 4643 4103 +a 4643 4644 4103 +a 4644 4645 4103 +a 4645 4646 4103 +a 4646 4647 4103 +a 4647 4648 4103 +a 4648 4649 4103 +a 4649 4650 4103 +a 4650 4651 4103 +a 4651 4652 4103 +a 4652 4653 4103 +a 4653 4654 4103 +a 4654 4655 4103 +a 4655 4656 4103 +a 4656 4657 4103 +a 4657 4658 4103 +a 4658 4659 4103 +a 4659 4660 4103 +a 4660 4661 4103 +a 4661 4662 4103 +a 4662 4663 4103 +a 4663 4664 4103 +a 4664 4665 4103 +a 4665 4666 4103 +a 4666 4667 4103 +a 4667 4668 4103 +a 4668 4669 4103 +a 4669 4670 4103 +a 4670 4671 4103 +a 4671 4672 4103 +a 4672 4673 4103 +a 4673 4674 4103 +a 4674 4675 4103 +a 4675 4676 4103 +a 4676 4677 4103 +a 4677 4678 4103 +a 4678 4679 4103 +a 4679 4680 4103 +a 4680 4681 4103 +a 4681 4682 4103 +a 4682 4683 4103 +a 4683 4684 4103 +a 4684 4685 4103 +a 4685 4686 4103 +a 4686 4687 4103 +a 4687 4688 4103 +a 4688 4689 4103 +a 4689 4690 4103 +a 4690 4691 4103 +a 4691 4692 4103 +a 4692 4693 4103 +a 4693 4694 4103 +a 4694 4695 4103 +a 4695 4696 4103 +a 4696 4697 4103 +a 4697 4698 4103 +a 4698 4699 4103 +a 4699 4700 4103 +a 4700 4701 4103 +a 4701 4702 4103 +a 4702 4703 4103 +a 4703 4704 4103 +a 4704 4705 4103 +a 4705 4706 4103 +a 4706 4707 4103 +a 4707 4708 4103 +a 4708 4709 4103 +a 4709 4710 4103 +a 4710 4711 4103 +a 4711 4712 4103 +a 4712 4713 4103 +a 4713 4714 4103 +a 4714 4715 4103 +a 4715 4716 4103 +a 4716 4717 4103 +a 4717 4718 4103 +a 4718 4719 4103 +a 4719 4720 4103 +a 4720 4721 4103 +a 4721 4722 4103 +a 4722 4723 4103 +a 4723 4724 4103 +a 4724 4725 4103 +a 4725 4726 4103 +a 4726 4727 4103 +a 4727 4728 4103 +a 4728 4729 4103 +a 4729 4730 4103 +a 4730 4731 4103 +a 4731 4732 4103 +a 4732 4733 4103 +a 4733 4734 4103 +a 4734 4735 4103 +a 4735 4736 4103 +a 4736 4737 4103 +a 4737 4738 4103 +a 4738 4739 4103 +a 4739 4740 4103 +a 4740 4741 4103 +a 4741 4742 4103 +a 4742 4743 4103 +a 4743 4744 4103 +a 4744 4745 4103 +a 4745 4746 4103 +a 4746 4747 4103 +a 4747 4748 4103 +a 4748 4749 4103 +a 4749 4750 4103 +a 4750 4751 4103 +a 4751 4752 4103 +a 4752 4753 4103 +a 4753 4754 4103 +a 4754 4755 4103 +a 4755 4756 4103 +a 4756 4757 4103 +a 4757 4758 4103 +a 4758 4759 4103 +a 4759 4760 4103 +a 4760 4761 4103 +a 4761 4762 4103 +a 4762 4763 4103 +a 4763 4764 4103 +a 4764 4765 4103 +a 4765 4766 4103 +a 4766 4767 4103 +a 4767 4768 4103 +a 4768 4769 4103 +a 4769 4770 4103 +a 4770 4771 4103 +a 4771 4772 4103 +a 4772 4773 4103 +a 4773 4774 4103 +a 4774 4775 4103 +a 4775 4776 4103 +a 4776 4777 4103 +a 4777 4778 4103 +a 4778 4779 4103 +a 4779 4780 4103 +a 4780 4781 4103 +a 4781 4782 4103 +a 4782 4783 4103 +a 4783 4784 4103 +a 4784 4785 4103 +a 4785 4786 4103 +a 4786 4787 4103 +a 4787 4788 4103 +a 4788 4789 4103 +a 4789 4790 4103 +a 4790 4791 4103 +a 4791 4792 4103 +a 4792 4793 4103 +a 4793 4794 4103 +a 4794 4795 4103 +a 4795 4796 4103 +a 4796 4797 4103 +a 4797 4798 4103 +a 4798 4799 4103 +a 4799 4800 4103 +a 4800 4801 4103 +a 4801 4802 4103 +a 4802 4803 4103 +a 4803 4804 4103 +a 4804 4805 4103 +a 4805 4806 4103 +a 4806 4807 4103 +a 4807 4808 4103 +a 4808 4809 4103 +a 4809 4810 4103 +a 4810 4811 4103 +a 4811 4812 4103 +a 4812 4813 4103 +a 4813 4814 4103 +a 4814 4815 4103 +a 4815 4816 4103 +a 4816 4817 4103 +a 4817 4818 4103 +a 4818 4819 4103 +a 4819 4820 4103 +a 4820 4821 4103 +a 4821 4822 4103 +a 4822 4823 4103 +a 4823 4824 4103 +a 4824 4825 4103 +a 4825 4826 4103 +a 4826 4827 4103 +a 4827 4828 4103 +a 4828 4829 4103 +a 4829 4830 4103 +a 4830 4831 4103 +a 4831 4832 4103 +a 4832 4833 4103 +a 4833 4834 4103 +a 4834 4835 4103 +a 4835 4836 4103 +a 4836 4837 4103 +a 4837 4838 4103 +a 4838 4839 4103 +a 4839 4840 4103 +a 4840 4841 4103 +a 4841 4842 4103 +a 4842 4843 4103 +a 4843 4844 4103 +a 4844 4845 4103 +a 4845 4846 4103 +a 4846 4847 4103 +a 4847 4848 4103 +a 4848 4849 4103 +a 4849 4850 4103 +a 4850 4851 4103 +a 4851 4852 4103 +a 4852 4853 4103 +a 4853 4854 4103 +a 4854 4855 4103 +a 4855 4856 4103 +a 4856 4857 4103 +a 4857 4858 4103 +a 4858 4859 4103 +a 4859 4860 4103 +a 4860 4861 4103 +a 4861 4862 4103 +a 4862 4863 4103 +a 4863 4864 4103 +a 4864 4865 4103 +a 4865 4866 4103 +a 4866 4867 4103 +a 4867 4868 4103 +a 4868 4869 4103 +a 4869 4870 4103 +a 4870 4871 4103 +a 4871 4872 4103 +a 4872 4873 4103 +a 4873 4874 4103 +a 4874 4875 4103 +a 4875 4876 4103 +a 4876 4877 4103 +a 4877 4878 4103 +a 4878 4879 4103 +a 4879 4880 4103 +a 4880 4881 4103 +a 4881 4882 4103 +a 4882 4883 4103 +a 4883 4884 4103 +a 4884 4885 4103 +a 4885 4886 4103 +a 4886 4887 4103 +a 4887 4888 4103 +a 4888 4889 4103 +a 4889 4890 4103 +a 4890 4891 4103 +a 4891 4892 4103 +a 4892 4893 4103 +a 4893 4894 4103 +a 4894 4895 4103 +a 4895 4896 4103 +a 4896 4897 4103 +a 4897 4898 4103 +a 4898 4899 4103 +a 4899 4900 4103 +a 4900 4901 4103 +a 4901 4902 4103 +a 4902 4903 4103 +a 4903 4904 4103 +a 4904 4905 4103 +a 4905 4906 4103 +a 4906 4907 4103 +a 4907 4908 4103 +a 4908 4909 4103 +a 4909 4910 4103 +a 4910 4911 4103 +a 4911 4912 4103 +a 4912 4913 4103 +a 4913 4914 4103 +a 4914 4915 4103 +a 4915 4916 4103 +a 4916 4917 4103 +a 4917 4918 4103 +a 4918 4919 4103 +a 4919 4920 4103 +a 4920 4921 4103 +a 4921 4922 4103 +a 4922 4923 4103 +a 4923 4924 4103 +a 4924 4925 4103 +a 4925 4926 4103 +a 4926 4927 4103 +a 4927 4928 4103 +a 4928 4929 4103 +a 4929 4930 4103 +a 4930 4931 4103 +a 4931 4932 4103 +a 4932 4933 4103 +a 4933 4934 4103 +a 4934 4935 4103 +a 4935 4936 4103 +a 4936 4937 4103 +a 4937 4938 4103 +a 4938 4939 4103 +a 4939 4940 4103 +a 4940 4941 4103 +a 4941 4942 4103 +a 4942 4943 4103 +a 4943 4944 4103 +a 4944 4945 4103 +a 4945 4946 4103 +a 4946 4947 4103 +a 4947 4948 4103 +a 4948 4949 4103 +a 4949 4950 4103 +a 4950 4951 4103 +a 4951 4952 4103 +a 4952 4953 4103 +a 4953 4954 4103 +a 4954 4955 4103 +a 4955 4956 4103 +a 4956 4957 4103 +a 4957 4958 4103 +a 4958 4959 4103 +a 4959 4960 4103 +a 4960 4961 4103 +a 4961 4962 4103 +a 4962 4963 4103 +a 4963 4964 4103 +a 4964 4965 4103 +a 4965 4966 4103 +a 4966 4967 4103 +a 4967 4968 4103 +a 4968 4969 4103 +a 4969 4970 4103 +a 4970 4971 4103 +a 4971 4972 4103 +a 4972 4973 4103 +a 4973 4974 4103 +a 4974 4975 4103 +a 4975 4976 4103 +a 4976 4977 4103 +a 4977 4978 4103 +a 4978 4979 4103 +a 4979 4980 4103 +a 4980 4981 4103 +a 4981 4982 4103 +a 4982 4983 4103 +a 4983 4984 4103 +a 4984 4985 4103 +a 4985 4986 4103 +a 4986 4987 4103 +a 4987 4988 4103 +a 4988 4989 4103 +a 4989 4990 4103 +a 4990 4991 4103 +a 4991 4992 4103 +a 4992 4993 4103 +a 4993 4994 4103 +a 4994 4995 4103 +a 4995 4996 4103 +a 4996 4997 4103 +a 4997 4998 4103 +a 4998 4999 4103 +a 4999 5000 4103 +a 5000 5001 4103 +a 5001 5002 4103 +a 5002 5003 4103 +a 5003 5004 4103 +a 5004 5005 4103 +a 5005 5006 4103 +a 5006 5007 4103 +a 5007 5008 4103 +a 5008 5009 4103 +a 5009 5010 4103 +a 5010 5011 4103 +a 5011 5012 4103 +a 5012 5013 4103 +a 5013 5014 4103 +a 5014 5015 4103 +a 5015 5016 4103 +a 5016 5017 4103 +a 5017 5018 4103 +a 5018 5019 4103 +a 5019 5020 4103 +a 5020 5021 4103 +a 5021 5022 4103 +a 5022 5023 4103 +a 5023 5024 4103 +a 5024 5025 4103 +a 5025 5026 4103 +a 5026 5027 4103 +a 5027 5028 4103 +a 5028 5029 4103 +a 5029 5030 4103 +a 5030 5031 4103 +a 5031 5032 4103 +a 5032 5033 4103 +a 5033 5034 4103 +a 5034 5035 4103 +a 5035 5036 4103 +a 5036 5037 4103 +a 5037 5038 4103 +a 5038 5039 4103 +a 5039 5040 4103 +a 5040 5041 4103 +a 5041 5042 4103 +a 5042 5043 4103 +a 5043 5044 4103 +a 5044 5045 4103 +a 5045 5046 4103 +a 5046 5047 4103 +a 5047 5048 4103 +a 5048 5049 4103 +a 5049 5050 4103 +a 5050 5051 4103 +a 5051 5052 4103 +a 5052 5053 4103 +a 5053 5054 4103 +a 5054 5055 4103 +a 5055 5056 4103 +a 5056 5057 4103 +a 5057 5058 4103 +a 5058 5059 4103 +a 5059 5060 4103 +a 5060 5061 4103 +a 5061 5062 4103 +a 5062 5063 4103 +a 5063 5064 4103 +a 5064 5065 4103 +a 5065 5066 4103 +a 5066 5067 4103 +a 5067 5068 4103 +a 5068 5069 4103 +a 5069 5070 4103 +a 5070 5071 4103 +a 5071 5072 4103 +a 5072 5073 4103 +a 5073 5074 4103 +a 5074 5075 4103 +a 5075 5076 4103 +a 5076 5077 4103 +a 5077 5078 4103 +a 5078 5079 4103 +a 5079 5080 4103 +a 5080 5081 4103 +a 5081 5082 4103 +a 5082 5083 4103 +a 5083 5084 4103 +a 5084 5085 4103 +a 5085 5086 4103 +a 5086 5087 4103 +a 5087 5088 4103 +a 5088 5089 4103 +a 5089 5090 4103 +a 5090 5091 4103 +a 5091 5092 4103 +a 5092 5093 4103 +a 5093 5094 4103 +a 5094 5095 4103 +a 5095 5096 4103 +a 5096 5097 4103 +a 5097 5098 4103 +a 5098 5099 4103 +a 5099 5100 4103 +a 5100 5101 4103 +a 5101 5102 4103 +a 5102 5103 4103 +a 5103 5104 4103 +a 5104 5105 4103 +a 5105 5106 4103 +a 5106 5107 4103 +a 5107 5108 4103 +a 5108 5109 4103 +a 5109 5110 4103 +a 5110 5111 4103 +a 5111 5112 4103 +a 5112 5113 4103 +a 5113 5114 4103 +a 5114 5115 4103 +a 5115 5116 4103 +a 5116 5117 4103 +a 5117 5118 4103 +a 5118 5119 4103 +a 5119 5120 4103 +a 5120 5121 4103 +a 5121 5122 4103 +a 5122 5123 4103 +a 5123 5124 4103 +a 5124 5125 4103 +a 5125 5126 4103 +a 5126 5127 4103 +a 5127 5128 4103 +a 5128 5129 4103 +a 5129 5130 4103 +a 5130 5131 4103 +a 5131 5132 4103 +a 5132 5133 4103 +a 5133 5134 4103 +a 5134 5135 4103 +a 5135 5136 4103 +a 5136 5137 4103 +a 5137 5138 4103 +a 5138 5139 4103 +a 5139 5140 4103 +a 5140 5141 4103 +a 5141 5142 4103 +a 5142 5143 4103 +a 5143 5144 4103 +a 5144 5145 4103 +a 5145 5146 4103 +a 5146 5147 4103 +a 5147 5148 4103 +a 5148 5149 4103 +a 5149 5150 4103 +a 5150 5151 4103 +a 5151 5152 4103 +a 5152 5153 4103 +a 5153 5154 4103 +a 5154 5155 4103 +a 5155 5156 4103 +a 5156 5157 4103 +a 5157 5158 4103 +a 5158 5159 4103 +a 5159 5160 4103 +a 5160 5161 4103 +a 5161 5162 4103 +a 5162 5163 4103 +a 5163 5164 4103 +a 5164 5165 4103 +a 5165 5166 4103 +a 5166 5167 4103 +a 5167 5168 4103 +a 5168 5169 4103 +a 5169 5170 4103 +a 5170 5171 4103 +a 5171 5172 4103 +a 5172 5173 4103 +a 5173 5174 4103 +a 5174 5175 4103 +a 5175 5176 4103 +a 5176 5177 4103 +a 5177 5178 4103 +a 5178 5179 4103 +a 5179 5180 4103 +a 5180 5181 4103 +a 5181 5182 4103 +a 5182 5183 4103 +a 5183 5184 4103 +a 5184 5185 4103 +a 5185 5186 4103 +a 5186 5187 4103 +a 5187 5188 4103 +a 5188 5189 4103 +a 5189 5190 4103 +a 5190 5191 4103 +a 5191 5192 4103 +a 5192 5193 4103 +a 5193 5194 4103 +a 5194 5195 4103 +a 5195 5196 4103 +a 5196 5197 4103 +a 5197 5198 4103 +a 5198 5199 4103 +a 5199 5200 4103 +a 5200 5201 4103 +a 5201 5202 4103 +a 5202 5203 4103 +a 5203 5204 4103 +a 5204 5205 4103 +a 5205 5206 4103 +a 5206 5207 4103 +a 5207 5208 4103 +a 5208 5209 4103 +a 5209 5210 4103 +a 5210 5211 4103 +a 5211 5212 4103 +a 5212 5213 4103 +a 5213 5214 4103 +a 5214 5215 4103 +a 5215 5216 4103 +a 5216 5217 4103 +a 5217 5218 4103 +a 5218 5219 4103 +a 5219 5220 4103 +a 5220 5221 4103 +a 5221 5222 4103 +a 5222 5223 4103 +a 5223 5224 4103 +a 5224 5225 4103 +a 5225 5226 4103 +a 5226 5227 4103 +a 5227 5228 4103 +a 5228 5229 4103 +a 5229 5230 4103 +a 5230 5231 4103 +a 5231 5232 4103 +a 5232 5233 4103 +a 5233 5234 4103 +a 5234 5235 4103 +a 5235 5236 4103 +a 5236 5237 4103 +a 5237 5238 4103 +a 5238 5239 4103 +a 5239 5240 4103 +a 5240 5241 4103 +a 5241 5242 4103 +a 5242 5243 4103 +a 5243 5244 4103 +a 5244 5245 4103 +a 5245 5246 4103 +a 5246 5247 4103 +a 5247 5248 4103 +a 5248 5249 4103 +a 5249 5250 4103 +a 5250 5251 4103 +a 5251 5252 4103 +a 5252 5253 4103 +a 5253 5254 4103 +a 5254 5255 4103 +a 5255 5256 4103 +a 5256 5257 4103 +a 5257 5258 4103 +a 5258 5259 4103 +a 5259 5260 4103 +a 5260 5261 4103 +a 5261 5262 4103 +a 5262 5263 4103 +a 5263 5264 4103 +a 5264 5265 4103 +a 5265 5266 4103 +a 5266 5267 4103 +a 5267 5268 4103 +a 5268 5269 4103 +a 5269 5270 4103 +a 5270 5271 4103 +a 5271 5272 4103 +a 5272 5273 4103 +a 5273 5274 4103 +a 5274 5275 4103 +a 5275 5276 4103 +a 5276 5277 4103 +a 5277 5278 4103 +a 5278 5279 4103 +a 5279 5280 4103 +a 5280 5281 4103 +a 5281 5282 4103 +a 5282 5283 4103 +a 5283 5284 4103 +a 5284 5285 4103 +a 5285 5286 4103 +a 5286 5287 4103 +a 5287 5288 4103 +a 5288 5289 4103 +a 5289 5290 4103 +a 5290 5291 4103 +a 5291 5292 4103 +a 5292 5293 4103 +a 5293 5294 4103 +a 5294 5295 4103 +a 5295 5296 4103 +a 5296 5297 4103 +a 5297 5298 4103 +a 5298 5299 4103 +a 5299 5300 4103 +a 5300 5301 4103 +a 5301 5302 4103 +a 5302 5303 4103 +a 5303 5304 4103 +a 5304 5305 4103 +a 5305 5306 4103 +a 5306 5307 4103 +a 5307 5308 4103 +a 5308 5309 4103 +a 5309 5310 4103 +a 5310 5311 4103 +a 5311 5312 4103 +a 5312 5313 4103 +a 5313 5314 4103 +a 5314 5315 4103 +a 5315 5316 4103 +a 5316 5317 4103 +a 5317 5318 4103 +a 5318 5319 4103 +a 5319 5320 4103 +a 5320 5321 4103 +a 5321 5322 4103 +a 5322 5323 4103 +a 5323 5324 4103 +a 5324 5325 4103 +a 5325 5326 4103 +a 5326 5327 4103 +a 5327 5328 4103 +a 5328 5329 4103 +a 5329 5330 4103 +a 5330 5331 4103 +a 5331 5332 4103 +a 5332 5333 4103 +a 5333 5334 4103 +a 5334 5335 4103 +a 5335 5336 4103 +a 5336 5337 4103 +a 5337 5338 4103 +a 5338 5339 4103 +a 5339 5340 4103 +a 5340 5341 4103 +a 5341 5342 4103 +a 5342 5343 4103 +a 5343 5344 4103 +a 5344 5345 4103 +a 5345 5346 4103 +a 5346 5347 4103 +a 5347 5348 4103 +a 5348 5349 4103 +a 5349 5350 4103 +a 5350 5351 4103 +a 5351 5352 4103 +a 5352 5353 4103 +a 5353 5354 4103 +a 5354 5355 4103 +a 5355 5356 4103 +a 5356 5357 4103 +a 5357 5358 4103 +a 5358 5359 4103 +a 5359 5360 4103 +a 5360 5361 4103 +a 5361 5362 4103 +a 5362 5363 4103 +a 5363 5364 4103 +a 5364 5365 4103 +a 5365 5366 4103 +a 5366 5367 4103 +a 5367 5368 4103 +a 5368 5369 4103 +a 5369 5370 4103 +a 5370 5371 4103 +a 5371 5372 4103 +a 5372 5373 4103 +a 5373 5374 4103 +a 5374 5375 4103 +a 5375 5376 4103 +a 5376 5377 4103 +a 5377 5378 4103 +a 5378 5379 4103 +a 5379 5380 4103 +a 5380 5381 4103 +a 5381 5382 4103 +a 5382 5383 4103 +a 5383 5384 4103 +a 5384 5385 4103 +a 5385 5386 4103 +a 5386 5387 4103 +a 5387 5388 4103 +a 5388 5389 4103 +a 5389 5390 4103 +a 5390 5391 4103 +a 5391 5392 4103 +a 5392 5393 4103 +a 5393 5394 4103 +a 5394 5395 4103 +a 5395 5396 4103 +a 5396 5397 4103 +a 5397 5398 4103 +a 5398 5399 4103 +a 5399 5400 4103 +a 5400 5401 4103 +a 5401 5402 4103 +a 5402 5403 4103 +a 5403 5404 4103 +a 5404 5405 4103 +a 5405 5406 4103 +a 5406 5407 4103 +a 5407 5408 4103 +a 5408 5409 4103 +a 5409 5410 4103 +a 5410 5411 4103 +a 5411 5412 4103 +a 5412 5413 4103 +a 5413 5414 4103 +a 5414 5415 4103 +a 5415 5416 4103 +a 5416 5417 4103 +a 5417 5418 4103 +a 5418 5419 4103 +a 5419 5420 4103 +a 5420 5421 4103 +a 5421 5422 4103 +a 5422 5423 4103 +a 5423 5424 4103 +a 5424 5425 4103 +a 5425 5426 4103 +a 5426 5427 4103 +a 5427 5428 4103 +a 5428 5429 4103 +a 5429 5430 4103 +a 5430 5431 4103 +a 5431 5432 4103 +a 5432 5433 4103 +a 5433 5434 4103 +a 5434 5435 4103 +a 5435 5436 4103 +a 5436 5437 4103 +a 5437 5438 4103 +a 5438 5439 4103 +a 5439 5440 4103 +a 5440 5441 4103 +a 5441 5442 4103 +a 5442 5443 4103 +a 5443 5444 4103 +a 5444 5445 4103 +a 5445 5446 4103 +a 5446 5447 4103 +a 5447 5448 4103 +a 5448 5449 4103 +a 5449 5450 4103 +a 5450 5451 4103 +a 5451 5452 4103 +a 5452 5453 4103 +a 5453 5454 4103 +a 5454 5455 4103 +a 5455 5456 4103 +a 5456 5457 4103 +a 5457 5458 4103 +a 5458 5459 4103 +a 5459 5460 4103 +a 5460 5461 4103 +a 5461 5462 4103 +a 5462 5463 4103 +a 5463 5464 4103 +a 5464 5465 4103 +a 5465 5466 4103 +a 5466 5467 4103 +a 5467 5468 4103 +a 5468 5469 4103 +a 5469 5470 4103 +a 5470 5471 4103 +a 5471 5472 4103 +a 5472 5473 4103 +a 5473 5474 4103 +a 5474 5475 4103 +a 5475 5476 4103 +a 5476 5477 4103 +a 5477 5478 4103 +a 5478 5479 4103 +a 5479 5480 4103 +a 5480 5481 4103 +a 5481 5482 4103 +a 5482 5483 4103 +a 5483 5484 4103 +a 5484 5485 4103 +a 5485 5486 4103 +a 5486 5487 4103 +a 5487 5488 4103 +a 5488 5489 4103 +a 5489 5490 4103 +a 5490 5491 4103 +a 5491 5492 4103 +a 5492 5493 4103 +a 5493 5494 4103 +a 5494 5495 4103 +a 5495 5496 4103 +a 5496 5497 4103 +a 5497 5498 4103 +a 5498 5499 4103 +a 5499 5500 4103 +a 5500 5501 4103 +a 5501 5502 4103 +a 5502 5503 4103 +a 5503 5504 4103 +a 5504 5505 4103 +a 5505 5506 4103 +a 5506 5507 4103 +a 5507 5508 4103 +a 5508 5509 4103 +a 5509 5510 4103 +a 5510 5511 4103 +a 5511 5512 4103 +a 5512 5513 4103 +a 5513 5514 4103 +a 5514 5515 4103 +a 5515 5516 4103 +a 5516 5517 4103 +a 5517 5518 4103 +a 5518 5519 4103 +a 5519 5520 4103 +a 5520 5521 4103 +a 5521 5522 4103 +a 5522 5523 4103 +a 5523 5524 4103 +a 5524 5525 4103 +a 5525 5526 4103 +a 5526 5527 4103 +a 5527 5528 4103 +a 5528 5529 4103 +a 5529 5530 4103 +a 5530 5531 4103 +a 5531 5532 4103 +a 5532 5533 4103 +a 5533 5534 4103 +a 5534 5535 4103 +a 5535 5536 4103 +a 5536 5537 4103 +a 5537 5538 4103 +a 5538 5539 4103 +a 5539 5540 4103 +a 5540 5541 4103 +a 5541 5542 4103 +a 5542 5543 4103 +a 5543 5544 4103 +a 5544 5545 4103 +a 5545 5546 4103 +a 5546 5547 4103 +a 5547 5548 4103 +a 5548 5549 4103 +a 5549 5550 4103 +a 5550 5551 4103 +a 5551 5552 4103 +a 5552 5553 4103 +a 5553 5554 4103 +a 5554 5555 4103 +a 5555 5556 4103 +a 5556 5557 4103 +a 5557 5558 4103 +a 5558 5559 4103 +a 5559 5560 4103 +a 5560 5561 4103 +a 5561 5562 4103 +a 5562 5563 4103 +a 5563 5564 4103 +a 5564 5565 4103 +a 5565 5566 4103 +a 5566 5567 4103 +a 5567 5568 4103 +a 5568 5569 4103 +a 5569 5570 4103 +a 5570 5571 4103 +a 5571 5572 4103 +a 5572 5573 4103 +a 5573 5574 4103 +a 5574 5575 4103 +a 5575 5576 4103 +a 5576 5577 4103 +a 5577 5578 4103 +a 5578 5579 4103 +a 5579 5580 4103 +a 5580 5581 4103 +a 5581 5582 4103 +a 5582 5583 4103 +a 5583 5584 4103 +a 5584 5585 4103 +a 5585 5586 4103 +a 5586 5587 4103 +a 5587 5588 4103 +a 5588 5589 4103 +a 5589 5590 4103 +a 5590 5591 4103 +a 5591 5592 4103 +a 5592 5593 4103 +a 5593 5594 4103 +a 5594 5595 4103 +a 5595 5596 4103 +a 5596 5597 4103 +a 5597 5598 4103 +a 5598 5599 4103 +a 5599 5600 4103 +a 5600 5601 4103 +a 5601 5602 4103 +a 5602 5603 4103 +a 5603 5604 4103 +a 5604 5605 4103 +a 5605 5606 4103 +a 5606 5607 4103 +a 5607 5608 4103 +a 5608 5609 4103 +a 5609 5610 4103 +a 5610 5611 4103 +a 5611 5612 4103 +a 5612 5613 4103 +a 5613 5614 4103 +a 5614 5615 4103 +a 5615 5616 4103 +a 5616 5617 4103 +a 5617 5618 4103 +a 5618 5619 4103 +a 5619 5620 4103 +a 5620 5621 4103 +a 5621 5622 4103 +a 5622 5623 4103 +a 5623 5624 4103 +a 5624 5625 4103 +a 5625 5626 4103 +a 5626 5627 4103 +a 5627 5628 4103 +a 5628 5629 4103 +a 5629 5630 4103 +a 5630 5631 4103 +a 5631 5632 4103 +a 5632 5633 4103 +a 5633 5634 4103 +a 5634 5635 4103 +a 5635 5636 4103 +a 5636 5637 4103 +a 5637 5638 4103 +a 5638 5639 4103 +a 5639 5640 4103 +a 5640 5641 4103 +a 5641 5642 4103 +a 5642 5643 4103 +a 5643 5644 4103 +a 5644 5645 4103 +a 5645 5646 4103 +a 5646 5647 4103 +a 5647 5648 4103 +a 5648 5649 4103 +a 5649 5650 4103 +a 5650 5651 4103 +a 5651 5652 4103 +a 5652 5653 4103 +a 5653 5654 4103 +a 5654 5655 4103 +a 5655 5656 4103 +a 5656 5657 4103 +a 5657 5658 4103 +a 5658 5659 4103 +a 5659 5660 4103 +a 5660 5661 4103 +a 5661 5662 4103 +a 5662 5663 4103 +a 5663 5664 4103 +a 5664 5665 4103 +a 5665 5666 4103 +a 5666 5667 4103 +a 5667 5668 4103 +a 5668 5669 4103 +a 5669 5670 4103 +a 5670 5671 4103 +a 5671 5672 4103 +a 5672 5673 4103 +a 5673 5674 4103 +a 5674 5675 4103 +a 5675 5676 4103 +a 5676 5677 4103 +a 5677 5678 4103 +a 5678 5679 4103 +a 5679 5680 4103 +a 5680 5681 4103 +a 5681 5682 4103 +a 5682 5683 4103 +a 5683 5684 4103 +a 5684 5685 4103 +a 5685 5686 4103 +a 5686 5687 4103 +a 5687 5688 4103 +a 5688 5689 4103 +a 5689 5690 4103 +a 5690 5691 4103 +a 5691 5692 4103 +a 5692 5693 4103 +a 5693 5694 4103 +a 5694 5695 4103 +a 5695 5696 4103 +a 5696 5697 4103 +a 5697 5698 4103 +a 5698 5699 4103 +a 5699 5700 4103 +a 5700 5701 4103 +a 5701 5702 4103 +a 5702 5703 4103 +a 5703 5704 4103 +a 5704 5705 4103 +a 5705 5706 4103 +a 5706 5707 4103 +a 5707 5708 4103 +a 5708 5709 4103 +a 5709 5710 4103 +a 5710 5711 4103 +a 5711 5712 4103 +a 5712 5713 4103 +a 5713 5714 4103 +a 5714 5715 4103 +a 5715 5716 4103 +a 5716 5717 4103 +a 5717 5718 4103 +a 5718 5719 4103 +a 5719 5720 4103 +a 5720 5721 4103 +a 5721 5722 4103 +a 5722 5723 4103 +a 5723 5724 4103 +a 5724 5725 4103 +a 5725 5726 4103 +a 5726 5727 4103 +a 5727 5728 4103 +a 5728 5729 4103 +a 5729 5730 4103 +a 5730 5731 4103 +a 5731 5732 4103 +a 5732 5733 4103 +a 5733 5734 4103 +a 5734 5735 4103 +a 5735 5736 4103 +a 5736 5737 4103 +a 5737 5738 4103 +a 5738 5739 4103 +a 5739 5740 4103 +a 5740 5741 4103 +a 5741 5742 4103 +a 5742 5743 4103 +a 5743 5744 4103 +a 5744 5745 4103 +a 5745 5746 4103 +a 5746 5747 4103 +a 5747 5748 4103 +a 5748 5749 4103 +a 5749 5750 4103 +a 5750 5751 4103 +a 5751 5752 4103 +a 5752 5753 4103 +a 5753 5754 4103 +a 5754 5755 4103 +a 5755 5756 4103 +a 5756 5757 4103 +a 5757 5758 4103 +a 5758 5759 4103 +a 5759 5760 4103 +a 5760 5761 4103 +a 5761 5762 4103 +a 5762 5763 4103 +a 5763 5764 4103 +a 5764 5765 4103 +a 5765 5766 4103 +a 5766 5767 4103 +a 5767 5768 4103 +a 5768 5769 4103 +a 5769 5770 4103 +a 5770 5771 4103 +a 5771 5772 4103 +a 5772 5773 4103 +a 5773 5774 4103 +a 5774 5775 4103 +a 5775 5776 4103 +a 5776 5777 4103 +a 5777 5778 4103 +a 5778 5779 4103 +a 5779 5780 4103 +a 5780 5781 4103 +a 5781 5782 4103 +a 5782 5783 4103 +a 5783 5784 4103 +a 5784 5785 4103 +a 5785 5786 4103 +a 5786 5787 4103 +a 5787 5788 4103 +a 5788 5789 4103 +a 5789 5790 4103 +a 5790 5791 4103 +a 5791 5792 4103 +a 5792 5793 4103 +a 5793 5794 4103 +a 5794 5795 4103 +a 5795 5796 4103 +a 5796 5797 4103 +a 5797 5798 4103 +a 5798 5799 4103 +a 5799 5800 4103 +a 5800 5801 4103 +a 5801 5802 4103 +a 5802 5803 4103 +a 5803 5804 4103 +a 5804 5805 4103 +a 5805 5806 4103 +a 5806 5807 4103 +a 5807 5808 4103 +a 5808 5809 4103 +a 5809 5810 4103 +a 5810 5811 4103 +a 5811 5812 4103 +a 5812 5813 4103 +a 5813 5814 4103 +a 5814 5815 4103 +a 5815 5816 4103 +a 5816 5817 4103 +a 5817 5818 4103 +a 5818 5819 4103 +a 5819 5820 4103 +a 5820 5821 4103 +a 5821 5822 4103 +a 5822 5823 4103 +a 5823 5824 4103 +a 5824 5825 4103 +a 5825 5826 4103 +a 5826 5827 4103 +a 5827 5828 4103 +a 5828 5829 4103 +a 5829 5830 4103 +a 5830 5831 4103 +a 5831 5832 4103 +a 5832 5833 4103 +a 5833 5834 4103 +a 5834 5835 4103 +a 5835 5836 4103 +a 5836 5837 4103 +a 5837 5838 4103 +a 5838 5839 4103 +a 5839 5840 4103 +a 5840 5841 4103 +a 5841 5842 4103 +a 5842 5843 4103 +a 5843 5844 4103 +a 5844 5845 4103 +a 5845 5846 4103 +a 5846 5847 4103 +a 5847 5848 4103 +a 5848 5849 4103 +a 5849 5850 4103 +a 5850 5851 4103 +a 5851 5852 4103 +a 5852 5853 4103 +a 5853 5854 4103 +a 5854 5855 4103 +a 5855 5856 4103 +a 5856 5857 4103 +a 5857 5858 4103 +a 5858 5859 4103 +a 5859 5860 4103 +a 5860 5861 4103 +a 5861 5862 4103 +a 5862 5863 4103 +a 5863 5864 4103 +a 5864 5865 4103 +a 5865 5866 4103 +a 5866 5867 4103 +a 5867 5868 4103 +a 5868 5869 4103 +a 5869 5870 4103 +a 5870 5871 4103 +a 5871 5872 4103 +a 5872 5873 4103 +a 5873 5874 4103 +a 5874 5875 4103 +a 5875 5876 4103 +a 5876 5877 4103 +a 5877 5878 4103 +a 5878 5879 4103 +a 5879 5880 4103 +a 5880 5881 4103 +a 5881 5882 4103 +a 5882 5883 4103 +a 5883 5884 4103 +a 5884 5885 4103 +a 5885 5886 4103 +a 5886 5887 4103 +a 5887 5888 4103 +a 5888 5889 4103 +a 5889 5890 4103 +a 5890 5891 4103 +a 5891 5892 4103 +a 5892 5893 4103 +a 5893 5894 4103 +a 5894 5895 4103 +a 5895 5896 4103 +a 5896 5897 4103 +a 5897 5898 4103 +a 5898 5899 4103 +a 5899 5900 4103 +a 5900 5901 4103 +a 5901 5902 4103 +a 5902 5903 4103 +a 5903 5904 4103 +a 5904 5905 4103 +a 5905 5906 4103 +a 5906 5907 4103 +a 5907 5908 4103 +a 5908 5909 4103 +a 5909 5910 4103 +a 5910 5911 4103 +a 5911 5912 4103 +a 5912 5913 4103 +a 5913 5914 4103 +a 5914 5915 4103 +a 5915 5916 4103 +a 5916 5917 4103 +a 5917 5918 4103 +a 5918 5919 4103 +a 5919 5920 4103 +a 5920 5921 4103 +a 5921 5922 4103 +a 5922 5923 4103 +a 5923 5924 4103 +a 5924 5925 4103 +a 5925 5926 4103 +a 5926 5927 4103 +a 5927 5928 4103 +a 5928 5929 4103 +a 5929 5930 4103 +a 5930 5931 4103 +a 5931 5932 4103 +a 5932 5933 4103 +a 5933 5934 4103 +a 5934 5935 4103 +a 5935 5936 4103 +a 5936 5937 4103 +a 5937 5938 4103 +a 5938 5939 4103 +a 5939 5940 4103 +a 5940 5941 4103 +a 5941 5942 4103 +a 5942 5943 4103 +a 5943 5944 4103 +a 5944 5945 4103 +a 5945 5946 4103 +a 5946 5947 4103 +a 5947 5948 4103 +a 5948 5949 4103 +a 5949 5950 4103 +a 5950 5951 4103 +a 5951 5952 4103 +a 5952 5953 4103 +a 5953 5954 4103 +a 5954 5955 4103 +a 5955 5956 4103 +a 5956 5957 4103 +a 5957 5958 4103 +a 5958 5959 4103 +a 5959 5960 4103 +a 5960 5961 4103 +a 5961 5962 4103 +a 5962 5963 4103 +a 5963 5964 4103 +a 5964 5965 4103 +a 5965 5966 4103 +a 5966 5967 4103 +a 5967 5968 4103 +a 5968 5969 4103 +a 5969 5970 4103 +a 5970 5971 4103 +a 5971 5972 4103 +a 5972 5973 4103 +a 5973 5974 4103 +a 5974 5975 4103 +a 5975 5976 4103 +a 5976 5977 4103 +a 5977 5978 4103 +a 5978 5979 4103 +a 5979 5980 4103 +a 5980 5981 4103 +a 5981 5982 4103 +a 5982 5983 4103 +a 5983 5984 4103 +a 5984 5985 4103 +a 5985 5986 4103 +a 5986 5987 4103 +a 5987 5988 4103 +a 5988 5989 4103 +a 5989 5990 4103 +a 5990 5991 4103 +a 5991 5992 4103 +a 5992 5993 4103 +a 5993 5994 4103 +a 5994 5995 4103 +a 5995 5996 4103 +a 5996 5997 4103 +a 5997 5998 4103 +a 5998 5999 4103 +a 5999 6000 4103 +a 6000 6001 4103 +a 6001 6002 4103 +a 6002 6003 4103 +a 6003 6004 4103 +a 6004 6005 4103 +a 6005 6006 4103 +a 6006 6007 4103 +a 6007 6008 4103 +a 6008 6009 4103 +a 6009 6010 4103 +a 6010 6011 4103 +a 6011 6012 4103 +a 6012 6013 4103 +a 6013 6014 4103 +a 6014 6015 4103 +a 6015 6016 4103 +a 6016 6017 4103 +a 6017 6018 4103 +a 6018 6019 4103 +a 6019 6020 4103 +a 6020 6021 4103 +a 6021 6022 4103 +a 6022 6023 4103 +a 6023 6024 4103 +a 6024 6025 4103 +a 6025 6026 4103 +a 6026 6027 4103 +a 6027 6028 4103 +a 6028 6029 4103 +a 6029 6030 4103 +a 6030 6031 4103 +a 6031 6032 4103 +a 6032 6033 4103 +a 6033 6034 4103 +a 6034 6035 4103 +a 6035 6036 4103 +a 6036 6037 4103 +a 6037 6038 4103 +a 6038 6039 4103 +a 6039 6040 4103 +a 6040 6041 4103 +a 6041 6042 4103 +a 6042 6043 4103 +a 6043 6044 4103 +a 6044 6045 4103 +a 6045 6046 4103 +a 6046 6047 4103 +a 6047 6048 4103 +a 6048 6049 4103 +a 6049 6050 4103 +a 6050 6051 4103 +a 6051 6052 4103 +a 6052 6053 4103 +a 6053 6054 4103 +a 6054 6055 4103 +a 6055 6056 4103 +a 6056 6057 4103 +a 6057 6058 4103 +a 6058 6059 4103 +a 6059 6060 4103 +a 6060 6061 4103 +a 6061 6062 4103 +a 6062 6063 4103 +a 6063 6064 4103 +a 6064 6065 4103 +a 6065 6066 4103 +a 6066 6067 4103 +a 6067 6068 4103 +a 6068 6069 4103 +a 6069 6070 4103 +a 6070 6071 4103 +a 6071 6072 4103 +a 6072 6073 4103 +a 6073 6074 4103 +a 6074 6075 4103 +a 6075 6076 4103 +a 6076 6077 4103 +a 6077 6078 4103 +a 6078 6079 4103 +a 6079 6080 4103 +a 6080 6081 4103 +a 6081 6082 4103 +a 6082 6083 4103 +a 6083 6084 4103 +a 6084 6085 4103 +a 6085 6086 4103 +a 6086 6087 4103 +a 6087 6088 4103 +a 6088 6089 4103 +a 6089 6090 4103 +a 6090 6091 4103 +a 6091 6092 4103 +a 6092 6093 4103 +a 6093 6094 4103 +a 6094 6095 4103 +a 6095 6096 4103 +a 6096 6097 4103 +a 6097 6098 4103 +a 6098 6099 4103 +a 6099 6100 4103 +a 6100 6101 4103 +a 6101 6102 4103 +a 6102 6103 4103 +a 6103 6104 4103 +a 6104 6105 4103 +a 6105 6106 4103 +a 6106 6107 4103 +a 6107 6108 4103 +a 6108 6109 4103 +a 6109 6110 4103 +a 6110 6111 4103 +a 6111 6112 4103 +a 6112 6113 4103 +a 6113 6114 4103 +a 6114 6115 4103 +a 6115 6116 4103 +a 6116 6117 4103 +a 6117 6118 4103 +a 6118 6119 4103 +a 6119 6120 4103 +a 6120 6121 4103 +a 6121 6122 4103 +a 6122 6123 4103 +a 6123 6124 4103 +a 6124 6125 4103 +a 6125 6126 4103 +a 6126 6127 4103 +a 6127 6128 4103 +a 6128 6129 4103 +a 6129 6130 4103 +a 6130 6131 4103 +a 6131 6132 4103 +a 6132 6133 4103 +a 6133 6134 4103 +a 6134 6135 4103 +a 6135 6136 4103 +a 6136 6137 4103 +a 6137 6138 4103 +a 6138 6139 4103 +a 6139 6140 4103 +a 6140 6141 4103 +a 6141 6142 4103 +a 6142 6143 4103 +a 6143 6144 4103 +a 6144 6145 4103 +a 6145 6146 4103 +a 6146 6147 4103 +a 6147 6148 4103 +a 6148 6149 4103 +a 6149 6150 4103 +a 6150 6151 4103 +a 6151 6152 4103 +a 6152 6153 4103 +a 6153 6154 4103 +a 6154 6155 4103 +a 6155 6156 4103 +a 6156 6157 4103 +a 6157 6158 4103 +a 6158 6159 4103 +a 6159 6160 4103 +a 6160 6161 4103 +a 6161 6162 4103 +a 6162 6163 4103 +a 6163 6164 4103 +a 6164 6165 4103 +a 6165 6166 4103 +a 6166 6167 4103 +a 6167 6168 4103 +a 6168 6169 4103 +a 6169 6170 4103 +a 6170 6171 4103 +a 6171 6172 4103 +a 6172 6173 4103 +a 6173 6174 4103 +a 6174 6175 4103 +a 6175 6176 4103 +a 6176 6177 4103 +a 6177 6178 4103 +a 6178 6179 4103 +a 6179 6180 4103 +a 6180 6181 4103 +a 6181 6182 4103 +a 6182 6183 4103 +a 6183 6184 4103 +a 6184 6185 4103 +a 6185 6186 4103 +a 6186 6187 4103 +a 6187 6188 4103 +a 6188 6189 4103 +a 6189 6190 4103 +a 6190 6191 4103 +a 6191 6192 4103 +a 6192 6193 4103 +a 6193 6194 4103 +a 6194 6195 4103 +a 6195 6196 4103 +a 6196 6197 4103 +a 6197 6198 4103 +a 6198 6199 4103 +a 6199 6200 4103 +a 6200 6201 4103 +a 6201 6202 4103 +a 6202 6203 4103 +a 6203 6204 4103 +a 6204 6205 4103 +a 6205 6206 4103 +a 6206 6207 4103 +a 6207 6208 4103 +a 6208 6209 4103 +a 6209 6210 4103 +a 6210 6211 4103 +a 6211 6212 4103 +a 6212 6213 4103 +a 6213 6214 4103 +a 6214 6215 4103 +a 6215 6216 4103 +a 6216 6217 4103 +a 6217 6218 4103 +a 6218 6219 4103 +a 6219 6220 4103 +a 6220 6221 4103 +a 6221 6222 4103 +a 6222 6223 4103 +a 6223 6224 4103 +a 6224 6225 4103 +a 6225 6226 4103 +a 6226 6227 4103 +a 6227 6228 4103 +a 6228 6229 4103 +a 6229 6230 4103 +a 6230 6231 4103 +a 6231 6232 4103 +a 6232 6233 4103 +a 6233 6234 4103 +a 6234 6235 4103 +a 6235 6236 4103 +a 6236 6237 4103 +a 6237 6238 4103 +a 6238 6239 4103 +a 6239 6240 4103 +a 6240 6241 4103 +a 6241 6242 4103 +a 6242 6243 4103 +a 6243 6244 4103 +a 6244 6245 4103 +a 6245 6246 4103 +a 6246 6247 4103 +a 6247 6248 4103 +a 6248 6249 4103 +a 6249 6250 4103 +a 6250 6251 4103 +a 6251 6252 4103 +a 6252 6253 4103 +a 6253 6254 4103 +a 6254 6255 4103 +a 6255 6256 4103 +a 6256 6257 4103 +a 6257 6258 4103 +a 6258 6259 4103 +a 6259 6260 4103 +a 6260 6261 4103 +a 6261 6262 4103 +a 6262 6263 4103 +a 6263 6264 4103 +a 6264 6265 4103 +a 6265 6266 4103 +a 6266 6267 4103 +a 6267 6268 4103 +a 6268 6269 4103 +a 6269 6270 4103 +a 6270 6271 4103 +a 6271 6272 4103 +a 6272 6273 4103 +a 6273 6274 4103 +a 6274 6275 4103 +a 6275 6276 4103 +a 6276 6277 4103 +a 6277 6278 4103 +a 6278 6279 4103 +a 6279 6280 4103 +a 6280 6281 4103 +a 6281 6282 4103 +a 6282 6283 4103 +a 6283 6284 4103 +a 6284 6285 4103 +a 6285 6286 4103 +a 6286 6287 4103 +a 6287 6288 4103 +a 6288 6289 4103 +a 6289 6290 4103 +a 6290 6291 4103 +a 6291 6292 4103 +a 6292 6293 4103 +a 6293 6294 4103 +a 6294 6295 4103 +a 6295 6296 4103 +a 6296 6297 4103 +a 6297 6298 4103 +a 6298 6299 4103 +a 6299 6300 4103 +a 6300 6301 4103 +a 6301 6302 4103 +a 6302 6303 4103 +a 6303 6304 4103 +a 6304 6305 4103 +a 6305 6306 4103 +a 6306 6307 4103 +a 6307 6308 4103 +a 6308 6309 4103 +a 6309 6310 4103 +a 6310 6311 4103 +a 6311 6312 4103 +a 6312 6313 4103 +a 6313 6314 4103 +a 6314 6315 4103 +a 6315 6316 4103 +a 6316 6317 4103 +a 6317 6318 4103 +a 6318 6319 4103 +a 6319 6320 4103 +a 6320 6321 4103 +a 6321 6322 4103 +a 6322 6323 4103 +a 6323 6324 4103 +a 6324 6325 4103 +a 6325 6326 4103 +a 6326 6327 4103 +a 6327 6328 4103 +a 6328 6329 4103 +a 6329 6330 4103 +a 6330 6331 4103 +a 6331 6332 4103 +a 6332 6333 4103 +a 6333 6334 4103 +a 6334 6335 4103 +a 6335 6336 4103 +a 6336 6337 4103 +a 6337 6338 4103 +a 6338 6339 4103 +a 6339 6340 4103 +a 6340 6341 4103 +a 6341 6342 4103 +a 6342 6343 4103 +a 6343 6344 4103 +a 6344 6345 4103 +a 6345 6346 4103 +a 6346 6347 4103 +a 6347 6348 4103 +a 6348 6349 4103 +a 6349 6350 4103 +a 6350 6351 4103 +a 6351 6352 4103 +a 6352 6353 4103 +a 6353 6354 4103 +a 6354 6355 4103 +a 6355 6356 4103 +a 6356 6357 4103 +a 6357 6358 4103 +a 6358 6359 4103 +a 6359 6360 4103 +a 6360 6361 4103 +a 6361 6362 4103 +a 6362 6363 4103 +a 6363 6364 4103 +a 6364 6365 4103 +a 6365 6366 4103 +a 6366 6367 4103 +a 6367 6368 4103 +a 6368 6369 4103 +a 6369 6370 4103 +a 6370 6371 4103 +a 6371 6372 4103 +a 6372 6373 4103 +a 6373 6374 4103 +a 6374 6375 4103 +a 6375 6376 4103 +a 6376 6377 4103 +a 6377 6378 4103 +a 6378 6379 4103 +a 6379 6380 4103 +a 6380 6381 4103 +a 6381 6382 4103 +a 6382 6383 4103 +a 6383 6384 4103 +a 6384 6385 4103 +a 6385 6386 4103 +a 6386 6387 4103 +a 6387 6388 4103 +a 6388 6389 4103 +a 6389 6390 4103 +a 6390 6391 4103 +a 6391 6392 4103 +a 6392 6393 4103 +a 6393 6394 4103 +a 6394 6395 4103 +a 6395 6396 4103 +a 6396 6397 4103 +a 6397 6398 4103 +a 6398 6399 4103 +a 6399 6400 4103 +a 6400 6401 4103 +a 6401 6402 4103 +a 6402 6403 4103 +a 6403 6404 4103 +a 6404 6405 4103 +a 6405 6406 4103 +a 6406 6407 4103 +a 6407 6408 4103 +a 6408 6409 4103 +a 6409 6410 4103 +a 6410 6411 4103 +a 6411 6412 4103 +a 6412 6413 4103 +a 6413 6414 4103 +a 6414 6415 4103 +a 6415 6416 4103 +a 6416 6417 4103 +a 6417 6418 4103 +a 6418 6419 4103 +a 6419 6420 4103 +a 6420 6421 4103 +a 6421 6422 4103 +a 6422 6423 4103 +a 6423 6424 4103 +a 6424 6425 4103 +a 6425 6426 4103 +a 6426 6427 4103 +a 6427 6428 4103 +a 6428 6429 4103 +a 6429 6430 4103 +a 6430 6431 4103 +a 6431 6432 4103 +a 6432 6433 4103 +a 6433 6434 4103 +a 6434 6435 4103 +a 6435 6436 4103 +a 6436 6437 4103 +a 6437 6438 4103 +a 6438 6439 4103 +a 6439 6440 4103 +a 6440 6441 4103 +a 6441 6442 4103 +a 6442 6443 4103 +a 6443 6444 4103 +a 6444 6445 4103 +a 6445 6446 4103 +a 6446 6447 4103 +a 6447 6448 4103 +a 6448 6449 4103 +a 6449 6450 4103 +a 6450 6451 4103 +a 6451 6452 4103 +a 6452 6453 4103 +a 6453 6454 4103 +a 6454 6455 4103 +a 6455 6456 4103 +a 6456 6457 4103 +a 6457 6458 4103 +a 6458 6459 4103 +a 6459 6460 4103 +a 6460 6461 4103 +a 6461 6462 4103 +a 6462 6463 4103 +a 6463 6464 4103 +a 6464 6465 4103 +a 6465 6466 4103 +a 6466 6467 4103 +a 6467 6468 4103 +a 6468 6469 4103 +a 6469 6470 4103 +a 6470 6471 4103 +a 6471 6472 4103 +a 6472 6473 4103 +a 6473 6474 4103 +a 6474 6475 4103 +a 6475 6476 4103 +a 6476 6477 4103 +a 6477 6478 4103 +a 6478 6479 4103 +a 6479 6480 4103 +a 6480 6481 4103 +a 6481 6482 4103 +a 6482 6483 4103 +a 6483 6484 4103 +a 6484 6485 4103 +a 6485 6486 4103 +a 6486 6487 4103 +a 6487 6488 4103 +a 6488 6489 4103 +a 6489 6490 4103 +a 6490 6491 4103 +a 6491 6492 4103 +a 6492 6493 4103 +a 6493 6494 4103 +a 6494 6495 4103 +a 6495 6496 4103 +a 6496 6497 4103 +a 6497 6498 4103 +a 6498 6499 4103 +a 6499 6500 4103 +a 6500 6501 4103 +a 6501 6502 4103 +a 6502 6503 4103 +a 6503 6504 4103 +a 6504 6505 4103 +a 6505 6506 4103 +a 6506 6507 4103 +a 6507 6508 4103 +a 6508 6509 4103 +a 6509 6510 4103 +a 6510 6511 4103 +a 6511 6512 4103 +a 6512 6513 4103 +a 6513 6514 4103 +a 6514 6515 4103 +a 6515 6516 4103 +a 6516 6517 4103 +a 6517 6518 4103 +a 6518 6519 4103 +a 6519 6520 4103 +a 6520 6521 4103 +a 6521 6522 4103 +a 6522 6523 4103 +a 6523 6524 4103 +a 6524 6525 4103 +a 6525 6526 4103 +a 6526 6527 4103 +a 6527 6528 4103 +a 6528 6529 4103 +a 6529 6530 4103 +a 6530 6531 4103 +a 6531 6532 4103 +a 6532 6533 4103 +a 6533 6534 4103 +a 6534 6535 4103 +a 6535 6536 4103 +a 6536 6537 4103 +a 6537 6538 4103 +a 6538 6539 4103 +a 6539 6540 4103 +a 6540 6541 4103 +a 6541 6542 4103 +a 6542 6543 4103 +a 6543 6544 4103 +a 6544 6545 4103 +a 6545 6546 4103 +a 6546 6547 4103 +a 6547 6548 4103 +a 6548 6549 4103 +a 6549 6550 4103 +a 6550 6551 4103 +a 6551 6552 4103 +a 6552 6553 4103 +a 6553 6554 4103 +a 6554 6555 4103 +a 6555 6556 4103 +a 6556 6557 4103 +a 6557 6558 4103 +a 6558 6559 4103 +a 6559 6560 4103 +a 6560 6561 4103 +a 6561 6562 4103 +a 6562 6563 4103 +a 6563 6564 4103 +a 6564 6565 4103 +a 6565 6566 4103 +a 6566 6567 4103 +a 6567 6568 4103 +a 6568 6569 4103 +a 6569 6570 4103 +a 6570 6571 4103 +a 6571 6572 4103 +a 6572 6573 4103 +a 6573 6574 4103 +a 6574 6575 4103 +a 6575 6576 4103 +a 6576 6577 4103 +a 6577 6578 4103 +a 6578 6579 4103 +a 6579 6580 4103 +a 6580 6581 4103 +a 6581 6582 4103 +a 6582 6583 4103 +a 6583 6584 4103 +a 6584 6585 4103 +a 6585 6586 4103 +a 6586 6587 4103 +a 6587 6588 4103 +a 6588 6589 4103 +a 6589 6590 4103 +a 6590 6591 4103 +a 6591 6592 4103 +a 6592 6593 4103 +a 6593 6594 4103 +a 6594 6595 4103 +a 6595 6596 4103 +a 6596 6597 4103 +a 6597 6598 4103 +a 6598 6599 4103 +a 6599 6600 4103 +a 6600 6601 4103 +a 6601 6602 4103 +a 6602 6603 4103 +a 6603 6604 4103 +a 6604 6605 4103 +a 6605 6606 4103 +a 6606 6607 4103 +a 6607 6608 4103 +a 6608 6609 4103 +a 6609 6610 4103 +a 6610 6611 4103 +a 6611 6612 4103 +a 6612 6613 4103 +a 6613 6614 4103 +a 6614 6615 4103 +a 6615 6616 4103 +a 6616 6617 4103 +a 6617 6618 4103 +a 6618 6619 4103 +a 6619 6620 4103 +a 6620 6621 4103 +a 6621 6622 4103 +a 6622 6623 4103 +a 6623 6624 4103 +a 6624 6625 4103 +a 6625 6626 4103 +a 6626 6627 4103 +a 6627 6628 4103 +a 6628 6629 4103 +a 6629 6630 4103 +a 6630 6631 4103 +a 6631 6632 4103 +a 6632 6633 4103 +a 6633 6634 4103 +a 6634 6635 4103 +a 6635 6636 4103 +a 6636 6637 4103 +a 6637 6638 4103 +a 6638 6639 4103 +a 6639 6640 4103 +a 6640 6641 4103 +a 6641 6642 4103 +a 6642 6643 4103 +a 6643 6644 4103 +a 6644 6645 4103 +a 6645 6646 4103 +a 6646 6647 4103 +a 6647 6648 4103 +a 6648 6649 4103 +a 6649 6650 4103 +a 6650 6651 4103 +a 6651 6652 4103 +a 6652 6653 4103 +a 6653 6654 4103 +a 6654 6655 4103 +a 6655 6656 4103 +a 6656 6657 4103 +a 6657 6658 4103 +a 6658 6659 4103 +a 6659 6660 4103 +a 6660 6661 4103 +a 6661 6662 4103 +a 6662 6663 4103 +a 6663 6664 4103 +a 6664 6665 4103 +a 6665 6666 4103 +a 6666 6667 4103 +a 6667 6668 4103 +a 6668 6669 4103 +a 6669 6670 4103 +a 6670 6671 4103 +a 6671 6672 4103 +a 6672 6673 4103 +a 6673 6674 4103 +a 6674 6675 4103 +a 6675 6676 4103 +a 6676 6677 4103 +a 6677 6678 4103 +a 6678 6679 4103 +a 6679 6680 4103 +a 6680 6681 4103 +a 6681 6682 4103 +a 6682 6683 4103 +a 6683 6684 4103 +a 6684 6685 4103 +a 6685 6686 4103 +a 6686 6687 4103 +a 6687 6688 4103 +a 6688 6689 4103 +a 6689 6690 4103 +a 6690 6691 4103 +a 6691 6692 4103 +a 6692 6693 4103 +a 6693 6694 4103 +a 6694 6695 4103 +a 6695 6696 4103 +a 6696 6697 4103 +a 6697 6698 4103 +a 6698 6699 4103 +a 6699 6700 4103 +a 6700 6701 4103 +a 6701 6702 4103 +a 6702 6703 4103 +a 6703 6704 4103 +a 6704 6705 4103 +a 6705 6706 4103 +a 6706 6707 4103 +a 6707 6708 4103 +a 6708 6709 4103 +a 6709 6710 4103 +a 6710 6711 4103 +a 6711 6712 4103 +a 6712 6713 4103 +a 6713 6714 4103 +a 6714 6715 4103 +a 6715 6716 4103 +a 6716 6717 4103 +a 6717 6718 4103 +a 6718 6719 4103 +a 6719 6720 4103 +a 6720 6721 4103 +a 6721 6722 4103 +a 6722 6723 4103 +a 6723 6724 4103 +a 6724 6725 4103 +a 6725 6726 4103 +a 6726 6727 4103 +a 6727 6728 4103 +a 6728 6729 4103 +a 6729 6730 4103 +a 6730 6731 4103 +a 6731 6732 4103 +a 6732 6733 4103 +a 6733 6734 4103 +a 6734 6735 4103 +a 6735 6736 4103 +a 6736 6737 4103 +a 6737 6738 4103 +a 6738 6739 4103 +a 6739 6740 4103 +a 6740 6741 4103 +a 6741 6742 4103 +a 6742 6743 4103 +a 6743 6744 4103 +a 6744 6745 4103 +a 6745 6746 4103 +a 6746 6747 4103 +a 6747 6748 4103 +a 6748 6749 4103 +a 6749 6750 4103 +a 6750 6751 4103 +a 6751 6752 4103 +a 6752 6753 4103 +a 6753 6754 4103 +a 6754 6755 4103 +a 6755 6756 4103 +a 6756 6757 4103 +a 6757 6758 4103 +a 6758 6759 4103 +a 6759 6760 4103 +a 6760 6761 4103 +a 6761 6762 4103 +a 6762 6763 4103 +a 6763 6764 4103 +a 6764 6765 4103 +a 6765 6766 4103 +a 6766 6767 4103 +a 6767 6768 4103 +a 6768 6769 4103 +a 6769 6770 4103 +a 6770 6771 4103 +a 6771 6772 4103 +a 6772 6773 4103 +a 6773 6774 4103 +a 6774 6775 4103 +a 6775 6776 4103 +a 6776 6777 4103 +a 6777 6778 4103 +a 6778 6779 4103 +a 6779 6780 4103 +a 6780 6781 4103 +a 6781 6782 4103 +a 6782 6783 4103 +a 6783 6784 4103 +a 6784 6785 4103 +a 6785 6786 4103 +a 6786 6787 4103 +a 6787 6788 4103 +a 6788 6789 4103 +a 6789 6790 4103 +a 6790 6791 4103 +a 6791 6792 4103 +a 6792 6793 4103 +a 6793 6794 4103 +a 6794 6795 4103 +a 6795 6796 4103 +a 6796 6797 4103 +a 6797 6798 4103 +a 6798 6799 4103 +a 6799 6800 4103 +a 6800 6801 4103 +a 6801 6802 4103 +a 6802 6803 4103 +a 6803 6804 4103 +a 6804 6805 4103 +a 6805 6806 4103 +a 6806 6807 4103 +a 6807 6808 4103 +a 6808 6809 4103 +a 6809 6810 4103 +a 6810 6811 4103 +a 6811 6812 4103 +a 6812 6813 4103 +a 6813 6814 4103 +a 6814 6815 4103 +a 6815 6816 4103 +a 6816 6817 4103 +a 6817 6818 4103 +a 6818 6819 4103 +a 6819 6820 4103 +a 6820 6821 4103 +a 6821 6822 4103 +a 6822 6823 4103 +a 6823 6824 4103 +a 6824 6825 4103 +a 6825 6826 4103 +a 6826 6827 4103 +a 6827 6828 4103 +a 6828 6829 4103 +a 6829 6830 4103 +a 6830 6831 4103 +a 6831 6832 4103 +a 6832 6833 4103 +a 6833 6834 4103 +a 6834 6835 4103 +a 6835 6836 4103 +a 6836 6837 4103 +a 6837 6838 4103 +a 6838 6839 4103 +a 6839 6840 4103 +a 6840 6841 4103 +a 6841 6842 4103 +a 6842 6843 4103 +a 6843 6844 4103 +a 6844 6845 4103 +a 6845 6846 4103 +a 6846 6847 4103 +a 6847 6848 4103 +a 6848 6849 4103 +a 6849 6850 4103 +a 6850 6851 4103 +a 6851 6852 4103 +a 6852 6853 4103 +a 6853 6854 4103 +a 6854 6855 4103 +a 6855 6856 4103 +a 6856 6857 4103 +a 6857 6858 4103 +a 6858 6859 4103 +a 6859 6860 4103 +a 6860 6861 4103 +a 6861 6862 4103 +a 6862 6863 4103 +a 6863 6864 4103 +a 6864 6865 4103 +a 6865 6866 4103 +a 6866 6867 4103 +a 6867 6868 4103 +a 6868 6869 4103 +a 6869 6870 4103 +a 6870 6871 4103 +a 6871 6872 4103 +a 6872 6873 4103 +a 6873 6874 4103 +a 6874 6875 4103 +a 6875 6876 4103 +a 6876 6877 4103 +a 6877 6878 4103 +a 6878 6879 4103 +a 6879 6880 4103 +a 6880 6881 4103 +a 6881 6882 4103 +a 6882 6883 4103 +a 6883 6884 4103 +a 6884 6885 4103 +a 6885 6886 4103 +a 6886 6887 4103 +a 6887 6888 4103 +a 6888 6889 4103 +a 6889 6890 4103 +a 6890 6891 4103 +a 6891 6892 4103 +a 6892 6893 4103 +a 6893 6894 4103 +a 6894 6895 4103 +a 6895 6896 4103 +a 6896 6897 4103 +a 6897 6898 4103 +a 6898 6899 4103 +a 6899 6900 4103 +a 6900 6901 4103 +a 6901 6902 4103 +a 6902 6903 4103 +a 6903 6904 4103 +a 6904 6905 4103 +a 6905 6906 4103 +a 6906 6907 4103 +a 6907 6908 4103 +a 6908 6909 4103 +a 6909 6910 4103 +a 6910 6911 4103 +a 6911 6912 4103 +a 6912 6913 4103 +a 6913 6914 4103 +a 6914 6915 4103 +a 6915 6916 4103 +a 6916 6917 4103 +a 6917 6918 4103 +a 6918 6919 4103 +a 6919 6920 4103 +a 6920 6921 4103 +a 6921 6922 4103 +a 6922 6923 4103 +a 6923 6924 4103 +a 6924 6925 4103 +a 6925 6926 4103 +a 6926 6927 4103 +a 6927 6928 4103 +a 6928 6929 4103 +a 6929 6930 4103 +a 6930 6931 4103 +a 6931 6932 4103 +a 6932 6933 4103 +a 6933 6934 4103 +a 6934 6935 4103 +a 6935 6936 4103 +a 6936 6937 4103 +a 6937 6938 4103 +a 6938 6939 4103 +a 6939 6940 4103 +a 6940 6941 4103 +a 6941 6942 4103 +a 6942 6943 4103 +a 6943 6944 4103 +a 6944 6945 4103 +a 6945 6946 4103 +a 6946 6947 4103 +a 6947 6948 4103 +a 6948 6949 4103 +a 6949 6950 4103 +a 6950 6951 4103 +a 6951 6952 4103 +a 6952 6953 4103 +a 6953 6954 4103 +a 6954 6955 4103 +a 6955 6956 4103 +a 6956 6957 4103 +a 6957 6958 4103 +a 6958 6959 4103 +a 6959 6960 4103 +a 6960 6961 4103 +a 6961 6962 4103 +a 6962 6963 4103 +a 6963 6964 4103 +a 6964 6965 4103 +a 6965 6966 4103 +a 6966 6967 4103 +a 6967 6968 4103 +a 6968 6969 4103 +a 6969 6970 4103 +a 6970 6971 4103 +a 6971 6972 4103 +a 6972 6973 4103 +a 6973 6974 4103 +a 6974 6975 4103 +a 6975 6976 4103 +a 6976 6977 4103 +a 6977 6978 4103 +a 6978 6979 4103 +a 6979 6980 4103 +a 6980 6981 4103 +a 6981 6982 4103 +a 6982 6983 4103 +a 6983 6984 4103 +a 6984 6985 4103 +a 6985 6986 4103 +a 6986 6987 4103 +a 6987 6988 4103 +a 6988 6989 4103 +a 6989 6990 4103 +a 6990 6991 4103 +a 6991 6992 4103 +a 6992 6993 4103 +a 6993 6994 4103 +a 6994 6995 4103 +a 6995 6996 4103 +a 6996 6997 4103 +a 6997 6998 4103 +a 6998 6999 4103 +a 6999 7000 4103 +a 7000 7001 4103 +a 7001 7002 4103 +a 7002 7003 4103 +a 7003 7004 4103 +a 7004 7005 4103 +a 7005 7006 4103 +a 7006 7007 4103 +a 7007 7008 4103 +a 7008 7009 4103 +a 7009 7010 4103 +a 7010 7011 4103 +a 7011 7012 4103 +a 7012 7013 4103 +a 7013 7014 4103 +a 7014 7015 4103 +a 7015 7016 4103 +a 7016 7017 4103 +a 7017 7018 4103 +a 7018 7019 4103 +a 7019 7020 4103 +a 7020 7021 4103 +a 7021 7022 4103 +a 7022 7023 4103 +a 7023 7024 4103 +a 7024 7025 4103 +a 7025 7026 4103 +a 7026 7027 4103 +a 7027 7028 4103 +a 7028 7029 4103 +a 7029 7030 4103 +a 7030 7031 4103 +a 7031 7032 4103 +a 7032 7033 4103 +a 7033 7034 4103 +a 7034 7035 4103 +a 7035 7036 4103 +a 7036 7037 4103 +a 7037 7038 4103 +a 7038 7039 4103 +a 7039 7040 4103 +a 7040 7041 4103 +a 7041 7042 4103 +a 7042 7043 4103 +a 7043 7044 4103 +a 7044 7045 4103 +a 7045 7046 4103 +a 7046 7047 4103 +a 7047 7048 4103 +a 7048 7049 4103 +a 7049 7050 4103 +a 7050 7051 4103 +a 7051 7052 4103 +a 7052 7053 4103 +a 7053 7054 4103 +a 7054 7055 4103 +a 7055 7056 4103 +a 7056 7057 4103 +a 7057 7058 4103 +a 7058 7059 4103 +a 7059 7060 4103 +a 7060 7061 4103 +a 7061 7062 4103 +a 7062 7063 4103 +a 7063 7064 4103 +a 7064 7065 4103 +a 7065 7066 4103 +a 7066 7067 4103 +a 7067 7068 4103 +a 7068 7069 4103 +a 7069 7070 4103 +a 7070 7071 4103 +a 7071 7072 4103 +a 7072 7073 4103 +a 7073 7074 4103 +a 7074 7075 4103 +a 7075 7076 4103 +a 7076 7077 4103 +a 7077 7078 4103 +a 7078 7079 4103 +a 7079 7080 4103 +a 7080 7081 4103 +a 7081 7082 4103 +a 7082 7083 4103 +a 7083 7084 4103 +a 7084 7085 4103 +a 7085 7086 4103 +a 7086 7087 4103 +a 7087 7088 4103 +a 7088 7089 4103 +a 7089 7090 4103 +a 7090 7091 4103 +a 7091 7092 4103 +a 7092 7093 4103 +a 7093 7094 4103 +a 7094 7095 4103 +a 7095 7096 4103 +a 7096 7097 4103 +a 7097 7098 4103 +a 7098 7099 4103 +a 7099 7100 4103 +a 7100 7101 4103 +a 7101 7102 4103 +a 7102 7103 4103 +a 7103 7104 4103 +a 7104 7105 4103 +a 7105 7106 4103 +a 7106 7107 4103 +a 7107 7108 4103 +a 7108 7109 4103 +a 7109 7110 4103 +a 7110 7111 4103 +a 7111 7112 4103 +a 7112 7113 4103 +a 7113 7114 4103 +a 7114 7115 4103 +a 7115 7116 4103 +a 7116 7117 4103 +a 7117 7118 4103 +a 7118 7119 4103 +a 7119 7120 4103 +a 7120 7121 4103 +a 7121 7122 4103 +a 7122 7123 4103 +a 7123 7124 4103 +a 7124 7125 4103 +a 7125 7126 4103 +a 7126 7127 4103 +a 7127 7128 4103 +a 7128 7129 4103 +a 7129 7130 4103 +a 7130 7131 4103 +a 7131 7132 4103 +a 7132 7133 4103 +a 7133 7134 4103 +a 7134 7135 4103 +a 7135 7136 4103 +a 7136 7137 4103 +a 7137 7138 4103 +a 7138 7139 4103 +a 7139 7140 4103 +a 7140 7141 4103 +a 7141 7142 4103 +a 7142 7143 4103 +a 7143 7144 4103 +a 7144 7145 4103 +a 7145 7146 4103 +a 7146 7147 4103 +a 7147 7148 4103 +a 7148 7149 4103 +a 7149 7150 4103 +a 7150 7151 4103 +a 7151 7152 4103 +a 7152 7153 4103 +a 7153 7154 4103 +a 7154 7155 4103 +a 7155 7156 4103 +a 7156 7157 4103 +a 7157 7158 4103 +a 7158 7159 4103 +a 7159 7160 4103 +a 7160 7161 4103 +a 7161 7162 4103 +a 7162 7163 4103 +a 7163 7164 4103 +a 7164 7165 4103 +a 7165 7166 4103 +a 7166 7167 4103 +a 7167 7168 4103 +a 7168 7169 4103 +a 7169 7170 4103 +a 7170 7171 4103 +a 7171 7172 4103 +a 7172 7173 4103 +a 7173 7174 4103 +a 7174 7175 4103 +a 7175 7176 4103 +a 7176 7177 4103 +a 7177 7178 4103 +a 7178 7179 4103 +a 7179 7180 4103 +a 7180 7181 4103 +a 7181 7182 4103 +a 7182 7183 4103 +a 7183 7184 4103 +a 7184 7185 4103 +a 7185 7186 4103 +a 7186 7187 4103 +a 7187 7188 4103 +a 7188 7189 4103 +a 7189 7190 4103 +a 7190 7191 4103 +a 7191 7192 4103 +a 7192 7193 4103 +a 7193 7194 4103 +a 7194 7195 4103 +a 7195 7196 4103 +a 7196 7197 4103 +a 7197 7198 4103 +a 7198 7199 4103 +a 7199 7200 4103 +a 7200 7201 4103 +a 7201 7202 4103 +a 7202 7203 4103 +a 7203 7204 4103 +a 7204 7205 4103 +a 7205 7206 4103 +a 7206 7207 4103 +a 7207 7208 4103 +a 7208 7209 4103 +a 7209 7210 4103 +a 7210 7211 4103 +a 7211 7212 4103 +a 7212 7213 4103 +a 7213 7214 4103 +a 7214 7215 4103 +a 7215 7216 4103 +a 7216 7217 4103 +a 7217 7218 4103 +a 7218 7219 4103 +a 7219 7220 4103 +a 7220 7221 4103 +a 7221 7222 4103 +a 7222 7223 4103 +a 7223 7224 4103 +a 7224 7225 4103 +a 7225 7226 4103 +a 7226 7227 4103 +a 7227 7228 4103 +a 7228 7229 4103 +a 7229 7230 4103 +a 7230 7231 4103 +a 7231 7232 4103 +a 7232 7233 4103 +a 7233 7234 4103 +a 7234 7235 4103 +a 7235 7236 4103 +a 7236 7237 4103 +a 7237 7238 4103 +a 7238 7239 4103 +a 7239 7240 4103 +a 7240 7241 4103 +a 7241 7242 4103 +a 7242 7243 4103 +a 7243 7244 4103 +a 7244 7245 4103 +a 7245 7246 4103 +a 7246 7247 4103 +a 7247 7248 4103 +a 7248 7249 4103 +a 7249 7250 4103 +a 7250 7251 4103 +a 7251 7252 4103 +a 7252 7253 4103 +a 7253 7254 4103 +a 7254 7255 4103 +a 7255 7256 4103 +a 7256 7257 4103 +a 7257 7258 4103 +a 7258 7259 4103 +a 7259 7260 4103 +a 7260 7261 4103 +a 7261 7262 4103 +a 7262 7263 4103 +a 7263 7264 4103 +a 7264 7265 4103 +a 7265 7266 4103 +a 7266 7267 4103 +a 7267 7268 4103 +a 7268 7269 4103 +a 7269 7270 4103 +a 7270 7271 4103 +a 7271 7272 4103 +a 7272 7273 4103 +a 7273 7274 4103 +a 7274 7275 4103 +a 7275 7276 4103 +a 7276 7277 4103 +a 7277 7278 4103 +a 7278 7279 4103 +a 7279 7280 4103 +a 7280 7281 4103 +a 7281 7282 4103 +a 7282 7283 4103 +a 7283 7284 4103 +a 7284 7285 4103 +a 7285 7286 4103 +a 7286 7287 4103 +a 7287 7288 4103 +a 7288 7289 4103 +a 7289 7290 4103 +a 7290 7291 4103 +a 7291 7292 4103 +a 7292 7293 4103 +a 7293 7294 4103 +a 7294 7295 4103 +a 7295 7296 4103 +a 7296 7297 4103 +a 7297 7298 4103 +a 7298 7299 4103 +a 7299 7300 4103 +a 7300 7301 4103 +a 7301 7302 4103 +a 7302 7303 4103 +a 7303 7304 4103 +a 7304 7305 4103 +a 7305 7306 4103 +a 7306 7307 4103 +a 7307 7308 4103 +a 7308 7309 4103 +a 7309 7310 4103 +a 7310 7311 4103 +a 7311 7312 4103 +a 7312 7313 4103 +a 7313 7314 4103 +a 7314 7315 4103 +a 7315 7316 4103 +a 7316 7317 4103 +a 7317 7318 4103 +a 7318 7319 4103 +a 7319 7320 4103 +a 7320 7321 4103 +a 7321 7322 4103 +a 7322 7323 4103 +a 7323 7324 4103 +a 7324 7325 4103 +a 7325 7326 4103 +a 7326 7327 4103 +a 7327 7328 4103 +a 7328 7329 4103 +a 7329 7330 4103 +a 7330 7331 4103 +a 7331 7332 4103 +a 7332 7333 4103 +a 7333 7334 4103 +a 7334 7335 4103 +a 7335 7336 4103 +a 7336 7337 4103 +a 7337 7338 4103 +a 7338 7339 4103 +a 7339 7340 4103 +a 7340 7341 4103 +a 7341 7342 4103 +a 7342 7343 4103 +a 7343 7344 4103 +a 7344 7345 4103 +a 7345 7346 4103 +a 7346 7347 4103 +a 7347 7348 4103 +a 7348 7349 4103 +a 7349 7350 4103 +a 7350 7351 4103 +a 7351 7352 4103 +a 7352 7353 4103 +a 7353 7354 4103 +a 7354 7355 4103 +a 7355 7356 4103 +a 7356 7357 4103 +a 7357 7358 4103 +a 7358 7359 4103 +a 7359 7360 4103 +a 7360 7361 4103 +a 7361 7362 4103 +a 7362 7363 4103 +a 7363 7364 4103 +a 7364 7365 4103 +a 7365 7366 4103 +a 7366 7367 4103 +a 7367 7368 4103 +a 7368 7369 4103 +a 7369 7370 4103 +a 7370 7371 4103 +a 7371 7372 4103 +a 7372 7373 4103 +a 7373 7374 4103 +a 7374 7375 4103 +a 7375 7376 4103 +a 7376 7377 4103 +a 7377 7378 4103 +a 7378 7379 4103 +a 7379 7380 4103 +a 7380 7381 4103 +a 7381 7382 4103 +a 7382 7383 4103 +a 7383 7384 4103 +a 7384 7385 4103 +a 7385 7386 4103 +a 7386 7387 4103 +a 7387 7388 4103 +a 7388 7389 4103 +a 7389 7390 4103 +a 7390 7391 4103 +a 7391 7392 4103 +a 7392 7393 4103 +a 7393 7394 4103 +a 7394 7395 4103 +a 7395 7396 4103 +a 7396 7397 4103 +a 7397 7398 4103 +a 7398 7399 4103 +a 7399 7400 4103 +a 7400 7401 4103 +a 7401 7402 4103 +a 7402 7403 4103 +a 7403 7404 4103 +a 7404 7405 4103 +a 7405 7406 4103 +a 7406 7407 4103 +a 7407 7408 4103 +a 7408 7409 4103 +a 7409 7410 4103 +a 7410 7411 4103 +a 7411 7412 4103 +a 7412 7413 4103 +a 7413 7414 4103 +a 7414 7415 4103 +a 7415 7416 4103 +a 7416 7417 4103 +a 7417 7418 4103 +a 7418 7419 4103 +a 7419 7420 4103 +a 7420 7421 4103 +a 7421 7422 4103 +a 7422 7423 4103 +a 7423 7424 4103 +a 7424 7425 4103 +a 7425 7426 4103 +a 7426 7427 4103 +a 7427 7428 4103 +a 7428 7429 4103 +a 7429 7430 4103 +a 7430 7431 4103 +a 7431 7432 4103 +a 7432 7433 4103 +a 7433 7434 4103 +a 7434 7435 4103 +a 7435 7436 4103 +a 7436 7437 4103 +a 7437 7438 4103 +a 7438 7439 4103 +a 7439 7440 4103 +a 7440 7441 4103 +a 7441 7442 4103 +a 7442 7443 4103 +a 7443 7444 4103 +a 7444 7445 4103 +a 7445 7446 4103 +a 7446 7447 4103 +a 7447 7448 4103 +a 7448 7449 4103 +a 7449 7450 4103 +a 7450 7451 4103 +a 7451 7452 4103 +a 7452 7453 4103 +a 7453 7454 4103 +a 7454 7455 4103 +a 7455 7456 4103 +a 7456 7457 4103 +a 7457 7458 4103 +a 7458 7459 4103 +a 7459 7460 4103 +a 7460 7461 4103 +a 7461 7462 4103 +a 7462 7463 4103 +a 7463 7464 4103 +a 7464 7465 4103 +a 7465 7466 4103 +a 7466 7467 4103 +a 7467 7468 4103 +a 7468 7469 4103 +a 7469 7470 4103 +a 7470 7471 4103 +a 7471 7472 4103 +a 7472 7473 4103 +a 7473 7474 4103 +a 7474 7475 4103 +a 7475 7476 4103 +a 7476 7477 4103 +a 7477 7478 4103 +a 7478 7479 4103 +a 7479 7480 4103 +a 7480 7481 4103 +a 7481 7482 4103 +a 7482 7483 4103 +a 7483 7484 4103 +a 7484 7485 4103 +a 7485 7486 4103 +a 7486 7487 4103 +a 7487 7488 4103 +a 7488 7489 4103 +a 7489 7490 4103 +a 7490 7491 4103 +a 7491 7492 4103 +a 7492 7493 4103 +a 7493 7494 4103 +a 7494 7495 4103 +a 7495 7496 4103 +a 7496 7497 4103 +a 7497 7498 4103 +a 7498 7499 4103 +a 7499 7500 4103 +a 7500 7501 4103 +a 7501 7502 4103 +a 7502 7503 4103 +a 7503 7504 4103 +a 7504 7505 4103 +a 7505 7506 4103 +a 7506 7507 4103 +a 7507 7508 4103 +a 7508 7509 4103 +a 7509 7510 4103 +a 7510 7511 4103 +a 7511 7512 4103 +a 7512 7513 4103 +a 7513 7514 4103 +a 7514 7515 4103 +a 7515 7516 4103 +a 7516 7517 4103 +a 7517 7518 4103 +a 7518 7519 4103 +a 7519 7520 4103 +a 7520 7521 4103 +a 7521 7522 4103 +a 7522 7523 4103 +a 7523 7524 4103 +a 7524 7525 4103 +a 7525 7526 4103 +a 7526 7527 4103 +a 7527 7528 4103 +a 7528 7529 4103 +a 7529 7530 4103 +a 7530 7531 4103 +a 7531 7532 4103 +a 7532 7533 4103 +a 7533 7534 4103 +a 7534 7535 4103 +a 7535 7536 4103 +a 7536 7537 4103 +a 7537 7538 4103 +a 7538 7539 4103 +a 7539 7540 4103 +a 7540 7541 4103 +a 7541 7542 4103 +a 7542 7543 4103 +a 7543 7544 4103 +a 7544 7545 4103 +a 7545 7546 4103 +a 7546 7547 4103 +a 7547 7548 4103 +a 7548 7549 4103 +a 7549 7550 4103 +a 7550 7551 4103 +a 7551 7552 4103 +a 7552 7553 4103 +a 7553 7554 4103 +a 7554 7555 4103 +a 7555 7556 4103 +a 7556 7557 4103 +a 7557 7558 4103 +a 7558 7559 4103 +a 7559 7560 4103 +a 7560 7561 4103 +a 7561 7562 4103 +a 7562 7563 4103 +a 7563 7564 4103 +a 7564 7565 4103 +a 7565 7566 4103 +a 7566 7567 4103 +a 7567 7568 4103 +a 7568 7569 4103 +a 7569 7570 4103 +a 7570 7571 4103 +a 7571 7572 4103 +a 7572 7573 4103 +a 7573 7574 4103 +a 7574 7575 4103 +a 7575 7576 4103 +a 7576 7577 4103 +a 7577 7578 4103 +a 7578 7579 4103 +a 7579 7580 4103 +a 7580 7581 4103 +a 7581 7582 4103 +a 7582 7583 4103 +a 7583 7584 4103 +a 7584 7585 4103 +a 7585 7586 4103 +a 7586 7587 4103 +a 7587 7588 4103 +a 7588 7589 4103 +a 7589 7590 4103 +a 7590 7591 4103 +a 7591 7592 4103 +a 7592 7593 4103 +a 7593 7594 4103 +a 7594 7595 4103 +a 7595 7596 4103 +a 7596 7597 4103 +a 7597 7598 4103 +a 7598 7599 4103 +a 7599 7600 4103 +a 7600 7601 4103 +a 7601 7602 4103 +a 7602 7603 4103 +a 7603 7604 4103 +a 7604 7605 4103 +a 7605 7606 4103 +a 7606 7607 4103 +a 7607 7608 4103 +a 7608 7609 4103 +a 7609 7610 4103 +a 7610 7611 4103 +a 7611 7612 4103 +a 7612 7613 4103 +a 7613 7614 4103 +a 7614 7615 4103 +a 7615 7616 4103 +a 7616 7617 4103 +a 7617 7618 4103 +a 7618 7619 4103 +a 7619 7620 4103 +a 7620 7621 4103 +a 7621 7622 4103 +a 7622 7623 4103 +a 7623 7624 4103 +a 7624 7625 4103 +a 7625 7626 4103 +a 7626 7627 4103 +a 7627 7628 4103 +a 7628 7629 4103 +a 7629 7630 4103 +a 7630 7631 4103 +a 7631 7632 4103 +a 7632 7633 4103 +a 7633 7634 4103 +a 7634 7635 4103 +a 7635 7636 4103 +a 7636 7637 4103 +a 7637 7638 4103 +a 7638 7639 4103 +a 7639 7640 4103 +a 7640 7641 4103 +a 7641 7642 4103 +a 7642 7643 4103 +a 7643 7644 4103 +a 7644 7645 4103 +a 7645 7646 4103 +a 7646 7647 4103 +a 7647 7648 4103 +a 7648 7649 4103 +a 7649 7650 4103 +a 7650 7651 4103 +a 7651 7652 4103 +a 7652 7653 4103 +a 7653 7654 4103 +a 7654 7655 4103 +a 7655 7656 4103 +a 7656 7657 4103 +a 7657 7658 4103 +a 7658 7659 4103 +a 7659 7660 4103 +a 7660 7661 4103 +a 7661 7662 4103 +a 7662 7663 4103 +a 7663 7664 4103 +a 7664 7665 4103 +a 7665 7666 4103 +a 7666 7667 4103 +a 7667 7668 4103 +a 7668 7669 4103 +a 7669 7670 4103 +a 7670 7671 4103 +a 7671 7672 4103 +a 7672 7673 4103 +a 7673 7674 4103 +a 7674 7675 4103 +a 7675 7676 4103 +a 7676 7677 4103 +a 7677 7678 4103 +a 7678 7679 4103 +a 7679 7680 4103 +a 7680 7681 4103 +a 7681 7682 4103 +a 7682 7683 4103 +a 7683 7684 4103 +a 7684 7685 4103 +a 7685 7686 4103 +a 7686 7687 4103 +a 7687 7688 4103 +a 7688 7689 4103 +a 7689 7690 4103 +a 7690 7691 4103 +a 7691 7692 4103 +a 7692 7693 4103 +a 7693 7694 4103 +a 7694 7695 4103 +a 7695 7696 4103 +a 7696 7697 4103 +a 7697 7698 4103 +a 7698 7699 4103 +a 7699 7700 4103 +a 7700 7701 4103 +a 7701 7702 4103 +a 7702 7703 4103 +a 7703 7704 4103 +a 7704 7705 4103 +a 7705 7706 4103 +a 7706 7707 4103 +a 7707 7708 4103 +a 7708 7709 4103 +a 7709 7710 4103 +a 7710 7711 4103 +a 7711 7712 4103 +a 7712 7713 4103 +a 7713 7714 4103 +a 7714 7715 4103 +a 7715 7716 4103 +a 7716 7717 4103 +a 7717 7718 4103 +a 7718 7719 4103 +a 7719 7720 4103 +a 7720 7721 4103 +a 7721 7722 4103 +a 7722 7723 4103 +a 7723 7724 4103 +a 7724 7725 4103 +a 7725 7726 4103 +a 7726 7727 4103 +a 7727 7728 4103 +a 7728 7729 4103 +a 7729 7730 4103 +a 7730 7731 4103 +a 7731 7732 4103 +a 7732 7733 4103 +a 7733 7734 4103 +a 7734 7735 4103 +a 7735 7736 4103 +a 7736 7737 4103 +a 7737 7738 4103 +a 7738 7739 4103 +a 7739 7740 4103 +a 7740 7741 4103 +a 7741 7742 4103 +a 7742 7743 4103 +a 7743 7744 4103 +a 7744 7745 4103 +a 7745 7746 4103 +a 7746 7747 4103 +a 7747 7748 4103 +a 7748 7749 4103 +a 7749 7750 4103 +a 7750 7751 4103 +a 7751 7752 4103 +a 7752 7753 4103 +a 7753 7754 4103 +a 7754 7755 4103 +a 7755 7756 4103 +a 7756 7757 4103 +a 7757 7758 4103 +a 7758 7759 4103 +a 7759 7760 4103 +a 7760 7761 4103 +a 7761 7762 4103 +a 7762 7763 4103 +a 7763 7764 4103 +a 7764 7765 4103 +a 7765 7766 4103 +a 7766 7767 4103 +a 7767 7768 4103 +a 7768 7769 4103 +a 7769 7770 4103 +a 7770 7771 4103 +a 7771 7772 4103 +a 7772 7773 4103 +a 7773 7774 4103 +a 7774 7775 4103 +a 7775 7776 4103 +a 7776 7777 4103 +a 7777 7778 4103 +a 7778 7779 4103 +a 7779 7780 4103 +a 7780 7781 4103 +a 7781 7782 4103 +a 7782 7783 4103 +a 7783 7784 4103 +a 7784 7785 4103 +a 7785 7786 4103 +a 7786 7787 4103 +a 7787 7788 4103 +a 7788 7789 4103 +a 7789 7790 4103 +a 7790 7791 4103 +a 7791 7792 4103 +a 7792 7793 4103 +a 7793 7794 4103 +a 7794 7795 4103 +a 7795 7796 4103 +a 7796 7797 4103 +a 7797 7798 4103 +a 7798 7799 4103 +a 7799 7800 4103 +a 7800 7801 4103 +a 7801 7802 4103 +a 7802 7803 4103 +a 7803 7804 4103 +a 7804 7805 4103 +a 7805 7806 4103 +a 7806 7807 4103 +a 7807 7808 4103 +a 7808 7809 4103 +a 7809 7810 4103 +a 7810 7811 4103 +a 7811 7812 4103 +a 7812 7813 4103 +a 7813 7814 4103 +a 7814 7815 4103 +a 7815 7816 4103 +a 7816 7817 4103 +a 7817 7818 4103 +a 7818 7819 4103 +a 7819 7820 4103 +a 7820 7821 4103 +a 7821 7822 4103 +a 7822 7823 4103 +a 7823 7824 4103 +a 7824 7825 4103 +a 7825 7826 4103 +a 7826 7827 4103 +a 7827 7828 4103 +a 7828 7829 4103 +a 7829 7830 4103 +a 7830 7831 4103 +a 7831 7832 4103 +a 7832 7833 4103 +a 7833 7834 4103 +a 7834 7835 4103 +a 7835 7836 4103 +a 7836 7837 4103 +a 7837 7838 4103 +a 7838 7839 4103 +a 7839 7840 4103 +a 7840 7841 4103 +a 7841 7842 4103 +a 7842 7843 4103 +a 7843 7844 4103 +a 7844 7845 4103 +a 7845 7846 4103 +a 7846 7847 4103 +a 7847 7848 4103 +a 7848 7849 4103 +a 7849 7850 4103 +a 7850 7851 4103 +a 7851 7852 4103 +a 7852 7853 4103 +a 7853 7854 4103 +a 7854 7855 4103 +a 7855 7856 4103 +a 7856 7857 4103 +a 7857 7858 4103 +a 7858 7859 4103 +a 7859 7860 4103 +a 7860 7861 4103 +a 7861 7862 4103 +a 7862 7863 4103 +a 7863 7864 4103 +a 7864 7865 4103 +a 7865 7866 4103 +a 7866 7867 4103 +a 7867 7868 4103 +a 7868 7869 4103 +a 7869 7870 4103 +a 7870 7871 4103 +a 7871 7872 4103 +a 7872 7873 4103 +a 7873 7874 4103 +a 7874 7875 4103 +a 7875 7876 4103 +a 7876 7877 4103 +a 7877 7878 4103 +a 7878 7879 4103 +a 7879 7880 4103 +a 7880 7881 4103 +a 7881 7882 4103 +a 7882 7883 4103 +a 7883 7884 4103 +a 7884 7885 4103 +a 7885 7886 4103 +a 7886 7887 4103 +a 7887 7888 4103 +a 7888 7889 4103 +a 7889 7890 4103 +a 7890 7891 4103 +a 7891 7892 4103 +a 7892 7893 4103 +a 7893 7894 4103 +a 7894 7895 4103 +a 7895 7896 4103 +a 7896 7897 4103 +a 7897 7898 4103 +a 7898 7899 4103 +a 7899 7900 4103 +a 7900 7901 4103 +a 7901 7902 4103 +a 7902 7903 4103 +a 7903 7904 4103 +a 7904 7905 4103 +a 7905 7906 4103 +a 7906 7907 4103 +a 7907 7908 4103 +a 7908 7909 4103 +a 7909 7910 4103 +a 7910 7911 4103 +a 7911 7912 4103 +a 7912 7913 4103 +a 7913 7914 4103 +a 7914 7915 4103 +a 7915 7916 4103 +a 7916 7917 4103 +a 7917 7918 4103 +a 7918 7919 4103 +a 7919 7920 4103 +a 7920 7921 4103 +a 7921 7922 4103 +a 7922 7923 4103 +a 7923 7924 4103 +a 7924 7925 4103 +a 7925 7926 4103 +a 7926 7927 4103 +a 7927 7928 4103 +a 7928 7929 4103 +a 7929 7930 4103 +a 7930 7931 4103 +a 7931 7932 4103 +a 7932 7933 4103 +a 7933 7934 4103 +a 7934 7935 4103 +a 7935 7936 4103 +a 7936 7937 4103 +a 7937 7938 4103 +a 7938 7939 4103 +a 7939 7940 4103 +a 7940 7941 4103 +a 7941 7942 4103 +a 7942 7943 4103 +a 7943 7944 4103 +a 7944 7945 4103 +a 7945 7946 4103 +a 7946 7947 4103 +a 7947 7948 4103 +a 7948 7949 4103 +a 7949 7950 4103 +a 7950 7951 4103 +a 7951 7952 4103 +a 7952 7953 4103 +a 7953 7954 4103 +a 7954 7955 4103 +a 7955 7956 4103 +a 7956 7957 4103 +a 7957 7958 4103 +a 7958 7959 4103 +a 7959 7960 4103 +a 7960 7961 4103 +a 7961 7962 4103 +a 7962 7963 4103 +a 7963 7964 4103 +a 7964 7965 4103 +a 7965 7966 4103 +a 7966 7967 4103 +a 7967 7968 4103 +a 7968 7969 4103 +a 7969 7970 4103 +a 7970 7971 4103 +a 7971 7972 4103 +a 7972 7973 4103 +a 7973 7974 4103 +a 7974 7975 4103 +a 7975 7976 4103 +a 7976 7977 4103 +a 7977 7978 4103 +a 7978 7979 4103 +a 7979 7980 4103 +a 7980 7981 4103 +a 7981 7982 4103 +a 7982 7983 4103 +a 7983 7984 4103 +a 7984 7985 4103 +a 7985 7986 4103 +a 7986 7987 4103 +a 7987 7988 4103 +a 7988 7989 4103 +a 7989 7990 4103 +a 7990 7991 4103 +a 7991 7992 4103 +a 7992 7993 4103 +a 7993 7994 4103 +a 7994 7995 4103 +a 7995 7996 4103 +a 7996 7997 4103 +a 7997 7998 4103 +a 7998 7999 4103 +a 7999 8000 4103 +a 8000 8001 4103 +a 8001 8002 4103 +a 8002 8003 4103 +a 8003 8004 4103 +a 8004 8005 4103 +a 8005 8006 4103 +a 8006 8007 4103 +a 8007 8008 4103 +a 8008 8009 4103 +a 8009 8010 4103 +a 8010 8011 4103 +a 8011 8012 4103 +a 8012 8013 4103 +a 8013 8014 4103 +a 8014 8015 4103 +a 8015 8016 4103 +a 8016 8017 4103 +a 8017 8018 4103 +a 8018 8019 4103 +a 8019 8020 4103 +a 8020 8021 4103 +a 8021 8022 4103 +a 8022 8023 4103 +a 8023 8024 4103 +a 8024 8025 4103 +a 8025 8026 4103 +a 8026 8027 4103 +a 8027 8028 4103 +a 8028 8029 4103 +a 8029 8030 4103 +a 8030 8031 4103 +a 8031 8032 4103 +a 8032 8033 4103 +a 8033 8034 4103 +a 8034 8035 4103 +a 8035 8036 4103 +a 8036 8037 4103 +a 8037 8038 4103 +a 8038 8039 4103 +a 8039 8040 4103 +a 8040 8041 4103 +a 8041 8042 4103 +a 8042 8043 4103 +a 8043 8044 4103 +a 8044 8045 4103 +a 8045 8046 4103 +a 8046 8047 4103 +a 8047 8048 4103 +a 8048 8049 4103 +a 8049 8050 4103 +a 8050 8051 4103 +a 8051 8052 4103 +a 8052 8053 4103 +a 8053 8054 4103 +a 8054 8055 4103 +a 8055 8056 4103 +a 8056 8057 4103 +a 8057 8058 4103 +a 8058 8059 4103 +a 8059 8060 4103 +a 8060 8061 4103 +a 8061 8062 4103 +a 8062 8063 4103 +a 8063 8064 4103 +a 8064 8065 4103 +a 8065 8066 4103 +a 8066 8067 4103 +a 8067 8068 4103 +a 8068 8069 4103 +a 8069 8070 4103 +a 8070 8071 4103 +a 8071 8072 4103 +a 8072 8073 4103 +a 8073 8074 4103 +a 8074 8075 4103 +a 8075 8076 4103 +a 8076 8077 4103 +a 8077 8078 4103 +a 8078 8079 4103 +a 8079 8080 4103 +a 8080 8081 4103 +a 8081 8082 4103 +a 8082 8083 4103 +a 8083 8084 4103 +a 8084 8085 4103 +a 8085 8086 4103 +a 8086 8087 4103 +a 8087 8088 4103 +a 8088 8089 4103 +a 8089 8090 4103 +a 8090 8091 4103 +a 8091 8092 4103 +a 8092 8093 4103 +a 8093 8094 4103 +a 8094 8095 4103 +a 8095 8096 4103 +a 8096 8097 4103 +a 8097 8098 4103 +a 8098 8099 4103 +a 8099 8100 4103 +a 8100 8101 4103 +a 8101 8102 4103 +a 8102 8103 4103 +a 8103 8104 4103 +a 8104 8105 4103 +a 8105 8106 4103 +a 8106 8107 4103 +a 8107 8108 4103 +a 8108 8109 4103 +a 8109 8110 4103 +a 8110 8111 4103 +a 8111 8112 4103 +a 8112 8113 4103 +a 8113 8114 4103 +a 8114 8115 4103 +a 8115 8116 4103 +a 8116 8117 4103 +a 8117 8118 4103 +a 8118 8119 4103 +a 8119 8120 4103 +a 8120 8121 4103 +a 8121 8122 4103 +a 8122 8123 4103 +a 8123 8124 4103 +a 8124 8125 4103 +a 8125 8126 4103 +a 8126 8127 4103 +a 8127 8128 4103 +a 8128 8129 4103 +a 8129 8130 4103 +a 8130 8131 4103 +a 8131 8132 4103 +a 8132 8133 4103 +a 8133 8134 4103 +a 8134 8135 4103 +a 8135 8136 4103 +a 8136 8137 4103 +a 8137 8138 4103 +a 8138 8139 4103 +a 8139 8140 4103 +a 8140 8141 4103 +a 8141 8142 4103 +a 8142 8143 4103 +a 8143 8144 4103 +a 8144 8145 4103 +a 8145 8146 4103 +a 8146 8147 4103 +a 8147 8148 4103 +a 8148 8149 4103 +a 8149 8150 4103 +a 8150 8151 4103 +a 8151 8152 4103 +a 8152 8153 4103 +a 8153 8154 4103 +a 8154 8155 4103 +a 8155 8156 4103 +a 8156 8157 4103 +a 8157 8158 4103 +a 8158 8159 4103 +a 8159 8160 4103 +a 8160 8161 4103 +a 8161 8162 4103 +a 8162 8163 4103 +a 8163 8164 4103 +a 8164 8165 4103 +a 8165 8166 4103 +a 8166 8167 4103 +a 8167 8168 4103 +a 8168 8169 4103 +a 8169 8170 4103 +a 8170 8171 4103 +a 8171 8172 4103 +a 8172 8173 4103 +a 8173 8174 4103 +a 8174 8175 4103 +a 8175 8176 4103 +a 8176 8177 4103 +a 8177 8178 4103 +a 8178 8179 4103 +a 8179 8180 4103 +a 8180 8181 4103 +a 8181 8182 4103 +a 8182 8183 4103 +a 8183 8184 4103 +a 8184 8185 4103 +a 8185 8186 4103 +a 8186 8187 4103 +a 8187 8188 4103 +a 8188 8189 4103 +a 8189 8190 4103 +a 8190 8191 4103 +a 8191 8192 4103 +a 8192 8193 4103 +a 8193 8194 4103 +a 8194 8195 4103 +a 8195 8196 4103 +a 8196 8197 4103 +a 8197 8198 4103 +a 8198 8199 4103 +a 8199 8200 4103 +a 8200 8201 4103 +a 8201 8202 4103 +a 8202 8203 4103 +a 8203 8204 4103 +a 8204 8205 4103 +a 8205 8206 4103 +a 8206 8207 4103 +a 8207 8208 4103 +a 8209 8210 4102 +a 8210 8211 4102 +a 8211 8212 4102 +a 8212 8213 4102 +a 8213 8214 4102 +a 8214 8215 4102 +a 8215 8216 4102 +a 8216 8217 4102 +a 8217 8218 4102 +a 8218 8219 4102 +a 8219 8220 4102 +a 8220 8221 4102 +a 8221 8222 4102 +a 8222 8223 4102 +a 8223 8224 4102 +a 8224 8225 4102 +a 8225 8226 4102 +a 8226 8227 4102 +a 8227 8228 4102 +a 8228 8229 4102 +a 8229 8230 4102 +a 8230 8231 4102 +a 8231 8232 4102 +a 8232 8233 4102 +a 8233 8234 4102 +a 8234 8235 4102 +a 8235 8236 4102 +a 8236 8237 4102 +a 8237 8238 4102 +a 8238 8239 4102 +a 8239 8240 4102 +a 8240 8241 4102 +a 8241 8242 4102 +a 8242 8243 4102 +a 8243 8244 4102 +a 8244 8245 4102 +a 8245 8246 4102 +a 8246 8247 4102 +a 8247 8248 4102 +a 8248 8249 4102 +a 8249 8250 4102 +a 8250 8251 4102 +a 8251 8252 4102 +a 8252 8253 4102 +a 8253 8254 4102 +a 8254 8255 4102 +a 8255 8256 4102 +a 8256 8257 4102 +a 8257 8258 4102 +a 8258 8259 4102 +a 8259 8260 4102 +a 8260 8261 4102 +a 8261 8262 4102 +a 8262 8263 4102 +a 8263 8264 4102 +a 8264 8265 4102 +a 8265 8266 4102 +a 8266 8267 4102 +a 8267 8268 4102 +a 8268 8269 4102 +a 8269 8270 4102 +a 8270 8271 4102 +a 8271 8272 4102 +a 8272 8273 4102 +a 8273 8274 4102 +a 8274 8275 4102 +a 8275 8276 4102 +a 8276 8277 4102 +a 8277 8278 4102 +a 8278 8279 4102 +a 8279 8280 4102 +a 8280 8281 4102 +a 8281 8282 4102 +a 8282 8283 4102 +a 8283 8284 4102 +a 8284 8285 4102 +a 8285 8286 4102 +a 8286 8287 4102 +a 8287 8288 4102 +a 8288 8289 4102 +a 8289 8290 4102 +a 8290 8291 4102 +a 8291 8292 4102 +a 8292 8293 4102 +a 8293 8294 4102 +a 8294 8295 4102 +a 8295 8296 4102 +a 8296 8297 4102 +a 8297 8298 4102 +a 8298 8299 4102 +a 8299 8300 4102 +a 8300 8301 4102 +a 8301 8302 4102 +a 8302 8303 4102 +a 8303 8304 4102 +a 8304 8305 4102 +a 8305 8306 4102 +a 8306 8307 4102 +a 8307 8308 4102 +a 8308 8309 4102 +a 8309 8310 4102 +a 8310 8311 4102 +a 8311 8312 4102 +a 8312 8313 4102 +a 8313 8314 4102 +a 8314 8315 4102 +a 8315 8316 4102 +a 8316 8317 4102 +a 8317 8318 4102 +a 8318 8319 4102 +a 8319 8320 4102 +a 8320 8321 4102 +a 8321 8322 4102 +a 8322 8323 4102 +a 8323 8324 4102 +a 8324 8325 4102 +a 8325 8326 4102 +a 8326 8327 4102 +a 8327 8328 4102 +a 8328 8329 4102 +a 8329 8330 4102 +a 8330 8331 4102 +a 8331 8332 4102 +a 8332 8333 4102 +a 8333 8334 4102 +a 8334 8335 4102 +a 8335 8336 4102 +a 8336 8337 4102 +a 8337 8338 4102 +a 8338 8339 4102 +a 8339 8340 4102 +a 8340 8341 4102 +a 8341 8342 4102 +a 8342 8343 4102 +a 8343 8344 4102 +a 8344 8345 4102 +a 8345 8346 4102 +a 8346 8347 4102 +a 8347 8348 4102 +a 8348 8349 4102 +a 8349 8350 4102 +a 8350 8351 4102 +a 8351 8352 4102 +a 8352 8353 4102 +a 8353 8354 4102 +a 8354 8355 4102 +a 8355 8356 4102 +a 8356 8357 4102 +a 8357 8358 4102 +a 8358 8359 4102 +a 8359 8360 4102 +a 8360 8361 4102 +a 8361 8362 4102 +a 8362 8363 4102 +a 8363 8364 4102 +a 8364 8365 4102 +a 8365 8366 4102 +a 8366 8367 4102 +a 8367 8368 4102 +a 8368 8369 4102 +a 8369 8370 4102 +a 8370 8371 4102 +a 8371 8372 4102 +a 8372 8373 4102 +a 8373 8374 4102 +a 8374 8375 4102 +a 8375 8376 4102 +a 8376 8377 4102 +a 8377 8378 4102 +a 8378 8379 4102 +a 8379 8380 4102 +a 8380 8381 4102 +a 8381 8382 4102 +a 8382 8383 4102 +a 8383 8384 4102 +a 8384 8385 4102 +a 8385 8386 4102 +a 8386 8387 4102 +a 8387 8388 4102 +a 8388 8389 4102 +a 8389 8390 4102 +a 8390 8391 4102 +a 8391 8392 4102 +a 8392 8393 4102 +a 8393 8394 4102 +a 8394 8395 4102 +a 8395 8396 4102 +a 8396 8397 4102 +a 8397 8398 4102 +a 8398 8399 4102 +a 8399 8400 4102 +a 8400 8401 4102 +a 8401 8402 4102 +a 8402 8403 4102 +a 8403 8404 4102 +a 8404 8405 4102 +a 8405 8406 4102 +a 8406 8407 4102 +a 8407 8408 4102 +a 8408 8409 4102 +a 8409 8410 4102 +a 8410 8411 4102 +a 8411 8412 4102 +a 8412 8413 4102 +a 8413 8414 4102 +a 8414 8415 4102 +a 8415 8416 4102 +a 8416 8417 4102 +a 8417 8418 4102 +a 8418 8419 4102 +a 8419 8420 4102 +a 8420 8421 4102 +a 8421 8422 4102 +a 8422 8423 4102 +a 8423 8424 4102 +a 8424 8425 4102 +a 8425 8426 4102 +a 8426 8427 4102 +a 8427 8428 4102 +a 8428 8429 4102 +a 8429 8430 4102 +a 8430 8431 4102 +a 8431 8432 4102 +a 8432 8433 4102 +a 8433 8434 4102 +a 8434 8435 4102 +a 8435 8436 4102 +a 8436 8437 4102 +a 8437 8438 4102 +a 8438 8439 4102 +a 8439 8440 4102 +a 8440 8441 4102 +a 8441 8442 4102 +a 8442 8443 4102 +a 8443 8444 4102 +a 8444 8445 4102 +a 8445 8446 4102 +a 8446 8447 4102 +a 8447 8448 4102 +a 8448 8449 4102 +a 8449 8450 4102 +a 8450 8451 4102 +a 8451 8452 4102 +a 8452 8453 4102 +a 8453 8454 4102 +a 8454 8455 4102 +a 8455 8456 4102 +a 8456 8457 4102 +a 8457 8458 4102 +a 8458 8459 4102 +a 8459 8460 4102 +a 8460 8461 4102 +a 8461 8462 4102 +a 8462 8463 4102 +a 8463 8464 4102 +a 8464 8465 4102 +a 8465 8466 4102 +a 8466 8467 4102 +a 8467 8468 4102 +a 8468 8469 4102 +a 8469 8470 4102 +a 8470 8471 4102 +a 8471 8472 4102 +a 8472 8473 4102 +a 8473 8474 4102 +a 8474 8475 4102 +a 8475 8476 4102 +a 8476 8477 4102 +a 8477 8478 4102 +a 8478 8479 4102 +a 8479 8480 4102 +a 8480 8481 4102 +a 8481 8482 4102 +a 8482 8483 4102 +a 8483 8484 4102 +a 8484 8485 4102 +a 8485 8486 4102 +a 8486 8487 4102 +a 8487 8488 4102 +a 8488 8489 4102 +a 8489 8490 4102 +a 8490 8491 4102 +a 8491 8492 4102 +a 8492 8493 4102 +a 8493 8494 4102 +a 8494 8495 4102 +a 8495 8496 4102 +a 8496 8497 4102 +a 8497 8498 4102 +a 8498 8499 4102 +a 8499 8500 4102 +a 8500 8501 4102 +a 8501 8502 4102 +a 8502 8503 4102 +a 8503 8504 4102 +a 8504 8505 4102 +a 8505 8506 4102 +a 8506 8507 4102 +a 8507 8508 4102 +a 8508 8509 4102 +a 8509 8510 4102 +a 8510 8511 4102 +a 8511 8512 4102 +a 8512 8513 4102 +a 8513 8514 4102 +a 8514 8515 4102 +a 8515 8516 4102 +a 8516 8517 4102 +a 8517 8518 4102 +a 8518 8519 4102 +a 8519 8520 4102 +a 8520 8521 4102 +a 8521 8522 4102 +a 8522 8523 4102 +a 8523 8524 4102 +a 8524 8525 4102 +a 8525 8526 4102 +a 8526 8527 4102 +a 8527 8528 4102 +a 8528 8529 4102 +a 8529 8530 4102 +a 8530 8531 4102 +a 8531 8532 4102 +a 8532 8533 4102 +a 8533 8534 4102 +a 8534 8535 4102 +a 8535 8536 4102 +a 8536 8537 4102 +a 8537 8538 4102 +a 8538 8539 4102 +a 8539 8540 4102 +a 8540 8541 4102 +a 8541 8542 4102 +a 8542 8543 4102 +a 8543 8544 4102 +a 8544 8545 4102 +a 8545 8546 4102 +a 8546 8547 4102 +a 8547 8548 4102 +a 8548 8549 4102 +a 8549 8550 4102 +a 8550 8551 4102 +a 8551 8552 4102 +a 8552 8553 4102 +a 8553 8554 4102 +a 8554 8555 4102 +a 8555 8556 4102 +a 8556 8557 4102 +a 8557 8558 4102 +a 8558 8559 4102 +a 8559 8560 4102 +a 8560 8561 4102 +a 8561 8562 4102 +a 8562 8563 4102 +a 8563 8564 4102 +a 8564 8565 4102 +a 8565 8566 4102 +a 8566 8567 4102 +a 8567 8568 4102 +a 8568 8569 4102 +a 8569 8570 4102 +a 8570 8571 4102 +a 8571 8572 4102 +a 8572 8573 4102 +a 8573 8574 4102 +a 8574 8575 4102 +a 8575 8576 4102 +a 8576 8577 4102 +a 8577 8578 4102 +a 8578 8579 4102 +a 8579 8580 4102 +a 8580 8581 4102 +a 8581 8582 4102 +a 8582 8583 4102 +a 8583 8584 4102 +a 8584 8585 4102 +a 8585 8586 4102 +a 8586 8587 4102 +a 8587 8588 4102 +a 8588 8589 4102 +a 8589 8590 4102 +a 8590 8591 4102 +a 8591 8592 4102 +a 8592 8593 4102 +a 8593 8594 4102 +a 8594 8595 4102 +a 8595 8596 4102 +a 8596 8597 4102 +a 8597 8598 4102 +a 8598 8599 4102 +a 8599 8600 4102 +a 8600 8601 4102 +a 8601 8602 4102 +a 8602 8603 4102 +a 8603 8604 4102 +a 8604 8605 4102 +a 8605 8606 4102 +a 8606 8607 4102 +a 8607 8608 4102 +a 8608 8609 4102 +a 8609 8610 4102 +a 8610 8611 4102 +a 8611 8612 4102 +a 8612 8613 4102 +a 8613 8614 4102 +a 8614 8615 4102 +a 8615 8616 4102 +a 8616 8617 4102 +a 8617 8618 4102 +a 8618 8619 4102 +a 8619 8620 4102 +a 8620 8621 4102 +a 8621 8622 4102 +a 8622 8623 4102 +a 8623 8624 4102 +a 8624 8625 4102 +a 8625 8626 4102 +a 8626 8627 4102 +a 8627 8628 4102 +a 8628 8629 4102 +a 8629 8630 4102 +a 8630 8631 4102 +a 8631 8632 4102 +a 8632 8633 4102 +a 8633 8634 4102 +a 8634 8635 4102 +a 8635 8636 4102 +a 8636 8637 4102 +a 8637 8638 4102 +a 8638 8639 4102 +a 8639 8640 4102 +a 8640 8641 4102 +a 8641 8642 4102 +a 8642 8643 4102 +a 8643 8644 4102 +a 8644 8645 4102 +a 8645 8646 4102 +a 8646 8647 4102 +a 8647 8648 4102 +a 8648 8649 4102 +a 8649 8650 4102 +a 8650 8651 4102 +a 8651 8652 4102 +a 8652 8653 4102 +a 8653 8654 4102 +a 8654 8655 4102 +a 8655 8656 4102 +a 8656 8657 4102 +a 8657 8658 4102 +a 8658 8659 4102 +a 8659 8660 4102 +a 8660 8661 4102 +a 8661 8662 4102 +a 8662 8663 4102 +a 8663 8664 4102 +a 8664 8665 4102 +a 8665 8666 4102 +a 8666 8667 4102 +a 8667 8668 4102 +a 8668 8669 4102 +a 8669 8670 4102 +a 8670 8671 4102 +a 8671 8672 4102 +a 8672 8673 4102 +a 8673 8674 4102 +a 8674 8675 4102 +a 8675 8676 4102 +a 8676 8677 4102 +a 8677 8678 4102 +a 8678 8679 4102 +a 8679 8680 4102 +a 8680 8681 4102 +a 8681 8682 4102 +a 8682 8683 4102 +a 8683 8684 4102 +a 8684 8685 4102 +a 8685 8686 4102 +a 8686 8687 4102 +a 8687 8688 4102 +a 8688 8689 4102 +a 8689 8690 4102 +a 8690 8691 4102 +a 8691 8692 4102 +a 8692 8693 4102 +a 8693 8694 4102 +a 8694 8695 4102 +a 8695 8696 4102 +a 8696 8697 4102 +a 8697 8698 4102 +a 8698 8699 4102 +a 8699 8700 4102 +a 8700 8701 4102 +a 8701 8702 4102 +a 8702 8703 4102 +a 8703 8704 4102 +a 8704 8705 4102 +a 8705 8706 4102 +a 8706 8707 4102 +a 8707 8708 4102 +a 8708 8709 4102 +a 8709 8710 4102 +a 8710 8711 4102 +a 8711 8712 4102 +a 8712 8713 4102 +a 8713 8714 4102 +a 8714 8715 4102 +a 8715 8716 4102 +a 8716 8717 4102 +a 8717 8718 4102 +a 8718 8719 4102 +a 8719 8720 4102 +a 8720 8721 4102 +a 8721 8722 4102 +a 8722 8723 4102 +a 8723 8724 4102 +a 8724 8725 4102 +a 8725 8726 4102 +a 8726 8727 4102 +a 8727 8728 4102 +a 8728 8729 4102 +a 8729 8730 4102 +a 8730 8731 4102 +a 8731 8732 4102 +a 8732 8733 4102 +a 8733 8734 4102 +a 8734 8735 4102 +a 8735 8736 4102 +a 8736 8737 4102 +a 8737 8738 4102 +a 8738 8739 4102 +a 8739 8740 4102 +a 8740 8741 4102 +a 8741 8742 4102 +a 8742 8743 4102 +a 8743 8744 4102 +a 8744 8745 4102 +a 8745 8746 4102 +a 8746 8747 4102 +a 8747 8748 4102 +a 8748 8749 4102 +a 8749 8750 4102 +a 8750 8751 4102 +a 8751 8752 4102 +a 8752 8753 4102 +a 8753 8754 4102 +a 8754 8755 4102 +a 8755 8756 4102 +a 8756 8757 4102 +a 8757 8758 4102 +a 8758 8759 4102 +a 8759 8760 4102 +a 8760 8761 4102 +a 8761 8762 4102 +a 8762 8763 4102 +a 8763 8764 4102 +a 8764 8765 4102 +a 8765 8766 4102 +a 8766 8767 4102 +a 8767 8768 4102 +a 8768 8769 4102 +a 8769 8770 4102 +a 8770 8771 4102 +a 8771 8772 4102 +a 8772 8773 4102 +a 8773 8774 4102 +a 8774 8775 4102 +a 8775 8776 4102 +a 8776 8777 4102 +a 8777 8778 4102 +a 8778 8779 4102 +a 8779 8780 4102 +a 8780 8781 4102 +a 8781 8782 4102 +a 8782 8783 4102 +a 8783 8784 4102 +a 8784 8785 4102 +a 8785 8786 4102 +a 8786 8787 4102 +a 8787 8788 4102 +a 8788 8789 4102 +a 8789 8790 4102 +a 8790 8791 4102 +a 8791 8792 4102 +a 8792 8793 4102 +a 8793 8794 4102 +a 8794 8795 4102 +a 8795 8796 4102 +a 8796 8797 4102 +a 8797 8798 4102 +a 8798 8799 4102 +a 8799 8800 4102 +a 8800 8801 4102 +a 8801 8802 4102 +a 8802 8803 4102 +a 8803 8804 4102 +a 8804 8805 4102 +a 8805 8806 4102 +a 8806 8807 4102 +a 8807 8808 4102 +a 8808 8809 4102 +a 8809 8810 4102 +a 8810 8811 4102 +a 8811 8812 4102 +a 8812 8813 4102 +a 8813 8814 4102 +a 8814 8815 4102 +a 8815 8816 4102 +a 8816 8817 4102 +a 8817 8818 4102 +a 8818 8819 4102 +a 8819 8820 4102 +a 8820 8821 4102 +a 8821 8822 4102 +a 8822 8823 4102 +a 8823 8824 4102 +a 8824 8825 4102 +a 8825 8826 4102 +a 8826 8827 4102 +a 8827 8828 4102 +a 8828 8829 4102 +a 8829 8830 4102 +a 8830 8831 4102 +a 8831 8832 4102 +a 8832 8833 4102 +a 8833 8834 4102 +a 8834 8835 4102 +a 8835 8836 4102 +a 8836 8837 4102 +a 8837 8838 4102 +a 8838 8839 4102 +a 8839 8840 4102 +a 8840 8841 4102 +a 8841 8842 4102 +a 8842 8843 4102 +a 8843 8844 4102 +a 8844 8845 4102 +a 8845 8846 4102 +a 8846 8847 4102 +a 8847 8848 4102 +a 8848 8849 4102 +a 8849 8850 4102 +a 8850 8851 4102 +a 8851 8852 4102 +a 8852 8853 4102 +a 8853 8854 4102 +a 8854 8855 4102 +a 8855 8856 4102 +a 8856 8857 4102 +a 8857 8858 4102 +a 8858 8859 4102 +a 8859 8860 4102 +a 8860 8861 4102 +a 8861 8862 4102 +a 8862 8863 4102 +a 8863 8864 4102 +a 8864 8865 4102 +a 8865 8866 4102 +a 8866 8867 4102 +a 8867 8868 4102 +a 8868 8869 4102 +a 8869 8870 4102 +a 8870 8871 4102 +a 8871 8872 4102 +a 8872 8873 4102 +a 8873 8874 4102 +a 8874 8875 4102 +a 8875 8876 4102 +a 8876 8877 4102 +a 8877 8878 4102 +a 8878 8879 4102 +a 8879 8880 4102 +a 8880 8881 4102 +a 8881 8882 4102 +a 8882 8883 4102 +a 8883 8884 4102 +a 8884 8885 4102 +a 8885 8886 4102 +a 8886 8887 4102 +a 8887 8888 4102 +a 8888 8889 4102 +a 8889 8890 4102 +a 8890 8891 4102 +a 8891 8892 4102 +a 8892 8893 4102 +a 8893 8894 4102 +a 8894 8895 4102 +a 8895 8896 4102 +a 8896 8897 4102 +a 8897 8898 4102 +a 8898 8899 4102 +a 8899 8900 4102 +a 8900 8901 4102 +a 8901 8902 4102 +a 8902 8903 4102 +a 8903 8904 4102 +a 8904 8905 4102 +a 8905 8906 4102 +a 8906 8907 4102 +a 8907 8908 4102 +a 8908 8909 4102 +a 8909 8910 4102 +a 8910 8911 4102 +a 8911 8912 4102 +a 8912 8913 4102 +a 8913 8914 4102 +a 8914 8915 4102 +a 8915 8916 4102 +a 8916 8917 4102 +a 8917 8918 4102 +a 8918 8919 4102 +a 8919 8920 4102 +a 8920 8921 4102 +a 8921 8922 4102 +a 8922 8923 4102 +a 8923 8924 4102 +a 8924 8925 4102 +a 8925 8926 4102 +a 8926 8927 4102 +a 8927 8928 4102 +a 8928 8929 4102 +a 8929 8930 4102 +a 8930 8931 4102 +a 8931 8932 4102 +a 8932 8933 4102 +a 8933 8934 4102 +a 8934 8935 4102 +a 8935 8936 4102 +a 8936 8937 4102 +a 8937 8938 4102 +a 8938 8939 4102 +a 8939 8940 4102 +a 8940 8941 4102 +a 8941 8942 4102 +a 8942 8943 4102 +a 8943 8944 4102 +a 8944 8945 4102 +a 8945 8946 4102 +a 8946 8947 4102 +a 8947 8948 4102 +a 8948 8949 4102 +a 8949 8950 4102 +a 8950 8951 4102 +a 8951 8952 4102 +a 8952 8953 4102 +a 8953 8954 4102 +a 8954 8955 4102 +a 8955 8956 4102 +a 8956 8957 4102 +a 8957 8958 4102 +a 8958 8959 4102 +a 8959 8960 4102 +a 8960 8961 4102 +a 8961 8962 4102 +a 8962 8963 4102 +a 8963 8964 4102 +a 8964 8965 4102 +a 8965 8966 4102 +a 8966 8967 4102 +a 8967 8968 4102 +a 8968 8969 4102 +a 8969 8970 4102 +a 8970 8971 4102 +a 8971 8972 4102 +a 8972 8973 4102 +a 8973 8974 4102 +a 8974 8975 4102 +a 8975 8976 4102 +a 8976 8977 4102 +a 8977 8978 4102 +a 8978 8979 4102 +a 8979 8980 4102 +a 8980 8981 4102 +a 8981 8982 4102 +a 8982 8983 4102 +a 8983 8984 4102 +a 8984 8985 4102 +a 8985 8986 4102 +a 8986 8987 4102 +a 8987 8988 4102 +a 8988 8989 4102 +a 8989 8990 4102 +a 8990 8991 4102 +a 8991 8992 4102 +a 8992 8993 4102 +a 8993 8994 4102 +a 8994 8995 4102 +a 8995 8996 4102 +a 8996 8997 4102 +a 8997 8998 4102 +a 8998 8999 4102 +a 8999 9000 4102 +a 9000 9001 4102 +a 9001 9002 4102 +a 9002 9003 4102 +a 9003 9004 4102 +a 9004 9005 4102 +a 9005 9006 4102 +a 9006 9007 4102 +a 9007 9008 4102 +a 9008 9009 4102 +a 9009 9010 4102 +a 9010 9011 4102 +a 9011 9012 4102 +a 9012 9013 4102 +a 9013 9014 4102 +a 9014 9015 4102 +a 9015 9016 4102 +a 9016 9017 4102 +a 9017 9018 4102 +a 9018 9019 4102 +a 9019 9020 4102 +a 9020 9021 4102 +a 9021 9022 4102 +a 9022 9023 4102 +a 9023 9024 4102 +a 9024 9025 4102 +a 9025 9026 4102 +a 9026 9027 4102 +a 9027 9028 4102 +a 9028 9029 4102 +a 9029 9030 4102 +a 9030 9031 4102 +a 9031 9032 4102 +a 9032 9033 4102 +a 9033 9034 4102 +a 9034 9035 4102 +a 9035 9036 4102 +a 9036 9037 4102 +a 9037 9038 4102 +a 9038 9039 4102 +a 9039 9040 4102 +a 9040 9041 4102 +a 9041 9042 4102 +a 9042 9043 4102 +a 9043 9044 4102 +a 9044 9045 4102 +a 9045 9046 4102 +a 9046 9047 4102 +a 9047 9048 4102 +a 9048 9049 4102 +a 9049 9050 4102 +a 9050 9051 4102 +a 9051 9052 4102 +a 9052 9053 4102 +a 9053 9054 4102 +a 9054 9055 4102 +a 9055 9056 4102 +a 9056 9057 4102 +a 9057 9058 4102 +a 9058 9059 4102 +a 9059 9060 4102 +a 9060 9061 4102 +a 9061 9062 4102 +a 9062 9063 4102 +a 9063 9064 4102 +a 9064 9065 4102 +a 9065 9066 4102 +a 9066 9067 4102 +a 9067 9068 4102 +a 9068 9069 4102 +a 9069 9070 4102 +a 9070 9071 4102 +a 9071 9072 4102 +a 9072 9073 4102 +a 9073 9074 4102 +a 9074 9075 4102 +a 9075 9076 4102 +a 9076 9077 4102 +a 9077 9078 4102 +a 9078 9079 4102 +a 9079 9080 4102 +a 9080 9081 4102 +a 9081 9082 4102 +a 9082 9083 4102 +a 9083 9084 4102 +a 9084 9085 4102 +a 9085 9086 4102 +a 9086 9087 4102 +a 9087 9088 4102 +a 9088 9089 4102 +a 9089 9090 4102 +a 9090 9091 4102 +a 9091 9092 4102 +a 9092 9093 4102 +a 9093 9094 4102 +a 9094 9095 4102 +a 9095 9096 4102 +a 9096 9097 4102 +a 9097 9098 4102 +a 9098 9099 4102 +a 9099 9100 4102 +a 9100 9101 4102 +a 9101 9102 4102 +a 9102 9103 4102 +a 9103 9104 4102 +a 9104 9105 4102 +a 9105 9106 4102 +a 9106 9107 4102 +a 9107 9108 4102 +a 9108 9109 4102 +a 9109 9110 4102 +a 9110 9111 4102 +a 9111 9112 4102 +a 9112 9113 4102 +a 9113 9114 4102 +a 9114 9115 4102 +a 9115 9116 4102 +a 9116 9117 4102 +a 9117 9118 4102 +a 9118 9119 4102 +a 9119 9120 4102 +a 9120 9121 4102 +a 9121 9122 4102 +a 9122 9123 4102 +a 9123 9124 4102 +a 9124 9125 4102 +a 9125 9126 4102 +a 9126 9127 4102 +a 9127 9128 4102 +a 9128 9129 4102 +a 9129 9130 4102 +a 9130 9131 4102 +a 9131 9132 4102 +a 9132 9133 4102 +a 9133 9134 4102 +a 9134 9135 4102 +a 9135 9136 4102 +a 9136 9137 4102 +a 9137 9138 4102 +a 9138 9139 4102 +a 9139 9140 4102 +a 9140 9141 4102 +a 9141 9142 4102 +a 9142 9143 4102 +a 9143 9144 4102 +a 9144 9145 4102 +a 9145 9146 4102 +a 9146 9147 4102 +a 9147 9148 4102 +a 9148 9149 4102 +a 9149 9150 4102 +a 9150 9151 4102 +a 9151 9152 4102 +a 9152 9153 4102 +a 9153 9154 4102 +a 9154 9155 4102 +a 9155 9156 4102 +a 9156 9157 4102 +a 9157 9158 4102 +a 9158 9159 4102 +a 9159 9160 4102 +a 9160 9161 4102 +a 9161 9162 4102 +a 9162 9163 4102 +a 9163 9164 4102 +a 9164 9165 4102 +a 9165 9166 4102 +a 9166 9167 4102 +a 9167 9168 4102 +a 9168 9169 4102 +a 9169 9170 4102 +a 9170 9171 4102 +a 9171 9172 4102 +a 9172 9173 4102 +a 9173 9174 4102 +a 9174 9175 4102 +a 9175 9176 4102 +a 9176 9177 4102 +a 9177 9178 4102 +a 9178 9179 4102 +a 9179 9180 4102 +a 9180 9181 4102 +a 9181 9182 4102 +a 9182 9183 4102 +a 9183 9184 4102 +a 9184 9185 4102 +a 9185 9186 4102 +a 9186 9187 4102 +a 9187 9188 4102 +a 9188 9189 4102 +a 9189 9190 4102 +a 9190 9191 4102 +a 9191 9192 4102 +a 9192 9193 4102 +a 9193 9194 4102 +a 9194 9195 4102 +a 9195 9196 4102 +a 9196 9197 4102 +a 9197 9198 4102 +a 9198 9199 4102 +a 9199 9200 4102 +a 9200 9201 4102 +a 9201 9202 4102 +a 9202 9203 4102 +a 9203 9204 4102 +a 9204 9205 4102 +a 9205 9206 4102 +a 9206 9207 4102 +a 9207 9208 4102 +a 9208 9209 4102 +a 9209 9210 4102 +a 9210 9211 4102 +a 9211 9212 4102 +a 9212 9213 4102 +a 9213 9214 4102 +a 9214 9215 4102 +a 9215 9216 4102 +a 9216 9217 4102 +a 9217 9218 4102 +a 9218 9219 4102 +a 9219 9220 4102 +a 9220 9221 4102 +a 9221 9222 4102 +a 9222 9223 4102 +a 9223 9224 4102 +a 9224 9225 4102 +a 9225 9226 4102 +a 9226 9227 4102 +a 9227 9228 4102 +a 9228 9229 4102 +a 9229 9230 4102 +a 9230 9231 4102 +a 9231 9232 4102 +a 9232 9233 4102 +a 9233 9234 4102 +a 9234 9235 4102 +a 9235 9236 4102 +a 9236 9237 4102 +a 9237 9238 4102 +a 9238 9239 4102 +a 9239 9240 4102 +a 9240 9241 4102 +a 9241 9242 4102 +a 9242 9243 4102 +a 9243 9244 4102 +a 9244 9245 4102 +a 9245 9246 4102 +a 9246 9247 4102 +a 9247 9248 4102 +a 9248 9249 4102 +a 9249 9250 4102 +a 9250 9251 4102 +a 9251 9252 4102 +a 9252 9253 4102 +a 9253 9254 4102 +a 9254 9255 4102 +a 9255 9256 4102 +a 9256 9257 4102 +a 9257 9258 4102 +a 9258 9259 4102 +a 9259 9260 4102 +a 9260 9261 4102 +a 9261 9262 4102 +a 9262 9263 4102 +a 9263 9264 4102 +a 9264 9265 4102 +a 9265 9266 4102 +a 9266 9267 4102 +a 9267 9268 4102 +a 9268 9269 4102 +a 9269 9270 4102 +a 9270 9271 4102 +a 9271 9272 4102 +a 9272 9273 4102 +a 9273 9274 4102 +a 9274 9275 4102 +a 9275 9276 4102 +a 9276 9277 4102 +a 9277 9278 4102 +a 9278 9279 4102 +a 9279 9280 4102 +a 9280 9281 4102 +a 9281 9282 4102 +a 9282 9283 4102 +a 9283 9284 4102 +a 9284 9285 4102 +a 9285 9286 4102 +a 9286 9287 4102 +a 9287 9288 4102 +a 9288 9289 4102 +a 9289 9290 4102 +a 9290 9291 4102 +a 9291 9292 4102 +a 9292 9293 4102 +a 9293 9294 4102 +a 9294 9295 4102 +a 9295 9296 4102 +a 9296 9297 4102 +a 9297 9298 4102 +a 9298 9299 4102 +a 9299 9300 4102 +a 9300 9301 4102 +a 9301 9302 4102 +a 9302 9303 4102 +a 9303 9304 4102 +a 9304 9305 4102 +a 9305 9306 4102 +a 9306 9307 4102 +a 9307 9308 4102 +a 9308 9309 4102 +a 9309 9310 4102 +a 9310 9311 4102 +a 9311 9312 4102 +a 9312 9313 4102 +a 9313 9314 4102 +a 9314 9315 4102 +a 9315 9316 4102 +a 9316 9317 4102 +a 9317 9318 4102 +a 9318 9319 4102 +a 9319 9320 4102 +a 9320 9321 4102 +a 9321 9322 4102 +a 9322 9323 4102 +a 9323 9324 4102 +a 9324 9325 4102 +a 9325 9326 4102 +a 9326 9327 4102 +a 9327 9328 4102 +a 9328 9329 4102 +a 9329 9330 4102 +a 9330 9331 4102 +a 9331 9332 4102 +a 9332 9333 4102 +a 9333 9334 4102 +a 9334 9335 4102 +a 9335 9336 4102 +a 9336 9337 4102 +a 9337 9338 4102 +a 9338 9339 4102 +a 9339 9340 4102 +a 9340 9341 4102 +a 9341 9342 4102 +a 9342 9343 4102 +a 9343 9344 4102 +a 9344 9345 4102 +a 9345 9346 4102 +a 9346 9347 4102 +a 9347 9348 4102 +a 9348 9349 4102 +a 9349 9350 4102 +a 9350 9351 4102 +a 9351 9352 4102 +a 9352 9353 4102 +a 9353 9354 4102 +a 9354 9355 4102 +a 9355 9356 4102 +a 9356 9357 4102 +a 9357 9358 4102 +a 9358 9359 4102 +a 9359 9360 4102 +a 9360 9361 4102 +a 9361 9362 4102 +a 9362 9363 4102 +a 9363 9364 4102 +a 9364 9365 4102 +a 9365 9366 4102 +a 9366 9367 4102 +a 9367 9368 4102 +a 9368 9369 4102 +a 9369 9370 4102 +a 9370 9371 4102 +a 9371 9372 4102 +a 9372 9373 4102 +a 9373 9374 4102 +a 9374 9375 4102 +a 9375 9376 4102 +a 9376 9377 4102 +a 9377 9378 4102 +a 9378 9379 4102 +a 9379 9380 4102 +a 9380 9381 4102 +a 9381 9382 4102 +a 9382 9383 4102 +a 9383 9384 4102 +a 9384 9385 4102 +a 9385 9386 4102 +a 9386 9387 4102 +a 9387 9388 4102 +a 9388 9389 4102 +a 9389 9390 4102 +a 9390 9391 4102 +a 9391 9392 4102 +a 9392 9393 4102 +a 9393 9394 4102 +a 9394 9395 4102 +a 9395 9396 4102 +a 9396 9397 4102 +a 9397 9398 4102 +a 9398 9399 4102 +a 9399 9400 4102 +a 9400 9401 4102 +a 9401 9402 4102 +a 9402 9403 4102 +a 9403 9404 4102 +a 9404 9405 4102 +a 9405 9406 4102 +a 9406 9407 4102 +a 9407 9408 4102 +a 9408 9409 4102 +a 9409 9410 4102 +a 9410 9411 4102 +a 9411 9412 4102 +a 9412 9413 4102 +a 9413 9414 4102 +a 9414 9415 4102 +a 9415 9416 4102 +a 9416 9417 4102 +a 9417 9418 4102 +a 9418 9419 4102 +a 9419 9420 4102 +a 9420 9421 4102 +a 9421 9422 4102 +a 9422 9423 4102 +a 9423 9424 4102 +a 9424 9425 4102 +a 9425 9426 4102 +a 9426 9427 4102 +a 9427 9428 4102 +a 9428 9429 4102 +a 9429 9430 4102 +a 9430 9431 4102 +a 9431 9432 4102 +a 9432 9433 4102 +a 9433 9434 4102 +a 9434 9435 4102 +a 9435 9436 4102 +a 9436 9437 4102 +a 9437 9438 4102 +a 9438 9439 4102 +a 9439 9440 4102 +a 9440 9441 4102 +a 9441 9442 4102 +a 9442 9443 4102 +a 9443 9444 4102 +a 9444 9445 4102 +a 9445 9446 4102 +a 9446 9447 4102 +a 9447 9448 4102 +a 9448 9449 4102 +a 9449 9450 4102 +a 9450 9451 4102 +a 9451 9452 4102 +a 9452 9453 4102 +a 9453 9454 4102 +a 9454 9455 4102 +a 9455 9456 4102 +a 9456 9457 4102 +a 9457 9458 4102 +a 9458 9459 4102 +a 9459 9460 4102 +a 9460 9461 4102 +a 9461 9462 4102 +a 9462 9463 4102 +a 9463 9464 4102 +a 9464 9465 4102 +a 9465 9466 4102 +a 9466 9467 4102 +a 9467 9468 4102 +a 9468 9469 4102 +a 9469 9470 4102 +a 9470 9471 4102 +a 9471 9472 4102 +a 9472 9473 4102 +a 9473 9474 4102 +a 9474 9475 4102 +a 9475 9476 4102 +a 9476 9477 4102 +a 9477 9478 4102 +a 9478 9479 4102 +a 9479 9480 4102 +a 9480 9481 4102 +a 9481 9482 4102 +a 9482 9483 4102 +a 9483 9484 4102 +a 9484 9485 4102 +a 9485 9486 4102 +a 9486 9487 4102 +a 9487 9488 4102 +a 9488 9489 4102 +a 9489 9490 4102 +a 9490 9491 4102 +a 9491 9492 4102 +a 9492 9493 4102 +a 9493 9494 4102 +a 9494 9495 4102 +a 9495 9496 4102 +a 9496 9497 4102 +a 9497 9498 4102 +a 9498 9499 4102 +a 9499 9500 4102 +a 9500 9501 4102 +a 9501 9502 4102 +a 9502 9503 4102 +a 9503 9504 4102 +a 9504 9505 4102 +a 9505 9506 4102 +a 9506 9507 4102 +a 9507 9508 4102 +a 9508 9509 4102 +a 9509 9510 4102 +a 9510 9511 4102 +a 9511 9512 4102 +a 9512 9513 4102 +a 9513 9514 4102 +a 9514 9515 4102 +a 9515 9516 4102 +a 9516 9517 4102 +a 9517 9518 4102 +a 9518 9519 4102 +a 9519 9520 4102 +a 9520 9521 4102 +a 9521 9522 4102 +a 9522 9523 4102 +a 9523 9524 4102 +a 9524 9525 4102 +a 9525 9526 4102 +a 9526 9527 4102 +a 9527 9528 4102 +a 9528 9529 4102 +a 9529 9530 4102 +a 9530 9531 4102 +a 9531 9532 4102 +a 9532 9533 4102 +a 9533 9534 4102 +a 9534 9535 4102 +a 9535 9536 4102 +a 9536 9537 4102 +a 9537 9538 4102 +a 9538 9539 4102 +a 9539 9540 4102 +a 9540 9541 4102 +a 9541 9542 4102 +a 9542 9543 4102 +a 9543 9544 4102 +a 9544 9545 4102 +a 9545 9546 4102 +a 9546 9547 4102 +a 9547 9548 4102 +a 9548 9549 4102 +a 9549 9550 4102 +a 9550 9551 4102 +a 9551 9552 4102 +a 9552 9553 4102 +a 9553 9554 4102 +a 9554 9555 4102 +a 9555 9556 4102 +a 9556 9557 4102 +a 9557 9558 4102 +a 9558 9559 4102 +a 9559 9560 4102 +a 9560 9561 4102 +a 9561 9562 4102 +a 9562 9563 4102 +a 9563 9564 4102 +a 9564 9565 4102 +a 9565 9566 4102 +a 9566 9567 4102 +a 9567 9568 4102 +a 9568 9569 4102 +a 9569 9570 4102 +a 9570 9571 4102 +a 9571 9572 4102 +a 9572 9573 4102 +a 9573 9574 4102 +a 9574 9575 4102 +a 9575 9576 4102 +a 9576 9577 4102 +a 9577 9578 4102 +a 9578 9579 4102 +a 9579 9580 4102 +a 9580 9581 4102 +a 9581 9582 4102 +a 9582 9583 4102 +a 9583 9584 4102 +a 9584 9585 4102 +a 9585 9586 4102 +a 9586 9587 4102 +a 9587 9588 4102 +a 9588 9589 4102 +a 9589 9590 4102 +a 9590 9591 4102 +a 9591 9592 4102 +a 9592 9593 4102 +a 9593 9594 4102 +a 9594 9595 4102 +a 9595 9596 4102 +a 9596 9597 4102 +a 9597 9598 4102 +a 9598 9599 4102 +a 9599 9600 4102 +a 9600 9601 4102 +a 9601 9602 4102 +a 9602 9603 4102 +a 9603 9604 4102 +a 9604 9605 4102 +a 9605 9606 4102 +a 9606 9607 4102 +a 9607 9608 4102 +a 9608 9609 4102 +a 9609 9610 4102 +a 9610 9611 4102 +a 9611 9612 4102 +a 9612 9613 4102 +a 9613 9614 4102 +a 9614 9615 4102 +a 9615 9616 4102 +a 9616 9617 4102 +a 9617 9618 4102 +a 9618 9619 4102 +a 9619 9620 4102 +a 9620 9621 4102 +a 9621 9622 4102 +a 9622 9623 4102 +a 9623 9624 4102 +a 9624 9625 4102 +a 9625 9626 4102 +a 9626 9627 4102 +a 9627 9628 4102 +a 9628 9629 4102 +a 9629 9630 4102 +a 9630 9631 4102 +a 9631 9632 4102 +a 9632 9633 4102 +a 9633 9634 4102 +a 9634 9635 4102 +a 9635 9636 4102 +a 9636 9637 4102 +a 9637 9638 4102 +a 9638 9639 4102 +a 9639 9640 4102 +a 9640 9641 4102 +a 9641 9642 4102 +a 9642 9643 4102 +a 9643 9644 4102 +a 9644 9645 4102 +a 9645 9646 4102 +a 9646 9647 4102 +a 9647 9648 4102 +a 9648 9649 4102 +a 9649 9650 4102 +a 9650 9651 4102 +a 9651 9652 4102 +a 9652 9653 4102 +a 9653 9654 4102 +a 9654 9655 4102 +a 9655 9656 4102 +a 9656 9657 4102 +a 9657 9658 4102 +a 9658 9659 4102 +a 9659 9660 4102 +a 9660 9661 4102 +a 9661 9662 4102 +a 9662 9663 4102 +a 9663 9664 4102 +a 9664 9665 4102 +a 9665 9666 4102 +a 9666 9667 4102 +a 9667 9668 4102 +a 9668 9669 4102 +a 9669 9670 4102 +a 9670 9671 4102 +a 9671 9672 4102 +a 9672 9673 4102 +a 9673 9674 4102 +a 9674 9675 4102 +a 9675 9676 4102 +a 9676 9677 4102 +a 9677 9678 4102 +a 9678 9679 4102 +a 9679 9680 4102 +a 9680 9681 4102 +a 9681 9682 4102 +a 9682 9683 4102 +a 9683 9684 4102 +a 9684 9685 4102 +a 9685 9686 4102 +a 9686 9687 4102 +a 9687 9688 4102 +a 9688 9689 4102 +a 9689 9690 4102 +a 9690 9691 4102 +a 9691 9692 4102 +a 9692 9693 4102 +a 9693 9694 4102 +a 9694 9695 4102 +a 9695 9696 4102 +a 9696 9697 4102 +a 9697 9698 4102 +a 9698 9699 4102 +a 9699 9700 4102 +a 9700 9701 4102 +a 9701 9702 4102 +a 9702 9703 4102 +a 9703 9704 4102 +a 9704 9705 4102 +a 9705 9706 4102 +a 9706 9707 4102 +a 9707 9708 4102 +a 9708 9709 4102 +a 9709 9710 4102 +a 9710 9711 4102 +a 9711 9712 4102 +a 9712 9713 4102 +a 9713 9714 4102 +a 9714 9715 4102 +a 9715 9716 4102 +a 9716 9717 4102 +a 9717 9718 4102 +a 9718 9719 4102 +a 9719 9720 4102 +a 9720 9721 4102 +a 9721 9722 4102 +a 9722 9723 4102 +a 9723 9724 4102 +a 9724 9725 4102 +a 9725 9726 4102 +a 9726 9727 4102 +a 9727 9728 4102 +a 9728 9729 4102 +a 9729 9730 4102 +a 9730 9731 4102 +a 9731 9732 4102 +a 9732 9733 4102 +a 9733 9734 4102 +a 9734 9735 4102 +a 9735 9736 4102 +a 9736 9737 4102 +a 9737 9738 4102 +a 9738 9739 4102 +a 9739 9740 4102 +a 9740 9741 4102 +a 9741 9742 4102 +a 9742 9743 4102 +a 9743 9744 4102 +a 9744 9745 4102 +a 9745 9746 4102 +a 9746 9747 4102 +a 9747 9748 4102 +a 9748 9749 4102 +a 9749 9750 4102 +a 9750 9751 4102 +a 9751 9752 4102 +a 9752 9753 4102 +a 9753 9754 4102 +a 9754 9755 4102 +a 9755 9756 4102 +a 9756 9757 4102 +a 9757 9758 4102 +a 9758 9759 4102 +a 9759 9760 4102 +a 9760 9761 4102 +a 9761 9762 4102 +a 9762 9763 4102 +a 9763 9764 4102 +a 9764 9765 4102 +a 9765 9766 4102 +a 9766 9767 4102 +a 9767 9768 4102 +a 9768 9769 4102 +a 9769 9770 4102 +a 9770 9771 4102 +a 9771 9772 4102 +a 9772 9773 4102 +a 9773 9774 4102 +a 9774 9775 4102 +a 9775 9776 4102 +a 9776 9777 4102 +a 9777 9778 4102 +a 9778 9779 4102 +a 9779 9780 4102 +a 9780 9781 4102 +a 9781 9782 4102 +a 9782 9783 4102 +a 9783 9784 4102 +a 9784 9785 4102 +a 9785 9786 4102 +a 9786 9787 4102 +a 9787 9788 4102 +a 9788 9789 4102 +a 9789 9790 4102 +a 9790 9791 4102 +a 9791 9792 4102 +a 9792 9793 4102 +a 9793 9794 4102 +a 9794 9795 4102 +a 9795 9796 4102 +a 9796 9797 4102 +a 9797 9798 4102 +a 9798 9799 4102 +a 9799 9800 4102 +a 9800 9801 4102 +a 9801 9802 4102 +a 9802 9803 4102 +a 9803 9804 4102 +a 9804 9805 4102 +a 9805 9806 4102 +a 9806 9807 4102 +a 9807 9808 4102 +a 9808 9809 4102 +a 9809 9810 4102 +a 9810 9811 4102 +a 9811 9812 4102 +a 9812 9813 4102 +a 9813 9814 4102 +a 9814 9815 4102 +a 9815 9816 4102 +a 9816 9817 4102 +a 9817 9818 4102 +a 9818 9819 4102 +a 9819 9820 4102 +a 9820 9821 4102 +a 9821 9822 4102 +a 9822 9823 4102 +a 9823 9824 4102 +a 9824 9825 4102 +a 9825 9826 4102 +a 9826 9827 4102 +a 9827 9828 4102 +a 9828 9829 4102 +a 9829 9830 4102 +a 9830 9831 4102 +a 9831 9832 4102 +a 9832 9833 4102 +a 9833 9834 4102 +a 9834 9835 4102 +a 9835 9836 4102 +a 9836 9837 4102 +a 9837 9838 4102 +a 9838 9839 4102 +a 9839 9840 4102 +a 9840 9841 4102 +a 9841 9842 4102 +a 9842 9843 4102 +a 9843 9844 4102 +a 9844 9845 4102 +a 9845 9846 4102 +a 9846 9847 4102 +a 9847 9848 4102 +a 9848 9849 4102 +a 9849 9850 4102 +a 9850 9851 4102 +a 9851 9852 4102 +a 9852 9853 4102 +a 9853 9854 4102 +a 9854 9855 4102 +a 9855 9856 4102 +a 9856 9857 4102 +a 9857 9858 4102 +a 9858 9859 4102 +a 9859 9860 4102 +a 9860 9861 4102 +a 9861 9862 4102 +a 9862 9863 4102 +a 9863 9864 4102 +a 9864 9865 4102 +a 9865 9866 4102 +a 9866 9867 4102 +a 9867 9868 4102 +a 9868 9869 4102 +a 9869 9870 4102 +a 9870 9871 4102 +a 9871 9872 4102 +a 9872 9873 4102 +a 9873 9874 4102 +a 9874 9875 4102 +a 9875 9876 4102 +a 9876 9877 4102 +a 9877 9878 4102 +a 9878 9879 4102 +a 9879 9880 4102 +a 9880 9881 4102 +a 9881 9882 4102 +a 9882 9883 4102 +a 9883 9884 4102 +a 9884 9885 4102 +a 9885 9886 4102 +a 9886 9887 4102 +a 9887 9888 4102 +a 9888 9889 4102 +a 9889 9890 4102 +a 9890 9891 4102 +a 9891 9892 4102 +a 9892 9893 4102 +a 9893 9894 4102 +a 9894 9895 4102 +a 9895 9896 4102 +a 9896 9897 4102 +a 9897 9898 4102 +a 9898 9899 4102 +a 9899 9900 4102 +a 9900 9901 4102 +a 9901 9902 4102 +a 9902 9903 4102 +a 9903 9904 4102 +a 9904 9905 4102 +a 9905 9906 4102 +a 9906 9907 4102 +a 9907 9908 4102 +a 9908 9909 4102 +a 9909 9910 4102 +a 9910 9911 4102 +a 9911 9912 4102 +a 9912 9913 4102 +a 9913 9914 4102 +a 9914 9915 4102 +a 9915 9916 4102 +a 9916 9917 4102 +a 9917 9918 4102 +a 9918 9919 4102 +a 9919 9920 4102 +a 9920 9921 4102 +a 9921 9922 4102 +a 9922 9923 4102 +a 9923 9924 4102 +a 9924 9925 4102 +a 9925 9926 4102 +a 9926 9927 4102 +a 9927 9928 4102 +a 9928 9929 4102 +a 9929 9930 4102 +a 9930 9931 4102 +a 9931 9932 4102 +a 9932 9933 4102 +a 9933 9934 4102 +a 9934 9935 4102 +a 9935 9936 4102 +a 9936 9937 4102 +a 9937 9938 4102 +a 9938 9939 4102 +a 9939 9940 4102 +a 9940 9941 4102 +a 9941 9942 4102 +a 9942 9943 4102 +a 9943 9944 4102 +a 9944 9945 4102 +a 9945 9946 4102 +a 9946 9947 4102 +a 9947 9948 4102 +a 9948 9949 4102 +a 9949 9950 4102 +a 9950 9951 4102 +a 9951 9952 4102 +a 9952 9953 4102 +a 9953 9954 4102 +a 9954 9955 4102 +a 9955 9956 4102 +a 9956 9957 4102 +a 9957 9958 4102 +a 9958 9959 4102 +a 9959 9960 4102 +a 9960 9961 4102 +a 9961 9962 4102 +a 9962 9963 4102 +a 9963 9964 4102 +a 9964 9965 4102 +a 9965 9966 4102 +a 9966 9967 4102 +a 9967 9968 4102 +a 9968 9969 4102 +a 9969 9970 4102 +a 9970 9971 4102 +a 9971 9972 4102 +a 9972 9973 4102 +a 9973 9974 4102 +a 9974 9975 4102 +a 9975 9976 4102 +a 9976 9977 4102 +a 9977 9978 4102 +a 9978 9979 4102 +a 9979 9980 4102 +a 9980 9981 4102 +a 9981 9982 4102 +a 9982 9983 4102 +a 9983 9984 4102 +a 9984 9985 4102 +a 9985 9986 4102 +a 9986 9987 4102 +a 9987 9988 4102 +a 9988 9989 4102 +a 9989 9990 4102 +a 9990 9991 4102 +a 9991 9992 4102 +a 9992 9993 4102 +a 9993 9994 4102 +a 9994 9995 4102 +a 9995 9996 4102 +a 9996 9997 4102 +a 9997 9998 4102 +a 9998 9999 4102 +a 9999 10000 4102 +a 10000 10001 4102 +a 10001 10002 4102 +a 10002 10003 4102 +a 10003 10004 4102 +a 10004 10005 4102 +a 10005 10006 4102 +a 10006 10007 4102 +a 10007 10008 4102 +a 10008 10009 4102 +a 10009 10010 4102 +a 10010 10011 4102 +a 10011 10012 4102 +a 10012 10013 4102 +a 10013 10014 4102 +a 10014 10015 4102 +a 10015 10016 4102 +a 10016 10017 4102 +a 10017 10018 4102 +a 10018 10019 4102 +a 10019 10020 4102 +a 10020 10021 4102 +a 10021 10022 4102 +a 10022 10023 4102 +a 10023 10024 4102 +a 10024 10025 4102 +a 10025 10026 4102 +a 10026 10027 4102 +a 10027 10028 4102 +a 10028 10029 4102 +a 10029 10030 4102 +a 10030 10031 4102 +a 10031 10032 4102 +a 10032 10033 4102 +a 10033 10034 4102 +a 10034 10035 4102 +a 10035 10036 4102 +a 10036 10037 4102 +a 10037 10038 4102 +a 10038 10039 4102 +a 10039 10040 4102 +a 10040 10041 4102 +a 10041 10042 4102 +a 10042 10043 4102 +a 10043 10044 4102 +a 10044 10045 4102 +a 10045 10046 4102 +a 10046 10047 4102 +a 10047 10048 4102 +a 10048 10049 4102 +a 10049 10050 4102 +a 10050 10051 4102 +a 10051 10052 4102 +a 10052 10053 4102 +a 10053 10054 4102 +a 10054 10055 4102 +a 10055 10056 4102 +a 10056 10057 4102 +a 10057 10058 4102 +a 10058 10059 4102 +a 10059 10060 4102 +a 10060 10061 4102 +a 10061 10062 4102 +a 10062 10063 4102 +a 10063 10064 4102 +a 10064 10065 4102 +a 10065 10066 4102 +a 10066 10067 4102 +a 10067 10068 4102 +a 10068 10069 4102 +a 10069 10070 4102 +a 10070 10071 4102 +a 10071 10072 4102 +a 10072 10073 4102 +a 10073 10074 4102 +a 10074 10075 4102 +a 10075 10076 4102 +a 10076 10077 4102 +a 10077 10078 4102 +a 10078 10079 4102 +a 10079 10080 4102 +a 10080 10081 4102 +a 10081 10082 4102 +a 10082 10083 4102 +a 10083 10084 4102 +a 10084 10085 4102 +a 10085 10086 4102 +a 10086 10087 4102 +a 10087 10088 4102 +a 10088 10089 4102 +a 10089 10090 4102 +a 10090 10091 4102 +a 10091 10092 4102 +a 10092 10093 4102 +a 10093 10094 4102 +a 10094 10095 4102 +a 10095 10096 4102 +a 10096 10097 4102 +a 10097 10098 4102 +a 10098 10099 4102 +a 10099 10100 4102 +a 10100 10101 4102 +a 10101 10102 4102 +a 10102 10103 4102 +a 10103 10104 4102 +a 10104 10105 4102 +a 10105 10106 4102 +a 10106 10107 4102 +a 10107 10108 4102 +a 10108 10109 4102 +a 10109 10110 4102 +a 10110 10111 4102 +a 10111 10112 4102 +a 10112 10113 4102 +a 10113 10114 4102 +a 10114 10115 4102 +a 10115 10116 4102 +a 10116 10117 4102 +a 10117 10118 4102 +a 10118 10119 4102 +a 10119 10120 4102 +a 10120 10121 4102 +a 10121 10122 4102 +a 10122 10123 4102 +a 10123 10124 4102 +a 10124 10125 4102 +a 10125 10126 4102 +a 10126 10127 4102 +a 10127 10128 4102 +a 10128 10129 4102 +a 10129 10130 4102 +a 10130 10131 4102 +a 10131 10132 4102 +a 10132 10133 4102 +a 10133 10134 4102 +a 10134 10135 4102 +a 10135 10136 4102 +a 10136 10137 4102 +a 10137 10138 4102 +a 10138 10139 4102 +a 10139 10140 4102 +a 10140 10141 4102 +a 10141 10142 4102 +a 10142 10143 4102 +a 10143 10144 4102 +a 10144 10145 4102 +a 10145 10146 4102 +a 10146 10147 4102 +a 10147 10148 4102 +a 10148 10149 4102 +a 10149 10150 4102 +a 10150 10151 4102 +a 10151 10152 4102 +a 10152 10153 4102 +a 10153 10154 4102 +a 10154 10155 4102 +a 10155 10156 4102 +a 10156 10157 4102 +a 10157 10158 4102 +a 10158 10159 4102 +a 10159 10160 4102 +a 10160 10161 4102 +a 10161 10162 4102 +a 10162 10163 4102 +a 10163 10164 4102 +a 10164 10165 4102 +a 10165 10166 4102 +a 10166 10167 4102 +a 10167 10168 4102 +a 10168 10169 4102 +a 10169 10170 4102 +a 10170 10171 4102 +a 10171 10172 4102 +a 10172 10173 4102 +a 10173 10174 4102 +a 10174 10175 4102 +a 10175 10176 4102 +a 10176 10177 4102 +a 10177 10178 4102 +a 10178 10179 4102 +a 10179 10180 4102 +a 10180 10181 4102 +a 10181 10182 4102 +a 10182 10183 4102 +a 10183 10184 4102 +a 10184 10185 4102 +a 10185 10186 4102 +a 10186 10187 4102 +a 10187 10188 4102 +a 10188 10189 4102 +a 10189 10190 4102 +a 10190 10191 4102 +a 10191 10192 4102 +a 10192 10193 4102 +a 10193 10194 4102 +a 10194 10195 4102 +a 10195 10196 4102 +a 10196 10197 4102 +a 10197 10198 4102 +a 10198 10199 4102 +a 10199 10200 4102 +a 10200 10201 4102 +a 10201 10202 4102 +a 10202 10203 4102 +a 10203 10204 4102 +a 10204 10205 4102 +a 10205 10206 4102 +a 10206 10207 4102 +a 10207 10208 4102 +a 10208 10209 4102 +a 10209 10210 4102 +a 10210 10211 4102 +a 10211 10212 4102 +a 10212 10213 4102 +a 10213 10214 4102 +a 10214 10215 4102 +a 10215 10216 4102 +a 10216 10217 4102 +a 10217 10218 4102 +a 10218 10219 4102 +a 10219 10220 4102 +a 10220 10221 4102 +a 10221 10222 4102 +a 10222 10223 4102 +a 10223 10224 4102 +a 10224 10225 4102 +a 10225 10226 4102 +a 10226 10227 4102 +a 10227 10228 4102 +a 10228 10229 4102 +a 10229 10230 4102 +a 10230 10231 4102 +a 10231 10232 4102 +a 10232 10233 4102 +a 10233 10234 4102 +a 10234 10235 4102 +a 10235 10236 4102 +a 10236 10237 4102 +a 10237 10238 4102 +a 10238 10239 4102 +a 10239 10240 4102 +a 10240 10241 4102 +a 10241 10242 4102 +a 10242 10243 4102 +a 10243 10244 4102 +a 10244 10245 4102 +a 10245 10246 4102 +a 10246 10247 4102 +a 10247 10248 4102 +a 10248 10249 4102 +a 10249 10250 4102 +a 10250 10251 4102 +a 10251 10252 4102 +a 10252 10253 4102 +a 10253 10254 4102 +a 10254 10255 4102 +a 10255 10256 4102 +a 10256 10257 4102 +a 10257 10258 4102 +a 10258 10259 4102 +a 10259 10260 4102 +a 10260 10261 4102 +a 10261 10262 4102 +a 10262 10263 4102 +a 10263 10264 4102 +a 10264 10265 4102 +a 10265 10266 4102 +a 10266 10267 4102 +a 10267 10268 4102 +a 10268 10269 4102 +a 10269 10270 4102 +a 10270 10271 4102 +a 10271 10272 4102 +a 10272 10273 4102 +a 10273 10274 4102 +a 10274 10275 4102 +a 10275 10276 4102 +a 10276 10277 4102 +a 10277 10278 4102 +a 10278 10279 4102 +a 10279 10280 4102 +a 10280 10281 4102 +a 10281 10282 4102 +a 10282 10283 4102 +a 10283 10284 4102 +a 10284 10285 4102 +a 10285 10286 4102 +a 10286 10287 4102 +a 10287 10288 4102 +a 10288 10289 4102 +a 10289 10290 4102 +a 10290 10291 4102 +a 10291 10292 4102 +a 10292 10293 4102 +a 10293 10294 4102 +a 10294 10295 4102 +a 10295 10296 4102 +a 10296 10297 4102 +a 10297 10298 4102 +a 10298 10299 4102 +a 10299 10300 4102 +a 10300 10301 4102 +a 10301 10302 4102 +a 10302 10303 4102 +a 10303 10304 4102 +a 10304 10305 4102 +a 10305 10306 4102 +a 10306 10307 4102 +a 10307 10308 4102 +a 10308 10309 4102 +a 10309 10310 4102 +a 10310 10311 4102 +a 10311 10312 4102 +a 10312 10313 4102 +a 10313 10314 4102 +a 10314 10315 4102 +a 10315 10316 4102 +a 10316 10317 4102 +a 10317 10318 4102 +a 10318 10319 4102 +a 10319 10320 4102 +a 10320 10321 4102 +a 10321 10322 4102 +a 10322 10323 4102 +a 10323 10324 4102 +a 10324 10325 4102 +a 10325 10326 4102 +a 10326 10327 4102 +a 10327 10328 4102 +a 10328 10329 4102 +a 10329 10330 4102 +a 10330 10331 4102 +a 10331 10332 4102 +a 10332 10333 4102 +a 10333 10334 4102 +a 10334 10335 4102 +a 10335 10336 4102 +a 10336 10337 4102 +a 10337 10338 4102 +a 10338 10339 4102 +a 10339 10340 4102 +a 10340 10341 4102 +a 10341 10342 4102 +a 10342 10343 4102 +a 10343 10344 4102 +a 10344 10345 4102 +a 10345 10346 4102 +a 10346 10347 4102 +a 10347 10348 4102 +a 10348 10349 4102 +a 10349 10350 4102 +a 10350 10351 4102 +a 10351 10352 4102 +a 10352 10353 4102 +a 10353 10354 4102 +a 10354 10355 4102 +a 10355 10356 4102 +a 10356 10357 4102 +a 10357 10358 4102 +a 10358 10359 4102 +a 10359 10360 4102 +a 10360 10361 4102 +a 10361 10362 4102 +a 10362 10363 4102 +a 10363 10364 4102 +a 10364 10365 4102 +a 10365 10366 4102 +a 10366 10367 4102 +a 10367 10368 4102 +a 10368 10369 4102 +a 10369 10370 4102 +a 10370 10371 4102 +a 10371 10372 4102 +a 10372 10373 4102 +a 10373 10374 4102 +a 10374 10375 4102 +a 10375 10376 4102 +a 10376 10377 4102 +a 10377 10378 4102 +a 10378 10379 4102 +a 10379 10380 4102 +a 10380 10381 4102 +a 10381 10382 4102 +a 10382 10383 4102 +a 10383 10384 4102 +a 10384 10385 4102 +a 10385 10386 4102 +a 10386 10387 4102 +a 10387 10388 4102 +a 10388 10389 4102 +a 10389 10390 4102 +a 10390 10391 4102 +a 10391 10392 4102 +a 10392 10393 4102 +a 10393 10394 4102 +a 10394 10395 4102 +a 10395 10396 4102 +a 10396 10397 4102 +a 10397 10398 4102 +a 10398 10399 4102 +a 10399 10400 4102 +a 10400 10401 4102 +a 10401 10402 4102 +a 10402 10403 4102 +a 10403 10404 4102 +a 10404 10405 4102 +a 10405 10406 4102 +a 10406 10407 4102 +a 10407 10408 4102 +a 10408 10409 4102 +a 10409 10410 4102 +a 10410 10411 4102 +a 10411 10412 4102 +a 10412 10413 4102 +a 10413 10414 4102 +a 10414 10415 4102 +a 10415 10416 4102 +a 10416 10417 4102 +a 10417 10418 4102 +a 10418 10419 4102 +a 10419 10420 4102 +a 10420 10421 4102 +a 10421 10422 4102 +a 10422 10423 4102 +a 10423 10424 4102 +a 10424 10425 4102 +a 10425 10426 4102 +a 10426 10427 4102 +a 10427 10428 4102 +a 10428 10429 4102 +a 10429 10430 4102 +a 10430 10431 4102 +a 10431 10432 4102 +a 10432 10433 4102 +a 10433 10434 4102 +a 10434 10435 4102 +a 10435 10436 4102 +a 10436 10437 4102 +a 10437 10438 4102 +a 10438 10439 4102 +a 10439 10440 4102 +a 10440 10441 4102 +a 10441 10442 4102 +a 10442 10443 4102 +a 10443 10444 4102 +a 10444 10445 4102 +a 10445 10446 4102 +a 10446 10447 4102 +a 10447 10448 4102 +a 10448 10449 4102 +a 10449 10450 4102 +a 10450 10451 4102 +a 10451 10452 4102 +a 10452 10453 4102 +a 10453 10454 4102 +a 10454 10455 4102 +a 10455 10456 4102 +a 10456 10457 4102 +a 10457 10458 4102 +a 10458 10459 4102 +a 10459 10460 4102 +a 10460 10461 4102 +a 10461 10462 4102 +a 10462 10463 4102 +a 10463 10464 4102 +a 10464 10465 4102 +a 10465 10466 4102 +a 10466 10467 4102 +a 10467 10468 4102 +a 10468 10469 4102 +a 10469 10470 4102 +a 10470 10471 4102 +a 10471 10472 4102 +a 10472 10473 4102 +a 10473 10474 4102 +a 10474 10475 4102 +a 10475 10476 4102 +a 10476 10477 4102 +a 10477 10478 4102 +a 10478 10479 4102 +a 10479 10480 4102 +a 10480 10481 4102 +a 10481 10482 4102 +a 10482 10483 4102 +a 10483 10484 4102 +a 10484 10485 4102 +a 10485 10486 4102 +a 10486 10487 4102 +a 10487 10488 4102 +a 10488 10489 4102 +a 10489 10490 4102 +a 10490 10491 4102 +a 10491 10492 4102 +a 10492 10493 4102 +a 10493 10494 4102 +a 10494 10495 4102 +a 10495 10496 4102 +a 10496 10497 4102 +a 10497 10498 4102 +a 10498 10499 4102 +a 10499 10500 4102 +a 10500 10501 4102 +a 10501 10502 4102 +a 10502 10503 4102 +a 10503 10504 4102 +a 10504 10505 4102 +a 10505 10506 4102 +a 10506 10507 4102 +a 10507 10508 4102 +a 10508 10509 4102 +a 10509 10510 4102 +a 10510 10511 4102 +a 10511 10512 4102 +a 10512 10513 4102 +a 10513 10514 4102 +a 10514 10515 4102 +a 10515 10516 4102 +a 10516 10517 4102 +a 10517 10518 4102 +a 10518 10519 4102 +a 10519 10520 4102 +a 10520 10521 4102 +a 10521 10522 4102 +a 10522 10523 4102 +a 10523 10524 4102 +a 10524 10525 4102 +a 10525 10526 4102 +a 10526 10527 4102 +a 10527 10528 4102 +a 10528 10529 4102 +a 10529 10530 4102 +a 10530 10531 4102 +a 10531 10532 4102 +a 10532 10533 4102 +a 10533 10534 4102 +a 10534 10535 4102 +a 10535 10536 4102 +a 10536 10537 4102 +a 10537 10538 4102 +a 10538 10539 4102 +a 10539 10540 4102 +a 10540 10541 4102 +a 10541 10542 4102 +a 10542 10543 4102 +a 10543 10544 4102 +a 10544 10545 4102 +a 10545 10546 4102 +a 10546 10547 4102 +a 10547 10548 4102 +a 10548 10549 4102 +a 10549 10550 4102 +a 10550 10551 4102 +a 10551 10552 4102 +a 10552 10553 4102 +a 10553 10554 4102 +a 10554 10555 4102 +a 10555 10556 4102 +a 10556 10557 4102 +a 10557 10558 4102 +a 10558 10559 4102 +a 10559 10560 4102 +a 10560 10561 4102 +a 10561 10562 4102 +a 10562 10563 4102 +a 10563 10564 4102 +a 10564 10565 4102 +a 10565 10566 4102 +a 10566 10567 4102 +a 10567 10568 4102 +a 10568 10569 4102 +a 10569 10570 4102 +a 10570 10571 4102 +a 10571 10572 4102 +a 10572 10573 4102 +a 10573 10574 4102 +a 10574 10575 4102 +a 10575 10576 4102 +a 10576 10577 4102 +a 10577 10578 4102 +a 10578 10579 4102 +a 10579 10580 4102 +a 10580 10581 4102 +a 10581 10582 4102 +a 10582 10583 4102 +a 10583 10584 4102 +a 10584 10585 4102 +a 10585 10586 4102 +a 10586 10587 4102 +a 10587 10588 4102 +a 10588 10589 4102 +a 10589 10590 4102 +a 10590 10591 4102 +a 10591 10592 4102 +a 10592 10593 4102 +a 10593 10594 4102 +a 10594 10595 4102 +a 10595 10596 4102 +a 10596 10597 4102 +a 10597 10598 4102 +a 10598 10599 4102 +a 10599 10600 4102 +a 10600 10601 4102 +a 10601 10602 4102 +a 10602 10603 4102 +a 10603 10604 4102 +a 10604 10605 4102 +a 10605 10606 4102 +a 10606 10607 4102 +a 10607 10608 4102 +a 10608 10609 4102 +a 10609 10610 4102 +a 10610 10611 4102 +a 10611 10612 4102 +a 10612 10613 4102 +a 10613 10614 4102 +a 10614 10615 4102 +a 10615 10616 4102 +a 10616 10617 4102 +a 10617 10618 4102 +a 10618 10619 4102 +a 10619 10620 4102 +a 10620 10621 4102 +a 10621 10622 4102 +a 10622 10623 4102 +a 10623 10624 4102 +a 10624 10625 4102 +a 10625 10626 4102 +a 10626 10627 4102 +a 10627 10628 4102 +a 10628 10629 4102 +a 10629 10630 4102 +a 10630 10631 4102 +a 10631 10632 4102 +a 10632 10633 4102 +a 10633 10634 4102 +a 10634 10635 4102 +a 10635 10636 4102 +a 10636 10637 4102 +a 10637 10638 4102 +a 10638 10639 4102 +a 10639 10640 4102 +a 10640 10641 4102 +a 10641 10642 4102 +a 10642 10643 4102 +a 10643 10644 4102 +a 10644 10645 4102 +a 10645 10646 4102 +a 10646 10647 4102 +a 10647 10648 4102 +a 10648 10649 4102 +a 10649 10650 4102 +a 10650 10651 4102 +a 10651 10652 4102 +a 10652 10653 4102 +a 10653 10654 4102 +a 10654 10655 4102 +a 10655 10656 4102 +a 10656 10657 4102 +a 10657 10658 4102 +a 10658 10659 4102 +a 10659 10660 4102 +a 10660 10661 4102 +a 10661 10662 4102 +a 10662 10663 4102 +a 10663 10664 4102 +a 10664 10665 4102 +a 10665 10666 4102 +a 10666 10667 4102 +a 10667 10668 4102 +a 10668 10669 4102 +a 10669 10670 4102 +a 10670 10671 4102 +a 10671 10672 4102 +a 10672 10673 4102 +a 10673 10674 4102 +a 10674 10675 4102 +a 10675 10676 4102 +a 10676 10677 4102 +a 10677 10678 4102 +a 10678 10679 4102 +a 10679 10680 4102 +a 10680 10681 4102 +a 10681 10682 4102 +a 10682 10683 4102 +a 10683 10684 4102 +a 10684 10685 4102 +a 10685 10686 4102 +a 10686 10687 4102 +a 10687 10688 4102 +a 10688 10689 4102 +a 10689 10690 4102 +a 10690 10691 4102 +a 10691 10692 4102 +a 10692 10693 4102 +a 10693 10694 4102 +a 10694 10695 4102 +a 10695 10696 4102 +a 10696 10697 4102 +a 10697 10698 4102 +a 10698 10699 4102 +a 10699 10700 4102 +a 10700 10701 4102 +a 10701 10702 4102 +a 10702 10703 4102 +a 10703 10704 4102 +a 10704 10705 4102 +a 10705 10706 4102 +a 10706 10707 4102 +a 10707 10708 4102 +a 10708 10709 4102 +a 10709 10710 4102 +a 10710 10711 4102 +a 10711 10712 4102 +a 10712 10713 4102 +a 10713 10714 4102 +a 10714 10715 4102 +a 10715 10716 4102 +a 10716 10717 4102 +a 10717 10718 4102 +a 10718 10719 4102 +a 10719 10720 4102 +a 10720 10721 4102 +a 10721 10722 4102 +a 10722 10723 4102 +a 10723 10724 4102 +a 10724 10725 4102 +a 10725 10726 4102 +a 10726 10727 4102 +a 10727 10728 4102 +a 10728 10729 4102 +a 10729 10730 4102 +a 10730 10731 4102 +a 10731 10732 4102 +a 10732 10733 4102 +a 10733 10734 4102 +a 10734 10735 4102 +a 10735 10736 4102 +a 10736 10737 4102 +a 10737 10738 4102 +a 10738 10739 4102 +a 10739 10740 4102 +a 10740 10741 4102 +a 10741 10742 4102 +a 10742 10743 4102 +a 10743 10744 4102 +a 10744 10745 4102 +a 10745 10746 4102 +a 10746 10747 4102 +a 10747 10748 4102 +a 10748 10749 4102 +a 10749 10750 4102 +a 10750 10751 4102 +a 10751 10752 4102 +a 10752 10753 4102 +a 10753 10754 4102 +a 10754 10755 4102 +a 10755 10756 4102 +a 10756 10757 4102 +a 10757 10758 4102 +a 10758 10759 4102 +a 10759 10760 4102 +a 10760 10761 4102 +a 10761 10762 4102 +a 10762 10763 4102 +a 10763 10764 4102 +a 10764 10765 4102 +a 10765 10766 4102 +a 10766 10767 4102 +a 10767 10768 4102 +a 10768 10769 4102 +a 10769 10770 4102 +a 10770 10771 4102 +a 10771 10772 4102 +a 10772 10773 4102 +a 10773 10774 4102 +a 10774 10775 4102 +a 10775 10776 4102 +a 10776 10777 4102 +a 10777 10778 4102 +a 10778 10779 4102 +a 10779 10780 4102 +a 10780 10781 4102 +a 10781 10782 4102 +a 10782 10783 4102 +a 10783 10784 4102 +a 10784 10785 4102 +a 10785 10786 4102 +a 10786 10787 4102 +a 10787 10788 4102 +a 10788 10789 4102 +a 10789 10790 4102 +a 10790 10791 4102 +a 10791 10792 4102 +a 10792 10793 4102 +a 10793 10794 4102 +a 10794 10795 4102 +a 10795 10796 4102 +a 10796 10797 4102 +a 10797 10798 4102 +a 10798 10799 4102 +a 10799 10800 4102 +a 10800 10801 4102 +a 10801 10802 4102 +a 10802 10803 4102 +a 10803 10804 4102 +a 10804 10805 4102 +a 10805 10806 4102 +a 10806 10807 4102 +a 10807 10808 4102 +a 10808 10809 4102 +a 10809 10810 4102 +a 10810 10811 4102 +a 10811 10812 4102 +a 10812 10813 4102 +a 10813 10814 4102 +a 10814 10815 4102 +a 10815 10816 4102 +a 10816 10817 4102 +a 10817 10818 4102 +a 10818 10819 4102 +a 10819 10820 4102 +a 10820 10821 4102 +a 10821 10822 4102 +a 10822 10823 4102 +a 10823 10824 4102 +a 10824 10825 4102 +a 10825 10826 4102 +a 10826 10827 4102 +a 10827 10828 4102 +a 10828 10829 4102 +a 10829 10830 4102 +a 10830 10831 4102 +a 10831 10832 4102 +a 10832 10833 4102 +a 10833 10834 4102 +a 10834 10835 4102 +a 10835 10836 4102 +a 10836 10837 4102 +a 10837 10838 4102 +a 10838 10839 4102 +a 10839 10840 4102 +a 10840 10841 4102 +a 10841 10842 4102 +a 10842 10843 4102 +a 10843 10844 4102 +a 10844 10845 4102 +a 10845 10846 4102 +a 10846 10847 4102 +a 10847 10848 4102 +a 10848 10849 4102 +a 10849 10850 4102 +a 10850 10851 4102 +a 10851 10852 4102 +a 10852 10853 4102 +a 10853 10854 4102 +a 10854 10855 4102 +a 10855 10856 4102 +a 10856 10857 4102 +a 10857 10858 4102 +a 10858 10859 4102 +a 10859 10860 4102 +a 10860 10861 4102 +a 10861 10862 4102 +a 10862 10863 4102 +a 10863 10864 4102 +a 10864 10865 4102 +a 10865 10866 4102 +a 10866 10867 4102 +a 10867 10868 4102 +a 10868 10869 4102 +a 10869 10870 4102 +a 10870 10871 4102 +a 10871 10872 4102 +a 10872 10873 4102 +a 10873 10874 4102 +a 10874 10875 4102 +a 10875 10876 4102 +a 10876 10877 4102 +a 10877 10878 4102 +a 10878 10879 4102 +a 10879 10880 4102 +a 10880 10881 4102 +a 10881 10882 4102 +a 10882 10883 4102 +a 10883 10884 4102 +a 10884 10885 4102 +a 10885 10886 4102 +a 10886 10887 4102 +a 10887 10888 4102 +a 10888 10889 4102 +a 10889 10890 4102 +a 10890 10891 4102 +a 10891 10892 4102 +a 10892 10893 4102 +a 10893 10894 4102 +a 10894 10895 4102 +a 10895 10896 4102 +a 10896 10897 4102 +a 10897 10898 4102 +a 10898 10899 4102 +a 10899 10900 4102 +a 10900 10901 4102 +a 10901 10902 4102 +a 10902 10903 4102 +a 10903 10904 4102 +a 10904 10905 4102 +a 10905 10906 4102 +a 10906 10907 4102 +a 10907 10908 4102 +a 10908 10909 4102 +a 10909 10910 4102 +a 10910 10911 4102 +a 10911 10912 4102 +a 10912 10913 4102 +a 10913 10914 4102 +a 10914 10915 4102 +a 10915 10916 4102 +a 10916 10917 4102 +a 10917 10918 4102 +a 10918 10919 4102 +a 10919 10920 4102 +a 10920 10921 4102 +a 10921 10922 4102 +a 10922 10923 4102 +a 10923 10924 4102 +a 10924 10925 4102 +a 10925 10926 4102 +a 10926 10927 4102 +a 10927 10928 4102 +a 10928 10929 4102 +a 10929 10930 4102 +a 10930 10931 4102 +a 10931 10932 4102 +a 10932 10933 4102 +a 10933 10934 4102 +a 10934 10935 4102 +a 10935 10936 4102 +a 10936 10937 4102 +a 10937 10938 4102 +a 10938 10939 4102 +a 10939 10940 4102 +a 10940 10941 4102 +a 10941 10942 4102 +a 10942 10943 4102 +a 10943 10944 4102 +a 10944 10945 4102 +a 10945 10946 4102 +a 10946 10947 4102 +a 10947 10948 4102 +a 10948 10949 4102 +a 10949 10950 4102 +a 10950 10951 4102 +a 10951 10952 4102 +a 10952 10953 4102 +a 10953 10954 4102 +a 10954 10955 4102 +a 10955 10956 4102 +a 10956 10957 4102 +a 10957 10958 4102 +a 10958 10959 4102 +a 10959 10960 4102 +a 10960 10961 4102 +a 10961 10962 4102 +a 10962 10963 4102 +a 10963 10964 4102 +a 10964 10965 4102 +a 10965 10966 4102 +a 10966 10967 4102 +a 10967 10968 4102 +a 10968 10969 4102 +a 10969 10970 4102 +a 10970 10971 4102 +a 10971 10972 4102 +a 10972 10973 4102 +a 10973 10974 4102 +a 10974 10975 4102 +a 10975 10976 4102 +a 10976 10977 4102 +a 10977 10978 4102 +a 10978 10979 4102 +a 10979 10980 4102 +a 10980 10981 4102 +a 10981 10982 4102 +a 10982 10983 4102 +a 10983 10984 4102 +a 10984 10985 4102 +a 10985 10986 4102 +a 10986 10987 4102 +a 10987 10988 4102 +a 10988 10989 4102 +a 10989 10990 4102 +a 10990 10991 4102 +a 10991 10992 4102 +a 10992 10993 4102 +a 10993 10994 4102 +a 10994 10995 4102 +a 10995 10996 4102 +a 10996 10997 4102 +a 10997 10998 4102 +a 10998 10999 4102 +a 10999 11000 4102 +a 11000 11001 4102 +a 11001 11002 4102 +a 11002 11003 4102 +a 11003 11004 4102 +a 11004 11005 4102 +a 11005 11006 4102 +a 11006 11007 4102 +a 11007 11008 4102 +a 11008 11009 4102 +a 11009 11010 4102 +a 11010 11011 4102 +a 11011 11012 4102 +a 11012 11013 4102 +a 11013 11014 4102 +a 11014 11015 4102 +a 11015 11016 4102 +a 11016 11017 4102 +a 11017 11018 4102 +a 11018 11019 4102 +a 11019 11020 4102 +a 11020 11021 4102 +a 11021 11022 4102 +a 11022 11023 4102 +a 11023 11024 4102 +a 11024 11025 4102 +a 11025 11026 4102 +a 11026 11027 4102 +a 11027 11028 4102 +a 11028 11029 4102 +a 11029 11030 4102 +a 11030 11031 4102 +a 11031 11032 4102 +a 11032 11033 4102 +a 11033 11034 4102 +a 11034 11035 4102 +a 11035 11036 4102 +a 11036 11037 4102 +a 11037 11038 4102 +a 11038 11039 4102 +a 11039 11040 4102 +a 11040 11041 4102 +a 11041 11042 4102 +a 11042 11043 4102 +a 11043 11044 4102 +a 11044 11045 4102 +a 11045 11046 4102 +a 11046 11047 4102 +a 11047 11048 4102 +a 11048 11049 4102 +a 11049 11050 4102 +a 11050 11051 4102 +a 11051 11052 4102 +a 11052 11053 4102 +a 11053 11054 4102 +a 11054 11055 4102 +a 11055 11056 4102 +a 11056 11057 4102 +a 11057 11058 4102 +a 11058 11059 4102 +a 11059 11060 4102 +a 11060 11061 4102 +a 11061 11062 4102 +a 11062 11063 4102 +a 11063 11064 4102 +a 11064 11065 4102 +a 11065 11066 4102 +a 11066 11067 4102 +a 11067 11068 4102 +a 11068 11069 4102 +a 11069 11070 4102 +a 11070 11071 4102 +a 11071 11072 4102 +a 11072 11073 4102 +a 11073 11074 4102 +a 11074 11075 4102 +a 11075 11076 4102 +a 11076 11077 4102 +a 11077 11078 4102 +a 11078 11079 4102 +a 11079 11080 4102 +a 11080 11081 4102 +a 11081 11082 4102 +a 11082 11083 4102 +a 11083 11084 4102 +a 11084 11085 4102 +a 11085 11086 4102 +a 11086 11087 4102 +a 11087 11088 4102 +a 11088 11089 4102 +a 11089 11090 4102 +a 11090 11091 4102 +a 11091 11092 4102 +a 11092 11093 4102 +a 11093 11094 4102 +a 11094 11095 4102 +a 11095 11096 4102 +a 11096 11097 4102 +a 11097 11098 4102 +a 11098 11099 4102 +a 11099 11100 4102 +a 11100 11101 4102 +a 11101 11102 4102 +a 11102 11103 4102 +a 11103 11104 4102 +a 11104 11105 4102 +a 11105 11106 4102 +a 11106 11107 4102 +a 11107 11108 4102 +a 11108 11109 4102 +a 11109 11110 4102 +a 11110 11111 4102 +a 11111 11112 4102 +a 11112 11113 4102 +a 11113 11114 4102 +a 11114 11115 4102 +a 11115 11116 4102 +a 11116 11117 4102 +a 11117 11118 4102 +a 11118 11119 4102 +a 11119 11120 4102 +a 11120 11121 4102 +a 11121 11122 4102 +a 11122 11123 4102 +a 11123 11124 4102 +a 11124 11125 4102 +a 11125 11126 4102 +a 11126 11127 4102 +a 11127 11128 4102 +a 11128 11129 4102 +a 11129 11130 4102 +a 11130 11131 4102 +a 11131 11132 4102 +a 11132 11133 4102 +a 11133 11134 4102 +a 11134 11135 4102 +a 11135 11136 4102 +a 11136 11137 4102 +a 11137 11138 4102 +a 11138 11139 4102 +a 11139 11140 4102 +a 11140 11141 4102 +a 11141 11142 4102 +a 11142 11143 4102 +a 11143 11144 4102 +a 11144 11145 4102 +a 11145 11146 4102 +a 11146 11147 4102 +a 11147 11148 4102 +a 11148 11149 4102 +a 11149 11150 4102 +a 11150 11151 4102 +a 11151 11152 4102 +a 11152 11153 4102 +a 11153 11154 4102 +a 11154 11155 4102 +a 11155 11156 4102 +a 11156 11157 4102 +a 11157 11158 4102 +a 11158 11159 4102 +a 11159 11160 4102 +a 11160 11161 4102 +a 11161 11162 4102 +a 11162 11163 4102 +a 11163 11164 4102 +a 11164 11165 4102 +a 11165 11166 4102 +a 11166 11167 4102 +a 11167 11168 4102 +a 11168 11169 4102 +a 11169 11170 4102 +a 11170 11171 4102 +a 11171 11172 4102 +a 11172 11173 4102 +a 11173 11174 4102 +a 11174 11175 4102 +a 11175 11176 4102 +a 11176 11177 4102 +a 11177 11178 4102 +a 11178 11179 4102 +a 11179 11180 4102 +a 11180 11181 4102 +a 11181 11182 4102 +a 11182 11183 4102 +a 11183 11184 4102 +a 11184 11185 4102 +a 11185 11186 4102 +a 11186 11187 4102 +a 11187 11188 4102 +a 11188 11189 4102 +a 11189 11190 4102 +a 11190 11191 4102 +a 11191 11192 4102 +a 11192 11193 4102 +a 11193 11194 4102 +a 11194 11195 4102 +a 11195 11196 4102 +a 11196 11197 4102 +a 11197 11198 4102 +a 11198 11199 4102 +a 11199 11200 4102 +a 11200 11201 4102 +a 11201 11202 4102 +a 11202 11203 4102 +a 11203 11204 4102 +a 11204 11205 4102 +a 11205 11206 4102 +a 11206 11207 4102 +a 11207 11208 4102 +a 11208 11209 4102 +a 11209 11210 4102 +a 11210 11211 4102 +a 11211 11212 4102 +a 11212 11213 4102 +a 11213 11214 4102 +a 11214 11215 4102 +a 11215 11216 4102 +a 11216 11217 4102 +a 11217 11218 4102 +a 11218 11219 4102 +a 11219 11220 4102 +a 11220 11221 4102 +a 11221 11222 4102 +a 11222 11223 4102 +a 11223 11224 4102 +a 11224 11225 4102 +a 11225 11226 4102 +a 11226 11227 4102 +a 11227 11228 4102 +a 11228 11229 4102 +a 11229 11230 4102 +a 11230 11231 4102 +a 11231 11232 4102 +a 11232 11233 4102 +a 11233 11234 4102 +a 11234 11235 4102 +a 11235 11236 4102 +a 11236 11237 4102 +a 11237 11238 4102 +a 11238 11239 4102 +a 11239 11240 4102 +a 11240 11241 4102 +a 11241 11242 4102 +a 11242 11243 4102 +a 11243 11244 4102 +a 11244 11245 4102 +a 11245 11246 4102 +a 11246 11247 4102 +a 11247 11248 4102 +a 11248 11249 4102 +a 11249 11250 4102 +a 11250 11251 4102 +a 11251 11252 4102 +a 11252 11253 4102 +a 11253 11254 4102 +a 11254 11255 4102 +a 11255 11256 4102 +a 11256 11257 4102 +a 11257 11258 4102 +a 11258 11259 4102 +a 11259 11260 4102 +a 11260 11261 4102 +a 11261 11262 4102 +a 11262 11263 4102 +a 11263 11264 4102 +a 11264 11265 4102 +a 11265 11266 4102 +a 11266 11267 4102 +a 11267 11268 4102 +a 11268 11269 4102 +a 11269 11270 4102 +a 11270 11271 4102 +a 11271 11272 4102 +a 11272 11273 4102 +a 11273 11274 4102 +a 11274 11275 4102 +a 11275 11276 4102 +a 11276 11277 4102 +a 11277 11278 4102 +a 11278 11279 4102 +a 11279 11280 4102 +a 11280 11281 4102 +a 11281 11282 4102 +a 11282 11283 4102 +a 11283 11284 4102 +a 11284 11285 4102 +a 11285 11286 4102 +a 11286 11287 4102 +a 11287 11288 4102 +a 11288 11289 4102 +a 11289 11290 4102 +a 11290 11291 4102 +a 11291 11292 4102 +a 11292 11293 4102 +a 11293 11294 4102 +a 11294 11295 4102 +a 11295 11296 4102 +a 11296 11297 4102 +a 11297 11298 4102 +a 11298 11299 4102 +a 11299 11300 4102 +a 11300 11301 4102 +a 11301 11302 4102 +a 11302 11303 4102 +a 11303 11304 4102 +a 11304 11305 4102 +a 11305 11306 4102 +a 11306 11307 4102 +a 11307 11308 4102 +a 11308 11309 4102 +a 11309 11310 4102 +a 11310 11311 4102 +a 11311 11312 4102 +a 11312 11313 4102 +a 11313 11314 4102 +a 11314 11315 4102 +a 11315 11316 4102 +a 11316 11317 4102 +a 11317 11318 4102 +a 11318 11319 4102 +a 11319 11320 4102 +a 11320 11321 4102 +a 11321 11322 4102 +a 11322 11323 4102 +a 11323 11324 4102 +a 11324 11325 4102 +a 11325 11326 4102 +a 11326 11327 4102 +a 11327 11328 4102 +a 11328 11329 4102 +a 11329 11330 4102 +a 11330 11331 4102 +a 11331 11332 4102 +a 11332 11333 4102 +a 11333 11334 4102 +a 11334 11335 4102 +a 11335 11336 4102 +a 11336 11337 4102 +a 11337 11338 4102 +a 11338 11339 4102 +a 11339 11340 4102 +a 11340 11341 4102 +a 11341 11342 4102 +a 11342 11343 4102 +a 11343 11344 4102 +a 11344 11345 4102 +a 11345 11346 4102 +a 11346 11347 4102 +a 11347 11348 4102 +a 11348 11349 4102 +a 11349 11350 4102 +a 11350 11351 4102 +a 11351 11352 4102 +a 11352 11353 4102 +a 11353 11354 4102 +a 11354 11355 4102 +a 11355 11356 4102 +a 11356 11357 4102 +a 11357 11358 4102 +a 11358 11359 4102 +a 11359 11360 4102 +a 11360 11361 4102 +a 11361 11362 4102 +a 11362 11363 4102 +a 11363 11364 4102 +a 11364 11365 4102 +a 11365 11366 4102 +a 11366 11367 4102 +a 11367 11368 4102 +a 11368 11369 4102 +a 11369 11370 4102 +a 11370 11371 4102 +a 11371 11372 4102 +a 11372 11373 4102 +a 11373 11374 4102 +a 11374 11375 4102 +a 11375 11376 4102 +a 11376 11377 4102 +a 11377 11378 4102 +a 11378 11379 4102 +a 11379 11380 4102 +a 11380 11381 4102 +a 11381 11382 4102 +a 11382 11383 4102 +a 11383 11384 4102 +a 11384 11385 4102 +a 11385 11386 4102 +a 11386 11387 4102 +a 11387 11388 4102 +a 11388 11389 4102 +a 11389 11390 4102 +a 11390 11391 4102 +a 11391 11392 4102 +a 11392 11393 4102 +a 11393 11394 4102 +a 11394 11395 4102 +a 11395 11396 4102 +a 11396 11397 4102 +a 11397 11398 4102 +a 11398 11399 4102 +a 11399 11400 4102 +a 11400 11401 4102 +a 11401 11402 4102 +a 11402 11403 4102 +a 11403 11404 4102 +a 11404 11405 4102 +a 11405 11406 4102 +a 11406 11407 4102 +a 11407 11408 4102 +a 11408 11409 4102 +a 11409 11410 4102 +a 11410 11411 4102 +a 11411 11412 4102 +a 11412 11413 4102 +a 11413 11414 4102 +a 11414 11415 4102 +a 11415 11416 4102 +a 11416 11417 4102 +a 11417 11418 4102 +a 11418 11419 4102 +a 11419 11420 4102 +a 11420 11421 4102 +a 11421 11422 4102 +a 11422 11423 4102 +a 11423 11424 4102 +a 11424 11425 4102 +a 11425 11426 4102 +a 11426 11427 4102 +a 11427 11428 4102 +a 11428 11429 4102 +a 11429 11430 4102 +a 11430 11431 4102 +a 11431 11432 4102 +a 11432 11433 4102 +a 11433 11434 4102 +a 11434 11435 4102 +a 11435 11436 4102 +a 11436 11437 4102 +a 11437 11438 4102 +a 11438 11439 4102 +a 11439 11440 4102 +a 11440 11441 4102 +a 11441 11442 4102 +a 11442 11443 4102 +a 11443 11444 4102 +a 11444 11445 4102 +a 11445 11446 4102 +a 11446 11447 4102 +a 11447 11448 4102 +a 11448 11449 4102 +a 11449 11450 4102 +a 11450 11451 4102 +a 11451 11452 4102 +a 11452 11453 4102 +a 11453 11454 4102 +a 11454 11455 4102 +a 11455 11456 4102 +a 11456 11457 4102 +a 11457 11458 4102 +a 11458 11459 4102 +a 11459 11460 4102 +a 11460 11461 4102 +a 11461 11462 4102 +a 11462 11463 4102 +a 11463 11464 4102 +a 11464 11465 4102 +a 11465 11466 4102 +a 11466 11467 4102 +a 11467 11468 4102 +a 11468 11469 4102 +a 11469 11470 4102 +a 11470 11471 4102 +a 11471 11472 4102 +a 11472 11473 4102 +a 11473 11474 4102 +a 11474 11475 4102 +a 11475 11476 4102 +a 11476 11477 4102 +a 11477 11478 4102 +a 11478 11479 4102 +a 11479 11480 4102 +a 11480 11481 4102 +a 11481 11482 4102 +a 11482 11483 4102 +a 11483 11484 4102 +a 11484 11485 4102 +a 11485 11486 4102 +a 11486 11487 4102 +a 11487 11488 4102 +a 11488 11489 4102 +a 11489 11490 4102 +a 11490 11491 4102 +a 11491 11492 4102 +a 11492 11493 4102 +a 11493 11494 4102 +a 11494 11495 4102 +a 11495 11496 4102 +a 11496 11497 4102 +a 11497 11498 4102 +a 11498 11499 4102 +a 11499 11500 4102 +a 11500 11501 4102 +a 11501 11502 4102 +a 11502 11503 4102 +a 11503 11504 4102 +a 11504 11505 4102 +a 11505 11506 4102 +a 11506 11507 4102 +a 11507 11508 4102 +a 11508 11509 4102 +a 11509 11510 4102 +a 11510 11511 4102 +a 11511 11512 4102 +a 11512 11513 4102 +a 11513 11514 4102 +a 11514 11515 4102 +a 11515 11516 4102 +a 11516 11517 4102 +a 11517 11518 4102 +a 11518 11519 4102 +a 11519 11520 4102 +a 11520 11521 4102 +a 11521 11522 4102 +a 11522 11523 4102 +a 11523 11524 4102 +a 11524 11525 4102 +a 11525 11526 4102 +a 11526 11527 4102 +a 11527 11528 4102 +a 11528 11529 4102 +a 11529 11530 4102 +a 11530 11531 4102 +a 11531 11532 4102 +a 11532 11533 4102 +a 11533 11534 4102 +a 11534 11535 4102 +a 11535 11536 4102 +a 11536 11537 4102 +a 11537 11538 4102 +a 11538 11539 4102 +a 11539 11540 4102 +a 11540 11541 4102 +a 11541 11542 4102 +a 11542 11543 4102 +a 11543 11544 4102 +a 11544 11545 4102 +a 11545 11546 4102 +a 11546 11547 4102 +a 11547 11548 4102 +a 11548 11549 4102 +a 11549 11550 4102 +a 11550 11551 4102 +a 11551 11552 4102 +a 11552 11553 4102 +a 11553 11554 4102 +a 11554 11555 4102 +a 11555 11556 4102 +a 11556 11557 4102 +a 11557 11558 4102 +a 11558 11559 4102 +a 11559 11560 4102 +a 11560 11561 4102 +a 11561 11562 4102 +a 11562 11563 4102 +a 11563 11564 4102 +a 11564 11565 4102 +a 11565 11566 4102 +a 11566 11567 4102 +a 11567 11568 4102 +a 11568 11569 4102 +a 11569 11570 4102 +a 11570 11571 4102 +a 11571 11572 4102 +a 11572 11573 4102 +a 11573 11574 4102 +a 11574 11575 4102 +a 11575 11576 4102 +a 11576 11577 4102 +a 11577 11578 4102 +a 11578 11579 4102 +a 11579 11580 4102 +a 11580 11581 4102 +a 11581 11582 4102 +a 11582 11583 4102 +a 11583 11584 4102 +a 11584 11585 4102 +a 11585 11586 4102 +a 11586 11587 4102 +a 11587 11588 4102 +a 11588 11589 4102 +a 11589 11590 4102 +a 11590 11591 4102 +a 11591 11592 4102 +a 11592 11593 4102 +a 11593 11594 4102 +a 11594 11595 4102 +a 11595 11596 4102 +a 11596 11597 4102 +a 11597 11598 4102 +a 11598 11599 4102 +a 11599 11600 4102 +a 11600 11601 4102 +a 11601 11602 4102 +a 11602 11603 4102 +a 11603 11604 4102 +a 11604 11605 4102 +a 11605 11606 4102 +a 11606 11607 4102 +a 11607 11608 4102 +a 11608 11609 4102 +a 11609 11610 4102 +a 11610 11611 4102 +a 11611 11612 4102 +a 11612 11613 4102 +a 11613 11614 4102 +a 11614 11615 4102 +a 11615 11616 4102 +a 11616 11617 4102 +a 11617 11618 4102 +a 11618 11619 4102 +a 11619 11620 4102 +a 11620 11621 4102 +a 11621 11622 4102 +a 11622 11623 4102 +a 11623 11624 4102 +a 11624 11625 4102 +a 11625 11626 4102 +a 11626 11627 4102 +a 11627 11628 4102 +a 11628 11629 4102 +a 11629 11630 4102 +a 11630 11631 4102 +a 11631 11632 4102 +a 11632 11633 4102 +a 11633 11634 4102 +a 11634 11635 4102 +a 11635 11636 4102 +a 11636 11637 4102 +a 11637 11638 4102 +a 11638 11639 4102 +a 11639 11640 4102 +a 11640 11641 4102 +a 11641 11642 4102 +a 11642 11643 4102 +a 11643 11644 4102 +a 11644 11645 4102 +a 11645 11646 4102 +a 11646 11647 4102 +a 11647 11648 4102 +a 11648 11649 4102 +a 11649 11650 4102 +a 11650 11651 4102 +a 11651 11652 4102 +a 11652 11653 4102 +a 11653 11654 4102 +a 11654 11655 4102 +a 11655 11656 4102 +a 11656 11657 4102 +a 11657 11658 4102 +a 11658 11659 4102 +a 11659 11660 4102 +a 11660 11661 4102 +a 11661 11662 4102 +a 11662 11663 4102 +a 11663 11664 4102 +a 11664 11665 4102 +a 11665 11666 4102 +a 11666 11667 4102 +a 11667 11668 4102 +a 11668 11669 4102 +a 11669 11670 4102 +a 11670 11671 4102 +a 11671 11672 4102 +a 11672 11673 4102 +a 11673 11674 4102 +a 11674 11675 4102 +a 11675 11676 4102 +a 11676 11677 4102 +a 11677 11678 4102 +a 11678 11679 4102 +a 11679 11680 4102 +a 11680 11681 4102 +a 11681 11682 4102 +a 11682 11683 4102 +a 11683 11684 4102 +a 11684 11685 4102 +a 11685 11686 4102 +a 11686 11687 4102 +a 11687 11688 4102 +a 11688 11689 4102 +a 11689 11690 4102 +a 11690 11691 4102 +a 11691 11692 4102 +a 11692 11693 4102 +a 11693 11694 4102 +a 11694 11695 4102 +a 11695 11696 4102 +a 11696 11697 4102 +a 11697 11698 4102 +a 11698 11699 4102 +a 11699 11700 4102 +a 11700 11701 4102 +a 11701 11702 4102 +a 11702 11703 4102 +a 11703 11704 4102 +a 11704 11705 4102 +a 11705 11706 4102 +a 11706 11707 4102 +a 11707 11708 4102 +a 11708 11709 4102 +a 11709 11710 4102 +a 11710 11711 4102 +a 11711 11712 4102 +a 11712 11713 4102 +a 11713 11714 4102 +a 11714 11715 4102 +a 11715 11716 4102 +a 11716 11717 4102 +a 11717 11718 4102 +a 11718 11719 4102 +a 11719 11720 4102 +a 11720 11721 4102 +a 11721 11722 4102 +a 11722 11723 4102 +a 11723 11724 4102 +a 11724 11725 4102 +a 11725 11726 4102 +a 11726 11727 4102 +a 11727 11728 4102 +a 11728 11729 4102 +a 11729 11730 4102 +a 11730 11731 4102 +a 11731 11732 4102 +a 11732 11733 4102 +a 11733 11734 4102 +a 11734 11735 4102 +a 11735 11736 4102 +a 11736 11737 4102 +a 11737 11738 4102 +a 11738 11739 4102 +a 11739 11740 4102 +a 11740 11741 4102 +a 11741 11742 4102 +a 11742 11743 4102 +a 11743 11744 4102 +a 11744 11745 4102 +a 11745 11746 4102 +a 11746 11747 4102 +a 11747 11748 4102 +a 11748 11749 4102 +a 11749 11750 4102 +a 11750 11751 4102 +a 11751 11752 4102 +a 11752 11753 4102 +a 11753 11754 4102 +a 11754 11755 4102 +a 11755 11756 4102 +a 11756 11757 4102 +a 11757 11758 4102 +a 11758 11759 4102 +a 11759 11760 4102 +a 11760 11761 4102 +a 11761 11762 4102 +a 11762 11763 4102 +a 11763 11764 4102 +a 11764 11765 4102 +a 11765 11766 4102 +a 11766 11767 4102 +a 11767 11768 4102 +a 11768 11769 4102 +a 11769 11770 4102 +a 11770 11771 4102 +a 11771 11772 4102 +a 11772 11773 4102 +a 11773 11774 4102 +a 11774 11775 4102 +a 11775 11776 4102 +a 11776 11777 4102 +a 11777 11778 4102 +a 11778 11779 4102 +a 11779 11780 4102 +a 11780 11781 4102 +a 11781 11782 4102 +a 11782 11783 4102 +a 11783 11784 4102 +a 11784 11785 4102 +a 11785 11786 4102 +a 11786 11787 4102 +a 11787 11788 4102 +a 11788 11789 4102 +a 11789 11790 4102 +a 11790 11791 4102 +a 11791 11792 4102 +a 11792 11793 4102 +a 11793 11794 4102 +a 11794 11795 4102 +a 11795 11796 4102 +a 11796 11797 4102 +a 11797 11798 4102 +a 11798 11799 4102 +a 11799 11800 4102 +a 11800 11801 4102 +a 11801 11802 4102 +a 11802 11803 4102 +a 11803 11804 4102 +a 11804 11805 4102 +a 11805 11806 4102 +a 11806 11807 4102 +a 11807 11808 4102 +a 11808 11809 4102 +a 11809 11810 4102 +a 11810 11811 4102 +a 11811 11812 4102 +a 11812 11813 4102 +a 11813 11814 4102 +a 11814 11815 4102 +a 11815 11816 4102 +a 11816 11817 4102 +a 11817 11818 4102 +a 11818 11819 4102 +a 11819 11820 4102 +a 11820 11821 4102 +a 11821 11822 4102 +a 11822 11823 4102 +a 11823 11824 4102 +a 11824 11825 4102 +a 11825 11826 4102 +a 11826 11827 4102 +a 11827 11828 4102 +a 11828 11829 4102 +a 11829 11830 4102 +a 11830 11831 4102 +a 11831 11832 4102 +a 11832 11833 4102 +a 11833 11834 4102 +a 11834 11835 4102 +a 11835 11836 4102 +a 11836 11837 4102 +a 11837 11838 4102 +a 11838 11839 4102 +a 11839 11840 4102 +a 11840 11841 4102 +a 11841 11842 4102 +a 11842 11843 4102 +a 11843 11844 4102 +a 11844 11845 4102 +a 11845 11846 4102 +a 11846 11847 4102 +a 11847 11848 4102 +a 11848 11849 4102 +a 11849 11850 4102 +a 11850 11851 4102 +a 11851 11852 4102 +a 11852 11853 4102 +a 11853 11854 4102 +a 11854 11855 4102 +a 11855 11856 4102 +a 11856 11857 4102 +a 11857 11858 4102 +a 11858 11859 4102 +a 11859 11860 4102 +a 11860 11861 4102 +a 11861 11862 4102 +a 11862 11863 4102 +a 11863 11864 4102 +a 11864 11865 4102 +a 11865 11866 4102 +a 11866 11867 4102 +a 11867 11868 4102 +a 11868 11869 4102 +a 11869 11870 4102 +a 11870 11871 4102 +a 11871 11872 4102 +a 11872 11873 4102 +a 11873 11874 4102 +a 11874 11875 4102 +a 11875 11876 4102 +a 11876 11877 4102 +a 11877 11878 4102 +a 11878 11879 4102 +a 11879 11880 4102 +a 11880 11881 4102 +a 11881 11882 4102 +a 11882 11883 4102 +a 11883 11884 4102 +a 11884 11885 4102 +a 11885 11886 4102 +a 11886 11887 4102 +a 11887 11888 4102 +a 11888 11889 4102 +a 11889 11890 4102 +a 11890 11891 4102 +a 11891 11892 4102 +a 11892 11893 4102 +a 11893 11894 4102 +a 11894 11895 4102 +a 11895 11896 4102 +a 11896 11897 4102 +a 11897 11898 4102 +a 11898 11899 4102 +a 11899 11900 4102 +a 11900 11901 4102 +a 11901 11902 4102 +a 11902 11903 4102 +a 11903 11904 4102 +a 11904 11905 4102 +a 11905 11906 4102 +a 11906 11907 4102 +a 11907 11908 4102 +a 11908 11909 4102 +a 11909 11910 4102 +a 11910 11911 4102 +a 11911 11912 4102 +a 11912 11913 4102 +a 11913 11914 4102 +a 11914 11915 4102 +a 11915 11916 4102 +a 11916 11917 4102 +a 11917 11918 4102 +a 11918 11919 4102 +a 11919 11920 4102 +a 11920 11921 4102 +a 11921 11922 4102 +a 11922 11923 4102 +a 11923 11924 4102 +a 11924 11925 4102 +a 11925 11926 4102 +a 11926 11927 4102 +a 11927 11928 4102 +a 11928 11929 4102 +a 11929 11930 4102 +a 11930 11931 4102 +a 11931 11932 4102 +a 11932 11933 4102 +a 11933 11934 4102 +a 11934 11935 4102 +a 11935 11936 4102 +a 11936 11937 4102 +a 11937 11938 4102 +a 11938 11939 4102 +a 11939 11940 4102 +a 11940 11941 4102 +a 11941 11942 4102 +a 11942 11943 4102 +a 11943 11944 4102 +a 11944 11945 4102 +a 11945 11946 4102 +a 11946 11947 4102 +a 11947 11948 4102 +a 11948 11949 4102 +a 11949 11950 4102 +a 11950 11951 4102 +a 11951 11952 4102 +a 11952 11953 4102 +a 11953 11954 4102 +a 11954 11955 4102 +a 11955 11956 4102 +a 11956 11957 4102 +a 11957 11958 4102 +a 11958 11959 4102 +a 11959 11960 4102 +a 11960 11961 4102 +a 11961 11962 4102 +a 11962 11963 4102 +a 11963 11964 4102 +a 11964 11965 4102 +a 11965 11966 4102 +a 11966 11967 4102 +a 11967 11968 4102 +a 11968 11969 4102 +a 11969 11970 4102 +a 11970 11971 4102 +a 11971 11972 4102 +a 11972 11973 4102 +a 11973 11974 4102 +a 11974 11975 4102 +a 11975 11976 4102 +a 11976 11977 4102 +a 11977 11978 4102 +a 11978 11979 4102 +a 11979 11980 4102 +a 11980 11981 4102 +a 11981 11982 4102 +a 11982 11983 4102 +a 11983 11984 4102 +a 11984 11985 4102 +a 11985 11986 4102 +a 11986 11987 4102 +a 11987 11988 4102 +a 11988 11989 4102 +a 11989 11990 4102 +a 11990 11991 4102 +a 11991 11992 4102 +a 11992 11993 4102 +a 11993 11994 4102 +a 11994 11995 4102 +a 11995 11996 4102 +a 11996 11997 4102 +a 11997 11998 4102 +a 11998 11999 4102 +a 11999 12000 4102 +a 12000 12001 4102 +a 12001 12002 4102 +a 12002 12003 4102 +a 12003 12004 4102 +a 12004 12005 4102 +a 12005 12006 4102 +a 12006 12007 4102 +a 12007 12008 4102 +a 12008 12009 4102 +a 12009 12010 4102 +a 12010 12011 4102 +a 12011 12012 4102 +a 12012 12013 4102 +a 12013 12014 4102 +a 12014 12015 4102 +a 12015 12016 4102 +a 12016 12017 4102 +a 12017 12018 4102 +a 12018 12019 4102 +a 12019 12020 4102 +a 12020 12021 4102 +a 12021 12022 4102 +a 12022 12023 4102 +a 12023 12024 4102 +a 12024 12025 4102 +a 12025 12026 4102 +a 12026 12027 4102 +a 12027 12028 4102 +a 12028 12029 4102 +a 12029 12030 4102 +a 12030 12031 4102 +a 12031 12032 4102 +a 12032 12033 4102 +a 12033 12034 4102 +a 12034 12035 4102 +a 12035 12036 4102 +a 12036 12037 4102 +a 12037 12038 4102 +a 12038 12039 4102 +a 12039 12040 4102 +a 12040 12041 4102 +a 12041 12042 4102 +a 12042 12043 4102 +a 12043 12044 4102 +a 12044 12045 4102 +a 12045 12046 4102 +a 12046 12047 4102 +a 12047 12048 4102 +a 12048 12049 4102 +a 12049 12050 4102 +a 12050 12051 4102 +a 12051 12052 4102 +a 12052 12053 4102 +a 12053 12054 4102 +a 12054 12055 4102 +a 12055 12056 4102 +a 12056 12057 4102 +a 12057 12058 4102 +a 12058 12059 4102 +a 12059 12060 4102 +a 12060 12061 4102 +a 12061 12062 4102 +a 12062 12063 4102 +a 12063 12064 4102 +a 12064 12065 4102 +a 12065 12066 4102 +a 12066 12067 4102 +a 12067 12068 4102 +a 12068 12069 4102 +a 12069 12070 4102 +a 12070 12071 4102 +a 12071 12072 4102 +a 12072 12073 4102 +a 12073 12074 4102 +a 12074 12075 4102 +a 12075 12076 4102 +a 12076 12077 4102 +a 12077 12078 4102 +a 12078 12079 4102 +a 12079 12080 4102 +a 12080 12081 4102 +a 12081 12082 4102 +a 12082 12083 4102 +a 12083 12084 4102 +a 12084 12085 4102 +a 12085 12086 4102 +a 12086 12087 4102 +a 12087 12088 4102 +a 12088 12089 4102 +a 12089 12090 4102 +a 12090 12091 4102 +a 12091 12092 4102 +a 12092 12093 4102 +a 12093 12094 4102 +a 12094 12095 4102 +a 12095 12096 4102 +a 12096 12097 4102 +a 12097 12098 4102 +a 12098 12099 4102 +a 12099 12100 4102 +a 12100 12101 4102 +a 12101 12102 4102 +a 12102 12103 4102 +a 12103 12104 4102 +a 12104 12105 4102 +a 12105 12106 4102 +a 12106 12107 4102 +a 12107 12108 4102 +a 12108 12109 4102 +a 12109 12110 4102 +a 12110 12111 4102 +a 12111 12112 4102 +a 12112 12113 4102 +a 12113 12114 4102 +a 12114 12115 4102 +a 12115 12116 4102 +a 12116 12117 4102 +a 12117 12118 4102 +a 12118 12119 4102 +a 12119 12120 4102 +a 12120 12121 4102 +a 12121 12122 4102 +a 12122 12123 4102 +a 12123 12124 4102 +a 12124 12125 4102 +a 12125 12126 4102 +a 12126 12127 4102 +a 12127 12128 4102 +a 12128 12129 4102 +a 12129 12130 4102 +a 12130 12131 4102 +a 12131 12132 4102 +a 12132 12133 4102 +a 12133 12134 4102 +a 12134 12135 4102 +a 12135 12136 4102 +a 12136 12137 4102 +a 12137 12138 4102 +a 12138 12139 4102 +a 12139 12140 4102 +a 12140 12141 4102 +a 12141 12142 4102 +a 12142 12143 4102 +a 12143 12144 4102 +a 12144 12145 4102 +a 12145 12146 4102 +a 12146 12147 4102 +a 12147 12148 4102 +a 12148 12149 4102 +a 12149 12150 4102 +a 12150 12151 4102 +a 12151 12152 4102 +a 12152 12153 4102 +a 12153 12154 4102 +a 12154 12155 4102 +a 12155 12156 4102 +a 12156 12157 4102 +a 12157 12158 4102 +a 12158 12159 4102 +a 12159 12160 4102 +a 12160 12161 4102 +a 12161 12162 4102 +a 12162 12163 4102 +a 12163 12164 4102 +a 12164 12165 4102 +a 12165 12166 4102 +a 12166 12167 4102 +a 12167 12168 4102 +a 12168 12169 4102 +a 12169 12170 4102 +a 12170 12171 4102 +a 12171 12172 4102 +a 12172 12173 4102 +a 12173 12174 4102 +a 12174 12175 4102 +a 12175 12176 4102 +a 12176 12177 4102 +a 12177 12178 4102 +a 12178 12179 4102 +a 12179 12180 4102 +a 12180 12181 4102 +a 12181 12182 4102 +a 12182 12183 4102 +a 12183 12184 4102 +a 12184 12185 4102 +a 12185 12186 4102 +a 12186 12187 4102 +a 12187 12188 4102 +a 12188 12189 4102 +a 12189 12190 4102 +a 12190 12191 4102 +a 12191 12192 4102 +a 12192 12193 4102 +a 12193 12194 4102 +a 12194 12195 4102 +a 12195 12196 4102 +a 12196 12197 4102 +a 12197 12198 4102 +a 12198 12199 4102 +a 12199 12200 4102 +a 12200 12201 4102 +a 12201 12202 4102 +a 12202 12203 4102 +a 12203 12204 4102 +a 12204 12205 4102 +a 12205 12206 4102 +a 12206 12207 4102 +a 12207 12208 4102 +a 12208 12209 4102 +a 12209 12210 4102 +a 12210 12211 4102 +a 12211 12212 4102 +a 12212 12213 4102 +a 12213 12214 4102 +a 12214 12215 4102 +a 12215 12216 4102 +a 12216 12217 4102 +a 12217 12218 4102 +a 12218 12219 4102 +a 12219 12220 4102 +a 12220 12221 4102 +a 12221 12222 4102 +a 12222 12223 4102 +a 12223 12224 4102 +a 12224 12225 4102 +a 12225 12226 4102 +a 12226 12227 4102 +a 12227 12228 4102 +a 12228 12229 4102 +a 12229 12230 4102 +a 12230 12231 4102 +a 12231 12232 4102 +a 12232 12233 4102 +a 12233 12234 4102 +a 12234 12235 4102 +a 12235 12236 4102 +a 12236 12237 4102 +a 12237 12238 4102 +a 12238 12239 4102 +a 12239 12240 4102 +a 12240 12241 4102 +a 12241 12242 4102 +a 12242 12243 4102 +a 12243 12244 4102 +a 12244 12245 4102 +a 12245 12246 4102 +a 12246 12247 4102 +a 12247 12248 4102 +a 12248 12249 4102 +a 12249 12250 4102 +a 12250 12251 4102 +a 12251 12252 4102 +a 12252 12253 4102 +a 12253 12254 4102 +a 12254 12255 4102 +a 12255 12256 4102 +a 12256 12257 4102 +a 12257 12258 4102 +a 12258 12259 4102 +a 12259 12260 4102 +a 12260 12261 4102 +a 12261 12262 4102 +a 12262 12263 4102 +a 12263 12264 4102 +a 12264 12265 4102 +a 12265 12266 4102 +a 12266 12267 4102 +a 12267 12268 4102 +a 12268 12269 4102 +a 12269 12270 4102 +a 12270 12271 4102 +a 12271 12272 4102 +a 12272 12273 4102 +a 12273 12274 4102 +a 12274 12275 4102 +a 12275 12276 4102 +a 12276 12277 4102 +a 12277 12278 4102 +a 12278 12279 4102 +a 12279 12280 4102 +a 12280 12281 4102 +a 12281 12282 4102 +a 12282 12283 4102 +a 12283 12284 4102 +a 12284 12285 4102 +a 12285 12286 4102 +a 12286 12287 4102 +a 12287 12288 4102 +a 12288 12289 4102 +a 12289 12290 4102 +a 12290 12291 4102 +a 12291 12292 4102 +a 12292 12293 4102 +a 12293 12294 4102 +a 12294 12295 4102 +a 12295 12296 4102 +a 12296 12297 4102 +a 12297 12298 4102 +a 12298 12299 4102 +a 12299 12300 4102 +a 12300 12301 4102 +a 12301 12302 4102 +a 12302 12303 4102 +a 12303 12304 4102 +a 12304 12305 4102 +a 12305 12306 4102 +a 12306 12307 4102 +a 12307 12308 4102 +a 12308 12309 4102 +a 12309 12310 4102 +a 12310 12311 4102 +a 12311 12312 4102 +a 12312 12313 4102 +a 12313 12314 4102 +a 12314 12315 4102 +a 12315 12316 4102 +a 12316 12317 4102 +a 12317 12318 4102 +a 12318 12319 4102 +a 12319 12320 4102 +a 12320 12321 4102 +a 12321 12322 4102 +a 12322 12323 4102 +a 12323 12324 4102 +a 12324 12325 4102 +a 12325 12326 4102 +a 12326 12327 4102 +a 12327 12328 4102 +a 12328 12329 4102 +a 12329 12330 4102 +a 12330 12331 4102 +a 12331 12332 4102 +a 12332 12333 4102 +a 12333 12334 4102 +a 12334 12335 4102 +a 12335 12336 4102 +a 12336 12337 4102 +a 12337 12338 4102 +a 12338 12339 4102 +a 12339 12340 4102 +a 12340 12341 4102 +a 12341 12342 4102 +a 12342 12343 4102 +a 12343 12344 4102 +a 12344 12345 4102 +a 12345 12346 4102 +a 12346 12347 4102 +a 12347 12348 4102 +a 12348 12349 4102 +a 12349 12350 4102 +a 12350 12351 4102 +a 12351 12352 4102 +a 12352 12353 4102 +a 12353 12354 4102 +a 12354 12355 4102 +a 12355 12356 4102 +a 12356 12357 4102 +a 12357 12358 4102 +a 12358 12359 4102 +a 12359 12360 4102 +a 12360 12361 4102 +a 12361 12362 4102 +a 12362 12363 4102 +a 12363 12364 4102 +a 12364 12365 4102 +a 12365 12366 4102 +a 12366 12367 4102 +a 12367 12368 4102 +a 12368 12369 4102 +a 12369 12370 4102 +a 12370 12371 4102 +a 12371 12372 4102 +a 12372 12373 4102 +a 12373 12374 4102 +a 12374 12375 4102 +a 12375 12376 4102 +a 12376 12377 4102 +a 12377 12378 4102 +a 12378 12379 4102 +a 12379 12380 4102 +a 12380 12381 4102 +a 12381 12382 4102 +a 12382 12383 4102 +a 12383 12384 4102 +a 12384 12385 4102 +a 12385 12386 4102 +a 12386 12387 4102 +a 12387 12388 4102 +a 12388 12389 4102 +a 12389 12390 4102 +a 12390 12391 4102 +a 12391 12392 4102 +a 12392 12393 4102 +a 12393 12394 4102 +a 12394 12395 4102 +a 12395 12396 4102 +a 12396 12397 4102 +a 12397 12398 4102 +a 12398 12399 4102 +a 12399 12400 4102 +a 12400 12401 4102 +a 12401 12402 4102 +a 12402 12403 4102 +a 12403 12404 4102 +a 12404 12405 4102 +a 12405 12406 4102 +a 12406 12407 4102 +a 12407 12408 4102 +a 12408 12409 4102 +a 12409 12410 4102 +a 12410 12411 4102 +a 12411 12412 4102 +a 12412 12413 4102 +a 12413 12414 4102 +a 12414 12415 4102 +a 12415 12416 4102 +a 12416 12417 4102 +a 12417 12418 4102 +a 12418 12419 4102 +a 12419 12420 4102 +a 12420 12421 4102 +a 12421 12422 4102 +a 12422 12423 4102 +a 12423 12424 4102 +a 12424 12425 4102 +a 12425 12426 4102 +a 12426 12427 4102 +a 12427 12428 4102 +a 12428 12429 4102 +a 12429 12430 4102 +a 12430 12431 4102 +a 12431 12432 4102 +a 12432 12433 4102 +a 12433 12434 4102 +a 12434 12435 4102 +a 12435 12436 4102 +a 12436 12437 4102 +a 12437 12438 4102 +a 12438 12439 4102 +a 12439 12440 4102 +a 12440 12441 4102 +a 12441 12442 4102 +a 12442 12443 4102 +a 12443 12444 4102 +a 12444 12445 4102 +a 12445 12446 4102 +a 12446 12447 4102 +a 12447 12448 4102 +a 12448 12449 4102 +a 12449 12450 4102 +a 12450 12451 4102 +a 12451 12452 4102 +a 12452 12453 4102 +a 12453 12454 4102 +a 12454 12455 4102 +a 12455 12456 4102 +a 12456 12457 4102 +a 12457 12458 4102 +a 12458 12459 4102 +a 12459 12460 4102 +a 12460 12461 4102 +a 12461 12462 4102 +a 12462 12463 4102 +a 12463 12464 4102 +a 12464 12465 4102 +a 12465 12466 4102 +a 12466 12467 4102 +a 12467 12468 4102 +a 12468 12469 4102 +a 12469 12470 4102 +a 12470 12471 4102 +a 12471 12472 4102 +a 12472 12473 4102 +a 12473 12474 4102 +a 12474 12475 4102 +a 12475 12476 4102 +a 12476 12477 4102 +a 12477 12478 4102 +a 12478 12479 4102 +a 12479 12480 4102 +a 12480 12481 4102 +a 12481 12482 4102 +a 12482 12483 4102 +a 12483 12484 4102 +a 12484 12485 4102 +a 12485 12486 4102 +a 12486 12487 4102 +a 12487 12488 4102 +a 12488 12489 4102 +a 12489 12490 4102 +a 12490 12491 4102 +a 12491 12492 4102 +a 12492 12493 4102 +a 12493 12494 4102 +a 12494 12495 4102 +a 12495 12496 4102 +a 12496 12497 4102 +a 12497 12498 4102 +a 12498 12499 4102 +a 12499 12500 4102 +a 12500 12501 4102 +a 12501 12502 4102 +a 12502 12503 4102 +a 12503 12504 4102 +a 12504 12505 4102 +a 12505 12506 4102 +a 12506 12507 4102 +a 12507 12508 4102 +a 12508 12509 4102 +a 12509 12510 4102 +a 12510 12511 4102 +a 12511 12512 4102 +a 12512 12513 4102 +a 12513 12514 4102 +a 12514 12515 4102 +a 12515 12516 4102 +a 12516 12517 4102 +a 12517 12518 4102 +a 12518 12519 4102 +a 12519 12520 4102 +a 12520 12521 4102 +a 12521 12522 4102 +a 12522 12523 4102 +a 12523 12524 4102 +a 12524 12525 4102 +a 12525 12526 4102 +a 12526 12527 4102 +a 12527 12528 4102 +a 12528 12529 4102 +a 12529 12530 4102 +a 12530 12531 4102 +a 12531 12532 4102 +a 12532 12533 4102 +a 12533 12534 4102 +a 12534 12535 4102 +a 12535 12536 4102 +a 12536 12537 4102 +a 12537 12538 4102 +a 12538 12539 4102 +a 12539 12540 4102 +a 12540 12541 4102 +a 12541 12542 4102 +a 12542 12543 4102 +a 12543 12544 4102 +a 12544 12545 4102 +a 12545 12546 4102 +a 12546 12547 4102 +a 12547 12548 4102 +a 12548 12549 4102 +a 12549 12550 4102 +a 12550 12551 4102 +a 12551 12552 4102 +a 12552 12553 4102 +a 12553 12554 4102 +a 12554 12555 4102 +a 12555 12556 4102 +a 12556 12557 4102 +a 12557 12558 4102 +a 12558 12559 4102 +a 12559 12560 4102 +a 12560 12561 4102 +a 12561 12562 4102 +a 12562 12563 4102 +a 12563 12564 4102 +a 12564 12565 4102 +a 12565 12566 4102 +a 12566 12567 4102 +a 12567 12568 4102 +a 12568 12569 4102 +a 12569 12570 4102 +a 12570 12571 4102 +a 12571 12572 4102 +a 12572 12573 4102 +a 12573 12574 4102 +a 12574 12575 4102 +a 12575 12576 4102 +a 12576 12577 4102 +a 12577 12578 4102 +a 12578 12579 4102 +a 12579 12580 4102 +a 12580 12581 4102 +a 12581 12582 4102 +a 12582 12583 4102 +a 12583 12584 4102 +a 12584 12585 4102 +a 12585 12586 4102 +a 12586 12587 4102 +a 12587 12588 4102 +a 12588 12589 4102 +a 12589 12590 4102 +a 12590 12591 4102 +a 12591 12592 4102 +a 12592 12593 4102 +a 12593 12594 4102 +a 12594 12595 4102 +a 12595 12596 4102 +a 12596 12597 4102 +a 12597 12598 4102 +a 12598 12599 4102 +a 12599 12600 4102 +a 12600 12601 4102 +a 12601 12602 4102 +a 12602 12603 4102 +a 12603 12604 4102 +a 12604 12605 4102 +a 12605 12606 4102 +a 12606 12607 4102 +a 12607 12608 4102 +a 12608 12609 4102 +a 12609 12610 4102 +a 12610 12611 4102 +a 12611 12612 4102 +a 12612 12613 4102 +a 12613 12614 4102 +a 12614 12615 4102 +a 12615 12616 4102 +a 12616 12617 4102 +a 12617 12618 4102 +a 12618 12619 4102 +a 12619 12620 4102 +a 12620 12621 4102 +a 12621 12622 4102 +a 12622 12623 4102 +a 12623 12624 4102 +a 12624 12625 4102 +a 12625 12626 4102 +a 12626 12627 4102 +a 12627 12628 4102 +a 12628 12629 4102 +a 12629 12630 4102 +a 12630 12631 4102 +a 12631 12632 4102 +a 12632 12633 4102 +a 12633 12634 4102 +a 12634 12635 4102 +a 12635 12636 4102 +a 12636 12637 4102 +a 12637 12638 4102 +a 12638 12639 4102 +a 12639 12640 4102 +a 12640 12641 4102 +a 12641 12642 4102 +a 12642 12643 4102 +a 12643 12644 4102 +a 12644 12645 4102 +a 12645 12646 4102 +a 12646 12647 4102 +a 12647 12648 4102 +a 12648 12649 4102 +a 12649 12650 4102 +a 12650 12651 4102 +a 12651 12652 4102 +a 12652 12653 4102 +a 12653 12654 4102 +a 12654 12655 4102 +a 12655 12656 4102 +a 12656 12657 4102 +a 12657 12658 4102 +a 12658 12659 4102 +a 12659 12660 4102 +a 12660 12661 4102 +a 12661 12662 4102 +a 12662 12663 4102 +a 12663 12664 4102 +a 12664 12665 4102 +a 12665 12666 4102 +a 12666 12667 4102 +a 12667 12668 4102 +a 12668 12669 4102 +a 12669 12670 4102 +a 12670 12671 4102 +a 12671 12672 4102 +a 12672 12673 4102 +a 12673 12674 4102 +a 12674 12675 4102 +a 12675 12676 4102 +a 12676 12677 4102 +a 12677 12678 4102 +a 12678 12679 4102 +a 12679 12680 4102 +a 12680 12681 4102 +a 12681 12682 4102 +a 12682 12683 4102 +a 12683 12684 4102 +a 12684 12685 4102 +a 12685 12686 4102 +a 12686 12687 4102 +a 12687 12688 4102 +a 12688 12689 4102 +a 12689 12690 4102 +a 12690 12691 4102 +a 12691 12692 4102 +a 12692 12693 4102 +a 12693 12694 4102 +a 12694 12695 4102 +a 12695 12696 4102 +a 12696 12697 4102 +a 12697 12698 4102 +a 12698 12699 4102 +a 12699 12700 4102 +a 12700 12701 4102 +a 12701 12702 4102 +a 12702 12703 4102 +a 12703 12704 4102 +a 12704 12705 4102 +a 12705 12706 4102 +a 12706 12707 4102 +a 12707 12708 4102 +a 12708 12709 4102 +a 12709 12710 4102 +a 12710 12711 4102 +a 12711 12712 4102 +a 12712 12713 4102 +a 12713 12714 4102 +a 12714 12715 4102 +a 12715 12716 4102 +a 12716 12717 4102 +a 12717 12718 4102 +a 12718 12719 4102 +a 12719 12720 4102 +a 12720 12721 4102 +a 12721 12722 4102 +a 12722 12723 4102 +a 12723 12724 4102 +a 12724 12725 4102 +a 12725 12726 4102 +a 12726 12727 4102 +a 12727 12728 4102 +a 12728 12729 4102 +a 12729 12730 4102 +a 12730 12731 4102 +a 12731 12732 4102 +a 12732 12733 4102 +a 12733 12734 4102 +a 12734 12735 4102 +a 12735 12736 4102 +a 12736 12737 4102 +a 12737 12738 4102 +a 12738 12739 4102 +a 12739 12740 4102 +a 12740 12741 4102 +a 12741 12742 4102 +a 12742 12743 4102 +a 12743 12744 4102 +a 12744 12745 4102 +a 12745 12746 4102 +a 12746 12747 4102 +a 12747 12748 4102 +a 12748 12749 4102 +a 12749 12750 4102 +a 12750 12751 4102 +a 12751 12752 4102 +a 12752 12753 4102 +a 12753 12754 4102 +a 12754 12755 4102 +a 12755 12756 4102 +a 12756 12757 4102 +a 12757 12758 4102 +a 12758 12759 4102 +a 12759 12760 4102 +a 12760 12761 4102 +a 12761 12762 4102 +a 12762 12763 4102 +a 12763 12764 4102 +a 12764 12765 4102 +a 12765 12766 4102 +a 12766 12767 4102 +a 12767 12768 4102 +a 12768 12769 4102 +a 12769 12770 4102 +a 12770 12771 4102 +a 12771 12772 4102 +a 12772 12773 4102 +a 12773 12774 4102 +a 12774 12775 4102 +a 12775 12776 4102 +a 12776 12777 4102 +a 12777 12778 4102 +a 12778 12779 4102 +a 12779 12780 4102 +a 12780 12781 4102 +a 12781 12782 4102 +a 12782 12783 4102 +a 12783 12784 4102 +a 12784 12785 4102 +a 12785 12786 4102 +a 12786 12787 4102 +a 12787 12788 4102 +a 12788 12789 4102 +a 12789 12790 4102 +a 12790 12791 4102 +a 12791 12792 4102 +a 12792 12793 4102 +a 12793 12794 4102 +a 12794 12795 4102 +a 12795 12796 4102 +a 12796 12797 4102 +a 12797 12798 4102 +a 12798 12799 4102 +a 12799 12800 4102 +a 12800 12801 4102 +a 12801 12802 4102 +a 12802 12803 4102 +a 12803 12804 4102 +a 12804 12805 4102 +a 12805 12806 4102 +a 12806 12807 4102 +a 12807 12808 4102 +a 12808 12809 4102 +a 12809 12810 4102 +a 12810 12811 4102 +a 12811 12812 4102 +a 12812 12813 4102 +a 12813 12814 4102 +a 12814 12815 4102 +a 12815 12816 4102 +a 12816 12817 4102 +a 12817 12818 4102 +a 12818 12819 4102 +a 12819 12820 4102 +a 12820 12821 4102 +a 12821 12822 4102 +a 12822 12823 4102 +a 12823 12824 4102 +a 12824 12825 4102 +a 12825 12826 4102 +a 12826 12827 4102 +a 12827 12828 4102 +a 12828 12829 4102 +a 12829 12830 4102 +a 12830 12831 4102 +a 12831 12832 4102 +a 12832 12833 4102 +a 12833 12834 4102 +a 12834 12835 4102 +a 12835 12836 4102 +a 12836 12837 4102 +a 12837 12838 4102 +a 12838 12839 4102 +a 12839 12840 4102 +a 12840 12841 4102 +a 12841 12842 4102 +a 12842 12843 4102 +a 12843 12844 4102 +a 12844 12845 4102 +a 12845 12846 4102 +a 12846 12847 4102 +a 12847 12848 4102 +a 12848 12849 4102 +a 12849 12850 4102 +a 12850 12851 4102 +a 12851 12852 4102 +a 12852 12853 4102 +a 12853 12854 4102 +a 12854 12855 4102 +a 12855 12856 4102 +a 12856 12857 4102 +a 12857 12858 4102 +a 12858 12859 4102 +a 12859 12860 4102 +a 12860 12861 4102 +a 12861 12862 4102 +a 12862 12863 4102 +a 12863 12864 4102 +a 12864 12865 4102 +a 12865 12866 4102 +a 12866 12867 4102 +a 12867 12868 4102 +a 12868 12869 4102 +a 12869 12870 4102 +a 12870 12871 4102 +a 12871 12872 4102 +a 12872 12873 4102 +a 12873 12874 4102 +a 12874 12875 4102 +a 12875 12876 4102 +a 12876 12877 4102 +a 12877 12878 4102 +a 12878 12879 4102 +a 12879 12880 4102 +a 12880 12881 4102 +a 12881 12882 4102 +a 12882 12883 4102 +a 12883 12884 4102 +a 12884 12885 4102 +a 12885 12886 4102 +a 12886 12887 4102 +a 12887 12888 4102 +a 12888 12889 4102 +a 12889 12890 4102 +a 12890 12891 4102 +a 12891 12892 4102 +a 12892 12893 4102 +a 12893 12894 4102 +a 12894 12895 4102 +a 12895 12896 4102 +a 12896 12897 4102 +a 12897 12898 4102 +a 12898 12899 4102 +a 12899 12900 4102 +a 12900 12901 4102 +a 12901 12902 4102 +a 12902 12903 4102 +a 12903 12904 4102 +a 12904 12905 4102 +a 12905 12906 4102 +a 12906 12907 4102 +a 12907 12908 4102 +a 12908 12909 4102 +a 12909 12910 4102 +a 12910 12911 4102 +a 12911 12912 4102 +a 12912 12913 4102 +a 12913 12914 4102 +a 12914 12915 4102 +a 12915 12916 4102 +a 12916 12917 4102 +a 12917 12918 4102 +a 12918 12919 4102 +a 12919 12920 4102 +a 12920 12921 4102 +a 12921 12922 4102 +a 12922 12923 4102 +a 12923 12924 4102 +a 12924 12925 4102 +a 12925 12926 4102 +a 12926 12927 4102 +a 12927 12928 4102 +a 12928 12929 4102 +a 12929 12930 4102 +a 12930 12931 4102 +a 12931 12932 4102 +a 12932 12933 4102 +a 12933 12934 4102 +a 12934 12935 4102 +a 12935 12936 4102 +a 12936 12937 4102 +a 12937 12938 4102 +a 12938 12939 4102 +a 12939 12940 4102 +a 12940 12941 4102 +a 12941 12942 4102 +a 12942 12943 4102 +a 12943 12944 4102 +a 12944 12945 4102 +a 12945 12946 4102 +a 12946 12947 4102 +a 12947 12948 4102 +a 12948 12949 4102 +a 12949 12950 4102 +a 12950 12951 4102 +a 12951 12952 4102 +a 12952 12953 4102 +a 12953 12954 4102 +a 12954 12955 4102 +a 12955 12956 4102 +a 12956 12957 4102 +a 12957 12958 4102 +a 12958 12959 4102 +a 12959 12960 4102 +a 12960 12961 4102 +a 12961 12962 4102 +a 12962 12963 4102 +a 12963 12964 4102 +a 12964 12965 4102 +a 12965 12966 4102 +a 12966 12967 4102 +a 12967 12968 4102 +a 12968 12969 4102 +a 12969 12970 4102 +a 12970 12971 4102 +a 12971 12972 4102 +a 12972 12973 4102 +a 12973 12974 4102 +a 12974 12975 4102 +a 12975 12976 4102 +a 12976 12977 4102 +a 12977 12978 4102 +a 12978 12979 4102 +a 12979 12980 4102 +a 12980 12981 4102 +a 12981 12982 4102 +a 12982 12983 4102 +a 12983 12984 4102 +a 12984 12985 4102 +a 12985 12986 4102 +a 12986 12987 4102 +a 12987 12988 4102 +a 12988 12989 4102 +a 12989 12990 4102 +a 12990 12991 4102 +a 12991 12992 4102 +a 12992 12993 4102 +a 12993 12994 4102 +a 12994 12995 4102 +a 12995 12996 4102 +a 12996 12997 4102 +a 12997 12998 4102 +a 12998 12999 4102 +a 12999 13000 4102 +a 13000 13001 4102 +a 13001 13002 4102 +a 13002 13003 4102 +a 13003 13004 4102 +a 13004 13005 4102 +a 13005 13006 4102 +a 13006 13007 4102 +a 13007 13008 4102 +a 13008 13009 4102 +a 13009 13010 4102 +a 13010 13011 4102 +a 13011 13012 4102 +a 13012 13013 4102 +a 13013 13014 4102 +a 13014 13015 4102 +a 13015 13016 4102 +a 13016 13017 4102 +a 13017 13018 4102 +a 13018 13019 4102 +a 13019 13020 4102 +a 13020 13021 4102 +a 13021 13022 4102 +a 13022 13023 4102 +a 13023 13024 4102 +a 13024 13025 4102 +a 13025 13026 4102 +a 13026 13027 4102 +a 13027 13028 4102 +a 13028 13029 4102 +a 13029 13030 4102 +a 13030 13031 4102 +a 13031 13032 4102 +a 13032 13033 4102 +a 13033 13034 4102 +a 13034 13035 4102 +a 13035 13036 4102 +a 13036 13037 4102 +a 13037 13038 4102 +a 13038 13039 4102 +a 13039 13040 4102 +a 13040 13041 4102 +a 13041 13042 4102 +a 13042 13043 4102 +a 13043 13044 4102 +a 13044 13045 4102 +a 13045 13046 4102 +a 13046 13047 4102 +a 13047 13048 4102 +a 13048 13049 4102 +a 13049 13050 4102 +a 13050 13051 4102 +a 13051 13052 4102 +a 13052 13053 4102 +a 13053 13054 4102 +a 13054 13055 4102 +a 13055 13056 4102 +a 13056 13057 4102 +a 13057 13058 4102 +a 13058 13059 4102 +a 13059 13060 4102 +a 13060 13061 4102 +a 13061 13062 4102 +a 13062 13063 4102 +a 13063 13064 4102 +a 13064 13065 4102 +a 13065 13066 4102 +a 13066 13067 4102 +a 13067 13068 4102 +a 13068 13069 4102 +a 13069 13070 4102 +a 13070 13071 4102 +a 13071 13072 4102 +a 13072 13073 4102 +a 13073 13074 4102 +a 13074 13075 4102 +a 13075 13076 4102 +a 13076 13077 4102 +a 13077 13078 4102 +a 13078 13079 4102 +a 13079 13080 4102 +a 13080 13081 4102 +a 13081 13082 4102 +a 13082 13083 4102 +a 13083 13084 4102 +a 13084 13085 4102 +a 13085 13086 4102 +a 13086 13087 4102 +a 13087 13088 4102 +a 13088 13089 4102 +a 13089 13090 4102 +a 13090 13091 4102 +a 13091 13092 4102 +a 13092 13093 4102 +a 13093 13094 4102 +a 13094 13095 4102 +a 13095 13096 4102 +a 13096 13097 4102 +a 13097 13098 4102 +a 13098 13099 4102 +a 13099 13100 4102 +a 13100 13101 4102 +a 13101 13102 4102 +a 13102 13103 4102 +a 13103 13104 4102 +a 13104 13105 4102 +a 13105 13106 4102 +a 13106 13107 4102 +a 13107 13108 4102 +a 13108 13109 4102 +a 13109 13110 4102 +a 13110 13111 4102 +a 13111 13112 4102 +a 13112 13113 4102 +a 13113 13114 4102 +a 13114 13115 4102 +a 13115 13116 4102 +a 13116 13117 4102 +a 13117 13118 4102 +a 13118 13119 4102 +a 13119 13120 4102 +a 13120 13121 4102 +a 13121 13122 4102 +a 13122 13123 4102 +a 13123 13124 4102 +a 13124 13125 4102 +a 13125 13126 4102 +a 13126 13127 4102 +a 13127 13128 4102 +a 13128 13129 4102 +a 13129 13130 4102 +a 13130 13131 4102 +a 13131 13132 4102 +a 13132 13133 4102 +a 13133 13134 4102 +a 13134 13135 4102 +a 13135 13136 4102 +a 13136 13137 4102 +a 13137 13138 4102 +a 13138 13139 4102 +a 13139 13140 4102 +a 13140 13141 4102 +a 13141 13142 4102 +a 13142 13143 4102 +a 13143 13144 4102 +a 13144 13145 4102 +a 13145 13146 4102 +a 13146 13147 4102 +a 13147 13148 4102 +a 13148 13149 4102 +a 13149 13150 4102 +a 13150 13151 4102 +a 13151 13152 4102 +a 13152 13153 4102 +a 13153 13154 4102 +a 13154 13155 4102 +a 13155 13156 4102 +a 13156 13157 4102 +a 13157 13158 4102 +a 13158 13159 4102 +a 13159 13160 4102 +a 13160 13161 4102 +a 13161 13162 4102 +a 13162 13163 4102 +a 13163 13164 4102 +a 13164 13165 4102 +a 13165 13166 4102 +a 13166 13167 4102 +a 13167 13168 4102 +a 13168 13169 4102 +a 13169 13170 4102 +a 13170 13171 4102 +a 13171 13172 4102 +a 13172 13173 4102 +a 13173 13174 4102 +a 13174 13175 4102 +a 13175 13176 4102 +a 13176 13177 4102 +a 13177 13178 4102 +a 13178 13179 4102 +a 13179 13180 4102 +a 13180 13181 4102 +a 13181 13182 4102 +a 13182 13183 4102 +a 13183 13184 4102 +a 13184 13185 4102 +a 13185 13186 4102 +a 13186 13187 4102 +a 13187 13188 4102 +a 13188 13189 4102 +a 13189 13190 4102 +a 13190 13191 4102 +a 13191 13192 4102 +a 13192 13193 4102 +a 13193 13194 4102 +a 13194 13195 4102 +a 13195 13196 4102 +a 13196 13197 4102 +a 13197 13198 4102 +a 13198 13199 4102 +a 13199 13200 4102 +a 13200 13201 4102 +a 13201 13202 4102 +a 13202 13203 4102 +a 13203 13204 4102 +a 13204 13205 4102 +a 13205 13206 4102 +a 13206 13207 4102 +a 13207 13208 4102 +a 13208 13209 4102 +a 13209 13210 4102 +a 13210 13211 4102 +a 13211 13212 4102 +a 13212 13213 4102 +a 13213 13214 4102 +a 13214 13215 4102 +a 13215 13216 4102 +a 13216 13217 4102 +a 13217 13218 4102 +a 13218 13219 4102 +a 13219 13220 4102 +a 13220 13221 4102 +a 13221 13222 4102 +a 13222 13223 4102 +a 13223 13224 4102 +a 13224 13225 4102 +a 13225 13226 4102 +a 13226 13227 4102 +a 13227 13228 4102 +a 13228 13229 4102 +a 13229 13230 4102 +a 13230 13231 4102 +a 13231 13232 4102 +a 13232 13233 4102 +a 13233 13234 4102 +a 13234 13235 4102 +a 13235 13236 4102 +a 13236 13237 4102 +a 13237 13238 4102 +a 13238 13239 4102 +a 13239 13240 4102 +a 13240 13241 4102 +a 13241 13242 4102 +a 13242 13243 4102 +a 13243 13244 4102 +a 13244 13245 4102 +a 13245 13246 4102 +a 13246 13247 4102 +a 13247 13248 4102 +a 13248 13249 4102 +a 13249 13250 4102 +a 13250 13251 4102 +a 13251 13252 4102 +a 13252 13253 4102 +a 13253 13254 4102 +a 13254 13255 4102 +a 13255 13256 4102 +a 13256 13257 4102 +a 13257 13258 4102 +a 13258 13259 4102 +a 13259 13260 4102 +a 13260 13261 4102 +a 13261 13262 4102 +a 13262 13263 4102 +a 13263 13264 4102 +a 13264 13265 4102 +a 13265 13266 4102 +a 13266 13267 4102 +a 13267 13268 4102 +a 13268 13269 4102 +a 13269 13270 4102 +a 13270 13271 4102 +a 13271 13272 4102 +a 13272 13273 4102 +a 13273 13274 4102 +a 13274 13275 4102 +a 13275 13276 4102 +a 13276 13277 4102 +a 13277 13278 4102 +a 13278 13279 4102 +a 13279 13280 4102 +a 13280 13281 4102 +a 13281 13282 4102 +a 13282 13283 4102 +a 13283 13284 4102 +a 13284 13285 4102 +a 13285 13286 4102 +a 13286 13287 4102 +a 13287 13288 4102 +a 13288 13289 4102 +a 13289 13290 4102 +a 13290 13291 4102 +a 13291 13292 4102 +a 13292 13293 4102 +a 13293 13294 4102 +a 13294 13295 4102 +a 13295 13296 4102 +a 13296 13297 4102 +a 13297 13298 4102 +a 13298 13299 4102 +a 13299 13300 4102 +a 13300 13301 4102 +a 13301 13302 4102 +a 13302 13303 4102 +a 13303 13304 4102 +a 13304 13305 4102 +a 13305 13306 4102 +a 13306 13307 4102 +a 13307 13308 4102 +a 13308 13309 4102 +a 13309 13310 4102 +a 13310 13311 4102 +a 13311 13312 4102 +a 13312 13313 4102 +a 13313 13314 4102 +a 13314 13315 4102 +a 13315 13316 4102 +a 13316 13317 4102 +a 13317 13318 4102 +a 13318 13319 4102 +a 13319 13320 4102 +a 13320 13321 4102 +a 13321 13322 4102 +a 13322 13323 4102 +a 13323 13324 4102 +a 13324 13325 4102 +a 13325 13326 4102 +a 13326 13327 4102 +a 13327 13328 4102 +a 13328 13329 4102 +a 13329 13330 4102 +a 13330 13331 4102 +a 13331 13332 4102 +a 13332 13333 4102 +a 13333 13334 4102 +a 13334 13335 4102 +a 13335 13336 4102 +a 13336 13337 4102 +a 13337 13338 4102 +a 13338 13339 4102 +a 13339 13340 4102 +a 13340 13341 4102 +a 13341 13342 4102 +a 13342 13343 4102 +a 13343 13344 4102 +a 13344 13345 4102 +a 13345 13346 4102 +a 13346 13347 4102 +a 13347 13348 4102 +a 13348 13349 4102 +a 13349 13350 4102 +a 13350 13351 4102 +a 13351 13352 4102 +a 13352 13353 4102 +a 13353 13354 4102 +a 13354 13355 4102 +a 13355 13356 4102 +a 13356 13357 4102 +a 13357 13358 4102 +a 13358 13359 4102 +a 13359 13360 4102 +a 13360 13361 4102 +a 13361 13362 4102 +a 13362 13363 4102 +a 13363 13364 4102 +a 13364 13365 4102 +a 13365 13366 4102 +a 13366 13367 4102 +a 13367 13368 4102 +a 13368 13369 4102 +a 13369 13370 4102 +a 13370 13371 4102 +a 13371 13372 4102 +a 13372 13373 4102 +a 13373 13374 4102 +a 13374 13375 4102 +a 13375 13376 4102 +a 13376 13377 4102 +a 13377 13378 4102 +a 13378 13379 4102 +a 13379 13380 4102 +a 13380 13381 4102 +a 13381 13382 4102 +a 13382 13383 4102 +a 13383 13384 4102 +a 13384 13385 4102 +a 13385 13386 4102 +a 13386 13387 4102 +a 13387 13388 4102 +a 13388 13389 4102 +a 13389 13390 4102 +a 13390 13391 4102 +a 13391 13392 4102 +a 13392 13393 4102 +a 13393 13394 4102 +a 13394 13395 4102 +a 13395 13396 4102 +a 13396 13397 4102 +a 13397 13398 4102 +a 13398 13399 4102 +a 13399 13400 4102 +a 13400 13401 4102 +a 13401 13402 4102 +a 13402 13403 4102 +a 13403 13404 4102 +a 13404 13405 4102 +a 13405 13406 4102 +a 13406 13407 4102 +a 13407 13408 4102 +a 13408 13409 4102 +a 13409 13410 4102 +a 13410 13411 4102 +a 13411 13412 4102 +a 13412 13413 4102 +a 13413 13414 4102 +a 13414 13415 4102 +a 13415 13416 4102 +a 13416 13417 4102 +a 13417 13418 4102 +a 13418 13419 4102 +a 13419 13420 4102 +a 13420 13421 4102 +a 13421 13422 4102 +a 13422 13423 4102 +a 13423 13424 4102 +a 13424 13425 4102 +a 13425 13426 4102 +a 13426 13427 4102 +a 13427 13428 4102 +a 13428 13429 4102 +a 13429 13430 4102 +a 13430 13431 4102 +a 13431 13432 4102 +a 13432 13433 4102 +a 13433 13434 4102 +a 13434 13435 4102 +a 13435 13436 4102 +a 13436 13437 4102 +a 13437 13438 4102 +a 13438 13439 4102 +a 13439 13440 4102 +a 13440 13441 4102 +a 13441 13442 4102 +a 13442 13443 4102 +a 13443 13444 4102 +a 13444 13445 4102 +a 13445 13446 4102 +a 13446 13447 4102 +a 13447 13448 4102 +a 13448 13449 4102 +a 13449 13450 4102 +a 13450 13451 4102 +a 13451 13452 4102 +a 13452 13453 4102 +a 13453 13454 4102 +a 13454 13455 4102 +a 13455 13456 4102 +a 13456 13457 4102 +a 13457 13458 4102 +a 13458 13459 4102 +a 13459 13460 4102 +a 13460 13461 4102 +a 13461 13462 4102 +a 13462 13463 4102 +a 13463 13464 4102 +a 13464 13465 4102 +a 13465 13466 4102 +a 13466 13467 4102 +a 13467 13468 4102 +a 13468 13469 4102 +a 13469 13470 4102 +a 13470 13471 4102 +a 13471 13472 4102 +a 13472 13473 4102 +a 13473 13474 4102 +a 13474 13475 4102 +a 13475 13476 4102 +a 13476 13477 4102 +a 13477 13478 4102 +a 13478 13479 4102 +a 13479 13480 4102 +a 13480 13481 4102 +a 13481 13482 4102 +a 13482 13483 4102 +a 13483 13484 4102 +a 13484 13485 4102 +a 13485 13486 4102 +a 13486 13487 4102 +a 13487 13488 4102 +a 13488 13489 4102 +a 13489 13490 4102 +a 13490 13491 4102 +a 13491 13492 4102 +a 13492 13493 4102 +a 13493 13494 4102 +a 13494 13495 4102 +a 13495 13496 4102 +a 13496 13497 4102 +a 13497 13498 4102 +a 13498 13499 4102 +a 13499 13500 4102 +a 13500 13501 4102 +a 13501 13502 4102 +a 13502 13503 4102 +a 13503 13504 4102 +a 13504 13505 4102 +a 13505 13506 4102 +a 13506 13507 4102 +a 13507 13508 4102 +a 13508 13509 4102 +a 13509 13510 4102 +a 13510 13511 4102 +a 13511 13512 4102 +a 13512 13513 4102 +a 13513 13514 4102 +a 13514 13515 4102 +a 13515 13516 4102 +a 13516 13517 4102 +a 13517 13518 4102 +a 13518 13519 4102 +a 13519 13520 4102 +a 13520 13521 4102 +a 13521 13522 4102 +a 13522 13523 4102 +a 13523 13524 4102 +a 13524 13525 4102 +a 13525 13526 4102 +a 13526 13527 4102 +a 13527 13528 4102 +a 13528 13529 4102 +a 13529 13530 4102 +a 13530 13531 4102 +a 13531 13532 4102 +a 13532 13533 4102 +a 13533 13534 4102 +a 13534 13535 4102 +a 13535 13536 4102 +a 13536 13537 4102 +a 13537 13538 4102 +a 13538 13539 4102 +a 13539 13540 4102 +a 13540 13541 4102 +a 13541 13542 4102 +a 13542 13543 4102 +a 13543 13544 4102 +a 13544 13545 4102 +a 13545 13546 4102 +a 13546 13547 4102 +a 13547 13548 4102 +a 13548 13549 4102 +a 13549 13550 4102 +a 13550 13551 4102 +a 13551 13552 4102 +a 13552 13553 4102 +a 13553 13554 4102 +a 13554 13555 4102 +a 13555 13556 4102 +a 13556 13557 4102 +a 13557 13558 4102 +a 13558 13559 4102 +a 13559 13560 4102 +a 13560 13561 4102 +a 13561 13562 4102 +a 13562 13563 4102 +a 13563 13564 4102 +a 13564 13565 4102 +a 13565 13566 4102 +a 13566 13567 4102 +a 13567 13568 4102 +a 13568 13569 4102 +a 13569 13570 4102 +a 13570 13571 4102 +a 13571 13572 4102 +a 13572 13573 4102 +a 13573 13574 4102 +a 13574 13575 4102 +a 13575 13576 4102 +a 13576 13577 4102 +a 13577 13578 4102 +a 13578 13579 4102 +a 13579 13580 4102 +a 13580 13581 4102 +a 13581 13582 4102 +a 13582 13583 4102 +a 13583 13584 4102 +a 13584 13585 4102 +a 13585 13586 4102 +a 13586 13587 4102 +a 13587 13588 4102 +a 13588 13589 4102 +a 13589 13590 4102 +a 13590 13591 4102 +a 13591 13592 4102 +a 13592 13593 4102 +a 13593 13594 4102 +a 13594 13595 4102 +a 13595 13596 4102 +a 13596 13597 4102 +a 13597 13598 4102 +a 13598 13599 4102 +a 13599 13600 4102 +a 13600 13601 4102 +a 13601 13602 4102 +a 13602 13603 4102 +a 13603 13604 4102 +a 13604 13605 4102 +a 13605 13606 4102 +a 13606 13607 4102 +a 13607 13608 4102 +a 13608 13609 4102 +a 13609 13610 4102 +a 13610 13611 4102 +a 13611 13612 4102 +a 13612 13613 4102 +a 13613 13614 4102 +a 13614 13615 4102 +a 13615 13616 4102 +a 13616 13617 4102 +a 13617 13618 4102 +a 13618 13619 4102 +a 13619 13620 4102 +a 13620 13621 4102 +a 13621 13622 4102 +a 13622 13623 4102 +a 13623 13624 4102 +a 13624 13625 4102 +a 13625 13626 4102 +a 13626 13627 4102 +a 13627 13628 4102 +a 13628 13629 4102 +a 13629 13630 4102 +a 13630 13631 4102 +a 13631 13632 4102 +a 13632 13633 4102 +a 13633 13634 4102 +a 13634 13635 4102 +a 13635 13636 4102 +a 13636 13637 4102 +a 13637 13638 4102 +a 13638 13639 4102 +a 13639 13640 4102 +a 13640 13641 4102 +a 13641 13642 4102 +a 13642 13643 4102 +a 13643 13644 4102 +a 13644 13645 4102 +a 13645 13646 4102 +a 13646 13647 4102 +a 13647 13648 4102 +a 13648 13649 4102 +a 13649 13650 4102 +a 13650 13651 4102 +a 13651 13652 4102 +a 13652 13653 4102 +a 13653 13654 4102 +a 13654 13655 4102 +a 13655 13656 4102 +a 13656 13657 4102 +a 13657 13658 4102 +a 13658 13659 4102 +a 13659 13660 4102 +a 13660 13661 4102 +a 13661 13662 4102 +a 13662 13663 4102 +a 13663 13664 4102 +a 13664 13665 4102 +a 13665 13666 4102 +a 13666 13667 4102 +a 13667 13668 4102 +a 13668 13669 4102 +a 13669 13670 4102 +a 13670 13671 4102 +a 13671 13672 4102 +a 13672 13673 4102 +a 13673 13674 4102 +a 13674 13675 4102 +a 13675 13676 4102 +a 13676 13677 4102 +a 13677 13678 4102 +a 13678 13679 4102 +a 13679 13680 4102 +a 13680 13681 4102 +a 13681 13682 4102 +a 13682 13683 4102 +a 13683 13684 4102 +a 13684 13685 4102 +a 13685 13686 4102 +a 13686 13687 4102 +a 13687 13688 4102 +a 13688 13689 4102 +a 13689 13690 4102 +a 13690 13691 4102 +a 13691 13692 4102 +a 13692 13693 4102 +a 13693 13694 4102 +a 13694 13695 4102 +a 13695 13696 4102 +a 13696 13697 4102 +a 13697 13698 4102 +a 13698 13699 4102 +a 13699 13700 4102 +a 13700 13701 4102 +a 13701 13702 4102 +a 13702 13703 4102 +a 13703 13704 4102 +a 13704 13705 4102 +a 13705 13706 4102 +a 13706 13707 4102 +a 13707 13708 4102 +a 13708 13709 4102 +a 13709 13710 4102 +a 13710 13711 4102 +a 13711 13712 4102 +a 13712 13713 4102 +a 13713 13714 4102 +a 13714 13715 4102 +a 13715 13716 4102 +a 13716 13717 4102 +a 13717 13718 4102 +a 13718 13719 4102 +a 13719 13720 4102 +a 13720 13721 4102 +a 13721 13722 4102 +a 13722 13723 4102 +a 13723 13724 4102 +a 13724 13725 4102 +a 13725 13726 4102 +a 13726 13727 4102 +a 13727 13728 4102 +a 13728 13729 4102 +a 13729 13730 4102 +a 13730 13731 4102 +a 13731 13732 4102 +a 13732 13733 4102 +a 13733 13734 4102 +a 13734 13735 4102 +a 13735 13736 4102 +a 13736 13737 4102 +a 13737 13738 4102 +a 13738 13739 4102 +a 13739 13740 4102 +a 13740 13741 4102 +a 13741 13742 4102 +a 13742 13743 4102 +a 13743 13744 4102 +a 13744 13745 4102 +a 13745 13746 4102 +a 13746 13747 4102 +a 13747 13748 4102 +a 13748 13749 4102 +a 13749 13750 4102 +a 13750 13751 4102 +a 13751 13752 4102 +a 13752 13753 4102 +a 13753 13754 4102 +a 13754 13755 4102 +a 13755 13756 4102 +a 13756 13757 4102 +a 13757 13758 4102 +a 13758 13759 4102 +a 13759 13760 4102 +a 13760 13761 4102 +a 13761 13762 4102 +a 13762 13763 4102 +a 13763 13764 4102 +a 13764 13765 4102 +a 13765 13766 4102 +a 13766 13767 4102 +a 13767 13768 4102 +a 13768 13769 4102 +a 13769 13770 4102 +a 13770 13771 4102 +a 13771 13772 4102 +a 13772 13773 4102 +a 13773 13774 4102 +a 13774 13775 4102 +a 13775 13776 4102 +a 13776 13777 4102 +a 13777 13778 4102 +a 13778 13779 4102 +a 13779 13780 4102 +a 13780 13781 4102 +a 13781 13782 4102 +a 13782 13783 4102 +a 13783 13784 4102 +a 13784 13785 4102 +a 13785 13786 4102 +a 13786 13787 4102 +a 13787 13788 4102 +a 13788 13789 4102 +a 13789 13790 4102 +a 13790 13791 4102 +a 13791 13792 4102 +a 13792 13793 4102 +a 13793 13794 4102 +a 13794 13795 4102 +a 13795 13796 4102 +a 13796 13797 4102 +a 13797 13798 4102 +a 13798 13799 4102 +a 13799 13800 4102 +a 13800 13801 4102 +a 13801 13802 4102 +a 13802 13803 4102 +a 13803 13804 4102 +a 13804 13805 4102 +a 13805 13806 4102 +a 13806 13807 4102 +a 13807 13808 4102 +a 13808 13809 4102 +a 13809 13810 4102 +a 13810 13811 4102 +a 13811 13812 4102 +a 13812 13813 4102 +a 13813 13814 4102 +a 13814 13815 4102 +a 13815 13816 4102 +a 13816 13817 4102 +a 13817 13818 4102 +a 13818 13819 4102 +a 13819 13820 4102 +a 13820 13821 4102 +a 13821 13822 4102 +a 13822 13823 4102 +a 13823 13824 4102 +a 13824 13825 4102 +a 13825 13826 4102 +a 13826 13827 4102 +a 13827 13828 4102 +a 13828 13829 4102 +a 13829 13830 4102 +a 13830 13831 4102 +a 13831 13832 4102 +a 13832 13833 4102 +a 13833 13834 4102 +a 13834 13835 4102 +a 13835 13836 4102 +a 13836 13837 4102 +a 13837 13838 4102 +a 13838 13839 4102 +a 13839 13840 4102 +a 13840 13841 4102 +a 13841 13842 4102 +a 13842 13843 4102 +a 13843 13844 4102 +a 13844 13845 4102 +a 13845 13846 4102 +a 13846 13847 4102 +a 13847 13848 4102 +a 13848 13849 4102 +a 13849 13850 4102 +a 13850 13851 4102 +a 13851 13852 4102 +a 13852 13853 4102 +a 13853 13854 4102 +a 13854 13855 4102 +a 13855 13856 4102 +a 13856 13857 4102 +a 13857 13858 4102 +a 13858 13859 4102 +a 13859 13860 4102 +a 13860 13861 4102 +a 13861 13862 4102 +a 13862 13863 4102 +a 13863 13864 4102 +a 13864 13865 4102 +a 13865 13866 4102 +a 13866 13867 4102 +a 13867 13868 4102 +a 13868 13869 4102 +a 13869 13870 4102 +a 13870 13871 4102 +a 13871 13872 4102 +a 13872 13873 4102 +a 13873 13874 4102 +a 13874 13875 4102 +a 13875 13876 4102 +a 13876 13877 4102 +a 13877 13878 4102 +a 13878 13879 4102 +a 13879 13880 4102 +a 13880 13881 4102 +a 13881 13882 4102 +a 13882 13883 4102 +a 13883 13884 4102 +a 13884 13885 4102 +a 13885 13886 4102 +a 13886 13887 4102 +a 13887 13888 4102 +a 13888 13889 4102 +a 13889 13890 4102 +a 13890 13891 4102 +a 13891 13892 4102 +a 13892 13893 4102 +a 13893 13894 4102 +a 13894 13895 4102 +a 13895 13896 4102 +a 13896 13897 4102 +a 13897 13898 4102 +a 13898 13899 4102 +a 13899 13900 4102 +a 13900 13901 4102 +a 13901 13902 4102 +a 13902 13903 4102 +a 13903 13904 4102 +a 13904 13905 4102 +a 13905 13906 4102 +a 13906 13907 4102 +a 13907 13908 4102 +a 13908 13909 4102 +a 13909 13910 4102 +a 13910 13911 4102 +a 13911 13912 4102 +a 13912 13913 4102 +a 13913 13914 4102 +a 13914 13915 4102 +a 13915 13916 4102 +a 13916 13917 4102 +a 13917 13918 4102 +a 13918 13919 4102 +a 13919 13920 4102 +a 13920 13921 4102 +a 13921 13922 4102 +a 13922 13923 4102 +a 13923 13924 4102 +a 13924 13925 4102 +a 13925 13926 4102 +a 13926 13927 4102 +a 13927 13928 4102 +a 13928 13929 4102 +a 13929 13930 4102 +a 13930 13931 4102 +a 13931 13932 4102 +a 13932 13933 4102 +a 13933 13934 4102 +a 13934 13935 4102 +a 13935 13936 4102 +a 13936 13937 4102 +a 13937 13938 4102 +a 13938 13939 4102 +a 13939 13940 4102 +a 13940 13941 4102 +a 13941 13942 4102 +a 13942 13943 4102 +a 13943 13944 4102 +a 13944 13945 4102 +a 13945 13946 4102 +a 13946 13947 4102 +a 13947 13948 4102 +a 13948 13949 4102 +a 13949 13950 4102 +a 13950 13951 4102 +a 13951 13952 4102 +a 13952 13953 4102 +a 13953 13954 4102 +a 13954 13955 4102 +a 13955 13956 4102 +a 13956 13957 4102 +a 13957 13958 4102 +a 13958 13959 4102 +a 13959 13960 4102 +a 13960 13961 4102 +a 13961 13962 4102 +a 13962 13963 4102 +a 13963 13964 4102 +a 13964 13965 4102 +a 13965 13966 4102 +a 13966 13967 4102 +a 13967 13968 4102 +a 13968 13969 4102 +a 13969 13970 4102 +a 13970 13971 4102 +a 13971 13972 4102 +a 13972 13973 4102 +a 13973 13974 4102 +a 13974 13975 4102 +a 13975 13976 4102 +a 13976 13977 4102 +a 13977 13978 4102 +a 13978 13979 4102 +a 13979 13980 4102 +a 13980 13981 4102 +a 13981 13982 4102 +a 13982 13983 4102 +a 13983 13984 4102 +a 13984 13985 4102 +a 13985 13986 4102 +a 13986 13987 4102 +a 13987 13988 4102 +a 13988 13989 4102 +a 13989 13990 4102 +a 13990 13991 4102 +a 13991 13992 4102 +a 13992 13993 4102 +a 13993 13994 4102 +a 13994 13995 4102 +a 13995 13996 4102 +a 13996 13997 4102 +a 13997 13998 4102 +a 13998 13999 4102 +a 13999 14000 4102 +a 14000 14001 4102 +a 14001 14002 4102 +a 14002 14003 4102 +a 14003 14004 4102 +a 14004 14005 4102 +a 14005 14006 4102 +a 14006 14007 4102 +a 14007 14008 4102 +a 14008 14009 4102 +a 14009 14010 4102 +a 14010 14011 4102 +a 14011 14012 4102 +a 14012 14013 4102 +a 14013 14014 4102 +a 14014 14015 4102 +a 14015 14016 4102 +a 14016 14017 4102 +a 14017 14018 4102 +a 14018 14019 4102 +a 14019 14020 4102 +a 14020 14021 4102 +a 14021 14022 4102 +a 14022 14023 4102 +a 14023 14024 4102 +a 14024 14025 4102 +a 14025 14026 4102 +a 14026 14027 4102 +a 14027 14028 4102 +a 14028 14029 4102 +a 14029 14030 4102 +a 14030 14031 4102 +a 14031 14032 4102 +a 14032 14033 4102 +a 14033 14034 4102 +a 14034 14035 4102 +a 14035 14036 4102 +a 14036 14037 4102 +a 14037 14038 4102 +a 14038 14039 4102 +a 14039 14040 4102 +a 14040 14041 4102 +a 14041 14042 4102 +a 14042 14043 4102 +a 14043 14044 4102 +a 14044 14045 4102 +a 14045 14046 4102 +a 14046 14047 4102 +a 14047 14048 4102 +a 14048 14049 4102 +a 14049 14050 4102 +a 14050 14051 4102 +a 14051 14052 4102 +a 14052 14053 4102 +a 14053 14054 4102 +a 14054 14055 4102 +a 14055 14056 4102 +a 14056 14057 4102 +a 14057 14058 4102 +a 14058 14059 4102 +a 14059 14060 4102 +a 14060 14061 4102 +a 14061 14062 4102 +a 14062 14063 4102 +a 14063 14064 4102 +a 14064 14065 4102 +a 14065 14066 4102 +a 14066 14067 4102 +a 14067 14068 4102 +a 14068 14069 4102 +a 14069 14070 4102 +a 14070 14071 4102 +a 14071 14072 4102 +a 14072 14073 4102 +a 14073 14074 4102 +a 14074 14075 4102 +a 14075 14076 4102 +a 14076 14077 4102 +a 14077 14078 4102 +a 14078 14079 4102 +a 14079 14080 4102 +a 14080 14081 4102 +a 14081 14082 4102 +a 14082 14083 4102 +a 14083 14084 4102 +a 14084 14085 4102 +a 14085 14086 4102 +a 14086 14087 4102 +a 14087 14088 4102 +a 14088 14089 4102 +a 14089 14090 4102 +a 14090 14091 4102 +a 14091 14092 4102 +a 14092 14093 4102 +a 14093 14094 4102 +a 14094 14095 4102 +a 14095 14096 4102 +a 14096 14097 4102 +a 14097 14098 4102 +a 14098 14099 4102 +a 14099 14100 4102 +a 14100 14101 4102 +a 14101 14102 4102 +a 14102 14103 4102 +a 14103 14104 4102 +a 14104 14105 4102 +a 14105 14106 4102 +a 14106 14107 4102 +a 14107 14108 4102 +a 14108 14109 4102 +a 14109 14110 4102 +a 14110 14111 4102 +a 14111 14112 4102 +a 14112 14113 4102 +a 14113 14114 4102 +a 14114 14115 4102 +a 14115 14116 4102 +a 14116 14117 4102 +a 14117 14118 4102 +a 14118 14119 4102 +a 14119 14120 4102 +a 14120 14121 4102 +a 14121 14122 4102 +a 14122 14123 4102 +a 14123 14124 4102 +a 14124 14125 4102 +a 14125 14126 4102 +a 14126 14127 4102 +a 14127 14128 4102 +a 14128 14129 4102 +a 14129 14130 4102 +a 14130 14131 4102 +a 14131 14132 4102 +a 14132 14133 4102 +a 14133 14134 4102 +a 14134 14135 4102 +a 14135 14136 4102 +a 14136 14137 4102 +a 14137 14138 4102 +a 14138 14139 4102 +a 14139 14140 4102 +a 14140 14141 4102 +a 14141 14142 4102 +a 14142 14143 4102 +a 14143 14144 4102 +a 14144 14145 4102 +a 14145 14146 4102 +a 14146 14147 4102 +a 14147 14148 4102 +a 14148 14149 4102 +a 14149 14150 4102 +a 14150 14151 4102 +a 14151 14152 4102 +a 14152 14153 4102 +a 14153 14154 4102 +a 14154 14155 4102 +a 14155 14156 4102 +a 14156 14157 4102 +a 14157 14158 4102 +a 14158 14159 4102 +a 14159 14160 4102 +a 14160 14161 4102 +a 14161 14162 4102 +a 14162 14163 4102 +a 14163 14164 4102 +a 14164 14165 4102 +a 14165 14166 4102 +a 14166 14167 4102 +a 14167 14168 4102 +a 14168 14169 4102 +a 14169 14170 4102 +a 14170 14171 4102 +a 14171 14172 4102 +a 14172 14173 4102 +a 14173 14174 4102 +a 14174 14175 4102 +a 14175 14176 4102 +a 14176 14177 4102 +a 14177 14178 4102 +a 14178 14179 4102 +a 14179 14180 4102 +a 14180 14181 4102 +a 14181 14182 4102 +a 14182 14183 4102 +a 14183 14184 4102 +a 14184 14185 4102 +a 14185 14186 4102 +a 14186 14187 4102 +a 14187 14188 4102 +a 14188 14189 4102 +a 14189 14190 4102 +a 14190 14191 4102 +a 14191 14192 4102 +a 14192 14193 4102 +a 14193 14194 4102 +a 14194 14195 4102 +a 14195 14196 4102 +a 14196 14197 4102 +a 14197 14198 4102 +a 14198 14199 4102 +a 14199 14200 4102 +a 14200 14201 4102 +a 14201 14202 4102 +a 14202 14203 4102 +a 14203 14204 4102 +a 14204 14205 4102 +a 14205 14206 4102 +a 14206 14207 4102 +a 14207 14208 4102 +a 14208 14209 4102 +a 14209 14210 4102 +a 14210 14211 4102 +a 14211 14212 4102 +a 14212 14213 4102 +a 14213 14214 4102 +a 14214 14215 4102 +a 14215 14216 4102 +a 14216 14217 4102 +a 14217 14218 4102 +a 14218 14219 4102 +a 14219 14220 4102 +a 14220 14221 4102 +a 14221 14222 4102 +a 14222 14223 4102 +a 14223 14224 4102 +a 14224 14225 4102 +a 14225 14226 4102 +a 14226 14227 4102 +a 14227 14228 4102 +a 14228 14229 4102 +a 14229 14230 4102 +a 14230 14231 4102 +a 14231 14232 4102 +a 14232 14233 4102 +a 14233 14234 4102 +a 14234 14235 4102 +a 14235 14236 4102 +a 14236 14237 4102 +a 14237 14238 4102 +a 14238 14239 4102 +a 14239 14240 4102 +a 14240 14241 4102 +a 14241 14242 4102 +a 14242 14243 4102 +a 14243 14244 4102 +a 14244 14245 4102 +a 14245 14246 4102 +a 14246 14247 4102 +a 14247 14248 4102 +a 14248 14249 4102 +a 14249 14250 4102 +a 14250 14251 4102 +a 14251 14252 4102 +a 14252 14253 4102 +a 14253 14254 4102 +a 14254 14255 4102 +a 14255 14256 4102 +a 14256 14257 4102 +a 14257 14258 4102 +a 14258 14259 4102 +a 14259 14260 4102 +a 14260 14261 4102 +a 14261 14262 4102 +a 14262 14263 4102 +a 14263 14264 4102 +a 14264 14265 4102 +a 14265 14266 4102 +a 14266 14267 4102 +a 14267 14268 4102 +a 14268 14269 4102 +a 14269 14270 4102 +a 14270 14271 4102 +a 14271 14272 4102 +a 14272 14273 4102 +a 14273 14274 4102 +a 14274 14275 4102 +a 14275 14276 4102 +a 14276 14277 4102 +a 14277 14278 4102 +a 14278 14279 4102 +a 14279 14280 4102 +a 14280 14281 4102 +a 14281 14282 4102 +a 14282 14283 4102 +a 14283 14284 4102 +a 14284 14285 4102 +a 14285 14286 4102 +a 14286 14287 4102 +a 14287 14288 4102 +a 14288 14289 4102 +a 14289 14290 4102 +a 14290 14291 4102 +a 14291 14292 4102 +a 14292 14293 4102 +a 14293 14294 4102 +a 14294 14295 4102 +a 14295 14296 4102 +a 14296 14297 4102 +a 14297 14298 4102 +a 14298 14299 4102 +a 14299 14300 4102 +a 14300 14301 4102 +a 14301 14302 4102 +a 14302 14303 4102 +a 14303 14304 4102 +a 14304 14305 4102 +a 14305 14306 4102 +a 14306 14307 4102 +a 14307 14308 4102 +a 14308 14309 4102 +a 14309 14310 4102 +a 14310 14311 4102 +a 14311 14312 4102 +a 14312 14313 4102 +a 14313 14314 4102 +a 14314 14315 4102 +a 14315 14316 4102 +a 14316 14317 4102 +a 14317 14318 4102 +a 14318 14319 4102 +a 14319 14320 4102 +a 14320 14321 4102 +a 14321 14322 4102 +a 14322 14323 4102 +a 14323 14324 4102 +a 14324 14325 4102 +a 14325 14326 4102 +a 14326 14327 4102 +a 14327 14328 4102 +a 14328 14329 4102 +a 14329 14330 4102 +a 14330 14331 4102 +a 14331 14332 4102 +a 14332 14333 4102 +a 14333 14334 4102 +a 14334 14335 4102 +a 14335 14336 4102 +a 14336 14337 4102 +a 14337 14338 4102 +a 14338 14339 4102 +a 14339 14340 4102 +a 14340 14341 4102 +a 14341 14342 4102 +a 14342 14343 4102 +a 14343 14344 4102 +a 14344 14345 4102 +a 14345 14346 4102 +a 14346 14347 4102 +a 14347 14348 4102 +a 14348 14349 4102 +a 14349 14350 4102 +a 14350 14351 4102 +a 14351 14352 4102 +a 14352 14353 4102 +a 14353 14354 4102 +a 14354 14355 4102 +a 14355 14356 4102 +a 14356 14357 4102 +a 14357 14358 4102 +a 14358 14359 4102 +a 14359 14360 4102 +a 14360 14361 4102 +a 14361 14362 4102 +a 14362 14363 4102 +a 14363 14364 4102 +a 14364 14365 4102 +a 14365 14366 4102 +a 14366 14367 4102 +a 14367 14368 4102 +a 14368 14369 4102 +a 14369 14370 4102 +a 14370 14371 4102 +a 14371 14372 4102 +a 14372 14373 4102 +a 14373 14374 4102 +a 14374 14375 4102 +a 14375 14376 4102 +a 14376 14377 4102 +a 14377 14378 4102 +a 14378 14379 4102 +a 14379 14380 4102 +a 14380 14381 4102 +a 14381 14382 4102 +a 14382 14383 4102 +a 14383 14384 4102 +a 14384 14385 4102 +a 14385 14386 4102 +a 14386 14387 4102 +a 14387 14388 4102 +a 14388 14389 4102 +a 14389 14390 4102 +a 14390 14391 4102 +a 14391 14392 4102 +a 14392 14393 4102 +a 14393 14394 4102 +a 14394 14395 4102 +a 14395 14396 4102 +a 14396 14397 4102 +a 14397 14398 4102 +a 14398 14399 4102 +a 14399 14400 4102 +a 14400 14401 4102 +a 14401 14402 4102 +a 14402 14403 4102 +a 14403 14404 4102 +a 14404 14405 4102 +a 14405 14406 4102 +a 14406 14407 4102 +a 14407 14408 4102 +a 14408 14409 4102 +a 14409 14410 4102 +a 14410 14411 4102 +a 14411 14412 4102 +a 14412 14413 4102 +a 14413 14414 4102 +a 14414 14415 4102 +a 14415 14416 4102 +a 14416 14417 4102 +a 14417 14418 4102 +a 14418 14419 4102 +a 14419 14420 4102 +a 14420 14421 4102 +a 14421 14422 4102 +a 14422 14423 4102 +a 14423 14424 4102 +a 14424 14425 4102 +a 14425 14426 4102 +a 14426 14427 4102 +a 14427 14428 4102 +a 14428 14429 4102 +a 14429 14430 4102 +a 14430 14431 4102 +a 14431 14432 4102 +a 14432 14433 4102 +a 14433 14434 4102 +a 14434 14435 4102 +a 14435 14436 4102 +a 14436 14437 4102 +a 14437 14438 4102 +a 14438 14439 4102 +a 14439 14440 4102 +a 14440 14441 4102 +a 14441 14442 4102 +a 14442 14443 4102 +a 14443 14444 4102 +a 14444 14445 4102 +a 14445 14446 4102 +a 14446 14447 4102 +a 14447 14448 4102 +a 14448 14449 4102 +a 14449 14450 4102 +a 14450 14451 4102 +a 14451 14452 4102 +a 14452 14453 4102 +a 14453 14454 4102 +a 14454 14455 4102 +a 14455 14456 4102 +a 14456 14457 4102 +a 14457 14458 4102 +a 14458 14459 4102 +a 14459 14460 4102 +a 14460 14461 4102 +a 14461 14462 4102 +a 14462 14463 4102 +a 14463 14464 4102 +a 14464 14465 4102 +a 14465 14466 4102 +a 14466 14467 4102 +a 14467 14468 4102 +a 14468 14469 4102 +a 14469 14470 4102 +a 14470 14471 4102 +a 14471 14472 4102 +a 14472 14473 4102 +a 14473 14474 4102 +a 14474 14475 4102 +a 14475 14476 4102 +a 14476 14477 4102 +a 14477 14478 4102 +a 14478 14479 4102 +a 14479 14480 4102 +a 14480 14481 4102 +a 14481 14482 4102 +a 14482 14483 4102 +a 14483 14484 4102 +a 14484 14485 4102 +a 14485 14486 4102 +a 14486 14487 4102 +a 14487 14488 4102 +a 14488 14489 4102 +a 14489 14490 4102 +a 14490 14491 4102 +a 14491 14492 4102 +a 14492 14493 4102 +a 14493 14494 4102 +a 14494 14495 4102 +a 14495 14496 4102 +a 14496 14497 4102 +a 14497 14498 4102 +a 14498 14499 4102 +a 14499 14500 4102 +a 14500 14501 4102 +a 14501 14502 4102 +a 14502 14503 4102 +a 14503 14504 4102 +a 14504 14505 4102 +a 14505 14506 4102 +a 14506 14507 4102 +a 14507 14508 4102 +a 14508 14509 4102 +a 14509 14510 4102 +a 14510 14511 4102 +a 14511 14512 4102 +a 14512 14513 4102 +a 14513 14514 4102 +a 14514 14515 4102 +a 14515 14516 4102 +a 14516 14517 4102 +a 14517 14518 4102 +a 14518 14519 4102 +a 14519 14520 4102 +a 14520 14521 4102 +a 14521 14522 4102 +a 14522 14523 4102 +a 14523 14524 4102 +a 14524 14525 4102 +a 14525 14526 4102 +a 14526 14527 4102 +a 14527 14528 4102 +a 14528 14529 4102 +a 14529 14530 4102 +a 14530 14531 4102 +a 14531 14532 4102 +a 14532 14533 4102 +a 14533 14534 4102 +a 14534 14535 4102 +a 14535 14536 4102 +a 14536 14537 4102 +a 14537 14538 4102 +a 14538 14539 4102 +a 14539 14540 4102 +a 14540 14541 4102 +a 14541 14542 4102 +a 14542 14543 4102 +a 14543 14544 4102 +a 14544 14545 4102 +a 14545 14546 4102 +a 14546 14547 4102 +a 14547 14548 4102 +a 14548 14549 4102 +a 14549 14550 4102 +a 14550 14551 4102 +a 14551 14552 4102 +a 14552 14553 4102 +a 14553 14554 4102 +a 14554 14555 4102 +a 14555 14556 4102 +a 14556 14557 4102 +a 14557 14558 4102 +a 14558 14559 4102 +a 14559 14560 4102 +a 14560 14561 4102 +a 14561 14562 4102 +a 14562 14563 4102 +a 14563 14564 4102 +a 14564 14565 4102 +a 14565 14566 4102 +a 14566 14567 4102 +a 14567 14568 4102 +a 14568 14569 4102 +a 14569 14570 4102 +a 14570 14571 4102 +a 14571 14572 4102 +a 14572 14573 4102 +a 14573 14574 4102 +a 14574 14575 4102 +a 14575 14576 4102 +a 14576 14577 4102 +a 14577 14578 4102 +a 14578 14579 4102 +a 14579 14580 4102 +a 14580 14581 4102 +a 14581 14582 4102 +a 14582 14583 4102 +a 14583 14584 4102 +a 14584 14585 4102 +a 14585 14586 4102 +a 14586 14587 4102 +a 14587 14588 4102 +a 14588 14589 4102 +a 14589 14590 4102 +a 14590 14591 4102 +a 14591 14592 4102 +a 14592 14593 4102 +a 14593 14594 4102 +a 14594 14595 4102 +a 14595 14596 4102 +a 14596 14597 4102 +a 14597 14598 4102 +a 14598 14599 4102 +a 14599 14600 4102 +a 14600 14601 4102 +a 14601 14602 4102 +a 14602 14603 4102 +a 14603 14604 4102 +a 14604 14605 4102 +a 14605 14606 4102 +a 14606 14607 4102 +a 14607 14608 4102 +a 14608 14609 4102 +a 14609 14610 4102 +a 14610 14611 4102 +a 14611 14612 4102 +a 14612 14613 4102 +a 14613 14614 4102 +a 14614 14615 4102 +a 14615 14616 4102 +a 14616 14617 4102 +a 14617 14618 4102 +a 14618 14619 4102 +a 14619 14620 4102 +a 14620 14621 4102 +a 14621 14622 4102 +a 14622 14623 4102 +a 14623 14624 4102 +a 14624 14625 4102 +a 14625 14626 4102 +a 14626 14627 4102 +a 14627 14628 4102 +a 14628 14629 4102 +a 14629 14630 4102 +a 14630 14631 4102 +a 14631 14632 4102 +a 14632 14633 4102 +a 14633 14634 4102 +a 14634 14635 4102 +a 14635 14636 4102 +a 14636 14637 4102 +a 14637 14638 4102 +a 14638 14639 4102 +a 14639 14640 4102 +a 14640 14641 4102 +a 14641 14642 4102 +a 14642 14643 4102 +a 14643 14644 4102 +a 14644 14645 4102 +a 14645 14646 4102 +a 14646 14647 4102 +a 14647 14648 4102 +a 14648 14649 4102 +a 14649 14650 4102 +a 14650 14651 4102 +a 14651 14652 4102 +a 14652 14653 4102 +a 14653 14654 4102 +a 14654 14655 4102 +a 14655 14656 4102 +a 14656 14657 4102 +a 14657 14658 4102 +a 14658 14659 4102 +a 14659 14660 4102 +a 14660 14661 4102 +a 14661 14662 4102 +a 14662 14663 4102 +a 14663 14664 4102 +a 14664 14665 4102 +a 14665 14666 4102 +a 14666 14667 4102 +a 14667 14668 4102 +a 14668 14669 4102 +a 14669 14670 4102 +a 14670 14671 4102 +a 14671 14672 4102 +a 14672 14673 4102 +a 14673 14674 4102 +a 14674 14675 4102 +a 14675 14676 4102 +a 14676 14677 4102 +a 14677 14678 4102 +a 14678 14679 4102 +a 14679 14680 4102 +a 14680 14681 4102 +a 14681 14682 4102 +a 14682 14683 4102 +a 14683 14684 4102 +a 14684 14685 4102 +a 14685 14686 4102 +a 14686 14687 4102 +a 14687 14688 4102 +a 14688 14689 4102 +a 14689 14690 4102 +a 14690 14691 4102 +a 14691 14692 4102 +a 14692 14693 4102 +a 14693 14694 4102 +a 14694 14695 4102 +a 14695 14696 4102 +a 14696 14697 4102 +a 14697 14698 4102 +a 14698 14699 4102 +a 14699 14700 4102 +a 14700 14701 4102 +a 14701 14702 4102 +a 14702 14703 4102 +a 14703 14704 4102 +a 14704 14705 4102 +a 14705 14706 4102 +a 14706 14707 4102 +a 14707 14708 4102 +a 14708 14709 4102 +a 14709 14710 4102 +a 14710 14711 4102 +a 14711 14712 4102 +a 14712 14713 4102 +a 14713 14714 4102 +a 14714 14715 4102 +a 14715 14716 4102 +a 14716 14717 4102 +a 14717 14718 4102 +a 14718 14719 4102 +a 14719 14720 4102 +a 14720 14721 4102 +a 14721 14722 4102 +a 14722 14723 4102 +a 14723 14724 4102 +a 14724 14725 4102 +a 14725 14726 4102 +a 14726 14727 4102 +a 14727 14728 4102 +a 14728 14729 4102 +a 14729 14730 4102 +a 14730 14731 4102 +a 14731 14732 4102 +a 14732 14733 4102 +a 14733 14734 4102 +a 14734 14735 4102 +a 14735 14736 4102 +a 14736 14737 4102 +a 14737 14738 4102 +a 14738 14739 4102 +a 14739 14740 4102 +a 14740 14741 4102 +a 14741 14742 4102 +a 14742 14743 4102 +a 14743 14744 4102 +a 14744 14745 4102 +a 14745 14746 4102 +a 14746 14747 4102 +a 14747 14748 4102 +a 14748 14749 4102 +a 14749 14750 4102 +a 14750 14751 4102 +a 14751 14752 4102 +a 14752 14753 4102 +a 14753 14754 4102 +a 14754 14755 4102 +a 14755 14756 4102 +a 14756 14757 4102 +a 14757 14758 4102 +a 14758 14759 4102 +a 14759 14760 4102 +a 14760 14761 4102 +a 14761 14762 4102 +a 14762 14763 4102 +a 14763 14764 4102 +a 14764 14765 4102 +a 14765 14766 4102 +a 14766 14767 4102 +a 14767 14768 4102 +a 14768 14769 4102 +a 14769 14770 4102 +a 14770 14771 4102 +a 14771 14772 4102 +a 14772 14773 4102 +a 14773 14774 4102 +a 14774 14775 4102 +a 14775 14776 4102 +a 14776 14777 4102 +a 14777 14778 4102 +a 14778 14779 4102 +a 14779 14780 4102 +a 14780 14781 4102 +a 14781 14782 4102 +a 14782 14783 4102 +a 14783 14784 4102 +a 14784 14785 4102 +a 14785 14786 4102 +a 14786 14787 4102 +a 14787 14788 4102 +a 14788 14789 4102 +a 14789 14790 4102 +a 14790 14791 4102 +a 14791 14792 4102 +a 14792 14793 4102 +a 14793 14794 4102 +a 14794 14795 4102 +a 14795 14796 4102 +a 14796 14797 4102 +a 14797 14798 4102 +a 14798 14799 4102 +a 14799 14800 4102 +a 14800 14801 4102 +a 14801 14802 4102 +a 14802 14803 4102 +a 14803 14804 4102 +a 14804 14805 4102 +a 14805 14806 4102 +a 14806 14807 4102 +a 14807 14808 4102 +a 14808 14809 4102 +a 14809 14810 4102 +a 14810 14811 4102 +a 14811 14812 4102 +a 14812 14813 4102 +a 14813 14814 4102 +a 14814 14815 4102 +a 14815 14816 4102 +a 14816 14817 4102 +a 14817 14818 4102 +a 14818 14819 4102 +a 14819 14820 4102 +a 14820 14821 4102 +a 14821 14822 4102 +a 14822 14823 4102 +a 14823 14824 4102 +a 14824 14825 4102 +a 14825 14826 4102 +a 14826 14827 4102 +a 14827 14828 4102 +a 14828 14829 4102 +a 14829 14830 4102 +a 14830 14831 4102 +a 14831 14832 4102 +a 14832 14833 4102 +a 14833 14834 4102 +a 14834 14835 4102 +a 14835 14836 4102 +a 14836 14837 4102 +a 14837 14838 4102 +a 14838 14839 4102 +a 14839 14840 4102 +a 14840 14841 4102 +a 14841 14842 4102 +a 14842 14843 4102 +a 14843 14844 4102 +a 14844 14845 4102 +a 14845 14846 4102 +a 14846 14847 4102 +a 14847 14848 4102 +a 14848 14849 4102 +a 14849 14850 4102 +a 14850 14851 4102 +a 14851 14852 4102 +a 14852 14853 4102 +a 14853 14854 4102 +a 14854 14855 4102 +a 14855 14856 4102 +a 14856 14857 4102 +a 14857 14858 4102 +a 14858 14859 4102 +a 14859 14860 4102 +a 14860 14861 4102 +a 14861 14862 4102 +a 14862 14863 4102 +a 14863 14864 4102 +a 14864 14865 4102 +a 14865 14866 4102 +a 14866 14867 4102 +a 14867 14868 4102 +a 14868 14869 4102 +a 14869 14870 4102 +a 14870 14871 4102 +a 14871 14872 4102 +a 14872 14873 4102 +a 14873 14874 4102 +a 14874 14875 4102 +a 14875 14876 4102 +a 14876 14877 4102 +a 14877 14878 4102 +a 14878 14879 4102 +a 14879 14880 4102 +a 14880 14881 4102 +a 14881 14882 4102 +a 14882 14883 4102 +a 14883 14884 4102 +a 14884 14885 4102 +a 14885 14886 4102 +a 14886 14887 4102 +a 14887 14888 4102 +a 14888 14889 4102 +a 14889 14890 4102 +a 14890 14891 4102 +a 14891 14892 4102 +a 14892 14893 4102 +a 14893 14894 4102 +a 14894 14895 4102 +a 14895 14896 4102 +a 14896 14897 4102 +a 14897 14898 4102 +a 14898 14899 4102 +a 14899 14900 4102 +a 14900 14901 4102 +a 14901 14902 4102 +a 14902 14903 4102 +a 14903 14904 4102 +a 14904 14905 4102 +a 14905 14906 4102 +a 14906 14907 4102 +a 14907 14908 4102 +a 14908 14909 4102 +a 14909 14910 4102 +a 14910 14911 4102 +a 14911 14912 4102 +a 14912 14913 4102 +a 14913 14914 4102 +a 14914 14915 4102 +a 14915 14916 4102 +a 14916 14917 4102 +a 14917 14918 4102 +a 14918 14919 4102 +a 14919 14920 4102 +a 14920 14921 4102 +a 14921 14922 4102 +a 14922 14923 4102 +a 14923 14924 4102 +a 14924 14925 4102 +a 14925 14926 4102 +a 14926 14927 4102 +a 14927 14928 4102 +a 14928 14929 4102 +a 14929 14930 4102 +a 14930 14931 4102 +a 14931 14932 4102 +a 14932 14933 4102 +a 14933 14934 4102 +a 14934 14935 4102 +a 14935 14936 4102 +a 14936 14937 4102 +a 14937 14938 4102 +a 14938 14939 4102 +a 14939 14940 4102 +a 14940 14941 4102 +a 14941 14942 4102 +a 14942 14943 4102 +a 14943 14944 4102 +a 14944 14945 4102 +a 14945 14946 4102 +a 14946 14947 4102 +a 14947 14948 4102 +a 14948 14949 4102 +a 14949 14950 4102 +a 14950 14951 4102 +a 14951 14952 4102 +a 14952 14953 4102 +a 14953 14954 4102 +a 14954 14955 4102 +a 14955 14956 4102 +a 14956 14957 4102 +a 14957 14958 4102 +a 14958 14959 4102 +a 14959 14960 4102 +a 14960 14961 4102 +a 14961 14962 4102 +a 14962 14963 4102 +a 14963 14964 4102 +a 14964 14965 4102 +a 14965 14966 4102 +a 14966 14967 4102 +a 14967 14968 4102 +a 14968 14969 4102 +a 14969 14970 4102 +a 14970 14971 4102 +a 14971 14972 4102 +a 14972 14973 4102 +a 14973 14974 4102 +a 14974 14975 4102 +a 14975 14976 4102 +a 14976 14977 4102 +a 14977 14978 4102 +a 14978 14979 4102 +a 14979 14980 4102 +a 14980 14981 4102 +a 14981 14982 4102 +a 14982 14983 4102 +a 14983 14984 4102 +a 14984 14985 4102 +a 14985 14986 4102 +a 14986 14987 4102 +a 14987 14988 4102 +a 14988 14989 4102 +a 14989 14990 4102 +a 14990 14991 4102 +a 14991 14992 4102 +a 14992 14993 4102 +a 14993 14994 4102 +a 14994 14995 4102 +a 14995 14996 4102 +a 14996 14997 4102 +a 14997 14998 4102 +a 14998 14999 4102 +a 14999 15000 4102 +a 15000 15001 4102 +a 15001 15002 4102 +a 15002 15003 4102 +a 15003 15004 4102 +a 15004 15005 4102 +a 15005 15006 4102 +a 15006 15007 4102 +a 15007 15008 4102 +a 15008 15009 4102 +a 15009 15010 4102 +a 15010 15011 4102 +a 15011 15012 4102 +a 15012 15013 4102 +a 15013 15014 4102 +a 15014 15015 4102 +a 15015 15016 4102 +a 15016 15017 4102 +a 15017 15018 4102 +a 15018 15019 4102 +a 15019 15020 4102 +a 15020 15021 4102 +a 15021 15022 4102 +a 15022 15023 4102 +a 15023 15024 4102 +a 15024 15025 4102 +a 15025 15026 4102 +a 15026 15027 4102 +a 15027 15028 4102 +a 15028 15029 4102 +a 15029 15030 4102 +a 15030 15031 4102 +a 15031 15032 4102 +a 15032 15033 4102 +a 15033 15034 4102 +a 15034 15035 4102 +a 15035 15036 4102 +a 15036 15037 4102 +a 15037 15038 4102 +a 15038 15039 4102 +a 15039 15040 4102 +a 15040 15041 4102 +a 15041 15042 4102 +a 15042 15043 4102 +a 15043 15044 4102 +a 15044 15045 4102 +a 15045 15046 4102 +a 15046 15047 4102 +a 15047 15048 4102 +a 15048 15049 4102 +a 15049 15050 4102 +a 15050 15051 4102 +a 15051 15052 4102 +a 15052 15053 4102 +a 15053 15054 4102 +a 15054 15055 4102 +a 15055 15056 4102 +a 15056 15057 4102 +a 15057 15058 4102 +a 15058 15059 4102 +a 15059 15060 4102 +a 15060 15061 4102 +a 15061 15062 4102 +a 15062 15063 4102 +a 15063 15064 4102 +a 15064 15065 4102 +a 15065 15066 4102 +a 15066 15067 4102 +a 15067 15068 4102 +a 15068 15069 4102 +a 15069 15070 4102 +a 15070 15071 4102 +a 15071 15072 4102 +a 15072 15073 4102 +a 15073 15074 4102 +a 15074 15075 4102 +a 15075 15076 4102 +a 15076 15077 4102 +a 15077 15078 4102 +a 15078 15079 4102 +a 15079 15080 4102 +a 15080 15081 4102 +a 15081 15082 4102 +a 15082 15083 4102 +a 15083 15084 4102 +a 15084 15085 4102 +a 15085 15086 4102 +a 15086 15087 4102 +a 15087 15088 4102 +a 15088 15089 4102 +a 15089 15090 4102 +a 15090 15091 4102 +a 15091 15092 4102 +a 15092 15093 4102 +a 15093 15094 4102 +a 15094 15095 4102 +a 15095 15096 4102 +a 15096 15097 4102 +a 15097 15098 4102 +a 15098 15099 4102 +a 15099 15100 4102 +a 15100 15101 4102 +a 15101 15102 4102 +a 15102 15103 4102 +a 15103 15104 4102 +a 15104 15105 4102 +a 15105 15106 4102 +a 15106 15107 4102 +a 15107 15108 4102 +a 15108 15109 4102 +a 15109 15110 4102 +a 15110 15111 4102 +a 15111 15112 4102 +a 15112 15113 4102 +a 15113 15114 4102 +a 15114 15115 4102 +a 15115 15116 4102 +a 15116 15117 4102 +a 15117 15118 4102 +a 15118 15119 4102 +a 15119 15120 4102 +a 15120 15121 4102 +a 15121 15122 4102 +a 15122 15123 4102 +a 15123 15124 4102 +a 15124 15125 4102 +a 15125 15126 4102 +a 15126 15127 4102 +a 15127 15128 4102 +a 15128 15129 4102 +a 15129 15130 4102 +a 15130 15131 4102 +a 15131 15132 4102 +a 15132 15133 4102 +a 15133 15134 4102 +a 15134 15135 4102 +a 15135 15136 4102 +a 15136 15137 4102 +a 15137 15138 4102 +a 15138 15139 4102 +a 15139 15140 4102 +a 15140 15141 4102 +a 15141 15142 4102 +a 15142 15143 4102 +a 15143 15144 4102 +a 15144 15145 4102 +a 15145 15146 4102 +a 15146 15147 4102 +a 15147 15148 4102 +a 15148 15149 4102 +a 15149 15150 4102 +a 15150 15151 4102 +a 15151 15152 4102 +a 15152 15153 4102 +a 15153 15154 4102 +a 15154 15155 4102 +a 15155 15156 4102 +a 15156 15157 4102 +a 15157 15158 4102 +a 15158 15159 4102 +a 15159 15160 4102 +a 15160 15161 4102 +a 15161 15162 4102 +a 15162 15163 4102 +a 15163 15164 4102 +a 15164 15165 4102 +a 15165 15166 4102 +a 15166 15167 4102 +a 15167 15168 4102 +a 15168 15169 4102 +a 15169 15170 4102 +a 15170 15171 4102 +a 15171 15172 4102 +a 15172 15173 4102 +a 15173 15174 4102 +a 15174 15175 4102 +a 15175 15176 4102 +a 15176 15177 4102 +a 15177 15178 4102 +a 15178 15179 4102 +a 15179 15180 4102 +a 15180 15181 4102 +a 15181 15182 4102 +a 15182 15183 4102 +a 15183 15184 4102 +a 15184 15185 4102 +a 15185 15186 4102 +a 15186 15187 4102 +a 15187 15188 4102 +a 15188 15189 4102 +a 15189 15190 4102 +a 15190 15191 4102 +a 15191 15192 4102 +a 15192 15193 4102 +a 15193 15194 4102 +a 15194 15195 4102 +a 15195 15196 4102 +a 15196 15197 4102 +a 15197 15198 4102 +a 15198 15199 4102 +a 15199 15200 4102 +a 15200 15201 4102 +a 15201 15202 4102 +a 15202 15203 4102 +a 15203 15204 4102 +a 15204 15205 4102 +a 15205 15206 4102 +a 15206 15207 4102 +a 15207 15208 4102 +a 15208 15209 4102 +a 15209 15210 4102 +a 15210 15211 4102 +a 15211 15212 4102 +a 15212 15213 4102 +a 15213 15214 4102 +a 15214 15215 4102 +a 15215 15216 4102 +a 15216 15217 4102 +a 15217 15218 4102 +a 15218 15219 4102 +a 15219 15220 4102 +a 15220 15221 4102 +a 15221 15222 4102 +a 15222 15223 4102 +a 15223 15224 4102 +a 15224 15225 4102 +a 15225 15226 4102 +a 15226 15227 4102 +a 15227 15228 4102 +a 15228 15229 4102 +a 15229 15230 4102 +a 15230 15231 4102 +a 15231 15232 4102 +a 15232 15233 4102 +a 15233 15234 4102 +a 15234 15235 4102 +a 15235 15236 4102 +a 15236 15237 4102 +a 15237 15238 4102 +a 15238 15239 4102 +a 15239 15240 4102 +a 15240 15241 4102 +a 15241 15242 4102 +a 15242 15243 4102 +a 15243 15244 4102 +a 15244 15245 4102 +a 15245 15246 4102 +a 15246 15247 4102 +a 15247 15248 4102 +a 15248 15249 4102 +a 15249 15250 4102 +a 15250 15251 4102 +a 15251 15252 4102 +a 15252 15253 4102 +a 15253 15254 4102 +a 15254 15255 4102 +a 15255 15256 4102 +a 15256 15257 4102 +a 15257 15258 4102 +a 15258 15259 4102 +a 15259 15260 4102 +a 15260 15261 4102 +a 15261 15262 4102 +a 15262 15263 4102 +a 15263 15264 4102 +a 15264 15265 4102 +a 15265 15266 4102 +a 15266 15267 4102 +a 15267 15268 4102 +a 15268 15269 4102 +a 15269 15270 4102 +a 15270 15271 4102 +a 15271 15272 4102 +a 15272 15273 4102 +a 15273 15274 4102 +a 15274 15275 4102 +a 15275 15276 4102 +a 15276 15277 4102 +a 15277 15278 4102 +a 15278 15279 4102 +a 15279 15280 4102 +a 15280 15281 4102 +a 15281 15282 4102 +a 15282 15283 4102 +a 15283 15284 4102 +a 15284 15285 4102 +a 15285 15286 4102 +a 15286 15287 4102 +a 15287 15288 4102 +a 15288 15289 4102 +a 15289 15290 4102 +a 15290 15291 4102 +a 15291 15292 4102 +a 15292 15293 4102 +a 15293 15294 4102 +a 15294 15295 4102 +a 15295 15296 4102 +a 15296 15297 4102 +a 15297 15298 4102 +a 15298 15299 4102 +a 15299 15300 4102 +a 15300 15301 4102 +a 15301 15302 4102 +a 15302 15303 4102 +a 15303 15304 4102 +a 15304 15305 4102 +a 15305 15306 4102 +a 15306 15307 4102 +a 15307 15308 4102 +a 15308 15309 4102 +a 15309 15310 4102 +a 15310 15311 4102 +a 15311 15312 4102 +a 15312 15313 4102 +a 15313 15314 4102 +a 15314 15315 4102 +a 15315 15316 4102 +a 15316 15317 4102 +a 15317 15318 4102 +a 15318 15319 4102 +a 15319 15320 4102 +a 15320 15321 4102 +a 15321 15322 4102 +a 15322 15323 4102 +a 15323 15324 4102 +a 15324 15325 4102 +a 15325 15326 4102 +a 15326 15327 4102 +a 15327 15328 4102 +a 15328 15329 4102 +a 15329 15330 4102 +a 15330 15331 4102 +a 15331 15332 4102 +a 15332 15333 4102 +a 15333 15334 4102 +a 15334 15335 4102 +a 15335 15336 4102 +a 15336 15337 4102 +a 15337 15338 4102 +a 15338 15339 4102 +a 15339 15340 4102 +a 15340 15341 4102 +a 15341 15342 4102 +a 15342 15343 4102 +a 15343 15344 4102 +a 15344 15345 4102 +a 15345 15346 4102 +a 15346 15347 4102 +a 15347 15348 4102 +a 15348 15349 4102 +a 15349 15350 4102 +a 15350 15351 4102 +a 15351 15352 4102 +a 15352 15353 4102 +a 15353 15354 4102 +a 15354 15355 4102 +a 15355 15356 4102 +a 15356 15357 4102 +a 15357 15358 4102 +a 15358 15359 4102 +a 15359 15360 4102 +a 15360 15361 4102 +a 15361 15362 4102 +a 15362 15363 4102 +a 15363 15364 4102 +a 15364 15365 4102 +a 15365 15366 4102 +a 15366 15367 4102 +a 15367 15368 4102 +a 15368 15369 4102 +a 15369 15370 4102 +a 15370 15371 4102 +a 15371 15372 4102 +a 15372 15373 4102 +a 15373 15374 4102 +a 15374 15375 4102 +a 15375 15376 4102 +a 15376 15377 4102 +a 15377 15378 4102 +a 15378 15379 4102 +a 15379 15380 4102 +a 15380 15381 4102 +a 15381 15382 4102 +a 15382 15383 4102 +a 15383 15384 4102 +a 15384 15385 4102 +a 15385 15386 4102 +a 15386 15387 4102 +a 15387 15388 4102 +a 15388 15389 4102 +a 15389 15390 4102 +a 15390 15391 4102 +a 15391 15392 4102 +a 15392 15393 4102 +a 15393 15394 4102 +a 15394 15395 4102 +a 15395 15396 4102 +a 15396 15397 4102 +a 15397 15398 4102 +a 15398 15399 4102 +a 15399 15400 4102 +a 15400 15401 4102 +a 15401 15402 4102 +a 15402 15403 4102 +a 15403 15404 4102 +a 15404 15405 4102 +a 15405 15406 4102 +a 15406 15407 4102 +a 15407 15408 4102 +a 15408 15409 4102 +a 15409 15410 4102 +a 15410 15411 4102 +a 15411 15412 4102 +a 15412 15413 4102 +a 15413 15414 4102 +a 15414 15415 4102 +a 15415 15416 4102 +a 15416 15417 4102 +a 15417 15418 4102 +a 15418 15419 4102 +a 15419 15420 4102 +a 15420 15421 4102 +a 15421 15422 4102 +a 15422 15423 4102 +a 15423 15424 4102 +a 15424 15425 4102 +a 15425 15426 4102 +a 15426 15427 4102 +a 15427 15428 4102 +a 15428 15429 4102 +a 15429 15430 4102 +a 15430 15431 4102 +a 15431 15432 4102 +a 15432 15433 4102 +a 15433 15434 4102 +a 15434 15435 4102 +a 15435 15436 4102 +a 15436 15437 4102 +a 15437 15438 4102 +a 15438 15439 4102 +a 15439 15440 4102 +a 15440 15441 4102 +a 15441 15442 4102 +a 15442 15443 4102 +a 15443 15444 4102 +a 15444 15445 4102 +a 15445 15446 4102 +a 15446 15447 4102 +a 15447 15448 4102 +a 15448 15449 4102 +a 15449 15450 4102 +a 15450 15451 4102 +a 15451 15452 4102 +a 15452 15453 4102 +a 15453 15454 4102 +a 15454 15455 4102 +a 15455 15456 4102 +a 15456 15457 4102 +a 15457 15458 4102 +a 15458 15459 4102 +a 15459 15460 4102 +a 15460 15461 4102 +a 15461 15462 4102 +a 15462 15463 4102 +a 15463 15464 4102 +a 15464 15465 4102 +a 15465 15466 4102 +a 15466 15467 4102 +a 15467 15468 4102 +a 15468 15469 4102 +a 15469 15470 4102 +a 15470 15471 4102 +a 15471 15472 4102 +a 15472 15473 4102 +a 15473 15474 4102 +a 15474 15475 4102 +a 15475 15476 4102 +a 15476 15477 4102 +a 15477 15478 4102 +a 15478 15479 4102 +a 15479 15480 4102 +a 15480 15481 4102 +a 15481 15482 4102 +a 15482 15483 4102 +a 15483 15484 4102 +a 15484 15485 4102 +a 15485 15486 4102 +a 15486 15487 4102 +a 15487 15488 4102 +a 15488 15489 4102 +a 15489 15490 4102 +a 15490 15491 4102 +a 15491 15492 4102 +a 15492 15493 4102 +a 15493 15494 4102 +a 15494 15495 4102 +a 15495 15496 4102 +a 15496 15497 4102 +a 15497 15498 4102 +a 15498 15499 4102 +a 15499 15500 4102 +a 15500 15501 4102 +a 15501 15502 4102 +a 15502 15503 4102 +a 15503 15504 4102 +a 15504 15505 4102 +a 15505 15506 4102 +a 15506 15507 4102 +a 15507 15508 4102 +a 15508 15509 4102 +a 15509 15510 4102 +a 15510 15511 4102 +a 15511 15512 4102 +a 15512 15513 4102 +a 15513 15514 4102 +a 15514 15515 4102 +a 15515 15516 4102 +a 15516 15517 4102 +a 15517 15518 4102 +a 15518 15519 4102 +a 15519 15520 4102 +a 15520 15521 4102 +a 15521 15522 4102 +a 15522 15523 4102 +a 15523 15524 4102 +a 15524 15525 4102 +a 15525 15526 4102 +a 15526 15527 4102 +a 15527 15528 4102 +a 15528 15529 4102 +a 15529 15530 4102 +a 15530 15531 4102 +a 15531 15532 4102 +a 15532 15533 4102 +a 15533 15534 4102 +a 15534 15535 4102 +a 15535 15536 4102 +a 15536 15537 4102 +a 15537 15538 4102 +a 15538 15539 4102 +a 15539 15540 4102 +a 15540 15541 4102 +a 15541 15542 4102 +a 15542 15543 4102 +a 15543 15544 4102 +a 15544 15545 4102 +a 15545 15546 4102 +a 15546 15547 4102 +a 15547 15548 4102 +a 15548 15549 4102 +a 15549 15550 4102 +a 15550 15551 4102 +a 15551 15552 4102 +a 15552 15553 4102 +a 15553 15554 4102 +a 15554 15555 4102 +a 15555 15556 4102 +a 15556 15557 4102 +a 15557 15558 4102 +a 15558 15559 4102 +a 15559 15560 4102 +a 15560 15561 4102 +a 15561 15562 4102 +a 15562 15563 4102 +a 15563 15564 4102 +a 15564 15565 4102 +a 15565 15566 4102 +a 15566 15567 4102 +a 15567 15568 4102 +a 15568 15569 4102 +a 15569 15570 4102 +a 15570 15571 4102 +a 15571 15572 4102 +a 15572 15573 4102 +a 15573 15574 4102 +a 15574 15575 4102 +a 15575 15576 4102 +a 15576 15577 4102 +a 15577 15578 4102 +a 15578 15579 4102 +a 15579 15580 4102 +a 15580 15581 4102 +a 15581 15582 4102 +a 15582 15583 4102 +a 15583 15584 4102 +a 15584 15585 4102 +a 15585 15586 4102 +a 15586 15587 4102 +a 15587 15588 4102 +a 15588 15589 4102 +a 15589 15590 4102 +a 15590 15591 4102 +a 15591 15592 4102 +a 15592 15593 4102 +a 15593 15594 4102 +a 15594 15595 4102 +a 15595 15596 4102 +a 15596 15597 4102 +a 15597 15598 4102 +a 15598 15599 4102 +a 15599 15600 4102 +a 15600 15601 4102 +a 15601 15602 4102 +a 15602 15603 4102 +a 15603 15604 4102 +a 15604 15605 4102 +a 15605 15606 4102 +a 15606 15607 4102 +a 15607 15608 4102 +a 15608 15609 4102 +a 15609 15610 4102 +a 15610 15611 4102 +a 15611 15612 4102 +a 15612 15613 4102 +a 15613 15614 4102 +a 15614 15615 4102 +a 15615 15616 4102 +a 15616 15617 4102 +a 15617 15618 4102 +a 15618 15619 4102 +a 15619 15620 4102 +a 15620 15621 4102 +a 15621 15622 4102 +a 15622 15623 4102 +a 15623 15624 4102 +a 15624 15625 4102 +a 15625 15626 4102 +a 15626 15627 4102 +a 15627 15628 4102 +a 15628 15629 4102 +a 15629 15630 4102 +a 15630 15631 4102 +a 15631 15632 4102 +a 15632 15633 4102 +a 15633 15634 4102 +a 15634 15635 4102 +a 15635 15636 4102 +a 15636 15637 4102 +a 15637 15638 4102 +a 15638 15639 4102 +a 15639 15640 4102 +a 15640 15641 4102 +a 15641 15642 4102 +a 15642 15643 4102 +a 15643 15644 4102 +a 15644 15645 4102 +a 15645 15646 4102 +a 15646 15647 4102 +a 15647 15648 4102 +a 15648 15649 4102 +a 15649 15650 4102 +a 15650 15651 4102 +a 15651 15652 4102 +a 15652 15653 4102 +a 15653 15654 4102 +a 15654 15655 4102 +a 15655 15656 4102 +a 15656 15657 4102 +a 15657 15658 4102 +a 15658 15659 4102 +a 15659 15660 4102 +a 15660 15661 4102 +a 15661 15662 4102 +a 15662 15663 4102 +a 15663 15664 4102 +a 15664 15665 4102 +a 15665 15666 4102 +a 15666 15667 4102 +a 15667 15668 4102 +a 15668 15669 4102 +a 15669 15670 4102 +a 15670 15671 4102 +a 15671 15672 4102 +a 15672 15673 4102 +a 15673 15674 4102 +a 15674 15675 4102 +a 15675 15676 4102 +a 15676 15677 4102 +a 15677 15678 4102 +a 15678 15679 4102 +a 15679 15680 4102 +a 15680 15681 4102 +a 15681 15682 4102 +a 15682 15683 4102 +a 15683 15684 4102 +a 15684 15685 4102 +a 15685 15686 4102 +a 15686 15687 4102 +a 15687 15688 4102 +a 15688 15689 4102 +a 15689 15690 4102 +a 15690 15691 4102 +a 15691 15692 4102 +a 15692 15693 4102 +a 15693 15694 4102 +a 15694 15695 4102 +a 15695 15696 4102 +a 15696 15697 4102 +a 15697 15698 4102 +a 15698 15699 4102 +a 15699 15700 4102 +a 15700 15701 4102 +a 15701 15702 4102 +a 15702 15703 4102 +a 15703 15704 4102 +a 15704 15705 4102 +a 15705 15706 4102 +a 15706 15707 4102 +a 15707 15708 4102 +a 15708 15709 4102 +a 15709 15710 4102 +a 15710 15711 4102 +a 15711 15712 4102 +a 15712 15713 4102 +a 15713 15714 4102 +a 15714 15715 4102 +a 15715 15716 4102 +a 15716 15717 4102 +a 15717 15718 4102 +a 15718 15719 4102 +a 15719 15720 4102 +a 15720 15721 4102 +a 15721 15722 4102 +a 15722 15723 4102 +a 15723 15724 4102 +a 15724 15725 4102 +a 15725 15726 4102 +a 15726 15727 4102 +a 15727 15728 4102 +a 15728 15729 4102 +a 15729 15730 4102 +a 15730 15731 4102 +a 15731 15732 4102 +a 15732 15733 4102 +a 15733 15734 4102 +a 15734 15735 4102 +a 15735 15736 4102 +a 15736 15737 4102 +a 15737 15738 4102 +a 15738 15739 4102 +a 15739 15740 4102 +a 15740 15741 4102 +a 15741 15742 4102 +a 15742 15743 4102 +a 15743 15744 4102 +a 15744 15745 4102 +a 15745 15746 4102 +a 15746 15747 4102 +a 15747 15748 4102 +a 15748 15749 4102 +a 15749 15750 4102 +a 15750 15751 4102 +a 15751 15752 4102 +a 15752 15753 4102 +a 15753 15754 4102 +a 15754 15755 4102 +a 15755 15756 4102 +a 15756 15757 4102 +a 15757 15758 4102 +a 15758 15759 4102 +a 15759 15760 4102 +a 15760 15761 4102 +a 15761 15762 4102 +a 15762 15763 4102 +a 15763 15764 4102 +a 15764 15765 4102 +a 15765 15766 4102 +a 15766 15767 4102 +a 15767 15768 4102 +a 15768 15769 4102 +a 15769 15770 4102 +a 15770 15771 4102 +a 15771 15772 4102 +a 15772 15773 4102 +a 15773 15774 4102 +a 15774 15775 4102 +a 15775 15776 4102 +a 15776 15777 4102 +a 15777 15778 4102 +a 15778 15779 4102 +a 15779 15780 4102 +a 15780 15781 4102 +a 15781 15782 4102 +a 15782 15783 4102 +a 15783 15784 4102 +a 15784 15785 4102 +a 15785 15786 4102 +a 15786 15787 4102 +a 15787 15788 4102 +a 15788 15789 4102 +a 15789 15790 4102 +a 15790 15791 4102 +a 15791 15792 4102 +a 15792 15793 4102 +a 15793 15794 4102 +a 15794 15795 4102 +a 15795 15796 4102 +a 15796 15797 4102 +a 15797 15798 4102 +a 15798 15799 4102 +a 15799 15800 4102 +a 15800 15801 4102 +a 15801 15802 4102 +a 15802 15803 4102 +a 15803 15804 4102 +a 15804 15805 4102 +a 15805 15806 4102 +a 15806 15807 4102 +a 15807 15808 4102 +a 15808 15809 4102 +a 15809 15810 4102 +a 15810 15811 4102 +a 15811 15812 4102 +a 15812 15813 4102 +a 15813 15814 4102 +a 15814 15815 4102 +a 15815 15816 4102 +a 15816 15817 4102 +a 15817 15818 4102 +a 15818 15819 4102 +a 15819 15820 4102 +a 15820 15821 4102 +a 15821 15822 4102 +a 15822 15823 4102 +a 15823 15824 4102 +a 15824 15825 4102 +a 15825 15826 4102 +a 15826 15827 4102 +a 15827 15828 4102 +a 15828 15829 4102 +a 15829 15830 4102 +a 15830 15831 4102 +a 15831 15832 4102 +a 15832 15833 4102 +a 15833 15834 4102 +a 15834 15835 4102 +a 15835 15836 4102 +a 15836 15837 4102 +a 15837 15838 4102 +a 15838 15839 4102 +a 15839 15840 4102 +a 15840 15841 4102 +a 15841 15842 4102 +a 15842 15843 4102 +a 15843 15844 4102 +a 15844 15845 4102 +a 15845 15846 4102 +a 15846 15847 4102 +a 15847 15848 4102 +a 15848 15849 4102 +a 15849 15850 4102 +a 15850 15851 4102 +a 15851 15852 4102 +a 15852 15853 4102 +a 15853 15854 4102 +a 15854 15855 4102 +a 15855 15856 4102 +a 15856 15857 4102 +a 15857 15858 4102 +a 15858 15859 4102 +a 15859 15860 4102 +a 15860 15861 4102 +a 15861 15862 4102 +a 15862 15863 4102 +a 15863 15864 4102 +a 15864 15865 4102 +a 15865 15866 4102 +a 15866 15867 4102 +a 15867 15868 4102 +a 15868 15869 4102 +a 15869 15870 4102 +a 15870 15871 4102 +a 15871 15872 4102 +a 15872 15873 4102 +a 15873 15874 4102 +a 15874 15875 4102 +a 15875 15876 4102 +a 15876 15877 4102 +a 15877 15878 4102 +a 15878 15879 4102 +a 15879 15880 4102 +a 15880 15881 4102 +a 15881 15882 4102 +a 15882 15883 4102 +a 15883 15884 4102 +a 15884 15885 4102 +a 15885 15886 4102 +a 15886 15887 4102 +a 15887 15888 4102 +a 15888 15889 4102 +a 15889 15890 4102 +a 15890 15891 4102 +a 15891 15892 4102 +a 15892 15893 4102 +a 15893 15894 4102 +a 15894 15895 4102 +a 15895 15896 4102 +a 15896 15897 4102 +a 15897 15898 4102 +a 15898 15899 4102 +a 15899 15900 4102 +a 15900 15901 4102 +a 15901 15902 4102 +a 15902 15903 4102 +a 15903 15904 4102 +a 15904 15905 4102 +a 15905 15906 4102 +a 15906 15907 4102 +a 15907 15908 4102 +a 15908 15909 4102 +a 15909 15910 4102 +a 15910 15911 4102 +a 15911 15912 4102 +a 15912 15913 4102 +a 15913 15914 4102 +a 15914 15915 4102 +a 15915 15916 4102 +a 15916 15917 4102 +a 15917 15918 4102 +a 15918 15919 4102 +a 15919 15920 4102 +a 15920 15921 4102 +a 15921 15922 4102 +a 15922 15923 4102 +a 15923 15924 4102 +a 15924 15925 4102 +a 15925 15926 4102 +a 15926 15927 4102 +a 15927 15928 4102 +a 15928 15929 4102 +a 15929 15930 4102 +a 15930 15931 4102 +a 15931 15932 4102 +a 15932 15933 4102 +a 15933 15934 4102 +a 15934 15935 4102 +a 15935 15936 4102 +a 15936 15937 4102 +a 15937 15938 4102 +a 15938 15939 4102 +a 15939 15940 4102 +a 15940 15941 4102 +a 15941 15942 4102 +a 15942 15943 4102 +a 15943 15944 4102 +a 15944 15945 4102 +a 15945 15946 4102 +a 15946 15947 4102 +a 15947 15948 4102 +a 15948 15949 4102 +a 15949 15950 4102 +a 15950 15951 4102 +a 15951 15952 4102 +a 15952 15953 4102 +a 15953 15954 4102 +a 15954 15955 4102 +a 15955 15956 4102 +a 15956 15957 4102 +a 15957 15958 4102 +a 15958 15959 4102 +a 15959 15960 4102 +a 15960 15961 4102 +a 15961 15962 4102 +a 15962 15963 4102 +a 15963 15964 4102 +a 15964 15965 4102 +a 15965 15966 4102 +a 15966 15967 4102 +a 15967 15968 4102 +a 15968 15969 4102 +a 15969 15970 4102 +a 15970 15971 4102 +a 15971 15972 4102 +a 15972 15973 4102 +a 15973 15974 4102 +a 15974 15975 4102 +a 15975 15976 4102 +a 15976 15977 4102 +a 15977 15978 4102 +a 15978 15979 4102 +a 15979 15980 4102 +a 15980 15981 4102 +a 15981 15982 4102 +a 15982 15983 4102 +a 15983 15984 4102 +a 15984 15985 4102 +a 15985 15986 4102 +a 15986 15987 4102 +a 15987 15988 4102 +a 15988 15989 4102 +a 15989 15990 4102 +a 15990 15991 4102 +a 15991 15992 4102 +a 15992 15993 4102 +a 15993 15994 4102 +a 15994 15995 4102 +a 15995 15996 4102 +a 15996 15997 4102 +a 15997 15998 4102 +a 15998 15999 4102 +a 15999 16000 4102 +a 16000 16001 4102 +a 16001 16002 4102 +a 16002 16003 4102 +a 16003 16004 4102 +a 16004 16005 4102 +a 16005 16006 4102 +a 16006 16007 4102 +a 16007 16008 4102 +a 16008 16009 4102 +a 16009 16010 4102 +a 16010 16011 4102 +a 16011 16012 4102 +a 16012 16013 4102 +a 16013 16014 4102 +a 16014 16015 4102 +a 16015 16016 4102 +a 16016 16017 4102 +a 16017 16018 4102 +a 16018 16019 4102 +a 16019 16020 4102 +a 16020 16021 4102 +a 16021 16022 4102 +a 16022 16023 4102 +a 16023 16024 4102 +a 16024 16025 4102 +a 16025 16026 4102 +a 16026 16027 4102 +a 16027 16028 4102 +a 16028 16029 4102 +a 16029 16030 4102 +a 16030 16031 4102 +a 16031 16032 4102 +a 16032 16033 4102 +a 16033 16034 4102 +a 16034 16035 4102 +a 16035 16036 4102 +a 16036 16037 4102 +a 16037 16038 4102 +a 16038 16039 4102 +a 16039 16040 4102 +a 16040 16041 4102 +a 16041 16042 4102 +a 16042 16043 4102 +a 16043 16044 4102 +a 16044 16045 4102 +a 16045 16046 4102 +a 16046 16047 4102 +a 16047 16048 4102 +a 16048 16049 4102 +a 16049 16050 4102 +a 16050 16051 4102 +a 16051 16052 4102 +a 16052 16053 4102 +a 16053 16054 4102 +a 16054 16055 4102 +a 16055 16056 4102 +a 16056 16057 4102 +a 16057 16058 4102 +a 16058 16059 4102 +a 16059 16060 4102 +a 16060 16061 4102 +a 16061 16062 4102 +a 16062 16063 4102 +a 16063 16064 4102 +a 16064 16065 4102 +a 16065 16066 4102 +a 16066 16067 4102 +a 16067 16068 4102 +a 16068 16069 4102 +a 16069 16070 4102 +a 16070 16071 4102 +a 16071 16072 4102 +a 16072 16073 4102 +a 16073 16074 4102 +a 16074 16075 4102 +a 16075 16076 4102 +a 16076 16077 4102 +a 16077 16078 4102 +a 16078 16079 4102 +a 16079 16080 4102 +a 16080 16081 4102 +a 16081 16082 4102 +a 16082 16083 4102 +a 16083 16084 4102 +a 16084 16085 4102 +a 16085 16086 4102 +a 16086 16087 4102 +a 16087 16088 4102 +a 16088 16089 4102 +a 16089 16090 4102 +a 16090 16091 4102 +a 16091 16092 4102 +a 16092 16093 4102 +a 16093 16094 4102 +a 16094 16095 4102 +a 16095 16096 4102 +a 16096 16097 4102 +a 16097 16098 4102 +a 16098 16099 4102 +a 16099 16100 4102 +a 16100 16101 4102 +a 16101 16102 4102 +a 16102 16103 4102 +a 16103 16104 4102 +a 16104 16105 4102 +a 16105 16106 4102 +a 16106 16107 4102 +a 16107 16108 4102 +a 16108 16109 4102 +a 16109 16110 4102 +a 16110 16111 4102 +a 16111 16112 4102 +a 16112 16113 4102 +a 16113 16114 4102 +a 16114 16115 4102 +a 16115 16116 4102 +a 16116 16117 4102 +a 16117 16118 4102 +a 16118 16119 4102 +a 16119 16120 4102 +a 16120 16121 4102 +a 16121 16122 4102 +a 16122 16123 4102 +a 16123 16124 4102 +a 16124 16125 4102 +a 16125 16126 4102 +a 16126 16127 4102 +a 16127 16128 4102 +a 16128 16129 4102 +a 16129 16130 4102 +a 16130 16131 4102 +a 16131 16132 4102 +a 16132 16133 4102 +a 16133 16134 4102 +a 16134 16135 4102 +a 16135 16136 4102 +a 16136 16137 4102 +a 16137 16138 4102 +a 16138 16139 4102 +a 16139 16140 4102 +a 16140 16141 4102 +a 16141 16142 4102 +a 16142 16143 4102 +a 16143 16144 4102 +a 16144 16145 4102 +a 16145 16146 4102 +a 16146 16147 4102 +a 16147 16148 4102 +a 16148 16149 4102 +a 16149 16150 4102 +a 16150 16151 4102 +a 16151 16152 4102 +a 16152 16153 4102 +a 16153 16154 4102 +a 16154 16155 4102 +a 16155 16156 4102 +a 16156 16157 4102 +a 16157 16158 4102 +a 16158 16159 4102 +a 16159 16160 4102 +a 16160 16161 4102 +a 16161 16162 4102 +a 16162 16163 4102 +a 16163 16164 4102 +a 16164 16165 4102 +a 16165 16166 4102 +a 16166 16167 4102 +a 16167 16168 4102 +a 16168 16169 4102 +a 16169 16170 4102 +a 16170 16171 4102 +a 16171 16172 4102 +a 16172 16173 4102 +a 16173 16174 4102 +a 16174 16175 4102 +a 16175 16176 4102 +a 16176 16177 4102 +a 16177 16178 4102 +a 16178 16179 4102 +a 16179 16180 4102 +a 16180 16181 4102 +a 16181 16182 4102 +a 16182 16183 4102 +a 16183 16184 4102 +a 16184 16185 4102 +a 16185 16186 4102 +a 16186 16187 4102 +a 16187 16188 4102 +a 16188 16189 4102 +a 16189 16190 4102 +a 16190 16191 4102 +a 16191 16192 4102 +a 16192 16193 4102 +a 16193 16194 4102 +a 16194 16195 4102 +a 16195 16196 4102 +a 16196 16197 4102 +a 16197 16198 4102 +a 16198 16199 4102 +a 16199 16200 4102 +a 16200 16201 4102 +a 16201 16202 4102 +a 16202 16203 4102 +a 16203 16204 4102 +a 16204 16205 4102 +a 16205 16206 4102 +a 16206 16207 4102 +a 16207 16208 4102 +a 16208 16209 4102 +a 16209 16210 4102 +a 16210 16211 4102 +a 16211 16212 4102 +a 16212 16213 4102 +a 16213 16214 4102 +a 16214 16215 4102 +a 16215 16216 4102 +a 16216 16217 4102 +a 16217 16218 4102 +a 16218 16219 4102 +a 16219 16220 4102 +a 16220 16221 4102 +a 16221 16222 4102 +a 16222 16223 4102 +a 16223 16224 4102 +a 16224 16225 4102 +a 16225 16226 4102 +a 16226 16227 4102 +a 16227 16228 4102 +a 16228 16229 4102 +a 16229 16230 4102 +a 16230 16231 4102 +a 16231 16232 4102 +a 16232 16233 4102 +a 16233 16234 4102 +a 16234 16235 4102 +a 16235 16236 4102 +a 16236 16237 4102 +a 16237 16238 4102 +a 16238 16239 4102 +a 16239 16240 4102 +a 16240 16241 4102 +a 16241 16242 4102 +a 16242 16243 4102 +a 16243 16244 4102 +a 16244 16245 4102 +a 16245 16246 4102 +a 16246 16247 4102 +a 16247 16248 4102 +a 16248 16249 4102 +a 16249 16250 4102 +a 16250 16251 4102 +a 16251 16252 4102 +a 16252 16253 4102 +a 16253 16254 4102 +a 16254 16255 4102 +a 16255 16256 4102 +a 16256 16257 4102 +a 16257 16258 4102 +a 16258 16259 4102 +a 16259 16260 4102 +a 16260 16261 4102 +a 16261 16262 4102 +a 16262 16263 4102 +a 16263 16264 4102 +a 16264 16265 4102 +a 16265 16266 4102 +a 16266 16267 4102 +a 16267 16268 4102 +a 16268 16269 4102 +a 16269 16270 4102 +a 16270 16271 4102 +a 16271 16272 4102 +a 16272 16273 4102 +a 16273 16274 4102 +a 16274 16275 4102 +a 16275 16276 4102 +a 16276 16277 4102 +a 16277 16278 4102 +a 16278 16279 4102 +a 16279 16280 4102 +a 16280 16281 4102 +a 16281 16282 4102 +a 16282 16283 4102 +a 16283 16284 4102 +a 16284 16285 4102 +a 16285 16286 4102 +a 16286 16287 4102 +a 16287 16288 4102 +a 16288 16289 4102 +a 16289 16290 4102 +a 16290 16291 4102 +a 16291 16292 4102 +a 16292 16293 4102 +a 16293 16294 4102 +a 16294 16295 4102 +a 16295 16296 4102 +a 16296 16297 4102 +a 16297 16298 4102 +a 16298 16299 4102 +a 16299 16300 4102 +a 16300 16301 4102 +a 16301 16302 4102 +a 16302 16303 4102 +a 16303 16304 4102 +a 16304 16305 4102 +a 16305 16306 4102 +a 16306 16307 4102 +a 16307 16308 4102 +a 16308 16309 4102 +a 16309 16310 4102 +a 16310 16311 4102 +a 16311 16312 4102 +a 16312 16313 4102 +a 16313 16314 4102 +a 16314 16315 4102 +a 16315 16316 4102 +a 16316 16317 4102 +a 16317 16318 4102 +a 16318 16319 4102 +a 16319 16320 4102 +a 16320 16321 4102 +a 16321 16322 4102 +a 16322 16323 4102 +a 16323 16324 4102 +a 16324 16325 4102 +a 16325 16326 4102 +a 16326 16327 4102 +a 16327 16328 4102 +a 16328 16329 4102 +a 16329 16330 4102 +a 16330 16331 4102 +a 16331 16332 4102 +a 16332 16333 4102 +a 16333 16334 4102 +a 16334 16335 4102 +a 16335 16336 4102 +a 16336 16337 4102 +a 16337 16338 4102 +a 16338 16339 4102 +a 16339 16340 4102 +a 16340 16341 4102 +a 16341 16342 4102 +a 16342 16343 4102 +a 16343 16344 4102 +a 16344 16345 4102 +a 16345 16346 4102 +a 16346 16347 4102 +a 16347 16348 4102 +a 16348 16349 4102 +a 16349 16350 4102 +a 16350 16351 4102 +a 16351 16352 4102 +a 16352 16353 4102 +a 16353 16354 4102 +a 16354 16355 4102 +a 16355 16356 4102 +a 16356 16357 4102 +a 16357 16358 4102 +a 16358 16359 4102 +a 16359 16360 4102 +a 16360 16361 4102 +a 16361 16362 4102 +a 16362 16363 4102 +a 16363 16364 4102 +a 16364 16365 4102 +a 16365 16366 4102 +a 16366 16367 4102 +a 16367 16368 4102 +a 16368 16369 4102 +a 16369 16370 4102 +a 16370 16371 4102 +a 16371 16372 4102 +a 16372 16373 4102 +a 16373 16374 4102 +a 16374 16375 4102 +a 16375 16376 4102 +a 16376 16377 4102 +a 16377 16378 4102 +a 16378 16379 4102 +a 16379 16380 4102 +a 16380 16381 4102 +a 16381 16382 4102 +a 16382 16383 4102 +a 16383 16384 4102 +a 16384 16385 4102 +a 16385 16386 4102 +a 16386 16387 4102 +a 16387 16388 4102 +a 16388 16389 4102 +a 16389 16390 4102 +a 16390 16391 4102 +a 16391 16392 4102 +a 16392 16393 4102 +a 16393 16394 4102 +a 16394 16395 4102 +a 16395 16396 4102 +a 16396 16397 4102 +a 16397 16398 4102 +a 16398 16399 4102 +a 16399 16400 4102 +a 16400 16401 4102 +a 16401 16402 4102 +a 16402 16403 4102 +a 16403 16404 4102 +a 16404 16405 4102 +a 16405 16406 4102 +a 16406 16407 4102 +a 16407 16408 4102 +a 16408 16409 4102 +a 16409 16410 4102 +a 16410 16411 4102 +a 16411 16412 4102 +a 16412 16413 4102 +a 16413 16414 4102 +a 8209 16414 1 +a 8210 16413 1 +a 8211 16412 1 +a 8212 16411 1 +a 8213 16410 1 +a 8214 16409 1 +a 8215 16408 1 +a 8216 16407 1 +a 8217 16406 1 +a 8218 16405 1 +a 8219 16404 1 +a 8220 16403 1 +a 8221 16402 1 +a 8222 16401 1 +a 8223 16400 1 +a 8224 16399 1 +a 8225 16398 1 +a 8226 16397 1 +a 8227 16396 1 +a 8228 16395 1 +a 8229 16394 1 +a 8230 16393 1 +a 8231 16392 1 +a 8232 16391 1 +a 8233 16390 1 +a 8234 16389 1 +a 8235 16388 1 +a 8236 16387 1 +a 8237 16386 1 +a 8238 16385 1 +a 8239 16384 1 +a 8240 16383 1 +a 8241 16382 1 +a 8242 16381 1 +a 8243 16380 1 +a 8244 16379 1 +a 8245 16378 1 +a 8246 16377 1 +a 8247 16376 1 +a 8248 16375 1 +a 8249 16374 1 +a 8250 16373 1 +a 8251 16372 1 +a 8252 16371 1 +a 8253 16370 1 +a 8254 16369 1 +a 8255 16368 1 +a 8256 16367 1 +a 8257 16366 1 +a 8258 16365 1 +a 8259 16364 1 +a 8260 16363 1 +a 8261 16362 1 +a 8262 16361 1 +a 8263 16360 1 +a 8264 16359 1 +a 8265 16358 1 +a 8266 16357 1 +a 8267 16356 1 +a 8268 16355 1 +a 8269 16354 1 +a 8270 16353 1 +a 8271 16352 1 +a 8272 16351 1 +a 8273 16350 1 +a 8274 16349 1 +a 8275 16348 1 +a 8276 16347 1 +a 8277 16346 1 +a 8278 16345 1 +a 8279 16344 1 +a 8280 16343 1 +a 8281 16342 1 +a 8282 16341 1 +a 8283 16340 1 +a 8284 16339 1 +a 8285 16338 1 +a 8286 16337 1 +a 8287 16336 1 +a 8288 16335 1 +a 8289 16334 1 +a 8290 16333 1 +a 8291 16332 1 +a 8292 16331 1 +a 8293 16330 1 +a 8294 16329 1 +a 8295 16328 1 +a 8296 16327 1 +a 8297 16326 1 +a 8298 16325 1 +a 8299 16324 1 +a 8300 16323 1 +a 8301 16322 1 +a 8302 16321 1 +a 8303 16320 1 +a 8304 16319 1 +a 8305 16318 1 +a 8306 16317 1 +a 8307 16316 1 +a 8308 16315 1 +a 8309 16314 1 +a 8310 16313 1 +a 8311 16312 1 +a 8312 16311 1 +a 8313 16310 1 +a 8314 16309 1 +a 8315 16308 1 +a 8316 16307 1 +a 8317 16306 1 +a 8318 16305 1 +a 8319 16304 1 +a 8320 16303 1 +a 8321 16302 1 +a 8322 16301 1 +a 8323 16300 1 +a 8324 16299 1 +a 8325 16298 1 +a 8326 16297 1 +a 8327 16296 1 +a 8328 16295 1 +a 8329 16294 1 +a 8330 16293 1 +a 8331 16292 1 +a 8332 16291 1 +a 8333 16290 1 +a 8334 16289 1 +a 8335 16288 1 +a 8336 16287 1 +a 8337 16286 1 +a 8338 16285 1 +a 8339 16284 1 +a 8340 16283 1 +a 8341 16282 1 +a 8342 16281 1 +a 8343 16280 1 +a 8344 16279 1 +a 8345 16278 1 +a 8346 16277 1 +a 8347 16276 1 +a 8348 16275 1 +a 8349 16274 1 +a 8350 16273 1 +a 8351 16272 1 +a 8352 16271 1 +a 8353 16270 1 +a 8354 16269 1 +a 8355 16268 1 +a 8356 16267 1 +a 8357 16266 1 +a 8358 16265 1 +a 8359 16264 1 +a 8360 16263 1 +a 8361 16262 1 +a 8362 16261 1 +a 8363 16260 1 +a 8364 16259 1 +a 8365 16258 1 +a 8366 16257 1 +a 8367 16256 1 +a 8368 16255 1 +a 8369 16254 1 +a 8370 16253 1 +a 8371 16252 1 +a 8372 16251 1 +a 8373 16250 1 +a 8374 16249 1 +a 8375 16248 1 +a 8376 16247 1 +a 8377 16246 1 +a 8378 16245 1 +a 8379 16244 1 +a 8380 16243 1 +a 8381 16242 1 +a 8382 16241 1 +a 8383 16240 1 +a 8384 16239 1 +a 8385 16238 1 +a 8386 16237 1 +a 8387 16236 1 +a 8388 16235 1 +a 8389 16234 1 +a 8390 16233 1 +a 8391 16232 1 +a 8392 16231 1 +a 8393 16230 1 +a 8394 16229 1 +a 8395 16228 1 +a 8396 16227 1 +a 8397 16226 1 +a 8398 16225 1 +a 8399 16224 1 +a 8400 16223 1 +a 8401 16222 1 +a 8402 16221 1 +a 8403 16220 1 +a 8404 16219 1 +a 8405 16218 1 +a 8406 16217 1 +a 8407 16216 1 +a 8408 16215 1 +a 8409 16214 1 +a 8410 16213 1 +a 8411 16212 1 +a 8412 16211 1 +a 8413 16210 1 +a 8414 16209 1 +a 8415 16208 1 +a 8416 16207 1 +a 8417 16206 1 +a 8418 16205 1 +a 8419 16204 1 +a 8420 16203 1 +a 8421 16202 1 +a 8422 16201 1 +a 8423 16200 1 +a 8424 16199 1 +a 8425 16198 1 +a 8426 16197 1 +a 8427 16196 1 +a 8428 16195 1 +a 8429 16194 1 +a 8430 16193 1 +a 8431 16192 1 +a 8432 16191 1 +a 8433 16190 1 +a 8434 16189 1 +a 8435 16188 1 +a 8436 16187 1 +a 8437 16186 1 +a 8438 16185 1 +a 8439 16184 1 +a 8440 16183 1 +a 8441 16182 1 +a 8442 16181 1 +a 8443 16180 1 +a 8444 16179 1 +a 8445 16178 1 +a 8446 16177 1 +a 8447 16176 1 +a 8448 16175 1 +a 8449 16174 1 +a 8450 16173 1 +a 8451 16172 1 +a 8452 16171 1 +a 8453 16170 1 +a 8454 16169 1 +a 8455 16168 1 +a 8456 16167 1 +a 8457 16166 1 +a 8458 16165 1 +a 8459 16164 1 +a 8460 16163 1 +a 8461 16162 1 +a 8462 16161 1 +a 8463 16160 1 +a 8464 16159 1 +a 8465 16158 1 +a 8466 16157 1 +a 8467 16156 1 +a 8468 16155 1 +a 8469 16154 1 +a 8470 16153 1 +a 8471 16152 1 +a 8472 16151 1 +a 8473 16150 1 +a 8474 16149 1 +a 8475 16148 1 +a 8476 16147 1 +a 8477 16146 1 +a 8478 16145 1 +a 8479 16144 1 +a 8480 16143 1 +a 8481 16142 1 +a 8482 16141 1 +a 8483 16140 1 +a 8484 16139 1 +a 8485 16138 1 +a 8486 16137 1 +a 8487 16136 1 +a 8488 16135 1 +a 8489 16134 1 +a 8490 16133 1 +a 8491 16132 1 +a 8492 16131 1 +a 8493 16130 1 +a 8494 16129 1 +a 8495 16128 1 +a 8496 16127 1 +a 8497 16126 1 +a 8498 16125 1 +a 8499 16124 1 +a 8500 16123 1 +a 8501 16122 1 +a 8502 16121 1 +a 8503 16120 1 +a 8504 16119 1 +a 8505 16118 1 +a 8506 16117 1 +a 8507 16116 1 +a 8508 16115 1 +a 8509 16114 1 +a 8510 16113 1 +a 8511 16112 1 +a 8512 16111 1 +a 8513 16110 1 +a 8514 16109 1 +a 8515 16108 1 +a 8516 16107 1 +a 8517 16106 1 +a 8518 16105 1 +a 8519 16104 1 +a 8520 16103 1 +a 8521 16102 1 +a 8522 16101 1 +a 8523 16100 1 +a 8524 16099 1 +a 8525 16098 1 +a 8526 16097 1 +a 8527 16096 1 +a 8528 16095 1 +a 8529 16094 1 +a 8530 16093 1 +a 8531 16092 1 +a 8532 16091 1 +a 8533 16090 1 +a 8534 16089 1 +a 8535 16088 1 +a 8536 16087 1 +a 8537 16086 1 +a 8538 16085 1 +a 8539 16084 1 +a 8540 16083 1 +a 8541 16082 1 +a 8542 16081 1 +a 8543 16080 1 +a 8544 16079 1 +a 8545 16078 1 +a 8546 16077 1 +a 8547 16076 1 +a 8548 16075 1 +a 8549 16074 1 +a 8550 16073 1 +a 8551 16072 1 +a 8552 16071 1 +a 8553 16070 1 +a 8554 16069 1 +a 8555 16068 1 +a 8556 16067 1 +a 8557 16066 1 +a 8558 16065 1 +a 8559 16064 1 +a 8560 16063 1 +a 8561 16062 1 +a 8562 16061 1 +a 8563 16060 1 +a 8564 16059 1 +a 8565 16058 1 +a 8566 16057 1 +a 8567 16056 1 +a 8568 16055 1 +a 8569 16054 1 +a 8570 16053 1 +a 8571 16052 1 +a 8572 16051 1 +a 8573 16050 1 +a 8574 16049 1 +a 8575 16048 1 +a 8576 16047 1 +a 8577 16046 1 +a 8578 16045 1 +a 8579 16044 1 +a 8580 16043 1 +a 8581 16042 1 +a 8582 16041 1 +a 8583 16040 1 +a 8584 16039 1 +a 8585 16038 1 +a 8586 16037 1 +a 8587 16036 1 +a 8588 16035 1 +a 8589 16034 1 +a 8590 16033 1 +a 8591 16032 1 +a 8592 16031 1 +a 8593 16030 1 +a 8594 16029 1 +a 8595 16028 1 +a 8596 16027 1 +a 8597 16026 1 +a 8598 16025 1 +a 8599 16024 1 +a 8600 16023 1 +a 8601 16022 1 +a 8602 16021 1 +a 8603 16020 1 +a 8604 16019 1 +a 8605 16018 1 +a 8606 16017 1 +a 8607 16016 1 +a 8608 16015 1 +a 8609 16014 1 +a 8610 16013 1 +a 8611 16012 1 +a 8612 16011 1 +a 8613 16010 1 +a 8614 16009 1 +a 8615 16008 1 +a 8616 16007 1 +a 8617 16006 1 +a 8618 16005 1 +a 8619 16004 1 +a 8620 16003 1 +a 8621 16002 1 +a 8622 16001 1 +a 8623 16000 1 +a 8624 15999 1 +a 8625 15998 1 +a 8626 15997 1 +a 8627 15996 1 +a 8628 15995 1 +a 8629 15994 1 +a 8630 15993 1 +a 8631 15992 1 +a 8632 15991 1 +a 8633 15990 1 +a 8634 15989 1 +a 8635 15988 1 +a 8636 15987 1 +a 8637 15986 1 +a 8638 15985 1 +a 8639 15984 1 +a 8640 15983 1 +a 8641 15982 1 +a 8642 15981 1 +a 8643 15980 1 +a 8644 15979 1 +a 8645 15978 1 +a 8646 15977 1 +a 8647 15976 1 +a 8648 15975 1 +a 8649 15974 1 +a 8650 15973 1 +a 8651 15972 1 +a 8652 15971 1 +a 8653 15970 1 +a 8654 15969 1 +a 8655 15968 1 +a 8656 15967 1 +a 8657 15966 1 +a 8658 15965 1 +a 8659 15964 1 +a 8660 15963 1 +a 8661 15962 1 +a 8662 15961 1 +a 8663 15960 1 +a 8664 15959 1 +a 8665 15958 1 +a 8666 15957 1 +a 8667 15956 1 +a 8668 15955 1 +a 8669 15954 1 +a 8670 15953 1 +a 8671 15952 1 +a 8672 15951 1 +a 8673 15950 1 +a 8674 15949 1 +a 8675 15948 1 +a 8676 15947 1 +a 8677 15946 1 +a 8678 15945 1 +a 8679 15944 1 +a 8680 15943 1 +a 8681 15942 1 +a 8682 15941 1 +a 8683 15940 1 +a 8684 15939 1 +a 8685 15938 1 +a 8686 15937 1 +a 8687 15936 1 +a 8688 15935 1 +a 8689 15934 1 +a 8690 15933 1 +a 8691 15932 1 +a 8692 15931 1 +a 8693 15930 1 +a 8694 15929 1 +a 8695 15928 1 +a 8696 15927 1 +a 8697 15926 1 +a 8698 15925 1 +a 8699 15924 1 +a 8700 15923 1 +a 8701 15922 1 +a 8702 15921 1 +a 8703 15920 1 +a 8704 15919 1 +a 8705 15918 1 +a 8706 15917 1 +a 8707 15916 1 +a 8708 15915 1 +a 8709 15914 1 +a 8710 15913 1 +a 8711 15912 1 +a 8712 15911 1 +a 8713 15910 1 +a 8714 15909 1 +a 8715 15908 1 +a 8716 15907 1 +a 8717 15906 1 +a 8718 15905 1 +a 8719 15904 1 +a 8720 15903 1 +a 8721 15902 1 +a 8722 15901 1 +a 8723 15900 1 +a 8724 15899 1 +a 8725 15898 1 +a 8726 15897 1 +a 8727 15896 1 +a 8728 15895 1 +a 8729 15894 1 +a 8730 15893 1 +a 8731 15892 1 +a 8732 15891 1 +a 8733 15890 1 +a 8734 15889 1 +a 8735 15888 1 +a 8736 15887 1 +a 8737 15886 1 +a 8738 15885 1 +a 8739 15884 1 +a 8740 15883 1 +a 8741 15882 1 +a 8742 15881 1 +a 8743 15880 1 +a 8744 15879 1 +a 8745 15878 1 +a 8746 15877 1 +a 8747 15876 1 +a 8748 15875 1 +a 8749 15874 1 +a 8750 15873 1 +a 8751 15872 1 +a 8752 15871 1 +a 8753 15870 1 +a 8754 15869 1 +a 8755 15868 1 +a 8756 15867 1 +a 8757 15866 1 +a 8758 15865 1 +a 8759 15864 1 +a 8760 15863 1 +a 8761 15862 1 +a 8762 15861 1 +a 8763 15860 1 +a 8764 15859 1 +a 8765 15858 1 +a 8766 15857 1 +a 8767 15856 1 +a 8768 15855 1 +a 8769 15854 1 +a 8770 15853 1 +a 8771 15852 1 +a 8772 15851 1 +a 8773 15850 1 +a 8774 15849 1 +a 8775 15848 1 +a 8776 15847 1 +a 8777 15846 1 +a 8778 15845 1 +a 8779 15844 1 +a 8780 15843 1 +a 8781 15842 1 +a 8782 15841 1 +a 8783 15840 1 +a 8784 15839 1 +a 8785 15838 1 +a 8786 15837 1 +a 8787 15836 1 +a 8788 15835 1 +a 8789 15834 1 +a 8790 15833 1 +a 8791 15832 1 +a 8792 15831 1 +a 8793 15830 1 +a 8794 15829 1 +a 8795 15828 1 +a 8796 15827 1 +a 8797 15826 1 +a 8798 15825 1 +a 8799 15824 1 +a 8800 15823 1 +a 8801 15822 1 +a 8802 15821 1 +a 8803 15820 1 +a 8804 15819 1 +a 8805 15818 1 +a 8806 15817 1 +a 8807 15816 1 +a 8808 15815 1 +a 8809 15814 1 +a 8810 15813 1 +a 8811 15812 1 +a 8812 15811 1 +a 8813 15810 1 +a 8814 15809 1 +a 8815 15808 1 +a 8816 15807 1 +a 8817 15806 1 +a 8818 15805 1 +a 8819 15804 1 +a 8820 15803 1 +a 8821 15802 1 +a 8822 15801 1 +a 8823 15800 1 +a 8824 15799 1 +a 8825 15798 1 +a 8826 15797 1 +a 8827 15796 1 +a 8828 15795 1 +a 8829 15794 1 +a 8830 15793 1 +a 8831 15792 1 +a 8832 15791 1 +a 8833 15790 1 +a 8834 15789 1 +a 8835 15788 1 +a 8836 15787 1 +a 8837 15786 1 +a 8838 15785 1 +a 8839 15784 1 +a 8840 15783 1 +a 8841 15782 1 +a 8842 15781 1 +a 8843 15780 1 +a 8844 15779 1 +a 8845 15778 1 +a 8846 15777 1 +a 8847 15776 1 +a 8848 15775 1 +a 8849 15774 1 +a 8850 15773 1 +a 8851 15772 1 +a 8852 15771 1 +a 8853 15770 1 +a 8854 15769 1 +a 8855 15768 1 +a 8856 15767 1 +a 8857 15766 1 +a 8858 15765 1 +a 8859 15764 1 +a 8860 15763 1 +a 8861 15762 1 +a 8862 15761 1 +a 8863 15760 1 +a 8864 15759 1 +a 8865 15758 1 +a 8866 15757 1 +a 8867 15756 1 +a 8868 15755 1 +a 8869 15754 1 +a 8870 15753 1 +a 8871 15752 1 +a 8872 15751 1 +a 8873 15750 1 +a 8874 15749 1 +a 8875 15748 1 +a 8876 15747 1 +a 8877 15746 1 +a 8878 15745 1 +a 8879 15744 1 +a 8880 15743 1 +a 8881 15742 1 +a 8882 15741 1 +a 8883 15740 1 +a 8884 15739 1 +a 8885 15738 1 +a 8886 15737 1 +a 8887 15736 1 +a 8888 15735 1 +a 8889 15734 1 +a 8890 15733 1 +a 8891 15732 1 +a 8892 15731 1 +a 8893 15730 1 +a 8894 15729 1 +a 8895 15728 1 +a 8896 15727 1 +a 8897 15726 1 +a 8898 15725 1 +a 8899 15724 1 +a 8900 15723 1 +a 8901 15722 1 +a 8902 15721 1 +a 8903 15720 1 +a 8904 15719 1 +a 8905 15718 1 +a 8906 15717 1 +a 8907 15716 1 +a 8908 15715 1 +a 8909 15714 1 +a 8910 15713 1 +a 8911 15712 1 +a 8912 15711 1 +a 8913 15710 1 +a 8914 15709 1 +a 8915 15708 1 +a 8916 15707 1 +a 8917 15706 1 +a 8918 15705 1 +a 8919 15704 1 +a 8920 15703 1 +a 8921 15702 1 +a 8922 15701 1 +a 8923 15700 1 +a 8924 15699 1 +a 8925 15698 1 +a 8926 15697 1 +a 8927 15696 1 +a 8928 15695 1 +a 8929 15694 1 +a 8930 15693 1 +a 8931 15692 1 +a 8932 15691 1 +a 8933 15690 1 +a 8934 15689 1 +a 8935 15688 1 +a 8936 15687 1 +a 8937 15686 1 +a 8938 15685 1 +a 8939 15684 1 +a 8940 15683 1 +a 8941 15682 1 +a 8942 15681 1 +a 8943 15680 1 +a 8944 15679 1 +a 8945 15678 1 +a 8946 15677 1 +a 8947 15676 1 +a 8948 15675 1 +a 8949 15674 1 +a 8950 15673 1 +a 8951 15672 1 +a 8952 15671 1 +a 8953 15670 1 +a 8954 15669 1 +a 8955 15668 1 +a 8956 15667 1 +a 8957 15666 1 +a 8958 15665 1 +a 8959 15664 1 +a 8960 15663 1 +a 8961 15662 1 +a 8962 15661 1 +a 8963 15660 1 +a 8964 15659 1 +a 8965 15658 1 +a 8966 15657 1 +a 8967 15656 1 +a 8968 15655 1 +a 8969 15654 1 +a 8970 15653 1 +a 8971 15652 1 +a 8972 15651 1 +a 8973 15650 1 +a 8974 15649 1 +a 8975 15648 1 +a 8976 15647 1 +a 8977 15646 1 +a 8978 15645 1 +a 8979 15644 1 +a 8980 15643 1 +a 8981 15642 1 +a 8982 15641 1 +a 8983 15640 1 +a 8984 15639 1 +a 8985 15638 1 +a 8986 15637 1 +a 8987 15636 1 +a 8988 15635 1 +a 8989 15634 1 +a 8990 15633 1 +a 8991 15632 1 +a 8992 15631 1 +a 8993 15630 1 +a 8994 15629 1 +a 8995 15628 1 +a 8996 15627 1 +a 8997 15626 1 +a 8998 15625 1 +a 8999 15624 1 +a 9000 15623 1 +a 9001 15622 1 +a 9002 15621 1 +a 9003 15620 1 +a 9004 15619 1 +a 9005 15618 1 +a 9006 15617 1 +a 9007 15616 1 +a 9008 15615 1 +a 9009 15614 1 +a 9010 15613 1 +a 9011 15612 1 +a 9012 15611 1 +a 9013 15610 1 +a 9014 15609 1 +a 9015 15608 1 +a 9016 15607 1 +a 9017 15606 1 +a 9018 15605 1 +a 9019 15604 1 +a 9020 15603 1 +a 9021 15602 1 +a 9022 15601 1 +a 9023 15600 1 +a 9024 15599 1 +a 9025 15598 1 +a 9026 15597 1 +a 9027 15596 1 +a 9028 15595 1 +a 9029 15594 1 +a 9030 15593 1 +a 9031 15592 1 +a 9032 15591 1 +a 9033 15590 1 +a 9034 15589 1 +a 9035 15588 1 +a 9036 15587 1 +a 9037 15586 1 +a 9038 15585 1 +a 9039 15584 1 +a 9040 15583 1 +a 9041 15582 1 +a 9042 15581 1 +a 9043 15580 1 +a 9044 15579 1 +a 9045 15578 1 +a 9046 15577 1 +a 9047 15576 1 +a 9048 15575 1 +a 9049 15574 1 +a 9050 15573 1 +a 9051 15572 1 +a 9052 15571 1 +a 9053 15570 1 +a 9054 15569 1 +a 9055 15568 1 +a 9056 15567 1 +a 9057 15566 1 +a 9058 15565 1 +a 9059 15564 1 +a 9060 15563 1 +a 9061 15562 1 +a 9062 15561 1 +a 9063 15560 1 +a 9064 15559 1 +a 9065 15558 1 +a 9066 15557 1 +a 9067 15556 1 +a 9068 15555 1 +a 9069 15554 1 +a 9070 15553 1 +a 9071 15552 1 +a 9072 15551 1 +a 9073 15550 1 +a 9074 15549 1 +a 9075 15548 1 +a 9076 15547 1 +a 9077 15546 1 +a 9078 15545 1 +a 9079 15544 1 +a 9080 15543 1 +a 9081 15542 1 +a 9082 15541 1 +a 9083 15540 1 +a 9084 15539 1 +a 9085 15538 1 +a 9086 15537 1 +a 9087 15536 1 +a 9088 15535 1 +a 9089 15534 1 +a 9090 15533 1 +a 9091 15532 1 +a 9092 15531 1 +a 9093 15530 1 +a 9094 15529 1 +a 9095 15528 1 +a 9096 15527 1 +a 9097 15526 1 +a 9098 15525 1 +a 9099 15524 1 +a 9100 15523 1 +a 9101 15522 1 +a 9102 15521 1 +a 9103 15520 1 +a 9104 15519 1 +a 9105 15518 1 +a 9106 15517 1 +a 9107 15516 1 +a 9108 15515 1 +a 9109 15514 1 +a 9110 15513 1 +a 9111 15512 1 +a 9112 15511 1 +a 9113 15510 1 +a 9114 15509 1 +a 9115 15508 1 +a 9116 15507 1 +a 9117 15506 1 +a 9118 15505 1 +a 9119 15504 1 +a 9120 15503 1 +a 9121 15502 1 +a 9122 15501 1 +a 9123 15500 1 +a 9124 15499 1 +a 9125 15498 1 +a 9126 15497 1 +a 9127 15496 1 +a 9128 15495 1 +a 9129 15494 1 +a 9130 15493 1 +a 9131 15492 1 +a 9132 15491 1 +a 9133 15490 1 +a 9134 15489 1 +a 9135 15488 1 +a 9136 15487 1 +a 9137 15486 1 +a 9138 15485 1 +a 9139 15484 1 +a 9140 15483 1 +a 9141 15482 1 +a 9142 15481 1 +a 9143 15480 1 +a 9144 15479 1 +a 9145 15478 1 +a 9146 15477 1 +a 9147 15476 1 +a 9148 15475 1 +a 9149 15474 1 +a 9150 15473 1 +a 9151 15472 1 +a 9152 15471 1 +a 9153 15470 1 +a 9154 15469 1 +a 9155 15468 1 +a 9156 15467 1 +a 9157 15466 1 +a 9158 15465 1 +a 9159 15464 1 +a 9160 15463 1 +a 9161 15462 1 +a 9162 15461 1 +a 9163 15460 1 +a 9164 15459 1 +a 9165 15458 1 +a 9166 15457 1 +a 9167 15456 1 +a 9168 15455 1 +a 9169 15454 1 +a 9170 15453 1 +a 9171 15452 1 +a 9172 15451 1 +a 9173 15450 1 +a 9174 15449 1 +a 9175 15448 1 +a 9176 15447 1 +a 9177 15446 1 +a 9178 15445 1 +a 9179 15444 1 +a 9180 15443 1 +a 9181 15442 1 +a 9182 15441 1 +a 9183 15440 1 +a 9184 15439 1 +a 9185 15438 1 +a 9186 15437 1 +a 9187 15436 1 +a 9188 15435 1 +a 9189 15434 1 +a 9190 15433 1 +a 9191 15432 1 +a 9192 15431 1 +a 9193 15430 1 +a 9194 15429 1 +a 9195 15428 1 +a 9196 15427 1 +a 9197 15426 1 +a 9198 15425 1 +a 9199 15424 1 +a 9200 15423 1 +a 9201 15422 1 +a 9202 15421 1 +a 9203 15420 1 +a 9204 15419 1 +a 9205 15418 1 +a 9206 15417 1 +a 9207 15416 1 +a 9208 15415 1 +a 9209 15414 1 +a 9210 15413 1 +a 9211 15412 1 +a 9212 15411 1 +a 9213 15410 1 +a 9214 15409 1 +a 9215 15408 1 +a 9216 15407 1 +a 9217 15406 1 +a 9218 15405 1 +a 9219 15404 1 +a 9220 15403 1 +a 9221 15402 1 +a 9222 15401 1 +a 9223 15400 1 +a 9224 15399 1 +a 9225 15398 1 +a 9226 15397 1 +a 9227 15396 1 +a 9228 15395 1 +a 9229 15394 1 +a 9230 15393 1 +a 9231 15392 1 +a 9232 15391 1 +a 9233 15390 1 +a 9234 15389 1 +a 9235 15388 1 +a 9236 15387 1 +a 9237 15386 1 +a 9238 15385 1 +a 9239 15384 1 +a 9240 15383 1 +a 9241 15382 1 +a 9242 15381 1 +a 9243 15380 1 +a 9244 15379 1 +a 9245 15378 1 +a 9246 15377 1 +a 9247 15376 1 +a 9248 15375 1 +a 9249 15374 1 +a 9250 15373 1 +a 9251 15372 1 +a 9252 15371 1 +a 9253 15370 1 +a 9254 15369 1 +a 9255 15368 1 +a 9256 15367 1 +a 9257 15366 1 +a 9258 15365 1 +a 9259 15364 1 +a 9260 15363 1 +a 9261 15362 1 +a 9262 15361 1 +a 9263 15360 1 +a 9264 15359 1 +a 9265 15358 1 +a 9266 15357 1 +a 9267 15356 1 +a 9268 15355 1 +a 9269 15354 1 +a 9270 15353 1 +a 9271 15352 1 +a 9272 15351 1 +a 9273 15350 1 +a 9274 15349 1 +a 9275 15348 1 +a 9276 15347 1 +a 9277 15346 1 +a 9278 15345 1 +a 9279 15344 1 +a 9280 15343 1 +a 9281 15342 1 +a 9282 15341 1 +a 9283 15340 1 +a 9284 15339 1 +a 9285 15338 1 +a 9286 15337 1 +a 9287 15336 1 +a 9288 15335 1 +a 9289 15334 1 +a 9290 15333 1 +a 9291 15332 1 +a 9292 15331 1 +a 9293 15330 1 +a 9294 15329 1 +a 9295 15328 1 +a 9296 15327 1 +a 9297 15326 1 +a 9298 15325 1 +a 9299 15324 1 +a 9300 15323 1 +a 9301 15322 1 +a 9302 15321 1 +a 9303 15320 1 +a 9304 15319 1 +a 9305 15318 1 +a 9306 15317 1 +a 9307 15316 1 +a 9308 15315 1 +a 9309 15314 1 +a 9310 15313 1 +a 9311 15312 1 +a 9312 15311 1 +a 9313 15310 1 +a 9314 15309 1 +a 9315 15308 1 +a 9316 15307 1 +a 9317 15306 1 +a 9318 15305 1 +a 9319 15304 1 +a 9320 15303 1 +a 9321 15302 1 +a 9322 15301 1 +a 9323 15300 1 +a 9324 15299 1 +a 9325 15298 1 +a 9326 15297 1 +a 9327 15296 1 +a 9328 15295 1 +a 9329 15294 1 +a 9330 15293 1 +a 9331 15292 1 +a 9332 15291 1 +a 9333 15290 1 +a 9334 15289 1 +a 9335 15288 1 +a 9336 15287 1 +a 9337 15286 1 +a 9338 15285 1 +a 9339 15284 1 +a 9340 15283 1 +a 9341 15282 1 +a 9342 15281 1 +a 9343 15280 1 +a 9344 15279 1 +a 9345 15278 1 +a 9346 15277 1 +a 9347 15276 1 +a 9348 15275 1 +a 9349 15274 1 +a 9350 15273 1 +a 9351 15272 1 +a 9352 15271 1 +a 9353 15270 1 +a 9354 15269 1 +a 9355 15268 1 +a 9356 15267 1 +a 9357 15266 1 +a 9358 15265 1 +a 9359 15264 1 +a 9360 15263 1 +a 9361 15262 1 +a 9362 15261 1 +a 9363 15260 1 +a 9364 15259 1 +a 9365 15258 1 +a 9366 15257 1 +a 9367 15256 1 +a 9368 15255 1 +a 9369 15254 1 +a 9370 15253 1 +a 9371 15252 1 +a 9372 15251 1 +a 9373 15250 1 +a 9374 15249 1 +a 9375 15248 1 +a 9376 15247 1 +a 9377 15246 1 +a 9378 15245 1 +a 9379 15244 1 +a 9380 15243 1 +a 9381 15242 1 +a 9382 15241 1 +a 9383 15240 1 +a 9384 15239 1 +a 9385 15238 1 +a 9386 15237 1 +a 9387 15236 1 +a 9388 15235 1 +a 9389 15234 1 +a 9390 15233 1 +a 9391 15232 1 +a 9392 15231 1 +a 9393 15230 1 +a 9394 15229 1 +a 9395 15228 1 +a 9396 15227 1 +a 9397 15226 1 +a 9398 15225 1 +a 9399 15224 1 +a 9400 15223 1 +a 9401 15222 1 +a 9402 15221 1 +a 9403 15220 1 +a 9404 15219 1 +a 9405 15218 1 +a 9406 15217 1 +a 9407 15216 1 +a 9408 15215 1 +a 9409 15214 1 +a 9410 15213 1 +a 9411 15212 1 +a 9412 15211 1 +a 9413 15210 1 +a 9414 15209 1 +a 9415 15208 1 +a 9416 15207 1 +a 9417 15206 1 +a 9418 15205 1 +a 9419 15204 1 +a 9420 15203 1 +a 9421 15202 1 +a 9422 15201 1 +a 9423 15200 1 +a 9424 15199 1 +a 9425 15198 1 +a 9426 15197 1 +a 9427 15196 1 +a 9428 15195 1 +a 9429 15194 1 +a 9430 15193 1 +a 9431 15192 1 +a 9432 15191 1 +a 9433 15190 1 +a 9434 15189 1 +a 9435 15188 1 +a 9436 15187 1 +a 9437 15186 1 +a 9438 15185 1 +a 9439 15184 1 +a 9440 15183 1 +a 9441 15182 1 +a 9442 15181 1 +a 9443 15180 1 +a 9444 15179 1 +a 9445 15178 1 +a 9446 15177 1 +a 9447 15176 1 +a 9448 15175 1 +a 9449 15174 1 +a 9450 15173 1 +a 9451 15172 1 +a 9452 15171 1 +a 9453 15170 1 +a 9454 15169 1 +a 9455 15168 1 +a 9456 15167 1 +a 9457 15166 1 +a 9458 15165 1 +a 9459 15164 1 +a 9460 15163 1 +a 9461 15162 1 +a 9462 15161 1 +a 9463 15160 1 +a 9464 15159 1 +a 9465 15158 1 +a 9466 15157 1 +a 9467 15156 1 +a 9468 15155 1 +a 9469 15154 1 +a 9470 15153 1 +a 9471 15152 1 +a 9472 15151 1 +a 9473 15150 1 +a 9474 15149 1 +a 9475 15148 1 +a 9476 15147 1 +a 9477 15146 1 +a 9478 15145 1 +a 9479 15144 1 +a 9480 15143 1 +a 9481 15142 1 +a 9482 15141 1 +a 9483 15140 1 +a 9484 15139 1 +a 9485 15138 1 +a 9486 15137 1 +a 9487 15136 1 +a 9488 15135 1 +a 9489 15134 1 +a 9490 15133 1 +a 9491 15132 1 +a 9492 15131 1 +a 9493 15130 1 +a 9494 15129 1 +a 9495 15128 1 +a 9496 15127 1 +a 9497 15126 1 +a 9498 15125 1 +a 9499 15124 1 +a 9500 15123 1 +a 9501 15122 1 +a 9502 15121 1 +a 9503 15120 1 +a 9504 15119 1 +a 9505 15118 1 +a 9506 15117 1 +a 9507 15116 1 +a 9508 15115 1 +a 9509 15114 1 +a 9510 15113 1 +a 9511 15112 1 +a 9512 15111 1 +a 9513 15110 1 +a 9514 15109 1 +a 9515 15108 1 +a 9516 15107 1 +a 9517 15106 1 +a 9518 15105 1 +a 9519 15104 1 +a 9520 15103 1 +a 9521 15102 1 +a 9522 15101 1 +a 9523 15100 1 +a 9524 15099 1 +a 9525 15098 1 +a 9526 15097 1 +a 9527 15096 1 +a 9528 15095 1 +a 9529 15094 1 +a 9530 15093 1 +a 9531 15092 1 +a 9532 15091 1 +a 9533 15090 1 +a 9534 15089 1 +a 9535 15088 1 +a 9536 15087 1 +a 9537 15086 1 +a 9538 15085 1 +a 9539 15084 1 +a 9540 15083 1 +a 9541 15082 1 +a 9542 15081 1 +a 9543 15080 1 +a 9544 15079 1 +a 9545 15078 1 +a 9546 15077 1 +a 9547 15076 1 +a 9548 15075 1 +a 9549 15074 1 +a 9550 15073 1 +a 9551 15072 1 +a 9552 15071 1 +a 9553 15070 1 +a 9554 15069 1 +a 9555 15068 1 +a 9556 15067 1 +a 9557 15066 1 +a 9558 15065 1 +a 9559 15064 1 +a 9560 15063 1 +a 9561 15062 1 +a 9562 15061 1 +a 9563 15060 1 +a 9564 15059 1 +a 9565 15058 1 +a 9566 15057 1 +a 9567 15056 1 +a 9568 15055 1 +a 9569 15054 1 +a 9570 15053 1 +a 9571 15052 1 +a 9572 15051 1 +a 9573 15050 1 +a 9574 15049 1 +a 9575 15048 1 +a 9576 15047 1 +a 9577 15046 1 +a 9578 15045 1 +a 9579 15044 1 +a 9580 15043 1 +a 9581 15042 1 +a 9582 15041 1 +a 9583 15040 1 +a 9584 15039 1 +a 9585 15038 1 +a 9586 15037 1 +a 9587 15036 1 +a 9588 15035 1 +a 9589 15034 1 +a 9590 15033 1 +a 9591 15032 1 +a 9592 15031 1 +a 9593 15030 1 +a 9594 15029 1 +a 9595 15028 1 +a 9596 15027 1 +a 9597 15026 1 +a 9598 15025 1 +a 9599 15024 1 +a 9600 15023 1 +a 9601 15022 1 +a 9602 15021 1 +a 9603 15020 1 +a 9604 15019 1 +a 9605 15018 1 +a 9606 15017 1 +a 9607 15016 1 +a 9608 15015 1 +a 9609 15014 1 +a 9610 15013 1 +a 9611 15012 1 +a 9612 15011 1 +a 9613 15010 1 +a 9614 15009 1 +a 9615 15008 1 +a 9616 15007 1 +a 9617 15006 1 +a 9618 15005 1 +a 9619 15004 1 +a 9620 15003 1 +a 9621 15002 1 +a 9622 15001 1 +a 9623 15000 1 +a 9624 14999 1 +a 9625 14998 1 +a 9626 14997 1 +a 9627 14996 1 +a 9628 14995 1 +a 9629 14994 1 +a 9630 14993 1 +a 9631 14992 1 +a 9632 14991 1 +a 9633 14990 1 +a 9634 14989 1 +a 9635 14988 1 +a 9636 14987 1 +a 9637 14986 1 +a 9638 14985 1 +a 9639 14984 1 +a 9640 14983 1 +a 9641 14982 1 +a 9642 14981 1 +a 9643 14980 1 +a 9644 14979 1 +a 9645 14978 1 +a 9646 14977 1 +a 9647 14976 1 +a 9648 14975 1 +a 9649 14974 1 +a 9650 14973 1 +a 9651 14972 1 +a 9652 14971 1 +a 9653 14970 1 +a 9654 14969 1 +a 9655 14968 1 +a 9656 14967 1 +a 9657 14966 1 +a 9658 14965 1 +a 9659 14964 1 +a 9660 14963 1 +a 9661 14962 1 +a 9662 14961 1 +a 9663 14960 1 +a 9664 14959 1 +a 9665 14958 1 +a 9666 14957 1 +a 9667 14956 1 +a 9668 14955 1 +a 9669 14954 1 +a 9670 14953 1 +a 9671 14952 1 +a 9672 14951 1 +a 9673 14950 1 +a 9674 14949 1 +a 9675 14948 1 +a 9676 14947 1 +a 9677 14946 1 +a 9678 14945 1 +a 9679 14944 1 +a 9680 14943 1 +a 9681 14942 1 +a 9682 14941 1 +a 9683 14940 1 +a 9684 14939 1 +a 9685 14938 1 +a 9686 14937 1 +a 9687 14936 1 +a 9688 14935 1 +a 9689 14934 1 +a 9690 14933 1 +a 9691 14932 1 +a 9692 14931 1 +a 9693 14930 1 +a 9694 14929 1 +a 9695 14928 1 +a 9696 14927 1 +a 9697 14926 1 +a 9698 14925 1 +a 9699 14924 1 +a 9700 14923 1 +a 9701 14922 1 +a 9702 14921 1 +a 9703 14920 1 +a 9704 14919 1 +a 9705 14918 1 +a 9706 14917 1 +a 9707 14916 1 +a 9708 14915 1 +a 9709 14914 1 +a 9710 14913 1 +a 9711 14912 1 +a 9712 14911 1 +a 9713 14910 1 +a 9714 14909 1 +a 9715 14908 1 +a 9716 14907 1 +a 9717 14906 1 +a 9718 14905 1 +a 9719 14904 1 +a 9720 14903 1 +a 9721 14902 1 +a 9722 14901 1 +a 9723 14900 1 +a 9724 14899 1 +a 9725 14898 1 +a 9726 14897 1 +a 9727 14896 1 +a 9728 14895 1 +a 9729 14894 1 +a 9730 14893 1 +a 9731 14892 1 +a 9732 14891 1 +a 9733 14890 1 +a 9734 14889 1 +a 9735 14888 1 +a 9736 14887 1 +a 9737 14886 1 +a 9738 14885 1 +a 9739 14884 1 +a 9740 14883 1 +a 9741 14882 1 +a 9742 14881 1 +a 9743 14880 1 +a 9744 14879 1 +a 9745 14878 1 +a 9746 14877 1 +a 9747 14876 1 +a 9748 14875 1 +a 9749 14874 1 +a 9750 14873 1 +a 9751 14872 1 +a 9752 14871 1 +a 9753 14870 1 +a 9754 14869 1 +a 9755 14868 1 +a 9756 14867 1 +a 9757 14866 1 +a 9758 14865 1 +a 9759 14864 1 +a 9760 14863 1 +a 9761 14862 1 +a 9762 14861 1 +a 9763 14860 1 +a 9764 14859 1 +a 9765 14858 1 +a 9766 14857 1 +a 9767 14856 1 +a 9768 14855 1 +a 9769 14854 1 +a 9770 14853 1 +a 9771 14852 1 +a 9772 14851 1 +a 9773 14850 1 +a 9774 14849 1 +a 9775 14848 1 +a 9776 14847 1 +a 9777 14846 1 +a 9778 14845 1 +a 9779 14844 1 +a 9780 14843 1 +a 9781 14842 1 +a 9782 14841 1 +a 9783 14840 1 +a 9784 14839 1 +a 9785 14838 1 +a 9786 14837 1 +a 9787 14836 1 +a 9788 14835 1 +a 9789 14834 1 +a 9790 14833 1 +a 9791 14832 1 +a 9792 14831 1 +a 9793 14830 1 +a 9794 14829 1 +a 9795 14828 1 +a 9796 14827 1 +a 9797 14826 1 +a 9798 14825 1 +a 9799 14824 1 +a 9800 14823 1 +a 9801 14822 1 +a 9802 14821 1 +a 9803 14820 1 +a 9804 14819 1 +a 9805 14818 1 +a 9806 14817 1 +a 9807 14816 1 +a 9808 14815 1 +a 9809 14814 1 +a 9810 14813 1 +a 9811 14812 1 +a 9812 14811 1 +a 9813 14810 1 +a 9814 14809 1 +a 9815 14808 1 +a 9816 14807 1 +a 9817 14806 1 +a 9818 14805 1 +a 9819 14804 1 +a 9820 14803 1 +a 9821 14802 1 +a 9822 14801 1 +a 9823 14800 1 +a 9824 14799 1 +a 9825 14798 1 +a 9826 14797 1 +a 9827 14796 1 +a 9828 14795 1 +a 9829 14794 1 +a 9830 14793 1 +a 9831 14792 1 +a 9832 14791 1 +a 9833 14790 1 +a 9834 14789 1 +a 9835 14788 1 +a 9836 14787 1 +a 9837 14786 1 +a 9838 14785 1 +a 9839 14784 1 +a 9840 14783 1 +a 9841 14782 1 +a 9842 14781 1 +a 9843 14780 1 +a 9844 14779 1 +a 9845 14778 1 +a 9846 14777 1 +a 9847 14776 1 +a 9848 14775 1 +a 9849 14774 1 +a 9850 14773 1 +a 9851 14772 1 +a 9852 14771 1 +a 9853 14770 1 +a 9854 14769 1 +a 9855 14768 1 +a 9856 14767 1 +a 9857 14766 1 +a 9858 14765 1 +a 9859 14764 1 +a 9860 14763 1 +a 9861 14762 1 +a 9862 14761 1 +a 9863 14760 1 +a 9864 14759 1 +a 9865 14758 1 +a 9866 14757 1 +a 9867 14756 1 +a 9868 14755 1 +a 9869 14754 1 +a 9870 14753 1 +a 9871 14752 1 +a 9872 14751 1 +a 9873 14750 1 +a 9874 14749 1 +a 9875 14748 1 +a 9876 14747 1 +a 9877 14746 1 +a 9878 14745 1 +a 9879 14744 1 +a 9880 14743 1 +a 9881 14742 1 +a 9882 14741 1 +a 9883 14740 1 +a 9884 14739 1 +a 9885 14738 1 +a 9886 14737 1 +a 9887 14736 1 +a 9888 14735 1 +a 9889 14734 1 +a 9890 14733 1 +a 9891 14732 1 +a 9892 14731 1 +a 9893 14730 1 +a 9894 14729 1 +a 9895 14728 1 +a 9896 14727 1 +a 9897 14726 1 +a 9898 14725 1 +a 9899 14724 1 +a 9900 14723 1 +a 9901 14722 1 +a 9902 14721 1 +a 9903 14720 1 +a 9904 14719 1 +a 9905 14718 1 +a 9906 14717 1 +a 9907 14716 1 +a 9908 14715 1 +a 9909 14714 1 +a 9910 14713 1 +a 9911 14712 1 +a 9912 14711 1 +a 9913 14710 1 +a 9914 14709 1 +a 9915 14708 1 +a 9916 14707 1 +a 9917 14706 1 +a 9918 14705 1 +a 9919 14704 1 +a 9920 14703 1 +a 9921 14702 1 +a 9922 14701 1 +a 9923 14700 1 +a 9924 14699 1 +a 9925 14698 1 +a 9926 14697 1 +a 9927 14696 1 +a 9928 14695 1 +a 9929 14694 1 +a 9930 14693 1 +a 9931 14692 1 +a 9932 14691 1 +a 9933 14690 1 +a 9934 14689 1 +a 9935 14688 1 +a 9936 14687 1 +a 9937 14686 1 +a 9938 14685 1 +a 9939 14684 1 +a 9940 14683 1 +a 9941 14682 1 +a 9942 14681 1 +a 9943 14680 1 +a 9944 14679 1 +a 9945 14678 1 +a 9946 14677 1 +a 9947 14676 1 +a 9948 14675 1 +a 9949 14674 1 +a 9950 14673 1 +a 9951 14672 1 +a 9952 14671 1 +a 9953 14670 1 +a 9954 14669 1 +a 9955 14668 1 +a 9956 14667 1 +a 9957 14666 1 +a 9958 14665 1 +a 9959 14664 1 +a 9960 14663 1 +a 9961 14662 1 +a 9962 14661 1 +a 9963 14660 1 +a 9964 14659 1 +a 9965 14658 1 +a 9966 14657 1 +a 9967 14656 1 +a 9968 14655 1 +a 9969 14654 1 +a 9970 14653 1 +a 9971 14652 1 +a 9972 14651 1 +a 9973 14650 1 +a 9974 14649 1 +a 9975 14648 1 +a 9976 14647 1 +a 9977 14646 1 +a 9978 14645 1 +a 9979 14644 1 +a 9980 14643 1 +a 9981 14642 1 +a 9982 14641 1 +a 9983 14640 1 +a 9984 14639 1 +a 9985 14638 1 +a 9986 14637 1 +a 9987 14636 1 +a 9988 14635 1 +a 9989 14634 1 +a 9990 14633 1 +a 9991 14632 1 +a 9992 14631 1 +a 9993 14630 1 +a 9994 14629 1 +a 9995 14628 1 +a 9996 14627 1 +a 9997 14626 1 +a 9998 14625 1 +a 9999 14624 1 +a 10000 14623 1 +a 10001 14622 1 +a 10002 14621 1 +a 10003 14620 1 +a 10004 14619 1 +a 10005 14618 1 +a 10006 14617 1 +a 10007 14616 1 +a 10008 14615 1 +a 10009 14614 1 +a 10010 14613 1 +a 10011 14612 1 +a 10012 14611 1 +a 10013 14610 1 +a 10014 14609 1 +a 10015 14608 1 +a 10016 14607 1 +a 10017 14606 1 +a 10018 14605 1 +a 10019 14604 1 +a 10020 14603 1 +a 10021 14602 1 +a 10022 14601 1 +a 10023 14600 1 +a 10024 14599 1 +a 10025 14598 1 +a 10026 14597 1 +a 10027 14596 1 +a 10028 14595 1 +a 10029 14594 1 +a 10030 14593 1 +a 10031 14592 1 +a 10032 14591 1 +a 10033 14590 1 +a 10034 14589 1 +a 10035 14588 1 +a 10036 14587 1 +a 10037 14586 1 +a 10038 14585 1 +a 10039 14584 1 +a 10040 14583 1 +a 10041 14582 1 +a 10042 14581 1 +a 10043 14580 1 +a 10044 14579 1 +a 10045 14578 1 +a 10046 14577 1 +a 10047 14576 1 +a 10048 14575 1 +a 10049 14574 1 +a 10050 14573 1 +a 10051 14572 1 +a 10052 14571 1 +a 10053 14570 1 +a 10054 14569 1 +a 10055 14568 1 +a 10056 14567 1 +a 10057 14566 1 +a 10058 14565 1 +a 10059 14564 1 +a 10060 14563 1 +a 10061 14562 1 +a 10062 14561 1 +a 10063 14560 1 +a 10064 14559 1 +a 10065 14558 1 +a 10066 14557 1 +a 10067 14556 1 +a 10068 14555 1 +a 10069 14554 1 +a 10070 14553 1 +a 10071 14552 1 +a 10072 14551 1 +a 10073 14550 1 +a 10074 14549 1 +a 10075 14548 1 +a 10076 14547 1 +a 10077 14546 1 +a 10078 14545 1 +a 10079 14544 1 +a 10080 14543 1 +a 10081 14542 1 +a 10082 14541 1 +a 10083 14540 1 +a 10084 14539 1 +a 10085 14538 1 +a 10086 14537 1 +a 10087 14536 1 +a 10088 14535 1 +a 10089 14534 1 +a 10090 14533 1 +a 10091 14532 1 +a 10092 14531 1 +a 10093 14530 1 +a 10094 14529 1 +a 10095 14528 1 +a 10096 14527 1 +a 10097 14526 1 +a 10098 14525 1 +a 10099 14524 1 +a 10100 14523 1 +a 10101 14522 1 +a 10102 14521 1 +a 10103 14520 1 +a 10104 14519 1 +a 10105 14518 1 +a 10106 14517 1 +a 10107 14516 1 +a 10108 14515 1 +a 10109 14514 1 +a 10110 14513 1 +a 10111 14512 1 +a 10112 14511 1 +a 10113 14510 1 +a 10114 14509 1 +a 10115 14508 1 +a 10116 14507 1 +a 10117 14506 1 +a 10118 14505 1 +a 10119 14504 1 +a 10120 14503 1 +a 10121 14502 1 +a 10122 14501 1 +a 10123 14500 1 +a 10124 14499 1 +a 10125 14498 1 +a 10126 14497 1 +a 10127 14496 1 +a 10128 14495 1 +a 10129 14494 1 +a 10130 14493 1 +a 10131 14492 1 +a 10132 14491 1 +a 10133 14490 1 +a 10134 14489 1 +a 10135 14488 1 +a 10136 14487 1 +a 10137 14486 1 +a 10138 14485 1 +a 10139 14484 1 +a 10140 14483 1 +a 10141 14482 1 +a 10142 14481 1 +a 10143 14480 1 +a 10144 14479 1 +a 10145 14478 1 +a 10146 14477 1 +a 10147 14476 1 +a 10148 14475 1 +a 10149 14474 1 +a 10150 14473 1 +a 10151 14472 1 +a 10152 14471 1 +a 10153 14470 1 +a 10154 14469 1 +a 10155 14468 1 +a 10156 14467 1 +a 10157 14466 1 +a 10158 14465 1 +a 10159 14464 1 +a 10160 14463 1 +a 10161 14462 1 +a 10162 14461 1 +a 10163 14460 1 +a 10164 14459 1 +a 10165 14458 1 +a 10166 14457 1 +a 10167 14456 1 +a 10168 14455 1 +a 10169 14454 1 +a 10170 14453 1 +a 10171 14452 1 +a 10172 14451 1 +a 10173 14450 1 +a 10174 14449 1 +a 10175 14448 1 +a 10176 14447 1 +a 10177 14446 1 +a 10178 14445 1 +a 10179 14444 1 +a 10180 14443 1 +a 10181 14442 1 +a 10182 14441 1 +a 10183 14440 1 +a 10184 14439 1 +a 10185 14438 1 +a 10186 14437 1 +a 10187 14436 1 +a 10188 14435 1 +a 10189 14434 1 +a 10190 14433 1 +a 10191 14432 1 +a 10192 14431 1 +a 10193 14430 1 +a 10194 14429 1 +a 10195 14428 1 +a 10196 14427 1 +a 10197 14426 1 +a 10198 14425 1 +a 10199 14424 1 +a 10200 14423 1 +a 10201 14422 1 +a 10202 14421 1 +a 10203 14420 1 +a 10204 14419 1 +a 10205 14418 1 +a 10206 14417 1 +a 10207 14416 1 +a 10208 14415 1 +a 10209 14414 1 +a 10210 14413 1 +a 10211 14412 1 +a 10212 14411 1 +a 10213 14410 1 +a 10214 14409 1 +a 10215 14408 1 +a 10216 14407 1 +a 10217 14406 1 +a 10218 14405 1 +a 10219 14404 1 +a 10220 14403 1 +a 10221 14402 1 +a 10222 14401 1 +a 10223 14400 1 +a 10224 14399 1 +a 10225 14398 1 +a 10226 14397 1 +a 10227 14396 1 +a 10228 14395 1 +a 10229 14394 1 +a 10230 14393 1 +a 10231 14392 1 +a 10232 14391 1 +a 10233 14390 1 +a 10234 14389 1 +a 10235 14388 1 +a 10236 14387 1 +a 10237 14386 1 +a 10238 14385 1 +a 10239 14384 1 +a 10240 14383 1 +a 10241 14382 1 +a 10242 14381 1 +a 10243 14380 1 +a 10244 14379 1 +a 10245 14378 1 +a 10246 14377 1 +a 10247 14376 1 +a 10248 14375 1 +a 10249 14374 1 +a 10250 14373 1 +a 10251 14372 1 +a 10252 14371 1 +a 10253 14370 1 +a 10254 14369 1 +a 10255 14368 1 +a 10256 14367 1 +a 10257 14366 1 +a 10258 14365 1 +a 10259 14364 1 +a 10260 14363 1 +a 10261 14362 1 +a 10262 14361 1 +a 10263 14360 1 +a 10264 14359 1 +a 10265 14358 1 +a 10266 14357 1 +a 10267 14356 1 +a 10268 14355 1 +a 10269 14354 1 +a 10270 14353 1 +a 10271 14352 1 +a 10272 14351 1 +a 10273 14350 1 +a 10274 14349 1 +a 10275 14348 1 +a 10276 14347 1 +a 10277 14346 1 +a 10278 14345 1 +a 10279 14344 1 +a 10280 14343 1 +a 10281 14342 1 +a 10282 14341 1 +a 10283 14340 1 +a 10284 14339 1 +a 10285 14338 1 +a 10286 14337 1 +a 10287 14336 1 +a 10288 14335 1 +a 10289 14334 1 +a 10290 14333 1 +a 10291 14332 1 +a 10292 14331 1 +a 10293 14330 1 +a 10294 14329 1 +a 10295 14328 1 +a 10296 14327 1 +a 10297 14326 1 +a 10298 14325 1 +a 10299 14324 1 +a 10300 14323 1 +a 10301 14322 1 +a 10302 14321 1 +a 10303 14320 1 +a 10304 14319 1 +a 10305 14318 1 +a 10306 14317 1 +a 10307 14316 1 +a 10308 14315 1 +a 10309 14314 1 +a 10310 14313 1 +a 10311 14312 1 +a 10312 14311 1 +a 10313 14310 1 +a 10314 14309 1 +a 10315 14308 1 +a 10316 14307 1 +a 10317 14306 1 +a 10318 14305 1 +a 10319 14304 1 +a 10320 14303 1 +a 10321 14302 1 +a 10322 14301 1 +a 10323 14300 1 +a 10324 14299 1 +a 10325 14298 1 +a 10326 14297 1 +a 10327 14296 1 +a 10328 14295 1 +a 10329 14294 1 +a 10330 14293 1 +a 10331 14292 1 +a 10332 14291 1 +a 10333 14290 1 +a 10334 14289 1 +a 10335 14288 1 +a 10336 14287 1 +a 10337 14286 1 +a 10338 14285 1 +a 10339 14284 1 +a 10340 14283 1 +a 10341 14282 1 +a 10342 14281 1 +a 10343 14280 1 +a 10344 14279 1 +a 10345 14278 1 +a 10346 14277 1 +a 10347 14276 1 +a 10348 14275 1 +a 10349 14274 1 +a 10350 14273 1 +a 10351 14272 1 +a 10352 14271 1 +a 10353 14270 1 +a 10354 14269 1 +a 10355 14268 1 +a 10356 14267 1 +a 10357 14266 1 +a 10358 14265 1 +a 10359 14264 1 +a 10360 14263 1 +a 10361 14262 1 +a 10362 14261 1 +a 10363 14260 1 +a 10364 14259 1 +a 10365 14258 1 +a 10366 14257 1 +a 10367 14256 1 +a 10368 14255 1 +a 10369 14254 1 +a 10370 14253 1 +a 10371 14252 1 +a 10372 14251 1 +a 10373 14250 1 +a 10374 14249 1 +a 10375 14248 1 +a 10376 14247 1 +a 10377 14246 1 +a 10378 14245 1 +a 10379 14244 1 +a 10380 14243 1 +a 10381 14242 1 +a 10382 14241 1 +a 10383 14240 1 +a 10384 14239 1 +a 10385 14238 1 +a 10386 14237 1 +a 10387 14236 1 +a 10388 14235 1 +a 10389 14234 1 +a 10390 14233 1 +a 10391 14232 1 +a 10392 14231 1 +a 10393 14230 1 +a 10394 14229 1 +a 10395 14228 1 +a 10396 14227 1 +a 10397 14226 1 +a 10398 14225 1 +a 10399 14224 1 +a 10400 14223 1 +a 10401 14222 1 +a 10402 14221 1 +a 10403 14220 1 +a 10404 14219 1 +a 10405 14218 1 +a 10406 14217 1 +a 10407 14216 1 +a 10408 14215 1 +a 10409 14214 1 +a 10410 14213 1 +a 10411 14212 1 +a 10412 14211 1 +a 10413 14210 1 +a 10414 14209 1 +a 10415 14208 1 +a 10416 14207 1 +a 10417 14206 1 +a 10418 14205 1 +a 10419 14204 1 +a 10420 14203 1 +a 10421 14202 1 +a 10422 14201 1 +a 10423 14200 1 +a 10424 14199 1 +a 10425 14198 1 +a 10426 14197 1 +a 10427 14196 1 +a 10428 14195 1 +a 10429 14194 1 +a 10430 14193 1 +a 10431 14192 1 +a 10432 14191 1 +a 10433 14190 1 +a 10434 14189 1 +a 10435 14188 1 +a 10436 14187 1 +a 10437 14186 1 +a 10438 14185 1 +a 10439 14184 1 +a 10440 14183 1 +a 10441 14182 1 +a 10442 14181 1 +a 10443 14180 1 +a 10444 14179 1 +a 10445 14178 1 +a 10446 14177 1 +a 10447 14176 1 +a 10448 14175 1 +a 10449 14174 1 +a 10450 14173 1 +a 10451 14172 1 +a 10452 14171 1 +a 10453 14170 1 +a 10454 14169 1 +a 10455 14168 1 +a 10456 14167 1 +a 10457 14166 1 +a 10458 14165 1 +a 10459 14164 1 +a 10460 14163 1 +a 10461 14162 1 +a 10462 14161 1 +a 10463 14160 1 +a 10464 14159 1 +a 10465 14158 1 +a 10466 14157 1 +a 10467 14156 1 +a 10468 14155 1 +a 10469 14154 1 +a 10470 14153 1 +a 10471 14152 1 +a 10472 14151 1 +a 10473 14150 1 +a 10474 14149 1 +a 10475 14148 1 +a 10476 14147 1 +a 10477 14146 1 +a 10478 14145 1 +a 10479 14144 1 +a 10480 14143 1 +a 10481 14142 1 +a 10482 14141 1 +a 10483 14140 1 +a 10484 14139 1 +a 10485 14138 1 +a 10486 14137 1 +a 10487 14136 1 +a 10488 14135 1 +a 10489 14134 1 +a 10490 14133 1 +a 10491 14132 1 +a 10492 14131 1 +a 10493 14130 1 +a 10494 14129 1 +a 10495 14128 1 +a 10496 14127 1 +a 10497 14126 1 +a 10498 14125 1 +a 10499 14124 1 +a 10500 14123 1 +a 10501 14122 1 +a 10502 14121 1 +a 10503 14120 1 +a 10504 14119 1 +a 10505 14118 1 +a 10506 14117 1 +a 10507 14116 1 +a 10508 14115 1 +a 10509 14114 1 +a 10510 14113 1 +a 10511 14112 1 +a 10512 14111 1 +a 10513 14110 1 +a 10514 14109 1 +a 10515 14108 1 +a 10516 14107 1 +a 10517 14106 1 +a 10518 14105 1 +a 10519 14104 1 +a 10520 14103 1 +a 10521 14102 1 +a 10522 14101 1 +a 10523 14100 1 +a 10524 14099 1 +a 10525 14098 1 +a 10526 14097 1 +a 10527 14096 1 +a 10528 14095 1 +a 10529 14094 1 +a 10530 14093 1 +a 10531 14092 1 +a 10532 14091 1 +a 10533 14090 1 +a 10534 14089 1 +a 10535 14088 1 +a 10536 14087 1 +a 10537 14086 1 +a 10538 14085 1 +a 10539 14084 1 +a 10540 14083 1 +a 10541 14082 1 +a 10542 14081 1 +a 10543 14080 1 +a 10544 14079 1 +a 10545 14078 1 +a 10546 14077 1 +a 10547 14076 1 +a 10548 14075 1 +a 10549 14074 1 +a 10550 14073 1 +a 10551 14072 1 +a 10552 14071 1 +a 10553 14070 1 +a 10554 14069 1 +a 10555 14068 1 +a 10556 14067 1 +a 10557 14066 1 +a 10558 14065 1 +a 10559 14064 1 +a 10560 14063 1 +a 10561 14062 1 +a 10562 14061 1 +a 10563 14060 1 +a 10564 14059 1 +a 10565 14058 1 +a 10566 14057 1 +a 10567 14056 1 +a 10568 14055 1 +a 10569 14054 1 +a 10570 14053 1 +a 10571 14052 1 +a 10572 14051 1 +a 10573 14050 1 +a 10574 14049 1 +a 10575 14048 1 +a 10576 14047 1 +a 10577 14046 1 +a 10578 14045 1 +a 10579 14044 1 +a 10580 14043 1 +a 10581 14042 1 +a 10582 14041 1 +a 10583 14040 1 +a 10584 14039 1 +a 10585 14038 1 +a 10586 14037 1 +a 10587 14036 1 +a 10588 14035 1 +a 10589 14034 1 +a 10590 14033 1 +a 10591 14032 1 +a 10592 14031 1 +a 10593 14030 1 +a 10594 14029 1 +a 10595 14028 1 +a 10596 14027 1 +a 10597 14026 1 +a 10598 14025 1 +a 10599 14024 1 +a 10600 14023 1 +a 10601 14022 1 +a 10602 14021 1 +a 10603 14020 1 +a 10604 14019 1 +a 10605 14018 1 +a 10606 14017 1 +a 10607 14016 1 +a 10608 14015 1 +a 10609 14014 1 +a 10610 14013 1 +a 10611 14012 1 +a 10612 14011 1 +a 10613 14010 1 +a 10614 14009 1 +a 10615 14008 1 +a 10616 14007 1 +a 10617 14006 1 +a 10618 14005 1 +a 10619 14004 1 +a 10620 14003 1 +a 10621 14002 1 +a 10622 14001 1 +a 10623 14000 1 +a 10624 13999 1 +a 10625 13998 1 +a 10626 13997 1 +a 10627 13996 1 +a 10628 13995 1 +a 10629 13994 1 +a 10630 13993 1 +a 10631 13992 1 +a 10632 13991 1 +a 10633 13990 1 +a 10634 13989 1 +a 10635 13988 1 +a 10636 13987 1 +a 10637 13986 1 +a 10638 13985 1 +a 10639 13984 1 +a 10640 13983 1 +a 10641 13982 1 +a 10642 13981 1 +a 10643 13980 1 +a 10644 13979 1 +a 10645 13978 1 +a 10646 13977 1 +a 10647 13976 1 +a 10648 13975 1 +a 10649 13974 1 +a 10650 13973 1 +a 10651 13972 1 +a 10652 13971 1 +a 10653 13970 1 +a 10654 13969 1 +a 10655 13968 1 +a 10656 13967 1 +a 10657 13966 1 +a 10658 13965 1 +a 10659 13964 1 +a 10660 13963 1 +a 10661 13962 1 +a 10662 13961 1 +a 10663 13960 1 +a 10664 13959 1 +a 10665 13958 1 +a 10666 13957 1 +a 10667 13956 1 +a 10668 13955 1 +a 10669 13954 1 +a 10670 13953 1 +a 10671 13952 1 +a 10672 13951 1 +a 10673 13950 1 +a 10674 13949 1 +a 10675 13948 1 +a 10676 13947 1 +a 10677 13946 1 +a 10678 13945 1 +a 10679 13944 1 +a 10680 13943 1 +a 10681 13942 1 +a 10682 13941 1 +a 10683 13940 1 +a 10684 13939 1 +a 10685 13938 1 +a 10686 13937 1 +a 10687 13936 1 +a 10688 13935 1 +a 10689 13934 1 +a 10690 13933 1 +a 10691 13932 1 +a 10692 13931 1 +a 10693 13930 1 +a 10694 13929 1 +a 10695 13928 1 +a 10696 13927 1 +a 10697 13926 1 +a 10698 13925 1 +a 10699 13924 1 +a 10700 13923 1 +a 10701 13922 1 +a 10702 13921 1 +a 10703 13920 1 +a 10704 13919 1 +a 10705 13918 1 +a 10706 13917 1 +a 10707 13916 1 +a 10708 13915 1 +a 10709 13914 1 +a 10710 13913 1 +a 10711 13912 1 +a 10712 13911 1 +a 10713 13910 1 +a 10714 13909 1 +a 10715 13908 1 +a 10716 13907 1 +a 10717 13906 1 +a 10718 13905 1 +a 10719 13904 1 +a 10720 13903 1 +a 10721 13902 1 +a 10722 13901 1 +a 10723 13900 1 +a 10724 13899 1 +a 10725 13898 1 +a 10726 13897 1 +a 10727 13896 1 +a 10728 13895 1 +a 10729 13894 1 +a 10730 13893 1 +a 10731 13892 1 +a 10732 13891 1 +a 10733 13890 1 +a 10734 13889 1 +a 10735 13888 1 +a 10736 13887 1 +a 10737 13886 1 +a 10738 13885 1 +a 10739 13884 1 +a 10740 13883 1 +a 10741 13882 1 +a 10742 13881 1 +a 10743 13880 1 +a 10744 13879 1 +a 10745 13878 1 +a 10746 13877 1 +a 10747 13876 1 +a 10748 13875 1 +a 10749 13874 1 +a 10750 13873 1 +a 10751 13872 1 +a 10752 13871 1 +a 10753 13870 1 +a 10754 13869 1 +a 10755 13868 1 +a 10756 13867 1 +a 10757 13866 1 +a 10758 13865 1 +a 10759 13864 1 +a 10760 13863 1 +a 10761 13862 1 +a 10762 13861 1 +a 10763 13860 1 +a 10764 13859 1 +a 10765 13858 1 +a 10766 13857 1 +a 10767 13856 1 +a 10768 13855 1 +a 10769 13854 1 +a 10770 13853 1 +a 10771 13852 1 +a 10772 13851 1 +a 10773 13850 1 +a 10774 13849 1 +a 10775 13848 1 +a 10776 13847 1 +a 10777 13846 1 +a 10778 13845 1 +a 10779 13844 1 +a 10780 13843 1 +a 10781 13842 1 +a 10782 13841 1 +a 10783 13840 1 +a 10784 13839 1 +a 10785 13838 1 +a 10786 13837 1 +a 10787 13836 1 +a 10788 13835 1 +a 10789 13834 1 +a 10790 13833 1 +a 10791 13832 1 +a 10792 13831 1 +a 10793 13830 1 +a 10794 13829 1 +a 10795 13828 1 +a 10796 13827 1 +a 10797 13826 1 +a 10798 13825 1 +a 10799 13824 1 +a 10800 13823 1 +a 10801 13822 1 +a 10802 13821 1 +a 10803 13820 1 +a 10804 13819 1 +a 10805 13818 1 +a 10806 13817 1 +a 10807 13816 1 +a 10808 13815 1 +a 10809 13814 1 +a 10810 13813 1 +a 10811 13812 1 +a 10812 13811 1 +a 10813 13810 1 +a 10814 13809 1 +a 10815 13808 1 +a 10816 13807 1 +a 10817 13806 1 +a 10818 13805 1 +a 10819 13804 1 +a 10820 13803 1 +a 10821 13802 1 +a 10822 13801 1 +a 10823 13800 1 +a 10824 13799 1 +a 10825 13798 1 +a 10826 13797 1 +a 10827 13796 1 +a 10828 13795 1 +a 10829 13794 1 +a 10830 13793 1 +a 10831 13792 1 +a 10832 13791 1 +a 10833 13790 1 +a 10834 13789 1 +a 10835 13788 1 +a 10836 13787 1 +a 10837 13786 1 +a 10838 13785 1 +a 10839 13784 1 +a 10840 13783 1 +a 10841 13782 1 +a 10842 13781 1 +a 10843 13780 1 +a 10844 13779 1 +a 10845 13778 1 +a 10846 13777 1 +a 10847 13776 1 +a 10848 13775 1 +a 10849 13774 1 +a 10850 13773 1 +a 10851 13772 1 +a 10852 13771 1 +a 10853 13770 1 +a 10854 13769 1 +a 10855 13768 1 +a 10856 13767 1 +a 10857 13766 1 +a 10858 13765 1 +a 10859 13764 1 +a 10860 13763 1 +a 10861 13762 1 +a 10862 13761 1 +a 10863 13760 1 +a 10864 13759 1 +a 10865 13758 1 +a 10866 13757 1 +a 10867 13756 1 +a 10868 13755 1 +a 10869 13754 1 +a 10870 13753 1 +a 10871 13752 1 +a 10872 13751 1 +a 10873 13750 1 +a 10874 13749 1 +a 10875 13748 1 +a 10876 13747 1 +a 10877 13746 1 +a 10878 13745 1 +a 10879 13744 1 +a 10880 13743 1 +a 10881 13742 1 +a 10882 13741 1 +a 10883 13740 1 +a 10884 13739 1 +a 10885 13738 1 +a 10886 13737 1 +a 10887 13736 1 +a 10888 13735 1 +a 10889 13734 1 +a 10890 13733 1 +a 10891 13732 1 +a 10892 13731 1 +a 10893 13730 1 +a 10894 13729 1 +a 10895 13728 1 +a 10896 13727 1 +a 10897 13726 1 +a 10898 13725 1 +a 10899 13724 1 +a 10900 13723 1 +a 10901 13722 1 +a 10902 13721 1 +a 10903 13720 1 +a 10904 13719 1 +a 10905 13718 1 +a 10906 13717 1 +a 10907 13716 1 +a 10908 13715 1 +a 10909 13714 1 +a 10910 13713 1 +a 10911 13712 1 +a 10912 13711 1 +a 10913 13710 1 +a 10914 13709 1 +a 10915 13708 1 +a 10916 13707 1 +a 10917 13706 1 +a 10918 13705 1 +a 10919 13704 1 +a 10920 13703 1 +a 10921 13702 1 +a 10922 13701 1 +a 10923 13700 1 +a 10924 13699 1 +a 10925 13698 1 +a 10926 13697 1 +a 10927 13696 1 +a 10928 13695 1 +a 10929 13694 1 +a 10930 13693 1 +a 10931 13692 1 +a 10932 13691 1 +a 10933 13690 1 +a 10934 13689 1 +a 10935 13688 1 +a 10936 13687 1 +a 10937 13686 1 +a 10938 13685 1 +a 10939 13684 1 +a 10940 13683 1 +a 10941 13682 1 +a 10942 13681 1 +a 10943 13680 1 +a 10944 13679 1 +a 10945 13678 1 +a 10946 13677 1 +a 10947 13676 1 +a 10948 13675 1 +a 10949 13674 1 +a 10950 13673 1 +a 10951 13672 1 +a 10952 13671 1 +a 10953 13670 1 +a 10954 13669 1 +a 10955 13668 1 +a 10956 13667 1 +a 10957 13666 1 +a 10958 13665 1 +a 10959 13664 1 +a 10960 13663 1 +a 10961 13662 1 +a 10962 13661 1 +a 10963 13660 1 +a 10964 13659 1 +a 10965 13658 1 +a 10966 13657 1 +a 10967 13656 1 +a 10968 13655 1 +a 10969 13654 1 +a 10970 13653 1 +a 10971 13652 1 +a 10972 13651 1 +a 10973 13650 1 +a 10974 13649 1 +a 10975 13648 1 +a 10976 13647 1 +a 10977 13646 1 +a 10978 13645 1 +a 10979 13644 1 +a 10980 13643 1 +a 10981 13642 1 +a 10982 13641 1 +a 10983 13640 1 +a 10984 13639 1 +a 10985 13638 1 +a 10986 13637 1 +a 10987 13636 1 +a 10988 13635 1 +a 10989 13634 1 +a 10990 13633 1 +a 10991 13632 1 +a 10992 13631 1 +a 10993 13630 1 +a 10994 13629 1 +a 10995 13628 1 +a 10996 13627 1 +a 10997 13626 1 +a 10998 13625 1 +a 10999 13624 1 +a 11000 13623 1 +a 11001 13622 1 +a 11002 13621 1 +a 11003 13620 1 +a 11004 13619 1 +a 11005 13618 1 +a 11006 13617 1 +a 11007 13616 1 +a 11008 13615 1 +a 11009 13614 1 +a 11010 13613 1 +a 11011 13612 1 +a 11012 13611 1 +a 11013 13610 1 +a 11014 13609 1 +a 11015 13608 1 +a 11016 13607 1 +a 11017 13606 1 +a 11018 13605 1 +a 11019 13604 1 +a 11020 13603 1 +a 11021 13602 1 +a 11022 13601 1 +a 11023 13600 1 +a 11024 13599 1 +a 11025 13598 1 +a 11026 13597 1 +a 11027 13596 1 +a 11028 13595 1 +a 11029 13594 1 +a 11030 13593 1 +a 11031 13592 1 +a 11032 13591 1 +a 11033 13590 1 +a 11034 13589 1 +a 11035 13588 1 +a 11036 13587 1 +a 11037 13586 1 +a 11038 13585 1 +a 11039 13584 1 +a 11040 13583 1 +a 11041 13582 1 +a 11042 13581 1 +a 11043 13580 1 +a 11044 13579 1 +a 11045 13578 1 +a 11046 13577 1 +a 11047 13576 1 +a 11048 13575 1 +a 11049 13574 1 +a 11050 13573 1 +a 11051 13572 1 +a 11052 13571 1 +a 11053 13570 1 +a 11054 13569 1 +a 11055 13568 1 +a 11056 13567 1 +a 11057 13566 1 +a 11058 13565 1 +a 11059 13564 1 +a 11060 13563 1 +a 11061 13562 1 +a 11062 13561 1 +a 11063 13560 1 +a 11064 13559 1 +a 11065 13558 1 +a 11066 13557 1 +a 11067 13556 1 +a 11068 13555 1 +a 11069 13554 1 +a 11070 13553 1 +a 11071 13552 1 +a 11072 13551 1 +a 11073 13550 1 +a 11074 13549 1 +a 11075 13548 1 +a 11076 13547 1 +a 11077 13546 1 +a 11078 13545 1 +a 11079 13544 1 +a 11080 13543 1 +a 11081 13542 1 +a 11082 13541 1 +a 11083 13540 1 +a 11084 13539 1 +a 11085 13538 1 +a 11086 13537 1 +a 11087 13536 1 +a 11088 13535 1 +a 11089 13534 1 +a 11090 13533 1 +a 11091 13532 1 +a 11092 13531 1 +a 11093 13530 1 +a 11094 13529 1 +a 11095 13528 1 +a 11096 13527 1 +a 11097 13526 1 +a 11098 13525 1 +a 11099 13524 1 +a 11100 13523 1 +a 11101 13522 1 +a 11102 13521 1 +a 11103 13520 1 +a 11104 13519 1 +a 11105 13518 1 +a 11106 13517 1 +a 11107 13516 1 +a 11108 13515 1 +a 11109 13514 1 +a 11110 13513 1 +a 11111 13512 1 +a 11112 13511 1 +a 11113 13510 1 +a 11114 13509 1 +a 11115 13508 1 +a 11116 13507 1 +a 11117 13506 1 +a 11118 13505 1 +a 11119 13504 1 +a 11120 13503 1 +a 11121 13502 1 +a 11122 13501 1 +a 11123 13500 1 +a 11124 13499 1 +a 11125 13498 1 +a 11126 13497 1 +a 11127 13496 1 +a 11128 13495 1 +a 11129 13494 1 +a 11130 13493 1 +a 11131 13492 1 +a 11132 13491 1 +a 11133 13490 1 +a 11134 13489 1 +a 11135 13488 1 +a 11136 13487 1 +a 11137 13486 1 +a 11138 13485 1 +a 11139 13484 1 +a 11140 13483 1 +a 11141 13482 1 +a 11142 13481 1 +a 11143 13480 1 +a 11144 13479 1 +a 11145 13478 1 +a 11146 13477 1 +a 11147 13476 1 +a 11148 13475 1 +a 11149 13474 1 +a 11150 13473 1 +a 11151 13472 1 +a 11152 13471 1 +a 11153 13470 1 +a 11154 13469 1 +a 11155 13468 1 +a 11156 13467 1 +a 11157 13466 1 +a 11158 13465 1 +a 11159 13464 1 +a 11160 13463 1 +a 11161 13462 1 +a 11162 13461 1 +a 11163 13460 1 +a 11164 13459 1 +a 11165 13458 1 +a 11166 13457 1 +a 11167 13456 1 +a 11168 13455 1 +a 11169 13454 1 +a 11170 13453 1 +a 11171 13452 1 +a 11172 13451 1 +a 11173 13450 1 +a 11174 13449 1 +a 11175 13448 1 +a 11176 13447 1 +a 11177 13446 1 +a 11178 13445 1 +a 11179 13444 1 +a 11180 13443 1 +a 11181 13442 1 +a 11182 13441 1 +a 11183 13440 1 +a 11184 13439 1 +a 11185 13438 1 +a 11186 13437 1 +a 11187 13436 1 +a 11188 13435 1 +a 11189 13434 1 +a 11190 13433 1 +a 11191 13432 1 +a 11192 13431 1 +a 11193 13430 1 +a 11194 13429 1 +a 11195 13428 1 +a 11196 13427 1 +a 11197 13426 1 +a 11198 13425 1 +a 11199 13424 1 +a 11200 13423 1 +a 11201 13422 1 +a 11202 13421 1 +a 11203 13420 1 +a 11204 13419 1 +a 11205 13418 1 +a 11206 13417 1 +a 11207 13416 1 +a 11208 13415 1 +a 11209 13414 1 +a 11210 13413 1 +a 11211 13412 1 +a 11212 13411 1 +a 11213 13410 1 +a 11214 13409 1 +a 11215 13408 1 +a 11216 13407 1 +a 11217 13406 1 +a 11218 13405 1 +a 11219 13404 1 +a 11220 13403 1 +a 11221 13402 1 +a 11222 13401 1 +a 11223 13400 1 +a 11224 13399 1 +a 11225 13398 1 +a 11226 13397 1 +a 11227 13396 1 +a 11228 13395 1 +a 11229 13394 1 +a 11230 13393 1 +a 11231 13392 1 +a 11232 13391 1 +a 11233 13390 1 +a 11234 13389 1 +a 11235 13388 1 +a 11236 13387 1 +a 11237 13386 1 +a 11238 13385 1 +a 11239 13384 1 +a 11240 13383 1 +a 11241 13382 1 +a 11242 13381 1 +a 11243 13380 1 +a 11244 13379 1 +a 11245 13378 1 +a 11246 13377 1 +a 11247 13376 1 +a 11248 13375 1 +a 11249 13374 1 +a 11250 13373 1 +a 11251 13372 1 +a 11252 13371 1 +a 11253 13370 1 +a 11254 13369 1 +a 11255 13368 1 +a 11256 13367 1 +a 11257 13366 1 +a 11258 13365 1 +a 11259 13364 1 +a 11260 13363 1 +a 11261 13362 1 +a 11262 13361 1 +a 11263 13360 1 +a 11264 13359 1 +a 11265 13358 1 +a 11266 13357 1 +a 11267 13356 1 +a 11268 13355 1 +a 11269 13354 1 +a 11270 13353 1 +a 11271 13352 1 +a 11272 13351 1 +a 11273 13350 1 +a 11274 13349 1 +a 11275 13348 1 +a 11276 13347 1 +a 11277 13346 1 +a 11278 13345 1 +a 11279 13344 1 +a 11280 13343 1 +a 11281 13342 1 +a 11282 13341 1 +a 11283 13340 1 +a 11284 13339 1 +a 11285 13338 1 +a 11286 13337 1 +a 11287 13336 1 +a 11288 13335 1 +a 11289 13334 1 +a 11290 13333 1 +a 11291 13332 1 +a 11292 13331 1 +a 11293 13330 1 +a 11294 13329 1 +a 11295 13328 1 +a 11296 13327 1 +a 11297 13326 1 +a 11298 13325 1 +a 11299 13324 1 +a 11300 13323 1 +a 11301 13322 1 +a 11302 13321 1 +a 11303 13320 1 +a 11304 13319 1 +a 11305 13318 1 +a 11306 13317 1 +a 11307 13316 1 +a 11308 13315 1 +a 11309 13314 1 +a 11310 13313 1 +a 11311 13312 1 +a 11312 13311 1 +a 11313 13310 1 +a 11314 13309 1 +a 11315 13308 1 +a 11316 13307 1 +a 11317 13306 1 +a 11318 13305 1 +a 11319 13304 1 +a 11320 13303 1 +a 11321 13302 1 +a 11322 13301 1 +a 11323 13300 1 +a 11324 13299 1 +a 11325 13298 1 +a 11326 13297 1 +a 11327 13296 1 +a 11328 13295 1 +a 11329 13294 1 +a 11330 13293 1 +a 11331 13292 1 +a 11332 13291 1 +a 11333 13290 1 +a 11334 13289 1 +a 11335 13288 1 +a 11336 13287 1 +a 11337 13286 1 +a 11338 13285 1 +a 11339 13284 1 +a 11340 13283 1 +a 11341 13282 1 +a 11342 13281 1 +a 11343 13280 1 +a 11344 13279 1 +a 11345 13278 1 +a 11346 13277 1 +a 11347 13276 1 +a 11348 13275 1 +a 11349 13274 1 +a 11350 13273 1 +a 11351 13272 1 +a 11352 13271 1 +a 11353 13270 1 +a 11354 13269 1 +a 11355 13268 1 +a 11356 13267 1 +a 11357 13266 1 +a 11358 13265 1 +a 11359 13264 1 +a 11360 13263 1 +a 11361 13262 1 +a 11362 13261 1 +a 11363 13260 1 +a 11364 13259 1 +a 11365 13258 1 +a 11366 13257 1 +a 11367 13256 1 +a 11368 13255 1 +a 11369 13254 1 +a 11370 13253 1 +a 11371 13252 1 +a 11372 13251 1 +a 11373 13250 1 +a 11374 13249 1 +a 11375 13248 1 +a 11376 13247 1 +a 11377 13246 1 +a 11378 13245 1 +a 11379 13244 1 +a 11380 13243 1 +a 11381 13242 1 +a 11382 13241 1 +a 11383 13240 1 +a 11384 13239 1 +a 11385 13238 1 +a 11386 13237 1 +a 11387 13236 1 +a 11388 13235 1 +a 11389 13234 1 +a 11390 13233 1 +a 11391 13232 1 +a 11392 13231 1 +a 11393 13230 1 +a 11394 13229 1 +a 11395 13228 1 +a 11396 13227 1 +a 11397 13226 1 +a 11398 13225 1 +a 11399 13224 1 +a 11400 13223 1 +a 11401 13222 1 +a 11402 13221 1 +a 11403 13220 1 +a 11404 13219 1 +a 11405 13218 1 +a 11406 13217 1 +a 11407 13216 1 +a 11408 13215 1 +a 11409 13214 1 +a 11410 13213 1 +a 11411 13212 1 +a 11412 13211 1 +a 11413 13210 1 +a 11414 13209 1 +a 11415 13208 1 +a 11416 13207 1 +a 11417 13206 1 +a 11418 13205 1 +a 11419 13204 1 +a 11420 13203 1 +a 11421 13202 1 +a 11422 13201 1 +a 11423 13200 1 +a 11424 13199 1 +a 11425 13198 1 +a 11426 13197 1 +a 11427 13196 1 +a 11428 13195 1 +a 11429 13194 1 +a 11430 13193 1 +a 11431 13192 1 +a 11432 13191 1 +a 11433 13190 1 +a 11434 13189 1 +a 11435 13188 1 +a 11436 13187 1 +a 11437 13186 1 +a 11438 13185 1 +a 11439 13184 1 +a 11440 13183 1 +a 11441 13182 1 +a 11442 13181 1 +a 11443 13180 1 +a 11444 13179 1 +a 11445 13178 1 +a 11446 13177 1 +a 11447 13176 1 +a 11448 13175 1 +a 11449 13174 1 +a 11450 13173 1 +a 11451 13172 1 +a 11452 13171 1 +a 11453 13170 1 +a 11454 13169 1 +a 11455 13168 1 +a 11456 13167 1 +a 11457 13166 1 +a 11458 13165 1 +a 11459 13164 1 +a 11460 13163 1 +a 11461 13162 1 +a 11462 13161 1 +a 11463 13160 1 +a 11464 13159 1 +a 11465 13158 1 +a 11466 13157 1 +a 11467 13156 1 +a 11468 13155 1 +a 11469 13154 1 +a 11470 13153 1 +a 11471 13152 1 +a 11472 13151 1 +a 11473 13150 1 +a 11474 13149 1 +a 11475 13148 1 +a 11476 13147 1 +a 11477 13146 1 +a 11478 13145 1 +a 11479 13144 1 +a 11480 13143 1 +a 11481 13142 1 +a 11482 13141 1 +a 11483 13140 1 +a 11484 13139 1 +a 11485 13138 1 +a 11486 13137 1 +a 11487 13136 1 +a 11488 13135 1 +a 11489 13134 1 +a 11490 13133 1 +a 11491 13132 1 +a 11492 13131 1 +a 11493 13130 1 +a 11494 13129 1 +a 11495 13128 1 +a 11496 13127 1 +a 11497 13126 1 +a 11498 13125 1 +a 11499 13124 1 +a 11500 13123 1 +a 11501 13122 1 +a 11502 13121 1 +a 11503 13120 1 +a 11504 13119 1 +a 11505 13118 1 +a 11506 13117 1 +a 11507 13116 1 +a 11508 13115 1 +a 11509 13114 1 +a 11510 13113 1 +a 11511 13112 1 +a 11512 13111 1 +a 11513 13110 1 +a 11514 13109 1 +a 11515 13108 1 +a 11516 13107 1 +a 11517 13106 1 +a 11518 13105 1 +a 11519 13104 1 +a 11520 13103 1 +a 11521 13102 1 +a 11522 13101 1 +a 11523 13100 1 +a 11524 13099 1 +a 11525 13098 1 +a 11526 13097 1 +a 11527 13096 1 +a 11528 13095 1 +a 11529 13094 1 +a 11530 13093 1 +a 11531 13092 1 +a 11532 13091 1 +a 11533 13090 1 +a 11534 13089 1 +a 11535 13088 1 +a 11536 13087 1 +a 11537 13086 1 +a 11538 13085 1 +a 11539 13084 1 +a 11540 13083 1 +a 11541 13082 1 +a 11542 13081 1 +a 11543 13080 1 +a 11544 13079 1 +a 11545 13078 1 +a 11546 13077 1 +a 11547 13076 1 +a 11548 13075 1 +a 11549 13074 1 +a 11550 13073 1 +a 11551 13072 1 +a 11552 13071 1 +a 11553 13070 1 +a 11554 13069 1 +a 11555 13068 1 +a 11556 13067 1 +a 11557 13066 1 +a 11558 13065 1 +a 11559 13064 1 +a 11560 13063 1 +a 11561 13062 1 +a 11562 13061 1 +a 11563 13060 1 +a 11564 13059 1 +a 11565 13058 1 +a 11566 13057 1 +a 11567 13056 1 +a 11568 13055 1 +a 11569 13054 1 +a 11570 13053 1 +a 11571 13052 1 +a 11572 13051 1 +a 11573 13050 1 +a 11574 13049 1 +a 11575 13048 1 +a 11576 13047 1 +a 11577 13046 1 +a 11578 13045 1 +a 11579 13044 1 +a 11580 13043 1 +a 11581 13042 1 +a 11582 13041 1 +a 11583 13040 1 +a 11584 13039 1 +a 11585 13038 1 +a 11586 13037 1 +a 11587 13036 1 +a 11588 13035 1 +a 11589 13034 1 +a 11590 13033 1 +a 11591 13032 1 +a 11592 13031 1 +a 11593 13030 1 +a 11594 13029 1 +a 11595 13028 1 +a 11596 13027 1 +a 11597 13026 1 +a 11598 13025 1 +a 11599 13024 1 +a 11600 13023 1 +a 11601 13022 1 +a 11602 13021 1 +a 11603 13020 1 +a 11604 13019 1 +a 11605 13018 1 +a 11606 13017 1 +a 11607 13016 1 +a 11608 13015 1 +a 11609 13014 1 +a 11610 13013 1 +a 11611 13012 1 +a 11612 13011 1 +a 11613 13010 1 +a 11614 13009 1 +a 11615 13008 1 +a 11616 13007 1 +a 11617 13006 1 +a 11618 13005 1 +a 11619 13004 1 +a 11620 13003 1 +a 11621 13002 1 +a 11622 13001 1 +a 11623 13000 1 +a 11624 12999 1 +a 11625 12998 1 +a 11626 12997 1 +a 11627 12996 1 +a 11628 12995 1 +a 11629 12994 1 +a 11630 12993 1 +a 11631 12992 1 +a 11632 12991 1 +a 11633 12990 1 +a 11634 12989 1 +a 11635 12988 1 +a 11636 12987 1 +a 11637 12986 1 +a 11638 12985 1 +a 11639 12984 1 +a 11640 12983 1 +a 11641 12982 1 +a 11642 12981 1 +a 11643 12980 1 +a 11644 12979 1 +a 11645 12978 1 +a 11646 12977 1 +a 11647 12976 1 +a 11648 12975 1 +a 11649 12974 1 +a 11650 12973 1 +a 11651 12972 1 +a 11652 12971 1 +a 11653 12970 1 +a 11654 12969 1 +a 11655 12968 1 +a 11656 12967 1 +a 11657 12966 1 +a 11658 12965 1 +a 11659 12964 1 +a 11660 12963 1 +a 11661 12962 1 +a 11662 12961 1 +a 11663 12960 1 +a 11664 12959 1 +a 11665 12958 1 +a 11666 12957 1 +a 11667 12956 1 +a 11668 12955 1 +a 11669 12954 1 +a 11670 12953 1 +a 11671 12952 1 +a 11672 12951 1 +a 11673 12950 1 +a 11674 12949 1 +a 11675 12948 1 +a 11676 12947 1 +a 11677 12946 1 +a 11678 12945 1 +a 11679 12944 1 +a 11680 12943 1 +a 11681 12942 1 +a 11682 12941 1 +a 11683 12940 1 +a 11684 12939 1 +a 11685 12938 1 +a 11686 12937 1 +a 11687 12936 1 +a 11688 12935 1 +a 11689 12934 1 +a 11690 12933 1 +a 11691 12932 1 +a 11692 12931 1 +a 11693 12930 1 +a 11694 12929 1 +a 11695 12928 1 +a 11696 12927 1 +a 11697 12926 1 +a 11698 12925 1 +a 11699 12924 1 +a 11700 12923 1 +a 11701 12922 1 +a 11702 12921 1 +a 11703 12920 1 +a 11704 12919 1 +a 11705 12918 1 +a 11706 12917 1 +a 11707 12916 1 +a 11708 12915 1 +a 11709 12914 1 +a 11710 12913 1 +a 11711 12912 1 +a 11712 12911 1 +a 11713 12910 1 +a 11714 12909 1 +a 11715 12908 1 +a 11716 12907 1 +a 11717 12906 1 +a 11718 12905 1 +a 11719 12904 1 +a 11720 12903 1 +a 11721 12902 1 +a 11722 12901 1 +a 11723 12900 1 +a 11724 12899 1 +a 11725 12898 1 +a 11726 12897 1 +a 11727 12896 1 +a 11728 12895 1 +a 11729 12894 1 +a 11730 12893 1 +a 11731 12892 1 +a 11732 12891 1 +a 11733 12890 1 +a 11734 12889 1 +a 11735 12888 1 +a 11736 12887 1 +a 11737 12886 1 +a 11738 12885 1 +a 11739 12884 1 +a 11740 12883 1 +a 11741 12882 1 +a 11742 12881 1 +a 11743 12880 1 +a 11744 12879 1 +a 11745 12878 1 +a 11746 12877 1 +a 11747 12876 1 +a 11748 12875 1 +a 11749 12874 1 +a 11750 12873 1 +a 11751 12872 1 +a 11752 12871 1 +a 11753 12870 1 +a 11754 12869 1 +a 11755 12868 1 +a 11756 12867 1 +a 11757 12866 1 +a 11758 12865 1 +a 11759 12864 1 +a 11760 12863 1 +a 11761 12862 1 +a 11762 12861 1 +a 11763 12860 1 +a 11764 12859 1 +a 11765 12858 1 +a 11766 12857 1 +a 11767 12856 1 +a 11768 12855 1 +a 11769 12854 1 +a 11770 12853 1 +a 11771 12852 1 +a 11772 12851 1 +a 11773 12850 1 +a 11774 12849 1 +a 11775 12848 1 +a 11776 12847 1 +a 11777 12846 1 +a 11778 12845 1 +a 11779 12844 1 +a 11780 12843 1 +a 11781 12842 1 +a 11782 12841 1 +a 11783 12840 1 +a 11784 12839 1 +a 11785 12838 1 +a 11786 12837 1 +a 11787 12836 1 +a 11788 12835 1 +a 11789 12834 1 +a 11790 12833 1 +a 11791 12832 1 +a 11792 12831 1 +a 11793 12830 1 +a 11794 12829 1 +a 11795 12828 1 +a 11796 12827 1 +a 11797 12826 1 +a 11798 12825 1 +a 11799 12824 1 +a 11800 12823 1 +a 11801 12822 1 +a 11802 12821 1 +a 11803 12820 1 +a 11804 12819 1 +a 11805 12818 1 +a 11806 12817 1 +a 11807 12816 1 +a 11808 12815 1 +a 11809 12814 1 +a 11810 12813 1 +a 11811 12812 1 +a 11812 12811 1 +a 11813 12810 1 +a 11814 12809 1 +a 11815 12808 1 +a 11816 12807 1 +a 11817 12806 1 +a 11818 12805 1 +a 11819 12804 1 +a 11820 12803 1 +a 11821 12802 1 +a 11822 12801 1 +a 11823 12800 1 +a 11824 12799 1 +a 11825 12798 1 +a 11826 12797 1 +a 11827 12796 1 +a 11828 12795 1 +a 11829 12794 1 +a 11830 12793 1 +a 11831 12792 1 +a 11832 12791 1 +a 11833 12790 1 +a 11834 12789 1 +a 11835 12788 1 +a 11836 12787 1 +a 11837 12786 1 +a 11838 12785 1 +a 11839 12784 1 +a 11840 12783 1 +a 11841 12782 1 +a 11842 12781 1 +a 11843 12780 1 +a 11844 12779 1 +a 11845 12778 1 +a 11846 12777 1 +a 11847 12776 1 +a 11848 12775 1 +a 11849 12774 1 +a 11850 12773 1 +a 11851 12772 1 +a 11852 12771 1 +a 11853 12770 1 +a 11854 12769 1 +a 11855 12768 1 +a 11856 12767 1 +a 11857 12766 1 +a 11858 12765 1 +a 11859 12764 1 +a 11860 12763 1 +a 11861 12762 1 +a 11862 12761 1 +a 11863 12760 1 +a 11864 12759 1 +a 11865 12758 1 +a 11866 12757 1 +a 11867 12756 1 +a 11868 12755 1 +a 11869 12754 1 +a 11870 12753 1 +a 11871 12752 1 +a 11872 12751 1 +a 11873 12750 1 +a 11874 12749 1 +a 11875 12748 1 +a 11876 12747 1 +a 11877 12746 1 +a 11878 12745 1 +a 11879 12744 1 +a 11880 12743 1 +a 11881 12742 1 +a 11882 12741 1 +a 11883 12740 1 +a 11884 12739 1 +a 11885 12738 1 +a 11886 12737 1 +a 11887 12736 1 +a 11888 12735 1 +a 11889 12734 1 +a 11890 12733 1 +a 11891 12732 1 +a 11892 12731 1 +a 11893 12730 1 +a 11894 12729 1 +a 11895 12728 1 +a 11896 12727 1 +a 11897 12726 1 +a 11898 12725 1 +a 11899 12724 1 +a 11900 12723 1 +a 11901 12722 1 +a 11902 12721 1 +a 11903 12720 1 +a 11904 12719 1 +a 11905 12718 1 +a 11906 12717 1 +a 11907 12716 1 +a 11908 12715 1 +a 11909 12714 1 +a 11910 12713 1 +a 11911 12712 1 +a 11912 12711 1 +a 11913 12710 1 +a 11914 12709 1 +a 11915 12708 1 +a 11916 12707 1 +a 11917 12706 1 +a 11918 12705 1 +a 11919 12704 1 +a 11920 12703 1 +a 11921 12702 1 +a 11922 12701 1 +a 11923 12700 1 +a 11924 12699 1 +a 11925 12698 1 +a 11926 12697 1 +a 11927 12696 1 +a 11928 12695 1 +a 11929 12694 1 +a 11930 12693 1 +a 11931 12692 1 +a 11932 12691 1 +a 11933 12690 1 +a 11934 12689 1 +a 11935 12688 1 +a 11936 12687 1 +a 11937 12686 1 +a 11938 12685 1 +a 11939 12684 1 +a 11940 12683 1 +a 11941 12682 1 +a 11942 12681 1 +a 11943 12680 1 +a 11944 12679 1 +a 11945 12678 1 +a 11946 12677 1 +a 11947 12676 1 +a 11948 12675 1 +a 11949 12674 1 +a 11950 12673 1 +a 11951 12672 1 +a 11952 12671 1 +a 11953 12670 1 +a 11954 12669 1 +a 11955 12668 1 +a 11956 12667 1 +a 11957 12666 1 +a 11958 12665 1 +a 11959 12664 1 +a 11960 12663 1 +a 11961 12662 1 +a 11962 12661 1 +a 11963 12660 1 +a 11964 12659 1 +a 11965 12658 1 +a 11966 12657 1 +a 11967 12656 1 +a 11968 12655 1 +a 11969 12654 1 +a 11970 12653 1 +a 11971 12652 1 +a 11972 12651 1 +a 11973 12650 1 +a 11974 12649 1 +a 11975 12648 1 +a 11976 12647 1 +a 11977 12646 1 +a 11978 12645 1 +a 11979 12644 1 +a 11980 12643 1 +a 11981 12642 1 +a 11982 12641 1 +a 11983 12640 1 +a 11984 12639 1 +a 11985 12638 1 +a 11986 12637 1 +a 11987 12636 1 +a 11988 12635 1 +a 11989 12634 1 +a 11990 12633 1 +a 11991 12632 1 +a 11992 12631 1 +a 11993 12630 1 +a 11994 12629 1 +a 11995 12628 1 +a 11996 12627 1 +a 11997 12626 1 +a 11998 12625 1 +a 11999 12624 1 +a 12000 12623 1 +a 12001 12622 1 +a 12002 12621 1 +a 12003 12620 1 +a 12004 12619 1 +a 12005 12618 1 +a 12006 12617 1 +a 12007 12616 1 +a 12008 12615 1 +a 12009 12614 1 +a 12010 12613 1 +a 12011 12612 1 +a 12012 12611 1 +a 12013 12610 1 +a 12014 12609 1 +a 12015 12608 1 +a 12016 12607 1 +a 12017 12606 1 +a 12018 12605 1 +a 12019 12604 1 +a 12020 12603 1 +a 12021 12602 1 +a 12022 12601 1 +a 12023 12600 1 +a 12024 12599 1 +a 12025 12598 1 +a 12026 12597 1 +a 12027 12596 1 +a 12028 12595 1 +a 12029 12594 1 +a 12030 12593 1 +a 12031 12592 1 +a 12032 12591 1 +a 12033 12590 1 +a 12034 12589 1 +a 12035 12588 1 +a 12036 12587 1 +a 12037 12586 1 +a 12038 12585 1 +a 12039 12584 1 +a 12040 12583 1 +a 12041 12582 1 +a 12042 12581 1 +a 12043 12580 1 +a 12044 12579 1 +a 12045 12578 1 +a 12046 12577 1 +a 12047 12576 1 +a 12048 12575 1 +a 12049 12574 1 +a 12050 12573 1 +a 12051 12572 1 +a 12052 12571 1 +a 12053 12570 1 +a 12054 12569 1 +a 12055 12568 1 +a 12056 12567 1 +a 12057 12566 1 +a 12058 12565 1 +a 12059 12564 1 +a 12060 12563 1 +a 12061 12562 1 +a 12062 12561 1 +a 12063 12560 1 +a 12064 12559 1 +a 12065 12558 1 +a 12066 12557 1 +a 12067 12556 1 +a 12068 12555 1 +a 12069 12554 1 +a 12070 12553 1 +a 12071 12552 1 +a 12072 12551 1 +a 12073 12550 1 +a 12074 12549 1 +a 12075 12548 1 +a 12076 12547 1 +a 12077 12546 1 +a 12078 12545 1 +a 12079 12544 1 +a 12080 12543 1 +a 12081 12542 1 +a 12082 12541 1 +a 12083 12540 1 +a 12084 12539 1 +a 12085 12538 1 +a 12086 12537 1 +a 12087 12536 1 +a 12088 12535 1 +a 12089 12534 1 +a 12090 12533 1 +a 12091 12532 1 +a 12092 12531 1 +a 12093 12530 1 +a 12094 12529 1 +a 12095 12528 1 +a 12096 12527 1 +a 12097 12526 1 +a 12098 12525 1 +a 12099 12524 1 +a 12100 12523 1 +a 12101 12522 1 +a 12102 12521 1 +a 12103 12520 1 +a 12104 12519 1 +a 12105 12518 1 +a 12106 12517 1 +a 12107 12516 1 +a 12108 12515 1 +a 12109 12514 1 +a 12110 12513 1 +a 12111 12512 1 +a 12112 12511 1 +a 12113 12510 1 +a 12114 12509 1 +a 12115 12508 1 +a 12116 12507 1 +a 12117 12506 1 +a 12118 12505 1 +a 12119 12504 1 +a 12120 12503 1 +a 12121 12502 1 +a 12122 12501 1 +a 12123 12500 1 +a 12124 12499 1 +a 12125 12498 1 +a 12126 12497 1 +a 12127 12496 1 +a 12128 12495 1 +a 12129 12494 1 +a 12130 12493 1 +a 12131 12492 1 +a 12132 12491 1 +a 12133 12490 1 +a 12134 12489 1 +a 12135 12488 1 +a 12136 12487 1 +a 12137 12486 1 +a 12138 12485 1 +a 12139 12484 1 +a 12140 12483 1 +a 12141 12482 1 +a 12142 12481 1 +a 12143 12480 1 +a 12144 12479 1 +a 12145 12478 1 +a 12146 12477 1 +a 12147 12476 1 +a 12148 12475 1 +a 12149 12474 1 +a 12150 12473 1 +a 12151 12472 1 +a 12152 12471 1 +a 12153 12470 1 +a 12154 12469 1 +a 12155 12468 1 +a 12156 12467 1 +a 12157 12466 1 +a 12158 12465 1 +a 12159 12464 1 +a 12160 12463 1 +a 12161 12462 1 +a 12162 12461 1 +a 12163 12460 1 +a 12164 12459 1 +a 12165 12458 1 +a 12166 12457 1 +a 12167 12456 1 +a 12168 12455 1 +a 12169 12454 1 +a 12170 12453 1 +a 12171 12452 1 +a 12172 12451 1 +a 12173 12450 1 +a 12174 12449 1 +a 12175 12448 1 +a 12176 12447 1 +a 12177 12446 1 +a 12178 12445 1 +a 12179 12444 1 +a 12180 12443 1 +a 12181 12442 1 +a 12182 12441 1 +a 12183 12440 1 +a 12184 12439 1 +a 12185 12438 1 +a 12186 12437 1 +a 12187 12436 1 +a 12188 12435 1 +a 12189 12434 1 +a 12190 12433 1 +a 12191 12432 1 +a 12192 12431 1 +a 12193 12430 1 +a 12194 12429 1 +a 12195 12428 1 +a 12196 12427 1 +a 12197 12426 1 +a 12198 12425 1 +a 12199 12424 1 +a 12200 12423 1 +a 12201 12422 1 +a 12202 12421 1 +a 12203 12420 1 +a 12204 12419 1 +a 12205 12418 1 +a 12206 12417 1 +a 12207 12416 1 +a 12208 12415 1 +a 12209 12414 1 +a 12210 12413 1 +a 12211 12412 1 +a 12212 12411 1 +a 12213 12410 1 +a 12214 12409 1 +a 12215 12408 1 +a 12216 12407 1 +a 12217 12406 1 +a 12218 12405 1 +a 12219 12404 1 +a 12220 12403 1 +a 12221 12402 1 +a 12222 12401 1 +a 12223 12400 1 +a 12224 12399 1 +a 12225 12398 1 +a 12226 12397 1 +a 12227 12396 1 +a 12228 12395 1 +a 12229 12394 1 +a 12230 12393 1 +a 12231 12392 1 +a 12232 12391 1 +a 12233 12390 1 +a 12234 12389 1 +a 12235 12388 1 +a 12236 12387 1 +a 12237 12386 1 +a 12238 12385 1 +a 12239 12384 1 +a 12240 12383 1 +a 12241 12382 1 +a 12242 12381 1 +a 12243 12380 1 +a 12244 12379 1 +a 12245 12378 1 +a 12246 12377 1 +a 12247 12376 1 +a 12248 12375 1 +a 12249 12374 1 +a 12250 12373 1 +a 12251 12372 1 +a 12252 12371 1 +a 12253 12370 1 +a 12254 12369 1 +a 12255 12368 1 +a 12256 12367 1 +a 12257 12366 1 +a 12258 12365 1 +a 12259 12364 1 +a 12260 12363 1 +a 12261 12362 1 +a 12262 12361 1 +a 12263 12360 1 +a 12264 12359 1 +a 12265 12358 1 +a 12266 12357 1 +a 12267 12356 1 +a 12268 12355 1 +a 12269 12354 1 +a 12270 12353 1 +a 12271 12352 1 +a 12272 12351 1 +a 12273 12350 1 +a 12274 12349 1 +a 12275 12348 1 +a 12276 12347 1 +a 12277 12346 1 +a 12278 12345 1 +a 12279 12344 1 +a 12280 12343 1 +a 12281 12342 1 +a 12282 12341 1 +a 12283 12340 1 +a 12284 12339 1 +a 12285 12338 1 +a 12286 12337 1 +a 12287 12336 1 +a 12288 12335 1 +a 12289 12334 1 +a 12290 12333 1 +a 12291 12332 1 +a 12292 12331 1 +a 12293 12330 1 +a 12294 12329 1 +a 12295 12328 1 +a 12296 12327 1 +a 12297 12326 1 +a 12298 12325 1 +a 12299 12324 1 +a 12300 12323 1 +a 12301 12322 1 +a 12302 12321 1 +a 12303 12320 1 +a 12304 12319 1 +a 12305 12318 1 +a 12306 12317 1 +a 12307 12316 1 +a 12308 12315 1 +a 12309 12314 1 +a 12310 12313 1 +a 1 3 1000000 +a 1 8209 1000000 +a 8208 2 1000000 +a 16414 2 1000000 diff --git a/examples/simple/bellman_ford.c b/examples/simple/bellman_ford.c new file mode 100644 index 0000000..46cda6b --- /dev/null +++ b/examples/simple/bellman_ford.c @@ -0,0 +1,94 @@ +/* + igraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_t weights; + igraph_real_t weights_data_0[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + igraph_real_t weights_data_1[] = { 6, 7, 8, -4, -2, -3, 9, 2, 7 }; + igraph_real_t weights_data_2[] = { 6, 7, 2, -4, -2, -3, 9, 2, 7 }; + igraph_matrix_t res; + + /* Initialize the library. */ + igraph_setup(); + + /* Graph with only positive weights */ + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + weights = igraph_vector_view(weights_data_0, + sizeof(weights_data_0) / sizeof(weights_data_0[0])); + + igraph_matrix_init(&res, 0, 0); + igraph_distances_bellman_ford(&g, &res, igraph_vss_all(), igraph_vss_all(), + &weights, IGRAPH_OUT); + igraph_matrix_print(&res); + + igraph_matrix_destroy(&res); + igraph_destroy(&g); + + printf("\n"); + + /***************************************/ + + /* Graph with negative weights */ + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 0, 3, 1, 3, 1, 4, 2, 1, 3, 2, 3, 4, 4, 0, 4, 2, -1); + + weights = igraph_vector_view(weights_data_1, + sizeof(weights_data_1) / sizeof(weights_data_1[0])); + + igraph_matrix_init(&res, 0, 0); + igraph_distances_bellman_ford(&g, &res, igraph_vss_all(), + igraph_vss_all(), &weights, IGRAPH_OUT); + igraph_matrix_print(&res); + + /***************************************/ + + /* Same graph with negative cycle */ + igraph_set_error_handler(igraph_error_handler_ignore); + weights = igraph_vector_view(weights_data_2, + sizeof(weights_data_2) / sizeof(weights_data_2[0])); + if (igraph_distances_bellman_ford(&g, &res, igraph_vss_all(), + igraph_vss_all(), + &weights, IGRAPH_OUT) != IGRAPH_ENEGCYCLE) { + return 1; + } + + igraph_matrix_destroy(&res); + igraph_destroy(&g); + + if (!IGRAPH_FINALLY_STACK_EMPTY) { + return 1; + } + + return 0; +} diff --git a/examples/simple/bellman_ford.out b/examples/simple/bellman_ford.out new file mode 100644 index 0000000..8711a3b --- /dev/null +++ b/examples/simple/bellman_ford.out @@ -0,0 +1,16 @@ + 0 0 0 1 5 2 1 13 3 5 +Inf 0 0 1 5 2 1 13 3 5 +Inf 1 0 1 6 3 1 14 4 6 +Inf 1 0 0 6 3 1 14 4 6 +Inf 5 4 5 0 2 3 8 3 5 +Inf 3 2 3 8 0 1 16 1 3 +Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf +Inf 4 3 4 9 1 2 0 1 4 +Inf Inf Inf Inf Inf Inf Inf Inf 0 4 +Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 + + 0 2 4 7 -2 +-2 0 2 5 -4 +-4 -2 0 3 -6 +-7 -5 -3 0 -9 + 2 4 6 9 0 diff --git a/examples/simple/blas.c b/examples/simple/blas.c new file mode 100644 index 0000000..fe73e52 --- /dev/null +++ b/examples/simple/blas.c @@ -0,0 +1,45 @@ + +#include + +int main(void) { + igraph_matrix_t m; + igraph_vector_t x, y, z; + igraph_real_t xz, xx; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_init_real(&x, 3, 1.0, 2.0, 3.0); + igraph_vector_init_real(&y, 4, 4.0, 5.0, 6.0, 7.0); + igraph_vector_init_real(&z, 3, -1.0, 0.0, 0.5); + + igraph_matrix_init(&m, 4, 3); + MATRIX(m, 0, 0) = 1; + MATRIX(m, 0, 1) = 2; + MATRIX(m, 0, 2) = 3; + MATRIX(m, 1, 0) = 2; + MATRIX(m, 1, 1) = 3; + MATRIX(m, 1, 2) = 4; + MATRIX(m, 2, 0) = 3; + MATRIX(m, 2, 1) = 4; + MATRIX(m, 2, 2) = 5; + MATRIX(m, 3, 0) = 4; + MATRIX(m, 3, 1) = 5; + MATRIX(m, 3, 2) = 6; + + /* Compute 2 m.x + 3 y and store it in y. */ + igraph_blas_dgemv(/* transpose= */ 0, /* alpha= */ 2, &m, &x, /* beta= */ 3, &y); + igraph_vector_print(&y); + + /* Compute the squared norm of x, as well as the dor product of x and z. */ + igraph_blas_ddot(&x, &x, &xx); + igraph_blas_ddot(&x, &z, &xz); + printf("x.x = %g, x.z = %g\n", xx, xz); + + igraph_matrix_destroy(&m); + igraph_vector_destroy(&z); + igraph_vector_destroy(&y); + igraph_vector_destroy(&x); + + return 0; +} diff --git a/examples/simple/blas.out b/examples/simple/blas.out new file mode 100644 index 0000000..345590b --- /dev/null +++ b/examples/simple/blas.out @@ -0,0 +1,2 @@ +40 55 70 85 +x.x = 14, x.z = 0.5 diff --git a/examples/simple/blas_dgemm.c b/examples/simple/blas_dgemm.c new file mode 100644 index 0000000..e3c6469 --- /dev/null +++ b/examples/simple/blas_dgemm.c @@ -0,0 +1,30 @@ +#include + +int main(void) { + igraph_matrix_t a, b, c; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&a, 2, 2); + MATRIX(a, 0, 0) = 1; + MATRIX(a, 0, 1) = 2; + MATRIX(a, 1, 0) = 3; + MATRIX(a, 1, 1) = 4; + + igraph_matrix_init(&b, 2, 2); + MATRIX(b, 0, 0) = 5; + MATRIX(b, 0, 1) = 6; + MATRIX(b, 1, 0) = 7; + MATRIX(b, 1, 1) = 8; + + igraph_matrix_init(&c, 2, 2); + + igraph_blas_dgemm(1, 1, 0.5, &a, &b, 0, &c); + igraph_matrix_printf(&c, "%g"); + + igraph_matrix_destroy(&a); + igraph_matrix_destroy(&b); + igraph_matrix_destroy(&c); + return 0; +} diff --git a/examples/simple/blas_dgemm.out b/examples/simple/blas_dgemm.out new file mode 100644 index 0000000..718f4a7 --- /dev/null +++ b/examples/simple/blas_dgemm.out @@ -0,0 +1,2 @@ +11.5 15.5 +17 23 diff --git a/examples/simple/cattributes.c b/examples/simple/cattributes.c new file mode 100644 index 0000000..b092206 --- /dev/null +++ b/examples/simple/cattributes.c @@ -0,0 +1,135 @@ +/* igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +/* Prints graph, vertex and edge attributes stored in a graph. */ +void print_attributes(const igraph_t *g) { + igraph_vector_int_t gtypes, vtypes, etypes; + igraph_strvector_t gnames, vnames, enames; + igraph_int_t i, j; + + igraph_vector_int_init(>ypes, 0); + igraph_vector_int_init(&vtypes, 0); + igraph_vector_int_init(&etypes, 0); + igraph_strvector_init(&gnames, 0); + igraph_strvector_init(&vnames, 0); + igraph_strvector_init(&enames, 0); + + igraph_cattribute_list(g, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes); + + /* graph attributes */ + for (i = 0; i < igraph_strvector_size(&gnames); i++) { + printf("%s=", igraph_strvector_get(&gnames, i)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(GAN(g, igraph_strvector_get(&gnames, i))); + putchar(' '); + } else { + printf("\"%s\" ", GAS(g, igraph_strvector_get(&gnames, i))); + } + } + printf("\n"); + + /* vertex attributes */ + for (i = 0; i < igraph_vcount(g); i++) { + printf("Vertex %" IGRAPH_PRId ": ", i); + for (j = 0; j < igraph_strvector_size(&vnames); j++) { + printf("%s=", igraph_strvector_get(&vnames, j)); + if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(VAN(g, igraph_strvector_get(&vnames, j), i)); + putchar(' '); + } else { + printf("\"%s\" ", VAS(g, igraph_strvector_get(&vnames, j), i)); + } + } + printf("\n"); + } + + /* edge attributes */ + for (i = 0; i < igraph_ecount(g); i++) { + printf("Edge %" IGRAPH_PRId " (%" IGRAPH_PRId "-%" IGRAPH_PRId "): ", i, IGRAPH_FROM(g, i), IGRAPH_TO(g, i)); + for (j = 0; j < igraph_strvector_size(&enames); j++) { + printf("%s=", igraph_strvector_get(&enames, j)); + if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(EAN(g, igraph_strvector_get(&enames, j), i)); + putchar(' '); + } else { + printf("\"%s\" ", EAS(g, igraph_strvector_get(&enames, j), i)); + } + } + printf("\n"); + } + + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + igraph_vector_int_destroy(&etypes); + igraph_vector_int_destroy(&vtypes); + igraph_vector_int_destroy(>ypes); +} + +int main(void) { + igraph_t graph; + igraph_vector_t y; + + /* Initialize the library. */ + igraph_setup(); + + /* Turn on attribute handling. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&graph, 3, IGRAPH_DIRECTED, 0,1, 1,2, -1); + + /* Set graph attributes. */ + /* numeric */ + SETGAN(&graph, "id", 10); + /* string */ + SETGAS(&graph, "name", "toy"); + /* boolean */ + SETGAB(&graph, "is_regular", false); + + /* Set edge string attribute. */ + SETEAS(&graph, "color", 1, "RED"); + + /* Set vertex attributes as vector. */ + igraph_vector_init(&y, igraph_vcount(&graph)); + igraph_vector_fill(&y, 1.23); + SETVANV(&graph, "y", &y); + igraph_vector_destroy(&y); + + /* Set single vertex numeric attribute. */ + SETVAN(&graph, "y", 0, -1); + + /* Delete graph attribute. */ + DELGA(&graph, "is_regular"); + + /* Print the final result. */ + print_attributes(&graph); + + /* Delete all remaining attributes. */ + DELALL(&graph); + + /* Destroy the graph. */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/cattributes.out b/examples/simple/cattributes.out new file mode 100644 index 0000000..623aa3a --- /dev/null +++ b/examples/simple/cattributes.out @@ -0,0 +1,6 @@ +id=10 name="toy" +Vertex 0: y=-1 +Vertex 1: y=1.23 +Vertex 2: y=1.23 +Edge 0 (0-1): color="" +Edge 1 (1-2): color="RED" diff --git a/examples/simple/cattributes2.c b/examples/simple/cattributes2.c new file mode 100644 index 0000000..c2ff4fa --- /dev/null +++ b/examples/simple/cattributes2.c @@ -0,0 +1,70 @@ +/* igraph library. + Copyright (C) 2007-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_t y; + igraph_warning_handler_t* oldwarnhandler; + + /* Initialize the library. */ + igraph_setup(); + + /* Turn on attribute handling. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Create a graph, add some attributes and save it as a GraphML file. */ + igraph_famous(&g, "Petersen"); + SETGAS(&g, "name", "Petersen's graph"); + SETGAN(&g, "vertices", igraph_vcount(&g)); + SETGAN(&g, "edges", igraph_ecount(&g)); + SETGAB(&g, "famous", true); + + igraph_vector_init_range(&y, 1, igraph_vcount(&g) + 1); + SETVANV(&g, "id", &y); + igraph_vector_destroy(&y); + + SETVAS(&g, "name", 0, "foo"); + SETVAS(&g, "name", 1, "foobar"); + + SETVAB(&g, "is_first", 0, true); + + igraph_vector_init_range(&y, 1, igraph_ecount(&g) + 1); + SETEANV(&g, "id", &y); + igraph_vector_destroy(&y); + + SETEAS(&g, "name", 0, "FOO"); + SETEAS(&g, "name", 1, "FOOBAR"); + + SETEAB(&g, "is_first", 0, true); + + /* Turn off the warning handler temporarily because the GML writer will + * print warnings about boolean attributes being converted to numbers, and + * we don't care about these. */ + oldwarnhandler = igraph_set_warning_handler(igraph_warning_handler_ignore); + igraph_write_graph_gml(&g, stdout, IGRAPH_WRITE_GML_DEFAULT_SW, 0, ""); + igraph_set_warning_handler(oldwarnhandler); + + /* Back to business. */ + igraph_write_graph_graphml(&g, stdout, /*prefixattr=*/ true); + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/cattributes2.out b/examples/simple/cattributes2.out new file mode 100644 index 0000000..f736f51 --- /dev/null +++ b/examples/simple/cattributes2.out @@ -0,0 +1,337 @@ +Version 1 +graph +[ + directed 0 + name "Petersen's graph" + vertices 10 + edges 15 + famous 1 + node + [ + id 1 + name "foo" + isfirst 1 + ] + node + [ + id 2 + name "foobar" + isfirst 0 + ] + node + [ + id 3 + name "" + isfirst 0 + ] + node + [ + id 4 + name "" + isfirst 0 + ] + node + [ + id 5 + name "" + isfirst 0 + ] + node + [ + id 6 + name "" + isfirst 0 + ] + node + [ + id 7 + name "" + isfirst 0 + ] + node + [ + id 8 + name "" + isfirst 0 + ] + node + [ + id 9 + name "" + isfirst 0 + ] + node + [ + id 10 + name "" + isfirst 0 + ] + edge + [ + source 2 + target 1 + id 1 + name "FOO" + isfirst 1 + ] + edge + [ + source 5 + target 1 + id 2 + name "FOOBAR" + isfirst 0 + ] + edge + [ + source 6 + target 1 + id 3 + name "" + isfirst 0 + ] + edge + [ + source 3 + target 2 + id 4 + name "" + isfirst 0 + ] + edge + [ + source 7 + target 2 + id 5 + name "" + isfirst 0 + ] + edge + [ + source 4 + target 3 + id 6 + name "" + isfirst 0 + ] + edge + [ + source 8 + target 3 + id 7 + name "" + isfirst 0 + ] + edge + [ + source 5 + target 4 + id 8 + name "" + isfirst 0 + ] + edge + [ + source 9 + target 4 + id 9 + name "" + isfirst 0 + ] + edge + [ + source 10 + target 5 + id 10 + name "" + isfirst 0 + ] + edge + [ + source 8 + target 6 + id 11 + name "" + isfirst 0 + ] + edge + [ + source 9 + target 6 + id 12 + name "" + isfirst 0 + ] + edge + [ + source 9 + target 7 + id 13 + name "" + isfirst 0 + ] + edge + [ + source 10 + target 7 + id 14 + name "" + isfirst 0 + ] + edge + [ + source 10 + target 8 + id 15 + name "" + isfirst 0 + ] +] + + + + + + + + + + + + + + + Petersen's graph + 10 + 15 + true + + 1 + foo + true + + + 2 + foobar + false + + + 3 + + false + + + 4 + + false + + + 5 + + false + + + 6 + + false + + + 7 + + false + + + 8 + + false + + + 9 + + false + + + 10 + + false + + + 1 + FOO + true + + + 2 + FOOBAR + false + + + 3 + + false + + + 4 + + false + + + 5 + + false + + + 6 + + false + + + 7 + + false + + + 8 + + false + + + 9 + + false + + + 10 + + false + + + 11 + + false + + + 12 + + false + + + 13 + + false + + + 14 + + false + + + 15 + + false + + + diff --git a/examples/simple/cattributes3.c b/examples/simple/cattributes3.c new file mode 100644 index 0000000..8186f99 --- /dev/null +++ b/examples/simple/cattributes3.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +igraph_error_t mf(const igraph_vector_t *input, igraph_real_t *output) { + *output = 0.0; + return IGRAPH_SUCCESS; +} + +static void simplify_write_destroy(igraph_t *g, igraph_attribute_combination_t *comb) { + igraph_simplify(g, /*remove_multiple=*/ true, /*remove_loops=*/ true, comb); + igraph_write_graph_graphml(g, stdout, /*prefixattr=*/ true); + igraph_attribute_combination_destroy(comb); + igraph_destroy(g); +} + +static void weight_test(igraph_t *g, igraph_attribute_combination_type_t weight_attr) { + igraph_t g2; + igraph_attribute_combination_t comb; + + igraph_copy(&g2, g); + igraph_attribute_combination(&comb, + "weight", weight_attr, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); +} + +int main(void) { + + igraph_t g, g2; + igraph_vector_t weight; + igraph_attribute_combination_t comb; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0, 1, 0, 1, 0, 1, + 1, 2, 2, 3, + -1); + + igraph_vector_init_range(&weight, 1, igraph_ecount(&g) + 1); + SETEANV(&g, "weight", &weight); + igraph_vector_destroy(&weight); + + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_SUM); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_PROD); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MIN); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MAX); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_FIRST); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_LAST); + weight_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MEAN); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_FUNCTION, mf, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "", IGRAPH_ATTRIBUTE_COMBINE_MEAN, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/cattributes3.out b/examples/simple/cattributes3.out new file mode 100644 index 0000000..de970eb --- /dev/null +++ b/examples/simple/cattributes3.out @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + 6 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 6 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 1 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 3 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 1 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 3 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 2 + + + 4 + + + 5 + + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + 2 + + + 4 + + + 5 + + + diff --git a/examples/simple/cattributes4.c b/examples/simple/cattributes4.c new file mode 100644 index 0000000..2db32a9 --- /dev/null +++ b/examples/simple/cattributes4.c @@ -0,0 +1,84 @@ +/* + igraph library. + Copyright (C) 2010-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +static void simplify_write_destroy(igraph_t *g, igraph_attribute_combination_t *comb) { + igraph_simplify(g, /*remove_multiple=*/ true, /*remove_loops=*/ true, comb); + igraph_write_graph_graphml(g, stdout, /*prefixattr=*/ true); + igraph_attribute_combination_destroy(comb); + igraph_destroy(g); +} + +int main(void) { + + igraph_t g, g2; + igraph_attribute_combination_t comb; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0, 1, 0, 1, 0, 1, + 1, 2, 2, 3, + -1); + + SETEAS(&g, "color", 0, "green"); + SETEAS(&g, "color", 1, "red"); + SETEAS(&g, "color", 2, "blue"); + SETEAS(&g, "color", 3, "white"); + SETEAS(&g, "color", 4, "black"); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + "color", IGRAPH_ATTRIBUTE_COMBINE_FIRST, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "", IGRAPH_ATTRIBUTE_COMBINE_LAST, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + "color", IGRAPH_ATTRIBUTE_COMBINE_CONCAT, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/cattributes4.out b/examples/simple/cattributes4.out new file mode 100644 index 0000000..bc3b76b --- /dev/null +++ b/examples/simple/cattributes4.out @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + green + + + white + + + black + + + + + + + + + + + + + + + + + + blue + + + white + + + black + + + + + + + + + + + + + + + + + + greenredblue + + + green + + + green + + + diff --git a/examples/simple/celegansneural.gml b/examples/simple/celegansneural.gml new file mode 100644 index 0000000..98697f4 --- /dev/null +++ b/examples/simple/celegansneural.gml @@ -0,0 +1,15644 @@ +Creator "Mark Newman on Thu Aug 31 12:59:09 2006" +graph +[ + directed 1 + node + [ + id 0 + label "1" + ] + node + [ + id 1 + label "51" + ] + node + [ + id 2 + label "72" + ] + node + [ + id 3 + label "77" + ] + node + [ + id 4 + label "78" + ] + node + [ + id 5 + label "2" + ] + node + [ + id 6 + label "90" + ] + node + [ + id 7 + label "92" + ] + node + [ + id 8 + label "158" + ] + node + [ + id 9 + label "159" + ] + node + [ + id 10 + label "113" + ] + node + [ + id 11 + label "69" + ] + node + [ + id 12 + label "71" + ] + node + [ + id 13 + label "89" + ] + node + [ + id 14 + label "91" + ] + node + [ + id 15 + label "3" + ] + node + [ + id 16 + label "47" + ] + node + [ + id 17 + label "9" + ] + node + [ + id 18 + label "17" + ] + node + [ + id 19 + label "21" + ] + node + [ + id 20 + label "93" + ] + node + [ + id 21 + label "94" + ] + node + [ + id 22 + label "23" + ] + node + [ + id 23 + label "121" + ] + node + [ + id 24 + label "125" + ] + node + [ + id 25 + label "131" + ] + node + [ + id 26 + label "31" + ] + node + [ + id 27 + label "4" + ] + node + [ + id 28 + label "60" + ] + node + [ + id 29 + label "10" + ] + node + [ + id 30 + label "16" + ] + node + [ + id 31 + label "18" + ] + node + [ + id 32 + label "22" + ] + node + [ + id 33 + label "24" + ] + node + [ + id 34 + label "97" + ] + node + [ + id 35 + label "122" + ] + node + [ + id 36 + label "126" + ] + node + [ + id 37 + label "132" + ] + node + [ + id 38 + label "32" + ] + node + [ + id 39 + label "303" + ] + node + [ + id 40 + label "5" + ] + node + [ + id 41 + label "7" + ] + node + [ + id 42 + label "222" + ] + node + [ + id 43 + label "101" + ] + node + [ + id 44 + label "305" + ] + node + [ + id 45 + label "6" + ] + node + [ + id 46 + label "102" + ] + node + [ + id 47 + label "99" + ] + node + [ + id 48 + label "100" + ] + node + [ + id 49 + label "27" + ] + node + [ + id 50 + label "8" + ] + node + [ + id 51 + label "26" + ] + node + [ + id 52 + label "44" + ] + node + [ + id 53 + label "37" + ] + node + [ + id 54 + label "11" + ] + node + [ + id 55 + label "19" + ] + node + [ + id 56 + label "29" + ] + node + [ + id 57 + label "12" + ] + node + [ + id 58 + label "41" + ] + node + [ + id 59 + label "118" + ] + node + [ + id 60 + label "25" + ] + node + [ + id 61 + label "30" + ] + node + [ + id 62 + label "13" + ] + node + [ + id 63 + label "143" + ] + node + [ + id 64 + label "28" + ] + node + [ + id 65 + label "43" + ] + node + [ + id 66 + label "14" + ] + node + [ + id 67 + label "144" + ] + node + [ + id 68 + label "20" + ] + node + [ + id 69 + label "34" + ] + node + [ + id 70 + label "15" + ] + node + [ + id 71 + label "40" + ] + node + [ + id 72 + label "128" + ] + node + [ + id 73 + label "139" + ] + node + [ + id 74 + label "140" + ] + node + [ + id 75 + label "108" + ] + node + [ + id 76 + label "35" + ] + node + [ + id 77 + label "107" + ] + node + [ + id 78 + label "133" + ] + node + [ + id 79 + label "134" + ] + node + [ + id 80 + label "105" + ] + node + [ + id 81 + label "106" + ] + node + [ + id 82 + label "36" + ] + node + [ + id 83 + label "33" + ] + node + [ + id 84 + label "73" + ] + node + [ + id 85 + label "136" + ] + node + [ + id 86 + label "74" + ] + node + [ + id 87 + label "161" + ] + node + [ + id 88 + label "129" + ] + node + [ + id 89 + label "135" + ] + node + [ + id 90 + label "120" + ] + node + [ + id 91 + label "38" + ] + node + [ + id 92 + label "39" + ] + node + [ + id 93 + label "160" + ] + node + [ + id 94 + label "130" + ] + node + [ + id 95 + label "174" + ] + node + [ + id 96 + label "42" + ] + node + [ + id 97 + label "189" + ] + node + [ + id 98 + label "82" + ] + node + [ + id 99 + label "70" + ] + node + [ + id 100 + label "45" + ] + node + [ + id 101 + label "141" + ] + node + [ + id 102 + label "55" + ] + node + [ + id 103 + label "119" + ] + node + [ + id 104 + label "137" + ] + node + [ + id 105 + label "46" + ] + node + [ + id 106 + label "142" + ] + node + [ + id 107 + label "114" + ] + node + [ + id 108 + label "56" + ] + node + [ + id 109 + label "62" + ] + node + [ + id 110 + label "86" + ] + node + [ + id 111 + label "193" + ] + node + [ + id 112 + label "138" + ] + node + [ + id 113 + label "109" + ] + node + [ + id 114 + label "52" + ] + node + [ + id 115 + label "58" + ] + node + [ + id 116 + label "61" + ] + node + [ + id 117 + label "75" + ] + node + [ + id 118 + label "76" + ] + node + [ + id 119 + label "81" + ] + node + [ + id 120 + label "85" + ] + node + [ + id 121 + label "48" + ] + node + [ + id 122 + label "110" + ] + node + [ + id 123 + label "80" + ] + node + [ + id 124 + label "88" + ] + node + [ + id 125 + label "216" + ] + node + [ + id 126 + label "49" + ] + node + [ + id 127 + label "54" + ] + node + [ + id 128 + label "50" + ] + node + [ + id 129 + label "154" + ] + node + [ + id 130 + label "96" + ] + node + [ + id 131 + label "127" + ] + node + [ + id 132 + label "95" + ] + node + [ + id 133 + label "166" + ] + node + [ + id 134 + label "53" + ] + node + [ + id 135 + label "57" + ] + node + [ + id 136 + label "63" + ] + node + [ + id 137 + label "198" + ] + node + [ + id 138 + label "87" + ] + node + [ + id 139 + label "84" + ] + node + [ + id 140 + label "59" + ] + node + [ + id 141 + label "67" + ] + node + [ + id 142 + label "178" + ] + node + [ + id 143 + label "64" + ] + node + [ + id 144 + label "65" + ] + node + [ + id 145 + label "220" + ] + node + [ + id 146 + label "66" + ] + node + [ + id 147 + label "68" + ] + node + [ + id 148 + label "221" + ] + node + [ + id 149 + label "111" + ] + node + [ + id 150 + label "112" + ] + node + [ + id 151 + label "146" + ] + node + [ + id 152 + label "225" + ] + node + [ + id 153 + label "186" + ] + node + [ + id 154 + label "226" + ] + node + [ + id 155 + label "227" + ] + node + [ + id 156 + label "228" + ] + node + [ + id 157 + label "229" + ] + node + [ + id 158 + label "230" + ] + node + [ + id 159 + label "150" + ] + node + [ + id 160 + label "234" + ] + node + [ + id 161 + label "235" + ] + node + [ + id 162 + label "236" + ] + node + [ + id 163 + label "237" + ] + node + [ + id 164 + label "238" + ] + node + [ + id 165 + label "239" + ] + node + [ + id 166 + label "187" + ] + node + [ + id 167 + label "188" + ] + node + [ + id 168 + label "240" + ] + node + [ + id 169 + label "242" + ] + node + [ + id 170 + label "203" + ] + node + [ + id 171 + label "179" + ] + node + [ + id 172 + label "217" + ] + node + [ + id 173 + label "162" + ] + node + [ + id 174 + label "164" + ] + node + [ + id 175 + label "249" + ] + node + [ + id 176 + label "250" + ] + node + [ + id 177 + label "195" + ] + node + [ + id 178 + label "251" + ] + node + [ + id 179 + label "252" + ] + node + [ + id 180 + label "253" + ] + node + [ + id 181 + label "254" + ] + node + [ + id 182 + label "255" + ] + node + [ + id 183 + label "197" + ] + node + [ + id 184 + label "204" + ] + node + [ + id 185 + label "180" + ] + node + [ + id 186 + label "163" + ] + node + [ + id 187 + label "117" + ] + node + [ + id 188 + label "256" + ] + node + [ + id 189 + label "276" + ] + node + [ + id 190 + label "306" + ] + node + [ + id 191 + label "177" + ] + node + [ + id 192 + label "241" + ] + node + [ + id 193 + label "200" + ] + node + [ + id 194 + label "215" + ] + node + [ + id 195 + label "199" + ] + node + [ + id 196 + label "165" + ] + node + [ + id 197 + label "169" + ] + node + [ + id 198 + label "79" + ] + node + [ + id 199 + label "148" + ] + node + [ + id 200 + label "168" + ] + node + [ + id 201 + label "145" + ] + node + [ + id 202 + label "147" + ] + node + [ + id 203 + label "192" + ] + node + [ + id 204 + label "219" + ] + node + [ + id 205 + label "157" + ] + node + [ + id 206 + label "172" + ] + node + [ + id 207 + label "218" + ] + node + [ + id 208 + label "83" + ] + node + [ + id 209 + label "98" + ] + node + [ + id 210 + label "124" + ] + node + [ + id 211 + label "103" + ] + node + [ + id 212 + label "104" + ] + node + [ + id 213 + label "115" + ] + node + [ + id 214 + label "123" + ] + node + [ + id 215 + label "156" + ] + node + [ + id 216 + label "170" + ] + node + [ + id 217 + label "183" + ] + node + [ + id 218 + label "116" + ] + node + [ + id 219 + label "153" + ] + node + [ + id 220 + label "224" + ] + node + [ + id 221 + label "196" + ] + node + [ + id 222 + label "214" + ] + node + [ + id 223 + label "213" + ] + node + [ + id 224 + label "155" + ] + node + [ + id 225 + label "173" + ] + node + [ + id 226 + label "149" + ] + node + [ + id 227 + label "275" + ] + node + [ + id 228 + label "205" + ] + node + [ + id 229 + label "206" + ] + node + [ + id 230 + label "151" + ] + node + [ + id 231 + label "277" + ] + node + [ + id 232 + label "152" + ] + node + [ + id 233 + label "245" + ] + node + [ + id 234 + label "278" + ] + node + [ + id 235 + label "201" + ] + node + [ + id 236 + label "202" + ] + node + [ + id 237 + label "167" + ] + node + [ + id 238 + label "269" + ] + node + [ + id 239 + label "184" + ] + node + [ + id 240 + label "171" + ] + node + [ + id 241 + label "185" + ] + node + [ + id 242 + label "175" + ] + node + [ + id 243 + label "176" + ] + node + [ + id 244 + label "272" + ] + node + [ + id 245 + label "270" + ] + node + [ + id 246 + label "258" + ] + node + [ + id 247 + label "181" + ] + node + [ + id 248 + label "182" + ] + node + [ + id 249 + label "274" + ] + node + [ + id 250 + label "190" + ] + node + [ + id 251 + label "191" + ] + node + [ + id 252 + label "194" + ] + node + [ + id 253 + label "244" + ] + node + [ + id 254 + label "260" + ] + node + [ + id 255 + label "207" + ] + node + [ + id 256 + label "223" + ] + node + [ + id 257 + label "208" + ] + node + [ + id 258 + label "209" + ] + node + [ + id 259 + label "210" + ] + node + [ + id 260 + label "211" + ] + node + [ + id 261 + label "212" + ] + node + [ + id 262 + label "261" + ] + node + [ + id 263 + label "262" + ] + node + [ + id 264 + label "263" + ] + node + [ + id 265 + label "264" + ] + node + [ + id 266 + label "265" + ] + node + [ + id 267 + label "266" + ] + node + [ + id 268 + label "282" + ] + node + [ + id 269 + label "279" + ] + node + [ + id 270 + label "231" + ] + node + [ + id 271 + label "280" + ] + node + [ + id 272 + label "232" + ] + node + [ + id 273 + label "281" + ] + node + [ + id 274 + label "233" + ] + node + [ + id 275 + label "246" + ] + node + [ + id 276 + label "247" + ] + node + [ + id 277 + label "248" + ] + node + [ + id 278 + label "243" + ] + node + [ + id 279 + label "257" + ] + node + [ + id 280 + label "259" + ] + node + [ + id 281 + label "267" + ] + node + [ + id 282 + label "268" + ] + node + [ + id 283 + label "271" + ] + node + [ + id 284 + label "273" + ] + node + [ + id 285 + label "291" + ] + node + [ + id 286 + label "292" + ] + node + [ + id 287 + label "293" + ] + node + [ + id 288 + label "294" + ] + node + [ + id 289 + label "295" + ] + node + [ + id 290 + label "296" + ] + node + [ + id 291 + label "297" + ] + node + [ + id 292 + label "298" + ] + node + [ + id 293 + label "299" + ] + node + [ + id 294 + label "300" + ] + node + [ + id 295 + label "301" + ] + node + [ + id 296 + label "302" + ] + edge + [ + source 0 + target 1 + value 1 + ] + edge + [ + source 0 + target 2 + value 2 + ] + edge + [ + source 0 + target 3 + value 1 + ] + edge + [ + source 0 + target 4 + value 2 + ] + edge + [ + source 0 + target 5 + value 1 + ] + edge + [ + source 0 + target 6 + value 6 + ] + edge + [ + source 0 + target 7 + value 6 + ] + edge + [ + source 0 + target 8 + value 1 + ] + edge + [ + source 0 + target 9 + value 4 + ] + edge + [ + source 1 + target 10 + value 1 + ] + edge + [ + source 1 + target 115 + value 1 + ] + edge + [ + source 1 + target 12 + value 2 + ] + edge + [ + source 1 + target 84 + value 3 + ] + edge + [ + source 1 + target 129 + value 1 + ] + edge + [ + source 1 + target 7 + value 2 + ] + edge + [ + source 1 + target 130 + value 12 + ] + edge + [ + source 1 + target 131 + value 1 + ] + edge + [ + source 1 + target 72 + value 2 + ] + edge + [ + source 1 + target 74 + value 1 + ] + edge + [ + source 2 + target 67 + value 1 + ] + edge + [ + source 2 + target 151 + value 1 + ] + edge + [ + source 2 + target 152 + value 4 + ] + edge + [ + source 2 + target 153 + value 9 + ] + edge + [ + source 2 + target 154 + value 3 + ] + edge + [ + source 2 + target 155 + value 2 + ] + edge + [ + source 2 + target 156 + value 2 + ] + edge + [ + source 2 + target 157 + value 4 + ] + edge + [ + source 2 + target 158 + value 2 + ] + edge + [ + source 2 + target 12 + value 3 + ] + edge + [ + source 2 + target 86 + value 2 + ] + edge + [ + source 2 + target 118 + value 2 + ] + edge + [ + source 2 + target 4 + value 4 + ] + edge + [ + source 2 + target 159 + value 4 + ] + edge + [ + source 2 + target 160 + value 4 + ] + edge + [ + source 2 + target 161 + value 9 + ] + edge + [ + source 2 + target 162 + value 12 + ] + edge + [ + source 2 + target 163 + value 9 + ] + edge + [ + source 2 + target 164 + value 2 + ] + edge + [ + source 2 + target 165 + value 7 + ] + edge + [ + source 2 + target 166 + value 14 + ] + edge + [ + source 2 + target 167 + value 5 + ] + edge + [ + source 2 + target 168 + value 2 + ] + edge + [ + source 2 + target 169 + value 2 + ] + edge + [ + source 2 + target 184 + value 5 + ] + edge + [ + source 2 + target 185 + value 2 + ] + edge + [ + source 2 + target 125 + value 28 + ] + edge + [ + source 2 + target 8 + value 1 + ] + edge + [ + source 2 + target 173 + value 1 + ] + edge + [ + source 2 + target 186 + value 2 + ] + edge + [ + source 2 + target 175 + value 10 + ] + edge + [ + source 2 + target 176 + value 14 + ] + edge + [ + source 2 + target 177 + value 2 + ] + edge + [ + source 2 + target 178 + value 3 + ] + edge + [ + source 2 + target 179 + value 4 + ] + edge + [ + source 2 + target 180 + value 4 + ] + edge + [ + source 2 + target 181 + value 6 + ] + edge + [ + source 2 + target 182 + value 4 + ] + edge + [ + source 2 + target 183 + value 2 + ] + edge + [ + source 3 + target 151 + value 5 + ] + edge + [ + source 3 + target 154 + value 2 + ] + edge + [ + source 3 + target 155 + value 1 + ] + edge + [ + source 3 + target 12 + value 44 + ] + edge + [ + source 3 + target 117 + value 1 + ] + edge + [ + source 3 + target 159 + value 12 + ] + edge + [ + source 3 + target 160 + value 6 + ] + edge + [ + source 3 + target 161 + value 4 + ] + edge + [ + source 3 + target 162 + value 1 + ] + edge + [ + source 3 + target 168 + value 2 + ] + edge + [ + source 3 + target 195 + value 1 + ] + edge + [ + source 3 + target 125 + value 1 + ] + edge + [ + source 3 + target 173 + value 8 + ] + edge + [ + source 3 + target 186 + value 17 + ] + edge + [ + source 3 + target 196 + value 6 + ] + edge + [ + source 3 + target 178 + value 2 + ] + edge + [ + source 3 + target 179 + value 5 + ] + edge + [ + source 3 + target 180 + value 1 + ] + edge + [ + source 3 + target 182 + value 1 + ] + edge + [ + source 3 + target 197 + value 1 + ] + edge + [ + source 3 + target 189 + value 1 + ] + edge + [ + source 4 + target 151 + value 5 + ] + edge + [ + source 4 + target 154 + value 2 + ] + edge + [ + source 4 + target 155 + value 1 + ] + edge + [ + source 4 + target 2 + value 44 + ] + edge + [ + source 4 + target 118 + value 1 + ] + edge + [ + source 4 + target 159 + value 12 + ] + edge + [ + source 4 + target 160 + value 6 + ] + edge + [ + source 4 + target 161 + value 4 + ] + edge + [ + source 4 + target 162 + value 1 + ] + edge + [ + source 4 + target 168 + value 2 + ] + edge + [ + source 4 + target 195 + value 1 + ] + edge + [ + source 4 + target 172 + value 1 + ] + edge + [ + source 4 + target 173 + value 8 + ] + edge + [ + source 4 + target 174 + value 17 + ] + edge + [ + source 4 + target 196 + value 6 + ] + edge + [ + source 4 + target 178 + value 2 + ] + edge + [ + source 4 + target 179 + value 5 + ] + edge + [ + source 4 + target 180 + value 1 + ] + edge + [ + source 4 + target 182 + value 1 + ] + edge + [ + source 4 + target 197 + value 1 + ] + edge + [ + source 4 + target 189 + value 1 + ] + edge + [ + source 5 + target 10 + value 1 + ] + edge + [ + source 5 + target 11 + value 1 + ] + edge + [ + source 5 + target 12 + value 3 + ] + edge + [ + source 5 + target 3 + value 4 + ] + edge + [ + source 5 + target 13 + value 4 + ] + edge + [ + source 5 + target 14 + value 4 + ] + edge + [ + source 5 + target 8 + value 7 + ] + edge + [ + source 6 + target 27 + value 1 + ] + edge + [ + source 6 + target 50 + value 1 + ] + edge + [ + source 6 + target 34 + value 2 + ] + edge + [ + source 6 + target 23 + value 12 + ] + edge + [ + source 6 + target 35 + value 8 + ] + edge + [ + source 6 + target 47 + value 5 + ] + edge + [ + source 6 + target 48 + value 7 + ] + edge + [ + source 6 + target 43 + value 4 + ] + edge + [ + source 6 + target 46 + value 9 + ] + edge + [ + source 6 + target 72 + value 1 + ] + edge + [ + source 6 + target 88 + value 1 + ] + edge + [ + source 6 + target 94 + value 1 + ] + edge + [ + source 6 + target 25 + value 1 + ] + edge + [ + source 6 + target 73 + value 6 + ] + edge + [ + source 6 + target 74 + value 10 + ] + edge + [ + source 6 + target 77 + value 13 + ] + edge + [ + source 6 + target 75 + value 7 + ] + edge + [ + source 7 + target 1 + value 1 + ] + edge + [ + source 7 + target 2 + value 2 + ] + edge + [ + source 7 + target 3 + value 4 + ] + edge + [ + source 7 + target 6 + value 2 + ] + edge + [ + source 7 + target 59 + value 1 + ] + edge + [ + source 7 + target 73 + value 1 + ] + edge + [ + source 8 + target 102 + value 2 + ] + edge + [ + source 8 + target 108 + value 1 + ] + edge + [ + source 8 + target 235 + value 1 + ] + edge + [ + source 8 + target 3 + value 1 + ] + edge + [ + source 8 + target 4 + value 2 + ] + edge + [ + source 8 + target 213 + value 1 + ] + edge + [ + source 8 + target 5 + value 2 + ] + edge + [ + source 8 + target 193 + value 1 + ] + edge + [ + source 8 + target 14 + value 1 + ] + edge + [ + source 8 + target 103 + value 2 + ] + edge + [ + source 8 + target 214 + value 1 + ] + edge + [ + source 8 + target 36 + value 3 + ] + edge + [ + source 9 + target 108 + value 2 + ] + edge + [ + source 9 + target 236 + value 1 + ] + edge + [ + source 9 + target 4 + value 2 + ] + edge + [ + source 9 + target 213 + value 4 + ] + edge + [ + source 9 + target 218 + value 1 + ] + edge + [ + source 9 + target 5 + value 1 + ] + edge + [ + source 9 + target 7 + value 1 + ] + edge + [ + source 9 + target 103 + value 1 + ] + edge + [ + source 9 + target 24 + value 4 + ] + edge + [ + source 10 + target 102 + value 13 + ] + edge + [ + source 10 + target 208 + value 3 + ] + edge + [ + source 10 + target 138 + value 1 + ] + edge + [ + source 10 + target 124 + value 1 + ] + edge + [ + source 10 + target 142 + value 1 + ] + edge + [ + source 10 + target 13 + value 5 + ] + edge + [ + source 10 + target 14 + value 5 + ] + edge + [ + source 11 + target 12 + value 2 + ] + edge + [ + source 11 + target 118 + value 1 + ] + edge + [ + source 11 + target 3 + value 4 + ] + edge + [ + source 11 + target 13 + value 6 + ] + edge + [ + source 11 + target 14 + value 9 + ] + edge + [ + source 12 + target 16 + value 1 + ] + edge + [ + source 12 + target 151 + value 1 + ] + edge + [ + source 12 + target 152 + value 4 + ] + edge + [ + source 12 + target 153 + value 9 + ] + edge + [ + source 12 + target 154 + value 3 + ] + edge + [ + source 12 + target 155 + value 2 + ] + edge + [ + source 12 + target 156 + value 2 + ] + edge + [ + source 12 + target 157 + value 4 + ] + edge + [ + source 12 + target 158 + value 2 + ] + edge + [ + source 12 + target 2 + value 3 + ] + edge + [ + source 12 + target 84 + value 2 + ] + edge + [ + source 12 + target 117 + value 2 + ] + edge + [ + source 12 + target 3 + value 4 + ] + edge + [ + source 12 + target 159 + value 4 + ] + edge + [ + source 12 + target 160 + value 4 + ] + edge + [ + source 12 + target 161 + value 9 + ] + edge + [ + source 12 + target 162 + value 12 + ] + edge + [ + source 12 + target 163 + value 9 + ] + edge + [ + source 12 + target 164 + value 2 + ] + edge + [ + source 12 + target 165 + value 7 + ] + edge + [ + source 12 + target 166 + value 14 + ] + edge + [ + source 12 + target 167 + value 5 + ] + edge + [ + source 12 + target 168 + value 1 + ] + edge + [ + source 12 + target 168 + value 2 + ] + edge + [ + source 12 + target 169 + value 2 + ] + edge + [ + source 12 + target 170 + value 5 + ] + edge + [ + source 12 + target 171 + value 2 + ] + edge + [ + source 12 + target 172 + value 28 + ] + edge + [ + source 12 + target 173 + value 1 + ] + edge + [ + source 12 + target 174 + value 2 + ] + edge + [ + source 12 + target 175 + value 10 + ] + edge + [ + source 12 + target 176 + value 14 + ] + edge + [ + source 12 + target 177 + value 2 + ] + edge + [ + source 12 + target 178 + value 3 + ] + edge + [ + source 12 + target 179 + value 4 + ] + edge + [ + source 12 + target 180 + value 4 + ] + edge + [ + source 12 + target 181 + value 6 + ] + edge + [ + source 12 + target 182 + value 4 + ] + edge + [ + source 12 + target 183 + value 2 + ] + edge + [ + source 13 + target 6 + value 1 + ] + edge + [ + source 13 + target 34 + value 2 + ] + edge + [ + source 13 + target 209 + value 3 + ] + edge + [ + source 13 + target 23 + value 10 + ] + edge + [ + source 13 + target 35 + value 6 + ] + edge + [ + source 13 + target 47 + value 6 + ] + edge + [ + source 13 + target 48 + value 5 + ] + edge + [ + source 13 + target 43 + value 7 + ] + edge + [ + source 13 + target 46 + value 10 + ] + edge + [ + source 13 + target 88 + value 1 + ] + edge + [ + source 13 + target 73 + value 9 + ] + edge + [ + source 13 + target 74 + value 8 + ] + edge + [ + source 13 + target 77 + value 3 + ] + edge + [ + source 13 + target 75 + value 12 + ] + edge + [ + source 14 + target 114 + value 1 + ] + edge + [ + source 14 + target 11 + value 1 + ] + edge + [ + source 14 + target 12 + value 1 + ] + edge + [ + source 14 + target 86 + value 1 + ] + edge + [ + source 14 + target 118 + value 1 + ] + edge + [ + source 14 + target 3 + value 2 + ] + edge + [ + source 14 + target 4 + value 5 + ] + edge + [ + source 14 + target 13 + value 3 + ] + edge + [ + source 14 + target 75 + value 2 + ] + edge + [ + source 15 + target 16 + value 1 + ] + edge + [ + source 15 + target 4 + value 3 + ] + edge + [ + source 15 + target 17 + value 2 + ] + edge + [ + source 15 + target 18 + value 4 + ] + edge + [ + source 15 + target 19 + value 5 + ] + edge + [ + source 15 + target 20 + value 6 + ] + edge + [ + source 15 + target 21 + value 4 + ] + edge + [ + source 15 + target 22 + value 1 + ] + edge + [ + source 15 + target 23 + value 3 + ] + edge + [ + source 15 + target 24 + value 1 + ] + edge + [ + source 15 + target 25 + value 2 + ] + edge + [ + source 15 + target 26 + value 2 + ] + edge + [ + source 16 + target 113 + value 5 + ] + edge + [ + source 16 + target 1 + value 6 + ] + edge + [ + source 16 + target 114 + value 1 + ] + edge + [ + source 16 + target 92 + value 1 + ] + edge + [ + source 16 + target 115 + value 3 + ] + edge + [ + source 16 + target 116 + value 2 + ] + edge + [ + source 16 + target 12 + value 2 + ] + edge + [ + source 16 + target 2 + value 4 + ] + edge + [ + source 16 + target 84 + value 1 + ] + edge + [ + source 16 + target 117 + value 1 + ] + edge + [ + source 16 + target 118 + value 4 + ] + edge + [ + source 16 + target 119 + value 1 + ] + edge + [ + source 16 + target 98 + value 2 + ] + edge + [ + source 16 + target 120 + value 2 + ] + edge + [ + source 16 + target 19 + value 1 + ] + edge + [ + source 16 + target 22 + value 1 + ] + edge + [ + source 16 + target 73 + value 2 + ] + edge + [ + source 17 + target 41 + value 1 + ] + edge + [ + source 17 + target 22 + value 7 + ] + edge + [ + source 17 + target 23 + value 11 + ] + edge + [ + source 17 + target 53 + value 1 + ] + edge + [ + source 17 + target 44 + value 10 + ] + edge + [ + source 18 + target 4 + value 19 + ] + edge + [ + source 18 + target 71 + value 2 + ] + edge + [ + source 18 + target 15 + value 2 + ] + edge + [ + source 18 + target 17 + value 1 + ] + edge + [ + source 18 + target 14 + value 7 + ] + edge + [ + source 18 + target 23 + value 7 + ] + edge + [ + source 18 + target 35 + value 1 + ] + edge + [ + source 18 + target 47 + value 2 + ] + edge + [ + source 18 + target 43 + value 1 + ] + edge + [ + source 18 + target 51 + value 2 + ] + edge + [ + source 18 + target 72 + value 1 + ] + edge + [ + source 18 + target 73 + value 3 + ] + edge + [ + source 18 + target 74 + value 4 + ] + edge + [ + source 18 + target 75 + value 4 + ] + edge + [ + source 18 + target 76 + value 1 + ] + edge + [ + source 19 + target 17 + value 2 + ] + edge + [ + source 19 + target 70 + value 1 + ] + edge + [ + source 19 + target 20 + value 1 + ] + edge + [ + source 19 + target 59 + value 2 + ] + edge + [ + source 19 + target 22 + value 2 + ] + edge + [ + source 19 + target 46 + value 4 + ] + edge + [ + source 19 + target 80 + value 3 + ] + edge + [ + source 20 + target 12 + value 5 + ] + edge + [ + source 20 + target 2 + value 6 + ] + edge + [ + source 20 + target 132 + value 1 + ] + edge + [ + source 20 + target 130 + value 2 + ] + edge + [ + source 20 + target 209 + value 1 + ] + edge + [ + source 20 + target 210 + value 1 + ] + edge + [ + source 20 + target 89 + value 2 + ] + edge + [ + source 20 + target 73 + value 3 + ] + edge + [ + source 20 + target 74 + value 4 + ] + edge + [ + source 20 + target 75 + value 1 + ] + edge + [ + source 21 + target 106 + value 1 + ] + edge + [ + source 21 + target 12 + value 5 + ] + edge + [ + source 21 + target 2 + value 3 + ] + edge + [ + source 21 + target 85 + value 1 + ] + edge + [ + source 21 + target 73 + value 2 + ] + edge + [ + source 21 + target 74 + value 3 + ] + edge + [ + source 21 + target 77 + value 2 + ] + edge + [ + source 21 + target 75 + value 1 + ] + edge + [ + source 22 + target 55 + value 2 + ] + edge + [ + source 23 + target 46 + value 7 + ] + edge + [ + source 23 + target 44 + value 8 + ] + edge + [ + source 24 + target 48 + value 1 + ] + edge + [ + source 24 + target 44 + value 8 + ] + edge + [ + source 25 + target 190 + value 1 + ] + edge + [ + source 26 + target 22 + value 3 + ] + edge + [ + source 26 + target 51 + value 1 + ] + edge + [ + source 26 + target 49 + value 1 + ] + edge + [ + source 26 + target 64 + value 2 + ] + edge + [ + source 26 + target 44 + value 9 + ] + edge + [ + source 27 + target 28 + value 1 + ] + edge + [ + source 27 + target 3 + value 4 + ] + edge + [ + source 27 + target 29 + value 2 + ] + edge + [ + source 27 + target 30 + value 1 + ] + edge + [ + source 27 + target 31 + value 5 + ] + edge + [ + source 27 + target 32 + value 3 + ] + edge + [ + source 27 + target 20 + value 2 + ] + edge + [ + source 27 + target 21 + value 4 + ] + edge + [ + source 27 + target 33 + value 1 + ] + edge + [ + source 27 + target 34 + value 1 + ] + edge + [ + source 27 + target 35 + value 2 + ] + edge + [ + source 27 + target 36 + value 2 + ] + edge + [ + source 27 + target 37 + value 2 + ] + edge + [ + source 27 + target 38 + value 1 + ] + edge + [ + source 27 + target 39 + value 1 + ] + edge + [ + source 28 + target 122 + value 10 + ] + edge + [ + source 28 + target 114 + value 2 + ] + edge + [ + source 29 + target 50 + value 1 + ] + edge + [ + source 29 + target 30 + value 1 + ] + edge + [ + source 29 + target 33 + value 8 + ] + edge + [ + source 29 + target 35 + value 11 + ] + edge + [ + source 29 + target 44 + value 10 + ] + edge + [ + source 30 + target 29 + value 5 + ] + edge + [ + source 30 + target 32 + value 2 + ] + edge + [ + source 30 + target 6 + value 2 + ] + edge + [ + source 30 + target 59 + value 4 + ] + edge + [ + source 30 + target 33 + value 14 + ] + edge + [ + source 30 + target 51 + value 3 + ] + edge + [ + source 30 + target 49 + value 2 + ] + edge + [ + source 30 + target 64 + value 3 + ] + edge + [ + source 30 + target 38 + value 3 + ] + edge + [ + source 30 + target 52 + value 1 + ] + edge + [ + source 31 + target 3 + value 14 + ] + edge + [ + source 31 + target 4 + value 1 + ] + edge + [ + source 31 + target 58 + value 2 + ] + edge + [ + source 31 + target 27 + value 6 + ] + edge + [ + source 31 + target 45 + value 3 + ] + edge + [ + source 31 + target 29 + value 1 + ] + edge + [ + source 31 + target 66 + value 1 + ] + edge + [ + source 31 + target 7 + value 10 + ] + edge + [ + source 31 + target 35 + value 10 + ] + edge + [ + source 31 + target 47 + value 2 + ] + edge + [ + source 31 + target 48 + value 1 + ] + edge + [ + source 31 + target 46 + value 3 + ] + edge + [ + source 31 + target 49 + value 2 + ] + edge + [ + source 31 + target 73 + value 1 + ] + edge + [ + source 31 + target 74 + value 1 + ] + edge + [ + source 31 + target 77 + value 3 + ] + edge + [ + source 31 + target 75 + value 3 + ] + edge + [ + source 32 + target 29 + value 1 + ] + edge + [ + source 32 + target 30 + value 1 + ] + edge + [ + source 32 + target 21 + value 1 + ] + edge + [ + source 32 + target 59 + value 1 + ] + edge + [ + source 32 + target 33 + value 1 + ] + edge + [ + source 32 + target 43 + value 3 + ] + edge + [ + source 32 + target 49 + value 1 + ] + edge + [ + source 32 + target 81 + value 3 + ] + edge + [ + source 33 + target 68 + value 2 + ] + edge + [ + source 34 + target 13 + value 1 + ] + edge + [ + source 34 + target 6 + value 1 + ] + edge + [ + source 34 + target 47 + value 2 + ] + edge + [ + source 34 + target 43 + value 1 + ] + edge + [ + source 34 + target 72 + value 3 + ] + edge + [ + source 34 + target 37 + value 3 + ] + edge + [ + source 34 + target 112 + value 1 + ] + edge + [ + source 34 + target 74 + value 1 + ] + edge + [ + source 34 + target 44 + value 4 + ] + edge + [ + source 35 + target 43 + value 10 + ] + edge + [ + source 35 + target 44 + value 10 + ] + edge + [ + source 36 + target 49 + value 1 + ] + edge + [ + source 36 + target 93 + value 1 + ] + edge + [ + source 36 + target 44 + value 7 + ] + edge + [ + source 37 + target 190 + value 1 + ] + edge + [ + source 38 + target 50 + value 1 + ] + edge + [ + source 38 + target 33 + value 4 + ] + edge + [ + source 38 + target 43 + value 1 + ] + edge + [ + source 38 + target 49 + value 2 + ] + edge + [ + source 38 + target 64 + value 2 + ] + edge + [ + source 38 + target 44 + value 9 + ] + edge + [ + source 40 + target 41 + value 1 + ] + edge + [ + source 40 + target 42 + value 1 + ] + edge + [ + source 40 + target 22 + value 3 + ] + edge + [ + source 40 + target 43 + value 6 + ] + edge + [ + source 40 + target 44 + value 4 + ] + edge + [ + source 41 + target 4 + value 2 + ] + edge + [ + source 41 + target 40 + value 1 + ] + edge + [ + source 41 + target 23 + value 3 + ] + edge + [ + source 41 + target 47 + value 1 + ] + edge + [ + source 41 + target 48 + value 3 + ] + edge + [ + source 41 + target 43 + value 4 + ] + edge + [ + source 41 + target 46 + value 2 + ] + edge + [ + source 41 + target 49 + value 1 + ] + edge + [ + source 41 + target 44 + value 10 + ] + edge + [ + source 42 + target 84 + value 4 + ] + edge + [ + source 42 + target 86 + value 3 + ] + edge + [ + source 42 + target 119 + value 2 + ] + edge + [ + source 42 + target 98 + value 2 + ] + edge + [ + source 42 + target 218 + value 1 + ] + edge + [ + source 42 + target 167 + value 1 + ] + edge + [ + source 42 + target 232 + value 1 + ] + edge + [ + source 42 + target 168 + value 1 + ] + edge + [ + source 42 + target 137 + value 1 + ] + edge + [ + source 42 + target 40 + value 1 + ] + edge + [ + source 42 + target 45 + value 1 + ] + edge + [ + source 42 + target 17 + value 1 + ] + edge + [ + source 42 + target 29 + value 1 + ] + edge + [ + source 42 + target 185 + value 1 + ] + edge + [ + source 42 + target 172 + value 1 + ] + edge + [ + source 42 + target 22 + value 4 + ] + edge + [ + source 42 + target 33 + value 3 + ] + edge + [ + source 42 + target 173 + value 1 + ] + edge + [ + source 43 + target 6 + value 1 + ] + edge + [ + source 43 + target 23 + value 1 + ] + edge + [ + source 43 + target 35 + value 6 + ] + edge + [ + source 43 + target 44 + value 7 + ] + edge + [ + source 45 + target 33 + value 7 + ] + edge + [ + source 45 + target 46 + value 8 + ] + edge + [ + source 45 + target 44 + value 8 + ] + edge + [ + source 46 + target 23 + value 10 + ] + edge + [ + source 46 + target 79 + value 1 + ] + edge + [ + source 46 + target 44 + value 9 + ] + edge + [ + source 47 + target 31 + value 1 + ] + edge + [ + source 47 + target 13 + value 5 + ] + edge + [ + source 47 + target 6 + value 3 + ] + edge + [ + source 47 + target 35 + value 1 + ] + edge + [ + source 47 + target 48 + value 2 + ] + edge + [ + source 47 + target 214 + value 1 + ] + edge + [ + source 47 + target 44 + value 11 + ] + edge + [ + source 48 + target 213 + value 1 + ] + edge + [ + source 48 + target 13 + value 5 + ] + edge + [ + source 48 + target 6 + value 7 + ] + edge + [ + source 48 + target 23 + value 1 + ] + edge + [ + source 48 + target 47 + value 1 + ] + edge + [ + source 48 + target 44 + value 4 + ] + edge + [ + source 49 + target 44 + value 14 + ] + edge + [ + source 50 + target 3 + value 1 + ] + edge + [ + source 50 + target 45 + value 1 + ] + edge + [ + source 50 + target 23 + value 3 + ] + edge + [ + source 50 + target 35 + value 4 + ] + edge + [ + source 50 + target 47 + value 4 + ] + edge + [ + source 50 + target 48 + value 1 + ] + edge + [ + source 50 + target 43 + value 1 + ] + edge + [ + source 50 + target 46 + value 4 + ] + edge + [ + source 50 + target 51 + value 2 + ] + edge + [ + source 50 + target 52 + value 1 + ] + edge + [ + source 50 + target 44 + value 12 + ] + edge + [ + source 51 + target 44 + value 11 + ] + edge + [ + source 52 + target 99 + value 4 + ] + edge + [ + source 52 + target 84 + value 1 + ] + edge + [ + source 52 + target 86 + value 1 + ] + edge + [ + source 52 + target 4 + value 1 + ] + edge + [ + source 52 + target 32 + value 1 + ] + edge + [ + source 52 + target 6 + value 4 + ] + edge + [ + source 52 + target 21 + value 1 + ] + edge + [ + source 52 + target 9 + value 2 + ] + edge + [ + source 52 + target 33 + value 3 + ] + edge + [ + source 52 + target 48 + value 1 + ] + edge + [ + source 52 + target 37 + value 1 + ] + edge + [ + source 53 + target 86 + value 1 + ] + edge + [ + source 53 + target 4 + value 5 + ] + edge + [ + source 53 + target 17 + value 1 + ] + edge + [ + source 53 + target 13 + value 1 + ] + edge + [ + source 53 + target 14 + value 2 + ] + edge + [ + source 53 + target 59 + value 1 + ] + edge + [ + source 53 + target 90 + value 1 + ] + edge + [ + source 53 + target 23 + value 4 + ] + edge + [ + source 53 + target 46 + value 2 + ] + edge + [ + source 53 + target 78 + value 1 + ] + edge + [ + source 53 + target 75 + value 4 + ] + edge + [ + source 54 + target 11 + value 1 + ] + edge + [ + source 54 + target 40 + value 7 + ] + edge + [ + source 54 + target 55 + value 2 + ] + edge + [ + source 54 + target 14 + value 1 + ] + edge + [ + source 54 + target 22 + value 9 + ] + edge + [ + source 54 + target 51 + value 4 + ] + edge + [ + source 54 + target 49 + value 4 + ] + edge + [ + source 54 + target 56 + value 3 + ] + edge + [ + source 55 + target 21 + value 1 + ] + edge + [ + source 55 + target 35 + value 4 + ] + edge + [ + source 55 + target 78 + value 3 + ] + edge + [ + source 56 + target 40 + value 2 + ] + edge + [ + source 56 + target 22 + value 3 + ] + edge + [ + source 56 + target 35 + value 1 + ] + edge + [ + source 56 + target 51 + value 1 + ] + edge + [ + source 56 + target 44 + value 6 + ] + edge + [ + source 57 + target 58 + value 1 + ] + edge + [ + source 57 + target 45 + value 7 + ] + edge + [ + source 57 + target 21 + value 1 + ] + edge + [ + source 57 + target 59 + value 1 + ] + edge + [ + source 57 + target 33 + value 12 + ] + edge + [ + source 57 + target 60 + value 1 + ] + edge + [ + source 57 + target 51 + value 3 + ] + edge + [ + source 57 + target 49 + value 2 + ] + edge + [ + source 57 + target 61 + value 3 + ] + edge + [ + source 58 + target 3 + value 5 + ] + edge + [ + source 58 + target 95 + value 1 + ] + edge + [ + source 58 + target 45 + value 5 + ] + edge + [ + source 58 + target 50 + value 1 + ] + edge + [ + source 58 + target 31 + value 6 + ] + edge + [ + source 58 + target 68 + value 5 + ] + edge + [ + source 58 + target 7 + value 1 + ] + edge + [ + source 58 + target 20 + value 4 + ] + edge + [ + source 58 + target 21 + value 2 + ] + edge + [ + source 58 + target 23 + value 1 + ] + edge + [ + source 58 + target 46 + value 2 + ] + edge + [ + source 58 + target 87 + value 1 + ] + edge + [ + source 58 + target 24 + value 4 + ] + edge + [ + source 58 + target 88 + value 1 + ] + edge + [ + source 58 + target 85 + value 1 + ] + edge + [ + source 58 + target 61 + value 1 + ] + edge + [ + source 58 + target 69 + value 2 + ] + edge + [ + source 58 + target 82 + value 1 + ] + edge + [ + source 59 + target 105 + value 1 + ] + edge + [ + source 59 + target 102 + value 5 + ] + edge + [ + source 59 + target 108 + value 4 + ] + edge + [ + source 59 + target 99 + value 1 + ] + edge + [ + source 59 + target 5 + value 1 + ] + edge + [ + source 59 + target 71 + value 1 + ] + edge + [ + source 59 + target 58 + value 1 + ] + edge + [ + source 59 + target 15 + value 1 + ] + edge + [ + source 59 + target 27 + value 2 + ] + edge + [ + source 59 + target 66 + value 1 + ] + edge + [ + source 59 + target 55 + value 3 + ] + edge + [ + source 59 + target 68 + value 3 + ] + edge + [ + source 59 + target 19 + value 3 + ] + edge + [ + source 59 + target 32 + value 7 + ] + edge + [ + source 59 + target 13 + value 12 + ] + edge + [ + source 59 + target 6 + value 9 + ] + edge + [ + source 59 + target 14 + value 5 + ] + edge + [ + source 59 + target 7 + value 4 + ] + edge + [ + source 59 + target 22 + value 5 + ] + edge + [ + source 59 + target 33 + value 5 + ] + edge + [ + source 59 + target 49 + value 1 + ] + edge + [ + source 59 + target 64 + value 1 + ] + edge + [ + source 59 + target 91 + value 1 + ] + edge + [ + source 59 + target 39 + value 1 + ] + edge + [ + source 60 + target 14 + value 1 + ] + edge + [ + source 60 + target 7 + value 1 + ] + edge + [ + source 60 + target 44 + value 10 + ] + edge + [ + source 61 + target 33 + value 3 + ] + edge + [ + source 61 + target 46 + value 1 + ] + edge + [ + source 61 + target 60 + value 1 + ] + edge + [ + source 61 + target 49 + value 1 + ] + edge + [ + source 61 + target 82 + value 1 + ] + edge + [ + source 61 + target 44 + value 6 + ] + edge + [ + source 62 + target 63 + value 2 + ] + edge + [ + source 62 + target 3 + value 1 + ] + edge + [ + source 62 + target 41 + value 1 + ] + edge + [ + source 62 + target 55 + value 6 + ] + edge + [ + source 62 + target 19 + value 8 + ] + edge + [ + source 62 + target 20 + value 1 + ] + edge + [ + source 62 + target 59 + value 9 + ] + edge + [ + source 62 + target 47 + value 3 + ] + edge + [ + source 62 + target 48 + value 1 + ] + edge + [ + source 62 + target 49 + value 2 + ] + edge + [ + source 62 + target 64 + value 2 + ] + edge + [ + source 62 + target 65 + value 2 + ] + edge + [ + source 63 + target 101 + value 1 + ] + edge + [ + source 63 + target 12 + value 2 + ] + edge + [ + source 63 + target 2 + value 3 + ] + edge + [ + source 63 + target 3 + value 1 + ] + edge + [ + source 63 + target 225 + value 1 + ] + edge + [ + source 63 + target 71 + value 1 + ] + edge + [ + source 63 + target 129 + value 2 + ] + edge + [ + source 63 + target 41 + value 1 + ] + edge + [ + source 63 + target 62 + value 2 + ] + edge + [ + source 63 + target 18 + value 2 + ] + edge + [ + source 63 + target 13 + value 1 + ] + edge + [ + source 63 + target 215 + value 1 + ] + edge + [ + source 63 + target 8 + value 5 + ] + edge + [ + source 63 + target 9 + value 6 + ] + edge + [ + source 63 + target 59 + value 2 + ] + edge + [ + source 63 + target 34 + value 1 + ] + edge + [ + source 63 + target 209 + value 1 + ] + edge + [ + source 63 + target 47 + value 2 + ] + edge + [ + source 63 + target 93 + value 3 + ] + edge + [ + source 63 + target 24 + value 1 + ] + edge + [ + source 63 + target 94 + value 1 + ] + edge + [ + source 63 + target 81 + value 1 + ] + edge + [ + source 63 + target 85 + value 1 + ] + edge + [ + source 63 + target 83 + value 1 + ] + edge + [ + source 64 + target 74 + value 1 + ] + edge + [ + source 64 + target 44 + value 5 + ] + edge + [ + source 65 + target 11 + value 7 + ] + edge + [ + source 65 + target 84 + value 2 + ] + edge + [ + source 65 + target 3 + value 3 + ] + edge + [ + source 65 + target 98 + value 2 + ] + edge + [ + source 65 + target 13 + value 8 + ] + edge + [ + source 65 + target 20 + value 1 + ] + edge + [ + source 65 + target 8 + value 2 + ] + edge + [ + source 66 + target 67 + value 2 + ] + edge + [ + source 66 + target 50 + value 1 + ] + edge + [ + source 66 + target 31 + value 2 + ] + edge + [ + source 66 + target 68 + value 1 + ] + edge + [ + source 66 + target 32 + value 7 + ] + edge + [ + source 66 + target 59 + value 5 + ] + edge + [ + source 66 + target 47 + value 1 + ] + edge + [ + source 66 + target 51 + value 2 + ] + edge + [ + source 66 + target 64 + value 1 + ] + edge + [ + source 66 + target 38 + value 1 + ] + edge + [ + source 66 + target 69 + value 1 + ] + edge + [ + source 67 + target 106 + value 1 + ] + edge + [ + source 67 + target 63 + value 2 + ] + edge + [ + source 67 + target 12 + value 5 + ] + edge + [ + source 67 + target 2 + value 1 + ] + edge + [ + source 67 + target 118 + value 1 + ] + edge + [ + source 67 + target 4 + value 1 + ] + edge + [ + source 67 + target 98 + value 1 + ] + edge + [ + source 67 + target 218 + value 1 + ] + edge + [ + source 67 + target 58 + value 1 + ] + edge + [ + source 67 + target 129 + value 1 + ] + edge + [ + source 67 + target 224 + value 2 + ] + edge + [ + source 67 + target 31 + value 2 + ] + edge + [ + source 67 + target 8 + value 7 + ] + edge + [ + source 67 + target 9 + value 4 + ] + edge + [ + source 67 + target 59 + value 1 + ] + edge + [ + source 67 + target 48 + value 2 + ] + edge + [ + source 67 + target 87 + value 2 + ] + edge + [ + source 67 + target 212 + value 1 + ] + edge + [ + source 68 + target 20 + value 1 + ] + edge + [ + source 68 + target 21 + value 1 + ] + edge + [ + source 68 + target 23 + value 3 + ] + edge + [ + source 68 + target 36 + value 1 + ] + edge + [ + source 68 + target 79 + value 2 + ] + edge + [ + source 69 + target 86 + value 1 + ] + edge + [ + source 69 + target 58 + value 1 + ] + edge + [ + source 69 + target 50 + value 3 + ] + edge + [ + source 69 + target 66 + value 1 + ] + edge + [ + source 69 + target 21 + value 1 + ] + edge + [ + source 69 + target 47 + value 1 + ] + edge + [ + source 69 + target 48 + value 1 + ] + edge + [ + source 69 + target 87 + value 1 + ] + edge + [ + source 69 + target 88 + value 1 + ] + edge + [ + source 69 + target 37 + value 1 + ] + edge + [ + source 69 + target 89 + value 1 + ] + edge + [ + source 69 + target 52 + value 7 + ] + edge + [ + source 70 + target 5 + value 1 + ] + edge + [ + source 70 + target 17 + value 6 + ] + edge + [ + source 70 + target 19 + value 1 + ] + edge + [ + source 70 + target 13 + value 1 + ] + edge + [ + source 70 + target 59 + value 2 + ] + edge + [ + source 70 + target 22 + value 11 + ] + edge + [ + source 70 + target 51 + value 2 + ] + edge + [ + source 70 + target 49 + value 5 + ] + edge + [ + source 70 + target 64 + value 1 + ] + edge + [ + source 70 + target 26 + value 4 + ] + edge + [ + source 71 + target 4 + value 5 + ] + edge + [ + source 71 + target 40 + value 4 + ] + edge + [ + source 71 + target 41 + value 1 + ] + edge + [ + source 71 + target 18 + value 2 + ] + edge + [ + source 71 + target 55 + value 6 + ] + edge + [ + source 71 + target 14 + value 2 + ] + edge + [ + source 71 + target 20 + value 1 + ] + edge + [ + source 71 + target 21 + value 3 + ] + edge + [ + source 71 + target 22 + value 2 + ] + edge + [ + source 71 + target 90 + value 1 + ] + edge + [ + source 71 + target 43 + value 3 + ] + edge + [ + source 71 + target 93 + value 3 + ] + edge + [ + source 71 + target 36 + value 4 + ] + edge + [ + source 71 + target 94 + value 1 + ] + edge + [ + source 71 + target 89 + value 1 + ] + edge + [ + source 71 + target 56 + value 2 + ] + edge + [ + source 71 + target 83 + value 2 + ] + edge + [ + source 71 + target 76 + value 2 + ] + edge + [ + source 72 + target 114 + value 1 + ] + edge + [ + source 72 + target 2 + value 3 + ] + edge + [ + source 72 + target 18 + value 1 + ] + edge + [ + source 72 + target 132 + value 4 + ] + edge + [ + source 72 + target 130 + value 5 + ] + edge + [ + source 72 + target 214 + value 1 + ] + edge + [ + source 72 + target 93 + value 1 + ] + edge + [ + source 73 + target 13 + value 1 + ] + edge + [ + source 73 + target 6 + value 1 + ] + edge + [ + source 73 + target 75 + value 1 + ] + edge + [ + source 73 + target 44 + value 3 + ] + edge + [ + source 74 + target 13 + value 2 + ] + edge + [ + source 74 + target 6 + value 1 + ] + edge + [ + source 74 + target 44 + value 3 + ] + edge + [ + source 75 + target 13 + value 8 + ] + edge + [ + source 75 + target 6 + value 4 + ] + edge + [ + source 75 + target 209 + value 1 + ] + edge + [ + source 75 + target 23 + value 2 + ] + edge + [ + source 75 + target 73 + value 2 + ] + edge + [ + source 75 + target 44 + value 3 + ] + edge + [ + source 76 + target 4 + value 3 + ] + edge + [ + source 76 + target 14 + value 1 + ] + edge + [ + source 76 + target 35 + value 4 + ] + edge + [ + source 76 + target 43 + value 5 + ] + edge + [ + source 76 + target 73 + value 1 + ] + edge + [ + source 76 + target 74 + value 2 + ] + edge + [ + source 77 + target 42 + value 1 + ] + edge + [ + source 77 + target 13 + value 3 + ] + edge + [ + source 77 + target 6 + value 7 + ] + edge + [ + source 77 + target 34 + value 1 + ] + edge + [ + source 77 + target 35 + value 1 + ] + edge + [ + source 77 + target 74 + value 4 + ] + edge + [ + source 77 + target 44 + value 2 + ] + edge + [ + source 78 + target 190 + value 1 + ] + edge + [ + source 79 + target 190 + value 1 + ] + edge + [ + source 80 + target 190 + value 1 + ] + edge + [ + source 81 + target 190 + value 1 + ] + edge + [ + source 82 + target 3 + value 2 + ] + edge + [ + source 82 + target 4 + value 2 + ] + edge + [ + source 82 + target 7 + value 1 + ] + edge + [ + source 82 + target 23 + value 2 + ] + edge + [ + source 82 + target 46 + value 4 + ] + edge + [ + source 82 + target 73 + value 4 + ] + edge + [ + source 82 + target 74 + value 1 + ] + edge + [ + source 83 + target 84 + value 1 + ] + edge + [ + source 83 + target 71 + value 1 + ] + edge + [ + source 83 + target 41 + value 1 + ] + edge + [ + source 83 + target 21 + value 1 + ] + edge + [ + source 83 + target 35 + value 1 + ] + edge + [ + source 83 + target 25 + value 1 + ] + edge + [ + source 83 + target 85 + value 1 + ] + edge + [ + source 83 + target 65 + value 4 + ] + edge + [ + source 84 + target 151 + value 1 + ] + edge + [ + source 84 + target 152 + value 2 + ] + edge + [ + source 84 + target 155 + value 2 + ] + edge + [ + source 84 + target 156 + value 2 + ] + edge + [ + source 84 + target 157 + value 1 + ] + edge + [ + source 84 + target 158 + value 2 + ] + edge + [ + source 84 + target 2 + value 25 + ] + edge + [ + source 84 + target 86 + value 2 + ] + edge + [ + source 84 + target 118 + value 3 + ] + edge + [ + source 84 + target 4 + value 3 + ] + edge + [ + source 84 + target 187 + value 1 + ] + edge + [ + source 84 + target 163 + value 1 + ] + edge + [ + source 84 + target 142 + value 1 + ] + edge + [ + source 84 + target 175 + value 1 + ] + edge + [ + source 84 + target 180 + value 1 + ] + edge + [ + source 84 + target 188 + value 1 + ] + edge + [ + source 84 + target 189 + value 1 + ] + edge + [ + source 84 + target 190 + value 6 + ] + edge + [ + source 85 + target 12 + value 1 + ] + edge + [ + source 85 + target 60 + value 3 + ] + edge + [ + source 85 + target 211 + value 2 + ] + edge + [ + source 85 + target 44 + value 9 + ] + edge + [ + source 86 + target 151 + value 1 + ] + edge + [ + source 86 + target 152 + value 2 + ] + edge + [ + source 86 + target 155 + value 2 + ] + edge + [ + source 86 + target 156 + value 2 + ] + edge + [ + source 86 + target 157 + value 1 + ] + edge + [ + source 86 + target 158 + value 2 + ] + edge + [ + source 86 + target 12 + value 25 + ] + edge + [ + source 86 + target 84 + value 2 + ] + edge + [ + source 86 + target 117 + value 3 + ] + edge + [ + source 86 + target 3 + value 3 + ] + edge + [ + source 86 + target 187 + value 1 + ] + edge + [ + source 86 + target 163 + value 1 + ] + edge + [ + source 86 + target 191 + value 1 + ] + edge + [ + source 86 + target 175 + value 1 + ] + edge + [ + source 86 + target 180 + value 1 + ] + edge + [ + source 86 + target 188 + value 1 + ] + edge + [ + source 86 + target 189 + value 1 + ] + edge + [ + source 86 + target 190 + value 6 + ] + edge + [ + source 87 + target 106 + value 1 + ] + edge + [ + source 87 + target 236 + value 1 + ] + edge + [ + source 87 + target 109 + value 1 + ] + edge + [ + source 87 + target 2 + value 1 + ] + edge + [ + source 87 + target 86 + value 1 + ] + edge + [ + source 87 + target 117 + value 1 + ] + edge + [ + source 87 + target 4 + value 3 + ] + edge + [ + source 87 + target 119 + value 1 + ] + edge + [ + source 87 + target 103 + value 1 + ] + edge + [ + source 87 + target 47 + value 3 + ] + edge + [ + source 87 + target 48 + value 3 + ] + edge + [ + source 87 + target 43 + value 1 + ] + edge + [ + source 87 + target 46 + value 4 + ] + edge + [ + source 87 + target 52 + value 1 + ] + edge + [ + source 87 + target 44 + value 3 + ] + edge + [ + source 88 + target 190 + value 1 + ] + edge + [ + source 89 + target 2 + value 1 + ] + edge + [ + source 89 + target 60 + value 4 + ] + edge + [ + source 89 + target 212 + value 2 + ] + edge + [ + source 89 + target 44 + value 9 + ] + edge + [ + source 90 + target 3 + value 8 + ] + edge + [ + source 90 + target 4 + value 6 + ] + edge + [ + source 90 + target 213 + value 1 + ] + edge + [ + source 90 + target 218 + value 3 + ] + edge + [ + source 90 + target 187 + value 2 + ] + edge + [ + source 90 + target 71 + value 1 + ] + edge + [ + source 90 + target 58 + value 2 + ] + edge + [ + source 90 + target 15 + value 2 + ] + edge + [ + source 90 + target 27 + value 1 + ] + edge + [ + source 90 + target 31 + value 1 + ] + edge + [ + source 90 + target 14 + value 3 + ] + edge + [ + source 90 + target 7 + value 6 + ] + edge + [ + source 90 + target 132 + value 2 + ] + edge + [ + source 90 + target 130 + value 4 + ] + edge + [ + source 90 + target 23 + value 1 + ] + edge + [ + source 90 + target 47 + value 2 + ] + edge + [ + source 90 + target 48 + value 5 + ] + edge + [ + source 90 + target 73 + value 1 + ] + edge + [ + source 90 + target 74 + value 1 + ] + edge + [ + source 90 + target 77 + value 1 + ] + edge + [ + source 90 + target 75 + value 1 + ] + edge + [ + source 90 + target 91 + value 1 + ] + edge + [ + source 91 + target 12 + value 1 + ] + edge + [ + source 91 + target 3 + value 6 + ] + edge + [ + source 91 + target 29 + value 1 + ] + edge + [ + source 91 + target 6 + value 1 + ] + edge + [ + source 91 + target 7 + value 1 + ] + edge + [ + source 91 + target 23 + value 1 + ] + edge + [ + source 91 + target 35 + value 4 + ] + edge + [ + source 91 + target 43 + value 3 + ] + edge + [ + source 91 + target 78 + value 1 + ] + edge + [ + source 91 + target 79 + value 1 + ] + edge + [ + source 91 + target 77 + value 3 + ] + edge + [ + source 92 + target 3 + value 2 + ] + edge + [ + source 92 + target 4 + value 2 + ] + edge + [ + source 92 + target 48 + value 1 + ] + edge + [ + source 92 + target 39 + value 1 + ] + edge + [ + source 92 + target 39 + value 2 + ] + edge + [ + source 93 + target 101 + value 1 + ] + edge + [ + source 93 + target 114 + value 1 + ] + edge + [ + source 93 + target 216 + value 1 + ] + edge + [ + source 93 + target 235 + value 1 + ] + edge + [ + source 93 + target 141 + value 1 + ] + edge + [ + source 93 + target 12 + value 1 + ] + edge + [ + source 93 + target 86 + value 2 + ] + edge + [ + source 93 + target 3 + value 2 + ] + edge + [ + source 93 + target 71 + value 1 + ] + edge + [ + source 93 + target 47 + value 1 + ] + edge + [ + source 93 + target 48 + value 3 + ] + edge + [ + source 93 + target 43 + value 4 + ] + edge + [ + source 93 + target 36 + value 1 + ] + edge + [ + source 93 + target 25 + value 1 + ] + edge + [ + source 93 + target 78 + value 2 + ] + edge + [ + source 93 + target 104 + value 2 + ] + edge + [ + source 93 + target 65 + value 1 + ] + edge + [ + source 93 + target 44 + value 5 + ] + edge + [ + source 94 + target 190 + value 1 + ] + edge + [ + source 95 + target 63 + value 1 + ] + edge + [ + source 95 + target 67 + value 1 + ] + edge + [ + source 95 + target 240 + value 1 + ] + edge + [ + source 95 + target 12 + value 5 + ] + edge + [ + source 95 + target 198 + value 1 + ] + edge + [ + source 95 + target 119 + value 2 + ] + edge + [ + source 95 + target 142 + value 7 + ] + edge + [ + source 95 + target 125 + value 2 + ] + edge + [ + source 95 + target 172 + value 2 + ] + edge + [ + source 95 + target 207 + value 4 + ] + edge + [ + source 95 + target 204 + value 1 + ] + edge + [ + source 95 + target 72 + value 1 + ] + edge + [ + source 95 + target 241 + value 1 + ] + edge + [ + source 95 + target 61 + value 1 + ] + edge + [ + source 96 + target 97 + value 1 + ] + edge + [ + source 96 + target 60 + value 1 + ] + edge + [ + source 96 + target 44 + value 1 + ] + edge + [ + source 97 + target 166 + value 2 + ] + edge + [ + source 97 + target 227 + value 1 + ] + edge + [ + source 97 + target 221 + value 1 + ] + edge + [ + source 97 + target 44 + value 29 + ] + edge + [ + source 98 + target 2 + value 2 + ] + edge + [ + source 98 + target 84 + value 4 + ] + edge + [ + source 98 + target 86 + value 1 + ] + edge + [ + source 98 + target 118 + value 4 + ] + edge + [ + source 98 + target 3 + value 4 + ] + edge + [ + source 98 + target 202 + value 1 + ] + edge + [ + source 98 + target 198 + value 1 + ] + edge + [ + source 98 + target 119 + value 1 + ] + edge + [ + source 98 + target 206 + value 1 + ] + edge + [ + source 98 + target 191 + value 1 + ] + edge + [ + source 98 + target 125 + value 2 + ] + edge + [ + source 98 + target 172 + value 4 + ] + edge + [ + source 98 + target 207 + value 1 + ] + edge + [ + source 98 + target 145 + value 1 + ] + edge + [ + source 98 + target 186 + value 1 + ] + edge + [ + source 99 + target 107 + value 1 + ] + edge + [ + source 99 + target 2 + value 1 + ] + edge + [ + source 99 + target 4 + value 3 + ] + edge + [ + source 99 + target 6 + value 10 + ] + edge + [ + source 99 + target 7 + value 12 + ] + edge + [ + source 99 + target 52 + value 1 + ] + edge + [ + source 100 + target 101 + value 1 + ] + edge + [ + source 100 + target 102 + value 11 + ] + edge + [ + source 100 + target 11 + value 3 + ] + edge + [ + source 100 + target 19 + value 1 + ] + edge + [ + source 100 + target 13 + value 12 + ] + edge + [ + source 100 + target 103 + value 2 + ] + edge + [ + source 100 + target 104 + value 2 + ] + edge + [ + source 101 + target 1 + value 1 + ] + edge + [ + source 101 + target 114 + value 2 + ] + edge + [ + source 101 + target 2 + value 1 + ] + edge + [ + source 101 + target 84 + value 4 + ] + edge + [ + source 101 + target 86 + value 5 + ] + edge + [ + source 101 + target 3 + value 1 + ] + edge + [ + source 101 + target 98 + value 4 + ] + edge + [ + source 101 + target 224 + value 1 + ] + edge + [ + source 101 + target 132 + value 3 + ] + edge + [ + source 101 + target 22 + value 1 + ] + edge + [ + source 101 + target 22 + value 1 + ] + edge + [ + source 101 + target 77 + value 2 + ] + edge + [ + source 102 + target 100 + value 2 + ] + edge + [ + source 102 + target 113 + value 2 + ] + edge + [ + source 102 + target 1 + value 4 + ] + edge + [ + source 102 + target 114 + value 7 + ] + edge + [ + source 102 + target 135 + value 1 + ] + edge + [ + source 102 + target 136 + value 1 + ] + edge + [ + source 102 + target 4 + value 4 + ] + edge + [ + source 102 + target 137 + value 1 + ] + edge + [ + source 102 + target 13 + value 7 + ] + edge + [ + source 102 + target 59 + value 1 + ] + edge + [ + source 102 + target 132 + value 3 + ] + edge + [ + source 102 + target 89 + value 4 + ] + edge + [ + source 102 + target 104 + value 7 + ] + edge + [ + source 103 + target 102 + value 6 + ] + edge + [ + source 103 + target 108 + value 5 + ] + edge + [ + source 103 + target 201 + value 1 + ] + edge + [ + source 103 + target 11 + value 1 + ] + edge + [ + source 103 + target 5 + value 1 + ] + edge + [ + source 103 + target 137 + value 3 + ] + edge + [ + source 103 + target 191 + value 1 + ] + edge + [ + source 103 + target 13 + value 6 + ] + edge + [ + source 103 + target 6 + value 1 + ] + edge + [ + source 103 + target 65 + value 5 + ] + edge + [ + source 103 + target 52 + value 2 + ] + edge + [ + source 104 + target 222 + value 1 + ] + edge + [ + source 104 + target 64 + value 5 + ] + edge + [ + source 104 + target 131 + value 3 + ] + edge + [ + source 104 + target 44 + value 4 + ] + edge + [ + source 105 + target 106 + value 1 + ] + edge + [ + source 105 + target 107 + value 1 + ] + edge + [ + source 105 + target 108 + value 8 + ] + edge + [ + source 105 + target 109 + value 1 + ] + edge + [ + source 105 + target 99 + value 3 + ] + edge + [ + source 105 + target 110 + value 2 + ] + edge + [ + source 105 + target 111 + value 1 + ] + edge + [ + source 105 + target 6 + value 12 + ] + edge + [ + source 105 + target 9 + value 2 + ] + edge + [ + source 105 + target 103 + value 3 + ] + edge + [ + source 105 + target 89 + value 1 + ] + edge + [ + source 105 + target 112 + value 2 + ] + edge + [ + source 105 + target 52 + value 1 + ] + edge + [ + source 106 + target 84 + value 3 + ] + edge + [ + source 106 + target 86 + value 4 + ] + edge + [ + source 106 + target 3 + value 1 + ] + edge + [ + source 106 + target 119 + value 3 + ] + edge + [ + source 106 + target 130 + value 4 + ] + edge + [ + source 106 + target 33 + value 1 + ] + edge + [ + source 106 + target 209 + value 1 + ] + edge + [ + source 106 + target 75 + value 2 + ] + edge + [ + source 107 + target 105 + value 1 + ] + edge + [ + source 107 + target 108 + value 6 + ] + edge + [ + source 107 + target 139 + value 1 + ] + edge + [ + source 107 + target 191 + value 1 + ] + edge + [ + source 107 + target 6 + value 4 + ] + edge + [ + source 107 + target 7 + value 4 + ] + edge + [ + source 108 + target 122 + value 1 + ] + edge + [ + source 108 + target 1 + value 9 + ] + edge + [ + source 108 + target 114 + value 1 + ] + edge + [ + source 108 + target 3 + value 4 + ] + edge + [ + source 108 + target 4 + value 1 + ] + edge + [ + source 108 + target 137 + value 2 + ] + edge + [ + source 108 + target 6 + value 7 + ] + edge + [ + source 108 + target 130 + value 4 + ] + edge + [ + source 108 + target 85 + value 5 + ] + edge + [ + source 108 + target 112 + value 3 + ] + edge + [ + source 109 + target 106 + value 1 + ] + edge + [ + source 109 + target 105 + value 2 + ] + edge + [ + source 109 + target 122 + value 9 + ] + edge + [ + source 109 + target 114 + value 3 + ] + edge + [ + source 109 + target 2 + value 5 + ] + edge + [ + source 109 + target 86 + value 3 + ] + edge + [ + source 109 + target 117 + value 5 + ] + edge + [ + source 109 + target 118 + value 1 + ] + edge + [ + source 109 + target 4 + value 2 + ] + edge + [ + source 109 + target 142 + value 1 + ] + edge + [ + source 109 + target 111 + value 1 + ] + edge + [ + source 109 + target 6 + value 2 + ] + edge + [ + source 109 + target 87 + value 2 + ] + edge + [ + source 110 + target 105 + value 4 + ] + edge + [ + source 110 + target 108 + value 4 + ] + edge + [ + source 110 + target 28 + value 1 + ] + edge + [ + source 110 + target 86 + value 2 + ] + edge + [ + source 110 + target 6 + value 1 + ] + edge + [ + source 110 + target 103 + value 2 + ] + edge + [ + source 110 + target 112 + value 1 + ] + edge + [ + source 111 + target 105 + value 1 + ] + edge + [ + source 111 + target 201 + value 1 + ] + edge + [ + source 111 + target 12 + value 1 + ] + edge + [ + source 111 + target 2 + value 2 + ] + edge + [ + source 111 + target 84 + value 3 + ] + edge + [ + source 111 + target 86 + value 7 + ] + edge + [ + source 111 + target 3 + value 1 + ] + edge + [ + source 111 + target 198 + value 3 + ] + edge + [ + source 111 + target 187 + value 4 + ] + edge + [ + source 111 + target 233 + value 1 + ] + edge + [ + source 111 + target 193 + value 2 + ] + edge + [ + source 111 + target 125 + value 5 + ] + edge + [ + source 111 + target 172 + value 7 + ] + edge + [ + source 111 + target 145 + value 1 + ] + edge + [ + source 111 + target 6 + value 2 + ] + edge + [ + source 111 + target 9 + value 1 + ] + edge + [ + source 111 + target 130 + value 1 + ] + edge + [ + source 111 + target 190 + value 3 + ] + edge + [ + source 112 + target 223 + value 3 + ] + edge + [ + source 112 + target 64 + value 3 + ] + edge + [ + source 112 + target 72 + value 4 + ] + edge + [ + source 112 + target 44 + value 4 + ] + edge + [ + source 113 + target 101 + value 1 + ] + edge + [ + source 113 + target 1 + value 11 + ] + edge + [ + source 113 + target 149 + value 2 + ] + edge + [ + source 113 + target 102 + value 2 + ] + edge + [ + source 113 + target 115 + value 2 + ] + edge + [ + source 113 + target 140 + value 1 + ] + edge + [ + source 113 + target 141 + value 3 + ] + edge + [ + source 113 + target 124 + value 1 + ] + edge + [ + source 113 + target 191 + value 1 + ] + edge + [ + source 113 + target 215 + value 1 + ] + edge + [ + source 114 + target 2 + value 1 + ] + edge + [ + source 114 + target 86 + value 3 + ] + edge + [ + source 114 + target 3 + value 1 + ] + edge + [ + source 114 + target 13 + value 1 + ] + edge + [ + source 114 + target 14 + value 4 + ] + edge + [ + source 114 + target 132 + value 14 + ] + edge + [ + source 114 + target 130 + value 1 + ] + edge + [ + source 114 + target 131 + value 1 + ] + edge + [ + source 114 + target 73 + value 3 + ] + edge + [ + source 114 + target 133 + value 1 + ] + edge + [ + source 115 + target 126 + value 3 + ] + edge + [ + source 115 + target 128 + value 1 + ] + edge + [ + source 115 + target 113 + value 4 + ] + edge + [ + source 115 + target 122 + value 3 + ] + edge + [ + source 115 + target 1 + value 1 + ] + edge + [ + source 115 + target 114 + value 7 + ] + edge + [ + source 115 + target 10 + value 4 + ] + edge + [ + source 115 + target 107 + value 12 + ] + edge + [ + source 115 + target 139 + value 1 + ] + edge + [ + source 115 + target 138 + value 1 + ] + edge + [ + source 115 + target 124 + value 2 + ] + edge + [ + source 116 + target 100 + value 3 + ] + edge + [ + source 116 + target 113 + value 7 + ] + edge + [ + source 116 + target 1 + value 5 + ] + edge + [ + source 116 + target 141 + value 1 + ] + edge + [ + source 116 + target 12 + value 2 + ] + edge + [ + source 116 + target 84 + value 6 + ] + edge + [ + source 116 + target 117 + value 2 + ] + edge + [ + source 116 + target 118 + value 4 + ] + edge + [ + source 116 + target 13 + value 4 + ] + edge + [ + source 116 + target 132 + value 1 + ] + edge + [ + source 116 + target 22 + value 1 + ] + edge + [ + source 116 + target 103 + value 1 + ] + edge + [ + source 117 + target 151 + value 1 + ] + edge + [ + source 117 + target 152 + value 2 + ] + edge + [ + source 117 + target 153 + value 2 + ] + edge + [ + source 117 + target 156 + value 1 + ] + edge + [ + source 117 + target 157 + value 1 + ] + edge + [ + source 117 + target 12 + value 70 + ] + edge + [ + source 117 + target 84 + value 1 + ] + edge + [ + source 117 + target 118 + value 2 + ] + edge + [ + source 117 + target 159 + value 3 + ] + edge + [ + source 117 + target 160 + value 2 + ] + edge + [ + source 117 + target 161 + value 6 + ] + edge + [ + source 117 + target 162 + value 4 + ] + edge + [ + source 117 + target 163 + value 3 + ] + edge + [ + source 117 + target 166 + value 1 + ] + edge + [ + source 117 + target 167 + value 1 + ] + edge + [ + source 117 + target 192 + value 1 + ] + edge + [ + source 117 + target 193 + value 1 + ] + edge + [ + source 117 + target 170 + value 3 + ] + edge + [ + source 117 + target 194 + value 1 + ] + edge + [ + source 117 + target 125 + value 1 + ] + edge + [ + source 117 + target 173 + value 3 + ] + edge + [ + source 117 + target 186 + value 6 + ] + edge + [ + source 117 + target 176 + value 1 + ] + edge + [ + source 117 + target 178 + value 1 + ] + edge + [ + source 117 + target 179 + value 3 + ] + edge + [ + source 117 + target 181 + value 1 + ] + edge + [ + source 117 + target 182 + value 2 + ] + edge + [ + source 118 + target 151 + value 1 + ] + edge + [ + source 118 + target 152 + value 2 + ] + edge + [ + source 118 + target 153 + value 2 + ] + edge + [ + source 118 + target 156 + value 1 + ] + edge + [ + source 118 + target 157 + value 1 + ] + edge + [ + source 118 + target 2 + value 70 + ] + edge + [ + source 118 + target 86 + value 1 + ] + edge + [ + source 118 + target 117 + value 2 + ] + edge + [ + source 118 + target 159 + value 3 + ] + edge + [ + source 118 + target 160 + value 2 + ] + edge + [ + source 118 + target 161 + value 6 + ] + edge + [ + source 118 + target 162 + value 4 + ] + edge + [ + source 118 + target 163 + value 3 + ] + edge + [ + source 118 + target 166 + value 1 + ] + edge + [ + source 118 + target 167 + value 1 + ] + edge + [ + source 118 + target 192 + value 1 + ] + edge + [ + source 118 + target 193 + value 1 + ] + edge + [ + source 118 + target 184 + value 3 + ] + edge + [ + source 118 + target 194 + value 1 + ] + edge + [ + source 118 + target 172 + value 1 + ] + edge + [ + source 118 + target 173 + value 3 + ] + edge + [ + source 118 + target 174 + value 6 + ] + edge + [ + source 118 + target 176 + value 1 + ] + edge + [ + source 118 + target 178 + value 1 + ] + edge + [ + source 118 + target 179 + value 3 + ] + edge + [ + source 118 + target 181 + value 1 + ] + edge + [ + source 118 + target 182 + value 2 + ] + edge + [ + source 119 + target 12 + value 2 + ] + edge + [ + source 119 + target 84 + value 1 + ] + edge + [ + source 119 + target 86 + value 5 + ] + edge + [ + source 119 + target 117 + value 3 + ] + edge + [ + source 119 + target 4 + value 4 + ] + edge + [ + source 119 + target 199 + value 1 + ] + edge + [ + source 119 + target 123 + value 1 + ] + edge + [ + source 119 + target 142 + value 1 + ] + edge + [ + source 119 + target 125 + value 1 + ] + edge + [ + source 119 + target 172 + value 3 + ] + edge + [ + source 119 + target 204 + value 2 + ] + edge + [ + source 119 + target 148 + value 1 + ] + edge + [ + source 119 + target 205 + value 1 + ] + edge + [ + source 119 + target 90 + value 2 + ] + edge + [ + source 119 + target 174 + value 1 + ] + edge + [ + source 120 + target 100 + value 8 + ] + edge + [ + source 120 + target 114 + value 1 + ] + edge + [ + source 120 + target 102 + value 7 + ] + edge + [ + source 120 + target 84 + value 1 + ] + edge + [ + source 120 + target 13 + value 3 + ] + edge + [ + source 120 + target 85 + value 1 + ] + edge + [ + source 121 + target 122 + value 10 + ] + edge + [ + source 121 + target 114 + value 11 + ] + edge + [ + source 121 + target 115 + value 1 + ] + edge + [ + source 121 + target 109 + value 3 + ] + edge + [ + source 121 + target 2 + value 2 + ] + edge + [ + source 121 + target 84 + value 1 + ] + edge + [ + source 121 + target 86 + value 2 + ] + edge + [ + source 121 + target 117 + value 5 + ] + edge + [ + source 121 + target 118 + value 2 + ] + edge + [ + source 121 + target 123 + value 1 + ] + edge + [ + source 121 + target 98 + value 2 + ] + edge + [ + source 121 + target 124 + value 3 + ] + edge + [ + source 121 + target 31 + value 1 + ] + edge + [ + source 121 + target 125 + value 2 + ] + edge + [ + source 121 + target 20 + value 1 + ] + edge + [ + source 121 + target 21 + value 1 + ] + edge + [ + source 122 + target 106 + value 1 + ] + edge + [ + source 122 + target 121 + value 1 + ] + edge + [ + source 122 + target 114 + value 12 + ] + edge + [ + source 122 + target 108 + value 1 + ] + edge + [ + source 122 + target 115 + value 1 + ] + edge + [ + source 122 + target 139 + value 1 + ] + edge + [ + source 122 + target 124 + value 1 + ] + edge + [ + source 122 + target 205 + value 2 + ] + edge + [ + source 123 + target 121 + value 2 + ] + edge + [ + source 123 + target 201 + value 1 + ] + edge + [ + source 123 + target 84 + value 1 + ] + edge + [ + source 123 + target 118 + value 1 + ] + edge + [ + source 123 + target 202 + value 5 + ] + edge + [ + source 123 + target 198 + value 2 + ] + edge + [ + source 123 + target 119 + value 1 + ] + edge + [ + source 123 + target 98 + value 2 + ] + edge + [ + source 123 + target 203 + value 4 + ] + edge + [ + source 123 + target 145 + value 2 + ] + edge + [ + source 123 + target 8 + value 1 + ] + edge + [ + source 123 + target 103 + value 3 + ] + edge + [ + source 123 + target 89 + value 1 + ] + edge + [ + source 123 + target 104 + value 1 + ] + edge + [ + source 123 + target 200 + value 1 + ] + edge + [ + source 124 + target 121 + value 1 + ] + edge + [ + source 124 + target 122 + value 2 + ] + edge + [ + source 124 + target 1 + value 1 + ] + edge + [ + source 124 + target 114 + value 3 + ] + edge + [ + source 124 + target 10 + value 4 + ] + edge + [ + source 124 + target 107 + value 9 + ] + edge + [ + source 124 + target 138 + value 2 + ] + edge + [ + source 125 + target 151 + value 1 + ] + edge + [ + source 125 + target 154 + value 2 + ] + edge + [ + source 125 + target 12 + value 7 + ] + edge + [ + source 125 + target 2 + value 9 + ] + edge + [ + source 125 + target 84 + value 4 + ] + edge + [ + source 125 + target 86 + value 16 + ] + edge + [ + source 125 + target 117 + value 3 + ] + edge + [ + source 125 + target 118 + value 6 + ] + edge + [ + source 125 + target 3 + value 2 + ] + edge + [ + source 125 + target 4 + value 1 + ] + edge + [ + source 125 + target 119 + value 3 + ] + edge + [ + source 125 + target 187 + value 1 + ] + edge + [ + source 125 + target 160 + value 1 + ] + edge + [ + source 125 + target 232 + value 4 + ] + edge + [ + source 125 + target 168 + value 5 + ] + edge + [ + source 125 + target 192 + value 7 + ] + edge + [ + source 125 + target 169 + value 3 + ] + edge + [ + source 125 + target 253 + value 4 + ] + edge + [ + source 125 + target 137 + value 4 + ] + edge + [ + source 125 + target 184 + value 1 + ] + edge + [ + source 125 + target 171 + value 1 + ] + edge + [ + source 125 + target 172 + value 3 + ] + edge + [ + source 125 + target 42 + value 1 + ] + edge + [ + source 125 + target 256 + value 1 + ] + edge + [ + source 125 + target 96 + value 5 + ] + edge + [ + source 125 + target 90 + value 2 + ] + edge + [ + source 125 + target 78 + value 2 + ] + edge + [ + source 125 + target 254 + value 1 + ] + edge + [ + source 125 + target 262 + value 1 + ] + edge + [ + source 125 + target 263 + value 4 + ] + edge + [ + source 125 + target 264 + value 1 + ] + edge + [ + source 125 + target 265 + value 5 + ] + edge + [ + source 125 + target 266 + value 3 + ] + edge + [ + source 125 + target 267 + value 2 + ] + edge + [ + source 126 + target 127 + value 1 + ] + edge + [ + source 126 + target 10 + value 7 + ] + edge + [ + source 127 + target 126 + value 5 + ] + edge + [ + source 127 + target 113 + value 2 + ] + edge + [ + source 127 + target 1 + value 2 + ] + edge + [ + source 127 + target 115 + value 3 + ] + edge + [ + source 127 + target 5 + value 3 + ] + edge + [ + source 127 + target 14 + value 1 + ] + edge + [ + source 127 + target 96 + value 1 + ] + edge + [ + source 127 + target 39 + value 1 + ] + edge + [ + source 128 + target 107 + value 11 + ] + edge + [ + source 128 + target 115 + value 1 + ] + edge + [ + source 129 + target 63 + value 1 + ] + edge + [ + source 129 + target 67 + value 2 + ] + edge + [ + source 129 + target 1 + value 1 + ] + edge + [ + source 129 + target 114 + value 2 + ] + edge + [ + source 129 + target 12 + value 13 + ] + edge + [ + source 129 + target 2 + value 18 + ] + edge + [ + source 129 + target 84 + value 4 + ] + edge + [ + source 129 + target 86 + value 5 + ] + edge + [ + source 129 + target 117 + value 6 + ] + edge + [ + source 129 + target 118 + value 14 + ] + edge + [ + source 129 + target 137 + value 1 + ] + edge + [ + source 129 + target 224 + value 1 + ] + edge + [ + source 130 + target 106 + value 1 + ] + edge + [ + source 130 + target 1 + value 4 + ] + edge + [ + source 130 + target 12 + value 2 + ] + edge + [ + source 130 + target 84 + value 2 + ] + edge + [ + source 130 + target 86 + value 5 + ] + edge + [ + source 130 + target 119 + value 1 + ] + edge + [ + source 130 + target 213 + value 1 + ] + edge + [ + source 130 + target 7 + value 1 + ] + edge + [ + source 130 + target 90 + value 1 + ] + edge + [ + source 130 + target 47 + value 2 + ] + edge + [ + source 130 + target 48 + value 1 + ] + edge + [ + source 130 + target 210 + value 2 + ] + edge + [ + source 130 + target 211 + value 3 + ] + edge + [ + source 130 + target 212 + value 3 + ] + edge + [ + source 130 + target 73 + value 2 + ] + edge + [ + source 130 + target 74 + value 3 + ] + edge + [ + source 130 + target 44 + value 4 + ] + edge + [ + source 131 + target 1 + value 1 + ] + edge + [ + source 131 + target 12 + value 5 + ] + edge + [ + source 131 + target 132 + value 3 + ] + edge + [ + source 131 + target 130 + value 5 + ] + edge + [ + source 131 + target 93 + value 1 + ] + edge + [ + source 132 + target 12 + value 1 + ] + edge + [ + source 132 + target 84 + value 2 + ] + edge + [ + source 132 + target 86 + value 3 + ] + edge + [ + source 132 + target 14 + value 1 + ] + edge + [ + source 132 + target 90 + value 1 + ] + edge + [ + source 132 + target 47 + value 1 + ] + edge + [ + source 132 + target 48 + value 3 + ] + edge + [ + source 132 + target 210 + value 1 + ] + edge + [ + source 132 + target 72 + value 1 + ] + edge + [ + source 132 + target 211 + value 3 + ] + edge + [ + source 132 + target 212 + value 2 + ] + edge + [ + source 132 + target 74 + value 5 + ] + edge + [ + source 132 + target 77 + value 1 + ] + edge + [ + source 132 + target 44 + value 4 + ] + edge + [ + source 133 + target 114 + value 1 + ] + edge + [ + source 133 + target 219 + value 1 + ] + edge + [ + source 133 + target 132 + value 1 + ] + edge + [ + source 133 + target 214 + value 2 + ] + edge + [ + source 133 + target 131 + value 9 + ] + edge + [ + source 133 + target 72 + value 4 + ] + edge + [ + source 133 + target 173 + value 1 + ] + edge + [ + source 133 + target 196 + value 3 + ] + edge + [ + source 133 + target 178 + value 1 + ] + edge + [ + source 133 + target 179 + value 1 + ] + edge + [ + source 133 + target 200 + value 2 + ] + edge + [ + source 133 + target 197 + value 1 + ] + edge + [ + source 133 + target 44 + value 5 + ] + edge + [ + source 134 + target 128 + value 5 + ] + edge + [ + source 134 + target 135 + value 2 + ] + edge + [ + source 134 + target 28 + value 1 + ] + edge + [ + source 134 + target 99 + value 1 + ] + edge + [ + source 134 + target 0 + value 3 + ] + edge + [ + source 134 + target 14 + value 1 + ] + edge + [ + source 134 + target 7 + value 2 + ] + edge + [ + source 134 + target 39 + value 1 + ] + edge + [ + source 135 + target 105 + value 1 + ] + edge + [ + source 135 + target 113 + value 4 + ] + edge + [ + source 135 + target 1 + value 7 + ] + edge + [ + source 135 + target 114 + value 3 + ] + edge + [ + source 135 + target 10 + value 12 + ] + edge + [ + source 135 + target 107 + value 6 + ] + edge + [ + source 135 + target 138 + value 4 + ] + edge + [ + source 135 + target 124 + value 2 + ] + edge + [ + source 135 + target 6 + value 1 + ] + edge + [ + source 136 + target 1 + value 1 + ] + edge + [ + source 136 + target 10 + value 2 + ] + edge + [ + source 136 + target 102 + value 1 + ] + edge + [ + source 136 + target 135 + value 1 + ] + edge + [ + source 136 + target 115 + value 1 + ] + edge + [ + source 136 + target 141 + value 2 + ] + edge + [ + source 136 + target 138 + value 2 + ] + edge + [ + source 136 + target 124 + value 2 + ] + edge + [ + source 137 + target 102 + value 3 + ] + edge + [ + source 137 + target 201 + value 3 + ] + edge + [ + source 137 + target 11 + value 1 + ] + edge + [ + source 137 + target 99 + value 1 + ] + edge + [ + source 137 + target 12 + value 3 + ] + edge + [ + source 137 + target 84 + value 1 + ] + edge + [ + source 137 + target 3 + value 8 + ] + edge + [ + source 137 + target 4 + value 6 + ] + edge + [ + source 137 + target 232 + value 1 + ] + edge + [ + source 137 + target 168 + value 3 + ] + edge + [ + source 137 + target 192 + value 1 + ] + edge + [ + source 137 + target 169 + value 1 + ] + edge + [ + source 137 + target 253 + value 2 + ] + edge + [ + source 137 + target 185 + value 2 + ] + edge + [ + source 137 + target 125 + value 5 + ] + edge + [ + source 137 + target 42 + value 2 + ] + edge + [ + source 137 + target 13 + value 1 + ] + edge + [ + source 137 + target 6 + value 2 + ] + edge + [ + source 137 + target 130 + value 1 + ] + edge + [ + source 137 + target 103 + value 1 + ] + edge + [ + source 137 + target 72 + value 1 + ] + edge + [ + source 137 + target 211 + value 1 + ] + edge + [ + source 137 + target 212 + value 1 + ] + edge + [ + source 137 + target 89 + value 3 + ] + edge + [ + source 137 + target 85 + value 2 + ] + edge + [ + source 137 + target 104 + value 3 + ] + edge + [ + source 137 + target 112 + value 2 + ] + edge + [ + source 137 + target 177 + value 1 + ] + edge + [ + source 137 + target 133 + value 1 + ] + edge + [ + source 137 + target 254 + value 2 + ] + edge + [ + source 138 + target 113 + value 2 + ] + edge + [ + source 138 + target 122 + value 4 + ] + edge + [ + source 138 + target 1 + value 1 + ] + edge + [ + source 138 + target 114 + value 1 + ] + edge + [ + source 138 + target 10 + value 13 + ] + edge + [ + source 138 + target 12 + value 1 + ] + edge + [ + source 138 + target 13 + value 2 + ] + edge + [ + source 139 + target 105 + value 3 + ] + edge + [ + source 139 + target 128 + value 9 + ] + edge + [ + source 139 + target 107 + value 2 + ] + edge + [ + source 139 + target 108 + value 7 + ] + edge + [ + source 139 + target 135 + value 1 + ] + edge + [ + source 139 + target 115 + value 2 + ] + edge + [ + source 139 + target 110 + value 2 + ] + edge + [ + source 139 + target 205 + value 2 + ] + edge + [ + source 139 + target 103 + value 1 + ] + edge + [ + source 140 + target 113 + value 9 + ] + edge + [ + source 140 + target 1 + value 3 + ] + edge + [ + source 140 + target 127 + value 1 + ] + edge + [ + source 140 + target 141 + value 1 + ] + edge + [ + source 141 + target 113 + value 11 + ] + edge + [ + source 141 + target 1 + value 3 + ] + edge + [ + source 141 + target 149 + value 2 + ] + edge + [ + source 141 + target 144 + value 1 + ] + edge + [ + source 142 + target 1 + value 1 + ] + edge + [ + source 142 + target 114 + value 1 + ] + edge + [ + source 142 + target 102 + value 1 + ] + edge + [ + source 142 + target 108 + value 1 + ] + edge + [ + source 142 + target 157 + value 1 + ] + edge + [ + source 142 + target 116 + value 2 + ] + edge + [ + source 142 + target 117 + value 3 + ] + edge + [ + source 142 + target 202 + value 2 + ] + edge + [ + source 142 + target 119 + value 1 + ] + edge + [ + source 142 + target 120 + value 1 + ] + edge + [ + source 142 + target 95 + value 2 + ] + edge + [ + source 142 + target 163 + value 2 + ] + edge + [ + source 142 + target 164 + value 1 + ] + edge + [ + source 142 + target 169 + value 1 + ] + edge + [ + source 142 + target 191 + value 1 + ] + edge + [ + source 142 + target 204 + value 2 + ] + edge + [ + source 142 + target 148 + value 1 + ] + edge + [ + source 142 + target 205 + value 3 + ] + edge + [ + source 142 + target 130 + value 1 + ] + edge + [ + source 142 + target 87 + value 1 + ] + edge + [ + source 142 + target 173 + value 1 + ] + edge + [ + source 142 + target 186 + value 3 + ] + edge + [ + source 142 + target 181 + value 1 + ] + edge + [ + source 142 + target 182 + value 1 + ] + edge + [ + source 142 + target 238 + value 4 + ] + edge + [ + source 142 + target 245 + value 2 + ] + edge + [ + source 142 + target 244 + value 5 + ] + edge + [ + source 142 + target 44 + value 3 + ] + edge + [ + source 142 + target 44 + value 21 + ] + edge + [ + source 143 + target 122 + value 3 + ] + edge + [ + source 143 + target 135 + value 2 + ] + edge + [ + source 143 + target 109 + value 1 + ] + edge + [ + source 143 + target 138 + value 1 + ] + edge + [ + source 143 + target 124 + value 2 + ] + edge + [ + source 144 + target 141 + value 4 + ] + edge + [ + source 144 + target 145 + value 13 + ] + edge + [ + source 145 + target 113 + value 6 + ] + edge + [ + source 145 + target 144 + value 1 + ] + edge + [ + source 145 + target 141 + value 4 + ] + edge + [ + source 145 + target 187 + value 1 + ] + edge + [ + source 145 + target 219 + value 1 + ] + edge + [ + source 145 + target 195 + value 1 + ] + edge + [ + source 145 + target 193 + value 1 + ] + edge + [ + source 145 + target 191 + value 3 + ] + edge + [ + source 145 + target 93 + value 1 + ] + edge + [ + source 145 + target 200 + value 1 + ] + edge + [ + source 145 + target 39 + value 1 + ] + edge + [ + source 146 + target 147 + value 4 + ] + edge + [ + source 146 + target 142 + value 1 + ] + edge + [ + source 146 + target 148 + value 13 + ] + edge + [ + source 147 + target 122 + value 11 + ] + edge + [ + source 147 + target 114 + value 1 + ] + edge + [ + source 147 + target 150 + value 1 + ] + edge + [ + source 147 + target 139 + value 1 + ] + edge + [ + source 148 + target 122 + value 7 + ] + edge + [ + source 148 + target 147 + value 4 + ] + edge + [ + source 148 + target 199 + value 1 + ] + edge + [ + source 148 + target 187 + value 1 + ] + edge + [ + source 148 + target 219 + value 1 + ] + edge + [ + source 148 + target 195 + value 1 + ] + edge + [ + source 148 + target 193 + value 1 + ] + edge + [ + source 148 + target 142 + value 1 + ] + edge + [ + source 148 + target 205 + value 1 + ] + edge + [ + source 148 + target 200 + value 1 + ] + edge + [ + source 148 + target 39 + value 1 + ] + edge + [ + source 149 + target 113 + value 5 + ] + edge + [ + source 149 + target 216 + value 1 + ] + edge + [ + source 149 + target 140 + value 2 + ] + edge + [ + source 149 + target 141 + value 2 + ] + edge + [ + source 149 + target 86 + value 2 + ] + edge + [ + source 149 + target 117 + value 1 + ] + edge + [ + source 149 + target 118 + value 1 + ] + edge + [ + source 149 + target 4 + value 1 + ] + edge + [ + source 149 + target 202 + value 4 + ] + edge + [ + source 149 + target 199 + value 1 + ] + edge + [ + source 149 + target 198 + value 1 + ] + edge + [ + source 149 + target 123 + value 2 + ] + edge + [ + source 149 + target 119 + value 1 + ] + edge + [ + source 149 + target 145 + value 1 + ] + edge + [ + source 149 + target 215 + value 1 + ] + edge + [ + source 149 + target 104 + value 1 + ] + edge + [ + source 149 + target 39 + value 1 + ] + edge + [ + source 149 + target 39 + value 1 + ] + edge + [ + source 150 + target 122 + value 4 + ] + edge + [ + source 150 + target 28 + value 2 + ] + edge + [ + source 150 + target 146 + value 2 + ] + edge + [ + source 150 + target 147 + value 2 + ] + edge + [ + source 150 + target 118 + value 1 + ] + edge + [ + source 150 + target 202 + value 3 + ] + edge + [ + source 150 + target 199 + value 2 + ] + edge + [ + source 150 + target 98 + value 1 + ] + edge + [ + source 150 + target 191 + value 1 + ] + edge + [ + source 150 + target 142 + value 2 + ] + edge + [ + source 150 + target 205 + value 1 + ] + edge + [ + source 150 + target 87 + value 2 + ] + edge + [ + source 150 + target 39 + value 1 + ] + edge + [ + source 151 + target 159 + value 1 + ] + edge + [ + source 151 + target 200 + value 1 + ] + edge + [ + source 151 + target 44 + value 1 + ] + edge + [ + source 152 + target 165 + value 1 + ] + edge + [ + source 152 + target 249 + value 16 + ] + edge + [ + source 152 + target 268 + value 2 + ] + edge + [ + source 152 + target 44 + value 15 + ] + edge + [ + source 153 + target 161 + value 1 + ] + edge + [ + source 153 + target 166 + value 1 + ] + edge + [ + source 153 + target 167 + value 1 + ] + edge + [ + source 153 + target 249 + value 2 + ] + edge + [ + source 153 + target 227 + value 16 + ] + edge + [ + source 153 + target 44 + value 15 + ] + edge + [ + source 154 + target 160 + value 1 + ] + edge + [ + source 154 + target 197 + value 1 + ] + edge + [ + source 154 + target 44 + value 15 + ] + edge + [ + source 155 + target 161 + value 1 + ] + edge + [ + source 155 + target 197 + value 2 + ] + edge + [ + source 155 + target 189 + value 16 + ] + edge + [ + source 155 + target 44 + value 15 + ] + edge + [ + source 156 + target 189 + value 2 + ] + edge + [ + source 156 + target 231 + value 16 + ] + edge + [ + source 156 + target 44 + value 15 + ] + edge + [ + source 157 + target 162 + value 1 + ] + edge + [ + source 157 + target 231 + value 2 + ] + edge + [ + source 157 + target 234 + value 16 + ] + edge + [ + source 157 + target 44 + value 15 + ] + edge + [ + source 158 + target 163 + value 1 + ] + edge + [ + source 158 + target 234 + value 2 + ] + edge + [ + source 158 + target 269 + value 16 + ] + edge + [ + source 158 + target 44 + value 15 + ] + edge + [ + source 159 + target 200 + value 1 + ] + edge + [ + source 159 + target 197 + value 1 + ] + edge + [ + source 159 + target 44 + value 10 + ] + edge + [ + source 160 + target 219 + value 1 + ] + edge + [ + source 160 + target 200 + value 1 + ] + edge + [ + source 160 + target 197 + value 23 + ] + edge + [ + source 160 + target 189 + value 6 + ] + edge + [ + source 160 + target 44 + value 29 + ] + edge + [ + source 161 + target 162 + value 1 + ] + edge + [ + source 161 + target 233 + value 1 + ] + edge + [ + source 161 + target 189 + value 23 + ] + edge + [ + source 161 + target 231 + value 6 + ] + edge + [ + source 161 + target 44 + value 29 + ] + edge + [ + source 162 + target 163 + value 1 + ] + edge + [ + source 162 + target 275 + value 1 + ] + edge + [ + source 162 + target 231 + value 23 + ] + edge + [ + source 162 + target 234 + value 6 + ] + edge + [ + source 162 + target 269 + value 1 + ] + edge + [ + source 162 + target 44 + value 29 + ] + edge + [ + source 163 + target 164 + value 1 + ] + edge + [ + source 163 + target 276 + value 1 + ] + edge + [ + source 163 + target 269 + value 23 + ] + edge + [ + source 163 + target 271 + value 1 + ] + edge + [ + source 163 + target 44 + value 29 + ] + edge + [ + source 164 + target 165 + value 1 + ] + edge + [ + source 164 + target 276 + value 1 + ] + edge + [ + source 164 + target 273 + value 23 + ] + edge + [ + source 164 + target 268 + value 1 + ] + edge + [ + source 164 + target 44 + value 29 + ] + edge + [ + source 165 + target 166 + value 1 + ] + edge + [ + source 165 + target 97 + value 1 + ] + edge + [ + source 165 + target 249 + value 23 + ] + edge + [ + source 165 + target 227 + value 1 + ] + edge + [ + source 165 + target 44 + value 29 + ] + edge + [ + source 166 + target 167 + value 1 + ] + edge + [ + source 166 + target 97 + value 1 + ] + edge + [ + source 166 + target 227 + value 23 + ] + edge + [ + source 166 + target 221 + value 1 + ] + edge + [ + source 166 + target 44 + value 29 + ] + edge + [ + source 167 + target 97 + value 1 + ] + edge + [ + source 167 + target 227 + value 23 + ] + edge + [ + source 167 + target 221 + value 1 + ] + edge + [ + source 167 + target 44 + value 29 + ] + edge + [ + source 168 + target 156 + value 1 + ] + edge + [ + source 168 + target 233 + value 4 + ] + edge + [ + source 168 + target 275 + value 9 + ] + edge + [ + source 168 + target 189 + value 1 + ] + edge + [ + source 168 + target 231 + value 9 + ] + edge + [ + source 168 + target 234 + value 22 + ] + edge + [ + source 168 + target 269 + value 7 + ] + edge + [ + source 168 + target 44 + value 22 + ] + edge + [ + source 169 + target 270 + value 1 + ] + edge + [ + source 169 + target 272 + value 1 + ] + edge + [ + source 169 + target 276 + value 4 + ] + edge + [ + source 169 + target 277 + value 9 + ] + edge + [ + source 169 + target 234 + value 1 + ] + edge + [ + source 169 + target 269 + value 9 + ] + edge + [ + source 169 + target 271 + value 22 + ] + edge + [ + source 169 + target 273 + value 7 + ] + edge + [ + source 169 + target 44 + value 22 + ] + edge + [ + source 170 + target 12 + value 5 + ] + edge + [ + source 170 + target 2 + value 4 + ] + edge + [ + source 170 + target 117 + value 4 + ] + edge + [ + source 170 + target 118 + value 2 + ] + edge + [ + source 170 + target 119 + value 1 + ] + edge + [ + source 170 + target 255 + value 1 + ] + edge + [ + source 170 + target 207 + value 1 + ] + edge + [ + source 170 + target 256 + value 1 + ] + edge + [ + source 171 + target 213 + value 22 + ] + edge + [ + source 171 + target 137 + value 61 + ] + edge + [ + source 171 + target 185 + value 1 + ] + edge + [ + source 171 + target 125 + value 2 + ] + edge + [ + source 171 + target 217 + value 1 + ] + edge + [ + source 171 + target 42 + value 2 + ] + edge + [ + source 171 + target 246 + value 1 + ] + edge + [ + source 171 + target 190 + value 4 + ] + edge + [ + source 172 + target 201 + value 1 + ] + edge + [ + source 172 + target 151 + value 1 + ] + edge + [ + source 172 + target 154 + value 2 + ] + edge + [ + source 172 + target 12 + value 8 + ] + edge + [ + source 172 + target 2 + value 10 + ] + edge + [ + source 172 + target 84 + value 9 + ] + edge + [ + source 172 + target 86 + value 6 + ] + edge + [ + source 172 + target 117 + value 6 + ] + edge + [ + source 172 + target 118 + value 1 + ] + edge + [ + source 172 + target 3 + value 3 + ] + edge + [ + source 172 + target 4 + value 1 + ] + edge + [ + source 172 + target 187 + value 1 + ] + edge + [ + source 172 + target 160 + value 1 + ] + edge + [ + source 172 + target 232 + value 4 + ] + edge + [ + source 172 + target 168 + value 5 + ] + edge + [ + source 172 + target 192 + value 7 + ] + edge + [ + source 172 + target 169 + value 3 + ] + edge + [ + source 172 + target 253 + value 4 + ] + edge + [ + source 172 + target 137 + value 4 + ] + edge + [ + source 172 + target 129 + value 1 + ] + edge + [ + source 172 + target 170 + value 1 + ] + edge + [ + source 172 + target 185 + value 1 + ] + edge + [ + source 172 + target 125 + value 1 + ] + edge + [ + source 172 + target 42 + value 1 + ] + edge + [ + source 172 + target 220 + value 1 + ] + edge + [ + source 172 + target 96 + value 4 + ] + edge + [ + source 172 + target 79 + value 1 + ] + edge + [ + source 172 + target 254 + value 1 + ] + edge + [ + source 172 + target 262 + value 1 + ] + edge + [ + source 172 + target 263 + value 4 + ] + edge + [ + source 172 + target 264 + value 1 + ] + edge + [ + source 172 + target 265 + value 5 + ] + edge + [ + source 172 + target 266 + value 3 + ] + edge + [ + source 172 + target 267 + value 2 + ] + edge + [ + source 173 + target 178 + value 1 + ] + edge + [ + source 174 + target 190 + value 1 + ] + edge + [ + source 175 + target 277 + value 8 + ] + edge + [ + source 175 + target 176 + value 1 + ] + edge + [ + source 175 + target 44 + value 10 + ] + edge + [ + source 176 + target 97 + value 8 + ] + edge + [ + source 176 + target 177 + value 1 + ] + edge + [ + source 176 + target 249 + value 3 + ] + edge + [ + source 176 + target 227 + value 2 + ] + edge + [ + source 176 + target 44 + value 10 + ] + edge + [ + source 177 + target 153 + value 2 + ] + edge + [ + source 177 + target 166 + value 3 + ] + edge + [ + source 177 + target 167 + value 5 + ] + edge + [ + source 177 + target 253 + value 4 + ] + edge + [ + source 177 + target 97 + value 2 + ] + edge + [ + source 177 + target 170 + value 2 + ] + edge + [ + source 177 + target 125 + value 2 + ] + edge + [ + source 177 + target 172 + value 3 + ] + edge + [ + source 177 + target 176 + value 1 + ] + edge + [ + source 177 + target 221 + value 2 + ] + edge + [ + source 177 + target 183 + value 11 + ] + edge + [ + source 177 + target 44 + value 14 + ] + edge + [ + source 178 + target 219 + value 8 + ] + edge + [ + source 178 + target 179 + value 1 + ] + edge + [ + source 178 + target 200 + value 1 + ] + edge + [ + source 178 + target 197 + value 3 + ] + edge + [ + source 178 + target 44 + value 10 + ] + edge + [ + source 179 + target 219 + value 18 + ] + edge + [ + source 179 + target 233 + value 11 + ] + edge + [ + source 179 + target 180 + value 1 + ] + edge + [ + source 179 + target 197 + value 3 + ] + edge + [ + source 179 + target 189 + value 2 + ] + edge + [ + source 179 + target 44 + value 31 + ] + edge + [ + source 180 + target 233 + value 8 + ] + edge + [ + source 180 + target 181 + value 1 + ] + edge + [ + source 180 + target 231 + value 1 + ] + edge + [ + source 180 + target 44 + value 10 + ] + edge + [ + source 181 + target 275 + value 8 + ] + edge + [ + source 181 + target 182 + value 1 + ] + edge + [ + source 181 + target 231 + value 3 + ] + edge + [ + source 181 + target 234 + value 2 + ] + edge + [ + source 181 + target 44 + value 10 + ] + edge + [ + source 182 + target 275 + value 8 + ] + edge + [ + source 182 + target 188 + value 1 + ] + edge + [ + source 182 + target 44 + value 10 + ] + edge + [ + source 183 + target 177 + value 1 + ] + edge + [ + source 183 + target 44 + value 8 + ] + edge + [ + source 184 + target 12 + value 3 + ] + edge + [ + source 184 + target 2 + value 7 + ] + edge + [ + source 184 + target 117 + value 1 + ] + edge + [ + source 184 + target 118 + value 3 + ] + edge + [ + source 184 + target 98 + value 1 + ] + edge + [ + source 184 + target 194 + value 1 + ] + edge + [ + source 184 + target 172 + value 3 + ] + edge + [ + source 184 + target 42 + value 1 + ] + edge + [ + source 184 + target 256 + value 1 + ] + edge + [ + source 185 + target 218 + value 22 + ] + edge + [ + source 185 + target 137 + value 61 + ] + edge + [ + source 185 + target 171 + value 1 + ] + edge + [ + source 185 + target 172 + value 2 + ] + edge + [ + source 185 + target 217 + value 1 + ] + edge + [ + source 185 + target 42 + value 2 + ] + edge + [ + source 185 + target 246 + value 1 + ] + edge + [ + source 185 + target 190 + value 4 + ] + edge + [ + source 186 + target 190 + value 1 + ] + edge + [ + source 187 + target 151 + value 1 + ] + edge + [ + source 187 + target 4 + value 1 + ] + edge + [ + source 187 + target 199 + value 1 + ] + edge + [ + source 187 + target 160 + value 1 + ] + edge + [ + source 187 + target 219 + value 1 + ] + edge + [ + source 187 + target 97 + value 2 + ] + edge + [ + source 187 + target 111 + value 1 + ] + edge + [ + source 187 + target 220 + value 1 + ] + edge + [ + source 187 + target 173 + value 5 + ] + edge + [ + source 187 + target 174 + value 7 + ] + edge + [ + source 187 + target 221 + value 3 + ] + edge + [ + source 187 + target 44 + value 6 + ] + edge + [ + source 188 + target 276 + value 8 + ] + edge + [ + source 188 + target 279 + value 1 + ] + edge + [ + source 188 + target 269 + value 3 + ] + edge + [ + source 188 + target 271 + value 2 + ] + edge + [ + source 188 + target 44 + value 10 + ] + edge + [ + source 189 + target 155 + value 2 + ] + edge + [ + source 189 + target 237 + value 2 + ] + edge + [ + source 189 + target 44 + value 25 + ] + edge + [ + source 191 + target 113 + value 2 + ] + edge + [ + source 191 + target 102 + value 2 + ] + edge + [ + source 191 + target 108 + value 1 + ] + edge + [ + source 191 + target 157 + value 1 + ] + edge + [ + source 191 + target 116 + value 1 + ] + edge + [ + source 191 + target 109 + value 2 + ] + edge + [ + source 191 + target 146 + value 1 + ] + edge + [ + source 191 + target 141 + value 1 + ] + edge + [ + source 191 + target 117 + value 3 + ] + edge + [ + source 191 + target 202 + value 6 + ] + edge + [ + source 191 + target 119 + value 2 + ] + edge + [ + source 191 + target 120 + value 2 + ] + edge + [ + source 191 + target 110 + value 2 + ] + edge + [ + source 191 + target 163 + value 1 + ] + edge + [ + source 191 + target 142 + value 3 + ] + edge + [ + source 191 + target 215 + value 3 + ] + edge + [ + source 191 + target 132 + value 2 + ] + edge + [ + source 191 + target 173 + value 1 + ] + edge + [ + source 191 + target 186 + value 3 + ] + edge + [ + source 191 + target 182 + value 1 + ] + edge + [ + source 191 + target 238 + value 2 + ] + edge + [ + source 191 + target 244 + value 10 + ] + edge + [ + source 191 + target 44 + value 3 + ] + edge + [ + source 191 + target 44 + value 22 + ] + edge + [ + source 192 + target 157 + value 1 + ] + edge + [ + source 192 + target 158 + value 1 + ] + edge + [ + source 192 + target 275 + value 4 + ] + edge + [ + source 192 + target 276 + value 9 + ] + edge + [ + source 192 + target 231 + value 1 + ] + edge + [ + source 192 + target 234 + value 9 + ] + edge + [ + source 192 + target 269 + value 22 + ] + edge + [ + source 192 + target 271 + value 7 + ] + edge + [ + source 192 + target 44 + value 22 + ] + edge + [ + source 193 + target 1 + value 1 + ] + edge + [ + source 193 + target 114 + value 3 + ] + edge + [ + source 193 + target 12 + value 5 + ] + edge + [ + source 193 + target 2 + value 7 + ] + edge + [ + source 193 + target 84 + value 1 + ] + edge + [ + source 193 + target 213 + value 2 + ] + edge + [ + source 193 + target 218 + value 1 + ] + edge + [ + source 193 + target 195 + value 1 + ] + edge + [ + source 193 + target 14 + value 1 + ] + edge + [ + source 193 + target 8 + value 5 + ] + edge + [ + source 193 + target 9 + value 5 + ] + edge + [ + source 193 + target 214 + value 2 + ] + edge + [ + source 193 + target 210 + value 4 + ] + edge + [ + source 194 + target 12 + value 7 + ] + edge + [ + source 194 + target 2 + value 7 + ] + edge + [ + source 194 + target 118 + value 23 + ] + edge + [ + source 194 + target 226 + value 1 + ] + edge + [ + source 194 + target 170 + value 1 + ] + edge + [ + source 194 + target 204 + value 1 + ] + edge + [ + source 195 + target 1 + value 2 + ] + edge + [ + source 195 + target 114 + value 5 + ] + edge + [ + source 195 + target 213 + value 7 + ] + edge + [ + source 195 + target 218 + value 7 + ] + edge + [ + source 195 + target 187 + value 2 + ] + edge + [ + source 195 + target 166 + value 2 + ] + edge + [ + source 195 + target 97 + value 1 + ] + edge + [ + source 195 + target 97 + value 3 + ] + edge + [ + source 195 + target 193 + value 5 + ] + edge + [ + source 195 + target 250 + value 1 + ] + edge + [ + source 195 + target 111 + value 2 + ] + edge + [ + source 195 + target 8 + value 2 + ] + edge + [ + source 195 + target 9 + value 3 + ] + edge + [ + source 195 + target 59 + value 1 + ] + edge + [ + source 195 + target 64 + value 1 + ] + edge + [ + source 195 + target 214 + value 2 + ] + edge + [ + source 195 + target 210 + value 4 + ] + edge + [ + source 195 + target 85 + value 1 + ] + edge + [ + source 195 + target 44 + value 5 + ] + edge + [ + source 195 + target 190 + value 5 + ] + edge + [ + source 196 + target 219 + value 8 + ] + edge + [ + source 196 + target 178 + value 1 + ] + edge + [ + source 196 + target 133 + value 1 + ] + edge + [ + source 196 + target 200 + value 3 + ] + edge + [ + source 196 + target 44 + value 10 + ] + edge + [ + source 197 + target 155 + value 2 + ] + edge + [ + source 197 + target 237 + value 2 + ] + edge + [ + source 197 + target 44 + value 8 + ] + edge + [ + source 198 + target 105 + value 3 + ] + edge + [ + source 198 + target 86 + value 2 + ] + edge + [ + source 198 + target 117 + value 1 + ] + edge + [ + source 198 + target 199 + value 5 + ] + edge + [ + source 198 + target 123 + value 2 + ] + edge + [ + source 198 + target 119 + value 4 + ] + edge + [ + source 198 + target 98 + value 1 + ] + edge + [ + source 198 + target 110 + value 1 + ] + edge + [ + source 198 + target 111 + value 1 + ] + edge + [ + source 198 + target 148 + value 2 + ] + edge + [ + source 198 + target 130 + value 1 + ] + edge + [ + source 198 + target 103 + value 3 + ] + edge + [ + source 198 + target 85 + value 2 + ] + edge + [ + source 198 + target 112 + value 2 + ] + edge + [ + source 198 + target 200 + value 1 + ] + edge + [ + source 199 + target 84 + value 1 + ] + edge + [ + source 199 + target 86 + value 8 + ] + edge + [ + source 199 + target 202 + value 4 + ] + edge + [ + source 199 + target 226 + value 2 + ] + edge + [ + source 199 + target 123 + value 5 + ] + edge + [ + source 199 + target 119 + value 4 + ] + edge + [ + source 199 + target 187 + value 1 + ] + edge + [ + source 199 + target 142 + value 2 + ] + edge + [ + source 199 + target 185 + value 1 + ] + edge + [ + source 199 + target 145 + value 1 + ] + edge + [ + source 199 + target 227 + value 1 + ] + edge + [ + source 199 + target 44 + value 2 + ] + edge + [ + source 200 + target 219 + value 1 + ] + edge + [ + source 200 + target 196 + value 2 + ] + edge + [ + source 200 + target 44 + value 8 + ] + edge + [ + source 201 + target 12 + value 5 + ] + edge + [ + source 201 + target 2 + value 3 + ] + edge + [ + source 201 + target 84 + value 4 + ] + edge + [ + source 201 + target 86 + value 5 + ] + edge + [ + source 201 + target 117 + value 1 + ] + edge + [ + source 201 + target 118 + value 1 + ] + edge + [ + source 201 + target 119 + value 1 + ] + edge + [ + source 201 + target 0 + value 2 + ] + edge + [ + source 201 + target 5 + value 2 + ] + edge + [ + source 201 + target 137 + value 1 + ] + edge + [ + source 201 + target 125 + value 1 + ] + edge + [ + source 201 + target 172 + value 2 + ] + edge + [ + source 201 + target 203 + value 1 + ] + edge + [ + source 201 + target 13 + value 3 + ] + edge + [ + source 201 + target 6 + value 2 + ] + edge + [ + source 201 + target 65 + value 1 + ] + edge + [ + source 202 + target 84 + value 5 + ] + edge + [ + source 202 + target 199 + value 5 + ] + edge + [ + source 202 + target 226 + value 2 + ] + edge + [ + source 202 + target 123 + value 6 + ] + edge + [ + source 202 + target 98 + value 4 + ] + edge + [ + source 202 + target 187 + value 1 + ] + edge + [ + source 202 + target 191 + value 1 + ] + edge + [ + source 202 + target 171 + value 1 + ] + edge + [ + source 202 + target 145 + value 2 + ] + edge + [ + source 202 + target 133 + value 1 + ] + edge + [ + source 202 + target 227 + value 1 + ] + edge + [ + source 202 + target 44 + value 2 + ] + edge + [ + source 203 + target 101 + value 1 + ] + edge + [ + source 203 + target 201 + value 1 + ] + edge + [ + source 203 + target 12 + value 2 + ] + edge + [ + source 203 + target 2 + value 2 + ] + edge + [ + source 203 + target 84 + value 5 + ] + edge + [ + source 203 + target 86 + value 6 + ] + edge + [ + source 203 + target 118 + value 2 + ] + edge + [ + source 203 + target 4 + value 1 + ] + edge + [ + source 203 + target 123 + value 2 + ] + edge + [ + source 203 + target 187 + value 4 + ] + edge + [ + source 203 + target 233 + value 1 + ] + edge + [ + source 203 + target 193 + value 2 + ] + edge + [ + source 203 + target 172 + value 4 + ] + edge + [ + source 203 + target 148 + value 1 + ] + edge + [ + source 203 + target 8 + value 2 + ] + edge + [ + source 203 + target 190 + value 3 + ] + edge + [ + source 204 + target 2 + value 6 + ] + edge + [ + source 204 + target 86 + value 3 + ] + edge + [ + source 204 + target 118 + value 6 + ] + edge + [ + source 204 + target 4 + value 3 + ] + edge + [ + source 204 + target 199 + value 1 + ] + edge + [ + source 204 + target 226 + value 1 + ] + edge + [ + source 204 + target 119 + value 2 + ] + edge + [ + source 204 + target 98 + value 7 + ] + edge + [ + source 204 + target 187 + value 4 + ] + edge + [ + source 204 + target 225 + value 2 + ] + edge + [ + source 204 + target 95 + value 3 + ] + edge + [ + source 204 + target 219 + value 2 + ] + edge + [ + source 204 + target 233 + value 1 + ] + edge + [ + source 204 + target 194 + value 3 + ] + edge + [ + source 204 + target 172 + value 2 + ] + edge + [ + source 204 + target 207 + value 5 + ] + edge + [ + source 204 + target 252 + value 2 + ] + edge + [ + source 204 + target 220 + value 3 + ] + edge + [ + source 204 + target 205 + value 1 + ] + edge + [ + source 204 + target 238 + value 2 + ] + edge + [ + source 204 + target 245 + value 1 + ] + edge + [ + source 204 + target 221 + value 1 + ] + edge + [ + source 204 + target 189 + value 2 + ] + edge + [ + source 204 + target 231 + value 4 + ] + edge + [ + source 204 + target 44 + value 7 + ] + edge + [ + source 204 + target 190 + value 2 + ] + edge + [ + source 205 + target 109 + value 2 + ] + edge + [ + source 205 + target 84 + value 1 + ] + edge + [ + source 205 + target 86 + value 16 + ] + edge + [ + source 205 + target 202 + value 1 + ] + edge + [ + source 205 + target 198 + value 1 + ] + edge + [ + source 205 + target 119 + value 1 + ] + edge + [ + source 205 + target 98 + value 2 + ] + edge + [ + source 205 + target 142 + value 1 + ] + edge + [ + source 205 + target 125 + value 1 + ] + edge + [ + source 205 + target 172 + value 1 + ] + edge + [ + source 205 + target 111 + value 4 + ] + edge + [ + source 205 + target 130 + value 4 + ] + edge + [ + source 205 + target 33 + value 1 + ] + edge + [ + source 206 + target 67 + value 3 + ] + edge + [ + source 206 + target 84 + value 6 + ] + edge + [ + source 206 + target 86 + value 5 + ] + edge + [ + source 206 + target 98 + value 1 + ] + edge + [ + source 206 + target 225 + value 3 + ] + edge + [ + source 206 + target 95 + value 4 + ] + edge + [ + source 206 + target 159 + value 1 + ] + edge + [ + source 206 + target 159 + value 1 + ] + edge + [ + source 206 + target 125 + value 4 + ] + edge + [ + source 206 + target 172 + value 6 + ] + edge + [ + source 206 + target 42 + value 3 + ] + edge + [ + source 206 + target 96 + value 1 + ] + edge + [ + source 206 + target 78 + value 1 + ] + edge + [ + source 207 + target 2 + value 6 + ] + edge + [ + source 207 + target 86 + value 3 + ] + edge + [ + source 207 + target 118 + value 6 + ] + edge + [ + source 207 + target 4 + value 3 + ] + edge + [ + source 207 + target 199 + value 1 + ] + edge + [ + source 207 + target 226 + value 1 + ] + edge + [ + source 207 + target 119 + value 3 + ] + edge + [ + source 207 + target 98 + value 7 + ] + edge + [ + source 207 + target 187 + value 4 + ] + edge + [ + source 207 + target 95 + value 3 + ] + edge + [ + source 207 + target 219 + value 2 + ] + edge + [ + source 207 + target 233 + value 1 + ] + edge + [ + source 207 + target 194 + value 3 + ] + edge + [ + source 207 + target 172 + value 2 + ] + edge + [ + source 207 + target 204 + value 4 + ] + edge + [ + source 207 + target 252 + value 2 + ] + edge + [ + source 207 + target 256 + value 3 + ] + edge + [ + source 207 + target 205 + value 1 + ] + edge + [ + source 207 + target 238 + value 2 + ] + edge + [ + source 207 + target 245 + value 1 + ] + edge + [ + source 207 + target 221 + value 1 + ] + edge + [ + source 207 + target 189 + value 2 + ] + edge + [ + source 207 + target 231 + value 4 + ] + edge + [ + source 207 + target 44 + value 7 + ] + edge + [ + source 207 + target 190 + value 2 + ] + edge + [ + source 208 + target 101 + value 1 + ] + edge + [ + source 208 + target 126 + value 5 + ] + edge + [ + source 208 + target 10 + value 1 + ] + edge + [ + source 208 + target 102 + value 10 + ] + edge + [ + source 208 + target 135 + value 4 + ] + edge + [ + source 208 + target 140 + value 1 + ] + edge + [ + source 208 + target 120 + value 1 + ] + edge + [ + source 209 + target 13 + value 2 + ] + edge + [ + source 209 + target 6 + value 2 + ] + edge + [ + source 209 + target 23 + value 1 + ] + edge + [ + source 209 + target 48 + value 1 + ] + edge + [ + source 209 + target 46 + value 1 + ] + edge + [ + source 209 + target 64 + value 1 + ] + edge + [ + source 209 + target 131 + value 2 + ] + edge + [ + source 209 + target 25 + value 2 + ] + edge + [ + source 209 + target 73 + value 2 + ] + edge + [ + source 209 + target 75 + value 2 + ] + edge + [ + source 209 + target 44 + value 5 + ] + edge + [ + source 210 + target 114 + value 1 + ] + edge + [ + source 210 + target 213 + value 1 + ] + edge + [ + source 210 + target 218 + value 3 + ] + edge + [ + source 210 + target 48 + value 2 + ] + edge + [ + source 210 + target 44 + value 4 + ] + edge + [ + source 211 + target 12 + value 15 + ] + edge + [ + source 211 + target 31 + value 1 + ] + edge + [ + source 211 + target 132 + value 2 + ] + edge + [ + source 211 + target 130 + value 9 + ] + edge + [ + source 211 + target 210 + value 2 + ] + edge + [ + source 211 + target 74 + value 6 + ] + edge + [ + source 212 + target 2 + value 11 + ] + edge + [ + source 212 + target 132 + value 4 + ] + edge + [ + source 212 + target 130 + value 1 + ] + edge + [ + source 212 + target 73 + value 6 + ] + edge + [ + source 213 + target 84 + value 1 + ] + edge + [ + source 213 + target 3 + value 2 + ] + edge + [ + source 213 + target 4 + value 1 + ] + edge + [ + source 213 + target 206 + value 1 + ] + edge + [ + source 213 + target 137 + value 1 + ] + edge + [ + source 213 + target 185 + value 4 + ] + edge + [ + source 213 + target 217 + value 1 + ] + edge + [ + source 213 + target 148 + value 1 + ] + edge + [ + source 213 + target 132 + value 2 + ] + edge + [ + source 213 + target 130 + value 1 + ] + edge + [ + source 213 + target 72 + value 1 + ] + edge + [ + source 213 + target 37 + value 1 + ] + edge + [ + source 213 + target 190 + value 5 + ] + edge + [ + source 214 + target 1 + value 1 + ] + edge + [ + source 214 + target 213 + value 1 + ] + edge + [ + source 214 + target 218 + value 4 + ] + edge + [ + source 214 + target 47 + value 5 + ] + edge + [ + source 214 + target 48 + value 1 + ] + edge + [ + source 214 + target 87 + value 1 + ] + edge + [ + source 214 + target 44 + value 5 + ] + edge + [ + source 215 + target 216 + value 2 + ] + edge + [ + source 215 + target 84 + value 10 + ] + edge + [ + source 215 + target 86 + value 1 + ] + edge + [ + source 215 + target 123 + value 1 + ] + edge + [ + source 215 + target 98 + value 2 + ] + edge + [ + source 215 + target 203 + value 3 + ] + edge + [ + source 215 + target 132 + value 2 + ] + edge + [ + source 215 + target 130 + value 1 + ] + edge + [ + source 216 + target 118 + value 1 + ] + edge + [ + source 216 + target 3 + value 1 + ] + edge + [ + source 216 + target 225 + value 5 + ] + edge + [ + source 216 + target 71 + value 3 + ] + edge + [ + source 216 + target 15 + value 1 + ] + edge + [ + source 216 + target 125 + value 4 + ] + edge + [ + source 216 + target 172 + value 2 + ] + edge + [ + source 216 + target 35 + value 1 + ] + edge + [ + source 216 + target 93 + value 1 + ] + edge + [ + source 216 + target 239 + value 1 + ] + edge + [ + source 217 + target 213 + value 11 + ] + edge + [ + source 217 + target 206 + value 1 + ] + edge + [ + source 217 + target 137 + value 3 + ] + edge + [ + source 217 + target 171 + value 10 + ] + edge + [ + source 217 + target 125 + value 2 + ] + edge + [ + source 217 + target 42 + value 2 + ] + edge + [ + source 217 + target 190 + value 1 + ] + edge + [ + source 218 + target 137 + value 1 + ] + edge + [ + source 218 + target 171 + value 4 + ] + edge + [ + source 218 + target 217 + value 1 + ] + edge + [ + source 218 + target 203 + value 1 + ] + edge + [ + source 218 + target 145 + value 1 + ] + edge + [ + source 218 + target 132 + value 3 + ] + edge + [ + source 218 + target 130 + value 3 + ] + edge + [ + source 218 + target 131 + value 1 + ] + edge + [ + source 218 + target 85 + value 1 + ] + edge + [ + source 218 + target 73 + value 1 + ] + edge + [ + source 218 + target 74 + value 2 + ] + edge + [ + source 218 + target 75 + value 1 + ] + edge + [ + source 218 + target 190 + value 5 + ] + edge + [ + source 219 + target 159 + value 2 + ] + edge + [ + source 219 + target 200 + value 1 + ] + edge + [ + source 219 + target 197 + value 1 + ] + edge + [ + source 219 + target 44 + value 29 + ] + edge + [ + source 220 + target 2 + value 1 + ] + edge + [ + source 220 + target 118 + value 1 + ] + edge + [ + source 220 + target 119 + value 1 + ] + edge + [ + source 220 + target 172 + value 1 + ] + edge + [ + source 220 + target 252 + value 3 + ] + edge + [ + source 220 + target 256 + value 1 + ] + edge + [ + source 220 + target 177 + value 1 + ] + edge + [ + source 221 + target 177 + value 1 + ] + edge + [ + source 221 + target 44 + value 8 + ] + edge + [ + source 222 + target 72 + value 3 + ] + edge + [ + source 222 + target 112 + value 5 + ] + edge + [ + source 223 + target 131 + value 5 + ] + edge + [ + source 223 + target 104 + value 6 + ] + edge + [ + source 224 + target 67 + value 2 + ] + edge + [ + source 224 + target 114 + value 1 + ] + edge + [ + source 224 + target 12 + value 10 + ] + edge + [ + source 224 + target 2 + value 4 + ] + edge + [ + source 224 + target 84 + value 4 + ] + edge + [ + source 224 + target 86 + value 2 + ] + edge + [ + source 224 + target 117 + value 10 + ] + edge + [ + source 224 + target 118 + value 2 + ] + edge + [ + source 224 + target 3 + value 3 + ] + edge + [ + source 224 + target 4 + value 2 + ] + edge + [ + source 224 + target 137 + value 1 + ] + edge + [ + source 224 + target 129 + value 1 + ] + edge + [ + source 224 + target 125 + value 2 + ] + edge + [ + source 224 + target 133 + value 1 + ] + edge + [ + source 224 + target 190 + value 3 + ] + edge + [ + source 225 + target 63 + value 4 + ] + edge + [ + source 225 + target 2 + value 2 + ] + edge + [ + source 225 + target 198 + value 1 + ] + edge + [ + source 225 + target 98 + value 1 + ] + edge + [ + source 225 + target 191 + value 4 + ] + edge + [ + source 225 + target 125 + value 2 + ] + edge + [ + source 225 + target 207 + value 1 + ] + edge + [ + source 225 + target 204 + value 2 + ] + edge + [ + source 225 + target 131 + value 2 + ] + edge + [ + source 225 + target 56 + value 1 + ] + edge + [ + source 225 + target 39 + value 1 + ] + edge + [ + source 226 + target 12 + value 3 + ] + edge + [ + source 226 + target 2 + value 1 + ] + edge + [ + source 226 + target 84 + value 4 + ] + edge + [ + source 226 + target 117 + value 1 + ] + edge + [ + source 226 + target 118 + value 1 + ] + edge + [ + source 226 + target 4 + value 1 + ] + edge + [ + source 226 + target 202 + value 1 + ] + edge + [ + source 226 + target 98 + value 1 + ] + edge + [ + source 226 + target 166 + value 1 + ] + edge + [ + source 226 + target 166 + value 1 + ] + edge + [ + source 226 + target 195 + value 1 + ] + edge + [ + source 226 + target 228 + value 2 + ] + edge + [ + source 226 + target 229 + value 1 + ] + edge + [ + source 226 + target 125 + value 1 + ] + edge + [ + source 226 + target 172 + value 1 + ] + edge + [ + source 226 + target 207 + value 2 + ] + edge + [ + source 226 + target 204 + value 2 + ] + edge + [ + source 226 + target 203 + value 1 + ] + edge + [ + source 226 + target 111 + value 1 + ] + edge + [ + source 226 + target 145 + value 1 + ] + edge + [ + source 226 + target 148 + value 1 + ] + edge + [ + source 226 + target 176 + value 1 + ] + edge + [ + source 226 + target 176 + value 1 + ] + edge + [ + source 226 + target 190 + value 1 + ] + edge + [ + source 226 + target 190 + value 1 + ] + edge + [ + source 227 + target 44 + value 8 + ] + edge + [ + source 228 + target 118 + value 1 + ] + edge + [ + source 228 + target 202 + value 3 + ] + edge + [ + source 228 + target 226 + value 5 + ] + edge + [ + source 228 + target 198 + value 1 + ] + edge + [ + source 228 + target 123 + value 1 + ] + edge + [ + source 228 + target 166 + value 1 + ] + edge + [ + source 228 + target 137 + value 2 + ] + edge + [ + source 228 + target 229 + value 5 + ] + edge + [ + source 228 + target 255 + value 5 + ] + edge + [ + source 228 + target 257 + value 4 + ] + edge + [ + source 228 + target 145 + value 2 + ] + edge + [ + source 229 + target 226 + value 3 + ] + edge + [ + source 229 + target 123 + value 1 + ] + edge + [ + source 229 + target 137 + value 2 + ] + edge + [ + source 229 + target 228 + value 6 + ] + edge + [ + source 229 + target 255 + value 1 + ] + edge + [ + source 229 + target 257 + value 5 + ] + edge + [ + source 229 + target 145 + value 2 + ] + edge + [ + source 230 + target 151 + value 1 + ] + edge + [ + source 230 + target 154 + value 1 + ] + edge + [ + source 230 + target 219 + value 4 + ] + edge + [ + source 230 + target 200 + value 1 + ] + edge + [ + source 230 + target 197 + value 9 + ] + edge + [ + source 230 + target 189 + value 22 + ] + edge + [ + source 230 + target 231 + value 7 + ] + edge + [ + source 230 + target 44 + value 22 + ] + edge + [ + source 231 + target 44 + value 25 + ] + edge + [ + source 232 + target 155 + value 1 + ] + edge + [ + source 232 + target 219 + value 1 + ] + edge + [ + source 232 + target 233 + value 4 + ] + edge + [ + source 232 + target 197 + value 1 + ] + edge + [ + source 232 + target 189 + value 9 + ] + edge + [ + source 232 + target 231 + value 22 + ] + edge + [ + source 232 + target 234 + value 7 + ] + edge + [ + source 232 + target 44 + value 22 + ] + edge + [ + source 233 + target 161 + value 2 + ] + edge + [ + source 233 + target 189 + value 1 + ] + edge + [ + source 233 + target 231 + value 1 + ] + edge + [ + source 233 + target 44 + value 29 + ] + edge + [ + source 234 + target 44 + value 25 + ] + edge + [ + source 235 + target 211 + value 3 + ] + edge + [ + source 235 + target 85 + value 2 + ] + edge + [ + source 235 + target 77 + value 1 + ] + edge + [ + source 236 + target 36 + value 1 + ] + edge + [ + source 236 + target 212 + value 3 + ] + edge + [ + source 236 + target 89 + value 1 + ] + edge + [ + source 236 + target 74 + value 1 + ] + edge + [ + source 236 + target 77 + value 1 + ] + edge + [ + source 237 + target 219 + value 19 + ] + edge + [ + source 237 + target 233 + value 1 + ] + edge + [ + source 237 + target 178 + value 1 + ] + edge + [ + source 237 + target 238 + value 1 + ] + edge + [ + source 237 + target 197 + value 6 + ] + edge + [ + source 237 + target 189 + value 4 + ] + edge + [ + source 237 + target 44 + value 23 + ] + edge + [ + source 238 + target 233 + value 4 + ] + edge + [ + source 238 + target 142 + value 1 + ] + edge + [ + source 238 + target 200 + value 1 + ] + edge + [ + source 238 + target 231 + value 2 + ] + edge + [ + source 238 + target 44 + value 2 + ] + edge + [ + source 238 + target 44 + value 8 + ] + edge + [ + source 239 + target 114 + value 1 + ] + edge + [ + source 239 + target 216 + value 1 + ] + edge + [ + source 239 + target 12 + value 2 + ] + edge + [ + source 239 + target 2 + value 3 + ] + edge + [ + source 239 + target 3 + value 1 + ] + edge + [ + source 239 + target 129 + value 1 + ] + edge + [ + source 239 + target 21 + value 1 + ] + edge + [ + source 239 + target 90 + value 2 + ] + edge + [ + source 239 + target 214 + value 1 + ] + edge + [ + source 240 + target 95 + value 5 + ] + edge + [ + source 240 + target 58 + value 1 + ] + edge + [ + source 240 + target 27 + value 1 + ] + edge + [ + source 240 + target 172 + value 2 + ] + edge + [ + source 240 + target 23 + value 1 + ] + edge + [ + source 240 + target 88 + value 1 + ] + edge + [ + source 241 + target 16 + value 1 + ] + edge + [ + source 241 + target 1 + value 2 + ] + edge + [ + source 241 + target 12 + value 2 + ] + edge + [ + source 241 + target 84 + value 5 + ] + edge + [ + source 241 + target 86 + value 4 + ] + edge + [ + source 241 + target 137 + value 3 + ] + edge + [ + source 241 + target 24 + value 2 + ] + edge + [ + source 241 + target 36 + value 1 + ] + edge + [ + source 242 + target 190 + value 1 + ] + edge + [ + source 243 + target 190 + value 1 + ] + edge + [ + source 244 + target 202 + value 1 + ] + edge + [ + source 244 + target 199 + value 1 + ] + edge + [ + source 244 + target 195 + value 1 + ] + edge + [ + source 244 + target 193 + value 2 + ] + edge + [ + source 244 + target 245 + value 1 + ] + edge + [ + source 244 + target 283 + value 1 + ] + edge + [ + source 244 + target 44 + value 10 + ] + edge + [ + source 245 + target 187 + value 1 + ] + edge + [ + source 245 + target 219 + value 2 + ] + edge + [ + source 245 + target 233 + value 4 + ] + edge + [ + source 245 + target 275 + value 4 + ] + edge + [ + source 245 + target 276 + value 10 + ] + edge + [ + source 245 + target 142 + value 1 + ] + edge + [ + source 245 + target 148 + value 3 + ] + edge + [ + source 245 + target 252 + value 1 + ] + edge + [ + source 245 + target 282 + value 2 + ] + edge + [ + source 245 + target 238 + value 1 + ] + edge + [ + source 245 + target 283 + value 1 + ] + edge + [ + source 245 + target 200 + value 1 + ] + edge + [ + source 245 + target 197 + value 1 + ] + edge + [ + source 245 + target 189 + value 1 + ] + edge + [ + source 245 + target 231 + value 2 + ] + edge + [ + source 245 + target 234 + value 4 + ] + edge + [ + source 245 + target 269 + value 3 + ] + edge + [ + source 245 + target 271 + value 5 + ] + edge + [ + source 245 + target 44 + value 8 + ] + edge + [ + source 246 + target 277 + value 8 + ] + edge + [ + source 246 + target 175 + value 1 + ] + edge + [ + source 246 + target 273 + value 3 + ] + edge + [ + source 246 + target 268 + value 2 + ] + edge + [ + source 246 + target 44 + value 30 + ] + edge + [ + source 247 + target 12 + value 27 + ] + edge + [ + source 247 + target 137 + value 3 + ] + edge + [ + source 247 + target 172 + value 28 + ] + edge + [ + source 247 + target 248 + value 1 + ] + edge + [ + source 247 + target 190 + value 1 + ] + edge + [ + source 248 + target 2 + value 27 + ] + edge + [ + source 248 + target 137 + value 3 + ] + edge + [ + source 248 + target 125 + value 28 + ] + edge + [ + source 248 + target 247 + value 1 + ] + edge + [ + source 248 + target 190 + value 1 + ] + edge + [ + source 249 + target 44 + value 8 + ] + edge + [ + source 250 + target 167 + value 1 + ] + edge + [ + source 250 + target 97 + value 1 + ] + edge + [ + source 250 + target 204 + value 1 + ] + edge + [ + source 250 + target 44 + value 1 + ] + edge + [ + source 251 + target 97 + value 1 + ] + edge + [ + source 251 + target 44 + value 1 + ] + edge + [ + source 252 + target 190 + value 1 + ] + edge + [ + source 253 + target 152 + value 1 + ] + edge + [ + source 253 + target 153 + value 1 + ] + edge + [ + source 253 + target 97 + value 4 + ] + edge + [ + source 253 + target 249 + value 22 + ] + edge + [ + source 253 + target 227 + value 7 + ] + edge + [ + source 253 + target 273 + value 1 + ] + edge + [ + source 253 + target 268 + value 9 + ] + edge + [ + source 253 + target 44 + value 22 + ] + edge + [ + source 254 + target 97 + value 15 + ] + edge + [ + source 254 + target 177 + value 1 + ] + edge + [ + source 254 + target 44 + value 18 + ] + edge + [ + source 255 + target 12 + value 9 + ] + edge + [ + source 255 + target 2 + value 6 + ] + edge + [ + source 255 + target 117 + value 1 + ] + edge + [ + source 255 + target 257 + value 1 + ] + edge + [ + source 255 + target 125 + value 13 + ] + edge + [ + source 255 + target 177 + value 2 + ] + edge + [ + source 256 + target 12 + value 1 + ] + edge + [ + source 256 + target 117 + value 1 + ] + edge + [ + source 256 + target 98 + value 1 + ] + edge + [ + source 256 + target 125 + value 1 + ] + edge + [ + source 256 + target 252 + value 3 + ] + edge + [ + source 256 + target 220 + value 1 + ] + edge + [ + source 256 + target 177 + value 1 + ] + edge + [ + source 257 + target 12 + value 7 + ] + edge + [ + source 257 + target 2 + value 7 + ] + edge + [ + source 257 + target 117 + value 1 + ] + edge + [ + source 257 + target 118 + value 1 + ] + edge + [ + source 257 + target 255 + value 1 + ] + edge + [ + source 257 + target 125 + value 6 + ] + edge + [ + source 257 + target 172 + value 3 + ] + edge + [ + source 257 + target 177 + value 1 + ] + edge + [ + source 258 + target 12 + value 1 + ] + edge + [ + source 258 + target 137 + value 5 + ] + edge + [ + source 258 + target 170 + value 1 + ] + edge + [ + source 258 + target 125 + value 2 + ] + edge + [ + source 258 + target 177 + value 1 + ] + edge + [ + source 259 + target 123 + value 1 + ] + edge + [ + source 259 + target 167 + value 6 + ] + edge + [ + source 259 + target 137 + value 7 + ] + edge + [ + source 259 + target 184 + value 1 + ] + edge + [ + source 259 + target 258 + value 1 + ] + edge + [ + source 259 + target 172 + value 8 + ] + edge + [ + source 259 + target 177 + value 1 + ] + edge + [ + source 260 + target 191 + value 2 + ] + edge + [ + source 261 + target 12 + value 5 + ] + edge + [ + source 261 + target 117 + value 4 + ] + edge + [ + source 261 + target 137 + value 5 + ] + edge + [ + source 261 + target 142 + value 2 + ] + edge + [ + source 261 + target 185 + value 5 + ] + edge + [ + source 261 + target 172 + value 1 + ] + edge + [ + source 262 + target 233 + value 15 + ] + edge + [ + source 262 + target 44 + value 18 + ] + edge + [ + source 263 + target 233 + value 6 + ] + edge + [ + source 263 + target 275 + value 15 + ] + edge + [ + source 263 + target 44 + value 18 + ] + edge + [ + source 264 + target 275 + value 15 + ] + edge + [ + source 264 + target 44 + value 18 + ] + edge + [ + source 265 + target 275 + value 6 + ] + edge + [ + source 265 + target 276 + value 15 + ] + edge + [ + source 265 + target 44 + value 18 + ] + edge + [ + source 266 + target 276 + value 15 + ] + edge + [ + source 266 + target 44 + value 18 + ] + edge + [ + source 267 + target 276 + value 6 + ] + edge + [ + source 267 + target 277 + value 15 + ] + edge + [ + source 267 + target 44 + value 18 + ] + edge + [ + source 268 + target 44 + value 25 + ] + edge + [ + source 269 + target 44 + value 25 + ] + edge + [ + source 270 + target 269 + value 2 + ] + edge + [ + source 270 + target 271 + value 16 + ] + edge + [ + source 270 + target 44 + value 15 + ] + edge + [ + source 271 + target 44 + value 25 + ] + edge + [ + source 272 + target 164 + value 1 + ] + edge + [ + source 272 + target 271 + value 2 + ] + edge + [ + source 272 + target 273 + value 16 + ] + edge + [ + source 272 + target 44 + value 15 + ] + edge + [ + source 273 + target 44 + value 25 + ] + edge + [ + source 274 + target 273 + value 2 + ] + edge + [ + source 274 + target 268 + value 16 + ] + edge + [ + source 274 + target 44 + value 15 + ] + edge + [ + source 275 + target 162 + value 2 + ] + edge + [ + source 275 + target 234 + value 1 + ] + edge + [ + source 275 + target 269 + value 1 + ] + edge + [ + source 275 + target 44 + value 29 + ] + edge + [ + source 276 + target 164 + value 2 + ] + edge + [ + source 276 + target 271 + value 1 + ] + edge + [ + source 276 + target 273 + value 1 + ] + edge + [ + source 276 + target 44 + value 29 + ] + edge + [ + source 277 + target 165 + value 2 + ] + edge + [ + source 277 + target 249 + value 1 + ] + edge + [ + source 277 + target 268 + value 1 + ] + edge + [ + source 277 + target 44 + value 29 + ] + edge + [ + source 278 + target 274 + value 1 + ] + edge + [ + source 278 + target 277 + value 4 + ] + edge + [ + source 278 + target 97 + value 9 + ] + edge + [ + source 278 + target 269 + value 1 + ] + edge + [ + source 278 + target 271 + value 9 + ] + edge + [ + source 278 + target 273 + value 22 + ] + edge + [ + source 278 + target 268 + value 7 + ] + edge + [ + source 278 + target 44 + value 22 + ] + edge + [ + source 279 + target 276 + value 8 + ] + edge + [ + source 279 + target 246 + value 1 + ] + edge + [ + source 279 + target 44 + value 10 + ] + edge + [ + source 280 + target 277 + value 6 + ] + edge + [ + source 280 + target 97 + value 15 + ] + edge + [ + source 280 + target 44 + value 18 + ] + edge + [ + source 281 + target 277 + value 15 + ] + edge + [ + source 281 + target 44 + value 18 + ] + edge + [ + source 282 + target 219 + value 1 + ] + edge + [ + source 282 + target 142 + value 1 + ] + edge + [ + source 282 + target 238 + value 1 + ] + edge + [ + source 282 + target 200 + value 1 + ] + edge + [ + source 282 + target 44 + value 2 + ] + edge + [ + source 282 + target 44 + value 2 + ] + edge + [ + source 283 + target 202 + value 1 + ] + edge + [ + source 283 + target 199 + value 1 + ] + edge + [ + source 283 + target 276 + value 2 + ] + edge + [ + source 283 + target 195 + value 2 + ] + edge + [ + source 283 + target 193 + value 2 + ] + edge + [ + source 283 + target 244 + value 2 + ] + edge + [ + source 283 + target 44 + value 8 + ] + edge + [ + source 284 + target 187 + value 1 + ] + edge + [ + source 284 + target 233 + value 1 + ] + edge + [ + source 284 + target 275 + value 1 + ] + edge + [ + source 284 + target 276 + value 1 + ] + edge + [ + source 284 + target 277 + value 1 + ] + edge + [ + source 284 + target 142 + value 1 + ] + edge + [ + source 284 + target 145 + value 1 + ] + edge + [ + source 284 + target 244 + value 1 + ] + edge + [ + source 284 + target 249 + value 1 + ] + edge + [ + source 284 + target 227 + value 1 + ] + edge + [ + source 284 + target 221 + value 1 + ] + edge + [ + source 284 + target 183 + value 1 + ] + edge + [ + source 284 + target 271 + value 1 + ] + edge + [ + source 284 + target 273 + value 1 + ] + edge + [ + source 284 + target 268 + value 1 + ] + edge + [ + source 284 + target 44 + value 1 + ] + edge + [ + source 284 + target 44 + value 10 + ] + edge + [ + source 285 + target 44 + value 1 + ] + edge + [ + source 286 + target 44 + value 1 + ] + edge + [ + source 287 + target 44 + value 1 + ] + edge + [ + source 288 + target 44 + value 1 + ] + edge + [ + source 289 + target 44 + value 1 + ] + edge + [ + source 290 + target 44 + value 1 + ] + edge + [ + source 291 + target 44 + value 1 + ] + edge + [ + source 292 + target 44 + value 1 + ] + edge + [ + source 293 + target 44 + value 1 + ] + edge + [ + source 294 + target 44 + value 1 + ] + edge + [ + source 295 + target 190 + value 1 + ] + edge + [ + source 296 + target 190 + value 1 + ] +] diff --git a/examples/simple/centralization.c b/examples/simple/centralization.c new file mode 100644 index 0000000..69c1ef2 --- /dev/null +++ b/examples/simple/centralization.c @@ -0,0 +1,79 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t graph; + igraph_real_t cent; + + /* Initialize the library. */ + igraph_setup(); + + /* Create an undirected star graph, which is the most centralized graph + * with several common centrality scores. */ + printf("undirected star graph:\n"); + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, /*center=*/ 0); + + igraph_centralization_degree(&graph, /*res=*/ NULL, + /*mode=*/ IGRAPH_ALL, IGRAPH_NO_LOOPS, + ¢, /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("degree centralization: %g\n", cent); + + igraph_centralization_betweenness(&graph, /*res=*/ NULL, + IGRAPH_UNDIRECTED, ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("betweenness centralization: %g\n", cent); + + + igraph_centralization_closeness(&graph, /*res=*/ NULL, + IGRAPH_ALL, ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("closeness centralization: %g\n", cent); + + igraph_destroy(&graph); + + /* With eigenvector centrality, the most centralized structure is + * a graph containing a single edge. */ + printf("\ngraph with single edge:\n"); + igraph_small(&graph, /*n=*/ 10, IGRAPH_UNDIRECTED, + 0,1, -1); + + igraph_centralization_eigenvector_centrality( + &graph, + /*vector=*/ NULL, + /*value=*/ NULL, + /*mode=*/ IGRAPH_ALL, + /*options=*/ NULL, + ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("eigenvector centralization: %g\n", cent); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/centralization.out b/examples/simple/centralization.out new file mode 100644 index 0000000..757de7b --- /dev/null +++ b/examples/simple/centralization.out @@ -0,0 +1,7 @@ +undirected star graph: +degree centralization: 1 +betweenness centralization: 1 +closeness centralization: 1 + +graph with single edge: +eigenvector centralization: 1 diff --git a/examples/simple/cohesive_blocks.c b/examples/simple/cohesive_blocks.c new file mode 100644 index 0000000..85d6f5c --- /dev/null +++ b/examples/simple/cohesive_blocks.c @@ -0,0 +1,65 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_list_t blocks; + igraph_vector_int_t cohesion; + igraph_vector_int_t parent; + igraph_t block_tree; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + igraph_famous(&g, "zachary"); + igraph_vector_int_list_init(&blocks, 0); + igraph_vector_int_init(&cohesion, 0); + igraph_vector_int_init(&parent, 0); + + igraph_cohesive_blocks(&g, &blocks, &cohesion, &parent, + &block_tree); + + printf("Blocks:\n"); + for (i = 0; i < igraph_vector_int_list_size(&blocks); i++) { + igraph_vector_int_t *sg = igraph_vector_int_list_get_ptr(&blocks, i); + printf(" "); + igraph_vector_int_print(sg); + } + printf("Cohesion:\n "); + igraph_vector_int_print(&cohesion); + printf("Parents:\n "); + igraph_vector_int_print(&parent); + printf("Block graph:\n"); + igraph_write_graph_edgelist(&block_tree, stdout); + + igraph_vector_int_list_destroy(&blocks); + igraph_vector_int_destroy(&cohesion); + igraph_vector_int_destroy(&parent); + igraph_destroy(&block_tree); + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/cohesive_blocks.out b/examples/simple/cohesive_blocks.out new file mode 100644 index 0000000..9c25498 --- /dev/null +++ b/examples/simple/cohesive_blocks.out @@ -0,0 +1,21 @@ +Blocks: + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 + 0 1 2 3 7 8 9 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 + 0 4 5 6 10 16 + 0 1 2 3 7 + 0 1 2 8 30 32 33 + 0 4 5 6 10 + 0 1 2 3 13 + 2 23 24 25 27 28 29 31 32 33 +Cohesion: + 1 2 2 4 3 3 4 3 +Parents: + -1 0 0 1 1 2 1 1 +Block graph: +0 1 +0 2 +1 3 +1 4 +1 6 +1 7 +2 5 diff --git a/examples/simple/coloring.c b/examples/simple/coloring.c new file mode 100644 index 0000000..a56bc00 --- /dev/null +++ b/examples/simple/coloring.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2017-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t colors; + igraph_bool_t valid_coloring; + + /* Initialize the library. */ + igraph_setup(); + + /* Setting a seed makes the result of erdos_renyi_game_gnm deterministic. */ + igraph_rng_seed(igraph_rng_default(), 42); + + /* IGRAPH_UNDIRECTED and IGRAPH_NO_LOOPS are both equivalent to 0/FALSE, but + communicate intent better in this context. */ + igraph_erdos_renyi_game_gnm(&graph, 1000, 10000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* As with all igraph functions, the vector in which the result is returned must + be initialized in advance. */ + igraph_vector_int_init(&colors, 0); + igraph_vertex_coloring_greedy(&graph, &colors, IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS); + + /* Verify that the colouring is valid, i.e. no two adjacent vertices have the same colour. */ + igraph_is_vertex_coloring(&graph, &colors, &valid_coloring); + IGRAPH_ASSERT(valid_coloring); + + /* Destroy data structure when we are done. */ + igraph_vector_int_destroy(&colors); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/creation.c b/examples/simple/creation.c new file mode 100644 index 0000000..75068d6 --- /dev/null +++ b/examples/simple/creation.c @@ -0,0 +1,33 @@ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t edges; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed graph with no vertices or edges. */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + + /* Add 5 vertices. Vertex IDs will range from 0 to 4, inclusive. */ + igraph_add_vertices(&graph, 5, NULL); + + /* Add 5 edges, specified as 5 consecutive pairs of vertex IDs + * stored in an integer vector. */ + igraph_vector_int_init_int(&edges, 10, + 0,1, 0,2, 3,1, 2,1, 0,4); + igraph_add_edges(&graph, &edges, NULL); + + igraph_vector_int_destroy(&edges); + + /* Now the graph has 5 vertices and 5 edges. */ + assert(igraph_vcount(&graph) == 5); + assert(igraph_ecount(&graph) == 5); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/distances.c b/examples/simple/distances.c new file mode 100644 index 0000000..6f62ee7 --- /dev/null +++ b/examples/simple/distances.c @@ -0,0 +1,69 @@ +/* + igraph library. + Copyright (C) 2008-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include + +int main(void) { + + igraph_t graph; + igraph_real_t weights_data[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + const igraph_vector_t weights = + igraph_vector_view(weights_data, sizeof(weights_data) / sizeof(weights_data[0])); + igraph_matrix_t res; + igraph_real_t cutoff; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&graph, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + igraph_matrix_init(&res, 0, 0); + + printf("Unweighted distances:\n\n"); + + igraph_distances(&graph, NULL, &res, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + igraph_matrix_print(&res); + + cutoff = 3; /* distances longer than this will be returned as infinity */ + printf("\nUnweighted distances with a cutoff of %g:\n\n", cutoff); + igraph_distances_cutoff(&graph, NULL, &res, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT, cutoff); + igraph_matrix_print(&res); + + printf("\nWeighted distances:\n\n"); + + igraph_distances(&graph, &weights, &res, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + igraph_matrix_print(&res); + + cutoff = 8; /* distances longer than this will be returned as infinity */ + printf("\nWeighted distances with a cutoff of %g:\n\n", cutoff); + igraph_distances_cutoff(&graph, &weights, &res, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT, cutoff); + igraph_matrix_print(&res); + + igraph_matrix_destroy(&res); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/distances.out b/examples/simple/distances.out new file mode 100644 index 0000000..a35c1bb --- /dev/null +++ b/examples/simple/distances.out @@ -0,0 +1,51 @@ +Unweighted distances: + + 0 1 1 1 2 2 2 3 3 3 +Inf 0 1 2 1 1 2 2 2 2 +Inf 1 0 1 2 2 1 3 3 3 +Inf 2 1 0 3 3 1 4 4 4 +Inf 3 2 3 0 1 2 1 2 2 +Inf 2 1 2 3 0 1 4 1 1 +Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf +Inf 3 2 3 4 1 2 0 1 2 +Inf Inf Inf Inf Inf Inf Inf Inf 0 1 +Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 + +Unweighted distances with a cutoff of 3: + + 0 1 1 1 2 2 2 3 3 3 +Inf 0 1 2 1 1 2 2 2 2 +Inf 1 0 1 2 2 1 3 3 3 +Inf 2 1 0 3 3 1 Inf Inf Inf +Inf 3 2 3 0 1 2 1 2 2 +Inf 2 1 2 3 0 1 Inf 1 1 +Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf +Inf 3 2 3 Inf 1 2 0 1 2 +Inf Inf Inf Inf Inf Inf Inf Inf 0 1 +Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 + +Weighted distances: + + 0 0 0 1 5 2 1 13 3 5 +Inf 0 0 1 5 2 1 13 3 5 +Inf 1 0 1 6 3 1 14 4 6 +Inf 1 0 0 6 3 1 14 4 6 +Inf 5 4 5 0 2 3 8 3 5 +Inf 3 2 3 8 0 1 16 1 3 +Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf +Inf 4 3 4 9 1 2 0 1 4 +Inf Inf Inf Inf Inf Inf Inf Inf 0 4 +Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 + +Weighted distances with a cutoff of 8: + + 0 0 0 1 5 2 1 Inf 3 5 +Inf 0 0 1 5 2 1 Inf 3 5 +Inf 1 0 1 6 3 1 Inf 4 6 +Inf 1 0 0 6 3 1 Inf 4 6 +Inf 5 4 5 0 2 3 8 3 5 +Inf 3 2 3 8 0 1 Inf 1 3 +Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf +Inf 4 3 4 Inf 1 2 0 1 4 +Inf Inf Inf Inf Inf Inf Inf Inf 0 4 +Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 diff --git a/examples/simple/dominator_tree.c b/examples/simple/dominator_tree.c new file mode 100644 index 0000000..452a2a5 --- /dev/null +++ b/examples/simple/dominator_tree.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g, domtree; + igraph_vector_int_t dom; + igraph_vector_int_t leftout; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&dom, 0); + igraph_vector_int_init(&leftout, 0); + + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 9, + 1, 0, 1, 2, + 2, 3, 2, 7, + 3, 1, + 4, 1, 4, 3, + 5, 2, 5, 3, 5, 4, 5, 8, + 6, 5, 6, 9, + 8, 7, + -1); + + igraph_dominator_tree(&g, /*root=*/ 9, &dom, &domtree, + &leftout, /*mode=*/ IGRAPH_IN); + igraph_vector_int_print(&dom); + igraph_vector_int_print(&leftout); + igraph_write_graph_edgelist(&domtree, stdout); + + igraph_vector_int_destroy(&dom); + igraph_vector_int_destroy(&leftout); + igraph_destroy(&domtree); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/dominator_tree.out b/examples/simple/dominator_tree.out new file mode 100644 index 0000000..7580ef9 --- /dev/null +++ b/examples/simple/dominator_tree.out @@ -0,0 +1,9 @@ +9 0 3 1 1 1 9 -2 -2 -1 +7 8 +0 9 +1 0 +2 3 +3 1 +4 1 +5 1 +6 9 diff --git a/examples/simple/dot.c b/examples/simple/dot.c new file mode 100644 index 0000000..051df4a --- /dev/null +++ b/examples/simple/dot.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g; + FILE *ifile; + + /* Initialize the library. */ + igraph_setup(); + + ifile = fopen("karate.gml", "r"); + if (ifile == 0) { + return 10; + } + + igraph_read_graph_gml(&g, ifile); + fclose(ifile); + + if (igraph_is_directed(&g)) { + printf("directed\n"); + } else { + printf("undirected\n"); + } + + igraph_write_graph_edgelist(&g, stdout); + printf("-----------------\n"); + igraph_write_graph_dot(&g, stdout); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/dot.out b/examples/simple/dot.out new file mode 100644 index 0000000..a959aeb --- /dev/null +++ b/examples/simple/dot.out @@ -0,0 +1,196 @@ +undirected +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 10 +0 11 +0 12 +0 13 +0 17 +0 19 +0 21 +0 31 +1 2 +1 3 +1 7 +1 13 +1 17 +1 19 +1 21 +1 30 +2 3 +2 7 +2 8 +2 9 +2 13 +2 27 +2 28 +2 32 +3 7 +3 12 +3 13 +4 6 +4 10 +5 6 +5 10 +5 16 +6 16 +8 30 +8 32 +8 33 +9 33 +13 33 +14 32 +14 33 +15 32 +15 33 +18 32 +18 33 +19 33 +20 32 +20 33 +22 32 +22 33 +23 25 +23 27 +23 29 +23 32 +23 33 +24 25 +24 27 +24 31 +25 31 +26 29 +26 33 +27 33 +28 31 +28 33 +29 32 +29 33 +30 32 +30 33 +31 32 +31 33 +32 33 +----------------- +/* Created by igraph @VERSION@ */ +graph { + 0; + 1; + 2; + 3; + 4; + 5; + 6; + 7; + 8; + 9; + 10; + 11; + 12; + 13; + 14; + 15; + 16; + 17; + 18; + 19; + 20; + 21; + 22; + 23; + 24; + 25; + 26; + 27; + 28; + 29; + 30; + 31; + 32; + 33; + + 1 -- 0; + 2 -- 0; + 2 -- 1; + 3 -- 0; + 3 -- 1; + 3 -- 2; + 4 -- 0; + 5 -- 0; + 6 -- 0; + 6 -- 4; + 6 -- 5; + 7 -- 0; + 7 -- 1; + 7 -- 2; + 7 -- 3; + 8 -- 0; + 8 -- 2; + 9 -- 2; + 10 -- 0; + 10 -- 4; + 10 -- 5; + 11 -- 0; + 12 -- 0; + 12 -- 3; + 13 -- 0; + 13 -- 1; + 13 -- 2; + 13 -- 3; + 16 -- 5; + 16 -- 6; + 17 -- 0; + 17 -- 1; + 19 -- 0; + 19 -- 1; + 21 -- 0; + 21 -- 1; + 25 -- 23; + 25 -- 24; + 27 -- 2; + 27 -- 23; + 27 -- 24; + 28 -- 2; + 29 -- 23; + 29 -- 26; + 30 -- 1; + 30 -- 8; + 31 -- 0; + 31 -- 24; + 31 -- 25; + 31 -- 28; + 32 -- 2; + 32 -- 8; + 32 -- 14; + 32 -- 15; + 32 -- 18; + 32 -- 20; + 32 -- 22; + 32 -- 23; + 32 -- 29; + 32 -- 30; + 32 -- 31; + 33 -- 8; + 33 -- 9; + 33 -- 13; + 33 -- 14; + 33 -- 15; + 33 -- 18; + 33 -- 19; + 33 -- 20; + 33 -- 22; + 33 -- 23; + 33 -- 26; + 33 -- 27; + 33 -- 28; + 33 -- 29; + 33 -- 30; + 33 -- 31; + 33 -- 32; +} diff --git a/examples/simple/dqueue.c b/examples/simple/dqueue.c new file mode 100644 index 0000000..8e35951 --- /dev/null +++ b/examples/simple/dqueue.c @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_dqueue_t q; + + /* Initialize the library. */ + igraph_setup(); + + /* igraph_dqueue_init, igraph_dqueue_destroy, igraph_dqueue_empty */ + igraph_dqueue_init(&q, 5); + if (!igraph_dqueue_empty(&q)) { + return 1; + } + igraph_dqueue_destroy(&q); + + /* igraph_dqueue_push, igraph_dqueue_pop */ + igraph_dqueue_init(&q, 4); + igraph_dqueue_push(&q, 1); + igraph_dqueue_push(&q, 2); + igraph_dqueue_push(&q, 3); + igraph_dqueue_push(&q, 4); + if (igraph_dqueue_pop(&q) != 1) { + return 2; + } + if (igraph_dqueue_pop(&q) != 2) { + return 3; + } + if (igraph_dqueue_pop(&q) != 3) { + return 4; + } + if (igraph_dqueue_pop(&q) != 4) { + return 5; + } + igraph_dqueue_destroy(&q); + + /* igraph_dqueue_clear, igraph_dqueue_size */ + igraph_dqueue_init(&q, 0); + if (igraph_dqueue_size(&q) != 0) { + return 6; + } + igraph_dqueue_clear(&q); + if (igraph_dqueue_size(&q) != 0) { + return 7; + } + for (int i = 0; i < 10; i++) { + igraph_dqueue_push(&q, i); + } + igraph_dqueue_clear(&q); + if (igraph_dqueue_size(&q) != 0) { + return 8; + } + igraph_dqueue_destroy(&q); + + /* TODO: igraph_dqueue_full */ + + /* igraph_dqueue_head, igraph_dqueue_back, igraph_dqueue_pop_back */ + igraph_dqueue_init(&q, 0); + for (int i = 0; i < 10; i++) { + igraph_dqueue_push(&q, i); + } + for (int i = 0; i < 10; i++) { + if (igraph_dqueue_head(&q) != 0) { + return 9; + } + if (igraph_dqueue_back(&q) != 9 - i) { + return 10; + } + if (igraph_dqueue_pop_back(&q) != 9 - i) { + return 11; + } + } + igraph_dqueue_destroy(&q); + + /* print */ + igraph_dqueue_init(&q, 4); + igraph_dqueue_push(&q, 1); + igraph_dqueue_push(&q, 2); + igraph_dqueue_push(&q, 3); + igraph_dqueue_push(&q, 4); + igraph_dqueue_pop(&q); + igraph_dqueue_pop(&q); + igraph_dqueue_push(&q, 5); + igraph_dqueue_push(&q, 6); + igraph_dqueue_print(&q); + + igraph_dqueue_clear(&q); + igraph_dqueue_print(&q); + + igraph_dqueue_destroy(&q); + + if (IGRAPH_FINALLY_STACK_SIZE() != 0) { + return 12; + } + + return 0; +} diff --git a/examples/simple/dqueue.out b/examples/simple/dqueue.out new file mode 100644 index 0000000..02defa8 --- /dev/null +++ b/examples/simple/dqueue.out @@ -0,0 +1,2 @@ +3 4 5 6 + diff --git a/examples/simple/edgelist1.dl b/examples/simple/edgelist1.dl new file mode 100644 index 0000000..2024ab8 --- /dev/null +++ b/examples/simple/edgelist1.dl @@ -0,0 +1,10 @@ +DL n=5 +format = edgelist1 +labels: +george, sally, jim, billy, jane +data: +1 2 +1 3 +2 3 +3 1 +4 3 diff --git a/examples/simple/edgelist2.dl b/examples/simple/edgelist2.dl new file mode 100644 index 0000000..5b38e18 --- /dev/null +++ b/examples/simple/edgelist2.dl @@ -0,0 +1,9 @@ +DL n=5 +format = edgelist1 +labels embedded: +data: +george sally +george jim +sally jim +billy george +jane jim diff --git a/examples/simple/edgelist3.dl b/examples/simple/edgelist3.dl new file mode 100644 index 0000000..a7625f3 --- /dev/null +++ b/examples/simple/edgelist3.dl @@ -0,0 +1,11 @@ +DL n=5 +format = edgelist1 +labels: +george, sally, jim, billy, jane +labels embedded: +data: +george sally +george jim +sally jim +billy george +jane jim diff --git a/examples/simple/edgelist4.dl b/examples/simple/edgelist4.dl new file mode 100644 index 0000000..cb2ce4d --- /dev/null +++ b/examples/simple/edgelist4.dl @@ -0,0 +1,10 @@ +DL n=5 +format = edgelist1 +labels: +george, sally, jim, billy, jane +data: +1 2 +1 3 -1 +2 3 +3 1 -1 +4 3 diff --git a/examples/simple/edgelist5.dl b/examples/simple/edgelist5.dl new file mode 100644 index 0000000..37173a5 --- /dev/null +++ b/examples/simple/edgelist5.dl @@ -0,0 +1,9 @@ +DL n=5 +format = edgelist1 +labels embedded: +data: +george sally 0.1 +george jim 0.5 +sally jim +billy george 1 +jane jim diff --git a/examples/simple/edgelist6.dl b/examples/simple/edgelist6.dl new file mode 100644 index 0000000..df7db54 --- /dev/null +++ b/examples/simple/edgelist6.dl @@ -0,0 +1,11 @@ +DL n=5 +format = edgelist1 +labels: +george, sally, jim, billy, jane +labels embedded: +data: +george sally +george jim 2 +sally jim +billy george 1 +jane jim 1e-5 diff --git a/examples/simple/edgelist7.dl b/examples/simple/edgelist7.dl new file mode 100644 index 0000000..edbe5e2 --- /dev/null +++ b/examples/simple/edgelist7.dl @@ -0,0 +1,6 @@ +DL n=4 +format = edgelist1 +data: +1 2 +2 3 +2 4 diff --git a/examples/simple/eigenvector_centrality.c b/examples/simple/eigenvector_centrality.c new file mode 100644 index 0000000..072276c --- /dev/null +++ b/examples/simple/eigenvector_centrality.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t graph; + igraph_vector_t vector, weights; + igraph_real_t value; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a star graph, with vertex 0 at the center, and associated edge weights. */ + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_vector_init_range(&weights, 1, igraph_ecount(&graph)+1); + + /* Initialize the vector where the result will be stored. */ + igraph_vector_init(&vector, 0); + + /* Compute eigenvector centrality. */ + igraph_eigenvector_centrality(&graph, &vector, &value, IGRAPH_OUT, + &weights, /*options=*/ NULL); + + /* Print results. */ + printf("eigenvalue: %g\n", value); + printf("eigenvector:\n"); + igraph_vector_print(&vector); + + /* Free allocated data structures. */ + igraph_vector_destroy(&vector); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/eigenvector_centrality.out b/examples/simple/eigenvector_centrality.out new file mode 100644 index 0000000..9ccd727 --- /dev/null +++ b/examples/simple/eigenvector_centrality.out @@ -0,0 +1,3 @@ +eigenvalue: 16.8819 +eigenvector: +1 0.0592349 0.11847 0.177705 0.23694 0.296174 0.355409 0.414644 0.473879 0.533114 diff --git a/examples/simple/even_tarjan.c b/examples/simple/even_tarjan.c new file mode 100644 index 0000000..5c04d0a --- /dev/null +++ b/examples/simple/even_tarjan.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + + igraph_t g, gbar; + igraph_int_t k1, k2 = INT_MAX; + igraph_real_t tmpk; + igraph_int_t i, j, n; + igraph_maxflow_stats_t stats; + + /* Initialize the library. */ + igraph_setup(); + + igraph_famous(&g, "meredith"); + igraph_even_tarjan_reduction(&g, &gbar, /*capacity=*/ NULL); + + igraph_vertex_connectivity(&g, &k1, /* checks= */ false); + + n = igraph_vcount(&g); + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + igraph_bool_t conn; + igraph_are_adjacent(&g, i, j, &conn); + if (conn) { + continue; + } + igraph_maxflow_value(&gbar, &tmpk, + /* source= */ i + n, + /* target= */ j, + /* capacity= */ 0, + &stats); + if (tmpk < k2) { + k2 = tmpk; + } + } + } + + igraph_destroy(&gbar); + igraph_destroy(&g); + + if (k1 != k2) { + printf("k1 = %" IGRAPH_PRId " while k2 = %" IGRAPH_PRId "\n", k1, k2); + return 1; + } + + return 0; +} diff --git a/examples/simple/flow.c b/examples/simple/flow.c new file mode 100644 index 0000000..015be39 --- /dev/null +++ b/examples/simple/flow.c @@ -0,0 +1,108 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_real_t flow; + igraph_vector_t capacity; + igraph_int_t source, target; + FILE *infile; + igraph_maxflow_stats_t stats; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_init(&capacity, 0); + + /***************/ + infile = fopen("ak-4102.max", "r"); + igraph_read_graph_dimacs_flow( + &g, infile, 0, 0, &source, &target, &capacity, IGRAPH_DIRECTED); + fclose(infile); + + igraph_maxflow_value(&g, &flow, source, target, &capacity, &stats); + + if (flow != 8207) { + return 1; + } + igraph_destroy(&g); + /***************/ + + /* /\***************\/ */ + /* infile=fopen("ak-8198.max", "r"); */ + /* igraph_read_graph_dimacs_flow(&g, infile, 0, 0, &source, &target, &capacity, */ + /* IGRAPH_DIRECTED); */ + /* fclose(infile); */ + + /* t=timer(); */ + /* igraph_maxflow_value(&g, &flow, source, target, &capacity, &stats); */ + /* t=timer()-t; */ + /* printf("8198: %g (time %.10f)\n", flow, t); */ + /* igraph_destroy(&g); */ + /* /\***************\/ */ + + /* /\***************\/ */ + /* infile=fopen("ak-16390.max", "r"); */ + /* igraph_read_graph_dimacs_flow(&g, infile, 0, 0, &source, &target, &capacity, */ + /* IGRAPH_DIRECTED); */ + /* fclose(infile); */ + + /* t=timer(); */ + /* igraph_maxflow_value(&g, &flow, source, target, &capacity, &stats); */ + /* t=timer()-t; */ + /* printf("16390: %g (time %.10f)\n", flow, t); */ + /* igraph_destroy(&g); */ + /* /\***************\/ */ + + /* /\***************\/ */ + /* infile=fopen("ak-32774.max", "r"); */ + /* igraph_read_graph_dimacs_flow(&g, infile, 0, 0, &source, &target, &capacity, */ + /* IGRAPH_DIRECTED); */ + /* fclose(infile); */ + + /* t=timer(); */ + /* igraph_maxflow_value(&g, &flow, source, target, &capacity, &stats); */ + /* t=timer()-t; */ + /* printf("32774: %g (time %.10f)\n", flow, t); */ + /* igraph_destroy(&g); */ + /* /\***************\/ */ + + /* /\***************\/ */ + /* infile=fopen("ak-65542.max", "r"); */ + /* igraph_read_graph_dimacs_flow(&g, infile, 0, 0, &source, &target, &capacity, */ + /* IGRAPH_DIRECTED); */ + /* fclose(infile); */ + + /* t=timer(); */ + /* igraph_maxflow_value(&g, &flow, source, target, &capacity, &stats); */ + /* t=timer()-t; */ + /* printf("65542: %g (time %.10f)\n", flow, t); */ + /* igraph_destroy(&g); */ + /* /\***************\/ */ + + igraph_vector_destroy(&capacity); + + return 0; +} diff --git a/examples/simple/flow2.c b/examples/simple/flow2.c new file mode 100644 index 0000000..992aa10 --- /dev/null +++ b/examples/simple/flow2.c @@ -0,0 +1,76 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_setup(); + + igraph_t g; + igraph_real_t flow_value; + igraph_vector_int_t cut; + igraph_vector_t capacity; + igraph_vector_int_t partition, partition2; + igraph_vector_t flow; + igraph_int_t i; + igraph_maxflow_stats_t stats; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 0, 5, 5, 4, 4, 3, 3, 0, -1); + igraph_vector_init_int_end(&capacity, -1, 3, 1, 2, 10, 1, 3, 2, -1); + igraph_vector_int_init(&cut, 0); + igraph_vector_int_init(&partition, 0); + igraph_vector_int_init(&partition2, 0); + igraph_vector_init(&flow, 0); + + igraph_maxflow(&g, &flow_value, &flow, &cut, &partition, &partition2, + /*source=*/ 0, /*target=*/ 2, &capacity, &stats); + + igraph_int_t nc = igraph_vector_int_size(&cut); + printf("flow value: %g\n", (double) flow_value); + printf("flow: "); + igraph_vector_print(&flow); + printf("first partition: "); + igraph_vector_int_print(&partition); + printf("second partition: "); + igraph_vector_int_print(&partition2); + printf("edges in the cut: "); + for (i = 0; i < nc; i++) { + igraph_int_t edge = VECTOR(cut)[i]; + igraph_int_t from = IGRAPH_FROM(&g, edge); + igraph_int_t to = IGRAPH_TO(&g, edge); + printf("%" IGRAPH_PRId "-%" IGRAPH_PRId " (%g), ", from, to, VECTOR(capacity)[edge]); + } + printf("\n"); + + igraph_vector_int_destroy(&cut); + igraph_vector_int_destroy(&partition2); + igraph_vector_int_destroy(&partition); + igraph_vector_destroy(&capacity); + igraph_vector_destroy(&flow); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/flow2.out b/examples/simple/flow2.out new file mode 100644 index 0000000..c741455 --- /dev/null +++ b/examples/simple/flow2.out @@ -0,0 +1,5 @@ +flow value: 1 +flow: 1 1 0 0 0 0 0 +first partition: 0 1 3 4 5 +second partition: 2 +edges in the cut: 1-2 (1), diff --git a/examples/simple/foreign.c b/examples/simple/foreign.c new file mode 100644 index 0000000..61afd95 --- /dev/null +++ b/examples/simple/foreign.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g; + FILE *ifile; + + /* Initialize the library. */ + igraph_setup(); + + /* Turn on attribute handling. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Read a Pajek file. */ + ifile = fopen("links.net", "r"); + if (ifile == 0) { + return 10; + } + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + /* Write it in edgelist format. */ + printf("The graph:\n"); + printf("Vertices: %" IGRAPH_PRId "\n", igraph_vcount(&g)); + printf("Edges: %" IGRAPH_PRId "\n", igraph_ecount(&g)); + printf("Directed: %i\n", igraph_is_directed(&g) ? 1 : 0); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/foreign.out b/examples/simple/foreign.out new file mode 100644 index 0000000..a1244f9 --- /dev/null +++ b/examples/simple/foreign.out @@ -0,0 +1,11 @@ +The graph: +Vertices: 4 +Edges: 7 +Directed: 1 +0 0 +0 1 +0 2 +1 0 +2 2 +2 3 +3 1 diff --git a/examples/simple/fullmatrix1.dl b/examples/simple/fullmatrix1.dl new file mode 100644 index 0000000..a77922c --- /dev/null +++ b/examples/simple/fullmatrix1.dl @@ -0,0 +1,7 @@ +DL N = 5 +Data: +0 1 1 1 1 +1 0 1 0 0 +1 1 0 0 1 +1 0 0 0 0 +1 0 1 0 0 diff --git a/examples/simple/fullmatrix2.dl b/examples/simple/fullmatrix2.dl new file mode 100644 index 0000000..2794732 --- /dev/null +++ b/examples/simple/fullmatrix2.dl @@ -0,0 +1,10 @@ +dl n=5 +format = fullmatrix +labels: +barry,david,lin,pat,russ +data: +0 1 1 1 0 +1 0 0 0 1 +1 0 0 1 0 +1 0 1 0 1 +0 1 0 1 0 diff --git a/examples/simple/fullmatrix3.dl b/examples/simple/fullmatrix3.dl new file mode 100644 index 0000000..7a966bf --- /dev/null +++ b/examples/simple/fullmatrix3.dl @@ -0,0 +1,12 @@ +dl n=5 +format = fullmatrix +labels: +barry,david +lin,pat +russ +data: +0 1 1 1 0 +1 0 0 0 1 +1 0 0 1 0 +1 0 1 0 1 +0 1 0 1 0 diff --git a/examples/simple/fullmatrix4.dl b/examples/simple/fullmatrix4.dl new file mode 100644 index 0000000..15d21df --- /dev/null +++ b/examples/simple/fullmatrix4.dl @@ -0,0 +1,10 @@ +dl n=5 +format = fullmatrix +labels embedded +data: +larry david lin pat russ +Larry 0 1 1 1 0 +david 1 0 0 0 1 +Lin 1 0 0 1 0 +Pat 1 0 1 0 1 +Russ 0 1 0 1 0 diff --git a/examples/simple/gml.c b/examples/simple/gml.c new file mode 100644 index 0000000..7f0d594 --- /dev/null +++ b/examples/simple/gml.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t graph; + FILE *ifile; + + /* Initialize the library. */ + igraph_setup(); + + ifile = fopen("karate.gml", "r"); + if (ifile == 0) { + return 1; + } + + igraph_read_graph_gml(&graph, ifile); + fclose(ifile); + + printf("The graph is %s.\n", igraph_is_directed(&graph) ? "directed" : "undirected"); + + /* Output as edge list */ + printf("\n-----------------\n"); + igraph_write_graph_edgelist(&graph, stdout); + + /* Output as GML */ + printf("\n-----------------\n"); + igraph_write_graph_gml(&graph, stdout,IGRAPH_WRITE_GML_DEFAULT_SW, 0, ""); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/gml.out b/examples/simple/gml.out new file mode 100644 index 0000000..fe1dfb1 --- /dev/null +++ b/examples/simple/gml.out @@ -0,0 +1,614 @@ +The graph is undirected. + +----------------- +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 10 +0 11 +0 12 +0 13 +0 17 +0 19 +0 21 +0 31 +1 2 +1 3 +1 7 +1 13 +1 17 +1 19 +1 21 +1 30 +2 3 +2 7 +2 8 +2 9 +2 13 +2 27 +2 28 +2 32 +3 7 +3 12 +3 13 +4 6 +4 10 +5 6 +5 10 +5 16 +6 16 +8 30 +8 32 +8 33 +9 33 +13 33 +14 32 +14 33 +15 32 +15 33 +18 32 +18 33 +19 33 +20 32 +20 33 +22 32 +22 33 +23 25 +23 27 +23 29 +23 32 +23 33 +24 25 +24 27 +24 31 +25 31 +26 29 +26 33 +27 33 +28 31 +28 33 +29 32 +29 33 +30 32 +30 33 +31 32 +31 33 +32 33 + +----------------- +Version 1 +graph +[ + directed 0 + node + [ + id 0 + ] + node + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + node + [ + id 4 + ] + node + [ + id 5 + ] + node + [ + id 6 + ] + node + [ + id 7 + ] + node + [ + id 8 + ] + node + [ + id 9 + ] + node + [ + id 10 + ] + node + [ + id 11 + ] + node + [ + id 12 + ] + node + [ + id 13 + ] + node + [ + id 14 + ] + node + [ + id 15 + ] + node + [ + id 16 + ] + node + [ + id 17 + ] + node + [ + id 18 + ] + node + [ + id 19 + ] + node + [ + id 20 + ] + node + [ + id 21 + ] + node + [ + id 22 + ] + node + [ + id 23 + ] + node + [ + id 24 + ] + node + [ + id 25 + ] + node + [ + id 26 + ] + node + [ + id 27 + ] + node + [ + id 28 + ] + node + [ + id 29 + ] + node + [ + id 30 + ] + node + [ + id 31 + ] + node + [ + id 32 + ] + node + [ + id 33 + ] + edge + [ + source 1 + target 0 + ] + edge + [ + source 2 + target 0 + ] + edge + [ + source 2 + target 1 + ] + edge + [ + source 3 + target 0 + ] + edge + [ + source 3 + target 1 + ] + edge + [ + source 3 + target 2 + ] + edge + [ + source 4 + target 0 + ] + edge + [ + source 5 + target 0 + ] + edge + [ + source 6 + target 0 + ] + edge + [ + source 6 + target 4 + ] + edge + [ + source 6 + target 5 + ] + edge + [ + source 7 + target 0 + ] + edge + [ + source 7 + target 1 + ] + edge + [ + source 7 + target 2 + ] + edge + [ + source 7 + target 3 + ] + edge + [ + source 8 + target 0 + ] + edge + [ + source 8 + target 2 + ] + edge + [ + source 9 + target 2 + ] + edge + [ + source 10 + target 0 + ] + edge + [ + source 10 + target 4 + ] + edge + [ + source 10 + target 5 + ] + edge + [ + source 11 + target 0 + ] + edge + [ + source 12 + target 0 + ] + edge + [ + source 12 + target 3 + ] + edge + [ + source 13 + target 0 + ] + edge + [ + source 13 + target 1 + ] + edge + [ + source 13 + target 2 + ] + edge + [ + source 13 + target 3 + ] + edge + [ + source 16 + target 5 + ] + edge + [ + source 16 + target 6 + ] + edge + [ + source 17 + target 0 + ] + edge + [ + source 17 + target 1 + ] + edge + [ + source 19 + target 0 + ] + edge + [ + source 19 + target 1 + ] + edge + [ + source 21 + target 0 + ] + edge + [ + source 21 + target 1 + ] + edge + [ + source 25 + target 23 + ] + edge + [ + source 25 + target 24 + ] + edge + [ + source 27 + target 2 + ] + edge + [ + source 27 + target 23 + ] + edge + [ + source 27 + target 24 + ] + edge + [ + source 28 + target 2 + ] + edge + [ + source 29 + target 23 + ] + edge + [ + source 29 + target 26 + ] + edge + [ + source 30 + target 1 + ] + edge + [ + source 30 + target 8 + ] + edge + [ + source 31 + target 0 + ] + edge + [ + source 31 + target 24 + ] + edge + [ + source 31 + target 25 + ] + edge + [ + source 31 + target 28 + ] + edge + [ + source 32 + target 2 + ] + edge + [ + source 32 + target 8 + ] + edge + [ + source 32 + target 14 + ] + edge + [ + source 32 + target 15 + ] + edge + [ + source 32 + target 18 + ] + edge + [ + source 32 + target 20 + ] + edge + [ + source 32 + target 22 + ] + edge + [ + source 32 + target 23 + ] + edge + [ + source 32 + target 29 + ] + edge + [ + source 32 + target 30 + ] + edge + [ + source 32 + target 31 + ] + edge + [ + source 33 + target 8 + ] + edge + [ + source 33 + target 9 + ] + edge + [ + source 33 + target 13 + ] + edge + [ + source 33 + target 14 + ] + edge + [ + source 33 + target 15 + ] + edge + [ + source 33 + target 18 + ] + edge + [ + source 33 + target 19 + ] + edge + [ + source 33 + target 20 + ] + edge + [ + source 33 + target 22 + ] + edge + [ + source 33 + target 23 + ] + edge + [ + source 33 + target 26 + ] + edge + [ + source 33 + target 27 + ] + edge + [ + source 33 + target 28 + ] + edge + [ + source 33 + target 29 + ] + edge + [ + source 33 + target 30 + ] + edge + [ + source 33 + target 31 + ] + edge + [ + source 33 + target 32 + ] +] diff --git a/examples/simple/graphml.c b/examples/simple/graphml.c new file mode 100644 index 0000000..37c5257 --- /dev/null +++ b/examples/simple/graphml.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2006-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include /* unlink */ + +int main(void) { + igraph_t graph; + const char *infilename = "test.graphml"; + const char *outfilename = "test2.graphml"; + + /* Initialize the library. */ + igraph_setup(); + + /* Set up attribute handling, so graph attributes can be imported + * from the GraphML file. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Problems in the GraphML file may cause igraph to print warnings. + * If this is not desired, set a silent warning handler: */ + igraph_set_warning_handler(&igraph_warning_handler_ignore); + + /* Read the contents of a GraphML file. */ + + /* GraphML */ + FILE *infile = fopen("test.graphml", "r"); + if (! infile) { + fprintf(stderr, "Could not open input file '%s'.", infilename); + exit(1); + } + + /* GraphML support is an optional feature in igraph. If igraph was compiled + * without GraphML support, igraph_read_graph_graphml() returns IGRAPH_UNIMPLEMENTED. + * We temporarily disable the default error handler so we can test for this condition. */ + igraph_error_handler_t *oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + igraph_error_t ret = igraph_read_graph_graphml(&graph, infile, 0); + if (ret == IGRAPH_UNIMPLEMENTED) { + fprintf(stderr, "igraph was compiled without GraphML support."); + exit(77); + } + if (ret != IGRAPH_SUCCESS) { + fprintf(stderr, "Unexpected error while reading GraphML."); + exit(1); + } + igraph_set_error_handler(oldhandler); + + + fclose(infile); + + /* Write it back into another file. */ + + FILE *outfile = fopen(outfilename, "w"); + if (outfile) { + igraph_write_graph_graphml(&graph, outfile, true); + fclose(outfile); + + /* Clean up after ourselves */ + unlink(outfilename); + } else { + fprintf(stderr, "Could not write output file '%s'.", outfilename); + } + + /* Destroy the graph */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_all_st_mincuts.c b/examples/simple/igraph_all_st_mincuts.c new file mode 100644 index 0000000..2d34e4a --- /dev/null +++ b/examples/simple/igraph_all_st_mincuts.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t partitions; + igraph_vector_int_list_t cuts; + igraph_real_t value; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 4, + /*capacity=*/ 0); + + igraph_int_t i, e, m, n = igraph_vector_int_list_size(&partitions); + printf("Found %" IGRAPH_PRId " cuts, value: %g\n", n, value); + for (i = 0; i < n; i++) { + igraph_vector_int_t *vec = igraph_vector_int_list_get_ptr(&partitions, i); + igraph_vector_int_t *vec2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("Partition %" IGRAPH_PRId ": ", i); + igraph_vector_int_print(vec); + if (vec2) { + printf("Cut %" IGRAPH_PRId ":\n", i); + m = igraph_vector_int_size(vec2); + for (e = 0; e < m; e++) { + igraph_int_t from = IGRAPH_FROM(&g, VECTOR(*vec2)[e]), to = IGRAPH_TO(&g, VECTOR(*vec2)[e]); + printf(" %" IGRAPH_PRId " -> %" IGRAPH_PRId "\n", from, to); + } + } + } + + igraph_vector_int_list_destroy(&partitions); + igraph_vector_int_list_destroy(&cuts); + printf("\n"); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_assortativity_degree.c b/examples/simple/igraph_assortativity_degree.c new file mode 100644 index 0000000..7256536 --- /dev/null +++ b/examples/simple/igraph_assortativity_degree.c @@ -0,0 +1,37 @@ +#include +#include + +int main(void){ + igraph_t g; + igraph_int_t vcount = 1000; + igraph_real_t pf = 0.2; + + /* Initialize the library. */ + igraph_setup(); + + /* Seed random number generator to ensure reproducibility. */ + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Forest fire model network with %" IGRAPH_PRId " vertices and %g forward burning probability.\n\n", + vcount, pf); + + for (int i = 0; i < 5; i++) { + igraph_real_t assortativity; + + /* Generate graph from the forest fire model. */ + igraph_forest_fire_game(&g, vcount, pf, 1.0, 1, IGRAPH_UNDIRECTED); + + /* Compute assortativity. */ + igraph_assortativity_degree(&g, &assortativity, /* ignore edge directions */ IGRAPH_UNDIRECTED); + printf("Assortativity before rewiring = %g\n", assortativity); + + /* Randomize the graph while preserving the degrees. */ + igraph_rewire(&g, 20 * igraph_ecount(&g), IGRAPH_SIMPLE_SW, NULL); + + /* Re-compute assortativity. Did it change? */ + igraph_assortativity_degree(&g, &assortativity, /* ignore edge directions */ IGRAPH_UNDIRECTED); + printf("Assortativity after rewiring = %g\n\n", assortativity); + + igraph_destroy(&g); + } +} diff --git a/examples/simple/igraph_assortativity_degree.out b/examples/simple/igraph_assortativity_degree.out new file mode 100644 index 0000000..2b86d35 --- /dev/null +++ b/examples/simple/igraph_assortativity_degree.out @@ -0,0 +1,17 @@ +Forest fire model network with 1000 vertices and 0.2 forward burning probability. + +Assortativity before rewiring = 0.164775 +Assortativity after rewiring = -0.0244963 + +Assortativity before rewiring = 0.204558 +Assortativity after rewiring = -0.0115178 + +Assortativity before rewiring = 0.25205 +Assortativity after rewiring = -0.0198939 + +Assortativity before rewiring = 0.136678 +Assortativity after rewiring = 0.00245584 + +Assortativity before rewiring = 0.262014 +Assortativity after rewiring = -0.00207304 + diff --git a/examples/simple/igraph_assortativity_nominal.c b/examples/simple/igraph_assortativity_nominal.c new file mode 100644 index 0000000..506b53b --- /dev/null +++ b/examples/simple/igraph_assortativity_nominal.c @@ -0,0 +1,46 @@ +#include +#include + +int main(void) { + igraph_int_t nodes = 120, types = 4; + igraph_matrix_t pref_matrix; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&pref_matrix, types, types); + + igraph_rng_seed(igraph_rng_default(), 42); + printf("Randomly generated graph with %" IGRAPH_PRId " nodes and %" IGRAPH_PRId " vertex types\n\n", nodes, types); + + /* Generate preference matrix giving connection probabilities for different vertex types */ + for (igraph_int_t i = 0; i < types; i++) { + for (igraph_int_t j = 0; j < types; j++) { + MATRIX(pref_matrix, i, j) = (i == j ? 0.1: 0.01); + } + } + + igraph_vector_int_t node_type_vec; + igraph_vector_int_init(&node_type_vec, nodes); + + for (int i = 0; i < 5; i++) { + igraph_real_t assortativity; + igraph_t g; + + /* Generate undirected graph with 1000 nodes and 50 vertex types */ + igraph_preference_game(&g, nodes, types, /* type_dist= */ NULL, /* fixed_sizes= */ 1, &pref_matrix, &node_type_vec, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + igraph_assortativity_nominal(&g, NULL, &node_type_vec, &assortativity, IGRAPH_UNDIRECTED, 1); + printf("Assortativity before rewiring = %g\n", assortativity); + + /* Rewire graph */ + igraph_rewire(&g, 10 * igraph_ecount(&g), IGRAPH_SIMPLE_SW, NULL); + + igraph_assortativity_nominal(&g, NULL, &node_type_vec, &assortativity, IGRAPH_UNDIRECTED, 1); + printf("Assortativity after rewiring = %g\n\n", assortativity); + + igraph_destroy(&g); + } + igraph_vector_int_destroy(&node_type_vec); + igraph_matrix_destroy(&pref_matrix); +} diff --git a/examples/simple/igraph_assortativity_nominal.out b/examples/simple/igraph_assortativity_nominal.out new file mode 100644 index 0000000..fc73f94 --- /dev/null +++ b/examples/simple/igraph_assortativity_nominal.out @@ -0,0 +1,17 @@ +Randomly generated graph with 120 nodes and 4 vertex types + +Assortativity before rewiring = 0.690908 +Assortativity after rewiring = -0.0376672 + +Assortativity before rewiring = 0.669352 +Assortativity after rewiring = -0.0178763 + +Assortativity before rewiring = 0.722101 +Assortativity after rewiring = 0.0421349 + +Assortativity before rewiring = 0.704048 +Assortativity after rewiring = -0.0244487 + +Assortativity before rewiring = 0.668842 +Assortativity after rewiring = 0.000612194 + diff --git a/examples/simple/igraph_atlas.c b/examples/simple/igraph_atlas.c new file mode 100644 index 0000000..a5e3009 --- /dev/null +++ b/examples/simple/igraph_atlas.c @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + + /* Initialize the library. */ + igraph_setup(); + + igraph_atlas(&g, 45); + igraph_write_graph_edgelist(&g, stdout); + printf("\n"); + igraph_destroy(&g); + + igraph_atlas(&g, 0); + igraph_write_graph_edgelist(&g, stdout); + printf("\n"); + igraph_destroy(&g); + + igraph_atlas(&g, 1252); + igraph_write_graph_edgelist(&g, stdout); + printf("\n"); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_atlas.out b/examples/simple/igraph_atlas.out new file mode 100644 index 0000000..38233da --- /dev/null +++ b/examples/simple/igraph_atlas.out @@ -0,0 +1,31 @@ +0 4 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 + + +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +1 2 +1 3 +1 4 +1 5 +1 6 +2 3 +2 4 +2 5 +2 6 +3 4 +3 5 +3 6 +4 5 +4 6 +5 6 + diff --git a/examples/simple/igraph_attribute_combination.c b/examples/simple/igraph_attribute_combination.c new file mode 100644 index 0000000..a362136 --- /dev/null +++ b/examples/simple/igraph_attribute_combination.c @@ -0,0 +1,31 @@ +#include + +int main(void) { + igraph_t graph; + igraph_attribute_combination_t comb; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, 0,1, + -1); + + SETEAB(&graph, "type", 0, true); + SETEAB(&graph, "type", 1, false); + + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + "type", IGRAPH_ATTRIBUTE_COMBINE_FIRST, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + igraph_simplify(&graph, /*remove_multiple=*/ true, /*remove_loops=*/ true, &comb); + igraph_write_graph_graphml(&graph, stdout, /*prefixattr=*/ true); + + igraph_destroy(&graph); + igraph_attribute_combination_destroy(&comb); + + return 0; +} diff --git a/examples/simple/igraph_attribute_combination.out b/examples/simple/igraph_attribute_combination.out new file mode 100644 index 0000000..cc6f8dd --- /dev/null +++ b/examples/simple/igraph_attribute_combination.out @@ -0,0 +1,17 @@ + + + + + + + + + + + true + + + diff --git a/examples/simple/igraph_average_path_length.c b/examples/simple/igraph_average_path_length.c new file mode 100644 index 0000000..b23770d --- /dev/null +++ b/examples/simple/igraph_average_path_length.c @@ -0,0 +1,25 @@ + +#include + +int main(void) { + igraph_t graph; + igraph_real_t result; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a random preferential attachment graph. */ + igraph_barabasi_game(&graph, 30, /*power=*/ 1, 30, NULL, /*outpref=*/ false, /*A=*/ 1, + IGRAPH_DIRECTED, IGRAPH_BARABASI_BAG, + /*start_from=*/ NULL); + + /* Compute the average shortest path length. */ + igraph_average_path_length(&graph, /*weights=*/ NULL, &result, + /*unconn_pairs=*/ NULL, IGRAPH_UNDIRECTED, /*unconn=*/ true); + printf("Average length of all-pairs shortest paths: %g\n", result); + + /* Destroy no-longer-needed objects. */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_avg_nearest_neighbor_degree.c b/examples/simple/igraph_avg_nearest_neighbor_degree.c new file mode 100644 index 0000000..3e05998 --- /dev/null +++ b/examples/simple/igraph_avg_nearest_neighbor_degree.c @@ -0,0 +1,65 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_t knn, knnk; + igraph_vector_t weights; + + /* Initialize the library. */ + igraph_setup(); + + igraph_famous(&graph, "Zachary"); + + igraph_vector_init(&knn, 0); + igraph_vector_init(&knnk, 0); + + igraph_avg_nearest_neighbor_degree(&graph, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_ALL, + &knn, &knnk, /*weights=*/ NULL); + + printf("knn: "); + igraph_vector_print(&knn); + printf("knn(k): "); + igraph_vector_print(&knnk); + + igraph_vector_init_range(&weights, 0, igraph_ecount(&graph)); + + igraph_avg_nearest_neighbor_degree(&graph, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_ALL, + &knn, &knnk, &weights); + igraph_vector_destroy(&weights); + + printf("knn: "); + igraph_vector_print(&knn); + printf("knn(k): "); + igraph_vector_print(&knnk); + + igraph_vector_destroy(&knn); + igraph_vector_destroy(&knnk); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_avg_nearest_neighbor_degree.out b/examples/simple/igraph_avg_nearest_neighbor_degree.out new file mode 100644 index 0000000..5fb4f85 --- /dev/null +++ b/examples/simple/igraph_avg_nearest_neighbor_degree.out @@ -0,0 +1,4 @@ +knn: 4.3125 5.77778 6.6 7.66667 7.66667 6.25 6.25 10.25 11.8 13.5 7.66667 16 11 11.6 14.5 14.5 4 12.5 14.5 14 14.5 12.5 14.5 8 4.33333 4.66667 10.5 8.75 11 9 10.75 9 5.08333 3.82353 +knn(k): 16 12.4091 8.22222 8.54167 10.4667 8.33333 NaN NaN 5.77778 6.6 NaN 5.08333 NaN NaN NaN 4.3125 3.82353 +knn: 3.45833 4.28205 5.4346 5.55634 4 3.42373 3.52991 8.64198 11.1104 14.2192 4.73171 16 8.32558 11.6143 14.5269 14.5258 4 11.625 14.5248 14.8953 14.5234 11.7222 14.5225 8.05085 4.34921 4.67935 10.5489 8.81395 11.2892 9.30741 12.0664 8.31319 5.35303 4.05771 +knn(k): 16 12.4884 7.07042 8.37055 9.71906 7.53953 NaN NaN 4.28205 5.4346 NaN 5.35303 NaN NaN NaN 3.45833 4.05771 diff --git a/examples/simple/igraph_barabasi_game.c b/examples/simple/igraph_barabasi_game.c new file mode 100644 index 0000000..e9cbca1 --- /dev/null +++ b/examples/simple/igraph_barabasi_game.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_int_t v; + igraph_vector_int_t v2, v3; + + /* Initialize the library. */ + igraph_setup(); + + igraph_barabasi_game(&g, 10, /*power=*/ 1, 2, 0, 0, /*A=*/ 1, 1, + IGRAPH_BARABASI_BAG, /*start_from=*/ 0); + if (igraph_ecount(&g) != 18) { + return 1; + } + if (igraph_vcount(&g) != 10) { + return 2; + } + if (!igraph_is_directed(&g)) { + return 3; + } + + igraph_vector_int_init(&v, 0); + igraph_get_edgelist(&g, &v, 0); + for (igraph_int_t i = 0; i < igraph_ecount(&g); i++) { + if (VECTOR(v)[2 * i] <= VECTOR(v)[2 * i + 1]) { + return 4; + } + } + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + + /* out-degree sequence */ + igraph_vector_int_init_int(&v3, 10, 0, 1, 3, 3, 4, 5, 6, 7, 8, 9); + + igraph_barabasi_game(&g, 10, /*power=*/ 1, 0, &v3, 0, /*A=*/ 1, 1, + IGRAPH_BARABASI_BAG, /*start_from=*/ 0); + if (igraph_ecount(&g) != igraph_vector_int_sum(&v3)) { + return 5; + } + igraph_vector_int_init(&v2, 0); + igraph_degree(&g, &v2, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + for (igraph_int_t i = 0; i < igraph_vcount(&g); i++) { + if (VECTOR(v3)[i] != VECTOR(v2)[i]) { + igraph_vector_int_print(&v3); + printf("\n"); + igraph_vector_int_print(&v2); + return 6; + } + } + igraph_vector_int_destroy(&v3); + igraph_vector_int_destroy(&v2); + igraph_destroy(&g); + + /* outpref, we cannot really test this quantitatively, + would need to set random seed */ + igraph_barabasi_game(&g, 10, /*power=*/ 1, 2, 0, 1, /*A=*/ 1, 1, + IGRAPH_BARABASI_BAG, /*start_from=*/ 0); + igraph_vector_int_init(&v, 0); + igraph_get_edgelist(&g, &v, 0); + for (igraph_int_t i = 0; i < igraph_ecount(&g); i++) { + if (VECTOR(v)[2 * i] <= VECTOR(v)[2 * i + 1]) { + return 7; + } + } + if (!igraph_is_directed(&g)) { + return 8; + } + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_barabasi_game2.c b/examples/simple/igraph_barabasi_game2.c new file mode 100644 index 0000000..b303da7 --- /dev/null +++ b/examples/simple/igraph_barabasi_game2.c @@ -0,0 +1,110 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g; + igraph_bool_t simple; + + /* Initialize the library. */ + igraph_setup(); + + igraph_barabasi_game(/* graph= */ &g, + /* n= */ 100, + /* power= */ 1.0, + /* m= */ 2, + /* outseq= */ 0, + /* outpref= */ 0, + /* A= */ 1.0, + /* directed= */ IGRAPH_DIRECTED, + /* algo= */ IGRAPH_BARABASI_PSUMTREE, + /* start_from= */ 0); + + if (igraph_ecount(&g) != 197) { + return 1; + } + if (igraph_vcount(&g) != 100) { + return 2; + } + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + if (!simple) { + return 3; + } + + igraph_destroy(&g); + + /* ============================== */ + + igraph_barabasi_game(/* graph= */ &g, + /* n= */ 100, + /* power= */ 1.0, + /* m= */ 2, + /* outseq= */ 0, + /* outpref= */ 0, + /* A= */ 1.0, + /* directed= */ IGRAPH_DIRECTED, + /* algo= */ IGRAPH_BARABASI_PSUMTREE_MULTIPLE, + /* start_from= */ 0); + + if (igraph_ecount(&g) != 198) { + return 4; + } + if (igraph_vcount(&g) != 100) { + return 5; + } + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + if (simple) { + return 6; + } + + igraph_destroy(&g); + + /* ============================== */ + + igraph_barabasi_game(/* graph= */ &g, + /* n= */ 100, + /* power= */ 1.0, + /* m= */ 2, + /* outseq= */ 0, + /* outpref= */ 0, + /* A= */ 1.0, + /* directed= */ IGRAPH_DIRECTED, + /* algo= */ IGRAPH_BARABASI_BAG, + /* start_from= */ 0); + + if (igraph_ecount(&g) != 198) { + return 7; + } + if (igraph_vcount(&g) != 100) { + return 8; + } + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + if (simple) { + return 9; + } + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_bfs.c b/examples/simple/igraph_bfs.c new file mode 100644 index 0000000..5f540f2 --- /dev/null +++ b/examples/simple/igraph_bfs.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2009-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t graph, ring; + igraph_vector_int_t order, rank, father, pred, succ, dist; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a disjoint union of two rings */ + igraph_ring(&ring, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_disjoint_union(&graph, &ring, &ring); + igraph_destroy(&ring); + + /* Initialize the vectors where the result will be stored. Any of these + * can be omitted and replaced with a null pointer when calling + * igraph_bfs() */ + igraph_vector_int_init(&order, 0); + igraph_vector_int_init(&rank, 0); + igraph_vector_int_init(&father, 0); + igraph_vector_int_init(&pred, 0); + igraph_vector_int_init(&succ, 0); + igraph_vector_int_init(&dist, 0); + + /* Now call the BFS function */ + igraph_bfs(&graph, /*root=*/0, /*roots=*/ NULL, /*neimode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, /*restricted=*/ NULL, + &order, &rank, &father, &pred, &succ, &dist, + /*callback=*/ NULL, /*extra=*/ NULL); + + /* Print the results */ + igraph_vector_int_print(&order); + igraph_vector_int_print(&rank); + igraph_vector_int_print(&father); + igraph_vector_int_print(&pred); + igraph_vector_int_print(&succ); + igraph_vector_int_print(&dist); + + /* Cleam up after ourselves */ + igraph_vector_int_destroy(&order); + igraph_vector_int_destroy(&rank); + igraph_vector_int_destroy(&father); + igraph_vector_int_destroy(&pred); + igraph_vector_int_destroy(&succ); + igraph_vector_int_destroy(&dist); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_bfs.out b/examples/simple/igraph_bfs.out new file mode 100644 index 0000000..e15657e --- /dev/null +++ b/examples/simple/igraph_bfs.out @@ -0,0 +1,6 @@ +0 1 9 2 8 3 7 4 6 5 10 11 19 12 18 13 17 14 16 15 +0 1 3 5 7 9 8 6 4 2 10 11 13 15 17 19 18 16 14 12 +-1 0 1 2 3 4 7 8 9 0 -1 10 11 12 13 14 17 18 19 10 +-1 0 9 8 7 6 4 3 2 1 -1 10 19 18 17 16 14 13 12 11 +1 9 8 7 6 -1 5 4 3 2 11 19 18 17 16 -1 15 14 13 12 +0 1 2 3 4 5 4 3 2 1 0 1 2 3 4 5 4 3 2 1 diff --git a/examples/simple/igraph_bfs_callback.c b/examples/simple/igraph_bfs_callback.c new file mode 100644 index 0000000..5735725 --- /dev/null +++ b/examples/simple/igraph_bfs_callback.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2009-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +igraph_error_t bfs_callback(const igraph_t *graph, + igraph_int_t vid, + igraph_int_t pred, + igraph_int_t succ, + igraph_int_t rank, + igraph_int_t dist, + void *extra) { + IGRAPH_UNUSED(graph); + IGRAPH_UNUSED(pred); + IGRAPH_UNUSED(succ); + IGRAPH_UNUSED(rank); + IGRAPH_UNUSED(dist); + printf(" %" IGRAPH_PRId "", vid); + return IGRAPH_SUCCESS; +} + +int main(void) { + igraph_t graph, ring; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a disjoint union of two rings */ + igraph_ring(&ring, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_disjoint_union(&graph, &ring, &ring); + igraph_destroy(&ring); + + /* Now call the BFS function */ + printf("("); + igraph_bfs(&graph, /*root=*/0, /*roots=*/ 0, /*neimode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, /*restricted=*/ 0, + /*order=*/ 0, /*rank=*/ 0, /*father=*/ 0, /*pred=*/ 0, + /*succ=*/ 0, /*dist=*/ 0, + /*callback=*/ bfs_callback, /*extra=*/ 0); + printf(" )\n"); + + /* Cleam up after ourselves */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_bfs_callback.out b/examples/simple/igraph_bfs_callback.out new file mode 100644 index 0000000..ce158cf --- /dev/null +++ b/examples/simple/igraph_bfs_callback.out @@ -0,0 +1 @@ +( 0 1 9 2 8 3 7 4 6 5 10 11 19 12 18 13 17 14 16 15 ) diff --git a/examples/simple/igraph_bfs_simple.c b/examples/simple/igraph_bfs_simple.c new file mode 100644 index 0000000..d8131f6 --- /dev/null +++ b/examples/simple/igraph_bfs_simple.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2009-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_t vids, layers, parents; + + /* Initialize the library. */ + igraph_setup(); + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 0); + + igraph_vector_int_init(&vids, 0); + igraph_vector_int_init(&layers, 0); + igraph_vector_int_init(&parents, 0); + + igraph_bfs_simple(&g, 0, IGRAPH_ALL, &vids, &layers, &parents); + + igraph_vector_int_print(&vids); + igraph_vector_int_print(&layers); + igraph_vector_int_print(&parents); + + igraph_destroy(&g); + + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&layers); + igraph_vector_int_destroy(&parents); + + return 0; +} diff --git a/examples/simple/igraph_bfs_simple.out b/examples/simple/igraph_bfs_simple.out new file mode 100644 index 0000000..7c6d1f1 --- /dev/null +++ b/examples/simple/igraph_bfs_simple.out @@ -0,0 +1,3 @@ +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 9 10 +-1 0 1 2 3 4 5 6 7 8 diff --git a/examples/simple/igraph_biconnected_components.c b/examples/simple/igraph_biconnected_components.c new file mode 100644 index 0000000..dd7d002 --- /dev/null +++ b/examples/simple/igraph_biconnected_components.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +/* Prints a vector of edge IDs as u--v vertex pairs. */ +void print_edge_vector(const igraph_t *graph, const igraph_vector_int_t *edges) { + const igraph_int_t n = igraph_vector_int_size(edges); + for (igraph_int_t i=0; i < n; i++) { + igraph_int_t edge = VECTOR(*edges)[i]; + printf("%" IGRAPH_PRId "--%" IGRAPH_PRId " ", IGRAPH_FROM(graph, edge), IGRAPH_TO(graph, edge)); + } + printf("\n"); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t component_vertices, component_edges; + igraph_int_t no; + + /* Initialize the library. */ + igraph_setup(); + + /* Create an example graph. */ + igraph_small(&graph, 7, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 3,0, + 2,4, 4,5, 5,2, + 0,6, + 0,7, + -1); + + /* The data structures that the result will be written to must be initialized first. */ + igraph_vector_int_list_init(&component_vertices, 0); + igraph_vector_int_list_init(&component_edges, 0); + + igraph_biconnected_components(&graph, &no, NULL, &component_edges, &component_vertices, NULL); + + printf("Number of components: %" IGRAPH_PRId "\n", no); + for (igraph_int_t i=0; i < no; i++) { + printf("\n"); + printf("Component %" IGRAPH_PRId ":\n", i); + printf("Vertices: "); + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&component_vertices, i)); + printf("Edges: "); + print_edge_vector(&graph, igraph_vector_int_list_get_ptr(&component_edges, i)); + } + + /* Destroy data structures after we no longer need them. */ + + igraph_vector_int_list_destroy(&component_edges); + igraph_vector_int_list_destroy(&component_vertices); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_biconnected_components.out b/examples/simple/igraph_biconnected_components.out new file mode 100644 index 0000000..7ebc7ba --- /dev/null +++ b/examples/simple/igraph_biconnected_components.out @@ -0,0 +1,17 @@ +Number of components: 4 + +Component 0: +Vertices: 5 4 2 +Edges: 5--2 5--4 4--2 + +Component 1: +Vertices: 3 2 1 0 +Edges: 3--0 3--2 2--1 1--0 + +Component 2: +Vertices: 6 0 +Edges: 6--0 + +Component 3: +Vertices: 7 0 +Edges: 7--0 diff --git a/examples/simple/igraph_bipartite_create.c b/examples/simple/igraph_bipartite_create.c new file mode 100644 index 0000000..bba1215 --- /dev/null +++ b/examples/simple/igraph_bipartite_create.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_int_t edges_array[] = {0, 1, 1, 2, 3, 4, 5, 6, 6, 5, 1, 4, 1, 6, 0, 3 }; + igraph_vector_int_t edges = + igraph_vector_int_view(edges_array, sizeof(edges_array) / sizeof(edges_array[0])); + igraph_vector_bool_t types; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_bool_init(&types, igraph_vector_int_max(&edges) + 1); + for (igraph_int_t i = 0; i < igraph_vector_bool_size(&types); i++) { + VECTOR(types)[i] = i % 2; + } + igraph_create_bipartite(&g, &types, &edges, IGRAPH_DIRECTED); + igraph_write_graph_edgelist(&g, stdout); + igraph_vector_bool_destroy(&types); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_bipartite_create.out b/examples/simple/igraph_bipartite_create.out new file mode 100644 index 0000000..ef269aa --- /dev/null +++ b/examples/simple/igraph_bipartite_create.out @@ -0,0 +1,8 @@ +0 1 +0 3 +1 2 +1 4 +1 6 +3 4 +5 6 +6 5 diff --git a/examples/simple/igraph_bipartite_projection.c b/examples/simple/igraph_bipartite_projection.c new file mode 100644 index 0000000..931996a --- /dev/null +++ b/examples/simple/igraph_bipartite_projection.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t g, p1, p2; + igraph_vector_bool_t types; + igraph_vector_int_t mult1, mult2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&mult1, 0); + igraph_vector_int_init(&mult2, 0); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 0,3, 3,2, 2,4, 4,5, -1); + igraph_vector_bool_init_int(&types, 6, 0, 1, 0, 1, 1, 0); + + igraph_bipartite_projection(&g, &types, &p1, &p2, &mult1, &mult2, /*probe1=*/ 1); + + igraph_write_graph_edgelist(&p1, stdout); + printf("\n"); + igraph_write_graph_edgelist(&p2, stdout); + printf("\n"); + + igraph_vector_int_print(&mult1); + igraph_vector_int_print(&mult2); + + igraph_vector_int_destroy(&mult1); + igraph_vector_int_destroy(&mult2); + igraph_destroy(&p1); + igraph_destroy(&p2); + igraph_destroy(&g); + igraph_vector_bool_destroy(&types); + + return 0; +} diff --git a/examples/simple/igraph_cliques.c b/examples/simple/igraph_cliques.c new file mode 100644 index 0000000..c185608 --- /dev/null +++ b/examples/simple/igraph_cliques.c @@ -0,0 +1,147 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int compare_vectors(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2) { + igraph_int_t s1, s2, i; + + s1 = igraph_vector_int_size(v1); + s2 = igraph_vector_int_size(v2); + if (s1 < s2) { + return -1; + } + if (s1 > s2) { + return 1; + } + for (i = 0; i < s1; ++i) { + if (VECTOR(*v1)[i] < VECTOR(*v2)[i]) { + return -1; + } + if (VECTOR(*v1)[i] > VECTOR(*v2)[i]) { + return 1; + } + } + return 0; +} + +/* Takes a pointer vector of vectors. Sorts each vector, then sorts the pointer vector */ +void canonicalize_list(igraph_vector_int_list_t *list) { + igraph_int_t len = igraph_vector_int_list_size(list); + for (igraph_int_t i = 0; i < len; ++i) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(list, i)); + } + igraph_vector_int_list_sort(list, &compare_vectors); +} + +struct userdata { + igraph_int_t i; + igraph_vector_int_list_t *list; +}; + +igraph_error_t handler(const igraph_vector_int_t *clique, void *arg) { + struct userdata *ud; + igraph_bool_t cont; + + ud = (struct userdata *) arg; + cont = 1; /* true */ + + if (compare_vectors(clique, igraph_vector_int_list_get_ptr(ud->list, ud->i)) != 0) { + printf("igraph_cliques() and igraph_cliques_callback() give different results.\n"); + cont = 0; /* false */ + } + + ud->i += 1; + + return cont ? IGRAPH_SUCCESS : IGRAPH_STOP; +} + +void test_callback(const igraph_t *graph) { + igraph_vector_int_list_t list; + struct userdata ud; + + igraph_vector_int_list_init(&list, 0); + igraph_cliques(graph, &list, 0, 0, IGRAPH_UNLIMITED); + + ud.i = 0; + ud.list = &list; + + igraph_cliques_callback(graph, 0, 0, &handler, (void *) &ud); + + igraph_vector_int_list_destroy(&list); +} + + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t result; + igraph_es_t es; + igraph_int_t omega; + igraph_int_t i, j, n; + const int params[] = {4, -1, 2, 2, 0, 0, -1, -1}; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_warning_handler(igraph_warning_handler_ignore); + + igraph_vector_int_list_init(&result, 0); + igraph_full(&g, 6, 0, 0); + igraph_es_pairs_small(&es, 0, 0, 1, 0, 2, 3, 5, -1); + igraph_delete_edges(&g, es); + igraph_es_destroy(&es); + + for (j = 0; j < sizeof(params) / (2 * sizeof(params[0])); j++) { + if (params[2 * j + 1] != 0) { + igraph_cliques(&g, &result, params[2 * j], params[2 * j + 1], IGRAPH_UNLIMITED); + } else { + igraph_largest_cliques(&g, &result); + } + n = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " cliques found\n", n); + canonicalize_list(&result); + for (i = 0; i < n; i++) { + igraph_vector_int_t* v = igraph_vector_int_list_get_ptr(&result, i); + igraph_vector_int_print(v); + } + } + + igraph_clique_number(&g, &omega); + printf("omega=%" IGRAPH_PRId "\n", omega); + + test_callback(&g); + + igraph_destroy(&g); + + igraph_kary_tree(&g, 5, 2, IGRAPH_TREE_OUT); + igraph_cliques(&g, &result, 5, 5, IGRAPH_UNLIMITED); + if (igraph_vector_int_list_size(&result) != 0) { + return 1; + } + + igraph_destroy(&g); + igraph_vector_int_list_destroy(&result); + + return 0; +} diff --git a/examples/simple/igraph_cliques.out b/examples/simple/igraph_cliques.out new file mode 100644 index 0000000..351d033 --- /dev/null +++ b/examples/simple/igraph_cliques.out @@ -0,0 +1,50 @@ +2 cliques found +1 2 3 4 +1 2 4 5 +12 cliques found +0 3 +0 4 +0 5 +1 2 +1 3 +1 4 +1 5 +2 3 +2 4 +2 5 +3 4 +4 5 +2 cliques found +1 2 3 4 +1 2 4 5 +29 cliques found +0 +1 +2 +3 +4 +5 +0 3 +0 4 +0 5 +1 2 +1 3 +1 4 +1 5 +2 3 +2 4 +2 5 +3 4 +4 5 +0 3 4 +0 4 5 +1 2 3 +1 2 4 +1 2 5 +1 3 4 +1 4 5 +2 3 4 +2 4 5 +1 2 3 4 +1 2 4 5 +omega=4 diff --git a/examples/simple/igraph_cocitation.c b/examples/simple/igraph_cocitation.c new file mode 100644 index 0000000..9ceb49c --- /dev/null +++ b/examples/simple/igraph_cocitation.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_matrix_t matrix; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a small test graph. */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 0, 1, 2, 1, 2, 0, 3, 0, + -1); + + /* As usual with igraph functions, the data structure in which the result + will be returned must be initialized in advance. */ + igraph_matrix_init(&matrix, 0, 0); + igraph_bibcoupling(&graph, &matrix, igraph_vss_all()); + printf("Bibliographic coupling matrix:\n"); + igraph_matrix_print(&matrix); + + igraph_cocitation(&graph, &matrix, igraph_vss_all()); + printf("\nCocitation matrix:\n"); + igraph_matrix_print(&matrix); + + /* Destroy data structures when we are done with them. */ + igraph_matrix_destroy(&matrix); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_cocitation.out b/examples/simple/igraph_cocitation.out new file mode 100644 index 0000000..8d83c5d --- /dev/null +++ b/examples/simple/igraph_cocitation.out @@ -0,0 +1,11 @@ +Bibliographic coupling matrix: +0 0 1 0 +0 0 0 0 +1 0 0 1 +0 0 1 0 + +Cocitation matrix: +0 1 0 0 +1 0 0 0 +0 0 0 0 +0 0 0 0 diff --git a/examples/simple/igraph_community_edge_betweenness.c b/examples/simple/igraph_community_edge_betweenness.c new file mode 100644 index 0000000..705f7e6 --- /dev/null +++ b/examples/simple/igraph_community_edge_betweenness.c @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t edges; + igraph_vector_t eb, weights; + igraph_matrix_int_t merges; + igraph_vector_int_t bridges; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 0, 2, 0, 3, 1, 3, 1, 4, + -1); + igraph_vector_init_range(&weights, 1, igraph_ecount(&graph)+1); + igraph_vector_init(&eb, 0); + igraph_vector_int_init(&edges, 0); + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_int_init(&bridges, 0); + igraph_community_edge_betweenness(&graph, &edges, &eb, &merges, + &bridges, /*modularity*/ NULL, + /*membership*/ NULL, + IGRAPH_UNDIRECTED, + &weights, + NULL); + printf("edges:\n"); + igraph_vector_int_print(&edges); + printf("edge betweenness:\n"); + igraph_vector_print(&eb); + printf("merges:\n"); + igraph_matrix_int_print(&merges); + printf("bridges:\n"); + igraph_vector_int_print(&bridges); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&eb); + igraph_vector_destroy(&weights); + igraph_matrix_int_destroy(&merges); + igraph_vector_int_destroy(&bridges); + + return 0; +} diff --git a/examples/simple/igraph_community_edge_betweenness.out b/examples/simple/igraph_community_edge_betweenness.out new file mode 100644 index 0000000..d89f4a6 --- /dev/null +++ b/examples/simple/igraph_community_edge_betweenness.out @@ -0,0 +1,11 @@ +edges: +0 1 3 4 2 5 +edge betweenness: +2 3.5 6 2 1 1 +merges: +1 4 +0 2 +5 3 +6 7 +bridges: +5 4 3 2 diff --git a/examples/simple/igraph_community_fastgreedy.c b/examples/simple/igraph_community_fastgreedy.c new file mode 100644 index 0000000..bf6649b --- /dev/null +++ b/examples/simple/igraph_community_fastgreedy.c @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + igraph_matrix_int_t merges; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init_int(&weights, 8, 10, 10, 1, 1, 1, 1, 1, 1); + + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 2,4, 2,5, 3,4, 3,5, 4,5, -1); + igraph_community_fastgreedy(&graph, &weights, &merges, + /*modularity*/ NULL, + /*membership=*/ NULL); + igraph_matrix_int_print(&merges); + igraph_destroy(&graph); + igraph_vector_destroy(&weights); + igraph_matrix_int_destroy(&merges); + + return 0; +} diff --git a/examples/simple/igraph_community_fastgreedy.out b/examples/simple/igraph_community_fastgreedy.out new file mode 100644 index 0000000..ba79f77 --- /dev/null +++ b/examples/simple/igraph_community_fastgreedy.out @@ -0,0 +1,5 @@ +1 0 +2 6 +3 4 +8 5 +9 7 diff --git a/examples/simple/igraph_community_label_propagation.c b/examples/simple/igraph_community_label_propagation.c new file mode 100644 index 0000000..5bfe90d --- /dev/null +++ b/examples/simple/igraph_community_label_propagation.c @@ -0,0 +1,59 @@ +/* + igraph library. + Copyright (C) 2007-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t membership; + igraph_real_t modularity; + + /* Initialize the library. */ + igraph_setup(); + + igraph_famous(&graph, "Zachary"); /* We use Zachary's karate club network. */ + + /* Label propagation is a stochastic method; the result will depend on the random seed. */ + igraph_rng_seed(igraph_rng_default(), 123); + + /* All igraph functions that returns their result in an igraph_vector_t must be given + an already initialized vector. */ + igraph_vector_int_init(&membership, 0); + igraph_community_label_propagation( + &graph, &membership, /* mode = */ IGRAPH_ALL, + /* weights= */ NULL, /* initial= */ NULL, /* fixed= */ NULL, + IGRAPH_LPA_DOMINANCE); + + /* Also calculate the modularity of the partition */ + igraph_modularity( + &graph, &membership, /* weights= */ NULL, /* resolution = */ 1, + /* directed= */ false, &modularity); + + printf("%" IGRAPH_PRId " communities found; modularity score is %g.\n", + igraph_vector_int_max(&membership) + 1, modularity); + + printf("Communities membership: "); + igraph_vector_int_print(&membership); + + /* Destroy data structures at the end. */ + igraph_vector_int_destroy(&membership); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_community_label_propagation.out b/examples/simple/igraph_community_label_propagation.out new file mode 100644 index 0000000..24dd8f6 --- /dev/null +++ b/examples/simple/igraph_community_label_propagation.out @@ -0,0 +1,2 @@ +3 communities found; modularity score is 0.394395. +Communities membership: 0 0 0 0 1 1 1 0 0 0 1 0 0 0 2 2 1 0 2 0 2 0 2 2 2 2 2 2 2 2 0 2 2 2 diff --git a/examples/simple/igraph_community_leading_eigenvector.c b/examples/simple/igraph_community_leading_eigenvector.c new file mode 100644 index 0000000..aa9e3c2 --- /dev/null +++ b/examples/simple/igraph_community_leading_eigenvector.c @@ -0,0 +1,92 @@ +/* + igraph library. + Copyright (C) 2007-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + + igraph_t g; + igraph_matrix_int_t merges; + igraph_vector_int_t membership; + + /* Initialize the library. */ + igraph_setup(); + + /* Zachary Karate club */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_int_init(&membership, 0); + + igraph_community_leading_eigenvector(&g, /*weights=*/ NULL, + &merges, &membership, + /*steps=*/ 1, + /*options=*/ NULL, + /*modularity=*/ NULL, + /*start=*/ NULL, + /*eigenvalues=*/ NULL, + /*eigenvectors=*/ NULL, + /*history=*/ NULL, + /*callback=*/ NULL, + /*callback_extra=*/ NULL); + + igraph_matrix_int_print(&merges); + igraph_vector_int_print(&membership); + + printf("\n"); + + /* Make all the steps */ + igraph_community_leading_eigenvector(&g, /*weights=*/ NULL, + &merges, &membership, + /*steps=*/ igraph_vcount(&g), + /*options=*/ NULL, + /*modularity=*/ NULL, + /*start=*/ NULL, + /*eigenvalues=*/ NULL, + /*eigenvectors=*/ NULL, + /*history=*/ NULL, + /*callback=*/ NULL, + /*callback_extra=*/ NULL); + + igraph_matrix_int_print(&merges); + igraph_vector_int_print(&membership); + + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_community_leading_eigenvector.out b/examples/simple/igraph_community_leading_eigenvector.out new file mode 100644 index 0000000..195196c --- /dev/null +++ b/examples/simple/igraph_community_leading_eigenvector.out @@ -0,0 +1,7 @@ +0 1 +0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 + +1 3 +0 2 +5 4 +0 2 2 2 0 0 0 2 1 1 0 0 2 2 1 1 0 2 1 2 1 2 1 3 3 3 1 3 3 1 1 3 1 1 diff --git a/examples/simple/igraph_community_leiden.c b/examples/simple/igraph_community_leiden.c new file mode 100644 index 0000000..29ea5b0 --- /dev/null +++ b/examples/simple/igraph_community_leiden.c @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2007-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t membership; + igraph_vector_t vertex_weights; + igraph_vector_t vertex_out_weights, vertex_in_weights; + igraph_int_t nb_clusters; + igraph_real_t quality; + + /* Initialize the library. */ + igraph_setup(); + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 0); + + /* UNDIRECTED EXAMPLE */ + + /* Simple unweighted undirected graph */ + igraph_small(&graph, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + + /* Perform Leiden algorithm using CPM for 1 iteration */ + igraph_vector_int_init(&membership, igraph_vcount(&graph)); + igraph_community_leiden(&graph, NULL, NULL, NULL, + /* resolution */ 0.05, + /* beta */ 0.01, + /* start */ false, + /* iterations */ 1, + &membership, &nb_clusters, &quality); + + printf("Leiden found %" IGRAPH_PRId " clusters using CPM (resolution parameter 0.05), quality is %.4f.\n", nb_clusters, quality); + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + /* Start from existing membership for 10 iterations to improve it further */ + igraph_community_leiden(&graph, NULL, NULL, NULL, + /* resolution */ 0.05, + /* beta */ 0.01, + /* start */ true, + /* iterations */ 10, + &membership, &nb_clusters, &quality); + + printf("Iterated Leiden, using CPM (resolution parameter 0.05), quality is %.4f.\n", quality); + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + /* Use degrees as vertex weights for optimizing modularity */ + igraph_vector_init(&vertex_weights, igraph_vcount(&graph)); + igraph_strength(&graph, &vertex_weights, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, NULL); + + /* Perform Leiden algorithm using modularity until stable iteration */ + igraph_community_leiden(&graph, NULL, &vertex_weights, NULL, + /* resolution */ 1.0 / (2 * igraph_ecount(&graph)), + /* beta */ 0.01, + /* start */ false, + /* iterations */ -1, + &membership, &nb_clusters, &quality); + + printf("Leiden found %" IGRAPH_PRId " clusters using modularity, quality is %.4f.\n", nb_clusters, quality); + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + igraph_vector_destroy(&vertex_weights); + igraph_vector_int_destroy(&membership); + igraph_destroy(&graph); + + /* DIRECTED EXAMPLE */ + + /* Simple unweighted directed graph */ + igraph_small(&graph, 6, IGRAPH_DIRECTED, + 0, 1, 0, 3, 0, 5, 1, 3, 1, 4, 2, 3, 4, 1, 5, 2, 5, 4, + -1); + + igraph_vector_int_init(&membership, igraph_vcount(&graph)); + igraph_vector_init(&vertex_out_weights, igraph_vcount(&graph)); + igraph_vector_init(&vertex_in_weights, igraph_vcount(&graph)); + igraph_strength(&graph, &vertex_out_weights, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, NULL); + igraph_strength(&graph, &vertex_in_weights, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, NULL); + + /* Perform Leiden algorithm using modularity for two iterations, + * which are usually sufficient to achieve stability. + * Note that in the directed case, we divide by the edge count m, not 2*m. */ + igraph_community_leiden(&graph, NULL, &vertex_out_weights, &vertex_in_weights, + /* resolution */ 1.0 / igraph_ecount(&graph), + /* beta */ 0.01, + /* start */ false, + /* iterations */ 2, + &membership, &nb_clusters, &quality); + + printf("Leiden found %" IGRAPH_PRId " clusters using modularity, quality is %.4f.\n", nb_clusters, quality); + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + igraph_vector_destroy(&vertex_in_weights); + igraph_vector_destroy(&vertex_out_weights); + igraph_vector_int_destroy(&membership); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_community_leiden.out b/examples/simple/igraph_community_leiden.out new file mode 100644 index 0000000..4c1ef20 --- /dev/null +++ b/examples/simple/igraph_community_leiden.out @@ -0,0 +1,12 @@ +Leiden found 2 clusters using CPM (resolution parameter 0.05), quality is 0.8929. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Iterated Leiden, using CPM (resolution parameter 0.05), quality is 0.8929. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Leiden found 2 clusters using modularity, quality is 0.4524. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Leiden found 3 clusters using modularity, quality is 0.1852. +Membership: 0 1 2 2 1 0 + diff --git a/examples/simple/igraph_community_multilevel.c b/examples/simple/igraph_community_multilevel.c new file mode 100644 index 0000000..cdcfa48 --- /dev/null +++ b/examples/simple/igraph_community_multilevel.c @@ -0,0 +1,115 @@ +/* vim:set ts=4 sts=4 sw=4 et: */ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void show_results(igraph_t *g, igraph_vector_int_t *membership, igraph_matrix_int_t *memberships, igraph_vector_t *modularity, FILE* f) { + igraph_int_t i, j, no_of_nodes = igraph_vcount(g); + + j = igraph_vector_which_max(modularity); + for (i = 0; i < igraph_vector_int_size(membership); i++) { + if (VECTOR(*membership)[i] != MATRIX(*memberships, j, i)) { + fprintf(f, "WARNING: best membership vector element %" IGRAPH_PRId " does not match the best one in the membership matrix\n", i); + } + } + + fprintf(f, "Modularities:\n"); + igraph_vector_print(modularity); + + for (i = 0; i < igraph_matrix_int_nrow(memberships); i++) { + for (j = 0; j < no_of_nodes; j++) { + fprintf(f, "%" IGRAPH_PRId " ", MATRIX(*memberships, i, j)); + } + fprintf(f, "\n"); + } + + fprintf(f, "\n"); +} + +int main(void) { + igraph_setup(); + igraph_t g; + igraph_vector_t modularity; + igraph_vector_int_t edges; + igraph_vector_int_t membership; + igraph_matrix_int_t memberships; + igraph_int_t i, j, k; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + igraph_matrix_int_init(&memberships, 0, 0); + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Unweighted test graph from the paper of Blondel et al */ + igraph_small(&g, 16, IGRAPH_UNDIRECTED, + 0, 2, 0, 3, 0, 4, 0, 5, + 1, 2, 1, 4, 1, 7, + 2, 4, 2, 5, 2, 6, + 3, 7, + 4, 10, + 5, 7, 5, 11, + 6, 7, 6, 11, + 8, 9, 8, 10, 8, 11, 8, 14, 8, 15, + 9, 12, 9, 14, + 10, 11, 10, 12, 10, 13, 10, 14, + 11, 13, + -1); + igraph_community_multilevel(&g, 0, 1, &membership, &memberships, &modularity); + show_results(&g, &membership, &memberships, &modularity, stdout); + + /* Higher resolution */ + igraph_community_multilevel(&g, 0, 1.5, &membership, &memberships, &modularity); + show_results(&g, &membership, &memberships, &modularity, stdout); + + igraph_destroy(&g); + + /* Ring of 30 cliques */ + igraph_vector_int_init(&edges, 0); + for (i = 0; i < 30; i++) { + for (j = 0; j < 5; j++) { + for (k = j + 1; k < 5; k++) { + igraph_vector_int_push_back(&edges, i * 5 + j); + igraph_vector_int_push_back(&edges, i * 5 + k); + } + } + } + for (i = 0; i < 30; i++) { + igraph_vector_int_push_back(&edges, i * 5 % 150); + igraph_vector_int_push_back(&edges, (i * 5 + 6) % 150); + } + igraph_create(&g, &edges, 150, 0); + igraph_community_multilevel(&g, 0, 1, &membership, &memberships, &modularity); + show_results(&g, &membership, &memberships, &modularity, stdout); + igraph_destroy(&g); + + igraph_vector_destroy(&modularity); + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&edges); + igraph_matrix_int_destroy(&memberships); + + return 0; +} diff --git a/examples/simple/igraph_community_multilevel.out b/examples/simple/igraph_community_multilevel.out new file mode 100644 index 0000000..0434329 --- /dev/null +++ b/examples/simple/igraph_community_multilevel.out @@ -0,0 +1,14 @@ +Modularities: +0.346301 0.392219 +0 0 0 1 0 0 1 1 2 2 2 3 2 3 2 2 +0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + +Modularities: +0.195472 +0 1 1 0 1 0 1 0 2 2 3 3 2 3 2 2 + +Modularities: +0.875758 0.887879 +0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 7 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 15 15 15 15 15 16 16 16 16 16 17 17 17 17 17 18 18 18 18 18 19 19 19 19 19 20 20 20 20 20 21 21 21 21 21 22 22 22 22 22 23 23 23 23 23 24 24 24 24 24 25 25 25 25 25 26 26 26 26 26 27 27 27 27 27 28 28 28 28 28 29 29 29 29 29 +0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 0 0 0 0 0 + diff --git a/examples/simple/igraph_community_optimal_modularity.c b/examples/simple/igraph_community_optimal_modularity.c new file mode 100644 index 0000000..1c6b35d --- /dev/null +++ b/examples/simple/igraph_community_optimal_modularity.c @@ -0,0 +1,57 @@ +/* igraph library. + Copyright (C) 2010-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t membership; + igraph_real_t modularity; + igraph_error_handler_t *handler; + igraph_error_t errcode; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&graph, 9, IGRAPH_UNDIRECTED, + 0, 3, 0, 4, 0, 1, 0, 2, 0, 5, 0, 6, 1, 3, 3, 5, 4, 7, 7, 8, 2, 4, 1, 2, 6, 8, + -1); + + igraph_vector_int_init(&membership, 0); + + /* Temporarily ignore errors so we can check if the functionality is available. */ + handler = igraph_set_error_handler(&igraph_error_handler_printignore); + + errcode = igraph_community_optimal_modularity(&graph, NULL, 1, &modularity, &membership); + if (errcode == IGRAPH_UNIMPLEMENTED) { + /* igraph was not build with GLPK; functionality unavailable, so we quit */ + return 77; + } + + /* Restore the previous error handler. */ + igraph_set_error_handler(handler); + + printf("Highest possible modularity: %g\n", modularity); + printf("Membership: "); + igraph_vector_int_print(&membership); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&membership); + + return 0; +} diff --git a/examples/simple/igraph_community_optimal_modularity.out b/examples/simple/igraph_community_optimal_modularity.out new file mode 100644 index 0000000..2003164 --- /dev/null +++ b/examples/simple/igraph_community_optimal_modularity.out @@ -0,0 +1,2 @@ +Highest possible modularity: 0.221893 +Membership: 0 0 0 0 1 0 1 1 1 diff --git a/examples/simple/igraph_complementer.c b/examples/simple/igraph_complementer.c new file mode 100644 index 0000000..41dcd91 --- /dev/null +++ b/examples/simple/igraph_complementer.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g1, g2; + + /* Initialize the library. */ + igraph_setup(); + + /* complementer of the empty graph */ + igraph_empty(&g1, 5, IGRAPH_DIRECTED); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* the same without loops */ + igraph_empty(&g1, 5, IGRAPH_DIRECTED); + igraph_complementer(&g2, &g1, IGRAPH_NO_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* complementer of the full graph */ + igraph_full(&g1, 5, IGRAPH_DIRECTED, IGRAPH_LOOPS); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + if (igraph_ecount(&g2) != 0) { + return 1; + } + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* complementer of the full graph, results loops only */ + igraph_full(&g1, 5, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /************** + * undirected * + *************/ + + /* complementer of the empty graph */ + igraph_empty(&g1, 5, IGRAPH_UNDIRECTED); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* the same without loops */ + igraph_empty(&g1, 5, IGRAPH_UNDIRECTED); + igraph_complementer(&g2, &g1, IGRAPH_NO_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* complementer of the full graph */ + igraph_full(&g1, 5, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + if (igraph_ecount(&g2) != 0) { + return 1; + } + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("---\n"); + + /* complementer of the full graph, results loops only */ + igraph_full(&g1, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_complementer(&g2, &g1, IGRAPH_LOOPS); + igraph_write_graph_edgelist(&g2, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + + return 0; +} diff --git a/examples/simple/igraph_complementer.out b/examples/simple/igraph_complementer.out new file mode 100644 index 0000000..25f2fdb --- /dev/null +++ b/examples/simple/igraph_complementer.out @@ -0,0 +1,87 @@ +0 0 +0 1 +0 2 +0 3 +0 4 +1 0 +1 1 +1 2 +1 3 +1 4 +2 0 +2 1 +2 2 +2 3 +2 4 +3 0 +3 1 +3 2 +3 3 +3 4 +4 0 +4 1 +4 2 +4 3 +4 4 +--- +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +--- +--- +0 0 +1 1 +2 2 +3 3 +4 4 +--- +0 0 +0 1 +0 2 +0 3 +0 4 +1 1 +1 2 +1 3 +1 4 +2 2 +2 3 +2 4 +3 3 +3 4 +4 4 +--- +0 1 +0 2 +0 3 +0 4 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 +--- +--- +0 0 +1 1 +2 2 +3 3 +4 4 diff --git a/examples/simple/igraph_compose.c b/examples/simple/igraph_compose.c new file mode 100644 index 0000000..a700564 --- /dev/null +++ b/examples/simple/igraph_compose.c @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g1, g2, res; + igraph_vector_int_t v; + igraph_vector_int_t map1, map2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&map1, 0); + igraph_vector_int_init(&map2, 0); + + /* composition with the empty graph */ + igraph_empty(&g1, 5, IGRAPH_DIRECTED); + igraph_full(&g2, 5, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + igraph_compose(&res, &g1, &g2, &map1, &map2); + if (igraph_ecount(&res) != 0) { + return 1; + } + if (igraph_vector_int_size(&map1) != 0 || igraph_vector_int_size(&map2) != 0) { + return 11; + } + igraph_destroy(&res); + igraph_compose(&res, &g2, &g1, &map1, &map2); + if (igraph_ecount(&res) != 0) { + return 2; + } + if (igraph_vector_int_size(&map1) != 0 || igraph_vector_int_size(&map2) != 0) { + return 12; + } + igraph_destroy(&res); + igraph_destroy(&g1); + igraph_destroy(&g2); + + /* same but undirected */ + igraph_empty(&g1, 5, IGRAPH_UNDIRECTED); + igraph_full(&g2, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_compose(&res, &g1, &g2, &map1, &map2); + if (igraph_ecount(&res) != 0) { + return 1; + } + if (igraph_vector_int_size(&map1) != 0 || igraph_vector_int_size(&map2) != 0) { + return 11; + } + igraph_destroy(&res); + igraph_compose(&res, &g2, &g1, &map1, &map2); + if (igraph_ecount(&res) != 0) { + return 2; + } + if (igraph_vector_int_size(&map1) != 0 || igraph_vector_int_size(&map2) != 0) { + return 12; + } + igraph_destroy(&res); + igraph_destroy(&g1); + igraph_destroy(&g2); + + /* proper directed graph */ + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 5, 6, -1); + igraph_create(&g1, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 2, 4, 5, 6, -1); + igraph_create(&g2, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_compose(&res, &g1, &g2, &map1, &map2); + igraph_write_graph_edgelist(&res, stdout); + igraph_vector_int_print(&map1); + igraph_vector_int_print(&map2); + igraph_destroy(&res); + igraph_destroy(&g1); + igraph_destroy(&g2); + + /* undirected graph */ + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 5, 6, -1); + igraph_create(&g1, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 0, 4, 5, 6, -1); + igraph_create(&g2, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_compose(&res, &g1, &g2, &map1, &map2); + igraph_write_graph_edgelist(&res, stdout); + igraph_vector_int_print(&map1); + igraph_vector_int_print(&map2); + igraph_destroy(&res); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_vector_int_destroy(&map2); + igraph_vector_int_destroy(&map1); + + return 0; +} diff --git a/examples/simple/igraph_compose.out b/examples/simple/igraph_compose.out new file mode 100644 index 0000000..0f37aa3 --- /dev/null +++ b/examples/simple/igraph_compose.out @@ -0,0 +1,11 @@ +1 4 +1 +1 +0 0 +0 2 +1 1 +1 4 +5 5 +6 6 +0 0 0 1 2 2 +0 1 0 0 2 2 diff --git a/examples/simple/igraph_contract_vertices.c b/examples/simple/igraph_contract_vertices.c new file mode 100644 index 0000000..ff9c565 --- /dev/null +++ b/examples/simple/igraph_contract_vertices.c @@ -0,0 +1,84 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +/* Create the condensation of a directed graph. + * See https://en.wikipedia.org/wiki/Strongly_connected_component#Definitions + * This example demonstrates how to write a basic igraph function, complete + * with error handling. */ +igraph_error_t condensation(const igraph_t *graph, igraph_t *cond) { + igraph_vector_int_t membership; + + /* Data structures such as vector must be initialized in igraph before use. */ + IGRAPH_CHECK(igraph_vector_int_init(&membership, 0)); + + /* Adding the initialized vector to the "finally" stack ensures that it will + * be automatically destroyed if an error occurs. */ + IGRAPH_FINALLY(igraph_vector_int_destroy, &membership); + + /* Functions that return an error code can be wrapped in IGRAPH_CHECK to pass that error + * up to the caller. */ + IGRAPH_CHECK(igraph_connected_components(graph, &membership, /* csize */ NULL, /* no */ NULL, IGRAPH_STRONG)); + + /* To compute the condensation, we simply contract strongly connected components. + * Since igraph_contract_vertices() modifies graphs in-place, we make a copy first. */ + IGRAPH_CHECK(igraph_copy(cond, graph)); + + /* Since we are not done creating the condensation yet, we add 'cond' to the + * "finally" stack, so that it will be destroyed if an error occurs. */ + IGRAPH_FINALLY(igraph_destroy, cond); + + /* Contract strongly connected components. */ + IGRAPH_CHECK(igraph_contract_vertices(cond, &membership, NULL)); + + /* igraph_contract_vertices() preserves all edges, some of which become + * parallel edges or self-loops after the contraction. We simplify these. */ + IGRAPH_CHECK(igraph_simplify(cond, /* remove_multiple */ true, /* remove_loops */ true, NULL)); + + /* Data structures that are no longer needed must be explicitly destroyed. + * If they were added to the "finally" stack, they must be removed explicitly, + * in the opposite order to how they were added. IGRAPH_FINALLY_CLEAN removes + * the indicated number of entries from the "finally" stack. We remove + * 'membership' because it was destroyed, and 'cond' because the responsibility + * to destroy it is now with the caller. */ + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; /* return with no error */ +} + +int main(void) { + igraph_t graph, cond; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a random directed graph with mean degree 2 and compute its condensation. */ + igraph_erdos_renyi_game_gnm(&graph, 100, 200, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + condensation(&graph, &cond); + + printf("Number of vertices in the condensation: %" IGRAPH_PRId "\n", igraph_vcount(&cond)); + igraph_write_graph_edgelist(&cond, stdout); + + /* Destroy data structures that are no longer needed. */ + igraph_destroy(&graph); + igraph_destroy(&cond); + + return 0; +} diff --git a/examples/simple/igraph_copy.c b/examples/simple/igraph_copy.c new file mode 100644 index 0000000..642e663 --- /dev/null +++ b/examples/simple/igraph_copy.c @@ -0,0 +1,58 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g1, g2; + igraph_vector_int_t v1, v2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&v1, 8); + VECTOR(v1)[0] = 0; + VECTOR(v1)[1] = 1; + VECTOR(v1)[2] = 1; + VECTOR(v1)[3] = 2; + VECTOR(v1)[4] = 2; + VECTOR(v1)[5] = 3; + VECTOR(v1)[6] = 2; + VECTOR(v1)[7] = 2; + + igraph_create(&g1, &v1, 0, 0); + igraph_copy(&g2, &g1); + + igraph_vector_int_init(&v2, 0); + igraph_get_edgelist(&g2, &v2, 0); + if (!igraph_vector_int_all_e(&v1, &v2)) { + return 1; + } + + igraph_vector_int_destroy(&v1); + igraph_vector_int_destroy(&v2); + igraph_destroy(&g1); + igraph_destroy(&g2); + + return 0; +} diff --git a/examples/simple/igraph_create.c b/examples/simple/igraph_create.c new file mode 100644 index 0000000..0114aeb --- /dev/null +++ b/examples/simple/igraph_create.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_int_t v1, v2; + + /* Initialize the library. */ + igraph_setup(); + + /* simple use */ + igraph_vector_int_init(&v1, 8); + VECTOR(v1)[0] = 0; + VECTOR(v1)[1] = 1; + VECTOR(v1)[2] = 1; + VECTOR(v1)[3] = 2; + VECTOR(v1)[4] = 2; + VECTOR(v1)[5] = 3; + VECTOR(v1)[6] = 2; + VECTOR(v1)[7] = 2; + igraph_create(&g, &v1, 0, 0); + if (igraph_vcount(&g) != 4) { + return 1; + } + igraph_vector_int_init(&v2, 0); + igraph_get_edgelist(&g, &v2, 0); + igraph_vector_int_sort(&v1); + igraph_vector_int_sort(&v2); + if (!igraph_vector_int_all_e(&v1, &v2)) { + return 2; + } + igraph_destroy(&g); + + /* higher number of vertices */ + igraph_create(&g, &v1, 10, 0); + if (igraph_vcount(&g) != 10) { + return 1; + } + igraph_get_edgelist(&g, &v2, 0); + igraph_vector_int_sort(&v1); + igraph_vector_int_sort(&v2); + if (!igraph_vector_int_all_e(&v1, &v2)) { + return 3; + } + igraph_destroy(&g); + igraph_vector_int_destroy(&v1); + igraph_vector_int_destroy(&v2); + + return 0; +} diff --git a/examples/simple/igraph_decompose.c b/examples/simple/igraph_decompose.c new file mode 100644 index 0000000..2de2963 --- /dev/null +++ b/examples/simple/igraph_decompose.c @@ -0,0 +1,50 @@ + +#include +#include + +int main(void) { + + igraph_t ring, g, *component; + igraph_graph_list_t complist; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + igraph_graph_list_init(&complist, 0); + + /* A ring, a single component */ + igraph_ring(&ring, 10, IGRAPH_UNDIRECTED, 0, 1); + + igraph_decompose(&ring, &complist, IGRAPH_WEAK, -1, 0); + component = igraph_graph_list_get_ptr(&complist, 0); + igraph_write_graph_edgelist(component, stdout); + igraph_destroy(&ring); + igraph_graph_list_clear(&complist); + + /* Random graph with a giant component */ + igraph_erdos_renyi_game_gnp(&g, 100, 4.0 / 100, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_decompose(&g, &complist, IGRAPH_WEAK, -1, 20); + if (igraph_graph_list_size(&complist) != 1) { + return 1; + } + igraph_destroy(&g); + igraph_graph_list_clear(&complist); + + /* A toy graph, three components maximum, with at least 2 vertices each */ + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 0, + 3, 4, 4, 5, 5, 6, + 8, 9, 9, 10, + -1); + igraph_decompose(&g, &complist, IGRAPH_WEAK, 3, 2); + for (i = 0; i < igraph_graph_list_size(&complist); i++) { + component = igraph_graph_list_get_ptr(&complist, i); + igraph_write_graph_edgelist(component, stdout); + } + igraph_destroy(&g); + + igraph_graph_list_destroy(&complist); + + return 0; +} diff --git a/examples/simple/igraph_decompose.out b/examples/simple/igraph_decompose.out new file mode 100644 index 0000000..370b3f7 --- /dev/null +++ b/examples/simple/igraph_decompose.out @@ -0,0 +1,18 @@ +0 1 +0 9 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +0 1 +1 2 +2 0 +0 1 +1 2 +2 3 +0 1 +1 2 diff --git a/examples/simple/igraph_degree.c b/examples/simple/igraph_degree.c new file mode 100644 index 0000000..27760d3 --- /dev/null +++ b/examples/simple/igraph_degree.c @@ -0,0 +1,153 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +igraph_bool_t handshaking_lemma(igraph_t *g, igraph_vector_int_t *v) { + /* Consistency check of the handshaking lemma: + * If d is the sum of all vertex degrees, then d = 2|E|. */ + return igraph_vector_int_sum(v) == 2 * igraph_ecount(g); +} + +int main(void) { + igraph_t g; + igraph_vector_int_t v; + igraph_vector_int_t seq; + igraph_int_t mdeg; + + /* Initialize the library. */ + igraph_setup(); + + /* Create graph */ + igraph_vector_int_init(&v, 8); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 2,2, -1); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + if (!handshaking_lemma(&g, &v)) { + exit(3); + } + + igraph_destroy(&g); + + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 2,2, -1); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_vector_int_print(&v); + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + if (!handshaking_lemma(&g, &v)) { + exit(4); + } + + /* Degree of the same vertex multiple times */ + + igraph_vector_int_init(&seq, 3); + VECTOR(seq)[0] = 2; + VECTOR(seq)[1] = 0; + VECTOR(seq)[2] = 2; + igraph_degree(&g, &v, igraph_vss_vector(&seq), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(&v); + + igraph_destroy(&g); + igraph_vector_int_destroy(&seq); + + /* Maximum degree */ + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, /* mutual */ false, /* circular */ false); + igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (mdeg != 2) { + exit(5); + } + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (! handshaking_lemma(&g, &v)) { + exit(6); + } + igraph_destroy(&g); + + igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (mdeg != 9) { + exit(7); + } + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (! handshaking_lemma(&g, &v)) { + exit(8); + } + igraph_destroy(&g); + + igraph_star(&g, 10, IGRAPH_STAR_OUT, /* center */ 0); + igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + if (mdeg != 9) { + exit(9); + } + igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + if (mdeg != 1) { + exit(10); + } + igraph_maxdegree(&g, &mdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (mdeg != 9) { + exit(11); + } + + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (! handshaking_lemma(&g, &v)) { + exit(12); + } + igraph_destroy(&g); + + igraph_vector_int_destroy(&v); + + return 0; +} diff --git a/examples/simple/igraph_degree.out b/examples/simple/igraph_degree.out new file mode 100644 index 0000000..2073f83 --- /dev/null +++ b/examples/simple/igraph_degree.out @@ -0,0 +1,13 @@ +1 1 1 0 +1 1 2 0 +0 1 1 1 +0 1 2 1 +1 2 2 1 +1 2 4 1 +1 2 2 1 +1 2 4 1 +1 2 2 1 +1 2 4 1 +1 2 2 1 +1 2 4 1 +4 1 4 diff --git a/examples/simple/igraph_degree_sequence_game.c b/examples/simple/igraph_degree_sequence_game.c new file mode 100644 index 0000000..e1a4e77 --- /dev/null +++ b/examples/simple/igraph_degree_sequence_game.c @@ -0,0 +1,114 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_t outdeg, indeg; + igraph_vector_int_t vec; + igraph_bool_t is_simple; + + /* Initialize the library. */ + igraph_setup(); + + /* Set random seed for reproducibility */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init_int(&outdeg, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3); + igraph_vector_int_init_int(&indeg, 10, 4, 4, 2, 2, 4, 4, 2, 2, 3, 3); + igraph_vector_int_init(&vec, 0); + + /* checking the configuration model, undirected graphs */ + igraph_degree_sequence_game(&g, &outdeg, 0, IGRAPH_DEGSEQ_CONFIGURATION); + if (igraph_is_directed(&g) || igraph_vcount(&g) != 10) { + return 1; + } + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)) { + return 2; + } + igraph_vector_int_print(&vec); + igraph_destroy(&g); + + /* checking the Viger-Latapy method, undirected graphs */ + igraph_degree_sequence_game(&g, &outdeg, 0, IGRAPH_DEGSEQ_VL); + if (igraph_is_directed(&g) || igraph_vcount(&g) != 10) { + return 3; + } + if (igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED) || !is_simple) { + return 4; + } + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS)) { + return 5; + } + igraph_vector_int_print(&vec); + igraph_destroy(&g); + + /* checking the configuration model, directed graphs */ + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_CONFIGURATION); + if (!igraph_is_directed(&g) || igraph_vcount(&g) != 10) { + return 6; + } + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)) { + return 7; + } + igraph_vector_int_print(&vec); + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)) { + return 8; + } + igraph_vector_int_print(&vec); + igraph_destroy(&g); + + /* checking the fast heuristic method, undirected graphs */ + igraph_degree_sequence_game(&g, &outdeg, 0, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + if (igraph_is_directed(&g) || igraph_vcount(&g) != 10) { + return 9; + } + if (igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED) || !is_simple) { + return 10; + } + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)) { + return 11; + } + igraph_vector_int_print(&vec); + igraph_destroy(&g); + + /* checking the fast heuristic method, directed graphs */ + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + if (!igraph_is_directed(&g) || igraph_vcount(&g) != 10) { + return 12; + } + if (igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED) || !is_simple) { + return 13; + } + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)) { + return 14; + } + igraph_vector_int_print(&vec); + if (igraph_degree(&g, &vec, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)) { + return 15; + } + igraph_vector_int_print(&vec); + igraph_destroy(&g); + + igraph_vector_int_destroy(&vec); + igraph_vector_int_destroy(&outdeg); + igraph_vector_int_destroy(&indeg); + + return 0; +} diff --git a/examples/simple/igraph_degree_sequence_game.out b/examples/simple/igraph_degree_sequence_game.out new file mode 100644 index 0000000..d2285c1 --- /dev/null +++ b/examples/simple/igraph_degree_sequence_game.out @@ -0,0 +1,7 @@ +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +4 4 2 2 4 4 2 2 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +4 4 2 2 4 4 2 2 3 3 diff --git a/examples/simple/igraph_delete_edges.c b/examples/simple/igraph_delete_edges.c new file mode 100644 index 0000000..1de63bb --- /dev/null +++ b/examples/simple/igraph_delete_edges.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_es_t es; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,3, -1); + + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, 3, 2, -1); + igraph_delete_edges(&g, es); + if (igraph_ecount(&g) != 3) { + return 1; + } + + igraph_es_destroy(&es); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_delete_vertices.c b/examples/simple/igraph_delete_vertices.c new file mode 100644 index 0000000..2946bd3 --- /dev/null +++ b/examples/simple/igraph_delete_vertices.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + + /* without edges */ + igraph_small(&g, 15, IGRAPH_UNDIRECTED, -1); + + igraph_delete_vertices(&g, igraph_vss_1(2)); + if (igraph_vcount(&g) != 14) { + return 2; + } + igraph_destroy(&g); + + /* with edges */ + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 2,2, -1); + igraph_delete_vertices(&g, igraph_vss_1(2)); + if (igraph_vcount(&g) != 3) { + return 3; + } + if (igraph_ecount(&g) != 1) { + return 4; + } + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_diameter.c b/examples/simple/igraph_diameter.c new file mode 100644 index 0000000..3b98534 --- /dev/null +++ b/examples/simple/igraph_diameter.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + + igraph_t graph; + igraph_real_t result; + igraph_int_t from, to; + igraph_vector_int_t path, path_edge; + + /* Initialize the library. */ + igraph_setup(); + + igraph_barabasi_game(&graph, 30, /*power=*/ 1, 30, 0, 0, /*A=*/ 1, + IGRAPH_DIRECTED, IGRAPH_BARABASI_BAG, + /*start_from=*/ NULL); + igraph_diameter(&graph, NULL, &result, NULL, NULL, NULL, NULL, IGRAPH_UNDIRECTED, 1); + + /* printf("diameter: %g\n", result); */ + + igraph_destroy(&graph); + + igraph_ring(&graph, 10, IGRAPH_DIRECTED, 0, 0); + igraph_vector_int_init(&path, 0); + igraph_vector_int_init(&path_edge, 0); + igraph_diameter(&graph, NULL, &result, &from, &to, &path, &path_edge, IGRAPH_DIRECTED, 1); + printf( + "diameter: %g, from %" IGRAPH_PRId " to %" IGRAPH_PRId "\n", + result, from, to + ); + igraph_vector_int_print(&path); + igraph_vector_int_print(&path_edge); + + igraph_vector_int_destroy(&path); + igraph_vector_int_destroy(&path_edge); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_diameter.out b/examples/simple/igraph_diameter.out new file mode 100644 index 0000000..5ca46cb --- /dev/null +++ b/examples/simple/igraph_diameter.out @@ -0,0 +1,3 @@ +diameter: 9, from 0 to 9 +0 1 2 3 4 5 6 7 8 9 +0 1 2 3 4 5 6 7 8 diff --git a/examples/simple/igraph_difference.c b/examples/simple/igraph_difference.c new file mode 100644 index 0000000..c9077b6 --- /dev/null +++ b/examples/simple/igraph_difference.c @@ -0,0 +1,139 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t orig, sub, diff; + igraph_vector_int_t v; + + /* Initialize the library. */ + igraph_setup(); + + /* Subtract from itself */ + printf("subtract itself\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 1, 4, 5, -1); + igraph_create(&orig, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_difference(&diff, &orig, &orig); + igraph_write_graph_edgelist(&diff, stdout); + if (igraph_ecount(&diff) != 0 || + igraph_vcount(&diff) != igraph_vcount(&orig)) { + return 1; + } + + igraph_destroy(&orig); + igraph_destroy(&diff); + + /* Same for undirected graph */ + printf("subtract itself, undirected\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 1, 4, 5, -1); + igraph_create(&orig, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 1, 0, 1, 2, 2, 1, 4, 5, -1); + igraph_create(&sub, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_difference(&diff, &orig, &sub); + igraph_write_graph_edgelist(&diff, stdout); + if (igraph_ecount(&diff) != 0 || + igraph_vcount(&diff) != igraph_vcount(&orig)) { + return 2; + } + + igraph_destroy(&orig); + igraph_destroy(&sub); + igraph_destroy(&diff); + + /* Subtract the empty graph */ + printf("subtract empty\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 1, 4, 5, -1); + igraph_create(&orig, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_empty(&sub, 3, IGRAPH_DIRECTED); + igraph_difference(&diff, &orig, &sub); + igraph_write_graph_edgelist(&diff, stdout); + if (igraph_ecount(&diff) != igraph_ecount(&orig) || + igraph_vcount(&diff) != igraph_vcount(&orig)) { + return 3; + } + + igraph_destroy(&orig); + igraph_destroy(&sub); + igraph_destroy(&diff); + + /* A `real' example */ + printf("real example\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 1, 4, 5, 8, 9, -1); + igraph_create(&orig, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 5, 4, 2, 1, 6, 7, -1); + igraph_create(&sub, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_difference(&diff, &orig, &sub); + igraph_write_graph_edgelist(&diff, stdout); + + igraph_destroy(&diff); + igraph_destroy(&orig); + igraph_destroy(&sub); + + /* undirected version */ + printf("real example, undirected\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 1, 4, 5, 8, 9, 8, 10, 8, 13, 8, 11, 8, 12, -1); + igraph_create(&orig, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 5, 4, 2, 1, 6, 7, 8, 10, 8, 13, -1); + igraph_create(&sub, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_difference(&diff, &orig, &sub); + igraph_write_graph_edgelist(&diff, stdout); + + igraph_destroy(&diff); + igraph_destroy(&orig); + igraph_destroy(&sub); + + /* undirected version with loop edge, tests Github issue #597 */ + printf("Github issue #597, undirected\n"); + igraph_vector_int_init_int_end(&v, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, -1); + igraph_create(&orig, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, -1); + igraph_create(&sub, &v, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&v); + + igraph_difference(&diff, &orig, &sub); + igraph_write_graph_edgelist(&diff, stdout); + + igraph_destroy(&diff); + igraph_destroy(&orig); + igraph_destroy(&sub); + + return 0; +} diff --git a/examples/simple/igraph_difference.out b/examples/simple/igraph_difference.out new file mode 100644 index 0000000..6bc55e8 --- /dev/null +++ b/examples/simple/igraph_difference.out @@ -0,0 +1,24 @@ +subtract itself +subtract itself, undirected +subtract empty +0 1 +1 2 +2 1 +4 5 +real example +1 2 +4 5 +8 9 +real example, undirected +1 2 +8 9 +8 11 +8 12 +Github issue #597, undirected +0 0 +0 9 +4 5 +5 6 +6 7 +7 8 +8 9 diff --git a/examples/simple/igraph_disjoint_union.c b/examples/simple/igraph_disjoint_union.c new file mode 100644 index 0000000..2856c6e --- /dev/null +++ b/examples/simple/igraph_disjoint_union.c @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t left, right, uni; + igraph_vector_ptr_t glist; + igraph_int_t i, n; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&left, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,3, -1); + igraph_small(&right, 5, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,4, -1); + + igraph_disjoint_union(&uni, &left, &right); + igraph_write_graph_edgelist(&uni, stdout); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&uni); + + /* Empty graph list; the result is the directed null graph. */ + igraph_vector_ptr_init(&glist, 0); + igraph_disjoint_union_many(&uni, &glist); + if (!igraph_is_directed(&uni) || igraph_vcount(&uni) != 0) { + return 1; + } + igraph_vector_ptr_destroy(&glist); + igraph_destroy(&uni); + + /* Non-empty graph list. */ + igraph_vector_ptr_init(&glist, 10); + n = igraph_vector_ptr_size(&glist); + for (i = 0; i < n; i++) { + VECTOR(glist)[i] = calloc(1, sizeof(igraph_t)); + igraph_small(VECTOR(glist)[i], 2, IGRAPH_DIRECTED, 0,1, 1,0, -1); + } + if (!igraph_is_directed(&uni)) { + return 2; + } + + igraph_disjoint_union_many(&uni, &glist); + igraph_write_graph_edgelist(&uni, stdout); + printf("\n"); + + /* Destroy and free the graph list. */ + n = igraph_vector_ptr_size(&glist); + for (i = 0; i < n; i++) { + igraph_destroy(VECTOR(glist)[i]); + free(VECTOR(glist)[i]); + } + igraph_vector_ptr_destroy(&glist); + igraph_destroy(&uni); + + return 0; +} diff --git a/examples/simple/igraph_disjoint_union.out b/examples/simple/igraph_disjoint_union.out new file mode 100644 index 0000000..2c5fb4b --- /dev/null +++ b/examples/simple/igraph_disjoint_union.out @@ -0,0 +1,30 @@ +0 1 +1 2 +2 2 +2 3 +4 5 +5 6 +6 6 +6 8 + +0 1 +1 0 +2 3 +3 2 +4 5 +5 4 +6 7 +7 6 +8 9 +9 8 +10 11 +11 10 +12 13 +13 12 +14 15 +15 14 +16 17 +17 16 +18 19 +19 18 + diff --git a/examples/simple/igraph_eccentricity.c b/examples/simple/igraph_eccentricity.c new file mode 100644 index 0000000..46c1019 --- /dev/null +++ b/examples/simple/igraph_eccentricity.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vector_t ecc; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_init(&ecc, 0); + + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + igraph_vector_print(&ecc); + igraph_destroy(&g); + + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_ALL); + igraph_vector_print(&ecc); + igraph_destroy(&g); + + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + igraph_vector_print(&ecc); + igraph_destroy(&g); + + igraph_vector_destroy(&ecc); + + return 0; +} diff --git a/examples/simple/igraph_eccentricity.out b/examples/simple/igraph_eccentricity.out new file mode 100644 index 0000000..a92f5e0 --- /dev/null +++ b/examples/simple/igraph_eccentricity.out @@ -0,0 +1,3 @@ +1 2 2 2 2 2 2 2 2 2 +1 2 2 2 2 2 2 2 2 2 +1 0 0 0 0 0 0 0 0 0 diff --git a/examples/simple/igraph_erdos_renyi_game_gnm.c b/examples/simple/igraph_erdos_renyi_game_gnm.c new file mode 100644 index 0000000..d13e9b4 --- /dev/null +++ b/examples/simple/igraph_erdos_renyi_game_gnm.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t component_sizes; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); /* make program deterministic */ + + /* Sample a graph from the Erdős-Rényi G(n,m) model */ + + igraph_erdos_renyi_game_gnm( + &graph, /* n= */ 100, /* m= */ 100, + IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* Compute the fraction of vertices contained within the largest connected component */ + + igraph_vector_int_init(&component_sizes, 0); + igraph_connected_components(&graph, NULL, &component_sizes, NULL, IGRAPH_STRONG); + + printf( + "Fraction of vertices in giant component: %g\n", + ((double) igraph_vector_int_max(&component_sizes)) / igraph_vcount(&graph) + ); + + /* Clean up data structures when no longer needed */ + + igraph_vector_int_destroy(&component_sizes); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_erdos_renyi_game_gnp.c b/examples/simple/igraph_erdos_renyi_game_gnp.c new file mode 100644 index 0000000..b99566b --- /dev/null +++ b/examples/simple/igraph_erdos_renyi_game_gnp.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t component_sizes; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); /* make program deterministic */ + + /* Sample a graph from the Erdős-Rényi G(n,p) model */ + + igraph_erdos_renyi_game_gnp( + &graph, /* n= */ 100, /* p= */ 0.01, + IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* Compute the fraction of vertices contained within the largest connected component */ + + igraph_vector_int_init(&component_sizes, 0); + igraph_connected_components(&graph, NULL, &component_sizes, NULL, IGRAPH_STRONG); + + printf( + "Fraction of vertices in giant component: %g\n", + ((double) igraph_vector_int_max(&component_sizes)) / igraph_vcount(&graph) + ); + + /* Clean up data structures when no longer needed */ + + igraph_vector_int_destroy(&component_sizes); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_es_pairs.c b/examples/simple/igraph_es_pairs.c new file mode 100644 index 0000000..b92bd58 --- /dev/null +++ b/examples/simple/igraph_es_pairs.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_int_t i; + igraph_int_t size; + + /* Initialize the library. */ + igraph_setup(); + + /* DIRECTED */ + + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + + for (i = 0; i < 100; i++) { + igraph_es_t es; + igraph_eit_t it; + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 5, 0, 2, 0, 3, 0, 4, 0, 7, 0, 9, -1); + igraph_eit_create(&g, es, &it); + igraph_es_size(&g, &es, &size); + IGRAPH_EIT_RESET(it); + while (!IGRAPH_EIT_END(it)) { + (void) IGRAPH_EIT_GET(it); + IGRAPH_EIT_NEXT(it); + size--; + } + if (size != 0) { + return 1; + } + igraph_eit_destroy(&it); + igraph_es_destroy(&es); + } + + igraph_destroy(&g); + + /* UNDIRECTED */ + + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + + for (i = 0; i < 100; i++) { + igraph_es_t es; + igraph_eit_t it; + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, + 0, 1, 2, 0, 5, 0, 0, 2, 3, 0, 0, 4, 7, 0, 0, 9, -1); + igraph_eit_create(&g, es, &it); + IGRAPH_EIT_RESET(it); + while (!IGRAPH_EIT_END(it)) { + (void) IGRAPH_EIT_GET(it); + IGRAPH_EIT_NEXT(it); + } + igraph_eit_destroy(&it); + igraph_es_destroy(&es); + } + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_feedback_arc_set.c b/examples/simple/igraph_feedback_arc_set.c new file mode 100644 index 0000000..e55428c --- /dev/null +++ b/examples/simple/igraph_feedback_arc_set.c @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_vector_int_t result; + igraph_bool_t dag; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&result, 0); + + /***********************************************************************/ + /* Approximation with Eades' method */ + /***********************************************************************/ + + /* Simple unweighted graph */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, -1); + igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_APPROX_EADES); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 1; + } + igraph_destroy(&g); + + /* Simple weighted graph */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, -1); + igraph_vector_init_int_end(&weights, -1, 1, 1, 3, 1, 1, 1, 1, 1, 1, -1); + igraph_feedback_arc_set(&g, &result, &weights, IGRAPH_FAS_APPROX_EADES); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 2; + } + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* Simple unweighted graph with loops */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, 1, 1, 4, 4, -1); + igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_APPROX_EADES); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 3; + } + igraph_destroy(&g); + + /* Null graph */ + igraph_empty(&g, 0, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_APPROX_EADES); + if (igraph_vector_int_size(&result) != 0) { + return 4; + } + igraph_destroy(&g); + + /* Singleton graph */ + igraph_empty(&g, 1, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_APPROX_EADES); + if (igraph_vector_int_size(&result) != 0) { + return 5; + } + igraph_destroy(&g); + + igraph_vector_int_destroy(&result); + + return 0; +} diff --git a/examples/simple/igraph_feedback_arc_set.out b/examples/simple/igraph_feedback_arc_set.out new file mode 100644 index 0000000..aeb5a03 --- /dev/null +++ b/examples/simple/igraph_feedback_arc_set.out @@ -0,0 +1,3 @@ +2 +1 +2 9 10 diff --git a/examples/simple/igraph_feedback_arc_set_ip.c b/examples/simple/igraph_feedback_arc_set_ip.c new file mode 100644 index 0000000..d7e0def --- /dev/null +++ b/examples/simple/igraph_feedback_arc_set_ip.c @@ -0,0 +1,127 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_setup(); + igraph_t g; + igraph_vector_t weights; + igraph_vector_int_t result; + igraph_bool_t dag; + igraph_error_t retval; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&result, 0); + + igraph_set_error_handler(&igraph_error_handler_printignore); + + /***********************************************************************/ + /* Exact solution with integer programming */ + /***********************************************************************/ + + /* Simple unweighted graph */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, -1); + retval = igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_EXACT_IP); + if (retval == IGRAPH_UNIMPLEMENTED) { + return 77; + } + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 1; + } + igraph_destroy(&g); + + /* Simple weighted graph */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, -1); + igraph_vector_init_int_end(&weights, -1, 1, 1, 3, 1, 1, 1, 1, 1, 1, -1); + igraph_feedback_arc_set(&g, &result, &weights, IGRAPH_FAS_EXACT_IP); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 2; + } + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* Simple unweighted graph with loops */ + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, 1, 1, 4, 4, -1); + igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_EXACT_IP); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 3; + } + igraph_destroy(&g); + + /* Disjoint union of two almost identical graphs */ + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 0, 2, 3, 2, 4, 0, 4, 4, 3, 5, 0, 6, 5, 1, 1, 4, 4, + 7, 8, 8, 9, 9, 7, 9, 10, 9, 11, 7, 11, 11, 10, 12, 7, 13, 12, + -1); + igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_EXACT_IP); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 4; + } + igraph_destroy(&g); + + /* Graph with lots of isolated vertices */ + igraph_small(&g, 10000, IGRAPH_DIRECTED, 0, 1, -1); + igraph_feedback_arc_set(&g, &result, 0, IGRAPH_FAS_EXACT_IP); + igraph_vector_int_print(&result); + igraph_delete_edges(&g, igraph_ess_vector(&result)); + igraph_is_dag(&g, &dag); + if (!dag) { + return 5; + } + igraph_destroy(&g); + + /* Null graph */ + igraph_empty(&g, 0, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_EXACT_IP); + if (igraph_vector_int_size(&result) != 0) { + return 6; + } + igraph_destroy(&g); + + /* Singleton graph */ + igraph_empty(&g, 1, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&g, &result, NULL, IGRAPH_FAS_EXACT_IP); + if (igraph_vector_int_size(&result) != 0) { + return 7; + } + igraph_destroy(&g); + + igraph_vector_int_destroy(&result); + + return 0; +} diff --git a/examples/simple/igraph_feedback_arc_set_ip.out b/examples/simple/igraph_feedback_arc_set_ip.out new file mode 100644 index 0000000..aa36d49 --- /dev/null +++ b/examples/simple/igraph_feedback_arc_set_ip.out @@ -0,0 +1,5 @@ +0 +0 +0 9 10 +0 9 10 11 + diff --git a/examples/simple/igraph_fisher_yates_shuffle.c b/examples/simple/igraph_fisher_yates_shuffle.c new file mode 100644 index 0000000..b40f44e --- /dev/null +++ b/examples/simple/igraph_fisher_yates_shuffle.c @@ -0,0 +1,105 @@ +/* + Test suite for the Fisher-Yates shuffle. + Copyright (C) 2011 Minh Van Nguyen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include + +int main(void) { + igraph_real_t d; + igraph_vector_t u, v; + igraph_int_t i, k, n; + + /* Initialize the library. */ + igraph_setup(); + + /******************************** + * Example usage + ********************************/ + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + /* Sequences with one element. Such sequences are trivially permuted. + * The result of any Fisher-Yates shuffle on a sequence with one element + * must be the original sequence itself. + */ + n = 1; + igraph_vector_init(&v, n); + + k = RNG_INTEGER(-1000, 1000); + VECTOR(v)[0] = k; + igraph_vector_shuffle(&v); + if (VECTOR(v)[0] != k) { + return 1; + } + d = RNG_UNIF(-1000.0, 1000.0); + + VECTOR(v)[0] = d; + igraph_vector_shuffle(&v); + if (VECTOR(v)[0] != d) { + return 2; + } + igraph_vector_destroy(&v); + + /* Sequences with multiple elements. A Fisher-Yates shuffle of a sequence S + * is a random permutation \pi(S) of S. Thus \pi(S) must have the same + * length and elements as the original sequence S. A major difference between + * S and its random permutation \pi(S) is that the order in which elements + * appear in \pi(S) is probably different from how elements are ordered in S. + * If S has length n = 1, then both \pi(S) and S are equivalent sequences in + * that \pi(S) is merely S and no permutation has taken place. If S has + * length n > 1, then there are n! possible permutations of S. Assume that + * each such permutation is equally likely to appear as a result of the + * Fisher-Yates shuffle. As n increases, the probability that S is different + * from \pi(S) also increases. We have a probability of 1 / n! that S and + * \pi(S) are equivalent sequences. + */ + n = 100; + igraph_vector_init(&u, n); + igraph_vector_init(&v, n); + + for (i = 0; i < n; i++) { + k = RNG_INTEGER(-1000, 1000); + VECTOR(u)[i] = k; + VECTOR(v)[i] = k; + } + + igraph_vector_shuffle(&v); + /* must have same length */ + if (igraph_vector_size(&v) != n) { + return 3; + } + if (igraph_vector_size(&u) != igraph_vector_size(&v)) { + return 4; + } + /* must have same elements */ + igraph_vector_sort(&u); + igraph_vector_sort(&v); + if (!igraph_vector_all_e(&u, &v)) { + return 5; + } + igraph_vector_destroy(&u); + igraph_vector_destroy(&v); + + /* empty sequence */ + igraph_vector_init(&v, 0); + igraph_vector_shuffle(&v); + igraph_vector_destroy(&v); + + return 0; +} diff --git a/examples/simple/igraph_full.c b/examples/simple/igraph_full.c new file mode 100644 index 0000000..4d22518 --- /dev/null +++ b/examples/simple/igraph_full.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_int_t n_vertices = 10; + + /* Initialize the library. */ + igraph_setup(); + + /* Create an undirected complete graph. */ + /* Use IGRAPH_UNDIRECTED and IGRAPH_NO_LOOPS instead of true and false for better readability. */ + igraph_full(&graph, n_vertices, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + printf("The undirected complete graph on %" IGRAPH_PRId " vertices has %" IGRAPH_PRId " edges.\n", + igraph_vcount(&graph), igraph_ecount(&graph)); + + /* Remember to destroy the object at the end. */ + igraph_destroy(&graph); + + /* Create a directed complete graph. */ + igraph_full(&graph, n_vertices, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + printf("The directed complete graph on %" IGRAPH_PRId " vertices has %" IGRAPH_PRId " edges.\n", + igraph_vcount(&graph), igraph_ecount(&graph)); + + igraph_destroy(&graph); + + /* Create an undirected complete graph with self-loops. */ + igraph_full(&graph, n_vertices, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + printf("The undirected complete graph on %" IGRAPH_PRId " vertices with self-loops has %" IGRAPH_PRId " edges.\n", + igraph_vcount(&graph), igraph_ecount(&graph)); + + igraph_destroy(&graph); + + /* Create a directed graph with self-loops. */ + igraph_full(&graph, n_vertices, IGRAPH_DIRECTED, IGRAPH_LOOPS); + printf("The directed complete graph on %" IGRAPH_PRId " vertices with self-loops has %" IGRAPH_PRId " edges.\n", + igraph_vcount(&graph), igraph_ecount(&graph)); + + igraph_destroy(&graph); + + return 0; + +} diff --git a/examples/simple/igraph_full.out b/examples/simple/igraph_full.out new file mode 100644 index 0000000..aa00c75 --- /dev/null +++ b/examples/simple/igraph_full.out @@ -0,0 +1,4 @@ +The undirected complete graph on 10 vertices has 45 edges. +The directed complete graph on 10 vertices has 90 edges. +The undirected complete graph on 10 vertices with self-loops has 55 edges. +The directed complete graph on 10 vertices with self-loops has 100 edges. diff --git a/examples/simple/igraph_get_all_shortest_paths_dijkstra.c b/examples/simple/igraph_get_all_shortest_paths_dijkstra.c new file mode 100644 index 0000000..fe6b422 --- /dev/null +++ b/examples/simple/igraph_get_all_shortest_paths_dijkstra.c @@ -0,0 +1,83 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +void print_and_destroy_items(igraph_vector_int_list_t* vec) { + igraph_int_t i; + + /* Sort the paths in a deterministic manner to avoid problems with + * different qsort() implementations on different platforms */ + igraph_vector_int_list_sort(vec, igraph_vector_int_colex_cmp); + for (i = 0; i < igraph_vector_int_list_size(vec); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(vec, i)); + } + + igraph_vector_int_list_destroy(vec); +} + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t vertices, edges; + + igraph_real_t weights[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + const igraph_vector_t weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + igraph_vector_int_t nrgeo; + igraph_vs_t vs; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_list_init(&vertices, 0); + igraph_vector_int_list_init(&edges, 0); + igraph_vs_vector_small(&vs, 1, 3, 4, 5, 2, 1, -1); + igraph_vector_int_init(&nrgeo, 0); + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ &vertices, /*edges=*/ &edges, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ &weights_vec, /*mode=*/ IGRAPH_OUT); + + printf("Vertices:\n"); + print_and_destroy_items(&vertices); + printf("\nEdges:\n"); + print_and_destroy_items(&edges); + printf("\nNumber of geodesics:\n"); + igraph_vector_int_print(&nrgeo); + + igraph_vector_int_destroy(&nrgeo); + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_get_all_shortest_paths_dijkstra.out b/examples/simple/igraph_get_all_shortest_paths_dijkstra.out new file mode 100644 index 0000000..0237c78 --- /dev/null +++ b/examples/simple/igraph_get_all_shortest_paths_dijkstra.out @@ -0,0 +1,18 @@ +Vertices: +0 1 +0 1 2 +0 3 +0 1 2 3 +0 1 4 +0 1 5 + +Edges: +0 +2 +0 3 +0 4 +0 5 +0 3 6 + +Number of geodesics: +1 1 1 2 1 1 1 0 1 1 diff --git a/examples/simple/igraph_get_eid.c b/examples/simple/igraph_get_eid.c new file mode 100644 index 0000000..7a2966d --- /dev/null +++ b/examples/simple/igraph_get_eid.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_int_t eid; + igraph_vector_int_t hist; + + /* Initialize the library. */ + igraph_setup(); + + /* DIRECTED */ + + igraph_star(&graph, 10, IGRAPH_STAR_OUT, 0); + + igraph_vector_int_init(&hist, 9); + + for (igraph_int_t i = 1; i < 10; i++) { + igraph_get_eid(&graph, &eid, 0, i, IGRAPH_DIRECTED, /*error=*/ true); + VECTOR(hist)[eid] = 1; + } + + igraph_vector_int_print(&hist); + + igraph_vector_int_destroy(&hist); + igraph_destroy(&graph); + + /* UNDIRECTED */ + + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + + igraph_vector_int_init(&hist, 9); + + for (igraph_int_t i = 1; i < 10; i++) { + igraph_get_eid(&graph, &eid, 0, i, IGRAPH_UNDIRECTED, /*error=*/ true); + VECTOR(hist)[eid] += 1; + igraph_get_eid(&graph, &eid, i, 0, IGRAPH_DIRECTED, /*error=*/ true); + VECTOR(hist)[eid] += 1; + } + igraph_vector_int_print(&hist); + + igraph_vector_int_destroy(&hist); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_get_eid.out b/examples/simple/igraph_get_eid.out new file mode 100644 index 0000000..8d4cb57 --- /dev/null +++ b/examples/simple/igraph_get_eid.out @@ -0,0 +1,2 @@ +1 1 1 1 1 1 1 1 1 +2 2 2 2 2 2 2 2 2 diff --git a/examples/simple/igraph_get_eids.c b/examples/simple/igraph_get_eids.c new file mode 100644 index 0000000..178a297 --- /dev/null +++ b/examples/simple/igraph_get_eids.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2008-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t g; + igraph_int_t nodes = 100; + igraph_int_t edges = 1000; + igraph_real_t p = 3.0 / nodes; + igraph_int_t runs = 10; + igraph_int_t r, e, ecount; + igraph_vector_int_t eids, pairs, path; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + igraph_vector_int_init(&pairs, edges * 2); + igraph_vector_int_init(&path, 0); + igraph_vector_int_init(&eids, 0); + + for (r = 0; r < runs; r++) { + igraph_vector_int_resize(&pairs, edges * 2); + igraph_vector_int_clear(&path); + igraph_vector_int_clear(&eids); + + igraph_erdos_renyi_game_gnp(&g, nodes, p, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + ecount = igraph_ecount(&g); + for (e = 0; e < edges; e++) { + igraph_int_t edge = RNG_INTEGER(0, ecount - 1); + VECTOR(pairs)[2 * e] = IGRAPH_FROM(&g, edge); + VECTOR(pairs)[2 * e + 1] = IGRAPH_TO(&g, edge); + } + igraph_get_eids(&g, &eids, &pairs, IGRAPH_UNDIRECTED, /*error=*/ true); + for (e = 0; e < edges; e++) { + igraph_int_t edge = VECTOR(eids)[e]; + igraph_int_t from1 = VECTOR(pairs)[2 * e]; + igraph_int_t to1 = VECTOR(pairs)[2 * e + 1]; + igraph_int_t from2 = IGRAPH_FROM(&g, edge); + igraph_int_t to2 = IGRAPH_TO(&g, edge); + igraph_int_t min1 = from1 < to1 ? from1 : to1; + igraph_int_t max1 = from1 < to1 ? to1 : from1; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + if (min1 != min2 || max1 != max2) { + return 11; + } + } + + igraph_diameter(&g, /*weights=*/ NULL, /*res=*/ NULL, /*from=*/ NULL, /*to=*/ NULL, &path, NULL, + IGRAPH_UNDIRECTED, /*unconn=*/ true); + igraph_vector_int_update(&pairs, &path); + igraph_expand_path_to_pairs(&pairs); + igraph_get_eids(&g, &eids, &pairs, /* directed= */ false, /*error=*/ true); + for (e = 0; e < igraph_vector_int_size(&path) - 1; e++) { + igraph_int_t edge = VECTOR(eids)[e]; + igraph_int_t from1 = VECTOR(path)[e]; + igraph_int_t to1 = VECTOR(path)[e + 1]; + igraph_int_t from2 = IGRAPH_FROM(&g, edge); + igraph_int_t to2 = IGRAPH_TO(&g, edge); + igraph_int_t min1 = from1 < to1 ? from1 : to1; + igraph_int_t max1 = from1 < to1 ? to1 : from1; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + if (min1 != min2 || max1 != max2) { + return 12; + } + } + + igraph_destroy(&g); + } + + igraph_vector_int_destroy(&path); + igraph_vector_int_destroy(&pairs); + igraph_vector_int_destroy(&eids); + + return 0; +} diff --git a/examples/simple/igraph_get_laplacian.c b/examples/simple/igraph_get_laplacian.c new file mode 100644 index 0000000..694fd26 --- /dev/null +++ b/examples/simple/igraph_get_laplacian.c @@ -0,0 +1,41 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + igraph_matrix_t L; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&L, 1, 1); + igraph_vector_init_int(&weights, 5, 1, 2, 3, 4, 5); + + igraph_ring(&graph, 5, IGRAPH_DIRECTED, 0, 1); + igraph_get_laplacian(&graph, &L, IGRAPH_OUT, IGRAPH_LAPLACIAN_SYMMETRIC, &weights); + igraph_matrix_print(&L); + + igraph_vector_destroy(&weights); + igraph_matrix_destroy(&L); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_get_laplacian.out b/examples/simple/igraph_get_laplacian.out new file mode 100644 index 0000000..6288c7d --- /dev/null +++ b/examples/simple/igraph_get_laplacian.out @@ -0,0 +1,5 @@ + 1 -0.707107 0 0 0 + 0 1 -0.816497 0 0 + 0 0 1 -0.866025 0 + 0 0 0 1 -0.894427 +-2.23607 0 0 0 1 diff --git a/examples/simple/igraph_get_laplacian_sparse.c b/examples/simple/igraph_get_laplacian_sparse.c new file mode 100644 index 0000000..d00131e --- /dev/null +++ b/examples/simple/igraph_get_laplacian_sparse.c @@ -0,0 +1,133 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int test_laplacian(const igraph_vector_t *w, igraph_bool_t dir, igraph_laplacian_normalization_t normalization) { + igraph_t g; + igraph_matrix_t m; + igraph_sparsemat_t sm; + igraph_vector_int_t vec; + igraph_vector_t *weights = 0; + igraph_neimode_t mode = IGRAPH_OUT; + + igraph_sparsemat_init(&sm, 0, 0, 0); + + if (w) { + weights = (igraph_vector_t*) calloc(1, sizeof(igraph_vector_t)); + igraph_vector_init_copy(weights, w); + } + + /* Base graph, no loop or multiple edges */ + igraph_ring(&g, 5, dir, 0, 1); + igraph_get_laplacian_sparse(&g, &sm, mode, normalization, weights); + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_as_matrix(&m, &sm); + igraph_matrix_print(&m); + igraph_matrix_destroy(&m); + printf("===\n"); + + /* Add some loop edges */ + igraph_vector_int_init_int(&vec, 4, 1, 1, 2, 2); + igraph_add_edges(&g, &vec, 0); + igraph_vector_int_destroy(&vec); + if (weights) { + igraph_vector_push_back(weights, 2); + igraph_vector_push_back(weights, 2); + } + + igraph_get_laplacian_sparse(&g, &sm, mode, normalization, weights); + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_as_matrix(&m, &sm); + igraph_matrix_print(&m); + igraph_matrix_destroy(&m); + printf("===\n"); + + /* Duplicate some edges */ + igraph_vector_int_init_int(&vec, 4, 1, 2, 3, 4); + igraph_add_edges(&g, &vec, 0); + igraph_vector_int_destroy(&vec); + if (weights) { + igraph_vector_push_back(weights, 3); + igraph_vector_push_back(weights, 3); + } + + igraph_get_laplacian_sparse(&g, &sm, mode, normalization, weights); + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_as_matrix(&m, &sm); + igraph_matrix_print(&m); + igraph_matrix_destroy(&m); + printf("===\n"); + + /* Add an isolated vertex */ + igraph_add_vertices(&g, 1, NULL); + + igraph_get_laplacian_sparse(&g, &sm, mode, normalization, weights); + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_as_matrix(&m, &sm); + igraph_matrix_print(&m); + igraph_matrix_destroy(&m); + + igraph_destroy(&g); + + if (weights) { + igraph_vector_destroy(weights); + free(weights); + } + + igraph_sparsemat_destroy(&sm); + + return 0; +} + +int main(void) { + int res; + int i; + igraph_vector_t weights; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_init_int(&weights, 5, 1, 2, 3, 4, 5); + + for (i = 0; i < 8; i++) { + igraph_bool_t is_normalized = i / 4; + igraph_vector_t* v = ((i & 2) / 2 ? &weights : 0); + igraph_bool_t dir = (i % 2 ? IGRAPH_DIRECTED : IGRAPH_UNDIRECTED); + + printf("=== %sormalized, %sweighted, %sdirected\n", + (is_normalized ? "N" : "Unn"), + (v != 0 ? "" : "un"), + (dir == IGRAPH_DIRECTED ? "" : "un") + ); + + res = test_laplacian(v, dir, is_normalized ? IGRAPH_LAPLACIAN_SYMMETRIC : IGRAPH_LAPLACIAN_UNNORMALIZED); + + if (res) { + return i + 1; + } + } + + igraph_vector_destroy(&weights); + + return 0; +} diff --git a/examples/simple/igraph_get_laplacian_sparse.out b/examples/simple/igraph_get_laplacian_sparse.out new file mode 100644 index 0000000..331560b --- /dev/null +++ b/examples/simple/igraph_get_laplacian_sparse.out @@ -0,0 +1,200 @@ +=== Unnormalized, unweighted, undirected + 2 -1 0 0 -1 +-1 2 -1 0 0 + 0 -1 2 -1 0 + 0 0 -1 2 -1 +-1 0 0 -1 2 +=== + 2 -1 0 0 -1 +-1 2 -1 0 0 + 0 -1 2 -1 0 + 0 0 -1 2 -1 +-1 0 0 -1 2 +=== + 2 -1 0 0 -1 +-1 3 -2 0 0 + 0 -2 3 -1 0 + 0 0 -1 3 -2 +-1 0 0 -2 3 +=== + 2 -1 0 0 -1 0 +-1 3 -2 0 0 0 + 0 -2 3 -1 0 0 + 0 0 -1 3 -2 0 +-1 0 0 -2 3 0 + 0 0 0 0 0 0 +=== Unnormalized, unweighted, directed + 1 -1 0 0 0 + 0 1 -1 0 0 + 0 0 1 -1 0 + 0 0 0 1 -1 +-1 0 0 0 1 +=== + 1 -1 0 0 0 + 0 1 -1 0 0 + 0 0 1 -1 0 + 0 0 0 1 -1 +-1 0 0 0 1 +=== + 1 -1 0 0 0 + 0 2 -2 0 0 + 0 0 1 -1 0 + 0 0 0 2 -2 +-1 0 0 0 1 +=== + 1 -1 0 0 0 0 + 0 2 -2 0 0 0 + 0 0 1 -1 0 0 + 0 0 0 2 -2 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== Unnormalized, weighted, undirected + 6 -1 0 0 -5 +-1 3 -2 0 0 + 0 -2 5 -3 0 + 0 0 -3 7 -4 +-5 0 0 -4 9 +=== + 6 -1 0 0 -5 +-1 3 -2 0 0 + 0 -2 5 -3 0 + 0 0 -3 7 -4 +-5 0 0 -4 9 +=== + 6 -1 0 0 -5 +-1 6 -5 0 0 + 0 -5 8 -3 0 + 0 0 -3 10 -7 +-5 0 0 -7 12 +=== + 6 -1 0 0 -5 0 +-1 6 -5 0 0 0 + 0 -5 8 -3 0 0 + 0 0 -3 10 -7 0 +-5 0 0 -7 12 0 + 0 0 0 0 0 0 +=== Unnormalized, weighted, directed + 1 -1 0 0 0 + 0 2 -2 0 0 + 0 0 3 -3 0 + 0 0 0 4 -4 +-5 0 0 0 5 +=== + 1 -1 0 0 0 + 0 2 -2 0 0 + 0 0 3 -3 0 + 0 0 0 4 -4 +-5 0 0 0 5 +=== + 1 -1 0 0 0 + 0 5 -5 0 0 + 0 0 3 -3 0 + 0 0 0 7 -7 +-5 0 0 0 5 +=== + 1 -1 0 0 0 0 + 0 5 -5 0 0 0 + 0 0 3 -3 0 0 + 0 0 0 7 -7 0 +-5 0 0 0 5 0 + 0 0 0 0 0 0 +=== Normalized, unweighted, undirected + 1 -0.5 0 0 -0.5 +-0.5 1 -0.5 0 0 + 0 -0.5 1 -0.5 0 + 0 0 -0.5 1 -0.5 +-0.5 0 0 -0.5 1 +=== + 1 -0.353553 0 0 -0.5 +-0.353553 0.5 -0.25 0 0 + 0 -0.25 0.5 -0.353553 0 + 0 0 -0.353553 1 -0.5 + -0.5 0 0 -0.5 1 +=== + 1 -0.316228 0 0 -0.408248 +-0.316228 0.6 -0.4 0 0 + 0 -0.4 0.6 -0.258199 0 + 0 0 -0.258199 1 -0.666667 +-0.408248 0 0 -0.666667 1 +=== + 1 -0.316228 0 0 -0.408248 0 +-0.316228 0.6 -0.4 0 0 0 + 0 -0.4 0.6 -0.258199 0 0 + 0 0 -0.258199 1 -0.666667 0 +-0.408248 0 0 -0.666667 1 0 + 0 0 0 0 0 0 +=== Normalized, unweighted, directed + 1 -1 0 0 0 + 0 1 -1 0 0 + 0 0 1 -1 0 + 0 0 0 1 -1 +-1 0 0 0 1 +=== + 1 -0.707107 0 0 0 + 0 0.5 -0.5 0 0 + 0 0 0.5 -0.707107 0 + 0 0 0 1 -1 +-1 0 0 0 1 +=== + 1 -0.57735 0 0 0 + 0 0.666667 -0.816497 0 0 + 0 0 0.5 -0.5 0 + 0 0 0 1 -1.41421 +-1 0 0 0 1 +=== + 1 -0.57735 0 0 0 0 + 0 0.666667 -0.816497 0 0 0 + 0 0 0.5 -0.5 0 0 + 0 0 0 1 -1.41421 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== Normalized, weighted, undirected + 1 -0.235702 0 0 -0.680414 +-0.235702 1 -0.516398 0 0 + 0 -0.516398 1 -0.507093 0 + 0 0 -0.507093 1 -0.503953 +-0.680414 0 0 -0.503953 1 +=== + 1 -0.154303 0 0 -0.680414 +-0.154303 0.428571 -0.251976 0 0 + 0 -0.251976 0.555556 -0.377964 0 + 0 0 -0.377964 1 -0.503953 +-0.680414 0 0 -0.503953 1 +=== + 1 -0.129099 0 0 -0.589256 +-0.129099 0.6 -0.456435 0 0 + 0 -0.456435 0.666667 -0.273861 0 + 0 0 -0.273861 1 -0.63901 +-0.589256 0 0 -0.63901 1 +=== + 1 -0.129099 0 0 -0.589256 0 +-0.129099 0.6 -0.456435 0 0 0 + 0 -0.456435 0.666667 -0.273861 0 0 + 0 0 -0.273861 1 -0.63901 0 +-0.589256 0 0 -0.63901 1 0 + 0 0 0 0 0 0 +=== Normalized, weighted, directed + 1 -0.707107 0 0 0 + 0 1 -0.816497 0 0 + 0 0 1 -0.866025 0 + 0 0 0 1 -0.894427 +-2.23607 0 0 0 1 +=== + 1 -0.5 0 0 0 + 0 0.5 -0.447214 0 0 + 0 0 0.6 -0.67082 0 + 0 0 0 1 -0.894427 +-2.23607 0 0 0 1 +=== + 1 -0.377964 0 0 0 + 0 0.714286 -0.845154 0 0 + 0 0 0.6 -0.507093 0 + 0 0 0 1 -1.18322 +-2.23607 0 0 0 1 +=== + 1 -0.377964 0 0 0 0 + 0 0.714286 -0.845154 0 0 0 + 0 0 0.6 -0.507093 0 0 + 0 0 0 1 -1.18322 0 +-2.23607 0 0 0 1 0 + 0 0 0 0 0 0 diff --git a/examples/simple/igraph_get_shortest_paths.c b/examples/simple/igraph_get_shortest_paths.c new file mode 100644 index 0000000..336f98b --- /dev/null +++ b/examples/simple/igraph_get_shortest_paths.c @@ -0,0 +1,116 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include + +int check_evecs(const igraph_t *graph, const igraph_vector_int_list_t *vecs, + const igraph_vector_int_list_t *evecs, int error_code) { + + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t i, n = igraph_vector_int_list_size(vecs); + if (igraph_vector_int_list_size(evecs) != n) { + exit(error_code + 1); + } + + for (i = 0; i < n; i++) { + igraph_vector_int_t *vvec = igraph_vector_int_list_get_ptr(vecs, i); + igraph_vector_int_t *evec = igraph_vector_int_list_get_ptr(evecs, i); + igraph_int_t j, n2 = igraph_vector_int_size(evec); + if (igraph_vector_int_size(vvec) == 0 && n2 == 0) { + continue; + } + if (igraph_vector_int_size(vvec) != n2 + 1) { + exit(error_code + 2); + } + for (j = 0; j < n2; j++) { + igraph_int_t edge = VECTOR(*evec)[j]; + igraph_int_t from = VECTOR(*vvec)[j]; + igraph_int_t to = VECTOR(*vvec)[j + 1]; + if (directed) { + if (from != IGRAPH_FROM(graph, edge) || + to != IGRAPH_TO (graph, edge)) { + exit(error_code); + } + } else { + igraph_int_t from2 = IGRAPH_FROM(graph, edge); + igraph_int_t to2 = IGRAPH_TO(graph, edge); + igraph_int_t min1 = from < to ? from : to; + igraph_int_t max1 = from < to ? to : from; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + if (min1 != min2 || max1 != max2) { + exit(error_code + 3); + } + } + } + } + + return 0; +} + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t vecs, evecs; + igraph_vector_int_t parents, inbound; + igraph_int_t i; + igraph_vs_t vs; + + /* Initialize the library. */ + igraph_setup(); + + igraph_ring(&g, 10, IGRAPH_DIRECTED, 0, 1); + + igraph_vector_int_list_init(&vecs, 0); + igraph_vector_int_list_init(&evecs, 0); + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound, 0); + + igraph_vs_vector_small(&vs, 1, 3, 5, 2, 1, -1); + + igraph_get_shortest_paths(&g, NULL, &vecs, &evecs, 0, vs, IGRAPH_OUT, &parents, &inbound); + + check_evecs(&g, &vecs, &evecs, 10); + + for (i = 0; i < igraph_vector_int_list_size(&vecs); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&vecs, i)); + } + + igraph_vector_int_print(&parents); + igraph_vector_int_print(&inbound); + + igraph_vector_int_list_destroy(&vecs); + igraph_vector_int_list_destroy(&evecs); + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&inbound); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + if (!IGRAPH_FINALLY_STACK_EMPTY) { + return 1; + } + + return 0; +} diff --git a/examples/simple/igraph_get_shortest_paths.out b/examples/simple/igraph_get_shortest_paths.out new file mode 100644 index 0000000..b172b8e --- /dev/null +++ b/examples/simple/igraph_get_shortest_paths.out @@ -0,0 +1,7 @@ +0 1 +0 1 2 3 +0 1 2 3 4 5 +0 1 2 +0 1 +-1 0 1 2 3 4 -2 -2 -2 -2 +-1 0 1 2 3 4 -1 -1 -1 -1 diff --git a/examples/simple/igraph_get_shortest_paths_dijkstra.c b/examples/simple/igraph_get_shortest_paths_dijkstra.c new file mode 100644 index 0000000..23db9cb --- /dev/null +++ b/examples/simple/igraph_get_shortest_paths_dijkstra.c @@ -0,0 +1,84 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t vecs, evecs; + igraph_vector_int_t parents, inbound; + igraph_int_t i; + igraph_real_t weights[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + igraph_vector_t weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + igraph_vs_t vs; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_list_init(&vecs, 0); + igraph_vector_int_list_init(&evecs, 0); + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound, 0); + + igraph_vs_vector_small(&vs, 0, 1, 3, 5, 2, 1, -1); + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + igraph_get_shortest_paths_dijkstra(&g, /*vertices=*/ &vecs, + /*edges=*/ &evecs, /*from=*/ 0, /*to=*/ vs, + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + printf("Vertices:\n"); + for (i = 0; i < igraph_vector_int_list_size(&vecs); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&vecs, i)); + } + + printf("\nEdges:\n"); + for (i = 0; i < igraph_vector_int_list_size(&evecs); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&evecs, i)); + } + + printf("\nParents:\n"); + igraph_vector_int_print(&parents); + + printf("\nInbound:\n"); + igraph_vector_int_print(&inbound); + + igraph_vector_int_list_destroy(&vecs); + igraph_vector_int_list_destroy(&evecs); + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&inbound); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_get_shortest_paths_dijkstra.out b/examples/simple/igraph_get_shortest_paths_dijkstra.out new file mode 100644 index 0000000..09c8a60 --- /dev/null +++ b/examples/simple/igraph_get_shortest_paths_dijkstra.out @@ -0,0 +1,21 @@ +Vertices: +0 +0 1 +0 3 +0 1 5 +0 1 2 +0 1 + +Edges: + +0 +2 +0 5 +0 3 +0 + +Parents: +-1 0 1 0 1 1 2 -2 5 5 + +Inbound: +-1 0 3 2 4 5 7 -1 13 14 diff --git a/examples/simple/igraph_girth.c b/examples/simple/igraph_girth.c new file mode 100644 index 0000000..e766662 --- /dev/null +++ b/examples/simple/igraph_girth.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_real_t girth; + igraph_vector_int_t circle; + igraph_int_t chord[] = { 0, 50 }; + const igraph_vector_int_t v = igraph_vector_int_view(chord, sizeof(chord) / sizeof(chord[0])); + + /* Initialize the library. */ + igraph_setup(); + + igraph_ring(&g, 100, IGRAPH_UNDIRECTED, 0, 1); + + igraph_add_edges(&g, &v, 0); + igraph_girth(&g, &girth, 0); + if (girth != 51) { + return 1; + } + + igraph_destroy(&g); + + /* Special case: null graph */ + igraph_ring(&g, 0, IGRAPH_UNDIRECTED, 0, 1); + igraph_vector_int_init(&circle, 1); + VECTOR(circle)[0] = 2; + igraph_girth(&g, &girth, &circle); + if (girth != IGRAPH_INFINITY) { + return 2; + } + if (igraph_vector_int_size(&circle) != 0) { + return 3; + } + igraph_vector_int_destroy(&circle); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_grg_game.c b/examples/simple/igraph_grg_game.c new file mode 100644 index 0000000..06d5cdd --- /dev/null +++ b/examples/simple/igraph_grg_game.c @@ -0,0 +1,53 @@ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_t x, y; + igraph_vector_t weights; + igraph_eit_t eit; + igraph_real_t avg_dist; + + /* Initialize the library. */ + igraph_setup(); + + /* Set random seed for reproducible results */ + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Create a random geometric graph and retrieve vertex coordinates */ + + igraph_vector_init(&x, 0); + igraph_vector_init(&y, 0); + + igraph_grg_game(&graph, 200, 0.1, /* torus */ false, &x, &y); + + /* Compute edge weights as geometric distance */ + + igraph_vector_init(&weights, igraph_ecount(&graph)); + igraph_eit_create(&graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit); + for (; ! IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + igraph_int_t u = IGRAPH_FROM(&graph, e); + igraph_int_t v = IGRAPH_TO(&graph, e); + + VECTOR(weights)[e] = hypot(VECTOR(x)[u] - VECTOR(x)[v], VECTOR(y)[u] - VECTOR(y)[v]); + } + igraph_eit_destroy(&eit); + + /* Compute average path length */ + + igraph_average_path_length(&graph, &weights, &avg_dist, NULL, IGRAPH_UNDIRECTED, /* unconn */ true); + + printf("Average distance in the geometric graph: %g.\n", avg_dist); + + /* Destroy data structures when no longer needed */ + + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + igraph_vector_destroy(&x); + igraph_vector_destroy(&y); + + return 0; +} diff --git a/examples/simple/igraph_grg_game.out b/examples/simple/igraph_grg_game.out new file mode 100644 index 0000000..dd1be69 --- /dev/null +++ b/examples/simple/igraph_grg_game.out @@ -0,0 +1 @@ +Average distance in the geometric graph: 0.645025. diff --git a/examples/simple/igraph_has_multiple.c b/examples/simple/igraph_has_multiple.c new file mode 100644 index 0000000..9a67035 --- /dev/null +++ b/examples/simple/igraph_has_multiple.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t graph; + igraph_bool_t res; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 1, 0, 1, 1, 0, 3, 4, 11, 10, -1); + igraph_has_multiple(&graph, &res); + if (!res) { + return 1; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0, 0, 1, 2, 1, 1, 2, 2, 2, 1, 2, 3, 2, 4, + 2, 5, 2, 6, 2, 2, 3, 2, 0, 0, 6, 2, 2, 2, 0, 0, -1); + igraph_has_multiple(&graph, &res); + if (!res) { + return 2; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 1, 1, 0, 3, 4, 11, 10, -1); + igraph_has_multiple(&graph, &res); + if (res) { + return 3; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0, 0, 1, 2, 1, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, 2, 2, -1); + igraph_has_multiple(&graph, &res); + if (!res) { + return 4; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0, 0, 1, 2, 1, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, -1); + igraph_has_multiple(&graph, &res); + if (res) { + return 5; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 1, 2, -1); + igraph_has_multiple(&graph, &res); + if (!res) { + return 6; + } + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 0, 0, 0, -1); + igraph_has_multiple(&graph, &res); + if (!res) { + return 7; + } + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_independent_sets.c b/examples/simple/igraph_independent_sets.c new file mode 100644 index 0000000..1535c1e --- /dev/null +++ b/examples/simple/igraph_independent_sets.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t result; + igraph_int_t n; + igraph_int_t alpha; + const int params[] = {4, -1, 2, 2, 0, 0, -1, -1}; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_list_init(&result, 0); + + igraph_kary_tree(&g, 5, 2, IGRAPH_TREE_OUT); + for (size_t j = 0; j < sizeof(params) / (2 * sizeof(params[0])); j++) { + if (params[2 * j + 1] != 0) { + igraph_independent_vertex_sets(&g, &result, params[2 * j], params[2 * j + 1], IGRAPH_UNLIMITED); + } else { + igraph_largest_independent_vertex_sets(&g, &result); + } + n = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " independent sets found\n", n); + for (igraph_int_t i = 0; i < n; i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&result, i)); + } + } + igraph_destroy(&g); + + igraph_kary_tree(&g, 10, 2, IGRAPH_TREE_OUT); + igraph_maximal_independent_vertex_sets(&g, &result, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + n = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " maximal independent sets found\n", n); + for (igraph_int_t i = 0; i < n; i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&result, i)); + } + + igraph_maximal_independent_vertex_sets(&g, &result, 4, 5, IGRAPH_UNLIMITED); + n = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " maximal independent sets between sizes 4 and 5 found\n", n); + for (igraph_int_t i = 0; i < n; i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&result, i)); + } + + igraph_independence_number(&g, &alpha); + printf("alpha=%" IGRAPH_PRId "\n", alpha); + + igraph_destroy(&g); + + igraph_vector_int_list_destroy(&result); + + return 0; +} diff --git a/examples/simple/igraph_independent_sets.out b/examples/simple/igraph_independent_sets.out new file mode 100644 index 0000000..70510a6 --- /dev/null +++ b/examples/simple/igraph_independent_sets.out @@ -0,0 +1,41 @@ +0 independent sets found +6 independent sets found +0 3 +0 4 +1 2 +2 3 +2 4 +3 4 +2 independent sets found +0 3 4 +2 3 4 +13 independent sets found +0 +1 +2 +3 +4 +0 3 +0 4 +1 2 +2 3 +2 4 +3 4 +0 3 4 +2 3 4 +9 maximal independent sets found +0 3 4 5 6 +0 3 5 6 9 +0 4 5 6 7 8 +0 5 6 7 8 9 +1 2 7 8 9 +1 5 6 7 8 9 +2 3 4 +2 3 9 +2 4 7 8 +4 maximal independent sets between sizes 4 and 5 found +0 3 4 5 6 +0 3 5 6 9 +1 2 7 8 9 +2 4 7 8 +alpha=6 diff --git a/examples/simple/igraph_intersection.c b/examples/simple/igraph_intersection.c new file mode 100644 index 0000000..a16416b --- /dev/null +++ b/examples/simple/igraph_intersection.c @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void print_vector(igraph_vector_t *v) { + igraph_int_t i, l = igraph_vector_size(v); + for (i = 0; i < l; i++) { + printf(" %" IGRAPH_PRId "", (igraph_int_t) VECTOR(*v)[i]); + } + printf("\n"); +} + +int main(void) { + + igraph_t left, right, isec; + igraph_vector_int_t v; + igraph_vector_ptr_t glist; + igraph_t g1, g2, g3; + igraph_vector_int_t edge_map1, edge_map2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, -1); + igraph_create(&left, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init_int_end(&v, -1, 1, 0, 5, 4, 1, 2, 3, 2, -1); + igraph_create(&right, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + igraph_vector_int_init(&edge_map1, 0); + igraph_vector_int_init(&edge_map2, 0); + + igraph_intersection(&isec, &left, &right, &edge_map1, &edge_map2); + igraph_vector_int_init(&v, 0); + igraph_get_edgelist(&isec, &v, 0); + printf("---\n"); + igraph_vector_int_print(&v); + igraph_vector_int_print(&edge_map1); + igraph_vector_int_print(&edge_map2); + printf("---\n"); + igraph_vector_int_destroy(&v); + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&isec); + igraph_vector_int_destroy(&edge_map1); + igraph_vector_int_destroy(&edge_map2); + + /* empty graph list */ + igraph_vector_ptr_init(&glist, 0); + igraph_intersection_many(&isec, &glist, 0); + if (igraph_vcount(&isec) != 0 || !igraph_is_directed(&isec)) { + return 1; + } + igraph_destroy(&isec); + igraph_vector_ptr_destroy(&glist); + + /* graph list with an empty graph */ + igraph_vector_ptr_init(&glist, 3); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, -1); + igraph_create(&g1, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, -1); + igraph_create(&g2, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + igraph_empty(&g3, 10, IGRAPH_DIRECTED); + + VECTOR(glist)[0] = &g1; + VECTOR(glist)[1] = &g2; + VECTOR(glist)[2] = &g3; + igraph_intersection_many(&isec, &glist, 0); + if (igraph_ecount(&isec) != 0 || igraph_vcount(&isec) != 10) { + return 2; + } + igraph_destroy(&g1); + igraph_destroy(&g2); + igraph_destroy(&g3); + igraph_destroy(&isec); + igraph_vector_ptr_destroy(&glist); + + /* "proper" graph list */ + igraph_vector_ptr_init(&glist, 3); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, -1); + igraph_create(&g1, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + igraph_vector_int_init_int_end(&v, -1, 0, 1, 1, 2, 2, 3, 3, 2, 4, 5, 6, 5, -1); + igraph_create(&g2, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + igraph_vector_int_init_int_end(&v, -1, 2, 3, 1, 0, 1, 2, 3, 2, 4, 5, 6, 5, 2, 3, -1); + igraph_create(&g3, &v, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&v); + + VECTOR(glist)[0] = &g1; + VECTOR(glist)[1] = &g2; + VECTOR(glist)[2] = &g3; + igraph_intersection_many(&isec, &glist, 0); + igraph_write_graph_edgelist(&isec, stdout); + igraph_destroy(&g1); + igraph_destroy(&g2); + igraph_destroy(&g3); + igraph_destroy(&isec); + igraph_vector_ptr_destroy(&glist); + + return 0; +} diff --git a/examples/simple/igraph_intersection.out b/examples/simple/igraph_intersection.out new file mode 100644 index 0000000..751d9db --- /dev/null +++ b/examples/simple/igraph_intersection.out @@ -0,0 +1,7 @@ +--- +1 2 +1 +2 +--- +1 2 +2 3 diff --git a/examples/simple/igraph_is_biconnected.c b/examples/simple/igraph_is_biconnected.c new file mode 100644 index 0000000..f72f52e --- /dev/null +++ b/examples/simple/igraph_is_biconnected.c @@ -0,0 +1,36 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + + igraph_t g; + igraph_bool_t result; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 7, 0, 0, 1, 1, 2, 2, 3, 3, 0, 2, 4, 4, 5, 2, 5, -1); + igraph_is_biconnected(&g, &result); + printf("Graph is%sbiconnected.\n", result ? " " : " not "); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_is_biconnected.out b/examples/simple/igraph_is_biconnected.out new file mode 100644 index 0000000..020696b --- /dev/null +++ b/examples/simple/igraph_is_biconnected.out @@ -0,0 +1 @@ +Graph is not biconnected. diff --git a/examples/simple/igraph_is_directed.c b/examples/simple/igraph_is_directed.c new file mode 100644 index 0000000..edcf3a7 --- /dev/null +++ b/examples/simple/igraph_is_directed.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + + /* Initialize the library. */ + igraph_setup(); + + igraph_empty(&g, 0, 0); + if (igraph_is_directed(&g)) { + return 1; + } + igraph_destroy(&g); + + igraph_empty(&g, 0, 1); + if (!igraph_is_directed(&g)) { + return 2; + } + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_is_loop.c b/examples/simple/igraph_is_loop.c new file mode 100644 index 0000000..a9ea587 --- /dev/null +++ b/examples/simple/igraph_is_loop.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2007-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +void analyze_loops(const igraph_t *graph) { + igraph_vector_bool_t is_loop; + igraph_bool_t has_loop; + igraph_int_t loop_count; + + igraph_has_loop(graph, &has_loop); + printf("Has loops? %s\n", has_loop ? "Yes" : "No"); + + igraph_count_loops(graph, &loop_count); + printf("How many? %" IGRAPH_PRId "\n", loop_count); + + igraph_vector_bool_init(&is_loop, 0); + igraph_is_loop(graph, &is_loop, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + printf("Loop positions: "); igraph_vector_bool_print(&is_loop); + igraph_vector_bool_destroy(&is_loop); + + printf("\n"); +} + +int main(void) { + igraph_t graph; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 0,1, 1,2, 2,1, 0,1, 1,0, 3,4, 11,10, -1); + analyze_loops(&graph); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,0, 1,1, 2,2, 2,3, 2,4, 2,5, 2,6, 2,2, 0,0, -1); + analyze_loops(&graph); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_is_loop.out b/examples/simple/igraph_is_loop.out new file mode 100644 index 0000000..cb38344 --- /dev/null +++ b/examples/simple/igraph_is_loop.out @@ -0,0 +1,8 @@ +Has loops? No +How many? 0 +Loop positions: 0 0 0 0 0 0 0 + +Has loops? Yes +How many? 5 +Loop positions: 1 1 1 0 0 0 0 1 1 + diff --git a/examples/simple/igraph_is_minimal_separator.c b/examples/simple/igraph_is_minimal_separator.c new file mode 100644 index 0000000..038a39e --- /dev/null +++ b/examples/simple/igraph_is_minimal_separator.c @@ -0,0 +1,76 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#define FAIL(msg, error) do { printf(msg "\n") ; return error; } while (0) + +int main(void) { + + igraph_t graph; + igraph_vector_int_t sep; + igraph_bool_t result; + + /* Initialize the library. */ + igraph_setup(); + + /* Simple star graph, remove the center */ + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_is_minimal_separator(&graph, igraph_vss_1(0), &result); + if (!result) { + FAIL("Center of star graph failed.", 1); + } + + /* Same graph, but another vertex */ + igraph_is_minimal_separator(&graph, igraph_vss_1(6), &result); + if (result) { + FAIL("Non-center of star graph failed.", 2); + } + igraph_destroy(&graph); + + /* Karate club */ + igraph_famous(&graph, "zachary"); + igraph_vector_int_init(&sep, 0); + igraph_vector_int_push_back(&sep, 32); + igraph_vector_int_push_back(&sep, 33); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&sep), &result); + if (!result) { + FAIL("Karate network (32,33) failed", 3); + } + + igraph_vector_int_resize(&sep, 5); + VECTOR(sep)[0] = 8; + VECTOR(sep)[1] = 9; + VECTOR(sep)[2] = 19; + VECTOR(sep)[3] = 30; + VECTOR(sep)[4] = 31; + igraph_is_minimal_separator(&graph, igraph_vss_vector(&sep), &result); + if (result) { + FAIL("Karate network (8,9,19,30,31) failed", 4); + } + + igraph_destroy(&graph); + igraph_vector_int_destroy(&sep); + + return 0; +} diff --git a/examples/simple/igraph_is_multiple.c b/examples/simple/igraph_is_multiple.c new file mode 100644 index 0000000..9fe65f4 --- /dev/null +++ b/examples/simple/igraph_is_multiple.c @@ -0,0 +1,58 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void print_vector(igraph_vector_bool_t *v, FILE *f) { + igraph_int_t i; + for (i = 0; i < igraph_vector_bool_size(v); i++) { + fprintf(f, " %i", VECTOR(*v)[i] ? 1 : 0); + } + fprintf(f, "\n"); +} + +int main(void) { + + igraph_t graph; + igraph_vector_bool_t v; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_bool_init(&v, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 1, 0, 1, 1, 0, 3, 4, 11, 10, -1); + igraph_is_multiple(&graph, &v, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + print_vector(&v, stdout); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0, 0, 1, 2, 1, 1, 2, 2, 2, 1, 2, 3, 2, 4, + 2, 5, 2, 6, 2, 2, 3, 2, 0, 0, 6, 2, 2, 2, 0, 0, -1); + igraph_is_multiple(&graph, &v, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + print_vector(&v, stdout); + igraph_destroy(&graph); + + igraph_vector_bool_destroy(&v); + + return 0; +} diff --git a/examples/simple/igraph_is_multiple.out b/examples/simple/igraph_is_multiple.out new file mode 100644 index 0000000..14ea4e2 --- /dev/null +++ b/examples/simple/igraph_is_multiple.out @@ -0,0 +1,2 @@ + 0 0 0 1 0 0 0 + 0 0 0 0 1 0 0 0 0 1 1 1 1 1 1 diff --git a/examples/simple/igraph_is_separator.c b/examples/simple/igraph_is_separator.c new file mode 100644 index 0000000..a6fed69 --- /dev/null +++ b/examples/simple/igraph_is_separator.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#define FAIL(msg, error) do { printf(msg "\n") ; return error; } while (0) + +int main(void) { + + igraph_t graph; + igraph_vector_int_t sep; + igraph_bool_t result; + + /* Initialize the library. */ + igraph_setup(); + + /* Simple star graph, remove the center */ + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_is_separator(&graph, igraph_vss_1(0), &result); + if (!result) { + FAIL("Center of star graph failed.", 1); + } + + /* Same graph, but another vertex */ + igraph_is_separator(&graph, igraph_vss_1(6), &result); + if (result) { + FAIL("Non-center of star graph failed.", 2); + } + + /* Same graph, all vertices but the center */ + igraph_is_separator(&graph, igraph_vss_range(1, 10), &result); + if (result) { + FAIL("All non-central vertices of star graph failed.", 5); + } + + /* Same graph, all vertices */ + igraph_is_separator(&graph, igraph_vss_range(0, 10), &result); + if (result) { + FAIL("All vertices of star graph failed.", 6); + } + igraph_destroy(&graph); + + /* Karate club */ + igraph_famous(&graph, "zachary"); + igraph_vector_int_init(&sep, 0); + igraph_vector_int_push_back(&sep, 32); + igraph_vector_int_push_back(&sep, 33); + igraph_is_separator(&graph, igraph_vss_vector(&sep), &result); + if (!result) { + FAIL("Karate network (32,33) failed", 3); + } + + igraph_vector_int_resize(&sep, 5); + VECTOR(sep)[0] = 8; + VECTOR(sep)[1] = 9; + VECTOR(sep)[2] = 19; + VECTOR(sep)[3] = 30; + VECTOR(sep)[4] = 31; + igraph_is_separator(&graph, igraph_vss_vector(&sep), &result); + if (result) { + FAIL("Karate network (8,9,19,30,31) failed", 4); + } + + igraph_destroy(&graph); + igraph_vector_int_destroy(&sep); + + return 0; +} diff --git a/examples/simple/igraph_isomorphic_vf2.c b/examples/simple/igraph_isomorphic_vf2.c new file mode 100644 index 0000000..7167878 --- /dev/null +++ b/examples/simple/igraph_isomorphic_vf2.c @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +int main(void) { + igraph_setup(); + + igraph_t ring1, ring2; + igraph_vector_int_t color1, color2; + igraph_vector_int_t perm; + igraph_bool_t iso; + igraph_int_t count; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 12345); + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/1); + igraph_vector_int_init_range(&perm, 0, igraph_vcount(&ring1)); + igraph_vector_int_shuffle(&perm); + igraph_permute_vertices(&ring1, &ring2, &perm); + + /* Everything has the same color */ + igraph_vector_int_init(&color1, igraph_vcount(&ring1)); + igraph_vector_int_init(&color2, igraph_vcount(&ring2)); + igraph_isomorphic_vf2(&ring1, &ring2, &color1, &color2, 0, 0, &iso, 0, 0, 0, 0, 0); + if (!iso) { + fprintf(stderr, "Single color failed.\n"); + return 1; + } + + /* Two colors, just counting */ + for (i = 0; i < igraph_vector_int_size(&color1); i += 2) { + VECTOR(color1)[i] = 1; + } + for (i = 0; i < igraph_vector_int_size(&color2); i++) { + VECTOR(color2)[i] = VECTOR(color1)[VECTOR(perm)[i]]; + } + igraph_count_isomorphisms_vf2(&ring1, &ring2, &color1, &color2, 0, 0, &count, 0, 0, 0); + if (count != 100) { + fprintf(stderr, "Count with two colors failed, expected 100, got %" IGRAPH_PRId ".\n", count); + return 2; + } + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + igraph_vector_int_destroy(&color2); + igraph_vector_int_destroy(&perm); + + /* Two colors, count subisomorphisms */ + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + igraph_ring(&ring2, 80, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + + igraph_vector_int_init(&color2, igraph_vcount(&ring2)); + for (i = 0; i < igraph_vector_int_size(&color1); i += 2) { + VECTOR(color1)[i] = 0; + VECTOR(color1)[i + 1] = 1; + } + for (i = 0; i < igraph_vector_int_size(&color2); i += 2) { + VECTOR(color2)[i] = 0; + VECTOR(color2)[i + 1] = 1; + } + igraph_count_subisomorphisms_vf2(&ring1, &ring2, &color1, &color2, 0, 0, + &count, 0, 0, 0); + if (count != 21) { + fprintf(stderr, "Count with two colors failed, expected 21, got %" IGRAPH_PRId ".\n", count); + return 3; + } + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + + return 0; +} diff --git a/examples/simple/igraph_join.c b/examples/simple/igraph_join.c new file mode 100644 index 0000000..949eac8 --- /dev/null +++ b/examples/simple/igraph_join.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t left, right, joined; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&left, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, -1); + igraph_small(&right, 5, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,4, -1); + + igraph_join(&joined, &left, &right); + igraph_write_graph_edgelist(&joined, stdout); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + + igraph_small(&left, 2, IGRAPH_DIRECTED, 0,1, -1); + igraph_small(&right, 3, IGRAPH_DIRECTED, 0,1, 2,1, -1); + + igraph_join(&joined, &left, &right); + igraph_write_graph_edgelist(&joined, stdout); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + return 0; +} diff --git a/examples/simple/igraph_join.out b/examples/simple/igraph_join.out new file mode 100644 index 0000000..65f662e --- /dev/null +++ b/examples/simple/igraph_join.out @@ -0,0 +1,44 @@ +0 1 +0 4 +0 5 +0 6 +0 7 +0 8 +1 2 +1 4 +1 5 +1 6 +1 7 +1 8 +2 2 +2 4 +2 5 +2 6 +2 7 +2 8 +3 4 +3 5 +3 6 +3 7 +3 8 +4 5 +5 6 +6 6 +6 8 + +0 1 +0 2 +0 3 +0 4 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +3 0 +3 1 +4 0 +4 1 +4 3 + diff --git a/examples/simple/igraph_kary_tree.c b/examples/simple/igraph_kary_tree.c new file mode 100644 index 0000000..b621f1d --- /dev/null +++ b/examples/simple/igraph_kary_tree.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_bool_t res; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed binary tree on 15 nodes, + with edges pointing towards the root. */ + igraph_kary_tree(&graph, 15, 2, IGRAPH_TREE_IN); + + igraph_is_tree(&graph, &res, NULL, IGRAPH_IN); + printf("Is it an in-tree? %s\n", res ? "Yes" : "No"); + + igraph_is_tree(&graph, &res, NULL, IGRAPH_OUT); + printf("Is it an out-tree? %s\n", res ? "Yes" : "No"); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_kary_tree.out b/examples/simple/igraph_kary_tree.out new file mode 100644 index 0000000..d1fca17 --- /dev/null +++ b/examples/simple/igraph_kary_tree.out @@ -0,0 +1,2 @@ +Is it an in-tree? Yes +Is it an out-tree? No diff --git a/examples/simple/igraph_lapack_dgeev.c b/examples/simple/igraph_lapack_dgeev.c new file mode 100644 index 0000000..013318b --- /dev/null +++ b/examples/simple/igraph_lapack_dgeev.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_matrix_t A; + igraph_matrix_t vectors_left, vectors_right; + igraph_vector_t values_real, values_imag; + igraph_vector_complex_t values; + int info = 1; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&A, 2, 2); + igraph_matrix_init(&vectors_left, 0, 0); + igraph_matrix_init(&vectors_right, 0, 0); + igraph_vector_init(&values_real, 0); + igraph_vector_init(&values_imag, 0); + MATRIX(A, 0, 0) = 1.0; + MATRIX(A, 0, 1) = 1.0; + MATRIX(A, 1, 0) = -1.0; + MATRIX(A, 1, 1) = 1.0; + + igraph_lapack_dgeev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, &info); + igraph_vector_complex_create(&values, &values_real, &values_imag); + printf("eigenvalues:\n"); + igraph_vector_complex_print(&values); + printf("left eigenvectors:\n"); + igraph_matrix_print(&vectors_left); + printf("right eigenvectors:\n"); + igraph_matrix_print(&vectors_right); + + igraph_vector_destroy(&values_imag); + igraph_vector_destroy(&values_real); + igraph_vector_complex_destroy(&values); + igraph_matrix_destroy(&vectors_right); + igraph_matrix_destroy(&vectors_left); + igraph_matrix_destroy(&A); + + return 0; +} diff --git a/examples/simple/igraph_lapack_dgeev.out b/examples/simple/igraph_lapack_dgeev.out new file mode 100644 index 0000000..4187d18 --- /dev/null +++ b/examples/simple/igraph_lapack_dgeev.out @@ -0,0 +1,8 @@ +eigenvalues: +1+1i 1-1i +left eigenvectors: +0.707107 0 + 0 0.707107 +right eigenvectors: +0.707107 0 + 0 0.707107 diff --git a/examples/simple/igraph_lapack_dgeevx.c b/examples/simple/igraph_lapack_dgeevx.c new file mode 100644 index 0000000..2f2d377 --- /dev/null +++ b/examples/simple/igraph_lapack_dgeevx.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +void matrix_to_complex_vectors(igraph_vector_complex_t *c1, igraph_vector_complex_t *c2, igraph_matrix_t *m) { + IGRAPH_REAL(VECTOR(*c1)[0]) = MATRIX(*m, 0, 0); + IGRAPH_REAL(VECTOR(*c1)[1]) = MATRIX(*m, 1, 0); + IGRAPH_IMAG(VECTOR(*c1)[0]) = MATRIX(*m, 0, 1); + IGRAPH_IMAG(VECTOR(*c1)[1]) = MATRIX(*m, 1, 1); + + IGRAPH_REAL(VECTOR(*c2)[0]) = MATRIX(*m, 0, 0); + IGRAPH_REAL(VECTOR(*c2)[1]) = MATRIX(*m, 1, 0); + IGRAPH_IMAG(VECTOR(*c2)[0]) = -MATRIX(*m, 0, 1); + IGRAPH_IMAG(VECTOR(*c2)[1]) = -MATRIX(*m, 1, 1); +} + +int main(void) { + igraph_matrix_t A; + igraph_matrix_t vectors_left, vectors_right; + igraph_vector_t values_real, values_imag; + igraph_vector_complex_t values; + igraph_vector_complex_t eigenvector1; + igraph_vector_complex_t eigenvector2; + int info = 1; + igraph_real_t abnrm; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&A, 2, 2); + igraph_matrix_init(&vectors_left, 0, 0); + igraph_matrix_init(&vectors_right, 0, 0); + igraph_vector_init(&values_real, 0); + igraph_vector_init(&values_imag, 0); + igraph_vector_complex_init(&eigenvector1, 2); + igraph_vector_complex_init(&eigenvector2, 2); + MATRIX(A, 0, 0) = 1.0; + MATRIX(A, 0, 1) = 1.0; + MATRIX(A, 1, 0) = -1.0; + MATRIX(A, 1, 1) = 1.0; + + igraph_lapack_dgeevx(IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH, + &A, &values_real, &values_imag, + &vectors_left, &vectors_right, NULL, NULL, + /*scale=*/ NULL, &abnrm, /*rconde=*/ NULL, + /*rcondv=*/ NULL, &info); + + igraph_vector_complex_create(&values, &values_real, &values_imag); + printf("eigenvalues:\n"); + igraph_vector_complex_print(&values); + + printf("\nleft eigenvectors:\n"); + /*matrix_to_complex_vectors only works because we have two complex + conjugate eigenvalues */ + matrix_to_complex_vectors(&eigenvector1, &eigenvector2, &vectors_left); + igraph_vector_complex_print(&eigenvector1); + igraph_vector_complex_print(&eigenvector2); + + printf("\nright eigenvectors:\n"); + matrix_to_complex_vectors(&eigenvector1, &eigenvector2, &vectors_right); + igraph_vector_complex_print(&eigenvector1); + igraph_vector_complex_print(&eigenvector2); + printf("\nOne-norm of the balanced matrix:\n%g\n", abnrm); + + igraph_vector_destroy(&values_imag); + igraph_vector_destroy(&values_real); + igraph_vector_complex_destroy(&values); + igraph_vector_complex_destroy(&eigenvector1); + igraph_vector_complex_destroy(&eigenvector2); + igraph_matrix_destroy(&vectors_right); + igraph_matrix_destroy(&vectors_left); + igraph_matrix_destroy(&A); + + return 0; +} diff --git a/examples/simple/igraph_lapack_dgeevx.out b/examples/simple/igraph_lapack_dgeevx.out new file mode 100644 index 0000000..dc45f4b --- /dev/null +++ b/examples/simple/igraph_lapack_dgeevx.out @@ -0,0 +1,13 @@ +eigenvalues: +1+1i 1-1i + +left eigenvectors: +0.707107+0i 0+0.707107i +0.707107-0i 0-0.707107i + +right eigenvectors: +0.707107+0i 0+0.707107i +0.707107-0i 0-0.707107i + +One-norm of the balanced matrix: +2 diff --git a/examples/simple/igraph_lapack_dgesv.c b/examples/simple/igraph_lapack_dgesv.c new file mode 100644 index 0000000..166a7f0 --- /dev/null +++ b/examples/simple/igraph_lapack_dgesv.c @@ -0,0 +1,153 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#define DIM 10 + +void igraph_print_warning(const char *reason, const char *file, + int line) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + printf("Warning: %s\n", reason); +} + +int main(void) { + + igraph_matrix_t A, B, RHS; + int info; + int i, j; + + /* Initialize the library. */ + igraph_setup(); + + /* Identity matrix, you have to start somewhere */ + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&B, DIM, 1); + for (i = 0; i < DIM; i++) { + MATRIX(A, i, i) = 1.0; + MATRIX(B, i, 0) = i + 1; + } + + igraph_matrix_init_copy(&RHS, &B); + igraph_lapack_dgesv(&A, /*ipiv=*/ 0, &RHS, &info); + + if (info != 0) { + return 1; + } + if (!igraph_matrix_all_e(&B, &RHS)) { + return 2; + } + + igraph_matrix_destroy(&A); + igraph_matrix_destroy(&B); + igraph_matrix_destroy(&RHS); + + /* Diagonal matrix */ + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&RHS, DIM, 1); + for (i = 0; i < DIM; i++) { + MATRIX(A, i, i) = i + 1; + MATRIX(RHS, i, 0) = i + 1; + } + + igraph_lapack_dgesv(&A, /*ipiv=*/ 0, &RHS, &info); + + if (info != 0) { + return 3; + } + for (i = 0; i < DIM; i++) { + if (MATRIX(RHS, i, 0) != 1.0) { + return 4; + } + } + + igraph_matrix_destroy(&A); + igraph_matrix_destroy(&RHS); + + /* A general matrix */ + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&B, DIM, 1); + igraph_matrix_init(&RHS, DIM, 1); + for (i = 0; i < DIM; i++) { + int j; + MATRIX(B, i, 0) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + for (j = 0; j < DIM; j++) { + MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, /*a=*/ &A, + /*x-*/ &MATRIX(B, 0, 0), /*beta=*/ 0, + /*y=*/ &MATRIX(RHS, 0, 0)); + + igraph_lapack_dgesv(&A, /*ipiv=*/ 0, &RHS, &info); + if (info != 0) { + return 5; + } + for (i = 0; i < DIM; i++) { + if (fabs(MATRIX(B, i, 0) - MATRIX(RHS, i, 0)) > 1e-11) { + return 6; + } + } + + igraph_matrix_destroy(&A); + igraph_matrix_destroy(&B); + igraph_matrix_destroy(&RHS); + + /* A singular matrix */ + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&B, DIM, 1); + igraph_matrix_init(&RHS, DIM, 1); + for (i = 0; i < DIM; i++) { + MATRIX(B, i, 0) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + for (j = 0; j < DIM; j++) { + MATRIX(A, i, j) = i == j ? 1 : 0; + } + } + for (i = 0; i < DIM; i++) { + MATRIX(A, DIM - 1, i) = MATRIX(A, 0, i); + } + + igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, /*a=*/ &A, + /*x-*/ &MATRIX(B, 0, 0), /*beta=*/ 0, + /*y=*/ &MATRIX(RHS, 0, 0)); + + igraph_set_warning_handler(igraph_print_warning); + igraph_lapack_dgesv(&A, /*ipiv=*/ 0, &RHS, &info); + if (info != 10) { + printf("LAPACK returned info = %d, should have been 10", info); + return 7; + } + + igraph_matrix_destroy(&A); + igraph_matrix_destroy(&B); + igraph_matrix_destroy(&RHS); + + return 0; +} diff --git a/examples/simple/igraph_lapack_dgesv.out b/examples/simple/igraph_lapack_dgesv.out new file mode 100644 index 0000000..2443131 --- /dev/null +++ b/examples/simple/igraph_lapack_dgesv.out @@ -0,0 +1 @@ +Warning: LU: factor is exactly singular. diff --git a/examples/simple/igraph_lapack_dsyevr.c b/examples/simple/igraph_lapack_dsyevr.c new file mode 100644 index 0000000..0fedd75 --- /dev/null +++ b/examples/simple/igraph_lapack_dsyevr.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_matrix_t A; + igraph_matrix_t vectors; + igraph_vector_t values; + + /* Initialize the library. */ + igraph_setup(); + + igraph_matrix_init(&A, 2, 2); + igraph_matrix_init(&vectors, 0, 0); + igraph_vector_init(&values, 0); + + MATRIX(A, 0, 0) = 2.0; + MATRIX(A, 0, 1) = -1.0; + MATRIX(A, 1, 0) = -1.0; + MATRIX(A, 1, 1) = 3.0; + + printf("Take a subset:\n"); + + igraph_lapack_dsyevr(&A, IGRAPH_LAPACK_DSYEV_SELECT, /*vl=*/ 0, /*vu=*/ 0, + /*vestimate=*/ 0, /*il=*/ 1, /*iu=*/ 1, + /*abstol=*/ 1e-10, &values, &vectors, + /*support=*/ 0); + printf("eigenvalues:\n"); + igraph_vector_print(&values); + printf("eigenvectors:\n"); + igraph_matrix_print(&vectors); + + printf("\nTake a subset based on an interval:\n"); + + igraph_lapack_dsyevr(&A, IGRAPH_LAPACK_DSYEV_INTERVAL, /*vl*/ 3, /*vu*/ 4, + /*vestimate=*/ 1, /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-10, &values, &vectors, + /*support=*/ 0); + + printf("eigenvalues:\n"); + igraph_vector_print(&values); + printf("eigenvectors:\n"); + igraph_matrix_print(&vectors); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&A); + + return 0; +} diff --git a/examples/simple/igraph_lapack_dsyevr.out b/examples/simple/igraph_lapack_dsyevr.out new file mode 100644 index 0000000..dbe6bbb --- /dev/null +++ b/examples/simple/igraph_lapack_dsyevr.out @@ -0,0 +1,13 @@ +Take a subset: +eigenvalues: +1.38197 +eigenvectors: +0.850651 +0.525731 + +Take a subset based on an interval: +eigenvalues: +3.61803 +eigenvectors: +-0.525731 + 0.850651 diff --git a/examples/simple/igraph_layout_reingold_tilford.c b/examples/simple/igraph_layout_reingold_tilford.c new file mode 100644 index 0000000..cdb37be --- /dev/null +++ b/examples/simple/igraph_layout_reingold_tilford.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t g; + FILE *f; + igraph_matrix_t coords; + + /* Initialize the library. */ + igraph_setup(); + + f = fopen("igraph_layout_reingold_tilford.in", "r"); + igraph_read_graph_edgelist(&g, f, 0, IGRAPH_DIRECTED); + fclose(f); + + igraph_matrix_init(&coords, 0, 0); + igraph_layout_reingold_tilford(&g, &coords, IGRAPH_IN, 0, 0); + igraph_matrix_print(&coords); + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_layout_reingold_tilford.in b/examples/simple/igraph_layout_reingold_tilford.in new file mode 100644 index 0000000..d49aef3 --- /dev/null +++ b/examples/simple/igraph_layout_reingold_tilford.in @@ -0,0 +1,44 @@ +1 0 +2 0 +3 0 +4 0 +5 0 +6 1 +7 13 +8 0 +9 5 +10 1 +11 0 +12 2 +13 0 +14 2 +15 13 +16 11 +17 5 +18 4 +19 4 +20 4 +21 4 +22 28 +23 4 +24 10 +25 24 +26 2 +27 4 +28 5 +29 8 +30 14 +31 33 +32 14 +33 14 +34 12 +35 37 +36 14 +37 16 +38 14 +39 12 +40 9 +41 37 +42 36 +43 41 +44 41 diff --git a/examples/simple/igraph_lcf.c b/examples/simple/igraph_lcf.c new file mode 100644 index 0000000..0a5875a --- /dev/null +++ b/examples/simple/igraph_lcf.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + + igraph_t g1, g2; + igraph_vector_int_t edges; + igraph_bool_t iso; + + /* Initialize the library. */ + igraph_setup(); + + // Heawood graph through LCF notation: [5, -5]^7 + // The number of vertices is normally the number of shifts + // multiplied by the number of repeats, in this case 2*7 = 14. + igraph_lcf_small(&g1, + /* n */ 14, + /* shifts */ 5, -5, + /* repeats */ 7, + 0); + + printf("edges:\n"); + igraph_vector_int_init(&edges, 0); + igraph_get_edgelist(&g1, &edges, false); + igraph_vector_int_print(&edges); + igraph_vector_int_destroy(&edges); + + // Built-in Heawood graph: + igraph_famous(&g2, "Heawood"); + igraph_isomorphic(&g1, &g2, &iso); + printf("isomorphic: %s\n", iso ? "true" : "false"); + igraph_destroy(&g2); + + igraph_destroy(&g1); + + return 0; +} diff --git a/examples/simple/igraph_lcf.out b/examples/simple/igraph_lcf.out new file mode 100644 index 0000000..07ba576 --- /dev/null +++ b/examples/simple/igraph_lcf.out @@ -0,0 +1,3 @@ +edges: +0 1 0 5 0 13 1 2 1 10 2 3 2 7 3 4 3 12 4 5 4 9 5 6 6 7 6 11 7 8 8 9 8 13 9 10 10 11 11 12 12 13 +isomorphic: true diff --git a/examples/simple/igraph_list_triangles.c b/examples/simple/igraph_list_triangles.c new file mode 100644 index 0000000..4b8e4d8 --- /dev/null +++ b/examples/simple/igraph_list_triangles.c @@ -0,0 +1,41 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_t v; + + /* Initialize the library. */ + igraph_setup(); + + igraph_full(&g, 5, 0, IGRAPH_NO_LOOPS); + + printf("Triangles in a full graph of 5 vertices:\n"); + igraph_vector_int_init(&v, 0); + igraph_list_triangles(&g, &v); + + const igraph_matrix_int_t result = igraph_matrix_int_view_from_vector(&v, /* nrow = */ 3); + igraph_matrix_int_print(&result); + + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_list_triangles.out b/examples/simple/igraph_list_triangles.out new file mode 100644 index 0000000..446331c --- /dev/null +++ b/examples/simple/igraph_list_triangles.out @@ -0,0 +1,4 @@ +Triangles in a full graph of 5 vertices: +0 0 0 0 0 0 1 1 1 2 +1 1 1 2 2 3 2 2 3 3 +4 2 3 4 3 4 4 3 4 4 diff --git a/examples/simple/igraph_maximal_cliques.c b/examples/simple/igraph_maximal_cliques.c new file mode 100644 index 0000000..a30614a --- /dev/null +++ b/examples/simple/igraph_maximal_cliques.c @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_list_t cliques; + igraph_int_t no; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 9, IGRAPH_UNDIRECTED, + 0,1, 0,2, 1,2, 2,3, 3,4, 3, 5, 4,5, 5,6, 6,7, -1); + igraph_vector_int_list_init(&cliques, 0); + igraph_maximal_cliques(&g, &cliques, /*min_size=*/ IGRAPH_UNLIMITED, + /*max_size=*/ IGRAPH_UNLIMITED, + /*max_results=*/ IGRAPH_UNLIMITED); + igraph_maximal_cliques_count(&g, &no, /*min_size=*/ IGRAPH_UNLIMITED, + /*max_size=*/ IGRAPH_UNLIMITED); + + if (no != igraph_vector_int_list_size(&cliques)) { + return 1; + } + + for (igraph_int_t i = 0; i < igraph_vector_int_list_size(&cliques); i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&cliques, i); + igraph_vector_int_print(v); + } + + igraph_vector_int_list_destroy(&cliques); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_maximum_bipartite_matching.c b/examples/simple/igraph_maximum_bipartite_matching.c new file mode 100644 index 0000000..d7ad8da --- /dev/null +++ b/examples/simple/igraph_maximum_bipartite_matching.c @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2012 Tamas Nepusz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + /* Test graph from the LEDA tutorial: + * http://www.leda-tutorial.org/en/unofficial/ch05s03s05.html + */ + igraph_t graph; + igraph_vector_bool_t types; + igraph_vector_int_t matching; + igraph_int_t matching_size; + igraph_real_t matching_weight; + igraph_bool_t is_matching; + int i; + + igraph_small(&graph, 0, 0, + 0, 8, 0, 12, 0, 14, + 1, 9, 1, 10, 1, 13, + 2, 8, 2, 9, + 3, 10, 3, 11, 3, 13, + 4, 9, 4, 14, + 5, 14, + 6, 9, 6, 14, + 7, 8, 7, 12, 7, 14 + , -1); + igraph_vector_bool_init(&types, 15); + for (i = 0; i < 15; i++) { + VECTOR(types)[i] = (i >= 8); + } + igraph_vector_int_init(&matching, 0); + + igraph_maximum_bipartite_matching(&graph, &types, &matching_size, + &matching_weight, &matching, 0, 0); + if (matching_size != 6) { + printf("matching_size is %" IGRAPH_PRId ", expected: 6\n", matching_size); + return 1; + } + if (matching_weight != 6) { + printf("matching_weight is %" IGRAPH_PRId ", expected: 6\n", (igraph_int_t) matching_weight); + return 2; + } + igraph_is_maximal_matching(&graph, &types, &matching, &is_matching); + if (!is_matching) { + printf("not a matching: "); + igraph_vector_int_print(&matching); + return 3; + } + + igraph_vector_int_destroy(&matching); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_mincut.c b/examples/simple/igraph_mincut.c new file mode 100644 index 0000000..e65e7c5 --- /dev/null +++ b/examples/simple/igraph_mincut.c @@ -0,0 +1,114 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int print_mincut(const igraph_t *graph, igraph_real_t value, + const igraph_vector_int_t *partition, + const igraph_vector_int_t *partition2, + const igraph_vector_int_t *cut, + const igraph_vector_t *capacity) { + + igraph_int_t i, nc = igraph_vector_int_size(cut); + igraph_bool_t directed = igraph_is_directed(graph); + + printf("mincut value: %g\n", (double) value); + printf("first partition: "); + igraph_vector_int_print(partition); + printf("second partition: "); + igraph_vector_int_print(partition2); + printf("edges in the cut: "); + for (i = 0; i < nc; i++) { + igraph_int_t edge = VECTOR(*cut)[i]; + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO (graph, edge); + if (!directed && from > to) { + igraph_int_t tmp = from; + from = to; + to = tmp; + } + printf("%" IGRAPH_PRId "-%" IGRAPH_PRId " (%g), ", from, to, VECTOR(*capacity)[edge]); + } + printf("\n"); + + return 0; +} + +int main(void) { + + igraph_t g; + igraph_vector_int_t partition, partition2, cut; + igraph_vector_t weights; + igraph_real_t value; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&partition, 0); + igraph_vector_int_init(&partition2, 0); + igraph_vector_int_init(&cut, 0); + + /* -------------------------------------------- */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 4, 1, 2, 1, 4, 1, 5, 2, 3, 2, 6, 3, 6, 3, 7, 4, 5, 5, 6, 6, 7, + -1); + igraph_vector_init_int_end(&weights, -1, 2, 3, 3, 2, 2, 4, 2, 2, 2, 3, 1, 3, -1); + + igraph_mincut(&g, &value, &partition, &partition2, &cut, &weights); + print_mincut(&g, value, &partition, &partition2, &cut, &weights); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* -------------------------------------------- */ + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 0, 5, 5, 4, 4, 3, 3, 0, -1); + igraph_vector_init_int_end(&weights, -1, 3, 1, 2, 10, 1, 3, 2, -1); + + igraph_mincut(&g, &value, &partition, &partition2, &cut, &weights); + print_mincut(&g, value, &partition, &partition2, &cut, &weights); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* -------------------------------------------- */ + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 4, 3, 3, 2, 2, 1, 1, 0, + -1); + igraph_vector_init_int_end(&weights, -1, 1, 1, 1, 1, -1); + igraph_mincut(&g, &value, &partition, &partition2, &cut, &weights); + print_mincut(&g, value, &partition, &partition2, &cut, &weights); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* -------------------------------------------- */ + + igraph_vector_int_destroy(&cut); + igraph_vector_int_destroy(&partition2); + igraph_vector_int_destroy(&partition); + + return 0; +} diff --git a/examples/simple/igraph_mincut.out b/examples/simple/igraph_mincut.out new file mode 100644 index 0000000..4308766 --- /dev/null +++ b/examples/simple/igraph_mincut.out @@ -0,0 +1,12 @@ +mincut value: 4 +first partition: 2 3 6 7 +second partition: 0 1 4 5 +edges in the cut: 1-2 (3), 5-6 (1), +mincut value: 1 +first partition: 1 +second partition: 0 2 3 4 5 +edges in the cut: 1-2 (1), +mincut value: 0 +first partition: 0 +second partition: 1 2 3 4 +edges in the cut: diff --git a/examples/simple/igraph_minimal_separators.c b/examples/simple/igraph_minimal_separators.c new file mode 100644 index 0000000..6f97545 --- /dev/null +++ b/examples/simple/igraph_minimal_separators.c @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t separators; + igraph_int_t i, n; + + /* Initialize the library. */ + igraph_setup(); + + igraph_famous(&graph, "zachary"); + igraph_vector_int_list_init(&separators, 0); + igraph_all_minimal_st_separators(&graph, &separators); + + n = igraph_vector_int_list_size(&separators); + for (i = 0; i < n; i++) { + igraph_bool_t res; + igraph_vector_int_t *sep = igraph_vector_int_list_get_ptr(&separators, i); + + igraph_is_separator(&graph, igraph_vss_vector(sep), &res); + if (!res) { + printf("Vertex set %" IGRAPH_PRId " is not a separator!\n", i); + igraph_vector_int_print(sep); + return 1; + } + } + + igraph_destroy(&graph); + igraph_vector_int_list_destroy(&separators); + + return 0; +} diff --git a/examples/simple/igraph_minimum_size_separators.c b/examples/simple/igraph_minimum_size_separators.c new file mode 100644 index 0000000..75fbb78 --- /dev/null +++ b/examples/simple/igraph_minimum_size_separators.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_setup(); + igraph_t g; + igraph_vector_int_list_t sep; + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, + -1); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + + for (igraph_int_t i = 0; i < igraph_vector_int_list_size(&sep); i++) { + igraph_vector_int_t* v = igraph_vector_int_list_get_ptr(&sep, i); + igraph_vector_int_print(v); + } + + igraph_vector_int_list_destroy(&sep); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_minimum_spanning_tree.c b/examples/simple/igraph_minimum_spanning_tree.c new file mode 100644 index 0000000..5e2dd7b --- /dev/null +++ b/examples/simple/igraph_minimum_spanning_tree.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_vector_t eb; + igraph_vector_int_t edges; + + /* Initialize the library. */ + igraph_setup(); + + /* Create the vector where the tree edges will be stored. */ + igraph_vector_int_init(&edges, 0); + + /* Create the Frucht graph */ + igraph_famous(&graph, "Frucht"); + + /* Compute the edge betweenness. */ + igraph_vector_init(&eb, igraph_ecount(&graph)); + igraph_edge_betweenness(&graph, /*weights=*/ NULL, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + + /* Use Prim's algorithm to compute the edges that belong to the minimum weight + * spanning tree, using edge betweenness values as edge weights. */ + igraph_minimum_spanning_tree(&graph, &edges, &eb, IGRAPH_MST_PRIM); + printf("Minimum spanning tree edges:\n"); + igraph_vector_int_print(&edges); + + /* A maximum spanning tree can be computed by first negating the weights. */ + igraph_vector_scale(&eb, -1); + + /* Compute and output the edges that belong to the maximum weight spanning tree, + * letting igraph automatically select the most suitable algorithm. */ + igraph_minimum_spanning_tree(&graph, &edges, &eb, IGRAPH_MST_AUTOMATIC); + printf("\nMaximum spanning tree edges:\n"); + igraph_vector_int_print(&edges); + + igraph_real_t total_tree_weight = 0; + igraph_int_t n = igraph_vector_int_size(&edges); + for (igraph_int_t i=0; i < n; i++) { + total_tree_weight += -VECTOR(eb)[ VECTOR(edges)[i] ]; + } + printf("\nTotal maximum spanning tree weight: %g\n", total_tree_weight); + + /* Clean up */ + igraph_destroy(&graph); + igraph_vector_destroy(&eb); + igraph_vector_int_destroy(&edges); + + return 0; +} diff --git a/examples/simple/igraph_minimum_spanning_tree.out b/examples/simple/igraph_minimum_spanning_tree.out new file mode 100644 index 0000000..d723d39 --- /dev/null +++ b/examples/simple/igraph_minimum_spanning_tree.out @@ -0,0 +1,7 @@ +Minimum spanning tree edges: +2 17 6 12 0 3 8 7 13 14 16 + +Maximum spanning tree edges: +11 10 0 9 1 13 17 7 15 4 2 + +Total maximum spanning tree weight: 102.5 diff --git a/examples/simple/igraph_motifs_randesu.c b/examples/simple/igraph_motifs_randesu.c new file mode 100644 index 0000000..93155ca --- /dev/null +++ b/examples/simple/igraph_motifs_randesu.c @@ -0,0 +1,55 @@ + +#include + +/* This is a callback function suitable for use with igraph_motifs_randesu_callback(). + * It prints each motif it is calld with. */ +igraph_error_t print_motif(const igraph_t *graph, const igraph_vector_int_t *vids, + igraph_int_t isoclass, void* extra) { + printf("Found isoclass %2" IGRAPH_PRId ": ", isoclass); + igraph_vector_int_print(vids); + return IGRAPH_SUCCESS; /* Return 'IGRAPH_SUCCESS': do not interrupt the search. */ +} + +int main(void) { + + igraph_t graph; + igraph_vector_t hist; + + /* Initialize the library. */ + igraph_setup(); + + /* Compute the 4-motif distritbuion in Zachary's karate club network. */ + + igraph_famous(&graph, "Zachary"); + igraph_vector_init(&hist, 0); + + igraph_motifs_randesu(&graph, &hist, 4, NULL); + + /* Compute the total number of motifs (connected 4-vertex subgraphs) + * so that we can print the normalized distribution. */ + igraph_real_t sum = 0.0; + igraph_int_t n = igraph_vector_size(&hist); + for (igraph_int_t i=0; i < n; i++) { + if (!isnan(VECTOR(hist)[i])) { + sum += VECTOR(hist)[i]; + } + } + printf("4-motif distribution:\n"); + for (igraph_int_t i=0; i < n; i++) { + /* Print NaN values in a platform-independent manner: */ + igraph_real_printf(VECTOR(hist)[i] / sum); + printf(" "); + } + printf("\n\n"); + + igraph_vector_destroy(&hist); + igraph_destroy(&graph); + + /* Identify the vertices of each three-motif in a small Kautz graph. */ + + igraph_kautz(&graph, 2, 1); + igraph_motifs_randesu_callback(&graph, 3, NULL, &print_motif, NULL); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_motifs_randesu.out b/examples/simple/igraph_motifs_randesu.out new file mode 100644 index 0000000..bff9d2b --- /dev/null +++ b/examples/simple/igraph_motifs_randesu.out @@ -0,0 +1,17 @@ +4-motif distribution: +NaN NaN NaN NaN 0.464664 NaN 0.288193 0.191282 0.0152349 0.0359712 0.0046551 + +Found isoclass 5: 0 4 2 +Found isoclass 11: 0 4 3 +Found isoclass 9: 0 4 1 +Found isoclass 9: 0 3 2 +Found isoclass 5: 0 3 5 +Found isoclass 9: 0 2 1 +Found isoclass 5: 0 2 5 +Found isoclass 11: 1 5 2 +Found isoclass 9: 1 5 4 +Found isoclass 5: 1 5 3 +Found isoclass 5: 1 4 2 +Found isoclass 5: 1 4 3 +Found isoclass 9: 2 5 3 +Found isoclass 9: 3 5 4 diff --git a/examples/simple/igraph_neighbors.c b/examples/simple/igraph_neighbors.c new file mode 100644 index 0000000..8fa3574 --- /dev/null +++ b/examples/simple/igraph_neighbors.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_int_t v; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&v, 0); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 2,2, -1); + + igraph_neighbors(&g, &v, 2, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + igraph_vector_int_sort(&v); + igraph_vector_int_print(&v); + + igraph_neighbors(&g, &v, 2, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + igraph_vector_int_sort(&v); + igraph_vector_int_print(&v); + + igraph_neighbors(&g, &v, 2, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + igraph_vector_int_sort(&v); + igraph_vector_int_print(&v); + + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + return 0; +} diff --git a/examples/simple/igraph_neighbors.out b/examples/simple/igraph_neighbors.out new file mode 100644 index 0000000..cba5b77 --- /dev/null +++ b/examples/simple/igraph_neighbors.out @@ -0,0 +1,3 @@ +2 3 +1 2 +1 2 2 3 diff --git a/examples/simple/igraph_pagerank.c b/examples/simple/igraph_pagerank.c new file mode 100644 index 0000000..bcc18bd --- /dev/null +++ b/examples/simple/igraph_pagerank.c @@ -0,0 +1,39 @@ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_t pagerank; + igraph_real_t value; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed graph */ + igraph_kautz(&graph, 2, 3); + + /* Initialize the vector where the results will be stored */ + igraph_vector_init(&pagerank, 0); + + igraph_pagerank(&graph, /* weights */ NULL, + &pagerank, &value, + /* damping */ 0.85, IGRAPH_DIRECTED, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, + NULL /* not needed with PRPACK method */); + + /* Check that the eigenvalue is 1, as expected. */ + if (fabs(value - 1.0) > 32*DBL_EPSILON) { + fprintf(stderr, "PageRank failed to converge.\n"); + return 1; + } + + /* Output the result */ + igraph_vector_print(&pagerank); + + /* Destroy data structure when no longer needed */ + igraph_vector_destroy(&pagerank); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_power_law_fit.c b/examples/simple/igraph_power_law_fit.c new file mode 100644 index 0000000..ecb253f --- /dev/null +++ b/examples/simple/igraph_power_law_fit.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_vector_t degree; + igraph_plfit_result_t model; + + /* Initialize the library. */ + igraph_setup(); + + /* Seed random number generator to ensure reproducibility. */ + igraph_rng_seed(igraph_rng_default(), 42); + + /* Generate a BA network; degree distribution is supposed to be a power-law + * if the graph is large enough */ + igraph_barabasi_game( + &g, 10000, /*power=*/ 1, /*m=*/ 2, + /* outseq= */ 0, /* outpref= */ 0, /*A=*/ 1, + IGRAPH_UNDIRECTED, IGRAPH_BARABASI_BAG, + /*start_from=*/ 0 + ); + + /* Get the vertex degrees. We use igraph_strength() because it stores its + * result in an igraph_vector_t */ + igraph_vector_init(°ree, 0); + igraph_strength(&g, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS, 0); + + /* Fit a power-law to the degrees */ + igraph_power_law_fit( + °ree, &model, /* xmin = */ -1, + /* force_continuous = */ 0 + ); + + /* If you also need a p-value: */ + /* igraph_plfit_result_calculate_p_value(&model, &p, 0.001); */ + + printf("alpha = %.5f\n", model.alpha); + printf("xmin = %.5f\n", model.xmin); + printf("log-likelihood = %.5f\n", model.L); + + igraph_vector_destroy(°ree); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_power_law_fit.out b/examples/simple/igraph_power_law_fit.out new file mode 100644 index 0000000..d24ecd2 --- /dev/null +++ b/examples/simple/igraph_power_law_fit.out @@ -0,0 +1,3 @@ +alpha = 3.04393 +xmin = 7.00000 +log-likelihood = -3103.87560 diff --git a/examples/simple/igraph_radius.c b/examples/simple/igraph_radius.c new file mode 100644 index 0000000..157d8b5 --- /dev/null +++ b/examples/simple/igraph_radius.c @@ -0,0 +1,56 @@ +/* vim:set ts=4 sts=4 sw=4 et: */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_real_t radius; + + /* Initialize the library. */ + igraph_setup(); + + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_radius(&g, NULL, &radius, IGRAPH_OUT); + if (radius != 1) { + return 1; + } + igraph_destroy(&g); + + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_radius(&g, NULL, &radius, IGRAPH_ALL); + if (radius != 1) { + return 2; + } + igraph_destroy(&g); + + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_radius(&g, NULL, &radius, IGRAPH_OUT); + if (radius != 0) { + return 3; + } + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_random_sample.c b/examples/simple/igraph_random_sample.c new file mode 100644 index 0000000..353b41a --- /dev/null +++ b/examples/simple/igraph_random_sample.c @@ -0,0 +1,30 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_vector_int_t V; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&V, 0); + igraph_random_sample(&V, 0, 100, 5); + igraph_vector_int_print(&V); + igraph_vector_int_destroy(&V); +} diff --git a/examples/simple/igraph_read_graph_dl.c b/examples/simple/igraph_read_graph_dl.c new file mode 100644 index 0000000..d946bb0 --- /dev/null +++ b/examples/simple/igraph_read_graph_dl.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +int main(void) { + + const char *files[] = { "fullmatrix1.dl", "fullmatrix2.dl", + "fullmatrix3.dl", "fullmatrix4.dl", + "edgelist1.dl", "edgelist2.dl", "edgelist3.dl", + "edgelist4.dl", "edgelist5.dl", "edgelist6.dl", + "edgelist7.dl", "nodelist1.dl", "nodelist2.dl" }; + igraph_t graph; + FILE *infile; + + /* Initialize the library. */ + igraph_setup(); + + /* Turn on attribute handling. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); i++) { + printf("Doing %s\n", files[i]); + infile = fopen(files[i], "r"); + if (!infile) { + printf("Cannot open file: %s\n", files[i]); + abort(); + } + igraph_read_graph_dl(&graph, infile, IGRAPH_DIRECTED); + fclose(infile); + igraph_write_graph_edgelist(&graph, stdout); + igraph_destroy(&graph); + } + + if (IGRAPH_FINALLY_STACK_SIZE() != 0) { + return 1; + } + + return 0; +} diff --git a/examples/simple/igraph_read_graph_dl.out b/examples/simple/igraph_read_graph_dl.out new file mode 100644 index 0000000..b62cc2f --- /dev/null +++ b/examples/simple/igraph_read_graph_dl.out @@ -0,0 +1,104 @@ +Doing fullmatrix1.dl +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +2 0 +2 1 +2 4 +3 0 +4 0 +4 2 +Doing fullmatrix2.dl +0 1 +0 2 +0 3 +1 0 +1 4 +2 0 +2 3 +3 0 +3 2 +3 4 +4 1 +4 3 +Doing fullmatrix3.dl +0 1 +0 2 +0 3 +1 0 +1 4 +2 0 +2 3 +3 0 +3 2 +3 4 +4 1 +4 3 +Doing fullmatrix4.dl +0 1 +0 2 +0 3 +1 0 +1 4 +2 0 +2 3 +3 0 +3 2 +3 4 +4 1 +4 3 +Doing edgelist1.dl +0 1 +0 2 +1 2 +2 0 +3 2 +Doing edgelist2.dl +0 1 +0 2 +1 2 +3 0 +4 2 +Doing edgelist3.dl +0 1 +0 2 +1 2 +3 0 +4 2 +Doing edgelist4.dl +0 1 +0 2 +1 2 +2 0 +3 2 +Doing edgelist5.dl +0 1 +0 2 +1 2 +3 0 +4 2 +Doing edgelist6.dl +0 1 +0 2 +1 2 +3 0 +4 2 +Doing edgelist7.dl +0 1 +1 2 +1 3 +Doing nodelist1.dl +0 1 +0 2 +1 2 +2 0 +3 2 +Doing nodelist2.dl +0 1 +0 2 +1 2 +3 0 +4 2 diff --git a/examples/simple/igraph_read_graph_graphdb.c b/examples/simple/igraph_read_graph_graphdb.c new file mode 100644 index 0000000..8c428d1 --- /dev/null +++ b/examples/simple/igraph_read_graph_graphdb.c @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + FILE *input; + + /* Initialize the library. */ + igraph_setup(); + + input = fopen("iso_b03_m1000.A00", "rb"); + if (!input) { + return 1; + } + igraph_read_graph_graphdb(&g, input, IGRAPH_DIRECTED); + fclose(input); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_read_graph_graphdb.out b/examples/simple/igraph_read_graph_graphdb.out new file mode 100644 index 0000000..ec9aa8e --- /dev/null +++ b/examples/simple/igraph_read_graph_graphdb.out @@ -0,0 +1,1500 @@ +0 723 +1 37 +1 951 +3 310 +3 439 +4 64 +4 873 +6 875 +7 617 +8 0 +8 429 +9 711 +10 181 +10 184 +11 257 +11 262 +11 365 +12 400 +12 781 +13 61 +13 482 +13 963 +15 68 +15 567 +16 649 +17 93 +17 355 +18 32 +18 503 +19 53 +19 360 +19 646 +20 71 +20 220 +21 900 +21 909 +22 552 +22 778 +25 623 +25 731 +27 232 +28 293 +28 378 +29 179 +30 542 +30 713 +31 535 +32 312 +33 246 +33 828 +33 868 +34 230 +35 382 +35 519 +36 321 +37 767 +38 216 +38 658 +39 400 +39 889 +40 352 +40 941 +41 60 +41 540 +42 680 +44 280 +44 734 +45 520 +46 302 +46 940 +46 959 +47 928 +49 149 +50 29 +50 216 +50 658 +52 758 +53 455 +53 514 +54 500 +55 51 +55 506 +56 647 +57 352 +57 821 +58 945 +58 953 +59 320 +59 393 +60 157 +62 200 +62 669 +64 886 +65 69 +65 638 +66 390 +66 537 +67 981 +69 42 +70 7 +70 759 +70 991 +71 244 +72 137 +72 450 +72 932 +73 980 +74 819 +75 508 +75 973 +76 295 +76 573 +76 838 +78 778 +79 290 +79 627 +80 981 +81 712 +82 907 +83 17 +83 109 +83 125 +85 97 +86 239 +86 390 +86 850 +87 185 +87 260 +87 652 +88 24 +88 522 +88 614 +89 682 +90 302 +92 164 +95 465 +95 601 +96 23 +96 822 +97 895 +98 195 +98 241 +98 899 +99 367 +99 392 +99 749 +101 811 +102 696 +103 423 +104 55 +104 147 +104 879 +105 68 +105 161 +105 931 +106 318 +106 379 +107 20 +107 816 +108 240 +108 718 +108 883 +109 355 +110 130 +110 337 +111 89 +111 499 +112 91 +112 236 +112 835 +113 217 +113 668 +113 675 +114 199 +114 453 +114 830 +115 607 +116 854 +117 651 +117 710 +118 100 +118 511 +119 347 +119 812 +120 490 +121 521 +121 632 +121 982 +122 16 +122 130 +124 251 +124 644 +124 986 +126 26 +126 727 +126 918 +127 437 +128 194 +128 327 +129 595 +129 639 +131 488 +131 708 +132 60 +132 538 +133 5 +133 203 +134 179 +134 258 +134 913 +135 271 +135 842 +136 696 +136 858 +136 937 +137 273 +138 930 +139 432 +139 654 +139 959 +140 329 +141 627 +141 923 +144 715 +144 939 +145 513 +145 967 +147 2 +147 51 +148 704 +148 746 +150 944 +151 358 +151 611 +152 460 +154 77 +155 190 +155 953 +156 441 +156 962 +156 997 +157 833 +158 508 +158 973 +159 4 +159 423 +162 495 +162 995 +163 173 +164 146 +165 142 +165 187 +165 928 +166 676 +166 804 +167 177 +167 377 +168 175 +168 410 +170 917 +171 473 +172 898 +173 880 +174 177 +175 579 +175 779 +176 243 +176 500 +178 24 +178 84 +180 201 +180 341 +180 813 +181 876 +182 106 +183 202 +185 417 +185 894 +187 47 +188 263 +188 275 +188 786 +189 460 +189 683 +190 58 +190 576 +191 116 +191 324 +192 424 +192 464 +192 670 +193 614 +193 697 +194 829 +195 370 +196 132 +196 952 +197 49 +197 511 +198 506 +198 633 +200 158 +200 775 +201 433 +201 780 +202 394 +202 632 +203 267 +203 855 +204 214 +204 805 +205 990 +206 577 +206 806 +207 705 +208 804 +209 700 +210 764 +211 210 +212 560 +212 575 +212 899 +213 817 +214 110 +215 62 +215 556 +216 664 +217 471 +219 381 +219 645 +219 653 +221 103 +221 409 +221 873 +222 123 +222 420 +223 838 +224 371 +225 479 +225 840 +226 513 +226 761 +226 967 +228 128 +228 346 +228 489 +229 218 +229 459 +230 218 +230 459 +231 135 +231 845 +232 943 +233 391 +233 446 +234 275 +234 786 +235 384 +235 993 +236 635 +236 882 +237 163 +237 824 +238 138 +238 808 +240 259 +241 350 +241 543 +242 238 +242 272 +243 54 +243 319 +244 220 +244 761 +245 628 +245 872 +246 341 +247 273 +248 263 +248 691 +249 752 +250 161 +251 227 +252 565 +253 97 +253 251 +253 414 +254 225 +254 789 +255 407 +255 451 +256 183 +257 457 +259 590 +260 672 +260 894 +261 623 +262 390 +263 527 +264 684 +265 821 +266 507 +266 815 +267 5 +267 307 +268 111 +268 856 +269 616 +270 261 +270 693 +270 742 +271 845 +272 808 +272 935 +274 45 +274 998 +276 16 +276 130 +277 224 +278 572 +279 608 +279 630 +281 207 +282 440 +283 34 +283 117 +285 674 +286 952 +287 73 +287 358 +288 737 +289 933 +290 174 +291 758 +292 283 +293 434 +293 655 +294 38 +296 300 +297 322 +297 892 +298 154 +299 515 +299 864 +300 960 +301 222 +303 127 +303 288 +303 978 +304 314 +304 557 +305 936 +306 9 +306 94 +307 301 +307 420 +309 997 +310 856 +311 634 +312 921 +313 865 +314 450 +315 186 +315 249 +316 199 +317 659 +318 28 +318 434 +319 578 +319 849 +321 205 +322 142 +322 187 +323 618 +323 774 +324 211 +325 699 +325 753 +325 758 +326 399 +327 489 +328 170 +328 597 +329 621 +330 887 +331 64 +331 167 +332 240 +332 883 +333 122 +333 155 +333 649 +334 329 +335 561 +335 608 +335 985 +336 825 +337 276 +338 51 +338 506 +338 891 +339 446 +339 536 +340 107 +340 630 +342 48 +342 61 +343 84 +343 406 +343 533 +344 696 +345 936 +346 920 +348 502 +348 985 +350 575 +351 559 +351 799 +352 265 +353 677 +353 948 +354 718 +356 653 +356 655 +357 661 +359 138 +359 242 +360 455 +362 82 +362 486 +363 285 +363 345 +363 925 +364 289 +364 686 +364 898 +366 504 +366 866 +366 885 +368 92 +369 211 +370 628 +370 897 +371 868 +372 6 +372 993 +373 357 +373 799 +374 68 +374 161 +375 213 +375 636 +376 977 +377 620 +378 653 +379 277 +380 24 +380 84 +380 533 +382 186 +382 320 +383 341 +383 813 +384 372 +384 875 +385 626 +386 127 +386 288 +386 621 +387 103 +387 409 +388 481 +388 870 +389 153 +389 388 +391 339 +393 151 +393 287 +394 481 +395 280 +396 208 +396 890 +397 408 +398 0 +398 846 +399 264 +399 974 +401 74 +402 898 +403 229 +404 160 +404 412 +405 404 +405 915 +406 321 +407 869 +407 948 +411 518 +411 541 +411 994 +412 818 +413 63 +414 85 +414 227 +415 143 +415 580 +415 764 +416 96 +416 385 +417 347 +417 812 +418 77 +418 298 +418 619 +419 266 +420 5 +421 143 +422 514 +424 633 +426 171 +426 785 +427 40 +427 52 +427 265 +430 512 +431 474 +431 881 +432 90 +434 182 +435 249 +435 487 +436 129 +436 751 +437 334 +437 621 +438 444 +439 466 +439 983 +440 120 +440 146 +441 295 +441 309 +442 419 +442 507 +442 867 +443 848 +444 381 +444 572 +445 324 +445 369 +445 954 +447 397 +447 798 +448 182 +448 224 +448 379 +449 170 +450 273 +452 629 +452 656 +452 933 +453 488 +453 708 +454 80 +454 472 +454 765 +455 730 +456 56 +457 89 +457 499 +458 22 +458 78 +459 839 +461 360 +461 730 +462 137 +462 247 +463 101 +463 368 +464 528 +465 747 +466 391 +466 531 +467 304 +467 693 +468 181 +468 184 +468 826 +469 6 +469 498 +470 523 +470 878 +471 675 +471 995 +472 776 +472 919 +473 740 +474 347 +474 666 +475 397 +475 798 +476 401 +476 518 +477 532 +477 981 +478 172 +478 402 +480 754 +481 153 +482 342 +482 610 +483 152 +483 612 +483 741 +484 18 +484 724 +484 796 +485 892 +485 938 +486 323 +486 907 +487 315 +490 282 +491 61 +491 902 +492 421 +492 550 +493 330 +493 701 +494 827 +495 996 +496 750 +496 904 +497 745 +497 950 +500 629 +501 493 +501 871 +502 239 +503 724 +503 905 +504 308 +504 430 +505 346 +505 489 +507 725 +508 775 +509 331 +509 377 +509 886 +510 91 +510 150 +511 149 +512 308 +512 549 +514 646 +515 94 +515 801 +516 465 +516 601 +517 271 +517 361 +518 906 +519 320 +521 256 +522 193 +523 37 +524 769 +524 809 +524 969 +525 969 +526 23 +526 416 +527 275 +527 691 +528 424 +529 100 +529 428 +529 494 +530 389 +531 233 +531 983 +532 67 +532 485 +533 984 +534 21 +534 460 +534 683 +536 462 +537 262 +537 365 +538 157 +539 27 +540 145 +540 662 +541 866 +542 714 +543 195 +544 220 +544 967 +545 35 +545 186 +545 487 +546 635 +546 732 +547 387 +547 681 +547 900 +548 141 +548 988 +549 359 +549 930 +550 39 +551 208 +551 890 +552 823 +552 939 +553 269 +554 362 +554 478 +555 464 +555 667 +556 928 +557 693 +558 565 +560 349 +560 525 +561 279 +562 298 +562 974 +563 559 +563 799 +564 517 +565 859 +566 648 +567 759 +568 539 +568 660 +569 566 +569 602 +569 659 +570 120 +570 146 +571 7 +571 398 +572 877 +573 223 +574 9 +575 525 +576 848 +578 654 +579 410 +579 908 +580 421 +581 741 +582 739 +582 943 +583 305 +584 539 +584 660 +584 726 +585 206 +586 610 +587 204 +587 687 +588 376 +588 480 +588 859 +589 282 +589 425 +589 690 +590 354 +591 78 +591 330 +592 947 +593 578 +593 849 +593 926 +594 1 +594 523 +595 422 +595 646 +597 449 +597 687 +598 210 +598 369 +599 194 +599 367 +600 231 +600 305 +601 688 +602 317 +603 618 +604 596 +604 927 +605 168 +605 694 +605 745 +606 43 +606 344 +607 698 +607 737 +608 348 +609 336 +610 48 +611 59 +611 519 +612 581 +612 862 +613 173 +613 237 +613 609 +614 178 +615 577 +615 851 +616 306 +616 574 +617 863 +619 562 +619 684 +620 177 +620 290 +622 590 +622 732 +624 902 +624 963 +624 988 +625 82 +625 172 +625 554 +626 596 +627 174 +628 965 +629 976 +630 782 +631 559 +631 837 +632 256 +633 670 +634 827 +635 622 +637 692 +638 42 +639 422 +640 313 +640 832 +640 982 +641 280 +641 505 +642 456 +642 637 +642 647 +643 522 +644 227 +644 698 +645 356 +648 45 +648 966 +649 953 +650 73 +650 250 +651 34 +651 218 +652 443 +652 672 +655 378 +656 289 +656 976 +657 302 +657 395 +659 840 +660 643 +661 910 +662 513 +663 469 +663 875 +663 884 +664 26 +664 294 +665 133 +665 603 +666 119 +666 881 +667 435 +667 752 +668 413 +668 802 +669 47 +669 556 +670 891 +671 470 +671 637 +673 255 +673 677 +673 948 +674 209 +674 345 +675 413 +676 14 +676 831 +678 392 +678 749 +679 311 +679 964 +681 409 +682 268 +682 310 +685 376 +685 480 +685 795 +686 402 +687 695 +688 837 +689 535 +689 847 +690 490 +690 716 +691 75 +692 456 +692 946 +694 410 +694 497 +695 328 +697 568 +697 643 +699 140 +699 334 +700 123 +700 285 +701 591 +702 91 +702 150 +702 835 +703 101 +704 615 +704 999 +705 433 +705 780 +706 400 +706 550 +706 781 +707 10 +707 869 +707 888 +708 797 +709 30 +709 43 +710 292 +710 729 +711 94 +711 299 +712 277 +712 371 +713 43 +714 743 +715 821 +715 922 +716 403 +716 425 +717 553 +718 259 +719 26 +719 294 +719 727 +720 154 +720 510 +721 902 +721 988 +722 340 +722 782 +722 816 +723 429 +723 832 +724 968 +725 647 +725 815 +726 27 +726 793 +727 284 +728 164 +728 570 +728 958 +729 581 +729 862 +730 235 +731 261 +731 742 +732 354 +733 169 +733 281 +733 803 +734 395 +735 171 +735 596 +735 785 +736 118 +736 149 +736 844 +737 978 +738 528 +738 555 +739 196 +739 538 +740 426 +740 746 +742 557 +743 357 +744 542 +744 709 +744 792 +745 160 +747 592 +748 760 +748 784 +748 990 +749 599 +750 586 +751 153 +751 530 +752 738 +753 140 +753 291 +754 677 +754 795 +755 636 +755 678 +755 756 +756 375 +756 392 +757 179 +757 258 +759 617 +760 36 +760 205 +761 544 +762 246 +762 383 +763 566 +763 602 +763 966 +765 23 +766 14 +766 717 +766 971 +769 361 +769 564 +770 63 +770 463 +770 703 +771 214 +771 337 +772 269 +772 574 +772 971 +773 548 +773 721 +774 603 +774 855 +775 215 +776 80 +776 477 +777 317 +777 479 +778 701 +779 197 +780 281 +781 431 +782 561 +783 25 +783 258 +783 913 +784 85 +784 895 +785 604 +787 143 +787 598 +787 764 +788 199 +788 638 +788 957 +789 2 +789 479 +790 606 +790 713 +791 381 +791 438 +791 645 +792 714 +792 910 +793 232 +793 286 +794 223 +794 872 +795 353 +796 968 +797 234 +798 326 +800 473 +800 746 +801 274 +802 63 +802 703 +803 207 +803 871 +804 831 +805 771 +805 807 +807 587 +807 695 +808 125 +809 349 +809 361 +810 131 +810 786 +810 797 +811 368 +811 958 +813 433 +814 152 +814 189 +814 741 +815 56 +816 71 +817 74 +817 906 +818 93 +818 405 +819 213 +819 636 +820 296 +820 975 +822 385 +822 927 +823 144 +823 922 +824 296 +824 975 +825 768 +826 102 +827 934 +828 81 +828 762 +829 327 +829 367 +830 316 +830 488 +831 396 +832 865 +833 41 +833 662 +834 29 +834 658 +834 757 +835 882 +836 31 +836 585 +836 806 +837 95 +839 403 +839 425 +840 777 +841 313 +841 521 +842 583 +842 600 +843 428 +843 494 +843 634 +844 100 +844 428 +845 564 +846 8 +847 332 +847 857 +848 672 +849 54 +850 66 +850 502 +851 148 +851 800 +852 516 +852 747 +852 947 +853 336 +853 768 +854 284 +854 954 +855 665 +856 942 +857 535 +858 102 +860 419 +860 750 +861 408 +861 475 +861 592 +862 292 +863 571 +863 846 +864 801 +864 998 +865 429 +866 994 +867 496 +867 860 +868 81 +869 451 +870 183 +870 394 +871 169 +872 965 +873 423 +874 31 +874 689 +874 806 +876 826 +876 858 +877 438 +877 929 +878 594 +879 2 +879 254 +880 609 +880 825 +881 12 +882 546 +883 857 +884 498 +885 430 +885 541 +886 159 +887 169 +887 501 +888 184 +888 451 +889 492 +889 580 +890 278 +891 198 +892 67 +893 768 +893 956 +893 979 +894 812 +896 257 +896 365 +896 499 +897 543 +899 350 +901 14 +901 166 +901 717 +903 247 +903 446 +903 536 +904 48 +904 586 +905 32 +906 401 +907 618 +908 49 +908 779 +909 683 +909 912 +910 743 +911 123 +911 209 +911 301 +912 681 +912 900 +913 623 +914 162 +914 498 +915 93 +915 355 +916 245 +916 794 +916 838 +917 968 +918 116 +918 284 +919 526 +919 765 +920 44 +920 641 +921 300 +921 820 +922 57 +923 79 +923 773 +924 351 +924 631 +924 688 +925 583 +925 936 +926 432 +926 654 +927 626 +929 278 +929 551 +930 308 +931 15 +931 991 +932 314 +932 467 +933 686 +934 311 +934 964 +935 109 +935 125 +937 344 +937 790 +938 142 +938 297 +939 458 +940 657 +940 734 +941 52 +941 291 +942 3 +943 286 +944 77 +944 720 +945 443 +945 576 +946 671 +946 878 +947 408 +949 217 +949 495 +950 160 +950 412 +951 309 +951 767 +952 582 +954 191 +955 650 +956 252 +956 558 +957 65 +957 316 +958 92 +959 90 +960 312 +960 905 +961 264 +961 326 +961 447 +962 295 +962 573 +963 491 +964 680 +965 897 +966 520 +969 349 +970 69 +970 679 +970 680 +971 553 +972 436 +972 530 +972 639 +973 248 +974 684 +975 163 +976 176 +977 252 +977 859 +978 115 +979 558 +979 853 +980 358 +980 955 +982 841 +983 942 +984 36 +984 406 +985 239 +986 115 +986 698 +987 449 +987 796 +987 917 +989 250 +989 374 +989 955 +990 895 +991 567 +992 373 +992 563 +992 661 +993 461 +994 476 +995 949 +996 884 +996 914 +997 767 +998 520 +999 577 +999 585 diff --git a/examples/simple/igraph_read_graph_lgl-1.lgl b/examples/simple/igraph_read_graph_lgl-1.lgl new file mode 100644 index 0000000..d85f4dc --- /dev/null +++ b/examples/simple/igraph_read_graph_lgl-1.lgl @@ -0,0 +1,7 @@ +# foo +bar +foobar 5 +# foobar +bat +tab +# tab diff --git a/examples/simple/igraph_read_graph_lgl-2.lgl b/examples/simple/igraph_read_graph_lgl-2.lgl new file mode 100644 index 0000000..9834c88 --- /dev/null +++ b/examples/simple/igraph_read_graph_lgl-2.lgl @@ -0,0 +1,7 @@ +# foo +bar 1 +foobar 2 +# foobar +bat 10 +tab +# tab diff --git a/examples/simple/igraph_read_graph_lgl-3.lgl b/examples/simple/igraph_read_graph_lgl-3.lgl new file mode 100644 index 0000000..f293023 --- /dev/null +++ b/examples/simple/igraph_read_graph_lgl-3.lgl @@ -0,0 +1,4 @@ +# +1 +# 1 +2 diff --git a/examples/simple/igraph_read_graph_lgl.c b/examples/simple/igraph_read_graph_lgl.c new file mode 100644 index 0000000..43f46fc --- /dev/null +++ b/examples/simple/igraph_read_graph_lgl.c @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2005-2012 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t g; + FILE *input; + + /* Initialize the library. */ + igraph_setup(); + + /* Turn on attribute handling. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Without names and weights */ + input = fopen("igraph_read_graph_lgl-1.lgl", "r"); + if (!input) { + return 1; + } + igraph_read_graph_lgl(&g, input, 0, IGRAPH_ADD_WEIGHTS_NO, 1); + fclose(input); + if (!igraph_is_directed(&g)) { + return 2; + } + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + /* With names and weights */ + input = fopen("igraph_read_graph_lgl-2.lgl", "r"); + if (!input) { + return 3; + } + igraph_read_graph_lgl(&g, input, 0, IGRAPH_ADD_WEIGHTS_NO, 1); + fclose(input); + if (!igraph_is_directed(&g)) { + return 4; + } + igraph_write_graph_ncol(&g, stdout, 0, 0); + igraph_destroy(&g); + + /* Same graph, but forcing undirected mode */ + input = fopen("igraph_read_graph_lgl-2.lgl", "r"); + igraph_read_graph_lgl(&g, input, 0, IGRAPH_ADD_WEIGHTS_NO, 0); + fclose(input); + if (igraph_is_directed(&g)) { + return 5; + } + igraph_write_graph_ncol(&g, stdout, 0, 0); + igraph_destroy(&g); + + /* Erroneous LGL file (empty vertex name) */ + input = fopen("igraph_read_graph_lgl-3.lgl", "r"); + if (!input) { + return 6; + } + igraph_set_error_handler(igraph_error_handler_ignore); + if (igraph_read_graph_lgl(&g, input, 0, IGRAPH_ADD_WEIGHTS_NO, 1) != + IGRAPH_PARSEERROR) { + return 7; + } + fclose(input); + + return 0; +} diff --git a/examples/simple/igraph_read_graph_lgl.out b/examples/simple/igraph_read_graph_lgl.out new file mode 100644 index 0000000..036899b --- /dev/null +++ b/examples/simple/igraph_read_graph_lgl.out @@ -0,0 +1,12 @@ +0 1 +0 2 +2 3 +2 4 +0 1 +0 2 +2 3 +2 4 +0 1 +0 2 +2 3 +2 4 diff --git a/examples/simple/igraph_realize_degree_sequence.c b/examples/simple/igraph_realize_degree_sequence.c new file mode 100644 index 0000000..0b8d148 --- /dev/null +++ b/examples/simple/igraph_realize_degree_sequence.c @@ -0,0 +1,41 @@ +#include +#include + +int main(void){ + igraph_t g1, g2, g3; + igraph_int_t nodes = 500, A = 0, power = 1, m = 1; + igraph_real_t assortativity; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); + printf("Demonstration of difference in assortativities of graphs with the same degree sequence but different linkages:\n\nInitial graph based on the Barabasi-Albert model with %" IGRAPH_PRId " nodes.\n", nodes); + + /* Graph 1 generated by a randomized graph generator */ + igraph_barabasi_game(&g1, nodes, power, m, NULL, /* outpref */ 0, A, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, /* start from */ NULL); + + igraph_vector_int_t degree; + igraph_vector_int_init(°ree, nodes); + igraph_degree(&g1, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS); + + /* Measuring assortativity of the first graph */ + igraph_assortativity_degree(&g1, &assortativity, IGRAPH_UNDIRECTED); + printf("Assortativity of initial graph = %g\n\n", assortativity); + igraph_destroy(&g1); + + /* Graph 2 (with the same degree sequence) generated by selecting vertices with the smallest degree first */ + igraph_realize_degree_sequence(&g2, °ree, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST); + igraph_assortativity_degree(&g2, &assortativity, IGRAPH_UNDIRECTED); + printf("Assortativity after choosing vertices with the smallest degrees first = %g\n\n", assortativity); + igraph_destroy(&g2); + + /* Graph 3 (with the same degree sequence) generated by selecting vertices with the largest degree first */ + igraph_realize_degree_sequence(&g3, °ree, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_LARGEST); + igraph_assortativity_degree(&g3, &assortativity, IGRAPH_UNDIRECTED); + printf("Assortativity after choosing vertices with the largest degrees first = %g\n", assortativity); + igraph_destroy(&g3); + igraph_vector_int_destroy(°ree); + + return 0; +} diff --git a/examples/simple/igraph_realize_degree_sequence.out b/examples/simple/igraph_realize_degree_sequence.out new file mode 100644 index 0000000..1abecd1 --- /dev/null +++ b/examples/simple/igraph_realize_degree_sequence.out @@ -0,0 +1,8 @@ +Demonstration of difference in assortativities of graphs with the same degree sequence but different linkages: + +Initial graph based on the Barabasi-Albert model with 500 nodes. +Assortativity of initial graph = -0.171365 + +Assortativity after choosing vertices with the smallest degrees first = -0.257722 + +Assortativity after choosing vertices with the largest degrees first = 0.367447 diff --git a/examples/simple/igraph_reciprocity.c b/examples/simple/igraph_reciprocity.c new file mode 100644 index 0000000..ed24db7 --- /dev/null +++ b/examples/simple/igraph_reciprocity.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + + igraph_t g; + igraph_real_t res; + + /* Initialize the library. */ + igraph_setup(); + + /* Trivial cases */ + + igraph_ring(&g, 100, IGRAPH_UNDIRECTED, 0, 0); + igraph_reciprocity(&g, &res, 0, IGRAPH_RECIPROCITY_DEFAULT); + igraph_destroy(&g); + + if (res != 1) { + return 1; + } + + /* Small test graph */ + + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 0, 2, 3, 3, 2, -1); + + igraph_reciprocity(&g, &res, 0, IGRAPH_RECIPROCITY_RATIO); + igraph_destroy(&g); + + if (res != 0.5) { + fprintf(stderr, "%f != %f\n", res, 0.5); + return 2; + } + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 1, -1); + igraph_reciprocity(&g, &res, 0, IGRAPH_RECIPROCITY_DEFAULT); + igraph_destroy(&g); + + if (fabs(res - 2.0 / 3.0) > 1e-15) { + fprintf(stderr, "%f != %f\n", res, 2.0 / 3.0); + return 3; + } + + return 0; +} diff --git a/examples/simple/igraph_regular_tree.c b/examples/simple/igraph_regular_tree.c new file mode 100644 index 0000000..d8be3ce --- /dev/null +++ b/examples/simple/igraph_regular_tree.c @@ -0,0 +1,30 @@ + +#include + +int main(void) { + igraph_t tree; + igraph_vector_t eccentricity; + igraph_bool_t is_tree; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a Bethe lattice with 5 levels, i.e. height 4. */ + igraph_regular_tree(&tree, 4, 3, IGRAPH_TREE_UNDIRECTED); + + /* Bethe lattices are trees. */ + igraph_is_tree(&tree, &is_tree, NULL, IGRAPH_ALL); + printf("Is it a tree? %s\n", is_tree ? "Yes." : "No."); + + /* Compute and print eccentricities. The root is the most central. */ + igraph_vector_init(&eccentricity, 0); + igraph_eccentricity(&tree, NULL, &eccentricity, igraph_vss_all(), IGRAPH_ALL); + printf("Vertex eccentricities:\n"); + igraph_vector_print(&eccentricity); + igraph_vector_destroy(&eccentricity); + + /* Clean up. */ + igraph_destroy(&tree); + + return 0; +} diff --git a/examples/simple/igraph_regular_tree.out b/examples/simple/igraph_regular_tree.out new file mode 100644 index 0000000..187d910 --- /dev/null +++ b/examples/simple/igraph_regular_tree.out @@ -0,0 +1,3 @@ +Is it a tree? Yes. +Vertex eccentricities: +4 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 diff --git a/examples/simple/igraph_ring.c b/examples/simple/igraph_ring.c new file mode 100644 index 0000000..01550d0 --- /dev/null +++ b/examples/simple/igraph_ring.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed path graph on 10 vertices. */ + igraph_ring(&graph, 10, IGRAPH_DIRECTED, /* mutual= */ 0, /* circular= */ 0); + + /* Output the edge list of the graph. */ + printf("10-path graph:\n"); + igraph_write_graph_edgelist(&graph, stdout); + + /* Destroy the graph. */ + igraph_destroy(&graph); + + /* Create a 4-cycle graph. */ + igraph_ring(&graph, 4, IGRAPH_UNDIRECTED, /* mutual= */ 0, /* circular= */ 1); + + /* Output the edge list of the graph. */ + printf("\n4-cycle graph:\n"); + igraph_write_graph_edgelist(&graph, stdout); + + /* Destroy the graph. */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_ring.out b/examples/simple/igraph_ring.out new file mode 100644 index 0000000..3cd8a54 --- /dev/null +++ b/examples/simple/igraph_ring.out @@ -0,0 +1,16 @@ +10-path graph: +0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 + +4-cycle graph: +0 1 +0 3 +1 2 +2 3 diff --git a/examples/simple/igraph_similarity.c b/examples/simple/igraph_similarity.c new file mode 100644 index 0000000..6e19759 --- /dev/null +++ b/examples/simple/igraph_similarity.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_matrix_t m; + igraph_vector_int_t pairs; + igraph_vector_t res; + igraph_int_t i, j, n; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 2, 1, 2, 0, 3, 0, + -1); + + igraph_matrix_init(&m, 0, 0); + igraph_vector_init(&res, 0); + igraph_vector_int_init(&pairs, 0); + + n = igraph_vcount(&g); + for (i = 0; i < n; i++) { + for (j = n - 1; j >= 0; j--) { + igraph_vector_int_push_back(&pairs, i); + igraph_vector_int_push_back(&pairs, j); + } + } + + printf("Jaccard similarity:\n"); + igraph_similarity_jaccard(&g, &m, igraph_vss_range(1, 3), igraph_vss_range(1, 3), IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_matrix_printf(&m, "%.2f"); + + printf("\nJaccard similarity, pairs:\n"); + igraph_similarity_jaccard_pairs(&g, &res, &pairs, IGRAPH_ALL, 0); + igraph_vector_print(&res); + + printf("\nJaccard similarity with edge selector:\n"); + igraph_similarity_jaccard_es(&g, &res, igraph_ess_all(IGRAPH_EDGEORDER_FROM), IGRAPH_IN, 0); + igraph_vector_print(&res); + + printf("\nDice similarity:\n"); + igraph_similarity_dice(&g, &m, igraph_vss_range(1, 3), igraph_vss_range(1, 3), IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_matrix_printf(&m, "%.2f"); + + printf("\nDice similarity, pairs:\n"); + igraph_similarity_dice_pairs(&g, &res, &pairs, IGRAPH_ALL, 0); + igraph_vector_print(&res); + + printf("\nDice similarity with edge selector:\n"); + igraph_similarity_dice_es(&g, &res, igraph_ess_all(IGRAPH_EDGEORDER_FROM), IGRAPH_IN, 0); + igraph_vector_print(&res); + + printf("\nWeighted inverse log similarity:\n"); + igraph_similarity_inverse_log_weighted(&g, &m, igraph_vss_all(), IGRAPH_ALL); + igraph_matrix_printf(&m, "%.2f"); + + igraph_matrix_destroy(&m); + igraph_destroy(&g); + igraph_vector_destroy(&res); + igraph_vector_int_destroy(&pairs); + + return 0; +} diff --git a/examples/simple/igraph_similarity.out b/examples/simple/igraph_similarity.out new file mode 100644 index 0000000..37f5069 --- /dev/null +++ b/examples/simple/igraph_similarity.out @@ -0,0 +1,25 @@ +Jaccard similarity: +1.00 0.33 +0.33 1.00 + +Jaccard similarity, pairs: +0 0.25 0.25 1 0.5 0.333333 1 0.25 0.5 1 0.333333 0.25 1 0.5 0.5 0 + +Jaccard similarity with edge selector: +0.333333 0 0 0 + +Dice similarity: +1.00 0.50 +0.50 1.00 + +Dice similarity, pairs: +0 0.4 0.4 1 0.666667 0.5 1 0.4 0.666667 1 0.5 0.4 1 0.666667 0.666667 0 + +Dice similarity with edge selector: +0.5 0 0 0 + +Weighted inverse log similarity: +0.00 1.44 1.44 0.00 +1.44 0.00 0.91 0.91 +1.44 0.91 0.00 0.91 +0.00 0.91 0.91 0.00 diff --git a/examples/simple/igraph_simplify.c b/examples/simple/igraph_simplify.c new file mode 100644 index 0000000..f698f91 --- /dev/null +++ b/examples/simple/igraph_simplify.c @@ -0,0 +1,94 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + + /* Initialize the library. */ + igraph_setup(); + + /* Multiple edges */ + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + if (igraph_ecount(&g) != 1) { + return 1; + } + igraph_destroy(&g); + + /* Loop edges*/ + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 0, 1, 1, 2, 2, 1, 2, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0, 0, 1, 1, 2, 2, 1, 2, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + /* Loop & multiple edges */ + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, -1); + igraph_simplify(&g, /* remove_multiple */ true, /* remove_loops */ false, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, -1); + igraph_simplify(&g, /* remove_multiple */ true, /* remove_loops */ false, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 2, 2, 2, 2, 2, 2, 3, 2, -1); + igraph_simplify(&g, /* remove_multiple */ false, /* remove_loops */ true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 3, 3, 3, 3, 3, 4, -1); + igraph_simplify(&g, /* remove_multiple */ false, /* remove_loops */ true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 3, 2, 3, 2, -1); + igraph_simplify(&g, true, true, /*edge_comb=*/ NULL); + if (igraph_ecount(&g) != 1) { + return 2; + } + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_simplify.out b/examples/simple/igraph_simplify.out new file mode 100644 index 0000000..3ad16e2 --- /dev/null +++ b/examples/simple/igraph_simplify.out @@ -0,0 +1,10 @@ +0 1 +1 2 +1 2 +0 0 +1 2 +1 1 +2 3 +3 2 +3 4 +3 2 diff --git a/examples/simple/igraph_small.c b/examples/simple/igraph_small.c new file mode 100644 index 0000000..299b74b --- /dev/null +++ b/examples/simple/igraph_small.c @@ -0,0 +1,37 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 6, 1, -1); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_small.out b/examples/simple/igraph_small.out new file mode 100644 index 0000000..4ed7c99 --- /dev/null +++ b/examples/simple/igraph_small.out @@ -0,0 +1,5 @@ +0 1 +1 2 +2 3 +3 4 +6 1 diff --git a/examples/simple/igraph_sparsemat.c b/examples/simple/igraph_sparsemat.c new file mode 100644 index 0000000..b2def05 --- /dev/null +++ b/examples/simple/igraph_sparsemat.c @@ -0,0 +1,175 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_sparsemat_t A, B, C, D; + igraph_t G, H; + igraph_vector_t vect; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + /* Create, compress, destroy */ + igraph_sparsemat_init(&A, 100, 20, 50); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_destroy(&A); + + /* Convert a ring graph to a matrix, print it, compress, print again */ +#define VC 10 + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_ring(&G, VC, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_get_adjacency_sparse(&G, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&G); + + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_print(&B, stdout); + + /* Basic query, nrow, ncol, type, is_triplet, is_cc */ + if (igraph_sparsemat_nrow(&A) != VC || + igraph_sparsemat_ncol(&A) != VC || + igraph_sparsemat_nrow(&B) != VC || + igraph_sparsemat_ncol(&B) != VC) { + return 1; + } + if (!igraph_sparsemat_is_triplet(&A)) { + return 2; + } + if (!igraph_sparsemat_is_cc(&B)) { + return 3; + } + if (igraph_sparsemat_type(&A) != IGRAPH_SPARSEMAT_TRIPLET) { + return 4; + } + if (igraph_sparsemat_type(&B) != IGRAPH_SPARSEMAT_CC) { + return 5; + } + + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); +#undef VC + + printf("------------------------\n"); + + /* Create unit matrices */ + igraph_sparsemat_init_eye(&A, /*n=*/ 5, /*nzmax=*/ 5, /*value=*/ 1.0, /*compress=*/ 0); + igraph_sparsemat_init_eye(&B, /*n=*/ 5, /*nzmax=*/ 5, /*value=*/ 1.0, /*compress=*/ 1); + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + + printf("------------------------\n"); + + /* Create diagonal matrices */ + igraph_vector_init(&vect, 5); + for (i = 0; i < 5; i++) { + VECTOR(vect)[i] = i; + } + igraph_sparsemat_init_diag(&A, /*nzmax=*/ 5, /*values=*/ &vect, /*compress=*/ 0); + igraph_sparsemat_init_diag(&B, /*nzmax=*/ 5, /*values=*/ &vect, /*compress=*/ 1); + igraph_vector_destroy(&vect); + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + + printf("------------------------\n"); + + /* Transpose matrices */ + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_kary_tree(&G, 10, /*children=*/ 2, IGRAPH_TREE_OUT); + igraph_get_adjacency_sparse(&G, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&G); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_transpose(&B, &C); + igraph_sparsemat_print(&C, stdout); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_destroy(&C); + + printf("------------------------\n"); + + /* Add duplicate elements */ + igraph_sparsemat_init(&A, 10, 10, /*nzmax=*/ 20); + for (i = 1; i < 10; i++) { + igraph_sparsemat_entry(&A, 0, i, 1.0); + } + for (i = 1; i < 10; i++) { + igraph_sparsemat_entry(&A, 0, i, 1.0); + } + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_dupl(&B); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + + printf("------------------------\n"); + + /* Drop zero elements */ + igraph_sparsemat_init(&A, 10, 10, /*nzmax=*/ 20); + igraph_sparsemat_entry(&A, 7, 3, 0.0); + for (i = 1; i < 10; i++) { + igraph_sparsemat_entry(&A, 0, i, 1.0); + igraph_sparsemat_entry(&A, 0, i, 0.0); + } + igraph_sparsemat_entry(&A, 0, 0, 0.0); + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_dropzeros(&B); + igraph_sparsemat_print(&B, stdout); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + + printf("------------------------\n"); + + /* Add two matrices */ + + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_sparsemat_init(&B, 1, 1, 0); + igraph_star(&G, 10, IGRAPH_STAR_OUT, /*center=*/ 0); + igraph_ring(&H, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_get_adjacency_sparse(&G, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_get_adjacency_sparse(&H, &B, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&G); + igraph_destroy(&H); + igraph_sparsemat_compress(&A, &C); + igraph_sparsemat_compress(&B, &D); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_add(&C, &D, /*alpha=*/ 1.0, /*beta=*/ 2.0, &A); + igraph_sparsemat_destroy(&C); + igraph_sparsemat_destroy(&D); + igraph_sparsemat_print(&A, stdout); + igraph_sparsemat_destroy(&A); + + return 0; +} diff --git a/examples/simple/igraph_sparsemat.out b/examples/simple/igraph_sparsemat.out new file mode 100644 index 0000000..33b5e6d --- /dev/null +++ b/examples/simple/igraph_sparsemat.out @@ -0,0 +1,295 @@ +1 0 : 1 +0 1 : 1 +2 1 : 1 +1 2 : 1 +3 2 : 1 +2 3 : 1 +4 3 : 1 +3 4 : 1 +5 4 : 1 +4 5 : 1 +6 5 : 1 +5 6 : 1 +7 6 : 1 +6 7 : 1 +8 7 : 1 +7 8 : 1 +9 8 : 1 +8 9 : 1 +9 0 : 1 +0 9 : 1 +col 0: locations 0 to 1 +1 : 1 +9 : 1 +col 1: locations 2 to 3 +0 : 1 +2 : 1 +col 2: locations 4 to 5 +1 : 1 +3 : 1 +col 3: locations 6 to 7 +2 : 1 +4 : 1 +col 4: locations 8 to 9 +3 : 1 +5 : 1 +col 5: locations 10 to 11 +4 : 1 +6 : 1 +col 6: locations 12 to 13 +5 : 1 +7 : 1 +col 7: locations 14 to 15 +6 : 1 +8 : 1 +col 8: locations 16 to 17 +7 : 1 +9 : 1 +col 9: locations 18 to 19 +8 : 1 +0 : 1 +------------------------ +0 0 : 1 +1 1 : 1 +2 2 : 1 +3 3 : 1 +4 4 : 1 +col 0: locations 0 to 0 +0 : 1 +col 1: locations 1 to 1 +1 : 1 +col 2: locations 2 to 2 +2 : 1 +col 3: locations 3 to 3 +3 : 1 +col 4: locations 4 to 4 +4 : 1 +------------------------ +0 0 : 0 +1 1 : 1 +2 2 : 2 +3 3 : 3 +4 4 : 4 +col 0: locations 0 to 0 +0 : 0 +col 1: locations 1 to 1 +1 : 1 +col 2: locations 2 to 2 +2 : 2 +col 3: locations 3 to 3 +3 : 3 +col 4: locations 4 to 4 +4 : 4 +------------------------ +col 0: locations 0 to -1 +col 1: locations 0 to 0 +0 : 1 +col 2: locations 1 to 1 +0 : 1 +col 3: locations 2 to 2 +1 : 1 +col 4: locations 3 to 3 +1 : 1 +col 5: locations 4 to 4 +2 : 1 +col 6: locations 5 to 5 +2 : 1 +col 7: locations 6 to 6 +3 : 1 +col 8: locations 7 to 7 +3 : 1 +col 9: locations 8 to 8 +4 : 1 +col 0: locations 0 to 1 +1 : 1 +2 : 1 +col 1: locations 2 to 3 +3 : 1 +4 : 1 +col 2: locations 4 to 5 +5 : 1 +6 : 1 +col 3: locations 6 to 7 +7 : 1 +8 : 1 +col 4: locations 8 to 8 +9 : 1 +col 5: locations 9 to 8 +col 6: locations 9 to 8 +col 7: locations 9 to 8 +col 8: locations 9 to 8 +col 9: locations 9 to 8 +------------------------ +0 1 : 1 +0 2 : 1 +0 3 : 1 +0 4 : 1 +0 5 : 1 +0 6 : 1 +0 7 : 1 +0 8 : 1 +0 9 : 1 +0 1 : 1 +0 2 : 1 +0 3 : 1 +0 4 : 1 +0 5 : 1 +0 6 : 1 +0 7 : 1 +0 8 : 1 +0 9 : 1 +col 0: locations 0 to -1 +col 1: locations 0 to 1 +0 : 1 +0 : 1 +col 2: locations 2 to 3 +0 : 1 +0 : 1 +col 3: locations 4 to 5 +0 : 1 +0 : 1 +col 4: locations 6 to 7 +0 : 1 +0 : 1 +col 5: locations 8 to 9 +0 : 1 +0 : 1 +col 6: locations 10 to 11 +0 : 1 +0 : 1 +col 7: locations 12 to 13 +0 : 1 +0 : 1 +col 8: locations 14 to 15 +0 : 1 +0 : 1 +col 9: locations 16 to 17 +0 : 1 +0 : 1 +col 0: locations 0 to -1 +col 1: locations 0 to 0 +0 : 2 +col 2: locations 1 to 1 +0 : 2 +col 3: locations 2 to 2 +0 : 2 +col 4: locations 3 to 3 +0 : 2 +col 5: locations 4 to 4 +0 : 2 +col 6: locations 5 to 5 +0 : 2 +col 7: locations 6 to 6 +0 : 2 +col 8: locations 7 to 7 +0 : 2 +col 9: locations 8 to 8 +0 : 2 +------------------------ +7 3 : 0 +0 1 : 1 +0 1 : 0 +0 2 : 1 +0 2 : 0 +0 3 : 1 +0 3 : 0 +0 4 : 1 +0 4 : 0 +0 5 : 1 +0 5 : 0 +0 6 : 1 +0 6 : 0 +0 7 : 1 +0 7 : 0 +0 8 : 1 +0 8 : 0 +0 9 : 1 +0 9 : 0 +0 0 : 0 +col 0: locations 0 to 0 +0 : 0 +col 1: locations 1 to 2 +0 : 1 +0 : 0 +col 2: locations 3 to 4 +0 : 1 +0 : 0 +col 3: locations 5 to 7 +7 : 0 +0 : 1 +0 : 0 +col 4: locations 8 to 9 +0 : 1 +0 : 0 +col 5: locations 10 to 11 +0 : 1 +0 : 0 +col 6: locations 12 to 13 +0 : 1 +0 : 0 +col 7: locations 14 to 15 +0 : 1 +0 : 0 +col 8: locations 16 to 17 +0 : 1 +0 : 0 +col 9: locations 18 to 19 +0 : 1 +0 : 0 +col 0: locations 0 to -1 +col 1: locations 0 to 0 +0 : 1 +col 2: locations 1 to 1 +0 : 1 +col 3: locations 2 to 2 +0 : 1 +col 4: locations 3 to 3 +0 : 1 +col 5: locations 4 to 4 +0 : 1 +col 6: locations 5 to 5 +0 : 1 +col 7: locations 6 to 6 +0 : 1 +col 8: locations 7 to 7 +0 : 1 +col 9: locations 8 to 8 +0 : 1 +------------------------ +col 0: locations 0 to 1 +1 : 2 +9 : 2 +col 1: locations 2 to 3 +0 : 3 +2 : 2 +col 2: locations 4 to 6 +0 : 1 +1 : 2 +3 : 2 +col 3: locations 7 to 9 +0 : 1 +2 : 2 +4 : 2 +col 4: locations 10 to 12 +0 : 1 +3 : 2 +5 : 2 +col 5: locations 13 to 15 +0 : 1 +4 : 2 +6 : 2 +col 6: locations 16 to 18 +0 : 1 +5 : 2 +7 : 2 +col 7: locations 19 to 21 +0 : 1 +6 : 2 +8 : 2 +col 8: locations 22 to 24 +0 : 1 +7 : 2 +9 : 2 +col 9: locations 25 to 26 +0 : 3 +8 : 2 diff --git a/examples/simple/igraph_sparsemat3.c b/examples/simple/igraph_sparsemat3.c new file mode 100644 index 0000000..be4e668 --- /dev/null +++ b/examples/simple/igraph_sparsemat3.c @@ -0,0 +1,310 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void permute(const igraph_matrix_t *M, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_matrix_t *res) { + + igraph_int_t nrow = igraph_vector_int_size(p); + igraph_int_t ncol = igraph_vector_int_size(q); + igraph_int_t i, j; + + igraph_matrix_resize(res, nrow, ncol); + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + igraph_int_t ii = VECTOR(*p)[i]; + igraph_int_t jj = VECTOR(*q)[j]; + MATRIX(*res, i, j) = MATRIX(*M, ii, jj); + } + } +} + +void permute_rows(const igraph_matrix_t *M, + const igraph_vector_int_t *p, + igraph_matrix_t *res) { + + igraph_int_t nrow = igraph_vector_int_size(p); + igraph_int_t ncol = igraph_matrix_ncol(M); + igraph_int_t i, j; + + igraph_matrix_resize(res, nrow, ncol); + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + igraph_int_t ii = VECTOR(*p)[i]; + MATRIX(*res, i, j) = MATRIX(*M, ii, j); + } + } +} + +void permute_cols(const igraph_matrix_t *M, + const igraph_vector_int_t *q, + igraph_matrix_t *res) { + + igraph_int_t nrow = igraph_matrix_nrow(M); + igraph_int_t ncol = igraph_vector_int_size(q); + igraph_int_t i, j; + + igraph_matrix_resize(res, nrow, ncol); + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + igraph_int_t jj = VECTOR(*q)[j]; + MATRIX(*res, i, j) = MATRIX(*M, i, jj); + } + } +} + +void random_permutation(igraph_vector_int_t *vec) { + /* We just do size(vec) * 2 swaps */ + igraph_int_t one, two, i, n = igraph_vector_int_size(vec); + igraph_int_t tmp; + for (i = 0; i < 2 * n; i++) { + one = RNG_INTEGER(0, n - 1); + two = RNG_INTEGER(0, n - 1); + tmp = VECTOR(*vec)[one]; + VECTOR(*vec)[one] = VECTOR(*vec)[two]; + VECTOR(*vec)[two] = tmp; + } +} + +igraph_bool_t check_same(const igraph_sparsemat_t *A, + const igraph_matrix_t *M) { + igraph_matrix_t A_dense; + igraph_bool_t result; + + igraph_matrix_init(&A_dense, 1, 1); + igraph_sparsemat_as_matrix(&A_dense, A); + result = igraph_matrix_all_e(&A_dense, M); + igraph_matrix_destroy(&A_dense); + + return result; +} + +int main(void) { + + igraph_sparsemat_t A, B; + igraph_matrix_t M, N; + igraph_vector_int_t p, q; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + /* Permutation of a matrix */ + +#define NROW 10 +#define NCOL 5 +#define EDGES NROW*NCOL/3 + igraph_matrix_init(&M, NROW, NCOL); + igraph_sparsemat_init(&A, NROW, NCOL, EDGES); + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(M, r, c) = MATRIX(M, r, c) + value; + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_int_init_range(&p, 0, NROW); + igraph_vector_int_init_range(&q, 0, NCOL); + + /* Identity */ + + igraph_matrix_init(&N, 0, 0); + permute(&M, &p, &q, &N); + + igraph_sparsemat_permute(&B, &p, &q, &A); + igraph_sparsemat_dupl(&A); + + if (! check_same(&A, &N)) { + return 1; + } + + /* Random permutation */ + random_permutation(&p); + random_permutation(&q); + + permute(&M, &p, &q, &N); + + igraph_sparsemat_destroy(&A); + igraph_sparsemat_permute(&B, &p, &q, &A); + igraph_sparsemat_dupl(&A); + + if (! check_same(&A, &N)) { + return 2; + } + + igraph_vector_int_destroy(&p); + igraph_vector_int_destroy(&q); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + igraph_matrix_destroy(&M); + igraph_matrix_destroy(&N); + +#undef NROW +#undef NCOL +#undef EDGES + + /* Indexing */ + +#define NROW 10 +#define NCOL 5 +#define EDGES NROW*NCOL/3 +#define I_NROW 6 +#define I_NCOL 3 + igraph_matrix_init(&M, NROW, NCOL); + igraph_sparsemat_init(&A, NROW, NCOL, EDGES); + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(M, r, c) = MATRIX(M, r, c) + value; + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_int_init(&p, I_NROW); + igraph_vector_int_init(&q, I_NCOL); + + for (i = 0; i < I_NROW; i++) { + VECTOR(p)[i] = RNG_INTEGER(0, I_NROW - 1); + } + for (i = 0; i < I_NCOL; i++) { + VECTOR(p)[i] = RNG_INTEGER(0, I_NCOL - 1); + } + + igraph_matrix_init(&N, 0, 0); + permute(&M, &p, &q, &N); + + igraph_sparsemat_index(&B, &p, &q, &A, 0); + + if (! check_same(&A, &N)) { + return 3; + } + + igraph_sparsemat_destroy(&A); + + /* Getting single elements with index() */ + + igraph_vector_int_resize(&p, 1); + igraph_vector_int_resize(&q, 1); + + for (i = 0; i < 100; i++) { + igraph_real_t value; + VECTOR(p)[0] = RNG_INTEGER(0, NROW - 1); + VECTOR(q)[0] = RNG_INTEGER(0, NCOL - 1); + igraph_sparsemat_index(&B, &p, &q, /*res=*/ 0, &value); + if (value != MATRIX(M, VECTOR(p)[0], VECTOR(q)[0])) { + return 4; + } + } + + /* Getting single elements with get() */ + + igraph_vector_int_resize(&p, 1); + igraph_vector_int_resize(&q, 1); + + for (i = 0; i < 100; i++) { + igraph_int_t row = RNG_INTEGER(0, NROW - 1); + igraph_int_t col = RNG_INTEGER(0, NCOL - 1); + if (igraph_sparsemat_get(&B, row, col) != MATRIX(M, row, col)) { + return 4; + } + } + + /* Getting submatrices with index() */ + + for (i = 0; i < 100; i++) { + igraph_real_t value; + VECTOR(p)[0] = RNG_INTEGER(0, NROW - 1); + VECTOR(q)[0] = RNG_INTEGER(0, NCOL - 1); + igraph_sparsemat_index(&B, &p, &q, /*res=*/ &A, &value); + igraph_sparsemat_destroy(&A); + if (value != MATRIX(M, VECTOR(p)[0], VECTOR(q)[0])) { + return 4; + } + } + + igraph_vector_int_destroy(&p); + igraph_vector_int_destroy(&q); + igraph_sparsemat_destroy(&B); + igraph_matrix_destroy(&M); + igraph_matrix_destroy(&N); + + /* Indexing only the rows or the columns */ + + igraph_matrix_init(&M, NROW, NCOL); + igraph_sparsemat_init(&A, NROW, NCOL, EDGES); + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(M, r, c) = MATRIX(M, r, c) + value; + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_int_init(&p, I_NROW); + igraph_vector_int_init(&q, I_NCOL); + + for (i = 0; i < I_NROW; i++) { + VECTOR(p)[i] = RNG_INTEGER(0, I_NROW - 1); + } + for (i = 0; i < I_NCOL; i++) { + VECTOR(p)[i] = RNG_INTEGER(0, I_NCOL - 1); + } + + igraph_matrix_init(&N, 0, 0); + permute_rows(&M, &p, &N); + + igraph_sparsemat_index(&B, &p, 0, &A, 0); + + if (! check_same(&A, &N)) { + return 5; + } + + permute_cols(&M, &q, &N); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_index(&B, 0, &q, &A, 0); + + if (! check_same(&A, &N)) { + return 6; + } + + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + igraph_vector_int_destroy(&p); + igraph_vector_int_destroy(&q); + igraph_matrix_destroy(&M); + igraph_matrix_destroy(&N); + + return 0; +} diff --git a/examples/simple/igraph_sparsemat3.out b/examples/simple/igraph_sparsemat3.out new file mode 100644 index 0000000..e69de29 diff --git a/examples/simple/igraph_sparsemat4.c b/examples/simple/igraph_sparsemat4.c new file mode 100644 index 0000000..ce8a3d1 --- /dev/null +++ b/examples/simple/igraph_sparsemat4.c @@ -0,0 +1,313 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +igraph_bool_t check_solution(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + const igraph_vector_t *b) { + + igraph_vector_t res; + igraph_real_t min, max; + igraph_bool_t success; + igraph_sparsemat_iterator_t it; + + igraph_vector_init_copy(&res, b); + + igraph_sparsemat_iterator_init(&it, (igraph_sparsemat_t*) A); + while (!igraph_sparsemat_iterator_end(&it)) { + igraph_int_t row = igraph_sparsemat_iterator_row(&it); + igraph_int_t col = igraph_sparsemat_iterator_col(&it); + igraph_real_t value = igraph_sparsemat_iterator_get(&it); + VECTOR(res)[row] -= VECTOR(*x)[col] * value; + igraph_sparsemat_iterator_next(&it); + } + + igraph_vector_minmax(&res, &min, &max); + + success = fabs(min) < 1e-12 && fabs(max) < 1e-12; + + if (!success) { + printf("Incorrect solution.\n\n"); + printf("A =\n"); igraph_sparsemat_print(A, stdout); printf("\n"); + printf("x =\n"); igraph_vector_print(x); printf("\n"); + printf("b =\n"); igraph_vector_print(b); printf("\n"); + printf("difference between A*x and b =\n"); + igraph_vector_print(&res); printf("\n\n"); + } + + igraph_vector_destroy(&res); + + return success; +} + +int main(void) { + + igraph_sparsemat_t A, B, C; + igraph_vector_t b, x; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + /* Seed the RNG for reproducible results */ + igraph_rng_seed(igraph_rng_default(), 123); + + /* lsolve */ + +#define DIM 10 +#define EDGES (DIM*DIM/6) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, DIM - 1); + igraph_int_t c = RNG_INTEGER(0, r); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_lsolve(&B, &b, &x); + + if (! check_solution(&B, &x, &b)) { + return 1; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&B); + +#undef DIM +#undef EDGES + + /* ltsolve */ + +#define DIM 10 +#define EDGES (DIM*DIM/6) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, DIM - 1); + igraph_int_t c = RNG_INTEGER(0, r); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_ltsolve(&B, &b, &x); + + igraph_sparsemat_transpose(&B, &A); + if (! check_solution(&A, &x, &b)) { + return 2; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_destroy(&A); + +#undef DIM +#undef EDGES + + /* usolve */ + +#define DIM 10 +#define EDGES (DIM*DIM/6) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, DIM - 1); + igraph_int_t c = RNG_INTEGER(0, r); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + igraph_sparsemat_transpose(&B, &A); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_usolve(&A, &b, &x); + + if (! check_solution(&A, &x, &b)) { + return 3; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_destroy(&A); + +#undef DIM +#undef EDGES + + /* utsolve */ + +#define DIM 10 +#define EDGES (DIM*DIM/6) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, DIM - 1); + igraph_int_t c = RNG_INTEGER(0, r); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + igraph_sparsemat_transpose(&B, &A); + igraph_sparsemat_destroy(&B); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_utsolve(&A, &b, &x); + + igraph_sparsemat_transpose(&A, &B); + if (! check_solution(&B, &x, &b)) { + return 4; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&B); + igraph_sparsemat_destroy(&A); + +#undef DIM +#undef EDGES + + /* cholsol */ + /* We need a positive definite matrix, so we create a full-rank + matrix first and then calculate A'A, which will be positive + definite. */ + +#define DIM 10 +#define EDGES (DIM*DIM/6) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t from = RNG_INTEGER(0, DIM - 1); + igraph_int_t to = RNG_INTEGER(0, DIM - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, from, to, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + igraph_sparsemat_transpose(&B, &A); + igraph_sparsemat_multiply(&A, &B, &C); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&B); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_cholsol(&C, &b, &x, /*order=*/ 0); + + if (! check_solution(&C, &x, &b)) { + return 5; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&C); + +#undef DIM +#undef EDGES + + /* lusol */ + +#define DIM 10 +#define EDGES (DIM*DIM/4) + igraph_sparsemat_init(&A, DIM, DIM, EDGES + DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, RNG_INTEGER(1, 3)); + } + for (i = 0; i < EDGES; i++) { + igraph_int_t from = RNG_INTEGER(0, DIM - 1); + igraph_int_t to = RNG_INTEGER(0, DIM - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + igraph_sparsemat_entry(&A, from, to, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_dupl(&B); + + igraph_vector_init(&b, DIM); + for (i = 0; i < DIM; i++) { + VECTOR(b)[i] = RNG_INTEGER(1, 10); + } + + igraph_vector_init(&x, DIM); + igraph_sparsemat_lusol(&B, &b, &x, /*order=*/ 0, /*tol=*/ 1e-10); + + if (! check_solution(&B, &x, &b)) { + return 6; + } + + igraph_vector_destroy(&b); + igraph_vector_destroy(&x); + igraph_sparsemat_destroy(&B); + +#undef DIM +#undef EDGES + + return 0; +} diff --git a/examples/simple/igraph_sparsemat4.out b/examples/simple/igraph_sparsemat4.out new file mode 100644 index 0000000..e69de29 diff --git a/examples/simple/igraph_sparsemat6.c b/examples/simple/igraph_sparsemat6.c new file mode 100644 index 0000000..2c79711 --- /dev/null +++ b/examples/simple/igraph_sparsemat6.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_matrix_t mat, mat2, mat3; + igraph_sparsemat_t spmat, spmat2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); + +#define NROW 10 +#define NCOL 7 +#define NUM_NONZEROS 15 + + igraph_matrix_init(&mat, NROW, NCOL); + for (igraph_int_t i = 0; i < NUM_NONZEROS; i++) { + igraph_int_t r = igraph_rng_get_integer(igraph_rng_default(), 0, NROW - 1); + igraph_int_t c = igraph_rng_get_integer(igraph_rng_default(), 0, NCOL - 1); + igraph_real_t val = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + MATRIX(mat, r, c) = val; + } + + igraph_matrix_as_sparsemat(&spmat, &mat, /*tol=*/ 1e-14); + igraph_matrix_init(&mat2, 0, 0); + igraph_sparsemat_as_matrix(&mat2, &spmat); + if (!igraph_matrix_all_e(&mat, &mat2)) { + return 1; + } + + igraph_sparsemat_compress(&spmat, &spmat2); + igraph_matrix_init(&mat3, 0, 0); + igraph_sparsemat_as_matrix(&mat3, &spmat2); + if (!igraph_matrix_all_e(&mat, &mat3)) { + return 2; + } + + igraph_matrix_destroy(&mat); + igraph_matrix_destroy(&mat2); + igraph_matrix_destroy(&mat3); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + + return 0; +} diff --git a/examples/simple/igraph_sparsemat7.c b/examples/simple/igraph_sparsemat7.c new file mode 100644 index 0000000..adeff75 --- /dev/null +++ b/examples/simple/igraph_sparsemat7.c @@ -0,0 +1,78 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#define DIM1 10 +#define DIM2 5 + +#define RANDINT(a) (igraph_rng_get_integer(igraph_rng_default(), 0, (a))) + +int main(void) { + igraph_matrix_t mat; + igraph_sparsemat_t spmat, spmat2; + igraph_real_t m1, m2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + igraph_sparsemat_entry(&spmat, 1, 2, -1.0); + igraph_sparsemat_entry(&spmat, 3, 2, 10.0); + for (int i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, RANDINT(DIM1 - 1), RANDINT(DIM2 - 1), 1.0); + } + igraph_sparsemat_entry(&spmat, 1, 2, -1.0); + igraph_sparsemat_entry(&spmat, 3, 2, 10.0); + + igraph_sparsemat_compress(&spmat, &spmat2); + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat2); + m1 = igraph_sparsemat_min(&spmat2); + m2 = igraph_matrix_min(&mat); + if (m1 != m2) { + printf("%f %f\n", m1, m2); + return 1; + } + m1 = igraph_sparsemat_max(&spmat2); + m2 = igraph_matrix_max(&mat); + if (m1 != m2) { + printf("%f %f\n", m1, m2); + return 2; + } + + igraph_sparsemat_minmax(&spmat2, &m1, &m2); + if (m1 != igraph_matrix_min(&mat)) { + return 3; + } + if (m2 != igraph_matrix_max(&mat)) { + return 4; + } + + igraph_matrix_destroy(&mat); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + + return 0; +} diff --git a/examples/simple/igraph_sparsemat8.c b/examples/simple/igraph_sparsemat8.c new file mode 100644 index 0000000..348295d --- /dev/null +++ b/examples/simple/igraph_sparsemat8.c @@ -0,0 +1,210 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#define DIM1 10 +#define DIM2 5 + +#define INT(a) (igraph_rng_get_integer(igraph_rng_default(), 0, (a))) + +int main(void) { + igraph_matrix_t mat, mat2; + igraph_sparsemat_t spmat, spmat2; + igraph_int_t i, j, nz1, nz2; + igraph_vector_t sums1, sums2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_rng_seed(igraph_rng_default(), 42); + + /* COPY */ + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, INT(DIM1 - 1), INT(DIM2 - 1), 1.0); + } + igraph_sparsemat_init_copy(&spmat2, &spmat); + + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat); + igraph_matrix_init(&mat2, 0, 0); + igraph_sparsemat_as_matrix(&mat2, &spmat2); + if (!igraph_matrix_all_e(&mat, &mat2)) { + return 1; + } + + igraph_matrix_destroy(&mat2); + igraph_sparsemat_destroy(&spmat2); + + igraph_sparsemat_compress(&spmat, &spmat2); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_init_copy(&spmat, &spmat2); + + igraph_matrix_init(&mat2, 0, 0); + igraph_sparsemat_as_matrix(&mat2, &spmat); + if (!igraph_matrix_all_e(&mat, &mat2)) { + return 2; + } + + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + igraph_matrix_destroy(&mat); + igraph_matrix_destroy(&mat2); + + /* COLSUMS, ROWSUMS */ + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, INT(DIM1 - 1), INT(DIM2 - 1), 1.0); + } + igraph_sparsemat_compress(&spmat, &spmat2); + + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat); + igraph_vector_init(&sums1, 0); + igraph_vector_init(&sums2, 0); + igraph_sparsemat_colsums(&spmat, &sums1); + igraph_matrix_colsum(&mat, &sums2); + if (!igraph_vector_all_e(&sums1, &sums2)) { + return 3; + } + igraph_sparsemat_colsums(&spmat2, &sums1); + if (!igraph_vector_all_e(&sums1, &sums2)) { + return 4; + } + + igraph_sparsemat_rowsums(&spmat, &sums1); + igraph_matrix_rowsum(&mat, &sums2); + if (!igraph_vector_all_e(&sums1, &sums2)) { + return 5; + } + igraph_sparsemat_rowsums(&spmat2, &sums1); + if (!igraph_vector_all_e(&sums1, &sums2)) { + return 6; + } + + igraph_matrix_destroy(&mat); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + igraph_vector_destroy(&sums1); + igraph_vector_destroy(&sums2); + + /* COUNT_NONZERO, COUNT_NONZEROTOL */ + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + igraph_sparsemat_entry(&spmat, 1, 2, 1.0); + igraph_sparsemat_entry(&spmat, 1, 2, 1.0); + igraph_sparsemat_entry(&spmat, 1, 3, 1e-12); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, INT(DIM1 - 1), INT(DIM2 - 1), 1.0); + } + igraph_sparsemat_compress(&spmat, &spmat2); + + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat2); + + nz1 = igraph_sparsemat_count_nonzero(&spmat2); + for (nz2 = 0, i = 0; i < igraph_matrix_nrow(&mat); i++) { + for (j = 0; j < igraph_matrix_ncol(&mat); j++) { + if (MATRIX(mat, i, j) != 0) { + nz2++; + } + } + } + if (nz1 != nz2) { + printf("%" IGRAPH_PRId " %" IGRAPH_PRId "\n", nz1, nz2); + return 7; + } + + nz1 = igraph_sparsemat_count_nonzerotol(&spmat2, 1e-10); + for (nz2 = 0, i = 0; i < igraph_matrix_nrow(&mat); i++) { + for (j = 0; j < igraph_matrix_ncol(&mat); j++) { + if (fabs(MATRIX(mat, i, j)) >= 1e-10) { + nz2++; + } + } + } + if (nz1 != nz2) { + printf("%" IGRAPH_PRId " %" IGRAPH_PRId "\n", nz1, nz2); + return 8; + } + + igraph_matrix_destroy(&mat); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + + /* SCALE */ + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, INT(DIM1 - 1), INT(DIM2 - 1), 1.0); + } + igraph_sparsemat_compress(&spmat, &spmat2); + + igraph_sparsemat_scale(&spmat, 2.0); + igraph_sparsemat_scale(&spmat2, 2.0); + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat); + igraph_matrix_init(&mat2, 0, 0); + igraph_sparsemat_as_matrix(&mat2, &spmat2); + igraph_matrix_scale(&mat, 1.0 / 2.0); + igraph_matrix_scale(&mat2, 1.0 / 2.0); + if (!igraph_matrix_all_e(&mat, &mat2)) { + return 9; + } + + igraph_matrix_destroy(&mat); + igraph_matrix_destroy(&mat2); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + + /* ADDROWS, ADDCOLS */ + + igraph_sparsemat_init(&spmat, DIM1, DIM2, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&spmat, INT(DIM1 - 1), INT(DIM2 - 1), 1.0); + } + igraph_sparsemat_compress(&spmat, &spmat2); + + igraph_sparsemat_add_rows(&spmat, 3); + igraph_sparsemat_add_cols(&spmat, 2); + + igraph_sparsemat_add_rows(&spmat2, 3); + igraph_sparsemat_add_cols(&spmat2, 2); + + igraph_matrix_init(&mat, 0, 0); + igraph_sparsemat_as_matrix(&mat, &spmat); + igraph_matrix_init(&mat2, 0, 0); + igraph_sparsemat_as_matrix(&mat2, &spmat2); + if (!igraph_matrix_all_e(&mat, &mat2)) { + return 10; + } + + igraph_matrix_destroy(&mat); + igraph_matrix_destroy(&mat2); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat2); + + return 0; +} diff --git a/examples/simple/igraph_star.c b/examples/simple/igraph_star.c new file mode 100644 index 0000000..d7174a8 --- /dev/null +++ b/examples/simple/igraph_star.c @@ -0,0 +1,38 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + + /* Initialize the library. */ + igraph_setup(); + + /* Create an undirected 6-star, with the 0th node as the centre. */ + igraph_star(&graph, 7, IGRAPH_STAR_UNDIRECTED, 0); + + /* Output the edge list of the graph. */ + igraph_write_graph_edgelist(&graph, stdout); + + /* Destroy the graph when we are done using it. */ + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_star.out b/examples/simple/igraph_star.out new file mode 100644 index 0000000..d3fc616 --- /dev/null +++ b/examples/simple/igraph_star.out @@ -0,0 +1,6 @@ +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 diff --git a/examples/simple/igraph_strvector.c b/examples/simple/igraph_strvector.c new file mode 100644 index 0000000..79b961c --- /dev/null +++ b/examples/simple/igraph_strvector.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void strvector_print(const igraph_strvector_t sv) { + igraph_int_t i, s = igraph_strvector_size(&sv); + for (i = 0; i < s; i++) { + printf("---%s---\n", igraph_strvector_get(&sv, i)); + } + printf("\n"); +} + +int main(void) { + + igraph_strvector_t sv1, sv2; + + /* Initialize the library. */ + igraph_setup(); + + printf("Initializing and setting elements:\n"); + igraph_strvector_init(&sv1, 5); + + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + igraph_strvector_set(&sv1, 3, "three"); + igraph_strvector_set(&sv1, 4, "four"); + strvector_print(sv1); + + printf("strvector size after first removing one element, and then the rest:\n"); + igraph_strvector_remove(&sv1, 4); + igraph_strvector_remove_section(&sv1, 0, 4); + printf("%" IGRAPH_PRId "\n\n", igraph_strvector_size(&sv1)); + + printf("Resize to three elements, and set them:\n"); + igraph_strvector_resize(&sv1, 3); + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + strvector_print(sv1); + + printf("Then copy the first element over the third element:\n"); + igraph_strvector_set(&sv1, 2, igraph_strvector_get(&sv1, 0)); + strvector_print(sv1); + + printf("Make a copy of the strvector and set the last element of the copy:\n"); + igraph_strvector_init_copy(&sv2, &sv1); + igraph_strvector_set(&sv2, 2, "copy two"); + strvector_print(sv2); + + printf("Append the copy to the strvector:\n"); + igraph_strvector_append(&sv1, &sv2); + strvector_print(sv1); + + printf("Add two strings at the end:\n"); + igraph_strvector_push_back(&sv1, "zeroth"); + igraph_strvector_push_back(&sv1, "first"); + strvector_print(sv1); + igraph_strvector_destroy(&sv1); + + printf("strvector size after clearing it:\n"); + igraph_strvector_clear(&sv2); + printf("%" IGRAPH_PRId "\n", igraph_strvector_size(&sv2)); + + igraph_strvector_destroy(&sv2); + + return 0; +} diff --git a/examples/simple/igraph_strvector.out b/examples/simple/igraph_strvector.out new file mode 100644 index 0000000..2cdbe3b --- /dev/null +++ b/examples/simple/igraph_strvector.out @@ -0,0 +1,45 @@ +Initializing and setting elements: +---zero--- +---one--- +---two--- +---three--- +---four--- + +strvector size after first removing one element, and then the rest: +0 + +Resize to three elements, and set them: +---zero--- +---one--- +---two--- + +Then copy the first element over the third element: +---zero--- +---one--- +---zero--- + +Make a copy of the strvector and set the last element of the copy: +---zero--- +---one--- +---copy two--- + +Append the copy to the strvector: +---zero--- +---one--- +---zero--- +---zero--- +---one--- +---copy two--- + +Add two strings at the end: +---zero--- +---one--- +---zero--- +---zero--- +---one--- +---copy two--- +---zeroth--- +---first--- + +strvector size after clearing it: +0 diff --git a/examples/simple/igraph_subisomorphic_lad.c b/examples/simple/igraph_subisomorphic_lad.c new file mode 100644 index 0000000..41d112f --- /dev/null +++ b/examples/simple/igraph_subisomorphic_lad.c @@ -0,0 +1,127 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +void print_maps(igraph_vector_int_t *map, igraph_vector_int_list_t *maps) { + igraph_int_t n, i; + igraph_vector_int_print(map); + n = igraph_vector_int_list_size(maps); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(maps, i); + igraph_vector_int_print(v); + } + igraph_vector_int_list_clear(maps); +} + +int main(void) { + igraph_t target, pattern; + igraph_bool_t iso; + igraph_vector_int_t map; + igraph_vector_int_list_t maps; + igraph_int_t i; + int domainsvec[] = { 0, 2, 8, -1, + 4, 5, 6, 7, -1, + 1, 3, 5, 6, 7, 8, -1, + 0, 2, 8, -1, + 1, 3, 7, 8, -1, -2 + }; + igraph_vector_int_list_t domains; + igraph_vector_int_t v; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&target, 9, IGRAPH_UNDIRECTED, + 0, 1, 0, 4, 0, 6, + 1, 4, 1, 2, + 2, 3, + 3, 4, 3, 5, 3, 7, 3, 8, + 4, 5, 4, 6, + 5, 6, 5, 8, + 7, 8, + -1); + + igraph_small(&pattern, 5, IGRAPH_UNDIRECTED, + 0, 1, 0, 4, + 1, 4, 1, 2, + 2, 3, + 3, 4, + -1); + + igraph_vector_int_init(&map, 0); + igraph_vector_int_list_init(&maps, 0); + + igraph_subisomorphic_lad(&pattern, &target, /*domains=*/ NULL, &iso, &map, + &maps, /*induced=*/ false); + + if (!iso) { + return 1; + } + print_maps(&map, &maps); + + printf("---------\n"); + + igraph_subisomorphic_lad(&pattern, &target, /*domains=*/ NULL, &iso, &map, + &maps, /*induced=*/ true); + + if (!iso) { + return 2; + } + print_maps(&map, &maps); + + printf("---------\n"); + + igraph_vector_int_list_init(&domains, 0); + i = 0; + igraph_vector_int_init(&v, 0); + while (1) { + if (domainsvec[i] == -2) { + break; + } else if (domainsvec[i] == -1) { + igraph_vector_int_list_push_back_copy(&domains, &v); + igraph_vector_int_clear(&v); + } else { + igraph_vector_int_push_back(&v, domainsvec[i]); + } + i++; + } + igraph_vector_int_destroy(&v); + + igraph_subisomorphic_lad(&pattern, &target, &domains, &iso, &map, &maps, + /*induced=*/ false); + + if (!iso) { + return 3; + } + print_maps(&map, &maps); + + igraph_vector_int_list_destroy(&domains); + igraph_vector_int_destroy(&map); + igraph_vector_int_list_destroy(&maps); + + igraph_destroy(&pattern); + igraph_destroy(&target); + + + return 0; +} diff --git a/examples/simple/igraph_subisomorphic_lad.out b/examples/simple/igraph_subisomorphic_lad.out new file mode 100644 index 0000000..215a48f --- /dev/null +++ b/examples/simple/igraph_subisomorphic_lad.out @@ -0,0 +1,30 @@ +1 0 6 5 4 +1 0 6 5 4 +0 1 2 3 4 +5 3 2 1 4 +7 3 4 5 8 +4 3 7 8 5 +8 3 4 6 5 +0 4 3 5 6 +0 4 3 2 1 +3 4 0 6 5 +6 4 3 8 5 +5 4 1 2 3 +5 4 1 0 6 +1 4 5 6 0 +8 5 6 4 3 +4 5 8 7 3 +3 5 6 0 4 +6 5 8 3 4 +0 6 5 3 4 +5 6 0 1 4 +7 8 5 4 3 +--------- +0 1 2 3 4 +0 1 2 3 4 +5 3 2 1 4 +5 4 1 2 3 +0 4 3 2 1 +--------- +0 4 3 2 1 +0 4 3 2 1 diff --git a/examples/simple/igraph_symmetric_tree.c b/examples/simple/igraph_symmetric_tree.c new file mode 100644 index 0000000..097b03d --- /dev/null +++ b/examples/simple/igraph_symmetric_tree.c @@ -0,0 +1,30 @@ + +#include + +int main(void) { + + igraph_t graph; + igraph_bool_t res; + igraph_vector_int_t v; + igraph_vector_int_init_int(&v, 3, 3, 4, 5); + + /* Initialize the library. */ + igraph_setup(); + + /* Create a directed symmetric tree with 2 levels - + 3 children in first and 4 children in second level, + 5 children in third level + with edges pointing towards the root. */ + igraph_symmetric_tree(&graph, &v, IGRAPH_TREE_IN); + + igraph_is_tree(&graph, &res, NULL, IGRAPH_IN); + printf("Is it an in-tree? %s\n", res ? "Yes" : "No"); + + igraph_is_tree(&graph, &res, NULL, IGRAPH_OUT); + printf("Is it an out-tree? %s\n", res ? "Yes" : "No"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&v); + + return 0; +} diff --git a/examples/simple/igraph_to_undirected.c b/examples/simple/igraph_to_undirected.c new file mode 100644 index 0000000..b1b8c55 --- /dev/null +++ b/examples/simple/igraph_to_undirected.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_vector_int_t v; + igraph_t g; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init_int(&v, 2, 5, 5); + igraph_square_lattice(&g, &v, 1, IGRAPH_DIRECTED, 1 /*mutual*/, 0 /*periodic*/); + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_COLLAPSE, + /*edge_comb=*/ 0); + igraph_write_graph_edgelist(&g, stdout); + + igraph_destroy(&g); + igraph_vector_int_destroy(&v); + + printf("---\n"); + + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 3, + 5, 6, 6, 5, 6, 7, 6, 7, 7, 6, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 9, 9, 9, 9, + -1); + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_MUTUAL, + /*edge_comb=*/ 0); + igraph_write_graph_edgelist(&g, stdout); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_to_undirected.out b/examples/simple/igraph_to_undirected.out new file mode 100644 index 0000000..4829e26 --- /dev/null +++ b/examples/simple/igraph_to_undirected.out @@ -0,0 +1,48 @@ +0 1 +0 5 +1 2 +1 6 +2 3 +2 7 +3 4 +3 8 +4 9 +5 6 +5 10 +6 7 +6 11 +7 8 +7 12 +8 9 +8 13 +9 14 +10 11 +10 15 +11 12 +11 16 +12 13 +12 17 +13 14 +13 18 +14 19 +15 16 +15 20 +16 17 +16 21 +17 18 +17 22 +18 19 +18 23 +19 24 +20 21 +21 22 +22 23 +23 24 +--- +5 6 +6 7 +7 8 +7 8 +8 8 +9 9 +9 9 diff --git a/examples/simple/igraph_topological_sorting.c b/examples/simple/igraph_topological_sorting.c new file mode 100644 index 0000000..0151d3c --- /dev/null +++ b/examples/simple/igraph_topological_sorting.c @@ -0,0 +1,51 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t res; + + /* Initialize the library. */ + igraph_setup(); + + /* Test graph taken from http://en.wikipedia.org/wiki/Topological_sorting + * @ 05.03.2006 */ + igraph_small(&graph, 8, IGRAPH_DIRECTED, + 0, 3, 0, 4, 1, 3, 2, 4, 2, 7, 3, 5, 3, 6, 3, 7, 4, 6, + -1); + + igraph_vector_int_init(&res, 0); + + /* Sort the vertices in "increasing" order. */ + igraph_topological_sorting(&graph, &res, IGRAPH_OUT); + igraph_vector_int_print(&res); + printf("\n"); + + /* Sort the vertices in "decreasing" order. */ + igraph_topological_sorting(&graph, &res, IGRAPH_IN); + igraph_vector_int_print(&res); + + /* Destroy data structures when done using them. */ + igraph_destroy(&graph); + igraph_vector_int_destroy(&res); + + return 0; +} diff --git a/examples/simple/igraph_topological_sorting.out b/examples/simple/igraph_topological_sorting.out new file mode 100644 index 0000000..7ff0e5f --- /dev/null +++ b/examples/simple/igraph_topological_sorting.out @@ -0,0 +1,3 @@ +0 1 2 3 4 5 7 6 + +5 6 7 4 3 2 0 1 diff --git a/examples/simple/igraph_transitivity.c b/examples/simple/igraph_transitivity.c new file mode 100644 index 0000000..1271546 --- /dev/null +++ b/examples/simple/igraph_transitivity.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_real_t res; + + /* Initialize the library. */ + igraph_setup(); + + /* Trivial cases */ + + igraph_ring(&g, 100, IGRAPH_UNDIRECTED, 0, 0); + igraph_transitivity_undirected(&g, &res, IGRAPH_TRANSITIVITY_NAN); + igraph_destroy(&g); + + if (res != 0) { + return 1; + } + + igraph_full(&g, 20, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_transitivity_undirected(&g, &res, IGRAPH_TRANSITIVITY_NAN); + igraph_destroy(&g); + + if (res != 1) { + return 2; + } + + /* Degenerate cases */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 2, 3, 4, 5, -1); + igraph_transitivity_undirected(&g, &res, IGRAPH_TRANSITIVITY_NAN); + /* res should be NaN here, any comparison must return false */ + if (res == 0 || res > 0 || res < 0) { + return 4; + } + igraph_transitivity_undirected(&g, &res, IGRAPH_TRANSITIVITY_ZERO); + /* res should be zero here */ + if (res) { + return 5; + } + igraph_destroy(&g); + + /* Zachary Karate club */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_transitivity_undirected(&g, &res, IGRAPH_TRANSITIVITY_NAN); + igraph_destroy(&g); + + if (res != 0.2556818181818181767717) { + fprintf(stderr, "%f != %f\n", res, 0.2556818181818181767717); + return 3; + } + + return 0; +} diff --git a/examples/simple/igraph_union.c b/examples/simple/igraph_union.c new file mode 100644 index 0000000..13b88d9 --- /dev/null +++ b/examples/simple/igraph_union.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +int main(void) { + igraph_t left, right, uni; + igraph_vector_int_t edge_map1, edge_map2; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&edge_map1, 0); + igraph_vector_int_init(&edge_map2, 0); + + igraph_small(&left, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,2, 2,3, 2,3, 3,2, + -1); + + igraph_small(&right, 6, IGRAPH_DIRECTED, + 0,1, 1,2, 2,2, 2,3, 5,2, + -1); + + igraph_union(&uni, &left, &right, &edge_map1, &edge_map2); + igraph_write_graph_edgelist(&uni, stdout); + igraph_vector_int_print(&edge_map1); + igraph_vector_int_print(&edge_map2); + + igraph_destroy(&uni); + igraph_destroy(&left); + igraph_destroy(&right); + + igraph_vector_int_destroy(&edge_map2); + igraph_vector_int_destroy(&edge_map1); + + return 0; +} diff --git a/examples/simple/igraph_union.out b/examples/simple/igraph_union.out new file mode 100644 index 0000000..d1259ca --- /dev/null +++ b/examples/simple/igraph_union.out @@ -0,0 +1,9 @@ +0 1 +1 2 +2 2 +2 3 +2 3 +3 2 +5 2 +0 1 2 3 4 5 +0 1 2 3 6 diff --git a/examples/simple/igraph_vector_int_list_sort.c b/examples/simple/igraph_vector_int_list_sort.c new file mode 100644 index 0000000..eca4da8 --- /dev/null +++ b/examples/simple/igraph_vector_int_list_sort.c @@ -0,0 +1,44 @@ + +#include +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t cliques; + igraph_int_t i, n; + + /* Initialize the library. */ + igraph_setup(); + + /* Set a random seed to make the program deterministic */ + igraph_rng_seed(igraph_rng_default(), 31415); + + /* Create a random graph with a given number of vertices and edges */ + igraph_erdos_renyi_game_gnm(&graph, 15, 80, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* Find all maximal cliques in the graph. IGRAPH_UNLIMITED signifies no limit on the clique sizes. */ + igraph_vector_int_list_init(&cliques, 0); + igraph_maximal_cliques(&graph, &cliques, /* min_size */ IGRAPH_UNLIMITED, /* max_size */ IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + + /* Print the cliques in lexicographical order */ + printf("Maximal cliques in lexicographical order:\n"); + igraph_vector_int_list_sort(&cliques, igraph_vector_int_lex_cmp); + n = igraph_vector_int_list_size(&cliques); + for (i=0; i < n; ++i) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&cliques, i)); + } + + /* Print the cliques in colexicographical order */ + printf("\nMaximal cliques in colexicographical order:\n"); + igraph_vector_int_list_sort(&cliques, igraph_vector_int_colex_cmp); + n = igraph_vector_int_list_size(&cliques); + for (i=0; i < n; ++i) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&cliques, i)); + } + + /* Destroy data structures when we no longer need them */ + igraph_vector_int_list_destroy(&cliques); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_version.c b/examples/simple/igraph_version.c new file mode 100644 index 0000000..17d5604 --- /dev/null +++ b/examples/simple/igraph_version.c @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +int main(void) { + char tmp[100]; + const char *string; + int major, minor, subminor; + + /* Initialize the library. */ + igraph_setup(); + + igraph_version(&string, &major, &minor, &subminor); + snprintf(tmp, sizeof(tmp), "%i.%i.%i", major, minor, subminor); + + if (strncmp(string, tmp, strlen(tmp))) { + return 1; + } + + return 0; +} diff --git a/examples/simple/igraph_vs_nonadj.c b/examples/simple/igraph_vs_nonadj.c new file mode 100644 index 0000000..94ffc61 --- /dev/null +++ b/examples/simple/igraph_vs_nonadj.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_vs_t vs; + igraph_vit_t vit; + igraph_int_t size; + + /* Initialize the library. */ + igraph_setup(); + + /* empty graph, all vertices */ + igraph_empty(&g, 10, IGRAPH_DIRECTED); + igraph_vs_nonadj(&vs, 0, IGRAPH_ALL); + igraph_vs_size(&g, &vs, &size); + printf("%" IGRAPH_PRId " ", size); + igraph_vit_create(&g, vs, &vit); + while (!IGRAPH_VIT_END(vit)) { + printf("%" IGRAPH_PRId " ", IGRAPH_VIT_GET(vit)); + IGRAPH_VIT_NEXT(vit); + } + printf("\n"); + + igraph_vit_destroy(&vit); + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + /* full graph, no vertices */ + igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + igraph_vs_nonadj(&vs, 0, IGRAPH_ALL); + igraph_vit_create(&g, vs, &vit); + while (!IGRAPH_VIT_END(vit)) { + printf("%" IGRAPH_PRId " ", IGRAPH_VIT_GET(vit)); + IGRAPH_VIT_NEXT(vit); + } + printf("\n"); + + igraph_vit_destroy(&vit); + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + + return 0; +} diff --git a/examples/simple/igraph_vs_nonadj.out b/examples/simple/igraph_vs_nonadj.out new file mode 100644 index 0000000..bc74074 --- /dev/null +++ b/examples/simple/igraph_vs_nonadj.out @@ -0,0 +1,2 @@ +10 0 1 2 3 4 5 6 7 8 9 + diff --git a/examples/simple/igraph_vs_range.c b/examples/simple/igraph_vs_range.c new file mode 100644 index 0000000..bfb5c00 --- /dev/null +++ b/examples/simple/igraph_vs_range.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_vs_t vs; + igraph_vit_t vit; + igraph_t g; + igraph_int_t size; + + /* Initialize the library. */ + igraph_setup(); + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + igraph_vs_range(&vs, 0, 10); + igraph_vit_create(&g, vs, &vit); + igraph_vs_size(&g, &vs, &size); + printf("%" IGRAPH_PRId "", size); + + while (!IGRAPH_VIT_END(vit)) { + printf(" %" IGRAPH_PRId "", IGRAPH_VIT_GET(vit)); + IGRAPH_VIT_NEXT(vit); + } + printf("\n"); + + igraph_vit_destroy(&vit); + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_vs_range.out b/examples/simple/igraph_vs_range.out new file mode 100644 index 0000000..1b4b2e8 --- /dev/null +++ b/examples/simple/igraph_vs_range.out @@ -0,0 +1 @@ +10 0 1 2 3 4 5 6 7 8 9 diff --git a/examples/simple/igraph_vs_vector.c b/examples/simple/igraph_vs_vector.c new file mode 100644 index 0000000..486199f --- /dev/null +++ b/examples/simple/igraph_vs_vector.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + + igraph_t g; + igraph_int_t edges_array[] = { 0, 1, 1, 2, 2, 2, 2, 3, 2, 4, 3, 4 }; + igraph_vector_int_t edges = + igraph_vector_int_view(edges_array, sizeof(edges_array) / sizeof(edges_array[0])); + igraph_vector_int_t v2; + igraph_int_t i; + igraph_vit_t vit; + igraph_vs_t vs; + igraph_int_t size; + + /* Initialize the library. */ + igraph_setup(); + + + igraph_create(&g, &edges, 0, IGRAPH_DIRECTED); + + /* Create iterator based on a vector (view) */ + igraph_vector_int_init(&v2, 6); + VECTOR(v2)[0] = 0; + VECTOR(v2)[1] = 2; + VECTOR(v2)[2] = 4; + VECTOR(v2)[3] = 0; + VECTOR(v2)[4] = 2; + VECTOR(v2)[5] = 4; + + igraph_vit_create(&g, igraph_vss_vector(&v2), &vit); + + i = 0; + while (!IGRAPH_VIT_END(vit)) { + if (IGRAPH_VIT_GET(vit) != VECTOR(v2)[i]) { + return 1; + } + IGRAPH_VIT_NEXT(vit); + i++; + } + if (i != igraph_vector_int_size(&v2)) { + return 2; + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&v2); + + /* Create small vector iterator */ + + igraph_vs_vector_small(&vs, 0, 2, 4, 0, 2, 4, 2, -1); + igraph_vit_create(&g, vs, &vit); + igraph_vs_size(&g, &vs, &size); + printf("%" IGRAPH_PRId " ", size); + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + printf("%" IGRAPH_PRId " ", IGRAPH_VIT_GET(vit)); + } + printf("\n"); + + igraph_vit_destroy(&vit); + igraph_vs_destroy(&vs); + + /* Clean up */ + + igraph_destroy(&g); + + return 0; +} diff --git a/examples/simple/igraph_vs_vector.out b/examples/simple/igraph_vs_vector.out new file mode 100644 index 0000000..cb9f83b --- /dev/null +++ b/examples/simple/igraph_vs_vector.out @@ -0,0 +1 @@ +7 0 2 4 0 2 4 2 diff --git a/examples/simple/igraph_weighted_adjacency.c b/examples/simple/igraph_weighted_adjacency.c new file mode 100644 index 0000000..9f21666 --- /dev/null +++ b/examples/simple/igraph_weighted_adjacency.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +int main(void) { + igraph_t graph; + igraph_real_t data[4][4] = { { 0, 1.2, 2.3, 0 }, + { 2.0, 0, 0, 1.0 }, + { 0, 0, 1.5, 0 }, + { 0, 1.0, 0, 0 } }; + + /* C arrays use row-major storage, while igraph's matrix uses column-major. + * The matrix 'mat' will be the transpose of 'data'. */ + const igraph_matrix_t mat = + igraph_matrix_view(*data, sizeof(data[0]) / sizeof(data[0][0]), + sizeof(data) / sizeof(data[0])); + igraph_vector_t weights; + igraph_vector_int_t edges; + igraph_int_t n; + + /* Initialize the library. */ + igraph_setup(); + + /* Initialize vector into which weights will be written. */ + igraph_vector_init(&weights, 0); + + igraph_weighted_adjacency(&graph, &mat, IGRAPH_ADJ_DIRECTED, &weights, IGRAPH_LOOPS_ONCE); + + /* When igraph_weighted_adjacency() returns, 'weights' will typically have + * more capacity allocated than what it uses. We may optionally free any + * unused capacity to save memory, although in most applications this + * is not necessary. */ + igraph_vector_resize_min(&weights); + + /* Get the edge list of the graph and output it, along with the weights. */ + + igraph_vector_int_init(&edges, 0); + igraph_get_edgelist(&graph, &edges, 0); + n = igraph_ecount(&graph); + + for (igraph_int_t i = 0; i < n; i++) { + printf("%" IGRAPH_PRId " --> %" IGRAPH_PRId ": %g\n", + VECTOR(edges)[2*i], VECTOR(edges)[2*i + 1], VECTOR(weights)[i]); + } + + /* Free all allocated storage. */ + igraph_vector_int_destroy(&edges); + igraph_destroy(&graph); + igraph_vector_destroy(&weights); + + return 0; +} diff --git a/examples/simple/igraph_weighted_adjacency.out b/examples/simple/igraph_weighted_adjacency.out new file mode 100644 index 0000000..089a869 --- /dev/null +++ b/examples/simple/igraph_weighted_adjacency.out @@ -0,0 +1,6 @@ +1 --> 0: 1.2 +2 --> 0: 2.3 +0 --> 1: 2 +3 --> 1: 1 +2 --> 2: 1.5 +1 --> 3: 1 diff --git a/examples/simple/igraph_write_graph_lgl.c b/examples/simple/igraph_write_graph_lgl.c new file mode 100644 index 0000000..85c50ee --- /dev/null +++ b/examples/simple/igraph_write_graph_lgl.c @@ -0,0 +1,50 @@ +#include + +int main(void) { + + igraph_t graph; + igraph_strvector_t names; + igraph_vector_t weights; + igraph_int_t i; + igraph_int_t vcount, ecount; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&graph, 7, IGRAPH_UNDIRECTED, + 0,1, 1,3, 1,2, 2,0, 4,2, 3,4, + -1); + vcount = igraph_vcount(&graph); + ecount = igraph_ecount(&graph); + + + printf("Output without isolates:\n"); + igraph_write_graph_lgl(&graph, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 0); + + + printf("\nOutput with isolates:\n"); + igraph_write_graph_lgl(&graph, stdout, /*names*/ NULL, /*weights*/ NULL, /*isolates*/ 1); + + + printf("\nOutput vertex and edge labels:\n"); + igraph_strvector_init(&names, vcount); + for (i = 0; i < vcount; i++) { + char str[2] = " "; /* initialize to ensure presence of null terminator */ + str[0] = 'A' + i; + igraph_strvector_set(&names, i, str); + } + SETVASV(&graph, "names", &names); + + igraph_vector_init_range(&weights, 1, ecount + 1); + SETEANV(&graph, "weights", &weights); + + igraph_write_graph_lgl(&graph, stdout, "names", "weights", /*isolates*/ 0); + + igraph_strvector_destroy(&names); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/igraph_write_graph_lgl.out b/examples/simple/igraph_write_graph_lgl.out new file mode 100644 index 0000000..2370f19 --- /dev/null +++ b/examples/simple/igraph_write_graph_lgl.out @@ -0,0 +1,37 @@ +Output without isolates: +# 0 +1 +2 +# 1 +2 +3 +# 2 +4 +# 3 +4 + +Output with isolates: +# 0 +1 +2 +# 1 +2 +3 +# 2 +4 +# 3 +4 +# 5 +# 6 + +Output vertex and edge labels: +# A +B 1 +C 4 +# B +C 3 +D 2 +# C +E 5 +# D +E 6 diff --git a/examples/simple/igraph_write_graph_pajek.c b/examples/simple/igraph_write_graph_pajek.c new file mode 100644 index 0000000..120fdc6 --- /dev/null +++ b/examples/simple/igraph_write_graph_pajek.c @@ -0,0 +1,76 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_strvector_t names; + + /* Initialize the library. */ + igraph_setup(); + + igraph_set_attribute_table(&igraph_cattribute_table); + + /* save a simple ring graph */ + igraph_ring(&g, 10, IGRAPH_DIRECTED, false /* mutual */, true /* circular */); + igraph_write_graph_pajek(&g, stdout); + + /* add some vertex attributes */ + igraph_strvector_init(&names, 0); + igraph_strvector_push_back(&names, "A"); + igraph_strvector_push_back(&names, "B"); + igraph_strvector_push_back(&names, "C"); + igraph_strvector_push_back(&names, "D"); + igraph_strvector_push_back(&names, "E"); + igraph_strvector_push_back(&names, "F"); + igraph_strvector_push_back(&names, "G"); + igraph_strvector_push_back(&names, "H"); + igraph_strvector_push_back(&names, "I"); + igraph_strvector_push_back(&names, "J"); + SETVASV(&g, "name", &names); + igraph_strvector_destroy(&names); + + /* save the graph with vertex names */ + igraph_write_graph_pajek(&g, stdout); + + igraph_strvector_init(&names, 0); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "escaping spaces"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "escaping\nnewline"); + igraph_strvector_push_back(&names, "square"); + igraph_strvector_push_back(&names, "encoding \"quotes\""); + SETVASV(&g, "shape", &names); + igraph_strvector_destroy(&names); + + /* save the graph with escaped shapes */ + igraph_write_graph_pajek(&g, stdout); + + /* destroy the graph */ + igraph_destroy(&g); + return 0; +} diff --git a/examples/simple/igraph_write_graph_pajek.out b/examples/simple/igraph_write_graph_pajek.out new file mode 100644 index 0000000..c10d7ab --- /dev/null +++ b/examples/simple/igraph_write_graph_pajek.out @@ -0,0 +1,56 @@ +*Vertices 10 +*Arcs +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +10 1 +*Vertices 10 +1 "A" +2 "B" +3 "C" +4 "D" +5 "E" +6 "F" +7 "G" +8 "H" +9 "I" +10 "J" +*Arcs +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +10 1 +*Vertices 10 +1 "A" "square" +2 "B" "square" +3 "C" "square" +4 "D" "square" +5 "E" "escaping spaces" +6 "F" "square" +7 "G" "square" +8 "H" "escaping\nnewline" +9 "I" "square" +10 "J" "encoding "quotes"" +*Arcs +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +10 1 diff --git a/examples/simple/iso_b03_m1000.A00 b/examples/simple/iso_b03_m1000.A00 new file mode 100644 index 0000000000000000000000000000000000000000..196b400b36190f54c01dc3d66bed33a73e0b5cfa GIT binary patch literal 5002 zcmX|_3A~R*7st;zPh3>CvhT9*a>;(l7Dbn+xLLE8WXZlHTlOtN;mW@6dr@RxBSc<` z*IJ>CNU!&M=6c_IKQqtFoZtDKGiS~-&&>Z?q;kSc^2?@3#H5^VQj==ZiJr<^NIM}F zS)nR>X>E|r5{YVZ?E-G1J{d()MK0>Dpj}g*GnG-`n#-HWbIVN#Vpsbf11i^O=huGNSw}0U?T0B7r92FGq;k|2U#=h-q%n+E2lo)p?_@Id zMC}V_D_ucdS+XNv45vwEJx@D@MN&eSM>}9h4D*`G9C8F*w|obNy=ox;z~?e@AMTNi zwbHWit%!->s|@AYRX@~Ug4acnY({SpaeYpvNLQpaWvZk?pTO=BIcUq!P}@u2_%B2G(r$!UtVYsa z-^BAnA&T{pZI>-@ZPA_4Ww@$?@D($8jEE&M-qK4*RuJ{c_%vR&kyR%1Eg$t7>5KJM zGBfZ^1$PGTKH&K-Wexl_>~V0vlWQSErE!SIerck`LA*`r4D*5D`&=Cqa-<}7V)Q9Z zkLDp=Mm#D?HR_&Hlh`iSV>+8&d)vZHs=&}4ABM?h(51sV+}FM#qO&E&ebgtYM{6H+ z9@p1spOXA&ou^gZ3efrro_st%=lOtg9Q&m#&W18pn(_|ws4B&fOsAY?CXo!(;WiE{ zk#vw5+6k`yGM~BE2HyjJF6axyRqb9_iAdg;W_q3RvvTnN^+>4?dKJ1ObvvU;Gk#xx zbT;cwJx|J0kCs9qE)kd(nu~edl(e+VNgVY#x#zL5j&L=>gLI6==sG;TO_@t{FHy>p zyRFH?*+_qs46uHzyFhP3wm+<116UGm5G-BM8-Xkh&*c=~7yj0g)XAg_`d#I9P#s}R zrW@=Ei2BG1MTAA1+1En&|t*lqb!K3sy!;x3sW6jBAw0-63l9?($(pv!c zN+fq^X9KxbC(tSo(8cIKuLD5LWKAM#Me9q-`H)qECAS`jtD+W1V==9i(gIv@ogZfC z8Sb92?WIh?)9hHpiZu)|Ka9TTyh&$E4*moyzRi9$_ih3n`cdSkPiECPL7da(ZePjTQ&S}w*@Vts+Fr*$M~k28a^NL>%sV6B*`s@U1a=ON*vfG_I#7iqmN+}NJIfoa-C%AO`WxZDCs$z{ zBDat=V5aON^pERleDA}aX?*?45~q3kx4wyHJmUkkGLfmN-E=E{n~c+(SlY7@-AFy!T!Y9 zNPUXB6z^*kPBj%_O)kHo|G2$Fd!AjQP7!^KhyMndpfjo6FOx}{oc7aplJ;_$jAtsp z!8-#huWB0@r%PMA%rm`wYuP~!h?Y{<&@r%8dK9~?*s3BU7+;_T;H#%!z){KVF?Q2F zq`qz&!#XN6H~B?7Aiaf^Z*3IPSX~K+@1yR)Nwi~#+Kbv({cMsT(-@r!I#% z3(12l>70;8I}wr5a8?Tp9PH#Pbn+8vjS5Hmk?aSZNfNR1RMsmuD)Bg_Z8NwIJ{W*fv&xw9+V&}q-Niq!|+rWB&=PpLh$zF7xVaM^D>2K$V*-%|# z!{8hz=cqgAXW$Eh_4mN!xo8>vCqZ}C^VG-m6Sxu?+rYDIc<-9)le9A-EyBCk#%0uZ)Y*8aWSq%D&d8qmU+F4( z$E6$Cot)cDu3|xDHTqR}c1GIR=Age=HrjQ1Mdfu+Tg|@}Z-9uWl!B>}#2~F@Ex@jj zU!*niwTxG{SFrl5R;AxEyd}@ta*%5zFIj+xtb)I5Bcff=IdmJ5<3+$WrucVgH*-El zNgX0vi*7|&CQ_eu8Hp2)LG0kFd*0UN!g z8zbxVHtkuOmv+f;a`7+CDSYvnJ`CRs-U?~TOlF~*TH-=v{M1a&>DM6xe7}A|%LRXv zRyP}#C!@0oFKWS<$zGtI*m@snD_?WTbFJu4NvdLQIDkmO|5P4ej(ylWvv;mM#q;3-O+t}<3Onh9zO zd8bksUm_y!Q8|rlru+x|h_JSeHFZ=9wH4j~7@r_y^cR`-8FG+-O)=ZUmkieoM_KhiT=e zbw>tc!QbpXVcjyZ#;N779i-=x@1(KVi<6_e1N13wHxWBZRphYPH;g6NYj|=RY`pvn zJ5}@ok@Wp}H#V}+UPUPlOO{7>k|x}@c>h8^O^{dlw))*@kbMECpMF^%_8~Rju{RG2FRgVU?Avy=iMDg75F`S^Z zM{gk`4dp)AI_i5;Wxa;CV=1kX6lP!d8}(%S6Fc2mrCd@s+$36tcqipyN{|&arE}Qp z;QbrYkDh-`RASLgfp;%yQ*0*ED@X5nxr4QjBsZLo*-y3(j(F0UQz<6}zPR94^7LN{ z+6PUAF8D@xYw{mTYx7g<6KLecihJxQhn1Rx-W=ph zxhT=+(Ft^ld6Uhvi~f!@7rCR{XM8NREF@pOb@$PExD@Y%N^TI7!1`C9wD?VOnN5W0 zDs%47{#6{S{`K@bi^*rfEB`Gfl0PMDRGV>s$9obz_Bi_oOfebawZzLmf{ATTESC2EoP3wh%h zYlcpF9fjX1w6^X**MDDOw?e*{=Mb1vz~J$1PAqE4BJ=UWL~XsM=l2GczmWOeZXVCo z@I44;g&wp@SQBQ_UbnGY++5&VNk1PqlBBa(558g%-|g(t)UclSr59NL{YCjbj5Dj2 av8ga027ivS0qvgAWTZZ`6h0HHYySnxHsUA% literal 0 HcmV?d00001 diff --git a/examples/simple/karate.gml b/examples/simple/karate.gml new file mode 100644 index 0000000..ecafe9c --- /dev/null +++ b/examples/simple/karate.gml @@ -0,0 +1,530 @@ +Creator "Mark Newman on Fri Jul 21 12:39:27 2006" +graph +[ + node + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + node + [ + id 4 + ] + node + [ + id 5 + ] + node + [ + id 6 + ] + node + [ + id 7 + ] + node + [ + id 8 + ] + node + [ + id 9 + ] + node + [ + id 10 + ] + node + [ + id 11 + ] + node + [ + id 12 + ] + node + [ + id 13 + ] + node + [ + id 14 + ] + node + [ + id 15 + ] + node + [ + id 16 + ] + node + [ + id 17 + ] + node + [ + id 18 + ] + node + [ + id 19 + ] + node + [ + id 20 + ] + node + [ + id 21 + ] + node + [ + id 22 + ] + node + [ + id 23 + ] + node + [ + id 24 + ] + node + [ + id 25 + ] + node + [ + id 26 + ] + node + [ + id 27 + ] + node + [ + id 28 + ] + node + [ + id 29 + ] + node + [ + id 30 + ] + node + [ + id 31 + ] + node + [ + id 32 + ] + node + [ + id 33 + ] + node + [ + id 34 + ] + edge + [ + source 2 + target 1 + ] + edge + [ + source 3 + target 1 + ] + edge + [ + source 3 + target 2 + ] + edge + [ + source 4 + target 1 + ] + edge + [ + source 4 + target 2 + ] + edge + [ + source 4 + target 3 + ] + edge + [ + source 5 + target 1 + ] + edge + [ + source 6 + target 1 + ] + edge + [ + source 7 + target 1 + ] + edge + [ + source 7 + target 5 + ] + edge + [ + source 7 + target 6 + ] + edge + [ + source 8 + target 1 + ] + edge + [ + source 8 + target 2 + ] + edge + [ + source 8 + target 3 + ] + edge + [ + source 8 + target 4 + ] + edge + [ + source 9 + target 1 + ] + edge + [ + source 9 + target 3 + ] + edge + [ + source 10 + target 3 + ] + edge + [ + source 11 + target 1 + ] + edge + [ + source 11 + target 5 + ] + edge + [ + source 11 + target 6 + ] + edge + [ + source 12 + target 1 + ] + edge + [ + source 13 + target 1 + ] + edge + [ + source 13 + target 4 + ] + edge + [ + source 14 + target 1 + ] + edge + [ + source 14 + target 2 + ] + edge + [ + source 14 + target 3 + ] + edge + [ + source 14 + target 4 + ] + edge + [ + source 17 + target 6 + ] + edge + [ + source 17 + target 7 + ] + edge + [ + source 18 + target 1 + ] + edge + [ + source 18 + target 2 + ] + edge + [ + source 20 + target 1 + ] + edge + [ + source 20 + target 2 + ] + edge + [ + source 22 + target 1 + ] + edge + [ + source 22 + target 2 + ] + edge + [ + source 26 + target 24 + ] + edge + [ + source 26 + target 25 + ] + edge + [ + source 28 + target 3 + ] + edge + [ + source 28 + target 24 + ] + edge + [ + source 28 + target 25 + ] + edge + [ + source 29 + target 3 + ] + edge + [ + source 30 + target 24 + ] + edge + [ + source 30 + target 27 + ] + edge + [ + source 31 + target 2 + ] + edge + [ + source 31 + target 9 + ] + edge + [ + source 32 + target 1 + ] + edge + [ + source 32 + target 25 + ] + edge + [ + source 32 + target 26 + ] + edge + [ + source 32 + target 29 + ] + edge + [ + source 33 + target 3 + ] + edge + [ + source 33 + target 9 + ] + edge + [ + source 33 + target 15 + ] + edge + [ + source 33 + target 16 + ] + edge + [ + source 33 + target 19 + ] + edge + [ + source 33 + target 21 + ] + edge + [ + source 33 + target 23 + ] + edge + [ + source 33 + target 24 + ] + edge + [ + source 33 + target 30 + ] + edge + [ + source 33 + target 31 + ] + edge + [ + source 33 + target 32 + ] + edge + [ + source 34 + target 9 + ] + edge + [ + source 34 + target 10 + ] + edge + [ + source 34 + target 14 + ] + edge + [ + source 34 + target 15 + ] + edge + [ + source 34 + target 16 + ] + edge + [ + source 34 + target 19 + ] + edge + [ + source 34 + target 20 + ] + edge + [ + source 34 + target 21 + ] + edge + [ + source 34 + target 23 + ] + edge + [ + source 34 + target 24 + ] + edge + [ + source 34 + target 27 + ] + edge + [ + source 34 + target 28 + ] + edge + [ + source 34 + target 29 + ] + edge + [ + source 34 + target 30 + ] + edge + [ + source 34 + target 31 + ] + edge + [ + source 34 + target 32 + ] + edge + [ + source 34 + target 33 + ] +] diff --git a/examples/simple/links.net b/examples/simple/links.net new file mode 100644 index 0000000..175b90b --- /dev/null +++ b/examples/simple/links.net @@ -0,0 +1,16 @@ +% Example Pajek file + +*Network TRALALA +*vertices 4 + 1 "1" 0.0938 0.0896 ellipse x_fact 1 y_fact 1 + 2 "2" 0.8188 0.2458 ellipse x_fact 1 y_fact 1 + 3 "3" 0.3688 0.7792 ellipse x_fact 1 + 4 "4" 0.9583 0.8563 ellipse x_fact 1 +*arcs +1 1 1 h2 0 w 3 c Blue s 3 a1 -130 k1 0.6 a2 -130 k2 0.6 ap 0.5 l "Bezier loop" lc BlueViolet fos 20 lr 58 lp 0.3 la 360 +2 1 1 h2 0 a1 120 k1 1.3 a2 -120 k2 0.3 ap 25 l "Bezier arc" lphi 270 la 180 lr 19 lp 0.5 +1 2 1 h2 0 a1 40 k1 2.8 a2 30 k2 0.8 ap 25 l "Bezier arc" lphi 90 la 0 lp 0.65 +4 2 -1 h2 0 w 1 k1 -2 k2 250 ap 25 l "Circular arc" c Red lc OrangeRed +3 4 1 p Dashed h2 0 w 2 c OliveGreen ap 25 l "Straight arc" lc PineGreen +1 3 1 p Dashed h2 0 w 5 k1 -1 k2 -20 ap 25 l "Oval arc" c Brown lc Black +3 3 -1 h1 6 w 1 h2 12 k1 -2 k2 -15 ap 0.5 l "Circular loop" c Red lc OrangeRed lphi 270 la 180 diff --git a/examples/simple/nodelist1.dl b/examples/simple/nodelist1.dl new file mode 100644 index 0000000..56d73ca --- /dev/null +++ b/examples/simple/nodelist1.dl @@ -0,0 +1,9 @@ +DL n=5 +format = nodelist1 +labels: +george, sally, jim, billy, jane +data: +1 2 3 +2 3 +3 1 +4 3 diff --git a/examples/simple/nodelist2.dl b/examples/simple/nodelist2.dl new file mode 100644 index 0000000..08f2a54 --- /dev/null +++ b/examples/simple/nodelist2.dl @@ -0,0 +1,8 @@ +DL n=5 +format = nodelist1 +labels embedded: +data: +george sally jim +sally jim +billy george +jane jim diff --git a/examples/simple/random_seed.c b/examples/simple/random_seed.c new file mode 100644 index 0000000..306a0b8 --- /dev/null +++ b/examples/simple/random_seed.c @@ -0,0 +1,37 @@ + +#include + +int main(void) { + igraph_t g1, g2; + igraph_bool_t iso; + + /* Initialize the library. */ + igraph_setup(); + + /* Seed the default random number generator and create a random graph. */ + igraph_rng_seed(igraph_rng_default(), 1122); + + igraph_erdos_renyi_game_gnp(&g1, 100, 3.0 / 100, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* Seed the generator with the same seed again, + * and create a graph with the same method. */ + + igraph_rng_seed(igraph_rng_default(), 1122); + + igraph_erdos_renyi_game_gnp(&g2, 100, 3.0 / 100, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* The two graphs will be identical. */ + + igraph_is_same_graph(&g1, &g2, &iso); + + if (!iso) { + return 1; + } + + /* Destroy no longer needed data structures. */ + + igraph_destroy(&g2); + igraph_destroy(&g1); + + return 0; +} diff --git a/examples/simple/safelocale.c b/examples/simple/safelocale.c new file mode 100644 index 0000000..686a313 --- /dev/null +++ b/examples/simple/safelocale.c @@ -0,0 +1,52 @@ + +#include + +#include +#include +#include + +int main(void) { + const char *filename = "weighted.gml"; + igraph_t graph; + igraph_safelocale_t loc; + + /* Initialize the library. */ + igraph_setup(); + + /* Attempt to set a locale that uses a decimal comma. Locale names + * differ between platforms, and not all locales are available, + * so the locale change may not be successful. */ + const char *locname = setlocale(LC_ALL, "de_DE"); + struct lconv *lc = localeconv(); + if (strcmp(lc->decimal_point, ",")) { + /* If decimal point is not a comma, presumably because the requested + * locale was not available, report locale information. */ + fprintf(stderr, "setlocale() returned '%s', decimal point is '%s'\n", + locname ? locname : "NULL", + lc->decimal_point); + } + + FILE *file = fopen(filename, "r"); + if (! file) { + fprintf(stderr, "Cannot open %s file.\n", filename); + exit(1); + } + + /* An attribute table is needed to read graph attributes. */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* At this point, the current locale may use decimal commas. + * We temporarily set a C locale using enter_safelocale() to + * allow the GML reader and writer to work correctly.*/ + igraph_enter_safelocale(&loc); + if (igraph_read_graph_gml(&graph, file) != IGRAPH_SUCCESS) { + fprintf(stderr, "Reading %s failed.\n", filename); + abort(); + } + igraph_write_graph_gml(&graph, stdout, IGRAPH_WRITE_GML_DEFAULT_SW, NULL, ""); + igraph_exit_safelocale(&loc); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/simple/safelocale.out b/examples/simple/safelocale.out new file mode 100644 index 0000000..cfdb381 --- /dev/null +++ b/examples/simple/safelocale.out @@ -0,0 +1,21 @@ +Version 1 +graph +[ + directed 0 + node + [ + id 1 + value 1.23 + ] + node + [ + id 2 + value 4.87e-05 + ] + edge + [ + source 2 + target 1 + weight -377.9 + ] +] diff --git a/examples/simple/test.graphml b/examples/simple/test.graphml new file mode 100644 index 0000000..a6ab576 --- /dev/null +++ b/examples/simple/test.graphml @@ -0,0 +1,55 @@ + + + + + yellow + + + + + + 1 + + + 2006-11-12 + + + + green + incorrect + true + + + + blue + 0 + + + red "with entities" + + + + false + + + turquoise + fAlSe + + + 1.0 + + + 1.0 + + + 2.0 + + + + + + 1.1 + + + diff --git a/examples/simple/walktrap.c b/examples/simple/walktrap.c new file mode 100644 index 0000000..60c519c --- /dev/null +++ b/examples/simple/walktrap.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +int main(void) { + igraph_t g; + igraph_matrix_int_t merges; + igraph_vector_t modularity; + igraph_int_t no_of_nodes; + igraph_int_t i; + + /* Initialize the library. */ + igraph_setup(); + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, 0, 5, 4, 9, -1); + igraph_vector_init(&modularity, 0); + igraph_matrix_int_init(&merges, 0, 0); + + igraph_community_walktrap(&g, + NULL /* no weights */, + 4 /* steps */, + &merges, &modularity, + /* membership=*/ NULL); + + no_of_nodes = igraph_vcount(&g); + printf("Merges:\n"); + for (i = 0; i < igraph_matrix_int_nrow(&merges); i++) { + printf("%2.1" IGRAPH_PRId " + %2." IGRAPH_PRId " -> %2." IGRAPH_PRId " (modularity %4.2f)\n", + MATRIX(merges, i, 0), MATRIX(merges, i, 1), + no_of_nodes + i, VECTOR(modularity)[i]); + } + + igraph_destroy(&g); + + igraph_matrix_int_destroy(&merges); + igraph_vector_destroy(&modularity); + + return 0; +} diff --git a/examples/simple/weighted.gml b/examples/simple/weighted.gml new file mode 100644 index 0000000..79d3638 --- /dev/null +++ b/examples/simple/weighted.gml @@ -0,0 +1,17 @@ +# This file is used to test the handling of decimal points +# under different locales. +graph [ + node [ + id 1 + value 1.23 + ] + node [ + id 2 + value 4.87e-5 + ] + edge [ + source 1 + target 2 + weight -3.779e+2 + ] +] diff --git a/examples/tutorial/tutorial1.c b/examples/tutorial/tutorial1.c new file mode 100644 index 0000000..86768ef --- /dev/null +++ b/examples/tutorial/tutorial1.c @@ -0,0 +1,33 @@ +#include + +int main(void) { + igraph_int_t num_vertices = 1000; + igraph_int_t num_edges = 1000; + igraph_real_t diameter, mean_degree; + igraph_t graph; + + /* Initialize the library. */ + igraph_setup(); + + /* Ensure identical results across runs. */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_erdos_renyi_game_gnm( + &graph, num_vertices, num_edges, + IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_diameter( + &graph, /* weights = */ NULL, + &diameter, + /* from = */ NULL, /* to = */ NULL, + /* vertex_path = */ NULL, /* edge_path = */ NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + + igraph_mean_degree(&graph, &mean_degree, IGRAPH_LOOPS); + printf("Diameter of a random graph with average degree %g: %g\n", + mean_degree, diameter); + + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/tutorial/tutorial2.c b/examples/tutorial/tutorial2.c new file mode 100644 index 0000000..8ce1e19 --- /dev/null +++ b/examples/tutorial/tutorial2.c @@ -0,0 +1,45 @@ +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t dimvector; + igraph_vector_int_t edges; + igraph_vector_bool_t periodic; + igraph_real_t avg_path_len; + + /* Initialize the library. */ + igraph_setup(); + + igraph_vector_int_init(&dimvector, 2); + VECTOR(dimvector)[0] = 30; + VECTOR(dimvector)[1] = 30; + + igraph_vector_bool_init(&periodic, 2); + igraph_vector_bool_fill(&periodic, true); + igraph_square_lattice(&graph, &dimvector, 0, IGRAPH_UNDIRECTED, + /* mutual= */ false, &periodic); + + igraph_average_path_length(&graph, NULL, &avg_path_len, NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + printf("Average path length (lattice): %g\n", (double) avg_path_len); + + /* Seed the RNG to ensure identical results across runs. */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&edges, 20); + for (igraph_int_t i = 0; i < igraph_vector_int_size(&edges); i++) { + VECTOR(edges)[i] = RNG_INTEGER(0, igraph_vcount(&graph) - 1); + } + + igraph_add_edges(&graph, &edges, NULL); + igraph_average_path_length(&graph, NULL, &avg_path_len, NULL, + IGRAPH_UNDIRECTED, /* unconn= */ true); + printf("Average path length (randomized lattice): %g\n", (double) avg_path_len); + + igraph_vector_bool_destroy(&periodic); + igraph_vector_int_destroy(&dimvector); + igraph_vector_int_destroy(&edges); + igraph_destroy(&graph); + + return 0; +} diff --git a/examples/tutorial/tutorial3.c b/examples/tutorial/tutorial3.c new file mode 100644 index 0000000..72e31cd --- /dev/null +++ b/examples/tutorial/tutorial3.c @@ -0,0 +1,53 @@ +#include + +int main(void) { + igraph_t graph; + igraph_vector_int_t result; + igraph_vector_t result_real; + igraph_int_t edges_array[] = { + 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, + 0,10, 0,11, 0,12, 0,13, 0,17, 0,19, 0,21, 0,31, + 1, 2, 1, 3, 1, 7, 1,13, 1,17, 1,19, 1,21, 1,30, + 2, 3, 2, 7, 2,27, 2,28, 2,32, 2, 9, 2, 8, 2,13, + 3, 7, 3,12, 3,13, 4, 6, 4,10, 5, 6, 5,10, 5,16, + 6,16, 8,30, 8,32, 8,33, 9,33, 13,33, 14,32, 14,33, + 15,32, 15,33, 18,32, 18,33, 19,33, 20,32, 20,33, + 22,32, 22,33, 23,25, 23,27, 23,32, 23,33, 23,29, + 24,25, 24,27, 24,31, 25,31, 26,29, 26,33, 27,33, + 28,31, 28,33, 29,32, 29,33, 30,32, 30,33, 31,32, + 31,33, 32,33 + }; + igraph_vector_int_t edges = + igraph_vector_int_view(edges_array, sizeof(edges_array) / sizeof(edges_array[0])); + + /* Initialize the library. */ + igraph_setup(); + + igraph_create(&graph, &edges, 0, IGRAPH_UNDIRECTED); + + igraph_vector_int_init(&result, 0); + igraph_vector_init(&result_real, 0); + + igraph_degree(&graph, &result, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + printf("Maximum degree is %10" IGRAPH_PRId ", vertex %2" IGRAPH_PRId ".\n", + igraph_vector_int_max(&result), + igraph_vector_int_which_max(&result)); + + igraph_closeness(&graph, &result_real, NULL, NULL, igraph_vss_all(), + IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ false); + printf("Maximum closeness is %10g, vertex %2" IGRAPH_PRId ".\n", + (double) igraph_vector_max(&result_real), + igraph_vector_which_max(&result_real)); + + igraph_betweenness(&graph, /* weights= */ NULL, &result_real, igraph_vss_all(), + IGRAPH_UNDIRECTED, /* normalized= */ false); + printf("Maximum betweenness is %10g, vertex %2" IGRAPH_PRId ".\n", + (double) igraph_vector_max(&result_real), + igraph_vector_which_max(&result_real)); + + igraph_vector_int_destroy(&result); + igraph_vector_destroy(&result_real); + igraph_destroy(&graph); + + return 0; +} diff --git a/igraph.pc.in b/igraph.pc.in new file mode 100644 index 0000000..f869537 --- /dev/null +++ b/igraph.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@PKGCONFIG_LIBDIR@ +includedir=@PKGCONFIG_INCLUDEDIR@ + +Name: libigraph +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +URL: @PROJECT_HOMEPAGE_URL@ +Libs: -L${libdir} -ligraph +Libs.private: @PKGCONFIG_LIBS_PRIVATE@ +Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@ +Cflags: -I${includedir}/igraph diff --git a/include/igraph.h b/include/igraph.h new file mode 100644 index 0000000..72b827c --- /dev/null +++ b/include/igraph.h @@ -0,0 +1,97 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_H +#define IGRAPH_H + +#include "igraph_version.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_random.h" +#include "igraph_progress.h" +#include "igraph_setup.h" +#include "igraph_statusbar.h" + +#include "igraph_types.h" +#include "igraph_complex.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" +#include "igraph_bitset.h" +#include "igraph_dqueue.h" +#include "igraph_stack.h" +#include "igraph_heap.h" +#include "igraph_psumtree.h" +#include "igraph_strvector.h" +#include "igraph_vector_list.h" +#include "igraph_vector_ptr.h" +#include "igraph_sparsemat.h" +#include "igraph_qsort.h" + +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_graph_list.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_games.h" +#include "igraph_centrality.h" +#include "igraph_paths.h" +#include "igraph_components.h" +#include "igraph_structural.h" +#include "igraph_transitivity.h" +#include "igraph_neighborhood.h" +#include "igraph_isomorphism.h" +#include "igraph_bipartite.h" +#include "igraph_cliques.h" +#include "igraph_layout.h" +#include "igraph_visitor.h" +#include "igraph_community.h" +#include "igraph_conversion.h" +#include "igraph_foreign.h" +#include "igraph_motifs.h" +#include "igraph_operators.h" +#include "igraph_flow.h" +#include "igraph_nongraph.h" +#include "igraph_cocitation.h" +#include "igraph_adjlist.h" +#include "igraph_attributes.h" +#include "igraph_blas.h" +#include "igraph_lapack.h" +#include "igraph_arpack.h" +#include "igraph_mixing.h" +#include "igraph_separators.h" +#include "igraph_cohesive_blocks.h" +#include "igraph_eigen.h" +#include "igraph_hrg.h" +#include "igraph_threading.h" +#include "igraph_interrupt.h" +#include "igraph_matching.h" +#include "igraph_embedding.h" +#include "igraph_scan.h" +#include "igraph_graphlets.h" +#include "igraph_epidemics.h" +#include "igraph_lsap.h" +#include "igraph_coloring.h" +#include "igraph_eulerian.h" +#include "igraph_graphicality.h" +#include "igraph_cycles.h" +#include "igraph_reachability.h" +#include "igraph_spatial.h" +#include "igraph_sampling.h" + +#endif diff --git a/include/igraph_adjlist.h b/include/igraph_adjlist.h new file mode 100644 index 0000000..175de6d --- /dev/null +++ b/include/igraph_adjlist.h @@ -0,0 +1,220 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ADJLIST_H +#define IGRAPH_ADJLIST_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +typedef struct igraph_adjlist_t { + igraph_int_t length; + igraph_vector_int_t *adjs; +} igraph_adjlist_t; + +typedef struct igraph_inclist_t { + igraph_int_t length; + igraph_vector_int_t *incs; +} igraph_inclist_t; + +IGRAPH_EXPORT igraph_error_t igraph_adjlist_init(const igraph_t *graph, igraph_adjlist_t *al, + igraph_neimode_t mode, igraph_loops_t loops, + igraph_bool_t multiple); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_init_empty(igraph_adjlist_t *al, igraph_int_t no_of_nodes); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_adjlist_size(const igraph_adjlist_t *al); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_init_complementer(const igraph_t *graph, + igraph_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_init_from_inclist( + const igraph_t *graph, igraph_adjlist_t *al, const igraph_inclist_t *il); +IGRAPH_EXPORT void igraph_adjlist_destroy(igraph_adjlist_t *al); +IGRAPH_EXPORT void igraph_adjlist_clear(igraph_adjlist_t *al); +IGRAPH_EXPORT void igraph_adjlist_sort(igraph_adjlist_t *al); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_simplify(igraph_adjlist_t *al); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_print(const igraph_adjlist_t *al); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_fprint(const igraph_adjlist_t *al, FILE *outfile); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_adjlist_has_edge(igraph_adjlist_t* al, igraph_int_t from, igraph_int_t to, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_adjlist_replace_edge(igraph_adjlist_t* al, igraph_int_t from, igraph_int_t oldto, igraph_int_t newto, igraph_bool_t directed); + +/** + * \define igraph_adjlist_get + * \brief Query a vector in an adjacency list. + * + * Returns a pointer to an \ref igraph_vector_int_t object from an + * adjacency list. The vector can be modified as desired. + * + * \param al The adjacency list object. + * \param no The vertex whose adjacent vertices will be returned. + * \return Pointer to the \ref igraph_vector_int_t object. + * + * Time complexity: O(1). + */ +#define igraph_adjlist_get(al,no) (&(al)->adjs[(igraph_int_t)(no)]) + +IGRAPH_EXPORT igraph_error_t igraph_adjlist(igraph_t *graph, const igraph_adjlist_t *adjlist, + igraph_neimode_t mode, igraph_bool_t duplicate); + +IGRAPH_EXPORT igraph_error_t igraph_inclist_init(const igraph_t *graph, + igraph_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_inclist_init_empty(igraph_inclist_t *il, igraph_int_t n); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_inclist_size(const igraph_inclist_t *al); +IGRAPH_EXPORT void igraph_inclist_destroy(igraph_inclist_t *il); +IGRAPH_EXPORT void igraph_inclist_clear(igraph_inclist_t *il); +IGRAPH_EXPORT igraph_error_t igraph_inclist_print(const igraph_inclist_t *il); +IGRAPH_EXPORT igraph_error_t igraph_inclist_fprint(const igraph_inclist_t *il, FILE *outfile); + +/** + * \define igraph_inclist_get + * \brief Query a vector in an incidence list. + * + * Returns a pointer to an igraph_vector_int_t object from an + * incidence list containing edge IDs. The vector can be modified, + * resized, etc. as desired. + * \param il Pointer to the incidence list. + * \param no The vertex for which the incident edges are returned. + * \return Pointer to an igraph_vector_int_t object. + * + * Time complexity: O(1). + */ +#define igraph_inclist_get(il,no) (&(il)->incs[(igraph_int_t)(no)]) + +typedef struct igraph_lazy_adjlist_t { + const igraph_t *graph; + igraph_int_t length; + igraph_vector_int_t **adjs; + igraph_neimode_t mode; + igraph_loops_t loops; + igraph_bool_t multiple; +} igraph_lazy_adjlist_t; + +IGRAPH_EXPORT igraph_error_t igraph_lazy_adjlist_init(const igraph_t *graph, + igraph_lazy_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops, + igraph_bool_t multiple); +IGRAPH_EXPORT void igraph_lazy_adjlist_destroy(igraph_lazy_adjlist_t *al); +IGRAPH_EXPORT void igraph_lazy_adjlist_clear(igraph_lazy_adjlist_t *al); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_lazy_adjlist_size(const igraph_lazy_adjlist_t *al); + +/** + * \define igraph_lazy_adjlist_has + * \brief Are adjacenct vertices already stored in a lazy adjacency list? + * + * \param al The lazy adjacency list. + * \param no The vertex ID to query. + * \return True if the adjacent vertices of this vertex are already computed + * and stored, false otherwise. + * + * Time complexity: O(1). + */ +#define igraph_lazy_adjlist_has(al,no) ((al)->adjs[(igraph_int_t)(no)] != NULL) + +/** + * \define igraph_lazy_adjlist_get + * \brief Query neighbor vertices. + * + * If the function is called for the first time for a vertex then the + * result is stored in the adjacency list and no further query + * operations are needed when the neighbors of the same vertex are + * queried again. + * + * \param al The lazy adjacency list. + * \param no The vertex ID to query. + * \return Pointer to a vector, or \c NULL upon error. Errors can only + * occur the first time this function is called for a given vertex. + * It is safe to modify this vector, + * modification does not affect the original graph. + * + * \sa \ref igraph_lazy_adjlist_has() to check if this function has + * already been called for a vertex. + * + * Time complexity: O(d), the number of neighbor vertices for the + * first time, O(1) for subsequent calls. + */ +#define igraph_lazy_adjlist_get(al,no) \ + (igraph_lazy_adjlist_has(al,no) ? ((al)->adjs[(igraph_int_t)(no)]) \ + : (igraph_i_lazy_adjlist_get_real(al, no))) +IGRAPH_EXPORT igraph_vector_int_t *igraph_i_lazy_adjlist_get_real(igraph_lazy_adjlist_t *al, igraph_int_t no); + +typedef struct igraph_lazy_inclist_t { + const igraph_t *graph; + igraph_int_t length; + igraph_vector_int_t **incs; + igraph_neimode_t mode; + igraph_loops_t loops; +} igraph_lazy_inclist_t; + +IGRAPH_EXPORT igraph_error_t igraph_lazy_inclist_init(const igraph_t *graph, + igraph_lazy_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops); +IGRAPH_EXPORT void igraph_lazy_inclist_destroy(igraph_lazy_inclist_t *il); +IGRAPH_EXPORT void igraph_lazy_inclist_clear(igraph_lazy_inclist_t *il); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_lazy_inclist_size(const igraph_lazy_inclist_t *il); + +/** + * \define igraph_lazy_inclist_has + * \brief Are incident edges already stored in a lazy inclist? + * + * \param il The lazy incidence list. + * \param no The vertex ID to query. + * \return True if the incident edges of this vertex are already computed + * and stored, false otherwise. + * + * Time complexity: O(1). + */ +#define igraph_lazy_inclist_has(il,no) ((il)->incs[(igraph_int_t)(no)] != NULL) + +/** + * \define igraph_lazy_inclist_get + * \brief Query incident edges. + * + * If the function is called for the first time for a vertex, then the + * result is stored in the incidence list and no further query + * operations are needed when the incident edges of the same vertex are + * queried again. + * + * \param il The lazy incidence list object. + * \param no The vertex ID to query. + * \return Pointer to a vector, or \c NULL upon error. Errors can only + * occur the first time this function is called for a given vertex. + * It is safe to modify this vector, + * modification does not affect the original graph. + * + * \sa \ref igraph_lazy_inclist_has() to check if this function has + * already been called for a vertex. + * + * Time complexity: O(d), the number of incident edges for the first + * time, O(1) for subsequent calls with the same \p no argument. + */ +#define igraph_lazy_inclist_get(il,no) \ + (igraph_lazy_inclist_has(il,no) ? ((il)->incs[(igraph_int_t)(no)]) \ + : (igraph_i_lazy_inclist_get_real(il,no))) +IGRAPH_EXPORT igraph_vector_int_t *igraph_i_lazy_inclist_get_real(igraph_lazy_inclist_t *il, igraph_int_t no); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_arpack.h b/include/igraph_arpack.h new file mode 100644 index 0000000..5912a66 --- /dev/null +++ b/include/igraph_arpack.h @@ -0,0 +1,395 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ARPACK_H +#define IGRAPH_ARPACK_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_arpack ARPACK interface in igraph + * + * + * ARPACK is a library for solving large scale eigenvalue problems. + * The package is designed to compute a few eigenvalues and corresponding + * eigenvectors of a general \c n by \c n matrix \c A. It is + * most appropriate for large sparse or structured matrices \c A where + * structured means that a matrix-vector product w <- Av requires + * order \c n rather than the usual order n^2 floating point + * operations. Please see https://github.com/opencollab/arpack-ng for details. + * + * + * + * The eigenvalue calculation in ARPACK (in the simplest + * case) involves the calculation of the \c Av product where \c A + * is the matrix we work with and \c v is an arbitrary vector. A + * user-defined function of type \ref igraph_arpack_function_t + * is expected to perform this product. If the product can be done + * efficiently, e.g. if the matrix is sparse, then ARPACK is usually + * able to calculate the eigenvalues very quickly. + * + * + * In igraph, eigenvalue/eigenvector calculations usually + * involve the following steps: + * \olist + * \oli Initialization of an \ref igraph_arpack_options_t data + * structure using \ref igraph_arpack_options_init. + * \oli Setting some options in the initialized \ref + * igraph_arpack_options_t object. + * \oli Defining a function of type \ref igraph_arpack_function_t. + * The input of this function is a vector, and the output + * should be the output matrix multiplied by the input vector. + * \oli Calling \ref igraph_arpack_rssolve() (is the matrix is + * symmetric), or \ref igraph_arpack_rnsolve(). + * \endolist + * The \ref igraph_arpack_options_t object can be used multiple + * times. + * + * + * + * If we have many eigenvalue problems to solve, then it might worth + * to create an \ref igraph_arpack_storage_t object, and initialize it + * via \ref igraph_arpack_storage_init(). This structure contains all + * memory needed for ARPACK (with the given upper limit regerding to + * the size of the eigenvalue problem). Then many problems can be + * solved using the same \ref igraph_arpack_storage_t object, without + * always reallocating the required memory. + * The \ref igraph_arpack_storage_t object needs to be destroyed by + * calling \ref igraph_arpack_storage_destroy() on it, when it is not + * needed any more. + * + * + * + * igraph does not contain all + * ARPACK routines, only the ones dealing with symmetric and + * non-symmetric eigenvalue problems using double precision real + * numbers. + * + * + */ + +/** + * \struct igraph_arpack_options_t + * \brief Options for ARPACK. + * + * This data structure contains the options of the ARPACK eigenvalue + * solver routines. It must be initialized by calling \ref + * igraph_arpack_options_init() on it. Then it can be used for + * multiple ARPACK calls, as the ARPACK solvers do not modify it. + * + * Input options: + * + * \member bmat Character. Whether to solve a standard ('I') ot a + * generalized problem ('B'). + * \member n Dimension of the eigenproblem. + * \member which Specifies which eigenvalues/vectors to + * compute. Possible values for symmetric matrices: + * \clist \cli LA + * Compute \c nev largest (algebraic) eigenvalues. + * \cli SA + * Compute \c nev smallest (algebraic) eigenvalues. + * \cli LM + * Compute \c nev largest (in magnitude) eigenvalues. + * \cli SM + * Compute \c nev smallest (in magnitude) eigenvalues. + * \cli BE + * Compute \c nev eigenvalues, half from each end of + * the spectrum. When \c nev is odd, compute one + * more from the high en than from the low + * end. \endclist + * Possible values for non-symmetric matrices: + * \clist \cli LM + * Compute \c nev largest (in magnitude) eigenvalues. + * \cli SM + * Compute \c nev smallest (in magnitude) eigenvalues. + * \cli LR + * Compute \c nev eigenvalues of largest real part. + * \cli SR + * Compute \c nev eigenvalues of smallest real part. + * \cli LI + * Compute \c nev eigenvalues of largest imaginary part. + * \cli SI + * Compute \c nev eigenvalues of smallest imaginary + * part. \endclist + * \member nev The number of eigenvalues to be computed. + * \member tol Stopping criterion: the relative accuracy + * of the Ritz value is considered acceptable if its error is less + * than \c tol times its estimated value. If this is set to zero + * then machine precision is used. + * \member ncv Number of Lanczos vectors to be generated. Setting this + * to zero means that \ref igraph_arpack_rssolve and \ref igraph_arpack_rnsolve + * will determine a suitable value for \c ncv automatically. + * \member ldv Numberic scalar. It should be set to + * zero in the current igraph implementation. + * \member ishift Either zero or one. If zero then the shifts are + * provided by the user via reverse communication. If one then exact + * shifts with respect to the reduced tridiagonal matrix \c T. + * Please always set this to one. + * \member mxiter Maximum number of Arnoldi update iterations allowed. + * \member nb Blocksize to be used in the recurrence. Please always + * leave this on the default value, one. + * \member mode The type of the eigenproblem to be solved. + * Possible values if the input matrix is symmetric: + * \olist + * \oli A*x=lambda*x, A is symmetric. + * \oli A*x=lambda*M*x, A is + * symmetric, M is symmetric positive definite. + * \oli K*x=lambda*M*x, K is + * symmetric, M is symmetric positive semi-definite. + * \oli K*x=lambda*KG*x, K is + * symmetric positive semi-definite, KG is symmetric + * indefinite. + * \oli A*x=lambda*M*x, A is + * symmetric, M is symmetric positive + * semi-definite. (Cayley transformed mode.) \endolist + * Please note that only \c mode ==1 was tested and other values + * might not work properly. + * Possible values if the input matrix is not symmetric: + * \olist + * \oli A*x=lambda*x. + * \oli A*x=lambda*M*x, M is + * symmetric positive definite. + * \oli A*x=lambda*M*x, M is + * symmetric semi-definite. + * \oli A*x=lambda*M*x, M is + * symmetric semi-definite. \endolist + * Please note that only \c mode == 1 was tested and other values + * might not work properly. + * \member start Whether to use the supplied starting vector (1), or + * use a random starting vector (0). The starting vector must be + * supplied in the first column of the \c vectors argument of the + * \ref igraph_arpack_rssolve() of \ref igraph_arpack_rnsolve() call. + * + * Output options: + * + * \member info Error flag of ARPACK. Possible values: + * \clist \cli 0 + * Normal exit. + * \cli 1 + * Maximum number of iterations taken. + * \cli 3 + * No shifts could be applied during a cycle of the + * Implicitly restarted Arnoldi iteration. One possibility + * is to increase the size of \c ncv relative to \c + * nev. \endclist + * ARPACK can return other error flags as well, but these are + * converted to igraph errors, see \ref igraph_error_type_t. + * \member ierr Error flag of the second ARPACK call (one eigenvalue + * computation usually involves two calls to ARPACK). This is + * always zero, as other error codes are converted to igraph errors. + * \member noiter Number of Arnoldi iterations taken. + * \member nconv Number of converged Ritz values. This + * represents the number of Ritz values that satisfy the + * convergence critetion. + * \member numop Total number of matrix-vector multiplications. + * \member numopb Not used currently. + * \member numreo Total number of steps of re-orthogonalization. + * + * Internal options: + * \member lworkl Do not modify this option. + * \member sigma The shift for the shift-invert mode. + * \member sigmai The imaginary part of the shift, for the + * non-symmetric or complex shift-invert mode. + * \member iparam Do not modify this option. + * \member ipntr Do not modify this option. + * + */ + +typedef struct igraph_arpack_options_t { + /* INPUT */ + char bmat[1]; /* I-standard problem, G-generalized */ + int n; /* Dimension of the eigenproblem */ + char which[2]; /* LA, SA, LM, SM, BE */ + int nev; /* Number of eigenvalues to be computed */ + igraph_real_t tol; /* Stopping criterion */ + int ncv; /* Number of columns in V */ + int ldv; /* Leading dimension of V */ + int ishift; /* 0-reverse comm., 1-exact with tridiagonal */ + int mxiter; /* Maximum number of update iterations to take */ + int nb; /* Block size on the recurrence, only 1 works */ + int mode; /* The kind of problem to be solved (1-5) + 1: A*x=l*x, A symmetric + 2: A*x=l*M*x, A symm. M pos. def. + 3: K*x = l*M*x, K symm., M pos. semidef. + 4: K*x = l*KG*x, K s. pos. semidef. KG s. indef. + 5: A*x = l*M*x, A symm., M symm. pos. semidef. */ + int start; /* 0: random, 1: use the supplied vector */ + int lworkl; /* Size of temporary storage, default is fine */ + igraph_real_t sigma; /* The shift for modes 3,4,5 */ + igraph_real_t sigmai; /* The imaginary part of shift for rnsolve */ + /* OUTPUT */ + int info; /* What happened, see docs */ + int ierr; /* What happened in the dseupd call */ + int noiter; /* The number of iterations taken */ + int nconv; + int numop; /* Number of OP*x operations */ + int numopb; /* Number of B*x operations if BMAT='G' */ + int numreo; /* Number of steps of re-orthogonalizations */ + /* INTERNAL */ + int iparam[11]; + int ipntr[14]; +} igraph_arpack_options_t; + +/** + * \struct igraph_arpack_storage_t + * \brief Storage for ARPACK. + * + * Public members, do not modify them directly, these are considered + * to be read-only. + * \member maxn Maximum rank of matrix. + * \member maxncv Maximum NCV. + * \member maxldv Maximum LDV. + * + * These members are considered to be private: + * \member workl Working memory. + * \member workd Working memory. + * \member d Memory for eigenvalues. + * \member resid Memory for residuals. + * \member ax Working memory. + * \member select Working memory. + * \member di Memory for eigenvalues, non-symmetric case only. + * \member workev Working memory, non-symmetric case only. + */ + +typedef struct igraph_arpack_storage_t { + int maxn, maxncv, maxldv; + igraph_real_t *v; + igraph_real_t *workl; + igraph_real_t *workd; + igraph_real_t *d; + igraph_real_t *resid; + igraph_real_t *ax; + int *select; + /* The following two are only used for non-symmetric problems: */ + igraph_real_t *di; + igraph_real_t *workev; +} igraph_arpack_storage_t; + +/** + * \typedef igraph_arpack_error_t + * \brief Error codes from ARPACK. + * + * These error codes represent error conditions returned from ARPACK. + * They are used internally to format error messages when igraph itself + * returns an \c IGRAPH_EARPACK error code from an ARPACK-related function. + * + * \enumval IGRAPH_ARPACK_NO_ERROR No error was encountered in ARPACK. + * \enumval IGRAPH_ARPACK_PROD Matrix-vector product failed (not used any more). + * \enumval IGRAPH_ARPACK_NPOS N must be positive. + * \enumval IGRAPH_ARPACK_NEVNPOS NEV must be positive. + * \enumval IGRAPH_ARPACK_NCVSMALL NCV must be bigger. + * \enumval IGRAPH_ARPACK_NONPOSI Maximum number of iterations should be positive. + * \enumval IGRAPH_ARPACK_WHICHINV Invalid WHICH parameter. + * \enumval IGRAPH_ARPACK_BMATINV Invalid BMAT parameter. + * \enumval IGRAPH_ARPACK_WORKLSMALL WORKL is too small. + * \enumval IGRAPH_ARPACK_TRIDERR LAPACK error in tridiagonal eigenvalue calculation. + * \enumval IGRAPH_ARPACK_ZEROSTART Starting vector is zero. + * \enumval IGRAPH_ARPACK_MODEINV MODE is invalid. + * \enumval IGRAPH_ARPACK_MODEBMAT MODE and BMAT are not compatible. + * \enumval IGRAPH_ARPACK_ISHIFT ISHIFT must be 0 or 1. + * \enumval IGRAPH_ARPACK_NEVBE NEV and WHICH='BE' are incompatible. + * \enumval IGRAPH_ARPACK_NOFACT Could not build an Arnoldi factorization. + * \enumval IGRAPH_ARPACK_FAILED No eigenvalues to sufficient accuracy. + * \enumval IGRAPH_ARPACK_HOWMNY HOWMNY is invalid. + * \enumval IGRAPH_ARPACK_HOWMNYS HOWMNY='S' is not implemented. + * \enumval IGRAPH_ARPACK_EVDIFF Different number of converged Ritz values. + * \enumval IGRAPH_ARPACK_SHUR Error from calculation of a real Schur form. + * \enumval IGRAPH_ARPACK_LAPACK LAPACK (dtrevc) error for calculating eigenvectors. + * \enumval IGRAPH_ARPACK_UNKNOWN Unknown ARPACK error. + */ +typedef enum { + IGRAPH_ARPACK_NO_ERROR = 0, + IGRAPH_ARPACK_PROD = 15, + IGRAPH_ARPACK_NPOS = 16, + IGRAPH_ARPACK_NEVNPOS = 17, + IGRAPH_ARPACK_NCVSMALL = 18, + IGRAPH_ARPACK_NONPOSI = 19, + IGRAPH_ARPACK_WHICHINV = 20, + IGRAPH_ARPACK_BMATINV = 21, + IGRAPH_ARPACK_WORKLSMALL = 22, + IGRAPH_ARPACK_TRIDERR = 23, + IGRAPH_ARPACK_ZEROSTART = 24, + IGRAPH_ARPACK_MODEINV = 25, + IGRAPH_ARPACK_MODEBMAT = 26, + IGRAPH_ARPACK_ISHIFT = 27, + IGRAPH_ARPACK_NEVBE = 28, + IGRAPH_ARPACK_NOFACT = 29, + IGRAPH_ARPACK_FAILED = 30, + IGRAPH_ARPACK_HOWMNY = 31, + IGRAPH_ARPACK_HOWMNYS = 32, + IGRAPH_ARPACK_EVDIFF = 33, + IGRAPH_ARPACK_SHUR = 34, + IGRAPH_ARPACK_LAPACK = 35, + IGRAPH_ARPACK_UNKNOWN = 36, + IGRAPH_ARPACK_MAXIT = 39, + IGRAPH_ARPACK_NOSHIFT = 40, + IGRAPH_ARPACK_REORDER = 41, +} igraph_arpack_error_t; + +IGRAPH_EXPORT void igraph_arpack_options_init(igraph_arpack_options_t *o); +IGRAPH_EXPORT igraph_arpack_options_t* igraph_arpack_options_get_default(void); + +IGRAPH_EXPORT igraph_error_t igraph_arpack_storage_init(igraph_arpack_storage_t *s, igraph_int_t maxn, + igraph_int_t maxncv, igraph_int_t maxldv, igraph_bool_t symm); +IGRAPH_EXPORT void igraph_arpack_storage_destroy(igraph_arpack_storage_t *s); + +/** + * \typedef igraph_arpack_function_t + * \brief Type of the ARPACK callback function. + * + * \param to Pointer to an \c igraph_real_t, the result of the + * matrix-vector product is expected to be stored here. + * \param from Pointer to an \c igraph_real_t, the input matrix should + * be multiplied by the vector stored here. + * \param n The length of the vector (which is the same as the order + * of the input matrix). + * \param extra Extra argument to the matrix-vector calculation + * function. This is coming from the \ref igraph_arpack_rssolve() + * or \ref igraph_arpack_rnsolve() function. + * \return Error code. If not \c IGRAPH_SUCCESS, then the ARPACK solver considers + * this as an error, stops and calls the igraph error handler. + */ + +typedef igraph_error_t igraph_arpack_function_t(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_arpack_rssolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, igraph_matrix_t *vectors); + +IGRAPH_EXPORT igraph_error_t igraph_arpack_rnsolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, igraph_matrix_t *vectors); + +IGRAPH_EXPORT igraph_error_t igraph_arpack_unpack_complex(igraph_matrix_t *vectors, igraph_matrix_t *values, + igraph_int_t nev); + +IGRAPH_EXPORT const char* igraph_arpack_error_to_string(igraph_arpack_error_t error); +IGRAPH_EXPORT igraph_arpack_error_t igraph_arpack_get_last_error(void); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_attributes.h b/include/igraph_attributes.h new file mode 100644 index 0000000..db0a3e6 --- /dev/null +++ b/include/igraph_attributes.h @@ -0,0 +1,931 @@ +/* + igraph library. + Copyright (C) 2005-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ATTRIBUTES_H +#define IGRAPH_ATTRIBUTES_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_strvector.h" +#include "igraph_vector_list.h" +#include "igraph_vector_ptr.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Attributes */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_attribute_type_t + * \brief The possible types of the attributes. + * + * Values of this enum are used by the attribute interface to communicate the + * type of an attribute to igraph's C core. When igraph is integrated in a + * high-level language, the attribute type reported by the interface may not + * necessarily have to match the exact data type in the high-level language as + * long as the attribute interface can provide a conversion from the native + * high-level attribute value to one of the data types listed here. When the + * high-level data type is complex and has no suitable conversion to one of the + * atomic igraph attribute types (numeric, string or Boolean), the attribute + * interface should report the attribute as having an "object" type, which is + * ignored by the C core. See also \ref igraph_attribute_table_t. + * + * \enumval IGRAPH_ATTRIBUTE_UNSPECIFIED Currently used internally + * as a "null value" or "placeholder value" in some algorithms. + * Attribute records with this type must not be passed to igraph + * functions. + * \enumval IGRAPH_ATTRIBUTE_NUMERIC Numeric attribute. + * \enumval IGRAPH_ATTRIBUTE_BOOLEAN Logical values, true or false. + * \enumval IGRAPH_ATTRIBUTE_STRING String attribute. + * \enumval IGRAPH_ATTRIBUTE_OBJECT Custom attribute type, to be + * used for special data types by client applications. The R and + * Python interfaces use this for attributes that hold R or Python + * objects. Usually ignored by igraph functions. + */ +typedef enum { + IGRAPH_ATTRIBUTE_UNSPECIFIED = 0, + IGRAPH_ATTRIBUTE_NUMERIC = 1, + IGRAPH_ATTRIBUTE_BOOLEAN = 2, + IGRAPH_ATTRIBUTE_STRING = 3, + IGRAPH_ATTRIBUTE_OBJECT = 127 +} igraph_attribute_type_t; + +/** + * \typedef igraph_attribute_elemtype_t + * \brief Types of objects to which attributes can be attached. + * + * \enumval IGRAPH_ATTRIBUTE_GRAPH Denotes that an attribute belongs to the + * entire graph. + * \enumval IGRAPH_ATTRIBUTE_VERTEX Denotes that an attribute belongs to the + * vertices of a graph. + * \enumval IGRAPH_ATTRIBUTE_EDGE Denotes that an attribute belongs to the + * edges of a graph. + */ +typedef enum { + IGRAPH_ATTRIBUTE_GRAPH = 0, + IGRAPH_ATTRIBUTE_VERTEX, + IGRAPH_ATTRIBUTE_EDGE +} igraph_attribute_elemtype_t; + +/* -------------------------------------------------- */ +/* Attribute records */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_attribute_record_t + * \brief An attribute record holding the name, type and values of an attribute. + * + * This composite data type is used in the attribute interface to specify a + * name-type-value triplet where the name is the name of a graph, vertex or + * edge attribute, the type is the corresponding igraph type of the attribute + * and the value is a \em vector of attribute values. Note that for graph + * attributes we use a vector of length 1. The type of the vector depends on + * the attribute type: it is \ref igraph_vector_t for numeric attributes, + * \c igraph_strvector_t for string attributes and \c igraph_vector_bool_t + * for Boolean attributes. + * + *
+ * The record also stores default values for the attribute. The default values + * are used when the value vector of the record is resized with + * \ref igraph_attribute_record_resize(). It is important that the record + * stores \em one default value only, corresponding to the type of the + * attribute record. The default value is \em cleared when the type of the + * record is changed. + */ +typedef struct igraph_attribute_record_t { + char *name; + igraph_attribute_type_t type; + union { + void *as_raw; + igraph_vector_t *as_vector; + igraph_strvector_t *as_strvector; + igraph_vector_bool_t *as_vector_bool; + } value; + union { + igraph_real_t numeric; + igraph_bool_t boolean; + char *string; + } default_value; +} igraph_attribute_record_t; + +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_init( + igraph_attribute_record_t *attr, const char* name, igraph_attribute_type_t type +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_init_copy( + igraph_attribute_record_t *to, const igraph_attribute_record_t *from +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_check_type( + const igraph_attribute_record_t *attr, igraph_attribute_type_t type +); +IGRAPH_EXPORT igraph_int_t igraph_attribute_record_size( + const igraph_attribute_record_t *attr +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_resize( + igraph_attribute_record_t *attr, igraph_int_t new_size +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_name( + igraph_attribute_record_t *attr, const char* name +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_numeric( + igraph_attribute_record_t *attr, igraph_real_t value +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_boolean( + igraph_attribute_record_t *attr, igraph_bool_t value +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_string( + igraph_attribute_record_t *attr, const char* value +); +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_type( + igraph_attribute_record_t *attr, igraph_attribute_type_t type +); +IGRAPH_EXPORT void igraph_attribute_record_destroy(igraph_attribute_record_t *attr); + +/* -------------------------------------------------- */ +/* Attribute combinations */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_attribute_combination_type_t + * The possible types of attribute combinations. + * + * \enumval IGRAPH_ATTRIBUTE_COMBINE_IGNORE Ignore old attributes, use an empty value. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_DEFAULT Use the default way to combine attributes (decided by the attribute handler implementation). + * \enumval IGRAPH_ATTRIBUTE_COMBINE_FUNCTION Supply your own function to combine + * attributes. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_SUM Take the sum of the attributes. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_PROD Take the product of the attributes. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_MIN Take the minimum attribute. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_MAX Take the maximum attribute. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_RANDOM Take a random attribute. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_FIRST Take the first attribute. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_LAST Take the last attribute. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_MEAN Take the mean of the attributes. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_MEDIAN Take the median of the attributes. + * \enumval IGRAPH_ATTRIBUTE_COMBINE_CONCAT Concatenate the attributes. + */ +typedef enum { + IGRAPH_ATTRIBUTE_COMBINE_IGNORE = 0, + IGRAPH_ATTRIBUTE_COMBINE_DEFAULT = 1, + IGRAPH_ATTRIBUTE_COMBINE_FUNCTION = 2, + IGRAPH_ATTRIBUTE_COMBINE_SUM = 3, + IGRAPH_ATTRIBUTE_COMBINE_PROD = 4, + IGRAPH_ATTRIBUTE_COMBINE_MIN = 5, + IGRAPH_ATTRIBUTE_COMBINE_MAX = 6, + IGRAPH_ATTRIBUTE_COMBINE_RANDOM = 7, + IGRAPH_ATTRIBUTE_COMBINE_FIRST = 8, + IGRAPH_ATTRIBUTE_COMBINE_LAST = 9, + IGRAPH_ATTRIBUTE_COMBINE_MEAN = 10, + IGRAPH_ATTRIBUTE_COMBINE_MEDIAN = 11, + IGRAPH_ATTRIBUTE_COMBINE_CONCAT = 12 +} igraph_attribute_combination_type_t; + +typedef void (*igraph_function_pointer_t)(void); + +typedef struct igraph_attribute_combination_record_t { + const char *name; /* can be NULL, meaning: the rest */ + igraph_attribute_combination_type_t type; + igraph_function_pointer_t func; +} igraph_attribute_combination_record_t; + +typedef struct igraph_attribute_combination_t { + igraph_vector_ptr_t list; +} igraph_attribute_combination_t; + +#define IGRAPH_NO_MORE_ATTRIBUTES ((const char*)0) + +IGRAPH_EXPORT igraph_error_t igraph_attribute_combination_init(igraph_attribute_combination_t *comb); +IGRAPH_EXPORT igraph_error_t igraph_attribute_combination(igraph_attribute_combination_t *comb, ...); +IGRAPH_EXPORT void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb); +IGRAPH_EXPORT igraph_error_t igraph_attribute_combination_add(igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t type, + igraph_function_pointer_t func); +IGRAPH_EXPORT igraph_error_t igraph_attribute_combination_remove(igraph_attribute_combination_t *comb, + const char *name); +IGRAPH_EXPORT igraph_error_t igraph_attribute_combination_query(const igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t *type, + igraph_function_pointer_t *func); + +/* -------------------------------------------------- */ +/* List of attribute records */ +/* -------------------------------------------------- */ + +#define ATTRIBUTE_RECORD_LIST +#define BASE_ATTRIBUTE_RECORD +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_ATTRIBUTE_RECORD +#undef ATTRIBUTE_RECORD_LIST + +/* -------------------------------------------------- */ +/* Attribute handler interface */ +/* -------------------------------------------------- */ + +/** + * \struct igraph_attribute_table_t + * \brief Table of functions to perform operations on attributes. + * + * This type collects the functions defining an attribute handler. + * It has the following members: + * + * \member init This function is called whenever a new graph object is + * created, right after it is created but before any vertices or + * edges are added. It is supposed to set the \c attr member of the \c + * igraph_t object, which is guaranteed to be set to a null pointer + * before this function is called. It is expected to set the \c attr member + * to a non-null value \em or return an error code. Leaving the \c attr + * member at a null value while returning success is invalid and will trigger + * an error in the C core of igraph itself. + * \member destroy This function is called whenever the graph object + * is destroyed, right before freeing the allocated memory. It is supposed + * to do any cleanup operations that are need to dispose of the \c attr + * member of the \c igraph_t object properly. The caller will set the + * \c attr member to a null pointer after this function returns. + * \member copy This function is called when the C core wants to populate the + * attributes of a graph from another graph. The structure of the target + * graph is already initialized by the time this function is called, and the + * \c attr member of the graph is set to a null pointer. The function is + * supposed to populate the \c attr member of the target \c igraph_t object + * to a non-null value \em or return an error code. Leaving the \c attr + * member at a null value while returning success is invalid and will trigger + * an error in the C core of igraph itself. + * \member add_vertices Called when vertices are added to a graph, after the + * base data structure was modified. The number of vertices that were added is + * supplied as an argument. The function is supposed to set up default values + * for each vertex attribute that is currently registered on the graph, for + * all the newly added vertices. Expected to return an error code. + * \member permute_vertices Called when a new graph is created based on an + * existing one such that there is a mapping from the vertices of the new + * graph back to the vertices of the old graph (e.g. if vertices are removed + * from a graph). The supplied index vector defines which old vertex + * a new vertex corresponds to. Its length is the same as the number of + * vertices in the new graph, and for each new vertex it provides the ID + * of the corresponding vertex in the old graph. The function is supposed to + * set up the values of the vertex attributes of the new graph based on the + * attributes of the old graph and the provided index vector. Note that the + * old and the new graph \em may be the same, in which case it is the + * responsibility of the function to ensure that the operation can safely be + * performed in-place. If the two graph instances are \em not the same, + * implementors may safely assume that the new graph has no vertex attributes + * yet (but it may already have graph or edge attributes by the time this + * function is called). + * \member combine_vertices This function is called when the creation + * of a new graph involves a merge (contraction, etc.) of vertices + * from another graph. The function is called after the new graph was created. + * An argument specifies how several vertices from the old graph map to a + * single vertex in the new graph. It is guaranteed that the old and the + * new graph instances are different when this callback is called. + * Implementors may safely assume that the new graph has no vertex attributes + * yet (but it may already have graph or edge attributes by the time this + * function is called). + * \member add_edges Called when new edges are added to a graph, after the + * base data structure was modified. A vector containing the endpoints of the + * new edges are supplied as an argument. The function is supposed to set up + * default values for each edge attribute that is currently registered on the + * graph, for all the newly added edges. Expected to return an error code. + * \member permute_edges Called when a new graph is created based on an + * existing one such that some of the edges in the new graph should copy the + * attributes of some edges from the old graph (this also includes the + * deletion of edges). The supplied index vector defines which old edge a new + * edge corresponds to. Its length is the same as the number of edges in the + * new graph, and for each edge it provides the ID of the corresponding edge + * in the old graph. The function is supposed to set up the values of the + * edge attributes of the new graph based on the attributes of the old graph + * and the provided index vector. Note that the old and the new graph \em may + * be the same, in which case it is the responsibility of the function to + * ensure that the operation can safely be performed in-place. If the two + * graph instances are \em not the same, implementors may safely assume that + * the new graph has no edge attributes yet (but it may already have graph or + * vertex attributes by the time this function is called). + * \member combine_edges This function is called when the creation + * of a new graph involves a merge (contraction, etc.) of edges + * from another graph. The function is after the new graph was created. + * An argument specifies how several edges from the old graph map to a + * single edge in the new graph. It is guaranteed that the old and the + * new graph instances are different when this callback is called. + * Implementors may safely assume that the new graph has no edge attributes + * yet (but it may already have graph or vertex attributes by the time this + * function is called). + * \member get_info Query the attributes of a graph, the names and + * types should be returned. + * \member has_attr Check whether a graph has the named + * graph/vertex/edge attribute. + * \member get_type Query the type of a graph/vertex/edge attribute. + * \member get_numeric_graph_attr Query a numeric graph attribute. The + * value should be appended to the provided \p value vector. No assumptions + * should be made about the initial contents of the \p value vector and it is + * not guaranteed to be empty. + * \member get_string_graph_attr Query a string graph attribute. The + * value should be appended to the provided \p value vector. No assumptions + * should be made about the initial contents of the \p value vector and it is + * not guaranteed to be empty. + * \member get_bool_graph_attr Query a boolean graph attribute. The + * value should be appended to the provided \p value vector. No assumptions + * should be made about the initial contents of the \p value vector and it is + * not guaranteed to be empty. + * \member get_numeric_vertex_attr Query a numeric vertex attribute, + * for the vertices included in \p vs. The attribute values should be + * appended to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + * \member get_string_vertex_attr Query a string vertex attribute, + * for the vertices included in \p vs. The attribute values should be + * appended to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + * \member get_bool_vertex_attr Query a boolean vertex attribute, + * for the vertices included in \p vs. The attribute values should be + * appended to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + * \member get_numeric_edge_attr Query a numeric edge attribute, for + * the edges included in \p es. The attribute values should be appended + * to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + * \member get_string_edge_attr Query a string edge attribute, for the + * the edges included in \p es. The attribute values should be appended + * to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + * \member get_bool_edge_attr Query a boolean edge attribute, for the + * the edges included in \p es. The attribute values should be appended + * to the provided \p value vector. No assumptions should be made + * about the initial contents of the \p value vector and it is not guaranteed + * to be empty. + */ + +typedef struct igraph_attribute_table_t { + igraph_error_t (*init)(igraph_t *graph, const igraph_attribute_record_list_t *attr); + void (*destroy)(igraph_t *graph); + igraph_error_t (*copy)(igraph_t *to, const igraph_t *from, igraph_bool_t ga, + igraph_bool_t va, igraph_bool_t ea); + igraph_error_t (*add_vertices)( + igraph_t *graph, igraph_int_t nv, + const igraph_attribute_record_list_t *attr + ); + igraph_error_t (*permute_vertices)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx); + igraph_error_t (*combine_vertices)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); + igraph_error_t (*add_edges)( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr + ); + igraph_error_t (*permute_edges)(const igraph_t *graph, + igraph_t *newgraph, const igraph_vector_int_t *idx); + igraph_error_t (*combine_edges)(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); + igraph_error_t (*get_info)(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, igraph_vector_int_t *etypes); + igraph_bool_t (*has_attr)(const igraph_t *graph, igraph_attribute_elemtype_t type, + const char *name); + igraph_error_t (*get_type)(const igraph_t *graph, igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, const char *name); + igraph_error_t (*get_numeric_graph_attr)(const igraph_t *graph, const char *name, + igraph_vector_t *value); + igraph_error_t (*get_string_graph_attr)(const igraph_t *graph, const char *name, + igraph_strvector_t *value); + igraph_error_t (*get_bool_graph_attr)(const igraph_t *igraph, const char *name, + igraph_vector_bool_t *value); + igraph_error_t (*get_numeric_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_vector_t *value); + igraph_error_t (*get_string_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_strvector_t *value); + igraph_error_t (*get_bool_vertex_attr)(const igraph_t *graph, const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value); + igraph_error_t (*get_numeric_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_vector_t *value); + igraph_error_t (*get_string_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_strvector_t *value); + igraph_error_t (*get_bool_edge_attr)(const igraph_t *graph, const char *name, + igraph_es_t es, + igraph_vector_bool_t *value); +} igraph_attribute_table_t; + +IGRAPH_EXPORT igraph_attribute_table_t * igraph_set_attribute_table(const igraph_attribute_table_t * table); + +IGRAPH_EXPORT igraph_bool_t igraph_has_attribute_table(void); + +/* Experimental attribute handler in C */ + +IGRAPH_EXPORT extern const igraph_attribute_table_t igraph_cattribute_table; + +IGRAPH_EXPORT igraph_real_t igraph_cattribute_GAN(const igraph_t *graph, const char *name); +IGRAPH_EXPORT igraph_bool_t igraph_cattribute_GAB(const igraph_t *graph, const char *name); +IGRAPH_EXPORT const char* igraph_cattribute_GAS(const igraph_t *graph, const char *name); +IGRAPH_EXPORT igraph_real_t igraph_cattribute_VAN(const igraph_t *graph, const char *name, + igraph_int_t vid); +IGRAPH_EXPORT igraph_bool_t igraph_cattribute_VAB(const igraph_t *graph, const char *name, + igraph_int_t vid); +IGRAPH_EXPORT const char* igraph_cattribute_VAS(const igraph_t *graph, const char *name, + igraph_int_t vid); +IGRAPH_EXPORT igraph_real_t igraph_cattribute_EAN(const igraph_t *graph, const char *name, + igraph_int_t eid); +IGRAPH_EXPORT igraph_bool_t igraph_cattribute_EAB(const igraph_t *graph, const char *name, + igraph_int_t eid); +IGRAPH_EXPORT const char* igraph_cattribute_EAS(const igraph_t *graph, const char *name, + igraph_int_t eid); + +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VANV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_t *result); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EANV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_t *result); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VASV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_strvector_t *result); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EASV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_strvector_t *result); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VABV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_bool_t *result); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EABV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_bool_t *result); + +IGRAPH_EXPORT igraph_error_t igraph_cattribute_list(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, igraph_vector_int_t *etypes); +IGRAPH_EXPORT igraph_bool_t igraph_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name); + +IGRAPH_EXPORT igraph_error_t igraph_cattribute_GAN_set(igraph_t *graph, const char *name, + igraph_real_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_GAB_set(igraph_t *graph, const char *name, + igraph_bool_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_GAS_set(igraph_t *graph, const char *name, + const char *value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAN_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_real_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAB_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_bool_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAS_set(igraph_t *graph, const char *name, + igraph_int_t vid, const char *value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAN_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_real_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAB_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_bool_t value); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAS_set(igraph_t *graph, const char *name, + igraph_int_t eid, const char *value); + +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_VAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v); +IGRAPH_EXPORT igraph_error_t igraph_cattribute_EAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv); + +IGRAPH_EXPORT void igraph_cattribute_remove_g(igraph_t *graph, const char *name); +IGRAPH_EXPORT void igraph_cattribute_remove_v(igraph_t *graph, const char *name); +IGRAPH_EXPORT void igraph_cattribute_remove_e(igraph_t *graph, const char *name); +IGRAPH_EXPORT void igraph_cattribute_remove_all(igraph_t *graph, igraph_bool_t g, + igraph_bool_t v, igraph_bool_t e); + +/** + * \define GAN + * Query a numeric graph attribute. + * + * This is shorthand for \ref igraph_cattribute_GAN(). + * \param graph The graph. + * \param n The name of the attribute. + * \return The value of the attribute. + */ +#define GAN(graph,n) (igraph_cattribute_GAN((graph), (n))) +/** + * \define GAB + * Query a boolean graph attribute. + * + * This is shorthand for \ref igraph_cattribute_GAB(). + * \param graph The graph. + * \param n The name of the attribute. + * \return The value of the attribute. + */ +#define GAB(graph,n) (igraph_cattribute_GAB((graph), (n))) +/** + * \define GAS + * Query a string graph attribute. + * + * This is shorthand for \ref igraph_cattribute_GAS(). + * \param graph The graph. + * \param n The name of the attribute. + * \return The value of the attribute. + */ +#define GAS(graph,n) (igraph_cattribute_GAS((graph), (n))) +/** + * \define VAN + * Query a numeric vertex attribute. + * + * This is shorthand for \ref igraph_cattribute_VAN(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v The id of the vertex. + * \return The value of the attribute. + */ +#define VAN(graph,n,v) (igraph_cattribute_VAN((graph), (n), (v))) +/** + * \define VAB + * Query a boolean vertex attribute. + * + * This is shorthand for \ref igraph_cattribute_VAB(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v The id of the vertex. + * \return The value of the attribute. + */ +#define VAB(graph,n,v) (igraph_cattribute_VAB((graph), (n), (v))) +/** + * \define VAS + * Query a string vertex attribute. + * + * This is shorthand for \ref igraph_cattribute_VAS(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v The id of the vertex. + * \return The value of the attribute. + */ +#define VAS(graph,n,v) (igraph_cattribute_VAS((graph), (n), (v))) +/** + * \define VANV + * Query a numeric vertex attribute for all vertices. + * + * This is a shorthand for \ref igraph_cattribute_VANV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define VANV(graph,n,vec) (igraph_cattribute_VANV((graph),(n), \ + igraph_vss_all(), (vec))) +/** + * \define VABV + * Query a boolean vertex attribute for all vertices. + * + * This is a shorthand for \ref igraph_cattribute_VABV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized boolean vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define VABV(graph,n,vec) (igraph_cattribute_VABV((graph),(n), \ + igraph_vss_all(), (vec))) +/** + * \define VASV + * Query a string vertex attribute for all vertices. + * + * This is a shorthand for \ref igraph_cattribute_VASV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized string vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define VASV(graph,n,vec) (igraph_cattribute_VASV((graph),(n), \ + igraph_vss_all(), (vec))) +/** + * \define EAN + * Query a numeric edge attribute. + * + * This is shorthand for \ref igraph_cattribute_EAN(). + * \param graph The graph. + * \param n The name of the attribute. + * \param e The id of the edge. + * \return The value of the attribute. + */ +#define EAN(graph,n,e) (igraph_cattribute_EAN((graph), (n), (e))) +/** + * \define EAB + * Query a boolean edge attribute. + * + * This is shorthand for \ref igraph_cattribute_EAB(). + * \param graph The graph. + * \param n The name of the attribute. + * \param e The id of the edge. + * \return The value of the attribute. + */ +#define EAB(graph,n,e) (igraph_cattribute_EAB((graph), (n), (e))) +/** + * \define EAS + * Query a string edge attribute. + * + * This is shorthand for \ref igraph_cattribute_EAS(). + * \param graph The graph. + * \param n The name of the attribute. + * \param e The id of the edge. + * \return The value of the attribute. + */ +#define EAS(graph,n,e) (igraph_cattribute_EAS((graph), (n), (e))) +/** + * \define EANV + * Query a numeric edge attribute for all edges. + * + * This is a shorthand for \ref igraph_cattribute_EANV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define EANV(graph,n,vec) (igraph_cattribute_EANV((graph),(n), \ + igraph_ess_all(IGRAPH_EDGEORDER_ID), (vec))) +/** + * \define EABV + * Query a boolean edge attribute for all edges. + * + * This is a shorthand for \ref igraph_cattribute_EABV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define EABV(graph,n,vec) (igraph_cattribute_EABV((graph),(n), \ + igraph_ess_all(IGRAPH_EDGEORDER_ID), (vec))) + +/** + * \define EASV + * Query a string edge attribute for all edges. + * + * This is a shorthand for \ref igraph_cattribute_EASV(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vec Pointer to an initialized string vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + */ +#define EASV(graph,n,vec) (igraph_cattribute_EASV((graph),(n), \ + igraph_ess_all(IGRAPH_EDGEORDER_ID), (vec))) +/** + * \define SETGAN + * Set a numeric graph attribute + * + * This is a shorthand for \ref igraph_cattribute_GAN_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETGAN(graph,n,value) (igraph_cattribute_GAN_set((graph),(n),(value))) +/** + * \define SETGAB + * Set a boolean graph attribute + * + * This is a shorthand for \ref igraph_cattribute_GAB_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETGAB(graph,n,value) (igraph_cattribute_GAB_set((graph),(n),(value))) +/** + * \define SETGAS + * Set a string graph attribute + * + * This is a shorthand for \ref igraph_cattribute_GAS_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETGAS(graph,n,value) (igraph_cattribute_GAS_set((graph),(n),(value))) +/** + * \define SETVAN + * Set a numeric vertex attribute + * + * This is a shorthand for \ref igraph_cattribute_VAN_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vid Ids of the vertices to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETVAN(graph,n,vid,value) (igraph_cattribute_VAN_set((graph),(n),(vid),(value))) +/** + * \define SETVAB + * Set a boolean vertex attribute + * + * This is a shorthand for \ref igraph_cattribute_VAB_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vid Ids of the vertices to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETVAB(graph,n,vid,value) (igraph_cattribute_VAB_set((graph),(n),(vid),(value))) +/** + * \define SETVAS + * Set a string vertex attribute + * + * This is a shorthand for \ref igraph_cattribute_VAS_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param vid Ids of the vertices to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETVAS(graph,n,vid,value) (igraph_cattribute_VAS_set((graph),(n),(vid),(value))) +/** + * \define SETEAN + * Set a numeric edge attribute + * + * This is a shorthand for \ref igraph_cattribute_EAN_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param eid Ids of the edges to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETEAN(graph,n,eid,value) (igraph_cattribute_EAN_set((graph),(n),(eid),(value))) +/** + * \define SETEAB + * Set a boolean edge attribute + * + * This is a shorthand for \ref igraph_cattribute_EAB_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param eid Ids of the edges to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETEAB(graph,n,eid,value) (igraph_cattribute_EAB_set((graph),(n),(eid),(value))) +/** + * \define SETEAS + * Set a string edge attribute + * + * This is a shorthand for \ref igraph_cattribute_EAS_set(). + * \param graph The graph. + * \param n The name of the attribute. + * \param eid Ids of the edges to set. + * \param value The new value of the attribute. + * \return Error code. + */ +#define SETEAS(graph,n,eid,value) (igraph_cattribute_EAS_set((graph),(n),(eid),(value))) + +/** + * \define SETVANV + * Set a numeric vertex attribute for all vertices + * + * This is a shorthand for \ref igraph_cattribute_VAN_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + * \return Error code. + */ +#define SETVANV(graph,n,v) (igraph_cattribute_VAN_setv((graph),(n),(v))) +/** + * \define SETVABV + * Set a boolean vertex attribute for all vertices + * + * This is a shorthand for \ref igraph_cattribute_VAB_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + * \return Error code. + */ +#define SETVABV(graph,n,v) (igraph_cattribute_VAB_setv((graph),(n),(v))) +/** + * \define SETVASV + * Set a string vertex attribute for all vertices + * + * This is a shorthand for \ref igraph_cattribute_VAS_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + * \return Error code. + */ +#define SETVASV(graph,n,v) (igraph_cattribute_VAS_setv((graph),(n),(v))) +/** + * \define SETEANV + * Set a numeric edge attribute for all edges + * + * This is a shorthand for \ref igraph_cattribute_EAN_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + */ +#define SETEANV(graph,n,v) (igraph_cattribute_EAN_setv((graph),(n),(v))) +/** + * \define SETEABV + * Set a boolean edge attribute for all edges + * + * This is a shorthand for \ref igraph_cattribute_EAB_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + */ +#define SETEABV(graph,n,v) (igraph_cattribute_EAB_setv((graph),(n),(v))) +/** + * \define SETEASV + * Set a string edge attribute for all edges + * + * This is a shorthand for \ref igraph_cattribute_EAS_setv(). + * \param graph The graph. + * \param n The name of the attribute. + * \param v Vector containing the new values of the attributes. + */ +#define SETEASV(graph,n,v) (igraph_cattribute_EAS_setv((graph),(n),(v))) + +/** + * \define DELGA + * Remove a graph attribute. + * + * A shorthand for \ref igraph_cattribute_remove_g(). + * \param graph The graph. + * \param n The name of the attribute to remove. + */ +#define DELGA(graph,n) (igraph_cattribute_remove_g((graph),(n))) +/** + * \define DELVA + * Remove a vertex attribute. + * + * A shorthand for \ref igraph_cattribute_remove_v(). + * \param graph The graph. + * \param n The name of the attribute to remove. + */ +#define DELVA(graph,n) (igraph_cattribute_remove_v((graph),(n))) +/** + * \define DELEA + * Remove an edge attribute. + * + * A shorthand for \ref igraph_cattribute_remove_e(). + * \param graph The graph. + * \param n The name of the attribute to remove. + */ +#define DELEA(graph,n) (igraph_cattribute_remove_e((graph),(n))) +/** + * \define DELGAS + * Remove all graph attributes. + * + * Calls \ref igraph_cattribute_remove_all(). + * \param graph The graph. + */ +#define DELGAS(graph) (igraph_cattribute_remove_all((graph),1,0,0)) +/** + * \define DELVAS + * Remove all vertex attributes. + * + * Calls \ref igraph_cattribute_remove_all(). + * \param graph The graph. + */ +#define DELVAS(graph) (igraph_cattribute_remove_all((graph),0,1,0)) +/** + * \define DELEAS + * Remove all edge attributes. + * + * Calls \ref igraph_cattribute_remove_all(). + * \param graph The graph. + */ +#define DELEAS(graph) (igraph_cattribute_remove_all((graph),0,0,1)) +/** + * \define DELALL + * Remove all attributes. + * + * All graph, vertex and edges attributes will be removed. + * Calls \ref igraph_cattribute_remove_all(). + * \param graph The graph. + */ +#define DELALL(graph) (igraph_cattribute_remove_all((graph),1,1,1)) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_bipartite.h b/include/igraph_bipartite.h new file mode 100644 index 0000000..d1a0ee4 --- /dev/null +++ b/include/igraph_bipartite.h @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_BIPARTITE_H +#define IGRAPH_BIPARTITE_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_graphicality.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Bipartite networks */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_full_bipartite(igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, + igraph_bool_t directed, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_create_bipartite(igraph_t *g, const igraph_vector_bool_t *types, + const igraph_vector_int_t *edges, + igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_bipartite_projection_size(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_int_t *vcount1, + igraph_int_t *ecount1, + igraph_int_t *vcount2, + igraph_int_t *ecount2); + +IGRAPH_EXPORT igraph_error_t igraph_bipartite_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_t *proj1, + igraph_t *proj2, + igraph_vector_int_t *multiplicity1, + igraph_vector_int_t *multiplicity2, + igraph_int_t probe1); + +IGRAPH_EXPORT igraph_error_t igraph_biadjacency( + igraph_t *graph, + igraph_vector_bool_t *types, + const igraph_matrix_t *biadjmatrix, + igraph_bool_t directed, + igraph_neimode_t mode, + igraph_bool_t multiple); + +IGRAPH_EXPORT igraph_error_t igraph_weighted_biadjacency( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_vector_t *weights, + const igraph_matrix_t *biadjmatrix, + igraph_bool_t directed, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_get_biadjacency(const igraph_t *graph, + const igraph_vector_bool_t *types, + const igraph_vector_t *weights, + igraph_matrix_t *res, + igraph_vector_int_t *row_ids, + igraph_vector_int_t *col_ids); + +IGRAPH_EXPORT igraph_error_t igraph_is_bipartite(const igraph_t *graph, + igraph_bool_t *res, + igraph_vector_bool_t *types); + +IGRAPH_EXPORT igraph_error_t igraph_bipartite_game_gnp( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + +IGRAPH_EXPORT igraph_error_t igraph_bipartite_game_gnm( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_bipartite_iea_game( + igraph_t *graph, igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_bitset.h b/include/igraph_bitset.h new file mode 100644 index 0000000..2696243 --- /dev/null +++ b/include/igraph_bitset.h @@ -0,0 +1,265 @@ +/* + igraph library. + Copyright (C) 2024-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_BITSET_H +#define IGRAPH_BITSET_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_types.h" + +/* Required for MSVC intrinsics such as __popcnt and __popcnt64 */ +#ifdef _MSC_VER + #include "intrin.h" +#endif + +IGRAPH_BEGIN_C_DECLS + +/** + * \ingroup bitset + * \section igraph_bitset_accessing_elements Accessing elements + * + * The simplest way to access an element of a bitset is + * to use the \ref IGRAPH_BIT_TEST(), \ref IGRAPH_BIT_SET() and \ref IGRAPH_BIT_CLEAR() macros. + * + * + * There are a few other macros which allow manual manipulation of bitsets. + * Those are \ref VECTOR(), \ref IGRAPH_BIT_SLOT(), \ref IGRAPH_BIT_MASK() and + * \ref IGRAPH_BIT_NSLOTS(). + */ + +/** + * \ingroup bitset + * \define IGRAPH_BIT_MASK + * \brief Computes mask used to access a specific bit of an integer. + * + * \experimental + * + * Used in combination with \ref IGRAPH_BIT_SLOT() to access an element of a bitset. + * + * Usage: + * \verbatim IGRAPH_BIT_MASK(10) \endverbatim + * to obtain an integer where only the 11th least significant bit is set. + * + * Note that passing negative values here results in undefined behaviour. + * + * \param b The only bit index that should have its bit set. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_MASK(i) ((igraph_uint_t)(1) << ((i) % IGRAPH_INTEGER_SIZE)) + +/** + * \ingroup bitset + * \define IGRAPH_BIT_SLOT + * \brief Computes index used to access a specific slot of a bitset. + * + * \experimental + * + * Used in combination with \ref IGRAPH_BIT_MASK to access an element of a bitset. + * + * Usage: + * \verbatim IGRAPH_BIT_SLOT(70) \endverbatim + * will return 1 if using 64-bit words or 2 if using 32-bit words. + * + * \param i The bit index whose slot should be determined. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_SLOT(i) ((i) / IGRAPH_INTEGER_SIZE) + +/** + * \ingroup bitset + * \define IGRAPH_BIT_SET + * \brief Sets a specific bit in a bitset to 1 without altering other bits. + * + * \experimental + * + * Usage: + * \verbatim IGRAPH_BIT_SET(bitset, 3) \endverbatim + * will set the fourth least significant bit in the bitset to 1. + * + * \param bitset The bitset + * \param i The bit index that should have its bit set to 1 after the operation. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_SET(bitset, i) (VECTOR((bitset))[IGRAPH_BIT_SLOT(i)] |= IGRAPH_BIT_MASK(i)) + +/** + * \ingroup bitset + * \define IGRAPH_BIT_CLEAR + * \brief Sets a specific bit in a bitset to 0 without altering other bits. + * + * \experimental + * + * Usage: + * \verbatim IGRAPH_BIT_CLEAR(bitset, 4) \endverbatim + * will set the fifth least significant bit in the bitset to 0. + * + * \param bitset The bitset + * \param i The bit index that should have its bit set to 0 after the operation. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_CLEAR(bitset, i) (VECTOR((bitset))[IGRAPH_BIT_SLOT(i)] &= ~IGRAPH_BIT_MASK(i)) + +/** + * \ingroup bitset + * \define IGRAPH_BIT_TEST + * \brief Tests whether a bit is set in a bitset. + * + * \experimental + * + * Returns 0 if the bit at the specified bit index is not set, + * otherwise returns a non-zero value. + * + * Usage: + * \verbatim IGRAPH_BIT_TEST(bitset, 7) \endverbatim + * will test the eighth least significant bit in the bitset. + * + * \param bitset The bitset + * \param i The bit index that should have its bit tested. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_TEST(bitset, i) (VECTOR((bitset))[IGRAPH_BIT_SLOT(i)] & IGRAPH_BIT_MASK(i)) + +/** + * \ingroup bitset + * \define IGRAPH_BIT_NSLOTS + * \brief Computes the number of slots required to store a specified number of bits. + * + * \experimental + * + * Usage: + * \verbatim IGRAPH_BIT_NSLOTS(70) \endverbatim + * will return 2 if using 64-bit words and 3 if using 32-bit words. + * \verbatim IGRAPH_BIT_NSLOTS(128) \endverbatim + * will return 2 if using 64-bit words and 4 if using 32-bit words. + * + * \param nbits The specified number of bits. + * + * Time complexity: O(1). + */ +#define IGRAPH_BIT_NSLOTS(nbits) ((nbits + IGRAPH_INTEGER_SIZE - (igraph_int_t)(1)) / IGRAPH_INTEGER_SIZE) + + +#if defined(__GNUC__) + /* GCC and Clang support these six builtins from very early versions. */ + + #define IGRAPH_I_POPCOUNT32(x) __builtin_popcount(x) + #define IGRAPH_I_POPCOUNT64(x) __builtin_popcountll(x) + + /* The result of the following four builtins is undefined for zero input, + * therefore we handle this specially. + * See https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html */ + #define IGRAPH_I_CTZ32(x) (x ? __builtin_ctz(x) : 32) + #define IGRAPH_I_CTZ64(x) (x ? __builtin_ctzll(x) : 64) + #define IGRAPH_I_CLZ32(x) (x ? __builtin_clz(x) : 32) + #define IGRAPH_I_CLZ64(x) (x ? __builtin_clzll(x) : 64) + +#else + /* Non-GNU compilers, i.e. MSVC and others. Attempt to use MSVC intrinsics + * when available, otherwise use fallback implementation. */ + + #if IGRAPH_INTEGER_SIZE == 32 + #ifdef HAVE__POPCNT + #define IGRAPH_I_POPCOUNT32(x) __popcnt(x) + #else + igraph_int_t igraph_i_popcnt(igraph_uint_t x); + #define IGRAPH_I_POPCOUNT32(x) igraph_i_popcnt(x) + #endif + #elif IGRAPH_INTEGER_SIZE == 64 + #ifdef HAVE__POPCNT64 + #define IGRAPH_I_POPCOUNT64(x) __popcnt64(x) + #else + igraph_int_t igraph_i_popcnt(igraph_uint_t x); + #define IGRAPH_I_POPCOUNT64(x) igraph_i_popcnt(x) + #endif + #else + #error "Unexpected IGRAPH_INTEGER_SIZE value." + #endif + + igraph_int_t igraph_i_ctz32(igraph_uint_t x); + igraph_int_t igraph_i_ctz64(igraph_uint_t x); + igraph_int_t igraph_i_clz32(igraph_uint_t x); + igraph_int_t igraph_i_clz64(igraph_uint_t x); + #define IGRAPH_I_CTZ32(x) igraph_i_ctz32(x) + #define IGRAPH_I_CTZ64(x) igraph_i_ctz64(x) + #define IGRAPH_I_CLZ32(x) igraph_i_clz32(x) + #define IGRAPH_I_CLZ64(x) igraph_i_clz64(x) + +#endif + + +#if IGRAPH_INTEGER_SIZE == 32 + #define IGRAPH_POPCOUNT IGRAPH_I_POPCOUNT32 + #define IGRAPH_CLZ IGRAPH_I_CLZ32 + #define IGRAPH_CTZ IGRAPH_I_CTZ32 +#elif IGRAPH_INTEGER_SIZE == 64 + #define IGRAPH_POPCOUNT IGRAPH_I_POPCOUNT64 + #define IGRAPH_CLZ IGRAPH_I_CLZ64 + #define IGRAPH_CTZ IGRAPH_I_CTZ64 +#else + #error "Unexpected IGRAPH_INTEGER_SIZE value." +#endif + +#define IGRAPH_CLO(x) IGRAPH_CLZ(~(x)) +#define IGRAPH_CTO(x) IGRAPH_CTZ(~(x)) + +typedef struct { + igraph_int_t size; + igraph_uint_t *stor_begin; + igraph_uint_t *stor_end; +} igraph_bitset_t; + +IGRAPH_EXPORT igraph_error_t igraph_bitset_init(igraph_bitset_t *bitset, igraph_int_t size); +IGRAPH_EXPORT void igraph_bitset_destroy(igraph_bitset_t *bitset); +IGRAPH_EXPORT igraph_error_t igraph_bitset_init_copy(igraph_bitset_t *dest, const igraph_bitset_t *src); +IGRAPH_EXPORT igraph_error_t igraph_bitset_update(igraph_bitset_t *dest, const igraph_bitset_t *src); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_capacity(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_size(const igraph_bitset_t *bitset); +IGRAPH_EXPORT igraph_error_t igraph_bitset_reserve(igraph_bitset_t *bitset, igraph_int_t capacity); +IGRAPH_EXPORT igraph_error_t igraph_bitset_resize(igraph_bitset_t *bitset, igraph_int_t new_size); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_popcount(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_countl_zero(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_countl_one(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_countr_zero(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_bitset_countr_one(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_bitset_is_all_zero(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_bitset_is_all_one(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_bitset_is_any_zero(const igraph_bitset_t *bitset); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_bitset_is_any_one(const igraph_bitset_t *bitset); +IGRAPH_EXPORT void igraph_bitset_or(igraph_bitset_t *dest, const igraph_bitset_t *src1, const igraph_bitset_t *src2); +IGRAPH_EXPORT void igraph_bitset_and(igraph_bitset_t *dest, const igraph_bitset_t *src1, const igraph_bitset_t *src2); +IGRAPH_EXPORT void igraph_bitset_xor(igraph_bitset_t *dest, const igraph_bitset_t *src1, const igraph_bitset_t *src2); +IGRAPH_EXPORT void igraph_bitset_not(igraph_bitset_t *dest, const igraph_bitset_t *src); +IGRAPH_EXPORT void igraph_bitset_fill(igraph_bitset_t *bitset, igraph_bool_t value); +IGRAPH_EXPORT void igraph_bitset_null(igraph_bitset_t *bitset); +IGRAPH_EXPORT igraph_error_t igraph_bitset_fprint(const igraph_bitset_t *bitset, FILE *file); +IGRAPH_EXPORT igraph_error_t igraph_bitset_print(const igraph_bitset_t *bitset); + +#define IGRAPH_BITSET_INIT_FINALLY(bitset, size) \ +do { IGRAPH_CHECK(igraph_bitset_init(bitset, size)); \ + IGRAPH_FINALLY(igraph_bitset_destroy, bitset); } while (0) + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_BITSET_H */ diff --git a/include/igraph_bitset_list.h b/include/igraph_bitset_list.h new file mode 100644 index 0000000..dd97dda --- /dev/null +++ b/include/igraph_bitset_list.h @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2024-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_BITSET_LIST_H +#define IGRAPH_BITSET_LIST_H + +#include "igraph_bitset.h" +#include "igraph_decls.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* List of graphs */ +/* -------------------------------------------------- */ + +#define BITSET_LIST +#define BASE_BITSET +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BITSET +#undef BITSET_LIST + +#define IGRAPH_BITSET_LIST_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_bitset_list_init(v, size)); \ + IGRAPH_FINALLY(igraph_bitset_list_destroy, v); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_blas.h b/include/igraph_blas.h new file mode 100644 index 0000000..f10a2ca --- /dev/null +++ b/include/igraph_blas.h @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_BLAS_H +#define IGRAPH_BLAS_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_blas BLAS interface in igraph + * + * + * BLAS is a highly optimized library for basic linear algebra operations + * such as vector-vector, matrix-vector and matrix-matrix product. + * Please see http://www.netlib.org/blas/ for details and a reference + * implementation in Fortran. igraph contains some wrapper functions + * that can be used to call BLAS routines in a somewhat more + * user-friendly way. Not all BLAS routines are included in igraph, + * and even those which are included might not have wrappers; + * the extension of the set of wrapped functions will probably be driven + * by igraph's internal requirements. The wrapper functions usually + * substitute double-precision floating point arrays used by BLAS with + * \type igraph_vector_t and \type igraph_matrix_t instances and also + * remove those parameters (such as the number of rows/columns) that + * can be inferred from the passed arguments directly. + * + */ + +IGRAPH_EXPORT igraph_error_t igraph_blas_dgemv(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_vector_t* x, + igraph_real_t beta, igraph_vector_t* y); +IGRAPH_EXPORT igraph_error_t igraph_blas_dgemm(igraph_bool_t transpose_a, + igraph_bool_t transpose_b, igraph_real_t alpha, const igraph_matrix_t* a, + const igraph_matrix_t* b, igraph_real_t beta, igraph_matrix_t* c); +IGRAPH_EXPORT igraph_error_t igraph_blas_dgemv_array(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_real_t* x, + igraph_real_t beta, igraph_real_t* y); + +IGRAPH_EXPORT igraph_real_t igraph_blas_dnrm2(const igraph_vector_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_blas_ddot(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *res); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_centrality.h b/include/igraph_centrality.h new file mode 100644 index 0000000..04fe325 --- /dev/null +++ b/include/igraph_centrality.h @@ -0,0 +1,207 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CENTRALITY_H +#define IGRAPH_CENTRALITY_H + +#include "igraph_decls.h" +#include "igraph_arpack.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Centrality */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_closeness(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_closeness_cutoff(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff); + +IGRAPH_EXPORT igraph_error_t igraph_harmonic_centrality(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_harmonic_centrality_cutoff(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff); + +IGRAPH_EXPORT igraph_error_t igraph_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff); + +IGRAPH_EXPORT igraph_error_t igraph_edge_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_edge_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff); + +IGRAPH_EXPORT igraph_error_t igraph_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_edge_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized); + +/** + * \typedef igraph_pagerank_algo_t + * \brief PageRank algorithm implementation. + * + * Algorithms to calculate PageRank. + * \enumval IGRAPH_PAGERANK_ALGO_ARPACK Use the ARPACK library, this + * was the PageRank implementation in igraph from version 0.5, until + * version 0.7. + * \enumval IGRAPH_PAGERANK_ALGO_PRPACK Use the PRPACK + * library. Currently this implementation is recommended. + */ + +typedef enum { + IGRAPH_PAGERANK_ALGO_ARPACK = 1, + IGRAPH_PAGERANK_ALGO_PRPACK = 2 +} igraph_pagerank_algo_t; + +IGRAPH_EXPORT igraph_error_t igraph_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_personalized_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + const igraph_vector_t *reset, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_personalized_pagerank_vs( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_vs_t reset_vids, igraph_real_t damping, + igraph_bool_t directed, igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_eigenvector_centrality(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_hub_and_authority_scores(const igraph_t *graph, igraph_vector_t *hub_vector, + igraph_vector_t *authority_vector, + igraph_real_t *value, + const igraph_vector_t *weights, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_constraint(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, const igraph_vector_t *weights); + +IGRAPH_EXPORT igraph_error_t igraph_convergence_degree(const igraph_t *graph, igraph_vector_t *result, + igraph_vector_t *ins, igraph_vector_t *outs); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_centralization(const igraph_vector_t *scores, + igraph_real_t theoretical_max, + igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_centralization_degree(const igraph_t *graph, igraph_vector_t *res, + igraph_neimode_t mode, igraph_loops_t loops, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_centralization_degree_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_loops_t loops, + igraph_real_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_centralization_betweenness(const igraph_t *graph, + igraph_vector_t *res, + igraph_bool_t directed, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_centralization_betweenness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_bool_t directed, + igraph_real_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_centralization_closeness(const igraph_t *graph, + igraph_vector_t *res, + igraph_neimode_t mode, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_centralization_closeness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_centralization_eigenvector_centrality(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + igraph_arpack_options_t *options, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized); +IGRAPH_EXPORT igraph_error_t igraph_centralization_eigenvector_centrality_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_cliques.h b/include/igraph_cliques.h new file mode 100644 index 0000000..c76ebd1 --- /dev/null +++ b/include/igraph_cliques.h @@ -0,0 +1,141 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CLIQUES_H +#define IGRAPH_CLIQUES_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Cliques, maximal independent vertex sets */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques( + const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques_file( + const igraph_t *graph, + FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques_count( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t min_size, igraph_int_t max_size); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques_subset( + const igraph_t *graph, const igraph_vector_int_t *subset, + igraph_vector_int_list_t *res, igraph_int_t *no, FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques_hist( + const igraph_t *graph, + igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size); + +IGRAPH_EXPORT igraph_error_t igraph_cliques( + const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_clique_size_hist( + const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size); + +IGRAPH_EXPORT igraph_error_t igraph_largest_cliques( + const igraph_t *graph, + igraph_vector_int_list_t *cliques); + +IGRAPH_EXPORT igraph_error_t igraph_clique_number(const igraph_t *graph, igraph_int_t *no); + +IGRAPH_EXPORT igraph_error_t igraph_weighted_cliques( + const igraph_t *graph, const igraph_vector_t *vertex_weights, + igraph_vector_int_list_t *res, + igraph_bool_t maximal, + igraph_real_t min_weight, igraph_real_t max_weight, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_largest_weighted_cliques( + const igraph_t *graph, const igraph_vector_t *vertex_weights, + igraph_vector_int_list_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_weighted_clique_number( + const igraph_t *graph, const igraph_vector_t *vertex_weights, + igraph_real_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_largest_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_independence_number(const igraph_t *graph, igraph_int_t *no); + +/** + * \typedef igraph_clique_handler_t + * \brief Type of clique handler functions. + * + * Callback type, called when a clique was found. + * + * See the details at the documentation of \ref + * igraph_cliques_callback(). + * + * \param clique The current clique. The clique is owned by the clique search + * routine. You do not need to destroy or free it if you do not want to store + * it; however, if you want to hold on to it for a longer period of time, you + * need to make a copy of it on your own and store the copy itself. + * \param arg This extra argument was passed to \ref + * igraph_cliques_callback() when it was called. + * \return Error code; \c IGRAPH_SUCCESS to continue the search or + * \c IGRAPH_STOP to stop the search without signaling an error. + */ +typedef igraph_error_t igraph_clique_handler_t(const igraph_vector_int_t *clique, void *arg); + +IGRAPH_EXPORT igraph_error_t igraph_cliques_callback( + const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + +IGRAPH_EXPORT igraph_error_t igraph_maximal_cliques_callback( + const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_cocitation.h b/include/igraph_cocitation.h new file mode 100644 index 0000000..706ca9a --- /dev/null +++ b/include/igraph_cocitation.h @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COCITATION_H +#define IGRAPH_COCITATION_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Cocitation and other similarity measures */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_cocitation(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids); +IGRAPH_EXPORT igraph_error_t igraph_bibcoupling(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids); + +IGRAPH_EXPORT igraph_error_t igraph_similarity_jaccard(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vit_from, igraph_vs_t vit_to, + igraph_neimode_t mode, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_similarity_jaccard_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_similarity_jaccard_es(const igraph_t *graph, igraph_vector_t *res, + igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops); + +IGRAPH_EXPORT igraph_error_t igraph_similarity_dice(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vit_from, igraph_vs_t vit_to, + igraph_neimode_t mode, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_similarity_dice_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_similarity_dice_es(const igraph_t *graph, igraph_vector_t *res, + igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops); + +IGRAPH_EXPORT igraph_error_t igraph_similarity_inverse_log_weighted(const igraph_t *graph, + igraph_matrix_t *res, igraph_vs_t vids, + igraph_neimode_t mode); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_cohesive_blocks.h b/include/igraph_cohesive_blocks.h new file mode 100644 index 0000000..924da03 --- /dev/null +++ b/include/igraph_cohesive_blocks.h @@ -0,0 +1,38 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COHESIVE_BLOCKS_H +#define IGRAPH_COHESIVE_BLOCKS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_cohesive_blocks(const igraph_t *graph, + igraph_vector_int_list_t *blocks, + igraph_vector_int_t *cohesion, + igraph_vector_int_t *parent, + igraph_t *block_tree); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_coloring.h b/include/igraph_coloring.h new file mode 100644 index 0000000..1831ad6 --- /dev/null +++ b/include/igraph_coloring.h @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2017-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COLORING_H +#define IGRAPH_COLORING_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \typedef igraph_coloring_greedy_t + * \brief Ordering heuristics for greedy graph coloring. + * + * Ordering heuristics for \ref igraph_vertex_coloring_greedy(). + * + * \enumval IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS + * Choose the vertex with largest number of already colored neighbors. + * \enumval IGRAPH_COLORING_GREEDY_DSATUR + * Choose the vertex with largest number of unique colors in its neighborhood, i.e. its + * "saturation degree". When multiple vertices have the same saturation degree, choose + * the one with the most not yet colored neighbors. Added in igraph 0.10.4. This heuristic + * is known as "DSatur", and was proposed in + * Daniel Brélaz: New methods to color the vertices of a graph, + * Commun. ACM 22, 4 (1979), 251–256. https://doi.org/10.1145/359094.359101 + */ +typedef enum { + IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS = 0, + IGRAPH_COLORING_GREEDY_DSATUR = 1 +} igraph_coloring_greedy_t; + +IGRAPH_EXPORT igraph_error_t igraph_vertex_coloring_greedy(const igraph_t *graph, igraph_vector_int_t *colors, igraph_coloring_greedy_t heuristic); + +IGRAPH_EXPORT igraph_error_t igraph_is_vertex_coloring(const igraph_t *graph, const igraph_vector_int_t *types, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_is_bipartite_coloring(const igraph_t *graph, const igraph_vector_bool_t *types, igraph_bool_t *res, igraph_neimode_t *mode); +IGRAPH_EXPORT igraph_error_t igraph_is_edge_coloring(const igraph_t *graph, const igraph_vector_int_t *types, igraph_bool_t *res); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_COLORING_H */ diff --git a/include/igraph_community.h b/include/igraph_community.h new file mode 100644 index 0000000..c8cab38 --- /dev/null +++ b/include/igraph_community.h @@ -0,0 +1,280 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COMMUNITY_H +#define IGRAPH_COMMUNITY_H + +#include "igraph_decls.h" + +#include "igraph_arpack.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* K-Cores and K-Truss */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_coreness( + const igraph_t *graph, igraph_vector_int_t *cores, igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_trussness( + const igraph_t* graph, igraph_vector_int_t* trussness); + +/* -------------------------------------------------- */ +/* Community Structure */ +/* -------------------------------------------------- */ + +/* TODO: cut.community */ +/* TODO: edge.type.matrix */ +/* TODO: */ + +IGRAPH_EXPORT igraph_error_t igraph_community_optimal_modularity(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t resolution, + igraph_real_t *modularity, + igraph_vector_int_t *membership); + +IGRAPH_EXPORT igraph_error_t igraph_community_spinglass(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + igraph_spinglass_implementation_t implementation, + igraph_real_t gamma_minus); + +IGRAPH_EXPORT igraph_error_t igraph_community_spinglass_single(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t vertex, + igraph_vector_int_t *community, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_real_t *inner_links, + igraph_real_t *outer_links, + igraph_int_t spins, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma); + +IGRAPH_EXPORT igraph_error_t igraph_community_walktrap(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t steps, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + +IGRAPH_EXPORT igraph_error_t igraph_community_infomap(const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_weights, + igraph_int_t nb_trials, + igraph_bool_t is_regularized, + igraph_real_t regularization_strength, + igraph_vector_int_t *membership, + igraph_real_t *codelength); + +IGRAPH_EXPORT igraph_error_t igraph_community_edge_betweenness(const igraph_t *graph, + igraph_vector_int_t *removed_edges, + igraph_vector_t *edge_betweenness, + igraph_matrix_int_t *merges, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership, + igraph_bool_t directed, + const igraph_vector_t *weights, + const igraph_vector_t *lengths); +IGRAPH_EXPORT igraph_error_t igraph_community_eb_get_merges(const igraph_t *graph, + igraph_bool_t directed, + const igraph_vector_int_t *edges, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + +IGRAPH_EXPORT igraph_error_t igraph_community_fastgreedy(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership); + +IGRAPH_EXPORT igraph_error_t igraph_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t nodes, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize); +IGRAPH_EXPORT igraph_error_t igraph_le_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_community_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, igraph_vector_int_t *generators, igraph_real_t *modularity, + const igraph_vector_t *lengths, const igraph_vector_t *weights, + igraph_neimode_t mode, igraph_real_t r); + +IGRAPH_EXPORT igraph_error_t igraph_modularity(const igraph_t *graph, + const igraph_vector_int_t *membership, + const igraph_vector_t *weights, + igraph_real_t resolution, + igraph_bool_t directed, + igraph_real_t *modularity); + +IGRAPH_EXPORT igraph_error_t igraph_modularity_matrix(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t resolution, + igraph_matrix_t *modmat, + igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_reindex_membership(igraph_vector_int_t *membership, + igraph_vector_int_t *new_to_old, + igraph_int_t *nb_clusters); + +typedef enum { IGRAPH_LEVC_HIST_SPLIT = 1, + IGRAPH_LEVC_HIST_FAILED, + IGRAPH_LEVC_HIST_START_FULL, + IGRAPH_LEVC_HIST_START_GIVEN + } igraph_leading_eigenvector_community_history_t; + +/** + * \typedef igraph_community_leading_eigenvector_callback_t + * Callback for the leading eigenvector community finding method. + * + * The leading eigenvector community finding implementation in igraph + * is able to call a callback function, after each eigenvalue + * calculation. This callback function must be of \c + * igraph_community_leading_eigenvector_callback_t type. + * The following arguments are passed to the callback: + * \param membership The actual membership vector, before recording + * the potential change implied by the newly found eigenvalue. + * \param comm The id of the community that the algorithm tried to + * split in the last iteration. The community IDs are indexed from + * zero here! + * \param eigenvalue The eigenvalue the algorithm has just found. + * \param eigenvector The eigenvector corresponding to the eigenvalue + * the algorithm just found. + * \param arpack_multiplier A function that was passed to \ref + * igraph_arpack_rssolve() to solve the last eigenproblem. + * \param arpack_extra The extra argument that was passed to the + * ARPACK solver. + * \param extra Extra argument that as passed to \ref + * igraph_community_leading_eigenvector(). + * + * \sa \ref igraph_community_leading_eigenvector(), \ref + * igraph_arpack_function_t, \ref igraph_arpack_rssolve(). + */ + +typedef igraph_error_t igraph_community_leading_eigenvector_callback_t( + const igraph_vector_int_t *membership, + igraph_int_t comm, + igraph_real_t eigenvalue, + const igraph_vector_t *eigenvector, + igraph_arpack_function_t *arpack_multiplier, + void *arpack_extra, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_community_leading_eigenvector(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_int_t *membership, + igraph_int_t steps, + igraph_arpack_options_t *options, + igraph_real_t *modularity, + igraph_bool_t start, + igraph_vector_t *eigenvalues, + igraph_vector_list_t *eigenvectors, + igraph_vector_int_t *history, + igraph_community_leading_eigenvector_callback_t *callback, + void *callback_extra); + +IGRAPH_EXPORT igraph_error_t igraph_community_fluid_communities(const igraph_t *graph, + igraph_int_t no_of_communities, + igraph_vector_int_t *membership); + + +IGRAPH_EXPORT igraph_error_t igraph_community_label_propagation(const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_neimode_t mode, + const igraph_vector_t *weights, + const igraph_vector_int_t *initial, + const igraph_vector_bool_t *fixed, + igraph_lpa_variant_t variant); + +IGRAPH_EXPORT igraph_error_t igraph_community_multilevel(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t resolution, + igraph_vector_int_t *membership, + igraph_matrix_int_t *memberships, + igraph_vector_t *modularity); + +typedef enum { + IGRAPH_LEIDEN_OBJECTIVE_MODULARITY = 0, + IGRAPH_LEIDEN_OBJECTIVE_CPM, + IGRAPH_LEIDEN_OBJECTIVE_ER +} igraph_leiden_objective_t; + +IGRAPH_EXPORT igraph_error_t igraph_community_leiden( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, igraph_real_t *quality); + +IGRAPH_EXPORT igraph_error_t igraph_community_leiden_simple( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_leiden_objective_t objective, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality); + +/* -------------------------------------------------- */ +/* Community Structure Comparison */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_compare_communities(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, + igraph_real_t* result, + igraph_community_comparison_t method); +IGRAPH_EXPORT igraph_error_t igraph_split_join_distance(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, + igraph_int_t* distance12, + igraph_int_t* distance21); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_complex.h b/include/igraph_complex.h new file mode 100644 index 0000000..36d3f2b --- /dev/null +++ b/include/igraph_complex.h @@ -0,0 +1,104 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COMPLEX_H +#define IGRAPH_COMPLEX_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +typedef struct igraph_complex_t { + igraph_real_t dat[2]; +} igraph_complex_t; + +#define IGRAPH_REAL(x) ((x).dat[0]) +#define IGRAPH_IMAG(x) ((x).dat[1]) +#define IGRAPH_COMPLEX_EQ(x,y) ((x).dat[0]==(y).dat[0] && (x).dat[1]==(y).dat[1]) + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex(igraph_real_t x, igraph_real_t y); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_polar(igraph_real_t r, igraph_real_t theta); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_bool_t igraph_complex_almost_equals(igraph_complex_t z1, + igraph_complex_t z2, + igraph_real_t eps); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_real_t igraph_complex_arg(igraph_complex_t z); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_real_t igraph_complex_abs(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_real_t igraph_complex_logabs(igraph_complex_t z); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_add(igraph_complex_t z1, + igraph_complex_t z2); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sub(igraph_complex_t z1, + igraph_complex_t z2); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_mul(igraph_complex_t z1, + igraph_complex_t z2); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_div(igraph_complex_t z1, + igraph_complex_t z2); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_add_real(igraph_complex_t z, + igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_add_imag(igraph_complex_t z, + igraph_real_t y); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sub_real(igraph_complex_t z, + igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sub_imag(igraph_complex_t z, + igraph_real_t y); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_mul_real(igraph_complex_t z, + igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_mul_imag(igraph_complex_t z, + igraph_real_t y); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_div_real(igraph_complex_t z, + igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_div_imag(igraph_complex_t z, + igraph_real_t y); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_conj(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_neg(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_inv(igraph_complex_t z); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sqrt(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sqrt_real(igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_exp(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_pow(igraph_complex_t z1, + igraph_complex_t z2); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_pow_real(igraph_complex_t z, + igraph_real_t x); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_log(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_log10(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_log_b(igraph_complex_t z, + igraph_complex_t b); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sin(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_cos(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_tan(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_sec(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_csc(igraph_complex_t z); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_complex_t igraph_complex_cot(igraph_complex_t z); + +IGRAPH_EXPORT int igraph_complex_printf(igraph_complex_t val); +IGRAPH_EXPORT int igraph_complex_fprintf(FILE *file, igraph_complex_t val); +IGRAPH_EXPORT int igraph_complex_printf_aligned(int width, igraph_complex_t val); +IGRAPH_EXPORT int igraph_complex_fprintf_aligned(FILE *file, int width, igraph_complex_t val); +IGRAPH_EXPORT int igraph_complex_snprintf(char *str, size_t size, igraph_complex_t val); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_components.h b/include/igraph_components.h new file mode 100644 index 0000000..e784c04 --- /dev/null +++ b/include/igraph_components.h @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COMPONENTS_H +#define IGRAPH_COMPONENTS_H + +#include "igraph_decls.h" + +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_graph_list.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Components */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_connected_components(const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no, + igraph_connectedness_t mode); +IGRAPH_EXPORT igraph_error_t igraph_is_connected(const igraph_t *graph, igraph_bool_t *res, + igraph_connectedness_t mode); +IGRAPH_EXPORT igraph_error_t igraph_decompose(const igraph_t *graph, igraph_graph_list_t *components, + igraph_connectedness_t mode, + igraph_int_t maxcompno, igraph_int_t minelements); +IGRAPH_EXPORT igraph_error_t igraph_articulation_points(const igraph_t *graph, + igraph_vector_int_t *res); +IGRAPH_EXPORT igraph_error_t igraph_biconnected_components(const igraph_t *graph, + igraph_int_t *no, + igraph_vector_int_list_t *tree_edges, + igraph_vector_int_list_t *component_edges, + igraph_vector_int_list_t *components, + igraph_vector_int_t *articulation_points); +IGRAPH_EXPORT igraph_error_t igraph_is_biconnected(const igraph_t *graph, igraph_bool_t *result); +IGRAPH_EXPORT igraph_error_t igraph_bridges(const igraph_t *graph, igraph_vector_int_t *bridges); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_bond_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count, + const igraph_vector_int_t *edge_order); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_site_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *edge_count, + const igraph_vector_int_t *vertex_order); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_edgelist_percolation( + const igraph_vector_int_t *edges, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_config.h.in b/include/igraph_config.h.in new file mode 100644 index 0000000..d8c910c --- /dev/null +++ b/include/igraph_config.h.in @@ -0,0 +1,51 @@ +/* + igraph library. + Copyright (C) 2011-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CONFIG_H +#define IGRAPH_CONFIG_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \define IGRAPH_INTEGER_SIZE + * + * Specifies the size of igraph's integer data type; must be one of 32 (for + * 32-bit integers) or 64 (for 64-bit integers). + */ +#define IGRAPH_INTEGER_SIZE @IGRAPH_INTEGER_SIZE@ + +#define IGRAPH_DEPRECATED_ENUMVAL @IGRAPH_DEPRECATED_ENUMVAL@ + +/** + * \define IGRAPH_BOOL_TYPE + * + * Specifies the C type to be used for igraph_bool_t. This is added here _only_ + * to support the R interface, where we want to be able to create views into + * R boolean vectors and treat them as an igraph_vector_bool_t, which requires + * us to align igraph_bool_t with R's boolean type. + * + * Any other use-case of overriding igraph's bool type is completely + * unsupported. + */ +#define IGRAPH_BOOL_TYPE bool + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_constants.h b/include/igraph_constants.h new file mode 100644 index 0000000..a9381ab --- /dev/null +++ b/include/igraph_constants.h @@ -0,0 +1,278 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CONSTANTS_H +#define IGRAPH_CONSTANTS_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Constants */ +/* -------------------------------------------------- */ + +/** + * \define IGRAPH_UNLIMITED + * \brief Constant for "do not limit results". + * + * A constant signifying that no limitation should be used with various cutoff, + * size limit or result set size parameters, such as minimum or maximum clique + * size, number of results returned, cutoff for path lengths, etc. Currently + * defined to -1. + */ +#define IGRAPH_UNLIMITED (-1) +/* Note to maintainers: IGRAPH_UNLIMITED is intended to support readability + * when *negative* parameter values indicate "no limit". Do not test + * directly against IGRAPH_UNLIMITED in implementations, test for negative + * values instead. */ + +/* These constants are meant to be used for sake of readability */ +enum { IGRAPH_UNDIRECTED = 0, IGRAPH_DIRECTED = 1 }; +enum { IGRAPH_NO_MULTIPLE = 0, IGRAPH_MULTIPLE = 1 }; +enum { IGRAPH_EDGE_UNLABELED = 0, IGRAPH_EDGE_LABELED = 1 }; + +/** + * \typedef igraph_loops_t + * \brief How to interpret self-loops in undirected graphs? + * + * Controls the interpretation of self-loops in undirected graphs, typically + * in the context of adjacency matrices or degrees. + * + * These constants are also used to improve readability in + * boolean contexts, with \c IGRAPH_NO_LOOPS, equivalent to \c false, + * signifying that loops should be ignored and \c IGRAPH_LOOPS, equivalent + * to \c true, that loops should be considered. + * + * \enumval IGRAPH_NO_LOOPS Self-loops are ignored. + * \enumval IGRAPH_LOOPS_TWICE Self-loops are considered, and counted twice + * in undirected graphs. For example, a self-loop contributes two to the + * degree of a vertex and to diagonal entries of adjacency matrices. This + * is the standard interpretation in graph theory, thus \c IGRAPH_LOOPS + * serves as an alias for this option. + * \enumval IGRAPH_LOOPS_ONCE Self-loops are considered, and counted only + * once in undirected graphs. + */ +typedef enum { + IGRAPH_NO_LOOPS = 0, + IGRAPH_LOOPS_TWICE = 1, + IGRAPH_LOOPS_ONCE = 2, + IGRAPH_LOOPS = IGRAPH_LOOPS_TWICE +} igraph_loops_t; +/* Note for the enum above: yes, IGRAPH_LOOPS_TWICE is 1, and IGRAPH_LOOPS_ONCE + * is 2. This is intentional, for the sake of backwards compatibility with + * earlier versions where we only had IGRAPH_LOOPS and it meant + * IGRAPH_LOOPS_TWICE */ + +typedef enum { IGRAPH_ASCENDING = 0, IGRAPH_DESCENDING = 1 } igraph_order_t; + +/** + * \typedef igraph_neimode_t + * \brief How to interpret edge directions in directed graphs? + * + * These "neighbor mode" constants are typically used to specify the treatment + * of edge directions in directed graphs, or which vertices to consider as + * adjacent to (i.e. neighbor of) a vertex. It is typically ignored in undirected + * graphs. + * + * \enumval IGRAPH_OUT Follow edge directions in directed graphs, or consider + * out-neighbors of vertices. + * \enumval IGRAPH_IN Follow edges in the reverse direction in directed graphs, + * or consider in-neighbors of vertices. + * \enumval IGRAPH_ALL Ignore edge directions in directed graphs, or consider + * all neighbours (both out and in-neighbors) of vertices. + */ +typedef enum { + IGRAPH_OUT = 1, + IGRAPH_IN = 2, + IGRAPH_ALL = 3 +} igraph_neimode_t; +/* Do not renumber the vaues above! Some internal code treats them as bitmasks + * and assumes that IGRAPH_ALL == IGRAPH_IN | IGRAPH_OUT and IGRAPH_IN & IGRAPH_OUT == 0. */ + +/* Reverse IGRAPH_OUT to IGRAPH_IN and vice versa. Leave other values alone. */ +#define IGRAPH_REVERSE_MODE(mode) \ + ((mode) == IGRAPH_IN ? IGRAPH_OUT : ((mode) == IGRAPH_OUT ? IGRAPH_IN : (mode))) + +typedef enum { IGRAPH_WEAK = 1, IGRAPH_STRONG = 2 } igraph_connectedness_t; + +typedef enum { IGRAPH_RECIPROCITY_DEFAULT = 0, + IGRAPH_RECIPROCITY_RATIO = 1 + } igraph_reciprocity_t; + +typedef enum { IGRAPH_ADJ_DIRECTED = 0, + IGRAPH_ADJ_UNDIRECTED, + IGRAPH_ADJ_UPPER, IGRAPH_ADJ_LOWER, IGRAPH_ADJ_MIN, + IGRAPH_ADJ_PLUS, + IGRAPH_ADJ_MAX, + } igraph_adjacency_t; + +typedef enum { IGRAPH_STAR_OUT = 0, IGRAPH_STAR_IN, + IGRAPH_STAR_UNDIRECTED, + IGRAPH_STAR_MUTUAL + } igraph_star_mode_t; + +typedef enum { IGRAPH_WHEEL_OUT = 0, IGRAPH_WHEEL_IN, + IGRAPH_WHEEL_UNDIRECTED, + IGRAPH_WHEEL_MUTUAL + } igraph_wheel_mode_t; + +typedef enum { IGRAPH_TREE_OUT = 0, IGRAPH_TREE_IN, + IGRAPH_TREE_UNDIRECTED + } igraph_tree_mode_t; + +typedef enum { IGRAPH_GET_ADJACENCY_UPPER = 0, + IGRAPH_GET_ADJACENCY_LOWER, + IGRAPH_GET_ADJACENCY_BOTH + } igraph_get_adjacency_t; + +typedef enum { IGRAPH_DEGSEQ_CONFIGURATION = 0, /* Configuration model, allowing non-simple graphs */ + IGRAPH_DEGSEQ_VL, /* Viger-Latapy, generates simple connected graphs */ + IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE, /* Fast heuristic, generates simple graphs */ + IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE, /* Configuration model, generates simple graphs */ + IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE, /* Edge-switching MCMC, generates simple graphs */ + } igraph_degseq_t; + +typedef enum { IGRAPH_REALIZE_DEGSEQ_SMALLEST = 0, + IGRAPH_REALIZE_DEGSEQ_LARGEST, + IGRAPH_REALIZE_DEGSEQ_INDEX + } igraph_realize_degseq_t; + +typedef enum { IGRAPH_RANDOM_TREE_PRUFER = 0, + IGRAPH_RANDOM_TREE_LERW + } igraph_random_tree_t; + +typedef enum { IGRAPH_EDGEORDER_ID = 0, + IGRAPH_EDGEORDER_FROM, + IGRAPH_EDGEORDER_TO + } igraph_edgeorder_type_t; + +typedef enum { IGRAPH_TO_DIRECTED_ARBITRARY = 0, + IGRAPH_TO_DIRECTED_MUTUAL, + IGRAPH_TO_DIRECTED_RANDOM, + IGRAPH_TO_DIRECTED_ACYCLIC + } igraph_to_directed_t; + +typedef enum { IGRAPH_TO_UNDIRECTED_EACH = 0, + IGRAPH_TO_UNDIRECTED_COLLAPSE, + IGRAPH_TO_UNDIRECTED_MUTUAL + } igraph_to_undirected_t; + +typedef enum { IGRAPH_VCONN_NEI_ERROR = 0, + IGRAPH_VCONN_NEI_NUMBER_OF_NODES, + IGRAPH_VCONN_NEI_IGNORE, + IGRAPH_VCONN_NEI_NEGATIVE + } igraph_vconn_nei_t; + +typedef enum { IGRAPH_SPINCOMM_UPDATE_SIMPLE = 0, + IGRAPH_SPINCOMM_UPDATE_CONFIG + } igraph_spincomm_update_t; + +typedef enum { IGRAPH_TRANSITIVITY_NAN = 0, + IGRAPH_TRANSITIVITY_ZERO + } igraph_transitivity_mode_t; + +typedef enum { IGRAPH_SPINCOMM_IMP_ORIG = 0, + IGRAPH_SPINCOMM_IMP_NEG + } igraph_spinglass_implementation_t; + +typedef enum { IGRAPH_COMMCMP_VI = 0, + IGRAPH_COMMCMP_NMI, + IGRAPH_COMMCMP_SPLIT_JOIN, + IGRAPH_COMMCMP_RAND, + IGRAPH_COMMCMP_ADJUSTED_RAND + } igraph_community_comparison_t; + +typedef enum { IGRAPH_ADD_WEIGHTS_NO = 0, + IGRAPH_ADD_WEIGHTS_YES, + IGRAPH_ADD_WEIGHTS_IF_PRESENT + } igraph_add_weights_t; + +typedef enum { IGRAPH_BARABASI_BAG = 0, + IGRAPH_BARABASI_PSUMTREE, + IGRAPH_BARABASI_PSUMTREE_MULTIPLE + } igraph_barabasi_algorithm_t; + +typedef enum { IGRAPH_FAS_EXACT_IP = 0, + IGRAPH_FAS_APPROX_EADES, + IGRAPH_FAS_EXACT_IP_CG, + IGRAPH_FAS_EXACT_IP_TI + } igraph_fas_algorithm_t; + +typedef enum { IGRAPH_FVS_EXACT_IP = 0 + } igraph_fvs_algorithm_t; + +typedef enum { IGRAPH_SUBGRAPH_AUTO = 0, + IGRAPH_SUBGRAPH_COPY_AND_DELETE, + IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH + } igraph_subgraph_implementation_t; + +typedef enum { IGRAPH_LAYOUT_GRID = 0, + IGRAPH_LAYOUT_NOGRID, + IGRAPH_LAYOUT_AUTOGRID + } igraph_layout_grid_t; + +typedef enum { IGRAPH_RANDOM_WALK_STUCK_ERROR = 0, + IGRAPH_RANDOM_WALK_STUCK_RETURN + } igraph_random_walk_stuck_t; + +typedef enum { IGRAPH_VORONOI_FIRST = 0, + IGRAPH_VORONOI_LAST, + IGRAPH_VORONOI_RANDOM + } igraph_voronoi_tiebreaker_t; + +typedef enum { IGRAPH_CHUNG_LU_ORIGINAL = 0, + IGRAPH_CHUNG_LU_MAXENT, + IGRAPH_CHUNG_LU_NR + } igraph_chung_lu_t; + +typedef enum { IGRAPH_ROW_MAJOR = 0, + IGRAPH_COLUMN_MAJOR = 1 + } igraph_matrix_storage_t; + +typedef enum { IGRAPH_MST_AUTOMATIC = 0, + IGRAPH_MST_UNWEIGHTED, + IGRAPH_MST_PRIM, + IGRAPH_MST_KRUSKAL + } igraph_mst_algorithm_t; + +typedef enum { IGRAPH_PRODUCT_CARTESIAN = 0, + IGRAPH_PRODUCT_LEXICOGRAPHIC, + IGRAPH_PRODUCT_STRONG, + IGRAPH_PRODUCT_TENSOR, + IGRAPH_PRODUCT_MODULAR + } igraph_product_t; + +/** + * \typedef igraph_lpa_variant_t + * \brief Label propagation algorithm variants of implementation + * + * Variants to run the label propagation algorithm. + * \enumval IGRAPH_LPA_DOMINANCE Check for dominance of all nodes after each iteration + * \enumval IGRAPH_LPA_RETENTION Keep current label if among dominant labels, only check if labels changed + * \enumval IGRAPH_LPA_FAST Sample from dominant labels, only check neighbors + */ +typedef enum { + IGRAPH_LPA_DOMINANCE = 0, /* Sample from dominant labels, check for dominance after each iteration. */ + IGRAPH_LPA_RETENTION, /* Keep current label if among dominant labels, only check if labels changed. */ + IGRAPH_LPA_FAST /* Sample from dominant labels, only check neighbors. */ +} igraph_lpa_variant_t; + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_constructors.h b/include/igraph_constructors.h new file mode 100644 index 0000000..f2a22ec --- /dev/null +++ b/include/igraph_constructors.h @@ -0,0 +1,105 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CONSTRUCTORS_H +#define IGRAPH_CONSTRUCTORS_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_datatype.h" +#include "igraph_graphicality.h" +#include "igraph_sparsemat.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Constructors, deterministic */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_create(igraph_t *graph, const igraph_vector_int_t *edges, igraph_int_t n, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_small(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + int first, ...); +IGRAPH_EXPORT igraph_error_t igraph_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_weighted_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_vector_t *weights, igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_sparse_adjacency(igraph_t *graph, igraph_sparsemat_t *adjmatrix, igraph_adjacency_t mode, igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_sparse_weighted_adjacency(igraph_t *graph, igraph_sparsemat_t *adjmatrix, igraph_adjacency_t mode, igraph_vector_t *weights, igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_star(igraph_t *graph, igraph_int_t n, igraph_star_mode_t mode, + igraph_int_t center); +IGRAPH_EXPORT igraph_error_t igraph_wheel(igraph_t *graph, igraph_int_t n, igraph_wheel_mode_t mode, + igraph_int_t center); +IGRAPH_EXPORT igraph_error_t igraph_hypercube(igraph_t *graph, + igraph_int_t n, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_square_lattice(igraph_t *graph, const igraph_vector_int_t *dimvector, igraph_int_t nei, + igraph_bool_t directed, igraph_bool_t mutual, const igraph_vector_bool_t *circular); +IGRAPH_EXPORT igraph_error_t igraph_ring(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_bool_t mutual, igraph_bool_t circular); +IGRAPH_EXPORT igraph_error_t igraph_path_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual); +IGRAPH_EXPORT igraph_error_t igraph_cycle_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual); +IGRAPH_EXPORT igraph_error_t igraph_kary_tree(igraph_t *graph, igraph_int_t n, igraph_int_t children, + igraph_tree_mode_t type); +IGRAPH_EXPORT igraph_error_t igraph_symmetric_tree(igraph_t *graph, const igraph_vector_int_t *branches, + igraph_tree_mode_t type); +IGRAPH_EXPORT igraph_error_t igraph_regular_tree(igraph_t *graph, igraph_int_t h, igraph_int_t k, + igraph_tree_mode_t type); +IGRAPH_EXPORT igraph_error_t igraph_tree_from_parent_vector(igraph_t *graph, const igraph_vector_int_t *parents, + igraph_tree_mode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_from_prufer(igraph_t *graph, const igraph_vector_int_t *prufer); +IGRAPH_EXPORT igraph_error_t igraph_full(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_full_multipartite(igraph_t *graph, igraph_vector_int_t *types, const igraph_vector_int_t *n, + igraph_bool_t directed, igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_turan(igraph_t *graph, igraph_vector_int_t *types, igraph_int_t n, igraph_int_t r); +IGRAPH_EXPORT igraph_error_t igraph_full_citation(igraph_t *graph, igraph_int_t n, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_atlas(igraph_t *graph, igraph_int_t number); +IGRAPH_EXPORT igraph_error_t igraph_extended_chordal_ring(igraph_t *graph, igraph_int_t nodes, + const igraph_matrix_int_t *W, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_linegraph(const igraph_t *graph, igraph_t *linegraph); + +IGRAPH_EXPORT igraph_error_t igraph_de_bruijn(igraph_t *graph, igraph_int_t m, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t igraph_circulant(igraph_t *graph, igraph_int_t n, const igraph_vector_int_t *l, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_generalized_petersen(igraph_t *graph, igraph_int_t n, igraph_int_t k); +IGRAPH_EXPORT igraph_error_t igraph_kautz(igraph_t *graph, igraph_int_t m, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t igraph_famous(igraph_t *graph, const char *name); +IGRAPH_EXPORT igraph_error_t igraph_lcf(igraph_t *graph, igraph_int_t n, + const igraph_vector_int_t *shifts, + igraph_int_t repeats); +IGRAPH_EXPORT igraph_error_t igraph_lcf_small(igraph_t *graph, igraph_int_t n, ...); +IGRAPH_EXPORT igraph_error_t igraph_realize_degree_sequence(igraph_t *graph, + const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, + igraph_edge_type_sw_t allowed_edge_types, + igraph_realize_degseq_t method); +IGRAPH_EXPORT igraph_error_t igraph_triangular_lattice(igraph_t *graph, const igraph_vector_int_t *dims, igraph_bool_t directed, igraph_bool_t mutual); +IGRAPH_EXPORT igraph_error_t igraph_hexagonal_lattice(igraph_t *graph, const igraph_vector_int_t *dims, igraph_bool_t directed, igraph_bool_t mutual); +IGRAPH_EXPORT igraph_error_t igraph_realize_bipartite_degree_sequence(igraph_t *graph, const igraph_vector_int_t *deg1, const igraph_vector_int_t *deg2, igraph_edge_type_sw_t allowed_edge_types, igraph_realize_degseq_t method); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_mycielski_graph(igraph_t *graph, igraph_int_t k); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_conversion.h b/include/igraph_conversion.h new file mode 100644 index 0000000..54a93f7 --- /dev/null +++ b/include/igraph_conversion.h @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CONVERSION_H +#define IGRAPH_CONVERSION_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_sparsemat.h" +#include "igraph_attributes.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Conversion */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_get_adjacency( + const igraph_t *graph, igraph_matrix_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops +); +IGRAPH_EXPORT igraph_error_t igraph_get_adjacency_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops +); + +IGRAPH_EXPORT igraph_error_t igraph_get_stochastic( + const igraph_t *graph, igraph_matrix_t *matrix, igraph_bool_t column_wise, + const igraph_vector_t *weights +); + +IGRAPH_EXPORT igraph_error_t igraph_get_stochastic_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_bool_t column_wise, + const igraph_vector_t *weights +); + +IGRAPH_EXPORT igraph_error_t igraph_get_edgelist(const igraph_t *graph, igraph_vector_int_t *res, igraph_bool_t bycol); + +IGRAPH_EXPORT igraph_error_t igraph_to_directed(igraph_t *graph, + igraph_to_directed_t flags); +IGRAPH_EXPORT igraph_error_t igraph_to_undirected(igraph_t *graph, + igraph_to_undirected_t mode, + const igraph_attribute_combination_t *edge_comb); +IGRAPH_EXPORT igraph_error_t igraph_to_prufer(const igraph_t *graph, igraph_vector_int_t *prufer); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_cycles.h b/include/igraph_cycles.h new file mode 100644 index 0000000..93e9f5e --- /dev/null +++ b/include/igraph_cycles.h @@ -0,0 +1,103 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CYCLES_H +#define IGRAPH_CYCLES_H + +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Directed acyclic graphs */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_topological_sorting( + const igraph_t *graph, igraph_vector_int_t *res, igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_is_dag(const igraph_t *graph, igraph_bool_t *res); + +/* -------------------------------------------------- */ +/* Cycle bases */ +/* -------------------------------------------------- */ + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_fundamental_cycles( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_int_t start_vid, igraph_real_t bfs_cutoff); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_minimum_cycle_basis( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_real_t bfs_cutoff, + igraph_bool_t complete, igraph_bool_t use_cycle_order); + +IGRAPH_EXPORT igraph_error_t igraph_find_cycle( + const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_neimode_t mode); + +/** + * \typedef igraph_cycle_handler_t + * \brief Type of cycle handler functions. + * + * Callback type, called by \ref igraph_simple_cycles_callback() when + * a cycle is found. + * + * \param vertices The vertices of the current cycle. Must not be modified. + * \param edges The edges of the current cycle. Must not be modified. + * \param arg The extra parameter passed to \ref igraph_simple_cycles_callback() + * \return Error code; \c IGRAPH_SUCCESS to continue the search or + * \c IGRAPH_STOP to stop the search without signaling an error. + */ +typedef igraph_error_t igraph_cycle_handler_t( + const igraph_vector_int_t *vertices, + const igraph_vector_int_t *edges, + void *arg); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_simple_cycles_callback( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, + igraph_int_t max_cycle_length, + igraph_cycle_handler_t *callback, void *arg); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_simple_cycles( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, igraph_int_t max_cycle_length, + igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_feedback_arc_set( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights, igraph_fas_algorithm_t algo); + +IGRAPH_EXPORT igraph_error_t igraph_feedback_vertex_set( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *vertex_weights, igraph_fvs_algorithm_t algo); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_datatype.h b/include/igraph_datatype.h new file mode 100644 index 0000000..1c36d1d --- /dev/null +++ b/include/igraph_datatype.h @@ -0,0 +1,122 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_DATATYPE_H +#define IGRAPH_DATATYPE_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +struct igraph_i_property_cache_t; +typedef struct igraph_i_property_cache_t igraph_i_property_cache_t; + +typedef enum { + /* Stores whether the graph has at least one self-loop. */ + IGRAPH_PROP_HAS_LOOP = 0, + + /* Stores whether the graph has at least one multi-edge, taking into account + * edge directions in directed graphs. In other words, this property should + * be false for a directed graph with edges (a, b) and (b, a), and true + * for a directed graph with edges (a, b) and (a, b) again. */ + IGRAPH_PROP_HAS_MULTI, + + /* Stores whether the graph has at least one reciprocal edge pair. Ignored + * in undirected graphs. This property should be true for a directed graph + * with edges (a, b) and (b, a), and false for a directed graph with + * edges (a, b) and (a, b) again. Self-loops (a, a) are not considered + * reciprocal. */ + IGRAPH_PROP_HAS_MUTUAL, + + /* Stores whether the graph is weakly connected. */ + IGRAPH_PROP_IS_WEAKLY_CONNECTED, + + /* Stores whether the graph is strongly connected. Ignored in undirected graphs. */ + IGRAPH_PROP_IS_STRONGLY_CONNECTED, + + /* Stores whether the graph is a directed acyclic graph. Not used for + * undirected graphs. */ + IGRAPH_PROP_IS_DAG, + + /* Stores whether the graph is a forest, i.e. an undirected or directed + * graph that is cycle-free even if we ignore edge directions. */ + IGRAPH_PROP_IS_FOREST, + + /* Dummy value used to count enum values */ + IGRAPH_PROP_I_SIZE +} igraph_cached_property_t; + +/** + * \ingroup internal + * \struct igraph_t + * \brief The internal data structure for storing graphs. + * + * It is simple and efficient. It has the following members: + * - n The number of vertices, redundant. + * - directed Whether the graph is directed. + * - from The first column of the edge list. + * - to The second column of the edge list. + * - oi The index of the edge list by the first column. Thus + * the first edge according to this order goes from + * \c from[oi[0]] to \c to[oi[0]]. The length of + * this vector is the same as the number of edges in the graph. + * - ii The index of the edge list by the second column. + * The length of this vector is the same as the number of edges. + * - os Contains pointers to the edgelist (\c from + * and \c to for every vertex. The first edge \em from + * vertex \c v is edge no. \c from[oi[os[v]]] if + * \c os[v]is This is basically the same as os, but this time + * for the incoming edges. + * + * For undirected graphs, the same edge list is stored, i.e. an + * undirected edge is stored only once. Currently, undirected edges + * are canonicalized so that the index of the 'from' vertex is not greater + * than the index of the 'to' vertex. Thus, if v1 <= v2, only the edge (v1, v2) + * needs to be searched for, not (v2, v1), to determine if v1 and v2 are connected. + * However, this fact is NOT guaranteed by the documented public API, + * and should not be relied upon by the implementation of any functions, + * except those belonging to the minimal API in type_indexededgelist.c. + * + * The storage requirements for a graph with \c |V| vertices + * and \c |E| edges is \c O(|E|+|V|). + */ +typedef struct { + igraph_int_t n; + igraph_bool_t directed; + igraph_vector_int_t from; + igraph_vector_int_t to; + igraph_vector_int_t oi; + igraph_vector_int_t ii; + igraph_vector_int_t os; + igraph_vector_int_t is; + void *attr; + igraph_i_property_cache_t *cache; +} igraph_t; + +IGRAPH_EXPORT void igraph_invalidate_cache(const igraph_t* graph); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_decls.h b/include/igraph_decls.h new file mode 100644 index 0000000..1c63c1f --- /dev/null +++ b/include/igraph_decls.h @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2016-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#undef IGRAPH_BEGIN_C_DECLS +#undef IGRAPH_END_C_DECLS +#ifdef __cplusplus + #define IGRAPH_BEGIN_C_DECLS extern "C" { + #define IGRAPH_END_C_DECLS } +#else + #define IGRAPH_BEGIN_C_DECLS /* empty */ + #define IGRAPH_END_C_DECLS /* empty */ +#endif + +/* This is to eliminate gcc warnings about unused parameters */ +#define IGRAPH_UNUSED(x) (void)(x) + +/* The pure function attribute of GCC-compatible compilers indicates + * that the function does not have side-effects, i.e. it does not + * modify global memory. This enables additional compiler optimizations + * such as common subexpression elimination. + * + * The const attribute is similar but with much more stringent requirements. + * The function must also not read global memory. Generally, const functions + * should not take pointers, and must compute the return value solely based + * on their input. + */ +#ifdef __GNUC__ +#define IGRAPH_FUNCATTR_PURE __attribute__((__pure__)) +#define IGRAPH_FUNCATTR_CONST __attribute__((__const__)) +#else +#define IGRAPH_FUNCATTR_PURE +#define IGRAPH_FUNCATTR_CONST +#endif + +/* IGRAPH_ASSUME() provides hints to the compiler about conditions + * that are true yet the compiler cannot deduce. Use with great care. + * Assuming a condition that is not actually true leads to undefined behaviour. */ +#if defined(__clang__) + /* For Clang, see https://clang.llvm.org/docs/LanguageExtensions.html */ +# if __has_builtin(__builtin_assume) +# define IGRAPH_ASSUME(expr) __builtin_assume(expr) +# else +# define IGRAPH_ASSUME(expr) /* empty */ +# endif +#elif defined(__GNUC__) && !defined(__ICC) + /* Introduced in GCC 4.5, https://gcc.gnu.org/gcc-4.5/changes.html */ +# define IGRAPH_ASSUME(expr) do { if (expr) {} else { __builtin_unreachable(); } } while (0) +#elif defined(_MSC_VER) || defined(__ICC) +# define IGRAPH_ASSUME(expr) __assume(expr) +#else +# define IGRAPH_ASSUME(expr) /* empty */ +#endif + +/* IGRAPH_I_STRINGIFY(X) evaluates X and converts the result to a string. */ +#define IGRAPH_I_STRINGIFY_I(X) #X +#define IGRAPH_I_STRINGIFY(X) IGRAPH_I_STRINGIFY_I(X) + +/* Include the definition of macros controlling symbol visibility */ +#include "igraph_export.h" + +/* Used instead of IGRAPH_EXPORT with functions that need to be tested, + * but are not part of the public API. */ +#define IGRAPH_PRIVATE_EXPORT IGRAPH_EXPORT + +/* Marks experimental functions. If IGRAPH_WARN_EXPERIMENTAL is defined to a + * nonzero value, supported compilers will emit a warning when these functions + * are used. */ +#if defined(__GNUC__) && IGRAPH_WARN_EXPERIMENTAL +#define IGRAPH_EXPERIMENTAL __attribute__((__warning__("Experimental function."))) +#else +#define IGRAPH_EXPERIMENTAL /* empty */ +#endif diff --git a/include/igraph_dqueue.h b/include/igraph_dqueue.h new file mode 100644 index 0000000..d96bc94 --- /dev/null +++ b/include/igraph_dqueue.h @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_DQUEUE_H +#define IGRAPH_DQUEUE_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* double ended queue, very useful */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define IGRAPH_DQUEUE_NULL { 0,0,0,0 } +#define IGRAPH_DQUEUE_INIT_FINALLY(q, capacity) \ + do { IGRAPH_CHECK(igraph_dqueue_init(q, capacity)); \ + IGRAPH_FINALLY(igraph_dqueue_destroy, q); } while (0) +#define IGRAPH_DQUEUE_INT_INIT_FINALLY(q, capacity) \ + do { IGRAPH_CHECK(igraph_dqueue_int_init(q, capacity)); \ + IGRAPH_FINALLY(igraph_dqueue_int_destroy, q); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_dqueue_pmt.h b/include/igraph_dqueue_pmt.h new file mode 100644 index 0000000..d547cd8 --- /dev/null +++ b/include/igraph_dqueue_pmt.h @@ -0,0 +1,44 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * Double ended queue data type. + * \ingroup internal + */ + +typedef struct TYPE(igraph_dqueue) { + BASE *begin; + BASE *end; + BASE *stor_begin; + BASE *stor_end; +} TYPE(igraph_dqueue); + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_dqueue, init)(TYPE(igraph_dqueue)* q, igraph_int_t capacity); +IGRAPH_EXPORT void FUNCTION(igraph_dqueue, destroy)(TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_dqueue, empty)(const TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT void FUNCTION(igraph_dqueue, clear)(TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_dqueue, full)(TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_dqueue, size)(const TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT BASE FUNCTION(igraph_dqueue, pop)(TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT BASE FUNCTION(igraph_dqueue, pop_back)(TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_dqueue, head)(const TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_dqueue, back)(const TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_dqueue, push)(TYPE(igraph_dqueue)* q, BASE elem); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_dqueue, print)(const TYPE(igraph_dqueue)* q); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_dqueue, fprint)(const TYPE(igraph_dqueue)* q, FILE *file); +IGRAPH_EXPORT BASE FUNCTION(igraph_dqueue, get)(const TYPE(igraph_dqueue) *q, igraph_int_t idx); diff --git a/include/igraph_eigen.h b/include/igraph_eigen.h new file mode 100644 index 0000000..281b83a --- /dev/null +++ b/include/igraph_eigen.h @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_EIGEN_H +#define IGRAPH_EIGEN_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_arpack.h" +#include "igraph_error.h" +#include "igraph_lapack.h" +#include "igraph_sparsemat.h" + +IGRAPH_BEGIN_C_DECLS + +typedef enum { + IGRAPH_EIGEN_AUTO = 0, + IGRAPH_EIGEN_LAPACK, + IGRAPH_EIGEN_ARPACK, + IGRAPH_EIGEN_COMP_AUTO, + IGRAPH_EIGEN_COMP_LAPACK, + IGRAPH_EIGEN_COMP_ARPACK +} igraph_eigen_algorithm_t; + +typedef enum { + IGRAPH_EIGEN_LM = 0, + IGRAPH_EIGEN_SM, /* 1 */ + IGRAPH_EIGEN_LA, /* 2 */ + IGRAPH_EIGEN_SA, /* 3 */ + IGRAPH_EIGEN_BE, /* 4 */ + IGRAPH_EIGEN_LR, /* 5 */ + IGRAPH_EIGEN_SR, /* 6 */ + IGRAPH_EIGEN_LI, /* 7 */ + IGRAPH_EIGEN_SI, /* 8 */ + IGRAPH_EIGEN_ALL, /* 9 */ + IGRAPH_EIGEN_INTERVAL, /* 10 */ + IGRAPH_EIGEN_SELECT /* 11 */ +} igraph_eigen_which_position_t; + +typedef struct igraph_eigen_which_t { + igraph_eigen_which_position_t pos; + int howmany; + int il, iu; + igraph_real_t vl, vu; + int vestimate; + igraph_lapack_dgeevx_balance_t balance; +} igraph_eigen_which_t; + +IGRAPH_EXPORT igraph_error_t igraph_eigen_matrix_symmetric(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors); + +IGRAPH_EXPORT igraph_error_t igraph_eigen_matrix(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors); + +IGRAPH_EXPORT igraph_error_t igraph_eigen_adjacency(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_embedding.h b/include/igraph_embedding.h new file mode 100644 index 0000000..2fa8ff6 --- /dev/null +++ b/include/igraph_embedding.h @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2013-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_EMBEDDING_H +#define IGRAPH_EMBEDDING_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_arpack.h" +#include "igraph_eigen.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_adjacency_spectral_embedding(const igraph_t *graph, + igraph_int_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + igraph_arpack_options_t *options); + +typedef enum { + IGRAPH_EMBEDDING_D_A = 0, + IGRAPH_EMBEDDING_I_DAD, + IGRAPH_EMBEDDING_DAD, + IGRAPH_EMBEDDING_OAP +} igraph_laplacian_spectral_embedding_type_t; + +IGRAPH_EXPORT igraph_error_t igraph_laplacian_spectral_embedding(const igraph_t *graph, + igraph_int_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options); + +IGRAPH_EXPORT igraph_error_t igraph_dim_select(const igraph_vector_t *sv, igraph_int_t *dim); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_epidemics.h b/include/igraph_epidemics.h new file mode 100644 index 0000000..361f38c --- /dev/null +++ b/include/igraph_epidemics.h @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2014-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_EPIDEMICS_H +#define IGRAPH_EPIDEMICS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \struct igraph_sir_t + * \brief The result of one SIR model simulation. + * + * Data structure to store the results of one simulation + * of the SIR (susceptible-infected-recovered) model on a graph. + * + * It has the following members. They are all (real or integer) + * vectors, and they are of the same length. + * + * \member times A vector, the times of the events are stored here. + * \member no_s An integer vector, the number of susceptibles in + * each time step is stored here. + * \member no_i An integer vector, the number of infected individuals + * at each time step, is stored here. + * \member no_r An integer vector, the number of recovered individuals + * is stored here at each time step. + */ + +typedef struct igraph_sir_t { + igraph_vector_t times; + igraph_vector_int_t no_s, no_i, no_r; +} igraph_sir_t; + +IGRAPH_EXPORT igraph_error_t igraph_sir_init(igraph_sir_t *sir); +IGRAPH_EXPORT void igraph_sir_destroy(igraph_sir_t *sir); + +IGRAPH_EXPORT igraph_error_t igraph_sir(const igraph_t *graph, igraph_real_t beta, + igraph_real_t gamma, igraph_int_t no_sim, + igraph_vector_ptr_t *result); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_error.h b/include/igraph_error.h new file mode 100644 index 0000000..33ecdb6 --- /dev/null +++ b/include/igraph_error.h @@ -0,0 +1,932 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ERROR_H +#define IGRAPH_ERROR_H + +#include "igraph_decls.h" +#include "igraph_config.h" + +#include + +IGRAPH_BEGIN_C_DECLS + +/* This file contains the igraph error handling. + * Most bits are taken literally from the GSL library (with the GSL_ + * prefix renamed to IGRAPH_), as I couldn't find a better way to do + * them. */ + +/* With some compilers, we use function attributes to help diagnostics + * and optimizations. These are not part of the public API, do not use + * them outside of igraph itself. + * + * IGRAPH_FUNCATTR_NORETURN indicates to the compiler that a function does not return. + * There are standard facilities for this, namely _Noreturn in C11 and [[noreturn]] in C++11. + * However, since igraph is currently compiled with older standards, and since + * the standard 'noreturn' specification would need to be diferent between C and C++, + * we do not use these facilities. + * + * IGRAPH_FUNCATTR_PRINTFLIKE(string, first) marks a function as having a printf-like syntax, + * allowing the compiler to check that the format specifiers match argument types. + * 'string' is the index of the string-argument and 'first' is the index of the + * first argument to check against format specifiers. + */ +#if defined(__GNUC__) +/* Compilers that support the GNU C syntax. Use __noreturn__ instead of 'noreturn' as the latter is a macro in C11. */ +#define IGRAPH_FUNCATTR_NORETURN __attribute__((__noreturn__)) +#define IGRAPH_FUNCATTR_PRINTFLIKE(string, first) __attribute__((__format__(printf, string, first))) +#elif defined(_MSC_VER) +/* Compilers that support the MSVC syntax. */ +#define IGRAPH_FUNCATTR_NORETURN __declspec(noreturn) +#define IGRAPH_FUNCATTR_PRINTFLIKE(string, first) +#else +#define IGRAPH_FUNCATTR_NORETURN +#define IGRAPH_FUNCATTR_PRINTFLIKE(string, first) +#endif + +/* IGRAPH_PREPROCESSOR_WARNING(reason) is a macro that evaluates to nothing + * but triggers a preprocessor warning with the given message, if the compiler + * supports this functionality. + */ +#if defined(__GNUC__) +#define IGRAPH_PREPROCESSOR_WARNING(reason) _Pragma(IGRAPH_I_STRINGIFY(GCC warning reason)) +#else +#define IGRAPH_PREPROCESSOR_WARNING(reason) /* empty */ +#endif + +/** + * \section error_handling_basics Error handling basics + * + * \a igraph functions can run into various problems preventing them + * from normal operation. The user might have supplied invalid arguments, + * e.g. a non-square matrix when a square-matrix was expected, or the program + * has run out of memory while some more memory allocation is required, etc. + * + * + * By default \a igraph aborts the program when it runs into an + * error. While this behavior might be good enough for smaller programs, + * it is without doubt avoidable in larger projects. Please read further + * if your project requires more sophisticated error handling. You can + * safely skip the rest of this chapter otherwise. + * + */ + +/** + * \section error_handlers Error handlers + * + * + * If \a igraph runs into an error - an invalid argument was supplied + * to a function, or we've ran out of memory - the control is + * transferred to the \emb error handler \eme function. + * + * The default error handler is \ref igraph_error_handler_abort which + * prints an error message and aborts the program. + * + * + * The \ref igraph_set_error_handler() function can be used to set a new + * error handler function of type \ref igraph_error_handler_t; see the + * documentation of this type for details. + * + * + * There are two other predefined error handler functions, + * \ref igraph_error_handler_ignore and \ref igraph_error_handler_printignore. + * These deallocate the temporarily allocated memory (more about this + * later) and return with the error code. The latter also prints an + * error message. If you use these error handlers you need to take + * care about possible errors yourself by checking the return value of + * (almost) every non-void \a igraph function. + * + * Independently of the error handler installed, all functions in the + * library do their best to leave their arguments + * \em semantically unchanged if an error + * happens. By semantically we mean that the implementation of an + * object supplied as an argument might change, but its + * \quote meaning \endquote in most cases does not. The rare occasions + * when this rule is violated are documented in this manual. + * + */ + +/** + * \section error_codes Error codes + * + * Every \a igraph function which can fail return a + * single integer error code. Some functions are very simple and + * cannot run into any error, these may return other types, or + * \type void as well. The error codes are defined by the + * \ref igraph_error_type_t enumeration. + * + */ + +/** + * \section writing_error_handlers Writing error handlers + * + * + * The contents of the rest of this chapter might be useful only + * for those who want to create an interface to \a igraph from another + * language, or use igraph from a GUI application. Most readers can + * safely skip to the next chapter. + * + * + * + * You can write and install error handlers simply by defining a + * function of type \ref igraph_error_handler_t and calling + * \ref igraph_set_error_handler(). This feature is useful for interface + * writers, as \a igraph will have the chance to + * signal errors the appropriate way. For example, the R interface uses + * R's native printing facilities to communicate errors, while the Python + * interface converts them into Python exceptions. + * + * + * + * The two main tasks of the error handler are to report the error + * (i.e. print the error message) and ensure proper resource cleanup. + * This is ensured by calling \ref IGRAPH_FINALLY_FREE(), which deallocates + * some of the temporary memory to avoid memory leaks. Note that this may + * invalidate the error message buffer \p reason passed to the error handler. + * Do not access it after having called \ref IGRAPH_FINALLY_FREE(). + * + * + * + * As of \a igraph 0.10, temporary memory is dellocated in stages, through + * multiple calls to the error handler (and indirectly to \ref IGRAPH_FINALLY_FREE()). + * Therefore, error handlers that do not abort the program + * immediately are expected to return. The error handler should not perform + * a longjmp, as this may lead to some of the memory not + * getting freed. + * + */ + +/** + * \section error_handling_internals Error handling internals + * + * + * If an error happens, the functions in the library call the + * \ref IGRAPH_ERROR() macro with a textual description of the error and an + * \a igraph error code. This macro calls (through the \ref + * igraph_error() function) the installed error handler. Another useful + * macro is \ref IGRAPH_CHECK(). This checks the return value of its + * argument, which is normally a function call, and calls \ref + * IGRAPH_ERROR() if it is not \c IGRAPH_SUCCESS. + * + */ + +/** + * \section deallocating_memory Deallocating memory + * + * + * If a function runs into an error (and the program is not aborted) + * the error handler should deallocate all temporary memory. This is + * done by storing the address and the destroy function of all temporary + * objects in a stack. The \ref IGRAPH_FINALLY function declares an object as + * temporary by placing its address in the stack. If an \a igraph function returns + * with success it calls \ref IGRAPH_FINALLY_CLEAN() with the + * number of objects to remove from the stack. If an error happens + * however, the error handler should call \ref IGRAPH_FINALLY_FREE() to + * deallocate each object added to the stack. This means that the + * temporary objects allocated in the calling function (and etc.) will + * be freed as well. + * + */ + +/** + * \section writing_functions_error_handling Writing \a igraph functions with + * proper error handling + * + * + * There are some simple rules to keep in order to have functions + * behaving well in erroneous situations. First, check the arguments + * of the functions and call \ref IGRAPH_ERROR() if they are invalid. Second, + * call \ref IGRAPH_FINALLY on each dynamically allocated object and call + * \ref IGRAPH_FINALLY_CLEAN() with the proper argument before returning. Third, use + * \ref IGRAPH_CHECK on all \a igraph function calls which can generate errors. + * + * + * The size of the stack used for this bookkeeping is fixed, and + * small. If you want to allocate several objects, write a destroy + * function which can deallocate all of these. See the + * adjlist.c file in the + * \a igraph source for an example. + * + * + * For some functions these mechanisms are simply not flexible + * enough. These functions should define their own error handlers and + * restore the error handler before they return. + * + * + * \example examples/simple/igraph_contract_vertices.c + */ + +/** + * \typedef igraph_error_type_t + * \brief Error code type. + * These are the possible values returned by \a igraph functions. + * Note that these are interesting only if you defined an error handler + * with \ref igraph_set_error_handler(). Otherwise the program is aborted + * and the function causing the error never returns. + * + * \enumval IGRAPH_SUCCESS The function successfully completed its task. + * \enumval IGRAPH_FAILURE Something went wrong. You'll almost never + * meet this error as normally more specific error codes are used. + * \enumval IGRAPH_ENOMEM There wasn't enough memory to allocate + * on the heap. + * \enumval IGRAPH_PARSEERROR A parse error was found in a file. + * \enumval IGRAPH_EINVAL A parameter's value is invalid. E.g. negative + * number was specified as the number of vertices. + * \enumval IGRAPH_EXISTS A graph/vertex/edge attribute is already + * installed with the given name. + * \enumval IGRAPH_EINVVID Invalid vertex ID, negative or too big. + * \enumval IGRAPH_EINVEID Invalid edge ID, negative or too big. + * \enumval IGRAPH_EINVMODE Invalid mode parameter. + * \enumval IGRAPH_EFILE A file operation failed. E.g. a file doesn't exist, + * or the user has no rights to open it. + * \enumval IGRAPH_UNIMPLEMENTED Attempted to call an unimplemented or + * disabled (at compile-time) function. + * \enumval IGRAPH_DIVERGED A numeric algorithm failed to converge. + * \enumval IGRAPH_ARPACK An error happened inside a calculation implemented + * in ARPACK. The calculation involved is most likely an eigenvector-related + * calculation. + * \enumval IGRAPH_ENEGCYCLE Negative cycle detected while calculating shortest paths. + * \enumval IGRAPH_EINTERNAL Internal error, likely a bug in igraph. + * \enumval IGRAPH_EATTRCOMBINE Unimplemented attribute combination + * method for the given attribute type. + * \enumval IGRAPH_EOVERFLOW Integer or double overflow. + * \enumval IGRAPH_EUNDERFLOW Integer or double underflow. + * \enumval IGRAPH_ERWSTUCK Random walk got stuck. + * \enumval IGRAPH_ERANGE Maximum vertex or edge count exceeded. + * \enumval IGRAPH_ENOSOL Input problem has no solution. + */ + +typedef enum { + IGRAPH_SUCCESS = 0, + IGRAPH_FAILURE = 1, + IGRAPH_ENOMEM = 2, + IGRAPH_PARSEERROR = 3, + IGRAPH_EINVAL = 4, + IGRAPH_EXISTS = 5, + /* IGRAPH_EINVEVECTOR = 6, */ /* removed in 1.0 */ + IGRAPH_EINVVID = 7, + IGRAPH_EINVEID = 8, /* used to be IGRAPH_NONSQUARE before 1.0 */ + IGRAPH_EINVMODE = 9, + IGRAPH_EFILE = 10, + IGRAPH_UNIMPLEMENTED = 12, + IGRAPH_INTERRUPTED = 13, + IGRAPH_DIVERGED = 14, + IGRAPH_EARPACK = 15, + /* ARPACK error codes from 15 to 36 were moved to igraph_arpack_error_t in 1.0 */ + IGRAPH_ENEGCYCLE = 37, + IGRAPH_EINTERNAL = 38, + /* ARPACK error codes from 39 to 41 were moved to igraph_arpack_error_t in 1.0 */ + /* IGRAPH_EDIVZERO = 42, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EBOUND = 43, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EROOT = 44, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ENOPFS = 45, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ENODFS = 46, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EFAIL = 47, */ /* removed in 1.0 */ + /* IGRAPH_GLP_EMIPGAP = 48, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ETMLIM = 49, */ /* removed in 1.0 */ + /* IGRAPH_GLP_ESTOP = 50, */ /* removed in 1.0 */ + /* IGRAPH_EATTRIBUTES = 51, */ /* removed in 1.0 */ + IGRAPH_EATTRCOMBINE = 52, + /* IGRAPH_ELAPACK = 53, */ /* removed in 1.0 */ + /* IGRAPH_EDRL = 54, */ /* deprecated in 0.10.2, removed in 1.0 */ + IGRAPH_EOVERFLOW = 55, + /* IGRAPH_EGLP = 56, */ /* removed in 1.0 */ + /* IGRAPH_CPUTIME = 57, */ /* removed in 1.0 */ + IGRAPH_EUNDERFLOW = 58, + IGRAPH_ERWSTUCK = 59, + IGRAPH_STOP = 60, + IGRAPH_ERANGE = 61, + IGRAPH_ENOSOL = 62 +} igraph_error_type_t; +/* Each enum value above must have a corresponding error string in + * igraph_i_error_strings[] in core/error.c + * + * Information on undocumented codes: + * - IGRAPH_STOP signals a request to stop in functions like igraph_i_maximal_cliques_bk() + */ + +/** + * \section error_handling_threads Error handling and threads + * + * + * It is likely that the \a igraph error handling + * method is \em not thread-safe, mainly because of + * the static global stack which is used to store the address of the + * temporarily allocated objects. This issue might be addressed in a + * later version of \a igraph. + * + */ + +/** + * \typedef igraph_error_t + * \brief Return type for functions returning an error code. + * + * This type is used as the return type of igraph functions that return an + * error code. It is a type alias because \type igraph_error_t used to be + * an \c int, and was used slightly differenly than \type igraph_error_type_t. + */ +typedef igraph_error_type_t igraph_error_t; + +/** + * \typedef igraph_error_handler_t + * \brief The type of error handler functions. + * + * This is the type of the error handler functions. + * + * \param reason Textual description of the error. + * \param file The source file in which the error is noticed. + * \param line The number of the line in the source file which triggered + * the error + * \param igraph_errno The \a igraph error code. + */ + +typedef void igraph_error_handler_t(const char *reason, const char *file, + int line, igraph_error_t igraph_errno); + +/** + * \var igraph_error_handler_abort + * \brief Abort program in case of error. + * + * The default error handler, prints an error message and aborts the + * program. + */ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_NORETURN igraph_error_handler_t igraph_error_handler_abort; + +/** + * \var igraph_error_handler_ignore + * \brief Ignore errors. + * + * This error handler frees the temporarily allocated memory and returns + * with the error code. + */ + +IGRAPH_EXPORT igraph_error_handler_t igraph_error_handler_ignore; + +/** + * \var igraph_error_handler_printignore + * \brief Print and ignore errors. + * + * Frees temporarily allocated memory, prints an error message to the + * standard error and returns with the error code. + */ + +IGRAPH_EXPORT igraph_error_handler_t igraph_error_handler_printignore; + +IGRAPH_EXPORT igraph_error_handler_t *igraph_set_error_handler(igraph_error_handler_t* new_handler); + + +/* We use IGRAPH_FILE_BASENAME instead of __FILE__ to ensure that full + * paths don't leak into the library code. IGRAPH_FILE_BASENAME is set up + * by the build system when compiling the individual files. However, when + * including igraph_error.h in user code, this macro is not defined so we + * fall back to __FILE__ here + */ +#ifndef IGRAPH_FILE_BASENAME +# define IGRAPH_FILE_BASENAME __FILE__ +#endif + +/** + * \define IGRAPH_ERROR + * \brief Triggers an error. + * + * \a igraph functions usually use this macro when they notice an error. + * It calls + * \ref igraph_error() with the proper parameters and if that returns + * the macro returns the "calling" function as well, with the error + * code. If for some (suspicious) reason you want to call the error + * handler without returning from the current function, call + * \ref igraph_error() directly. + * + * \param reason Textual description of the error. This should be + * something more descriptive than the text associated with the error + * code. E.g. if the error code is \c IGRAPH_EINVAL, + * its associated text (see \ref igraph_strerror()) is "Invalid + * value" and this string should explain which parameter was invalid + * and maybe why. + * \param igraph_errno The \a igraph error code. + */ + +#define IGRAPH_ERROR(reason, igraph_errno) \ + do { \ + igraph_error (reason, IGRAPH_FILE_BASENAME, __LINE__, igraph_errno) ; \ + return igraph_errno ; \ + } while (0) + +#define IGRAPH_ERROR_NO_RETURN(reason, igraph_errno) \ + do { \ + igraph_error (reason, IGRAPH_FILE_BASENAME, __LINE__, igraph_errno) ; \ + } while (0) + +IGRAPH_EXPORT igraph_error_t igraph_error(const char *reason, const char *file, + int line, igraph_error_t igraph_errno); + +/** + * \define IGRAPH_ERRORF + * \brief Triggers an error, with printf-like syntax. + * + * \a igraph functions can use this macro when they notice an error and + * want to pass on extra information to the user about what went wrong. + * It calls \ref igraph_errorf() with the proper parameters and if that + * returns the macro returns the "calling" function as well, with the + * error code. If for some (suspicious) reason you want to call the + * error handler without returning from the current function, call + * \ref igraph_errorf() directly. + * + * \param reason Textual description of the error, a template string + * with the same syntax as the standard printf C library function. + * This should be something more descriptive than the text associated + * with the error code. E.g. if the error code is \c IGRAPH_EINVAL, + * its associated text (see \ref igraph_strerror()) is "Invalid + * value" and this string should explain which parameter was invalid + * and maybe what was expected and what was recieved. + * \param igraph_errno The \a igraph error code. + * \param ... The additional arguments to be substituted into the + * template string. + */ + +#define IGRAPH_ERRORF(reason, igraph_errno, ...) \ + do { \ + igraph_errorf(reason, IGRAPH_FILE_BASENAME, __LINE__, \ + igraph_errno, __VA_ARGS__) ; \ + return igraph_errno; \ + } while (0) + + +IGRAPH_FUNCATTR_PRINTFLIKE(1,5) +IGRAPH_EXPORT igraph_error_t igraph_errorf(const char *reason, const char *file, + int line, igraph_error_t igraph_errno, + ...); + +IGRAPH_EXPORT igraph_error_t igraph_errorvf(const char *reason, const char *file, + int line, igraph_error_t igraph_errno, + va_list ap); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE const char *igraph_strerror(igraph_error_t igraph_errno); + +#define IGRAPH_ERROR_SELECT_2(a,b) ((a) != IGRAPH_SUCCESS ? (a) : ((b) != IGRAPH_SUCCESS ? (b) : IGRAPH_SUCCESS)) +#define IGRAPH_ERROR_SELECT_3(a,b,c) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_2(b,c)) +#define IGRAPH_ERROR_SELECT_4(a,b,c,d) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_3(b,c,d)) +#define IGRAPH_ERROR_SELECT_5(a,b,c,d,e) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_4(b,c,d,e)) + +/* Now comes the more convenient error handling macro arsenal. + * Ideas taken from exception.{h,c} by Laurent Deniau see + * http://cern.ch/Laurent.Deniau/html/oopc/oopc.html#Exceptions for more + * information. We don't use the exception handling code though. */ + +struct igraph_i_protectedPtr { + int level; + void *ptr; + void (*func)(void*); +}; + +typedef void igraph_finally_func_t(void *); + +IGRAPH_EXPORT void IGRAPH_FINALLY_REAL(igraph_finally_func_t *func, void *ptr); + +/** + * \function IGRAPH_FINALLY_CLEAN + * \brief Signals clean deallocation of objects. + * + * Removes the specified number of objects from the stack of + * temporarily allocated objects. It is typically called + * immediately after manually destroying the objects: + * + * + * igraph_vector_t vector; + * igraph_vector_init(&vector, 10); + * IGRAPH_FINALLY(igraph_vector_destroy, &vector); + * // use vector + * igraph_vector_destroy(&vector); + * IGRAPH_FINALLY_CLEAN(1); + * + * + * \param num The number of objects to remove from the bookkeeping + * stack. + */ + +IGRAPH_EXPORT void IGRAPH_FINALLY_CLEAN(int num); + +/** + * \function IGRAPH_FINALLY_FREE + * \brief Deallocates objects registered at the current level. + * + * Calls the destroy function for all objects in the current level + * of the stack of temporarily allocated objects, i.e. up to the + * nearest mark set by IGRAPH_FINALLY_ENTER(). + * This function must only be called from an error handler. + * It is \em not appropriate to use it + * instead of destroying each unneeded object of a function, as it + * destroys the temporary objects of the caller function (and so on) + * as well. + */ + +IGRAPH_EXPORT void IGRAPH_FINALLY_FREE(void); + +IGRAPH_EXPORT void IGRAPH_FINALLY_ENTER(void); +IGRAPH_EXPORT void IGRAPH_FINALLY_EXIT(void); + +/** + * \function IGRAPH_FINALLY_STACK_SIZE + * \brief The number of registered objects. + * + * Returns the number of objects in the stack of temporarily allocated + * objects. This function is handy if you write an own igraph routine and + * you want to make sure it handles errors properly. A properly written + * igraph routine should not leave pointers to temporarily allocated objects + * in the finally stack, because otherwise an \ref IGRAPH_FINALLY_FREE call + * in another igraph function would result in freeing these objects as well + * (and this is really hard to debug, since the error will be not in that + * function that shows erroneous behaviour). Therefore, it is advised to + * write your own test cases and examine \ref IGRAPH_FINALLY_STACK_SIZE + * before and after your test cases - the numbers should be equal. + */ +IGRAPH_EXPORT int IGRAPH_FINALLY_STACK_SIZE(void); + +/** + * \define IGRAPH_FINALLY_STACK_EMPTY + * \brief Returns true if there are no registered objects, false otherwise. + * + * This is just a shorthand notation for checking that + * \ref IGRAPH_FINALLY_STACK_SIZE() is zero. + */ +#define IGRAPH_FINALLY_STACK_EMPTY (IGRAPH_FINALLY_STACK_SIZE() == 0) + +/** + * \define IGRAPH_FINALLY + * \brief Registers an object for deallocation. + * + * This macro places the address of an object, together with the + * address of its destructor on a stack. This stack is used if an + * error happens to deallocate temporarily allocated objects to + * prevent memory leaks. After manual deallocation, objects are removed + * from the stack using \ref IGRAPH_FINALLY_CLEAN(). + * + * + * The typical usage is just after an initialization: + * + * + * IGRAPH_CHECK(igraph_vector_init(&vector, 0)); + * IGRAPH_FINALLY(igraph_vector_destroy, &vector); + * + * + * The most commonly used data structures, such as \ref igraph_vector_t, + * have associated convenience macros that initialize the object and register + * it on this stack in one step. Thus the pattern above can be replaced with a + * single line: + * + * + * IGRAPH_VECTOR_INIT_FINALLY(&vector, 0); + * + * + * \param func The function which is normally called to + * destroy the object. + * \param ptr Pointer to the object itself. + */ + +#define IGRAPH_FINALLY(func, ptr) \ + do { \ + /* the following branch makes the compiler check the compatibility of \ + * func and ptr to detect cases when we are accidentally invoking an \ + * incorrect destructor function with the pointer */ \ + if (0) { func(ptr); } \ + IGRAPH_FINALLY_REAL((igraph_finally_func_t*)(func), (ptr)); \ + } while (0) + +#if defined(__GNUC__) + #define IGRAPH_UNLIKELY(a) __builtin_expect(!!(a), 0) + #define IGRAPH_LIKELY(a) __builtin_expect(!!(a), 1) +#else + #define IGRAPH_UNLIKELY(a) a + #define IGRAPH_LIKELY(a) a +#endif + +#if IGRAPH_VERIFY_FINALLY_STACK == 1 +#define IGRAPH_CHECK(a) \ + do { \ + int enter_stack_size = IGRAPH_FINALLY_STACK_SIZE(); \ + igraph_error_t igraph_i_ret=(a); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != IGRAPH_SUCCESS)) {\ + IGRAPH_ERROR("", igraph_i_ret); \ + } \ + if (IGRAPH_UNLIKELY(enter_stack_size != IGRAPH_FINALLY_STACK_SIZE())) { \ + IGRAPH_FATAL("Non-matching number of IGRAPH_FINALLY and IGRAPH_FINALLY_CLEAN."); \ + } \ + } while (0) +#else +/** + * \define IGRAPH_CHECK + * \brief Checks the return value of a function call. + * + * \param expr An expression, usually a function call. It is guaranteed to + * be evaluated only once. + * + * Executes the expression and checks its value. If this is not + * \c IGRAPH_SUCCESS, it calls \ref IGRAPH_ERROR with + * the value as the error code. Here is an example usage: + * \verbatim IGRAPH_CHECK(vector_push_back(&v, 100)); \endverbatim + * + * There is only one reason to use this macro when writing + * \a igraph functions. If the user installs an error handler which + * returns to the auxiliary calling code (like \ref + * igraph_error_handler_ignore and \ref + * igraph_error_handler_printignore), and the \a igraph function + * signalling the error is called from another \a igraph function + * then we need to make sure that the error is propagated back to + * the auxiliary (i.e. non-igraph) calling function. This is achieved + * by using IGRAPH_CHECK on every \a igraph + * call which can return an error code. + */ +#define IGRAPH_CHECK(expr) \ + do { \ + igraph_error_t igraph_i_ret = (expr); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != IGRAPH_SUCCESS)) {\ + IGRAPH_ERROR("", igraph_i_ret); \ + } \ + } while (0) +#endif + +/** + * \define IGRAPH_CHECK_CALLBACK + * \brief Checks the return value of a callback. + * + * Identical to \ref IGRAPH_CHECK, but treats \c IGRAPH_STOP as a normal + * (non-erroneous) return code. This macro is used in some igraph functions + * that allow the user to hook into a long-running calculation with a callback + * function. When the user-defined callback function returns \c IGRAPH_SUCCESS, + * the calculation will proceed normally. Returning \c IGRAPH_STOP from the + * callback will terminate the calculation without reporting an error. Returning + * any other value from the callback is treated as an error code, and igraph + * will trigger the necessary cleanup functions before exiting the function. + * + * + * Note that \c IGRAPH_CHECK_CALLBACK does not handle \c IGRAPH_STOP by any + * means except returning it in the variable pointed to by \c code. It is the + * responsibility of the caller to handle \c IGRAPH_STOP accordingly. + * + * \param expr An expression, usually a call to a user-defined callback function. + * It is guaranteed to be evaluated only once. + * \param code Pointer to an optional variable of type igraph_error_t; + * the value of this variable will be set to the error code if it is not a null + * pointer. + */ +#define IGRAPH_CHECK_CALLBACK(expr, code) \ + do { \ + igraph_error_t igraph_i_ret = (expr); \ + *(code) = igraph_i_ret; \ + if (IGRAPH_UNLIKELY(igraph_i_ret != IGRAPH_SUCCESS && igraph_i_ret != IGRAPH_STOP)) { \ + IGRAPH_ERROR("", igraph_i_ret); \ + } \ + } while (0) + +/** + * \define IGRAPH_CHECK_OOM + * \brief Checks for out-of-memory conditions after a memory allocation. + * + * This function should be called on pointers after memory allocations. The + * function checks whether the returned pointer is NULL, and if so, sets an + * error message with the \c IGRAPH_ENOMEM error code. + * + * \param ptr The pointer to check. + * \param message The error message to use when the pointer is \c NULL. + */ +#define IGRAPH_CHECK_OOM(ptr, message) \ + do { \ + if (IGRAPH_UNLIKELY(!ptr)) { \ + IGRAPH_ERROR(message, IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + } \ + } while (0) + +/** + * \section about_igraph_warnings Warning messages + * + * + * \a igraph also supports warning messages in addition to error + * messages. Warning messages typically do not terminate the + * program, but they are usually crucial to the user. + * + * + * + * \a igraph warnings are handled similarly to errors. There is a + * separate warning handler function that is called whenever + * an \a igraph function triggers a warning. This handler can be + * set by the \ref igraph_set_warning_handler() function. There are + * two predefined simple warning handlers, + * \ref igraph_warning_handler_ignore() and + * \ref igraph_warning_handler_print(), the latter being the default. + * + * + * + * To trigger a warning, \a igraph functions typically use the + * \ref IGRAPH_WARNING() macro, the \ref igraph_warning() function, + * or if more flexibility is needed, \ref igraph_warningf(). + * + */ + +/** + * \typedef igraph_warning_handler_t + * \brief The type of igraph warning handler functions. + * + * Currently it is defined to have the same type as + * \ref igraph_error_handler_t, although the last (error code) + * argument is not used. + */ + +typedef void igraph_warning_handler_t(const char *reason, + const char *file, int line); + + +IGRAPH_EXPORT igraph_warning_handler_t *igraph_set_warning_handler(igraph_warning_handler_t* new_handler); + +IGRAPH_EXPORT extern igraph_warning_handler_t igraph_warning_handler_ignore; +IGRAPH_EXPORT extern igraph_warning_handler_t igraph_warning_handler_print; + +IGRAPH_EXPORT void igraph_warning(const char *reason, + const char *file, int line); + +/** + * \define IGRAPH_WARNINGF + * \brief Triggers a warning, with printf-like syntax. + * + * \a igraph functions can use this macro when they notice a warning and + * want to pass on extra information to the user about what went wrong. + * It calls \ref igraph_warningf() with the proper parameters and no + * error code. + * \param reason Textual description of the warning, a template string + * with the same syntax as the standard printf C library function. + * \param ... The additional arguments to be substituted into the + * template string. + */ + +#define IGRAPH_WARNINGF(reason, ...) \ + do { \ + igraph_warningf(reason, IGRAPH_FILE_BASENAME, __LINE__, \ + __VA_ARGS__); \ + } while (0) + + +IGRAPH_FUNCATTR_PRINTFLIKE(1,4) +IGRAPH_EXPORT void igraph_warningf(const char *reason, + const char *file, int line, ...); + +/** + * \define IGRAPH_WARNING + * \brief Triggers a warning. + * + * This is the usual way of triggering a warning from an igraph + * function. It calls \ref igraph_warning(). + * \param reason The warning message. + */ + +#define IGRAPH_WARNING(reason) \ + do { \ + igraph_warning(reason, IGRAPH_FILE_BASENAME, __LINE__); \ + } while (0) + + +/** + * \section fatal_error_handlers Fatal errors + * + * + * In some rare situations, \a igraph may encounter an internal error + * that cannot be fully handled. In this case, it will call the + * current fatal error handler. The default fatal error handler + * simply prints the error and aborts the program. + * + * + * + * Fatal error handlers do not return. Typically, they might abort the + * the program immediately, or in the case of the high-level \a igraph + * interfaces, they might return to the top level using a + * longjmp(). The fatal error handler is only called when + * a serious error has occurred, and as a result igraph may be in an + * inconsistent state. The purpose of returning to the top level is to + * give the user a chance to save their work instead of aborting immediately. + * However, the program session should be restarted as soon as possible. + * + * + * + * Most projects that use \a igraph will use the default fatal error + * handler. + * + */ + +/** + * \typedef igraph_fatal_handler_t + * \brief The type of igraph fatal error handler functions. + * + * Functions of this type \em must not return. Typically they + * call abort() or do a longjmp(). + * + * \param reason Textual description of the error. + * \param file The source file in which the error is noticed. + * \param line The number of the line in the source file which triggered the error. + */ + +typedef void igraph_fatal_handler_t(const char *reason, const char *file, int line); + +IGRAPH_EXPORT igraph_fatal_handler_t *igraph_set_fatal_handler(igraph_fatal_handler_t *new_handler); + +/** + * \var igraph_fatal_handler_abort + * \brief Abort program in case of fatal error. + * + * The default fatal error handler, prints an error message and aborts the program. + */ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_NORETURN igraph_fatal_handler_t igraph_fatal_handler_abort; + +IGRAPH_EXPORT IGRAPH_FUNCATTR_NORETURN void igraph_fatal(const char *reason, + const char *file, int line); +IGRAPH_FUNCATTR_PRINTFLIKE(1,4) +IGRAPH_EXPORT IGRAPH_FUNCATTR_NORETURN void igraph_fatalf(const char *reason, + const char *file, int line, ...); + +/** + * \define IGRAPH_FATALF + * \brief Triggers a fatal error, with printf-like syntax. + * + * \a igraph functions can use this macro when a fatal error occurs and + * want to pass on extra information to the user about what went wrong. + * It calls \ref igraph_fatalf() with the proper parameters. + * + * \param reason Textual description of the error, a template string + * with the same syntax as the standard printf C library function. + * \param ... The additional arguments to be substituted into the + * template string. + */ + +#define IGRAPH_FATALF(reason, ...) \ + do { \ + igraph_fatalf(reason, IGRAPH_FILE_BASENAME, __LINE__, \ + __VA_ARGS__); \ + } while (0) + +/** + * \define IGRAPH_FATAL + * \brief Triggers a fatal error. + * + * This is the usual way of triggering a fatal error from an igraph + * function. It calls \ref igraph_fatal(). + * + * + * Use this macro only in situations where the error cannot be handled. + * The normal way to handle errors is \ref IGRAPH_ERROR(). + * + * \param reason The error message. + */ + +#define IGRAPH_FATAL(reason) \ + do { \ + igraph_fatal(reason, IGRAPH_FILE_BASENAME, __LINE__); \ + } while (0) + +/** + * \define IGRAPH_ASSERT + * \brief igraph-specific replacement for assert(). + * + * This macro is like the standard assert(), but instead of + * calling abort(), it calls \ref igraph_fatal(). This allows for returning + * the control to the calling program, e.g. returning to the top level in a high-level + * \a igraph interface. + * + * + * Unlike assert(), IGRAPH_ASSERT() is not disabled + * when the \c NDEBUG macro is defined. + * + * + * This macro is meant for internal use by \a igraph. + * + * + * Since a typical fatal error handler does a longjmp(), avoid using this + * macro in C++ code. With most compilers, destructor will not be called when + * longjmp() leaves the current scope. + * + * \param condition The condition to be checked. + */ + +#define IGRAPH_ASSERT(condition) \ + do { \ + if (IGRAPH_UNLIKELY(!(condition))) { \ + igraph_fatal("Assertion failed: " #condition, IGRAPH_FILE_BASENAME, __LINE__); \ + } \ + } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_eulerian.h b/include/igraph_eulerian.h new file mode 100644 index 0000000..07eafa2 --- /dev/null +++ b/include/igraph_eulerian.h @@ -0,0 +1,34 @@ +/* + igraph library. + Copyright (C) 2020-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_EULERIAN_H +#define IGRAPH_EULERIAN_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_is_eulerian(const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle); +IGRAPH_EXPORT igraph_error_t igraph_eulerian_path(const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res); +IGRAPH_EXPORT igraph_error_t igraph_eulerian_cycle(const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_flow.h b/include/igraph_flow.h new file mode 100644 index 0000000..8b5e59f --- /dev/null +++ b/include/igraph_flow.h @@ -0,0 +1,153 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_FLOW_H +#define IGRAPH_FLOW_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Maximum flows, minimum cuts & such */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_maxflow_stats_t + * \brief Data structure holding statistics from the push-relabel maximum flow solver. + * + * \param nopush The number of push operations performed. + * \param norelabel The number of relabel operarions performed. + * \param nogap The number of times the gap heuristics was used. + * \param nogapnodes The total number of vertices that were + * omitted form further calculations because of the gap + * heuristics. + * \param nobfs The number of times the reverse BFS was run to + * assign good values to the height function. This includes + * an initial run before the whole algorithm, so it is always + * at least one. + */ + +typedef struct { + igraph_int_t nopush, norelabel, nogap, nogapnodes, nobfs; +} igraph_maxflow_stats_t; + +IGRAPH_EXPORT igraph_error_t igraph_maxflow(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *flow, igraph_vector_int_t *cut, + igraph_vector_int_t *partition, igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); +IGRAPH_EXPORT igraph_error_t igraph_maxflow_value(const igraph_t *graph, igraph_real_t *value, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); + +IGRAPH_EXPORT igraph_error_t igraph_st_mincut(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_t *cut, igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); +IGRAPH_EXPORT igraph_error_t igraph_st_mincut_value(const igraph_t *graph, igraph_real_t *res, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); + +IGRAPH_EXPORT igraph_error_t igraph_mincut_value(const igraph_t *graph, igraph_real_t *res, + const igraph_vector_t *capacity); +IGRAPH_EXPORT igraph_error_t igraph_mincut(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_vector_int_t *cut, + const igraph_vector_t *capacity); + +IGRAPH_EXPORT igraph_error_t igraph_st_vertex_connectivity(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors); +IGRAPH_EXPORT igraph_error_t igraph_vertex_connectivity(const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks); + +IGRAPH_EXPORT igraph_error_t igraph_st_edge_connectivity(const igraph_t *graph, igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); +IGRAPH_EXPORT igraph_error_t igraph_edge_connectivity(const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks); + +IGRAPH_EXPORT igraph_error_t igraph_edge_disjoint_paths(const igraph_t *graph, igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); +IGRAPH_EXPORT igraph_error_t igraph_vertex_disjoint_paths(const igraph_t *graph, igraph_int_t *res, + igraph_int_t source, + igraph_int_t target); + +IGRAPH_EXPORT igraph_error_t igraph_adhesion(const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks); +IGRAPH_EXPORT igraph_error_t igraph_cohesion(const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks); + +/* s-t cut listing related stuff */ + +IGRAPH_EXPORT igraph_error_t igraph_even_tarjan_reduction(const igraph_t *graph, igraph_t *graphbar, + igraph_vector_t *capacity); + +IGRAPH_EXPORT igraph_error_t igraph_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow); + +IGRAPH_EXPORT igraph_error_t igraph_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow); + +IGRAPH_EXPORT igraph_error_t igraph_dominator_tree(const igraph_t *graph, + igraph_int_t root, + igraph_vector_int_t *dom, + igraph_t *domtree, + igraph_vector_int_t *leftout, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_all_st_cuts(const igraph_t *graph, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target); + +IGRAPH_EXPORT igraph_error_t igraph_all_st_mincuts(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target, + const igraph_vector_t *capacity); + +IGRAPH_EXPORT igraph_error_t igraph_gomory_hu_tree(const igraph_t *graph, + igraph_t *tree, + igraph_vector_t *flows, + const igraph_vector_t *capacity); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_foreign.h b/include/igraph_foreign.h new file mode 100644 index 0000000..0c9a0a2 --- /dev/null +++ b/include/igraph_foreign.h @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_FOREIGN_H +#define IGRAPH_FOREIGN_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_strvector.h" + +#include + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Read and write foreign formats */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_read_graph_edgelist(igraph_t *graph, FILE *instream, + igraph_int_t n, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_ncol(igraph_t *graph, FILE *instream, + const igraph_strvector_t *predefnames, igraph_bool_t names, + igraph_add_weights_t weights, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_lgl(igraph_t *graph, FILE *instream, + igraph_bool_t names, igraph_add_weights_t weights, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_pajek(igraph_t *graph, FILE *instream); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_graphml(igraph_t *graph, FILE *instream, + igraph_int_t index); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_dimacs_flow(igraph_t *graph, FILE *instream, + igraph_strvector_t *problem, + igraph_vector_int_t *label, + igraph_int_t *source, + igraph_int_t *target, + igraph_vector_t *capacity, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_graphdb(igraph_t *graph, FILE *instream, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_gml(igraph_t *graph, FILE *instream); +IGRAPH_EXPORT igraph_error_t igraph_read_graph_dl(igraph_t *graph, FILE *instream, + igraph_bool_t directed); + +typedef unsigned int igraph_write_gml_sw_t; + +enum { + IGRAPH_WRITE_GML_DEFAULT_SW = 0x0, /* default settings */ + IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW = 0x1 /* only encode " characters, nothing else */ +}; + +IGRAPH_EXPORT igraph_error_t igraph_write_graph_edgelist(const igraph_t *graph, FILE *outstream); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_ncol(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_lgl(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights, + igraph_bool_t isolates); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_graphml(const igraph_t *graph, FILE *outstream, + igraph_bool_t prefixattr); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_pajek(const igraph_t *graph, FILE *outstream); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_dimacs_flow(const igraph_t *graph, FILE *outstream, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_gml(const igraph_t *graph, FILE *outstream, + igraph_write_gml_sw_t options, + const igraph_vector_t *id, const char *creator); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_dot(const igraph_t *graph, FILE *outstream); +IGRAPH_EXPORT igraph_error_t igraph_write_graph_leda(const igraph_t *graph, FILE *outstream, + const char* vertex_attr_name, const char* edge_attr_name); + +/* -------------------------------------------------- */ +/* Convenience functions for temporary locale setting */ +/* -------------------------------------------------- */ + +typedef struct igraph_safelocale_s *igraph_safelocale_t; + +IGRAPH_EXPORT igraph_error_t igraph_enter_safelocale(igraph_safelocale_t *loc); +IGRAPH_EXPORT void igraph_exit_safelocale(igraph_safelocale_t *loc); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_games.h b/include/igraph_games.h new file mode 100644 index 0000000..2ba1bbb --- /dev/null +++ b/include/igraph_games.h @@ -0,0 +1,229 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GAMES_H +#define IGRAPH_GAMES_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_graphicality.h" +#include "igraph_matrix.h" +#include "igraph_matrix_list.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Constructors, games (=stochastic) */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_barabasi_game(igraph_t *graph, igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + igraph_barabasi_algorithm_t algo, + const igraph_t *start_from); + +IGRAPH_EXPORT igraph_error_t igraph_erdos_renyi_game_gnp( + igraph_t *graph, + igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + +IGRAPH_EXPORT igraph_error_t igraph_erdos_renyi_game_gnm( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_iea_game( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops); + +IGRAPH_EXPORT igraph_error_t igraph_degree_sequence_game(igraph_t *graph, const igraph_vector_int_t *out_deg, + const igraph_vector_int_t *in_deg, + igraph_degseq_t method); +IGRAPH_EXPORT igraph_error_t igraph_growing_random_game(igraph_t *graph, igraph_int_t n, + igraph_int_t m, igraph_bool_t directed, igraph_bool_t citation); +IGRAPH_EXPORT igraph_error_t igraph_barabasi_aging_game(igraph_t *graph, + igraph_int_t nodes, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_int_t aging_bin, + igraph_real_t zero_deg_appeal, + igraph_real_t zero_age_appeal, + igraph_real_t deg_coef, + igraph_real_t age_coef, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_recent_degree_game(igraph_t *graph, igraph_int_t n, + igraph_real_t power, + igraph_int_t window, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t zero_appeal, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_recent_degree_aging_game(igraph_t *graph, + igraph_int_t nodes, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_int_t aging_bin, + igraph_int_t window, + igraph_real_t zero_appeal, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_callaway_traits_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t edges_per_step, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec); +IGRAPH_EXPORT igraph_error_t igraph_establishment_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t k, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec); +IGRAPH_EXPORT igraph_error_t igraph_grg_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t radius, igraph_bool_t torus, + igraph_vector_t *x, igraph_vector_t *y); +IGRAPH_EXPORT igraph_error_t igraph_preference_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, + const igraph_vector_t *type_dist, + igraph_bool_t fixed_sizes, + const igraph_matrix_t *pref_matrix, + igraph_vector_int_t *node_type_vec, + igraph_bool_t directed, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_asymmetric_preference_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t out_types, + igraph_int_t in_types, + const igraph_matrix_t *type_dist_matrix, + const igraph_matrix_t *pref_matrix, + igraph_vector_int_t *node_type_out_vec, + igraph_vector_int_t *node_type_in_vec, + igraph_bool_t loops); + +IGRAPH_EXPORT igraph_error_t igraph_rewire_edges(igraph_t *graph, igraph_real_t prob, + igraph_edge_type_sw_t allowed_edge_types); +IGRAPH_EXPORT igraph_error_t igraph_rewire_directed_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_watts_strogatz_game(igraph_t *graph, igraph_int_t dim, + igraph_int_t size, igraph_int_t nei, + igraph_real_t p, + igraph_edge_type_sw_t allowed_edge_types); + +IGRAPH_EXPORT igraph_error_t igraph_lastcit_game(igraph_t *graph, + igraph_int_t nodes, igraph_int_t edges_per_node, + igraph_int_t agebins, + const igraph_vector_t *preference, igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_vector_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_citing_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_matrix_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_forest_fire_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t fw_prob, igraph_real_t bw_factor, + igraph_int_t ambs, igraph_bool_t directed); + + +IGRAPH_EXPORT igraph_error_t igraph_simple_interconnected_islands_game( + igraph_t *graph, + igraph_int_t islands_n, + igraph_int_t islands_size, + igraph_real_t islands_pin, + igraph_int_t n_inter); + +IGRAPH_EXPORT igraph_error_t igraph_static_fitness_game(igraph_t *graph, igraph_int_t no_of_edges, + const igraph_vector_t *fitness_out, const igraph_vector_t *fitness_in, + igraph_edge_type_sw_t allowed_edge_types); + +IGRAPH_EXPORT igraph_error_t igraph_static_power_law_game(igraph_t *graph, + igraph_int_t no_of_nodes, igraph_int_t no_of_edges, + igraph_real_t exponent_out, igraph_real_t exponent_in, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t finite_size_correction); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_chung_lu_game(igraph_t *graph, + const igraph_vector_t *expected_out_deg, + const igraph_vector_t *expected_in_deg, + igraph_bool_t loops, + igraph_chung_lu_t variant); + +IGRAPH_EXPORT igraph_error_t igraph_k_regular_game(igraph_t *graph, + igraph_int_t no_of_nodes, igraph_int_t k, + igraph_bool_t directed, igraph_bool_t multiple); + +IGRAPH_EXPORT igraph_error_t igraph_sbm_game( + igraph_t *graph, + const igraph_matrix_t *pref_matrix, + const igraph_vector_int_t *block_sizes, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types); + +IGRAPH_EXPORT igraph_error_t igraph_hsbm_game(igraph_t *graph, igraph_int_t n, + igraph_int_t m, const igraph_vector_t *rho, + const igraph_matrix_t *C, igraph_real_t p); + +IGRAPH_EXPORT igraph_error_t igraph_hsbm_list_game(igraph_t *graph, igraph_int_t n, + const igraph_vector_int_t *mlist, + const igraph_vector_list_t *rholist, + const igraph_matrix_list_t *Clist, + igraph_real_t p); + +IGRAPH_EXPORT igraph_error_t igraph_correlated_game(igraph_t *new_graph, const igraph_t *old_graph, + igraph_real_t corr, igraph_real_t p, + const igraph_vector_int_t *permutation); + +IGRAPH_EXPORT igraph_error_t igraph_correlated_pair_game(igraph_t *graph1, igraph_t *graph2, + igraph_int_t n, igraph_real_t corr, igraph_real_t p, + igraph_bool_t directed, + const igraph_vector_int_t *permutation); + +IGRAPH_EXPORT igraph_error_t igraph_tree_game(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_random_tree_t method); + +IGRAPH_EXPORT igraph_error_t igraph_dot_product_game(igraph_t *graph, const igraph_matrix_t *vecs, + igraph_bool_t directed); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_graph_list.h b/include/igraph_graph_list.h new file mode 100644 index 0000000..ec0f5ad --- /dev/null +++ b/include/igraph_graph_list.h @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GRAPH_LIST_H +#define IGRAPH_GRAPH_LIST_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* List of graphs */ +/* -------------------------------------------------- */ + +#define GRAPH_LIST +#define BASE_GRAPH +#define EXTRA_TYPE_FIELDS igraph_bool_t directed; +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef EXTRA_TYPE_FIELDS +#undef BASE_GRAPH +#undef GRAPH_LIST + +void igraph_graph_list_set_directed(igraph_graph_list_t* list, igraph_bool_t directed); + +/* -------------------------------------------------- */ +/* Helper macros */ +/* -------------------------------------------------- */ + +#define IGRAPH_GRAPH_LIST_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_graph_list_init(v, size)); \ + IGRAPH_FINALLY(igraph_graph_list_destroy, v); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_graphicality.h b/include/igraph_graphicality.h new file mode 100644 index 0000000..6890b69 --- /dev/null +++ b/include/igraph_graphicality.h @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2020-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GRAPHICALITY_H +#define IGRAPH_GRAPHICALITY_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \typedef igraph_edge_type_sw_t + * \brief What types of non-simple edges to allow? + * + * This type is used with multiple functions to specify what types of non-simple + * edges to allow, create or consider a graph. The constants below are treated + * as "switches" that can be turned on individually and combined using the + * bitwise-or operator. For example, + * IGRAPH_LOOPS_SW + * allows only self-loops but not multi-edges, while + * IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW + * allows both. + * + * \enumval IGRAPH_SIMPLE_SW A shorthand for simple graphs only, which is the default + * assumption. + * \enumval IGRAPH_LOOPS_SW Allow or consider self-loops. + * \enumval IGRAPH_MULTI_SW Allow or consider multi-edges. + */ +typedef unsigned int igraph_edge_type_sw_t; + +/* + * bit 0: self-loops alowed? + * bit 1: more than one edge allowed between distinct vertices? + * bit 2: more than one self-loop allowed (assuming bit 0 is set)? + */ +enum { + IGRAPH_SIMPLE_SW = 0x00, /* 000 */ + IGRAPH_LOOPS_SW = 0x01, /* 001 */ + IGRAPH_MULTI_SW = 0x06 /* 110 */ +}; + +IGRAPH_EXPORT igraph_error_t igraph_is_graphical(const igraph_vector_int_t *out_degrees, + const igraph_vector_int_t *in_degrees, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_is_bigraphical(const igraph_vector_int_t *degrees1, + const igraph_vector_int_t *degrees2, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res); + +IGRAPH_END_C_DECLS + +#endif // IGRAPH_GRAPHICALITY_H diff --git a/include/igraph_graphlets.h b/include/igraph_graphlets.h new file mode 100644 index 0000000..64b3927 --- /dev/null +++ b/include/igraph_graphlets.h @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2013-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GRAPHLETS_H +#define IGRAPH_GRAPHLETS_H + +#include "igraph_decls.h" + +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_graphlets_candidate_basis(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *thresholds); + +IGRAPH_EXPORT igraph_error_t igraph_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + igraph_int_t niter); + +IGRAPH_EXPORT igraph_error_t igraph_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_int_t niter); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_heap.h b/include/igraph_heap.h new file mode 100644 index 0000000..1209327 --- /dev/null +++ b/include/igraph_heap.h @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_HEAP_H +#define IGRAPH_HEAP_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Heap */ +/* -------------------------------------------------- */ + +/** + * Heap data type. + * \ingroup internal + */ + +#define BASE_IGRAPH_REAL +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_INT + +#define BASE_CHAR +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_CHAR + +#define IGRAPH_HEAP_NULL { 0,0,0 } + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_heap_pmt.h b/include/igraph_heap_pmt.h new file mode 100644 index 0000000..3d6f77a --- /dev/null +++ b/include/igraph_heap_pmt.h @@ -0,0 +1,34 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +typedef struct TYPE(igraph_heap) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; +} TYPE(igraph_heap); + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_heap, init)(TYPE(igraph_heap)* h, igraph_int_t capacity); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_heap, init_array)(TYPE(igraph_heap) *t, const BASE *data, igraph_int_t len); +IGRAPH_EXPORT void FUNCTION(igraph_heap, destroy)(TYPE(igraph_heap)* h); +IGRAPH_EXPORT void FUNCTION(igraph_heap, clear)(TYPE(igraph_heap)* h); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_heap, empty)(const TYPE(igraph_heap)* h); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_heap, push)(TYPE(igraph_heap)* h, BASE elem); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_heap, top)(const TYPE(igraph_heap)* h); +IGRAPH_EXPORT BASE FUNCTION(igraph_heap, delete_top)(TYPE(igraph_heap)* h); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_heap, size)(const TYPE(igraph_heap)* h); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_heap, reserve)(TYPE(igraph_heap)* h, igraph_int_t capacity); diff --git a/include/igraph_hrg.h b/include/igraph_hrg.h new file mode 100644 index 0000000..ce9cd7e --- /dev/null +++ b/include/igraph_hrg.h @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_HRG_H +#define IGRAPH_HRG_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_graph_list.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \struct igraph_hrg_t + * \brief Data structure to store a hierarchical random graph. + * + * A hierarchical random graph (HRG) can be given as a binary tree, + * where the internal vertices are labeled with real numbers. + * + * Note that you don't necessarily have to know this + * internal representation for using the HRG functions, just pass the + * HRG objects created by one igraph function, to another igraph + * function. + * + * + * It has the following members: + * + * \member left Vector that contains the left children of the internal + * tree vertices. The first vertex is always the root vertex, so + * the first element of the vector is the left child of the root + * vertex. Internal vertices are denoted with negative numbers, + * starting from -1 and going down, i.e. the root vertex is + * -1. Leaf vertices are denoted by non-negative number, starting + * from zero and up. + * \member right Vector that contains the right children of the + * vertices, with the same encoding as the \c left vector. + * \member prob The connection probabilities attached to the internal + * vertices, the first number belongs to the root vertex + * (i.e. internal vertex -1), the second to internal vertex -2, + * etc. + * \member edges The number of edges in the subtree below the given + * internal vertex. + * \member vertices The number of vertices in the subtree below the + * given internal vertex, including itself. + */ + +typedef struct igraph_hrg_t { + igraph_vector_int_t left; + igraph_vector_int_t right; + igraph_vector_t prob; + igraph_vector_int_t vertices; + igraph_vector_int_t edges; +} igraph_hrg_t; + +IGRAPH_EXPORT igraph_error_t igraph_hrg_init(igraph_hrg_t *hrg, igraph_int_t n); +IGRAPH_EXPORT void igraph_hrg_destroy(igraph_hrg_t *hrg); +IGRAPH_EXPORT igraph_int_t igraph_hrg_size(const igraph_hrg_t *hrg); +IGRAPH_EXPORT igraph_error_t igraph_hrg_resize(igraph_hrg_t *hrg, igraph_int_t newsize); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_fit( + const igraph_t *graph, igraph_hrg_t *hrg, igraph_bool_t start, + igraph_int_t steps +); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_sample( + const igraph_hrg_t *hrg, igraph_t *sample +); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_sample_many( + const igraph_hrg_t *hrg, igraph_graph_list_t *samples, + igraph_int_t num_samples +); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_game( + igraph_t *graph, const igraph_hrg_t *hrg +); + +IGRAPH_EXPORT igraph_error_t igraph_from_hrg_dendrogram( + igraph_t *graph, const igraph_hrg_t *hrg, igraph_vector_t *prob +); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_consensus(const igraph_t *graph, + igraph_vector_int_t *parents, + igraph_vector_t *weights, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_predict(const igraph_t *graph, + igraph_vector_int_t *edges, + igraph_vector_t *prob, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples, + igraph_int_t num_bins); + +IGRAPH_EXPORT igraph_error_t igraph_hrg_create(igraph_hrg_t *hrg, + const igraph_t *graph, + const igraph_vector_t *prob); + +/* Deprecated functions: */ + +IGRAPH_DEPRECATED IGRAPH_EXPORT igraph_error_t igraph_hrg_dendrogram( + igraph_t *graph, const igraph_hrg_t *hrg +); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_HRG_H */ diff --git a/include/igraph_interface.h b/include/igraph_interface.h new file mode 100644 index 0000000..25b19cc --- /dev/null +++ b/include/igraph_interface.h @@ -0,0 +1,155 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_INTERFACE_H +#define IGRAPH_INTERFACE_H + +#include "igraph_decls.h" +#include "igraph_attributes.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Interface */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_empty(igraph_t *graph, igraph_int_t n, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_empty_attrs( + igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + const igraph_attribute_record_list_t* attr +); +IGRAPH_EXPORT void igraph_destroy(igraph_t *graph); +IGRAPH_EXPORT igraph_error_t igraph_copy(igraph_t *to, const igraph_t *from); +IGRAPH_EXPORT igraph_error_t igraph_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t* attr +); +IGRAPH_EXPORT igraph_error_t igraph_add_vertices( + igraph_t *graph, igraph_int_t nv, + const igraph_attribute_record_list_t* attr +); +IGRAPH_EXPORT igraph_error_t igraph_delete_edges(igraph_t *graph, igraph_es_t edges); +IGRAPH_EXPORT igraph_error_t igraph_delete_vertices(igraph_t *graph, igraph_vs_t vertices); +IGRAPH_EXPORT igraph_error_t igraph_delete_vertices_map( + igraph_t *graph, igraph_vs_t vertices, igraph_vector_int_t *map, + igraph_vector_int_t *invmap +); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_vcount(const igraph_t *graph); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_ecount(const igraph_t *graph); +IGRAPH_EXPORT igraph_error_t igraph_neighbors( + const igraph_t *graph, igraph_vector_int_t *neis, igraph_int_t vid, + igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple +); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_is_directed(const igraph_t *graph); +IGRAPH_EXPORT igraph_error_t igraph_degree_1( + const igraph_t *graph, igraph_int_t *deg, igraph_int_t vid, + igraph_neimode_t mode, igraph_loops_t loops +); +IGRAPH_EXPORT igraph_error_t igraph_degree( + const igraph_t *graph, igraph_vector_int_t *res, igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops +); +IGRAPH_EXPORT igraph_error_t igraph_edge(const igraph_t *graph, igraph_int_t eid, + igraph_int_t *from, igraph_int_t *to); +IGRAPH_EXPORT igraph_error_t igraph_edges(const igraph_t *graph, igraph_es_t eids, + igraph_vector_int_t *edges, igraph_bool_t bycol); +IGRAPH_EXPORT igraph_error_t igraph_get_eid(const igraph_t *graph, igraph_int_t *eid, + igraph_int_t from, igraph_int_t to, + igraph_bool_t directed, igraph_bool_t error); +IGRAPH_EXPORT igraph_error_t igraph_get_eids(const igraph_t *graph, igraph_vector_int_t *eids, + const igraph_vector_int_t *pairs, + igraph_bool_t directed, igraph_bool_t error); +IGRAPH_EXPORT igraph_error_t igraph_get_all_eids_between(const igraph_t *graph, igraph_vector_int_t *eids, + igraph_int_t source, igraph_int_t target, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_incident( + const igraph_t *graph, igraph_vector_int_t *eids, igraph_int_t pnode, + igraph_neimode_t mode, igraph_loops_t loops +); +IGRAPH_EXPORT igraph_error_t igraph_is_same_graph(const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *res); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_i_property_cache_get_bool(const igraph_t *graph, igraph_cached_property_t prop); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_i_property_cache_has(const igraph_t *graph, igraph_cached_property_t prop); +IGRAPH_EXPORT void igraph_i_property_cache_set_bool(const igraph_t *graph, igraph_cached_property_t prop, igraph_bool_t value); +IGRAPH_EXPORT void igraph_i_property_cache_set_bool_checked(const igraph_t *graph, igraph_cached_property_t prop, igraph_bool_t value); +IGRAPH_EXPORT void igraph_i_property_cache_invalidate(const igraph_t *graph, igraph_cached_property_t prop); +IGRAPH_EXPORT void igraph_i_property_cache_invalidate_all(const igraph_t *graph); + +#define IGRAPH_RETURN_IF_CACHED_BOOL(graphptr, prop, resptr) \ + do { \ + if (igraph_i_property_cache_has((graphptr), (prop))) { \ + *(resptr) = igraph_i_property_cache_get_bool((graphptr), (prop)); \ + return IGRAPH_SUCCESS; \ + } \ + } while (0) + +/** + * \define IGRAPH_FROM + * \brief The source vertex of an edge. + * + * Faster than \ref igraph_edge(), but no error checking is done: \p eid is assumed to be valid. + * + * \param graph The graph. + * \param eid The edge ID. + * \return The source vertex of the edge. + * \sa \ref igraph_edge() if error checking is desired. + */ +#define IGRAPH_FROM(graph,eid) ((igraph_int_t)(VECTOR((graph)->from)[(igraph_int_t)(eid)])) + +/** + * \define IGRAPH_TO + * \brief The target vertex of an edge. + * + * Faster than \ref igraph_edge(), but no error checking is done: \p eid is assumed to be valid. + * + * \param graph The graph object. + * \param eid The edge ID. + * \return The target vertex of the edge. + * \sa \ref igraph_edge() if error checking is desired. + */ +#define IGRAPH_TO(graph,eid) ((igraph_int_t)(VECTOR((graph)->to) [(igraph_int_t)(eid)])) + +/** + * \define IGRAPH_OTHER + * \brief The other endpoint of an edge. + * + * Typically used with undirected edges when one endpoint of the edge is known, + * and the other endpoint is needed. No error checking is done: + * \p eid and \p vid are assumed to be valid. + * + * \param graph The graph object. + * \param eid The edge ID. + * \param vid The vertex ID of one endpoint of an edge. + * \return The other endpoint of the edge. + * \sa \ref IGRAPH_TO() and \ref IGRAPH_FROM() to get the source and target + * of directed edges. + */ +#define IGRAPH_OTHER(graph,eid,vid) \ + ((igraph_int_t)(IGRAPH_TO(graph,(eid))==(vid) ? IGRAPH_FROM((graph),(eid)) : IGRAPH_TO((graph),(eid)))) + +IGRAPH_DEPRECATED IGRAPH_EXPORT igraph_error_t igraph_delete_vertices_idx( + igraph_t *graph, igraph_vs_t vertices, igraph_vector_int_t *idx, + igraph_vector_int_t *invidx +); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_interrupt.h b/include/igraph_interrupt.h new file mode 100644 index 0000000..f1bf197 --- /dev/null +++ b/include/igraph_interrupt.h @@ -0,0 +1,123 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_INTERRUPT_H +#define IGRAPH_INTERRUPT_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* This file contains the igraph interruption handling. */ + +/** + * \section interrupthandlers Interruption handlers + * + * + * \a igraph is designed to be embeddable into several higher level + * languages (R and Python interfaces are included in the original + * package). Since most higher level languages consider internal \a igraph + * calls as atomic, interruption requests (like Ctrl-C in Python) must + * be handled differently depending on the environment \a igraph embeds + * into. + * + * An \emb interruption handler \eme is a function which is called regularly + * by \a igraph during long calculations. A typical usage of the interruption + * handler is to check whether the user tried to interrupt the calculation + * and return an appropriate value to signal this condition. For example, + * in R, one must call an internal R function regularly to check for + * interruption requests, and the \a igraph interruption handler is the + * perfect place to do that. + * + * If you are using the plain C interface of \a igraph or if you are + * allowed to replace the operating system's interruption handler (like + * SIGINT in Un*x systems), these calls are not of much use to you. + * + * The default interruption handler is empty. + * The \ref igraph_set_interruption_handler() function can be used to set a + * new interruption handler function of type + * \ref igraph_interruption_handler_t, see the + * documentation of this type for details. + * + */ + +/** + * \section writing_interruption_handlers Writing interruption handlers + * + * + * You can write and install interruption handlers simply by defining a + * function of type \ref igraph_interruption_handler_t and calling + * \ref igraph_set_interruption_handler(). This feature is useful for + * interface writers, because usually this is the only way to allow handling + * of Ctrl-C and similar keypresses properly. + * + * + * Your interruption handler will be called regularly during long operations + * (so it is not guaranteed to be called during operations which tend to be + * short, like adding single edges). An interruption handler accepts no + * parameters and must return \c IGRAPH_SUCCESS if the calculation should go on. All + * other return values are considered to be a request for interruption, + * and the caller function would return a special error code, \c IGRAPH_INTERRUPTED. + * It is up to your error handler function to handle this error properly. + * + */ + +/** + * \section writing_functions_interruption_handling Writing \a igraph functions with + * proper interruption handling + * + * + * There is practically a simple rule that should be obeyed when writing + * \a igraph functions. If the calculation is expected to take a long time + * in large graphs (a simple rule of thumb is to assume this for every + * function with a time complexity of at least O(n^2)), call + * \ref IGRAPH_ALLOW_INTERRUPTION in regular intervals like every 10th + * iteration or so. + * + */ + +/** + * \typedef igraph_interruption_handler_t + * + * This is the type of the interruption handler functions. + * + * \return false if the calculation should go on, true if the calculation + * should be interrupted. + */ + +typedef igraph_bool_t igraph_interruption_handler_t(void); + +/** + * \function igraph_allow_interruption + * + * This is the function which is called (usually via the + * \ref IGRAPH_ALLOW_INTERRUPTION macro) if \a igraph is checking for interruption + * requests. + * + * \return false if the calculation should go on, true if the calculation + * should be interrupted. + */ + +IGRAPH_EXPORT igraph_bool_t igraph_allow_interruption(void); + +IGRAPH_EXPORT igraph_interruption_handler_t * igraph_set_interruption_handler (igraph_interruption_handler_t * new_handler); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_isomorphism.h b/include/igraph_isomorphism.h new file mode 100644 index 0000000..32f1eb7 --- /dev/null +++ b/include/igraph_isomorphism.h @@ -0,0 +1,282 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ISOMORPHISM_H +#define IGRAPH_ISOMORPHISM_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_types.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + + +/* -------------------------------------------------- */ +/* Graph isomorphisms */ +/* -------------------------------------------------- */ + +/* Common functions */ +IGRAPH_EXPORT igraph_error_t igraph_simplify_and_colorize( + const igraph_t *graph, igraph_t *res, + igraph_vector_int_t *vertex_color, igraph_vector_int_t *edge_color); +IGRAPH_EXPORT igraph_error_t igraph_invert_permutation( + const igraph_vector_int_t *permutation, igraph_vector_int_t *inverse); + +/* Generic interface */ +IGRAPH_EXPORT igraph_error_t igraph_isomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); +IGRAPH_EXPORT igraph_error_t igraph_subisomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); +IGRAPH_EXPORT igraph_error_t igraph_count_automorphisms( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_real_t *result); +IGRAPH_EXPORT igraph_error_t igraph_automorphism_group( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators +); +IGRAPH_EXPORT igraph_error_t igraph_canonical_permutation( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling +); + +/* LAD */ +IGRAPH_EXPORT igraph_error_t igraph_subisomorphic_lad( + const igraph_t *pattern, const igraph_t *target, const igraph_vector_int_list_t *domains, + igraph_bool_t *iso, igraph_vector_int_t *map, igraph_vector_int_list_t *maps, + igraph_bool_t induced +); + +/* VF2 family*/ +/** + * \typedef igraph_isohandler_t + * Callback type, called when an isomorphism was found + * + * See the details at the documentation of \ref + * igraph_get_isomorphisms_vf2_callback(). + * \param map12 The mapping from the first graph to the second. + * \param map21 The mapping from the second graph to the first, the + * inverse of \p map12 basically. + * \param arg This extra argument was passed to \ref + * igraph_get_isomorphisms_vf2_callback() when it was called. + * \return \c IGRAPH_SUCCESS to continue the search, \c IGRAPH_STOP to + * terminate the search. Any other return value is interpreted as an + * igraph error code, which will then abort the search and return the + * same error code from the caller function. + */ + + +typedef igraph_error_t igraph_isohandler_t(const igraph_vector_int_t *map12, + const igraph_vector_int_t *map21, void *arg); + +/** + * \typedef igraph_isocompat_t + * Callback type, called to check whether two vertices or edges are compatible + * + * VF2 (subgraph) isomorphism functions can be restricted by defining + * relations on the vertices and/or edges of the graphs, and then checking + * whether the vertices (edges) match according to these relations. + * + * This feature is implemented by two callbacks, one for + * vertices, one for edges. Every time igraph tries to match a vertex (edge) + * of the first (sub)graph to a vertex of the second graph, the vertex + * (edge) compatibility callback is called. The callback returns a + * logical value, giving whether the two vertices match. + * + * Both callback functions are of type \c igraph_isocompat_t. + * \param graph1 The first graph. + * \param graph2 The second graph. + * \param g1_num The id of a vertex or edge in the first graph. + * \param g2_num The id of a vertex or edge in the second graph. + * \param arg Extra argument to pass to the callback functions. + * \return Logical scalar, whether vertex (or edge) \p g1_num in \p graph1 + * is compatible with vertex (or edge) \p g2_num in \p graph2. + */ + +typedef igraph_bool_t igraph_isocompat_t(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg); + +IGRAPH_EXPORT igraph_error_t igraph_isomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, + igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_count_isomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_get_isomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_get_isomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg +); +IGRAPH_EXPORT igraph_error_t igraph_subisomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, + igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_count_subisomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_get_subisomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +IGRAPH_EXPORT igraph_error_t igraph_get_subisomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg +); + +/* BLISS family */ +/** + * \struct igraph_bliss_info_t + * \brief Information about a Bliss run. + * + * Some secondary information found by the Bliss algorithm is stored + * here. It is useful if you wany to study the internal working of the + * algorithm. + * + * \member nof_nodes The number of nodes in the search tree. + * \member nof_leaf_nodes The number of leaf nodes in the search tree. + * \member nof_bad_nodes Number of bad nodes. + * \member nof_canupdates Number of canrep updates. + * \member nof_generators Number of generators of the automorphism group. + * \member max_level Maximum level. + * \member group_size The size of the automorphism group of the graph, + * given as a string. It should be deallocated via + * \ref igraph_free() if not needed any more. + * + * See https://users.aalto.fi/~tjunttil/bliss/ + * for details about the algorithm and these parameters. + */ +typedef struct igraph_bliss_info_t { + unsigned long nof_nodes; + unsigned long nof_leaf_nodes; + unsigned long nof_bad_nodes; + unsigned long nof_canupdates; + unsigned long nof_generators; + unsigned long max_level; + char *group_size; +} igraph_bliss_info_t; + +/** + * \typedef igraph_bliss_sh_t + * \brief Splitting heuristics for Bliss. + * + * \c IGRAPH_BLISS_FL provides good performance for many graphs, and is a reasonable + * default choice. \c IGRAPH_BLISS_FSM is recommended for graphs that have some + * combinatorial structure, and is the default of the Bliss library's command + * line tool. + * + * \enumval IGRAPH_BLISS_F First non-singleton cell. + * \enumval IGRAPH_BLISS_FL First largest non-singleton cell. + * \enumval IGRAPH_BLISS_FS First smallest non-singleton cell. + * \enumval IGRAPH_BLISS_FM First maximally non-trivially connected + * non-singleton cell. + * \enumval IGRAPH_BLISS_FLM Largest maximally non-trivially connected + * non-singleton cell. + * \enumval IGRAPH_BLISS_FSM Smallest maximally non-trivially + * connected non-singleton cell. + */ + +typedef enum { IGRAPH_BLISS_F = 0, IGRAPH_BLISS_FL, + IGRAPH_BLISS_FS, IGRAPH_BLISS_FM, + IGRAPH_BLISS_FLM, IGRAPH_BLISS_FSM + } igraph_bliss_sh_t; + +IGRAPH_EXPORT igraph_error_t igraph_canonical_permutation_bliss(const igraph_t *graph, const igraph_vector_int_t *colors, igraph_vector_int_t *labeling, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info); +IGRAPH_EXPORT igraph_error_t igraph_isomorphic_bliss(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *colors1, const igraph_vector_int_t *colors2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_bliss_sh_t sh, + igraph_bliss_info_t *info1, igraph_bliss_info_t *info2); + +IGRAPH_EXPORT igraph_error_t igraph_count_automorphisms_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info); +IGRAPH_EXPORT igraph_error_t igraph_automorphism_group_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info +); + +/* Functions for small graphs (<= 4 vertices for directed graphs, <= 6 for undirected graphs) */ +IGRAPH_EXPORT igraph_error_t igraph_isoclass(const igraph_t *graph, igraph_int_t *isoclass); +IGRAPH_EXPORT igraph_error_t igraph_isoclass_subgraph(const igraph_t *graph, igraph_vs_t vids, + igraph_int_t *isoclass); +IGRAPH_EXPORT igraph_error_t igraph_isoclass_create(igraph_t *graph, igraph_int_t size, + igraph_int_t number, igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_graph_count(igraph_int_t n, igraph_bool_t directed, igraph_int_t *count); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_iterators.h b/include/igraph_iterators.h new file mode 100644 index 0000000..c43e34a --- /dev/null +++ b/include/igraph_iterators.h @@ -0,0 +1,416 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ITERATORS_H +#define IGRAPH_ITERATORS_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Vertex selectors */ +/* -------------------------------------------------- */ + +typedef enum { + IGRAPH_VS_ALL, + IGRAPH_VS_ADJ, + IGRAPH_VS_NONE, + IGRAPH_VS_1, + IGRAPH_VS_VECTORPTR, + IGRAPH_VS_VECTOR, + IGRAPH_VS_RANGE, + IGRAPH_VS_NONADJ, +} igraph_vs_type_t; + +typedef struct igraph_vs_t { + igraph_vs_type_t type; + union { + igraph_int_t vid; /* single vertex */ + const igraph_vector_int_t *vecptr; /* vector of vertices */ + struct { + igraph_int_t vid; + igraph_neimode_t mode; + igraph_loops_t loops; + igraph_bool_t multiple; + } adj; /* adjacent vertices */ + struct { + igraph_int_t start; /* first index (inclusive) */ + igraph_int_t end; /* last index (exclusive) */ + } range; /* range of vertices */ + } data; +} igraph_vs_t; + +IGRAPH_EXPORT igraph_error_t igraph_vs_all(igraph_vs_t *vs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_vs_t igraph_vss_all(void); + +IGRAPH_EXPORT igraph_error_t igraph_vs_adj( + igraph_vs_t *vs, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops, igraph_bool_t multiple +); + +IGRAPH_EXPORT igraph_error_t igraph_vs_nonadj(igraph_vs_t *vs, igraph_int_t vid, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_vs_none(igraph_vs_t *vs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_vs_t igraph_vss_none(void); + +IGRAPH_EXPORT igraph_error_t igraph_vs_1(igraph_vs_t *vs, igraph_int_t vid); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_vs_t igraph_vss_1(igraph_int_t vid); + +IGRAPH_EXPORT igraph_error_t igraph_vs_vector(igraph_vs_t *vs, + const igraph_vector_int_t *v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_vs_t igraph_vss_vector(const igraph_vector_int_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_vs_vector_small(igraph_vs_t *vs, ...); + +IGRAPH_EXPORT igraph_error_t igraph_vs_vector_copy(igraph_vs_t *vs, + const igraph_vector_int_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_vs_range(igraph_vs_t *vs, igraph_int_t start, igraph_int_t end); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_vs_t igraph_vss_range(igraph_int_t start, igraph_int_t end); + +IGRAPH_EXPORT void igraph_vs_destroy(igraph_vs_t *vs); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vs_is_all(const igraph_vs_t *vs); + +IGRAPH_EXPORT igraph_error_t igraph_vs_copy(igraph_vs_t* dest, const igraph_vs_t* src); + +IGRAPH_EXPORT igraph_error_t igraph_vs_as_vector(const igraph_t *graph, igraph_vs_t vs, + igraph_vector_int_t *v); +IGRAPH_EXPORT igraph_error_t igraph_vs_size(const igraph_t *graph, const igraph_vs_t *vs, + igraph_int_t *result); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_vs_type_t igraph_vs_type(const igraph_vs_t *vs); + +/* -------------------------------------------------- */ +/* Vertex iterators */ +/* -------------------------------------------------- */ + +typedef enum { + IGRAPH_VIT_RANGE, + IGRAPH_VIT_VECTOR, + IGRAPH_VIT_VECTORPTR, +} igraph_vit_type_t; + +typedef struct igraph_vit_t { + igraph_vit_type_t type; + igraph_int_t pos; + igraph_int_t start; /* first index */ + igraph_int_t end; /* one past last index */ + const igraph_vector_int_t *vec; +} igraph_vit_t; + +/** + * \section IGRAPH_VIT Stepping over the vertices + * + * After creating an iterator with \ref igraph_vit_create(), it + * points to the first vertex in the vertex determined by the vertex + * selector (if there is any). The \ref IGRAPH_VIT_NEXT() macro steps + * to the next vertex, \ref IGRAPH_VIT_END() checks whether there are + * more vertices to visit, \ref IGRAPH_VIT_SIZE() gives the total size + * of the vertices visited so far and to be visited. \ref + * IGRAPH_VIT_RESET() resets the iterator, it will point to the first + * vertex again. Finally \ref IGRAPH_VIT_GET() gives the current vertex + * pointed to by the iterator (call this only if \ref IGRAPH_VIT_END() + * is false). + * + * + * Here is an example on how to step over the neighbors of vertex 0: + * + * igraph_vs_t vs; + * igraph_vit_t vit; + * ... + * igraph_vs_adj(&vs, 0, IGRAPH_ALL); + * igraph_vit_create(&graph, vs, &vit); + * while (!IGRAPH_VIT_END(vit)) { + * printf(" %" IGRAPH_PRId, IGRAPH_VIT_GET(vit)); + * IGRAPH_VIT_NEXT(vit); + * } + * printf("\n"); + * ... + * igraph_vit_destroy(&vit); + * igraph_vs_destroy(&vs); + * + * + */ + +/** + * \define IGRAPH_VIT_NEXT + * \brief Next vertex. + * + * Steps the iterator to the next vertex. Only call this function if + * \ref IGRAPH_VIT_END() returns false. + * \param vit The vertex iterator to step. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_NEXT(vit) (++((vit).pos)) +/** + * \define IGRAPH_VIT_END + * \brief Are we at the end? + * + * Checks whether there are more vertices to step to. + * \param vit The vertex iterator to check. + * \return Logical value, if true there are no more vertices to step + * to. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_END(vit) ((vit).pos >= (vit).end) +/** + * \define IGRAPH_VIT_SIZE + * \brief Size of a vertex iterator. + * + * Gives the number of vertices in a vertex iterator. + * \param vit The vertex iterator. + * \return The number of vertices. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_SIZE(vit) ((vit).end - (vit).start) +/** + * \define IGRAPH_VIT_RESET + * \brief Reset a vertex iterator. + * + * Resets a vertex iterator. After calling this macro the iterator + * will point to the first vertex. + * \param vit The vertex iterator. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_RESET(vit) ((vit).pos = (vit).start) +/** + * \define IGRAPH_VIT_GET + * \brief Query the current position. + * + * Gives the vertex ID of the current vertex pointed to by the + * iterator. + * \param vit The vertex iterator. + * \return The vertex ID of the current vertex. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_GET(vit) \ + ((igraph_int_t)(((vit).type == IGRAPH_VIT_RANGE) ? (vit).pos : \ + VECTOR(*(vit).vec)[(vit).pos])) + +IGRAPH_EXPORT igraph_error_t igraph_vit_create(const igraph_t *graph, + igraph_vs_t vs, igraph_vit_t *vit); +IGRAPH_EXPORT void igraph_vit_destroy(const igraph_vit_t *vit); + +IGRAPH_EXPORT igraph_error_t igraph_vit_as_vector(const igraph_vit_t *vit, igraph_vector_int_t *v); + +/* -------------------------------------------------- */ +/* Edge Selectors */ +/* -------------------------------------------------- */ + +typedef enum { + IGRAPH_ES_ALL, + IGRAPH_ES_ALLFROM, + IGRAPH_ES_ALLTO, + IGRAPH_ES_INCIDENT, + IGRAPH_ES_NONE, + IGRAPH_ES_1, + IGRAPH_ES_VECTORPTR, + IGRAPH_ES_VECTOR, + IGRAPH_ES_RANGE, + IGRAPH_ES_PAIRS, + IGRAPH_ES_PATH, + IGRAPH_ES_ALL_BETWEEN, +} igraph_es_type_t; + +typedef struct igraph_es_t { + igraph_es_type_t type; + union { + igraph_int_t vid; + igraph_int_t eid; + const igraph_vector_int_t *vecptr; + struct { + igraph_int_t vid; + igraph_neimode_t mode; + igraph_loops_t loops; + } incident; + struct { + igraph_int_t start; /* first index (inclusive) */ + igraph_int_t end; /* last index (exclusive) */ + } range; + struct { + const igraph_vector_int_t *ptr; + igraph_bool_t mode; + } path; + struct { + igraph_int_t from; + igraph_int_t to; + igraph_bool_t directed; + } between; + } data; +} igraph_es_t; + +IGRAPH_EXPORT igraph_error_t igraph_es_all(igraph_es_t *es, + igraph_edgeorder_type_t order); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_es_t igraph_ess_all(igraph_edgeorder_type_t order); + +IGRAPH_EXPORT igraph_error_t igraph_es_incident( + igraph_es_t *es, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops +); + +IGRAPH_EXPORT igraph_error_t igraph_es_none(igraph_es_t *es); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_es_t igraph_ess_none(void); + +IGRAPH_EXPORT igraph_error_t igraph_es_1(igraph_es_t *es, igraph_int_t eid); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_es_t igraph_ess_1(igraph_int_t eid); + +IGRAPH_EXPORT igraph_error_t igraph_es_vector(igraph_es_t *es, + const igraph_vector_int_t *v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_es_t igraph_ess_vector(const igraph_vector_int_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_es_range(igraph_es_t *es, igraph_int_t from, igraph_int_t to); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_es_t igraph_ess_range(igraph_int_t from, igraph_int_t to); + +IGRAPH_EXPORT igraph_error_t igraph_es_vector_copy(igraph_es_t *es, const igraph_vector_int_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_es_pairs(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_es_pairs_small(igraph_es_t *es, igraph_bool_t directed, int first, ...); + +IGRAPH_EXPORT igraph_error_t igraph_es_path(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_es_path_small(igraph_es_t *es, igraph_bool_t directed, int first, ...); + +IGRAPH_EXPORT igraph_error_t igraph_es_all_between( + igraph_es_t *es, igraph_int_t from, igraph_int_t to, + igraph_bool_t directed +); + +IGRAPH_EXPORT void igraph_es_destroy(igraph_es_t *es); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_es_is_all(const igraph_es_t *es); + +IGRAPH_EXPORT igraph_error_t igraph_es_copy(igraph_es_t* dest, const igraph_es_t* src); + +IGRAPH_EXPORT igraph_error_t igraph_es_as_vector(const igraph_t *graph, igraph_es_t es, + igraph_vector_int_t *v); +IGRAPH_EXPORT igraph_error_t igraph_es_size(const igraph_t *graph, const igraph_es_t *es, + igraph_int_t *result); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_es_type_t igraph_es_type(const igraph_es_t *es); + + +/* -------------------------------------------------- */ +/* Edge Iterators */ +/* -------------------------------------------------- */ + +typedef enum { + IGRAPH_EIT_RANGE, + IGRAPH_EIT_VECTOR, + IGRAPH_EIT_VECTORPTR, +} igraph_eit_type_t; + +typedef struct igraph_eit_t { + igraph_eit_type_t type; + igraph_int_t pos; + igraph_int_t start; /* first index */ + igraph_int_t end; /* one past last index */ + const igraph_vector_int_t *vec; +} igraph_eit_t; + +/** + * \section IGRAPH_EIT Stepping over the edges + * + * Just like for vertex iterators, macros are provided for + * stepping over a sequence of edges: \ref IGRAPH_EIT_NEXT() goes to + * the next edge, \ref IGRAPH_EIT_END() checks whether there are more + * edges to visit, \ref IGRAPH_EIT_SIZE() gives the number of edges in + * the edge sequence, \ref IGRAPH_EIT_RESET() resets the iterator to + * the first edge and \ref IGRAPH_EIT_GET() returns the id of the + * current edge. + */ + +/** + * \define IGRAPH_EIT_NEXT + * \brief Next edge. + * + * Steps the iterator to the next edge. Call this function only if + * \ref IGRAPH_EIT_END() returns false. + * \param eit The edge iterator to step. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_NEXT(eit) (++((eit).pos)) +/** + * \define IGRAPH_EIT_END + * \brief Are we at the end? + * + * Checks whether there are more edges to step to. + * \param wit The edge iterator to check. + * \return Logical value, if true there are no more edges + * to step to. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_END(eit) ((eit).pos >= (eit).end) +/** + * \define IGRAPH_EIT_SIZE + * \brief Number of edges in the iterator. + * + * Gives the number of edges in an edge iterator. + * \param eit The edge iterator. + * \return The number of edges. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_SIZE(eit) ((eit).end - (eit).start) +/** + * \define IGRAPH_EIT_RESET + * \brief Reset an edge iterator. + * + * Resets an edge iterator. After calling this macro the iterator will + * point to the first edge. + * \param eit The edge iterator. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_RESET(eit) ((eit).pos = (eit).start) +/** + * \define IGRAPH_EIT_GET + * \brief Query an edge iterator. + * + * Gives the edge ID of the current edge pointed to by an iterator. + * \param eit The edge iterator. + * \return The id of the current edge. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_GET(eit) \ + (igraph_int_t)((((eit).type == IGRAPH_EIT_RANGE) ? (eit).pos : \ + VECTOR(*(eit).vec)[(eit).pos])) + +IGRAPH_EXPORT igraph_error_t igraph_eit_create(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); +IGRAPH_EXPORT void igraph_eit_destroy(const igraph_eit_t *eit); + +IGRAPH_EXPORT igraph_error_t igraph_eit_as_vector(const igraph_eit_t *eit, igraph_vector_int_t *v); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_lapack.h b/include/igraph_lapack.h new file mode 100644 index 0000000..aa712f2 --- /dev/null +++ b/include/igraph_lapack.h @@ -0,0 +1,107 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_LAPACK_H +#define IGRAPH_LAPACK_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_lapack LAPACK interface in igraph + * + * + * LAPACK is written in Fortran90 and provides routines for solving + * systems of simultaneous linear equations, least-squares solutions + * of linear systems of equations, eigenvalue problems, and singular + * value problems. The associated matrix factorizations (LU, Cholesky, + * QR, SVD, Schur, generalized Schur) are also provided, as are + * related computations such as reordering of the Schur factorizations + * and estimating condition numbers. Dense and banded matrices are + * handled, but not general sparse matrices. In all areas, similar + * functionality is provided for real and complex matrices, in both + * single and double precision. + * + * + * + * igraph provides an interface to a very limited set of LAPACK + * functions, using the regular igraph data structures. + * + * + * + * See more about LAPACK at http://www.netlib.org/lapack/ + * + */ + +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgetrf(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + int *info); +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgetrs(igraph_bool_t transpose, const igraph_matrix_t *a, + const igraph_vector_int_t *ipiv, igraph_matrix_t *b); +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgesv(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + igraph_matrix_t *b, int *info); + +typedef enum { IGRAPH_LAPACK_DSYEV_ALL, + IGRAPH_LAPACK_DSYEV_INTERVAL, + IGRAPH_LAPACK_DSYEV_SELECT + } igraph_lapack_dsyev_which_t; + +IGRAPH_EXPORT igraph_error_t igraph_lapack_dsyevr(const igraph_matrix_t *A, + igraph_lapack_dsyev_which_t which, + igraph_real_t vl, igraph_real_t vu, int vestimate, + int il, int iu, igraph_real_t abstol, + igraph_vector_t *values, igraph_matrix_t *vectors, + igraph_vector_int_t *support); + +/* TODO: should we use complex vectors/matrices? */ + +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgeev(const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, int *info); + +typedef enum { IGRAPH_LAPACK_DGEEVX_BALANCE_NONE = 0, + IGRAPH_LAPACK_DGEEVX_BALANCE_PERM, + IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE, + IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH + } +igraph_lapack_dgeevx_balance_t; + +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgeevx(igraph_lapack_dgeevx_balance_t balance, + const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *ilo, int *ihi, igraph_vector_t *scale, + igraph_real_t *abnrm, + igraph_vector_t *rconde, + igraph_vector_t *rcondv, + int *info); + +IGRAPH_EXPORT igraph_error_t igraph_lapack_dgehrd(const igraph_matrix_t *A, + int ilo, int ihi, + igraph_matrix_t *result); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_layout.h b/include/igraph_layout.h new file mode 100644 index 0000000..00812f0 --- /dev/null +++ b/include/igraph_layout.h @@ -0,0 +1,293 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_LAYOUT_H +#define IGRAPH_LAYOUT_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_matrix.h" +#include "igraph_matrix_list.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_layouts + * + * Layout generator functions (or at least most of them) try to place the + * vertices and edges of a graph on a 2D plane or in 3D space in a way + * which visually pleases the human eye. + * + * They take a graph object and a number of parameters as arguments + * and return an \type igraph_matrix_t, in which each row gives the + * coordinates of a vertex. + */ + +/* -------------------------------------------------- */ +/* Layouts */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res); +IGRAPH_EXPORT igraph_error_t igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t order); +IGRAPH_EXPORT igraph_error_t igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t center, const igraph_vector_int_t *order); +IGRAPH_EXPORT igraph_error_t igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, igraph_int_t width); +IGRAPH_EXPORT igraph_error_t igraph_layout_fruchterman_reingold(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + igraph_layout_grid_t grid, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy); + +IGRAPH_EXPORT igraph_error_t igraph_layout_kamada_kawai(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy); + +IGRAPH_EXPORT igraph_error_t igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t maxiter, igraph_real_t maxdelta, + igraph_real_t area, igraph_real_t coolexp, + igraph_real_t repulserad, igraph_real_t cellsize, igraph_int_t root); +IGRAPH_EXPORT igraph_error_t igraph_layout_reingold_tilford(const igraph_t *graph, igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel); +IGRAPH_EXPORT igraph_error_t igraph_layout_reingold_tilford_circular(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel); +IGRAPH_EXPORT igraph_error_t igraph_layout_sugiyama( + const igraph_t *graph, igraph_matrix_t *res, igraph_matrix_list_t *routing, + const igraph_vector_int_t* layers, igraph_real_t hgap, + igraph_real_t vgap, igraph_int_t maxiter, const igraph_vector_t *weights); + +IGRAPH_EXPORT igraph_error_t igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res); +IGRAPH_EXPORT igraph_error_t igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res); +IGRAPH_EXPORT igraph_error_t igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t width, igraph_int_t height); +IGRAPH_EXPORT igraph_error_t igraph_layout_fruchterman_reingold_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy, + const igraph_vector_t *minz, + const igraph_vector_t *maxz); + +IGRAPH_EXPORT igraph_error_t igraph_layout_kamada_kawai_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz); + +IGRAPH_EXPORT igraph_error_t igraph_layout_graphopt(const igraph_t *graph, + igraph_matrix_t *res, igraph_int_t niter, + igraph_real_t node_charge, igraph_real_t node_mass, + igraph_real_t spring_length, + igraph_real_t spring_constant, + igraph_real_t max_sa_movement, + igraph_bool_t use_seed); + +IGRAPH_EXPORT igraph_error_t igraph_layout_mds(const igraph_t *graph, igraph_matrix_t *res, + const igraph_matrix_t *dist, igraph_int_t dim); + +IGRAPH_EXPORT igraph_error_t igraph_layout_bipartite(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, igraph_real_t hgap, + igraph_real_t vgap, igraph_int_t maxiter); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_layout_umap(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_layout_umap_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_layout_umap_compute_weights(const igraph_t *graph, + const igraph_vector_t *distances, + igraph_vector_t *weights); + + +/** + * \struct igraph_layout_drl_options_t + * Parameters for the DrL layout generator + * + * \member edge_cut The edge cutting parameter. + * Edge cutting is done in the late stages of the + * algorithm in order to achieve less dense layouts. Edges are cut + * if there is a lot of stress on them (a large value in the + * objective function sum). The edge cutting parameter is a value + * between 0 and 1 with 0 representing no edge cutting and 1 + * representing maximal edge cutting. The default value is 32/40. + * \member init_iterations Number of iterations, initial phase. + * \member init_temperature Start temperature, initial phase. + * \member init_attraction Attraction, initial phase. + * \member init_damping_mult Damping factor, initial phase. + * \member liquid_iterations Number of iterations in the liquid phase. + * \member liquid_temperature Start temperature in the liquid phase. + * \member liquid_attraction Attraction in the liquid phase. + * \member liquid_damping_mult Multiplicatie damping factor, liquid phase. + * \member expansion_iterations Number of iterations in the expansion phase. + * \member expansion_temperature Start temperature in the expansion phase. + * \member expansion_attraction Attraction, expansion phase. + * \member expansion_damping_mult Damping factor, expansion phase. + * \member cooldown_iterations Number of iterations in the cooldown phase. + * \member cooldown_temperature Start temperature in the cooldown phase. + * \member cooldown_attraction Attraction in the cooldown phase. + * \member cooldown_damping_mult Damping fact int the cooldown phase. + * \member crunch_iterations Number of iterations in the crunch phase. + * \member crunch_temperature Start temperature in the crunch phase. + * \member crunch_attraction Attraction in the crunch phase. + * \member crunch_damping_mult Damping factor in the crunch phase. + * \member simmer_iterations Number of iterations in the simmer phase. + * \member simmer_temperature Start temperature in te simmer phase. + * \member simmer_attraction Attraction in the simmer phase. + * \member simmer_damping_mult Multiplicative damping factor in the simmer phase. + */ + +typedef struct igraph_layout_drl_options_t { + igraph_real_t edge_cut; + igraph_int_t init_iterations; + igraph_real_t init_temperature; + igraph_real_t init_attraction; + igraph_real_t init_damping_mult; + igraph_int_t liquid_iterations; + igraph_real_t liquid_temperature; + igraph_real_t liquid_attraction; + igraph_real_t liquid_damping_mult; + igraph_int_t expansion_iterations; + igraph_real_t expansion_temperature; + igraph_real_t expansion_attraction; + igraph_real_t expansion_damping_mult; + igraph_int_t cooldown_iterations; + igraph_real_t cooldown_temperature; + igraph_real_t cooldown_attraction; + igraph_real_t cooldown_damping_mult; + igraph_int_t crunch_iterations; + igraph_real_t crunch_temperature; + igraph_real_t crunch_attraction; + igraph_real_t crunch_damping_mult; + igraph_int_t simmer_iterations; + igraph_real_t simmer_temperature; + igraph_real_t simmer_attraction; + igraph_real_t simmer_damping_mult; +} igraph_layout_drl_options_t; + +/** + * \typedef igraph_layout_drl_default_t + * Predefined parameter templates for the DrL layout generator + * + * These constants can be used to initialize a set of DrL parameters. + * These can then be modified according to the user's needs. + * \enumval IGRAPH_LAYOUT_DRL_DEFAULT The deafult parameters. + * \enumval IGRAPH_LAYOUT_DRL_COARSEN Slightly modified parameters to + * get a coarser layout. + * \enumval IGRAPH_LAYOUT_DRL_COARSEST An even coarser layout. + * \enumval IGRAPH_LAYOUT_DRL_REFINE Refine an already calculated layout. + * \enumval IGRAPH_LAYOUT_DRL_FINAL Finalize an already refined layout. + */ + +typedef enum { IGRAPH_LAYOUT_DRL_DEFAULT = 0, + IGRAPH_LAYOUT_DRL_COARSEN, + IGRAPH_LAYOUT_DRL_COARSEST, + IGRAPH_LAYOUT_DRL_REFINE, + IGRAPH_LAYOUT_DRL_FINAL + } igraph_layout_drl_default_t; + +IGRAPH_EXPORT igraph_error_t igraph_layout_drl_options_init(igraph_layout_drl_options_t *options, + igraph_layout_drl_default_t templ); +IGRAPH_EXPORT igraph_error_t igraph_layout_drl(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + +IGRAPH_EXPORT igraph_error_t igraph_layout_drl_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + +IGRAPH_EXPORT igraph_error_t igraph_layout_merge_dla(const igraph_vector_ptr_t *graphs, + const igraph_matrix_list_t *coords, + igraph_matrix_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_layout_gem(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t temp_max, igraph_real_t temp_min, + igraph_real_t temp_init); + +IGRAPH_EXPORT igraph_error_t igraph_layout_davidson_harel(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_int_t fineiter, igraph_real_t cool_fact, + igraph_real_t weight_node_dist, igraph_real_t weight_border, + igraph_real_t weight_edge_lengths, + igraph_real_t weight_edge_crossings, + igraph_real_t weight_node_edge_dist); + +/** + * \typedef igraph_root_choice_t + * \brief Root choice heuristic for tree visualizations. + * + * Used with \ref igraph_roots_for_tree_layout(). + */ + +typedef enum { + IGRAPH_ROOT_CHOICE_DEGREE, + IGRAPH_ROOT_CHOICE_ECCENTRICITY +} igraph_root_choice_t; + +IGRAPH_EXPORT igraph_error_t igraph_roots_for_tree_layout( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_vector_int_t *roots, + igraph_root_choice_t use_eccentricity); + +IGRAPH_EXPORT igraph_error_t igraph_layout_align(const igraph_t *graph, igraph_matrix_t *layout); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_lsap.h b/include/igraph_lsap.h new file mode 100644 index 0000000..64a7fdf --- /dev/null +++ b/include/igraph_lsap.h @@ -0,0 +1,35 @@ +/* + igraph library. + Copyright (C) 2014-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_LSAP_H +#define IGRAPH_LSAP_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_vector.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_solve_lsap(const igraph_matrix_t *c, igraph_int_t n, + igraph_vector_int_t *p); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_matching.h b/include/igraph_matching.h new file mode 100644 index 0000000..bd9ad0a --- /dev/null +++ b/include/igraph_matching.h @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2012-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MATCHING_H +#define IGRAPH_MATCHING_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Matchings in graphs */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_is_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_int_t* matching, + igraph_bool_t* result); +IGRAPH_EXPORT igraph_error_t igraph_is_maximal_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_int_t* matching, + igraph_bool_t* result); + +IGRAPH_EXPORT igraph_error_t igraph_maximum_bipartite_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_int_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_int_t* matching, + const igraph_vector_t* weights, igraph_real_t eps); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_matrix.h b/include/igraph_matrix.h new file mode 100644 index 0000000..0644c48 --- /dev/null +++ b/include/igraph_matrix.h @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MATRIX_H +#define IGRAPH_MATRIX_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Matrix, very similar to vector */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#define IGRAPH_MATRIX_NULL { IGRAPH_VECTOR_NULL, 0, 0 } +#define IGRAPH_MATRIX_INIT_FINALLY(m, nr, nc) \ + do { IGRAPH_CHECK(igraph_matrix_init(m, nr, nc)); \ + IGRAPH_FINALLY(igraph_matrix_destroy, m); } while (0) +#define IGRAPH_MATRIX_INT_INIT_FINALLY(m, nr, nc) \ + do { IGRAPH_CHECK(igraph_matrix_int_init(m, nr, nc)); \ + IGRAPH_FINALLY(igraph_matrix_int_destroy, m); } while (0) + +/** + * \ingroup matrix + * \define MATRIX + * \brief Accessing an element of a matrix. + * + * Note that there are no range checks right now. + * This functionality might be redefined as a proper function later. + * \param m The matrix object. + * \param i The index of the row, starting with zero. + * \param j The index of the column, starting with zero. + * + * Time complexity: O(1). + */ +#define MATRIX(m,i,j) ((m).data.stor_begin[(m).nrow*(j)+(i)]) + + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_matrix_all_almost_e(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs, + igraph_real_t eps); + +IGRAPH_EXPORT igraph_error_t igraph_matrix_zapsmall(igraph_matrix_t *m, igraph_real_t tol); +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_zapsmall(igraph_matrix_complex_t *m, igraph_real_t tol); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_matrix_list.h b/include/igraph_matrix_list.h new file mode 100644 index 0000000..e2f1dd1 --- /dev/null +++ b/include/igraph_matrix_list.h @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MATRIX_LIST_H +#define IGRAPH_MATRIX_LIST_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Flexible list of matrices */ +/* -------------------------------------------------- */ + +/* Indicate to igraph_typed_list_pmt.h that we are going to work with _matrices_ + * of the base type, not the base type directly */ +#define MATRIX_LIST + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#undef MATRIX_LIST + +/* -------------------------------------------------- */ +/* Helper macros */ +/* -------------------------------------------------- */ + +#define IGRAPH_MATRIX_LIST_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_matrix_list_init(v, size)); \ + IGRAPH_FINALLY(igraph_matrix_list_destroy, v); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_matrix_pmt.h b/include/igraph_matrix_pmt.h new file mode 100644 index 0000000..6ca4c45 --- /dev/null +++ b/include/igraph_matrix_pmt.h @@ -0,0 +1,251 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +typedef struct TYPE(igraph_matrix) { + TYPE(igraph_vector) data; + igraph_int_t nrow, ncol; +} TYPE(igraph_matrix); + +/*---------------*/ +/* Allocation */ +/*---------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, init)( + TYPE(igraph_matrix) *m, igraph_int_t nrow, igraph_int_t ncol); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, init_array)( + TYPE(igraph_matrix)* m, const BASE* data, igraph_int_t nrow, igraph_int_t ncol, igraph_matrix_storage_t storage); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, init_copy)( + TYPE(igraph_matrix) *to, const TYPE(igraph_matrix) *from); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, destroy)(TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_matrix, capacity)(const TYPE(igraph_matrix) *m); + +/*--------------------*/ +/* Accessing elements */ +/*--------------------*/ + +/* MATRIX */ +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_matrix, get)( + const TYPE(igraph_matrix) *m, igraph_int_t row, igraph_int_t col); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE* FUNCTION(igraph_matrix, get_ptr)( + const TYPE(igraph_matrix) *m, igraph_int_t row, igraph_int_t col); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, set)( + TYPE(igraph_matrix)* m, igraph_int_t row, igraph_int_t col, BASE value); + +/*------------------------------*/ +/* Initializing matrix elements */ +/*------------------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_matrix, null)(TYPE(igraph_matrix) *m); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, fill)(TYPE(igraph_matrix) *m, BASE e); + +/*-----------------------*/ +/* Matrix views */ +/*-----------------------*/ + +IGRAPH_EXPORT TYPE(igraph_matrix) FUNCTION(igraph_matrix, view)( + const BASE *data, + igraph_int_t nrow, igraph_int_t ncol); +IGRAPH_EXPORT TYPE(igraph_matrix) FUNCTION(igraph_matrix, view_from_vector)( + const TYPE(igraph_vector) *v, + igraph_int_t ncol +); + +/*------------------*/ +/* Copying matrices */ +/*------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_matrix, copy_to)(const TYPE(igraph_matrix) *m, BASE *to, igraph_matrix_storage_t storage); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, update)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, rbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, cbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, swap)(TYPE(igraph_matrix) *m1, TYPE(igraph_matrix) *m2); + +/*--------------------------*/ +/* Copying rows and columns */ +/*--------------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, get_row)( + const TYPE(igraph_matrix) *m, TYPE(igraph_vector) *res, igraph_int_t index); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, get_col)( + const TYPE(igraph_matrix) *m, TYPE(igraph_vector) *res, igraph_int_t index); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, set_row)( + TYPE(igraph_matrix) *m, const TYPE(igraph_vector) *v, igraph_int_t index); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, set_col)( + TYPE(igraph_matrix) *m, const TYPE(igraph_vector) *v, igraph_int_t index); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, select_rows)( + const TYPE(igraph_matrix) *m, TYPE(igraph_matrix) *res, const igraph_vector_int_t *rows); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, select_cols)( + const TYPE(igraph_matrix) *m, TYPE(igraph_matrix) *res, const igraph_vector_int_t *cols); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, select_rows_cols)( + const TYPE(igraph_matrix) *m, TYPE(igraph_matrix) *res, + const igraph_vector_int_t *rows, const igraph_vector_int_t *cols); + +/*-----------------------------*/ +/* Exchanging rows and columns */ +/*-----------------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, swap_rows)( + TYPE(igraph_matrix) *m, igraph_int_t i, igraph_int_t j); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, swap_cols)( + TYPE(igraph_matrix) *m, igraph_int_t i, igraph_int_t j); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, swap_rowcol)( + TYPE(igraph_matrix) *m, igraph_int_t i, igraph_int_t j); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, transpose)(TYPE(igraph_matrix) *m); + +/*-----------------------------*/ +/* Matrix operations */ +/*-----------------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, add)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, sub)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, mul_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, div_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, scale)(TYPE(igraph_matrix) *m, BASE by); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, add_constant)(TYPE(igraph_matrix) *m, BASE plus); + +/*-----------------------------*/ +/* Finding minimum and maximum */ +/*-----------------------------*/ + +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t FUNCTION(igraph_matrix, min)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t FUNCTION(igraph_matrix, max)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, which_min)( + const TYPE(igraph_matrix) *m, igraph_int_t *i, igraph_int_t *j); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, which_max)( + const TYPE(igraph_matrix) *m, igraph_int_t *i, igraph_int_t *j); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, minmax)( + const TYPE(igraph_matrix) *m, BASE *min, BASE *max); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, which_minmax)( + const TYPE(igraph_matrix) *m, igraph_int_t *imin, igraph_int_t *jmin, + igraph_int_t *imax, igraph_int_t *jmax); +#endif + +/*------------------------------*/ +/* Comparison */ +/*------------------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, all_e)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, all_l)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, all_g)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, all_le)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, all_ge)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +#endif + +/*-------------------*/ +/* Matrix properties */ +/*-------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, isnull)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, empty)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_matrix, size)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_matrix, nrow)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_matrix, ncol)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, is_symmetric)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_matrix, sum)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_matrix, prod)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, rowsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, colsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, is_equal)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t FUNCTION(igraph_matrix, maxdifference)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +#endif + +/*------------------------*/ +/* Searching for elements */ +/*------------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_matrix, contains)( + const TYPE(igraph_matrix) *m, BASE e); +IGRAPH_EXPORT igraph_bool_t FUNCTION(igraph_matrix, search)( + const TYPE(igraph_matrix) *m, igraph_int_t from, BASE what, + igraph_int_t *pos, igraph_int_t *row, igraph_int_t *col); + +/*------------------------*/ +/* Resizing operations */ +/*------------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, resize)( + TYPE(igraph_matrix) *m, igraph_int_t nrow, igraph_int_t ncol); +IGRAPH_EXPORT void FUNCTION(igraph_matrix, resize_min)( + TYPE(igraph_matrix) *m); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, add_cols)( + TYPE(igraph_matrix) *m, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, add_rows)( + TYPE(igraph_matrix) *m, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, remove_col)( + TYPE(igraph_matrix) *m, igraph_int_t col); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, remove_row)( + TYPE(igraph_matrix) *m, igraph_int_t row); + +/*------------------------*/ +/* Print as text */ +/*------------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, print)(const TYPE(igraph_matrix) *m); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, fprint)(const TYPE(igraph_matrix) *m, FILE *file); + +#ifdef OUT_FORMAT +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, printf)(const TYPE(igraph_matrix) *m, const char *format); +#endif /* OUT_FORMAT */ + +/*-----------------------------------------*/ +/* Operations specific to complex matrices */ +/*-----------------------------------------*/ + +#ifdef BASE_COMPLEX + +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_real(const igraph_matrix_complex_t *v, + igraph_matrix_t *real); +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_imag(const igraph_matrix_complex_t *v, + igraph_matrix_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_realimag(const igraph_matrix_complex_t *v, + igraph_matrix_t *real, + igraph_matrix_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_create(igraph_matrix_complex_t *v, + const igraph_matrix_t *real, + const igraph_matrix_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_matrix_complex_create_polar(igraph_matrix_complex_t *v, + const igraph_matrix_t *r, + const igraph_matrix_t *theta); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_matrix_complex_all_almost_e(igraph_matrix_complex_t *lhs, + igraph_matrix_complex_t *rhs, + igraph_real_t eps); + +#endif /* BASE_COMPLEX */ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_matrix, permdelete_rows)( + TYPE(igraph_matrix) *m, igraph_int_t *index, igraph_int_t nremove); diff --git a/include/igraph_memory.h b/include/igraph_memory.h new file mode 100644 index 0000000..81bdf6c --- /dev/null +++ b/include/igraph_memory.h @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MEMORY_H +#define IGRAPH_MEMORY_H + +#include "igraph_decls.h" + +#include +#include + +IGRAPH_BEGIN_C_DECLS + +/* Helper macro to check if n*sizeof(t) overflows in IGRAPH_CALLOC and IGRAPH_REALLOC */ +#define IGRAPH_I_ALLOC_CHECK_OVERFLOW(n,t,expr) \ + (t*) ((0 <= (n) && ((size_t)(n)) <= SIZE_MAX / sizeof(t)) ? (expr) : NULL) + +#define IGRAPH_CALLOC(n,t) IGRAPH_I_ALLOC_CHECK_OVERFLOW(n, t, calloc(sizeof(t) * ((n) > 0 ? (n) : 1), 1)) +#define IGRAPH_MALLOC(n) malloc( (size_t) ((n) > 0 ? (n) : 1) ) +#define IGRAPH_REALLOC(p,n,t) IGRAPH_I_ALLOC_CHECK_OVERFLOW(n, t, realloc((void*)(p), sizeof(t) * ((n) > 0 ? (n) : 1))) +#define IGRAPH_FREE(p) (free( (void *)(p) ), (p) = NULL) + +IGRAPH_EXPORT void *igraph_calloc(size_t count, size_t size); +IGRAPH_EXPORT void *igraph_malloc(size_t size); +IGRAPH_EXPORT void *igraph_realloc(void* ptr, size_t size); +IGRAPH_EXPORT void igraph_free(void *ptr); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_mixing.h b/include/igraph_mixing.h new file mode 100644 index 0000000..c0028a6 --- /dev/null +++ b/include/igraph_mixing.h @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MIXING_H +#define IGRAPH_MIXING_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_assortativity_nominal( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_int_t *types, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_assortativity( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_t *values, const igraph_vector_t *values_in, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_EXPORT igraph_error_t igraph_assortativity_degree(const igraph_t *graph, + igraph_real_t *res, + igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_joint_degree_matrix( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *jdm, + igraph_int_t dout, igraph_int_t din); + +IGRAPH_EXPORT igraph_error_t igraph_joint_degree_distribution( + const igraph_t *graph, const igraph_vector_t *weights, igraph_matrix_t *p, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors, + igraph_bool_t normalized, + igraph_int_t max_from_degree, igraph_int_t max_to_degree); + +IGRAPH_EXPORT igraph_error_t igraph_joint_type_distribution( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, + igraph_bool_t directed, igraph_bool_t normalized); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_motifs.h b/include/igraph_motifs.h new file mode 100644 index 0000000..c46d354 --- /dev/null +++ b/include/igraph_motifs.h @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MOTIFS_H +#define IGRAPH_MOTIFS_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Graph motifs */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_motifs_handler_t + * \brief Callback type for \c igraph_motifs_randesu_callback. + * + * \ref igraph_motifs_randesu_callback() calls a specified callback + * function whenever a new motif is found during a motif search. This + * callback function must be of type \c igraph_motifs_handler_t. It has + * the following arguments: + * + * \param graph The graph that that algorithm is working on. Of course + * this must not be modified. + * \param vids The IDs of the vertices in the motif that has just been + * found. This vector is owned by the motif search algorithm, so do not + * modify or destroy it; make a copy of it if you need it later. + * \param isoclass The isomorphism class of the motif that has just been + * found. Use \ref igraph_graph_count() to find the maximum possible + * isoclass for graphs of a given size. See \ref igraph_isoclass and + * \ref igraph_isoclass_subgraph for more information. + * \param extra The extra argument that was passed to \ref + * igraph_motifs_randesu_callback(). + * \return \c IGRAPH_SUCCESS to continue the motif search, + * \c IGRAPH_STOP to stop the motif search and return to the caller + * normally. Any other return value is interpreted as an igraph error code, + * which will terminate the search and return the same error code to the + * caller. + * + * \sa \ref igraph_motifs_randesu_callback() + */ + +typedef igraph_error_t igraph_motifs_handler_t(const igraph_t *graph, + const igraph_vector_int_t *vids, + igraph_int_t isoclass, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t size, const igraph_vector_t *cut_prob); + +IGRAPH_EXPORT igraph_error_t igraph_motifs_randesu_callback(const igraph_t *graph, igraph_int_t size, + const igraph_vector_t *cut_prob, + igraph_motifs_handler_t *callback, + void* extra); + +IGRAPH_EXPORT igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_real_t *est, + igraph_int_t size, const igraph_vector_t *cut_prob, + igraph_int_t sample_size, + const igraph_vector_int_t *sample); +IGRAPH_EXPORT igraph_error_t igraph_motifs_randesu_no(const igraph_t *graph, igraph_real_t *no, + igraph_int_t size, const igraph_vector_t *cut_prob); + +IGRAPH_EXPORT igraph_error_t igraph_dyad_census(const igraph_t *graph, igraph_real_t *mut, + igraph_real_t *asym, igraph_real_t *null); +IGRAPH_EXPORT igraph_error_t igraph_triad_census(const igraph_t *igraph, igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_count_adjacent_triangles(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids); + +IGRAPH_EXPORT igraph_error_t igraph_list_triangles(const igraph_t *graph, + igraph_vector_int_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_count_triangles(const igraph_t *graph, igraph_real_t *res); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_neighborhood.h b/include/igraph_neighborhood.h new file mode 100644 index 0000000..da5e089 --- /dev/null +++ b/include/igraph_neighborhood.h @@ -0,0 +1,44 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_NEIGHBORHOOD_H +#define IGRAPH_NEIGHBORHOOD_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_graph_list.h" +#include "igraph_iterators.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_neighborhood_size(const igraph_t *graph, igraph_vector_int_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist); +IGRAPH_EXPORT igraph_error_t igraph_neighborhood(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist); +IGRAPH_EXPORT igraph_error_t igraph_neighborhood_graphs(const igraph_t *graph, igraph_graph_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, + igraph_int_t mindist); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_nongraph.h b/include/igraph_nongraph.h new file mode 100644 index 0000000..2be9cef --- /dev/null +++ b/include/igraph_nongraph.h @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_NONGRAPH_H +#define IGRAPH_NONGRAPH_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \def IGRAPH_SHORTEST_PATH_EPSILON + * + * Relative error threshold used in weighted shortest path calculations + * to decide whether two shortest paths are of equal length. + */ +#define IGRAPH_SHORTEST_PATH_EPSILON 1e-10 + +/* -------------------------------------------------- */ +/* Other, not graph related */ +/* -------------------------------------------------- */ + +/** + * \struct igraph_plfit_result_t + * \brief Result of fitting a power-law distribution to a vector. + * + * This data structure contains the result of \ref igraph_power_law_fit(), + * which tries to fit a power-law distribution to a vector of numbers. The + * structure contains the following members: + * + * \member continuous Whether the fitted power-law distribution was continuous + * or discrete. + * \member alpha The exponent of the fitted power-law distribution. + * \member xmin The minimum value from which the power-law distribution was + * fitted. In other words, only the values larger than \c xmin + * were used from the input vector. + * \member L The log-likelihood of the fitted parameters; in other words, + * the probability of observing the input vector given the + * parameters. + * \member D The test statistic of a Kolmogorov-Smirnov test that compares + * the fitted distribution with the input vector. Smaller scores + * denote better fit. + * \member p The p-value of the Kolmogorov-Smirnov test; \c NaN if it has + * not been calculated yet. Small p-values (less than 0.05) + * indicate that the test rejected the hypothesis that the + * original data could have been drawn from the fitted power-law + * distribution. + * \member data The vector containing the original input data. May not be valid + * any more if the caller already destroyed the vector. + */ +typedef struct igraph_plfit_result_t { + igraph_bool_t continuous; + igraph_real_t alpha; + igraph_real_t xmin; + igraph_real_t L; + igraph_real_t D; + const igraph_vector_t* data; +} igraph_plfit_result_t; + +IGRAPH_EXPORT igraph_error_t igraph_running_mean(const igraph_vector_t *data, igraph_vector_t *res, + igraph_int_t binwidth); +IGRAPH_EXPORT igraph_error_t igraph_random_sample(igraph_vector_int_t *res, igraph_int_t l, igraph_int_t h, + igraph_int_t length); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST igraph_bool_t igraph_almost_equals(double a, double b, double eps); +IGRAPH_EXPORT IGRAPH_FUNCATTR_CONST int igraph_cmp_epsilon(double a, double b, double eps); + +IGRAPH_EXPORT igraph_error_t igraph_power_law_fit( + const igraph_vector_t* vector, igraph_plfit_result_t* result, + igraph_real_t xmin, igraph_bool_t force_continuous +); +IGRAPH_EXPORT igraph_error_t igraph_plfit_result_calculate_p_value( + const igraph_plfit_result_t* model, igraph_real_t* result, igraph_real_t precision +); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_operators.h b/include/igraph_operators.h new file mode 100644 index 0000000..5e6100f --- /dev/null +++ b/include/igraph_operators.h @@ -0,0 +1,118 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_OPERATORS_H +#define IGRAPH_OPERATORS_H + +#include "igraph_decls.h" +#include "igraph_attributes.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_graphicality.h" +#include "igraph_types.h" +#include "igraph_vector_list.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Graph operators */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_rewiring_stats_t + * \brief Data structure holding statistics from graph rewiring. + * + * \param successful_swaps Number of successful rewiring trials (successful swaps). + */ + +typedef struct { + igraph_int_t successful_swaps; + + /* unused members added at the end to allow us to extend the stats in the future + * without breaking ABI compatibility. Do not use these fields in your own code */ + + igraph_int_t unused1_; + igraph_int_t unused2_; + igraph_int_t unused3_; +} igraph_rewiring_stats_t; + +IGRAPH_EXPORT igraph_error_t igraph_add_edge(igraph_t *graph, igraph_int_t from, igraph_int_t to); +IGRAPH_EXPORT igraph_error_t igraph_disjoint_union(igraph_t *res, + const igraph_t *left, const igraph_t *right); +IGRAPH_EXPORT igraph_error_t igraph_disjoint_union_many(igraph_t *res, + const igraph_vector_ptr_t *graphs); +IGRAPH_EXPORT igraph_error_t igraph_union(igraph_t *res, const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2); +IGRAPH_EXPORT igraph_error_t igraph_union_many(igraph_t *res, const igraph_vector_ptr_t *graphs, + igraph_vector_int_list_t *edgemaps); +IGRAPH_EXPORT igraph_error_t igraph_join(igraph_t *res, const igraph_t *left, + const igraph_t *right); +IGRAPH_EXPORT igraph_error_t igraph_intersection(igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, + igraph_vector_int_t *edge_map2); +IGRAPH_EXPORT igraph_error_t igraph_intersection_many(igraph_t *res, + const igraph_vector_ptr_t *graphs, + igraph_vector_int_list_t *edgemaps); +IGRAPH_EXPORT igraph_error_t igraph_difference(igraph_t *res, + const igraph_t *orig, const igraph_t *sub); +IGRAPH_EXPORT igraph_error_t igraph_complementer(igraph_t *res, const igraph_t *graph, + igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_compose(igraph_t *res, const igraph_t *g1, const igraph_t *g2, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2); +IGRAPH_EXPORT igraph_error_t igraph_contract_vertices(igraph_t *graph, + const igraph_vector_int_t *mapping, + const igraph_attribute_combination_t *vertex_comb); +IGRAPH_EXPORT igraph_error_t igraph_permute_vertices(const igraph_t *graph, igraph_t *res, + const igraph_vector_int_t *permutation); +IGRAPH_EXPORT igraph_error_t igraph_connect_neighborhood(igraph_t *graph, igraph_int_t order, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_graph_power(const igraph_t *graph, igraph_t *res, + igraph_int_t order, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_rewire(igraph_t *graph, igraph_int_t n, igraph_edge_type_sw_t allowed_edge_types, + igraph_rewiring_stats_t *stats); +IGRAPH_EXPORT igraph_error_t igraph_simplify(igraph_t *graph, + igraph_bool_t remove_multiple, igraph_bool_t remove_loops, + const igraph_attribute_combination_t *edge_comb); +IGRAPH_EXPORT igraph_error_t igraph_induced_subgraph_map(const igraph_t *graph, igraph_t *res, + igraph_vs_t vids, + igraph_subgraph_implementation_t impl, + igraph_vector_int_t *map, + igraph_vector_int_t *invmap); +IGRAPH_EXPORT igraph_error_t igraph_induced_subgraph(const igraph_t *graph, igraph_t *res, + igraph_vs_t vids, igraph_subgraph_implementation_t impl); +IGRAPH_EXPORT igraph_error_t igraph_induced_subgraph_edges( + const igraph_t *graph, igraph_vs_t vids, igraph_vector_int_t *edges); +IGRAPH_EXPORT igraph_error_t igraph_subgraph_from_edges(const igraph_t *graph, igraph_t *res, + igraph_es_t eids, igraph_bool_t delete_vertices); +IGRAPH_EXPORT igraph_error_t igraph_reverse_edges(igraph_t *graph, igraph_es_t eids); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2, + igraph_product_t type); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_rooted_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2, + igraph_int_t root); +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_mycielskian(const igraph_t *graph, igraph_t *res, igraph_int_t k); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_paths.h b/include/igraph_paths.h new file mode 100644 index 0000000..d15d274 --- /dev/null +++ b/include/igraph_paths.h @@ -0,0 +1,301 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_PATHS_H +#define IGRAPH_PATHS_H + +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_matrix.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \typedef igraph_astar_heuristic_func_t + * \brief Distance estimator for A* algorithm. + * + * \ref igraph_get_shortest_path_astar() uses a heuristic based on a distance + * estimate to the target vertex to guide its search, and determine + * which vertex to try next. The heuristic function is expected to compute + * an estimate of the distance between \p from and \p to. In order for + * \ref igraph_get_shortest_path_astar() to find an exact shortest path, + * the distance must not be overestimated, i.e. the heuristic function + * must be \em admissible. + * + * \param result The result of the heuristic, i.e. the estimated distance. + * A lower value will mean this vertex will be a better candidate for + * exploration. + * \param from The vertex ID of the candidate vertex will be passed here. + * \param to The vertex ID of the endpoint of the path, i.e. the \c to parameter + * given to \ref igraph_get_shortest_path_astar(), will be passed here. + * \param extra The \c extra argument that was passed to + * \ref igraph_get_shortest_path_astar(). + * \return Error code. Must return \c IGRAPH_SUCCESS if there were no errors. + * This can be used to break off the algorithm if something unexpected happens, + * like a failed memory allocation (\c IGRAPH_ENOMEM). + * + * \sa \ref igraph_get_shortest_path_astar() + */ +typedef igraph_error_t igraph_astar_heuristic_func_t( + igraph_real_t *result, + igraph_int_t from, igraph_int_t to, + void *extra); + +typedef enum { + IGRAPH_FLOYD_WARSHALL_AUTOMATIC = 0, + IGRAPH_FLOYD_WARSHALL_ORIGINAL = 1, + IGRAPH_FLOYD_WARSHALL_TREE = 2 +} igraph_floyd_warshall_algorithm_t; + +IGRAPH_EXPORT igraph_error_t igraph_diameter( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_int_t *from, igraph_int_t *to, + igraph_vector_int_t *vertex_path, igraph_vector_int_t *edge_path, + igraph_bool_t directed, igraph_bool_t unconn +); + +IGRAPH_EXPORT igraph_error_t igraph_distances( + const igraph_t *graph, const igraph_vector_t *weights, igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, igraph_neimode_t mode +); +IGRAPH_EXPORT igraph_error_t igraph_distances_bellman_ford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_distances_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_distances_johnson(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_distances_floyd_warshall(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_floyd_warshall_algorithm_t method); + +IGRAPH_EXPORT igraph_error_t igraph_distances_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, igraph_neimode_t mode, igraph_real_t cutoff +); +IGRAPH_EXPORT igraph_error_t igraph_distances_dijkstra_cutoff( + const igraph_t *graph, igraph_matrix_t *res, igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, igraph_neimode_t mode, igraph_real_t cutoff +); + +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_paths( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_int_t from, igraph_vs_t to, igraph_neimode_t mode, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges +); +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_paths_bellman_ford(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_path( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_t *vertices, igraph_vector_int_t *edges, + igraph_int_t from, igraph_int_t to, igraph_neimode_t mode +); +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_path_bellman_ford(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_path_dijkstra(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_get_shortest_path_astar(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_astar_heuristic_func_t *heuristic, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_get_all_shortest_paths( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, igraph_int_t from, igraph_vs_t to, + igraph_neimode_t mode +); +IGRAPH_EXPORT igraph_error_t igraph_get_all_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_average_path_length( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_real_t *unconn_pairs, igraph_bool_t directed, igraph_bool_t unconn +); +IGRAPH_EXPORT igraph_error_t igraph_path_length_hist(const igraph_t *graph, igraph_vector_t *res, + igraph_real_t *unconnected, igraph_bool_t directed); + +IGRAPH_EXPORT igraph_error_t igraph_global_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_bool_t directed +); +IGRAPH_EXPORT igraph_error_t igraph_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + igraph_vs_t vids, igraph_bool_t directed, igraph_neimode_t mode +); +IGRAPH_EXPORT igraph_error_t igraph_average_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_bool_t directed, igraph_neimode_t mode +); + +IGRAPH_EXPORT igraph_error_t igraph_eccentricity( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + igraph_vs_t vids, igraph_neimode_t mode +); + +IGRAPH_EXPORT igraph_error_t igraph_radius( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *radius, + igraph_neimode_t mode +); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_graph_center( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_t *res, igraph_neimode_t mode +); + +IGRAPH_EXPORT igraph_error_t igraph_pseudo_diameter( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *diameter, igraph_int_t vid_start, + igraph_int_t *from, igraph_int_t *to, + igraph_bool_t directed, igraph_bool_t unconn +); + +IGRAPH_EXPORT igraph_error_t +igraph_get_all_simple_paths(const igraph_t *graph, igraph_vector_int_list_t *res, igraph_int_t from, + const igraph_vs_t to, igraph_neimode_t mode, igraph_int_t minlen, + igraph_int_t maxlen, igraph_int_t max_results); + +IGRAPH_EXPORT igraph_error_t igraph_random_walk(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t start, + igraph_neimode_t mode, + igraph_int_t steps, + igraph_random_walk_stuck_t stuck); + +IGRAPH_EXPORT igraph_error_t igraph_get_k_shortest_paths(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *vertex_paths, + igraph_vector_int_list_t *edge_paths, + igraph_int_t k, + igraph_int_t from, + igraph_int_t to, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_spanner(const igraph_t *graph, + igraph_vector_int_t *spanner, + igraph_real_t stretch, + const igraph_vector_t *weights); + +IGRAPH_EXPORT igraph_error_t igraph_get_widest_paths(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); +IGRAPH_EXPORT igraph_error_t igraph_get_widest_path(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_widest_path_widths_floyd_warshall(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_widest_path_widths_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_voronoi(const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_t *distances, + const igraph_vector_int_t *generators, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_voronoi_tiebreaker_t tiebreaker); + +IGRAPH_EXPORT igraph_error_t igraph_expand_path_to_pairs(igraph_vector_int_t *path); + +IGRAPH_EXPORT igraph_error_t igraph_vertex_path_from_edge_path( + const igraph_t *graph, igraph_int_t start, + const igraph_vector_int_t *edge_path, igraph_vector_int_t *vertex_path, + igraph_neimode_t mode); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_pmt.h b/include/igraph_pmt.h new file mode 100644 index 0000000..1032cce --- /dev/null +++ b/include/igraph_pmt.h @@ -0,0 +1,205 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#define CONCAT2x(a,b) a ## _ ## b +#define CONCAT2(a,b) CONCAT2x(a,b) +#define CONCAT3x(a,b,c) a ## _ ## b ## _ ## c +#define CONCAT3(a,b,c) CONCAT3x(a,b,c) +#define CONCAT4x(a,b,c,d) a ## _ ## b ## _ ## c ## _ ## d +#define CONCAT4(a,b,c,d) CONCAT4x(a,b,c,d) +#define CONCAT5x(a,b,c,d,e) a ## _ ## b ## _ ## c ## _ ## d ## _ ## e +#define CONCAT5(a,b,c,d,e) CONCAT5x(a,b,c,d,e) + +#if defined(BASE_IGRAPH_REAL) + #define BASE igraph_real_t + #define BASE_VECTOR igraph_vector_t + #define BASE_MATRIX igraph_matrix_t + #define SHORT + #define OUT_FORMAT "%g" + #define PRINTFUNC(val) igraph_real_printf(val) + #define SNPRINTFUNC(str, size, val) igraph_real_snprintf(str, size, val) + #define FPRINTFUNC_ALIGNED(file, width, val) igraph_real_fprintf_aligned(file, width, val) + #define FPRINTFUNC(file, val) igraph_real_fprintf(file, val) + #define ZERO 0.0 + #define ONE 1.0 + #define MULTIPLICITY 1 + +#elif defined(BASE_CHAR) + #define BASE char + #define BASE_VECTOR igraph_vector_char_t + #define BASE_MATRIX igraph_matrix_char_t + #define SHORT char + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_BOOL) + #define BASE igraph_bool_t + #define BASE_VECTOR igraph_vector_bool_t + #define BASE_MATRIX igraph_matrix_bool_t + #define SHORT bool + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + #define NOTORDERED 1 + #define NOABS 1 + #define EQ(a,b) ((a && b) || (!a && !b)) + +#elif defined(BASE_INT) + #define BASE igraph_int_t + #define BASE_VECTOR igraph_vector_int_t + #define BASE_MATRIX igraph_matrix_int_t + #define SHORT int + #define OUT_FORMAT "%" IGRAPH_PRId + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_FORTRAN_INT) + #define BASE int + #define SHORT fortran_int + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_PTR) + #define BASE void* + #define SHORT ptr + #define ZERO 0 + #define MULTIPLICITY 1 + +#elif defined(BASE_COMPLEX) + #undef complex + #define BASE igraph_complex_t + #define BASE_VECTOR igraph_vector_complex_t + #define BASE_MATRIX igraph_matrix_complex_t + #define SHORT complex + #define PRINTFUNC(val) igraph_complex_printf(val) + #define SNPRINTFUNC(str, size, val) igraph_complex_snprintf(str, size, val) + #define FPRINTFUNC_ALIGNED(file, width, val) igraph_complex_fprintf_aligned(file, width, val) + #define FPRINTFUNC(file, val) igraph_complex_fprintf(file, val) + #define ZERO {{0.0, 0.0}} + #define ONE {{1.0, 0.0}} + #define MULTIPLICITY 2 + #define NOTORDERED 1 + #define NOABS 1 + #define SUM(a,b,c) ((a) = igraph_complex_add((b),(c))) + #define DIFF(a,b,c) ((a) = igraph_complex_sub((b),(c))) + #define PROD(a,b,c) ((a) = igraph_complex_mul((b),(c))) + #define DIV(a,b,c) ((a) = igraph_complex_div((b),(c))) + #define EQ(a,b) IGRAPH_COMPLEX_EQ((a),(b)) + #define SQ(a) IGRAPH_REAL(igraph_complex_mul((a),(a))) + +#elif defined(BASE_GRAPH) + #define BASE igraph_t + +#elif defined(BASE_ATTRIBUTE_RECORD) + #define BASE igraph_attribute_record_t + +#elif defined(BASE_BITSET) + #define BASE igraph_bitset_t + +#else + #error unknown BASE_ directive +#endif + +#if defined(VECTOR_LIST) + #if defined(BASE_IGRAPH_REAL) + #define FUNCTION(c) CONCAT2x(igraph_vector_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_vector_list,c) + #define TYPE igraph_vector_list_t + #elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define FUNCTION(c) CONCAT2x(igraph_vector_bool_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_vector_bool_list,c) + #define TYPE igraph_vector_bool_list_t + #else + #define FUNCTION(c) CONCAT4(igraph_vector,SHORT,list,c) + #define INTERNAL_FUNCTION(c) CONCAT4(igraph_i_vector,SHORT,list,c) + #define TYPE CONCAT3(igraph_vector,SHORT,list_t) + #endif +#elif defined(MATRIX_LIST) + #if defined(BASE_IGRAPH_REAL) + #define FUNCTION(c) CONCAT2x(igraph_matrix_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_matrix_list,c) + #define TYPE igraph_matrix_list_t + #elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define FUNCTION(c) CONCAT2x(igraph_matrix_bool_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_matrix_bool_list,c) + #define TYPE igraph_matrix_bool_list_t + #else + #define FUNCTION(c) CONCAT4(igraph_matrix,SHORT,list,c) + #define INTERNAL_FUNCTION(c) CONCAT4(igraph_i_matrix,SHORT,list,c) + #define TYPE CONCAT3(igraph_matrix,SHORT,list_t) + #endif +#elif defined(GRAPH_LIST) + #define FUNCTION(c) CONCAT2x(igraph_graph_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_graph_list,c) + #define TYPE igraph_graph_list_t +#elif defined(ATTRIBUTE_RECORD_LIST) + #define FUNCTION(c) CONCAT2x(igraph_attribute_record_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_attribute_record_list,c) + #define TYPE igraph_attribute_record_list_t +#elif defined(BITSET_LIST) + #define FUNCTION(c) CONCAT2x(igraph_bitset_list,c) + #define INTERNAL_FUNCTION(c) CONCAT2x(igraph_i_bitset_list,c) + #define TYPE igraph_bitset_list_t +#else + #if defined(BASE_IGRAPH_REAL) + #define FUNCTION(a,c) CONCAT2(a,c) + #define TYPE(a) CONCAT2(a,t) + #elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define FUNCTION(a,c) CONCAT3x(a,bool,c) + #define TYPE(a) CONCAT3x(a,bool,t) + #else + #define FUNCTION(a,c) CONCAT3(a,SHORT,c) + #define TYPE(a) CONCAT3(a,SHORT,t) + #endif +#endif + +#if defined(HEAP_TYPE_MIN) + #define HEAPMORE < + #define HEAPMOREEQ <= + #define HEAPLESS > + #define HEAPLESSEQ >= + #undef FUNCTION + #undef INTERNAL_FUNCTION + #undef TYPE + #if defined(BASE_IGRAPH_REAL) + #define FUNCTION(dir,name) CONCAT3(dir,min,name) + #define TYPE(dir) CONCAT3(dir,min,t) + #else + #define FUNCTION(a,c) CONCAT4(a,min,SHORT,c) + #define TYPE(dir) CONCAT4(dir,min,SHORT,t) + #endif +#endif + +#if defined(HEAP_TYPE_MAX) + #define HEAPMORE > + #define HEAPMOREEQ >= + #define HEAPLESS < + #define HEAPLESSEQ <= +#endif diff --git a/include/igraph_pmt_off.h b/include/igraph_pmt_off.h new file mode 100644 index 0000000..14be2b8 --- /dev/null +++ b/include/igraph_pmt_off.h @@ -0,0 +1,181 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef ATOMIC + #undef ATOMIC +#endif + +#ifdef ATOMIC_IO + #undef ATOMIC_IO +#endif + +#ifdef BASE + #undef BASE +#endif + +#ifdef BASE_EPSILON + #undef BASE_EPSILON +#endif + +#ifdef BASE_MATRIX + #undef BASE_MATRIX +#endif + +#ifdef BASE_VECTOR + #undef BASE_VECTOR +#endif + +#ifdef CONCAT2 + #undef CONCAT2 +#endif + +#ifdef CONCAT2x + #undef CONCAT2x +#endif + +#ifdef CONCAT3 + #undef CONCAT3 +#endif + +#ifdef CONCAT3x + #undef CONCAT3x +#endif + +#ifdef CONCAT4 + #undef CONCAT4 +#endif + +#ifdef CONCAT4x + #undef CONCAT4x +#endif + +#ifdef CONCAT5 + #undef CONCAT5 +#endif + +#ifdef CONCAT5x + #undef CONCAT5x +#endif + +#ifdef FP + #undef FP +#endif + +#ifdef FUNCTION + #undef FUNCTION +#endif + +#ifdef IN_FORMAT + #undef IN_FORMAT +#endif + +#ifdef INTERNAL_FUNCTION + #undef INTERNAL_FUNCTION +#endif + +#ifdef MULTIPLICITY + #undef MULTIPLICITY +#endif + +#ifdef ONE + #undef ONE +#endif + +#ifdef OUT_FORMAT + #undef OUT_FORMAT +#endif + +#ifdef SHORT + #undef SHORT +#endif + +#ifdef TYPE + #undef TYPE +#endif + +#ifdef ZERO + #undef ZERO +#endif + +#ifdef HEAPMORE + #undef HEAPMORE +#endif + +#ifdef HEAPLESS + #undef HEAPLESS +#endif + +#ifdef HEAPMOREEQ + #undef HEAPMOREEQ +#endif + +#ifdef HEAPLESSEQ + #undef HEAPLESSEQ +#endif + +#ifdef SUM + #undef SUM +#endif + +#ifdef SQ + #undef SQ +#endif + +#ifdef PROD + #undef PROD +#endif + +#ifdef NOTORDERED + #undef NOTORDERED +#endif + +#ifdef EQ + #undef EQ +#endif + +#ifdef DIFF + #undef DIFF +#endif + +#ifdef DIV + #undef DIV +#endif + +#ifdef NOABS + #undef NOABS +#endif + +#ifdef PRINTFUNC + #undef PRINTFUNC +#endif + +#ifdef SNPRINTFUNC + #undef SNPRINTFUNC +#endif + +#ifdef FPRINTFUNC_ALIGNED + #undef FPRINTFUNC_ALIGNED +#endif + +#ifdef FPRINTFUNC + #undef FPRINTFUNC +#endif + +#ifdef UNSIGNED + #undef UNSIGNED +#endif diff --git a/include/igraph_progress.h b/include/igraph_progress.h new file mode 100644 index 0000000..48055af --- /dev/null +++ b/include/igraph_progress.h @@ -0,0 +1,197 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_PROGRESS_H +#define IGRAPH_PROGRESS_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_progress_handlers About progress handlers + * + * It is often useful to report the progress of some long + * calculation, to allow the user to follow the computation and + * guess the total running time. A couple of igraph functions + * support this at the time of writing, hopefully more will support it + * in the future. + * + * + * + * To see the progress of a computation, the user has to install a + * progress handler, as there is none installed by default. + * If an igraph function supports progress reporting, then it + * calls the installed progress handler periodically, and passes a + * percentage value to it, the percentage of computation already + * performed. To install a progress handler, you need to call + * \ref igraph_set_progress_handler(). Currently there is a single + * pre-defined progress handler, called \ref + * igraph_progress_handler_stderr(). + * + */ + +/** + * \section writing_progress_handlers Writing progress handlers + * + * + * To write a new progress handler, one needs to create a function of + * type \ref igraph_progress_handler_t. The new progress handler + * can then be installed with the \ref igraph_set_progress_handler() + * function. + * + * + * + * One can assume that the first progress handler call from a + * calculation will be call with zero as the \p percentage argument, + * and the last call from a function will have 100 as the \p + * percentage argument. Note, however, that if an error happens in the + * middle of a computation, then the 100 percent call might be + * omitted. + * + */ + +/** + * \section igraph_functions_with_progress Writing igraph functions with progress reporting + * + * + * If you want to write a function that uses igraph and supports + * progress reporting, you need to include \ref igraph_progress() + * calls in your function, usually via the \ref IGRAPH_PROGRESS() + * macro. + * + * + * + * It is good practice to always include a call to \ref + * igraph_progress() with a zero \p percentage argument, before the + * computation; and another call with 100 \p percentage value + * after the computation is completed. + * + * + * + * It is also good practice \em not to call \ref igraph_progress() too + * often, as this would slow down the computation. It might not be + * worth to support progress reporting in functions with linear or + * log-linear time complexity, as these are fast, even with a large + * amount of data. For functions with quadratic or higher time + * complexity make sure that the time complexity of the progress + * reporting is constant or at least linear. In practice this means + * having at most O(n) progress checks and at most 100 + * \ref igraph_progress() calls. + * + */ + +/** + * \section progress_and_threads Multi-threaded programs + * + * + * In multi-threaded programs, each thread has its own progress + * handler, if thread-local storage is supported and igraph is + * thread-safe. See the \ref IGRAPH_THREAD_SAFE macro for checking + * whether an igraph build is thread-safe. + * + */ + +/* -------------------------------------------------- */ +/* Progress handlers */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_progress_handler_t + * \brief Type of progress handler functions + * + * This is the type of the igraph progress handler functions. + * There is currently one such predefined function, + * \ref igraph_progress_handler_stderr(), but the user can + * write and set up more sophisticated ones. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the name \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return If the return value of the progress handler is not + * \c IGRAPH_SUCCESS, then \ref igraph_progress() returns the + * error code from the progress handler intact. The \ref IGRAPH_PROGRESS() + * macro also frees all allocated memory. + */ + +typedef igraph_error_t igraph_progress_handler_t(const char *message, igraph_real_t percent, + void *data); + +IGRAPH_EXPORT extern igraph_progress_handler_t igraph_progress_handler_stderr; + +IGRAPH_EXPORT igraph_progress_handler_t * igraph_set_progress_handler(igraph_progress_handler_t new_handler); + +IGRAPH_EXPORT igraph_error_t igraph_progress(const char *message, igraph_real_t percent, void *data); + +IGRAPH_EXPORT igraph_error_t igraph_progressf(const char *message, igraph_real_t percent, void *data, + ...); + +/** + * \define IGRAPH_PROGRESS + * \brief Report the progress of a calculation from an igraph function (macro variant). + * + * The standard way to report progress from an igraph function + * \param message A string, a textual message that references the + * calculation under progress. + * \param percent Numeric scalar, the percentage that is complete. + * \param data User-defined data, this can be used in user-defined + * progress handler functions, from user-written igraph functions. + * \return If the return value of the progress handler is not + * \c IGRAPH_SUCCESS, then \ref igraph_progress() returns the + * error code from the progress handler intact. The \ref IGRAPH_PROGRESS() + * macro also frees all allocated memory. + */ + +#define IGRAPH_PROGRESS(message, percent, data) \ + do { \ + IGRAPH_CHECK(igraph_progress((message), (percent), (data))); \ + } while (0) + +/** + * \define IGRAPH_PROGRESSF + * \brief Report the progress of a calculation from an igraph function, printf-like (macro variant). + * + * This is the more flexible version of \ref IGRAPH_PROGRESS(), + * having a printf-like syntax. As this macro takes variable + * number of arguments, they must be all supplied as a single + * argument, enclosed in parentheses. \ref igraph_progressf() is then + * called with the given arguments. + * + * \param args The arguments to pass to \ref igraph_progressf(). + * \return If the progress handler returns with a value other than + * \c IGRAPH_SUCCESS, then the function that called this + * macro returns as well, with the same error code, after + * cleaning up all allocated memory as needed. + */ + +#define IGRAPH_PROGRESSF(args) \ + do { \ + IGRAPH_CHECK(igraph_progressf args); \ + } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_psumtree.h b/include/igraph_psumtree.h new file mode 100644 index 0000000..5e3d5e7 --- /dev/null +++ b/include/igraph_psumtree.h @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_PSUMTREE_H +#define IGRAPH_PSUMTREE_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +typedef struct { + igraph_vector_t v; + igraph_int_t size; + igraph_int_t offset; +} igraph_psumtree_t; + +IGRAPH_EXPORT igraph_error_t igraph_psumtree_init(igraph_psumtree_t *t, igraph_int_t size); +IGRAPH_EXPORT void igraph_psumtree_reset(igraph_psumtree_t *t); +IGRAPH_EXPORT void igraph_psumtree_destroy(igraph_psumtree_t *t); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_psumtree_get(const igraph_psumtree_t *t, igraph_int_t idx); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_psumtree_size(const igraph_psumtree_t *t); +IGRAPH_EXPORT igraph_error_t igraph_psumtree_search(const igraph_psumtree_t *t, igraph_int_t *idx, + igraph_real_t elem); +IGRAPH_EXPORT igraph_error_t igraph_psumtree_update(igraph_psumtree_t *t, igraph_int_t idx, + igraph_real_t new_value); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_psumtree_sum(const igraph_psumtree_t *t); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_qsort.h b/include/igraph_qsort.h new file mode 100644 index 0000000..9992727 --- /dev/null +++ b/include/igraph_qsort.h @@ -0,0 +1,35 @@ +/* + igraph library. + Copyright (C) 2011-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_QSORT_H +#define IGRAPH_QSORT_H + +#include "igraph_decls.h" + +#include + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT void igraph_qsort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)); +IGRAPH_EXPORT void igraph_qsort_r(void *base, size_t nel, size_t width, void *thunk, + int (*compar)(void *, const void *, const void *)); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_random.h b/include/igraph_random.h new file mode 100644 index 0000000..c3efc38 --- /dev/null +++ b/include/igraph_random.h @@ -0,0 +1,165 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_RANDOM_H +#define IGRAPH_RANDOM_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +#include +#include + +IGRAPH_BEGIN_C_DECLS + +/* The new RNG interface is (somewhat) modelled on the GSL */ + +/* When implementing your own RNG in igraph, the following methods must be + * supplied in the corresponding igraph_rng_type_t structure: + * + * - init() + * - destroy() + * - seed() + * - get() + * + * Optionally, you can provide specialized routines for several distributions + * in the following functions: + * + * - get_int() + * - get_real() + * - get_norm() + * - get_geom() + * - get_binom() + * - get_exp() + * - get_gamma() + * - get_pois() + * + * The best is probably to define get() leave the others as NULL; igraph will use + * default implementations for these. + * + * Note that if all that you would do in a get_real() implementation is to + * generate random bits with get() and divide by the maximum, don't do that; + * The default implementation takes care of calling get() a sufficient number of + * times to utilize most of the precision of the igraph_real_t type, and generate + * accurate variates. Inaccuracies in the output of get_real() can get magnified + * when using the default generators for non-uniform distributions. + * When implementing get_real(), the sampling range must be half-open, i.e. [0, 1). + * If unsure, leave get_real() unimplemented and igraph will provide an implementation + * in terms of get(). + * + * When implementing get_int(), you do not need to check whether lo < hi; + * the caller is responsible for ensuring that this is the case. You can always + * assume that hi > lo. Note that both endpoints are _inclusive_, and you must + * make sure that your generation scheme works for both 32-bit and 64-bit + * versions of igraph_int_t as igraph can be compiled for both cases. If + * you are unsure, leave get_int() unimplemented and igraph will provide its + * own implementation based on get(). + */ +typedef struct igraph_rng_type_t { + const char *name; + uint8_t bits; + + /* Initialization and destruction */ + igraph_error_t (*init)(void **state); + void (*destroy)(void *state); + + /* Seeding */ + igraph_error_t (*seed)(void *state, igraph_uint_t seed); + + /* Fundamental generator: return as many random bits as the RNG supports in + * a single round */ + igraph_uint_t (*get)(void *state); + + /* Optional generators; defaults are provided by igraph that rely solely + * on get() */ + igraph_int_t (*get_int)(void *state, igraph_int_t l, igraph_int_t h); + igraph_real_t (*get_real)(void *state); + igraph_real_t (*get_norm)(void *state); + igraph_real_t (*get_geom)(void *state, igraph_real_t p); + igraph_real_t (*get_binom)(void *state, igraph_int_t n, igraph_real_t p); + igraph_real_t (*get_exp)(void *state, igraph_real_t rate); + igraph_real_t (*get_gamma)(void *state, igraph_real_t shape, + igraph_real_t scale); + igraph_real_t (*get_pois)(void *state, igraph_real_t mu); +} igraph_rng_type_t; + +typedef struct igraph_rng_t { + const igraph_rng_type_t *type; + void *state; + igraph_bool_t is_seeded; +} igraph_rng_t; + +/* --------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_rng_init(igraph_rng_t *rng, const igraph_rng_type_t *type); +IGRAPH_EXPORT void igraph_rng_destroy(igraph_rng_t *rng); + +IGRAPH_EXPORT igraph_error_t igraph_rng_seed(igraph_rng_t *rng, igraph_uint_t seed); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_rng_bits(const igraph_rng_t* rng); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_uint_t igraph_rng_max(const igraph_rng_t *rng); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE const char *igraph_rng_name(const igraph_rng_t *rng); + +IGRAPH_EXPORT igraph_bool_t igraph_rng_get_bool(igraph_rng_t *rng) ; +IGRAPH_EXPORT igraph_int_t igraph_rng_get_integer( + igraph_rng_t *rng, igraph_int_t l, igraph_int_t h +); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_normal( + igraph_rng_t *rng, igraph_real_t m, igraph_real_t s +); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_unif( + igraph_rng_t *rng, igraph_real_t l, igraph_real_t h +); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_unif01(igraph_rng_t *rng); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_geom(igraph_rng_t *rng, igraph_real_t p); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_binom( + igraph_rng_t *rng, igraph_int_t n, igraph_real_t p +); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_exp(igraph_rng_t *rng, igraph_real_t rate); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_gamma( + igraph_rng_t *rng, igraph_real_t shape, igraph_real_t scale +); +IGRAPH_EXPORT igraph_real_t igraph_rng_get_pois(igraph_rng_t *rng, igraph_real_t rate); + +/* --------------------------------- */ + +IGRAPH_EXPORT extern const igraph_rng_type_t igraph_rngtype_glibc2; +IGRAPH_EXPORT extern const igraph_rng_type_t igraph_rngtype_mt19937; +IGRAPH_EXPORT extern const igraph_rng_type_t igraph_rngtype_pcg32; +IGRAPH_EXPORT extern const igraph_rng_type_t igraph_rngtype_pcg64; + +IGRAPH_EXPORT igraph_rng_t *igraph_rng_default(void); +IGRAPH_EXPORT igraph_rng_t *igraph_rng_set_default(igraph_rng_t *rng); + +/* --------------------------------- */ + +#define RNG_BOOL() (igraph_rng_get_bool(igraph_rng_default())) +#define RNG_INTEGER(l,h) (igraph_rng_get_integer(igraph_rng_default(),(l),(h))) +#define RNG_NORMAL(m,s) (igraph_rng_get_normal(igraph_rng_default(),(m),(s))) +#define RNG_UNIF(l,h) (igraph_rng_get_unif(igraph_rng_default(),(l),(h))) +#define RNG_UNIF01() (igraph_rng_get_unif01(igraph_rng_default())) +#define RNG_GEOM(p) (igraph_rng_get_geom(igraph_rng_default(),(p))) +#define RNG_BINOM(n,p) (igraph_rng_get_binom(igraph_rng_default(),(n),(p))) +#define RNG_EXP(rate) (igraph_rng_get_exp(igraph_rng_default(),(rate))) +#define RNG_POIS(rate) (igraph_rng_get_pois(igraph_rng_default(),(rate))) +#define RNG_GAMMA(shape, scale) \ + (igraph_rng_get_gamma(igraph_rng_default(), (shape), (scale))) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_reachability.h b/include/igraph_reachability.h new file mode 100644 index 0000000..b51f998 --- /dev/null +++ b/include/igraph_reachability.h @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2024-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_REACHABILITY_H +#define IGRAPH_REACHABILITY_H + +#include "igraph_bitset_list.h" +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_reachability( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t *no_of_components, + igraph_bitset_list_t *reach, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_count_reachable( + const igraph_t *graph, + igraph_vector_int_t *counts, + igraph_neimode_t mode); + + +IGRAPH_EXPORT igraph_error_t igraph_transitive_closure(const igraph_t *graph, + igraph_t* closure); + +IGRAPH_END_C_DECLS + +#endif // IGRAPH_REACHABILITY_H diff --git a/include/igraph_sampling.h b/include/igraph_sampling.h new file mode 100644 index 0000000..322e7ce --- /dev/null +++ b/include/igraph_sampling.h @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SAMPLING_H +#define IGRAPH_SAMPLING_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_random.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_rng_sample_sphere_surface( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res +); + +IGRAPH_EXPORT igraph_error_t igraph_rng_sample_sphere_volume( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res +); + +IGRAPH_EXPORT igraph_error_t igraph_rng_sample_dirichlet( + igraph_rng_t* rng, igraph_int_t n, const igraph_vector_t *alpha, igraph_matrix_t *res +); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_scan.h b/include/igraph_scan.h new file mode 100644 index 0000000..c3eb73c --- /dev/null +++ b/include/igraph_scan.h @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2013-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SCAN_H +#define IGRAPH_SCAN_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_0(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_0_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_1_ecount(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_1_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_k_ecount(const igraph_t *graph, igraph_int_t k, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_k_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_int_t k, igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_local_scan_neighborhood_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *neighborhoods); +IGRAPH_EXPORT igraph_error_t igraph_local_scan_subset_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *neighborhoods); +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_separators.h b/include/igraph_separators.h new file mode 100644 index 0000000..1108a01 --- /dev/null +++ b/include/igraph_separators.h @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SEPARATORS_H +#define IGRAPH_SEPARATORS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_types.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_is_separator(const igraph_t *graph, + igraph_vs_t candidate, + igraph_bool_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_all_minimal_st_separators(const igraph_t *graph, + igraph_vector_int_list_t *separators); + +IGRAPH_EXPORT igraph_error_t igraph_is_minimal_separator(const igraph_t *graph, + igraph_vs_t candidate, + igraph_bool_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_minimum_size_separators(const igraph_t *graph, + igraph_vector_int_list_t *separators); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_setup.h b/include/igraph_setup.h new file mode 100644 index 0000000..90532d1 --- /dev/null +++ b/include/igraph_setup.h @@ -0,0 +1,31 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SETUP_H +#define IGRAPH_SETUP_H + +#include "igraph_decls.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_setup(void); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_sparsemat.h b/include/igraph_sparsemat.h new file mode 100644 index 0000000..1ea5778 --- /dev/null +++ b/include/igraph_sparsemat.h @@ -0,0 +1,287 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SPARSEMAT_H +#define IGRAPH_SPARSEMAT_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_arpack.h" + +#include + +IGRAPH_BEGIN_C_DECLS + +/* + * These types are private to igraph, and customized to use igraph_int_t. + * Do not attempt to access them using a separate copy of the CXSparse library. + * Use the public igraph_sparsemat_... types instead. + */ +struct cs_igraph_sparse; +struct cs_igraph_symbolic; +struct cs_igraph_numeric; + +typedef struct { + struct cs_igraph_sparse *cs; +} igraph_sparsemat_t; + +typedef struct { + struct cs_igraph_symbolic *symbolic; +} igraph_sparsemat_symbolic_t; + +typedef struct { + struct cs_igraph_numeric *numeric; +} igraph_sparsemat_numeric_t; + +typedef enum { IGRAPH_SPARSEMAT_TRIPLET, + IGRAPH_SPARSEMAT_CC + } igraph_sparsemat_type_t; + +typedef struct { + const igraph_sparsemat_t *mat; + igraph_int_t pos; + igraph_int_t col; +} igraph_sparsemat_iterator_t; + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_init( + igraph_sparsemat_t *A, igraph_int_t rows, igraph_int_t cols, + igraph_int_t nzmax +); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_init_copy( + igraph_sparsemat_t *to, const igraph_sparsemat_t *from); +IGRAPH_EXPORT void igraph_sparsemat_destroy(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_realloc(igraph_sparsemat_t *A, igraph_int_t nzmax); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_init_eye(igraph_sparsemat_t *A, + igraph_int_t n, igraph_int_t nzmax, + igraph_real_t value, igraph_bool_t compress); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_init_diag(igraph_sparsemat_t *A, + igraph_int_t nzmax, const igraph_vector_t *values, + igraph_bool_t compress); + +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_nrow(const igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_ncol(const igraph_sparsemat_t *B); +IGRAPH_EXPORT igraph_sparsemat_type_t igraph_sparsemat_type(const igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_bool_t igraph_sparsemat_is_triplet(const igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_bool_t igraph_sparsemat_is_cc(const igraph_sparsemat_t *A); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_permute(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res); + +IGRAPH_EXPORT igraph_real_t igraph_sparsemat_get( + const igraph_sparsemat_t *A, igraph_int_t row, igraph_int_t col +); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_index(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_entry(igraph_sparsemat_t *A, + igraph_int_t row, igraph_int_t col, igraph_real_t elem); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_compress(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_transpose( + const igraph_sparsemat_t *A, igraph_sparsemat_t *res +); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_is_symmetric(const igraph_sparsemat_t *A, igraph_bool_t *result); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_dupl(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_fkeep(igraph_sparsemat_t *A, + igraph_int_t (*fkeep)(igraph_int_t, igraph_int_t, igraph_real_t, void*), + void *other); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_dropzeros(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_droptol(igraph_sparsemat_t *A, igraph_real_t tol); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_multiply(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_sparsemat_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_add(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_real_t alpha, + igraph_real_t beta, + igraph_sparsemat_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_gaxpy(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_lsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_ltsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_usolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_utsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_cholsol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_lusol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order, + igraph_real_t tol); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_print(const igraph_sparsemat_t *A, + FILE *outstream); + +IGRAPH_EXPORT igraph_error_t igraph_matrix_as_sparsemat(igraph_sparsemat_t *res, + const igraph_matrix_t *mat, + igraph_real_t tol); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_as_matrix(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat); + +typedef enum { IGRAPH_SPARSEMAT_SOLVE_LU, + IGRAPH_SPARSEMAT_SOLVE_QR + } igraph_sparsemat_solve_t; + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_arpack_rssolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_sparsemat_solve_t solvemethod); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_arpack_rnsolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, + igraph_matrix_t *vectors); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_lu(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din, double tol); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_qr(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_luresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_qrresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_symbqr(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_symblu(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + + +IGRAPH_EXPORT void igraph_sparsemat_symbolic_destroy(igraph_sparsemat_symbolic_t *dis); +IGRAPH_EXPORT void igraph_sparsemat_numeric_destroy(igraph_sparsemat_numeric_t *din); + +IGRAPH_EXPORT igraph_real_t igraph_sparsemat_max(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_real_t igraph_sparsemat_min(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_minmax(igraph_sparsemat_t *A, + igraph_real_t *min, igraph_real_t *max); + +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_count_nonzero(igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_count_nonzerotol(igraph_sparsemat_t *A, + igraph_real_t tol); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_rowsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_colsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_rowmins(igraph_sparsemat_t *A, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_colmins(igraph_sparsemat_t *A, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_rowmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_colmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_which_min_rows(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_which_min_cols(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_scale(igraph_sparsemat_t *A, igraph_real_t by); + + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_add_rows(igraph_sparsemat_t *A, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_add_cols(igraph_sparsemat_t *A, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_resize(igraph_sparsemat_t *A, + igraph_int_t nrow, igraph_int_t ncol, igraph_int_t nzmax); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_nonzero_storage(const igraph_sparsemat_t *A); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_getelements(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_getelements_sorted(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_scale_rows(igraph_sparsemat_t *A, + const igraph_vector_t *fact); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_scale_cols(igraph_sparsemat_t *A, + const igraph_vector_t *fact); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_multiply_by_dense(const igraph_sparsemat_t *A, + const igraph_matrix_t *B, + igraph_matrix_t *res); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_dense_multiply(const igraph_matrix_t *A, + const igraph_sparsemat_t *B, + igraph_matrix_t *res); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_sort(const igraph_sparsemat_t *A, + igraph_sparsemat_t *sorted); + +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_nzmax(const igraph_sparsemat_t *A); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_neg(igraph_sparsemat_t *A); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_normalize_cols(igraph_sparsemat_t *sparsemat, + igraph_bool_t allow_zeros); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_normalize_rows(igraph_sparsemat_t *sparsemat, + igraph_bool_t allow_zeros); + +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_iterator_init( + igraph_sparsemat_iterator_t *it, const igraph_sparsemat_t *sparsemat +); +IGRAPH_EXPORT igraph_error_t igraph_sparsemat_iterator_reset(igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_bool_t igraph_sparsemat_iterator_end(const igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_iterator_row(const igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_iterator_col(const igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_iterator_idx(const igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_real_t igraph_sparsemat_iterator_get(const igraph_sparsemat_iterator_t *it); +IGRAPH_EXPORT igraph_int_t igraph_sparsemat_iterator_next(igraph_sparsemat_iterator_t *it); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_spatial.h b/include/igraph_spatial.h new file mode 100644 index 0000000..1e1ad35 --- /dev/null +++ b/include/igraph_spatial.h @@ -0,0 +1,91 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SPATIAL_H +#define IGRAPH_SPATIAL_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_error.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_delaunay_graph( + igraph_t *graph, + const igraph_matrix_t *points); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_lune_beta_skeleton( + igraph_t *graph, + const igraph_matrix_t *points, + igraph_real_t beta); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_circle_beta_skeleton( + igraph_t *graph, + const igraph_matrix_t *points, + igraph_real_t beta); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_beta_weighted_gabriel_graph( + igraph_t *graph, + igraph_vector_t *weights, + const igraph_matrix_t *points, + igraph_real_t max_beta); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_gabriel_graph( + igraph_t *graph, const igraph_matrix_t *points); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_relative_neighborhood_graph( + igraph_t *graph, const igraph_matrix_t *points); + +/** + * \typedef igraph_metric_t + * \brief Metric functions for use with spatial computation. + * + * \enumval IGRAPH_METRIC_EUCLIDEAN The Euclidean distance, i.e. L2 metric. + * \enumval IGRAPH_METRIC_MANHATTAN The Manhattan distance, i.e. L1 metric. + */ +typedef enum { + IGRAPH_METRIC_EUCLIDEAN = 0, + IGRAPH_METRIC_L2 = IGRAPH_METRIC_EUCLIDEAN, + IGRAPH_METRIC_MANHATTAN = 1, + IGRAPH_METRIC_L1 = IGRAPH_METRIC_MANHATTAN +} igraph_metric_t; + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_nearest_neighbor_graph( + igraph_t *graph, + const igraph_matrix_t *points, + igraph_metric_t metric, + igraph_int_t neighbors, + igraph_real_t cutoff, + igraph_bool_t directed); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_spatial_edge_lengths( + const igraph_t *graph, + igraph_vector_t *lengths, + const igraph_matrix_t *points, + igraph_metric_t metric); + +IGRAPH_EXPORT igraph_error_t igraph_convex_hull_2d( + const igraph_matrix_t *data, + igraph_vector_int_t *resverts, + igraph_matrix_t *rescoords); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_stack.h b/include/igraph_stack.h new file mode 100644 index 0000000..0b84a0a --- /dev/null +++ b/include/igraph_stack.h @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_STACK_H +#define IGRAPH_STACK_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Plain stack */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define IGRAPH_STACK_NULL { 0,0,0 } +#define IGRAPH_STACK_INIT_FINALLY(s, capacity) \ + do { IGRAPH_CHECK(igraph_stack_init(s, capacity)); \ + IGRAPH_FINALLY(igraph_stack_destroy, s); } while (0) +#define IGRAPH_STACK_INT_INIT_FINALLY(s, capacity) \ + do { IGRAPH_CHECK(igraph_stack_int_init(s, capacity)); \ + IGRAPH_FINALLY(igraph_stack_int_destroy, s); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_stack_pmt.h b/include/igraph_stack_pmt.h new file mode 100644 index 0000000..4d1c320 --- /dev/null +++ b/include/igraph_stack_pmt.h @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +/** + * Stack data type. + * \ingroup internal + */ + +typedef struct TYPE(igraph_stack) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; +} TYPE(igraph_stack); + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_stack, init)(TYPE(igraph_stack)* s, igraph_int_t capacity); +IGRAPH_EXPORT void FUNCTION(igraph_stack, destroy)(TYPE(igraph_stack)* s); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_stack, reserve)(TYPE(igraph_stack)* s, igraph_int_t capacity); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_stack, empty)(TYPE(igraph_stack)* s); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_stack, size)(const TYPE(igraph_stack)* s); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_stack, capacity)(const TYPE(igraph_stack)* s); +IGRAPH_EXPORT void FUNCTION(igraph_stack, clear)(TYPE(igraph_stack)* s); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_stack, push)(TYPE(igraph_stack)* s, BASE elem); +IGRAPH_EXPORT BASE FUNCTION(igraph_stack, pop)(TYPE(igraph_stack)* s); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_stack, top)(const TYPE(igraph_stack)* s); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_stack, print)(const TYPE(igraph_stack)* s); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_stack, fprint)(const TYPE(igraph_stack)* s, FILE *file); diff --git a/include/igraph_statusbar.h b/include/igraph_statusbar.h new file mode 100644 index 0000000..d8c1bae --- /dev/null +++ b/include/igraph_statusbar.h @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_STATUSBAR_H +#define IGRAPH_STATUSBAR_H + +#include "igraph_decls.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \section about_status_handlers Status reporting + * + * + * In addition to the possibility of reporting the progress of an + * igraph computation via \ref igraph_progress(), it is also possible + * to report simple status messages from within igraph functions, + * without having to judge how much of the computation was performed + * already. For this one needs to install a status handler function. + * + * + * + * Status handler functions must be of type \ref igraph_status_handler_t + * and they can be installed by a call to \ref igraph_set_status_handler(). + * Currently there is a simple predefined status handler function, + * called \ref igraph_status_handler_stderr(), but the user can define + * new ones. + * + * + * + * igraph functions report their status via a call to the + * \ref IGRAPH_STATUS() or the \ref IGRAPH_STATUSF() macro. + * + */ + +/** + * \typedef igraph_status_handler_t + * + * The type of the igraph status handler functions + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return Error code. The current calculation will abort if you return anything + * else than \c IGRAPH_SUCCESS here. + */ + +typedef igraph_error_t igraph_status_handler_t(const char *message, void *data); + +IGRAPH_EXPORT extern igraph_status_handler_t igraph_status_handler_stderr; + +IGRAPH_EXPORT igraph_status_handler_t *igraph_set_status_handler(igraph_status_handler_t new_handler); + +IGRAPH_EXPORT igraph_error_t igraph_status(const char *message, void *data); + +/** + * \define IGRAPH_STATUS + * Report the status of an igraph function. + * + * Typically this function is called only a handful of times from + * an igraph function. E.g. if an algorithm has three major + * steps, then it is logical to call it three times, to + * signal the three major steps. + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return If the status handler returns with a value other than + * \c IGRAPH_SUCCESS, then the function that called this + * macro returns as well, with the same error code, after + * cleaning up all allocated memory as needed. + */ + +#define IGRAPH_STATUS(message, data) \ + do { \ + IGRAPH_CHECK(igraph_status((message), (data))); \ + } while (0) + +IGRAPH_EXPORT igraph_error_t igraph_statusf(const char *message, void *data, ...); + +/** + * \define IGRAPH_STATUSF + * Report the status from an igraph function + * + * This is the more flexible version of \ref IGRAPH_STATUS(), + * having a printf-like syntax. As this macro takes variable + * number of arguments, they must be all supplied as a single + * argument, enclosed in parentheses. \ref igraph_statusf() is then + * called with the given arguments. + * + * \param args The arguments to pass to \ref igraph_statusf(). + * \return If the status handler returns with a value other than + * \c IGRAPH_SUCCESS, then the function that called this + * macro returns as well, with the same error code, after + * cleaning up all allocated memory as needed. + */ + +#define IGRAPH_STATUSF(args) \ + do { \ + IGRAPH_CHECK(igraph_statusf args); \ + } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_structural.h b/include/igraph_structural.h new file mode 100644 index 0000000..b568422 --- /dev/null +++ b/include/igraph_structural.h @@ -0,0 +1,173 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_STRUCTURAL_H +#define IGRAPH_STRUCTURAL_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_constants.h" +#include "igraph_iterators.h" +#include "igraph_matrix.h" +#include "igraph_sparsemat.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Basic query functions */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_are_adjacent(const igraph_t *graph, igraph_int_t v1, igraph_int_t v2, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_count_multiple(const igraph_t *graph, igraph_vector_int_t *res, igraph_es_t es); +IGRAPH_EXPORT igraph_error_t igraph_count_multiple_1(const igraph_t *graph, igraph_int_t *res, igraph_int_t eid); +IGRAPH_EXPORT igraph_error_t igraph_density(const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *res, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_diversity(const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_vs_t vs); +IGRAPH_EXPORT igraph_error_t igraph_girth(const igraph_t *graph, igraph_real_t *girth, + igraph_vector_int_t *circle); +IGRAPH_EXPORT igraph_error_t igraph_has_loop(const igraph_t *graph, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_count_loops(const igraph_t *graph, igraph_int_t *loop_count); +IGRAPH_EXPORT igraph_error_t igraph_is_loop(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); +IGRAPH_EXPORT igraph_error_t igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); +IGRAPH_EXPORT igraph_error_t igraph_is_mutual(const igraph_t *graph, igraph_vector_bool_t *res, igraph_es_t es, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_has_mutual(const igraph_t *graph, igraph_bool_t *res, igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_is_simple(const igraph_t *graph, igraph_bool_t *res, igraph_bool_t directed); +IGRAPH_EXPORT igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_int_t *root, igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_is_acyclic(const igraph_t *graph, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_is_forest(const igraph_t *graph, igraph_bool_t *res, + igraph_vector_int_t *roots, igraph_neimode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_maxdegree(const igraph_t *graph, igraph_int_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + igraph_loops_t loops); +IGRAPH_EXPORT igraph_error_t igraph_mean_degree(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t loops); +IGRAPH_EXPORT igraph_error_t igraph_reciprocity(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t ignore_loops, + igraph_reciprocity_t mode); +IGRAPH_EXPORT igraph_error_t igraph_strength( + const igraph_t *graph, igraph_vector_t *res, igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops, const igraph_vector_t *weights +); +IGRAPH_EXPORT igraph_error_t igraph_sort_vertex_ids_by_degree( + const igraph_t *graph, igraph_vector_int_t *outvids, igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops, igraph_order_t order, + igraph_bool_t only_indices +); +IGRAPH_EXPORT igraph_error_t igraph_is_perfect(const igraph_t *graph, igraph_bool_t *perfect); + +/* -------------------------------------------------- */ +/* Structural properties */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_is_complete(const igraph_t *graph, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_is_clique(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t directed, igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_is_independent_vertex_set(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t *res); +IGRAPH_EXPORT igraph_error_t igraph_minimum_spanning_tree( + const igraph_t *graph, igraph_vector_int_t *res, + const igraph_vector_t *weights, igraph_mst_algorithm_t method); +IGRAPH_EXPORT igraph_error_t igraph_random_spanning_tree(const igraph_t *graph, igraph_vector_int_t *res, + igraph_int_t vid); + +IGRAPH_EXPORT igraph_error_t igraph_subcomponent(const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t vid, + igraph_neimode_t mode); + +IGRAPH_EXPORT igraph_error_t igraph_unfold_tree(const igraph_t *graph, igraph_t *tree, + igraph_neimode_t mode, const igraph_vector_int_t *roots, + igraph_vector_int_t *vertex_index); + +IGRAPH_EXPORT igraph_error_t igraph_maximum_cardinality_search(const igraph_t *graph, + igraph_vector_int_t *alpha, + igraph_vector_int_t *alpham1); +IGRAPH_EXPORT igraph_error_t igraph_is_chordal(const igraph_t *graph, + const igraph_vector_int_t *alpha, + const igraph_vector_int_t *alpham1, + igraph_bool_t *chordal, + igraph_vector_int_t *fill_in, + igraph_t *newgraph); +IGRAPH_EXPORT igraph_error_t igraph_avg_nearest_neighbor_degree(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights); +IGRAPH_EXPORT igraph_error_t igraph_degree_correlation_vector( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *knnk, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors); + +IGRAPH_EXPERIMENTAL IGRAPH_EXPORT igraph_error_t igraph_rich_club_sequence( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_t *res, + const igraph_vector_int_t *vertex_order, + igraph_bool_t normalized, + igraph_bool_t loops, + igraph_bool_t directed); + + +/* -------------------------------------------------- */ +/* Spectral Properties */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_laplacian_normalization_t + * \brief Normalization methods for a Laplacian matrix. + * + * Normalization methods for \ref igraph_get_laplacian() and + * \ref igraph_get_laplacian_sparse(). In the following, \c A refers to the + * (possibly weighted) adjacency matrix and \c D is a diagonal matrix containing + * degrees (unweighted case) or strengths (weighted case). Out-, in- or total degrees + * are used according to the \p mode parameter. + * + * \enumval IGRAPH_LAPLACIAN_UNNORMALIZED Unnormalized Laplacian, L = D - A. + * \enumval IGRAPH_LAPLACIAN_SYMMETRIC Symmetrically normalized Laplacian, L = I - D^(-1/2) A D^(-1/2). + * \enumval IGRAPH_LAPLACIAN_LEFT Left-stochastic normalized Laplacian, L = I - D^-1 A. + * \enumval IGRAPH_LAPLACIAN_RIGHT Right-stochastic normalized Laplacian, L = I - A D^-1. + */ +typedef enum { + IGRAPH_LAPLACIAN_UNNORMALIZED = 0, + IGRAPH_LAPLACIAN_SYMMETRIC = 1, + IGRAPH_LAPLACIAN_LEFT = 2, + IGRAPH_LAPLACIAN_RIGHT = 3 +} igraph_laplacian_normalization_t; + +IGRAPH_EXPORT igraph_error_t igraph_get_laplacian( + const igraph_t *graph, igraph_matrix_t *res, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights +); +IGRAPH_EXPORT igraph_error_t igraph_get_laplacian_sparse( + const igraph_t *graph, igraph_sparsemat_t *sparseres, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights +); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_strvector.h b/include/igraph_strvector.h new file mode 100644 index 0000000..cf671d4 --- /dev/null +++ b/include/igraph_strvector.h @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_STRVECTOR_H +#define IGRAPH_STRVECTOR_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * Vector of strings + * \ingroup internal + */ + +typedef struct s_igraph_strvector { + /* Empty strings "" are represented using NULL. */ + const char **stor_begin; + const char **stor_end; + const char **end; +} igraph_strvector_t; + +/** + * \define STR + * \brief Indexing string vectors. + * + * This is a macro that allows to query the elements of a string vector, just + * like \ref igraph_strvector_get(). Note this macro cannot be used to set an + * element. Use \ref igraph_strvector_set() to set an element instead. + * + * \param sv The string vector + * \param i The index of the element. + * \return The element at position \p i. + * + * Time complexity: O(1). + * + * \deprecated-by igraph_strvector_get 0.10.9 + */ +#define STR(sv,i) \ + (IGRAPH_PREPROCESSOR_WARNING("STR() is deprecated. Use igraph_strvector_get() instead.") \ + igraph_strvector_get(&sv, i)) + +#define IGRAPH_STRVECTOR_NULL { 0,0,0 } +#define IGRAPH_STRVECTOR_INIT_FINALLY(sv, size) \ + do { IGRAPH_CHECK(igraph_strvector_init(sv, size)); \ + IGRAPH_FINALLY( igraph_strvector_destroy, sv); } while (0) + +IGRAPH_EXPORT igraph_error_t igraph_strvector_init(igraph_strvector_t *sv, igraph_int_t len); +IGRAPH_EXPORT void igraph_strvector_destroy(igraph_strvector_t *sv); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_strvector_size(const igraph_strvector_t *sv); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_strvector_capacity(const igraph_strvector_t *sv); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE const char *igraph_strvector_get(const igraph_strvector_t *sv, igraph_int_t idx); +IGRAPH_EXPORT igraph_error_t igraph_strvector_set( + igraph_strvector_t *sv, igraph_int_t idx, const char *value); +IGRAPH_EXPORT igraph_error_t igraph_strvector_set_len( + igraph_strvector_t *sv, igraph_int_t idx, const char *value, size_t len); +IGRAPH_EXPORT void igraph_strvector_clear(igraph_strvector_t *sv); +IGRAPH_EXPORT void igraph_strvector_remove_section( + igraph_strvector_t *v, igraph_int_t from, igraph_int_t to); +IGRAPH_EXPORT void igraph_strvector_remove( + igraph_strvector_t *v, igraph_int_t elem); +IGRAPH_EXPORT igraph_error_t igraph_strvector_init_copy( + igraph_strvector_t *to, const igraph_strvector_t *from); +IGRAPH_EXPORT igraph_error_t igraph_strvector_append( + igraph_strvector_t *to, const igraph_strvector_t *from); +IGRAPH_EXPORT igraph_error_t igraph_strvector_merge( + igraph_strvector_t *to, igraph_strvector_t *from); +IGRAPH_EXPORT void igraph_strvector_swap(igraph_strvector_t *v1, igraph_strvector_t *v2); +IGRAPH_EXPORT igraph_error_t igraph_strvector_update( + igraph_strvector_t *to, const igraph_strvector_t *from); +IGRAPH_EXPORT igraph_error_t igraph_strvector_resize( + igraph_strvector_t* v, igraph_int_t newsize); +IGRAPH_EXPORT void igraph_strvector_resize_min(igraph_strvector_t *sv); +IGRAPH_EXPORT igraph_error_t igraph_strvector_push_back(igraph_strvector_t *v, + const char *value); +IGRAPH_EXPORT igraph_error_t igraph_strvector_push_back_len(igraph_strvector_t *v, + const char *value, size_t len); +IGRAPH_EXPORT igraph_error_t igraph_strvector_fprint(const igraph_strvector_t *v, FILE *file, + const char *sep); +IGRAPH_EXPORT igraph_error_t igraph_strvector_print(const igraph_strvector_t *v, + const char *sep); + +IGRAPH_EXPORT igraph_error_t igraph_strvector_index(const igraph_strvector_t *v, + igraph_strvector_t *newv, + const igraph_vector_int_t *idx); + +IGRAPH_EXPORT igraph_error_t igraph_strvector_reserve(igraph_strvector_t *sv, + igraph_int_t capacity); + +IGRAPH_EXPORT void igraph_strvector_swap_elements(igraph_strvector_t *sv, + igraph_int_t i, igraph_int_t j); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_threading.h.in b/include/igraph_threading.h.in new file mode 100644 index 0000000..06f4d2b --- /dev/null +++ b/include/igraph_threading.h.in @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2011-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_THREADING_H +#define IGRAPH_THREADING_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * \define IGRAPH_THREAD_SAFE + * + * Specifies whether igraph was built in thread-safe mode. + * + * This macro is defined to 1 if the current build of the igraph library is + * built in thread-safe mode, and 0 if it is not. A thread-safe igraph library + * attempts to use thread-local data structures instead of global ones, but + * note that this is not (and can not) be guaranteed for third-party libraries + * that igraph links to. + */ + +#cmakedefine01 IGRAPH_THREAD_SAFE + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_transitivity.h b/include/igraph_transitivity.h new file mode 100644 index 0000000..b2cd6f3 --- /dev/null +++ b/include/igraph_transitivity.h @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_TRANSITIVITY_H +#define IGRAPH_TRANSITIVITY_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_EXPORT igraph_error_t igraph_transitivity_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_transitivity_local_undirected(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_transitivity_mode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_transitivity_avglocal_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_transitivity_barrat(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode); +IGRAPH_EXPORT igraph_error_t igraph_ecc(const igraph_t *graph, + igraph_vector_t *res, + igraph_es_t eids, + igraph_int_t k, + igraph_bool_t offset, + igraph_bool_t normalize); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_typed_list_pmt.h b/include/igraph_typed_list_pmt.h new file mode 100644 index 0000000..679fabf --- /dev/null +++ b/include/igraph_typed_list_pmt.h @@ -0,0 +1,116 @@ +/* + igraph library. + Copyright (C) 2021-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if defined(VECTOR_LIST) + /* It was indicated that every item in a list is a vector of the base type + * so let's define ITEM_TYPE appropriately */ + #define ITEM_TYPE BASE_VECTOR +#elif defined(MATRIX_LIST) + /* It was indicated that every item in a list is a matrix of the base type + * so let's define ITEM_TYPE appropriately */ + #define ITEM_TYPE BASE_MATRIX +#else + #define ITEM_TYPE BASE +#endif + +/** + * Vector list, dealing with lists of typed vectors efficiently. + * \ingroup types + */ + +typedef struct { + ITEM_TYPE* stor_begin; + ITEM_TYPE* stor_end; + ITEM_TYPE* end; +#ifdef EXTRA_TYPE_FIELDS + EXTRA_TYPE_FIELDS +#endif +} TYPE; + +/*--------------------*/ +/* Allocation */ +/*--------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(init)(TYPE* v, igraph_int_t size); +IGRAPH_EXPORT igraph_error_t FUNCTION(init_copy)(TYPE* to, const TYPE* from); +IGRAPH_EXPORT void FUNCTION(destroy)(TYPE* v); + +/*--------------------*/ +/* Accessing elements */ +/*--------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE ITEM_TYPE* FUNCTION(get_ptr)(const TYPE* v, igraph_int_t pos); +IGRAPH_EXPORT void FUNCTION(set)(TYPE* v, igraph_int_t pos, ITEM_TYPE* e); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE ITEM_TYPE* FUNCTION(tail_ptr)(const TYPE *v); + +/*-----------------*/ +/* List properties */ +/*-----------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(capacity)(const TYPE* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(empty)(const TYPE* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(size)(const TYPE* v); + +/*------------------------*/ +/* Resizing operations */ +/*------------------------*/ + +IGRAPH_EXPORT void FUNCTION(clear)(TYPE* v); +IGRAPH_EXPORT igraph_error_t FUNCTION(reserve)(TYPE* v, igraph_int_t capacity); +IGRAPH_EXPORT igraph_error_t FUNCTION(resize)(TYPE* v, igraph_int_t new_size); + +/*------------------------*/ +/* Adding/removing items */ +/*------------------------*/ + +IGRAPH_EXPORT void FUNCTION(discard)(TYPE* v, igraph_int_t index); +IGRAPH_EXPORT void FUNCTION(discard_back)(TYPE* v); +IGRAPH_EXPORT void FUNCTION(discard_fast)(TYPE* v, igraph_int_t index); +IGRAPH_EXPORT igraph_error_t FUNCTION(insert)(TYPE* v, igraph_int_t pos, ITEM_TYPE* e); +IGRAPH_EXPORT igraph_error_t FUNCTION(insert_copy)(TYPE* v, igraph_int_t pos, const ITEM_TYPE* e); +IGRAPH_EXPORT igraph_error_t FUNCTION(insert_new)(TYPE* v, igraph_int_t pos, ITEM_TYPE** result); +IGRAPH_EXPORT igraph_error_t FUNCTION(push_back)(TYPE* v, ITEM_TYPE* e); +IGRAPH_EXPORT igraph_error_t FUNCTION(push_back_copy)(TYPE* v, const ITEM_TYPE* e); +IGRAPH_EXPORT igraph_error_t FUNCTION(push_back_new)(TYPE* v, ITEM_TYPE** result); +IGRAPH_EXPORT ITEM_TYPE FUNCTION(pop_back)(TYPE* v); +IGRAPH_EXPORT igraph_error_t FUNCTION(remove)(TYPE* v, igraph_int_t index, ITEM_TYPE* e); +IGRAPH_EXPORT igraph_error_t FUNCTION(remove_fast)(TYPE* v, igraph_int_t index, ITEM_TYPE* e); +IGRAPH_EXPORT void FUNCTION(replace)(TYPE* v, igraph_int_t pos, ITEM_TYPE* e); +IGRAPH_EXPORT void FUNCTION(remove_consecutive_duplicates)(TYPE *v, igraph_bool_t (*eq)(const ITEM_TYPE*, const ITEM_TYPE*)); + +/*------------------*/ +/* Exchanging items */ +/*------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(permute)(TYPE *v, const igraph_vector_int_t *index); +IGRAPH_EXPORT igraph_error_t FUNCTION(reverse)(TYPE *v); +IGRAPH_EXPORT void FUNCTION(swap)(TYPE *v1, TYPE *v2); +IGRAPH_EXPORT void FUNCTION(swap_elements)(TYPE* v, igraph_int_t i, igraph_int_t j); + +/*-----------*/ +/* Sorting */ +/*-----------*/ + +IGRAPH_EXPORT void FUNCTION(sort)( + TYPE *v, int (*cmp)(const ITEM_TYPE*, const ITEM_TYPE*)); +IGRAPH_EXPORT igraph_error_t FUNCTION(sort_ind)( + TYPE *v, igraph_vector_int_t *ind, + int (*cmp)(const ITEM_TYPE*, const ITEM_TYPE*) +); + +#undef ITEM_TYPE diff --git a/include/igraph_types.h b/include/igraph_types.h new file mode 100644 index 0000000..5acd293 --- /dev/null +++ b/include/igraph_types.h @@ -0,0 +1,142 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_TYPES_H +#define IGRAPH_TYPES_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +#ifdef __cplusplus + #define __STDC_FORMAT_MACROS /* needed for PRId32 and PRId64 from inttypes.h on Linux */ +#endif + +#include "igraph_config.h" + +#include +#include +#include +#include +#include +#include + + +#if !defined(IGRAPH_INTEGER_SIZE) +# error "igraph integer size not defined; check the value of IGRAPH_INTEGER_SIZE when compiling" +#elif IGRAPH_INTEGER_SIZE == 64 +typedef int64_t igraph_int_t; +typedef uint64_t igraph_uint_t; +#elif IGRAPH_INTEGER_SIZE == 32 +typedef int32_t igraph_int_t; +typedef uint32_t igraph_uint_t; +#else +# error "Invalid igraph integer size; check the value of IGRAPH_INTEGER_SIZE when compiling" +#endif + +typedef igraph_int_t igraph_integer_t; + +typedef double igraph_real_t; + +/* IGRAPH_BOOL_TYPE is set to 'bool' by default, and it is not meant to be + * overridden, except for the R interface where we know what we are doing. + * See igraph_config.h for more info */ +typedef IGRAPH_BOOL_TYPE igraph_bool_t; + +/* printf format specifier for igraph_int_t */ +#if IGRAPH_INTEGER_SIZE == 64 +# define IGRAPH_PRId PRId64 +# define IGRAPH_PRIu PRIu64 +#else +# define IGRAPH_PRId PRId32 +# define IGRAPH_PRIu PRIu32 +#endif + +/* maximum and minimum allowed values for igraph_int_t */ +#if IGRAPH_INTEGER_SIZE == 64 +# define IGRAPH_INTEGER_MAX INT64_MAX +# define IGRAPH_INTEGER_MIN INT64_MIN +#else +# define IGRAPH_INTEGER_MAX INT32_MAX +# define IGRAPH_INTEGER_MIN INT32_MIN +#endif + +/* maximum and minimum allowed values for igraph_uint_t */ +#if IGRAPH_INTEGER_SIZE == 64 +# define IGRAPH_UINT_MAX UINT64_MAX +# define IGRAPH_UINT_MIN UINT64_MIN +#else +# define IGRAPH_UINT_MAX UINT32_MAX +# define IGRAPH_UINT_MIN UINT32_MIN +#endif + + +/** + * \define IGRAPH_VCOUNT_MAX + * \brief The maximum number of vertices supported in igraph graphs. + * + * The value of this constant is one less than \c IGRAPH_INTEGER_MAX . + * When igraph is compiled in 32-bit mode, this means that you are limited + * to 231 – 2 (about 2.1 billion) vertices. In + * 64-bit mode, the limit is 263 – 2 so you are much + * more likely to hit out-of-memory issues due to other reasons before reaching + * this limit. + */ +#define IGRAPH_VCOUNT_MAX (IGRAPH_INTEGER_MAX-1) +/* The 'os' and 'is' vectors in igraph_t have vcount+1 elements, + * thus this cannot currently be larger than IGRAPH_INTEGER_MAX-1. + * Several functions rely on this value not exceeding IGRAPH_INTEGER_MAX-1 + * to avoid overflow. + */ + +/** + * \define IGRAPH_ECOUNT_MAX + * \brief The maximum number of edges supported in igraph graphs. + * + * The value of this constant is half of \c IGRAPH_INTEGER_MAX . + * When igraph is compiled in 32-bit mode, this means that you are limited + * to approximately 230 (about 1.07 billion) + * vertices. In 64-bit mode, the limit is approximately + * 262 so you are much more likely to hit + * out-of-memory issues due to other reasons before reaching this limit. + */ +#define IGRAPH_ECOUNT_MAX (IGRAPH_INTEGER_MAX/2) +/* The endpoints of edges are often stored in a vector twice the length + * of the edge count, thus this cannot be larger than IGRAPH_INTEGER_MAX/2. + * Some of the overflow checking code relies on this. */ + +/* Replacements for printf that print doubles in the same way on all platforms + * (even for NaN and infinities) */ +IGRAPH_EXPORT int igraph_real_printf(igraph_real_t val); +IGRAPH_EXPORT int igraph_real_fprintf(FILE *file, igraph_real_t val); +IGRAPH_EXPORT int igraph_real_printf_aligned(int width, igraph_real_t val); +IGRAPH_EXPORT int igraph_real_fprintf_aligned(FILE *file, int width, igraph_real_t val); +IGRAPH_EXPORT int igraph_real_snprintf(char *str, size_t size, igraph_real_t val); + +/* Replacements for printf that print doubles in the same way on all platforms + * (even for NaN and infinities) with the largest possible precision */ +IGRAPH_EXPORT int igraph_real_printf_precise(igraph_real_t val); +IGRAPH_EXPORT int igraph_real_fprintf_precise(FILE *file, igraph_real_t val); +IGRAPH_EXPORT int igraph_real_snprintf_precise(char *str, size_t size, igraph_real_t val); + +#define IGRAPH_INFINITY ((double)INFINITY) +#define IGRAPH_NAN ((double)NAN) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_vector.h b/include/igraph_vector.h new file mode 100644 index 0000000..16d7a9d --- /dev/null +++ b/include/igraph_vector.h @@ -0,0 +1,158 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_VECTOR_H +#define IGRAPH_VECTOR_H + +#include "igraph_complex.h" +#include "igraph_constants.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Flexible vector */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +/* -------------------------------------------------- */ +/* Helper macros */ +/* -------------------------------------------------- */ + +#ifndef IGRAPH_VECTOR_NULL + #define IGRAPH_VECTOR_NULL { 0,0,0 } +#endif + +#ifndef IGRAPH_VECTOR_INIT_FINALLY +#define IGRAPH_VECTOR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_BOOL_INIT_FINALLY +#define IGRAPH_VECTOR_BOOL_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_bool_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_bool_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_CHAR_INIT_FINALLY +#define IGRAPH_VECTOR_CHAR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_char_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_char_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_INT_INIT_FINALLY +#define IGRAPH_VECTOR_INT_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_int_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_int_destroy, v); } while (0) +#endif + +/* -------------------------------------------------- */ +/* Type-specific vector functions */ +/* -------------------------------------------------- */ + +IGRAPH_EXPORT igraph_error_t igraph_vector_floor(const igraph_vector_t *from, igraph_vector_int_t *to); +IGRAPH_EXPORT igraph_error_t igraph_vector_round(const igraph_vector_t *from, igraph_vector_int_t *to); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vector_all_almost_e(const igraph_vector_t *lhs, + const igraph_vector_t *rhs, + igraph_real_t eps); + +IGRAPH_EXPORT igraph_error_t igraph_vector_zapsmall(igraph_vector_t *v, igraph_real_t tol); +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_zapsmall(igraph_vector_complex_t *v, igraph_real_t tol) ; + +IGRAPH_EXPORT igraph_error_t igraph_vector_is_nan(const igraph_vector_t *v, + igraph_vector_bool_t *is_nan); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vector_is_any_nan(const igraph_vector_t *v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vector_is_all_finite(const igraph_vector_t *v); + +IGRAPH_EXPORT igraph_error_t igraph_vector_int_pair_order(const igraph_vector_int_t* v, const igraph_vector_int_t *v2, + igraph_vector_int_t* res, igraph_int_t maxval); + +/* For internal use only: */ + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_vector_int_order( + const igraph_vector_int_t* v, + igraph_vector_int_t* res, + igraph_int_t maxval); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_vector_int_rank( + const igraph_vector_int_t *v, + igraph_vector_int_t *res, + igraph_int_t maxval); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_vector_list.h b/include/igraph_vector_list.h new file mode 100644 index 0000000..20b7c02 --- /dev/null +++ b/include/igraph_vector_list.h @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_VECTOR_LIST_H +#define IGRAPH_VECTOR_LIST_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Flexible list of vectors */ +/* -------------------------------------------------- */ + +/* Indicate to igraph_typed_list_pmt.h that we are going to work with _vectors_ + * of the base type, not the base type directly */ +#define VECTOR_LIST + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_typed_list_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#undef VECTOR_LIST + +/* -------------------------------------------------- */ +/* Helper macros */ +/* -------------------------------------------------- */ + +#define IGRAPH_VECTOR_LIST_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_list_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_list_destroy, v); } while (0) +#define IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_int_list_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_int_list_destroy, v); } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_vector_pmt.h b/include/igraph_vector_pmt.h new file mode 100644 index 0000000..9548af4 --- /dev/null +++ b/include/igraph_vector_pmt.h @@ -0,0 +1,313 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/*--------------------*/ +/* Allocation */ +/*--------------------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init)( + TYPE(igraph_vector) *v, igraph_int_t size); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_array)( + TYPE(igraph_vector) *v, const BASE *data, igraph_int_t length); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_copy)( + TYPE(igraph_vector) *to, const TYPE(igraph_vector) *from); + +#ifndef NOTORDERED +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_range)(TYPE(igraph_vector)*v, BASE start, BASE end); +#endif + +IGRAPH_EXPORT void FUNCTION(igraph_vector, destroy)(TYPE(igraph_vector) *v); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_vector, capacity)(const TYPE(igraph_vector)*v); + +/*--------------------*/ +/* Accessing elements */ +/*--------------------*/ + +#ifndef VECTOR +/** + * \ingroup vector + * \define VECTOR + * \brief Accessing an element of a vector. + * + * Usage: + * \verbatim VECTOR(v)[0] \endverbatim + * to access the first element of the vector, you can also use this in + * assignments, like: + * \verbatim VECTOR(v)[10] = 5; \endverbatim + * + * Note that there are no range checks right now. + * + * \param v The vector object. + * + * Time complexity: O(1). + */ +#define VECTOR(v) ((v).stor_begin) +#endif + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, get)(const TYPE(igraph_vector) *v, igraph_int_t pos); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE* FUNCTION(igraph_vector, get_ptr)(const TYPE(igraph_vector) *v, igraph_int_t pos); +IGRAPH_EXPORT void FUNCTION(igraph_vector, set)(TYPE(igraph_vector) *v, igraph_int_t pos, BASE value); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, tail)(const TYPE(igraph_vector) *v); + +/*-----------------------*/ +/* Initializing elements */ +/*-----------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_vector, null)(TYPE(igraph_vector) *v); +IGRAPH_EXPORT void FUNCTION(igraph_vector, fill)(TYPE(igraph_vector) *v, BASE e); + +#ifndef NOTORDERED +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, range)(TYPE(igraph_vector) *v, BASE start, BASE end); +#endif + +/*-----------------------*/ +/* Vector views */ +/*-----------------------*/ + +IGRAPH_EXPORT TYPE(igraph_vector) FUNCTION(igraph_vector, view)(const BASE *data, + igraph_int_t length); + +/*-----------------------*/ +/* Copying vectors */ +/*-----------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_vector, copy_to)(const TYPE(igraph_vector) *v, BASE *to); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, update)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, append)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); +IGRAPH_EXPORT void FUNCTION(igraph_vector, swap)(TYPE(igraph_vector) *v1, TYPE(igraph_vector) *v2); + +/*-----------------------*/ +/* Exchanging elements */ +/*-----------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_vector, swap_elements)( + TYPE(igraph_vector) *v, igraph_int_t i, igraph_int_t j); +IGRAPH_EXPORT void FUNCTION(igraph_vector, reverse)(TYPE(igraph_vector) *v); +IGRAPH_EXPORT void FUNCTION(igraph_vector, reverse_section)( + TYPE(igraph_vector) *v, igraph_int_t from, igraph_int_t to); +IGRAPH_EXPORT void FUNCTION(igraph_vector, rotate_left)( + TYPE(igraph_vector) *v, igraph_int_t n); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, permute)(TYPE(igraph_vector) *v, + const igraph_vector_int_t *ind); +IGRAPH_EXPORT void FUNCTION(igraph_vector, shuffle)(TYPE(igraph_vector) *v); + +/*-----------------------*/ +/* Vector operations */ +/*-----------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_vector, add_constant)(TYPE(igraph_vector) *v, BASE plus); +IGRAPH_EXPORT void FUNCTION(igraph_vector, scale)(TYPE(igraph_vector) *v, BASE by); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, add)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, sub)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, mul)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, div)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, cumsum)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); + +#ifndef NOABS + IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, abs)(TYPE(igraph_vector) *v); +#endif + +/*------------------------------*/ +/* Comparison */ +/*------------------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, all_e)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, all_l)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, all_g)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, all_le)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, all_ge)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE int FUNCTION(igraph_vector, lex_cmp)( + const TYPE(igraph_vector) *lhs, const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE int FUNCTION(igraph_vector, colex_cmp)( + const TYPE(igraph_vector) *lhs, const TYPE(igraph_vector) *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE int FUNCTION(igraph_vector, lex_cmp_untyped)(const void *lhs, const void *rhs); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE int FUNCTION(igraph_vector, colex_cmp_untyped)(const void *lhs, const void *rhs); +#endif + +/*------------------------------*/ +/* Finding minimum and maximum */ +/*------------------------------*/ + +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, min)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, max)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_vector, which_min)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_vector, which_max)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT void FUNCTION(igraph_vector, minmax)( + const TYPE(igraph_vector) *v, BASE *min, BASE *max); +IGRAPH_EXPORT void FUNCTION(igraph_vector, which_minmax)( + const TYPE(igraph_vector) *v, igraph_int_t *which_min, igraph_int_t *which_max); +#endif + +/*-------------------*/ +/* Vector properties */ +/*-------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, empty)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_vector, size)(const TYPE(igraph_vector)* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, isnull)(const TYPE(igraph_vector) *v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, sum)(const TYPE(igraph_vector) *v); +IGRAPH_DEPRECATED IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t FUNCTION(igraph_vector, sumsq)(const TYPE(igraph_vector) *v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE BASE FUNCTION(igraph_vector, prod)(const TYPE(igraph_vector) *v); +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, isininterval)(const TYPE(igraph_vector) *v, + BASE low, BASE high); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, any_smaller)(const TYPE(igraph_vector) *v, + BASE limit); +#endif +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, is_equal)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +#ifndef NOTORDERED +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t FUNCTION(igraph_vector, maxdifference)(const TYPE(igraph_vector) *m1, + const TYPE(igraph_vector) *m2); +#endif + +/*------------------------*/ +/* Searching for elements */ +/*------------------------*/ + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, contains)(const TYPE(igraph_vector) *v, BASE e); +IGRAPH_EXPORT igraph_bool_t FUNCTION(igraph_vector, search)( + const TYPE(igraph_vector) *v, igraph_int_t from, BASE what, igraph_int_t *pos); +#ifndef NOTORDERED +IGRAPH_EXPORT igraph_bool_t FUNCTION(igraph_vector, binsearch_slice)( + const TYPE(igraph_vector) *v, BASE what, igraph_int_t *pos, + igraph_int_t start, igraph_int_t end); +IGRAPH_EXPORT igraph_bool_t FUNCTION(igraph_vector, binsearch)( + const TYPE(igraph_vector) *v, BASE what, igraph_int_t *pos); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t FUNCTION(igraph_vector, contains_sorted)( + const TYPE(igraph_vector) *v, BASE what); +#endif + +/*------------------------*/ +/* Resizing operations */ +/*------------------------*/ + +IGRAPH_EXPORT void FUNCTION(igraph_vector, clear)(TYPE(igraph_vector)* v); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, resize)( + TYPE(igraph_vector)* v, igraph_int_t new_size); +IGRAPH_EXPORT void FUNCTION(igraph_vector, resize_min)(TYPE(igraph_vector)*v); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, reserve)( + TYPE(igraph_vector)* v, igraph_int_t capacity); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, push_back)(TYPE(igraph_vector)* v, BASE e); +IGRAPH_EXPORT BASE FUNCTION(igraph_vector, pop_back)(TYPE(igraph_vector)* v); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, insert)( + TYPE(igraph_vector) *v, igraph_int_t pos, BASE value); +IGRAPH_EXPORT void FUNCTION(igraph_vector, remove)( + TYPE(igraph_vector) *v, igraph_int_t elem); +IGRAPH_EXPORT void FUNCTION(igraph_vector, remove_fast)( + TYPE(igraph_vector) *v, igraph_int_t elem); +IGRAPH_EXPORT void FUNCTION(igraph_vector, remove_section)( + TYPE(igraph_vector) *v, igraph_int_t from, igraph_int_t to); + +/*-----------*/ +/* Sorting */ +/*-----------*/ + +#ifndef NOTORDERED + +IGRAPH_EXPORT void FUNCTION(igraph_vector, sort)(TYPE(igraph_vector) *v); +IGRAPH_EXPORT void FUNCTION(igraph_vector, reverse_sort)(TYPE(igraph_vector) *v); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, sort_ind)( + const TYPE(igraph_vector) *v, igraph_vector_int_t *inds, igraph_order_t order); + +#endif + +/*-----------*/ +/* Printing */ +/*-----------*/ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, print)(const TYPE(igraph_vector) *v); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, fprint)(const TYPE(igraph_vector) *v, FILE *file); + +#ifdef OUT_FORMAT +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, printf)(const TYPE(igraph_vector) *v, const char *format); +#endif /* OUT_FORMAT */ + +/*----------------------------------------*/ +/* Operations specific to complex vectors */ +/*----------------------------------------*/ + +#ifdef BASE_COMPLEX + +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_real(const igraph_vector_complex_t *v, + igraph_vector_t *real); +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_imag(const igraph_vector_complex_t *v, + igraph_vector_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_realimag(const igraph_vector_complex_t *v, + igraph_vector_t *real, + igraph_vector_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_create(igraph_vector_complex_t *v, + const igraph_vector_t *real, + const igraph_vector_t *imag); +IGRAPH_EXPORT igraph_error_t igraph_vector_complex_create_polar(igraph_vector_complex_t *v, + const igraph_vector_t *r, + const igraph_vector_t *theta); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vector_complex_all_almost_e(const igraph_vector_complex_t *lhs, + const igraph_vector_complex_t *rhs, + igraph_real_t eps); + +#endif /* BASE_COMPLEX */ + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_real)(TYPE(igraph_vector)*v, int no, ...); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_int)(TYPE(igraph_vector)*v, int no, ...); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_real_end)(TYPE(igraph_vector)*v, double endmark, ...); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, init_int_end)(TYPE(igraph_vector)*v, int endmark, ...); + +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, move_interval)( + TYPE(igraph_vector) *v, igraph_int_t begin, igraph_int_t end, + igraph_int_t to); +#ifndef NOTORDERED +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, filter_smaller)(TYPE(igraph_vector) *v, BASE elem); +#endif +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, get_interval)( + const TYPE(igraph_vector) *v, TYPE(igraph_vector) *res, + igraph_int_t from, igraph_int_t to); +#ifndef NOTORDERED +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, difference_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, difference_and_intersection_sorted)( + const TYPE(igraph_vector) *v1, const TYPE(igraph_vector) *v2, + TYPE(igraph_vector) *vdiff12, TYPE(igraph_vector) *vdiff21, + TYPE(igraph_vector) *vinter); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, intersect_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t FUNCTION(igraph_vector, intersection_size_sorted)( + const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +#endif +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, index)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *newv, + const igraph_vector_int_t *idx); +IGRAPH_EXPORT igraph_error_t FUNCTION(igraph_vector, index_in_place)(TYPE(igraph_vector) *v, + const igraph_vector_int_t *idx); diff --git a/include/igraph_vector_ptr.h b/include/igraph_vector_ptr.h new file mode 100644 index 0000000..90a89ba --- /dev/null +++ b/include/igraph_vector_ptr.h @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_VECTOR_PTR_H +#define IGRAPH_VECTOR_PTR_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Flexible vector, storing pointers */ +/* -------------------------------------------------- */ + +/** + * Vector, storing pointers efficiently + * \ingroup internal + * + */ +typedef struct s_vector_ptr { + void** stor_begin; + void** stor_end; + void** end; + igraph_finally_func_t* item_destructor; +} igraph_vector_ptr_t; + +#define IGRAPH_VECTOR_PTR_NULL { 0,0,0,0 } +#define IGRAPH_VECTOR_PTR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_ptr_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_ptr_destroy, v); } while (0) + +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_init(igraph_vector_ptr_t* v, igraph_int_t size); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_init_array(igraph_vector_ptr_t* v, void *const *data, igraph_int_t length); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_init_copy(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from); +IGRAPH_EXPORT igraph_vector_ptr_t igraph_vector_ptr_view (void *const *data, igraph_int_t length); +IGRAPH_EXPORT void igraph_vector_ptr_destroy(igraph_vector_ptr_t* v); +IGRAPH_EXPORT void igraph_vector_ptr_free_all(igraph_vector_ptr_t* v); +IGRAPH_EXPORT void igraph_vector_ptr_destroy_all(igraph_vector_ptr_t* v); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_reserve(igraph_vector_ptr_t* v, igraph_int_t capacity); +IGRAPH_EXPORT void igraph_vector_ptr_resize_min(igraph_vector_ptr_t* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_vector_ptr_empty(const igraph_vector_ptr_t* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_vector_ptr_size(const igraph_vector_ptr_t* v); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_vector_ptr_capacity(const igraph_vector_ptr_t* v); +IGRAPH_EXPORT void igraph_vector_ptr_clear(igraph_vector_ptr_t* v); +IGRAPH_EXPORT void igraph_vector_ptr_null(igraph_vector_ptr_t* v); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_push_back(igraph_vector_ptr_t* v, void* e); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_append(igraph_vector_ptr_t *to, + const igraph_vector_ptr_t *from); +IGRAPH_EXPORT void *igraph_vector_ptr_pop_back(igraph_vector_ptr_t *v); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_insert(igraph_vector_ptr_t *v, igraph_int_t pos, void* e); +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE void *igraph_vector_ptr_get(const igraph_vector_ptr_t* v, igraph_int_t pos); +IGRAPH_EXPORT void igraph_vector_ptr_set(igraph_vector_ptr_t* v, igraph_int_t pos, void* value); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_resize(igraph_vector_ptr_t* v, igraph_int_t newsize); +IGRAPH_EXPORT void igraph_vector_ptr_copy_to(const igraph_vector_ptr_t *v, void** to); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_permute(igraph_vector_ptr_t* v, const igraph_vector_int_t* index); +IGRAPH_EXPORT void igraph_vector_ptr_remove(igraph_vector_ptr_t *v, igraph_int_t pos); +IGRAPH_EXPORT void igraph_vector_ptr_sort(igraph_vector_ptr_t *v, int(*compar)(const void*, const void*)); +IGRAPH_EXPORT igraph_error_t igraph_vector_ptr_sort_ind( + igraph_vector_ptr_t *v, igraph_vector_int_t *inds, int(*compar)(const void*, const void*)); + +IGRAPH_EXPORT IGRAPH_FUNCATTR_PURE igraph_finally_func_t* igraph_vector_ptr_get_item_destructor(const igraph_vector_ptr_t *v); +IGRAPH_EXPORT igraph_finally_func_t* igraph_vector_ptr_set_item_destructor(igraph_vector_ptr_t *v, + igraph_finally_func_t *func); + +/** + * \define IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR + * \brief Sets the item destructor for this pointer vector (macro version). + * + * This macro is expanded to \ref igraph_vector_ptr_set_item_destructor(), the + * only difference is that the second argument is automatically cast to an + * \c igraph_finally_func_t*. The cast is necessary in most cases as the + * destructor functions we use (such as \ref igraph_vector_destroy()) take a + * pointer to some concrete igraph data type, while \c igraph_finally_func_t + * expects \c void* + */ +#define IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(v, func) \ + igraph_vector_ptr_set_item_destructor((v), (igraph_finally_func_t*)(func)) + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_vector_type.h b/include/igraph_vector_type.h new file mode 100644 index 0000000..95c39ae --- /dev/null +++ b/include/igraph_vector_type.h @@ -0,0 +1,28 @@ +/* + igraph library. + Copyright (C) 2013-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * Vector, dealing with arrays efficiently. + * \ingroup types + */ + +typedef struct TYPE(igraph_vector) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; +} TYPE(igraph_vector); diff --git a/include/igraph_version.h.in b/include/igraph_version.h.in new file mode 100644 index 0000000..74bfc5e --- /dev/null +++ b/include/igraph_version.h.in @@ -0,0 +1,39 @@ +/* + igraph library. + Copyright (C) 2010-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_VERSION_H +#define IGRAPH_VERSION_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +#define IGRAPH_VERSION "@PACKAGE_VERSION@" +#define IGRAPH_VERSION_MAJOR @PACKAGE_VERSION_MAJOR@ +#define IGRAPH_VERSION_MINOR @PACKAGE_VERSION_MINOR@ +#define IGRAPH_VERSION_PATCH @PACKAGE_VERSION_PATCH@ +#define IGRAPH_VERSION_PRERELEASE "@PACKAGE_VERSION_PRERELEASE@" + +IGRAPH_EXPORT void igraph_version(const char **version_string, + int *major, + int *minor, + int *patch); + +IGRAPH_END_C_DECLS + +#endif diff --git a/include/igraph_visitor.h b/include/igraph_visitor.h new file mode 100644 index 0000000..7edd3fe --- /dev/null +++ b/include/igraph_visitor.h @@ -0,0 +1,134 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_VISITOR_H +#define IGRAPH_VISITOR_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_datatype.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Visitor-like functions */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_bfshandler_t + * \brief Callback type for BFS function. + * + * \ref igraph_bfs() is able to call a callback function, whenever a + * new vertex is found, while doing the breadth-first search. This + * callback function must be of type \c igraph_bfshandler_t. It has + * the following arguments: + * + * \param graph The graph that the algorithm is working on. Of course + * this must not be modified. + * \param vid The id of the vertex just found by the breadth-first + * search. + * \param pred The id of the previous vertex visited. It is -1 if + * there is no previous vertex, because the current vertex is the root + * is a search tree. + * \param succ The id of the next vertex that will be visited. It is + * -1 if there is no next vertex, because the current vertex is the + * last one in a search tree. + * \param rank The rank of the current vertex, it starts with zero. + * \param dist The distance (number of hops) of the current vertex + * from the root of the current search tree. + * \param extra The extra argument that was passed to \ref + * igraph_bfs(). + * \return \c IGRAPH_SUCCESS if the BFS should continue, \c IGRAPH_STOP + * if the BFS should stop and return to the caller normally. Any other + * value is treated as an igraph error code, terminating the search and + * returning to the caller with the same error code. If a BFS is + * is terminated prematurely, then all elements of the result vectors + * that were not yet calculated at the point of the termination + * contain negative values. + * + * \sa \ref igraph_bfs() + */ + +typedef igraph_error_t igraph_bfshandler_t(const igraph_t *graph, + igraph_int_t vid, + igraph_int_t pred, + igraph_int_t succ, + igraph_int_t rank, + igraph_int_t dist, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_bfs(const igraph_t *graph, + igraph_int_t root, const igraph_vector_int_t *roots, + igraph_neimode_t mode, igraph_bool_t unreachable, + const igraph_vector_int_t *restricted, + igraph_vector_int_t *order, igraph_vector_int_t *rank, + igraph_vector_int_t *parents, + igraph_vector_int_t *pred, igraph_vector_int_t *succ, + igraph_vector_int_t *dist, igraph_bfshandler_t *callback, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_bfs_simple(const igraph_t *graph, igraph_int_t root, igraph_neimode_t mode, + igraph_vector_int_t *order, igraph_vector_int_t *layers, + igraph_vector_int_t *parents); + +/** + * \function igraph_dfshandler_t + * \brief Callback type for the DFS function. + * + * \ref igraph_dfs() is able to call a callback function, whenever a + * new vertex is discovered, and/or whenever a subtree is + * completed. These callbacks must be of type \c + * igraph_dfshandler_t. They have the following arguments: + * + * \param graph The graph that the algorithm is working on. Of course + * this must not be modified. + * \param vid The id of the vertex just found by the depth-first + * search. + * \param dist The distance (number of hops) of the current vertex + * from the root of the current search tree. + * \param extra The extra argument that was passed to \ref + * igraph_dfs(). + * \return \c IGRAPH_SUCCESS if the DFS should continue, \c IGRAPH_STOP + * if the DFS should stop and return to the caller normally. Any other + * value is treated as an igraph error code, terminating the search and + * returning to the caller with the same error code. If a DFS is + * is terminated prematurely, then all elements of the result vectors + * that were not yet calculated at the point of the termination + * contain negative values. + * + * \sa \ref igraph_dfs() + */ + +typedef igraph_error_t igraph_dfshandler_t(const igraph_t *graph, + igraph_int_t vid, + igraph_int_t dist, + void *extra); + +IGRAPH_EXPORT igraph_error_t igraph_dfs(const igraph_t *graph, igraph_int_t root, + igraph_neimode_t mode, igraph_bool_t unreachable, + igraph_vector_int_t *order, + igraph_vector_int_t *order_out, igraph_vector_int_t *parents, + igraph_vector_int_t *dist, igraph_dfshandler_t *in_callback, + igraph_dfshandler_t *out_callback, + void *extra); + +IGRAPH_END_C_DECLS + +#endif diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt new file mode 100644 index 0000000..57a89d5 --- /dev/null +++ b/interfaces/CMakeLists.txt @@ -0,0 +1,28 @@ +# Check whether the user has Stimulus on its PATH +find_program(STIMULUS_COMMAND stimulus) + +# Add a custom targer that checks functions.yaml and types.yaml +if(STIMULUS_COMMAND) + add_custom_command( + OUTPUT test_stimulus_specifications.cpp + COMMAND ${STIMULUS_COMMAND} + -l ci:validate + -f ${CMAKE_CURRENT_SOURCE_DIR}/functions.yaml + -t ${CMAKE_CURRENT_SOURCE_DIR}/types.yaml + -o test_stimulus_specifications.cpp + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/functions.yaml + ${CMAKE_CURRENT_SOURCE_DIR}/types.yaml + COMMENT "Generating C++ checker for Stimulus function and type specifications..." + USES_TERMINAL + ) + add_executable(test_stimulus test_stimulus_specifications.cpp) + target_include_directories(test_stimulus PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include) + + add_custom_target( + check_stimulus + COMMAND test_stimulus + DEPENDS test_stimulus + COMMENT "Running C++ checker for Stimulus function and type specifications..." + ) +endif(STIMULUS_COMMAND) diff --git a/interfaces/functions.yaml b/interfaces/functions.yaml new file mode 100644 index 0000000..544ac0e --- /dev/null +++ b/interfaces/functions.yaml @@ -0,0 +1,2817 @@ +# vim:set ts=4 sw=4 sts=4 et: +# +# This file is a YAML representation of the signatures of most igraph +# functions. They are currently used by some of the higher level interfaces to +# generate code using our internal tool called Stimulus +# +# See https://github.com/igraph/stimulus for more information + +####################################### +# The basic interface +####################################### + +igraph_empty: + PARAMS: OUT GRAPH graph, INTEGER n=0, BOOLEAN directed=True + FLAGS: no_rng + +igraph_add_edges: + PARAMS: INOUT GRAPH graph, VERTEX_INDEX_PAIRS edges, ATTRIBUTES attr + DEPS: edges ON graph + FLAGS: no_rng + +igraph_empty_attrs: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed, ATTRIBUTES attr + FLAGS: no_rng + +igraph_add_vertices: + PARAMS: INOUT GRAPH graph, INTEGER nv, ATTRIBUTES attr + FLAGS: no_rng + +igraph_copy: + PARAMS: OUT GRAPH to, IN GRAPH from + FLAGS: no_rng + +igraph_delete_edges: + PARAMS: INOUT GRAPH graph, EDGE_SELECTOR edges + DEPS: edges ON graph + FLAGS: no_rng + +igraph_delete_vertices: + PARAMS: INOUT GRAPH graph, VERTEX_SELECTOR vertices + DEPS: vertices ON graph + FLAGS: no_rng + +igraph_delete_vertices_map: + PARAMS: |- + INOUT GRAPH graph, VERTEX_SELECTOR vertices, + OPTIONAL OUT VECTOR_INT idx, + OPTIONAL OUT VECTOR_INT invidx + DEPS: vertices ON graph + FLAGS: no_rng + +igraph_vcount: + PARAMS: GRAPH graph + RETURN: INTEGER + FLAGS: no_rng + +igraph_ecount: + PARAMS: GRAPH graph + RETURN: INTEGER + FLAGS: no_rng + +igraph_neighbors: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES neis, VERTEX vid, NEIMODE mode=ALL, + LOOPS loops=TWICE, BOOLEAN multiple=True + DEPS: vid ON graph, neis ON graph + FLAGS: no_rng + +igraph_is_directed: + PARAMS: GRAPH graph + RETURN: BOOLEAN + FLAGS: no_rng + +igraph_degree: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT res, VERTEX_SELECTOR vids=ALL, NEIMODE mode=ALL, + LOOPS loops=TWICE + DEPS: vids ON graph + FLAGS: no_rng + +igraph_edge: + PARAMS: GRAPH graph, INTEGER eid, OUT INTEGER from, OUT INTEGER to + FLAGS: no_rng + +igraph_edges: + PARAMS: GRAPH graph, EDGE_SELECTOR eids, OUT VECTOR_INT edges, BOOLEAN bycol=False + DEPS: eids ON graph + FLAGS: no_rng + +igraph_get_eid: + PARAMS: |- + GRAPH graph, OUT EDGE eid, VERTEX from, VERTEX to, + BOOLEAN directed=True, BOOLEAN error=True + DEPS: eid ON graph, from ON graph, to ON graph + FLAGS: no_rng + +igraph_get_eids: + PARAMS: |- + GRAPH graph, OUT EDGE_INDICES eids, VERTEX_INDEX_PAIRS pairs, + BOOLEAN directed=True, BOOLEAN error=True + DEPS: eids ON graph, pairs ON graph + FLAGS: no_rng + +igraph_get_all_eids_between: + PARAMS: |- + GRAPH graph, OUT EDGE_INDICES eids, VERTEX from, VERTEX to, + BOOLEAN directed=True + DEPS: eids ON graph, from ON graph, to ON graph + FLAGS: no_rng + +igraph_incident: + PARAMS: GRAPH graph, OUT EDGE_INDICES eids, VERTEX vid, NEIMODE mode=ALL, LOOPS loops=TWICE + DEPS: eids ON graph, vid ON graph + FLAGS: no_rng + +igraph_is_same_graph: + PARAMS: GRAPH graph1, GRAPH graph2, OUT BOOLEAN res + FLAGS: no_rng + +####################################### +# Constructors, deterministic +####################################### + +igraph_create: + PARAMS: OUT GRAPH graph, VECTOR_INT edges, INTEGER n=0, BOOLEAN directed=True + FLAGS: no_rng + +igraph_adjacency: + PARAMS: |- + OUT GRAPH graph, MATRIX adjmatrix, ADJACENCY_MODE mode=DIRECTED, LOOPS loops=ONCE + +igraph_sparse_adjacency: + # adjmatrix is declared as INOUT because it might be modified during the + # construction to eliminate duplicate elements from the representation + PARAMS: |- + OUT GRAPH graph, INOUT SPARSEMAT adjmatrix, ADJACENCY_MODE mode=DIRECTED, LOOPS loops=ONCE + +igraph_sparse_weighted_adjacency: + # adjmatrix is declared as INOUT because it might be modified during the + # construction to eliminate duplicate elements from the representation + PARAMS: |- + OUT GRAPH graph, INOUT SPARSEMAT adjmatrix, ADJACENCY_MODE mode=DIRECTED, + OUT EDGE_WEIGHTS weights, LOOPS loops=ONCE + DEPS: weights ON graph + +igraph_weighted_adjacency: + PARAMS: |- + OUT GRAPH graph, MATRIX adjmatrix, ADJACENCY_MODE mode=DIRECTED, + OUT EDGE_WEIGHTS weights, LOOPS loops=ONCE + DEPS: weights ON graph + +igraph_star: + PARAMS: OUT GRAPH graph, INTEGER n, STAR_MODE mode=OUT, INTEGER center=0 + +igraph_wheel: + PARAMS: OUT GRAPH graph, INTEGER n, WHEEL_MODE mode=OUT, INTEGER center=0 + +igraph_hypercube: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=False + +igraph_square_lattice: + PARAMS: |- + OUT GRAPH graph, VECTOR_INT dimvector, INTEGER nei=1, + BOOLEAN directed=False, BOOLEAN mutual=False, OPTIONAL VECTOR_BOOL periodic + +igraph_triangular_lattice: + PARAMS: |- + OUT GRAPH graph, VECTOR_INT dimvector, BOOLEAN directed=False, BOOLEAN mutual=False + +igraph_ring: + PARAMS: |- + OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN mutual=False, + BOOLEAN circular=True + +igraph_path_graph: + PARAMS: |- + OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN mutual=False + +igraph_cycle_graph: + PARAMS: |- + OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN mutual=False + +igraph_kary_tree: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER children=2, TREE_MODE type=OUT + +igraph_symmetric_tree: + PARAMS: OUT GRAPH graph, VECTOR_INT branches, TREE_MODE type=OUT + +igraph_regular_tree: + PARAMS: OUT GRAPH graph, INTEGER h, INTEGER k=3, TREE_MODE type=UNDIRECTED + +igraph_full: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN loops=False + FLAGS: no_rng + +igraph_full_citation: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=True + +igraph_atlas: + PARAMS: OUT GRAPH graph, INTEGER number=0 + +igraph_extended_chordal_ring: + PARAMS: OUT GRAPH graph, INTEGER nodes, MATRIX_INT W, BOOLEAN directed=False + +igraph_connect_neighborhood: + PARAMS: INOUT GRAPH graph, INTEGER order=2, NEIMODE mode=ALL + +igraph_graph_power: + PARAMS: IN GRAPH graph, OUT GRAPH res, INTEGER order, BOOLEAN directed=False + +igraph_linegraph: + PARAMS: GRAPH graph, OUT GRAPH linegraph + +igraph_de_bruijn: + PARAMS: OUT GRAPH graph, INTEGER m, INTEGER n + FLAGS: no_rng + +igraph_kautz: + PARAMS: OUT GRAPH graph, INTEGER m, INTEGER n + FLAGS: no_rng + +igraph_famous: + PARAMS: OUT GRAPH graph, CSTRING name + FLAGS: no_rng + +igraph_lcf: + PARAMS: OUT GRAPH graph, INTEGER n, VECTOR_INT shifts, INTEGER repeats=1 + +igraph_mycielski_graph: + PARAMS: OUT GRAPH graph, INTEGER k + +igraph_adjlist: + PARAMS: |- + OUT GRAPH graph, ADJLIST adjlist, NEIMODE mode=OUT, + BOOLEAN duplicate=True + +igraph_full_bipartite: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT BIPARTITE_TYPES types, INTEGER n1, + INTEGER n2, BOOLEAN directed=False, NEIMODE mode=ALL + +igraph_full_multipartite: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT INDEX_VECTOR types, VECTOR_INT n, + BOOLEAN directed=False, NEIMODE mode=ALL + +igraph_realize_degree_sequence: + PARAMS: |- + OUT GRAPH graph, VECTOR_INT out_deg, OPTIONAL VECTOR_INT in_deg, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, REALIZE_DEGSEQ_METHOD method=SMALLEST + +igraph_realize_bipartite_degree_sequence: + PARAMS: |- + OUT GRAPH graph, VECTOR_INT degrees1, VECTOR_INT degrees2, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, REALIZE_DEGSEQ_METHOD method=SMALLEST + +igraph_circulant: + PARAMS: OUT GRAPH graph, INTEGER n, VECTOR_INT shifts, BOOLEAN directed=False + +igraph_generalized_petersen: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER k + +igraph_turan: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT INDEX_VECTOR types, INTEGER n, INTEGER r + +####################################### +# Constructors, games +####################################### + +igraph_barabasi_game: + PARAMS: |- + OUT GRAPH graph, INTEGER n, REAL power=1.0, INTEGER m=1, + OPTIONAL VECTOR_INT outseq, BOOLEAN outpref=False, REAL A=1.0, + BOOLEAN directed=True, BARABASI_ALGORITHM algo=BAG, + OPTIONAL GRAPH start_from + +igraph_erdos_renyi_game_gnp: + PARAMS: |- + OUT GRAPH graph, INTEGER n, REAL p, BOOLEAN directed=False, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, + BOOLEAN edge_labeled=False + +igraph_erdos_renyi_game_gnm: + PARAMS: |- + OUT GRAPH graph, INTEGER n, INTEGER m, BOOLEAN directed=False, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, + BOOLEAN edge_labeled=False + +igraph_iea_game: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER m, BOOLEAN directed=False, BOOLEAN loops=False + +igraph_degree_sequence_game: + PARAMS: |- + OUT GRAPH graph, VECTOR_INT out_deg, OPTIONAL VECTOR_INT in_deg, + DEGSEQ_MODE method=CONFIGURATION + +igraph_growing_random_game: + PARAMS: |- + OUT GRAPH graph, INTEGER n, INTEGER m=1, BOOLEAN directed=False, + BOOLEAN citation=False + +igraph_barabasi_aging_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER m=1, OPTIONAL VECTOR_INT outseq, + BOOLEAN outpref=False, REAL pa_exp=1.0, REAL aging_exp=0.0, INTEGER aging_bin=1, + REAL zero_deg_appeal=1.0, REAL zero_age_appeal=0.0, REAL deg_coef=1.0, + REAL age_coef=1.0, BOOLEAN directed=True + +igraph_recent_degree_game: + PARAMS: |- + OUT GRAPH graph, INTEGER n, REAL power=1.0, INTEGER window=1, + INTEGER m=1, OPTIONAL VECTOR_INT outseq, BOOLEAN outpref=False, + REAL zero_appeal=1.0, BOOLEAN directed=True + +igraph_recent_degree_aging_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER m=1, OPTIONAL VECTOR_INT outseq, + BOOLEAN outpref=False, REAL pa_exp=1.0, REAL aging_exp=0.0, INTEGER aging_bin=1, + INTEGER window=1, REAL zero_appeal=1.0, BOOLEAN directed=True + +igraph_callaway_traits_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER types, + INTEGER edges_per_step=1, VECTOR type_dist, MATRIX pref_matrix, + BOOLEAN directed=False, OPTIONAL OUT VECTOR_INT node_type_vec + +igraph_establishment_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER types, INTEGER k=1, + VECTOR type_dist, MATRIX pref_matrix, BOOLEAN directed=True, + OPTIONAL OUT VECTOR_INT node_type_vec + +igraph_grg_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, REAL radius, BOOLEAN torus=False, + OPTIONAL OUT VECTOR x, OPTIONAL OUT VECTOR y + +igraph_preference_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER types, + VECTOR type_dist, BOOLEAN fixed_sizes=False, + MATRIX pref_matrix, OUT VECTOR_INT node_type_vec, + BOOLEAN directed=False, BOOLEAN loops=False + +igraph_asymmetric_preference_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER out_types, INTEGER in_types, + MATRIX type_dist_matrix, MATRIX pref_matrix, + OUT VECTOR_INT node_type_out_vec, OUT VECTOR_INT node_type_in_vec, + BOOLEAN loops=False + +igraph_rewire_edges: + PARAMS: |- + INOUT GRAPH graph, REAL prob, + EDGE_TYPE_SW allowed_edge_types=SIMPLE + +igraph_rewire_directed_edges: + PARAMS: |- + INOUT GRAPH graph, REAL prob, BOOLEAN loops=False, + NEIMODE mode=OUT + +igraph_watts_strogatz_game: + PARAMS: |- + OUT GRAPH graph, INTEGER dim, INTEGER size, INTEGER nei, + REAL p, EDGE_TYPE_SW allowed_edge_types=SIMPLE + +igraph_lastcit_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INTEGER edges_per_node=1, + INTEGER agebins=1, VECTOR preference, BOOLEAN directed=True + +igraph_cited_type_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INDEX_VECTOR types, VECTOR pref, + INTEGER edges_per_step=1, BOOLEAN directed=True + +igraph_citing_cited_type_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, INDEX_VECTOR types, MATRIX pref, + INTEGER edges_per_step=1, BOOLEAN directed=True + +igraph_forest_fire_game: + PARAMS: |- + OUT GRAPH graph, INTEGER nodes, REAL fw_prob, REAL bw_factor=1, + INTEGER ambs=1, BOOLEAN directed=True + +igraph_simple_interconnected_islands_game: + PARAMS: |- + OUT GRAPH graph, INTEGER islands_n, INTEGER islands_size, + REAL islands_pin, INTEGER n_inter + +# Use a default of loops=True, as this is what's in the original Chung-Lu paper +igraph_chung_lu_game: + PARAMS: |- + OUT GRAPH graph, VECTOR out_weights, OPTIONAL VECTOR in_weights, + BOOLEAN loops=True, CHUNG_LU_VARIANT variant=ORIGINAL + +igraph_static_fitness_game: + PARAMS: |- + OUT GRAPH graph, INTEGER no_of_edges, VECTOR fitness_out, + OPTIONAL VECTOR fitness_in, + EDGE_TYPE_SW allowed_edge_types=SIMPLE + +igraph_static_power_law_game: + PARAMS: |- + OUT GRAPH graph, INTEGER no_of_nodes, INTEGER no_of_edges, + REAL exponent_out, REAL exponent_in=-1, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, + BOOLEAN finite_size_correction=True + +igraph_k_regular_game: + PARAMS: |- + OUT GRAPH graph, INTEGER no_of_nodes, INTEGER k, + BOOLEAN directed=False, BOOLEAN multiple=False + +igraph_sbm_game: + PARAMS: |- + OUT GRAPH graph, MATRIX pref_matrix, + VECTOR_INT block_sizes, BOOLEAN directed=False, + EDGE_TYPE_SW allowed_edge_types=SIMPLE + +igraph_hsbm_game: + INTERNAL: true + PARAMS: |- + OUT GRAPH graph, INTEGER n, INTEGER m, + VECTOR rho, MATRIX C, REAL p + +igraph_hsbm_list_game: + INTERNAL: true + PARAMS: |- + OUT GRAPH graph, INTEGER n, VECTOR_INT mlist, + VECTOR_LIST rholist, MATRIX_LIST Clist, REAL p + +igraph_correlated_game: + PARAMS: |- + OUT GRAPH new_graph, GRAPH old_graph, + REAL corr, REAL p=edge_density(old_graph), OPTIONAL INDEX_VECTOR permutation + +igraph_correlated_pair_game: + PARAMS: |- + OUT GRAPH graph1, OUT GRAPH graph2, INTEGER n, REAL corr, + REAL p, BOOLEAN directed=False, + OPTIONAL INDEX_VECTOR permutation + +igraph_dot_product_game: + PARAMS: OUT GRAPH graph, MATRIX vecs, BOOLEAN directed=False + +####################################### +# Basic query functions +####################################### + +igraph_are_adjacent: + PARAMS: GRAPH graph, VERTEX v1, VERTEX v2, OUT BOOLEAN res + DEPS: v1 ON graph, v2 ON graph + +####################################### +# Structural properties +####################################### + +igraph_diameter: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT REAL res, OUT INTEGER from, OUT INTEGER to, + OPTIONAL OUT VECTOR_INT vertex_path, + OPTIONAL OUT VECTOR_INT edge_path, + BOOLEAN directed=True, BOOLEAN unconnected=True + DEPS: weights ON graph + +igraph_closeness: + PARAMS: |- + GRAPH graph, OUT VERTEX_QTY res, + OPTIONAL OUT VECTOR_INT reachable_count, + OPTIONAL OUT BOOLEAN all_reachable, + VERTEX_SELECTOR vids=ALL, + NEIMODE mode=OUT, OPTIONAL EDGE_WEIGHTS weights, + BOOLEAN normalized=False + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_closeness_cutoff: + PARAMS: |- + GRAPH graph, OUT VERTEX_QTY res, + OPTIONAL OUT VECTOR_INT reachable_count, + OPTIONAL OUT BOOLEAN all_reachable, + VERTEX_SELECTOR vids=ALL, + NEIMODE mode=OUT, OPTIONAL EDGE_WEIGHTS weights, + BOOLEAN normalized=False, REAL cutoff=UNLIMITED + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_distances: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT MATRIX res, + VERTEX_SELECTOR from=ALL, VERTEX_SELECTOR to=ALL, NEIMODE mode=OUT + DEPS: weights ON graph, from ON graph, to ON graph + +igraph_distances_cutoff: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT MATRIX res, + VERTEX_SELECTOR from=ALL, VERTEX_SELECTOR to=ALL, NEIMODE mode=OUT, REAL cutoff=UNLIMITED + DEPS: weights ON graph, from ON graph, to ON graph + +igraph_get_shortest_path: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + VERTEX from, VERTEX to, NEIMODE mode=OUT + DEPS: weights ON graph, from ON graph, to ON graph, vertices ON graph, edges ON graph + +igraph_get_shortest_path_bellman_ford: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + VERTEX from, VERTEX to, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph, vertices ON graph, edges ON graph + +igraph_get_shortest_path_dijkstra: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + VERTEX from, VERTEX to, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph, vertices ON graph, edges ON graph + +igraph_get_shortest_path_astar: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + VERTEX from, VERTEX to, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, + OPTIONAL ASTAR_HEURISTIC_FUNC heuristic, OPTIONAL EXTRA extra + DEPS: from ON graph, to ON graph, weights ON graph, vertices ON graph, edges ON graph + +igraph_get_shortest_paths: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT VERTEX_INDICES_LIST vertices, OPTIONAL OUT EDGE_INDICES_LIST edges, + VERTEX from, VERTEX_SELECTOR to=ALL, NEIMODE mode=OUT, + OPTIONAL OUT VECTOR_INT parents, + OPTIONAL OUT VECTOR_INT inbound_edges + DEPS: weights ON graph, edges ON graph, from ON graph, to ON graph + +igraph_get_all_shortest_paths: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT VERTEX_INDICES_LIST vertices, OPTIONAL OUT EDGE_INDICES_LIST edges, + OPTIONAL OUT VECTOR_INT nrgeo, VERTEX from, VERTEX_SELECTOR to, NEIMODE mode=OUT + DEPS: weights ON graph, edges ON graph, from ON graph, to ON graph + +igraph_distances_dijkstra: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_distances_dijkstra_cutoff: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, REAL cutoff=UNLIMITED + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_get_shortest_paths_dijkstra: + PARAMS: |- + GRAPH graph, OPTIONAL OUT VERTEX_INDICES_LIST vertices, + OPTIONAL OUT EDGE_INDICES_LIST edges, VERTEX from, VERTEX_SELECTOR to=ALL, + OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, + OPTIONAL OUT VECTOR_INT parents, + OPTIONAL OUT VECTOR_INT inbound_edges + DEPS: |- + vertices ON graph, edges ON graph, from ON graph, to ON graph, + weights ON graph + +igraph_get_shortest_paths_bellman_ford: + PARAMS: |- + GRAPH graph, OPTIONAL OUT VERTEX_INDICES_LIST vertices, + OPTIONAL OUT EDGE_INDICES_LIST edges, VERTEX from, VERTEX_SELECTOR to=ALL, + OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, + OPTIONAL OUT VECTOR_INT parents, + OPTIONAL OUT VECTOR_INT inbound_edges + DEPS: |- + vertices ON graph, edges ON graph, from ON graph, to ON graph, + weights ON graph + +igraph_get_all_shortest_paths_dijkstra: + PARAMS: |- + GRAPH graph, OPTIONAL OUT VERTEX_INDICES_LIST vertices, + OPTIONAL OUT EDGE_INDICES_LIST edges, OPTIONAL OUT VECTOR_INT nrgeo, + VERTEX from, VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, + NEIMODE mode=OUT + DEPS: |- + weights ON graph, from ON graph, to ON graph, vertices ON graph, edges ON graph + +igraph_distances_bellman_ford: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_distances_johnson: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_distances_floyd_warshall: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, + FWALGORITHM method=AUTOMATIC + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_voronoi: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT membership, OUT VECTOR distances, + VERTEX_INDICES generators, OPTIONAL EDGE_WEIGHTS weights, NEIMODE mode=OUT, VORONOI_TIEBREAKER tiebreaker=RANDOM + DEPS: weights ON graph, generators ON graph + +igraph_get_all_simple_paths: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, VERTEX from, + VERTEX_SELECTOR to=ALL, NEIMODE mode=OUT, + INTEGER minlen=UNLIMITED, INTEGER maxlen=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: from ON graph, to ON graph, res ON graph + +igraph_get_k_shortest_paths: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT VERTEX_INDICES_LIST vertex_paths, + OPTIONAL OUT EDGE_INDICES_LIST edge_paths, + INTEGER k, VERTEX from, VERTEX to, NEIMODE mode=OUT + DEPS: |- + from ON graph, to ON graph, weights ON graph, vertex_paths ON graph, edge_paths ON graph + +igraph_get_widest_path: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + VERTEX from, VERTEX to, EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: |- + from ON graph, to ON graph, weights ON graph, vertices ON graph, edges ON graph + +igraph_get_widest_paths: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES_LIST vertices, OPTIONAL OUT EDGE_INDICES_LIST edges, + VERTEX from, VERTEX_SELECTOR to=ALL, EDGE_WEIGHTS weights, + NEIMODE mode=OUT, OPTIONAL OUT VECTOR_INT parents, + OPTIONAL OUT VECTOR_INT inbound_edges + DEPS: |- + from ON graph, to ON graph, weights ON graph, vertices ON graph, + edges ON graph + +igraph_widest_path_widths_dijkstra: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_widest_path_widths_floyd_warshall: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, + VERTEX_SELECTOR to=ALL, EDGE_WEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + +igraph_spanner: + PARAMS: |- + GRAPH graph, OUT EDGE_INDICES spanner, REAL stretch, OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_subcomponent: + PARAMS: GRAPH graph, OUT VERTEX_INDICES res, VERTEX vid, NEIMODE mode=ALL + DEPS: vid ON graph, res ON graph + +igraph_betweenness: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_QTY res, + VERTEX_SELECTOR vids=ALL, BOOLEAN directed=True, BOOLEAN normalized=False + DEPS: weights ON graph, vids ON graph, res ON graph vids + +igraph_betweenness_cutoff: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_QTY res, + VERTEX_SELECTOR vids=ALL, BOOLEAN directed=True, BOOLEAN normalized=False, + REAL cutoff=UNLIMITED + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_betweenness_subset: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_QTY res, + VERTEX_SELECTOR vids=ALL, VERTEX_SELECTOR sources=ALL, VERTEX_SELECTOR targets=ALL, + BOOLEAN directed=True, BOOLEAN normalized=False + DEPS: |- + vids ON graph, weights ON graph, res ON graph vids, sources ON graph, targets ON graph + +igraph_edge_betweenness: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VECTOR res, EDGE_SELECTOR eids=ALL, + BOOLEAN directed=True, BOOLEAN normalized=False + DEPS: eids ON graph, weights ON graph, res ON graph eids + +igraph_edge_betweenness_cutoff: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VECTOR res, EDGE_SELECTOR eids=ALL, + BOOLEAN directed=True, BOOLEAN normalized=False, REAL cutoff=UNLIMITED + DEPS: eids ON graph, weights ON graph, res ON graph eids + +igraph_edge_betweenness_subset: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VECTOR res, + VERTEX_SELECTOR sources=ALL, VERTEX_SELECTOR targets=ALL, EDGE_SELECTOR eids=ALL, + BOOLEAN directed=True, BOOLEAN normalized=False + DEPS: |- + eids ON graph, weights ON graph, res ON graph eids, sources ON graph, targets ON graph + +igraph_harmonic_centrality: + PARAMS: |- + GRAPH graph, OUT VERTEX_QTY res, VERTEX_SELECTOR vids=ALL, + NEIMODE mode=OUT, OPTIONAL EDGE_WEIGHTS weights, BOOLEAN normalized=False + DEPS: weights ON graph, vids ON graph, res ON graph vids + +igraph_harmonic_centrality_cutoff: + PARAMS: |- + GRAPH graph, OUT VERTEX_QTY res, VERTEX_SELECTOR vids=ALL, + NEIMODE mode=OUT, OPTIONAL EDGE_WEIGHTS weights, BOOLEAN normalized=False, + REAL cutoff=UNLIMITED + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_pagerank: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VERTEX_QTY vector, OUT REAL value, + REAL damping=0.85, BOOLEAN directed=True, + VERTEX_SELECTOR vids=ALL, + PAGERANKALGO algo=PRPACK, + OPTIONAL INOUT PAGERANKOPT options + DEPS: |- + vids ON graph, weights ON graph, vector ON graph vids, + options ON algo + +igraph_personalized_pagerank: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VERTEX_QTY vector, OUT REAL value, + OPTIONAL VECTOR reset, + REAL damping=0.85, BOOLEAN directed=True, + VERTEX_SELECTOR vids=ALL, + PAGERANKALGO algo=PRPACK, + OPTIONAL INOUT PAGERANKOPT options + DEPS: |- + vids ON graph, weights ON graph, vector ON graph vids, + options ON algo + +igraph_personalized_pagerank_vs: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + PRIMARY OUT VERTEX_QTY vector, OUT REAL value, + VERTEX_SELECTOR reset_vids, + REAL damping=0.85, BOOLEAN directed=True, + VERTEX_SELECTOR vids=ALL, + PAGERANKALGO algo=PRPACK, + OPTIONAL INOUT PAGERANKOPT options + DEPS: |- + vids ON graph, weights ON graph, vector ON graph vids, + options ON algo + +igraph_rewire: + PARAMS: INOUT GRAPH rewire, INTEGER n, EDGE_TYPE_SW allowed_edge_types=SIMPLE, OPTIONAL OUT REWIRING_STATS stats + +igraph_induced_subgraph: + PARAMS: GRAPH graph, OUT GRAPH res, VERTEX_SELECTOR vids, SUBGRAPH_IMPL impl=AUTO + DEPS: vids ON graph + +igraph_subgraph_from_edges: + PARAMS: GRAPH graph, OUT GRAPH res, EDGE_SELECTOR eids, BOOLEAN delete_vertices=True + DEPS: eids ON graph + +igraph_reverse_edges: + PARAMS: INOUT GRAPH graph, EDGE_SELECTOR eids=ALL + DEPS: eids ON graph + +igraph_average_path_length: + PARAMS: GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, PRIMARY OUT REAL res, + OPTIONAL OUT REAL unconn_pairs, BOOLEAN directed=True, BOOLEAN unconn=True + DEPS: weights ON graph + +igraph_path_length_hist: + PARAMS: |- + GRAPH graph, OUT VECTOR res, OUT REAL unconnected, + BOOLEAN directed=True + +igraph_simplify: + PARAMS: |- + INOUT GRAPH graph, BOOLEAN remove_multiple=True, + BOOLEAN remove_loops=True, + EDGE_ATTRIBUTE_COMBINATION edge_attr_comb=Default + +igraph_transitivity_undirected: + PARAMS: GRAPH graph, OUT REAL res, TRANSITIVITY_MODE mode=NAN + +igraph_transitivity_local_undirected: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEX_SELECTOR vids=ALL, TRANSITIVITY_MODE mode=NAN + DEPS: vids ON graph + +igraph_transitivity_avglocal_undirected: + PARAMS: GRAPH graph, OUT REAL res, TRANSITIVITY_MODE mode=NAN + +igraph_transitivity_barrat: + PARAMS: |- + GRAPH graph, OUT VECTOR res, VERTEX_SELECTOR vids=ALL, + OPTIONAL EDGE_WEIGHTS weights, TRANSITIVITY_MODE mode=NAN + DEPS: res ON graph, vids ON graph, weights ON graph + +igraph_ecc: + PARAMS: |- + GRAPH graph, OUT VECTOR res, EDGE_SELECTOR eids=ALL, + INTEGER k=3, BOOLEAN offset=False, BOOLEAN normalize=True + DEPS: res ON graph, eids ON graph + +igraph_reciprocity: + PARAMS: |- + GRAPH graph, OUT REAL res, BOOLEAN ignore_loops=True, + RECIP mode=DEFAULT + +igraph_constraint: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEX_SELECTOR vids=ALL, OPTIONAL EDGE_WEIGHTS weights + DEPS: vids ON graph, weights ON graph + +igraph_maxdegree: + PARAMS: |- + GRAPH graph, OUT INTEGER res, VERTEX_SELECTOR vids=ALL, NEIMODE mode=ALL, + LOOPS loops=TWICE + DEPS: vids ON graph + +igraph_density: + PARAMS: GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT REAL res, BOOLEAN loops=False + DEPS: weights ON graph + +igraph_mean_degree: + PARAMS: GRAPH graph, OUT REAL res, BOOLEAN loops=True + +igraph_neighborhood_size: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT res, VERTEX_SELECTOR vids, INTEGER order, + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS: vids ON graph + +igraph_neighborhood: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, + VERTEX_SELECTOR vids, INTEGER order, + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS: res ON graph, vids ON graph + +igraph_neighborhood_graphs: + PARAMS: |- + GRAPH graph, OUT GRAPH_LIST res, VERTEX_SELECTOR vids, + INTEGER order, + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS: vids ON graph + +igraph_topological_sorting: + PARAMS: GRAPH graph, OUT VECTOR_INT res, NEIMODE mode=OUT + +igraph_feedback_arc_set: + # Default algorithm is the approximate method because it is faster and the + # function is _not_ called igraph_minimum_feedback_arc_set + PARAMS: GRAPH graph, OUT EDGE_INDICES result, OPTIONAL EDGE_WEIGHTS weights, FAS_ALGORITHM algo=APPROX_EADES + DEPS: result ON graph, weights ON graph + +igraph_feedback_vertex_set: + PARAMS: GRAPH graph, OUT VERTEX_INDICES result, OPTIONAL VERTEX_WEIGHTS weights, FVS_ALGORITHM algo=EXACT_IP + DEPS: result ON graph, weights ON graph + +igraph_is_loop: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGE_SELECTOR es=ALL + DEPS: es ON graph + +igraph_is_dag: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_is_acyclic: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_is_simple: + PARAMS: GRAPH graph, OUT BOOLEAN res, BOOLEAN directed=True + +igraph_is_multiple: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGE_SELECTOR es=ALL + DEPS: es ON graph + +igraph_has_loop: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_has_multiple: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_count_loops: + PARAMS: GRAPH graph, OUT INTEGER loop_count + +igraph_count_multiple: + PARAMS: GRAPH graph, OUT VECTOR_INT res, EDGE_SELECTOR es=ALL + DEPS: es ON graph + +igraph_girth: + PARAMS: GRAPH graph, OUT REAL girth, OUT VERTEX_INDICES cycle + DEPS: cycle ON graph + +igraph_is_perfect: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_add_edge: + PARAMS: INOUT GRAPH graph, INTEGER from, INTEGER to + +igraph_eigenvector_centrality: + PARAMS: |- + GRAPH graph, OUT ALL_VERTEX_QTY vector, OUT REAL value, + NEIMODE mode=OUT, + OPTIONAL EDGE_WEIGHTS weights, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS + DEPS: weights ON graph, vector ON graph + +igraph_hub_and_authority_scores: + PARAMS: |- + GRAPH graph, OUT ALL_VERTEX_QTY hub_vector, OUT ALL_VERTEX_QTY authority_vector, + OUT REAL value, OPTIONAL EDGE_WEIGHTS weights, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS + DEPS: weights ON graph, hub_vector ON graph, authority_vector ON graph + +igraph_unfold_tree: + PARAMS: |- + GRAPH graph, OUT GRAPH tree, NEIMODE mode=ALL, VECTOR_INT roots, + OPTIONAL OUT INDEX_VECTOR vertex_index + +igraph_is_mutual: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGE_SELECTOR es=ALL, BOOLEAN loops=True + DEPS: es ON graph + +igraph_has_mutual: + PARAMS: GRAPH graph, OUT BOOLEAN res, BOOLEAN loops=True + +igraph_maximum_cardinality_search: + PARAMS: GRAPH graph, OPTIONAL OUT INDEX_VECTOR alpha, OPTIONAL OUT VERTEX_INDICES alpham1 + DEPS: alpham1 ON graph + +igraph_is_chordal: + PARAMS: |- + GRAPH graph, OPTIONAL INDEX_VECTOR alpha, OPTIONAL VERTEX_INDICES alpham1, + OPTIONAL OUT BOOLEAN chordal, OPTIONAL OUT VECTOR_INT fillin, + OPTIONAL OUT GRAPH newgraph + DEPS: alpham1 ON graph + +igraph_avg_nearest_neighbor_degree: + PARAMS: |- + GRAPH graph, VERTEX_SELECTOR vids=ALL, + NEIMODE mode=ALL, NEIMODE neighbor_degree_mode=ALL, + OPTIONAL OUT VERTEX_QTY knn, OPTIONAL OUT VECTOR knnk, + OPTIONAL EDGE_WEIGHTS weights + DEPS: vids ON graph, weights ON graph, knn ON graph vids + +igraph_degree_correlation_vector: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VECTOR knnk, + NEIMODE from_mode=OUT, NEIMODE to_mode=IN, + BOOLEAN directed_neighbors=True + DEPS: weights ON graph + +igraph_rich_club_sequence: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VECTOR res, + INDEX_VECTOR vertex_order, + BOOLEAN normalized=True, + BOOLEAN loops=False, + BOOLEAN directed=True + DEPS: weights ON graph + +igraph_strength: + PARAMS: |- + GRAPH graph, OUT VERTEX_QTY res, VERTEX_SELECTOR vids=ALL, + NEIMODE mode=ALL, LOOPS loops=TWICE, OPTIONAL EDGE_WEIGHTS weights + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_centralization: + PARAMS: VECTOR scores, REAL theoretical_max=0, BOOLEAN normalized=True + RETURN: REAL + +igraph_centralization_degree: + PARAMS: |- + GRAPH graph, OUT VECTOR res, + NEIMODE mode=ALL, LOOPS loops=TWICE, + OUT REAL centralization, OUT REAL theoretical_max, + BOOLEAN normalized=True + +igraph_centralization_degree_tmax: + # The general consensus is that the 'loops' argument of this function + # should not have a default value; see this comment from @torfason: + # https://github.com/igraph/rigraph/issues/369#issuecomment-939893681 + PARAMS: |- + OPTIONAL GRAPH graph, INTEGER nodes=0, NEIMODE mode=ALL, + LOOPS loops, OUT REAL res + +igraph_centralization_betweenness: + PARAMS: |- + GRAPH graph, OUT VECTOR res, + BOOLEAN directed=True, + OUT REAL centralization, + OUT REAL theoretical_max, + BOOLEAN normalized=True + +igraph_centralization_betweenness_tmax: + PARAMS: |- + OPTIONAL GRAPH graph, INTEGER nodes=0, + BOOLEAN directed=True, OUT REAL res + +igraph_centralization_closeness: + PARAMS: |- + GRAPH graph, OUT VECTOR res, + NEIMODE mode=OUT, OUT REAL centralization, + OUT REAL theoretical_max, + BOOLEAN normalized=True + +igraph_centralization_closeness_tmax: + PARAMS: |- + OPTIONAL GRAPH graph, INTEGER nodes=0, + NEIMODE mode=OUT, OUT REAL res + +igraph_centralization_eigenvector_centrality: + PARAMS: |- + GRAPH graph, OUT VECTOR vector, OUT REAL value, + NEIMODE mode=OUT, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS, + OUT REAL centralization, OUT REAL theoretical_max, + BOOLEAN normalized=True + +igraph_centralization_eigenvector_centrality_tmax: + PARAMS: |- + OPTIONAL GRAPH graph, INTEGER nodes=0, + NEIMODE mode=OUT, + OUT REAL res + +igraph_assortativity_nominal: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + INDEX_VECTOR types, + OUT REAL res, + BOOLEAN directed=True, BOOLEAN normalized=True + DEPS: weights ON graph, types ON graph + +igraph_assortativity: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + VECTOR values, OPTIONAL VECTOR values_in, + OUT REAL res, BOOLEAN directed=True, BOOLEAN normalized=True + DEPS: weights ON graph, values ON graph, values_in ON graph + +igraph_assortativity_degree: + PARAMS: GRAPH graph, OUT REAL res, BOOLEAN directed=True + +igraph_joint_degree_matrix: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT MATRIX jdm, + INTEGER max_out_degree=UNLIMITED, INTEGER max_in_degree=UNLIMITED + DEPS: weights ON graph + +igraph_joint_degree_distribution: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT MATRIX p, + NEIMODE from_mode=OUT, NEIMODE to_mode=IN, + BOOLEAN directed_neighbors=True, + BOOLEAN normalized=True, + INTEGER max_from_degree=UNLIMITED, INTEGER max_to_degree=UNLIMITED + DEPS: weights ON graph + +igraph_joint_type_distribution: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT MATRIX p, + INDEX_VECTOR from_types, OPTIONAL INDEX_VECTOR to_types, + BOOLEAN directed=True, + BOOLEAN normalized=True + DEPS: weights ON graph + +igraph_contract_vertices: + PARAMS: |- + INOUT GRAPH graph, INDEX_VECTOR mapping, + VERTEX_ATTRIBUTE_COMBINATION vertex_attr_comb=Default + +igraph_eccentricity: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VERTEX_QTY res, VERTEX_SELECTOR vids=ALL, + NEIMODE mode=ALL + DEPS: weights ON graph, vids ON graph, res ON graph vids + +igraph_graph_center: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_INDICES res, NEIMODE mode=ALL + DEPS: weights ON graph, res ON graph + +igraph_radius: + PARAMS: GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT REAL radius, NEIMODE mode=ALL + DEPS: weights ON graph + +igraph_pseudo_diameter: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT REAL diameter, VERTEX start_vid, + OPTIONAL OUT INTEGER from, OPTIONAL OUT INTEGER to, + BOOLEAN directed=True, BOOLEAN unconnected=True + DEPS: weights ON graph, start_vid ON graph + +igraph_diversity: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_QTY res, + VERTEX_SELECTOR vids=ALL + DEPS: weights ON graph, vids ON graph, res ON graph vids + +igraph_random_walk: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_INDICES vertices, OUT EDGE_INDICES edges, + VERTEX start, NEIMODE mode=OUT, INTEGER steps, RWSTUCK stuck=RETURN + DEPS: start ON graph, weights ON graph, vertices ON graph, edges ON graph + +igraph_global_efficiency: + PARAMS: GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT REAL res, BOOLEAN directed=True + DEPS: weights ON graph + +igraph_local_efficiency: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT VERTEX_QTY res, + VERTEX_SELECTOR vids=ALL, BOOLEAN directed=True, NEIMODE mode=ALL + DEPS: vids ON graph, weights ON graph, res ON graph vids + +igraph_average_local_efficiency: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT REAL res, + BOOLEAN directed=True, NEIMODE mode=ALL + DEPS: weights ON graph + +igraph_transitive_closure: + PARAMS: GRAPH graph, OUT GRAPH closure + +igraph_trussness: + PARAMS: GRAPH graph, OUT VECTOR_INT trussness + +####################################### +# Degree sequences +####################################### + +igraph_is_bigraphical: + PARAMS: |- + VECTOR_INT degrees1, VECTOR_INT degrees2, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, OUT BOOLEAN res + +igraph_is_graphical: + PARAMS: |- + VECTOR_INT out_deg, OPTIONAL VECTOR_INT in_deg, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, OUT BOOLEAN res + +####################################### +# Visitors +####################################### + +igraph_bfs: + PARAMS: |- + GRAPH graph, VERTEX root, OPTIONAL VERTEX_INDICES roots, + NEIMODE mode=OUT, BOOLEAN unreachable, + VERTEX_INDICES restricted, + OUT VERTEX_INDICES order, OUT VECTOR_INT rank, + OUT VECTOR_INT parents, + OUT VECTOR_INT pred, OUT VECTOR_INT succ, + OUT VECTOR_INT dist, OPTIONAL BFS_FUNC callback, OPTIONAL EXTRA extra + DEPS: root ON graph, roots ON graph, restricted ON graph, order ON graph + +igraph_bfs_simple: + PARAMS: |- + GRAPH graph, VERTEX root, + NEIMODE mode=OUT, + OUT VERTEX_INDICES order, + OUT VECTOR_INT layers, + OUT VECTOR_INT parents + DEPS: root ON graph, order ON graph + +igraph_dfs: + PARAMS: |- + GRAPH graph, VERTEX root, NEIMODE mode=OUT, BOOLEAN unreachable, + OUT VERTEX_INDICES order, OUT VERTEX_INDICES order_out, + OUT VECTOR_INT father, OUT VECTOR_INT dist, + OPTIONAL DFS_FUNC in_callback, OPTIONAL DFS_FUNC out_callback, OPTIONAL EXTRA extra + DEPS: root ON graph, order ON graph, order_out ON graph + +####################################### +# Bipartite graphs +####################################### + +igraph_bipartite_projection_size: + PARAMS: |- + GRAPH graph, BIPARTITE_TYPES types, + OUT INTEGER vcount1, OUT INTEGER ecount1, + OUT INTEGER vcount2, OUT INTEGER ecount2 + DEPS: types ON graph + +igraph_bipartite_projection: + PARAMS: |- + GRAPH graph, BIPARTITE_TYPES types, + OUT GRAPH proj1, OUT GRAPH proj2, + OPTIONAL OUT VECTOR_INT multiplicity1, + OPTIONAL OUT VECTOR_INT multiplicity2, INTEGER probe1=-1 + DEPS: types ON graph + +igraph_create_bipartite: + PARAMS: |- + OUT GRAPH graph, IN BIPARTITE_TYPES types, + VECTOR_INT edges, BOOLEAN directed=False + +igraph_biadjacency: + PARAMS: |- + OUT GRAPH graph, OUT BIPARTITE_TYPES types, MATRIX biadjmatrix, + BOOLEAN directed=False, NEIMODE mode=ALL, + BOOLEAN multiple=False + +igraph_weighted_biadjacency: + PARAMS: |- + OUT GRAPH graph, + OUT BIPARTITE_TYPES types, OUT EDGE_WEIGHTS weights, + MATRIX biadjmatrix, + BOOLEAN directed=False, NEIMODE mode=ALL + DEPS: |- + weights ON graph, biadjmatrix ON graph, types ON graph + +igraph_get_biadjacency: + PARAMS: |- + GRAPH graph, BIPARTITE_TYPES types, OPTIONAL EDGE_WEIGHTS weights, + OUT MATRIX res, + OPTIONAL OUT INDEX_VECTOR row_ids, OPTIONAL OUT INDEX_VECTOR col_ids + DEPS: types ON graph, weights ON graph + +igraph_is_bipartite: + PARAMS: GRAPH graph, OUT BOOLEAN res, OPTIONAL OUT BIPARTITE_TYPES types + +igraph_bipartite_game_gnp: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT BIPARTITE_TYPES types, + INTEGER n1, INTEGER n2, REAL p, + BOOLEAN directed=False, NEIMODE mode=ALL, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, BOOLEAN edge_labeled=False + +igraph_bipartite_game_gnm: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT BIPARTITE_TYPES types, + INTEGER n1, INTEGER n2, INTEGER m, + BOOLEAN directed=False, NEIMODE mode=ALL, + EDGE_TYPE_SW allowed_edge_types=SIMPLE, BOOLEAN edge_labeled=False + +igraph_bipartite_iea_game: + PARAMS: |- + OUT GRAPH graph, OPTIONAL OUT BIPARTITE_TYPES types, + INTEGER n1, INTEGER n2, INTEGER m, + BOOLEAN directed=False, NEIMODE mode=ALL + DEPS: |- + types ON graph + +####################################### +# Spectral properties +####################################### + +igraph_get_laplacian: + PARAMS: |- + GRAPH graph, OUT MATRIX res, NEIMODE mode=OUT, + LAPLACIAN_NORMALIZATION normalization=UNNORMALIZED, OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_get_laplacian_sparse: + PARAMS: |- + GRAPH graph, OUT SPARSEMAT sparseres, NEIMODE mode=OUT, + LAPLACIAN_NORMALIZATION normalization=UNNORMALIZED, OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +####################################### +# Components +####################################### + +igraph_connected_components: + PARAMS: |- + GRAPH graph, PRIMARY OUT VECTOR_INT membership, OUT VECTOR_INT csize, + OUT INTEGER no, CONNECTEDNESS mode=WEAK + +igraph_is_connected: + PARAMS: GRAPH graph, OUT BOOLEAN res, CONNECTEDNESS mode=WEAK + +igraph_decompose: + PARAMS: |- + GRAPH graph, OUT GRAPH_LIST components, CONNECTEDNESS mode=WEAK, + INTEGER maxcompno=UNLIMITED, INTEGER minelements=1 + +igraph_articulation_points: + PARAMS: GRAPH graph, OUT VERTEX_INDICES res + DEPS: res ON graph + +igraph_biconnected_components: + PARAMS: |- + GRAPH graph, OUT INTEGER no, + OPTIONAL OUT EDGE_INDICES_LIST tree_edges, + OPTIONAL OUT EDGE_INDICES_LIST component_edges, + OPTIONAL OUT VERTEX_INDICES_LIST components, + OUT VERTEX_INDICES articulation_points + DEPS: |- + tree_edges ON graph, component_edges ON graph, + components ON graph, articulation_points ON graph + +igraph_bridges: + PARAMS: GRAPH graph, OUT EDGE_INDICES res + DEPS: res ON graph + +igraph_is_biconnected: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_count_reachable: + PARAMS: GRAPH graph, OUT VECTOR_INT counts, NEIMODE mode=OUT + +####################################### +# Percolation +####################################### + +igraph_bond_percolation: + PARAMS: GRAPH graph, OUT VECTOR_INT giant_size, OUT VECTOR_INT vetex_count, OPTIONAL EDGE_INDICES edge_order + DEPS: edge_order ON graph + +igraph_site_percolation: + PARAMS: GRAPH graph, OUT VECTOR_INT giant_size, OUT VECTOR_INT edge_count, OPTIONAL VERTEX_INDICES vertex_order + DEPS: vertex_order ON graph + +igraph_edgelist_percolation: + PARAMS: VERTEX_INDEX_PAIRS edges, OUT VECTOR_INT giant_size, OUT VECTOR_INT vertex_count + +####################################### +# Cliques +####################################### + +igraph_is_clique: + PARAMS: |- + GRAPH graph, VERTEX_SELECTOR candidate, BOOLEAN directed=False, + OUT BOOLEAN res + DEPS: candidate ON graph + +igraph_cliques: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: res ON graph + +igraph_cliques_callback: + PARAMS: |- + GRAPH graph, INTEGER min_size=0, INTEGER max_size=0, + CLIQUE_FUNC cliquehandler_fn, OPTIONAL EXTRA arg + +igraph_clique_size_hist: + PARAMS: |- + GRAPH graph, OUT VECTOR hist, INTEGER min_size=0, INTEGER max_size=0 + +igraph_largest_cliques: + PARAMS: GRAPH graph, OUT VERTEX_INDICES_LIST res + DEPS: res ON graph + +igraph_maximal_cliques: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: res ON graph + +igraph_maximal_cliques_subset: + PARAMS: |- + GRAPH graph, VERTEX_INDICES subset, PRIMARY OUT VERTEX_INDICES_LIST res, + OUT INTEGER no, OPTIONAL OUTFILE outfile, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: subset ON graph, res ON graph + +igraph_maximal_cliques_callback: + PARAMS: |- + GRAPH graph, + INTEGER min_size=0, INTEGER max_size=0, + CLIQUE_FUNC cliquehandler_fn, OPTIONAL EXTRA arg + +igraph_maximal_cliques_count: + PARAMS: |- + GRAPH graph, OUT INTEGER no, INTEGER min_size=0, INTEGER max_size=0 + +igraph_maximal_cliques_file: + PARAMS: |- + GRAPH graph, OUTFILE res, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + +igraph_maximal_cliques_hist: + PARAMS: |- + GRAPH graph, OUT VECTOR hist, INTEGER min_size=0, INTEGER max_size=0 + +igraph_clique_number: + PARAMS: GRAPH graph, OUT INTEGER no + +igraph_weighted_cliques: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_WEIGHTS vertex_weights, OUT VERTEX_INDICES_LIST res, + BOOLEAN maximal=False, + REAL min_weight=UNLIMITED, REAL max_weight=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: vertex_weights ON graph, res ON graph + +igraph_largest_weighted_cliques: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_WEIGHTS vertex_weights, OUT VERTEX_INDICES_LIST res + DEPS: vertex_weights ON graph, res ON graph + +igraph_weighted_clique_number: + PARAMS: GRAPH graph, OPTIONAL VERTEX_WEIGHTS vertex_weights, OUT REAL res + DEPS: vertex_weights ON graph + +igraph_is_independent_vertex_set: + PARAMS: GRAPH graph, VERTEX_SELECTOR candidate, OUT BOOLEAN res + DEPS: candidate ON graph + +igraph_independent_vertex_sets: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: res ON graph + +igraph_largest_independent_vertex_sets: + PARAMS: GRAPH graph, OUT VERTEX_INDICES_LIST res + DEPS: res ON graph + +igraph_maximal_independent_vertex_sets: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST res, + INTEGER min_size=UNLIMITED, INTEGER max_size=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: res ON graph + +igraph_independence_number: + PARAMS: GRAPH graph, OUT INTEGER no + +####################################### +# Layouts +####################################### + +igraph_layout_random: + PARAMS: GRAPH graph, OUT MATRIX res + +igraph_layout_circle: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR order=ALL + DEPS: order ON graph + +igraph_layout_star: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX center=V(graph)[1], + OPTIONAL INDEX_VECTOR order + DEPS: center ON graph + +igraph_layout_grid: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER width=0 + +igraph_layout_grid_3d: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER width=0, INTEGER height=0 + +igraph_layout_fruchterman_reingold: + PARAMS: |- + GRAPH graph, OPTIONAL INOUT MATRIX coords, + BOOLEAN use_seed=False, INTEGER niter=500, + REAL start_temp=sqrt(vcount(graph)), + LAYOUT_GRID grid=AUTO, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL VECTOR minx, OPTIONAL VECTOR maxx, + OPTIONAL VECTOR miny, OPTIONAL VECTOR maxy, + DEPRECATED coolexp, DEPRECATED maxdelta, DEPRECATED area, + DEPRECATED repulserad + DEPS: weights ON graph + +igraph_layout_kamada_kawai: + PARAMS: |- + GRAPH graph, INOUT MATRIX coords, BOOLEAN use_seed=False, + INTEGER maxiter=500, REAL epsilon=0.0, + REAL kkconst=vcount(graph), OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL VECTOR minx, OPTIONAL VECTOR maxx, + OPTIONAL VECTOR miny, OPTIONAL VECTOR maxy + DEPS: weights ON graph + +igraph_layout_lgl: + PARAMS: |- + GRAPH graph, OUT MATRIX res, INTEGER maxiter=150, REAL maxdelta=VCOUNT(graph), + REAL area=VCOUNT(graph)^2, REAL coolexp=1.5, REAL repulserad=VCOUNT(graph)^3, REAL cellsize=VCOUNT(graph), + INTEGER root=-1 + +igraph_layout_reingold_tilford: + PARAMS: |- + GRAPH graph, OUT MATRIX res, NEIMODE mode=OUT, + OPTIONAL VERTEX_INDICES roots, OPTIONAL VECTOR_INT rootlevel + DEPS: roots ON graph + +igraph_layout_reingold_tilford_circular: + PARAMS: |- + GRAPH graph, OUT MATRIX res, NEIMODE mode=OUT, + OPTIONAL VERTEX_INDICES roots, OPTIONAL VECTOR_INT rootlevel + DEPS: roots ON graph + +igraph_roots_for_tree_layout: + PARAMS: |- + GRAPH graph, NEIMODE mode=OUT, OUT VERTEX_INDICES roots, ROOTCHOICE heuristic + DEPS: roots ON graph + +igraph_layout_random_3d: + PARAMS: GRAPH graph, OUT MATRIX res + +igraph_layout_sphere: + PARAMS: GRAPH graph, OUT MATRIX res + +igraph_layout_fruchterman_reingold_3d: + PARAMS: |- + GRAPH graph, OPTIONAL INOUT MATRIX coords, + BOOLEAN use_seed=False, INTEGER niter=500, + REAL start_temp=sqrt(vcount(graph)), + OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL VECTOR minx, OPTIONAL VECTOR maxx, + OPTIONAL VECTOR miny, OPTIONAL VECTOR maxy, + OPTIONAL VECTOR minz, OPTIONAL VECTOR maxz, + DEPRECATED coolexp, DEPRECATED maxdelta, DEPRECATED area, + DEPRECATED repulserad + DEPS: weights ON graph + +igraph_layout_kamada_kawai_3d: + PARAMS: |- + GRAPH graph, INOUT MATRIX coords, BOOLEAN use_seed=False, + INTEGER maxiter=500, REAL epsilon=0.0, + REAL kkconst=vcount(graph), OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL VECTOR minx, OPTIONAL VECTOR maxx, + OPTIONAL VECTOR miny, OPTIONAL VECTOR maxy, + OPTIONAL VECTOR minz, OPTIONAL VECTOR maxz + DEPS: weights ON graph + +igraph_layout_graphopt: + PARAMS: |- + GRAPH graph, INOUT MATRIX res, INTEGER niter=500, + REAL node_charge=0.001, REAL node_mass=30, + REAL spring_length=0, REAL spring_constant=1, + REAL max_sa_movement=5, BOOLEAN use_seed=False + +igraph_layout_drl: + PARAMS: |- + GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, + DRL_OPTIONS options=drl_defaults$default, OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_layout_drl_3d: + PARAMS: |- + GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, + DRL_OPTIONS options=drl_defaults$default, OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_layout_merge_dla: + PARAMS: GRAPH_PTR_LIST graphs, MATRIX_LIST coords, OUT MATRIX res + +igraph_layout_sugiyama: + PARAMS: |- + GRAPH graph, OUT MATRIX res, OPTIONAL OUT MATRIX_LIST routing, + OPTIONAL INDEX_VECTOR layers, + REAL hgap=1, REAL vgap=1, INTEGER maxiter=100, + OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_layout_mds: + PARAMS: |- + GRAPH graph, OUT MATRIX res, OPTIONAL MATRIX dist, INTEGER dim=2 + +igraph_layout_bipartite: + PARAMS: |- + GRAPH graph, BIPARTITE_TYPES types, OUT MATRIX res, + REAL hgap=1, REAL vgap=1, INTEGER maxiter=100 + DEPS: types ON graph + +igraph_layout_gem: + PARAMS: |- + GRAPH graph, INOUT MATRIX res=matrix(), + BOOLEAN use_seed=False, + INTEGER maxiter=40*vcount(graph)^2, + REAL temp_max=vcount(graph), + REAL temp_min=1/10, REAL temp_init=sqrt(vcount(graph)) + +igraph_layout_davidson_harel: + PARAMS: |- + GRAPH graph, INOUT MATRIX res=matrix(), + BOOLEAN use_seed=False, INTEGER maxiter=10, + INTEGER fineiter=FINEITER, REAL cool_fact=0.75, + REAL weight_node_dist=1.0, REAL weight_border=0.0, + REAL weight_edge_lengths=ELENW, + REAL weight_edge_crossings=ECROSSW, + REAL weight_node_edge_dist=NEDISTW + +igraph_layout_umap: + PARAMS: |- + GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, + OPTIONAL VECTOR distances, REAL min_dist=0.0, INTEGER epochs=200, + BOOLEAN distances_are_weights=False + +igraph_layout_umap_3d: + PARAMS: |- + GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, + OPTIONAL VECTOR distances, REAL min_dist=0.0, INTEGER epochs=200, + BOOLEAN distances_are_weights=False + +igraph_layout_umap_compute_weights: + PARAMS: |- + GRAPH graph, VECTOR distances, INOUT VECTOR weights + +igraph_layout_align: + PARAMS: GRAPH graph, INOUT MATRIX layout + +####################################### +# Cocitation and other similarity measures +####################################### + +igraph_cocitation: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR vids=ALL + DEPS: vids ON graph + +igraph_bibcoupling: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR vids=ALL + DEPS: vids ON graph + +igraph_similarity_dice: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, VERTEX_SELECTOR to=ALL, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: from ON graph, to ON graph, res ON from, res ON to + +igraph_similarity_dice_es: + PARAMS: |- + GRAPH graph, OUT VECTOR res, EDGE_SELECTOR es=ALL, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: es ON graph + +igraph_similarity_dice_pairs: + PARAMS: |- + GRAPH graph, OUT VECTOR res, VERTEX_INDEX_PAIRS pairs, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: pairs ON graph + +igraph_similarity_inverse_log_weighted: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR vids=ALL, NEIMODE mode=ALL + DEPS: vids ON graph + +igraph_similarity_jaccard: + PARAMS: |- + GRAPH graph, OUT MATRIX res, VERTEX_SELECTOR from=ALL, VERTEX_SELECTOR to=ALL, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: from ON graph, to ON graph, res ON from, res ON to + +igraph_similarity_jaccard_es: + PARAMS: |- + GRAPH graph, OUT VECTOR res, EDGE_SELECTOR es=ALL, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: es ON graph + +igraph_similarity_jaccard_pairs: + PARAMS: |- + GRAPH graph, OUT VECTOR res, VERTEX_INDEX_PAIRS pairs, NEIMODE mode=ALL, + BOOLEAN loops=False + DEPS: pairs ON graph + +####################################### +# Community structure +####################################### + +igraph_compare_communities: + PARAMS: |- + VECTOR_INT comm1, VECTOR_INT comm2, OUT REAL res, COMMCMP method=VI + +igraph_community_spinglass: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT REAL modularity, + OUT REAL temperature, OUT VECTOR_INT membership, OUT VECTOR_INT csize, + INTEGER spins=25, BOOLEAN parupdate=False, REAL starttemp=1, REAL stoptemp=0.01, + REAL coolfact=0.99, SPINCOMMUPDATE update_rule=CONFIG, REAL gamma=1.0, + SPINGLASS_IMPLEMENTATION implementation=ORIG, REAL lambda=1.0 + DEPS: weights ON graph + +igraph_community_spinglass_single: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, INTEGER vertex, + OUT VECTOR_INT community, OUT REAL cohesion, OUT REAL adhesion, + OUT REAL inner_links, OUT REAL outer_links, + INTEGER spins=25, SPINCOMMUPDATE update_rule=CONFIG, REAL gamma=1.0 + DEPS: weights ON graph + +igraph_community_walktrap: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, INTEGER steps=4, + OUT MATRIX_INT merges, OUT VECTOR modularity, OUT VECTOR_INT membership + DEPS: weights ON graph + +igraph_community_edge_betweenness: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT removed_edges, OPTIONAL OUT VECTOR edge_betweenness, + OPTIONAL OUT MATRIX_INT merges, OPTIONAL OUT INDEX_VECTOR bridges, + OPTIONAL OUT VECTOR modularity, OPTIONAL OUT VECTOR_INT membership, + BOOLEAN directed=True, + OPTIONAL EDGE_WEIGHTS weights, OPTIONAL EDGE_LENGTHS lengths + DEPS: weights ON graph, lengths ON graph, edge_betweenness ON graph + +igraph_community_eb_get_merges: + PARAMS: |- + GRAPH graph, BOOLEAN directed, EDGE_INDICES edges, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT MATRIX_INT merges, OPTIONAL OUT INDEX_VECTOR bridges, + OPTIONAL OUT VECTOR modularity, OPTIONAL OUT VECTOR_INT membership + DEPS: weights ON graph + +igraph_community_fastgreedy: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, OUT MATRIX_INT merges, + OPTIONAL OUT VECTOR modularity, OPTIONAL OUT VECTOR_INT membership + DEPS: weights ON graph + +igraph_community_to_membership: + PARAMS: |- + MATRIX_INT merges, INTEGER nodes, INTEGER steps, + OPTIONAL OUT VECTOR_INT membership, OPTIONAL OUT VECTOR_INT csize + +igraph_le_community_to_membership: + PARAMS: |- + MATRIX_INT merges, INTEGER steps, INOUT VECTOR_INT membership, + OPTIONAL OUT VECTOR_INT csize + +igraph_modularity: + PARAMS: |- + GRAPH graph, VECTOR_INT membership, OPTIONAL EDGE_WEIGHTS weights, + REAL resolution=1.0, BOOLEAN directed=True, OUT REAL modularity + DEPS: weights ON graph + +igraph_modularity_matrix: + PARAMS: |- + GRAPH graph, + OPTIONAL EDGE_WEIGHTS weights, + REAL resolution=1.0, + OUT MATRIX modmat, + BOOLEAN directed=True + DEPS: weights ON graph + +igraph_reindex_membership: + PARAMS: |- + INOUT VECTOR_INT membership, OUT INDEX_VECTOR new_to_old, + OUT INTEGER nb_clusters + +igraph_community_leading_eigenvector: + PARAMS: |- + GRAPH graph, + OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL OUT MATRIX_INT merges, + OPTIONAL OUT VECTOR_INT membership, + INTEGER steps=-1, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS, + OPTIONAL OUT REAL modularity, + BOOLEAN start=False, + OPTIONAL OUT VECTOR eigenvalues, + OPTIONAL OUT VECTOR_LIST eigenvectors, + OPTIONAL OUT VECTOR_INT history, + OPTIONAL LEVC_FUNC callback, + OPTIONAL EXTRA callback_extra + DEPS: weights ON graph + +igraph_community_fluid_communities: + PARAMS: |- + GRAPH graph, INTEGER no_of_communities, OUT VECTOR_INT membership + +igraph_community_label_propagation: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT membership, NEIMODE mode=ALL, + OPTIONAL EDGE_WEIGHTS weights, OPTIONAL INDEX_VECTOR initial, + OPTIONAL VECTOR_BOOL fixed, LPA_VARIANT lpa_variant=DOMINANCE + DEPS: weights ON graph + +igraph_community_multilevel: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, REAL resolution=1.0, + OUT VECTOR_INT membership, OPTIONAL OUT MATRIX_INT memberships, + OPTIONAL OUT VECTOR modularity + DEPS: weights ON graph + +igraph_community_optimal_modularity: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, REAL resolution=1.0, + OPTIONAL OUT REAL modularity, OPTIONAL OUT VECTOR_INT membership + DEPS: weights ON graph + +igraph_community_leiden: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OPTIONAL VERTEX_WEIGHTS vertex_out_weights, + OPTIONAL VERTEX_WEIGHTS vertex_in_weights, + REAL resolution, REAL beta=0.01, BOOLEAN start=False, INTEGER n_iterations=2, + OPTIONAL INOUT VECTOR_INT membership, + OUT INTEGER nb_clusters, OUT REAL quality + DEPS: weights ON graph, vertex_out_weights ON graph, vertex_in_weights ON graph + +igraph_community_leiden_simple: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + LEIDEN_OBJECTIVE objective, + REAL resolution, REAL beta=0.01, BOOLEAN start=False, INTEGER n_iterations=2, + OPTIONAL INOUT VECTOR_INT membership, + OUT INTEGER nb_clusters, OUT REAL quality + DEPS: weights ON graph + +igraph_split_join_distance: + PARAMS: |- + VECTOR_INT comm1, VECTOR_INT comm2, OUT INTEGER distance12, + OUT INTEGER distance21 + +igraph_community_infomap: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS edge_weights, + OPTIONAL VERTEX_WEIGHTS vertex_weights, INTEGER nb_trials=10, + BOOLEAN is_regularized=False, REAL regularization_strength=1, + OPTIONAL OUT VECTOR_INT membership, OPTIONAL OUT REAL codelength + DEPS: edge_weights ON graph, vertex_weights ON graph + +igraph_community_voronoi: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VECTOR_INT membership, + OPTIONAL OUT VERTEX_INDICES generators, + OPTIONAL OUT REAL modularity, + OPTIONAL EDGE_LENGTHS lengths, OPTIONAL EDGE_WEIGHTS weights, + NEIMODE mode=OUT, REAL radius=-1 + DEPS: generators ON graph, weights ON graph, lengths ON graph + +####################################### +# Graphlets +####################################### + +igraph_graphlets: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VERTEX_INDICES_LIST cliques, OUT VECTOR Mu, INTEGER niter=1000 + DEPS: weights ON graph, cliques ON graph + +igraph_graphlets_candidate_basis: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT VERTEX_INDICES_LIST cliques, OUT VECTOR thresholds + DEPS: weights ON graph, cliques ON graph + +igraph_graphlets_project: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + VERTEX_INDICES_LIST cliques, INOUT VECTOR Muc, + BOOLEAN startMu=False, INTEGER niter=1000 + DEPS: weights ON graph + +####################################### +# Hierarchical random graphs +####################################### + +igraph_hrg_fit: + PARAMS: |- + GRAPH graph, INOUT HRG hrg=Default, BOOLEAN start=False, + INTEGER steps=0 + +igraph_hrg_sample: + PARAMS: HRG hrg, OUT GRAPH sample + +igraph_hrg_sample_many: + PARAMS: HRG hrg, OUT GRAPH_LIST samples, INTEGER num_samples + +igraph_hrg_game: + PARAMS: OUT GRAPH graph, HRG hrg + +igraph_hrg_consensus: + PARAMS: |- + GRAPH graph, OUT VECTOR_INT parents, OUT VECTOR weights, + INOUT HRG hrg=Default, BOOLEAN start=False, + INTEGER num_samples=10000 + +igraph_hrg_predict: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES edges, OUT VECTOR prob, + INOUT HRG hrg=Default, BOOLEAN start=False, + INTEGER num_samples=10000, INTEGER num_bins=25 + DEPS: edges ON graph + +igraph_hrg_create: + PARAMS: OUT HRG hrg, GRAPH graph, VECTOR prob + DEPS: prob ON graph + +igraph_hrg_resize: + PARAMS: INOUT HRG hrg, INTEGER newsize + +igraph_hrg_size: + PARAMS: HRG hrg + RETURN: INTEGER + +igraph_from_hrg_dendrogram: + PARAMS: OUT GRAPH graph, HRG hrg, OUT VECTOR prob + +####################################### +# Conversion +####################################### + +igraph_get_adjacency: + PARAMS: |- + GRAPH graph, OUT MATRIX res, GETADJACENCY type=BOTH, + OPTIONAL EDGE_WEIGHTS weights, LOOPS loops=ONCE + DEPS: weights ON graph + +igraph_get_adjacency_sparse: + PARAMS: |- + GRAPH graph, OUT SPARSEMAT sparsemat, GETADJACENCY type=BOTH, + OPTIONAL EDGE_WEIGHTS weights, LOOPS loops=ONCE + DEPS: weights ON graph + +igraph_get_edgelist: + PARAMS: GRAPH graph, OUT VECTOR_INT res, BOOLEAN bycol=False + +igraph_get_stochastic: + PARAMS: |- + GRAPH graph, OUT MATRIX res, BOOLEAN column_wise=False, + OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_get_stochastic_sparse: + PARAMS: |- + GRAPH graph, OUT SPARSEMAT sparsemat, BOOLEAN column_wise=False, + OPTIONAL EDGE_WEIGHTS weights + DEPS: weights ON graph + +igraph_to_directed: + PARAMS: INOUT GRAPH graph, TODIRECTED mode=MUTUAL + +igraph_to_undirected: + PARAMS: |- + INOUT GRAPH graph, TOUNDIRECTED mode=COLLAPSE, + EDGE_ATTRIBUTE_COMBINATION edge_attr_comb=Default + +####################################### +# Read and write foreign formats +####################################### + +igraph_read_graph_edgelist: + PARAMS: OUT GRAPH graph, INFILE instream, INTEGER n=0, BOOLEAN directed=True + +igraph_read_graph_ncol: + PARAMS: |- + OUT GRAPH graph, INFILE instream, OPTIONAL VECTOR_STR predefnames, + BOOLEAN names=True, ADD_WEIGHTS weights=True, BOOLEAN directed=True + +igraph_read_graph_lgl: + PARAMS: |- + OUT GRAPH graph, INFILE instream, BOOLEAN names=True, + ADD_WEIGHTS weights=True, BOOLEAN directed=True + +igraph_read_graph_pajek: + PARAMS: OUT GRAPH graph, INFILE instream + +igraph_read_graph_graphml: + PARAMS: OUT GRAPH graph, INFILE instream, INTEGER index=0 + +igraph_read_graph_dimacs_flow: + PARAMS: |- + OUT GRAPH graph, INFILE instream, + OPTIONAL OUT VECTOR_STR problem, OPTIONAL OUT VECTOR_INT label, + OPTIONAL OUT INTEGER source, OPTIONAL OUT INTEGER target, + OPTIONAL OUT VECTOR capacity, BOOLEAN directed=True + +igraph_read_graph_graphdb: + PARAMS: OUT GRAPH graph, INFILE instream, BOOLEAN directed=False + +igraph_read_graph_gml: + PARAMS: OUT GRAPH graph, INFILE instream + +igraph_read_graph_dl: + PARAMS: OUT GRAPH graph, INFILE instream, BOOLEAN directed=True + +igraph_write_graph_edgelist: + PARAMS: GRAPH graph, OUTFILE outstream + +igraph_write_graph_ncol: + PARAMS: GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight" + +igraph_write_graph_lgl: + PARAMS: |- + GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight", + BOOLEAN isolates=True + +igraph_write_graph_leda: + PARAMS: GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight" + +igraph_write_graph_graphml: + PARAMS: GRAPH graph, OUTFILE outstream, BOOLEAN prefixattr=True + +igraph_write_graph_pajek: + PARAMS: GRAPH graph, OUTFILE outstream + +igraph_write_graph_dimacs_flow: + PARAMS: |- + GRAPH graph, OUTFILE outstream, VERTEX source=0, VERTEX target=0, + VECTOR capacity + +igraph_write_graph_gml: + PARAMS: GRAPH graph, OUTFILE outstream, WRITE_GML_SW options=DEFAULT, VECTOR id, OPTIONAL CSTRING creator + +igraph_write_graph_dot: + PARAMS: GRAPH graph, OUTFILE outstream + +####################################### +# Motifs +####################################### + +igraph_motifs_randesu: + PARAMS: GRAPH graph, OUT VECTOR hist, INTEGER size=3, OPTIONAL VECTOR cut_prob + +igraph_motifs_randesu_estimate: + PARAMS: |- + GRAPH graph, OUT REAL est, INTEGER size=3, OPTIONAL VECTOR cut_prob, + INTEGER sample_size, OPTIONAL VECTOR_INT sample + +igraph_motifs_randesu_no: + PARAMS: GRAPH graph, OUT REAL no, INTEGER size=3, OPTIONAL VECTOR cut_prob + +igraph_dyad_census: + PARAMS: GRAPH graph, OUT REAL mut, OUT REAL asym, OUT REAL null + RETURN: ERROR + +igraph_triad_census: + PARAMS: GRAPH graph, OUT VECTOR res + RETURN: ERROR + +igraph_count_adjacent_triangles: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEX_SELECTOR vids=ALL + DEPS: vids ON graph + +igraph_count_triangles: + PARAMS: GRAPH graph, OUT REAL res + +igraph_local_scan_0: + PARAMS: |- + GRAPH graph, OUT VECTOR res, OPTIONAL EDGE_WEIGHTS weights, + NEIMODE mode=OUT + DEPS: weights ON graph + +igraph_local_scan_0_them: + PARAMS: |- + GRAPH us, GRAPH them, OUT VECTOR res, + OPTIONAL EDGE_WEIGHTS weights_them, NEIMODE mode=OUT + DEPS: weights_them ON them + +igraph_local_scan_1_ecount: + PARAMS: |- + GRAPH graph, OUT VECTOR res, OPTIONAL EDGE_WEIGHTS weights, + NEIMODE mode=OUT + DEPS: weights ON graph + +igraph_local_scan_1_ecount_them: + PARAMS: |- + GRAPH us, GRAPH them, OUT VECTOR res, + OPTIONAL EDGE_WEIGHTS weights_them, NEIMODE mode=OUT + DEPS: weights_them ON them + +igraph_local_scan_k_ecount: + PARAMS: |- + GRAPH graph, INTEGER k, OUT VECTOR res, OPTIONAL EDGE_WEIGHTS weights, + NEIMODE mode=OUT + DEPS: weights ON graph + +igraph_local_scan_k_ecount_them: + PARAMS: |- + GRAPH us, GRAPH them, INTEGER k, OUT VECTOR res, + OPTIONAL EDGE_WEIGHTS weights_them, NEIMODE mode=OUT + DEPS: weights_them ON them + +igraph_local_scan_neighborhood_ecount: + PARAMS: |- + GRAPH graph, OUT VECTOR res, OPTIONAL EDGE_WEIGHTS weights, + VERTEX_INDICES_LIST neighborhoods + DEPS: weights ON graph + +igraph_local_scan_subset_ecount: + PARAMS: |- + GRAPH graph, OUT VECTOR res, OPTIONAL EDGE_WEIGHTS weights, + VERTEX_INDICES_LIST subsets + DEPS: weights ON graph + +igraph_list_triangles: + PARAMS: GRAPH graph, OUT VERTEX_INDICES res + DEPS: res ON graph + +####################################### +# Graph operators +####################################### + +igraph_disjoint_union: + PARAMS: OUT GRAPH res, GRAPH left, GRAPH right + +igraph_disjoint_union_many: + PARAMS: OUT GRAPH res, GRAPH_PTR_LIST graphs + +igraph_join: + PARAMS: OUT GRAPH res, GRAPH left, GRAPH right + +igraph_union: + PARAMS: |- + OUT GRAPH res, GRAPH left, GRAPH right, + OUT INDEX_VECTOR edge_map_left, OUT INDEX_VECTOR edge_map_right + DEPS: edge_map_left ON left, edge_map_right ON right + +igraph_union_many: + PARAMS: OUT GRAPH res, GRAPH_PTR_LIST graphs, OUT VECTOR_INT_LIST edgemaps + +igraph_intersection: + PARAMS: |- + OUT GRAPH res, GRAPH left, GRAPH right, + OUT INDEX_VECTOR edge_map_left, OUT INDEX_VECTOR edge_map_right + DEPS: edge_map_left ON left, edge_map_right ON right + +igraph_intersection_many: + PARAMS: OUT GRAPH res, GRAPH_PTR_LIST graphs, OUT VECTOR_INT_LIST edgemaps + +igraph_difference: + PARAMS: OUT GRAPH res, GRAPH orig, GRAPH sub + +igraph_complementer: + PARAMS: OUT GRAPH res, GRAPH graph, BOOLEAN loops=False + +igraph_compose: + PARAMS: |- + OUT GRAPH res, GRAPH g1, GRAPH g2, + OUT INDEX_VECTOR edge_map1, OUT INDEX_VECTOR edge_map2 + DEPS: edge_map1 ON g1, edge_map2 ON g2 + +igraph_induced_subgraph_map: + PARAMS: |- + GRAPH graph, OUT GRAPH res, VERTEX_SELECTOR vids, SUBGRAPH_IMPL impl=AUTO, + OPTIONAL OUT INDEX_VECTOR map, OPTIONAL OUT INDEX_VECTOR invmap + DEPS: vids ON graph + +igraph_mycielskian: + PARAMS: |- + IN GRAPH graph, OUT GRAPH res, INTEGER k=1 + +igraph_product: + PARAMS: |- + OUT GRAPH res, GRAPH g1, GRAPH g2, GRAPH_PRODUCT_TYPE type=CARTESIAN + +igraph_rooted_product: + PARAMS: |- + OUT GRAPH res, GRAPH g1, GRAPH g2, VERTEX root + DEPS: root ON g2 + +####################################### +# Maximum flows, minimum cuts +####################################### + +igraph_gomory_hu_tree: + PARAMS: GRAPH graph, OUT GRAPH tree, OPTIONAL OUT VECTOR flows, OPTIONAL EDGE_CAPACITIES capacity + DEPS: capacity ON graph + +igraph_maxflow: + PARAMS: |- + GRAPH graph, OUT REAL value, OPTIONAL OUT VECTOR flow, + OUT EDGE_INDICES cut, OPTIONAL OUT VERTEX_INDICES partition1, + OPTIONAL OUT VERTEX_INDICES partition2, VERTEX source, VERTEX target, + OPTIONAL EDGE_CAPACITIES capacity, OPTIONAL OUT MAXFLOW_STATS stats + DEPS: |- + capacity ON graph, source ON graph, target ON graph, + partition1 ON graph, partition2 ON graph, flow ON graph, + cut ON graph + +igraph_maxflow_value: + PARAMS: |- + GRAPH graph, OUT REAL value, VERTEX source, VERTEX target, + OPTIONAL EDGE_CAPACITIES capacity, OPTIONAL OUT MAXFLOW_STATS stats + DEPS: source ON graph, target ON graph, capacity ON graph + +igraph_mincut: + PARAMS: |- + GRAPH graph, OUT REAL value, OUT VERTEX_INDICES partition1, + OUT VERTEX_INDICES partition2, OUT EDGE_INDICES cut, + OPTIONAL EDGE_CAPACITIES capacity + DEPS: capacity ON graph, partition1 ON graph, partition2 ON graph, cut ON graph + +igraph_mincut_value: + PARAMS: GRAPH graph, OUT REAL res, OPTIONAL EDGE_CAPACITIES capacity + DEPS: capacity ON graph + +igraph_residual_graph: + PARAMS: |- + GRAPH graph, EDGE_CAPACITIES capacity, OUT GRAPH residual, + OUT EDGE_CAPACITIES residual_capacity, VECTOR flow + DEPS: capacity ON graph, flow ON graph, residual_capacity ON residual + +igraph_reverse_residual_graph: + PARAMS: |- + GRAPH graph, EDGE_CAPACITIES capacity, OUT GRAPH residual, + VECTOR flow + DEPS: capacity ON graph, flow ON graph + +igraph_st_mincut: + PARAMS: |- + GRAPH graph, OUT REAL value, OUT EDGE_INDICES cut, + OPTIONAL OUT VERTEX_INDICES partition1, + OPTIONAL OUT VERTEX_INDICES partition2, + VERTEX source, VERTEX target, OPTIONAL EDGE_CAPACITIES capacity + DEPS: |- + capacity ON graph, source ON graph, target ON graph, + partition1 ON graph, partition2 ON graph, cut ON graph + +igraph_st_mincut_value: + PARAMS: |- + GRAPH graph, OUT REAL res, VERTEX source, VERTEX target, + OPTIONAL EDGE_CAPACITIES capacity + DEPS: source ON graph, target ON graph, capacity ON graph + +igraph_st_vertex_connectivity: + PARAMS: |- + GRAPH graph, OUT INTEGER res, VERTEX source, VERTEX target, + VCONNNEI neighbors=NUMBER_OF_NODES + DEPS: source ON graph, target ON graph + +igraph_vertex_connectivity: + PARAMS: GRAPH graph, OUT INTEGER res, BOOLEAN checks=True + +igraph_st_edge_connectivity: + PARAMS: GRAPH graph, OUT INTEGER res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + +igraph_edge_connectivity: + PARAMS: GRAPH graph, OUT INTEGER res, BOOLEAN checks=True + +igraph_edge_disjoint_paths: + PARAMS: GRAPH graph, OUT INTEGER res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + +igraph_vertex_disjoint_paths: + PARAMS: GRAPH graph, OUT INTEGER res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + +igraph_adhesion: + PARAMS: GRAPH graph, OUT INTEGER res, BOOLEAN checks=True + +igraph_cohesion: + PARAMS: GRAPH graph, OUT INTEGER res, BOOLEAN checks=True + +####################################### +# Listing s-t cuts, separators +####################################### + +igraph_dominator_tree: + PARAMS: |- + GRAPH graph, VERTEX root, OUT INDEX_VECTOR dom, + OPTIONAL OUT GRAPH domtree, OUT VERTEX_INDICES leftout, + NEIMODE mode=OUT + DEPS: root ON graph, leftout ON graph + +igraph_all_st_cuts: + PARAMS: |- + GRAPH graph, OPTIONAL OUT EDGE_INDICES_LIST cuts, + OPTIONAL OUT VERTEX_INDICES_LIST partition1s, + VERTEX source, VERTEX target + DEPS: |- + source ON graph, target ON graph, cuts ON graph, + partition1s ON graph + +igraph_all_st_mincuts: + PARAMS: |- + GRAPH graph, OUT REAL value, + OPTIONAL OUT EDGE_INDICES_LIST cuts, + OPTIONAL OUT VERTEX_INDICES_LIST partition1s, + VERTEX source, VERTEX target, OPTIONAL EDGE_CAPACITIES capacity + DEPS: |- + capacity ON graph, source ON graph, target ON graph, + cuts ON graph, partition1s ON graph + +igraph_even_tarjan_reduction: + PARAMS: GRAPH graph, OUT GRAPH graphbar, OPTIONAL OUT EDGE_CAPACITIES capacity + DEPS: |- + capacity ON graphbar + +igraph_is_separator: + PARAMS: GRAPH graph, VERTEX_SELECTOR candidate, OUT BOOLEAN res + DEPS: candidate ON graph + +igraph_is_minimal_separator: + PARAMS: GRAPH graph, VERTEX_SELECTOR candidate, OUT BOOLEAN res + DEPS: candidate ON graph + +igraph_all_minimal_st_separators: + PARAMS: GRAPH graph, OUT VERTEX_INDICES_LIST separators + DEPS: separators ON graph + +igraph_minimum_size_separators: + PARAMS: GRAPH graph, OUT VERTEX_INDICES_LIST separators + DEPS: separators ON graph + +igraph_cohesive_blocks: + PARAMS: |- + GRAPH graph, OUT VERTEX_INDICES_LIST blocks, + OUT VECTOR_INT cohesion, OUT INDEX_VECTOR parent, + OUT GRAPH blockTree + DEPS: blocks ON graph + +####################################### +# K-Cores +####################################### + +igraph_coreness: + PARAMS: GRAPH graph, OUT VECTOR_INT cores, NEIMODE mode=ALL + +####################################### +# Graph isomorphism +####################################### + +igraph_isoclass: + PARAMS: GRAPH graph, OUT INTEGER isoclass + +igraph_isomorphic: + PARAMS: GRAPH graph1, GRAPH graph2, OUT BOOLEAN iso + +igraph_automorphism_group: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, OUT VERTEX_INDICES_LIST generators + DEPS: colors ON graph, generators ON graph + +igraph_count_automorphisms: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, OUT REAL result + DEPS: colors ON graph + +igraph_isoclass_subgraph: + PARAMS: GRAPH graph, VERTEX_SELECTOR vids, OUT INTEGER isoclass + DEPS: vids ON graph + +igraph_isoclass_create: + PARAMS: OUT GRAPH graph, INTEGER size, INTEGER number, BOOLEAN directed=True + +igraph_isomorphic_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, + OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, + OPTIONAL EDGE_COLORS edge_color2, + OUT BOOLEAN iso, + OPTIONAL OUT INDEX_VECTOR map12, OPTIONAL OUT INDEX_VECTOR map21, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_isomorphisms_vf2_callback: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OPTIONAL OUT INDEX_VECTOR map12, OPTIONAL OUT INDEX_VECTOR map21, + ISOMORPHISM_FUNC ishohandler_fn, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA arg + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_count_isomorphisms_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OUT INTEGER count, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_isomorphisms_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OUT VECTOR_INT_LIST maps, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_subisomorphic: + PARAMS: GRAPH graph1, GRAPH graph2, OUT BOOLEAN iso + +igraph_subisomorphic_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OUT BOOLEAN iso, + OPTIONAL OUT INDEX_VECTOR map12, OPTIONAL OUT INDEX_VECTOR map21, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_subisomorphisms_vf2_callback: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OPTIONAL OUT INDEX_VECTOR map12, OPTIONAL OUT INDEX_VECTOR map21, + ISOMORPHISM_FUNC ishohandler_fn, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA arg + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_count_subisomorphisms_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OUT INTEGER count, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_subisomorphisms_vf2: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS vertex_color1, OPTIONAL VERTEX_COLORS vertex_color2, + OPTIONAL EDGE_COLORS edge_color1, OPTIONAL EDGE_COLORS edge_color2, + OUT VECTOR_INT_LIST maps, + OPTIONAL ISOCOMPAT_FUNC node_compat_fn, + OPTIONAL ISOCOMPAT_FUNC edge_compat_fn, + OPTIONAL EXTRA extra + DEPS: |- + vertex_color1 ON graph1, vertex_color2 ON graph2, + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_canonical_permutation: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, + OUT INDEX_VECTOR labeling + DEPS: colors ON graph + +igraph_canonical_permutation_bliss: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, + OUT INDEX_VECTOR labeling, BLISSSH sh="fm", OUT BLISSINFO info + DEPS: colors ON graph + +igraph_permute_vertices: + PARAMS: GRAPH graph, OUT GRAPH res, INDEX_VECTOR permutation + +igraph_isomorphic_bliss: + PARAMS: |- + GRAPH graph1, GRAPH graph2, + OPTIONAL VERTEX_COLORS colors1, OPTIONAL VERTEX_COLORS colors2, + OUT BOOLEAN iso, OPTIONAL OUT INDEX_VECTOR map12, + OPTIONAL OUT INDEX_VECTOR map21, BLISSSH sh="fm", + OPTIONAL OUT BLISSINFO info1, OPTIONAL OUT BLISSINFO info2 + DEPS: colors1 ON graph1, colors2 ON graph2 + +igraph_count_automorphisms_bliss: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, BLISSSH sh="fm", OUT BLISSINFO info + DEPS: colors ON graph + +igraph_automorphism_group_bliss: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX_COLORS colors, PRIMARY OUT VERTEX_INDICES_LIST generators, + BLISSSH sh="fm", OUT BLISSINFO info + DEPS: colors ON graph, generators ON graph + +igraph_subisomorphic_lad: + PARAMS: |- + GRAPH pattern, GRAPH target, OPTIONAL VERTEX_INDICES_LIST domains, + OPTIONAL OUT BOOLEAN iso, OUT INDEX_VECTOR map, + OPTIONAL OUT VECTOR_INT_LIST maps, BOOLEAN induced + +igraph_simplify_and_colorize: + # Despite their names, vertex_color and edge_color are not really colors + # but _multiplicities_, so we simply use VECTOR_INT there + PARAMS: |- + GRAPH graph, OUT GRAPH res, OUT VECTOR_INT vertex_color, OUT VECTOR_INT edge_color + DEPS: vertex_color ON graph, edge_color ON graph + +igraph_graph_count: + PARAMS: INTEGER n, BOOLEAN directed=False, OUT INTEGER count + +####################################### +# Matching +####################################### + +igraph_is_matching: + PARAMS: |- + GRAPH graph, OPTIONAL BIPARTITE_TYPES types, + INDEX_VECTOR matching, OUT BOOLEAN res + DEPS: types ON graph, matching ON graph + +igraph_is_maximal_matching: + PARAMS: |- + GRAPH graph, OPTIONAL BIPARTITE_TYPES types, + INDEX_VECTOR matching, OUT BOOLEAN res + DEPS: types ON graph + +igraph_maximum_bipartite_matching: + PARAMS: |- + GRAPH graph, BIPARTITE_TYPES types, + OPTIONAL OUT INTEGER matching_size, + OPTIONAL OUT REAL matching_weight, + OUT INDEX_VECTOR matching, + OPTIONAL EDGE_WEIGHTS weights, REAL eps=.Machine$double.eps + DEPS: types ON graph, weights ON graph + +####################################### +# Embedding +####################################### + +igraph_adjacency_spectral_embedding: + PARAMS: |- + GRAPH graph, INTEGER no, OPTIONAL EDGE_WEIGHTS weights, + EIGENWHICHPOS which=ASE, BOOLEAN scaled=True, OUT MATRIX X, + OPTIONAL OUT MATRIX Y, OPTIONAL OUT VECTOR D, + VECTOR cvec=AsmDefaultCvec, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS + DEPS: weights ON graph, cvec ON graph + +igraph_laplacian_spectral_embedding: + PARAMS: |- + GRAPH graph, INTEGER no, OPTIONAL EDGE_WEIGHTS weights, + EIGENWHICHPOS which=ASE, + LSETYPE type=Default, BOOLEAN scaled=True, OUT MATRIX X, + OPTIONAL OUT MATRIX Y, OPTIONAL OUT VECTOR D, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS + DEPS: weights ON graph, type ON graph + +####################################### +# Eigensolvers +####################################### + +igraph_eigen_adjacency: + PARAMS: |- + GRAPH graph, EIGENALGO algorithm=ARPACK, + EIGENWHICH which=Default, + INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS, + INOUT ARPACK_STORAGE storage, OUT VECTOR values, OUT MATRIX vectors, + OUT VECTOR_COMPLEX cmplxvalues, OUT MATRIX_COMPLEX cmplxvectors + +####################################### +# Fitting power laws +####################################### + +igraph_power_law_fit: + PARAMS: |- + VECTOR data, OUT PLFIT res, REAL xmin=-1, + BOOLEAN force_continuous=False + +####################################### +# Dynamics, on networks +####################################### + +igraph_sir: + PARAMS: |- + GRAPH graph, REAL beta, REAL gamma, INTEGER no_sim=100, + OUT SIR_LIST res + +####################################### +# Other, not graph related +####################################### + +igraph_running_mean: + PARAMS: VECTOR data, OUT VECTOR res, INTEGER binwidth + +igraph_random_sample: + PARAMS: OUT VECTOR_INT res, INTEGER l, INTEGER h, INTEGER length + +igraph_convex_hull_2d: + PARAMS: MATRIX data, OUT INDEX_VECTOR resverts, OUT MATRIX rescoords + +igraph_dim_select: + PARAMS: VECTOR sv, OUT INTEGER dim + +igraph_almost_equals: + PARAMS: DOUBLE a, DOUBLE b, DOUBLE eps + RETURN: BOOLEAN + +igraph_cmp_epsilon: + PARAMS: DOUBLE a, DOUBLE b, DOUBLE eps + RETURN: INT + +igraph_eigen_matrix: + PARAMS: |- + MATRIX A, SPARSEMAT sA, ARPACK_FUNC fun, INT n, OPTIONAL EXTRA extra, + EIGENALGO algorithm, EIGENWHICH which, INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS, + INOUT ARPACK_STORAGE storage, OUT VECTOR_COMPLEX values, OUT MATRIX_COMPLEX vectors + +igraph_eigen_matrix_symmetric: + PARAMS: |- + MATRIX A, SPARSEMAT sA, ARPACK_FUNC fun, INT n, OPTIONAL EXTRA extra, + EIGENALGO algorithm, EIGENWHICH which, INOUT ARPACK_OPTIONS options=ARPACK_DEFAULTS, + INOUT ARPACK_STORAGE storage, OUT VECTOR values, OUT MATRIX vectors + +igraph_solve_lsap: + PARAMS: MATRIX c, INTEGER n, OUT VECTOR_INT p + +####################################### +# Finding cycles +####################################### + +igraph_find_cycle: + PARAMS: |- + GRAPH graph, OPTIONAL OUT VERTEX_INDICES vertices, OPTIONAL OUT EDGE_INDICES edges, + NEIMODE mode=OUT + DEPS: vertices ON graph, edges ON graph + +igraph_simple_cycles: + PARAMS: |- + GRAPH graph, + OPTIONAL OUT VERTEX_INDICES_LIST vertices, OPTIONAL OUT EDGE_INDICES_LIST edges, + NEIMODE mode=OUT, INTEGER min_cycle_length=UNLIMITED, INTEGER max_cycle_length=UNLIMITED, + INTEGER max_results=UNLIMITED + DEPS: vertices ON graph, edges ON graph + +igraph_simple_cycles_callback: + PARAMS: |- + GRAPH graph, + NEIMODE mode=OUT, INTEGER min_cycle_length=UNLIMITED, INTEGER max_cycle_length=UNLIMITED, + CYCLE_FUNC cycle_handler, OPTIONAL EXTRA arg + +####################################### +# Eulerian functions +####################################### + +igraph_is_eulerian: + PARAMS: GRAPH graph, OUT BOOLEAN has_path, OUT BOOLEAN has_cycle + +igraph_eulerian_path: + PARAMS: GRAPH graph, OPTIONAL OUT EDGE_INDICES edge_res, OPTIONAL OUT VERTEX_INDICES vertex_res + DEPS: edge_res ON graph, vertex_res ON graph + +igraph_eulerian_cycle: + PARAMS: GRAPH graph, OPTIONAL OUT EDGE_INDICES edge_res, OPTIONAL OUT VERTEX_INDICES vertex_res + DEPS: edge_res ON graph, vertex_res ON graph + +####################################### +# Cycle bases +####################################### + +igraph_fundamental_cycles: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT EDGE_INDICES_LIST basis, + OPTIONAL VERTEX start, + REAL bfs_cutoff=UNLIMITED + DEPS: weights ON graph, basis ON graph, start ON graph + +igraph_minimum_cycle_basis: + PARAMS: |- + GRAPH graph, OPTIONAL EDGE_WEIGHTS weights, + OUT EDGE_INDICES_LIST basis, + REAL bfs_cutoff=UNLIMITED, + BOOLEAN complete=True, BOOLEAN use_cycle_order=True + DEPS: weights ON graph, basis ON graph + +####################################### +# Trees +####################################### + +igraph_is_tree: + PARAMS: GRAPH graph, PRIMARY OUT BOOLEAN res, OPTIONAL OUT VERTEX root, NEIMODE mode=OUT + DEPS: root ON graph + +igraph_is_forest: + PARAMS: GRAPH graph, PRIMARY OUT BOOLEAN res, OPTIONAL OUT VERTEX_INDICES roots, NEIMODE mode=OUT + DEPS: roots ON graph + +igraph_from_prufer: + PARAMS: OUT GRAPH graph, INDEX_VECTOR prufer + +igraph_to_prufer: + PARAMS: GRAPH graph, OUT INDEX_VECTOR prufer + +igraph_tree_from_parent_vector: + PARAMS: OUT GRAPH graph, INDEX_VECTOR parents, TREE_MODE type=OUT + +igraph_is_complete: + PARAMS: GRAPH graph, OUT BOOLEAN res + +igraph_minimum_spanning_tree: + PARAMS: GRAPH graph, OUT EDGE_INDICES res, OPTIONAL EDGE_WEIGHTS weights, MSTALGORITHM method=AUTOMATIC + DEPS: res ON graph, weights ON graph + +igraph_random_spanning_tree: + PARAMS: GRAPH graph, OUT EDGE_INDICES res, OPTIONAL VERTEX vid + DEPS: res ON graph, vid ON graph + +igraph_tree_game: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, RANDOM_TREE_METHOD method=LERW + +####################################### +# Spatial +####################################### + +igraph_nearest_neighbor_graph: + PARAMS: OUT GRAPH graph, MATRIX points, METRIC metric, INTEGER neighbors, REAL cutoff, BOOLEAN directed + +igraph_delaunay_graph: + PARAMS: OUT GRAPH graph, MATRIX points + +igraph_gabriel_graph: + PARAMS: OUT GRAPH graph, MATRIX points + +igraph_relative_neighborhood_graph: + PARAMS: OUT GRAPH graph, MATRIX points + +igraph_lune_beta_skeleton: + PARAMS: OUT GRAPH graph, MATRIX points, REAL beta=1 + +igraph_circle_beta_skeleton: + PARAMS: OUT GRAPH graph, MATRIX points, REAL beta=1 + +igraph_beta_weighted_gabriel_graph: + PARAMS: OUT GRAPH graph, OUT EDGE_WEIGHTS weights, MATRIX points, REAL max_beta=UNLIMITED + DEPS: weights ON graph + +igraph_spatial_edge_lengths: + PARAMS: GRAPH graph, OUT EDGE_LENGTHS lengths, MATRIX points, METRIC METRIC + DEPS: lengths ON graph + +####################################### +# Coloring +####################################### + +igraph_vertex_coloring_greedy: + PARAMS: GRAPH graph, OUT VERTEX_COLORS colors, GREEDY_COLORING_HEURISTIC heuristic=NEIGHBORS + DEPS: colors ON graph + +igraph_is_vertex_coloring: + PARAMS: GRAPH graph, VERTEX_COLORS types, OUT BOOLEAN res + DEPS: types ON graph + +igraph_is_bipartite_coloring: + PARAMS: GRAPH graph, BIPARTITE_TYPES types, OUT BOOLEAN res, OPTIONAL OUT NEIMODE mode + DEPS: types ON graph + +igraph_is_edge_coloring: + PARAMS: GRAPH graph, EDGE_COLORS types, OUT BOOLEAN res + DEPS: types ON graph + +####################################### +# Other, (yet) undocumented functions +####################################### + +igraph_convergence_degree: + PARAMS: GRAPH graph, OUT VECTOR result, OUT VECTOR in, OUT VECTOR out + +igraph_has_attribute_table: + RETURN: BOOLEAN + +####################################### +# Progress, status handling +####################################### + +igraph_progress: + PARAMS: CSTRING message, REAL percent, OPTIONAL EXTRA data + +igraph_status: + PARAMS: CSTRING message, OPTIONAL EXTRA data + +igraph_strerror: + PARAMS: ERROR igraph_errno + RETURN: CSTRING + +####################################### +# Other functions, documented, graph related +####################################### + +igraph_expand_path_to_pairs: + PARAMS: INOUT VERTEX_INDICES path + +igraph_invalidate_cache: + PARAMS: GRAPH graph + RETURN: VOID + +igraph_vertex_path_from_edge_path: + PARAMS: |- + GRAPH graph, OPTIONAL VERTEX start, EDGE_INDICES edge_path, + OUT VERTEX_INDICES vertex_path, NEIMODE mode=OUT + +####################################### +# Meta info +####################################### + +igraph_version: + PARAMS: |- + OPTIONAL OUT CSTRING version_string, OPTIONAL OUT INT major, + OPTIONAL OUT INT minor, OPTIONAL OUT INT subminor + RETURN: VOID diff --git a/interfaces/types.yaml b/interfaces/types.yaml new file mode 100644 index 0000000..eda9572 --- /dev/null +++ b/interfaces/types.yaml @@ -0,0 +1,652 @@ +# vim:set ts=4 sw=4 sts=4 et: +# +# This file is a YAML representation of the types used in the functions.yaml +# function specification file. It provides the meaning of each type in comments +# and also specifies the C types corresponding to each abstract type. +# +# See https://github.com/igraph/stimulus for more information + +############################################################################### +## Core igraph data types +############################################################################### + +INTEGER: + # An ordinary igraph integer + CTYPE: igraph_int_t + +REAL: + # An ordinary igraph floating-point number + CTYPE: igraph_real_t + +BOOLEAN: + # An ordinary igraph Boolean value + CTYPE: igraph_bool_t + +COMPLEX: + # An ordinary igraph complex number + CTYPE: igraph_complex_t + +ERROR: + # An igraph error code + CTYPE: igraph_error_t + +############################################################################### +## C data types +############################################################################### + +INT: + # A C integer + CTYPE: int + +CSTRING: + # A null-terminated immutable C string + CTYPE: const char* + +INFILE: + # A file, already open for reading + CTYPE: FILE* + +OUTFILE: + # A file, already open for writing + CTYPE: FILE* + +DOUBLE: + # A C double + CTYPE: double + +VOID: + # C void + CTYPE: void + +############################################################################### +# Vectors, matrices and other template types +############################################################################### + +INDEX_VECTOR: + # A vector of integer indices that should adapt to the conventions of the + # host language (i.e. 1-based for R, Mathematica, Octave etc., 0-based for + # Python and similar). + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +VECTOR: + # A vector of floating-point numbers + CTYPE: igraph_vector_t + FLAGS: BY_REF + +VECTOR_INT: + # A vector of igraph integers + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +VECTOR_BOOL: + # A vector of Boolean values + CTYPE: igraph_vector_bool_t + FLAGS: BY_REF + +VECTOR_COMPLEX: + # A vector of igraph complex numbers + CTYPE: igraph_vector_complex_t + +VECTOR_STR: + # A vector of strings + CTYPE: igraph_strvector_t + FLAGS: BY_REF + +VECTOR_LIST: + # A list containing vectors of floating-point numbers + CTYPE: igraph_vector_list_t + FLAGS: BY_REF + +VECTOR_INT_LIST: + # A list containing vectors of integers + CTYPE: igraph_vector_int_list_t + FLAGS: BY_REF + +MATRIX: + # A matrix of floating-point numbers + CTYPE: igraph_matrix_t + FLAGS: BY_REF + +MATRIX_INT: + # A matrix of igraph integers + CTYPE: igraph_matrix_int_t + FLAGS: BY_REF + +MATRIX_COMPLEX: + # A matrix of igraph complex numbers + CTYPE: igraph_matrix_complex_t + +MATRIX_LIST: + # A list containing matrices of floating-point numbers + CTYPE: igraph_matrix_list_t + FLAGS: BY_REF + +SPARSEMAT: + # A sparse matrix of floating-point numbers + CTYPE: igraph_sparsemat_t + FLAGS: BY_REF + +############################################################################### +# Vertices, edges, vertex and edge selectors +############################################################################### + +EDGE: + # A single edge index + CTYPE: igraph_int_t + +EDGE_INDICES: + # An integer vector containing edge indices. + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +EDGE_SELECTOR: + # An igraph edge selector. Typically used only as an input argument type. + CTYPE: igraph_es_t + +VERTEX: + # A single vertex index + CTYPE: igraph_int_t + +VERTEX_INDICES: + # An integer vector containing vertex indices. + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +VERTEX_INDEX_PAIRS: + # An integer vector containing pairs of vertex indices, in a flattened + # representation + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +VERTEX_SELECTOR: + # An igraph vertex selector. Typically used only as an input argument type. + CTYPE: igraph_vs_t + +############################################################################### +# Specialized vectors with semantic meaning +############################################################################### + +BIPARTITE_TYPES: + # A vector containing Booleans that define the two partitions of a + # bipartite graph + CTYPE: igraph_vector_bool_t + FLAGS: BY_REF + +EDGE_CAPACITIES: + # A vector containing edge capacities (typically for max-flow algorithms) + CTYPE: igraph_vector_t + FLAGS: BY_REF + +EDGE_COLORS: + # A vector containing edge colors + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +EDGE_LENGTHS: + # A vector containing edge lengths + CTYPE: igraph_vector_t + FLAGS: BY_REF + +EDGE_WEIGHTS: + # A vector containing edge weights + CTYPE: igraph_vector_t + FLAGS: BY_REF + +EDGE_INDICES_LIST: + # A list containing vectors of igraph integers where each such + # vector represents a sequence of edge indices. + CTYPE: igraph_vector_int_list_t + FLAGS: BY_REF + +GRAPH_LIST: + # A list containing graphs (owned by the list itself) + CTYPE: igraph_graph_list_t + FLAGS: BY_REF + +GRAPH_PTR_LIST: + # A vector containing pointers to graph objects (not owned by the vector) + CTYPE: igraph_vector_ptr_t + FLAGS: BY_REF + +ALL_VERTEX_QTY: + # A vector of floating-point numbers where each entry corresponds to + # one of the vertices in a graph and its value represents some quantity + # associated to the vertex with the same index. Higher-level interfaces may + # use this type to provide a "named vector" such that each entry can be + # indexed either by the vertex index or by the vertex name. + CTYPE: igraph_vector_t + FLAGS: BY_REF + +VERTEX_QTY: + # Same as ALL_VERTEX_QTY, but only for a subset of vertices, + # typically referred to by a `vids` argument. + CTYPE: igraph_vector_t + FLAGS: BY_REF + +SIR_LIST: + # A vector containing pointers to igraph_sir_t objects + CTYPE: igraph_vector_ptr_t + FLAGS: BY_REF + +VERTEX_INDICES_LIST: + # A list containing vectors of igraph integers where each such + # vector represents a sequence of vertex indices. + CTYPE: igraph_vector_int_list_t + FLAGS: BY_REF + +VERTEX_COLORS: + # A vector containing vertex colors + CTYPE: igraph_vector_int_t + FLAGS: BY_REF + +VERTEX_WEIGHTS: + # A vector containing vertex weights + CTYPE: igraph_vector_t + FLAGS: BY_REF + +############################################################################### +# Graph representations +############################################################################### + +GRAPH: + # An igraph graph + CTYPE: igraph_t + FLAGS: BY_REF + +ADJLIST: + # A graph represented as an adjacency list + CTYPE: igraph_adjlist_t + FLAGS: BY_REF + +INCLIST: + # A graph represented as an incidence list + CTYPE: igraph_inclist_t + FLAGS: BY_REF + +############################################################################### +# Enums +############################################################################### + +ADD_WEIGHTS: + # Whether to add the weights of the edges read from a file to the graph + # being created + CTYPE: igraph_add_weights_t + FLAGS: ENUM + +ADJACENCY_MODE: + # Enum that describes how an adjacency matrix should be constructed + CTYPE: igraph_adjacency_t + FLAGS: ENUM + +BARABASI_ALGORITHM: + # Enum that describes the various implementations of the Barabasi model + # that igraph supports + CTYPE: igraph_barabasi_algorithm_t + FLAGS: ENUM + +BLISSSH: + # Enum containing splitting heuristics for the Bliss algorithm + CTYPE: igraph_bliss_sh_t + FLAGS: ENUM + +CHUNG_LU_VARIANT: + # Enum that describes the Chung-Lu model variants supported by igraph + CTYPE: igraph_chung_lu_t + FLAGS: ENUM + +COMMCMP: + # Enum containing identifiers for community comparison methods + CTYPE: igraph_community_comparison_t + FLAGS: ENUM + +CONNECTEDNESS: + # Enum that selects between weak and strong connectivity + CTYPE: igraph_connectedness_t + FLAGS: ENUM + +DEGSEQ_MODE: + # Enum that describes the various implementations of generating a graph + # with an arbitrary degree sequence + CTYPE: igraph_degseq_t + FLAGS: ENUM + +EIGENALGO: + # Enum used for selecting an algorithm that determines the eigenvalues + # and eigenvectors of some input + CTYPE: igraph_eigen_algorithm_t + FLAGS: ENUM + +EIGENWHICHPOS: + # Enum representing which eigenvalues to use in the spectral embedding + # algorithm + CTYPE: igraph_eigen_which_position_t + FLAGS: ENUM + +FAS_ALGORITHM: + # Enum representing feedback arc set algorithms + CTYPE: igraph_fas_algorithm_t + FLAGS: ENUM + +FVS_ALGORITHM: + # Enum representing feedback vertex set algorithms + CTYPE: igraph_fvs_algorithm_t + FLAGS: ENUM + +FWALGORITHM: + # Enum that describes the variant of the Floyd-Warshall algorithm to use in + # Floyd-Warshall graph distances computing function + CTYPE: igraph_floyd_warshall_algorithm_t + FLAGS: ENUM + +GETADJACENCY: + # Enum storing how to retrieve the adjacency matrix from a graph + CTYPE: igraph_get_adjacency_t + FLAGS: ENUM + +GREEDY_COLORING_HEURISTIC: + # Enum representing different heuristics for a greedy vertex coloring + CTYPE: igraph_coloring_greedy_t + FLAGS: ENUM + +LAPLACIAN_NORMALIZATION: + # Enum representing the possible normalization methods of a Laplacian + # matrix + CTYPE: igraph_laplacian_normalization_t + FLAGS: ENUM + +LAYOUT_GRID: + # Whether to use the fast (but less accurate) grid-based version of a + # layout algorithm that supports it (typically the Fruchterman-Reingold + # layout) + CTYPE: igraph_layout_grid_t + FLAGS: ENUM + +LEIDEN_OBJECTIVE: + # The objective function to maximize with the Leiden community detection + # method. + CTYPE: igraph_leiden_objective_t + FLAGS: ENUM + +LOOPS: + # Enum that describes how loop edges should be handled in undirected graphs + # in functions that support it. Possible options are: no loops, loops + # counted once, loops counted twice + CTYPE: igraph_loops_t + FLAGS: ENUM + +LSETYPE: + # Enum storing the possible types (definitions) of the Laplacian matrix + # to use in the Laplacian spectral embedding algorithms + CTYPE: igraph_laplacian_spectral_embedding_type_t + FLAGS: ENUM + +METRIC: + # Enum that describes the metric to be used for spatial algorithms + CTYPE: igraph_metric_t + FLAGS: ENUM + +MSTALGORITHM: + # Enum that describes the algorithm for computing a minimum spanning tree + CTYPE: igraph_mst_algorithm_t + FLAGS: ENUM + +NEIMODE: + # Enum that describes how a particular function should take into account + # the neighbors of vertices + CTYPE: igraph_neimode_t + FLAGS: ENUM + +ORDER: + # Whether ordering should be ascending or descending + CTYPE: igraph_order_t + FLAGS: ENUM + +PAGERANKALGO: + # Enum that describes the various implementations of the PageRank algorithm + CTYPE: igraph_pagerank_algo_t + FLAGS: ENUM + +GRAPH_PRODUCT_TYPE: + # Enum that describes the various implementations of the graph products + CTYPE: igraph_product_t + FLAGS: ENUM + +RANDOM_TREE_METHOD: + # Enum that describes the various implementation of the uniform random tree + # sampling method + CTYPE: igraph_random_tree_t + FLAGS: ENUM + +REALIZE_DEGSEQ_METHOD: + # Enum that describes the various methods for realizing a graph with an + # arbitrary degree sequence + CTYPE: igraph_realize_degseq_t + FLAGS: ENUM + +RECIP: + # Enum that describes how the reciprocity of a graph should be calculated + CTYPE: igraph_reciprocity_t + FLAGS: ENUM + +ROOTCHOICE: + # Enum for the heuristic of igraph_roots_for_tree_layout() + CTYPE: igraph_root_choice_t + FLAGS: ENUM + +RWSTUCK: + # Enum that describes what igraph should do when a random walk gets stuck + # in a sink vertex + CTYPE: igraph_random_walk_stuck_t + FLAGS: ENUM + +SPINCOMMUPDATE: + # Enum containing update modes for the spinglass community detection + # algorithm + CTYPE: igraph_spincomm_update_t + FLAGS: ENUM + +SPINGLASS_IMPLEMENTATION: + # Enum that describes the various implementations of the spinglass community + # detection algorithm + CTYPE: igraph_spinglass_implementation_t + FLAGS: ENUM + +STAR_MODE: + # Enum that describes how a star graph should be constructed + CTYPE: igraph_star_mode_t + FLAGS: ENUM + +SUBGRAPH_IMPL: + # Enum that describes how igraph should create an induced subgraph of a + # graph + CTYPE: igraph_subgraph_implementation_t + FLAGS: ENUM + +TODIRECTED: + # Enum representing the possible ways to convert an undirected graph to a + # directed one + CTYPE: igraph_to_directed_t + FLAGS: ENUM + +TOUNDIRECTED: + # Enum representing the possible ways to convert a directed graph to an + # undirected one + CTYPE: igraph_to_undirected_t + FLAGS: ENUM + +TRANSITIVITY_MODE: + # Enum that specifies how isolated vertices should be handled in transitivity + # calcuations + CTYPE: igraph_transitivity_mode_t + FLAGS: ENUM + +TREE_MODE: + # Enum that describes how a tree graph should be constructed + CTYPE: igraph_tree_mode_t + FLAGS: ENUM + +VCONNNEI: + # Enum specifying what to do in vertex connectivity tests when the two + # vertices being tested are already connected + CTYPE: igraph_vconn_nei_t + FLAGS: ENUM + +VORONOI_TIEBREAKER: + # Enum specifying what to do when two vertices are at equal distance from + # multiple generators while computing Voronoi partitionings + CTYPE: igraph_voronoi_tiebreaker_t + FLAGS: ENUM + +WHEEL_MODE: + # Enum that describes how a wheel graph should be constructed + CTYPE: igraph_wheel_mode_t + FLAGS: ENUM + +LPA_VARIANT: + # Enum that describes the label propagation algorithm variant + CTYPE: igraph_lpa_variant_t + FLAGS: ENUM + +############################################################################### +# Switches / flags / bits +############################################################################### + +EDGE_TYPE_SW: + # Flag bitfield that specifies what sort of edges are allowed in an + # algorithm + CTYPE: igraph_edge_type_sw_t + FLAGS: BITS + +WRITE_GML_SW: + # Flag bitfield that specifies how to write GML files. + CTYPE: igraph_write_gml_sw_t + FLAGS: BITS + +############################################################################### +# Callbacks +############################################################################### + +ARPACK_FUNC: + # ARPACK matrix multiplication function. + CTYPE: igraph_arpack_function_t + +CLIQUE_FUNC: + # Callback function for igraph_cliques_callback(). Called with every clique + # that was found by the function. + CTYPE: igraph_clique_handler_t + +CYCLE_FUNC: + # Callback function for igraph_simple_cycles_callback(). Called with every + # cycle that was found by the function. + CTYPE: igraph_cycle_handler_t + +BFS_FUNC: + # Callback function for igraph_bfs(). Called with every vertex that was + # visited during the BFS traversal. + CTYPE: igraph_bfshandler_t + +DFS_FUNC: + # Callback function for igraph_dfs(). Called with every vertex that was + # visited during the DFS traversal. + CTYPE: igraph_dfshandler_t + +ISOCOMPAT_FUNC: + # Callback function for isomorphism algorithms that determines whether two + # vertices are compatible or not. + CTYPE: igraph_isocompat_t + +ISOMORPHISM_FUNC: + # Callback function that is called by isomorphism functions when an + # isomorphism is found + CTYPE: igraph_isohandler_t + +LEVC_FUNC: + # Callback function for igraph_leading_eigenvector_community(). Called + # after each eigenvalue / eigenvector calculation. + CTYPE: igraph_community_leading_eigenvector_callback_t + +############################################################################### +# Miscellaneous +############################################################################### + +ARPACK_OPTIONS: + # Structure that contains the options of the ARPACK eigensolver. + CTYPE: igraph_arpack_options_t + FLAGS: BY_REF + +ARPACK_STORAGE: + # Pointer to a general-purpose memory block that ARPACK-based algorithms + # may use as a working area. + CTYPE: igraph_arpack_storage_t + FLAGS: BY_REF + +ASTAR_HEURISTIC_FUNC: + # A* heuristic function + CTYPE: igraph_astar_heuristic_func_t + +ATTRIBUTES: + # A data structure specifying graph/vertex/edge attributes to add to a + # graph in a low-level igraph C function + CTYPE: igraph_attribute_record_list_t + FLAGS: BY_REF + +BLISSINFO: + # Struct holding information about the internal statistics of a single + # run of the Bliss algorithm + CTYPE: igraph_bliss_info_t + +DRL_OPTIONS: + # Structure containing the options of the DrL layout algorithm + CTYPE: igraph_layout_drl_options_t + FLAGS: BY_REF + +EDGE_ATTRIBUTE_COMBINATION: + # Structure specifying how the attributes of edges should be combined + # during graph operations that may merge multiple edges into a single one + CTYPE: igraph_attribute_combination_t + FLAGS: BY_REF + +EIGENWHICH: + # Structure representing which eigenvalue(s) to use in the spectral embedding + # algorithm + CTYPE: igraph_eigen_which_t + FLAGS: BY_REF + +EXTRA: + # Thunk argument that usually accompanies callback functions and can be used + # to provide user-specific data or context to the callback function + CTYPE: void + FLAGS: BY_REF + +HRG: + # Structure storing a fitted hierarchical random graph model + CTYPE: igraph_hrg_t + FLAGS: BY_REF + +MAXFLOW_STATS: + # Structure storing statistics about a single run of a max-flow algorithm + CTYPE: igraph_maxflow_stats_t + FLAGS: BY_REF + +PAGERANKOPT: + # Enum that describes the PageRank options pointer, which is used only if + # the PageRank implementation uses ARPACK + CTYPE: igraph_arpack_options_t + FLAGS: BY_REF + +PLFIT: + # Structure representing the result of a power-law fitting algorithms + CTYPE: igraph_plfit_result_t + FLAGS: BY_REF + +REWIRING_STATS: + # Structure storing statistics about degree-preserving edge rewiring + CTYPE: igraph_rewiring_stats_t + FLAGS: BY_REF + +VERTEX_ATTRIBUTE_COMBINATION: + # Structure specifying how the attributes of vertices should be combined + # during graph operations that may merge multiple vertices into a single one + CTYPE: igraph_attribute_combination_t + FLAGS: BY_REF diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..db13a6b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,511 @@ + +# Traverse subdirectories +add_subdirectory(centrality/prpack) +add_subdirectory(cliques/cliquer) +add_subdirectory(isomorphism/bliss) + +# Generate lexers and parsers +set(PARSER_SOURCES) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/io/parsers) +foreach(FORMAT dl gml lgl ncol pajek) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/io/parsers/${FORMAT}-parser.c) + list(APPEND PARSER_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/io/parsers/${FORMAT}-lexer.c + ${CMAKE_CURRENT_SOURCE_DIR}/io/parsers/${FORMAT}-parser.c + ) + else() + if (BISON_VERSION VERSION_GREATER_EQUAL 3) + set(bison_no_deprecated -Wno-deprecated) + endif() + if (NOT FLEX_KEEP_LINE_NUMBERS) + set(bison_hide_line_numbers --no-lines) + set(flex_hide_line_numbers --noline) + endif() + bison_target( + ${FORMAT}_parser io/${FORMAT}-parser.y ${CMAKE_CURRENT_BINARY_DIR}/io/parsers/${FORMAT}-parser.c + COMPILE_FLAGS "${bison_hide_line_numbers} ${bison_no_deprecated}" + ) + flex_target( + ${FORMAT}_lexer io/${FORMAT}-lexer.l ${CMAKE_CURRENT_BINARY_DIR}/io/parsers/${FORMAT}-lexer.c + COMPILE_FLAGS "${flex_hide_line_numbers}" + DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/io/parsers/${FORMAT}-lexer.h + ) + add_flex_bison_dependency(${FORMAT}_lexer ${FORMAT}_parser) + list(APPEND PARSER_SOURCES ${BISON_${FORMAT}_parser_OUTPUTS} ${FLEX_${FORMAT}_lexer_OUTPUTS}) + endif() +endforeach() +add_custom_target(parsersources SOURCES ${PARSER_SOURCES}) + +# Declare the files needed to compile the igraph library +add_library( + igraph + core/bitset.c + core/bitset_list.c + core/buckets.c + core/cutheap.c + core/dqueue.c + core/error.c + core/estack.c + core/fixed_vectorlist.c + core/genheap.c + core/grid.c + core/heap.c + core/indheap.c + core/interruption.c + core/marked_queue.c + core/matrix.c + core/matrix_list.c + core/memory.c + core/printing.c + core/progress.c + core/psumtree.c + core/set.c + core/setup.c + core/sparsemat.c + core/stack.c + core/statusbar.c + core/strvector.c + core/trie.c + core/vector.c + core/vector_list.c + core/vector_ptr.c + + math/complex.c + math/safe_intop.c + math/utils.c + + linalg/arpack.c + linalg/blas.c + linalg/eigen.c + linalg/lapack.c + + random/random.c + random/random_device.cpp + random/rng_glibc2.c + random/rng_mt19937.c + random/rng_pcg32.c + random/rng_pcg64.c + random/sampling.c + + graph/adjlist.c + graph/attributes.c + graph/basic_query.c + graph/caching.c + graph/cattributes.c + graph/graph_list.c + graph/iterators.c + graph/type_common.c + graph/type_indexededgelist.c + graph/visitors.c + + constructors/adjacency.c + constructors/atlas.c + constructors/basic_constructors.c + constructors/circulant.c + constructors/de_bruijn.c + constructors/famous.c + constructors/full.c + constructors/generalized_petersen.c + constructors/kautz.c + constructors/lattices.c + constructors/lcf.c + constructors/linegraph.c + constructors/mycielskian.c + constructors/prufer.c + constructors/regular.c + constructors/trees.c + + games/barabasi.c + games/callaway_traits.c + games/chung_lu.c + games/citations.c + games/correlated.c + games/degree_sequence_vl/gengraph_degree_sequence.cpp + games/degree_sequence_vl/gengraph_graph_molloy_hash.cpp + games/degree_sequence_vl/gengraph_graph_molloy_optimized.cpp + games/degree_sequence_vl/gengraph_mr-connected.cpp + games/degree_sequence.c + games/dotproduct.c + games/erdos_renyi.c + games/establishment.c + games/forestfire.c + games/grg.c + games/growing_random.c + games/islands.c + games/k_regular.c + games/preference.c + games/recent_degree.c + games/sbm.c + games/static_fitness.c + games/tree.c + games/watts_strogatz.c + + centrality/betweenness.c + centrality/centrality_other.c + centrality/centralization.c + centrality/closeness.c + centrality/coreness.c + centrality/eigenvector.c + centrality/hub_authority.c + centrality/pagerank.c + centrality/truss.cpp + centrality/prpack.cpp + + cliques/cliquer_wrapper.c + cliques/cliques.c + cliques/maximal_cliques.c + cliques/glet.c + + community/community_misc.c + community/edge_betweenness.c + community/fast_modularity.c + community/fluid.c + community/label_propagation.c + community/leading_eigenvector.c + community/leiden.c + community/louvain.c + community/modularity.c + community/infomap.cpp + community/optimal_modularity.c + community/spinglass/clustertool.cpp + community/spinglass/NetDataTypes.cpp + community/spinglass/NetRoutines.cpp + community/spinglass/pottsmodel_2.cpp + community/voronoi.c + community/walktrap/walktrap_communities.cpp + community/walktrap/walktrap_graph.cpp + community/walktrap/walktrap_heap.cpp + community/walktrap/walktrap.cpp + + connectivity/cohesive_blocks.c + connectivity/components.c + connectivity/percolation.c + connectivity/separators.c + connectivity/reachability.c + + cycles/cycle_bases.c + cycles/feedback_sets.c + cycles/order_cycle.cpp + cycles/simple_cycles.c + + flow/flow.c + flow/flow_conversion.c + flow/st-cuts.c + + hrg/hrg_types.cc + hrg/hrg.cc + + io/dimacs.c + io/dl.c + io/dot.c + io/edgelist.c + io/graphml.c + io/gml-tree.c + io/gml.c + io/graphdb.c + io/leda.c + io/lgl.c + io/ncol.c + io/pajek.c + io/parse_utils.c + ${PARSER_SOURCES} + + layout/align.c + layout/circular.c + layout/davidson_harel.c + layout/drl/DensityGrid.cpp + layout/drl/DensityGrid_3d.cpp + layout/drl/drl_graph.cpp + layout/drl/drl_graph_3d.cpp + layout/drl/drl_layout.cpp + layout/drl/drl_layout_3d.cpp + layout/fruchterman_reingold.c + layout/gem.c + layout/graphopt.c + layout/kamada_kawai.c + layout/large_graph.c + layout/layout_bipartite.c + layout/layout_grid.c + layout/layout_random.c + layout/mds.c + layout/merge_dla.c + layout/merge_grid.c + layout/reingold_tilford.c + layout/sugiyama.c + layout/umap.c + + operators/add_edge.c + operators/complementer.c + operators/compose.c + operators/connect_neighborhood.c + operators/contract.c + operators/difference.c + operators/disjoint_union.c + operators/intersection.c + operators/join.c + operators/misc_internal.c + operators/permute.c + operators/products.c + operators/reverse.c + operators/rewire.c + operators/rewire_edges.c + operators/simplify.c + operators/subgraph.c + operators/union.c + + paths/all_shortest_paths.c + paths/astar.c + paths/bellman_ford.c + paths/dijkstra.c + paths/distances.c + paths/eulerian.c + paths/floyd_warshall.c + paths/histogram.c + paths/johnson.c + paths/random_walk.c + paths/shortest_paths.c + paths/simple_paths.c + paths/sparsifier.c + paths/unweighted.c + paths/voronoi.c + paths/widest_paths.c + + properties/basic_properties.c + properties/complete.c + properties/constraint.c + properties/convergence_degree.c + properties/dag.c + properties/degrees.c + properties/ecc.c + properties/girth.c + properties/loops.c + properties/multiplicity.c + properties/neighborhood.c + properties/perfect.c + properties/rich_club.c + properties/spectral.c + properties/trees.c + properties/triangles.c + + isomorphism/bliss.cc + isomorphism/isoclasses.c + isomorphism/lad.c + isomorphism/isomorphism_misc.c + isomorphism/queries.c + isomorphism/vf2.c + + misc/bipartite.c + misc/chordality.c + misc/cocitation.c + misc/coloring.c + misc/conversion.c + misc/degree_sequence.cpp + misc/embedding.c + misc/graphicality.c + misc/matching.c + misc/mixing.c + misc/motifs.c + misc/other.c + misc/power_law_fit.c + misc/scan.c + misc/sir.c + misc/spanning_trees.c + + spatial/beta_skeleton.cpp + spatial/delaunay.c + spatial/convex_hull.c + spatial/edge_lengths.c + spatial/nearest_neighbor.cpp + + internal/glpk_support.c + internal/hacks.c + internal/lsap.c + internal/qsort_r.c + internal/qsort.c + internal/utils.c + + version.c + + # Vendored library sources. Yes, this is horrible. + $,$,$>,$,> + $,$,> + $,$,> + $,$,> + $,$,> + $,$,> + $,$,> + $,$,> +) + +# Required by Xcode new build system +add_dependencies(igraph parsersources) + +# Set soname for the library +set_target_properties(igraph PROPERTIES VERSION "4.0.1") +set_target_properties(igraph PROPERTIES SOVERSION 4) + +# Add extra compiler definitions if needed +target_compile_definitions( + igraph + PRIVATE + IGRAPH_VERIFY_FINALLY_STACK=$,1,0> +) +# target_compile_options( +# # -Wconversion could be useful? +# igraph PRIVATE -Wshorten-64-to-32 +# ) + +# Make sure that a macro named IGRAPH_FILE_BASENAME is provided in every +# compiler call so we can use these in debug messages without revealing the +# full path of the file on the machine where it was compiled +define_file_basename_for_sources(igraph) + +# Add include path. Includes are in ../include but they get installed to +# /include/igraph, hence the two options. We also have some private +# includes that are generated at compile time but are not part of the public +# interface. +target_include_directories( + igraph + PUBLIC + $ + $ + $ + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/vendor + + # Vendored library include paths + "$<$:$>" + "$<$:$>" + "$<$:$>" + + # Include paths for dependencies + "$<$:${GLPK_INCLUDE_DIR}>" + "$<$:${INFOMAP_INCLUDE_DIR}>" + "$<$:${GMP_INCLUDE_DIR}>" + "$<$:${LIBXML2_INCLUDE_DIRS}>" + "$<$:${PLFIT_INCLUDE_DIRS}>" +) + +if(MATH_LIBRARY) + target_link_libraries(igraph PUBLIC ${MATH_LIBRARY}) +endif() + +if(ARPACK_LIBRARIES) + target_link_libraries(igraph PRIVATE ${ARPACK_LIBRARIES}) +endif() + +if(BLAS_FOUND) + target_link_libraries(igraph PRIVATE ${BLAS_LIBRARIES}) +endif() + +if(GLPK_LIBRARIES) + target_link_libraries(igraph PRIVATE ${GLPK_LIBRARIES}) +endif() + +if(INFOMAP_LIBRARIES) + target_link_libraries(igraph PRIVATE ${INFOMAP_LIBRARIES}) +endif() + +if(GMP_LIBRARIES) + target_link_libraries(igraph PRIVATE ${GMP_LIBRARIES}) +endif() + +if(LAPACK_LIBRARIES) + target_link_libraries(igraph PRIVATE ${LAPACK_LIBRARIES}) +endif() + +if(LIBXML2_LIBRARIES) + target_link_libraries(igraph PRIVATE ${LIBXML2_LIBRARIES}) +endif() + +if(PLFIT_LIBRARIES) + target_link_libraries(igraph PRIVATE ${PLFIT_LIBRARIES}) +endif() + +# Link igraph statically to some of the libraries from the subdirectories +target_link_libraries( + igraph + PRIVATE + bliss cliquer cxsparse_vendored pcg prpack qhull_vendored +) + +if (NOT BUILD_SHARED_LIBS) + target_compile_definitions(igraph PUBLIC IGRAPH_STATIC) +else() + target_compile_definitions(igraph PRIVATE igraph_EXPORTS) +endif() + +if(MSVC) + # Add MSVC-specific include path for some headers that are missing on Windows + target_include_directories(igraph PRIVATE ${PROJECT_SOURCE_DIR}/msvc/include) +endif() + +# Turn on all warnings for GCC, clang and MSVC +use_all_warnings(igraph) + +# GNUInstallDirs be included before generating the pkgconfig file, as it defines +# CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR variables. +include(GNUInstallDirs) + +# Generate pkgconfig file +include(pkgconfig_helpers) + +include(GenerateExportHeader) +generate_export_header(igraph + STATIC_DEFINE IGRAPH_STATIC + EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/igraph_export.h +) + +# Provide an igraph-config.cmake file in the installation directory so +# users can find the installed igraph library with FIND_PACKAGE(igraph) +# from their CMakeLists.txt files +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/etc/cmake/igraph-config.cmake.in + ${PROJECT_BINARY_DIR}/igraph-config.cmake + # Install destination selected according to https://wiki.debian.org/CMake + # and by looking at how eigen3 does it in Ubuntu + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/igraph +) +write_basic_package_version_file( + ${PROJECT_BINARY_DIR}/igraph-config-version.cmake + VERSION ${PACKAGE_VERSION_BASE} + COMPATIBILITY SameMinorVersion +) + +# Define how to install the library +install( + TARGETS igraph bliss cliquer cxsparse_vendored pcg prpack qhull_vendored + EXPORT igraph_targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) +install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/igraph + FILES_MATCHING PATTERN "*.h" +) +install( + DIRECTORY ${PROJECT_BINARY_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/igraph + FILES_MATCHING PATTERN "*.h" +) +install( + FILES ${PROJECT_BINARY_DIR}/igraph.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig +) +install( + FILES ${PROJECT_BINARY_DIR}/igraph-config.cmake + ${PROJECT_BINARY_DIR}/igraph-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/igraph +) +install( + EXPORT igraph_targets + FILE igraph-targets.cmake + NAMESPACE igraph:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/igraph +) diff --git a/src/centrality/betweenness.c b/src/centrality/betweenness.c new file mode 100644 index 0000000..03f7948 --- /dev/null +++ b/src/centrality/betweenness.c @@ -0,0 +1,1404 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_nongraph.h" +#include "igraph_progress.h" +#include "igraph_stack.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +/* + * We provide separate implementations of single-source shortest path searches, + * one with incidence lists and one with adjacency lists. We use the implementation + * based on adjacency lists when possible (i.e. when weights are not needed) to + * avoid an expensive IGRAPH_OTHER() lookup on edge IDs. The cost of this macro + * comes from the inability of the branch predictor to predict accurately whether + * the condition in the macro will be true or not. + * + * The following four functions are very similar in their structure. If you make + * a modification to one of them, consider whether the same modification makes + * sense in the context of the remaining three functions as well. + */ + +/** + * Internal function to calculate the single source shortest paths for the + * vertex unweighted case. + * + * \param graph the graph to calculate the single source shortest paths on + * \param source the source node + * \param dist distance of each node from the source node \em plus one; + * must be filled with zeros initially + * \param nrgeo vector storing the number of geodesics from the source node + * to each node; must be filled with zeros initially + * \param stack stack in which the nodes are pushed in the order they are + * discovered during the traversal + * \param parents adjacent list that starts empty and that stores the IDs + * of the vertices that lead to a given node during the traversal + * \param adjlist the adjacency list of the graph + * \param cutoff cutoff length of shortest paths + */ +static igraph_error_t sspf( + igraph_int_t source, + igraph_vector_t *dist, + igraph_real_t *nrgeo, + igraph_stack_int_t *stack, + igraph_adjlist_t *parents, + const igraph_adjlist_t *adjlist, + igraph_real_t cutoff) { + + igraph_dqueue_int_t queue; + const igraph_vector_int_t *neis; + igraph_vector_int_t *v; + igraph_int_t nlen; + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&queue, 100); + + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, source)); + VECTOR(*dist)[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_dqueue_int_empty(&queue)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&queue); + + /* Ignore vertices that are more distant than the cutoff */ + if (cutoff >= 0 && VECTOR(*dist)[actnode] > cutoff + 1) { + /* Reset variables if node is too distant */ + VECTOR(*dist)[actnode] = 0; + nrgeo[actnode] = 0; + igraph_vector_int_clear(igraph_adjlist_get(parents, actnode)); + continue; + } + + /* Record that we have visited this node */ + IGRAPH_CHECK(igraph_stack_int_push(stack, actnode)); + + /* Examine the neighbors of this node */ + neis = igraph_adjlist_get(adjlist, actnode); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + + if (VECTOR(*dist)[neighbor] == 0) { + /* We have found 'neighbor' for the first time */ + VECTOR(*dist)[neighbor] = VECTOR(*dist)[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, neighbor)); + } + + if (VECTOR(*dist)[neighbor] == VECTOR(*dist)[actnode] + 1 && + (VECTOR(*dist)[neighbor] <= cutoff + 1 || cutoff < 0)) { + /* Only add if the node is not more distant than the cutoff */ + v = igraph_adjlist_get(parents, neighbor); + IGRAPH_CHECK(igraph_vector_int_push_back(v, actnode)); + nrgeo[neighbor] += nrgeo[actnode]; + } + } + } + + igraph_dqueue_int_destroy(&queue); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Internal function to calculate the single source shortest paths for the + * edge unweighted case. + * + * \param graph the graph to calculate the single source shortest paths on + * \param source the source node + * \param dist distance of each node from the source node \em plus one; + * must be filled with zeros initially + * \param nrgeo vector storing the number of geodesics from the source node + * to each node; must be filled with zeros initially + * \param stack stack in which the nodes are pushed in the order they are + * discovered during the traversal + * \param parents incidence list that starts empty and that stores the IDs + * of the edges that lead to a given node during the traversal + * \param inclist the incidence list of the graph + * \param cutoff cutoff length of shortest paths + */ +static igraph_error_t sspf_edge( + const igraph_t *graph, + igraph_int_t source, + igraph_vector_t *dist, + igraph_real_t *nrgeo, + igraph_stack_int_t *stack, + igraph_inclist_t *parents, + const igraph_inclist_t *inclist, + igraph_real_t cutoff) { + + igraph_dqueue_int_t queue; + const igraph_vector_int_t *neis; + igraph_vector_int_t *v; + igraph_int_t nlen; + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&queue, 100); + + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, source)); + VECTOR(*dist)[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_dqueue_int_empty(&queue)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&queue); + + /* Ignore vertices that are more distant than the cutoff */ + if (cutoff >= 0 && VECTOR(*dist)[actnode] > cutoff + 1) { + /* Reset variables if node is too distant */ + VECTOR(*dist)[actnode] = 0; + nrgeo[actnode] = 0; + igraph_vector_int_clear(igraph_inclist_get(parents, actnode)); + continue; + } + + /* Record that we have visited this node */ + IGRAPH_CHECK(igraph_stack_int_push(stack, actnode)); + + /* Examine the neighbors of this node */ + neis = igraph_inclist_get(inclist, actnode); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, actnode); + + if (VECTOR(*dist)[neighbor] == 0) { + /* We have found 'neighbor' for the first time */ + VECTOR(*dist)[neighbor] = VECTOR(*dist)[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, neighbor)); + } + + if (VECTOR(*dist)[neighbor] == VECTOR(*dist)[actnode] + 1 && + (VECTOR(*dist)[neighbor] <= cutoff + 1 || cutoff < 0)) { + /* Only add if the node is not more distant than the cutoff */ + v = igraph_inclist_get(parents, neighbor); + IGRAPH_CHECK(igraph_vector_int_push_back(v, edge)); + nrgeo[neighbor] += nrgeo[actnode]; + } + } + } + + igraph_dqueue_int_destroy(&queue); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Internal function to calculate the single source shortest paths for the vertex + * weighted case. + * + * \param graph the graph to calculate the single source shortest paths on + * \param weights the weights of the edges + * \param source the source node + * \param dist distance of each node from the source node \em plus one; + * must be filled with zeros initially + * \param nrgeo vector storing the number of geodesics from the source node + * to each node; must be filled with zeros initially + * \param stack stack in which the nodes are pushed in the order they are + * discovered during the traversal + * \param parents adjacency list that starts empty and that stores the IDs + * of the vertices that lead to a given node during the traversal + * \param inclist the incidence list of the graph + * \param cutoff cutoff length of shortest paths + */ +static igraph_error_t sspf_weighted( + const igraph_t *graph, + igraph_int_t source, + igraph_vector_t *dist, + igraph_real_t *nrgeo, + const igraph_vector_t *weights, + igraph_stack_int_t *stack, + igraph_adjlist_t *parents, + const igraph_inclist_t *inclist, + igraph_real_t cutoff) { + + const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON; + + int cmp_result; + igraph_2wheap_t queue; + const igraph_vector_int_t *neis; + igraph_vector_int_t *v; + igraph_int_t nlen; + + /* TODO: this is an O|V| step here. We could save some time by pre-allocating + * the two-way heap in the caller and re-using it here */ + IGRAPH_CHECK(igraph_2wheap_init(&queue, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_2wheap_destroy, &queue); + + igraph_2wheap_push_with_index(&queue, source, -1.0); /* reserved */ + VECTOR(*dist)[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_2wheap_empty(&queue)) { + igraph_int_t minnei = igraph_2wheap_max_index(&queue); + igraph_real_t mindist = -igraph_2wheap_delete_max(&queue); + + /* Ignore vertices that are more distant than the cutoff */ + if (cutoff >= 0 && mindist > cutoff + 1.0) { + /* Reset variables if node is too distant */ + VECTOR(*dist)[minnei] = 0; + nrgeo[minnei] = 0; + igraph_vector_int_clear(igraph_adjlist_get(parents, minnei)); + continue; + } + + /* Record that we have visited this node */ + IGRAPH_CHECK(igraph_stack_int_push(stack, minnei)); + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_inclist_get(inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(*dist)[to]; + + if (curdist == 0) { + /* this means curdist is infinity */ + cmp_result = -1; + } else { + cmp_result = igraph_cmp_epsilon(altdist, curdist, eps); + } + + if (curdist == 0) { + /* This is the first non-infinite distance */ + v = igraph_adjlist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_resize(v, 1)); + VECTOR(*v)[0] = minnei; + nrgeo[to] = nrgeo[minnei]; + VECTOR(*dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&queue, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + v = igraph_adjlist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_resize(v, 1)); + VECTOR(*v)[0] = minnei; + nrgeo[to] = nrgeo[minnei]; + VECTOR(*dist)[to] = altdist; + igraph_2wheap_modify(&queue, to, -altdist); + } else if (cmp_result == 0 && (altdist <= cutoff + 1.0 || cutoff < 0)) { + /* Only add if the node is not more distant than the cutoff */ + v = igraph_adjlist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_push_back(v, minnei)); + nrgeo[to] += nrgeo[minnei]; + } + } + } + + igraph_2wheap_destroy(&queue); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Internal function to calculate the single source shortest paths for the edge + * weighted case. + * + * \param graph the graph to calculate the single source shortest paths on + * \param weights the weights of the edges + * \param source the source node + * \param dist distance of each node from the source node \em plus one; + * must be filled with zeros initially + * \param nrgeo vector storing the number of geodesics from the source node + * to each node; must be filled with zeros initially + * \param stack stack in which the nodes are pushed in the order they are + * discovered during the traversal + * \param parents incidence list that starts empty and that stores the IDs + * of the edges that lead to a given node during the traversal + * \param inclist the incidence list of the graph + * \param cutoff cutoff length of shortest paths + */ +static igraph_error_t sspf_weighted_edge( + const igraph_t *graph, + igraph_int_t source, + igraph_vector_t *dist, + igraph_real_t *nrgeo, + const igraph_vector_t *weights, + igraph_stack_int_t *stack, + igraph_inclist_t *parents, + const igraph_inclist_t *inclist, + igraph_real_t cutoff) { + + const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON; + + int cmp_result; + igraph_2wheap_t queue; + const igraph_vector_int_t *neis; + igraph_vector_int_t *v; + igraph_int_t nlen; + + /* TODO: this is an O|V| step here. We could save some time by pre-allocating + * the two-way heap in the caller and re-using it here */ + IGRAPH_CHECK(igraph_2wheap_init(&queue, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_2wheap_destroy, &queue); + + igraph_2wheap_push_with_index(&queue, source, -1.0); /* reserved */ + VECTOR(*dist)[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_2wheap_empty(&queue)) { + igraph_int_t minnei = igraph_2wheap_max_index(&queue); + igraph_real_t mindist = -igraph_2wheap_delete_max(&queue); + + /* Ignore vertices that are more distant than the cutoff */ + if (cutoff >= 0 && mindist > cutoff + 1.0) { + /* Reset variables if node is too distant */ + VECTOR(*dist)[minnei] = 0; + nrgeo[minnei] = 0; + igraph_vector_int_clear(igraph_inclist_get(parents, minnei)); + continue; + } + + /* Record that we have visited this node */ + IGRAPH_CHECK(igraph_stack_int_push(stack, minnei)); + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_inclist_get(inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(*dist)[to]; + + if (curdist == 0) { + /* this means curdist is infinity */ + cmp_result = -1; + } else { + cmp_result = igraph_cmp_epsilon(altdist, curdist, eps); + } + + if (curdist == 0) { + /* This is the first non-infinite distance */ + v = igraph_inclist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_resize(v, 1)); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + VECTOR(*dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&queue, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + v = igraph_inclist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_resize(v, 1)); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + VECTOR(*dist)[to] = altdist; + igraph_2wheap_modify(&queue, to, -altdist); + } else if (cmp_result == 0 && (altdist <= cutoff + 1.0 || cutoff < 0)) { + /* Only add if the node is not more distant than the cutoff */ + v = igraph_inclist_get(parents, to); + IGRAPH_CHECK(igraph_vector_int_push_back(v, edge)); + nrgeo[to] += nrgeo[minnei]; + } + } + } + + igraph_2wheap_destroy(&queue); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t betweenness_check_weights( + const igraph_vector_t *weights, igraph_int_t no_of_edges +) { + igraph_real_t minweight; + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Edge weight vector length must match the number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Edge weights must be positive for betweenness.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Edge weights must not contain NaN values.", IGRAPH_EINVAL); + } else if (minweight <= IGRAPH_SHORTEST_PATH_EPSILON) { + IGRAPH_WARNINGF( + "Some weights are smaller than the path length comparison tolerance (%g), " + "betweenness calculations may suffer from numerical precision issues.", + IGRAPH_SHORTEST_PATH_EPSILON + ); + } + } + } + + return IGRAPH_SUCCESS; +} + +/***** Vertex betweenness *****/ + +/** + * \ingroup structural + * \function igraph_betweenness + * \brief Betweenness centrality of some vertices. + * + * The betweenness centrality of a vertex \c v is the number of shortest paths + * passing through it. If there is more than one shortest path between two + * vertices, the fraction of these passing through \c v is counted. + * + * + * Reference: + * + * + * Ulrik Brandes: A faster algorithm for betweenness centrality. + * The Journal of Mathematical Sociology, 25(2), 163–177 (2001). + * https://doi.org/10.1080/0022250X.2001.9990249 + * + * \param graph The graph object. + * \param weights An optional vector containing edge weights for + * calculating weighted betweenness. No edge weight may be NaN. + * Supply a null pointer here for unweighted betweenness. + * \param res The result of the computation, a vector containing the + * betweenness scores for the specified vertices. + * \param vids The vertices for which the range-limited betweenness centrality + * scores will be returned. This paramerer is for convenience only and + * does not affect performance. Internally, the betewenness of all + * vertices is calculated. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores by the number of + * vertex pairs. In directed graphs, the number of ordered vertex pairs, + * in undirected graphs the number of unordered vertex pairs is used. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID passed in \p vids. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * Note that the time complexity is independent of the number of + * vertices for which the score is calculated. + * + * \sa \ref igraph_edge_betweenness() for calculating the betweenness score + * of the edges in a graph; \ref igraph_betweenness_cutoff() to + * calculate the range-limited betweenness of the vertices in a graph; + * \ref igraph_betweenness_subset() to consider shortest paths only between + * two vertex subsets for calculating betweenness. + */ +igraph_error_t igraph_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized) { + return igraph_betweenness_cutoff(graph, weights, res, vids, directed, normalized, -1); +} + +/** + * \ingroup structural + * \function igraph_betweenness_cutoff + * \brief Range-limited betweenness centrality. + * + * This function computes a range-limited version of betweenness centrality + * by considering only those shortest paths whose length is no greater + * then the given cutoff value. + * + * \param graph The graph object. + * \param weights An optional vector containing edge weights for + * calculating weighted betweenness. No edge weight may be NaN. + * Supply a null pointer here for unweighted betweenness. + * \param res The result of the computation, a vector containing the + * range-limited betweenness scores for the specified vertices. + * \param vids The vertices for which the range-limited betweenness centrality + * scores will be returned. This paramerer is for convenience only and + * does not affect performance. Internally, the betewenness of all + * vertices is calculated. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores by the number of + * vertex pairs. In directed graphs, the number of ordered vertex pairs, + * in undirected graphs the number of unordered vertex pairs is used. + * \param cutoff The maximal length of paths that will be considered. + * If negative or \ref IGRAPH_UNLIMITED, the exact betweenness will be + * calculated, and there will be no upper limit on path lengths. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID passed in \p vids. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * Note that the time complexity is independent of the number of + * vertices for which the score is calculated. + * + * \sa \ref igraph_betweenness() to calculate the exact betweenness and + * \ref igraph_edge_betweenness_cutoff() to calculate the range-limited + * edge betweenness. + */ +igraph_error_t igraph_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_adjlist_t adjlist, parents; + igraph_inclist_t inclist; + igraph_int_t source, j, neighbor; + igraph_stack_int_t S; + igraph_neimode_t mode; + igraph_vector_t dist; + /* Note: nrgeo holds the number of shortest paths, which may be very large in some cases, + * e.g. in a grid graph. If using an integer type, this results in overflow. + * With a 'long long int', overflow already affects the result for a grid as small as 36*36. + * Therefore, we use a 'igraph_real_t' instead. While a 'igraph_real_t' holds fewer digits than a + * 'long long int', i.e. its precision is lower, it is effectively immune to overflow. + * The impact on the precision of the final result is negligible. The max betweenness + * is correct to 14 decimal digits, i.e. the precision limit of 'igraph_real_t', even + * for a 101*101 grid graph. */ + igraph_real_t *nrgeo = 0; + igraph_real_t *tmpscore; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_vit_t vit; + igraph_real_t normalization_factor; + + if (!igraph_is_directed(graph)) { + directed = false; + } + mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + + IGRAPH_CHECK(betweenness_check_weights(weights, no_of_edges)); + + if (weights) { + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + } + + IGRAPH_CHECK(igraph_adjlist_init_empty(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &parents); + + IGRAPH_CHECK(igraph_stack_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &S); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + + nrgeo = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for betweenness calculation."); + IGRAPH_FINALLY(igraph_free, nrgeo); + + tmpscore = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(tmpscore, "Insufficient memory for betweenness calculation."); + IGRAPH_FINALLY(igraph_free, tmpscore); + + if (igraph_vs_is_all(&vids)) { + /* result covers all vertices */ + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + tmpres = res; + } else { + /* result needed only for a subset of the vertices */ + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_nodes); + } + + for (source = 0; source < no_of_nodes; source++) { + + /* Loop invariant that is valid at this point: + * + * - the stack S is empty + * - the 'dist' vector contains zeros only + * - the 'nrgeo' array contains zeros only + * - the 'tmpscore' array contains zeros only + * - the 'parents' adjacency list contains empty vectors only + */ + + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + /* Conduct a single-source shortest path search from the source node */ + if (weights) { + IGRAPH_CHECK(sspf_weighted(graph, source, &dist, nrgeo, weights, &S, &parents, &inclist, cutoff)); + } else { + IGRAPH_CHECK(sspf(source, &dist, nrgeo, &S, &parents, &adjlist, cutoff)); + } + + /* Aggregate betweenness scores for the nodes we have reached in this + * traversal */ + while (!igraph_stack_int_empty(&S)) { + igraph_int_t actnode = igraph_stack_int_pop(&S); + igraph_vector_int_t *neis = igraph_adjlist_get(&parents, actnode); + igraph_int_t nneis = igraph_vector_int_size(neis); + igraph_real_t coeff = (1 + tmpscore[actnode]) / nrgeo[actnode]; + + for (j = 0; j < nneis; j++) { + neighbor = VECTOR(*neis)[j]; + tmpscore[neighbor] += nrgeo[neighbor] * coeff; + } + + if (actnode != source) { + VECTOR(*tmpres)[actnode] += tmpscore[actnode]; + } + + /* Reset variables to ensure that the 'for' loop invariant will + * still be valid in the next iteration */ + + VECTOR(dist)[actnode] = 0; + nrgeo[actnode] = 0; + tmpscore[actnode] = 0; + igraph_vector_int_clear(neis); + } + + } /* for source < no_of_nodes */ + + /* Keep only the requested vertices */ + if (!igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (j = 0, IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), j++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + VECTOR(*res)[j] = VECTOR(*tmpres)[node]; + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + /* The betweenness calculation is always done for all _ordered_ vertex pairs. + * We only need to correct for this in undirected graphs when normalized=false. */ + if (normalized) { + normalization_factor = 1.0 / (no_of_nodes * (no_of_nodes - 1.0)); + } else { + normalization_factor = directed ? 1.0 : 0.5; + } + + igraph_vector_scale(res, normalization_factor); + + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0, 0); + + IGRAPH_FREE(nrgeo); + IGRAPH_FREE(tmpscore); + igraph_vector_destroy(&dist); + igraph_stack_int_destroy(&S); + igraph_adjlist_destroy(&parents); + if (weights) { + igraph_inclist_destroy(&inclist); + } else { + igraph_adjlist_destroy(&adjlist); + } + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/***** Edge betweenness *****/ + + +/** + * \ingroup structural + * \function igraph_edge_betweenness + * \brief Betweenness centrality of the edges. + * + * The betweenness centrality of an edge \c e is the number of shortest paths + * passing through it. If there is more than one shortest path between two + * vertices, the fraction of these passing through \c e is counted. + * + * + * Reference: + * + * + * Ulrik Brandes: A faster algorithm for betweenness centrality. + * The Journal of Mathematical Sociology, 25(2), 163–177 (2001). + * https://doi.org/10.1080/0022250X.2001.9990249 + * + * \param graph The graph object. + * \param weights An optional weight vector for weighted edge + * betweenness. No edge weight may be NaN. Supply a null + * pointer here for the unweighted version. + * \param res The result of the computation, vector containing the + * betweenness scores for the edges. + * \param eids The edges for which the betweenness centrality will be returned. + * This parameter is for convenience only, and does not affect + * performance. Internally, the betweenness is be calculated for all + * edges. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores by the number of + * vertex pairs. In directed graphs, the number of ordered vertex pairs, + * in undirected graphs the number of unordered vertex pairs is used. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_betweenness() for calculating the betweenness score of the + * vertices in a graph; \ref igraph_edge_betweenness_cutoff() to compute the + * range-limited betweenness score of the edges in a graph; + * \ref igraph_edge_betweenness_subset() to consider shortest paths only between + * two vertex subsets for calculating betweenness. + */ +igraph_error_t igraph_edge_betweenness( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized) { + + return igraph_edge_betweenness_cutoff( + graph, weights, + res, eids, + directed, normalized, -1); +} + +/** + * \ingroup structural + * \function igraph_edge_betweenness_cutoff + * \brief Range-limited betweenness centrality of the edges. + * + * This function computes a range-limited version of edge betweenness centrality + * by considering only those shortest paths whose length is no greater + * then the given cutoff value. + * + * \param graph The graph object. + * \param weights An optional weight vector for weighted + * betweenness. No edge weight may be NaN. Supply a null + * pointer here for unweighted betweenness. + * \param res The result of the computation, vector containing the + * betweenness scores for the edges. + * \param eids The edges for which the betweenness centrality will be returned. + * This parameter is for convenience only, and does not affect + * performance. Internally, the betweenness is be calculated for all + * edges. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores by the number of + * vertex pairs. In directed graphs, the number of ordered vertex pairs, + * in undirected graphs the number of unordered vertex pairs is used. + * \param cutoff The maximal length of paths that will be considered. + * If negative of \ref IGRAPH_UNLIMITED, the exact betweenness will be + * calculated (no upper limit on path lengths). + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_edge_betweenness() to compute the exact edge betweenness and + * \ref igraph_betweenness_cutoff() to compute the range-limited vertex betweenness. + */ +igraph_error_t igraph_edge_betweenness_cutoff( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized, + igraph_real_t cutoff) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_inclist_t inclist, parents; + igraph_neimode_t mode; + igraph_vector_t dist; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_real_t *nrgeo; + igraph_real_t *tmpscore; + igraph_int_t source, j; + igraph_stack_int_t S; + igraph_real_t normalization_factor; + + if (!igraph_is_directed(graph)) { + directed = false; + } + mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + + IGRAPH_CHECK(betweenness_check_weights(weights, no_of_edges)); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_inclist_init_empty(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_inclist_destroy, &parents); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + + nrgeo = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for edge betweenness calculation."); + IGRAPH_FINALLY(igraph_free, nrgeo); + + tmpscore = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + if (tmpscore == 0) { + IGRAPH_ERROR("Insufficient memory for edge betweenness calculation.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmpscore); + + IGRAPH_CHECK(igraph_stack_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &S); + + if (!igraph_es_is_all(&eids)) { + /* result needed only for a subset of the vertices */ + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_edges); + } else { + /* result covers all vertices */ + IGRAPH_CHECK(igraph_vector_resize(res, no_of_edges)); + igraph_vector_null(res); + tmpres = res; + } + + for (source = 0; source < no_of_nodes; source++) { + + /* Loop invariant that is valid at this point: + * + * - the stack S is empty + * - the 'dist' vector contains zeros only + * - the 'nrgeo' array contains zeros only + * - the 'tmpscore' array contains zeros only + * - the 'parents' incidence list contains empty vectors only + */ + + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + /* Conduct a single-source shortest path search from the source node */ + if (weights) { + IGRAPH_CHECK(sspf_weighted_edge(graph, source, &dist, nrgeo, weights, &S, &parents, &inclist, cutoff)); + } else { + IGRAPH_CHECK(sspf_edge(graph, source, &dist, nrgeo, &S, &parents, &inclist, cutoff)); + } + + /* Aggregate betweenness scores for the edges we have reached in this + * traversal */ + while (!igraph_stack_int_empty(&S)) { + igraph_int_t actnode = igraph_stack_int_pop(&S); + igraph_vector_int_t *parentv = igraph_inclist_get(&parents, actnode); + igraph_int_t parentv_len = igraph_vector_int_size(parentv); + igraph_real_t coeff = (1 + tmpscore[actnode]) / nrgeo[actnode]; + + for (j = 0; j < parentv_len; j++) { + igraph_int_t fedge = VECTOR(*parentv)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, fedge, actnode); + tmpscore[neighbor] += nrgeo[neighbor] * coeff; + VECTOR(*tmpres)[fedge] += nrgeo[neighbor] * coeff; + } + + /* Reset variables to ensure that the 'for' loop invariant will + * still be valid in the next iteration */ + + VECTOR(dist)[actnode] = 0; + nrgeo[actnode] = 0; + tmpscore[actnode] = 0; + igraph_vector_int_clear(parentv); + } + } /* source < no_of_nodes */ + + /* Keep only the requested edges */ + if (!igraph_es_is_all(&eids)) { + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (j = 0, IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), j++) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + VECTOR(*res)[j] = VECTOR(*tmpres)[edge]; + } + + igraph_eit_destroy(&eit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + /* The betweenness calculation is always done for all _ordered_ vertex pairs. + * We only need to correct for this in undirected graphs when normalized=false. */ + if (normalized) { + normalization_factor = 1.0 / (no_of_nodes * (no_of_nodes - 1.0)); + } else { + normalization_factor = directed ? 1.0 : 0.5; + } + + igraph_vector_scale(res, normalization_factor); + + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0, 0); + + igraph_stack_int_destroy(&S); + igraph_inclist_destroy(&inclist); + igraph_inclist_destroy(&parents); + igraph_vector_destroy(&dist); + IGRAPH_FREE(tmpscore); + IGRAPH_FREE(nrgeo); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_betweenness_subset + * \brief Betweenness centrality for a subset of source and target vertices. + * + * This function computes the subset-limited version of betweenness centrality + * by considering only those shortest paths that lie between vertices in a given + * source and target subset. + * + * \param graph The graph object. + * \param weights An optional vector containing edge weights for + * calculating weighted betweenness. No edge weight may be NaN. + * Supply a null pointer here for unweighted betweenness. + * \param res The result of the computation, a vector containing the + * betweenness score for the subset of vertices. + * \param sources A vertex selector for the sources of the shortest paths taken + * into considuration in the betweenness calculation. + * \param targets A vertex selector for the targets of the shortest paths taken + * into considuration in the betweenness calculation. + * \param vids The vertices for which the subset-limited betweenness centrality + * scores will be computed. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores. Normalization is + * currently unimplemented, and setting this to \c true raises an error. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID passed in \p vids, + * \p sources or \p targets + * + * Time complexity: O(|S||E|), where + * |S| is the number of vertices in the subset and + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_betweenness() to calculate the exact vertex betweenness and + * \ref igraph_betweenness_cutoff() to calculate the range-limited vertex + * betweenness. + */ +igraph_error_t igraph_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_vs_t vids, + igraph_bool_t directed, igraph_bool_t normalized) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_sources; + igraph_int_t no_of_processed_sources; + igraph_adjlist_t adjlist, parents; + igraph_inclist_t inclist; + igraph_int_t source, j; + igraph_stack_int_t S; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_neimode_t mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + igraph_int_t parent; + igraph_vector_t dist; + igraph_real_t *nrgeo; + igraph_real_t *tmpscore; + igraph_vit_t vit; + bool *is_target; + + if (normalized) { + IGRAPH_ERROR("Normalization is not yet implemented for subset betweenness.", IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(betweenness_check_weights(weights, no_of_edges)); + + IGRAPH_CHECK(igraph_vs_size(graph, &sources, &no_of_sources)); + + if (weights) { + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + } + + IGRAPH_CHECK(igraph_adjlist_init_empty(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &parents); + + IGRAPH_CHECK(igraph_stack_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &S); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + + nrgeo = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for subset betweenness calculation."); + IGRAPH_FINALLY(igraph_free, nrgeo); + + tmpscore = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(tmpscore, "Insufficient memory for subset betweenness calculation."); + IGRAPH_FINALLY(igraph_free, tmpscore); + + is_target = IGRAPH_CALLOC(no_of_nodes, bool); + IGRAPH_CHECK_OOM(is_target, "Insufficient memory for subset betweenness calculation."); + IGRAPH_FINALLY(igraph_free, is_target); + + IGRAPH_CHECK(igraph_vit_create(graph, targets, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + is_target[IGRAPH_VIT_GET(vit)] = true; + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + if (!igraph_vs_is_all(&vids)) { + /* result needed only for a subset of the vertices */ + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_nodes); + } else { + /* result covers all vertices */ + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + tmpres = res; + } + + IGRAPH_CHECK(igraph_vit_create(graph, sources, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + for ( + no_of_processed_sources = 0, IGRAPH_VIT_RESET(vit); + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), no_of_processed_sources++ + ) { + source = IGRAPH_VIT_GET(vit); + + IGRAPH_PROGRESS( + "Betweenness centrality (subset): ", + 100.0 * no_of_processed_sources / no_of_sources, 0 + ); + IGRAPH_ALLOW_INTERRUPTION(); + + /* Loop invariant that is valid at this point: + * + * - the stack S is empty + * - the 'dist' vector contains zeros only + * - the 'nrgeo' array contains zeros only + * - the 'tmpscore' array contains zeros only + * - the 'parents' adjacency list contains empty vectors only + */ + + /* TODO: there is more room for optimization here; the single-source + * shortest path search runs until it reaches all the nodes in the + * component of the source node even if we are only interested in a + * smaller target subset. We could stop the search when all target + * nodes were reached. + */ + + /* Conduct a single-source shortest path search from the source node */ + if (weights) { + IGRAPH_CHECK(sspf_weighted(graph, source, &dist, nrgeo, weights, &S, &parents, &inclist, -1)); + } else { + IGRAPH_CHECK(sspf(source, &dist, nrgeo, &S, &parents, &adjlist, -1)); + } + + /* Aggregate betweenness scores for the nodes we have reached in this + * traversal */ + while (!igraph_stack_int_empty(&S)) { + igraph_int_t actnode = igraph_stack_int_pop(&S); + igraph_vector_int_t *parentv = igraph_adjlist_get(&parents, actnode); + igraph_int_t parentv_len = igraph_vector_int_size(parentv); + igraph_real_t coeff; + + if (is_target[actnode]) { + coeff = (1 + tmpscore[actnode]) / nrgeo[actnode]; + } else { + coeff = tmpscore[actnode] / nrgeo[actnode]; + } + + for (j = 0; j < parentv_len; j++) { + parent = VECTOR(*parentv)[j]; + tmpscore[parent] += nrgeo[parent] * coeff; + } + + if (actnode != source) { + VECTOR(*tmpres)[actnode] += tmpscore[actnode]; + } + + /* Reset variables to ensure that the 'for' loop invariant will + * still be valid in the next iteration */ + + VECTOR(dist)[actnode] = 0; + nrgeo[actnode] = 0; + tmpscore[actnode] = 0; + igraph_vector_int_clear(parentv); + } + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + /* Keep only the requested vertices */ + if (!igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + for (j = 0, IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), j++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + VECTOR(*res)[j] = VECTOR(*tmpres)[node]; + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + if (!directed || !igraph_is_directed(graph)) { + igraph_vector_scale(res, 0.5); + } + + IGRAPH_FREE(is_target); + IGRAPH_FREE(tmpscore); + IGRAPH_FREE(nrgeo); + igraph_vector_destroy(&dist); + igraph_stack_int_destroy(&S); + igraph_adjlist_destroy(&parents); + if (weights) { + igraph_inclist_destroy(&inclist); + } else { + igraph_adjlist_destroy(&adjlist); + } + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_edge_betweenness_subset + * \brief Edge betweenness centrality for a subset of source and target vertices. + * + * This function computes the subset-limited version of edge betweenness centrality + * by considering only those shortest paths that lie between vertices in a given + * source and target subset. + * + * \param graph The graph object. + * \param weights An optional weight vector for weighted + * betweenness. No edge weight may be NaN. Supply a null + * pointer here for unweighted betweenness. + * \param res The result of the computation, vector containing the + * betweenness scores for the edges. + * \param sources A vertex selector for the sources of the shortest paths taken + * into considuration in the betweenness calculation. + * \param targets A vertex selector for the targets of the shortest paths taken + * into considuration in the betweenness calculation. + * \param eids The edges for which the subset-limited betweenness centrality + * scores will be returned. This parameter is for convenience only. + * Internally, the betweenness will be calculated for all edges. + * \param directed If true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param normalized Whether to normalize betweenness scores. Normalization is + * currently unimplemented, and setting this to \c true raises an error. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID passed in \p sources or \p targets + * + * Time complexity: O(|S||E|), where + * |S| is the number of vertices in the subset and + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_edge_betweenness() to compute the exact edge betweenness and + * \ref igraph_edge_betweenness_cutoff() to compute the range-limited edge betweenness. + */ +igraph_error_t igraph_edge_betweenness_subset( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + igraph_vs_t sources, igraph_vs_t targets, + igraph_es_t eids, + igraph_bool_t directed, igraph_bool_t normalized) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_sources; + igraph_int_t no_of_processed_sources; + igraph_inclist_t inclist, parents; + igraph_vit_t vit; + igraph_eit_t eit; + igraph_neimode_t mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + igraph_vector_t dist; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_real_t *nrgeo; + igraph_real_t *tmpscore; + igraph_int_t source, j; + bool *is_target; + igraph_stack_int_t S; + + if (normalized) { + IGRAPH_ERROR("Normalization is not yet implemented for subset edge betweenness.", IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(betweenness_check_weights(weights, no_of_edges)); + + IGRAPH_CHECK(igraph_vs_size(graph, &sources, &no_of_sources)); + + is_target = IGRAPH_CALLOC(no_of_nodes, bool); + IGRAPH_CHECK_OOM(is_target, "Insufficient memory for subset edge betweenness calculation."); + IGRAPH_FINALLY(igraph_free, is_target); + + IGRAPH_CHECK(igraph_vit_create(graph, targets, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + is_target[IGRAPH_VIT_GET(vit)] = true; + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_CHECK(igraph_inclist_init_empty(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_inclist_destroy, &parents); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + + nrgeo = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for subset edge betweenness calculation."); + IGRAPH_FINALLY(igraph_free, nrgeo); + + tmpscore = IGRAPH_CALLOC(no_of_nodes, igraph_real_t); + IGRAPH_CHECK_OOM(tmpscore, "Insufficient memory for subset edge betweenness calculation."); + IGRAPH_FINALLY(igraph_free, tmpscore); + + IGRAPH_CHECK(igraph_stack_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &S); + + if (!igraph_es_is_all(&eids)) { + /* result needed only for a subset of the vertices */ + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_edges); + } else { + /* result covers all vertices */ + IGRAPH_CHECK(igraph_vector_resize(res, no_of_edges)); + igraph_vector_null(res); + tmpres = res; + } + + IGRAPH_CHECK(igraph_vit_create(graph, sources, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + for ( + no_of_processed_sources = 0, IGRAPH_VIT_RESET(vit); + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), no_of_processed_sources++ + ) { + source = IGRAPH_VIT_GET(vit); + + IGRAPH_PROGRESS( + "Edge betweenness centrality (subset): ", + 100.0 * no_of_processed_sources / no_of_sources, 0 + ); + IGRAPH_ALLOW_INTERRUPTION(); + + /* Loop invariant that is valid at this point: + * + * - the stack S is empty + * - the 'dist' vector contains zeros only + * - the 'nrgeo' array contains zeros only + * - the 'tmpscore' array contains zeros only + * - the 'parents' incidence list contains empty vectors only + */ + + /* TODO: there is more room for optimization here; the single-source + * shortest path search runs until it reaches all the nodes in the + * component of the source node even if we are only interested in a + * smaller target subset. We could stop the search when all target + * nodes were reached. + */ + + /* Conduct a single-source shortest path search from the source node */ + if (weights) { + IGRAPH_CHECK(sspf_weighted_edge(graph, source, &dist, nrgeo, weights, &S, &parents, &inclist, -1)); + } else { + IGRAPH_CHECK(sspf_edge(graph, source, &dist, nrgeo, &S, &parents, &inclist, -1)); + } + + /* Aggregate betweenness scores for the nodes we have reached in this + * traversal */ + while (!igraph_stack_int_empty(&S)) { + igraph_int_t actnode = igraph_stack_int_pop(&S); + igraph_vector_int_t *parentv = igraph_inclist_get(&parents, actnode); + igraph_int_t parentv_len = igraph_vector_int_size(parentv); + igraph_real_t coeff; + + if (is_target[actnode]) { + coeff = (1 + tmpscore[actnode]) / nrgeo[actnode]; + } else { + coeff = tmpscore[actnode] / nrgeo[actnode]; + } + + for (j = 0; j < parentv_len; j++) { + igraph_int_t parent_edge = VECTOR(*parentv)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, parent_edge, actnode); + tmpscore[neighbor] += nrgeo[neighbor] * coeff; + VECTOR(*tmpres)[parent_edge] += nrgeo[neighbor] * coeff; + } + + /* Reset variables to ensure that the 'for' loop invariant will + * still be valid in the next iteration */ + + VECTOR(dist)[actnode] = 0; + nrgeo[actnode] = 0; + tmpscore[actnode] = 0; + igraph_vector_int_clear(parentv); + } + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + /* Keep only the requested edges */ + if (!igraph_es_is_all(&eids)) { + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (j = 0, IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), j++) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + VECTOR(*res)[j] = VECTOR(*tmpres)[edge]; + } + + igraph_eit_destroy(&eit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + + if (!directed || !igraph_is_directed(graph)) { + igraph_vector_scale(res, 0.5); + } + + igraph_stack_int_destroy(&S); + IGRAPH_FREE(tmpscore); + IGRAPH_FREE(nrgeo); + igraph_vector_destroy(&dist); + igraph_inclist_destroy(&parents); + igraph_inclist_destroy(&inclist); + IGRAPH_FREE(is_target); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} diff --git a/src/centrality/centrality_internal.h b/src/centrality/centrality_internal.h new file mode 100644 index 0000000..10e4bc6 --- /dev/null +++ b/src/centrality/centrality_internal.h @@ -0,0 +1,37 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CENTRALITY_INTERNAL_H +#define IGRAPH_CENTRALITY_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_bool_t igraph_i_vector_mostly_negative(const igraph_vector_t *vector); +void igraph_i_vector_scale_by_max_abs(igraph_vector_t *vec); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/centrality/centrality_other.c b/src/centrality/centrality_other.c new file mode 100644 index 0000000..5865d8b --- /dev/null +++ b/src/centrality/centrality_other.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "centrality/centrality_internal.h" + +igraph_bool_t igraph_i_vector_mostly_negative(const igraph_vector_t *vector) { + /* Many of the centrality measures correspond to the eigenvector of some + * matrix. When v is an eigenvector, c*v is also an eigenvector, therefore + * it may happen that all the scores in the eigenvector are negative, in which + * case we want to negate them since the centrality scores should be positive. + * However, since ARPACK is not always stable, sometimes it happens that + * *some* of the centrality scores are small negative numbers. This function + * helps distinguish between the two cases; it should return true if most of + * the values are relatively large negative numbers, in which case we should + * negate the eigenvector. + */ + igraph_int_t n = igraph_vector_size(vector); + igraph_real_t mi, ma; + + if (n == 0) { + return false; + } + + igraph_vector_minmax(vector, &mi, &ma); + + if (mi >= 0) { + return false; + } + if (ma <= 0) { + return true; + } + + /* is the most negative value larger in magnitude than the most positive? */ + return (-mi/ma > 1); +} + +/* Normalizes a vector of real numbers such that the largest value, as well as + * the largest value by magnitude, are 1.0. This is used by functions that + * produce eigenvector-like centrality values, scaling the largest centrality + * to 1.0. */ +void igraph_i_vector_scale_by_max_abs(igraph_vector_t *vec) { + const igraph_int_t n = igraph_vector_size(vec); + igraph_real_t amax = 0; + igraph_int_t which = 0; + + for (igraph_int_t i = 0; i < n; i++) { + igraph_real_t tmp; + tmp = fabs(VECTOR(*vec)[i]); + if (tmp > amax) { + amax = tmp; + which = i; + } + } + if (amax != 0) { + igraph_vector_scale(vec, 1 / VECTOR(*vec)[which]); + } +} diff --git a/src/centrality/centralization.c b/src/centrality/centralization.c new file mode 100644 index 0000000..976a151 --- /dev/null +++ b/src/centrality/centralization.c @@ -0,0 +1,723 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_interface.h" +#include "igraph_structural.h" +#include "igraph_vector.h" + +#include "core/math.h" + +/** + * \function igraph_centralization + * \brief Calculate the centralization score from the node level scores. + * + * For a centrality score defined on the vertices of a graph, it is + * possible to define a graph level centralization index, by + * calculating the sum of the deviations from the maximum centrality + * score. Consequently, the higher the centralization index of the + * graph, the more centralized the structure is. + * + * + * In order to make graphs of different sizes comparable, + * the centralization index is usually normalized to a number between + * zero and one, by dividing the (unnormalized) centralization score + * of the most centralized structure with the same number of vertices. + * + * + * For most centrality indices, the most centralized structure is the + * star graph, a single center connected to all other nodes in the network. + * There is some variation depending on whether the graph is directed or not, + * whether loop edges are allowed, etc. + * + * + * This function simply calculates the graph level index, if the node + * level scores and the theoretical maximum are given. It is called by + * all the measure-specific centralization functions. It uses the calculation + * + * + * C = sum_v ((max_u c_u) - c_v) + * + * + * where \c c are the centrality scores passed in \p scores. If \p normalized + * is \c true, then C/theoretical_max is returned. + * + * \param scores A vector containing the node-level centrality scores. + * \param theoretical_max The graph level centrality score of the most + * centralized graph with the same number of vertices. Only used + * if \c normalized set to true. + * \param normalized Boolean, whether to normalize the centralization + * by dividing the supplied theoretical maximum. + * \return The graph level index. + * + * \sa \ref igraph_centralization_degree(), \ref + * igraph_centralization_betweenness(), \ref + * igraph_centralization_closeness(), and \ref + * igraph_centralization_eigenvector_centrality() for specific + * centralization functions. + * + * Time complexity: O(n), the length of the score vector. + * + * \example examples/simple/centralization.c + */ + +igraph_real_t igraph_centralization(const igraph_vector_t *scores, + igraph_real_t theoretical_max, + igraph_bool_t normalized) { + + igraph_int_t no_of_nodes = igraph_vector_size(scores); + igraph_real_t cent; + + if (no_of_nodes != 0) { + igraph_real_t maxscore = igraph_vector_max(scores); + cent = no_of_nodes * maxscore - igraph_vector_sum(scores); + if (normalized) { + cent = cent / theoretical_max; + } + } else { + cent = IGRAPH_NAN; + } + + return cent; +} + +/** + * \function igraph_centralization_degree + * \brief Calculate vertex degree and graph centralization. + * + * This function calculates the degree of the vertices by passing its + * arguments to \ref igraph_degree(); and it calculates the graph + * level centralization index based on the results by calling \ref + * igraph_centralization(). + * + * \param graph The input graph. + * \param res A vector if you need the node-level degree scores, or a + * null pointer otherwise. + * \param mode Constant the specifies the type of degree for directed + * graphs. Possible values: \c IGRAPH_IN, \c IGRAPH_OUT and \c + * IGRAPH_ALL. This argument is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges when calculating the + * degree (and the centralization). \c IGRAPH_NO_LOOPS ignores loop + * edges; \c IGRAPH_LOOPS_ONCE counts each loop edge only once; + * \c IGRAPH_LOOPS_TWICE counts each loop edge twice in undirected + * graphs and once in directed graphs. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_degree(). + * + * Time complexity: the complexity of \ref igraph_degree() plus O(n), + * the number of vertices queried, for calculating the centralization + * score. + */ + +igraph_error_t igraph_centralization_degree( + const igraph_t *graph, igraph_vector_t *res, igraph_neimode_t mode, + igraph_loops_t loops, igraph_real_t *centralization, + igraph_real_t *theoretical_max, igraph_bool_t normalized +) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_strength(graph, scores, igraph_vss_all(), mode, loops, 0)); + IGRAPH_CHECK(igraph_centralization_degree_tmax(graph, 0, mode, loops, tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_degree_tmax + * \brief Theoretical maximum for graph centralization based on degree. + * + * This function returns the theoretical maximum graph centrality + * based on vertex degree. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the \p graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The \p nodes argument is ignored in + * this case. The \p mode argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the \p graph + * argument. In this case the \p nodes and \p mode + * arguments are considered. + * + * + * The most centralized structure is the star. More specifically, for + * undirected graphs it is the star, for directed graphs it is the + * in-star or the out-star. + * + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * \p graph argument is not a null pointer. + * \param mode Constant, whether the calculation is based on in-degree + * (\c IGRAPH_IN), out-degree (\c IGRAPH_OUT) + * or total degree (\c IGRAPH_ALL). This is ignored if + * the \p graph argument is not a null pointer and the + * given graph is undirected. + * \param loops Specifies how to treat loop edges when calculating the + * degree (and the centralization). \c IGRAPH_NO_LOOPS ignores loop + * edges; \c IGRAPH_LOOPS_ONCE counts each loop edge only once; + * \c IGRAPH_LOOPS_TWICE counts each loop edge twice in undirected + * graphs and once in directed graphs. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_degree() and \ref + * igraph_centralization(). + */ + +igraph_error_t igraph_centralization_degree_tmax( + const igraph_t *graph, igraph_int_t nodes, igraph_neimode_t mode, + igraph_loops_t loops, igraph_real_t *res +) { + + igraph_bool_t directed = (mode != IGRAPH_ALL); + igraph_real_t real_nodes; + + if (graph) { + directed = igraph_is_directed(graph); + nodes = igraph_vcount(graph); + } else { + if (nodes < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + } + + if (nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (directed) { + switch (mode) { + case IGRAPH_IN: + case IGRAPH_OUT: + if (loops == IGRAPH_NO_LOOPS) { + *res = (real_nodes - 1) * (real_nodes - 1); + } else { + *res = (real_nodes - 1) * real_nodes; + } + break; + case IGRAPH_ALL: + if (loops == IGRAPH_NO_LOOPS) { + *res = 2 * (real_nodes - 1) * (real_nodes - 2); + } else { + *res = 2 * (real_nodes - 1) * (real_nodes - 1); + } + break; + } + } else { + if (loops == IGRAPH_NO_LOOPS) { + *res = (real_nodes - 1) * (real_nodes - 2); + } else if (loops == IGRAPH_LOOPS_ONCE) { + *res = (real_nodes - 1) * (real_nodes - 1); + } else { + *res = (real_nodes - 1) * real_nodes; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_betweenness + * \brief Calculate vertex betweenness and graph centralization. + * + * This function calculates the betweenness centrality of the vertices + * by passing its arguments to \ref igraph_betweenness(); and it + * calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * + * \param graph The input graph. + * \param res A vector if you need the node-level betweenness scores, or a + * null pointer otherwise. + * \param directed Boolean, whether to consider directed paths when + * calculating betweenness. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_betweenness(). + * + * Time complexity: the complexity of \ref igraph_betweenness() plus + * O(n), the number of vertices queried, for calculating the + * centralization score. + */ + +igraph_error_t igraph_centralization_betweenness(const igraph_t *graph, + igraph_vector_t *res, + igraph_bool_t directed, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_betweenness(graph, /*weights=*/ 0, scores, igraph_vss_all(), directed, false)); + + IGRAPH_CHECK(igraph_centralization_betweenness_tmax(graph, 0, directed, tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_betweenness_tmax + * \brief Theoretical maximum for graph centralization based on betweenness. + * + * This function returns the theoretical maximum graph centrality + * based on vertex betweenness. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the \p graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The \p nodes argument is ignored in + * this case. The \p directed argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the \p graph + * argument. In this case the \p nodes and \p directed + * arguments are considered. + * + * + * The most centralized structure is the star. + * + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * \p graph argument is not a null pointer. + * \param directed Boolean, whether to use directed paths in + * the betweenness calculation. This argument is ignored if + * \p graph is not a null pointer and it is undirected. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_betweenness() and \ref + * igraph_centralization(). + */ + +igraph_error_t igraph_centralization_betweenness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_bool_t directed, + igraph_real_t *res) { + igraph_real_t real_nodes; + + if (graph) { + directed = directed && igraph_is_directed(graph); + nodes = igraph_vcount(graph); + } else { + if (nodes < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + } + + if (nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (directed) { + *res = (real_nodes - 1) * (real_nodes - 1) * (real_nodes - 2); + } else { + *res = (real_nodes - 1) * (real_nodes - 1) * (real_nodes - 2) / 2.0; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_closeness + * \brief Calculate vertex closeness and graph centralization. + * + * This function calculates the closeness centrality of the vertices + * by passing its arguments to \ref igraph_closeness(); and it + * calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * + * \param graph The input graph. + * \param res A vector if you need the node-level closeness scores, or a + * null pointer otherwise. + * \param mode Constant the specifies the type of closeness for directed + * graphs. Possible values: \c IGRAPH_IN, \c IGRAPH_OUT and \c + * IGRAPH_ALL. This argument is ignored for undirected graphs. See + * \ref igraph_closeness() argument with the same name for more. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_closeness(). + * + * Time complexity: the complexity of \ref igraph_closeness() plus + * O(n), the number of vertices queried, for calculating the + * centralization score. + */ + +igraph_error_t igraph_centralization_closeness(const igraph_t *graph, + igraph_vector_t *res, + igraph_neimode_t mode, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_closeness(graph, scores, NULL, NULL, igraph_vss_all(), mode, + /*weights=*/ 0, /*normalized=*/ 1)); + + IGRAPH_CHECK(igraph_centralization_closeness_tmax(graph, 0, mode, + tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_closeness_tmax + * \brief Theoretical maximum for graph centralization based on closeness. + * + * This function returns the theoretical maximum graph centrality + * based on vertex closeness. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the \p graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The \p nodes argument is ignored in + * this case. The \p mode argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the \p graph + * argument. In this case the \p nodes and \p mode + * arguments are considered. + * + * + * The most centralized structure is the star. + * + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * \p graph argument is not a null pointer. + * \param mode Constant, specifies what kind of distances to consider + * to calculate closeness. See the \p mode argument of + * \ref igraph_closeness() for details. This argument is ignored + * if \p graph is not a null pointer and it is undirected. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_closeness() and \ref + * igraph_centralization(). + */ + +igraph_error_t igraph_centralization_closeness_tmax(const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res) { + igraph_real_t real_nodes; + + if (graph) { + nodes = igraph_vcount(graph); + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + } else { + if (nodes < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + } + + if (nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (mode != IGRAPH_ALL) { + *res = (real_nodes - 1) * (1.0 - 1.0 / real_nodes); + } else { + *res = (real_nodes - 1) * (real_nodes - 2) / (2.0 * real_nodes - 3); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_eigenvector_centrality + * \brief Calculate eigenvector centrality scores and graph centralization. + * + * This function calculates the eigenvector centrality of the vertices + * by passing its arguments to \ref igraph_eigenvector_centrality(); + * and it calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * + * + * Note that vertex-level eigenvector centrality scores do not have + * a natural scale. As with any eigenvector, their interpretation is + * invariant to scaling by a constant factor. However, due to how + * graph-level \em centralization is defined, its value depends on the + * specific scale/normalization used for vertex-level scores. Which of + * two graphs will have a higher eigenvector \em centralization depends + * on the choice of normalization for centralities. This function makes + * the specific choice of scaling vertex-level centrality scores by their + * maximum (i.e. it uses the ∞-norm). Other normalization choices, such + * as the 1-norm or 2-norm are not currently implemented. + * + * \param graph The input graph. + * \param vector A vector if you need the node-level eigenvector + * centrality scores, or a null pointer otherwise. + * \param value If not a null pointer, then the leading eigenvalue is + * stored here. + * \param mode How to consider edge directions in directed graphs. + * See \ref igraph_eigenvector_centrality() for details. Ignored + * for directed graphs. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_eigenvector_centrality(). + * + * Time complexity: the complexity of \ref + * igraph_eigenvector_centrality() plus O(|V|), the number of vertices + * for the calculating the centralization. + */ + +igraph_error_t igraph_centralization_eigenvector_centrality( + const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + igraph_arpack_options_t *options, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = vector; + igraph_real_t realvalue, *myvalue = value; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!vector) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + if (!value) { + myvalue = &realvalue; + } + + IGRAPH_CHECK(igraph_eigenvector_centrality(graph, scores, myvalue, mode, + /*weights=*/ NULL, + options)); + + IGRAPH_CHECK(igraph_centralization_eigenvector_centrality_tmax( + graph, 0, mode, tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!vector) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_centralization_eigenvector_centrality_tmax + * \brief Theoretical maximum centralization for eigenvector centrality. + * + * This function returns the theoretical maximum graph centrality + * based on vertex eigenvector centrality. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the \p graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The \p nodes argument is ignored in + * this case. The \p mode argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the \p graph. argument. + * In this case the \p nodes and \p mode arguments are considered. + * + * + * The most centralized directed structure is the in-star with \p mode + * set to \c IGRAPH_OUT, and the out-star with \p mode set to \c IGRAPH_IN. + * The most centralized undirected structure is the graph with a single edge. + * + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * \p graph argument is not a null pointer. + * \param mode How to consider edge directions in directed graphs. + * See \ref igraph_eigenvector_centrality() for details. This argument + * is ignored if \p graph is not a null pointer and it is + * undirected. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_closeness() and \ref + * igraph_centralization(). + */ + +igraph_error_t igraph_centralization_eigenvector_centrality_tmax( + const igraph_t *graph, + igraph_int_t nodes, + igraph_neimode_t mode, + igraph_real_t *res) { + + if (graph) { + nodes = igraph_vcount(graph); + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + } else { + if (nodes < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + } + + if (nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + if (nodes == 1) { + *res = 0; + return IGRAPH_SUCCESS; + } + + if (mode != IGRAPH_ALL) { + *res = nodes - 1; + } else { + *res = nodes - 2; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/centrality/closeness.c b/src/centrality/closeness.c new file mode 100644 index 0000000..96a7b9f --- /dev/null +++ b/src/centrality/closeness.c @@ -0,0 +1,805 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_dqueue.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +/***** Closeness centrality *****/ + +/** + * \ingroup structural + * \function igraph_closeness + * \brief Closeness centrality calculations for some vertices. + * + * The closeness centrality of a vertex measures how easily other + * vertices can be reached from it (or the other way: how easily it + * can be reached from the other vertices). It is defined as + * the inverse of the mean distance to (or from) all other vertices. + * + * + * Closeness centrality is meaningful only for connected graphs. + * If the graph is not connected, igraph computes the inverse of the + * mean distance to (or from) all \em reachable vertices. In undirected + * graphs, this is equivalent to computing the closeness separately in + * each connected component. The optional \p all_reachable output + * parameter is provided to help detect when the graph is disconnected. + * + * + * While there is no universally adopted definition of closeness centrality + * for disconnected graphs, there have been some attempts for generalizing + * the concept to the disconnected case. One type of approach considers the mean distance + * only to reachable vertices, then re-scales the obtained certrality score + * by a factor that depends on the number of reachable vertices + * (i.e. the size of the component in the undirected case). + * To facilitate computing these generalizations of closeness centrality, + * the number of reachable vertices (not including the starting vertex) + * is returned in \p reachable_count. + * + * + * In disconnected graphs, consider using the harmonic centrality, + * computable using \ref igraph_harmonic_centrality(). + * + * + * For isolated vertices, i.e. those having no associated paths, NaN is returned. + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * closeness centrality scores for the given vertices. + * \param reachable_count If not \c NULL, this vector will contain the number of + * vertices reachable from each vertex for which the closeness is calculated + * (not including that vertex). + * \param all_reachable Pointer to a Boolean. If not \c NULL, it indicates if all + * vertices of the graph were reachable from each vertex in \p vids. + * If false, the graph is non-connected. If true, and the graph is undirected, + * or if the graph is directed and \p vids contains all vertices, then the + * graph is connected. + * \param vids The vertices for which the closeness centrality will be computed. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param weights An optional vector containing edge weights for weighted + * closeness. NaN values re not allowed as weights. Supply a null + * pointer here for traditional, unweighted closeness. + * \param normalized If true, the inverse of the mean distance to reachable + * vertices is returned. If false, the inverse of the sum of distances + * is returned. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(n|E|) for the unweighted case and O(n|E|log|V|+|V|) + * for the weighted case, where n is the number + * of vertices for which the calculation is done, |V| is the number of vertices + * and |E| is the number of edges in the graph. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_betweenness(), + * \ref igraph_harmonic_centrality(). + * See \ref igraph_closeness_cutoff() for the range-limited closeness centrality. + */ +igraph_error_t igraph_closeness(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + return igraph_closeness_cutoff(graph, res, reachable_count, all_reachable, vids, mode, weights, normalized, -1); +} + +static igraph_error_t igraph_i_closeness_cutoff_weighted(const igraph_t *graph, + igraph_vector_t *res, + igraph_vector_int_t *reachable_count, + igraph_bool_t *all_reachable, + const igraph_vs_t vids, + igraph_neimode_t mode, + igraph_real_t cutoff, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + + /* See igraph_distances_dijkstra() for the implementation + details and the dirty tricks. */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + igraph_2wheap_t Q; + igraph_vit_t vit; + igraph_int_t nodes_to_calc; + + igraph_lazy_inclist_t inclist; + igraph_int_t i, j; + + igraph_vector_t dist; + igraph_vector_int_t which; + igraph_int_t nodes_reached; + + igraph_real_t mindist = 0; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weight vector must be positive.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + if (reachable_count) { + IGRAPH_CHECK(igraph_vector_int_resize(reachable_count, nodes_to_calc)); + } + + if (all_reachable) { + *all_reachable = true; /* be optimistic */ + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&which, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + + igraph_int_t source = IGRAPH_VIT_GET(vit); + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); /* reserved */ + VECTOR(which)[source] = i + 1; + VECTOR(dist)[source] = 1.0; /* actual distance is zero but we need to store distance + 1 */ + nodes_reached = 0; + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + /* Now check all neighbors of minnei for a shorter path */ + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, minnei); + igraph_int_t nlen; + + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + nlen = igraph_vector_int_size(neis); + mindist = -igraph_2wheap_delete_max(&Q); + + if (cutoff >= 0 && (mindist - 1.0) > cutoff) { + continue; /* NOT break!!! */ + } + + VECTOR(*res)[i] += (mindist - 1.0); + nodes_reached++; + + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dist)[to]; + + if (VECTOR(which)[to] != i + 1) { + /* First non-infinite distance */ + VECTOR(which)[to] = i + 1; + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist)); + } else if (curdist == 0 /* this means curdist is infinity */ || altdist < curdist) { + /* This is a shorter path */ + VECTOR(dist)[to] = altdist; + igraph_2wheap_modify(&Q, to, -altdist); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + if (reachable_count) { + VECTOR(*reachable_count)[i] = nodes_reached - 1; + } + + if (normalized) { + /* compute the inverse of the average distance, considering only reachable nodes */ + VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : ((igraph_real_t) (nodes_reached-1)) / VECTOR(*res)[i]; + } else { + /* compute the inverse of the sum of distances */ + VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : 1.0 / VECTOR(*res)[i]; + } + + if (all_reachable) { + if (nodes_reached < no_of_nodes) { + *all_reachable = false; + } + } + } /* !IGRAPH_VIT_END(vit) */ + + igraph_vector_int_destroy(&which); + igraph_vector_destroy(&dist); + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_closeness_cutoff + * \brief Range limited closeness centrality. + * + * This function computes a range-limited version of closeness centrality + * by considering only those shortest paths whose length is no greater + * then the given cutoff value. + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * range-limited closeness centrality scores for the given vertices. + * \param reachable_count If not \c NULL, this vector will contain the number of + * vertices reachable within the cutoff distance from each vertex for which + * the range-limited closeness is calculated (not including that vertex). + * \param all_reachable Pointer to a Boolean. If not \c NULL, it indicates if all + * vertices of the graph were reachable from each vertex in \p vids within + * the given cutoff distance. + * \param vids The vertices for which the range limited closeness centrality + * will be computed. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param weights An optional vector containing edge weights for + * weighted closeness. No edge weight may be NaN. Supply a null + * pointer here for traditional, unweighted closeness. + * \param normalized If true, the inverse of the mean distance to vertices + * reachable within the cutoff is returned. If false, the inverse + * of the sum of distances is returned. + * \param cutoff The maximal length of paths that will be considered. + * If negative or \ref IGRAPH_UNLIMITED, the exact closeness will be + * calculated (no upper limit on path lengths). + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: At most O(n|E|) for the unweighted case and O(n|E|log|V|+|V|) + * for the weighted case, where n is the number + * of vertices for which the calculation is done, |V| is the number of vertices + * and |E| is the number of edges in the graph. The timing decreases with smaller + * cutoffs in a way that depends on the graph structure. + * + * \sa \ref igraph_closeness() to calculate the exact closeness centrality. + */ + +igraph_error_t igraph_closeness_cutoff(const igraph_t *graph, igraph_vector_t *res, + igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t already_counted; + igraph_vector_int_t *neis; + igraph_int_t i, j; + igraph_int_t nodes_reached; + igraph_adjlist_t allneis; + + igraph_int_t actdist = 0; + + igraph_dqueue_int_t q; + + igraph_int_t nodes_to_calc; + igraph_vit_t vit; + + if (weights) { + return igraph_i_closeness_cutoff_weighted(graph, res, reachable_count, all_reachable, vids, mode, cutoff, + weights, normalized); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + if (reachable_count) { + IGRAPH_CHECK(igraph_vector_int_resize(reachable_count, nodes_to_calc)); + } + + if (all_reachable) { + *all_reachable = true; /* be optimistic */ + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for closeness.", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&already_counted, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + nodes_reached = 0; + + igraph_dqueue_int_clear(&q); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, IGRAPH_VIT_GET(vit))); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + VECTOR(already_counted)[IGRAPH_VIT_GET(vit)] = i + 1; + + IGRAPH_PROGRESS("Closeness: ", 100.0 * i / nodes_to_calc, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act = igraph_dqueue_int_pop(&q); + actdist = igraph_dqueue_int_pop(&q); + + if (cutoff >= 0 && actdist > cutoff) { + continue; /* NOT break!!! */ + } + + VECTOR(*res)[i] += actdist; + nodes_reached++; + + /* check the neighbors */ + neis = igraph_adjlist_get(&allneis, act); + igraph_int_t nei_count = igraph_vector_int_size(neis); + for (j = 0; j < nei_count; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (VECTOR(already_counted)[neighbor] == i + 1) { + continue; + } + VECTOR(already_counted)[neighbor] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } + + if (reachable_count) { + VECTOR(*reachable_count)[i] = nodes_reached - 1; + } + + if (normalized) { + /* compute the inverse of the average distance, considering only reachable nodes */ + VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : ((igraph_real_t) (nodes_reached-1)) / VECTOR(*res)[i]; + } else { + /* compute the inverse of the sum of distances */ + VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : 1.0 / VECTOR(*res)[i]; + } + + if (all_reachable) { + if (nodes_reached < no_of_nodes) { + *all_reachable = false; + } + } + } + + IGRAPH_PROGRESS("Closeness: ", 100.0, NULL); + + /* Clean */ + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&already_counted); + igraph_vit_destroy(&vit); + igraph_adjlist_destroy(&allneis); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/***** Harmonic centrality *****/ + +static igraph_error_t igraph_i_harmonic_centrality_unweighted(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + igraph_bool_t normalized, + igraph_real_t cutoff) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t already_counted; + igraph_vector_int_t *neis; + igraph_int_t i, j; + igraph_adjlist_t allneis; + + igraph_int_t actdist = 0; + + igraph_dqueue_int_t q; + + igraph_int_t nodes_to_calc; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for harmonic centrality.", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&already_counted, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) + { + igraph_int_t source = IGRAPH_VIT_GET(vit); + + igraph_dqueue_int_clear(&q); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, source)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + VECTOR(already_counted)[source] = i + 1; + + IGRAPH_PROGRESS("Harmonic centrality: ", 100.0 * i / nodes_to_calc, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act = igraph_dqueue_int_pop(&q); + actdist = igraph_dqueue_int_pop(&q); + + if (cutoff >= 0 && actdist > cutoff) { + continue; /* NOT break!!! */ + } + + /* Exclude self-distance, which is zero. */ + if (source != act) { + VECTOR(*res)[i] += 1.0/actdist; + } + + /* check the neighbors */ + neis = igraph_adjlist_get(&allneis, act); + igraph_int_t nei_count = igraph_vector_int_size(neis); + for (j = 0; j < nei_count; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (VECTOR(already_counted)[neighbor] == i + 1) { + continue; + } + VECTOR(already_counted)[neighbor] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } + } + + if (normalized && no_of_nodes > 1 /* not a null graph or singleton graph */) { + igraph_vector_scale(res, 1.0 / (no_of_nodes - 1)); + } + + IGRAPH_PROGRESS("Harmonic centrality: ", 100.0, NULL); + + /* Clean */ + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&already_counted); + igraph_vit_destroy(&vit); + igraph_adjlist_destroy(&allneis); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_harmonic_centrality_weighted(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff) { + + /* See igraph_distances_dijkstra() for the implementation + details and the dirty tricks. */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + igraph_2wheap_t Q; + igraph_vit_t vit; + igraph_int_t nodes_to_calc; + + igraph_lazy_inclist_t inclist; + igraph_int_t i, j; + + igraph_vector_t dist; + igraph_vector_int_t which; + + igraph_real_t mindist = 0; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weight vector must be positive.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&which, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + + igraph_int_t source = IGRAPH_VIT_GET(vit); + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); /* reserved */ + VECTOR(which)[source] = i + 1; + VECTOR(dist)[source] = 1.0; /* actual distance is zero but we need to store distance + 1 */ + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + /* Now check all neighbors of minnei for a shorter path */ + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, minnei); + igraph_int_t nlen; + + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + nlen = igraph_vector_int_size(neis); + mindist = -igraph_2wheap_delete_max(&Q); + + if (cutoff >= 0 && (mindist - 1.0) > cutoff) { + continue; /* NOT break!!! */ + } + + /* Exclude self-distance, which is zero. */ + if (source != minnei) { + VECTOR(*res)[i] += 1.0 / (mindist - 1.0); + } + + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dist)[to]; + + if (VECTOR(which)[to] != i + 1) { + /* First non-infinite distance */ + VECTOR(which)[to] = i + 1; + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist)); + } else if (curdist == 0 /* this means curdist is infinity */ || altdist < curdist) { + /* This is a shorter path */ + VECTOR(dist)[to] = altdist; + igraph_2wheap_modify(&Q, to, -altdist); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + } /* !IGRAPH_VIT_END(vit) */ + + if (normalized && no_of_nodes > 1 /* not a null graph or singleton graph */) { + igraph_vector_scale(res, 1.0 / (no_of_nodes - 1)); + } + + igraph_vector_int_destroy(&which); + igraph_vector_destroy(&dist); + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_harmonic_centrality_cutoff + * \brief Range limited harmonic centrality. + * + * This function computes the range limited version of harmonic centrality: + * only those shortest paths are considered whose length is not above the given cutoff. + * The inverse distance to vertices not reachable within the cutoff is considered + * to be zero. + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * range limited harmonic centrality scores for the given vertices. + * \param vids The vertices for which the harmonic centrality will be computed. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param weights An optional vector containing edge weights for + * weighted harmonic centrality. No edge weight may be NaN. + * If \c NULL, all weights are considered to be one. + * \param normalized Boolean, whether to normalize the result. If true, + * the result is the mean inverse path length to other vertices. + * i.e. it is normalized by the number of vertices minus one. + * If false, the result is the sum of inverse path lengths to other + * vertices. + * \param cutoff The maximal length of paths that will be considered. + * The inverse distance to vertices that are not reachable within + * the cutoff path length is considered to be zero. + * Supply a negative value to compute the exact harmonic centrality, + * without any upper limit on the length of paths. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: At most O(n|E|) for the unweighted case and O(n|E|log|V|+|V|) + * for the weighted case, where n is the number + * of vertices for which the calculation is done, |V| is the number of vertices + * and |E| is the number of edges in the graph. The timing decreases with smaller + * cutoffs in a way that depends on the graph structure. + * + * \sa \ref igraph_harmonic_centrality() to calculate the exact harmonic centrality. + * Other centrality types: \ref igraph_closeness(), \ref igraph_betweenness(). + */ + +igraph_error_t igraph_harmonic_centrality_cutoff(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized, + igraph_real_t cutoff) { + if (weights) { + return igraph_i_harmonic_centrality_weighted(graph, res, vids, mode, weights, normalized, cutoff); + } else { + return igraph_i_harmonic_centrality_unweighted(graph, res, vids, mode, normalized, cutoff); + } +} + + +/** + * \ingroup structural + * \function igraph_harmonic_centrality + * \brief Harmonic centrality for some vertices. + * + * The harmonic centrality of a vertex is the mean inverse distance to + * all other vertices. The inverse distance to an unreachable vertex + * is considered to be zero. + * + * + * References: + * + * + * M. Marchiori and V. Latora, Harmony in the small-world, Physica A 285, pp. 539-546 (2000). + * https://doi.org/10.1016/S0378-4371%2800%2900311-3 + * + * + * Y. Rochat, Closeness Centrality Extended to Unconnected Graphs: the Harmonic Centrality Index, ASNA 2009. + * https://infoscience.epfl.ch/record/200525 + * + * + * S. Vigna and P. Boldi, Axioms for Centrality, Internet Mathematics 10, (2014). + * https://doi.org/10.1080/15427951.2013.865686 + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * harmonic centrality scores for the given vertices. + * \param vids The vertices for which the harmonic centrality will be computed. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param weights An optional vector containing edge weights for + * weighted harmonic centrality. No edge weight may be NaN. + * If \c NULL, all weights are considered to be one. + * \param normalized Boolean, whether to normalize the result. If true, + * the result is the mean inverse path length to other vertices, + * i.e. it is normalized by the number of vertices minus one. + * If false, the result is the sum of inverse path lengths to other + * vertices. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(n|E|) for the unweighted case and O(n*|E|log|V|+|V|) + * for the weighted case, where n is the number + * of vertices for which the calculation is done, |V| is the number of vertices + * and |E| is the number of edges in the graph. + * + * \sa Other centrality types: \ref igraph_closeness(), \ref igraph_degree(), \ref igraph_betweenness(). + */ + +igraph_error_t igraph_harmonic_centrality(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + return igraph_harmonic_centrality_cutoff(graph, res, vids, mode, weights, normalized, /* cutoff= */ -1); +} diff --git a/src/centrality/coreness.c b/src/centrality/coreness.c new file mode 100644 index 0000000..9c383d8 --- /dev/null +++ b/src/centrality/coreness.c @@ -0,0 +1,157 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" + +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" + +/** + * \function igraph_coreness + * \brief The coreness of the vertices in a graph. + * + * The k-core of a graph is a maximal subgraph in which each vertex + * has at least degree k. (Degree here means the degree in the + * subgraph of course.). The coreness of a vertex is the highest order + * of a k-core containing the vertex. + * + * + * This function implements the algorithm presented in Vladimir + * Batagelj, Matjaz Zaversnik: An O(m) Algorithm for Cores + * Decomposition of Networks. + * https://arxiv.org/abs/cs/0310049 + * + * \param graph The input graph. + * \param cores Pointer to an initialized vector, the result of the + * computation will be stored here. It will be resized as + * needed. For each vertex it contains the highest order of a + * core containing the vertex. + * \param mode For directed graph it specifies whether to calculate + * in-cores, out-cores or the undirected version. It is ignored + * for undirected graphs. Possible values: \c IGRAPH_ALL + * undirected version, \c IGRAPH_IN in-cores, \c IGRAPH_OUT + * out-cores. + * \return Error code. + * + * Time complexity: O(|E|), the number of edges. + */ + +igraph_error_t igraph_coreness(const igraph_t *graph, + igraph_vector_int_t *cores, igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t *bin, *vert, *pos; + igraph_int_t maxdeg; + igraph_vector_int_t neis; + igraph_neimode_t omode; + + if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) { + IGRAPH_ERROR("Invalid mode in k-cores.", IGRAPH_EINVMODE); + } + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + omode = IGRAPH_REVERSE_MODE(mode); + + /* catch null graph */ + if (no_of_nodes == 0) { + igraph_vector_int_clear(cores); + return IGRAPH_SUCCESS; + } + + vert = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(vert, "Insufficient memory for k-cores."); + IGRAPH_FINALLY(igraph_free, vert); + + pos = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(pos, "Insufficient memory for k-cores."); + IGRAPH_FINALLY(igraph_free, pos); + + /* maximum degree + degree of vertices */ + IGRAPH_CHECK(igraph_degree(graph, cores, igraph_vss_all(), mode, IGRAPH_LOOPS)); + + /* null graph was already handled earlier, 'cores' is not empty */ + maxdeg = igraph_vector_int_max(cores); + + bin = IGRAPH_CALLOC(maxdeg + 1, igraph_int_t); + IGRAPH_CHECK_OOM(bin, "Insufficient memory for k-cores."); + IGRAPH_FINALLY(igraph_free, bin); + + /* degree histogram */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + bin[ VECTOR(*cores)[i] ] += 1; + } + + /* start pointers */ + for (igraph_int_t d = 0, start = 0; d <= maxdeg; d++) { + igraph_int_t k = bin[d]; + bin[d] = start; + start += k; + } + + /* sort in vert (and corrupt bin) */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + pos[i] = bin[VECTOR(*cores)[i]]; + vert[pos[i]] = i; + bin[VECTOR(*cores)[i]] += 1; + } + + /* correct bin */ + for (igraph_int_t d = maxdeg; d > 0; d--) { + bin[d] = bin[d - 1]; + } + bin[0] = 0; + + /* this is the main algorithm */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, maxdeg); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t v = vert[i]; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, omode, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t nei_count = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < nei_count; j++) { + igraph_int_t u = VECTOR(neis)[j]; + if (VECTOR(*cores)[u] > VECTOR(*cores)[v]) { + igraph_int_t du = VECTOR(*cores)[u]; + igraph_int_t pu = pos[u]; + igraph_int_t pw = bin[du]; + igraph_int_t w = vert[pw]; + if (u != w) { + pos[u] = pw; + pos[w] = pu; + vert[pu] = w; + vert[pw] = u; + } + bin[du] += 1; + VECTOR(*cores)[u] -= 1; + } + } + } + + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + igraph_free(bin); + igraph_free(pos); + igraph_free(vert); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/centrality/eigenvector.c b/src/centrality/eigenvector.c new file mode 100644 index 0000000..0cafd77 --- /dev/null +++ b/src/centrality/eigenvector.c @@ -0,0 +1,646 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_cycles.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "centrality/centrality_internal.h" + +#include +#include +#include + +/* Multiplies vector 'from' by the unweighted adjacency matrix and stores the result in 'to'. */ +static igraph_error_t adjmat_mul_unweighted(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_adjlist_t *adjlist = extra; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += from[nei]; + } + } + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_eigenvector_centrality_t { + const igraph_t *graph; + const igraph_inclist_t *inclist; + const igraph_vector_t *weights; +} igraph_i_eigenvector_centrality_t; + +/* Multiplies vector 'from' by the weighted adjacency matrix and stores the result in 'to'. */ +static igraph_error_t adjmat_mul_weighted(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_eigenvector_centrality_t *data = extra; + const igraph_t *graph = data->graph; + const igraph_inclist_t *inclist = data->inclist; + const igraph_vector_t *weights = data->weights; + igraph_vector_int_t *edges; + igraph_int_t i, j, nlen; + + for (i = 0; i < n; i++) { + edges = igraph_inclist_get(inclist, i); + nlen = igraph_vector_int_size(edges); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*edges)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + } + + return IGRAPH_SUCCESS; +} + +/* Checks if any eigenvector centrality values are zero. If they are, it indicates that the + * graph is not (strongly) connected. Eigenvector centrality is not meaningful for such graphs. + * To account for numerical inaccuracies, a threshold of 'eps' is used when testing for zero. + * This function is intended to be used with eigenvector centrality values scaled such that + * the maximum is 1. 'eps' is chosen accordinly. + */ +static void warn_zero_entries(const igraph_vector_t *evcent) { + /* This is a conservative tolerance that will still catch most values + * which should be zero without being triggered by small yet truly + * nonzero values. + * See https://github.com/igraph/igraph/pull/2592 */ + const igraph_real_t tol = 10 * DBL_EPSILON; + const igraph_int_t n = igraph_vector_size(evcent); + + for (igraph_int_t i=0; i < n; i++) { + igraph_real_t x = VECTOR(*evcent)[i]; + if (-tol < x && x < tol) { + IGRAPH_WARNING("Some eigenvector centralities are nearly zero, indicating that the graph may not be (strongly) connected. " + "Eigenvector centrality is not meaningful for disconnected graphs."); + return; + } + } +} + +static igraph_error_t igraph_i_eigenvector_centrality_undirected(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + igraph_vector_t values; + igraph_matrix_t vectors; + igraph_vector_t degree; + igraph_int_t i; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t negative_weights = false; + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (no_of_edges == 0) { + /* special case: empty graph */ + if (value) { + *value = 0; + } + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, igraph_vcount(graph))); + igraph_vector_fill(vector, 1); + } + if (no_of_nodes > 1) { + IGRAPH_WARNING("The graph has no edges and is disconnected. " + "Eigenvector centrality is not meaningful for disconnected graphs."); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weights vector length (%" IGRAPH_PRId ") not equal to " + "number of edges (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + /* Safe to call minmax, ecount == 0 case was caught earlier */ + igraph_vector_minmax(weights, &min, &max); + if (min == 0 && max == 0) { + /* special case: all weights are zeros */ + if (value) { + *value = 0; + } + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, igraph_vcount(graph))); + igraph_vector_fill(vector, 1); + } + if (no_of_nodes > 1) { + IGRAPH_WARNING("All edge weights are zero, making the graph effectively disconnected. " + "Eigenvector centrality is not meaningful for disconnected graphs."); + } + return IGRAPH_SUCCESS; + } + + if (min < 0) { + /* When there are negative weights, the eigenvalue and the eigenvector are no + * longer guaranteed to be non-negative. */ + negative_weights = true; + IGRAPH_WARNING("Negative weight in graph. The largest eigenvalue " + "will be selected, but it may not be the largest in magnitude. " + "Some eigenvector centralities may be negative."); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, no_of_nodes, 1); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_LOOPS, weights)); + + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(degree)[i]) { + /* Note: Keep random perturbation non-negative. */ + MATRIX(vectors, i, 0) = VECTOR(degree)[i] + RNG_UNIF(0, 1e-4); + } else if (! negative_weights) { + /* The eigenvector centrality of zero degree vertices is also zero. */ + MATRIX(vectors, i, 0) = 0.0; + } else { + /* When negative weights are present, a zero strength may occur even + * if the degree is not zero, and some edges have non-zero weight. */ + igraph_int_t deg; + IGRAPH_CHECK(igraph_degree_1(graph, °, i, IGRAPH_ALL, IGRAPH_LOOPS)); + MATRIX(vectors, i, 0) = deg == 0 ? 0.0 : 1.0; + } + + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + options->n = (int) no_of_nodes; + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->which[0] = 'L'; options->which[1] = 'A'; + options->start = 1; /* no random start vector */ + + if (!weights) { + + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rssolve(adjmat_mul_unweighted, + &adjlist, options, 0, &values, &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + igraph_inclist_t inclist; + igraph_i_eigenvector_centrality_t data; + + data.graph = graph; + data.inclist = &inclist; + data.weights = weights; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_arpack_rssolve(adjmat_mul_weighted, + &data, options, 0, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, no_of_nodes)); + + /* Note: With non-negative weights, a zero eigenvalue should only occur + * when there are no edges, or all edge have zero weight. This case is + * caught earlier. Thus we do not handle the case of zero eigenvalues here .*/ + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*vector)[i] = MATRIX(vectors, i, 0); + } + + /* Scale result so that the largest value is 1.0. */ + igraph_i_vector_scale_by_max_abs(vector); + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + if (! negative_weights) { + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*vector)[i] < 0) { + VECTOR(*vector)[i] = 0; + } + } + + warn_zero_entries(vector); + } + } + + if (value) { + *value = VECTOR(values)[0]; + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine."); + } + + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigenvector_centrality_directed(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + igraph_matrix_t values; + igraph_matrix_t vectors; + igraph_vector_t indegree; + igraph_bool_t dag; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t negative_weights = false; + + mode = IGRAPH_REVERSE_MODE(mode); + + if (no_of_edges == 0) { + /* special case: empty graph */ + if (value) { + *value = 0; + } + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, igraph_vcount(graph))); + igraph_vector_fill(vector, 1); + } + if (no_of_nodes > 1) { + IGRAPH_WARNING("The graph has no edges and is disconnected. " + "Eigenvector centrality is not meaningful for disconnected graphs."); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weights vector length (%" IGRAPH_PRId ") not equal to " + "number of edges (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + /* Safe to call minmax, ecount == 0 case was caught earlier */ + igraph_vector_minmax(weights, &min, &max); + + if (min < 0.0) { + /* When there are negative weights, the eigenvalue and the eigenvector are no + * longer guaranteed to be non-negative, or even real-valued. */ + negative_weights = true; + IGRAPH_WARNING("Negative weights in directed graph, eigenpair may be complex."); + } + if (min == 0.0 && max == 0.0) { + /* special case: all weights are zeros */ + if (value) { + *value = 0; + } + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, igraph_vcount(graph))); + igraph_vector_fill(vector, 1); + } + if (no_of_nodes > 1) { + IGRAPH_WARNING("All edge weights are zero, making the graph effectively disconnected. " + "Eigenvector centrality is not meaningful for disconnected graphs."); + } + return IGRAPH_SUCCESS; + } + } + + /* If the graph is a DAG, the eigenvalue is zero, and degenerate. Vectors that + * are zero in all vertices that have some out-edges, as well as their linear + * combinations, are all valid eigenvectors. + * + * ARPACK may converge to one of these, or it may converge to an all-zero + * vector, depending on chance. To eliminate this uncertainty, we return + * an eigenvector that has 1s in sinks (vertices with no out-edges) and 0s + * everywhere else. This ensures that the in-star, which is treated as the + * "most centralized network" for eigenvector centralization, has a non-zero + * centrality score in its centre. See https://github.com/igraph/igraph/issues/2679 + * for a discussion of the topic. + * + * For simplicity, we identify sinks by checking for zero out-strength. This + * also identifies effective sinks where out-edges exist, but they all have zero + * weight. This test is valid only when there are no negative weights, otherwise + * non-zero weights may still add up to a zero strength. Since negative weights + * are problematic anyway, we leave that case entirely up to ARPACK, without + * special treatment. + */ + if (! negative_weights) { + IGRAPH_CHECK(igraph_is_dag(graph, &dag)); + if (dag) { + /* special case: graph is a DAG */ + IGRAPH_WARNING("Graph is directed and acyclic; " + "returning eigenvector centralities of 1 in sink vertices, " + "and 0 everywhere else."); + if (value) { + *value = 0; + } + if (vector) { + IGRAPH_CHECK(igraph_strength( + graph, vector, igraph_vss_all(), IGRAPH_REVERSE_MODE(mode), + IGRAPH_LOOPS, weights + )); + for (igraph_int_t i=0; i < no_of_nodes; i++) { + if (VECTOR(*vector)[i] == 0) { + VECTOR(*vector)[i] = 1; + } else { + VECTOR(*vector)[i] = 0; + } + } + } + return IGRAPH_SUCCESS; + } + } + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for ARPACK.", IGRAPH_EOVERFLOW); + } + + options->n = (int) no_of_nodes; + options->start = 1; + options->nev = 1; + /* Use higher NCV than usual as long directed cycles cause convergence issues. + * According to numerical experiments, a cycle graph C_n tends to need about + * NCV = n/4 at minimum. */ + options->ncv = no_of_nodes > 30 ? 30 : options->n; + /* LM mode is not OK here because +1 and -1 can be eigenvalues at the + * same time, e.g.: a -> b -> a, c -> a */ + options->which[0] = 'L' ; options->which[1] = 'R'; + + IGRAPH_MATRIX_INIT_FINALLY(&values, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, no_of_nodes, 1); + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), + mode, IGRAPH_LOOPS, weights)); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(indegree)[i]) { + /* Note: Keep random perturbation non-negative. */ + MATRIX(vectors, i, 0) = VECTOR(indegree)[i] + RNG_UNIF(0, 1e-4); + } else if (! negative_weights) { + /* The eigenvector centrality of zero in-degree vertices is also zero. */ + MATRIX(vectors, i, 0) = 0.0; + } else { + /* When negative weights are present, a zero in-strength may occur even + * if the in-degree is not zero, and some in-edges have non-zero weight. */ + igraph_int_t deg; + IGRAPH_CHECK(igraph_degree_1(graph, °, i, mode, IGRAPH_LOOPS)); + MATRIX(vectors, i, 0) = deg == 0 ? 0.0 : 1.0; + } + } + + igraph_vector_destroy(&indegree); + IGRAPH_FINALLY_CLEAN(1); + + if (!weights) { + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(adjmat_mul_unweighted, + &adjlist, options, NULL, &values, + &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_inclist_t inclist; + igraph_i_eigenvector_centrality_t data; + + data.graph = graph; + data.inclist = &inclist; + data.weights = weights; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(adjmat_mul_weighted, + &data, options, NULL, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, options->n)); + + if (!negative_weights && MATRIX(values, 0, 0) <= 0) { + /* Pathological case: largest eigenvalue is zero, therefore all the + * scores can also be zeros, this will be a valid eigenvector. + * This usually happens with graphs that have lots of sinks and + * sources only. */ + igraph_vector_fill(vector, 0); + MATRIX(values, 0, 0) = 0; + } else { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(*vector)[i] = MATRIX(vectors, i, 0); + } + + /* Scale result so that the largest value is 1.0. */ + igraph_i_vector_scale_by_max_abs(vector); + } + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + if (! negative_weights) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(*vector)[i] < 0) { + VECTOR(*vector)[i] = 0; + } + } + + warn_zero_entries(vector); + } + } + + if (value) { + *value = MATRIX(values, 0, 0); + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine."); + } + + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eigenvector_centrality + * \brief Eigenvector centrality of the vertices. + * + * Eigenvector centrality is a measure of the importance of a node in a + * network. It assigns relative scores to all nodes in the network based + * on the principle that connections from high-scoring nodes contribute + * more to the score of the node in question than equal connections from + * low-scoring nodes. Specifically, the eigenvector centrality of each + * vertex is proportional to the sum of eigenvector centralities of its + * neighbors. In practice, the centralities are determined by calculating the + * eigenvector corresponding to the largest positive eigenvalue of the + * adjacency matrix. This is motivated by the fact that the principal + * eigenvector is guaranteed to be non-negative, assuming that edge weights + * are also non-negative. In fact, in connected undirected graphs, this is + * the \em only non-negative eigenvector. + * + * + * In the undirected case, this function considers + * the diagonal entries of the adjacency matrix to be \em twice the number of + * self-loops on the corresponding vertex. + * + * + * In the weighted case, the eigenvector centrality of a vertex is proportional + * to the weighted sum of centralities of its neighbours, i.e. + * c_j = sum_i w_ij c_i, where w_ij is the weight + * of the edge connecting vertex \c i to \c j. The weights of parallel edges + * are added up. + * + * + * The centrality scores returned by igraph are normalized such that the largest + * eigenvector centrality score is 1, unless all scores are zeros. + * + * + * Eigenvector centrality is meaningful only for (strongly) connected graphs. + * Undirected graphs that are not connected should be decomposed into connected + * components, and the eigenvector centrality calculated for each separately. + * This function does not directly verify that the graph is connected. If it is + * not, in the undirected case the scores of all but one component will typically + * be zeros. When zeros are detected, a warning is issued. + * + * + * Also note that the adjacency matrix of a directed acyclic graph or the + * adjacency matrix of an empty graph does not possess positive eigenvalues, + * therefore the eigenvector centrality is not meaningful for these graphs. + * igraph will return an eigenvalue of zero in such cases. The returned + * eigenvector centralities will all be equal for vertices with zero out-degree + * or zero in-degrees (depending on whether \p mode is \c IGRAPH_OUT or \c IGRAPH_IN) + * and zeros for other vertices. Such pathological cases can be detected + * by asking igraph to calculate the eigenvalue as well (using the \p value + * parameter, see below) and checking whether the eigenvalue is very close + * to zero. + * + * + * Eigenvector centrality was developed for networks with non-negative edge + * weights. While igraph does not refuse to carry out the calculation with + * negative weights, it will issue a warning. + * + * + * When working with directed graphs, consider using hub and authority + * scores instead, see \ref igraph_hub_and_authority_scores(). + * + * \param graph The input graph. It may be directed. + * \param vector Pointer to an initialized vector, it will be resized + * as needed. The result of the computation is stored here. It can + * be a null pointer, then it is ignored. + * \param value If not a null pointer, then the eigenvalue + * corresponding to the found eigenvector is stored here. + * \param mode How to consider edge directions in directed graphs. + * It is ignored for undirected graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the left eigenvector of the adjacency matrix is calculated, + * i.e. the centrality of a vertex is proportional to the sum + * of centralities of vertices pointing to it. This is the standard + * eigenvector centrality. + * \cli IGRAPH_IN + * the right eigenvector of the adjacency matrix is calculated, + * i.e. the centrality of a vertex is proportional to the sum + * of centralities of vertices it points to. + * \cli IGRAPH_ALL + * edge directions are ignored, and the unweighted eigenvector + * centrality is calculated. + * \endclist + * \param weights A null pointer (indicating no edge weights), or a vector + * giving the weights of the edges. Weights should be positive to guarantee + * a meaningful result. The algorithm might produce complex numbers when some + * weights are negative and the graph is directed. In this case only + * the real part is reported. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Supply \c NULL here to use the defaults. Note that the + * function overwrites the n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code. + * + * Time complexity: depends on the input graph, usually it is O(|V|+|E|). + * + * \sa \ref igraph_pagerank and \ref igraph_personalized_pagerank for + * modifications of eigenvector centrality. + * \ref igraph_hub_and_authority_scores() for a similar pair of measures + * intended for directed graphs. + * + * \example examples/simple/eigenvector_centrality.c + */ + +igraph_error_t igraph_eigenvector_centrality(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + if (!options) { + options = igraph_arpack_options_get_default(); + } + + if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) { + IGRAPH_ERROR("Invalid mode for eigenvector centrality.", IGRAPH_EINVAL); + } + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (mode == IGRAPH_ALL) { + return igraph_i_eigenvector_centrality_undirected(graph, vector, value, + weights, options); + } else { + return igraph_i_eigenvector_centrality_directed(graph, vector, value, + mode, weights, options); + } +} diff --git a/src/centrality/hub_authority.c b/src/centrality/hub_authority.c new file mode 100644 index 0000000..f74da43 --- /dev/null +++ b/src/centrality/hub_authority.c @@ -0,0 +1,505 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_structural.h" +#include "igraph_random.h" + +#include "centrality/centrality_internal.h" + +#include +#include + +/* struct for the unweighted variant of the HITS algorithm */ +typedef struct igraph_i_kleinberg_data_t { + igraph_adjlist_t *in; + igraph_adjlist_t *out; + igraph_vector_t *tmp; +} igraph_i_kleinberg_data_t; + +/* struct for the weighted variant of the HITS algorithm */ +typedef struct igraph_i_kleinberg_data2_t { + const igraph_t *graph; + igraph_inclist_t *in; + igraph_inclist_t *out; + igraph_vector_t *tmp; + const igraph_vector_t *weights; +} igraph_i_kleinberg_data2_t; + +/* Checks if at least a certain fraction of HITS centrality scores are zero. + * Any zero value indicates that the graphs corresponding to A A^T and A^T A are + * not connected, and therefore the solution is not unique. However, this situation + * is fairly common, and difficult to control. Thefore we only warn if the number + * of zero values exceeds a certain fraction. + * + * To account for numerical inaccuracies, a threshold of 'eps' is used when testing for zero. + * This function is intended to be used with centrality values scaled such that + * the maximum is 1. 'eps' is chosen accordinly. + * + * See the analogous function used in igraph_eigenvector_centrality() for details + * on the choice of 'eps'. + */ +static void warn_zero_entries(const igraph_vector_t *cent) { + const igraph_real_t tol = 10 * DBL_EPSILON; + const igraph_real_t frac = 0.3; /* warn if at least this fraction of centralities is zero */ + const igraph_int_t n = igraph_vector_size(cent); + + /* Skip check for small graphs */ + if (n < 10) { + return; + } + + const igraph_int_t max_zero_cnt = ((igraph_int_t) (frac*n)); + igraph_int_t zero_cnt = 0; + + for (igraph_int_t i=0; i < n; i++) { + igraph_real_t x = VECTOR(*cent)[i]; + if (-tol < x && x < tol) { + if (++zero_cnt > max_zero_cnt) { + IGRAPH_WARNINGF( + "More than %d%% of hub or authority scores are zeros. The presence of zero values " + "indicates that the solution is not unique, thus the returned result may not be meaningful.", + (int) (frac * 100) + ); + return; + } + } + } +} + +static igraph_error_t igraph_i_kleinberg_unweighted_hub_to_auth( + igraph_int_t n, igraph_vector_t *to, const igraph_real_t *from, + igraph_adjlist_t *in) { + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(in, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*to)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + VECTOR(*to)[i] += from[nei]; + } + } + return IGRAPH_SUCCESS; +} + +/* ARPACK auxiliary routine for the unweighted HITS algorithm */ +static igraph_error_t igraph_i_kleinberg_unweighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_kleinberg_data_t *data = (igraph_i_kleinberg_data_t*)extra; + igraph_adjlist_t *out = data->out; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + igraph_i_kleinberg_unweighted_hub_to_auth(n, tmp, from, data->in); + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(out, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_kleinberg_weighted_hub_to_auth(igraph_int_t n, + igraph_vector_t *to, const igraph_real_t *from, igraph_inclist_t *in, + const igraph_t *g, const igraph_vector_t *weights) { + igraph_vector_int_t *neis; + igraph_int_t nlen, i, j; + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(in, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*to)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei_edge = VECTOR(*neis)[j]; + igraph_int_t nei = IGRAPH_OTHER(g, nei_edge, i); + VECTOR(*to)[i] += from[nei] * VECTOR(*weights)[nei_edge]; + } + } + return IGRAPH_SUCCESS; +} + +/* ARPACK auxiliary routine for the weighted HITS algorithm */ +static igraph_error_t igraph_i_kleinberg_weighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_kleinberg_data2_t *data = (igraph_i_kleinberg_data2_t*)extra; + igraph_inclist_t *out = data->out; + igraph_vector_t *tmp = data->tmp; + const igraph_vector_t *weights = data->weights; + const igraph_t *g = data->graph; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + igraph_i_kleinberg_weighted_hub_to_auth(n, tmp, from, data->in, g, weights); + + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(out, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei_edge = VECTOR(*neis)[j]; + igraph_int_t nei = IGRAPH_OTHER(g, nei_edge, i); + to[i] += VECTOR(*tmp)[nei] * VECTOR(*weights)[nei_edge]; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hub_and_authority_scores + * \brief Kleinberg's hub and authority scores (HITS). + * + * Hub and authority scores are a generalization of the ideas behind + * eigenvector centrality to directed graphs. The authority score of + * a vertex is proportional to the sum of the hub scores of vertices + * that point to it. Conversely, the hub score of a vertex is proportional + * to the sum of authority scores of vertices that it points to. These + * concepts are also known under the name Hyperlink-Induced Topic Search (HITS). + * + * + * The hub and authority scores of the vertices are defined as the principal + * eigenvectors of A A^T and A^T A, respectively, + * where A is the adjacency matrix of the graph and A^T + * is its transpose. The motivation for choosing the principal eigenvector + * is that it is guaranteed to be non-negative when edge weights are also + * non-negative. + * + * + * If vectors \c h and \c a contain hub and authority scores, then the two + * scores are related by h = A a and a = A^T h. + * When the principal eigenvalue of A A^T is degenerate, there + * is no unique solution to the hub- and authority-score problem. + * igraph guarantees that the scores that are returned are matching, i.e. are + * related by these formulas, even in this situation. + * + * + * Note that hub and authority scores are not well behaved in extremely sparse + * graphs where no single connected component dominates the undirected graphs + * corresponding to A A^T and A^T A. In these cases, + * there are many different non-negative eigenvectors, all reasonable solutions + * to the HITS equations. The symptom of such a situation is that a large + * fraction of the scores are zeros. igraph issues a warning when this is + * detected. + * + * + * Results are scaled so that the largest hub and authority scores are both 1. + * + * + * The concept of hub and authority scores were developed for \em directed graphs. + * In undirected graphs, both the hub and authority scores are equal to the + * eigenvector centrality, which can be computed using + * \ref igraph_eigenvector_centrality(). + * + * + * HITS scores were developed for networks with non-negative edge weights. + * While igraph does not refuse to carry out the calculation with negative + * weights, it will issue a warning. + * + * + * See the following reference on the meaning of this score: + * J. Kleinberg. Authoritative sources in a hyperlinked + * environment. \emb Proc. 9th ACM-SIAM Symposium on Discrete + * Algorithms, \eme 1998. Extended version in \emb Journal of the + * ACM \eme 46 (1999). + * https://doi.org/10.1145/324133.324140 + * Also appears as IBM Research Report RJ 10076, May 1997. + * + * \param graph The input graph. Can be directed and undirected. + * \param hub_vector Pointer to an initialized vector, the hub scores are + * stored here. If a null pointer then it is ignored. + * \param authority_vector Pointer to an initialized vector, the authority + * scores are stored here. If a null pointer then it is ignored. + * \param value If not a null pointer then the eigenvalue + * corresponding to the calculated eigenvectors is stored here. + * \param weights A null pointer (meaning no edge weights), or a vector + * giving the weights of the edges. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Supply \c NULL here to use the defaults. Note that the + * function overwrites the n (number of vertices) parameter + * and it always starts the calculation from a vector calculated based on + * the degree of the vertices. + * \return Error code. + * + * Time complexity: depends on the input graph, usually it is O(|V|), + * the number of vertices. + * + * \sa \ref igraph_pagerank(), \ref igraph_personalized_pagerank(); + * \ref igraph_eigenvector_centrality() for a similar measure intended + * for undirected graphs. + */ +igraph_error_t igraph_hub_and_authority_scores(const igraph_t *graph, + igraph_vector_t *hub_vector, igraph_vector_t *authority_vector, + igraph_real_t *value, + const igraph_vector_t *weights, igraph_arpack_options_t *options) { + + /* The current implementation computes hub scores, i.e the principal + * eigenvector of A A^T, and transforms these to authority scores as + * authority = A^T hub. */ + + igraph_adjlist_t inadjlist, outadjlist; + igraph_inclist_t ininclist, outinclist; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_t tmp; + igraph_vector_t values; + igraph_matrix_t vectors; + igraph_i_kleinberg_data_t extra; + igraph_i_kleinberg_data2_t extra2; + igraph_vector_t *my_hub_vector_p; + igraph_vector_t my_hub_vector; + igraph_bool_t negative_weights = false; + + if (! igraph_is_directed(graph)) { + /* In undirected graphs, hub and authority scores are the same as eigenvector + * centralities. We issue a warning to avoid user confusion. + * If Ax = lambda x then A^2 x = lambda^2 x. Therefore the principal + * eigenvector of A is also a principal eigenvector of A^2. However, + * if both lambda and -lambda are eigenvalues of A, then the lambda^2 + * eigenvalue of A^2 will be degenerate. This happens for example in + * an even cycle graph where 2 and -2 are the largest eigenvalues in magnitude, + * therefore 4 is a degenerate eigenvalue of A^2, with eigenspace spanned by + * (0, 1, 0, 1, ...) and (1, 0, 1, ...). The vector (1, 1, ...), which + * would be expected by users based on symmetry considerations, may not be + * returned. We avoid such issues by falling back to igraph_eigenvector_centrality() */ + + IGRAPH_WARNING("Hub and authority scores requested for undirected graph. " + "These are the same as eigenvector centralities."); + if (! hub_vector) { + IGRAPH_VECTOR_INIT_FINALLY(&my_hub_vector, no_of_nodes); + my_hub_vector_p = &my_hub_vector; + } else { + my_hub_vector_p = hub_vector; + } + IGRAPH_CHECK(igraph_eigenvector_centrality(graph, my_hub_vector_p, value, IGRAPH_ALL, weights, options)); + *value = (*value) * (*value); /* adjust the eigenvalue, see comment at top */ + if (authority_vector) { + IGRAPH_CHECK(igraph_vector_update(authority_vector, my_hub_vector_p)); + } + if (! hub_vector) { + igraph_vector_destroy(&my_hub_vector); + IGRAPH_FINALLY_CLEAN(1); + } + return IGRAPH_SUCCESS; + } + + if (igraph_ecount(graph) == 0) { + /* special case: empty graph */ + if (value) { + *value = 0; + } + if (hub_vector) { + IGRAPH_CHECK(igraph_vector_resize(hub_vector, no_of_nodes)); + igraph_vector_fill(hub_vector, 1.0); + } + if (authority_vector) { + IGRAPH_CHECK(igraph_vector_resize(authority_vector, no_of_nodes)); + igraph_vector_fill(authority_vector, 1.0); + } + if (no_of_nodes > 1) { + IGRAPH_WARNING("The graph has no edges. Hub and authority scores are not meaningful."); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERRORF( + "Weights vector length (%" IGRAPH_PRId ") should match number of " + "edges (%" IGRAPH_PRId ") when calculating " + "hub or authority scores.", + IGRAPH_EINVAL, + igraph_vector_size(weights), + igraph_ecount(graph)); + } + + /* Safe to call minmax, ecount == 0 case was caught earlier */ + igraph_vector_minmax(weights, &min, &max); + + if (min < 0.0) { + /* When there are negative weights, the principal eigenvalue and the eigenvector + * are no longer guaranteed to be non-negative. */ + negative_weights = true; + IGRAPH_WARNING("Negative weight in graph. The largest eigenvalue " + "will be selected, but it may not be the largest in magnitude. " + "Some hub and authority scores may be negative."); + } + + if (min == 0 && max == 0) { + /* special case: all weights are zeros */ + if (value) { + *value = 0; + } + if (hub_vector) { + IGRAPH_CHECK(igraph_vector_resize(hub_vector, no_of_nodes)); + igraph_vector_fill(hub_vector, 1); + } + if (authority_vector) { + IGRAPH_CHECK(igraph_vector_resize(authority_vector, no_of_nodes)); + igraph_vector_fill(authority_vector, 1); + } + IGRAPH_WARNING("All edge weights are zero. Hub and authority scores are not meaningful."); + return IGRAPH_SUCCESS; + } + } + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (!options) { + options = igraph_arpack_options_get_default(); + } + + options->n = (int) no_of_nodes; + options->start = 1; /* no random start vector */ + + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, options->n); + + if (weights == NULL) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &inadjlist, IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &inadjlist); + IGRAPH_CHECK(igraph_adjlist_init(graph, &outadjlist, IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &outadjlist); + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &ininclist, IGRAPH_IN, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &ininclist); + IGRAPH_CHECK(igraph_inclist_init(graph, &outinclist, IGRAPH_OUT, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &outinclist); + } + + /* We calculate hub scores, which correlate with out-degrees / out-strengths. + * Thus we use out-strengths as starting values. */ + IGRAPH_CHECK(igraph_strength(graph, &tmp, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, weights)); + + for (igraph_int_t i = 0; i < options->n; i++) { + if (VECTOR(tmp)[i] != 0) { + /* Note: Keep random perturbation non-negative. */ + MATRIX(vectors, i, 0) = VECTOR(tmp)[i] + RNG_UNIF(0, 1e-4); + } else if (! negative_weights) { + /* The hub score of zero out-degree vertices is also zero. */ + MATRIX(vectors, i, 0) = 0.0; + } else { + /* When negative weights are present, a zero out-strength may occur even + * if the out-degree is not zero, and some out-edges have non-zero weight. */ + igraph_int_t deg; + IGRAPH_CHECK(igraph_degree_1(graph, °, i, IGRAPH_OUT, /* loops */ true)); + MATRIX(vectors, i, 0) = deg == 0 ? 0.0 : 1.0; + } + } + + extra.in = &inadjlist; extra.out = &outadjlist; extra.tmp = &tmp; + extra2.in = &ininclist; extra2.out = &outinclist; extra2.tmp = &tmp; + extra2.graph = graph; extra2.weights = weights; + + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->which[0] = 'L'; options->which[1] = 'A'; + + if (weights == NULL) { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_kleinberg_unweighted, &extra, + options, 0, &values, &vectors)); + } else { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_kleinberg_weighted, &extra2, + options, 0, &values, &vectors)); + } + + if (value) { + *value = VECTOR(values)[0]; + } + + if (hub_vector || authority_vector) { + if (!hub_vector) { + IGRAPH_VECTOR_INIT_FINALLY(&my_hub_vector, options->n); + my_hub_vector_p = &my_hub_vector; + } else { + my_hub_vector_p = hub_vector; + } + + IGRAPH_CHECK(igraph_vector_resize(my_hub_vector_p, options->n)); + for (igraph_int_t i = 0; i < options->n; i++) { + VECTOR(*my_hub_vector_p)[i] = MATRIX(vectors, i, 0); + } + + igraph_i_vector_scale_by_max_abs(my_hub_vector_p); + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + if (! negative_weights) { + for (igraph_int_t i = 0; i < options->n; i++) { + if (VECTOR(*my_hub_vector_p)[i] < 0) { + VECTOR(*my_hub_vector_p)[i] = 0; + } + } + } + + warn_zero_entries(my_hub_vector_p); + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + if (authority_vector) { + IGRAPH_CHECK(igraph_vector_resize(authority_vector, no_of_nodes)); + igraph_vector_null(authority_vector); + if (weights == NULL) { + igraph_i_kleinberg_unweighted_hub_to_auth(no_of_nodes, authority_vector, &VECTOR(*my_hub_vector_p)[0], &inadjlist); + } else { + igraph_i_kleinberg_weighted_hub_to_auth(no_of_nodes, authority_vector, &VECTOR(*my_hub_vector_p)[0], &ininclist, graph, weights); + } + igraph_i_vector_scale_by_max_abs(authority_vector); + } + + if (!hub_vector && authority_vector) { + igraph_vector_destroy(&my_hub_vector); + IGRAPH_FINALLY_CLEAN(1); + } + if (weights == NULL) { + igraph_adjlist_destroy(&outadjlist); + igraph_adjlist_destroy(&inadjlist); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_inclist_destroy(&outinclist); + igraph_inclist_destroy(&ininclist); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/centrality/pagerank.c b/src/centrality/pagerank.c new file mode 100644 index 0000000..d216c58 --- /dev/null +++ b/src/centrality/pagerank.c @@ -0,0 +1,721 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "centrality/prpack_internal.h" + +#include + +static igraph_error_t igraph_i_personalized_pagerank_arpack(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + const igraph_vector_t *reset, + const igraph_vector_t *weights, + igraph_arpack_options_t *options); + +typedef struct { + const igraph_t *graph; + igraph_adjlist_t *adjlist; + igraph_real_t damping; + igraph_vector_t *outdegree; + igraph_vector_t *tmp; + igraph_vector_t *reset; +} pagerank_data_t; + +typedef struct { + const igraph_t *graph; + igraph_inclist_t *inclist; + const igraph_vector_t *weights; + igraph_real_t damping; + igraph_vector_t *outdegree; + igraph_vector_t *tmp; + igraph_vector_t *reset; +} pagerank_data_weighted_t; + +/* The two pagerank_operator functions below update the probabilities of a random walker + * being in each of the vertices after one step of the walk. */ + +static igraph_error_t pagerank_operator_unweighted(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + pagerank_data_t *data = extra; + igraph_adjlist_t *adjlist = data->adjlist; + igraph_vector_t *outdegree = data->outdegree; + igraph_vector_t *tmp = data->tmp; + igraph_vector_t *reset = data->reset; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + igraph_real_t sumfrom = 0.0; + igraph_real_t fact = 1 - data->damping; + + /* Calculate p(x) / outdegree(x) in advance for all the vertices. + * Note that we may divide by zero here; this is intentional since + * we won't use those values and we save a comparison this way. + * At the same time, we calculate the global probability of a + * random jump in `sumfrom`. For vertices with no outgoing edges, + * we will surely jump from there if we are there, hence those + * vertices contribute p(x) to the teleportation probability. + * For vertices with some outgoing edges, we jump from there with + * probability `fact` if we are there, hence they contribute + * p(x)*fact */ + for (i = 0; i < n; i++) { + sumfrom += VECTOR(*outdegree)[i] != 0 ? from[i] * fact : from[i]; + VECTOR(*tmp)[i] = from[i] / VECTOR(*outdegree)[i]; + } + + /* Here we calculate the part of the `to` vector that results from + * moving along links (and not from teleportation) */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + to[i] *= data->damping; + } + + /* Now we add the contribution from random jumps. `reset` is a vector + * that defines the probability of ending up in vertex i after a jump. + * `sumfrom` is the global probability of jumping as mentioned above. */ + /* printf("sumfrom = %.6f\n", (float)sumfrom); */ + + if (reset) { + /* Running personalized PageRank */ + for (i = 0; i < n; i++) { + to[i] += sumfrom * VECTOR(*reset)[i]; + } + } else { + /* Traditional PageRank with uniform reset vector */ + sumfrom /= n; + for (i = 0; i < n; i++) { + to[i] += sumfrom; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t pagerank_operator_weighted(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + pagerank_data_weighted_t *data = extra; + const igraph_t *graph = data->graph; + igraph_inclist_t *inclist = data->inclist; + const igraph_vector_t *weights = data->weights; + igraph_vector_t *outdegree = data->outdegree; + igraph_vector_t *tmp = data->tmp; + igraph_vector_t *reset = data->reset; + igraph_int_t i, j, nlen; + igraph_real_t sumfrom = 0.0; + igraph_vector_int_t *neis; + igraph_real_t fact = 1 - data->damping; + + /* + printf("PageRank weighted: multiplying vector: "); + for (i=0; i 0) { + sumfrom += from[i] * fact; + VECTOR(*tmp)[i] = from[i] / VECTOR(*outdegree)[i]; + } else { + sumfrom += from[i]; + /* The following value is used only when all outgoing edges have + * weight zero (as opposed to there being no outgoing edges at all). + * We set it to zero to avoid a 0.0*inf situation when computing + * to[i] below. */ + VECTOR(*tmp)[i] = 0; + } + } + + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(inclist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + to[i] += VECTOR(*weights)[edge] * VECTOR(*tmp)[nei]; + } + to[i] *= data->damping; + } + + /* printf("sumfrom = %.6f\n", (float)sumfrom); */ + + if (reset) { + /* Running personalized PageRank */ + for (i = 0; i < n; i++) { + to[i] += sumfrom * VECTOR(*reset)[i]; + } + } else { + /* Traditional PageRank with uniform reset vector */ + sumfrom /= n; + for (i = 0; i < n; i++) { + to[i] += sumfrom; + } + } + + /* + printf("PageRank weighted: multiplied vector: "); + for (i=0; i1 - damping
. + * If the random walker gets stuck in a sink vertex, it will also restart + * from a random vertex. + * + * + * The PageRank centrality is mainly useful for directed graphs. In undirected + * graphs it converges to trivial values proportional to degrees as the damping + * factor approaches 1. + * + * + * Starting from version 0.9, igraph has two PageRank implementations, + * and the user can choose between them. The first implementation is + * \c IGRAPH_PAGERANK_ALGO_ARPACK, which phrases the PageRank calculation + * as an eigenvalue problem, which is then solved using the ARPACK library. + * This was the default before igraph version 0.7. The second and recommended + * implementation is \c IGRAPH_PAGERANK_ALGO_PRPACK. This is using the + * PRPACK package, see https://github.com/dgleich/prpack. PRPACK uses an + * algebraic method, i.e. solves a linear system to obtain the PageRank + * scores. + * + * + * Note that the PageRank of a given vertex depends on the PageRank + * of all other vertices, so even if you want to calculate the PageRank for + * only some of the vertices, all of them must be calculated. Requesting + * the PageRank for only some of the vertices does not result in any + * performance increase at all. + * + * + * References: + * + * + * Sergey Brin and Larry Page: The Anatomy of a Large-Scale Hypertextual + * Web Search Engine. Proceedings of the 7th World-Wide Web Conference, + * Brisbane, Australia, April 1998. + * https://doi.org/10.1016/S0169-7552(98)00110-X + * + * \param graph The graph object. + * \param weights Optional edge weights. May be a \c NULL pointer, + * meaning unweighted edges, or a vector of non-negative values + * of the same length as the number of edges. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable. When using \c IGRAPH_PAGERANK_ALGO_ARPACK, + * the eigenvalue corresponding to the PageRank vector is stored here. It is + * expected to be exactly one. Checking this value can be used to diagnose cases + * when ARPACK failed to converge to the leading eigenvector. + * When using \c IGRAPH_PAGERANK_ALGO_PRPACK, this is always set to 1.0. + * \param damping The damping factor ("d" in the original paper). + * Must be a probability in the range [0, 1]. A commonly used value is 0.85. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param vids The vertex IDs for which the PageRank is returned. This parameter + * is only for convenience. Computing PageRank for fewer than all vertices will + * not speed up the calculation. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_ARPACK, \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param options Options for the ARPACK method. See \ref igraph_arpack_options_t + * for details. Supply \c NULL here to use the defaults. Note that the function + * overwrites the n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and it always + * starts the calculation from a non-random vector calculated based on the + * degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID in \p vids. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_personalized_pagerank() and \ref igraph_personalized_pagerank_vs() + * for the personalized PageRank measure. See \ref igraph_arpack_rssolve() and + * \ref igraph_arpack_rnsolve() for the underlying machinery used by + * \c IGRAPH_PAGERANK_ALGO_ARPACK. + * + * \example examples/simple/igraph_pagerank.c + */ +igraph_error_t igraph_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options) { + return igraph_personalized_pagerank( + graph, weights, + vector, value, + NULL, + damping, directed, + vids, + algo, + options); +} + +/** + * \function igraph_personalized_pagerank_vs + * \brief Calculates the personalized Google PageRank for the specified vertices. + * + * The personalized PageRank is similar to the original PageRank measure, but + * when the random walk is restarted, a new starting vertex is chosen according to + * a specified distribution. + * This distribution is used both when restarting randomly with probability + * 1 - damping, and when the walker is forced to restart due to being + * stuck in a sink vertex (a vertex with no outgoing edges). + * + * + * This simplified interface takes a vertex sequence and resets the random walk to + * one of the vertices in the specified vertex sequence, chosen uniformly. A typical + * application of personalized PageRank is when the random walk is reset to the same + * vertex every time: this can easily be achieved using \ref igraph_vss_1() which + * generates a vertex sequence containing only a single vertex. + * + * + * Note that the personalized PageRank of a given vertex depends on the + * personalized PageRank of all other vertices, so even if you want to calculate + * the personalized PageRank for only some of the vertices, all of them must be + * calculated. Requesting the personalized PageRank for only some of the vertices + * does not result in any performance increase at all. + * + * \param graph The graph object. + * \param weights Optional edge weights, it is either a null pointer, + * then the edges are not weighted, or a vector of the same length + * as the number of edges. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable. When using \c IGRAPH_PAGERANK_ALGO_ARPACK, + * the eigenvalue corresponding to the PageRank vector is stored here. It is + * expected to be exactly one. Checking this value can be used to diagnose cases + * when ARPACK failed to converge to the leading eigenvector. + * When using \c IGRAPH_PAGERANK_ALGO_PRPACK, this is always set to 1.0. + * \param reset_vids IDs of the vertices used when resetting the random walk. + * The walk will be restarted from a vertex in this set, chosen uniformly at + * random. Duplicate vertices are allowed. + * \param damping The damping factor ("d" in the original paper). + * Must be a probability in the range [0, 1]. A commonly used value is 0.85. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param vids The vertex IDs for which the PageRank is returned. This parameter + * is only for convenience. Computing PageRank for fewer than all vertices will + * not speed up the calculation. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_ARPACK, \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param options Options for the ARPACK method. See \ref igraph_arpack_options_t + * for details. Supply \c NULL here to use the defaults. Note that the function + * overwrites the n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and it always + * starts the calculation from a non-random vector calculated based on the + * degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID in + * \p vids or an empty reset vertex sequence in + * \p vids_reset. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_pagerank() for the non-personalized implementation. + */ +igraph_error_t igraph_personalized_pagerank_vs( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + igraph_vs_t reset_vids, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options) { + + igraph_vector_t reset; + igraph_vit_t vit; + + IGRAPH_VECTOR_INIT_FINALLY(&reset, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_vit_create(graph, reset_vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + while (!IGRAPH_VIT_END(vit)) { + /* Increment by 1 instead of setting to 1 to handle duplicates. */ + VECTOR(reset)[IGRAPH_VIT_GET(vit)] += 1.0; + IGRAPH_VIT_NEXT(vit); + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_personalized_pagerank( + graph, weights, vector, + value, &reset, + damping, directed, vids, algo, + options)); + + igraph_vector_destroy(&reset); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_personalized_pagerank + * \brief Calculates the personalized Google PageRank for the specified vertices. + * + * The personalized PageRank is similar to the original PageRank measure, but + * when the random walk is restarted, a new starting vertex is chosen non-uniformly, + * according to the distribution specified in \p reset + * (instead of the uniform distribution in the original PageRank measure). + * The \p reset distribution is used both when restarting randomly with probability + * 1 - damping, and when the walker is forced to restart due to being + * stuck in a sink vertex (a vertex with no outgoing edges). + * + * + * Note that the personalized PageRank of a given vertex depends on the + * personalized PageRank of all other vertices, so even if you want to calculate + * the personalized PageRank for only some of the vertices, all of them must be + * calculated. Requesting the personalized PageRank for only some of the vertices + * does not result in any performance increase at all. + * + * \param graph The graph object. + * \param weights Optional edge weights. May be a \c NULL pointer, + * meaning unweighted edges, or a vector of non-negative values + * of the same length as the number of edges. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable. When using \c IGRAPH_PAGERANK_ALGO_ARPACK, + * the eigenvalue corresponding to the PageRank vector is stored here. It is + * expected to be exactly one. Checking this value can be used to diagnose cases + * when ARPACK failed to converge to the leading eigenvector. + * When using \c IGRAPH_PAGERANK_ALGO_PRPACK, this is always set to 1.0. + * \param reset The probability distribution over the vertices used when + * resetting the random walk. It is either a \c NULL pointer (denoting + * a uniform choice that results in the original PageRank measure) + * or a vector of the same length as the number of vertices. + * \param damping The damping factor ("d" in the original paper). + * Must be a probability in the range [0, 1]. A commonly used value is 0.85. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param vids The vertex IDs for which the PageRank is returned. This parameter + * is only for convenience. Computing PageRank for fewer than all vertices will + * not speed up the calculation. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_ARPACK, \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param options Options for the ARPACK method. See \ref igraph_arpack_options_t + * for details. Supply \c NULL here to use the defaults. Note that the function + * overwrites the n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and it always + * starts the calculation from a non-random vector calculated based on the + * degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID in + * \p vids or an invalid reset vector in \p reset. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_pagerank() for the non-personalized implementation, + * \ref igraph_personalized_pagerank_vs() for a personalized implementation + * with resetting to specific vertices. + */ +igraph_error_t igraph_personalized_pagerank( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *vector, igraph_real_t *value, + const igraph_vector_t *reset, + igraph_real_t damping, igraph_bool_t directed, + igraph_vs_t vids, + igraph_pagerank_algo_t algo, + igraph_arpack_options_t *options) { + + if (damping < 0.0 || damping > 1.0) { + IGRAPH_ERROR("The PageRank damping factor must be in the range [0,1].", IGRAPH_EINVAL); + } + + if (algo == IGRAPH_PAGERANK_ALGO_ARPACK) { + return igraph_i_personalized_pagerank_arpack(graph, vector, value, vids, + directed, damping, reset, + weights, options ? options : igraph_arpack_options_get_default() + ); + } else if (algo == IGRAPH_PAGERANK_ALGO_PRPACK) { + return igraph_i_personalized_pagerank_prpack(graph, vector, value, vids, + directed, damping, reset, + weights); + } + + IGRAPH_ERROR("Unknown PageRank algorithm", IGRAPH_EINVAL); +} + +/* + * ARPACK-based implementation of \c igraph_personalized_pagerank. + * + * See \c igraph_personalized_pagerank for the documentation of the parameters. + */ +static igraph_error_t igraph_i_personalized_pagerank_arpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + const igraph_vector_t *reset, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + igraph_matrix_t values; + igraph_matrix_t vectors; + igraph_neimode_t dirmode; + igraph_vector_t outdegree; + igraph_vector_t indegree; + igraph_vector_t tmp; + igraph_vector_t normalized_reset; + + igraph_int_t i; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + igraph_real_t reset_sum; /* used only when reset != NULL */ + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weights vector when calculating PageRank scores.", IGRAPH_EINVAL); + } + + if (reset && igraph_vector_size(reset) != no_of_nodes) { + IGRAPH_ERROR("Invalid length of reset vector when calculating personalized PageRank scores.", IGRAPH_EINVAL); + } + + if (reset) { + reset_sum = igraph_vector_sum(reset); + if (no_of_nodes > 0 && reset_sum == 0) { + IGRAPH_ERROR("The sum of the elements in the reset vector must not be zero.", IGRAPH_EINVAL); + } + + igraph_real_t reset_min = igraph_vector_min(reset); + if (reset_min < 0) { + IGRAPH_ERROR("The reset vector must not contain negative elements.", IGRAPH_EINVAL); + } + if (!isfinite(reset_sum)) { + IGRAPH_ERROR("The reset vector must not contain infinite or NaN values.", IGRAPH_EINVAL); + } + } + + if (no_of_edges == 0) { + /* Special case: graph with no edges. Result is the same as the personalization vector. */ + if (value) { + *value = 1.0; + } + if (vector) { + if (reset && no_of_nodes > 0) { + IGRAPH_CHECK(igraph_vector_update(vector, reset)); + igraph_vector_scale(vector, 1.0 / reset_sum); + } else { + IGRAPH_CHECK(igraph_vector_resize(vector, no_of_nodes)); + igraph_vector_fill(vector, 1.0 / no_of_nodes); + } + } + return IGRAPH_SUCCESS; + } + + options->n = (int) no_of_nodes; + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rnsolve */ + options->which[0] = 'L'; options->which[1] = 'R'; + options->start = 1; /* no random start vector */ + + directed = directed && igraph_is_directed(graph); + + if (weights) { + igraph_real_t min, max; + + /* Safe to call minmax, ecount == 0 case was caught earlier */ + igraph_vector_minmax(weights, &min, &max); + if (min < 0) { + IGRAPH_ERROR("Edge weights must not be negative.", IGRAPH_EINVAL); + } + if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + if (min == 0 && max == 0) { + /* Special case: all weights are zeros. Result is the same as the personalization vector. */ + if (value) { + *value = 1.0; + } + if (vector) { + IGRAPH_CHECK(igraph_vector_resize(vector, no_of_nodes)); + if (reset) { + for (i=0; i < no_of_nodes; ++i) { + VECTOR(*vector)[i] = VECTOR(*reset)[i]; + } + igraph_vector_scale(vector, 1.0 / igraph_vector_sum(vector)); + } else { + igraph_vector_fill(vector, 1.0 / no_of_nodes); + } + } + return IGRAPH_SUCCESS; + } + } + + IGRAPH_MATRIX_INIT_FINALLY(&values, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + + if (directed) { + dirmode = IGRAPH_IN; + } else { + dirmode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, options->n); + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, options->n); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, options->n); + + if (reset) { + /* Normalize reset vector so the sum is 1 */ + IGRAPH_CHECK(igraph_vector_init_copy(&normalized_reset, reset)); + IGRAPH_FINALLY(igraph_vector_destroy, &normalized_reset); + + igraph_vector_scale(&normalized_reset, 1.0 / reset_sum); + } + + IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(), + directed ? IGRAPH_OUT : IGRAPH_ALL, IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), + directed ? IGRAPH_IN : IGRAPH_ALL, IGRAPH_LOOPS, weights)); + + /* Set up an appropriate starting vector. We start from the (possibly weighted) + * in-degrees plus some small random noise to avoid convergence problems. */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(indegree)[i] > 0) { + /* Note: Keep random perturbation non-negative. */ + MATRIX(vectors, i, 0) = VECTOR(indegree)[i] + RNG_UNIF(0, 1e-4); + } else { + MATRIX(vectors, i, 0) = 1; + } + } + + if (!weights) { + + igraph_adjlist_t adjlist; + pagerank_data_t data; + + data.graph = graph; + data.adjlist = &adjlist; + data.damping = damping; + data.outdegree = &outdegree; + data.tmp = &tmp; + data.reset = reset ? &normalized_reset : NULL; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, dirmode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(pagerank_operator_unweighted, + &data, options, NULL, &values, &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + igraph_inclist_t inclist; + pagerank_data_weighted_t data; + + data.graph = graph; + data.inclist = &inclist; + data.weights = weights; + data.damping = damping; + data.outdegree = &outdegree; + data.tmp = &tmp; + data.reset = reset ? &normalized_reset : NULL; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, dirmode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(pagerank_operator_weighted, + &data, options, NULL, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (reset) { + igraph_vector_destroy(&normalized_reset); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&tmp); + igraph_vector_destroy(&outdegree); + igraph_vector_destroy(&indegree); + IGRAPH_FINALLY_CLEAN(3); + + if (value) { + *value = MATRIX(values, 0, 0); + } + + if (vector) { + igraph_vit_t vit; + igraph_int_t nodes_to_calc; + igraph_real_t sum = 0; + + for (i = 0; i < no_of_nodes; i++) { + sum += MATRIX(vectors, i, 0); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_resize(vector, nodes_to_calc)); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + VECTOR(*vector)[i] = MATRIX(vectors, IGRAPH_VIT_GET(vit), 0); + VECTOR(*vector)[i] /= sum; + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/centrality/prpack.cpp b/src/centrality/prpack.cpp new file mode 100644 index 0000000..1391b28 --- /dev/null +++ b/src/centrality/prpack.cpp @@ -0,0 +1,134 @@ +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_error.h" + +#include "centrality/prpack_internal.h" +#include "centrality/prpack/prpack_igraph_graph.h" +#include "centrality/prpack/prpack_solver.h" +#include "core/exceptions.h" + +#include + +using namespace prpack; +using namespace std; + +/* + * PRPACK-based implementation of \c igraph_personalized_pagerank. + * + * See \c igraph_personalized_pagerank for the documentation of the parameters. + */ +igraph_error_t igraph_i_personalized_pagerank_prpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + const igraph_vector_t *reset, + const igraph_vector_t *weights) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_int_t i, no_of_nodes = igraph_vcount(graph); + + double *u = nullptr; + std::unique_ptr v; + + if (reset) { + if (igraph_vector_size(reset) != no_of_nodes) { + IGRAPH_ERROR("Invalid length of reset vector when calculating personalized PageRank scores.", IGRAPH_EINVAL); + } + + /* Normalize reset vector so the sum is 1 */ + double reset_min = igraph_vector_min(reset); + if (reset_min < 0) { + IGRAPH_ERROR("The reset vector must not contain negative elements.", IGRAPH_EINVAL); + } + + double reset_sum = igraph_vector_sum(reset); + if (reset_sum == 0) { + IGRAPH_ERROR("The sum of the elements in the reset vector must not be zero.", IGRAPH_EINVAL); + } + + if (!isfinite(reset_sum)) { + IGRAPH_ERROR("The reset vector must not contain infinite or NaN values.", IGRAPH_EINVAL); + } + + // Construct the personalization vector + v.reset(new double[no_of_nodes]); + for (i = 0; i < no_of_nodes; i++) { + v[i] = VECTOR(*reset)[i] / reset_sum; + } + + // u is the distribution used when restarting the walk due to being stuck in a sink + // v is the distribution used when restarting due to damping + // Here we use the same distribution for both + u = v.get(); + } + + // Since PRPACK uses the algebraic method to solve PageRank, damping factors very close to 1.0 + // may lead to numerical instability, the apperance of non-finite values, or the iteration + // never terminating. + if (damping > 0.999) { + IGRAPH_WARNINGF( + "Damping factor is %g. " + "Damping values close to 1 may lead to numerical instability when using PRPACK.", + damping); + } + + // Construct and run the solver + prpack_igraph_graph prpack_graph; + IGRAPH_CHECK(prpack_graph.convert_from_igraph(graph, weights, directed)); + prpack_solver solver(&prpack_graph, false); + std::unique_ptr res( solver.solve(damping, 1e-10, u, v.get(), "") ); + + // Delete the personalization vector + v.reset(); + + // Check whether the solver converged + // TODO: this is commented out because some of the solvers do not implement it yet + /* + if (!res->converged) { + IGRAPH_WARNING("PRPACK solver failed to converge. Results may be inaccurate."); + } + */ + + // Fill the result vector + { + // Use of igraph "finally" stack is safe in this block + // since no exceptions can be thrown from here. + + igraph_vit_t vit; + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + igraph_int_t nodes_to_calc = IGRAPH_VIT_SIZE(vit); + IGRAPH_CHECK(igraph_vector_resize(vector, nodes_to_calc)); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + VECTOR(*vector)[i] = res->x[IGRAPH_VIT_GET(vit)]; + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + // PRPACK calculates PageRank scores by solving a linear system, + // so there is no eigenvalue. We return an exact 1.0 in all cases. + if (value) { + *value = 1.0; + } + + return IGRAPH_SUCCESS; + + IGRAPH_HANDLE_EXCEPTIONS_END; +} diff --git a/src/centrality/prpack/CMakeLists.txt b/src/centrality/prpack/CMakeLists.txt new file mode 100644 index 0000000..8bf5291 --- /dev/null +++ b/src/centrality/prpack/CMakeLists.txt @@ -0,0 +1,44 @@ +# Declare the files needed to compile the PRPACK-related stuff +add_library( + prpack + OBJECT + prpack_base_graph.cpp + prpack_igraph_graph.cpp + prpack_preprocessed_ge_graph.cpp + prpack_preprocessed_gs_graph.cpp + prpack_preprocessed_scc_graph.cpp + prpack_preprocessed_schur_graph.cpp + prpack_result.cpp + prpack_solver.cpp + prpack_utils.cpp +) + +target_compile_definitions( + prpack + PUBLIC + PRPACK_IGRAPH_SUPPORT=1 +) + +target_include_directories( + prpack + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET prpack PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Since these are included as object files, they should call the +# function as is (without visibility specification) +target_compile_definitions(prpack PRIVATE IGRAPH_STATIC) + +# PRPACK attempts to use OpenMP pragmas, so check whether we need any extra +# compiler flags to support it +if(IGRAPH_OPENMP_SUPPORT) + target_link_libraries(prpack PRIVATE OpenMP::OpenMP_CXX) +endif() + +# Turn on all warnings for GCC, clang and MSVC +use_all_warnings(prpack) diff --git a/src/centrality/prpack/prpack.h b/src/centrality/prpack/prpack.h new file mode 100644 index 0000000..bcddf37 --- /dev/null +++ b/src/centrality/prpack/prpack.h @@ -0,0 +1,11 @@ +#ifndef PRPACK +#define PRPACK + +#include "prpack_csc.h" +#include "prpack_csr.h" +#include "prpack_edge_list.h" +#include "prpack_base_graph.h" +#include "prpack_solver.h" +#include "prpack_result.h" + +#endif diff --git a/src/centrality/prpack/prpack_base_graph.cpp b/src/centrality/prpack/prpack_base_graph.cpp new file mode 100644 index 0000000..f89febb --- /dev/null +++ b/src/centrality/prpack/prpack_base_graph.cpp @@ -0,0 +1,345 @@ +#include "prpack_base_graph.h" +#include "prpack_utils.h" +#include +//#include +//#include +#include +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_base_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; +} + +prpack_base_graph::prpack_base_graph() { + initialize(); + num_vs = num_es = 0; +} + +prpack_base_graph::prpack_base_graph(const prpack_csc* g) { + initialize(); + num_vs = g->num_vs; + num_es = g->num_es; + // fill in heads and tails + num_self_es = 0; + int* hs = g->heads; + int* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = hs[h]; + const int end_ti = (h + 1 != num_vs) ? hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = ts[ti]; + ++tails[t]; + if (h == t) + ++num_self_es; + } + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = hs[h]; + const int end_ti = (h + 1 != num_vs) ? hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = ts[ti]; + heads[tails[t] + osets[t]++] = h; + } + } + // clean up + delete[] osets; +} + +prpack_base_graph::prpack_base_graph(const prpack_int64_csc* g) { + initialize(); + // TODO remove the assert and add better behavior + assert(g->num_vs <= std::numeric_limits::max()); + num_vs = (int)g->num_vs; + num_es = (int)g->num_es; + // fill in heads and tails + num_self_es = 0; + int64_t* hs = g->heads; + int64_t* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = (int)hs[h]; + const int end_ti = (h + 1 != num_vs) ? (int)hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = (int)ts[ti]; + ++tails[t]; + if (h == t) + ++num_self_es; + } + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = (int)hs[h]; + const int end_ti = (h + 1 != num_vs) ? (int)hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = (int)ts[ti]; + heads[tails[t] + osets[t]++] = h; + } + } + // clean up + delete[] osets; +} + +prpack_base_graph::prpack_base_graph(const prpack_csr* g) { + (void)g; // to silence an unused argument warning + initialize(); + throw std::runtime_error("not implemented yet"); +} + +prpack_base_graph::prpack_base_graph(const prpack_edge_list* g) { + initialize(); + num_vs = g->num_vs; + num_es = g->num_es; + // fill in heads and tails + num_self_es = 0; + int* hs = g->heads; + int* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) + heads[tails[ts[i]] + osets[ts[i]]++] = hs[i]; + // clean up + delete[] osets; +} + +#if 0 +prpack_base_graph::prpack_base_graph(const char* filename, const char* format, const bool weighted) { + initialize(); + FILE* f = fopen(filename, "r"); + const string s(filename); + const string t(format); + const string ext = (t == "") ? s.substr(s.rfind('.') + 1) : t; + if (ext == "smat") { + read_smat(f, weighted); + } else { + prpack_utils::validate(!weighted, + "Error: graph format is not compatible with weighted option."); + if (ext == "edges" || ext == "eg2") { + read_edges(f); + } else if (ext == "graph-txt") { + read_ascii(f); + } else { + prpack_utils::validate(false, "Error: invalid graph format."); + } + } + fclose(f); +} +#endif + +prpack_base_graph::~prpack_base_graph() { + delete[] heads; + delete[] tails; + delete[] vals; +} + +#if 0 +void prpack_base_graph::read_smat(FILE* f, const bool weighted) { + // read in header + double ignore = 0.0; + int retval = fscanf(f, "%d %lf %d", &num_vs, &ignore, &num_es); + if (retval != 3) { + throw std::runtime_error("error while parsing smat file"); + } + // fill in heads and tails + num_self_es = 0; + int* hs = new int[num_es]; + int* ts = new int[num_es]; + heads = new int[num_es]; + tails = new int[num_vs]; + double* vs = NULL; + if (weighted) { + vs = new double[num_es]; + vals = new double[num_es]; + } + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + retval = fscanf(f, "%d %d %lf", &hs[i], &ts[i], &((weighted) ? vs[i] : ignore)); + if (retval != 3) { + throw std::runtime_error("error while parsing smat file"); + } + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) { + const int idx = tails[ts[i]] + osets[ts[i]]++; + heads[idx] = hs[i]; + if (weighted) + vals[idx] = vs[i]; + } + // clean up + delete[] hs; + delete[] ts; + delete[] vs; + delete[] osets; +} + +void prpack_base_graph::read_edges(FILE* f) { + vector > al; + int h, t; + num_es = num_self_es = 0; + while (fscanf(f, "%d %d", &h, &t) == 2) { + const int m = (h < t) ? t : h; + if ((int) al.size() < m + 1) + al.resize(m + 1); + al[t].push_back(h); + ++num_es; + if (h == t) + ++num_self_es; + } + num_vs = al.size(); + heads = new int[num_es]; + tails = new int[num_vs]; + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + for (int j = 0; j < (int) al[tails_i].size(); ++j) + heads[heads_i++] = al[tails_i][j]; + } +} + +void prpack_base_graph::read_ascii(FILE* f) { + int retval = fscanf(f, "%d", &num_vs); + if (retval != 1) { + throw std::runtime_error("error while parsing ascii file"); + } + while (getc(f) != '\n'); + vector* al = new vector[num_vs]; + num_es = num_self_es = 0; + char s[32]; + for (int h = 0; h < num_vs; ++h) { + bool line_ended = false; + while (!line_ended) { + for (int i = 0; ; ++i) { + s[i] = getc(f); + if ('9' < s[i] || s[i] < '0') { + line_ended = s[i] == '\n'; + if (i != 0) { + s[i] = '\0'; + const int t = atoi(s); + al[t].push_back(h); + ++num_es; + if (h == t) + ++num_self_es; + } + break; + } + } + } + } + heads = new int[num_es]; + tails = new int[num_vs]; + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + for (int j = 0; j < (int) al[tails_i].size(); ++j) + heads[heads_i++] = al[tails_i][j]; + } + delete[] al; +} +#endif + +prpack_base_graph::prpack_base_graph(int nverts, int nedges, + std::pair* edges) { + initialize(); + num_vs = nverts; + num_es = nedges; + + // fill in heads and tails + num_self_es = 0; + int* hs = new int[num_es]; + int* ts = new int[num_es]; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + assert(edges[i].first >= 0 && edges[i].first < num_vs); + assert(edges[i].second >= 0 && edges[i].second < num_vs); + hs[i] = edges[i].first; + ts[i] = edges[i].second; + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) + heads[tails[ts[i]] + osets[ts[i]]++] = hs[i]; + // clean up + delete[] hs; + delete[] ts; + delete[] osets; +} + +/** Normalize the edge weights to sum to one. + */ +void prpack_base_graph::normalize_weights() { + if (!vals) { + // skip normalizing weights if not using values + return; + } + std::vector rowsums(num_vs,0.); + // the graph is in a compressed in-edge list. + for (int i=0; i +#include + +namespace prpack { + + class prpack_base_graph { + private: + // helper methods + void initialize(); +#if 0 + void read_smat(std::FILE* f, const bool weighted); + void read_edges(std::FILE* f); + void read_ascii(std::FILE* f); +#endif + public: + // instance variables + int num_vs; + int num_es; + int num_self_es; + int* heads; + int* tails; + double* vals; + // constructors + prpack_base_graph(); // only to support inheritance + prpack_base_graph(const prpack_csc* g); + prpack_base_graph(const prpack_int64_csc* g); + prpack_base_graph(const prpack_csr* g); + prpack_base_graph(const prpack_edge_list* g); +#if 0 + prpack_base_graph(const char* filename, const char* format, const bool weighted); +#endif + prpack_base_graph(int nverts, int nedges, std::pair* edges); + // destructor + ~prpack_base_graph(); + // operations + void normalize_weights(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_csc.h b/src/centrality/prpack/prpack_csc.h new file mode 100644 index 0000000..91f352d --- /dev/null +++ b/src/centrality/prpack/prpack_csc.h @@ -0,0 +1,30 @@ +#ifndef PRPACK_CSC +#define PRPACK_CSC + +#if !defined(_MSC_VER) && !defined (__MINGW32__) && !defined (__MINGW64__) +# include +#else +# include +typedef __int64 int64_t; +#endif + +namespace prpack { + + class prpack_csc { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + + class prpack_int64_csc { + public: + int64_t num_vs; + int64_t num_es; + int64_t* heads; + int64_t* tails; + }; +} + +#endif diff --git a/src/centrality/prpack/prpack_csr.h b/src/centrality/prpack/prpack_csr.h new file mode 100644 index 0000000..3c8c7b7 --- /dev/null +++ b/src/centrality/prpack/prpack_csr.h @@ -0,0 +1,16 @@ +#ifndef PRPACK_CSR +#define PRPACK_CSR + +namespace prpack { + + class prpack_csr { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_edge_list.h b/src/centrality/prpack/prpack_edge_list.h new file mode 100644 index 0000000..a84466f --- /dev/null +++ b/src/centrality/prpack/prpack_edge_list.h @@ -0,0 +1,16 @@ +#ifndef PRPACK_EDGE_LIST +#define PRPACK_EDGE_LIST + +namespace prpack { + + class prpack_edge_list { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_igraph_graph.cpp b/src/centrality/prpack/prpack_igraph_graph.cpp new file mode 100644 index 0000000..35cd85a --- /dev/null +++ b/src/centrality/prpack/prpack_igraph_graph.cpp @@ -0,0 +1,176 @@ +#include "prpack_igraph_graph.h" +#include +#include +#include + +#include "igraph_interface.h" + +using namespace prpack; +using namespace std; + +#ifdef PRPACK_IGRAPH_SUPPORT + +igraph_error_t prpack_igraph_graph::convert_from_igraph( + const igraph_t *g, const igraph_vector_t *weights, bool directed) { + + const bool treat_as_directed = igraph_is_directed(g) && directed; + const igraph_int_t vcount = igraph_vcount(g); + const igraph_int_t ecount = igraph_ecount(g); + double *p_weight; + int *p_head; + + if (vcount > INT_MAX) { + IGRAPH_ERROR("Too many vertices for PRPACK.", IGRAPH_EINVAL); + } + if (ecount > (treat_as_directed ? INT_MAX : INT_MAX/2)) { + IGRAPH_ERROR("Too many edges for PRPACK.", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL); + } + + // Get the number of vertices and edges. For undirected graphs, we add + // an edge in both directions. + num_vs = (int) vcount; + num_es = (int) ecount; + num_self_es = 0; + if (!treat_as_directed) { + num_es *= 2; + } + + // Allocate memory for heads and tails + p_head = heads = new int[num_es]; + tails = new int[num_vs]; + memset(tails, 0, num_vs * sizeof(tails[0])); + + // Allocate memory for weights if needed + if (weights) { + p_weight = vals = new double[num_es]; + } + + // Count the number of ignored edges (those with negative or zero weight) + int num_ignored_es = 0; + + if (treat_as_directed) { + // Use of igraph "finally" stack is safe in this block + // since no exceptions can be thrown from here. + + // Select all the edges and iterate over them by the source vertices + // Add the edges + igraph_eit_t eit; + IGRAPH_CHECK(igraph_eit_create(g, igraph_ess_all(IGRAPH_EDGEORDER_TO), &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + IGRAPH_EIT_NEXT(eit); + + // Handle the weight + if (weights != NULL) { + // Does this edge have zero or negative weight? + if (VECTOR(*weights)[eid] < 0) { + // Negative weights are disallowed. + IGRAPH_ERROR("Edge weights must not be negative.", IGRAPH_EINVAL); + } else if (isnan(VECTOR(*weights)[eid])) { + IGRAPH_ERROR("Edge weights must not be NaN.", IGRAPH_EINVAL); + } else if (VECTOR(*weights)[eid] == 0) { + // Edges with zero weight are ignored. + num_ignored_es++; + continue; + } + + *p_weight = VECTOR(*weights)[eid]; + ++p_weight; + } + + *p_head = IGRAPH_FROM(g, eid); + ++p_head; + ++tails[IGRAPH_TO(g, eid)]; + + if (IGRAPH_FROM(g, eid) == IGRAPH_TO(g, eid)) { + ++num_self_es; + } + } + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + } else { + // Use of igraph "finally" stack is safe in this block + // since no exceptions can be thrown from here. + + // Select all the edges and iterate over them by the target vertices + igraph_vector_int_t neis; + IGRAPH_CHECK(igraph_vector_int_init(&neis, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + for (int i = 0; i < num_vs; i++) { + IGRAPH_CHECK(igraph_incident(g, &neis, i, IGRAPH_ALL, IGRAPH_LOOPS)); + + int temp = igraph_vector_int_size(&neis); + + // TODO: should loop edges be added in both directions? + int *p_head_copy = p_head; + for (int j = 0; j < temp; j++) { + if (weights != NULL) { + if (VECTOR(*weights)[VECTOR(neis)[j]] <= 0) { + // Ignore + num_ignored_es++; + continue; + } + + *p_weight = VECTOR(*weights)[VECTOR(neis)[j]]; + ++p_weight; + } + + *p_head = IGRAPH_OTHER(g, VECTOR(neis)[j], i); + if (i == *p_head) { + num_self_es++; + } + ++p_head; + } + tails[i] = p_head - p_head_copy; + } + + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + // Decrease num_es by the number of ignored edges + num_es -= num_ignored_es; + + // Finalize the tails vector + for (int i = 0, sum = 0; i < num_vs; ++i) { + int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + + // Normalize the weights + normalize_weights(); + + // Debug + /* + printf("Heads:"); + for (i = 0; i < num_es; ++i) { + printf(" %d", heads[i]); + } + printf("\n"); + printf("Tails:"); + for (i = 0; i < num_vs; ++i) { + printf(" %d", tails[i]); + } + printf("\n"); + if (vals) { + printf("Vals:"); + for (i = 0; i < num_es; ++i) { + printf(" %.4f", vals[i]); + } + printf("\n"); + } + printf("===========================\n"); + */ + + return IGRAPH_SUCCESS; +} + +// PRPACK_IGRAPH_SUPPORT +#endif diff --git a/src/centrality/prpack/prpack_igraph_graph.h b/src/centrality/prpack/prpack_igraph_graph.h new file mode 100644 index 0000000..6bbcaa0 --- /dev/null +++ b/src/centrality/prpack/prpack_igraph_graph.h @@ -0,0 +1,33 @@ +#ifndef PRPACK_IGRAPH_GRAPH +#define PRPACK_IGRAPH_GRAPH + +#ifdef PRPACK_IGRAPH_SUPPORT + +#include "prpack_base_graph.h" + +#include "igraph_datatype.h" +#include "igraph_vector.h" + +namespace prpack { + + class prpack_igraph_graph : public prpack_base_graph { + public: + // constructors + prpack_igraph_graph() { } + + // We use a separate function to carry out the actual construction of the graph. + // The base class constructor sets the heads/tails/vals arrays to NULL, + // so these can safely be delete'ed by the destructor when + // convert_from_igraph() fails. + igraph_error_t convert_from_igraph(const igraph_t *g, + const igraph_vector_t *weights, + bool directed = true); + }; + +} + +// PRPACK_IGRAPH_SUPPORT +#endif + +// PRPACK_IGRAPH_GRAPH +#endif diff --git a/src/centrality/prpack/prpack_preprocessed_ge_graph.cpp b/src/centrality/prpack/prpack_preprocessed_ge_graph.cpp new file mode 100644 index 0000000..303e30d --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_ge_graph.cpp @@ -0,0 +1,65 @@ +#include "prpack_preprocessed_ge_graph.h" +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_ge_graph::initialize() { + matrix = NULL; + d = NULL; +} + +void prpack_preprocessed_ge_graph::initialize_weighted(const prpack_base_graph* bg) { + // initialize d + fill(d, d + num_vs, 1); + // fill in the matrix + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) { + const int start_j = bg->tails[i]; + const int end_j = (i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es; + for (int j = start_j; j < end_j; ++j) { + matrix[inum_vs + bg->heads[j]] += bg->vals[j]; + d[bg->heads[j]] -= bg->vals[j]; + } + } +} + +void prpack_preprocessed_ge_graph::initialize_unweighted(const prpack_base_graph* bg) { + // fill in the matrix + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) { + const int start_j = bg->tails[i]; + const int end_j = (i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es; + for (int j = start_j; j < end_j; ++j) + ++matrix[inum_vs + bg->heads[j]]; + } + // normalize the columns + for (int j = 0; j < num_vs; ++j) { + double sum = 0; + for (int inum_vs = 0; inum_vs < num_vs*num_vs; inum_vs += num_vs) + sum += matrix[inum_vs + j]; + if (sum > 0) { + d[j] = 0; + const double coeff = 1/sum; + for (int inum_vs = 0; inum_vs < num_vs*num_vs; inum_vs += num_vs) + matrix[inum_vs + j] *= coeff; + } else { + d[j] = 1; + } + } +} + +prpack_preprocessed_ge_graph::prpack_preprocessed_ge_graph(const prpack_base_graph* bg) { + initialize(); + num_vs = bg->num_vs; + num_es = bg->num_es; + matrix = new double[num_vs*num_vs]; + d = new double[num_vs]; + fill(matrix, matrix + num_vs*num_vs, 0); + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_ge_graph::~prpack_preprocessed_ge_graph() { + delete[] matrix; + delete[] d; +} diff --git a/src/centrality/prpack/prpack_preprocessed_ge_graph.h b/src/centrality/prpack/prpack_preprocessed_ge_graph.h new file mode 100644 index 0000000..678568f --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_ge_graph.h @@ -0,0 +1,26 @@ +#ifndef PRPACK_PREPROCESSED_GE_GRAPH +#define PRPACK_PREPROCESSED_GE_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_ge_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + double* matrix; + // constructors + prpack_preprocessed_ge_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_ge_graph(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_preprocessed_graph.h b/src/centrality/prpack/prpack_preprocessed_graph.h new file mode 100644 index 0000000..4f5a318 --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_graph.h @@ -0,0 +1,17 @@ +#ifndef PRPACK_PREPROCESSED_GRAPH +#define PRPACK_PREPROCESSED_GRAPH + +namespace prpack { + + // TODO: this class should not be seeable by the users of the library. + // Super graph class. + class prpack_preprocessed_graph { + public: + int num_vs; + int num_es; + double* d; + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_preprocessed_gs_graph.cpp b/src/centrality/prpack/prpack_preprocessed_gs_graph.cpp new file mode 100644 index 0000000..2cefd5d --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_gs_graph.cpp @@ -0,0 +1,80 @@ +#include "prpack_preprocessed_gs_graph.h" +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_gs_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; +} + +void prpack_preprocessed_gs_graph::initialize_weighted(const prpack_base_graph* bg) { + vals = new double[num_es]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + ii[tails_i] = 0; + const int start_j = bg->tails[tails_i]; + const int end_j = (tails_i + 1 != num_vs) ? bg->tails[tails_i + 1]: bg->num_es; + for (int j = start_j; j < end_j; ++j) { + if (tails_i == bg->heads[j]) + ii[tails_i] += bg->vals[j]; + else { + heads[heads_i] = bg->heads[j]; + vals[heads_i] = bg->vals[j]; + ++heads_i; + } + d[bg->heads[j]] -= bg->vals[j]; + } + } +} + +void prpack_preprocessed_gs_graph::initialize_unweighted(const prpack_base_graph* bg) { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + ii[tails_i] = 0; + const int start_j = bg->tails[tails_i]; + const int end_j = (tails_i + 1 != num_vs) ? bg->tails[tails_i + 1]: bg->num_es; + for (int j = start_j; j < end_j; ++j) { + if (tails_i == bg->heads[j]) + ++ii[tails_i]; + else + heads[heads_i++] = bg->heads[j]; + ++num_outlinks[bg->heads[j]]; + } + } + for (int i = 0; i < num_vs; ++i) { + if (num_outlinks[i] == 0) + num_outlinks[i] = -1; + ii[i] /= num_outlinks[i]; + } +} + +prpack_preprocessed_gs_graph::prpack_preprocessed_gs_graph(const prpack_base_graph* bg) { + initialize(); + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + heads = new int[num_es]; + tails = new int[num_vs]; + ii = new double[num_vs]; + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_gs_graph::~prpack_preprocessed_gs_graph() { + delete[] heads; + delete[] tails; + delete[] vals; + delete[] ii; + delete[] d; + delete[] num_outlinks; +} diff --git a/src/centrality/prpack/prpack_preprocessed_gs_graph.h b/src/centrality/prpack/prpack_preprocessed_gs_graph.h new file mode 100644 index 0000000..eec3fe2 --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_gs_graph.h @@ -0,0 +1,30 @@ +#ifndef PRPACK_PREPROCESSED_GS_GRAPH +#define PRPACK_PREPROCESSED_GS_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_gs_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int* heads; + int* tails; + double* vals; + double* ii; + double* num_outlinks; + // constructors + prpack_preprocessed_gs_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_gs_graph(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_preprocessed_scc_graph.cpp b/src/centrality/prpack/prpack_preprocessed_scc_graph.cpp new file mode 100644 index 0000000..a34ad7e --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_scc_graph.cpp @@ -0,0 +1,201 @@ +#include "prpack_preprocessed_scc_graph.h" +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_scc_graph::initialize() { + heads_inside = NULL; + tails_inside = NULL; + vals_inside = NULL; + heads_outside = NULL; + tails_outside = NULL; + vals_outside = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; + divisions = NULL; + encoding = NULL; + decoding = NULL; +} + +void prpack_preprocessed_scc_graph::initialize_weighted(const prpack_base_graph* bg) { + vals_inside = new double[num_es]; + vals_outside = new double[num_es]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_i = divisions[comp_i]; + const int end_i = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + for (int i = start_i; i < end_i; ++i) { + ii[i] = 0; + const int decoded = decoding[i]; + const int start_j = bg->tails[decoded]; + const int end_j = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + tails_inside[i] = num_es_inside; + tails_outside[i] = num_es_outside; + for (int j = start_j; j < end_j; ++j) { + const int h = encoding[bg->heads[j]]; + if (h == i) { + ii[i] += bg->vals[j]; + } else { + if (start_i <= h && h < end_i) { + heads_inside[num_es_inside] = h; + vals_inside[num_es_inside] = bg->vals[j]; + ++num_es_inside; + } else { + heads_outside[num_es_outside] = h; + vals_outside[num_es_outside] = bg->vals[j]; + ++num_es_outside; + } + } + d[h] -= bg->vals[j]; + } + } + } +} + +void prpack_preprocessed_scc_graph::initialize_unweighted(const prpack_base_graph* bg) { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_i = divisions[comp_i]; + const int end_i = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + for (int i = start_i; i < end_i; ++i) { + ii[i] = 0; + const int decoded = decoding[i]; + const int start_j = bg->tails[decoded]; + const int end_j = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + tails_inside[i] = num_es_inside; + tails_outside[i] = num_es_outside; + for (int j = start_j; j < end_j; ++j) { + const int h = encoding[bg->heads[j]]; + if (h == i) { + ++ii[i]; + } else { + if (start_i <= h && h < end_i) + heads_inside[num_es_inside++] = h; + else + heads_outside[num_es_outside++] = h; + } + ++num_outlinks[h]; + } + } + } + for (int i = 0; i < num_vs; ++i) { + if (num_outlinks[i] == 0) + num_outlinks[i] = -1; + ii[i] /= num_outlinks[i]; + } +} + +prpack_preprocessed_scc_graph::prpack_preprocessed_scc_graph(const prpack_base_graph* bg) { + initialize(); + // initialize instance variables + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + // initialize Tarjan's algorithm variables + num_comps = 0; + int mn = 0; // the number of vertices seen so far + int sz = 0; // size of st + int decoding_i = 0; // size of decoding currently filled in + decoding = new int[num_vs]; + int* scc = new int[num_vs]; // the strongly connected component this vertex is in + int* low = new int[num_vs]; // the lowest index this vertex can reach + int* num = new int[num_vs]; // the index of this vertex in the dfs traversal + int* st = new int[num_vs]; // a stack for the dfs + memset(num, -1, num_vs*sizeof(num[0])); + memset(scc, -1, num_vs*sizeof(scc[0])); + int* cs1 = new int[num_vs]; // call stack variable for dfs + int* cs2 = new int[num_vs]; // call stack variable for dfs + // run iterative Tarjan's algorithm + for (int root = 0; root < num_vs; ++root) { + if (num[root] != -1) + continue; + int csz = 1; + cs1[0] = root; + cs2[0] = bg->tails[root]; + // dfs + while (csz) { + const int p = cs1[csz - 1]; // node we're dfs-ing on + int& it = cs2[csz - 1]; // iteration of the for loop + if (it == bg->tails[p]) { + low[p] = num[p] = mn++; + st[sz++] = p; + } else { + low[p] = min(low[p], low[bg->heads[it - 1]]); + } + bool done = false; + int end_it = (p + 1 != num_vs) ? bg->tails[p + 1] : bg->num_es; + for (; it < end_it; ++it) { + int h = bg->heads[it]; + if (scc[h] == -1) { + if (num[h] == -1) { + // dfs(h, p); + cs1[csz] = h; + cs2[csz++] = bg->tails[h]; + ++it; + done = true; + break; + } + low[p] = min(low[p], low[h]); + } + } + if (done) + continue; + // if p is the first explored vertex of a scc + if (low[p] == num[p]) { + cs1[num_vs - 1 - num_comps] = decoding_i; + while (scc[p] != num_comps) { + scc[st[--sz]] = num_comps; + decoding[decoding_i++] = st[sz]; + } + ++num_comps; + } + --csz; + } + } + // set up other instance variables + divisions = new int[num_comps]; + divisions[0] = 0; + for (int i = 1; i < num_comps; ++i) + divisions[i] = cs1[num_vs - 1 - i]; + encoding = num; + for (int i = 0; i < num_vs; ++i) + encoding[decoding[i]] = i; + // fill in inside and outside instance variables + ii = new double[num_vs]; + tails_inside = cs1; + heads_inside = new int[num_es]; + tails_outside = cs2; + heads_outside = new int[num_es]; + num_es_inside = num_es_outside = 0; + // continue initialization based off of weightedness + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); + // free memory + // do not free num <==> encoding + // do not free cs1 <==> tails_inside + // do not free cs2 <==> tails_outside + delete[] scc; + delete[] low; + delete[] st; +} + +prpack_preprocessed_scc_graph::~prpack_preprocessed_scc_graph() { + delete[] heads_inside; + delete[] tails_inside; + delete[] vals_inside; + delete[] heads_outside; + delete[] tails_outside; + delete[] vals_outside; + delete[] ii; + delete[] d; + delete[] num_outlinks; + delete[] divisions; + delete[] encoding; + delete[] decoding; +} diff --git a/src/centrality/prpack/prpack_preprocessed_scc_graph.h b/src/centrality/prpack/prpack_preprocessed_scc_graph.h new file mode 100644 index 0000000..6584c3f --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_scc_graph.h @@ -0,0 +1,39 @@ +#ifndef PRPACK_PREPROCESSED_SCC_GRAPH +#define PRPACK_PREPROCESSED_SCC_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_scc_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int num_es_inside; + int* heads_inside; + int* tails_inside; + double* vals_inside; + int num_es_outside; + int* heads_outside; + int* tails_outside; + double* vals_outside; + double* ii; + double* num_outlinks; + int num_comps; + int* divisions; + int* encoding; + int* decoding; + // constructors + prpack_preprocessed_scc_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_scc_graph(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_preprocessed_schur_graph.cpp b/src/centrality/prpack/prpack_preprocessed_schur_graph.cpp new file mode 100644 index 0000000..2a422d0 --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_schur_graph.cpp @@ -0,0 +1,120 @@ +#include "prpack_preprocessed_schur_graph.h" +#include +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_schur_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; + encoding = NULL; + decoding = NULL; +} + +void prpack_preprocessed_schur_graph::initialize_weighted(const prpack_base_graph* bg) { + // permute d + ii = d; + d = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + d[encoding[i]] = ii[i]; + // convert bg to head/tail format + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + ii[tails_i] = 0; + tails[tails_i] = heads_i; + const int decoded = decoding[tails_i]; + const int start_i = bg->tails[decoded]; + const int end_i = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + for (int i = start_i; i < end_i; ++i) { + if (decoded == bg->heads[i]) + ii[tails_i] += bg->vals[i]; + else { + heads[heads_i] = encoding[bg->heads[i]]; + vals[heads_i] = bg->vals[i]; + ++heads_i; + } + } + } +} + +void prpack_preprocessed_schur_graph::initialize_unweighted(const prpack_base_graph* bg) { + // permute num_outlinks + ii = num_outlinks; + num_outlinks = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + num_outlinks[encoding[i]] = (ii[i] == 0) ? -1 : ii[i]; + // convert bg to head/tail format + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + ii[tails_i] = 0; + tails[tails_i] = heads_i; + const int decoded = decoding[tails_i]; + const int start_i = bg->tails[decoded]; + const int end_i = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + for (int i = start_i; i < end_i; ++i) { + if (decoded == bg->heads[i]) + ++ii[tails_i]; + else + heads[heads_i++] = encoding[bg->heads[i]]; + } + if (ii[tails_i] > 0) + ii[tails_i] /= num_outlinks[tails_i]; + } +} + +prpack_preprocessed_schur_graph::prpack_preprocessed_schur_graph(const prpack_base_graph* bg) { + initialize(); + // initialize instance variables + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + tails = new int[num_vs]; + heads = new int[num_es]; + const bool weighted = bg->vals != NULL; + if (weighted) { + vals = new double[num_vs]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int i = 0; i < bg->num_es; ++i) + d[bg->heads[i]] -= bg->vals[i]; + } else { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int i = 0; i < bg->num_es; ++i) + ++num_outlinks[bg->heads[i]]; + } + // permute no-inlink vertices to the beginning, and no-outlink vertices to the end + encoding = new int[num_vs]; + decoding = new int[num_vs]; + num_no_in_vs = num_no_out_vs = 0; + for (int i = 0; i < num_vs; ++i) { + if (bg->tails[i] == ((i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es)) { + decoding[encoding[i] = num_no_in_vs] = i; + ++num_no_in_vs; + } else if ((weighted) ? (d[i] == 1) : (num_outlinks[i] == 0)) { + decoding[encoding[i] = num_vs - 1 - num_no_out_vs] = i; + ++num_no_out_vs; + } + } + // permute everything else + for (int i = 0, p = num_no_in_vs; i < num_vs; ++i) + if (bg->tails[i] < ((i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es) && ((weighted) ? (d[i] < 1) : (num_outlinks[i] > 0))) + decoding[encoding[i] = p++] = i; + // continue initialization based off of weightedness + if (weighted) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_schur_graph::~prpack_preprocessed_schur_graph() { + delete[] heads; + delete[] tails; + delete[] vals; + delete[] ii; + delete[] d; + delete[] num_outlinks; + delete[] encoding; + delete[] decoding; +} diff --git a/src/centrality/prpack/prpack_preprocessed_schur_graph.h b/src/centrality/prpack/prpack_preprocessed_schur_graph.h new file mode 100644 index 0000000..a0593b1 --- /dev/null +++ b/src/centrality/prpack/prpack_preprocessed_schur_graph.h @@ -0,0 +1,33 @@ +#ifndef PRPACK_PREPROCESSED_SCHUR_GRAPH +#define PRPACK_PREPROCESSED_SCHUR_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + class prpack_preprocessed_schur_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int num_no_in_vs; + int num_no_out_vs; + int* heads; + int* tails; + double* vals; + double* ii; + double* num_outlinks; + int* encoding; + int* decoding; + // constructors + prpack_preprocessed_schur_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_schur_graph(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_result.cpp b/src/centrality/prpack/prpack_result.cpp new file mode 100644 index 0000000..1fef884 --- /dev/null +++ b/src/centrality/prpack/prpack_result.cpp @@ -0,0 +1,11 @@ +#include "prpack_result.h" +#include +using namespace prpack; + +prpack_result::prpack_result() { + x = NULL; +} + +prpack_result::~prpack_result() { + delete[] x; +} diff --git a/src/centrality/prpack/prpack_result.h b/src/centrality/prpack/prpack_result.h new file mode 100644 index 0000000..00d96b9 --- /dev/null +++ b/src/centrality/prpack/prpack_result.h @@ -0,0 +1,30 @@ +#ifndef PRPACK_RESULT +#define PRPACK_RESULT + +#include +#include + +namespace prpack { + + // Result class. + class prpack_result { + public: + // instance variables + int num_vs; + int num_es; + double* x; + double read_time; + double preprocess_time; + double compute_time; + int64_t num_es_touched; + std::string method; + int converged; + // constructor + prpack_result(); + // destructor + ~prpack_result(); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_solver.cpp b/src/centrality/prpack/prpack_solver.cpp new file mode 100644 index 0000000..4d2d1f5 --- /dev/null +++ b/src/centrality/prpack/prpack_solver.cpp @@ -0,0 +1,886 @@ +#include "prpack_solver.h" +#include "prpack_utils.h" +#include +#include +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_solver::initialize() { + geg = NULL; + gsg = NULL; + sg = NULL; + sccg = NULL; + owns_bg = true; +} + +prpack_solver::prpack_solver(const prpack_csc* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_int64_csc* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_csr* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_edge_list* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(prpack_base_graph* g, bool owns_bg) { + initialize(); + this->owns_bg = owns_bg; + TIME(read_time, bg = g); +} + +#if 0 +prpack_solver::prpack_solver(const char* filename, const char* format, const bool weighted) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(filename, format, weighted)); +} +#endif + +prpack_solver::~prpack_solver() { + if (owns_bg) { + delete bg; + } + delete geg; + delete gsg; + delete sg; + delete sccg; +} + +int prpack_solver::get_num_vs() { + return bg->num_vs; +} + +prpack_result* prpack_solver::solve(const double alpha, const double tol, const char* method) { + return solve(alpha, tol, NULL, NULL, method); +} + +prpack_result* prpack_solver::solve( + const double alpha, + const double tol, + const double* u, + const double* v, + const char* method) { + double preprocess_time = 0; + double compute_time = 0; + prpack_result* ret = NULL; + // decide which method to run + string m; + if (strcmp(method, "") != 0) + m = string(method); + else { + if (bg->num_vs < 128) + m = "ge"; + else if (sccg != NULL) + m = "sccgs"; + else if (sg != NULL) + m = "sg"; + else + m = "sccgs"; + if (u != v) + m += "_uv"; + } + // run the appropriate method + if (m == "ge") { + if (geg == NULL) { + TIME(preprocess_time, geg = new prpack_preprocessed_ge_graph(bg)); + } + TIME(compute_time, ret = solve_via_ge( + alpha, + tol, + geg->num_vs, + geg->matrix, + u)); + } else if (m == "ge_uv") { + if (geg == NULL) { + TIME(preprocess_time, geg = new prpack_preprocessed_ge_graph(bg)); + } + TIME(compute_time, ret = solve_via_ge_uv( + alpha, + tol, + geg->num_vs, + geg->matrix, + geg->d, + u, + v)); + } else if (m == "gs") { + if (gsg == NULL) { + TIME(preprocess_time, gsg = new prpack_preprocessed_gs_graph(bg)); + } + TIME(compute_time, ret = solve_via_gs( + alpha, + tol, + gsg->num_vs, + gsg->num_es, + gsg->heads, + gsg->tails, + gsg->vals, + gsg->ii, + gsg->d, + gsg->num_outlinks, + u, + v)); + } else if (m == "gserr") { + if (gsg == NULL) { + TIME(preprocess_time, gsg = new prpack_preprocessed_gs_graph(bg)); + } + TIME(compute_time, ret = solve_via_gs_err( + alpha, + tol, + gsg->num_vs, + gsg->num_es, + gsg->heads, + gsg->tails, + gsg->ii, + gsg->num_outlinks, + u, + v)); + } else if (m == "sgs") { + if (sg == NULL) { + TIME(preprocess_time, sg = new prpack_preprocessed_schur_graph(bg)); + } + TIME(compute_time, ret = solve_via_schur_gs( + alpha, + tol, + sg->num_vs, + sg->num_no_in_vs, + sg->num_no_out_vs, + sg->num_es, + sg->heads, + sg->tails, + sg->vals, + sg->ii, + sg->d, + sg->num_outlinks, + u, + sg->encoding, + sg->decoding)); + } else if (m == "sgs_uv") { + if (sg == NULL) { + TIME(preprocess_time, sg = new prpack_preprocessed_schur_graph(bg)); + } + TIME(compute_time, ret = solve_via_schur_gs_uv( + alpha, + tol, + sg->num_vs, + sg->num_no_in_vs, + sg->num_no_out_vs, + sg->num_es, + sg->heads, + sg->tails, + sg->vals, + sg->ii, + sg->d, + sg->num_outlinks, + u, + v, + sg->encoding, + sg->decoding)); + } else if (m == "sccgs") { + if (sccg == NULL) { + TIME(preprocess_time, sccg = new prpack_preprocessed_scc_graph(bg)); + } + TIME(compute_time, ret = solve_via_scc_gs( + alpha, + tol, + sccg->num_vs, + sccg->num_es_inside, + sccg->heads_inside, + sccg->tails_inside, + sccg->vals_inside, + sccg->num_es_outside, + sccg->heads_outside, + sccg->tails_outside, + sccg->vals_outside, + sccg->ii, + sccg->d, + sccg->num_outlinks, + u, + sccg->num_comps, + sccg->divisions, + sccg->encoding, + sccg->decoding)); + } else if (m == "sccgs_uv") { + if (sccg == NULL) { + TIME(preprocess_time, sccg = new prpack_preprocessed_scc_graph(bg)); + } + TIME(compute_time, ret = solve_via_scc_gs_uv( + alpha, + tol, + sccg->num_vs, + sccg->num_es_inside, + sccg->heads_inside, + sccg->tails_inside, + sccg->vals_inside, + sccg->num_es_outside, + sccg->heads_outside, + sccg->tails_outside, + sccg->vals_outside, + sccg->ii, + sccg->d, + sccg->num_outlinks, + u, + v, + sccg->num_comps, + sccg->divisions, + sccg->encoding, + sccg->decoding)); + } else { + throw invalid_argument("Unknown method specified for PRPACK: '" + m + "'."); + } + ret->method = m; + ret->read_time = read_time; + ret->preprocess_time = preprocess_time; + ret->compute_time = compute_time; + ret->num_vs = bg->num_vs; + ret->num_es = bg->num_es; + return ret; +} + +// VARIOUS SOLVING METHODS //////////////////////////////////////////////////////////////////////// + +prpack_result* prpack_solver::solve_via_ge( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* uv) { + prpack_result* ret = new prpack_result(); + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? uv : &uv_const; + // create matrix A + double* A = new double[num_vs*num_vs]; + for (int i = 0; i < num_vs*num_vs; ++i) + A[i] = -alpha*matrix[i]; + for (int i = 0; i < num_vs*num_vs; i += num_vs + 1) + ++A[i]; + // create vector b + double* b = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + b[i] = uv[uv_exists*i]; + // solve and normalize + ge(num_vs, A, b); + normalize(num_vs, b); + // clean up and return + delete[] A; + ret->num_es_touched = -1; + ret->x = b; + return ret; +} + +prpack_result* prpack_solver::solve_via_ge_uv( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* d, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // create matrix A + double* A = new double[num_vs*num_vs]; + for (int i = 0; i < num_vs*num_vs; ++i) + A[i] = -alpha*matrix[i]; + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) + for (int j = 0; j < num_vs; ++j) + A[inum_vs + j] -= alpha*u[u_exists*i]*d[j]; + for (int i = 0; i < num_vs*num_vs; i += num_vs + 1) + ++A[i]; + // create vector b + double* b = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + b[i] = (1 - alpha)*v[v_exists*i]; + // solve + ge(num_vs, A, b); + // clean up and return + delete[] A; + ret->num_es_touched = -1; + ret->x = b; + return ret; +} + +// Vanilla Gauss-Seidel. +prpack_result* prpack_solver::solve_via_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals != NULL; + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + x[i] = 0; + // initialize delta + double delta = 0; + // run Gauss-Seidel + ret->num_es_touched = 0; + double err = 1, c = 0; + do { + if (weighted) { + for (int i = 0; i < num_vs; ++i) { + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]*vals[j]; + new_val = alpha*new_val + (1 - alpha)*v[v_exists*i]; + delta -= alpha*x[i]*d[i]; + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*(d[i]*u[u_exists*i] + (1 - d[i])*ii[i]); + delta += alpha*new_val*d[i]; + COMPENSATED_SUM(err, x[i] - new_val, c); + x[i] = new_val; + } + } else { + for (int i = 0; i < num_vs; ++i) { + const double old_val = x[i]*num_outlinks[i]; + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + new_val = alpha*new_val + (1 - alpha)*v[v_exists*i]; + if (num_outlinks[i] < 0) { + delta -= alpha*old_val; + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*u[u_exists*i]; + delta += alpha*new_val; + } else { + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*ii[i]; + } + COMPENSATED_SUM(err, old_val - new_val, c); + x[i] = new_val/num_outlinks[i]; + } + } + // update iteration index + ret->num_es_touched += num_es; + } while (err >= tol); + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // return results + ret->x = x; + return ret; +} + +// Implement a gauss-seidel-like process with a strict error bound +// we return a solution with 1-norm error less than tol. +prpack_result* prpack_solver::solve_via_gs_err( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* ii, + const double* num_outlinks, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // Note to Dave, we can't rescale v because we could be running this + // same routine from multiple threads. + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) { + x[i] = 0.; + } + // initialize delta + double delta = 0.; + // run Gauss-Seidel, note that we store x/deg[i] throughout this + // iteration. + int64_t maxedges = (int64_t)((double)num_es*std::min( + log(tol)/log(alpha), + (double)PRPACK_SOLVER_MAX_ITERS)); + ret->num_es_touched = 0; + double err=1., c = 0.; + do { + // iterate through vertices + for (int i = 0; i < num_vs; ++i) { + double old_val = x[i]*num_outlinks[i]; // adjust back to the "true" value. + double new_val = 0.; + int start_j = tails[i], end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + } + new_val = alpha*new_val + alpha*ii[i]*old_val + (1.0-alpha)*v[v_exists*i]; + new_val += delta*u[u_exists*i]; // add the dangling node adjustment + if (num_outlinks[i] < 0) { + delta += alpha*(new_val - old_val); + } + // note that new_val > old_val, but the fabs is just for + COMPENSATED_SUM(err, -(new_val - old_val), c); + x[i] = new_val/num_outlinks[i]; + } + // update iteration index + ret->num_es_touched += num_es; + } while (err >= tol && ret->num_es_touched < maxedges); + if (err >= tol) { + ret->converged = 0; + } else { + ret->converged = 1; + } + // undo num_outlinks transformation + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // return results + ret->x = x; + return ret; +} + +// Gauss-Seidel using the Schur complement to separate dangling nodes. +prpack_result* prpack_solver::solve_via_schur_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int* encoding, + const int* decoding, + const bool should_normalize) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals != NULL; + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? prpack_utils::permute(num_vs, uv, encoding) : &uv_const; + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs - num_no_out_vs; ++i) + x[i] = uv[uv_exists*i]/(1 - alpha*ii[i])/((weighted) ? 1 : num_outlinks[i]); + // run Gauss-Seidel for the top left part of (I - alpha*P)*x = uv + ret->num_es_touched = 0; + double err, c; + do { + // iterate through vertices + int num_es_touched = 0; + err = c = 0; +#ifdef _OPENMP + #pragma omp parallel for firstprivate(c) reduction(+:err, num_es_touched) schedule(dynamic, 64) +#endif + for (int i = num_no_in_vs; i < num_vs - num_no_out_vs; ++i) { + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + if (weighted) { + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]*vals[j]; + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + new_val = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + x[i] = new_val; + } else { + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + new_val = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + x[i] = new_val/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + // update iteration index + ret->num_es_touched += num_es_touched; + } while (err/(1 - alpha) >= tol); + // solve for the dangling nodes + int num_es_touched = 0; +#ifdef _OPENMP + #pragma omp parallel for reduction(+:num_es_touched) schedule(dynamic, 64) +#endif + for (int i = num_vs - num_no_out_vs; i < num_vs; ++i) { + x[i] = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + x[i] += x[heads[j]]*((weighted) ? vals[j] : 1); + x[i] = (alpha*x[i] + uv[uv_exists*i])/(1 - alpha*ii[i]); + num_es_touched += end_j - start_j; + } + ret->num_es_touched += num_es_touched; + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs - num_no_out_vs; ++i) + x[i] *= num_outlinks[i]; + // normalize x to get the solution for: (I - alpha*P - alpha*u*d')*x = (1 - alpha)*v + if (should_normalize) + normalize(num_vs, x); + // return results + ret->x = prpack_utils::permute(num_vs, x, decoding); + delete[] x; + if (uv_exists) + delete[] uv; + return ret; +} + +prpack_result* prpack_solver::solve_via_schur_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int* encoding, + const int* decoding) { + // solve uv = u + prpack_result* ret_u = solve_via_schur_gs( + alpha, + tol, + num_vs, + num_no_in_vs, + num_no_out_vs, + num_es, + heads, + tails, + vals, + ii, + d, + num_outlinks, + u, + encoding, + decoding, + false); + // solve uv = v + prpack_result* ret_v = solve_via_schur_gs( + alpha, + tol, + num_vs, + num_no_in_vs, + num_no_out_vs, + num_es, + heads, + tails, + vals, + ii, + d, + num_outlinks, + v, + encoding, + decoding, + false); + // combine the u and v cases + return combine_uv(num_vs, d, num_outlinks, encoding, alpha, ret_u, ret_v); +} + +/** Gauss-Seidel using strongly connected components. + * Notes: + * If not weighted, then we store x[i] = "x[i]/outdegree" to + * avoid additional arithmetic. We don't do this for the weighted + * case because the adjustment may not be constant. + */ +prpack_result* prpack_solver::solve_via_scc_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding, + const bool should_normalize) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals_inside != NULL; + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? prpack_utils::permute(num_vs, uv, encoding) : &uv_const; + // CHECK initialize the solution with one iteration of GS from x=0. + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + x[i] = uv[uv_exists*i]/(1 - alpha*ii[i])/((weighted) ? 1 : num_outlinks[i]); + // create x_outside + double* x_outside = new double[num_vs]; + // run Gauss-Seidel for (I - alpha*P)*x = uv + ret->num_es_touched = 0; + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_comp = divisions[comp_i]; + const int end_comp = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + const bool parallelize = end_comp - start_comp > 512; + // initialize relevant x_outside values + for (int i = start_comp; i < end_comp; ++i) { + x_outside[i] = 0; + const int start_j = tails_outside[i]; + const int end_j = (i + 1 != num_vs) ? tails_outside[i + 1] : num_es_outside; + for (int j = start_j; j < end_j; ++j) + x_outside[i] += x[heads_outside[j]]*((weighted) ? vals_outside[j] : 1.); + ret->num_es_touched += end_j - start_j; + } + double err, c; + do { + int num_es_touched = 0; + err = c = 0; + if (parallelize) { + // iterate through vertices +#ifdef _OPENMP + #pragma omp parallel for firstprivate(c) reduction(+:err, num_es_touched) schedule(dynamic, 64) +#endif + for (int i = start_comp; i < end_comp; ++i) { + double new_val = x_outside[i]; + const int start_j = tails_inside[i]; + const int end_j = (i + 1 != num_vs) ? tails_inside[i + 1] : num_es_inside; + if (weighted) { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]*vals_inside[j]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + } else { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i])/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + } else { + for (int i = start_comp; i < end_comp; ++i) { + double new_val = x_outside[i]; + const int start_j = tails_inside[i]; + const int end_j = (i + 1 != num_vs) ? tails_inside[i + 1] : num_es_inside; + if (weighted) { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]*vals_inside[j]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + } else { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i])/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + } + // update iteration index + ret->num_es_touched += num_es_touched; + } while (err/(1 - alpha) >= tol*(end_comp - start_comp)/num_vs); + } + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // normalize x to get the solution for: (I - alpha*P - alpha*u*d')*x = (1 - alpha)*v + if (should_normalize) + normalize(num_vs, x); + // return results + ret->x = prpack_utils::permute(num_vs, x, decoding); + delete[] x; + delete[] x_outside; + if (uv_exists) + delete[] uv; + return ret; +} + +prpack_result* prpack_solver::solve_via_scc_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding) { + // solve uv = u + prpack_result* ret_u = solve_via_scc_gs( + alpha, + tol, + num_vs, + num_es_inside, + heads_inside, + tails_inside, + vals_inside, + num_es_outside, + heads_outside, + tails_outside, + vals_outside, + ii, + d, + num_outlinks, + u, + num_comps, + divisions, + encoding, + decoding, + false); + // solve uv = v + prpack_result* ret_v = solve_via_scc_gs( + alpha, + tol, + num_vs, + num_es_inside, + heads_inside, + tails_inside, + vals_inside, + num_es_outside, + heads_outside, + tails_outside, + vals_outside, + ii, + d, + num_outlinks, + v, + num_comps, + divisions, + encoding, + decoding, + false); + // combine u and v + return combine_uv(num_vs, d, num_outlinks, encoding, alpha, ret_u, ret_v); +} + +// VARIOUS HELPER METHODS ///////////////////////////////////////////////////////////////////////// + +// Run Gaussian-Elimination (note: this changes A and returns the solution in b) +void prpack_solver::ge(const int sz, double* A, double* b) { + // put into triangular form + for (int i = 0, isz = 0; i < sz; ++i, isz += sz) + for (int k = 0, ksz = 0; k < i; ++k, ksz += sz) + if (A[isz + k] != 0) { + const double coeff = A[isz + k]/A[ksz + k]; + A[isz + k] = 0; + for (int j = k + 1; j < sz; ++j) + A[isz + j] -= coeff*A[ksz + j]; + b[i] -= coeff*b[k]; + } + // backwards substitution + for (int i = sz - 1, isz = (sz - 1)*sz; i >= 0; --i, isz -= sz) { + for (int j = i + 1; j < sz; ++j) + b[i] -= A[isz + j]*b[j]; + b[i] /= A[isz + i]; + } +} + +// Normalize a vector to sum to 1. +void prpack_solver::normalize(const int length, double* x) { + double norm = 0, c = 0; + for (int i = 0; i < length; ++i) { + COMPENSATED_SUM(norm, x[i], c); + } + norm = 1/norm; + for (int i = 0; i < length; ++i) + x[i] *= norm; +} + +// Combine u and v results. +prpack_result* prpack_solver::combine_uv( + const int num_vs, + const double* d, + const double* num_outlinks, + const int* encoding, + const double alpha, + const prpack_result* ret_u, + const prpack_result* ret_v) { + prpack_result* ret = new prpack_result(); + const bool weighted = d != NULL; + double delta_u = 0; + double delta_v = 0; + for (int i = 0; i < num_vs; ++i) { + if ((weighted) ? (d[encoding[i]] == 1) : (num_outlinks[encoding[i]] < 0)) { + delta_u += ret_u->x[i]; + delta_v += ret_v->x[i]; + } + } + const double s = ((1 - alpha)*alpha*delta_v)/(1 - alpha*delta_u); + const double t = 1 - alpha; + ret->x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + ret->x[i] = s*ret_u->x[i] + t*ret_v->x[i]; + ret->num_es_touched = ret_u->num_es_touched + ret_v->num_es_touched; + // clean up and return + delete ret_u; + delete ret_v; + return ret; +} diff --git a/src/centrality/prpack/prpack_solver.h b/src/centrality/prpack/prpack_solver.h new file mode 100644 index 0000000..91a3cef --- /dev/null +++ b/src/centrality/prpack/prpack_solver.h @@ -0,0 +1,180 @@ +#ifndef PRPACK_SOLVER +#define PRPACK_SOLVER +#include "prpack_base_graph.h" +#include "prpack_csc.h" +#include "prpack_csr.h" +#include "prpack_edge_list.h" +#include "prpack_preprocessed_ge_graph.h" +#include "prpack_preprocessed_gs_graph.h" +#include "prpack_preprocessed_scc_graph.h" +#include "prpack_preprocessed_schur_graph.h" +#include "prpack_result.h" + +// TODO Make this a user configurable variable +#define PRPACK_SOLVER_MAX_ITERS 1000000 + +namespace prpack { + + // Solver class. + class prpack_solver { + private: + // instance variables + double read_time; + prpack_base_graph* bg; + prpack_preprocessed_ge_graph* geg; + prpack_preprocessed_gs_graph* gsg; + prpack_preprocessed_schur_graph* sg; + prpack_preprocessed_scc_graph* sccg; + bool owns_bg; + // methods + void initialize(); + static prpack_result* solve_via_ge( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* uv); + static prpack_result* solve_via_ge_uv( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* d, + const double* u, + const double* v); + static prpack_result* solve_via_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v); + static prpack_result* solve_via_gs_err( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* ii, + const double* num_outlinks, + const double* u, + const double* v); + static prpack_result* solve_via_schur_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int* encoding, + const int* decoding, + const bool should_normalize = true); + static prpack_result* solve_via_schur_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int* encoding, + const int* decoding); + static prpack_result* solve_via_scc_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding, + const bool should_normalize = true); + static prpack_result* solve_via_scc_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding); + static void ge(const int sz, double* A, double* b); + static void normalize(const int length, double* x); + static prpack_result* combine_uv( + const int num_vs, + const double* d, + const double* num_outlinks, + const int* encoding, + const double alpha, + const prpack_result* ret_u, + const prpack_result* ret_v); + public: + // constructors + prpack_solver(const prpack_csc* g); + prpack_solver(const prpack_int64_csc* g); + prpack_solver(const prpack_csr* g); + prpack_solver(const prpack_edge_list* g); + prpack_solver(prpack_base_graph* g, bool owns_bg=true); +#if 0 + prpack_solver(const char* filename, const char* format, const bool weighted); +#endif + // destructor + ~prpack_solver(); + // methods + int get_num_vs(); + prpack_result* solve(const double alpha, const double tol, const char* method); + prpack_result* solve( + const double alpha, + const double tol, + const double* u, + const double* v, + const char* method); + }; + +} + +#endif diff --git a/src/centrality/prpack/prpack_utils.cpp b/src/centrality/prpack/prpack_utils.cpp new file mode 100644 index 0000000..c23eaf8 --- /dev/null +++ b/src/centrality/prpack/prpack_utils.cpp @@ -0,0 +1,58 @@ +/** + * @file prpack_utils.cpp + * An assortment of utility functions for reporting errors, checking time, + * and working with vectors. + */ + +#include "prpack_utils.h" + +#ifdef PRPACK_IGRAPH_SUPPORT +#include "igraph_error.h" +#else +#include +#endif + +#include +using namespace prpack; +using namespace std; + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#endif +double prpack_utils::get_time() { + LARGE_INTEGER t, freq; + QueryPerformanceCounter(&t); + QueryPerformanceFrequency(&freq); + return double(t.QuadPart)/double(freq.QuadPart); +} +#else +#include +#include +double prpack_utils::get_time() { + struct timeval t; + gettimeofday(&t, NULL); + return (t.tv_sec*1.0 + t.tv_usec/1000000.0); +} +#endif + +// Fails and outputs 'msg' if 'condition' is false. +void prpack_utils::validate(const bool condition, const string& msg) { + if (!condition) { +#ifdef PRPACK_IGRAPH_SUPPORT + IGRAPH_FATALF("Internal error in PRPACK: %s", msg.c_str()); +#else + cerr << msg << endl; + exit(-1); +#endif + } +} + +// Permute a vector. +double* prpack_utils::permute(const int length, const double* a, const int* coding) { + double* ret = new double[length]; + for (int i = 0; i < length; ++i) + ret[coding[i]] = a[i]; + return ret; +} diff --git a/src/centrality/prpack/prpack_utils.h b/src/centrality/prpack/prpack_utils.h new file mode 100644 index 0000000..261370d --- /dev/null +++ b/src/centrality/prpack/prpack_utils.h @@ -0,0 +1,33 @@ +#ifndef PRPACK_UTILS +#define PRPACK_UTILS +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#endif +#include + +// Computes the time taken to do X and stores it in T. +#define TIME(T, X) \ + (T) = prpack_utils::get_time(); \ + (X); \ + (T) = prpack_utils::get_time() - (T) + +// Computes S += A using C as a carry-over. +// This is a macro over a function as it is faster this way. +#define COMPENSATED_SUM(S, A, C) \ + double compensated_sum_y = (A) - (C); \ + double compensated_sum_t = (S) + compensated_sum_y; \ + (C) = compensated_sum_t - (S) - compensated_sum_y; \ + (S) = compensated_sum_t + +namespace prpack { + + class prpack_utils { + public: + static double get_time(); + static void validate(const bool condition, const std::string& msg); + static double* permute(const int length, const double* a, const int* coding); + }; + +} + +#endif diff --git a/src/centrality/prpack_internal.h b/src/centrality/prpack_internal.h new file mode 100644 index 0000000..28b4b52 --- /dev/null +++ b/src/centrality/prpack_internal.h @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_PRPACK_H +#define IGRAPH_PRPACK_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +#include "igraph_interface.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_personalized_pagerank_prpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + const igraph_vector_t *reset, + const igraph_vector_t *weights); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/centrality/truss.cpp b/src/centrality/truss.cpp new file mode 100644 index 0000000..13e1024 --- /dev/null +++ b/src/centrality/truss.cpp @@ -0,0 +1,286 @@ +/* + Copyright 2017 The Johns Hopkins University Applied Physics Laboratory LLC. All Rights Reserved. + Copyright 2021 The igraph team. + + Truss algorithm for cohesive subgroups. + + Author: Alex Perrone + Date: 2017-08-03 + Minor edits: The igraph team, 2021 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_motifs.h" +#include "igraph_structural.h" + +#include "core/exceptions.h" +#include "core/interruption.h" + +#include +#include + +using std::vector; +using std::unordered_set; + + +// Unpack the triangles as a vector of vertices to be a vector of edges. +// So, instead of the triangle specified as vertices [1, 2, 3], return the +// edges as [1, 2, 1, 3, 2, 3] so that the support can be computed. +static igraph_error_t igraph_truss_i_unpack(const igraph_vector_int_t *tri, igraph_vector_int_t *unpacked_tri) { + igraph_int_t num_triangles = igraph_vector_int_size(tri); + + IGRAPH_CHECK(igraph_vector_int_resize(unpacked_tri, 2 * num_triangles)); + + for (igraph_int_t i = 0, j = 0; i < num_triangles; i += 3, j += 6) { + VECTOR(*unpacked_tri)[j] = VECTOR(*unpacked_tri)[j+2] = VECTOR(*tri)[i]; + VECTOR(*unpacked_tri)[j+1] = VECTOR(*unpacked_tri)[j+4] = VECTOR(*tri)[i+1]; + VECTOR(*unpacked_tri)[j+3] = VECTOR(*unpacked_tri)[j+5] = VECTOR(*tri)[i+2]; + } + + return IGRAPH_SUCCESS; +} + + +// Compute the edge support, i.e. number of triangles each edge occurs in. +// Time complexity: O(m), where m is the number of edges listed in eid. +static void igraph_truss_i_compute_support(const igraph_vector_int_t *eid, igraph_vector_int_t *support) { + igraph_int_t m = igraph_vector_int_size(eid); + for (igraph_int_t i = 0; i < m; ++i) { + VECTOR(*support)[VECTOR(*eid)[i]] += 1; + } +} + + +/* internal function doing the computations once the support is defined */ +static igraph_error_t igraph_i_trussness(const igraph_t *graph, igraph_vector_int_t *support, + igraph_vector_int_t *trussness) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_adjlist_t adjlist; + igraph_vector_int_t commonNeighbors; + igraph_vector_bool_t completed; + + // C++ data structures + vector< unordered_set > vec; + + // Allocate memory for result + igraph_int_t no_of_edges = igraph_vector_int_size(support); + IGRAPH_CHECK(igraph_vector_int_resize(trussness, no_of_edges)); + if (no_of_edges == 0) { + return IGRAPH_SUCCESS; + } + + // Get max possible value = max entry in support. + // This cannot be computed if there are no edges, hence the above check + igraph_int_t max = igraph_vector_int_max(support); + + // Initialize completed edges. + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&completed, no_of_edges); + + // The vector of levels. Each level of the vector is a set of edges initially + // at that level of support, where support is # of triangles the edge is in. + vec.resize(max + 1); + + // Add each edge to its appropriate level of support. + for (igraph_int_t i = 0; i < no_of_edges; ++i) { + vec[VECTOR(*support)[i]].insert(i); // insert edge i into its support level + } + + // Record the trussness of edges at level 0. These edges are not part + // of any triangles, so there's not much to do and we "complete" them + for (auto edge : vec[0]) { + VECTOR(*trussness)[edge] = 2; + VECTOR(completed)[edge] = true; + } + + // Initialize variables needed below. + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_VECTOR_INT_INIT_FINALLY(&commonNeighbors, 0); + + // Move through the levels, one level at a time, starting at first level. + for (igraph_int_t level = 1; level <= max; ++level) { + + /* Track down edges one at a time */ + while (!vec[level].empty()) { + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_int_t seed = *vec[level].begin(); // pull out the first edge + vec[level].erase(seed); // remove the first element + + /* Find the vertices of this edge */ + igraph_int_t fromVertex = IGRAPH_FROM(graph, seed); + igraph_int_t toVertex = IGRAPH_TO(graph, seed); + + /* Find neighbors of both vertices. If they run into each other, + * there is a triangle. We rely on the neighbor lists being sorted, + * as guaranteed by igraph_adjlist_init(), when computing intersections. */ + igraph_vector_int_t *fromNeighbors = igraph_adjlist_get(&adjlist, fromVertex); + igraph_vector_int_t *toNeighbors = igraph_adjlist_get(&adjlist, toVertex); + igraph_vector_int_t *q1 = fromNeighbors; + igraph_vector_int_t *q2 = toNeighbors; + + if (igraph_vector_int_size(q1) > igraph_vector_int_size(q2)) { + // case: #fromNeighbors > #toNeigbors, so make q1 the smaller set. + q1 = toNeighbors; + q2 = fromNeighbors; + } + + // Intersect the neighbors. + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(q1, q2, &commonNeighbors)); + + /* Go over the overlapping neighbors and check each */ + igraph_int_t ncommon = igraph_vector_int_size(&commonNeighbors); + for (igraph_int_t j = 0; j < ncommon; j++) { + igraph_int_t n = VECTOR(commonNeighbors)[j]; // the common neighbor + igraph_int_t e1, e2; + IGRAPH_CHECK(igraph_get_eid(graph, &e1, fromVertex, n, IGRAPH_UNDIRECTED, /* error= */ true)); + IGRAPH_CHECK(igraph_get_eid(graph, &e2, toVertex, n, IGRAPH_UNDIRECTED, /* error= */ true)); + + bool e1_complete = VECTOR(completed)[e1]; + bool e2_complete = VECTOR(completed)[e2]; + + if (!e1_complete && !e2_complete) { + igraph_int_t newLevel; + + // Demote this edge, if higher than current level. + if (VECTOR(*support)[e1] > level) { + VECTOR(*support)[e1] -= 1; // decrement the level + newLevel = VECTOR(*support)[e1]; + vec[newLevel].insert(e1); + vec[newLevel + 1].erase(e1); // the old level + } + // Demote this edge, if higher than current level. + if (VECTOR(*support)[e2] > level) { + VECTOR(*support)[e2] -= 1; // decrement the level + newLevel = VECTOR(*support)[e2]; + vec[newLevel].insert(e2); + vec[newLevel + 1].erase(e2); // the old level + } + } + } + // Record this edge; its level is its trussness. + VECTOR(*trussness)[seed] = level + 2; + VECTOR(completed)[seed] = true; // mark as complete + igraph_vector_int_clear(&commonNeighbors); + } // end while + } // end for-loop over levels + + // Clean up. + igraph_vector_int_destroy(&commonNeighbors); + igraph_adjlist_destroy(&adjlist); + igraph_vector_bool_destroy(&completed); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; + + IGRAPH_HANDLE_EXCEPTIONS_END; +} + + +/** + * \function igraph_trussness + * \brief Finding the "trussness" of the edges in a network. + * + * A k-truss is a subgraph in which every edge occurs in at least k-2 triangles + * in the subgraph. The trussness of an edge indicates the highest k-truss that + * the edge occurs in. + * + * + * This function returns the highest \c k for each edge. If you are interested in + * a particular k-truss subgraph, you can subset the graph to those edges + * which are >= k because each k-truss is a subgraph of a (k–1)-truss + * Thus, to get all 4-trusses, take k >= 4 because the 5-trusses, 6-trusses, + * etc. need to be included. + * + * + * The current implementation of this function iteratively decrements support + * of each edge using O(|E|) space and O(|E|^1.5) time. The implementation does + * not support multigraphs; use \ref igraph_simplify() to collapse edges before + * calling this function. + * + * + * Reference: + * + * + * See Algorithm 2 in: + * Wang, Jia, and James Cheng. "Truss decomposition in massive networks." + * Proceedings of the VLDB Endowment 5.9 (2012): 812-823. + * https://doi.org/10.14778/2311906.2311909 + * + * \param graph The input graph. Loop edges are allowed; multigraphs are not. + * \param truss Pointer to initialized vector of truss values that will + * indicate the highest k-truss each edge occurs in. It will be resized as + * needed. + * \return Error code. + * + * Time complexity: It should be O(|E|^1.5) according to the reference. + */ +igraph_error_t igraph_trussness(const igraph_t* graph, igraph_vector_int_t* trussness) { + igraph_vector_int_t triangles, support, unpacked_triangles, eid; + igraph_bool_t is_multigraph; + + /* Check whether the graph is a multigraph; trussness will not work for these */ + IGRAPH_CHECK(igraph_has_multiple(graph, &is_multigraph)); + if (! is_multigraph && igraph_is_directed(graph)) { + /* Directed graphs with mutual edges are effectively multigraphs + * when edge directions are ignored. */ + IGRAPH_CHECK(igraph_has_mutual(graph, &is_multigraph, /* loops */ false)); + } + if (is_multigraph) { + IGRAPH_ERROR("Trussness is not implemented for graphs with multi-edges.", IGRAPH_UNIMPLEMENTED); + } + + /* Manage the stack to make it memory safe: do not change the order of + * initialization of the following four vectors */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&support, igraph_ecount(graph)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eid, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&unpacked_triangles, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&triangles, 0); + + // List the triangles as vertex triplets. + IGRAPH_CHECK(igraph_list_triangles(graph, &triangles)); + + // Unpack the triangles from vertex list to edge list. + IGRAPH_CHECK(igraph_truss_i_unpack(&triangles, &unpacked_triangles)); + igraph_vector_int_destroy(&triangles); + IGRAPH_FINALLY_CLEAN(1); + + // Get the edge IDs of the unpacked triangles. Note: a given eid can occur + // multiple times in this list if it is in multiple triangles. + IGRAPH_CHECK(igraph_get_eids(graph, &eid, &unpacked_triangles, /* directed = */ false, /* error = */ true)); + igraph_vector_int_destroy(&unpacked_triangles); + IGRAPH_FINALLY_CLEAN(1); + + // Compute the support of the edges. + igraph_truss_i_compute_support(&eid, &support); + igraph_vector_int_destroy(&eid); + IGRAPH_FINALLY_CLEAN(1); + + // Compute the trussness of the edges. + IGRAPH_CHECK(igraph_i_trussness(graph, &support, trussness)); + igraph_vector_int_destroy(&support); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/cliques/cliquer/CMakeLists.txt b/src/cliques/cliquer/CMakeLists.txt new file mode 100644 index 0000000..7926f4e --- /dev/null +++ b/src/cliques/cliquer/CMakeLists.txt @@ -0,0 +1,27 @@ +# Declare the files needed to compile Cliquer +add_library( + cliquer + OBJECT + EXCLUDE_FROM_ALL + cliquer.c + cliquer_graph.c + reorder.c +) + +target_include_directories( + cliquer + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${PROJECT_BINARY_DIR}/src +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET cliquer PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Since these are included as object files, they should call the +# function as is (without visibility specification) +target_compile_definitions(cliquer PRIVATE IGRAPH_STATIC) + +use_all_warnings(cliquer) diff --git a/src/cliques/cliquer/README b/src/cliques/cliquer/README new file mode 100644 index 0000000..e238708 --- /dev/null +++ b/src/cliques/cliquer/README @@ -0,0 +1,61 @@ + +Cliquer - routines for clique searching +--------------------------------------- + + +Cliquer is a set of C routines for finding cliques in an arbitrary +weighted graph. It uses an exact branch-and-bound algorithm recently +developed by Patric Ostergard. It is designed with the aim of being +efficient while still being flexible and easy to use. + +Cliquer was developed on Linux, and it should compile without +modification on most modern UNIX systems. Other operating systems may +require minor changes to the source code. + +Features: + + * support for both weighted and unweighted graphs (faster routines + for unweighted graphs) + * search for maximum clique / maximum-weight clique + * search for clique with size / weight within a given range + * restrict search to maximal cliques + * store found cliques in memory + * call a user-defined function for every clique found + * Cliquer is re-entrant, so you can use the clique-searching + functions from within the callback function + +The full documentation can be obtained via the www page of +Cliquer . + + +License + +Cliquer is Copyright (C) 2002 Sampo Niskanen, Patric Ostergard. + +Cliquer is licensed under the GNU General Public License as published +by the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. The full license is included in +the file LICENSE. + +Basically, you can use Cliquer for any purpose, provided that any +programs or modifications you make and distribute are also licensed +under the GNU GPL. + +ABSOLUTELY NO GUARANTEES OR WARRANTIES are made concerning the +suitability, correctness, or any other aspect of these routines. + + +Contact + +Cliquer was mainly written by Sampo Niskanen . + +For bug-fixes, feedback, and, in particular, for putting your +name on the mailing list for important information regarding Cliquer, +please contact: + +Patric Ostergard +Department of Communications and Networking +Aalto University +P.O. Box 13000, 00076 Aalto +FINLAND + diff --git a/src/cliques/cliquer/cliquer.c b/src/cliques/cliquer/cliquer.c new file mode 100644 index 0000000..dd7cb92 --- /dev/null +++ b/src/cliques/cliquer/cliquer.c @@ -0,0 +1,1868 @@ + +/* + * This file contains the clique searching routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#include +#include +#include +/* +#include +#include +#include +*/ + +#include "cliquer.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +/* Default cliquer options */ +IGRAPH_THREAD_LOCAL clique_options clique_default_options = { + reorder_by_default, NULL, /*clique_print_time*/ NULL, NULL, NULL, NULL, NULL, 0 +}; + + +/* Calculate d/q, rounding result upwards/downwards. */ +#define DIV_UP(d,q) (((d)+(q)-1)/(q)) +#define DIV_DOWN(d,q) ((int)((d)/(q))) + + +/* Global variables used: */ +/* These must be saved and restored in re-entrance. */ +static IGRAPH_THREAD_LOCAL int *clique_size; /* c[i] == max. clique size in {0,1,...,i-1} */ +static IGRAPH_THREAD_LOCAL set_t current_clique; /* Current clique being searched. */ +static IGRAPH_THREAD_LOCAL set_t best_clique; /* Largest/heaviest clique found so far. */ +/*static struct tms cputimer;*/ /* Timer for opts->time_function() */ +/*static struct timeval realtimer;*/ /* Timer for opts->time_function() */ +static IGRAPH_THREAD_LOCAL int clique_list_count=0; /* No. of cliques in opts->clique_list[] */ +static IGRAPH_THREAD_LOCAL int weight_multiplier=1; /* Weights multiplied by this when passing + * to time_function(). */ + +/* List cache (contains memory blocks of size g->n * sizeof(int)) */ +static IGRAPH_THREAD_LOCAL int **temp_list=NULL; +static IGRAPH_THREAD_LOCAL int temp_count=0; + + +/* + * Macros for re-entrance. ENTRANCE_SAVE() must be called immediately + * after variable definitions, ENTRANCE_RESTORE() restores global + * variables to original values. entrance_level should be increased + * and decreased accordingly. + */ +static IGRAPH_THREAD_LOCAL int entrance_level=0; /* How many levels for entrance have occurred? */ + +#define ENTRANCE_SAVE() \ +int *old_clique_size = clique_size; \ +set_t old_current_clique = current_clique; \ +set_t old_best_clique = best_clique; \ +int old_clique_list_count = clique_list_count; \ +int old_weight_multiplier = weight_multiplier; \ +int **old_temp_list = temp_list; \ +int old_temp_count = temp_count; \ +/*struct tms old_cputimer; \ +struct timeval old_realtimer; \ +memcpy(&old_cputimer,&cputimer,sizeof(struct tms)); \ +memcpy(&old_realtimer,&realtimer,sizeof(struct timeval));*/ + +#define ENTRANCE_RESTORE() \ +clique_size = old_clique_size; \ +current_clique = old_current_clique; \ +best_clique = old_best_clique; \ +clique_list_count = old_clique_list_count; \ +weight_multiplier = old_weight_multiplier; \ +temp_list = old_temp_list; \ +temp_count = old_temp_count; \ +/*memcpy(&cputimer,&old_cputimer,sizeof(struct tms)); \ +memcpy(&realtimer,&old_realtimer,sizeof(struct timeval));*/ + + +/* Number of clock ticks per second (as returned by sysconf(_SC_CLK_TCK)) */ +/*static int clocks_per_sec=0;*/ + + + + +/* Recursion and helper functions */ +static boolean sub_unweighted_single(int *table, int size, int min_size, + graph_t *g); +static igraph_error_t sub_unweighted_all(int *table, int size, int min_size, int max_size, + boolean maximal, graph_t *g, + clique_options *opts, CLIQUER_LARGE_INT *num_found); +static igraph_error_t sub_weighted_all(int *table, int size, int weight, + int current_weight, int prune_low, int prune_high, + int min_weight, int max_weight, boolean maximal, + graph_t *g, clique_options *opts, int *weight_found); + + +static igraph_error_t store_clique(set_t clique, graph_t *g, clique_options *opts); +static boolean is_maximal(set_t clique, graph_t *g); +static igraph_error_t false_function(set_t clique,graph_t *g,clique_options *opts); + + + + + +/***** Unweighted searches *****/ +/* + * Unweighted searches are done separately from weighted searches because + * some effective pruning methods can be used when the vertex weights + * are all 1. Single and all clique finding routines are separated, + * because the single clique finding routine can store the found clique + * while it is returning from the recursion, thus requiring no implicit + * storing of the current clique. When searching for all cliques the + * current clique must be stored. + */ + + +/* + * unweighted_clique_search_single() + * + * Searches for a single clique of size min_size. Stores maximum clique + * sizes into clique_size[]. + * + * table - the order of the vertices in g to use + * min_size - minimum size of clique to search for. If min_size==0, + * searches for a maximum clique. + * g - the graph + * opts - time printing options + * + * opts->time_function is called after each base-level recursion, if + * non-NULL. + * + * Returns the size of the clique found, or 0 if min_size>0 and a clique + * of that size was not found (or if time_function aborted the search). + * The largest clique found is stored in current_clique. + * + * Note: Does NOT use opts->user_function of opts->clique_list. + */ +static int unweighted_clique_search_single(int *table, int min_size, + graph_t *g, clique_options *opts) { + /* + struct tms tms; + struct timeval timeval; + */ + int i,j; + int v,w; + int *newtable; + int newsize; + + v=table[0]; + clique_size[v]=1; + set_empty(current_clique); + SET_ADD_ELEMENT(current_clique,v); + if (min_size==1) + return 1; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + for (i=1; i < g->n; i++) { + w=v; + v=table[i]; + + newsize=0; + for (j=0; jtime_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + temp_list[temp_count++]=newtable; + return 0; + } + } + */ + + if (min_size) { + if (clique_size[v]>=min_size) { + temp_list[temp_count++]=newtable; + return clique_size[v]; + } + if (clique_size[v]+g->n-i-1 < min_size) { + temp_list[temp_count++]=newtable; + return 0; + } + } + } + + temp_list[temp_count++]=newtable; + + if (min_size) + return 0; + return clique_size[v]; +} + +/* + * sub_unweighted_single() + * + * Recursion function for searching for a single clique of size min_size. + * + * table - subset of the vertices in graph + * size - size of table + * min_size - size of clique to look for within the subgraph + * (decreased with every recursion) + * g - the graph + * + * Returns TRUE if a clique of size min_size is found, FALSE otherwise. + * If a clique of size min_size is found, it is stored in current_clique. + * + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + */ +static boolean sub_unweighted_single(int *table, int size, int min_size, + graph_t *g) { + int i; + int v; + int *newtable; + int *p1, *p2; + + /* Zero or one vertices needed anymore. */ + if (min_size <= 1) { + if (size>0 && min_size==1) { + set_empty(current_clique); + SET_ADD_ELEMENT(current_clique,table[0]); + return TRUE; + } + if (min_size==0) { + set_empty(current_clique); + return TRUE; + } + return FALSE; + } + if (size < min_size) + return FALSE; + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = size-1; i >= 0; i--) { + v = table[i]; + + if (clique_size[v] < min_size) + break; + /* This is faster when compiling with gcc than placing + * this in the for-loop condition. */ + if (i+1 < min_size) + break; + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + for (p2=table; p2 < table+i; p2++) { + int w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + p1++; + } + } + + /* Avoid unnecessary loops (next size == p1-newtable) */ + if (p1-newtable < min_size-1) + continue; + /* Now p1-newtable >= min_size-1 >= 2-1 == 1, so we can use + * p1-newtable-1 safely. */ + if (clique_size[newtable[p1-newtable-1]] < min_size-1) + continue; + + if (sub_unweighted_single(newtable,p1-newtable, + min_size-1,g)) { + /* Clique found. */ + SET_ADD_ELEMENT(current_clique,v); + temp_list[temp_count++]=newtable; + return TRUE; + } + } + temp_list[temp_count++]=newtable; + return FALSE; +} + + +/* + * unweighted_clique_search_all() + * + * Searches for all cliques with size at least min_size and at most + * max_size. Stores the cliques as opts declares. + * + * table - the order of the vertices in g to search + * start - first index where the subgraph table[0], ..., table[start] + * might include a requested kind of clique + * min_size - minimum size of clique to search for. min_size > 0 ! + * max_size - maximum size of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * maximal - requires cliques to be maximal + * g - the graph + * opts - time printing and clique storage options + * num_found - number of cliques found + * + * Cliques found are stored as defined by opts->user_function and + * opts->clique_list. opts->time_function is called after each + * base-level recursion, if non-NULL. + * + * clique_size[] must be defined and correct for all values of + * table[0], ..., table[start-1]. + */ +static igraph_error_t unweighted_clique_search_all( + int *table, int start, int min_size, int max_size, boolean maximal, + graph_t *g, clique_options *opts, CLIQUER_LARGE_INT *num_found +) { + /* + struct timeval timeval; + struct tms tms; + */ + int i, j; + int v; + int *newtable; + int newsize; + CLIQUER_LARGE_INT r; + CLIQUER_LARGE_INT count=0; + igraph_error_t retval = IGRAPH_SUCCESS; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + clique_list_count=0; + set_empty(current_clique); + for (i=start; i < g->n; i++) { + v=table[i]; + clique_size[v]=min_size; /* Do not prune here. */ + + newsize=0; + for (j=0; jtime_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,min_size * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + /* Abort. */ + break; + } + } +#endif + } + temp_list[temp_count++]=newtable; + + if (num_found) { + *num_found = count; + } + + return retval; +} + +/* + * sub_unweighted_all() + * + * Recursion function for searching for all cliques of given size. + * + * table - subset of vertices of graph g + * size - size of table + * min_size - minimum size of cliques to search for (decreased with + * every recursion) + * max_size - maximum size of cliques to search for (decreased with + * every recursion). If no upper limit is desired, use + * eg. INT_MAX + * maximal - require cliques to be maximal (passed through) + * g - the graph + * opts - storage options + * num_found - number of cliques found + * + * All cliques of suitable size found are stored according to opts. + * + * Returns the number of cliques found. If user_function returns FALSE, + * then the number of cliques is returned negative. + * + * Uses current_clique to store the currently-being-searched clique. + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + */ +static igraph_error_t sub_unweighted_all(int *table, int size, int min_size, int max_size, + boolean maximal, graph_t *g, + clique_options *opts, CLIQUER_LARGE_INT *num_found) { + igraph_error_t retval = IGRAPH_SUCCESS; + int i; + int v; + int *newtable; + int *p1, *p2; + CLIQUER_LARGE_INT n; + CLIQUER_LARGE_INT count=0; /* Amount of cliques found */ + + if (min_size <= 0) { + if ((!maximal) || is_maximal(current_clique,g)) { + /* We've found one. Store it. */ + count++; + retval = store_clique(current_clique, g, opts); + if (retval != IGRAPH_SUCCESS) { + *num_found = count; + return retval; + } + } + if (max_size <= 0) { + /* If we add another element, size will be too big. */ + *num_found = count; + return IGRAPH_SUCCESS; + } + } + + if (size < min_size) { + *num_found = count; + return IGRAPH_SUCCESS; + } + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i=size-1; i>=0; i--) { + v = table[i]; + if (clique_size[v] < min_size) { + break; + } + if (i+1 < min_size) { + break; + } + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + for (p2=table; p2 < table+i; p2++) { + int w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + p1++; + } + } + + /* Avoid unnecessary loops (next size == p1-newtable) */ + if (p1-newtable < min_size-1) { + continue; + } + + SET_ADD_ELEMENT(current_clique,v); + retval = sub_unweighted_all(newtable,p1-newtable, + min_size-1,max_size-1,maximal,g,opts,&n); + SET_DEL_ELEMENT(current_clique,v); + count += n; + if (retval != IGRAPH_SUCCESS || n < 0) { + /* This is from a recursive call to sub_unweighted_all(), + * pass through IGRAPH_STOP unaltered. */ + break; + } + } + temp_list[temp_count++]=newtable; + + *num_found = count; + return retval; +} + + + + +/***** Weighted clique searches *****/ +/* + * Weighted clique searches can use the same recursive routine, because + * in both cases (single/all) they have to search through all potential + * permutations searching for heavier cliques. + */ + + +/* + * weighted_clique_search_single() + * + * Searches for a single clique of weight at least min_weight, and at + * most max_weight. Stores maximum clique sizes into clique_size[] + * (or min_weight-1, whichever is smaller). + * + * table - the order of the vertices in g to use + * min_weight - minimum weight of clique to search for. If min_weight==0, + * then searches for a maximum weight clique + * max_weight - maximum weight of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * g - the graph + * opts - time printing options + * + * opts->time_function is called after each base-level recursion, if + * non-NULL. + * + * Returns 0 if a clique of requested weight was not found (also if + * time_function requested an abort), otherwise returns >= 1. + * If min_weight==0 (search for maximum-weight clique), then the return + * value is the weight of the clique found. The found clique is stored + * in best_clique. + * + * Note: Does NOT use opts->user_function of opts->clique_list. + */ +static igraph_error_t weighted_clique_search_single(int *table, int min_weight, + int max_weight, graph_t *g, + clique_options *opts, int *result) { + /* + struct timeval timeval; + struct tms tms; + */ + int i,j; + int v; + int *newtable; + int newsize; + int newweight; + int search_weight; + int min_w; + clique_options localopts; + igraph_error_t retval = IGRAPH_SUCCESS; + + ASSERT(result != NULL); + + if (min_weight==0) + min_w=INT_MAX; + else + min_w=min_weight; + + + if (min_weight==1) { + /* min_weight==1 may cause trouble in the routine, and + * it's trivial to check as it's own case. + * We write nothing to clique_size[]. */ + for (i=0; i < g->n; i++) { + if (g->weights[table[i]] <= max_weight) { + set_empty(best_clique); + SET_ADD_ELEMENT(best_clique,table[i]); + *result = g->weights[table[i]]; + return IGRAPH_SUCCESS; + } + } + + *result = 0; + return IGRAPH_SUCCESS; + } + + localopts.time_function=NULL; + localopts.reorder_function=NULL; + localopts.reorder_map=NULL; + localopts.user_function=false_function; + localopts.user_data=NULL; + localopts.clique_list=&best_clique; + localopts.clique_list_length=1; + clique_list_count=0; + + v=table[0]; + set_empty(best_clique); + SET_ADD_ELEMENT(best_clique,v); + search_weight=g->weights[v]; + if (min_weight && (search_weight >= min_weight)) { + if (search_weight <= max_weight) { + /* Found suitable clique. */ + *result = search_weight; + return IGRAPH_SUCCESS; + } + search_weight=min_weight-1; + } + clique_size[v]=search_weight; + set_empty(current_clique); + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = 1; i < g->n; i++) { + v=table[i]; + + newsize=0; + newweight=0; + for (j=0; jweights[table[j]]; + newtable[newsize]=table[j]; + newsize++; + } + } + + + SET_ADD_ELEMENT(current_clique,v); + retval=sub_weighted_all(newtable,newsize,newweight, + g->weights[v],search_weight, + clique_size[table[i-1]] + + g->weights[v], + min_w,max_weight,FALSE, + g,&localopts, &search_weight); + SET_DEL_ELEMENT(current_clique,v); + if (retval != IGRAPH_SUCCESS || search_weight < 0) { + /* This is from a top-level call to sub_weighted_all(), + * transform IGRAPH_STOP to success. */ + if (retval == IGRAPH_STOP) { + retval = IGRAPH_SUCCESS; + } + break; + } + + clique_size[v]=search_weight; + + /* + if (opts->time_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + set_free(current_clique); + current_clique=NULL; + break; + } + } + */ + } + temp_list[temp_count++]=newtable; + if (min_weight && (search_weight > 0)) { + /* Requested clique has not been found. */ + *result = 0; + } else { + *result = clique_size[table[i-1]]; + } + + return retval; +} + + +/* + * weighted_clique_search_all() + * + * Searches for all cliques with weight at least min_weight and at most + * max_weight. Stores the cliques as opts declares. + * + * table - the order of the vertices in g to search + * start - first index where the subgraph table[0], ..., table[start] + * might include a requested kind of clique + * min_weight - minimum weight of clique to search for. min_weight > 0 ! + * max_weight - maximum weight of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * maximal - search only for maximal cliques + * g - the graph + * opts - time printing and clique storage options + * num_found - number of cliques found + * + * Cliques found are stored as defined by opts->user_function and + * opts->clique_list. opts->time_function is called after each + * base-level recursion, if non-NULL. + * + * clique_size[] must be defined and correct for all values of + * table[0], ..., table[start-1]. + * + * Returns the number of cliques stored (not necessarily number of cliques + * in graph, if user/time_function aborts). + */ +static igraph_error_t weighted_clique_search_all(int *table, int start, + int min_weight, int max_weight, + boolean maximal, graph_t *g, + clique_options *opts, int* num_found) { + /* + struct timeval timeval; + struct tms tms; + */ + int i,j; + int v; + int *newtable; + int newsize; + int newweight; + igraph_error_t retval = IGRAPH_SUCCESS; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + clique_list_count=0; + set_empty(current_clique); + for (i=start; i < g->n; i++) { + v=table[i]; + clique_size[v]=min_weight; /* Do not prune here. */ + + newsize=0; + newweight=0; + for (j=0; jweights[table[j]]; + newsize++; + } + } + + SET_ADD_ELEMENT(current_clique,v); + retval=sub_weighted_all(newtable,newsize,newweight, + g->weights[v],min_weight-1,INT_MAX, + min_weight,max_weight,maximal,g,opts,&j); + SET_DEL_ELEMENT(current_clique,v); + + if (retval != IGRAPH_SUCCESS || j < 0) { + /* Abort. */ + /* This is from a top-level call to sub_weighted_all(), + * transform IGRAPH_STOP to success. */ + if (retval == IGRAPH_STOP) { + retval = IGRAPH_SUCCESS; + } + break; + } + + /* + if (opts->time_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + set_free(current_clique); + current_clique=NULL; + break; + } + } + */ + } + temp_list[temp_count++]=newtable; + + if (num_found) { + *num_found = clique_list_count; + } + + return retval; +} + +/* + * sub_weighted_all() + * + * Recursion function for searching for all cliques of given weight. + * + * table - subset of vertices of graph g + * size - size of table + * weight - total weight of vertices in table + * current_weight - weight of clique found so far + * prune_low - ignore all cliques with weight less or equal to this value + * (often heaviest clique found so far) (passed through) + * prune_high - maximum weight possible for clique in this subgraph + * (passed through) + * min_size - minimum weight of cliques to search for (passed through) + * Must be greater than 0. + * max_size - maximum weight of cliques to search for (passed through) + * If no upper limit is desired, use eg. INT_MAX + * maximal - search only for maximal cliques + * g - the graph + * opts - storage options + * weight_found - weight of the heaviest clique found (prune_low if a heavier + * clique hasn't been found); if a clique with weight at least + * min_size is found then min_size-1 is returned. + * + * All cliques of suitable weight found are stored according to opts. + * + * The largest clique found smaller than max_weight is stored in + * best_clique, if non-NULL. + * + * Uses current_clique to store the currently-being-searched clique. + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + * + * To search for a single maximum clique, use min_weight==max_weight==INT_MAX, + * with best_clique non-NULL. To search for a single given-weight clique, + * use opts->clique_list and opts->user_function=false_function. When + * searching for all cliques, min_weight should be given the minimum weight + * desired. + */ +static igraph_error_t sub_weighted_all(int *table, int size, int weight, + int current_weight, int prune_low, int prune_high, + int min_weight, int max_weight, boolean maximal, + graph_t *g, clique_options *opts, int* weight_found) { + igraph_error_t retval = IGRAPH_SUCCESS; + int i; + int v,w; + int *newtable; + int *p1, *p2; + int newweight; + + if (current_weight >= min_weight) { + if ((current_weight <= max_weight) && + ((!maximal) || is_maximal(current_clique,g))) { + /* We've found one. Store it. */ + retval = store_clique(current_clique,g,opts); + if (retval) { + *weight_found = -1; + return retval; + } + } + if (current_weight >= max_weight) { + /* Clique too heavy. */ + *weight_found = min_weight-1; + return IGRAPH_SUCCESS; + } + } + if (size <= 0) { + /* current_weight < min_weight, prune_low < min_weight, + * so return value is always < min_weight. */ + if (current_weight>prune_low) { + if (best_clique) { + best_clique = set_copy(best_clique,current_clique); + } + if (current_weight < min_weight) { + *weight_found = current_weight; + return IGRAPH_SUCCESS; + } else { + *weight_found = min_weight-1; + return IGRAPH_SUCCESS; + } + } else { + *weight_found = prune_low; + return IGRAPH_SUCCESS; + } + } + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = size-1; i >= 0; i--) { + v = table[i]; + if (current_weight+clique_size[v] <= prune_low) { + /* Dealing with subset without heavy enough clique. */ + break; + } + if (current_weight+weight <= prune_low) { + /* Even if all elements are added, won't do. */ + break; + } + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + newweight = 0; + for (p2=table; p2 < table+i; p2++) { + w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + newweight += g->weights[w]; + p1++; + } + } + + w=g->weights[v]; + weight-=w; + /* Avoid a few unnecessary loops */ + if (current_weight+w+newweight <= prune_low) { + continue; + } + + SET_ADD_ELEMENT(current_clique,v); + retval=sub_weighted_all(newtable,p1-newtable, + newweight, + current_weight+w, + prune_low,prune_high, + min_weight,max_weight,maximal, + g,opts, &prune_low); + SET_DEL_ELEMENT(current_clique,v); + if (retval != IGRAPH_SUCCESS || (prune_low<0) || (prune_low>=prune_high)) { + /* Impossible to find larger clique. */ + /* This is from a recursive call to sub_weighted_all(), + * pass through IGRAPH_STOP unaltered. */ + break; + } + } + temp_list[temp_count++]=newtable; + + *weight_found = prune_low; + return IGRAPH_SUCCESS; +} + + + + +/***** Helper functions *****/ + + +/* + * store_clique() + * + * Stores a clique according to given user options. + * + * clique - the clique to store + * opts - storage options + * + * Returns the same igraph error code as the one returned by + * opts->user_function(). Returns IGRAPH_SUCCESS if no callback is defined. + */ +static igraph_error_t store_clique(set_t clique, graph_t *g, clique_options *opts) { + + clique_list_count++; + + /* clique_list[] */ + if (opts->clique_list) { + /* + * This has been a major source of bugs: + * Has clique_list_count been set to 0 before calling + * the recursions? + */ + if (clique_list_count <= 0) { + IGRAPH_FATAL("CLIQUER INTERNAL ERROR: clique_list_count has negative value! Please report as a bug."); + } + if (clique_list_count <= opts->clique_list_length) + opts->clique_list[clique_list_count-1] = + set_copy(opts->clique_list[clique_list_count-1], clique); + } + + /* user_function() */ + if (opts->user_function) { + return opts->user_function(clique, g, opts); + } + + return IGRAPH_SUCCESS; +} + +/* + * maximalize_clique() + * + * Adds greedily all possible vertices in g to set s to make it a maximal + * clique. + * + * s - clique of vertices to make maximal + * g - graph + * + * Note: Not very optimized (uses a simple O(n^2) routine), but is called + * at maximum once per clique_xxx() call, so it shouldn't matter. + */ +static void maximalize_clique(set_t s,graph_t *g) { + int i,j; + boolean add; + + for (i=0; i < g->n; i++) { + add=TRUE; + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(s,j) && !GRAPH_IS_EDGE(g,i,j)) { + add=FALSE; + break; + } + } + if (add) { + SET_ADD_ELEMENT(s,i); + } + } + return; +} + + +/* + * is_maximal() + * + * Check whether a clique is maximal or not. + * + * clique - set of vertices in clique + * g - graph + * + * Returns TRUE is clique is a maximal clique of g, otherwise FALSE. + */ +static boolean is_maximal(set_t clique, graph_t *g) { + int i,j; + int *table; + int len; + boolean addable; + + if (temp_count) { + temp_count--; + table=temp_list[temp_count]; + } else { + table=malloc(g->n * sizeof(int)); + } + + len=0; + for (i=0; i < g->n; i++) + if (SET_CONTAINS_FAST(clique,i)) + table[len++]=i; + + for (i=0; i < g->n; i++) { + addable=TRUE; + for (j=0; jtime_function() requests abort). + * + * The returned clique is newly allocated and can be freed by set_free(). + * + * Note: Does NOT use opts->user_function() or opts->clique_list[]. + */ +igraph_error_t clique_unweighted_find_single(graph_t *g,int min_size,int max_size, + boolean maximal, clique_options *opts, set_t *clique) { + int i; + int *table; + set_t s; + igraph_error_t retval = IGRAPH_SUCCESS; + CLIQUER_LARGE_INT found; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT(clique!=NULL); + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_size>=0); + ASSERT(max_size>=0); + ASSERT((max_size==0) || (min_size <= max_size)); + ASSERT(!((min_size==0) && (max_size>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_size>0) && (min_size>max_size)) { + /* state was not changed */ + entrance_level--; + *clique = NULL; + return IGRAPH_SUCCESS; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Dynamic allocation */ + current_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,FALSE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + if (unweighted_clique_search_single(table,min_size,g,opts) == 0) { + set_free(current_clique); + current_clique=NULL; + goto cleanreturn; + } + if (maximal && (min_size>0)) { + maximalize_clique(current_clique,g); + + if ((max_size > 0) && (set_size(current_clique) > max_size)) { + clique_options localopts; + + s = set_new(g->n); + localopts.time_function = opts->time_function; + localopts.output = opts->output; + localopts.user_function = false_function; + localopts.clique_list = &s; + localopts.clique_list_length = 1; + + for (i=0; i < g->n-1; i++) + if (clique_size[table[i]]>=min_size) + break; + retval = unweighted_clique_search_all( + table, i, min_size, max_size, maximal, g, &localopts, &found + ); + set_free(current_clique); + if (retval || !found) { + current_clique=NULL; + } else { + current_clique=s; + } + } + } + + cleanreturn: + *clique = current_clique; + + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + free(table); + free(clique_size); + + ENTRANCE_RESTORE(); + entrance_level--; + + return retval; +} + + +/* + * clique_unweighted_find_all() + * + * Find all cliques with size at least min_size and at most max_size. + * + * g - the graph + * min_size - minimum size of cliques to search for. If min_size==0, + * searches for maximum cliques. + * max_size - maximum size of cliques to search for. If max_size==0, no + * upper limit is used. If min_size==0, this must also be 0. + * maximal - require cliques to be maximal cliques + * opts - time printing and clique storage options + * num_found - the number of cliques found. This can be less than the number + * of cliques in the graph iff opts->time_function() returns + * FALSE (request abort) or opts->user_function() returns an + * igraph error code + * + * The cliques found are stored in opts->clique_list[] and + * opts->user_function() is called with them (if non-NULL). The cliques + * stored in opts->clique_list[] are newly allocated, and can be freed + * by set_free(). + */ +igraph_error_t clique_unweighted_find_all( + graph_t *g, int min_size, int max_size, boolean maximal, clique_options *opts, + CLIQUER_LARGE_INT *num_found +) { + int i; + int *table; + CLIQUER_LARGE_INT count; + igraph_error_t retval = IGRAPH_SUCCESS; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_size>=0); + ASSERT(max_size>=0); + ASSERT((max_size==0) || (min_size <= max_size)); + ASSERT(!((min_size==0) && (max_size>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_size>0) && (min_size>max_size)) { + /* state was not changed */ + entrance_level--; + if (num_found) { + *num_found = 0; + } + return IGRAPH_SUCCESS; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Dynamic allocation */ + current_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + clique_list_count=0; + memset(clique_size,0,g->n * sizeof(int)); + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,FALSE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + + /* Search as normal until there is a chance to find a suitable + * clique. */ + if (unweighted_clique_search_single(table,min_size,g,opts) == 0) { + count=0; + goto cleanreturn; + } + + if (min_size==0 && max_size==0) { + min_size=max_size=clique_size[table[g->n-1]]; + maximal=FALSE; /* No need to test, since we're searching + * for maximum cliques. */ + } + if (max_size==0) { + max_size=INT_MAX; + } + + for (i=0; i < g->n-1; i++) + if (clique_size[table[i]] >= min_size) + break; + + retval = unweighted_clique_search_all(table, i, min_size, max_size, maximal, g, opts, &count); + + cleanreturn: + /* Free resources */ + for (i=0; itime_function() requests abort). + * + * The returned clique is newly allocated and can be freed by set_free(). + * + * Note: Does NOT use opts->user_function() or opts->clique_list[]. + * Note: Automatically uses clique_unweighted_find_single if all vertex + * weights are the same. + */ +igraph_error_t clique_find_single( + graph_t *g, int min_weight, int max_weight, boolean maximal, + clique_options *opts, set_t *clique +) { + int i; + int *table; + set_t s; + igraph_error_t retval = IGRAPH_SUCCESS; + int weight_found; + int num_found; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT(clique!=NULL); + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_weight>=0); + ASSERT(max_weight>=0); + ASSERT((max_weight==0) || (min_weight <= max_weight)); + ASSERT(!((min_weight==0) && (max_weight>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_weight>0) && (min_weight>max_weight)) { + /* state was not changed */ + entrance_level--; + *clique = NULL; + return IGRAPH_SUCCESS; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Check whether we can use unweighted routines. */ + if (!graph_weighted(g)) { + min_weight=DIV_UP(min_weight,g->weights[0]); + if (max_weight) { + max_weight=DIV_DOWN(max_weight,g->weights[0]); + if (max_weight < min_weight) { + /* state was not changed */ + entrance_level--; + *clique = NULL; + return IGRAPH_SUCCESS; + } + } + + weight_multiplier = g->weights[0]; + entrance_level--; + retval = clique_unweighted_find_single(g, min_weight, max_weight, maximal, opts, &s); + ENTRANCE_RESTORE(); + *clique = s; + return retval; + } + + /* Dynamic allocation */ + current_clique=set_new(g->n); + best_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + memset(clique_size, 0, g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + clique_list_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,TRUE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + if (max_weight==0) + max_weight=INT_MAX; + + retval = weighted_clique_search_single(table, min_weight, max_weight, g, opts, &weight_found); + + if (retval || weight_found == 0) { + /* Requested clique has not been found. */ + set_free(best_clique); + best_clique=NULL; + goto cleanreturn; + } + + if (maximal && (min_weight>0)) { + maximalize_clique(best_clique,g); + if (graph_subgraph_weight(g,best_clique) > max_weight) { + clique_options localopts; + + localopts.time_function = opts->time_function; + localopts.output = opts->output; + localopts.user_function = false_function; + localopts.clique_list = &best_clique; + localopts.clique_list_length = 1; + + for (i=0; i < g->n-1; i++) + if ((clique_size[table[i]] >= min_weight) || + (clique_size[table[i]] == 0)) + break; + + retval = weighted_clique_search_all( + table, i, min_weight, max_weight, maximal, g, &localopts, &num_found + ); + + if (retval || !weight_found) { + set_free(best_clique); + best_clique=NULL; + } + } + } + + cleanreturn: + s=best_clique; + + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + temp_list=NULL; + temp_count=0; + free(table); + set_free(current_clique); + current_clique=NULL; + free(clique_size); + clique_size=NULL; + + ENTRANCE_RESTORE(); + entrance_level--; + + *clique = s; + + return retval; +} + + + + + +/* + * clique_find_all() + * + * Find all cliques with weight at least min_weight and at most max_weight. + * + * g - the graph + * min_weight - minimum weight of cliques to search for. If min_weight==0, + * searches for maximum weight cliques. + * max_weight - maximum weight of cliques to search for. If max_weight==0, + * no upper limit is used. If min_weight==0, max_weight must + * also be 0. + * maximal - require cliques to be maximal cliques + * opts - time printing and clique storage options + * num_found - the number of cliques found. This can be less than the number + * of cliques in the graph iff opts->time_function() returns + * FALSE (request abort) or opts->user_function() returns an + * igraph error code + * + * The cliques found are stored in opts->clique_list[] and + * opts->user_function() is called with them (if non-NULL). The cliques + * stored in opts->clique_list[] are newly allocated, and can be freed + * by set_free(). + * + * Note: Automatically uses clique_unweighted_find_all if all vertex + * weights are the same. + */ +igraph_error_t clique_find_all(graph_t *g, int min_weight, int max_weight, + boolean maximal, clique_options *opts, int *num_found) { + int i,n; + int *table; + CLIQUER_LARGE_INT r; + igraph_error_t retval = IGRAPH_SUCCESS; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_weight>=0); + ASSERT(max_weight>=0); + ASSERT((max_weight==0) || (min_weight <= max_weight)); + ASSERT(!((min_weight==0) && (max_weight>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_weight>0) && (min_weight>max_weight)) { + /* state was not changed */ + entrance_level--; + if (num_found) { + *num_found = 0; + } + return IGRAPH_SUCCESS; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + if (!graph_weighted(g)) { + min_weight=DIV_UP(min_weight,g->weights[0]); + if (max_weight) { + max_weight=DIV_DOWN(max_weight,g->weights[0]); + if (max_weight < min_weight) { + /* state was not changed */ + entrance_level--; + if (num_found) { + *num_found = 0; + } + return IGRAPH_SUCCESS; + } + } + + weight_multiplier = g->weights[0]; + entrance_level--; + retval = clique_unweighted_find_all(g, min_weight, max_weight, maximal, opts, &r); + ENTRANCE_RESTORE(); + if (num_found) { + *num_found = r; + } + return retval; + } + + /* Dynamic allocation */ + current_clique=set_new(g->n); + best_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + memset(clique_size, 0, g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,TRUE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + /* First phase */ + retval = weighted_clique_search_single(table, min_weight, INT_MAX, g, opts, &n); + if (retval || n == 0) { + /* Requested clique has not been found. */ + goto cleanreturn; + } + + if (min_weight==0) { + min_weight=n; + max_weight=n; + maximal=FALSE; /* They're maximum cliques already. */ + } + if (max_weight==0) + max_weight=INT_MAX; + + for (i=0; i < g->n; i++) + if ((clique_size[table[i]] >= min_weight) || + (clique_size[table[i]] == 0)) + break; + + /* Second phase */ + retval = weighted_clique_search_all(table, i, min_weight, max_weight, maximal, g, opts, &n); + +cleanreturn: + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + free(table); + set_free(current_clique); + set_free(best_clique); + free(clique_size); + + ENTRANCE_RESTORE(); + entrance_level--; + + if (num_found) { + *num_found = n; + } + + return retval; +} + + + + + + + + + + + + + + + + +#if 0 +/* + * clique_print_time() + * + * Reports current running information every 0.1 seconds or when values + * change. + * + * level - re-entrance level + * i - current recursion level + * n - maximum recursion level + * max - weight of heaviest clique found + * cputime - CPU time used in algorithm so far + * realtime - real time used in algorithm so far + * opts - prints information to (FILE *)opts->output (or stdout if NULL) + * + * Returns always TRUE (ie. never requests abort). + */ +boolean clique_print_time(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts) { + static float prev_time=100; + static int prev_i=100; + static int prev_max=100; + static int prev_level=0; + FILE *fp=opts->output; + int j; + + if (fp==NULL) + fp=stdout; + + if (ABS(prev_time-realtime)>0.1 || i==n || ioutput (or stdout if NULL) + * + * Returns always TRUE (ie. never requests abort). + */ +boolean clique_print_time_always(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts) { + static float prev_time=100; + static int prev_i=100; + FILE *fp=opts->output; + int j; + + if (fp==NULL) + fp=stdout; + + for (j=1; j + +/* This is an igraph-specific modification to cliquer. + * We use a 64-bit CLIQUER_LARGE_INT (even on 32-bit systems) in places + * which are prone to overflow. Since cliquer indicates interruption by + * returning -1 times the clique count, the effect of overflow is that + * it returns a partial (i.e. incorrect) result without warning. */ +#include +#ifndef CLIQUER_LARGE_INT +#define CLIQUER_LARGE_INT int64_t +#endif + +#include "set.h" +#include "graph.h" +#include "reorder.h" + +typedef struct _clique_options clique_options; +struct _clique_options { + int *(*reorder_function)(graph_t *, boolean); + int *reorder_map; + + /* arguments: level, n, max, user_time, system_time, opts */ + boolean (*time_function)(int,int,int,int,double,double, + clique_options *); + FILE *output; + + igraph_error_t (*user_function)(set_t,graph_t *,clique_options *); + void *user_data; + set_t *clique_list; + int clique_list_length; +}; + +/* Weighted clique functions */ +extern igraph_error_t clique_max_weight( + graph_t *g, clique_options *opts, int *weight_found +); +extern igraph_error_t clique_find_single( + graph_t *g, int min_weight, int max_weight, boolean maximal, + clique_options *opts, set_t *clique +); +extern igraph_error_t clique_find_all( + graph_t *g, int req_weight, boolean exact, boolean maximal, + clique_options *opts, int *num_found +); + +/* Unweighted clique functions */ +#define clique_unweighted_max_size clique_unweighted_max_weight +extern igraph_error_t clique_unweighted_max_weight( + graph_t *g, clique_options *opts, int *weight_found +); +extern igraph_error_t clique_unweighted_find_single( + graph_t *g, int min_size, int max_size, boolean maximal, + clique_options *opts, set_t *clique +); +extern igraph_error_t clique_unweighted_find_all( + graph_t *g, int min_size, int max_size, boolean maximal, + clique_options *opts, CLIQUER_LARGE_INT *num_found +); + +/* Time printing functions */ +/* +extern boolean clique_print_time(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts); +extern boolean clique_print_time_always(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts); +*/ + +/* Alternate spelling (let's be a little forgiving): */ +#define cliquer_options clique_options +#define cliquer_default_options clique_default_options + +#endif /* !CLIQUER_H */ diff --git a/src/cliques/cliquer/cliquer_graph.c b/src/cliques/cliquer/cliquer_graph.c new file mode 100644 index 0000000..e94ee20 --- /dev/null +++ b/src/cliques/cliquer/cliquer_graph.c @@ -0,0 +1,764 @@ + +/* + * This file contains the graph handling routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + + +#include +#include +#include "graph.h" + + +/* +static graph_t *graph_read_dimacs_binary(FILE *fp,char *firstline); +static graph_t *graph_read_dimacs_ascii(FILE *fp,char *firstline); +*/ + + +/* + * graph_new() + * + * Returns a newly allocated graph with n vertices all with weight 1, + * and no edges. + */ +graph_t *graph_new(int n) { + graph_t *g; + int i; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(n>0); + + g=malloc(sizeof(graph_t)); + g->n=n; + g->edges=malloc(g->n * sizeof(set_t)); + g->weights=malloc(g->n * sizeof(int)); + for (i=0; i < g->n; i++) { + g->edges[i]=set_new(n); + g->weights[i]=1; + } + return g; +} + +/* + * graph_free() + * + * Frees the memory associated with the graph g. + */ +void graph_free(graph_t *g) { + int i; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(g->n > 0); + + for (i=0; i < g->n; i++) { + set_free(g->edges[i]); + } + free(g->weights); + free(g->edges); + free(g); + return; +} + + +/* + * graph_resize() + * + * Resizes graph g to given size. If size > g->n, the new vertices are + * not connected to any others and their weights are set to 1. + * If size < g->n, the last g->n - size vertices are removed. + */ +void graph_resize(graph_t *g, int size) { + int i; + + ASSERT(g!=NULL); + ASSERT(g->n > 0); + ASSERT(size > 0); + + if (g->n == size) + return; + + /* Free/alloc extra edge-sets */ + for (i=size; i < g->n; i++) + set_free(g->edges[i]); + g->edges=realloc(g->edges, size * sizeof(set_t)); + for (i=g->n; i < size; i++) + g->edges[i]=set_new(size); + + /* Resize original sets */ + for (i=0; i < MIN(g->n,size); i++) { + g->edges[i]=set_resize(g->edges[i],size); + } + + /* Weights */ + g->weights=realloc(g->weights, size * sizeof(int)); + for (i=g->n; iweights[i]=1; + + g->n=size; + return; +} + +/* + * graph_crop() + * + * Resizes the graph so as to remove all highest-valued isolated vertices. + */ +void graph_crop(graph_t *g) { + int i; + + for (i=g->n-1; i>=1; i--) + if (set_size(g->edges[i])>0) + break; + graph_resize(g,i+1); + return; +} + + +/* + * graph_weighted() + * + * Returns TRUE if all vertex weights of graph g are all the same. + * + * Note: Does NOT require weights to be 1. + */ +boolean graph_weighted(graph_t *g) { + int i,w; + + w=g->weights[0]; + for (i=1; i < g->n; i++) + if (g->weights[i] != w) + return TRUE; + return FALSE; +} + +/* + * graph_edge_count() + * + * Returns the number of edges in graph g. + */ +int graph_edge_count(graph_t *g) { + int i; + int count=0; + + for (i=0; i < g->n; i++) { + count += set_size(g->edges[i]); + } + return count/2; +} + + +#if 0 +/* + * graph_write_dimacs_ascii_file() + * + * Writes an ASCII dimacs-format file of graph g, with comment, to + * given file. + * + * Returns TRUE if successful, FALSE if an error occurred. + */ +boolean graph_write_dimacs_ascii_file(graph_t *g, char *comment, char *file) { + FILE *fp; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(file!=NULL); + + if ((fp=fopen(file,"wb"))==NULL) + return FALSE; + if (!graph_write_dimacs_ascii(g,comment,fp)) { + fclose(fp); + return FALSE; + } + fclose(fp); + return TRUE; +} + +/* + * graph_write_dimacs_ascii() + * + * Writes an ASCII dimacs-format file of graph g, with comment, to the + * file stream fp. + * + * Returns TRUE if successful, FALSE if an error occurred. + */ +boolean graph_write_dimacs_ascii(graph_t *g, char *comment, FILE *fp) { + int i,j; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(graph_test(g,NULL)); + ASSERT(fp!=NULL); + + if (comment) + fprintf(fp,"c %s\n",comment); + fprintf(fp,"p edge %d %d\n",g->n,graph_edge_count(g)); + for (i=0; i < g->n; i++) + if (g->weights[i]!=1) + fprintf(fp,"n %d %d\n",i+1,g->weights[i]); + for (i=0; i < g->n; i++) + for (j=0; j= headersize) { \ + headersize+=1024; \ + header=realloc(header,headersize); \ +} \ +strncat(header,s,1000); \ +headerlength+=strlen(s); + +boolean graph_write_dimacs_binary(graph_t *g, char *comment,FILE *fp) { + char *buf; + char *header=NULL; + int headersize=0; + int headerlength=0; + int i,j; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(graph_test(g,NULL)); + ASSERT(fp!=NULL); + + buf=malloc(MAX(1024,g->n/8+1)); + header=malloc(1024); + header[0]=0; + headersize=1024; + if (comment) { + strcpy(buf,"c "); + strncat(buf,comment,1000); + strcat(buf,"\n"); + STR_APPEND(buf); + } + sprintf(buf,"p edge %d %d\n",g->n,graph_edge_count(g)); + STR_APPEND(buf); + for (i=0; i < g->n; i++) { + if (g->weights[i]!=1) { + sprintf(buf,"n %d %d\n",i+1,g->weights[i]); + STR_APPEND(buf); + } + } + + fprintf(fp,"%d\n",(int)strlen(header)); + fprintf(fp,"%s",header); + free(header); + + for (i=0; i < g->n; i++) { + memset(buf,0,i/8+1); + for (j=0; j=strlen(str)) /* blank line */ + return TRUE; + if (str[i+1]!=0 && !isspace(str[i+1])) /* not 1-char field */ + return FALSE; + + switch (str[i]) { + case 'c': + return TRUE; + case 'p': + if (g->n != 0) + return FALSE; + if (sscanf(str," p %15s %d %d %2s",tmp,&(g->n),&i,tmp)!=3) + return FALSE; + if (g->n <= 0) + return FALSE; + g->edges=calloc(g->n,sizeof(set_t)); + for (i=0; in; i++) + g->edges[i]=set_new(g->n); + g->weights=calloc(g->n,sizeof(int)); + for (i=0; in; i++) + g->weights[i]=1; + return TRUE; + case 'n': + if ((g->n <= 0) || (g->weights == NULL)) + return FALSE; + if (sscanf(str," n %d %d %2s",&i,&w,tmp)!=2) + return FALSE; + if (i<1 || i>g->n) + return FALSE; + if (w<=0) + return FALSE; + g->weights[i-1]=w; + return TRUE; + case 'e': + if ((g->n <= 0) || (g->edges == NULL)) + return FALSE; + if (sscanf(str," e %d %d %2s",&i,&j,tmp)!=2) + return FALSE; + if (i<1 || j<1 || i>g->n || j>g->n) + return FALSE; + if (i==j) /* We want antireflexive graphs. */ + return TRUE; + GRAPH_ADD_EDGE(g,i-1,j-1); + return TRUE; + case 'd': + case 'v': + case 'x': + return TRUE; + default: + fprintf(stderr,"Warning: ignoring field '%c' in " + "input.\n",str[i]); + return TRUE; + } +} + + +/* + * graph_read_dimacs_binary() + * + * Reads a dimacs-format binary file from file stream fp with the first + * line being firstline. + * + * Returns the newly-allocated graph or NULL if an error occurred. + * + * TODO: This function leaks memory when reading erroneous files. + */ +static graph_t *graph_read_dimacs_binary(FILE *fp,char *firstline) { + int length=0; + graph_t *g; + int i,j; + char *buffer; + char *start; + char *end; + char **buf; + char tmp[10]; + + if (sscanf(firstline," %d %2s",&length,tmp)!=1) + return NULL; + if (length<=0) { + fprintf(stderr,"Malformed preamble: preamble size < 0.\n"); + return NULL; + } + buffer=malloc(length+2); + if (fread(buffer,1,length,fp)n <= 0) { + fprintf(stderr,"Malformed preamble: number of " + "vertices <= 0\n"); + free(g); + return NULL; + } + + /* Binary part. */ + buf=calloc(g->n,sizeof(char*)); + for (i=0; i < g->n; i++) { + buf[i]=calloc(g->n,1); + if (fread(buf[i],1,i/8+1,fp) < (i/8+1)) { + fprintf(stderr,"Unexpected end of file when " + "reading graph.\n"); + return NULL; + } + } + + for (i=0; i < g->n; i++) { + for (j=0; jn <= 0) { + free(g); + fprintf(stderr,"Unexpected end of file when reading graph.\n"); + return NULL; + } + + return g; +} +#endif + + +#if 0 +/* + * graph_print() + * + * Prints a representation of the graph g to stdout (along with any errors + * noticed). Mainly useful for debugging purposes and trivial output. + * + * The output consists of a first line describing the dimensions and then + * one line per vertex containing the vertex number (numbered 0,...,n-1), + * the vertex weight (if the graph is weighted), "->" and then a list + * of all vertices it is adjacent to. + */ +void graph_print(graph_t *g) { + int i,j; + int asymm=0; + int refl=0; + int nonpos=0; + int extra=0; + unsigned int weight=0; + boolean weighted; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + + if (g==NULL) { + printf(" WARNING: Graph pointer is NULL!\n"); + return; + } + if (g->n <= 0) { + printf(" WARNING: Graph has %d vertices " + "(should be positive)!\n",g->n); + return; + } + + weighted=graph_weighted(g); + + printf("%s graph has %d vertices, %d edges (density %.2f).\n", + weighted?"Weighted":((g->weights[0]==1)? + "Unweighted":"Semi-weighted"), + g->n,graph_edge_count(g), + (float)graph_edge_count(g)/((float)(g->n - 1)*(g->n)/2)); + + for (i=0; i < g->n; i++) { + printf("%2d",i); + if (weighted) { + printf(" w=%d",g->weights[i]); + if (g->weights[i] <= 0) { + printf("*NON-POSITIVE*"); + nonpos++; + } + } + if (weight < INT_MAX) + weight+=g->weights[i]; + printf(" ->"); + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + printf(" %d",j); + if (i==j) { + printf("*REFLEXIVE*"); + refl++; + } + if (!SET_CONTAINS_FAST(g->edges[j],i)) { + printf("*ASYMMERTIC*"); + asymm++; + } + } + } + for (j=g->n; j < SET_ARRAY_LENGTH(g->edges[i])*ELEMENTSIZE; + j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + printf(" %d*NON-EXISTENT*",j); + extra++; + } + } + printf("\n"); + } + + if (asymm) + printf(" WARNING: Graph contained %d asymmetric edges!\n", + asymm); + if (refl) + printf(" WARNING: Graph contained %d reflexive edges!\n", + refl); + if (nonpos) + printf(" WARNING: Graph contained %d non-positive vertex " + "weights!\n",nonpos); + if (extra) + printf(" WARNING: Graph contained %d edges to " + "non-existent vertices!\n",extra); + if (weight>=INT_MAX) + printf(" WARNING: Total graph weight >= INT_MAX!\n"); + return; +} + + +/* + * graph_test() + * + * Tests graph g to be valid. Checks that g is non-NULL, the edges are + * symmetric and anti-reflexive, and that all vertex weights are positive. + * If output is non-NULL, prints a few lines telling the status of the graph + * to file descriptor output. + * + * Returns TRUE if the graph is valid, FALSE otherwise. + */ +boolean graph_test(graph_t *g,FILE *output) { + int i,j; + int edges=0; + int asymm=0; + int nonpos=0; + int refl=0; + int extra=0; + unsigned int weight=0; + boolean weighted; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + + if (g==NULL) { + if (output) + fprintf(output," WARNING: Graph pointer is NULL!\n"); + return FALSE; + } + + weighted=graph_weighted(g); + + for (i=0; i < g->n; i++) { + if (g->edges[i]==NULL) { + if (output) + fprintf(output," WARNING: Graph edge set " + "NULL!\n" + " (further warning suppressed)\n"); + return FALSE; + } + if (SET_MAX_SIZE(g->edges[i]) < g->n) { + if (output) + fprintf(output," WARNING: Graph edge set " + "too small!\n" + " (further warnings suppressed)\n"); + return FALSE; + } + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + edges++; + if (i==j) { + refl++; + } + if (!SET_CONTAINS_FAST(g->edges[j],i)) { + asymm++; + } + } + } + for (j=g->n; j < SET_ARRAY_LENGTH(g->edges[i])*ELEMENTSIZE; + j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) + extra++; + } + if (g->weights[i] <= 0) + nonpos++; + if (weightweights[i]; + } + + edges/=2; /* Each is counted twice. */ + + if (output) { + /* Semi-weighted means all weights are equal, but not 1. */ + fprintf(output,"%s graph has %d vertices, %d edges " + "(density %.2f).\n", + weighted?"Weighted": + ((g->weights[0]==1)?"Unweighted":"Semi-weighted"), + g->n,edges,(float)edges/((float)(g->n - 1)*(g->n)/2)); + + if (asymm) + fprintf(output," WARNING: Graph contained %d " + "asymmetric edges!\n",asymm); + if (refl) + fprintf(output," WARNING: Graph contained %d " + "reflexive edges!\n",refl); + if (nonpos) + fprintf(output," WARNING: Graph contained %d " + "non-positive vertex weights!\n",nonpos); + if (extra) + fprintf(output," WARNING: Graph contained %d edges " + "to non-existent vertices!\n",extra); + if (weight>=INT_MAX) + fprintf(output," WARNING: Total graph weight >= " + "INT_MAX!\n"); + if (asymm==0 && refl==0 && nonpos==0 && extra==0 && + weight=INT_MAX) + return FALSE; + + return TRUE; +} + + +/* + * graph_test_regular() + * + * Returns the vertex degree for regular graphs, or -1 if the graph is + * not regular. + */ +int graph_test_regular(graph_t *g) { + int i,n; + + n=set_size(g->edges[0]); + + for (i=1; i < g->n; i++) { + if (set_size(g->edges[i]) != n) + return -1; + } + return n; +} + +#endif diff --git a/src/cliques/cliquer/cliquerconf.h b/src/cliques/cliquer/cliquerconf.h new file mode 100644 index 0000000..47d923b --- /dev/null +++ b/src/cliques/cliquer/cliquerconf.h @@ -0,0 +1,68 @@ + +#ifndef CLIQUERCONF_H +#define CLIQUERCONF_H + +/* + * setelement is the basic memory type used in sets. It is often fastest + * to be as large as can fit into the CPU registers. + * + * ELEMENTSIZE is the size of one setelement, measured in bits. It must + * be either 16, 32 or 64 (otherwise additional changes must be made to + * the source). + * + * The default is to use "unsigned long int" and attempt to guess the + * size using , which should work pretty well. Check functioning + * with "make test". + */ + +/* typedef unsigned long int setelement; */ +/* #define ELEMENTSIZE 64 */ + + +/* + * INLINE is a command prepended to function declarations to instruct the + * compiler to inline the function. If inlining is not desired, define blank. + * + * The default is to use "inline", which is recognized by most compilers. + */ + +/* #define INLINE */ +/* #define INLINE __inline__ */ +#if __STDC_VERSION__ >= 199901L + #define INLINE inline +#else + #if defined(_MSC_VER) + #define INLINE __inline + #elif defined(__GNUC__) + #define INLINE __inline__ + #else + #define INLINE + #endif +#endif + + +/* + * Set handling functions are defined as static functions in set.h for + * performance reasons. This may cause unnecessary warnings from the + * compiler. Some compilers (such as GCC) have the possibility to turn + * off the warnings on a per-function basis using a flag prepended to + * the function declaration. + * + * The default is to use the correct attribute when compiling with GCC, + * or no flag otherwise. + */ + +/* #define UNUSED_FUNCTION __attribute__((unused)) */ +/* #define UNUSED_FUNCTION */ + + +/* + * Uncommenting the following will disable all assertions (checks that + * function arguments and other variables are correct). This is highly + * discouraged, as it allows bugs to go unnoticed easier. The assertions + * are set so that they do not slow down programs notably. + */ + +/* #define ASSERT(x) */ + +#endif /* !CLIQUERCONF_H */ diff --git a/src/cliques/cliquer/graph.h b/src/cliques/cliquer/graph.h new file mode 100644 index 0000000..56f92af --- /dev/null +++ b/src/cliques/cliquer/graph.h @@ -0,0 +1,75 @@ + +#ifndef CLIQUER_GRAPH_H +#define CLIQUER_GRAPH_H + +#include "set.h" + +typedef struct _graph_t graph_t; +struct _graph_t { + int n; /* Vertices numbered 0...n-1 */ + set_t *edges; /* A list of n sets (the edges). */ + int *weights; /* A list of n vertex weights. */ +}; + + +#define GRAPH_IS_EDGE_FAST(g,i,j) (SET_CONTAINS_FAST((g)->edges[(i)],(j))) +#define GRAPH_IS_EDGE(g,i,j) (((i)<((g)->n))?SET_CONTAINS((g)->edges[(i)], \ + (j)):FALSE) +#define GRAPH_ADD_EDGE(g,i,j) do { \ + SET_ADD_ELEMENT((g)->edges[(i)],(j)); \ + SET_ADD_ELEMENT((g)->edges[(j)],(i)); \ +} while (FALSE) +#define GRAPH_DEL_EDGE(g,i,j) do { \ + SET_DEL_ELEMENT((g)->edges[(i)],(j)); \ + SET_DEL_ELEMENT((g)->edges[(j)],(i)); \ +} while (FALSE) + + +extern graph_t *graph_new(int n); +extern void graph_free(graph_t *g); +extern void graph_resize(graph_t *g, int size); +extern void graph_crop(graph_t *g); + +extern boolean graph_weighted(graph_t *g); +extern int graph_edge_count(graph_t *g); + +/* +extern graph_t *graph_read_dimacs(FILE *fp); +extern graph_t *graph_read_dimacs_file(char *file); +extern boolean graph_write_dimacs_ascii(graph_t *g, char *comment,FILE *fp); +extern boolean graph_write_dimacs_ascii_file(graph_t *g,char *comment, + char *file); +extern boolean graph_write_dimacs_binary(graph_t *g, char *comment,FILE *fp); +extern boolean graph_write_dimacs_binary_file(graph_t *g, char *comment, + char *file); + +extern void graph_print(graph_t *g); +extern boolean graph_test(graph_t *g, FILE *output); +extern int graph_test_regular(graph_t *g); +*/ + +UNUSED_FUNCTION INLINE +static int graph_subgraph_weight(graph_t *g,set_t s) { + unsigned int i,j; + int count=0; + setelement e; + + for (i=0; iweights[i*ELEMENTSIZE+j]; + e = e>>1; + } + } + } + return count; +} + +UNUSED_FUNCTION INLINE +static int graph_vertex_degree(graph_t *g, int v) { + return set_size(g->edges[v]); +} + +#endif /* !CLIQUER_GRAPH_H */ diff --git a/src/cliques/cliquer/misc.h b/src/cliques/cliquer/misc.h new file mode 100644 index 0000000..618b5c1 --- /dev/null +++ b/src/cliques/cliquer/misc.h @@ -0,0 +1,59 @@ + +#ifndef CLIQUER_MISC_H +#define CLIQUER_MISC_H + +#include "igraph_error.h" +#include "cliquerconf.h" + +/* + * We #define boolean instead of using a typedef because nauty.h uses it + * also. AFAIK, there is no way to check for an existing typedef, and + * re-typedefing is illegal (even when using exactly the same datatype!). + */ +#ifndef boolean +#define boolean int +#endif + + +/* + * The original cliquer source has some functions incorrectly marked as unused, + * thus leave this undefined. + */ +#define UNUSED_FUNCTION + + +/* + * Default inlining directive: "inline" + */ +#ifndef INLINE +#define INLINE inline +#endif + + +#include +#include + +#ifndef ASSERT +#define ASSERT IGRAPH_ASSERT +#endif /* !ASSERT */ + + +#ifndef FALSE +#define FALSE (0) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif +#ifndef ABS +#define ABS(v) (((v)<0)?(-(v)):(v)) +#endif + +#endif /* !CLIQUER_MISC_H */ diff --git a/src/cliques/cliquer/reorder.c b/src/cliques/cliquer/reorder.c new file mode 100644 index 0000000..15d197a --- /dev/null +++ b/src/cliques/cliquer/reorder.c @@ -0,0 +1,424 @@ + +/* + * This file contains the vertex reordering routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#include "reorder.h" + +#include + +#include + +#include + + +/* + * reorder_set() + * + * Reorders the set s with a function i -> order[i]. + * + * Note: Assumes that order is the same size as SET_MAX_SIZE(s). + */ +void reorder_set(set_t s,int *order) { + set_t tmp; + int i,j; + setelement e; + + ASSERT(reorder_is_bijection(order,SET_MAX_SIZE(s))); + + tmp=set_new(SET_MAX_SIZE(s)); + + for (i=0; i<(SET_MAX_SIZE(s)/ELEMENTSIZE); i++) { + e=s[i]; + if (e==0) + continue; + for (j=0; j>1; + } + } + if (SET_MAX_SIZE(s)%ELEMENTSIZE) { + e=s[i]; + for (j=0; j<(SET_MAX_SIZE(s)%ELEMENTSIZE); j++) { + if (e&1) { + SET_ADD_ELEMENT(tmp,order[i*ELEMENTSIZE+j]); + } + e = e>>1; + } + } + set_copy(s,tmp); + set_free(tmp); + return; +} + + +/* + * reorder_graph() + * + * Reorders the vertices in the graph with function i -> order[i]. + * + * Note: Assumes that order is of size g->n. + */ +void reorder_graph(graph_t *g, int *order) { + int i; + set_t *tmp_e; + int *tmp_w; + + ASSERT(reorder_is_bijection(order,g->n)); + + tmp_e=malloc(g->n * sizeof(set_t)); + tmp_w=malloc(g->n * sizeof(int)); + for (i=0; in; i++) { + reorder_set(g->edges[i],order); + tmp_e[order[i]]=g->edges[i]; + tmp_w[order[i]]=g->weights[i]; + } + for (i=0; in; i++) { + g->edges[i]=tmp_e[i]; + g->weights[i]=tmp_w[i]; + } + free(tmp_e); + free(tmp_w); + return; +} + + + +/* + * reorder_duplicate() + * + * Returns a newly allocated duplicate of the given ordering. + */ +int *reorder_duplicate(int *order,int n) { + int *new; + + new=malloc(n*sizeof(int)); + memcpy(new,order,n*sizeof(int)); + return new; +} + +/* + * reorder_invert() + * + * Inverts the given ordering so that new[old[i]]==i. + * + * Note: Asserts that order is a bijection. + */ +void reorder_invert(int *order,int n) { + int *new; + int i; + + ASSERT(reorder_is_bijection(order,n)); + + new=malloc(n*sizeof(int)); + for (i=0; i {0,...,n-1}. + * + * Returns TRUE if it is a bijection, FALSE otherwise. + */ +boolean reorder_is_bijection(int *order,int n) { + boolean *used; + int i; + + used=calloc(n,sizeof(boolean)); + for (i=0; i=n) { + free(used); + return FALSE; + } + if (used[order[i]]) { + free(used); + return FALSE; + } + used[order[i]]=TRUE; + } + for (i=0; in); +} + +/* + * reorder_by_reverse() + * + * Returns a reverse identity ordering. + */ +int *reorder_by_reverse(graph_t *g,boolean weighted) { + int i; + int *order; + + order=malloc(g->n * sizeof(int)); + for (i=0; i < g->n; i++) + order[i]=g->n-i-1; + return order; +} + +/* + * reorder_by_greedy_coloring() + * + * Equivalent to reorder_by_weighted_greedy_coloring or + * reorder_by_unweighted_greedy_coloring according to the value of weighted. + */ +int *reorder_by_greedy_coloring(graph_t *g,boolean weighted) { + if (weighted) + return reorder_by_weighted_greedy_coloring(g,weighted); + else + return reorder_by_unweighted_greedy_coloring(g,weighted); +} + + +/* + * reorder_by_unweighted_greedy_coloring() + * + * Returns an ordering for the graph g by coloring the clique one + * color at a time, always adding the vertex of largest degree within + * the uncolored graph, and numbering these vertices 0, 1, ... + * + * Experimentally efficient for use with unweighted graphs. + */ +int *reorder_by_unweighted_greedy_coloring(graph_t *g,boolean weighted) { + int i,j,v; + boolean *tmp_used; + int *degree; /* -1 for used vertices */ + int *order; + int maxdegree,maxvertex=0; + boolean samecolor; + + tmp_used=calloc(g->n,sizeof(boolean)); + degree=calloc(g->n,sizeof(int)); + order=calloc(g->n,sizeof(int)); + + for (i=0; i < g->n; i++) { + for (j=0; j < g->n; j++) { + ASSERT(!((i==j) && GRAPH_IS_EDGE(g,i,j))); + if (GRAPH_IS_EDGE(g,i,j)) + degree[i]++; + } + } + + v=0; + while (v < g->n) { + /* Reset tmp_used. */ + memset(tmp_used,0,g->n * sizeof(boolean)); + + do { + /* Find vertex to be colored. */ + maxdegree=0; + samecolor=FALSE; + for (i=0; i < g->n; i++) { + if (!tmp_used[i] && degree[i] >= maxdegree) { + maxvertex=i; + maxdegree=degree[i]; + samecolor=TRUE; + } + } + if (samecolor) { + order[v]=maxvertex; + degree[maxvertex]=-1; + v++; + + /* Mark neighbors not to color with same + * color and update neighbor degrees. */ + for (i=0; i < g->n; i++) { + if (GRAPH_IS_EDGE(g,maxvertex,i)) { + tmp_used[i]=TRUE; + degree[i]--; + } + } + } + } while (samecolor); + } + + free(tmp_used); + free(degree); + return order; +} + +/* + * reorder_by_weighted_greedy_coloring() + * + * Returns an ordering for the graph g by coloring the clique one + * color at a time, always adding the vertex that (in order of importance): + * 1. has the minimum weight in the remaining graph + * 2. has the largest sum of weights surrounding the vertex + * + * Experimentally efficient for use with weighted graphs. + */ +int *reorder_by_weighted_greedy_coloring(graph_t *g, boolean weighted) { + int i,j,p=0; + int cnt; + int *nwt; /* Sum of surrounding vertices' weights */ + int min_wt,max_nwt; + boolean *used; + int *order; + + nwt=malloc(g->n * sizeof(int)); + order=malloc(g->n * sizeof(int)); + used=calloc(g->n,sizeof(boolean)); + + for (i=0; i < g->n; i++) { + nwt[i]=0; + for (j=0; j < g->n; j++) + if (GRAPH_IS_EDGE(g, i, j)) + nwt[i] += g->weights[j]; + } + + for (cnt=0; cnt < g->n; cnt++) { + min_wt=INT_MAX; + max_nwt=-1; + for (i=g->n-1; i>=0; i--) + if ((!used[i]) && (g->weights[i] < min_wt)) + min_wt=g->weights[i]; + for (i=g->n-1; i>=0; i--) { + if (used[i] || (g->weights[i] > min_wt)) + continue; + if (nwt[i] > max_nwt) { + max_nwt=nwt[i]; + p=i; + } + } + order[cnt]=p; + used[p]=TRUE; + for (j=0; j < g->n; j++) + if ((!used[j]) && (GRAPH_IS_EDGE(g, p, j))) + nwt[j] -= g->weights[p]; + } + + free(nwt); + free(used); + + ASSERT(reorder_is_bijection(order,g->n)); + + return order; +} + +/* + * reorder_by_degree() + * + * Returns a reordering of the graph g so that the vertices with largest + * degrees (most neighbors) are first. + */ +int *reorder_by_degree(graph_t *g, boolean weighted) { + int i,j,v; + int *degree; + int *order; + int maxdegree,maxvertex=0; + + degree=calloc(g->n,sizeof(int)); + order=calloc(g->n,sizeof(int)); + + for (i=0; i < g->n; i++) { + for (j=0; j < g->n; j++) { + ASSERT(!((i==j) && GRAPH_IS_EDGE(g,i,j))); + if (GRAPH_IS_EDGE(g,i,j)) + degree[i]++; + } + } + + for (v=0; v < g->n; v++) { + maxdegree=0; + for (i=0; i < g->n; i++) { + if (degree[i] >= maxdegree) { + maxvertex=i; + maxdegree=degree[i]; + } + } + order[v]=maxvertex; + degree[maxvertex]=-1; /* used */ +/*** Max. degree withing unselected graph: + for (i=0; i < g->n; i++) { + if (GRAPH_IS_EDGE(g,maxvertex,i)) + degree[i]--; + } +***/ + } + + free(degree); + return order; +} + +/* + * reorder_by_random() + * + * Returns a random reordering for graph g. + * Note: Used the functions rand() and srand() to generate the random + * numbers. srand() is re-initialized every time reorder_by_random() + * is called using the system time. + */ +int *reorder_by_random(graph_t *g, boolean weighted) { + int i,r; + int *new; + boolean *used; + + new=calloc(g->n, sizeof(int)); + used=calloc(g->n, sizeof(boolean)); + for (i=0; i < g->n; i++) { + do { + r = igraph_rng_get_integer(igraph_rng_default(), 0, g->n - 1); + } while (used[r]); + new[i]=r; + used[r]=TRUE; + } + free(used); + return new; +} diff --git a/src/cliques/cliquer/reorder.h b/src/cliques/cliquer/reorder.h new file mode 100644 index 0000000..5c06d31 --- /dev/null +++ b/src/cliques/cliquer/reorder.h @@ -0,0 +1,26 @@ + +#ifndef CLIQUER_REORDER_H +#define CLIQUER_REORDER_H + +#include "set.h" +#include "graph.h" + +extern void reorder_set(set_t s,int *order); +extern void reorder_graph(graph_t *g, int *order); +extern int *reorder_duplicate(int *order,int n); +extern void reorder_invert(int *order,int n); +extern void reorder_reverse(int *order,int n); +extern int *reorder_ident(int n); +extern boolean reorder_is_bijection(int *order,int n); + + +#define reorder_by_default reorder_by_greedy_coloring +extern int *reorder_by_greedy_coloring(graph_t *g, boolean weighted); +extern int *reorder_by_weighted_greedy_coloring(graph_t *g, boolean weighted); +extern int *reorder_by_unweighted_greedy_coloring(graph_t *g,boolean weighted); +extern int *reorder_by_degree(graph_t *g, boolean weighted); +extern int *reorder_by_random(graph_t *g, boolean weighted); +extern int *reorder_by_ident(graph_t *g, boolean weighted); +extern int *reorder_by_reverse(graph_t *g, boolean weighted); + +#endif /* !CLIQUER_REORDER_H */ diff --git a/src/cliques/cliquer/set.h b/src/cliques/cliquer/set.h new file mode 100644 index 0000000..bca7aa0 --- /dev/null +++ b/src/cliques/cliquer/set.h @@ -0,0 +1,386 @@ + +/* + * This file contains the set handling routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#ifndef CLIQUER_SET_H +#define CLIQUER_SET_H + +#include +#include +#include +#include +#include "misc.h" + +/* + * Sets are arrays of setelement's (typically unsigned long int's) with + * representative bits for each value they can contain. The values + * are numbered 0,...,n-1. + */ + + +/*** Variable types and constants. ***/ + + +/* + * If setelement hasn't been declared: + * - use "unsigned long int" as setelement + * - try to deduce size from ULONG_MAX + */ + +#ifndef ELEMENTSIZE +typedef unsigned long int setelement; +# if (ULONG_MAX == 65535) +# define ELEMENTSIZE 16 +# elif (ULONG_MAX == 4294967295) +# define ELEMENTSIZE 32 +# else +# define ELEMENTSIZE 64 +# endif +#endif /* !ELEMENTSIZE */ + +typedef setelement * set_t; + + +/*** Counting amount of 1 bits in a setelement ***/ + +/* Array for amount of 1 bits in a byte. */ +static int set_bit_count[256] = { + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 }; + +/* The following macros assume that all higher bits are 0. + * They may in some cases be useful also on with other ELEMENTSIZE's, + * so we define them all. */ +#define SET_ELEMENT_BIT_COUNT_8(a) (set_bit_count[(a)]) +#define SET_ELEMENT_BIT_COUNT_16(a) (set_bit_count[(a)>>8] + \ + set_bit_count[(a)&0xFF]) +#define SET_ELEMENT_BIT_COUNT_32(a) (set_bit_count[(a)>>24] + \ + set_bit_count[((a)>>16)&0xFF] + \ + set_bit_count[((a)>>8)&0xFF] + \ + set_bit_count[(a)&0xFF]) +#define SET_ELEMENT_BIT_COUNT_64(a) (set_bit_count[(a)>>56] + \ + set_bit_count[((a)>>48)&0xFF] + \ + set_bit_count[((a)>>40)&0xFF] + \ + set_bit_count[((a)>>32)&0xFF] + \ + set_bit_count[((a)>>24)&0xFF] + \ + set_bit_count[((a)>>16)&0xFF] + \ + set_bit_count[((a)>>8)&0xFF] + \ + set_bit_count[(a)&0xFF]) +#if (ELEMENTSIZE==64) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_64(a) +# define FULL_ELEMENT ((setelement)0xFFFFFFFFFFFFFFFF) +#elif (ELEMENTSIZE==32) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_32(a) +# define FULL_ELEMENT ((setelement)0xFFFFFFFF) +#elif (ELEMENTSIZE==16) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_16(a) +# define FULL_ELEMENT ((setelement)0xFFFF) +#else +# error "SET_ELEMENT_BIT_COUNT(a) not defined for current ELEMENTSIZE" +#endif + + + +/*** Macros and functions ***/ + +/* + * Gives a value with bit x (counting from lsb up) set. + * + * Making this as a table might speed up things on some machines + * (though on most modern machines it's faster to shift instead of + * using memory). Making it a macro makes it easy to change. + */ +#define SET_BIT_MASK(x) ((setelement)1<<(x)) + + + +/* Set element handling macros */ + +#define SET_ELEMENT_INTERSECT(a,b) ((a)&(b)) +#define SET_ELEMENT_UNION(a,b) ((a)|(b)) +#define SET_ELEMENT_DIFFERENCE(a,b) ((a)&(~(b))) +#define SET_ELEMENT_CONTAINS(e,v) ((e)&SET_BIT_MASK(v)) + + +/* Set handling macros */ + +#define SET_ADD_ELEMENT(s,a) \ + ((s)[(a)/ELEMENTSIZE] |= SET_BIT_MASK((a)%ELEMENTSIZE)) +#define SET_DEL_ELEMENT(s,a) \ + ((s)[(a)/ELEMENTSIZE] &= ~SET_BIT_MASK((a)%ELEMENTSIZE)) +#define SET_CONTAINS_FAST(s,a) (SET_ELEMENT_CONTAINS((s)[(a)/ELEMENTSIZE], \ + (a)%ELEMENTSIZE)) +#define SET_CONTAINS(s,a) (((a)0); + + n=(size/ELEMENTSIZE+1)+1; + s=calloc(n,sizeof(setelement)); + s[0]=size; + + return &(s[1]); +} + +/* + * set_free() + * + * Free the memory associated with set s. + */ +UNUSED_FUNCTION INLINE +static void set_free(set_t s) { + ASSERT(s!=NULL); + free(&(s[-1])); +} + +/* + * set_resize() + * + * Resizes set s to given size. If the size is less than SET_MAX_SIZE(s), + * the last elements are dropped. + * + * Returns a pointer to the new set. + */ +UNUSED_FUNCTION INLINE +static set_t set_resize(set_t s, unsigned int size) { + unsigned int n; + + ASSERT(size>0); + + n=(size/ELEMENTSIZE+1); + s=((setelement *)realloc(s-1,(n+1)*sizeof(setelement)))+1; + + if (n>SET_ARRAY_LENGTH(s)) + memset(s+SET_ARRAY_LENGTH(s),0, + (n-SET_ARRAY_LENGTH(s))*sizeof(setelement)); + if (size < SET_MAX_SIZE(s)) + s[(size-1)/ELEMENTSIZE] &= (FULL_ELEMENT >> + (ELEMENTSIZE-size%ELEMENTSIZE)); + s[-1]=size; + + return s; +} + +/* + * set_size() + * + * Returns the number of elements in set s. + */ +UNUSED_FUNCTION INLINE +static int set_size(set_t s) { + int count=0; + setelement *c; + + for (c=s; c < s+SET_ARRAY_LENGTH(s); c++) + count+=SET_ELEMENT_BIT_COUNT(*c); + return count; +} + +/* + * set_duplicate() + * + * Returns a newly allocated duplicate of set s. + */ +UNUSED_FUNCTION INLINE +static set_t set_duplicate(set_t s) { + set_t new; + + new=set_new(SET_MAX_SIZE(s)); + memcpy(new,s,SET_ARRAY_LENGTH(s)*sizeof(setelement)); + return new; +} + +/* + * set_copy() + * + * Copies set src to dest. If dest is NULL, is equal to set_duplicate. + * If dest smaller than src, it is freed and a new set of the same size as + * src is returned. + */ +UNUSED_FUNCTION INLINE +static set_t set_copy(set_t dest,set_t src) { + if (dest==NULL) + return set_duplicate(src); + if (SET_MAX_SIZE(dest)=0) { + * // i is in set s + * } + */ +UNUSED_FUNCTION INLINE +static int set_return_next(set_t s, unsigned int n) { + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + + while (n%ELEMENTSIZE) { + if (SET_CONTAINS(s,n)) + return n; + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + + while (s[n/ELEMENTSIZE]==0) { + n+=ELEMENTSIZE; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + while (!SET_CONTAINS(s,n)) { + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + return n; +} + + +/* + * set_print() + * + * Prints the size and contents of set s to stdout. + * Mainly useful for debugging purposes and trivial output. + */ +/* +UNUSED_FUNCTION +static void set_print(set_t s) { + int i; + printf("size=%d(max %d)",set_size(s),(int)SET_MAX_SIZE(s)); + for (i=0; i + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#ifndef IGRAPH_CLIQUER_H +#define IGRAPH_CLIQUER_H + +#include "igraph_decls.h" +#include "igraph_cliques.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_cliquer_cliques(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +igraph_error_t igraph_i_cliquer_histogram(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size); + +igraph_error_t igraph_i_cliquer_callback(const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + +igraph_error_t igraph_i_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res, + igraph_real_t min_weight, igraph_real_t max_weight, igraph_bool_t maximal, + igraph_int_t max_results); + +igraph_error_t igraph_i_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res); + +igraph_error_t igraph_i_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res); + +IGRAPH_END_C_DECLS + +#endif // IGRAPH_CLIQUER_H diff --git a/src/cliques/cliquer_wrapper.c b/src/cliques/cliquer_wrapper.c new file mode 100644 index 0000000..e01370d --- /dev/null +++ b/src/cliques/cliquer_wrapper.c @@ -0,0 +1,472 @@ +/* + igraph library. + Copyright (C) 2016-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "igraph_error.h" +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "cliques/cliquer_internal.h" +#include "cliques/cliquer/cliquer.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +#include + +/* We shall use this option struct for all calls to Cliquer */ +static IGRAPH_THREAD_LOCAL clique_options igraph_cliquer_opt = { + reorder_by_default, NULL, NULL, NULL, NULL, NULL, NULL, 0 +}; + + +/* Convert an igraph graph to a Cliquer graph */ +static igraph_error_t igraph_to_cliquer(const igraph_t *ig, graph_t **cg) { + igraph_int_t vcount, ecount; + igraph_int_t i; + + if (igraph_is_directed(ig)) { + IGRAPH_WARNING("Edge directions are ignored for clique calculations."); + } + + vcount = igraph_vcount(ig); + ecount = igraph_ecount(ig); + + if (vcount > INT_MAX) { + IGRAPH_ERROR("Graph too large for Cliquer.", IGRAPH_EOVERFLOW); + } + + *cg = graph_new((int) vcount); + + for (i = 0; i < ecount; ++i) { + igraph_int_t s, t; + s = IGRAPH_FROM(ig, i); + t = IGRAPH_TO(ig, i); + if (s != t) { + GRAPH_ADD_EDGE(*cg, s, t); + } + } + + return IGRAPH_SUCCESS; +} + + +/* Copy weights to a Cliquer graph */ +static igraph_error_t set_weights(const igraph_vector_t *vertex_weights, graph_t *g) { + igraph_int_t i; + + IGRAPH_ASSERT(vertex_weights != NULL); + + if (igraph_vector_size(vertex_weights) != g->n) { + IGRAPH_ERROR("Invalid vertex weight vector length.", IGRAPH_EINVAL); + } + + for (i = 0; i < g->n; ++i) { + g->weights[i] = VECTOR(*vertex_weights)[i]; + if (g->weights[i] != VECTOR(*vertex_weights)[i]) { + IGRAPH_WARNING("Only integer vertex weights are supported; weights will be truncated to their integer parts."); + } + if (g->weights[i] <= 0) { + IGRAPH_ERROR("Vertex weights must be positive.", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + + +/* Find all cliques. */ + +typedef struct { + igraph_vector_int_t clique; + igraph_vector_int_list_t* result; + igraph_int_t max_results; +} igraph_i_cliquer_cliques_user_data_t; + +static igraph_error_t igraph_i_cliquer_cliques_user_data_init( + igraph_i_cliquer_cliques_user_data_t* data, + igraph_vector_int_list_t* result +) { + data->result = result; + data->max_results = IGRAPH_UNLIMITED; + igraph_vector_int_list_clear(result); + return igraph_vector_int_init(&data->clique, 0); +} + +static void igraph_i_cliquer_cliques_user_data_destroy( + igraph_i_cliquer_cliques_user_data_t* data +) { + igraph_vector_int_destroy(&data->clique); + data->result = 0; +} + +static igraph_error_t collect_cliques_callback(set_t s, graph_t *g, clique_options *opt) { + int i; + igraph_int_t j; + igraph_i_cliquer_cliques_user_data_t* data = (igraph_i_cliquer_cliques_user_data_t *) opt->user_data; + + IGRAPH_UNUSED(g); + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_vector_int_resize(&data->clique, set_size(s))); + + i = -1; j = 0; + while ((i = set_return_next(s, i)) >= 0) { + VECTOR(data->clique)[j++] = i; + } + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(data->result, &data->clique)); + + if (data->max_results > 0 && igraph_vector_int_list_size(data->result) == data->max_results) { + return IGRAPH_STOP; + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_cliquer_cliques( + const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + + graph_t *g; + igraph_int_t vcount = igraph_vcount(graph); + igraph_i_cliquer_cliques_user_data_t data; + + if (vcount == 0 || max_results == 0) { + igraph_vector_int_list_clear(res); + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = 0; + } + + if (max_size > INT_MAX) { + max_size = INT_MAX; + } + + if (max_size > 0 && max_size < min_size) { + IGRAPH_ERROR("Maximum clique size must not be smaller than the minimum clique size.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cliquer_cliques_user_data_init(&data, res)); + IGRAPH_FINALLY(igraph_i_cliquer_cliques_user_data_destroy, &data); + + data.max_results = max_results; + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + igraph_cliquer_opt.user_data = &data; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_CHECK(clique_unweighted_find_all(g, (int) min_size, (int) max_size, /* maximal= */ FALSE, &igraph_cliquer_opt, NULL)); + + graph_free(g); + igraph_i_cliquer_cliques_user_data_destroy(&data); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/* Count cliques of each size. */ + +static igraph_error_t count_cliques_callback(set_t s, graph_t *g, clique_options *opt) { + igraph_vector_t *hist; + + IGRAPH_UNUSED(g); + + IGRAPH_ALLOW_INTERRUPTION(); + + hist = (igraph_vector_t *) opt->user_data; + VECTOR(*hist)[set_size(s) - 1] += 1; + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_cliquer_histogram(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size) { + graph_t *g; + igraph_int_t i; + igraph_int_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + igraph_vector_clear(hist); + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = vcount; /* also used for initial hist vector size, do not set to zero */ + } + + if (max_size > INT_MAX) { + max_size = INT_MAX; + } + + if (max_size < min_size) { + IGRAPH_ERRORF("Maximum clique size (%" IGRAPH_PRId ") must not be " + "smaller than minimum clique size (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, max_size, min_size); + } + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(igraph_vector_resize(hist, max_size)); + igraph_vector_null(hist); + igraph_cliquer_opt.user_data = hist; + igraph_cliquer_opt.user_function = &count_cliques_callback; + + IGRAPH_CHECK(clique_unweighted_find_all(g, (int) min_size, (int) max_size, /* maximal= */ FALSE, &igraph_cliquer_opt, NULL)); + + for (i = max_size; i > 0; --i) { + if (VECTOR(*hist)[i - 1] > 0) { + break; + } + } + IGRAPH_CHECK(igraph_vector_resize(hist, i)); + igraph_vector_resize_min(hist); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Call function for each clique. */ + +struct callback_data { + igraph_vector_int_t *clique; + igraph_clique_handler_t *handler; + void *arg; +}; + +static igraph_error_t callback_callback(set_t s, graph_t *g, clique_options *opt) { + struct callback_data *cd; + int i; + igraph_int_t j; + igraph_error_t retval; + + IGRAPH_UNUSED(g); + + IGRAPH_ALLOW_INTERRUPTION(); + + cd = (struct callback_data *) opt->user_data; + + IGRAPH_CHECK(igraph_vector_int_resize(cd->clique, set_size(s))); + + i = -1; j = 0; + while ((i = set_return_next(s, i)) >= 0) { + VECTOR(*cd->clique)[j++] = i; + } + + retval = (*(cd->handler))(cd->clique, cd->arg); + + return retval; +} + +igraph_error_t igraph_i_cliquer_callback(const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg) { + graph_t *g; + igraph_vector_int_t current_clique; + struct callback_data cd; + igraph_int_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = 0; + } + + if (max_size > INT_MAX) { + max_size = INT_MAX; + } + + if (max_size > 0 && max_size < min_size) { + IGRAPH_ERROR("Maximum clique size must not be smaller than the minimum clique size.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_VECTOR_INT_INIT_FINALLY(¤t_clique, min_size); + + cd.clique = ¤t_clique; + cd.handler = cliquehandler_fn; + cd.arg = arg; + igraph_cliquer_opt.user_data = &cd; + igraph_cliquer_opt.user_function = &callback_callback; + + IGRAPH_CHECK(clique_unweighted_find_all(g, (int) min_size, (int) max_size, /* maximal= */ FALSE, &igraph_cliquer_opt, NULL)); + + igraph_vector_int_destroy(¤t_clique); + graph_free(g); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/* Find weighted cliques in given weight range. */ + +igraph_error_t igraph_i_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res, + igraph_real_t min_weight, igraph_real_t max_weight, igraph_bool_t maximal, + igraph_int_t max_results) { + graph_t *g; + igraph_int_t vcount = igraph_vcount(graph); + igraph_i_cliquer_cliques_user_data_t data; + + if (vcount == 0 || max_results == 0) { + igraph_vector_int_list_clear(res); + return IGRAPH_SUCCESS; + } + + if (min_weight != (int) min_weight) { + IGRAPH_WARNING("Only integer vertex weights are supported; the minimum weight will be truncated to its integer part."); + min_weight = (int) min_weight; + } + + if (max_weight != (int) max_weight) { + IGRAPH_WARNING("Only integer vertex weights are supported; the maximum weight will be truncated to its integer part."); + max_weight = (int) max_weight; + } + + if (min_weight <= 0) { + min_weight = 1; + } + if (max_weight <= 0) { + max_weight = 0; + } + + if (max_weight > 0 && max_weight < min_weight) { + IGRAPH_ERROR("Maximum clique weight must not be smaller than minimum clique weight.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cliquer_cliques_user_data_init(&data, res)); + IGRAPH_FINALLY(igraph_i_cliquer_cliques_user_data_destroy, &data); + data.max_results = max_results; + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_cliquer_opt.user_data = &data; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_CHECK(clique_find_all(g, (int) min_weight, (int) max_weight, maximal, &igraph_cliquer_opt, NULL)); + + graph_free(g); + igraph_i_cliquer_cliques_user_data_destroy(&data); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/* Find largest weighted cliques. */ + +igraph_error_t igraph_i_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res) { + graph_t *g; + igraph_int_t vcount = igraph_vcount(graph); + igraph_i_cliquer_cliques_user_data_t data; + + if (vcount == 0) { + igraph_vector_int_list_clear(res); + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_i_cliquer_cliques_user_data_init(&data, res)); + IGRAPH_FINALLY(igraph_i_cliquer_cliques_user_data_destroy, &data); + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_cliquer_opt.user_data = &data; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_CHECK(clique_find_all(g, 0, 0, FALSE, &igraph_cliquer_opt, NULL)); + + graph_free(g); + igraph_i_cliquer_cliques_user_data_destroy(&data); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/* Find weight of largest weight clique. */ + +static igraph_error_t check_interruption_callback(set_t s, graph_t *g, clique_options *opt) { + IGRAPH_UNUSED(s); IGRAPH_UNUSED(g); IGRAPH_UNUSED(opt); + IGRAPH_ALLOW_INTERRUPTION(); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res) { + graph_t *g; + igraph_int_t vcount = igraph_vcount(graph); + int res_int; + + if (vcount == 0) { + if (res) { + *res = 0; + } + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_to_cliquer(graph, &g)); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_cliquer_opt.user_function = &check_interruption_callback; + + IGRAPH_CHECK(clique_max_weight(g, &igraph_cliquer_opt, &res_int)); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + if (res) { + *res = res_int; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/cliques/cliques.c b/src/cliques/cliques.c new file mode 100644 index 0000000..83c2941 --- /dev/null +++ b/src/cliques/cliques.c @@ -0,0 +1,1119 @@ +/* + igraph library. + Copyright (C) 2005-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_cliques.h" + +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" + +#include "cliques/cliquer_internal.h" +#include "core/interruption.h" +#include "core/set.h" + +static igraph_error_t igraph_i_find_k_indsets( + igraph_int_t size, + const igraph_int_t *member_storage, + igraph_int_t **new_member_storage, + igraph_int_t old_count, + igraph_int_t *new_count, + igraph_lazy_adjlist_t *al) { + + igraph_int_t l, m, n, new_member_storage_size; + const igraph_int_t *c1, *c2; + const igraph_vector_int_t *neis; + igraph_int_t v1, v2; + igraph_bool_t ok; + + /* Allocate the storage */ + *new_member_storage = IGRAPH_REALLOC(*new_member_storage, + (size_t) (size * old_count), + igraph_int_t); + IGRAPH_CHECK_OOM(*new_member_storage, "Insufficient memory for independent vertex sets."); + + new_member_storage_size = size * old_count; + IGRAPH_FINALLY(igraph_free, *new_member_storage); + + m = n = 0; + + /* Now consider all pairs of i-1-indsets and see if they can be merged */ + for (igraph_int_t j = 0; j < old_count; j++) { + for (igraph_int_t k = j + 1; k < old_count; k++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Since indsets are represented by their vertex indices in increasing + * order, two indsets can be merged iff they have exactly the same + * indices excluding one AND there is no edge between the two different + * vertices */ + c1 = member_storage + j * (size - 1); + c2 = member_storage + k * (size - 1); + /* Find the longest prefixes of c1 and c2 that are equal */ + for (l = 0; l < size - 1 && c1[l] == c2[l]; l++) { + (*new_member_storage)[m++] = c1[l]; + } + /* Now, if l == size-1, the two vectors are totally equal. This is a bug */ + IGRAPH_ASSERT(l != size-1); + /* Assuming that j (*new_member_storage)[m - 1]) { + (*new_member_storage)[m++] = v2; + n = m; + } else { + m = n; + } + } else { + m = n; + } + } + /* See if new_member_storage is full. If so, reallocate */ + if (m == new_member_storage_size) { + IGRAPH_FINALLY_CLEAN(1); + *new_member_storage = IGRAPH_REALLOC(*new_member_storage, + (size_t) new_member_storage_size * 2, + igraph_int_t); + IGRAPH_CHECK_OOM(*new_member_storage, "Insufficient memory to find independent vertex sets."); + new_member_storage_size *= 2; + IGRAPH_FINALLY(igraph_free, *new_member_storage); + } + } + } + + /* Calculate how many independent vertex sets we have found */ + *new_count = n / size; + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cliques + * \brief Finds all or some cliques in a graph. + * + * + * Cliques are fully connected subgraphs of a graph. + * + * + * If you are only interested in the size of the largest clique in the graph, + * use \ref igraph_clique_number() instead. + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \param min_size Integer specifying the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer specifying the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many cliques will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_largest_cliques() and \ref igraph_clique_number(). + * + * Time complexity: Exponential + * + * \example examples/simple/igraph_cliques.c + */ +igraph_error_t igraph_cliques( + const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + return igraph_i_cliquer_cliques(graph, res, min_size, max_size, max_results); +} + + +/** + * \function igraph_clique_size_hist + * \brief Counts cliques of each size in the graph. + * + * + * Cliques are fully connected subgraphs of a graph. + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param hist Pointer to an initialized vector. The result will be stored + * here. The first element will store the number of size-1 cliques, the second + * element the number of size-2 cliques, etc. For cliques smaller than \p min_size, + * zero counts will be returned. + * \param min_size Integer specifying the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer specifying the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_cliques() and \ref igraph_cliques_callback() + * + * Time complexity: Exponential + * + */ +igraph_error_t igraph_clique_size_hist(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size) { + return igraph_i_cliquer_histogram(graph, hist, min_size, max_size); +} + + +/** + * \function igraph_cliques_callback + * \brief Calls a function for each clique in the graph. + * + * + * Cliques are fully connected subgraphs of a graph. This function + * enumerates all cliques within the given size range and calls + * \p cliquehandler_fn for each of them. The cliques are passed to the + * callback function as a pointer to an \ref igraph_vector_int_t. Destroying and + * freeing this vector is left up to the user. Use \ref igraph_vector_int_destroy() + * to destroy it first, then free it using \ref igraph_free(). + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param min_size Integer specifying the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer specifying the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param cliquehandler_fn Callback function to be called for each clique. + * See also \ref igraph_clique_handler_t. + * \param arg Extra argument to supply to \p cliquehandler_fn. + * \return Error code. + * + * \sa \ref igraph_cliques() + * + * Time complexity: Exponential + * + */ +igraph_error_t igraph_cliques_callback(const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg) { + return igraph_i_cliquer_callback(graph, min_size, max_size, cliquehandler_fn, arg); +} + + +/** + * \function igraph_weighted_cliques + * \brief Finds all cliques in a given weight range in a vertex weighted graph. + * + * + * Cliques are fully connected subgraphs of a graph. + * The weight of a clique is the sum of the weights + * of individual vertices within the clique. + * + * + * Only positive integer vertex weights are supported. + * + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. You may pass \c NULL + * here to make each vertex have a weight of 1. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \param maximal If true, only maximal cliques will be returned + * \param min_weight Integer specifying the minimum weight of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_weight Integer specifying the maximum weight of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many cliques will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_maximal_cliques() + * + * Time complexity: Exponential + * + */ +igraph_error_t igraph_weighted_cliques( + const igraph_t *graph, const igraph_vector_t *vertex_weights, + igraph_vector_int_list_t *res, + igraph_bool_t maximal, + igraph_real_t min_weight, igraph_real_t max_weight, + igraph_int_t max_results) { + + if (vertex_weights) { + return igraph_i_weighted_cliques(graph, vertex_weights, res, min_weight, max_weight, maximal, max_results); + } else if (maximal) { + return igraph_maximal_cliques(graph, res, min_weight, max_weight, max_results); + } else { + return igraph_cliques(graph, res, min_weight, max_weight, max_results); + } +} + + +/** + * \function igraph_largest_weighted_cliques + * \brief Finds the largest weight clique(s) in a graph. + * + * The weight of a clique is the sum of the weights of its vertices. + * This function finds the clique(s) having the largest weight in the graph. + * + * + * Only positive integer vertex weights are supported. + * + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. You may pass \c NULL + * here to make each vertex have a weight of 1. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \return Error code. + * + * \sa \ref igraph_weighted_cliques(), \ref igraph_weighted_clique_number(), \ref igraph_largest_cliques() + * + * Time complexity: TODO + */ +igraph_error_t igraph_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_int_list_t *res) { + if (vertex_weights) { + return igraph_i_largest_weighted_cliques(graph, vertex_weights, res); + } else { + return igraph_largest_cliques(graph, res); + } +} + + +/** + * \function igraph_weighted_clique_number + * \brief Finds the weight of the largest weight clique in the graph. + * + * The weight of a clique is the sum of the weights of its vertices. + * This function finds the weight of the largest weight clique. + * + * + * Only positive integer vertex weights are supported. + * + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. You may pass \c NULL + * here to make each vertex have a weight of 1. + * \param res The largest weight will be returned to the \c igraph_real_t + * pointed to by this variable. + * \return Error code. + * + * \sa \ref igraph_weighted_cliques(), \ref igraph_largest_weighted_cliques(), \ref igraph_clique_number() + * + * Time complexity: TODO + * + */ +igraph_error_t igraph_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res) { + if (vertex_weights) { + return igraph_i_weighted_clique_number(graph, vertex_weights, res); + } else { + igraph_int_t res_int; + IGRAPH_CHECK(igraph_clique_number(graph, &res_int)); + if (res) { + *res = res_int; + } + return IGRAPH_SUCCESS; + } +} + +static igraph_error_t igraph_i_maximal_or_largest_cliques_or_indsets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t *clique_number, + igraph_bool_t keep_only_largest, + igraph_bool_t complementer); + +/** + * \function igraph_independent_vertex_sets + * \brief Finds all independent vertex sets in a graph. + * + * + * A vertex set is considered independent if there are no edges between + * them. + * + * + * If you are interested in the size of the largest independent vertex set, + * use \ref igraph_independence_number() instead. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \param min_size Integer specifying the minimum size of the sets to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer specifying the maximum size of the sets to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many independent vertex sets will be recorded. + * If negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_largest_independent_vertex_sets(), + * \ref igraph_independence_number(). + * + * Time complexity: TODO + * + * \example examples/simple/igraph_independent_sets.c + */ +igraph_error_t igraph_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + + igraph_int_t no_of_nodes; + igraph_vector_int_t *indset; + igraph_int_t *member_storage, *new_member_storage, *c1; + igraph_int_t indset_count, old_indset_count; + igraph_lazy_adjlist_t al; + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored during independent vertex set calculations."); + } + + no_of_nodes = igraph_vcount(graph); + + if (min_size < 0) { + min_size = 0; + } + if (max_size > no_of_nodes || max_size <= 0) { + max_size = no_of_nodes; + } + if (max_results < 0) { + max_results = IGRAPH_INTEGER_MAX; + } + + igraph_vector_int_list_clear(res); + + /* Add size-1 indsets if requested */ + if (min_size <= 1) { + igraph_int_t max_singletons_to_add = + (max_results > no_of_nodes) ? no_of_nodes : max_results; + IGRAPH_CHECK(igraph_vector_int_list_resize(res, max_singletons_to_add)); + for (igraph_int_t i = 0; i < max_singletons_to_add; i++) { + indset = igraph_vector_int_list_get_ptr(res, i); + IGRAPH_CHECK(igraph_vector_int_push_back(indset, i)); + --max_results; + } + } + + if (max_results == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + /* Will be resized later, if needed. */ + member_storage = IGRAPH_CALLOC(1, igraph_int_t); + IGRAPH_CHECK_OOM(member_storage, "Insufficient memory for independent vertex set calculation."); + IGRAPH_FINALLY(igraph_free, member_storage); + + /* Find all 1-cliques: every vertex will be a clique */ + new_member_storage = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(new_member_storage, "Insufficient memory for independent vertex set calculation."); + IGRAPH_FINALLY(igraph_free, new_member_storage); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + new_member_storage[i] = i; + } + indset_count = no_of_nodes; + old_indset_count = 0; + + for (igraph_int_t i = 2; i <= max_size && indset_count > 1; i++) { + + /* Here new_member_storage contains the independent vertex sets found in + the previous iteration. Save this into member_storage, might be needed later */ + + c1 = member_storage; + member_storage = new_member_storage; + new_member_storage = c1; + old_indset_count = indset_count; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Calculate the independent vertex sets */ + + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_i_find_k_indsets(i, member_storage, + &new_member_storage, + old_indset_count, + &indset_count, + &al)); + IGRAPH_FINALLY(igraph_free, member_storage); + IGRAPH_FINALLY(igraph_free, new_member_storage); + + /* Add the cliques just found to the result if requested */ + if (i >= min_size && i <= max_size) { + for (igraph_int_t j = 0, k = 0; j < indset_count; j++, k += i) { + const igraph_vector_int_t new_member_storage_view = + igraph_vector_int_view(new_member_storage + k, i); + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &new_member_storage_view)); + if (--max_results == 0) { + goto done; + } + } + } + + } /* i <= max_size && clique_count != 0 */ + +done: + IGRAPH_FREE(new_member_storage); + IGRAPH_FREE(member_storage); + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_largest_independent_vertex_sets + * \brief Finds the largest independent vertex set(s) in a graph. + * + * + * An independent vertex set is largest if there is no other + * independent vertex set with more vertices in the graph. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \return Error code. + * + * \sa \ref igraph_independent_vertex_sets(), \ref + * igraph_maximal_independent_vertex_sets(). + * + * Time complexity: TODO + */ + +igraph_error_t igraph_largest_independent_vertex_sets(const igraph_t *graph, + igraph_vector_int_list_t *res) { + return igraph_i_maximal_or_largest_cliques_or_indsets(graph, res, NULL, true, false); +} + +typedef struct igraph_i_max_ind_vsets_data_t { + igraph_int_t matrix_size; + igraph_adjlist_t adj_list; /* Adjacency list of the graph */ + igraph_vector_int_t deg; /* Degrees of individual nodes */ + igraph_set_t* buckets; /* Bucket array */ + /* The IS value for each node. Still to be explained :) */ + igraph_int_t* IS; + igraph_int_t largest_set_size; /* Size of the largest set encountered */ + igraph_bool_t keep_only_largest; /* True if we keep only the largest sets */ + igraph_int_t min_size, max_size; + igraph_int_t max_results; +} igraph_i_max_ind_vsets_data_t; + +static igraph_error_t igraph_i_maximal_independent_vertex_sets_backtrack( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_i_max_ind_vsets_data_t *clqdata, + igraph_int_t level) { + + igraph_int_t v1, v2, v3, c, j, k; + igraph_vector_int_t *neis1, *neis2; + igraph_bool_t f; + igraph_int_t it_state; + + if (clqdata->max_results == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + if (level >= clqdata->matrix_size - 1) { + igraph_int_t size = 0; + + if (res) { + igraph_vector_int_t vec, *newvec; + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + + for (v1 = 0; v1 < clqdata->matrix_size; v1++) { + if (clqdata->IS[v1] == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&vec, v1)); + } + } + + size = igraph_vector_int_size(&vec); + + /* Trick for efficient insertion of a new vector into a vector list: + * Instead of copying the vector contents, we add an empty vector to + * the list, then swap it with the vector to-be-added in O(1) time. */ + if (!clqdata->keep_only_largest) { + if (clqdata->min_size <= size && size <= clqdata->max_size) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(res, &newvec)); + igraph_vector_int_swap(newvec, &vec); + if (clqdata->max_results >= 0) --(clqdata->max_results); + } + } else { + if (size > clqdata->largest_set_size) { + /* We are keeping only the largest sets, and we've found one that's + * larger than all previous sets, so we have to clear the list */ + igraph_vector_int_list_clear(res); + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(res, &newvec)); + igraph_vector_int_swap(newvec, &vec); + } else if (size == clqdata->largest_set_size) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(res, &newvec)); + igraph_vector_int_swap(newvec, &vec); + } + } + + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + } else { + for (v1 = 0, size = 0; v1 < clqdata->matrix_size; v1++) { + if (clqdata->IS[v1] == 0) { + size++; + } + } + } + if (size > clqdata->largest_set_size) { + clqdata->largest_set_size = size; + } + } else { + v1 = level + 1; + /* Count the number of vertices with an index less than v1 that have + * an IS value of zero */ + neis1 = igraph_adjlist_get(&clqdata->adj_list, v1); + c = 0; + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = VECTOR(*neis1)[j]) <= level) { + if (clqdata->IS[v2] == 0) { + c++; + } + j++; + } + + if (c == 0) { + /* If there are no such nodes... */ + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]++; + j++; + } + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, v1)); + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]--; + j++; + } + } else { + /* If there are such nodes, store the count in the IS value of v1 */ + clqdata->IS[v1] = c; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, v1)); + clqdata->IS[v1] = 0; + + f = true; + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = VECTOR(*neis1)[j]) <= level) { + if (clqdata->IS[v2] == 0) { + IGRAPH_CHECK(igraph_set_add(&clqdata->buckets[v1], j)); + neis2 = igraph_adjlist_get(&clqdata->adj_list, v2); + k = 0; + while (k < VECTOR(clqdata->deg)[v2] && + (v3 = VECTOR(*neis2)[k]) <= level) { + clqdata->IS[v3]--; + if (clqdata->IS[v3] == 0) { + f = false; + } + k++; + } + } + clqdata->IS[v2]++; + j++; + } + + if (f) { + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, v1)); + } + + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]--; + j++; + } + + it_state = 0; + while (igraph_set_iterate(&clqdata->buckets[v1], &it_state, &j)) { + v2 = VECTOR(*neis1)[j]; + neis2 = igraph_adjlist_get(&clqdata->adj_list, v2); + k = 0; + while (k < VECTOR(clqdata->deg)[v2] && + (v3 = VECTOR(*neis2)[k]) <= level) { + clqdata->IS[v3]++; + k++; + } + } + igraph_set_clear(&clqdata->buckets[v1]); + } + } + + return IGRAPH_SUCCESS; +} + +/* TODO (ugly hack): + * + * This version does not know the length of the array, and is safe to use + * ONLY on arrays which have not been completely filled out and were + * originally initialized to zero. It relies on igraph_set_inited() + * returning false when igraph_set_t is all-zero-bytes. + * This function is meant for use with IGRAPH_FINALLY. + * + * Should probably be replaced with a proper igraph_vector_ptr_t. + */ +static void free_set_array_incomplete(igraph_set_t *array) { + igraph_int_t i = 0; + while (igraph_set_inited(array + i)) { + igraph_set_destroy(array + i); + i++; + } + IGRAPH_FREE(array); +} + +static void free_set_array(igraph_set_t *array, igraph_int_t n) { + for (igraph_int_t i=0; i < n; i++) { + igraph_set_destroy(&array[i]); + } + IGRAPH_FREE(array); +} + +/** + * \function igraph_maximal_independent_vertex_sets + * \brief Finds all maximal independent vertex sets of a graph. + * + * + * A maximal independent vertex set is an independent vertex set which + * can't be extended any more by adding a new vertex to it. + * + * + * The algorithm used here is based on the following paper: + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for + * generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * + * The implementation was originally written by Kevin O'Neill and modified + * by K M Briggs in the Very Nauty Graph Library. I simply re-wrote it to + * use igraph's data structures. + * + * + * If you are interested in the size of the largest independent vertex set, + * use \ref igraph_independence_number() instead. + * + * \param graph The input graph. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \param min_size Integer specifying the minimum size of the sets to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer specifying the maximum size of the sets to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many independent vertex sets will be recorded. + * If negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(), \ref + * igraph_independence_number() + * + * Time complexity: TODO. + */ +igraph_error_t igraph_maximal_independent_vertex_sets( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + + igraph_i_max_ind_vsets_data_t clqdata; + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored during independent vertex set calculations."); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = false; + + if (max_size <= 0) { + max_size = IGRAPH_INTEGER_MAX; + } + + if (max_results < 0) { + max_results = IGRAPH_INTEGER_MAX; + } + + clqdata.min_size = min_size; + clqdata.max_size = max_size; + clqdata.max_results = max_results; + + IGRAPH_CHECK(igraph_adjlist_init( + graph, &clqdata.adj_list, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE + )); + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(clqdata.IS, "Insufficient memory for maximal independent vertex sets."); + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = IGRAPH_CALLOC(no_of_nodes + 1, igraph_set_t); + IGRAPH_CHECK_OOM(clqdata.buckets, "Insufficient memory for maximal independent vertex sets."); + IGRAPH_FINALLY(free_set_array_incomplete, clqdata.buckets); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + igraph_vector_int_list_clear(res); + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, &clqdata, 0)); + + /* Cleanup */ + free_set_array(clqdata.buckets, no_of_nodes); + igraph_vector_int_destroy(&clqdata.deg); + IGRAPH_FREE(clqdata.IS); + igraph_adjlist_destroy(&clqdata.adj_list); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_independence_number + * \brief Finds the independence number of the graph. + * + * + * The independence number of a graph is the cardinality of the largest + * independent vertex set. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param no The independence number will be returned to the \c + * igraph_int_t pointed by this variable. + * \return Error code. + * + * \sa \ref igraph_independent_vertex_sets(). + * + * Time complexity: TODO. + */ +igraph_error_t igraph_independence_number(const igraph_t *graph, igraph_int_t *no) { + igraph_i_max_ind_vsets_data_t clqdata; + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored during independence number calculations."); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = false; + clqdata.max_results = IGRAPH_INTEGER_MAX; + + IGRAPH_CHECK(igraph_adjlist_init( + graph, &clqdata.adj_list, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE + )); + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(clqdata.IS, "Insufficient memory for independence number calculation."); + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = IGRAPH_CALLOC(no_of_nodes + 1, igraph_set_t); + IGRAPH_CHECK_OOM(clqdata.buckets, "Insufficient memory for independence number calculation."); + IGRAPH_FINALLY(free_set_array_incomplete, clqdata.buckets); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, 0, &clqdata, 0)); + *no = clqdata.largest_set_size; + + /* Cleanup */ + free_set_array(clqdata.buckets, no_of_nodes); + igraph_vector_int_destroy(&clqdata.deg); + IGRAPH_FREE(clqdata.IS); + igraph_adjlist_destroy(&clqdata.adj_list); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/*************************************************************************/ +/* MAXIMAL CLIQUES, LARGEST CLIQUES */ +/*************************************************************************/ + +static igraph_error_t igraph_i_maximal_cliques_store_max_size(const igraph_vector_int_t* clique, void* data) { + igraph_int_t* result = (igraph_int_t*)data; + if (*result < igraph_vector_int_size(clique)) { + *result = igraph_vector_int_size(clique); + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_largest_cliques_store(const igraph_vector_int_t* clique, void* data) { + igraph_vector_int_list_t* result = (igraph_vector_int_list_t*)data; + igraph_int_t n; + + /* Is the current clique at least as large as the others that we have found? */ + if (!igraph_vector_int_list_empty(result)) { + igraph_vector_int_t* first; + + n = igraph_vector_int_size(clique); + first = igraph_vector_int_list_get_ptr(result, 0); + if (n < igraph_vector_int_size(first)) { + return IGRAPH_SUCCESS; + } + + if (n > igraph_vector_int_size(first)) { + igraph_vector_int_list_clear(result); + } + } + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(result, clique)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_largest_cliques + * \brief Finds the largest clique(s) in a graph. + * + * + * A clique is largest (quite intuitively) if there is no other clique + * in the graph which contains more vertices. + * + * + * Note that this is not necessarily the same as a maximal clique, + * i.e. the largest cliques are always maximal but a maximal clique is + * not always largest. + * + * The current implementation of this function searches + * for maximal cliques using \ref igraph_maximal_cliques_callback() and drops + * those that are not the largest. + * + * The implementation of this function changed between + * igraph 0.5 and 0.6, so the order of the cliques and the order of + * vertices within the cliques will almost surely be different between + * these two versions. + * + * \param graph The input graph. + * \param res Pointer to an initialized list of integer vectors. The cliques + * will be stored here as vectors of vertex IDs. + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_maximal_cliques() + * + * Time complexity: O(3^(|V|/3)) worst case. + */ + +igraph_error_t igraph_largest_cliques(const igraph_t *graph, igraph_vector_int_list_t *res) { + igraph_vector_int_list_clear(res); + IGRAPH_CHECK(igraph_maximal_cliques_callback(graph, 0, 0, &igraph_i_largest_cliques_store, (void *) res)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_clique_number + * \brief Finds the clique number of the graph. + * + * + * The clique number of a graph is the size of the largest clique. + * + * The current implementation of this function searches + * for maximal cliques using \ref igraph_maximal_cliques_callback() and keeps + * track of the size of the largest clique that was found. + * + * \param graph The input graph. + * \param no The clique number will be returned to the \c igraph_int_t + * pointed by this variable. + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_largest_cliques(). + * + * Time complexity: O(3^(|V|/3)) worst case. + */ +igraph_error_t igraph_clique_number(const igraph_t *graph, igraph_int_t *no) { + *no = 0; + return igraph_maximal_cliques_callback(graph, 0, 0, &igraph_i_maximal_cliques_store_max_size, (void *) no); +} + +static igraph_error_t igraph_i_maximal_or_largest_cliques_or_indsets(const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t *clique_number, + igraph_bool_t keep_only_largest, + igraph_bool_t complementer) { + + igraph_i_max_ind_vsets_data_t clqdata; + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored for largest independent vertex set or clique calculations."); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = keep_only_largest; + clqdata.min_size = 0; + clqdata.max_size = IGRAPH_INTEGER_MAX; + clqdata.max_results = IGRAPH_INTEGER_MAX; + + if (complementer) { + IGRAPH_CHECK(igraph_adjlist_init_complementer(graph, &clqdata.adj_list, IGRAPH_ALL, IGRAPH_NO_LOOPS)); + } else { + IGRAPH_CHECK(igraph_adjlist_init( + graph, &clqdata.adj_list, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE + )); + } + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(clqdata.IS, "Insufficient memory for largest independent sets or cliques."); + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = IGRAPH_CALLOC(no_of_nodes + 1, igraph_set_t); + IGRAPH_CHECK_OOM(clqdata.buckets, "Insufficient memory for largest independent sets or cliques."); + IGRAPH_FINALLY(free_set_array_incomplete, clqdata.buckets); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + if (res) { + igraph_vector_int_list_clear(res); + } + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, &clqdata, 0)); + + /* Cleanup */ + free_set_array(clqdata.buckets, no_of_nodes); + igraph_vector_int_destroy(&clqdata.deg); + igraph_free(clqdata.IS); + igraph_adjlist_destroy(&clqdata.adj_list); + IGRAPH_FINALLY_CLEAN(4); + + if (clique_number) { + *clique_number = clqdata.largest_set_size; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/cliques/glet.c b/src/cliques/glet.c new file mode 100644 index 0000000..3e9092a --- /dev/null +++ b/src/cliques/glet.c @@ -0,0 +1,881 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_graphlets.h" + +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_cliques.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_operators.h" +#include "igraph_qsort.h" +#include "igraph_structural.h" + +/** + * \section graphlets_intro Introduction + * + * + * Graphlet decomposition models a weighted undirected graph + * via the union of potentially overlapping dense social groups. + * This is done by a two-step algorithm. In the first step, a candidate + * set of groups (a candidate basis) is created by finding cliques + * in the thresholded input graph. In the second step, + * the graph is projected onto the candidate basis, resulting in a + * weight coefficient for each clique in the candidate basis. + * + * + * + * For more information on graphlet decomposition, see + * Hossein Azari Soufiani and Edoardo M Airoldi: "Graphlet decomposition of a weighted network", + * https://arxiv.org/abs/1203.2821 and http://proceedings.mlr.press/v22/azari12/azari12.pdf + * + * + * + * igraph contains three functions for performing the graphlet + * decomponsition of a graph. The first is \ref igraph_graphlets(), which + * performs both steps of the method and returns a list of subgraphs + * with their corresponding weights. The other two functions + * correspond to the first and second steps of the algorithm, and they are + * useful if the user wishes to perform them individually: + * \ref igraph_graphlets_candidate_basis() and + * \ref igraph_graphlets_project(). + * + * + * + * + * Note: The term "graphlet" is used for several unrelated concepts + * in the literature. If you are looking to count induced subgraphs, see + * \ref igraph_motifs_randesu() and \ref igraph_subisomorphic_lad(). + * + * + */ + +typedef struct { + igraph_vector_int_t *resultids; + igraph_t *result; + igraph_vector_t *resultweights; + igraph_int_t nc; +} igraph_i_subclique_next_free_t; + +static void igraph_i_subclique_next_free(void *ptr) { + igraph_i_subclique_next_free_t *data = ptr; + igraph_int_t i; + if (data->resultids) { + for (i = 0; i < data->nc; i++) { + igraph_vector_int_destroy(&data->resultids[i]); + } + IGRAPH_FREE(data->resultids); + } + if (data->result) { + for (i = 0; i < data->nc; i++) { + igraph_destroy(&data->result[i]); + } + IGRAPH_FREE(data->result); + } + if (data->resultweights) { + for (i = 0; i < data->nc; i++) { + igraph_vector_destroy(&data->resultweights[i]); + } + IGRAPH_FREE(data->resultweights); + } +} + +/** + * \function igraph_i_subclique_next + * Calculate subcliques of the cliques found at the previous level + * + * \param graph Input graph. + * \param weight Edge weights. + * \param ids The IDs of the vertices in the input graph. + * \param cliques A list of \ref igraph_vector_int_t, vertex IDs for cliques. + * \param result The result is stored here, a list of graphs is stored + * here. + * \param resultids The IDs of the vertices in the result graphs is + * stored here. + * \param clique_thr The thresholds for the cliques are stored here, + * if not a null pointer. + * \param next_thr The next thresholds for the cliques are stored + * here, if not a null pointer. + * + */ + +static igraph_error_t igraph_i_subclique_next(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_int_t *ids, + const igraph_vector_int_list_t *cliques, + igraph_t **result, + igraph_vector_t **resultweights, + igraph_vector_int_t **resultids, + igraph_vector_t *clique_thr, + igraph_vector_t *next_thr) { + + /* The input is a set of cliques, that were found at a previous level. + For each clique, we calculate the next threshold, drop the isolate + vertices, and create a new graph from them. */ + + igraph_vector_int_t mark, map; + igraph_vector_int_t edges; + igraph_vector_int_t neis, newedges; + igraph_int_t c, nc = igraph_vector_int_list_size(cliques); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_i_subclique_next_free_t freedata = { NULL, NULL, NULL, nc }; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weight vector", IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(ids) != no_of_nodes) { + IGRAPH_ERROR("Invalid length of ID vector", IGRAPH_EINVAL); + } + + IGRAPH_FINALLY(igraph_i_subclique_next_free, &freedata); + + *resultids = IGRAPH_CALLOC(nc, igraph_vector_int_t); + IGRAPH_CHECK_OOM(*resultids, "Cannot calculate next cliques."); + freedata.resultids = *resultids; + + *resultweights = IGRAPH_CALLOC(nc, igraph_vector_t); + IGRAPH_CHECK_OOM(*resultweights, "Cannot calculate next cliques."); + freedata.resultweights = *resultweights; + + *result = IGRAPH_CALLOC(nc, igraph_t); + IGRAPH_CHECK_OOM(*result, "Cannot calculate next cliques."); + freedata.result = *result; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&newedges, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&mark, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&map, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 10); + + if (clique_thr) { + IGRAPH_CHECK(igraph_vector_resize(clique_thr, nc)); + } + if (next_thr) { + IGRAPH_CHECK(igraph_vector_resize(next_thr, nc)); + } + + /* Iterate over all cliques. We will create graphs for all + subgraphs defined by the cliques. */ + + for (c = 0; c < nc; c++) { + igraph_vector_int_t *clique = igraph_vector_int_list_get_ptr(cliques, c); + igraph_real_t minweight = IGRAPH_INFINITY, nextweight = IGRAPH_INFINITY; + igraph_int_t e, v, clsize = igraph_vector_int_size(clique); + igraph_int_t noe, nov = 0; + igraph_vector_int_t *newids = (*resultids) + c; + igraph_vector_t *neww = (*resultweights) + c; + igraph_t *newgraph = (*result) + c; + + igraph_vector_int_clear(&edges); + igraph_vector_int_clear(&newedges); + + /* --------------------------------------------------- */ + + /* Iterate over the vertices of a clique and find the + edges within the clique, put them in a list. + At the same time, search for the minimum edge weight within + the clique and the next edge weight if any. */ + + for (v = 0; v < clsize; v++) { + igraph_int_t i, neilen, node = VECTOR(*clique)[v]; + IGRAPH_CHECK(igraph_incident(graph, &neis, node, IGRAPH_ALL, IGRAPH_LOOPS)); + neilen = igraph_vector_int_size(&neis); + VECTOR(mark)[node] = c + 1; + for (i = 0; i < neilen; i++) { + igraph_int_t edge = VECTOR(neis)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, node); + if (VECTOR(mark)[nei] == c + 1) { + igraph_real_t w = VECTOR(*weights)[edge]; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, edge)); + if (w < minweight) { + nextweight = minweight; + minweight = w; + } else if (w > minweight && w < nextweight) { + nextweight = w; + } + } + } + } /* v < clsize */ + + /* --------------------------------------------------- */ + + /* OK, we have stored the edges and found the weight of + the clique and the next weight to consider */ + + if (clique_thr) { + VECTOR(*clique_thr)[c] = minweight; + } + if (next_thr) { + VECTOR(*next_thr )[c] = nextweight; + } + + /* --------------------------------------------------- */ + + /* Now we create the subgraph from the edges above the next + threshold, and their incident vertices. */ + + IGRAPH_CHECK(igraph_vector_int_init(newids, 0)); + IGRAPH_CHECK(igraph_vector_init(neww, 0)); + + /* We use mark[] to denote the vertices already mapped to + the new graph. If this is -(c+1), then the vertex was + mapped, otherwise it was not. The mapping itself is in + map[]. */ + + noe = igraph_vector_int_size(&edges); + for (e = 0; e < noe; e++) { + igraph_int_t edge = VECTOR(edges)[e]; + igraph_int_t from, to; + igraph_real_t w = VECTOR(*weights)[edge]; + IGRAPH_CHECK(igraph_edge(graph, edge, &from, &to)); + if (w >= nextweight) { + if (VECTOR(mark)[from] == c + 1) { + VECTOR(map)[from] = nov++; + VECTOR(mark)[from] = -(c + 1); + IGRAPH_CHECK(igraph_vector_int_push_back(newids, VECTOR(*ids)[from])); + } + if (VECTOR(mark)[to] == c + 1) { + VECTOR(map)[to] = nov++; + VECTOR(mark)[to] = -(c + 1); + IGRAPH_CHECK(igraph_vector_int_push_back(newids, VECTOR(*ids)[to])); + } + IGRAPH_CHECK(igraph_vector_push_back(neww, w)); + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, VECTOR(map)[from])); + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, VECTOR(map)[to])); + } + } + + IGRAPH_CHECK(igraph_create(newgraph, &newedges, nov, IGRAPH_UNDIRECTED)); + + /* --------------------------------------------------- */ + + } /* c < nc */ + + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&mark); + igraph_vector_int_destroy(&map); + igraph_vector_int_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(6); /* + freedata */ + + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphlets_destroy_clique_list(igraph_vector_ptr_t *vl) { + igraph_int_t i, n = igraph_vector_ptr_size(vl); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = (igraph_vector_int_t*) VECTOR(*vl)[i]; + if (v) { + igraph_vector_int_destroy(v); + IGRAPH_FREE(v); + } + } + igraph_vector_ptr_destroy(vl); +} + +static igraph_error_t igraph_i_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds, + const igraph_vector_int_t *ids, + igraph_real_t startthr) { + + /* This version is different from the main function, and is + appropriate to use in recursive calls, because it _adds_ the + results to 'cliques' and 'thresholds' and uses the supplied + 'startthr' */ + + igraph_vector_int_list_t mycliques; + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t subv; + igraph_t subg; + igraph_t *newgraphs = NULL; + igraph_vector_t *newweights = NULL; + igraph_vector_int_t *newids = NULL; + igraph_vector_t clique_thr, next_thr; + igraph_i_subclique_next_free_t freedata = { NULL, NULL, NULL, 0 }; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&mycliques, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&subv, 0); + + /* We start by finding cliques at the lowest threshold */ + for (igraph_int_t i = 0; i < no_of_edges; i++) { + if (VECTOR(*weights)[i] >= startthr) { + IGRAPH_CHECK(igraph_vector_int_push_back(&subv, i)); + } + } + IGRAPH_CHECK(igraph_subgraph_from_edges(graph, &subg, igraph_ess_vector(&subv), /*delete_vertices=*/ 0)); + IGRAPH_FINALLY(igraph_destroy, &subg); + IGRAPH_CHECK(igraph_maximal_cliques(&subg, &mycliques, /*min_size=*/ 0, /*max_size=*/ 0, IGRAPH_UNLIMITED)); + igraph_destroy(&subg); + igraph_vector_int_destroy(&subv); + IGRAPH_FINALLY_CLEAN(2); + + const igraph_int_t nocliques = igraph_vector_int_list_size(&mycliques); + + /* Get the next cliques and thresholds */ + IGRAPH_VECTOR_INIT_FINALLY(&next_thr, 0); + IGRAPH_VECTOR_INIT_FINALLY(&clique_thr, 0); + + IGRAPH_CHECK(igraph_i_subclique_next( + graph, weights, ids, &mycliques, &newgraphs, &newweights, &newids, + &clique_thr, &next_thr + )); + + freedata.result = newgraphs; + freedata.resultids = newids; + freedata.resultweights = newweights; + freedata.nc = nocliques; + IGRAPH_FINALLY(igraph_i_subclique_next_free, &freedata); + + /* Store cliques at the current level */ + + IGRAPH_CHECK(igraph_vector_append(thresholds, &clique_thr)); + + igraph_vector_destroy(&clique_thr); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_ptr_resize(cliques, igraph_vector_ptr_size(cliques) + nocliques)); + for (igraph_int_t i = 0, j = igraph_vector_ptr_size(cliques) - 1; i < nocliques; i++, j--) { + igraph_vector_int_t *cl = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(cl, "Cannot find graphlets."); + IGRAPH_FINALLY(igraph_free, cl); + + *cl = igraph_vector_int_list_pop_back(&mycliques); + + /* From this point onwards, _we_ own the clique and not `mycliques'. + * We pass on the ownership to `cliques' */ + VECTOR(*cliques)[j] = cl; + IGRAPH_FINALLY_CLEAN(1); + + const igraph_int_t n = igraph_vector_int_size(cl); + for (igraph_int_t k = 0; k < n; k++) { + igraph_int_t node = VECTOR(*cl)[k]; + VECTOR(*cl)[k] = VECTOR(*ids)[node]; + } + igraph_vector_int_sort(cl); + } + + igraph_vector_int_list_destroy(&mycliques); /* contents was copied over to `cliques' */ + IGRAPH_FINALLY_CLEAN(1); + + /* Recursive calls for cliques found */ + for (igraph_int_t i = 0; i < nocliques; i++) { + igraph_t *g = newgraphs + i; + if (igraph_vcount(g) > 1) { + igraph_vector_t *w_sub = newweights + i; + igraph_vector_int_t *ids_sub = newids + i; + IGRAPH_CHECK(igraph_i_graphlets(g, w_sub, cliques, thresholds, ids_sub, VECTOR(next_thr)[i])); + } + } + + igraph_vector_destroy(&next_thr); + igraph_i_subclique_next_free(&freedata); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +typedef struct { + const igraph_vector_ptr_t *cliques; + const igraph_vector_t *thresholds; +} igraph_i_graphlets_filter_t; + +static int igraph_i_graphlets_filter_cmp(void *data, const void *a, const void *b) { + igraph_i_graphlets_filter_t *ddata = (igraph_i_graphlets_filter_t *) data; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t t_a = VECTOR(*ddata->thresholds)[*aa]; + igraph_real_t t_b = VECTOR(*ddata->thresholds)[*bb]; + igraph_vector_int_t *v_a, *v_b; + igraph_int_t s_a, s_b; + + if (t_a < t_b) { + return -1; + } else if (t_a > t_b) { + return 1; + } + + v_a = (igraph_vector_int_t*) VECTOR(*ddata->cliques)[*aa]; + v_b = (igraph_vector_int_t*) VECTOR(*ddata->cliques)[*bb]; + s_a = igraph_vector_int_size(v_a); + s_b = igraph_vector_int_size(v_b); + + if (s_a < s_b) { + return -1; + } else if (s_a > s_b) { + return 1; + } else { + return 0; + } +} + +static igraph_error_t igraph_i_graphlets_filter(igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds) { + + /* Filter out non-maximal cliques. Every non-maximal clique is + part of a maximal clique, at the same threshold. + + First we order the cliques, according to their threshold, and + then according to their size. So when we look for a candidate + superset, we only need to check the cliques next in the list, + until their threshold is different. */ + + igraph_int_t i, iptr, nocliques = igraph_vector_ptr_size(cliques); + igraph_vector_int_t order; + igraph_i_graphlets_filter_t sortdata = { cliques, thresholds }; + + IGRAPH_CHECK(igraph_vector_int_init_range(&order, 0, nocliques)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order); + + igraph_qsort_r(VECTOR(order), nocliques, sizeof(VECTOR(order)[0]), &sortdata, + igraph_i_graphlets_filter_cmp); + + for (i = 0; i < nocliques - 1; i++) { + igraph_int_t ri = VECTOR(order)[i]; + igraph_vector_int_t *needle = VECTOR(*cliques)[ri]; + igraph_real_t thr_i = VECTOR(*thresholds)[ri]; + igraph_int_t n_i = igraph_vector_int_size(needle); + + for (igraph_int_t j = i + 1; j < nocliques; j++) { + igraph_int_t rj = VECTOR(order)[j]; + igraph_real_t thr_j = VECTOR(*thresholds)[rj]; + igraph_vector_int_t *hay; + igraph_int_t n_j, pi = 0, pj = 0; + + /* Done, not found */ + if (thr_j != thr_i) { + break; + } + + /* Check size of hay */ + hay = VECTOR(*cliques)[rj]; + n_j = igraph_vector_int_size(hay); + if (n_i > n_j) { + continue; + } + + /* Check if hay is a superset */ + while (pi < n_i && pj < n_j && n_i - pi <= n_j - pj) { + igraph_int_t ei = VECTOR(*needle)[pi]; + igraph_int_t ej = VECTOR(*hay)[pj]; + if (ei < ej) { + break; + } else if (ei > ej) { + pj++; + } else { + pi++; pj++; + } + } + if (pi == n_i) { + /* Found, delete immediately */ + igraph_vector_int_destroy(needle); + igraph_free(needle); + VECTOR(*cliques)[ri] = 0; + break; + } + } + } + + /* Remove null pointers from the list of cliques */ + for (i = 0, iptr = 0; i < nocliques; i++) { + igraph_vector_int_t *v = VECTOR(*cliques)[i]; + if (v) { + VECTOR(*cliques)[iptr] = v; + VECTOR(*thresholds)[iptr] = VECTOR(*thresholds)[i]; + iptr++; + } + } + IGRAPH_CHECK(igraph_vector_ptr_resize(cliques, iptr)); + IGRAPH_CHECK(igraph_vector_resize(thresholds, iptr)); + + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_graphlets_candidate_basis + * Calculate a candidate graphlets basis + * + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges, a vector. + * \param cliques An initialized list of integer vectors. The graphlet basis is + * stored here. Each element of the list is an integer vector of + * vertex IDs, encoding a single basis subgraph. + * \param thresholds An initialized vector, the (highest possible) + * weight thresholds for finding the basis subgraphs are stored + * here. + * \return Error code. + * + * See also: \ref igraph_graphlets() and \ref igraph_graphlets_project(). + */ + +igraph_error_t igraph_graphlets_candidate_basis(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *thresholds) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_real_t minthr; + igraph_vector_int_t ids; + igraph_bool_t simple; + igraph_int_t i, no_of_cliques; + igraph_vector_ptr_t mycliques; + + /* Some checks */ + if (weights == NULL) { + IGRAPH_ERROR("Graphlet functions require weighted graphs", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &simple, IGRAPH_UNDIRECTED)); + if (!simple) { + IGRAPH_ERROR("Graphlets work on simple graphs only", IGRAPH_EINVAL); + } + + /* Internally, we will still use igraph_vector_ptr_t instead of + * igraph_vector_int_list_t to manage the list of cliques; this is because + * we are going to append & filter the list and it's more complicated to + * do with an igraph_vector_int_list_t */ + IGRAPH_CHECK(igraph_vector_ptr_init(&mycliques, 0)); + IGRAPH_FINALLY(igraph_i_graphlets_destroy_clique_list, &mycliques); + + igraph_vector_int_list_clear(cliques); + igraph_vector_clear(thresholds); + + minthr = igraph_vector_min(weights); + + IGRAPH_CHECK(igraph_vector_int_init_range(&ids, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &ids); + + IGRAPH_CHECK(igraph_i_graphlets(graph, weights, &mycliques, thresholds, &ids, minthr)); + + igraph_vector_int_destroy(&ids); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_i_graphlets_filter(&mycliques, thresholds)); + + /* Pass ownership of cliques in `mycliques' to `cliques' so the user does + * not have to work with igraph_vector_ptr_t */ + no_of_cliques = igraph_vector_ptr_size(&mycliques); + for (i = 0; i < no_of_cliques; i++) { + IGRAPH_CHECK(igraph_vector_int_list_push_back( + cliques, VECTOR(mycliques)[i] + )); + IGRAPH_FREE(VECTOR(mycliques)[i]); + } + + /* `mycliques' is now empty so we can clear and destroy */ + igraph_vector_ptr_clear(&mycliques); + igraph_vector_ptr_destroy(&mycliques); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* TODO: not made static because it is used by the R interface */ +igraph_error_t igraph_i_graphlets_project( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_int_list_t *cliques, igraph_vector_t *Mu, igraph_bool_t startMu, + igraph_int_t niter, igraph_int_t vid1 +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_cliques = igraph_vector_int_list_size(cliques); + igraph_vector_int_t vcl, vclidx, ecl, eclidx, cel, celidx; + igraph_vector_int_t edgelist; + igraph_vector_t newweights, normfact; + igraph_int_t i, total_vertices, e, ptr, total_edges; + igraph_bool_t simple; + + /* Check arguments */ + if (weights == NULL) { + IGRAPH_ERROR("Graphlet functions require weighted graphs", IGRAPH_EINVAL); + } + if (no_of_edges != igraph_vector_size(weights)) { + IGRAPH_ERROR("Invalid weight vector size", IGRAPH_EINVAL); + } + if (startMu && igraph_vector_size(Mu) != no_cliques) { + IGRAPH_ERROR("Invalid start coefficient vector size", IGRAPH_EINVAL); + } + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_is_simple(graph, &simple, IGRAPH_UNDIRECTED)); + if (!simple) { + IGRAPH_ERROR("Graphlets work on simple graphs only", IGRAPH_EINVAL); + } + + if (!startMu) { + IGRAPH_CHECK(igraph_vector_resize(Mu, no_cliques)); + igraph_vector_fill(Mu, 1); + } + + /* Count # cliques per vertex. Also, create an index + for the edges per clique. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&vclidx, no_of_nodes + 2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&celidx, no_cliques + 3); + for (i = 0, total_vertices = 0, total_edges = 0; i < no_cliques; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(cliques, i); + igraph_int_t j, n = igraph_vector_int_size(v); + total_vertices += n; + total_edges += n * (n - 1) / 2; + VECTOR(celidx)[i + 2] = total_edges; + for (j = 0; j < n; j++) { + igraph_int_t vv = VECTOR(*v)[j] - vid1; + VECTOR(vclidx)[vv + 2] += 1; + } + } + VECTOR(celidx)[i + 2] = total_edges; + + /* Finalize index vector */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(vclidx)[i + 2] += VECTOR(vclidx)[i + 1]; + } + + /* Create vertex-clique list, the cliques for each vertex. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&vcl, total_vertices); + for (i = 0; i < no_cliques; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(cliques, i); + igraph_int_t j, n = igraph_vector_int_size(v); + for (j = 0; j < n; j++) { + igraph_int_t vv = VECTOR(*v)[j] - vid1; + igraph_int_t p = VECTOR(vclidx)[vv + 1]; + VECTOR(vcl)[p] = i; + VECTOR(vclidx)[vv + 1] += 1; + } + } + + /* Create an edge-clique list, the cliques of each edge */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&ecl, total_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eclidx, no_of_edges + 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edgelist, no_of_edges * 2); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edgelist, /*by_col=*/ 0)); + for (i = 0, e = 0, ptr = 0; e < no_of_edges; e++) { + igraph_int_t from = VECTOR(edgelist)[i++]; + igraph_int_t to = VECTOR(edgelist)[i++]; + igraph_int_t from_s = VECTOR(vclidx)[from]; + igraph_int_t from_e = VECTOR(vclidx)[from + 1]; + igraph_int_t to_s = VECTOR(vclidx)[to]; + igraph_int_t to_e = VECTOR(vclidx)[to + 1]; + VECTOR(eclidx)[e] = ptr; + while (from_s < from_e && to_s < to_e) { + igraph_int_t from_v = VECTOR(vcl)[from_s]; + igraph_int_t to_v = VECTOR(vcl)[to_s]; + if (from_v == to_v) { + VECTOR(ecl)[ptr++] = from_v; + from_s++; to_s++; + } else if (from_v < to_v) { + from_s++; + } else { + to_s++; + } + } + } + VECTOR(eclidx)[e] = ptr; + + igraph_vector_int_destroy(&edgelist); + IGRAPH_FINALLY_CLEAN(1); + + /* Convert the edge-clique list to a clique-edge list */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&cel, total_edges); + for (i = 0; i < no_of_edges; i++) { + igraph_int_t ecl_s = VECTOR(eclidx)[i], ecl_e = VECTOR(eclidx)[i + 1], j; + for (j = ecl_s; j < ecl_e; j++) { + igraph_int_t cl = VECTOR(ecl)[j]; + igraph_int_t epos = VECTOR(celidx)[cl + 1]; + VECTOR(cel)[epos] = i; + VECTOR(celidx)[cl + 1] += 1; + } + } + + /* Normalizing factors for the iteration */ + IGRAPH_VECTOR_INIT_FINALLY(&normfact, no_cliques); + for (i = 0; i < no_cliques; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(cliques, i); + igraph_int_t n = igraph_vector_int_size(v); + VECTOR(normfact)[i] = n * (n + 1) / 2; + } + + /* We have the clique-edge list, so do the projection now */ + IGRAPH_VECTOR_INIT_FINALLY(&newweights, no_of_edges); + for (i = 0; i < niter; i++) { + for (e = 0; e < no_of_edges; e++) { + igraph_int_t start = VECTOR(eclidx)[e]; + igraph_int_t end = VECTOR(eclidx)[e + 1]; + VECTOR(newweights)[e] = 0.0001; + while (start < end) { + igraph_int_t clique = VECTOR(ecl)[start++]; + VECTOR(newweights)[e] += VECTOR(*Mu)[clique]; + } + } + for (e = 0; e < no_cliques; e++) { + igraph_real_t sumratio = 0; + igraph_int_t start = VECTOR(celidx)[e]; + igraph_int_t end = VECTOR(celidx)[e + 1]; + while (start < end) { + igraph_int_t edge = VECTOR(cel)[start++]; + sumratio += VECTOR(*weights)[edge] / VECTOR(newweights)[edge]; + } + VECTOR(*Mu)[e] *= sumratio / VECTOR(normfact)[e]; + } + } + + igraph_vector_destroy(&newweights); + igraph_vector_destroy(&normfact); + igraph_vector_int_destroy(&cel); + igraph_vector_int_destroy(&eclidx); + igraph_vector_int_destroy(&ecl); + igraph_vector_int_destroy(&vcl); + igraph_vector_int_destroy(&celidx); + igraph_vector_int_destroy(&vclidx); + IGRAPH_FINALLY_CLEAN(8); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_graphlets_project + * \brief Project a graph on a graphlets basis. + * + * Note that the graph projected does not have to be the same that + * was used to calculate the graphlet basis, but it is assumed that + * it has the same number of vertices, and the vertex IDs of the two + * graphs match. + * + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges in the input graph, a vector. + * \param cliques An initialized list of integer vectors. The graphlet basis is + * stored here. Each element of the list is an integer vector of + * vertex IDs, encoding a single basis subgraph. + * \param Mu An initialized vector, the weights of the graphlets will + * be stored here. This vector is also used to initialize the + * the weight vector for the iterative algorithm, if the + * \c startMu argument is true. + * \param startMu If true, then the supplied Mu vector is + * used as the starting point of the iteration. Otherwise a + * constant 1 vector is used. + * \param niter The number of iterations to perform. + * \return Error code. + * + * See also: \ref igraph_graphlets() and + * \ref igraph_graphlets_candidate_basis(). + */ + +igraph_error_t igraph_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + igraph_int_t niter) { + + return igraph_i_graphlets_project(graph, weights, cliques, Mu, startMu, + niter, /*vid1=*/ 0); +} + +typedef struct igraph_i_graphlets_order_t { + const igraph_vector_int_list_t *cliques; + const igraph_vector_t *Mu; +} igraph_i_graphlets_order_t; + +static int igraph_i_graphlets_order_cmp(void *data, const void *a, const void *b) { + igraph_i_graphlets_order_t *ddata = (igraph_i_graphlets_order_t*) data; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t Mu_a = VECTOR(*ddata->Mu)[*aa]; + igraph_real_t Mu_b = VECTOR(*ddata->Mu)[*bb]; + + if (Mu_a < Mu_b) { + return 1; + } else if (Mu_a > Mu_b) { + return -1; + } else { + return 0; + } +} + +/** + * \function igraph_graphlets + * \brief Calculate graphlets basis and project the graph on it. + * + * This function simply calls \ref igraph_graphlets_candidate_basis() + * and \ref igraph_graphlets_project(), and then orders the graphlets + * according to decreasing weights. + * + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges, a vector. + * \param cliques An initialized list of integer vectors. The graphlet basis is + * stored here. Each element of the list is an integer vector of + * vertex IDs, encoding a single basis subgraph. + * \param Mu An initialized vector, the weights of the graphlets will + * be stored here. + * \param niter The number of iterations to perform for the projection step. + * \return Error code. + * + * See also: \ref igraph_graphlets_candidate_basis() and + * \ref igraph_graphlets_project(). + */ + +igraph_error_t igraph_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *cliques, + igraph_vector_t *Mu, igraph_int_t niter) { + + igraph_int_t nocliques; + igraph_vector_t thresholds; + igraph_vector_int_t order; + igraph_i_graphlets_order_t sortdata = { cliques, Mu }; + + IGRAPH_VECTOR_INIT_FINALLY(&thresholds, 0); + IGRAPH_CHECK(igraph_graphlets_candidate_basis(graph, weights, cliques, &thresholds)); + igraph_vector_destroy(&thresholds); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_graphlets_project(graph, weights, cliques, Mu, /*startMu=*/ false, niter)); + + nocliques = igraph_vector_int_list_size(cliques); + IGRAPH_CHECK(igraph_vector_int_init_range(&order, 0, nocliques)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order); + + igraph_qsort_r(VECTOR(order), nocliques, sizeof(VECTOR(order)[0]), &sortdata, + igraph_i_graphlets_order_cmp); + + IGRAPH_CHECK(igraph_vector_int_list_permute(cliques, &order)); + IGRAPH_CHECK(igraph_vector_index_in_place(Mu, &order)); + + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/cliques/maximal_cliques.c b/src/cliques/maximal_cliques.c new file mode 100644 index 0000000..c841cde --- /dev/null +++ b/src/cliques/maximal_cliques.c @@ -0,0 +1,619 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cliques.h" + +#include "igraph_adjlist.h" +#include "igraph_constants.h" +#include "igraph_community.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_progress.h" + +#include "core/interruption.h" + +#define CONCAT2x(a,b) a ## b +#define CONCAT2(a,b) CONCAT2x(a,b) +#define FUNCTION(name,sfx) CONCAT2(name,sfx) + +static igraph_error_t igraph_i_maximal_cliques_reorder_adjlists( + const igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t PE, igraph_int_t XS, igraph_int_t XE, + const igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist); + +static igraph_error_t igraph_i_maximal_cliques_select_pivot( + const igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t PE, igraph_int_t XS, igraph_int_t XE, + const igraph_vector_int_t *pos, + const igraph_adjlist_t *adjlist, + igraph_int_t *pivot, + igraph_vector_int_t *nextv, + igraph_int_t oldPS, igraph_int_t oldXE); + +static igraph_error_t igraph_i_maximal_cliques_down( + igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t PE, igraph_int_t XS, igraph_int_t XE, + igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, igraph_int_t mynextv, + igraph_vector_int_t *R, + igraph_int_t *newPS, igraph_int_t *newXE); + +static igraph_error_t igraph_i_maximal_cliques_PX( + igraph_vector_int_t *PX, igraph_int_t PS, igraph_int_t *PE, + igraph_int_t *XS, igraph_int_t XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, igraph_int_t v, + igraph_vector_int_t *H); + +static igraph_error_t igraph_i_maximal_cliques_up( + igraph_vector_int_t *PX, igraph_int_t PS, igraph_int_t PE, + igraph_int_t XS, igraph_int_t XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, + igraph_vector_int_t *R, + igraph_vector_int_t *H); + +#define PRINT_PX do { \ + igraph_int_t j; \ + printf("PX="); \ + for (j=0; j= sPS && avneipos <= sPE) { + if (pp != avnei) { + igraph_int_t tmp = *avnei; + *avnei = *pp; + *pp = tmp; + } + pp++; + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_maximal_cliques_select_pivot( + const igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t PE, + igraph_int_t XS, igraph_int_t XE, + const igraph_vector_int_t *pos, + const igraph_adjlist_t *adjlist, + igraph_int_t *pivot, + igraph_vector_int_t *nextv, + igraph_int_t oldPS, igraph_int_t oldXE) { + igraph_vector_int_t *pivotvectneis; + igraph_int_t j, pivotvectlen; + igraph_int_t i, usize = -1; + igraph_int_t soldPS = oldPS + 1, soldXE = oldXE + 1, sPS = PS + 1, sPE = PE + 1; + + IGRAPH_UNUSED(XS); + + /* Choose a pivotvect, and bring up P vertices at the same time */ + for (i = PS; i <= XE; i++) { + igraph_int_t av = VECTOR(*PX)[i]; + igraph_vector_int_t *avneis = igraph_adjlist_get(adjlist, av); + igraph_int_t *avp = VECTOR(*avneis); + igraph_int_t avlen = igraph_vector_int_size(avneis); + igraph_int_t *ave = avp + avlen; + igraph_int_t *avnei = avp, *pp = avp; + + for (; avnei < ave; avnei++) { + igraph_int_t avneipos = VECTOR(*pos)[(*avnei)]; + if (avneipos < soldPS || avneipos > soldXE) { + break; + } + if (avneipos >= sPS && avneipos <= sPE) { + if (pp != avnei) { + igraph_int_t tmp = *avnei; + *avnei = *pp; + *pp = tmp; + } + pp++; + } + } + if ((j = pp - avp) > usize) { + *pivot = av; + usize = j; + } + } + + IGRAPH_CHECK(igraph_vector_int_push_back(nextv, -1)); + pivotvectneis = igraph_adjlist_get(adjlist, *pivot); + pivotvectlen = igraph_vector_int_size(pivotvectneis); + + for (j = PS; j <= PE; j++) { + igraph_int_t vcand = VECTOR(*PX)[j]; + igraph_bool_t nei = false; + igraph_int_t k = 0; + for (k = 0; k < pivotvectlen; k++) { + igraph_int_t unv = VECTOR(*pivotvectneis)[k]; + igraph_int_t unvpos = VECTOR(*pos)[unv]; + if (unvpos < sPS || unvpos > sPE) { + break; + } + if (unv == vcand) { + nei = true; + break; + } + } + if (!nei) { + IGRAPH_CHECK(igraph_vector_int_push_back(nextv, vcand)); + } + } + + return IGRAPH_SUCCESS; +} + +#define SWAP(p1,p2) do { \ + igraph_int_t v1=VECTOR(*PX)[p1]; \ + igraph_int_t v2=VECTOR(*PX)[p2]; \ + VECTOR(*PX)[p1] = v2; \ + VECTOR(*PX)[p2] = v1; \ + VECTOR(*pos)[v1] = (p2)+1; \ + VECTOR(*pos)[v2] = (p1)+1; \ + } while (0) + +static igraph_error_t igraph_i_maximal_cliques_down(igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t PE, + igraph_int_t XS, igraph_int_t XE, + igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, igraph_int_t mynextv, + igraph_vector_int_t *R, + igraph_int_t *newPS, igraph_int_t *newXE) { + + igraph_vector_int_t *vneis = igraph_adjlist_get(adjlist, mynextv); + igraph_int_t j, vneislen = igraph_vector_int_size(vneis); + igraph_int_t sPS = PS + 1, sPE = PE + 1, sXS = XS + 1, sXE = XE + 1; + + *newPS = PE + 1; *newXE = XS - 1; + for (j = 0; j < vneislen; j++) { + igraph_int_t vnei = VECTOR(*vneis)[j]; + igraph_int_t vneipos = VECTOR(*pos)[vnei]; + if (vneipos >= sPS && vneipos <= sPE) { + (*newPS)--; + SWAP(vneipos - 1, *newPS); + } else if (vneipos >= sXS && vneipos <= sXE) { + (*newXE)++; + SWAP(vneipos - 1, *newXE); + } + } + + IGRAPH_CHECK(igraph_vector_int_push_back(R, mynextv)); + + return IGRAPH_SUCCESS; +} + +#undef SWAP + +static igraph_error_t igraph_i_maximal_cliques_PX(igraph_vector_int_t *PX, + igraph_int_t PS, igraph_int_t *PE, igraph_int_t *XS, igraph_int_t XE, + igraph_vector_int_t *pos, igraph_adjlist_t *adjlist, igraph_int_t v, + igraph_vector_int_t *H +) { + + igraph_int_t vpos = VECTOR(*pos)[v] - 1; + igraph_int_t tmp = VECTOR(*PX)[*PE]; + + IGRAPH_UNUSED(PS); + IGRAPH_UNUSED(XE); + IGRAPH_UNUSED(adjlist); + + VECTOR(*PX)[vpos] = tmp; + VECTOR(*PX)[*PE] = v; + VECTOR(*pos)[v] = (*PE) + 1; + VECTOR(*pos)[tmp] = vpos + 1; + (*PE)--; (*XS)--; + IGRAPH_CHECK(igraph_vector_int_push_back(H, v)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_maximal_cliques_up( + igraph_vector_int_t *PX, igraph_int_t PS, igraph_int_t PE, + igraph_int_t XS, igraph_int_t XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, + igraph_vector_int_t *R, + igraph_vector_int_t *H +) { + igraph_int_t vv; + + IGRAPH_UNUSED(PS); + IGRAPH_UNUSED(PE); + IGRAPH_UNUSED(XE); + IGRAPH_UNUSED(adjlist); + + igraph_vector_int_pop_back(R); + + while ((vv = igraph_vector_int_pop_back(H)) != -1) { + igraph_int_t vvpos = VECTOR(*pos)[vv]; + igraph_int_t tmp = VECTOR(*PX)[XS]; + VECTOR(*PX)[XS] = vv; + VECTOR(*PX)[vvpos - 1] = tmp; + VECTOR(*pos)[vv] = XS + 1; + VECTOR(*pos)[tmp] = vvpos; + PE++; XS++; + } + + return IGRAPH_SUCCESS; +} + + +/* igraph_maximal_cliques */ + +igraph_error_t igraph_i_maximal_cliques( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_ORIG +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_ORIG + +/** + * \function igraph_maximal_cliques + * \brief Finds all maximal cliques in a graph. + * + * This function lists maximal cliques within a size range, ignoring edge + * directions. A clique is a subset of vertices in which all vertex pairs are + * connected. A \em maximal clique is a clique which is not a strict subset + * of any larger clique. + * + * + * No guarantees are given about the order in which cliques are returned. + * + * + * The current implementation uses a modified Bron-Kerbosch algorithm due to + * Eppstein, Löffler and Strash. + * + * + * Reference: + * + * + * David Eppstein, Maarten Löffler, Darren Strash: + * Listing All Maximal Cliques in Sparse Graphs in Near-Optimal Time. + * Algorithms and Computation, Lecture Notes in Computer Science, + * volume 6506, pp 403-414 (2010). + * https://doi.org/10.1007/978-3-642-17517-6_36 + * https://arxiv.org/abs/1006.5440 + * + * \param graph The input graph. Edge directions are ignored. + * \param res Pointer to list of integer vectors. The maximal cliques + * will be returned here as vectors of vertex IDs. Note that vertices + * of a clique may be returned in arbitrary order. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many cliques will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_maximal_independent_vertex_sets() to find maximal + * independent sets, which are cliques of the complement graph; + * \ref igraph_clique_number() to find the size of the largest clique; + * \ref igraph_cliques() to find all cliques. + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + * \example examples/simple/igraph_maximal_cliques.c + */ + +igraph_error_t igraph_maximal_cliques( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + return igraph_i_maximal_cliques(graph, res, min_size, max_size, max_results); +} + + +/* igraph_maximal_cliques_count */ + +igraph_error_t igraph_i_maximal_cliques_count( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_COUNT +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_COUNT + +/** + * \function igraph_maximal_cliques_count + * \brief Count the number of maximal cliques in a graph. + * + * See \ref igraph_maximal_cliques() for details. + * + * \param graph The input graph. Edge directions are ignored. + * \param res Pointer to an \c igraph_int_t; the number of maximal + * cliques will be stored here. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + * \example examples/simple/igraph_maximal_cliques.c + */ + +igraph_error_t igraph_maximal_cliques_count( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t min_size, igraph_int_t max_size) { + return igraph_i_maximal_cliques_count(graph, res, min_size, max_size, IGRAPH_UNLIMITED); +} + +/* igraph_maximal_cliques_file */ + +igraph_error_t igraph_i_maximal_cliques_file( + const igraph_t *graph, + FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_FILE +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_FILE + +/** + * \function igraph_maximal_cliques_file + * \brief Find maximal cliques and write them to a file. + * + * This function enumerates all maximal cliques within a size range + * and writes them to file. See \ref igraph_maximal_cliques() for + * details + * + * \param graph The input graph. Edge directions are ignored. + * \param outfile Pointer to the output file, it should be writable. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many cliques will be output. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs.* + * + */ + +igraph_error_t igraph_maximal_cliques_file( + const igraph_t *graph, + FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + return igraph_i_maximal_cliques_file(graph, outfile, min_size, max_size, max_results); +} + + +/* igraph_maximal_cliques_subset */ + +igraph_error_t igraph_i_maximal_cliques_subset( + const igraph_t *graph, const igraph_vector_int_t *subset, + igraph_vector_int_list_t *res, igraph_int_t *no, + FILE *outfile, igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_FULL +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_FULL + +/** + * \function igraph_maximal_cliques_subset + * \brief Maximal cliques for a subset of initial vertices. + * + * This function enumerates all maximal cliques for a subset of initial + * vertices and writes them to file. See \ref igraph_maximal_cliques() + * for details. + * + * \param graph The input graph. Edge directions are ignored. + * \param subset Pointer to an \c igraph_vector_int_t containing the + * subset of initial vertices. + * \param res Pointer to a list of integer vectors; the cliques will be + * stored here. + * \param no Pointer to an \c igraph_int_t; the number of maximal + * cliques will be stored here. + * \param outfile Pointer to an output file or \c NULL. + * When not \c NULL, the file should be writable. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param max_results At most this many cliques will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + */ + +igraph_error_t igraph_maximal_cliques_subset( + const igraph_t *graph, const igraph_vector_int_t *subset, + igraph_vector_int_list_t *res, igraph_int_t *no, FILE *outfile, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results) { + return igraph_i_maximal_cliques_subset(graph, subset, res, no, outfile, min_size, max_size, max_results); +} + + +/* igraph_maximal_cliques_callback */ + +igraph_error_t igraph_i_maximal_cliques_callback( + const igraph_t *graph, + igraph_clique_handler_t *cliquehandler_fn, void *arg, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_CALLBACK +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_CALLBACK + +/** + * \function igraph_maximal_cliques_callback + * \brief Finds maximal cliques in a graph and calls a function for each one. + * + * This function enumerates all maximal cliques within the given size range + * and calls \p cliquehandler_fn for each of them. The cliques are passed to the + * callback function as a pointer to an \ref igraph_vector_int_t. The vector is + * owned by the maximal clique search routine so users are expected to make a + * copy of the vector using \ref igraph_vector_int_init_copy() if they want to + * hold on to it. + * + * \param graph The input graph. Edge directions are ignored. + * \param cliquehandler_fn Callback function to be called for each clique. + * See also \ref igraph_clique_handler_t. + * \param arg Extra argument to supply to \p cliquehandler_fn. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(), \ref igraph_cliques_callback(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + */ + +igraph_error_t igraph_maximal_cliques_callback( + const igraph_t *graph, + igraph_int_t min_size, igraph_int_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg) { + return igraph_i_maximal_cliques_callback(graph, cliquehandler_fn, arg, min_size, max_size, IGRAPH_UNLIMITED); +} + + +/* igraph_maximal_cliques_hist */ + +igraph_error_t igraph_i_maximal_cliques_hist( + const igraph_t *graph, + igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size, + igraph_int_t max_results); + +#define IGRAPH_MC_HIST +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_HIST + +/** + * \function igraph_maximal_cliques_hist + * \brief Counts the number of maximal cliques of each size in a graph. + * + * This function counts how many maximal cliques of each size are present in + * the graph. Maximal cliques of size one are simply isolated vertices. + * + * \param graph The input graph. Edge directions are ignored. + * \param hist Pointer to an initialized vector. The result will be stored + * here. The first element will store the number of size-1 maximal cliques, + * the second element the number of size-2 maximal cliques, etc. + * For cliques smaller than \p min_size, zero counts will be returned. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(), \ref igraph_clique_size_hist(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + */ + +igraph_error_t igraph_maximal_cliques_hist( + const igraph_t *graph, + igraph_vector_t *hist, + igraph_int_t min_size, igraph_int_t max_size) { + return igraph_i_maximal_cliques_hist(graph, hist, min_size, max_size, IGRAPH_UNLIMITED); +} diff --git a/src/cliques/maximal_cliques_template.h b/src/cliques/maximal_cliques_template.h new file mode 100644 index 0000000..cfc5cb8 --- /dev/null +++ b/src/cliques/maximal_cliques_template.h @@ -0,0 +1,378 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifdef IGRAPH_MC_ORIG +#define RESTYPE igraph_vector_int_list_t *res +#define RESNAME res +#define SUFFIX +#define RECORD do { \ + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, R)); \ + } while (0) +#define PREPARE do { \ + igraph_vector_int_list_clear(res); \ + } while (0) +#define CLEANUP +#define FOR_LOOP_OVER_VERTICES for (i=0; i hsize) { \ + igraph_int_t hcapacity = igraph_vector_capacity(hist); \ + igraph_int_t j; \ + igraph_error_t err; \ + if (hcapacity < clsize && clsize < 2*hcapacity) \ + err = igraph_vector_reserve(hist, 2*hcapacity); \ + err = igraph_vector_resize(hist, clsize); \ + if (err != IGRAPH_SUCCESS) \ + IGRAPH_ERROR("Cannot count maximal cliques", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + for (j=hsize; j < clsize; j++) \ + VECTOR(*hist)[j] = 0; \ + } \ + VECTOR(*hist)[clsize-1] += 1; \ + } while (0) +#define PREPARE \ + igraph_vector_clear(hist); \ + IGRAPH_CHECK(igraph_vector_reserve(hist, 50)); /* initially reserve space for 50 elements */ +#define CLEANUP +#define FOR_LOOP_OVER_VERTICES for (i=0; i PE && XS > XE) { + /* Found a maximum clique, report it */ + igraph_int_t clsize = igraph_vector_int_size(R); + if (min_size <= clsize && (clsize <= max_size || max_size <= 0)) { + RECORD; + *result_count += 1; + if (max_results >= 0 && *result_count == max_results) { + return IGRAPH_STOP; + } + } + } else if (PS <= PE) { + /* Select a pivot element */ + igraph_int_t pivot, mynextv; + IGRAPH_CHECK(igraph_i_maximal_cliques_select_pivot( + PX, PS, PE, XS, XE, pos, adjlist, &pivot, nextv, oldPS, oldXE + )); + while ((mynextv = igraph_vector_int_pop_back(nextv)) != -1) { + igraph_int_t newPS, newXE; + + /* Going down, prepare */ + IGRAPH_CHECK(igraph_i_maximal_cliques_down( + PX, PS, PE, XS, XE, pos, adjlist, mynextv, R, &newPS, &newXE + )); + /* Recursive call */ + err = FUNCTION(igraph_i_maximal_cliques_bk, SUFFIX)( + PX, newPS, PE, XS, newXE, PS, XE, R, + pos, adjlist, RESNAME, nextv, H, + min_size, max_size, + max_results, result_count); + + if (err == IGRAPH_STOP) { + return err; + } else { + IGRAPH_CHECK(err); + } + /* Putting v from P to X */ + if (igraph_vector_int_tail(nextv) != -1) { + IGRAPH_CHECK(igraph_i_maximal_cliques_PX( + PX, PS, &PE, &XS, XE, pos, adjlist, mynextv, H + )); + } + } + } + + /* Putting back vertices from X to P, see notes in H */ + IGRAPH_CHECK(igraph_i_maximal_cliques_up(PX, PS, PE, XS, XE, pos, adjlist, R, H)); + + return IGRAPH_SUCCESS; +} + +igraph_error_t FUNCTION(igraph_i_maximal_cliques, SUFFIX)( + const igraph_t *graph, + RESTYPE, + igraph_int_t min_size, + igraph_int_t max_size, + igraph_int_t max_results) { + + /* Implementation details. TODO */ + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t PX, R, H, pos, nextv; + igraph_vector_int_t coreness; + igraph_vector_int_t order; + igraph_vector_int_t rank; /* TODO: this is not needed */ + igraph_int_t i, ii, nn; + igraph_adjlist_t adjlist, fulladjlist; + igraph_int_t result_count = 0; + igraph_real_t pgreset = round(no_of_nodes / 100.0), pg = pgreset, pgc = 0; + igraph_error_t err; + + IGRAPH_UNUSED(nn); /* not used by all implementations */ + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored for maximal clique calculations."); + } + + PREPARE; + + if (max_results == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&rank, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&coreness, no_of_nodes); + IGRAPH_CHECK(igraph_coreness(graph, &coreness, /*mode=*/ IGRAPH_ALL)); + IGRAPH_CHECK(igraph_vector_int_sort_ind(&coreness, &order, IGRAPH_ASCENDING)); + for (ii = 0; ii < no_of_nodes; ii++) { + igraph_int_t v = VECTOR(order)[ii]; + VECTOR(rank)[v] = ii; + } + + igraph_vector_int_destroy(&coreness); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &fulladjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &fulladjlist); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&PX, 20); + IGRAPH_VECTOR_INT_INIT_FINALLY(&R, 20); + IGRAPH_VECTOR_INT_INIT_FINALLY(&H, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&pos, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nextv, 100); + + FOR_LOOP_OVER_VERTICES { + igraph_int_t v; + igraph_int_t vrank; + igraph_vector_int_t *vneis; + igraph_int_t vdeg; + igraph_int_t Pptr, Xptr, PS, PE, XS, XE; + igraph_int_t j; + + FOR_LOOP_OVER_VERTICES_PREPARE; + + v = VECTOR(order)[i]; + vrank = VECTOR(rank)[v]; + vneis = igraph_adjlist_get(&fulladjlist, v); + vdeg = igraph_vector_int_size(vneis); + Pptr = 0; Xptr = vdeg - 1; PS = 0; XE = vdeg - 1; + + pg--; + if (pg <= 0) { + IGRAPH_PROGRESS("Maximal cliques: ", pgc++, NULL); + pg = pgreset; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_vector_int_resize(&PX, vdeg)); + IGRAPH_CHECK(igraph_vector_int_resize(&R, 1)); + IGRAPH_CHECK(igraph_vector_int_resize(&H, 1)); + igraph_vector_int_null(&pos); /* TODO: makes it quadratic? */ + IGRAPH_CHECK(igraph_vector_int_resize(&nextv, 1)); + + VECTOR(H)[0] = -1; /* marks the end of the recursion */ + VECTOR(nextv)[0] = -1; + + /* ================================================================*/ + /* P <- G(v[i]) intersect { v[i+1], ..., v[n-1] } + X <- G(v[i]) intersect { v[0], ..., v[i-1] } */ + + VECTOR(R)[0] = v; + for (j = 0; j < vdeg; j++) { + igraph_int_t vx = VECTOR(*vneis)[j]; + if (VECTOR(rank)[vx] > vrank) { + VECTOR(PX)[Pptr] = vx; + VECTOR(pos)[vx] = Pptr + 1; + Pptr++; + } else if (VECTOR(rank)[vx] < vrank) { + VECTOR(PX)[Xptr] = vx; + VECTOR(pos)[vx] = Xptr + 1; + Xptr--; + } + } + + PE = Pptr - 1; XS = Xptr + 1; /* end of P, start of X in PX */ + + /* Create an adjacency list that is specific to the + v vertex. It only contains 'v' and its neighbors. Moreover, we + only deal with the vertices in P and X (and R). */ + IGRAPH_CHECK(igraph_vector_int_update( + igraph_adjlist_get(&adjlist, v), + igraph_adjlist_get(&fulladjlist, v) + )); + for (j = 0; j <= vdeg - 1; j++) { + igraph_int_t vv = VECTOR(PX)[j]; + igraph_vector_int_t *fadj = igraph_adjlist_get(&fulladjlist, vv); + igraph_vector_int_t *radj = igraph_adjlist_get(&adjlist, vv); + igraph_int_t k, fn = igraph_vector_int_size(fadj); + igraph_vector_int_clear(radj); + for (k = 0; k < fn; k++) { + igraph_int_t nei = VECTOR(*fadj)[k]; + igraph_int_t neipos = VECTOR(pos)[nei] - 1; + if (neipos >= PS && neipos <= XE) { + IGRAPH_CHECK(igraph_vector_int_push_back(radj, nei)); + } + } + } + + /* Reorder the adjacency lists, according to P and X. */ + IGRAPH_CHECK(igraph_i_maximal_cliques_reorder_adjlists( + &PX, PS, PE, XS, XE, &pos, &adjlist + )); + + err = FUNCTION(igraph_i_maximal_cliques_bk, SUFFIX)( + &PX, PS, PE, XS, XE, PS, XE, &R, &pos, + &adjlist, RESNAME, &nextv, &H, + min_size, max_size, max_results, &result_count); + if (err == IGRAPH_STOP) { + break; + } else { + IGRAPH_CHECK(err); + } + } + + IGRAPH_PROGRESS("Maximal cliques: ", 100.0, NULL); + + CLEANUP; + + igraph_vector_int_destroy(&nextv); + igraph_vector_int_destroy(&pos); + igraph_vector_int_destroy(&H); + igraph_vector_int_destroy(&R); + igraph_vector_int_destroy(&PX); + igraph_adjlist_destroy(&fulladjlist); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&rank); + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} + +#undef RESTYPE +#undef RESNAME +#undef SUFFIX +#undef RECORD +#undef PREPARE +#undef CLEANUP +#undef FOR_LOOP_OVER_VERTICES +#undef FOR_LOOP_OVER_VERTICES_PREPARE diff --git a/src/community/community_internal.h b/src/community/community_internal.h new file mode 100644 index 0000000..4282547 --- /dev/null +++ b/src/community/community_internal.h @@ -0,0 +1,34 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_COMMUNITY_INTERNAL_H +#define IGRAPH_COMMUNITY_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_reindex_membership_large( + igraph_vector_int_t *membership, + igraph_vector_int_t *new_to_old, + igraph_int_t *nb_clusters); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_COMMUNITY_INTERNAL_H */ diff --git a/src/community/community_misc.c b/src/community/community_misc.c new file mode 100644 index 0000000..6072da6 --- /dev/null +++ b/src/community/community_misc.c @@ -0,0 +1,1019 @@ +/* + igraph library. + Copyright (C) 2007-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" +#include "igraph_memory.h" +#include "igraph_sparsemat.h" + +#include "community/community_internal.h" + +#include +#include + +/** + * \section about_community + * + * + * Community detection is concerned with clustering the vertices of networks + * into tightly connected subgraphs called "communities". The following + * references provide a good introduction to the topic of community detection: + * + * + * + * S. Fortunato: + * "Community Detection in Graphs". + * Physics Reports 486, no. 3–5 (2010): 75–174. + * https://doi.org/10.1016/j.physrep.2009.11.002. + * + * + * + * S. Fortunato and D. Hric: + * "Community Detection in Networks: A User Guide". + * Physics Reports 659 (2016): 1–44. + * https://doi.org/10.1016/j.physrep.2016.09.002. + * + */ + +/** + * \function igraph_community_to_membership + * \brief Cut a dendrogram after a given number of merges. + * + * This function creates a membership vector from a dendrogram whose leaves + * are individual vertices by cutting it at the specified level. It produces + * a membership vector that contains for each vertex its cluster ID, numbered + * from zero. This is the same membership vector format that is produced by + * \ref igraph_connected_components(), as well as all community detection + * functions in igraph. + * + * + * It takes as input the number of vertices \p n, and a \p merges matrix + * encoding the dendrogram, in the format produced by hierarchical clustering + * functions such as \ref igraph_community_edge_betweenness(), + * \ref igraph_community_walktrap() or \ref igraph_community_fastgreedy(). + * The matrix must have two columns and up to n - 1 rows. + * Each row represents merging two dendrogram nodes into their parent node. + * The leaf nodes of the dendrogram are indexed from 0 to n - 1 + * and are identical to the vertices of the graph that is being partitioned + * into communities. Row \c i contains the children of dendrogram node + * with index n + i. + * + * + * This function performs \p steps merge operations as prescribed by + * the \p merges matrix and returns the resulting partitioning into + * n - steps communities. + * + * + * If \p merges is not a complete dendrogram, it is possible to + * take \p steps steps if \p steps is not bigger than the number + * lines in \p merges. + * + * \param merges The two-column matrix containing the merge operations. + * \param nodes The number of leaf nodes in the dendrogram. + * \param steps Integer constant, the number of steps to take. + * \param membership Pointer to an initialized vector, the membership + * results will be stored here, if not \c NULL. The vector will be + * resized as needed. + * \param csize Pointer to an initialized vector, or \c NULL. If not \c NULL + * then the sizes of the components will be stored here, the vector + * will be resized as needed. + * \return Error code. + * + * \sa \ref igraph_community_walktrap(), \ref + * igraph_community_edge_betweenness(), \ref + * igraph_community_fastgreedy() for community structure detection + * algorithms producing merge matrices in this format; + * \ref igraph_le_community_to_membership() to perform merges + * starting from a given cluster assignment. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ +igraph_error_t igraph_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t nodes, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize) { + + const igraph_int_t no_of_nodes = nodes; + const igraph_int_t components = no_of_nodes - steps; + igraph_int_t found = 0; + igraph_vector_int_t tmp; + igraph_vector_bool_t already_merged; + igraph_vector_int_t own_membership; + igraph_bool_t using_own_membership = false; + + if (steps > igraph_matrix_int_nrow(merges)) { + IGRAPH_ERRORF("Number of steps is greater than number of rows in merges matrix: found %" + IGRAPH_PRId " steps, %" IGRAPH_PRId " rows.", IGRAPH_EINVAL, steps, igraph_matrix_int_nrow(merges)); + } + + if (igraph_matrix_int_ncol(merges) != 2) { + IGRAPH_ERRORF("The merges matrix should have two columns, but has %" IGRAPH_PRId ".", + IGRAPH_EINVAL, igraph_matrix_int_ncol(merges)); + } + if (steps < 0) { + IGRAPH_ERRORF("Number of steps should be non-negative, found %" IGRAPH_PRId ".", IGRAPH_EINVAL, steps); + } + + if (csize != NULL && membership == NULL) { + /* we need a membership vector to calculate 'csize' but the user did + * not provide one; let's allocate one ourselves */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&own_membership, no_of_nodes); + using_own_membership = true; + membership = &own_membership; + } + + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_null(membership); + } + if (csize) { + IGRAPH_CHECK(igraph_vector_int_resize(csize, components)); + igraph_vector_int_null(csize); + } + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&already_merged, steps + no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, steps); + + for (igraph_int_t i = steps - 1; i >= 0; i--) { + const igraph_int_t c1 = MATRIX(*merges, i, 0); + const igraph_int_t c2 = MATRIX(*merges, i, 1); + + if (VECTOR(already_merged)[c1] == 0) { + VECTOR(already_merged)[c1] = true; + } else { + IGRAPH_ERRORF("Merges matrix contains multiple merges of cluster %" IGRAPH_PRId ".", IGRAPH_EINVAL, c1); + } + if (VECTOR(already_merged)[c2] == 0) { + VECTOR(already_merged)[c2] = true; + } else { + IGRAPH_ERRORF("Merges matrix contains multiple merges of cluster %" IGRAPH_PRId ".", IGRAPH_EINVAL, c2); + } + + /* new component? */ + if (VECTOR(tmp)[i] == 0) { + found++; + VECTOR(tmp)[i] = found; + } + + if (c1 < no_of_nodes) { + const igraph_int_t cid = VECTOR(tmp)[i] - 1; + if (membership) { + VECTOR(*membership)[c1] = cid + 1; + } + if (csize) { + VECTOR(*csize)[cid] += 1; + } + } else { + VECTOR(tmp)[c1 - no_of_nodes] = VECTOR(tmp)[i]; + } + + if (c2 < no_of_nodes) { + const igraph_int_t cid = VECTOR(tmp)[i] - 1; + if (membership) { + VECTOR(*membership)[c2] = cid + 1; + } + if (csize) { + VECTOR(*csize)[cid] += 1; + } + } else { + VECTOR(tmp)[c2 - no_of_nodes] = VECTOR(tmp)[i]; + } + + } + + if (membership || csize) { + /* It can never happen that csize != NULL and membership == NULL; we have + * handled that case above. Thus we skip if (membership) checks, which + * would confuse static analysers in this context. */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + const igraph_int_t c = VECTOR(*membership)[i]; + if (c != 0) { + VECTOR(*membership)[i] = c - 1; + } else { + if (csize) { + VECTOR(*csize)[found] += 1; + } + VECTOR(*membership)[i] = found; + found++; + } + } + } + + igraph_vector_int_destroy(&tmp); + igraph_vector_bool_destroy(&already_merged); + IGRAPH_FINALLY_CLEAN(2); + + if (using_own_membership) { + igraph_vector_int_destroy(&own_membership); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + + +/** + * This slower implementation of igraph_reindex_membership() is used when + * cluster indices are not within the range 0..vcount-1. + * + * \param membership Vector encoding the membership of each vertex. + * Elements may be arbitrary integers, however, its length must not be zero. + * \param new_to_old If not \c NULL, a mapping from new to old cluster + * indices will be stored here. + * \param nb_clusters If not \c NULL, the number of clusters will be stored here. + * + * Time complexity: O(n log(n)) where n is the membership vector length. + */ +igraph_error_t igraph_i_reindex_membership_large( + igraph_vector_int_t *membership, + igraph_vector_int_t *new_to_old, + igraph_int_t *nb_clusters) { + + const igraph_int_t vcount = igraph_vector_int_size(membership); + igraph_vector_int_t permutation; + + /* The vcount == 0 case requires special handling because the main + * algorithm of this function cannot handle it. */ + if (vcount == 0) { + if (new_to_old) { + igraph_vector_int_clear(new_to_old); + } + if (nb_clusters) { + *nb_clusters = 0; + } + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&permutation, vcount); + + IGRAPH_CHECK(igraph_vector_int_sort_ind(membership, &permutation, IGRAPH_ASCENDING)); + + if (new_to_old) { + igraph_vector_int_clear(new_to_old); + } + + igraph_int_t j = VECTOR(permutation)[0]; + igraph_int_t c_old = VECTOR(*membership)[j]; + igraph_int_t c_old_prev = c_old; + igraph_int_t c_new = 0; + if (new_to_old) { + IGRAPH_CHECK(igraph_vector_int_push_back(new_to_old, c_old)); + } + VECTOR(*membership)[j] = c_new; + + for (igraph_int_t i=1; i < vcount; i++) { + j = VECTOR(permutation)[i]; + c_old = VECTOR(*membership)[j]; + if (c_old != c_old_prev) { + if (new_to_old) { + IGRAPH_CHECK(igraph_vector_int_push_back(new_to_old, c_old)); + } + c_new++; + } + c_old_prev = c_old; + VECTOR(*membership)[j] = c_new; + } + + if (nb_clusters) { + *nb_clusters = c_new+1; + } + + igraph_vector_int_destroy(&permutation); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_reindex_membership + * \brief Makes the IDs in a membership vector contiguous. + * + * This function reindexes component IDs in a membership vector in a way that + * the new IDs start from zero and go up to C-1, where \c C is the + * number of unique component IDs in the original vector. + * + * \param membership Numeric vector which gives the type of each + * vertex, i.e. the component to which it belongs. + * The vector will be altered in-place. + * \param new_to_old Pointer to a vector which will contain the + * old component ID for each new one, or \c NULL, + * in which case it is not returned. The vector + * will be resized as needed. + * \param nb_clusters Pointer to an integer for the number of + * distinct clusters. If not \c NULL, this will be + * updated to reflect the number of distinct + * clusters found in membership. + * \return Error code. + * + * Time complexity: Let n be the length of the membership vector. + * O(n) if cluster indices are within 0..n-1, and O(n log(n)) otherwise. + */ +igraph_error_t igraph_reindex_membership( + igraph_vector_int_t *membership, + igraph_vector_int_t *new_to_old, + igraph_int_t *nb_clusters) { + + const igraph_int_t vcount = igraph_vector_int_size(membership); + igraph_vector_int_t new_cluster; + igraph_int_t i_nb_clusters; + + /* First, we assume cluster indices in the range 0..vcount-1, + * and attempt to use a performant implementation. If indices + * outside of this range are found, we will fall back to a + * slower alternative implementation. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_cluster, vcount); + + if (new_to_old) { + igraph_vector_int_clear(new_to_old); + } + + /* Clean clusters. We will store the new cluster + 1 so that membership == 0 + * indicates that no cluster was assigned yet. */ + i_nb_clusters = 1; + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_int_t c = VECTOR(*membership)[i]; + + if (c < 0 || c >= vcount) { + /* Found cluster indices out of the 0..vcount-1 range. + * Use alternative implementation that supports these. */ + igraph_vector_int_destroy(&new_cluster); + IGRAPH_FINALLY_CLEAN(1); + return igraph_i_reindex_membership_large(membership, new_to_old, nb_clusters); + } + + if (VECTOR(new_cluster)[c] == 0) { + VECTOR(new_cluster)[c] = i_nb_clusters; + i_nb_clusters += 1; + if (new_to_old) { + IGRAPH_CHECK(igraph_vector_int_push_back(new_to_old, c)); + } + } + } + + /* Assign new membership */ + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_int_t c = VECTOR(*membership)[i]; + VECTOR(*membership)[i] = VECTOR(new_cluster)[c] - 1; + } + + if (nb_clusters) { + /* We used the cluster + 1, so correct */ + *nb_clusters = i_nb_clusters - 1; + } + + igraph_vector_int_destroy(&new_cluster); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_compare_communities_vi(const igraph_vector_int_t *v1, + const igraph_vector_int_t *v2, igraph_real_t* result); +static igraph_error_t igraph_i_compare_communities_nmi(const igraph_vector_int_t *v1, + const igraph_vector_int_t *v2, igraph_real_t* result); +static igraph_error_t igraph_i_compare_communities_rand(const igraph_vector_int_t *v1, + const igraph_vector_int_t *v2, igraph_real_t* result, igraph_bool_t adjust); +static igraph_error_t igraph_i_split_join_distance(const igraph_vector_int_t *v1, + const igraph_vector_int_t *v2, igraph_int_t* distance12, + igraph_int_t* distance21); + +/** + * \ingroup communities + * \function igraph_compare_communities + * \brief Compares community structures using various metrics. + * + * This function assesses the distance between two community structures + * using the variation of information (VI) metric of Meila (2003), the + * normalized mutual information (NMI) of Danon et al (2005), the + * split-join distance of van Dongen (2000), the Rand index of Rand (1971) + * or the adjusted Rand index of Hubert and Arabie (1985). + * + * + * Some of these measures are defined based on the entropy of a discrete + * random variable associated with a given clustering \c C of vertices. + * Let \c p_i be the probability that a randomly picked vertex would be part + * of cluster \c i. Then the entropy of the clustering is + * + * + * H(C) = - sum_i p_i log p_i + * + * + * Similarly, we can define the joint entropy of two clusterings \c C_1 and \c C_2 + * based on the probability \c p_ij that a random vertex is part of cluster \c i + * in the first clustering and cluster \c j in the second one: + * + * + * H(C_1, C_2) = - sum_ii p_ij log p_ij + * + * + * The mutual information of \c C_1 and \c C_2 is then + * MI(C_1, C_2) = H(C_1) + H(C_2) - H(C_1, C_2) >= 0 . + * A large mutual information indicates a high overlap between the two clusterings. + * The normalized mutual information, as computed by igraph, is + * + * + * NMI(C_1, C_2) = 2 MI(C_1, C_2) / (H(C_1) + H(C_2)). + * + * + * It takes its value from the interval (0, 1], with 1 achieved when the two clusterings + * coincide. + * + * + * The variation of information is defined as + * VI(C_1, C_2) = [H(C_1) - MI(C_1, C_2)] + [H(C_2) - MI(C_1, C_2)]. + * Lower values of the variation of information indicate a smaller difference between + * the two clusterings, with VI = 0 achieved precisely when they coincide. + * igraph uses natural units for the variation of information, i.e. it uses the + * natural logarithm when computing entropies. + * + * + * The Rand index is defined as the probability that the two clusterings agree + * about the cluster memberships of a randomly chosen vertex \em pair. All vertex + * pairs are considered, and the two clusterings are considered to be in agreement + * about the memberships of a vertex pair if either the two vertices are in the + * same cluster in both clusterings, or they are in different clusters in both + * clusterings. The Rand index is then the number of vertex pairs in agreement, + * divided by the total number of vertex pairs. A Rand index of zero means that + * the two clusterings disagree about the membership of all vertex pairs, while + * 1 means that the two clusterings are identical. + * + * + * The adjusted Rand index is similar to the Rand index, but it takes into + * account that agreement between the two clusterings may also occur by chance + * even if the two clusterings are chosen completely randomly. The adjusted + * Rand index therefore subtracts the expected fraction of agreements from the + * value of the Rand index, and divides the result by one minus the expected + * fraction of agreements. The maximum value of the adjusted Rand index is + * still 1 (similarly to the Rand index), indicating maximum agreement, but + * the value may be less than zero if there is \em less agreement between the + * two clusterings than what would be expected by chance. + * + * + * For an explanation of the split-join distance, see \ref igraph_split_join_distance(). + * + * + * References: + * + * + * Meilă M: Comparing clusterings by the variation of information. + * In: Schölkopf B, Warmuth MK (eds.). Learning Theory and Kernel Machines: + * 16th Annual Conference on Computational Learning Theory and 7th Kernel + * Workshop, COLT/Kernel 2003, Washington, DC, USA. Lecture Notes in Computer + * Science, vol. 2777, Springer, 2003. ISBN: 978-3-540-40720-1. + * https://doi.org/10.1007/978-3-540-45167-9_14 + * + * + * Danon L, Diaz-Guilera A, Duch J, Arenas A: Comparing community structure + * identification. J Stat Mech P09008, 2005. + * https://doi.org/10.1088/1742-5468/2005/09/P09008 + * + * + * van Dongen S: Performance criteria for graph clustering and Markov cluster + * experiments. Technical Report INS-R0012, National Research Institute for + * Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * https://ir.cwi.nl/pub/4461 + * + * + * Rand WM: Objective criteria for the evaluation of clustering methods. + * J Am Stat Assoc 66(336):846-850, 1971. + * https://doi.org/10.2307/2284239 + * + * + * Hubert L and Arabie P: Comparing partitions. Journal of Classification + * 2:193-218, 1985. + * https://doi.org/10.1007/BF01908075 + * + * \param comm1 the membership vector of the first community structure + * \param comm2 the membership vector of the second community structure + * \param result the result is stored here. + * \param method the comparison method to use. \c IGRAPH_COMMCMP_VI + * selects the variation of information (VI) metric of + * Meila (2003), \c IGRAPH_COMMCMP_NMI selects the + * normalized mutual information measure proposed by + * Danon et al (2005), \c IGRAPH_COMMCMP_SPLIT_JOIN + * selects the split-join distance of van Dongen (2000), + * \c IGRAPH_COMMCMP_RAND selects the unadjusted Rand + * index (1971) and \c IGRAPH_COMMCMP_ADJUSTED_RAND + * selects the adjusted Rand index. + * + * \return Error code. + * + * \sa \ref igraph_split_join_distance(). + * + * Time complexity: O(n log(n)). + */ +igraph_error_t igraph_compare_communities(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, igraph_real_t* result, + igraph_community_comparison_t method) { + igraph_vector_int_t c1, c2; + + if (igraph_vector_int_size(comm1) != igraph_vector_int_size(comm2)) { + IGRAPH_ERROR("community membership vectors have different lengths", IGRAPH_EINVAL); + } + + /* Copy and reindex membership vectors to make sure they are continuous */ + IGRAPH_CHECK(igraph_vector_int_init_copy(&c1, comm1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &c1); + + IGRAPH_CHECK(igraph_vector_int_init_copy(&c2, comm2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &c2); + + IGRAPH_CHECK(igraph_reindex_membership(&c1, NULL, NULL)); + IGRAPH_CHECK(igraph_reindex_membership(&c2, NULL, NULL)); + + switch (method) { + case IGRAPH_COMMCMP_VI: + IGRAPH_CHECK(igraph_i_compare_communities_vi(&c1, &c2, result)); + break; + + case IGRAPH_COMMCMP_NMI: + IGRAPH_CHECK(igraph_i_compare_communities_nmi(&c1, &c2, result)); + break; + + case IGRAPH_COMMCMP_SPLIT_JOIN: { + igraph_int_t d12, d21; + IGRAPH_CHECK(igraph_i_split_join_distance(&c1, &c2, &d12, &d21)); + *result = d12 + d21; + } + break; + + case IGRAPH_COMMCMP_RAND: + case IGRAPH_COMMCMP_ADJUSTED_RAND: + IGRAPH_CHECK(igraph_i_compare_communities_rand(&c1, &c2, result, + method == IGRAPH_COMMCMP_ADJUSTED_RAND)); + break; + + default: + IGRAPH_ERROR("unknown community comparison method", IGRAPH_EINVAL); + } + + /* Clean up everything */ + igraph_vector_int_destroy(&c1); + igraph_vector_int_destroy(&c2); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_split_join_distance + * \brief Calculates the split-join distance of two community structures. + * + * The split-join distance between partitions A and B is the sum of the + * projection distance of A from B and the projection distance of B from + * A. The projection distance is an asymmetric measure and it is defined + * as follows: + * + * + * First, each set in partition A is evaluated against all sets in partition + * B. For each set in partition A, the best matching set in partition B is + * found and the overlap size is calculated. (Matching is quantified by the + * size of the overlap between the two sets). Then, the maximal overlap sizes + * for each set in A are summed together and subtracted from the number of + * elements in A. + * + * + * The split-join distance will be returned in two arguments, \c distance12 + * will contain the projection distance of the first partition from the + * second, while \c distance21 will be the projection distance of the second + * partition from the first. This makes it easier to detect whether a + * partition is a subpartition of the other, since in this case, the + * corresponding distance will be zero. + * + * + * Reference: + * + * + * van Dongen S: Performance criteria for graph clustering and Markov cluster + * experiments. Technical Report INS-R0012, National Research Institute for + * Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * + * \param comm1 the membership vector of the first community structure + * \param comm2 the membership vector of the second community structure + * \param distance12 pointer to an \c igraph_int_t, the projection distance + * of the first community structure from the second one will be + * returned here. + * \param distance21 pointer to an \c igraph_int_t, the projection distance + * of the second community structure from the first one will be + * returned here. + * \return Error code. + * + * \sa \ref igraph_compare_communities() with the \c IGRAPH_COMMCMP_SPLIT_JOIN + * method if you are not interested in the individual distances but only the sum + * of them. + * + * Time complexity: O(n log(n)). + */ +igraph_error_t igraph_split_join_distance(const igraph_vector_int_t *comm1, + const igraph_vector_int_t *comm2, igraph_int_t *distance12, + igraph_int_t *distance21) { + igraph_vector_int_t c1, c2; + + if (igraph_vector_int_size(comm1) != igraph_vector_int_size(comm2)) { + IGRAPH_ERRORF("Community membership vectors have different lengths: %" IGRAPH_PRId " and %" IGRAPH_PRId ".", + IGRAPH_EINVAL, igraph_vector_int_size(comm1), igraph_vector_int_size(comm2)); + } + + /* Copy and reindex membership vectors to make sure they are continuous */ + IGRAPH_CHECK(igraph_vector_int_init_copy(&c1, comm1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &c1); + + IGRAPH_CHECK(igraph_vector_int_init_copy(&c2, comm2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &c2); + + IGRAPH_CHECK(igraph_reindex_membership(&c1, NULL, NULL)); + IGRAPH_CHECK(igraph_reindex_membership(&c2, NULL, NULL)); + + IGRAPH_CHECK(igraph_i_split_join_distance(&c1, &c2, distance12, distance21)); + + /* Clean up everything */ + igraph_vector_int_destroy(&c1); + igraph_vector_int_destroy(&c2); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * Calculates the entropy and the mutual information for two reindexed community + * membership vectors v1 and v2. This is needed by both Meila's and Danon's + * community comparison measure. + */ +static igraph_error_t igraph_i_entropy_and_mutual_information(const igraph_vector_int_t* v1, + const igraph_vector_int_t* v2, double* h1, double* h2, double* mut_inf) { + igraph_int_t i, n; + igraph_int_t k1; + igraph_int_t k2; + igraph_real_t *p1, *p2; + igraph_sparsemat_t m; + igraph_sparsemat_t mu; /* uncompressed */ + igraph_sparsemat_iterator_t mit; + + n = igraph_vector_int_size(v1); + if (n == 0) { + *h1 = 0; + *h2 = 0; + *mut_inf = 0; + return IGRAPH_SUCCESS; + } + k1 = igraph_vector_int_max(v1) + 1; + k2 = igraph_vector_int_max(v2) + 1; + p1 = IGRAPH_CALLOC(k1, igraph_real_t); + if (p1 == 0) { + IGRAPH_ERROR("Insufficient memory for computing community entropy.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p1); + p2 = IGRAPH_CALLOC(k2, igraph_real_t); + if (p2 == 0) { + IGRAPH_ERROR("Insufficient memory for computing community entropy.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p2); + + /* Calculate the entropy of v1 */ + *h1 = 0.0; + for (i = 0; i < n; i++) { + p1[VECTOR(*v1)[i]]++; + } + for (i = 0; i < k1; i++) { + p1[i] /= n; + *h1 -= p1[i] * log(p1[i]); + } + + /* Calculate the entropy of v2 */ + *h2 = 0.0; + for (i = 0; i < n; i++) { + p2[VECTOR(*v2)[i]]++; + } + for (i = 0; i < k2; i++) { + p2[i] /= n; + *h2 -= p2[i] * log(p2[i]); + } + + /* We will only need the logs of p1 and p2 from now on */ + for (i = 0; i < k1; i++) { + p1[i] = log(p1[i]); + } + for (i = 0; i < k2; i++) { + p2[i] = log(p2[i]); + } + + /* Calculate the mutual information of v1 and v2 */ + *mut_inf = 0.0; + IGRAPH_CHECK(igraph_sparsemat_init(&mu, k1, k2, n)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &mu); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_sparsemat_entry( + &mu, VECTOR(*v1)[i], + VECTOR(*v2)[i], 1 + )); + } + + IGRAPH_CHECK(igraph_sparsemat_compress(&mu, &m)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &m); + IGRAPH_CHECK(igraph_sparsemat_dupl(&m)); + + IGRAPH_CHECK(igraph_sparsemat_iterator_init(&mit, &m)); + while (!igraph_sparsemat_iterator_end(&mit)) { + double p = igraph_sparsemat_iterator_get(&mit)/ n; + *mut_inf += p * (log(p) - p1[igraph_sparsemat_iterator_row(&mit)] - p2[igraph_sparsemat_iterator_col(&mit)]); + igraph_sparsemat_iterator_next(&mit); + } + igraph_sparsemat_destroy(&m); + igraph_sparsemat_destroy(&mu); + IGRAPH_FREE(p1); IGRAPH_FREE(p2); + + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the normalized mutual information (NMI) measure of + * Danon et al. This function assumes that the community membership + * vectors have already been normalized using igraph_reindex_membership(). + * + * + * Reference: Danon L, Diaz-Guilera A, Duch J, Arenas A: Comparing community + * structure identification. J Stat Mech P09008, 2005. + * + * + * Time complexity: O(n log(n)) + */ +static igraph_error_t igraph_i_compare_communities_nmi(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_real_t* result) { + double h1, h2, mut_inf; + + IGRAPH_CHECK(igraph_i_entropy_and_mutual_information(v1, v2, &h1, &h2, &mut_inf)); + + if (h1 == 0 && h2 == 0) { + *result = 1; + } else { + *result = 2 * mut_inf / (h1 + h2); + } + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the variation of information metric (VI) of + * Meila et al. This function assumes that the community membership + * vectors have already been normalized using igraph_reindex_membership(). + * + * + * Reference: Meila M: Comparing clusterings by the variation of information. + * In: Schölkopf B, Warmuth MK (eds.). Learning Theory and Kernel Machines: + * 16th Annual Conference on Computational Learning Theory and 7th Kernel + * Workshop, COLT/Kernel 2003, Washington, DC, USA. Lecture Notes in Computer + * Science, vol. 2777, Springer, 2003. ISBN: 978-3-540-40720-1. + * + * + * Time complexity: O(n log(n)) + */ +static igraph_error_t igraph_i_compare_communities_vi(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_real_t* result) { + double h1, h2, mut_inf; + + IGRAPH_CHECK(igraph_i_entropy_and_mutual_information(v1, v2, &h1, &h2, &mut_inf)); + *result = h1 + h2 - 2 * mut_inf; + + return IGRAPH_SUCCESS; +} + +/** + * \brief Calculates the confusion matrix for two clusterings. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_membership(). + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static igraph_error_t igraph_i_confusion_matrix(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_sparsemat_t *m) { + igraph_int_t k1, k2, i, n; + + n = igraph_vector_int_size(v1); + if (n == 0) { + IGRAPH_CHECK(igraph_sparsemat_resize(m, 0, 0, 0)); + return IGRAPH_SUCCESS; + } + + k1 = igraph_vector_int_max(v1) + 1; + k2 = igraph_vector_int_max(v2) + 1; + IGRAPH_CHECK(igraph_sparsemat_resize(m, k1, k2, n)); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_sparsemat_entry( + m, VECTOR(*v1)[i], VECTOR(*v2)[i], 1 + )); + } + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the split-join distance of van Dongen. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_membership(). + * + * + * Reference: van Dongen S: Performance criteria for graph clustering and Markov + * cluster experiments. Technical Report INS-R0012, National Research Institute + * for Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static igraph_error_t igraph_i_split_join_distance(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_int_t* distance12, igraph_int_t* distance21) { + igraph_int_t n = igraph_vector_int_size(v1); + igraph_vector_t rowmax, colmax; + igraph_sparsemat_t m; + igraph_sparsemat_t mu; /* uncompressed */ + igraph_sparsemat_iterator_t mit; + + if (n == 0) { + *distance12 = 0; + *distance21 = 0; + return IGRAPH_SUCCESS; + } + /* Calculate the confusion matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&mu, 1, 1, 0)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &mu); + IGRAPH_CHECK(igraph_i_confusion_matrix(v1, v2, &mu)); + + /* Initialize vectors that will store the row/columnwise maxima */ + IGRAPH_VECTOR_INIT_FINALLY(&rowmax, igraph_sparsemat_nrow(&mu)); + IGRAPH_VECTOR_INIT_FINALLY(&colmax, igraph_sparsemat_ncol(&mu)); + + /* Find the row/columnwise maxima */ + igraph_sparsemat_compress(&mu, &m); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &m); + IGRAPH_CHECK(igraph_sparsemat_dupl(&m)); + IGRAPH_CHECK(igraph_sparsemat_iterator_init(&mit, &m)); + while (!igraph_sparsemat_iterator_end(&mit)) { + igraph_real_t value = igraph_sparsemat_iterator_get(&mit); + igraph_int_t row = igraph_sparsemat_iterator_row(&mit); + igraph_int_t col = igraph_sparsemat_iterator_col(&mit); + if (value > VECTOR(rowmax)[row]) { + VECTOR(rowmax)[row] = value; + } + if (value > VECTOR(colmax)[col]) { + VECTOR(colmax)[col] = value; + } + igraph_sparsemat_iterator_next(&mit); + } + + /* Calculate the distances */ + *distance12 = (igraph_int_t) (n - igraph_vector_sum(&rowmax)); + *distance21 = (igraph_int_t) (n - igraph_vector_sum(&colmax)); + + igraph_vector_destroy(&rowmax); + igraph_vector_destroy(&colmax); + igraph_sparsemat_destroy(&m); + igraph_sparsemat_destroy(&mu); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the adjusted and unadjusted Rand indices. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_membership(). + * + * + * References: + * + * + * Rand WM: Objective criteria for the evaluation of clustering methods. J Am + * Stat Assoc 66(336):846-850, 1971. + * + * + * Hubert L and Arabie P: Comparing partitions. Journal of Classification + * 2:193-218, 1985. + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static igraph_error_t igraph_i_compare_communities_rand( + const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_real_t *result, igraph_bool_t adjust) { + igraph_sparsemat_t m; + igraph_sparsemat_t mu; /* uncompressed */ + igraph_sparsemat_iterator_t mit; + igraph_vector_t rowsums, colsums; + igraph_int_t i, nrow, ncol; + igraph_real_t rand, n; + igraph_real_t frac_pairs_in_1, frac_pairs_in_2; + + if (igraph_vector_int_size(v1) <= 1) { + IGRAPH_ERRORF("Rand indices not defined for only zero or one vertices. " + "Found membership vector of size %" IGRAPH_PRId ".", IGRAPH_EINVAL, igraph_vector_int_size(v1)); + } + + /* Calculate the confusion matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&mu, 1, 1, 0)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &mu); + IGRAPH_CHECK(igraph_i_confusion_matrix(v1, v2, &mu)); + + /* The unadjusted Rand index is defined as (a+d) / (a+b+c+d), where: + * + * - a is the number of pairs in the same cluster both in v1 and v2. This + * equals the sum of n(i,j) choose 2 for all i and j. + * + * - b is the number of pairs in the same cluster in v1 and in different + * clusters in v2. This is sum n(i,*) choose 2 for all i minus a. + * n(i,*) is the number of elements in cluster i in v1. + * + * - c is the number of pairs in the same cluster in v2 and in different + * clusters in v1. This is sum n(*,j) choose 2 for all j minus a. + * n(*,j) is the number of elements in cluster j in v2. + * + * - d is (n choose 2) - a - b - c. + * + * Therefore, a+d = (n choose 2) - b - c + * = (n choose 2) - sum (n(i,*) choose 2) + * - sum (n(*,j) choose 2) + * + 2 * sum (n(i,j) choose 2). + * + * Since a+b+c+d = (n choose 2) and this goes in the denominator, we can + * just as well start dividing each term in a+d by (n choose 2), which + * yields: + * + * 1 - sum( n(i,*)/n * (n(i,*)-1)/(n-1) ) + * - sum( n(*,i)/n * (n(*,i)-1)/(n-1) ) + * + sum( n(i,j)/n * (n(i,j)-1)/(n-1) ) * 2 + */ + + /* Calculate row and column sums */ + nrow = igraph_sparsemat_nrow(&mu); + ncol = igraph_sparsemat_ncol(&mu); + n = igraph_vector_int_size(v1); + IGRAPH_VECTOR_INIT_FINALLY(&rowsums, nrow); + IGRAPH_VECTOR_INIT_FINALLY(&colsums, ncol); + IGRAPH_CHECK(igraph_sparsemat_rowsums(&mu, &rowsums)); + IGRAPH_CHECK(igraph_sparsemat_colsums(&mu, &colsums)); + + /* Start calculating the unadjusted Rand index */ + rand = 0.0; + igraph_sparsemat_compress(&mu, &m); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &m); + IGRAPH_CHECK(igraph_sparsemat_dupl(&m)); + + IGRAPH_CHECK(igraph_sparsemat_iterator_init(&mit, &m)); + while (!igraph_sparsemat_iterator_end(&mit)) { + igraph_real_t value = igraph_sparsemat_iterator_get(&mit); + rand += (value / n) * (value - 1) / (n - 1); + igraph_sparsemat_iterator_next(&mit); + } + + frac_pairs_in_1 = frac_pairs_in_2 = 0.0; + for (i = 0; i < nrow; i++) { + frac_pairs_in_1 += (VECTOR(rowsums)[i] / n) * (VECTOR(rowsums)[i] - 1) / (n - 1); + } + for (i = 0; i < ncol; i++) { + frac_pairs_in_2 += (VECTOR(colsums)[i] / n) * (VECTOR(colsums)[i] - 1) / (n - 1); + } + + rand = 1.0 + 2 * rand - frac_pairs_in_1 - frac_pairs_in_2; + + if (adjust) { + double expected = frac_pairs_in_1 * frac_pairs_in_2 + + (1 - frac_pairs_in_1) * (1 - frac_pairs_in_2); + rand = (rand - expected) / (1 - expected); + } + + igraph_vector_destroy(&rowsums); + igraph_vector_destroy(&colsums); + igraph_sparsemat_destroy(&m); + igraph_sparsemat_destroy(&mu); + IGRAPH_FINALLY_CLEAN(4); + + *result = rand; + + return IGRAPH_SUCCESS; +} diff --git a/src/community/edge_betweenness.c b/src/community/edge_betweenness.c new file mode 100644 index 0000000..d5982b3 --- /dev/null +++ b/src/community/edge_betweenness.c @@ -0,0 +1,809 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_nongraph.h" +#include "igraph_progress.h" +#include "igraph_stack.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +#include + +static igraph_error_t igraph_i_rewrite_membership_vector(igraph_vector_int_t *membership) { + const igraph_int_t no = igraph_vector_int_max(membership) + 1; + igraph_vector_int_t idx; + igraph_int_t realno = 0; + const igraph_int_t len = igraph_vector_int_size(membership); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, no); + for (igraph_int_t i = 0; i < len; i++) { + const igraph_int_t t = VECTOR(*membership)[i]; + if (VECTOR(idx)[t]) { + VECTOR(*membership)[i] = VECTOR(idx)[t] - 1; + } else { + VECTOR(idx)[t] = ++realno; + VECTOR(*membership)[i] = VECTOR(idx)[t] - 1; + } + } + igraph_vector_int_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_community_eb_get_merges2(const igraph_t *graph, + const igraph_bool_t directed, + const igraph_vector_int_t *edges, + const igraph_vector_t *weights, + igraph_matrix_int_t *res, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership) { + + igraph_vector_int_t mymembership; + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_real_t maxmod = -1; + igraph_int_t midx = 0; + igraph_int_t no_comps; + const igraph_bool_t use_directed = directed && igraph_is_directed(graph); + igraph_int_t max_merges; + + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + } + if (modularity || res || bridges) { + IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_comps, IGRAPH_WEAK)); + max_merges = no_of_nodes - no_comps; + + if (modularity) { + IGRAPH_CHECK(igraph_vector_resize(modularity, + max_merges + 1)); + } + if (res) { + IGRAPH_CHECK(igraph_matrix_int_resize(res, max_merges, + 2)); + } + if (bridges) { + IGRAPH_CHECK(igraph_vector_int_resize(bridges, max_merges)); + } + } + + IGRAPH_CHECK(igraph_vector_int_init_range(&mymembership, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &mymembership); + + if (membership) { + IGRAPH_CHECK(igraph_vector_int_update(membership, &mymembership)); + } + + IGRAPH_CHECK(igraph_modularity(graph, &mymembership, weights, + /* resolution */ 1, + use_directed, &maxmod)); + if (modularity) { + VECTOR(*modularity)[0] = maxmod; + } + + for (igraph_int_t i = igraph_vector_int_size(edges) - 1; i >= 0; i--) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + igraph_int_t c1 = VECTOR(mymembership)[from]; + igraph_int_t c2 = VECTOR(mymembership)[to]; + igraph_real_t actmod; + + if (c1 != c2) { /* this is a merge */ + if (res) { + MATRIX(*res, midx, 0) = c1; + MATRIX(*res, midx, 1) = c2; + } + if (bridges) { + VECTOR(*bridges)[midx] = i; + } + + /* The new cluster has id no_of_nodes+midx+1 */ + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + if (VECTOR(mymembership)[j] == c1 || + VECTOR(mymembership)[j] == c2) { + VECTOR(mymembership)[j] = no_of_nodes + midx; + } + } + + IGRAPH_CHECK(igraph_modularity(graph, &mymembership, weights, + /* resolution */ 1, + use_directed, &actmod)); + if (modularity) { + VECTOR(*modularity)[midx + 1] = actmod; + if (actmod > maxmod) { + maxmod = actmod; + if (membership) { + IGRAPH_CHECK(igraph_vector_int_update(membership, &mymembership)); + } + } + } + + midx++; + } + } + + if (membership) { + IGRAPH_CHECK(igraph_i_rewrite_membership_vector(membership)); + } + + igraph_vector_int_destroy(&mymembership); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_community_eb_get_merges + * \brief Calculating the merges, i.e. the dendrogram for an edge betweenness community structure. + * + * This function is handy if you have a sequence of edges which are + * gradually removed from the network and you would like to know how + * the network falls apart into separate components. The edge sequence + * may come from the \ref igraph_community_edge_betweenness() + * function, but this is not necessary. Note that \ref + * igraph_community_edge_betweenness() can also calculate the + * dendrogram, via its \p merges argument. Merges happen when the + * edge removal process is run backwards and two components become + * connected. + * + * \param graph The input graph. + * \param directed Whether to use the directed or undirected version + * of modularity. Will be ignored for undirected graphs. + * \param edges Vector containing the edges to be removed from the + * network, all edges are expected to appear exactly once in the + * vector. + * \param weights An optional vector containing edge weights. If null, + * the unweighted modularity scores will be calculated. If not null, + * the weighted modularity scores will be calculated. Ignored if both + * \p modularity and \p membership are \c NULL pointers. + * \param res Pointer to an initialized matrix, if not \c NULL then the + * dendrogram will be stored here, in the same form as for the + * \ref igraph_community_walktrap() function: the matrix has two columns + * and each line is a merge given by the IDs of the merged + * components. The component IDs are numbered from zero and + * component IDs smaller than the number of vertices in the graph + * belong to individual vertices. The non-trivial components + * containing at least two vertices are numbered from \c n, where \c n is + * the number of vertices in the graph. So if the first line + * contains \c a and \c b that means that components \c a and \c b + * are merged into component \c n, the second line creates + * component n + 1, etc. The matrix will be resized as needed. + * \param bridges Pointer to an initialized vector of \c NULL. If not + * \c NULL then the indices into \p edges of all edges which caused + * one of the merges will be put here. This is equal to all edge removals + * which separated the network into more components, in reverse order. + * \param modularity If not a null pointer, then the modularity values + * for the different divisions, corresponding to the merges matrix, + * will be stored here. + * \param membership If not a null pointer, then the membership vector + * for the best division (in terms of modularity) will be stored + * here. + * \return Error code. + * + * \sa \ref igraph_community_edge_betweenness(). + * + * Time complexity: O(|E|+|V|log|V|), |V| is the number of vertices, + * |E| is the number of edges. + */ +igraph_error_t igraph_community_eb_get_merges(const igraph_t *graph, + const igraph_bool_t directed, + const igraph_vector_int_t *edges, + const igraph_vector_t *weights, + igraph_matrix_int_t *res, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t ptr; + igraph_int_t midx = 0; + igraph_int_t no_comps; + const igraph_int_t no_removed_edges = igraph_vector_int_size(edges); + igraph_int_t max_merges; + + if (! igraph_vector_int_isininterval(edges, 0, no_of_edges-1)) { + IGRAPH_ERROR( + "Cannot calculate merges of edge betweenness community detection.", + IGRAPH_EINVEID + ); + } + if (no_removed_edges < no_of_edges) { + IGRAPH_ERRORF("Number of removed edges (%" IGRAPH_PRId ") should be equal to " + "number of edges in graph (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + no_removed_edges, no_of_edges); + } + + /* catch null graph early */ + if (no_of_nodes == 0) { + if (res) { + IGRAPH_CHECK(igraph_matrix_int_resize(res, 0, 2)); + } + if (bridges) { + igraph_vector_int_clear(bridges); + } + if (modularity) { + IGRAPH_CHECK(igraph_vector_resize(modularity, 1)); + VECTOR(*modularity)[0] = IGRAPH_NAN; + } + if (membership) { + igraph_vector_int_clear(membership); + } + return IGRAPH_SUCCESS; + } + + if (membership || modularity) { + return igraph_i_community_eb_get_merges2(graph, + directed && igraph_is_directed(graph), + edges, weights, + res, bridges, modularity, membership); + } + + IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_comps, IGRAPH_WEAK)); + + max_merges = no_of_nodes - no_comps; + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptr, no_of_nodes * 2 - 1); + if (res) { + IGRAPH_CHECK(igraph_matrix_int_resize(res, max_merges, 2)); + } + if (bridges) { + IGRAPH_CHECK(igraph_vector_int_resize(bridges, max_merges)); + } + + for (igraph_int_t i = igraph_vector_int_size(edges) - 1; i >= 0; i--) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t from, to, c1, c2, idx; + IGRAPH_CHECK(igraph_edge(graph, edge, &from, &to)); + idx = from + 1; + while (VECTOR(ptr)[idx - 1] != 0) { + idx = VECTOR(ptr)[idx - 1]; + } + c1 = idx - 1; + idx = to + 1; + while (VECTOR(ptr)[idx - 1] != 0) { + idx = VECTOR(ptr)[idx - 1]; + } + c2 = idx - 1; + if (c1 != c2) { /* this is a merge */ + if (res) { + MATRIX(*res, midx, 0) = c1; + MATRIX(*res, midx, 1) = c2; + } + if (bridges) { + VECTOR(*bridges)[midx] = i; + } + + VECTOR(ptr)[c1] = no_of_nodes + midx + 1; + VECTOR(ptr)[c2] = no_of_nodes + midx + 1; + VECTOR(ptr)[from] = no_of_nodes + midx + 1; + VECTOR(ptr)[to] = no_of_nodes + midx + 1; + + midx++; + } + } + + igraph_vector_int_destroy(&ptr); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Find the index i for which v[i] / w[i] is the largest + * and the corresponding element is active (i.e. !passive[i]). + * If w is a null pointer then all w[i] are assumed to be 1, + * and the ranking is done solely based on v. + * + * This function requires that at least one element is active. */ +static igraph_int_t igraph_i_which_max_active_ratio( + const igraph_vector_t *v, + const igraph_vector_t *w, + igraph_bitset_t *passive) { + + const igraph_int_t size = igraph_vector_size(v); + igraph_int_t which = igraph_bitset_countr_one(passive); /* start with first active element */ + igraph_real_t max = VECTOR(*v)[which] / (w ? VECTOR(*w)[which] : 1.0); + + for (igraph_int_t i = which+1; i < size; i++) { + igraph_real_t elem = VECTOR(*v)[i] / (w ? VECTOR(*w)[i] : 1.0); + if (! IGRAPH_BIT_TEST(*passive, i) && elem > max) { + max = elem; + which = i; + } + } + + return which; +} + +/** + * \function igraph_community_edge_betweenness + * \brief Community finding based on edge betweenness. + * + * Community structure detection based on the betweenness of the edges + * in the network. This method is also known as the Girvan-Newman + * algorithm. + * + * + * The idea behind this method is that the betweenness of the edges connecting + * two communities is typically high, as many of the shortest paths between + * vertices in separate communities pass through them. The algorithm + * successively removes edges with the highest betweenness, recalculating + * betweenness values after each removal. This way eventually the network splits + * into two components, then one of these components splits again, and so on, + * until all edges are removed. The resulting hierarhical partitioning of the + * vertices can be encoded as a dendrogram. + * + * + * In directed graphs, when \p directed is set to true, the directed version + * of betweenness and modularity are used, however, only splits into + * \em weakly connected components are detected. + * + * + * When edge weights are given, the ratio of betweenness and weight values + * is used to choose which edges to remove first, as described in + * M. E. J. Newman: Analysis of Weighted Networks (2004), Section C. + * Thus, edges with large weights are treated as strong connections, + * and will be removed later than weak connections having similar betweenness. + * Weights are also used for calculating modularity. + * + * + * If lengths are given, they will be considered for shortest path length + * calculations while computing betweenness values. + * + * + * Note: In igraph 0.10, this function interpreted weights in a different, + * erroneous way, and issued a warning when weights were used. Please + * see https://github.com/igraph/igraph/issues/2229 for additional details. + * + * + * References: + * + * + * M. Girvan and M. E. J. Newman, + * Community Structure in Social and Biological Networks, PNAS 99, 7821 (2002). + * https://doi.org/10.1073/pnas.122653799 + * + * + * M. E. J. Newman, + * Analysis of Weighted Networks, Phys. Rev. E 70, 9 (2004). + * https://doi.org/10.1103/PhysRevE.70.056131 + * + * \param graph The input graph. + * \param removed_edges Pointer to an initialized integer vector, which will + * be resized as needed. The IDs of the removed edges in the order of their + * removal will be stored here. This vector is suitable as input to + * \ref igraph_community_eb_get_merges(). This parameter may be \c NULL if + * the edge IDs are not needed by the caller. + * \param edge_betweenness Pointer to an initialized vector or + * \c NULL. In the former case the edge betweenness of the removed + * edges is stored here. The vector will be resized as needed. + * Note that the betweenness values stored here are \em not divided + * by weights. + * \param merges Pointer to an initialized matrix or \c NULL. If not \c NULL + * then merges performed by the algorithm are stored here. Even if + * this is a divisive algorithm, we can replay it backwards and + * note which two clusters were merged. Clusters are numbered from + * zero. See \ref igraph_community_to_membership() for details. The + * matrix will be resized as needed. + * \param bridges Pointer to an initialized vector of \c NULL. If not + * \c NULL then the indices into \p result of all edges which caused + * one of the \p merges will be put here. This is equivalent to all edge removals + * which separated the network into more components, in reverse order. + * \param modularity If not a null pointer, then the modularity values + * of the different divisions are stored here, in the order + * corresponding to the merge matrix. The modularity values will + * take weights into account if \p weights is not null. + * \param membership If not a null pointer, then the membership vector, + * corresponding to the highest modularity value, is stored here. + * \param directed Boolean constant. Controls whether to calculate directed + * betweenness (i.e. directed paths) for directed graphs, and whether + * to use the directed version of modularity. It is ignored for undirected + * graphs. + * \param weights An optional vector containing edge weights. If not \c NULL, + * the weights will be used to divide the edge betweenness scores, + * as well as for the calculation of modularity. + * \param lengths An optional vector containing edge lengths. If not \c NULL, + * path lengths used in the betweenness calculation will take these + * lengths into account. + * \return Error code. + * + * \sa \ref igraph_community_eb_get_merges(), \ref + * igraph_community_spinglass(), \ref igraph_community_walktrap(). + * + * Time complexity: O(|V||E|^2), as the betweenness calculation requires + * O(|V||E|) and we do it |E|-1 times. + * + * \example examples/simple/igraph_community_edge_betweenness.c + */ +igraph_error_t igraph_community_edge_betweenness(const igraph_t *graph, + igraph_vector_int_t *removed_edges, + igraph_vector_t *edge_betweenness, + igraph_matrix_int_t *merges, + igraph_vector_int_t *bridges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership, + igraph_bool_t directed, + const igraph_vector_t *weights, + const igraph_vector_t *lengths) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + double *distance, *tmpscore; + double *nrgeo; + + igraph_inclist_t elist_out, elist_in, parents; + igraph_inclist_t *elist_out_p, *elist_in_p; + igraph_vector_int_t *neip; + igraph_int_t neino; + igraph_vector_t eb; + igraph_int_t maxedge, pos; + igraph_int_t from, to; + igraph_bool_t result_owned = false; + igraph_stack_int_t stack; + igraph_real_t steps, steps_done; + igraph_bitset_t passive; + + /* Needed only for the unweighted case */ + igraph_dqueue_int_t q; + + /* Needed only for the weighted case */ + igraph_2wheap_t heap; + + if (removed_edges == NULL) { + removed_edges = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(removed_edges, "Insufficient memory for edge betweenness-based community detection."); + IGRAPH_FINALLY(igraph_free, removed_edges); + + IGRAPH_VECTOR_INT_INIT_FINALLY(removed_edges, 0); + result_owned = true; + } + + directed = directed && igraph_is_directed(graph); + if (directed) { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_in, IGRAPH_IN, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_in); + elist_out_p = &elist_out; + elist_in_p = &elist_in; + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + elist_out_p = elist_in_p = &elist_out; + } + + distance = IGRAPH_CALLOC(no_of_nodes, double); + IGRAPH_CHECK_OOM(distance, "Insufficient memory for edge betweenness-based community detection."); + IGRAPH_FINALLY(igraph_free, distance); + + nrgeo = IGRAPH_CALLOC(no_of_nodes, double); + IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for edge betweenness-based community detection."); + IGRAPH_FINALLY(igraph_free, nrgeo); + + tmpscore = IGRAPH_CALLOC(no_of_nodes, double); + IGRAPH_CHECK_OOM(tmpscore, "Insufficient memory for edge betweenness-based community detection."); + IGRAPH_FINALLY(igraph_free, tmpscore); + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + /* Must not call vector_min on empty vector */ + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weights must be strictly positive.", IGRAPH_EINVAL); + } + + if (isnan(minweight)) { + IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL); + } + } + } + + if (lengths == NULL) { + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + } else { + if (igraph_vector_size(lengths) != no_of_edges) { + IGRAPH_ERROR("Edge length vector size must agree with number of edges.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + /* Must not call vector_min on empty vector */ + igraph_real_t minlength = igraph_vector_min(lengths); + if (minlength <= 0) { + IGRAPH_ERROR("Edge lengths must be strictly positive.", IGRAPH_EINVAL); + } + + if (isnan(minlength)) { + IGRAPH_ERROR("Edge lengths must not be NaN.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_2wheap_init(&heap, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &heap); + IGRAPH_CHECK(igraph_inclist_init_empty(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_inclist_destroy, &parents); + } + + IGRAPH_STACK_INT_INIT_FINALLY(&stack, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_int_resize(removed_edges, no_of_edges)); + if (edge_betweenness) { + IGRAPH_CHECK(igraph_vector_resize(edge_betweenness, no_of_edges)); + if (no_of_edges > 0) { + VECTOR(*edge_betweenness)[no_of_edges - 1] = 0; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&eb, no_of_edges); + IGRAPH_BITSET_INIT_FINALLY(&passive, no_of_edges); + + /* Estimate the number of steps to be taken. + * It is assumed that one iteration is O(|E||V|), but |V| is constant + * anyway, so we will have approximately |E|^2 / 2 steps, and one + * iteration of the outer loop advances the step counter by the number + * of remaining edges at that iteration. + */ + steps = no_of_edges / 2.0 * (no_of_edges + 1); + steps_done = 0; + + for (igraph_int_t e = 0; e < no_of_edges; steps_done += no_of_edges - e, e++) { + IGRAPH_PROGRESS("Edge betweenness community detection: ", + 100.0 * steps_done / steps, NULL); + + igraph_vector_null(&eb); + + if (lengths == NULL) { + /* Unweighted variant follows */ + + /* The following for loop is copied almost intact from + * igraph_edge_betweenness_cutoff */ + for (igraph_int_t source = 0; source < no_of_nodes; source++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + memset(distance, 0, (size_t) no_of_nodes * sizeof(double)); + memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(double)); + memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double)); + igraph_stack_int_clear(&stack); /* it should be empty anyway... */ + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, source)); + + nrgeo[source] = 1; + distance[source] = 0; + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + + neip = igraph_inclist_get(elist_out_p, actnode); + neino = igraph_vector_int_size(neip); + for (igraph_int_t i = 0; i < neino; i++) { + igraph_int_t edge = VECTOR(*neip)[i]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, actnode); + if (nrgeo[neighbor] != 0) { + /* we've already seen this node, another shortest path? */ + if (distance[neighbor] == distance[actnode] + 1) { + nrgeo[neighbor] += nrgeo[actnode]; + } + } else { + /* we haven't seen this node yet */ + nrgeo[neighbor] += nrgeo[actnode]; + distance[neighbor] = distance[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, neighbor)); + } + } + } /* while !igraph_dqueue_int_empty */ + + /* Ok, we've the distance of each node and also the number of + shortest paths to them. Now we do an inverse search, starting + with the farthest nodes. */ + while (!igraph_stack_int_empty(&stack)) { + igraph_int_t actnode = igraph_stack_int_pop(&stack); + if (distance[actnode] < 1) { + continue; /* skip source node */ + } + + /* set the temporary score of the friends */ + neip = igraph_inclist_get(elist_in_p, actnode); + neino = igraph_vector_int_size(neip); + for (igraph_int_t i = 0; i < neino; i++) { + igraph_int_t edge = VECTOR(*neip)[i]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, actnode); + if (distance[neighbor] == distance[actnode] - 1 && + nrgeo[neighbor] != 0) { + tmpscore[neighbor] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + VECTOR(eb)[edge] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + } + } + } + /* Ok, we've the scores for this source */ + } /* for source <= no_of_nodes */ + } else { + /* Weighted variant follows */ + + const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON; + int cmp_result; + + /* The following for loop is copied almost intact from + * igraph_i_edge_betweenness_cutoff_weighted */ + for (igraph_int_t source = 0; source < no_of_nodes; source++) { + /* This will contain the edge betweenness in the current step */ + IGRAPH_ALLOW_INTERRUPTION(); + + memset(distance, 0, (size_t) no_of_nodes * sizeof(double)); + memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(double)); + memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double)); + + IGRAPH_CHECK(igraph_2wheap_push_with_index(&heap, source, 0)); + distance[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_2wheap_empty(&heap)) { + igraph_int_t minnei = igraph_2wheap_max_index(&heap); + igraph_real_t mindist = -igraph_2wheap_delete_max(&heap); + + IGRAPH_CHECK(igraph_stack_int_push(&stack, minnei)); + + neip = igraph_inclist_get(elist_out_p, minnei); + neino = igraph_vector_int_size(neip); + + for (igraph_int_t i = 0; i < neino; i++) { + igraph_int_t edge = VECTOR(*neip)[i]; + igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*lengths)[edge]; + igraph_real_t curdist = distance[to]; + igraph_vector_int_t *v; + + /* Note: curdist == 0 means infinity, and for this case + * cmp_result should be -1. However, this case is handled + * specially below, without referring to cmp_result. */ + cmp_result = igraph_cmp_epsilon(altdist, curdist - 1, eps); + + if (curdist == 0) { + /* This is the first finite distance to 'to' */ + v = igraph_inclist_get(&parents, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + distance[to] = altdist + 1.0; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&heap, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + v = igraph_inclist_get(&parents, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + distance[to] = altdist + 1.0; + igraph_2wheap_modify(&heap, to, -altdist); + } else if (cmp_result == 0) { + /* Another path with the same length */ + v = igraph_inclist_get(&parents, to); + IGRAPH_CHECK(igraph_vector_int_push_back(v, edge)); + nrgeo[to] += nrgeo[minnei]; + } + } + } /* igraph_2wheap_empty(&Q) */ + + while (!igraph_stack_int_empty(&stack)) { + igraph_int_t w = igraph_stack_int_pop(&stack); + igraph_vector_int_t *parv = igraph_inclist_get(&parents, w); + igraph_int_t parv_len = igraph_vector_int_size(parv); + + for (igraph_int_t i = 0; i < parv_len; i++) { + igraph_int_t fedge = VECTOR(*parv)[i]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, fedge, w); + tmpscore[neighbor] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w]; + VECTOR(eb)[fedge] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w]; + } + + tmpscore[w] = 0; + distance[w] = 0; + nrgeo[w] = 0; + igraph_vector_int_clear(parv); + } + } /* source < no_of_nodes */ + } + + /* Now look for the smallest edge betweenness */ + /* and eliminate that edge from the network */ + maxedge = igraph_i_which_max_active_ratio(&eb, weights, &passive); + VECTOR(*removed_edges)[e] = maxedge; + if (edge_betweenness) { + VECTOR(*edge_betweenness)[e] = VECTOR(eb)[maxedge]; + if (!directed) { + VECTOR(*edge_betweenness)[e] /= 2.0; + } + } + IGRAPH_BIT_SET(passive, maxedge); + IGRAPH_CHECK(igraph_edge(graph, maxedge, &from, &to)); + + neip = igraph_inclist_get(elist_in_p, to); + neino = igraph_vector_int_size(neip); + igraph_vector_int_search(neip, 0, maxedge, &pos); + VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1]; + igraph_vector_int_pop_back(neip); + + neip = igraph_inclist_get(elist_out_p, from); + neino = igraph_vector_int_size(neip); + igraph_vector_int_search(neip, 0, maxedge, &pos); + VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1]; + igraph_vector_int_pop_back(neip); + } + + IGRAPH_PROGRESS("Edge betweenness community detection: ", 100.0, NULL); + + igraph_bitset_destroy(&passive); + igraph_vector_destroy(&eb); + igraph_stack_int_destroy(&stack); + IGRAPH_FINALLY_CLEAN(3); + + if (lengths == NULL) { + igraph_dqueue_int_destroy(&q); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_2wheap_destroy(&heap); + igraph_inclist_destroy(&parents); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_free(tmpscore); + igraph_free(nrgeo); + igraph_free(distance); + IGRAPH_FINALLY_CLEAN(3); + + if (directed) { + igraph_inclist_destroy(&elist_out); + igraph_inclist_destroy(&elist_in); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_inclist_destroy(&elist_out); + IGRAPH_FINALLY_CLEAN(1); + } + + if (merges || bridges || modularity || membership) { + IGRAPH_CHECK(igraph_community_eb_get_merges(graph, directed, removed_edges, weights, merges, + bridges, modularity, + membership)); + } + + if (result_owned) { + igraph_vector_int_destroy(removed_edges); + IGRAPH_FREE(removed_edges); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/community/fast_modularity.c b/src/community/fast_modularity.c new file mode 100644 index 0000000..0b4c8a9 --- /dev/null +++ b/src/community/fast_modularity.c @@ -0,0 +1,1072 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_memory.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_structural.h" +#include "igraph_vector_ptr.h" + +#include "core/interruption.h" + +/* #define IGRAPH_FASTCOMM_DEBUG */ + +#ifdef IGRAPH_FASTCOMM_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif + +/* + * Implementation of the community structure algorithm originally published + * by Clauset et al in: + * + * A. Clauset, M.E.J. Newman and C. Moore, "Finding community structure in + * very large networks.". Phys. Rev. E 70, 066111 (2004). + * + * The data structures being used are slightly different and they are described + * most closely in: + * + * K. Wakita, T. Tsurumi, "Finding community structure in mega-scale social + * networks.". arXiv:cs/0702048v1. + * + * We maintain a vector of communities, each of which containing a list of + * pointers to their neighboring communities along with the increase in the + * modularity score that could be achieved by joining the two communities. + * Each community has a pointer to one of its neighbors - the one which would + * result in the highest increase in modularity after a join. The local + * (community-level) maximums are also stored in an indexed max-heap. The + * max-heap itself stores its elements in an array which satisfies the heap + * property, but to allow us to access any of the elements in the array based + * on the community index (and not based on the array index - which depends on + * the element's actual position in the heap), we also maintain an index + * vector in the heap: the ith element of the index vector contains the + * position of community i in the array of the max-heap. When we perform + * sifting operations on the heap to restore the heap property, we also maintain + * the index vector. + */ + +/* Structure storing a pair of communities along with their dQ values */ +typedef struct s_igraph_i_fastgreedy_commpair { + igraph_int_t first; /* first member of the community pair */ + igraph_int_t second; /* second member of the community pair */ + igraph_real_t *dq; /* pointer to a member of the dq vector storing the */ + /* increase in modularity achieved when joining */ + struct s_igraph_i_fastgreedy_commpair *opposite; +} igraph_i_fastgreedy_commpair; + +/* Structure storing a community */ +typedef struct { + igraph_int_t id; /* Identifier of the community (for merges matrix) */ + igraph_int_t size; /* Size of the community */ + igraph_vector_ptr_t neis; /* references to neighboring communities */ + igraph_i_fastgreedy_commpair *maxdq; /* community pair with maximal dq */ +} igraph_i_fastgreedy_community; + +/* Global community list structure */ +typedef struct { + igraph_int_t no_of_communities, n; /* number of communities, number of vertices */ + igraph_i_fastgreedy_community *e; /* list of communities */ + igraph_i_fastgreedy_community **heap; /* heap of communities */ + igraph_int_t *heapindex; /* heap index to speed up lookup by community idx */ +} igraph_i_fastgreedy_community_list; + +/* Scans the community neighborhood list for the new maximal dq value. + * Returns true if the maximum is different from the previous one, + * false otherwise. */ +static igraph_bool_t igraph_i_fastgreedy_community_rescan_max(igraph_i_fastgreedy_community *comm) { + igraph_int_t i, n; + igraph_i_fastgreedy_commpair *p, *best; + igraph_real_t bestdq, currdq; + + n = igraph_vector_ptr_size(&comm->neis); + if (n == 0) { + comm->maxdq = NULL; + return true; + } + + best = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[0]; + bestdq = *best->dq; + for (i = 1; i < n; i++) { + p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i]; + currdq = *p->dq; + if (currdq > bestdq) { + best = p; + bestdq = currdq; + } + } + + if (best != comm->maxdq) { + comm->maxdq = best; + return true; + } else { + return false; + } +} + +/* Destroys the global community list object */ +static void igraph_i_fastgreedy_community_list_destroy( + igraph_i_fastgreedy_community_list *list) { + igraph_int_t i; + for (i = 0; i < list->n; i++) { + igraph_vector_ptr_destroy(&list->e[i].neis); + } + IGRAPH_FREE(list->e); + if (list->heapindex != NULL) { + IGRAPH_FREE(list->heapindex); + } + if (list->heap != NULL) { + IGRAPH_FREE(list->heap); + } +} + +/* Community list heap maintenance: sift down */ +static void igraph_i_fastgreedy_community_list_sift_down( + igraph_i_fastgreedy_community_list *list, igraph_int_t idx) { + igraph_int_t root, child, c1, c2; + igraph_i_fastgreedy_community *dummy; + igraph_int_t dummy2; + igraph_i_fastgreedy_community** heap = list->heap; + igraph_int_t *heapindex = list->heapindex; + + root = idx; + while (root * 2 + 1 < list->no_of_communities) { + child = root * 2 + 1; + if (child + 1 < list->no_of_communities && + *heap[child]->maxdq->dq < *heap[child + 1]->maxdq->dq) { + child++; + } + if (*heap[root]->maxdq->dq < *heap[child]->maxdq->dq) { + c1 = heap[root]->maxdq->first; + c2 = heap[child]->maxdq->first; + + dummy = heap[root]; + heap[root] = heap[child]; + heap[child] = dummy; + + dummy2 = heapindex[c1]; + heapindex[c1] = heapindex[c2]; + heapindex[c2] = dummy2; + + root = child; + } else { + break; + } + } +} + +/* Community list heap maintenance: sift up */ +static void igraph_i_fastgreedy_community_list_sift_up( + igraph_i_fastgreedy_community_list *list, igraph_int_t idx) { + igraph_int_t root, parent, c1, c2; + igraph_i_fastgreedy_community *dummy; + igraph_int_t dummy2; + igraph_i_fastgreedy_community** heap = list->heap; + igraph_int_t *heapindex = list->heapindex; + + root = idx; + while (root > 0) { + parent = (root - 1) / 2; + if (*heap[parent]->maxdq->dq < *heap[root]->maxdq->dq) { + c1 = heap[root]->maxdq->first; + c2 = heap[parent]->maxdq->first; + + dummy = heap[parent]; + heap[parent] = heap[root]; + heap[root] = dummy; + + dummy2 = heapindex[c1]; + heapindex[c1] = heapindex[c2]; + heapindex[c2] = dummy2; + + root = parent; + } else { + break; + } + } +} + +/* Builds the community heap for the first time */ +static void igraph_i_fastgreedy_community_list_build_heap( + igraph_i_fastgreedy_community_list *list) { + igraph_int_t i; + for (i = list->no_of_communities / 2 - 1; i >= 0; i--) { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } +} + +/* Finds the element belonging to a given community in the heap and return its + * index in the heap array */ +#define igraph_i_fastgreedy_community_list_find_in_heap(list, idx) (list)->heapindex[idx] + +/* Dumps the heap - for debugging purposes */ +/* +static void igraph_i_fastgreedy_community_list_dump_heap( + igraph_i_fastgreedy_community_list *list) { + igraph_int_t i; + debug("Heap:\n"); + for (i = 0; i < list->no_of_communities; i++) { + debug("(%ld, %p, %p)", i, list->heap[i], + list->heap[i]->maxdq); + if (list->heap[i]->maxdq) { + debug(" (%" IGRAPH_PRId ", %" IGRAPH_PRId ", %.7f)", list->heap[i]->maxdq->first, + list->heap[i]->maxdq->second, *list->heap[i]->maxdq->dq); + } + debug("\n"); + } + debug("Heap index:\n"); + for (i = 0; i < list->no_of_communities; i++) { + debug("%" IGRAPH_PRId " ", list->heapindex[i]); + } + debug("\nEND\n"); +} +*/ + +/* Checks if the community heap satisfies the heap property. + * Only useful for debugging. */ +/* +static void igraph_i_fastgreedy_community_list_check_heap( + igraph_i_fastgreedy_community_list *list) { + igraph_int_t i; + for (i = 0; i < list->no_of_communities / 2; i++) { + if ((2 * i + 1 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 1]->maxdq->dq) || + (2 * i + 2 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 2]->maxdq->dq)) { + IGRAPH_WARNING("Heap property violated"); + debug("Position: %" IGRAPH_PRId ", %" IGRAPH_PRId " and %" IGRAPH_PRId "\n", i, 2 * i + 1, 2 * i + 2); + igraph_i_fastgreedy_community_list_dump_heap(list); + } + } +} +*/ + +/* Removes a given element from the heap */ +static void igraph_i_fastgreedy_community_list_remove( + igraph_i_fastgreedy_community_list *list, igraph_int_t idx) { + igraph_real_t old; + igraph_int_t commidx; + + /* First adjust the index */ + commidx = list->heap[list->no_of_communities - 1]->maxdq->first; + list->heapindex[commidx] = idx; + commidx = list->heap[idx]->maxdq->first; + list->heapindex[commidx] = -1; + + /* Now remove the element */ + old = *list->heap[idx]->maxdq->dq; + list->heap[idx] = list->heap[list->no_of_communities - 1]; + list->no_of_communities--; + + /* Recover heap property */ + if (old > *list->heap[idx]->maxdq->dq) { + igraph_i_fastgreedy_community_list_sift_down(list, idx); + } else { + igraph_i_fastgreedy_community_list_sift_up(list, idx); + } +} + +/* Removes a given element from the heap when there are no more neighbors + * for it (comm->maxdq is NULL) */ +static void igraph_i_fastgreedy_community_list_remove2( + igraph_i_fastgreedy_community_list *list, igraph_int_t idx, igraph_int_t comm) { + igraph_int_t i; + + if (idx == list->no_of_communities - 1) { + /* We removed the rightmost element on the bottom level, no problem, + * there's nothing to be done */ + list->heapindex[comm] = -1; + list->no_of_communities--; + return; + } + + /* First adjust the index */ + i = list->heap[list->no_of_communities - 1]->maxdq->first; + list->heapindex[i] = idx; + list->heapindex[comm] = -1; + + /* Now remove the element */ + list->heap[idx] = list->heap[list->no_of_communities - 1]; + list->no_of_communities--; + + /* Recover heap property */ + for (i = list->no_of_communities / 2 - 1; i >= 0; i--) { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } +} + +/* Removes the pair belonging to community k from the neighborhood list + * of community c (that is, clist[c]) and recalculates maxdq */ +static void igraph_i_fastgreedy_community_remove_nei( + igraph_i_fastgreedy_community_list *list, igraph_int_t c, igraph_int_t k) { + igraph_int_t i, n; + igraph_bool_t rescan = false; + igraph_i_fastgreedy_commpair *p; + igraph_i_fastgreedy_community *comm; + igraph_real_t olddq; + + comm = &list->e[c]; + n = igraph_vector_ptr_size(&comm->neis); + for (i = 0; i < n; i++) { + p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i]; + if (p->second == k) { + /* Check current maxdq */ + if (comm->maxdq == p) { + rescan = true; + } + break; + } + } + if (i < n) { + olddq = *comm->maxdq->dq; + igraph_vector_ptr_remove(&comm->neis, i); + if (rescan) { + igraph_i_fastgreedy_community_rescan_max(comm); + i = igraph_i_fastgreedy_community_list_find_in_heap(list, c); + if (comm->maxdq) { + if (*comm->maxdq->dq > olddq) { + igraph_i_fastgreedy_community_list_sift_up(list, i); + } else { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } + } else { + /* no more neighbors for this community. we should remove this + * community from the heap and restore the heap property */ + debug("REMOVING (NO MORE NEIS): %" IGRAPH_PRId "\n", i); + igraph_i_fastgreedy_community_list_remove2(list, i, c); + } + } + } +} + +/* Auxiliary function to sort a community pair list with respect to the + * `second` field */ +static int igraph_i_fastgreedy_commpair_cmp(const void *p1, const void *p2) { + igraph_i_fastgreedy_commpair *cp1, *cp2; + igraph_int_t diff; + cp1 = *(igraph_i_fastgreedy_commpair**)p1; + cp2 = *(igraph_i_fastgreedy_commpair**)p2; + diff = cp1->second - cp2->second; + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; +} + +/* Sorts the neighbor list of the community with the given index, optionally + * optimizing the process if we know that the list is nearly sorted and only + * a given pair is in the wrong place. */ +static void igraph_i_fastgreedy_community_sort_neighbors_of( + igraph_i_fastgreedy_community_list *list, igraph_int_t index, + igraph_i_fastgreedy_commpair *changed_pair) { + igraph_vector_ptr_t *vec; + igraph_int_t i, n; + igraph_bool_t can_skip_sort = false; + igraph_i_fastgreedy_commpair *other_pair; + + vec = &list->e[index].neis; + if (changed_pair != NULL) { + /* Optimized sorting */ + + /* First we look for changed_pair in vec */ + n = igraph_vector_ptr_size(vec); + for (i = 0; i < n; i++) { + if (VECTOR(*vec)[i] == changed_pair) { + break; + } + } + + /* Did we find it? We should have -- otherwise it's a bug */ + IGRAPH_ASSERT(i < n); + + /* Okay, the pair that changed is at index i. We need to figure out where + * its new place should be. We can simply try moving the item all the way + * to the left as long as the comparison function tells so (since the + * rest of the vector is sorted), and then move all the way to the right + * as long as the comparison function tells so, and we will be okay. */ + + /* Shifting to the left */ + while (i > 0) { + other_pair = VECTOR(*vec)[i - 1]; + if (other_pair->second > changed_pair->second) { + VECTOR(*vec)[i] = other_pair; + i--; + } else { + break; + } + } + VECTOR(*vec)[i] = changed_pair; + + /* Shifting to the right */ + while (i < n - 1) { + other_pair = VECTOR(*vec)[i + 1]; + if (other_pair->second < changed_pair->second) { + VECTOR(*vec)[i] = other_pair; + i++; + } else { + break; + } + } + VECTOR(*vec)[i] = changed_pair; + + /* Mark that we don't need a full sort */ + can_skip_sort = true; + } + + if (!can_skip_sort) { + /* Fallback to full sorting */ + igraph_vector_ptr_sort(vec, igraph_i_fastgreedy_commpair_cmp); + } +} + +/* Updates the dq value of community pair p in the community with index p->first + * of the community list clist to newdq and restores the heap property + * in community c if necessary. Returns 1 if the maximum in the row had + * to be updated, zero otherwise */ +static igraph_bool_t igraph_i_fastgreedy_community_update_dq( + igraph_i_fastgreedy_community_list *list, + igraph_i_fastgreedy_commpair *p, igraph_real_t newdq) { + + igraph_int_t i, j, to, from; + igraph_real_t olddq; + igraph_i_fastgreedy_community *comm_to, *comm_from; + to = p->first; from = p->second; + comm_to = &list->e[to]; + comm_from = &list->e[from]; + if (comm_to->maxdq == p && newdq >= *p->dq) { + /* If we are adjusting the current maximum and it is increased, we don't + * have to re-scan for the new maximum */ + *p->dq = newdq; + /* The maximum was increased, so perform a sift-up in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_up(list, i); + /* Let's check the opposite side. If the pair was not the maximal in + * the opposite side (the other community list)... */ + if (comm_from->maxdq != p->opposite) { + if (*comm_from->maxdq->dq < newdq) { + /* ...and it will become the maximal, we need to adjust and sift up */ + comm_from->maxdq = p->opposite; + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } else { + /* The pair was not the maximal in the opposite side and it will + * NOT become the maximal, there's nothing to do there */ + } + } else { + /* The pair was maximal in the opposite side, so we need to sift it up + * with the new value */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + return true; + } else if (comm_to->maxdq != p && (newdq <= *comm_to->maxdq->dq)) { + /* If we are modifying an item which is not the current maximum, and the + * new value is less than the current maximum, we don't + * have to re-scan for the new maximum */ + olddq = *p->dq; + *p->dq = newdq; + /* However, if the item was the maximum on the opposite side, we'd better + * re-scan it */ + if (comm_from->maxdq == p->opposite) { + if (olddq > newdq) { + /* Decreased the maximum on the other side, we have to re-scan for the + * new maximum */ + igraph_i_fastgreedy_community_rescan_max(comm_from); + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_down(list, j); + } else { + /* Increased the maximum on the other side, we don't have to re-scan + * but we might have to sift up */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + } + return false; + } else { + /* We got here in two cases: + (1) the pair we are modifying right now is the maximum in the given + community and we are decreasing it + (2) the pair we are modifying right now is NOT the maximum in the + given community, but we increase it so much that it will become + the new maximum + */ + *p->dq = newdq; + if (comm_to->maxdq != p) { + /* case (2) */ + comm_to->maxdq = p; + /* The maximum was increased, so perform a sift-up in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_up(list, i); + /* Opposite side. Chances are that the new value became the maximum + * in the opposite side, but check it first */ + if (comm_from->maxdq != p->opposite) { + if (*comm_from->maxdq->dq < newdq) { + /* Yes, it will become the new maximum */ + comm_from->maxdq = p->opposite; + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } else { + /* No, nothing to do there */ + } + } else { + /* Already increased the maximum on the opposite side, so sift it up */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + } else { + /* case (1) */ + /* This is the worst, we have to re-scan the whole community to find + * the new maximum and update the global maximum as well if necessary */ + igraph_i_fastgreedy_community_rescan_max(comm_to); + /* The maximum was decreased, so perform a sift-down in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_down(list, i); + if (comm_from->maxdq != p->opposite) { + /* The one that we decreased on the opposite side is not the + * maximal one. Nothing to do. */ + } else { + /* We decreased the maximal on the opposite side as well. Re-scan + * and sift down */ + igraph_i_fastgreedy_community_rescan_max(comm_from); + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_down(list, j); + } + } + } + return true; +} + +/** + * \function igraph_community_fastgreedy + * \brief Finding community structure by greedy optimization of modularity. + * + * This function implements the fast greedy modularity optimization + * algorithm for finding community structure, see + * A Clauset, MEJ Newman, C Moore: Finding community structure in very + * large networks, http://www.arxiv.org/abs/cond-mat/0408187 for the + * details. + * + * + * Some improvements proposed in K Wakita, T Tsurumi: Finding community + * structure in mega-scale social networks, + * http://www.arxiv.org/abs/cs.CY/0702048v1 have also been implemented. + * + * \param graph The input graph. It must be a graph without multiple edges. + * This is checked and an error message is given for graphs with multiple + * edges. + * \param weights Potentially a numeric vector containing edge + * weights. Supply a null pointer here for unweighted graphs. The + * weights are expected to be non-negative. + * \param merges Pointer to an initialized matrix or \c NULL, the result of the + * computation is stored here as a merges matrix representing a dendrogram. + * The matrix has two columns and each merge corresponds to one merge, the + * IDs of the two merged components are stored. The component IDs are numbered + * from zero and the first \c n components are the individual vertices, \c n is + * the number of vertices in the graph. Component \c n is created + * in the first merge, component n+1 in the second merge, etc. + * The matrix will be resized as needed. If this argument is \c NULL + * then it is ignored completely. + * \param modularity Pointer to an initialized vector or \c NULL pointer, + * in the former case the modularity scores along the stages of the + * computation are recorded here. The vector will be resized as + * needed. + * \param membership Pointer to a vector. If not a null pointer, then + * the membership vector corresponding to the best split (in terms + * of modularity) is stored here. + * \return Error code. + * + * \sa \ref igraph_community_to_membership() to cut the dendrogram at + * an arbitrary number of steps. + * + * Time complexity: O(|E||V|log|V|) in the worst case, + * O(|E|+|V|log^2|V|) typically, |V| is the number of vertices, |E| is + * the number of edges. + * + * \example examples/simple/igraph_community_fastgreedy.c + */ +igraph_error_t igraph_community_fastgreedy(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership) { + igraph_int_t no_of_edges, no_of_nodes, no_of_joins, total_joins; + igraph_int_t i, j, k, n, m, from, to, dummy, best_no_of_joins; + igraph_eit_t edgeit; + igraph_i_fastgreedy_commpair *pairs, *p1, *p2; + igraph_i_fastgreedy_community_list communities; + igraph_vector_t a; + igraph_vector_int_t degrees; + igraph_real_t q, *dq, bestq, weight_sum, loop_weight_sum; + igraph_bool_t has_multiple; + igraph_matrix_int_t merges_local; + + /*igraph_int_t join_order[] = { 16,5, 5,6, 6,0, 4,0, 10,0, 26,29, 29,33, 23,33, 27,33, 25,24, 24,31, 12,3, 21,1, 30,8, 8,32, 9,2, 17,1, 11,0, 7,3, 3,2, 13,2, 1,2, 28,31, 31,33, 22,32, 18,32, 20,32, 32,33, 15,33, 14,33, 0,19, 19,2, -1,-1 };*/ + /*igraph_int_t join_order[] = { 43,42, 42,41, 44,41, 41,36, 35,36, 37,36, 36,29, 38,29, 34,29, 39,29, 33,29, 40,29, 32,29, 14,29, 30,29, 31,29, 6,18, 18,4, 23,4, 21,4, 19,4, 27,4, 20,4, 22,4, 26,4, 25,4, 24,4, 17,4, 0,13, 13,2, 1,2, 11,2, 8,2, 5,2, 3,2, 10,2, 9,2, 7,2, 2,28, 28,15, 12,15, 29,16, 4,15, -1,-1 };*/ + + no_of_nodes = igraph_vcount(graph); + no_of_edges = igraph_ecount(graph); + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Fast greedy community detection works on undirected graphs only.", IGRAPH_UNIMPLEMENTED); + } + + total_joins = no_of_nodes > 0 ? no_of_nodes - 1 : 0; + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Length of weight vector must agree with number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weights must not be negative.", IGRAPH_EINVAL); + } + if (isnan(minweight)) { + IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL); + } + } + weight_sum = igraph_vector_sum(weights); + } else { + weight_sum = no_of_edges; + } + + IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple)); + if (has_multiple) { + IGRAPH_ERROR("Fast greedy community detection works only on graphs without multi-edges.", IGRAPH_EINVAL); + } + + if (membership != NULL && merges == NULL) { + /* We need the merge matrix because the user wants the membership + * vector, so we allocate one on our own */ + IGRAPH_CHECK(igraph_matrix_int_init(&merges_local, total_joins, 2)); + IGRAPH_FINALLY(igraph_matrix_int_destroy, &merges_local); + merges = &merges_local; + } + + if (merges != NULL) { + IGRAPH_CHECK(igraph_matrix_int_resize(merges, total_joins, 2)); + igraph_matrix_int_null(merges); + } + + if (modularity != NULL) { + IGRAPH_CHECK(igraph_vector_resize(modularity, total_joins + 1)); + } + + /* Create degree vector */ + IGRAPH_VECTOR_INIT_FINALLY(&a, no_of_nodes); + if (weights) { + debug("Calculating weighted degrees\n"); + for (i = 0; i < no_of_edges; i++) { + VECTOR(a)[IGRAPH_FROM(graph, i)] += VECTOR(*weights)[i]; + VECTOR(a)[IGRAPH_TO(graph, i)] += VECTOR(*weights)[i]; + } + } else { + debug("Calculating degrees\n"); + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(a)[i] = VECTOR(degrees)[i]; + } + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Create list of communities */ + debug("Creating community list\n"); + communities.n = no_of_nodes; + communities.no_of_communities = no_of_nodes; + communities.e = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community); + IGRAPH_CHECK_OOM(communities.e, "Insufficient memory for fast greedy community detection."); + IGRAPH_FINALLY(igraph_free, communities.e); + + communities.heap = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community*); + IGRAPH_CHECK_OOM(communities.heap, "Insufficient memory for fast greedy community detection."); + IGRAPH_FINALLY(igraph_free, communities.heap); + + communities.heapindex = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(communities.heapindex, "Insufficient memory for fast greedy community detection."); + + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_FINALLY(igraph_i_fastgreedy_community_list_destroy, &communities); + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_vector_ptr_init(&communities.e[i].neis, 0)); + communities.e[i].id = i; + communities.e[i].size = 1; + } + + /* Create list of community pairs from edges */ + debug("Allocating dq vector\n"); + dq = IGRAPH_CALLOC(no_of_edges, igraph_real_t); + IGRAPH_CHECK_OOM(dq, "Insufficient memory for fast greedy community detection."); + IGRAPH_FINALLY(igraph_free, dq); + + debug("Creating community pair list\n"); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + pairs = IGRAPH_CALLOC(2 * no_of_edges, igraph_i_fastgreedy_commpair); + IGRAPH_CHECK_OOM(pairs, "Insufficient memory for fast greedy community detection."); + IGRAPH_FINALLY(igraph_free, pairs); + + loop_weight_sum = 0; + for (i = 0, j = 0; !IGRAPH_EIT_END(edgeit); i += 2, j++, IGRAPH_EIT_NEXT(edgeit)) { + igraph_int_t eidx = IGRAPH_EIT_GET(edgeit); + + /* Create the pairs themselves */ + from = IGRAPH_FROM(graph, eidx); to = IGRAPH_TO(graph, eidx); + if (from == to) { + loop_weight_sum += weights ? 2 * VECTOR(*weights)[eidx] : 2; + continue; + } + + if (from > to) { + dummy = from; from = to; to = dummy; + } + if (weights) { + dq[j] = 2 * (VECTOR(*weights)[eidx] / (weight_sum * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * weight_sum * weight_sum)); + } else { + dq[j] = 2 * (1.0 / (no_of_edges * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * no_of_edges * no_of_edges)); + } + pairs[i].first = from; + pairs[i].second = to; + pairs[i].dq = &dq[j]; + pairs[i].opposite = &pairs[i + 1]; + pairs[i + 1].first = to; + pairs[i + 1].second = from; + pairs[i + 1].dq = pairs[i].dq; + pairs[i + 1].opposite = &pairs[i]; + /* Link the pair to the communities */ + IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[from].neis, &pairs[i])); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, &pairs[i + 1])); + /* Update maximums */ + if (communities.e[from].maxdq == NULL || *communities.e[from].maxdq->dq < *pairs[i].dq) { + communities.e[from].maxdq = &pairs[i]; + } + if (communities.e[to].maxdq == NULL || *communities.e[to].maxdq->dq < *pairs[i + 1].dq) { + communities.e[to].maxdq = &pairs[i + 1]; + } + } + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + + /* Sorting community neighbor lists by community IDs */ + debug("Sorting community neighbor lists\n"); + for (i = 0, j = 0; i < no_of_nodes; i++) { + igraph_i_fastgreedy_community_sort_neighbors_of(&communities, i, NULL); + /* Isolated vertices and vertices with loop edges only won't be stored in + * the heap (to avoid maxdq == NULL) */ + if (communities.e[i].maxdq != NULL) { + communities.heap[j] = &communities.e[i]; + communities.heapindex[i] = j; + j++; + } else { + communities.heapindex[i] = -1; + } + } + communities.no_of_communities = j; + + /* Calculate proper vector a (see paper) and initial modularity */ + q = 2.0 * (weights ? weight_sum : no_of_edges); + if (q == 0) { + /* All the weights are zero */ + } else { + igraph_vector_scale(&a, 1.0 / q); + q = loop_weight_sum / q; + for (i = 0; i < no_of_nodes; i++) { + q -= VECTOR(a)[i] * VECTOR(a)[i]; + } + } + + /* Initialize "best modularity" value and best merge counter */ + bestq = q; + best_no_of_joins = 0; + + /* Initializing community heap */ + debug("Initializing community heap\n"); + igraph_i_fastgreedy_community_list_build_heap(&communities); + + debug("Initial modularity: %.4f\n", q); + + /* Let's rock ;) */ + no_of_joins = 0; + while (no_of_joins < total_joins) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_PROGRESS("Fast greedy community detection", no_of_joins * 100.0 / total_joins, 0); + + /* Store the modularity */ + if (modularity) { + VECTOR(*modularity)[no_of_joins] = q; + } + + /* Update best modularity if needed */ + if (q >= bestq) { + bestq = q; + best_no_of_joins = no_of_joins; + } + + /* Some debug info if needed */ + /* igraph_i_fastgreedy_community_list_check_heap(&communities); */ +#ifdef IGRAPH_FASTCOMM_DEBUG + debug("===========================================\n"); + for (i = 0; i < communities.n; i++) { + if (communities.e[i].maxdq == 0) { + debug("Community #%ld: PASSIVE\n", i); + continue; + } + debug("Community #%ld\n ", i); + for (j = 0; j < igraph_vector_ptr_size(&communities.e[i].neis); j++) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[i].neis)[j]; + debug(" (%ld,%ld,%.4f)", p1->first, p1->second, *p1->dq); + } + p1 = communities.e[i].maxdq; + debug("\n Maxdq: (%ld,%ld,%.4f)\n", p1->first, p1->second, *p1->dq); + } + debug("Global maxdq is: (%ld,%ld,%.4f)\n", communities.heap[0]->maxdq->first, + communities.heap[0]->maxdq->second, *communities.heap[0]->maxdq->dq); + for (i = 0; i < communities.no_of_communities; i++) { + debug("(%ld,%ld,%.4f) ", communities.heap[i]->maxdq->first, communities.heap[i]->maxdq->second, *communities.heap[0]->maxdq->dq); + } + debug("\n"); +#endif + if (communities.heap[0] == NULL) { + break; /* no more communities */ + } + if (communities.heap[0]->maxdq == NULL) { + break; /* there are only isolated comms */ + } + to = communities.heap[0]->maxdq->second; + from = communities.heap[0]->maxdq->first; + + debug("Q[%ld] = %.7f\tdQ = %.7f\t |H| = %ld\n", + no_of_joins, q, *communities.heap[0]->maxdq->dq, no_of_nodes - no_of_joins - 1); + + /* IGRAPH_FASTCOMM_DEBUG */ + /* from=join_order[no_of_joins*2]; to=join_order[no_of_joins*2+1]; + if (to == -1) break; + for (i=0; isecond == from) communities.maxdq = p1; + } */ + + n = igraph_vector_ptr_size(&communities.e[to].neis); + m = igraph_vector_ptr_size(&communities.e[from].neis); + /*if (n>m) { + dummy=n; n=m; m=dummy; + dummy=to; to=from; from=dummy; + }*/ + debug(" joining: %ld <- %ld\n", to, from); + q += *communities.heap[0]->maxdq->dq; + + /* Merge the second community into the first */ + i = j = 0; + while (i < n && j < m) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i]; + p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j]; + debug("Pairs: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second, + p2->first, p2->second); + if (p1->second < p2->second) { + /* Considering p1 from now on */ + debug(" Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second); + if (p1->second == from) { + debug(" WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", to, from); + } else { + /* chain, case 1 */ + debug(" CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + } + i++; + } else if (p1->second == p2->second) { + /* p1->first, p1->second and p2->first form a triangle */ + debug(" Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second, + p2->first, p2->second); + /* Update dq value */ + debug(" TRIANGLE: %ld-%ld-%ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, *p2->dq, p1->first, p1->second, *p1->dq + *p2->dq); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq + *p2->dq); + igraph_i_fastgreedy_community_remove_nei(&communities, p1->second, from); + i++; + j++; + } else { + debug(" Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->first, p2->second); + if (p2->second == to) { + debug(" WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->second, p2->first); + } else { + /* chain, case 2 */ + debug(" CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n", + to, p2->second, from, to, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + p2->opposite->second = to; + /* p2->opposite->second changed, so it means that + * communities.e[p2->second].neis (which contains p2->opposite) is + * not sorted anymore. We have to find the index of p2->opposite in + * this vector and move it to the correct place. Moving should be an + * O(n) operation; re-sorting would be O(n*logn) or even worse, + * depending on the pivoting strategy used by qsort() since the + * vector is nearly sorted */ + igraph_i_fastgreedy_community_sort_neighbors_of( + &communities, p2->second, p2->opposite); + /* link from.neis[j] to the current place in to.neis if + * from.neis[j] != to */ + p2->first = to; + IGRAPH_CHECK(igraph_vector_ptr_insert(&communities.e[to].neis, i, p2)); + n++; i++; + if (*p2->dq > *communities.e[to].maxdq->dq) { + communities.e[to].maxdq = p2; + k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to); + igraph_i_fastgreedy_community_list_sift_up(&communities, k); + } + igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + } + j++; + } + } + + p1 = NULL; + while (i < n) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i]; + if (p1->second == from) { + debug(" WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, from); + } else { + /* chain, case 1 */ + debug(" CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + } + i++; + } + while (j < m) { + p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j]; + if (to == p2->second) { + j++; + continue; + } + /* chain, case 2 */ + debug(" CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n", + to, p2->second, from, p1 ? p1->first : -1, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + p2->opposite->second = to; + /* need to re-sort community nei list `p2->second` */ + igraph_i_fastgreedy_community_sort_neighbors_of(&communities, p2->second, p2->opposite); + /* link from.neis[j] to the current place in to.neis if + * from.neis[j] != to */ + p2->first = to; + IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, p2)); + if (*p2->dq > *communities.e[to].maxdq->dq) { + communities.e[to].maxdq = p2; + k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to); + igraph_i_fastgreedy_community_list_sift_up(&communities, k); + } + igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + j++; + } + + /* Now, remove community `from` from the neighbors of community `to` */ + if (communities.no_of_communities > 2) { + debug(" REMOVING: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", to, from); + igraph_i_fastgreedy_community_remove_nei(&communities, to, from); + i = igraph_i_fastgreedy_community_list_find_in_heap(&communities, from); + igraph_i_fastgreedy_community_list_remove(&communities, i); + } + communities.e[from].maxdq = NULL; + + /* Update community sizes */ + communities.e[to].size += communities.e[from].size; + communities.e[from].size = 0; + + /* record what has been merged */ + /* igraph_vector_ptr_clear is not enough here as it won't free + * the memory consumed by communities.e[from].neis. Thanks + * to Tom Gregorovic for pointing that out. */ + igraph_vector_ptr_destroy(&communities.e[from].neis); + if (merges) { + MATRIX(*merges, no_of_joins, 0) = communities.e[to].id; + MATRIX(*merges, no_of_joins, 1) = communities.e[from].id; + communities.e[to].id = no_of_nodes + no_of_joins; + } + + /* Update vector a */ + VECTOR(a)[to] += VECTOR(a)[from]; + VECTOR(a)[from] = 0.0; + + no_of_joins++; + } + /* TODO: continue merging when some isolated communities remained. Always + * joining the communities with the least number of nodes results in the + * smallest decrease in modularity every step. Now we're simply deleting + * the excess rows from the merge matrix */ + if (merges != NULL) { + if (no_of_joins < total_joins) { + igraph_int_t *ivec; + igraph_int_t merges_nrow = igraph_matrix_int_nrow(merges); + + ivec = IGRAPH_CALLOC(merges_nrow, igraph_int_t); + IGRAPH_CHECK_OOM(ivec, "Insufficient memory for fast greedy community detection."); + IGRAPH_FINALLY(igraph_free, ivec); + + for (i = 0; i < no_of_joins; i++) { + ivec[i] = i + 1; + } + + igraph_matrix_int_permdelete_rows(merges, ivec, total_joins - no_of_joins); + + IGRAPH_FREE(ivec); + IGRAPH_FINALLY_CLEAN(1); + } + } + IGRAPH_PROGRESS("Fast greedy community detection", 100.0, 0); + + if (modularity) { + VECTOR(*modularity)[no_of_joins] = q; + IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_joins + 1)); + } + + /* Internally, the algorithm does not create NaN values. + * If the graph has no edges, the final modularity will be zero. + * We change this to NaN for consistency. */ + if (modularity && no_of_edges == 0) { + IGRAPH_ASSERT(no_of_joins == 0); + VECTOR(*modularity)[0] = IGRAPH_NAN; + } + + debug("Freeing memory\n"); + IGRAPH_FREE(pairs); + IGRAPH_FREE(dq); + igraph_i_fastgreedy_community_list_destroy(&communities); + igraph_vector_destroy(&a); + IGRAPH_FINALLY_CLEAN(4); + + if (membership) { + IGRAPH_CHECK(igraph_community_to_membership(merges, + no_of_nodes, + /*steps=*/ best_no_of_joins, + membership, + /*csize=*/ NULL)); + } + + if (merges == &merges_local) { + igraph_matrix_int_destroy(&merges_local); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +#ifdef IGRAPH_FASTCOMM_DEBUG + #undef IGRAPH_FASTCOMM_DEBUG +#endif diff --git a/src/community/fluid.c b/src/community/fluid.c new file mode 100644 index 0000000..25bbee5 --- /dev/null +++ b/src/community/fluid.c @@ -0,0 +1,247 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +/** + * \ingroup communities + * \function igraph_community_fluid_communities + * \brief Community detection based on fluids interacting on the graph. + * + * The algorithm is based on the simple idea of + * several fluids interacting in a non-homogeneous environment + * (the graph topology), expanding and contracting based on their + * interaction and density. Weighted graphs are not supported. + * + * + * This function implements the community detection method described in: + * Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive, + * Scalable and Diverse Community Detection Algorithm. In: Complex Networks + * & Their Applications VI: Proceedings of Complex Networks 2017 (The Sixth + * International Conference on Complex Networks and Their Applications), + * Springer, vol 689, p 229. https://doi.org/10.1007/978-3-319-72150-7_19 + * + * \param graph The input graph. The graph must be simple and connected. + * Edge directions will be ignored. + * \param no_of_communities The number of communities to be found. Must be + * greater than 0 and fewer than number of vertices in the graph. + * \param membership The result vector mapping vertices to the communities + * they are assigned to. + * \return Error code. + * + * Time complexity: O(|E|) + */ +igraph_error_t igraph_community_fluid_communities( + const igraph_t *graph, + igraph_int_t no_of_communities, + igraph_vector_int_t *membership) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, j, k, kv1; + igraph_adjlist_t al; + igraph_real_t max_density; + igraph_bool_t is_simple, is_connected, running; + igraph_vector_t density, label_counters; + igraph_vector_int_t dominant_labels, node_order, com_to_numvertices; + + /* Checking input values */ + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored by fluid community detection."); + } + IGRAPH_CHECK(igraph_is_simple(graph, &is_simple, IGRAPH_UNDIRECTED)); + if (!is_simple) { + IGRAPH_ERROR("Fluid community detection supports only simple graphs.", IGRAPH_EINVAL); + } + + /* This must come before the connectedness check so we can support the null + * graph (considered disconnected) for purposes of convenience. */ + if (no_of_nodes < 2) { + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_null(membership); + } + return IGRAPH_SUCCESS; + } + + if (no_of_communities < 1) { + IGRAPH_ERROR("Number of requested communities must be positive.", IGRAPH_EINVAL); + } + + if (no_of_communities > no_of_nodes) { + IGRAPH_ERROR("Number of requested communities must not be greater than the number of nodes.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_connected(graph, &is_connected, IGRAPH_WEAK)); + if (!is_connected) { + IGRAPH_ERROR("Fluid community detection supports only connected graphs.", IGRAPH_EINVAL); + } + + /* Internal variables initialization */ + max_density = 1.0; + + /* Resize membership vector (number of nodes) */ + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + + /* Initialize density and com_to_numvertices vectors */ + IGRAPH_CHECK(igraph_vector_init(&density, no_of_communities)); + IGRAPH_FINALLY(igraph_vector_destroy, &density); + IGRAPH_CHECK(igraph_vector_int_init(&com_to_numvertices, no_of_communities)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &com_to_numvertices); + + /* Initialize node ordering vector */ + IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order); + + /* Initialize the membership vector with 0 values */ + igraph_vector_int_null(membership); + /* Initialize densities to max_density */ + igraph_vector_fill(&density, max_density); + + /* Initialize com_to_numvertices and initialize communities into membership vector */ + igraph_vector_int_shuffle(&node_order); + for (i = 0; i < no_of_communities; i++) { + /* Initialize membership at initial nodes for each community + * where 0 refers to have no label*/ + VECTOR(*membership)[VECTOR(node_order)[i]] = i + 1; + /* Initialize com_to_numvertices list: Number of vertices for each community */ + VECTOR(com_to_numvertices)[i] = 1; + } + + /* Create an adjacency list representation for efficiency. */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + /* Create storage space for counting distinct labels and dominant ones */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, no_of_communities); + + IGRAPH_CHECK(igraph_vector_init(&label_counters, no_of_communities)); + IGRAPH_FINALLY(igraph_vector_destroy, &label_counters); + + /* running is the convergence boolean variable */ + running = true; + while (running) { + /* Declarations of variables used inside main loop */ + igraph_int_t v1, size, rand_idx; + igraph_real_t max_count, label_counter_diff; + igraph_vector_int_t *neis; + igraph_bool_t same_label_in_dominant; + + running = false; + + /* Shuffle the node ordering vector */ + igraph_vector_int_shuffle(&node_order); + /* In the prescribed order, loop over the vertices and reassign labels */ + for (i = 0; i < no_of_nodes; i++) { + /* Clear dominant_labels and nonzero_labels vectors */ + igraph_vector_int_clear(&dominant_labels); + igraph_vector_null(&label_counters); + + /* Obtain actual node index */ + v1 = VECTOR(node_order)[i]; + /* Take into account same label in updating rule */ + kv1 = VECTOR(*membership)[v1]; + max_count = 0.0; + if (kv1 != 0) { + VECTOR(label_counters)[kv1 - 1] += VECTOR(density)[kv1 - 1]; + /* Set up max_count */ + max_count = VECTOR(density)[kv1 - 1]; + /* Initialize dominant_labels */ + IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = kv1; + } + + /* Count the weights corresponding to different labels */ + neis = igraph_adjlist_get(&al, v1); + size = igraph_vector_int_size(neis); + for (j = 0; j < size; j++) { + k = VECTOR(*membership)[VECTOR(*neis)[j]]; + /* skip if it has no label yet */ + if (k == 0) { + continue; + } + /* Update label counter and evaluate diff against max_count*/ + VECTOR(label_counters)[k - 1] += VECTOR(density)[k - 1]; + label_counter_diff = VECTOR(label_counters)[k - 1] - max_count; + /* Check if this label must be included in dominant_labels vector */ + if (label_counter_diff > 0.0001) { + max_count = VECTOR(label_counters)[k - 1]; + IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (-0.0001 < label_counter_diff && label_counter_diff < 0.0001) { + IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k)); + } + } + + if (!igraph_vector_int_empty(&dominant_labels)) { + /* Maintain same label if it exists in dominant_labels */ + same_label_in_dominant = igraph_vector_int_contains(&dominant_labels, kv1); + + if (!same_label_in_dominant) { + /* We need at least one more iteration */ + running = true; + + /* Select randomly from the dominant labels */ + rand_idx = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1); + k = VECTOR(dominant_labels)[rand_idx]; + + if (kv1 != 0) { + /* Subtract 1 vertex from corresponding community in com_to_numvertices */ + VECTOR(com_to_numvertices)[kv1 - 1] -= 1; + /* Re-calculate density for community kv1 */ + VECTOR(density)[kv1 - 1] = max_density / VECTOR(com_to_numvertices)[kv1 - 1]; + } + + /* Update vertex new label */ + VECTOR(*membership)[v1] = k; + + /* Add 1 vertex to corresponding new community in com_to_numvertices */ + VECTOR(com_to_numvertices)[k - 1] += 1; + /* Re-calculate density for new community k */ + VECTOR(density)[k - 1] = max_density / VECTOR(com_to_numvertices)[k - 1]; + } + } + } + } + + /* Shift back the membership vector */ + /* There must be no 0 labels in membership vector at this point */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] -= 1; + IGRAPH_ASSERT(VECTOR(*membership)[i] >= 0); /* all vertices must have a community assigned */ + } + + igraph_adjlist_destroy(&al); + igraph_vector_int_destroy(&node_order); + igraph_vector_destroy(&density); + igraph_vector_int_destroy(&com_to_numvertices); + igraph_vector_destroy(&label_counters); + igraph_vector_int_destroy(&dominant_labels); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/infomap.cpp b/src/community/infomap.cpp new file mode 100644 index 0000000..4ae90e9 --- /dev/null +++ b/src/community/infomap.cpp @@ -0,0 +1,275 @@ +/* + igraph library. + Copyright (C) 2011-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" + +#include "config.h" + +#ifdef HAVE_INFOMAP + +#include "igraph_interface.h" +#include "igraph_interrupt.h" + +#include "core/exceptions.h" + +#include +#include + +#include "Infomap.h" + +static igraph_error_t infomap_get_membership(infomap::InfomapBase &infomap, igraph_vector_int_t *membership) { + igraph_int_t n = infomap.numLeafNodes(); + + IGRAPH_CHECK(igraph_vector_int_resize(membership, n)); + + for (auto it(infomap.iterTreePhysical(1)); !it.isEnd(); ++it) { + infomap::InfoNode &node = *it; + if (node.isLeaf()) { + // Note: We must use moduleIndex() and not moduleId(), as the latter + // may be >= vcount, which causes igraph_reindex_membership() to fail. + VECTOR(*membership)[node.physicalId] = it.moduleIndex(); + } + } + + // Re-index membership + IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t convert_igraph_to_infomap(const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_weights, + infomap::Network &network) { + + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + + if (vcount > UINT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for Infomap.", IGRAPH_EINVAL); + } + + for (igraph_int_t v = 0; v < vcount; v++) { + if (vertex_weights) { + double weight = VECTOR(*vertex_weights)[v]; + if (weight < 0) { + IGRAPH_ERRORF("Vertex weights must not be negative, got %g.", + IGRAPH_EINVAL, weight); + } + if (! std::isfinite(weight)) { + IGRAPH_ERRORF("Vertex weights must not be infinite or NaN, got %g.", + IGRAPH_EINVAL, weight); + } + network.addNode(v, weight); + } else { + network.addNode(v); + } + } + + for (igraph_int_t e = 0; e < ecount; e++) { + igraph_int_t v1 = IGRAPH_FROM(graph, e); + igraph_int_t v2 = IGRAPH_TO(graph, e); + + if (edge_weights) { + double weight = VECTOR(*edge_weights)[e]; + if (weight < 0) { + IGRAPH_ERRORF("Edge weights must not be negative, got %g.", + IGRAPH_EINVAL, weight); + } + if (! std::isfinite(weight)) { + IGRAPH_ERRORF("Edge weights must not be infinite or NaN, got %g.", + IGRAPH_EINVAL, weight); + } + network.addLink(v1, v2, weight); + } else { + network.addLink(v1, v2); + } + } + + return IGRAPH_SUCCESS; +} + +// Needed in case C++'s bool is not compatible with igraph's igraph_bool_t +// which may happen in some configurations on some platforms, notable with R/igraph. +static bool infomap_allow_interruption() { + return igraph_allow_interruption(); +} +#endif // HAVE_INFOMAP + +/** + * \function igraph_community_infomap + * \brief Community structure that minimizes the expected description length of a random walker trajectory. + * + * Implementation of the Infomap community detection algorithm of + * Martin Rosvall and Carl T. Bergstrom. This algorithm takes edge directions + * into account. For more details, see the visualization of the math and the + * map generator at https://www.mapequation.org. + * + * + * Infomap is based on a random walker model similar to PageRank: the walker + * either chooses out-edges to follow with probabilities proportional to edge + * weights, or teleports to a random vertex with probability 0.15. Vertex weights + * can be given to control the probability of choosing different vertices as + * the target of the teleportation. In addition, Infomap can be regularized to + * account for potential missing links. + * + * + * As of igraph 1.0, the Infomap library written by Daniel Edler, Anton Holmgren + * and Martin Rosvall is used. See https://github.com/mapequation/infomap/. + * + * + * If you want to specify a random seed (as in the original + * implementation) you can use \ref igraph_rng_seed(). + * + * + * References: + * + * + * M. Rosvall and C. T. Bergstrom: + * Maps of information flow reveal community structure in complex networks, + * PNAS 105, 1118 (2008). + * https://dx.doi.org/10.1073/pnas.0706851105, https://arxiv.org/abs/0707.0609 + * + * + * M. Rosvall, D. Axelsson, and C. T. Bergstrom: + * The map equation, + * Eur. Phys. J. Special Topics 178, 13 (2009). + * https://dx.doi.org/10.1140/epjst/e2010-01179-1, https://arxiv.org/abs/0906.1405 + * + * + * Smiljanić, Jelena, Daniel Edler, and Martin Rosvall: Mapping Flows on + * Sparse Networks with Missing Links. Phys Rev E 102 (1–1): 012302 (2020). + * https://doi.org/10.1103/PhysRevE.102.012302, https://arxiv.org/abs/2106.14798 + * + * \param graph The input graph. Edge directions are taken into account. + * \param edge_weights Numeric vector giving the weights of the edges. + * The random walker will favour edges with high weights over + * edges with low weights; the probability of picking a particular + * outbound edge from a node is directly proportional to its weight. + * If it is \c NULL then all edges will have equal + * weights. The weights are expected to be non-negative. + * \param vertex_weights Numeric vector giving the weights of the vertices. + * Vertices with higher weights are favoured by the random walker + * when it teleports to a new vertex. The probability of picking a vertex + * when the random walker teleports is directly proportional to the weight + * of the vertex. If this argument is \c NULL then all vertices will have + * equal weights. Weights are expected to be positive. + * \param nb_trials The number of attempts to partition the network + * (can be any integer value equal to or larger than 1). + * \param is_regularized If true, adds a fully connected Bayesian prior network + * to avoid overfitting due to missing links. + * \param regularization_strength Adjust relative strength of the Bayesian prior + * network used for regularization. This multiplies the default strength, a + * parameter of 1 hence uses the default regularization strength. Ignored + * when \p is_regularized is set to \c false. + * \param membership Pointer to a vector. The membership vector is + * stored here. \c NULL means that the caller is not interested in the + * membership vector. + * \param codelength Pointer to a real. If not \c NULL the code length of the + * partition is stored here. + * \return Error code. + * When Infomap is not available, \c IGRAPH_UNIMPLEMENTED is returned. + * + * \sa \ref igraph_community_spinglass(), \ref + * igraph_community_edge_betweenness(), \ref igraph_community_walktrap(). + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_community_infomap( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_weights, + igraph_int_t nb_trials, + igraph_bool_t is_regularized, + igraph_real_t regularization_strength, + igraph_vector_int_t *membership, + igraph_real_t *codelength) { + +#ifndef HAVE_INFOMAP + IGRAPH_ERROR("Infomap is not available.", IGRAPH_UNIMPLEMENTED); +#else + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + + if (edge_weights) { + if (igraph_vector_size(edge_weights) != ecount) { + IGRAPH_ERROR("Length of edge weight vector does not match edge count.", + IGRAPH_EINVAL); + } + } + + if (vertex_weights) { + if (igraph_vector_size(vertex_weights) != vcount) { + IGRAPH_ERROR("Length of vertex weight vector does not match edge count.", + IGRAPH_EINVAL); + } + } + + if (nb_trials < 1) { + IGRAPH_ERRORF("Number of trials must be at least 1, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + nb_trials); + } + + // Handle null graph + if (vcount == 0) { + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, 0)); + } + + if (codelength) { + *codelength = IGRAPH_NAN; + } + + return IGRAPH_SUCCESS; + } + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + // Configure infomap + infomap::Config conf; + conf.twoLevel = true; + conf.numTrials = nb_trials; + conf.silent = true; + conf.directed = igraph_is_directed(graph); + conf.interruptionHandler = &infomap_allow_interruption; + conf.regularized = is_regularized; + conf.regularizationStrength = regularization_strength; + + infomap::InfomapBase infomap(conf); + + IGRAPH_CHECK(convert_igraph_to_infomap(graph, edge_weights, vertex_weights, infomap.network())); + + infomap.run(); + + if (membership) { + IGRAPH_CHECK(infomap_get_membership(infomap, membership)); + } + + if (codelength) { + *codelength = infomap.codelength(); + } + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; + +#endif +} diff --git a/src/community/label_propagation.c b/src/community/label_propagation.c new file mode 100644 index 0000000..a829844 --- /dev/null +++ b/src/community/label_propagation.c @@ -0,0 +1,765 @@ +/* + igraph library. + Copyright (C) 2007-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_random.h" + +#include "core/interruption.h" + +static igraph_error_t community_label_propagation( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_neimode_t mode, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed, + igraph_bool_t retention) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_not_fixed_nodes = 0; + igraph_int_t i, j, k; + igraph_adjlist_t al; + igraph_inclist_t il; + igraph_bool_t running, control_iteration; + + igraph_vector_t label_weights; + igraph_vector_int_t dominant_labels, nonzero_labels, node_order; + igraph_neimode_t reverse_mode; + int iter = 0; /* interruption counter */ + + reverse_mode = IGRAPH_REVERSE_MODE(mode); + + /* Create an adjacency/incidence list representation for efficiency. + * For the unweighted case, the adjacency list is enough. For the + * weighted case, we need the incidence list */ + if (weights) { + IGRAPH_CHECK(igraph_inclist_init(graph, &il, reverse_mode, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, reverse_mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + } + + /* Create storage space for counting distinct labels and dominant ones */ + IGRAPH_VECTOR_INIT_FINALLY(&label_weights, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nonzero_labels, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&dominant_labels, 2)); + + /* Initialize node ordering vector with only the not fixed nodes */ + if (fixed) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(*fixed)[i]) { + VECTOR(node_order)[no_of_not_fixed_nodes] = i; + no_of_not_fixed_nodes++; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes)); + } else { + IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order); + no_of_not_fixed_nodes = no_of_nodes; + } + + /* There are two modes of operation in this implementation: retention or + * dominance. When using retention, we prefer to keep the current label of a node. + * Only if the current label is not among the dominant labels will we + * update the label. If a label changes, we will continue to iterate + * over all nodes. + * + * When not using retention we check for dominance after each iteration. This + * is implemented as two alternating types of iterations, one for changing + * labels and the other one for checking the end condition - every vertex in the + * graph has a label to which the maximum number of its neighbors belongs. If + * control_iteration is true, we are just checking the end condition and not + * relabeling nodes. + */ + + control_iteration = true; + running = true; + while (running) { + igraph_int_t v1, num_neis; + igraph_real_t max_count; + igraph_vector_int_t *neis; + igraph_vector_int_t *ineis; + igraph_bool_t was_zero; + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + + if (retention) { + /* We stop in this iteration by default, unless a label changes */ + running = false; + /* Shuffle the node ordering vector */ + igraph_vector_int_shuffle(&node_order); + } else { + if (control_iteration) { + /* If we are in the control iteration, we expect in the beginning of + the iteration that all vertices meet the end condition, so 'running' is false. + If some of them does not, 'running' is set to true later in the code. */ + running = false; + } else { + /* Shuffle the node ordering vector if we are in the label updating iteration */ + igraph_vector_int_shuffle(&node_order); + } + } + + /* In the prescribed order, loop over the vertices and reassign labels */ + for (i = 0; i < no_of_not_fixed_nodes; i++) { + v1 = VECTOR(node_order)[i]; + + /* Count the weights corresponding to different labels */ + igraph_vector_int_clear(&dominant_labels); + igraph_vector_int_clear(&nonzero_labels); + max_count = 0.0; + if (weights) { + + ineis = igraph_inclist_get(&il, v1); + num_neis = igraph_vector_int_size(ineis); + + for (j = 0; j < num_neis; j++) { + k = VECTOR(*membership)[IGRAPH_OTHER(graph, VECTOR(*ineis)[j], v1)]; + if (k < 0) { + continue; /* skip if it has no label yet */ + } + was_zero = (VECTOR(label_weights)[k] == 0); + VECTOR(label_weights)[k] += VECTOR(*weights)[VECTOR(*ineis)[j]]; + + if (was_zero && VECTOR(label_weights)[k] != 0) { + /* weights just became nonzero */ + IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k)); + } + + if (max_count < VECTOR(label_weights)[k]) { + max_count = VECTOR(label_weights)[k]; + IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (max_count == VECTOR(label_weights)[k]) { + IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k)); + } + } + } else { + + neis = igraph_adjlist_get(&al, v1); + num_neis = igraph_vector_int_size(neis); + + for (j = 0; j < num_neis; j++) { + + k = VECTOR(*membership)[VECTOR(*neis)[j]]; + if (k < 0) { + continue; /* skip if it has no label yet */ + } + VECTOR(label_weights)[k]++; + + if (VECTOR(label_weights)[k] == 1) { + /* weights just became nonzero */ + IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k)); + } + + if (max_count < VECTOR(label_weights)[k]) { + max_count = VECTOR(label_weights)[k]; + IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (max_count == VECTOR(label_weights)[k]) { + IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k)); + } + } + } + + if (igraph_vector_int_size(&dominant_labels) > 0) { + if (retention) { + /* If we are using retention, we first check if the current label + is among the maximum label. */ + j = (long)VECTOR(*membership)[v1]; + if (j < 0 || /* Doesn't have a label yet */ + VECTOR(label_weights)[j] == 0 || /* Label not present in neighbors */ + VECTOR(label_weights)[j] < max_count /* Label not dominant */) { + /* Select randomly from the dominant labels */ + k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1); + k = VECTOR(dominant_labels)[(long int)k]; + /* If label changes, we will continue running */ + if (k != j) { + running = true; + } + /* Actually change label */ + VECTOR(*membership)[v1] = k; + } + } else { + /* We are not using retention, so check if we should do a control iteration + or an update iteration. */ + if (control_iteration) { + /* Check if the _current_ label of the node is also dominant */ + k = VECTOR(*membership)[v1]; + if (k < 0 || /* No label assigned yet or */ + VECTOR(label_weights)[k] < max_count /* Label is not maximum */ + ) { + /* Nope, we need at least one more iteration */ + running = true; + } + } else { + /* Select randomly from the dominant labels */ + k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1); + VECTOR(*membership)[v1] = VECTOR(dominant_labels)[k]; + } + } + } + + /* Clear the nonzero elements in label_weights */ + num_neis = igraph_vector_int_size(&nonzero_labels); + for (j = 0; j < num_neis; j++) { + VECTOR(label_weights)[VECTOR(nonzero_labels)[j]] = 0; + } + } + + /* Alternating between control iterations and label updating iterations */ + if (!retention) { + control_iteration = !control_iteration; + } + } + + if (weights) { + igraph_inclist_destroy(&il); + } else { + igraph_adjlist_destroy(&al); + } + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_int_destroy(&node_order); + igraph_vector_int_destroy(&nonzero_labels); + igraph_vector_int_destroy(&dominant_labels); + igraph_vector_destroy(&label_weights); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t community_fast_label_propagation( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_neimode_t mode, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_not_fixed_nodes = 0; + igraph_int_t i, j, k; + igraph_inclist_t il; + igraph_adjlist_t al; + + igraph_vector_t label_weights; + igraph_vector_int_t dominant_labels, nonzero_labels, node_order; + igraph_dqueue_int_t queue; + igraph_vector_bool_t in_queue; + igraph_neimode_t reverse_mode; + int iter = 0; /* interruption counter */ + + reverse_mode = IGRAPH_REVERSE_MODE(mode); + + if (weights) { + IGRAPH_CHECK(igraph_inclist_init(graph, &il, reverse_mode, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, reverse_mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + } + + /* Create storage space for counting distinct labels and dominant ones */ + IGRAPH_VECTOR_INIT_FINALLY(&label_weights, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nonzero_labels, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&dominant_labels, 2)); + + /* Initialize node ordering vector with only the not fixed nodes */ + IGRAPH_DQUEUE_INT_INIT_FINALLY(&queue, no_of_nodes); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&in_queue, no_of_nodes); + + /* Initialize node ordering vector with only the not fixed nodes */ + if (fixed) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(*fixed)[i]) { + VECTOR(node_order)[no_of_not_fixed_nodes] = i; + no_of_not_fixed_nodes++; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes)); + } else { + IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order); + no_of_not_fixed_nodes = no_of_nodes; + } + + for (i = 0; i < no_of_not_fixed_nodes; i++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, VECTOR(node_order)[i])); + VECTOR(in_queue)[VECTOR(node_order)[i]] = true; + } + igraph_vector_int_destroy(&node_order); + IGRAPH_FINALLY_CLEAN(1); + + while (!igraph_dqueue_int_empty(&queue)) { + igraph_int_t v1, v2, e = -1, num_neis; + igraph_real_t max_count; + igraph_vector_int_t *neis; + igraph_bool_t was_zero; + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + + v1 = igraph_dqueue_int_pop(&queue); + VECTOR(in_queue)[v1] = false; + + /* Count the weights corresponding to different labels */ + igraph_vector_int_clear(&dominant_labels); + igraph_vector_int_clear(&nonzero_labels); + max_count = 0.0; + if (weights) { + neis = igraph_inclist_get(&il, v1); + } else { + neis = igraph_adjlist_get(&al, v1); + } + + num_neis = igraph_vector_int_size(neis); + for (j = 0; j < num_neis; j++) { + if (weights) { + e = VECTOR(*neis)[j]; + v2 = IGRAPH_OTHER(graph, e, v1); + } else { + v2 = VECTOR(*neis)[j]; + } + k = VECTOR(*membership)[v2]; + if (k < 0) { + continue; /* skip if it has no label yet */ + } + was_zero = (VECTOR(label_weights)[k] == 0); + VECTOR(label_weights)[k] += (weights ? VECTOR(*weights)[e] : 1); + + if (was_zero && VECTOR(label_weights)[k] >= 0) { + /* counter just became non-negative */ + IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k)); + } + + if (max_count < VECTOR(label_weights)[k]) { + max_count = VECTOR(label_weights)[k]; + IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (max_count == VECTOR(label_weights)[k]) { + IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k)); + } + } + + if (igraph_vector_int_size(&dominant_labels) > 0) { + igraph_int_t current_label = VECTOR(*membership)[v1]; + + /* Select randomly from the dominant labels */ + k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1); + igraph_int_t new_label = VECTOR(dominant_labels)[k]; /* a dominant label */ + + /* Check if the _current_ label of the node is not the same */ + if (new_label != current_label) { + /* We still need to consider its neighbors not in the new community */ + for (j = 0; j < num_neis; j++) { + if (weights) { + e = VECTOR(*neis)[j]; + v2 = IGRAPH_OTHER(graph, e, v1); + } else { + v2 = VECTOR(*neis)[j]; + } + if (!VECTOR(in_queue)[v2]) { + igraph_int_t neigh_label = VECTOR(*membership)[v2]; /* neighbor community */ + if (neigh_label != new_label && /* not in new community */ + (fixed == NULL || !VECTOR(*fixed)[v2]) ) { /* not fixed */ + IGRAPH_CHECK(igraph_dqueue_int_push(&queue, v2)); + VECTOR(in_queue)[v2] = true; + } + } + } + } + VECTOR(*membership)[v1] = new_label; + } + + /* Clear the nonzero elements in label_weights */ + num_neis = igraph_vector_int_size(&nonzero_labels); + for (j = 0; j < num_neis; j++) { + VECTOR(label_weights)[VECTOR(nonzero_labels)[j]] = 0; + } + } + + if (weights) { + igraph_inclist_destroy(&il); + } else { + igraph_adjlist_destroy(&al); + } + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_bool_destroy(&in_queue); + igraph_dqueue_int_destroy(&queue); + igraph_vector_destroy(&label_weights); + igraph_vector_int_destroy(&dominant_labels); + igraph_vector_int_destroy(&nonzero_labels); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_community_label_propagation + * \brief Community detection based on label propagation. + * + * This function implements the label propagation-based community detection + * algorithm described by Raghavan, Albert and Kumara (2007). This version extends + * the original method by the ability to take edge weights into consideration + * and also by allowing some labels to be fixed. In addition, it implements + * the fast label propagation alternative introduced by Traag and Å ubelj (2023). + * + * + * The algorithm works by iterating over nodes and updating the label of a node + * based on the labels of its neighbors. The labels that are most frequent among + * the neighbors are said to be dominant labels. The label of a node is always + * updated to a dominant label. The algorithm guarantees that the label for each + * is dominant when it terminates. + * + * + * There are several variants implemented, which work slightly differently with + * the dominance of labels. Nodes with a dominant label might no longer have a + * dominant label if one of their neighbors change label. In \c + * IGRAPH_LPA_DOMINANCE an additional iteration over all nodes is made after + * updating all labels to double check whether all nodes indeed have a dominant + * label. When updating the label of a node, labels are always sampled from + * among all dominant labels. The algorithm stops when all nodes have dominant + * labels. In \c IGRAPH_LPA_RETENTION instead labels are only updated when they + * are not dominant. That is, they retain their current label whenever the + * current label is already dominant. The algorithm does not make an additional + * iteration to check for dominance. Instead, it simply keeps track whether a + * label has been updated, and terminates if no updates have been made. In \c + * IGRAPH_LPA_FAST labels are sampled from among all dominant labels, similar to + * \c IGRAPH_LPA_DOMINANCE. Instead of iterating over all nodes, it keeps track + * of a queue of nodes that should be considered. Nodes are popped from the + * queue when they are considered for update. When the label of a node is + * updated, the node's neighbors are added to the queue again (if they weren't + * already in the queue). The algorithm terminates when the queue is empty. All + * variants guarantee that the labels for all nodes are dominant. + * + * + * Weights are taken into account as follows: when the new label of node + * \c i is determined, the algorithm iterates over all edges incident on + * node \c i and calculate the total weight of edges leading to other + * nodes with label 0, 1, 2, ..., \c k - 1 (where \c k is the number of possible + * labels). The new label of node \c i will then be the label whose edges + * (among the ones incident on node \c i) have the highest total weight. + * + * + * For directed graphs, it is important to know that labels can circulate + * freely only within the strongly connected components of the graph and + * may propagate in only one direction (or not at all) \em between strongly + * connected components. You should treat directed edges as directed only + * if you are aware of the consequences. + * + * + * References: + * + * + * Raghavan, U.N. and Albert, R. and Kumara, S.: Near linear time algorithm to + * detect community structures in large-scale networks. Phys Rev E 76, 036106 + * (2007). https://doi.org/10.1103/PhysRevE.76.036106 + * + * + * Å ubelj, L.: Label propagation for clustering. Chapter in "Advances in + * Network Clustering and Blockmodeling" edited by P. Doreian, V. Batagelj + * & A. Ferligoj (Wiley, New York, 2018). + * https://doi.org/10.1002/9781119483298.ch5 + * https://arxiv.org/abs/1709.05634 + * + * + * Traag, V. A., and Å ubelj, L.: Large network community detection by fast + * label propagation. Scientific Reports, 13:1, (2023). + * https://doi.org/10.1038/s41598-023-29610-z + * https://arxiv.org/abs/2209.13338 + * + * \param graph The input graph. Note that the algorithm was originally + * defined for undirected graphs. You are advised to set \p mode to + * \c IGRAPH_ALL if you pass a directed graph here to treat it as + * undirected. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community (label). + * \param mode Whether to consider edge directions for the label propagation, + * and if so, which direction the labels should propagate. Ignored for + * undirected graphs. \c IGRAPH_ALL means to ignore edge directions (even + * in directed graphs). \c IGRAPH_OUT means to propagate labels along the + * natural direction of the edges. \c IGRAPH_IN means to propagate labels + * \em backwards (i.e. from head to tail). It is advised to set this to + * \c IGRAPH_ALL unless you are specifically interested in the effect of + * edge directions. + * \param weights The weight vector, it should contain a positive + * weight for all the edges. + * \param initial The initial state. If \c NULL, every vertex will have + * a different label at the beginning. Otherwise it must be a vector + * with an entry for each vertex. Non-negative values denote different + * labels, negative entries denote vertices without labels. Unlabeled + * vertices which are not reachable from any labeled ones will remain + * unlabeled at the end of the label propagation process, and will be + * labeled in an additional step to avoid returning negative values in + * \p membership. In undirected graphs, this happens when entire connected + * components are unlabeled. Then, each unlabeled component will receive + * its own separate label. In directed graphs, the outcome of the + * additional labeling should be considered undefined and may change + * in the future; please do not rely on it. + * \param fixed Boolean vector denoting which labels are fixed. Of course + * this makes sense only if you provided an initial state, otherwise + * this element will be ignored. Note that vertices without labels + * cannot be fixed. The fixed status will be ignored for these with a + * warning. Also note that label numbers by themselves have no meaning, + * and igraph may renumber labels. However, co-membership constraints + * will be respected: two vertices can be fixed to be in the same or in + * different communities. + * \param lpa_variant Which variant of the label propagation algorithm to run. + * \clist + * \cli IGRAPH_LPA_DOMINANCE + * check for dominance of all nodes after each iteration. + * \cli IGRAPH_LPA_RETENTION + * keep current label if among dominant labels, only check if labels changed. + * \cli IGRAPH_LPA_FAST + * sample from dominant labels, only check neighbors. + * \endclist + * \return Error code. + * + * Time complexity: O(m+n) + * + * \example examples/simple/igraph_community_label_propagation.c + */ +igraph_error_t igraph_community_label_propagation(const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_neimode_t mode, + const igraph_vector_t *weights, + const igraph_vector_int_t *initial, + const igraph_vector_bool_t *fixed, + igraph_lpa_variant_t lpa_variant) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_not_fixed_nodes = no_of_nodes; + igraph_int_t i, j, k; + igraph_bool_t unlabelled_left; + + /* We make a copy of 'fixed' as a pointer into 'fixed_copy' after casting + * away the constness, and promise ourselves that we will make a proper + * copy of 'fixed' into 'fixed_copy' as soon as we start mutating it */ + igraph_vector_bool_t *fixed_copy = (igraph_vector_bool_t *) fixed; + + /* Unlabelled nodes are represented with -1. */ +#define IS_UNLABELLED(x) (VECTOR(*membership)[x] < 0) + + /* Do some initial checks */ + if (fixed && igraph_vector_bool_size(fixed) != no_of_nodes) { + IGRAPH_ERROR("Fixed labeling vector length must agree with number of nodes.", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Length of weight vector must agree with number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weights must not be negative.", IGRAPH_EINVAL); + } + if (isnan(minweight)) { + IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL); + } + } + } + if (fixed && !initial) { + IGRAPH_WARNING("Ignoring fixed vertices as no initial labeling given."); + } + + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + + if (initial) { + if (igraph_vector_int_size(initial) != no_of_nodes) { + IGRAPH_ERROR("Initial labeling vector length must agree with number of nodes.", IGRAPH_EINVAL); + } + /* Check if the labels used are valid, initialize membership vector */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*initial)[i] < 0) { + VECTOR(*membership)[i] = -1; + } else { + VECTOR(*membership)[i] = VECTOR(*initial)[i]; + } + } + if (fixed) { + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fixed)[i]) { + if (IS_UNLABELLED(i)) { + IGRAPH_WARNING("Fixed nodes cannot be unlabeled, ignoring them."); + + /* We cannot modify 'fixed' because it is const, so we make a copy and + * modify 'fixed_copy' instead */ + if (fixed_copy == fixed) { + fixed_copy = IGRAPH_CALLOC(1, igraph_vector_bool_t); + IGRAPH_CHECK_OOM(fixed_copy, "Insufficient memory for label propagation."); + IGRAPH_FINALLY(igraph_free, fixed_copy); + IGRAPH_CHECK(igraph_vector_bool_init_copy(fixed_copy, fixed)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, fixed_copy); + } + + VECTOR(*fixed_copy)[i] = false; + } else { + no_of_not_fixed_nodes--; + } + } + } + } + + i = igraph_vector_int_max(membership); + if (i > no_of_nodes) { + IGRAPH_ERROR("Elements of the initial labeling vector must be between 0 and |V|-1.", IGRAPH_EINVAL); + } + } else { + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = i; + } + } + + /* From this point onwards we use 'fixed_copy' instead of 'fixed' */ + switch (lpa_variant) { + case IGRAPH_LPA_FAST: + IGRAPH_CHECK(community_fast_label_propagation(graph, membership, mode, weights, fixed_copy)); + break; + + case IGRAPH_LPA_RETENTION: + IGRAPH_CHECK(community_label_propagation(graph, membership, mode, weights, fixed_copy, /* retention */ true )); + break; + + case IGRAPH_LPA_DOMINANCE: + IGRAPH_CHECK(community_label_propagation(graph, membership, mode, weights, fixed_copy, /* retention */ false)); + break; + + default: + IGRAPH_ERROR("Invalid igraph_lpa_variant_t.", IGRAPH_EINVAL); + } + + /* Permute labels in increasing order */ + igraph_vector_int_t relabel_label; + IGRAPH_CHECK(igraph_vector_int_init(&relabel_label, no_of_nodes)); + igraph_vector_int_fill(&relabel_label, -1); + IGRAPH_FINALLY(igraph_vector_int_destroy, &relabel_label); + + j = 0; + unlabelled_left = false; + for (i = 0; i < no_of_nodes; i++) { + k = VECTOR(*membership)[i]; + if (k >= 0) { + if (VECTOR(relabel_label)[k] == -1) { + /* We have seen this label for the first time */ + VECTOR(relabel_label)[k] = j; + k = j; + j++; + } else { + k = VECTOR(relabel_label)[k]; + } + } else { + /* This is an unlabeled vertex */ + unlabelled_left = true; + } + VECTOR(*membership)[i] = k; + } + + /* If any nodes are left unlabelled, we assign the remaining labels to them, + * as well as to all unlabelled nodes reachable from them. + * + * Note that only those nodes could remain unlabelled which were unreachable + * from any labelled ones. Thus, in the undirected case, fully unlabelled + * connected components remain unlabelled. Here we label each such component + * with the same label. + */ + if (unlabelled_left) { + igraph_dqueue_int_t q; + igraph_vector_int_t neis; + + igraph_vector_int_t node_order; + /* Initialize node ordering vector with only the not fixed nodes */ + if (fixed) { + no_of_not_fixed_nodes = 0; + IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(*fixed)[i]) { + VECTOR(node_order)[no_of_not_fixed_nodes] = i; + no_of_not_fixed_nodes++; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes)); + } else { + IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order); + no_of_not_fixed_nodes = no_of_nodes; + } + /* Shuffle the node ordering vector */ + igraph_vector_int_shuffle(&node_order); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + + for (i=0; i < no_of_not_fixed_nodes; ++i) { + igraph_int_t v = VECTOR(node_order)[i]; + + /* Is this node unlabelled? */ + if (IS_UNLABELLED(v)) { + /* If yes, we label it, and do a BFS to apply the same label + * to all other unlabelled nodes reachable from it */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, v)); + VECTOR(*membership)[v] = j; + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t ni, num_neis; + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + num_neis = igraph_vector_int_size(&neis); + + for (ni = 0; ni < num_neis; ++ni) { + igraph_int_t neighbor = VECTOR(neis)[ni]; + if (IS_UNLABELLED(neighbor)) { + VECTOR(*membership)[neighbor] = j; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + } + } + } + j++; + } + } + + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&node_order); + IGRAPH_FINALLY_CLEAN(3); + } + + igraph_vector_int_destroy(&relabel_label); + IGRAPH_FINALLY_CLEAN(1); + + if (fixed != fixed_copy) { + igraph_vector_bool_destroy(fixed_copy); + IGRAPH_FREE(fixed_copy); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/community/leading_eigenvector.c b/src/community/leading_eigenvector.c new file mode 100644 index 0000000..f25b63a --- /dev/null +++ b/src/community/leading_eigenvector.c @@ -0,0 +1,866 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +#include + +/** + * \section about_leading_eigenvector_methods + * + * + * The function documented in these section implements the + * leading eigenvector method developed by Mark Newman and + * published in MEJ Newman: Finding community structure using the + * eigenvectors of matrices, Phys Rev E 74:036104 (2006). + * + * + * The heart of the method is the definition of the modularity matrix + * B = A - P, \c A being the adjacency matrix of the (undirected) + * network, and \c P contains the probability that certain edges are + * present according to the configuration model. In + * other words, a \c P_ij element of \c P is the probability that there is an + * edge between vertices \c i and \c j in a random network in which the + * degrees of all vertices are the same as in the input graph. See + * \ref igraph_modularity_matrix() for more details. + * + * + * The leading eigenvector method works by calculating the eigenvector + * of the modularity matrix for the largest positive eigenvalue and + * then separating vertices into two community based on the sign of + * the corresponding element in the eigenvector. If all elements in + * the eigenvector are of the same sign that means that the network + * has no underlying community structure. + * Check Newman's paper to understand why this is a good method for + * detecting community structure. + * + * + * The leading eigenvector community structure detection method is + * implemented in \ref igraph_community_leading_eigenvector(). After + * the initial split, the following splits are done in a way to + * optimize modularity regarding to the original network. Note that + * any further refinement, for example using Kernighan-Lin, as + * proposed in Section V.A of Newman (2006), is not implemented here. + * + * + * + * \example examples/simple/igraph_community_leading_eigenvector.c + * + */ + +typedef struct igraph_i_community_leading_eigenvector_data_t { + igraph_vector_int_t *idx; + igraph_vector_int_t *idx2; + igraph_adjlist_t *adjlist; + igraph_inclist_t *inclist; + igraph_vector_t *tmp; + igraph_int_t no_of_edges; + igraph_vector_int_t *mymembership; + igraph_int_t comm; + const igraph_vector_t *weights; + const igraph_t *graph; + igraph_vector_t *strength; + igraph_real_t sumweights; +} igraph_i_community_leading_eigenvector_data_t; + +static igraph_error_t igraph_i_community_leading_eigenvector( + igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + igraph_int_t size = n; + igraph_vector_int_t *idx = data->idx; + igraph_vector_int_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_adjlist_t *adjlist = data->adjlist; + igraph_real_t ktx, ktx2; + igraph_int_t no_of_edges = data->no_of_edges; + igraph_vector_int_t *mymembership = data->mymembership; + igraph_int_t comm = data->comm; + + /* Ax */ + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + igraph_int_t nlen = igraph_vector_int_size(neis); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (igraph_int_t k = 0; k < nlen; k++) { + igraph_int_t nei = VECTOR(*neis)[k]; + igraph_int_t neimemb = VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + to[j] += from[ VECTOR(*idx2)[nei] ]; + VECTOR(*tmp)[j] += 1; + } + } + } + + /* Now calculate k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + igraph_int_t degree = igraph_vector_int_size(neis); + ktx += from[j] * degree; + ktx2 += degree; + } + ktx = ktx / no_of_edges / 2.0; + ktx2 = ktx2 / no_of_edges / 2.0; + + /* Now calculate Bx */ + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + igraph_real_t degree = igraph_vector_int_size(neis); + to[j] = to[j] - ktx * degree; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * degree; + } + + /* -d_ij summa l in G B_il */ + for (igraph_int_t j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_community_leading_eigenvector_weighted( + igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + igraph_int_t size = n; + igraph_vector_int_t *idx = data->idx; + igraph_vector_int_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_inclist_t *inclist = data->inclist; + igraph_real_t ktx, ktx2; + igraph_vector_int_t *mymembership = data->mymembership; + igraph_int_t comm = data->comm; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *strength = data->strength; + igraph_real_t sw = data->sumweights; + + /* Ax */ + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_vector_int_t *inc = igraph_inclist_get(inclist, oldid); + igraph_int_t nlen = igraph_vector_int_size(inc); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (igraph_int_t k = 0; k < nlen; k++) { + igraph_int_t edge = VECTOR(*inc)[k]; + igraph_real_t w = VECTOR(*weights)[edge]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, oldid); + igraph_int_t neimemb = VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + to[j] += from[ VECTOR(*idx2)[nei] ] * w; + VECTOR(*tmp)[j] += w; + } + } + } + + /* k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + ktx += from[j] * str; + ktx2 += str; + } + ktx = ktx / sw / 2.0; + ktx2 = ktx2 / sw / 2.0; + + /* Bx */ + for (igraph_int_t j = 0; j < size; j++) { + igraph_int_t oldid = VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + to[j] = to[j] - ktx * str; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * str; + } + + /* -d_ij summa l in G B_il */ + for (igraph_int_t j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return IGRAPH_SUCCESS; +} + +static void igraph_i_error_handler_none(const char *reason, const char *file, + int line, igraph_error_t igraph_errno) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(igraph_errno); + /* do nothing */ +} + + +/** + * \ingroup communities + * \function igraph_community_leading_eigenvector + * \brief Leading eigenvector community finding (proper version). + * + * Newman's leading eigenvector method for detecting community + * structure. This is the proper implementation of the recursive, + * divisive algorithm: each split is done by maximizing the modularity + * regarding the original network, see MEJ Newman: Finding community + * structure in networks using the eigenvectors of matrices, + * Phys Rev E 74:036104 (2006). + * https://doi.org/10.1103/PhysRevE.74.036104 + * + * \param graph The input graph. Edge directions will be ignored. + * \param weights The weights of the edges, or \c NULL for unweighted graphs. + * \param merges The result of the algorithm, a matrix containing the + * information about the splits performed. The matrix is built in + * the opposite way however, it is like the result of an + * agglomerative algorithm. Unlike with most other hierarchical + * community detection functions in igraph, the integers in this matrix + * represent community indices, not vertex indices. If at the end of + * the algorithm (after \p steps steps was done) there are p + * communities, then these are numbered from zero to p-1. + * The first line of the matrix contains the first merge + * (which is in reality the last split) of two communities into + * community p, the merge in the second line forms + * community p+1, etc. The matrix should be + * initialized before calling and will be resized as needed. + * This argument is ignored if it is \c NULL. + * \param membership The membership of the vertices after all the + * splits were performed will be stored here. The vector must be + * initialized before calling and will be resized as needed. + * This argument is ignored if it is \c NULL. This argument can + * also be used to supply a starting configuration for the community + * finding, in the format of a membership vector. In this case the + * \p start argument must be set to \c true. + * \param steps The maximum number of steps to perform. It might + * happen that some component (or the whole network) has no + * underlying community structure and no further steps can be + * done. If you want as many steps as possible then supply the + * number of vertices in the network here. + * \param options The options for ARPACK. Supply \c NULL here to use the + * defaults. \c n is always overwritten. \c ncv is set to at least 4. + * \param modularity If not a null pointer, then it must be a pointer + * to a real number and the modularity score of the final division + * is stored here. + * \param start Boolean, whether to use the community structure given + * in the \p membership argument as a starting point. + * \param eigenvalues Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the eigenvalues calculated + * along the community structure detection are stored here. The + * non-positive eigenvalues, that do not result a split, are stored + * as well. + * \param eigenvectors If not a null pointer, then the eigenvectors + * that are calculated in each step of the algorithm are stored here, + * in a list of vectors. Each eigenvector is stored in an + * \ref igraph_vector_t object. + * \param history Pointer to an initialized vector or a null pointer. + * If not a null pointer, then a trace of the algorithm is stored + * here, encoded numerically. The various operations: + * \clist + * \cli IGRAPH_LEVC_HIST_START_FULL + * Start the algorithm from an initial state where each connected + * component is a separate community. + * \cli IGRAPH_LEVC_HIST_START_GIVEN + * Start the algorithm from a given community structure. The next + * value in the vector contains the initial number of + * communities. + * \cli IGRAPH_LEVC_HIST_SPLIT + * Split a community into two communities. The id of the splitted + * community is given in the next element of the history vector. + * The id of the first new community is the same as the id of the + * splitted community. The id of the second community equals to + * the number of communities before the split. + * \cli IGRAPH_LEVC_HIST_FAILED + * Tried to split a community, but it was not worth it, as it + * does not result in a bigger modularity value. The id of the + * community is given in the next element of the vector. + * \endclist + * \param callback A null pointer or a function of type \ref + * igraph_community_leading_eigenvector_callback_t. If given, this + * callback function is called after each eigenvector/eigenvalue + * calculation. If the callback returns \c IGRAPH_STOP, then the + * community finding algorithm stops. If it returns \c IGRAPH_SUCCESS, + * the algorithm continues normally. Any other return value is considered + * an igraph error code and will terminete the algorithm with the same + * error code. See the arguments passed to the callback at the documentation + * of \ref igraph_community_leading_eigenvector_callback_t. + * \param callback_extra Extra argument to pass to the callback + * function. + * \return Error code. + * + * \sa \ref igraph_community_walktrap() and \ref + * igraph_community_spinglass() for other community structure + * detection methods. + * + * Time complexity: O(|E|+|V|^2*steps), |V| is the number of vertices, + * |E| the number of edges, steps the number of splits + * performed. + */ +igraph_error_t igraph_community_leading_eigenvector( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_int_t *merges, + igraph_vector_int_t *membership, + igraph_int_t steps, + igraph_arpack_options_t *options, + igraph_real_t *modularity, + igraph_bool_t start, + igraph_vector_t *eigenvalues, + igraph_vector_list_t *eigenvectors, + igraph_vector_int_t *history, + igraph_community_leading_eigenvector_callback_t *callback, + void *callback_extra) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_dqueue_int_t tosplit; + igraph_vector_int_t idx, idx2; + igraph_vector_t mymerges; + igraph_vector_t strength, tmp; + igraph_vector_t start_vec; + igraph_int_t staken = 0; + igraph_adjlist_t adjlist; + igraph_inclist_t inclist; + igraph_int_t i, j, k, l; + igraph_int_t communities; + igraph_vector_int_t vmembership, *mymembership = membership; + igraph_i_community_leading_eigenvector_data_t extra; + igraph_arpack_storage_t storage; + igraph_real_t mod = 0; + igraph_arpack_function_t *arpcb1 = + weights ? igraph_i_community_leading_eigenvector_weighted : + igraph_i_community_leading_eigenvector; + igraph_real_t sumweights = 0.0; + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph too large for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (weights && no_of_edges != igraph_vector_size(weights)) { + IGRAPH_ERROR("Weight vector length does not match number of edges.", IGRAPH_EINVAL); + } + + if (start && !membership) { + IGRAPH_ERROR("Cannot start from given configuration if memberships missing.", IGRAPH_EINVAL); + } + + if (start && membership && + igraph_vector_int_size(membership) != no_of_nodes) { + IGRAPH_ERROR("Supplied membership vector length does not match number of vertices.", + IGRAPH_EINVAL); + } + + if (start && membership && igraph_vector_int_max(membership) >= no_of_nodes) { + IGRAPH_WARNING("Too many communities in membership start vector."); + } + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Directed graph supplied, edge directions will be ignored."); + } + + if (steps < 0 || steps > no_of_nodes - 1) { + steps = no_of_nodes > 0 ? no_of_nodes - 1 : 0; + } + + if (!membership) { + mymembership = &vmembership; + IGRAPH_VECTOR_INT_INIT_FINALLY(mymembership, 0); + } + + IGRAPH_VECTOR_INIT_FINALLY(&mymerges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&mymerges, steps * 2)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, 0); + if (eigenvalues) { + igraph_vector_clear(eigenvalues); + } + if (eigenvectors) { + igraph_vector_list_clear(eigenvectors); + } + + if (!start) { + /* Calculate the weakly connected components in the graph and use them as + * an initial split */ + IGRAPH_CHECK(igraph_connected_components(graph, mymembership, &idx, NULL, IGRAPH_WEAK)); + communities = igraph_vector_int_size(&idx); + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, + IGRAPH_LEVC_HIST_START_FULL)); + } + } else { + /* Just create the idx vector for the given membership vector */ + communities = igraph_vector_int_max(mymembership) + 1; + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, + IGRAPH_LEVC_HIST_START_GIVEN)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, communities)); + } + IGRAPH_CHECK(igraph_vector_int_resize(&idx, communities)); + igraph_vector_int_null(&idx); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t t = VECTOR(*mymembership)[i]; + VECTOR(idx)[t] += 1; + } + } + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&tosplit, 100); + for (i = 0; i < communities; i++) { + if (VECTOR(idx)[i] > 2) { + IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, i)); + } + } + for (i = 1; i < communities; i++) { + /* Record merge */ + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i)); + if (eigenvalues) { + IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, IGRAPH_NAN)); + } + if (eigenvectors) { + /* There are no eigenvectors associated to these steps because the + * splits were given by the user (or by the components of the graph) + * so we push empty vectors */ + IGRAPH_CHECK(igraph_vector_list_push_back_new(eigenvectors, NULL)); + } + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, IGRAPH_LEVC_HIST_SPLIT)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, i - 1)); + } + } + staken = communities - 1; + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, no_of_nodes); + IGRAPH_CHECK(igraph_vector_int_resize(&idx, no_of_nodes)); + igraph_vector_int_null(&idx); + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx2, no_of_nodes); + if (!weights) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_LOOPS, weights)); + sumweights = igraph_vector_sum(weights); + } + + if (options == NULL) { + options = igraph_arpack_options_get_default(); + } + + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->which[0] = 'L'; options->which[1] = 'A'; + + /* Memory for ARPACK */ + /* We are allocating memory for 20 eigenvectors since options->ncv won't be + * larger than 20 when using automatic mode in igraph_arpack_rssolve */ + IGRAPH_CHECK(igraph_arpack_storage_init(&storage, (int) no_of_nodes, 20, + (int) no_of_nodes, 1)); + IGRAPH_FINALLY(igraph_arpack_storage_destroy, &storage); + extra.idx = &idx; + extra.idx2 = &idx2; + extra.tmp = &tmp; + extra.adjlist = &adjlist; + extra.inclist = &inclist; + extra.weights = weights; + extra.sumweights = sumweights; + extra.graph = graph; + extra.strength = &strength; + extra.no_of_edges = no_of_edges; + extra.mymembership = mymembership; + + while (!igraph_dqueue_int_empty(&tosplit) && staken < steps) { + igraph_int_t comm = igraph_dqueue_int_pop_back(&tosplit); + /* depth first search */ + igraph_int_t size = 0; + + IGRAPH_ALLOW_INTERRUPTION(); + + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*mymembership)[i] == comm) { + VECTOR(idx)[size] = i; + VECTOR(idx2)[i] = size++; + } + } + + staken++; + if (size <= 2) { + continue; + } + + options->n = (int) size; + options->info = 0; + options->nev = 1; + options->ldv = 0; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->nconv = 0; + options->lworkl = 0; /* we surely have enough space */ + extra.comm = comm; + + /* Use a random start vector, but don't let ARPACK generate the + * start vector -- we want to use our own RNG. Also, we want to generate + * values close to +1 and -1 as this is what the eigenvector should + * look like if there _is_ some kind of a community structure at this + * step to discover. Experiments showed that shuffling a vector + * containing equal number of slightly perturbed +/-1 values yields + * convergence in most cases. */ + options->start = 1; + options->mxiter = options->mxiter > 10000 ? options->mxiter : 10000; /* use more iterations, we've had convergence problems with 3000 */ + + for (i = 0; i < options->n; i++) { + storage.resid[i] = (i % 2 ? 1 : -1) + RNG_UNIF(-0.1, 0.1); + } + + start_vec = igraph_vector_view(storage.resid, options->n); + igraph_vector_shuffle(&start_vec); + + { + igraph_error_t retval; + igraph_error_handler_t *errh = + igraph_set_error_handler(igraph_i_error_handler_none); + retval = igraph_arpack_rssolve(arpcb1, &extra, options, &storage, /*values=*/ NULL, /*vectors=*/ NULL); + igraph_set_error_handler(errh); + if (retval == IGRAPH_EARPACK) { + /* TODO(ntamas): get last ARPACK error code. Some errors are OK. */ + igraph_arpack_error_t arpack_error = igraph_arpack_get_last_error(); + if (arpack_error != IGRAPH_ARPACK_MAXIT && arpack_error != IGRAPH_ARPACK_NOSHIFT) { + IGRAPH_ERROR(igraph_arpack_error_to_string(arpack_error), IGRAPH_EARPACK); + } + } else if (retval != IGRAPH_SUCCESS) { + IGRAPH_ERROR("Leading eigenvector calculation failed.", retval); + } + } + + if (options->nconv < 1) { + IGRAPH_ERROR(igraph_arpack_error_to_string(IGRAPH_ARPACK_FAILED), IGRAPH_EARPACK); + } + + /* Ok, we have the leading eigenvector of the modularity matrix */ + + /* ---------------------------------------------------------------*/ + /* To avoid numeric errors */ + if (fabs(storage.d[0]) < 1e-8) { + storage.d[0] = 0; + } + + /* We replace very small (in absolute value) elements of the + leading eigenvector with zero, to get the same result, + consistently.*/ + for (i = 0; i < size; i++) { + if (fabs(storage.v[i]) < 1e-8) { + storage.v[i] = 0; + } + } + + /* Just to have the always the same result, we multiply by -1 + if the first (nonzero) element is not positive. */ + for (i = 0; i < size; i++) { + if (storage.v[i] != 0) { + break; + } + } + if (i < size && storage.v[i] < 0) { + for (i = 0; i < size; i++) { + storage.v[i] = - storage.v[i]; + } + } + /* ---------------------------------------------------------------*/ + + if (callback) { + const igraph_vector_t vv = igraph_vector_view(storage.v, size);; + igraph_error_t ret; + + IGRAPH_CHECK_CALLBACK( + callback( + mymembership, comm, storage.d[0], &vv, arpcb1, + &extra, callback_extra + ), &ret + ); + + if (ret == IGRAPH_STOP) { + break; + } + } + + if (eigenvalues) { + IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, storage.d[0])); + } + + if (eigenvectors) { + igraph_vector_t *v; + /* TODO: this would be faster if we had an igraph_vector_list_push_back_new_with_size_hint */ + IGRAPH_CHECK(igraph_vector_list_push_back_new(eigenvectors, &v)); + IGRAPH_CHECK(igraph_vector_resize(v, size)); + for (i = 0; i < size; i++) { + VECTOR(*v)[i] = storage.v[i]; + } + } + + if (storage.d[0] <= 0) { + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, comm)); + } + continue; + } + + /* Count the number of vertices in each community after the split */ + l = 0; + for (j = 0; j < size; j++) { + if (storage.v[j] < 0) { + storage.v[j] = -1; + l++; + } else { + storage.v[j] = 1; + } + } + if (l == 0 || l == size) { + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, comm)); + } + continue; + } + + /* Check that Q increases with our choice of split */ + arpcb1(storage.v + size, storage.v, (int) size, &extra); + mod = 0; + for (i = 0; i < size; i++) { + mod += storage.v[size + i] * storage.v[i]; + } + if (mod <= 1e-8) { + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, comm)); + } + continue; + } + + communities++; + + /* Rewrite the mymembership vector */ + for (j = 0; j < size; j++) { + if (storage.v[j] < 0) { + igraph_int_t oldid = VECTOR(idx)[j]; + VECTOR(*mymembership)[oldid] = communities - 1; + } + } + + /* Record merge */ + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, comm)); + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, communities - 1)); + if (history) { + IGRAPH_CHECK(igraph_vector_int_push_back(history, IGRAPH_LEVC_HIST_SPLIT)); + IGRAPH_CHECK(igraph_vector_int_push_back(history, comm)); + } + + /* Store the resulting communities in the queue if needed */ + if (l > 1) { + IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, communities - 1)); + } + if (size - l > 1) { + IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, comm)); + } + + } + + igraph_arpack_storage_destroy(&storage); + IGRAPH_FINALLY_CLEAN(1); + if (!weights) { + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_inclist_destroy(&inclist); + igraph_vector_destroy(&strength); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_dqueue_int_destroy(&tosplit); + igraph_vector_destroy(&tmp); + igraph_vector_int_destroy(&idx2); + IGRAPH_FINALLY_CLEAN(3); + + /* reform the mymerges vector */ + if (merges) { + igraph_vector_int_null(&idx); + l = igraph_vector_size(&mymerges); + k = communities; + j = 0; + IGRAPH_CHECK(igraph_matrix_int_resize(merges, l / 2, 2)); + for (i = l; i > 0; i -= 2) { + igraph_int_t from = VECTOR(mymerges)[i - 1]; + igraph_int_t to = VECTOR(mymerges)[i - 2]; + MATRIX(*merges, j, 0) = VECTOR(mymerges)[i - 2]; + MATRIX(*merges, j, 1) = VECTOR(mymerges)[i - 1]; + if (VECTOR(idx)[from] != 0) { + MATRIX(*merges, j, 1) = VECTOR(idx)[from] - 1; + } + if (VECTOR(idx)[to] != 0) { + MATRIX(*merges, j, 0) = VECTOR(idx)[to] - 1; + } + VECTOR(idx)[to] = ++k; + j++; + } + } + + igraph_vector_int_destroy(&idx); + igraph_vector_destroy(&mymerges); + IGRAPH_FINALLY_CLEAN(2); + + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, mymembership, weights, + /* resolution */ 1, + IGRAPH_UNDIRECTED, modularity)); + } + + if (!membership) { + igraph_vector_int_destroy(mymembership); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_le_community_to_membership + * \brief Cut an incomplete dendrogram after a given number of merges, starting with an initial cluster assignment. + * + * This function takes a dendrogram whose leaves are cluster IDs given in an + * initial cluster assignment provided in \p membership. Then it updates + * the cluster assignment by performing the specified number of mergers, + * as given by the dendrogram encoded in \p merges. It is a more general + * version of \ref igraph_community_to_membership(), which assumes that + * the dendrogram leaves are singleton clusters corresponding to individual + * vertices. + * + * + * This dendrogram format is suitable for divise hierarchical community + * detection algorithms that stop before dividing the graph into individual + * vertices, such as \ref igraph_community_leading_eigenvector(). + * + * + * Initially, \p membership is expected to contain \c m contiguous cluster + * indices, numbered from zero. These correspond to the leaf nodes of the + * dendrogram. Row \c i of the two-column \p merges matrix contains the IDs of + * clusters that are merged together into dendrogram node m + i. + * It may have up to m - 1 rows. + * + * + * This function performs \p steps merge operations as prescribed by the + * \p merges matrix and updates \p membership to the resulting partitioning + * into m - steps communities. + * + * \param merges The two-column matrix containing the merge operations. + * See \ref igraph_community_leading_eigenvector() for the + * detailed syntax. This is usually from the output of the + * leading eigenvector community structure detection routines. + * \param steps The number of steps to make according to \c merges. + * \param membership Initially the starting membership vector, + * on output the resulting membership vector, after performing \c steps merges. + * \param csize Optionally the sizes of the communities are stored here, + * if this is not a null pointer, but an initialized vector. + * \return Error code. + * + * \sa \ref igraph_community_to_membership() for a simpler interface that + * starts by merging individual vertices. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_le_community_to_membership(const igraph_matrix_int_t *merges, + igraph_int_t steps, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize) { + + igraph_int_t no_of_nodes = igraph_vector_int_size(membership); + igraph_vector_int_t fake_memb; + igraph_int_t components, i; + + if (no_of_nodes > 0) { + components = igraph_vector_int_max(membership) + 1; + } else { + components = 0; + } + if (components > no_of_nodes) { + IGRAPH_ERRORF("Invalid membership vector: number of components (%" IGRAPH_PRId ") must " + "not be greater than the number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, components, no_of_nodes); + } + if (steps >= components) { + IGRAPH_ERRORF("Number of steps (%" IGRAPH_PRId ") must be smaller than number of components (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, steps, components); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&fake_memb, components); + + /* Check membership vector */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*membership)[i] < 0) { + IGRAPH_ERRORF("Invalid membership vector, negative ID found: %" IGRAPH_PRId ".", IGRAPH_EINVAL, VECTOR(*membership)[i]); + } + VECTOR(fake_memb)[ VECTOR(*membership)[i] ] += 1; + } + for (i = 0; i < components; i++) { + if (VECTOR(fake_memb)[i] == 0) { + /* Ideally the empty cluster's index would be reported. + However, doing so would be confusing as some high-level interfaces + use 1-based indexing, some 0-based. */ + IGRAPH_ERROR("Invalid membership vector, empty cluster found.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_community_to_membership(merges, components, steps, &fake_memb, NULL)); + + /* Ok, now we have the membership of the initial components, + rewrite the original membership vector. */ + + if (csize) { + IGRAPH_CHECK(igraph_vector_int_resize(csize, components - steps)); + igraph_vector_int_null(csize); + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = VECTOR(fake_memb)[ VECTOR(*membership)[i] ]; + if (csize) { + VECTOR(*csize)[ VECTOR(*membership)[i] ] += 1; + } + } + + igraph_vector_int_destroy(&fake_memb); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/leiden.c b/src/community/leiden.c new file mode 100644 index 0000000..8667b8b --- /dev/null +++ b/src/community/leiden.c @@ -0,0 +1,1503 @@ +/* + igraph library. + Copyright (C) 2020-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_stack.h" +#include "igraph_structural.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +#include "core/interruption.h" + +/* Move vertices in order to improve the quality of a partition. + * + * This function considers each vertex and greedily moves it to a neighboring + * community that maximizes the improvement in the quality of a partition. + * Only moves that strictly improve the quality are considered. + * + * The vertices are examined in a queue, and initially all vertices are put in the + * queue in a random order. Vertices are popped from the queue when they are + * examined, and only neighbors of vertices that are moved (which are not part of + * the cluster the vertex was moved to) are pushed to the queue again. + * + * The \p membership vector is used as the starting point to move around vertices, + * and is updated in-place. + * + */ +static igraph_error_t leiden_fastmove_vertices( + const igraph_t *graph, + const igraph_inclist_t *edges_per_vertex, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + const igraph_real_t resolution, + igraph_int_t *nb_clusters, + igraph_vector_int_t *membership, + igraph_bool_t *changed) { + + const igraph_int_t n = igraph_vcount(graph); + const igraph_bool_t directed = (vertex_in_weights != NULL); + igraph_dqueue_int_t unstable_vertices; + igraph_real_t max_diff, diff; + igraph_bitset_t neighbor_cluster_added, vertex_is_stable; + igraph_vector_t cluster_out_weights, cluster_in_weights; + igraph_vector_t edge_weights_per_cluster; + igraph_vector_int_t neighbor_clusters; + igraph_vector_int_t vertex_order; + igraph_vector_int_t nb_vertices_per_cluster; + igraph_stack_int_t empty_clusters; + igraph_int_t c, nb_neigh_clusters; + int iter = 0; + + /* Initialize queue of unstable vertices and whether vertex is stable. Only + * unstable vertices are in the queue. */ + IGRAPH_BITSET_INIT_FINALLY(&vertex_is_stable, n); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&unstable_vertices, n); + + /* Shuffle vertices */ + IGRAPH_CHECK(igraph_vector_int_init_range(&vertex_order, 0, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vertex_order); + igraph_vector_int_shuffle(&vertex_order); + + /* Add to the queue */ + for (igraph_int_t i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&unstable_vertices, VECTOR(vertex_order)[i])); + } + + /* Initialize cluster weights and nb vertices */ + IGRAPH_VECTOR_INIT_FINALLY(&cluster_out_weights, n); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&cluster_in_weights, n); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&nb_vertices_per_cluster, n); + for (igraph_int_t i = 0; i < n; i++) { + c = VECTOR(*membership)[i]; + VECTOR(cluster_out_weights)[c] += VECTOR(*vertex_out_weights)[i]; + if (directed) { + VECTOR(cluster_in_weights)[c] += VECTOR(*vertex_in_weights)[i]; + } + VECTOR(nb_vertices_per_cluster)[c] += 1; + } + + /* Initialize empty clusters */ + IGRAPH_STACK_INT_INIT_FINALLY(&empty_clusters, n); + for (c = 0; c < n; c++) { + if (VECTOR(nb_vertices_per_cluster)[c] == 0) { + IGRAPH_CHECK(igraph_stack_int_push(&empty_clusters, c)); + } + } + + /* Initialize vectors to be used in calculating differences */ + IGRAPH_VECTOR_INIT_FINALLY(&edge_weights_per_cluster, n); + + /* Initialize neighboring cluster */ + IGRAPH_BITSET_INIT_FINALLY(&neighbor_cluster_added, n); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbor_clusters, n); + + /* Iterate while the queue is not empty */ + while (!igraph_dqueue_int_empty(&unstable_vertices)) { + igraph_int_t v = igraph_dqueue_int_pop(&unstable_vertices); + igraph_int_t best_cluster, current_cluster = VECTOR(*membership)[v]; + igraph_int_t degree; + igraph_vector_int_t *edges; + + /* Remove vertex from current cluster */ + VECTOR(cluster_out_weights)[current_cluster] -= VECTOR(*vertex_out_weights)[v]; + if (directed) { + VECTOR(cluster_in_weights)[current_cluster] -= VECTOR(*vertex_in_weights)[v]; + } + VECTOR(nb_vertices_per_cluster)[current_cluster]--; + if (VECTOR(nb_vertices_per_cluster)[current_cluster] == 0) { + IGRAPH_CHECK(igraph_stack_int_push(&empty_clusters, current_cluster)); + } + + /* Find out neighboring clusters */ + c = igraph_stack_int_top(&empty_clusters); + VECTOR(neighbor_clusters)[0] = c; + IGRAPH_BIT_SET(neighbor_cluster_added, c); + nb_neigh_clusters = 1; + + /* Determine the edge weight to each neighboring cluster */ + edges = igraph_inclist_get(edges_per_vertex, v); + degree = igraph_vector_int_size(edges); + for (igraph_int_t i = 0; i < degree; i++) { + igraph_int_t e = VECTOR(*edges)[i]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + if (u != v) { + c = VECTOR(*membership)[u]; + if (!IGRAPH_BIT_TEST(neighbor_cluster_added, c)) { + IGRAPH_BIT_SET(neighbor_cluster_added, c); + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c; + } + VECTOR(edge_weights_per_cluster)[c] += VECTOR(*edge_weights)[e]; + } + } + + /* Calculate maximum diff */ + best_cluster = current_cluster; + max_diff = VECTOR(edge_weights_per_cluster)[current_cluster]; + if (directed) { + max_diff -= + (VECTOR(*vertex_in_weights)[v] * VECTOR(cluster_out_weights)[current_cluster] + + VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_in_weights)[current_cluster]) * resolution; + } else { + max_diff -= VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_out_weights)[current_cluster] * resolution; + } + for (igraph_int_t i = 0; i < nb_neigh_clusters; i++) { + c = VECTOR(neighbor_clusters)[i]; + diff = VECTOR(edge_weights_per_cluster)[c]; + if (directed) { + diff -= (VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_in_weights)[c] + + VECTOR(*vertex_in_weights)[v] * VECTOR(cluster_out_weights)[c]) * resolution; + } else { + diff -= VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_out_weights)[c] * resolution; + } + /* Only consider strictly improving moves. + * Note that this is important in considering convergence. + */ + if (diff > max_diff) { + best_cluster = c; + max_diff = diff; + } + VECTOR(edge_weights_per_cluster)[c] = 0.0; + IGRAPH_BIT_CLEAR(neighbor_cluster_added, c); + } + + /* Move vertex to best cluster */ + VECTOR(cluster_out_weights)[best_cluster] += VECTOR(*vertex_out_weights)[v]; + if (directed) { + VECTOR(cluster_in_weights)[best_cluster] += VECTOR(*vertex_in_weights)[v]; + } + VECTOR(nb_vertices_per_cluster)[best_cluster]++; + if (best_cluster == igraph_stack_int_top(&empty_clusters)) { + igraph_stack_int_pop(&empty_clusters); + } + + /* Mark vertex as stable */ + IGRAPH_BIT_SET(vertex_is_stable, v); + + /* Add stable neighbours that are not part of the new cluster to the queue */ + if (best_cluster != current_cluster) { + *changed = true; + VECTOR(*membership)[v] = best_cluster; + + for (igraph_int_t i = 0; i < degree; i++) { + igraph_int_t e = VECTOR(*edges)[i]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + if (IGRAPH_BIT_TEST(vertex_is_stable, u) && VECTOR(*membership)[u] != best_cluster) { + IGRAPH_CHECK(igraph_dqueue_int_push(&unstable_vertices, u)); + IGRAPH_BIT_CLEAR(vertex_is_stable, u); + } + } + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, nb_clusters)); + + igraph_vector_int_destroy(&neighbor_clusters); + igraph_bitset_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weights_per_cluster); + igraph_stack_int_destroy(&empty_clusters); + igraph_vector_int_destroy(&nb_vertices_per_cluster); + if (directed) igraph_vector_destroy(&cluster_in_weights); + igraph_vector_destroy(&cluster_out_weights); + igraph_vector_int_destroy(&vertex_order); + igraph_dqueue_int_destroy(&unstable_vertices); + igraph_bitset_destroy(&vertex_is_stable); + if (directed) { + IGRAPH_FINALLY_CLEAN(10); + } else { + IGRAPH_FINALLY_CLEAN(9); + } + + return IGRAPH_SUCCESS; +} + +/* Clean a refined membership vector. + * + * This function examines all vertices in \p vertex_subset and updates + * \p refined_membership to ensure that the clusters are numbered consecutively, + * starting from \p nb_refined_clusters. The \p nb_refined_clusters is also + * updated itself. If C is the initial \p nb_refined_clusters and C' the + * resulting \p nb_refined_clusters, then vertices in \p vertex_subset are numbered + * C, C + 1, ..., C' - 1. + */ +static igraph_error_t leiden_clean_refined_membership( + const igraph_vector_int_t* vertex_subset, + igraph_vector_int_t *refined_membership, + igraph_int_t* nb_refined_clusters) { + + const igraph_int_t n = igraph_vector_int_size(vertex_subset); + igraph_vector_int_t new_cluster; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_cluster, n); + + /* Clean clusters. We will store the new cluster + 1 so that cluster == 0 + * indicates that no membership was assigned yet. */ + *nb_refined_clusters += 1; + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t v = VECTOR(*vertex_subset)[i]; + igraph_int_t c = VECTOR(*refined_membership)[v]; + if (VECTOR(new_cluster)[c] == 0) { + VECTOR(new_cluster)[c] = *nb_refined_clusters; + *nb_refined_clusters += 1; + } + } + + /* Assign new cluster */ + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t v = VECTOR(*vertex_subset)[i]; + igraph_int_t c = VECTOR(*refined_membership)[v]; + VECTOR(*refined_membership)[v] = VECTOR(new_cluster)[c] - 1; + } + /* We used the cluster + 1, so correct */ + *nb_refined_clusters -= 1; + + igraph_vector_int_destroy(&new_cluster); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Merge vertices for a subset of the vertices. This is used to refine a partition. + * + * The vertices included in \p vertex_subset are assumed to be the vertices i for which + * membership[i] = cluster_subset. + * + * All vertices in \p vertex_subset are initialized to a singleton partition in \p + * refined_membership. Only singleton clusters can be merged if they are + * sufficiently well connected to the current subgraph induced by \p + * vertex_subset. + * + * We only examine each vertex once. Instead of greedily choosing the maximum + * possible cluster to merge with, the cluster is chosen randomly among all + * possibilities that do not decrease the quality of the partition. The + * probability of choosing a certain cluster is proportional to exp(diff/beta). + * For beta to 0 this converges to selecting a cluster with the maximum + * improvement. For beta to infinity this converges to a uniform distribution + * among all eligible clusters. + * + * The \p refined_membership is updated for vertex in \p vertex_subset. The number + * of refined clusters, \p nb_refined_clusters is used to set the actual refined + * cluster membership and is updated after this routine. Within each cluster + * (i.e. for a given \p vertex_subset), the refined membership is initially simply + * set to 0, ..., n - 1 (for n vertices in \p vertex_subset). However, for each \p + * vertex_subset the refined membership should of course be unique. Hence, after + * merging, the refined membership starts with \p nb_refined_clusters, which is + * also updated to ensure that the resulting \p nb_refined_clusters counts all + * refined clusters that have already been processed. See + * leiden_clean_refined_membership for more information about + * this aspect. + */ +static igraph_error_t leiden_merge_vertices( + const igraph_t *graph, + const igraph_inclist_t *edges_per_vertex, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + const igraph_vector_int_t *vertex_subset, + const igraph_vector_int_t *membership, + const igraph_int_t cluster_subset, + const igraph_real_t resolution, + const igraph_real_t beta, + igraph_int_t *nb_refined_clusters, + igraph_vector_int_t *refined_membership) { + + const igraph_bool_t directed = (vertex_in_weights != NULL); + igraph_vector_int_t vertex_order; + igraph_bitset_t non_singleton_cluster, neighbor_cluster_added; + igraph_real_t max_diff, total_cum_trans_diff, diff; + igraph_real_t total_vertex_out_weight = 0.0, total_vertex_in_weight = 0.0; + const igraph_int_t n = igraph_vector_int_size(vertex_subset); + igraph_vector_t cluster_out_weights, cluster_in_weights; + igraph_vector_t cum_trans_diff, edge_weights_per_cluster, external_edge_weight_per_cluster_in_subset; + igraph_vector_int_t neighbor_clusters; + igraph_vector_int_t *edges, nb_vertices_per_cluster; + igraph_int_t degree, nb_neigh_clusters; + + /* Initialize cluster weights */ + IGRAPH_VECTOR_INIT_FINALLY(&cluster_out_weights, n); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&cluster_in_weights, n); + } + + /* Initialize number of vertices per cluster */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&nb_vertices_per_cluster, n); + + /* Initialize external edge weight per cluster in subset */ + IGRAPH_VECTOR_INIT_FINALLY(&external_edge_weight_per_cluster_in_subset, n); + + /* Initialize administration for a singleton partition */ + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t v = VECTOR(*vertex_subset)[i]; + VECTOR(*refined_membership)[v] = i; + VECTOR(cluster_out_weights)[i] += VECTOR(*vertex_out_weights)[v]; + total_vertex_out_weight += VECTOR(*vertex_out_weights)[v]; + if (directed) { + VECTOR(cluster_in_weights)[i] += VECTOR(*vertex_in_weights)[v]; + total_vertex_in_weight += VECTOR(*vertex_in_weights)[v]; + } + VECTOR(nb_vertices_per_cluster)[i] += 1; + + /* Find out neighboring clusters */ + edges = igraph_inclist_get(edges_per_vertex, v); + degree = igraph_vector_int_size(edges); + for (igraph_int_t j = 0; j < degree; j++) { + igraph_int_t e = VECTOR(*edges)[j]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + if (u != v && VECTOR(*membership)[u] == cluster_subset) { + VECTOR(external_edge_weight_per_cluster_in_subset)[i] += VECTOR(*edge_weights)[e]; + } + } + } + + /* Shuffle vertices */ + IGRAPH_CHECK(igraph_vector_int_init_copy(&vertex_order, vertex_subset)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vertex_order); + igraph_vector_int_shuffle(&vertex_order); + + /* Initialize non singleton clusters */ + IGRAPH_BITSET_INIT_FINALLY(&non_singleton_cluster, n); + + /* Initialize vectors to be used in calculating differences */ + IGRAPH_VECTOR_INIT_FINALLY(&edge_weights_per_cluster, n); + + /* Initialize neighboring cluster */ + IGRAPH_BITSET_INIT_FINALLY(&neighbor_cluster_added, n); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbor_clusters, n); + + /* Initialize cumulative transformed difference */ + IGRAPH_VECTOR_INIT_FINALLY(&cum_trans_diff, n); + + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t v = VECTOR(vertex_order)[i]; + igraph_int_t chosen_cluster, best_cluster, current_cluster = VECTOR(*refined_membership)[v]; + igraph_real_t vertex_weight_prod; + + if (directed) { + vertex_weight_prod = + VECTOR(cluster_out_weights)[current_cluster] * (total_vertex_in_weight - VECTOR(cluster_in_weights)[current_cluster]) + + VECTOR(cluster_in_weights)[current_cluster] * (total_vertex_out_weight - VECTOR(cluster_out_weights)[current_cluster]); + } else { + vertex_weight_prod = VECTOR(cluster_out_weights)[current_cluster] * (total_vertex_out_weight - VECTOR(cluster_out_weights)[current_cluster]); + } + + if (!IGRAPH_BIT_TEST(non_singleton_cluster, current_cluster) && + (VECTOR(external_edge_weight_per_cluster_in_subset)[current_cluster] >= + vertex_weight_prod * resolution)) { + /* Remove vertex from current cluster, which is then a singleton by + * definition. */ + VECTOR(cluster_out_weights)[current_cluster] = 0.0; + if (directed) { + VECTOR(cluster_in_weights)[current_cluster] = 0.0; + } + VECTOR(nb_vertices_per_cluster)[current_cluster] = 0; + + /* Find out neighboring clusters */ + edges = igraph_inclist_get(edges_per_vertex, v); + degree = igraph_vector_int_size(edges); + + /* Also add current cluster to ensure it can be chosen. */ + VECTOR(neighbor_clusters)[0] = current_cluster; + IGRAPH_BIT_SET(neighbor_cluster_added, current_cluster); + nb_neigh_clusters = 1; + for (igraph_int_t j = 0; j < degree; j++) { + igraph_int_t e = VECTOR(*edges)[j]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + if (u != v && VECTOR(*membership)[u] == cluster_subset) { + igraph_int_t c = VECTOR(*refined_membership)[u]; + if (!IGRAPH_BIT_TEST(neighbor_cluster_added, c)) { + IGRAPH_BIT_SET(neighbor_cluster_added, c); + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c; + } + VECTOR(edge_weights_per_cluster)[c] += VECTOR(*edge_weights)[e]; + } + } + + /* Calculate diffs */ + best_cluster = current_cluster; + max_diff = 0.0; + total_cum_trans_diff = 0.0; + for (igraph_int_t j = 0; j < nb_neigh_clusters; j++) { + igraph_int_t c = VECTOR(neighbor_clusters)[j]; + + if (directed) { + vertex_weight_prod = + VECTOR(cluster_out_weights)[c] * (total_vertex_in_weight - VECTOR(cluster_in_weights)[c]) + + VECTOR(cluster_in_weights)[c] * (total_vertex_out_weight - VECTOR(cluster_out_weights)[c]); + } else { + vertex_weight_prod = VECTOR(cluster_out_weights)[c] * (total_vertex_out_weight - VECTOR(cluster_out_weights)[c]); + } + + if (VECTOR(external_edge_weight_per_cluster_in_subset)[c] >= vertex_weight_prod * resolution) { + diff = VECTOR(edge_weights_per_cluster)[c]; + if (directed) { + diff -= (VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_in_weights)[c] + + VECTOR(*vertex_in_weights)[v] * VECTOR(cluster_out_weights)[c]) * resolution; + } else { + diff -= VECTOR(*vertex_out_weights)[v] * VECTOR(cluster_out_weights)[c] * resolution; + } + + + if (diff > max_diff) { + best_cluster = c; + max_diff = diff; + } + + /* Calculate the transformed difference for sampling */ + if (diff >= 0) { + total_cum_trans_diff += exp(diff / beta); + } + + } + + VECTOR(cum_trans_diff)[j] = total_cum_trans_diff; + VECTOR(edge_weights_per_cluster)[c] = 0.0; + IGRAPH_BIT_CLEAR(neighbor_cluster_added, c); + } + + /* Determine the neighboring cluster to which the currently selected vertex + * will be moved. + */ + if (total_cum_trans_diff < IGRAPH_INFINITY) { + igraph_real_t r = RNG_UNIF(0, total_cum_trans_diff); + igraph_int_t chosen_idx; + igraph_vector_binsearch_slice(&cum_trans_diff, r, &chosen_idx, 0, nb_neigh_clusters); + chosen_cluster = VECTOR(neighbor_clusters)[chosen_idx]; + } else { + chosen_cluster = best_cluster; + } + + /* Move vertex to randomly chosen cluster */ + VECTOR(cluster_out_weights)[chosen_cluster] += VECTOR(*vertex_out_weights)[v]; + if (directed) { + VECTOR(cluster_in_weights)[chosen_cluster] += VECTOR(*vertex_in_weights)[v]; + } + VECTOR(nb_vertices_per_cluster)[chosen_cluster]++; + + for (igraph_int_t j = 0; j < degree; j++) { + igraph_int_t e = VECTOR(*edges)[j]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + if (VECTOR(*membership)[u] == cluster_subset) { + if (VECTOR(*refined_membership)[u] == chosen_cluster) { + VECTOR(external_edge_weight_per_cluster_in_subset)[chosen_cluster] -= VECTOR(*edge_weights)[e]; + } else { + VECTOR(external_edge_weight_per_cluster_in_subset)[chosen_cluster] += VECTOR(*edge_weights)[e]; + } + } + } + + /* Set cluster */ + if (chosen_cluster != current_cluster) { + VECTOR(*refined_membership)[v] = chosen_cluster; + + IGRAPH_BIT_SET(non_singleton_cluster, chosen_cluster); + } + } /* end if singleton and may be merged */ + } + + IGRAPH_CHECK(leiden_clean_refined_membership(vertex_subset, refined_membership, nb_refined_clusters)); + + igraph_vector_destroy(&cum_trans_diff); + igraph_vector_int_destroy(&neighbor_clusters); + igraph_bitset_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weights_per_cluster); + igraph_bitset_destroy(&non_singleton_cluster); + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&external_edge_weight_per_cluster_in_subset); + igraph_vector_int_destroy(&nb_vertices_per_cluster); + if (directed) igraph_vector_destroy(&cluster_in_weights); + igraph_vector_destroy(&cluster_out_weights); + if (directed) { + IGRAPH_FINALLY_CLEAN(10); + } else { + IGRAPH_FINALLY_CLEAN(9); + } + + return IGRAPH_SUCCESS; +} + +/* Create clusters out of a membership vector. + * + * It is assumed that the incoming list of integer vectors is already sized + * appropriately (i.e. it has at least as many items as the number of clusters + * in the membership vector), and that each item in the list of integer vectors + * is empty. + */ +static igraph_error_t leiden_get_clusters( + const igraph_vector_int_t *membership, + igraph_vector_int_list_t *clusters) { + + const igraph_int_t n = igraph_vector_int_size(membership); + + for (igraph_int_t i = 0; i < n; i++) { + /* Get cluster for vertex i */ + igraph_vector_int_t *cluster = igraph_vector_int_list_get_ptr(clusters, VECTOR(*membership)[i]); + + /* Add vertex i to cluster vector */ + IGRAPH_CHECK(igraph_vector_int_push_back(cluster, i)); + } + + return IGRAPH_SUCCESS; +} + +/* Aggregate the graph based on the \p refined membership while setting the + * membership of each aggregated vertex according to the \p membership. + * + * Technically speaking we have that + * aggregated_membership[refined_membership[v]] = membership[v] for each vertex v. + * + * The new aggregated graph is returned in \p aggregated_graph. This graph + * object should not yet be initialized, igraph_create() is called on it, and + * responsibility for destroying the object lies with the calling method + * + * The remaining results, aggregated_edge_weights, aggregate_vertex_weights and + * aggregated_membership are all expected to be initialized. + * + */ +static igraph_error_t leiden_aggregate( + const igraph_t *graph, + const igraph_inclist_t *edges_per_vertex, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + const igraph_vector_int_t *membership, + const igraph_vector_int_t *refined_membership, + const igraph_int_t nb_refined_clusters, + igraph_t *aggregated_graph, + igraph_vector_t *aggregated_edge_weights, + igraph_vector_t *aggregated_vertex_out_weights, + igraph_vector_t *aggregated_vertex_in_weights, + igraph_vector_int_t *aggregated_membership) { + + const igraph_bool_t directed = (vertex_in_weights != NULL); + igraph_vector_int_t aggregated_edges; + igraph_vector_t edge_weight_to_cluster; + igraph_vector_int_list_t refined_clusters; + igraph_vector_int_t *incident_edges; + igraph_vector_int_t neighbor_clusters; + igraph_bitset_t neighbor_cluster_added; + igraph_int_t c, degree, nb_neigh_clusters; + + /* Get refined clusters */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&refined_clusters, nb_refined_clusters); + IGRAPH_CHECK(leiden_get_clusters(refined_membership, &refined_clusters)); + + /* Initialize new edges */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&aggregated_edges, 0); + + /* We clear the aggregated edge weights, we will push each new edge weight */ + igraph_vector_clear(aggregated_edge_weights); + /* Simply resize the aggregated vertex weights and membership, they can be set directly */ + IGRAPH_CHECK(igraph_vector_resize(aggregated_vertex_out_weights, nb_refined_clusters)); + if (directed) { + IGRAPH_CHECK(igraph_vector_resize(aggregated_vertex_in_weights, nb_refined_clusters)); + } + IGRAPH_CHECK(igraph_vector_int_resize(aggregated_membership, nb_refined_clusters)); + + IGRAPH_VECTOR_INIT_FINALLY(&edge_weight_to_cluster, nb_refined_clusters); + + /* Initialize neighboring cluster */ + IGRAPH_BITSET_INIT_FINALLY(&neighbor_cluster_added, nb_refined_clusters); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbor_clusters, nb_refined_clusters); + + /* Check per cluster */ + for (c = 0; c < nb_refined_clusters; c++) { + igraph_vector_int_t* refined_cluster = igraph_vector_int_list_get_ptr(&refined_clusters, c); + igraph_int_t n_c = igraph_vector_int_size(refined_cluster); + igraph_int_t v = -1; + + /* Calculate the total edge weight to other clusters */ + VECTOR(*aggregated_vertex_out_weights)[c] = 0.0; + if (directed) { + VECTOR(*aggregated_vertex_in_weights)[c] = 0.0; + } + nb_neigh_clusters = 0; + for (igraph_int_t i = 0; i < n_c; i++) { + v = VECTOR(*refined_cluster)[i]; + incident_edges = igraph_inclist_get(edges_per_vertex, v); + degree = igraph_vector_int_size(incident_edges); + + for (igraph_int_t j = 0; j < degree; j++) { + igraph_int_t e = VECTOR(*incident_edges)[j]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + igraph_int_t c2 = VECTOR(*refined_membership)[u]; + + if (c2 > c) { + if (!IGRAPH_BIT_TEST(neighbor_cluster_added, c2)) { + IGRAPH_BIT_SET(neighbor_cluster_added, c2); + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c2; + } + VECTOR(edge_weight_to_cluster)[c2] += VECTOR(*edge_weights)[e]; + } + } + + VECTOR(*aggregated_vertex_out_weights)[c] += VECTOR(*vertex_out_weights)[v]; + if (directed) { + VECTOR(*aggregated_vertex_in_weights)[c] += VECTOR(*vertex_in_weights)[v]; + } + } + + /* Add actual edges from this cluster to the other clusters */ + for (igraph_int_t i = 0; i < nb_neigh_clusters; i++) { + igraph_int_t c2 = VECTOR(neighbor_clusters)[i]; + + /* Add edge */ + IGRAPH_CHECK(igraph_vector_int_push_back(&aggregated_edges, c)); + IGRAPH_CHECK(igraph_vector_int_push_back(&aggregated_edges, c2)); + + /* Add edge weight */ + IGRAPH_CHECK(igraph_vector_push_back(aggregated_edge_weights, VECTOR(edge_weight_to_cluster)[c2])); + + VECTOR(edge_weight_to_cluster)[c2] = 0.0; + IGRAPH_BIT_CLEAR(neighbor_cluster_added, c2); + } + + VECTOR(*aggregated_membership)[c] = VECTOR(*membership)[v]; + + } + + igraph_vector_int_destroy(&neighbor_clusters); + igraph_bitset_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weight_to_cluster); + igraph_vector_int_list_destroy(&refined_clusters); + IGRAPH_FINALLY_CLEAN(4); + + igraph_destroy(aggregated_graph); + IGRAPH_CHECK(igraph_create(aggregated_graph, &aggregated_edges, nb_refined_clusters, + directed)); + + igraph_vector_int_destroy(&aggregated_edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Calculate the quality of the partition. + * + * The quality is defined as + * + * 1 / 2m sum_ij (A_ij - gamma n_i n_j) d(s_i, s_j) + * + * for undirected graphs and as + * + * 1 / m sum_ij (A_ij - gamma n^out_i n^in_j) d(s_i, s_j) + * + * where m is the total edge weight, A_ij is the weight of edge (i, j), gamma is + * the so-called resolution parameter, n_i is the vertex weight of vertex i, s_i is + * the cluster of vertex i and d(x, y) = 1 if and only if x = y and 0 otherwise. + * + * Note that by setting n_i = k_i the degree of vertex i and dividing gamma by 2m, + * we effectively optimize modularity. By setting n_i = 1 we optimize the + * Constant Potts Model. + * + * This can be represented as a sum over clusters as + * + * 1 / 2m sum_c (e_c - gamma N_c^2) + * + * where e_c = sum_ij A_ij d(s_i, c)d(s_j, c) is the internal edge weight + * in cluster c (or twice this value if undirected) and + * N_c = sum_i n_i d(s_i, c) is the sum of the vertex weights inside cluster c. + * This is how the quality is calculated in practice. + */ +static igraph_error_t leiden_quality( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + const igraph_vector_int_t *membership, + const igraph_int_t nb_clusters, + const igraph_real_t resolution, + igraph_real_t *quality) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_bool_t directed = (vertex_in_weights != NULL); + const igraph_real_t directed_multiplier = directed ? 1.0 : 2.0; + igraph_vector_t cluster_out_weights, cluster_in_weights; + igraph_real_t total_edge_weight = 0.0; + + *quality = 0.0; + + for (igraph_int_t e=0; e < ecount; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + total_edge_weight += VECTOR(*edge_weights)[e]; + + /* We add the internal edge weights. */ + if (VECTOR(*membership)[from] == VECTOR(*membership)[to]) { + *quality += directed_multiplier * VECTOR(*edge_weights)[e]; + } + } + + /* Initialize and compute cluster weights. */ + + IGRAPH_VECTOR_INIT_FINALLY(&cluster_out_weights, vcount); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&cluster_in_weights, vcount); + } + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_int_t c = VECTOR(*membership)[i]; + VECTOR(cluster_out_weights)[c] += VECTOR(*vertex_out_weights)[i]; + if (directed) { + VECTOR(cluster_in_weights)[c] += VECTOR(*vertex_in_weights)[i]; + } + } + + /* We subtract gamma * N^out_c * N^in_c */ + + for (igraph_int_t c = 0; c < nb_clusters; c++) { + if (directed) { + *quality -= resolution * VECTOR(cluster_out_weights)[c] * VECTOR(cluster_in_weights)[c]; + } else { + *quality -= resolution * VECTOR(cluster_out_weights)[c] * VECTOR(cluster_out_weights)[c]; + } + } + + if (directed) { + igraph_vector_destroy(&cluster_in_weights); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&cluster_out_weights); + IGRAPH_FINALLY_CLEAN(1); + + /* We normalise by m or 2m depending on directedness */ + *quality /= (directed_multiplier * total_edge_weight); + + return IGRAPH_SUCCESS; +} + +/* This is the core of the Leiden algorithm and relies on subroutines to + * perform the three different phases: (1) local moving of vertices, (2) + * refinement of the partition and (3) aggregation of the network based on the + * refined partition, using the non-refined partition to create an initial + * partition for the aggregate network. + */ +static igraph_error_t community_leiden( + const igraph_t *graph, + igraph_vector_t *edge_weights, + igraph_vector_t *vertex_out_weights, + igraph_vector_t *vertex_in_weights, + igraph_real_t resolution, + igraph_real_t beta, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality, + igraph_bool_t *changed) { + + const igraph_int_t n = igraph_vcount(graph); + const igraph_bool_t directed = (vertex_in_weights != NULL); + igraph_int_t nb_refined_clusters; + igraph_int_t i, c; + igraph_t aggregated_graph, *i_graph; + igraph_vector_t aggregated_edge_weights; + igraph_vector_t aggregated_vertex_out_weights, aggregated_vertex_in_weights; + igraph_vector_int_t aggregated_membership; + igraph_vector_t *i_edge_weights; + igraph_vector_t *i_vertex_out_weights, *i_vertex_in_weights; + igraph_vector_int_t *i_membership; + igraph_vector_t tmp_edge_weights, tmp_vertex_out_weights, tmp_vertex_in_weights; + igraph_vector_int_t tmp_membership; + igraph_vector_int_t refined_membership; + igraph_vector_int_t aggregate_vertex; + igraph_vector_int_list_t clusters; + igraph_inclist_t edges_per_vertex; + igraph_bool_t continue_clustering; + igraph_int_t level = 0; + + /* Initialize temporary weights and membership to be used in aggregation */ + IGRAPH_VECTOR_INIT_FINALLY(&tmp_edge_weights, 0); + IGRAPH_VECTOR_INIT_FINALLY(&tmp_vertex_out_weights, 0); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&tmp_vertex_in_weights, 0); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp_membership, 0); + + /* Initialize clusters */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&clusters, n); + + /* Initialize aggregate vertices, which initially is identical to simply the + * vertices in the graph. */ + IGRAPH_CHECK(igraph_vector_int_init_range(&aggregate_vertex, 0, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &aggregate_vertex); + + /* Initialize refined membership */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&refined_membership, 0); + + /* Initialize aggregated graph */ + IGRAPH_CHECK(igraph_empty(&aggregated_graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, &aggregated_graph); + + /* Initialize aggregated edge weights */ + IGRAPH_VECTOR_INIT_FINALLY(&aggregated_edge_weights, 0); + + /* Initialize aggregated vertex weights */ + IGRAPH_VECTOR_INIT_FINALLY(&aggregated_vertex_out_weights, 0); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&aggregated_vertex_in_weights, 0); + } + + /* Initialize aggregated membership */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&aggregated_membership, 0); + + /* Set actual graph, weights and membership to be used. */ + i_graph = (igraph_t*)graph; + i_edge_weights = edge_weights; + i_vertex_out_weights = vertex_out_weights; + i_vertex_in_weights = directed ? vertex_in_weights : NULL; + i_membership = membership; + + /* Clean membership: ensure that cluster indices are 0 <= c < n. */ + IGRAPH_CHECK(igraph_reindex_membership(i_membership, NULL, nb_clusters)); + + /* We start out with no changes, whenever a vertex is moved, this will be set to true. */ + *changed = false; + do { + + /* Get incidence list for fast iteration */ + IGRAPH_CHECK(igraph_inclist_init( i_graph, &edges_per_vertex, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &edges_per_vertex); + + /* Move around the vertices in order to increase the quality */ + IGRAPH_CHECK(leiden_fastmove_vertices(i_graph, + &edges_per_vertex, + i_edge_weights, + i_vertex_out_weights, i_vertex_in_weights, + resolution, + nb_clusters, + i_membership, + changed)); + + /* We only continue clustering if not all clusters are represented by a + * single vertex yet + */ + continue_clustering = (*nb_clusters < igraph_vcount(i_graph)); + + if (continue_clustering) { + /* Set original membership */ + if (level > 0) { + for (i = 0; i < n; i++) { + igraph_int_t v_aggregate = VECTOR(aggregate_vertex)[i]; + VECTOR(*membership)[i] = VECTOR(*i_membership)[v_aggregate]; + } + } + + /* Get vertex sets for each cluster. */ + IGRAPH_CHECK(leiden_get_clusters(i_membership, &clusters)); + + /* Ensure refined membership is correct size */ + IGRAPH_CHECK(igraph_vector_int_resize(&refined_membership, igraph_vcount(i_graph))); + + /* Refine each cluster */ + nb_refined_clusters = 0; + for (c = 0; c < *nb_clusters; c++) { + igraph_vector_int_t* cluster = igraph_vector_int_list_get_ptr(&clusters, c); + IGRAPH_CHECK(leiden_merge_vertices(i_graph, + &edges_per_vertex, + i_edge_weights, + i_vertex_out_weights, i_vertex_in_weights, + cluster, i_membership, c, + resolution, beta, + &nb_refined_clusters, &refined_membership)); + /* Empty cluster */ + igraph_vector_int_clear(cluster); + } + + /* If refinement didn't aggregate anything, we aggregate on the basis of + * the actual clustering */ + if (nb_refined_clusters >= igraph_vcount(i_graph)) { + IGRAPH_CHECK(igraph_vector_int_update(&refined_membership, i_membership)); + nb_refined_clusters = *nb_clusters; + } + + /* Keep track of aggregate vertex. */ + for (i = 0; i < n; i++) { + /* Current aggregate vertex */ + igraph_int_t v_aggregate = VECTOR(aggregate_vertex)[i]; + /* New aggregate vertex */ + VECTOR(aggregate_vertex)[i] = VECTOR(refined_membership)[v_aggregate]; + } + + IGRAPH_CHECK(leiden_aggregate( + i_graph, + &edges_per_vertex, + i_edge_weights, + i_vertex_out_weights, i_vertex_in_weights, + i_membership, &refined_membership, nb_refined_clusters, + &aggregated_graph, + &tmp_edge_weights, + &tmp_vertex_out_weights, directed ? &tmp_vertex_in_weights : NULL, + &tmp_membership)); + + /* On the lowest level, the actual graph and vertex and edge weights and + * membership are used. On higher levels, we will use the aggregated graph + * and associated vectors. + */ + if (level == 0) { + /* Set actual graph, weights and membership to be used. */ + i_graph = &aggregated_graph; + i_edge_weights = &aggregated_edge_weights; + i_vertex_out_weights = &aggregated_vertex_out_weights; + if (directed) { + i_vertex_in_weights = &aggregated_vertex_in_weights; + } + i_membership = &aggregated_membership; + } + + /* Update the aggregated administration. */ + IGRAPH_CHECK(igraph_vector_update(i_edge_weights, &tmp_edge_weights)); + IGRAPH_CHECK(igraph_vector_update(i_vertex_out_weights, &tmp_vertex_out_weights)); + if (directed) { + IGRAPH_CHECK(igraph_vector_update(i_vertex_in_weights, &tmp_vertex_in_weights)); + } + IGRAPH_CHECK(igraph_vector_int_update(i_membership, &tmp_membership)); + + level += 1; + } + + /* We are done iterating, so we destroy the incidence list */ + igraph_inclist_destroy(&edges_per_vertex); + IGRAPH_FINALLY_CLEAN(1); + } while (continue_clustering); + + /* Free aggregated graph and associated vectors */ + igraph_vector_int_destroy(&aggregated_membership); + if (directed) igraph_vector_destroy(&aggregated_vertex_in_weights); + igraph_vector_destroy(&aggregated_vertex_out_weights); + igraph_vector_destroy(&aggregated_edge_weights); + igraph_destroy(&aggregated_graph); + + /* Free remaining memory */ + igraph_vector_int_destroy(&refined_membership); + igraph_vector_int_destroy(&aggregate_vertex); + igraph_vector_int_list_destroy(&clusters); + igraph_vector_int_destroy(&tmp_membership); + igraph_vector_destroy(&tmp_vertex_out_weights); + if (directed) igraph_vector_destroy(&tmp_vertex_in_weights); + igraph_vector_destroy(&tmp_edge_weights); + + if (directed) { + IGRAPH_FINALLY_CLEAN(12); + } else { + IGRAPH_FINALLY_CLEAN(10); + } + + /* Calculate quality */ + if (quality) { + IGRAPH_CHECK(leiden_quality(graph, + edge_weights, vertex_out_weights, vertex_in_weights, + membership, + *nb_clusters, resolution, + quality)); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_community_leiden + * \brief Finding community structure using the Leiden algorithm. + * + * This function implements the Leiden algorithm for finding community + * structure. + * + * + * It is similar to the multilevel algorithm, often called the Louvain + * algorithm, but it is faster and yields higher quality solutions. It can + * optimize both modularity and the Constant Potts Model, which does not suffer + * from the resolution-limit (see Traag, Van Dooren & Nesterov). + * + * + * The Leiden algorithm consists of three phases: (1) local moving of vertices, (2) + * refinement of the partition and (3) aggregation of the network based on the + * refined partition, using the non-refined partition to create an initial + * partition for the aggregate network. In the local move procedure in the + * Leiden algorithm, only vertices whose neighborhood has changed are visited. Only + * moves that strictly improve the quality function are made. The refinement is + * done by restarting from a singleton partition within each cluster and + * gradually merging the subclusters. When aggregating, a single cluster may + * then be represented by several vertices (which are the subclusters identified in + * the refinement). + * + * + * The Leiden algorithm provides several guarantees. The Leiden algorithm is + * typically iterated: the output of one iteration is used as the input for the + * next iteration. At each iteration all clusters are guaranteed to be (weakly) + * connected and well-separated. After an iteration in which nothing has + * changed, all vertices and some parts are guaranteed to be locally optimally + * assigned. Note that even if a single iteration did not result in any change, + * it is still possible that a subsequent iteration might find some + * improvement. Each iteration explores different subsets of vertices to consider + * for moving from one cluster to another. Finally, asymptotically, all subsets + * of all clusters are guaranteed to be locally optimally assigned. For more + * details, please see Traag, Waltman & van Eck (2019). + * + * + * The objective function being optimized is + * + * + * 1 / 2m sum_ij (A_ij - γ n_i n_j) δ(s_i, s_j) + * + * + * in the undirected case and + * + * + * 1 / m sum_ij (A_ij - γ n^out_i n^in_j) δ(s_i, s_j) + * + * + * in the directed case. + * Here \c m is the total edge weight, A_ij is the weight of edge + * (i, j), \c γ is the so-called resolution parameter, n_i + * is the vertex weight of vertex \c i (separate out- and in-weights are used + * with directed graphs), s_i is the cluster of vertex + * \c i and δ(x, y) = 1 if and only if x = y and 0 + * otherwise. + * + * + * By setting n_i = k_i, the degree of vertex \c i, and + * dividing \c γ by 2m (by \c m in the directed case), we effectively + * obtain an expression for modularity. Hence, the standard modularity will be + * optimized when you supply the degrees (out- and in-degrees with directed graphs) + * as the vertex weights and by supplying as a resolution parameter + * 1/(2m) (1/m with directed graphs). + * Use the \ref igraph_community_leiden_simple() convenience function to + * compute vertex weights automatically for modularity maximization. + * + * + * References: + * + * + * V. A. Traag, L. Waltman, N. J. van Eck: + * From Louvain to Leiden: guaranteeing well-connected communities. + * Scientific Reports, 9(1), 5233 (2019). + * http://dx.doi.org/10.1038/s41598-019-41695-z + * + * + * V. A. Traag, P. Van Dooren, and Y. Nesterov: + * Narrow scope for resolution-limit-free community detection. + * Phys. Rev. E 84, 016114 (2011). + * https://doi.org/10.1103/PhysRevE.84.016114 + * + * \param graph The input graph. + * \param edge_weights Numeric vector containing edge weights. If \c NULL, + * every edge has equal weight of 1. The weights need not be non-negative. + * \param vertex_out_weights Numeric vector containing vertex weights, or vertex + * out-weights for directed graphs. If \c NULL, every vertex has equal + * weight of 1. + * \param vertex_in_weights Numeric vector containing vertex in-weights for + * directed graphs. If set to \c NULL, in-weights are assumed to be the same + * as out-weights, which effectively ignores edge directions. + * Must be \c NULL for undirected graphs. + * \param n_iterations Iterate the core Leiden algorithm the indicated number + * of times. If this is a negative number, it will continue iterating until + * an iteration did not change the clustering. Two iterations are often + * sufficient, thus 2 is a reasonable default. + * \param beta The randomness used in the refinement step when merging. A small + * amount of randomness (\c beta = 0.01) typically works well. + * \param start Start from membership vector. If this is true, the optimization + * will start from the provided membership vector. If this is false, the + * optimization will start from a singleton partition. + * \param n_iterations Iterate the core Leiden algorithm for the indicated number + * of times. If this is a negative number, it will continue iterating until + * an iteration did not change the clustering. + * \param membership The membership vector. This is both used as the initial + * membership from which optimisation starts and is updated in place. It + * must hence be properly initialized. When finding clusters from scratch it + * is typically started using a singleton clustering. This can be achieved + * using \ref igraph_vector_int_init_range(). + * \param nb_clusters The number of clusters contained in the final \p membership. + * If \c NULL, the number of clusters will not be returned. + * \param quality The quality of the partition, in terms of the objective + * function as included in the documentation. If \c NULL the quality will + * not be calculated. + * \return Error code. + * + * Time complexity: near linear on sparse graphs. + * + * \sa \ref igraph_community_leiden_simple() for a simplified interface + * that allows specifying an objective function directly and does not require + * vertex weights. + * + * \example examples/simple/igraph_community_leiden.c + */ +igraph_error_t igraph_community_leiden( + const igraph_t *graph, + const igraph_vector_t *edge_weights, + const igraph_vector_t *vertex_out_weights, + const igraph_vector_t *vertex_in_weights, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t *i_edge_weights, *i_vertex_out_weights, *i_vertex_in_weights; + igraph_int_t i_nb_clusters; + + if (!nb_clusters) { + nb_clusters = &i_nb_clusters; + } + + if (start) { + if (!membership) { + IGRAPH_ERROR("Cannot start optimization if membership is missing.", IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(membership) != vcount) { + IGRAPH_ERROR("Membership vector length does not equal the number of vertices.", IGRAPH_EINVAL); + } + } else { + if (!membership) + IGRAPH_ERROR("Membership vector should be supplied and initialized, " + "even when not starting optimization from it.", IGRAPH_EINVAL); + + IGRAPH_CHECK(igraph_vector_int_range(membership, 0, vcount)); + } + + /* Check edge weights to possibly use default. */ + if (!edge_weights) { + i_edge_weights = IGRAPH_CALLOC(1, igraph_vector_t); + IGRAPH_CHECK_OOM(i_edge_weights, "Leiden algorithm failed, could not allocate memory for edge weights."); + IGRAPH_FINALLY(igraph_free, i_edge_weights); + IGRAPH_CHECK(igraph_vector_init(i_edge_weights, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_vector_destroy, i_edge_weights); + igraph_vector_fill(i_edge_weights, 1); + } else { + if (igraph_vector_size(edge_weights) != ecount) { + IGRAPH_ERRORF("Edge weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(edge_weights), ecount); + } + i_edge_weights = (igraph_vector_t*)edge_weights; + } + + /* Check vertex out-weights to possibly use default. */ + if (!vertex_out_weights) { + i_vertex_out_weights = IGRAPH_CALLOC(1, igraph_vector_t); + IGRAPH_CHECK_OOM(i_vertex_out_weights, "Leiden algorithm failed, could not allocate memory for vertex weights."); + IGRAPH_FINALLY(igraph_free, i_vertex_out_weights); + IGRAPH_VECTOR_INIT_FINALLY(i_vertex_out_weights, vcount); + igraph_vector_fill(i_vertex_out_weights, 1); + } else { + if (igraph_vector_size(vertex_out_weights) != vcount) { + IGRAPH_ERRORF("Vertex %sweight vector length (%" IGRAPH_PRId ") does not match number of vertices (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + directed ? "out-" : "", + igraph_vector_size(vertex_out_weights), vcount); + } + i_vertex_out_weights = (igraph_vector_t*)vertex_out_weights; + } + + if (directed) { + /* When in-weights are not given for a directed graph, + * assume that they are the same as the out-weights. + * This effectively ignores edge directions. */ + if (vertex_in_weights) { + if (igraph_vector_size(vertex_in_weights) != vcount) { + IGRAPH_ERRORF("Vertex in-weight vector length (%" IGRAPH_PRId ") does not match number of vertices (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(vertex_in_weights), vcount); + } + i_vertex_in_weights = (igraph_vector_t*)vertex_in_weights; + } else { + i_vertex_in_weights = i_vertex_out_weights; + } + } else { + /* In-weights must be NULL in the undirected case. */ + if (vertex_in_weights) { + IGRAPH_ERROR("Vertex in-weights must not be given for undirected graphs.", IGRAPH_EINVAL); + } else { + i_vertex_in_weights = NULL; + } + } + + /* Perform actual Leiden algorithm iteratively. We either + * perform a fixed number of iterations, or we perform + * iterations until the quality remains unchanged. Even if + * a single iteration did not change anything, a subsequent + * iteration may still find some improvement. This is because + * each iteration explores different subsets of vertices. + */ + igraph_bool_t changed = true; + for (igraph_int_t itr = 0; + n_iterations < 0 ? changed : itr < n_iterations; + itr++) { + IGRAPH_CHECK(community_leiden(graph, + i_edge_weights, i_vertex_out_weights, i_vertex_in_weights, + resolution, beta, + membership, nb_clusters, quality, &changed)); + } + + if (!edge_weights) { + igraph_vector_destroy(i_edge_weights); + IGRAPH_FREE(i_edge_weights); + IGRAPH_FINALLY_CLEAN(2); + } + + if (!vertex_out_weights) { + igraph_vector_destroy(i_vertex_out_weights); + IGRAPH_FREE(i_vertex_out_weights); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_community_leiden_simple + * \brief Finding community structure using the Leiden algorithm, simple interface. + * + * This is a simplified interface to \ref igraph_community_leiden() for + * convenience purposes. Instead of requiring vertex weights, it allows + * choosing from a set of objective functions to maximize. It implements + * these objective functions by passing suitable vertex weights to + * \ref igraph_community_leiden(), as explained in the documentation of + * that function. + * + * \param graph The input graph. May be directed or undirected. + * \param weights The edge weights. If \c NULL, all weights are assumed to be 1. + * \param objective The objective function to maximize. + * \clist + * \cli IGRAPH_LEIDEN_OBJECTIVE_MODULARITY + * Use the generalized modularity, defined as + * Q = 1/(2m) sum_ij (A_ij - γ k_i k_j / (2m)) δ(c_i, c_j) + * for undirected graphs and as + * Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i, c_j) + * for directed graphs. This effectively uses a multigraph configuration + * model as the null model. Edge weights must not be negative. + * \cli IGRAPH_LEIDEN_OBJECTIVE_CPM + * Use the constant Potts model, whose objective function is defined as + * Q = 1/(2m) sum_ij (A_ij - γ) δ(c_i, c_j) + * for undirected graphs and as + * Q = 1/m sum_ij (A_ij - γ) δ(c_i, c_j) + * for directed graphs. Edge weights are allowed to be negative. + * Edge directions have no impact on the result. + * \cli IGRAPH_LEIDEN_OBJECTIVE_ER + * Use an objective function based on the multigraph Erdős-Rényi G(n,p) + * null model, defined as + * Q = 1/(2m) sum_ij (A_ij - γ p) δ(c_i, c_j) + * for undirected graphs and as + * Q = 1/m sum_ij (A_ij - γ p) δ(c_i, c_j) + * for directed graphs. \c p is the weighted density, i.e. the average + * link strength between all vertex pairs (whether adjacent or not). + * Edge weights must not be negative. Edge directions have no impact on + * the result. + * \endclist + * In the above formulas, \c A is the adjacency matrix, \c m is the total + * edge weight, \c k are the (out- and in-) degrees, \c γ is the resolution + * parameter, and δ(c_i, c_j) is 1 if vertices \c i and \c j + * are in the same community and 0 otherwise. Edge directions are only + * relevant with \c IGRAPH_LEIDEN_OBJECTIVE_MODULARITY. The other two + * objective functions are equivalent between directed and undirected graphs: + * the formal difference is due to each edge being included twice in + * undirected (symmetric) adjacency matrices. + * \param resolution The resolution parameter, which is represented by γ in + * the objective functions detailed above. + * \param beta The randomness used in the refinement step when merging. A small + * amount of randomness (\c beta = 0.01) typically works well. + * \param start Start from membership vector. If this is true, the optimization + * will start from the provided membership vector. If this is false, the + * optimization will start from a singleton partition. + * \param n_iterations Iterate the core Leiden algorithm the indicated number + * of times. If this is a negative number, it will continue iterating until + * an iteration did not change the clustering. Two iterations are often + * sufficient, thus 2 is a reasonable default. + * \param membership The membership vector. If \p start is set to \c false, + * it will be resized appropriately. If \p start is \c true, it must be + * a valid membership vector for the given \p graph. + * \param nb_clusters The number of clusters contained in the final \p membership. + * If \c NULL, the number of clusters will not be returned. + * \param quality The quality of the partition, in terms of the objective + * function selected by \p objective. If \c NULL the quality will + * not be calculated. + * \return Error code. + * + * Time complexity: near linear on sparse graphs. + * + * \sa \ref igraph_community_leiden() for a more flexible interface that + * allows specifying raw vertex weights. + */ +igraph_error_t igraph_community_leiden_simple( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_leiden_objective_t objective, + igraph_real_t resolution, + igraph_real_t beta, + igraph_bool_t start, + igraph_int_t n_iterations, + igraph_vector_int_t *membership, + igraph_int_t *nb_clusters, + igraph_real_t *quality) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t vertex_out_weights, vertex_in_weights; + igraph_vector_int_t i_membership, *p_membership; + igraph_real_t min_weight = IGRAPH_INFINITY; + + /* Basic weight vector validation, calculate properties used for validation steps + * specific to different objective functions. */ + if (weights) { + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Edge weight vector length does not match number of edges.", IGRAPH_EINVAL); + } + for (igraph_int_t i=0; i < ecount; i++) { + igraph_real_t w = VECTOR(*weights)[i]; + if (w < min_weight) { + min_weight = w; + } + if (! isfinite(w)) { + IGRAPH_ERRORF("Edge weights must not be infinite or NaN, got %g.", + IGRAPH_EINVAL, w); + } + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&vertex_out_weights, vcount); + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&vertex_in_weights, vcount); + } + + /* igraph_community_leiden() always requires an initialized membership vector + * of the correct size to be given. We relax this requirement to the case + * when start = true. */ + if (start) { + if (!membership) { + IGRAPH_ERROR("Requesting to start the computation from a specific " + "community assignment, but no membership vector given.", + IGRAPH_EINVAL); + } + if (igraph_vector_int_size(membership) != vcount) { + IGRAPH_ERRORF("Requesting to start the computation from a specific " + "community assignment, but the given membership vector " + "has a different size (%" IGRAPH_PRId " than the vertex " + "count (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_int_size(membership), vcount); + } + p_membership = membership; + } else { + if (!membership) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&i_membership, vcount); + p_membership = &i_membership; + } else { + IGRAPH_CHECK(igraph_vector_int_resize(membership, vcount)); + p_membership = membership; + } + } + + switch (objective) { + case IGRAPH_LEIDEN_OBJECTIVE_MODULARITY: + if (min_weight < 0) { + IGRAPH_ERRORF("Edge weights must not be negative for Leiden community " + "detection with modularity objective function, got %g.", + IGRAPH_EINVAL, + min_weight); + } + + IGRAPH_CHECK(igraph_strength( + graph, &vertex_out_weights, + igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, weights)); + if (directed) { + IGRAPH_CHECK(igraph_strength( + graph, &vertex_in_weights, + igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, weights)); + } + + /* If directed, the sum of vertex_out_weights is the total edge weight. + * If undirected, it is twice the total edge weight. */ + resolution /= igraph_vector_sum(&vertex_out_weights); + + break; + + case IGRAPH_LEIDEN_OBJECTIVE_CPM: + /* TODO: Potential minor optimization is to use the same vector for both. */ + igraph_vector_fill(&vertex_out_weights, 1); + if (directed) { + igraph_vector_fill(&vertex_in_weights, 1); + } + + break; + + case IGRAPH_LEIDEN_OBJECTIVE_ER: + if (min_weight < 0) { + IGRAPH_ERRORF("Edge weights must not be negative for Leiden community " + "detection with ER objective function, got %g.", + IGRAPH_EINVAL, + min_weight); + } + + /* TODO: Potential minor optimization is to use the same vector for both. */ + igraph_vector_fill(&vertex_out_weights, 1); + if (directed) { + igraph_vector_fill(&vertex_in_weights, 1); + } + + { + igraph_real_t p; + /* Note: Loops must be allowed, as the aggregation step of the + * algorithm effectively creates them. */ + IGRAPH_CHECK(igraph_density(graph, weights, &p, /* loops */ true)); + resolution *= p; + } + + break; + + + default: + IGRAPH_ERROR("Invalid objective function for Leiden community detection.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_community_leiden( + graph, weights, + &vertex_out_weights, directed ? &vertex_in_weights : NULL, + resolution, beta, start, n_iterations, p_membership, nb_clusters, quality)); + + if (!membership) { + igraph_vector_int_destroy(&i_membership); + IGRAPH_FINALLY_CLEAN(1); + } + + if (directed) { + igraph_vector_destroy(&vertex_in_weights); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&vertex_out_weights); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/louvain.c b/src/community/louvain.c new file mode 100644 index 0000000..e76c79f --- /dev/null +++ b/src/community/louvain.c @@ -0,0 +1,735 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_qsort.h" +#include "igraph_random.h" + +#include "core/interruption.h" + +/* Structure storing a community */ +typedef struct { + igraph_int_t size; /* Size of the community */ + igraph_real_t weight_inside; /* Sum of edge weights inside community */ + igraph_real_t weight_all; /* Sum of edge weights starting/ending in the community */ +} igraph_i_multilevel_community; + +/* Global community list structure */ +typedef struct { + igraph_int_t communities_no, vertices_no; /* Number of communities, number of vertices */ + igraph_real_t weight_sum; /* Sum of edges weight in the whole graph */ + igraph_i_multilevel_community *item; /* List of communities */ + igraph_vector_int_t *membership; /* Community IDs */ + igraph_vector_t *weights; /* Graph edge weights */ +} igraph_i_multilevel_community_list; + +/* Computes the modularity of a community partitioning */ +static igraph_real_t igraph_i_multilevel_community_modularity( + const igraph_i_multilevel_community_list *communities, + const igraph_real_t resolution) { + igraph_real_t result = 0.0; + igraph_real_t m = communities->weight_sum; + + for (igraph_int_t i = 0; i < communities->vertices_no; i++) { + if (communities->item[i].size > 0) { + result += (communities->item[i].weight_inside - resolution * communities->item[i].weight_all * communities->item[i].weight_all / m) / m; + } + } + + return result; +} + +typedef struct { + igraph_int_t from; + igraph_int_t to; + igraph_int_t id; +} igraph_i_multilevel_link; + +static int igraph_i_multilevel_link_cmp(const void *a, const void *b) { + igraph_int_t diff; + + diff = ((igraph_i_multilevel_link*)a)->from - ((igraph_i_multilevel_link*)b)->from; + + if (diff < 0) { + return -1; + } else if (diff > 0) { + return 1; + } + + diff = ((igraph_i_multilevel_link*)a)->to - ((igraph_i_multilevel_link*)b)->to; + + if (diff < 0) { + return -1; + } else if (diff > 0) { + return 1; + } else { + return 0; + } +} + +/* removes multiple edges and returns new edge IDs for each edge in |E|log|E| */ +static igraph_error_t igraph_i_multilevel_simplify_multiple(igraph_t *graph, igraph_vector_int_t *eids) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t l = -1, last_from = -1, last_to = -1; + igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_int_t edges; + igraph_i_multilevel_link *links; + + /* Make sure there's enough space in eids to store the new edge IDs */ + IGRAPH_CHECK(igraph_vector_int_resize(eids, ecount)); + + links = IGRAPH_CALLOC(ecount, igraph_i_multilevel_link); + IGRAPH_CHECK_OOM(links, "Multi-level community structure detection failed."); + IGRAPH_FINALLY(igraph_free, links); + + for (igraph_int_t i = 0; i < ecount; i++) { + links[i].from = IGRAPH_FROM(graph, i); + links[i].to = IGRAPH_TO(graph, i); + links[i].id = i; + } + + igraph_qsort(links, (size_t) ecount, sizeof(igraph_i_multilevel_link), + igraph_i_multilevel_link_cmp); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + for (igraph_int_t i = 0; i < ecount; i++) { + if (links[i].from == last_from && links[i].to == last_to) { + VECTOR(*eids)[links[i].id] = l; + continue; + } + + last_from = links[i].from; + last_to = links[i].to; + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, last_from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, last_to)); + + l++; + + VECTOR(*eids)[links[i].id] = l; + } + + IGRAPH_FREE(links); + IGRAPH_FINALLY_CLEAN(1); + + igraph_destroy(graph); + IGRAPH_CHECK(igraph_create(graph, &edges, igraph_vcount(graph), directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +typedef struct { + igraph_int_t community; + igraph_real_t weight; +} igraph_i_multilevel_community_link; + +static int igraph_i_multilevel_community_link_cmp(const void *a, const void *b) { + igraph_int_t diff = ( + ((igraph_i_multilevel_community_link*)a)->community - + ((igraph_i_multilevel_community_link*)b)->community + ); + return diff < 0 ? -1 : diff > 0 ? 1 : 0; +} + +/** + * Given a graph, a community structure and a vertex ID, this method + * calculates: + * + * - edges: the list of edge IDs that are incident on the vertex + * - weight_all: the total weight of these edges + * - weight_inside: the total weight of edges that stay within the same + * community where the given vertex is right now, excluding loop edges + * - weight_loop: the total weight of loop edges + * - links_community and links_weight: together these two vectors list the + * communities incident on this vertex and the total weight of edges + * pointing to these communities + */ +static igraph_error_t igraph_i_multilevel_community_links( + const igraph_t *graph, + const igraph_i_multilevel_community_list *communities, + igraph_int_t vertex, igraph_vector_int_t *edges, + igraph_real_t *weight_all, igraph_real_t *weight_inside, igraph_real_t *weight_loop, + igraph_vector_int_t *links_community, igraph_vector_t *links_weight) { + + igraph_int_t n, last = -1, c = -1; + igraph_real_t weight = 1; + igraph_int_t to, to_community; + igraph_int_t community = VECTOR(*(communities->membership))[vertex]; + igraph_i_multilevel_community_link *links; + + *weight_all = *weight_inside = *weight_loop = 0; + + igraph_vector_int_clear(links_community); + igraph_vector_clear(links_weight); + + /* Get the list of incident edges */ + IGRAPH_CHECK(igraph_incident(graph, edges, vertex, IGRAPH_ALL, IGRAPH_LOOPS)); + + n = igraph_vector_int_size(edges); + links = IGRAPH_CALLOC(n, igraph_i_multilevel_community_link); + IGRAPH_CHECK_OOM(links, "Multi-level community structure detection failed."); + IGRAPH_FINALLY(igraph_free, links); + + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t eidx = VECTOR(*edges)[i]; + weight = VECTOR(*communities->weights)[eidx]; + + to = IGRAPH_OTHER(graph, eidx, vertex); + + *weight_all += weight; + if (to == vertex) { + *weight_loop += weight; + + links[i].community = community; + links[i].weight = 0; + continue; + } + + to_community = VECTOR(*(communities->membership))[to]; + if (community == to_community) { + *weight_inside += weight; + } + + /* debug("Link %ld (C: %ld) <-> %ld (C: %ld)\n", vertex, community, to, to_community); */ + + links[i].community = to_community; + links[i].weight = weight; + } + + /* Sort links by community ID and merge the same */ + igraph_qsort((void*)links, (size_t) n, sizeof(igraph_i_multilevel_community_link), + igraph_i_multilevel_community_link_cmp); + for (igraph_int_t i = 0; i < n; i++) { + to_community = links[i].community; + if (to_community != last) { + IGRAPH_CHECK(igraph_vector_int_push_back(links_community, to_community)); + IGRAPH_CHECK(igraph_vector_push_back(links_weight, links[i].weight)); + last = to_community; + c++; + } else { + VECTOR(*links_weight)[c] += links[i].weight; + } + } + + igraph_free(links); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_real_t igraph_i_multilevel_community_modularity_gain( + const igraph_i_multilevel_community_list *communities, + igraph_int_t community, igraph_int_t vertex, + igraph_real_t weight_all, igraph_real_t weight_inside, + const igraph_real_t resolution) { + IGRAPH_UNUSED(vertex); + return weight_inside - + resolution * communities->item[community].weight_all * weight_all / communities->weight_sum; +} + +/* Shrinks communities into single vertices, keeping all the edges. + * This method is internal because it destroys the graph in-place and + * creates a new one -- this is fine for the multilevel community + * detection where a copy of the original graph is used anyway. + * The membership vector will also be rewritten by the underlying + * igraph_membership_reindex call */ +static igraph_error_t igraph_i_multilevel_shrink(igraph_t *graph, igraph_vector_int_t *membership) { + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + IGRAPH_ASSERT(igraph_vector_int_size(membership) == no_of_nodes); + + if (no_of_nodes == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*no_of_edges); + + IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL)); + + /* Create the new edgelist */ + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, /* bycol= */ false)); + for (igraph_int_t i=0; i < 2*no_of_edges; i++) { + VECTOR(edges)[i] = VECTOR(*membership)[ VECTOR(edges)[i] ]; + } + + /* Create the new graph */ + igraph_destroy(graph); + no_of_nodes = igraph_vector_int_max(membership) + 1; + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_i_community_multilevel_step + * \brief Performs a single step of the multi-level modularity optimization method. + * + * This function implements a single step of the multi-level modularity optimization + * algorithm for finding community structure, see VD Blondel, J-L Guillaume, + * R Lambiotte and E Lefebvre: Fast unfolding of community hierarchies in large + * networks, http://arxiv.org/abs/0803.0476 for the details. + * + * This function was contributed by Tom Gregorovic. + * + * \param graph The input graph. It must be an undirected graph. + * \param weights Numeric vector containing edge weights. If \c NULL, + * every edge has equal weight. The weights are expected + * to be non-negative. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community. + * \param modularity The modularity of the partition is returned here. + * \c NULL means that the modularity is not needed. + * \param resolution Resolution parameter. Must be greater than or equal to 0. + * Default is 1. Lower values favor fewer, larger communities; + * higher values favor more, smaller communities. + * \return Error code. + * + * Time complexity: in average near linear on sparse graphs. + */ +static igraph_error_t igraph_i_community_multilevel_step( + igraph_t *graph, + igraph_vector_t *weights, + igraph_vector_int_t *membership, + igraph_real_t *modularity, + const igraph_real_t resolution) { + + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + igraph_real_t q, pass_q; + /* int pass; // used only for debugging */ + igraph_bool_t changed; + igraph_vector_int_t links_community; + igraph_vector_t links_weight; + igraph_vector_int_t edges; + igraph_vector_int_t temp_membership; + igraph_i_multilevel_community_list communities; + igraph_vector_int_t node_order; + + IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, vcount)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order); + igraph_vector_int_shuffle(&node_order); + + /* Initialize data structures */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&links_community, 0); + IGRAPH_VECTOR_INIT_FINALLY(&links_weight, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&temp_membership, vcount); + IGRAPH_CHECK(igraph_vector_int_resize(membership, vcount)); + + /* Initialize list of communities from graph vertices */ + communities.vertices_no = vcount; + communities.communities_no = vcount; + communities.weights = weights; + communities.weight_sum = 2.0 * igraph_vector_sum(weights); + communities.membership = membership; + communities.item = IGRAPH_CALLOC(vcount, igraph_i_multilevel_community); + IGRAPH_CHECK_OOM(communities.item, "Multi-level community structure detection failed."); + IGRAPH_FINALLY(igraph_free, communities.item); + + /* Still initializing the communities data structure */ + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(*communities.membership)[i] = i; + communities.item[i].size = 1; + communities.item[i].weight_inside = 0; + communities.item[i].weight_all = 0; + } + + /* Some more initialization :) */ + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t ffrom = IGRAPH_FROM(graph, i), fto = IGRAPH_TO(graph, i); + igraph_real_t weight = 1; + + weight = VECTOR(*weights)[i]; + communities.item[ffrom].weight_all += weight; + communities.item[fto].weight_all += weight; + if (ffrom == fto) { + communities.item[ffrom].weight_inside += 2 * weight; + } + } + + q = igraph_i_multilevel_community_modularity(&communities, resolution); + /* pass = 1; */ + + do { /* Pass begin */ + igraph_int_t temp_communities_no = communities.communities_no; + + pass_q = q; + changed = false; + + /* Save the current membership, it will be restored in case of worse result */ + IGRAPH_CHECK(igraph_vector_int_update(&temp_membership, communities.membership)); + + /* Apply a random inversion to the node_order permutation vector to help escape + * rare situations of an infinite loop. A full re-shuffling of node_order would + * have a measurable performance impact, hence the single inversion. + * See https://github.com/igraph/igraph/issues/2650 for details. */ + if (vcount > 1) { + igraph_int_t i1 = RNG_INTEGER(0, vcount-1); + igraph_int_t i2 = RNG_INTEGER(0, vcount-1); + igraph_int_t tmp = VECTOR(node_order)[i1]; + VECTOR(node_order)[i1] = VECTOR(node_order)[i2]; + VECTOR(node_order)[i2] = tmp; + } + + for (igraph_int_t i = 0; i < vcount; i++) { + /* Exclude vertex from its current community */ + igraph_real_t weight_all = 0; + igraph_real_t weight_inside = 0; + igraph_real_t weight_loop = 0; + igraph_real_t max_q_gain = 0; + igraph_real_t max_weight; + igraph_int_t old_id, new_id, n, ni; + + ni = VECTOR(node_order)[i]; + + igraph_i_multilevel_community_links(graph, &communities, + ni, &edges, + &weight_all, &weight_inside, + &weight_loop, &links_community, + &links_weight); + + old_id = VECTOR(*(communities.membership))[ni]; + new_id = old_id; + + /* Update old community */ + VECTOR(*communities.membership)[ni] = -1; + communities.item[old_id].size--; + if (communities.item[old_id].size == 0) { + communities.communities_no--; + } + communities.item[old_id].weight_all -= weight_all; + communities.item[old_id].weight_inside -= 2 * weight_inside + weight_loop; + + /* debug("Remove %ld all: %lf Inside: %lf\n", ni, -weight_all, -2*weight_inside + weight_loop); */ + + /* Find new community to join with the best modification gain */ + max_q_gain = 0; + max_weight = weight_inside; + n = igraph_vector_int_size(&links_community); + + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t c = VECTOR(links_community)[j]; + igraph_real_t w = VECTOR(links_weight)[j]; + + igraph_real_t q_gain = + igraph_i_multilevel_community_modularity_gain(&communities, c, ni, + weight_all, w, resolution); + /* debug("Link %ld -> %ld weight: %lf gain: %lf\n", ni, c, (double) w, (double) q_gain); */ + if (q_gain > max_q_gain) { + new_id = c; + max_q_gain = q_gain; + max_weight = w; + } + } + + /* debug("Added vertex %ld to community %ld (gain %lf).\n", ni, new_id, (double) max_q_gain); */ + + /* Add vertex to "new" community and update it */ + VECTOR(*communities.membership)[ni] = new_id; + if (communities.item[new_id].size == 0) { + communities.communities_no++; + } + communities.item[new_id].size++; + communities.item[new_id].weight_all += weight_all; + communities.item[new_id].weight_inside += 2 * max_weight + weight_loop; + + if (new_id != old_id) { + changed = true; + } + } + + q = igraph_i_multilevel_community_modularity(&communities, resolution); + + if (changed && (q > pass_q)) { + /* debug("Pass %d (changed: %d) Communities: %ld Modularity from %lf to %lf\n", + pass, changed, communities.communities_no, (double) pass_q, (double) q); */ + /* pass++; */ + } else { + /* No changes or the modularity became worse, restore last membership */ + IGRAPH_CHECK(igraph_vector_int_update(communities.membership, &temp_membership)); + communities.communities_no = temp_communities_no; + break; + } + + IGRAPH_ALLOW_INTERRUPTION(); + } while (changed && (q > pass_q)); /* Pass end */ + + if (modularity) { + *modularity = q; + } + + /* debug("Result Communities: %ld Modularity: %lf\n", + communities.communities_no, (double) q); */ + + IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL)); + + /* Shrink the nodes of the graph according to the present community structure + * and simplify the resulting graph */ + + /* TODO: check if we really need to copy temp_membership */ + IGRAPH_CHECK(igraph_vector_int_update(&temp_membership, membership)); + IGRAPH_CHECK(igraph_i_multilevel_shrink(graph, &temp_membership)); + igraph_vector_int_destroy(&temp_membership); + IGRAPH_FINALLY_CLEAN(1); + + /* Update edge weights after shrinking and simplification */ + /* Here we reuse the edges vector as we don't need the previous contents anymore */ + /* TODO: can we use igraph_simplify here? */ + IGRAPH_CHECK(igraph_i_multilevel_simplify_multiple(graph, &edges)); + + /* We reuse the links_weight vector to store the old edge weights */ + IGRAPH_CHECK(igraph_vector_update(&links_weight, weights)); + igraph_vector_null(weights); + + for (igraph_int_t i = 0; i < ecount; i++) { + VECTOR(*weights)[VECTOR(edges)[i]] += VECTOR(links_weight)[i]; + } + + igraph_free(communities.item); + igraph_vector_int_destroy(&links_community); + igraph_vector_destroy(&links_weight); + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&node_order); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_community_multilevel + * \brief Finding community structure by multi-level optimization of modularity (Louvain). + * + * This function implements a multi-level modularity optimization algorithm + * for finding community structure, sometimes known as the Louvain algorithm. + * + * + * The algorithm is based on the modularity measure and a hierarchical approach. + * Initially, each vertex is assigned to a community on its own. In every step, + * vertices are re-assigned to communities in a local, greedy way: in a random + * order, each vertex is moved to the community with which it achieves the highest + * contribution to modularity. When no vertices can be reassigned, each community + * is considered a vertex on its own, and the process starts again with the merged + * communities. The process stops when there is only a single vertex left or when + * the modularity cannot be increased any more in a step. + * + * + * The resolution parameter \c γ allows finding communities at different + * resolutions. Higher values of the resolution parameter typically result in + * more, smaller communities. Lower values typically result in fewer, larger + * communities. The original definition of modularity is retrieved when setting + * γ=1. Note that the returned modularity value is calculated using + * the indicated resolution parameter. See \ref igraph_modularity() for more details. + * + * + * The original version of this function was contributed by Tom Gregorovic. + * + * + * Reference: + * + * + * Blondel, V. D., Guillaume, J.-L., Lambiotte, R., & Lefebvre, E.: + * Fast unfolding of communities in large networks. + * Journal of Statistical Mechanics: Theory and Experiment, 10008(10), 6 (2008). + * https://doi.org/10.1088/1742-5468/2008/10/P10008 + * + * \param graph The input graph. It must be an undirected graph. + * \param weights Numeric vector containing edge weights. If \c NULL, every edge + * has equal weight. The weights are expected to be non-negative. + * \param resolution Resolution parameter. Must be greater than or equal to 0. + * Lower values favor fewer, larger communities; + * higher values favor more, smaller communities. + * Set it to 1 to use the classical definition of modularity. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community. The vector + * must be initialized and it will be resized accordingly. + * \param memberships Numeric matrix that will contain the membership vector after + * each level, if not \c NULL. It must be initialized and + * it will be resized accordingly. + * \param modularity Numeric vector that will contain the modularity score + * after each level, if not \c NULL. It must be initialized + * and it will be resized accordingly. + * \return Error code. + * + * Time complexity: in average near linear on sparse graphs. + * + * \example examples/simple/igraph_community_multilevel.c + */ + +igraph_error_t igraph_community_multilevel(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_vector_int_t *membership, + igraph_matrix_int_t *memberships, + igraph_vector_t *modularity) { + + igraph_t g; + igraph_vector_t w; + igraph_vector_int_t m; + igraph_vector_int_t level_membership; + igraph_real_t prev_q = -1, q = -1; + igraph_int_t level = 1; + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + + /* Initial sanity checks on the input parameters */ + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Multi-level community detection works for undirected graphs only.", + IGRAPH_UNIMPLEMENTED); + } + if (weights) { + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL); + } + if (ecount > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weight vector must not be negative.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + if (resolution < 0.0) { + IGRAPH_ERROR("The resolution parameter must be non-negative.", IGRAPH_EINVAL); + } + + /* Make a copy of the original graph, we will do the merges on the copy */ + IGRAPH_CHECK(igraph_copy(&g, graph)); + IGRAPH_FINALLY(igraph_destroy, &g); + + if (weights) { + IGRAPH_CHECK(igraph_vector_init_copy(&w, weights)); + IGRAPH_FINALLY(igraph_vector_destroy, &w); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&w, igraph_ecount(&g)); + igraph_vector_fill(&w, 1); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&m, vcount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&level_membership, vcount); + + if (memberships || membership) { + /* Put each vertex in its own community */ + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(level_membership)[i] = i; + } + } + if (memberships) { + /* Resize the membership matrix to have vcount columns and no rows */ + IGRAPH_CHECK(igraph_matrix_int_resize(memberships, 0, vcount)); + } + if (modularity) { + /* Clear the modularity vector */ + igraph_vector_clear(modularity); + } + + while (true) { + /* Remember the previous modularity and vertex count, do a single step */ + igraph_int_t step_vcount = igraph_vcount(&g); + + prev_q = q; + IGRAPH_CHECK(igraph_i_community_multilevel_step(&g, &w, &m, &q, resolution)); + + /* Were there any merges? If not, we have to stop the process */ + if (igraph_vcount(&g) == step_vcount || q < prev_q) { + break; + } + + if (memberships || membership) { + for (igraph_int_t i = 0; i < vcount; i++) { + /* Readjust the membership vector */ + VECTOR(level_membership)[i] = VECTOR(m)[ VECTOR(level_membership)[i] ]; + } + } + + if (modularity) { + /* If we have to return the modularity scores, add it to the modularity vector */ + IGRAPH_CHECK(igraph_vector_push_back(modularity, q)); + } + + if (memberships) { + /* If we have to return the membership vectors at each level, store the new + * membership vector */ + IGRAPH_CHECK(igraph_matrix_int_add_rows(memberships, 1)); + IGRAPH_CHECK(igraph_matrix_int_set_row(memberships, &level_membership, level - 1)); + } + + /* debug("Level: %d Communities: %ld Modularity: %f\n", level, igraph_vcount(&g), + (double) q); */ + + /* Increase the level counter */ + level++; + } + + /* It might happen that there are no merges, so every vertex is in its + own community. We still might want the modularity score for that. */ + if (modularity && igraph_vector_size(modularity) == 0) { + igraph_vector_int_t tmp; + igraph_real_t mod; + + IGRAPH_CHECK(igraph_vector_int_init_range(&tmp, 0, vcount)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp); + + IGRAPH_CHECK(igraph_modularity(graph, &tmp, weights, resolution, + /* only undirected */ false, &mod)); + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_resize(modularity, 1)); + VECTOR(*modularity)[0] = mod; + } + + /* If we need the final membership vector, copy it to the output */ + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, vcount)); + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(*membership)[i] = VECTOR(level_membership)[i]; + } + } + + /* Destroy the copy of the graph */ + igraph_destroy(&g); + + /* Destroy the temporary vectors */ + igraph_vector_int_destroy(&m); + igraph_vector_destroy(&w); + igraph_vector_int_destroy(&level_membership); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/modularity.c b/src/community/modularity.c new file mode 100644 index 0000000..3541a3e --- /dev/null +++ b/src/community/modularity.c @@ -0,0 +1,393 @@ +/* + igraph library. + Copyright (C) 2007-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_interface.h" +#include "igraph_structural.h" + +#include "community/community_internal.h" + +/** + * \function igraph_modularity + * \brief Calculates the modularity of a graph with respect to some clusters or vertex types. + * + * The modularity of a graph with respect to some clustering of the vertices + * (or assignment of vertex types) + * measures how strongly separated the different clusters are from each + * other compared to a random null model. It is defined as + * + * + * Q = 1/(2m) sum_ij (A_ij - γ k_i k_j / (2m)) δ(c_i,c_j), + * + * + * where \c m is the number of edges, A_ij is the adjacency matrix, + * \c k_i is the degree of vertex \c i, \c c_i is the cluster that vertex \c i belongs to + * (or its vertex type), δ(i,j)=1 if i=j and 0 otherwise, + * and the sum goes over all \c i, \c j pairs of vertices. Note that in this formula, + * the diagonal of the adjacency matrix contains twice the number of self-loops. + * + * + * The resolution parameter \c γ allows weighting the random null model, which + * might be useful when finding partitions with a high modularity. Maximizing modularity + * with higher values of the resolution parameter typically results in more, smaller clusters + * when finding partitions with a high modularity. Lower values typically results in + * fewer, larger clusters. The original definition of modularity is retrieved + * when setting γ = 1. + * + * + * Modularity can also be calculated on directed graphs. This only requires a relatively + * modest change, + * + * + * Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i,c_j), + * + * + * where \c k^out_i is the out-degree of node \c i and \c k^in_j is the in-degree of node \c j. + * + * + * Modularity on weighted graphs is also meaningful. When taking + * edge weights into account, \c A_ij equals the weight of the corresponding edge + * (or 0 if there is no edge), \c k_i is the strength (i.e. the weighted degree) of + * vertex \c i, with similar counterparts for a directed graph, and \c m is the total + * weight of all edges. + * + * + * Note that the modularity is not well-defined for graphs with no edges. + * igraph returns \c NaN for graphs with no edges; see + * https://github.com/igraph/igraph/issues/1539 for + * a detailed discussion. + * + * + * For the original definition of modularity, see Newman, M. E. J., and Girvan, M. + * (2004). Finding and evaluating community structure in networks. + * Physical Review E 69, 026113. https://doi.org/10.1103/PhysRevE.69.026113 + * + * + * For the directed definition of modularity, see Leicht, E. A., and Newman, M. E. + * J. (2008). Community Structure in Directed Networks. Physical Review Letters 100, + * 118703. https://doi.org/10.1103/PhysRevLett.100.118703 + * + * + * For the introduction of the resolution parameter \c γ, see Reichardt, J., and + * Bornholdt, S. (2006). Statistical mechanics of community detection. Physical + * Review E 74, 016110. https://doi.org/10.1103/PhysRevE.74.016110 + * + * \param graph The input graph. + * \param membership Numeric vector of integer values which gives the type of each + * vertex, i.e. the cluster to which it belongs. + * It does not have to be consecutive, i.e. empty communities + * are allowed. For better performance, ensure that community + * indices are nonnegative and smaller than the vertex count. + * This can be ensured using \ref igraph_reindex_membership(). + * \param weights Weight vector or \c NULL if no weights are specified. + * \param resolution The resolution parameter \c γ. Must not be negative. + * Set it to 1 to use the classical definition of modularity. + * \param directed Whether to use the directed or undirected version of modularity. + * Ignored for undirected graphs. + * \param modularity Pointer to a real number, the result will be + * stored here. + * \return Error code. + * + * \sa \ref igraph_modularity_matrix() + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges, assuming that community indices are nonnegative and smaller + * than the vertex count. Otherwise, O(|V| log |V| + |E|). + */ +igraph_error_t igraph_modularity(const igraph_t *graph, + const igraph_vector_int_t *membership, + const igraph_vector_t *weights, + const igraph_real_t resolution, + const igraph_bool_t directed, + igraph_real_t *modularity) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_vector_int_t *p_membership; + igraph_vector_int_t i_membership; + igraph_bool_t using_i_membership = false; + igraph_vector_t k_out, k_in; + igraph_int_t min_cluster_id, max_cluster_id, no_of_partitions; + igraph_real_t e; /* count/fraction of edges/weights within partitions */ + igraph_real_t m; /* edge count / weight sum */ + igraph_int_t c1, c2; + /* Only consider the graph as directed if it actually is directed */ + igraph_bool_t use_directed = directed && igraph_is_directed(graph); + igraph_real_t directed_multiplier = (use_directed ? 1 : 2); + + if (igraph_vector_int_size(membership) != vcount) { + IGRAPH_ERROR("Membership vector size differs from number of vertices.", + IGRAPH_EINVAL); + } + if (resolution < 0.0) { + IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL); + } + + if (ecount == 0) { + /* Special case: the modularity of graphs with no edges is not + * well-defined */ + *modularity = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + /* At this point, the 'membership' vector does not have length zero, + thus it is safe to call igraph_vector_int_minmax(). */ + + /* If community indices are outside of the standard range, automatically + * reindex them. */ + igraph_vector_int_minmax(membership, &min_cluster_id, &max_cluster_id); + if (min_cluster_id < 0 || max_cluster_id >= vcount) { + IGRAPH_CHECK(igraph_vector_int_init_copy(&i_membership, membership)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &i_membership); + IGRAPH_CHECK(igraph_i_reindex_membership_large(&i_membership, NULL, &no_of_partitions)); + p_membership = &i_membership; + using_i_membership = true; + } else { + no_of_partitions = max_cluster_id + 1; + p_membership = membership; + } + + IGRAPH_VECTOR_INIT_FINALLY(&k_out, no_of_partitions); + IGRAPH_VECTOR_INIT_FINALLY(&k_in, no_of_partitions); + + e = 0.0; + if (weights) { + if (igraph_vector_size(weights) != ecount) + IGRAPH_ERROR("Weight vector size differs from number of edges.", + IGRAPH_EINVAL); + m = 0.0; + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_real_t w = VECTOR(*weights)[i]; + if (w < 0) { + IGRAPH_ERROR("Negative weight in weight vector.", IGRAPH_EINVAL); + } + c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ]; + c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ]; + if (c1 == c2) { + e += directed_multiplier * w; + } + VECTOR(k_out)[c1] += w; + VECTOR(k_in)[c2] += w; + m += w; + } + } else { + m = ecount; + for (igraph_int_t i = 0; i < ecount; i++) { + c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ]; + c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ]; + if (c1 == c2) { + e += directed_multiplier; + } + VECTOR(k_out)[c1] += 1; + VECTOR(k_in)[c2] += 1; + } + } + + if (!use_directed) { + /* Graph is undirected, simply add vectors */ + igraph_vector_add(&k_out, &k_in); + igraph_vector_update(&k_in, &k_out); + } + + /* Divide all vectors by total weight. */ + igraph_vector_scale(&k_out, 1.0/( directed_multiplier * m ) ); + igraph_vector_scale(&k_in, 1.0/( directed_multiplier * m ) ); + e /= directed_multiplier * m; + + if (m > 0) { + *modularity = e; + for (igraph_int_t i = 0; i < no_of_partitions; i++) { + *modularity -= resolution * VECTOR(k_out)[i] * VECTOR(k_in)[i]; + } + } else { + *modularity = IGRAPH_NAN; + } + + igraph_vector_destroy(&k_out); + igraph_vector_destroy(&k_in); + IGRAPH_FINALLY_CLEAN(2); + + if (using_i_membership) { + igraph_vector_int_destroy(&i_membership); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_modularity_matrix_get_adjacency( + const igraph_t *graph, igraph_matrix_t *res, + const igraph_vector_t *weights, igraph_bool_t directed) { + + /* Specifically used to handle weights and/or ignore direction */ + igraph_eit_t edgeit; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t from, to; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + if (weights) { + for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) { + igraph_int_t edge = IGRAPH_EIT_GET(edgeit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + MATRIX(*res, from, to) += VECTOR(*weights)[edge]; + if (!directed) { + MATRIX(*res, to, from) += VECTOR(*weights)[edge]; + } + } + } else { + for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) { + igraph_int_t edge = IGRAPH_EIT_GET(edgeit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + MATRIX(*res, from, to) += 1; + if (!directed) { + MATRIX(*res, to, from) += 1; + } + } + } + + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_modularity_matrix + * \brief Calculates the modularity matrix. + * + * This function returns the modularity matrix, which is defined as + * + * + * B_ij = A_ij - γ k_i k_j / (2m) + * + * + * for undirected graphs, where \c A_ij is the adjacency matrix, \c γ is the + * resolution parameter, \c k_i is the degree of vertex \c i, and \c m is the + * number of edges in the graph. When there are no edges, or the weights add up + * to zero, the result is undefined. + * + * + * For directed graphs the modularity matrix is changed to + * + * + * B_ij = A_ij - γ k^out_i k^in_j / m + * + * + * where k^out_i is the out-degree of node \c i and k^in_j is the + * in-degree of node \c j. + * + * + * Note that self-loops in undirected graphs are multiplied by 2 in this + * implementation. If weights are specified, the weighted counterparts of the adjacency + * matrix and degrees are used. + * + * \param graph The input graph. + * \param weights Edge weights, pointer to a vector. If this is a null pointer + * then every edge is assumed to have a weight of 1. + * \param resolution The resolution parameter \c γ. Must not be negative. + * Default is 1. Lower values favor fewer, larger communities; + * higher values favor more, smaller communities. + * \param modmat Pointer to an initialized matrix in which the modularity + * matrix is stored. + * \param directed For directed graphs: if the edges should be treated as + * undirected. For undirected graphs this is ignored. + * \return Error code. + * + * \sa \ref igraph_modularity() + */ +igraph_error_t igraph_modularity_matrix(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_matrix_t *modmat, + igraph_bool_t directed) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_real_t sw = weights ? igraph_vector_sum(weights) : no_of_edges; + igraph_vector_t deg, in_deg, out_deg; + igraph_real_t scaling_factor; + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (resolution < 0.0) { + IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL); + } + + if (!igraph_is_directed(graph)) { + directed = false; + } + IGRAPH_CHECK(igraph_i_modularity_matrix_get_adjacency(graph, modmat, weights, directed)); + + /* Performance notes: + * - Iterating in column-major order makes a large difference. + * - Applying the scaling_factor to in_deg (or out_deg) first to reduce the + * number of multiplications does not make an appreciable performance + * difference. However, doing this in the undirected case causes the result + * matrix to sometimes not be strictly symmetric due to the non-associativity + * of floating point multiplication. + */ + + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&in_deg, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&out_deg, no_of_nodes); + + IGRAPH_CHECK(igraph_strength(graph, &in_deg, igraph_vss_all(), IGRAPH_IN, + IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &out_deg, igraph_vss_all(), IGRAPH_OUT, + IGRAPH_LOOPS, weights)); + + scaling_factor = resolution / sw; + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + MATRIX(*modmat, i, j) -= VECTOR(out_deg)[i] * VECTOR(in_deg)[j] * scaling_factor; + } + } + igraph_vector_destroy(&in_deg); + igraph_vector_destroy(&out_deg); + IGRAPH_FINALLY_CLEAN(2); + } else { + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + + IGRAPH_CHECK(igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + scaling_factor = resolution / 2.0 / sw; + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + MATRIX(*modmat, i, j) -= VECTOR(deg)[i] * VECTOR(deg)[j] * scaling_factor; + } + } + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/community/optimal_modularity.c b/src/community/optimal_modularity.c new file mode 100644 index 0000000..23aebf4 --- /dev/null +++ b/src/community/optimal_modularity.c @@ -0,0 +1,316 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" + +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_structural.h" + +#include "core/interruption.h" +#include "internal/glpk_support.h" +#include "math/safe_intop.h" + +#include + +/** + * \function igraph_community_optimal_modularity + * \brief Calculate the community structure with the highest modularity value. + * + * This function calculates the optimal community structure for a graph, in + * terms of maximal modularity score. Both undirected and directed graphs + * are supported. + * + * + * The calculation is done by transforming the modularity maximization + * into an integer programming problem, and then calling the GLPK + * library to solve that. Please see Ulrik Brandes et al.: On + * Modularity Clustering, IEEE Transactions on Knowledge and Data + * Engineering 20(2):172-188, 2008 + * https://doi.org/10.1109/TKDE.2007.190689. + * + * + * Note that exact modularity optimization is an NP-complete problem, and + * all known algorithms for it have exponential time complexity. This + * means that you probably don't want to run this function on larger + * graphs. Graphs with up to fifty vertices should be fine, graphs + * with a couple of hundred vertices might be possible. + * + * \param graph The input graph. It may be undirected or directed. + * \param weights Vector giving the weights of the edges. If it is + * \c NULL then each edge is supposed to have the same weight. + * \param resolution Resolution parameter. Must be greater than or equal to 0. + * Lower values favor fewer, larger communities; higher values favor more, + * smaller communities. Set it to 1 to use the classical definition of + * modularity. + * \param modularity Pointer to a real number, or a null pointer. + * If it is not a null pointer, then a optimal modularity value + * is returned here. + * \param membership Pointer to a vector, or a null pointer. If not a + * null pointer, then the membership vector of the optimal + * community structure is stored here. + * \return Error code. + * When GLPK is not available, \c IGRAPH_UNIMPLEMENTED is returned. + * + * \sa \ref igraph_modularity(), \ref igraph_community_fastgreedy() + * for an algorithm that finds a local optimum in a greedy way. + * + * Time complexity: exponential in the number of vertices. + * + * \example examples/simple/igraph_community_optimal_modularity.c + */ + +igraph_error_t igraph_community_optimal_modularity(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_real_t resolution, + igraph_real_t *modularity, + igraph_vector_int_t *membership) { + +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); +#else + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t no_of_variables; + igraph_int_t i, j, k, l; + int st; + int idx[] = { 0, 0, 0, 0 }; + double coef[] = { 0.0, 1.0, 1.0, -2.0 }; + igraph_real_t total_weight; + igraph_vector_t indegree; + igraph_vector_t outdegree; + + glp_prob *ip; + glp_iocp parm; + + if (resolution < 0.0) { + IGRAPH_ERRORF("Resolution must be positive, got %g.", IGRAPH_EINVAL, resolution); + } + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + /* Must not call vector_min on empty vector */ + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Negative weights are not allowed in weight vector.", IGRAPH_EINVAL); + } + if (isnan(minweight)) { + IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL); + } + } + } + + /* Avoid problems with the null graph */ + if (no_of_nodes < 2) { + /* Cater for the case when membership was not given, but modularity was requested. */ + igraph_vector_int_t imembership, *pmembership; + if (membership) { + pmembership = membership; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&imembership, no_of_nodes); + pmembership = &imembership; + } + + IGRAPH_CHECK(igraph_vector_int_resize(pmembership, no_of_nodes)); + igraph_vector_int_null(pmembership); + + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, pmembership, NULL, resolution, igraph_is_directed(graph), modularity)); + } + + if (! membership) { + igraph_vector_int_destroy(&imembership); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; + } + + /* no_of_variables = no_of_nodes * (no_of_nodes + 1) / 2; + * + * Here we do not use IGRAPH_SAFE_N_CHOOSE_2 because later we rely on + * (no_of_nodes + 1) * no_of_nodes not overflowing even before the + * division by 2. See IDX() macro. + */ + IGRAPH_SAFE_MULT(no_of_nodes + 1, no_of_nodes, &no_of_variables); + no_of_variables /= 2; + if (no_of_variables > INT_MAX) { + IGRAPH_ERROR("Problem too large for GLPK.", IGRAPH_EOVERFLOW); + } + + if (weights) { + total_weight = igraph_vector_sum(weights); + } else { + total_weight = no_of_edges; + } + if (!directed) { + total_weight *= 2; + } + + /* Special case */ + if (no_of_edges == 0 || total_weight == 0) { + if (modularity) { + *modularity = IGRAPH_NAN; + } + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_null(membership); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS, weights)); + + IGRAPH_GLPK_SETUP(); + + ip = glp_create_prob(); + IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); + + glp_set_obj_dir(ip, GLP_MAX); + st = glp_add_cols(ip, (int) no_of_variables); + + /* variables are binary */ + for (i = 0; i < no_of_variables; i++) { + glp_set_col_kind(ip, (int)(st + i), GLP_BV); + } + +#define IDX(a,b) (int)((b)*((b)+1)/2+(a)) + + /* reflexivity */ + for (i = 0; i < no_of_nodes; i++) { + glp_set_col_bnds(ip, (st + IDX(i, i)), GLP_FX, 1.0, 1.0); + } + + /* transitivity */ + for (i = 0; i < no_of_nodes; i++) { + for (j = i + 1; j < no_of_nodes; j++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (k = j + 1; k < no_of_nodes; k++) { + int newrow = glp_add_rows(ip, 3); + + glp_set_row_bnds(ip, newrow, GLP_UP, 0.0, 1.0); + idx[1] = (st + IDX(i, j)); idx[2] = (st + IDX(j, k)); + idx[3] = (st + IDX(i, k)); + glp_set_mat_row(ip, newrow, 3, idx, coef); + + glp_set_row_bnds(ip, newrow + 1, GLP_UP, 0.0, 1.0); + idx[1] = st + IDX(i, j); idx[2] = st + IDX(i, k); idx[3] = st + IDX(j, k); + glp_set_mat_row(ip, newrow + 1, 3, idx, coef); + + glp_set_row_bnds(ip, newrow + 2, GLP_UP, 0.0, 1.0); + idx[1] = st + IDX(i, k); idx[2] = st + IDX(j, k); idx[3] = st + IDX(i, j); + glp_set_mat_row(ip, newrow + 2, 3, idx, coef); + + } + } + } + + /* objective function */ + { + igraph_real_t c; + + /* first part: -strength(i)*strength(j)/total_weight for every node pair */ + for (i = 0; i < no_of_nodes; i++) { + for (j = i + 1; j < no_of_nodes; j++) { + c = -VECTOR(indegree)[i] * VECTOR(outdegree)[j] / total_weight \ + -VECTOR(outdegree)[i] * VECTOR(indegree)[j] / total_weight; + c *= resolution; + glp_set_obj_coef(ip, st + IDX(i, j), c); + } + /* special case for (i,i) */ + c = -VECTOR(indegree)[i] * VECTOR(outdegree)[i] / total_weight; + c *= resolution; + glp_set_obj_coef(ip, st + IDX(i, i), c); + } + + /* second part: add the weighted adjacency matrix to the coefficient matrix */ + for (k = 0; k < no_of_edges; k++) { + i = IGRAPH_FROM(graph, k); + j = IGRAPH_TO(graph, k); + if (i > j) { + l = i; i = j; j = l; + } + c = weights ? VECTOR(*weights)[k] : 1.0; + if (!directed || i == j) { + c *= 2.0; + } + glp_set_obj_coef(ip, st + IDX(i, j), c + glp_get_obj_coef(ip, st + IDX(i, j))); + } + } + + /* solve it */ + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_DTH; + parm.bt_tech = GLP_BT_BLB; + parm.presolve = GLP_ON; + parm.binarize = GLP_ON; + parm.cb_func = igraph_i_glpk_interruption_hook; + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), "Modularity optimization failed"); + + /* store the results */ + if (modularity) { + *modularity = glp_mip_obj_val(ip) / total_weight; + } + + if (membership) { + igraph_int_t comm = 0; /* id of the last community that was found */ + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (j = 0; j < i; j++) { + int val = (int) glp_mip_col_val(ip, st + IDX(j, i)); + if (val == 1) { + VECTOR(*membership)[i] = VECTOR(*membership)[j]; + break; + } + } + if (j == i) { /* new community */ + VECTOR(*membership)[i] = comm++; + } + } + } + +#undef IDX + + igraph_vector_destroy(&indegree); + igraph_vector_destroy(&outdegree); + glp_delete_prob(ip); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; + +#endif + +} diff --git a/src/community/spinglass/NetDataTypes.cpp b/src/community/spinglass/NetDataTypes.cpp new file mode 100644 index 0000000..5f83300 --- /dev/null +++ b/src/community/spinglass/NetDataTypes.cpp @@ -0,0 +1,101 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetDataTypes.cpp - description + ------------------- + begin : Mon Oct 6 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "NetDataTypes.h" + +int NNode::Connect_To(NNode* neighbour, double weight_) { + NLink *link; + //sollen doppelte Links erlaubt sein?? NEIN + if (!neighbour) { + return 0; + } + if (!(neighbours.Is_In_List(neighbour)) && (neighbour != this)) { + neighbours.Push(neighbour); // nachbar hier eintragen + neighbour->neighbours.Push(this); // diesen knoten beim nachbarn eintragen + + link = new NLink(this, neighbour, weight_); //link erzeugen + global_link_list->Push(link); // in globaler liste eintragen + n_links.Push(link); // bei diesem Knoten eintragen + neighbour->n_links.Push(link); // beim nachbarn eintragen + + return 1; + } + return 0; +} + +NLink *NNode::Get_LinkToNeighbour(const NNode* neighbour) { + DLList_Iter iter; + NLink *l_cur, *link = nullptr; + bool found = false; + // finde einen bestimmten Link aus der Liste der links eines Knotens + l_cur = iter.First(&n_links); + while (!iter.End() && !found) { + if (((l_cur->Get_Start() == this) && (l_cur->Get_End() == neighbour)) || ((l_cur->Get_End() == this) && (l_cur->Get_Start() == neighbour))) { + found = true; + link = l_cur; + } + l_cur = iter.Next(); + } + if (found) { + return link; + } else { + return nullptr; + } +} + +igraph_int_t NNode::Disconnect_From(NNode* neighbour) { + //sollen doppelte Links erlaubt sein?? s.o. + neighbours.fDelete(neighbour); + n_links.fDelete(Get_LinkToNeighbour(neighbour)); + neighbour->n_links.fDelete(neighbour->Get_LinkToNeighbour(this)); + neighbour->neighbours.fDelete(this); + return 1; +} + +igraph_int_t NNode::Disconnect_From_All() { + igraph_int_t number_of_neighbours = 0; + while (neighbours.Size()) { + Disconnect_From(neighbours.Pop()); + number_of_neighbours++; + } + return number_of_neighbours ; +} diff --git a/src/community/spinglass/NetDataTypes.h b/src/community/spinglass/NetDataTypes.h new file mode 100644 index 0000000..aa9432f --- /dev/null +++ b/src/community/spinglass/NetDataTypes.h @@ -0,0 +1,567 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetDataTypes.h - description + ------------------- + begin : Mon Oct 6 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef NETDATATYPES_H +#define NETDATATYPES_H + +#include "igraph_types.h" + +#include +#include + +// In igraph, we set node names to be a string representation of the one-based +// vertex ID. This takes at most 20 characters. Add one for a potential sign +// (should not happen) and one more for the null terminator. +#define SPINGLASS_MAX_NAME_LEN 22 + +//########################################################################################### + +struct HUGE_INDEX { + unsigned int field_index; + igraph_int_t in_field_index; +}; + +template +class HugeArray { + igraph_int_t size = 2; + unsigned int highest_field_index = 0; + const igraph_int_t max_bit_left = 1UL << 31; //wir setzen das 31. Bit auf 1 + igraph_int_t max_index = 0; + DATA *data; + DATA *fields[32]; +public: + HugeArray(); + HugeArray(const HugeArray &) = delete; + HugeArray & operator = (const HugeArray &) = delete; + ~HugeArray(); + HUGE_INDEX get_huge_index(igraph_int_t) const; + DATA &Set(igraph_int_t index); + DATA Get(igraph_int_t index) { return Set(index); } + DATA &operator[](igraph_int_t index) { return Set(index); } + igraph_int_t Size() const { return max_index; } +} ; + +//############################################################################################### +template class DLList; +template class DL_Indexed_List; +template using ClusterList= DLList; +template class DLList_Iter; + +template +class DLItem { + friend class DLList ; + friend class DL_Indexed_List; + friend class DLList_Iter; + + L_DATA item; + igraph_int_t index; + DLItem *previous; + DLItem *next; + DLItem(L_DATA i, igraph_int_t ind); + DLItem(L_DATA i, igraph_int_t ind, DLItem *p, DLItem *n); +public: + void del() { + delete item; + } +}; + +template +class DLList { + friend class DLList_Iter; +protected: + DLItem *head; + DLItem *tail; + igraph_int_t number_of_items = 0; + virtual DLItem *pInsert(L_DATA, DLItem*); + virtual L_DATA pDelete(DLItem*); +public: + DLList(); + DLList(const DLList &) = delete; + DLList & operator = (const DLList &) = delete; + virtual ~DLList(); + igraph_int_t Size() const { + return number_of_items; + } + int fDelete(L_DATA); + virtual L_DATA Push(L_DATA); + virtual L_DATA Pop(); + virtual L_DATA Get(igraph_int_t); + igraph_int_t Is_In_List(L_DATA); + void delete_items(); +}; + +template +class DL_Indexed_List : public DLList { + DLItem *pInsert(L_DATA, DLItem*) final; + L_DATA pDelete(DLItem*) final; + HugeArray*> array; + igraph_int_t last_index = 0; + +public: + DL_Indexed_List() = default; + L_DATA Push(L_DATA) final; + L_DATA Pop() final; + L_DATA Get(igraph_int_t) final; +}; + +//##################################################################################################### + +template class DLList_Iter { + const DLList *list = nullptr; + const DLItem *current = nullptr; + bool end_reached = true; + +public: + L_DATA Next(); + L_DATA Previous(); + L_DATA First(const DLList *l); + L_DATA Last(const DLList *l); + bool End() const { + return end_reached; + } + bool Swap(DLList_Iter); //swapt die beiden Elemente, wenn sie in der gleichen Liste stehen!! +}; + +//##################################################################################################### + +class NLink; + +class NNode { + igraph_int_t index; + igraph_int_t cluster_index; + igraph_int_t marker = 0; + double weight = 0.0; + + DLList neighbours; //list with pointers to neighbours + DLList n_links; + DLList *global_link_list; + char name[SPINGLASS_MAX_NAME_LEN]; +public : + NNode(igraph_int_t ind, igraph_int_t c_ind, DLList *ll, const char *n) : + index(ind), cluster_index(c_ind), global_link_list(ll) + { + strcpy(name, n); + } + NNode(const NNode &) = delete; + NNode &operator=(const NNode &) = delete; + ~NNode() { Disconnect_From_All(); } + + igraph_int_t Get_Index() const { + return index; + } + igraph_int_t Get_ClusterIndex() const { + return cluster_index; + } + igraph_int_t Get_Marker() const { + return marker; + } + void Set_Marker(igraph_int_t m) { + marker = m; + } + void Set_ClusterIndex(igraph_int_t ci) { + cluster_index = ci; + } + igraph_int_t Get_Degree() const { + return (neighbours.Size()); + } + const char *Get_Name() { + return name; + } + void Set_Name(const char *n) { + strcpy(name, n); + } + double Get_Weight() const { + return weight; + } + void Set_Weight(double w) { + weight = w; + } + int Connect_To(NNode*, double); + const DLList *Get_Neighbours() const { + return &neighbours; + } + const DLList *Get_Links() const { + return &n_links; + } + igraph_int_t Disconnect_From(NNode*); + igraph_int_t Disconnect_From_All(); + NLink *Get_LinkToNeighbour(const NNode *neighbour); +}; + +//##################################################################################################### + +class NLink { + + NNode *start; + NNode *end; + double weight; + +public : + NLink(NNode *s, NNode *e, double w) : start(s), end(e), weight(w) { } + NLink(const NLink &) = delete; + NLink & operator = (const NLink &) = delete; + ~NLink() { start->Disconnect_From(end); } + + NNode *Get_Start() { return start; } + NNode *Get_End() { return end; } + const NNode *Get_Start() const { return start; } + const NNode *Get_End() const { return end; } + + double Get_Weight() const { return weight; } +}; + +//##################################################################################################### + +struct network { + DL_Indexed_List node_list; + DL_Indexed_List link_list; + DL_Indexed_List*> cluster_list; + double sum_weights; + + network() = default; + network (const network &) = delete; + network & operator = (const network &) = delete; + + ~network() { + ClusterList *cl_cur; + + while (link_list.Size()) { + delete link_list.Pop(); + } + while (node_list.Size()) { + delete node_list.Pop(); + } + while (cluster_list.Size()) { + cl_cur = cluster_list.Pop(); + while (cl_cur->Size()) { + cl_cur->Pop(); + } + delete cl_cur; + } + } +}; + +template +HugeArray::HugeArray() { + data = new DATA[2]; //ein extra Platz fuer das Nullelement + data[0] = 0; + data[1] = 0; + for (auto & field : fields) { + field = nullptr; + } + fields[highest_field_index] = data; +} + +template HugeArray::~HugeArray() { + for (unsigned int i = 0; i <= highest_field_index; i++) { + data = fields[i]; + delete [] data; + } +} + +template +HUGE_INDEX HugeArray::get_huge_index(igraph_int_t index) const { + HUGE_INDEX h_index; + unsigned int shift_index = 0; + igraph_int_t help_index; + help_index = index; + if (index < 2) { + h_index.field_index = 0; + h_index.in_field_index = index; + return h_index; + } + // wie oft muessen wir help_index nach links shiften, damit das 31. Bit gesetzt ist?? + while (!(max_bit_left & help_index)) { + help_index <<= 1; + shift_index++; + } + h_index.field_index = 31 - shift_index; // das hoechste besetzte Bit im Index + help_index = igraph_int_t(1) << h_index.field_index; // in help_index wird das hoechste besetzte Bit von Index gesetzt + h_index.in_field_index = (index ^ help_index); // index XOR help_index, womit alle bits unter dem hoechsten erhalten bleiben + return h_index; +} + +template +DATA &HugeArray::Set(igraph_int_t index) { + igraph_int_t data_size; + while (size < index + 1) { + highest_field_index++; + data_size = igraph_int_t(1) << highest_field_index; + data = new DATA[data_size]; + for (igraph_int_t i = 0; i < data_size; i++) { + data[i] = 0; + } + size = size + data_size; //overflow noch abfangen + fields[highest_field_index] = data; + } + HUGE_INDEX h_index = get_huge_index(index); + data = fields[h_index.field_index]; + if (max_index < index) { + max_index = index; + } + return data[h_index.in_field_index]; +} + + +//############################################################################### +template +DLItem::DLItem(L_DATA i, igraph_int_t ind) : + item(i), index(ind), previous(nullptr), next(nullptr) { } + +template +DLItem::DLItem(L_DATA i, igraph_int_t ind, DLItem *p, DLItem *n) : + item(i), index(ind), previous(p), next(n) { } + +//###################################################################################################################### +template +DLList::DLList() { + head = new DLItem(NULL, 0); //fuer head und Tail gibt es das gleiche Array-Element!! Vorsicht!! + tail = new DLItem(NULL, 0); + + head->next = tail; + tail->previous = head; +} + +template +DLList::~DLList() { + DLItem *cur = head, *next; + while (cur) { + next = cur->next; + delete cur; + cur = next; + } + number_of_items = 0; +} + +template +void DLList::delete_items() { + DLItem *cur, *next; + cur = this->head; + while (cur) { + next = cur->next; + cur->del(); + cur = next; + } + this->number_of_items = 0; +} + +//privates Insert +template +DLItem *DLList::pInsert(L_DATA data, DLItem *pos) { + auto *i = new DLItem(data, number_of_items + 1, pos->previous, pos); + pos->previous->next = i; + pos->previous = i; + number_of_items++; + return i; +} +//privates delete +template +L_DATA DLList::pDelete(DLItem *i) { + assert(number_of_items > 0); + L_DATA data = i->item; + i->previous->next = i->next; + i->next->previous = i->previous; + delete i; + number_of_items--; + return data; +} + +//oeffentliche Delete +template +int DLList::fDelete(L_DATA data) { + if ((number_of_items == 0) || (!data)) { + return 0; + } + DLItem *cur; + cur = head->next; + while ((cur != tail) && (cur->item != data)) { + cur = cur->next; + } + if (cur != tail) { + return (pDelete(cur) != 0); + } + return 0; +} + +template +L_DATA DLList::Push(L_DATA data) { + DLItem *tmp = pInsert(data, tail); + return tmp->item; +} + +template +L_DATA DLList::Pop() { + return pDelete(tail->previous); +} + + +template +L_DATA DLList::Get(igraph_int_t pos) { + if ((pos < 1) || (pos > (number_of_items + 1))) { + return 0; + } + DLItem *cur = head; + while (pos--) { + cur = cur->next; + } + return (cur->item); +} + +//gibt Index des gesuchte Listenelement zurueck, besser waere eigentlich zeiger +template +igraph_int_t DLList::Is_In_List(L_DATA data) { + DLItem *cur = head, *next; + igraph_int_t pos = 0; + while (cur) { + next = cur->next; + if (cur->item == data) { + return pos ; + } + cur = next; + pos++; + } + return 0; +} + +//###################################################################################################################### + +//privates Insert +template +DLItem *DL_Indexed_List::pInsert(L_DATA data, DLItem *pos) { + auto *i = new DLItem(data, last_index, pos->previous, pos); + pos->previous->next = i; + pos->previous = i; + this->number_of_items++; + array[last_index] = i; + last_index++; + return i; +} +//privates delete +template +L_DATA DL_Indexed_List::pDelete(DLItem *i) { + assert(this->number_of_items > 0); + L_DATA data = i->item; + i->previous->next = i->next; + i->next->previous = i->previous; + array[i->index] = 0; + last_index = i->index; + delete i; + this->number_of_items--; + return data; +} +template +L_DATA DL_Indexed_List::Push(L_DATA data) { + DLItem *tmp; + tmp = pInsert(data, this->tail); + return tmp->item; +} + +template +L_DATA DL_Indexed_List::Pop() { + return pDelete(this->tail->previous); +} + +template +L_DATA DL_Indexed_List::Get(igraph_int_t pos) { + if (pos > this->number_of_items - 1) { + return 0; + } + return array[pos]->item; +} + +//##################################################################################### + +template +L_DATA DLList_Iter::Next() { + current = current->next; + if (current == (list->tail)) { + end_reached = true; + } + return (current->item); +} + +template +L_DATA DLList_Iter::Previous() { + current = current->previous; + if (current == (list->head)) { + end_reached = true; + } + return (current->item); +} + +template +L_DATA DLList_Iter::First(const DLList *l) { + list = l; + current = list->head->next; + if (current == (list->tail)) { + end_reached = true; + } else { + end_reached = false; + } + return (current->item); +} + +template +L_DATA DLList_Iter::Last(const DLList *l) { + list = l; + current = list->tail->previous; + if (current == (list->head)) { + end_reached = true; // falls die List leer ist + } else { + end_reached = false; + } + return (current->item); +} + +template +bool DLList_Iter::Swap(DLList_Iter b) { + L_DATA h; + if (list != b.list) { + return false; //elemeten muessen aus der gleichen List stammen + } + if (end_reached || b.end_reached) { + return false; + } + h = current->item; current->item = b.current->item; b.current->item = h; + return true; +} + +#endif diff --git a/src/community/spinglass/NetRoutines.cpp b/src/community/spinglass/NetRoutines.cpp new file mode 100644 index 0000000..d25b996 --- /dev/null +++ b/src/community/spinglass/NetRoutines.cpp @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetRoutines.cpp - description + ------------------- + begin : Tue Oct 28 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "NetRoutines.h" +#include "NetDataTypes.h" + +#include "igraph_types.h" +#include "igraph_interface.h" + +igraph_error_t igraph_i_read_network_spinglass( + const igraph_t *graph, const igraph_vector_t *weights, + network *net, igraph_bool_t use_weights) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + double sum_weight; + + for (igraph_int_t vid = 0; vid < no_of_nodes; vid++) { + char name[SPINGLASS_MAX_NAME_LEN]; + snprintf(name, sizeof(name) / sizeof(name[0]), "%" IGRAPH_PRId "", vid+1); + net->node_list.Push(new NNode(vid, 0, &net->link_list, name)); + } + + sum_weight = 0.0; + for (igraph_int_t eid = 0; eid < no_of_edges; eid++) { + igraph_int_t v1 = IGRAPH_FROM(graph, eid); + igraph_int_t v2 = IGRAPH_TO(graph, eid); + igraph_real_t w = use_weights ? VECTOR(*weights)[eid] : 1.0; + + NNode *node1 = net->node_list.Get(v1); + NNode *node2 = net->node_list.Get(v2); + + node1->Connect_To(node2, w); + + sum_weight += w; + } + + net->sum_weights = sum_weight; + + return IGRAPH_SUCCESS; +} diff --git a/src/community/spinglass/NetRoutines.h b/src/community/spinglass/NetRoutines.h new file mode 100644 index 0000000..d36f760 --- /dev/null +++ b/src/community/spinglass/NetRoutines.h @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetRoutines.h - description + ------------------- + begin : Tue Oct 28 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef NETROUTINES_H +#define NETROUTINES_H + +#include "NetDataTypes.h" +#include "igraph_types.h" +#include "igraph_datatype.h" + +igraph_error_t igraph_i_read_network_spinglass( + const igraph_t *graph, const igraph_vector_t *weights, + network *net, igraph_bool_t use_weights); + +#endif diff --git a/src/community/spinglass/clustertool.cpp b/src/community/spinglass/clustertool.cpp new file mode 100644 index 0000000..825219a --- /dev/null +++ b/src/community/spinglass/clustertool.cpp @@ -0,0 +1,632 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Joerg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + main.cpp - description + ------------------- + begin : Tue Jul 13 11:26:47 CEST 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "NetDataTypes.h" +#include "NetRoutines.h" +#include "pottsmodel_2.h" + +#include "igraph_community.h" +#include "igraph_components.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "core/exceptions.h" + +static igraph_error_t igraph_i_community_spinglass_orig( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma); + +static igraph_error_t igraph_i_community_spinglass_negative( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + igraph_real_t gamma_minus); + +/** + * \function igraph_community_spinglass + * \brief Community detection based on statistical mechanics. + * + * This function implements the community structure detection + * algorithm proposed by Joerg Reichardt and Stefan Bornholdt. + * The algorithm is described in their paper: Statistical Mechanics of + * Community Detection, http://arxiv.org/abs/cond-mat/0603718 . + * + * + * From version 0.6, igraph also supports an extension to + * the algorithm that allows negative edge weights. This is described + * in V. A. Traag and Jeroen Bruggeman: Community detection in networks + * with positive and negative links, http://arxiv.org/abs/0811.2329 . + * + * \param graph The input graph, it may be directed but the direction + * of the edges is ignored by the algorithm. + * \param weights The vector giving the edge weights, it may be \c NULL, + * in which case all edges are weighted equally. The edge weights + * must be positive unless using the \c IGRAPH_SPINCOMM_IMP_NEG + * implementation. + * \param modularity Pointer to a real number, if not \c NULL then the + * modularity score of the solution will be stored here. This is the + * gereralized modularity, taking into account the resolution parameter + * \p gamma. See \ref igraph_modularity() for details. + * \param temperature Pointer to a real number, if not \c NULL then + * the temperature at the end of the algorithm will be stored + * here. + * \param membership Pointer to an initialized vector or \c NULL. If + * not \c NULL then the result of the clustering will be stored + * here. For each vertex, the number of its cluster is given, with the + * first cluster numbered zero. The vector will be resized as + * needed. + * \param csize Pointer to an initialized vector or \c NULL. If not \c + * NULL then the sizes of the clusters will stored here in cluster + * number order. The vector will be resized as needed. + * \param spins Integer giving the number of spins, i.e. the maximum + * number of clusters. Even if the number of spins is high the number of + * clusters in the result might be small. + * \param parupdate A Boolean constant, whether to update all spins in + * parallel. It is not implemented in the \c IGRAPH_SPINCOMM_INP_NEG + * implementation. + * \param starttemp Real number, the temperature at the start. A reasonable + * default is 1.0. + * \param stoptemp Real number, the algorithm stops at this temperature. A + * reasonable default is 0.01. + * \param coolfact Real number, the cooling factor for the simulated + * annealing. A reasonable default is 0.99. + * \param update_rule The type of the update rule. Possible values: \c + * IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c + * IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defines + * the null model based on which the actual clustering is done. If + * this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph + * (i.e. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the + * configuration model is used. The configuration means that the + * baseline for the clustering is a random graph with the same + * degree distribution as the input graph. + * \param gamma Real number. The gamma parameter of the algorithm, + * acting as a resolution parameter. Smaller values typically lead to + * larger clusters, larger values typically lead to smaller clusters. + * \param implementation Constant, chooses between the two + * implementations of the spin-glass algorithm that are included + * in igraph. \c IGRAPH_SPINCOMM_IMP_ORIG selects the original + * implementation, this is faster, \c IGRAPH_SPINCOMM_INP_NEG selects + * an implementation that allows negative edge weights. + * \param gamma_minus Real number. Parameter for the \c IGRAPH_SPINCOMM_IMP_NEG + * implementation. This acts as a resolution parameter for the negative part + * of the network. Smaller values of \p gamma_minus leads to fewer negative + * edges within clusters. If this argument is set to zero, the algorithm + * reduces to a graph coloring algorithm when all edges have negative + * weights, using the number of spins as the number of colors. + * \return Error code. + * + * \sa \ref igraph_community_spinglass_single() for calculating the community + * of a single vertex. + * + * Time complexity: TODO. + * + */ + +igraph_error_t igraph_community_spinglass(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + igraph_spinglass_implementation_t implementation, + igraph_real_t gamma_minus) { + + IGRAPH_HANDLE_EXCEPTIONS( + switch (implementation) { + case IGRAPH_SPINCOMM_IMP_ORIG: + return igraph_i_community_spinglass_orig(graph, weights, modularity, + temperature, membership, csize, + spins, parupdate, starttemp, + stoptemp, coolfact, update_rule, + gamma); + break; + case IGRAPH_SPINCOMM_IMP_NEG: + return igraph_i_community_spinglass_negative(graph, weights, modularity, + temperature, membership, csize, + spins, parupdate, starttemp, + stoptemp, coolfact, + update_rule, gamma, + gamma_minus); + break; + default: + IGRAPH_ERROR("Unknown implementation in spinglass community detection.", + IGRAPH_EINVAL); + } + ); +} + +static igraph_error_t igraph_i_community_spinglass_orig( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t changes, runs; + igraph_bool_t use_weights = false; + bool zeroT; + double kT, acc, prob; + + /* Check arguments */ + + if (spins < 2) { + IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule for spinglass community detection.", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + use_weights = true; + if (igraph_vector_size(weights) > 0 && igraph_vector_min(weights) < 0) { + IGRAPH_ERROR( + "Weights must not be negative when using the original implementation of spinglass communities. " + "Select the implementation meant for negative weights.", + IGRAPH_EINVAL); + } + } + if (coolfact < 0 || coolfact >= 1.0) { + IGRAPH_ERROR("Cooling factor must be positive and strictly smaller than 1.", IGRAPH_EINVAL); + } + if (gamma < 0.0) { + IGRAPH_ERROR("Gamma value must not be negative.", IGRAPH_EINVAL); + } + if ( !(starttemp == 0 && stoptemp == 0) ) { + if (! (starttemp > 0 && stoptemp > 0)) { + IGRAPH_ERROR("Starting and stopping temperatures must be both positive or both zero.", + IGRAPH_EINVAL); + } + if (starttemp <= stoptemp) { + IGRAPH_ERROR("The starting temperature must be larger than the stopping temperature.", + IGRAPH_EINVAL); + } + } + + /* The spinglass algorithm does not handle the trivial cases of the + null and singleton graphs, so we catch them here. */ + if (no_of_nodes < 2) { + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_null(membership); + } + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, membership, nullptr, 1, igraph_is_directed(graph), modularity)); + } + if (temperature) { + *temperature = stoptemp; + } + if (csize) { + /* 0 clusters for 0 nodes, 1 cluster for 1 node */ + IGRAPH_CHECK(igraph_vector_int_resize(csize, no_of_nodes)); + igraph_vector_int_fill(csize, 1); + } + return IGRAPH_SUCCESS; + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with unconnected graph.", IGRAPH_EINVAL); + } + + network net; + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights, + &net, use_weights)); + + prob = 2.0 * net.sum_weights / double(net.node_list.Size()) + / double(net.node_list.Size() - 1); + + PottsModel pm(&net, spins, update_rule); + + if ((stoptemp == 0.0) && (starttemp == 0.0)) { + zeroT = true; + } else { + zeroT = false; + } + if (!zeroT) { + kT = pm.FindStartTemp(gamma, prob, starttemp); + } else { + kT = stoptemp; + } + /* assign random initial configuration */ + pm.assign_initial_conf(-1); + runs = 0; + changes = 1; + + while (changes > 0 && (kT / stoptemp > 1.0 || (zeroT && runs < 150))) { + + IGRAPH_ALLOW_INTERRUPTION(); + + runs++; + if (!zeroT) { + kT *= coolfact; + if (parupdate) { + changes = pm.HeatBathParallelLookup(gamma, prob, kT, 50); + } else { + acc = pm.HeatBathLookup(gamma, prob, kT, 50); + if (acc < (1.0 - 1.0 / double(spins)) * 0.01) { + changes = 0; + } else { + changes = 1; + } + } + } else { + if (parupdate) { + changes = pm.HeatBathParallelLookupZeroTemp(gamma, prob, 50); + } else { + acc = pm.HeatBathLookupZeroTemp(gamma, prob, 50); + /* less than 1 percent acceptance ratio */ + if (acc < (1.0 - 1.0 / double(spins)) * 0.01) { + changes = 0; + } else { + changes = 1; + } + } + } + } /* while loop */ + + pm.WriteClusters(modularity, temperature, csize, membership, kT, gamma); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_community_spinglass_single + * \brief Community of a single node based on statistical mechanics. + * + * This function implements the community structure detection + * algorithm proposed by Joerg Reichardt and Stefan Bornholdt. It is + * described in their paper: Statistical Mechanics of + * Community Detection, http://arxiv.org/abs/cond-mat/0603718 . + * + * + * This function calculates the community of a single vertex without + * calculating all the communities in the graph. + * + * \param graph The input graph, it may be directed but the direction + * of the edges is not used in the algorithm. + * \param weights Pointer to a vector with the weights of the edges. + * Alternatively \c NULL can be supplied to have the same weight + * for every edge. + * \param vertex The vertex ID of the vertex of which this community is + * calculated. + * \param community Pointer to an initialized vector, the result, the + * IDs of the vertices in the community of the input vertex will be + * stored here. The vector will be resized as needed. + * \param cohesion Pointer to a real variable, if not \c NULL the + * cohesion index of the community will be stored here. + * \param adhesion Pointer to a real variable, if not \c NULL the + * adhesion index of the community will be stored here. + * \param inner_links Pointer to a real, if not \c NULL the + * number of edges within the community (or the sum of their weights) + * is stored here. + * \param outer_links Pointer to a real, if not \c NULL the + * number of edges between the community and the rest of the graph + * (or the sum of their weights) will be stored here. + * \param spins The number of spins to use, this can be higher than + * the actual number of clusters in the network, in which case some + * clusters will contain zero vertices. + * \param update_rule The type of the update rule. Possible values: \c + * IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c + * IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defined + * the null model based on which the actual clustering is done. If + * this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph + * (ie. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the + * configuration model is used. The configuration means that the + * baseline for the clustering is a random graph with the same + * degree distribution as the input graph. + * \param gamma Real number. The gamma parameter of the + * algorithm. This defined the weight of the missing and existing + * links in the quality function for the clustering. The default + * value in the original code was 1.0, which is equal weight to + * missing and existing edges. Smaller values make the existing + * links contibute more to the energy function which is minimized + * in the algorithm. Bigger values make the missing links more + * important. (If my understanding is correct.) + * \return Error code. + * + * \sa igraph_community_spinglass() for the traditional version of the + * algorithm. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_community_spinglass_single(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t vertex, + igraph_vector_int_t *community, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_real_t *inner_links, + igraph_real_t *outer_links, + igraph_int_t spins, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma) { + IGRAPH_HANDLE_EXCEPTIONS( + igraph_bool_t use_weights = false; + char startnode[SPINGLASS_MAX_NAME_LEN]; + + /* Check arguments */ + + if (spins < 2) { + IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid edge weight vector length.", IGRAPH_EINVAL); + } + use_weights = 1; + } + if (gamma < 0.0) { + IGRAPH_ERROR("Invalid gamma value.", IGRAPH_EINVAL); + } + if (vertex < 0 || vertex > igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid vertex ID.", IGRAPH_EINVAL); + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with disconnected graph.", IGRAPH_EINVAL); + } + + network net; + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights, + &net, use_weights)); + + PottsModel pm(&net, spins, update_rule); + + /* to be expected, if we want to find the community around a particular node*/ + /* the initial conf is needed, because otherwise, + the degree of the nodes is not in the weight property, stupid!!! */ + pm.assign_initial_conf(-1); + snprintf(startnode, sizeof(startnode) / sizeof(startnode[0]), "%" IGRAPH_PRId "", vertex + 1); + pm.FindCommunityFromStart(gamma, startnode, community, + cohesion, adhesion, inner_links, outer_links); + ); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_community_spinglass_negative( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + igraph_real_t gamma_minus) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t runs; + igraph_bool_t use_weights = false; + bool zeroT; + double kT, acc; + igraph_real_t d_n; + igraph_real_t d_p; + + /* Check arguments */ + + if (parupdate) { + IGRAPH_ERROR("Parallel spin update not implemented with negative weights.", + IGRAPH_UNIMPLEMENTED); + } + + if (spins < 2) { + IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule for spinglass community detection.", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + use_weights = true; + } + if (coolfact < 0 || coolfact >= 1.0) { + IGRAPH_ERROR("Cooling factor must be positive and strictly smaller than 1.", IGRAPH_EINVAL); + } + if (gamma < 0.0) { + IGRAPH_ERROR("Gamma value must not be negative.", IGRAPH_EINVAL); + } + if ( !(starttemp == 0 && stoptemp == 0) ) { + if (! (starttemp > 0 && stoptemp > 0)) { + IGRAPH_ERROR("Starting and stopping temperatures must be both positive or both zero.", + IGRAPH_EINVAL); + } + if (starttemp <= stoptemp) { + IGRAPH_ERROR("The starting temperature must be larger than the stopping temperature.", + IGRAPH_EINVAL); + } + } + + /* The spinglass algorithm does not handle the trivial cases of the + null and singleton graphs, so we catch them here. */ + if (no_of_nodes < 2) { + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_null(membership); + } + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, membership, nullptr, 1, igraph_is_directed(graph), modularity)); + } + if (temperature) { + *temperature = stoptemp; + } + if (csize) { + /* 0 clusters for 0 nodes, 1 cluster for 1 node */ + IGRAPH_CHECK(igraph_vector_int_resize(csize, no_of_nodes)); + igraph_vector_int_fill(csize, 1); + } + return IGRAPH_SUCCESS; + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with unconnected graph.", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) > 0) { + igraph_vector_minmax(weights, &d_n, &d_p); + } else { + d_n = d_p = 1; + } + + if (d_n > 0) { + d_n = 0; + } + if (d_p < 0) { + d_p = 0; + } + d_n = -d_n; + + network net; + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights, + &net, use_weights)); + + bool directed = igraph_is_directed(graph); + + PottsModelN pm(&net, spins, directed); + + if ((stoptemp == 0.0) && (starttemp == 0.0)) { + zeroT = true; + } else { + zeroT = false; + } + + //Begin at a high enough temperature + kT = pm.FindStartTemp(gamma, gamma_minus, starttemp); + + /* assign random initial configuration */ + pm.assign_initial_conf(true); + + runs = 0; + while (kT / stoptemp > 1.0 || (zeroT && runs < 150)) { + IGRAPH_ALLOW_INTERRUPTION(); + + runs++; + kT = kT * coolfact; + acc = pm.HeatBathLookup(gamma, gamma_minus, kT, 50); + if (acc < (1.0 - 1.0 / double(spins)) * 0.001) { + break; + } + } /* while loop */ + + /* These are needed, otherwise 'modularity' is not calculated */ + igraph_matrix_t adhesion, normalized_adhesion; + igraph_real_t polarization; + IGRAPH_MATRIX_INIT_FINALLY(&adhesion, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&normalized_adhesion, 0, 0); + pm.WriteClusters(modularity, temperature, csize, membership, + &adhesion, &normalized_adhesion, &polarization, + kT, d_p, d_n); + igraph_matrix_destroy(&normalized_adhesion); + igraph_matrix_destroy(&adhesion); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/spinglass/pottsmodel_2.cpp b/src/community/spinglass/pottsmodel_2.cpp new file mode 100644 index 0000000..cdc1286 --- /dev/null +++ b/src/community/spinglass/pottsmodel_2.cpp @@ -0,0 +1,1708 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + This file was modified by Vincent Traag + The original copyright notice follows here */ + +/*************************************************************************** + pottsmodel.cpp - description + ------------------- + begin : Fri May 28 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "pottsmodel_2.h" + +#include "igraph_random.h" +// #include "core/interruption.h" + +#include +#include + +using namespace std; + +//################################################################################################# +PottsModel::PottsModel(network *n, igraph_int_t qvalue, int m) : + net(n), q(qvalue), operation_mode(m), Qmatrix(qvalue+1) +{ + DLList_Iter iter; + const NNode *n_cur; + igraph_int_t *i_ptr; + //needed in calculating modularity + Qa = new double[q + 1]; + //weights for each spin state needed in Monte Carlo process + weights = new double[q + 1]; + //bookkeeping of occupation numbers of spin states or the number of links in community + color_field = new double[q + 1]; + neighbours = new double[q + 1]; + + num_of_nodes = net->node_list.Size(); + num_of_links = net->link_list.Size(); + + n_cur = iter.First(&net->node_list); + while (!iter.End()) { + if (k_max < n_cur->Get_Degree()) { + k_max = n_cur->Get_Degree(); + } + i_ptr = new igraph_int_t; + *i_ptr = 0; + new_spins.Push(i_ptr); + i_ptr = new igraph_int_t; + *i_ptr = 0; + previous_spins.Push(i_ptr); + n_cur = iter.Next(); + } +} +//####################################################### +//Destructor of PottsModel +//######################################################## +PottsModel::~PottsModel() { + /* The DLItem destructor does not delete its item currently, + because of some bad design. As a workaround, we delete them here + by hand */ + new_spins.delete_items(); + previous_spins.delete_items(); + delete [] Qa; + delete [] weights; + delete [] color_field; + delete [] neighbours; +} +//##################################################### +//Assing an initial random configuration of spins to nodes +//if called with negative argument or the spin used as argument +//when called with positve one. +//This may be handy, if you want to warm up the network. +//#################################################### +igraph_int_t PottsModel::assign_initial_conf(igraph_int_t spin) { + igraph_int_t s; + DLList_Iter iter; + DLList_Iter l_iter; + NNode *n_cur; + const NLink *l_cur; + double sum_weight; + + // initialize colorfield + for (igraph_int_t i = 0; i <= q; i++) { + color_field[i] = 0.0; + } + // + total_degree_sum = 0.0; + n_cur = iter.First(&net->node_list); + while (!iter.End()) { + if (spin < 0) { + s = RNG_INTEGER(1, q); + } else { + s = spin; + } + n_cur->Set_ClusterIndex(s); + l_cur = l_iter.First(n_cur->Get_Links()); + sum_weight = 0; + while (!l_iter.End()) { + sum_weight += l_cur->Get_Weight(); //weight should be one, in case we are not using it. + l_cur = l_iter.Next(); + } + // we set the sum of the weights or the degree as the weight of the node, this way + // we do not have to calculate it again. + n_cur->Set_Weight(sum_weight); + + // in case we want all links to be contribute equally - parameter gamm=fixed + if (operation_mode == 0) { + color_field[s]++; + } else { + color_field[s] += sum_weight; + } + // or in case we want to use a weight of each link that is proportional to k_i\times k_j + total_degree_sum += sum_weight; + n_cur = iter.Next(); + } + + return net->node_list.Size(); +} + +//##################################################################### +// Q denotes the modularity of the network +// This function calculates it initially +// In the event of a spin changing its state, it only needs updating +// Note that Qmatrix and Qa are only counting! The normalization +// by num_of_links is done later +//#################################################################### +double PottsModel::initialize_Qmatrix() { + DLList_Iter l_iter; + NLink *l_cur; + igraph_int_t i, j; + //initialize with zeros + num_of_links = net->link_list.Size(); + for (i = 0; i <= q; i++) { + Qa[i] = 0.0; + for (j = i; j <= q; j++) { + Qmatrix[i][j] = 0.0; + Qmatrix[j][i] = 0.0; + } + } + //go over all links and make corresponding entries in Q matrix + //An edge connecting state i wiht state j will get an entry in Qij and Qji + l_cur = l_iter.First(&net->link_list); + while (!l_iter.End()) { + i = l_cur->Get_Start()->Get_ClusterIndex(); + j = l_cur->Get_End()->Get_ClusterIndex(); + Qmatrix[i][j] += l_cur->Get_Weight(); + Qmatrix[j][i] += l_cur->Get_Weight(); + + l_cur = l_iter.Next(); + } + //Finally, calculate sum over rows and keep in Qa + for (i = 0; i <= q; i++) { + for (j = 0; j <= q; j++) { + Qa[i] += Qmatrix[i][j]; + } + } + return calculate_Q(); +} +//#################################################################### +// This function does the actual calculation of Q from the matrix +// The normalization by num_of_links is done here +//#################################################################### +double PottsModel::calculate_Q() { + double Q = 0.0; + for (igraph_int_t i = 0; i <= q; i++) { + Q += Qmatrix[i][i] - Qa[i] * Qa[i] / double(2.0 * net->sum_weights); + } + Q /= double(2.0 * net->sum_weights); + return Q; +} + +//########################################################################## +// We would like to start from a temperature with at least 95 of all proposed +// spin changes accepted in 50 sweeps over the network +// The function returns the Temperature found +//######################################################################### +double PottsModel::FindStartTemp(double gamma, double prob, double ts) { + double kT; + kT = ts; + //assing random initial condition + assign_initial_conf(-1); + //initialize Modularity matrix, from now on, it will be updated at every spin change + initialize_Qmatrix(); + // the factor 1-1/q is important, since even, at infinite temperature, + // only 1-1/q of all spins do change their state, since a randomly chooses new + // state is with prob. 1/q the old state. + while (acceptance < (1.0 - 1.0 / double(q)) * 0.95) { //want 95% acceptance + kT = kT * 1.1; + HeatBathParallelLookup(gamma, prob, kT, 50); + } + kT *= 1.1; // just to be sure... + return kT; +} + +//############################################################## +//This function does a parallel update at zero T +//Hence, it is really fast on easy problems +//max sweeps is the maximum number of sweeps it should perform, +//if it does not converge earlier +//############################################################## +igraph_int_t PottsModel::HeatBathParallelLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps) { + DLList_Iter net_iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + unsigned int sweep; + igraph_int_t *SPIN, *P_SPIN, old_spin, new_spin, spin_opt; + igraph_int_t changes; + double h, delta = 0, deltaE, deltaEmin, w, degree; + bool cyclic = false; + + sweep = 0; + changes = 1; + while (sweep < max_sweeps && changes) { + cyclic = true; + sweep++; + changes = 0; + //Loop over all nodes + node = net_iter.First(&net->node_list); + SPIN = i_iter.First(&new_spins); + while (!net_iter.End()) { + // How many neighbors of each type? + // set them all zero + for (igraph_int_t i = 0; i <= q; i++) { + neighbours[i] = 0; + } + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + switch (operation_mode) { + case 0: { + delta = 1.0; + break; + } + case 1: { //newman modularity + prob = degree / total_degree_sum; + delta = degree; + break; + } + default: + IGRAPH_FATAL("Must not reach here."); + } + + + spin_opt = old_spin; + deltaEmin = 0.0; + for (igraph_int_t spin = 1; spin <= q; spin++) { // all possible spin states + if (spin != old_spin) { + h = color_field[spin] + delta - color_field[old_spin]; + deltaE = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (deltaE < deltaEmin) { + spin_opt = spin; + deltaEmin = deltaE; + } + } + } // for spin + + //Put optimal spin on list for later update + *SPIN = spin_opt; + node = net_iter.Next(); + SPIN = i_iter.Next(); + } // while !net_iter.End() + + //------------------------------- + //Now set all spins to new values + node = net_iter.First(&net->node_list); + SPIN = i_iter.First(&new_spins); + P_SPIN = i_iter2.First(&previous_spins); + while (!net_iter.End()) { + old_spin = node->Get_ClusterIndex(); + new_spin = *SPIN; + if (new_spin != old_spin) { // Do we really have a change?? + changes++; + node->Set_ClusterIndex(new_spin); + //this is important!! + //In Parallel update, there occur cyclic attractors of size two + //which then make the program run for ever + if (new_spin != *P_SPIN) { + cyclic = false; + } + *P_SPIN = old_spin; + color_field[old_spin]--; + color_field[new_spin]++; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + node = net_iter.Next(); + SPIN = i_iter.Next(); + P_SPIN = i_iter2.Next(); + } // while (!net_iter.End()) + } // while markov + + // In case of a cyclic attractor, we want to interrupt + if (cyclic) { + acceptance = 0.0; + return 0; + } else { + acceptance = double(changes) / double(num_of_nodes); + return changes; + } +} +//################################################################################### +//The same function as before, but rather than parallel update, it pics the nodes to update +//randomly +//################################################################################### +double PottsModel::HeatBathLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps) { + DLList_Iter l_iter; + NNode *node, *n_cur; + NLink *l_cur; + igraph_int_t new_spin, spin_opt, old_spin; + unsigned int sweep; + igraph_int_t r; + igraph_int_t changes; + double delta = 0, h, deltaE, deltaEmin, w, degree; + + sweep = 0; + changes = 0; + while (sweep < max_sweeps) { + sweep++; + //ueber alle Knoten im Netz + for (igraph_int_t n = 0; n < num_of_nodes; n++) { + r = RNG_INTEGER(0, num_of_nodes - 1); + node = net->node_list.Get(r); + // Wir zaehlen, wieviele Nachbarn von jedem spin vorhanden sind + // erst mal alles Null setzen + for (igraph_int_t i = 0; i <= q; i++) { + neighbours[i] = 0; + } + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + switch (operation_mode) { + case 0: { + delta = 1.0; + break; + } + case 1: { //newman modularity + prob = degree / total_degree_sum; + delta = degree; + break; + } + default: + IGRAPH_FATAL("Must not reach here."); + } + + + spin_opt = old_spin; + deltaEmin = 0.0; + for (igraph_int_t spin = 1; spin <= q; spin++) { // alle moeglichen Spins + if (spin != old_spin) { + h = color_field[spin] + delta - color_field[old_spin]; + deltaE = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (deltaE < deltaEmin) { + spin_opt = spin; + deltaEmin = deltaE; + } + } + } // for spin + + //------------------------------- + //Now update the spins + new_spin = spin_opt; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + } // for n + } // while markov + + acceptance = double(changes) / double(num_of_nodes) / double(sweep); + return acceptance; +} +//##################################################################################### +//This function performs a parallel update at Terperature T +//##################################################################################### +igraph_int_t PottsModel::HeatBathParallelLookup(double gamma, double prob, double kT, unsigned int max_sweeps) { + DLList_Iter net_iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + igraph_int_t new_spin, spin_opt, old_spin; + igraph_int_t *SPIN, *P_SPIN; + unsigned int sweep; + igraph_int_t max_q; + igraph_int_t changes; + double h, delta = 0, norm, r, beta, minweight, prefac = 0, w, degree; + bool cyclic = false/*, found*/; + igraph_int_t number_of_nodes; + + sweep = 0; + changes = 1; + number_of_nodes = net->node_list.Size(); + while (sweep < max_sweeps && changes) { + cyclic = true; + sweep++; + changes = 0; + //Loop over all nodes + node = net_iter.First(&net->node_list); + SPIN = i_iter.First(&new_spins); + while (!net_iter.End()) { + // Initialize neighbours and weights + for (igraph_int_t i = 0; i <= q; i++) { + neighbours[i] = 0; + weights[i] = 0; + } + norm = 0.0; + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + switch (operation_mode) { + case 0: { + prefac = 1.0; + delta = 1.0; + break; + } + case 1: { //newman modularity + prefac = 1.0; + prob = degree / total_degree_sum; + delta = degree; + break; + } + default: + IGRAPH_FATAL("Must not reach here."); + } + spin_opt = old_spin; + beta = 1.0 / kT * prefac; + minweight = 0.0; + weights[old_spin] = 0.0; + for (igraph_int_t spin = 1; spin <= q; spin++) { // loop over all possible new spins + if (spin != old_spin) { // only if we have a different than old spin! + h = color_field[spin] + delta - color_field[old_spin]; + weights[spin] = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (weights[spin] < minweight) { + minweight = weights[spin]; + } + } + } // for spin + for (igraph_int_t spin = 1; spin <= q; spin++) { // loop over all possibe spins + weights[spin] -= minweight; // subtract minweight + // to avoid numerical problems with large exponents + weights[spin] = exp(-beta * weights[spin]); + norm += weights[spin]; + } // for spin + + //now choose a new spin + r = RNG_UNIF(0, norm); + new_spin = 1; + while (new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; + break; + } else { + r -= weights[new_spin]; + } + new_spin++; + } + //Put new spin on list + *SPIN = spin_opt; + + node = net_iter.Next(); + SPIN = i_iter.Next(); + } // while !net_iter.End() + + //------------------------------- + //now update all spins + node = net_iter.First(&net->node_list); + SPIN = i_iter.First(&new_spins); + P_SPIN = i_iter2.First(&previous_spins); + while (!net_iter.End()) { + old_spin = node->Get_ClusterIndex(); + new_spin = *SPIN; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + if (new_spin != *P_SPIN) { + cyclic = false; + } + *P_SPIN = old_spin; + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + node = net_iter.Next(); + SPIN = i_iter.Next(); + P_SPIN = i_iter2.Next(); + } // while (!net_iter.End()) + + } // while markov + max_q = 0; + for (igraph_int_t i = 1; i <= q; i++) if (color_field[i] > max_q) { + max_q = igraph_int_t(color_field[i]); + } + + //again, we would not like to end up in cyclic attractors + if (cyclic && changes) { + acceptance = double(changes) / double(number_of_nodes); + return 0; + } else { + acceptance = double(changes) / double(number_of_nodes); + return changes; + } +} +//############################################################## +// This is the function generally used for optimisation, +// as the parallel update has its flaws, due to the cyclic attractors +//############################################################## +double PottsModel::HeatBathLookup(double gamma, double prob, double kT, unsigned int max_sweeps) { + DLList_Iter l_iter; + NNode *node, *n_cur; + NLink *l_cur; + igraph_int_t new_spin, spin_opt, old_spin; + unsigned int sweep; + igraph_int_t max_q; + igraph_int_t rn; + igraph_int_t changes; + double degree, w, delta = 0, h; + double norm, r, beta, minweight, prefac = 0; + igraph_int_t number_of_nodes; + sweep = 0; + changes = 0; + number_of_nodes = net->node_list.Size(); + while (sweep < max_sweeps) { + sweep++; + //loop over all nodes in network + for (igraph_int_t n = 0; n < number_of_nodes; n++) { + rn = RNG_INTEGER(0, number_of_nodes - 1); + + node = net->node_list.Get(rn); + // initialize the neighbours and the weights + for (igraph_int_t i = 0; i <= q; i++) { + neighbours[i] = 0.0; + weights[i] = 0.0; + } + norm = 0.0; + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + + //Look for optimal spin + + old_spin = node->Get_ClusterIndex(); + switch (operation_mode) { + case 0: { + prefac = 1.0; + delta = 1.0; + break; + } + case 1: {//newman modularity + prefac = 1.0; + prob = degree / total_degree_sum; + delta = degree; + break; + } + default: + IGRAPH_FATAL("Must not reach here."); + } + spin_opt = old_spin; + beta = 1.0 / kT * prefac; + minweight = 0.0; + weights[old_spin] = 0.0; + for (igraph_int_t spin = 1; spin <= q; spin++) { // all possible new spins + if (spin != old_spin) { // except the old one! + h = color_field[spin] - (color_field[old_spin] - delta); + weights[spin] = neighbours[old_spin] - neighbours[spin] + gamma * prob * h; + if (weights[spin] < minweight) { + minweight = weights[spin]; + } + } + } // for spin + for (igraph_int_t spin = 1; spin <= q; spin++) { // all possible new spins + weights[spin] -= minweight; // subtract minweigt + // for numerical stability + weights[spin] = exp(-beta * weights[spin]); + norm += weights[spin]; + } // for spin + + + //choose a new spin + r = RNG_UNIF(0, norm); + new_spin = 1; + while (new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; + break; + } else { + r -= weights[new_spin]; + } + new_spin++; + } + //------------------------------- + //now set the new spin + new_spin = spin_opt; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + } // for n + } // while markov + max_q = 0; + + for (igraph_int_t i = 1; i <= q; i++) if (color_field[i] > max_q) { + max_q = igraph_int_t(color_field[i] + 0.5); + } + + acceptance = double(changes) / double(number_of_nodes) / double(sweep); + return acceptance; +} + +//############################################################################################### +//# Here we try to minimize the affinity to the rest of the network +//############################################################################################### +double PottsModel::FindCommunityFromStart( + double gamma, + const char *nodename, + igraph_vector_int_t *result, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_real_t *my_inner_links, + igraph_real_t *my_outer_links) const { + DLList_Iter iter, iter2; + DLList_Iter l_iter; + DLList to_do; + DLList community; + NNode *start_node = nullptr, *n_cur, *neighbor, *max_aff_node, *node; + NLink *l_cur; + bool found = false, add = false, remove = false; + double degree, delta_aff_add, delta_aff_rem, max_delta_aff, Ks = 0.0, Kr = 0, kis, kir, w; + igraph_int_t community_marker = 5; + igraph_int_t to_do_marker = 10; + double inner_links = 0, outer_links = 0, aff_r, aff_s; + + // find the node in the network + n_cur = iter.First(&net->node_list); + while (!found && !iter.End()) { + if (0 == strcmp(n_cur->Get_Name(), nodename)) { + start_node = n_cur; + found = true; + community.Push(start_node); + start_node->Set_Marker(community_marker); + Ks = start_node->Get_Weight(); + Kr = total_degree_sum - start_node->Get_Weight(); + } + n_cur = iter.Next(); + } + if (!found) { + return -1; + } + //############################# + // initialize the to_do list and community with the neighbours of start node + //############################# + neighbor = iter.First(start_node->Get_Neighbours()); + while (!iter.End()) { + community.Push(neighbor); + neighbor->Set_Marker(community_marker); + Ks += neighbor->Get_Weight(); + Kr -= neighbor->Get_Weight(); + neighbor = iter.Next(); + } + node = iter.First(&community); + while (!iter.End()) { + //now add at the second neighbors to the to_do list + neighbor = iter2.First(node->Get_Neighbours()); + while (!iter2.End()) { + if (neighbor->Get_Marker() != community_marker && neighbor->Get_Marker() != to_do_marker) { + to_do.Push(neighbor); + neighbor->Set_Marker(to_do_marker); + } + neighbor = iter2.Next(); + } + node = iter.Next(); + } + + //############# + //repeat, as long as we are still adding nodes to the communtiy + //############# + add = true; + remove = true; + while (add || remove) { + //############################# + //calculate the affinity changes of all nodes for adding every node in the to_do list to the community + //############################## + + // TODO + // IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + + max_delta_aff = 0.0; + max_aff_node = nullptr; + add = false; + node = iter.First(&to_do); + while (!iter.End()) { + //printf("Checking Links of %s\n",node->Get_Name()); + degree = node->Get_Weight(); + kis = 0.0; + kir = 0.0; + // For every of the neighbors, check, count the links to the community + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + if (n_cur->Get_Marker() == community_marker) { + kis += w; //the weight/number of links to the community + } else { + kir += w; //the weight/number of links to the rest of the network + } + l_cur = l_iter.Next(); + } + aff_r = kir - gamma / total_degree_sum * (Kr - degree) * degree; + aff_s = kis - gamma / total_degree_sum * Ks * degree; + delta_aff_add = aff_r - aff_s; + if (delta_aff_add <= max_delta_aff) { + max_delta_aff = delta_aff_add; + max_aff_node = node; + add = true; + } + node = iter.Next(); + } + //################ + //calculate the affinity changes for removing every single node from the community + //################ + inner_links = 0; + outer_links = 0; + remove = false; + node = iter.First(&community); + while (!iter.End()) { + //printf("Checking Links of %s\n",node->Get_Name()); + degree = node->Get_Weight(); + kis = 0.0; + kir = 0.0; + // For every of the neighbors, check, count the links to the community + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + if (n_cur->Get_Marker() == community_marker) { + kis += w; + inner_links += w; //summing all w gives twice the number of inner links(weights) + } else { + kir += w; + outer_links += w; + } + l_cur = l_iter.Next(); + } + aff_r = kir - gamma / total_degree_sum * Kr * degree; + aff_s = kis - gamma / total_degree_sum * (Ks - degree) * degree; + delta_aff_rem = aff_s - aff_r; + // we should not remove the nodes, we have just added + if (delta_aff_rem < max_delta_aff) { + max_delta_aff = delta_aff_rem ; + max_aff_node = node; + remove = true; + add = false; + } + node = iter.Next(); + } + inner_links = inner_links * 0.5; + //################ + // Now check, whether we want to remove or add a node + //################ + if (add) { + //################ + //add the node of maximum affinity to the community + //############### + community.Push(max_aff_node); + max_aff_node->Set_Marker(community_marker); + //delete node from to_do + to_do.fDelete(max_aff_node); + //update the sum of degrees in the community + Ks += max_aff_node->Get_Weight(); + Kr -= max_aff_node->Get_Weight(); + //now add all neighbors of this node, that are not already + //in the to_do list or in the community + neighbor = iter.First(max_aff_node->Get_Neighbours()); + while (!iter.End()) { + if (neighbor->Get_Marker() != community_marker && neighbor->Get_Marker() != to_do_marker) { + to_do.Push(neighbor); + neighbor->Set_Marker(to_do_marker); + //printf("Adding node %s to to_do list.\n",neighbor->Get_Name()); + } + neighbor = iter.Next(); + } + } + if (remove) { + //################ + //remove those with negative affinities + //################ + community.fDelete(max_aff_node); + max_aff_node->Set_Marker(to_do_marker); + //update the sum of degrees in the community + Ks -= max_aff_node->Get_Weight(); + Kr += max_aff_node->Get_Weight(); + //add the node to to_do again + to_do.Push(max_aff_node); + } + // TODO + // IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + } + //################### + //write the node in the community to a file + //################### + if (cohesion) { + *cohesion = inner_links - gamma / total_degree_sum * Ks * Ks * 0.5; + } + if (adhesion) { + *adhesion = outer_links - gamma / total_degree_sum * Ks * Kr; + } + if (my_inner_links) { + *my_inner_links = inner_links; + } + if (my_outer_links) { + *my_outer_links = outer_links; + } + if (result) { + node = iter.First(&community); + igraph_vector_int_clear(result); + while (!iter.End()) { + IGRAPH_CHECK(igraph_vector_int_push_back(result, node->Get_Index())); + node = iter.Next(); + } + } + igraph_int_t size = community.Size(); + return size; +} + +//################################################################################################ +// this Function writes the clusters to disk +//################################################################################################ +igraph_int_t PottsModel::WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *csize, + igraph_vector_int_t *membership, + double kT, double gamma) const { + const NNode *n_cur, *n_cur2; + DLList_Iter iter, iter2; + HugeArray inner_links; + HugeArray outer_links; + HugeArray nodes; + + if (temperature) { + *temperature = kT; + } + + if (csize || membership || modularity) { + // TODO: count the number of clusters + for (igraph_int_t spin = 1; spin <= q; spin++) { + inner_links[spin] = 0; + outer_links[spin] = 0; + nodes[spin] = 0; + n_cur = iter.First(&net->node_list); + while (!iter.End()) { + if (n_cur->Get_ClusterIndex() == spin) { + nodes[spin]++; + n_cur2 = iter2.First(n_cur->Get_Neighbours()); + while (!iter2.End()) { + if (n_cur2->Get_ClusterIndex() == spin) { + inner_links[spin]++; + } else { + outer_links[spin]++; + } + n_cur2 = iter2.Next(); + } + } + n_cur = iter.Next(); + } + } + } + if (modularity) { + *modularity = 0.0; + for (igraph_int_t spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + double t1 = inner_links[spin] / net->sum_weights / 2.0; + double t2 = (inner_links[spin] + outer_links[spin]) / + net->sum_weights / 2.0; + *modularity += t1; + *modularity -= gamma * t2 * t2; + } + } + } + if (csize) { + igraph_vector_int_clear(csize); + for (igraph_int_t spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + inner_links[spin] /= 2; + IGRAPH_CHECK(igraph_vector_int_push_back(csize, nodes[spin])); + } + } + } + + //die Elemente der Cluster + if (membership) { + igraph_int_t no = -1; + IGRAPH_CHECK(igraph_vector_int_resize(membership, num_of_nodes)); + for (igraph_int_t spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + no++; + } + n_cur = iter.First(&net->node_list); + while (!iter.End()) { + if (n_cur->Get_ClusterIndex() == spin) { + VECTOR(*membership)[ n_cur->Get_Index() ] = no; + } + n_cur = iter.Next(); + } + } + } + + return num_of_nodes; +} + +//################################################################################################# +PottsModelN::PottsModelN(network *n, igraph_int_t num_communities, bool directed) : + net(n), q(num_communities), num_nodes(net->node_list.Size()), is_directed(directed) +{ } +//####################################################### +//Destructor of PottsModel +//######################################################## +PottsModelN::~PottsModelN() { + delete [] degree_pos_in; + delete [] degree_neg_in; + delete [] degree_pos_out; + delete [] degree_neg_out; + + delete [] degree_community_pos_in; + delete [] degree_community_neg_in; + delete [] degree_community_pos_out; + delete [] degree_community_neg_out; + + delete [] weights; + delete [] neighbours; + delete [] csize; + + delete [] spin; +} + +void PottsModelN::assign_initial_conf(bool init_spins) { + igraph_int_t s; + DLList_Iter l_iter; + const NNode *n_cur; + const NLink *l_cur; + + if (init_spins) { + // Free the arrays before (re-)allocating them + // These arrays are initialized to NULL, so it is safe to delete even before allocation + delete [] degree_pos_in; + delete [] degree_neg_in; + delete [] degree_pos_out; + delete [] degree_neg_out; + + delete [] spin; + + //Bookkeeping of the various degrees (positive/negative) and (in/out) + degree_pos_in = new double[num_nodes]; //Postive indegree of the nodes (or sum of weights) + degree_neg_in = new double[num_nodes]; //Negative indegree of the nodes (or sum of weights) + degree_pos_out = new double[num_nodes]; //Postive outdegree of the nodes (or sum of weights) + degree_neg_out = new double[num_nodes]; //Negative outdegree of the nodes (or sum of weights) + + spin = new igraph_int_t[num_nodes]; //The spin state of each node + } + + if (is_init) { + delete [] degree_community_pos_in; + delete [] degree_community_neg_in; + delete [] degree_community_pos_out; + delete [] degree_community_neg_out; + + delete [] weights; + delete [] neighbours; + delete [] csize; + } + + is_init = true; + + //Bookkeep of occupation numbers of spin states or the number of links in community... + degree_community_pos_in = new double[q + 1]; //Positive sum of indegree for communities + degree_community_neg_in = new double[q + 1]; //Negative sum of indegree for communities + degree_community_pos_out = new double[q + 1]; //Positive sum of outegree for communities + degree_community_neg_out = new double[q + 1]; //Negative sum of outdegree for communities + + //...and of weights and neighbours for in the HeathBathLookup + weights = new double[q + 1]; //The weights for changing to another spin state + neighbours = new double[q + 1]; //The number of neighbours (or weights) in different spin states + csize = new igraph_int_t[q + 1]; //The number of nodes in each community + + + //Initialize communities + for (igraph_int_t i = 0; i <= q; i++) { + degree_community_pos_in[i] = 0.0; + degree_community_neg_in[i] = 0.0; + degree_community_pos_out[i] = 0.0; + degree_community_neg_out[i] = 0.0; + + csize[i] = 0; + } + + //Initialize vectors + if (init_spins) { + for (igraph_int_t i = 0; i < num_nodes; i++) { + degree_pos_in[i] = 0.0; + degree_neg_in[i] = 0.0; + degree_pos_out[i] = 0.0; + degree_neg_out[i] = 0.0; + +#ifdef SPINGLASS_DEBUG + printf("Initializing spin %d", i); +#endif + spin[i] = 0; + } + } + m_p = 0.0; + m_n = 0.0; + //Set community for each node, and + //correctly store it in the bookkeeping + + double sum_weight_pos_in, sum_weight_pos_out, sum_weight_neg_in, sum_weight_neg_out; + + for (igraph_int_t v = 0; v < num_nodes; v++) { + if (init_spins) { + s = RNG_INTEGER(1, q); //The new spin s + spin[v] = s; + } else { + s = spin[v]; + } + +#ifdef SPINGLASS_DEBUG + printf("Spin %d assigned to node %d.\n", s, v); +#endif + + n_cur = net->node_list.Get(v); + + l_cur = l_iter.First(n_cur->Get_Links()); + + sum_weight_pos_in = 0.0; + sum_weight_pos_out = 0.0; + sum_weight_neg_in = 0.0; + sum_weight_neg_out = 0.0; + + while (!l_iter.End()) { + double w = l_cur->Get_Weight(); + if (l_cur->Get_Start() == n_cur) //From this to other, so outgoing link + if (w > 0) { + sum_weight_pos_out += w; //Increase positive outgoing weight + } else { + sum_weight_neg_out -= w; //Increase negative outgoing weight + } else if (w > 0) { + sum_weight_pos_in += w; //Increase positive incoming weight + } else { + sum_weight_neg_in -= w; //Increase negative incoming weight + } + + l_cur = l_iter.Next(); + } + + if (!is_directed) { + double sum_weight_pos = sum_weight_pos_out + sum_weight_pos_in; + sum_weight_pos_out = sum_weight_pos; + sum_weight_pos_in = sum_weight_pos; + double sum_weight_neg = sum_weight_neg_out + sum_weight_neg_in; + sum_weight_neg_out = sum_weight_neg; + sum_weight_neg_in = sum_weight_neg; + } + + if (init_spins) { + //Set the degrees correctly + degree_pos_in[v] = sum_weight_pos_in; + degree_neg_in[v] = sum_weight_neg_in; + degree_pos_out[v] = sum_weight_pos_out; + degree_neg_out[v] = sum_weight_neg_out; + } + + //Correct the community bookkeeping + degree_community_pos_in[s] += sum_weight_pos_in; + degree_community_neg_in[s] += sum_weight_neg_in; + degree_community_pos_out[s] += sum_weight_pos_out; + degree_community_neg_out[s] += sum_weight_neg_out; + + //Community just increased + csize[s]++; + + //Sum the weights (notice that sum of indegrees equals sum of outdegrees) + m_p += sum_weight_pos_in; + m_n += sum_weight_neg_in; + } + +#ifdef SPINGLASS_DEBUG + printf("Done assigning.\n"); +#endif +} +//############################################################## +// This is the function generally used for optimisation, +// as the parallel update has its flaws, due to the cyclic attractors +//############################################################## +double PottsModelN::HeatBathLookup(double gamma, double lambda, double t, unsigned int max_sweeps) { +#ifdef SPINGLASS_DEBUG + printf("Starting sweep at temperature %f.\n", t); +#endif + DLList_Iter l_iter; + const NNode *node, *n_cur; + const NLink *l_cur; + /* The new_spin contains the spin to which we will update, + * the spin_opt is the optional spin we will consider and + * the old_spin is the spin of the node we are currently + * changing. + */ + igraph_int_t new_spin, spin_opt, old_spin; + unsigned int sweep; //current sweep + igraph_int_t changes/*, problemcount*/; //Number of changes and number of problems encountered + + double exp_old_spin; //The expectation value for the old spin + double exp_spin; //The expectation value for the other spin(s) + igraph_int_t v; //The node we will be investigating + + //The variables required for the calculations + double delta_pos_out, delta_pos_in, delta_neg_out, delta_neg_in; + double k_v_pos_out, k_v_pos_in, k_v_neg_out, k_v_neg_in; + + //weight of edge + double w; + + double beta = 1.0 / t; //Weight for probabilities + double r = 0.0; //random number used for assigning new spin + + double maxweight = 0.0; + double sum_weights = 0.0; //sum_weights for normalizing the probabilities + + sweep = 0; + changes = 0; + double m_pt = m_p; + double m_nt = m_n; + + if (m_pt < 0.001) { + m_pt = 1; + } + + if (m_nt < 0.001) { + m_nt = 1; + } + + while (sweep < max_sweeps) { + sweep++; + //loop over all nodes in network + for (igraph_int_t n = 0; n < num_nodes; n++) { + //Look for a random node + v = RNG_INTEGER(0, num_nodes - 1); + //We will be investigating node v + + node = net->node_list.Get(v); + + /*******************************************/ + // initialize the neighbours and the weights + // problemcount = 0; + for (igraph_int_t i = 0; i <= q; i++) { + neighbours[i] = 0.0; + weights[i] = 0.0; + } + + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + //Add the link to the correct cluster + neighbours[spin[n_cur->Get_Index()]] += w; + l_cur = l_iter.Next(); + } + //We now have the weight of the (in and out) neighbours + //in each cluster available to us. + /*******************************************/ + old_spin = spin[v]; + + //Look for optimal spin + + //Set the appropriate variable + delta_pos_out = degree_pos_out[v]; + delta_pos_in = degree_pos_in[v]; + delta_neg_out = degree_neg_out[v]; + delta_neg_in = degree_neg_in[v]; + + k_v_pos_out = gamma * delta_pos_out / m_pt; + k_v_pos_in = gamma * delta_pos_in / m_pt; + k_v_neg_out = lambda * delta_neg_out / m_nt; + k_v_neg_in = lambda * delta_neg_in / m_nt; + + //The expectation value for the old spin + if (is_directed) + exp_old_spin = (k_v_pos_out * (degree_community_pos_in[old_spin] - delta_pos_in) - + k_v_neg_out * (degree_community_neg_in[old_spin] - delta_neg_in)) + + (k_v_pos_in * (degree_community_pos_out[old_spin] - delta_pos_out) - + k_v_neg_in * (degree_community_neg_out[old_spin] - delta_neg_out)); + else + exp_old_spin = (k_v_pos_out * (degree_community_pos_in[old_spin] - delta_pos_in) - + k_v_neg_out * (degree_community_neg_in[old_spin] - delta_neg_in)); + + /*******************************************/ + //Calculating probabilities for each transition to another + //community. + + maxweight = 0.0; + weights[old_spin] = 0.0; + + for (spin_opt = 1; spin_opt <= q; spin_opt++) { // all possible new spins + if (spin_opt != old_spin) { // except the old one! + if (is_directed) + exp_spin = (k_v_pos_out * degree_community_pos_in[spin_opt] - k_v_neg_out * degree_community_neg_in[spin_opt]) + + (k_v_pos_in * degree_community_pos_out[spin_opt] - k_v_neg_in * degree_community_neg_out[spin_opt]); + else { + exp_spin = (k_v_pos_out * degree_community_pos_in[spin_opt] - k_v_neg_out * degree_community_neg_in[spin_opt]); + } + + weights[spin_opt] = (neighbours[spin_opt] - exp_spin) - (neighbours[old_spin] - exp_old_spin); + + if (weights[spin_opt] > maxweight) { + maxweight = weights[spin_opt]; + } + } + } // for spin + + //Calculate exp. prob. an + sum_weights = 0.0; + for (spin_opt = 1; spin_opt <= q; spin_opt++) { // all possible new spins + weights[spin_opt] -= maxweight; //subtract maxweight for numerical stability (otherwise overflow). + weights[spin_opt] = exp(beta * weights[spin_opt]); + sum_weights += weights[spin_opt]; + } // for spin + /*******************************************/ + + + /*******************************************/ + //Choose a new spin dependent on the calculated probabilities + r = RNG_UNIF(0, sum_weights); + new_spin = 1; + + while (new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; //We have found are new spin + break; + } else { + r -= weights[new_spin]; //Perhaps the next spin is the one we want + } + + new_spin++; + } + + new_spin = spin_opt; + //If there wasn't a problem we should have found + //our new spin. + /*******************************************/ + + + /*******************************************/ + //The new spin is available to us, so change + //all the appropriate counters. + if (new_spin != old_spin) { // Did we really change something?? + changes++; + spin[v] = new_spin; + + //The new spin increase by one, and the old spin decreases by one + csize[new_spin]++; csize[old_spin]--; + + //Change the sums of degree for the old spin... + degree_community_pos_in[old_spin] -= delta_pos_in; + degree_community_neg_in[old_spin] -= delta_neg_in; + degree_community_pos_out[old_spin] -= delta_pos_out; + degree_community_neg_out[old_spin] -= delta_neg_out; + + //...and for the new spin + degree_community_pos_in[new_spin] += delta_pos_in; + degree_community_neg_in[new_spin] += delta_neg_in; + degree_community_pos_out[new_spin] += delta_pos_out; + degree_community_neg_out[new_spin] += delta_neg_out; + } + + //We have no change a node from old_spin to new_spin + /*******************************************/ + + } // for n + } // while sweep +#ifdef SPINGLASS_DEBUG + printf("Done %d sweeps.\n", max_sweeps); + printf("%ld changes made for %d nodes.\n", changes, num_nodes); + printf("Last node is %d and last random number is %f with sum of weights %f with spin %d.\n", v, r, sum_weights, old_spin); +#endif + + return (double(changes) / double(num_nodes) / double(sweep)); +} + +//We need to begin at a suitable temperature. That is, a temperature at which +//enough nodes may change their initially assigned communties +double PottsModelN::FindStartTemp(double gamma, double lambda, double ts) { + double kT; + kT = ts; + //assing random initial condition + assign_initial_conf(true); + // the factor 1-1/q is important, since even, at infinite temperature, + // only 1-1/q of all spins do change their state, since a randomly chooses new + // state is with prob. 1/q the old state. + double acceptance = 0.0; + while (acceptance < (1.0 - 1.0 / double(q)) * 0.95) { //want 95% acceptance + kT = kT * 1.1; + acceptance = HeatBathLookup(gamma, lambda, kT, 50); + } + kT *= 1.1; // just to be sure... + return kT; +} + +igraph_int_t PottsModelN::WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *community_size, + igraph_vector_int_t *membership, + igraph_matrix_t *adhesion, + igraph_matrix_t *normalised_adhesion, + igraph_real_t *polarization, + double t, + double d_p, + double d_n) { + +#ifdef SPINGLASS_DEBUG + printf("Start writing clusters.\n"); +#endif + //Reassign each community so that we retrieve a community assignment 1 through num_communities + auto *cluster_assign = new igraph_int_t[q + 1]; + for (igraph_int_t i = 0; i <= q; i++) { + cluster_assign[i] = 0; + } + + igraph_int_t num_clusters = 0; + + //Find out what the new communities will be + for (igraph_int_t i = 0; i < num_nodes; i++) { + igraph_int_t s = spin[i]; + if (cluster_assign[s] == 0) { + num_clusters++; + cluster_assign[s] = num_clusters; +#ifdef SPINGLASS_DEBUG + printf("Setting cluster %d to %d.\n", s, num_clusters); +#endif + } + } + + //And now assign each node to its new community + q = num_clusters; + for (igraph_int_t i = 0; i < num_nodes; i++) { +#ifdef SPINGLASS_DEBUG + printf("Setting node %d to %d.\n", i, cluster_assign[spin[i]]); +#endif + igraph_int_t s = cluster_assign[spin[i]]; + spin[i] = s; +#ifdef SPINGLASS_DEBUG + printf("Have set node %d to %d.\n", i, s); +#endif + } + assign_initial_conf(false); + + delete [] cluster_assign; + + if (temperature) { + *temperature = t; + } + + if (community_size) { + //Initialize the vector + IGRAPH_CHECK(igraph_vector_int_resize(community_size, q)); + for (igraph_int_t spin_opt = 1; spin_opt <= q; spin_opt++) { + //Set the community size + VECTOR(*community_size)[spin_opt - 1] = csize[spin_opt]; + } + } + + //Set the membership + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, num_nodes)); + for (igraph_int_t i = 0; i < num_nodes; i++) { + VECTOR(*membership)[ i ] = spin[i] - 1; + } + } + + double Q = 0.0; //Modularity + if (adhesion) { + IGRAPH_CHECK(igraph_matrix_resize(adhesion, q, q)); + IGRAPH_CHECK(igraph_matrix_resize(normalised_adhesion, q, q)); + + double **num_links_pos = nullptr; + double **num_links_neg = nullptr; + //memory allocated for elements of rows. + num_links_pos = new double *[q + 1] ; + num_links_neg = new double *[q + 1] ; + + //memory allocated for elements of each column. + for ( igraph_int_t i = 0 ; i < q + 1 ; i++) { + num_links_pos[i] = new double[q + 1]; + num_links_neg[i] = new double[q + 1]; + } + + + + //Init num_links + for (igraph_int_t i = 0; i <= q; i++) { + for (igraph_int_t j = 0; j <= q; j++) { + num_links_pos[i][j] = 0.0; + num_links_neg[i][j] = 0.0; + } + } + + DLList_Iter iter_l; + const NLink *l_cur = iter_l.First(&net->link_list); + + double w = 0.0; + + while (!iter_l.End()) { + w = l_cur->Get_Weight(); + igraph_int_t a = spin[l_cur->Get_Start()->Get_Index()]; + igraph_int_t b = spin[l_cur->Get_End()->Get_Index()]; + if (w > 0) { + num_links_pos[a][b] += w; + if (!is_directed && a != b) { //Only one edge is defined in case it is undirected + num_links_pos[b][a] += w; + } + } else { + num_links_neg[a][b] -= w; + if (!is_directed && a != b) { //Only one edge is defined in case it is undirected + num_links_neg[b][a] -= w; + } + } + + l_cur = iter_l.Next(); + } //while links + +#ifdef SPINGLASS_DEBUG + printf("d_p: %f\n", d_p); + printf("d_n: %f\n", d_n); +#endif + + double expected = 0.0; + double a = 0.0; + double normal_a = 0.0; + + double delta, u_p, u_n; + double max_expected, max_a; + + //We don't take into account the lambda or gamma for + //computing the modularity and adhesion, since they + //are then incomparable to other definitions. + for (igraph_int_t i = 1; i <= q; i++) { + for (igraph_int_t j = 1; j <= q; j++) { + if (!is_directed && i == j) + expected = degree_community_pos_out[i] * degree_community_pos_in[j] / (m_p == 0 ? 1 : 2 * m_p) + - degree_community_neg_out[i] * degree_community_neg_in[j] / (m_n == 0 ? 1 : 2 * m_n); + else + expected = degree_community_pos_out[i] * degree_community_pos_in[j] / (m_p == 0 ? 1 : m_p) + - degree_community_neg_out[i] * degree_community_neg_in[j] / (m_n == 0 ? 1 : m_n); + + a = (num_links_pos[i][j] - num_links_neg[i][j]) - expected; + + if (i == j) { //cohesion + if (is_directed) { + delta = d_p * csize[i] * (csize[i] - 1); //Maximum amount + } else { + delta = d_p * csize[i] * (csize[i] - 1) / 2; //Maximum amount + } + + u_p = delta - num_links_pos[i][i]; //Add as many positive links we can + u_n = -num_links_neg[i][i]; //Delete as many negative links we can + Q += a; + } else { //adhesion + if (is_directed) { + delta = d_n * csize[i] * csize[j] * 2; //Maximum amount + } else { + delta = d_n * csize[i] * csize[j]; //Maximum amount + } + + u_p = -num_links_pos[i][j]; //Delete as many positive links we can + u_n = delta - num_links_neg[i][j]; //Add as many negative links we can + } + + if (!is_directed && i == j) + max_expected = (degree_community_pos_out[i] + u_p) * (degree_community_pos_in[j] + u_p) / ((m_p + u_p) == 0 ? 1 : 2 * (m_p + u_p)) + - (degree_community_neg_out[i] - u_n) * (degree_community_neg_in[j] + u_n) / ((m_n + u_n) == 0 ? 1 : 2 * (m_n + u_n)); + else + max_expected = (degree_community_pos_out[i] + u_p) * (degree_community_pos_in[j] + u_p) / ((m_p + u_p) == 0 ? 1 : m_p + u_p) + - (degree_community_neg_out[i] - u_n) * (degree_community_neg_in[j] + u_n) / ((m_n + u_n) == 0 ? 1 : m_n + u_n); + max_a = ((num_links_pos[i][j] + u_p) - (num_links_neg[i][j] + u_n)) - max_expected; + + + //In cases where we haven't actually found a ground state + //the adhesion/cohesion *might* not be negative/positive, + //hence the maximum adhesion and cohesion might behave quite + //strangely. In order to prevent that, we limit them to 1 in + //absolute value, and prevent from dividing by zero (even if + //chuck norris would). + if (i == j) { + normal_a = a / (max_a == 0 ? a : max_a); + } else { + normal_a = -a / (max_a == 0 ? a : max_a); + } + + if (normal_a > 1) { + normal_a = 1; + } else if (normal_a < -1) { + normal_a = -1; + } + + MATRIX(*adhesion, i - 1, j - 1) = a; + MATRIX(*normalised_adhesion, i - 1, j - 1) = normal_a; + } //for j + //printf("\n"); + } //for i + + //free the allocated memory + for ( igraph_int_t i = 0 ; i < q + 1 ; i++ ) { + delete [] num_links_pos[i] ; + delete [] num_links_neg[i]; + } + delete [] num_links_pos ; + delete [] num_links_neg ; + + } //adhesion + + if (modularity) { + if (is_directed) { + *modularity = Q / (m_p + m_n); + } else { + *modularity = 2 * Q / (m_p + m_n); //Correction for the way m_p and m_n are counted. Modularity is 1/m, not 1/2m + } + } + + if (polarization) { + double sum_ad = 0.0; + for (igraph_int_t i = 0; i < q; i++) { + for (igraph_int_t j = 0; j < q; j++) { + if (i != j) { + sum_ad -= MATRIX(*normalised_adhesion, i, j); + } + } + } + *polarization = sum_ad / (q * q - q); + } +#ifdef SPINGLASS_DEBUG + printf("Finished writing cluster.\n"); +#endif + return num_nodes; +} diff --git a/src/community/spinglass/pottsmodel_2.h b/src/community/spinglass/pottsmodel_2.h new file mode 100644 index 0000000..a92861f --- /dev/null +++ b/src/community/spinglass/pottsmodel_2.h @@ -0,0 +1,164 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + This file was modified by Vincent Traag + The original copyright notice follows here */ + +/*************************************************************************** + pottsmodel.h - description + ------------------- + begin : Fri May 28 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef POTTSMODEL_H +#define POTTSMODEL_H + +#include "NetDataTypes.h" + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +// Simple matrix class with heap allocation, allowing mat[i][j] indexing. +class SimpleMatrix { + double *data; + const size_t n; + +public: + explicit SimpleMatrix(size_t n_) : n(n_) { data = new double[n*n]; } + SimpleMatrix(const SimpleMatrix &) = delete; + ~SimpleMatrix() { delete [] data; } + + // Return a pointer to the i'th column, which can be indexed into using a second [] operator. + // We assume column-major storage. + double *operator [] (size_t i) { return &(data[n*i]); } +}; + +class PottsModel { +private: + //these lists are needed to keep track of spin states for parallel update mode + DL_Indexed_List new_spins; + DL_Indexed_List previous_spins; + + HugeArray*> correlation; + network *net; + igraph_int_t q; + unsigned int operation_mode; + SimpleMatrix Qmatrix; + double* Qa; + double* weights; + double total_degree_sum; + igraph_int_t num_of_nodes; + igraph_int_t num_of_links; + igraph_int_t k_max = 0; + double acceptance = 0; + double* neighbours; + double* color_field; +public: + PottsModel(network *net, igraph_int_t q, int norm_by_degree); + ~PottsModel(); + + igraph_int_t assign_initial_conf(igraph_int_t spin); + + double initialize_Qmatrix(); + double calculate_Q(); + + double FindStartTemp(double gamma, double prob, double ts); + igraph_int_t HeatBathParallelLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps); + double HeatBathLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps); + igraph_int_t HeatBathParallelLookup(double gamma, double prob, double kT, unsigned int max_sweeps); + double HeatBathLookup(double gamma, double prob, double kT, unsigned int max_sweeps); + + igraph_int_t WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *csize, igraph_vector_int_t *membership, + double kT, double gamma) const; + + double FindCommunityFromStart(double gamma, const char *nodename, + igraph_vector_int_t *result, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_real_t *inner_links, + igraph_real_t *outer_links) const; +}; + + +class PottsModelN { +private: + HugeArray*> correlation; + network *net; + + igraph_int_t q; //number of communities + double m_p; //number of positive ties (or sum of degrees), this equals the number of edges only if it is undirected and each edge has a weight of 1 + double m_n; //number of negative ties (or sum of degrees) + igraph_int_t num_nodes; //number of nodes + bool is_directed; + + bool is_init = false; + + double *degree_pos_in = nullptr; //Postive indegree of the nodes (or sum of weights) + double *degree_neg_in = nullptr; //Negative indegree of the nodes (or sum of weights) + double *degree_pos_out = nullptr; //Postive outdegree of the nodes (or sum of weights) + double *degree_neg_out = nullptr; //Negative outdegree of the nodes (or sum of weights) + + double *degree_community_pos_in = nullptr; //Positive sum of indegree for communities + double *degree_community_neg_in = nullptr; //Negative sum of indegree for communities + double *degree_community_pos_out = nullptr; //Positive sum of outegree for communities + double *degree_community_neg_out = nullptr; //Negative sum of outdegree for communities + + igraph_int_t *csize = nullptr; //The number of nodes in each community + igraph_int_t *spin = nullptr; //The membership of each node + + double *neighbours = nullptr; //Array of neighbours of a vertex in each community + double *weights = nullptr; //Weights of all possible transitions to another community + +public: + PottsModelN(network *n, igraph_int_t num_communities, bool directed); + ~PottsModelN(); + void assign_initial_conf(bool init_spins); + double FindStartTemp(double gamma, double lambda, double ts); + double HeatBathLookup(double gamma, double lambda, double t, unsigned int max_sweeps); + igraph_int_t WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_int_t *community_size, + igraph_vector_int_t *membership, + igraph_matrix_t *adhesion, + igraph_matrix_t *normalised_adhesion, + igraph_real_t *polarization, + double t, + double d_p, + double d_n); +}; + +#endif diff --git a/src/community/voronoi.c b/src/community/voronoi.c new file mode 100644 index 0000000..fc3d0d7 --- /dev/null +++ b/src/community/voronoi.c @@ -0,0 +1,645 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_community.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_nongraph.h" +#include "igraph_paths.h" +#include "igraph_structural.h" +#include "igraph_transitivity.h" + +#include "core/indheap.h" + +/** + * Unweighted local relative density for some vertices. + * + * This function ignores self-loops and edge multiplicities. + * For isolated vertices, zero is returned. + * + * \param graph The input graph. + * \param res Pointer to a vector, the result will be stored here. + * \param vs Vertex selector, the vertices for which to perform the calculation. + * \return Error code. + * + * Time complexity: TODO. + */ +static igraph_error_t igraph_i_local_relative_density(const igraph_t *graph, igraph_vector_t *res, igraph_vs_t vs) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t vs_size; + igraph_vector_int_t nei_mask; /* which nodes are in the local neighbourhood? */ + igraph_vector_int_t nei_done; /* which local nodes have already been processed? -- avoids duplicate processing in multigraphs */ + igraph_lazy_adjlist_t al; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_mask, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_done, no_of_nodes); + + IGRAPH_CHECK(igraph_vit_create(graph, vs, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + vs_size = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_resize(res, vs_size)); + + for (igraph_int_t i=0; ! IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t w = IGRAPH_VIT_GET(vit); + igraph_int_t int_count = 0, ext_count = 0; + + igraph_vector_int_t *w_neis = igraph_lazy_adjlist_get(&al, w); + IGRAPH_CHECK_OOM(w_neis, "Cannot calculate local relative density."); + + igraph_int_t dw = igraph_vector_int_size(w_neis); + + /* mark neighbours of w, as well as w itself */ + for (igraph_int_t j=0; j < dw; ++j) { + VECTOR(nei_mask)[ VECTOR(*w_neis)[j] ] = i + 1; + } + VECTOR(nei_mask)[w] = i + 1; + + /* all incident edges of w are internal */ + int_count += dw; + VECTOR(nei_done)[w] = i + 1; + + for (igraph_int_t j=0; j < dw; ++j) { + igraph_int_t v = VECTOR(*w_neis)[j]; + + if (VECTOR(nei_done)[v] == i + 1) { + continue; + } else { + VECTOR(nei_done)[v] = i + 1; + } + + igraph_vector_int_t *v_neis = igraph_lazy_adjlist_get(&al, v); + IGRAPH_CHECK_OOM(v_neis, "Cannot calculate local relative density."); + + igraph_int_t dv = igraph_vector_int_size(v_neis); + + for (igraph_int_t k=0; k < dv; ++k) { + igraph_int_t u = VECTOR(*v_neis)[k]; + + if (VECTOR(nei_mask)[u] == i + 1) { + int_count += 1; + } else { + ext_count += 1; + } + } + } + + IGRAPH_ASSERT(int_count % 2 == 0); + int_count /= 2; + + VECTOR(*res)[i] = int_count == 0 ? 0.0 : (igraph_real_t) int_count / (igraph_real_t) (int_count + ext_count); + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&nei_done); + igraph_vector_int_destroy(&nei_mask); + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/* Weighted local density: we simply multiply the unweighted local relative density with the undirected strength. */ +static igraph_error_t weighted_local_density(const igraph_t *graph, igraph_vector_t *res, const igraph_vector_t *weights) { + igraph_vector_t str; + + IGRAPH_CHECK(igraph_i_local_relative_density(graph, res, igraph_vss_all())); + + IGRAPH_VECTOR_INIT_FINALLY(&str, igraph_vcount(graph)); + + IGRAPH_CHECK(igraph_strength(graph, &str, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS, weights)); + + igraph_vector_mul(res, &str); + + igraph_vector_destroy(&str); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * Chooses the generated points for the Voronoi partitioning. + * + * Each generator has the highest local density within a radius \p r around it. + * + * Additionally, if rmax != NULL, the longest distance reached will be stored here. + * This may be smaller than \p r. This feature is used to determine the largest r + * value worth considering, through calling this function with r = INFINITY. + */ +static igraph_error_t choose_generators( + const igraph_t *graph, + igraph_vector_int_t *generators, + igraph_real_t *rmax, + const igraph_vector_t *local_rel_dens, + const igraph_vector_t *lengths, + igraph_neimode_t mode, + igraph_real_t r) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t ord; + igraph_bitset_t excluded; + igraph_int_t excluded_count; + igraph_inclist_t il; + igraph_2wheap_t q; + igraph_real_t radius_max; + + /* ord[i] is the index of the ith largest element of local_rel_dens */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&ord, 0); + IGRAPH_CHECK(igraph_vector_sort_ind(local_rel_dens, &ord, IGRAPH_DESCENDING)); + + /* If excluded[v] is true, then v is closer to some already chosen generator than r */ + IGRAPH_BITSET_INIT_FINALLY(&excluded, no_of_nodes); + excluded_count = 0; + + /* The input graph is expected to be simple, but we still set IGRAPH_LOOPS, + * as inclist_init() performs better this way. */ + IGRAPH_CHECK(igraph_inclist_init(graph, &il, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_CHECK(igraph_2wheap_init(&q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &q); + + radius_max = -IGRAPH_INFINITY; + igraph_vector_int_clear(generators); + for (igraph_int_t i=0; i < no_of_nodes; i++) { + igraph_int_t g = VECTOR(ord)[i]; + + if (IGRAPH_BIT_TEST(excluded, g)) continue; + + IGRAPH_CHECK(igraph_vector_int_push_back(generators, g)); + + igraph_2wheap_clear(&q); + IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, g, -0.0)); + while (!igraph_2wheap_empty(&q)) { + igraph_int_t vid = igraph_2wheap_max_index(&q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&q); + + /* Exceeded cutoff distance, do not search further along this path. */ + if (mindist > r) continue; + + /* Note: We cannot stop the search after hitting an excluded vertex + * because it is possible that another non-excluded one is reachable only + * through this one. */ + if (! IGRAPH_BIT_TEST(excluded, vid)) { + IGRAPH_BIT_SET(excluded, vid); + excluded_count++; + } + + if (mindist > radius_max) { + radius_max = mindist; + } + + igraph_vector_int_t *inc_edges = igraph_inclist_get(&il, vid); + igraph_int_t inc_count = igraph_vector_int_size(inc_edges); + for (igraph_int_t j=0; j < inc_count; j++) { + igraph_int_t edge = VECTOR(*inc_edges)[j]; + igraph_real_t weight = VECTOR(*lengths)[edge]; + + /* Optimization: do not follow infinite-length edges. */ + if (weight == IGRAPH_INFINITY) { + continue; + } + + igraph_int_t to = IGRAPH_OTHER(graph, edge, vid); + igraph_real_t altdist = mindist + weight; + + if (!igraph_2wheap_has_elem(&q, to)) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, to, -altdist)); + } else if (igraph_2wheap_has_active(&q, to)) { + igraph_real_t curdist = -igraph_2wheap_get(&q, to); + if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(&q, to, -altdist); + } + } + } + } + + /* All vertices have been excluded, no need to search further. */ + if (excluded_count == no_of_nodes) break; + } + + if (rmax) { + *rmax = radius_max; + } + + igraph_2wheap_destroy(&q); + igraph_inclist_destroy(&il); + igraph_bitset_destroy(&excluded); + igraph_vector_int_destroy(&ord); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/* Find the smallest and largest reasonable values of r to consider for the purpose + * of choosing generator points. */ +static igraph_error_t estimate_minmax_r( + const igraph_t *graph, + const igraph_vector_t *local_rel_dens, + const igraph_vector_t *lengths, + igraph_neimode_t mode, + igraph_real_t *minr, igraph_real_t *maxr) { + + igraph_vector_int_t generators; + + /* As minimum distance, we use the shortest edge length. This may be shorter than the shortest + * incident edge of a generator point, but underestimating the minimum distance does not affect + * the radius optimization negatively. */ + *minr = igraph_vector_min(lengths); + + /* To determine the maximum distance, we run a generator selection with r=INFINITY, + * and record the longest actual distance encountered in the process. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&generators, 0); + IGRAPH_CHECK(choose_generators(graph, &generators, maxr, local_rel_dens, lengths, mode, IGRAPH_INFINITY)); + igraph_vector_int_destroy(&generators); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +typedef igraph_error_t optfun_t(double x, double *res, void *extra); + +/* This is the coefficient of the second order part when fitting a quadratic + * polynomial to the points given in the argument. */ +static igraph_real_t coeff2( + igraph_real_t x1, igraph_real_t x2, igraph_real_t x3, + igraph_real_t f1, igraph_real_t f2, igraph_real_t f3) { + igraph_real_t num = x1*(f3 - f2) + x2*(f1 - f3) + x3*(f2 - f1); + igraph_real_t denom = (x1 - x2)*(x1 - x3)*(x2 - x3); + return num / denom; +} + +/* Given the stationary point of the quadratic fit to the given points */ +static igraph_real_t peakx( + igraph_real_t x1, igraph_real_t x2, igraph_real_t x3, + igraph_real_t f1, igraph_real_t f2, igraph_real_t f3) { + igraph_real_t x1s = x1*x1, x2s = x2*x2, x3s = x3*x3; + igraph_real_t num = f3 * (x1s - x2s) + f1 * (x2s - x3s) + f2 * (x3s - x1s); + igraph_real_t denom = f3 * (x1 - x2) + f1 * (x2 - x3) + f2 * (x3 - x1); + return 0.5 * num / denom; +} + +/** + * Simple Brent's method optimizer, with some specializations for the use + * case at hand (see code comments). It must be called with x2 > x1. + * The optimal argument is the last one for which f() is invoked. + * f() is expected to record this in 'extra'. + */ +static igraph_error_t brent_opt(optfun_t *f, igraph_real_t x1, igraph_real_t x2, void *extra) { + igraph_real_t lo = x1, hi = x2; + + IGRAPH_ASSERT(isfinite(lo)); + IGRAPH_ASSERT(isfinite(hi)); + + /* We choose the initial x3 point to be closer to x1 than x2. + * This is so that if f1 == f2, the next computed point (newx) + * would not coincide with x3. */ + igraph_real_t x3 = 0.6*x1 + 0.4*x2; + igraph_real_t f1, f2, f3; + + IGRAPH_CHECK(f(x1, &f1, extra)); + + /* Catch special case that would wreak havoc in the optimizer. */ + if (x1 == x2) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(f(x2, &f2, extra)); + IGRAPH_CHECK(f(x3, &f3, extra)); + + /* We expect that the middle point, f3, is greater than the boundary points. */ + + /* Currently, we do not handle the case when f3 < f1. */ + if (f1 > f3) { + IGRAPH_ERROR("Optimizer did not converge while maximizing modularity for Voronoi communities.", + IGRAPH_DIVERGED); + } + + /* It sometimes happens in disconnected graphs that the maximum is reached at or near the + * top of the radius range. If so, we bisect the (x3, x2) interval to search for a configuration + * where f3 >= f2. */ + if (f2 > f3) { + /* Limit iterations to 'maxiter'. */ + const int maxiter = 10; + int i; + for (i=0; i < maxiter; ++i) { + x1 = x3; f1 = f3; + x3 = 0.5 * (x1 + x2); + IGRAPH_CHECK(f(x3, &f3, extra)); + + if (f3 >= f2) break; + } + /* If no maximum was found in 'maxiter' bisections, just take the upper end of the range. */ + if (i == maxiter) { + IGRAPH_CHECK(f(x2, &f2, extra)); + return IGRAPH_SUCCESS; + } + } + + /* Limit iterations to 20 */ + for (int i=0; i < 20; ++i) { + igraph_real_t newx, newf; + + newx = peakx(x1, x2, x3, f1, f2, f3); + IGRAPH_CHECK(f(newx, &newf, extra)); + + /* We need to decide whether we drop (x1, f1) or (x2, f2) for the following iterations. + * The sign of a1 (or a2) determines whether dropping x1 (or x2) yields a convex or concave + * parabola in the next iteration. We need a negative sign = concave parabola, + * as we are looking for a maximum. We always keep (x3, f3) as it was the last added point. */ + igraph_real_t a1 = coeff2(x2, x3, newx, f2, f3, newf); + igraph_real_t a2 = coeff2(x1, x3, newx, f1, f3, newf); + + /* We cannot continue without the Brent optimizer switching to minimization. + * Terminate search, accepting the current result. */ + if (a1 >= 0 && a2 >= 0) { + break; + } + + if (a1 <= a2) { + x1 = x2; + x2 = x3; + x3 = newx; + + f1 = f2; + f2 = f3; + f3 = newf; + } else { + x2 = x1; + x1 = x3; + x3 = newx; + + f2 = f1; + f1 = f3; + f3 = newf; + } + + /* Check if value goes out of initial interval. */ + if (x3 < lo || x3 > hi) { + IGRAPH_ERROR("Optimizer did not converge while maximizing modularity for Voronoi communities.", + IGRAPH_DIVERGED); + } + + /* We exploit the fact that we are optimizing a discrete valued function, and we can + * detect convergence by checking that the function value stays exactly the same. + * + * As an optimization, we only check whether the two of the three f values are the same. + * Almost always, when this is the case, another iteration would not yield a better + * maximum, however, saving a call to f() improves performance noticeably. + */ + const igraph_real_t eps = 1e-10; + int c1 = igraph_cmp_epsilon(f1, f3, eps); + int c2 = igraph_cmp_epsilon(f2, f3, eps); + if (c1 == 0 || c2 == 0) { + break; + } + } + + return IGRAPH_SUCCESS; +} + + +/* Work data for get_modularity() */ +typedef struct { + const igraph_t *graph; + const igraph_vector_t *local_dens; + const igraph_vector_t *lengths; + const igraph_vector_t *weights; + igraph_neimode_t mode; + igraph_vector_int_t *generators; + igraph_vector_int_t *membership; + igraph_real_t modularity; +} get_modularity_work_t; + + +/* Objective function used with brent_opt(), it computes the modularity for a given radius. */ +static igraph_error_t get_modularity(igraph_real_t r, igraph_real_t *modularity, void *extra) { + get_modularity_work_t *gm = extra; + + IGRAPH_CHECK(choose_generators(gm->graph, gm->generators, NULL, + gm->local_dens, gm->lengths, gm->mode, + r)); + IGRAPH_CHECK(igraph_voronoi(gm->graph, gm->membership, NULL, + gm->generators, gm->lengths, + gm->mode, IGRAPH_VORONOI_RANDOM)); + IGRAPH_CHECK(igraph_modularity(gm->graph, gm->membership, gm->weights, + 1, gm->mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED, + &gm->modularity)); + *modularity = gm->modularity; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_community_voronoi + * \brief Finds communities using Voronoi partitioning. + * + * \experimental + * + * This function finds communities using a Voronoi partitioning of vertices based + * on the given edge lengths divided by the edge clustering coefficient + * (\ref igraph_ecc()). The generator vertices are chosen to be those with the + * largest local relative density within a radius \p r, with the local relative + * density of a vertex defined as + * s m / (m + k), where \c s is the strength of the vertex, + * \c m is the number of edges within the vertex's first order neighborhood, + * while \c k is the number of edges with only one endpoint within this + * neighborhood. + * + * + * References: + * + * + * Deritei et al., Community detection by graph Voronoi diagrams, + * New Journal of Physics 16, 063007 (2014) + * https://doi.org/10.1088/1367-2630/16/6/063007 + * + * + * Molnár et al., Community Detection in Directed Weighted Networks using Voronoi Partitioning, + * Scientific Reports 14, 8124 (2024) + * https://doi.org/10.1038/s41598-024-58624-4 + * + * \param graph The input graph. It must be simple. + * \param membership If not \c NULL, the membership of each vertex is returned here. + * \param generators If not \c NULL, the generator points used for Voronoi partitioning are returned here. + * \param modularity If not \c NULL, the modularity score of the partitioning is returned here. + * \param lengths Edge lengths, or \c NULL to consider all edges as having unit length. + * Voronoi partitioning will use edge lengths equal to lengths / ECC where ECC is the edge + * clustering coefficient. + * \param weights Edge weights, or \c NULL to consider all edges as having unit weight. + * Weights are used when selecting generator points, as well as for computing modularity. + * \param mode If \c IGRAPH_OUT, distances from generator points to all other nodes are considered. + * If \c IGRAPH_IN, the reverse distances are used. If \c IGRAPH_ALL, edge directions are ignored. + * This parameter is ignored for undirected graphs. + * \param r The radius/resolution to use when selecting generator points. The larger this value, the + * fewer partitions there will be. Pass in a negative value to automatically select the radius + * that maximizes modularity. + * \return Error code. + * + * \sa \ref igraph_voronoi(), \ref igraph_ecc(). + * + * Time complexity: TODO. + */ +igraph_error_t igraph_community_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, igraph_vector_int_t *generators, + igraph_real_t *modularity, + const igraph_vector_t *lengths, const igraph_vector_t *weights, + igraph_neimode_t mode, igraph_real_t r) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_t local_rel_dens; + igraph_vector_t lengths2; /* lengths2 = lengths / ecc */ + igraph_vector_int_t imembership, igenerators; + igraph_vector_int_t *pmembership, *pgenerators; + igraph_bool_t simple; + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (lengths && igraph_vector_size(lengths) != no_of_edges) { + IGRAPH_ERROR("Edge length vector size does not match edge count.", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Edge length vector size does not match edge count.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &simple, mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED)); + if (! simple) { + IGRAPH_ERROR("The graph must be simple for Voronoi communities.", IGRAPH_EINVAL); + } + + if (no_of_edges == 0) { + /* Also handles no_of_nodes <= 1 */ + if (membership) { + IGRAPH_CHECK(igraph_vector_int_range(membership, 0, no_of_nodes)); + } + if (generators) { + IGRAPH_CHECK(igraph_vector_int_range(generators, 0, no_of_nodes)); + } + return IGRAPH_SUCCESS; + } + + if (! generators) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&igenerators, no_of_nodes); + pgenerators = &igenerators; + } else { + pgenerators = generators; + } + + if (! membership) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&imembership, no_of_nodes); + pmembership = &imembership; + } else { + pmembership = membership; + } + + if (lengths) { + igraph_real_t m = igraph_vector_min(lengths); + if (isnan(m)) { + IGRAPH_ERROR("Edge lengths must not be NaN.", IGRAPH_EINVAL); + } + if (m < 0) { + IGRAPH_ERROR("Edge lengths must be non-negative.", IGRAPH_EINVAL); + } + } + + if (weights) { + igraph_real_t m = igraph_vector_min(weights); + if (isnan(m)) { + IGRAPH_ERROR("Edge weights must not be NaN.", IGRAPH_EINVAL); + } + if (m <= 0) { + IGRAPH_ERROR("Edge weights must be positive.", IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&local_rel_dens, 0); + + IGRAPH_CHECK(weighted_local_density(graph, &local_rel_dens, weights)); + + IGRAPH_VECTOR_INIT_FINALLY(&lengths2, 0); + IGRAPH_CHECK(igraph_ecc(graph, &lengths2, igraph_ess_all(IGRAPH_EDGEORDER_ID), 3, true, true)); + + /* Note: ECC is never NaN but it may be Inf */ + for (igraph_int_t i=0; i < no_of_edges; i++) { + VECTOR(lengths2)[i] = 1 / (VECTOR(lengths2)[i]); + } + if (lengths) { + igraph_vector_mul(&lengths2, lengths); + } + + if (r < 0) { + igraph_real_t minr, maxr; + + IGRAPH_CHECK(estimate_minmax_r(graph, &local_rel_dens, &lengths2, mode, &minr, &maxr)); + + get_modularity_work_t gm = { + graph, + &local_rel_dens, + &lengths2, + weights, + mode, + pgenerators, + pmembership, + /* modularity */ IGRAPH_NAN + }; + IGRAPH_CHECK(brent_opt(get_modularity, minr, maxr, &gm)); + if (modularity) { + *modularity = gm.modularity; + } + } else { + IGRAPH_CHECK(choose_generators(graph, pgenerators, NULL, &local_rel_dens, &lengths2, mode, r)); + IGRAPH_CHECK(igraph_voronoi(graph, membership, NULL, pgenerators, &lengths2, mode, IGRAPH_VORONOI_RANDOM)); + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, pmembership, weights, 1, + mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED, modularity)); + } + } + + if (! generators) { + igraph_vector_int_destroy(&igenerators); + IGRAPH_FINALLY_CLEAN(1); + } + if (! membership) { + igraph_vector_int_destroy(&imembership); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&local_rel_dens); + igraph_vector_destroy(&lengths2); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/community/walktrap/walktrap.cpp b/src/community/walktrap/walktrap.cpp new file mode 100644 index 0000000..bd419e4 --- /dev/null +++ b/src/community/walktrap/walktrap.cpp @@ -0,0 +1,233 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: walktrap.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_graph.h" +#include "walktrap_communities.h" + +#include "igraph_community.h" +#include "igraph_components.h" +#include "igraph_interface.h" + +#include "core/exceptions.h" +#include "core/interruption.h" + +#include +#include + +// This is necessary for GCC 5 and earlier, where including +// makes isnan() unusable without the std:: prefix, even if +// was included as well. +using std::isnan; + +using namespace igraph::walktrap; + +/** + * \function igraph_community_walktrap + * \brief Community finding using a random walk based similarity measure. + * + * This function is the implementation of the Walktrap community + * finding algorithm, see Pascal Pons, Matthieu Latapy: Computing + * communities in large networks using random walks, + * https://arxiv.org/abs/physics/0512106 + * + * + * Currently the original C++ implementation is used in igraph, + * see https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html + * We are grateful to Matthieu Latapy and Pascal Pons for providing this + * source code. + * + * + * In contrast to the original implementation, isolated vertices are allowed + * in the graph and they are assumed to have a single incident loop edge with + * weight 1. + * + * \param graph The input graph, edge directions are ignored. + * \param weights Numeric vector giving the weights of the edges. + * If it is a NULL pointer then all edges will have equal + * weights. The weights are expected to be positive. + * \param steps Integer constant, the length of the random walks. + * Typically, good results are obtained with values between + * 3-8 with 4-5 being a reasonable default. + * \param merges Pointer to a matrix, the merges performed by the + * algorithm will be stored here (if not \c NULL). Each merge is a + * row in a two-column matrix and contains the IDs of the merged + * clusters. Clusters are numbered from zero and cluster numbers + * smaller than the number of nodes in the network belong to the + * individual vertices as singleton clusters. In each step a new + * cluster is created from two other clusters and its id will be + * one larger than the largest cluster id so far. This means that + * before the first merge we have \c n clusters (the number of + * vertices in the graph) numbered from zero to n - 1. + * The first merge creates cluster \c n, the second cluster + * n + 1, etc. + * \param modularity Pointer to a vector. If not \c NULL then the + * modularity score of the current clustering is stored here after + * each merge operation. + * \param membership Pointer to a vector. If not a \c NULL pointer, then + * the membership vector corresponding to the maximal modularity + * score is stored here. + * \return Error code. + * + * \sa \ref igraph_community_spinglass(), \ref + * igraph_community_edge_betweenness(). + * + * Time complexity: O(|E||V|^2) in the worst case, O(|V|^2 log|V|) typically, + * |V| is the number of vertices, |E| is the number of edges. + * + * \example examples/simple/walktrap.c + */ + +igraph_error_t igraph_community_walktrap(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_int_t steps, + igraph_matrix_int_t *merges, + igraph_vector_t *modularity, + igraph_vector_int_t *membership) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t comp_count; + igraph_matrix_int_t imerges, *pmerges = merges; + igraph_vector_t imodularity, *pmodularity = modularity; + + if (steps <= 0) { + IGRAPH_ERROR("Length of random walks must be positive for walktrap community detection.", IGRAPH_EINVAL); + } + + if (steps > INT_MAX) { + IGRAPH_ERROR("Length of random walks too large for walktrap community detection.", IGRAPH_EINVAL); + } + + int length = steps; + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + + if (membership) { + /* We need both 'modularity' and 'merges' to compute 'membership'. + * If they were not provided by the called, we allocate these here. */ + + if (! modularity) { + IGRAPH_VECTOR_INIT_FINALLY(&imodularity, 0); + pmodularity = &imodularity; + } + + if (! merges) { + IGRAPH_MATRIX_INT_INIT_FINALLY(&imerges, 0, 0); + pmerges = &imerges; + } + } + + IGRAPH_HANDLE_EXCEPTIONS( + Graph G; + IGRAPH_CHECK(G.convert_from_igraph(graph, weights)); + + if (pmerges || pmodularity) { + IGRAPH_CHECK(igraph_connected_components(graph, /*membership=*/ NULL, /*csize=*/ NULL, + &comp_count, IGRAPH_WEAK)); + } + if (pmerges) { + IGRAPH_CHECK(igraph_matrix_int_resize(pmerges, no_of_nodes - comp_count, 2)); + } + if (pmodularity) { + IGRAPH_CHECK(igraph_vector_resize(pmodularity, no_of_nodes - comp_count + 1)); + igraph_vector_null(pmodularity); + } + Communities C(&G, length, pmerges, pmodularity); + + while (!C.H->is_empty()) { + IGRAPH_ALLOW_INTERRUPTION(); + C.merge_nearest_communities(); + } + ); + + if (membership) { + igraph_int_t m; + m = no_of_nodes > 0 ? igraph_vector_which_max(pmodularity) : 0; + IGRAPH_CHECK(igraph_community_to_membership(pmerges, no_of_nodes, + /*steps=*/ m, + membership, + /*csize=*/ NULL)); + + if (! merges) { + igraph_matrix_int_destroy(&imerges); + IGRAPH_FINALLY_CLEAN(1); + } + if (! modularity) { + igraph_vector_destroy(&imodularity); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* The walktrap implementation cannot work with NaN values internally, + * and produces 0 for the modularity of edgeless graphs. We correct + * this to NaN in the last step for consistency. */ + if (modularity && no_of_edges == 0) { + VECTOR(*modularity)[0] = IGRAPH_NAN; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/community/walktrap/walktrap_communities.cpp b/src/community/walktrap/walktrap_communities.cpp new file mode 100644 index 0000000..4435351 --- /dev/null +++ b/src/community/walktrap/walktrap_communities.cpp @@ -0,0 +1,785 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: communities.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_communities.h" +#include "config.h" /* IGRAPH_THREAD_LOCAL */ +#include +#include + +using namespace std; + +namespace igraph { + +namespace walktrap { + +IGRAPH_THREAD_LOCAL int Probabilities::length = 0; +IGRAPH_THREAD_LOCAL Communities* Probabilities::C = nullptr; +IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector1 = nullptr; +IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector2 = nullptr; +IGRAPH_THREAD_LOCAL int* Probabilities::id = nullptr; +IGRAPH_THREAD_LOCAL int* Probabilities::vertices1 = nullptr; +IGRAPH_THREAD_LOCAL int* Probabilities::vertices2 = nullptr; +IGRAPH_THREAD_LOCAL int Probabilities::current_id = 0; + + +Neighbor::Neighbor() { + next_community1 = nullptr; + previous_community1 = nullptr; + next_community2 = nullptr; + previous_community2 = nullptr; + heap_index = -1; +} + +Probabilities::~Probabilities() { + delete[] P; + delete[] vertices; +} + +Probabilities::Probabilities(int community) { + Graph* G = C->G; + int nb_vertices1 = 0; + int nb_vertices2 = 0; + + double initial_proba = 1. / static_cast(C->communities[community].size); + int last = C->members[C->communities[community].last_member]; + for (int m = C->communities[community].first_member; m != last; m = C->members[m]) { + tmp_vector1[m] = initial_proba; + vertices1[nb_vertices1++] = m; + } + + for (int t = 0; t < length; t++) { + current_id++; + if (nb_vertices1 > (G->nb_vertices / 2)) { + nb_vertices2 = G->nb_vertices; + for (int i = 0; i < G->nb_vertices; i++) { + tmp_vector2[i] = 0.; + } + if (nb_vertices1 == G->nb_vertices) { + for (int i = 0; i < G->nb_vertices; i++) { + double proba = tmp_vector1[i] / G->vertices[i].total_weight; + for (int j = 0; j < G->vertices[i].degree; j++) { + tmp_vector2[G->vertices[i].edges[j].neighbor] += proba * G->vertices[i].edges[j].weight; + } + } + } else { + for (int i = 0; i < nb_vertices1; i++) { + int v1 = vertices1[i]; + double proba = tmp_vector1[v1] / G->vertices[v1].total_weight; + for (int j = 0; j < G->vertices[v1].degree; j++) { + tmp_vector2[G->vertices[v1].edges[j].neighbor] += proba * G->vertices[v1].edges[j].weight; + } + } + } + } else { + nb_vertices2 = 0; + for (int i = 0; i < nb_vertices1; i++) { + int v1 = vertices1[i]; + double proba = tmp_vector1[v1] / G->vertices[v1].total_weight; + for (int j = 0; j < G->vertices[v1].degree; j++) { + int v2 = G->vertices[v1].edges[j].neighbor; + if (id[v2] == current_id) { + tmp_vector2[v2] += proba * G->vertices[v1].edges[j].weight; + } else { + tmp_vector2[v2] = proba * G->vertices[v1].edges[j].weight; + id[v2] = current_id; + vertices2[nb_vertices2++] = v2; + } + } + } + } + double* tmp = tmp_vector2; + tmp_vector2 = tmp_vector1; + tmp_vector1 = tmp; + + int* tmp2 = vertices2; + vertices2 = vertices1; + vertices1 = tmp2; + + nb_vertices1 = nb_vertices2; + } + + if (nb_vertices1 > (G->nb_vertices / 2)) { + P = new double[G->nb_vertices]; + size = G->nb_vertices; + vertices = nullptr; + if (nb_vertices1 == G->nb_vertices) { + for (int i = 0; i < G->nb_vertices; i++) { + P[i] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight); + } + } else { + for (int i = 0; i < G->nb_vertices; i++) { + P[i] = 0.; + } + for (int i = 0; i < nb_vertices1; i++) { + P[vertices1[i]] = tmp_vector1[vertices1[i]] / sqrt(G->vertices[vertices1[i]].total_weight); + } + } + } else { + P = new double[nb_vertices1]; + size = nb_vertices1; + vertices = new int[nb_vertices1]; + int j = 0; + for (int i = 0; i < G->nb_vertices; i++) { + if (id[i] == current_id) { + P[j] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight); + vertices[j] = i; + j++; + } + } + } +} + +Probabilities::Probabilities(int community1, int community2) { + // The two following probability vectors must exist. + // Do not call this function if it is not the case. + Probabilities* P1 = C->communities[community1].P; + Probabilities* P2 = C->communities[community2].P; + + double w1 = C->communities[community1].size / static_cast(C->communities[community1].size + C->communities[community2].size); + double w2 = C->communities[community2].size / static_cast(C->communities[community1].size + C->communities[community2].size); + + + if (P1->size == C->G->nb_vertices) { + P = new double[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = nullptr; + + if (P2->size == C->G->nb_vertices) { // two full vectors + for (int i = 0; i < C->G->nb_vertices; i++) { + P[i] = P1->P[i] * w1 + P2->P[i] * w2; + } + } else { // P1 full vector, P2 partial vector + int j = 0; + for (int i = 0; i < P2->size; i++) { + for (; j < P2->vertices[i]; j++) { + P[j] = P1->P[j] * w1; + } + P[j] = P1->P[j] * w1 + P2->P[i] * w2; + j++; + } + for (; j < C->G->nb_vertices; j++) { + P[j] = P1->P[j] * w1; + } + } + } else { + if (P2->size == C->G->nb_vertices) { // P1 partial vector, P2 full vector + P = new double[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = nullptr; + + int j = 0; + for (int i = 0; i < P1->size; i++) { + for (; j < P1->vertices[i]; j++) { + P[j] = P2->P[j] * w2; + } + P[j] = P1->P[i] * w1 + P2->P[j] * w2; + j++; + } + for (; j < C->G->nb_vertices; j++) { + P[j] = P2->P[j] * w2; + } + } else { // two partial vectors + int i = 0; + int j = 0; + int nb_vertices1 = 0; + while ((i < P1->size) && (j < P2->size)) { + if (P1->vertices[i] < P2->vertices[j]) { + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1; + vertices1[nb_vertices1++] = P1->vertices[i]; + i++; + continue; + } + if (P1->vertices[i] > P2->vertices[j]) { + tmp_vector1[P2->vertices[j]] = P2->P[j] * w2; + vertices1[nb_vertices1++] = P2->vertices[j]; + j++; + continue; + } + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1 + P2->P[j] * w2; + vertices1[nb_vertices1++] = P1->vertices[i]; + i++; + j++; + } + if (i == P1->size) { + for (; j < P2->size; j++) { + tmp_vector1[P2->vertices[j]] = P2->P[j] * w2; + vertices1[nb_vertices1++] = P2->vertices[j]; + } + } else { + for (; i < P1->size; i++) { + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1; + vertices1[nb_vertices1++] = P1->vertices[i]; + } + } + + if (nb_vertices1 > (C->G->nb_vertices / 2)) { + P = new double[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = nullptr; + for (int i = 0; i < C->G->nb_vertices; i++) { + P[i] = 0.; + } + for (int i = 0; i < nb_vertices1; i++) { + P[vertices1[i]] = tmp_vector1[vertices1[i]]; + } + } else { + P = new double[nb_vertices1]; + size = nb_vertices1; + vertices = new int[nb_vertices1]; + for (int i = 0; i < nb_vertices1; i++) { + vertices[i] = vertices1[i]; + P[i] = tmp_vector1[vertices1[i]]; + } + } + } + } +} + +double Probabilities::compute_distance(const Probabilities* P2) const { + double r = 0.0; + if (vertices) { + if (P2->vertices) { // two partial vectors + int i = 0; + int j = 0; + while ((i < size) && (j < P2->size)) { + if (vertices[i] < P2->vertices[j]) { + r += P[i] * P[i]; + i++; + continue; + } + if (vertices[i] > P2->vertices[j]) { + r += P2->P[j] * P2->P[j]; + j++; + continue; + } + r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]); + i++; + j++; + } + if (i == size) { + for (; j < P2->size; j++) { + r += P2->P[j] * P2->P[j]; + } + } else { + for (; i < size; i++) { + r += P[i] * P[i]; + } + } + } else { // P1 partial vector, P2 full vector + + int i = 0; + for (int j = 0; j < size; j++) { + for (; i < vertices[j]; i++) { + r += P2->P[i] * P2->P[i]; + } + r += (P[j] - P2->P[i]) * (P[j] - P2->P[i]); + i++; + } + for (; i < P2->size; i++) { + r += P2->P[i] * P2->P[i]; + } + } + } else { + if (P2->vertices) { // P1 full vector, P2 partial vector + int i = 0; + for (int j = 0; j < P2->size; j++) { + for (; i < P2->vertices[j]; i++) { + r += P[i] * P[i]; + } + r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]); + i++; + } + for (; i < size; i++) { + r += P[i] * P[i]; + } + } else { // two full vectors + for (int i = 0; i < size; i++) { + r += (P[i] - P2->P[i]) * (P[i] - P2->P[i]); + } + } + } + return r; +} + +Community::Community() { + P = nullptr; + first_neighbor = nullptr; + last_neighbor = nullptr; + sub_community_of = -1; + sub_communities[0] = -1; + sub_communities[1] = -1; + sigma = 0.; + internal_weight = 0.; + total_weight = 0.; +} + +Community::~Community() { + delete P; +} + + +Communities::Communities(Graph* graph, int random_walks_length, + igraph_matrix_int_t *pmerges, + igraph_vector_t *pmodularity) { + G = graph; + merges = pmerges; + mergeidx = 0; + modularity = pmodularity; + + Probabilities::C = this; + Probabilities::length = random_walks_length; + Probabilities::tmp_vector1 = new double[G->nb_vertices]; + Probabilities::tmp_vector2 = new double[G->nb_vertices]; + Probabilities::id = new int[G->nb_vertices]; + for (int i = 0; i < G->nb_vertices; i++) { + Probabilities::id[i] = 0; + } + Probabilities::vertices1 = new int[G->nb_vertices]; + Probabilities::vertices2 = new int[G->nb_vertices]; + Probabilities::current_id = 0; + + members = new int[G->nb_vertices]; + for (int i = 0; i < G->nb_vertices; i++) { + members[i] = -1; + } + + H = new Neighbor_heap(G->nb_edges); + IGRAPH_ASSUME(G->nb_vertices >= 0); // avoid false-positive GCC warnings + communities = new Community[2 * G->nb_vertices]; + + // init the n single vertex communities + + for (int i = 0; i < G->nb_vertices; i++) { + communities[i].this_community = i; + communities[i].first_member = i; + communities[i].last_member = i; + communities[i].size = 1; + communities[i].sub_community_of = 0; + } + + nb_communities = G->nb_vertices; + nb_active_communities = G->nb_vertices; + + for (int i = 0; i < G->nb_vertices; i++) + for (int j = 0; j < G->vertices[i].degree; j++) + if (i < G->vertices[i].edges[j].neighbor) { + communities[i].total_weight += G->vertices[i].edges[j].weight / 2.; + communities[G->vertices[i].edges[j].neighbor].total_weight += G->vertices[i].edges[j].weight / 2.; + Neighbor* N = new Neighbor; + N->community1 = i; + N->community2 = G->vertices[i].edges[j].neighbor; + N->delta_sigma = -1. / double(min(G->vertices[i].degree, G->vertices[G->vertices[i].edges[j].neighbor].degree)); + N->weight = G->vertices[i].edges[j].weight; + N->exact = false; + add_neighbor(N); + } + + /* int c = 0; */ + Neighbor* N = H->get_first(); + if (N == nullptr) { + return; /* this can happen if there are no edges */ + } + while (!N->exact) { + update_neighbor(N, compute_delta_sigma(N->community1, N->community2)); + N->exact = true; + N = H->get_first(); + /* TODO: this could use igraph_progress */ + /* if(!silent) { */ + /* c++; */ + /* for(int k = (500*(c-1))/G->nb_edges + 1; k <= (500*c)/G->nb_edges; k++) { */ + /* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */ + /* cerr << "."; */ + /* } */ + /* } */ + } + + if (modularity) { + double Q = 0.0; + for (int i = 0; i < nb_communities; i++) { + if (communities[i].sub_community_of == 0) { + Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight); + } + } + Q /= G->total_weight; + VECTOR(*modularity)[mergeidx] = Q; + } +} + +Communities::~Communities() { + delete[] members; + delete[] communities; + delete H; + + delete[] Probabilities::tmp_vector1; + delete[] Probabilities::tmp_vector2; + delete[] Probabilities::id; + delete[] Probabilities::vertices1; + delete[] Probabilities::vertices2; +} + +void Community::add_neighbor(Neighbor* N) { // add a new neighbor at the end of the list + if (last_neighbor) { + if (last_neighbor->community1 == this_community) { + last_neighbor->next_community1 = N; + } else { + last_neighbor->next_community2 = N; + } + + if (N->community1 == this_community) { + N->previous_community1 = last_neighbor; + } else { + N->previous_community2 = last_neighbor; + } + } else { + first_neighbor = N; + if (N->community1 == this_community) { + N->previous_community1 = nullptr; + } else { + N->previous_community2 = nullptr; + } + } + last_neighbor = N; +} + +void Community::remove_neighbor(Neighbor* N) { // remove a neighbor from the list + if (N->community1 == this_community) { + if (N->next_community1) { +// if (N->next_community1->community1 == this_community) + N->next_community1->previous_community1 = N->previous_community1; +// else +// N->next_community1->previous_community2 = N->previous_community1; + } else { + last_neighbor = N->previous_community1; + } + if (N->previous_community1) { + if (N->previous_community1->community1 == this_community) { + N->previous_community1->next_community1 = N->next_community1; + } else { + N->previous_community1->next_community2 = N->next_community1; + } + } else { + first_neighbor = N->next_community1; + } + } else { + if (N->next_community2) { + if (N->next_community2->community1 == this_community) { + N->next_community2->previous_community1 = N->previous_community2; + } else { + N->next_community2->previous_community2 = N->previous_community2; + } + } else { + last_neighbor = N->previous_community2; + } + if (N->previous_community2) { +// if (N->previous_community2->community1 == this_community) +// N->previous_community2->next_community1 = N->next_community2; +// else + N->previous_community2->next_community2 = N->next_community2; + } else { + first_neighbor = N->next_community2; + } + } +} + +void Communities::remove_neighbor(Neighbor* N) { + communities[N->community1].remove_neighbor(N); + communities[N->community2].remove_neighbor(N); + H->remove(N); +} + +void Communities::add_neighbor(Neighbor* N) { + communities[N->community1].add_neighbor(N); + communities[N->community2].add_neighbor(N); + H->add(N); +} + +void Communities::update_neighbor(Neighbor* N, double new_delta_sigma) { + N->delta_sigma = new_delta_sigma; + H->update(N); +} + +void Communities::merge_communities(Neighbor* merge_N) { + int c1 = merge_N->community1; + int c2 = merge_N->community2; + + communities[nb_communities].first_member = communities[c1].first_member; // merge the + communities[nb_communities].last_member = communities[c2].last_member; // two lists + members[communities[c1].last_member] = communities[c2].first_member; // of members + + communities[nb_communities].size = communities[c1].size + communities[c2].size; + communities[nb_communities].this_community = nb_communities; + communities[nb_communities].sub_community_of = 0; + communities[nb_communities].sub_communities[0] = c1; + communities[nb_communities].sub_communities[1] = c2; + communities[nb_communities].total_weight = communities[c1].total_weight + communities[c2].total_weight; + communities[nb_communities].internal_weight = communities[c1].internal_weight + communities[c2].internal_weight + merge_N->weight; + communities[nb_communities].sigma = communities[c1].sigma + communities[c2].sigma + merge_N->delta_sigma; + + communities[c1].sub_community_of = nb_communities; + communities[c2].sub_community_of = nb_communities; + +// update the new probability vector... + + if (communities[c1].P && communities[c2].P) { + communities[nb_communities].P = new Probabilities(c1, c2); + } + + if (communities[c1].P) { + delete communities[c1].P; + communities[c1].P = nullptr; + } + if (communities[c2].P) { + delete communities[c2].P; + communities[c2].P = nullptr; + } + +// update the new neighbors +// by enumerating all the neighbors of c1 and c2 + + Neighbor* N1 = communities[c1].first_neighbor; + Neighbor* N2 = communities[c2].first_neighbor; + + while (N1 && N2) { + int neighbor_community1; + int neighbor_community2; + + if (N1->community1 == c1) { + neighbor_community1 = N1->community2; + } else { + neighbor_community1 = N1->community1; + } + if (N2->community1 == c2) { + neighbor_community2 = N2->community2; + } else { + neighbor_community2 = N2->community1; + } + + if (neighbor_community1 < neighbor_community2) { + Neighbor* tmp = N1; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community1; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size)); //compute_delta_sigma(neighbor_community1, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + + if (neighbor_community2 < neighbor_community1) { + Neighbor* tmp = N2; + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community2; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community2].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community2].size)); //compute_delta_sigma(neighbor_community2, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + + if (neighbor_community1 == neighbor_community2) { + Neighbor* tmp1 = N1; + Neighbor* tmp2 = N2; + bool exact = N1->exact && N2->exact; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp1); + remove_neighbor(tmp2); + Neighbor* N = new Neighbor; + N->weight = tmp1->weight + tmp2->weight; + N->community1 = neighbor_community1; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp1->delta_sigma + double(communities[c2].size + communities[neighbor_community1].size) * tmp2->delta_sigma - double(communities[neighbor_community1].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size)); + N->exact = exact; + delete tmp1; + delete tmp2; + add_neighbor(N); + } + } + + + if (!N1) { + while (N2) { +// double delta_sigma2 = N2->delta_sigma; + int neighbor_community; + if (N2->community1 == c2) { + neighbor_community = N2->community2; + } else { + neighbor_community = N2->community1; + } + Neighbor* tmp = N2; + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + } + if (!N2) { + while (N1) { +// double delta_sigma1 = N1->delta_sigma; + int neighbor_community; + if (N1->community1 == c1) { + neighbor_community = N1->community2; + } else { + neighbor_community = N1->community1; + } + Neighbor* tmp = N1; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + } + + nb_communities++; + nb_active_communities--; +} + +double Communities::merge_nearest_communities() { + Neighbor* N = H->get_first(); + while (!N->exact) { + update_neighbor(N, compute_delta_sigma(N->community1, N->community2)); + N->exact = true; + N = H->get_first(); + } + + double d = N->delta_sigma; + remove_neighbor(N); + + merge_communities(N); + + if (merges) { + MATRIX(*merges, mergeidx, 0) = N->community1; + MATRIX(*merges, mergeidx, 1) = N->community2; + } + + mergeidx++; + + if (modularity) { + double Q = 0.0; + for (int i = 0; i < nb_communities; i++) { + if (communities[i].sub_community_of == 0) { + Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight); + } + } + Q /= G->total_weight; + VECTOR(*modularity)[mergeidx] = Q; + } + + delete N; + + /* This could use igraph_progress */ + /* if(!silent) { */ + /* for(int k = (500*(G->nb_vertices - nb_active_communities - 1))/(G->nb_vertices-1) + 1; k <= (500*(G->nb_vertices - nb_active_communities))/(G->nb_vertices-1); k++) { */ + /* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */ + /* cerr << "."; */ + /* } */ + /* } */ + return d; +} + +double Communities::compute_delta_sigma(int community1, int community2) const { + if (!communities[community1].P) { + communities[community1].P = new Probabilities(community1); + } + if (!communities[community2].P) { + communities[community2].P = new Probabilities(community2); + } + + return communities[community1].P->compute_distance(communities[community2].P) * double(communities[community1].size) * double(communities[community2].size) / double(communities[community1].size + communities[community2].size); +} + +} +} /* end of namespaces */ diff --git a/src/community/walktrap/walktrap_communities.h b/src/community/walktrap/walktrap_communities.h new file mode 100644 index 0000000..fc37fac --- /dev/null +++ b/src/community/walktrap/walktrap_communities.h @@ -0,0 +1,160 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: communities.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + + +#ifndef WALKTRAP_COMMUNITIES_H +#define WALKTRAP_COMMUNITIES_H + +#include "walktrap_graph.h" +#include "walktrap_heap.h" +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +namespace igraph { + +namespace walktrap { + +class Communities; +class Probabilities { +public: + static IGRAPH_THREAD_LOCAL double* tmp_vector1; // + static IGRAPH_THREAD_LOCAL double* tmp_vector2; // + static IGRAPH_THREAD_LOCAL int* id; // + static IGRAPH_THREAD_LOCAL int* vertices1; // + static IGRAPH_THREAD_LOCAL int* vertices2; // + static IGRAPH_THREAD_LOCAL int current_id; // + + static IGRAPH_THREAD_LOCAL Communities* C; // pointer to all the communities + static IGRAPH_THREAD_LOCAL int length; // length of the random walks + + + int size; // number of probabilities stored + int* vertices; // the vertices corresponding to the stored probabilities, 0 if all the probabilities are stored + double* P; // the probabilities + + double compute_distance(const Probabilities* P2) const; // compute the squared distance r^2 between this probability vector and P2 + explicit Probabilities(int community); // compute the probability vector of a community + Probabilities(int community1, int community2); // merge the probability vectors of two communities in a new one + // the two communities must have their probability vectors stored + + ~Probabilities(); // destructor +}; + +class Community { +public: + + Neighbor* first_neighbor; // first item of the list of adjacent communities + Neighbor* last_neighbor; // last item of the list of adjacent communities + + int this_community; // number of this community + int first_member; // number of the first vertex of the community + int last_member; // number of the last vertex of the community + int size; // number of members of the community + + Probabilities* P; // the probability vector, 0 if not stored. + + + double sigma; // sigma(C) of the community + double internal_weight; // sum of the weight of the internal edges + double total_weight; // sum of the weight of all the edges of the community (an edge between two communities is a half-edge for each community) + + int sub_communities[2]; // the two sub communities, -1 if no sub communities; + int sub_community_of; // number of the community in which this community has been merged + // 0 if the community is active + // -1 if the community is not used + + void add_neighbor(Neighbor* N); + void remove_neighbor(Neighbor* N); + + Community(); // create an empty community + ~Community(); // destructor +}; + +class Communities { +private: + igraph_matrix_int_t *merges; + igraph_int_t mergeidx; + igraph_vector_t *modularity; + +public: + Graph* G; // the graph + int* members; // the members of each community represented as a chained list. + // a community points to the first_member the array which contains + // the next member (-1 = end of the community) + Neighbor_heap* H; // the distances between adjacent communities. + + + Community* communities; // array of the communities + + int nb_communities; // number of valid communities + int nb_active_communities; // number of active communities + + Communities(Graph* G, int random_walks_length = 3, + igraph_matrix_int_t *merges = nullptr, + igraph_vector_t *modularity = nullptr); // Constructor + ~Communities(); // Destructor + + void merge_communities(Neighbor* N); // create a community by merging two existing communities + double merge_nearest_communities(); + + double compute_delta_sigma(int c1, int c2) const; // compute delta_sigma(c1,c2) + + void remove_neighbor(Neighbor* N); + void add_neighbor(Neighbor* N); + void update_neighbor(Neighbor* N, double new_delta_sigma); +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_COMMUNITIES_H diff --git a/src/community/walktrap/walktrap_graph.cpp b/src/community/walktrap/walktrap_graph.cpp new file mode 100644 index 0000000..eede149 --- /dev/null +++ b/src/community/walktrap/walktrap_graph.cpp @@ -0,0 +1,228 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: graph.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_graph.h" + +#include "igraph_interface.h" + +#include +#include +#include + +using namespace std; + +namespace igraph { + +namespace walktrap { + +bool operator<(const Edge& E1, const Edge& E2) { + return (E1.neighbor < E2.neighbor); +} + + +Vertex::Vertex() { + degree = 0; + edges = nullptr; + total_weight = 0.; +} + +Vertex::~Vertex() { + delete[] edges; +} + +Graph::Graph() { + nb_vertices = 0; + nb_edges = 0; + vertices = nullptr; + total_weight = 0.; +} + +Graph::~Graph () { + delete[] vertices; +} + +class Edge_list { +public: + int* V1; + int* V2; + double* W; + + int size; + int size_max; + + void add(int v1, int v2, double w); + Edge_list() { + size = 0; + size_max = 1024; + V1 = new int[1024]; + V2 = new int[1024]; + W = new double[1024]; + } + + ~Edge_list() { + delete[] V1; + delete[] V2; + delete[] W; + } +}; + +void Edge_list::add(int v1, int v2, double w) { + if (size == size_max) { + int* tmp1 = new int[2 * size_max]; + int* tmp2 = new int[2 * size_max]; + double* tmp3 = new double[2 * size_max]; + for (int i = 0; i < size_max; i++) { + tmp1[i] = V1[i]; + tmp2[i] = V2[i]; + tmp3[i] = W[i]; + } + delete[] V1; + delete[] V2; + delete[] W; + V1 = tmp1; + V2 = tmp2; + W = tmp3; + size_max *= 2; + } + V1[size] = v1; + V2[size] = v2; + W[size] = w; + size++; +} + +igraph_error_t Graph::convert_from_igraph(const igraph_t *graph, + const igraph_vector_t *weights) { + Graph &G = *this; + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + // Avoid warnings with GCC when compiling with LTO. + IGRAPH_ASSUME(no_of_nodes >= 0); + IGRAPH_ASSUME(no_of_edges >= 0); + + // Refactoring the walktrap code to support larger graphs is pointless + // as running the algorithm on them would take an impractically long time. + if (no_of_nodes > INT_MAX || no_of_edges > INT_MAX) { + IGRAPH_ERROR("Graph too large for walktrap community detection.", IGRAPH_EINVAL); + } + + Edge_list EL; + + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_real_t w = weights ? VECTOR(*weights)[i] : 1.0; + EL.add(IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i), w); + } + + G.nb_vertices = no_of_nodes; + G.vertices = new Vertex[G.nb_vertices]; + G.nb_edges = 0; + G.total_weight = 0.0; + + for (int i = 0; i < EL.size; i++) { + G.vertices[EL.V1[i]].degree++; + G.vertices[EL.V2[i]].degree++; + G.vertices[EL.V1[i]].total_weight += EL.W[i]; + G.vertices[EL.V2[i]].total_weight += EL.W[i]; + G.nb_edges++; + G.total_weight += EL.W[i]; + } + + for (int i = 0; i < G.nb_vertices; i++) { + int deg = G.vertices[i].degree; + double w = (deg == 0) ? 1.0 : (G.vertices[i].total_weight / double(deg)); + G.vertices[i].edges = new Edge[deg + 1]; + G.vertices[i].edges[0].neighbor = i; + G.vertices[i].edges[0].weight = w; + G.vertices[i].total_weight += w; + G.vertices[i].degree = 1; + } + + for (int i = 0; i < EL.size; i++) { + G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].neighbor = EL.V2[i]; + G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].weight = EL.W[i]; + G.vertices[EL.V1[i]].degree++; + G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].neighbor = EL.V1[i]; + G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].weight = EL.W[i]; + G.vertices[EL.V2[i]].degree++; + } + + for (int i = 0; i < G.nb_vertices; i++) { + /* Check for zero strength, as it may lead to crashes the in walktrap algorithm. + * See https://github.com/igraph/igraph/pull/2043 */ + if (G.vertices[i].total_weight == 0) { + /* G.vertices will be destroyed by Graph::~Graph() */ + IGRAPH_ERROR("Vertex with zero strength found: all vertices must have positive strength for walktrap.", + IGRAPH_EINVAL); + } + sort(G.vertices[i].edges, G.vertices[i].edges + G.vertices[i].degree); + } + + for (int i = 0; i < G.nb_vertices; i++) { // merge multi edges + int a = 0; + for (int b = 1; b < G.vertices[i].degree; b++) { + if (G.vertices[i].edges[b].neighbor == G.vertices[i].edges[a].neighbor) { + G.vertices[i].edges[a].weight += G.vertices[i].edges[b].weight; + } else { + G.vertices[i].edges[++a] = G.vertices[i].edges[b]; + } + } + G.vertices[i].degree = a + 1; + } + + return IGRAPH_SUCCESS; +} + +} +} diff --git a/src/community/walktrap/walktrap_graph.h b/src/community/walktrap/walktrap_graph.h new file mode 100644 index 0000000..1c66cee --- /dev/null +++ b/src/community/walktrap/walktrap_graph.h @@ -0,0 +1,100 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here */ + +// File: graph.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +/* FSF address above was fixed by Tamas Nepusz */ + + +#ifndef WALKTRAP_GRAPH_H +#define WALKTRAP_GRAPH_H + +#include "igraph_community.h" + +namespace igraph { + +namespace walktrap { + +class Edge { // code an edge of a given vertex +public: + int neighbor; // the number of the neighbor vertex + double weight; // the weight of the edge +}; +bool operator<(const Edge& E1, const Edge& E2); + + +class Vertex { +public: + Edge* edges; // the edges of the vertex + int degree; // number of neighbors + double total_weight; // the total weight of the vertex + + Vertex(); // creates empty vertex + ~Vertex(); // destructor +}; + +class Graph { +public: + int nb_vertices; // number of vertices + int nb_edges; // number of edges + double total_weight; // total weight of the edges + Vertex* vertices; // array of the vertices + + Graph(); // create an empty graph + ~Graph(); // destructor + + igraph_error_t convert_from_igraph(const igraph_t *igraph, const igraph_vector_t *weights); +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_GRAPH_H diff --git a/src/community/walktrap/walktrap_heap.cpp b/src/community/walktrap/walktrap_heap.cpp new file mode 100644 index 0000000..df17d07 --- /dev/null +++ b/src/community/walktrap/walktrap_heap.cpp @@ -0,0 +1,141 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: heap.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_heap.h" + +using namespace igraph::walktrap; + +void Neighbor_heap::move_up(int index) { + while (H[index / 2]->delta_sigma > H[index]->delta_sigma) { + Neighbor* tmp = H[index / 2]; + H[index]->heap_index = index / 2; + H[index / 2] = H[index]; + tmp->heap_index = index; + H[index] = tmp; + index = index / 2; + } +} + +void Neighbor_heap::move_down(int index) { + while (true) { + int min = index; + if ((2 * index < size) && (H[2 * index]->delta_sigma < H[min]->delta_sigma)) { + min = 2 * index; + } + if (2 * index + 1 < size && H[2 * index + 1]->delta_sigma < H[min]->delta_sigma) { + min = 2 * index + 1; + } + if (min != index) { + Neighbor* tmp = H[min]; + H[index]->heap_index = min; + H[min] = H[index]; + tmp->heap_index = index; + H[index] = tmp; + index = min; + } else { + break; + } + } +} + +Neighbor* Neighbor_heap::get_first() { + if (size == 0) { + return nullptr; + } else { + return H[0]; + } +} + +void Neighbor_heap::remove(Neighbor* N) { + if (N->heap_index == -1 || size == 0) { + return; + } + Neighbor* last_N = H[--size]; + H[N->heap_index] = last_N; + last_N->heap_index = N->heap_index; + move_up(last_N->heap_index); + move_down(last_N->heap_index); + N->heap_index = -1; +} + +void Neighbor_heap::add(Neighbor* N) { + if (size >= max_size) { + return; + } + N->heap_index = size++; + H[N->heap_index] = N; + move_up(N->heap_index); +} + +void Neighbor_heap::update(Neighbor* N) { + if (N->heap_index == -1) { + return; + } + move_up(N->heap_index); + move_down(N->heap_index); +} + +Neighbor_heap::Neighbor_heap(int max_s) { + max_size = max_s; + size = 0; + H = new Neighbor*[max_s]; +} + +Neighbor_heap::~Neighbor_heap() { + delete[] H; +} + +bool Neighbor_heap::is_empty() const { + return (size == 0); +} diff --git a/src/community/walktrap/walktrap_heap.h b/src/community/walktrap/walktrap_heap.h new file mode 100644 index 0000000..675bbe1 --- /dev/null +++ b/src/community/walktrap/walktrap_heap.h @@ -0,0 +1,106 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: heap.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pons@liafa.jussieu.fr +// Web page : http://www.liafa.jussieu.fr/~pons/ +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#ifndef WALKTRAP_HEAP_H +#define WALKTRAP_HEAP_H + +namespace igraph { + +namespace walktrap { + +class Neighbor { +public: + int community1; // the two adjacent communities + int community2; // community1 < community2 + + double delta_sigma; // the delta sigma between the two communities + double weight; // the total weight of the edges between the two communities + bool exact; // true if delta_sigma is exact, false if it is only a lower bound + + Neighbor* next_community1; // pointers of two double + Neighbor* previous_community1; // chained lists containing + Neighbor* next_community2; // all the neighbors of + Neighbor* previous_community2; // each communities. + + int heap_index; // + + Neighbor(); +}; + + +class Neighbor_heap { +private: + int size; + int max_size; + + Neighbor** H; // the heap that contains a pointer to each Neighbor object stored + + void move_up(int index); + void move_down(int index); + +public: + void add(Neighbor* N); // add a new distance + void update(Neighbor* N); // update a distance + void remove(Neighbor* N); // remove a distance + Neighbor* get_first(); // get the first item + bool is_empty() const; + + explicit Neighbor_heap(int max_size); + ~Neighbor_heap(); +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_HEAP_H diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..953e3e4 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,40 @@ +#ifndef IGRAPH_PRIVATE_CONFIG_H +#define IGRAPH_PRIVATE_CONFIG_H + +#cmakedefine HAVE_STRCASECMP 1 +#cmakedefine HAVE_STRNCASECMP 1 +#cmakedefine HAVE__STRICMP 1 +#cmakedefine HAVE__STRNICMP 1 +#cmakedefine HAVE_STRDUP 1 +#cmakedefine HAVE_STRNDUP 1 +#cmakedefine HAVE_USELOCALE 1 +#cmakedefine HAVE_XLOCALE 1 +#cmakedefine HAVE__CONFIGTHREADLOCALE 1 + +#cmakedefine HAVE_BUILTIN_OVERFLOW 1 + +#cmakedefine HAVE__UMUL128 1 +#cmakedefine HAVE___UMULH 1 +#cmakedefine HAVE___UINT128_T 1 + +#cmakedefine HAVE__POPCNT64 1 +#cmakedefine HAVE__POPCNT 1 +#cmakedefine HAVE__BITSCANFORWARD64 1 +#cmakedefine HAVE__BITSCANFORWARD 1 +#cmakedefine HAVE__BITSCANREVERSE64 1 +#cmakedefine HAVE__BITSCANREVERSE 1 + +#cmakedefine HAVE_GLPK 1 +#cmakedefine HAVE_LIBXML 1 +#cmakedefine HAVE_INFOMAP 1 + +#cmakedefine INTERNAL_BLAS 1 +#cmakedefine INTERNAL_LAPACK 1 +#cmakedefine INTERNAL_ARPACK 1 +#cmakedefine INTERNAL_GMP 1 + + +#define IGRAPH_F77_SAVE static @TLS_KEYWORD@ +#define IGRAPH_THREAD_LOCAL @TLS_KEYWORD@ + +#endif diff --git a/src/connectivity/cohesive_blocks.c b/src/connectivity/cohesive_blocks.c new file mode 100644 index 0000000..f84e5d5 --- /dev/null +++ b/src/connectivity/cohesive_blocks.c @@ -0,0 +1,568 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cohesive_blocks.h" + +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_flow.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_operators.h" +#include "igraph_separators.h" +#include "igraph_statusbar.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +static void igraph_i_cohesive_blocks_free_graphs(igraph_vector_ptr_t *ptr) { + igraph_int_t i, n = igraph_vector_ptr_size(ptr); + + for (i = 0; i < n; i++) { + igraph_t *g = VECTOR(*ptr)[i]; + if (g) { + igraph_destroy(g); + IGRAPH_FREE(VECTOR(*ptr)[i]); /* also sets it to NULL */ + } + } +} + +/* This is kind of a BFS to find the components of the graph, after + * deleting the vertices marked in 'excluded'. + * These vertices are not put in the BFS queue, but they are added to + * all neighboring components. + */ + +static igraph_error_t igraph_i_cb_components(igraph_t *graph, + const igraph_vector_bool_t *excluded, + igraph_vector_int_t *components, + igraph_int_t *no, + /* working area follows */ + igraph_vector_int_t *compid, + igraph_dqueue_int_t *Q, + igraph_vector_int_t *neis) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i; + igraph_int_t cno = 0; + + igraph_vector_int_clear(components); + igraph_dqueue_int_clear(Q); + IGRAPH_CHECK(igraph_vector_int_resize(compid, no_of_nodes)); + igraph_vector_int_null(compid); + + for (i = 0; i < no_of_nodes; i++) { + + if (VECTOR(*compid)[i]) { + continue; + } + if (VECTOR(*excluded)[i]) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_int_push(Q, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(components, i)); + VECTOR(*compid)[i] = ++cno; + + while (!igraph_dqueue_int_empty(Q)) { + igraph_int_t node = igraph_dqueue_int_pop(Q); + igraph_int_t j, n; + IGRAPH_CHECK(igraph_neighbors( + graph, neis, node, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + igraph_int_t v = VECTOR(*neis)[j]; + if (VECTOR(*excluded)[v]) { + if (VECTOR(*compid)[v] != cno) { + VECTOR(*compid)[v] = cno; + IGRAPH_CHECK(igraph_vector_int_push_back(components, v)); + } + } else { + if (VECTOR(*compid)[v] == 0) { + VECTOR(*compid)[v] = cno; /* could be anything positive */ + IGRAPH_CHECK(igraph_vector_int_push_back(components, v)); + IGRAPH_CHECK(igraph_dqueue_int_push(Q, v)); + } + } + } + } /* while !igraph_dqueue_int_empty */ + + IGRAPH_CHECK(igraph_vector_int_push_back(components, -1)); + + } /* for ik. Thus a hiearchy of vertex subsets + * is found, with the entire graph G at its root. + * + * + * This function implements cohesive blocking and + * calculates the complete cohesive block hierarchy of a graph. + * + * + * See the following reference for details: + * + * + * J. Moody and D. R. White. Structural + * cohesion and embeddedness: A hierarchical concept of social + * groups. American Sociological Review, 68(1):103--127, Feb 2003. + * https://doi.org/10.2307/3088904 + * + * \param graph The input graph. It must be undirected and simple. See + * \ref igraph_is_simple(). + * \param blocks If not a null pointer, then it must be an initialized + * list of integers vectors; the cohesive blocks will be stored here. + * Each block is encoded with a vector of type \ref igraph_vector_int_t that + * contains the vertex IDs of the block. + * \param cohesion If not a null pointer, then it must be an initialized + * vector and the cohesion of the blocks is stored here, in the same + * order as the blocks in the \p blocks vector list. + * \param parent If not a null pointer, then it must be an initialized + * vector and the block hierarchy is stored here. For each block, the + * ID (i.e. the position in the \p blocks vector list) of its + * parent block is stored. For the top block in the hierarchy, + * -1 is stored. + * \param block_tree If not a null pointer, then it must be a pointer + * to an uninitialized graph, and the block hierarchy is stored + * here as an igraph graph. The vertex IDs correspond to the order + * of the blocks in the \p blocks vector. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/cohesive_blocks.c + */ + +igraph_error_t igraph_cohesive_blocks(const igraph_t *graph, + igraph_vector_int_list_t *blocks, + igraph_vector_int_t *cohesion, + igraph_vector_int_t *parent, + igraph_t *block_tree) { + + /* Some implementation comments. Everything is relatively + straightforward, except, that we need to follow the vertex IDs + of the various subgraphs, without having to store two-way + mappings at each level. The subgraphs can overlap, this + complicates things a bit. + + The 'Q' vector is used as a double ended queue and it contains + the subgraphs to work on in the future. Some other vectors are + associated with it. 'Qparent' gives the parent graph of a graph + in Q. Qmapping gives the mapping of the vertices from the graph + to the parent graph. Qcohesion is the vertex connectivity of the + graph. + + Qptr is an integer and points to the next graph to work on. + */ + + /* In theory, Q could be an igraph_graph_list_t; however, in that case + * we would not be able to pop off graphs from the front of the list as + * all elements of an igraph_graph_list_t are expected to be initialized, + * valid graphs. That's why we use an igraph_vector_ptr_t instead. */ + + igraph_vector_ptr_t Q; + igraph_vector_int_list_t Qmapping; + igraph_vector_int_t Qparent; + igraph_vector_int_t Qcohesion; + igraph_vector_bool_t Qcheck; + igraph_int_t Qptr = 0; + igraph_int_t conn; + igraph_bool_t is_simple; + + igraph_t *graph_copy; + + igraph_vector_int_list_t separators; + igraph_vector_int_t compvertices; + igraph_vector_int_t components; + igraph_vector_int_t newmapping; + igraph_vector_bool_t marked; + + igraph_vector_int_t compid; + igraph_dqueue_int_t bfsQ; + igraph_vector_int_t neis; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Cohesive blocking only works on undirected graphs.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &is_simple, IGRAPH_DIRECTED)); + if (!is_simple) { + IGRAPH_ERROR("Cohesive blocking only works on simple graphs.", + IGRAPH_EINVAL); + } + + if (blocks) { + igraph_vector_int_list_clear(blocks); + } + if (cohesion) { + igraph_vector_int_clear(cohesion); + } + if (parent) { + igraph_vector_int_clear(parent); + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&Q, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &Q); + IGRAPH_FINALLY(igraph_i_cohesive_blocks_free_graphs, &Q); + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&Qmapping, 1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&Qparent, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&Qcohesion, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&Qcheck, 1); + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&separators, 0); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&compvertices, 0); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&marked, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_int_init(&bfsQ, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &bfsQ); + IGRAPH_VECTOR_INT_INIT_FINALLY(&compid, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&components, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newmapping, 0); + + /* Put the input graph in the queue */ + graph_copy = IGRAPH_CALLOC(1, igraph_t); + IGRAPH_CHECK_OOM(graph_copy, "Insufficient memory for cohesive blocking."); + + IGRAPH_CHECK(igraph_copy(graph_copy, graph)); + VECTOR(Q)[0] = graph_copy; + VECTOR(Qparent)[0] = -1; /* Has no parent */ + IGRAPH_CHECK(igraph_vertex_connectivity(graph, &conn, /*checks=*/ true)); + VECTOR(Qcohesion)[0] = conn; + VECTOR(Qcheck)[0] = false; + + /* Then work until the queue is empty */ + while (Qptr < igraph_vector_ptr_size(&Q)) { + igraph_t *mygraph = VECTOR(Q)[Qptr]; + igraph_bool_t mycheck = VECTOR(Qcheck)[Qptr]; + igraph_int_t mynodes = igraph_vcount(mygraph); + igraph_int_t i, nsep; + igraph_int_t no, kept = 0; + igraph_int_t cptr = 0; + igraph_int_t nsepv = 0; + igraph_bool_t addedsep = false; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Get the separators */ + IGRAPH_CHECK(igraph_minimum_size_separators(mygraph, &separators)); + nsep = igraph_vector_int_list_size(&separators); + + /* Remove them from the graph, also mark them */ + IGRAPH_CHECK(igraph_vector_bool_resize(&marked, mynodes)); + igraph_vector_bool_null(&marked); + for (i = 0; i < nsep; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&separators, i); + igraph_int_t j, n = igraph_vector_int_size(v); + for (j = 0; j < n; j++) { + igraph_int_t vv = VECTOR(*v)[j]; + if (!VECTOR(marked)[vv]) { + nsepv++; + VECTOR(marked)[vv] = true; + } + } + } + + /* Find the connected components, omitting the separator vertices, + but including the neighboring separator vertices + */ + IGRAPH_CHECK(igraph_i_cb_components(mygraph, &marked, + &components, &no, + &compid, &bfsQ, &neis)); + + /* Add the separator vertices themselves, as another component, + but only if there is at least one vertex not included in any + separator. */ + if (nsepv != mynodes) { + addedsep = true; + for (i = 0; i < mynodes; i++) { + if (VECTOR(marked)[i]) { + IGRAPH_CHECK(igraph_vector_int_push_back(&components, i)); + } + } + IGRAPH_CHECK(igraph_vector_int_push_back(&components, -1)); + no++; + } + + for (i = 0; i < no; i++) { + igraph_t *newgraph; + igraph_int_t maxdeg; + + igraph_vector_int_clear(&compvertices); + + while (true) { + igraph_int_t v = VECTOR(components)[cptr++]; + if (v < 0) { + break; + } + IGRAPH_CHECK(igraph_vector_int_push_back(&compvertices, v)); + } + + newgraph = IGRAPH_CALLOC(1, igraph_t); + IGRAPH_CHECK_OOM(newgraph, "Insufficient memory for cohesive blocking."); + IGRAPH_FINALLY(igraph_free, newgraph); + + IGRAPH_CHECK(igraph_induced_subgraph_map(mygraph, newgraph, + igraph_vss_vector(&compvertices), + IGRAPH_SUBGRAPH_AUTO, + /*map=*/ NULL, + /*invmap=*/ &newmapping)); + IGRAPH_FINALLY(igraph_destroy, newgraph); + + IGRAPH_CHECK(igraph_maxdegree(newgraph, &maxdeg, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_LOOPS)); + if (maxdeg > VECTOR(Qcohesion)[Qptr]) { + igraph_int_t newconn; + kept++; + IGRAPH_CHECK(igraph_vector_ptr_push_back(&Q, newgraph)); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(&Qmapping, &newmapping)); + IGRAPH_CHECK(igraph_vertex_connectivity(newgraph, &newconn, + /*checks=*/ 1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&Qcohesion, newconn)); + IGRAPH_CHECK(igraph_vector_int_push_back(&Qparent, Qptr)); + IGRAPH_CHECK(igraph_vector_bool_push_back(&Qcheck, + mycheck || addedsep)); + } else { + igraph_destroy(newgraph); + igraph_free(newgraph); + IGRAPH_FINALLY_CLEAN(2); + } + } + + igraph_destroy(mygraph); + igraph_free(mygraph); + VECTOR(Q)[Qptr] = NULL; + + Qptr++; + } + + igraph_vector_int_destroy(&newmapping); + igraph_vector_int_destroy(&components); + igraph_vector_int_destroy(&compid); + igraph_dqueue_int_destroy(&bfsQ); + igraph_vector_int_destroy(&neis); + igraph_vector_bool_destroy(&marked); + igraph_vector_int_destroy(&compvertices); + igraph_vector_int_list_destroy(&separators); + IGRAPH_FINALLY_CLEAN(8); + + if (blocks || cohesion || parent || block_tree) { + igraph_int_t noblocks = Qptr, badblocks = 0; + igraph_vector_bool_t removed; + igraph_int_t i, resptr = 0; + igraph_vector_int_t rewritemap; + + IGRAPH_CHECK(igraph_vector_bool_init(&removed, noblocks)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &removed); + IGRAPH_CHECK(igraph_vector_int_init(&rewritemap, noblocks)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &rewritemap); + + for (i = 1; i < noblocks; i++) { + igraph_int_t p = VECTOR(Qparent)[i]; + while (VECTOR(removed)[p]) { + p = VECTOR(Qparent)[p]; + } + if (VECTOR(Qcohesion)[p] >= VECTOR(Qcohesion)[i]) { + VECTOR(removed)[i] = true; + badblocks++; + } + } + + /* Rewrite the mappings */ + for (i = 1; i < Qptr; i++) { + igraph_int_t j, n, p = VECTOR(Qparent)[i]; + igraph_vector_int_t *mapping, *pmapping; + + if (p == 0) { + continue; + } + + mapping = igraph_vector_int_list_get_ptr(&Qmapping, i); + pmapping = igraph_vector_int_list_get_ptr(&Qmapping, p); + + n = igraph_vector_int_size(mapping); + for (j = 0; j < n; j++) { + igraph_int_t v = VECTOR(*mapping)[j]; + VECTOR(*mapping)[j] = VECTOR(*pmapping)[v]; + } + } + + /* Because we also put the separator vertices in the queue, it is + not ensured that the found blocks are not subsets of each other. + We check this now. */ + for (i = 1; i < noblocks; i++) { + igraph_int_t j, ic; + igraph_vector_int_t *ivec; + if (!VECTOR(Qcheck)[i] || VECTOR(removed)[i]) { + continue; + } + ivec = igraph_vector_int_list_get_ptr(&Qmapping, i); + ic = VECTOR(Qcohesion)[i]; + for (j = 1; j < noblocks; j++) { + igraph_vector_int_t *jvec; + igraph_int_t jc; + if (j == i || !VECTOR(Qcheck)[j] || VECTOR(removed)[j]) { + continue; + } + jvec = igraph_vector_int_list_get_ptr(&Qmapping, j); + jc = VECTOR(Qcohesion)[j]; + if (igraph_i_cb_isin(ivec, jvec) && jc >= ic) { + badblocks++; + VECTOR(removed)[i] = true; + break; + } + } + } + + noblocks -= badblocks; + + if (blocks) { + IGRAPH_CHECK(igraph_vector_int_list_resize(blocks, noblocks)); + } + if (cohesion) { + IGRAPH_CHECK(igraph_vector_int_resize(cohesion, noblocks)); + } + if (parent) { + IGRAPH_CHECK(igraph_vector_int_resize(parent, noblocks)); + } + + for (i = 0; i < Qptr; i++) { + if (VECTOR(removed)[i]) { + continue; + } + VECTOR(rewritemap)[i] = resptr; + if (cohesion) { + VECTOR(*cohesion)[resptr] = VECTOR(Qcohesion)[i]; + } + if (parent || block_tree) { + igraph_int_t p = VECTOR(Qparent)[i]; + while (p >= 0 && VECTOR(removed)[p]) { + p = VECTOR(Qparent)[p]; + } + if (p >= 0) { + p = VECTOR(rewritemap)[p]; + } + VECTOR(Qparent)[i] = p; + if (parent) { + VECTOR(*parent)[resptr] = p; + } + } + if (blocks) { + IGRAPH_CHECK( + igraph_vector_int_update( + igraph_vector_int_list_get_ptr(blocks, resptr), + igraph_vector_int_list_get_ptr(&Qmapping, i) + ) + ); + igraph_vector_int_clear(igraph_vector_int_list_get_ptr(&Qmapping, i)); + } + resptr++; + } + + /* Plus the original graph */ + if (blocks) { + igraph_int_t num_vertices = igraph_vcount(graph); + igraph_vector_int_t *orig = igraph_vector_int_list_get_ptr(blocks, 0); + IGRAPH_CHECK(igraph_vector_int_resize(orig, num_vertices)); + for (i = 0; i < num_vertices; i++) { + VECTOR(*orig)[i] = i; + } + } + + if (block_tree) { + igraph_vector_int_t edges; + igraph_int_t eptr = 0; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, noblocks * 2 - 2); + for (i = 1; i < Qptr; i++) { + if (VECTOR(removed)[i]) { + continue; + } + VECTOR(edges)[eptr++] = VECTOR(Qparent)[i]; + VECTOR(edges)[eptr++] = VECTOR(rewritemap)[i]; + } + + IGRAPH_CHECK(igraph_create(block_tree, &edges, noblocks, + IGRAPH_DIRECTED)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&rewritemap); + igraph_vector_bool_destroy(&removed); + IGRAPH_FINALLY_CLEAN(2); + + } + + igraph_vector_bool_destroy(&Qcheck); + igraph_vector_int_destroy(&Qcohesion); + igraph_vector_int_destroy(&Qparent); + igraph_vector_int_list_destroy(&Qmapping); + IGRAPH_FINALLY_CLEAN(4); + + igraph_vector_ptr_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); /* + the elements of Q, they were already destroyed */ + + return IGRAPH_SUCCESS; +} diff --git a/src/connectivity/components.c b/src/connectivity/components.c new file mode 100644 index 0000000..4c8da28 --- /dev/null +++ b/src/connectivity/components.c @@ -0,0 +1,1608 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_components.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_stack.h" +#include "igraph_structural.h" +#include "igraph_vector.h" + +#include "core/interruption.h" +#include "operators/subgraph.h" + +static igraph_error_t igraph_i_connected_components_weak( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no +); +static igraph_error_t igraph_i_connected_components_strong( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no +); + +/** + * \ingroup structural + * \function igraph_connected_components + * \brief Calculates the (weakly or strongly) connected components in a graph. + * + * When computing strongly connected components, the components will be + * indexed in topological order. In other words, vertex \c v is reachable + * from vertex \c u precisely when membership[u] <= membership[v]. + * + * \param graph The graph object to analyze. + * \param membership For every vertex the ID of its component is given. + * The vector has to be preinitialized and will be resized as needed. + * Alternatively this argument can be \c NULL, in which case it is ignored. + * \param csize For every component it gives its size, the order being defined + * by the component IDs. The vector must be preinitialized and will be + * resized as needed. Alternatively this argument can be \c NULL, in which + * case it is ignored. + * \param no Pointer to an integer, if not \c NULL then the number of + * components will be stored here. + * \param mode For directed graph this specifies whether to calculate + * weakly or strongly connected components. Possible values: + * \clist + * \cli IGRAPH_WEAK + * Compute weakly connected components, i.e. ignore edge directions. + * \cli IGRAPH_STRONG + * Compute strongly connnected components, i.e. consider edge directions. + * \endclist + * This parameter is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of vertices + * and edges in the graph. + * + * \example examples/simple/igraph_contract_vertices.c + */ + +igraph_error_t igraph_connected_components( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no, igraph_connectedness_t mode +) { + if (mode == IGRAPH_WEAK || !igraph_is_directed(graph)) { + return igraph_i_connected_components_weak(graph, membership, csize, no); + } else if (mode == IGRAPH_STRONG) { + return igraph_i_connected_components_strong(graph, membership, csize, no); + } + + IGRAPH_ERROR("Invalid connectedness mode.", IGRAPH_EINVAL); +} + +static igraph_error_t igraph_i_connected_components_weak( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_components; + igraph_bitset_t already_added; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t neis = IGRAPH_VECTOR_NULL; + + /* Memory for result, csize is dynamically allocated */ + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + } + if (csize) { + igraph_vector_int_clear(csize); + } + + /* Try to make use of cached information. */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED)) { + /* If we know that the graph is weakly connected from the cache, + * we can return the result right away. We keep in mind that + * the null graph is considered disconnected, therefore any connected + * graph has precisely one component. */ + if (membership) { + /* All vertices are members of the same component, + * component number 0. */ + igraph_vector_int_null(membership); + } + if (csize) { + /* The size of the single component is the same as the vertex count. */ + IGRAPH_CHECK(igraph_vector_int_push_back(csize, no_of_nodes)); + } + if (no) { + /* There is one component. */ + *no = 1; + } + return IGRAPH_SUCCESS; + } + + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, no_of_nodes > 100000 ? 10000 : no_of_nodes / 10); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + /* The algorithm */ + + no_of_components = 0; + for (igraph_int_t first_node = 0; first_node < no_of_nodes; ++first_node) { + igraph_int_t act_component_size; + + if (IGRAPH_BIT_TEST(already_added, first_node)) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_BIT_SET(already_added, first_node); + act_component_size = 1; + if (membership) { + VECTOR(*membership)[first_node] = no_of_components; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, first_node)); + + while ( !igraph_dqueue_int_empty(&q) ) { + igraph_int_t act_node = igraph_dqueue_int_pop(&q); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, act_node, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t nei_count = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < nei_count; i++) { + igraph_int_t neighbor = VECTOR(neis)[i]; + if (IGRAPH_BIT_TEST(already_added, neighbor)) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_BIT_SET(already_added, neighbor); + act_component_size++; + if (membership) { + VECTOR(*membership)[neighbor] = no_of_components; + } + } + } + + no_of_components++; + if (csize) { + IGRAPH_CHECK(igraph_vector_int_push_back(csize, act_component_size)); + } + } + + /* Cleaning up */ + + if (no) { + *no = no_of_components; + } + + /* Clean up */ + igraph_bitset_destroy(&already_added); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); + + /* Update cache */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, no_of_components == 1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_connected_components_strong( + const igraph_t *graph, igraph_vector_int_t *membership, + igraph_vector_int_t *csize, igraph_int_t *no +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t next_nei = IGRAPH_VECTOR_NULL; + igraph_int_t num_seen; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_int_t no_of_components = 0; + igraph_vector_int_t out = IGRAPH_VECTOR_NULL; + igraph_adjlist_t adjlist; + + /* Memory for result, csize is dynamically allocated */ + if (membership) { + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + } + if (csize) { + igraph_vector_int_clear(csize); + } + + /* Try to make use of cached information. */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_STRONGLY_CONNECTED) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_STRONGLY_CONNECTED)) { + /* If we know that the graph is strongly connected from the cache, + * we can return the result right away. We keep in mind that + * the null graph is considered disconnected, therefore any connected + * graph has precisely one component. */ + if (membership) { + /* All vertices are members of the same component, + * component number 0. */ + igraph_vector_int_null(membership); + } + if (csize) { + /* The size of the single component is the same as the vertex count. */ + IGRAPH_CHECK(igraph_vector_int_push_back(csize, no_of_nodes)); + } + if (no) { + /* There is one component. */ + *no = 1; + } + return IGRAPH_SUCCESS; + } + + /* The result */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&next_nei, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_vector_int_reserve(&out, no_of_nodes)); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + num_seen = 0; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + const igraph_vector_int_t *tmp; + + IGRAPH_ALLOW_INTERRUPTION(); + + tmp = igraph_adjlist_get(&adjlist, i); + if (VECTOR(next_nei)[i] > igraph_vector_int_size(tmp)) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act_node = igraph_dqueue_int_back(&q); + tmp = igraph_adjlist_get(&adjlist, act_node); + if (VECTOR(next_nei)[act_node] == 0) { + /* this is the first time we've met this vertex */ + VECTOR(next_nei)[act_node]++; + } else if (VECTOR(next_nei)[act_node] <= igraph_vector_int_size(tmp)) { + /* we've already met this vertex but it has more children */ + igraph_int_t neighbor = VECTOR(*tmp)[VECTOR(next_nei)[act_node] - 1]; + if (VECTOR(next_nei)[neighbor] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + } + VECTOR(next_nei)[act_node]++; + } else { + /* we've met this vertex and it has no more children */ + IGRAPH_CHECK(igraph_vector_int_push_back(&out, act_node)); + igraph_dqueue_int_pop_back(&q); + num_seen++; + + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } /* while q */ + } /* for */ + + IGRAPH_PROGRESS("Strongly connected components: ", 50.0, NULL); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* OK, we've the 'out' values for the nodes, let's use them in + decreasing order with the help of a heap */ + + igraph_vector_int_null(&next_nei); /* mark already added vertices */ + num_seen = 0; + + while (!igraph_vector_int_empty(&out)) { + igraph_int_t act_component_size; + igraph_int_t grandfather = igraph_vector_int_pop_back(&out); + + if (VECTOR(next_nei)[grandfather] != 0) { + continue; + } + VECTOR(next_nei)[grandfather] = 1; + act_component_size = 1; + if (membership) { + VECTOR(*membership)[grandfather] = no_of_components; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, grandfather)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act_node = igraph_dqueue_int_pop_back(&q); + const igraph_vector_int_t *tmp = igraph_adjlist_get(&adjlist, act_node); + const igraph_int_t n = igraph_vector_int_size(tmp); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t neighbor = VECTOR(*tmp)[i]; + if (VECTOR(next_nei)[neighbor] != 0) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + VECTOR(next_nei)[neighbor] = 1; + act_component_size++; + if (membership) { + VECTOR(*membership)[neighbor] = no_of_components; + } + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } + + no_of_components++; + if (csize) { + IGRAPH_CHECK(igraph_vector_int_push_back(csize, act_component_size)); + } + } + + IGRAPH_PROGRESS("Strongly connected components: ", 100.0, NULL); + + if (no) { + *no = no_of_components; + } + + /* Clean up */ + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&out); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&next_nei); + IGRAPH_FINALLY_CLEAN(4); + + /* Update cache */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_STRONGLY_CONNECTED, no_of_components == 1); + if (no_of_components == 1) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, true); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_is_connected_weak(const igraph_t *graph, igraph_bool_t *res); + +/** + * \ingroup structural + * \function igraph_is_connected + * \brief Decides whether the graph is (weakly or strongly) connected. + * + * A graph is considered connected when any of its vertices is reachable + * from any other. A directed graph with this property is called + * \em strongly connected. A directed graph that would be connected when + * ignoring the directions of its edges is called \em weakly connected. + * + * + * A graph with zero vertices (i.e. the null graph) is \em not connected by + * definition. This behaviour changed in igraph 0.9; earlier versions assumed + * that the null graph is connected. See the following issue on Github for the + * argument that led us to change the definition: + * https://github.com/igraph/igraph/issues/1539 + * + * + * The return value of this function is cached in the graph itself, separately + * for weak and strong connectivity. Calling the function multiple times with + * no modifications to the graph in between will return a cached value in O(1) + * time. + * + * \param graph The graph object to analyze. + * \param res Pointer to a Boolean variable, the result will be stored + * here. + * \param mode For a directed graph this specifies whether to calculate + * weak or strong connectedness. Possible values: + * \c IGRAPH_WEAK, + * \c IGRAPH_STRONG. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVAL: invalid mode argument. + * + * \sa \ref igraph_connected_components() to find the connected components, + * \ref igraph_is_biconnected() to check if the graph is 2-vertex-connected. + * + * Time complexity: O(|V|+|E|), the + * number of vertices + * plus the number of edges in the graph. + */ + +igraph_error_t igraph_is_connected(const igraph_t *graph, igraph_bool_t *res, + igraph_connectedness_t mode) { + + igraph_cached_property_t prop; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no; + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_WEAK; + } + + switch (mode) { + case IGRAPH_WEAK: + prop = IGRAPH_PROP_IS_WEAKLY_CONNECTED; + break; + + case IGRAPH_STRONG: + prop = IGRAPH_PROP_IS_STRONGLY_CONNECTED; + break; + + default: + IGRAPH_ERROR("Invalid connectedness mode.", IGRAPH_EINVAL); + } + + IGRAPH_RETURN_IF_CACHED_BOOL(graph, prop, res); + + if (no_of_nodes == 0) { + /* Changed in igraph 0.9; see https://github.com/igraph/igraph/issues/1539 + * for the reasoning behind the change */ + *res = false; + } else if (no_of_nodes == 1) { + *res = true; + } else if (mode == IGRAPH_WEAK) { + IGRAPH_CHECK(igraph_i_is_connected_weak(graph, res)); + } else { /* mode == IGRAPH_STRONG */ + /* A strongly connected graph has at least as many edges as vertices, + * except for the singleton graph, which is handled above. */ + if (igraph_ecount(graph) < no_of_nodes) { + *res = false; + } else { + IGRAPH_CHECK(igraph_i_connected_components_strong(graph, NULL, NULL, &no)); + *res = (no == 1); + } + } + + /* Cache updates are done in igraph_i_connected_components_strong() and + * igraph_i_is_connected_weak() because those might be called from other + * places and we want to make use of the caching if so */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_is_connected_weak(const igraph_t *graph, igraph_bool_t *res) { + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t added_count; + igraph_bitset_t already_added; + igraph_vector_int_t neis; + igraph_dqueue_int_t q; + + /* By convention, the null graph is not considered connected. + * See https://github.com/igraph/igraph/issues/1538 */ + if (no_of_nodes == 0) { + *res = false; + goto exit; + } + + /* A connected graph has at least |V| - 1 edges. */ + if (no_of_edges < no_of_nodes - 1) { + *res = false; + goto exit; + } + + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 10); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + /* Try to find at least two components */ + IGRAPH_BIT_SET(already_added, 0); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + + added_count = 1; + while (! igraph_dqueue_int_empty(&q)) { + IGRAPH_ALLOW_INTERRUPTION(); + + const igraph_int_t actnode = igraph_dqueue_int_pop(&q); + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, actnode, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + const igraph_int_t nei_count = igraph_vector_int_size(&neis); + + for (igraph_int_t i = 0; i < nei_count; i++) { + const igraph_int_t neighbor = VECTOR(neis)[i]; + if (IGRAPH_BIT_TEST(already_added, neighbor)) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + added_count++; + IGRAPH_BIT_SET(already_added, neighbor); + + if (added_count == no_of_nodes) { + /* We have already reached all nodes: the graph is connected. + * We can stop the traversal now. */ + goto done; + } + } + } + +done: + /* Connected? */ + *res = (added_count == no_of_nodes); + + igraph_bitset_destroy(&already_added); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); + +exit: + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, *res); + if (igraph_is_directed(graph) && !(*res)) { + /* If the graph is not weakly connected, it is not strongly connected + * either so we can also cache that */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_STRONGLY_CONNECTED, *res); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_decompose_weak(const igraph_t *graph, + igraph_graph_list_t *components, + igraph_int_t maxcompno, igraph_int_t minelements); + +static igraph_error_t igraph_i_decompose_strong(const igraph_t *graph, + igraph_graph_list_t *components, + igraph_int_t maxcompno, igraph_int_t minelements); + +/** + * \function igraph_decompose + * \brief Decomposes a graph into connected components. + * + * Creates a separate graph for each component of a graph. Note that the + * vertex IDs in the new graphs will be different than in the original + * graph, except when there is only a single component in the original graph. + * + * \param graph The original graph. + * \param components This list of graphs will contain the individual components. + * It should be initialized before calling this function and will be resized + * to hold the graphs. + * \param mode Either \c IGRAPH_WEAK or \c IGRAPH_STRONG for weakly + * and strongly connected components respectively. + * \param maxcompno The maximum number of components to return. The + * first \p maxcompno components will be returned (which hold at + * least \p minelements vertices, see the next parameter), the + * others will be ignored. Supply -1 here if you don't + * want to limit the number of components. + * \param minelements The minimum number of vertices a component + * should contain in order to place it in the \p components + * vector. For example, supplying 2 here ignores isolated vertices. + * \return Error code, \c IGRAPH_ENOMEM if there is not enough memory + * to perform the operation. + * + * Added in version 0.2. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_decompose.c + */ + +igraph_error_t igraph_decompose(const igraph_t *graph, igraph_graph_list_t *components, + igraph_connectedness_t mode, + igraph_int_t maxcompno, igraph_int_t minelements) { + if (!igraph_is_directed(graph)) { + mode = IGRAPH_WEAK; + } + + switch (mode) { + case IGRAPH_WEAK: + return igraph_i_decompose_weak(graph, components, maxcompno, minelements); + case IGRAPH_STRONG: + return igraph_i_decompose_strong(graph, components, maxcompno, minelements); + default: + IGRAPH_ERROR("Invalid connectedness mode.", IGRAPH_EINVAL); + } +} + +static igraph_error_t igraph_i_decompose_weak(const igraph_t *graph, + igraph_graph_list_t *components, + igraph_int_t maxcompno, igraph_int_t minelements) { + + igraph_int_t actstart; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t resco = 0; /* number of graphs created so far */ + igraph_bitset_t already_added; + igraph_dqueue_int_t q; + igraph_vector_int_t verts; + igraph_vector_int_t neis; + igraph_vector_int_t vids_old2new; + igraph_int_t i; + igraph_t newg; + + + if (maxcompno < 0) { + maxcompno = IGRAPH_INTEGER_MAX; + } + + igraph_graph_list_clear(components); + + /* already_added keeps track of what nodes made it into a graph already */ + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&verts, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids_old2new, no_of_nodes); + igraph_vector_int_fill(&vids_old2new, -1); + + /* vids_old2new would have been created internally in igraph_induced_subgraph(), + but it is slow if the graph is large and consists of many small components, + so we create it once here and then re-use it */ + + /* add a node and its neighbors at once, recursively + then switch to next node that has not been added already */ + for (actstart = 0; resco < maxcompno && actstart < no_of_nodes; actstart++) { + + if (IGRAPH_BIT_TEST(already_added, actstart)) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_int_clear(&verts); + + /* add the node itself */ + IGRAPH_BIT_SET(already_added, actstart); + IGRAPH_CHECK(igraph_vector_int_push_back(&verts, actstart)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actstart)); + + /* add the neighbors, recursively */ + while (!igraph_dqueue_int_empty(&q) ) { + /* pop from the queue of this component */ + igraph_int_t actvert = igraph_dqueue_int_pop(&q); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, actvert, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t nei_count = igraph_vector_int_size(&neis); + /* iterate over the neighbors */ + for (i = 0; i < nei_count; i++) { + igraph_int_t neighbor = VECTOR(neis)[i]; + if (IGRAPH_BIT_TEST(already_added, neighbor)) { + continue; + } + /* add neighbor */ + IGRAPH_BIT_SET(already_added, neighbor); + + /* recursion: append neighbor to the queues */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_vector_int_push_back(&verts, neighbor)); + } + } + + /* ok, we have a component */ + if (igraph_vector_int_size(&verts) < minelements) { + continue; + } + + IGRAPH_CHECK(igraph_i_induced_subgraph_map( + graph, &newg, igraph_vss_vector(&verts), + IGRAPH_SUBGRAPH_AUTO, &vids_old2new, + /* invmap = */ NULL, /* map_is_prepared = */ true + )); + IGRAPH_FINALLY(igraph_destroy, &newg); + IGRAPH_CHECK(igraph_graph_list_push_back(components, &newg)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of newg now taken by 'components' */ + resco++; + + /* vids_old2new does not have to be cleaned up here; since we are doing + * weak decomposition, each vertex will appear in only one of the + * connected components so we won't ever touch an item in vids_old2new + * if it was already set to a non-zero value in a previous component */ + + } /* for actstart++ */ + + igraph_vector_int_destroy(&vids_old2new); + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&verts); + igraph_dqueue_int_destroy(&q); + igraph_bitset_destroy(&already_added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_decompose_strong(const igraph_t *graph, + igraph_graph_list_t *components, + igraph_int_t maxcompno, igraph_int_t minelements) { + + + igraph_int_t no_of_nodes = igraph_vcount(graph); + + /* this is a heap used twice for checking what nodes have + * been counted already */ + igraph_vector_int_t next_nei = IGRAPH_VECTOR_NULL; + + igraph_int_t i, n, num_seen; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + + igraph_int_t no_of_components = 0; + + igraph_vector_int_t out = IGRAPH_VECTOR_NULL; + const igraph_vector_int_t* tmp; + + igraph_adjlist_t adjlist; + igraph_vector_int_t verts; + igraph_vector_int_t vids_old2new; + igraph_t newg; + + if (maxcompno < 0) { + maxcompno = IGRAPH_INTEGER_MAX; + } + + igraph_graph_list_clear(components); + + /* The result */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids_old2new, no_of_nodes); + igraph_vector_int_fill(&vids_old2new, -1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&verts, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&next_nei, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_vector_int_reserve(&out, no_of_nodes)); + + igraph_vector_int_null(&out); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* vids_old2new would have been created internally in igraph_induced_subgraph(), + but it is slow if the graph is large and consists of many small components, + so we create it once here and then re-use it */ + + /* number of components seen */ + num_seen = 0; + /* populate the 'out' vector by browsing a node and following up + all its neighbors recursively, then switching to the next + unassigned node */ + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* get all the 'out' neighbors of this node + * NOTE: next_nei is initialized [0, 0, ...] */ + tmp = igraph_adjlist_get(&adjlist, i); + if (VECTOR(next_nei)[i] > igraph_vector_int_size(tmp)) { + continue; + } + + /* add this node to the queue for this component */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + + /* consume the tree from this node ("root") recursively + * until there is no more */ + while (!igraph_dqueue_int_empty(&q)) { + /* this looks up but does NOT consume the queue */ + igraph_int_t act_node = igraph_dqueue_int_back(&q); + + /* get all neighbors of this node */ + tmp = igraph_adjlist_get(&adjlist, act_node); + if (VECTOR(next_nei)[act_node] == 0) { + /* this is the first time we've met this vertex, + * because next_nei is initialized [0, 0, ...] */ + VECTOR(next_nei)[act_node]++; + /* back to the queue, same vertex is up again */ + + } else if (VECTOR(next_nei)[act_node] <= igraph_vector_int_size(tmp)) { + /* we've already met this vertex but it has more children */ + igraph_int_t neighbor = VECTOR(*tmp)[VECTOR(next_nei)[act_node] - 1]; + if (VECTOR(next_nei)[neighbor] == 0) { + /* add the root of the other children to the queue */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + } + VECTOR(next_nei)[act_node]++; + } else { + /* we've met this vertex and it has no more children */ + IGRAPH_CHECK(igraph_vector_int_push_back(&out, act_node)); + /* this consumes the queue, since there's nowhere to go */ + igraph_dqueue_int_pop_back(&q); + num_seen++; + + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } /* while q */ + } /* for */ + + IGRAPH_PROGRESS("Strongly connected components: ", 50.0, NULL); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* OK, we've the 'out' values for the nodes, let's use them in + * decreasing order with the help of the next_nei heap */ + + igraph_vector_int_null(&next_nei); /* mark already added vertices */ + + /* number of components built */ + num_seen = 0; + while (!igraph_vector_int_empty(&out) && no_of_components < maxcompno) { + /* consume the vector from the last element */ + igraph_int_t grandfather = igraph_vector_int_pop_back(&out); + + /* been here, done that + * NOTE: next_nei is initialized as [0, 0, ...] */ + if (VECTOR(next_nei)[grandfather] != 0) { + continue; + } + + /* collect all the members of this component */ + igraph_vector_int_clear(&verts); + + /* this node is gone for any future components */ + VECTOR(next_nei)[grandfather] = 1; + + /* add to component */ + IGRAPH_CHECK(igraph_vector_int_push_back(&verts, grandfather)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, grandfather)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + while (!igraph_dqueue_int_empty(&q)) { + /* consume the queue from this node */ + igraph_int_t act_node = igraph_dqueue_int_pop_back(&q); + tmp = igraph_adjlist_get(&adjlist, act_node); + n = igraph_vector_int_size(tmp); + for (i = 0; i < n; i++) { + igraph_int_t neighbor = VECTOR(*tmp)[i]; + if (VECTOR(next_nei)[neighbor] != 0) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + VECTOR(next_nei)[neighbor] = 1; + + /* add to component */ + IGRAPH_CHECK(igraph_vector_int_push_back(&verts, neighbor)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } + + /* ok, we have a component */ + if (igraph_vector_int_size(&verts) < minelements) { + continue; + } + + IGRAPH_CHECK(igraph_i_induced_subgraph_map( + graph, &newg, igraph_vss_vector(&verts), + IGRAPH_SUBGRAPH_AUTO, &vids_old2new, + /* invmap = */ 0, /* map_is_prepared = */ 1 + )); + IGRAPH_FINALLY(igraph_destroy, &newg); + IGRAPH_CHECK(igraph_graph_list_push_back(components, &newg)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of newg now taken by 'components' */ + + /* vids_old2new has to be cleaned up here because a vertex may appear + * in multiple strongly connected components. Simply calling + * igraph_vector_int_fill() would be an O(n) operation where n is the number + * of vertices in the large graph so we cannot do that; we have to + * iterate over 'verts' instead */ + n = igraph_vector_int_size(&verts); + for (i = 0; i < n; i++) { + VECTOR(vids_old2new)[VECTOR(verts)[i]] = 0; + } + + no_of_components++; + } + + IGRAPH_PROGRESS("Strongly connected components: ", 100.0, NULL); + + /* Clean up, return */ + + igraph_vector_int_destroy(&vids_old2new); + igraph_vector_int_destroy(&verts); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&out); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&next_nei); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; + +} + +/** + * \function igraph_articulation_points + * \brief Finds the articulation points in a graph. + * + * A vertex is an articulation point if its removal increases + * the number of (weakly) connected components in the graph. + * + * + * Note that a graph without any articulation points is not necessarily + * biconnected. Counterexamples are the two-vertex complete graph as well + * as empty graphs. Use \ref igraph_is_biconnected() to check whether + * a graph is biconnected. + * + * \param graph The input graph. It will be treated as undirected. + * \param res Pointer to an initialized vector, the articulation points will + * be stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and edges. + * + * \sa \ref igraph_biconnected_components(), \ref igraph_is_bipartite(), + * \ref igraph_connected_components(), \ref igraph_bridges() + */ + +igraph_error_t igraph_articulation_points(const igraph_t *graph, igraph_vector_int_t *res) { + + return igraph_biconnected_components(graph, NULL, NULL, NULL, NULL, res); +} + +/** + * \function igraph_biconnected_components + * \brief Calculates biconnected components. + * + * A graph is biconnected if the removal of any single vertex (and + * its incident edges) does not disconnect it. + * + * + * A biconnected component of a graph is a maximal biconnected + * subgraph of it. The biconnected components of a graph can be given + * by a partition of its edges: every edge is a member of exactly + * one biconnected component. Note that this is not true for + * vertices: the same vertex can be part of many biconnected + * components, while isolated vertices are part of none at all. + * + * + * Note that some authors do not consider the graph consisting of + * two connected vertices as biconnected, however, igraph does. + * + * + * igraph does not consider components containing a single vertex only as + * being biconnected. Isolated vertices will not be part of any of the + * biconnected components. This means that checking whether there is a single + * biconnected component is not sufficient for determining if a graph is + * biconnected. Use \ref igraph_is_biconnected() for this purpose. + * + * \param graph The input graph. It will be treated as undirected. + * \param no If not a \c NULL pointer, the number of biconnected components will + * be stored here. + * \param tree_edges If not a \c NULL pointer, then the found components + * are stored here, in a list of vectors. Every vector in the list + * is a biconnected component, represented by its edges. More precisely, + * a spanning tree of the biconnected component is returned. + * \param component_edges If not a \c NULL pointer, then the edges of the + * biconnected components are stored here, in the same form as for + * \c tree_edges. + * \param components If not a \c NULL pointer, then the vertices of the + * biconnected components are stored here, in the same format as + * for the previous two arguments. + * \param articulation_points If not a NULL pointer, then the + * articulation points of the graph are stored in this vector. + * A vertex is an articulation point if its removal increases the + * number of (weakly) connected components in the graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges, but only if you do not calculate \p components and + * \p component_edges. If you calculate \p components, then it is + * quadratic in the number of vertices. If you calculate + * \p component_edges as well, then it is cubic in the number of + * vertices. + * + * \sa \ref igraph_articulation_points(), \ref igraph_is_biconnected(), + * \ref igraph_connected_components(). + * + * \example examples/simple/igraph_biconnected_components.c + */ + +igraph_error_t igraph_biconnected_components(const igraph_t *graph, + igraph_int_t *no, + igraph_vector_int_list_t *tree_edges, + igraph_vector_int_list_t *component_edges, + igraph_vector_int_list_t *components, + igraph_vector_int_t *articulation_points) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t nextptr; + igraph_vector_int_t num, low; + igraph_bitset_t found; + igraph_vector_int_t *adjedges; + igraph_stack_int_t path; + igraph_stack_int_t edgestack; + igraph_inclist_t inclist; + igraph_int_t counter, rootdfs = 0; + igraph_vector_int_t vertex_added; + igraph_int_t comps = 0; + igraph_vector_int_list_t *mycomponents = components, vcomponents; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&nextptr, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&num, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&low, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&found, no_of_nodes); + + IGRAPH_STACK_INT_INIT_FINALLY(&path, 100); + IGRAPH_STACK_INT_INIT_FINALLY(&edgestack, 100); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_added, no_of_nodes); + + if (no) { + *no = 0; + } + if (tree_edges) { + igraph_vector_int_list_clear(tree_edges); + } + if (components) { + igraph_vector_int_list_clear(components); + } + if (component_edges) { + igraph_vector_int_list_clear(component_edges); + } + if (articulation_points) { + igraph_vector_int_clear(articulation_points); + } + if (component_edges && !components) { + mycomponents = &vcomponents; + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(mycomponents, 0); + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + + if (VECTOR(low)[i] != 0) { + continue; /* already visited */ + } + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_stack_int_push(&path, i)); + counter = 1; + rootdfs = 0; + VECTOR(low)[i] = VECTOR(num)[i] = counter++; + while (!igraph_stack_int_empty(&path)) { + igraph_int_t n; + igraph_int_t act = igraph_stack_int_top(&path); + igraph_int_t actnext = VECTOR(nextptr)[act]; + + adjedges = igraph_inclist_get(&inclist, act); + n = igraph_vector_int_size(adjedges); + if (actnext < n) { + /* Step down (maybe) */ + igraph_int_t edge = VECTOR(*adjedges)[actnext]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, act); + if (VECTOR(low)[nei] == 0) { + if (act == i) { + rootdfs++; + } + IGRAPH_CHECK(igraph_stack_int_push(&edgestack, edge)); + IGRAPH_CHECK(igraph_stack_int_push(&path, nei)); + VECTOR(low)[nei] = VECTOR(num)[nei] = counter++; + } else { + /* Update low value if needed */ + if (VECTOR(num)[nei] < VECTOR(low)[act]) { + VECTOR(low)[act] = VECTOR(num)[nei]; + } + } + VECTOR(nextptr)[act] += 1; + } else { + /* Step up */ + igraph_stack_int_pop(&path); + if (!igraph_stack_int_empty(&path)) { + igraph_int_t prev = igraph_stack_int_top(&path); + /* Update LOW value if needed */ + if (VECTOR(low)[act] < VECTOR(low)[prev]) { + VECTOR(low)[prev] = VECTOR(low)[act]; + } + /* Check for articulation point */ + if (VECTOR(low)[act] >= VECTOR(num)[prev]) { + if (articulation_points && !IGRAPH_BIT_TEST(found, prev) + && prev != i /* the root */) { + IGRAPH_CHECK(igraph_vector_int_push_back(articulation_points, prev)); + IGRAPH_BIT_SET(found, prev); + } + if (no) { + *no += 1; + } + + /*------------------------------------*/ + /* Record the biconnected component just found */ + if (tree_edges || mycomponents) { + igraph_vector_int_t *v, *v2; + comps++; + if (tree_edges) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(tree_edges, &v)); + } + if (mycomponents) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(mycomponents, &v2)); + } + + while (!igraph_stack_int_empty(&edgestack)) { + igraph_int_t e = igraph_stack_int_pop(&edgestack); + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + if (tree_edges) { + IGRAPH_CHECK(igraph_vector_int_push_back(v, e)); + } + if (mycomponents) { + if (VECTOR(vertex_added)[from] != comps) { + VECTOR(vertex_added)[from] = comps; + IGRAPH_CHECK(igraph_vector_int_push_back(v2, from)); + } + if (VECTOR(vertex_added)[to] != comps) { + VECTOR(vertex_added)[to] = comps; + IGRAPH_CHECK(igraph_vector_int_push_back(v2, to)); + } + } + if (from == prev || to == prev) { + break; + } + } + + if (component_edges) { + igraph_vector_int_t *nodes = igraph_vector_int_list_get_ptr(mycomponents, comps - 1); + igraph_int_t ii, no_vert = igraph_vector_int_size(nodes); + igraph_vector_int_t *vv; + + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(component_edges, &vv)); + for (ii = 0; ii < no_vert; ii++) { + igraph_int_t vert = VECTOR(*nodes)[ii]; + igraph_vector_int_t *edges = igraph_inclist_get(&inclist, + vert); + igraph_int_t j, nn = igraph_vector_int_size(edges); + for (j = 0; j < nn; j++) { + igraph_int_t e = VECTOR(*edges)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, e, vert); + if (VECTOR(vertex_added)[nei] == comps && nei < vert) { + IGRAPH_CHECK(igraph_vector_int_push_back(vv, e)); + } + } + } + } + } /* record component if requested */ + /*------------------------------------*/ + + } + } /* !igraph_stack_int_empty(&path) */ + } + + } /* !igraph_stack_int_empty(&path) */ + + if (articulation_points && rootdfs >= 2) { + IGRAPH_CHECK(igraph_vector_int_push_back(articulation_points, i)); + } + + } /* i < no_of_nodes */ + + if (mycomponents != components) { + igraph_vector_int_list_destroy(mycomponents); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&vertex_added); + igraph_inclist_destroy(&inclist); + igraph_stack_int_destroy(&edgestack); + igraph_stack_int_destroy(&path); + igraph_bitset_destroy(&found); + igraph_vector_int_destroy(&low); + igraph_vector_int_destroy(&num); + igraph_vector_int_destroy(&nextptr); + IGRAPH_FINALLY_CLEAN(8); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_biconnected + * \brief Checks whether a graph is biconnected. + * + * A graph is biconnected if the removal of any single vertex (and + * its incident edges) does not disconnect it. + * + * + * igraph does not consider single-vertex graphs biconnected. + * + * + * Note that some authors do not consider the graph consisting of + * two connected vertices as biconnected, however, igraph does. + * + * \param graph The input graph. It will be treated as undirected. + * \param res If not a \c NULL pointer, the result will be returned here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and edges. + * + * \sa \ref igraph_articulation_points(), \ref igraph_biconnected_components(). + * + * \example examples/simple/igraph_is_biconnected.c + */ + +igraph_error_t igraph_is_biconnected(const igraph_t *graph, igraph_bool_t *res) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t nextptr; + igraph_vector_int_t num, low; + igraph_stack_int_t path; + igraph_lazy_adjlist_t inclist; + igraph_bool_t is_biconnected = true; + + if (no_of_nodes == 0 || no_of_nodes == 1) { + /* The null graph is not connected, hence it is not biconnected either. + * The singleton graph is not biconnected. */ + is_biconnected = false; + goto exit2; + } + + /* no_of_nodes == 2 is special: if the two nodes are connected, then the + * graph is both biconnected _and_ acyclic, unlike no_of_nodes >= 3, where + * the graph is not acyclic if it is biconnected. */ + + /* We do not touch the cache for graphs with less than three nodes because + * of all the edge cases. */ + if (no_of_nodes >= 3 && ( + (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED)) || + (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST)) + )) { + is_biconnected = false; + goto exit2; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&nextptr, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&num, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&low, no_of_nodes); + + IGRAPH_STACK_INT_INIT_FINALLY(&path, 100); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inclist); + + const igraph_int_t root = 0; /* start DFS from vertex 0 */ + igraph_int_t counter = 1; + igraph_int_t rootdfs = 0; + IGRAPH_CHECK(igraph_stack_int_push(&path, root)); + VECTOR(low)[root] = VECTOR(num)[root] = counter++; + while (!igraph_stack_int_empty(&path)) { + igraph_int_t act = igraph_stack_int_top(&path); + igraph_int_t actnext = VECTOR(nextptr)[act]; + + const igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&inclist, act); + const igraph_int_t n = igraph_vector_int_size(neis); + if (actnext < n) { + /* Step down (maybe) */ + igraph_int_t nei = VECTOR(*neis)[actnext]; + if (VECTOR(low)[nei] == 0) { + if (act == root) { + rootdfs++; + } + IGRAPH_CHECK(igraph_stack_int_push(&path, nei)); + VECTOR(low)[nei] = VECTOR(num)[nei] = counter++; + } else { + /* Update low value if needed */ + if (VECTOR(num)[nei] < VECTOR(low)[act]) { + VECTOR(low)[act] = VECTOR(num)[nei]; + } + } + VECTOR(nextptr)[act] += 1; + } else { + /* Step up */ + igraph_stack_int_pop(&path); + if (!igraph_stack_int_empty(&path)) { + igraph_int_t prev = igraph_stack_int_top(&path); + /* Update LOW value if needed */ + if (VECTOR(low)[act] < VECTOR(low)[prev]) { + VECTOR(low)[prev] = VECTOR(low)[act]; + } + /* Check for articulation point */ + if (VECTOR(low)[act] >= VECTOR(num)[prev]) { + if (prev != root /* the root */) { + /* Found an articulation point, the graph is not biconnected */ + is_biconnected = false; + goto exit; + } + } + } /* !igraph_stack_int_empty(&path) */ + } + + } /* !igraph_stack_int_empty(&path) */ + + /* The root is an articulation point, the graph is not biconnected */ + if (rootdfs >= 2) { + is_biconnected = false; + goto exit; + } + + /* We did not reach all vertices, the graph is not connected */ + if (counter <= no_of_nodes) { + is_biconnected = false; + goto exit; + } + +exit: + + igraph_lazy_adjlist_destroy(&inclist); + igraph_stack_int_destroy(&path); + igraph_vector_int_destroy(&low); + igraph_vector_int_destroy(&num); + igraph_vector_int_destroy(&nextptr); + IGRAPH_FINALLY_CLEAN(5); + +exit2: + + if (res) { + *res = is_biconnected; + } + + /* We do not touch the cache for graphs with less than three nodes because + * of all the edge cases. */ + if (is_biconnected && no_of_nodes >= 3) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, true); + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, false); + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_bridges + * \brief Finds all bridges in a graph. + * + * An edge is a bridge if its removal increases the number of (weakly) + * connected components in the graph. + * + * \param graph The input graph. It will be treated as undirected. + * \param bridges Pointer to an initialized vector, the + * bridges will be stored here as edge indices. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and edges. + * + * \sa \ref igraph_articulation_points(), \ref igraph_biconnected_components(), + * \ref igraph_connected_components() + */ + +igraph_error_t igraph_bridges(const igraph_t *graph, igraph_vector_int_t *bridges) { + + /* The algorithm is based on https://www.geeksforgeeks.org/bridge-in-a-graph/ + but instead of keeping track of the parent of each vertex in the DFS tree + we keep track of its incoming edge. This is necessary to support multigraphs. + Additionally, we use explicit stacks instead of recursion to avoid + stack overflow. */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_inclist_t il; + igraph_bitset_t visited; + igraph_vector_int_t vis; /* vis[u] time when vertex u was first visited */ + igraph_vector_int_t low; /* low[u] is the lowest visit time of vertices reachable from u */ + igraph_vector_int_t incoming_edge; + igraph_stack_int_t su, si; + igraph_int_t time; + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + + IGRAPH_BITSET_INIT_FINALLY(&visited, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vis, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&low, no_of_nodes); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&incoming_edge, no_of_nodes); + igraph_vector_int_fill(&incoming_edge, -1); + + IGRAPH_STACK_INT_INIT_FINALLY(&su, 0); + IGRAPH_STACK_INT_INIT_FINALLY(&si, 0); + + igraph_vector_int_clear(bridges); + + time = 0; + for (igraph_int_t start = 0; start < no_of_nodes; ++start) { + if (! IGRAPH_BIT_TEST(visited, start)) { + /* Perform a DFS from 'start'. + * The top of the su stack is u, the vertex currently being visited. + * The top of the si stack is i, the index of u's neighbour that will + * be processed next. */ + + IGRAPH_CHECK(igraph_stack_int_push(&su, start)); + IGRAPH_CHECK(igraph_stack_int_push(&si, 0)); + + while (! igraph_stack_int_empty(&su)) { + igraph_int_t u = igraph_stack_int_pop(&su); + igraph_int_t i = igraph_stack_int_pop(&si); + + if (i == 0) { + /* We are at the first step of visiting vertex u. */ + + IGRAPH_BIT_SET(visited, u); + + time += 1; + + VECTOR(vis)[u] = time; + VECTOR(low)[u] = time; + } + + igraph_vector_int_t *incedges = igraph_inclist_get(&il, u); + + if (i < igraph_vector_int_size(incedges)) { + IGRAPH_CHECK(igraph_stack_int_push(&su, u)); + IGRAPH_CHECK(igraph_stack_int_push(&si, i+1)); + + igraph_int_t edge = VECTOR(*incedges)[i]; + igraph_int_t v = IGRAPH_OTHER(graph, edge, u); + + if (! IGRAPH_BIT_TEST(visited, v)) { + VECTOR(incoming_edge)[v] = edge; + + IGRAPH_CHECK(igraph_stack_int_push(&su, v)); + IGRAPH_CHECK(igraph_stack_int_push(&si, 0)); + } else if (edge != VECTOR(incoming_edge)[u]) { + VECTOR(low)[u] = VECTOR(low)[u] < VECTOR(vis)[v] ? VECTOR(low)[u] : VECTOR(vis)[v]; + } + } else { + /* We are done visiting vertex u, so it won't be put back on the stack. + * We are ready to update the 'low' value of its parent w, and decide + * whether its incoming edge is a bridge. */ + + igraph_int_t edge = VECTOR(incoming_edge)[u]; + if (edge >= 0) { + igraph_int_t w = IGRAPH_OTHER(graph, edge, u); /* parent of u in DFS tree */ + VECTOR(low)[w] = VECTOR(low)[w] < VECTOR(low)[u] ? VECTOR(low)[w] : VECTOR(low)[u]; + if (VECTOR(low)[u] > VECTOR(vis)[w]) { + IGRAPH_CHECK(igraph_vector_int_push_back(bridges, edge)); + } + } + } + } + } + } + + igraph_stack_int_destroy(&si); + igraph_stack_int_destroy(&su); + igraph_vector_int_destroy(&incoming_edge); + igraph_vector_int_destroy(&low); + igraph_vector_int_destroy(&vis); + igraph_bitset_destroy(&visited); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_subcomponent + * \brief The vertices reachable from a given vertex. + * + * This function returns the set of vertices reachable from a specified + * vertex. In undirected graphs, this is simple the set of vertices within + * the same component. + * + * \param graph The graph object. + * \param res The result, vector with the IDs of the vertices reachable + * from \p vertex. + * \param vertex The id of the vertex of which the component is + * searched. + * \param mode Type of the component for directed graphs, possible + * values: + * \clist + * \cli IGRAPH_OUT + * the set of vertices reachable \em from the + * \p vertex, + * \cli IGRAPH_IN + * the set of vertices from which the + * \p vertex is reachable. + * \cli IGRAPH_ALL + * the graph is considered as an + * undirected graph. Note that this is \em not the same + * as the union of the previous two. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p vertex is an invalid vertex ID + * \cli IGRAPH_EINVMODE + * invalid mode argument passed. + * \endclist + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_induced_subgraph() if you want a graph object consisting only + * a given set of vertices and the edges between them; + * \ref igraph_reachability() to efficiently compute the reachable set from \em all + * vertices; \ref igraph_neighborhood() to find vertices within a given distance. + */ +igraph_error_t igraph_subcomponent( + const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t vertex, + igraph_neimode_t mode +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_bitset_t already_added; + igraph_int_t i, vsize; + igraph_vector_int_t tmp = IGRAPH_VECTOR_NULL; + + if (vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("Vertex id out of range.", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + igraph_vector_int_clear(res); + + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, vertex)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, vertex)); + IGRAPH_BIT_SET(already_added, vertex); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_neighbors(graph, &tmp, actnode, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); + vsize = igraph_vector_int_size(&tmp); + for (i = 0; i < vsize; i++) { + igraph_int_t neighbor = VECTOR(tmp)[i]; + + if (IGRAPH_BIT_TEST(already_added, neighbor)) { + continue; + } + IGRAPH_BIT_SET(already_added, neighbor); + IGRAPH_CHECK(igraph_vector_int_push_back(res, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + } + } + + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&tmp); + igraph_bitset_destroy(&already_added); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/connectivity/percolation.c b/src/connectivity/percolation.c new file mode 100644 index 0000000..7534a97 --- /dev/null +++ b/src/connectivity/percolation.c @@ -0,0 +1,404 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_components.h" + +#include "igraph_bitset.h" +#include "igraph_constants.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "core/interruption.h" + +/** + * \function percolate_edge + * \brief Percolates a single edge. + * + * \param links Vector representing parents. + * \param sizes sizes[i] is the number of children of links[i] + * \param biggest The biggest value in sizes, is updated if a bigger cluster is created. + * \param a A vertex incident to the edge. + * \param b The other vertex incident to the edge. + */ + +static void percolate_edge(igraph_vector_int_t *links, + igraph_vector_int_t *sizes, + igraph_int_t *biggest, + igraph_int_t a, + igraph_int_t b) { + + // Find head of each tree + while (VECTOR(*links)[a] != a) { + VECTOR(*links)[a] = VECTOR(*links)[VECTOR(*links)[a]]; + a = VECTOR(*links)[a]; + } + while (VECTOR(*links)[b] != b) { + VECTOR(*links)[b] = VECTOR(*links)[VECTOR(*links)[b]]; + b = VECTOR(*links)[b]; + } + + // If they are already connected, exit early + if (a == b) { + return; + } + + // Make smaller child of larger + igraph_int_t parent, child; + if (VECTOR(*sizes)[a] < VECTOR(*sizes)[b]) { + parent = b; + child = a; + } else { + parent = a; + child = b; + } + + VECTOR(*links)[child] = parent; + VECTOR(*sizes)[parent] += VECTOR(*sizes)[child]; + + // If made new biggest component, update biggest + if (VECTOR(*sizes)[parent] >= *biggest) { + *biggest = VECTOR(*sizes)[parent]; + } +} + +/** + * \function igraph_edgelist_percolation + * \brief The size of the largest component as vertex pairs are connected. + * + * \experimental + * + * Calculates the size of the largest connected component as edges are added + * to a graph in the given order. This function differs from + * \ref igraph_bond_percolation() in that it take a list of vertex pairs as input. + * + * \param edges Vector of edges, where the i-th edge has endpoints + * edges[2i] and edges[2i+1]. + * \param giant_size giant_size[i] will contain the size of the + * largest connected component after edge \c i is added. + * \param vertex_count vertex_count[i] will contain the number of + * vertices with at least one edge after edge \c i is added. + * \return Error code. + * + * \sa \ref igraph_bond_percolation() to specify edges by their ID in a graph object. + * + * Time complexity: O(|E| a(|E|)) where a is the inverse Ackermann function, + * for all practical purposes it is not above 5. + */ + +igraph_error_t igraph_edgelist_percolation( + const igraph_vector_int_t *edges, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count) { + + igraph_int_t biggest = 1; + igraph_int_t vertices_added = 0; + igraph_int_t lower, upper; + int iter = 0; + + igraph_int_t ecount = igraph_vector_int_size(edges); + + if (ecount % 2 == 1) { + IGRAPH_ERROR("Invalid edge list, odd number of elements.", IGRAPH_EINVAL); + } + ecount = ecount / 2; + + if (giant_size != NULL) { + IGRAPH_CHECK(igraph_vector_int_resize(giant_size, ecount)); + } + if (vertex_count != NULL) { + IGRAPH_CHECK(igraph_vector_int_resize(vertex_count, ecount)); + } + + // Handle edge case of no edges. + if (ecount == 0) { + return IGRAPH_SUCCESS; + } + + igraph_vector_int_minmax(edges, &lower, &upper); + + if (lower < 0) { + IGRAPH_ERROR("Invalid vertex ID.", IGRAPH_EINVVID); + } + + const igraph_int_t vcount = upper + 1; + + igraph_vector_int_t sizes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&sizes, vcount); + + igraph_vector_int_t links; + IGRAPH_VECTOR_INT_INIT_FINALLY(&links, vcount); + + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(sizes)[i] = -1; + VECTOR(links)[i] = i; + } + + for (igraph_int_t i = 0; i < ecount; i++) { + const igraph_int_t from = VECTOR(*edges)[2*i]; + const igraph_int_t to = VECTOR(*edges)[2*i + 1]; + if (VECTOR(sizes)[from] == -1) { + vertices_added++; + VECTOR(sizes)[from] = 1; + } + if (VECTOR(sizes)[to] == -1) { + vertices_added++; + VECTOR(sizes)[to] = 1; + } + percolate_edge(&links, &sizes, &biggest, from, to); + if (giant_size != NULL) { + VECTOR(*giant_size)[i] = biggest; + } + if (vertex_count != NULL) { + VECTOR(*vertex_count)[i] = vertices_added; + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + igraph_vector_int_destroy(&links); + igraph_vector_int_destroy(&sizes); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bond_percolation + * \brief The size of the largest component as edges are added to a graph. + * + * \experimental + * + * Calculates the bond percolation curve, i.e. the size of the largest connected + * component as edges are added to the graph in the order given. If both + * \p giant_size and \p edge_order are reversed, it is the size of the largest + * component as edges are removed from the graph. If no edge order is given, + * a random one will be used. + * + * \param graph The graph that edges are assumed to be in. Edge directions + * are ignored. + * \param giant_size giant_size[i] will contain the size of the + * largest component after having added the edge with index + * edge_order[i]. + * \param vertex_count vertex_count[i] will contain the number + * of vertices that have at least one incident edge after adding the edge + * with index edge_order[i]. + * \param edge_order The order the edges are added in. Must not contain duplicates. + * If \c NULL, a random order will be used. + * \return Error code. + * + * \sa \ref igraph_edgelist_percolation() to specify the edges to be added by + * their endpoints; \ref igraph_site_percolation() to compute the vertex percolation + * curve; \ref igraph_connected_components() to find the size of connected components. + * + * Time complexity: O(|V| + |E| a(|E|)) where a is the inverse Ackermann function, + * for all practical purposes it is not above 5. + */ + +igraph_error_t igraph_bond_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *vertex_count, + const igraph_vector_int_t *edge_order) { + + const igraph_vector_int_t *p_edge_order; + igraph_vector_int_t i_edge_order; + igraph_vector_int_t edges; + + // Use a random edge order when no edge order was given + if (edge_order == NULL) { + IGRAPH_CHECK(igraph_vector_int_init_range(&i_edge_order, 0, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_vector_int_destroy, &i_edge_order); + igraph_vector_int_shuffle(&i_edge_order); + p_edge_order = &i_edge_order; + } else { + // Verify that there are no duplicates. + const igraph_int_t no_of_added_edges = igraph_vector_int_size(edge_order); + igraph_bitset_t present_edges; + + IGRAPH_BITSET_INIT_FINALLY(&present_edges, no_of_added_edges); + + for (igraph_int_t i = 0; i < no_of_added_edges; i++) { + if (IGRAPH_BIT_TEST(present_edges, VECTOR(*edge_order)[i])) { + IGRAPH_ERROR("Duplicate edges in edge order vector.", IGRAPH_EINVAL); + } + IGRAPH_BIT_SET(present_edges, VECTOR(*edge_order)[i]); + } + igraph_bitset_destroy(&present_edges); + IGRAPH_FINALLY_CLEAN(1); + p_edge_order = edge_order; + } + + // Initialize edge list. igraph_edges() will validate edge IDs. + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2 * igraph_vector_int_size(p_edge_order)); + IGRAPH_CHECK(igraph_edges(graph, igraph_ess_vector(p_edge_order), &edges, /* bycol = */ 0)); + + // Defer to igraph_edgelist_percolation() + IGRAPH_CHECK(igraph_edgelist_percolation(&edges, giant_size, vertex_count)); + + // Cleanup + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + if (edge_order == NULL) { + igraph_vector_int_destroy(&i_edge_order); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t percolate_site(const igraph_t *graph, + igraph_vector_int_t *links, + igraph_vector_int_t *sizes, + igraph_int_t *biggest, + igraph_int_t *edges_added, + igraph_int_t vertex, + igraph_vector_int_t *neighbors) { + + if (VECTOR(*sizes)[vertex] != 0) { + IGRAPH_ERROR("Duplicate vertices in vertex order vector.", IGRAPH_EINVAL); + } + + VECTOR(*sizes)[vertex] = 1; + + IGRAPH_CHECK(igraph_neighbors(graph, neighbors, vertex, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + + igraph_int_t neighbor_count = igraph_vector_int_size(neighbors); + for (igraph_int_t i = 0; i < neighbor_count; i++) { + // Do not add edges to vertices that have not been added. + if (VECTOR(*sizes)[VECTOR(*neighbors)[i]] == 0) { + continue; + } + *edges_added += 1; + percolate_edge(links, sizes, biggest, vertex, VECTOR(*neighbors)[i]); + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_site_percolation + * \brief The size of the largest component as vertices are added to a graph. + * + * \experimental + * + * Calculates the site percolation curve, i.e. the size of the largest connected + * component as vertices are added in the given order. If both \p giant_size + * and \p vertex_order are reversed, it is the size of the largest component + * as vertices are removed from the graph. If no vertex order is given, a random + * one will be used. + * + * \param graph The graph that vertices are assumed to be in. Edge directions + * are ignored. + * \param giant_size giant_size[i] will contain the size of the + * largest component after having added the vertex with index + * vertex_order[i]. + * \param edge_count edge_count[i] will contain the numer of edges + * in the graph having added the vertex with index + * vertex_order[i]. + * \param vertex_order The order the vertices are added in. Must not contain + * duplicates. If \c NULL, a random order will be used. + * \return Error code. + * + * \sa \ref igraph_bond_percolation() to compute the edge percolation curve; + * \ref igraph_connected_components() to find the size of connected components. + * + * Time complexity: O(|V| + |E| a(|E|)) where a is the inverse Ackermann function, + * for all practical purposes it is not above 5. + */ + +igraph_error_t igraph_site_percolation( + const igraph_t *graph, + igraph_vector_int_t *giant_size, + igraph_vector_int_t *edge_count, + const igraph_vector_int_t *vertex_order) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_vector_int_t *p_vertex_order; + igraph_vector_int_t i_vertex_order; + int iter = 0; + + // Use a random vertex order when no vertex order was given + if (vertex_order == NULL) { + IGRAPH_CHECK(igraph_vector_int_init_range(&i_vertex_order, 0, vcount)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &i_vertex_order); + igraph_vector_int_shuffle(&i_vertex_order); + p_vertex_order = &i_vertex_order; + } else { + p_vertex_order = vertex_order; + } + + // Initialize variables + igraph_int_t number_percolated = igraph_vector_int_size(p_vertex_order); + igraph_int_t biggest = 1; // largest component size so far + igraph_int_t edges_added = 0; // no. of edges added so far + + igraph_vector_int_t sizes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&sizes, vcount); + + igraph_vector_int_t links; + IGRAPH_VECTOR_INT_INIT_FINALLY(&links, vcount); + + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(sizes)[i] = 0; + VECTOR(links)[i] = i; + } + + igraph_vector_int_t neighbors; + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbors, 0); + + if (giant_size != NULL) { + IGRAPH_CHECK(igraph_vector_int_resize(giant_size, number_percolated)); + } + if (edge_count != NULL) { + IGRAPH_CHECK(igraph_vector_int_resize(edge_count, number_percolated)); + } + + // Percolation + for (igraph_int_t i = 0; i < number_percolated; i++) { + const igraph_int_t vid = VECTOR(*p_vertex_order)[i]; + if (vid < 0 || vid >= vcount) { + IGRAPH_ERROR("Invalid vertex ID.", IGRAPH_EINVVID); + } + IGRAPH_CHECK(percolate_site(graph, &links, &sizes, + &biggest, &edges_added, vid, &neighbors)); + if (giant_size != NULL) { + VECTOR(*giant_size)[i] = biggest; + } + if (edge_count != NULL) { + VECTOR(*edge_count)[i] = edges_added; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + // Cleanup + igraph_vector_int_destroy(&neighbors); + igraph_vector_int_destroy(&links); + igraph_vector_int_destroy(&sizes); + IGRAPH_FINALLY_CLEAN(3); + + if (vertex_order == NULL) { + igraph_vector_int_destroy(&i_vertex_order); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/connectivity/reachability.c b/src/connectivity/reachability.c new file mode 100644 index 0000000..06c2aa1 --- /dev/null +++ b/src/connectivity/reachability.c @@ -0,0 +1,257 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_reachability.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset_list.h" +#include "igraph_components.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" + + +/** + * \ingroup structural + * \function igraph_reachability + * \brief Calculates which vertices are reachable from each vertex in the graph. + * + * The resulting list will contain one bitset for each strongly connected component. + * The bitset for component i will have its j-th bit set, if vertex j is reachable + * from some vertex in component i in 0 or more steps. + * In particular, a vertex is always reachable from itself. + * + * \param graph The graph object to analyze. + * \param membership Pointer to an integer vector. For every vertex, + * the ID of its component is given. The vector will be resized as needed. + * This parameter must not be \c NULL. + * \param csize Pointer to an integer vector or \c NULL. For every component, it + * gives its size (vertex count), the order being defined by the component + * IDs. The vector will be resized as needed. + * \param no_of_components Pointer to an integer or \c NULL. The number of + * components will be stored here. + * \param reach A list of bitsets representing the result. It will be resized + * as needed. reach[membership[u]][v] is set to \c true if + * vertex \c v is reachable from vertex \c u. + * \param mode In directed graphs, controls the treatment of edge directions. + * Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed + * by traversing edges along their direction. With \c IGRAPH_IN, edges are + * traversed opposite to their direction. With \c IGRAPH_ALL, edge directions + * are ignored and the graph is treated as undirected. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory + * to perform the operation. + * + * \sa \ref igraph_connected_components() to find the connnected components + * of a graph; \ref igraph_count_reachable() to count how many vertices + * are reachable from each vertex; \ref igraph_subcomponent() to find + * which vertices are rechable from a single vertex. + * + * Time complexity: O(|C||V|/w + |V| + |E|), where + * |C| is the number of strongly connected components (at most |V|), + * |V| is the number of vertices, and + * |E| is the number of edges respectively, + * and w is the bit width of \type igraph_int_t, typically the + * word size of the machine (32 or 64). + */ + +igraph_error_t igraph_reachability( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_int_t *csize, + igraph_int_t *no_of_components, + igraph_bitset_list_t *reach, + igraph_neimode_t mode) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_comps; + igraph_adjlist_t adjlist, dag; + + if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) { + IGRAPH_ERROR("Invalid mode for reachability.", IGRAPH_EINVMODE); + } + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_connected_components(graph, + membership, csize, &no_of_comps, + mode == IGRAPH_ALL ? IGRAPH_WEAK : IGRAPH_STRONG)); + + if (no_of_components) { + *no_of_components = no_of_comps; + } + + IGRAPH_CHECK(igraph_bitset_list_resize(reach, no_of_comps)); + + for (igraph_int_t comp = 0; comp < no_of_comps; comp++) { + IGRAPH_CHECK(igraph_bitset_resize(igraph_bitset_list_get_ptr(reach, comp), no_of_nodes)); + } + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + IGRAPH_BIT_SET(*igraph_bitset_list_get_ptr(reach, VECTOR(*membership)[v]), v); + } + + if (mode == IGRAPH_ALL) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_adjlist_init_empty(&dag, no_of_comps)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &dag); + + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + const igraph_vector_int_t *neighbours = igraph_adjlist_get(&adjlist, v); + igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, VECTOR(*membership)[v]); + const igraph_int_t n = igraph_vector_int_size(neighbours); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t w = VECTOR(*neighbours)[i]; + if (VECTOR(*membership)[v] != VECTOR(*membership)[w]) { + IGRAPH_CHECK(igraph_vector_int_push_back(dag_neighbours, VECTOR(*membership)[w])); + } + } + } + + /* Iterate through strongly connected components in reverser topological order, + * exploiting the fact that they are indexed in topological order. */ + for (igraph_int_t i = 0; i < no_of_comps; i++) { + const igraph_int_t comp = mode == IGRAPH_IN ? i : no_of_comps - i - 1; + const igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, comp); + igraph_bitset_t *from_bitset = igraph_bitset_list_get_ptr(reach, comp); + const igraph_int_t n = igraph_vector_int_size(dag_neighbours); + for (igraph_int_t j = 0; j < n; j++) { + const igraph_bitset_t *to_bitset = igraph_bitset_list_get_ptr(reach, VECTOR(*dag_neighbours)[j]); + igraph_bitset_or(from_bitset, from_bitset, to_bitset); + } + } + + igraph_adjlist_destroy(&adjlist); + igraph_adjlist_destroy(&dag); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_count_reachable + * \brief The number of vertices reachable from each vertex in the graph. + * + * \param graph The graph object to analyze. + * \param counts Integer vector. counts[v] will store the number + * of vertices reachable from vertex \c v, including \c v itself. + * \param mode In directed graphs, controls the treatment of edge directions. + * Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed + * by traversing edges along their direction. With \c IGRAPH_IN, edges are + * traversed opposite to their direction. With \c IGRAPH_ALL, edge directions + * are ignored and the graph is treated as undirected. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory + * to perform the operation. + * + * \sa \ref igraph_connected_components(), \ref igraph_transitive_closure() + * + * Time complexity: O(|C||V|/w + |V| + |E|), where + * |C| is the number of strongly connected components (at most |V|), + * |V| is the number of vertices, and + * |E| is the number of edges respectively, + * and w is the bit width of \type igraph_int_t, typically the + * word size of the machine (32 or 64). + */ + +igraph_error_t igraph_count_reachable(const igraph_t *graph, + igraph_vector_int_t *counts, + igraph_neimode_t mode) { + + igraph_vector_int_t membership; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_bitset_list_t reach; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0); + + IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, mode)); + + IGRAPH_CHECK(igraph_vector_int_resize(counts, igraph_vcount(graph))); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(*counts)[i] = igraph_bitset_popcount(igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[i])); + } + + igraph_bitset_list_destroy(&reach); + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_transitive_closure + * \brief Computes the transitive closure of a graph. + * + * The resulting graph will have an edge from vertex \c i to vertex \c j + * if \c j is reachable from \c i. + * + * \param graph The graph object to analyze. + * \param closure The resulting graph representing the transitive closure. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory + * to perform the operation. + * + * \sa \ref igraph_connected_components(), \ref igraph_count_reachable() + * + * Time complexity: O(|V|^2 + |E|), where + * |V| is the number of vertices, and + * |E| is the number of edges, respectively. + */ +igraph_error_t igraph_transitive_closure(const igraph_t *graph, igraph_t *closure) { + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_int_t membership, edges; + igraph_bitset_list_t reach; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0); + + IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, IGRAPH_OUT)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + for (igraph_int_t u = 0; u < no_of_nodes; u++) { + const igraph_bitset_t *row = igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[u]); + for (igraph_int_t v = directed ? 0 : u + 1; v < no_of_nodes; v++) { + if (u != v && IGRAPH_BIT_TEST(*row, v)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, u)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v)); + } + } + } + + igraph_bitset_list_destroy(&reach); + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(closure, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/connectivity/separators.c b/src/connectivity/separators.c new file mode 100644 index 0000000..5c52d21 --- /dev/null +++ b/src/connectivity/separators.c @@ -0,0 +1,887 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_separators.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_flow.h" +#include "igraph_interface.h" +#include "igraph_operators.h" +#include "igraph_structural.h" +#include "igraph_vector.h" + +#include "core/interruption.h" + + +/** + * \function igraph_i_is_separator + * + * Checks if vertex set \c S is a separator. Optionally, also checks if it + * is a _minimal_ separator. + * + * \param graph The graph, edge directions are ignored. + * \param S Candidate vertex set. + * \param is_separator Pointer to boolean, is S a separator? + * \param is_minimal If not \c NULL, it is also checked whether the separator + * is minimal. This takes additional time. If S is not a separator, this is + * set to false + */ +static igraph_error_t igraph_i_is_separator( + const igraph_t *graph, + igraph_vs_t S, + igraph_bool_t *is_separator, + igraph_bool_t *is_minimal +) { + + const igraph_int_t vcount = igraph_vcount(graph); + + /* mark[v] means: + * + * bit 0: Was v visited? + * bit 1: Is v in S? + * bit 2: Used to keep track of which vertices were reachable in S + * from its neighbourhood in the minimal separator test. + */ + igraph_vector_char_t mark; + + igraph_vector_int_t neis; + igraph_dqueue_int_t Q; + igraph_vit_t vit; + igraph_int_t S_size = 0, S_visited_count = 0; + + IGRAPH_CHECK(igraph_vit_create(graph, S, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&mark, vcount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 10); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100); + +#define VISITED(x) (VECTOR(mark)[x] & 1) /* Was x visited? */ +#define SET_VISITED(x) (VECTOR(mark)[x] |= 1) /* Mark x as visited. */ +#define IN_S(x) (VECTOR(mark)[x] & 2) /* Is x in S? */ +#define SET_IN_S(x) (VECTOR(mark)[x] |= 2) /* Mark x as being in S. */ + + /* Mark and count vertices in S, taking care not to double-count + * when duplicate vertices were passed in. */ + for (; ! IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + const igraph_int_t u = IGRAPH_VIT_GET(vit); + if (! IN_S(u)) { + SET_IN_S(u); + S_size++; + } + } + + /* If S contains more than |V| - 2 vertices, it is not a separator. */ + if (S_size > vcount-2) { + *is_separator = false; + goto done; + } + + /* Assume that S is not a separator until proven otherwise. */ + *is_separator = false; + + for (IGRAPH_VIT_RESET(vit); ! IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + + const igraph_int_t u = IGRAPH_VIT_GET(vit); + if (VISITED(u)) { + continue; + } + + /* For the sake of the following discussion, and counting of degrees, + * let us consider each undirected edge as a pair of directed ones. + */ + + /* We start at a vertex in S, and traverse the graph with the following + * restriction: + * + * Once we exited S through an edge, we may not exit through + * any other edge again (but we may re-enter S). With this traversal, + * we can determine: once we find ourselves outside of S, are there + * any vertices which we can only reach by passing through S again? + * + * Theorem: The visited vertices in S constitute a separating set if and + * only if when the traversal is complete, some of them still have + * untraversed out-edges. + * + * Note that this refers only to the subset of S visited during this + * traversal, not the rest of S. If there are remaining vertices in S, + * they may constitute a separating set as well. We cannot conclude that + * S is NOT a separator until all its vertices have been considered, hence + * the outer for-loop. + */ + + /* Sum of in-degrees of visited vertices in S. */ + igraph_int_t degsum = 0; + + /* Number of in-edges of vertices in S that were traversed. */ + igraph_int_t edgecount = 0; + + /* Have we already traversed an edge leaving S? */ + igraph_bool_t exited = false; + + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, u)); + + while (! igraph_dqueue_int_empty(&Q)) { + const igraph_int_t v = igraph_dqueue_int_pop(&Q); + if (VISITED(v)) { + continue; + } + + SET_VISITED(v); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + + const igraph_int_t dv = igraph_vector_int_size(&neis); + + if (IN_S(v)) { + degsum += dv; + S_visited_count++; + } + + for (igraph_int_t i=0; i < dv; i++) { + const igraph_int_t w = VECTOR(neis)[i]; + + /* Decide whether to traverse the v -> w edge. */ + if (!exited || !IN_S(v) || IN_S(w)) { + if (IN_S(w)) { + edgecount++; + } + + if (!VISITED(w)) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, w)); + } + + if (!exited && IN_S(v) && !IN_S(w)) { + exited = true; + } + } + } + } + + /* If some incident edges of visited vertices in S are + * still untraversed, then S is a separator. We are done. */ + if (degsum > edgecount) { + *is_separator = true; + break; + } + } + +done: + + /* Optionally, check if S is also a _minimal_ separator. */ + if (is_minimal != NULL) { + /* Be optimistic, and assume that if S was found to be a separator, + * it is a minimal separator. */ + *is_minimal = *is_separator; + + /* If S was proven to be a separator before visiting all of its + * vertices, it is not minimal. We are done. */ + if (*is_minimal && S_visited_count < S_size) { + *is_minimal = false; + } + + /* The subset of S with untraversed out-edges forms a separator. + * Therefore, if S contains vertices with no untraversed out-edges, + * S is not minimal. + * + * Suppose it doesn't contain any. + * + * Then for S to be minimal, each of its vertices should be reachable + * from any vertex in the unvisited neighbourhood of S. In order to + * separate a vertex in this neighbourhood, we need to cut precisely + * those vertices in S which are reachable from it. + * + * The check below verifies BOTH of the above conditions by traversing + * the graph from each unvisited neighbour of S with the constraint + * of never entering S. + */ + if (*is_minimal) { + igraph_vector_int_t Sneis; + IGRAPH_VECTOR_INT_INIT_FINALLY(&Sneis, 10); + + /* If the 2nd bit of mark[v] is the same as 'bit', it indicates + * that v in S was reached in the current testing round. + * We flip 'bit' between testing rounds, assuming that the previous + * round reached all of S. */ + igraph_bool_t bit = true; + +#define REACHED(x) (!(VECTOR(mark)[x] & 4) == !bit) /* Was x in S reached already? */ +#define FLIP_REACHED(x) (VECTOR(mark)[x] ^= 4) /* Flip the reachability status of x in S. */ + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + const igraph_int_t u = IGRAPH_VIT_GET(vit); + IGRAPH_CHECK(igraph_neighbors( + graph, &Sneis, u, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + const igraph_int_t du = igraph_vector_int_size(&Sneis); + for (igraph_int_t i=0; i < du; i++) { + + igraph_int_t v = VECTOR(Sneis)[i]; + if (VISITED(v)) { + continue; + } + + /* How many vertices in S were reachable from u? */ + igraph_int_t S_reached = 0; + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, v)); + while (! igraph_dqueue_int_empty(&Q)) { + v = igraph_dqueue_int_pop(&Q); + if (VISITED(v)) { + continue; + } + + SET_VISITED(v); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + + const igraph_int_t dv = igraph_vector_int_size(&neis); + + for (igraph_int_t j=0; j < dv; j++) { + const igraph_int_t w = VECTOR(neis)[j]; + + if (! VISITED(w)) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, w)); + } else if (IN_S(w) && !REACHED(w)) { + S_reached++; + FLIP_REACHED(w); /* set as reachable */ + } + } + } + + bit = !bit; + + if (S_reached < S_size) { + *is_minimal = false; + break; + } + } + + if (! *is_minimal) { + break; + } + } + + igraph_vector_int_destroy(&Sneis); + IGRAPH_FINALLY_CLEAN(1); + } + } + +#undef REACHED +#undef FLIP_REACHED +#undef VISITED +#undef SET_VISITED +#undef IN_S +#undef SET_IN_S + + + igraph_dqueue_int_destroy(&Q); + igraph_vector_int_destroy(&neis); + igraph_vector_char_destroy(&mark); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_separator + * \brief Would removing this set of vertices disconnect the graph? + * + * A vertex set \c S is a separator if there are vertices \c u and \c v + * in the graph such that all paths between \c u and \c v pass through + * some vertices in \c S. + * + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param candidate The candidate separator. + * \param res Pointer to a boolean variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number vertices and edges. + * + * \example examples/simple/igraph_is_separator.c + */ + +igraph_error_t igraph_is_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res) { + + return igraph_i_is_separator(graph, candidate, res, NULL); +} + +/** + * \function igraph_is_minimal_separator + * \brief Decides whether a set of vertices is a minimal separator. + * + * A vertex separator \c S is minimal is no proper subset of \c S + * is also a separator. + * + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param candidate The candidate minimal separators. + * \param res Pointer to a boolean variable, the result is stored + * here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number vertices and edges. + * + * \example examples/simple/igraph_is_minimal_separator.c + */ + +igraph_error_t igraph_is_minimal_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res) { + + igraph_bool_t is_separator; + return igraph_i_is_separator(graph, candidate, &is_separator, res); +} + +/* --------------------------------------------------------------------*/ + +#define UPDATEMARK() do { \ + (*mark)++; \ + if (!(*mark)) { \ + igraph_vector_int_null(leaveout); \ + (*mark)=1; \ + } \ + } while (0) + +static igraph_error_t igraph_i_connected_components_leaveout(const igraph_adjlist_t *adjlist, + igraph_vector_int_t *components, + igraph_vector_int_t *leaveout, + igraph_int_t *mark, + igraph_dqueue_int_t *Q) { + + /* Another trick: we use the same 'leaveout' vector to mark the + * vertices that were already found in the BFS + */ + + igraph_int_t i, no_of_nodes = igraph_adjlist_size(adjlist); + + igraph_dqueue_int_clear(Q); + igraph_vector_int_clear(components); + + for (i = 0; i < no_of_nodes; i++) { + + if (VECTOR(*leaveout)[i] == *mark) { + continue; + } + + VECTOR(*leaveout)[i] = *mark; + IGRAPH_CHECK(igraph_dqueue_int_push(Q, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(components, i)); + + while (!igraph_dqueue_int_empty(Q)) { + igraph_int_t act_node = igraph_dqueue_int_pop(Q); + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, act_node); + igraph_int_t j, n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (VECTOR(*leaveout)[nei] == *mark) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_int_push(Q, nei)); + VECTOR(*leaveout)[nei] = *mark; + IGRAPH_CHECK(igraph_vector_int_push_back(components, nei)); + } + } + + IGRAPH_CHECK(igraph_vector_int_push_back(components, -1)); + } + + UPDATEMARK(); + + return IGRAPH_SUCCESS; +} + +static igraph_bool_t igraph_i_separators_is_not_seen_yet( + const igraph_vector_int_list_t *comps, const igraph_vector_int_t *newc +) { + + igraph_int_t co, nocomps = igraph_vector_int_list_size(comps); + + for (co = 0; co < nocomps; co++) { + igraph_vector_int_t *act = igraph_vector_int_list_get_ptr(comps, co); + if (igraph_vector_int_all_e(act, newc)) { + return false; + } + } + + /* If not found, then it is new */ + return true; +} + +static igraph_error_t igraph_i_separators_store(igraph_vector_int_list_t *separators, + const igraph_adjlist_t *adjlist, + igraph_vector_int_t *components, + igraph_vector_int_t *leaveout, + igraph_int_t *mark, + igraph_vector_int_t *sorter) { + + /* We need to store N(C), the neighborhood of C, but only if it is + * not already stored among the separators. + */ + + igraph_int_t cptr = 0, next, complen = igraph_vector_int_size(components); + + while (cptr < complen) { + igraph_int_t saved = cptr; + igraph_vector_int_clear(sorter); + + /* Calculate N(C) for the next C */ + + while ( (next = VECTOR(*components)[cptr++]) != -1) { + VECTOR(*leaveout)[next] = *mark; + } + cptr = saved; + + while ( (next = VECTOR(*components)[cptr++]) != -1) { + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, next); + igraph_int_t j, nn = igraph_vector_int_size(neis); + for (j = 0; j < nn; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (VECTOR(*leaveout)[nei] != *mark) { + IGRAPH_CHECK(igraph_vector_int_push_back(sorter, nei)); + VECTOR(*leaveout)[nei] = *mark; + } + } + } + igraph_vector_int_sort(sorter); + + UPDATEMARK(); + + /* Add it to the list of separators, if it is new */ + /* TODO: Is there a cleaner way to avoid empty separators, + * or is this an inherent limitation of the algorithm? + * See https://github.com/igraph/igraph/issues/2517 */ + if ( + igraph_vector_int_size(sorter) > 0 && + igraph_i_separators_is_not_seen_yet(separators, sorter) + ) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(separators, sorter)); + } + } /* while cptr < complen */ + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_all_minimal_st_separators + * \brief List all vertex sets that are minimal (s,t) separators for some s and t. + * + * This function lists all vertex sets that are minimal (s,t) + * separators for some (s,t) vertex pair. + * + * + * Note that some vertex sets returned by this function may not be minimal + * with respect to disconnecting the graph (or increasing the number of + * connected components). Take for example the 5-vertex graph with edges + * 0-1-2-3-4-1. This function returns the vertex sets + * {1}, {2,4} and {1,3}. + * Notice that {1,3} is not minimal with respect to disconnecting + * the graph, as {1} would be sufficient for that. However, it is + * minimal with respect to separating vertices \c 2 and \c 4. + * + * + * See more about the implemented algorithm in + * Anne Berry, Jean-Paul Bordat and Olivier Cogis: Generating All the + * Minimal Separators of a Graph, In: Peter Widmayer, Gabriele Neyer + * and Stephan Eidenbenz (editors): Graph-theoretic concepts in + * computer science, 1665, 167--172, 1999. Springer. + * https://doi.org/10.1007/3-540-46784-X_17 + * + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param separators Pointer to a list of integer vectors, the separators + * will be stored here. + * \return Error code. + * + * \sa \ref igraph_minimum_size_separators() + * + * Time complexity: O(n|V|^3), |V| is the number of vertices, n is the + * number of separators. + * + * \example examples/simple/igraph_minimal_separators.c + */ + +igraph_error_t igraph_all_minimal_st_separators( + const igraph_t *graph, igraph_vector_int_list_t *separators +) { + + /* + * Some notes about the tricks used here. For finding the components + * of the graph after removing some vertices, we do the + * following. First we mark the vertices with the actual mark stamp + * (mark), then run breadth-first search on the graph, but not + * considering the marked vertices. Then we increase the mark. If + * there is integer overflow here, then we zero out the mark and set + * it to one. (We might as well just always zero it out.) + * + * For each separator the vertices are stored in vertex ID order. + * This facilitates the comparison of the separators when we find a + * potential new candidate. + * + * The try_next pointer show the next separator to try as a basis. + */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t leaveout; + igraph_int_t try_next = 0; + igraph_int_t mark = 1; + igraph_int_t v; + + igraph_adjlist_t adjlist; + igraph_vector_int_t components; + igraph_dqueue_int_t Q; + igraph_vector_int_t sorter; + + igraph_vector_int_list_clear(separators); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&leaveout, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&components, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&components, no_of_nodes * 2)); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + IGRAPH_VECTOR_INT_INIT_FINALLY(&sorter, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&sorter, no_of_nodes)); + + /* --------------------------------------------------------------- + * INITIALIZATION, we check whether the neighborhoods of the + * vertices separate the graph. The ones that do will form the + * initial basis. + */ + + for (v = 0; v < no_of_nodes; v++) { + + /* Mark v and its neighbors */ + igraph_vector_int_t *neis = igraph_adjlist_get(&adjlist, v); + igraph_int_t i, n = igraph_vector_int_size(neis); + VECTOR(leaveout)[v] = mark; + for (i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + VECTOR(leaveout)[nei] = mark; + } + + /* Find the components */ + IGRAPH_CHECK(igraph_i_connected_components_leaveout( + &adjlist, &components, &leaveout, &mark, &Q)); + + /* Store the corresponding separators, N(C) for each component C */ + IGRAPH_CHECK(igraph_i_separators_store(separators, &adjlist, &components, + &leaveout, &mark, &sorter)); + + } + + /* --------------------------------------------------------------- + * GENERATION, we need to use all already found separators as + * basis and see if they generate more separators + */ + + while (try_next < igraph_vector_int_list_size(separators)) { + /* copy "basis" out of the vector_list because we are going to + * mutate the vector_list later, and this can potentially invalidate + * the pointer */ + igraph_vector_int_t basis = *(igraph_vector_int_list_get_ptr(separators, try_next)); + igraph_int_t b, basislen = igraph_vector_int_size(&basis); + for (b = 0; b < basislen; b++) { + + /* Remove N(x) U basis */ + igraph_int_t x = VECTOR(basis)[b]; + igraph_vector_int_t *neis = igraph_adjlist_get(&adjlist, x); + igraph_int_t i, n = igraph_vector_int_size(neis); + for (i = 0; i < basislen; i++) { + igraph_int_t sn = VECTOR(basis)[i]; + VECTOR(leaveout)[sn] = mark; + } + for (i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + VECTOR(leaveout)[nei] = mark; + } + + /* Find the components */ + IGRAPH_CHECK(igraph_i_connected_components_leaveout( + &adjlist, &components, &leaveout, &mark, &Q)); + + /* Store the corresponding separators, N(C) for each component C */ + IGRAPH_CHECK(igraph_i_separators_store(separators, &adjlist, + &components, &leaveout, &mark, + &sorter)); + } + + try_next++; + } + + /* --------------------------------------------------------------- */ + + igraph_vector_int_destroy(&sorter); + igraph_dqueue_int_destroy(&Q); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&components); + igraph_vector_int_destroy(&leaveout); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +#undef UPDATEMARK + +static igraph_error_t igraph_i_minimum_size_separators_append( + igraph_vector_int_list_t *old, igraph_vector_int_list_t *new +) { + + igraph_int_t olen = igraph_vector_int_list_size(old); + igraph_int_t j; + + while (!igraph_vector_int_list_empty(new)) { + igraph_vector_int_t *newvec = igraph_vector_int_list_tail_ptr(new); + + /* Check whether the separator is already in `old' */ + for (j = 0; j < olen; j++) { + igraph_vector_int_t *oldvec = igraph_vector_int_list_get_ptr(old, j); + if (igraph_vector_int_all_e(oldvec, newvec)) { + break; + } + } + + if (j == olen) { + /* We have found a new separator, append it to `old'. We do it by + * extending it with an empty vector and then swapping it with + * the new vector to be appended */ + igraph_vector_int_t *oldvec; + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(old, &oldvec)); + igraph_vector_int_swap(oldvec, newvec); + olen++; + } + + igraph_vector_int_list_discard_back(new); + } + + return IGRAPH_SUCCESS; +} + +/** + * Finds the k largest degree vertices. + */ +static igraph_error_t igraph_i_minimum_size_separators_topkdeg( + const igraph_t *graph, igraph_vector_int_t *res, const igraph_int_t k +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t deg, order; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); + + /* It is assumed that this function receives only simple graphs, so we can use the + * faster IGRAPH_LOOPS here instead of the slower IGRAPH_NO_LOOPS. */ + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + IGRAPH_CHECK(igraph_i_vector_int_order(°, &order, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_int_resize(res, k)); + for (igraph_int_t i = 0; i < k; i++) { + VECTOR(*res)[i] = VECTOR(order)[no_of_nodes - 1 - i]; + } + + igraph_vector_int_destroy(&order); + igraph_vector_int_destroy(°); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_minimum_size_separators + * \brief Find all minimum size separating vertex sets. + * + * This function lists all separator vertex sets of minimum size. + * A vertex set is a separator if its removal disconnects the graph. + * + * + * If the graph is already disconnected, no separators are returned. + * Note that this convention differs from that used by some other + * funtions such as \ref igraph_all_minimal_st_separators(). + * + * + * Complete graphs have no vertex separators. + * + * + * The implementation is based on the following paper: + * Arkady Kanevsky: Finding all minimum-size separating vertex sets in + * a graph, Networks 23, 533--541, 1993. + * https://doi.org/10.1002/net.3230230604 + * + * \param graph The input graph, which must be undirected. + * \param separators An initialized list of integer vectors, the separators + * are stored here. It is a list of pointers to \type igraph_vector_int_t + * objects. Each vector will contain the IDs of the vertices in + * the separator. The separators are returned in an arbitrary order. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_minimum_size_separators.c + */ + +igraph_error_t igraph_minimum_size_separators( + const igraph_t *graph, igraph_vector_int_list_t *separators +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t conn; + igraph_vector_int_t X; + igraph_int_t k, n; + igraph_bool_t issepX; + igraph_t Gbar; + igraph_vector_t phi; + igraph_t graph_copy; + igraph_vector_t capacity; + igraph_maxflow_stats_t stats; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Minimum size separators currently only works on undirected graphs.", + IGRAPH_EINVAL); + } + + igraph_vector_int_list_clear(separators); + + /* ---------------------------------------------------------------- */ + /* 1 Find the vertex connectivity of 'graph' */ + IGRAPH_CHECK(igraph_vertex_connectivity(graph, &conn, /* checks= */ true)); + k = conn; + + /* Special cases for low connectivity, two exits here! */ + if (conn == 0 || conn == no_of_nodes - 1) { + /* Nothing to do on disconnected or complete graphs: + * there are no separators. */ + return IGRAPH_SUCCESS; + } else if (conn == 1) { + igraph_vector_int_t ap; + IGRAPH_VECTOR_INT_INIT_FINALLY(&ap, 0); + IGRAPH_CHECK(igraph_articulation_points(graph, &ap)); + n = igraph_vector_int_size(&ap); + IGRAPH_CHECK(igraph_vector_int_list_resize(separators, n)); + for (igraph_int_t i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(separators, i); + IGRAPH_CHECK(igraph_vector_int_push_back(v, VECTOR(ap)[i])); + } + igraph_vector_int_destroy(&ap); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + + /* Work on a copy of 'graph' */ + IGRAPH_CHECK(igraph_copy(&graph_copy, graph)); + IGRAPH_FINALLY(igraph_destroy, &graph_copy); + IGRAPH_CHECK(igraph_simplify(&graph_copy, /* remove_multiple */ true, /* remove_loops */ true, NULL)); + + /* ---------------------------------------------------------------- */ + /* 2 Find k vertices with the largest degrees (x1;..,xk). Check + if these k vertices form a separating k-set of G */ + IGRAPH_CHECK(igraph_vector_int_init(&X, conn)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &X); + IGRAPH_CHECK(igraph_i_minimum_size_separators_topkdeg(&graph_copy, &X, k)); + IGRAPH_CHECK(igraph_is_separator(&graph_copy, igraph_vss_vector(&X), + &issepX)); + if (issepX) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(separators, &X)); + } + + /* Create Gbar, the Even-Tarjan reduction of graph */ + IGRAPH_VECTOR_INIT_FINALLY(&capacity, 0); + IGRAPH_CHECK(igraph_even_tarjan_reduction(&graph_copy, &Gbar, &capacity)); + IGRAPH_FINALLY(igraph_destroy, &Gbar); + + IGRAPH_VECTOR_INIT_FINALLY(&phi, no_of_edges); + + /* ---------------------------------------------------------------- */ + /* 3 If v[j] != x[i] and v[j] is not adjacent to x[i] then */ + for (igraph_int_t i = 0; i < k; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + igraph_int_t xi = VECTOR(X)[i]; + igraph_real_t phivalue; + igraph_bool_t conn; + + if (xi == j) { + continue; /* the same vertex */ + } + IGRAPH_CHECK(igraph_are_adjacent(&graph_copy, xi, j, &conn)); + if (conn) { + continue; /* they are connected */ + } + + /* --------------------------------------------------------------- */ + /* 4 Compute a maximum flow phi in Gbar from x[i] to v[j]. + If |phi|=k, then */ + IGRAPH_CHECK(igraph_maxflow(&Gbar, &phivalue, &phi, /*cut=*/ NULL, + /*partition=*/ NULL, /*partition2=*/ NULL, + /* source= */ xi + no_of_nodes, + /* target= */ j, + &capacity, &stats)); + + if (phivalue == k) { + + /* ------------------------------------------------------------- */ + /* 5-6-7. Find all k-sets separating x[i] and v[j]. */ + igraph_vector_int_list_t stcuts; + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&stcuts, 0); + IGRAPH_CHECK(igraph_all_st_mincuts(&Gbar, /*value=*/ NULL, + /*cuts=*/ &stcuts, + /*partition1s=*/ NULL, + /*source=*/ xi + no_of_nodes, + /*target=*/ j, + /*capacity=*/ &capacity)); + + IGRAPH_CHECK(igraph_i_minimum_size_separators_append(separators, &stcuts)); + igraph_vector_int_list_destroy(&stcuts); + IGRAPH_FINALLY_CLEAN(1); + + } /* if phivalue == k */ + + /* --------------------------------------------------------------- */ + /* 8 Add edge (x[i],v[j]) to G. */ + IGRAPH_CHECK(igraph_add_edge(&graph_copy, xi, j)); + IGRAPH_CHECK(igraph_add_edge(&Gbar, xi + no_of_nodes, j)); + IGRAPH_CHECK(igraph_add_edge(&Gbar, j + no_of_nodes, xi)); + IGRAPH_CHECK(igraph_vector_push_back(&capacity, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_push_back(&capacity, no_of_nodes)); + + } /* for j>= 1; + break; + case IGRAPH_LOOPS_ONCE: + default: + break; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_adjacency_directed_or_plus( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_adjacency_t mode, igraph_loops_t loops +) { + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + + /* For sake of consistency with the rest of the library, IGRAPH_LOOPS_TWICE + * is treated as IGRAPH_LOOPS_ONCE for directed graphs */ + if (mode == IGRAPH_ADJ_DIRECTED && loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t M = MATRIX(*adjmatrix, i, j); + if (i == j) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&M, loops)); + } + for (igraph_int_t k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_adjacency_max( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t i, j, k, M1, M2; + + for (i = 0; i < no_of_nodes; i++) { + /* do the loops first */ + M1 = MATRIX(*adjmatrix, i, i); + if (M1) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&M1, loops)); + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + } + } + + /* then the rest */ + for (j = i + 1; j < no_of_nodes; j++) { + M1 = MATRIX(*adjmatrix, i, j); + M2 = MATRIX(*adjmatrix, j, i); + if (M1 < M2) { + M1 = M2; + } + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_adjacency_undirected( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + if (!igraph_matrix_is_symmetric(adjmatrix)) { + IGRAPH_ERROR( + "Adjacency matrix should be symmetric to produce an undirected graph.", + IGRAPH_EINVAL + ); + } + return igraph_i_adjacency_max(adjmatrix, edges, loops); +} + +static igraph_error_t igraph_i_adjacency_upper( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t M; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < j; i++) { + M = MATRIX(*adjmatrix, i, j); + for (igraph_int_t k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + + /* do the loops as well */ + M = MATRIX(*adjmatrix, j, j); + if (M) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&M, loops)); + for (igraph_int_t k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_adjacency_lower( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t M; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + /* do the loops first */ + M = MATRIX(*adjmatrix, j, j); + if (M) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&M, loops)); + for (igraph_int_t k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + + for (igraph_int_t i = j+1; i < no_of_nodes; i++) { + M = MATRIX(*adjmatrix, i, j); + for (igraph_int_t k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_adjacency_min( + const igraph_matrix_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t i, j, k, M1, M2; + + for (i = 0; i < no_of_nodes; i++) { + /* do the loops first */ + M1 = MATRIX(*adjmatrix, i, i); + if (M1) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&M1, loops)); + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + } + } + + /* then the rest */ + for (j = i + 1; j < no_of_nodes; j++) { + M1 = MATRIX(*adjmatrix, i, j); + M2 = MATRIX(*adjmatrix, j, i); + if (M1 > M2) { + M1 = M2; + } + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + } + } + } + + return IGRAPH_SUCCESS; +} + + + +/** + * \ingroup generators + * \function igraph_adjacency + * \brief Creates a graph from an adjacency matrix. + * + * The order of the vertices in the matrix is preserved, i.e. the vertex + * corresponding to the first row/column will be vertex with id 0, the + * next row is for vertex 1, etc. No guarantees are given about the ordering + * of edges. + * + * \param graph Pointer to an uninitialized graph object. + * \param adjmatrix The adjacency matrix. How it is interpreted + * depends on the \p mode argument. + * \param mode Constant to specify how the given matrix is interpreted + * as an adjacency matrix. Possible values (A(i,j) is the element in + * row i and column j in the adjacency matrix \p adjmatrix): + * \clist + * \cli IGRAPH_ADJ_DIRECTED + * The graph will be directed and an element gives the number of edges + * between two vertices. + * \cli IGRAPH_ADJ_UNDIRECTED + * The graph will be undirected and an element gives the number of + * edges between two vertices. If the input matrix is not symmetric, + * an error is thrown. + * \cli IGRAPH_ADJ_MAX + * An undirected graph will be created and the number of edges between + * vertices i and j is max(A(i,j), A(j,i)). + * \cli IGRAPH_ADJ_MIN + * An undirected graph will be created with min(A(i,j), A(j,i)) edges + * between vertices i and j. + * \cli IGRAPH_ADJ_PLUS + * An undirected graph will be created with A(i,j)+A(j,i) edges + * between vertices i and j. + * \cli IGRAPH_ADJ_UPPER + * An undirected graph will be created. Only the upper right triangle + * (including the diagonal) is used for the number of edges. + * \cli IGRAPH_ADJ_LOWER + * An undirected graph will be created. Only the lower left triangle + * (including the diagonal) is used for the number of edges. + * \endclist + * \param loops Constant of type \ref igraph_loops_t to specify how the diagonal + * of the matrix should be treated when creating loop edges. Ignored for + * modes \c IGRAPH_ADJ_DIRECTED, \c IGRAPH_ADJ_UPPER and \c IGRAPH_ADJ_LOWER. + * \clist + * \cli IGRAPH_NO_LOOPS + * Ignore the diagonal of the input matrix and do not create loops. + * \cli IGRAPH_LOOPS_ONCE + * Treat the diagonal entries as the number of loop edges incident on + * the corresponding vertex. + * \cli IGRAPH_LOOPS_TWICE + * Treat the diagonal entries as \em twice the number of loop edges + * incident on the corresponding vertex. Odd numbers in the diagonal + * will return an error code. + * \endclist + * \return Error code, + * \c IGRAPH_EINVAL: Non-square adjacency matrix, negative entry in + * adjacency matrix, or an odd number was found in the diagonal with + * \c IGRAPH_LOOPS_TWICE + * + * Time complexity: O(|V||V|), + * |V| is the number of vertices in the graph. + */ +igraph_error_t igraph_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_loops_t loops +) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + + /* Some checks */ + if (igraph_matrix_nrow(adjmatrix) != igraph_matrix_ncol(adjmatrix)) { + IGRAPH_ERROR("Adjacency matrices must be square.", IGRAPH_EINVAL); + } + + if (no_of_nodes != 0 && igraph_matrix_min(adjmatrix) < 0) { + IGRAPH_ERRORF("Edge counts should be non-negative, found %g.", IGRAPH_EINVAL, + igraph_matrix_min(adjmatrix)); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + /* Collect the edges */ + no_of_nodes = igraph_matrix_nrow(adjmatrix); + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_adjacency_directed_or_plus(adjmatrix, &edges, mode, loops)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_adjacency_max(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_UNDIRECTED: + IGRAPH_CHECK(igraph_i_adjacency_undirected(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_adjacency_upper(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_adjacency_lower(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_adjacency_min(adjmatrix, &edges, loops)); + break; + default: + IGRAPH_ERROR("Invalid adjacency mode.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, (mode == IGRAPH_ADJ_DIRECTED))); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_directed( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); +static igraph_error_t igraph_i_weighted_adjacency_plus( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); +static igraph_error_t igraph_i_weighted_adjacency_max( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); +static igraph_error_t igraph_i_weighted_adjacency_upper( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); +static igraph_error_t igraph_i_weighted_adjacency_lower( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); +static igraph_error_t igraph_i_weighted_adjacency_min( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +); + +static void igraph_i_adjust_loop_edge_weight(igraph_real_t* weight, igraph_loops_t loops) { + /* The compiler should be smart enough to figure out that this can be + * inlined */ + switch (loops) { + case IGRAPH_NO_LOOPS: + *weight = 0.0; + break; + case IGRAPH_LOOPS_TWICE: + *weight /= 2; + break; + case IGRAPH_LOOPS_ONCE: + default: + break; + } +} + +static igraph_error_t igraph_i_weighted_adjacency_directed( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + + /* For sake of consistency with the rest of the library, IGRAPH_LOOPS_TWICE + * is treated as IGRAPH_LOOPS_ONCE for directed graphs */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j); + if (M != 0.0) { + if (i == j) { + igraph_i_adjust_loop_edge_weight(&M, loops); + if (M == 0.0) { + continue; + } + } + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_plus( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t i, j; + igraph_real_t M; + + for (i = 0; i < no_of_nodes; i++) { + if (loops != IGRAPH_NO_LOOPS) { + M = MATRIX(*adjmatrix, i, i); + if (M != 0.0) { + igraph_i_adjust_loop_edge_weight(&M, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + for (j = i + 1; j < no_of_nodes; j++) { + M = MATRIX(*adjmatrix, i, j) + MATRIX(*adjmatrix, j, i); + if (M != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_max( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t i, j; + igraph_real_t M1, M2; + + for (i = 0; i < no_of_nodes; i++) { + /* do the loops first */ + if (loops) { + M1 = MATRIX(*adjmatrix, i, i); + if (M1 != 0.0) { + igraph_i_adjust_loop_edge_weight(&M1, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + + /* then the rest */ + for (j = i + 1; j < no_of_nodes; j++) { + M1 = MATRIX(*adjmatrix, i, j); + M2 = MATRIX(*adjmatrix, j, i); + if (M1 < M2 || isnan(M2)) { + M1 = M2; + } + if (M1 != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_undirected( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + /* We do not use igraph_matrix_is_symmetric() for this check, as we need to + * allow symmetric matrices with NaN values. igraph_matrix_is_symmetric() + * returns false for these as NaN != NaN. */ + const igraph_int_t n = igraph_matrix_nrow(adjmatrix); + for (igraph_int_t i=0; i < n; i++) { + /* do the loops first */ + if (loops) { + igraph_real_t M = MATRIX(*adjmatrix, i, i); + if (M != 0.0) { + igraph_i_adjust_loop_edge_weight(&M, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + for (igraph_int_t j=0; j < i; j++) { + igraph_real_t M1 = MATRIX(*adjmatrix, i, j); + igraph_real_t M2 = MATRIX(*adjmatrix, j, i); + if (IGRAPH_UNLIKELY(M1 != M2 && ! (isnan(M1) && isnan(M2)))) { + IGRAPH_ERROR( + "Adjacency matrix should be symmetric to produce an undirected graph.", + IGRAPH_EINVAL + ); + } else if (M1 != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + } + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_weighted_adjacency_upper( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_real_t M; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + for (igraph_int_t i = 0; i < j; i++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j); + if (M != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + /* do the loops as well */ + if (loops) { + M = MATRIX(*adjmatrix, j, j); + if (M != 0.0) { + igraph_i_adjust_loop_edge_weight(&M, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_lower( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + const igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_real_t M; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + /* do the loops first */ + if (loops) { + M = MATRIX(*adjmatrix, j, j); + if (M != 0.0) { + igraph_i_adjust_loop_edge_weight(&M, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + for (igraph_int_t i = j+1; i < no_of_nodes; i++) { + M = MATRIX(*adjmatrix, i, j); + if (M != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_weighted_adjacency_min( + const igraph_matrix_t *adjmatrix, + igraph_vector_int_t *edges, + igraph_vector_t *weights, + igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(adjmatrix); + igraph_int_t i, j; + igraph_real_t M1, M2; + + for (i = 0; i < no_of_nodes; i++) { + /* do the loops first */ + if (loops) { + M1 = MATRIX(*adjmatrix, i, i); + if (M1 != 0.0) { + igraph_i_adjust_loop_edge_weight(&M1, loops); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + + /* then the rest */ + for (j = i + 1; j < no_of_nodes; j++) { + M1 = MATRIX(*adjmatrix, i, j); + M2 = MATRIX(*adjmatrix, j, i); + if (M1 > M2 || isnan(M2)) { + M1 = M2; + } + if (M1 != 0.0) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_weighted_adjacency + * \brief Creates a graph from a weighted adjacency matrix. + * + * The order of the vertices in the matrix is preserved, i.e. the vertex + * corresponding to the first row/column will be vertex with id 0, the + * next row is for vertex 1, etc. No guarantees are given for the ordering + * of edges. + * + * \param graph Pointer to an uninitialized graph object. + * \param adjmatrix The weighted adjacency matrix. How it is interpreted + * depends on the \p mode argument. The common feature is that + * edges with zero weights are considered nonexistent (however, + * negative weights are permitted). + * \param mode Constant to specify how the given matrix is interpreted + * as an adjacency matrix. Possible values (A(i,j) is the element in row + * i and column j in the adjacency matrix \p adjmatrix): + * \clist + * \cli IGRAPH_ADJ_DIRECTED + * The graph will be directed and an element specifies the weight of the + * edge between two vertices. + * \cli IGRAPH_ADJ_UNDIRECTED + * This is the same as \c IGRAPH_ADJ_MAX, for convenience. + * \cli IGRAPH_ADJ_MAX + * An undirected graph will be created and the weight of the edge between + * vertices i and j is max(A(i,j), A(j,i)). + * \cli IGRAPH_ADJ_MIN + * An undirected graph will be created and the weight of the edge between + * vertices i and j is min(A(i,j), A(j,i)). + * \cli IGRAPH_ADJ_PLUS + * An undirected graph will be created and the weight of the edge between + * vertices i and j is A(i,j)+A(j,i). + * \cli IGRAPH_ADJ_UPPER + * An undirected graph will be created. Only the upper right triangle + * (including the diagonal) is used for the edge weights. + * \cli IGRAPH_ADJ_LOWER + * An undirected graph will be created. Only the lower left triangle + * (including the diagonal) is used for the edge weights. + * \endclist + * \param weights Pointer to an initialized vector, the weights will be stored here. + * \param loops Constant to specify how the diagonal of the matrix should be + * treated when creating loop edges. Ignored for modes + * \c IGRAPH_ADJ_DIRECTED, \c IGRAPH_ADJ_UPPER and \c IGRAPH_ADJ_LOWER. + * \clist + * \cli IGRAPH_NO_LOOPS + * Ignore the diagonal of the input matrix and do not create loops. + * \cli IGRAPH_LOOPS_ONCE + * Treat the diagonal entries as the weight of the loop edge incident + * on the corresponding vertex. + * \cli IGRAPH_LOOPS_TWICE + * Treat the diagonal entries as \em twice the weight of the loop edge + * incident on the corresponding vertex. + * \endclist + * \return Error code, + * \c IGRAPH_EINVAL: non-square matrix. + * + * Time complexity: O(|V||V|), + * |V| is the number of vertices in the graph. + * + * \example examples/simple/igraph_weighted_adjacency.c + */ +igraph_error_t igraph_weighted_adjacency( + igraph_t *graph, const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, + igraph_vector_t *weights, igraph_loops_t loops +) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t no_of_nodes; + + /* Some checks */ + if (igraph_matrix_nrow(adjmatrix) != igraph_matrix_ncol(adjmatrix)) { + IGRAPH_ERROR("Adjacency matrices must be square.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + igraph_vector_clear(weights); + + /* Collect the edges */ + no_of_nodes = igraph_matrix_nrow(adjmatrix); + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + IGRAPH_CHECK(igraph_i_weighted_adjacency_directed(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_weighted_adjacency_max(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_UNDIRECTED: + IGRAPH_CHECK(igraph_i_weighted_adjacency_undirected(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_weighted_adjacency_upper(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_weighted_adjacency_lower(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_weighted_adjacency_min(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_weighted_adjacency_plus(adjmatrix, &edges, + weights, loops)); + break; + default: + IGRAPH_ERROR("Invalid adjacency mode.", IGRAPH_EINVAL); + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, no_of_nodes, (mode == IGRAPH_ADJ_DIRECTED))); + IGRAPH_FINALLY(igraph_destroy, graph); + if (igraph_vector_int_size(&edges) > 0) { + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + } + IGRAPH_FINALLY_CLEAN(1); + + /* Cleanup */ + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adjlist + * \brief Creates a graph from an adjacency list. + * + * An adjacency list is a list of vectors, containing the neighbors + * of all vertices. For operations that involve many changes to the + * graph structure, it is recommended that you convert the graph into + * an adjacency list via \ref igraph_adjlist_init(), perform the + * modifications (these are cheap for an adjacency list) and then + * recreate the igraph graph via this function. + * + * \param graph Pointer to an uninitialized graph object. + * \param adjlist The adjacency list. + * \param mode Whether or not to create a directed graph. \c IGRAPH_ALL + * means an undirected graph, \c IGRAPH_OUT means a + * directed graph from an out-adjacency list (i.e. each + * list contains the successors of the corresponding + * vertices), \c IGRAPH_IN means a directed graph from an + * in-adjacency list + * \param duplicate Boolean constant. For undirected graphs this specifies + * whether each edge is included twice, in the vectors of + * both adjacent vertices. If this is \c false, then it is + * assumed that every edge is included only once. This argument + * is ignored for directed graphs. + * \return Error code. + * + * \sa \ref igraph_adjlist_init() for the opposite operation. + * + * Time complexity: O(|V|+|E|). + * + */ +igraph_error_t igraph_adjlist(igraph_t *graph, const igraph_adjlist_t *adjlist, + igraph_neimode_t mode, igraph_bool_t duplicate) { + + const igraph_int_t no_of_nodes = igraph_adjlist_size(adjlist); + igraph_int_t no_of_edges = 0; + + igraph_vector_int_t edges; + igraph_int_t edgeptr = 0; + + duplicate = duplicate && (mode == IGRAPH_ALL); /* only duplicate if undirected */ + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + no_of_edges += igraph_vector_int_size(igraph_adjlist_get(adjlist, i)); + } + + if (duplicate) { + no_of_edges /= 2; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2 * no_of_edges); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, i); + const igraph_int_t n = igraph_vector_int_size(neis); + igraph_int_t loops = 0; + + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (nei == i) { + loops++; + } else { + if (! duplicate || nei > i) { + if (edgeptr + 2 > 2 * no_of_edges) { + IGRAPH_ERROR("Invalid adjacency list, most probably not correctly" + " duplicated edges for an undirected graph.", IGRAPH_EINVAL); + } + if (mode == IGRAPH_IN) { + VECTOR(edges)[edgeptr++] = nei; + VECTOR(edges)[edgeptr++] = i; + } else { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = nei; + } + } + } + } + /* loops */ + if (duplicate) { + loops = loops / 2; + } + if (edgeptr + 2 * loops > 2 * no_of_edges) { + IGRAPH_ERROR("Invalid adjacency list, most probably not correctly" + " duplicated edges for an undirected graph.", IGRAPH_EINVAL); + } + for (igraph_int_t j = 0; j < loops; j++) { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = i; + } + } + + if (mode == IGRAPH_ALL) { + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_UNDIRECTED)); + } else { + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + } + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_directed_or_plus( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_adjacency_t mode, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_init(&it, adjmatrix); + + /* For sake of consistency with the rest of the library, IGRAPH_LOOPS_TWICE + * is treated as IGRAPH_LOOPS_ONCE for directed graphs */ + if (mode == IGRAPH_ADJ_DIRECTED && loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + igraph_int_t multi = igraph_sparsemat_iterator_get(&it); + if (to == from) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&multi, loops)); + } + for (igraph_int_t count = 0; count < multi; count++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, to)); + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_max( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_real_t other; + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_int_t multi = igraph_sparsemat_iterator_get(&it); + if (to == from) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&multi, loops)); + } else { + other = igraph_sparsemat_get(adjmatrix, to, from); + multi = multi > other ? multi : other; + } + for (igraph_int_t count = 0; count < multi; count++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, to)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_min( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_real_t other; + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_int_t multi = igraph_sparsemat_iterator_get(&it); + if (to == from) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&multi, loops)); + } else { + other = igraph_sparsemat_get(adjmatrix, to, from); + multi = multi < other ? multi : other; + } + for (igraph_int_t count = 0; count < multi; count++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, to)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_upper( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_int_t multi = igraph_sparsemat_iterator_get(&it); + if (to == from) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&multi, loops)); + } + for (igraph_int_t count = 0; count < multi; count++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, to)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_lower( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to > from) { + continue; + } + igraph_int_t multi = igraph_sparsemat_iterator_get(&it); + if (to == from) { + IGRAPH_CHECK(igraph_i_adjust_loop_edge_count(&multi, loops)); + } + for (igraph_int_t count = 0; count < multi; count++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, to)); + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_adjacency_undirected( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_loops_t loops +) { + igraph_bool_t sym; + + IGRAPH_CHECK(igraph_sparsemat_is_symmetric(adjmatrix, &sym)); + if (!sym) { + IGRAPH_ERROR( + "Adjacency matrix should be symmetric to produce an undirected graph.", + IGRAPH_EINVAL + ); + } + return igraph_i_sparse_adjacency_max(adjmatrix, edges, loops); +} + +/** + * \ingroup generators + * \function igraph_sparse_adjacency + * \brief Creates a graph from a sparse adjacency matrix. + * + * This has the same functionality as \ref igraph_adjacency(), but uses + * a column-compressed adjacency matrix. + * + * + * Time complexity: O(|E|), + * where |E| is the number of edges in the graph. + */ + +igraph_error_t igraph_sparse_adjacency(igraph_t *graph, igraph_sparsemat_t *adjmatrix, + igraph_adjacency_t mode, igraph_loops_t loops) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t no_of_nodes = igraph_sparsemat_nrow(adjmatrix); + igraph_int_t no_of_nonzeros = igraph_sparsemat_count_nonzero(adjmatrix); + igraph_int_t approx_no_of_edges; + + if (!igraph_sparsemat_is_cc(adjmatrix)) { + IGRAPH_ERROR("Sparse adjacency matrix should be in column-compressed " + "form.", IGRAPH_EINVAL); + } + if (no_of_nodes != igraph_sparsemat_ncol(adjmatrix)) { + IGRAPH_ERROR("Adjacency matrix is non-square.", IGRAPH_EINVAL); + } + + if (no_of_nodes != 0 && igraph_sparsemat_min(adjmatrix) < 0) { + IGRAPH_ERRORF("Edge counts should be non-negative, found %g.", IGRAPH_EINVAL, + igraph_sparsemat_min(adjmatrix)); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + /* Approximate the number of edges in the graph based on the number of + * nonzero elements in the matrix */ + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + case IGRAPH_ADJ_PLUS: + case IGRAPH_ADJ_UPPER: + case IGRAPH_ADJ_LOWER: + approx_no_of_edges = no_of_nonzeros; + break; + case IGRAPH_ADJ_UNDIRECTED: + case IGRAPH_ADJ_MAX: + case IGRAPH_ADJ_MIN: + approx_no_of_edges = no_of_nonzeros / 2; + break; + default: + approx_no_of_edges = no_of_nonzeros; + break; + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, approx_no_of_edges * 2)); + + /* Collect the edges */ + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_sparse_adjacency_directed_or_plus(adjmatrix, &edges, mode, loops)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_sparse_adjacency_max(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_UNDIRECTED: + IGRAPH_CHECK(igraph_i_sparse_adjacency_undirected(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_sparse_adjacency_upper(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_sparse_adjacency_lower(adjmatrix, &edges, loops)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_sparse_adjacency_min(adjmatrix, &edges, loops)); + break; + default: + IGRAPH_ERROR("Invalid adjacency mode.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, (mode == IGRAPH_ADJ_DIRECTED))); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_max ( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_init(&it, adjmatrix); + igraph_int_t e = 0; + igraph_real_t other; + + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } else { + other = igraph_sparsemat_get(adjmatrix, to, from); + weight = weight > other ? weight : other; + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_min ( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_int_t e = 0; + igraph_real_t other; + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } else { + other = igraph_sparsemat_get(adjmatrix, to, from); + weight = weight < other ? weight : other; + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_plus ( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_int_t e = 0; + igraph_real_t other; + + igraph_sparsemat_iterator_init(&it, adjmatrix); + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + if (to < from) { + continue; + } + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } else { + other = igraph_sparsemat_get(adjmatrix, to, from); + weight += other; + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_upper( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_init(&it, adjmatrix); + igraph_int_t e = 0; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to < from) { + continue; + } + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_lower( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_init(&it, adjmatrix); + igraph_int_t e = 0; + + /* IGRAPH_LOOPS_TWICE is treated as IGRAPH_LOOPS_ONCE -- it makes no sense + * for loops to appear twice in the adjacency matrix when the lower triangle + * is empty; double-counting of loops in undirected graphs happens because + * the upper and the lower triangle are added on top of each other on the + * diagonal. See discussion in #2501: + * + * https://github.com/igraph/igraph/issues/2501#issuecomment-1949345675 */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to > from) { + continue; + } + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparse_weighted_adjacency_undirected ( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_bool_t sym; + + IGRAPH_CHECK(igraph_sparsemat_is_symmetric(adjmatrix, &sym)); + if (!sym) { + IGRAPH_ERROR( + "Adjacency matrix should be symmetric to produce an undirected graph.", + IGRAPH_EINVAL + ); + } + return igraph_i_sparse_weighted_adjacency_max(adjmatrix, edges, weights, loops); +} + + +static igraph_error_t igraph_i_sparse_weighted_adjacency_directed( + igraph_sparsemat_t *adjmatrix, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_init(&it, adjmatrix); + igraph_int_t e = 0; + + /* For sake of consistency with the rest of the library, IGRAPH_LOOPS_TWICE + * is treated as IGRAPH_LOOPS_ONCE for directed graphs */ + if (loops == IGRAPH_LOOPS_TWICE) { + loops = IGRAPH_LOOPS_ONCE; + } + + for (; !igraph_sparsemat_iterator_end(&it); igraph_sparsemat_iterator_next(&it)) { + igraph_int_t from = igraph_sparsemat_iterator_row(&it); + igraph_int_t to = igraph_sparsemat_iterator_col(&it); + igraph_real_t weight = igraph_sparsemat_iterator_get(&it); + if (to == from) { + igraph_i_adjust_loop_edge_weight(&weight, loops); + } + if (weight != 0) { + VECTOR(*weights)[e/2] = weight; + VECTOR(*edges)[e++] = from; + VECTOR(*edges)[e++] = to; + } + } + igraph_vector_int_resize(edges, e); /* shrinks */ + igraph_vector_resize(weights, e/2); /* shrinks */ + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_sparse_weighted_adjacency + * \brief Creates a graph from a weighted sparse adjacency matrix. + * + * This has the same functionality as \ref igraph_weighted_adjacency(), but uses + * a column-compressed adjacency matrix. + * + * + * Time complexity: O(|E|), + * where |E| is the number of edges in the graph. + */ + + +igraph_error_t igraph_sparse_weighted_adjacency( + igraph_t *graph, igraph_sparsemat_t *adjmatrix, igraph_adjacency_t mode, + igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = igraph_sparsemat_nrow(adjmatrix); + igraph_int_t no_of_edges = igraph_sparsemat_count_nonzero(adjmatrix); + + if (!igraph_sparsemat_is_cc(adjmatrix)) { + IGRAPH_ERROR("Sparse adjacency matrix should be in column-compressed form.", IGRAPH_EINVAL); + } + if (no_of_nodes != igraph_sparsemat_ncol(adjmatrix)) { + IGRAPH_ERROR("Adjacency matrix is non-square.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_vector_resize(weights, no_of_edges)); + + /* Collect the edges */ + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_directed(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_max(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_UNDIRECTED: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_undirected(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_upper(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_lower(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_min(adjmatrix, &edges, + weights, loops)); + break; + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_sparse_weighted_adjacency_plus(adjmatrix, &edges, + weights, loops)); + break; + default: + IGRAPH_ERROR("Invalid adjacency mode.", IGRAPH_EINVAL); + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, no_of_nodes, (mode == IGRAPH_ADJ_DIRECTED))); + IGRAPH_FINALLY(igraph_destroy, graph); + if (igraph_vector_int_size(&edges) > 0) { + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + } + IGRAPH_FINALLY_CLEAN(1); + + /* Cleanup */ + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/atlas-edges.h b/src/constructors/atlas-edges.h new file mode 100644 index 0000000..accc395 --- /dev/null +++ b/src/constructors/atlas-edges.h @@ -0,0 +1,1291 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CONSTRUCTORS_ATLAS_EDGES_H +#define IGRAPH_CONSTRUCTORS_ATLAS_EDGES_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +const igraph_int_t igraph_i_atlas_edges[] = { + 0, 0, + 1, 0, + 2, 0, + 2, 1, 0, 1, + 3, 0, + 3, 1, 1, 2, + 3, 2, 0, 1, 0, 2, + 3, 3, 0, 1, 0, 2, 1, 2, + 4, 0, + 4, 1, 3, 2, + 4, 2, 3, 2, 3, 1, + 4, 2, 0, 1, 3, 2, + 4, 3, 3, 2, 1, 2, 3, 1, + 4, 3, 3, 0, 3, 1, 3, 2, + 4, 3, 0, 1, 1, 2, 0, 3, + 4, 4, 3, 2, 1, 2, 3, 1, 3, 0, + 4, 4, 0, 1, 1, 2, 2, 3, 0, 3, + 4, 5, 0, 1, 0, 2, 0, 3, 1, 2, 2, 3, + 4, 6, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, + 5, 0, + 5, 1, 4, 3, + 5, 2, 1, 2, 0, 1, + 5, 2, 0, 2, 4, 3, + 5, 3, 1, 2, 0, 1, 2, 0, + 5, 3, 4, 3, 3, 2, 3, 1, + 5, 3, 3, 2, 4, 3, 0, 4, + 5, 3, 1, 2, 0, 1, 4, 3, + 5, 4, 4, 3, 1, 2, 3, 1, 3, 2, + 5, 4, 0, 3, 1, 0, 2, 1, 3, 2, + 5, 4, 4, 3, 4, 0, 4, 1, 4, 2, + 5, 4, 4, 0, 3, 1, 4, 3, 3, 2, + 5, 4, 2, 3, 1, 2, 0, 1, 4, 0, + 5, 4, 1, 2, 0, 1, 2, 0, 4, 3, + 5, 5, 0, 3, 2, 0, 3, 2, 1, 0, 2, 1, + 5, 5, 4, 2, 4, 3, 2, 3, 4, 1, 4, 0, + 5, 5, 0, 1, 1, 2, 2, 3, 0, 4, 0, 2, + 5, 5, 4, 0, 1, 2, 4, 3, 3, 2, 3, 1, + 5, 5, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, + 5, 5, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, + 5, 6, 1, 0, 4, 1, 4, 0, 0, 3, 1, 3, 3, 4, + 5, 6, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 2, 1, + 5, 6, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 3, 4, + 5, 6, 0, 1, 4, 3, 2, 3, 4, 2, 4, 0, 4, 1, + 5, 6, 0, 4, 3, 0, 4, 3, 2, 3, 1, 2, 0, 1, + 5, 6, 2, 1, 0, 2, 3, 0, 1, 3, 4, 1, 0, 4, + 5, 7, 4, 0, 1, 2, 4, 3, 3, 2, 3, 1, 4, 1, 2, 4, + 5, 7, 4, 1, 2, 4, 3, 2, 1, 3, 3, 4, 0, 3, 4, 0, + 5, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 5, 7, 2, 1, 0, 2, 3, 0, 1, 3, 4, 1, 0, 4, 2, 4, + 5, 8, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 4, 0, 3, 4, 0, 3, + 5, 8, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, + 5, 9, 0, 1, 3, 4, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, + 5, 10, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 6, 0, + 6, 1, 5, 4, + 6, 2, 0, 3, 5, 4, + 6, 2, 1, 3, 1, 2, + 6, 3, 1, 3, 2, 1, 3, 2, + 6, 3, 0, 3, 5, 0, 4, 0, + 6, 3, 4, 3, 5, 4, 0, 5, + 6, 3, 4, 3, 5, 1, 5, 2, + 6, 3, 1, 2, 3, 0, 5, 4, + 6, 4, 0, 3, 4, 0, 5, 4, 0, 5, + 6, 4, 3, 0, 5, 3, 4, 5, 0, 4, + 6, 4, 5, 1, 5, 3, 5, 2, 0, 5, + 6, 4, 4, 3, 3, 1, 4, 0, 3, 2, + 6, 4, 0, 2, 1, 3, 2, 1, 5, 3, + 6, 4, 1, 3, 2, 1, 3, 2, 0, 5, + 6, 4, 1, 2, 0, 3, 5, 0, 4, 0, + 6, 4, 4, 5, 1, 2, 0, 5, 3, 4, + 6, 4, 0, 2, 4, 0, 3, 1, 5, 3, + 6, 5, 3, 0, 5, 3, 4, 5, 0, 4, 5, 0, + 6, 5, 5, 3, 3, 1, 3, 2, 4, 3, 4, 5, + 6, 5, 5, 3, 5, 4, 2, 3, 3, 4, 0, 4, + 6, 5, 4, 3, 1, 2, 4, 0, 3, 2, 3, 1, + 6, 5, 1, 4, 3, 4, 4, 0, 2, 1, 3, 2, + 6, 5, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, + 6, 5, 5, 3, 5, 4, 5, 0, 5, 1, 5, 2, + 6, 5, 1, 4, 5, 1, 1, 0, 2, 1, 2, 3, + 6, 5, 0, 1, 3, 4, 0, 2, 3, 0, 5, 3, + 6, 5, 1, 0, 2, 1, 2, 4, 1, 3, 5, 3, + 6, 5, 4, 3, 0, 5, 4, 0, 3, 2, 3, 1, + 6, 5, 1, 2, 0, 1, 4, 5, 1, 3, 2, 3, + 6, 5, 0, 1, 0, 5, 2, 3, 3, 4, 4, 5, + 6, 5, 4, 3, 5, 1, 5, 2, 0, 3, 4, 0, + 6, 5, 1, 2, 3, 0, 5, 3, 4, 5, 0, 4, + 6, 6, 0, 3, 5, 0, 4, 5, 3, 4, 5, 3, 4, 0, + 6, 6, 1, 4, 2, 4, 4, 0, 2, 3, 3, 1, 3, 4, + 6, 6, 1, 4, 2, 4, 4, 0, 2, 1, 3, 1, 2, 3, + 6, 6, 2, 0, 5, 4, 4, 3, 5, 3, 4, 0, 2, 4, + 6, 6, 3, 2, 4, 3, 0, 4, 1, 0, 2, 1, 0, 3, + 6, 6, 4, 1, 3, 1, 4, 2, 3, 2, 2, 0, 1, 0, + 6, 6, 5, 2, 5, 3, 5, 4, 3, 4, 5, 1, 5, 0, + 6, 6, 4, 3, 4, 2, 4, 0, 1, 4, 3, 0, 5, 3, + 6, 6, 4, 3, 3, 5, 5, 4, 5, 1, 3, 2, 4, 0, + 6, 6, 4, 2, 1, 2, 4, 3, 4, 1, 4, 0, 0, 5, + 6, 6, 1, 2, 3, 1, 0, 3, 2, 0, 4, 0, 5, 0, + 6, 6, 2, 0, 4, 2, 1, 4, 2, 1, 3, 1, 5, 3, + 6, 6, 1, 2, 3, 1, 0, 3, 2, 0, 4, 0, 5, 3, + 6, 6, 5, 3, 2, 5, 2, 0, 4, 2, 4, 3, 3, 1, + 6, 6, 0, 2, 3, 4, 1, 0, 5, 3, 4, 5, 3, 0, + 6, 6, 1, 2, 3, 0, 5, 3, 4, 5, 0, 4, 5, 0, + 6, 6, 4, 3, 1, 2, 4, 0, 3, 2, 3, 1, 5, 0, + 6, 6, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, + 6, 6, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, + 6, 6, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, + 6, 6, 1, 3, 2, 1, 3, 2, 0, 4, 5, 0, 4, 5, + 6, 7, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, + 6, 7, 1, 4, 2, 4, 2, 1, 3, 1, 2, 3, 2, 0, 0, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 6, 7, 0, 1, 3, 2, 0, 2, 3, 0, 3, 1, 5, 1, 5, 2, + 6, 7, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 3, 4, + 6, 7, 1, 0, 4, 1, 2, 4, 3, 2, 5, 1, 2, 5, 1, 2, + 6, 7, 0, 4, 2, 0, 1, 2, 3, 1, 5, 3, 3, 0, 2, 3, + 6, 7, 1, 4, 2, 4, 2, 3, 2, 1, 3, 1, 4, 5, 0, 4, + 6, 7, 1, 0, 4, 1, 2, 4, 3, 2, 5, 1, 2, 5, 4, 5, + 6, 7, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, + 6, 7, 0, 5, 4, 0, 5, 4, 0, 2, 3, 0, 3, 2, 0, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 4, 1, + 6, 7, 0, 1, 4, 0, 1, 4, 0, 2, 3, 0, 3, 2, 3, 5, + 6, 7, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, 3, 4, + 6, 7, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, + 6, 7, 1, 5, 0, 1, 4, 0, 3, 4, 2, 3, 1, 2, 0, 3, + 6, 7, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, 2, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 5, 1, + 6, 7, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, + 6, 7, 5, 0, 3, 5, 2, 3, 0, 2, 1, 3, 4, 1, 3, 4, + 6, 7, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 2, 3, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, + 6, 7, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, + 6, 7, 1, 2, 0, 1, 2, 0, 3, 0, 4, 3, 5, 4, 3, 5, + 6, 8, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, + 6, 8, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, + 6, 8, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 5, 3, 4, 0, + 6, 8, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, + 6, 8, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, + 6, 8, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 5, 1, 5, 3, + 6, 8, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, + 6, 8, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, + 6, 8, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 1, 4, 0, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, + 6, 8, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, + 6, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, + 6, 9, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, + 6, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, + 6, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, + 6, 9, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, + 6, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, + 6, 9, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 4, 5, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 5, + 6, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, + 6, 10, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, + 6, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, + 6, 10, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, + 6, 10, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 3, 2, 0, 3, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, + 6, 11, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, + 6, 11, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, + 6, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 5, 1, + 6, 11, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, + 6, 11, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, + 6, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, + 6, 12, 0, 1, 1, 2, 0, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, + 6, 12, 3, 2, 1, 3, 2, 1, 0, 2, 5, 0, 2, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, + 6, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 5, + 6, 12, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, + 6, 12, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, + 6, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, 0, 4, + 6, 13, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, 3, 0, + 6, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, + 6, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, + 7, 0, + 7, 1, 6, 5, + 7, 2, 2, 3, 1, 2, + 7, 2, 5, 4, 6, 0, + 7, 3, 0, 4, 4, 2, 2, 0, + 7, 3, 0, 1, 0, 6, 0, 5, + 7, 3, 5, 4, 6, 0, 5, 6, + 7, 3, 3, 2, 1, 2, 5, 6, + 7, 3, 3, 1, 5, 6, 0, 4, + 7, 4, 2, 5, 6, 2, 5, 6, 1, 2, + 7, 4, 1, 2, 4, 1, 5, 4, 2, 5, + 7, 4, 1, 0, 5, 1, 1, 2, 4, 1, + 7, 4, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 4, 3, 4, 2, 3, 1, 2, 0, 1, + 7, 4, 4, 2, 0, 4, 2, 0, 5, 6, + 7, 4, 0, 1, 6, 0, 0, 5, 4, 2, + 7, 4, 3, 1, 5, 4, 6, 5, 0, 6, + 7, 4, 0, 4, 3, 0, 2, 5, 6, 2, + 7, 4, 2, 3, 1, 2, 6, 0, 5, 4, + 7, 5, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, + 7, 5, 2, 5, 6, 2, 5, 6, 4, 2, 3, 2, + 7, 5, 4, 2, 4, 0, 2, 0, 5, 4, 6, 0, + 7, 5, 2, 5, 6, 2, 5, 6, 1, 2, 0, 1, + 7, 5, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, + 7, 5, 1, 2, 0, 1, 4, 0, 3, 4, 2, 3, + 7, 5, 5, 1, 5, 0, 2, 5, 3, 5, 4, 5, + 7, 5, 1, 5, 6, 1, 1, 0, 2, 1, 3, 2, + 7, 5, 1, 5, 4, 1, 2, 3, 6, 2, 2, 1, + 7, 5, 1, 5, 6, 1, 1, 2, 2, 3, 4, 3, + 7, 5, 2, 1, 3, 2, 4, 3, 5, 4, 3, 6, + 7, 5, 6, 5, 2, 6, 1, 2, 5, 2, 3, 4, + 7, 5, 4, 3, 5, 4, 6, 5, 0, 6, 1, 0, + 7, 5, 0, 4, 3, 0, 2, 5, 6, 2, 5, 6, + 7, 5, 4, 1, 5, 2, 6, 5, 3, 6, 2, 3, + 7, 5, 1, 4, 3, 1, 1, 0, 2, 1, 6, 5, + 7, 5, 0, 4, 3, 0, 1, 0, 2, 1, 6, 5, + 7, 5, 0, 4, 3, 0, 2, 1, 5, 2, 6, 2, + 7, 5, 6, 5, 3, 4, 2, 3, 1, 2, 0, 1, + 7, 5, 2, 3, 1, 2, 6, 0, 5, 6, 5, 4, + 7, 5, 0, 1, 4, 6, 5, 4, 3, 2, 6, 5, + 7, 6, 1, 5, 6, 1, 5, 6, 2, 5, 1, 2, 6, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, + 7, 6, 0, 4, 3, 0, 1, 3, 2, 1, 1, 4, 3, 4, + 7, 6, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, + 7, 6, 1, 2, 4, 1, 5, 4, 2, 5, 0, 1, 4, 0, + 7, 6, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, + 7, 6, 2, 5, 6, 2, 5, 6, 2, 4, 1, 2, 3, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 1, 2, 2, 5, 6, 2, + 7, 6, 5, 4, 6, 5, 1, 6, 5, 1, 3, 6, 0, 1, + 7, 6, 6, 5, 1, 6, 5, 1, 3, 1, 0, 3, 1, 4, + 7, 6, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 1, 2, 2, 5, 6, 5, + 7, 6, 2, 3, 1, 2, 3, 6, 5, 4, 6, 5, 5, 2, + 7, 6, 2, 5, 6, 2, 5, 6, 1, 4, 3, 1, 2, 1, + 7, 6, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 6, 3, + 7, 6, 0, 4, 3, 0, 1, 3, 6, 5, 1, 4, 1, 0, + 7, 6, 1, 4, 3, 1, 2, 3, 5, 2, 6, 5, 2, 6, + 7, 6, 6, 3, 5, 6, 4, 5, 1, 4, 2, 1, 5, 2, + 7, 6, 1, 0, 3, 1, 6, 3, 5, 6, 4, 5, 1, 4, + 7, 6, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, + 7, 6, 0, 4, 3, 0, 4, 3, 2, 5, 6, 2, 5, 6, + 7, 6, 6, 3, 0, 6, 6, 2, 5, 6, 6, 1, 4, 6, + 7, 6, 2, 4, 5, 2, 2, 3, 6, 2, 1, 2, 1, 0, + 7, 6, 1, 0, 2, 1, 5, 2, 1, 4, 3, 1, 6, 2, + 7, 6, 1, 0, 2, 1, 3, 6, 1, 3, 4, 1, 5, 4, + 7, 6, 1, 0, 2, 1, 5, 2, 6, 5, 1, 4, 3, 1, + 7, 6, 1, 0, 2, 4, 5, 2, 6, 5, 2, 6, 3, 2, + 7, 6, 4, 0, 1, 4, 3, 1, 2, 1, 5, 2, 6, 2, + 7, 6, 6, 5, 1, 2, 0, 1, 2, 0, 3, 2, 0, 4, + 7, 6, 0, 4, 3, 0, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 6, 1, 0, 3, 1, 6, 3, 2, 6, 4, 1, 5, 4, + 7, 6, 2, 5, 6, 2, 4, 2, 1, 4, 3, 1, 0, 3, + 7, 6, 0, 4, 3, 0, 2, 3, 4, 2, 1, 2, 6, 5, + 7, 6, 0, 4, 3, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 6, 3, 4, 1, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 6, 4, 5, 0, 4, 3, 0, 6, 3, 1, 0, 2, 1, + 7, 6, 2, 5, 6, 2, 5, 6, 1, 4, 3, 1, 1, 0, + 7, 6, 4, 5, 3, 4, 2, 3, 1, 2, 0, 1, 6, 0, + 7, 6, 6, 4, 5, 6, 4, 5, 2, 3, 1, 2, 0, 1, + 7, 6, 0, 1, 4, 0, 2, 3, 5, 2, 6, 5, 3, 6, + 7, 6, 1, 2, 0, 1, 4, 0, 3, 4, 2, 3, 6, 5, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 3, 4, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 5, 2, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 1, 0, + 7, 7, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, 2, 0, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 2, 6, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 3, 4, 6, 3, + 7, 7, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, 3, 4, + 7, 7, 0, 4, 3, 0, 1, 3, 3, 6, 1, 4, 1, 0, 5, 4, + 7, 7, 0, 4, 3, 0, 1, 3, 6, 5, 1, 4, 1, 0, 3, 4, + 7, 7, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, 2, 1, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 2, 5, + 7, 7, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, 3, 1, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 2, 0, 2, 1, 6, 0, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 3, 5, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 3, 5, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 1, 5, + 7, 7, 3, 2, 4, 3, 3, 5, 2, 4, 5, 2, 6, 1, 6, 4, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 0, 3, + 7, 7, 3, 4, 1, 3, 2, 1, 6, 2, 5, 6, 1, 5, 4, 1, + 7, 7, 0, 1, 4, 0, 1, 4, 2, 1, 3, 2, 5, 3, 4, 5, + 7, 7, 6, 3, 5, 6, 1, 5, 2, 1, 3, 2, 4, 2, 5, 4, + 7, 7, 1, 2, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 5, 2, + 7, 7, 4, 1, 3, 4, 1, 3, 2, 1, 6, 2, 5, 6, 2, 5, + 7, 7, 3, 0, 6, 3, 0, 6, 1, 0, 0, 2, 5, 0, 0, 4, + 7, 7, 1, 5, 6, 1, 1, 2, 3, 1, 4, 3, 1, 4, 4, 0, + 7, 7, 5, 0, 6, 5, 0, 6, 5, 2, 1, 5, 6, 3, 4, 6, + 7, 7, 4, 1, 0, 4, 1, 0, 2, 1, 0, 3, 6, 0, 4, 5, + 7, 7, 5, 2, 6, 5, 2, 6, 2, 4, 3, 2, 1, 0, 2, 1, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 1, 5, 6, 1, + 7, 7, 1, 0, 4, 1, 0, 4, 5, 4, 2, 1, 3, 2, 6, 1, + 7, 7, 0, 1, 4, 0, 1, 4, 2, 1, 3, 2, 5, 4, 6, 4, + 7, 7, 2, 3, 5, 2, 6, 5, 3, 6, 1, 2, 4, 5, 0, 5, + 7, 7, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 2, 1, 6, 5, + 7, 7, 2, 5, 6, 2, 5, 6, 4, 2, 1, 2, 0, 1, 3, 1, + 7, 7, 2, 5, 6, 2, 4, 2, 1, 4, 3, 1, 2, 3, 0, 1, + 7, 7, 6, 2, 5, 6, 2, 5, 1, 2, 0, 1, 4, 1, 3, 1, + 7, 7, 0, 4, 3, 0, 1, 3, 4, 1, 5, 4, 2, 1, 6, 3, + 7, 7, 2, 5, 6, 2, 5, 6, 4, 5, 3, 6, 1, 2, 0, 1, + 7, 7, 2, 5, 6, 2, 1, 4, 1, 2, 0, 1, 4, 0, 0, 3, + 7, 7, 6, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, 3, 4, + 7, 7, 4, 1, 0, 4, 1, 0, 3, 6, 2, 3, 0, 2, 5, 0, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 5, 2, 6, 1, + 7, 7, 4, 1, 0, 4, 1, 0, 2, 3, 0, 2, 5, 0, 6, 5, + 7, 7, 0, 1, 5, 0, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, + 7, 7, 1, 0, 4, 1, 2, 4, 3, 2, 4, 3, 0, 4, 6, 5, + 7, 7, 3, 6, 2, 3, 1, 2, 0, 1, 4, 0, 1, 4, 5, 4, + 7, 7, 1, 0, 5, 1, 6, 5, 2, 6, 1, 2, 3, 2, 4, 3, + 7, 7, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 4, 1, + 7, 7, 5, 2, 6, 5, 2, 6, 1, 2, 4, 1, 0, 4, 3, 1, + 7, 7, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 5, 2, + 7, 7, 1, 4, 0, 1, 2, 0, 3, 2, 5, 3, 0, 5, 6, 3, + 7, 7, 2, 1, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 4, + 7, 7, 5, 2, 6, 5, 2, 6, 1, 2, 0, 1, 4, 0, 3, 0, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 5, 2, 6, 2, + 7, 7, 1, 0, 2, 1, 5, 2, 4, 5, 0, 4, 4, 1, 6, 3, + 7, 7, 2, 5, 6, 2, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, + 7, 7, 6, 5, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, + 7, 7, 2, 1, 5, 2, 4, 5, 0, 4, 3, 0, 6, 3, 2, 6, + 7, 7, 4, 0, 3, 4, 1, 3, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 7, 6, 5, 2, 6, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 7, 4, 1, 0, 4, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, + 7, 7, 0, 4, 3, 0, 4, 3, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, + 7, 7, 1, 0, 4, 1, 0, 4, 5, 2, 6, 5, 3, 6, 2, 3, + 7, 8, 0, 1, 4, 0, 5, 4, 2, 5, 1, 2, 5, 1, 4, 1, 2, 4, + 7, 8, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 5, 0, 0, 4, 2, 0, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 3, 4, 5, 1, 6, 1, + 7, 8, 4, 1, 5, 4, 2, 5, 1, 2, 5, 1, 6, 5, 2, 4, 3, 2, + 7, 8, 1, 3, 0, 1, 4, 0, 2, 4, 1, 2, 4, 1, 5, 4, 1, 5, + 7, 8, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 3, 0, 0, 6, 4, 0, + 7, 8, 1, 0, 2, 1, 5, 2, 4, 5, 0, 4, 2, 0, 5, 0, 6, 5, + 7, 8, 1, 0, 2, 1, 3, 2, 1, 3, 4, 3, 2, 4, 5, 2, 3, 5, + 7, 8, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 3, 0, 6, 0, 4, 5, + 7, 8, 1, 0, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 3, 5, + 7, 8, 3, 5, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 4, 6, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 3, 4, 2, 1, 5, 2, + 7, 8, 3, 5, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 0, 3, + 7, 8, 4, 0, 2, 4, 0, 2, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, + 7, 8, 3, 2, 6, 3, 5, 6, 2, 5, 0, 2, 5, 0, 4, 5, 2, 4, + 7, 8, 0, 5, 4, 0, 2, 4, 5, 2, 1, 5, 4, 1, 3, 4, 5, 3, + 7, 8, 2, 3, 1, 2, 4, 1, 5, 4, 1, 5, 5, 2, 6, 5, 3, 6, + 7, 8, 5, 2, 4, 5, 0, 4, 3, 0, 6, 3, 2, 6, 4, 2, 3, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 5, 4, 2, 5, + 7, 8, 5, 6, 2, 5, 6, 2, 3, 2, 4, 3, 0, 4, 3, 0, 2, 4, + 7, 8, 1, 0, 5, 0, 3, 2, 1, 3, 5, 2, 6, 1, 6, 2, 6, 5, + 7, 8, 5, 4, 6, 5, 3, 6, 0, 3, 4, 0, 2, 4, 3, 2, 0, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 1, 5, + 7, 8, 5, 0, 6, 2, 0, 6, 1, 0, 2, 1, 5, 2, 4, 5, 4, 6, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 2, 1, 1, 5, 6, 1, + 7, 8, 0, 2, 4, 0, 1, 4, 0, 1, 3, 0, 1, 3, 5, 1, 6, 1, + 7, 8, 4, 2, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 1, 5, 6, 1, + 7, 8, 0, 4, 3, 0, 4, 3, 1, 4, 3, 1, 1, 5, 2, 1, 6, 1, + 7, 8, 2, 1, 0, 2, 3, 0, 5, 3, 2, 5, 3, 2, 4, 3, 6, 5, + 7, 8, 4, 2, 0, 4, 3, 0, 1, 3, 4, 1, 3, 4, 1, 5, 6, 1, + 7, 8, 2, 1, 0, 2, 3, 0, 5, 3, 2, 5, 6, 5, 4, 3, 5, 0, + 7, 8, 1, 0, 2, 1, 3, 2, 1, 3, 4, 2, 3, 4, 4, 5, 6, 4, + 7, 8, 6, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, 0, 1, 3, 4, + 7, 8, 0, 1, 6, 5, 2, 3, 6, 4, 6, 3, 6, 2, 6, 0, 6, 1, + 7, 8, 6, 4, 1, 2, 2, 3, 6, 5, 4, 5, 6, 2, 6, 0, 6, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 5, 6, 4, 6, 3, 6, 0, 6, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 6, 1, 5, 1, 2, 5, + 7, 8, 3, 0, 2, 3, 4, 2, 0, 4, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 8, 2, 1, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 2, 4, 5, + 7, 8, 1, 0, 2, 1, 3, 2, 4, 3, 5, 2, 1, 5, 6, 1, 2, 6, + 7, 8, 2, 5, 4, 2, 1, 4, 3, 1, 0, 3, 1, 0, 2, 1, 6, 2, + 7, 8, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 6, 3, + 7, 8, 0, 1, 4, 0, 1, 4, 2, 1, 4, 2, 5, 4, 1, 5, 6, 3, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 1, 6, 5, 0, + 7, 8, 4, 5, 0, 4, 1, 0, 4, 1, 3, 0, 1, 3, 6, 1, 2, 6, + 7, 8, 2, 5, 4, 2, 0, 4, 1, 0, 4, 1, 3, 0, 1, 3, 6, 1, + 7, 8, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 4, 5, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 1, 6, 5, 3, + 7, 8, 0, 4, 3, 0, 4, 3, 1, 4, 3, 1, 5, 1, 6, 2, 1, 6, + 7, 8, 2, 3, 1, 2, 0, 1, 5, 0, 4, 5, 0, 4, 2, 0, 6, 5, + 7, 8, 4, 5, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 6, 2, + 7, 8, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 4, 1, 2, 6, 5, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 3, 4, 5, 6, 2, 6, 0, 6, 1, + 7, 8, 4, 1, 0, 4, 3, 0, 1, 3, 0, 1, 2, 1, 5, 2, 6, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 5, 4, 5, 6, 2, 6, 4, 6, 1, + 7, 8, 0, 1, 4, 0, 0, 2, 5, 0, 6, 5, 3, 6, 2, 3, 5, 2, + 7, 8, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 2, 5, 6, 2, + 7, 8, 4, 5, 3, 4, 1, 3, 2, 1, 6, 2, 4, 6, 3, 2, 0, 1, + 7, 8, 1, 0, 2, 6, 3, 2, 4, 3, 5, 2, 1, 5, 6, 1, 6, 5, + 7, 8, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 5, 2, 4, 1, + 7, 8, 4, 1, 0, 4, 3, 0, 1, 3, 3, 4, 2, 1, 2, 5, 6, 2, + 7, 8, 0, 6, 4, 0, 1, 4, 3, 1, 0, 3, 2, 4, 3, 2, 5, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 1, 0, 6, 5, + 7, 8, 0, 1, 4, 0, 3, 2, 6, 3, 5, 6, 2, 5, 6, 2, 3, 5, + 7, 8, 5, 2, 6, 5, 2, 6, 4, 2, 0, 4, 3, 0, 2, 3, 1, 2, + 7, 8, 2, 0, 1, 2, 0, 1, 5, 0, 4, 5, 0, 4, 6, 0, 3, 6, + 7, 8, 0, 1, 2, 0, 3, 2, 2, 1, 1, 4, 5, 4, 5, 3, 1, 6, + 7, 8, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, + 7, 8, 6, 1, 0, 6, 1, 0, 5, 1, 0, 5, 2, 1, 3, 2, 4, 3, + 7, 8, 6, 5, 2, 6, 1, 2, 4, 1, 3, 4, 0, 3, 4, 0, 2, 4, + 7, 8, 1, 6, 0, 1, 5, 0, 1, 5, 3, 0, 4, 3, 2, 4, 0, 2, + 7, 8, 2, 6, 4, 2, 0, 4, 1, 0, 4, 1, 3, 4, 5, 3, 2, 5, + 7, 8, 1, 0, 2, 1, 6, 2, 5, 6, 1, 5, 4, 1, 3, 4, 2, 3, + 7, 8, 6, 1, 4, 3, 1, 0, 5, 1, 3, 2, 2, 1, 4, 6, 5, 4, + 7, 8, 4, 2, 0, 4, 1, 0, 4, 1, 3, 4, 6, 3, 5, 6, 3, 5, + 7, 8, 4, 1, 2, 4, 0, 2, 6, 0, 3, 6, 0, 3, 5, 0, 4, 5, + 7, 8, 5, 6, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, + 7, 8, 6, 3, 5, 6, 4, 5, 0, 4, 1, 0, 2, 1, 5, 2, 4, 1, + 7, 8, 0, 1, 2, 0, 3, 2, 2, 1, 1, 4, 5, 4, 5, 3, 4, 6, + 7, 8, 4, 0, 3, 4, 2, 3, 6, 2, 5, 6, 1, 5, 4, 1, 2, 1, + 7, 8, 6, 1, 0, 6, 4, 3, 5, 1, 0, 5, 2, 1, 3, 2, 5, 6, + 7, 8, 6, 2, 5, 6, 3, 5, 6, 3, 4, 3, 0, 4, 1, 0, 4, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 6, 5, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 6, 4, 2, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 6, 0, 5, + 7, 8, 4, 0, 2, 4, 3, 2, 6, 3, 5, 6, 4, 5, 1, 2, 5, 1, + 7, 8, 5, 1, 2, 4, 3, 2, 0, 3, 5, 0, 4, 5, 1, 2, 0, 6, + 7, 8, 5, 6, 2, 5, 4, 2, 0, 4, 3, 0, 2, 3, 1, 4, 3, 1, + 7, 8, 0, 4, 1, 0, 4, 1, 3, 4, 5, 3, 6, 5, 2, 6, 4, 2, + 7, 8, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 2, 6, 1, + 7, 8, 1, 2, 0, 1, 4, 0, 5, 4, 2, 5, 3, 2, 6, 3, 5, 6, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 6, 1, 6, + 7, 8, 6, 2, 5, 6, 2, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 5, 6, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 3, + 7, 8, 0, 4, 1, 0, 3, 2, 1, 4, 2, 5, 5, 3, 6, 4, 6, 3, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 6, 2, 5, 6, 2, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, + 7, 9, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, + 7, 9, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, + 7, 9, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 2, 6, + 7, 9, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 5, 3, 4, 0, 1, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 3, 1, 3, 2, 2, 1, 4, 1, 5, 2, 2, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 1, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 2, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 5, 6, + 7, 9, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 4, 0, 6, 5, 6, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 6, 5, 4, 0, 3, 2, 4, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 5, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, 5, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 4, 3, 1, 3, 2, 3, 0, 5, 1, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 5, 1, 5, 3, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 0, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 3, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, 5, 6, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 2, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 6, 5, 6, 1, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 2, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 0, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 1, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 0, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 5, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 0, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 4, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 2, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 1, 3, 1, 3, 2, 2, 1, 6, 0, 6, 4, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 3, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 2, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 1, 3, 1, 3, 2, 3, 0, 6, 4, 6, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 1, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 6, 0, + 7, 9, 5, 3, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 6, 0, 6, 2, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 4, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 5, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 1, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 3, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, 0, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 3, 3, 1, 3, 2, 5, 1, 6, 4, 6, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, 0, 6, + 7, 9, 6, 3, 1, 2, 6, 5, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, + 7, 9, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 6, 2, 5, 6, 2, 5, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 3, 4, 6, 5, 6, 0, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 6, 4, 6, 3, + 7, 9, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 5, 2, 0, 5, 2, 0, + 7, 9, 4, 1, 3, 1, 2, 3, 4, 0, 5, 0, 5, 2, 5, 4, 6, 4, 5, 6, + 7, 9, 1, 0, 2, 1, 6, 2, 3, 6, 5, 3, 4, 5, 3, 4, 2, 3, 0, 2, + 7, 9, 0, 2, 5, 0, 1, 5, 2, 1, 4, 2, 5, 4, 6, 5, 3, 6, 2, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 1, 3, 4, 1, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 4, 5, 1, 6, 1, 0, 6, + 7, 9, 0, 4, 1, 0, 4, 1, 3, 4, 2, 3, 6, 2, 5, 6, 1, 5, 2, 1, + 7, 9, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 3, 6, 5, 3, 6, + 7, 9, 6, 5, 3, 6, 2, 3, 0, 4, 0, 5, 1, 0, 2, 0, 1, 2, 5, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 6, 1, 0, 6, + 7, 9, 5, 2, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, 5, 4, 1, 5, 0, 1, + 7, 9, 2, 4, 1, 2, 4, 1, 5, 4, 0, 5, 1, 0, 6, 4, 3, 6, 2, 3, + 7, 9, 6, 2, 5, 6, 2, 5, 1, 2, 0, 1, 4, 0, 1, 4, 3, 1, 0, 3, + 7, 9, 0, 5, 6, 0, 1, 6, 4, 1, 2, 4, 3, 2, 1, 3, 5, 1, 6, 5, + 7, 9, 6, 5, 3, 6, 2, 3, 5, 2, 0, 5, 1, 0, 4, 1, 0, 4, 2, 0, + 7, 9, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 6, 2, 5, 6, 2, 5, 3, 2, + 7, 9, 1, 0, 4, 1, 5, 4, 0, 5, 6, 0, 3, 6, 2, 3, 0, 2, 3, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 1, 5, 4, 5, 3, 6, 0, 6, 4, + 7, 9, 0, 1, 4, 0, 1, 4, 2, 1, 5, 2, 4, 5, 6, 5, 3, 6, 2, 3, + 7, 9, 1, 0, 6, 3, 0, 4, 5, 0, 3, 5, 5, 6, 1, 2, 1, 4, 6, 2, + 7, 9, 6, 2, 5, 6, 2, 5, 1, 2, 0, 3, 4, 0, 1, 4, 3, 1, 3, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 1, 5, 6, 6, 0, + 7, 9, 0, 4, 1, 0, 2, 1, 3, 2, 0, 3, 2, 4, 5, 4, 6, 5, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 6, 1, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 4, 6, 1, 6, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 2, + 7, 9, 0, 4, 3, 0, 4, 3, 6, 1, 5, 6, 1, 5, 2, 1, 5, 2, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, + 7, 10, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, + 7, 10, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, + 7, 10, 0, 1, 1, 2, 3, 4, 0, 2, 3, 0, 2, 4, 5, 2, 1, 5, 4, 1, 3, 5, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, 2, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, 1, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 1, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 0, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, 4, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 3, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, 2, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 2, 6, + 7, 10, 2, 3, 1, 2, 4, 1, 5, 4, 2, 5, 0, 2, 4, 0, 0, 1, 5, 0, 6, 5, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 5, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 4, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 3, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, 0, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 1, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 0, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 3, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 0, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 2, 0, 5, 3, 2, 3, 6, 2, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 4, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 2, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 4, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 3, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 2, 0, 5, 3, 2, 3, 0, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 1, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 4, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 4, 6, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 0, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 0, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 5, 6, 4, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 1, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 4, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 3, 6, + 7, 10, 4, 3, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 6, 0, 6, 2, + 7, 10, 1, 0, 2, 1, 3, 2, 4, 3, 5, 4, 1, 5, 6, 1, 4, 6, 2, 6, 5, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 2, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 5, 6, 4, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 1, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 4, 6, + 7, 10, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, 0, 6, + 7, 10, 4, 0, 1, 4, 3, 1, 2, 3, 1, 2, 6, 1, 0, 6, 5, 0, 1, 5, 0, 1, + 7, 10, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 2, 1, 5, 2, 1, 4, 2, 5, 4, + 7, 10, 2, 0, 1, 2, 3, 1, 0, 3, 6, 0, 1, 6, 5, 1, 0, 5, 4, 0, 1, 4, + 7, 10, 6, 4, 1, 2, 6, 5, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 0, 5, 5, 2, 6, 1, 2, 6, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 0, 5, 2, 6, 2, 0, 6, + 7, 10, 6, 4, 1, 2, 6, 5, 3, 4, 4, 5, 0, 5, 6, 3, 6, 1, 6, 2, 0, 4, + 7, 10, 1, 0, 2, 1, 0, 2, 3, 2, 4, 3, 2, 4, 5, 2, 4, 5, 6, 4, 1, 6, + 7, 10, 0, 1, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 5, 2, 6, + 7, 10, 0, 2, 5, 0, 4, 5, 2, 4, 1, 2, 5, 1, 6, 5, 3, 6, 2, 3, 2, 6, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 0, 5, 6, 0, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 6, 4, 0, 2, 4, 0, + 7, 10, 0, 4, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, 4, 2, 1, 4, 3, 1, 4, 3, + 7, 10, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, 4, 5, 3, 1, + 7, 10, 6, 5, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 4, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 3, 5, 2, 6, 5, 6, 1, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 2, 4, 0, 4, 6, 0, 5, 6, 0, 5, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 0, 5, 0, 4, 6, 0, 5, 6, 1, 4, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 4, 2, + 7, 10, 1, 2, 5, 1, 6, 5, 2, 6, 1, 6, 5, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 10, 4, 2, 6, 2, 5, 3, 4, 1, 2, 0, 6, 3, 5, 2, 0, 1, 0, 4, 6, 0, + 7, 10, 4, 2, 3, 6, 5, 3, 5, 1, 2, 0, 6, 0, 5, 2, 1, 4, 0, 4, 5, 4, + 7, 10, 4, 0, 5, 4, 4, 1, 2, 1, 3, 2, 0, 3, 3, 4, 5, 3, 6, 1, 6, 5, + 7, 10, 0, 4, 1, 0, 2, 1, 4, 2, 3, 4, 5, 3, 4, 5, 5, 2, 6, 3, 2, 6, + 7, 10, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, 4, 5, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 3, 6, 1, 6, 5, 5, 1, + 7, 10, 1, 0, 4, 1, 0, 4, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 5, 2, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 5, 4, 6, 1, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 4, 1, 6, 1, 6, 5, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 3, 2, 5, 6, 1, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 2, 5, 4, 6, 5, 6, 0, 0, 2, + 7, 10, 2, 0, 5, 2, 1, 5, 0, 1, 3, 0, 5, 3, 6, 5, 4, 6, 0, 4, 4, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 1, 5, 6, 2, 6, 5, + 7, 10, 5, 0, 6, 5, 2, 6, 3, 2, 0, 3, 4, 0, 2, 4, 4, 3, 1, 4, 3, 1, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 3, 5, 6, 3, 5, 4, 5, 4, 6, + 7, 10, 5, 2, 2, 1, 3, 2, 4, 3, 1, 4, 5, 0, 6, 1, 6, 0, 1, 5, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 6, 4, 4, 2, 5, 1, + 7, 10, 4, 2, 2, 3, 4, 1, 0, 1, 3, 0, 6, 4, 0, 6, 5, 0, 4, 5, 1, 5, + 7, 10, 2, 1, 5, 2, 3, 5, 0, 3, 4, 0, 6, 4, 3, 6, 1, 3, 4, 1, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 6, 5, 6, 3, + 7, 10, 0, 1, 4, 0, 1, 4, 2, 1, 5, 2, 4, 5, 6, 5, 3, 6, 2, 3, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 5, 6, 1, 6, 2, 4, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 3, 5, 2, 6, 5, 6, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 3, 6, 2, 4, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 5, 6, 3, + 7, 10, 0, 5, 6, 0, 1, 6, 0, 1, 1, 5, 2, 1, 3, 2, 4, 3, 6, 4, 4, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 5, 6, 4, 2, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 5, 6, 3, + 7, 10, 2, 1, 2, 0, 3, 2, 4, 3, 1, 4, 5, 1, 5, 0, 6, 0, 1, 6, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 6, 1, 6, 4, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 2, 4, 1, 6, 0, 5, 6, + 7, 10, 3, 1, 0, 3, 5, 0, 1, 5, 2, 1, 6, 2, 0, 6, 0, 4, 4, 2, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 4, 6, 1, 6, 2, 6, 5, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 2, 4, 5, 4, 5, 0, 6, 4, 6, 1, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 2, 6, 1, 4, 2, 5, 1, + 7, 10, 5, 2, 6, 5, 2, 6, 4, 2, 0, 4, 3, 0, 2, 3, 1, 0, 1, 3, 4, 1, + 7, 10, 3, 4, 1, 3, 4, 1, 0, 4, 3, 0, 1, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 10, 5, 6, 2, 5, 6, 2, 3, 6, 0, 3, 4, 0, 5, 4, 1, 4, 3, 1, 2, 1, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 5, 4, 2, + 7, 10, 1, 0, 2, 1, 3, 2, 0, 3, 5, 0, 1, 5, 4, 5, 6, 4, 3, 6, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 2, 5, 6, 0, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, + 7, 11, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 5, 1, + 7, 11, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 2, 5, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 2, 0, 6, + 7, 11, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 6, 5, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 6, 4, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, 1, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, 2, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 5, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 2, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 6, 1, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 3, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 6, 4, + 7, 11, 0, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 0, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 0, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 1, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 0, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 2, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 2, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 5, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 5, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 4, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 6, 5, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 1, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 2, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 1, 6, + 7, 11, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 6, 3, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 6, 3, + 7, 11, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 3, 2, 0, 3, 1, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 6, 2, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 6, + 7, 11, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 11, 6, 5, 0, 6, 5, 0, 1, 5, 6, 1, 2, 6, 5, 2, 3, 5, 6, 3, 4, 6, 5, 4, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 2, 5, 6, 2, 1, 6, 3, 1, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 1, 3, 5, 6, 1, 4, 6, 1, 4, 3, 1, + 7, 11, 1, 4, 2, 3, 4, 2, 0, 6, 4, 5, 6, 5, 3, 1, 6, 4, 3, 0, 3, 6, 4, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 6, 4, 2, 6, 5, 2, 0, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 0, 3, 0, 2, 0, 6, 0, 3, 6, + 7, 11, 0, 1, 2, 0, 5, 2, 6, 5, 2, 6, 1, 2, 4, 1, 2, 4, 3, 2, 4, 3, 3, 1, + 7, 11, 4, 5, 1, 4, 2, 1, 3, 2, 6, 3, 5, 6, 2, 5, 4, 2, 3, 5, 0, 5, 2, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 2, 5, 2, 5, 0, 6, 2, 4, 6, 5, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 1, 2, 6, 0, 2, 6, 0, 5, 2, 0, 5, + 7, 11, 0, 5, 6, 0, 1, 6, 5, 1, 2, 5, 6, 2, 4, 3, 3, 2, 4, 5, 6, 4, 6, 5, + 7, 11, 0, 5, 6, 0, 1, 6, 5, 1, 2, 5, 6, 2, 3, 6, 5, 3, 4, 5, 6, 4, 4, 3, + 7, 11, 0, 5, 0, 6, 1, 2, 1, 6, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 2, 4, 5, 2, 4, 5, 6, 5, 6, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 4, 2, 0, 4, 2, 0, 6, 4, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 5, 1, 5, 2, 6, 1, 2, 6, 4, 2, 5, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 2, 5, 4, 2, 6, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 2, 0, 2, 4, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 1, 5, 3, 4, 5, 1, 4, 6, 5, 6, 1, + 7, 11, 0, 4, 3, 0, 4, 3, 2, 4, 3, 2, 1, 3, 2, 1, 4, 1, 5, 2, 6, 5, 2, 6, + 7, 11, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 11, 0, 1, 4, 0, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 4, 1, 2, 4, 5, 2, 1, 5, + 7, 11, 0, 4, 3, 0, 4, 3, 2, 4, 6, 2, 1, 6, 5, 1, 2, 5, 3, 2, 1, 3, 4, 1, + 7, 11, 0, 1, 6, 5, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, + 7, 11, 4, 1, 0, 4, 1, 0, 3, 1, 0, 3, 5, 1, 6, 5, 1, 6, 2, 1, 5, 2, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 4, 6, 5, 4, 6, + 7, 11, 1, 0, 2, 1, 3, 2, 4, 3, 0, 4, 2, 0, 5, 2, 6, 5, 3, 6, 6, 0, 0, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 2, 4, 0, 6, 4, 0, 6, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 0, 5, 2, 6, 5, 4, 6, 0, 5, 6, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 2, 6, 3, 6, 0, 3, 4, 0, + 7, 11, 4, 6, 5, 4, 6, 5, 3, 6, 5, 3, 2, 5, 3, 2, 5, 0, 6, 0, 1, 0, 2, 1, + 7, 11, 2, 0, 4, 2, 5, 4, 3, 5, 1, 3, 0, 1, 2, 1, 3, 2, 6, 3, 5, 6, 4, 3, + 7, 11, 4, 3, 4, 2, 1, 4, 3, 1, 0, 3, 1, 0, 3, 2, 3, 5, 2, 5, 6, 0, 4, 6, + 7, 11, 0, 1, 0, 2, 2, 3, 5, 1, 1, 3, 5, 2, 6, 3, 6, 0, 5, 3, 4, 5, 3, 4, + 7, 11, 4, 0, 1, 4, 6, 1, 0, 6, 3, 0, 1, 3, 5, 1, 0, 5, 6, 5, 2, 3, 0, 2, + 7, 11, 0, 1, 5, 0, 4, 5, 1, 4, 2, 1, 3, 2, 4, 3, 4, 2, 6, 4, 2, 6, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 3, 5, 6, 3, 4, 6, 5, 6, + 7, 11, 6, 3, 5, 6, 2, 5, 3, 2, 5, 3, 4, 5, 2, 4, 1, 2, 5, 1, 0, 1, 4, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 6, 5, 4, 6, + 7, 11, 0, 4, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, + 7, 11, 5, 0, 0, 1, 3, 0, 5, 3, 2, 5, 6, 2, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 2, 5, 6, 4, 6, 2, 3, 6, + 7, 11, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 1, 2, 0, 1, 5, 1, 2, 5, 4, 2, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 1, 5, 3, 5, 1, 6, 4, 6, 5, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 3, 6, 0, 6, 2, 6, 3, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 5, 4, 6, 3, 6, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 5, 1, 6, 2, 6, 4, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 6, 1, 6, 4, 5, 2, 3, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 1, 4, 6, 6, 5, 2, 6, + 7, 11, 0, 3, 0, 6, 1, 2, 1, 5, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 11, 5, 1, 6, 5, 4, 6, 3, 4, 2, 3, 0, 2, 1, 0, 5, 0, 6, 0, 2, 6, 4, 2, + 7, 11, 1, 0, 2, 1, 3, 2, 4, 3, 0, 4, 5, 2, 3, 5, 6, 0, 6, 5, 6, 2, 3, 6, + 7, 11, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 4, 1, 5, 1, 6, 0, 6, 1, 5, 3, 4, 5, + 7, 11, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 11, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 5, 4, 5, 3, 6, 4, 6, 2, 6, 0, 1, 6, + 7, 11, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 5, 0, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 6, 1, 6, 2, 5, 1, 3, 5, 5, 2, 4, 5, + 7, 11, 0, 5, 0, 6, 1, 4, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 3, 6, 4, 6, 2, 2, 0, 4, 0, + 7, 11, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 5, 4, 5, 3, 6, 4, 6, 2, 4, 0, 1, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 6, 2, 0, 6, 1, 6, + 7, 11, 0, 3, 4, 0, 1, 4, 3, 1, 4, 3, 0, 1, 2, 4, 6, 2, 5, 6, 2, 5, 1, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 6, 1, 6, 5, + 7, 11, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 4, 0, 5, 0, 6, 5, 3, 6, 2, 3, 5, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 5, 1, 5, 4, 6, 5, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 6, 3, 6, 4, + 7, 11, 0, 4, 3, 0, 1, 3, 4, 1, 3, 4, 5, 1, 6, 5, 1, 6, 2, 1, 5, 2, 6, 2, + 7, 11, 4, 1, 0, 4, 3, 0, 2, 5, 5, 4, 6, 5, 2, 6, 1, 2, 3, 1, 6, 3, 2, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 2, 5, 4, 5, 3, 6, 0, 6, 5, 3, 6, + 7, 11, 5, 2, 2, 4, 5, 3, 4, 1, 5, 4, 0, 1, 3, 0, 0, 2, 6, 2, 6, 3, 0, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 6, 1, 0, 6, + 7, 11, 0, 3, 0, 4, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 11, 4, 0, 3, 4, 5, 3, 0, 5, 1, 0, 2, 1, 3, 2, 4, 1, 5, 2, 6, 4, 5, 6, + 7, 11, 2, 3, 4, 2, 0, 4, 5, 0, 1, 5, 4, 1, 3, 4, 5, 3, 1, 0, 6, 5, 6, 2, + 7, 11, 4, 1, 0, 4, 3, 0, 4, 3, 5, 4, 6, 5, 2, 6, 1, 2, 3, 1, 6, 3, 2, 5, + 7, 11, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 0, 1, 6, 0, 5, 6, 2, 5, 1, 5, 4, 1, + 7, 11, 0, 3, 0, 4, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 6, 4, 5, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 5, 1, 5, 4, 6, 1, 5, 6, + 7, 11, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 0, 1, 5, 0, 4, 0, 5, 2, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 2, 3, 5, 4, 5, 3, 0, 6, 5, 6, 1, + 7, 11, 0, 4, 1, 0, 4, 1, 3, 4, 2, 3, 1, 2, 6, 1, 5, 6, 3, 5, 5, 4, 2, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 4, 2, 6, 2, 3, 6, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 1, 6, 5, 2, 4, 1, 0, 3, + 7, 11, 0, 3, 0, 4, 1, 2, 1, 5, 1, 6, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 3, 5, 1, 6, 3, 6, 4, 4, 2, 0, 3, + 7, 11, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 6, 4, 5, + 7, 11, 4, 3, 2, 4, 3, 2, 0, 3, 2, 1, 5, 4, 5, 0, 6, 4, 6, 1, 1, 5, 0, 6, + 7, 11, 6, 4, 3, 6, 1, 3, 4, 1, 0, 4, 2, 0, 3, 2, 0, 1, 5, 0, 6, 5, 5, 2, + 7, 11, 6, 1, 2, 6, 1, 2, 0, 1, 3, 0, 4, 3, 5, 4, 3, 5, 2, 0, 4, 0, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 0, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 5, 1, 2, 4, + 7, 12, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, 6, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 4, 6, 5, 3, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 1, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 6, 1, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 6, 1, 6, 5, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 3, 5, 0, 3, 5, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 1, 4, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 3, 5, 0, 3, 3, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 4, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 2, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 0, 4, + 7, 12, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, 3, 6, + 7, 12, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, 5, 6, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 6, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 0, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 4, 5, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 5, 6, + 7, 12, 0, 3, 6, 0, 5, 6, 3, 5, 1, 3, 6, 1, 4, 6, 3, 4, 2, 3, 6, 2, 3, 6, 5, 4, + 7, 12, 0, 1, 4, 0, 5, 4, 1, 5, 4, 1, 2, 4, 1, 2, 6, 1, 4, 6, 2, 6, 3, 2, 4, 3, + 7, 12, 4, 1, 3, 2, 3, 0, 4, 2, 5, 1, 5, 0, 3, 5, 4, 3, 5, 4, 6, 5, 3, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 4, 2, 3, 4, 5, 3, 0, 5, 6, 3, 1, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 6, 3, 0, 6, 5, 0, 1, 5, 4, 1, 2, 4, + 7, 12, 6, 2, 5, 6, 3, 5, 2, 3, 1, 2, 4, 1, 5, 4, 2, 5, 4, 2, 0, 4, 1, 0, 5, 1, + 7, 12, 5, 4, 6, 5, 3, 6, 4, 3, 0, 4, 3, 0, 1, 3, 0, 1, 4, 1, 3, 5, 2, 3, 4, 2, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 1, 5, 6, 1, 0, 6, + 7, 12, 1, 2, 0, 1, 2, 0, 3, 2, 0, 3, 1, 3, 4, 2, 0, 4, 5, 4, 2, 5, 6, 2, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 2, 4, 5, 6, 4, 3, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 1, 5, 6, 1, 2, 6, + 7, 12, 1, 2, 0, 1, 2, 0, 3, 2, 0, 3, 1, 3, 4, 2, 0, 4, 6, 4, 2, 6, 5, 2, 4, 5, + 7, 12, 1, 0, 2, 1, 0, 2, 3, 0, 4, 3, 0, 4, 5, 0, 3, 5, 4, 5, 6, 4, 3, 6, 6, 0, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 4, 1, 0, 4, 5, 0, 2, 5, 6, 5, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 4, 0, 6, 5, 0, 3, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 1, 5, 6, 1, 0, 6, 5, 0, 4, 5, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 1, 0, 6, 5, 0, 2, 5, + 7, 12, 5, 4, 3, 5, 4, 3, 6, 4, 3, 6, 2, 3, 4, 2, 0, 4, 3, 0, 1, 0, 2, 1, 6, 2, + 7, 12, 4, 1, 3, 2, 3, 0, 4, 2, 5, 1, 5, 0, 3, 5, 4, 3, 5, 4, 6, 5, 0, 6, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 1, 0, 6, 5, 0, 1, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 3, 1, 5, 1, 6, 5, 4, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 4, 3, 5, 6, 3, 4, 6, + 7, 12, 1, 0, 2, 1, 3, 2, 0, 3, 4, 3, 1, 4, 4, 0, 5, 4, 0, 5, 3, 5, 6, 0, 1, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 2, 4, 6, 5, 0, 1, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 5, 4, 2, 5, 1, 5, 6, 1, 5, 6, + 7, 12, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 4, 2, 5, 3, 6, 1, 6, 4, 4, 0, 3, 4, 1, 5, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 2, 4, 3, 4, 1, 0, 4, 5, 2, 5, 4, 6, 0, 3, 6, + 7, 12, 5, 0, 2, 5, 6, 2, 3, 6, 2, 3, 1, 2, 0, 1, 4, 0, 1, 4, 5, 1, 6, 5, 0, 2, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 4, 2, 6, 5, 2, 3, 5, + 7, 12, 0, 2, 1, 0, 5, 1, 3, 5, 6, 3, 2, 6, 4, 2, 3, 4, 5, 4, 2, 5, 1, 2, 4, 1, + 7, 12, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 4, 0, 1, 4, 4, 2, 3, 4, 5, 4, 6, 5, 3, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 6, 1, 2, 6, 4, 6, 3, 4, 5, 3, 6, 5, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 6, 5, 0, 6, 3, 4, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 2, 5, 6, 0, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 5, 4, 6, 3, 4, 6, 1, 6, 6, 5, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 2, 6, 5, 0, 6, 6, 2, 0, 5, 1, 5, 6, 1, + 7, 12, 0, 1, 6, 5, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, 5, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 6, 1, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 4, 5, 6, 4, 5, 6, + 7, 12, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 6, 3, 6, 1, + 7, 12, 0, 4, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 4, 6, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 6, 1, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 3, 5, 1, 6, 3, 6, 4, 4, 2, 0, 3, 4, 3, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 6, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 6, 5, 0, 6, 1, 6, + 7, 12, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 4, 1, 1, 0, 5, 1, 5, 2, 6, 1, 2, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 6, 5, 3, 1, 5, 6, 2, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 5, 5, 3, 6, 1, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 5, 5, 3, 6, 1, 0, 6, + 7, 12, 4, 3, 1, 2, 0, 1, 0, 3, 4, 0, 6, 4, 4, 2, 5, 4, 6, 2, 6, 3, 3, 2, 5, 1, + 7, 12, 2, 3, 4, 2, 0, 4, 6, 0, 6, 5, 4, 5, 1, 3, 5, 1, 0, 5, 6, 1, 3, 6, 5, 3, + 7, 12, 0, 3, 0, 5, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 4, 3, 5, 3, 6, 1, 3, 6, 6, 2, 0, 6, 4, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 6, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 4, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 3, 1, 6, + 7, 12, 4, 3, 1, 2, 5, 0, 0, 3, 4, 0, 4, 1, 4, 2, 5, 1, 6, 2, 6, 3, 3, 2, 6, 4, + 7, 12, 2, 3, 4, 2, 5, 4, 0, 5, 6, 0, 1, 6, 3, 1, 6, 3, 5, 3, 1, 5, 4, 0, 3, 0, + 7, 12, 0, 3, 0, 5, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 12, 3, 2, 1, 2, 5, 0, 0, 3, 4, 0, 4, 1, 6, 4, 5, 1, 6, 2, 6, 3, 6, 1, 0, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 5, 1, 2, 1, 4, 1, 6, 2, 4, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 6, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 3, 4, 5, 3, 2, 6, 6, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 3, 4, 5, 3, 6, 1, 6, 5, + 7, 12, 0, 2, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 1, 2, 4, 5, 2, 0, 5, 4, 3, 6, 5, 6, 4, 3, 5, + 7, 12, 0, 2, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 12, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 5, 3, 4, 5, 6, 4, + 7, 12, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 1, 0, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 5, 3, 6, 4, 1, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 6, 4, 6, 5, 6, + 7, 12, 2, 3, 4, 2, 5, 2, 4, 1, 6, 0, 3, 0, 3, 1, 6, 3, 5, 6, 1, 5, 4, 0, 3, 4, + 7, 12, 2, 3, 4, 2, 4, 1, 2, 5, 6, 0, 6, 4, 3, 1, 6, 3, 0, 3, 1, 5, 4, 0, 5, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 1, 3, 6, 4, 5, 4, + 7, 12, 6, 3, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 0, 6, 4, 1, 3, 4, 6, 5, 5, 1, 0, 4, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 4, 5, 2, 4, 6, 2, 5, 6, 2, 5, 3, 2, 6, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 5, 2, 5, 4, 6, 4, 6, 3, 6, 2, 5, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, + 7, 12, 3, 0, 4, 2, 3, 1, 4, 0, 5, 2, 5, 1, 4, 3, 5, 4, 3, 5, 6, 1, 0, 6, 2, 6, + 7, 12, 1, 0, 4, 1, 0, 4, 5, 0, 6, 5, 1, 6, 3, 4, 5, 3, 2, 3, 5, 2, 6, 3, 2, 6, + 7, 12, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, 6, 5, + 7, 12, 3, 6, 1, 2, 0, 6, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 2, 5, 4, 5, 6, 4, + 7, 13, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, 6, 4, + 7, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, 1, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 6, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 5, 1, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, 5, 6, + 7, 13, 0, 5, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 5, 6, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 2, 1, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 6, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 0, 3, 6, 5, 3, 4, 5, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 0, 3, 6, 5, 3, 0, 5, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 4, 0, 6, 5, 0, 3, 5, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 4, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 1, 6, 5, 1, 6, + 7, 13, 0, 4, 0, 6, 1, 3, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 6, 5, 4, 6, + 7, 13, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 6, 5, 5, 1, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 4, 0, 5, 6, 0, 3, 6, 6, 4, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 5, 1, 2, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 5, 6, + 7, 13, 1, 0, 2, 1, 4, 2, 1, 4, 3, 1, 0, 3, 2, 3, 5, 2, 1, 5, 0, 5, 6, 0, 1, 6, 2, 6, + 7, 13, 2, 5, 6, 2, 5, 6, 4, 5, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 6, 3, 2, 1, 4, 2, 3, 2, + 7, 13, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 4, 3, 2, 1, 3, 4, 1, 0, 4, 3, 0, 6, 3, 1, 6, 5, 1, 4, 5, 6, 4, 3, 5, 1, 2, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 3, 5, 3, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 5, 3, 6, 4, 5, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 3, 4, 3, 5, + 7, 13, 0, 2, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 5, 1, 2, 5, 1, 4, 5, 3, 4, 0, 3, 4, 0, 3, 2, 4, 2, 1, 3, 6, 3, 2, 6, 1, 6, + 7, 13, 0, 4, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 0, 4, 6, 1, 4, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 5, 0, 6, 5, 1, 6, 4, 0, + 7, 13, 0, 1, 1, 2, 0, 2, 3, 0, 1, 3, 3, 2, 4, 0, 2, 5, 3, 4, 5, 3, 0, 5, 6, 4, 6, 1, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 6, 2, 5, 6, 0, 5, 1, 2, + 7, 13, 5, 4, 6, 2, 6, 4, 4, 3, 5, 0, 3, 1, 3, 2, 6, 3, 5, 6, 4, 0, 1, 4, 5, 1, 0, 3, + 7, 13, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 6, 5, 6, 4, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 0, 4, 0, 6, 4, 6, + 7, 13, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 5, 0, 4, 5, 1, 5, 6, 1, 0, 6, 3, 6, + 7, 13, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 6, 3, 4, 6, 5, 1, + 7, 13, 5, 2, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 6, 2, 6, 3, 1, 2, 3, 1, 4, 0, + 7, 13, 1, 0, 2, 1, 0, 2, 0, 3, 3, 2, 6, 2, 1, 6, 5, 1, 6, 5, 4, 6, 5, 4, 0, 5, 4, 0, + 7, 13, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 1, 2, 4, 1, 3, 1, 0, 4, 2, 3, 0, 3, 2, 0, 5, 0, 6, 5, 4, 6, 3, 5, 4, 2, + 7, 13, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 0, 4, 2, 3, 6, 5, 0, 6, + 7, 13, 5, 2, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 6, 2, 6, 3, 2, 3, 5, 0, 4, 0, + 7, 13, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 6, 3, 6, 4, 4, 2, 0, 3, + 7, 13, 0, 1, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 2, 0, 6, 5, 0, 2, 5, 5, 3, 4, 5, 6, 4, 3, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 5, 0, 6, 0, 2, 2, 5, 5, 3, 4, 5, 6, 4, 3, 6, + 7, 13, 3, 4, 0, 3, 4, 0, 3, 6, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 5, 2, 3, 5, 6, 2, 5, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 6, 5, 3, 6, 4, 6, + 7, 13, 1, 0, 2, 1, 6, 0, 1, 4, 3, 1, 0, 3, 2, 3, 1, 6, 1, 5, 2, 6, 4, 3, 5, 4, 6, 5, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 5, 2, 6, 3, 5, 4, 6, + 7, 13, 0, 6, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 6, 3, 4, 1, 3, 4, 6, 5, 1, 5, 3, 5, 1, 3, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 6, 4, 4, 3, 5, 4, 5, 2, 6, 0, 3, 6, 3, 5, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 6, 0, 4, 6, 5, 0, 6, 6, 4, 2, 5, 5, 3, 4, 5, 1, 5, 6, 1, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 3, 5, 2, 6, 5, 3, 6, 4, 3, 5, 4, 0, 5, 2, 0, 1, 2, 0, 1, 5, 1, 4, 2, 6, 4, + 7, 13, 2, 1, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 3, 4, + 7, 13, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 6, 0, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 4, 6, 5, 1, 4, 5, 3, 1, 5, 2, 6, 0, 6, 1, 2, 1, 4, 1, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 3, 5, 4, 5, 2, 5, 1, 6, 5, 1, 6, 2, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 13, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 1, 3, 6, 4, 5, 4, 5, 3, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 2, 0, 5, 0, 6, 1, 2, 1, 4, 1, 6, 2, 3, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 3, 4, 5, 0, 3, 5, 0, 6, 6, 4, 2, 3, 4, 2, 1, 0, 2, 1, 1, 6, 5, 1, 2, 5, 6, 2, + 7, 13, 0, 2, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 3, 0, 2, 3, 4, 2, 0, 4, 5, 3, 5, 2, 5, 4, 1, 3, 1, 0, 4, 1, 6, 0, 3, 6, 1, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 6, 0, 1, 6, 6, 3, 4, 6, 1, 2, 5, 0, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 5, 3, 5, 4, 6, 4, 6, 2, 6, 5, 5, 2, 3, 6, + 7, 13, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 2, 1, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, 0, 4, 1, 6, + 7, 14, 0, 6, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 1, 2, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 3, 6, 5, 3, 4, 5, 6, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 6, 2, 1, 6, 5, 1, 0, 5, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 2, 3, 5, 6, 4, 0, 6, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, 6, 4, 0, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 3, 4, 3, 5, 4, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 0, 1, 5, 1, 0, 4, 1, 4, 5, 3, 2, 5, 6, 4, 0, 6, + 7, 14, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 5, 1, 2, 6, 0, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 6, 0, 3, 4, 3, 1, 5, 4, 5, 0, 0, 3, 1, 5, 5, 3, 6, 4, 6, 1, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 6, 5, 4, 6, 5, 4, + 7, 14, 3, 1, 1, 4, 2, 3, 3, 4, 0, 4, 1, 5, 0, 1, 0, 2, 2, 5, 5, 3, 4, 5, 1, 2, 6, 2, 5, 6, + 7, 14, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 6, 1, 2, 1, 4, 1, 5, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 1, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 4, 6, 5, 2, 1, 5, 0, 5, 6, 3, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 4, 2, 3, 0, 6, 1, 5, 6, + 7, 14, 0, 4, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 1, 2, 5, 2, 4, 0, 3, 1, 5, 0, 6, 5, 6, 4, 0, 1, + 7, 14, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 5, 2, 6, 0, 6, 1, 5, 0, 1, 2, 3, 1, 4, 0, + 7, 14, 5, 6, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 2, 1, 3, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 6, 0, 3, 6, + 7, 14, 3, 1, 4, 2, 4, 5, 4, 0, 1, 4, 0, 3, 5, 0, 5, 2, 6, 1, 3, 6, 6, 0, 5, 6, 6, 2, 4, 6, + 7, 14, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 4, 1, 5, 6, 1, 3, 6, 0, 5, 6, 0, + 7, 14, 3, 4, 4, 2, 1, 5, 4, 0, 1, 4, 5, 3, 3, 0, 5, 2, 6, 4, 0, 6, 3, 6, 2, 6, 5, 6, 1, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 2, 3, 2, 6, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 4, 5, 3, 4, 3, 1, 5, 2, 1, 6, 5, 6, 6, 0, 5, 3, 6, 4, 0, 1, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 6, 0, 3, 1, 5, 2, 4, 5, 5, 6, 1, 5, 5, 3, 6, 4, 3, 0, + 7, 14, 3, 1, 4, 2, 0, 3, 4, 0, 1, 4, 5, 3, 5, 0, 5, 2, 6, 4, 1, 6, 6, 3, 0, 6, 6, 5, 2, 6, + 7, 14, 0, 1, 4, 2, 3, 0, 4, 0, 4, 5, 5, 3, 1, 3, 5, 2, 6, 4, 2, 6, 6, 5, 3, 6, 6, 1, 0, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 6, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 6, 1, 3, 1, 5, 2, 5, 0, 5, 6, 4, 5, 5, 3, 6, 0, 3, 0, + 7, 14, 2, 3, 4, 2, 3, 0, 4, 0, 4, 5, 3, 4, 3, 1, 5, 2, 0, 1, 5, 6, 6, 0, 5, 3, 6, 4, 1, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 0, 4, 0, 6, 1, 3, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 4, 5, 3, 1, 5, 2, 5, 0, 6, 1, 0, 6, 5, 3, 6, 4, 3, 0, + 7, 14, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 4, 5, 3, 5, 3, 1, 5, 2, 3, 0, 1, 4, 6, 0, 1, 6, 6, 4, 0, 1, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 5, 1, 2, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 5, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 0, 2, 5, 0, 3, 5, 1, 3, 6, 1, 4, 6, 2, 4, + 7, 14, 0, 3, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 5, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, 1, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, 0, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 4, 6, 5, 6, 1, 5, 3, 5, 2, 3, 2, 4, 1, 6, 0, 1, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 6, 1, 4, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 0, 5, 5, 2, 3, 5, 4, 5, 6, 1, 5, 6, + 7, 15, 4, 3, 4, 5, 5, 3, 0, 1, 0, 5, 0, 3, 2, 4, 1, 5, 1, 3, 6, 5, 3, 6, 6, 0, 1, 6, 6, 2, 4, 6, + 7, 15, 3, 4, 4, 5, 5, 6, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 4, 6, 1, 5, 3, 5, 2, 3, 2, 4, 1, 6, 0, 1, + 7, 15, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 6, 1, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 4, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 6, 0, 4, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 1, 1, 2, 0, 2, 3, 0, 1, 3, 2, 3, 5, 1, 3, 5, 4, 3, 2, 4, 6, 2, 3, 6, 6, 1, 0, 5, 4, 0, + 7, 15, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 3, 1, 4, 2, 0, 4, 3, 0, 6, 3, 4, 6, 5, 4, 3, 5, 6, 2, 5, 1, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 6, 3, + 7, 15, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 6, 1, 2, 6, 5, 2, 4, 5, 6, 4, 0, 6, 6, 5, 3, 6, + 7, 15, 0, 1, 5, 3, 1, 3, 0, 4, 3, 0, 4, 3, 2, 4, 5, 2, 4, 5, 6, 4, 2, 6, 6, 5, 3, 6, 6, 1, 0, 6, + 7, 15, 5, 2, 4, 5, 3, 1, 0, 4, 0, 5, 0, 3, 2, 4, 1, 5, 1, 4, 6, 3, 1, 6, 6, 0, 5, 6, 6, 2, 4, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 2, 0, 3, 1, 6, 4, 5, 6, 6, 3, 0, 6, 6, 2, 1, 6, + 7, 15, 5, 2, 3, 0, 5, 3, 0, 4, 0, 5, 4, 3, 2, 4, 1, 5, 1, 4, 6, 4, 2, 6, 6, 0, 5, 6, 6, 3, 1, 6, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 6, 1, 4, 5, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 0, 6, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 3, 4, 0, 1, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 6, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 15, 5, 2, 4, 5, 5, 3, 0, 4, 0, 1, 1, 3, 2, 4, 3, 0, 1, 4, 6, 4, 1, 6, 6, 0, 3, 6, 6, 2, 5, 6, + 7, 15, 5, 0, 4, 3, 5, 3, 5, 2, 0, 1, 1, 3, 2, 4, 3, 0, 1, 4, 6, 2, 5, 6, 6, 4, 3, 6, 6, 0, 1, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 4, 6, 0, 1, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 0, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 2, 0, 3, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 15, 3, 4, 5, 0, 0, 3, 0, 4, 4, 6, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 6, 4, 5, 2, 0, 3, 0, 4, 2, 4, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 0, 1, 5, 6, 4, 5, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 6, 2, 5, 6, 6, 1, 0, 6, 6, 4, 3, 6, + 7, 15, 3, 0, 3, 5, 3, 4, 2, 0, 2, 5, 2, 4, 1, 4, 1, 5, 1, 0, 6, 0, 1, 6, 6, 5, 3, 6, 6, 4, 2, 6, + 7, 15, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 15, 3, 4, 6, 2, 0, 3, 0, 4, 5, 0, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 4, 5, 2, 3, 2, 4, 5, 1, 5, 2, + 7, 15, 3, 4, 6, 2, 0, 3, 0, 4, 5, 0, 5, 6, 3, 6, 1, 3, 1, 4, 0, 1, 4, 6, 2, 3, 2, 4, 5, 1, 5, 2, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 2, 1, 6, 6, 0, 4, 6, 5, 4, 0, 5, 3, 5, 6, 3, 5, 2, 1, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 2, 6, + 7, 16, 3, 0, 4, 1, 4, 3, 1, 3, 4, 0, 2, 5, 6, 2, 5, 6, 1, 5, 4, 5, 3, 5, 0, 5, 0, 6, 3, 6, 4, 6, 6, 1, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 5, 6, 4, 5, 2, 5, + 7, 16, 2, 4, 3, 1, 3, 0, 4, 3, 4, 0, 5, 2, 4, 5, 5, 0, 3, 5, 5, 1, 6, 5, 1, 6, 3, 6, 6, 0, 4, 6, 6, 2, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 4, 4, 1, 3, 0, 3, 1, 4, 0, 5, 2, 4, 5, 5, 0, 3, 5, 6, 5, 1, 5, 6, 1, 3, 6, 4, 6, 6, 2, 6, 0, + 7, 16, 0, 1, 0, 3, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 5, 0, 1, 4, 5, 1, 3, 5, 0, 4, 3, 5, 3, 2, 4, 1, 4, 3, 0, 6, 3, 2, 6, 6, 4, 5, 6, 6, 1, 0, 6, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 5, 5, 1, 3, 1, 0, 4, 5, 0, 4, 3, 5, 3, 2, 4, 1, 4, 3, 0, 6, 2, 4, 6, 5, 6, 6, 1, 0, 6, 3, 6, + 7, 16, 1, 6, 0, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 5, 6, 4, 5, 2, 5, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 6, 2, 3, 2, 4, 5, 6, 0, 1, 2, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 16, 2, 5, 5, 1, 3, 5, 0, 4, 0, 1, 4, 3, 3, 2, 2, 4, 1, 4, 0, 5, 6, 4, 2, 6, 6, 3, 5, 6, 6, 1, 0, 6, + 7, 16, 5, 6, 5, 1, 0, 3, 0, 4, 0, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 6, 2, 4, 5, 2, 5, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 0, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 5, 0, 2, 3, 2, 4, 6, 2, 6, 5, 2, 5, + 7, 16, 5, 0, 5, 1, 0, 3, 0, 4, 6, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 6, 2, 4, 5, 2, 5, + 7, 17, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 6, 2, 1, 6, + 7, 17, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 4, 5, 4, 6, + 7, 17, 4, 0, 4, 3, 0, 1, 3, 0, 2, 4, 3, 1, 5, 3, 4, 5, 5, 2, 6, 5, 5, 0, 1, 5, 6, 1, 0, 6, 6, 4, 2, 6, 3, 6, + 7, 17, 0, 1, 5, 1, 5, 3, 0, 4, 5, 0, 4, 3, 3, 1, 2, 5, 1, 4, 3, 0, 2, 4, 6, 2, 5, 6, 6, 3, 1, 6, 6, 0, 4, 6, + 7, 17, 3, 4, 5, 1, 0, 3, 0, 4, 4, 5, 4, 6, 3, 6, 1, 3, 1, 4, 0, 1, 3, 5, 2, 3, 2, 4, 2, 5, 5, 0, 5, 6, 6, 2, + 7, 17, 3, 2, 4, 1, 0, 1, 3, 0, 2, 4, 4, 3, 5, 1, 4, 5, 5, 2, 0, 5, 5, 3, 6, 5, 2, 6, 6, 3, 0, 6, 1, 6, 4, 6, + 7, 17, 3, 2, 4, 1, 4, 0, 3, 0, 2, 4, 3, 1, 5, 2, 4, 5, 5, 0, 3, 5, 5, 1, 6, 5, 2, 6, 6, 0, 3, 6, 6, 4, 1, 6, + 7, 17, 3, 2, 5, 1, 5, 0, 0, 4, 0, 1, 4, 3, 5, 3, 2, 5, 1, 4, 3, 0, 2, 4, 6, 4, 5, 6, 6, 2, 3, 6, 6, 0, 1, 6, + 7, 17, 3, 2, 5, 1, 5, 0, 0, 4, 4, 5, 0, 1, 3, 1, 2, 5, 1, 4, 3, 0, 2, 4, 6, 0, 3, 6, 6, 1, 5, 6, 6, 2, 4, 6, + 7, 17, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 6, 1, 0, 6, 5, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 18, 4, 0, 4, 5, 3, 0, 3, 5, 2, 0, 2, 5, 1, 3, 1, 4, 1, 5, 1, 0, 2, 3, 2, 4, 6, 0, 5, 6, 6, 1, 2, 6, 6, 4, 3, 6, + 7, 19, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 19, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 20, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 21, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, +}; + +const igraph_int_t igraph_i_atlas_edges_pos[] = {0, 2, 4, 6, 10, 12, 16, 22, 30, 32, 36, 42, 48, 56, 64, 72, 82, 92, 104, 118, 120, 124, 130, 136, 144, 152, 160, 168, 178, 188, 198, 208, 218, 228, 240, 252, 264, 276, 288, 300, 314, 328, 342, 356, 370, 384, 400, 416, 432, 448, 466, 484, 504, 526, 528, 532, 538, 544, 552, 560, 568, 576, 584, 594, 604, 614, 624, 634, 644, 654, 664, 674, 686, 698, 710, 722, 734, 746, 758, 770, 782, 794, 806, 818, 830, 842, 854, 868, 882, 896, 910, 924, 938, 952, 966, 980, 994, 1008, 1022, 1036, 1050, 1064, 1078, 1092, 1106, 1120, 1134, 1148, 1164, 1180, 1196, 1212, 1228, 1244, 1260, 1276, 1292, 1308, 1324, 1340, 1356, 1372, 1388, 1404, 1420, 1436, 1452, 1468, 1484, 1500, 1516, 1532, 1550, 1568, 1586, 1604, 1622, 1640, 1658, 1676, 1694, 1712, 1730, 1748, 1766, 1784, 1802, 1820, 1838, 1856, 1874, 1892, 1910, 1928, 1946, 1964, 1984, 2004, 2024, 2044, 2064, 2084, 2104, 2124, 2144, 2164, 2184, 2204, 2224, 2244, 2264, 2284, 2304, 2324, 2344, 2364, 2384, 2406, 2428, 2450, 2472, 2494, 2516, 2538, 2560, 2582, 2604, 2626, 2648, 2670, 2692, 2714, 2738, 2762, 2786, 2810, 2834, 2858, 2882, 2906, 2930, 2956, 2982, 3008, 3034, 3060, 3088, 3116, 3146, 3178, 3180, 3184, 3190, 3196, 3204, 3212, 3220, 3228, 3236, 3246, 3256, 3266, 3276, 3286, 3296, 3306, 3316, 3326, 3336, 3348, 3360, 3372, 3384, 3396, 3408, 3420, 3432, 3444, 3456, 3468, 3480, 3492, 3504, 3516, 3528, 3540, 3552, 3564, 3576, 3588, 3602, 3616, 3630, 3644, 3658, 3672, 3686, 3700, 3714, 3728, 3742, 3756, 3770, 3784, 3798, 3812, 3826, 3840, 3854, 3868, 3882, 3896, 3910, 3924, 3938, 3952, 3966, 3980, 3994, 4008, 4022, 4036, 4050, 4064, 4078, 4092, 4106, 4120, 4134, 4148, 4162, 4178, 4194, 4210, 4226, 4242, 4258, 4274, 4290, 4306, 4322, 4338, 4354, 4370, 4386, 4402, 4418, 4434, 4450, 4466, 4482, 4498, 4514, 4530, 4546, 4562, 4578, 4594, 4610, 4626, 4642, 4658, 4674, 4690, 4706, 4722, 4738, 4754, 4770, 4786, 4802, 4818, 4834, 4850, 4866, 4882, 4898, 4914, 4930, 4946, 4962, 4978, 4994, 5010, 5026, 5042, 5058, 5074, 5090, 5106, 5122, 5138, 5154, 5170, 5186, 5202, 5220, 5238, 5256, 5274, 5292, 5310, 5328, 5346, 5364, 5382, 5400, 5418, 5436, 5454, 5472, 5490, 5508, 5526, 5544, 5562, 5580, 5598, 5616, 5634, 5652, 5670, 5688, 5706, 5724, 5742, 5760, 5778, 5796, 5814, 5832, 5850, 5868, 5886, 5904, 5922, 5940, 5958, 5976, 5994, 6012, 6030, 6048, 6066, 6084, 6102, 6120, 6138, 6156, 6174, 6192, 6210, 6228, 6246, 6264, 6282, 6300, 6318, 6336, 6354, 6372, 6390, 6408, 6426, 6444, 6462, 6480, 6498, 6516, 6534, 6552, 6570, 6588, 6606, 6624, 6642, 6660, 6678, 6696, 6714, 6732, 6750, 6768, 6786, 6804, 6822, 6840, 6858, 6876, 6894, 6912, 6930, 6948, 6968, 6988, 7008, 7028, 7048, 7068, 7088, 7108, 7128, 7148, 7168, 7188, 7208, 7228, 7248, 7268, 7288, 7308, 7328, 7348, 7368, 7388, 7408, 7428, 7448, 7468, 7488, 7508, 7528, 7548, 7568, 7588, 7608, 7628, 7648, 7668, 7688, 7708, 7728, 7748, 7768, 7788, 7808, 7828, 7848, 7868, 7888, 7908, 7928, 7948, 7968, 7988, 8008, 8028, 8048, 8068, 8088, 8108, 8128, 8148, 8168, 8188, 8208, 8228, 8248, 8268, 8288, 8308, 8328, 8348, 8368, 8388, 8408, 8428, 8448, 8468, 8488, 8508, 8528, 8548, 8568, 8588, 8608, 8628, 8648, 8668, 8688, 8708, 8728, 8748, 8768, 8788, 8808, 8828, 8848, 8868, 8888, 8908, 8928, 8948, 8968, 8988, 9008, 9028, 9048, 9068, 9088, 9108, 9128, 9148, 9168, 9188, 9208, 9228, 9248, 9268, 9288, 9308, 9328, 9348, 9368, 9388, 9408, 9428, 9448, 9468, 9488, 9508, 9528, 9548, 9568, 9590, 9612, 9634, 9656, 9678, 9700, 9722, 9744, 9766, 9788, 9810, 9832, 9854, 9876, 9898, 9920, 9942, 9964, 9986, 10008, 10030, 10052, 10074, 10096, 10118, 10140, 10162, 10184, 10206, 10228, 10250, 10272, 10294, 10316, 10338, 10360, 10382, 10404, 10426, 10448, 10470, 10492, 10514, 10536, 10558, 10580, 10602, 10624, 10646, 10668, 10690, 10712, 10734, 10756, 10778, 10800, 10822, 10844, 10866, 10888, 10910, 10932, 10954, 10976, 10998, 11020, 11042, 11064, 11086, 11108, 11130, 11152, 11174, 11196, 11218, 11240, 11262, 11284, 11306, 11328, 11350, 11372, 11394, 11416, 11438, 11460, 11482, 11504, 11526, 11548, 11570, 11592, 11614, 11636, 11658, 11680, 11702, 11724, 11746, 11768, 11790, 11812, 11834, 11856, 11878, 11900, 11922, 11944, 11966, 11988, 12010, 12032, 12054, 12076, 12098, 12120, 12142, 12164, 12186, 12208, 12230, 12252, 12274, 12296, 12318, 12340, 12362, 12384, 12406, 12428, 12450, 12472, 12494, 12516, 12538, 12560, 12582, 12604, 12626, 12648, 12670, 12692, 12714, 12736, 12758, 12780, 12802, 12824, 12848, 12872, 12896, 12920, 12944, 12968, 12992, 13016, 13040, 13064, 13088, 13112, 13136, 13160, 13184, 13208, 13232, 13256, 13280, 13304, 13328, 13352, 13376, 13400, 13424, 13448, 13472, 13496, 13520, 13544, 13568, 13592, 13616, 13640, 13664, 13688, 13712, 13736, 13760, 13784, 13808, 13832, 13856, 13880, 13904, 13928, 13952, 13976, 14000, 14024, 14048, 14072, 14096, 14120, 14144, 14168, 14192, 14216, 14240, 14264, 14288, 14312, 14336, 14360, 14384, 14408, 14432, 14456, 14480, 14504, 14528, 14552, 14576, 14600, 14624, 14648, 14672, 14696, 14720, 14744, 14768, 14792, 14816, 14840, 14864, 14888, 14912, 14936, 14960, 14984, 15008, 15032, 15056, 15080, 15104, 15128, 15152, 15176, 15200, 15224, 15248, 15272, 15296, 15320, 15344, 15368, 15392, 15416, 15440, 15464, 15488, 15512, 15536, 15560, 15584, 15608, 15632, 15656, 15680, 15704, 15728, 15752, 15776, 15800, 15824, 15848, 15872, 15896, 15920, 15944, 15968, 15992, 16016, 16040, 16064, 16088, 16112, 16136, 16160, 16184, 16208, 16232, 16256, 16280, 16304, 16328, 16352, 16376, 16402, 16428, 16454, 16480, 16506, 16532, 16558, 16584, 16610, 16636, 16662, 16688, 16714, 16740, 16766, 16792, 16818, 16844, 16870, 16896, 16922, 16948, 16974, 17000, 17026, 17052, 17078, 17104, 17130, 17156, 17182, 17208, 17234, 17260, 17286, 17312, 17338, 17364, 17390, 17416, 17442, 17468, 17494, 17520, 17546, 17572, 17598, 17624, 17650, 17676, 17702, 17728, 17754, 17780, 17806, 17832, 17858, 17884, 17910, 17936, 17962, 17988, 18014, 18040, 18066, 18092, 18118, 18144, 18170, 18196, 18222, 18248, 18274, 18300, 18326, 18352, 18378, 18404, 18430, 18456, 18482, 18508, 18534, 18560, 18586, 18612, 18638, 18664, 18690, 18716, 18742, 18768, 18794, 18820, 18846, 18872, 18898, 18924, 18950, 18976, 19002, 19028, 19054, 19080, 19106, 19132, 19158, 19184, 19210, 19236, 19262, 19288, 19314, 19340, 19366, 19392, 19418, 19444, 19470, 19496, 19522, 19548, 19574, 19600, 19626, 19652, 19678, 19704, 19730, 19756, 19782, 19810, 19838, 19866, 19894, 19922, 19950, 19978, 20006, 20034, 20062, 20090, 20118, 20146, 20174, 20202, 20230, 20258, 20286, 20314, 20342, 20370, 20398, 20426, 20454, 20482, 20510, 20538, 20566, 20594, 20622, 20650, 20678, 20706, 20734, 20762, 20790, 20818, 20846, 20874, 20902, 20930, 20958, 20986, 21014, 21042, 21070, 21098, 21126, 21154, 21182, 21210, 21238, 21266, 21294, 21322, 21350, 21378, 21406, 21434, 21462, 21490, 21518, 21546, 21574, 21602, 21630, 21658, 21686, 21714, 21742, 21770, 21798, 21826, 21854, 21882, 21910, 21938, 21966, 21994, 22022, 22050, 22078, 22106, 22134, 22162, 22190, 22218, 22246, 22274, 22302, 22330, 22358, 22386, 22414, 22442, 22470, 22498, 22528, 22558, 22588, 22618, 22648, 22678, 22708, 22738, 22768, 22798, 22828, 22858, 22888, 22918, 22948, 22978, 23008, 23038, 23068, 23098, 23128, 23158, 23188, 23218, 23248, 23278, 23308, 23338, 23368, 23398, 23428, 23458, 23488, 23518, 23548, 23578, 23608, 23638, 23668, 23698, 23728, 23758, 23788, 23818, 23848, 23878, 23908, 23938, 23968, 23998, 24028, 24058, 24088, 24118, 24148, 24178, 24208, 24238, 24268, 24298, 24328, 24358, 24388, 24418, 24448, 24480, 24512, 24544, 24576, 24608, 24640, 24672, 24704, 24736, 24768, 24800, 24832, 24864, 24896, 24928, 24960, 24992, 25024, 25056, 25088, 25120, 25152, 25184, 25216, 25248, 25280, 25312, 25344, 25376, 25408, 25440, 25472, 25504, 25536, 25568, 25600, 25632, 25664, 25696, 25728, 25760, 25794, 25828, 25862, 25896, 25930, 25964, 25998, 26032, 26066, 26100, 26134, 26168, 26202, 26236, 26270, 26304, 26338, 26372, 26406, 26440, 26474, 26510, 26546, 26582, 26618, 26654, 26690, 26726, 26762, 26798, 26834, 26872, 26910, 26948, 26986, 27024, 27064, 27104, 27146}; + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_CONSTRUCTORS_ATLAS_EDGES_H */ diff --git a/src/constructors/atlas.c b/src/constructors/atlas.c new file mode 100644 index 0000000..fac3875 --- /dev/null +++ b/src/constructors/atlas.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2006-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" + +#include "constructors/atlas-edges.h" + +/** + * \function igraph_atlas + * \brief Create a small graph from the \quote Graph Atlas \endquote. + * + * The graph atlas contains all simple undirected unlabeled graphs on between + * 0 and 7 vertices. The number of the graph is given as a parameter. + * The graphs are listed: + * \olist + * \oli in increasing order of number of vertices; + * \oli for a fixed number of vertices, in increasing order of the + * number of edges; + * \oli for fixed numbers of vertices and edges, in lexicographically + * increasing order of the degree sequence, for example + * 111223 < 112222; + * \oli for fixed degree sequence, in increasing number of + * automorphisms. + * \endolist + * + * + * The data was converted from the NetworkX software package, + * see https://networkx.org/. + * + * + * See \emb An Atlas of Graphs \eme by Ronald C. Read and Robin J. Wilson, + * Oxford University Press, 1998. + * + * \param graph Pointer to an uninitialized graph object. + * \param number The number of the graph to generate. Must be between 0 and + * 1252 (inclusive). Graphs on 0-7 vertices start at numbers 0, 1, 2, 4, + * 8, 19, 53, and 209, respectively. + * \return Error code. + * + * Added in version 0.2. + * + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of + * edges. + * + * \example examples/simple/igraph_atlas.c + */ +igraph_error_t igraph_atlas(igraph_t *graph, igraph_int_t number) { + + const igraph_int_t atlas_size = + sizeof(igraph_i_atlas_edges_pos) / sizeof(igraph_i_atlas_edges_pos[0]); + + if (number < 0 || + number >= atlas_size) { + IGRAPH_ERRORF("No such graph in atlas. " + "The graph index must be less than %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + atlas_size); + } + + igraph_int_t pos = igraph_i_atlas_edges_pos[number]; + igraph_int_t n = igraph_i_atlas_edges[pos]; + igraph_int_t e = igraph_i_atlas_edges[pos + 1]; + const igraph_vector_int_t edges = igraph_vector_int_view(igraph_i_atlas_edges + pos + 2, e * 2); + + IGRAPH_CHECK(igraph_create(graph, + &edges, + n, + IGRAPH_UNDIRECTED)); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/basic_constructors.c b/src/constructors/basic_constructors.c new file mode 100644 index 0000000..c048e1d --- /dev/null +++ b/src/constructors/basic_constructors.c @@ -0,0 +1,143 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + + +/** + * \ingroup generators + * \function igraph_create + * \brief Creates a graph with the specified edges. + * + * \param graph An uninitialized graph object. + * \param edges The edges to add, the first two elements are the first + * edge, etc. + * \param n The number of vertices in the graph, if smaller or equal + * to the highest vertex ID in the \p edges vector it + * will be increased automatically. So it is safe to give 0 + * here. + * \param directed Boolean, whether to create a directed graph or + * not. If yes, then the first edge points from the first + * vertex ID in \p edges to the second, etc. + * \return Error code: + * \c IGRAPH_EINVAL: invalid edges vector (odd number of vertices). + * \c IGRAPH_EINVVID: invalid (negative) vertex ID. + * + * Time complexity: O(|V|+|E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \example examples/simple/igraph_create.c + */ +igraph_error_t igraph_create(igraph_t *graph, const igraph_vector_int_t *edges, + igraph_int_t n, igraph_bool_t directed) { + igraph_bool_t has_edges = igraph_vector_int_size(edges) > 0; + igraph_int_t max; + + if (igraph_vector_int_size(edges) % 2 != 0) { + IGRAPH_ERROR("Invalid (odd) edges vector.", IGRAPH_EINVAL); + } + if (!igraph_vector_int_isininterval(edges, 0, IGRAPH_VCOUNT_MAX-1)) { + IGRAPH_ERROR("Invalid (negative or too large) vertex ID.", IGRAPH_EINVVID); + } + + /* The + 1 here cannot overflow as above we have already + * checked that vertex IDs are within range. */ + max = has_edges ? igraph_vector_int_max(edges) + 1 : 0; + + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + if (has_edges) { + n = igraph_vcount(graph); + if (n < max) { + IGRAPH_CHECK(igraph_add_vertices(graph, (max - n), 0)); + } + IGRAPH_CHECK(igraph_add_edges(graph, edges, 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_small + * \brief Shorthand to create a small graph, giving the edges as arguments. + * + * This function is handy when a relatively small graph needs to be created. + * Instead of giving the edges as a vector, they are given simply as + * arguments and a -1 needs to be given after the last meaningful + * edge argument. + * + * + * This function is intended to be used with vertex IDs that are entered as + * literal integers. If you use a variable instead of a literal, make sure + * that it is of type int, as this is the type that this function + * assumes for all variadic arguments. Using a different integer type is + * undefined behaviour and likely to cause platform-specific issues. + * + * \param graph Pointer to an uninitialized graph object. The result + * will be stored here. + * \param n The number of vertices in the graph; a non-negative integer. + * \param directed Boolean constant; gives whether the graph should be + * directed. Supported values are: + * \clist + * \cli IGRAPH_DIRECTED + * The graph to be created will be \em directed. + * \cli IGRAPH_UNDIRECTED + * The graph to be created will be \em undirected. + * \endclist + * \param ... The additional arguments giving the edges of the graph, + * and \em must be of type int. Don't forget to supply an + * additional -1 after the last (meaningful) argument. The + * \p first parameter is present for technical reasons and represents + * the first variadic argument. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph to create. + * + * \example examples/simple/igraph_small.c + */ + +igraph_error_t igraph_small(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + int first, ...) { + igraph_vector_int_t edges; + va_list ap; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + va_start(ap, first); + int num = first; + while (num != -1) { + igraph_vector_int_push_back(&edges, num); + num = va_arg(ap, int); + } + va_end(ap); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/circulant.c b/src/constructors/circulant.c new file mode 100644 index 0000000..232f966 --- /dev/null +++ b/src/constructors/circulant.c @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_circulant + * \brief Creates a circulant graph. + * + * A circulant graph G(n, shifts) consists of \p n vertices v_0, ..., + * v_(n-1) such that for each \c s_i in the list of offsets \p shifts, \c v_j is + * connected to v_((j + s_i) mod n) for all j. + * + * + * The function can generate either directed or undirected graphs. It does not generate + * multi-edges or self-loops. + * + * \param graph Pointer to an uninitialized graph object, the result will + * be stored here. + * \param n Integer, the number of vertices in the circulant graph. + * \param shifts Integer vector, a list of the offsets within the circulant graph. + * \param directed Boolean, whether to create a directed graph. + * \return Error code. + * + * \sa \ref igraph_ring(), \ref igraph_generalized_petersen(), + * \ref igraph_extended_chordal_ring(), \ref igraph_lcf() + * + * Time complexity: O(|V| |shifts|), the number of vertices in the graph times the number + * of shifts. + */ + +igraph_error_t igraph_circulant(igraph_t *graph, igraph_int_t n, const igraph_vector_int_t *shifts, igraph_bool_t directed) { + + igraph_vector_int_t edges; + igraph_vector_bool_t shift_seen; + igraph_int_t i, j; + igraph_int_t limit; + igraph_int_t shift_size = igraph_vector_int_size(shifts); + + if (n < 0) { + IGRAPH_ERRORF("Number of nodes = %" IGRAPH_PRId " must be non-negative.", IGRAPH_EINVAL, n); + } + if (n == 0) { + return igraph_empty(graph, 0, directed); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + { + igraph_int_t size; + IGRAPH_SAFE_MULT(n, shift_size, &size); + IGRAPH_SAFE_MULT(size, 2, &size); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, size)); + } + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&shift_seen, n); + VECTOR(shift_seen)[0] = true; /* do not allow self loops */ + + for (i = 0; i < shift_size; i++) { + /* simplify the shift */ + igraph_int_t shift = VECTOR(*shifts)[i] % n; + if (shift < 0) { + shift += n; + } + if (!directed) { + if (shift >= (n + 1) / 2) { + shift = n - shift; + } + } + + /* only use shift if non-zero and we haven't seen it before */ + if (!VECTOR(shift_seen)[shift]) { + if (n % 2 == 0 && shift == n / 2 && !directed) { + limit = n / 2; /* this to avoid doubling up the n/2 shift for even n and undirected graph */ + } else { + limit = n; + } + for (j = 0; j < limit; j++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, (j + shift) % n)); + } + + VECTOR(shift_seen)[shift] = true; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + igraph_vector_bool_destroy(&shift_seen); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/de_bruijn.c b/src/constructors/de_bruijn.c new file mode 100644 index 0000000..0eb5afb --- /dev/null +++ b/src/constructors/de_bruijn.c @@ -0,0 +1,113 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/** + * \function igraph_de_bruijn + * \brief Generate a de Bruijn graph. + * + * A de Bruijn graph represents relationships between strings. An alphabet + * of \c m letters are used and strings of length \c n are considered. + * A vertex corresponds to every possible string and there is a directed edge + * from vertex \c v to vertex \c w if the string of \c v can be transformed into + * the string of \c w by removing its first letter and appending a letter to it. + * + * + * Please note that the graph will have \c m to the power \c n vertices and + * even more edges, so probably you don't want to supply too big numbers for + * \c m and \c n. + * + * + * De Bruijn graphs have some interesting properties, please see another source, + * e.g. Wikipedia for details. + * + * \param graph Pointer to an uninitialized graph object, the result will be + * stored here. + * \param m Integer, the number of letters in the alphabet. + * \param n Integer, the length of the strings. + * \return Error code. + * + * \sa \ref igraph_kautz(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges. + */ +igraph_error_t igraph_de_bruijn(igraph_t *graph, igraph_int_t m, igraph_int_t n) { + + /* m - number of symbols */ + /* n - length of strings */ + + igraph_int_t no_of_nodes, no_of_edges; + igraph_vector_int_t edges; + igraph_int_t i, j; + int iter = 0; + + if (m < 0 || n < 0) { + IGRAPH_ERROR("`m' and `n' should be non-negative in a de Bruijn graph", + IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_empty(graph, 1, IGRAPH_DIRECTED); + } + if (m == 0) { + return igraph_empty(graph, 0, IGRAPH_DIRECTED); + } + + { + igraph_real_t no_of_nodes_real = pow(m, n); + no_of_nodes = no_of_nodes_real; + if (no_of_nodes != no_of_nodes_real) { + IGRAPH_ERRORF("Parameters (%" IGRAPH_PRId ", %" IGRAPH_PRId ") too large for De Bruijn graph.", IGRAPH_EINVAL, + m, n); + } + } + /* no_of_edges = m * no_of_nodes */ + IGRAPH_SAFE_MULT(no_of_nodes, m, &no_of_edges); + + { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + } + + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t basis = (i * m) % no_of_nodes; + for (j = 0; j < m; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, basis + j); /* reserved */ + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/famous.c b/src/constructors/famous.c new file mode 100644 index 0000000..7e963a4 --- /dev/null +++ b/src/constructors/famous.c @@ -0,0 +1,495 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "internal/hacks.h" /* strcasecmp */ + +const igraph_int_t igraph_i_famous_bull[] = { + 5, 5, 0, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4 +}; + +const igraph_int_t igraph_i_famous_chvatal[] = { + 12, 24, 0, + 5, 6, 6, 7, 7, 8, 8, 9, 5, 9, 4, 5, 4, 8, 2, 8, 2, 6, 0, 6, 0, 9, 3, 9, 3, 7, + 1, 7, 1, 5, 1, 10, 4, 10, 4, 11, 2, 11, 0, 10, 0, 11, 3, 11, 3, 10, 1, 2 +}; + +const igraph_int_t igraph_i_famous_coxeter[] = { + 28, 42, 0, + 0, 1, 0, 2, 0, 7, 1, 4, 1, 13, 2, 3, 2, 8, 3, 6, 3, 9, 4, 5, 4, 12, 5, 6, 5, + 11, 6, 10, 7, 19, 7, 24, 8, 20, 8, 23, 9, 14, 9, 22, 10, 15, 10, 21, 11, 16, + 11, 27, 12, 17, 12, 26, 13, 18, 13, 25, 14, 17, 14, 18, 15, 18, 15, 19, 16, 19, + 16, 20, 17, 20, 21, 23, 21, 26, 22, 24, 22, 27, 23, 25, 24, 26, 25, 27 +}; + +const igraph_int_t igraph_i_famous_cubical[] = { + 8, 12, 0, + 0, 1, 1, 2, 2, 3, 0, 3, 4, 5, 5, 6, 6, 7, 4, 7, 0, 4, 1, 5, 2, 6, 3, 7 +}; + +const igraph_int_t igraph_i_famous_diamond[] = { + 4, 5, 0, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 3 +}; + +const igraph_int_t igraph_i_famous_dodecahedron[] = { + 20, 30, 0, + 0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 10, 5, 11, 6, + 10, 6, 14, 7, 13, 7, 14, 8, 12, 8, 13, 9, 11, 9, 12, 10, 15, 11, 16, 12, 17, + 13, 18, 14, 19, 15, 16, 15, 19, 16, 17, 17, 18, 18, 19 +}; + +const igraph_int_t igraph_i_famous_folkman[] = { + 20, 40, 0, + 0, 5, 0, 8, 0, 10, 0, 13, 1, 7, 1, 9, 1, 12, 1, 14, 2, 6, 2, 8, 2, 11, 2, 13, + 3, 5, 3, 7, 3, 10, 3, 12, 4, 6, 4, 9, 4, 11, 4, 14, 5, 15, 5, 19, 6, 15, 6, 16, + 7, 16, 7, 17, 8, 17, 8, 18, 9, 18, 9, 19, 10, 15, 10, 19, 11, 15, 11, 16, 12, + 16, 12, 17, 13, 17, 13, 18, 14, 18, 14, 19 +}; + +const igraph_int_t igraph_i_famous_franklin[] = { + 12, 18, 0, + 0, 1, 0, 2, 0, 6, 1, 3, 1, 7, 2, 4, 2, 10, 3, 5, 3, 11, 4, 5, 4, 6, 5, 7, 6, 8, + 7, 9, 8, 9, 8, 11, 9, 10, 10, 11 +}; + +const igraph_int_t igraph_i_famous_frucht[] = { + 12, 18, 0, + 0, 1, 0, 2, 0, 11, 1, 3, 1, 6, 2, 5, 2, 10, 3, 4, 3, 6, 4, 8, 4, 11, 5, 9, 5, + 10, 6, 7, 7, 8, 7, 9, 8, 9, 10, 11 +}; + +const igraph_int_t igraph_i_famous_grotzsch[] = { + 11, 20, 0, + 0, 1, 0, 2, 0, 7, 0, 10, 1, 3, 1, 6, 1, 9, 2, 4, 2, 6, 2, 8, 3, 4, 3, 8, 3, 10, + 4, 7, 4, 9, 5, 6, 5, 7, 5, 8, 5, 9, 5, 10 +}; + +const igraph_int_t igraph_i_famous_heawood[] = { + 14, 21, 0, + 0, 1, 0, 5, 0, 13, 1, 2, 1, 10, 2, 3, 2, 7, 3, 4, 3, 12, 4, 5, 4, 9, 5, 6, 6, + 7, 6, 11, 7, 8, 8, 9, 8, 13, 9, 10, 10, 11, 11, 12, 12, 13 +}; + +const igraph_int_t igraph_i_famous_herschel[] = { + 11, 18, 0, + 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 6, 1, 7, 2, 10, 3, 9, 4, 8, 4, 9, 5, 8, + 5, 10, 6, 8, 6, 9, 7, 8, 7, 10 +}; + +const igraph_int_t igraph_i_famous_house[] = { + 5, 6, 0, + 0, 1, 0, 2, 1, 3, 2, 3, 2, 4, 3, 4 +}; + +const igraph_int_t igraph_i_famous_housex[] = { + 5, 8, 0, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4 +}; + +const igraph_int_t igraph_i_famous_icosahedron[] = { + 12, 30, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 8, 1, 2, 1, 6, 1, 7, 1, 8, 2, 4, 2, 5, 2, 6, 3, 4, + 3, 8, 3, 9, 3, 11, 4, 5, 4, 11, 5, 6, 5, 10, 5, 11, 6, 7, 6, 10, 7, 8, 7, 9, 7, + 10, 8, 9, 9, 10, 9, 11, 10, 11 +}; + +const igraph_int_t igraph_i_famous_krackhardt_kite[] = { + 10, 18, 0, + 0, 1, 0, 2, 0, 3, 0, 5, 1, 3, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, 5, 7, 6, 7, 7, 8, 8, 9 +}; + +const igraph_int_t igraph_i_famous_levi[] = { + 30, 45, 0, + 0, 1, 0, 7, 0, 29, 1, 2, 1, 24, 2, 3, 2, 11, 3, 4, 3, 16, 4, 5, 4, 21, 5, 6, 5, + 26, 6, 7, 6, 13, 7, 8, 8, 9, 8, 17, 9, 10, 9, 22, 10, 11, 10, 27, 11, 12, 12, + 13, 12, 19, 13, 14, 14, 15, 14, 23, 15, 16, 15, 28, 16, 17, 17, 18, 18, 19, 18, + 25, 19, 20, 20, 21, 20, 29, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29 +}; + +const igraph_int_t igraph_i_famous_mcgee[] = { + 24, 36, 0, + 0, 1, 0, 7, 0, 23, 1, 2, 1, 18, 2, 3, 2, 14, 3, 4, 3, 10, 4, 5, 4, 21, 5, 6, 5, + 17, 6, 7, 6, 13, 7, 8, 8, 9, 8, 20, 9, 10, 9, 16, 10, 11, 11, 12, 11, 23, 12, + 13, 12, 19, 13, 14, 14, 15, 15, 16, 15, 22, 16, 17, 17, 18, 18, 19, 19, 20, 20, + 21, 21, 22, 22, 23 +}; + +const igraph_int_t igraph_i_famous_meredith[] = { + 70, 140, 0, + 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 7, 11, + 7, 12, 7, 13, 8, 11, 8, 12, 8, 13, 9, 11, 9, 12, 9, 13, 10, 11, 10, 12, 10, 13, + 14, 18, 14, 19, 14, 20, 15, 18, 15, 19, 15, 20, 16, 18, 16, 19, 16, 20, 17, 18, + 17, 19, 17, 20, 21, 25, 21, 26, 21, 27, 22, 25, 22, 26, 22, 27, 23, 25, 23, 26, + 23, 27, 24, 25, 24, 26, 24, 27, 28, 32, 28, 33, 28, 34, 29, 32, 29, 33, 29, 34, + 30, 32, 30, 33, 30, 34, 31, 32, 31, 33, 31, 34, 35, 39, 35, 40, 35, 41, 36, 39, + 36, 40, 36, 41, 37, 39, 37, 40, 37, 41, 38, 39, 38, 40, 38, 41, 42, 46, 42, 47, + 42, 48, 43, 46, 43, 47, 43, 48, 44, 46, 44, 47, 44, 48, 45, 46, 45, 47, 45, 48, + 49, 53, 49, 54, 49, 55, 50, 53, 50, 54, 50, 55, 51, 53, 51, 54, 51, 55, 52, 53, + 52, 54, 52, 55, 56, 60, 56, 61, 56, 62, 57, 60, 57, 61, 57, 62, 58, 60, 58, 61, + 58, 62, 59, 60, 59, 61, 59, 62, 63, 67, 63, 68, 63, 69, 64, 67, 64, 68, 64, 69, + 65, 67, 65, 68, 65, 69, 66, 67, 66, 68, 66, 69, 2, 50, 1, 51, 9, 57, 8, 58, 16, + 64, 15, 65, 23, 36, 22, 37, 30, 43, 29, 44, 3, 21, 7, 24, 14, 31, 0, 17, 10, + 28, 38, 42, 35, 66, 59, 63, 52, 56, 45, 49 +}; + +const igraph_int_t igraph_i_famous_noperfectmatching[] = { + 16, 27, 0, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4, 4, 5, 5, 6, 5, 7, 6, 12, 6, 13, + 7, 8, 7, 9, 8, 9, 8, 10, 8, 11, 9, 10, 9, 11, 10, 11, 12, 13, 12, 14, 12, 15, + 13, 14, 13, 15, 14, 15 +}; + +const igraph_int_t igraph_i_famous_nonline[] = { + 50, 72, 0, + 0, 1, 0, 2, 0, 3, 4, 6, 4, 7, 5, 6, 5, 7, 6, 7, 7, 8, 9, 11, 9, 12, 9, 13, 10, + 11, 10, 12, 10, 13, 11, 12, 11, 13, 12, 13, 14, 15, 15, 16, 15, 17, 16, 17, 16, + 18, 17, 18, 18, 19, 20, 21, 20, 22, 20, 23, 21, 22, 21, 23, 21, 24, 22, 23, 22, + 24, 24, 25, 26, 27, 26, 28, 26, 29, 27, 28, 27, 29, 27, 30, 27, 31, 28, 29, 28, + 30, 28, 31, 30, 31, 32, 34, 32, 35, 32, 36, 33, 34, 33, 35, 33, 37, 34, 35, 36, + 37, 38, 39, 38, 40, 38, 43, 39, 40, 39, 41, 39, 42, 39, 43, 40, 41, 41, 42, 42, + 43, 44, 45, 44, 46, 45, 46, 45, 47, 46, 47, 46, 48, 47, 48, 47, 49, 48, 49 +}; + +const igraph_int_t igraph_i_famous_octahedron[] = { + 6, 12, 0, + 0, 1, 0, 2, 1, 2, 3, 4, 3, 5, 4, 5, 0, 3, 0, 5, 1, 3, 1, 4, 2, 4, 2, 5 +}; + +const igraph_int_t igraph_i_famous_petersen[] = { + 10, 15, 0, + 0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9 +}; + +const igraph_int_t igraph_i_famous_robertson[] = { + 19, 38, 0, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, + 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 0, 18, 0, 4, 4, 9, 9, 13, 13, + 17, 2, 17, 2, 6, 6, 10, 10, 15, 0, 15, 1, 8, 8, 16, 5, 16, 5, 12, 1, 12, 7, 18, + 7, 14, 3, 14, 3, 11, 11, 18 +}; + +const igraph_int_t igraph_i_famous_smallestcyclicgroup[] = { + 9, 15, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 7, 1, 8, 2, 5, 2, 6, 2, 7, 3, 8, + 4, 5, 6, 7 +}; + +const igraph_int_t igraph_i_famous_tetrahedron[] = { + 4, 6, 0, + 0, 3, 1, 3, 2, 3, 0, 1, 1, 2, 0, 2 +}; + +const igraph_int_t igraph_i_famous_thomassen[] = { + 34, 52, 0, + 0, 2, 0, 3, 1, 3, 1, 4, 2, 4, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9, 10, 12, 10, 13, 11, + 13, 11, 14, 12, 14, 15, 17, 15, 18, 16, 18, 16, 19, 17, 19, 9, 19, 4, 14, 24, + 25, 25, 26, 20, 26, 20, 21, 21, 22, 22, 23, 23, 27, 27, 28, 28, 29, 29, 30, 30, + 31, 31, 32, 32, 33, 24, 33, 5, 24, 6, 25, 7, 26, 8, 20, 0, 20, 1, 21, 2, 22, 3, + 23, 10, 27, 11, 28, 12, 29, 13, 30, 15, 30, 16, 31, 17, 32, 18, 33 +}; + +const igraph_int_t igraph_i_famous_tutte[] = { + 46, 69, 0, + 0, 10, 0, 11, 0, 12, 1, 2, 1, 7, 1, 19, 2, 3, 2, 41, 3, 4, 3, 27, 4, 5, 4, 33, + 5, 6, 5, 45, 6, 9, 6, 29, 7, 8, 7, 21, 8, 9, 8, 22, 9, 24, 10, 13, 10, 14, 11, + 26, 11, 28, 12, 30, 12, 31, 13, 15, 13, 21, 14, 15, 14, 18, 15, 16, 16, 17, 16, + 20, 17, 18, 17, 23, 18, 24, 19, 25, 19, 40, 20, 21, 20, 22, 22, 23, 23, 24, 25, + 26, 25, 38, 26, 34, 27, 28, 27, 39, 28, 34, 29, 30, 29, 44, 30, 35, 31, 32, 31, + 35, 32, 33, 32, 42, 33, 43, 34, 36, 35, 37, 36, 38, 36, 39, 37, 42, 37, 44, 38, + 40, 39, 41, 40, 41, 42, 43, 43, 45, 44, 45 +}; + +const igraph_int_t igraph_i_famous_uniquely3colorable[] = { + 12, 22, 0, + 0, 1, 0, 3, 0, 6, 0, 8, 1, 4, 1, 7, 1, 9, 2, 3, 2, 6, 2, 7, 2, 9, 2, 11, 3, 4, + 3, 10, 4, 5, 4, 11, 5, 6, 5, 7, 5, 8, 5, 10, 8, 11, 9, 10 +}; + +const igraph_int_t igraph_i_famous_walther[] = { + 25, 31, 0, + 0, 1, 1, 2, 1, 8, 2, 3, 2, 13, 3, 4, 3, 16, 4, 5, 5, 6, 5, 19, 6, 7, 6, 20, 7, + 21, 8, 9, 8, 13, 9, 10, 9, 22, 10, 11, 10, 20, 11, 12, 13, 14, 14, 15, 14, 23, + 15, 16, 15, 17, 17, 18, 18, 19, 18, 24, 20, 24, 22, 23, 23, 24 +}; + +const igraph_int_t igraph_i_famous_zachary[] = { + 34, 78, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 10, 0, 11, 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, 0, 31, + 1, 2, 1, 3, 1, 7, 1, 13, 1, 17, 1, 19, 1, 21, 1, 30, + 2, 3, 2, 7, 2, 27, 2, 28, 2, 32, 2, 9, 2, 8, 2, 13, + 3, 7, 3, 12, 3, 13, 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, 13, 33, 14, 32, 14, 33, + 15, 32, 15, 33, 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 32, 23, 33, 23, 29, + 24, 25, 24, 27, 24, 31, 25, 31, 26, 29, 26, 33, 27, 33, + 28, 31, 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, 31, 32, 31, 33, + 32, 33 +}; + +static igraph_error_t igraph_i_famous(igraph_t *graph, const igraph_int_t *data) { + igraph_int_t no_of_nodes = data[0]; + igraph_int_t no_of_edges = data[1]; + igraph_bool_t directed = (igraph_bool_t) data[2]; + const igraph_vector_int_t edges = igraph_vector_int_view(data + 3, 2 * no_of_edges); + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_famous + * \brief Create a famous graph by simply providing its name. + * + * + * The name of the graph can be simply supplied as a string. + * Note that this function creates graphs which don't take any parameters, + * there are separate functions for graphs with parameters, e.g. \ref + * igraph_full() for creating a full graph. + * + * + * The following graphs are supported: + * \clist + * \cli Bull + * The bull graph, 5 vertices, 5 edges, resembles the + * head of a bull if drawn properly. + * \cli Chvatal + * This is the smallest triangle-free graph that is + * both 4-chromatic and 4-regular. According to the Grunbaum + * conjecture there exists an m-regular, m-chromatic graph + * with n vertices for every m>1 and n>2. The Chvatal graph + * is an example for m=4 and n=12. It has 24 edges. + * \cli Coxeter + * A non-Hamiltonian cubic symmetric graph with 28 + * vertices and 42 edges. + * \cli Cubical + * The Platonic graph of the cube. A convex regular + * polyhedron with 8 vertices and 12 edges. + * \cli Diamond + * A graph with 4 vertices and 5 edges, resembles a + * schematic diamond if drawn properly. + * \cli Dodecahedral, Dodecahedron + * Another Platonic solid + * with 20 vertices and 30 edges. + * \cli Folkman + * The semisymmetric graph with minimum number of + * vertices, 20 and 40 edges. A semisymmetric graph is + * regular, edge transitive and not vertex transitive. + * \cli Franklin + * This is a graph whose embedding to the Klein + * bottle can be colored with six colors, it is a + * counterexample to the necessity of the Heawood + * conjecture on a Klein bottle. It has 12 vertices and 18 + * edges. + * \cli Frucht + * The Frucht Graph is the smallest cubical graph + * whose automorphism group consists only of the identity + * element. It has 12 vertices and 18 edges. + * \cli Grotzsch, Groetzsch + * The Grötzsch graph is a triangle-free graph with + * 11 vertices, 20 edges, and chromatic number 4. It is named after + * German mathematician Herbert Grötzsch, and its existence + * demonstrates that the assumption of planarity is necessary in + * Grötzsch's theorem that every triangle-free planar + * graph is 3-colorable. + * \cli Heawood + * The Heawood graph is an undirected graph with 14 + * vertices and 21 edges. The graph is cubic, and all cycles in the + * graph have six or more edges. Every smaller cubic graph has shorter + * cycles, so this graph is the 6-cage, the smallest cubic graph of + * girth 6. + * \cli Herschel + * The Herschel graph is the smallest + * nonhamiltonian polyhedral graph. It is the + * unique such graph on 11 nodes, and has 18 edges. + * \cli House + * The house graph is a 5-vertex, 6-edge graph, the + * schematic draw of a house if drawn properly, basically a + * triangle on top of a square. + * \cli HouseX + * The same as the house graph with an X in the square. 5 + * vertices and 8 edges. + * \cli Icosahedral, Icosahedron + * A Platonic solid with 12 + * vertices and 30 edges. + * \cli Krackhardt_Kite + * A social network with 10 vertices and 18 edges. + * Krackhardt, D. Assessing the Political Landscape: + * Structure, Cognition, and Power in Organizations. + * Admin. Sci. Quart. 35, 342-369, 1990. + * \cli Levi + * The graph is a 4-arc transitive cubic graph, it has + * 30 vertices and 45 edges. + * \cli McGee + * The McGee graph is the unique 3-regular 7-cage + * graph, it has 24 vertices and 36 edges. + * \cli Meredith + * The Meredith graph is a quartic graph on 70 + * nodes and 140 edges that is a counterexample to the conjecture that + * every 4-regular 4-connected graph is Hamiltonian. + * \cli Noperfectmatching + * A connected graph with 16 vertices and + * 27 edges containing no perfect matching. A matching in a graph + * is a set of pairwise non-incident edges; that is, no two edges + * share a common vertex. A perfect matching is a matching + * which covers all vertices of the graph. + * \cli Nonline + * A graph whose connected components are the 9 + * graphs whose presence as a vertex-induced subgraph in a + * graph makes a nonline graph. It has 50 vertices and 72 edges. + * \cli Octahedral, Octahedron + * Platonic solid with 6 + * vertices and 12 edges. + * \cli Petersen + * A 3-regular graph with 10 vertices and 15 edges. It is + * the smallest hypohamiltonian graph, i.e. it is + * non-hamiltonian but removing any single vertex from it makes it + * Hamiltonian. + * \cli Robertson + * The unique (4,5)-cage graph, i.e. a 4-regular + * graph of girth 5. It has 19 vertices and 38 edges. + * \cli Smallestcyclicgroup + * A smallest nontrivial graph + * whose automorphism group is cyclic. It has 9 vertices and + * 15 edges. + * \cli Tetrahedral, Tetrahedron + * Platonic solid with 4 + * vertices and 6 edges. + * \cli Thomassen + * The smallest hypotraceable graph, + * on 34 vertices and 52 edges. A hypotracable graph does + * not contain a Hamiltonian path but after removing any + * single vertex from it the remainder always contains a + * Hamiltonian path. A graph containing a Hamiltonian path + * is called traceable. + * \cli Tutte + * Tait's Hamiltonian graph conjecture states that + * every 3-connected 3-regular planar graph is Hamiltonian. + * This graph is a counterexample. It has 46 vertices and 69 + * edges. + * \cli Uniquely3colorable + * Returns a 12-vertex, triangle-free + * graph with chromatic number 3 that is uniquely + * 3-colorable. + * \cli Walther + * An identity graph with 25 vertices and 31 + * edges. An identity graph has a single graph automorphism, + * the trivial one. + * \cli Zachary + * Social network of friendships between 34 members of a + * karate club at a US university in the 1970s. See + * W. W. Zachary, An information flow model for conflict and + * fission in small groups, Journal of Anthropological + * Research 33, 452-473 (1977). + * \endclist + * + * \param graph Pointer to an uninitialized graph object. + * \param name Character constant, the name of the graph to be + * created, it is case insensitive. + * \return Error code, \c IGRAPH_EINVAL if there is no graph with the + * given name. + * + * \sa Other functions for creating graph structures: + * \ref igraph_ring(), \ref igraph_kary_tree(), \ref igraph_square_lattice(), + * \ref igraph_full(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph. + */ + +igraph_error_t igraph_famous(igraph_t *graph, const char *name) { + + if (!strcasecmp(name, "bull")) { + return igraph_i_famous(graph, igraph_i_famous_bull); + } else if (!strcasecmp(name, "chvatal")) { + return igraph_i_famous(graph, igraph_i_famous_chvatal); + } else if (!strcasecmp(name, "coxeter")) { + return igraph_i_famous(graph, igraph_i_famous_coxeter); + } else if (!strcasecmp(name, "cubical")) { + return igraph_i_famous(graph, igraph_i_famous_cubical); + } else if (!strcasecmp(name, "diamond")) { + return igraph_i_famous(graph, igraph_i_famous_diamond); + } else if (!strcasecmp(name, "dodecahedral") || + !strcasecmp(name, "dodecahedron")) { + return igraph_i_famous(graph, igraph_i_famous_dodecahedron); + } else if (!strcasecmp(name, "folkman")) { + return igraph_i_famous(graph, igraph_i_famous_folkman); + } else if (!strcasecmp(name, "franklin")) { + return igraph_i_famous(graph, igraph_i_famous_franklin); + } else if (!strcasecmp(name, "frucht")) { + return igraph_i_famous(graph, igraph_i_famous_frucht); + } else if (!strcasecmp(name, "grotzsch") || + !strcasecmp(name, "groetzsch")) { + return igraph_i_famous(graph, igraph_i_famous_grotzsch); + } else if (!strcasecmp(name, "heawood")) { + return igraph_i_famous(graph, igraph_i_famous_heawood); + } else if (!strcasecmp(name, "herschel")) { + return igraph_i_famous(graph, igraph_i_famous_herschel); + } else if (!strcasecmp(name, "house")) { + return igraph_i_famous(graph, igraph_i_famous_house); + } else if (!strcasecmp(name, "housex")) { + return igraph_i_famous(graph, igraph_i_famous_housex); + } else if (!strcasecmp(name, "icosahedral") || + !strcasecmp(name, "icosahedron")) { + return igraph_i_famous(graph, igraph_i_famous_icosahedron); + } else if (!strcasecmp(name, "krackhardt_kite")) { + return igraph_i_famous(graph, igraph_i_famous_krackhardt_kite); + } else if (!strcasecmp(name, "levi")) { + return igraph_i_famous(graph, igraph_i_famous_levi); + } else if (!strcasecmp(name, "mcgee")) { + return igraph_i_famous(graph, igraph_i_famous_mcgee); + } else if (!strcasecmp(name, "meredith")) { + return igraph_i_famous(graph, igraph_i_famous_meredith); + } else if (!strcasecmp(name, "noperfectmatching")) { + return igraph_i_famous(graph, igraph_i_famous_noperfectmatching); + } else if (!strcasecmp(name, "nonline")) { + return igraph_i_famous(graph, igraph_i_famous_nonline); + } else if (!strcasecmp(name, "octahedral") || + !strcasecmp(name, "octahedron")) { + return igraph_i_famous(graph, igraph_i_famous_octahedron); + } else if (!strcasecmp(name, "petersen")) { + return igraph_i_famous(graph, igraph_i_famous_petersen); + } else if (!strcasecmp(name, "robertson")) { + return igraph_i_famous(graph, igraph_i_famous_robertson); + } else if (!strcasecmp(name, "smallestcyclicgroup")) { + return igraph_i_famous(graph, igraph_i_famous_smallestcyclicgroup); + } else if (!strcasecmp(name, "tetrahedral") || + !strcasecmp(name, "tetrahedron")) { + return igraph_i_famous(graph, igraph_i_famous_tetrahedron); + } else if (!strcasecmp(name, "thomassen")) { + return igraph_i_famous(graph, igraph_i_famous_thomassen); + } else if (!strcasecmp(name, "tutte")) { + return igraph_i_famous(graph, igraph_i_famous_tutte); + } else if (!strcasecmp(name, "uniquely3colorable")) { + return igraph_i_famous(graph, igraph_i_famous_uniquely3colorable); + } else if (!strcasecmp(name, "walther")) { + return igraph_i_famous(graph, igraph_i_famous_walther); + } else if (!strcasecmp(name, "zachary")) { + return igraph_i_famous(graph, igraph_i_famous_zachary); + } + + IGRAPH_ERRORF("%s is not a known graph. See the documentation for valid graph names.", IGRAPH_EINVAL, name); +} diff --git a/src/constructors/full.c b/src/constructors/full.c new file mode 100644 index 0000000..583e87f --- /dev/null +++ b/src/constructors/full.c @@ -0,0 +1,376 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/** + * \ingroup generators + * \function igraph_full + * \brief Creates a full graph (complete graph). + * + * In a full graph every possible edge is present: every vertex is + * connected to every other vertex. \a igraph generalizes the usual + * concept of complete graphs in graph theory to graphs with self-loops + * as well as to directed graphs. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param directed Whether to create a directed graph. + * \param loops Whether to include self-loops. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|^2) = O(|E|), + * where |V| is the number of vertices and |E| is the number of edges. + * + * \sa \ref igraph_square_lattice(), \ref igraph_star(), \ref igraph_kary_tree() + * for creating other regular structures. + * + * \example examples/simple/igraph_full.c + */ +igraph_error_t igraph_full(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_bool_t loops) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t no_of_edges2; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + if (directed && loops) { + /* ecount = n * n */ + IGRAPH_SAFE_MULT(n, n, &no_of_edges2); + IGRAPH_SAFE_MULT(no_of_edges2, 2, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + for (igraph_int_t i = 0; i < n; i++) { + for (igraph_int_t j = 0; j < n; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, j); /* reserved */ + } + IGRAPH_ALLOW_INTERRUPTION(); + } + } else if (directed && !loops) { + /* ecount = n * (n - 1) */ + IGRAPH_SAFE_MULT(n, n - 1, &no_of_edges2); + IGRAPH_SAFE_MULT(no_of_edges2, 2, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + for (igraph_int_t i = 0; i < n; i++) { + for (igraph_int_t j = 0; j < i; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, j); /* reserved */ + } + for (igraph_int_t j = i + 1; j < n; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, j); /* reserved */ + } + IGRAPH_ALLOW_INTERRUPTION(); + } + } else if (!directed && loops) { + /* ecount = n * (n + 1) / 2 */ + IGRAPH_SAFE_ADD(n, 1, &no_of_edges2); + IGRAPH_SAFE_MULT(n, no_of_edges2, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + for (igraph_int_t i = 0; i < n; i++) { + for (igraph_int_t j = i; j < n; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, j); /* reserved */ + } + IGRAPH_ALLOW_INTERRUPTION(); + } + } else { + /* ecount = n * (n - 1) / 2 */ + IGRAPH_SAFE_MULT(n, n - 1, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + for (igraph_int_t i = 0; i < n; i++) { + for (igraph_int_t j = i + 1; j < n; j++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, j); /* reserved */ + } + IGRAPH_ALLOW_INTERRUPTION(); + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_full_multipartite + * \brief Creates a full multipartite graph. + * + * A multipartite graph contains two or more types of vertices and connections + * are only possible between two vertices of different types. This function + * creates a complete multipartite graph. + * + * \param graph Pointer to an uninitialized graph object, the graph will be + * created here. + * \param types Pointer to an integer vector. If not a null pointer, + * the type of each vertex will be stored here. + * \param n Pointer to an integer vector, the number of vertices + * of each type. + * \param directed Boolean, whether to create a directed graph. + * \param mode A constant that gives the type of connections for + * directed graphs. If \c IGRAPH_OUT, then edges point from vertices + * of low-index vertices to high-index vertices; if + * \c IGRAPH_IN, then the opposite direction is realized; + * \c IGRAPH_ALL, then mutual edges will be created. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa \ref igraph_full_bipartite() for complete bipartite graphs, + * \ref igraph_turan() for Turán graphs. + */ +igraph_error_t igraph_full_multipartite(igraph_t *graph, + igraph_vector_int_t *types, + const igraph_vector_int_t *n, + igraph_bool_t directed, + igraph_neimode_t mode) { + + igraph_vector_int_t edges; + igraph_vector_int_t n_acc; + + igraph_int_t no_of_types = igraph_vector_int_size(n); + + if (no_of_types == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + if (types) { + igraph_vector_int_clear(types); + } + return IGRAPH_SUCCESS; + } + + if (igraph_vector_int_min(n) < 0) { + IGRAPH_ERROR("Number of vertices must not be negative in any partition.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&n_acc, no_of_types+1); + VECTOR(n_acc)[0] = 0; + for (igraph_int_t i = 1; i < no_of_types+1; i++) { + IGRAPH_SAFE_ADD(VECTOR(n_acc)[i-1], VECTOR(*n)[i-1], + &VECTOR(n_acc)[i]); + } + + igraph_int_t no_of_edges2 = 0; + + for (igraph_int_t i = 0; i < no_of_types; i++) { + igraph_int_t v = VECTOR(*n)[i]; + igraph_int_t partial_sum = VECTOR(n_acc)[no_of_types] - v; + IGRAPH_SAFE_MULT(partial_sum, v, &partial_sum); + IGRAPH_SAFE_ADD(no_of_edges2, partial_sum, &no_of_edges2); + } + + if (directed && mode == IGRAPH_ALL) { + IGRAPH_SAFE_MULT(no_of_edges2, 2, &no_of_edges2); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + + igraph_int_t ptr = 0; + + for (igraph_int_t from_type = 0; from_type < no_of_types-1; from_type++) { + igraph_int_t edge_from = VECTOR(n_acc)[from_type]; + for (igraph_int_t i = 0; i < VECTOR(*n)[from_type]; i++) { + for (igraph_int_t to_type = from_type+1; to_type < no_of_types; to_type++) { + igraph_int_t edge_to = VECTOR(n_acc)[to_type]; + for (igraph_int_t j = 0; j < VECTOR(*n)[to_type]; j++) { + if (!directed || mode == IGRAPH_OUT) { + VECTOR(edges)[ptr++] = edge_from; + VECTOR(edges)[ptr++] = edge_to; + } else if (mode == IGRAPH_IN) { + VECTOR(edges)[ptr++] = edge_to; + VECTOR(edges)[ptr++] = edge_from; + } else { + VECTOR(edges)[ptr++] = edge_from; + VECTOR(edges)[ptr++] = edge_to; + VECTOR(edges)[ptr++] = edge_to; + VECTOR(edges)[ptr++] = edge_from; + } + edge_to++; + } + } + edge_from++; + IGRAPH_ALLOW_INTERRUPTION(); + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, VECTOR(n_acc)[no_of_types], directed)); + + if (types) { + IGRAPH_CHECK(igraph_vector_int_resize(types, VECTOR(n_acc)[no_of_types])); + if (VECTOR(n_acc)[no_of_types] > 0) { + igraph_int_t v = 1; + for (igraph_int_t i = 0; i < VECTOR(n_acc)[no_of_types]; i++) { + if (i == VECTOR(n_acc)[v]) { + v++; + } + VECTOR(*types)[i] = v-1; + } + } + } + + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&n_acc); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_turan + * \brief Creates a Turán graph. + * + * Turán graphs are complete multipartite graphs with the property + * that the sizes of the partitions are as close to equal as possible. + * + * + * The Turán graph with \p n vertices and \p r partitions is the densest + * graph on \p n vertices that does not contain a clique of size + * r+1. + * + * + * This function generates undirected graphs. The null graph is + * returned when the number of vertices is zero. A complete graph is + * returned if the number of partitions is greater than the number of + * vertices. + * + * \param graph Pointer to an igraph_t object, the graph will be + * created here. + * \param types Pointer to an integer vector. If not a null pointer, + * the type (partition index) of each vertex will be stored here. + * \param n Integer, the number of vertices in the graph. + * \param r Integer, the number of partitions of the graph, must be + * positive. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa \ref igraph_full_multipartite() for full multipartite graphs. + */ +igraph_error_t igraph_turan(igraph_t *graph, + igraph_vector_int_t *types, + igraph_int_t n, + igraph_int_t r) { + igraph_int_t quotient; + igraph_int_t remainder; + igraph_vector_int_t subsets; + + if (n < 0) { + IGRAPH_ERRORF("Number of vertices must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, n); + } + + if (r <= 0) { + IGRAPH_ERRORF("Number of partitions must be positive, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, r); + } + + if (n == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, IGRAPH_UNDIRECTED)); + if (types) { + igraph_vector_int_clear(types); + } + return IGRAPH_SUCCESS; + } + + if (r > n) { + r = n; + } + + quotient = n / r; + remainder = n % r; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&subsets, r); + + igraph_vector_int_fill(&subsets, quotient); + for (igraph_int_t i = 0; i < remainder; i++) { + VECTOR(subsets)[i]++; + } + + IGRAPH_CHECK(igraph_full_multipartite(graph, types, &subsets, + IGRAPH_UNDIRECTED, IGRAPH_ALL)); + igraph_vector_int_destroy(&subsets); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_full_citation + * \brief Creates a full citation graph (a complete directed acyclic graph). + * + * This is a directed graph, where every i->j edge is + * present if and only if j<i. + * If the \p directed argument is false then an undirected graph is + * created, and it is just a complete graph. + * + * \param graph Pointer to an uninitialized graph object, the result + * is stored here. + * \param n The number of vertices. + * \param directed Whether to created a directed graph. If false an + * undirected graph is created. + * \return Error code. + * + * \sa \ref igraph_full() + * + * Time complexity: O(|V|^2) = O(|E|), + * where |V| is the number of vertices and |E| is the number of edges. + */ +igraph_error_t igraph_full_citation(igraph_t *graph, igraph_int_t n, + igraph_bool_t directed) { + igraph_vector_int_t edges; + igraph_int_t ptr = 0; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices.", IGRAPH_EINVAL); + } + + { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(n, n-1, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + for (igraph_int_t i = 1; i < n; i++) { + for (igraph_int_t j = 0; j < i; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = j; + } + IGRAPH_ALLOW_INTERRUPTION(); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/generalized_petersen.c b/src/constructors/generalized_petersen.c new file mode 100644 index 0000000..a667045 --- /dev/null +++ b/src/constructors/generalized_petersen.c @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_generalized_petersen + * \brief Creates a Generalized Petersen graph. + * + * The generalized Petersen graph G(n, k) consists of \p n vertices + * \c v_0, ..., \c v_n forming an "outer" cycle graph, and \p n additional vertices + * \c u_0, ..., \c u_n forming an "inner" circulant graph where u_i + * is connected to u_(i + k mod n). Additionally, all \c v_i are + * connected to \c u_i. + * + * + * G(n, k) has \c 2n vertices and \c 3n edges. The Petersen graph + * itself is G(5, 2). + * + * + * Reference: + * + * + * M. E. Watkins, + * A Theorem on Tait Colorings with an Application to the Generalized Petersen Graphs, + * Journal of Combinatorial Theory 6, 152-164 (1969). + * https://doi.org/10.1016%2FS0021-9800%2869%2980116-X + * + * \param graph Pointer to an uninitialized graph object, the result will + * be stored here. + * \param n Integer, \c n is the number of vertices in the inner and outer + * cycle/circulant graphs. It must be at least 3. + * \param k Integer, \c k is the shift of the circulant graph. It must be + * positive and less than n/2. + * \return Error code. + * + * \sa \ref igraph_famous() for the original Petersen graph. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ +igraph_error_t igraph_generalized_petersen(igraph_t *graph, igraph_int_t n, igraph_int_t k) { + /* This is a generalized Petersen graph constructor */ + igraph_vector_int_t edges; + igraph_int_t no_of_nodes, no_of_edges2; + igraph_int_t i; + + if (n < 3) { + IGRAPH_ERRORF("n = %" IGRAPH_PRId " must be at least 3.", IGRAPH_EINVAL, n); + } + + IGRAPH_SAFE_MULT(n, 2, &no_of_nodes); + + /* The seemingly redundant k < n check avoids integer overflow on 2*k in 2*k < n. + * Note that 2*n has already been checked not to overflow above. */ + if (! (k > 0 && k < n && 2*k < n)) { + IGRAPH_ERRORF("k = %" IGRAPH_PRId " must be positive and less than n/2 with n = %" IGRAPH_PRId ".", IGRAPH_EINVAL, k, n); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_SAFE_MULT(n, 6, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + + for (i = 0; i < n; i++) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, (i + 1) % n); /* reserved */ + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, i + n); /* reserved */ + igraph_vector_int_push_back(&edges, i + n); /* reserved */ + igraph_vector_int_push_back(&edges, ((i + k) % n) + n); /* reserved */ + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/kautz.c b/src/constructors/kautz.c new file mode 100644 index 0000000..7b68a26 --- /dev/null +++ b/src/constructors/kautz.c @@ -0,0 +1,210 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/** + * \function igraph_kautz + * \brief Generate a Kautz graph. + * + * A Kautz graph is a labeled graph, vertices are labeled by strings + * of length \c n+1 above an alphabet with \c m+1 letters, with + * the restriction that every two consecutive letters in the string + * must be different. There is a directed edge from a vertex \c v to + * another vertex \c w if it is possible to transform the string of + * \c v into the string of \c w by removing the first letter and + * appending a letter to it. For string length 1 the new letter + * cannot equal the old letter, so there are no loops. + * + * + * Kautz graphs have some interesting properties, see e.g. Wikipedia + * for details. + * + * + * Vincent Matossian wrote the first version of this function in R, + * thanks. + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param m Integer, \c m+1 is the number of letters in the alphabet. + * \param n Integer, \c n+1 is the length of the strings. + * \return Error code. + * + * \sa \ref igraph_de_bruijn(). + * + * Time complexity: O(|V|* [(m+1)/m]^n +|E|), in practice it is more + * like O(|V|+|E|). |V| is the number of vertices, |E| is the number + * of edges and \c m and \c n are the corresponding arguments. + */ +igraph_error_t igraph_kautz(igraph_t *graph, igraph_int_t m, igraph_int_t n) { + + /* m+1 - number of symbols */ + /* n+1 - length of strings */ + + igraph_int_t no_of_nodes, no_of_edges; + igraph_int_t allstrings; + igraph_int_t i, j, idx = 0; + igraph_vector_int_t edges; + igraph_vector_int_t digits, table; + igraph_vector_int_t index1, index2; + igraph_int_t actb = 0; + igraph_int_t actvalue = 0; + int iter = 0; + + if (m < 0 || n < 0) { + IGRAPH_ERROR("`m' and `n' should be non-negative in a Kautz graph", + IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_full(graph, m + 1, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + } + if (m == 0) { + return igraph_empty(graph, 0, IGRAPH_DIRECTED); + } + + /* no_of_nodes = ((m + 1) * pow(m, n)) */ + { + igraph_real_t m_to_pow_n_real = pow(m, n); + igraph_int_t m_to_pow_n = m_to_pow_n_real; + if (m_to_pow_n != m_to_pow_n_real) { + IGRAPH_ERRORF("Parameters (%" IGRAPH_PRId ", %" IGRAPH_PRId ") too large for Kautz graph.", IGRAPH_EINVAL, + m, n); + } + IGRAPH_SAFE_MULT(m+1, m_to_pow_n, &no_of_nodes); + } + /* no_of_edges = m * no_of_nodes */ + IGRAPH_SAFE_MULT(no_of_nodes, m, &no_of_edges); + + { + igraph_real_t allstrings_real = pow(m + 1, n + 1); + allstrings = allstrings_real; + if (allstrings != allstrings_real) { + IGRAPH_ERRORF("Parameters (%" IGRAPH_PRId ", %" IGRAPH_PRId ") too large for Kautz graph.", IGRAPH_EINVAL, + m, n); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + IGRAPH_CHECK(igraph_vector_int_init(&table, n + 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &table); + j = 1; + for (i = n; i >= 0; i--) { + VECTOR(table)[i] = j; + j *= (m + 1); + } + + IGRAPH_CHECK(igraph_vector_int_init(&digits, n + 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &digits); + IGRAPH_CHECK(igraph_vector_int_init(&index1, pow(m + 1, n + 1))); + IGRAPH_FINALLY(igraph_vector_int_destroy, &index1); + IGRAPH_CHECK(igraph_vector_int_init(&index2, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &index2); + + /* Fill the index tables*/ + while (true) { + /* at the beginning of the loop, 0:actb contain the valid prefix */ + /* we might need to fill it to get a valid string */ + igraph_int_t z = 0; + if (VECTOR(digits)[actb] == 0) { + z = 1; + } + for (actb++; actb <= n; actb++) { + VECTOR(digits)[actb] = z; + actvalue += z * VECTOR(table)[actb]; + z = 1 - z; + } + actb = n; + + /* ok, we have a valid string now */ + VECTOR(index1)[actvalue] = idx + 1; + VECTOR(index2)[idx] = actvalue; + idx++; + + /* finished? */ + if (idx >= no_of_nodes) { + break; + } + + /* not yet, we need a valid prefix now */ + while (true) { + /* try to increase digits at position actb */ + igraph_int_t next = VECTOR(digits)[actb] + 1; + if (actb != 0 && VECTOR(digits)[actb - 1] == next) { + next++; + } + if (next <= m) { + /* ok, no problem */ + actvalue += (next - VECTOR(digits)[actb]) * VECTOR(table)[actb]; + VECTOR(digits)[actb] = next; + break; + } else { + /* bad luck, try the previous digit */ + actvalue -= VECTOR(digits)[actb] * VECTOR(table)[actb]; + actb--; + } + } + } + + { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + } + + /* Now come the edges at last */ + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t fromvalue = VECTOR(index2)[i]; + igraph_int_t lastdigit = fromvalue % (m + 1); + igraph_int_t basis = (fromvalue * (m + 1)) % allstrings; + for (j = 0; j <= m; j++) { + igraph_int_t tovalue, to; + if (j == lastdigit) { + continue; + } + tovalue = basis + j; + to = VECTOR(index1)[tovalue] - 1; + if (to < 0) { + continue; + } + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + igraph_vector_int_destroy(&index2); + igraph_vector_int_destroy(&index1); + igraph_vector_int_destroy(&digits); + igraph_vector_int_destroy(&table); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/lattices.c b/src/constructors/lattices.c new file mode 100644 index 0000000..bdbc1c5 --- /dev/null +++ b/src/constructors/lattices.c @@ -0,0 +1,601 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +#define MIN(n, m) (n < m ? n : m) +#define MAX(n, m) (n < m ? m : n) + +#define VERTEX_INDEX(i, j) \ + lex_ordering ? row_count * (i - VECTOR(*row_start_vector)[j]) + j : (VECTOR(row_lengths_prefix_sum_vector)[j] + i - VECTOR(*row_start_vector)[j]) + +#define ROW_END(j) (VECTOR(*row_start_vector)[j] + VECTOR(*row_lengths_vector)[j] - 1) +#define ADD_EDGE_IJ_KL_IF_EXISTS(i, j, k, l) \ + if (VECTOR(*row_start_vector)[l] <= k && k <= ROW_END(l) && 0 <= l && l <= row_count - 1) \ + { \ + igraph_vector_int_push_back(&edges, VERTEX_INDEX((i), (j))); /* reserved */ \ + igraph_vector_int_push_back(&edges, VERTEX_INDEX((k), (l))); /* reserved */ \ + if (directed && mutual) \ + { \ + igraph_vector_int_push_back(&edges, VERTEX_INDEX((k), (l))); /* reserved */ \ + igraph_vector_int_push_back(&edges, VERTEX_INDEX((i), (j))); /* reserved */ \ + } \ + } + +#define COMPUTE_NUMBER_OF_VERTICES() \ + do \ + { \ + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_prefix_sum_vector, row_count + 1); \ + VECTOR(row_lengths_prefix_sum_vector)[0] = 0; \ + for (i = 1; i < row_count + 1; i++) \ + { \ + IGRAPH_SAFE_ADD(VECTOR(row_lengths_prefix_sum_vector)[i - 1], VECTOR(*row_lengths_vector)[i - 1], &(VECTOR(row_lengths_prefix_sum_vector)[i])); \ + } \ + no_of_nodes = VECTOR(row_lengths_prefix_sum_vector)[row_count]; \ + } while (0) + +/** + * Creates a triangular lattice whose vertices have the form (i, j) for non-negative integers i and j + * and (i, j) is connected with (i + 1, j), (i, j + 1), and (i - 1, j + 1) provided a vertex + * exists. Thus, all vertices have degree at most 6. + * + * + * The vertices of the resulting graph are ordered lexicographically with the 2nd coordinate being + * more significant, e.g., (i, j) < (i + 1, j) and (i + 1, j) < (i, j + 1) unless + * \c lex_ordering is set to true in which case the roles of the coordinates are reversed. + * + * \param graph An uninitialized graph object. + * \param directed Boolean, whether to create a directed graph. + * If the \c mutual argument is not set to true, + * edges will be directed from lower-index vertices towards + * higher-index ones. + * \param mutual Boolean, if the graph is directed this gives whether + * to create all connections as mutual. + * \param lex_ordering Boolean, set to true if the vertices of the resulting graph are ordered + * lexicographically with the 1st coordinate being more significant. Use only when all the + * rows have the number of vertices. + * \param row_lengths_vector Integer vector, defines the number of vertices with + * the second coordinate equal to the index. The length of this vector must match + * the length of \p row_start_vector. All coordinates must be non-negative. + * \param row_start_vector Integer vector, defines the leftmost coordinate of + * the vertex with the second coordinate equal to the index. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid (negative) length of row_lengths_vector does not match the length of the + * row_start_vector. + * + * Time complexity: O(|V|), where |V| is the number of vertices in the generated graph. + */ +static igraph_error_t triangular_lattice( + igraph_t *graph, igraph_bool_t directed, igraph_bool_t mutual, igraph_bool_t lex_ordering, + const igraph_vector_int_t *row_lengths_vector, const igraph_vector_int_t *row_start_vector) { + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t row_count = igraph_vector_int_size(row_lengths_vector); + igraph_int_t no_of_nodes; + igraph_vector_int_t row_lengths_prefix_sum_vector; + igraph_int_t i, j; + + if (igraph_vector_int_size(row_lengths_vector) != igraph_vector_int_size(row_start_vector)) { + IGRAPH_ERRORF( + "Length of row_lengths_vector vector (%" IGRAPH_PRId ") must match the length of the " + "row_start_vector (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_int_size(row_lengths_vector), + igraph_vector_int_size(row_start_vector)); + } + + + if (row_count > 0 && lex_ordering && !igraph_vector_int_isininterval(row_lengths_vector, VECTOR(*row_lengths_vector)[0], VECTOR(*row_lengths_vector)[0])) { + IGRAPH_ERROR( + "row_lengths_vector must have all the coordinates the same", IGRAPH_EINVAL); + } + + for (i = 0; i < row_count; i++) { + if (VECTOR(*row_lengths_vector)[i] < 0) { + IGRAPH_ERRORF( + "row_lengths_vector vector must have non-negative coordinates, " + "was (%" IGRAPH_PRId ") for the (%" IGRAPH_PRId ")-th row.", + IGRAPH_EINVAL, VECTOR(*row_lengths_vector)[i], i); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + COMPUTE_NUMBER_OF_VERTICES(); + + /* computing the number of edges in the constructed triangular lattice */ + igraph_int_t no_of_edges2 = VECTOR(*row_lengths_vector)[row_count - 1] - 1; + igraph_int_t multiplier = mutual && directed ? 4 : 2; + for (j = 0; j < row_count - 1; j++) { + IGRAPH_SAFE_ADD(no_of_edges2, VECTOR(*row_lengths_vector)[j] - 1, &no_of_edges2); + IGRAPH_SAFE_ADD(no_of_edges2, MIN(ROW_END(j), ROW_END((j + 1))) - MAX(VECTOR(*row_start_vector)[j], VECTOR(*row_start_vector)[j + 1]) + 1, + &no_of_edges2); + IGRAPH_SAFE_ADD(no_of_edges2, MIN(ROW_END(j), ROW_END((j + 1)) + 1) - MAX(VECTOR(*row_start_vector)[j], VECTOR(*row_start_vector)[j + 1] + 1) + 1, + &no_of_edges2); + } + IGRAPH_SAFE_MULT(no_of_edges2, multiplier, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + + /* constructing the edge array */ + igraph_int_t k; + for (j = 0; j < row_count; j++) { + IGRAPH_ALLOW_INTERRUPTION(); + for (i = 0; i < VECTOR(*row_lengths_vector)[j]; i++) { + k = VECTOR(*row_start_vector)[j] + i; + ADD_EDGE_IJ_KL_IF_EXISTS(k, j, (k + 1), j); + if (j < row_count - 1) { + ADD_EDGE_IJ_KL_IF_EXISTS(k, j, k, (j + 1)); + ADD_EDGE_IJ_KL_IF_EXISTS(k, j, (k - 1), (j + 1)); + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&row_lengths_prefix_sum_vector); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t triangular_lattice_triangle_shape(igraph_t *graph, igraph_int_t size, igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t row_count = size; + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count); + + for (i = 0; i < row_count; i++) { + VECTOR(row_lengths_vector)[i] = size - i; + VECTOR(row_start_vector)[i] = 0; + } + + IGRAPH_CHECK(triangular_lattice(graph, directed, mutual, false, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t triangular_lattice_rectangle_shape( + igraph_t *graph, igraph_int_t size_x, igraph_int_t size_y, + igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t row_count = size_x; + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count); + + for (i = 0; i < row_count; i++) { + VECTOR(row_lengths_vector)[i] = size_y; + VECTOR(row_start_vector)[i] = (row_count - i) / 2; + } + + IGRAPH_CHECK(triangular_lattice(graph, directed, mutual, false, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t triangular_lattice_hex_shape( + igraph_t *graph, igraph_int_t size_x, igraph_int_t size_y, + igraph_int_t size_z, igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t row_count = size_y + size_z - 1; + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count); + + igraph_int_t row_length = size_x; + igraph_int_t row_start = size_y - 1; + igraph_int_t first_threshold = MIN(size_y - 1, size_z - 1); + igraph_int_t second_threshold = MAX(size_y - 1, size_z - 1); + igraph_int_t sgn_flag = size_y < size_z ? 0 : -1; + + for (i = 0; i < row_count; i++) { + VECTOR(row_lengths_vector)[i] = row_length; + VECTOR(row_start_vector)[i] = row_start; + + if (i < first_threshold) { + row_length++; + row_start--; + } else if (i < second_threshold) { + row_start += sgn_flag; + } else { + row_length--; + } + } + + IGRAPH_CHECK(triangular_lattice(graph, directed, mutual, false, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_triangular_lattice + * \brief A triangular lattice with the given shape. + * + * Creates a triangular lattice whose vertices have the form (i, j) for non-negative + * integers i and j and (i, j) is generally connected with (i + 1, j), (i, j + 1), + * and (i - 1, j + 1). The function constructs a planar dual of the graph + * constructed by \ref igraph_hexagonal_lattice(). In particular, there a one-to-one + * correspondence between the vertices in the constructed graph and the cycles of + * length 6 in the graph constructed by \ref igraph_hexagonal_lattice() + * with the same \p dims parameter. + * + * + * The vertices of the resulting graph are ordered lexicographically with the 2nd + * coordinate being more significant, e.g., (i, j) < (i + 1, j) and + * (i + 1, j) < (i, j + 1) + * + * \param graph An uninitialized graph object. + * \param dims Integer vector, defines the shape of the lattice. + * If \p dims is of length 1, the resulting lattice has a triangular shape + * where each side of the triangle contains dims[0] vertices. + * If \p dims is of length 2, the resulting lattice has a + * "quasi rectangular" shape with the sides containing dims[0] and + * dims[1] vertices, respectively. + * If \p dims is of length 3, the resulting lattice has a hexagonal shape + * where the sides of the hexagon contain dims[0], dims[1] + * and dims[2] vertices. All dimensions must be non-negative. + * \param directed Boolean, whether to create a directed graph. + * If the \c mutual argument is not set to true, edges will be directed from + * lower-index vertices towards higher-index ones. + * \param mutual Boolean, if the graph is directed this gives whether + * to create all connections as mutual. + * \return Error code: + * \c IGRAPH_EINVAL: The size of \p dims must be either 1, 2, or 3 with all + * the components at least 1. + * + * \sa \ref igraph_hexagonal_lattice() and \ref igraph_square_lattice() for creating + * other types of lattices; \ref igraph_regular_tree() to create a Bethe lattice. + * + * Time complexity: O(|V|), where |V| is the number of vertices in the generated graph. + * + */ +igraph_error_t igraph_triangular_lattice( + igraph_t *graph, const igraph_vector_int_t *dims, + igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t num_dims = igraph_vector_int_size(dims); + if (igraph_vector_int_any_smaller(dims, 0)) { + IGRAPH_ERROR("Invalid dimension vector.", IGRAPH_EINVAL); + } + /* If a coordinate of dims is 0 the result is an empty graph. */ + if (igraph_vector_int_contains(dims, 0)) { + return igraph_empty(graph, 0, directed); + } + + switch (num_dims) { + case 1: + IGRAPH_CHECK(triangular_lattice_triangle_shape(graph, VECTOR(*dims)[0], directed, mutual)); + break; + case 2: + IGRAPH_CHECK(triangular_lattice_rectangle_shape(graph, VECTOR(*dims)[0], VECTOR(*dims)[1], directed, mutual)); + break; + case 3: + IGRAPH_CHECK(triangular_lattice_hex_shape(graph, VECTOR(*dims)[0], VECTOR(*dims)[1], VECTOR(*dims)[2], directed, mutual)); + break; + default: + IGRAPH_ERRORF( + "The size of the dimension vector must be 1, 2 or 3, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, num_dims); + } + + return IGRAPH_SUCCESS; +} + + +/** + * Creates a hexagonal lattice whose vertices have the form (i, j) for non-negative integers i and j + * and (i, j) is connected with (i + 1, j), and if i is odd also with (i - 1, j + 1) provided a vertex + * exists. Thus, all vertices have degree at most 3. + * + * + * The vertices of the resulting graph are ordered lexicographically with the 2nd coordinate being + * more significant, e.g., (i, j) < (i + 1, j) and (i + 1, j) < (i, j + 1). + * + * \param graph An uninitialized graph object. + * \param directed Boolean, whether to create a directed graph. + * If the \c mutual argument is not set to true, + * edges will be directed from lower-index vertices towards + * higher-index ones. + * \param mutual Boolean, if the graph is directed this gives whether + * to create all connections as mutual. + * \param row_lengths_vector Integer vector, defines the number of vertices with + * the second coordinate equal to the index. The length of this vector must match + * the length of \p row_start_vector. All coordinates must be non-negative. + * \param row_start_vector Integer vector, defines the leftmost coordinate of + * the vertex with the second coordinate equal to the index. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid (negative) length of row_lengths_vector does not match the length of the + * row_start_vector. + * + * Time complexity: O(|V|), where |V| is the number of vertices in the generated graph. + */ +static igraph_error_t hexagonal_lattice( + igraph_t *graph, igraph_bool_t directed, igraph_bool_t mutual, + const igraph_vector_int_t *row_lengths_vector, const igraph_vector_int_t *row_start_vector +) { + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t row_count = igraph_vector_int_size(row_lengths_vector); + igraph_int_t no_of_nodes; + igraph_vector_int_t row_lengths_prefix_sum_vector; + igraph_int_t i, j; + igraph_bool_t lex_ordering = false; + + if (igraph_vector_int_size(row_lengths_vector) != igraph_vector_int_size(row_start_vector)) { + IGRAPH_ERRORF( + "Length of row_lengths_vector vector (%" IGRAPH_PRId ") must match the length of the " + "row_start_vector (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_int_size(row_lengths_vector), + igraph_vector_int_size(row_start_vector) + ); + } + + for (i = 0; i < row_count; i++) { + if (VECTOR(*row_lengths_vector)[i] < 0) { + IGRAPH_ERRORF( + "row_lengths_vector vector must have non-negative coordinates, " + "was (%" IGRAPH_PRId ") for the (%" IGRAPH_PRId ")-th row.", + IGRAPH_EINVAL, VECTOR(*row_lengths_vector)[i], i); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + COMPUTE_NUMBER_OF_VERTICES(); + + /* computing the number of edges in the constructed hex lattice */ + igraph_int_t no_of_edges2 = VECTOR(*row_lengths_vector)[row_count - 1] - 1; + igraph_int_t multiplier = mutual && directed ? 4 : 2, low, high; + for (j = 0; j < row_count - 1; j++) { + IGRAPH_SAFE_ADD(no_of_edges2, VECTOR(*row_lengths_vector)[j] - 1, &no_of_edges2); + low = MAX((VECTOR(*row_start_vector)[j] - 1), (VECTOR(*row_start_vector)[j + 1])); + low = low % 2 ? low + 1 : low; + high = MIN((ROW_END(j) - 1), (ROW_END(j + 1))); + high = high % 2 ? high - 1 : high; + IGRAPH_SAFE_ADD(no_of_edges2, (high - low) / 2 + 1, &no_of_edges2); + } + IGRAPH_SAFE_MULT(no_of_edges2, multiplier, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + + /* constructing the edge array */ + igraph_int_t k; + for (j = 0; j < row_count; j++) { + IGRAPH_ALLOW_INTERRUPTION(); + for (i = 0; i < VECTOR(*row_lengths_vector)[j]; i++) { + k = VECTOR(*row_start_vector)[j] + i; + ADD_EDGE_IJ_KL_IF_EXISTS(k, j, (k + 1), j); + if (j < row_count - 1 && k % 2 == 1) { + ADD_EDGE_IJ_KL_IF_EXISTS(k, j, (k - 1), (j + 1)); + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&row_lengths_prefix_sum_vector); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t hexagonal_lattice_triangle_shape(igraph_t *graph, igraph_int_t size, igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t row_count; + IGRAPH_SAFE_ADD(size, 2, &row_count); + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count - 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count - 1); + + for (i = 0; i < row_count - 1; i++) { + VECTOR(row_lengths_vector)[i] = 2 * (row_count - i) - (i ? 1 : 3); + VECTOR(row_start_vector)[i] = (i ? 0 : 1); + } + + IGRAPH_CHECK(hexagonal_lattice(graph, directed, mutual, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t hexagonal_lattice_rectangle_shape( + igraph_t *graph, igraph_int_t size_x, igraph_int_t size_y, + igraph_bool_t directed, igraph_bool_t mutual +) { + igraph_int_t row_count; + IGRAPH_SAFE_ADD(size_x, 1, &row_count); + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t actual_size_y; + IGRAPH_SAFE_ADD(size_y, 1, &actual_size_y); + IGRAPH_SAFE_MULT(actual_size_y, 2, &actual_size_y); + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count); + + igraph_bool_t is_first_row, is_last_row, is_start_odd; + + for (i = 0; i < row_count; i++) { + is_first_row = (i == 0); + is_last_row = i == row_count - 1; + is_start_odd = (row_count - i - 1) % 2; + VECTOR(row_lengths_vector)[i] = actual_size_y - (is_first_row || is_last_row ? 1 : 0); + VECTOR(row_start_vector)[i] = row_count - i - 1 + (is_first_row && !is_start_odd ? 1 : 0); + } + + IGRAPH_CHECK(hexagonal_lattice(graph, directed, mutual, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t hexagonal_lattice_hex_shape( + igraph_t *graph, igraph_int_t size_x, igraph_int_t size_y, + igraph_int_t size_z, igraph_bool_t directed, igraph_bool_t mutual +) { + igraph_int_t row_count = size_y + size_z; + igraph_vector_int_t row_lengths_vector; + igraph_vector_int_t row_start_vector; + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_lengths_vector, row_count); + IGRAPH_VECTOR_INT_INIT_FINALLY(&row_start_vector, row_count); + + igraph_int_t row_length; + IGRAPH_SAFE_MULT(size_x, 2, &row_length); + IGRAPH_SAFE_ADD(row_length, 1, &row_length); + igraph_int_t row_start; + IGRAPH_SAFE_MULT(size_y, 2, &row_start); + IGRAPH_SAFE_ADD(row_start, -1, &row_start); + igraph_int_t first_threshold = MIN(size_y - 1, size_z - 1); + igraph_int_t second_threshold = MAX(size_y - 1, size_z - 1); + igraph_int_t sgn_flag = size_y < size_z ? 0 : -2; + + for (i = 0; i < row_count; i++) { + VECTOR(row_lengths_vector)[i] = row_length; + VECTOR(row_start_vector)[i] = row_start; + + if (i < first_threshold) { + row_length += 2; + row_start -= 2; + } else if (i < second_threshold) { + row_start += sgn_flag; + } else { + row_length -= 2; + } + if (i == size_y - 1) { + row_start--; + row_length++; + } + if (i == size_z - 1) { + row_length++; + } + } + + IGRAPH_CHECK(hexagonal_lattice(graph, directed, mutual, &row_lengths_vector, &row_start_vector)); + + igraph_vector_int_destroy(&row_lengths_vector); + igraph_vector_int_destroy(&row_start_vector); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hexagonal_lattice + * \brief A hexagonal lattice with the given shape. + * + * Creates a hexagonal lattice whose vertices have the form (i, j) for non-negative + * integers i and j and (i, j) is generally connected with (i + 1, j), and if i is + * odd also with (i - 1, j + 1). The function constructs a planar dual of the graph + * constructed by \ref igraph_triangular_lattice(). In particular, there a one-to-one + * correspondence between the cycles of length 6 in the constructed graph and the + * vertices of the graph constructed by \ref igraph_triangular_lattice() function + * with the same \p dims parameter. + * + * + * The vertices of the resulting graph are ordered lexicographically with the 2nd + * coordinate being more significant, e.g., (i, j) < (i + 1, j) and + * (i + 1, j) < (i, j + 1) + * + * \param graph An uninitialized graph object. + * \param dims Integer vector, defines the shape of the lattice. + * If \p dims is of length 1, the resulting lattice has a triangular shape + * where each side of the triangle contains dims[0] vertices. + * If \p dims is of length 2, the resulting lattice has a "quasi rectangular" + * shape with the sides containing dims[0] and dims[1] + * vertices, respectively. If \p dims is of length 3, the resulting lattice has + * a hexagonal shape where the sides of the hexagon contain dims[0], + * dims[1] and dims[2] vertices. All coordinates must + * be non-negative. + * \param directed Boolean, whether to create a directed graph. + * If the \c mutual argument is not set to true, edges will be directed from + * lower-index vertices towards higher-index ones. + * \param mutual Boolean, if the graph is directed this gives whether to create + * all connections as mutual. + * \return Error code: + * \c IGRAPH_EINVAL: The size of \p dims must be either 1, 2, or 3 with all + * the components at least 1. + * + * \sa \ref igraph_triangular_lattice() and \ref igraph_square_lattice() for creating + * other types of lattices; ; \ref igraph_regular_tree() to create a Bethe lattice. + * + * Time complexity: O(|V|), where |V| is the number of vertices in the generated graph. + */ +igraph_error_t igraph_hexagonal_lattice( + igraph_t *graph, const igraph_vector_int_t *dims, + igraph_bool_t directed, igraph_bool_t mutual) { + igraph_int_t num_dims = igraph_vector_int_size(dims); + if (igraph_vector_int_any_smaller(dims, 0)) { + IGRAPH_ERROR("Invalid dimension vector.", IGRAPH_EINVAL); + } + /* If a coordinate of dims is 0 the result is an empty graph. */ + if (igraph_vector_int_any_smaller(dims, 1)) { + return igraph_empty(graph, 0, directed); + } + + switch (num_dims) { + case 1: + IGRAPH_CHECK(hexagonal_lattice_triangle_shape(graph, VECTOR(*dims)[0], directed, mutual)); + break; + case 2: + IGRAPH_CHECK(hexagonal_lattice_rectangle_shape(graph, VECTOR(*dims)[0], VECTOR(*dims)[1], directed, mutual)); + break; + case 3: + IGRAPH_CHECK(hexagonal_lattice_hex_shape(graph, VECTOR(*dims)[0], VECTOR(*dims)[1], VECTOR(*dims)[2], directed, mutual)); + break; + default: + IGRAPH_ERRORF( + "The size of the dimension vector must be 1, 2 or 3, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, num_dims + ); + } + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/lcf.c b/src/constructors/lcf.c new file mode 100644 index 0000000..8df89e2 --- /dev/null +++ b/src/constructors/lcf.c @@ -0,0 +1,156 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_operators.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_lcf + * \brief Creates a graph from LCF notation. + * + * LCF notation (named after Lederberg, Coxeter and Frucht) is a concise notation + * for 3-regular Hamiltonian graphs. It consists of three parameters: the + * number of vertices in the graph, a list of shifts giving additional + * edges to a cycle backbone, and another integer giving how many times + * the shifts should be performed. See + * https://mathworld.wolfram.com/LCFNotation.html for details. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer constant giving the number of vertices. This + * is normally set to the number of shifts multiplied by the + * number of repeats. + * \param shifts An integer vector giving the shifts. + * \param repeats The number of repeats for the shifts. + * \return Error code. + * + * \sa \ref igraph_lcf_small(), \ref igraph_extended_chordal_ring() + * + * Time complexity: O(|V|+|E|), linear in the number of vertices plus + * the number of edges. + */ +igraph_error_t igraph_lcf(igraph_t *graph, igraph_int_t n, + const igraph_vector_int_t *shifts, + igraph_int_t repeats) { + + igraph_vector_int_t edges; + igraph_int_t no_of_shifts = igraph_vector_int_size(shifts); + igraph_int_t ptr = 0, sptr = 0; + igraph_int_t no_of_nodes = n; + igraph_int_t no_of_edges; + igraph_int_t no_of_edges2; + + if (repeats < 0) { + IGRAPH_ERROR("Number of repeats must not be negative.", IGRAPH_EINVAL); + } + + /* no_of_edges = n + no_of_shifts * repeats */ + IGRAPH_SAFE_MULT(no_of_shifts, repeats, &no_of_edges); + IGRAPH_SAFE_ADD(no_of_edges, n, &no_of_edges); + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges2); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + + if (no_of_nodes > 0) { + /* Create a ring first */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = i + 1; + } + VECTOR(edges)[ptr - 1] = 0; + } + + /* Then add the rest */ + while (ptr < 2 * no_of_edges) { + igraph_int_t sh = VECTOR(*shifts)[sptr % no_of_shifts]; + igraph_int_t from = sptr % no_of_nodes; + igraph_int_t to = (no_of_nodes + sptr + sh) % no_of_nodes; + VECTOR(edges)[ptr++] = from; + VECTOR(edges)[ptr++] = to; + sptr++; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, IGRAPH_UNDIRECTED)); + IGRAPH_CHECK(igraph_simplify(graph, true, true, NULL)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lcf_small + * \brief Shorthand to create a graph from LCF notation, giving shifts as the arguments. + * + * This function provides a shorthand to give the shifts of the LCF notation + * directly as function arguments. See \ref igraph_lcf() for an explanation + * of LCF notation. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param ... The shifts and the number of repeats for the shifts, + * plus an additional 0 to mark the end of the arguments. + * \return Error code. + * + * \sa See \ref igraph_lcf() for a similar function using an + * \ref igraph_vector_t instead of the variable length argument list; + * \ref igraph_circulant() to create circulant graphs. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_lcf.c + */ +igraph_error_t igraph_lcf_small(igraph_t *graph, igraph_int_t n, ...) { + igraph_vector_int_t shifts; + igraph_int_t repeats; + va_list ap; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&shifts, 0); + + va_start(ap, n); + while (true) { + igraph_error_t err; + int num = va_arg(ap, int); + if (num == 0) { + break; + } + err = igraph_vector_int_push_back(&shifts, num); + if (err != IGRAPH_SUCCESS) { + va_end(ap); + IGRAPH_ERROR("", err); + } + } + va_end(ap); + if (igraph_vector_int_size(&shifts) == 0) { + repeats = 0; + } else { + repeats = igraph_vector_int_pop_back(&shifts); + } + + IGRAPH_CHECK(igraph_lcf(graph, n, &shifts, repeats)); + igraph_vector_int_destroy(&shifts); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/linegraph.c b/src/constructors/linegraph.c new file mode 100644 index 0000000..413dd99 --- /dev/null +++ b/src/constructors/linegraph.c @@ -0,0 +1,174 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" + +#include "core/interruption.h" + +/* Note to self: tried using adjacency lists instead of igraph_incident queries, + * with minimal performance improvements on a graph with 70K vertices and 360K + * edges. (1.09s instead of 1.10s). I think it's not worth the fuss. */ +static igraph_error_t igraph_i_linegraph_undirected(const igraph_t *graph, igraph_t *linegraph) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t adjedges, adjedges2; + igraph_vector_int_t edges; + igraph_int_t prev = -1; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjedges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjedges2, 0); + + for (igraph_int_t e1 = 0; e1 < no_of_edges; e1++) { + igraph_int_t from = IGRAPH_FROM(graph, e1); + igraph_int_t to = IGRAPH_TO(graph, e1); + igraph_int_t n; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (from != prev) { + IGRAPH_CHECK(igraph_incident(graph, &adjedges, from, IGRAPH_ALL, IGRAPH_LOOPS)); + } + n = igraph_vector_int_size(&adjedges); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t e2 = VECTOR(adjedges)[i]; + if (e2 < e1) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e2)); + } + } + + IGRAPH_CHECK(igraph_incident(graph, &adjedges2, to, IGRAPH_ALL, IGRAPH_LOOPS)); + n = igraph_vector_int_size(&adjedges2); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t e2 = VECTOR(adjedges2)[i]; + if (e2 < e1) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e2)); + } + } + + /* Self-loops are considered self-adjacent. */ + if (from == to) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e1)); + } + + prev = from; + } + + igraph_vector_int_destroy(&adjedges); + igraph_vector_int_destroy(&adjedges2); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(linegraph, &edges, no_of_edges, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_linegraph_directed(const igraph_t *graph, igraph_t *linegraph) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t i, j, n; + igraph_vector_int_t adjedges; + igraph_vector_int_t edges; + igraph_int_t prev = -1; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjedges, 0); + + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + + IGRAPH_ALLOW_INTERRUPTION(); + + if (from != prev) { + IGRAPH_CHECK(igraph_incident(graph, &adjedges, from, IGRAPH_IN, IGRAPH_LOOPS)); + } + n = igraph_vector_int_size(&adjedges); + for (j = 0; j < n; j++) { + igraph_int_t e = VECTOR(adjedges)[j]; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, e)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + } + + prev = from; + } + + igraph_vector_int_destroy(&adjedges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(linegraph, &edges, no_of_edges, igraph_is_directed(graph))); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_linegraph + * \brief Create the line graph of a graph. + * + * The line graph L(G) of a G undirected graph is defined as follows. + * L(G) has one vertex for each edge in G and two different vertices in L(G) + * are connected by an edge if their corresponding edges share an end point. + * In a multigraph, if two end points are shared, two edges are created. + * The single vertex of an undirected self-loop is counted as two end points. + * + * + * The line graph L(G) of a G directed graph is slightly different: + * L(G) has one vertex for each edge in G and two vertices in L(G) are connected + * by a directed edge if the target of the first vertex's corresponding edge + * is the same as the source of the second vertex's corresponding edge. + * + * + * Self-loops are considered self-adjacent, thus their corresponding vertex + * in the line graph will also a have a single self-loop, in both undirected + * and directed graphs. + * + * + * Edge \em i in the original graph will correspond to vertex \em i + * in the line graph. + * + * + * The first version of this function was contributed by Vincent Matossian, + * thanks. + * + * \param graph The input graph, may be directed or undirected. + * \param linegraph Pointer to an uninitialized graph object, the + * result is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of edges plus the number of vertices. + */ + +igraph_error_t igraph_linegraph(const igraph_t *graph, igraph_t *linegraph) { + + if (igraph_is_directed(graph)) { + return igraph_i_linegraph_directed(graph, linegraph); + } else { + return igraph_i_linegraph_undirected(graph, linegraph); + } +} diff --git a/src/constructors/mycielskian.c b/src/constructors/mycielskian.c new file mode 100644 index 0000000..ff0f9ea --- /dev/null +++ b/src/constructors/mycielskian.c @@ -0,0 +1,244 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" +#include "igraph_operators.h" + +#include "igraph_conversion.h" +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/** + * \function igraph_mycielskian + * \brief Generate the Mycielskian of a graph with \p k iterations. + * + * \experimental + * + * The Mycielskian of a graph is a larger graph formed using a construction due + * to Jan Mycielski that increases the chromatic number by one while preserving + * the triangle-free property. The Mycielski construction can be used to create + * triangle-free graphs with an arbitrarily large chromatic number. + * + * + * Let the \c n vertices of the given graph \c G be \c v_1, ..., \c v_n. + * The Mycielskian of \c G, denoted M(G), contains \c G itself as + * a subgraph, together with \c n+1 additional vertices: + * + * \ilist + * \ili A vertex \c u_i corresponding to each vertex \c v_i of \c G. + * \ili An extra vertex \c w. + * \endilist + * + * + * The edges are added as follows: + * + * \ilist + * \ili Each vertex \c u_i is connected to \c w, forming a star. + * \ili For each edge (v_i, v_j) in \c G, two new edges are added: + * (u_i, v_j) and (v_i, u_j). + * \endilist + * + * Thus, if \c G has \c n vertices and \c m edges, the Mycielskian M(G) + * has 2n + 1 vertices, and 3m + n edges. + * + * + * igraph uses an alternative construction in two special cases: + * + * \ilist + * \ili The Mycielskian of the null graph is the singleton graph. + * \ili The Mycielskian of the singleton graph is the two-path. + * \endilist + * + * This ensures that iterative applications of the construction, starting from + * the null or singleton graph, always yields connected graphs. In fact these + * are the Mycielski graphs that \ref igraph_mycielski_graph() produces. + * + * + * igraph extends the construction to directed graphs, as well as to non-simple + * graphs, by following the above constructions rules literally. + * + * + * This function can apply the Mycielski transformation an arbitrary number of + * times, controlled by the parameter \p k. The k-th iterated Mycielskian has + * n_k = (n + 1) * 2^k - 1 + * vertices and + * m_k = ((2m + 2n + 1) * 3^k - n_{k+1}) / 2 + * edges, where \c n and \c m are the vertex and edge count of the original + * graph, respectively. + * + * \param graph Pointer to the input graph. + * \param res Pointer to an uninitialized graph object where the Mycielskian + * of the input graph will be stored. + * \param k Integer, the number of Mycielskian iterations (must be non-negative). + * \return Error code. + * + * \sa \ref igraph_mycielski_graph(). + * + * Time complexity: O(|V| 2^k + |E| 3^k) where |V| and |E| are the vertex and + * edge counts, respectively. + */ +igraph_error_t igraph_mycielskian(const igraph_t *graph, igraph_t *res, igraph_int_t k) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_int_t edges; + + if (k < 0) { + IGRAPH_ERROR("The number of Mycielski iterations must not be negative.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, /* bycol */ false)); + + /* Special case: null graph. + * We add a single vertex. */ + if (vcount == 0 && k > 0) { + vcount += 1; + k--; + } + + /* Special case: singleton graph. + * We add a new vertex and connect it to the existing vertex. */ + if (vcount == 1 && k > 0) { + vcount += 1; + ecount += 1; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, 0)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, 1)); + k--; + } + + /* No more special cases, we are ready for performing the remaining Mycielski iterations. */ + + /* Compute the number of vertices and edges. Since these are exponential in k, + * overflow checks are important. */ + + igraph_int_t new_vcount = vcount; + igraph_int_t new_ecount = ecount; + + for (igraph_int_t i = 0; i < k; i++) { + // new edges = 3 * old edges + old vertices + IGRAPH_SAFE_MULT(new_ecount, 3, &new_ecount); + IGRAPH_SAFE_ADD(new_ecount, new_vcount, &new_ecount); + + // new vertices = 2 * old vertices + 1 + IGRAPH_SAFE_MULT(new_vcount, 2, &new_vcount); + IGRAPH_SAFE_ADD(new_vcount, 1, &new_vcount); + } + + IGRAPH_CHECK(igraph_vector_int_resize(&edges, new_ecount * 2)); + + igraph_int_t edge_index = 2 * ecount; // Current last edge index in edge vector + igraph_int_t offset = vcount; // Tracks where new vertices start + + for (igraph_int_t i = 0; i < k; i++) { + igraph_int_t prev_vcount = offset; // Number of vertices before this step + igraph_int_t w = offset * 2; // The new 'w' node index + igraph_int_t last_edge_index = edge_index; // Mark where edges before this step end + + // For each edge before this step, add two new edges + for (igraph_int_t j = 0; j < last_edge_index; j += 2) { + igraph_int_t v1 = VECTOR(edges)[j]; + igraph_int_t v2 = VECTOR(edges)[j + 1]; + + VECTOR(edges)[edge_index++] = v1; + VECTOR(edges)[edge_index++] = offset + v2; + + VECTOR(edges)[edge_index++] = v2; + VECTOR(edges)[edge_index++] = offset + v1; + } + + // Add edges connecting each `ui` to `w` (forming a star) + for (igraph_int_t j = prev_vcount; j < w; j++) { + VECTOR(edges)[edge_index++] = j; + VECTOR(edges)[edge_index++] = w; + } + + // Update offset for next step + offset = offset * 2 + 1; + + IGRAPH_ALLOW_INTERRUPTION(); + } + + IGRAPH_CHECK(igraph_create(res, &edges, new_vcount, igraph_is_directed(graph))); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_mycielski_graph + * \brief The Mycielski graph of order \p k. + * + * The Mycielski graph of order \p k, denoted \c M_k, is a triangle-free graph on + * \p k vertices with chromatic number \p k. It is defined through the Mycielski + * construction described in the documentation of \ref igraph_mycielskian(). + * + * + * Some authors define Mycielski graphs only for k > 1. + * igraph extends this to all k >= 0. + * The first few Mycielski graphs are: + * \olist + * \oli M_0: Null graph + * \oli M_1: Single vertex + * \oli M_2: Path graph with 2 vertices + * \oli M_3: Cycle graph with 5 vertices + * \oli M_4: Grötzsch graph (a triangle-free graph with chromatic number 4) + * \endolist + * + * The vertex count of \c M_k is + * n_k = 3 * 2^(k-2) - 1 for k > 1 and \c k otherwise. + * The edge count is + * m_k = (7 * 3^(k-2) + 1) / 2 - 3 * 2^(k - 2) for k > 1 + * and 0 otherwise. + * + * \param graph Pointer to an uninitialized graph object. The generated + * Mycielski graph will be stored here. + * \param k Integer, the order of the Mycielski graph (must be non-negative). + * \return Error code. + * + * \sa \ref igraph_mycielskian(). + * + * Time complexity: O(3^k), i.e. exponential in \p k. + */ +igraph_error_t igraph_mycielski_graph(igraph_t *graph, igraph_int_t k) { + igraph_t g; + + if (k < 0) { + IGRAPH_ERROR("The Mycielski graph order must not be negative.", IGRAPH_EINVAL); + } + + /* Special cases: M_0 and M_1 are the null graph and singleton graph, respectively. */ + if (k <= 1) { + IGRAPH_CHECK(igraph_empty(graph, k, IGRAPH_UNDIRECTED)); + return IGRAPH_SUCCESS; + } + + /* For k >= 2, start construction from P_2. */ + IGRAPH_CHECK(igraph_ring(&g, 2, IGRAPH_UNDIRECTED, false, false)); + IGRAPH_FINALLY(igraph_destroy, &g); + + IGRAPH_CHECK(igraph_mycielskian(&g, graph, k - 2)); + + igraph_destroy(&g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/prufer.c b/src/constructors/prufer.c new file mode 100644 index 0000000..bc18212 --- /dev/null +++ b/src/constructors/prufer.c @@ -0,0 +1,125 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" + +#include "math/safe_intop.h" + +/** + * \ingroup generators + * \function igraph_from_prufer + * \brief Generates a tree from a Prüfer sequence. + * + * A Prüfer sequence is a unique sequence of integers associated + * with a labelled tree. A tree on n vertices can be represented + * by a sequence of n-2 integers, each between 0 and + * n-1 (inclusive). + * + * The algorithm used by this function is based on + * Paulius Micikevičius, Saverio Caminiti, Narsingh Deo: + * Linear-time Algorithms for Encoding Trees as Sequences of Node Labels + * + * \param graph Pointer to an uninitialized graph object. + * \param prufer The Prüfer sequence + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * there is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * invalid Prüfer sequence given + * \endclist + * + * \sa \ref igraph_to_prufer(), \ref igraph_kary_tree(), \ref igraph_tree_game() + * + * Time complexity: O(|V|), where |V| is the number of vertices in the tree. + * + */ +igraph_error_t igraph_from_prufer(igraph_t *graph, const igraph_vector_int_t *prufer) { + igraph_vector_int_t degree; + igraph_vector_int_t edges; + igraph_int_t n; + igraph_int_t i, k; + igraph_int_t u, v; /* vertices */ + igraph_int_t ec; + + IGRAPH_SAFE_ADD(igraph_vector_int_size(prufer), 2, &n); + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, n); /* initializes vector to zeros */ + { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(n - 1, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + /* build out-degree vector (i.e. number of child vertices) and verify Prufer sequence */ + for (i = 0; i < n - 2; ++i) { + igraph_int_t w = VECTOR(*prufer)[i]; + if (w >= n || w < 0) { + IGRAPH_ERROR("Invalid Prufer sequence.", IGRAPH_EINVAL); + } + VECTOR(degree)[w] += 1; + } + + v = 0; /* initialize v now, in case Prufer sequence is empty */ + k = 0; /* index into the Prufer vector */ + ec = 0; /* index into the edges vector */ + for (i = 0; i < n; ++i) { + u = i; + + while (k < n - 2 && u <= i && (VECTOR(degree)[u] == 0)) { + /* u is a leaf here */ + + v = VECTOR(*prufer)[k]; /* parent of u */ + + /* add edge */ + VECTOR(edges)[ec++] = v; + VECTOR(edges)[ec++] = u; + + k += 1; + + VECTOR(degree)[v] -= 1; + + u = v; + } + + if (k == n - 2) { + break; + } + } + + /* find u for last edge, v is already set */ + for (u = i + 1; u < n; ++u) + if ((VECTOR(degree)[u] == 0) && u != v) { + break; + } + + /* add last edge */ + VECTOR(edges)[ec++] = v; + VECTOR(edges)[ec++] = u; + + IGRAPH_CHECK(igraph_create(graph, &edges, n, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/regular.c b/src/constructors/regular.c new file mode 100644 index 0000000..426559f --- /dev/null +++ b/src/constructors/regular.c @@ -0,0 +1,1032 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_constructors.h" + +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_operators.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/** + * \ingroup generators + * \function igraph_star + * \brief Creates a \em star graph, every vertex connects only to the center. + * + * \param graph Pointer to an uninitialized graph object, this will + * be the result. + * \param n Integer constant, the number of vertices in the graph. + * \param mode Constant, gives the type of the star graph to + * create. Possible values: + * \clist + * \cli IGRAPH_STAR_OUT + * directed star graph, edges point + * \em from the center to the other vertices. + * \cli IGRAPH_STAR_IN + * directed star graph, edges point + * \em to the center from the other vertices. + * \cli IGRAPH_STAR_MUTUAL + * directed star graph with mutual edges. + * \cli IGRAPH_STAR_UNDIRECTED + * an undirected star graph is + * created. + * \endclist + * \param center Id of the vertex which will be the center of the + * graph. + * \return Error code: + * \clist + * \cli IGRAPH_EINVVID + * invalid number of vertices. + * \cli IGRAPH_EINVAL + * invalid center vertex. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|), the + * number of vertices in the graph. + * + * \sa \ref igraph_wheel(), \ref igraph_square_lattice(), \ref igraph_ring(), \ref igraph_kary_tree() + * for creating other regular structures. + * + * \example examples/simple/igraph_star.c + */ +igraph_error_t igraph_star(igraph_t *graph, igraph_int_t n, igraph_star_mode_t mode, + igraph_int_t center) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t i; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices.", IGRAPH_EINVVID); + } + if (center < 0 || center > n - 1) { + IGRAPH_ERROR("Invalid center vertex.", IGRAPH_EINVAL); + } + if (mode != IGRAPH_STAR_OUT && mode != IGRAPH_STAR_IN && + mode != IGRAPH_STAR_MUTUAL && mode != IGRAPH_STAR_UNDIRECTED) { + IGRAPH_ERROR("Invalid star mode.", IGRAPH_EINVMODE); + } + + if (mode != IGRAPH_STAR_MUTUAL) { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(n-1, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } else { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(n-1, 4, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + if (mode == IGRAPH_STAR_OUT) { + for (i = 0; i < center; i++) { + VECTOR(edges)[2 * i] = center; + VECTOR(edges)[2 * i + 1] = i; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[2 * (i - 1)] = center; + VECTOR(edges)[2 * (i - 1) + 1] = i; + } + } else if (mode == IGRAPH_STAR_MUTUAL) { + for (i = 0; i < center; i++) { + VECTOR(edges)[4 * i] = center; + VECTOR(edges)[4 * i + 1] = i; + VECTOR(edges)[4 * i + 2] = i; + VECTOR(edges)[4 * i + 3] = center; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[4 * i - 4] = center; + VECTOR(edges)[4 * i - 3] = i; + VECTOR(edges)[4 * i - 2] = i; + VECTOR(edges)[4 * i - 1] = center; + } + } else { + for (i = 0; i < center; i++) { + VECTOR(edges)[2 * i + 1] = center; + VECTOR(edges)[2 * i] = i; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[2 * (i - 1) + 1] = center; + VECTOR(edges)[2 * (i - 1)] = i; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, 0, + (mode != IGRAPH_STAR_UNDIRECTED))); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_wheel + * \brief Creates a \em wheel graph, a union of a star and a cycle graph. + * + * A wheel graph on \p n vertices can be thought of as a wheel with + * n - 1 spokes. The cycle graph part makes up the rim, + * while the star graph part adds the spokes. + * + * + * Note that the two and three-vertex wheel graphs are non-simple: + * The two-vertex wheel graph contains a self-loop, while the three-vertex + * wheel graph contains parallel edges (a 1-cycle and a 2-cycle, respectively). + * + * \param graph Pointer to an uninitialized graph object, this will + * be the result. + * \param n Integer constant, the number of vertices in the graph. + * \param mode Constant, gives the type of the star graph to + * create. Possible values: + * \clist + * \cli IGRAPH_WHEEL_OUT + * directed wheel graph, edges point + * \em from the center to the other vertices. + * \cli IGRAPH_WHEEL_IN + * directed wheel graph, edges point + * \em to the center from the other vertices. + * \cli IGRAPH_WHEEL_MUTUAL + * directed wheel graph with mutual edges. + * \cli IGRAPH_WHEEL_UNDIRECTED + * an undirected wheel graph is + * created. + * \endclist + * \param center Id of the vertex which will be the center of the + * graph. + * \return Error code: + * \clist + * \cli IGRAPH_EINVVID + * invalid number of vertices. + * \cli IGRAPH_EINVAL + * invalid center vertex. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|), the + * number of vertices in the graph. + * + * \sa \ref igraph_square_lattice(), \ref igraph_ring(), \ref igraph_star(), + * \ref igraph_kary_tree() for creating other regular structures. + * + */ + +igraph_error_t igraph_wheel(igraph_t *graph, igraph_int_t n, igraph_wheel_mode_t mode, + igraph_int_t center) { + + igraph_star_mode_t star_mode; + igraph_vector_int_t rim_edges = IGRAPH_VECTOR_NULL; + igraph_int_t i; + + /* Firstly creates a star by the function \ref igraph_star() and makes + * use of its existing input parameter checking ability, it can check + * "Invalid number of vertices" and "Invalid center vertex". */ + switch (mode) + { + case IGRAPH_WHEEL_OUT: + star_mode = IGRAPH_STAR_OUT; + break; + case IGRAPH_WHEEL_IN: + star_mode = IGRAPH_STAR_IN; + break; + case IGRAPH_WHEEL_MUTUAL: + star_mode = IGRAPH_STAR_MUTUAL; + break; + case IGRAPH_WHEEL_UNDIRECTED: + star_mode = IGRAPH_STAR_UNDIRECTED; + break; + default: + IGRAPH_ERROR("Invalid wheel graph mode.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_star(graph, n, star_mode, center)); + + /* If n <= 1, wheel graph is identical with star graph, + * no further processing is needed. */ + if (n <= 1) { + return IGRAPH_SUCCESS; + } + + /* Register the star for deallocation in case of error flow before + * the entire wheel is successfully created. */ + IGRAPH_FINALLY(igraph_destroy, graph); + + /* Add edges to the rim. As the rim (or cycle) has n - 1 vertices, + * it will have n - 1 edges. For MUTUAL mode, number of edges + * will be double. */ + if (mode == IGRAPH_WHEEL_MUTUAL) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&rim_edges, 4 * (n-1)); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&rim_edges, 2 * (n-1)); + } + + /* Assign first n-1 edges (MUTUAL will be handled later). */ + for (i = 0; i < n-2; i++) { + if ( i < center ) { + VECTOR(rim_edges)[2 * i] = i; + if ( i + 1 < center ) { + VECTOR(rim_edges)[2 * i + 1] = i + 1; + } else { + VECTOR(rim_edges)[2 * i + 1] = i + 2; + } + } else { + VECTOR(rim_edges)[2 * i] = i + 1; + VECTOR(rim_edges)[2 * i + 1] = i + 2; + } + } + + /* Assign the last edge (MUTUAL will be handled later). */ + if ( n - 2 < center ) { + VECTOR(rim_edges)[2 * n - 4] = n - 2; + } else { + VECTOR(rim_edges)[2 * n - 4] = n - 1; + } + if ( center > 0 ) { + VECTOR(rim_edges)[2 * n - 3] = 0; + } else { + VECTOR(rim_edges)[2 * n - 3] = 1; + } + + /* For MUTUAL mode, add reverse-direction edges. */ + if (mode == IGRAPH_WHEEL_MUTUAL) { + for (i=0; i < 2 * (n-1); i++) { + VECTOR(rim_edges)[4 * (n-1) - 1 - i] = VECTOR(rim_edges)[i]; + } + } + + /* Combine the rim into the star to make it a wheel graph. */ + IGRAPH_CHECK(igraph_add_edges(graph, &rim_edges, NULL)); + + igraph_vector_int_destroy(&rim_edges); + + /* 2 instead of 1 because the star graph is registered before. */ + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_square_lattice + * \brief Arbitrary dimensional square lattices. + * + * Creates d-dimensional square lattices of the given size. Optionally, + * the lattice can be made periodic, and the neighbors within a given + * graph distance can be connected. + * + * + * In the zero-dimensional case, the singleton graph is returned. + * + * + * The vertices of the resulting graph are ordered such that the + * index of the vertex at position (i_1, i_2, i_3, ..., i_d) + * in a lattice of size (n_1, n_2, ..., n_d) will be + * i_1 + n_1 * i_2 + n_1 * n_2 * i_3 + .... + * + * \param graph An uninitialized graph object. + * \param dimvector Vector giving the sizes of the lattice in each of + * its dimensions. The dimension of the lattice will be the + * same as the length of this vector. + * \param nei Integer value giving the distance (number of steps) + * within which two vertices will be connected. + * \param directed Boolean, whether to create a directed graph. + * If the \c mutual and \c circular arguments are not set to true, + * edges will be directed from lower-index vertices towards + * higher-index ones. + * \param mutual Boolean, if the graph is directed this gives whether + * to create all connections as mutual. + * \param periodic Boolean vector, defines whether the generated lattice is + * periodic along each dimension. The length of this vector must match + * the length of \p dimvector. This parameter may also be \c NULL, which + * implies that the lattice will not be periodic. + * \return Error code: + * \c IGRAPH_EINVAL: invalid (negative) dimension vector or mismatch + * between the length of the dimension vector and the periodicity vector. + * + * \sa \ref igraph_hypercube() to create a hypercube graph; \ref igraph_ring() + * to create a cycle graph or path graph; \ref igraph_triangular_lattice() + * and \ref igraph_hexagonal_lattice() to create other types of lattices; + * \ref igraph_regular_tree() to create a Bethe lattice. + * + * Time complexity: If \p nei is less than two then it is O(|V|+|E|) (as + * far as I remember), |V| and |E| are the number of vertices + * and edges in the generated graph. Otherwise it is O(|V|*d^k+|E|), d + * is the average degree of the graph, k is the \p nei argument. + */ +igraph_error_t igraph_square_lattice( + igraph_t *graph, const igraph_vector_int_t *dimvector, igraph_int_t nei, + igraph_bool_t directed, igraph_bool_t mutual, const igraph_vector_bool_t *periodic +) { + + igraph_int_t dims = igraph_vector_int_size(dimvector); + igraph_int_t no_of_nodes; + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t *coords, *weights; + igraph_int_t i, j; + int carry, pos; + int iter = 0; + + if (igraph_vector_int_any_smaller(dimvector, 0)) { + IGRAPH_ERROR("Invalid dimension vector.", IGRAPH_EINVAL); + } + + if (periodic && igraph_vector_bool_size(periodic) != dims) { + IGRAPH_ERRORF( + "Length of periodicity vector must match the length of the " + "dimension vector (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, dims + ); + } + + /* compute no. of nodes in overflow-safe manner */ + IGRAPH_CHECK(igraph_i_safe_vector_int_prod(dimvector, &no_of_nodes)); + + /* init coords & weights */ + + coords = IGRAPH_CALLOC(dims, igraph_int_t); + IGRAPH_CHECK_OOM(coords, "Lattice creation failed."); + IGRAPH_FINALLY(igraph_free, coords); + + weights = IGRAPH_CALLOC(dims, igraph_int_t); + IGRAPH_CHECK_OOM(weights, "Lattice creation failed."); + IGRAPH_FINALLY(igraph_free, weights); + + if (dims > 0) { + weights[0] = 1; + for (i = 1; i < dims; i++) { + weights[i] = weights[i - 1] * VECTOR(*dimvector)[i - 1]; + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + if (mutual && directed) { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(no_of_nodes, dims, &no_of_edges2); + IGRAPH_SAFE_MULT(no_of_edges2, 2, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + } else { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(no_of_nodes, dims, &no_of_edges2); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + } + +#define IS_PERIODIC(dim) ((periodic && VECTOR(*periodic)[dim])) + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + + /* Connect the current node to the "next" node along each dimension */ + for (j = 0; j < dims; j++) { + igraph_bool_t is_periodic = IS_PERIODIC(j); + + if (is_periodic|| coords[j] != VECTOR(*dimvector)[j] - 1) { + igraph_int_t new_nei; + if (coords[j] != VECTOR(*dimvector)[j] - 1) { + new_nei = i + weights[j] + 1; + } else { + new_nei = i - (VECTOR(*dimvector)[j] - 1) * weights[j] + 1; + } + if (new_nei != i + 1 && + (VECTOR(*dimvector)[j] != 2 || coords[j] != 1 || directed)) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, new_nei - 1); /* reserved */ + } + } /* if is_periodic || coords[j] */ + if (mutual && directed && (is_periodic || coords[j] != 0)) { + igraph_int_t new_nei; + if (coords[j] != 0) { + new_nei = i - weights[j] + 1; + } else { + new_nei = i + (VECTOR(*dimvector)[j] - 1) * weights[j] + 1; + } + if (new_nei != i + 1 && + (VECTOR(*dimvector)[j] != 2 || !is_periodic)) { + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, new_nei - 1); /* reserved */ + } + } /* if is_periodic || coords[0] */ + } /* for j= 2) { + IGRAPH_CHECK(igraph_connect_neighborhood(graph, nei, IGRAPH_ALL)); + } + + /* clean up */ + IGRAPH_FREE(coords); + IGRAPH_FREE(weights); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_ring + * \brief Creates a \em cycle graph or a \em path graph. + * + * A circular ring on \c n vertices is commonly known in graph + * theory as the cycle graph, and often denoted by C_n. + * Removing a single edge from the cycle graph C_n results + * in the path graph P_n. This function can generate both. + * + * + * When \p n is 1 or 2, the result may not be a simple graph: + * the one-cycle contains a self-loop and the undirected or reciprocally + * connected directed two-cycle contains parallel edges. + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param directed Whether to create a directed graph. + * All edges will be oriented in the same direction along + * the cycle or path. + * \param mutual Whether to create mutual edges in directed + * graphs. It is ignored for undirected graphs. + * \param circular Whether to create a closed ring (a cycle) + * or an open path. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|), the number of vertices in the graph. + * + * \sa \ref igraph_square_lattice() for generating more general + * (periodic or non-periodic) lattices. + * + * \example examples/simple/igraph_ring.c + */ +igraph_error_t igraph_ring(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + igraph_bool_t mutual, igraph_bool_t circular) { + + igraph_vector_int_t edges; + igraph_int_t no_of_edges, no_of_edges2; + igraph_int_t i; + + if (n < 0) { + IGRAPH_ERRORF("The number of vertices must be non-negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, n); + } + + if (n == 0) { + return igraph_empty(graph, 0, directed); + } + + no_of_edges = circular ? n : n-1; + if (directed && mutual) { + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges); + } + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + + if (directed && mutual) { + for (i=0; i < n-1; ++i) { + VECTOR(edges)[4*i] = i; + VECTOR(edges)[4*i+1] = i+1; + VECTOR(edges)[4*i+2] = i+1; + VECTOR(edges)[4*i+3] = i; + } + if (circular) { + /* Now i == n-1 */ + VECTOR(edges)[4*i] = i; + VECTOR(edges)[4*i+1] = 0; + VECTOR(edges)[4*i+2] = 0; + VECTOR(edges)[4*i+3] = i; + } + } else { + for (i=0; i < n-1; ++i) { + VECTOR(edges)[2*i] = i; + VECTOR(edges)[2*i+1] = i+1; + } + if (circular) { + /* Now i == n-1 */ + VECTOR(edges)[2*i] = i; + VECTOR(edges)[2*i+1] = 0; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_path_graph + * \brief A path graph \c P_n. + * + * Creates the path graph \c P_n on \p n vertices. + * + * + * This is a convenience wrapper to \ref igraph_ring(). + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param directed Whether to create a directed graph. + * \param mutual Whether to create mutual edges in directed + * graphs. It is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ +igraph_error_t igraph_path_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual) { + return igraph_ring(graph, n, directed, mutual, /* circular= */ false); +} + +/** + * \ingroup generators + * \function igraph_cycle_graph + * \brief A cycle graph \c C_n. + * + * Creates the cycle graph \c C_n on \p n vertices. + * + * + * When \p n is 1 or 2, the result may not be a simple graph: + * the one-cycle contains a self-loop and the undirected or reciprocally + * connected directed two-cycle contains parallel edges. + * + * + * This is a convenience wrapper to \ref igraph_ring(). + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param directed Whether to create a directed graph. + * \param mutual Whether to create mutual edges in directed + * graphs. It is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ +igraph_error_t igraph_cycle_graph( + igraph_t *graph, igraph_int_t n, + igraph_bool_t directed, igraph_bool_t mutual) { + return igraph_ring(graph, n, directed, mutual, /* circular= */ true); +} + +/** + * \ingroup generators + * \function igraph_kary_tree + * \brief Creates a k-ary tree in which almost all vertices have k children. + * + * To obtain a completely symmetric tree with \c l layers, where each + * vertex has precisely \p children descendants, use + * n = (children^(l+1) - 1) / (children - 1). + * Such trees are often called k-ary trees, where \c k refers + * to the number of children. + * + * + * Note that for n=0, the null graph is returned, + * which is not considered to be a tree by \ref igraph_is_tree(). + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param children Integer, the number of children of a vertex in the + * tree. + * \param type Constant, gives whether to create a directed tree, and + * if this is the case, also its orientation. Possible values: + * \clist + * \cli IGRAPH_TREE_OUT + * directed tree, the edges point + * from the parents to their children. + * \cli IGRAPH_TREE_IN + * directed tree, the edges point from + * the children to their parents. + * \cli IGRAPH_TREE_UNDIRECTED + * undirected tree. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * \c IGRAPH_INVMODE: invalid mode argument. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_regular_tree(), \ref igraph_symmetric_tree() and \ref igraph_star() + * for creating other regular structures; \ref igraph_from_prufer() and + * \ref igraph_tree_from_parent_vector() for creating arbitrary trees; + * \ref igraph_tree_game() for uniform random sampling of trees; + * \ref igraph_realize_degree_sequence() with \c IGRAPH_REALIZE_DEGSEQ_SMALLEST + * to create a tree with given degrees. + * + * \example examples/simple/igraph_kary_tree.c + */ +igraph_error_t igraph_kary_tree(igraph_t *graph, igraph_int_t n, igraph_int_t children, + igraph_tree_mode_t type) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t i, j; + igraph_int_t idx = 0; + igraph_int_t to = 1; + + if (n < 0) { + IGRAPH_ERROR("Number of vertices cannot be negative.", IGRAPH_EINVAL); + } + if (children <= 0) { + IGRAPH_ERROR("Number of children must be positive.", IGRAPH_EINVAL); + } + if (type != IGRAPH_TREE_OUT && type != IGRAPH_TREE_IN && + type != IGRAPH_TREE_UNDIRECTED) { + IGRAPH_ERROR("Invalid tree orientation type.", IGRAPH_EINVMODE); + } + + { + igraph_int_t no_of_edges2; + if (n > 0) { + IGRAPH_SAFE_MULT(n-1, 2, &no_of_edges2); + } else { + no_of_edges2 = 0; + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + i = 0; + if (type == IGRAPH_TREE_OUT) { + while (idx < 2 * (n - 1)) { + for (j = 0; j < children && idx < 2 * (n - 1); j++) { + VECTOR(edges)[idx++] = i; + VECTOR(edges)[idx++] = to++; + } + i++; + } + } else { + while (idx < 2 * (n - 1)) { + for (j = 0; j < children && idx < 2 * (n - 1); j++) { + VECTOR(edges)[idx++] = to++; + VECTOR(edges)[idx++] = i; + } + i++; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, type != IGRAPH_TREE_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_symmetric_tree + * \brief Creates a symmetric tree with the specified number of branches at each level. + * + * This function creates a tree in which all vertices at distance \c d from the + * root have \p branching_counts[d] children. + * + * \param graph Pointer to an uninitialized graph object. + * \param branches Vector detailing the number of branches at each level. + * \param type Constant, gives whether to create a directed tree, and + * if this is the case, also its orientation. Possible values: + * \clist + * \cli IGRAPH_TREE_OUT + * directed tree, the edges point + * from the parents to their children. + * \cli IGRAPH_TREE_IN + * directed tree, the edges point from + * the children to their parents. + * \cli IGRAPH_TREE_UNDIRECTED + * undirected tree. + * \endclist + * \return Error code: + * \c IGRAPH_INVMODE: invalid mode argument. + * \c IGRAPH_EINVAL: invalid number of children. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_kary_tree(), \ref igraph_regular_tree() and \ref igraph_star() + * for creating other regular tree structures; + * \ref igraph_from_prufer() for creating arbitrary trees; + * \ref igraph_tree_game() for uniform random sampling of trees. + * + * \example examples/simple/igraph_symmetric_tree.c + */ + +igraph_error_t igraph_symmetric_tree(igraph_t *graph, const igraph_vector_int_t *branches, + igraph_tree_mode_t type) { + + igraph_vector_int_t edges; + igraph_int_t j, k, temp, no_of_nodes, idx, parent, child, level_end; + igraph_int_t branching_counts_size = igraph_vector_int_size(branches); + + if (type != IGRAPH_TREE_OUT && type != IGRAPH_TREE_IN && type != IGRAPH_TREE_UNDIRECTED) { + IGRAPH_ERROR("Invalid tree orientation type.", IGRAPH_EINVMODE); + } + if (!igraph_vector_int_empty(branches) && igraph_vector_int_min(branches) <= 0) { + IGRAPH_ERROR("The number of branches must be positive at each level.", IGRAPH_EINVAL); + } + + /* Compute the number of vertices in the tree. */ + no_of_nodes = 1; + temp = 1; + for (j = 0; j < branching_counts_size; ++j) { + IGRAPH_SAFE_MULT(temp, VECTOR(*branches)[j], &temp); + IGRAPH_SAFE_ADD(no_of_nodes, temp, &no_of_nodes); + } + + /* Trees have precisely |E| = |V| - 1 edges. */ + { + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(no_of_nodes - 1, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + idx = 0; + + /* Current parent and child vertex ids. + * parent -> child edges will be added. */ + child = 1; + parent = 0; + for (k = 0; k < branching_counts_size; ++k) { + level_end = child; /* points to one past the last vertex of the current level of parents */ + while (parent < level_end) { + IGRAPH_ALLOW_INTERRUPTION(); + for (j = 0; j < VECTOR(*branches)[k]; j++) { + if (type == IGRAPH_TREE_IN) { + VECTOR(edges)[idx++] = child++; + VECTOR(edges)[idx++] = parent; + } else { + VECTOR(edges)[idx++] = parent; + VECTOR(edges)[idx++] = child++; + } + } + parent++; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, type != IGRAPH_TREE_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_regular_tree + * \brief Creates a regular tree. + * + * All vertices of a regular tree, except its leaves, have the same total degree \p k. + * This is different from a k-ary tree (\ref igraph_kary_tree()), where all + * vertices have the same number of children, thus the degre of the root is + * one less than the degree of the other internal vertices. Regular trees + * are also referred to as Bethe lattices. + * + * \param graph Pointer to an uninitialized graph object. + * \param h The height of the tree, i.e. the distance between the root and the leaves. + * \param k The degree of the regular tree. + * \param type Constant, gives whether to create a directed tree, and + * if this is the case, also its orientation. Possible values: + * \clist + * \cli IGRAPH_TREE_OUT + * directed tree, the edges point + * from the parents to their children. + * \cli IGRAPH_TREE_IN + * directed tree, the edges point from + * the children to their parents. + * \cli IGRAPH_TREE_UNDIRECTED + * undirected tree. + * \endclist + * + * \return Error code. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_kary_tree() to create k-ary tree where each vertex has the same + * number of children, i.e. out-degree, instead of the same total degree. + * \ref igraph_symmetric_tree() to use a different number of children at each level. + * + * \example examples/simple/igraph_regular_tree.c + */ + +igraph_error_t igraph_regular_tree(igraph_t *graph, igraph_int_t h, igraph_int_t k, igraph_tree_mode_t type) { + igraph_vector_int_t branching_counts; + + if (h < 1) { + IGRAPH_ERRORF("Height of regular tree must be positive, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, h); + } + if (k < 2 ) { + IGRAPH_ERRORF("Degree of regular tree must be at least 2, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, k); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&branching_counts, h); + igraph_vector_int_fill(&branching_counts, k-1); + if (h > 0) { + VECTOR(branching_counts)[0] += 1; + } + + IGRAPH_CHECK(igraph_symmetric_tree(graph, &branching_counts, type)); + + igraph_vector_int_destroy(&branching_counts); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_extended_chordal_ring + * \brief Create an extended chordal ring. + * + * An extended chordal ring is a cycle graph with additional chords + * connecting its vertices. + * + * Each row \c L of the matrix \p W specifies a set of chords to be + * inserted, in the following way: vertex \c i will connect to a vertex + * L[(i mod p)] steps ahead of it along the cycle, where + * \c p is the length of \c L. + * In other words, vertex \c i will be connected to vertex + * (i + L[(i mod p)]) mod nodes. If multiple edges are + * defined in this way, this will output a non-simple graph. The result + * can be simplified using \ref igraph_simplify(). + * + * + * See also Kotsis, G: Interconnection Topologies for Parallel Processing + * Systems, PARS Mitteilungen 11, 1-6, 1993. The igraph extended chordal + * rings are not identical to the ones in the paper. In igraph + * the matrix specifies which edges to add. In the paper, a condition is + * specified which should simultaneously hold between two endpoints and + * the reverse endpoints. + * + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param nodes Integer constant, the number of vertices in the + * graph. It must be at least 3. + * \param W The matrix specifying the extra edges. The number of + * columns should divide the number of total vertices. The elements + * are allowed to be negative. + * \param directed Whether the graph should be directed. + * \return Error code. + * + * \sa \ref igraph_ring(), \ref igraph_lcf(), \ref igraph_circulant() + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ +igraph_error_t igraph_extended_chordal_ring( + igraph_t *graph, igraph_int_t nodes, const igraph_matrix_int_t *W, + igraph_bool_t directed) { + igraph_vector_int_t edges; + igraph_int_t period = igraph_matrix_int_ncol(W); + igraph_int_t nrow = igraph_matrix_int_nrow(W); + igraph_int_t i, j, mpos = 0, epos = 0; + + if (nodes < 3) { + IGRAPH_ERROR("An extended chordal ring has at least 3 nodes.", IGRAPH_EINVAL); + } + + if (nodes % period != 0) { + IGRAPH_ERROR("The period (number of columns in W) should divide the number of nodes.", + IGRAPH_EINVAL); + } + + { + /* ecount = nodes + nodes * nrow */ + igraph_int_t no_of_edges2; + IGRAPH_SAFE_MULT(nodes, nrow, &no_of_edges2); + IGRAPH_SAFE_ADD(no_of_edges2, nodes, &no_of_edges2); + IGRAPH_SAFE_MULT(no_of_edges2, 2, &no_of_edges2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges2); + } + + for (i = 0; i < nodes - 1; i++) { + VECTOR(edges)[epos++] = i; + VECTOR(edges)[epos++] = i + 1; + } + VECTOR(edges)[epos++] = nodes - 1; + VECTOR(edges)[epos++] = 0; + + if (nrow > 0) { + for (i = 0; i < nodes; i++) { + for (j = 0; j < nrow; j++) { + igraph_int_t offset = MATRIX(*W, j, mpos); + igraph_int_t v = (i + offset) % nodes; + + if (v < 0) { + v += nodes; /* handle negative offsets */ + } + + VECTOR(edges)[epos++] = i; + VECTOR(edges)[epos++] = v; + + } + mpos++; if (mpos == period) { + mpos = 0; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hypercube + * \brief The n-dimensional hypercube graph. + * + * The hypercube graph \c Q_n has 2^n vertices and + * 2^(n-1) n edges. Two vertices are connected when the binary + * representations of their zero-based vertex IDs differs in precisely one bit. + * + * \param graph An uninitialized graph object. + * \param n The dimension of the hypercube graph. + * \param directed Whether the graph should be directed. Edges will point + * from lower index vertices towards higher index ones. + * \return Error code. + * + * \sa \ref igraph_square_lattice() + * + * Time complexity: O(2^n) + */ +igraph_error_t igraph_hypercube(igraph_t *graph, + igraph_int_t n, igraph_bool_t directed) { + + /* An n-dimensional hypercube graph has 2^n vertices and 2^(n-1)*n edges. + * The maximum possible dimension is calculated with the assumption that + * the largest possible edge count is no more than half IGRAPH_INTEGER_MAX, + * which is in fact the current limit. */ + + const igraph_int_t maxn = + (IGRAPH_INTEGER_SIZE - 1) - (igraph_int_t) ceil(log2(IGRAPH_INTEGER_SIZE)); + + if (n < 0) { + IGRAPH_ERROR("Hypercube dimension must not be negative.", IGRAPH_EINVAL); + } + + if (n > maxn) { + IGRAPH_ERRORF("The requested hypercube graph dimension (%" IGRAPH_PRId + ") is too high. It must be no greater than %" IGRAPH_PRId ".", + IGRAPH_EINVAL, n, maxn); + } + + /* Integer overflow is no longer a concern after the above check. */ + + const igraph_int_t vcount = (igraph_int_t) 1 << n; + const igraph_int_t ecount = n > 0 ? ((igraph_int_t) 1 << (n-1)) * n : 0; /* avoid UBSan warning */ + igraph_vector_int_t edges; + igraph_int_t p; + int iter = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*ecount); + + p = 0; + for (igraph_int_t v=0; v < vcount; v++) { + igraph_int_t bit = 1; + for (igraph_int_t i=0; i < n; i++) { + const igraph_int_t u = v ^ bit; + if (v < u) { + VECTOR(edges)[p++] = v; + VECTOR(edges)[p++] = u; + } + bit <<= 1; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 16); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, vcount, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/constructors/trees.c b/src/constructors/trees.c new file mode 100644 index 0000000..880de40 --- /dev/null +++ b/src/constructors/trees.c @@ -0,0 +1,161 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_vector.h" + +/** + * \function igraph_tree_from_parent_vector + * \brief Constructs a tree or forest from a vector encoding the parent of each vertex. + * + * Rooted trees and forests are conveniently represented using a \p parents + * vector where the ID of the parent of vertex \c v is stored in parents[v]. + * This function serves to construct an igraph graph from a parent vector representation. + * The result is guaranteed to be a forest or a tree. If the \p parents vector + * is found to encode a cycle or a self-loop, an error is raised. + * + * + * Several igraph functions produce such vectors, such as graph traversal + * functions (\ref igraph_bfs() and \ref igraph_dfs()), shortest path functions + * that construct a shortest path tree, as well as some other specialized + * functions like \ref igraph_dominator_tree() or \ref igraph_cohesive_blocks(). + * Vertices which do not have parents (i.e. roots) get a negative entry in the + * \p parents vector. + * + * + * Use \ref igraph_bfs() or \ref igraph_dfs() to convert a forest into a parent + * vector representation. For trees, i.e. forests with a single root, it is + * more convenient to use \ref igraph_bfs_simple(). + * + * \param graph Pointer to an uninitialized graph object. + * \param parents The parent vector. parents[v] is the ID of + * the parent vertex of \c v. parents[v] < 0 indicates that + * \c v does not have a parent. + * \param type Constant, gives whether to create a directed tree, and + * if this is the case, also its orientation. Possible values: + * \clist + * \cli IGRAPH_TREE_OUT + * directed tree, the edges point from the parents to their children. + * \cli IGRAPH_TREE_IN + * directed tree, the edges point from the children to their parents. + * \cli IGRAPH_TREE_UNDIRECTED undirected tree. + * \endclist + * \return Error code. + * + * \sa \ref igraph_bfs(), \ref igraph_bfs_simple() for back-conversion; + * \ref igraph_from_prufer() for creating trees from Prüfer sequences; + * \ref igraph_is_tree() and \ref igraph_is_forest() to check if a graph + * is a tree or forest. + * + * Time complexity: O(n) where n is the length of \p parents. + */ +igraph_error_t igraph_tree_from_parent_vector( + igraph_t *graph, + const igraph_vector_int_t *parents, + igraph_tree_mode_t type) { + + const igraph_int_t no_of_nodes = igraph_vector_int_size(parents); + igraph_vector_int_t seen; + igraph_vector_int_t edges; + igraph_bool_t directed, intree; + + switch (type) { + case IGRAPH_TREE_OUT: + directed = true; intree = false; break; + case IGRAPH_TREE_IN: + directed = true; intree = true; break; + case IGRAPH_TREE_UNDIRECTED: + directed = false; intree = true; break; + default: + IGRAPH_ERROR("Invalid tree mode.", IGRAPH_EINVAL); + } + + /* Catch null graph case */ + if (no_of_nodes == 0) { + return igraph_empty(graph, 0, directed); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&seen, no_of_nodes); + + /* A tree has no_of_nodes - 1 edges but a forest has fewer. In order to support + * the use case of extracting small sub-trees of large graphs, we only reserve + * the full amount of memory needed for a tree when the graph is small. + * This also eliminates the need to check for integer overflow. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_nodes > 1024 ? 2048 : 2*(no_of_nodes-1)); + igraph_vector_int_clear(&edges); + + igraph_int_t c=1; + for (igraph_int_t i=0; i < no_of_nodes; i++) { + igraph_int_t v = i; + + if (VECTOR(seen)[v]) continue; + + while (true) { + igraph_int_t u; + + VECTOR(seen)[v] = c; /* mark v as seen in the current round */ + u = VECTOR(*parents)[v]; + + if (u < 0) { + break; /* v is a root, stop traversal */ + } + if (u >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex ID in parent vector.", IGRAPH_EINVVID); + } + + if (intree) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, u)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, u)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v)); + } + + if (VECTOR(seen)[u]) { + if (VECTOR(seen)[u] == c) { + /* u was already seen in the current round, we found a cycle. + * We distinguish between self-loops, i.e. 1-cycles, and longer + * cycles in order to make the error message more useful. */ + IGRAPH_ERROR( + u==v + ? "Found a self-loop while constructing tree from parent vector." + : "Found a cycle while constructing tree from parent vector.", + IGRAPH_EINVAL); + } + break; /* u was seen in a previous round, stop traversal */ + } + + v = u; + } + + c++; + } + + igraph_vector_int_destroy(&seen); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/core/bitset.c b/src/core/bitset.c new file mode 100644 index 0000000..1ffe902 --- /dev/null +++ b/src/core/bitset.c @@ -0,0 +1,817 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "string.h" + +#include "igraph_bitset.h" +#include "igraph_memory.h" + +igraph_int_t igraph_i_ctz32(igraph_uint_t x) { +#ifdef HAVE__BITSCANFORWARD + unsigned long index; + return _BitScanForward(&index, x) ? index : 32; +#else + for (igraph_int_t i = 0; i < 32; ++i) { + if (IGRAPH_BIT_MASK(i) & x) { + return i; + } + } + return 32; +#endif +} + +igraph_int_t igraph_i_clz32(igraph_uint_t x) { +#ifdef HAVE_BITSCANREVERSE + unsigned long index; + return _BitScanReverse(&index, x) ? 31 - index : 32; +#else + for (igraph_int_t i = 31; i >= 0; --i) { + if (IGRAPH_BIT_MASK(i) & x) { + return 31 - i; + } + } + return 32; +#endif +} + +igraph_int_t igraph_i_popcnt(igraph_uint_t x) { + igraph_int_t result = 0; + while (x) { + result++; + x = x & (x - 1); + } + return result; +} + +/* Fallbacks for 64-bit word (and igraph_int_t/igraph_uint_t) size */ +#if IGRAPH_INTEGER_SIZE == 64 +igraph_int_t igraph_i_ctz64(igraph_uint_t x) { +#ifdef HAVE_BITSCANFORWARD64 + unsigned long index; + return _BitScanForward64(&index, x) ? index : 64; +#else + for (igraph_int_t i = 0; i < 64; ++i) { + if (IGRAPH_BIT_MASK(i) & x) { + return i; + } + } + return 64; +#endif +} + +igraph_int_t igraph_i_clz64(igraph_uint_t x) { +#ifdef HAVE_BITSCANREVERSE64 + unsigned long index; + return _BitScanReverse64(&index, x) ? 63 - index : 64; +#else + for (igraph_int_t i = 63; i >= 0; --i) { + if (IGRAPH_BIT_MASK(i) & x) { + return 63 - i; + } + } + return 64; +#endif +} +#endif /* IGRAPH_INTEGER_SIZE == 64 */ + +/** + * \ingroup bitset + * \section about_igraph_bitset_t_objects About \type igraph_bitset_t objects + * + * The \type igraph_bitset_t data type is a simple and efficient + * interface to arrays containing boolean values. It is similar to + * the \type bitset template in the C++ standard library, although the main + * difference being the C++ version's size is initialized at compile time. + * + * The \type igraph_bitset_t type and use O(n/w) space + * to store n elements, where w is the bit width of \type igraph_int_t, + * the integer type used throughout the library (either 32 or 64). + * Sometimes they use more, this is because bitsets can + * shrink, but even if they shrink, the current implementation does not free a + * single bit of memory. + * + * The elements in an \type igraph_bitset_t object and its variants are + * indexed from zero, we follow the usual C convention here. Bitsets are indexed + * from right to left, meaning index 0 is the least significant bit and index + * n - 1 is the most significant bit. + * + * The elements of a bitset always occupy a single block of + * memory, the starting address of this memory block can be queried + * with the \ref VECTOR() macro. This way, bitset objects can be used + * with standard mathematical libraries, like the GNU Scientific + * Library. + * + * Note that while the interface of bitset functions is similar to + * igraph's vector functions, there is one major difference: bitset functions + * such as \ref igraph_bitset_and() do not verify that that sizes of input + * parameters are compatible, and do not automatically resize the output + * parameter. Doing so is the responsibility of the user. + */ + +/** + * \ingroup bitset + * \section igraph_bitset_constructors_and_destructors Constructors and + * destructors + * + * \type igraph_bitset_t objects have to be initialized before using + * them, this is analogous to calling a constructor on them. There are two + * \type igraph_bitset_t constructors, for your convenience. + * \ref igraph_bitset_init() is the basic constructor, it + * creates a bitset of the given length, filled with zeros. + * \ref igraph_bitset_init_copy() creates a new identical copy + * of an already existing and initialized bitset. + * + * If a \type igraph_bitset_t object is not needed any more, it + * should be destroyed to free its allocated memory by calling the + * \type igraph_bitset_t destructor, \ref igraph_bitset_destroy(). + */ + +/** + * \ingroup bitset + * \function igraph_bitset_init + * \brief Initializes a bitset object (constructor). + * + * + * Every bitset needs to be initialized before it can be used, and + * there are a number of initialization functions or otherwise called + * constructors. This function constructs a bitset of the given size and + * initializes each entry to 0. + * + * + * Every bitset object initialized by this function should be + * destroyed (ie. the memory allocated for it should be freed) when it + * is not needed anymore, the \ref igraph_bitset_destroy() function is + * responsible for this. + * + * \param bitset Pointer to a not yet initialized bitset object. + * \param size The size of the bitset. + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, the amount of + * \quote time \endquote required to allocate + * O(n/w) elements, + * n is the number of elements. + * w is the word size of the machine (32 or 64). + */ + +igraph_error_t igraph_bitset_init(igraph_bitset_t *bitset, igraph_int_t size) { + igraph_int_t alloc_size = IGRAPH_BIT_NSLOTS(size); + bitset->stor_begin = IGRAPH_CALLOC(alloc_size, igraph_uint_t); + IGRAPH_CHECK_OOM(bitset->stor_begin, "Cannot initialize bitset."); + bitset->size = size; + bitset->stor_end = bitset->stor_begin + alloc_size; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup bitset + * \function igraph_bitset_destroy + * \brief Destroys a bitset object. + * + * All bitsets initialized by \ref igraph_bitset_init() should be properly + * destroyed by this function. A destroyed bitset needs to be + * reinitialized by \ref igraph_bitset_init() or + * another constructor. + * + * \param bitset Pointer to the (previously initialized) bitset object to + * destroy. + * + * Time complexity: operating system dependent. + */ + +void igraph_bitset_destroy(igraph_bitset_t *bitset) { + IGRAPH_ASSERT(bitset != NULL); + IGRAPH_FREE(bitset->stor_begin); + bitset->size = 0; +} + +/** + * \ingroup bitset + * \function igraph_bitset_init_copy + * \brief Initializes a bitset from another bitset object (constructor). + * + * The contents of the existing bitset object will be copied to + * the new one. + * + * \param dest Pointer to a not yet initialized bitset object. + * \param src The original bitset object to copy. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually + * O(n/w), + * n is the size of the bitset, + * w is the word size of the machine (32 or 64). + */ + +igraph_error_t igraph_bitset_init_copy(igraph_bitset_t *dest, const igraph_bitset_t *src) { + IGRAPH_ASSERT(src != NULL); + IGRAPH_ASSERT(src->stor_begin != NULL); + IGRAPH_CHECK(igraph_bitset_init(dest, src->size)); + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = VECTOR(*src)[i]; + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup bitset + * \function igraph_bitset_update + * \brief Update a bitset from another one. + * + * The size and contents of \p dest will be identical to that of \p src. + * + * \param dest Pointer to an initialized bitset object. This will be updated. + * \param src The bitset to update from. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually + * O(n/w), + * n is the size of the bitset, + * w is the word size of the machine (32 or 64). + */ + +igraph_error_t igraph_bitset_update(igraph_bitset_t *dest, const igraph_bitset_t *src) { + IGRAPH_ASSERT(src != NULL); + IGRAPH_ASSERT(src->stor_begin != NULL); + IGRAPH_CHECK(igraph_bitset_reserve(dest, src->size)); + dest->size = src->size; + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = VECTOR(*src)[i]; + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup bitset + * \function igraph_bitset_capacity + * \brief Returns the allocated capacity of the bitset. + * + * Note that this might be different from the size of the bitset (as + * queried by \ref igraph_bitset_size()), and specifies how many elements + * the bitset can hold, without reallocation. + * + * \param bitset Pointer to the (previously initialized) bitset object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_bitset_size(). + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_bitset_capacity(const igraph_bitset_t *bitset) { + return IGRAPH_INTEGER_SIZE * (bitset->stor_end - bitset->stor_begin); +} + +/** + * \ingroup bitset + * \function igraph_bitset_size + * \brief Returns the length of the bitset. + * + * \param bitset The bitset object + * \return The size of the bitset. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_bitset_size(const igraph_bitset_t *bitset) { + return bitset->size; +} + +/** + * \ingroup bitset + * \function igraph_bitset_reserve + * \brief Reserves memory for a bitset. + * + * \a igraph bitsets are flexible, they can grow and + * shrink. Growing + * however occasionally needs the data in the bitset to be copied. + * In order to avoid this, you can call this function to reserve space for + * future growth of the bitset. + * + * + * Note that this function does \em not change the size of the + * bitset. Let us see a small example to clarify things: if you + * reserve space for 100 elements and the size of your + * bitset was (and still is) 60, then you can surely add additional 40 + * elements to your bitset before it will be copied. + * + * \param bitset The bitset object. + * \param capacity The new \em allocated size of the bitset. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n/w), + * n is the new allocated size of the bitset, + * w is the word size of the machine (32 or 64). + */ + +igraph_error_t igraph_bitset_reserve(igraph_bitset_t *bitset, igraph_int_t capacity) { + igraph_int_t current_capacity; + igraph_uint_t *tmp; + + IGRAPH_ASSERT(bitset != NULL); + IGRAPH_ASSERT(bitset->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + current_capacity = igraph_bitset_capacity(bitset); + + if (IGRAPH_BIT_NSLOTS(capacity) <= IGRAPH_BIT_NSLOTS(current_capacity)) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(bitset->stor_begin, IGRAPH_BIT_NSLOTS(capacity), igraph_uint_t); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for bitset."); + + bitset->stor_begin = tmp; + bitset->stor_end = bitset->stor_begin + IGRAPH_BIT_NSLOTS(capacity); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup bitset + * \function igraph_bitset_resize + * \brief Resizes the bitset. + * + * Note that this function does not free any memory, just sets the + * size of the bitset to the given one. It may, on the other hand, + * allocate more memory if the new size is larger than the previous + * one. In this case the newly appeared elements in the bitset are + * set to zero. + * + * \param bitset The bitset object + * \param new_size The new size of the bitset. + * \return Error code, + * \c IGRAPH_ENOMEM if there is not enough + * memory. Note that this function \em never returns an error + * if the bitset is made smaller. + * \sa \ref igraph_bitset_reserve() for allocating memory for future + * extensions of a bitset. + * + * Time complexity: O(1) if the new + * size is smaller, operating system dependent if it is larger. In the + * latter case it is usually around + * O(n/w), + * n is the new size of the bitset, + * w is the word size of the machine (32 or 64). + */ + +igraph_error_t igraph_bitset_resize(igraph_bitset_t *bitset, igraph_int_t new_size) { + IGRAPH_ASSERT(bitset != NULL); + IGRAPH_ASSERT(bitset->stor_begin != NULL); + IGRAPH_CHECK(igraph_bitset_reserve(bitset, new_size)); + + if (new_size > bitset->size) { + for (igraph_int_t i = bitset->size; i % IGRAPH_INTEGER_SIZE != 0; ++i) { + IGRAPH_BIT_CLEAR(*bitset, i); + } + memset(bitset->stor_begin + IGRAPH_BIT_NSLOTS(bitset->size), 0, + sizeof(igraph_uint_t) * (IGRAPH_BIT_NSLOTS(new_size) - IGRAPH_BIT_NSLOTS(bitset->size))); + } + bitset->size = new_size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup bitset + * \function igraph_bitset_popcount + * \brief The population count of the bitset. + * + * Returns the number of set bits, also called the population count, + * of the bitset. + * + * \param bitset The bitset object + * \return The population count of the bitset. + * + * Time complexity: O(n/w). + */ + +igraph_int_t igraph_bitset_popcount(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? ~zero : ((one << final_block_size) - 1); + igraph_int_t count = 0; + + for (igraph_int_t i = 0; i + 1 < slots; ++i) { + count += IGRAPH_POPCOUNT(VECTOR(*bitset)[i]); + } + if (bitset->size) { + count += IGRAPH_POPCOUNT(mask & VECTOR(*bitset)[slots - 1]); + } + + return count; +} + +/** + * \ingroup bitset + * \function igraph_bitset_countl_zero + * \brief The number of leading zeros in the bitset. + * + * Returns the number of leading (starting at the most significant bit) + * zeros in the bitset before the first one is encountered. If the bitset + * is all zeros, then its size is returned. + * + * \param bitset The bitset object + * \return The number of leading zeros in the bitset. + * + * Time complexity: O(n/w). + */ + +igraph_int_t igraph_bitset_countl_zero(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t padding = IGRAPH_INTEGER_SIZE - final_block_size; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? ~zero : ((one << final_block_size) - one); + + if (bitset->size && (mask & VECTOR(*bitset)[slots - 1]) != 0) { + return IGRAPH_CLZ(mask & VECTOR(*bitset)[slots - 1]) - padding; + } + for (igraph_int_t i = 1; i < slots; ++i) { + if (VECTOR(*bitset)[slots - i - 1] != 0) { + return IGRAPH_INTEGER_SIZE * i + IGRAPH_CLZ(VECTOR(*bitset)[slots - i - 1]) - padding; + } + } + + return bitset->size; +} + +/** + * \ingroup bitset + * \function igraph_bitset_countl_one + * \brief The number of leading ones in the bitset. + * + * Returns the number of leading ones (starting at the most significant bit) + * in the bitset before the first zero is encountered. + * If the bitset is all ones, then its size is returned. + * + * \param bitset The bitset object + * \return The number of leading ones in the bitset. + * + * Time complexity: O(n/w). + */ + +igraph_int_t igraph_bitset_countl_one(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t padding = IGRAPH_INTEGER_SIZE - final_block_size; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? zero : ~((one << final_block_size) - one); + + if (bitset->size && (mask | VECTOR(*bitset)[slots - 1]) != ~zero) { + return IGRAPH_CLO(mask | VECTOR(*bitset)[slots - 1]) - padding; + } + for (igraph_int_t i = 1; i < slots; ++i) { + if (VECTOR(*bitset)[slots - i - 1] != ~zero) { + return IGRAPH_INTEGER_SIZE * i + IGRAPH_CLO(VECTOR(*bitset)[slots - i - 1]) - padding; + } + } + + return bitset->size; +} + +/** + * \ingroup bitset + * \function igraph_bitset_countr_zero + * \brief The number of trailing zeros in the bitset. + * + * Returns the number of trailing (starting at the least significant bit) + * zeros in the bitset before the first one is encountered. + * If the bitset is all zeros, then its size is returned. + * + * \param bitset The bitset object + * \return The number of trailing zeros in the bitset. + * + * Time complexity: O(n/w). + */ + +igraph_int_t igraph_bitset_countr_zero(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? ~zero : ((one << final_block_size) - one); + + for (igraph_int_t i = 0; i + 1 < slots; ++i) { + if (VECTOR(*bitset)[i] != zero) { + return IGRAPH_INTEGER_SIZE * i + IGRAPH_CTZ(VECTOR(*bitset)[i]); + } + } + if (bitset->size && (mask & VECTOR(*bitset)[slots - 1]) != zero) { + return IGRAPH_INTEGER_SIZE * (slots - 1) + IGRAPH_CTZ(mask & VECTOR(*bitset)[slots - 1]); + } + + return bitset->size; +} + +/** + * \ingroup bitset + * \function igraph_bitset_countr_one + * \brief The number of trailing ones in the bitset. + * + * Returns the number of trailing ones (starting at the least significant bit) + * in the bitset before the first zero is encountered. + * If the bitset is all ones, then its size is returned. + * + * \param bitset The bitset object + * \return The number of trailing ones in the bitset. + * + * Time complexity: O(n/w). + */ + +igraph_int_t igraph_bitset_countr_one(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? zero : ~((one << final_block_size) - one); + + for (igraph_int_t i = 0; i + 1 < slots; ++i) { + if (VECTOR(*bitset)[i] != ~zero) { + return IGRAPH_INTEGER_SIZE * i + IGRAPH_CTO(VECTOR(*bitset)[i]); + } + } + if (bitset->size && (mask | VECTOR(*bitset)[slots - 1]) != ~zero) { + return IGRAPH_INTEGER_SIZE * (slots - 1) + IGRAPH_CTO(mask | VECTOR(*bitset)[slots - 1]); + } + + return bitset->size; +} + +/** + * \ingroup bitset + * \function igraph_bitset_is_all_zero + * \brief Are all bits zeros? + * + * \param bitset The bitset object to test. + * \return True if none of the bits are set. + * + * Time complexity: O(n/w). + */ + +igraph_bool_t igraph_bitset_is_all_zero(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? ~zero : ((one << final_block_size) - one); + + for (igraph_int_t i = 0; i < slots - 1; i++) { + if (VECTOR(*bitset)[i] != zero) { + return false; + } + } + if (bitset->size && (mask & VECTOR(*bitset)[slots - 1]) != zero) { + return false; + } + return true; +} + +/** + * \ingroup bitset + * \function igraph_bitset_is_all_one + * \brief Are all bits ones? + * + * \param bitset The bitset object to test. + * \return True if all of the bits are set. + * + * Time complexity: O(n/w). + */ + +igraph_bool_t igraph_bitset_is_all_one(const igraph_bitset_t *bitset) { + const igraph_int_t final_block_size = bitset->size % IGRAPH_INTEGER_SIZE ? bitset->size % IGRAPH_INTEGER_SIZE : IGRAPH_INTEGER_SIZE; + const igraph_int_t slots = IGRAPH_BIT_NSLOTS(bitset->size); + const igraph_uint_t one = 1, zero = 0; /* to avoid the need to cast 1 and 0 to igraph_uint_t below */ + const igraph_uint_t mask = final_block_size == IGRAPH_INTEGER_SIZE ? zero : ~((one << final_block_size) - one); + + for (igraph_int_t i = 0; i < slots - 1; i++) { + if (VECTOR(*bitset)[i] != ~zero) { + return false; + } + } + if (bitset->size && (mask | VECTOR(*bitset)[slots - 1]) != ~zero) { + return false; + } + return true; +} + +/** + * \ingroup bitset + * \function igraph_bitset_is_any_zero + * \brief Are any bits zeros? + * + * \param bitset The bitset object to test. + * \return True if at least one bit is zero. + * + * Time complexity: O(n/w). + */ + +igraph_bool_t igraph_bitset_is_any_zero(const igraph_bitset_t *bitset) { + return ! igraph_bitset_is_all_one(bitset); +} + +/** + * \ingroup bitset + * \function igraph_bitset_is_any_one + * \brief Are any bits ones? + * + * \param bitset The bitset object to test. + * \return True if at least one bit is one. + * + * Time complexity: O(n/w). + */ + +igraph_bool_t igraph_bitset_is_any_one(const igraph_bitset_t *bitset) { + return ! igraph_bitset_is_all_zero(bitset); +} + +/** + * \ingroup bitset + * \function igraph_bitset_or + * \brief Bitwise OR of two bitsets. + * + * Applies a bitwise or to the contents of two bitsets and stores it in an + * already initialized bitset. The destination bitset may be equal to one + * (or even both) of the sources. When working with bitsets, it is common + * that those created are of the same size fixed size. Therefore, this + * function does not check the sizes of the bitsets passed to it, the caller + * must do so if necessary. + * + * \param dest The bitset object where the result is stored + * \param src1 A bitset. Must have have same size as \p dest. + * \param src2 A bitset. Must have have same size as \p dest. + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_or(igraph_bitset_t *dest, + const igraph_bitset_t *src1, const igraph_bitset_t *src2) { + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = VECTOR(*src1)[i] | VECTOR(*src2)[i]; + } +} + +/** + * \ingroup bitset + * \function igraph_bitset_and + * \brief Bitwise AND of two bitsets. + * + * Applies a bitwise and to the contents of two bitsets and stores it in an + * already initialized bitset. The destination bitset may be equal to one + * (or even both) of the sources. When working with bitsets, it is common + * that those created are of the same size fixed size. Therefore, this + * function does not check the sizes of the bitsets passed to it, the caller + * must do so if necessary. + * + * \param dest The bitset object where the result is stored + * \param src1 A bitset. Must have have same size as \p dest. + * \param src2 A bitset. Must have have same size as \p dest. + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_and(igraph_bitset_t *dest, const igraph_bitset_t *src1, const igraph_bitset_t *src2) { + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = VECTOR(*src1)[i] & VECTOR(*src2)[i]; + } +} + +/** + * \ingroup bitset + * \function igraph_bitset_xor + * \brief Bitwise XOR of two bitsets. + * + * Applies a bitwise xor to the contents of two bitsets and stores it in + * an already initialized bitset. The destination bitset may be equal to + * one (or even both) of the sources. When working with bitsets, it is common + * that those created are of the same size fixed size. Therefore, this + * function does not check the sizes of the bitsets passed to it, the caller + * must do so if necessary. + * + * \param dest The bitset object where the result is stored + * \param src1 A bitset. Must have have same size as \p dest. + * \param src2 A bitset. Must have have same size as \p dest. + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_xor(igraph_bitset_t *dest, + const igraph_bitset_t *src1, const igraph_bitset_t *src2) { + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = VECTOR(*src1)[i] ^ VECTOR(*src2)[i]; + } +} + +/** + * \ingroup bitset + * \function igraph_bitset_not + * \brief Bitwise negation of a bitset. + * + * Applies a bitwise not to the contents of a bitset and stores it in an + * already initialized bitset. The destination bitset may be equal to the + * source. When working with bitsets, it is common that those created are + * of the same size fixed size. Therefore, this function does not check the + * sizes of the bitsets passed to it, the caller must do so if necessary. + * + * \param dest The bitset object where the result is stored + * \param src A bitset. Must have have same size as \p dest. + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_not(igraph_bitset_t *dest, const igraph_bitset_t *src) { + for (igraph_int_t i = 0; i < IGRAPH_BIT_NSLOTS(dest->size); ++i) { + VECTOR(*dest)[i] = ~VECTOR(*src)[i]; + } +} + +/** + * \ingroup bitset + * \function igraph_bitset_fill + * \brief Fills a bitset with a constant value. + * + * Sets all bits of a bitset to the same value. + * + * \param bitset The bitset object to modify. + * \param value The value to set for all bits. + * + * \sa \ref igraph_bitset_null() + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_fill(igraph_bitset_t *bitset, igraph_bool_t value) { + memset(bitset->stor_begin, + value ? ~ (unsigned char) 0 : 0, + sizeof(igraph_uint_t) * IGRAPH_BIT_NSLOTS(bitset->size)); +} + +/** + * \ingroup bitset + * \function igraph_bitset_null + * \brief Clears all bits in a bitset. + * + * \param bitset The bitset object to clear all bits in. + * + * \sa \ref igraph_bitset_fill() + * + * Time complexity: O(n/w). + */ + +void igraph_bitset_null(igraph_bitset_t *bitset) { + igraph_bitset_fill(bitset, false); +} + +/** + * \ingroup bitset + * \function igraph_bitset_fprint + * \brief Prints the bits of a bitset. + * + * Outputs the contents of a bitset to a file. + * The bitset is written from index n-1 to index 0, left to right, + * such that index 0 is the least significant bit and index n-1 is + * the most significant bit, where n is the size of the bitset. + * This is the reverse of how sequential structures are usually written, + * such as vectors, but consistent with the bitset being a binary + * representation of an integer and how they are usually written. + * + * + * No newline is printed at the end. + * + * \param bitset The bitset to be printed. + * \param file The file to be written. + * \return Error code. + * + * Time complexity: O(n). + */ +igraph_error_t igraph_bitset_fprint(const igraph_bitset_t *bitset, FILE *file) { + for (igraph_int_t i = bitset->size - 1; i >= 0; i--) { + fputc(IGRAPH_BIT_TEST(*bitset, i) ? '1' : '0', file); + } + return IGRAPH_SUCCESS; +} + +#ifndef USING_R +igraph_error_t igraph_bitset_print(const igraph_bitset_t *bitset) { + return igraph_bitset_fprint(bitset, stdout); +} +#endif diff --git a/src/core/bitset_list.c b/src/core/bitset_list.c new file mode 100644 index 0000000..c6dc79c --- /dev/null +++ b/src/core/bitset_list.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_bitset_list.h" + +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_types.h" + +#define BITSET_LIST +#define BASE_BITSET +#define CUSTOM_INIT_DESTROY +#include "igraph_pmt.h" +#include "typed_list.pmt" +#include "igraph_pmt_off.h" +#undef CUSTOM_INIT_DESTROY +#undef BASE_BITSET +#undef BITSET_LIST + +static igraph_error_t igraph_i_bitset_list_init_item( + const igraph_bitset_list_t* list, igraph_bitset_t* item +) { + IGRAPH_UNUSED(list); + return igraph_bitset_init(item, 0); +} + +static igraph_error_t igraph_i_bitset_list_copy_item( + igraph_bitset_t* dest, const igraph_bitset_t* source +) { + return igraph_bitset_init_copy(dest, source); +} + +static void igraph_i_bitset_list_destroy_item(igraph_bitset_t* item) { + igraph_bitset_destroy(item); +} diff --git a/src/core/buckets.c b/src/core/buckets.c new file mode 100644 index 0000000..28330b3 --- /dev/null +++ b/src/core/buckets.c @@ -0,0 +1,192 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "core/buckets.h" + +/* The igraph_buckets_t data structure can store at most 'size' + * unique integers in 'bsize' buckets. It has the following simple + * operations (in addition to _init() and _destroy(): + * - _add() adding an element to the given bucket. + * - _popmax() removing an element from the bucket with the highest + * id. + * Currently buckets work as stacks, last-in-first-out mode. + * - _empty() queries whether the buckets is empty. + * + * Internal representation: we use a vector to create single linked + * lists, and another vector that points to the starting element of + * each bucket. Zero means the end of the chain. So bucket i contains + * elements bptr[i], buckets[bptr[i]], buckets[buckets[bptr[i]]], + * etc., until a zero is found. + * + * We also keep the total number of elements in the buckets and the + * id of the non-empty bucket with the highest id, to facilitate the + * _empty() and _popmax() operations. + */ + +igraph_error_t igraph_buckets_init(igraph_buckets_t *b, igraph_int_t bsize, igraph_int_t size) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&b->bptr, bsize); + IGRAPH_VECTOR_INT_INIT_FINALLY(&b->buckets, size); + b->max = -1; b->no = 0; + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +void igraph_buckets_destroy(igraph_buckets_t *b) { + igraph_vector_int_destroy(&b->bptr); + igraph_vector_int_destroy(&b->buckets); +} + +igraph_int_t igraph_buckets_popmax(igraph_buckets_t *b) { + /* Precondition: there is at least a non-empty bucket */ + /* Search for the highest bucket first */ + igraph_int_t max; + while ( (max = VECTOR(b->bptr)[b->max]) == 0) { + b->max --; + } + VECTOR(b->bptr)[b->max] = VECTOR(b->buckets)[max - 1]; + b->no--; + + return max - 1; +} + +igraph_int_t igraph_buckets_pop(igraph_buckets_t *b, igraph_int_t bucket) { + igraph_int_t ret = VECTOR(b->bptr)[bucket] - 1; + VECTOR(b->bptr)[bucket] = VECTOR(b->buckets)[ret]; + b->no--; + return ret; +} + +igraph_bool_t igraph_buckets_empty(const igraph_buckets_t *b) { + return (b->no == 0); +} + +igraph_bool_t igraph_buckets_empty_bucket(const igraph_buckets_t *b, + igraph_int_t bucket) { + return VECTOR(b->bptr)[bucket] == 0; +} + +void igraph_buckets_add(igraph_buckets_t *b, igraph_int_t bucket, + igraph_int_t elem) { + + VECTOR(b->buckets)[elem] = VECTOR(b->bptr)[bucket]; + VECTOR(b->bptr)[bucket] = elem + 1; + if (bucket > b->max) { + b->max = bucket; + } + b->no++; +} + +void igraph_buckets_clear(igraph_buckets_t *b) { + igraph_vector_int_null(&b->bptr); + igraph_vector_int_null(&b->buckets); + b->max = -1; + b->no = 0; +} + +igraph_error_t igraph_dbuckets_init(igraph_dbuckets_t *b, igraph_int_t bsize, igraph_int_t size) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&b->bptr, bsize); + IGRAPH_VECTOR_INT_INIT_FINALLY(&b->next, size); + IGRAPH_VECTOR_INT_INIT_FINALLY(&b->prev, size); + b->max = -1; b->no = 0; + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +void igraph_dbuckets_destroy(igraph_dbuckets_t *b) { + igraph_vector_int_destroy(&b->bptr); + igraph_vector_int_destroy(&b->next); + igraph_vector_int_destroy(&b->prev); +} + +void igraph_dbuckets_clear(igraph_dbuckets_t *b) { + igraph_vector_int_null(&b->bptr); + igraph_vector_int_null(&b->next); + igraph_vector_int_null(&b->prev); + b->max = -1; + b->no = 0; +} + +igraph_int_t igraph_dbuckets_popmax(igraph_dbuckets_t *b) { + while ( VECTOR(b->bptr)[b->max] == 0) { + b->max--; + } + return igraph_dbuckets_pop(b, b->max); +} + +igraph_int_t igraph_dbuckets_pop(igraph_dbuckets_t *b, igraph_int_t bucket) { + igraph_int_t ret = VECTOR(b->bptr)[bucket] - 1; + igraph_int_t next = VECTOR(b->next)[ret]; + VECTOR(b->bptr)[bucket] = next; + if (next != 0) { + VECTOR(b->prev)[next - 1] = 0; + } + + b->no--; + return ret; +} + +igraph_bool_t igraph_dbuckets_empty(const igraph_dbuckets_t *b) { + return (b->no == 0); +} + +igraph_bool_t igraph_dbuckets_empty_bucket(const igraph_dbuckets_t *b, + igraph_int_t bucket) { + return VECTOR(b->bptr)[bucket] == 0; +} + +void igraph_dbuckets_add(igraph_dbuckets_t *b, igraph_int_t bucket, + igraph_int_t elem) { + igraph_int_t oldfirst = VECTOR(b->bptr)[bucket]; + VECTOR(b->bptr)[bucket] = elem + 1; + VECTOR(b->next)[elem] = oldfirst; + if (oldfirst != 0) { + VECTOR(b->prev)[oldfirst - 1] = elem + 1; + } + if (bucket > b->max) { + b->max = bucket; + } + b->no++; +} + +/* Remove an arbitrary element */ + +void igraph_dbuckets_delete(igraph_dbuckets_t *b, igraph_int_t bucket, + igraph_int_t elem) { + if (VECTOR(b->bptr)[bucket] == elem + 1) { + /* First element in bucket */ + igraph_int_t next = VECTOR(b->next)[elem]; + if (next != 0) { + VECTOR(b->prev)[next - 1] = 0; + } + VECTOR(b->bptr)[bucket] = next; + } else { + igraph_int_t next = VECTOR(b->next)[elem]; + igraph_int_t prev = VECTOR(b->prev)[elem]; + if (next != 0) { + VECTOR(b->prev)[next - 1] = prev; + } + if (prev != 0) { + VECTOR(b->next)[prev - 1] = next; + } + } + b->no--; +} diff --git a/src/core/buckets.h b/src/core/buckets.h new file mode 100644 index 0000000..6b86259 --- /dev/null +++ b/src/core/buckets.h @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CORE_BUCKETS_H +#define IGRAPH_CORE_BUCKETS_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* Buckets, needed for the maximum flow algorithm */ + +typedef struct igraph_buckets_t { + igraph_vector_int_t bptr; + igraph_vector_int_t buckets; + igraph_int_t max, no; +} igraph_buckets_t; + +igraph_error_t igraph_buckets_init(igraph_buckets_t *b, igraph_int_t bsize, igraph_int_t size); +void igraph_buckets_destroy(igraph_buckets_t *b); +void igraph_buckets_clear(igraph_buckets_t *b); +igraph_int_t igraph_buckets_popmax(igraph_buckets_t *b); +igraph_int_t igraph_buckets_pop(igraph_buckets_t *b, igraph_int_t bucket); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_buckets_empty(const igraph_buckets_t *b); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_buckets_empty_bucket(const igraph_buckets_t *b, + igraph_int_t bucket); +void igraph_buckets_add(igraph_buckets_t *b, igraph_int_t bucket, + igraph_int_t elem); + +typedef struct igraph_dbuckets_t { + igraph_vector_int_t bptr; + igraph_vector_int_t next, prev; + igraph_int_t max, no; +} igraph_dbuckets_t; + +igraph_error_t igraph_dbuckets_init(igraph_dbuckets_t *b, igraph_int_t bsize, igraph_int_t size); +void igraph_dbuckets_destroy(igraph_dbuckets_t *b); +void igraph_dbuckets_clear(igraph_dbuckets_t *b); +igraph_int_t igraph_dbuckets_popmax(igraph_dbuckets_t *b); +igraph_int_t igraph_dbuckets_pop(igraph_dbuckets_t *b, igraph_int_t bucket); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_dbuckets_empty(const igraph_dbuckets_t *b); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_dbuckets_empty_bucket(const igraph_dbuckets_t *b, + igraph_int_t bucket); +void igraph_dbuckets_add(igraph_dbuckets_t *b, igraph_int_t bucket, + igraph_int_t elem); +void igraph_dbuckets_delete(igraph_dbuckets_t *b, igraph_int_t bucket, + igraph_int_t elem); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/cutheap.c b/src/core/cutheap.c new file mode 100644 index 0000000..29d15cd --- /dev/null +++ b/src/core/cutheap.c @@ -0,0 +1,168 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" + +#include "core/cutheap.h" + +#define PARENT(x) ((x)/2) +#define LEFTCHILD(x) ((x)*2+1) +#define RIGHTCHILD(x) ((x)*2) +#define INACTIVE IGRAPH_INFINITY +#define UNDEFINED 0.0 +#define INDEXINC 1 + +static void igraph_i_cutheap_switch(igraph_i_cutheap_t *ch, + igraph_int_t hidx1, igraph_int_t hidx2) { + if (hidx1 != hidx2) { + igraph_int_t idx1 = VECTOR(ch->index)[hidx1]; + igraph_int_t idx2 = VECTOR(ch->index)[hidx2]; + + igraph_real_t tmp = VECTOR(ch->heap)[hidx1]; + VECTOR(ch->heap)[hidx1] = VECTOR(ch->heap)[hidx2]; + VECTOR(ch->heap)[hidx2] = tmp; + + VECTOR(ch->index)[hidx1] = idx2; + VECTOR(ch->index)[hidx2] = idx1; + + VECTOR(ch->hptr)[idx1] = hidx2 + INDEXINC; + VECTOR(ch->hptr)[idx2] = hidx1 + INDEXINC; + } +} + +static void igraph_i_cutheap_sink(igraph_i_cutheap_t *ch, igraph_int_t hidx) { + igraph_int_t size = igraph_vector_size(&ch->heap); + if (LEFTCHILD(hidx) >= size) { + /* leaf node */ + } else if (RIGHTCHILD(hidx) == size || + VECTOR(ch->heap)[LEFTCHILD(hidx)] >= + VECTOR(ch->heap)[RIGHTCHILD(hidx)]) { + /* sink to the left if needed */ + if (VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[LEFTCHILD(hidx)]) { + igraph_i_cutheap_switch(ch, hidx, LEFTCHILD(hidx)); + igraph_i_cutheap_sink(ch, LEFTCHILD(hidx)); + } + } else { + /* sink to the right */ + if (VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[RIGHTCHILD(hidx)]) { + igraph_i_cutheap_switch(ch, hidx, RIGHTCHILD(hidx)); + igraph_i_cutheap_sink(ch, RIGHTCHILD(hidx)); + } + } +} + +static void igraph_i_cutheap_shift_up(igraph_i_cutheap_t *ch, igraph_int_t hidx) { + if (hidx == 0 || VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[PARENT(hidx)]) { + /* at the top */ + } else { + igraph_i_cutheap_switch(ch, hidx, PARENT(hidx)); + igraph_i_cutheap_shift_up(ch, PARENT(hidx)); + } +} + +igraph_error_t igraph_i_cutheap_init(igraph_i_cutheap_t *ch, igraph_int_t nodes) { + ch->dnodes = nodes; + IGRAPH_VECTOR_INIT_FINALLY(&ch->heap, nodes); /* all zero */ + IGRAPH_CHECK(igraph_vector_int_init_range(&ch->index, 0, nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &ch->index); + IGRAPH_CHECK(igraph_vector_init_range(&ch->hptr, INDEXINC, nodes + INDEXINC)); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +void igraph_i_cutheap_destroy(igraph_i_cutheap_t *ch) { + igraph_vector_destroy(&ch->hptr); + igraph_vector_int_destroy(&ch->index); + igraph_vector_destroy(&ch->heap); +} + +igraph_bool_t igraph_i_cutheap_empty(igraph_i_cutheap_t *ch) { + return igraph_vector_empty(&ch->heap); +} + +/* Number of active vertices */ + +igraph_int_t igraph_i_cutheap_active_size(igraph_i_cutheap_t *ch) { + return igraph_vector_size(&ch->heap); +} + +/* Number of all (defined) vertices */ + +igraph_int_t igraph_i_cutheap_size(igraph_i_cutheap_t *ch) { + return ch->dnodes; +} + +igraph_real_t igraph_i_cutheap_maxvalue(igraph_i_cutheap_t *ch) { + return VECTOR(ch->heap)[0]; +} + +igraph_int_t igraph_i_cutheap_popmax(igraph_i_cutheap_t *ch) { + igraph_int_t size = igraph_vector_size(&ch->heap); + igraph_int_t maxindex = VECTOR(ch->index)[0]; + /* put the last element to the top */ + igraph_i_cutheap_switch(ch, 0, size - 1); + /* remove the last element */ + VECTOR(ch->hptr)[ igraph_vector_int_tail(&ch->index)] = INACTIVE; + igraph_vector_pop_back(&ch->heap); + igraph_vector_int_pop_back(&ch->index); + igraph_i_cutheap_sink(ch, 0); + + return maxindex; +} + +/* Update the value of an active vertex, if not active it will be ignored */ + +void igraph_i_cutheap_update( + igraph_i_cutheap_t *ch, igraph_int_t index, igraph_real_t add) { + igraph_real_t hidx = VECTOR(ch->hptr)[index]; + if (hidx != INACTIVE && hidx != UNDEFINED) { + igraph_int_t hidx2 = (hidx - INDEXINC); + /* printf("updating vertex %li, heap index %li\n", index, hidx2); */ + VECTOR(ch->heap)[hidx2] += add; + igraph_i_cutheap_sink(ch, hidx2); + igraph_i_cutheap_shift_up(ch, hidx2); + } +} + +/* Reset the value of all vertices to zero and make them active */ + +igraph_error_t igraph_i_cutheap_reset_undefine(igraph_i_cutheap_t *ch, igraph_int_t vertex) { + igraph_int_t i, j, n = igraph_vector_size(&ch->hptr); + /* undefine */ + VECTOR(ch->hptr)[vertex] = UNDEFINED; + ch->dnodes -= 1; + + IGRAPH_CHECK(igraph_vector_resize(&ch->heap, ch->dnodes)); + igraph_vector_null(&ch->heap); + + IGRAPH_CHECK(igraph_vector_int_resize(&ch->index, ch->dnodes)); + + j = 0; + for (i = 0; i < n; i++) { + if (VECTOR(ch->hptr)[i] != UNDEFINED) { + VECTOR(ch->index)[j] = i; + VECTOR(ch->hptr)[i] = j + INDEXINC; + j++; + } + } + + return IGRAPH_SUCCESS; +} diff --git a/src/core/cutheap.h b/src/core/cutheap.h new file mode 100644 index 0000000..30901d3 --- /dev/null +++ b/src/core/cutheap.h @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CORE_CUTHEAP_H +#define IGRAPH_CORE_CUTHEAP_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* Special maximum heap, needed for the minimum cut algorithm */ + +typedef struct igraph_i_cutheap_t { + igraph_vector_t heap; + igraph_vector_int_t index; + igraph_vector_t hptr; + igraph_int_t dnodes; +} igraph_i_cutheap_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_cutheap_init(igraph_i_cutheap_t *ch, igraph_int_t nodes); +IGRAPH_PRIVATE_EXPORT void igraph_i_cutheap_destroy(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_i_cutheap_empty(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_i_cutheap_active_size(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_i_cutheap_size(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_i_cutheap_maxvalue(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT igraph_int_t igraph_i_cutheap_popmax(igraph_i_cutheap_t *ch); +IGRAPH_PRIVATE_EXPORT void igraph_i_cutheap_update(igraph_i_cutheap_t *ch, igraph_int_t index, igraph_real_t add); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_cutheap_reset_undefine(igraph_i_cutheap_t *ch, igraph_int_t vertex); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/dqueue.c b/src/core/dqueue.c new file mode 100644 index 0000000..142f2bc --- /dev/null +++ b/src/core/dqueue.c @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_dqueue.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL diff --git a/src/core/dqueue.pmt b/src/core/dqueue.pmt new file mode 100644 index 0000000..e7803fc --- /dev/null +++ b/src/core/dqueue.pmt @@ -0,0 +1,430 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" + +#include /* memcpy & co. */ +#include + +/* Notes on the internal representation of dqueue: + * + * 'stor_begin' points at the beginning of the allocated storage. + * 'stor_end' points one past the allocated storage. + * + * 'begin' points at the first element of the queue contents. + * 'end' points one past the last element. + * + * The queue elements are stored "cyclically" within the allocated + * buffer, and arithmetic on 'begin' and 'end' is done modulo + * 'size = stor_end - stor_begin'. Thus the smallest valid value of + * 'begin' and 'end' is 'stor_begin'. Their largest valid value is + * 'stor_end - 1'. + * + * This means that 'begin == end' would be true both when the queue + * is full and when it is empty. To distinguish between these + * two situations, 'end' is set to NULL when the queue is empty. + */ + +/** + * \section igraph_dqueue + * + * This is the classic data type of the double ended queue. Most of + * the time it is used if a First-In-First-Out (FIFO) behavior is + * needed. See the operations below. + * + * + * + * \example examples/simple/dqueue.c + * + */ + +/** + * \ingroup dqueue + * \function igraph_dqueue_init + * \brief Initialize a double ended queue (deque). + * + * The queue will be always empty. + * + * \param q Pointer to an uninitialized deque. + * \param capacity How many elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p capacity). + */ + +igraph_error_t FUNCTION(igraph_dqueue, init)(TYPE(igraph_dqueue)* q, igraph_int_t capacity) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(capacity >= 0); + + if (capacity == 0) capacity = 1; + + q->stor_begin = IGRAPH_CALLOC(capacity, BASE); + IGRAPH_CHECK_OOM(q->stor_begin, "Cannot initialize dqueue."); + q->stor_end = q->stor_begin + capacity; + q->begin = q->stor_begin; + q->end = NULL; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_destroy + * \brief Destroy a double ended queue. + * + * \param q The queue to destroy. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_dqueue, destroy)(TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_FREE(q->stor_begin); /* sets to NULL */ +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_empty + * \brief Decide whether the queue is empty. + * + * \param q The queue. + * \return Boolean, true if \p q contains at least one element, + * false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_dqueue, empty)(const TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + return q->end == NULL; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_clear + * \brief Remove all elements from the queue. + * + * \param q The queue. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_dqueue, clear)(TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + q->begin = q->stor_begin; + q->end = NULL; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_full + * \brief Check whether the queue is full. + * + * If a queue is full the next \ref igraph_dqueue_push() operation will allocate + * more memory. + * + * \param q The queue. + * \return \c true if \p q is full, \c false otherwise. + * + * Time complecity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_dqueue, full)(TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + return q->begin == q->end; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_size + * \brief Number of elements in the queue. + * + * \param q The queue. + * \return Integer, the number of elements currently in the queue. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_dqueue, size)(const TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + if (q->end == NULL) { + return 0; + } else if (q->begin < q->end) { + return q->end - q->begin; + } else { + return q->stor_end - q->begin + q->end - q->stor_begin; + } +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_head + * \brief Head of the queue. + * + * The queue must contain at least one element. + * + * \param q The queue. + * \return The first element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, head)(const TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + IGRAPH_ASSERT(q->stor_end != NULL); /* queue is not empty */ + return *(q->begin); +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_back + * \brief Tail of the queue. + * + * The queue must contain at least one element. + * + * \param q The queue. + * \return The last element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, back)(const TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + IGRAPH_ASSERT(q->stor_end != NULL); /* queue is not empty */ + if (q->end == q->stor_begin) { + return *(q->stor_end - 1); + } + return *(q->end - 1); +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_pop + * \brief Remove the head. + * + * Removes and returns the first element in the queue. The queue must + * be non-empty. + * + * \param q The input queue. + * \return The first element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, pop)(TYPE(igraph_dqueue)* q) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + IGRAPH_ASSERT(q->stor_end != NULL); /* queue is not empty */ + BASE tmp = *(q->begin); + (q->begin)++; + if (q->begin == q->stor_end) { + q->begin = q->stor_begin; + } + if (q->begin == q->end) { + q->end = NULL; + } + + return tmp; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_pop_back + * \brief Removes the tail. + * + * Removes and returns the last element in the queue. The queue must + * be non-empty. + * + * \param q The queue. + * \return The last element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, pop_back)(TYPE(igraph_dqueue)* q) { + BASE tmp; + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + IGRAPH_ASSERT(q->stor_end != NULL); /* queue is not empty */ + if (q->end != q->stor_begin) { + tmp = *((q->end) - 1); + q->end = (q->end) - 1; + } else { + tmp = *((q->stor_end) - 1); + q->end = (q->stor_end) - 1; + } + if (q->begin == q->end) { + q->end = NULL; + } + + return tmp; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_push + * \brief Appends an element. + * + * Append an element to the end of the queue. + * + * \param q The queue. + * \param elem The element to append. + * \return Error code. + * + * Time complexity: O(1) if no memory allocation is needed, O(n), the + * number of elements in the queue otherwise. But note that by + * allocating always twice as much memory as the current size of the + * queue we ensure that n push operations can always be done in at + * most O(n) time. (Assuming memory allocation is at most linear.) + */ + +igraph_error_t FUNCTION(igraph_dqueue, push)(TYPE(igraph_dqueue)* q, BASE elem) { + IGRAPH_ASSERT(q != NULL); + IGRAPH_ASSERT(q->stor_begin != NULL); + if (q->begin != q->end) { + /* not full */ + if (q->end == NULL) { + q->end = q->begin; + } + *(q->end) = elem; + (q->end)++; + if (q->end == q->stor_end) { + q->end = q->stor_begin; + } + } else { + /* full, allocate more storage */ + + BASE *bigger = NULL, *old = q->stor_begin; + igraph_int_t old_size = q->stor_end - q->stor_begin; + igraph_int_t new_capacity = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to dqueue, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_capacity == 0) { + new_capacity = 1; + } + + bigger = IGRAPH_CALLOC(new_capacity, BASE); + IGRAPH_CHECK_OOM(bigger, "Cannot push to dqueue."); + + if (q->stor_end - q->begin > 0) { + memcpy(bigger, q->begin, + (size_t)(q->stor_end - q->begin) * sizeof(BASE)); + } + if (q->end - q->stor_begin > 0) { + memcpy(bigger + (q->stor_end - q->begin), q->stor_begin, + (size_t)(q->end - q->stor_begin) * sizeof(BASE)); + } + + q->end = bigger + old_size; + q->stor_end = bigger + new_capacity; + q->stor_begin = bigger; + q->begin = bigger; + + *(q->end) = elem; + (q->end)++; + if (q->end == q->stor_end) { + q->end = q->stor_begin; + } + + IGRAPH_FREE(old); + } + + return IGRAPH_SUCCESS; +} + +#if defined (OUT_FORMAT) + +#ifndef USING_R +igraph_error_t FUNCTION(igraph_dqueue, print)(const TYPE(igraph_dqueue)* q) { + return FUNCTION(igraph_dqueue, fprint)(q, stdout); +} +#endif + +igraph_error_t FUNCTION(igraph_dqueue, fprint)(const TYPE(igraph_dqueue)* q, FILE *file) { + if (q->end != NULL) { + /* There is one element at least */ + BASE *p = q->begin; + fprintf(file, OUT_FORMAT, *p); + p++; + if (q->end > q->begin) { + /* Q is in one piece */ + while (p != q->end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + } else { + /* Q is in two pieces */ + while (p != q->stor_end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + p = q->stor_begin; + while (p != q->end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + } + } + + fprintf(file, "\n"); + + return IGRAPH_SUCCESS; +} + +#endif + +/** + * \ingroup dqueue + * \function igraph_dqueue_get + * \brief Access an element in a queue. + * + * \param q The queue. + * \param idx The index of the element within the queue. + * \return The desired element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, get)(const TYPE(igraph_dqueue) *q, igraph_int_t idx) { + IGRAPH_ASSERT(idx >= 0); + IGRAPH_ASSERT(idx < FUNCTION(igraph_dqueue, size)(q)); + if ((q->begin + idx < q->end) || + (q->begin >= q->end && q->begin + idx < q->stor_end)) { + return q->begin[idx]; + } else if (q->begin >= q->end && q->stor_begin + idx < q->end) { + idx = idx - (q->stor_end - q->begin); + return q->stor_begin[idx]; + } else { + /* The assertions at the top make it impossible to reach here, + but omitting this branch would cause compiler warnings. */ + IGRAPH_FATAL("Out of bounds access in dqueue."); + } +} diff --git a/src/core/error.c b/src/core/error.c new file mode 100644 index 0000000..27180ad --- /dev/null +++ b/src/core/error.c @@ -0,0 +1,601 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_types.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +#include +#include +#include + +/* Detecting ASan with GCC: + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * Detecting ASan with Clang: + * https://clang.llvm.org/docs/AddressSanitizer.html#conditional-compilation-with-has-feature-address-sanitizer + */ +#if defined(__SANITIZE_ADDRESS__) +# define IGRAPH_SANITIZER_AVAILABLE 1 +#elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# define IGRAPH_SANITIZER_AVAILABLE 1 +# endif +#endif + +#ifdef IGRAPH_SANITIZER_AVAILABLE +#include +#endif + +#ifdef USING_R +#include +#endif + +/***** Helper functions *****/ + +/* All calls to abort() in this compilation unit must go through igraph_abort(), + * in order to make it easy for igraph's R interface to not have any reference to abort(), + * which is disallowed by CRAN. + * + * Since the R interface sets its own error / fatal error handlers, this function + * is never actually called by it. + * + * Note that some of the other #ifndef USING_R's in this file are still needed + * to avoid references to fprintf and stderr. + */ +static IGRAPH_FUNCATTR_NORETURN void igraph_abort(void) { +#ifndef USING_R +#ifdef IGRAPH_SANITIZER_AVAILABLE + fprintf(stderr, "\nStack trace:\n"); + __sanitizer_print_stack_trace(); +#endif + abort(); +#else + /* R's error() function is declared 'noreturn'. We use it here to satisfy the compiler that igraph_abort() does indeed not return. */ + error("igraph_abort() was called. This should never happen. Please report this as an igraph bug, along with steps to reproduce it."); +#endif +} + + +/***** Handling errors *****/ + +static IGRAPH_THREAD_LOCAL igraph_error_handler_t *igraph_i_error_handler = 0; +static IGRAPH_THREAD_LOCAL char igraph_i_errormsg_buffer[500]; +static IGRAPH_THREAD_LOCAL char igraph_i_warningmsg_buffer[500]; +static IGRAPH_THREAD_LOCAL char igraph_i_fatalmsg_buffer[500]; + +/* Error strings corresponding to each igraph_error_type_t enum value. */ +static const char *igraph_i_error_strings[] = { + /* 0 */ "No error", + /* 1 */ "Failed", + /* 2 */ "Out of memory", + /* 3 */ "Parse error", + /* 4 */ "Invalid value", + /* 5 */ "Already exists", + /* 6 */ NULL, // "Invalid edge vector", /* removed in 1.0 */ + /* 7 */ "Invalid vertex ID", + /* 8 */ "Invalid edge ID", + /* 9 */ "Invalid mode", + /* 10 */ "File operation error", + /* 11 */ "Unfold infinite iterator", + /* 12 */ "Unimplemented function call", + /* 13 */ "Interrupted", + /* 14 */ "Numeric procedure did not converge", + + /* ARPACK error codes moved to igraph_arpack_error_t in arpack.c from version 1.0 */ + /* 15 */ "ARPACK error", // used to be "Matrix-vector product failed", + /* 16 */ NULL, // "N must be positive", + /* 17 */ NULL, // "NEV must be positive", + /* 18 */ NULL, // "NCV must be greater than NEV and less than or equal to N " + // "(and for the non-symmetric solver NCV-NEV >=2 must also hold)", + /* 19 */ NULL, // "Maximum number of iterations should be positive", + /* 20 */ NULL, // "Invalid WHICH parameter", + /* 21 */ NULL, // "Invalid BMAT parameter", + /* 22 */ NULL, // "WORKL is too small", + /* 23 */ NULL, // "LAPACK error in tridiagonal eigenvalue calculation", + /* 24 */ NULL, // "Starting vector is zero", + /* 25 */ NULL, // "MODE is invalid", + /* 26 */ NULL, // "MODE and BMAT are not compatible", + /* 27 */ NULL, // "ISHIFT must be 0 or 1", + /* 28 */ NULL, // "NEV and WHICH='BE' are incompatible", + /* 29 */ NULL, // "Could not build an Arnoldi factorization", + /* 30 */ NULL, // "No eigenvalues to sufficient accuracy", + /* 31 */ NULL, // "HOWMNY is invalid", + /* 32 */ NULL, // "HOWMNY='S' is not implemented", + /* 33 */ NULL, // "Different number of converged Ritz values", + /* 34 */ NULL, // "Error from calculation of a real Schur form", + /* 35 */ NULL, // "LAPACK (dtrevc) error for calculating eigenvectors", + /* 36 */ NULL, // "Unknown ARPACK error", + /* ARPACK error codes end here */ + + /* 37 */ "Negative cycle detected while calculating shortest paths", + /* 38 */ "Internal error, likely a bug in igraph", + + /* More ARPACK error codes moved to igraph_arpack_error_t in arpack.c from version 1.0 */ + /* 39 */ NULL, // "Maximum number of iterations reached", + /* 40 */ NULL, // "No shifts could be applied during a cycle of the " + // "Implicitly restarted Arnoldi iteration. One possibility " + // "is to increase the size of NCV relative to NEV", + /* 41 */ NULL, // "The Schur form computed by LAPACK routine dlahqr " + // "could not be reordered by LAPACK routine dtrsen.", + /* ARPACK error codes end here */ + + /* 42 */ NULL, // "Big integer division by zero", /* removed in 1.0 */ + /* 43 */ NULL, // "GLPK Error, GLP_EBOUND", /* removed in 1.0 */ + /* 44 */ NULL, // "GLPK Error, GLP_EROOT", /* removed in 1.0 */ + /* 45 */ NULL, // "GLPK Error, GLP_ENOPFS", /* removed in 1.0 */ + /* 46 */ NULL, // "GLPK Error, GLP_ENODFS", /* removed in 1.0 */ + /* 47 */ NULL, // "GLPK Error, GLP_EFAIL", /* removed in 1.0 */ + /* 48 */ NULL, // "GLPK Error, GLP_EMIPGAP", /* removed in 1.0 */ + /* 49 */ NULL, // "GLPK Error, GLP_ETMLIM", /* removed in 1.0 */ + /* 50 */ NULL, // "GLPK Error, GLP_STOP", /* removed in 1.0 */ + /* 51 */ NULL, // "Internal attribute handler error", /* removed in 1.0 */ + /* 52 */ "Unimplemented attribute combination for this type", + /* 53 */ NULL, // "LAPACK call resulted in an error", /* removed in 1.0 */ + /* 54 */ NULL, // "Internal DrL error", /* removed in 1.0 */ + /* 55 */ "Integer or double overflow", + /* 56 */ NULL, // "Internal GPLK error", /* removed in 1.0 */ + /* 57 */ NULL, // "CPU time exceeded", /* removed in 1.0 */ + /* 58 */ "Integer or double underflow", + /* 59 */ "Random walk got stuck", + /* 60 */ "Search stopped; this error should never be visible to the user, " + "please report this error along with the steps to reproduce it.", + /* 61 */ "Result too large", + /* 62 */ "Input problem has no solution" +}; + + +/** + * \function igraph_strerror + * \brief Textual description of an error. + * + * This is a simple utility function, it gives a short general textual + * description for an \a igraph error code. + * + * \param igraph_errno The \a igraph error code. + * \return pointer to the textual description of the error code. + */ + +const char *igraph_strerror(const igraph_error_t igraph_errno) { + if ((int) igraph_errno < 0 || + (int) igraph_errno >= sizeof(igraph_i_error_strings) / sizeof(igraph_i_error_strings[0])) { + IGRAPH_FATALF("Invalid error code %d; no error string available.", (int) igraph_errno); + } + const char *msg = igraph_i_error_strings[igraph_errno]; + /* Messages removed in 1.0 were replaced by NULL and should not be used. */ + IGRAPH_ASSERT(msg != NULL); + return msg; +} + + +/** + * \function igraph_error + * \brief Reports an error. + * + * \a igraph functions usually call this function (most often via the + * \ref IGRAPH_ERROR macro) if they notice an error. + * It calls the currently installed error handler function with the + * supplied arguments. + * + * \param reason Textual description of the error. + * \param file The source file in which the error was noticed. + * \param line The number of line in the source file which triggered the + * error. + * \param igraph_errno The \a igraph error code. + * \return The error code (if it returns). + * + * \sa \ref igraph_errorf() + */ + +igraph_error_t igraph_error(const char *reason, const char *file, int line, + igraph_error_t igraph_errno) { + + if (igraph_i_error_handler) { + igraph_i_error_handler(reason, file, line, igraph_errno); +#ifndef USING_R + } else { + igraph_error_handler_abort(reason, file, line, igraph_errno); +#endif + } + return igraph_errno; +} + + +/** + * \function igraph_errorf + * \brief Reports an error, printf-like version. + * + * \param reason Textual description of the error, interpreted as + * a \c printf format string. + * \param file The source file in which the error was noticed. + * \param line The line in the source file which triggered the error. + * \param igraph_errno The \a igraph error code. + * \param ... Additional parameters, the values to substitute into the + * format string. + * \return The error code (if it returns). + * + * \sa \ref igraph_error() + */ + +igraph_error_t igraph_errorf(const char *reason, const char *file, int line, + igraph_error_t igraph_errno, ...) { + va_list ap; + va_start(ap, igraph_errno); + vsnprintf(igraph_i_errormsg_buffer, + sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap); + va_end(ap); + return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno); +} + +igraph_error_t igraph_errorvf(const char *reason, const char *file, int line, + igraph_error_t igraph_errno, va_list ap) { + vsnprintf(igraph_i_errormsg_buffer, + sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap); + return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno); +} + +#ifndef USING_R +void igraph_error_handler_abort(const char *reason, const char *file, + int line, igraph_error_t igraph_errno) { + fprintf(stderr, "Error at %s:%i : %s - %s.\n", + file, line, reason, igraph_strerror(igraph_errno)); + igraph_abort(); +} +#endif + +void igraph_error_handler_ignore(const char *reason, const char *file, + int line, igraph_error_t igraph_errno) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(igraph_errno); + + IGRAPH_FINALLY_FREE(); +} + +#ifndef USING_R +void igraph_error_handler_printignore(const char *reason, const char *file, + int line, igraph_error_t igraph_errno) { + fprintf(stderr, "Error at %s:%i : %s - %s.\n", + file, line, reason, igraph_strerror(igraph_errno)); + IGRAPH_FINALLY_FREE(); +} +#endif + + +/** + * \function igraph_set_error_handler + * \brief Sets a new error handler. + * + * Installs a new error handler. If called with \c NULL, it installs the + * default error handler (which is currently \ref igraph_error_handler_abort). + * + * \param new_handler The error handler function to install. + * \return The old error handler function. This should be saved and + * restored if \p new_handler is not needed any + * more. + */ + +igraph_error_handler_t *igraph_set_error_handler(igraph_error_handler_t *new_handler) { + igraph_error_handler_t *previous_handler = igraph_i_error_handler; + igraph_i_error_handler = new_handler; + return previous_handler; +} + + +/***** "Finally" stack *****/ + +IGRAPH_THREAD_LOCAL struct igraph_i_protectedPtr igraph_i_finally_stack[100]; +IGRAPH_THREAD_LOCAL int igraph_i_finally_stack_size = 0; +IGRAPH_THREAD_LOCAL int igraph_i_finally_stack_level = 0; + +static void igraph_i_reset_finally_stack(void) { + igraph_i_finally_stack_size = 0; + igraph_i_finally_stack_level = 0; +} + +/* + * Adds another element to the free list + */ + +void IGRAPH_FINALLY_REAL(void (*func)(void*), void* ptr) { + int no = igraph_i_finally_stack_size; + if (no < 0) { + /* Reset finally stack in case fatal error handler does a longjmp instead of terminating the process: */ + igraph_i_reset_finally_stack(); + IGRAPH_FATALF("Corrupt finally stack: it contains %d elements.", no); + } + if (no >= (int) (sizeof(igraph_i_finally_stack) / sizeof(igraph_i_finally_stack[0]))) { + /* Reset finally stack in case fatal error handler does a longjmp instead of terminating the process: */ + igraph_i_reset_finally_stack(); + IGRAPH_FATALF("Finally stack too large: it contains %d elements.", no); + } + igraph_i_finally_stack[no].ptr = ptr; + igraph_i_finally_stack[no].func = func; + igraph_i_finally_stack[no].level = igraph_i_finally_stack_level; + igraph_i_finally_stack_size++; +} + +void IGRAPH_FINALLY_CLEAN(int minus) { + igraph_i_finally_stack_size -= minus; + if (igraph_i_finally_stack_size < 0) { + int left = igraph_i_finally_stack_size + minus; + /* Reset finally stack in case fatal error handler does a longjmp instead of terminating the process: */ + igraph_i_reset_finally_stack(); + IGRAPH_FATALF("Corrupt finally stack: trying to pop %d element(s) when only %d left.", minus, left); + } +} + +void IGRAPH_FINALLY_FREE(void) { + for (; igraph_i_finally_stack_size > 0; igraph_i_finally_stack_size--) { + int p = igraph_i_finally_stack_size - 1; + /* Call destructors only up to the current level */ + if (igraph_i_finally_stack[p].level < igraph_i_finally_stack_level) { + break; + } + igraph_i_finally_stack[p].func(igraph_i_finally_stack[p].ptr); + } +} + +int IGRAPH_FINALLY_STACK_SIZE(void) { + return igraph_i_finally_stack_size; +} + +/** + * \function IGRAPH_FINALLY_ENTER + * + * For internal use only. + * + * Opens a new level in the finally stack. Must have a matching + * IGRAPH_FINALLY_EXIT() call that closes the level and exits it. + * + * The finally stack is divided into "levels". A call to IGRAPH_FINALLY_FREE() + * will only unwind the current level of the finally stack, not any of the lower + * levels. This mechanism is used to allow some functions to pause stack unwinding + * until they can restore their data structures into a consistent state. + * See \ref igraph_add_edges() for an example usage. + */ +void IGRAPH_FINALLY_ENTER(void) { + int no = igraph_i_finally_stack_size; + /* Level indices must always be in increasing order in the finally stack */ + if (no > 0 && igraph_i_finally_stack[no-1].level > igraph_i_finally_stack_level) { + /* Reset finally stack in case fatal error handler does a longjmp instead of terminating the process: */ + igraph_i_reset_finally_stack(); + IGRAPH_FATAL("Corrupt finally stack: cannot create new finally stack level before last one is freed."); + } + igraph_i_finally_stack_level++; +} + +/** + * \function IGRAPH_FINALLY_EXIT + * + * For internal use only. + * + * Exists the current level of the finally stack, see IGRAPH_FINALLY_ENTER() + * for details. If an error occured inbetween the last pair of + * IGRAPH_FINALLY_ENTER()/EXIT() calls, a call to igraph_error(), typically + * through IGRAPH_ERROR(), is mandatory directly after IGRAPH_FINALLY_EXIT(). + * This ensures that resource cleanup will properly resume. + */ +void IGRAPH_FINALLY_EXIT(void) { + igraph_i_finally_stack_level--; + if (igraph_i_finally_stack_level < 0) { + /* Reset finally stack in case fatal error handler does a longjmp instead of terminating the process: */ + igraph_i_reset_finally_stack(); + IGRAPH_FATAL("Corrupt finally stack: trying to exit outermost finally stack level."); + } +} + + +/***** Handling warnings *****/ + +static IGRAPH_THREAD_LOCAL igraph_warning_handler_t *igraph_i_warning_handler = 0; + +/** + * \function igraph_warning_handler_ignore + * \brief Ignores all warnings. + * + * This warning handler function simply ignores all warnings. + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning.. + */ + +void igraph_warning_handler_ignore(const char *reason, const char *file, int line) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); +} + +#ifndef USING_R + +/** + * \function igraph_warning_handler_print + * \brief Prints all warnings to the standard error. + * + * This warning handler function simply prints all warnings to the + * standard error. + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning.. + */ + +void igraph_warning_handler_print(const char *reason, const char *file, int line) { + fprintf(stderr, "Warning at %s:%i : %s\n", file, line, reason); +} +#endif + + +/** + * \function igraph_warning + * \brief Reports a warning. + * + * Call this function if you want to trigger a warning from within + * a function that uses \a igraph. + * + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning. + */ + +void igraph_warning(const char *reason, const char *file, int line) { + + if (igraph_i_warning_handler) { + igraph_i_warning_handler(reason, file, line); +#ifndef USING_R + } else { + igraph_warning_handler_print(reason, file, line); +#endif + } +} + + +/** + * \function igraph_warningf + * \brief Reports a warning, printf-like version. + * + * This function is similar to \ref igraph_warning(), but + * uses a printf-like syntax. It substitutes the additional arguments + * into the \p reason template string and calls \ref igraph_warning(). + * + * \param reason Textual description of the warning, a template string + * with the same syntax as the standard printf C library function. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning. + * \param ... The additional arguments to be substituted into the + * template string. + */ + +void igraph_warningf(const char *reason, const char *file, int line, ...) { + va_list ap; + va_start(ap, line); + vsnprintf(igraph_i_warningmsg_buffer, + sizeof(igraph_i_warningmsg_buffer) / sizeof(char), reason, ap); + va_end(ap); + igraph_warning(igraph_i_warningmsg_buffer, file, line); +} + + +/** + * \function igraph_set_warning_handler + * \brief Installs a warning handler. + * + * Install the supplied warning handler function. + * + * \param new_handler The new warning handler function to install. + * Supply a null pointer here to uninstall the current + * warning handler, without installing a new one. + * \return The current warning handler function. + */ + +igraph_warning_handler_t *igraph_set_warning_handler(igraph_warning_handler_t *new_handler) { + igraph_warning_handler_t *previous_handler = igraph_i_warning_handler; + igraph_i_warning_handler = new_handler; + return previous_handler; +} + + +/***** Handling fatal errors *****/ + +static IGRAPH_THREAD_LOCAL igraph_fatal_handler_t *igraph_i_fatal_handler = NULL; + +/** + * \function igraph_set_fatal_handler + * \brief Installs a fatal error handler. + * + * Installs the supplied fatal error handler function. + * + * + * Fatal error handler functions \em must not return. Typically, the fatal + * error handler would either call abort() or longjmp(). + * + * \param new_handler The new fatal error handler function to install. + * Supply a null pointer here to uninstall the current + * fatal error handler, without installing a new one. + * \return The current fatal error handler function. + */ + +igraph_fatal_handler_t *igraph_set_fatal_handler(igraph_fatal_handler_t *new_handler) { + igraph_fatal_handler_t *previous_handler = igraph_i_fatal_handler; + igraph_i_fatal_handler = new_handler; + return previous_handler; +} + +#ifndef USING_R +void igraph_fatal_handler_abort(const char *reason, const char *file, int line) { + fprintf(stderr, "Fatal error at %s:%i : %s\n", file, line, reason); + igraph_abort(); +} +#endif + + +/** + * \function igraph_fatal + * \brief Triggers a fatal error. + * + * This function triggers a fatal error. Typically it is called indirectly through + * \ref IGRAPH_FATAL() or \ref IGRAPH_ASSERT(). + * + * \param reason Textual description of the error. + * \param file The source file in which the error was noticed. + * \param line The number of line in the source file which triggered the error. + */ + +void igraph_fatal(const char *reason, const char *file, int line) { + if (igraph_i_fatal_handler) { + igraph_i_fatal_handler(reason, file, line); +#ifndef USING_R + } else { + igraph_fatal_handler_abort(reason, file, line); +#endif + } + /* The following line should never be reached, as fatal error handlers are not supposed to return. + It is here to satisfy the compiler that this function indeed does not return. */ + igraph_abort(); +} + + +/** + * \function igraph_fatalf + * \brief Triggers a fatal error, printf-like syntax. + * + * This function is similar to \ref igraph_fatal(), but + * uses a printf-like syntax. It substitutes the additional arguments + * into the \p reason template string and calls \ref igraph_fatal(). + * + * \param reason Textual description of the error. + * \param file The source file in which the error was noticed. + * \param line The number of line in the source file which triggered the error. + * \param ... The additional arguments to be substituted into the template string. + */ + +void igraph_fatalf(const char *reason, const char *file, int line, ...) { + va_list ap; + va_start(ap, line); + vsnprintf(igraph_i_fatalmsg_buffer, sizeof(igraph_i_fatalmsg_buffer) / sizeof(char), reason, ap); + va_end(ap); + igraph_fatal(igraph_i_fatalmsg_buffer, file, line); +} diff --git a/src/core/estack.c b/src/core/estack.c new file mode 100644 index 0000000..918b47a --- /dev/null +++ b/src/core/estack.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "core/estack.h" + +igraph_error_t igraph_estack_init(igraph_estack_t *s, igraph_int_t setsize, + igraph_int_t stacksize) { + IGRAPH_CHECK(igraph_bitset_init(&s->isin, setsize)); + IGRAPH_FINALLY(igraph_bitset_destroy, &s->isin); + IGRAPH_CHECK(igraph_stack_int_init(&s->stack, stacksize)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +void igraph_estack_destroy(igraph_estack_t *s) { + igraph_stack_int_destroy(&s->stack); + igraph_bitset_destroy(&s->isin); +} + +igraph_error_t igraph_estack_push(igraph_estack_t *s, igraph_int_t elem) { + if ( !IGRAPH_BIT_TEST(s->isin, elem)) { + IGRAPH_CHECK(igraph_stack_int_push(&s->stack, elem)); + IGRAPH_BIT_SET(s->isin, elem); + } + return IGRAPH_SUCCESS; +} + +igraph_int_t igraph_estack_pop(igraph_estack_t *s) { + igraph_int_t elem = igraph_stack_int_pop(&s->stack); + IGRAPH_BIT_CLEAR(s->isin, elem); + return elem; +} + +igraph_bool_t igraph_estack_iselement(const igraph_estack_t *s, + igraph_int_t elem) { + return IGRAPH_BIT_TEST(s->isin, elem); +} + +igraph_int_t igraph_estack_size(const igraph_estack_t *s) { + return igraph_stack_int_size(&s->stack); +} + +#ifndef USING_R +igraph_error_t igraph_estack_print(const igraph_estack_t *s) { + return igraph_stack_int_print(&s->stack); +} +#endif diff --git a/src/core/estack.h b/src/core/estack.h new file mode 100644 index 0000000..8ffd183 --- /dev/null +++ b/src/core/estack.h @@ -0,0 +1,51 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_ESTACK_H +#define IGRAPH_ESTACK_H + +#include "igraph_decls.h" +#include "igraph_bitset.h" +#include "igraph_stack.h" + +IGRAPH_BEGIN_C_DECLS + +typedef struct igraph_estack_t { + igraph_stack_int_t stack; + igraph_bitset_t isin; +} igraph_estack_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_estack_init( + igraph_estack_t *s, igraph_int_t setsize, igraph_int_t stacksize); +IGRAPH_PRIVATE_EXPORT void igraph_estack_destroy(igraph_estack_t *s); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_estack_push(igraph_estack_t *s, igraph_int_t elem); +IGRAPH_PRIVATE_EXPORT igraph_int_t igraph_estack_pop(igraph_estack_t *s); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_estack_iselement(const igraph_estack_t *s, + igraph_int_t elem); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_estack_size(const igraph_estack_t *s); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_estack_print(const igraph_estack_t *s); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/exceptions.h b/src/core/exceptions.h new file mode 100644 index 0000000..d486082 --- /dev/null +++ b/src/core/exceptions.h @@ -0,0 +1,34 @@ +#ifndef IGRAPH_HANDLE_EXCEPTIONS_H +#define IGRAPH_HANDLE_EXCEPTIONS_H + +#include "igraph_error.h" + +#include +#include +#include + +/* igraph functions which may be called from C code must not throw C++ exceptions. + * This includes all public functions. This macro is meant to handle exceptions thrown + * by C++ libraries used by igraph (such as bliss). Wrap the entire body + * of public functions implemented in C++ in IGRAPH_HANDLE_EXCEPTIONS(). + * + * In some cases IGRAPH_HANDLE_EXCEPTIONS() won't work because the + * C preprocessor gets confused by the code block. In that case one can use + * IGRAPH_HANDLE_EXCEPTIONS_BEGIN; and IGRAPH_HANDLE_EXCEPTIONS_END at the + * beginning and end of the code block. + */ +#define IGRAPH_HANDLE_EXCEPTIONS_BEGIN \ + try { +#define IGRAPH_HANDLE_EXCEPTIONS_END \ + } \ + catch (const std::bad_alloc &e) { IGRAPH_ERROR(e.what(), IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ } \ + catch (const std::range_error &e) { IGRAPH_ERROR(e.what(), IGRAPH_EOVERFLOW); /* LCOV_EXCL_LINE */ } \ + catch (const std::exception &e) { IGRAPH_ERROR(e.what(), IGRAPH_FAILURE); /* LCOV_EXCL_LINE */ } \ + catch (...) { IGRAPH_ERROR("Unknown exception caught.", IGRAPH_FAILURE); /* LCOV_EXCL_LINE */ } + +#define IGRAPH_HANDLE_EXCEPTIONS(code) \ + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; \ + code; \ + IGRAPH_HANDLE_EXCEPTIONS_END; + +#endif // IGRAPH_HANDLE_EXCEPTIONS_H diff --git a/src/core/fixed_vectorlist.c b/src/core/fixed_vectorlist.c new file mode 100644 index 0000000..724c83b --- /dev/null +++ b/src/core/fixed_vectorlist.c @@ -0,0 +1,58 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "core/fixed_vectorlist.h" + +void igraph_fixed_vectorlist_destroy(igraph_fixed_vectorlist_t *l) { + igraph_vector_int_list_destroy(&l->vecs); +} + +igraph_error_t igraph_fixed_vectorlist_convert( + igraph_fixed_vectorlist_t *l, const igraph_vector_int_t *from, + igraph_int_t size +) { + igraph_vector_int_t sizes; + igraph_int_t i, no = igraph_vector_int_size(from), to; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&l->vecs, size); + IGRAPH_VECTOR_INT_INIT_FINALLY(&sizes, size); + + for (i = 0; i < no; i++) { + to = VECTOR(*from)[i]; + if (to >= 0) { + VECTOR(sizes)[to] += 1; + } + } + + for (i = 0; i < no; i++) { + to = VECTOR(*from)[i]; + if (to >= 0) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&l->vecs, to); + IGRAPH_CHECK(igraph_vector_int_push_back(v, i)); + } + } + + igraph_vector_int_destroy(&sizes); + IGRAPH_FINALLY_CLEAN(2); /* + l->vecs */ + + return IGRAPH_SUCCESS; +} diff --git a/src/core/fixed_vectorlist.h b/src/core/fixed_vectorlist.h new file mode 100644 index 0000000..2571a2d --- /dev/null +++ b/src/core/fixed_vectorlist.h @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_TYPES_INTERNAL_H +#define IGRAPH_TYPES_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Vectorlist, fixed length */ +/* -------------------------------------------------- */ + +typedef struct igraph_fixed_vectorlist_t { + igraph_vector_int_list_t vecs; + igraph_int_t length; +} igraph_fixed_vectorlist_t; + +void igraph_fixed_vectorlist_destroy(igraph_fixed_vectorlist_t *l); +igraph_error_t igraph_fixed_vectorlist_convert(igraph_fixed_vectorlist_t *l, + const igraph_vector_int_t *from, + igraph_int_t size); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/genheap.c b/src/core/genheap.c new file mode 100644 index 0000000..c748c3e --- /dev/null +++ b/src/core/genheap.c @@ -0,0 +1,307 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "igraph_memory.h" + +#include "core/genheap.h" + +#include /* memcpy */ + +#if defined(_MSC_VER) && _MSC_VER < 1927 + /* MSVC does not understand restrict before version 19.27 */ + #define restrict __restrict +#endif + +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +#define ELEM(h, x) ((char *) h->data + x * h->item_size) + +/* This is a smart indexed heap that can hold elements of arbitrary type. + * In addition to the "normal" indexed heap, it allows to access every element + * through its index in O(1) time. In other words, for this heap the indexing + * operation is O(1), the normal heap does this in O(n) time. + */ + +static void swapfunc(char * restrict a, char * restrict b, size_t es) { + char t; + + do { + t = *a; + *a++ = *b; + *b++ = t; + } while (--es > 0); +} + +static void igraph_i_gen2wheap_switch(igraph_gen2wheap_t *h, + igraph_int_t e1, igraph_int_t e2) { + if (e1 != e2) { + igraph_int_t tmp1, tmp2; + + swapfunc(ELEM(h, e1), ELEM(h, e2), h->item_size); + + tmp1 = VECTOR(h->index)[e1]; + tmp2 = VECTOR(h->index)[e2]; + + VECTOR(h->index2)[tmp1] = e2 + 2; + VECTOR(h->index2)[tmp2] = e1 + 2; + + VECTOR(h->index)[e1] = tmp2; + VECTOR(h->index)[e2] = tmp1; + } +} + +static void igraph_i_gen2wheap_shift_up(igraph_gen2wheap_t *h, + igraph_int_t elem) { + if (elem == 0 || h->cmp(ELEM(h, elem), ELEM(h, PARENT(elem))) < 0) { + /* at the top */ + } else { + igraph_i_gen2wheap_switch(h, elem, PARENT(elem)); + igraph_i_gen2wheap_shift_up(h, PARENT(elem)); + } +} + +static void igraph_i_gen2wheap_sink(igraph_gen2wheap_t *h, + igraph_int_t head) { + igraph_int_t size = igraph_gen2wheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + h->cmp(ELEM(h, LEFTCHILD(head)), ELEM(h, RIGHTCHILD(head))) >= 0) { + /* sink to the left if needed */ + if (h->cmp(ELEM(h, head), ELEM(h, LEFTCHILD(head))) < 0) { + igraph_i_gen2wheap_switch(h, head, LEFTCHILD(head)); + igraph_i_gen2wheap_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (h->cmp(ELEM(h, head), ELEM(h, RIGHTCHILD(head))) < 0) { + igraph_i_gen2wheap_switch(h, head, RIGHTCHILD(head)); + igraph_i_gen2wheap_sink(h, RIGHTCHILD(head)); + } + } +} + +/* ------------------ */ +/* These are public */ +/* ------------------ */ + +/** + * Initializes a new two-way heap. The max_size parameter defines the maximum + * number of items that the heap can hold. + */ +igraph_error_t igraph_gen2wheap_init( + igraph_gen2wheap_t *h, + int (*cmp)(const void *, const void *), + size_t item_size, igraph_int_t max_size +) { + /* TODO: Currently, storage is allocated for the maximum number of elements + * right from the start. This is sufficient for the only use case as of this + * writing, the D-SATUR graph colouring algorithm, but it may not be efficcient + * for other use cases. Consider improving this in the future. + */ + h->max_size = max_size; + /* We start with the biggest */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&h->index2, max_size); + h->cmp = cmp; + h->item_size = item_size; + h->data = igraph_calloc(max_size, item_size); + IGRAPH_CHECK_OOM(h->data, "Cannot initialize generic heap."); + IGRAPH_FINALLY(igraph_free, h->data); + IGRAPH_CHECK(igraph_vector_int_init(&h->index, 0)); + + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/** + * Destroys a two-way heap. + */ +void igraph_gen2wheap_destroy(igraph_gen2wheap_t *h) { + IGRAPH_FREE(h->data); + igraph_vector_int_destroy(&h->index); + igraph_vector_int_destroy(&h->index2); +} + +/** + * Returns the current number of elements in the two-way heap. + */ +igraph_int_t igraph_gen2wheap_size(const igraph_gen2wheap_t *h) { + return igraph_vector_int_size(&h->index); +} + +/** + * Clears a two-way heap, i.e. removes all the elements from the heap. + */ +void igraph_gen2wheap_clear(igraph_gen2wheap_t *h) { + igraph_vector_int_clear(&h->index); + igraph_vector_int_null(&h->index2); +} + +/** + * Returns whether the two-way heap is empty. + */ +igraph_bool_t igraph_gen2wheap_empty(const igraph_gen2wheap_t *h) { + return igraph_vector_int_empty(&h->index); +} + +/** + * Pushes a new element into the two-way heap, with the given associated index. + * The index must be between 0 and the size of the heap minus 1, inclusive. It + * is assumed (and not checked) that the heap does not have another item with + * the same index. + */ +igraph_error_t igraph_gen2wheap_push_with_index(igraph_gen2wheap_t *h, + igraph_int_t idx, const void *elem) { + + igraph_int_t size = igraph_vector_int_size(&h->index); + + if (size > IGRAPH_INTEGER_MAX - 2) { + /* to allow size+2 below */ + IGRAPH_ERROR("Cannot push to gen2wheap, already at maximum size.", IGRAPH_EOVERFLOW); + } + + memcpy(ELEM(h, size), elem, h->item_size); + IGRAPH_CHECK(igraph_vector_int_push_back(&h->index, idx)); + VECTOR(h->index2)[idx] = size + 2; + + /* maintain heap */ + igraph_i_gen2wheap_shift_up(h, size); + + return IGRAPH_SUCCESS; +} + +/** + * Returns the maximum number of elements that the two-way heap can hold. This + * is also one larger than the maximum allowed index that can be passed to + * \c igraph_gen2wheap_push_with_index . + */ +igraph_int_t igraph_gen2wheap_max_size(const igraph_gen2wheap_t *h) { + return h->max_size; +} + +/** + * Returns a pointer to the largest element in the heap. + */ +const void *igraph_gen2wheap_max(const igraph_gen2wheap_t *h) { + return ELEM(h, 0); +} + +/** + * Returns the index that was associated to the largest element in the heap + * when it was pushed to the heap. + */ +igraph_int_t igraph_gen2wheap_max_index(const igraph_gen2wheap_t *h) { + return VECTOR(h->index)[0]; +} + +/** + * Returns whether the heap contains an element with the given index, even if + * it was deactivated earlier. + */ +igraph_bool_t igraph_gen2wheap_has_elem(const igraph_gen2wheap_t *h, igraph_int_t idx) { + return VECTOR(h->index2)[idx] != 0; +} + +/** + * Returns whether the heap contains an element with the given index \em and it + * has not been deactivated yet. + */ +igraph_bool_t igraph_gen2wheap_has_active(const igraph_gen2wheap_t *h, igraph_int_t idx) { + return VECTOR(h->index2)[idx] > 1; +} + +/** + * Returns a pointer to the item at the given index in the two-way heap. + */ +const void *igraph_gen2wheap_get(const igraph_gen2wheap_t *h, igraph_int_t idx) { + igraph_int_t i = VECTOR(h->index2)[idx] - 2; + return ELEM(h, i); +} + +/** + * Deletes the largest element from the two-way heap. + * + * This function does \em not change the indices associated to the elements + * that remain in the heap. + */ +void igraph_gen2wheap_delete_max(igraph_gen2wheap_t *h) { + igraph_int_t tmpidx = VECTOR(h->index)[0]; + igraph_i_gen2wheap_switch(h, 0, igraph_gen2wheap_size(h) - 1); + igraph_vector_int_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 0; + igraph_i_gen2wheap_sink(h, 0); +} + +/** + * Deactivates the largest element from the two-way heap. + * + * This function does \em not change the indices associated to the elements + * that remain in the heap. + */ +void igraph_gen2wheap_deactivate_max(igraph_gen2wheap_t *h) { + igraph_int_t tmpidx = VECTOR(h->index)[0]; + igraph_i_gen2wheap_switch(h, 0, igraph_gen2wheap_size(h) - 1); + igraph_vector_int_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 1; + igraph_i_gen2wheap_sink(h, 0); +} + +/** + * Modifies the value associated to the given index in the two-way heap. + */ +void igraph_gen2wheap_modify(igraph_gen2wheap_t *h, igraph_int_t idx, const void *elem) { + + igraph_int_t pos = VECTOR(h->index2)[idx] - 2; + + memcpy(ELEM(h, pos), elem, h->item_size); + igraph_i_gen2wheap_sink(h, pos); + igraph_i_gen2wheap_shift_up(h, pos); +} + +/** + * Checks that the heap is in a consistent state + */ +igraph_error_t igraph_gen2wheap_check(const igraph_gen2wheap_t *h) { + igraph_int_t size = igraph_gen2wheap_size(h); + igraph_int_t i; + igraph_bool_t error = false; + + /* Check the heap property */ + for (i = 0; i < size; i++) { + if (LEFTCHILD(i) >= size) { + break; + } + if (h->cmp(ELEM(h, LEFTCHILD(i)), ELEM(h, i)) > 0) { + error = true; break; + } + if (RIGHTCHILD(i) >= size) { + break; + } + if (h->cmp(ELEM(h, RIGHTCHILD(i)), ELEM(h, i)) > 0) { + error = true; break; + } + } + + if (error) { + IGRAPH_ERROR("Inconsistent heap.", IGRAPH_EINTERNAL); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/core/genheap.h b/src/core/genheap.h new file mode 100644 index 0000000..c8b18dd --- /dev/null +++ b/src/core/genheap.h @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "igraph_types.h" +#include "igraph_vector.h" + +typedef struct igraph_gen2wheap_t { + /** Maximum number of items in the heap */ + igraph_int_t max_size; + + /** The size of an individual item */ + size_t item_size; + + /** The items themselves in the heap */ + /* TODO: currently this is always allocated to have max_size */ + void *data; + + /** qsort-style comparison function used to order items */ + int (*cmp)(const void *, const void *); + + /** An integer index associated to each item in the heap; this vector is + * always modified in tandem with \c data . Values in this vector are + * between 0 and size-1 */ + igraph_vector_int_t index; + + /** + * A _reverse_ index that allows O(1) lookup of the position of a given + * value within the \c index member. Note that it uses two special values: + * index2[i] == 0 means that \c i is not in \c index at all, while + * index2[i] == 1 means that \c i is in \c index but it was _deactivated_. + * The semantics of deactivation is up to the user of the data structure + * to decide. Other than these two special values, index2[i] == j means + * that index[j-2] == i and data[j-2] is the corresponding item in the heap + */ + igraph_vector_int_t index2; +} igraph_gen2wheap_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_gen2wheap_init( + igraph_gen2wheap_t *h, + int (*cmp)(const void *, const void *), + size_t item_size, igraph_int_t max_size +); +IGRAPH_PRIVATE_EXPORT void igraph_gen2wheap_destroy(igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gen2wheap_size(const igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT void igraph_gen2wheap_clear(igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_gen2wheap_empty(const igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_gen2wheap_push_with_index(igraph_gen2wheap_t *h, + igraph_int_t idx, const void *elem); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gen2wheap_max_size(const igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE const void *igraph_gen2wheap_max(const igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gen2wheap_max_index(const igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_gen2wheap_has_elem(const igraph_gen2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_gen2wheap_has_active(const igraph_gen2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE const void *igraph_gen2wheap_get(const igraph_gen2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT void igraph_gen2wheap_delete_max(igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT void igraph_gen2wheap_deactivate_max(igraph_gen2wheap_t *h); +IGRAPH_PRIVATE_EXPORT void igraph_gen2wheap_modify(igraph_gen2wheap_t *h, igraph_int_t idx, const void *elem); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_gen2wheap_check(const igraph_gen2wheap_t *h); diff --git a/src/core/grid.c b/src/core/grid.c new file mode 100644 index 0000000..d0d9102 --- /dev/null +++ b/src/core/grid.c @@ -0,0 +1,275 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_types.h" + +#include "core/grid.h" + +#include + +/* internal function */ + +static void igraph_i_2dgrid_which( + igraph_2dgrid_t *grid, igraph_real_t xc, igraph_real_t yc, + igraph_int_t *x, igraph_int_t *y +) { + if (xc <= grid->minx) { + *x = 0; + } else if (xc >= grid->maxx) { + *x = grid->stepsx - 1; + } else { + *x = floor((xc - (grid->minx)) / (grid->deltax)); + } + + if (yc <= grid->miny) { + *y = 0; + } else if (yc >= grid->maxy) { + *y = grid->stepsy - 1; + } else { + *y = floor((yc - (grid->miny)) / (grid->deltay)); + } +} + +igraph_error_t igraph_2dgrid_init(igraph_2dgrid_t *grid, igraph_matrix_t *coords, + igraph_real_t minx, igraph_real_t maxx, igraph_real_t deltax, + igraph_real_t miny, igraph_real_t maxy, igraph_real_t deltay) { + igraph_int_t no_of_points; + + IGRAPH_ASSERT(minx <= maxx); + IGRAPH_ASSERT(miny <= maxy); + IGRAPH_ASSERT(deltax > 0 && deltay > 0); + IGRAPH_ASSERT(isfinite(minx) && isfinite(maxx) && isfinite(miny) && isfinite(maxy)); + IGRAPH_ASSERT(isfinite(deltax) && isfinite(deltay)); + + grid->coords = coords; + grid->minx = minx; + grid->maxx = maxx; + grid->deltax = deltax; + grid->miny = miny; + grid->maxy = maxy; + grid->deltay = deltay; + + grid->stepsx = ceil((maxx - minx) / deltax); + grid->stepsy = ceil((maxy - miny) / deltay); + + no_of_points = igraph_matrix_nrow(coords); + + IGRAPH_CHECK(igraph_matrix_int_init(&grid->startidx, grid->stepsx, grid->stepsy)); + IGRAPH_FINALLY(igraph_matrix_int_destroy, &grid->startidx); + IGRAPH_VECTOR_INT_INIT_FINALLY(&grid->next, no_of_points); + IGRAPH_VECTOR_INT_INIT_FINALLY(&grid->prev, no_of_points); + + igraph_vector_int_null(&grid->prev); + igraph_vector_int_null(&grid->next); + + grid->massx = 0; + grid->massy = 0; + grid->vertices = 0; + + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +void igraph_2dgrid_destroy(igraph_2dgrid_t *grid) { + igraph_matrix_int_destroy(&grid->startidx); + igraph_vector_int_destroy(&grid->next); + igraph_vector_int_destroy(&grid->prev); +} + +void igraph_2dgrid_add(igraph_2dgrid_t *grid, igraph_int_t elem, + igraph_real_t xc, igraph_real_t yc) { + igraph_int_t x, y; + igraph_int_t first; + + MATRIX(*grid->coords, elem, 0) = xc; + MATRIX(*grid->coords, elem, 1) = yc; + + /* add to cell */ + igraph_i_2dgrid_which(grid, xc, yc, &x, &y); + first = MATRIX(grid->startidx, x, y); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, x, y) = elem + 1; + + grid->massx += xc; + grid->massy += yc; + grid->vertices += 1; +} + +void igraph_2dgrid_add2(igraph_2dgrid_t *grid, igraph_int_t elem) { + igraph_int_t x, y; + igraph_int_t first; + igraph_real_t xc, yc; + + xc = MATRIX(*grid->coords, elem, 0); + yc = MATRIX(*grid->coords, elem, 1); + + /* add to cell */ + igraph_i_2dgrid_which(grid, xc, yc, &x, &y); + first = MATRIX(grid->startidx, x, y); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, x, y) = elem + 1; + + grid->massx += xc; + grid->massy += yc; + grid->vertices += 1; +} + +void igraph_2dgrid_move(igraph_2dgrid_t *grid, igraph_int_t elem, + igraph_real_t xc, igraph_real_t yc) { + igraph_int_t oldx, oldy; + igraph_int_t newx, newy; + igraph_real_t oldxc = MATRIX(*grid->coords, elem, 0); + igraph_real_t oldyc = MATRIX(*grid->coords, elem, 1); + igraph_int_t first; + + xc = oldxc + xc; yc = oldyc + yc; + + igraph_i_2dgrid_which(grid, oldxc, oldyc, &oldx, &oldy); + igraph_i_2dgrid_which(grid, xc, yc, &newx, &newy); + if (oldx != newx || oldy != newy) { + /* remove from this cell */ + if (VECTOR(grid->prev)[elem] != 0) { + VECTOR(grid->next) [ VECTOR(grid->prev)[elem] - 1 ] = + VECTOR(grid->next)[elem]; + } else { + MATRIX(grid->startidx, oldx, oldy) = VECTOR(grid->next)[elem]; + } + if (VECTOR(grid->next)[elem] != 0) { + VECTOR(grid->prev)[ VECTOR(grid->next)[elem] - 1 ] = + VECTOR(grid->prev)[elem]; + } + + /* add to this cell */ + first = MATRIX(grid->startidx, newx, newy); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, newx, newy) = elem + 1; + } + + grid->massx += -oldxc + xc; + grid->massy += -oldyc + yc; + + MATRIX(*grid->coords, elem, 0) = xc; + MATRIX(*grid->coords, elem, 1) = yc; + +} + +void igraph_2dgrid_getcenter(const igraph_2dgrid_t *grid, + igraph_real_t *massx, igraph_real_t *massy) { + *massx = (grid->massx) / (grid->vertices); + *massy = (grid->massy) / (grid->vertices); +} + +igraph_bool_t igraph_2dgrid_in(const igraph_2dgrid_t *grid, igraph_int_t elem) { + return VECTOR(grid->next)[elem] != -1; +} + +igraph_real_t igraph_2dgrid_sq_dist(const igraph_2dgrid_t *grid, + igraph_int_t e1, igraph_int_t e2) { + igraph_real_t x = MATRIX(*grid->coords, e1, 0) - MATRIX(*grid->coords, e2, 0); + igraph_real_t y = MATRIX(*grid->coords, e1, 1) - MATRIX(*grid->coords, e2, 1); + + return x * x + y * y; +} + +void igraph_2dgrid_reset(igraph_2dgrid_t *grid, igraph_2dgrid_iterator_t *it) { + /* Search for the first cell containing a vertex */ + it->x = 0; it->y = 0; it->vid = MATRIX(grid->startidx, 0, 0); + while ( it->vid == 0 && (it->x < grid->stepsx - 1 || it->y < grid->stepsy - 1)) { + it->x += 1; + if (it->x == grid->stepsx) { + it->x = 0; it->y += 1; + } + it->vid = MATRIX(grid->startidx, it->x, it->y); + } +} + +igraph_int_t igraph_2dgrid_next(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it) { + igraph_int_t ret = it->vid; + + if (ret == 0) { + return 0; + } + + /* First neighbor */ + it->ncells = -1; + if (it->x != grid->stepsx - 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x + 1; + it->ny[it->ncells] = it->y; + } + if (it->y != grid->stepsy - 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x; + it->ny[it->ncells] = it->y + 1; + } + if (it->ncells == 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x + 1; + it->ny[it->ncells] = it->y + 1; + } + it->ncells += 1; + it->nx[it->ncells] = it->x; + it->ny[it->ncells] = it->y; + + it->nei = VECTOR(grid->next) [ ret - 1 ]; + while (it->ncells > 0 && it->nei == 0 ) { + it->ncells -= 1; + it->nei = MATRIX(grid->startidx, it->nx[it->ncells], it->ny[it->ncells]); + } + + /* Next vertex */ + it->vid = VECTOR(grid->next)[ it->vid - 1 ]; + while ( (it->x < grid->stepsx - 1 || it->y < grid->stepsy - 1) && + it->vid == 0) { + it->x += 1; + if (it->x == grid->stepsx) { + it->x = 0; it->y += 1; + } + it->vid = MATRIX(grid->startidx, it->x, it->y); + } + + return ret; +} + +igraph_int_t igraph_2dgrid_next_nei(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it) { + igraph_int_t ret = it->nei; + + if (it->nei != 0) { + it->nei = VECTOR(grid->next) [ ret - 1 ]; + } + while (it->ncells > 0 && it->nei == 0 ) { + it->ncells -= 1; + it->nei = MATRIX(grid->startidx, it->nx[it->ncells], it->ny[it->ncells]); + } + + return ret; +} diff --git a/src/core/grid.h b/src/core/grid.h new file mode 100644 index 0000000..65a41ff --- /dev/null +++ b/src/core/grid.h @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CORE_GRID_H +#define IGRAPH_CORE_GRID_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * 2d grid containing points + */ + +typedef struct igraph_2dgrid_t { + igraph_matrix_t *coords; /* The current coordinates in the grid */ + igraph_real_t minx, maxx, deltax; /* Minimum and maximum X coordinates and X spacing */ + igraph_real_t miny, maxy, deltay; /* Minimum and maximum Y coordinates and Y spacing */ + igraph_int_t stepsx, stepsy; /* Number of cells in the X and Y directions */ + igraph_matrix_int_t startidx; /* startidx[i, j] is the index of an arbitrary point in that grid cell, plus one; zero means "empty cell" */ + igraph_vector_int_t next; /* next[i] is the index of the point following point i in the same cell, plus one; zero means "last point" */ + igraph_vector_int_t prev; /* prev[i] is the index of the point preceding point i in the same cell, plus one; zero means "first point" */ + igraph_real_t massx, massy; /* The sum of the coordinates */ + igraph_int_t vertices; /* Number of active vertices */ +} igraph_2dgrid_t; + +igraph_error_t igraph_2dgrid_init(igraph_2dgrid_t *grid, igraph_matrix_t *coords, + igraph_real_t minx, igraph_real_t maxx, igraph_real_t deltax, + igraph_real_t miny, igraph_real_t maxy, igraph_real_t deltay); +void igraph_2dgrid_destroy(igraph_2dgrid_t *grid); +void igraph_2dgrid_add(igraph_2dgrid_t *grid, igraph_int_t elem, + igraph_real_t xc, igraph_real_t yc); +void igraph_2dgrid_add2(igraph_2dgrid_t *grid, igraph_int_t elem); +void igraph_2dgrid_move(igraph_2dgrid_t *grid, igraph_int_t elem, + igraph_real_t xc, igraph_real_t yc); +void igraph_2dgrid_getcenter(const igraph_2dgrid_t *grid, + igraph_real_t *massx, igraph_real_t *massy); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_2dgrid_in(const igraph_2dgrid_t *grid, igraph_int_t elem); +IGRAPH_FUNCATTR_PURE igraph_real_t igraph_2dgrid_sq_dist(const igraph_2dgrid_t *grid, + igraph_int_t e1, igraph_int_t e2); + +typedef struct igraph_2dgrid_iterator_t { + igraph_int_t vid, x, y; + igraph_int_t nei; + igraph_int_t nx[4], ny[4], ncells; +} igraph_2dgrid_iterator_t; + +void igraph_2dgrid_reset(igraph_2dgrid_t *grid, igraph_2dgrid_iterator_t *it); +igraph_int_t igraph_2dgrid_next(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it); +igraph_int_t igraph_2dgrid_next_nei(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/heap.c b/src/core/heap.c new file mode 100644 index 0000000..ac0523d --- /dev/null +++ b/src/core/heap.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_heap.h" + +#define BASE_IGRAPH_REAL +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_INT + +#define BASE_CHAR +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_CHAR diff --git a/src/core/heap.pmt b/src/core/heap.pmt new file mode 100644 index 0000000..6b406c5 --- /dev/null +++ b/src/core/heap.pmt @@ -0,0 +1,377 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" + +#include /* memcpy & co. */ +#include + +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +/* Declare internal functions */ +static void FUNCTION(igraph_heap, i_build)(BASE* arr, igraph_int_t size, igraph_int_t head); +static void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, igraph_int_t size, igraph_int_t elem); +static void FUNCTION(igraph_heap, i_sink)(BASE* arr, igraph_int_t size, igraph_int_t head); +static void FUNCTION(igraph_heap, i_switch)(BASE* arr, igraph_int_t e1, igraph_int_t e2); + +/** + * \ingroup heap + * \function igraph_heap_init + * \brief Initializes an empty heap object. + * + * Creates an \em empty heap, and also allocates memory + * for some elements. + * + * \param h Pointer to an uninitialized heap object. + * \param capacity Number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p alloc_size), assuming memory allocation is a + * linear operation. + */ + +igraph_error_t FUNCTION(igraph_heap, init)(TYPE(igraph_heap)* h, igraph_int_t capacity) { + IGRAPH_ASSERT(capacity >= 0); + if (capacity == 0 ) { + capacity = 1; + } + h->stor_begin = IGRAPH_CALLOC(capacity, BASE); + IGRAPH_CHECK_OOM(h->stor_begin, "Cannot initialize heap."); + h->stor_end = h->stor_begin + capacity; + h->end = h->stor_begin; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup heap + * \function igraph_heap_init_array + * \brief Build a heap from an array. + * + * Initializes a heap object from an array. The heap is also + * built of course (constructor). + * + * \param h Pointer to an uninitialized heap object. + * \param data Pointer to an array of base data type. + * \param len The length of the array at \p data. + * \return Error code. + * + * Time complexity: O(n), the number of elements in the heap. + */ + +igraph_error_t FUNCTION(igraph_heap, init_array)(TYPE(igraph_heap) *h, const BASE *data, igraph_int_t len) { + h->stor_begin = IGRAPH_CALLOC(len, BASE); + IGRAPH_CHECK_OOM(h->stor_begin, "Cannot initialize heap from array."); + h->stor_end = h->stor_begin + len; + h->end = h->stor_end; + + memcpy(h->stor_begin, data, (size_t) len * sizeof(BASE)); + + FUNCTION(igraph_heap, i_build) (h->stor_begin, h->end - h->stor_begin, 0); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup heap + * \function igraph_heap_destroy + * \brief Destroys an initialized heap object. + * + * \param h The heap object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_heap, destroy)(TYPE(igraph_heap)* h) { + if (h->stor_begin != NULL) { + IGRAPH_FREE(h->stor_begin); /* sets to NULL */ + } +} + +/** + * \ingroup heap + * \function igraph_heap_empty + * \brief Decides whether a heap object is empty. + * + * \param h The heap object. + * \return \c true if the heap is empty, \c false otherwise. + * + * TIme complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_heap, empty)(const TYPE(igraph_heap)* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->stor_begin == h->end; +} + +/** + * \ingroup heap + * \function igraph_heap_clear + * \brief Removes all elements from a heap. + * + * This function simply sets the size of the heap to zero, it does + * not free any allocated memory. For that you have to call + * \ref igraph_heap_destroy(). + * + * \param h The heap object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_heap, clear)(TYPE(igraph_heap)* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + h->end = h->stor_begin; +} + +/** + * \ingroup heap + * \function igraph_heap_push + * \brief Add an element. + * + * Adds an element to the heap. + * + * \param h The heap object. + * \param elem The element to add. + * \return Error code. + * + * Time complexity: O(log n), n is the number of elements in the + * heap if no reallocation is needed, O(n) otherwise. It is ensured + * that n push operations are performed in O(n log n) time. + */ + +igraph_error_t FUNCTION(igraph_heap, push)(TYPE(igraph_heap)* h, BASE elem) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + igraph_int_t old_size = FUNCTION(igraph_heap, size)(h); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to heap, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(igraph_heap, reserve)(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + + /* maintain heap */ + FUNCTION(igraph_heap, i_shift_up)(h->stor_begin, FUNCTION(igraph_heap, size)(h), + FUNCTION(igraph_heap, size)(h) - 1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup heap + * \function igraph_heap_top + * \brief Top element. + * + * For maximum heaps this is the largest, for minimum heaps the + * smallest element of the heap. + * + * \param h The heap object. + * \return The top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_heap, top)(const TYPE(igraph_heap)* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + IGRAPH_ASSERT(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup heap + * \function igraph_heap_delete_top + * \brief Removes and returns the top element. + * + * Removes and returns the top element of the heap. For maximum heaps + * this is the largest, for minimum heaps the smallest element. + * + * \param h The heap object. + * \return The top element. + * + * Time complexity: O(log n), n is the number of elements in the + * heap. + */ + +BASE FUNCTION(igraph_heap, delete_top)(TYPE(igraph_heap)* h) { + BASE tmp; + + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + FUNCTION(igraph_heap, i_switch)(h->stor_begin, 0, FUNCTION(igraph_heap, size)(h) - 1); + h->end -= 1; + FUNCTION(igraph_heap, i_sink)(h->stor_begin, h->end - h->stor_begin, 0); + + return tmp; +} + +/** + * \ingroup heap + * \function igraph_heap_size + * \brief Number of elements in the heap. + * + * Gives the number of elements in a heap. + * + * \param h The heap object. + * \return The number of elements in the heap. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_heap, size)(const TYPE(igraph_heap)* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->end - h->stor_begin; +} + +/** + * \ingroup heap + * \function igraph_heap_reserve + * \brief Reserves memory for a heap. + * + * Allocates memory for future use. The size of the heap is + * unchanged. If the heap is larger than the \p capacity parameter then + * nothing happens. + * + * \param h The heap object. + * \param capacity The number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p capacity) if \p capacity is larger than the current + * number of elements. O(1) otherwise. + */ + +igraph_error_t FUNCTION(igraph_heap, reserve)(TYPE(igraph_heap)* h, igraph_int_t capacity) { + igraph_int_t actual_size = FUNCTION(igraph_heap, size)(h); + BASE *tmp; + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + if (capacity <= actual_size) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(h->stor_begin, (size_t) capacity, BASE); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for heap."); + + h->stor_begin = tmp; + h->stor_end = h->stor_begin + capacity; + h->end = h->stor_begin + actual_size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup heap + * \brief Build a heap, this should not be called directly. + */ + +void FUNCTION(igraph_heap, i_build)(BASE* arr, + igraph_int_t size, igraph_int_t head) { + + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head) ); + FUNCTION(igraph_heap, i_build)(arr, size, RIGHTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, head); + } else { + /* none */ + } +} + +/** + * \ingroup heap + * \brief Shift an element upwards in a heap, this should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, igraph_int_t size, igraph_int_t elem) { + + if (elem == 0 || arr[elem] HEAPLESS arr[PARENT(elem)]) { + /* at the top */ + } else { + FUNCTION(igraph_heap, i_switch)(arr, elem, PARENT(elem)); + FUNCTION(igraph_heap, i_shift_up)(arr, size, PARENT(elem)); + } +} + +/** + * \ingroup heap + * \brief Moves an element down in a heap, this function should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_sink)(BASE* arr, igraph_int_t size, igraph_int_t head) { + + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + arr[LEFTCHILD(head)] HEAPMOREEQ arr[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (arr[head] HEAPLESS arr[LEFTCHILD(head)]) { + FUNCTION(igraph_heap, i_switch)(arr, head, LEFTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (arr[head] HEAPLESS arr[RIGHTCHILD(head)]) { + FUNCTION(igraph_heap, i_switch)(arr, head, RIGHTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup heap + * \brief Switches two elements in a heap, this function should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_switch)(BASE* arr, igraph_int_t e1, igraph_int_t e2) { + if (e1 != e2) { + BASE tmp = arr[e1]; + arr[e1] = arr[e2]; + arr[e2] = tmp; + } +} diff --git a/src/core/indheap.c b/src/core/indheap.c new file mode 100644 index 0000000..662041a --- /dev/null +++ b/src/core/indheap.c @@ -0,0 +1,1025 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" + +#include "core/indheap.h" + +#include /* memcpy & co. */ +#include + +/* -------------------------------------------------- */ +/* Indexed heap */ +/* -------------------------------------------------- */ + +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +static void igraph_indheap_i_build(igraph_indheap_t* h, igraph_int_t head); +static void igraph_indheap_i_shift_up(igraph_indheap_t* h, igraph_int_t elem); +static void igraph_indheap_i_sink(igraph_indheap_t* h, igraph_int_t head); +static void igraph_indheap_i_switch(igraph_indheap_t* h, igraph_int_t e1, igraph_int_t e2); + +/** + * \ingroup indheap + * \brief Initializes an indexed heap (constructor). + * + * \return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +igraph_error_t igraph_indheap_init(igraph_indheap_t* h, igraph_int_t alloc_size) { + IGRAPH_ASSERT(alloc_size >= 0); + if (alloc_size == 0 ) { + alloc_size = 1; + } + h->stor_begin = IGRAPH_CALLOC(alloc_size, igraph_real_t); + if (! h->stor_begin) { + h->index_begin = NULL; + IGRAPH_ERROR("indheap init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + h->index_begin = IGRAPH_CALLOC(alloc_size, igraph_int_t); + if (! h->index_begin) { + IGRAPH_FREE(h->stor_begin); + h->stor_begin = NULL; + IGRAPH_ERROR("indheap init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin; + + return IGRAPH_SUCCESS; +} + +void igraph_indheap_clear(igraph_indheap_t *h) { + h->end = h->stor_begin; +} + +/** + * \ingroup indheap + * \brief Initializes and build an indexed heap from a C array (constructor). + * + * \return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +igraph_error_t igraph_indheap_init_array(igraph_indheap_t *h, const igraph_real_t *data, igraph_int_t len) { + igraph_int_t i; + igraph_int_t alloc_size; + + IGRAPH_ASSERT(len >= 0); + alloc_size = (len <= 0) ? 1 : len; + + h->stor_begin = IGRAPH_CALLOC(alloc_size, igraph_real_t); + if (! h->stor_begin) { + h->index_begin = 0; + IGRAPH_ERROR("indheap init from array failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + h->index_begin = IGRAPH_CALLOC(alloc_size, igraph_int_t); + if (! h->index_begin) { + IGRAPH_FREE(h->stor_begin); + h->stor_begin = 0; + IGRAPH_ERROR("indheap init from array failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin + len; + + memcpy(h->stor_begin, data, (size_t) len * sizeof(igraph_real_t)); + for (i = 0; i < len; i++) { + h->index_begin[i] = i + 1; + } + + igraph_indheap_i_build(h, 0); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup indheap + * \brief Destroys an initialized indexed heap. + */ + +void igraph_indheap_destroy(igraph_indheap_t* h) { + IGRAPH_ASSERT(h != NULL); + if (h->stor_begin != NULL) { + IGRAPH_FREE(h->stor_begin); + } + if (h->index_begin != NULL) { + IGRAPH_FREE(h->index_begin); + } +} + +/** + * \ingroup indheap + * \brief Checks whether a heap is empty. + */ + +igraph_bool_t igraph_indheap_empty(const igraph_indheap_t *h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->stor_begin == h->end; +} + +/** + * \ingroup indheap + * \brief Adds an element to an indexed heap. + */ + +igraph_error_t igraph_indheap_push(igraph_indheap_t* h, igraph_real_t elem) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + igraph_int_t old_size = igraph_indheap_size(h); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to indheap, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_indheap_size(h) - 1) = igraph_indheap_size(h) - 1; + + /* maintain indheap */ + igraph_indheap_i_shift_up(h, igraph_indheap_size(h) - 1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup indheap + * \brief Adds an element to an indexed heap with a given index. + */ + +igraph_error_t igraph_indheap_push_with_index(igraph_indheap_t* h, igraph_int_t idx, igraph_real_t elem) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + igraph_int_t old_size = igraph_indheap_size(h); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to indheap, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_indheap_size(h) - 1) = idx; + + /* maintain indheap */ + igraph_indheap_i_shift_up(h, igraph_indheap_size(h) - 1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup indheap + * \brief Modifies an element in an indexed heap. + */ + +void igraph_indheap_modify(igraph_indheap_t* h, igraph_int_t idx, igraph_real_t elem) { + igraph_int_t i, n; + + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + n = igraph_indheap_size(h); + for (i = 0; i < n; i++) + if (h->index_begin[i] == idx) { + h->stor_begin[i] = elem; + break; + } + + if (i == n) { + return; + } + + /* maintain indheap */ + igraph_indheap_i_build(h, 0); +} + +/** + * \ingroup indheap + * \brief Returns the largest element in an indexed heap. + */ + +igraph_real_t igraph_indheap_max(const igraph_indheap_t *h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + IGRAPH_ASSERT(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup indheap + * \brief Removes the largest element from an indexed heap. + */ + +igraph_real_t igraph_indheap_delete_max(igraph_indheap_t* h) { + igraph_real_t tmp; + + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + igraph_indheap_i_switch(h, 0, igraph_indheap_size(h) - 1); + h->end -= 1; + igraph_indheap_i_sink(h, 0); + + return tmp; +} + +/** + * \ingroup indheap + * \brief Gives the number of elements in an indexed heap. + */ + +igraph_int_t igraph_indheap_size(const igraph_indheap_t* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->end - h->stor_begin; +} + +/** + * \ingroup indheap + * \brief Reserves more memory for an indexed heap. + * + * \return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +igraph_error_t igraph_indheap_reserve(igraph_indheap_t* h, igraph_int_t size) { + igraph_int_t actual_size = igraph_indheap_size(h); + igraph_real_t *tmp1; + igraph_int_t *tmp2; + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + if (size <= actual_size) { + return IGRAPH_SUCCESS; + } + + tmp1 = IGRAPH_CALLOC(size, igraph_real_t); + if (tmp1 == 0) { + IGRAPH_ERROR("indheap reserve failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmp1); + tmp2 = IGRAPH_CALLOC(size, igraph_int_t); + if (tmp2 == 0) { + IGRAPH_ERROR("indheap reserve failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmp2); + memcpy(tmp1, h->stor_begin, (size_t) actual_size * sizeof(igraph_real_t)); + memcpy(tmp2, h->index_begin, (size_t) actual_size * sizeof(igraph_int_t)); + IGRAPH_FREE(h->stor_begin); + IGRAPH_FREE(h->index_begin); + + h->stor_begin = tmp1; + h->index_begin = tmp2; + h->stor_end = h->stor_begin + size; + h->end = h->stor_begin + actual_size; + + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup indheap + * \brief Returns the index of the largest element in an indexed heap. + */ + +igraph_int_t igraph_indheap_max_index(const igraph_indheap_t *h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->index_begin[0]; +} + +/** + * \ingroup indheap + * \brief Builds an indexed heap, this function should not be called + * directly. + */ + +static void igraph_indheap_i_build(igraph_indheap_t* h, igraph_int_t head) { + + igraph_int_t size = igraph_indheap_size(h); + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + igraph_indheap_i_build(h, LEFTCHILD(head) ); + igraph_indheap_i_build(h, RIGHTCHILD(head)); + igraph_indheap_i_sink(h, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + igraph_indheap_i_build(h, LEFTCHILD(head)); + igraph_indheap_i_sink(h, head); + } else { + /* none */ + } +} + +/** + * \ingroup indheap + * \brief Moves an element up in the heap, don't call this function + * directly. + */ + +static void igraph_indheap_i_shift_up(igraph_indheap_t *h, igraph_int_t elem) { + + if (elem == 0 || h->stor_begin[elem] < h->stor_begin[PARENT(elem)]) { + /* at the top */ + } else { + igraph_indheap_i_switch(h, elem, PARENT(elem)); + igraph_indheap_i_shift_up(h, PARENT(elem)); + } +} + +/** + * \ingroup indheap + * \brief Moves an element down in the heap, don't call this function + * directly. + */ + +static void igraph_indheap_i_sink(igraph_indheap_t* h, igraph_int_t head) { + + igraph_int_t size = igraph_indheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + h->stor_begin[LEFTCHILD(head)] >= h->stor_begin[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (h->stor_begin[head] < h->stor_begin[LEFTCHILD(head)]) { + igraph_indheap_i_switch(h, head, LEFTCHILD(head)); + igraph_indheap_i_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (h->stor_begin[head] < h->stor_begin[RIGHTCHILD(head)]) { + igraph_indheap_i_switch(h, head, RIGHTCHILD(head)); + igraph_indheap_i_sink(h, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup indheap + * \brief Switches two elements in a heap, don't call this function + * directly. + */ + +static void igraph_indheap_i_switch(igraph_indheap_t* h, igraph_int_t e1, igraph_int_t e2) { + if (e1 != e2) { + igraph_real_t tmp = h->stor_begin[e1]; + h->stor_begin[e1] = h->stor_begin[e2]; + h->stor_begin[e2] = tmp; + + tmp = h->index_begin[e1]; + h->index_begin[e1] = h->index_begin[e2]; + h->index_begin[e2] = tmp; + } +} + +/*************************************************/ + +/* -------------------------------------------------- */ +/* Doubly indexed heap */ +/* -------------------------------------------------- */ + +/* static void igraph_d_indheap_i_build(igraph_d_indheap_t* h, igraph_int_t head); */ /* Unused function */ +static void igraph_d_indheap_i_shift_up(igraph_d_indheap_t* h, igraph_int_t elem); +static void igraph_d_indheap_i_sink(igraph_d_indheap_t* h, igraph_int_t head); +static void igraph_d_indheap_i_switch(igraph_d_indheap_t* h, igraph_int_t e1, igraph_int_t e2); + +/** + * \ingroup doubleindheap + * \brief Initializes an empty doubly indexed heap object (constructor). + * + * \return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +igraph_error_t igraph_d_indheap_init(igraph_d_indheap_t* h, igraph_int_t alloc_size) { + IGRAPH_ASSERT(alloc_size >= 0); + if (alloc_size == 0 ) { + alloc_size = 1; + } + h->stor_begin = IGRAPH_CALLOC(alloc_size, igraph_real_t); + if (h->stor_begin == NULL) { + h->index_begin = NULL; + h->index2_begin = NULL; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin; + h->index_begin = IGRAPH_CALLOC(alloc_size, igraph_int_t); + if (h->index_begin == NULL) { + IGRAPH_FREE(h->stor_begin); + h->stor_begin = NULL; + h->index2_begin = NULL; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + h->index2_begin = IGRAPH_CALLOC(alloc_size, igraph_int_t); + if (h->index2_begin == NULL) { + IGRAPH_FREE(h->stor_begin); + IGRAPH_FREE(h->index_begin); + h->stor_begin = NULL; + h->index_begin = NULL; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup doubleindheap + * \brief Destroys an initialized doubly indexed heap object. + */ + +void igraph_d_indheap_destroy(igraph_d_indheap_t* h) { + IGRAPH_ASSERT(h != NULL); + if (h->stor_begin != NULL) { + IGRAPH_FREE(h->stor_begin); + } + if (h->index_begin != NULL) { + IGRAPH_FREE(h->index_begin); + } + if (h->index2_begin != NULL) { + IGRAPH_FREE(h->index2_begin); + } +} + +/** + * \ingroup doubleindheap + * \brief Decides whether a heap is empty. + */ + +igraph_bool_t igraph_d_indheap_empty(const igraph_d_indheap_t *h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->stor_begin == h->end; +} + +/** + * \ingroup doubleindheap + * \brief Adds an element to the heap. + */ + +igraph_error_t igraph_d_indheap_push(igraph_d_indheap_t* h, igraph_real_t elem, + igraph_int_t idx, igraph_int_t idx2) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + igraph_int_t old_size = igraph_d_indheap_size(h); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to indheap, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_d_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_d_indheap_size(h) - 1) = idx ; + *(h->index2_begin + igraph_d_indheap_size(h) - 1) = idx2 ; + + /* maintain d_indheap */ + igraph_d_indheap_i_shift_up(h, igraph_d_indheap_size(h) - 1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup doubleindheap + * \brief Returns the largest element in the heap. + */ + +igraph_real_t igraph_d_indheap_max(const igraph_d_indheap_t *h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + IGRAPH_ASSERT(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup doubleindheap + * \brief Removes the largest element from the heap. + */ + +igraph_real_t igraph_d_indheap_delete_max(igraph_d_indheap_t* h) { + igraph_real_t tmp; + + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + igraph_d_indheap_i_switch(h, 0, igraph_d_indheap_size(h) - 1); + h->end -= 1; + igraph_d_indheap_i_sink(h, 0); + + return tmp; +} + +/** + * \ingroup doubleindheap + * \brief Gives the number of elements in the heap. + */ + +igraph_int_t igraph_d_indheap_size(const igraph_d_indheap_t* h) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + return h->end - h->stor_begin; +} + +/** + * \ingroup doubleindheap + * \brief Allocates memory for a heap. + * + * \return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +igraph_error_t igraph_d_indheap_reserve(igraph_d_indheap_t* h, igraph_int_t size) { + igraph_int_t actual_size = igraph_d_indheap_size(h); + igraph_real_t *tmp1; + igraph_int_t *tmp2, *tmp3; + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + + if (size <= actual_size) { + return IGRAPH_SUCCESS; + } + + tmp1 = IGRAPH_CALLOC(size, igraph_real_t); + if (tmp1 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmp1); + tmp2 = IGRAPH_CALLOC(size, igraph_int_t); + if (tmp2 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmp2); + tmp3 = IGRAPH_CALLOC(size, igraph_int_t); + if (tmp3 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, tmp3); + + memcpy(tmp1, h->stor_begin, (size_t) actual_size * sizeof(igraph_real_t)); + memcpy(tmp2, h->index_begin, (size_t) actual_size * sizeof(igraph_int_t)); + memcpy(tmp3, h->index2_begin, (size_t) actual_size * sizeof(igraph_int_t)); + IGRAPH_FREE(h->stor_begin); + IGRAPH_FREE(h->index_begin); + IGRAPH_FREE(h->index2_begin); + + h->stor_begin = tmp1; + h->stor_end = h->stor_begin + size; + h->end = h->stor_begin + actual_size; + h->index_begin = tmp2; + h->index2_begin = tmp3; + + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup doubleindheap + * \brief Gives the indices of the maximal element in the heap. + */ + +void igraph_d_indheap_max_index(igraph_d_indheap_t *h, igraph_int_t *idx, igraph_int_t *idx2) { + IGRAPH_ASSERT(h != NULL); + IGRAPH_ASSERT(h->stor_begin != NULL); + (*idx) = h->index_begin[0]; + (*idx2) = h->index2_begin[0]; +} + +/** + * \ingroup doubleindheap + * \brief Builds the heap, don't call it directly. + */ + +/* Unused function, temporarily disabled */ +#if 0 +static void igraph_d_indheap_i_build(igraph_d_indheap_t* h, igraph_int_t head) { + + igraph_int_t size = igraph_d_indheap_size(h); + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + igraph_d_indheap_i_build(h, LEFTCHILD(head) ); + igraph_d_indheap_i_build(h, RIGHTCHILD(head)); + igraph_d_indheap_i_sink(h, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + igraph_d_indheap_i_build(h, LEFTCHILD(head)); + igraph_d_indheap_i_sink(h, head); + } else { + /* none */ + } +} +#endif + +/** + * \ingroup doubleindheap + * \brief Moves an element up in the heap, don't call it directly. + */ + +static void igraph_d_indheap_i_shift_up(igraph_d_indheap_t *h, igraph_int_t elem) { + + if (elem == 0 || h->stor_begin[elem] < h->stor_begin[PARENT(elem)]) { + /* at the top */ + } else { + igraph_d_indheap_i_switch(h, elem, PARENT(elem)); + igraph_d_indheap_i_shift_up(h, PARENT(elem)); + } +} + +/** + * \ingroup doubleindheap + * \brief Moves an element down in the heap, don't call it directly. + */ + +static void igraph_d_indheap_i_sink(igraph_d_indheap_t* h, igraph_int_t head) { + + igraph_int_t size = igraph_d_indheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + h->stor_begin[LEFTCHILD(head)] >= h->stor_begin[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (h->stor_begin[head] < h->stor_begin[LEFTCHILD(head)]) { + igraph_d_indheap_i_switch(h, head, LEFTCHILD(head)); + igraph_d_indheap_i_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (h->stor_begin[head] < h->stor_begin[RIGHTCHILD(head)]) { + igraph_d_indheap_i_switch(h, head, RIGHTCHILD(head)); + igraph_d_indheap_i_sink(h, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup doubleindheap + * \brief Switches two elements in the heap, don't call it directly. + */ + +static void igraph_d_indheap_i_switch(igraph_d_indheap_t* h, igraph_int_t e1, igraph_int_t e2) { + if (e1 != e2) { + igraph_int_t tmpi; + igraph_real_t tmp = h->stor_begin[e1]; + h->stor_begin[e1] = h->stor_begin[e2]; + h->stor_begin[e2] = tmp; + + tmpi = h->index_begin[e1]; + h->index_begin[e1] = h->index_begin[e2]; + h->index_begin[e2] = tmpi; + + tmpi = h->index2_begin[e1]; + h->index2_begin[e1] = h->index2_begin[e2]; + h->index2_begin[e2] = tmpi; + } +} + +/*************************************************/ + +/* -------------------------------------------------- */ +/* Two-way indexed heap */ +/* -------------------------------------------------- */ + +#undef PARENT +#undef LEFTCHILD +#undef RIGHTCHILD +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +/* This is a smart indexed heap. In addition to the "normal" indexed heap + it allows to access every element through its index in O(1) time. + In other words, for this heap the indexing operation is O(1), the + normal heap does this in O(n) time.... */ + +static void igraph_i_2wheap_switch(igraph_2wheap_t *h, + igraph_int_t e1, igraph_int_t e2) { + if (e1 != e2) { + igraph_int_t tmp1, tmp2; + igraph_real_t tmp3 = VECTOR(h->data)[e1]; + VECTOR(h->data)[e1] = VECTOR(h->data)[e2]; + VECTOR(h->data)[e2] = tmp3; + + tmp1 = VECTOR(h->index)[e1]; + tmp2 = VECTOR(h->index)[e2]; + + VECTOR(h->index2)[tmp1] = e2 + 2; + VECTOR(h->index2)[tmp2] = e1 + 2; + + VECTOR(h->index)[e1] = tmp2; + VECTOR(h->index)[e2] = tmp1; + } +} + +static void igraph_i_2wheap_shift_up(igraph_2wheap_t *h, + igraph_int_t elem) { + if (elem == 0 || VECTOR(h->data)[elem] < VECTOR(h->data)[PARENT(elem)]) { + /* at the top */ + } else { + igraph_i_2wheap_switch(h, elem, PARENT(elem)); + igraph_i_2wheap_shift_up(h, PARENT(elem)); + } +} + +static void igraph_i_2wheap_sink(igraph_2wheap_t *h, + igraph_int_t head) { + igraph_int_t size = igraph_2wheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + VECTOR(h->data)[LEFTCHILD(head)] >= VECTOR(h->data)[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (VECTOR(h->data)[head] < VECTOR(h->data)[LEFTCHILD(head)]) { + igraph_i_2wheap_switch(h, head, LEFTCHILD(head)); + igraph_i_2wheap_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (VECTOR(h->data)[head] < VECTOR(h->data)[RIGHTCHILD(head)]) { + igraph_i_2wheap_switch(h, head, RIGHTCHILD(head)); + igraph_i_2wheap_sink(h, RIGHTCHILD(head)); + } + } +} + +/* ------------------ */ +/* These are public */ +/* ------------------ */ + +/** + * Initializes a new two-way heap. The max_size parameter defines the maximum + * number of items that the heap can hold. + */ +igraph_error_t igraph_2wheap_init(igraph_2wheap_t *h, igraph_int_t max_size) { + h->max_size = max_size; + /* We start with the biggest */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&h->index2, max_size); + IGRAPH_VECTOR_INIT_FINALLY(&h->data, 0); + IGRAPH_CHECK(igraph_vector_int_init(&h->index, 0)); + /* IGRAPH_FINALLY(igraph_vector_int_destroy, &h->index); */ + + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/** + * Destroys a two-way heap. + */ +void igraph_2wheap_destroy(igraph_2wheap_t *h) { + igraph_vector_destroy(&h->data); + igraph_vector_int_destroy(&h->index); + igraph_vector_int_destroy(&h->index2); +} + +/** + * Clears a two-way heap, i.e. removes all the elements from the heap. + */ +void igraph_2wheap_clear(igraph_2wheap_t *h) { + igraph_vector_clear(&h->data); + igraph_vector_int_clear(&h->index); + igraph_vector_int_null(&h->index2); +} + +/** + * Returns whether the two-way heap is empty. + */ +igraph_bool_t igraph_2wheap_empty(const igraph_2wheap_t *h) { + return igraph_vector_empty(&h->data); +} + +/** + * Pushes a new element into the two-way heap, with the given associated index. + * The index must be between 0 and the size of the heap minus 1, inclusive. It + * is assumed (and not checked) that the heap does not have another item with + * the same index. + */ +igraph_error_t igraph_2wheap_push_with_index(igraph_2wheap_t *h, + igraph_int_t idx, igraph_real_t elem) { + + /* printf("-> %.2g [%li]\n", elem, idx); */ + + igraph_int_t size = igraph_vector_size(&h->data); + + if (size > IGRAPH_INTEGER_MAX - 2) { + /* to allow size+2 below */ + IGRAPH_ERROR("Cannot push to 2wheap, already at maximum size.", IGRAPH_EOVERFLOW); + } + + IGRAPH_CHECK(igraph_vector_push_back(&h->data, elem)); + IGRAPH_CHECK(igraph_vector_int_push_back(&h->index, idx)); + VECTOR(h->index2)[idx] = size + 2; + + /* maintain heap */ + igraph_i_2wheap_shift_up(h, size); + return IGRAPH_SUCCESS; +} + +/** + * Returns the current number of elements in the two-way heap. + */ +igraph_int_t igraph_2wheap_size(const igraph_2wheap_t *h) { + return igraph_vector_size(&h->data); +} + +/** + * Returns the maximum number of elements that the two-way heap can hold. This + * is also one larger than the maximum allowed index that can be passed to + * \c igraph_2wheap_push_with_index . + */ +igraph_int_t igraph_2wheap_max_size(const igraph_2wheap_t *h) { + return h->max_size; +} + +/** + * Returns the largest element in the heap. + */ +igraph_real_t igraph_2wheap_max(const igraph_2wheap_t *h) { + return VECTOR(h->data)[0]; +} + +/** + * Returns the index that was associated to the largest element in the heap + * when it was pushed to the heap. + */ +igraph_int_t igraph_2wheap_max_index(const igraph_2wheap_t *h) { + return VECTOR(h->index)[0]; +} + +/** + * Returns whether the heap contains an element with the given index, even if + * it was deactivated earlier. + */ +igraph_bool_t igraph_2wheap_has_elem(const igraph_2wheap_t *h, igraph_int_t idx) { + return (VECTOR(h->index2)[idx] != 0); +} + +/** + * Returns whether the heap contains an element with the given index \em and it + * has not been deactivated yet. + */ +igraph_bool_t igraph_2wheap_has_active(const igraph_2wheap_t *h, igraph_int_t idx) { + return VECTOR(h->index2)[idx] > 1; +} + +/** + * Returns the item at the given index in the two-way heap. + */ +igraph_real_t igraph_2wheap_get(const igraph_2wheap_t *h, igraph_int_t idx) { + igraph_int_t i = VECTOR(h->index2)[idx] - 2; + return VECTOR(h->data)[i]; +} + +/** + * Deletes and returns the largest element from the two-way heap. + * + * This function does \em not change the indices associated to the elements + * that remain in the heap. + */ +igraph_real_t igraph_2wheap_delete_max(igraph_2wheap_t *h) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + igraph_int_t tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_int_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 0; + igraph_i_2wheap_sink(h, 0); + + /* printf("<-max %.2g\n", tmp); */ + + return tmp; +} + +/** + * Deactivates and returns the largest element from the two-way heap. + * + * This function does \em not change the indices associated to the elements + * that remain in the heap. + */ +igraph_real_t igraph_2wheap_deactivate_max(igraph_2wheap_t *h) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + igraph_int_t tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_int_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 1; + igraph_i_2wheap_sink(h, 0); + + return tmp; +} + +/** + * Deletes the largest element from the heap and returns it along with its + * associated index (the latter being returned in an output argument). + * + * This function does \em not change the indices associated to the elements + * that remain in the heap. + */ +igraph_real_t igraph_2wheap_delete_max_index(igraph_2wheap_t *h, igraph_int_t *idx) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + igraph_int_t tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_int_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 0; + igraph_i_2wheap_sink(h, 0); + + if (idx) { + *idx = tmpidx; + } + return tmp; +} + +/** + * Modifies the value associated to the given index in the two-way heap. + */ +void igraph_2wheap_modify(igraph_2wheap_t *h, igraph_int_t idx, igraph_real_t elem) { + + igraph_int_t pos = VECTOR(h->index2)[idx] - 2; + + /* printf("-- %.2g -> %.2g\n", VECTOR(h->data)[pos], elem); */ + + VECTOR(h->data)[pos] = elem; + igraph_i_2wheap_sink(h, pos); + igraph_i_2wheap_shift_up(h, pos); +} + +/** + * Checks that the heap is in a consistent state + */ +igraph_error_t igraph_2wheap_check(const igraph_2wheap_t *h) { + igraph_int_t size = igraph_2wheap_size(h); + igraph_int_t i; + igraph_bool_t error = false; + + /* Check the heap property */ + for (i = 0; i < size; i++) { + if (LEFTCHILD(i) >= size) { + break; + } + if (VECTOR(h->data)[LEFTCHILD(i)] > VECTOR(h->data)[i]) { + error = true; break; + } + if (RIGHTCHILD(i) >= size) { + break; + } + if (VECTOR(h->data)[RIGHTCHILD(i)] > VECTOR(h->data)[i]) { + error = true; break; + } + } + + if (error) { + IGRAPH_ERROR("Inconsistent heap.", IGRAPH_EINTERNAL); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/core/indheap.h b/src/core/indheap.h new file mode 100644 index 0000000..a718b45 --- /dev/null +++ b/src/core/indheap.h @@ -0,0 +1,154 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CORE_INDHEAP_H +#define IGRAPH_CORE_INDHEAP_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Indexed heap */ +/* -------------------------------------------------- */ + +/** + * Indexed heap data type. + * \ingroup internal + */ + +typedef struct s_indheap { + igraph_real_t* stor_begin; + igraph_real_t* stor_end; + igraph_real_t* end; + igraph_int_t* index_begin; +} igraph_indheap_t; + +#define IGRAPH_INDHEAP_NULL { 0,0,0,0 } + +igraph_error_t igraph_indheap_init(igraph_indheap_t* h, igraph_int_t size); +igraph_error_t igraph_indheap_init_array(igraph_indheap_t *t, const igraph_real_t *data, igraph_int_t len); +void igraph_indheap_destroy(igraph_indheap_t* h); +void igraph_indheap_clear(igraph_indheap_t *h); +IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_indheap_empty(const igraph_indheap_t* h); +igraph_error_t igraph_indheap_push(igraph_indheap_t* h, igraph_real_t elem); +igraph_error_t igraph_indheap_push_with_index(igraph_indheap_t* h, igraph_int_t idx, igraph_real_t elem); +void igraph_indheap_modify(igraph_indheap_t* h, igraph_int_t idx, igraph_real_t elem); +IGRAPH_FUNCATTR_PURE igraph_real_t igraph_indheap_max(const igraph_indheap_t* h); +igraph_real_t igraph_indheap_delete_max(igraph_indheap_t* h); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_indheap_size(const igraph_indheap_t *h); +igraph_error_t igraph_indheap_reserve(igraph_indheap_t* h, igraph_int_t size); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_indheap_max_index(const igraph_indheap_t *h); + + +/* -------------------------------------------------- */ +/* Doubly indexed heap */ +/* -------------------------------------------------- */ + +/* This is a heap containing double elements and + two indices, its intended usage is the storage of + weighted edges. +*/ + +/** + * Doubly indexed heap data type. + * \ingroup internal + */ + +typedef struct s_indheap_d { + igraph_real_t* stor_begin; + igraph_real_t* stor_end; + igraph_real_t* end; + igraph_int_t* index_begin; + igraph_int_t* index2_begin; +} igraph_d_indheap_t; + + +#define IGRAPH_D_INDHEAP_NULL { 0,0,0,0,0 } + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_d_indheap_init(igraph_d_indheap_t *h, igraph_int_t size); +IGRAPH_PRIVATE_EXPORT void igraph_d_indheap_destroy(igraph_d_indheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_bool_t igraph_d_indheap_empty(const igraph_d_indheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_d_indheap_push(igraph_d_indheap_t *h, igraph_real_t elem, + igraph_int_t idx, igraph_int_t idx2); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_d_indheap_max(const igraph_d_indheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_real_t igraph_d_indheap_delete_max(igraph_d_indheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_d_indheap_size(const igraph_d_indheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_d_indheap_reserve(igraph_d_indheap_t *h, igraph_int_t size); +IGRAPH_PRIVATE_EXPORT void igraph_d_indheap_max_index(igraph_d_indheap_t *h, igraph_int_t *idx, igraph_int_t *idx2); + +/* -------------------------------------------------- */ +/* Two-way indexed heap */ +/* -------------------------------------------------- */ + +/* This is a smart indexed heap. In addition to the "normal" indexed heap + it allows to access every element through its index in O(1) time. + In other words, for this heap the _modify operation is O(1), the + normal heap does this in O(n) time.... */ + +typedef struct igraph_2wheap_t { + /** Number of items in the heap */ + igraph_int_t max_size; + + /** The items themselves in the heap */ + igraph_vector_t data; + + /** An integer index associated to each item in the heap; this vector is + * always modified in tandem with \c data . Values in this vector are + * between 0 and size-1 */ + igraph_vector_int_t index; + + /** + * A _reverse_ index that allows O(1) lookup of the position of a given + * value within the \c index member. Note that it uses two special values: + * index2[i] == 0 means that \c i is not in \c index at all, while + * index2[i] == 1 means that \c i is in \c index but it was _deactivated_. + * The semantics of deactivation is up to the user of the data structure + * to decide. Other than these two special values, index2[i] == j means + * that index[j-2] == i and data[j-2] is the corresponding item in the heap + */ + igraph_vector_int_t index2; +} igraph_2wheap_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_2wheap_init(igraph_2wheap_t *h, igraph_int_t size); +IGRAPH_PRIVATE_EXPORT void igraph_2wheap_destroy(igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT void igraph_2wheap_clear(igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_2wheap_push_with_index(igraph_2wheap_t *h, + igraph_int_t idx, igraph_real_t elem); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_2wheap_empty(const igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_2wheap_size(const igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_2wheap_max_size(const igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_2wheap_max(const igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_2wheap_max_index(const igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_real_t igraph_2wheap_deactivate_max(igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_2wheap_has_elem(const igraph_2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_2wheap_has_active(const igraph_2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_real_t igraph_2wheap_get(const igraph_2wheap_t *h, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT igraph_real_t igraph_2wheap_delete_max(igraph_2wheap_t *h); +IGRAPH_PRIVATE_EXPORT igraph_real_t igraph_2wheap_delete_max_index(igraph_2wheap_t *h, igraph_int_t *idx); +IGRAPH_PRIVATE_EXPORT void igraph_2wheap_modify(igraph_2wheap_t *h, igraph_int_t idx, igraph_real_t elem); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_2wheap_check(const igraph_2wheap_t *h); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/interruption.c b/src/core/interruption.c new file mode 100644 index 0000000..2c30c17 --- /dev/null +++ b/src/core/interruption.c @@ -0,0 +1,40 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interrupt.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +IGRAPH_THREAD_LOCAL igraph_interruption_handler_t *igraph_i_interruption_handler = NULL; + +igraph_bool_t igraph_allow_interruption(void) { + if (igraph_i_interruption_handler) { + return igraph_i_interruption_handler(); + } + return false; +} + +igraph_interruption_handler_t *igraph_set_interruption_handler (igraph_interruption_handler_t *new_handler) { + igraph_interruption_handler_t *previous_handler = igraph_i_interruption_handler; + igraph_i_interruption_handler = new_handler; + return previous_handler; +} diff --git a/src/core/interruption.h b/src/core/interruption.h new file mode 100644 index 0000000..b05f671 --- /dev/null +++ b/src/core/interruption.h @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_INTERRUPT_INTERNAL_H +#define IGRAPH_INTERRUPT_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_interrupt.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +IGRAPH_BEGIN_C_DECLS + +extern IGRAPH_THREAD_LOCAL igraph_interruption_handler_t *igraph_i_interruption_handler; + +/** + * \define IGRAPH_ALLOW_INTERRUPTION + * \brief + * + * This macro should be called when interruption is allowed. It calls + * \ref igraph_allow_interruption() and if that returns anything but + * \c IGRAPH_SUCCESS then the macro returns the "calling" function as well, + * with the proper error code (\c IGRAPH_INTERRUPTED). + */ + +#define IGRAPH_ALLOW_INTERRUPTION() \ + do { \ + if (igraph_i_interruption_handler) { \ + if (igraph_allow_interruption()) { \ + return IGRAPH_INTERRUPTED; \ + } \ + } \ + } while (0) + +/** + * \define IGRAPH_ALLOW_INTERRUPTION_LIMITED + * + * This is a variant of IGRAPH_ALLOW_INTERRUPTION() that checks for interruption + * only on every 'skips' call. The 'iter' macro parameter is the name of a variable, + * usually of type 'int', that is used to count calls to this macto. It must be declared + * separately, outside of the loop where IGRAPH_ALLOW_INTERRUPTION_LIMITED() is called, + * and initialized to 0. Example: + * + * int myiter = 0; + * for (igraph_int_t i=0; i < n; i++) { + * // Allow for interruption every 1000th iteration + * IGRAPH_ALLOW_INTERRUPTION_LIMITED(myiter, 1000); + * } + * + */ +#define IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, skips) \ + do { \ + if (++iter >= skips) { \ + IGRAPH_ALLOW_INTERRUPTION(); \ + iter = 0; \ + } \ + } while (0) + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/marked_queue.c b/src/core/marked_queue.c new file mode 100644 index 0000000..7c8da93 --- /dev/null +++ b/src/core/marked_queue.c @@ -0,0 +1,114 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "core/marked_queue.h" + +#define BATCH_MARKER -1 + +igraph_error_t igraph_marked_queue_int_init(igraph_marked_queue_int_t *q, + igraph_int_t size) { + IGRAPH_CHECK(igraph_dqueue_int_init(&q->Q, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q->Q); + IGRAPH_CHECK(igraph_vector_int_init(&q->set, size)); + q->mark = 1; + q->size = 0; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +void igraph_marked_queue_int_destroy(igraph_marked_queue_int_t *q) { + igraph_vector_int_destroy(&q->set); + igraph_dqueue_int_destroy(&q->Q); +} + +void igraph_marked_queue_int_reset(igraph_marked_queue_int_t *q) { + igraph_dqueue_int_clear(&q->Q); + q->size = 0; + q->mark += 1; + if (q->mark == 0) { + igraph_vector_int_null(&q->set); + q->mark += 1; + } +} + +igraph_bool_t igraph_marked_queue_int_empty(const igraph_marked_queue_int_t *q) { + return q->size == 0; +} + +igraph_int_t igraph_marked_queue_int_size(const igraph_marked_queue_int_t *q) { + return q->size; +} + +igraph_bool_t igraph_marked_queue_int_iselement(const igraph_marked_queue_int_t *q, + igraph_int_t elem) { + return (VECTOR(q->set)[elem] == q->mark); +} + +igraph_error_t igraph_marked_queue_int_push(igraph_marked_queue_int_t *q, igraph_int_t elem) { + if (VECTOR(q->set)[elem] != q->mark) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q->Q, elem)); + VECTOR(q->set)[elem] = q->mark; + q->size += 1; + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_marked_queue_int_start_batch(igraph_marked_queue_int_t *q) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q->Q, BATCH_MARKER)); + return IGRAPH_SUCCESS; +} + +void igraph_marked_queue_int_pop_back_batch(igraph_marked_queue_int_t *q) { + igraph_int_t size = igraph_dqueue_int_size(&q->Q); + igraph_int_t elem; + while (size > 0 && + (elem = igraph_dqueue_int_pop_back(&q->Q)) != BATCH_MARKER) { + VECTOR(q->set)[elem] = 0; + size--; + q->size--; + } +} + +#ifndef USING_R +igraph_error_t igraph_marked_queue_int_print(const igraph_marked_queue_int_t *q) { + IGRAPH_CHECK(igraph_dqueue_int_print(&q->Q)); + return IGRAPH_SUCCESS; +} +#endif + +igraph_error_t igraph_marked_queue_int_fprint(const igraph_marked_queue_int_t *q, FILE *file) { + IGRAPH_CHECK(igraph_dqueue_int_fprint(&q->Q, file)); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_marked_queue_int_as_vector(const igraph_marked_queue_int_t *q, + igraph_vector_int_t *vec) { + igraph_int_t i, p, n = igraph_dqueue_int_size(&q->Q); + IGRAPH_CHECK(igraph_vector_int_resize(vec, q->size)); + for (i = 0, p = 0; i < n; i++) { + igraph_int_t e = igraph_dqueue_int_get(&q->Q, i); + if (e != BATCH_MARKER) { + VECTOR(*vec)[p++] = e; + } + } + return IGRAPH_SUCCESS; +} diff --git a/src/core/marked_queue.h b/src/core/marked_queue.h new file mode 100644 index 0000000..93a9bfd --- /dev/null +++ b/src/core/marked_queue.h @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MARKED_QUEUE_H +#define IGRAPH_MARKED_QUEUE_H + +#include "igraph_decls.h" +#include "igraph_vector.h" +#include "igraph_dqueue.h" + +#include + +IGRAPH_BEGIN_C_DECLS + +/* This is essentially a double ended queue, with some extra features: + (1) The is-element? operation is fast, O(1). This requires that we + know a limit for the number of elements in the queue. + (2) We can insert elements in batches, and the whole batch can be + removed at once. + + Currently only the top-end operations are implemented, so the queue + is essentially a stack. +*/ + +typedef struct igraph_marked_queue_int_t { + igraph_dqueue_int_t Q; + igraph_vector_int_t set; + igraph_int_t mark; + igraph_int_t size; +} igraph_marked_queue_int_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_init(igraph_marked_queue_int_t *q, + igraph_int_t size); +IGRAPH_PRIVATE_EXPORT void igraph_marked_queue_int_destroy(igraph_marked_queue_int_t *q); +IGRAPH_PRIVATE_EXPORT void igraph_marked_queue_int_reset(igraph_marked_queue_int_t *q); + +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_marked_queue_int_empty(const igraph_marked_queue_int_t *q); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_marked_queue_int_size(const igraph_marked_queue_int_t *q); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_print(const igraph_marked_queue_int_t *q); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_fprint(const igraph_marked_queue_int_t *q, FILE *file); + +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_marked_queue_int_iselement(const igraph_marked_queue_int_t *q, + igraph_int_t elem); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_push(igraph_marked_queue_int_t *q, igraph_int_t elem); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_start_batch(igraph_marked_queue_int_t *q); +IGRAPH_PRIVATE_EXPORT void igraph_marked_queue_int_pop_back_batch(igraph_marked_queue_int_t *q); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_marked_queue_int_as_vector(const igraph_marked_queue_int_t *q, + igraph_vector_int_t *vec); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/math.h b/src/core/math.h new file mode 100644 index 0000000..c91c19a --- /dev/null +++ b/src/core/math.h @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CORE_MATH_H +#define IGRAPH_CORE_MATH_H + +#include + +/* Math constants are not part of standard C */ + +/* The following definitions contain enough precision for + * an IEEE-754 quadruple-precision floating point format. */ + +#ifndef M_E +#define M_E 2.71828182845904523536028747135266250 +#endif + +#ifndef M_LOG2E +#define M_LOG2E 1.44269504088896340735992468100189214 +#endif + +#ifndef M_LOG10E +#define M_LOG10E 0.434294481903251827651128918916605082 +#endif + +#ifndef M_LN2 +#define M_LN2 0.693147180559945309417232121458176568 +#endif + +#ifndef M_LN10 +#define M_LN10 2.30258509299404568401799145468436421 +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923132169163975144 +#endif + +#ifndef M_PI_4 +#define M_PI_4 0.785398163397448309615660845819875721 +#endif + +#ifndef M_1_PI +#define M_1_PI 0.318309886183790671537767526745028724 +#endif + +#ifndef M_2_PI +#define M_2_PI 0.636619772367581343075535053490057448 +#endif + +#ifndef M_2_SQRTPI +#define M_2_SQRTPI 1.12837916709551257389615890312154517 +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880168872420969808 +#endif + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.707106781186547524400844362104849039 +#endif + +#endif /* IGRAPH_CORE_MATH_H */ diff --git a/src/core/matrix.c b/src/core/matrix.c new file mode 100644 index 0000000..643dd98 --- /dev/null +++ b/src/core/matrix.c @@ -0,0 +1,290 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_matrix.h" +#include "igraph_types.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +/** + * \ingroup matrix + * \function igraph_matrix_complex_real + * \brief Gives the real part of a complex matrix. + * + * \param m Pointer to a complex matrix. + * \param real Pointer to an initialized matrix. The result will be stored here. + * \return Error code. + * + * Time complexity: O(n), + * n is the + * number of elements in the matrix. + */ + +igraph_error_t igraph_matrix_complex_real(const igraph_matrix_complex_t *m, + igraph_matrix_t *real) { + igraph_int_t nrow = igraph_matrix_complex_nrow(m); + igraph_int_t ncol = igraph_matrix_complex_ncol(m); + IGRAPH_CHECK(igraph_matrix_resize(real, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_real(&m->data, &real->data)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_complex_imag + * \brief Gives the imaginary part of a complex matrix. + * + * \param m Pointer to a complex matrix. + * \param imag Pointer to an initialized matrix. The result will be stored here. + * \return Error code. + * + * Time complexity: O(n), + * n is the + * number of elements in the matrix. + */ + +igraph_error_t igraph_matrix_complex_imag(const igraph_matrix_complex_t *m, + igraph_matrix_t *imag) { + igraph_int_t nrow = igraph_matrix_complex_nrow(m); + igraph_int_t ncol = igraph_matrix_complex_ncol(m); + IGRAPH_CHECK(igraph_matrix_resize(imag, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_imag(&m->data, &imag->data)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_complex_realimag + * \brief Gives the real and imaginary parts of a complex matrix. + * + * \param m Pointer to a complex matrix. + * \param real Pointer to an initialized matrix. The real part will be stored here. + * \param imag Pointer to an initialized matrix. The imaginary part will be stored here. + * \return Error code. + * + * Time complexity: O(n), + * n is the + * number of elements in the matrix. + */ + +igraph_error_t igraph_matrix_complex_realimag(const igraph_matrix_complex_t *m, + igraph_matrix_t *real, + igraph_matrix_t *imag) { + igraph_int_t nrow = igraph_matrix_complex_nrow(m); + igraph_int_t ncol = igraph_matrix_complex_ncol(m); + IGRAPH_CHECK(igraph_matrix_resize(real, nrow, ncol)); + IGRAPH_CHECK(igraph_matrix_resize(imag, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_realimag(&m->data, &real->data, + &imag->data)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_complex_create + * \brief Creates a complex matrix from a real and imaginary part. + * + * \param m Pointer to an uninitialized complex matrix. + * \param real Pointer to the real part of the complex matrix. + * \param imag Pointer to the imaginary part of the complex matrix. + * \return Error code. + * + * Time complexity: O(n), + * n is the + * number of elements in the matrix. + */ + +igraph_error_t igraph_matrix_complex_create(igraph_matrix_complex_t *m, + const igraph_matrix_t *real, + const igraph_matrix_t *imag) { + igraph_int_t nrowr = igraph_matrix_nrow(real); + igraph_int_t ncolr = igraph_matrix_ncol(real); + igraph_int_t nrowi = igraph_matrix_nrow(imag); + igraph_int_t ncoli = igraph_matrix_ncol(imag); + + if (nrowr != nrowi || ncolr != ncoli) { + IGRAPH_ERRORF("Dimensions of real (%" IGRAPH_PRId " by %" IGRAPH_PRId ") and " + "imaginary (%" IGRAPH_PRId " by %" IGRAPH_PRId ") matrices must match.", + IGRAPH_EINVAL, nrowr, ncolr, nrowi, ncoli); + } + + IGRAPH_CHECK(igraph_matrix_complex_init(m, nrowr, ncolr)); + + for (igraph_int_t i = 0; i < nrowr * ncolr; i++) { + VECTOR(m->data)[i] = igraph_complex(VECTOR(real->data)[i], VECTOR(imag->data)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_complex_create_polar + * \brief Creates a complex matrix from a magnitude and an angle. + * + * \param m Pointer to an uninitialized complex matrix. + * \param r Pointer to a real matrix containing magnitudes. + * \param theta Pointer to a real matrix containing arguments (phase angles). + * \return Error code. + * + * Time complexity: O(n), + * n is the + * number of elements in the matrix. + */ + +igraph_error_t igraph_matrix_complex_create_polar(igraph_matrix_complex_t *m, + const igraph_matrix_t *r, + const igraph_matrix_t *theta) { + igraph_int_t nrowr = igraph_matrix_nrow(r); + igraph_int_t ncolr = igraph_matrix_ncol(r); + igraph_int_t nrowt = igraph_matrix_nrow(theta); + igraph_int_t ncolt = igraph_matrix_ncol(theta); + + if (nrowr != nrowt || ncolr != ncolt) { + IGRAPH_ERRORF("Dimensions of magnitude (%" IGRAPH_PRId " by %" IGRAPH_PRId ") and " + "angle (%" IGRAPH_PRId " by %" IGRAPH_PRId ") matrices must match.", + IGRAPH_EINVAL, nrowr, ncolr, nrowt, ncolt); + } + + IGRAPH_CHECK(igraph_matrix_complex_init(m, nrowr, ncolr)); + + for (igraph_int_t i = 0; i < nrowr * ncolr; i++) { + VECTOR(m->data)[i] = igraph_complex_polar(VECTOR(r->data)[i], VECTOR(theta->data)[i]); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_complex_all_almost_e + * \brief Are all elements almost equal? + * + * Checks if the elements of two complex matrices are equal within a relative tolerance. + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \param eps Relative tolerance, see \ref igraph_complex_almost_equals() for details. + * \return True if the two matrices are almost equal, false if there is at least + * one differing element or if the matrices are not of the same dimensions. + */ +igraph_bool_t igraph_matrix_complex_all_almost_e(igraph_matrix_complex_t *lhs, + igraph_matrix_complex_t *rhs, + igraph_real_t eps) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + igraph_vector_complex_all_almost_e(&lhs->data, &rhs->data, eps); +} + + +/** + * \function igraph_matrix_all_almost_e + * \brief Are all elements almost equal? + * + * Checks if the elements of two matrices are equal within a relative tolerance. + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \param eps Relative tolerance, see \ref igraph_almost_equals() for details. + * \return True if the two matrices are almost equal, false if there is at least + * one differing element or if the matrices are not of the same dimensions. + */ +igraph_bool_t igraph_matrix_all_almost_e(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs, + igraph_real_t eps) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + igraph_vector_all_almost_e(&lhs->data, &rhs->data, eps); +} + + +/** + * \function igraph_matrix_zapsmall + * \brief Replaces small elements of a matrix by exact zeros. + * + * Matrix elements which are smaller in magnitude than the given absolute + * tolerance will be replaced by exact zeros. The default tolerance + * corresponds to two-thirds of the representable digits of \type igraph_real_t, + * i.e. DBL_EPSILON^(2/3) which is approximately 10^-10. + * + * \param m The matrix to process, it will be changed in-place. + * \param tol Tolerance value. Numbers smaller than this in magnitude will + * be replaced by zeros. Pass in zero to use the default tolerance. + * Must not be negative. + * \return Error code. + * + * \sa \ref igraph_matrix_all_almost_e() and \ref igraph_almost_equals() to + * perform comparisons with relative tolerances. + */ +igraph_error_t igraph_matrix_zapsmall(igraph_matrix_t *m, igraph_real_t tol) { + return igraph_vector_zapsmall(&m->data, tol); +} + +/** + * \function igraph_matrix_complex_zapsmall + * \brief Replaces small elements of a complex matrix by exact zeros. + * + * Similarly to \ref igraph_matrix_zapsmall(), small elements will be replaced + * by zeros. The operation is performed separately on the real and imaginary + * parts of the numbers. This way, complex numbers with a large real part and + * tiny imaginary part will effectively be transformed to real numbers. + * The default tolerance + * corresponds to two-thirds of the representable digits of \type igraph_real_t, + * i.e. DBL_EPSILON^(2/3) which is approximately 10^-10. + * + * \param m The matrix to process, it will be changed in-place. + * \param tol Tolerance value. Real and imaginary parts smaller than this in + * magnitude will be replaced by zeros. Pass in zero to use the default + * tolerance. Must not be negative. + * \return Error code. + * + * \sa \ref igraph_matrix_complex_all_almost_e() and + * \ref igraph_complex_almost_equals() to perform comparisons with relative + * tolerances. + */ +igraph_error_t igraph_matrix_complex_zapsmall(igraph_matrix_complex_t *m, igraph_real_t tol) { + return igraph_vector_complex_zapsmall(&m->data, tol); +} diff --git a/src/core/matrix.pmt b/src/core/matrix.pmt new file mode 100644 index 0000000..adae8d3 --- /dev/null +++ b/src/core/matrix.pmt @@ -0,0 +1,1838 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" + +#include "math/safe_intop.h" + +#include /* memcpy & co. */ +#include + +/** + * \section about_igraph_matrix_t_objects About \type igraph_matrix_t objects + * + * This type is just an interface to \type igraph_vector_t. + * + * The \type igraph_matrix_t type usually stores n + * elements in O(n) space, but not always. See the documentation of + * the vector type. + */ + +/** + * \section igraph_matrix_constructor_and_destructor Matrix constructors and + * destructors + */ + +/** + * \ingroup matrix + * \function igraph_matrix_init + * \brief Initializes a matrix. + * + * + * Every matrix needs to be initialized before using it. This is done + * by calling this function. A matrix has to be destroyed if it is not + * needed any more; see \ref igraph_matrix_destroy(). + * \param m Pointer to a not yet initialized matrix object to be + * initialized. + * \param nrow The number of rows in the matrix. + * \param ncol The number of columns in the matrix. + * \return Error code. + * + * Time complexity: usually O(n), n is the number of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, init)( + TYPE(igraph_matrix) *m, igraph_int_t nrow, igraph_int_t ncol) { + igraph_int_t size; + IGRAPH_ASSERT(nrow >= 0 && ncol >= 0); + IGRAPH_SAFE_MULT(nrow, ncol, &size); + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(&m->data, size)); + m->nrow = nrow; + m->ncol = ncol; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_view + * \brief Creates a matrix view into an existing array. + * + * + * This function lets you treat an existing C array as a matrix. The elements + * of the matrix are assumed to be stored in column-major order in the array, + * i.e. the elements of the first column are stored first, followed by the + * second column and so on. + * + * + * Since this function creates a view into an existing array, you must \em not + * destroy the \type igraph_matrix_t object when you are done with it. Similarly, + * you must \em not call any function on it that may attempt to modify the size + * of the matrix. Modifying an element in the matrix will modify the underlying + * array as the two share the same memory block. + * + * \param data The raw array that the matrix provides a view into. + * \param nrow The number of rows in the matrix. + * \param ncol The number of columns in the matrix. + * \return The matrix object providing the view into the array. + * + * Time complexity: O(1). + */ + +TYPE(igraph_matrix) FUNCTION(igraph_matrix, view)( + const BASE *data, + igraph_int_t nrow, igraph_int_t ncol) { + + /* temporarily cast away the constness */ + TYPE(igraph_matrix) m; + + /* No overflow checking, as this function does not return igraph_error_t. + * It is the caller's resposibility to ensure that the size of 'data' + * matches nrow*ncol, which also implies that nrow*ncol does not overflow. */ + + m.data = FUNCTION(igraph_vector, view)(data, ncol * nrow); + m.nrow = nrow; + m.ncol = ncol; + + return m; +} + +/** + * \ingroup matrix + * \function igraph_matrix_view_from_vector + * \brief Creates a matrix view that treats an existing vector as a matrix. + * + * + * This function lets you treat an existing igraph vector as a matrix. The + * elements of the matrix are assumed to be stored in column-major order in the + * vector, i.e. the elements of the first column are stored first, followed by + * the second column and so on. + * + * + * Since this function creates a view into an existing vector, you must \em not + * destroy the \c igraph_matrix_t object when you are done with it. Similarly, + * you must \em not call any function on it that may attempt to modify the size + * of the vector. Modifying an element in the matrix will modify the underlying + * vector as the two share the same memory block. + * + * + * Additionally, you must \em not attempt to grow the underlying vector by any + * vector operation as that may result in a re-allocation of the backing memory + * block of the vector, and the matrix view will not be informed about the + * re-allocation so it will point to an invalid memory area afterwards. + * + * \param v The vector that the matrix will provide a view into. + * \param nrow The number of rows in the matrix. The number of columns will be + * derived implicitly from the size of the vector. If the number of + * items in the vector is not divisible by the number of rows, the + * last few elements of the vector will not be covered by the view. + * \return The matrix object providing the view into the vector. + * + * Time complexity: O(1). + */ + +TYPE(igraph_matrix) FUNCTION(igraph_matrix, view_from_vector)( + const TYPE(igraph_vector) *v, + igraph_int_t nrow +) { + igraph_int_t size = FUNCTION(igraph_vector, size)(v); + igraph_int_t ncol = nrow > 0 ? size / nrow : 0; + + return FUNCTION(igraph_matrix, view)(VECTOR(*v), nrow, ncol); +} + +/** + * \ingroup matrix + * \function igraph_matrix_destroy + * \brief Destroys a matrix object. + * + * + * This function frees all the memory allocated for a matrix + * object. The destroyed object needs to be reinitialized before using + * it again. + * \param m The matrix to destroy. + * + * Time complexity: operating system dependent. + */ + +void FUNCTION(igraph_matrix, destroy)(TYPE(igraph_matrix) *m) { + FUNCTION(igraph_vector, destroy)(&m->data); +} + +/** + * \ingroup matrix + * \function igraph_matrix_capacity + * \brief Returns the number of elements allocated for a matrix. + * + * Note that this might be different from the size of the matrix (as + * queried by \ref igraph_matrix_size(), and specifies how many elements + * the matrix can hold, without reallocation. + * \param v Pointer to the (previously initialized) matrix object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_matrix_size(), \ref igraph_matrix_nrow(), + * \ref igraph_matrix_ncol(). + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_matrix, capacity)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, capacity)(&m->data); +} + + +/** + * \section igraph_matrix_accessing_elements Accessing elements of a matrix + */ + +/** + * \ingroup matrix + * \function igraph_matrix_resize + * \brief Resizes a matrix. + * + * + * This function resizes a matrix by adding more elements to it. + * The matrix contains arbitrary data after resizing it. + * That is, after calling this function you cannot expect that element + * (i,j) in the matrix remains the + * same as before. + * \param m Pointer to an already initialized matrix object. + * \param nrow The number of rows in the resized matrix. + * \param ncol The number of columns in the resized matrix. + * \return Error code. + * + * Time complexity: O(1) if the + * matrix gets smaller, usually O(n) + * if it gets larger, n is the + * number of elements in the resized matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, resize)(TYPE(igraph_matrix) *m, igraph_int_t nrow, igraph_int_t ncol) { + igraph_int_t size; + IGRAPH_ASSERT(nrow >= 0 && ncol >= 0); + IGRAPH_SAFE_MULT(nrow, ncol, &size); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(&m->data, size)); + m->nrow = nrow; + m->ncol = ncol; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_resize_min + * \brief Deallocates unused memory for a matrix. + * + * This function attempts to deallocate the unused reserved storage + * of a matrix. + * + * \param m Pointer to an initialized matrix. + * + * \sa \ref igraph_matrix_resize(). + * + * Time complexity: operating system dependent, O(n) at worst. + */ + +void FUNCTION(igraph_matrix, resize_min)(TYPE(igraph_matrix) *m) { + FUNCTION(igraph_vector, resize_min)(&m->data); +} + + +/** + * \ingroup matrix + * \function igraph_matrix_size + * \brief The number of elements in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The size of the matrix. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_matrix, size)(const TYPE(igraph_matrix) *m) { + return (m->nrow) * (m->ncol); +} + +/** + * \ingroup matrix + * \function igraph_matrix_nrow + * \brief The number of rows in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The number of rows in the matrix. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_matrix, nrow)(const TYPE(igraph_matrix) *m) { + return m->nrow; +} + +/** + * \ingroup matrix + * \function igraph_matrix_ncol + * \brief The number of columns in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The number of columns in the matrix. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_matrix, ncol)(const TYPE(igraph_matrix) *m) { + return m->ncol; +} + +/** + * Copies matrix data stored contiguously while transposing. Applications include implementing + * matrix transposition as well as changing between row-major and column-major storage formats. + * + * \param dst Data will be copied into this vector. It must have length m-by-n. It will not be resized. + * \param src Vector containing the data to be copied. It is assumed to have length m-by-n. + * Must not be the same as \p dst . + * \param m The size of contiguous blocks. This is the number of columns when using column-major + * storage format, or the number of rows when using row-major format. + * \param n The number of blocks. This is the number of rows when using column-major format, + * or the number of columns when using row-major format. + */ +static void FUNCTION(igraph_i, transpose_copy)( + TYPE(igraph_vector) *dst, const TYPE(igraph_vector) *src, + size_t m, size_t n) { + + IGRAPH_ASSERT(dst != src); + + /* Block size of 4 was found to yield the best performance when benchmarking with: + * - Intel Core i7-7920HQ on macOS. + * - AMD Ryzen Threadripper 3990X on Linux. + */ + const size_t blocksize = 4; + for (size_t i=0; i < m; i += blocksize) { + for (size_t j=0; j < n; j++) { + for (size_t k=0; k < blocksize && i+k < m; k++) { + VECTOR(*dst)[j + (i+k)*n] = VECTOR(*src)[i+k + j*m]; + } + } + } +} + +/** + * \ingroup matrix + * \function igraph_matrix_copy_to + * \brief Copies a matrix to a regular C array. + * + * + * The C array should be of sufficient size; there are (of course) no + * range checks. + * + * \param m Pointer to an initialized matrix object. + * \param to Pointer to a C array; the place to copy the data to. + * \param storage \c IGRAPH_ROW_MAJOR to write the data in row-major format, + * \c IGRAPH_COLUMN_MAJOR to write it in column-major format. Currently + * igraph uses column-major storage internally, thus \c IGRAPH_COLUMN_MAJOR + * is much faster. + * \return Error code. + * + * Time complexity: O(n), + * n is the number of + * elements in the matrix. + */ +void FUNCTION(igraph_matrix, copy_to)(const TYPE(igraph_matrix) *m, BASE *to, igraph_matrix_storage_t storage) { + if (storage == IGRAPH_ROW_MAJOR) { + TYPE(igraph_vector) dst = FUNCTION(igraph_vector, view)(to, m->nrow * m->ncol); + FUNCTION(igraph_i, transpose_copy)(&dst, &m->data, m->nrow, m->ncol); + } else if (storage == IGRAPH_COLUMN_MAJOR) { + FUNCTION(igraph_vector, copy_to)(&m->data, to); + } else { + IGRAPH_FATAL("Invalid matrix storage format."); + } +} + +/** + * \ingroup matrix + * \function igraph_matrix_null + * \brief Sets all elements in a matrix to zero. + * + * \param m Pointer to an initialized matrix object. + * + * Time complexity: O(n), + * n is the number of elements in + * the matrix. + */ + +void FUNCTION(igraph_matrix, null)(TYPE(igraph_matrix) *m) { + FUNCTION(igraph_vector, null)(&m->data); +} + +/** + * \ingroup matrix + * \function igraph_matrix_add_cols + * \brief Adds columns to a matrix. + * \param m The matrix object. + * \param n The number of columns to add. + * \return Error code, \c IGRAPH_ENOMEM if there is + * not enough memory to perform the operation. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, add_cols)(TYPE(igraph_matrix) *m, igraph_int_t n) { + igraph_int_t new_ncol; + IGRAPH_SAFE_ADD(m->ncol, n, &new_ncol); + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(m, m->nrow, new_ncol)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_add_rows + * \brief Adds rows to a matrix. + * \param m The matrix object. + * \param n The number of rows to add. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory for the operation. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, add_rows)(TYPE(igraph_matrix) *m, igraph_int_t n) { + igraph_int_t new_nrow, new_size; + IGRAPH_SAFE_ADD(m->nrow, n, &new_nrow); + IGRAPH_SAFE_MULT(m->ncol, new_nrow, &new_size); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(&m->data, new_size)); + for (igraph_int_t i = m->ncol - 1; i >= 0; i--) { + FUNCTION(igraph_vector, move_interval)(&m->data, (m->nrow)*i, (m->nrow) * (i + 1), + new_nrow * i); + } + m->nrow = new_nrow; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_remove_col + * \brief Removes a column from a matrix. + * + * \param m The matrix object. + * \param col The column to remove. + * \return Error code, always returns with success. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, remove_col)(TYPE(igraph_matrix) *m, igraph_int_t col) { + FUNCTION(igraph_vector, remove_section)(&m->data, (m->nrow)*col, (m->nrow) * (col + 1)); + m->ncol--; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_permdelete_rows + * \brief Removes rows from a matrix (for internal use). + * + * Time complexity: linear with the number of elements of the original + * matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, permdelete_rows)( + TYPE(igraph_matrix) *m, igraph_int_t *index, igraph_int_t nremove) { + igraph_int_t i, j; + for (j = 0; j < m->nrow; j++) { + if (index[j] != 0) { + for (i = 0; i < m->ncol; i++) { + MATRIX(*m, index[j] - 1, i) = MATRIX(*m, j, i); + } + } + } + /* Remove unnecessary elements from the end of each column */ + for (i = 0; i < m->ncol; i++) + FUNCTION(igraph_vector, remove_section)(&m->data, + (i + 1) * (m->nrow - nremove), (i + 1) * (m->nrow - nremove) + nremove); + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(m, m->nrow - nremove, m->ncol)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_init_array + * \brief Initializes a matrix from an ordinary C array (constructor). + * + * The array is assumed to store the matrix data contiguously, either in + * a column-major or row-major format. In other words, \p data may + * store concatenated matrix columns or concatenated matrix rows. + * Constructing a matrix from column-major data is faster, as this is + * igraph's native storage format. + * + * \param v Pointer to an uninitialized matrix object. + * \param data A regular C array, storing the elements of the matrix in + * column-major order, i.e. the elements of the first column are stored + * first, followed by the second column and so on. + * \param nrow The number of rows in the matrix. + * \param ncol The number of columns in the matrix. + * \param storage \c IGRAPH_ROW_MAJOR if the array is in row-major format, + \c IGRAPH_COLUMN_MAJOR if the array is in column-major format. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system specific, usually + * O(\p nrow \p ncol). + */ + +igraph_error_t FUNCTION(igraph_matrix, init_array)( + TYPE(igraph_matrix) *m, const BASE *data, + igraph_int_t nrow, igraph_int_t ncol, + igraph_matrix_storage_t storage) { + + igraph_int_t length; + IGRAPH_SAFE_MULT(nrow, ncol, &length); + IGRAPH_CHECK(FUNCTION(igraph_matrix, init)(m, nrow, ncol)); + const TYPE(igraph_vector) v = FUNCTION(igraph_vector, view)(data, length); + + if (storage == IGRAPH_COLUMN_MAJOR) { + IGRAPH_CHECK(FUNCTION(igraph_vector, update)(&m->data, &v)); + } else if (storage == IGRAPH_ROW_MAJOR) { + FUNCTION(igraph_i, transpose_copy)(&m->data, &v, ncol, nrow); + } else { + IGRAPH_ERROR("Invalid storage type argument.", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup matrix + * \function igraph_matrix_init_copy + * \brief Copies a matrix. + * + * + * Creates a matrix object by copying from an existing matrix. + * \param to Pointer to an uninitialized matrix object. + * \param from The initialized matrix object to copy. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory to allocate the new matrix. + * + * Time complexity: O(n), the number + * of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, init_copy)(TYPE(igraph_matrix) *to, const TYPE(igraph_matrix) *from) { + IGRAPH_CHECK(FUNCTION(igraph_vector, init_copy)(&to->data, &from->data)); + to->nrow = from->nrow; + to->ncol = from->ncol; + return IGRAPH_SUCCESS; +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_max + * \brief Largest element of a matrix. + * + * + * If the matrix is empty, an arbitrary number is returned. + * \param m The matrix object. + * \return The maximum element of \p m, or NaN if any element is NaN. + * + * Added in version 0.2. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_real_t FUNCTION(igraph_matrix, max)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, max)(&m->data); +} + +#endif + +/** + * \function igraph_matrix_scale + * + * Multiplies each element of the matrix by a constant. + * \param m The matrix. + * \param by The constant. + * + * Added in version 0.2. + * + * Time complexity: O(n), the number of elements in the matrix. + */ + +void FUNCTION(igraph_matrix, scale)(TYPE(igraph_matrix) *m, BASE by) { + FUNCTION(igraph_vector, scale)(&m->data, by); +} + +/** + * \function igraph_matrix_select_rows + * \brief Select some rows of a matrix. + * + * This function selects some rows of a matrix and returns them in a + * new matrix. The result matrix should be initialized before calling + * the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param rows Vector; it contains the row indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, select_rows)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_int_t *rows) { + igraph_int_t norows = igraph_vector_int_size(rows); + igraph_int_t i, j, ncols = FUNCTION(igraph_matrix, ncol)(m); + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, norows, ncols)); + /* Iterate in column-major order for better performance. */ + for (j = 0; j < ncols; j++) { + for (i = 0; i < norows; i++) { + MATRIX(*res, i, j) = MATRIX(*m, VECTOR(*rows)[i], j); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_select_rows_cols + * \brief Select some rows and columns of a matrix. + * + * This function selects some rows and columns of a matrix and returns + * them in a new matrix. The result matrix should be initialized before + * calling the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param rows Vector; it contains the row indices (starting with + * zero) to extract. Note that no range checking is performed. + * \param cols Vector; it contains the column indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, select_rows_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_int_t *rows, + const igraph_vector_int_t *cols) { + igraph_int_t nrows = igraph_vector_int_size(rows); + igraph_int_t ncols = igraph_vector_int_size(cols); + igraph_int_t i, j; + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, nrows, ncols)); + /* Iterate in column-major order for better performance. */ + for (j = 0; j < ncols; j++) { + for (i = 0; i < nrows; i++) { + MATRIX(*res, i, j) = MATRIX(*m, VECTOR(*rows)[i], VECTOR(*cols)[j]); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_get_col + * \brief Select a column. + * + * Extract a column of a matrix and return it as a vector. + * \param m The input matrix. + * \param res The result will we stored in this vector. It should be + * initialized and will be resized as needed. + * \param index The index of the column to select. + * \return Error code. + * + * Time complexity: O(n), the number of rows in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, get_col)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, + igraph_int_t index) { + igraph_int_t nrow = FUNCTION(igraph_matrix, nrow)(m); + + if (index >= m->ncol) { + IGRAPH_ERROR("Index out of range for selecting matrix column.", IGRAPH_EINVAL); + } + IGRAPH_CHECK(FUNCTION(igraph_vector, get_interval)(&m->data, res, + nrow * index, nrow * (index + 1))); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_sum + * \brief Sum of elements. + * + * Returns the sum of the elements of a matrix. + * \param m The input matrix. + * \return The sum of the elements. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +BASE FUNCTION(igraph_matrix, sum)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, sum)(&m->data); +} + +/** + * \function igraph_matrix_all_e + * \brief Are all elements equal? + * + * Checks element-wise equality of two matrices. For matrices containing floating + * point values, consider using \ref igraph_matrix_all_almost_e(). + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return True if the elements in the \p lhs are all + * equal to the corresponding elements in \p rhs. Returns false + * if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_e)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_e)(&lhs->data, &rhs->data); +} + +igraph_bool_t +FUNCTION(igraph_matrix, is_equal)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return FUNCTION(igraph_matrix, all_e)(lhs, rhs); +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_all_l + * \brief Are all elements less? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return True if the elements in the \p lhs are all + * less than the corresponding elements in \p rhs. Returns false + * if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_l)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_l)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_g + * \brief Are all elements greater? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return True if the elements in the \p lhs are all + * greater than the corresponding elements in \p rhs. Returns false + * if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_g)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_g)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_le + * \brief Are all elements less or equal? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return True if the elements in the \p lhs are all + * less than or equal to the corresponding elements in \p + * rhs. Returns false if the dimensions of the matrices + * don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t +FUNCTION(igraph_matrix, all_le)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_le)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_ge + * \brief Are all elements greater or equal? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return True if the elements in the \p lhs are all + * greater than or equal to the corresponding elements in \p + * rhs. Returns false if the dimensions of the matrices + * don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t +FUNCTION(igraph_matrix, all_ge)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_ge)(&lhs->data, &rhs->data); +} + +#endif + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_maxdifference + * \brief Maximum absolute difference between two matrices. + * + * Calculate the maximum absolute difference of two matrices. Both matrices + * must be non-empty. If their dimensions differ then a warning is given and + * the comparison is performed by vectors columnwise from both matrices. + * The remaining elements in the larger vector are ignored. + * \param m1 The first matrix. + * \param m2 The second matrix. + * \return The element with the largest absolute value in \c m1 - \c m2. + * + * Time complexity: O(mn), the elements in the smaller matrix. + */ + +igraph_real_t FUNCTION(igraph_matrix, maxdifference)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->ncol != m2->ncol || m1->nrow != m2->nrow) { + IGRAPH_WARNING("Comparing non-conformant matrices."); + } + return FUNCTION(igraph_vector, maxdifference)(&m1->data, &m2->data); +} + +#endif + +#define SWAP(TYPE,a,b) do { TYPE igraph_i_tmp = (a); (a) = (b); (b) = igraph_i_tmp; } while (0) + +/** + * \function igraph_matrix_transpose + * \brief Transpose of a matrix. + * + * Calculates the transpose of a matrix. When the matrix is non-square, + * this function reallocates the storage used for the matrix. + * + * \param m The input (and output) matrix. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, transpose)(TYPE(igraph_matrix) *m) { + if (m->nrow > 1 && m->ncol > 1) { + if (m->nrow == m->ncol) { + /* In-place transpose for square matrices. */ + + /* Block size of 4 was found to yield the best performance during benchmarking, + * see igraph_i_transpose_copy() */ + const size_t blocksize = 4; + const size_t n = m->nrow; + size_t k=0; + for (k=0; k + blocksize - 1 < n; k += blocksize) { + for (size_t i = k; i < k + blocksize; ++i) { + for (size_t j = i + 1; j < k + blocksize; ++j) { + SWAP(BASE, VECTOR(m->data)[j + i*n], VECTOR(m->data)[i + j*n]); + } + } + for (size_t i = k + blocksize; i < n; ++i) { + for (size_t j = k; j < k + blocksize; ++j) { + SWAP(BASE, VECTOR(m->data)[j + i*n], VECTOR(m->data)[i + j*n]); + } + } + } + for (size_t i = k; i < n; ++i) { + for (size_t j = i + 1; j < n; ++j) { + SWAP(BASE, VECTOR(m->data)[j + i*n], VECTOR(m->data)[i + j*n]); + } + } + } else { + /* Allocate new storage for non-square matrices. */ + TYPE(igraph_vector) newdata; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(&newdata, m->nrow * m->ncol)); + FUNCTION(igraph_i, transpose_copy)(&newdata, &m->data, m->nrow, m->ncol); + FUNCTION(igraph_vector, destroy)(&m->data); + m->data = newdata; + } + } + + SWAP(igraph_int_t, m->nrow, m->ncol); + + return IGRAPH_SUCCESS; +} + +#undef SWAP + +/** + * \function igraph_matrix_get + * Extract an element from a matrix. + * + * Use this if you need a function for some reason and cannot use the + * \ref MATRIX macro. Note that no range checking is performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \return The element in the given row and column. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_matrix, get)(const TYPE(igraph_matrix) *m, + igraph_int_t row, igraph_int_t col) { + return MATRIX(*m, row, col); +} + +/** + * \function igraph_matrix_get_ptr + * Pointer to an element of a matrix. + * + * The function returns a pointer to an element. No range checking is + * performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \return Pointer to the element in the given row and column. + * + * Time complexity: O(1). + */ + +BASE* FUNCTION(igraph_matrix, get_ptr)(const TYPE(igraph_matrix) *m, + igraph_int_t row, igraph_int_t col) { + return &MATRIX(*m, row, col); +} + +/** + * \function igraph_matrix_set + * Set an element. + * + * Set an element of a matrix. No range checking is performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \param value The new value of the element. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_matrix, set)( + TYPE(igraph_matrix)* m, igraph_int_t row, igraph_int_t col, + BASE value) { + MATRIX(*m, row, col) = value; +} + +/** + * \function igraph_matrix_fill + * Fill with an element. + * + * Set the matrix to a constant matrix. + * \param m The input matrix. + * \param e The element to set. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, fill)(TYPE(igraph_matrix) *m, BASE e) { + FUNCTION(igraph_vector, fill)(&m->data, e); +} + +/** + * \function igraph_matrix_update + * Update from another matrix. + * + * This function replicates \p from in the matrix \p to. + * Note that \p to must be already initialized. + * \param to The result matrix. + * \param from The matrix to replicate; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_matrix, update)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(to, from->nrow, from->ncol)); + FUNCTION(igraph_vector, update)(&to->data, &from->data); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_rbind + * Combine two matrices rowwise. + * + * This function places the rows of \p from below the rows of \c to + * and stores the result in \p to. The number of columns in the two + * matrices must match. + * \param to The upper matrix; the result is also stored here. + * \param from The lower matrix. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the newly created + * matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, rbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + igraph_int_t tocols = to->ncol, fromcols = from->ncol; + igraph_int_t torows = to->nrow, fromrows = from->nrow; + igraph_int_t offset, c, r, index, offset2; + if (tocols != fromcols) { + IGRAPH_ERROR("Cannot do rbind, number of columns do not match.", IGRAPH_EINVAL); + } + + igraph_int_t new_size; /* new_size = tocols * (fromrows + torows) */ + IGRAPH_SAFE_ADD(fromrows, torows, &new_size); + IGRAPH_SAFE_MULT(tocols, new_size, &new_size); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(&to->data, new_size)); + to->nrow += fromrows; + + offset = (tocols - 1) * fromrows; + index = tocols * torows - 1; + for (c = tocols - 1; c > 0; c--) { + for (r = 0; r < torows; r++, index--) { + VECTOR(to->data)[index + offset] = VECTOR(to->data)[index]; + } + offset -= fromrows; + } + + offset = torows; offset2 = 0; + for (c = 0; c < tocols; c++) { + memcpy(VECTOR(to->data) + offset, VECTOR(from->data) + offset2, + sizeof(BASE) * fromrows); + offset += fromrows + torows; + offset2 += fromrows; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_cbind + * Combine matrices columnwise. + * + * This function places the columns of \p from on the right of \p to, + * and stores the result in \p to. + * \param to The left matrix; the result is stored here too. + * \param from The right matrix. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements on the new matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, cbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + + igraph_int_t tocols = to->ncol, fromcols = from->ncol; + igraph_int_t torows = to->nrow, fromrows = from->nrow; + if (torows != fromrows) { + IGRAPH_ERROR("Cannot do rbind, number of rows do not match.", IGRAPH_EINVAL); + } + + igraph_int_t new_tocols; + IGRAPH_SAFE_ADD(tocols, fromcols, &new_tocols); + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(to, torows, new_tocols)); + + FUNCTION(igraph_vector, copy_to)(&from->data, VECTOR(to->data) + tocols * torows); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_swap + * \brief Swap two matrices. + * + * The contents of the two matrices will be swapped. + * \param m1 The first matrix. + * \param m2 The second matrix. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_matrix, swap)(TYPE(igraph_matrix) *m1, TYPE(igraph_matrix) *m2) { + igraph_int_t tmp; + + tmp = m1->nrow; + m1->nrow = m2->nrow; + m2->nrow = tmp; + + tmp = m1->ncol; + m1->ncol = m2->ncol; + m2->ncol = tmp; + + FUNCTION(igraph_vector, swap)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_get_row + * Extract a row. + * + * Extract a row from a matrix and return it as a vector. + * \param m The input matrix. + * \param res Pointer to an initialized vector; it will be resized if + * needed. + * \param index The index of the row to select. + * \return Error code. + * + * Time complexity: O(n), the number of columns in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, get_row)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, igraph_int_t index) { + igraph_int_t rows = m->nrow, cols = m->ncol; + igraph_int_t i, j; + + if (index >= rows) { + IGRAPH_ERROR("Index out of range for selecting matrix row.", IGRAPH_EINVAL); + } + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, cols)); + + for (i = index, j = 0; j < cols; i += rows, j++) { + VECTOR(*res)[j] = VECTOR(m->data)[i]; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_set_row + * Set a row from a vector. + * + * Sets the elements of a row with the given vector. This has the effect of + * setting row \c index to have the elements in the vector \c v. The length of + * the vector and the number of columns in the matrix must match, + * otherwise an error is triggered. + * \param m The input matrix. + * \param v The vector containing the new elements of the row. + * \param index Index of the row to set. + * \return Error code. + * + * Time complexity: O(n), the number of columns in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, set_row)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, igraph_int_t index) { + const igraph_int_t rows = m->nrow, cols = m->ncol; + + if (index >= rows) { + IGRAPH_ERROR("Index out of range for selecting matrix row.", IGRAPH_EINVAL); + } + if (FUNCTION(igraph_vector, size)(v) != cols) { + IGRAPH_ERROR("Cannot set matrix row, invalid vector length.", IGRAPH_EINVAL); + } + for (igraph_int_t i = index, j = 0; j < cols; i += rows, j++) { + VECTOR(m->data)[i] = VECTOR(*v)[j]; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_set_col + * Set a column from a vector. + * + * Sets the elements of a column with the given vector. In effect, column + * \c index will be set with elements from the vector \c v. The length of + * the vector and the number of rows in the matrix must match, + * otherwise an error is triggered. + * \param m The input matrix. + * \param v The vector containing the new elements of the column. + * \param index Index of the column to set. + * \return Error code. + * + * Time complexity: O(m), the number of rows in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, set_col)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, igraph_int_t index) { + const igraph_int_t rows = m->nrow, cols = m->ncol; + + if (index >= cols) { + IGRAPH_ERROR("Index out of range for setting matrix column.", IGRAPH_EINVAL); + } + if (FUNCTION(igraph_vector, size)(v) != rows) { + IGRAPH_ERROR("Cannot set matrix column, invalid vector length.", IGRAPH_EINVAL); + } + for (igraph_int_t i = index * rows, j = 0; j < rows; i++, j++) { + VECTOR(m->data)[i] = VECTOR(*v)[j]; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_swap_rows + * Swap two rows. + * + * Swap two rows in the matrix. + * \param m The input matrix. + * \param i The index of the first row. + * \param j The index of the second row. + * \return Error code. + * + * Time complexity: O(n), the number of columns. + */ + +igraph_error_t FUNCTION(igraph_matrix, swap_rows)(TYPE(igraph_matrix) *m, + igraph_int_t i, igraph_int_t j) { + const igraph_int_t ncol = m->ncol, nrow = m->nrow; + const igraph_int_t n = nrow * ncol; + + if (i >= nrow || j >= nrow) { + IGRAPH_ERROR("Cannot swap rows, index out of range.", IGRAPH_EINVAL); + } + if (i == j) { + return IGRAPH_SUCCESS; + } + for (igraph_int_t index1 = i, index2 = j; index1 < n; index1 += nrow, index2 += nrow) { + BASE tmp; + tmp = VECTOR(m->data)[index1]; + VECTOR(m->data)[index1] = VECTOR(m->data)[index2]; + VECTOR(m->data)[index2] = tmp; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_swap_cols + * Swap two columns. + * + * Swap two columns in the matrix. + * \param m The input matrix. + * \param i The index of the first column. + * \param j The index of the second column. + * \return Error code. + * + * Time complexity: O(m), the number of rows. + */ + +igraph_error_t FUNCTION(igraph_matrix, swap_cols)(TYPE(igraph_matrix) *m, + igraph_int_t i, igraph_int_t j) { + const igraph_int_t ncol = m->ncol, nrow = m->nrow; + + if (i >= ncol || j >= ncol) { + IGRAPH_ERROR("Cannot swap columns, index out of range.", IGRAPH_EINVAL); + } + if (i == j) { + return IGRAPH_SUCCESS; + } + for (igraph_int_t index1 = i * nrow, index2 = j * nrow, k = 0; k < nrow; k++, index1++, index2++) { + BASE tmp = VECTOR(m->data)[index1]; + VECTOR(m->data)[index1] = VECTOR(m->data)[index2]; + VECTOR(m->data)[index2] = tmp; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_add_constant + * Add a constant to every element. + * + * \param m The input matrix. + * \param plud The constant to add. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, add_constant)(TYPE(igraph_matrix) *m, BASE plus) { + FUNCTION(igraph_vector, add_constant)(&m->data, plus); +} + +/** + * \function igraph_matrix_add + * Add two matrices. + * + * Add \p m2 to \p m1, and store the result in \p m1. The dimensions of the + * matrices must match. + * \param m1 The first matrix; the result will be stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_matrix, add)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot add non-conformant matrices.", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, add)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_sub + * Difference of two matrices. + * + * Subtract \p m2 from \p m1 and store the result in \p m1. + * The dimensions of the two matrices must match. + * \param m1 The first matrix; the result is stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_matrix, sub)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot subtract non-conformant matrices.", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, sub)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_mul_elements + * \brief Elementwise matrix multiplication. + * + * Multiply \p m1 by \p m2 elementwise and store the result in \p m1. + * The dimensions of the two matrices must match. + * + * \param m1 The first matrix; the result is stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_matrix, mul_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot multiply elements of non-conformant matrices.", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, mul)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_div_elements + * Elementwise division. + * + * Divide \p m1 by \p m2 elementwise and store the result in \p m1. + * The dimensions of the two matrices must match. + * \param m1 The dividend. The result is store here. + * \param m2 The divisor. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_matrix, div_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot divide non-conformant matrices.", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, div)(&m1->data, &m2->data); +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_min + * \brief Smallest element of a matrix. + * + * The matrix must be non-empty. + * + * \param m The input matrix. + * \return The smallest element of \p m, or NaN if any element is NaN. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_real_t FUNCTION(igraph_matrix, min)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, min)(&m->data); +} + +/** + * \function igraph_matrix_which_min + * \brief Indices of the smallest element. + * + * The matrix must be non-empty. If the smallest element is not unique, + * then the indices of the first such element are returned. If the matrix contains + * NaN values, the indices of the first NaN value are returned. + * + * \param m The matrix. + * \param i Pointer to an igraph_int_t. The row index of the + * minimum is stored here. + * \param j Pointer to an igraph_int_t. The column index of + * the minimum is stored here. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, which_min)( + const TYPE(igraph_matrix) *m, igraph_int_t *i, igraph_int_t *j) { + igraph_int_t vmin = FUNCTION(igraph_vector, which_min)(&m->data); + *i = vmin % m->nrow; + *j = vmin / m->nrow; +} + +/** + * \function igraph_matrix_which_max + * \brief Indices of the largest element. + * + * The matrix must be non-empty. If the largest element is not unique, + * then the indices of the first such element are returned. If the matrix contains + * NaN values, the indices of the first NaN value are returned. + * + * \param m The matrix. + * \param i Pointer to an igraph_int_t. The row index of the + * maximum is stored here. + * \param j Pointer to an igraph_int_t. The column index of + * the maximum is stored here. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, which_max)( + const TYPE(igraph_matrix) *m, igraph_int_t *i, igraph_int_t *j) { + igraph_int_t vmax = FUNCTION(igraph_vector, which_max)(&m->data); + *i = vmax % m->nrow; + *j = vmax / m->nrow; +} + +/** + * \function igraph_matrix_minmax + * \brief Minimum and maximum elements of a matrix. + * + * Handy if you want to have both the smallest and largest element of + * a matrix. The matrix is only traversed once. The matrix must be non-empty. + * If a matrix contains at least one NaN, both \c min and \c max will be NaN. + * + * \param m The input matrix. It must contain at least one element. + * \param min Pointer to a base type variable. The minimum is stored here. + * \param max Pointer to a base type variable. The maximum is stored here. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, minmax)(const TYPE(igraph_matrix) *m, + BASE *min, BASE *max) { + FUNCTION(igraph_vector, minmax)(&m->data, min, max); +} + +/** + * \function igraph_matrix_which_minmax + * \brief Indices of the minimum and maximum elements. + * + * Handy if you need the indices of the smallest and largest + * elements. The matrix is traversed only once. The matrix must be + * non-empty. If the minimum or maximum is not unique, the index + * of the first minimum or the first maximum is returned, respectively. + * If a matrix contains at least one NaN, both \c which_min and \c which_max + * will point to the first NaN value. + * + * \param m The input matrix. + * \param imin Pointer to an igraph_int_t, the row index of + * the minimum is stored here. + * \param jmin Pointer to an igraph_int_t, the column index of + * the minimum is stored here. + * \param imax Pointer to an igraph_int_t, the row index of + * the maximum is stored here. + * \param jmax Pointer to an igraph_int_t, the column index of + * the maximum is stored here. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, which_minmax)(const TYPE(igraph_matrix) *m, + igraph_int_t *imin, igraph_int_t *jmin, + igraph_int_t *imax, igraph_int_t *jmax) { + igraph_int_t vmin, vmax; + FUNCTION(igraph_vector, which_minmax)(&m->data, &vmin, &vmax); + *imin = vmin % m->nrow; + *jmin = vmin / m->nrow; + *imax = vmax % m->nrow; + *jmax = vmax / m->nrow; +} + +#endif + +/** + * \function igraph_matrix_isnull + * \brief Checks for a null matrix. + * + * Checks whether all elements are zero. + * + * \param m The input matrix. + * \return Boolean, \c true is \p m contains only zeros and \c false + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, isnull)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, isnull)(&m->data); +} + +/** + * \function igraph_matrix_empty + * \brief Is the matrix empty? + * + * It is possible to have a matrix with zero rows or zero columns, or + * even both. This functions checks for these. + * + * \param m The input matrix. + * \return Boolean, \c true if the matrix contains zero elements, and + * \c false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_matrix, empty)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, empty)(&m->data); +} + +/** + * \function igraph_matrix_is_symmetric + * \brief Is the matrix symmetric? + * + * A non-square matrix is not symmetric by definition. + * + * \param m The input matrix. + * \return Boolean, \c true if the matrix is square and symmetric, \c + * false otherwise. + * + * Time complexity: O(mn), the number of elements. O(1) for non-square + * matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, is_symmetric)(const TYPE(igraph_matrix) *m) { + + const igraph_int_t n = m->nrow; + if (m->ncol != n) { + return false; + } + for (igraph_int_t r = 1; r < n; r++) { + for (igraph_int_t c = 0; c < r; c++) { + BASE a1 = MATRIX(*m, r, c); + BASE a2 = MATRIX(*m, c, r); +#ifdef EQ + if (!EQ(a1, a2)) { + return false; + } +#else + if (a1 != a2) { + return false; + } +#endif + } + } + return true; +} + +/** + * \function igraph_matrix_prod + * \brief Product of all matrix elements. + * + * Note that this function can result in overflow easily, even for not too + * big matrices. Overflow is not checked. + * + * \param m The input matrix. + * \return The product of the elements. + * + * Time complexity: O(mn), the number of elements. + */ + +BASE FUNCTION(igraph_matrix, prod)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, prod)(&m->data); +} + +/** + * \function igraph_matrix_rowsum + * \brief Rowwise sum. + * + * Calculate the sum of the elements in each row. + * + * \param m The input matrix. + * \param res Pointer to an initialized vector; the result is stored + * here. It will be resized if necessary. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, rowsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res) { + const igraph_int_t nrow = m->nrow, ncol = m->ncol; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, nrow)); + FUNCTION(igraph_vector, null)(res); + /* Iterate in column-major order for better performance. */ + for (igraph_int_t c = 0; c < ncol; c++) { + for (igraph_int_t r = 0; r < nrow; r++) { +#ifdef SUM + SUM(VECTOR(*res)[r], VECTOR(*res)[r], MATRIX(*m, r, c)); +#else + VECTOR(*res)[r] += MATRIX(*m, r, c); +#endif + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_colsum + * \brief Columnwise sum. + * + * Calculate the sum of the elements in each column. + * + * \param m The input matrix. + * \param res Pointer to an initialized vector; the result is stored + * here. It will be resized if necessary. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, colsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res) { + const igraph_int_t nrow = m->nrow, ncol = m->ncol; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, ncol)); + for (igraph_int_t c = 0; c < ncol; c++) { + BASE sum = ZERO; + for (igraph_int_t r = 0; r < nrow; r++) { +#ifdef SUM + SUM(sum, sum, MATRIX(*m, r, c)); +#else + sum += MATRIX(*m, r, c); +#endif + } + VECTOR(*res)[c] = sum; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_contains + * Search for an element. + * + * Search for the given element in the matrix. + * \param m The input matrix. + * \param e The element to search for. + * \return Boolean, \c true if the matrix contains \p e, \c false + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, contains)(const TYPE(igraph_matrix) *m, + BASE e) { + return FUNCTION(igraph_vector, contains)(&m->data, e); +} + +/** + * \function igraph_matrix_search + * Search from a given position. + * + * Search for an element in a matrix and start the search from the + * given position. The search is performed columnwise. + * \param m The input matrix. + * \param from The position to search from, the positions are + * enumerated columnwise. + * \param what The element to search for. + * \param pos Pointer to an igraph_int_t. If the element is + * found, then this is set to the position of its first appearance. + * \param row Pointer to an igraph_int_t. If the element is + * found, then this is set to its row index. + * \param col Pointer to an igraph_int_t. If the element is + * found, then this is set to its column index. + * \return Boolean, \c true if the element is found, \c false + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, search)(const TYPE(igraph_matrix) *m, + igraph_int_t from, BASE what, igraph_int_t *pos, + igraph_int_t *row, igraph_int_t *col) { + igraph_bool_t find = FUNCTION(igraph_vector, search)(&m->data, from, what, pos); + if (find) { + *row = *pos % m->nrow; + *col = *pos / m->nrow; + } + return find; +} + +/** + * \function igraph_matrix_remove_row + * Remove a row. + * + * A row is removed from the matrix. + * \param m The input matrix. + * \param row The index of the row to remove. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, remove_row)(TYPE(igraph_matrix) *m, igraph_int_t row) { + + igraph_int_t c, r, index = row + 1, leap = 1, n = m->nrow * m->ncol; + if (row >= m->nrow) { + IGRAPH_ERROR("Cannot remove row, index out of range.", IGRAPH_EINVAL); + } + + for (c = 0; c < m->ncol; c++) { + for (r = 0; r < m->nrow - 1 && index < n; r++) { + VECTOR(m->data)[index - leap] = VECTOR(m->data)[index]; + index++; + } + leap++; + index++; + } + m->nrow--; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(&m->data, m->nrow * m->ncol)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_matrix_select_cols + * \brief Select some columns of a matrix. + * + * This function selects some columns of a matrix and returns them in a + * new matrix. The result matrix should be initialized before calling + * the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param cols Vector; it contains the column indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +igraph_error_t FUNCTION(igraph_matrix, select_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_int_t *cols) { + igraph_int_t ncols = igraph_vector_int_size(cols); + igraph_int_t nrows = m->nrow; + igraph_int_t i, j; + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, nrows, ncols)); + /* Iterate in column-major order for better performance. */ + for (j = 0; j < ncols; j++) { + for (i = 0; i < nrows; i++) { + MATRIX(*res, i, j) = MATRIX(*m, i, VECTOR(*cols)[j]); + } + } + return IGRAPH_SUCCESS; +} + +#ifdef OUT_FORMAT +#ifndef USING_R +igraph_error_t FUNCTION(igraph_matrix, printf)(const TYPE(igraph_matrix) *m, + const char *format) { + igraph_int_t nr = FUNCTION(igraph_matrix, nrow)(m); + igraph_int_t nc = FUNCTION(igraph_matrix, ncol)(m); + igraph_int_t i, j; + + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + if (j != 0) { + putchar(' '); + } + printf(format, MATRIX(*m, i, j)); + } + printf("\n"); + } + + return IGRAPH_SUCCESS; +} +#endif /* USING_R */ +#endif /* OUT_FORMAT */ + +#if defined(OUT_FORMAT) || defined(FPRINTFUNC) + +#ifndef USING_R + +igraph_error_t FUNCTION(igraph_matrix, print)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_matrix, fprint)(m, stdout); +} + +#endif /* USING_R */ + +igraph_error_t FUNCTION(igraph_matrix, fprint)(const TYPE(igraph_matrix) *m, FILE *file) { + igraph_int_t nr = FUNCTION(igraph_matrix, nrow)(m); + igraph_int_t nc = FUNCTION(igraph_matrix, ncol)(m); + igraph_int_t i, j; + igraph_vector_int_t column_width; + +#ifdef OUT_FORMAT + /* Insert dynamic width specifier '*' in format string. */ + char format[ sizeof(OUT_FORMAT) + 1 ] = "%*"; + strcpy(format + 2, (const char *) OUT_FORMAT + 1); +#endif + + IGRAPH_VECTOR_INT_INIT_FINALLY(&column_width, nc); + + /* First we detect the width needed for each matrix column. + * snprintf() may be passed a NULL pointer with a buffer size of 0. + * It will then return the number of characters that would have been written, + * without writing anything. */ + for (j = 0; j < nc; j++) { + for (i = 0; i < nr; i++) { + const int min_width = 1; /* minimum field width */ + int width; +#ifdef SNPRINTFUNC + width = SNPRINTFUNC(NULL, 0, MATRIX(*m, i, j)); +#else + width = snprintf(NULL, 0, OUT_FORMAT, MATRIX(*m, i, j)); +#endif + if (width < min_width) { + width = min_width; + } + if (width > VECTOR(column_width)[j]) { + VECTOR(column_width)[j] = width; + } + } + } + + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + if (j != 0) { + fputc(' ', file); + } +#ifdef FPRINTFUNC_ALIGNED + FPRINTFUNC_ALIGNED(file, (int) VECTOR(column_width)[j], MATRIX(*m, i, j)); +#else + fprintf(file, format, (int) VECTOR(column_width)[j], MATRIX(*m, i, j)); +#endif + } + fprintf(file, "\n"); + } + + igraph_vector_int_destroy(&column_width); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#endif /* defined(OUT_FORMAT) || defined(FPRINTFUNC) */ diff --git a/src/core/matrix_list.c b/src/core/matrix_list.c new file mode 100644 index 0000000..5d6b295 --- /dev/null +++ b/src/core/matrix_list.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_matrix_list.h" + +#define MATRIX_LIST + +#define BASE_IGRAPH_REAL +#define CUSTOM_INIT_DESTROY +#include "igraph_pmt.h" +#include "typed_list.pmt" +#include "igraph_pmt_off.h" +#undef CUSTOM_INIT_DESTROY +#undef BASE_IGRAPH_REAL + +static igraph_error_t igraph_i_matrix_list_init_item( + const igraph_matrix_list_t* list, igraph_matrix_t* item +) { + IGRAPH_UNUSED(list); + return igraph_matrix_init(item, 0, 0); +} + +static igraph_error_t igraph_i_matrix_list_copy_item( + igraph_matrix_t* dest, const igraph_matrix_t* source +) { + return igraph_matrix_init_copy(dest, source); +} + +static void igraph_i_matrix_list_destroy_item(igraph_matrix_t* item) { + igraph_matrix_destroy(item); +} + +#undef MATRIX_LIST diff --git a/src/core/memory.c b/src/core/memory.c new file mode 100644 index 0000000..f6164f7 --- /dev/null +++ b/src/core/memory.c @@ -0,0 +1,123 @@ +/* + igraph library. + Copyright (C) 2003-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_memory.h" + +/** + * \section about_alloc_funcs About allocation functions + * + * + * Some igraph functions return a pointer vector (\type igraph_vector_ptr_t) + * containing pointers to other igraph or other data types. These data + * types are dynamically allocated and have to be deallocated + * manually when the user does not need them any more. \type igraph_vector_ptr_t + * has functions to deallocate the contained pointers on its own, but in this + * case it has to be ensured that these pointers are allocated by a function + * that corresponds to the deallocator function that igraph uses. + * + * + * + * To this end, igraph exports the memory allocation functions that are used + * internally so the user of the library can ensure that the proper functions + * are used when pointers are moved between the code written by the user and + * the code of the igraph library. + * + * + * + * Additionally, the memory allocator functions used by igraph work around the + * quirks of classical \c malloc(), \c realloc() and \c calloc() implementations + * where the behaviour of allocating zero bytes is undefined. igraph allocator + * functions will always allocate at least one byte. + * + */ + +/** + * \function igraph_free + * \brief Deallocates memory that was allocated by igraph functions. + * + * This function exposes the \c free() function used internally by igraph. + * + * \param ptr Pointer to the piece of memory to be deallocated. + * + * Time complexity: platform dependent, ideally it should be O(1). + * + * \sa \ref igraph_calloc(), \ref igraph_malloc(), \ref igraph_realloc() + */ + +void igraph_free(void *ptr) { + IGRAPH_FREE(ptr); +} + + +/** + * \function igraph_calloc + * \brief Allocates memory that can be safely deallocated by igraph functions. + * + * This function behaves like \c calloc(), but it ensures that at least one + * byte is allocated even when the caller asks for zero bytes. + * + * \param count Number of items to be allocated. + * \param size Size of a single item to be allocated. + * \return Pointer to the piece of allocated memory; \c NULL if the allocation + * failed. + * + * \sa \ref igraph_malloc(), \ref igraph_realloc(), \ref igraph_free() + */ + +void *igraph_calloc(size_t count, size_t size) { + return (void *) IGRAPH_CALLOC(count * size, char); +} + + +/** + * \function igraph_malloc + * \brief Allocates memory that can be safely deallocated by igraph functions. + * + * This function behaves like \c malloc(), but it ensures that at least one + * byte is allocated even when the caller asks for zero bytes. + * + * \param size Number of bytes to be allocated. Zero is treated as one byte. + * \return Pointer to the piece of allocated memory; \c NULL if the allocation + * failed. + * + * \sa \ref igraph_calloc(), \ref igraph_realloc(), \ref igraph_free() + */ + +void *igraph_malloc(size_t size) { + return IGRAPH_MALLOC(size); +} + + +/** + * \function igraph_realloc + * \brief Reallocate memory that can be safely deallocated by igraph functions. + * + * This function behaves like \c realloc(), but it ensures that at least one + * byte is allocated even when the caller asks for zero bytes. + * + * \param ptr The pointer to reallocate. + * \param size Number of bytes to be allocated. + * \return Pointer to the piece of allocated memory; \c NULL if the allocation + * failed. + * + * \sa \ref igraph_free(), \ref igraph_malloc() + */ + +void *igraph_realloc(void *ptr, size_t size) { + return (void *) IGRAPH_REALLOC(ptr, size, char); +} diff --git a/src/core/printing.c b/src/core/printing.c new file mode 100644 index 0000000..8c82ae8 --- /dev/null +++ b/src/core/printing.c @@ -0,0 +1,230 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_complex.h" +#include "igraph_error.h" +#include "igraph_types.h" + +#include + +/* The number of digits chosen here will be used in all places where + * igraph_real_fprintf_precise() is used, including all textual graph + * formats such as GML, GraphML, Pajek, etc. DBL_DIG digits are sufficient + * to preserve the decimal representation during a + * decimal (textual) -> binary -> decimal (textual) round-trip conversion. + * This many digits are however not sufficient for a lossless + * binary -> decimal -> binary conversion. Thus, writing numerical attributes + * to a file and reading them back in may cause a tiny change in the last + * binary digit of numbers. This change is minute, always smaller than 10^-15 + * times the original number, thus acceptable. + * + * We could output more digits, but that would come with its own problem: + * It would sometimes cause a change in the decimal representation originally + * input by users, which is surprising and confusing. For example, + * + * printf("%.17g\n", 100.1) + * + * outputs 100.09999999999999 instead of 100.1. We can prevent this by + * using DBL_DIG == 15 digits instead of 17, which would be required + * for a lossless binary -> decimal -> binary round-tripping. + * + * This justifies using DBL_DIG digits, and not more, in all places. + */ +#ifdef DBL_DIG + /* Use DBL_DIG to determine the maximum precision used for %g */ + #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%." IGRAPH_I_STRINGIFY(DBL_DIG) "g" +#else + /* Assume a precision of 15 digits for %g, which is what IEEE-754 doubles require. */ + #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%.15g" +#endif + +int igraph_real_fprintf(FILE *file, igraph_real_t val) { + if (isfinite(val)) { + return fprintf(file, "%g", val); + } else if (isnan(val)) { + return fprintf(file, "NaN"); + } else if (isinf(val)) { + if (val < 0) { + return fprintf(file, "-Inf"); + } else { + return fprintf(file, "Inf"); + } + } + IGRAPH_FATAL("Value is not finite, not infinite and not NaN either!"); /* LCOV_EXCL_LINE */ +} + +#ifndef USING_R +int igraph_real_printf(igraph_real_t val) { + return igraph_real_fprintf(stdout, val); +} +#endif + +int igraph_real_fprintf_aligned(FILE *file, int width, igraph_real_t val) { + if (isfinite(val)) { + return fprintf(file, "%*g", width, val); + } else if (isnan(val)) { + return fprintf(file, "%*s", width, "NaN"); + } else if (isinf(val)) { + if (val < 0) { + return fprintf(file, "%*s", width, "-Inf"); + } else { + return fprintf(file, "%*s", width, "Inf"); + } + } + IGRAPH_FATAL("Value is not finite, not infinite and not NaN either!"); /* LCOV_EXCL_LINE */ +} + +#ifndef USING_R +int igraph_real_printf_aligned(int width, igraph_real_t val) { + return igraph_real_fprintf_aligned(stdout, width, val); +} +#endif + +int igraph_real_snprintf(char *str, size_t size, igraph_real_t val) { + if (isfinite(val)) { + return snprintf(str, size, "%g", val); + } else if (isnan(val)) { + return snprintf(str, size, "NaN"); + } else if (isinf(val)) { + if (val < 0) { + return snprintf(str, size, "-Inf"); + } else { + return snprintf(str, size, "Inf"); + } + } + IGRAPH_FATAL("Value is not finite, not infinite and not NaN either!"); /* LCOV_EXCL_LINE */ +} + +int igraph_real_fprintf_precise(FILE *file, igraph_real_t val) { + if (isfinite(val)) { + return fprintf(file, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } else if (isnan(val)) { + return fprintf(file, "NaN"); + } else if (isinf(val)) { + if (val < 0) { + return fprintf(file, "-Inf"); + } else { + return fprintf(file, "Inf"); + } + } + IGRAPH_FATAL("Value is not finite, not infinite and not NaN either!"); /* LCOV_EXCL_LINE */ +} + +#ifndef USING_R +int igraph_real_printf_precise(igraph_real_t val) { + return igraph_real_fprintf_precise(stdout, val); +} +#endif + +int igraph_real_snprintf_precise(char *str, size_t size, igraph_real_t val) { + if (isfinite(val)) { + return snprintf(str, size, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } else if (isnan(val)) { + return snprintf(str, size, "NaN"); + } else if (isinf(val)) { + if (val < 0) { + return snprintf(str, size, "-Inf"); + } else { + return snprintf(str, size, "Inf"); + } + } + IGRAPH_FATAL("Value is not finite, not infinite and not NaN either!"); /* LCOV_EXCL_LINE */ +} + +#define PROPAGATE() \ + do { \ + if (res < 0) { \ + return -1; \ + } \ + cnt += res; \ + } while (0) + +int igraph_complex_fprintf(FILE *file, igraph_complex_t val) { + int res, cnt = 0; + igraph_real_t re = IGRAPH_REAL(val), im = IGRAPH_IMAG(val); + res = igraph_real_fprintf(file, re); + PROPAGATE(); + if (! signbit(im)) { + res = fprintf(file, "+"); + PROPAGATE(); + } + res = igraph_real_fprintf(file, im); + PROPAGATE(); + res = fprintf(file, "i"); + PROPAGATE(); + return cnt; +} + +#undef PROPAGATE + +#ifndef USING_R +int igraph_complex_printf(igraph_complex_t val) { + return igraph_complex_fprintf(stdout, val); +} +#endif + +#define PROPAGATE() \ + do { \ + if (res < 0) { \ + return -1; \ + } \ + cnt += res; \ + /* remember that 'size' is unsigned, can't check if size - res < 0! */ \ + if (size > res) size -= res; \ + else size = 0; \ + if (size == 0) str = NULL; else str += res; \ + } while (0) + +int igraph_complex_snprintf(char *str, size_t size, igraph_complex_t val) { + int res, cnt = 0; + igraph_real_t re = IGRAPH_REAL(val), im = IGRAPH_IMAG(val); + res = igraph_real_snprintf(str, size, re); + PROPAGATE(); + if (! signbit(im)) { + res = snprintf(str, size, "+"); + PROPAGATE(); + } + res = igraph_real_snprintf(str, size, im); + PROPAGATE(); + res = snprintf(str, size, "i"); + PROPAGATE(); + return cnt; +} + +int igraph_complex_fprintf_aligned(FILE *file, int width, igraph_complex_t val) { + /* Most characters produces by %g is 13, so including 'i' and null terminator we + * need up to 13 + 13 + 1 + 1 = 28 characters in total. */ + char buf[28]; + + if (igraph_complex_snprintf(buf, sizeof(buf) / sizeof(buf[0]), val) < 0) { + return -1; + } + return fprintf(file, "%*s", width, buf); +} + +#ifndef USING_R +int igraph_complex_printf_aligned(int width, igraph_complex_t val) { + return igraph_complex_fprintf_aligned(stdout, width, val); +} +#endif + +#undef PROPAGATE diff --git a/src/core/progress.c b/src/core/progress.c new file mode 100644 index 0000000..11725f1 --- /dev/null +++ b/src/core/progress.c @@ -0,0 +1,150 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_progress.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +static IGRAPH_THREAD_LOCAL igraph_progress_handler_t *igraph_i_progress_handler = 0; +static IGRAPH_THREAD_LOCAL char igraph_i_progressmsg_buffer[1000]; + +/** + * \function igraph_progress + * \brief Report the progress of a calculation from an igraph function. + * + * Note that the usual way to report progress is the \ref IGRAPH_PROGRESS + * macro, as that takes care of the return value of the progress + * handler. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the name \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return Error code from the progress handler function, or \c IGRAPH_SUCCESS + * if no progress handler function was registered. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_progress(const char *message, igraph_real_t percent, void *data) { + if (igraph_i_progress_handler) { + return igraph_i_progress_handler(message, percent, data); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_progressf + * \brief Report the progress of a calculation from an igraph function, printf-like. + * + * This is a more flexible version of \ref igraph_progress(), with + * a printf-like template string. First the template string + * is filled with the additional arguments and then \ref + * igraph_progress() is called. + * + * Note that there is an upper limit for the length of + * the \p message string, currently 1000 characters. + * \param message A string describing the function or algorithm + * that is reporting the progress. For this function this is a + * template string, using the same syntax as the standard + * \c libc \c printf function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \param ... Additional argument that were specified in the + * \p message argument. + * \return Error code from the progress handler function, or \c IGRAPH_SUCCESS + * if no progress handler function was registered. + * \return + */ + +igraph_error_t igraph_progressf(const char *message, igraph_real_t percent, void *data, + ...) { + va_list ap; + va_start(ap, data); + vsnprintf(igraph_i_progressmsg_buffer, + sizeof(igraph_i_progressmsg_buffer) / sizeof(char), message, ap); + va_end(ap); + return igraph_progress(igraph_i_progressmsg_buffer, percent, data); +} + +#ifndef USING_R + +/** + * \function igraph_progress_handler_stderr + * \brief A simple predefined progress handler. + * + * This simple progress handler first prints \p message, and then + * the percentage complete value in a short message to standard error. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the same \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return This function always returns with \c IGRAPH_SUCCESS. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_progress_handler_stderr(const char *message, igraph_real_t percent, + void* data) { + IGRAPH_UNUSED(data); + fputs(message, stderr); + fprintf(stderr, "%.1f percent ready.\n", percent); + return IGRAPH_SUCCESS; +} +#endif + +/** + * \function igraph_set_progress_handler + * \brief Install a progress handler, or remove the current handler. + * + * There is a single simple predefined progress handler: + * \ref igraph_progress_handler_stderr(). + * \param new_handler Pointer to a function of type + * \ref igraph_progress_handler_t, the progress handler function to + * install. To uninstall the current progress handler, this argument + * can be a null pointer. + * \return Pointer to the previously installed progress handler function. + * + * Time complexity: O(1). + */ + +igraph_progress_handler_t * +igraph_set_progress_handler(igraph_progress_handler_t new_handler) { + igraph_progress_handler_t *previous_handler = igraph_i_progress_handler; + igraph_i_progress_handler = new_handler; + return previous_handler; +} diff --git a/src/core/psumtree.c b/src/core/psumtree.c new file mode 100644 index 0000000..5d93841 --- /dev/null +++ b/src/core/psumtree.c @@ -0,0 +1,267 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + Copyright (C) 2006 Elliot Paquette + Kalamazoo College, 1200 Academy st, Kalamazoo, MI + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_psumtree.h" +#include "igraph_error.h" + +#include "math/safe_intop.h" + +#include + +/** + * \ingroup psumtree + * \section igraph_psumtree + * + * The \type igraph_psumtree_t data type represents a partial prefix sum + * tree. A partial prefix sum tree is a data structure that can be used to draw + * samples from a discrete probability distribution with dynamic probabilities + * that are updated frequently. This is achieved by creating a binary tree where + * the leaves are the items. Each leaf contains the probability corresponding to + * the items. Intermediate nodes of the tree always contain the sum of its two + * children. When the value of a leaf node is updated, the values of its + * ancestors are also updated accordingly. + * + * Samples can be drawn from the probability distribution represented by + * the tree by generating a uniform random number between 0 (inclusive) and the + * value of the root of the tree (exclusive), and then following the branches + * of the tree as follows. In each step, the value in the current node is + * compared with the generated number. If the value in the node is larger, + * the left branch of the tree is taken; otherwise the generated number is + * decreased by the value in the node and the right branch of the tree is + * taken, until a leaf node is reached. + * + * Note that the sampling process works only if all the values in the tree + * are non-negative. This is enforced by the object; in particular, trying to + * set a negative value for an item will produce an igraph error. + */ + +/* + * Internally, a partial prefix sum tree is stored in a contiguous chunk of + * memory which we treat as a vector v. The first part (0,...,offset - 1) of + * the vector v contains the prefixes of the values contained in the latter part + * (offset, offset + size - 1) of vector v. + * + * More precisely: the part between (offset, offset + size - 1) of vector v + * contains the values (not necessarily probabilities) corresponding to the + * individual items. For the part in front of it, it holds that the value at + * index i (zero-based) is the sum of values at index (2*i + 1) and index + * (2*i + 2). The item at index zero contains the sum of all values in the + * slice between (offset, offset + size - 1). + */ + +/** + * \ingroup psumtree + * \function igraph_psumtree_init + * \brief Initializes a partial prefix sum tree. + * + * + * The tree is initialized with a fixed number of elements. After initialization, + * the value corresponding to each element is zero. + * + * \param t The tree to initialize. + * \param size The number of elements in the tree. It must be at least one. + * \return Error code, typically \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: O(n) for a tree containing n elements + */ +igraph_error_t igraph_psumtree_init(igraph_psumtree_t *t, igraph_int_t size) { + igraph_int_t vecsize; + + IGRAPH_ASSERT(size > 0); + + t->size = size; + + /* offset = 2^ceiling(log2(size)) - 1 */ + IGRAPH_CHECK(igraph_i_safe_next_pow_2(size, &t->offset)); + t->offset -= 1; + + IGRAPH_SAFE_ADD(t->offset, t->size, &vecsize); + IGRAPH_CHECK(igraph_vector_init(&t->v, vecsize)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_reset + * \brief Resets all the values in the tree to zero. + * + * \param t The tree to reset. + */ +void igraph_psumtree_reset(igraph_psumtree_t *t) { + igraph_vector_null(&t->v); +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_destroy + * \brief Destroys a partial prefix sum tree. + * + * + * All partial prefix sum trees initialized by \ref igraph_psumtree_init() + * should be properly destroyed by this function. A destroyed tree needs to be + * reinitialized by \ref igraph_psumtree_init() if you want to use it again. + * + * \param t Pointer to the (previously initialized) tree to destroy. + * + * Time complexity: operating system dependent. + */ +void igraph_psumtree_destroy(igraph_psumtree_t *t) { + igraph_vector_destroy(&(t->v)); +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_get + * \brief Retrieves the value corresponding to an item in the tree. + * + * + * + * \param t The tree to query. + * \param idx The index of the item whose value is to be retrieved. + * \return The value corresponding to the item with the given index. + * + * Time complexity: O(1) + */ +igraph_real_t igraph_psumtree_get(const igraph_psumtree_t *t, igraph_int_t idx) { + const igraph_vector_t *tree = &t->v; + return VECTOR(*tree)[t->offset + idx]; +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_search + * \brief Finds an item in the tree, given a value. + * + * This function finds the item with the lowest index where it holds that the + * sum of all the items with a \em lower index is less than or equal to the given + * value and that the sum of all the items with a lower index plus the item + * itself is larger than the given value. + * + * + * If you think about the partial prefix sum tree as a tool to sample from a + * discrete probability distribution, then calling this function repeatedly + * with uniformly distributed random numbers in the range 0 (inclusive) to the + * sum of all values in the tree (exclusive) will sample the items in the tree + * with a probability that is proportional to their associated values. + * + * \param t The tree to query. + * \param idx The index of the item is returned here. + * \param search The value to use for the search. Must be in the interval + * [0, sum), where \c sum is the sum of all elements + * (leaves) in the tree. + * \return Error code; currently the search always succeeds. + * + * Time complexity: O(log n), where n is the number of items in the tree. + */ +igraph_error_t igraph_psumtree_search(const igraph_psumtree_t *t, igraph_int_t *idx, + igraph_real_t search) { + const igraph_vector_t *tree = &t->v; + igraph_int_t i = 1; + igraph_int_t size = igraph_vector_size(tree); + + IGRAPH_ASSERT(search >= 0); + IGRAPH_ASSERT(search < igraph_psumtree_sum(t)); + + while ( 2 * i + 1 <= size) { + if ( search < VECTOR(*tree)[i * 2 - 1] ) { + i <<= 1; + } else { + search -= VECTOR(*tree)[i * 2 - 1]; + i <<= 1; + i += 1; + } + } + if (2 * i <= size) { + i = 2 * i; + } + + *idx = i - t->offset - 1; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_update + * \brief Updates the value associated to an item in the tree. + * + * \param t The tree to query. + * \param idx The index of the item to update. + * \param new_value The new value of the item. + * \return Error code, \c IGRAPH_EINVAL if the new value is negative or NaN, + * \c IGRAPH_SUCCESS if the operation was successful. + * + * Time complexity: O(log n), where n is the number of items in the tree. + */ +igraph_error_t igraph_psumtree_update(igraph_psumtree_t *t, igraph_int_t idx, + igraph_real_t new_value) { + const igraph_vector_t *tree = &t->v; + igraph_real_t difference; + + if (new_value >= 0 && isfinite(new_value)) { + idx = idx + t->offset + 1; + difference = new_value - VECTOR(*tree)[idx - 1]; + + while ( idx >= 1 ) { + VECTOR(*tree)[idx - 1] += difference; + idx >>= 1; + } + + return IGRAPH_SUCCESS; + } else { + /* Caters for negative values, infinity and NaN. */ + IGRAPH_ERRORF("Trying to use negative or non-finite weight (%g) when " + "sampling from discrete distribution using prefix sum trees.", + IGRAPH_EINVAL, new_value); + } +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_size + * \brief Returns the size of the tree. + * + * \param t The tree object + * \return The number of discrete items in the tree. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_psumtree_size(const igraph_psumtree_t *t) { + return t->size; +} + +/** + * \ingroup psumtree + * \function igraph_psumtree_sum + * \brief Returns the sum of the values of the leaves in the tree. + * + * \param t The tree object + * \return The sum of the values of the leaves in the tree. + * + * Time complexity: O(1). + */ +igraph_real_t igraph_psumtree_sum(const igraph_psumtree_t *t) { + return VECTOR(t->v)[0]; +} diff --git a/src/core/set.c b/src/core/set.c new file mode 100644 index 0000000..5bcfc52 --- /dev/null +++ b/src/core/set.c @@ -0,0 +1,323 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_memory.h" + +#include "core/set.h" + +#include /* memmove */ + +#define SET(s) ((s).stor_begin) + +/** + * \ingroup set + * \function igraph_set_init + * \brief Initializes a set. + * + * Initializes an empty set (with zero elements). Allocates memory for + * the requested capacity. No re-allocation will be necessary until the + * number of elements exceeds this initial capacity. + * + * \param set Pointer to the set to be initialized. + * \param capacity The expected number of elements in the set. + * + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the expected size of the set. + */ +igraph_error_t igraph_set_init(igraph_set_t *set, igraph_int_t capacity) { + igraph_int_t alloc_size; + + IGRAPH_ASSERT(capacity >= 0); + alloc_size = capacity > 0 ? capacity : 1; + set->stor_begin = IGRAPH_CALLOC(alloc_size, igraph_int_t); + if (! set->stor_begin) { + IGRAPH_ERROR("Cannot initialize set.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + set->stor_end = set->stor_begin + alloc_size; + set->end = set->stor_begin; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup set + * \function igraph_set_destroy + * \brief Destroys a set object. + * + * \param set Pointer to the set to be destroyed. + * + * Time complexity: operating system dependent. + */ +void igraph_set_destroy(igraph_set_t *set) { + IGRAPH_ASSERT(set != NULL); + if (set->stor_begin != NULL) { + IGRAPH_FREE(set->stor_begin); /* sets to NULL */ + } +} + +/** + * \ingroup set + * \function igraph_set_inited + * \brief Determines whether a set is initialized or not. + * + * This function checks whether the internal storage for the members of the + * set has been allocated or not, and it assumes that the pointer for the + * internal storage area contains \c NULL if the area is not initialized yet. + * This only applies if you have allocated an array of sets with \c IGRAPH_CALLOC or + * if you used the \c IGRAPH_SET_NULL constant to initialize the set. + * + * \param set The set object. + * \return Boolean, whether the set is initialized. + * + * Time complexity: O(1) + */ +igraph_bool_t igraph_set_inited(igraph_set_t *set) { + return (set->stor_begin != NULL); +} + +/** + * \ingroup set + * \function igraph_set_reserve + * \brief Reserves memory for a set. + * + * \param set The set object. + * \param capacity the new \em allocated capacity of the set. + * \return Error code. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the new allocated size of the set. + */ +igraph_error_t igraph_set_reserve(igraph_set_t *set, igraph_int_t capacity) { + igraph_int_t actual_size = igraph_set_size(set); + igraph_int_t *tmp; + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + if (capacity <= actual_size) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(set->stor_begin, capacity, igraph_int_t); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for set."); + + set->stor_begin = tmp; + set->stor_end = set->stor_begin + capacity; + set->end = set->stor_begin + actual_size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup set + * \function igraph_set_empty + * \brief Decides whether the size of the set is zero. + * + * \param set The set object. + * \return True if the size of the set is not zero and false otherwise. + * + * Time complexity: O(1). + */ +igraph_bool_t igraph_set_empty(const igraph_set_t *set) { + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + return set->stor_begin == set->end; +} + +/** + * \ingroup set + * \function igraph_set_clear + * \brief Removes all elements from the set. + * + * This function simply sets the size of the set to zero, it does + * not free any allocated memory. For that you have to call + * + * \ref igraph_set_destroy(). + * + * \param set The set object. + * + * Time complexity: O(1). + */ +void igraph_set_clear(igraph_set_t *set) { + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + set->end = set->stor_begin; +} + + +/** + * \ingroup set + * \function igraph_set_size + * \brief Gives the size of the set. + * + * The number of elements in the set. + * + * \param set The set object + * \return The size of the set. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_set_size(const igraph_set_t *set) { + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + return set->end - set->stor_begin; +} + + +/** + * \ingroup set + * \function igraph_set_add + * \brief Adds an element to the set. + * + * \param set The set object. + * \param e The element to be added. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(log(n)), n is the number of elements in \p set. + */ +igraph_error_t igraph_set_add(igraph_set_t *set, igraph_int_t e) { + igraph_int_t left, right, middle; + igraph_int_t size; + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + + size = igraph_set_size(set); + + /* search where to insert the new element */ + left = 0; + right = size - 1; + while (left < right - 1) { + middle = (left + right) / 2; + if (SET(*set)[middle] > e) { + right = middle; + } else if (SET(*set)[middle] < e) { + left = middle; + } else { + left = middle; + break; + } + } + + if (right >= 0 && SET(*set)[left] != e && SET(*set)[right] == e) { + left = right; + } + + while (left < size && set->stor_begin[left] < e) { + left++; + } + if (left >= size || set->stor_begin[left] != e) { + /* full, allocate more storage */ + if (set->stor_end == set->end) { + igraph_int_t new_size = size < IGRAPH_INTEGER_MAX/2 ? size * 2 : IGRAPH_INTEGER_MAX; + if (size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot add to set, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_set_reserve(set, new_size)); + } + + /* Element should be inserted at position 'left' */ + if (left < size) + memmove(set->stor_begin + left + 1, set->stor_begin + left, + (size - left) * sizeof(set->stor_begin[0])); + + set->stor_begin[left] = e; + set->end += 1; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup set + * \function igraph_set_contains + * \brief Checks whether a given element is in the set or not. + * + * \param set The set object. + * \param e The element being sought. + * \return True if \p e is found, false otherwise. + * + * Time complexity: O(log(n)), n is the number of elements in \p set. + */ +igraph_bool_t igraph_set_contains(const igraph_set_t *set, igraph_int_t e) { + igraph_int_t left, right, middle; + + IGRAPH_ASSERT(set != NULL); + IGRAPH_ASSERT(set->stor_begin != NULL); + + left = 0; + right = igraph_set_size(set) - 1; + + if (right == -1) { + return false; /* the set is empty */ + } + + /* search for the new element */ + while (left < right - 1) { + middle = (left + right) / 2; + if (SET(*set)[middle] > e) { + right = middle; + } else if (SET(*set)[middle] < e) { + left = middle; + } else { + return true; + } + } + + return SET(*set)[left] == e || SET(*set)[right] == e; +} + +/** + * \ingroup set + * \function igraph_set_iterate + * \brief Iterates through the element of the set. + * + * Elements are returned in an arbitrary order. + * + * \param set The set object. + * \param state Internal state of the iteration. + * This should be a pointer to a \type igraph_int_t variable + * which must be zero for the first invocation. + * The object must not be adjusted and its value should + * not be used for anything during the iteration. + * \param element The next element or 0 (if the iteration + * has ended) is returned here. + * + * \return True if there are more elements, false otherwise. + */ +igraph_bool_t igraph_set_iterate(const igraph_set_t *set, igraph_int_t *state, + igraph_int_t *element) { + IGRAPH_ASSERT(set != 0); + IGRAPH_ASSERT(set->stor_begin != 0); + IGRAPH_ASSERT(state != 0); + IGRAPH_ASSERT(element != 0); + + if (*state < igraph_set_size(set)) { + *element = set->stor_begin[*state]; + *state = *state + 1; + return true; + } else { + *element = 0; + return false; + } +} diff --git a/src/core/set.h b/src/core/set.h new file mode 100644 index 0000000..5ac0cda --- /dev/null +++ b/src/core/set.h @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2009-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CORE_SET_H +#define IGRAPH_CORE_SET_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* -------------------------------------------------- */ +/* Flexible set */ +/* -------------------------------------------------- */ + +/** + * Set containing integer numbers regardless of the order + * \ingroup types + */ + +typedef struct s_set { + igraph_int_t* stor_begin; + igraph_int_t* stor_end; + igraph_int_t* end; +} igraph_set_t; + +#define IGRAPH_SET_NULL { 0,0,0 } +#define IGRAPH_SET_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_set_init(v, size)); \ + IGRAPH_FINALLY(igraph_set_destroy, v); } while (0) + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_set_init(igraph_set_t *set, igraph_int_t capacity); +IGRAPH_PRIVATE_EXPORT void igraph_set_destroy(igraph_set_t *set); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_set_inited(igraph_set_t *set); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_set_reserve(igraph_set_t *set, igraph_int_t capacity); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_set_empty(const igraph_set_t *set); +IGRAPH_PRIVATE_EXPORT void igraph_set_clear(igraph_set_t *set); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_set_size(const igraph_set_t *set); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_set_add(igraph_set_t *set, igraph_int_t e); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_bool_t igraph_set_contains(const igraph_set_t *set, igraph_int_t e); +IGRAPH_PRIVATE_EXPORT igraph_bool_t igraph_set_iterate(const igraph_set_t *set, + igraph_int_t *state, + igraph_int_t *element); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/setup.c b/src/core/setup.c new file mode 100644 index 0000000..bef69f0 --- /dev/null +++ b/src/core/setup.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_setup.h" +#include "igraph_random.h" + +#include "random/random_internal.h" + +/** + * \ingroup setup + * \function setup_rng + * \brief Initializes the random number generator. + * + * This function initializes the igraph library by setting up a random seed + * for its internal random number generator. It should be called before + * using any igraph functions that may use random numbers. + * + * \return Error code; currently always \c IGRAPH_SUCCESS. + */ +static igraph_error_t setup_rng(void) { + igraph_rng_t *rng = igraph_rng_default(); + + if (!rng->is_seeded) { + igraph_rng_seed(rng, igraph_i_get_random_seed()); + rng->is_seeded = true; + }; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup setup + * \function igraph_setup + * \brief Initializes the igraph library. + * + * This function is a convenience function to call all setup functions that are + * provided by the igraph library. + * + * + * Most of the library functions will work even if this function is not called, + * but it is recommended to call it before using any igraph functions that may + * use random numbers, such as graph generators or random sampling functions. + * This function initializes the random number generator with a seed based on + * the current time, ensuring that the random numbers generated by igraph are + * different each time the program is run. + * + * \return Error code; currently always \c IGRAPH_SUCCESS. + */ +igraph_error_t igraph_setup(void) { + IGRAPH_CHECK(setup_rng()); + return IGRAPH_SUCCESS; +} diff --git a/src/core/sparsemat.c b/src/core/sparsemat.c new file mode 100644 index 0000000..f946467 --- /dev/null +++ b/src/core/sparsemat.c @@ -0,0 +1,3251 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_sparsemat.h" + +#include "igraph_memory.h" +#include "igraph_types.h" + +#include "internal/hacks.h" /* IGRAPH_STATIC_ASSERT */ + +#include +#include + +#include +#undef cs /* because otherwise it messes up the name of the 'cs' member in igraph_sparsemat_t */ + +/* Returns the number of potential nonzero elements in the given sparse matrix. + * The returned value can be used to iterate over A->cs->x no matter whether the + * matrix is in triplet or column-compressed form */ +static CS_INT igraph_i_sparsemat_count_elements(const igraph_sparsemat_t* A) { + return A->cs->nz < 0 ? A->cs->p[A->cs->n] : A->cs->nz; +} + +/** + * \section about_sparsemat About sparse matrices + * + * + * The igraph_sparsemat_t data type stores sparse matrices, + * i.e. matrices in which the majority of the elements are zero. + * + * + * The data type is essentially a wrapper to some of the + * functions in the CXSparse library, by Tim Davis, see + * http://faculty.cse.tamu.edu/davis/suitesparse.html + * + * + * + * Matrices can be stored in two formats: triplet and + * column-compressed. The triplet format is intended for sparse matrix + * initialization, as it is easy to add new (non-zero) elements to + * it. Most of the computations are done on sparse matrices in + * column-compressed format, after the user has converted the triplet + * matrix to column-compressed, via \ref igraph_sparsemat_compress(). + * + * + * + * Both formats are dynamic, in the sense that new elements can be + * added to them, possibly resulting the allocation of more memory. + * + * + * + * Row and column indices follow the C convention and are zero-based. + * + * + * + * \example examples/simple/igraph_sparsemat.c + * \example examples/simple/igraph_sparsemat3.c + * \example examples/simple/igraph_sparsemat4.c + * \example examples/simple/igraph_sparsemat6.c + * \example examples/simple/igraph_sparsemat7.c + * \example examples/simple/igraph_sparsemat8.c + * + */ + +/** + * \function igraph_sparsemat_init + * \brief Initializes a sparse matrix, in triplet format. + * + * This is the most common way to create a sparse matrix, together + * with the \ref igraph_sparsemat_entry() function, which can be used to + * add the non-zero elements one by one. Once done, the user can call + * \ref igraph_sparsemat_compress() to convert the matrix to + * column-compressed, to allow computations with it. + * + * The user must call \ref igraph_sparsemat_destroy() on + * the matrix to deallocate the memory, once the matrix is no more + * needed. + * \param A Pointer to a not yet initialized sparse matrix. + * \param rows The number of rows in the matrix. + * \param cols The number of columns. + * \param nzmax The maximum number of non-zero elements in the + * matrix. It is not compulsory to get this right, but it is + * useful for the allocation of the proper amount of memory. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_init(igraph_sparsemat_t *A, igraph_int_t rows, + igraph_int_t cols, igraph_int_t nzmax) { + IGRAPH_STATIC_ASSERT(sizeof(igraph_int_t) == sizeof(CS_INT)); + IGRAPH_STATIC_ASSERT(sizeof(igraph_real_t) == sizeof(CS_ENTRY)); + + if (rows < 0) { + IGRAPH_ERROR("Negative number of rows", IGRAPH_EINVAL); + } + if (cols < 0) { + IGRAPH_ERROR("Negative number of columns", IGRAPH_EINVAL); + } + + A->cs = cs_spalloc( rows, cols, nzmax, /*values=*/ 1, + /*triplet=*/ 1); + if (!A->cs) { + IGRAPH_ERROR("Cannot allocate memory for sparse matrix", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_init_copy + * \brief Copies a sparse matrix. + * + * Create a sparse matrix object, by copying another one. The source + * matrix can be either in triplet or column-compressed format. + * + * + * Exactly the same amount of memory will be allocated to the + * copy matrix, as it is currently for the original one. + * \param to Pointer to an uninitialized sparse matrix, the copy will + * be created here. + * \param from The sparse matrix to copy. + * \return Error code. + * + * Time complexity: O(n+nzmax), the number of columns plus the maximum + * number of non-zero elements. + */ + +igraph_error_t igraph_sparsemat_init_copy( + igraph_sparsemat_t *to, const igraph_sparsemat_t *from +) { + + CS_INT ne = from->cs->nz == -1 ? from->cs->n + 1 : from->cs->nzmax; + + to->cs = cs_spalloc(from->cs->m, from->cs->n, from->cs->nzmax, + /*values=*/ 1, + /*triplet=*/ igraph_sparsemat_is_triplet(from)); + + to->cs->nzmax = from->cs->nzmax; + to->cs->m = from->cs->m; + to->cs->n = from->cs->n; + to->cs->nz = from->cs->nz; + + memcpy(to->cs->p, from->cs->p, sizeof(CS_INT) * (size_t) ne); + memcpy(to->cs->i, from->cs->i, sizeof(CS_INT) * (size_t) (from->cs->nzmax)); + memcpy(to->cs->x, from->cs->x, sizeof(CS_ENTRY) * (size_t) (from->cs->nzmax)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_destroy + * \brief Deallocates memory used by a sparse matrix. + * + * One destroyed, the sparse matrix must be initialized again, before + * calling any other operation on it. + * \param A The sparse matrix to destroy. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_destroy(igraph_sparsemat_t *A) { + cs_spfree(A->cs); +} + +/** + * \function igraph_sparsemat_realloc + * \brief Allocates more (or less) memory for a sparse matrix. + * + * Sparse matrices automatically allocate more memory, as needed. To + * control memory allocation, the user can call this function, to + * allocate memory for a given number of non-zero elements. + * + * \param A The sparse matrix, it can be in triplet or + * column-compressed format. + * \param nzmax The new maximum number of non-zero elements. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_realloc(igraph_sparsemat_t *A, igraph_int_t nzmax) { + if (!cs_sprealloc(A->cs, nzmax)) { + IGRAPH_ERROR("Could not allocate more memory for sparse matrix.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_nrow + * \brief Number of rows. + * + * \param A The input matrix, in triplet or column-compressed format. + * \return The number of rows in the \p A matrix. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_nrow(const igraph_sparsemat_t *A) { + return A->cs->m; +} + +/** + * \function igraph_sparsemat_ncol + * \brief Number of columns. + * + * \param A The input matrix, in triplet or column-compressed format. + * \return The number of columns in the \p A matrix. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_ncol(const igraph_sparsemat_t *A) { + return A->cs->n; +} + +/** + * \function igraph_sparsemat_type + * \brief Type of a sparse matrix (triplet or column-compressed). + * + * Gives whether a sparse matrix is stored in the triplet format or in + * column-compressed format. + * \param A The input matrix. + * \return Either \c IGRAPH_SPARSEMAT_CC or \c + * IGRAPH_SPARSEMAT_TRIPLET. + * + * Time complexity: O(1). + */ + +igraph_sparsemat_type_t igraph_sparsemat_type(const igraph_sparsemat_t *A) { + return igraph_sparsemat_is_cc(A) ? IGRAPH_SPARSEMAT_CC : IGRAPH_SPARSEMAT_TRIPLET; +} + +/** + * \function igraph_sparsemat_is_triplet + * \brief Is this sparse matrix in triplet format? + * + * Decides whether a sparse matrix is in triplet format. + * \param A The input matrix. + * \return One if the input matrix is in triplet format, zero + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_sparsemat_is_triplet(const igraph_sparsemat_t *A) { + return A->cs->nz >= 0; +} + +/** + * \function igraph_sparsemat_is_cc + * \brief Is this sparse matrix in column-compressed format? + * + * Decides whether a sparse matrix is in column-compressed format. + * \param A The input matrix. + * \return One if the input matrix is in column-compressed format, zero + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_sparsemat_is_cc(const igraph_sparsemat_t *A) { + return A->cs->nz < 0; +} + +/** + * \function igraph_sparsemat_permute + * \brief Permutes the rows and columns of a sparse matrix. + * + * \param A The input matrix, it must be in column-compressed format. + * \param p Integer vector, giving the permutation of the rows. + * \param q Integer vector, the permutation of the columns. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \return Error code. + * + * Time complexity: O(m+n+nz), the number of rows plus the number of + * columns plus the number of non-zero elements in the matrix. + */ + +igraph_error_t igraph_sparsemat_permute(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res) { + + CS_INT nrow = A->cs->m, ncol = A->cs->n; + CS_INT* pinv; + CS_INT i; + + if (nrow != igraph_vector_int_size(p)) { + IGRAPH_ERROR("Invalid row permutation length.", IGRAPH_FAILURE); + } + if (ncol != igraph_vector_int_size(q)) { + IGRAPH_ERROR("Invalid column permutation length.", IGRAPH_FAILURE); + } + + /* We invert the permutation by hand */ + pinv = IGRAPH_CALLOC(nrow, CS_INT); + if (pinv == 0) { + IGRAPH_ERROR("Cannot allocate index vector for permutation.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, pinv); + for (i = 0; i < nrow; i++) { + pinv[ VECTOR(*p)[i] ] = i; + } + + /* And call the permutation routine */ + res->cs = cs_permute(A->cs, pinv, (const CS_INT*) VECTOR(*q), /*values=*/ 1); + if (!res->cs) { + IGRAPH_ERROR("Cannot index sparse matrix", IGRAPH_FAILURE); + } + + IGRAPH_FREE(pinv); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_index_rows(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t II, II2; + CS_INT nrow = A->cs->m; + igraph_int_t idx_rows = igraph_vector_int_size(p); + igraph_int_t k; + + /* Create index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&II2, idx_rows, nrow, idx_rows)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II2); + for (k = 0; k < idx_rows; k++) { + IGRAPH_CHECK(igraph_sparsemat_entry(&II2, k, VECTOR(*p)[k], 1.0)); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&II2, &II)); + igraph_sparsemat_destroy(&II2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(&II, A, res)); + igraph_sparsemat_destroy(&II); + IGRAPH_FINALLY_CLEAN(1); + + if (constres) { + if (res->cs->p[1] != 0) { + *constres = res->cs->x[0]; + } else { + *constres = 0.0; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_index_cols(const igraph_sparsemat_t *A, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t JJ, JJ2; + CS_INT ncol = A->cs->n; + igraph_int_t idx_cols = igraph_vector_int_size(q); + igraph_int_t k; + + /* Create index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&JJ2, ncol, idx_cols, idx_cols)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ2); + for (k = 0; k < idx_cols; k++) { + IGRAPH_CHECK(igraph_sparsemat_entry(&JJ2, VECTOR(*q)[k], k, 1.0)); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&JJ2, &JJ)); + igraph_sparsemat_destroy(&JJ2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(A, &JJ, res)); + igraph_sparsemat_destroy(&JJ); + IGRAPH_FINALLY_CLEAN(1); + + if (constres) { + if (res->cs->p [1] != 0) { + *constres = res->cs->x [0]; + } else { + *constres = 0.0; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_index + * \brief Extracts a submatrix or a single element. + * + * This function indexes into a sparse matrix. + * It serves two purposes. First, it can extract + * submatrices from a sparse matrix. Second, as a special case, it can + * extract a single element from a sparse matrix. + * + * \param A The input matrix, it must be in column-compressed format. + * \param p An integer vector, or a null pointer. The selected row + * index or indices. A null pointer selects all rows. + * \param q An integer vector, or a null pointer. The selected column + * index or indices. A null pointer selects all columns. + * \param res Pointer to an uninitialized sparse matrix, or a null + * pointer. If not a null pointer, then the selected submatrix is + * stored here. + * \param constres Pointer to a real variable or a null pointer. If + * not a null pointer, then the first non-zero element in the + * selected submatrix is stored here, if there is one. Otherwise + * zero is stored here. This behavior is handy if one + * wants to select a single entry from the matrix. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_index(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t II, JJ, II2, JJ2, tmp; + CS_INT nrow = A->cs->m; + CS_INT ncol = A->cs->n; + igraph_int_t idx_rows = p ? igraph_vector_int_size(p) : -1; + igraph_int_t idx_cols = q ? igraph_vector_int_size(q) : -1; + igraph_int_t k; + + igraph_sparsemat_t *myres = res, mres; + + if (!p && !q) { + IGRAPH_ERROR("No index vectors", IGRAPH_EINVAL); + } + + if (!res && (idx_rows != 1 || idx_cols != 1)) { + IGRAPH_ERROR("Sparse matrix indexing: must give `res' if not a " + "single element is selected", IGRAPH_EINVAL); + } + + if (!q) { + return igraph_i_sparsemat_index_rows(A, p, res, constres); + } + if (!p) { + return igraph_i_sparsemat_index_cols(A, q, res, constres); + } + + if (!res) { + myres = &mres; + } + + /* Create first index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&II2, idx_rows, nrow, idx_rows)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II2); + for (k = 0; k < idx_rows; k++) { + IGRAPH_CHECK(igraph_sparsemat_entry(&II2, k, VECTOR(*p)[k], 1.0)); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&II2, &II)); + igraph_sparsemat_destroy(&II2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II); + + /* Create second index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&JJ2, ncol, idx_cols, idx_cols)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ2); + for (k = 0; k < idx_cols; k++) { + IGRAPH_CHECK(igraph_sparsemat_entry(&JJ2, VECTOR(*q)[k], k, 1.0)); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&JJ2, &JJ)); + igraph_sparsemat_destroy(&JJ2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(&II, A, &tmp)); + igraph_sparsemat_destroy(&II); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_multiply(&tmp, &JJ, myres)); + igraph_sparsemat_destroy(&tmp); + igraph_sparsemat_destroy(&JJ); + IGRAPH_FINALLY_CLEAN(2); + + if (constres) { + if (myres->cs->p [1] != 0) { + *constres = myres->cs->x [0]; + } else { + *constres = 0.0; + } + } + + if (!res) { + igraph_sparsemat_destroy(myres); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_entry + * \brief Adds an element to a sparse matrix. + * + * This function can be used to add the entries to a sparse matrix, + * after initializing it with \ref igraph_sparsemat_init(). If you add + * multiple entries in the same position, they will all be saved, and + * the resulting value is the sum of all entries in that position. + * + * \param A The input matrix, it must be in triplet format. + * \param row The row index of the entry to add. + * \param col The column index of the entry to add. + * \param elem The value of the entry. + * \return Error code. + * + * Time complexity: O(1) on average. + */ + +igraph_error_t igraph_sparsemat_entry(igraph_sparsemat_t *A, + igraph_int_t row, igraph_int_t col, igraph_real_t elem) { + if (!igraph_sparsemat_is_triplet(A)) { + IGRAPH_ERROR("Entries can only be added to sparse matrices that are in triplet format.", + IGRAPH_EINVAL); + } + + if (!cs_entry(A->cs, row, col, elem)) { + IGRAPH_ERROR("Cannot add entry to sparse matrix.", + IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_compress + * \brief Converts a sparse matrix to column-compressed format. + * + * Converts a sparse matrix from triplet format to column-compressed format. + * Almost all sparse matrix operations require that the matrix is in + * column-compressed format. + * + * \param A The input matrix, it must be in triplet format. + * \param res Pointer to an uninitialized sparse matrix object, the + * compressed version of \p A is stored here. + * \return Error code. + * + * Time complexity: O(nz) where \c nz is the number of non-zero elements. + */ + +igraph_error_t igraph_sparsemat_compress(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res) { + + if (! igraph_sparsemat_is_triplet(A)) { + IGRAPH_ERROR("Sparse matrix to compress is not in triplet format.", IGRAPH_EINVAL); + } + res->cs = cs_compress(A->cs); + if (!res->cs) { + IGRAPH_ERROR("Cannot compress sparse matrix", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +static igraph_real_t igraph_i_sparsemat_get_cc( + const igraph_sparsemat_t *A, igraph_int_t row, igraph_int_t col +) { + /* elements in column 'col' are at indices + * A->cs->p[col] .. A->cs->p[col+1] (open from right) in + * A->cs->x . + * + * Their corresponding row indices are in A->cs->i . + */ + + CS_INT lo = A->cs->p[col]; + CS_INT hi = A->cs->p[col + 1]; + igraph_real_t result = 0.0; + + /* TODO: this could be faster with binary search if A->cs->i + * is sorted, which I think should be */ + for (; lo < hi; lo++) { + if (A->cs->i[lo] == row) { + result += A->cs->x[lo]; + } + } + + return result; +} + +static igraph_real_t igraph_i_sparsemat_get_triplet( + const igraph_sparsemat_t *A, igraph_int_t row, igraph_int_t col +) { + igraph_sparsemat_iterator_t it; + igraph_real_t result = 0.0; + + igraph_sparsemat_iterator_init(&it, A); + while (!igraph_sparsemat_iterator_end(&it)) { + if ( + igraph_sparsemat_iterator_row(&it) == row && + igraph_sparsemat_iterator_col(&it) == col + ) { + result += igraph_sparsemat_iterator_get(&it); + } + igraph_sparsemat_iterator_next(&it); + } + + return result; +} + +/** + * \function igraph_sparsemat_get + * \brief Return the value of a single element from a sparse matrix. + * + * \param A The input matrix, in triplet or column-compressed format. + * \param row The row index + * \param col The column index + * \return The value of the cell with the given row and column indices in the + * matrix; zero if the indices are out of bounds. + * + * Time complexity: TODO. + */ +igraph_real_t igraph_sparsemat_get( + const igraph_sparsemat_t *A, igraph_int_t row, igraph_int_t col +) { + if (row < 0 || col < 0 || row >= A->cs->m || col >= A->cs->n) { + return 0.0; + } else if (igraph_sparsemat_is_cc(A)) { + return igraph_i_sparsemat_get_cc(A, row, col); + } else { + return igraph_i_sparsemat_get_triplet(A, row, col); + } +} + +/** + * \function igraph_sparsemat_transpose + * \brief Transposes a sparse matrix. + * + * \param A The input matrix, column-compressed or triple format. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_transpose( + const igraph_sparsemat_t *A, igraph_sparsemat_t *res +) { + + if (igraph_sparsemat_is_cc(A)) { + /* column-compressed */ + res->cs = cs_transpose(A->cs, /* values = */ 1); + if (!res->cs) { + IGRAPH_ERROR("Cannot transpose sparse matrix", IGRAPH_FAILURE); + } + } else { + /* triplets */ + CS_INT *tmp; + IGRAPH_CHECK(igraph_sparsemat_init_copy(res, A)); + tmp = res->cs->p; + res->cs->p = res->cs->i; + res->cs->i = tmp; + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_is_symmetric_cc(const igraph_sparsemat_t *A, igraph_bool_t *result) { + igraph_sparsemat_t t, tt; + igraph_bool_t res; + igraph_int_t nz; + + IGRAPH_CHECK(igraph_sparsemat_transpose(A, &t)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &t); + IGRAPH_CHECK(igraph_sparsemat_dupl(&t)); + IGRAPH_CHECK(igraph_sparsemat_transpose(&t, &tt)); + igraph_sparsemat_destroy(&t); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tt); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tt, &t)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &t); + + nz = t.cs->p[t.cs->n]; + res = memcmp(t.cs->i, tt.cs->i, sizeof(CS_INT) * (size_t) nz) == 0; + res = res && memcmp(t.cs->p, tt.cs->p, sizeof(CS_INT) * + (size_t)(t.cs->n + 1)) == 0; + res = res && memcmp(t.cs->x, tt.cs->x, sizeof(CS_ENTRY) * (size_t)nz) == 0; + + igraph_sparsemat_destroy(&t); + igraph_sparsemat_destroy(&tt); + IGRAPH_FINALLY_CLEAN(2); + + *result = res; + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_is_symmetric_triplet(const igraph_sparsemat_t *A, igraph_bool_t *result) { + igraph_sparsemat_t tmp; + + IGRAPH_CHECK(igraph_sparsemat_compress(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_i_sparsemat_is_symmetric_cc(&tmp, result)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_is_symmetric + * \brief Returns whether a sparse matrix is symmetric. + * + * \param A The input matrix + * \param result Pointer to an \c igraph_bool_t ; the result is provided here. + * \return Error code. + */ + +igraph_error_t igraph_sparsemat_is_symmetric(const igraph_sparsemat_t *A, igraph_bool_t *result) { + if (A->cs->m != A->cs->n) { + *result = false; + } else if (igraph_sparsemat_is_cc(A)) { + IGRAPH_CHECK(igraph_i_sparsemat_is_symmetric_cc(A, result)); + } else { + IGRAPH_CHECK(igraph_i_sparsemat_is_symmetric_triplet(A, result)); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_dupl + * \brief Removes duplicate elements from a sparse matrix. + * + * It is possible that a column-compressed sparse matrix stores a + * single matrix entry in multiple pieces. The entry is then the sum + * of all its pieces. (Some functions create matrices like this.) This + * function eliminates the multiple pieces. + * + * \param A The input matrix, in column-compressed format. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_dupl(igraph_sparsemat_t *A) { + + if (! igraph_sparsemat_is_cc(A)) { + IGRAPH_ERROR("Sparse matrix must be in compressed format in order to remove duplicates.", IGRAPH_EINVAL); + } + + if (!cs_dupl(A->cs)) { + IGRAPH_ERROR("Cannot remove duplicates from sparse matrix.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +struct fkeep_wrapper_data { + igraph_int_t (*fkeep) (igraph_int_t, igraph_int_t, igraph_real_t, void*); + void* data; +}; + +static CS_INT fkeep_wrapper(CS_INT row, CS_INT col, double value, void* data) { + return ((struct fkeep_wrapper_data*)data)->fkeep( + row, col, value, ((struct fkeep_wrapper_data*)data)->data + ); +} + +/** + * \function igraph_sparsemat_fkeep + * \brief Filters the elements of a sparse matrix. + * + * This function can be used to filter the (non-zero) elements of a + * sparse matrix. For all entries, it calls the supplied function and + * depending on the return values either keeps, or deleted the element + * from the matrix. + * + * \param A The input matrix, in column-compressed format. + * \param fkeep The filter function. It must take four arguments: the + * first is an \c igraph_int_t, the row index of the entry, the second is + * another \c igraph_int_t, the column index. The third is \c igraph_real_t, + * the value of the entry. The fourth element is a \c void pointer, + * the \p other argument is passed here. The function must return + * an \c int. If this is zero, then the entry is deleted, otherwise + * it is kept. + * \param other A \c void pointer that is passed to the filtering + * function. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_fkeep( + igraph_sparsemat_t *A, + igraph_int_t (*fkeep)(igraph_int_t, igraph_int_t, igraph_real_t, void*), + void *other +) { + struct fkeep_wrapper_data wrapper_data = { + /* .fkeep = */ fkeep, + /* .data = */ other + }; + + IGRAPH_ASSERT(A); + IGRAPH_ASSERT(fkeep); + if (!igraph_sparsemat_is_cc(A)) { + IGRAPH_ERROR("The sparse matrix is not in compressed format.", IGRAPH_EINVAL); + } + if (cs_fkeep(A->cs, fkeep_wrapper, &wrapper_data) < 0) { + IGRAPH_ERROR("External function cs_keep has returned an unknown error while filtering the matrix.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_dropzeros + * \brief Drops the zero elements from a sparse matrix. + * + * As a result of matrix operations, some of the entries in a sparse + * matrix might be zero. This function removes these entries. + * + * \param A The input matrix, it must be in column-compressed format. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_dropzeros(igraph_sparsemat_t *A) { + + if (!cs_dropzeros(A->cs)) { + IGRAPH_ERROR("Cannot drop zeros from sparse matrix", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_droptol + * \brief Drops the almost zero elements from a sparse matrix. + * + * This function is similar to \ref igraph_sparsemat_dropzeros(), but it + * also drops entries that are closer to zero than the given tolerance + * threshold. + * + * \param A The input matrix, it must be in column-compressed format. + * \param tol Real number, giving the tolerance threshold. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_droptol(igraph_sparsemat_t *A, igraph_real_t tol) { + + IGRAPH_ASSERT(A); + if (!igraph_sparsemat_is_cc(A)) { + IGRAPH_ERROR("The sparse matrix is not in compressed format.", IGRAPH_EINVAL); + } + if (cs_droptol(A->cs, tol) < 0) { + IGRAPH_ERROR("External function cs_droptol has returned an unknown error.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_multiply + * \brief Matrix multiplication. + * + * Multiplies two sparse matrices. + * + * \param A The first input matrix (left hand side), in + * column-compressed format. + * \param B The second input matrix (right hand side), in + * column-compressed format. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_multiply(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_sparsemat_t *res) { + + res->cs = cs_multiply(A->cs, B->cs); + if (!res->cs) { + IGRAPH_ERROR("Cannot multiply matrices", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_add + * \brief Sum of two sparse matrices. + * + * \param A The first input matrix, in column-compressed format. + * \param B The second input matrix, in column-compressed format. + * \param alpha Real value, \p A is multiplied by \p alpha before the + * addition. + * \param beta Real value, \p B is multiplied by \p beta before the + * addition. + * \param res Pointer to an uninitialized sparse matrix, the result + * is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_add(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_real_t alpha, + igraph_real_t beta, + igraph_sparsemat_t *res) { + + res->cs = cs_add(A->cs, B->cs, alpha, beta); + if (!res->cs) { + IGRAPH_ERROR("Cannot add matrices", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_gaxpy + * \brief Matrix-vector product, added to another vector. + * + * \param A The input matrix, in column-compressed format. + * \param x The input vector, its size must match the number of + * columns in \p A. + * \param res This vector is added to the matrix-vector product + * and it is overwritten by the result. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_gaxpy(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + igraph_vector_t *res) { + + if (A->cs->n != igraph_vector_size(x) || + A->cs->m != igraph_vector_size(res)) { + IGRAPH_ERROR("Invalid matrix/vector size for multiplication", + IGRAPH_EINVAL); + } + + if (! (cs_gaxpy(A->cs, VECTOR(*x), VECTOR(*res)))) { + IGRAPH_ERROR("Cannot perform sparse matrix vector multiplication", + IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_lsolve + * \brief Solves a lower-triangular linear system. + * + * Solve the Lx=b linear equation system, where the L coefficient + * matrix is square and lower-triangular, with a zero-free diagonal. + * + * \param L The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_lsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (L->cs->m != L->cs->n) { + IGRAPH_ERROR("Cannot perform lower triangular solve on non-square matrix.", IGRAPH_EINVAL); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_lsolve(L->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform lower triangular solve.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_ltsolve + * \brief Solves an upper-triangular linear system. + * + * Solve the L'x=b linear equation system, where the L + * matrix is square and lower-triangular, with a zero-free diagonal. + * + * \param L The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_ltsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (L->cs->m != L->cs->n) { + IGRAPH_ERROR( + "Cannot perform transposed lower triangular solve on non-square matrix.", + IGRAPH_EINVAL + ); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (!cs_ltsolve(L->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform lower triangular solve.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_usolve + * \brief Solves an upper-triangular linear system. + * + * Solves the Ux=b upper triangular system. + * + * \param U The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_usolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (U->cs->m != U->cs->n) { + IGRAPH_ERROR( + "Cannot perform upper triangular solve on non-square matrix.", + IGRAPH_EINVAL + ); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_usolve(U->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform upper triangular solve.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_utsolve + * \brief Solves a lower-triangular linear system. + * + * This is the same as \ref igraph_sparsemat_usolve(), but U'x=b is + * solved, where the apostrophe denotes the transpose. + * + * \param U The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_utsolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (U->cs->m != U->cs->n) { + IGRAPH_ERROR( + "Cannot perform transposed upper triangular solve on non-square matrix.", + IGRAPH_EINVAL + ); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (!cs_utsolve(U->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform transposed upper triangular solve.", + IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_cholsol + * \brief Solves a symmetric linear system via Cholesky decomposition. + * + * Solve Ax=b, where A is a symmetric positive definite matrix. + * + * \param A The input matrix, in column-compressed format. + * \param b The right hand side. + * \param res An initialized vector, the result is stored here. + * \param order An integer giving the ordering method to use for the + * factorization. Zero is the natural ordering; if it is one, then + * the fill-reducing minimum-degree ordering of A+A' is used. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_cholsol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order) { + + if (A->cs->m != A->cs->n) { + IGRAPH_ERROR( + "Cannot perform sparse symmetric solve on non-square matrix.", + IGRAPH_EINVAL + ); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_cholsol(order, A->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform sparse symmetric solve.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_lusol + * \brief Solves a linear system via LU decomposition. + * + * Solve Ax=b, via LU factorization of A. + * + * \param A The input matrix, in column-compressed format. + * \param b The right hand side of the equation. + * \param res An initialized vector, the result is stored here. + * \param order The ordering method to use, zero means the natural + * ordering, one means the fill-reducing minimum-degree ordering of + * A+A', two means the ordering of A'*A, after removing the dense + * rows from A. Three means the ordering of A'*A. + * \param tol Real number, the tolerance limit to use for the numeric + * LU factorization. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_lusol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + igraph_int_t order, + igraph_real_t tol) { + + if (A->cs->m != A->cs->n) { + IGRAPH_ERROR("Cannot perform LU solve on non-square matrix.", IGRAPH_EINVAL); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_lusol(order, A->cs, VECTOR(*res), tol)) { + IGRAPH_ERROR("Cannot perform LU solve.", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +#define CHECK(x) if ((x)<0) { IGRAPH_ERROR("Cannot write to file", IGRAPH_EFILE); } + +/** + * \function igraph_sparsemat_print + * \brief Prints a sparse matrix to a file. + * + * Only the non-zero entries are printed. This function serves more as + * a debugging utility, as currently there is no function that could + * read back the printed matrix from the file. + * + * \param A The input matrix, triplet or column-compressed format. + * \param outstream The stream to print it to. + * \return Error code. + * + * Time complexity: O(nz) for triplet matrices, O(n+nz) for + * column-compressed matrices. nz is the number of non-zero elements, + * n is the number columns in the matrix. + */ + +igraph_error_t igraph_sparsemat_print(const igraph_sparsemat_t *A, + FILE *outstream) { + + if (igraph_sparsemat_is_cc(A)) { + /* CC */ + CS_INT j, p; + for (j = 0; j < A->cs->n; j++) { + CHECK(fprintf(outstream, "col " CS_ID ": locations " CS_ID " to " CS_ID "\n", + j, A->cs->p[j], A->cs->p[j + 1] - 1)); + for (p = A->cs->p[j]; p < A->cs->p[j + 1]; p++) { + CHECK(fprintf(outstream, CS_ID " : %g\n", A->cs->i[p], A->cs->x[p])); + } + } + } else { + /* Triplet */ + CS_INT p; + for (p = 0; p < A->cs->nz; p++) { + CHECK(fprintf(outstream, CS_ID " " CS_ID " : %g\n", + A->cs->i[p], A->cs->p[p], A->cs->x[p])); + } + } + + return IGRAPH_SUCCESS; +} + +#undef CHECK + +static igraph_error_t igraph_i_sparsemat_eye_triplet( + igraph_sparsemat_t *A, igraph_int_t n, igraph_int_t nzmax, + igraph_real_t value +) { + igraph_int_t i; + + IGRAPH_CHECK(igraph_sparsemat_init(A, n, n, nzmax)); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_sparsemat_entry(A, i, i, value)); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_eye_cc( + igraph_sparsemat_t *A, igraph_int_t n, igraph_real_t value +) { + igraph_int_t i; + + A->cs = cs_spalloc(n, n, n, /*values=*/ 1, /*triplet=*/ 0); + if (!A->cs) { + IGRAPH_ERROR("Cannot create eye sparse matrix", IGRAPH_FAILURE); + } + + for (i = 0; i < n; i++) { + A->cs->p [i] = i; + A->cs->i [i] = i; + A->cs->x [i] = value; + } + A->cs->p [n] = n; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_init_eye + * \brief Creates a sparse identity matrix. + * + * \param A An uninitialized sparse matrix, the result is stored + * here. + * \param n The number of rows and number of columns in the matrix. + * \param nzmax The maximum number of non-zero elements, this + * essentially gives the amount of memory that will be allocated for + * matrix elements. + * \param value The value to store in the diagonal. + * \param compress Whether to create a column-compressed matrix. If + * false, then a triplet matrix is created. + * \return Error code. + * + * Time complexity: O(n). + */ + +igraph_error_t igraph_sparsemat_init_eye( + igraph_sparsemat_t *A, igraph_int_t n, igraph_int_t nzmax, + igraph_real_t value, igraph_bool_t compress +) { + if (compress) { + return igraph_i_sparsemat_eye_cc(A, n, value); + } else { + return igraph_i_sparsemat_eye_triplet(A, n, nzmax, value); + } +} + +static igraph_error_t igraph_i_sparsemat_init_diag_triplet( + igraph_sparsemat_t *A, igraph_int_t nzmax, const igraph_vector_t *values +) { + + CS_INT i, n = igraph_vector_size(values); + + IGRAPH_CHECK(igraph_sparsemat_init(A, n, n, nzmax)); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_sparsemat_entry(A, i, i, VECTOR(*values)[i])); + } + + return IGRAPH_SUCCESS; + +} + +static igraph_error_t igraph_i_sparsemat_init_diag_cc(igraph_sparsemat_t *A, + const igraph_vector_t *values) { + + CS_INT i, n = igraph_vector_size(values); + + A->cs = cs_spalloc(n, n, n, /*values=*/ 1, /*triplet=*/ 0); + if (!A->cs) { + IGRAPH_ERROR("Cannot create eye sparse matrix", IGRAPH_FAILURE); + } + + for (i = 0; i < n; i++) { + A->cs->p [i] = i; + A->cs->i [i] = i; + A->cs->x [i] = VECTOR(*values)[i]; + } + A->cs->p [n] = n; + + return IGRAPH_SUCCESS; + +} + +/** + * \function igraph_sparsemat_init_diag + * \brief Creates a sparse diagonal matrix. + * + * \param A An uninitialized sparse matrix, the result is stored + * here. + * \param nzmax The maximum number of non-zero elements, this + * essentially gives the amount of memory that will be allocated for + * matrix elements. + * \param values The values to store in the diagonal, the size of the + * matrix defined by the length of this vector. + * \param compress Whether to create a column-compressed matrix. If + * false, then a triplet matrix is created. + * \return Error code. + * + * Time complexity: O(n), the length of the diagonal vector. + */ + +igraph_error_t igraph_sparsemat_init_diag( + igraph_sparsemat_t *A, igraph_int_t nzmax, const igraph_vector_t *values, + igraph_bool_t compress +) { + if (compress) { + return (igraph_i_sparsemat_init_diag_cc(A, values)); + } else { + return (igraph_i_sparsemat_init_diag_triplet(A, nzmax, values)); + } +} + +static igraph_error_t igraph_i_sparsemat_arpack_multiply(igraph_real_t *to, + const igraph_real_t *from, + int n, + void *extra) { + igraph_sparsemat_t *A = extra; + const igraph_vector_t vfrom = igraph_vector_view(from, n); + igraph_vector_t vto = igraph_vector_view(to, n); + + igraph_vector_null(&vto); + IGRAPH_CHECK(igraph_sparsemat_gaxpy(A, &vfrom, &vto)); + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_sparsemat_arpack_rssolve_data_t { + igraph_sparsemat_symbolic_t *dis; + igraph_sparsemat_numeric_t *din; + igraph_real_t tol; + igraph_sparsemat_solve_t method; +} igraph_i_sparsemat_arpack_rssolve_data_t; + +static igraph_error_t igraph_i_sparsemat_arpack_solve(igraph_real_t *to, + const igraph_real_t *from, + int n, + void *extra) { + + igraph_i_sparsemat_arpack_rssolve_data_t *data = extra; + const igraph_vector_t vfrom = igraph_vector_view(from, n); + igraph_vector_t vto = igraph_vector_view(to, n); + + if (data->method == IGRAPH_SPARSEMAT_SOLVE_LU) { + IGRAPH_CHECK(igraph_sparsemat_luresol(data->dis, data->din, &vfrom, + &vto)); + } else if (data->method == IGRAPH_SPARSEMAT_SOLVE_QR) { + IGRAPH_CHECK(igraph_sparsemat_qrresol(data->dis, data->din, &vfrom, + &vto)); + + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_arpack_rssolve + * \brief Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK. + * + * \param A The input matrix, must be column-compressed. + * \param options It is passed to \ref igraph_arpack_rssolve(). Supply + * \c NULL here to use the defaults. See \ref igraph_arpack_options_t for the + * details. If \c mode is 1, then ARPACK uses regular mode, if \c mode is 3, + * then shift and invert mode is used and the \c sigma structure member defines + * the shift. + * \param storage Storage for ARPACK. See \ref + * igraph_arpack_rssolve() and \ref igraph_arpack_storage_t for + * details. + * \param values An initialized vector or a null pointer, the + * eigenvalues are stored here. + * \param vectors An initialised matrix, or a null pointer, the + * eigenvectors are stored here, in the columns. + * \param solvemethod The method to solve the linear system, if \c + * mode is 3, i.e. the shift and invert mode is used. + * Possible values: + * \clist + * \cli IGRAPH_SPARSEMAT_SOLVE_LU + * The linear system is solved using LU decomposition. + * \cli IGRAPH_SPARSEMAT_SOLVE_QR + * The linear system is solved using QR decomposition. + * \endclist + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_arpack_rssolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_sparsemat_solve_t solvemethod) { + + igraph_int_t n = igraph_sparsemat_nrow(A); + + if (n != igraph_sparsemat_ncol(A)) { + IGRAPH_ERROR("Non-square matrix for ARPACK.", IGRAPH_EINVAL); + } + + if (n > INT_MAX) { + IGRAPH_ERROR("Matrix too large for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + + options->n = (int) n; + + if (options->mode == 1) { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_sparsemat_arpack_multiply, + (void*) A, options, storage, + values, vectors)); + } else if (options->mode == 3) { + igraph_real_t sigma = options->sigma; + igraph_sparsemat_t OP, eye; + igraph_sparsemat_symbolic_t symb; + igraph_sparsemat_numeric_t num; + igraph_i_sparsemat_arpack_rssolve_data_t data; + /*-----------------------------------*/ + /* We need to factor the (A-sigma*I) */ + /*-----------------------------------*/ + + /* Create (A-sigma*I) */ + IGRAPH_CHECK(igraph_sparsemat_init_eye(&eye, /*n=*/ n, /*nzmax=*/ n, + /*value=*/ -sigma, /*compress=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &eye); + IGRAPH_CHECK(igraph_sparsemat_add(/*A=*/ A, /*B=*/ &eye, /*alpha=*/ 1.0, + /*beta=*/ 1.0, /*res=*/ &OP)); + igraph_sparsemat_destroy(&eye); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &OP); + + if (solvemethod == IGRAPH_SPARSEMAT_SOLVE_LU) { + /* Symbolic analysis */ + IGRAPH_CHECK(igraph_sparsemat_symblu(/*order=*/ 0, &OP, &symb)); + IGRAPH_FINALLY(igraph_sparsemat_symbolic_destroy, &symb); + /* Numeric LU factorization */ + IGRAPH_CHECK(igraph_sparsemat_lu(&OP, &symb, &num, /*tol=*/ 0)); + IGRAPH_FINALLY(igraph_sparsemat_numeric_destroy, &num); + } else if (solvemethod == IGRAPH_SPARSEMAT_SOLVE_QR) { + /* Symbolic analysis */ + IGRAPH_CHECK(igraph_sparsemat_symbqr(/*order=*/ 0, &OP, &symb)); + IGRAPH_FINALLY(igraph_sparsemat_symbolic_destroy, &symb); + /* Numeric QR factorization */ + IGRAPH_CHECK(igraph_sparsemat_qr(&OP, &symb, &num)); + IGRAPH_FINALLY(igraph_sparsemat_numeric_destroy, &num); + } + + data.dis = &symb; + data.din = # + data.tol = options->tol; + data.method = solvemethod; + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_sparsemat_arpack_solve, + (void*) &data, options, storage, + values, vectors)); + + igraph_sparsemat_numeric_destroy(&num); + igraph_sparsemat_symbolic_destroy(&symb); + igraph_sparsemat_destroy(&OP); + IGRAPH_FINALLY_CLEAN(3); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_arpack_rnsolve + * \brief Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK. + * + * Eigenvalues and/or eigenvectors of a nonsymmetric sparse matrix. + * + * \param A The input matrix, in column-compressed mode. + * \param options ARPACK options, it is passed to \ref + * igraph_arpack_rnsolve(). Supply \c NULL here to use the defaults. + * See also \ref igraph_arpack_options_t for details. + * \param storage Storage for ARPACK, this is passed to \ref + * igraph_arpack_rnsolve(). See \ref igraph_arpack_storage_t for + * details. + * \param values An initialized matrix, or a null pointer. If not a + * null pointer, then the eigenvalues are stored here, the first + * column is the real part, the second column is the imaginary + * part. + * \param vectors An initialized matrix, or a null pointer. If not a + * null pointer, then the eigenvectors are stored here, please see + * \ref igraph_arpack_rnsolve() for the format. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_arpack_rnsolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, + igraph_matrix_t *vectors) { + + igraph_int_t n = igraph_sparsemat_nrow(A); + + if (n > INT_MAX) { + IGRAPH_ERROR("Matrix too large for ARPACK.", IGRAPH_EOVERFLOW); + } + + if (n != igraph_sparsemat_ncol(A)) { + IGRAPH_ERROR("Non-square matrix for ARPACK.", IGRAPH_EINVAL); + } + + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + + options->n = (int) n; + + return igraph_arpack_rnsolve(igraph_i_sparsemat_arpack_multiply, + (void*) A, options, storage, + values, vectors); +} + +/** + * \function igraph_sparsemat_symbqr + * \brief Symbolic QR decomposition. + * + * QR decomposition of sparse matrices involves two steps, the first + * is calling this function, and then \ref + * igraph_sparsemat_qr(). + * + * \param order The ordering to use: 0 means natural ordering, 1 means + * minimum degree ordering of A+A', 2 is minimum degree ordering of + * A'A after removing the dense rows from A, and 3 is the minimum + * degree ordering of A'A. + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic analysis is stored here. Once + * not needed anymore, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy(). + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_symbqr(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis) { + + dis->symbolic = cs_sqr(order, A->cs, /*qr=*/ 1); + if (!dis->symbolic) { + IGRAPH_ERROR("Cannot do symbolic QR decomposition", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_symblu + * \brief Symbolic LU decomposition. + * + * LU decomposition of sparse matrices involves two steps, the first + * is calling this function, and then \ref igraph_sparsemat_lu(). + * + * \param order The ordering to use: 0 means natural ordering, 1 means + * minimum degree ordering of A+A', 2 is minimum degree ordering of + * A'A after removing the dense rows from A, and 3 is the minimum + * degree ordering of A'A. + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic analysis is stored here. Once + * not needed anymore, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy(). + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_symblu(igraph_int_t order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis) { + + dis->symbolic = cs_sqr(order, A->cs, /*qr=*/ 0); + if (!dis->symbolic) { + IGRAPH_ERROR("Cannot do symbolic LU decomposition", IGRAPH_FAILURE); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_lu + * \brief LU decomposition of a sparse matrix. + * + * Performs numeric sparse LU decomposition of a matrix. + * + * \param A The input matrix, in column-compressed format. + * \param dis The symbolic analysis for LU decomposition, coming from + * a call to the \ref igraph_sparsemat_symblu() function. + * \param din The numeric decomposition, the result is stored here. It + * can be used to solve linear systems with changing right hand + * side vectors, by calling \ref igraph_sparsemat_luresol(). Once + * not needed any more, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy() on it. + * \param tol The tolerance for the numeric LU decomposition. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_lu(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din, double tol) { + din->numeric = cs_lu(A->cs, dis->symbolic, tol); + if (!din->numeric) { + IGRAPH_ERROR("Cannot do LU decomposition", IGRAPH_FAILURE); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_qr + * \brief QR decomposition of a sparse matrix. + * + * Numeric QR decomposition of a sparse matrix. + * + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic QR analysis, from the + * function \ref igraph_sparsemat_symbqr(). + * \param din The result of the decomposition is stored here, it can + * be used to solve many linear systems with the same coefficient + * matrix and changing right hand sides, using the \ref + * igraph_sparsemat_qrresol() function. Once not needed any more, + * one should call \ref igraph_sparsemat_numeric_destroy() on it to + * free the allocated memory. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_qr(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din) { + din->numeric = cs_qr(A->cs, dis->symbolic); + if (!din->numeric) { + IGRAPH_ERROR("Cannot do QR decomposition", IGRAPH_FAILURE); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_luresol + * \brief Solves a linear system using a precomputed LU decomposition. + * + * Uses the LU decomposition of a matrix to solve linear systems. + * + * \param dis The symbolic analysis of the coefficient matrix, the + * result of \ref igraph_sparsemat_symblu(). + * \param din The LU decomposition, the result of a call to \ref + * igraph_sparsemat_lu(). + * \param b A vector that defines the right hand side of the linear + * equation system. + * \param res An initialized vector, the solution of the linear system + * is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_luresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res) { + igraph_int_t n = din->numeric->L->n; + igraph_real_t *workspace; + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + workspace = IGRAPH_CALLOC(n, igraph_real_t); + if (!workspace) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, workspace); + + if (!cs_ipvec(din->numeric->pinv, VECTOR(*res), workspace, n)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_lsolve(din->numeric->L, workspace)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_usolve(din->numeric->U, workspace)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_ipvec(dis->symbolic->q, workspace, VECTOR(*res), n)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + + IGRAPH_FREE(workspace); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_qrresol + * \brief Solves a linear system using a precomputed QR decomposition. + * + * Solves a linear system using a QR decomposition of its coefficient + * matrix. + * + * \param dis Symbolic analysis of the coefficient matrix, the result + * of \ref igraph_sparsemat_symbqr(). + * \param din The QR decomposition of the coefficient matrix, the + * result of \ref igraph_sparsemat_qr(). + * \param b Vector, giving the right hand side of the linear equation + * system. + * \param res An initialized vector, the solution is stored here. It + * is resized as needed. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_qrresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res) { + igraph_int_t n = din->numeric->L->n; + igraph_real_t *workspace; + igraph_int_t k; + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + workspace = IGRAPH_CALLOC(dis->symbolic ? dis->symbolic->m2 : 1, + igraph_real_t); + if (!workspace) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + IGRAPH_FINALLY(igraph_free, workspace); + + if (!cs_ipvec(dis->symbolic->pinv, VECTOR(*res), workspace, n)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + for (k = 0; k < n; k++) { + if (!cs_happly(din->numeric->L, k, din->numeric->B[k], workspace)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + } + if (!cs_usolve(din->numeric->U, workspace)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_ipvec(dis->symbolic->q, workspace, VECTOR(*res), n)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + + IGRAPH_FREE(workspace); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_symbolic_destroy + * \brief Deallocates memory after a symbolic decomposition. + * + * Frees the memory allocated by \ref igraph_sparsemat_symbqr() or + * \ref igraph_sparsemat_symblu(). + * + * \param dis The symbolic analysis. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_symbolic_destroy(igraph_sparsemat_symbolic_t *dis) { + cs_sfree(dis->symbolic); + dis->symbolic = 0; +} + +/** + * \function igraph_sparsemat_numeric_destroy + * \brief Deallocates memory after a numeric decomposition. + * + * Frees the memoty allocated by \ref igraph_sparsemat_qr() or \ref + * igraph_sparsemat_lu(). + * + * \param din The LU or QR decomposition. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_numeric_destroy(igraph_sparsemat_numeric_t *din) { + cs_nfree(din->numeric); + din->numeric = 0; +} + +/** + * \function igraph_matrix_as_sparsemat + * \brief Converts a dense matrix to a sparse matrix. + * + * \param res An uninitialized sparse matrix, the result is stored + * here. + * \param mat The dense input matrix. + * \param tol The tolerance for zero comparisons. Values closer than + * \p tol to zero are considered as zero, and will not be included + * in the sparse matrix. + * \return Error code. + * + * \sa \ref igraph_sparsemat_as_matrix() for the reverse conversion. + * + * Time complexity: O(mn), the number of elements in the dense + * matrix. + */ + +igraph_error_t igraph_matrix_as_sparsemat(igraph_sparsemat_t *res, + const igraph_matrix_t *mat, + igraph_real_t tol) { + igraph_int_t nrow = igraph_matrix_nrow(mat); + igraph_int_t ncol = igraph_matrix_ncol(mat); + igraph_int_t i, j, nzmax = 0; + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + if (fabs(MATRIX(*mat, i, j)) > tol) { + nzmax++; + } + } + } + + IGRAPH_CHECK(igraph_sparsemat_init(res, nrow, ncol, nzmax)); + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + if (fabs(MATRIX(*mat, i, j)) > tol) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, i, j, MATRIX(*mat, i, j))); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_as_matrix_cc(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + + igraph_int_t nrow = igraph_sparsemat_nrow(spmat); + igraph_int_t ncol = igraph_sparsemat_ncol(spmat); + CS_INT from = 0, to = 0; + CS_INT *p = spmat->cs->p; + CS_INT *i = spmat->cs->i; + CS_ENTRY *x = spmat->cs->x; + CS_INT elem_count = spmat->cs->p[ spmat->cs->n ]; + + IGRAPH_CHECK(igraph_matrix_resize(res, nrow, ncol)); + igraph_matrix_null(res); + + while (*p < elem_count) { + while (to < *(p + 1)) { + MATRIX(*res, *i, from) += *x; + to++; + i++; + x++; + } + from++; + p++; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_as_matrix_triplet(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + igraph_int_t nrow = igraph_sparsemat_nrow(spmat); + igraph_int_t ncol = igraph_sparsemat_ncol(spmat); + CS_INT *i = spmat->cs->p; + CS_INT *j = spmat->cs->i; + CS_ENTRY *x = spmat->cs->x; + CS_INT nz = spmat->cs->nz; + CS_INT e; + + IGRAPH_CHECK(igraph_matrix_resize(res, nrow, ncol)); + igraph_matrix_null(res); + + for (e = 0; e < nz; e++, i++, j++, x++) { + MATRIX(*res, *j, *i) += *x; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_as_matrix + * \brief Converts a sparse matrix to a dense matrix. + * + * \param res Pointer to an initialized matrix, the result is stored + * here. It will be resized to the required size. + * \param spmat The input sparse matrix, in triplet or + * column-compressed format. + * \return Error code. + * + * \sa \ref igraph_matrix_as_sparsemat() for the reverse conversion. + * + * Time complexity: O(mn), the number of elements in the dense + * matrix. + */ + +igraph_error_t igraph_sparsemat_as_matrix(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + if (spmat->cs->nz < 0) { + return (igraph_i_sparsemat_as_matrix_cc(res, spmat)); + } else { + return (igraph_i_sparsemat_as_matrix_triplet(res, spmat)); + } +} + +/** + * \function igraph_sparsemat_max + * \brief Maximum of a sparse matrix. + * + * \param A The input matrix, column-compressed. + * \return The maximum in the input matrix, or -IGRAPH_INFINITY + * if the matrix has zero elements. + * + * Time complexity: TODO. + */ + +igraph_real_t igraph_sparsemat_max(igraph_sparsemat_t *A) { + CS_INT i, n; + CS_ENTRY *ptr; + igraph_real_t res; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = igraph_i_sparsemat_count_elements(A); + if (n == 0) { + return -IGRAPH_INFINITY; + } + res = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr > res) { + res = *ptr; + } + } + return res; +} + +/* TODO: CC matrix don't actually need _dupl, + because the elements are right beside each other. + Same for max and minmax. */ + +/** + * \function igraph_sparsemat_min + * \brief Minimum of a sparse matrix. + * + * \param A The input matrix, column-compressed. + * \return The minimum in the input matrix, or \c IGRAPH_INFINITY + * if the matrix has zero elements. + * + * Time complexity: TODO. + */ + +igraph_real_t igraph_sparsemat_min(igraph_sparsemat_t *A) { + CS_INT i, n; + CS_ENTRY *ptr; + igraph_real_t res; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = igraph_i_sparsemat_count_elements(A); + if (n == 0) { + return IGRAPH_INFINITY; + } + res = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr < res) { + res = *ptr; + } + } + return res; +} + +/** + * \function igraph_sparsemat_minmax + * \brief Minimum and maximum of a sparse matrix. + * + * \param A The input matrix, column-compressed. + * \param min The minimum in the input matrix is stored here, or \c + * IGRAPH_INFINITY if the matrix has zero elements. + * \param max The maximum in the input matrix is stored here, or + * -IGRAPH_INFINITY if the matrix has zero elements. + * \return Error code. + * + * Time complexity: TODO. + */ + + +igraph_error_t igraph_sparsemat_minmax(igraph_sparsemat_t *A, + igraph_real_t *min, igraph_real_t *max) { + CS_INT i, n; + CS_ENTRY *ptr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = igraph_i_sparsemat_count_elements(A); + if (n == 0) { + *min = IGRAPH_INFINITY; + *max = -IGRAPH_INFINITY; + return IGRAPH_SUCCESS; + } + *min = *max = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr > *max) { + *max = *ptr; + } else if (*ptr < *min) { + *min = *ptr; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_count_nonzero + * \brief Counts nonzero elements of a sparse matrix. + * + * \param A The input matrix, column-compressed. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_int_t igraph_sparsemat_count_nonzero(igraph_sparsemat_t *A) { + CS_INT i, n; + CS_ENTRY *ptr; + igraph_int_t res = 0; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = igraph_i_sparsemat_count_elements(A); + if (n == 0) { + return 0; + } + for (i = 0; i < n; i++, ptr++) { + if (*ptr) { + res++; + } + } + return res; +} + +/** + * \function igraph_sparsemat_count_nonzerotol + * \brief Counts nonzero elements of a sparse matrix, ignoring elements close to zero. + * + * Count the number of matrix entries that are closer to zero than \p tol. + * + * \param A The input matrix, column-compressed. + * \param tol The tolerance for zero comparisons. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_int_t igraph_sparsemat_count_nonzerotol(igraph_sparsemat_t *A, + igraph_real_t tol) { + CS_INT i, n; + CS_ENTRY *ptr; + igraph_int_t res = 0; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = igraph_i_sparsemat_count_elements(A); + if (n == 0) { + return 0; + } + for (i = 0; i < n; i++, ptr++) { + if (*ptr < - tol || *ptr > tol) { + res++; + } + } + return res; +} + +static igraph_error_t igraph_i_sparsemat_rowsums_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pi = A->cs->i; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_null(res); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + VECTOR(*res)[ *pi ] += *px; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_rowsums_cc(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT ne = A->cs->p[A->cs->n]; + CS_ENTRY *px = A->cs->x; + CS_INT *pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_null(res); + + for (; pi < A->cs->i + ne; pi++, px++) { + VECTOR(*res)[ *pi ] += *px; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_rowsums + * \brief Row-wise sums. + * + * \param A The input matrix, in triplet or column-compressed format. + * \param res An initialized vector, the result is stored here. It + * will be resized as needed. + * \return Error code. + * + * Time complexity: O(nz), the number of non-zero elements. + */ + +igraph_error_t igraph_sparsemat_rowsums(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowsums_triplet(A, res); + } else { + return igraph_i_sparsemat_rowsums_cc(A, res); + } +} + +static igraph_error_t igraph_i_sparsemat_rowmins_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pi = A->cs->i; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, IGRAPH_INFINITY); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_rowmins_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT ne; + CS_ENTRY *px; + CS_INT *pi; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ne = A->cs->p[A->cs->n]; + px = A->cs->x; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, IGRAPH_INFINITY); + + for (; pi < A->cs->i + ne; pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_rowmins(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowmins_triplet(A, res); + } else { + return igraph_i_sparsemat_rowmins_cc(A, res); + } +} + + +static igraph_error_t igraph_i_sparsemat_rowmaxs_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pi = A->cs->i; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, -IGRAPH_INFINITY); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + if (*px > VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_rowmaxs_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT ne; + CS_ENTRY *px; + CS_INT *pi; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ne = A->cs->p[A->cs->n]; + px = A->cs->x; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, -IGRAPH_INFINITY); + + for (; pi < A->cs->i + ne; pi++, px++) { + if (*px > VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_rowmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowmaxs_triplet(A, res); + } else { + return igraph_i_sparsemat_rowmaxs_cc(A, res); + } +} + +static igraph_error_t igraph_i_sparsemat_colmins_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pp = A->cs->p; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_fill(res, IGRAPH_INFINITY); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + if (*px < VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_colmins_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT n; + CS_ENTRY *px; + CS_INT *pp; + CS_INT *pi; + double *pr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, IGRAPH_INFINITY); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px < *pr) { + *pr = *px; + } + } + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_colmins(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colmins_triplet(A, res); + } else { + return igraph_i_sparsemat_colmins_cc(A, res); + } +} + +static igraph_error_t igraph_i_sparsemat_colmaxs_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pp = A->cs->p; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_fill(res, -IGRAPH_INFINITY); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + if (*px > VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_colmaxs_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT n; + CS_ENTRY *px; + CS_INT *pp; + CS_INT *pi; + double *pr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, -IGRAPH_INFINITY); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px > *pr) { + *pr = *px; + } + } + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_colmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colmaxs_triplet(A, res); + } else { + return igraph_i_sparsemat_colmaxs_cc(A, res); + } +} + +static igraph_error_t igraph_i_sparsemat_which_min_rows_triplet(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + CS_INT i; + CS_INT *pi = A->cs->i; + CS_INT *pp = A->cs->p; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->m)); + igraph_vector_fill(res, IGRAPH_INFINITY); + igraph_vector_int_null(pos); + + for (i = 0; i < A->cs->nz; i++, pi++, px++, pp++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + VECTOR(*pos)[ *pi ] = *pp; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_which_min_rows_cc(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + CS_INT n; + CS_ENTRY *px; + CS_INT *pp; + CS_INT *pi; + igraph_int_t j; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->m)); + igraph_vector_fill(res, IGRAPH_INFINITY); + igraph_vector_int_null(pos); + + for (j = 0; pp < A->cs->p + n; pp++, j++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + VECTOR(*pos)[ *pi ] = j; + } + } + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_which_min_rows(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_which_min_rows_triplet(A, res, pos); + } else { + return igraph_i_sparsemat_which_min_rows_cc(A, res, pos); + } +} + +static igraph_error_t igraph_i_sparsemat_which_min_cols_triplet(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + + CS_INT i; + CS_INT *pi = A->cs->i; + CS_INT *pp = A->cs->p; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->n)); + igraph_vector_fill(res, IGRAPH_INFINITY); + igraph_vector_int_null(pos); + + for (i = 0; i < A->cs->nz; i++, pi++, pp++, px++) { + if (*px < VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + VECTOR(*pos)[ *pp ] = *pi; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_which_min_cols_cc(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + CS_INT n, j, p; + CS_ENTRY *px; + double *pr; + igraph_int_t *ppos; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, IGRAPH_INFINITY); + pr = VECTOR(*res); + IGRAPH_CHECK(igraph_vector_int_resize(pos, n)); + igraph_vector_int_null(pos); + ppos = VECTOR(*pos); + + for (j = 0; j < A->cs->n; j++, pr++, ppos++) { + for (p = A->cs->p[j]; p < A->cs->p[j + 1]; p++, px++) { + if (*px < *pr) { + *pr = *px; + *ppos = A->cs->i[p]; + } + } + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_which_min_cols(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_which_min_cols_triplet(A, res, pos); + } else { + return igraph_i_sparsemat_which_min_cols_cc(A, res, pos); + } +} + +static igraph_error_t igraph_i_sparsemat_colsums_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT i; + CS_INT *pp = A->cs->p; + CS_ENTRY *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_null(res); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + VECTOR(*res)[ *pp ] += *px; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_colsums_cc(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + CS_INT n = A->cs->n; + CS_ENTRY *px = A->cs->x; + CS_INT *pp = A->cs->p; + CS_INT *pi = A->cs->i; + double *pr; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_null(res); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + *pr += *px; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_colsums + * \brief Column-wise sums. + * + * \param A The input matrix, in triplet or column-compressed format. + * \param res An initialized vector, the result is stored here. It + * will be resized as needed. + * \return Error code. + * + * Time complexity: O(nz) for triplet matrices, O(nz+n) for + * column-compressed ones, nz is the number of non-zero elements, n is + * the number of columns. + */ + +igraph_error_t igraph_sparsemat_colsums(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colsums_triplet(A, res); + } else { + return igraph_i_sparsemat_colsums_cc(A, res); + } +} + +/** + * \function igraph_sparsemat_scale + * \brief Scales a sparse matrix. + * + * Multiplies all elements of a sparse matrix, by the given factor. + * + * \param A The input matrix. + * \param by The scaling factor. + * \return Error code. + * + * Time complexity: O(nz), the number of non-zero elements in the + * matrix. + */ + +igraph_error_t igraph_sparsemat_scale(igraph_sparsemat_t *A, igraph_real_t by) { + + CS_ENTRY *px = A->cs->x; + CS_ENTRY *stop = px + igraph_i_sparsemat_count_elements(A); + + for (; px < stop; px++) { + *px *= by; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_add_rows + * \brief Adds rows to a sparse matrix. + * + * The current matrix elements are retained and all elements in the + * new rows are zero. + * \param A The input matrix, in triplet or column-compressed format. + * \param n The number of rows to add. + * \return Error code. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_sparsemat_add_rows(igraph_sparsemat_t *A, igraph_int_t n) { + A->cs->m += n; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_add_cols + * \brief Adds columns to a sparse matrix. + * + * The current matrix elements are retained, and all elements in the + * new columns are zero. + * \param A The input matrix, in triplet or column-compressed format. + * \param n The number of columns to add. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_add_cols(igraph_sparsemat_t *A, igraph_int_t n) { + if (igraph_sparsemat_is_triplet(A)) { + A->cs->n += n; + } else { + CS_INT realloc_ok = 0, i; + CS_INT *newp = cs_realloc(A->cs->p, (A->cs->n + n + 1), sizeof(CS_INT), &realloc_ok); + if (!realloc_ok) { + IGRAPH_ERROR("Cannot add columns to sparse matrix", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + if (newp != A->cs->p) { + A->cs->p = newp; + } + for (i = A->cs->n + 1; i < A->cs->n + n + 1; i++) { + A->cs->p[i] = A->cs->p[i - 1]; + } + A->cs->n += n; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_resize + * \brief Resizes a sparse matrix and clears all the elements. + * + * This function resizes a sparse matrix. The resized sparse matrix + * will become empty, even if it contained nonzero entries. + * + * \param A The initialized sparse matrix to resize. + * \param nrow The new number of rows. + * \param ncol The new number of columns. + * \param nzmax The new maximum number of elements. + * \return Error code. + * + * Time complexity: O(nzmax), the maximum number of non-zero elements. + */ + +igraph_error_t igraph_sparsemat_resize(igraph_sparsemat_t *A, igraph_int_t nrow, + igraph_int_t ncol, igraph_int_t nzmax) { + + if (igraph_sparsemat_is_cc(A)) { + igraph_sparsemat_t tmp; + IGRAPH_CHECK(igraph_sparsemat_init(&tmp, nrow, ncol, nzmax)); + igraph_sparsemat_destroy(A); + *A = tmp; + } else { + IGRAPH_CHECK(igraph_sparsemat_realloc(A, nzmax)); + A->cs->m = nrow; + A->cs->n = ncol; + A->cs->nz = 0; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_nonzero_storage + * \brief Returns number of stored entries of a sparse matrix. + * + * This function will return the number of stored entries of a sparse + * matrix. These entries can be zero, and multiple entries can be + * at the same position. Use \ref igraph_sparsemat_dupl() to sum + * duplicate entries, and \ref igraph_sparsemat_dropzeros() to remove + * zeros. + * + * \param A A sparse matrix in either triplet or compressed form. + * \return Number of stored entries. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_nonzero_storage(const igraph_sparsemat_t *A) { + return igraph_i_sparsemat_count_elements(A); +} + + +/** + * \function igraph_sparsemat_getelements + * \brief Returns all elements of a sparse matrix. + * + * This function will return the elements of a sparse matrix in three vectors. + * Two vectors will indicate where the elements are located, and one will + * specify the elements themselves. + * + * \param A A sparse matrix in either triplet or compressed form. + * \param i An initialized integer vector. This will store the rows of the + * returned elements. + * \param j An initialized integer vector. For a triplet matrix this will + * store the columns of the returned elements. For a compressed + * matrix, if the column index is \c k, then j[k] + * is the index in \p x of the start of the \c k-th column, and + * the last element of \c j is the total number of elements. + * The total number of elements in the \c k-th column is + * therefore j[k+1] - j[k]. For example, if there + * is one element in the first column, and five in the second, + * \c j will be set to {0, 1, 6}. + * \param x An initialized vector. The elements will be placed here. + * \return Error code. + * + * Time complexity: O(n), the number of stored elements in the sparse matrix. + */ + +igraph_error_t igraph_sparsemat_getelements(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x) { + CS_INT nz = A->cs->nz; + if (nz < 0) { + nz = A->cs->p[A->cs->n]; + IGRAPH_CHECK(igraph_vector_int_resize(i, nz)); + IGRAPH_CHECK(igraph_vector_int_resize(j, A->cs->n + 1)); + IGRAPH_CHECK(igraph_vector_resize(x, nz)); + memcpy(VECTOR(*i), A->cs->i, (size_t) nz * sizeof(CS_INT)); + memcpy(VECTOR(*j), A->cs->p, (size_t) (A->cs->n + 1) * sizeof(CS_INT)); + memcpy(VECTOR(*x), A->cs->x, (size_t) nz * sizeof(CS_ENTRY)); + } else { + IGRAPH_CHECK(igraph_vector_int_resize(i, nz)); + IGRAPH_CHECK(igraph_vector_int_resize(j, nz)); + IGRAPH_CHECK(igraph_vector_resize(x, nz)); + memcpy(VECTOR(*i), A->cs->i, (size_t) nz * sizeof(CS_INT)); + memcpy(VECTOR(*j), A->cs->p, (size_t) nz * sizeof(CS_INT)); + memcpy(VECTOR(*x), A->cs->x, (size_t) nz * sizeof(CS_ENTRY)); + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_scale_rows(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + CS_INT *i = A->cs->i; + CS_ENTRY *x = A->cs->x; + CS_INT no_of_edges = igraph_i_sparsemat_count_elements(A); + CS_INT e; + + for (e = 0; e < no_of_edges; e++, x++, i++) { + igraph_real_t f = VECTOR(*fact)[*i]; + (*x) *= f; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_scale_cols_cc(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + CS_INT *i = A->cs->i; + CS_ENTRY *x = A->cs->x; + CS_INT no_of_edges = A->cs->p[A->cs->n]; + CS_INT e; + CS_INT c = 0; /* actual column */ + + for (e = 0; e < no_of_edges; e++, x++, i++) { + igraph_real_t f; + while (c < A->cs->n && A->cs->p[c + 1] == e) { + c++; + } + f = VECTOR(*fact)[c]; + (*x) *= f; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_sparsemat_scale_cols_triplet(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + CS_INT *j = A->cs->p; + CS_ENTRY *x = A->cs->x; + CS_INT no_of_edges = A->cs->nz; + CS_INT e; + + for (e = 0; e < no_of_edges; e++, x++, j++) { + igraph_real_t f = VECTOR(*fact)[*j]; + (*x) *= f; + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_scale_cols(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + if (igraph_sparsemat_is_cc(A)) { + return igraph_i_sparsemat_scale_cols_cc(A, fact); + } else { + return igraph_i_sparsemat_scale_cols_triplet(A, fact); + } +} + +igraph_error_t igraph_sparsemat_multiply_by_dense(const igraph_sparsemat_t *A, + const igraph_matrix_t *B, + igraph_matrix_t *res) { + + igraph_int_t m = igraph_sparsemat_nrow(A); + igraph_int_t n = igraph_sparsemat_ncol(A); + igraph_int_t p = igraph_matrix_ncol(B); + igraph_int_t i; + + if (igraph_matrix_nrow(B) != n) { + IGRAPH_ERROR("Invalid dimensions in sparse-dense matrix product", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, m, p)); + igraph_matrix_null(res); + + for (i = 0; i < p; i++) { + if (!(cs_gaxpy(A->cs, &MATRIX(*B, 0, i), &MATRIX(*res, 0, i)))) { + IGRAPH_ERROR("Cannot perform sparse-dense matrix multiplication", + IGRAPH_FAILURE); + } + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_sparsemat_dense_multiply(const igraph_matrix_t *A, + const igraph_sparsemat_t *B, + igraph_matrix_t *res) { + igraph_int_t m = igraph_matrix_nrow(A); + igraph_int_t n = igraph_matrix_ncol(A); + igraph_int_t p = igraph_sparsemat_ncol(B); + igraph_int_t r, c; + CS_INT *Bp = B->cs->p; + + if (igraph_sparsemat_nrow(B) != n) { + IGRAPH_ERROR("Invalid dimensions in dense-sparse matrix product", + IGRAPH_EINVAL); + } + + if (!igraph_sparsemat_is_cc(B)) { + IGRAPH_ERROR("Dense-sparse product is only implemented for " + "column-compressed sparse matrices", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, m, p)); + igraph_matrix_null(res); + + for (c = 0; c < p; c++) { + for (r = 0; r < m; r++) { + igraph_int_t idx = *Bp; + while (idx < * (Bp + 1)) { + MATRIX(*res, r, c) += MATRIX(*A, r, B->cs->i[idx]) * B->cs->x[idx]; + idx++; + } + } + Bp++; + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_sparsemat_sort + * \brief Sorts all elements of a sparse matrix by row and column indices. + * + * This function will sort the elements of a sparse matrix such that iterating + * over the entries will return them sorted by column indices; elements in the + * same column are then sorted by row indices. + * + * \param A A sparse matrix in either triplet or compressed form. + * \param sorted An uninitialized sparse matrix; the result will be returned + * here. The result will be in triplet form if the input was in triplet + * form, otherwise it will be in compressed form. Note that sorting is + * more efficient when the matrix is already in compressed form. + * \return Error code. + * + * Time complexity: TODO + */ + +igraph_error_t igraph_sparsemat_sort(const igraph_sparsemat_t *A, + igraph_sparsemat_t *sorted) { + igraph_sparsemat_t tmp; + igraph_sparsemat_t tmp2; + + if (igraph_sparsemat_is_cc(A)) { + /* for column-compressed matrices, we will transpose the matrix twice, + * which will sort the indices as a side effect */ + IGRAPH_CHECK(igraph_sparsemat_transpose(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tmp, sorted)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_sparsemat_iterator_t it; + + /* for triplet matrices, we convert it to compressed column representation, + * sort it, then we convert back */ + IGRAPH_CHECK(igraph_sparsemat_compress(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_sort(&tmp, &tmp2)); + + igraph_sparsemat_destroy(&tmp); + tmp = tmp2; /* tmp is still protected in the FINALLY stack */ + + IGRAPH_CHECK(igraph_sparsemat_init( + sorted, + igraph_sparsemat_nrow(&tmp), + igraph_sparsemat_ncol(&tmp), + igraph_i_sparsemat_count_elements(&tmp) + )); + IGRAPH_FINALLY(igraph_sparsemat_destroy, sorted); + + IGRAPH_CHECK(igraph_sparsemat_iterator_init(&it, &tmp)); + while (!igraph_sparsemat_iterator_end(&it)) { + IGRAPH_CHECK(igraph_sparsemat_entry( + sorted, + igraph_sparsemat_iterator_row(&it), + igraph_sparsemat_iterator_col(&it), + igraph_sparsemat_iterator_get(&it) + )); + igraph_sparsemat_iterator_next(&it); + } + + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); /* tmp + sorted */ + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_getelements_sorted + * \brief Returns all elements of a sparse matrix, sorted by row and column indices. + * + * This function will sort a sparse matrix and return the elements in three + * vectors. Two vectors will indicate where the elements are located, + * and one will specify the elements themselves. + * + * + * Sorting is done based on the \em indices of the elements, not their + * numeric values. The returned entries will be sorted by column indices; + * entries in the same column are then sorted by row indices. + * + * \param A A sparse matrix in either triplet or compressed form. + * \param i An initialized integer vector. This will store the rows of the + * returned elements. + * \param j An initialized integer vector. For a triplet matrix this will + * store the columns of the returned elements. For a compressed + * matrix, if the column index is \c k, then j[k] + * is the index in \p x of the start of the \c k-th column, and + * the last element of \c j is the total number of elements. + * The total number of elements in the \c k-th column is + * therefore j[k+1] - j[k]. For example, if there + * is one element in the first column, and five in the second, + * \c j will be set to {0, 1, 6}. + * \param x An initialized vector. The elements will be placed here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_sparsemat_getelements_sorted(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x) { + igraph_sparsemat_t tmp; + IGRAPH_CHECK(igraph_sparsemat_sort(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_getelements(&tmp, i, j, x)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + /* TODO: in triplets format, we could in theory sort the entries without + * going through an extra sorting step (which temporarily converts the + * matrix into compressed format). This is not implemented yet. */ + + return IGRAPH_SUCCESS; +} + +igraph_int_t igraph_sparsemat_nzmax(const igraph_sparsemat_t *A) { + return A->cs->nzmax; +} + +igraph_error_t igraph_sparsemat_neg(igraph_sparsemat_t *A) { + CS_INT i; + CS_INT nz = igraph_i_sparsemat_count_elements(A); + CS_ENTRY *px = A->cs->x; + + for (i = 0; i < nz; i++, px++) { + *px = - (*px); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_normalize_cols + * \brief Normalizes the column sums of a sparse matrix to a given value. + * + * \param sparsemat The sparse matrix to normalize + * \param allow_zeros If false, zero-sum columns will be rejected with an error. + * \return \c IGRAPH_SUCCESS if everything was successful, + * \c IGRAPH_EINVAL if there is at least one column with zero sum and it + * is disallowed, + * \c IGRAPH_ENOMEM for out-of-memory conditions + */ + +igraph_error_t igraph_sparsemat_normalize_cols( + igraph_sparsemat_t *sparsemat, igraph_bool_t allow_zeros +) { + igraph_vector_t sum; + const igraph_int_t no_of_nodes = igraph_sparsemat_nrow(sparsemat); + + IGRAPH_VECTOR_INIT_FINALLY(&sum, no_of_nodes); + + IGRAPH_CHECK(igraph_sparsemat_colsums(sparsemat, &sum)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(sum)[i] != 0.0) { + VECTOR(sum)[i] = 1.0 / VECTOR(sum)[i]; + } else if (!allow_zeros) { + IGRAPH_ERROR("Columns with zero sum are not allowed.", IGRAPH_EINVAL); + } + } + IGRAPH_CHECK(igraph_sparsemat_scale_cols(sparsemat, &sum)); + + igraph_vector_destroy(&sum); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_normalize_rows + * \brief Normalizes the row sums of a sparse matrix to a given value. + * + * \param sparsemat The sparse matrix to normalize + * \param allow_zeros If false, zero-sum rows will be rejected with an error. + * \return \c IGRAPH_SUCCESS if everything was successful, + * \c IGRAPH_EINVAL if there is at least one row with zero sum and it + * is disallowed, + * \c IGRAPH_ENOMEM for out-of-memory conditions + */ + +igraph_error_t igraph_sparsemat_normalize_rows( + igraph_sparsemat_t *sparsemat, igraph_bool_t allow_zeros +) { + igraph_vector_t sum; + const igraph_int_t no_of_nodes = igraph_sparsemat_nrow(sparsemat); + + IGRAPH_VECTOR_INIT_FINALLY(&sum, no_of_nodes); + + IGRAPH_CHECK(igraph_sparsemat_rowsums(sparsemat, &sum)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(sum)[i] != 0.0) { + VECTOR(sum)[i] = 1.0 / VECTOR(sum)[i]; + } else if (!allow_zeros) { + IGRAPH_ERROR("Rows with zero sum are not allowed.", IGRAPH_EINVAL); + } + } + IGRAPH_CHECK(igraph_sparsemat_scale_rows(sparsemat, &sum)); + + igraph_vector_destroy(&sum); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_iterator_init + * \brief Initialize a sparse matrix iterator. + * + * \param it A pointer to an uninitialized sparse matrix iterator. + * \param sparsemat Pointer to the sparse matrix. + * \return Error code. This will always return \c IGRAPH_SUCCESS + * + * Time complexity: O(n), the number of columns of the sparse matrix. + */ + +igraph_error_t igraph_sparsemat_iterator_init( + igraph_sparsemat_iterator_t *it, const igraph_sparsemat_t *sparsemat +) { + + it->mat = sparsemat; + igraph_sparsemat_iterator_reset(it); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_iterator_reset + * \brief Reset a sparse matrix iterator to the first element. + * + * \param it A pointer to the sparse matrix iterator. + * \return Error code. This will always return \c IGRAPH_SUCCESS + * + * Time complexity: O(n), the number of columns of the sparse matrix. + */ + +igraph_error_t igraph_sparsemat_iterator_reset(igraph_sparsemat_iterator_t *it) { + it->pos = 0; + it->col = 0; + if (!igraph_sparsemat_is_triplet(it->mat)) { + while (it->col < it->mat->cs->n && + it->mat->cs->p[it->col + 1] == it->pos) { + it->col ++; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sparsemat_iterator_end + * \brief Query if the iterator is past the last element. + * + * \param it A pointer to the sparse matrix iterator. + * \return true if the iterator is past the last element, false if it + * points to an element in a sparse matrix. + * + * Time complexity: O(1). + */ + +igraph_bool_t +igraph_sparsemat_iterator_end(const igraph_sparsemat_iterator_t *it) { + CS_INT nz = it->mat->cs->nz == -1 ? it->mat->cs->p[it->mat->cs->n] : + it->mat->cs->nz; + return it->pos >= nz; +} + +/** + * \function igraph_sparsemat_iterator_row + * \brief Return the row of the iterator. + * + * \param it A pointer to the sparse matrix iterator. + * \return The row of the element at the current iterator position. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_iterator_row(const igraph_sparsemat_iterator_t *it) { + return it->mat->cs->i[it->pos]; +} + +/** + * \function igraph_sparsemat_iterator_col + * \brief Return the column of the iterator. + * + * \param it A pointer to the sparse matrix iterator. + * \return The column of the element at the current iterator position. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_iterator_col(const igraph_sparsemat_iterator_t *it) { + if (igraph_sparsemat_is_triplet(it->mat)) { + return it->mat->cs->p[it->pos]; + } else { + return it->col; + } +} + +/** + * \function igraph_sparsemat_iterator_get + * \brief Return the element at the current iterator position. + * + * \param it A pointer to the sparse matrix iterator. + * \return The value of the element at the current iterator position. + * + * Time complexity: O(1). + */ + +igraph_real_t +igraph_sparsemat_iterator_get(const igraph_sparsemat_iterator_t *it) { + return it->mat->cs->x[it->pos]; +} + +/** + * \function igraph_sparsemat_iterator_next + * \brief Let a sparse matrix iterator go to the next element. + * + * \param it A pointer to the sparse matrix iterator. + * \return The position of the iterator in the element vector. + * + * Time complexity: O(n), the number of columns of the sparse matrix. + */ + +igraph_int_t igraph_sparsemat_iterator_next(igraph_sparsemat_iterator_t *it) { + it->pos += 1; + while (it->col < it->mat->cs->n && + it->mat->cs->p[it->col + 1] == it->pos) { + it->col++; + } + return it->pos; +} + +/** + * \function igraph_sparsemat_iterator_idx + * \brief Returns the element vector index of a sparse matrix iterator. + * + * \param it A pointer to the sparse matrix iterator. + * \return The position of the iterator in the element vector. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_sparsemat_iterator_idx(const igraph_sparsemat_iterator_t *it) { + return it->pos; +} diff --git a/src/core/stack.c b/src/core/stack.c new file mode 100644 index 0000000..0f567d4 --- /dev/null +++ b/src/core/stack.c @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_stack.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL diff --git a/src/core/stack.pmt b/src/core/stack.pmt new file mode 100644 index 0000000..cfc7bed --- /dev/null +++ b/src/core/stack.pmt @@ -0,0 +1,305 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" + +#include /* memcpy & co. */ +#include + +/** + * \ingroup stack + * \function igraph_stack_init + * \brief Initializes a stack. + * + * The initialized stack is always empty. + * + * \param s Pointer to an uninitialized stack. + * \param capacity The number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p size). + */ + +igraph_error_t FUNCTION(igraph_stack, init)(TYPE(igraph_stack)* s, igraph_int_t capacity) { + igraph_int_t alloc_size; + IGRAPH_ASSERT(capacity >= 0); + alloc_size = capacity > 0 ? capacity : 1; + IGRAPH_ASSERT(s != NULL); + s->stor_begin = IGRAPH_CALLOC(alloc_size, BASE); + if (s->stor_begin == NULL) { + IGRAPH_ERROR("Cannot initialize stack.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + s->stor_end = s->stor_begin + alloc_size; + s->end = s->stor_begin; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup stack + * \function igraph_stack_destroy + * \brief Destroys a stack object. + * + * Deallocate the memory used for a stack. + * It is possible to reinitialize a destroyed stack again by + * \ref igraph_stack_init(). + * \param s The stack to destroy. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_stack, destroy) (TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + if (s->stor_begin != NULL) { + IGRAPH_FREE(s->stor_begin); + s->stor_begin = NULL; + } +} + +/** + * \ingroup stack + * \function igraph_stack_capacity + * \brief Returns the allocated capacity of the stack. + * + * Note that this might be different from the size of the stack (as + * queried by \ref igraph_stack_size()), and specifies how many elements + * the stack can hold, without reallocation. + * + * \param v Pointer to the (previously initialized) stack object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_stack_size(). + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_stack, capacity)(const TYPE(igraph_stack) *s) { + return s->stor_end - s->stor_begin; +} + +/** + * \ingroup stack + * \function igraph_stack_reserve + * \brief Reserve memory. + * + * Reserve memory for future use. The actual size of the stack is + * unchanged. + * \param s The stack object. + * \param size The number of elements to reserve memory for. If it is + * not bigger than the current size then nothing happens. + * \return Error code. + * + * Time complexity: should be around O(n), the new allocated size of + * the stack. + */ + +igraph_error_t FUNCTION(igraph_stack, reserve)(TYPE(igraph_stack)* s, igraph_int_t capacity) { + igraph_int_t current_capacity; + BASE *tmp; + + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + current_capacity = FUNCTION(igraph_stack, capacity)(s); + + if (capacity <= current_capacity) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(s->stor_begin, capacity, BASE); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for stack."); + + s->end = tmp + (s->end - s->stor_begin); + s->stor_begin = tmp; + s->stor_end = s->stor_begin + capacity; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup stack + * \function igraph_stack_empty + * \brief Decides whether a stack object is empty. + * + * \param s The stack object. + * \return Boolean, \c true if the stack is empty, \c false + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_stack, empty)(TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + return s->stor_begin == s->end; +} + +/** + * \ingroup stack + * \function igraph_stack_size + * \brief Returns the number of elements in a stack. + * + * \param s The stack object. + * \return The number of elements in the stack. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_stack, size)(const TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + return s->end - s->stor_begin; +} + +/** + * \ingroup stack + * \function igraph_stack_clear + * \brief Removes all elements from a stack. + * + * \param s The stack object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_stack, clear)(TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + s->end = s->stor_begin; +} + +/** + * \ingroup stack + * \function igraph_stack_push + * \brief Places an element on the top of a stack. + * + * The capacity of the stack is increased, if needed. + * \param s The stack object. + * \param elem The element to push. + * \return Error code. + * + * Time complexity: O(1) is no reallocation is needed, O(n) + * otherwise, but it is ensured that n push operations are performed + * in O(n) time. + */ + +igraph_error_t FUNCTION(igraph_stack, push)(TYPE(igraph_stack)* s, BASE elem) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + + if (s->stor_end == s->end) { + /* full, allocate more storage */ + igraph_int_t old_size = FUNCTION(igraph_stack, size)(s); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to stack, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(igraph_stack, reserve)(s, new_size)); + } + + *(s->end) = elem; + s->end += 1; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup stack + * \function igraph_stack_pop + * \brief Removes and returns an element from the top of a stack. + * + * The stack must contain at least one element, call \ref + * igraph_stack_empty() to make sure of this. + * \param s The stack object. + * \return The removed top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_stack, pop)(TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + IGRAPH_ASSERT(s->end != NULL); + IGRAPH_ASSERT(s->end != s->stor_begin); + + (s->end)--; + + return *(s->end); +} + +/** + * \ingroup stack + * \function igraph_stack_top + * \brief Query top element. + * + * Returns the top element of the stack, without removing it. + * The stack must be non-empty. + * \param s The stack. + * \return The top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_stack, top)(const TYPE(igraph_stack)* s) { + IGRAPH_ASSERT(s != NULL); + IGRAPH_ASSERT(s->stor_begin != NULL); + IGRAPH_ASSERT(s->end != NULL); + IGRAPH_ASSERT(s->end != s->stor_begin); + + return *(s->end - 1); +} + +#if defined(OUT_FORMAT) || defined(FPRINTFUNC) + +#ifndef USING_R +igraph_error_t FUNCTION(igraph_stack, print)(const TYPE(igraph_stack) *s) { + return FUNCTION(igraph_stack, fprint)(s, stdout); +} +#endif /* USING_R */ + +igraph_error_t FUNCTION(igraph_stack, fprint)(const TYPE(igraph_stack) *s, FILE *file) { + igraph_int_t i, n = FUNCTION(igraph_stack, size)(s); + if (n != 0) { +#ifdef FPRINTFUNC + FPRINTFUNC(file, s->stor_begin[0]); +#else + fprintf(file, OUT_FORMAT, s->stor_begin[0]); +#endif + } + for (i = 1; i < n; i++) { +#ifdef FPRINTFUNC + fputc(' ', file); fprintf(file, OUT_FORMAT, s->stor_begin[i]); +#else + fprintf(file, " " OUT_FORMAT, s->stor_begin[i]); +#endif + } + fprintf(file, "\n"); + return IGRAPH_SUCCESS; +} + +#endif /* defined(OUT_FORMAT) || defined(FPRINTFUNC) */ diff --git a/src/core/statusbar.c b/src/core/statusbar.c new file mode 100644 index 0000000..b56e113 --- /dev/null +++ b/src/core/statusbar.c @@ -0,0 +1,130 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_statusbar.h" +#include "igraph_error.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL */ + +#include +#include + +static IGRAPH_THREAD_LOCAL igraph_status_handler_t *igraph_i_status_handler = 0; + +/** + * \function igraph_status + * \brief Reports status from an igraph function. + * + * It calls the installed status handler function, if there is + * one. Otherwise it does nothing. Note that the standard way to + * report the status from an igraph function is the + * \ref IGRAPH_STATUS or \ref IGRAPH_STATUSF macro, as these + * take care of cleaning up allocated memory from the calling + * function if the status handler returns with an error code. + * + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return Error code from the status handler function, or \c IGRAPH_SUCCESS + * if no status handler function was registered. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_status(const char *message, void *data) { + if (igraph_i_status_handler) { + return igraph_i_status_handler(message, data); + } else { + return IGRAPH_SUCCESS; + } +} + +/** + * \function igraph_statusf + * \brief Report status, more flexible printf-like version. + * + * This is the more flexible version of \ref igraph_status(), + * that has a syntax similar to the \c printf standard C library function. + * It substitutes the values of the additional arguments into the + * \p message template string and calls \ref igraph_status(). + * + * \param message Status message template string, the syntax is the same + * as for the \c printf function. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \param ... The additional arguments to fill the template given in the + * \p message argument. + * \return Error code from the status handler function, or \c IGRAPH_SUCCESS + * if no status handler function was registered. + */ + +igraph_error_t igraph_statusf(const char *message, void *data, ...) { + char buffer[300]; + va_list ap; + va_start(ap, data); + vsnprintf(buffer, sizeof(buffer) - 1, message, ap); + va_end(ap); + return igraph_status(buffer, data); +} + +#ifndef USING_R + +/** + * \function igraph_status_handler_stderr + * A simple predefined status handler function. + * + * A simple status handler function that writes the status + * message to the standard error. + * + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return Error code. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_status_handler_stderr(const char *message, void *data) { + IGRAPH_UNUSED(data); + fputs(message, stderr); + return IGRAPH_SUCCESS; +} +#endif + +/** + * \function igraph_set_status_handler + * Install of uninstall a status handler function. + * + * To uninstall the currently installed status handler, call + * this function with a null pointer. + * \param new_handler The status handler function to install. + * \return The previously installed status handler function. + * + * Time complexity: O(1). + */ + +igraph_status_handler_t * +igraph_set_status_handler(igraph_status_handler_t new_handler) { + igraph_status_handler_t *previous_handler = igraph_i_status_handler; + igraph_i_status_handler = new_handler; + return previous_handler; +} diff --git a/src/core/strvector.c b/src/core/strvector.c new file mode 100644 index 0000000..84e6a6d --- /dev/null +++ b/src/core/strvector.c @@ -0,0 +1,738 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_strvector.h" + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" + +#include "internal/hacks.h" /* strdup */ +#include "math/safe_intop.h" + +#include /* memcpy & co. */ +#include + +/** + * \section igraph_strvector_t + * + * The igraph_strvector_t type is a vector of null-terminated + * strings. It is used internally for storing graph attribute names as well as + * string attributes in the C attribute handler. + * + * + * + * This container automatically manages the memory of its elements. + * The strings within an igraph_strvector_t should be considered + * constant, and not modified directly. Functions that add new elements + * always make copies of the string passed to them. + * + * + * + * \example examples/simple/igraph_strvector.c + * + */ + +/** + * \ingroup strvector + * \function igraph_strvector_init + * \brief Initializes a string vector. + * + * Reserves memory for the string vector, a string vector must be + * first initialized before calling other functions on it. + * All elements of the string vector are set to the empty string. + * + * \param sv Pointer to an initialized string vector. + * \param size The (initial) length of the string vector. + * \return Error code. + * + * Time complexity: O(\p len). + */ + +igraph_error_t igraph_strvector_init(igraph_strvector_t *sv, igraph_int_t size) { + + sv->stor_begin = IGRAPH_CALLOC(size, const char *); + IGRAPH_CHECK_OOM(sv->stor_begin, "Cannot initialize string vector."); + + sv->stor_end = sv->stor_begin + size; + sv->end = sv->stor_end; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_destroy + * \brief Frees the memory allocated for the string vector. + * + * Destroy a string vector. It may be reinitialized with \ref + * igraph_strvector_init() later. + * \param sv The string vector. + * + * Time complexity: O(l), the total length of the strings, maybe less + * depending on the memory manager. + */ + +void igraph_strvector_destroy(igraph_strvector_t *sv) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + for (const char **ptr = sv->stor_begin; ptr < sv->end; ptr++) { + IGRAPH_FREE(*ptr); + } + IGRAPH_FREE(sv->stor_begin); +} + +/** + * \ingroup strvector + * \function igraph_strvector_get + * \brief Retrieves an element of a string vector. + * + * Query an element of a string vector. The returned string must not be modified. + * + * \param sv The input string vector. + * \param idx The index of the element to query. + * + * Time complexity: O(1). + */ + +const char *igraph_strvector_get(const igraph_strvector_t *sv, igraph_int_t idx) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + return sv->stor_begin[idx] ? sv->stor_begin[idx] : ""; +} + +/** + * \ingroup strvector + * \function igraph_strvector_set + * \brief Sets an element of the string vector from a string. + * + * The provided \p value is copied into the \p idx position in the + * string vector. + * + * \param sv The string vector. + * \param idx The position to set. + * \param value The new value. + * \return Error code. + * + * Time complexity: O(l), the length of the new string. Maybe more, + * depending on the memory management, if reallocation is needed. + */ + +igraph_error_t igraph_strvector_set(igraph_strvector_t *sv, igraph_int_t idx, + const char *value) { + return igraph_strvector_set_len(sv, idx, value, strlen(value)); +} + +/** + * \ingroup strvector + * \function igraph_strvector_set_len + * \brief Sets an element of the string vector given a buffer and its size. + * + * This is almost the same as \ref igraph_strvector_set, but the new + * value is not a zero terminated string, but its length is given. + * + * \param sv The string vector. + * \param idx The position to set. + * \param value The new value. + * \param len The length of the new value. + * \return Error code. + * + * Time complexity: O(l), the length of the new string. Maybe more, + * depending on the memory management, if reallocation is needed. + */ +igraph_error_t igraph_strvector_set_len(igraph_strvector_t *sv, igraph_int_t idx, + const char *value, size_t len) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + + if (sv->stor_begin[idx] == NULL) { + sv->stor_begin[idx] = strndup(value, len); + IGRAPH_CHECK_OOM(sv->stor_begin[idx], "Cannot reserve space for new item in string vector."); + } else { + char *tmp = IGRAPH_REALLOC(sv->stor_begin[idx], len + 1, char); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for new item in string vector."); + + memcpy(tmp, value, len * sizeof(char)); + tmp[len] = '\0'; + sv->stor_begin[idx] = tmp; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_remove_section + * \brief Removes a section from a string vector. + * + * This function removes the range [from, to) from the string vector. + * + * \param sv The string vector. + * \param from The position of the first element to remove. + * \param to The position of the first element \em not to remove. + */ + +void igraph_strvector_remove_section( + igraph_strvector_t *sv, igraph_int_t from, igraph_int_t to) { + igraph_int_t size = igraph_strvector_size(sv); + igraph_int_t i; + + if (from < 0) { + from = 0; + } + + if (to > size) { + to = size; + } + + if (to > from) { + for (i = from; i < to; i++) { + IGRAPH_FREE(sv->stor_begin[i]); + } + + memmove(sv->stor_begin + from, sv->stor_begin + to, + sizeof(char*) * (sv->end - sv->stor_begin - to)); + sv->end -= (to - from); + } +} + +/** + * \ingroup strvector + * \function igraph_strvector_remove + * \brief Removes a single element from a string vector. + * + * The string will be one shorter. + * \param sv The string vector. + * \param elem The index of the element to remove. + * + * Time complexity: O(n), the length of the string. + */ + +void igraph_strvector_remove(igraph_strvector_t *sv, igraph_int_t elem) { + igraph_strvector_remove_section(sv, elem, elem + 1); +} + +/** + * \ingroup strvector + * \function igraph_strvector_init_copy + * \brief Initialization by copying. + * + * Initializes a string vector by copying another string vector. + * + * \param to Pointer to an uninitialized string vector. + * \param from The other string vector, to be copied. + * \return Error code. + * + * Time complexity: O(l), the total length of the strings in \p from. + */ + +igraph_error_t igraph_strvector_init_copy(igraph_strvector_t *to, + const igraph_strvector_t *from) { + igraph_int_t from_size = igraph_strvector_size(from); + + to->stor_begin = IGRAPH_CALLOC(from_size, const char *); + IGRAPH_CHECK_OOM(to->stor_begin, "Cannot copy string vector."); + + for (igraph_int_t i = 0; i < from_size; i++) { + /* If the string in the 'from' vector is empty, we represent it as NULL. + * The NULL value was already set by IGRAPH_CALLOC(). */ + if (from->stor_begin[i] == NULL || from->stor_begin[i][0] == '\0') { + continue; + } + to->stor_begin[i] = strdup(from->stor_begin[i]); + if (to->stor_begin[i] == NULL) { + /* LCOV_EXCL_START */ + for (igraph_int_t j = 0; j < i; j++) { + IGRAPH_FREE(to->stor_begin[j]); + } + IGRAPH_FREE(to->stor_begin); + IGRAPH_ERROR("Cannot copy string vector.", IGRAPH_ENOMEM); + /* LCOV_EXCL_STOP */ + } + } + + to->stor_end = to->stor_begin + from_size; + to->end = to->stor_end; + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_strvector_append + * \brief Concatenates two string vectors. + * + * Appends the contents of the \p from vector to the \p to vector. + * If the \p from vector is no longer needed after this operation, + * use \ref igraph_strvector_merge() for better performance. + * + * \param to The first string vector, the result is stored here. + * \param from The second string vector, it is kept unchanged. + * \return Error code. + * + * \sa \ref igraph_strvector_merge() + * + * Time complexity: O(n+l2), n is the number of strings in the new + * string vector, l2 is the total length of strings in the \p from + * string vector. + */ + +igraph_error_t igraph_strvector_append(igraph_strvector_t *to, + const igraph_strvector_t *from) { + const igraph_int_t to_size = igraph_strvector_size(to); + const igraph_int_t from_size = igraph_strvector_size(from); + const igraph_int_t to_capacity = igraph_strvector_capacity(to); + igraph_int_t new_to_size; + igraph_bool_t error = false; + const char *tmp; + + IGRAPH_SAFE_ADD(to_size, from_size, &new_to_size); + + if (to_capacity < new_to_size) { + igraph_int_t new_to_capacity = to_capacity < IGRAPH_INTEGER_MAX/2 ? to_capacity * 2 : IGRAPH_INTEGER_MAX; + if (new_to_capacity < new_to_size) { + new_to_capacity = new_to_size; + } + IGRAPH_CHECK(igraph_strvector_reserve(to, new_to_capacity)); + } + + for (igraph_int_t i = 0; i < from_size; i++) { + if (from->stor_begin[i] == NULL || from->stor_begin[i][0] == '\0') { + /* Represent empty strings as NULL. */ + tmp = NULL; + } else { + tmp = strdup(from->stor_begin[i]); + if (tmp == NULL) { + error = true; + break; + } + } + *(to->end) = tmp; + to->end++; + } + + if (error) { + igraph_strvector_resize(to, to_size); /* always shrinks */ + IGRAPH_ERROR("Cannot append string vector.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_merge + * \brief Moves the contents of a string vector to the end of another. + * + * Transfers the contents of the \p from vector to the end of \p to, clearing + * \p from in the process. If this operation fails, both vectors are left intact. + * This function does not copy or reallocate individual strings, therefore it + * performs better than \ref igraph_strvector_append(). + * + * \param to The target vector. The contents of \p from will be appended to it. + * \param from The source vector. It will be cleared. + * \return Error code. + * + * \sa \ref igraph_strvector_append() + * + * Time complexity: O(l2) if \p to has sufficient capacity, O(2*l1+l2) otherwise, + * where l1 and l2 are the lengths of \p to and \from respectively. + */ +igraph_error_t igraph_strvector_merge(igraph_strvector_t *to, igraph_strvector_t *from) { + const char **p1, **p2, **pe; + igraph_int_t newlen; + + IGRAPH_SAFE_ADD(igraph_strvector_size(to), igraph_strvector_size(from), &newlen); + IGRAPH_CHECK(igraph_strvector_reserve(to, newlen)); + + /* transfer contents of 'from' to 'to */ + for (p1 = to->end, p2 = from->stor_begin, pe = to->stor_begin + newlen; + p1 < pe; ++p1, ++p2) + { + *p1 = *p2; + } + to->end = pe; + + /* clear 'from' */ + from->end = from->stor_begin; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_strvector_clear + * \brief Removes all elements from a string vector. + * + * After this operation the string vector will be empty. + * + * \param sv The string vector. + * + * Time complexity: O(l), the total length of strings, maybe less, + * depending on the memory manager. + */ + +void igraph_strvector_clear(igraph_strvector_t *sv) { + igraph_int_t n = igraph_strvector_size(sv); + + for (igraph_int_t i = 0; i < n; i++) { + IGRAPH_FREE(sv->stor_begin[i]); + } + sv->end = sv->stor_begin; +} + +/** + * \ingroup strvector + * \function igraph_strvector_resize + * \brief Resizes a string vector. + * + * If the new size is bigger then empty strings are added, if it is + * smaller then the unneeded elements are removed. + * + * \param sv The string vector. + * \param newsize The new size. + * \return Error code. + * + * Time complexity: O(n), the number of strings if the vector is made + * bigger, O(l), the total length of the deleted strings if it is made + * smaller, maybe less, depending on memory management. + */ + +igraph_error_t igraph_strvector_resize(igraph_strvector_t *sv, igraph_int_t newsize) { + igraph_int_t toadd = newsize - igraph_strvector_size(sv); + igraph_int_t oldsize = igraph_strvector_size(sv); + + if (newsize < oldsize) { + for (igraph_int_t i = newsize; i < oldsize; i++) { + IGRAPH_FREE(sv->stor_begin[i]); + } + sv->end = sv->stor_begin + newsize; + } else if (newsize > oldsize) { + IGRAPH_CHECK(igraph_strvector_reserve(sv, newsize)); + memset(sv->stor_begin + oldsize, 0, toadd * sizeof(const char *)); + sv->end = sv->stor_begin + newsize; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_capacity + * \brief Returns the capacity of a string vector. + * + * \param sv The string vector. + * \return The capacity of the string vector. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_strvector_capacity(const igraph_strvector_t *sv) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + return sv->stor_end - sv->stor_begin; +} + +/** + * \ingroup strvector + * \function igraph_strvector_reserve + * \brief Reserves memory for a string vector. + * + * + * \a igraph string vectors are flexible, they can grow and + * shrink. Growing however occasionally needs the data in the vector to be copied. + * In order to avoid this, you can call this function to reserve space for + * future growth of the vector. + * + * + * Note that this function does \em not change the size of the + * string vector. Let us see a small example to clarify things: if you + * reserve space for 100 strings and the size of your + * vector was (and still is) 60, then you can surely add additional 40 + * strings to your vector before it will be copied. + * + * \param sv The string vector object. + * \param capacity The new \em allocated size of the string vector. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the new allocated size of the vector. + */ + +igraph_error_t igraph_strvector_reserve(igraph_strvector_t *sv, igraph_int_t capacity) { + igraph_int_t current_capacity = igraph_strvector_capacity(sv); + + if (capacity <= current_capacity) { + return IGRAPH_SUCCESS; + } + + const char **tmp = IGRAPH_REALLOC(sv->stor_begin, capacity, const char *); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for new items in string vector."); + + sv->end = tmp + (sv->end - sv->stor_begin); + sv->stor_begin = tmp; + sv->stor_end = sv->stor_begin + capacity; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_resize_min + * \brief Deallocates the unused memory of a string vector. + * + * This function attempts to deallocate the unused reserved storage + * of a string vector. If it succeeds, \ref igraph_strvector_size() and + * \ref igraph_strvector_capacity() will be the same. The data in the + * string vector is always preserved, even if deallocation is not successful. + * + * \param sv The string vector. + * + * Time complexity: Operating system dependent, at most O(n). + */ + +void igraph_strvector_resize_min(igraph_strvector_t *sv) { + if (sv->stor_end == sv->end) { + return; + } + + const igraph_int_t size = (sv->end - sv->stor_begin); + const char **tmp = IGRAPH_REALLOC(sv->stor_begin, size, const char *); + + if (tmp != NULL) { + sv->stor_begin = tmp; + sv->stor_end = sv->end = sv->stor_begin + size; + } +} + +/** + * \ingroup strvector + * \function igraph_strvector_size + * \brief Returns the size of a string vector. + * + * \param sv The string vector. + * \return The length of the string vector. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_strvector_size(const igraph_strvector_t *sv) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + return sv->end - sv->stor_begin; +} + +/** + * Ensures that the vector has at least one extra slot at the end of its + * allocated storage area. + */ +static igraph_error_t strvector_expand_if_full(igraph_strvector_t *sv) { + IGRAPH_ASSERT(sv != NULL); + IGRAPH_ASSERT(sv->stor_begin != NULL); + + if (sv->stor_end == sv->end) { + igraph_int_t old_size = igraph_strvector_size(sv); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot add new item to string vector, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_strvector_reserve(sv, new_size)); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_push_back + * \brief Adds an element to the back of a string vector. + * + * \param sv The string vector. + * \param value The string to add; it will be copied. + * \return Error code. + * + * Time complexity: O(n+l), n is the total number of strings, l is the + * length of the new string. + */ + +igraph_error_t igraph_strvector_push_back(igraph_strvector_t *sv, const char *value) { + IGRAPH_CHECK(strvector_expand_if_full(sv)); + const char *tmp = strdup(value); + IGRAPH_CHECK_OOM(tmp, "Cannot push new string to string vector."); + *sv->end = tmp; + sv->end++; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_push_back_len + * \brief Adds a string of the given length to the back of a string vector. + * + * \param sv The string vector. + * \param value The start of the string to add. At most \p len characters will be copied. + * \param len The length of the string. + * \return Error code. + * + * Time complexity: O(n+l), n is the total number of strings, l is the + * length of the new string. + */ + +igraph_error_t igraph_strvector_push_back_len( + igraph_strvector_t *sv, + const char *value, size_t len) { + + IGRAPH_CHECK(strvector_expand_if_full(sv)); + const char *tmp = strndup(value, len); + IGRAPH_CHECK_OOM(tmp, "Insufficient memory to push to string vector."); + *sv->end = tmp; + sv->end++; + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup strvector + * \function igraph_strvector_print + * \brief Prints a string vector to a file. + * + * \param sv The string vector. + * \param file The file to write to. + * \param sep The separator to print between strings. + * \return Error code. + */ +igraph_error_t igraph_strvector_fprint(const igraph_strvector_t *sv, FILE *file, + const char *sep) { + + const igraph_int_t n = igraph_strvector_size(sv); + if (n != 0) { + fprintf(file, "%s", igraph_strvector_get(sv, 0)); + } + for (igraph_int_t i = 1; i < n; i++) { + fprintf(file, "%s%s", sep, igraph_strvector_get(sv, i)); + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_print + * \brief Prints a string vector. + * + * \param sv The string vector. + * \param sep The separator to print between strings. + * \return Error code. + */ +#ifndef USING_R +igraph_error_t igraph_strvector_print(const igraph_strvector_t *sv, + const char *sep) { + return igraph_strvector_fprint(sv, stdout, sep); +} +#endif + +/** + * \ingroup strvector + * \function igraph_strvector_index + * \brief Takes elements at given positions from a string vector. + * + * \param sv The string vector. + * \param newv An initialized string vector, it will be resized as needed. + * \param idx An integer vector of indices to take from \p sv. + * \return Error code. + */ +igraph_error_t igraph_strvector_index(const igraph_strvector_t *sv, + igraph_strvector_t *newv, + const igraph_vector_int_t *idx) { + + igraph_int_t newlen = igraph_vector_int_size(idx); + IGRAPH_CHECK(igraph_strvector_resize(newv, newlen)); + + for (igraph_int_t i = 0; i < newlen; i++) { + igraph_int_t j = VECTOR(*idx)[i]; + const char *str = igraph_strvector_get(sv, j); + IGRAPH_CHECK(igraph_strvector_set(newv, i, str)); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_update + * \brief Updates a string vector from another one. + * + * After this operation the contents of \p to will be exactly the same + * as that of \p from. The vector \p to will be resized if it was originally + * shorter or longer than \p from. + * + * \param to The string vector to update. + * \param from The string vector to update from. + * \return Error code. + */ +igraph_error_t igraph_strvector_update( + igraph_strvector_t *to, const igraph_strvector_t *from +) { + igraph_strvector_clear(to); + IGRAPH_CHECK(igraph_strvector_append(to, from)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup strvector + * \function igraph_strvector_swap + * \brief Swaps all elements of two string vectors. + * + * \param v1 The first string vector. + * \param v2 The second string vector. + * + * Time complexity: O(1). + */ +void igraph_strvector_swap(igraph_strvector_t *v1, igraph_strvector_t *v2) { + igraph_strvector_t tmp; + + tmp = *v1; + *v1 = *v2; + *v2 = tmp; +} + +/** + * \function igraph_strvector_swap_elements + * \brief Swap two elements in a string vector. + * + * Note that currently no range checking is performed. + * + * \param sv The string vector. + * \param i Index of the first element. + * \param j Index of the second element (may be the same as the first one). + * + * Time complexity: O(1). + */ +void igraph_strvector_swap_elements(igraph_strvector_t *sv, igraph_int_t i, igraph_int_t j) { + const char *tmp = sv->stor_begin[i]; + sv->stor_begin[i] = sv->stor_begin[j]; + sv->stor_begin[j] = tmp; +} diff --git a/src/core/trie.c b/src/core/trie.c new file mode 100644 index 0000000..99cc6ab --- /dev/null +++ b/src/core/trie.c @@ -0,0 +1,434 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" + +#include "core/trie.h" +#include "internal/hacks.h" /* strdup */ + +#include +#include + + +/* + * igraph_trie_t is a data structures that stores an ordered list of strings. + * It allows an efficient lookup of the index of a string. It has the capability + * to also store the list of strings directly for reverse lookup of strings + * by index. + */ + +/* Allocates memory for a trie node. */ +static igraph_error_t igraph_i_trie_init_node(igraph_trie_node_t *t) { + IGRAPH_STRVECTOR_INIT_FINALLY(&t->strs, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->values, 0); + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +static void igraph_i_trie_destroy_node(igraph_trie_node_t *t); + +/** + * \ingroup igraphtrie + * \brief Creates a trie. + * + * \param t An uninitialized trie. + * \param storekeys Specifies whether keys are stored for reverse lookup. + * \return Error code: Errors by \ref igraph_strvector_init(), + * \ref igraph_vector_ptr_init() and \ref igraph_vector_init() might be returned. + */ + +igraph_error_t igraph_trie_init(igraph_trie_t *t, igraph_bool_t storekeys) { + t->maxvalue = -1; + t->storekeys = storekeys; + IGRAPH_CHECK(igraph_i_trie_init_node(&t->node)); + IGRAPH_FINALLY(igraph_i_trie_destroy_node, &t->node); + if (storekeys) { + IGRAPH_CHECK(igraph_strvector_init(&t->keys, 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +static void igraph_i_trie_destroy_node_helper(igraph_trie_node_t *t, igraph_bool_t sfree) { + igraph_strvector_destroy(&t->strs); + igraph_int_t children_size = igraph_vector_ptr_size(&t->children); + for (igraph_int_t i = 0; i < children_size; i++) { + igraph_trie_node_t *child = VECTOR(t->children)[i]; + if (child != NULL) { + igraph_i_trie_destroy_node_helper(child, true); + } + } + igraph_vector_ptr_destroy(&t->children); + igraph_vector_int_destroy(&t->values); + if (sfree) { + IGRAPH_FREE(t); + } +} + +/* Deallocates a trie node. */ +static void igraph_i_trie_destroy_node(igraph_trie_node_t *t) { + igraph_i_trie_destroy_node_helper(t, false); +} + +/** + * \ingroup igraphtrie + * \brief Destroys a trie (frees allocated memory). + * + * \param t The trie. + */ + +void igraph_trie_destroy(igraph_trie_t *t) { + if (t->storekeys) { + igraph_strvector_destroy(&t->keys); + } + igraph_i_trie_destroy_node(&t->node); +} + + +/* Computes the location (index) of the first difference between 'str' and 'key' */ +static size_t igraph_i_strdiff(const char *str, const char *key) { + size_t diff = 0; + while (key[diff] != '\0' && str[diff] != '\0' && str[diff] == key[diff]) { + diff++; + } + return diff; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert in a trie (not to be called directly). + * + * \return Error code, usually \c IGRAPH_ENOMEM. + */ + +static igraph_error_t igraph_i_trie_get_node( + igraph_trie_node_t *t, const char *key, igraph_int_t newvalue, + igraph_int_t *id +) { + assert(key != NULL); + + /* If newvalue is negative, we don't add the node if nonexistent, only check + * for its existence */ + igraph_bool_t add = (newvalue >= 0); + + igraph_int_t strs_size = igraph_strvector_size(&t->strs); + for (igraph_int_t i = 0; i < strs_size; i++) { + size_t diff; + const char *str = igraph_strvector_get(&t->strs, i); + diff = igraph_i_strdiff(str, key); + + if (diff == 0) { + + /* ------------------------------------ */ + /* No match, next */ + + } else if (str[diff] == '\0' && key[diff] == '\0') { + + /* ------------------------------------ */ + /* They are exactly the same */ + if (VECTOR(t->values)[i] != -1) { + *id = VECTOR(t->values)[i]; + return IGRAPH_SUCCESS; + } else { + VECTOR(t->values)[i] = newvalue; + *id = newvalue; + return IGRAPH_SUCCESS; + } + + } else if (str[diff] == '\0') { + + /* ------------------------------------ */ + /* str is prefix of key, follow its link if there is one */ + igraph_trie_node_t *node = VECTOR(t->children)[i]; + if (node != NULL) { + return igraph_i_trie_get_node(node, key + diff, newvalue, id); + } else if (add) { + igraph_trie_node_t *new_node = IGRAPH_CALLOC(1, igraph_trie_node_t); + IGRAPH_CHECK_OOM(new_node, "Cannot add to trie."); + IGRAPH_FINALLY(igraph_free, new_node); + + IGRAPH_STRVECTOR_INIT_FINALLY(&new_node->strs, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&new_node->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_node->values, 1); + IGRAPH_CHECK(igraph_strvector_set(&new_node->strs, 0, key + diff)); + IGRAPH_FINALLY_CLEAN(4); + + VECTOR(new_node->children)[0] = 0; + VECTOR(new_node->values)[0] = newvalue; + + VECTOR(t->children)[i] = new_node; + + *id = newvalue; + return IGRAPH_SUCCESS; + } else { + *id = -1; + return IGRAPH_SUCCESS; + } + + } else if (key[diff] == '\0' && add) { + + /* ------------------------------------ */ + /* key is prefix of str, the node has to be cut */ + char *str2; + + igraph_trie_node_t *node = IGRAPH_CALLOC(1, igraph_trie_node_t); + IGRAPH_CHECK_OOM(node, "Cannot add to trie."); + IGRAPH_FINALLY(igraph_free, node); + + IGRAPH_STRVECTOR_INIT_FINALLY(&node->strs, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&node->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&node->values, 1); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 0, str + diff)); + + VECTOR(node->children)[0] = VECTOR(t->children)[i]; + VECTOR(node->values)[0] = VECTOR(t->values)[i]; + + str2 = strdup(str); + IGRAPH_CHECK_OOM(str2, "Cannot add to trie."); + IGRAPH_FINALLY(igraph_free, str2); + str2[diff] = '\0'; + + IGRAPH_CHECK(igraph_strvector_set(&t->strs, i, str2)); + + IGRAPH_FREE(str2); + IGRAPH_FINALLY_CLEAN(5); + + VECTOR(t->values)[i] = newvalue; + VECTOR(t->children)[i] = node; + + *id = newvalue; + return IGRAPH_SUCCESS; + + } else if (add) { + + /* ------------------------------------ */ + /* the first diff characters match */ + char *str2; + + igraph_trie_node_t *node = IGRAPH_CALLOC(1, igraph_trie_node_t); + IGRAPH_CHECK_OOM(node, "Cannot add to trie."); + IGRAPH_FINALLY(igraph_free, node); + + IGRAPH_STRVECTOR_INIT_FINALLY(&node->strs, 2); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&node->children, 2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&node->values, 2); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 0, str + diff)); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 1, key + diff)); + VECTOR(node->children)[0] = VECTOR(t->children)[i]; + VECTOR(node->children)[1] = 0; + VECTOR(node->values)[0] = VECTOR(t->values)[i]; + VECTOR(node->values)[1] = newvalue; + + str2 = strdup(str); + IGRAPH_CHECK_OOM(str2, "Cannot add to trie."); + + str2[diff] = '\0'; + IGRAPH_FINALLY(igraph_free, str2); + + IGRAPH_CHECK(igraph_strvector_set(&t->strs, i, str2)); + + IGRAPH_FREE(str2); + IGRAPH_FINALLY_CLEAN(5); + + VECTOR(t->values)[i] = -1; + VECTOR(t->children)[i] = node; + + *id = newvalue; + return IGRAPH_SUCCESS; + } else { + + /* ------------------------------------------------- */ + /* No match, but we requested not to add the new key */ + *id = -1; + return IGRAPH_SUCCESS; + } + } + + /* ------------------------------------ */ + /* Nothing matches */ + + if (add) { + /* Memory saving at the cost of performance may be possible by using the pattern + * CHECK(reserve(vec, size(vec) + 1)); + * push_back(vec, value); + * This was the original pattern used before igraph 0.10. */ + IGRAPH_CHECK(igraph_strvector_push_back(&t->strs, key)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&t->children, NULL)); + IGRAPH_CHECK(igraph_vector_int_push_back(&t->values, newvalue)); + *id = newvalue; + } else { + *id = -1; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert a null-terminated string in a trie. + * + * \param t The trie. + * \param key The string to search for. If not found, it will be inserted. + * \param id The index of the string is stored here. + * \return Error code, usually \c IGRAPH_ENOMEM. + */ + +igraph_error_t igraph_trie_get(igraph_trie_t *t, const char *key, igraph_int_t *id) { + assert(key != NULL); + + if (*key == '\0') { + IGRAPH_ERROR("Keys in a trie cannot be empty.", IGRAPH_EINVAL); + } + + if (!t->storekeys) { + IGRAPH_CHECK(igraph_i_trie_get_node(&t->node, key, t->maxvalue + 1, id)); + if (*id > t->maxvalue) { + t->maxvalue = *id; + } + } else { + igraph_error_t ret; + + IGRAPH_FINALLY_ENTER(); + /* Add it to the string vector first, we can undo this later */ + ret = igraph_strvector_push_back(&t->keys, key); + if (ret != IGRAPH_SUCCESS) { + IGRAPH_FINALLY_EXIT(); + IGRAPH_ERROR("Cannot get element from trie.", ret); + } + ret = igraph_i_trie_get_node(&t->node, key, t->maxvalue + 1, id); + if (ret != IGRAPH_SUCCESS) { + igraph_strvector_resize(&t->keys, igraph_strvector_size(&t->keys) - 1); /* shrinks, error safe */ + IGRAPH_FINALLY_EXIT(); + IGRAPH_ERROR("Cannot get element from trie.", ret); + } + + /* everything is fine */ + if (*id > t->maxvalue) { + t->maxvalue = *id; + } else { + igraph_strvector_resize(&t->keys, igraph_strvector_size(&t->keys) - 1); /* shrinks, error safe */ + } + IGRAPH_FINALLY_EXIT(); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert a string of given length in a trie. + * + * This function is identical to \ref igraph_trie_get(), except that + * it takes a string of a given length as input instead of a null-terminated + * string. + * + * \param t The trie. + * \param key The string to search for. If not found, it will be inserted. + * \param length The length of \p key. + * \param id The index of the string is stored here. + * \return Error code, usually \c IGRAPH_ENOMEM. + */ + +igraph_error_t igraph_trie_get_len( + igraph_trie_t *t, const char *key, + igraph_int_t length, + igraph_int_t *id) { + + char *tmp = strndup(key, length); + IGRAPH_CHECK_OOM(tmp, "Cannot get from trie."); + IGRAPH_FINALLY(igraph_free, tmp); + IGRAPH_CHECK(igraph_trie_get(t, tmp, id)); + IGRAPH_FREE(tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup igraphtrie + * \brief Search in a trie. + * + * This variant does not add \p key to the trie if it does not exist. + * In this case, a negative \p id is returned. + * + * \param t The trie. + * \param key The string to search for. + * \param id If \p key is found, its index is stored here. Otherwise, + * a negative value is returned. + * \return Error code. + */ + +igraph_error_t igraph_trie_check(igraph_trie_t *t, const char *key, igraph_int_t *id) { + IGRAPH_CHECK(igraph_i_trie_get_node(&t->node, key, -1, id)); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup igraphtrie + * \brief Get an element of a trie based on its index. + * + * \param t The trie. + * \param idx The index of the string. It is not checked that it is within range. + * \return The string with the given index. If the trie does not store the keys for + * reverse lookup, \c NULL is returned. + */ + +const char* igraph_trie_idx(igraph_trie_t *t, igraph_int_t idx) { + if (! t->storekeys) { + return NULL; + } + return igraph_strvector_get(&t->keys, idx); +} + +/** + * \ingroup igraphtrie + * \brief Returns the size of a trie. + * + * \param t The trie. + * \return The size of the trie, i.e. one larger than the maximum index. + */ + +igraph_int_t igraph_trie_size(igraph_trie_t *t) { + return t->maxvalue + 1; +} + +/* Hmmm, very dirty.... */ + +/** + * \ingroup igraphtrie + * \brief Retrieves all the keys from the trie. + * + * + * Note that the returned pointer is a \em borrowed reference into the internal + * string vector of the trie. Do \em not modify it and do \em not use it after + * the trie was destroyed. + * + * \param t The trie. + * \return The borrowed reference. + */ + +const igraph_strvector_t* igraph_i_trie_borrow_keys(igraph_trie_t *t) { + return &t->keys; +} diff --git a/src/core/trie.h b/src/core/trie.h new file mode 100644 index 0000000..84a8daa --- /dev/null +++ b/src/core/trie.h @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_CORE_TRIE_H +#define IGRAPH_CORE_TRIE_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_strvector.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +/** + * Trie data type + * \ingroup internal + */ + +typedef struct s_igraph_trie_node { + igraph_strvector_t strs; + igraph_vector_ptr_t children; + igraph_vector_int_t values; +} igraph_trie_node_t; + +typedef struct s_igraph_trie { + igraph_trie_node_t node; + igraph_int_t maxvalue; + igraph_bool_t storekeys; + igraph_strvector_t keys; +} igraph_trie_t; + +#define IGRAPH_TRIE_NULL \ + { { IGRAPH_STRVECTOR_NULL, IGRAPH_VECTOR_PTR_NULL, IGRAPH_VECTOR_NULL}, \ + 0, 0, IGRAPH_STRVECTOR_NULL } +#define IGRAPH_TRIE_INIT_FINALLY(tr, sk) \ + do { IGRAPH_CHECK(igraph_trie_init(tr, sk)); \ + IGRAPH_FINALLY(igraph_trie_destroy, tr); } while (0) + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_trie_init(igraph_trie_t *t, igraph_bool_t storekeys); +IGRAPH_PRIVATE_EXPORT void igraph_trie_destroy(igraph_trie_t *t); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_trie_get(igraph_trie_t *t, const char *key, igraph_int_t *id); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_trie_check(igraph_trie_t *t, const char *key, igraph_int_t *id); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_trie_get_len(igraph_trie_t *t, const char *key, igraph_int_t length, + igraph_int_t *id); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE const char* igraph_trie_idx(igraph_trie_t *t, igraph_int_t idx); +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_PURE igraph_int_t igraph_trie_size(igraph_trie_t *t); + +const igraph_strvector_t* igraph_i_trie_borrow_keys(igraph_trie_t *t); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/core/typed_list.pmt b/src/core/typed_list.pmt new file mode 100644 index 0000000..4f23915 --- /dev/null +++ b/src/core/typed_list.pmt @@ -0,0 +1,1123 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include /* memmove */ + +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_qsort.h" + +#if defined(VECTOR_LIST) + /* It was indicated that every item in a list is a vector of the base type + * so let's define ITEM_TYPE appropriately */ + #define ITEM_TYPE BASE_VECTOR + + /* Define the macro that creates the name of a function that refers to a single + * _item_ in the vector */ + #if defined(BASE_IGRAPH_REAL) + #define ITEM_FUNCTION(f) CONCAT2x(igraph_vector,f) + #elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define ITEM_FUNCTION(f) CONCAT2x(igraph_vector_bool,f) + #else + #define ITEM_FUNCTION(f) CONCAT3(igraph_vector,SHORT,f) + #endif +#elif defined(MATRIX_LIST) + /* It was indicated that every item in a list is a matrix of the base type + * so let's define ITEM_TYPE appropriately */ + #define ITEM_TYPE BASE_MATRIX + + /* Define the macro that creates the name of a function that refers to a single + * _item_ in the matrix */ + #if defined(BASE_IGRAPH_REAL) + #define ITEM_FUNCTION(f) CONCAT2x(igraph_matrix,f) + #elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define ITEM_FUNCTION(f) CONCAT2x(igraph_matrix_bool,f) + #else + #define ITEM_FUNCTION(f) CONCAT3(igraph_matrix,SHORT,f) + #endif +#else + #define ITEM_TYPE BASE + + /* Define the macro that creates the name of a function that refers to a single + * _item_ in the vector */ + #if defined(BASE_GRAPH) + #define ITEM_FUNCTION(f) CONCAT2x(igraph,f) + #elif defined(BASE_ATTRIBUTE_RECORD) + #define ITEM_FUNCTION(f) CONCAT2x(igraph_attribute_record,f) + #endif + #if defined(BASE_BITSET) + #define ITEM_FUNCTION(f) CONCAT2x(igraph_bitset,f) + #endif +#endif + +static igraph_error_t INTERNAL_FUNCTION(init_item)(const TYPE *list, ITEM_TYPE *item); +static igraph_error_t INTERNAL_FUNCTION(copy_item)(ITEM_TYPE *dest, const ITEM_TYPE *source); +static void INTERNAL_FUNCTION(destroy_item)(ITEM_TYPE *item); + +static igraph_error_t INTERNAL_FUNCTION(init_slice)(const TYPE *list, ITEM_TYPE *start, ITEM_TYPE *end); +static void INTERNAL_FUNCTION(destroy_slice)(const TYPE *list, ITEM_TYPE *start, ITEM_TYPE *end); +static igraph_error_t INTERNAL_FUNCTION(expand_if_full)(TYPE *list); +static int INTERNAL_FUNCTION(sort_ind_cmp)(void *thunk, const void *p1, const void *p2); + +/** + * \ingroup vector_list + * \function igraph_vector_list_init + * \brief Initializes a list of vectors (constructor). + * + * + * This function constructs a list of vectors of the given size, and initializes + * each vector in the newly created list to become an empty vector. + * + * + * Vector objects initialized by this function are \em owned by the list, and + * they will be destroyed automatically when the list is destroyed with + * \ref igraph_vector_list_destroy(). + * + * \param v Pointer to a not yet initialized list of vectors. + * \param size The size of the list. + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, the amount of + * \quote time \endquote required to allocate + * O(n) elements and initialize the corresponding vectors; + * n is the number of elements. + */ + +igraph_error_t FUNCTION(init)(TYPE *v, igraph_int_t size) { + igraph_int_t alloc_size = size > 0 ? size : 1; + IGRAPH_ASSERT(size >= 0); + v->stor_begin = IGRAPH_CALLOC(alloc_size, ITEM_TYPE); + if (v->stor_begin == 0) { + IGRAPH_ERROR("Cannot initialize list.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + v->stor_end = v->stor_begin + alloc_size; + v->end = v->stor_begin + size; + + IGRAPH_CHECK(INTERNAL_FUNCTION(init_slice)(v, v->stor_begin, v->end)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_init_copy + * \brief Initializes a list of vectors from another list of vectors (constructor). + * + * + * The contents of the existing list will be copied to the new one. + * \param to Pointer to a not yet initialized list of vectors. + * \param from The original list of vectors to copy. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually O(nm), n is the size of + * the list, m is the size of each element in the list, assuming that copying a + * single item takes O(m) time. + */ + +igraph_error_t FUNCTION(init_copy)(TYPE* to, const TYPE* from) { + igraph_int_t i, size = FUNCTION(size)(from); + + IGRAPH_CHECK(FUNCTION(init)(to, 0)); + IGRAPH_FINALLY(FUNCTION(destroy), to); + + for (i = 0; i < size; i++) { + IGRAPH_CHECK(FUNCTION(push_back_copy)(to, FUNCTION(get_ptr)(from, i))); + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_destroy + * \brief Destroys a list of vectors object. + * + * + * All lists initialized by \ref igraph_vector_list_init() should be properly + * destroyed by this function. A destroyed list of vectors needs to be + * reinitialized by \ref igraph_vector_list_init() if you want to use it again. + * + * + * Vectors that are in the list when it is destroyed are also destroyed + * implicitly. + * + * \param v Pointer to the (previously initialized) list object to + * destroy. + * + * Time complexity: operating system dependent. + */ + +void FUNCTION(destroy)(TYPE *v) { + IGRAPH_ASSERT(v != 0); + + if (v->stor_begin != 0) { + FUNCTION(clear)(v); + IGRAPH_FREE(v->stor_begin); + v->stor_begin = NULL; + } +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_capacity + * \brief Returns the allocated capacity of the list. + * + * Note that this might be different from the size of the list (as + * queried by \ref igraph_vector_list_size()), and specifies how many vectors + * the list can hold, without reallocation. + * + * \param v Pointer to the (previously initialized) list object to query. + * \return The allocated capacity. + * + * \sa \ref igraph_vector_list_size(). + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(capacity)(const TYPE *v) { + return v->stor_end - v->stor_begin; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_reserve + * \brief Reserves memory for a list. + * + * + * \a igraph lists are flexible, they can grow and shrink. Growing + * however occasionally needs the data in the list to be copied. + * In order to avoid this, you can call this function to reserve space for + * future growth of the list. + * + * + * Note that this function does \em not change the size of the list, neither + * does it initialize any new vectors. Let us see a small example to clarify + * things: if you reserve space for 100 elements and the size of your + * list was (and still is) 60, then you can surely add additional 40 + * new vectors to your list before it will be copied. + * \param v The list object. + * \param capacity The new \em allocated size of the list. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the new allocated size of the list. + */ + +igraph_error_t FUNCTION(reserve)(TYPE *v, igraph_int_t capacity) { + igraph_int_t current_capacity; + ITEM_TYPE *tmp; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + current_capacity = FUNCTION(capacity)(v); + + if (capacity <= current_capacity) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(v->stor_begin, capacity, ITEM_TYPE); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for list."); + + v->end = tmp + (v->end - v->stor_begin); + v->stor_begin = tmp; + v->stor_end = v->stor_begin + capacity; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_empty + * \brief Decides whether the size of the list is zero. + * + * \param v The list object. + * \return True if the size of the list is zero and false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(empty)(const TYPE *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_begin == v->end; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_size + * \brief The size of the vector list. + * + * Returns the number of vectors stored in the list. + * + * \param v The list object + * \return The size of the list. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(size)(const TYPE *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->end - v->stor_begin; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_resize + * \brief Resizes the list of vectors. + * + * + * Note that this function does not free any memory, just sets the + * size of the list to the given one. It can on the other hand + * allocate more memory if the new size is larger than the previous + * one. + * + * + * When the new size is larger than the current size, the newly added + * vectors in the list are initialized to empty vectors. When the new + * size is smaller than the current size, the vectors that were removed + * from the end of the list are destroyed automatically. + * + * \param v The list object + * \param new_size The new size of the list. + * \return Error code, + * \c IGRAPH_ENOMEM if there is not enough + * memory. Note that this function \em never returns an error + * if the list is made smaller. + * \sa \ref igraph_vector_list_reserve() for allocating memory for future + * extensions of a list. + * + * Time complexity: O(m) if the new size is smaller (m is the number of items + * that were removed from the list), operating system dependent if the new + * size is larger. In the latter case it is usually around O(n), where n is the + * new size of the vector. + */ +igraph_error_t FUNCTION(resize)(TYPE *v, igraph_int_t new_size) { + igraph_int_t old_size; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + IGRAPH_CHECK(FUNCTION(reserve)(v, new_size)); + + old_size = FUNCTION(size)(v); + + if (old_size < new_size) { + IGRAPH_CHECK(INTERNAL_FUNCTION(init_slice)(v, v->stor_begin + old_size, v->stor_begin + new_size)); + } else if (old_size > new_size) { + INTERNAL_FUNCTION(destroy_slice)(v, v->stor_begin + new_size, v->stor_begin + old_size); + } + + v->end = v->stor_begin + new_size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_list_clear + * \brief Removes all elements from a list of vectors. + * + * This function sets the size of the list to zero, and it also destroys all + * the vectors that were placed in the list before clearing it. + * + * \param v The list object. + * + * Time complexity: O(n), n is the number of items being deleted. + */ +void FUNCTION(clear)(TYPE *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + INTERNAL_FUNCTION(destroy_slice)(v, v->stor_begin, v->end); + v->end = v->stor_begin; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_get_ptr + * \brief The address of a vector in the vector list. + * + * \param v The list object. + * \param pos The position of the vector in the list. The position of the first + * vector is zero. + * \return A pointer to the vector. It remains valid as long as the underlying + * list of vectors is not modified. + * + * Time complexity: O(1). + */ +ITEM_TYPE *FUNCTION(get_ptr)(const TYPE *v, igraph_int_t pos) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_begin + pos; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_set + * \brief Sets the vector at the given index in the list. + * + * + * This function destroys the vector that is already at the given index \p pos + * in the list, and replaces it with the vector pointed to by \p e. + * The ownership of the vector pointed to by \p e is taken by the list so + * the user is not responsible for destroying \p e any more; it will be + * destroyed when the list itself is destroyed or if \p e gets removed from the + * list without passing on the ownership to somewhere else. + * + * \param v The list object. + * \param pos The index to modify in the list. + * \param e The vector to set in the list. + * + * Time complexity: O(1). + */ +void FUNCTION(set)(TYPE *v, igraph_int_t pos, ITEM_TYPE *e) { + INTERNAL_FUNCTION(destroy_item)(v->stor_begin + pos); + v->stor_begin[pos] = *e; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_replace + * \brief Replaces the vector at the given index in the list with another one. + * + * + * This function replaces the vector that is already at the given index \p pos + * in the list with the vector pointed to by \p e. The ownership of the vector + * pointed to by \p e is taken by the list so the user is not responsible for + * destroying \p e any more. At the same time, the ownership of the vector that + * \em was in the list at position \p pos will be transferred to the caller and + * \p e will be updated to point to it, so the caller becomes responsible for + * destroying it when it does not need the vector any more. + * + * \param v The list object. + * \param pos The index to modify in the list. + * \param e The vector to swap with the one already in the list. + * + * Time complexity: O(1). + */ +void FUNCTION(replace)(TYPE *v, igraph_int_t pos, ITEM_TYPE *e) { + ITEM_TYPE old_value = *(FUNCTION(get_ptr)(v, pos)); + v->stor_begin[pos] = *e; + *e = old_value; +} + +/** + * \function igraph_vector_list_swap + * \brief Swaps all elements of two vector lists. + * + * \param v1 The first list. + * \param v2 The second list. + * \return Error code. + * + * Time complexity: O(1). + */ + +void FUNCTION(swap)(TYPE *v1, TYPE *v2) { + TYPE tmp; + + tmp = *v1; + *v1 = *v2; + *v2 = tmp; +} + +/** + * \function igraph_vector_list_swap_elements + * \brief Swap two elements in a vector list. + * + * Note that currently no range checking is performed. + * + * \param v The input list. + * \param i Index of the first element. + * \param j Index of the second element (may be the same as the + * first one). + * \return Error code, currently always \c IGRAPH_SUCCESS. + * + * Time complexity: O(1). + */ + +void FUNCTION(swap_elements)(TYPE *v1, igraph_int_t i, igraph_int_t j) { + ITEM_TYPE tmp = v1->stor_begin[i]; + v1->stor_begin[i] = v1->stor_begin[j]; + v1->stor_begin[j] = tmp; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_tail_ptr + * \brief The address of the last vector in the vector list. + * + * \param v The list object. + * \return A pointer to the last vector in the list, or \c NULL if the list + * is empty. + * + * Time complexity: O(1). + */ +ITEM_TYPE *FUNCTION(tail_ptr)(const TYPE *v) { + igraph_int_t size = FUNCTION(size)(v); + return size > 0 ? FUNCTION(get_ptr)(v, size - 1) : 0; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_discard + * \brief Discards the item at the given index in the vector list. + * + * This function removes the vector at the given index from the list, and + * moves all subsequent items in the list by one slot to the left to fill + * the gap. The vector that was removed from the list is destroyed automatically. + * + * \param v The list object. + * \param index Index of the item to be discarded and destroyed. + * \sa \ref igraph_vector_list_discard_fast() if you do not care about the + * order of the items in the list, \ref igraph_vector_list_remove() if you + * want to gain ownership of the item that was removed instead of destroying it. + * + * Time complexity: O(n), where n is the number of items in the list. + */ +void FUNCTION(discard)(TYPE *v, igraph_int_t index) { + igraph_int_t size = FUNCTION(size)(v); + + if (size > 0) { + INTERNAL_FUNCTION(destroy_item)(v->stor_begin + index); + memmove(v->stor_begin + index, v->stor_begin + index + 1, sizeof(ITEM_TYPE) * (size - index - 1)); + v->end -= 1; + } +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_discard_back + * \brief Discards the last item in the vector list. + * + * This function removes the last vector from the list and destroys it. + * + * \param v The list object. + * + * Time complexity: O(1). + */ +void FUNCTION(discard_back)(TYPE *v) { + igraph_int_t size = FUNCTION(size)(v); + if (size > 0) { + INTERNAL_FUNCTION(destroy_item)(v->end - 1); + v->end -= 1; + } +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_discard_fast + * \brief Discards the item at the given index in the vector list and moves the last item to its place. + * + * This function removes the vector at the given index from the list, and + * moves the last item in the list to \p index to fill the gap. The vector that + * was removed from the list is destroyed automatically. + * + * \param v The list object. + * \param index Index of the item to be discarded and destroyed. + * \sa \ref igraph_vector_list_discard() if you want to preserve the order of the + * items in the list, \ref igraph_vector_list_remove_fast() if you want to gain + * ownership of the item that was removed instead of destroying it. + * + * Time complexity: O(1). + */ +void FUNCTION(discard_fast)(TYPE *v, igraph_int_t index) { + igraph_int_t size = FUNCTION(size)(v); + + if (size > 0) { + INTERNAL_FUNCTION(destroy_item)(v->stor_begin + index); + v->end -= 1; + v->stor_begin[index] = *(v->end); + } +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_push_back + * \brief Appends an existing vector to the list, transferring ownership. + * + * This function resizes the list to be one element longer, and sets the very last + * element in the list to the specified vector \p e . The list takes ownership + * of the vector so the user is not responsible for freeing \p e any more; + * the vector will be destroyed when the list itself is destroyed or if \p e gets + * removed from the list without passing on the ownership to somewhere else. + * + * \param v The list object. + * \param e Pointer to the vector to append to the list. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: operating system dependent. What is important is that + * a sequence of n subsequent calls to this function has time complexity + * O(n), even if there hadn't been any space reserved for the new elements by + * \ref igraph_vector_list_reserve(). This is implemented by a trick similar to + * the C++ \type vector class: each time more memory is allocated for a + * vector, the size of the additionally allocated memory is the same + * as the vector's current length. (We assume here that the time + * complexity of memory allocation is at most linear). + */ +igraph_error_t FUNCTION(push_back)(TYPE *v, ITEM_TYPE *e) { + IGRAPH_CHECK(INTERNAL_FUNCTION(expand_if_full)(v)); + *(v->end) = *e; + v->end += 1; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_push_back_copy + * \brief Appends the copy of a vector to the list. + * + * This function resizes the list to be one element longer, and copies the + * specified vector given as an argument to the last element. The newly added + * element is owned by the list, but the ownership of the original vector is + * retained at the caller. + * + * \param v The list object. + * \param e Pointer to the vector to copy to the end of the list. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: same as \ref igraph_vector_list_push_back() plus the time + * needed to copy the vector (which is O(n) for n elements in the vector). + */ +igraph_error_t FUNCTION(push_back_copy)(TYPE *v, const ITEM_TYPE *e) { + ITEM_TYPE copy; + IGRAPH_CHECK(INTERNAL_FUNCTION(copy_item)(©, e)); + IGRAPH_FINALLY(INTERNAL_FUNCTION(destroy_item), ©); + IGRAPH_CHECK(FUNCTION(push_back)(v, ©)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_push_back_new + * \brief Appends a new vector to the list. + * + * This function resizes the list to be one element longer. The newly added + * element will be an empty vector that is owned by the list. A pointer to + * the newly added element is returned in the last argument if it is not + * \c NULL . + * + * \param v The list object. + * \param result Pointer to a vector pointer; this will be updated to point to + * the newly added vector. May be \c NULL if you do not need a pointer + * to the newly added vector. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: same as \ref igraph_vector_list_push_back(). + */ +igraph_error_t FUNCTION(push_back_new)(TYPE *v, ITEM_TYPE** e) { + IGRAPH_CHECK(INTERNAL_FUNCTION(expand_if_full)(v)); + IGRAPH_CHECK(INTERNAL_FUNCTION(init_item)(v, v->end)); + if (e) { + *e = v->end; + } + v->end += 1; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_insert + * \brief Inserts an existing vector into the list, transferring ownership. + * + * This function inserts \p e into the list at the given index, moving other + * items towards the end of the list as needed. The list takes ownership + * of the vector so the user is not responsible for freeing \p e any more; + * the vector will be destroyed when the list itself is destroyed or if \p e gets + * removed from the list without passing on the ownership to somewhere else. + * + * \param v The list object. + * \param pos The position where the new element is to be inserted. + * \param e Pointer to the vector to insert into the list. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(n). + */ +igraph_error_t FUNCTION(insert)(TYPE *v, igraph_int_t pos, ITEM_TYPE *e) { + igraph_int_t size = FUNCTION(size)(v); + IGRAPH_ASSERT(0 <= pos && pos <= size); + IGRAPH_CHECK(INTERNAL_FUNCTION(expand_if_full)(v)); + if (pos < size) { + memmove(v->stor_begin + pos + 1, v->stor_begin + pos, sizeof(ITEM_TYPE) * (size - pos)); + } + v->end += 1; + v->stor_begin[pos] = *e; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_insert_copy + * \brief Inserts the copy of a vector to the list. + * + * This function inserts a copy of \p e into the list at the given index, moving + * other items towards the end of the list as needed. The newly added + * element is owned by the list, but the ownership of the original vector is + * retained at the caller. + * + * \param v The list object. + * \param pos The position where the new element is to be inserted. + * \param e Pointer to the vector to copy to the end of the list. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: same as \ref igraph_vector_list_insert() plus the time + * needed to copy the vector (which is O(n) for n elements in the vector). + */ +igraph_error_t FUNCTION(insert_copy)(TYPE *v, igraph_int_t pos, const ITEM_TYPE *e) { + ITEM_TYPE copy; + IGRAPH_CHECK(INTERNAL_FUNCTION(copy_item)(©, e)); + IGRAPH_FINALLY(INTERNAL_FUNCTION(destroy_item), ©); + IGRAPH_CHECK(FUNCTION(insert)(v, pos, ©)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_insert_new + * \brief Inserts a new vector into the list. + * + * This function inserts a newly created empty vector into the list at the given + * index, moving other items towards the end of the list as needed. The newly + * added vector is owned by the list. A pointer to the new element is returned + * in the last argument if it is not \c NULL . + * + * \param v The list object. + * \param pos The position where the new element is to be inserted. + * \param result Pointer to a vector pointer; this will be updated to point to + * the newly added vector. May be \c NULL if you do not need a pointer + * to the newly added vector. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: same as \ref igraph_vector_list_push_back(). + */ +igraph_error_t FUNCTION(insert_new)(TYPE *v, igraph_int_t pos, ITEM_TYPE** e) { + ITEM_TYPE copy; + IGRAPH_CHECK(INTERNAL_FUNCTION(init_item)(v, ©)); + IGRAPH_FINALLY(INTERNAL_FUNCTION(destroy_item), ©); + IGRAPH_CHECK(FUNCTION(insert)(v, pos, ©)); + IGRAPH_FINALLY_CLEAN(1); + if (e) { + *e = FUNCTION(get_ptr)(v, pos); + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_remove + * \brief Removes the item at the given index from the vector list and transfer ownership to the caller. + * + * This function removes the vector at the given index from the list, and + * moves all subsequent items in the list by one slot to the left to fill + * the gap. The vector that was removed from the list is returned in \p e + * and its ownership is passed back to the caller; in other words, the caller + * becomes responsible for destroying the vector when it is not needed any more. + * + * \param v The list object. + * \param index Index of the item to be removed. + * \param result Pointer to an \ref igraph_vector_t object; it will be updated to the + * item that was removed from the list. Ownership of this vector is + * passed on to the caller. It is an error to supply a null pointer here. + * \sa \ref igraph_vector_list_discard() if you are not interested in the item + * that was removed, \ref igraph_vector_list_remove_fast() if you do not care + * about the order of the items in the list. + * + * Time complexity: O(n), where n is the number of items in the list. + */ +igraph_error_t FUNCTION(remove)(TYPE *v, igraph_int_t index, ITEM_TYPE *result) { + igraph_int_t size = FUNCTION(size)(v); + + IGRAPH_ASSERT(result != 0); + + if (index < 0 || index >= size) { + IGRAPH_ERROR("invalid index when removing item", IGRAPH_EINVAL); + } + + *result = *(FUNCTION(get_ptr)(v, index)); + + memmove(v->stor_begin + index, v->stor_begin + index + 1, sizeof(ITEM_TYPE) * (size - index - 1)); + v->end -= 1; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_pop_back + * \brief Removes the last item from the vector list and transfer ownership to the caller. + * + * This function removes the last vector from the list. The vector that was + * removed from the list is returned and its ownership is passed back to the + * caller; in other words, the caller becomes responsible for destroying + * the vector when it is not needed any more. + * + * + * It is an error to call this function with an empty vector. + * + * \param v The list object. + * \param result Pointer to an \ref igraph_vector_t object; it will be updated to the + * item that was removed from the list. Ownership of this vector is + * passed on to the caller. + * + * Time complexity: O(1). + */ +ITEM_TYPE FUNCTION(pop_back)(TYPE *v) { + IGRAPH_ASSERT(!FUNCTION(empty)(v)); + v->end -= 1; + return *(v->end); +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_remove_fast + * \brief Removes the item at the given index in the vector list, move the last item to its place and transfer ownership to the caller. + * + * This function removes the vector at the given index from the list, + * moves the last item in the list to \p index to fill the gap, and then + * transfers ownership of the removed vector back to the caller; in other words, + * the caller becomes responsible for destroying the vector when it is not + * needed any more. + * + * \param v The list object. + * \param index Index of the item to be removed. + * \param result Pointer to an \ref igraph_vector_t object; it will be updated to the + * item that was removed from the list. Ownership of this vector is + * passed on to the caller. It is an error to supply a null pointer here. + * \sa \ref igraph_vector_list_remove() if you want to preserve the order of the + * items in the list, \ref igraph_vector_list_discard_fast() if you are not + * interested in the item that was removed. + * + * Time complexity: O(1). + */ +igraph_error_t FUNCTION(remove_fast)(TYPE *v, igraph_int_t index, ITEM_TYPE *result) { + igraph_int_t size = FUNCTION(size)(v); + + IGRAPH_ASSERT(result != 0); + + if (index < 0 || index >= size) { + IGRAPH_ERROR("invalid index when removing item", IGRAPH_EINVAL); + } + + *result = *(FUNCTION(get_ptr)(v, index)); + + v->end -= 1; + v->stor_begin[index] = *(v->end); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_permute + * \brief Permutes the elements of a list in place according to an index vector. + * + * + * This function takes a list \c v and a corresponding index vector \c index, + * and permutes the elements of \c v such that \c v[index[i]] is moved to become + * \c v[i] after the function is executed. + * + * + * It is an error to call this function with an index vector that does not + * represent a valid permutation. Each element in the index vector must be + * between 0 and the length of the list minus one (inclusive), and each such + * element must appear only once. The function does not attempt to validate the + * index vector. Memory may be leaked if the index vector does not satisfy these + * conditions. + * + * + * The index vector that this function takes is compatible with the index vector + * returned from \ref igraph_vector_list_sort_ind(); passing in the index vector + * from \ref igraph_vector_list_sort_ind() will sort the original vector. + * + * \param v the list to permute + * \param index the index vector + * + * Time complexity: O(n), the number of items in the list. + */ +igraph_error_t FUNCTION(permute)(TYPE *v, const igraph_vector_int_t* index) { + ITEM_TYPE *work; + igraph_int_t i, size; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(index != NULL); + IGRAPH_ASSERT(index->stor_begin != NULL); + + size = igraph_vector_int_size(index); + IGRAPH_ASSERT(FUNCTION(size)(v) == size); + + work = IGRAPH_CALLOC(size, ITEM_TYPE); + if (work == 0) { + IGRAPH_ERROR("Cannot permute list.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + for (i = 0; i < size; i++) { + work[i] = v->stor_begin[VECTOR(*index)[i]]; + } + + memcpy(v->stor_begin, work, sizeof(ITEM_TYPE) * size); + + IGRAPH_FREE(work); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_sort + * \brief Sorts the elements of the list into ascending order. + * + * \param v Pointer to an initialized list object. + * \param cmp A comparison function that takes pointers to two vectors and + * returns zero if the two vectors are considered equal, any negative + * number if the first vector is smaller and any positive number if the + * second vector is smaller. + * \return Error code. + * + * Time complexity: O(n log n) for n elements. + */ +void FUNCTION(sort)(TYPE *v, int (*cmp)(const ITEM_TYPE*, const ITEM_TYPE*)) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + igraph_qsort( + v->stor_begin, FUNCTION(size)(v), sizeof(ITEM_TYPE), + (int(*)(const void*, const void*))cmp + ); +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_sort_ind + * \brief Returns a permutation of indices that sorts the list. + * + * Takes an unsorted list \p v as input and computes an array of + * indices \p inds such that v[ inds[i] ], with i increasing from 0, is + * an ordered array according to the comparison function \p cmp. The order of + * indices for identical elements is not defined. + * + * \param v the list to be sorted + * \param inds the output array of indices. This must be initialized, + * but will be resized + * \param cmp A comparison function that takes pointers to two vectors and + * returns zero if the two vectors are considered equal, any negative + * number if the first vector is smaller and any positive number if the + * second vector is smaller. + * \return Error code. + * + * Time complexity: O(n log n) for n elements. + */ +igraph_error_t FUNCTION(sort_ind)( + TYPE *v, igraph_vector_int_t *inds, + int (*cmp)(const ITEM_TYPE*, const ITEM_TYPE*) +) { + igraph_int_t i, n = FUNCTION(size)(v); + ITEM_TYPE **vind, *first; + + IGRAPH_CHECK(igraph_vector_int_resize(inds, n)); + if (n == 0) { + return IGRAPH_SUCCESS; + } + + vind = IGRAPH_CALLOC(n, ITEM_TYPE*); + if (vind == 0) { + IGRAPH_ERROR("igraph_vector_list_sort_ind failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + for (i = 0; i < n; i++) { + vind[i] = v->stor_begin + i; + } + first = vind[0]; + igraph_qsort_r( + vind, n, sizeof(ITEM_TYPE*), (void*) cmp, + (int(*)(void*, const void*, const void*)) INTERNAL_FUNCTION(sort_ind_cmp) + ); + for (i = 0; i < n; i++) { + VECTOR(*inds)[i] = vind[i] - first; + } + IGRAPH_FREE(vind); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector_list + * \function igraph_vector_list_remove_consecutive_duplicates + * \brief Removes consecutive duplicates from a vector list. + * + * Removes consecutive duplicate vectors from the list. Optionally, a custom + * equivalence relation may be used to determine when two vectors are + * considered to be the same. + * + * + * An efficient way to remove all duplicates, not just consecutive ones, + * is to first sort the vector list using \ref igraph_vector_list_sort(), + * then use this function. This will of course re-order the list. + * + * \param v The list to remove consecutive duplicates from. + * \param eq A comparison function that takes pointers to two vectors and + * returns true if they are equivalent. It is assumed that it implements + * a transitive, but not necessarily symmetric relation. + * Use \ref igraph_vector_all_e() to consider vector equivalent only + * when their contents are identical. + * + * \sa \ref igraph_vector_list_sort() + * + * Time complexity: O(n), the number of items in the list. + */ +void FUNCTION(remove_consecutive_duplicates)( + TYPE *v, igraph_bool_t (*eq)(const ITEM_TYPE*, const ITEM_TYPE*) +) { + igraph_int_t i, j, n = FUNCTION(size)(v); + ITEM_TYPE *p = v->stor_begin; + + if (n < 2) { + return; + } + + for (i=0, j=0; i < n-1; ++i) { + if (eq(&p[i], &p[i+1])) { + INTERNAL_FUNCTION(destroy_item)(&p[i]); + } else { + p[j++] = p[i]; + } + } + p[j++] = p[n-1]; + + v->end = p + j; +} + +/** + * \function igraph_vector_list_reverse + * \brief Reverses the elements of a vector list. + * + * The first element will be last, the last element will be + * first, etc. + * \param v The input vector list. + * \return Error code, currently always \c IGRAPH_SUCCESS. + * + * Time complexity: O(n), the number of elements. + */ +igraph_error_t FUNCTION(reverse)(TYPE *v) { + igraph_int_t n = FUNCTION(size)(v), n2 = n / 2; + igraph_int_t i, j; + for (i = 0, j = n - 1; i < n2; i++, j--) { + ITEM_TYPE tmp; + tmp = VECTOR(*v)[i]; + VECTOR(*v)[i] = VECTOR(*v)[j]; + VECTOR(*v)[j] = tmp; + } + return IGRAPH_SUCCESS; +} + +/* ************************************************************************ */ + +#ifndef CUSTOM_INIT_DESTROY + +static igraph_error_t INTERNAL_FUNCTION(init_item)(const TYPE *list, ITEM_TYPE *item) { + IGRAPH_UNUSED(list); + return ITEM_FUNCTION(init)(item, 0); +} + +static igraph_error_t INTERNAL_FUNCTION(copy_item)(ITEM_TYPE *dest, const ITEM_TYPE *source) { + return ITEM_FUNCTION(init_copy)(dest, source); +} + +static void INTERNAL_FUNCTION(destroy_item)(ITEM_TYPE *item) { + ITEM_FUNCTION(destroy)(item); +} + +#endif + +/* ************************************************************************ */ + +static igraph_error_t INTERNAL_FUNCTION(init_slice)(const TYPE *list, ITEM_TYPE *start, ITEM_TYPE *end) { + ITEM_TYPE *current; + igraph_error_t retval; + + for (current = start; current < end; current++) { + retval = INTERNAL_FUNCTION(init_item)(list, current); + if (retval) { + INTERNAL_FUNCTION(destroy_slice)(list, start, current); + IGRAPH_CHECK(retval); + } + } + + return IGRAPH_SUCCESS; +} + +static void INTERNAL_FUNCTION(destroy_slice)(const TYPE *list, ITEM_TYPE *start, ITEM_TYPE *end) { + IGRAPH_UNUSED(list); + for (; start < end; start++) { + INTERNAL_FUNCTION(destroy_item)(start); + } +} + +/** + * Ensures that the vector has at least one extra slot at the end of its + * allocated storage area. + */ +static igraph_error_t INTERNAL_FUNCTION(expand_if_full)(TYPE *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + if (v->stor_end == v->end) { + igraph_int_t old_size = FUNCTION(size)(v); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot add new item to list, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(reserve)(v, new_size)); + } + + return IGRAPH_SUCCESS; +} + +/** + * Helper function passed to qsort from igraph_vector_list_sort_ind + */ +static int INTERNAL_FUNCTION(sort_ind_cmp)(void *thunk, const void *p1, const void *p2) { + int (*cmp)(const ITEM_TYPE*, const ITEM_TYPE*) = (int (*)(const ITEM_TYPE*, const ITEM_TYPE*)) thunk; + ITEM_TYPE **pa = (ITEM_TYPE **) p1; + ITEM_TYPE **pb = (ITEM_TYPE **) p2; + return cmp(*pa, *pb); +} + +#undef ITEM_FUNCTION diff --git a/src/core/vector.c b/src/core/vector.c new file mode 100644 index 0000000..86d58d0 --- /dev/null +++ b/src/core/vector.c @@ -0,0 +1,651 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector.h" + +#include "igraph_complex.h" +#include "igraph_types.h" +#include "igraph_nongraph.h" + +#include + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +/** + * \ingroup vector + * \function igraph_vector_floor + * \brief Transform a real vector to an integer vector by flooring each element. + * + * Flooring means rounding down to the nearest integer. + * + * \param from The original real vector object. + * \param to Pointer to an initialized integer vector. The result will be stored here. + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory + * + * Time complexity: O(n), where n is the number of elements in the vector. + */ +igraph_error_t igraph_vector_floor(const igraph_vector_t *from, igraph_vector_int_t *to) { + const igraph_int_t n = igraph_vector_size(from); + + IGRAPH_CHECK(igraph_vector_int_resize(to, n)); + for (igraph_int_t i = 0; i < n; i++) { + VECTOR(*to)[i] = floor(VECTOR(*from)[i]); + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_vector_round(const igraph_vector_t *from, igraph_vector_int_t *to) { + const igraph_int_t n = igraph_vector_size(from); + + IGRAPH_CHECK(igraph_vector_int_resize(to, n)); + for (igraph_int_t i = 0; i < n; i++) { + VECTOR(*to)[i] = round(VECTOR(*from)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_int_pair_order + * \brief Calculates the order of the elements in a pair of integer vectors of equal length. + * + * The smallest element will have order zero, the second smallest + * order one, etc. + * + * \param v The original \ref igraph_vector_int_t object. + * \param v2 A secondary key, another \ref igraph_vector_int_t object. + * \param res An initialized \ref igraph_vector_int_t object, it will be + * resized to match the size of \p v. The result of the computation will + * be stored here. + * \param nodes Hint, the largest element in \p v. + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory + * + * Time complexity: O() + */ + +igraph_error_t igraph_vector_int_pair_order(const igraph_vector_int_t* v, + const igraph_vector_int_t* v2, + igraph_vector_int_t* res, igraph_int_t nodes) { + igraph_int_t edges = igraph_vector_int_size(v); + igraph_vector_int_t ptr; + igraph_vector_int_t rad; + igraph_int_t i, j; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptr, nodes + 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&rad, edges); + IGRAPH_CHECK(igraph_vector_int_resize(res, edges)); + + for (i = 0; i < edges; i++) { + igraph_int_t radix = VECTOR(*v2)[i]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[i] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = i + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + igraph_int_t next = VECTOR(ptr)[i] - 1; + VECTOR(*res)[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = VECTOR(rad)[next] - 1; + VECTOR(*res)[j++] = next; + } + } + } + + igraph_vector_int_null(&ptr); + igraph_vector_int_null(&rad); + + for (i = 0; i < edges; i++) { + igraph_int_t edge = VECTOR(*res)[edges - i - 1]; + igraph_int_t radix = VECTOR(*v)[edge]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[edge] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = edge + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + igraph_int_t next = VECTOR(ptr)[i] - 1; + VECTOR(*res)[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = VECTOR(rad)[next] - 1; + VECTOR(*res)[j++] = next; + } + } + } + + igraph_vector_int_destroy(&ptr); + igraph_vector_int_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_i_vector_int_order + * + * \param v Integer vector with non-negative entries. + * \param res The indices of the elements of \p v will be written here + * in sorted order. + * \param maxval The largest value in \p v must be provided here. + * \return Error code. + * + * Time complexity: O(maxval). + */ +igraph_error_t igraph_i_vector_int_order( + const igraph_vector_int_t *v, + igraph_vector_int_t *res, + igraph_int_t maxval) { + + const igraph_int_t size = igraph_vector_int_size(v); + igraph_vector_int_t ptr; + igraph_vector_int_t rad; + igraph_int_t j; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptr, maxval + 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&rad, size); + IGRAPH_CHECK(igraph_vector_int_resize(res, size)); + + for (igraph_int_t i = 0; i < size; i++) { + igraph_int_t radix = VECTOR(*v)[i]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[i] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = i + 1; + } + + j = 0; + for (igraph_int_t i = 0; i < maxval + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + igraph_int_t next = VECTOR(ptr)[i] - 1; + VECTOR(*res)[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = VECTOR(rad)[next] - 1; + VECTOR(*res)[j++] = next; + } + } + } + + igraph_vector_int_destroy(&ptr); + igraph_vector_int_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_i_vector_int_rank + * + * \param v Integer vector with non-negative entries. + * \param res The zero-based rank of the elements of \p v will be written here + * from smallest to largest. + * \param maxval The largest value in \p v must be provided here. + * \return Error code. + * + * Time complexity: O(maxval). + */ +igraph_error_t igraph_i_vector_int_rank( + const igraph_vector_int_t *v, + igraph_vector_int_t *res, + igraph_int_t maxval) { + + const igraph_int_t size = igraph_vector_int_size(v); + igraph_vector_int_t rad; + igraph_vector_int_t ptr; + igraph_int_t c = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&rad, maxval); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptr, size); + IGRAPH_CHECK(igraph_vector_int_resize(res, size)); + + for (igraph_int_t i = 0; i < size; i++) { + igraph_int_t elem = VECTOR(*v)[i]; + VECTOR(ptr)[i] = VECTOR(rad)[elem]; + VECTOR(rad)[elem] = i + 1; + } + + for (igraph_int_t i = 0; i < maxval; i++) { + igraph_int_t p = VECTOR(rad)[i]; + while (p != 0) { + VECTOR(*res)[p - 1] = c++; + p = VECTOR(ptr)[p - 1]; + } + } + + igraph_vector_int_destroy(&ptr); + igraph_vector_int_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_complex_real + * \brief Gives the real part of a complex vector. + * + * \param v Pointer to a complex vector. + * \param real Pointer to an initialized vector. The result will be stored here. + * \return Error code. + * + * Time complexity: O(n), n is the number of elements in the vector. + */ + +igraph_error_t igraph_vector_complex_real(const igraph_vector_complex_t *v, + igraph_vector_t *real) { + igraph_int_t i, n = igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(real, n)); + for (i = 0; i < n; i++) { + VECTOR(*real)[i] = IGRAPH_REAL(VECTOR(*v)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_complex_imag + * \brief Gives the imaginary part of a complex vector. + * + * \param v Pointer to a complex vector. + * \param imag Pointer to an initialized vector. The result will be stored here. + * \return Error code. + * + * Time complexity: O(n), n is the number of elements in the vector. + */ + +igraph_error_t igraph_vector_complex_imag(const igraph_vector_complex_t *v, + igraph_vector_t *imag) { + igraph_int_t i, n = igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(imag, n)); + for (i = 0; i < n; i++) { + VECTOR(*imag)[i] = IGRAPH_IMAG(VECTOR(*v)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_complex_realimag + * \brief Gives the real and imaginary parts of a complex vector. + * + * \param v Pointer to a complex vector. + * \param real Pointer to an initialized vector. The real part will be stored here. + * \param imag Pointer to an initialized vector. The imaginary part will be stored here. + * \return Error code. + * + * Time complexity: O(n), n is the number of elements in the vector. + */ + +igraph_error_t igraph_vector_complex_realimag(const igraph_vector_complex_t *v, + igraph_vector_t *real, + igraph_vector_t *imag) { + igraph_int_t i, n = igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(real, n)); + IGRAPH_CHECK(igraph_vector_resize(imag, n)); + for (i = 0; i < n; i++) { + igraph_complex_t z = VECTOR(*v)[i]; + VECTOR(*real)[i] = IGRAPH_REAL(z); + VECTOR(*imag)[i] = IGRAPH_IMAG(z); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_complex_create + * \brief Creates a complex vector from a real and imaginary part. + * + * \param v Pointer to an uninitialized complex vector. + * \param real Pointer to the real part of the complex vector. + * \param imag Pointer to the imaginary part of the complex vector. + * \return Error code. + * + * Time complexity: O(n), n is the number of elements in the vector. + */ + +igraph_error_t igraph_vector_complex_create(igraph_vector_complex_t *v, + const igraph_vector_t *real, + const igraph_vector_t *imag) { + igraph_int_t i, n = igraph_vector_size(real); + if (n != igraph_vector_size(imag)) { + IGRAPH_ERROR("Real and imag vector sizes don't match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_complex_init(v, n)); + /* FINALLY not needed */ + + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = igraph_complex(VECTOR(*real)[i], VECTOR(*imag)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_complex_create_polar + * \brief Creates a complex matrix from a magnitude and an angle. + * + * \param v Pointer to an uninitialized complex vector. + * \param r Pointer to a real vector containing magnitudes. + * \param theta Pointer to a real vector containing arguments (phase angles). + * \return Error code. + * + * Time complexity: O(n), n is the number of elements in the vector. + */ + +igraph_error_t igraph_vector_complex_create_polar(igraph_vector_complex_t *v, + const igraph_vector_t *r, + const igraph_vector_t *theta) { + igraph_int_t i, n = igraph_vector_size(r); + if (n != igraph_vector_size(theta)) { + IGRAPH_ERROR("'r' and 'theta' vector sizes don't match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_complex_init(v, n)); + /* FINALLY not needed */ + + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = igraph_complex_polar(VECTOR(*r)[i], VECTOR(*theta)[i]); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_complex_all_almost_e + * \brief Are all elements almost equal? + * + * Checks if the elements of two complex vectors are equal within a relative tolerance. + * + * \param lhs The first vector. + * \param rhs The second vector. + * \param eps Relative tolerance, see \ref igraph_complex_almost_equals() for details. + * \return True if the two vectors are almost equal, false if there is at least + * one differing element or if the vectors are not of the same size. + */ +igraph_bool_t igraph_vector_complex_all_almost_e(const igraph_vector_complex_t *lhs, + const igraph_vector_complex_t *rhs, + igraph_real_t eps) { + + igraph_int_t n = igraph_vector_complex_size(lhs); + + if (lhs == rhs) { + return true; + } + + if (igraph_vector_complex_size(rhs) != n) { + return false; + } + + for (igraph_int_t i=0; i < n; i++) { + if (! igraph_complex_almost_equals(VECTOR(*lhs)[i], VECTOR(*rhs)[i], eps)) + return false; + } + + return true; +} + + +/** + * \function igraph_vector_all_almost_e + * \brief Are all elements almost equal? + * + * Checks if the elements of two vectors are equal within a relative tolerance. + * + * \param lhs The first vector. + * \param rhs The second vector. + * \param eps Relative tolerance, see \ref igraph_almost_equals() for details. + * \return True if the two vectors are almost equal, false if there is at least + * one differing element or if the vectors are not of the same size. + */ +igraph_bool_t igraph_vector_all_almost_e(const igraph_vector_t *lhs, + const igraph_vector_t *rhs, + igraph_real_t eps) { + + igraph_int_t n = igraph_vector_size(lhs); + + if (lhs == rhs) { + return true; + } + + if (igraph_vector_size(rhs) != n) { + return false; + } + + for (igraph_int_t i=0; i < n; i++) { + if (! igraph_almost_equals(VECTOR(*lhs)[i], VECTOR(*rhs)[i], eps)) + return false; + } + + return true; +} + +/** + * \function igraph_vector_zapsmall + * \brief Replaces small elements of a vector by exact zeros. + * + * Vector elements which are smaller in magnitude than the given absolute + * tolerance will be replaced by exact zeros. The default tolerance + * corresponds to two-thirds of the representable digits of \type igraph_real_t, + * i.e. DBL_EPSILON^(2/3) which is approximately 10^-10. + * + * \param v The vector to process, it will be changed in-place. + * \param tol Tolerance value. Numbers smaller than this in magnitude will + * be replaced by zeros. Pass in zero to use the default tolerance. + * Must not be negative. + * \return Error code. + * + * \sa \ref igraph_vector_all_almost_e() and \ref igraph_almost_equals() to + * perform comparisons with relative tolerances. + */ +igraph_error_t igraph_vector_zapsmall(igraph_vector_t *v, igraph_real_t tol) { + igraph_int_t i, n = igraph_vector_size(v); + if (tol < 0.0) { + IGRAPH_ERROR("Tolerance must be positive or zero.", IGRAPH_EINVAL); + } + if (tol == 0.0) { + tol = pow(DBL_EPSILON, 2.0/3); + } + for (i = 0; i < n; i++) { + igraph_real_t val = VECTOR(*v)[i]; + if (val < tol && val > -tol) { + VECTOR(*v)[i] = 0.0; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_complex_zapsmall + * \brief Replaces small elements of a complex vector by exact zeros. + * + * Similarly to \ref igraph_vector_zapsmall(), small elements will be replaced + * by zeros. The operation is performed separately on the real and imaginary + * parts of the numbers. This way, complex numbers with a large real part and + * tiny imaginary part will effectively be transformed to real numbers. + * The default tolerance + * corresponds to two-thirds of the representable digits of \type igraph_real_t, + * i.e. DBL_EPSILON^(2/3) which is approximately 10^-10. + * + * \param v The vector to process, it will be changed in-place. + * \param tol Tolerance value. Real and imaginary parts smaller than this in + * magnitude will be replaced by zeros. Pass in zero to use the default + * tolerance. Must not be negative. + * \return Error code. + * + * \sa \ref igraph_vector_complex_all_almost_e() and + * \ref igraph_complex_almost_equals() to perform comparisons with relative + * tolerances. + */ +igraph_error_t igraph_vector_complex_zapsmall(igraph_vector_complex_t *v, igraph_real_t tol) { + igraph_int_t i, n = igraph_vector_complex_size(v); + if (tol < 0.0) { + IGRAPH_ERROR("Tolerance must be positive or zero.", IGRAPH_EINVAL); + } + if (tol == 0.0) { + tol = pow(DBL_EPSILON, 2.0/3); + } + for (i = 0; i < n; i++) { + igraph_complex_t val = VECTOR(*v)[i]; + igraph_bool_t zapped = false; + if (IGRAPH_REAL(val) < tol && IGRAPH_REAL(val) > -tol) { + IGRAPH_REAL(val) = 0.0; + zapped = true; + } + if (IGRAPH_IMAG(val) < tol && IGRAPH_IMAG(val) > -tol) { + IGRAPH_IMAG(val) = 0.0; + zapped = true; + } + if (zapped) { + VECTOR(*v)[i] = val; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_is_nan + * \brief Check for each element if it is NaN. + * + * \param v The \type igraph_vector_t object to check. + * \param is_nan The resulting boolean vector indicating for each element + * whether it is NaN or not. + * \return Error code, + * \c IGRAPH_ENOMEM if there is not enough memory. + * Note that this function \em never returns an error + * if the vector \p is_nan will already be large enough. + * + * Time complexity: O(n), the number of elements. + */ +igraph_error_t igraph_vector_is_nan(const igraph_vector_t *v, igraph_vector_bool_t *is_nan) +{ + igraph_real_t *ptr; + igraph_bool_t *ptr_nan; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(is_nan != NULL); + IGRAPH_ASSERT(is_nan->stor_begin != NULL); + IGRAPH_CHECK(igraph_vector_bool_resize(is_nan, igraph_vector_size(v))); + for (ptr = v->stor_begin, ptr_nan = is_nan->stor_begin; ptr < v->end; ptr++, ptr_nan++) { + *ptr_nan = isnan(*ptr); + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_is_any_nan + * \brief Check if any element is NaN. + * + * \param v The \type igraph_vector_t object to check. + * \return True if any element is NaN, false otherwise. + * + * Time complexity: O(n), the number of elements. + */ +igraph_bool_t igraph_vector_is_any_nan(const igraph_vector_t *v) +{ + igraph_real_t *ptr; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + ptr = v->stor_begin; + while (ptr < v->end) { + if (isnan(*ptr)) { + return true; + } + ptr++; + } + return false; +} + + +/** + * \ingroup vector + * \function igraph_vector_is_all_finite + * \brief Check if all elements are finite. + * + * \param v The \type igraph_vector_t object to check. + * \return True if none of the elements are infinite or NaN. + * + * Time complexity: O(n), the number of elements. + */ +igraph_bool_t igraph_vector_is_all_finite(const igraph_vector_t *v) +{ + igraph_real_t *ptr; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + ptr = v->stor_begin; + while (ptr < v->end) { + if (!isfinite(*ptr)) { + return false; + } + ptr++; + } + return true; +} diff --git a/src/core/vector.pmt b/src/core/vector.pmt new file mode 100644 index 0000000..d4e0da8 --- /dev/null +++ b/src/core/vector.pmt @@ -0,0 +1,3464 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_random.h" +#include "igraph_qsort.h" + +#include "math/safe_intop.h" + +#include /* memcpy & co. */ +#include +#include /* va_start & co */ +#include + +/** + * \ingroup vector + * \section about_igraph_vector_t_objects About \type igraph_vector_t objects + * + * The \type igraph_vector_t data type is a simple and efficient + * interface to arrays containing numbers. It is something similar to (but much + * simpler than) the \type vector template in the C++ standard library. + * + * There are multiple variants of \type igraph_vector_t; the basic variant + * stores doubles, but there is also \type igraph_vector_int_t for integers (of + * type \type igraph_int_t), \c igraph_vector_bool_t for booleans (of type + * \type igraph_bool_t) and so on. Vectors are used extensively in \a igraph; all + * functions that expect or return a list of numbers use \type igraph_vector_t or + * \type igraph_vector_int_t to achieve this. Integer vectors are typically used + * when the vector is supposed to hold vertex or edge identifiers, while + * \type igraph_vector_t is used when the vector is expected to hold fractional + * numbers or infinities. + * + * The \type igraph_vector_t type and its variants usually use O(n) space + * to store n elements. Sometimes they use more, this is because vectors can + * shrink, but even if they shrink, the current implementation does not free a + * single bit of memory. + * + * The elements in an \type igraph_vector_t object and its variants are + * indexed from zero, we follow the usual C convention here. + * + * The elements of a vector always occupy a single block of + * memory, the starting address of this memory block can be queried + * with the \ref VECTOR macro. This way, vector objects can be used + * with standard mathematical libraries, like the GNU Scientific + * Library. + * + * Almost all of the functions described below for \type igraph_vector_t + * also exist for all the other vector type variants. These variants are not + * documented separately; you can simply replace \c vector with \c vector_int, + * \c vector_bool or something similar if you need a function for another + * variant. For instance, to initialize a vector of type \type igraph_vector_int_t, + * you need to use \ref igraph_vector_int_init() and not \ref igraph_vector_init(). + */ + +/** + * \ingroup vector + * \section igraph_vector_constructors_and_destructors Constructors and + * destructors + * + * \type igraph_vector_t objects have to be initialized before using + * them, this is analogous to calling a constructor on them. There are a + * number of \type igraph_vector_t constructors, for your + * convenience. \ref igraph_vector_init() is the basic constructor, it + * creates a vector of the given length, filled with zeros. + * \ref igraph_vector_init_copy() creates a new identical copy + * of an already existing and initialized vector. \ref + * igraph_vector_init_array() creates a vector by copying a regular C array. + * \ref igraph_vector_init_range() creates a vector containing a regular + * sequence with increment one. + * + * \ref igraph_vector_view() is a special constructor, it allows you to + * handle a regular C array as a \type vector without copying + * its elements. + * + * + * If a \type igraph_vector_t object is not needed any more, it + * should be destroyed to free its allocated memory by calling the + * \type igraph_vector_t destructor, \ref igraph_vector_destroy(). + * + * Note that vectors created by \ref igraph_vector_view() are special, + * you must not call \ref igraph_vector_destroy() on these. + */ + +/** + * \ingroup vector + * \function igraph_vector_init + * \brief Initializes a vector object (constructor). + * + * + * Every vector needs to be initialized before it can be used, and + * there are a number of initialization functions or otherwise called + * constructors. This function constructs a vector of the given size and + * initializes each entry to 0. Note that \ref igraph_vector_null() can be + * used to set each element of a vector to zero. However, if you want a + * vector of zeros, it is much faster to use this function than to create a + * vector and then invoke \ref igraph_vector_null(). + * + * + * Every vector object initialized by this function should be + * destroyed (ie. the memory allocated for it should be freed) when it + * is not needed anymore, the \ref igraph_vector_destroy() function is + * responsible for this. + * \param v Pointer to a not yet initialized vector object. + * \param size The size of the vector. + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, the amount of + * \quote time \endquote required to allocate + * O(n) elements, + * n is the number of elements. + */ + +igraph_error_t FUNCTION(igraph_vector, init)(TYPE(igraph_vector) *v, igraph_int_t size) { + igraph_int_t alloc_size; + IGRAPH_ASSERT(size >= 0); + alloc_size = size > 0 ? size : 1; + + /* When this function fails, it should leave stor_begin set to NULL, + * so that vector_destroy() is still safe to call on the vector. + * This simplifies freeing partially initialized data structures, + * such as adjacency lists, when an error occurs mid-initialization. */ + v->stor_begin = IGRAPH_CALLOC(alloc_size, BASE); + IGRAPH_CHECK_OOM(v->stor_begin, "Insufficient memory to initialize vector."); + v->stor_end = v->stor_begin + alloc_size; + v->end = v->stor_begin + size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_view + * \brief Handle a regular C array as a \type igraph_vector_t. + * + * This function lets you treat an existing C array as an \ref igraph_vector_t. + * + * + * Since this function creates a view into an existing array, you must \em not + * destroy the \c igraph_vector_t object when you are done with it. Similarly, + * you must \em not call any function on it that may attempt to modify the size + * of the vector. Modifying an element in the vector will modify the underlying + * array as the two share the same memory block. + * + * + * Typical usage pattern: + * + * + * igraph_real_t array[] = { 1.0, 1.5, 2.0 }; + * const igraph_vector_t v = igraph_vector_view(array, sizeof(array) / sizeof(array[0])); + * printf("The sum of vector elements is %g.\n", igraph_vector_sum(&v)); + * + * + * \param data The raw array that the vector provides a view into. + * \param length The length of the C array. + * \return The vector object providing the view into the array. + * + * Time complexity: O(1) + */ + +TYPE(igraph_vector) FUNCTION(igraph_vector, view)( + const BASE *data, igraph_int_t length) { + + static const BASE dummy = ZERO; + TYPE(igraph_vector) v; + + /* When the length is zero, we allow 'data' to be NULL. + * An igraph_vector_t may never contain a NULL pointer, + * thus we use a pointer to a dummy variable in this case. */ + if (length == 0) { + data = &dummy; + } else { + IGRAPH_ASSERT(data != NULL); + } + + v.stor_begin = (BASE*)data; + v.stor_end = (BASE*)data + length; + v.end = v.stor_end; + return v; +} + +#ifndef BASE_COMPLEX + +/** + * \ingroup vector + * \function igraph_vector_init_real + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * Because of how C and the C library handles variable length argument + * lists, it is required that you supply real constants to this + * function. This means that + * \verbatim igraph_vector_t v; + * igraph_vector_init_real(&v, 5, 1,2,3,4,5); \endverbatim + * is an error at runtime and the results are undefined. This is + * the proper way: + * \verbatim igraph_vector_t v; + * igraph_vector_init_real(&v, 5, 1.0,2.0,3.0,4.0,5.0); \endverbatim + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param no Positive integer, the number of \type igraph_real_t + * parameters to follow. + * \param ... The elements of the vector. + * \return Error code, this can be \c IGRAPH_ENOMEM + * if there isn't enough memory to allocate the vector. + * + * \sa \ref igraph_vector_init_real_end(), \ref igraph_vector_init_int() for similar + * functions. + * + * Time complexity: depends on the time required to allocate memory, + * but at least O(n), the number of + * elements in the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, init_real)(TYPE(igraph_vector) *v, int no, ...) { + int i = 0; + va_list ap; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, no)); + + va_start(ap, no); + for (i = 0; i < no; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, double); + } + va_end(ap); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_init_real_end + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * This constructor is similar to \ref igraph_vector_init_real(), the only + * difference is that instead of giving the number of elements in the + * vector, a special marker element follows the last real vector + * element. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param endmark This element will signal the end of the vector. It + * will \em not be part of the vector. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory. + * + * \sa \ref igraph_vector_init_real() and \ref igraph_vector_init_int_end() for + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +igraph_error_t FUNCTION(igraph_vector, init_real_end)(TYPE(igraph_vector) *v, + double endmark, ...) { + int i = 0, n = 0; + va_list ap; + + va_start(ap, endmark); + while (1) { + BASE num = (BASE) va_arg(ap, double); + if (num == endmark) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, n)); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), v); + + va_start(ap, endmark); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, double); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_init_int + * \brief Create an \type igraph_vector_t containing the parameters. + * + * + * This function is similar to \ref igraph_vector_init_real(), but it expects + * \type int parameters. It is important that all parameters + * should be of this type, otherwise the result of the function call + * is undefined. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param no The number of \type int parameters to follow. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there is + * not enough memory. + * \sa \ref igraph_vector_init_real() and \ref igraph_vector_init_int_end(), these are + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +igraph_error_t FUNCTION(igraph_vector, init_int)(TYPE(igraph_vector) *v, int no, ...) { + int i = 0; + va_list ap; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, no)); + + va_start(ap, no); + for (i = 0; i < no; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, int); + } + va_end(ap); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_init_int_end + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * This constructor is similar to \ref igraph_vector_init_int(), the only + * difference is that instead of giving the number of elements in the + * vector, a special marker element follows the last real vector + * element. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param endmark This element will signal the end of the vector. It + * will \em not be part of the vector. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory. + * + * \sa \ref igraph_vector_init_int() and \ref igraph_vector_init_real_end() for + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +igraph_error_t FUNCTION(igraph_vector, init_int_end)(TYPE(igraph_vector) *v, int endmark, ...) { + int i = 0, n = 0; + va_list ap; + + va_start(ap, endmark); + while (1) { + int num = va_arg(ap, int); + if (num == endmark) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, n)); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), v); + + va_start(ap, endmark); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +#endif /* ifndef BASE_COMPLEX */ + +/** + * \ingroup vector + * \function igraph_vector_destroy + * \brief Destroys a vector object. + * + * + * All vectors initialized by \ref igraph_vector_init() should be properly + * destroyed by this function. A destroyed vector needs to be + * reinitialized by \ref igraph_vector_init(), \ref igraph_vector_init_array() or + * another constructor. + * \param v Pointer to the (previously initialized) vector object to + * destroy. + * + * Time complexity: operating system dependent. + */ + +void FUNCTION(igraph_vector, destroy)(TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + /* vector_init() will leave stor_begin set to NULL when it fails. + * We handle these cases gracefully. */ + if (v->stor_begin != NULL) { + IGRAPH_FREE(v->stor_begin); + v->stor_begin = NULL; + } +} + +/** + * \ingroup vector + * \function igraph_vector_capacity + * \brief Returns the allocated capacity of the vector. + * + * Note that this might be different from the size of the vector (as + * queried by \ref igraph_vector_size()), and specifies how many elements + * the vector can hold, without reallocation. + * + * \param v Pointer to the (previously initialized) vector object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_vector_size(). + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_vector, capacity)(const TYPE(igraph_vector) *v) { + return v->stor_end - v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_reserve + * \brief Reserves memory for a vector. + * + * + * \a igraph vectors are flexible, they can grow and + * shrink. Growing + * however occasionally needs the data in the vector to be copied. + * In order to avoid this, you can call this function to reserve space for + * future growth of the vector. + * + * + * Note that this function does \em not change the size of the + * vector. Let us see a small example to clarify things: if you + * reserve space for 100 elements and the size of your + * vector was (and still is) 60, then you can surely add additional 40 + * elements to your vector before it will be copied. + * \param v The vector object. + * \param capacity The new \em allocated size of the vector. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n + * is the new allocated size of the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, reserve)(TYPE(igraph_vector) *v, igraph_int_t capacity) { + igraph_int_t current_capacity; + BASE *tmp; + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + current_capacity = FUNCTION(igraph_vector, capacity)(v); + + if (capacity <= current_capacity) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(v->stor_begin, capacity, BASE); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for vector."); + + v->end = tmp + (v->end - v->stor_begin); + v->stor_begin = tmp; + v->stor_end = v->stor_begin + capacity; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_empty + * \brief Decides whether the size of the vector is zero. + * + * \param v The vector object. + * \return True if the size of the vector is zero and false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_vector, empty)(const TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_begin == v->end; +} + +/** + * \ingroup vector + * \function igraph_vector_size + * \brief The size of the vector. + * + * Returns the number of elements stored in the vector. + * + * \param v The vector object + * \return The size of the vector. + * + * Time complexity: O(1). + */ + +igraph_int_t FUNCTION(igraph_vector, size)(const TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->end - v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_clear + * \brief Removes all elements from a vector. + * + * + * This function simply sets the size of the vector to zero, it does + * not free any allocated memory. For that you have to call + * \ref igraph_vector_destroy(). + * \param v The vector object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_vector, clear)(TYPE(igraph_vector)* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + v->end = v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_push_back + * \brief Appends one element to a vector. + * + * This function resizes the vector to be one element longer and + * sets the very last element in the vector to \p e. + * + * \param v The vector object. + * \param e The element to append to the vector. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: operating system dependent. What is important is that + * a sequence of n + * subsequent calls to this function has time complexity + * O(n), even if there + * hadn't been any space reserved for the new elements by + * \ref igraph_vector_reserve(). This is implemented by a trick similar to the C++ + * \type vector class: each time more memory is allocated for a + * vector, the size of the additionally allocated memory is the same + * as the vector's current length. (We assume here that the time + * complexity of memory allocation is at most linear.) + */ + +igraph_error_t FUNCTION(igraph_vector, push_back)(TYPE(igraph_vector) *v, BASE e) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + if (v->stor_end == v->end) { + /* full, allocate more storage */ + igraph_int_t old_size = FUNCTION(igraph_vector, size)(v); + igraph_int_t new_size = old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX; + if (old_size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot push to vector, already at maximum size.", IGRAPH_EOVERFLOW); + } + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(igraph_vector, reserve)(v, new_size)); + } + + *(v->end) = e; + v->end += 1; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_insert + * \brief Inserts a single element into a vector. + * + * Note that this function does not do range checking. Insertion will shift the + * elements from the position given to the end of the vector one position to the + * right, and the new element will be inserted in the empty space created at + * the given position. The size of the vector will increase by one. + * + * \param v The vector object. + * \param pos The position where the new element is to be inserted. + * \param value The new element to be inserted. + */ +igraph_error_t FUNCTION(igraph_vector, insert)( + TYPE(igraph_vector) *v, igraph_int_t pos, BASE value) { + igraph_int_t size = FUNCTION(igraph_vector, size)(v); + IGRAPH_ASSERT(0 <= pos && pos <= size); + if (size == IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Cannot insert to vector, already at maximum size.", IGRAPH_EOVERFLOW); + } + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(v, size + 1)); + if (pos < size) { + memmove(v->stor_begin + pos + 1, v->stor_begin + pos, + sizeof(BASE) * (size - pos)); + } + v->stor_begin[pos] = value; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \section igraph_vector_accessing_elements Accessing elements + * + * The simplest and most performant way to access an element of a vector is + * to use the \ref VECTOR macro. This macro can be used both for querying and setting + * \type igraph_vector_t elements. If you need a function, \ref + * igraph_vector_get() queries and \ref igraph_vector_set() sets an element of a + * vector. \ref igraph_vector_get_ptr() returns the address of an element. + * + * \ref igraph_vector_tail() returns the last element of a non-empty + * vector. There is no igraph_vector_head() function + * however, as it is easy to write VECTOR(v)[0] + * instead. + */ + +/** + * \ingroup vector + * \function igraph_vector_get + * \brief Access an element of a vector. + * + * Unless you need a function, consider using the \ref VECTOR + * macro instead for better performance. + * + * \param v The \type igraph_vector_t object. + * \param pos The position of the element, the index of the first + * element is zero. + * \return The desired element. + * \sa \ref igraph_vector_get_ptr() and the \ref VECTOR macro. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, get)(const TYPE(igraph_vector) *v, igraph_int_t pos) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return * (v->stor_begin + pos); +} + +/** + * \ingroup vector + * \function igraph_vector_get_ptr + * \brief Get the address of an element of a vector. + * + * Unless you need a function, consider using the \ref VECTOR + * macro instead for better performance. + * + * \param v The \type igraph_vector_t object. + * \param pos The position of the element, the position of the first + * element is zero. + * \return Pointer to the desired element. + * \sa \ref igraph_vector_get() and the \ref VECTOR macro. + * + * Time complexity: O(1). + */ + +BASE* FUNCTION(igraph_vector, get_ptr)(const TYPE(igraph_vector) *v, igraph_int_t pos) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_begin + pos; +} + +/** + * \ingroup vector + * \function igraph_vector_set + * \brief Assignment to an element of a vector. + * + * Unless you need a function, consider using the \ref VECTOR + * macro instead for better performance. + * + * \param v The \type igraph_vector_t element. + * \param pos Position of the element to set. + * \param value New value of the element. + * \sa \ref igraph_vector_get(). + */ + +void FUNCTION(igraph_vector, set)(TYPE(igraph_vector) *v, igraph_int_t pos, BASE value) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + *(v->stor_begin + pos) = value; +} + +/** + * \ingroup vector + * \function igraph_vector_null + * \brief Sets each element in the vector to zero. + * + * + * Note that \ref igraph_vector_init() sets the elements to zero as well, so + * it makes no sense to call this function on a just initialized + * vector. Thus if you want to construct a vector of zeros, then you should + * use \ref igraph_vector_init(). + * + * \param v The vector object. + * + * Time complexity: O(n), the size of + * the vector. + */ + +void FUNCTION(igraph_vector, null)(TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + if (FUNCTION(igraph_vector, size)(v) > 0) { + memset(v->stor_begin, 0, sizeof(BASE) * FUNCTION(igraph_vector, size)(v)); + } +} + +/** + * \function igraph_vector_fill + * \brief Fill a vector with a constant element. + * + * Sets each element of the vector to the supplied constant. + * + * \param vector The vector to work on. + * \param e The element to fill with. + * + * Time complexity: O(n), the size of the vector. + */ + +void FUNCTION(igraph_vector, fill)(TYPE(igraph_vector) *v, BASE e) { + BASE *ptr; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + /* Optimize the commonly used integer case when filling with 0 + * or -1, which are an all-0 and all-1 bit pattern that the + * compiler can generate higher efficiency code for. */ +#if defined(BASE_INT) + if (e == 0) { + FUNCTION(igraph_vector, null)(v); + return; + } + if (e == -1) { + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + *ptr = -1; + } + return; + } +#endif + + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + *ptr = e; + } +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_range + * \brief Updates a vector to store a range. + * + * Sets the elements of the vector to contain the numbers \p start, \p start+1, + * ..., \p end-1. Note that the range is closed from the left and open from the + * right, according to C conventions. The vector will be resized to fit the range. + * + * \param v The vector to update. + * \param start The lower limit in the range (inclusive). + * \param end The upper limit in the range (exclusive). + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory. + * + * Time complexity: O(n), the number of elements in the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, range)(TYPE(igraph_vector) *v, BASE start, BASE end) { + BASE *p; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(v, (end - start))); + + for (p = v->stor_begin; p < v->end; p++) { + *p = start; + start = start + ONE; + } + + return IGRAPH_SUCCESS; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_tail + * \brief Returns the last element in a vector. + * + * It is an error to call this function on an empty vector, the result + * is undefined. + * + * \param v The vector object. + * \return The last element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, tail)(const TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return *((v->end) - 1); +} + +/** + * \ingroup vector + * \function igraph_vector_pop_back + * \brief Removes and returns the last element of a vector. + * + * It is an error to call this function with an empty vector. + * + * \param v The vector object. + * \return The removed last element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, pop_back)(TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(v->end != NULL); + IGRAPH_ASSERT(v->end != v->stor_begin); + + (v->end)--; + + return *(v->end); +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_permute + * \brief Permutes the elements of a vector in place according to an index vector. + * + * This function takes a vector \c v and a corresponding index vector \c ind, + * and permutes the elements of \c v such that \c v[ind[i]] is moved to become + * \c v[i] after the function is executed. + * + * + * It is an error to call this function with an index vector that does not + * represent a valid permutation. Each element in the index vector must be + * between 0 and the length of the vector minus one (inclusive), and each such + * element must appear only once. The function does not attempt to validate the + * index vector. + * + * + * The index vector that this function takes is compatible with the index vector + * returned from \ref igraph_vector_sort_ind(); passing in the index vector + * from \ref igraph_vector_sort_ind() will sort the original vector. + * + * + * As a special case, this function allows the index vector to be \em shorter + * than the vector being permuted, in which case the elements whose indices do + * not occur in the index vector will be removed from the vector. + * + * \param v the vector to permute + * \param ind the index vector + * + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: O(n), the size of the vector. + */ +igraph_error_t FUNCTION(igraph_vector, permute)(TYPE(igraph_vector) *v, const igraph_vector_int_t *index) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(index != NULL); + IGRAPH_ASSERT(index->stor_begin != NULL); + IGRAPH_ASSERT(FUNCTION(igraph_vector, size)(v) >= igraph_vector_int_size(index)); + + TYPE(igraph_vector) v_copy; + BASE *v_ptr; + igraph_int_t *ind_ptr; + + /* There is a more space-efficient algorithm that needs O(1) space only, + * but it messes up the index vector, which we don't want */ + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(&v_copy, igraph_vector_int_size(index))); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), &v_copy); + + for ( + v_ptr = v_copy.stor_begin, ind_ptr = index->stor_begin; + ind_ptr < index->end; + v_ptr++, ind_ptr++ + ) { + *v_ptr = VECTOR(*v)[*ind_ptr]; + } + + IGRAPH_CHECK(FUNCTION(igraph_vector, update)(v, &v_copy)); + + FUNCTION(igraph_vector, destroy)(&v_copy); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_sort_cmp + * \brief Internal comparison function of vector elements, used by \ref igraph_vector_sort(). + */ + +static int FUNCTION(igraph_vector, sort_cmp)(const void *a, const void *b) { + const BASE *da = (const BASE *) a; + const BASE *db = (const BASE *) b; + + return (*da > *db) - (*da < *db); +} + +/** + * \ingroup vector + * \function igraph_vector_reverse_sort_cmp + * \brief Internal comparison function of vector elements, used by \ref igraph_vector_reverse_sort(). + */ + +static int FUNCTION(igraph_vector, reverse_sort_cmp)(const void *a, const void *b) { + const BASE *da = (const BASE *) a; + const BASE *db = (const BASE *) b; + + return (*da < *db) - (*da > *db); +} + +/** + * \ingroup vector + * \function igraph_vector_sort + * \brief Sorts the elements of the vector into ascending order. + * + * + * If the vector contains any NaN values, the resulting ordering of + * NaN values is undefined and may appear anywhere in the vector. + * \param v Pointer to an initialized vector object. + * + * Time complexity: + * O(n log n) for n elements. + */ + +void FUNCTION(igraph_vector, sort)(TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + igraph_qsort(v->stor_begin, FUNCTION(igraph_vector, size)(v), + sizeof(BASE), FUNCTION(igraph_vector, sort_cmp)); +} + +/** + * \ingroup vector + * \function igraph_vector_reverse_sort + * \brief Sorts the elements of the vector into descending order. + * + * + * If the vector contains any NaN values, the resulting ordering of + * NaN values is undefined and may appear anywhere in the vector. + * \param v Pointer to an initialized vector object. + * + * Time complexity: + * O(n log n) for n elements. + */ + +void FUNCTION(igraph_vector, reverse_sort)(TYPE(igraph_vector) *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + igraph_qsort(v->stor_begin, FUNCTION(igraph_vector, size)(v), + sizeof(BASE), FUNCTION(igraph_vector, reverse_sort_cmp)); +} + +/** + * Ascending comparison function passed to qsort from igraph_vector_sort_ind + */ +static int FUNCTION(igraph_vector, i_sort_ind_cmp_asc)(const void *p1, const void *p2) { + BASE **pa = (BASE **) p1; + BASE **pb = (BASE **) p2; + if ( **pa < **pb ) { + return -1; + } + if ( **pa > **pb) { + return 1; + } + return 0; +} + +/** + * Descending comparison function passed to qsort from igraph_vector_sort_ind + */ +static int FUNCTION(igraph_vector, i_sort_ind_cmp_desc)(const void *p1, const void *p2) { + BASE **pa = (BASE **) p1; + BASE **pb = (BASE **) p2; + if ( **pa < **pb ) { + return 1; + } + if ( **pa > **pb) { + return -1; + } + return 0; +} + +/** + * \function igraph_vector_sort_ind + * \brief Returns a permutation of indices that sorts a vector. + * + * Takes an unsorted array \c v as input and computes an array of indices + * \p inds such that v[ inds[i] ], with \c i increasing from 0, + * is an ordered array (either ascending or descending, depending on + * \p order). The order of indices for identical elements is not + * defined. If the vector contains any NaN values, the ordering of + * NaN values is undefined. + * + * \param v the array to be sorted + * \param inds the output array of indices. This must be initialized, + * but will be resized + * \param order whether the output array should be sorted in ascending + * or descending order. Use \c IGRAPH_ASCENDING for ascending and + * \c IGRAPH_DESCENDING for descending order. + * \return Error code. + * + * This routine uses igraph's built-in qsort routine. + * Algorithm: 1) create an array of pointers to the elements of v. 2) + * Pass this array to qsort. 3) after sorting the difference between + * the pointer value and the first pointer value gives its original + * position in the array. Use this to set the values of inds. + */ + +igraph_error_t FUNCTION(igraph_vector, sort_ind)( + const TYPE(igraph_vector) *v, + igraph_vector_int_t *inds, + igraph_order_t order) { + + igraph_int_t i, n = FUNCTION(igraph_vector, size)(v); + BASE **vind, *first; + IGRAPH_CHECK(igraph_vector_int_resize(inds, n)); + if (n == 0) { + return IGRAPH_SUCCESS; + } + vind = IGRAPH_CALLOC(n, BASE*); + IGRAPH_CHECK_OOM(vind, "Insufficient memory to sort vector."); + for (i = 0; i < n; i++) { + vind[i] = &VECTOR(*v)[i]; + } + first = vind[0]; + if (order == IGRAPH_ASCENDING) { + igraph_qsort(vind, n, sizeof(BASE*), FUNCTION(igraph_vector, i_sort_ind_cmp_asc)); + } else { + igraph_qsort(vind, n, sizeof(BASE*), FUNCTION(igraph_vector, i_sort_ind_cmp_desc)); + } + for (i = 0; i < n; i++) { + VECTOR(*inds)[i] = vind[i] - first; + } + IGRAPH_FREE(vind); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_lex_cmp + * \brief Lexicographical comparison of two vectors (type-safe variant). + * + * If the elements of two vectors match but one is shorter, the shorter + * one comes first. Thus {1, 3} comes after {1, 2, 3}, but before {1, 3, 4}. + * + * + * This function is typically used together with \ref igraph_vector_list_sort(). + * + * \param lhs Pointer to the first vector. + * \param rhs Pointer to the second vector. + * \return -1 if \p lhs is lexicographically smaller, + * 0 if \p lhs and \p rhs are equal, else 1. + * \sa \ref igraph_vector_lex_cmp_untyped() for an untyped variant of this + * function, or \ref igraph_vector_colex_cmp() to compare vectors starting from + * the last element. + * + * Time complexity: O(n), the number of elements in the smaller vector. + * + * \example examples/simple/igraph_vector_int_list_sort.c + */ +int FUNCTION(igraph_vector, lex_cmp)( + const TYPE(igraph_vector) *lhs, const TYPE(igraph_vector) *rhs +) { + igraph_int_t i, sa, sb; + const TYPE(igraph_vector) *a = lhs, *b = rhs; + + sa = FUNCTION(igraph_vector, size)(a); + sb = FUNCTION(igraph_vector, size)(b); + + for (i = 0; i < sa; i++) { + if (i >= sb) { + /* b is shorter, and equal to the first part of a */ + return 1; + } + if (VECTOR(*a)[i] < VECTOR(*b)[i]) { + return -1; + } + if (VECTOR(*a)[i] > VECTOR(*b)[i]) { + return 1; + } + } + if (i == sb) { + return 0; + } + /* a is shorter, and equal to the first part of b */ + return -1; +} + +/** + * \function igraph_vector_lex_cmp_untyped + * \brief Lexicographical comparison of two vectors (non-type-safe). + * + * + * If the elements of two vectors match but one is shorter, the shorter + * one comes first. Thus {1, 3} comes after {1, 2, 3}, but before {1, 3, 4}. + * + * + * This function is typically used together with \ref igraph_vector_ptr_sort(). + * + * \param lhs Pointer to a pointer to the first vector (interpreted as an igraph_vector_t **). + * \param rhs Pointer to a pointer to the second vector (interpreted as an igraph_vector_t **). + * \return -1 if \p lhs is lexicographically smaller, + * 0 if \p lhs and \p rhs are equal, else 1. + * \sa \ref igraph_vector_lex_cmp() for a type-safe variant of this + * function, or \ref igraph_vector_colex_cmp_untyped() to compare vectors starting from + * the last element. + * + * Time complexity: O(n), the number of elements in the smaller vector. + */ +int FUNCTION(igraph_vector, lex_cmp_untyped)(const void *lhs, const void *rhs) { + const TYPE(igraph_vector) *a = * (TYPE(igraph_vector) **) lhs; + const TYPE(igraph_vector) *b = * (TYPE(igraph_vector) **) rhs; + return FUNCTION(igraph_vector, lex_cmp)(a, b); +} + +/** + * \function igraph_vector_colex_cmp + * \brief Colexicographical comparison of two vectors. + * + * This comparison starts from the last element of both vectors and + * moves backward. If the elements of two vectors match but one is + * shorter, the shorter one comes first. Thus {1, 2} comes after {3, 2, 1}, + * but before {0, 1, 2}. + * + * + * This function is typically used together with \ref igraph_vector_list_sort(). + * + * \param lhs Pointer to a pointer to the first vector. + * \param rhs Pointer to a pointer to the second vector. + * \return -1 if \p lhs in reverse order is + * lexicographically smaller than the reverse of \p rhs, + * 0 if \p lhs and \p rhs are equal, else 1. + * \sa \ref igraph_vector_colex_cmp_untyped() for an untyped variant of this + * function, or \ref igraph_vector_lex_cmp() to compare vectors starting from + * the first element. + * + * Time complexity: O(n), the number of elements in the smaller vector. + * + * \example examples/simple/igraph_vector_int_list_sort.c + */ +int FUNCTION(igraph_vector, colex_cmp)( + const TYPE(igraph_vector) *lhs, const TYPE(igraph_vector) *rhs +) { + igraph_int_t i, sa, sb, rai, rbi; + const TYPE(igraph_vector) *a = lhs, *b = rhs; + + sa = FUNCTION(igraph_vector, size)(a); + sb = FUNCTION(igraph_vector, size)(b); + + for (i = 0; i < sa; i++) { + if (i >= sb) { + /* b is shorter, and equal to the last part of a */ + return 1; + } + /* use reversed indexes */ + rai = sa - i - 1; + rbi = sb - i - 1; + if (VECTOR(*a)[rai] < VECTOR(*b)[rbi]) { + return -1; + } + if (VECTOR(*a)[rai] > VECTOR(*b)[rbi]) { + return 1; + } + } + if (i == sb) { + return 0; + } + /* a is shorter, and equal to the last part of b */ + return -1; +} + +/** + * \function igraph_vector_colex_cmp_untyped + * \brief Colexicographical comparison of two vectors. + * + * + * This comparison starts from the last element of both vectors and + * moves backward. If the elements of two vectors match but one is + * shorter, the shorter one comes first. Thus {1, 2} comes after {3, 2, 1}, + * but before {0, 1, 2}. + * + * + * This function is typically used together with \ref igraph_vector_ptr_sort(). + * + * \param lhs Pointer to a pointer to the first vector (interpreted as an igraph_vector_t **). + * \param rhs Pointer to a pointer to the second vector (interpreted as an igraph_vector_t **). + * \return -1 if \p lhs in reverse order is + * lexicographically smaller than the reverse of \p rhs, + * 0 if \p lhs and \p rhs are equal, else 1. + * \sa \ref igraph_vector_colex_cmp() for a type-safe variant of this + * function, \ref igraph_vector_lex_cmp_untyped() to compare vectors starting from + * the first element. + * + * Time complexity: O(n), the number of elements in the smaller vector. + */ +int FUNCTION(igraph_vector, colex_cmp_untyped)(const void *lhs, const void *rhs) { + const TYPE(igraph_vector) *a = * (TYPE(igraph_vector) **) lhs; + const TYPE(igraph_vector) *b = * (TYPE(igraph_vector) **) rhs; + return FUNCTION(igraph_vector, colex_cmp)(a, b); +} + +#endif /*NOTORDERED*/ + +/** + * \ingroup vector + * \function igraph_vector_resize + * \brief Resize the vector. + * + * + * Note that this function does not free any memory, just sets the + * size of the vector to the given one. It can on the other hand + * allocate more memory if the new size is larger than the previous + * one. In this case the newly appeared elements in the vector are + * \em not set to zero, they are uninitialized. + * \param v The vector object + * \param new_size The new size of the vector. + * \return Error code, + * \c IGRAPH_ENOMEM if there is not enough + * memory. Note that this function \em never returns an error + * if the vector is made smaller. + * \sa \ref igraph_vector_reserve() for allocating memory for future + * extensions of a vector. \ref igraph_vector_resize_min() for + * deallocating the unnneded memory for a vector. + * + * Time complexity: O(1) if the new + * size is smaller, operating system dependent if it is larger. In the + * latter case it is usually around + * O(n), + * n is the new size of the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, resize)(TYPE(igraph_vector)* v, igraph_int_t new_size) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_CHECK(FUNCTION(igraph_vector, reserve)(v, new_size)); + v->end = v->stor_begin + new_size; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_resize_min + * \brief Deallocate the unused memory of a vector. + * + * This function attempts to deallocate the unused reserved storage + * of a vector. If it succeeds, \ref igraph_vector_size() and + * \ref igraph_vector_capacity() will be the same. The data in the + * vector is always preserved, even if deallocation is not successful. + * + * \param v Pointer to an initialized vector. + * + * \sa \ref igraph_vector_resize(), \ref igraph_vector_reserve(). + * + * Time complexity: operating system dependent, O(n) at worst. + */ + +void FUNCTION(igraph_vector, resize_min)(TYPE(igraph_vector) *v) { + igraph_int_t size; + BASE *tmp; + if (v->stor_end == v->end) { + return; + } + + size = (v->end - v->stor_begin); + tmp = IGRAPH_REALLOC(v->stor_begin, size, BASE); + + if (tmp != NULL) { + v->stor_begin = tmp; + v->stor_end = v->end = v->stor_begin + size; + } +} + +#ifndef NOTORDERED + +/* We will use x != x for NaN checks below and Clang does not like it unless + * we disable a warning */ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-compare" +#endif + +/** + * \ingroup vector + * \function igraph_vector_max + * \brief Largest element of a vector. + * + * The vector must not be empty. + * + * \param v The vector object. + * \return The maximum element of \p v, or NaN if any element is NaN. + * + * Time complexity: O(n), the number of elements. + */ +BASE FUNCTION(igraph_vector, max)(const TYPE(igraph_vector) *v) { + BASE max; + BASE *ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + max = *(v->stor_begin); +#if defined(BASE_IGRAPH_REAL) + if (isnan(max)) { return max; }; /* Result is NaN */ +#endif + ptr = v->stor_begin + 1; + while (ptr < v->end) { + if ((*ptr) > max) { + max = *ptr; + } +#if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) + return *ptr; /* Result is NaN */ +#endif + ptr++; + } + return max; +} + + +/** + * \ingroup vector + * \function igraph_vector_which_max + * \brief Gives the index of the maximum element of the vector. + * + * The vector must not be empty. If the largest + * element is not unique, then the index of the first is returned. + * If the vector contains NaN values, the index of the first NaN value + * is returned. + * + * \param v The vector object. + * \return The index of the first maximum element. + * + * Time complexity: O(n), n is the size of the vector. + */ +igraph_int_t FUNCTION(igraph_vector, which_max)(const TYPE(igraph_vector) *v) { + BASE *max; + BASE *ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + max = ptr = v->stor_begin; +#if defined(BASE_IGRAPH_REAL) + if (isnan(*ptr)) { return ptr - v->stor_begin; } /* Result is NaN */ +#endif + ptr++; + while (ptr < v->end) { + if (*ptr > *max) { + max = ptr; + } +#if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) { + return ptr - v->stor_begin; /* Result is NaN */ + } +#endif + ptr++; + } + return max - v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_min + * \brief Smallest element of a vector. + * + * The vector must not be empty. + * + * \param v The input vector. + * \return The smallest element of \p v, or NaN if any element is NaN. + * + * Time complexity: O(n), the number of elements. + */ + +BASE FUNCTION(igraph_vector, min)(const TYPE(igraph_vector) *v) { + BASE min; + BASE *ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + min = *(v->stor_begin); +#if defined(BASE_IGRAPH_REAL) + if (isnan(min)) { return min; }; /* Result is NaN */ +#endif + ptr = v->stor_begin + 1; + while (ptr < v->end) { + if ((*ptr) < min) { + min = *ptr; + } +#if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) { + return *ptr; /* Result is NaN */ + } +#endif + ptr++; + } + return min; +} + +/** + * \ingroup vector + * \function igraph_vector_which_min + * \brief Index of the smallest element. + * + * The vector must not be empty. If the smallest element + * is not unique, then the index of the first is returned. If the vector + * contains NaN values, the index of the first NaN value is returned. + * + * \param v The input vector. + * \return Index of the smallest element. + * + * Time complexity: O(n), the number of elements. + */ +igraph_int_t FUNCTION(igraph_vector, which_min)(const TYPE(igraph_vector)* v) { + BASE *min; + BASE *ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + min = ptr = v->stor_begin; +#if defined(BASE_IGRAPH_REAL) + if (isnan(*ptr)) { return ptr - v->stor_begin; } /* Result is NaN */ +#endif + ptr++; + while (ptr < v->end) { + if (*ptr < *min) { + min = ptr; + } +#if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) { + return ptr - v->stor_begin; /* Result is NaN */ + } +#endif + ptr++; + } + return min - v->stor_begin; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +/** + * \ingroup vector + * \function igraph_vector_init_array + * \brief Initializes a vector from an ordinary C array (constructor). + * + * \param v Pointer to an uninitialized vector object. + * \param data A regular C array. + * \param length The length of the C array. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system specific, usually + * O(\p length). + */ + +igraph_error_t FUNCTION(igraph_vector, init_array)( + TYPE(igraph_vector) *v, const BASE *data, igraph_int_t length) { + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, length)); + + /* Note: memcpy() behaviour is undefined if data==NULL, even if length==0. */ + if (length > 0) { + memcpy(v->stor_begin, data, length * sizeof(BASE)); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_copy_to + * \brief Copies the contents of a vector to a C array. + * + * + * The C array should have sufficient length. + * \param v The vector object. + * \param to The C array. + * + * Time complexity: O(n), + * n is the size of the vector. + */ + +void FUNCTION(igraph_vector, copy_to)(const TYPE(igraph_vector) *v, BASE *to) { + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + if (v->end != v->stor_begin) { + memcpy(to, v->stor_begin, sizeof(BASE) * (v->end - v->stor_begin)); + } +} + +/** + * \ingroup vector + * \function igraph_vector_init_copy + * \brief Initializes a vector from another vector object (constructor). + * + * + * The contents of the existing vector object will be copied to + * the new one. + * \param to Pointer to a not yet initialized vector object. + * \param from The original vector object to copy. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually + * O(n), + * n is the size of the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, init_copy)( + TYPE(igraph_vector) *to, const TYPE(igraph_vector) *from +) { + igraph_int_t from_size; + + IGRAPH_ASSERT(from != NULL); + IGRAPH_ASSERT(from->stor_begin != NULL); + + from_size = FUNCTION(igraph_vector, size)(from); + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(to, from_size)); + + memcpy(to->stor_begin, from->stor_begin, from_size * sizeof(BASE)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vector + * \function igraph_vector_sum + * \brief Calculates the sum of the elements in the vector. + * + * For the empty vector 0 is returned. + * + * \param v The vector object. + * \return The sum of the elements. + * + * Time complexity: O(n), the size of + * the vector. + */ + +BASE FUNCTION(igraph_vector, sum)(const TYPE(igraph_vector) *v) { + BASE res = ZERO; + const BASE *p; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef SUM + SUM(res, res, *p); +#else + res += *p; +#endif + } + return res; +} + +igraph_real_t FUNCTION(igraph_vector, sumsq)(const TYPE(igraph_vector) *v) { + igraph_real_t res = 0.0; + const BASE *p; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef SQ + res += SQ(*p); +#else + res += (*p) * (*p); +#endif + } + return res; +} + +/** + * \ingroup vector + * \function igraph_vector_prod + * \brief Calculates the product of the elements in the vector. + * + * + * For the empty vector one (1) is returned. + * \param v The vector object. + * \return The product of the elements. + * + * Time complexity: O(n), the size of + * the vector. + */ + +BASE FUNCTION(igraph_vector, prod)(const TYPE(igraph_vector) *v) { + BASE res = ONE; + const BASE *p; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef PROD + PROD(res, res, *p); +#else + res *= *p; +#endif + } + return res; +} + +/** + * \ingroup vector + * \function igraph_vector_cumsum + * \brief Calculates the cumulative sum of the elements in the vector. + * + * + * \param to An initialized vector object that will store the cumulative + * sums. Element i of this vector will store the sum of the elements + * of the 'from' vector, up to and including element i. + * \param from The input vector. + * \return Error code. + * + * Time complexity: O(n), the size of the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, cumsum)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + BASE res = ZERO; + const BASE *p; + BASE *p2; + + IGRAPH_ASSERT(from != NULL); + IGRAPH_ASSERT(from->stor_begin != NULL); + IGRAPH_ASSERT(to != NULL); + IGRAPH_ASSERT(to->stor_begin != NULL); + + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(to, FUNCTION(igraph_vector, size)(from))); + + for (p = from->stor_begin, p2 = to->stor_begin; p < from->end; p++, p2++) { +#ifdef SUM + SUM(res, res, *p); +#else + res += *p; +#endif + *p2 = res; + } + + return IGRAPH_SUCCESS; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_init_range + * \brief Initializes a vector with a range. + * + * + * The vector will contain the numbers \p start, \p start+1, ..., \p end-1. Note + * that the range is closed from the left and open from the right, according to + * C conventions. + * + * \param v Pointer to an uninitialized vector object. + * \param start The lower limit in the range (inclusive). + * \param end The upper limit in the range (exclusive). + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory. + * + * Time complexity: O(n), the number of elements in the vector. + */ + +igraph_error_t FUNCTION(igraph_vector, init_range)(TYPE(igraph_vector) *v, BASE start, BASE end) { + BASE *p; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, (end - start))); + + for (p = v->stor_begin; p < v->end; p++) { + *p = start; + start = start + ONE; + } + + return IGRAPH_SUCCESS; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_remove_section + * \brief Deletes a section from a vector. + * + * + * \param v The vector object. + * \param from The position of the first element to remove. + * \param to The position of the first element \em not to remove. + * + * Time complexity: O(n-from), + * n is the number of elements in the + * vector. + */ + +void FUNCTION(igraph_vector, remove_section)( + TYPE(igraph_vector) *v, igraph_int_t from, igraph_int_t to) { + igraph_int_t size = FUNCTION(igraph_vector, size)(v); + + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + if (from < 0) { + from = 0; + } + + if (to > size) { + to = size; + } + + if (to > from) { + memmove(v->stor_begin + from, v->stor_begin + to, + sizeof(BASE) * (v->end - v->stor_begin - to)); + v->end -= (to - from); + } +} + +/** + * \ingroup vector + * \function igraph_vector_remove + * \brief Removes a single element from a vector. + * + * Note that this function does not do range checking. + * \param v The vector object. + * \param elem The position of the element to remove. + * + * Time complexity: O(n-elem), + * n is the number of elements in the + * vector. + */ + +void FUNCTION(igraph_vector, remove)(TYPE(igraph_vector) *v, igraph_int_t elem) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + FUNCTION(igraph_vector, remove_section)(v, elem, elem + 1); +} + +/** + * \ingroup vector + * \function igraph_vector_remove_fast + * \brief Removes a single element from a vector, \em not keeping the order of the remaining elements. + * + * This function removes the element with the given element from the vector by + * swapping it with the last element and then popping it off. You can use this + * function instead of \ref igraph_vector_remove() to gain some speed if the + * order of elements does not matter. + * + * + * Note that this function does not do range checking. + * + * \param v The vector object. + * \param elem The position of the element to remove. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_vector, remove_fast)(TYPE(igraph_vector) *v, igraph_int_t elem) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + igraph_int_t len = FUNCTION(igraph_vector, size)(v); + VECTOR(*v)[elem] = VECTOR(*v)[len - 1]; + FUNCTION(igraph_vector, pop_back)(v); +} + +/** + * \ingroup vector + * \function igraph_vector_move_interval + * \brief Copies a section of a vector. + * + * + * The source and the destination sections are allowed to overlap; this will + * be handled internally by the function. + * \param v The vector object. + * \param begin The position of the first element to move. + * \param end The position of the first element \em not to move. + * \param to The target position. + * \return Error code, the current implementation always returns with + * success. + * + * Time complexity: O(end-begin). + */ + +igraph_error_t FUNCTION(igraph_vector, move_interval)(TYPE(igraph_vector) *v, + igraph_int_t begin, igraph_int_t end, igraph_int_t to) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + memmove(v->stor_begin + to, v->stor_begin + begin, + sizeof(BASE) * (end - begin)); + + return IGRAPH_SUCCESS; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_isininterval + * \brief Checks if all elements of a vector are in the given interval. + * + * \param v The vector object. + * \param low The lower limit of the interval (inclusive). + * \param high The higher limit of the interval (inclusive). + * \return True if the vector is empty or all vector elements + * are in the interval, false otherwise. If any element is NaN, it will + * return false. + * + * Time complexity: O(n), the number of elements in the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, isininterval)(const TYPE(igraph_vector) *v, + BASE low, + BASE high) { + BASE *ptr; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + /* Note that the following is not equivalent to *ptr < low || *ptr > high + * when *ptr is NaN! */ + if (!(*ptr >= low && *ptr <= high)) { + return 0; + } + } + return 1; +} + +/** + * \ingroup vector + * \function igraph_vector_any_smaller + * \brief Checks if any element of a vector is smaller than a limit. + * + * \param v The \type igraph_vector_t object. + * \param limit The limit. + * \return True if the vector contains at least one + * smaller element than \p limit, false otherwise. + * + * Time complexity: O(n), the number of elements in the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, any_smaller)(const TYPE(igraph_vector) *v, + BASE limit) { + BASE *ptr; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + if (*ptr < limit) { + return 1; + } + } + return 0; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_all_e + * \brief Are all elements equal? + * + * Checks element-wise equality of two vectors. For vectors containing floating + * point values, consider using \ref igraph_matrix_all_almost_e(). + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * equal to the corresponding elements in \p rhs. Returns + * false if the lengths of the vectors don't match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_e)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + igraph_int_t i, s; + IGRAPH_ASSERT(lhs != 0); + IGRAPH_ASSERT(rhs != 0); + IGRAPH_ASSERT(lhs->stor_begin != 0); + IGRAPH_ASSERT(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return false; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; +#ifdef EQ + if (!EQ(l, r)) { +#else + if (l != r) { +#endif + return false; + } + } + return true; + } +} + +/** + * \ingroup vector + * \function igraph_vector_is_equal + * \brief Are all elements equal? + * + * This is an alias of \ref igraph_vector_all_e() with a more intuitive name. + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * equal to the corresponding elements in \p rhs. Returns + * false if the lengths of the vectors don't match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, is_equal)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + return FUNCTION(igraph_vector, all_e)(lhs, rhs); +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_all_l + * \brief Are all elements less? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * less than the corresponding elements in \p rhs. Returns false + * if the lengths of the vectors don't match. If any element + * is NaN, it will return false. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_l)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + igraph_int_t i, s; + IGRAPH_ASSERT(lhs != 0); + IGRAPH_ASSERT(rhs != 0); + IGRAPH_ASSERT(lhs->stor_begin != 0); + IGRAPH_ASSERT(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return false; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l >= r) { + return false; + } + } + return true; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_g + * \brief Are all elements greater? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * greater than the corresponding elements in \p rhs. Returns false + * if the lengths of the vectors don't match. If any element + * is NaN, it will return false. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_g)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + + igraph_int_t i, s; + IGRAPH_ASSERT(lhs != 0); + IGRAPH_ASSERT(rhs != 0); + IGRAPH_ASSERT(lhs->stor_begin != 0); + IGRAPH_ASSERT(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return false; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l <= r) { + return false; + } + } + return true; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_le + * \brief Are all elements less or equal? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * less than or equal to the corresponding elements in \p + * rhs. Returns false if the lengths of the vectors don't + * match. If any element is NaN, it will return false. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_le)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + igraph_int_t i, s; + IGRAPH_ASSERT(lhs != 0); + IGRAPH_ASSERT(rhs != 0); + IGRAPH_ASSERT(lhs->stor_begin != 0); + IGRAPH_ASSERT(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return false; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l > r) { + return false; + } + } + return true; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_ge + * \brief Are all elements greater or equal? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return True if the elements in the \p lhs are all + * greater than or equal to the corresponding elements in \p + * rhs. Returns false if the lengths of the vectors don't + * match. If any element is NaN, it will return false. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_ge)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + igraph_int_t i, s; + IGRAPH_ASSERT(lhs != 0); + IGRAPH_ASSERT(rhs != 0); + IGRAPH_ASSERT(lhs->stor_begin != 0); + IGRAPH_ASSERT(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return false; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l < r) { + return false; + } + } + return true; + } +} + +#endif + + +#ifndef NOTORDERED + +static igraph_bool_t FUNCTION(igraph_i_vector, binsearch_slice)(const TYPE(igraph_vector) *v, + BASE what, igraph_int_t *pos, igraph_int_t start, igraph_int_t end); + +/** + * \ingroup vector + * \function igraph_vector_binsearch + * \brief Finds an element by binary searching a sorted vector. + * + * + * It is assumed that the vector is sorted. If the specified element + * (\p what) is not in the vector, then the + * position of where it should be inserted (to keep the vector sorted) + * is returned. If the vector contains any NaN values, the returned + * value is undefined and \p pos may point to any position. + * + * \param v The \type igraph_vector_t object. + * \param what The element to search for. + * \param pos Pointer to an \type igraph_int_t. This is set to the + * position of an instance of \p what in the + * vector if it is present. If \p v does not + * contain \p what then + * \p pos is set to the position to which it + * should be inserted (to keep the vector sorted of course). + * \return True if \p what is found in the vector, false otherwise. + * + * Time complexity: O(log(n)), + * n is the number of elements in + * \p v. + */ + +igraph_bool_t FUNCTION(igraph_vector, binsearch)(const TYPE(igraph_vector) *v, + BASE what, igraph_int_t *pos) { + return FUNCTION(igraph_i_vector, binsearch_slice)(v, what, pos, + 0, FUNCTION(igraph_vector, size)(v)); +} + +/** + * \ingroup vector + * \function igraph_vector_binsearch_slice + * \brief Finds an element by binary searching a sorted slice of a vector. + * + * + * It is assumed that the indicated slice of the vector, from \p start to \p end, + * is sorted. If the specified element (\p what) is not in the slice of the + * vector, then the position of where it should be inserted (to keep the \em slice + * sorted) is returned. Note that this means that the returned index will point + * \em inside the slice (including its endpoints), but will not evaluate values + * \em outside the slice. If the indicated slice contains any NaN values, the + * returned value is undefined and \c pos may point to any position within + * the slice. + * + * \param v The \type igraph_vector_t object. + * \param what The element to search for. + * \param pos Pointer to an \type igraph_int_t. This is set to the position of an + * instance of \p what in the slice of the vector if it is present. If \p + * v does not contain \p what then \p pos is set to the position to which + * it should be inserted (to keep the vector sorted). + * \param start The start position of the slice to search (inclusive). + * \param end The end position of the slice to search (exclusive). + * \return True if \p what is found in the vector, false otherwise. + * + * Time complexity: O(log(n)), + * n is the number of elements in the slice of \p v, i.e. \p end - \p start. + */ + +igraph_bool_t FUNCTION(igraph_vector, binsearch_slice)(const TYPE(igraph_vector) *v, + BASE what, igraph_int_t *pos, igraph_int_t start, igraph_int_t end) { + igraph_int_t left = start; + igraph_int_t right = end - 1; + + if (left < 0) + IGRAPH_ERROR("Invalid start position.", IGRAPH_EINVAL); + + if (right >= FUNCTION(igraph_vector, size)(v)) + IGRAPH_ERROR("Invalid end position.", IGRAPH_EINVAL); + + if (left > right) + IGRAPH_ERROR("Invalid slice, start position must be smaller than end position.", + IGRAPH_EINVAL); + + return FUNCTION(igraph_i_vector, binsearch_slice)(v, what, pos, start, end); +} + +static igraph_bool_t FUNCTION(igraph_i_vector, binsearch_slice)(const TYPE(igraph_vector) *v, + BASE what, igraph_int_t *pos, igraph_int_t start, igraph_int_t end) { + igraph_int_t left = start; + igraph_int_t right = end - 1; + + while (left <= right) { + /* (right + left) / 2 could theoretically overflow for long vectors */ + igraph_int_t middle = left + ((right - left) >> 1); + if (VECTOR(*v)[middle] > what) { + right = middle - 1; + } else if (VECTOR(*v)[middle] < what) { + left = middle + 1; + } else { + if (pos != 0) { + *pos = middle; + } + return true; + } + } + + /* if we are here, the element was not found */ + if (pos != 0) { + *pos = left; + } + + return false; +} + +/** + * \ingroup vector + * \function igraph_vector_contains_sorted + * \brief Binary search in a sorted vector. + * + * It is assumed that the vector is sorted. + * + * \param v The \type igraph_vector_t object. + * \param what The element to search for. + * \return True if \p what is found in the vector, false otherwise. + * + * Time complexity: O(log(n)), n is the number of elements in \p v. + */ + +igraph_bool_t FUNCTION(igraph_vector, contains_sorted)(const TYPE(igraph_vector) *v, BASE what) { + igraph_int_t left = 0; + igraph_int_t right = FUNCTION(igraph_vector, size)(v) - 1; + + while (left <= right) { + /* (right + left) / 2 could theoretically overflow for long vectors */ + igraph_int_t middle = left + ((right - left) >> 1); + if (what < VECTOR(*v)[middle]) { + right = middle - 1; + } else if (what > VECTOR(*v)[middle]) { + left = middle + 1; + } else { + return true; + } + } + + return false; +} + +#endif + +/** + * \function igraph_vector_scale + * \brief Multiplies all elements of a vector by a constant. + * + * \param v The vector. + * \param by The constant. + * \return Error code. The current implementation always returns with success. + * + * Added in version 0.2. + * + * Time complexity: O(n), the number of elements in a vector. + */ + +void FUNCTION(igraph_vector, scale)(TYPE(igraph_vector) *v, BASE by) { + const igraph_int_t n = FUNCTION(igraph_vector, size)(v); +#ifdef EQ + const BASE one = ONE; + if (EQ(by, one)) { + return; + } +#else + if (by == ONE) { + return; + } +#endif + for (igraph_int_t i = 0; i < n; i++) { +#ifdef PROD + PROD(VECTOR(*v)[i], VECTOR(*v)[i], by); +#else + VECTOR(*v)[i] *= by; +#endif + } +} + +/** + * \function igraph_vector_add_constant + * \brief Add a constant to the vector. + * + * \p plus is added to every element of \p v. Note that overflow + * might happen. + * \param v The input vector. + * \param plus The constant to add. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, add_constant)(TYPE(igraph_vector) *v, BASE plus) { + const igraph_int_t n = FUNCTION(igraph_vector, size)(v); +#ifdef EQ + const BASE zero = ZERO; + if (EQ(plus, zero)) { + return; + } +#else + if (plus == ZERO) { + return; + } +#endif + for (igraph_int_t i = 0; i < n; i++) { +#ifdef SUM + SUM(VECTOR(*v)[i], VECTOR(*v)[i], plus); +#else + VECTOR(*v)[i] += plus; +#endif + } +} + +/** + * \function igraph_vector_contains + * \brief Linear search in a vector. + * + * Check whether the supplied element is included in the vector, by + * linear search. + * + * \param v The input vector. + * \param what The element to look for. + * \return \c true if the element is found and \c false otherwise. + * + * Time complexity: O(n), the length of the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, contains)(const TYPE(igraph_vector) *v, + BASE what) { + const BASE *p = v->stor_begin; + while (p < v->end) { +#ifdef EQ + if (EQ(*p, what)) { +#else + if (*p == what) { +#endif + return true; + } + p++; + } + return false; +} + +/** + * \function igraph_vector_search + * \brief Searches in a vector from a given position. + * + * The supplied element \p what is searched in vector \p v, starting + * from element index \p from. If found then the index of the first + * instance (after \p from) is stored in \p pos. + * + * \param v The input vector. + * \param from The index to start searching from. No range checking is + * performed. + * \param what The element to find. + * \param pos If not \c NULL then the index of the found element is + * stored here. + * \return Boolean, \c true if the element was found, \c false + * otherwise. + * + * Time complexity: O(m), the number of elements to search, the length + * of the vector minus the \p from argument. + */ + +igraph_bool_t FUNCTION(igraph_vector, search)(const TYPE(igraph_vector) *v, + igraph_int_t from, BASE what, igraph_int_t *pos) { + igraph_int_t i, n = FUNCTION(igraph_vector, size)(v); + for (i = from; i < n; i++) { +#ifdef EQ + if (EQ(VECTOR(*v)[i], what)) { + break; + } +#else + if (VECTOR(*v)[i] == what) { + break; + } +#endif + } + + if (i < n) { + if (pos != 0) { + *pos = i; + } + return true; + } else { + return false; + } +} + +#ifndef NOTORDERED + +/** + * \function igraph_vector_filter_smaller + * \ingroup internal + */ + +igraph_error_t FUNCTION(igraph_vector, filter_smaller)(TYPE(igraph_vector) *v, + BASE elem) { + igraph_int_t i = 0, n = FUNCTION(igraph_vector, size)(v); + igraph_int_t s; + while (i < n && VECTOR(*v)[i] < elem) { + i++; + } + s = i; + + while (s < n && VECTOR(*v)[s] == elem) { + s++; + } + + FUNCTION(igraph_vector, remove_section)(v, 0, i + (s - i) / 2); + return IGRAPH_SUCCESS; +} + +#endif + +/** + * \function igraph_vector_append + * \brief Append a vector to another one. + * + * The target vector will be resized (except when \p from is empty). + * \param to The vector to append to. + * \param from The vector to append, it is kept unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements in the new vector. + */ + +igraph_error_t FUNCTION(igraph_vector, append)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + IGRAPH_ASSERT(to != NULL); + IGRAPH_ASSERT(to->stor_begin != NULL); + + const igraph_int_t to_size = FUNCTION(igraph_vector, size)(to); + const igraph_int_t from_size = FUNCTION(igraph_vector, size)(from); + const igraph_int_t to_capacity = FUNCTION(igraph_vector, capacity)(to); + igraph_int_t new_to_size; + + IGRAPH_SAFE_ADD(to_size, from_size, &new_to_size); + + if (to_capacity < new_to_size) { + igraph_int_t new_to_capacity = to_capacity < IGRAPH_INTEGER_MAX/2 ? to_capacity * 2 : IGRAPH_INTEGER_MAX; + if (new_to_capacity < new_to_size) { + new_to_capacity = new_to_size; + } + IGRAPH_CHECK(FUNCTION(igraph_vector, reserve)(to, new_to_capacity)); + } + + memcpy(to->stor_begin + to_size, from->stor_begin, + sizeof(BASE) * from_size); + to->end = to->stor_begin + new_to_size; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_get_interval + */ + +igraph_error_t FUNCTION(igraph_vector, get_interval)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *res, igraph_int_t from, igraph_int_t to) { + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, to - from)); + memcpy(res->stor_begin, v->stor_begin + from, + (to - from) * sizeof(BASE)); + return IGRAPH_SUCCESS; +} + +#ifndef NOTORDERED + +/** + * \function igraph_vector_maxdifference + * \brief The maximum absolute difference of \p m1 and \p m2. + * + * The element with the largest absolute value in \p m1 - \p m2 is + * returned. Both vectors must be non-empty, but they not need to have + * the same length, the extra elements in the longer vector are ignored. If + * any value is NaN in the shorter vector, the result will be NaN. + * \param m1 The first vector. + * \param m2 The second vector. + * \return The maximum absolute difference of \p m1 and \p m2. + * + * Time complexity: O(n), the number of elements in the shorter + * vector. + */ + +igraph_real_t FUNCTION(igraph_vector, maxdifference)(const TYPE(igraph_vector) *m1, + const TYPE(igraph_vector) *m2) { + igraph_int_t n1 = FUNCTION(igraph_vector, size)(m1); + igraph_int_t n2 = FUNCTION(igraph_vector, size)(m2); + igraph_int_t n = n1 < n2 ? n1 : n2; + igraph_int_t i; + igraph_real_t diff = 0.0; + + for (i = 0; i < n; i++) { + igraph_real_t d = fabs((igraph_real_t)(VECTOR(*m1)[i]) - + (igraph_real_t)(VECTOR(*m2)[i])); + if (d > diff) { + diff = d; + } + #if defined(BASE_IGRAPH_REAL) + else if (isnan(d)) { /* Result is NaN */ + return d; + }; + #endif + } + + return diff; +} + +#endif + +/** + * \function igraph_vector_update + * \brief Update a vector from another one. + * + * After this operation the contents of \p to will be exactly the same + * as that of \p from. The vector \p to will be resized if it was originally + * shorter or longer than \p from. + * \param to The vector to update. + * \param from The vector to update from. + * \return Error code. + * + * Time complexity: O(n), the number of elements in \p from. + */ + +igraph_error_t FUNCTION(igraph_vector, update)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + igraph_int_t n = FUNCTION(igraph_vector, size)(from); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(to, n)); + memcpy(to->stor_begin, from->stor_begin, sizeof(BASE)*n); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_swap + * \brief Swap all elements of two vectors. + * + * \param v1 The first vector. + * \param v2 The second vector. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_vector, swap)(TYPE(igraph_vector) *v1, TYPE(igraph_vector) *v2) { + + TYPE(igraph_vector) tmp; + + tmp = *v1; + *v1 = *v2; + *v2 = tmp; +} + +/** + * \function igraph_vector_swap_elements + * \brief Swap two elements in a vector. + * + * Note that currently no range checking is performed. + * + * \param v The input vector. + * \param i Index of the first element. + * \param j Index of the second element (may be the same as the first one). + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_vector, swap_elements)(TYPE(igraph_vector) *v, + igraph_int_t i, igraph_int_t j) { + BASE tmp = VECTOR(*v)[i]; + VECTOR(*v)[i] = VECTOR(*v)[j]; + VECTOR(*v)[j] = tmp; +} + +/** + * \function igraph_vector_reverse_section + * \brief Reverse the elements in a section of a vector. + * + * \param v The input vector. + * \param from Index of the first element to include in the reversal. + * \param to Index of the first element \em not to include in the reversal. + * + * \sa igraph_vector_reverse() to reverse the entire vector. + * + * Time complexity: O(to - from), the number of elements to reverse. + */ + +void FUNCTION(igraph_vector, reverse_section)(TYPE(igraph_vector) *v, igraph_int_t from, igraph_int_t to) { + const igraph_int_t mid = (from + to) / 2; + for (igraph_int_t i = from, j = to - 1; i < mid; i++, j--) { + BASE tmp = VECTOR(*v)[i]; + VECTOR(*v)[i] = VECTOR(*v)[j]; + VECTOR(*v)[j] = tmp; + } +} + +/** + * \function igraph_vector_reverse + * \brief Reverse the elements of a vector. + * + * The first element will be last, the last element will be + * first, etc. + * + * \param v The input vector. + * + * \sa igraph_vector_reverse_section() to reverse only a section of a vector. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, reverse)(TYPE(igraph_vector) *v) { + FUNCTION(igraph_vector, reverse_section)(v, 0, FUNCTION(igraph_vector, size)(v)); +} + +/** + * \function igraph_vector_rotate_left + * \brief Rotates the elements of a vector to the left. + * + * Rotates the elements of a vector to the left by the given number of steps. + * Element index \p n will have index 0 after the rotation. + * For example, rotating (0, 1, 2, 3, 4, 5) by 2 yields + * (2, 3, 4, 5, 0, 1). + * + * \param v The input vector. + * \param n The number of steps to rotate by. Passing a negative value rotates + * to the right. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, rotate_left)(TYPE(igraph_vector) *v, igraph_int_t n) { + const igraph_int_t size = FUNCTION(igraph_vector, size)(v); + n = n % size; + if (n < 0) n += size; + if (n == 0) return; + FUNCTION(igraph_vector, reverse_section)(v, 0, n); + FUNCTION(igraph_vector, reverse_section)(v, n, size); + FUNCTION(igraph_vector, reverse_section)(v, 0, size); +} + +/** + * \ingroup vector + * \function igraph_vector_shuffle + * \brief Shuffles a vector in-place using the Fisher-Yates method. + * + * + * The Fisher-Yates shuffle ensures that every permutation is + * equally probable when using a proper randomness source. Of course + * this does not apply to pseudo-random generators as the cycle of + * these generators is less than the number of possible permutations + * of the vector if the vector is long enough. + * \param v The vector object. + * + * Time complexity: O(n), + * n is the number of elements in the + * vector. + * + * + * References: + * \clist + * \cli (Fisher & Yates 1963) + * R. A. Fisher and F. Yates. \emb Statistical Tables for Biological, + * Agricultural and Medical Research. \eme Oliver and Boyd, 6th edition, + * 1963, page 37. + * \cli (Knuth 1998) + * D. E. Knuth. \emb Seminumerical Algorithms, \eme volume 2 of \emb The Art + * of Computer Programming. \eme Addison-Wesley, 3rd edition, 1998, page 145. + * \endclist + * + * \example examples/simple/igraph_fisher_yates_shuffle.c + */ + +void FUNCTION(igraph_vector, shuffle)(TYPE(igraph_vector) *v) { + igraph_int_t n = FUNCTION(igraph_vector, size)(v); + igraph_int_t k; + BASE dummy; + + while (n > 1) { + k = RNG_INTEGER(0, n - 1); + n--; + dummy = VECTOR(*v)[n]; + VECTOR(*v)[n] = VECTOR(*v)[k]; + VECTOR(*v)[k] = dummy; + } +} + +/** + * \function igraph_vector_add + * \brief Add two vectors. + * + * Add the elements of \p v2 to \p v1, the result is stored in \p + * v1. The two vectors must have the same length. + * \param v1 The first vector, the result will be stored here. + * \param v2 The second vector, its contents will be unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_vector, add)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + igraph_int_t n1 = FUNCTION(igraph_vector, size)(v1); + igraph_int_t n2 = FUNCTION(igraph_vector, size)(v2); + igraph_int_t i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors to be added must have the same sizes.", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef SUM + SUM(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] += VECTOR(*v2)[i]; +#endif + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_sub + * \brief Subtract a vector from another one. + * + * Subtract the elements of \p v2 from \p v1, the result is stored in + * \p v1. The two vectors must have the same length. + * \param v1 The first vector, to subtract from. The result is stored + * here. + * \param v2 The vector to subtract, it will be unchanged. + * \return Error code. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_error_t FUNCTION(igraph_vector, sub)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + igraph_int_t n1 = FUNCTION(igraph_vector, size)(v1); + igraph_int_t n2 = FUNCTION(igraph_vector, size)(v2); + igraph_int_t i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors to be subtracted must have the same sizes.", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef DIFF + DIFF(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] -= VECTOR(*v2)[i]; +#endif + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_mul + * \brief Multiply two vectors. + * + * \p v1 will be multiplied by \p v2, elementwise. The two vectors + * must have the same length. + * \param v1 The first vector, the result will be stored here. + * \param v2 The second vector, it is left unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +igraph_error_t FUNCTION(igraph_vector, mul)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + igraph_int_t n1 = FUNCTION(igraph_vector, size)(v1); + igraph_int_t n2 = FUNCTION(igraph_vector, size)(v2); + igraph_int_t i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors to be multiplied must have the same sizes.", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef PROD + PROD(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] *= VECTOR(*v2)[i]; +#endif + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_div + * \brief Divide a vector by another one. + * + * \p v1 is divided by \p v2, elementwise. They must have the same length. If the + * base type of the vector can generate divide by zero errors then + * please make sure that \p v2 contains no zero if you want to avoid + * trouble. + * \param v1 The dividend. The result is also stored here. + * \param v2 The divisor, it is left unchanged. + * \return Error code. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_error_t FUNCTION(igraph_vector, div)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + igraph_int_t n1 = FUNCTION(igraph_vector, size)(v1); + igraph_int_t n2 = FUNCTION(igraph_vector, size)(v2); + igraph_int_t i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors to be divided must have the same sizes.", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef DIV + DIV(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] /= VECTOR(*v2)[i]; +#endif + } + + return IGRAPH_SUCCESS; +} + +#ifndef NOABS + +igraph_error_t FUNCTION(igraph_vector, abs)(TYPE(igraph_vector) *v) { +#ifdef UNSIGNED + /* Nothing do to, unsigned type */ + IGRAPH_UNUSED(v); +#else + igraph_int_t i, n = FUNCTION(igraph_vector, size)(v); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = VECTOR(*v)[i] >= 0 ? VECTOR(*v)[i] : -VECTOR(*v)[i]; + } +#endif + + return IGRAPH_SUCCESS; +} + +#endif + +#ifndef NOTORDERED + +/** + * \function igraph_vector_minmax + * \brief Minimum and maximum elements of a vector. + * + * Handy if you want to have both the smallest and largest element of + * a vector. The vector is only traversed once. The vector must be non-empty. + * If a vector contains at least one NaN, both \c min and \c max will be NaN. + * + * \param v The input vector. It must contain at least one element. + * \param min Pointer to a base type variable, the minimum is stored here. + * \param max Pointer to a base type variable, the maximum is stored here. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, minmax)(const TYPE(igraph_vector) *v, + BASE *min, BASE *max) { + BASE* ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + *min = *max = *(v->stor_begin); + #if defined(BASE_IGRAPH_REAL) + if (isnan(*min)) { return; }; /* Result is NaN */ + #endif + ptr = v->stor_begin + 1; + while (ptr < v->end) { + if (*ptr > *max) { + *max = *ptr; + } else if (*ptr < *min) { + *min = *ptr; + } + #if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) { /* Result is NaN */ + *min = *max = *ptr; + return; + }; + #endif + ptr++; + } +} + +/** + * \function igraph_vector_which_minmax + * \brief Index of the minimum and maximum elements. + * + * Handy if you need the indices of the smallest and largest + * elements. The vector is traversed only once. The vector must be + * non-empty. If the minimum or maximum is not unique, the index + * of the first minimum or the first maximum is returned, respectively. + * If a vector contains at least one NaN, both \c which_min and \c which_max + * will point to the first NaN value. + * + * \param v The input vector. It must contain at least one element. + * \param which_min The index of the minimum element will be stored + * here. + * \param which_max The index of the maximum element will be stored + * here. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, which_minmax)(const TYPE(igraph_vector) *v, + igraph_int_t *which_min, igraph_int_t *which_max) { + BASE *min, *max; + BASE *ptr; + IGRAPH_ASSERT(!FUNCTION(igraph_vector, empty)(v)); + ptr = v->stor_begin; + min = max = ptr; + #if defined(BASE_IGRAPH_REAL) + if (isnan(*ptr)) { /* Result is NaN */ + *which_min = *which_max = 0; + return; + } + #endif + while (ptr < v->end) { + if (*ptr > *max) { + max = ptr; + } else if (*ptr < *min) { + min = ptr; + } +#if defined(BASE_IGRAPH_REAL) + else if (isnan(*ptr)) { /* Result is NaN */ + *which_min = *which_max = ptr - v->stor_begin; + return; + } +#endif + ptr++; + } + *which_min = min - v->stor_begin; + *which_max = max - v->stor_begin; +} + +#endif + +/** + * \function igraph_vector_isnull + * \brief Are all elements zero? + * + * Checks whether all elements of a vector are zero. + * \param v The input vector + * \return Boolean, \c true if the vector contains only zeros, \c + * false otherwise. + * + * Time complexity: O(n), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_vector, isnull)(const TYPE(igraph_vector) *v) { + + igraph_int_t n = FUNCTION(igraph_vector, size)(v); + igraph_int_t i = 0; + +#ifdef EQ + while (i < n && EQ(VECTOR(*v)[i], (BASE) ZERO)) { +#else + while (i < n && VECTOR(*v)[i] == (BASE) ZERO) { +#endif + i++; + } + + return i == n; +} + +#ifndef NOTORDERED + + +/* + * Sorted vector intersection + */ + +/* Recursive Baeza-Yates intersection algorithm for sorted vector intersection. */ +static igraph_error_t FUNCTION(igraph_i_vector, intersect_sorted)( + const TYPE(igraph_vector) *v1, igraph_int_t begin1, igraph_int_t end1, + const TYPE(igraph_vector) *v2, igraph_int_t begin2, igraph_int_t end2, + TYPE(igraph_vector) *result) { + igraph_int_t size1, size2, probe1, probe2; + + if (begin1 == end1 || begin2 == end2) { + return IGRAPH_SUCCESS; + } + + size1 = end1 - begin1; + size2 = end2 - begin2; + + if (size1 < size2) { + probe1 = begin1 + (size1 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v2, VECTOR(*v1)[probe1], &probe2, begin2, end2); + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + )); + if (!(probe2 == end2 || VECTOR(*v1)[probe1] < VECTOR(*v2)[probe2])) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, VECTOR(*v2)[probe2])); + probe2++; + } + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, probe1 + 1, end1, v2, probe2, end2, result + )); + } else { + probe2 = begin2 + (size2 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v1, VECTOR(*v2)[probe2], &probe1, begin1, end1); + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + )); + if (!(probe1 == end1 || VECTOR(*v2)[probe2] < VECTOR(*v1)[probe1])) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, VECTOR(*v2)[probe2])); + probe1++; + } + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, probe1, end1, v2, probe2 + 1, end2, result + )); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_intersect_sorted + * \brief Set intersection of two sorted vectors. + * + * The elements that are contained in both vectors are stored in the result + * vector. All three vectors must be initialized. + * + * + * For similar-size vectors, this function uses a straightforward linear scan. + * When the vector sizes differ substantially, it uses the set intersection + * method of Ricardo Baeza-Yates, which takes logarithmic time in the length + * of the larger vector. + * + * + * The algorithm keeps the multiplicities of the elements: if an element appears + * \c k1 times in the first vector and \c k2 times in the second, the result + * will include that element min(k1, k2) times. + * + * + * Reference: + * + * + * Baeza-Yates R: A fast set intersection algorithm for sorted + * sequences. In: Lecture Notes in Computer Science, vol. 3109/2004, pp. + * 400--408, 2004. Springer Berlin/Heidelberg. + * https://doi.org/10.1007/978-3-540-27801-6_30 + * + * \param v1 The first vector + * \param v2 The second vector + * \param result The result vector, which will also be sorted. + * \return Error code. + * + * Time complexity: O(m log(n)) where m is the size of the smaller vector + * and n is the size of the larger one. + */ +igraph_error_t FUNCTION(igraph_vector, intersect_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result) { + igraph_int_t size1, size2; + igraph_real_t r; + + size1 = FUNCTION(igraph_vector, size)(v1); + size2 = FUNCTION(igraph_vector, size)(v2); + + FUNCTION(igraph_vector, clear)(result); + + if (size1 == 0 || size2 == 0) { + return IGRAPH_SUCCESS; + } + + r = size1 > size2 ? (igraph_real_t) size1 / size2 : (igraph_real_t) size2 / size1; + + /* When the set size ratio is small, use a simple linear scan. See comments in + * igraph_vector_intersection_size_sorted() for the justification of this r threshold. */ + if (r < 10) { + igraph_int_t i1 = 0, i2 = 0; + while (i1 < size1 && i2 < size2) { + BASE e1 = VECTOR(*v1)[i1], e2 = VECTOR(*v2)[i2]; + if (e1 < e2) { + i1++; + } else if (e1 > e2) { + i2++; + } else { + i1++; i2++; + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, e1)); + } + } + } else { + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, 0, size1, v2, 0, size2, result)); + } + return IGRAPH_SUCCESS; +} + +/* Recursive Baeza-Yates intersection algorithm for sorted vector intersection size. */ +static void FUNCTION(igraph_i_vector, intersection_size_sorted)( + const TYPE(igraph_vector) *v1, igraph_int_t begin1, igraph_int_t end1, + const TYPE(igraph_vector) *v2, igraph_int_t begin2, igraph_int_t end2, + igraph_int_t *result) { + + igraph_int_t size1, size2, probe1, probe2; + + if (begin1 == end1 || begin2 == end2) { + return; + } + + size1 = end1 - begin1; + size2 = end2 - begin2; + + if (size1 < size2) { + probe1 = begin1 + (size1 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v2, VECTOR(*v1)[probe1], &probe2, begin2, end2); + FUNCTION(igraph_i_vector, intersection_size_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + ); + if (!(probe2 == end2 || VECTOR(*v1)[probe1] < VECTOR(*v2)[probe2])) { + (*result)++; + probe2++; + } + FUNCTION(igraph_i_vector, intersection_size_sorted)( + v1, probe1 + 1, end1, v2, probe2, end2, result + ); + } else { + probe2 = begin2 + (size2 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v1, VECTOR(*v2)[probe2], &probe1, begin1, end1); + FUNCTION(igraph_i_vector, intersection_size_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + ); + if (!(probe1 == end1 || VECTOR(*v2)[probe2] < VECTOR(*v1)[probe1])) { + (*result)++; + probe1++; + } + FUNCTION(igraph_i_vector, intersection_size_sorted)( + v1, probe1, end1, v2, probe2 + 1, end2, result + ); + } +} + +/** + * \function igraph_vector_intersection_size_sorted + * \brief Intersection size of two sorted vectors. + * + * Counts elements that are present in both vectors. This is particularly + * useful for counting common neighbours of two vertices. + * + * + * For similar-size vectors, this function uses a straightforward linear scan. + * When the vector sizes differ substantially, it uses the set intersection + * method of Ricardo Baeza-Yates, which takes logarithmic time in the length + * of the larger vector. + * + * + * The algorithm keeps the multiplicities of the elements: if an element appears + * \c k1 times in the first vector and \c k2 times in the second, the result + * will include that element min(k1, k2) times. + * + * + * Reference: + * + * + * Baeza-Yates R: A fast set intersection algorithm for sorted + * sequences. In: Lecture Notes in Computer Science, vol. 3109/2004, pp. + * 400--408, 2004. Springer Berlin/Heidelberg. + * https://doi.org/10.1007/978-3-540-27801-6_30 + * + * \param v1 The first vector + * \param v2 The second vector + * \return The number of common elements. + * + * Time complexity: O(m log(n)) where m is the size of the smaller vector + * and n is the size of the larger one. + */ + +igraph_int_t FUNCTION(igraph_vector, intersection_size_sorted)( + const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + igraph_int_t size1, size2, count; + igraph_real_t r; + + size1 = FUNCTION(igraph_vector, size)(v1); + size2 = FUNCTION(igraph_vector, size)(v2); + + count = 0; + + if (size1 == 0 || size2 == 0) { + return count; + } + + r = size1 > size2 ? (igraph_real_t) size1 / size2 : (igraph_real_t) size2 / size1; + + /* When the set size ratio is small, use a simple linear scan. The ideal ratio + * seems to be affected by processor cache effects. It depends on the machine, + * as well as on the input sizes. The threshold of 10 was determined empirically + * by using the intersection.c (direct) and igraph_ecc.c (indirect) benchmarks. + * See https://github.com/igraph/igraph/pull/2618 */ + if (r < 10) { + /* This is a fast branchless implementation that uses arithmetic + * instead of conditionals. */ + igraph_int_t i1 = 0, i2 = 0; + while (i1 < size1 && i2 < size2) { + BASE e1 = VECTOR(*v1)[i1], e2 = VECTOR(*v2)[i2]; + igraph_int_t d1 = (e1 <= e2), d2 = (e1 >= e2); + i1 += d1; i2 += d2; + count += (d1 == d2); + } + } else { + FUNCTION(igraph_i_vector, intersection_size_sorted)( + v1, 0, size1, v2, 0, size2, &count); + } + + return count; +} + +/** + * \function igraph_vector_difference_sorted + * \brief Set difference of two sorted vectors. + * + * The elements that are contained in only the first vector but not the second are + * stored in the result vector. All three vectors must be initialized. + * + * + * The algorithm keeps the multiplicities of the elements: if an element appears + * \c k1 times in the first vector and \c k2 times in the second, the result + * will include that element max(0, k1-k2) times. + * + * \param v1 the first vector + * \param v2 the second vector + * \param result the result vector + */ +igraph_error_t FUNCTION(igraph_vector, difference_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result) { + return FUNCTION(igraph_vector, difference_and_intersection_sorted)( + v1, v2, result, NULL, NULL + ); +} + +/** + * \function igraph_vector_difference_and_intersection_sorted + * \brief Simultaneous difference and intersection of two sorted vectors. + * + * This function iterates over all the elements of the two input vectors and + * sorts them into three other vectors: elements that are in the first vector + * but not in the second, elements that are in the second vector but not in the + * first, and the intersection of the two vectors. The input vectors must be + * initialized. The output arguments can be \c NULL, but they must be initialized + * if they are not \c NULL and will be resized accordingly. + * + * + * The multiplicities of the individual elements are treated consistently with + * \ref igraph_vector_difference_sorted() and \ref igraph_vector_intersect_sorted(): + * The algorithm keeps the multiplicities of the elements: if an element appears + * \c k1 times in the first vector and \c k2 times in the second, the intersection + * vector will include that element min(k1, k2) times, while the + * difference vectors will include that element max(0, k1-k2) and + * max(0, k2-k1) times, respectively. + * + * \param v1 the first vector + * \param v2 the second vector + * \param vdiff12 output vector containing the elements that are in the first + * vector but not the second one, or \c NULL if not needed + * \param vdiff21 output vector containing the elements that are in the second + * vector but not the first one, or \c NULL if not needed + * \param vint output vector containing the intersection, or \c NULL if not + * needed + */ +igraph_error_t FUNCTION(igraph_vector, difference_and_intersection_sorted)( + const TYPE(igraph_vector) *v1, const TYPE(igraph_vector) *v2, + TYPE(igraph_vector) *vdiff12, TYPE(igraph_vector)*vdiff21, + TYPE(igraph_vector)*vint) { + igraph_int_t i, j, i0, j0, oldsize; + i0 = FUNCTION(igraph_vector, size)(v1); + j0 = FUNCTION(igraph_vector, size)(v2); + i = j = 0; + + if (!vdiff12 && !vdiff21 && !vint) { + /* nothing to compute */ + return IGRAPH_SUCCESS; + } + + if (vdiff12) { + FUNCTION(igraph_vector, clear)(vdiff12); + } + if (vdiff21) { + FUNCTION(igraph_vector, clear)(vdiff21); + } + if (vint) { + FUNCTION(igraph_vector, clear)(vint); + } + + if (i0 == 0) { + /* v1 is empty, this is easy */ + if (vdiff21) { + IGRAPH_CHECK(FUNCTION(igraph_vector, update)(vdiff21, v2)); + } + return IGRAPH_SUCCESS; + } + + if (j0 == 0) { + /* v2 is empty, this is easy */ + if (vdiff12) { + IGRAPH_CHECK(FUNCTION(igraph_vector, update)(vdiff12, v1)); + } + return IGRAPH_SUCCESS; + } + + /* Copy the part of v1 that is less than the first element of v2 */ + while (i < i0 && VECTOR(*v1)[i] < VECTOR(*v2)[j]) { + i++; + } + if (vdiff12 && i > 0) { + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(vdiff12, i)); + memcpy(vdiff12->stor_begin, v1->stor_begin, sizeof(BASE) * i); + } + + /* Copy the part of v2 that is less than the first element of v1 */ + while (j < j0 && VECTOR(*v2)[j] < VECTOR(*v1)[i]) { + j++; + } + if (vdiff21 && j > 0) { + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(vdiff21, j)); + memcpy(vdiff21->stor_begin, v2->stor_begin, sizeof(BASE) * j); + } + + /* Now process the common segments */ + while (i < i0 && j < j0) { + BASE element1 = VECTOR(*v1)[i]; + BASE element2 = VECTOR(*v2)[j]; + if (element1 == element2) { + i++; j++; + if (vint) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(vint, element1)); + } + } else if (element1 < element2) { + if (vdiff12) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(vdiff12, element1)); + } + i++; + } else { + if (vdiff21) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(vdiff21, element2)); + } + j++; + } + } + + /* Copy the part of v1 that is greater than the last element of v2 */ + if (vdiff12 && i < i0) { + oldsize = FUNCTION(igraph_vector, size)(vdiff12); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(vdiff12, oldsize + i0 - i)); + memcpy(vdiff12->stor_begin + oldsize, v1->stor_begin + i, + sizeof(BASE) * (i0 - i)); + } + + /* Copy the part of v2 that is greater than the last element of v1 */ + if (vdiff21 && j < j0) { + oldsize = FUNCTION(igraph_vector, size)(vdiff21); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(vdiff21, oldsize + j0 - j)); + memcpy(vdiff21->stor_begin + oldsize, v2->stor_begin + j, + sizeof(BASE) * (j0 - j)); + } + + return IGRAPH_SUCCESS; +} + +#endif + +#ifdef OUT_FORMAT +#ifndef USING_R +igraph_error_t FUNCTION(igraph_vector, printf)(const TYPE(igraph_vector) *v, const char *format) { + igraph_int_t i, n = FUNCTION(igraph_vector, size)(v); + if (n != 0) { + printf(format, VECTOR(*v)[0]); + } + for (i = 1; i < n; i++) { + putchar(' '); printf(format, VECTOR(*v)[i]); + } + printf("\n"); + return IGRAPH_SUCCESS; +} +#endif /* USING_R */ +#endif /* OUT_FORMAT */ + +#if defined(OUT_FORMAT) || defined(FPRINTFUNC) + +#ifndef USING_R +igraph_error_t FUNCTION(igraph_vector, print)(const TYPE(igraph_vector) *v) { + return FUNCTION(igraph_vector, fprint)(v, stdout); +} +#endif + +igraph_error_t FUNCTION(igraph_vector, fprint)(const TYPE(igraph_vector) *v, FILE *file) { + igraph_int_t i, n = FUNCTION(igraph_vector, size)(v); + if (n != 0) { +#ifdef FPRINTFUNC + FPRINTFUNC(file, VECTOR(*v)[0]); +#else + fprintf(file, OUT_FORMAT, VECTOR(*v)[0]); +#endif + } + for (i = 1; i < n; i++) { +#ifdef FPRINTFUNC + fputc(' ', file); FPRINTFUNC(file, VECTOR(*v)[i]); +#else + fprintf(file, " " OUT_FORMAT, VECTOR(*v)[i]); +#endif + } + fprintf(file, "\n"); + return IGRAPH_SUCCESS; +} + +#endif /* defined(OUT_FORMAT) || defined(FPRINTFUNC) */ + +/** + * \function igraph_vector_index + * \brief Extract elements from a vector at specific indices. + * + * \param v the vector to extract elements from + * \param newv the result vector + * \param idx vector containing the indices of the elements to extract + * + * \sa \ref igraph_vector_index_in_place for the in-place variant + */ + +igraph_error_t FUNCTION(igraph_vector, index)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *newv, + const igraph_vector_int_t *idx) { + + igraph_int_t i, j, newlen = igraph_vector_int_size(idx); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(newv, newlen)); + + for (i = 0; i < newlen; i++) { + j = VECTOR(*idx)[i]; + VECTOR(*newv)[i] = VECTOR(*v)[j]; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_index_in_place + * \brief Extract elements from a vector at specific indices in-place. + * + * \param v the vector to extract elements from. This will be modified in-place. + * \param idx vector containing the indices of the elements to extract + * + * \sa \ref igraph_vector_index for a function that does not modify the original + * vector + */ + +igraph_error_t FUNCTION(igraph_vector, index_in_place)(TYPE(igraph_vector) *v, + const igraph_vector_int_t *idx) { + BASE *tmp; + igraph_int_t i, n = igraph_vector_int_size(idx); + + tmp = IGRAPH_CALLOC(n, BASE); + IGRAPH_CHECK_OOM(tmp, "Insufficient memory to index vector."); + + for (i = 0; i < n; i++) { + tmp[i] = VECTOR(*v)[ VECTOR(*idx)[i] ]; + } + + IGRAPH_FREE(v->stor_begin); + v->stor_begin = tmp; + v->stor_end = v->end = tmp + n; + + return IGRAPH_SUCCESS; +} diff --git a/src/core/vector_list.c b/src/core/vector_list.c new file mode 100644 index 0000000..93455d2 --- /dev/null +++ b/src/core/vector_list.c @@ -0,0 +1,170 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector_list.h" + +#define VECTOR_LIST + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "typed_list.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "typed_list.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#undef VECTOR_LIST + +/** + * \ingroup vector_list + * \section about_igraph_vector_list_t_objects About \type igraph_vector_list_t objects + * + * The \type igraph_vector_list_t data type is essentially a list of + * \type igraph_vector_t objects with automatic memory management. It is something + * similar to (but much simpler than) the \type vector template in the C++ + * standard library where the elements are vectors themselves. + * + * There are multiple variants of \type igraph_vector_list_t; the basic variant + * stores vectors of doubles (i.e. each item is an \ref igraph_vector_t), but + * there is also \type igraph_vector_int_list_t for integers (where each item is + * an \type igraph_vector_int_t), \type igraph_matrix_list_t for matrices of + * doubles and so on. The following list summarizes the variants that are + * currently available in the library: + * + * \ilist + * \ili \type igraph_vector_list_t for lists of vectors of floating-point numbers + * (\type igraph_vector_t) + * \ili \type igraph_vector_int_list_t for lists of integer vectors + * (\type igraph_vector_int_t) + * \ili \type igraph_matrix_list_t for lists of matrices of floating-point numbers + * (\type igraph_matrix_t) + * \ili \type igraph_graph_list_t for lists of graphs (\type igraph_t) + * \endilist + * + * Lists of vectors are used in \a igraph in many + * cases, e.g., when returning lists of paths, cliques or vertex sets. + * Functions that expect or return a list of numeric vectors typically use + * \type igraph_vector_list_t or \type igraph_vector_int_list_t to achieve this. + * Lists of integer vectors are used when the vectors in the list are supposed + * to hold vertex or edge identifiers, while lists of floating-point vectors + * are used when the vectors are expected to hold fractional numbers or + * infinities. + * + * The elements in an \type igraph_vector_list_t object and its variants are + * indexed from zero, we follow the usual C convention here. + * + * Almost all of the functions described below for \type igraph_vector_list_t + * also exist for all the other vector list variants. These variants are not + * documented separately; you can simply replace \c vector_list with, say, + * \c vector_int_list if you need a function for another variant. For instance, + * to initialize a list of integer vectors, you need to use + * \c igraph_vector_int_list_init() and not \ref igraph_vector_list_init(). + * + * Before diving into a detailed description of the functions related to + * lists of vectors, we must also talk about the \em ownership rules of these + * objects. The most important rule is that the vectors in the list are + * owned by the list itself, meaning that the user is \em not responsible + * for allocating memory for the vectors or for freeing the memory associated + * to the vectors. It is the responsibility of the list to allocate and initialize + * the vectors when new items are created in the list, and it is also the + * responsibility of the list to destroy the items when they are removed from + * the list without passing on their ownership to the user. As a consequence, + * the list may not contain "uninitialized" or "null" items; each item is + * initialized when it comes to existence. If you create a list containing + * one million vectors, you are not only allocating memory for one million + * \ref igraph_vector_t object but you are also initializing one million + * vectors. Also, if you have a list containing one million vectors and you + * clear the list by calling \ref igraph_vector_list_clear(), the list will + * implicitly destroy these lists, and any pointers that you may hold to the + * items become invalid at once. + * + * Speaking about pointers, the typical way of working with vectors in + * a list is to obtain a pointer to one of the items via the + * \ref igraph_vector_list_get_ptr() method and then passing this pointer + * onwards to functions that manipulate \ref igraph_vector_t objects. However, + * note that the pointers are \em ephemeral in the sense that they may be + * invalidated any time when the list is modified because a modification may + * involve the re-allocation of the internal storage of the list if more space + * is needed, and the pointers that you obtained will not follow the + * reallocation. This limitation does not appear often in real-world usage of + * \c igraph_vector_list_t and in general, the advantages of the automatic + * memory management outweigh this limitation. + */ + +/** + * \ingroup vector_list + * \section igraph_vector_list_constructors_and_destructors Constructors and + * destructors + * + * \type igraph_vector_list_t objects have to be initialized before using + * them, this is analogous to calling a constructor on them. + * \ref igraph_vector_list_init() is the basic constructor; it creates a list + * of the given length and also initializes each vector in the newly created + * list to zero length. + * + * If an \type igraph_vector_list_t object is not needed any more, it + * should be destroyed to free its allocated memory by calling the + * \type igraph_vector_list_t destructor, \ref igraph_vector_list_destroy(). + * Calling the destructor also destroys all the vectors inside the vector + * list due to the ownership rules. If you want to keep a few of the vectors + * in the vector list, you need to copy them with \ref igraph_vector_init_copy() or + * \ref igraph_vector_update(), or you need to remove them from the list and + * take ownership by calling \ref igraph_vector_list_pop_back(), + * \ref igraph_vector_list_remove() or \ref igraph_vector_list_remove_fast() . + */ + + +/** + * \ingroup vector_list + * \section igraph_vector_list_accessing_elements Accessing elements + * + * Elements of a vector list may be accessed with the + * \ref igraph_vector_list_get_ptr() function. The function returns a \em pointer + * to the vector with a given index inside the list, and you may then pass + * this pointer onwards to other functions that can query or manipulate + * vectors. The pointer itself is guaranteed to stay valid as long as the + * list itself is not modified; however, \em any modification to the list + * will invalidate the pointer, even modifications that are seemingly unrelated + * to the vector that the pointer points to (such as adding a new vector at + * the end of the list). This is because the list data structure may be forced + * to re-allocate its internal storage if a new element does not fit into the + * already allocated space, and there are no guarantees that the re-allocated + * block remains at the same memory location (typically it gets moved elsewhere). + * + * + * Note that the standard \ref VECTOR macro that works for ordinary vectors + * does not work for lists of vectors to access the i-th element (but of course + * you can use it to index into an existing vector that you retrieved from the + * vector list with \ref igraph_vector_list_get_ptr() ). This is because the + * macro notation would allow one to overwrite the vector in the list with + * another one without the list knowing about it, so the list would not be able + * to destroy the vector that was overwritten by a new one. + * + * + * \ref igraph_vector_list_tail_ptr() returns a pointer to the last + * vector in the list, or \c NULL if the list is empty. There is no + * igraph_vector_list_head_ptr(), however, as it is easy to + * write igraph_vector_list_get_ptr(v, 0) instead. + */ diff --git a/src/core/vector_ptr.c b/src/core/vector_ptr.c new file mode 100644 index 0000000..f451713 --- /dev/null +++ b/src/core/vector_ptr.c @@ -0,0 +1,839 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector_ptr.h" + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_qsort.h" + +#include "math/safe_intop.h" + +#include /* memcpy & co. */ +#include /* uintptr_t */ +#include + +/** + * \section about_igraph_vector_ptr_objects Pointer vectors + * (igraph_vector_ptr_t) + * + * The \type igraph_vector_ptr_t data type is very similar to + * the \ref igraph_vector_t type, but it stores generic pointers instead of + * real numbers. + * + * This type has the same space complexity as \ref + * igraph_vector_t, and most implemented operations work the same way + * as for \ref igraph_vector_t. + * + * The same \ref VECTOR macro used for ordinary vectors can be + * used for pointer vectors as well, please note that a typeless + * generic pointer will be provided by this macro and you may need to + * cast it to a specific pointer before starting to work with it. + * + * Pointer vectors may have an associated item destructor function + * which takes a pointer and returns nothing. The item destructor will + * be called on each item in the pointer vector when it is destroyed by + * \ref igraph_vector_ptr_destroy() or \ref igraph_vector_ptr_destroy_all(), + * or when its elements are freed by \ref igraph_vector_ptr_free_all(). + * Note that the semantics of an item destructor does not coincide with + * C++ destructors; for instance, when a pointer vector is resized to a + * smaller size, the extra items will \em not be destroyed automatically! + * Nevertheless, item destructors may become handy in many cases; for + * instance, a vector of graphs generated by some function can + * be destroyed with a single call to \ref igraph_vector_ptr_destroy_all() + * if the item destructor is set to \ref igraph_destroy(). + */ + + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_init + * \brief Initialize a pointer vector (constructor). + * + * + * This is the constructor of the pointer vector data type. All + * pointer vectors constructed this way should be destroyed via + * calling \ref igraph_vector_ptr_destroy(). + * \param v Pointer to an uninitialized + * igraph_vector_ptr_t object, to be created. + * \param size Integer, the size of the pointer vector. + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + * + * Time complexity: operating system dependent, the amount of \quote + * time \endquote required to allocate \p size elements. + */ + +igraph_error_t igraph_vector_ptr_init(igraph_vector_ptr_t* v, igraph_int_t size) { + igraph_int_t alloc_size = size > 0 ? size : 1; + IGRAPH_ASSERT(v != NULL); + if (size < 0) { + size = 0; + } + v->stor_begin = IGRAPH_CALLOC(alloc_size, void*); + if (v->stor_begin == 0) { + IGRAPH_ERROR("vector ptr init failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + v->stor_end = v->stor_begin + alloc_size; + v->end = v->stor_begin + size; + v->item_destructor = 0; + + return IGRAPH_SUCCESS; +} + +/** + */ + +igraph_vector_ptr_t igraph_vector_ptr_view( + void *const *data, igraph_int_t length +) { + igraph_vector_ptr_t v; + v.stor_begin = (void **)data; + v.stor_end = (void**)data + length; + v.end = v.stor_end; + v.item_destructor = 0; + return v; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_destroy + * \brief Destroys a pointer vector. + * + * + * The destructor for pointer vectors. + * \param v Pointer to the pointer vector to destroy. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to deallocate O(n) bytes, n is the number of + * elements allocated for the pointer vector (not necessarily the + * number of elements in the vector). + */ + +void igraph_vector_ptr_destroy(igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != 0); + if (v->stor_begin != 0) { + IGRAPH_FREE(v->stor_begin); + v->stor_begin = NULL; + } +} + +static void igraph_i_vector_ptr_call_item_destructor_all(igraph_vector_ptr_t* v) { + void **ptr; + + if (v->item_destructor != 0) { + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + if (*ptr != 0) { + v->item_destructor(*ptr); + } + } + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_free_all + * \brief Frees all the elements of a pointer vector. + * + * If an item destructor is set for this pointer vector, this function will + * first call the destructor on all elements of the vector and then + * free all the elements using \ref igraph_free(). If an item destructor is not set, + * the elements will simply be freed. + * + * \param v Pointer to the pointer vector whose elements will be freed. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to call the destructor n times and then + * deallocate O(n) pointers, each pointing to a memory area of + * arbitrary size. n is the number of elements in the pointer vector. + */ + +void igraph_vector_ptr_free_all(igraph_vector_ptr_t* v) { + void **ptr; + IGRAPH_ASSERT(v != 0); + IGRAPH_ASSERT(v->stor_begin != 0); + + igraph_i_vector_ptr_call_item_destructor_all(v); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + IGRAPH_FREE(*ptr); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_destroy_all + * \brief Frees all the elements and destroys the pointer vector. + * + * This function is equivalent to \ref igraph_vector_ptr_free_all() + * followed by \ref igraph_vector_ptr_destroy(). + * + * \param v Pointer to the pointer vector to destroy. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to deallocate O(n) pointers, each pointing to + * a memory area of arbitrary size, plus the \quote time \endquote + * required to deallocate O(n) bytes, n being the number of elements + * allocated for the pointer vector (not necessarily the number of + * elements in the vector). + */ + +void igraph_vector_ptr_destroy_all(igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != 0); + IGRAPH_ASSERT(v->stor_begin != 0); + igraph_vector_ptr_free_all(v); + igraph_vector_ptr_set_item_destructor(v, 0); + igraph_vector_ptr_destroy(v); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_reserve + * \brief Reserves memory for a pointer vector for later use. + * + * \return Error code. + */ + +igraph_error_t igraph_vector_ptr_reserve(igraph_vector_ptr_t* v, igraph_int_t capacity) { + igraph_int_t actual_size = igraph_vector_ptr_size(v); + void **tmp; + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(capacity >= 0); + + if (capacity <= igraph_vector_ptr_size(v)) { + return IGRAPH_SUCCESS; + } + + tmp = IGRAPH_REALLOC(v->stor_begin, (size_t) capacity, void*); + IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for pointer vector."); + + v->stor_begin = tmp; + v->stor_end = v->stor_begin + capacity; + v->end = v->stor_begin + actual_size; + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_resize_min + * \brief Deallocate the unused memory of a pointer vector. + * + * This function attempts to deallocate the unused reserved storage + * of a pointer vector. If it succeeds, \ref igraph_vector_ptr_size() and + * \ref igraph_vector_ptr_capacity() will be the same. The data in the + * pointer vector is always preserved, even if deallocation is not successful. + * + * \param v Pointer to an initialized pointer vector. + * + * \sa \ref igraph_vector_ptr_resize(), \ref igraph_vector_ptr_reserve(). + * + * Time complexity: operating system dependent, O(n) at worst. + */ + +void igraph_vector_ptr_resize_min(igraph_vector_ptr_t* v) { + igraph_int_t size; + void **tmp; + if (v->stor_end == v->end) { + return; + } + + size = (v->end - v->stor_begin); + tmp = IGRAPH_REALLOC(v->stor_begin, size, void *); + + if (tmp != NULL) { + v->stor_begin = tmp; + v->stor_end = v->end = v->stor_begin + size; + } +} + +/** + * \ingroup vectorptr + * \brief Decides whether the pointer vector is empty. + */ + +igraph_bool_t igraph_vector_ptr_empty(const igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_begin == v->end; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_size + * \brief Gives the number of elements in the pointer vector. + * + * \param v The pointer vector object. + * \return The size of the object, i.e. the number of pointers stored. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_vector_ptr_size(const igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->end - v->stor_begin; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_capacity + * \brief Returns the allocated capacity of the pointer vector. + * + * \param v The pointer vector object. + * \return The allocated capacity. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_vector_ptr_capacity(const igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return v->stor_end - v->stor_begin; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_clear + * \brief Removes all elements from a pointer vector. + * + * + * This function resizes a pointer to vector to zero length. Note that + * the pointed objects are \em not deallocated, you should call + * \ref igraph_free() on them, or make sure that their allocated memory is freed + * in some other way, you'll get memory leaks otherwise. If you have + * set up an item destructor earlier, the destructor will be called + * on every element. + * + * + * Note that the current implementation of this function does + * \em not deallocate the memory required for storing the + * pointers, so making a pointer vector smaller this way does not give + * back any memory. This behavior might change in the future. + * \param v The pointer vector to clear. + * + * Time complexity: O(1). + */ + +void igraph_vector_ptr_clear(igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + igraph_i_vector_ptr_call_item_destructor_all(v); + v->end = v->stor_begin; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_push_back + * \brief Appends an element to the back of a pointer vector. + * + * \param v The pointer vector. + * \param e The new element to include in the pointer vector. + * \return Error code. + * \sa \ref igraph_vector_push_back() for the corresponding operation of + * the ordinary vector type. + * + * Time complexity: O(1) or O(n), n is the number of elements in the + * vector. The pointer vector implementation ensures that n subsequent + * push_back operations need O(n) time to complete. + */ + +igraph_error_t igraph_vector_ptr_push_back(igraph_vector_ptr_t* v, void* e) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + + /* full, allocate more storage */ + if (v->stor_end == v->end) { + igraph_int_t new_size = igraph_vector_ptr_size(v) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_vector_ptr_reserve(v, new_size)); + } + + *(v->end) = e; + v->end += 1; + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_pop_back + * \brief Removes and returns the last element of a pointer vector. + * + * + * It is an error to call this function with an empty vector. + * + * \param v The pointer vector. + * \return The removed last element. + * + * Time complexity: O(1). + */ + +void *igraph_vector_ptr_pop_back(igraph_vector_ptr_t *v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(v->stor_begin != v->end); + v->end -= 1; + return *(v->end); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_insert + * \brief Inserts a single element into a pointer vector. + * + * Note that this function does not do range checking. Insertion will shift the + * elements from the position given to the end of the vector one position to the + * right, and the new element will be inserted in the empty space created at + * the given position. The size of the vector will increase by one. + * + * \param v The pointer vector object. + * \param pos The position where the new element is inserted. + * \param e The inserted element + */ +igraph_error_t igraph_vector_ptr_insert(igraph_vector_ptr_t* v, igraph_int_t pos, void* e) { + igraph_int_t size = igraph_vector_ptr_size(v); + IGRAPH_CHECK(igraph_vector_ptr_resize(v, size + 1)); + if (pos < size) { + memmove(v->stor_begin + pos + 1, v->stor_begin + pos, + sizeof(void*) * (size_t) (size - pos)); + } + v->stor_begin[pos] = e; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_get + * \brief Access an element of a pointer vector. + * + * \param v Pointer to a pointer vector. + * \param pos The index of the pointer to return. + * \return The pointer at \p pos position. + * + * Time complexity: O(1). + */ + +void *igraph_vector_ptr_get(const igraph_vector_ptr_t* v, igraph_int_t pos) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + return *(v->stor_begin + pos); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_set + * \brief Assign to an element of a pointer vector. + * + * \param v Pointer to a pointer vector. + * \param pos The index of the pointer to update. + * \param value The new pointer to set in the vector. + * + * Time complexity: O(1). + */ + +void igraph_vector_ptr_set(igraph_vector_ptr_t* v, igraph_int_t pos, void* value) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + *(v->stor_begin + pos) = value; +} + +/** + * \ingroup vectorptr + * \brief Set all elements of a pointer vector to the NULL pointer. + */ + +void igraph_vector_ptr_null(igraph_vector_ptr_t* v) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + if (igraph_vector_ptr_size(v) > 0) { + memset(v->stor_begin, 0, sizeof(void*) * + (size_t) igraph_vector_ptr_size(v)); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_resize + * \brief Resizes a pointer vector. + * + * + * Note that if a vector is made smaller the pointed object are not + * deallocated by this function and the item destructor is not called + * on the extra elements. + * + * \param v A pointer vector. + * \param newsize The new size of the pointer vector. + * \return Error code. + * + * Time complexity: O(1) if the vector if made smaller. Operating + * system dependent otherwise, the amount of \quote time \endquote + * needed to allocate the memory for the vector elements. + */ + +igraph_error_t igraph_vector_ptr_resize(igraph_vector_ptr_t* v, igraph_int_t newsize) { + IGRAPH_CHECK(igraph_vector_ptr_reserve(v, newsize)); + v->end = v->stor_begin + newsize; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \brief Initializes a pointer vector from an array (constructor). + * + * \param v Pointer to an uninitialized + * igraph_vector_ptr_t object to be initialized. + * \param data The array of pointers that serves as the initial contents of the + * pointer vector. + * \param length Integer, the length of the array. + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + */ + +igraph_error_t igraph_vector_ptr_init_array(igraph_vector_ptr_t *v, void *const *data, igraph_int_t length) { + v->stor_begin = IGRAPH_CALLOC(length, void*); + if (v->stor_begin == 0) { + IGRAPH_ERROR("Cannot initialize pointer vector from array", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + v->stor_end = v->stor_begin + length; + v->end = v->stor_end; + v->item_destructor = 0; + memcpy(v->stor_begin, data, (size_t) length * sizeof(void*)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \brief Copy the contents of a pointer vector to a regular C array. + */ + +void igraph_vector_ptr_copy_to(const igraph_vector_ptr_t *v, void** to) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + if (v->end != v->stor_begin) { + memcpy(to, v->stor_begin, sizeof(void*) * + (size_t) (v->end - v->stor_begin)); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_init_copy + * \brief Initializes a pointer vector from another one (constructor). + * + * + * This function creates a pointer vector by copying another one. This + * is shallow copy, only the pointers in the vector will be copied. + * + * + * It is potentially dangerous to copy a pointer vector with an associated + * item destructor. The copied vector will inherit the item destructor, + * which may cause problems when both vectors are destroyed as the items + * might get destroyed twice. Make sure you know what you are doing when + * copying a pointer vector with an item destructor, or unset the item + * destructor on one of the vectors later. + * + * \param to Pointer to an uninitialized pointer vector object. + * \param from A pointer vector object. + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + * + * Time complexity: O(n) if allocating memory for n elements can be + * done in O(n) time. + */ + +igraph_error_t igraph_vector_ptr_init_copy(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from) { + igraph_int_t from_size; + + IGRAPH_ASSERT(from != NULL); + /* IGRAPH_ASSERT(from->stor_begin != NULL); */ /* TODO */ + + from_size = igraph_vector_ptr_size(from); + + to->stor_begin = IGRAPH_CALLOC(from_size, void*); + if (to->stor_begin == 0) { + IGRAPH_ERROR("Cannot copy pointer vector", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + to->stor_end = to->stor_begin + igraph_vector_ptr_size(from); + to->end = to->stor_end; + to->item_destructor = from->item_destructor; + memcpy(to->stor_begin, from->stor_begin, + (size_t) igraph_vector_ptr_size(from)*sizeof(void*)); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \brief Remove an element from a pointer vector. + */ + +void igraph_vector_ptr_remove(igraph_vector_ptr_t *v, igraph_int_t pos) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + if (pos + 1 < igraph_vector_ptr_size(v)) { /* No need to move data when removing the last element. */ + memmove(v->stor_begin + pos, v->stor_begin + pos + 1, + sizeof(void*) * (size_t) (igraph_vector_ptr_size(v) - pos - 1)); + } + v->end--; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_sort + * \brief Sorts the pointer vector based on an external comparison function. + * + * Sometimes it is necessary to sort the pointers in the vector based on + * the property of the element being referenced by the pointer. This + * function allows us to sort the vector based on an arbitrary external + * comparison function which accepts two void * pointers \c p1 and \c p2 + * and returns an integer less than, equal to or greater than zero if the + * first argument is considered to be respectively less than, equal to, or + * greater than the second. \c p1 and \c p2 will point to the pointer in the + * vector, so they have to be double-dereferenced if one wants to get access + * to the underlying object the address of which is stored in \c v. + * + * \param v The pointer vector to be sorted. + * \param compar A qsort-compatible comparison function. It must take pointers to the + * elements of the pointer vector. For example, if the pointer vector contains + * igraph_vector_t * pointers, then the comparison function must + * interpret its arguments as igraph_vector_t **. + */ +void igraph_vector_ptr_sort(igraph_vector_ptr_t *v, int (*compar)(const void*, const void*)) { + igraph_qsort(v->stor_begin, (size_t) igraph_vector_ptr_size(v), sizeof(void*), + compar); +} + +igraph_error_t igraph_vector_ptr_append(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from) { + const igraph_int_t to_size = igraph_vector_ptr_size(to); + const igraph_int_t from_size = igraph_vector_ptr_size(from); + const igraph_int_t to_capacity = igraph_vector_ptr_capacity(to); + igraph_int_t new_to_size; + + IGRAPH_SAFE_ADD(to_size, from_size, &new_to_size); + + if (to_capacity < new_to_size) { + igraph_int_t new_to_capacity = to_capacity < IGRAPH_INTEGER_MAX/2 ? to_capacity * 2 : IGRAPH_INTEGER_MAX; + if (new_to_capacity < new_to_size) { + new_to_capacity = new_to_size; + } + IGRAPH_CHECK(igraph_vector_ptr_reserve(to, new_to_capacity)); + } + + memcpy(to->stor_begin + to_size, from->stor_begin, + sizeof(void *) * from_size); + to->end = to->stor_begin + new_to_size; + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_set_item_destructor + * \brief Sets the item destructor for this pointer vector. + * + * The item destructor is a function which will be called on every non-null + * pointer stored in this vector when \ref igraph_vector_ptr_destroy(), + * igraph_vector_ptr_destroy_all() or \ref igraph_vector_ptr_free_all() + * is called. + * + * \return The old item destructor. + * + * Time complexity: O(1). + */ +igraph_finally_func_t* igraph_vector_ptr_set_item_destructor( + igraph_vector_ptr_t *v, igraph_finally_func_t *func) { + igraph_finally_func_t* result = v->item_destructor; + + v->item_destructor = func; + + return result; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_get_item_destructor + * \brief Gets the current item destructor for this pointer vector. + * + * The item destructor is a function which will be called on every non-null + * pointer stored in this vector when \ref igraph_vector_ptr_destroy(), + * igraph_vector_ptr_destroy_all() or \ref igraph_vector_ptr_free_all() + * is called. + * + * \return The current item destructor. + * + * Time complexity: O(1). + */ +igraph_finally_func_t* igraph_vector_ptr_get_item_destructor(const igraph_vector_ptr_t *v) { + IGRAPH_ASSERT(v != 0); + return v->item_destructor; +} + +typedef int cmp_t (const void *, const void *); + +/** + * Comparison function passed to qsort_r from igraph_vector_ptr_sort_ind + */ +static int igraph_vector_ptr_i_sort_ind_cmp(void *thunk, const void *p1, const void *p2) { + cmp_t *cmp = (cmp_t *) thunk; + uintptr_t *pa = (uintptr_t*) p1; + uintptr_t *pb = (uintptr_t*) p2; + void **item_a_ptr = (void**) *pa; + void **item_b_ptr = (void**) *pb; + return cmp(*item_a_ptr, *item_b_ptr); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_sort_ind + * \brief Returns a permutation of indices that sorts a vector of pointers. + * + * Takes an unsorted array \c v as input and computes an array of + * indices inds such that v[ inds[i] ], with i increasing from 0, is + * an ordered array (either ascending or descending, depending on + * \v order). The order of indices for identical elements is not + * defined. + * + * \param v the array to be sorted + * \param inds the output array of indices. This must be initialized, + * but will be resized + * \param cmp a comparator function that takes two elements of the pointer + * vector being sorted (these are constant pointers on their own) + * and returns a negative value if the item \em "pointed to" by the + * first pointer is smaller than the item \em "pointed to" by the + * second pointer, a positive value if it is larger, or zero if the + * two items are equal + * \return Error code. + * + * This routine uses the C library qsort routine. + * Algorithm: 1) create an array of pointers to the elements of v. 2) + * Pass this array to qsort. 3) after sorting the difference between + * the pointer value and the first pointer value gives its original + * position in the array. Use this to set the values of inds. + */ + +igraph_error_t igraph_vector_ptr_sort_ind(igraph_vector_ptr_t *v, + igraph_vector_int_t *inds, cmp_t *cmp) { + igraph_int_t i; + uintptr_t *vind, first; + igraph_int_t n = igraph_vector_ptr_size(v); + + IGRAPH_CHECK(igraph_vector_int_resize(inds, n)); + if (n == 0) { + return IGRAPH_SUCCESS; + } + + vind = IGRAPH_CALLOC(n, uintptr_t); + if (vind == 0) { + IGRAPH_ERROR("igraph_vector_ptr_sort_ind failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + for (i = 0; i < n; i++) { + vind[i] = (uintptr_t) &VECTOR(*v)[i]; + } + + first = vind[0]; + + igraph_qsort_r(vind, n, sizeof(vind[0]), (void*)cmp, igraph_vector_ptr_i_sort_ind_cmp); + + for (i = 0; i < n; i++) { + VECTOR(*inds)[i] = (vind[i] - first) / sizeof(uintptr_t); + } + + IGRAPH_FREE(vind); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_permute + * \brief Permutes the elements of a pointer vector in place according to an index vector. + * + * + * This function takes a vector \c v and a corresponding index vector \c ind, + * and permutes the elements of \c v such that \c v[ind[i]] is moved to become + * \c v[i] after the function is executed. + * + * + * It is an error to call this function with an index vector that does not + * represent a valid permutation. Each element in the index vector must be + * between 0 and the length of the vector minus one (inclusive), and each such + * element must appear only once. The function does not attempt to validate the + * index vector. + * + * + * The index vector that this function takes is compatible with the index vector + * returned from \ref igraph_vector_ptr_sort_ind(); passing in the index vector + * from \ref igraph_vector_ptr_sort_ind() will sort the original vector. + * + * + * As a special case, this function allows the index vector to be \em shorter + * than the vector being permuted, in which case the elements whose indices do + * not occur in the index vector will be removed from the vector. + * + * \param v the vector to permute + * \param ind the index vector + * + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: O(n), the size of the vector. + */ +igraph_error_t igraph_vector_ptr_permute(igraph_vector_ptr_t* v, const igraph_vector_int_t* index) { + IGRAPH_ASSERT(v != NULL); + IGRAPH_ASSERT(v->stor_begin != NULL); + IGRAPH_ASSERT(index != NULL); + IGRAPH_ASSERT(index->stor_begin != NULL); + IGRAPH_ASSERT(igraph_vector_ptr_size(v) >= igraph_vector_int_size(index)); + + igraph_vector_ptr_t v_copy; + void** v_ptr; + igraph_int_t *ind_ptr; + + /* There is a more space-efficient algorithm that needs O(1) space only, + * but it messes up the index vector, which we don't want */ + + IGRAPH_CHECK(igraph_vector_ptr_init(&v_copy, igraph_vector_int_size(index))); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &v_copy); + + for ( + v_ptr = v_copy.stor_begin, ind_ptr = index->stor_begin; + ind_ptr < index->end; + v_ptr++, ind_ptr++ + ) { + *v_ptr = VECTOR(*v)[*ind_ptr]; + } + + IGRAPH_CHECK(igraph_vector_ptr_resize(v, igraph_vector_int_size(index))); + igraph_vector_ptr_copy_to(&v_copy, VECTOR(*v)); + + igraph_vector_ptr_destroy(&v_copy); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/cycles/cycle_bases.c b/src/cycles/cycle_bases.c new file mode 100644 index 0000000..c81feab --- /dev/null +++ b/src/cycles/cycle_bases.c @@ -0,0 +1,542 @@ +/* + igraph library. + Copyright (C) 2021-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_cycles.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_error.h" +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "cycles/order_cycle.h" + +/**** Fundamental cycles *****/ + +/* Computes fundamental cycles for the connected component containing 'start_vid', + * and appends them to 'result'. + * + * 'visited' must be a vector of length igraph_vcount(graph). + * visited[u] will be set to mark+1 or mark+2 for each visited vertex 'u'. + * No elements of 'visited' must have these values when calling this function. + * 'mark' can be specified in order to be able to re-use a 'visited' vector + * multiple times without having to re-set all its elements. + * + * During the operation of the function, mark+1 indicates that a vertex has been + * queued for processing, but not processed yet. mark+2 indicates that it has + * been processed. + */ +static igraph_error_t +igraph_i_fundamental_cycles_bfs( + const igraph_t *graph, + igraph_vector_int_list_t *result, + igraph_int_t start_vid, + igraph_int_t bfs_cutoff, + const igraph_inclist_t *inclist, + igraph_vector_int_t *visited, + igraph_int_t mark /* mark used in 'visited' */) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vector_int_t pred_edge; + igraph_vector_int_t u_back, v_back; + + if (start_vid < 0 || start_vid >= no_of_nodes) { + IGRAPH_ERROR("Invalid starting vertex id.", IGRAPH_EINVAL); + } + + if (mark > IGRAPH_INTEGER_MAX - 2) { + IGRAPH_ERROR("Graph too large for cycle basis.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&pred_edge, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&u_back, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_back, 0); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 0); + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, start_vid)); /* vertex id */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); /* distance from start_vid*/ + VECTOR(*visited)[start_vid] = mark + 1; /* mark as seen */ + VECTOR(pred_edge)[start_vid] = -1; /* non-valid predecessor edge id for root vertex */ + + while (! igraph_dqueue_int_empty(&q)) { + igraph_int_t v = igraph_dqueue_int_pop(&q); + igraph_int_t vdist = igraph_dqueue_int_pop(&q); + + igraph_vector_int_t *incs = igraph_inclist_get(inclist, v); + igraph_int_t n = igraph_vector_int_size(incs); + igraph_int_t i, j; + + IGRAPH_ALLOW_INTERRUPTION(); + + for (i=0; i < n; ++i) { + igraph_int_t e = VECTOR(*incs)[i]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + + if (e == VECTOR(pred_edge)[v]) { + /* do not follow the edge through which we came to v */ + continue; + } + + if (VECTOR(*visited)[u] == mark + 2) { + /* u has already been processed */ + continue; + } else if (VECTOR(*visited)[u] == mark + 1) { + /* u has been seen but not yet processed */ + + /* Found cycle edge u-v. Now we walk back up the BFS tree + * in order to find the common ancestor of u and v. We exploit + * that the distance of u from the start vertex is either the + * same as that of v, or one greater. */ + + igraph_int_t up = u, vp = v; + igraph_int_t u_back_len, v_back_len; + igraph_vector_int_t cycle; + + IGRAPH_CHECK(igraph_vector_int_push_back(&v_back, e)); + for (;;) { + igraph_int_t upe, vpe; + + if (up == vp) { + break; + } + + upe = VECTOR(pred_edge)[up]; + IGRAPH_CHECK(igraph_vector_int_push_back(&u_back, upe)); + up = IGRAPH_OTHER(graph, upe, up); + + if (up == vp) { + break; + } + + vpe = VECTOR(pred_edge)[vp]; + IGRAPH_CHECK(igraph_vector_int_push_back(&v_back, vpe)); + vp = IGRAPH_OTHER(graph, vpe, vp); + } + + u_back_len = igraph_vector_int_size(&u_back); + v_back_len = igraph_vector_int_size(&v_back); + IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, u_back_len + v_back_len); + + for (j=0; j < v_back_len; ++j) { + VECTOR(cycle)[j] = VECTOR(v_back)[j]; + } + for (j=0; j < u_back_len; ++j) { + VECTOR(cycle)[v_back_len + j] = VECTOR(u_back)[u_back_len - j - 1]; + } + + igraph_vector_int_clear(&v_back); + igraph_vector_int_clear(&u_back); + + IGRAPH_CHECK(igraph_vector_int_list_push_back(result, &cycle)); + IGRAPH_FINALLY_CLEAN(1); /* pass ownership of 'cycle' to 'result' */ + } else { + /* encountering u for the first time, queue it for processing */ + + /* Only queue vertices with distance at most 'bfs_cutoff' from the root. */ + /* Negative 'bfs_cutoff' indicates no cutoff. */ + if (bfs_cutoff < 0 || vdist < bfs_cutoff) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, u)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, vdist + 1)); + VECTOR(*visited)[u] = mark + 1; + VECTOR(pred_edge)[u] = e; + } + } + } + + VECTOR(*visited)[v] = mark + 2; /* mark v as processed */ + } /* ! igraph_dqueue_int_empty(&q) */ + + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&v_back); + igraph_vector_int_destroy(&u_back); + igraph_vector_int_destroy(&pred_edge); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_fundamental_cycles + * \brief Finds a fundamental cycle basis. + * + * \experimental + * + * This function computes a fundamental cycle basis associated with a breadth-first + * search tree of the graph. + * + * + * Edge directions are ignored. Multi-edges and self-loops are supported. + * + * \param graph The graph object. + * \param weights Currently unused. + * \param result An initialized integer vector list. The result will be stored here, + * each vector containing the edge IDs of a basis element. + * \param start_vid If negative, a complete fundamental cycle basis is returned. + * If a vertex ID, the fundamental cycles associated with the BFS tree rooted + * in that vertex will be returned, only for the weakly connected component + * containing that vertex. + * \param bfs_cutoff If negative, a complete cycle basis is returned. Otherwise, only + * cycles of length 2*bfs_cutoff + 1 or shorter are included. \p bfs_cutoff + * is used to limit the depth of the BFS tree when searching for cycle edges. + * \return Error code. + * + * \sa \ref igraph_minimum_cycle_basis() + * + * Time complexity: O(|V| + |E|). + */ +igraph_error_t igraph_fundamental_cycles( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_int_t start_vid, igraph_real_t bfs_cutoff) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t estimated_rank; + igraph_int_t i; + igraph_inclist_t inclist; + igraph_vector_int_t visited; /* see comments before igraph_i_fundamental_cycles_bfs() */ + + IGRAPH_UNUSED(weights); + + if (start_vid >= no_of_nodes) { + IGRAPH_ERROR("Vertex id out of range.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&visited, no_of_nodes); + + /* Compute cycle rank assuming that the graph is connected. */ + estimated_rank = no_of_edges - no_of_nodes + 1; + estimated_rank = estimated_rank < 0 ? 0 : estimated_rank; + + igraph_vector_int_list_clear(result); + IGRAPH_CHECK(igraph_vector_int_list_reserve(result, estimated_rank)); + + if (start_vid < 0) { + for (i=0; i < no_of_nodes; ++i) { + if (! VECTOR(visited)[i]) { + IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs(graph, result, i, bfs_cutoff, &inclist, + &visited, /* mark */ 0)); + } + } + } else { + IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs(graph, result, start_vid, bfs_cutoff, &inclist, + &visited, /* mark */ 0)); + } + + igraph_vector_int_destroy(&visited); + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/***** Minimum weight cycle basis *****/ + +/* In this implementation, the cycle vectors (basis elements) are stored as a sparse representation: + * a sorted list of edge indices. */ + + +/* qsort-compatible comparison for sparse cycle vectors: shorter ones come first, use lexicographic + * order for equal length ones. Lexicographic order helps keep row insertion into the reduced matrix + * efficient during Gaussian elimination, by ensuring that insertions usually happen near the end. */ +static int cycle_cmp(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2) { + igraph_int_t n1 = igraph_vector_int_size(v1), n2 = igraph_vector_int_size(v2); + + if (n1 < n2) { + return -1; + } else if (n1 > n2) { + return 1; + } else { + return igraph_vector_int_lex_cmp(v1, v2); + } +} + +/* Adding cycle vectors produces the symmetric difference of the corresponding edge sets. */ +static igraph_error_t cycle_add(const igraph_vector_int_t *a, const igraph_vector_int_t *b, igraph_vector_int_t *res) { + igraph_int_t na = igraph_vector_int_size(a), nb = igraph_vector_int_size(b); + const igraph_int_t *pa = VECTOR(*a), *pb = VECTOR(*b); + const igraph_int_t *pa_end = pa + na, *pb_end = pb + nb; + + igraph_vector_int_clear(res); + for (;;) { + while (pa != pa_end && pb != pb_end && *pa < *pb) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, *pa)); + pa++; + } + while (pa != pa_end && pb != pb_end && *pa == *pb) { + pa++; pb++; + } + while (pa != pa_end && pb != pb_end && *pb < *pa) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, *pb)); + pb++; + } + if (pa == pa_end) { + while (pb != pb_end) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, *pb)); + pb++; + } + break; + } + if (pb == pb_end) { + while (pa != pa_end) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, *pa)); + pa++; + } + break; + } + } + + return IGRAPH_SUCCESS; +} + + +#define MATROW(m, i) (&VECTOR(m)[i]) +#define MATEL(m, i, j) VECTOR(*MATROW(m, i))[j] + +/* Gaussian elimination for sparse cycle vectors. 'reduced_matrix' is always maintained + * in row-echelon form. This function decides if 'cycle' is linearly independent of this + * matrix, and if not, it adds it to the matrix. */ +static igraph_error_t gaussian_elimination(igraph_vector_int_list_t *reduced_matrix, + const igraph_vector_int_t *cycle, + igraph_bool_t *independent) { + + const igraph_int_t nrow = igraph_vector_int_list_size(reduced_matrix); + igraph_int_t i; + + igraph_vector_int_t work, tmp; + + IGRAPH_CHECK(igraph_vector_int_init_copy(&work, cycle)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &work); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + + for (i=0; i < nrow; ++i) { + igraph_vector_int_t *row = MATROW(*reduced_matrix, i); + + if ( VECTOR(*row)[0] < VECTOR(work)[0] ) { + continue; + } else if ( VECTOR(*row)[0] == VECTOR(work)[0] ) { + IGRAPH_CHECK(cycle_add(row, &work, &tmp)); + if (igraph_vector_int_empty(&tmp)) { + *independent = false; + igraph_vector_int_destroy(&work); + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; + } + igraph_vector_int_swap(&work, &tmp); + } else { /* VECTOR(*row)[0] > VECTOR(work)[0] */ + break; + } + } + + /* 'cycle' was linearly independent, insert new row into matrix */ + *independent = true; + IGRAPH_CHECK(igraph_vector_int_list_insert(reduced_matrix, i, &work)); /* transfers ownership */ + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); /* +1, transferring ownership of 'work' to 'reduced_matrix' */ + + return IGRAPH_SUCCESS; +} + +#undef MATEL +#undef MATROW + + +/** + * \function igraph_minimum_cycle_basis + * \brief Computes a minimum weight cycle basis. + * + * \experimental + * + * This function computes a minimum weight cycle basis of a graph. Currently, + * a modified version of Horton's algorithm is used that allows for cutoffs. + * + * + * Edge directions are ignored. Multi-edges and self-loops are supported. + * + * + * References: + * + * + * Horton, J. D. (1987) + * A polynomial-time algorithm to find the shortest cycle basis of a graph, + * SIAM Journal on Computing, 16 (2): 358–366. + * https://doi.org/10.1137%2F0216026 + * + * \param graph The graph object. + * \param weights Currently unused. + * \param result An initialized integer vector list, the elements of the cycle + * basis will be stored here as vectors of edge IDs. + * \param bfs_cutoff If negative, an exact minimum cycle basis is returned. Otherwise + * only those cycles in the result will be part of some minimum cycle basis which + * are of size 2*bfs_cutoff + 1 or smaller. Cycles longer than this limit + * may not be of the smallest possible size. + * \p bfs_cutoff is used to limit the depth of the BFS tree when computing candidate + * cycles. Specifying a bfs_cutoff can speed up the computation substantially. + * \param complete Boolean value. Used only when \p bfs_cutoff was given. + * If true, a complete basis is returned. If false, only cycles not greater + * than 2*bfs_cutoff + 1 are returned. This may save computation + * time, however, the result will not span the entire cycle space. + * \param use_cycle_order If true, each cycle is returned in natural order: + * the edge IDs will appear ordered along the cycle. This comes at a small + * performance cost. If false, no guarantees are given about the ordering + * of edge IDs within cycles. This parameter exists solely to control + * performance tradeoffs. + * \return Error code. + * + * \sa \ref igraph_fundamental_cycles() + * + * Time complexity: TODO. + */ +igraph_error_t igraph_minimum_cycle_basis( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *result, + igraph_real_t bfs_cutoff, + igraph_bool_t complete, igraph_bool_t use_cycle_order) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t rank; + igraph_vector_int_list_t candidates; + + IGRAPH_UNUSED(weights); + + /* Compute candidate elements for the minimum weight basis. */ + { + igraph_inclist_t inclist; + igraph_vector_int_t visited; /* visited[v] % 3 is zero for unvisited vertices, see igraph_i_fundamental_cycles_bfs() */ + igraph_vector_int_t degrees; + igraph_int_t no_of_comps; + igraph_int_t mark; + + /* We use the degrees to avoid doing a BFS from vertices with d < 3, except in special cases. + * Degrees cannot be computed from the inclist because there we use IGRAPH_LOOPS_ONCE. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_of_comps, IGRAPH_WEAK)); + rank = no_of_edges - no_of_nodes + no_of_comps; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&visited, no_of_nodes); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + /* TODO: estimate space to reserve. 'rank' is a lower bound only. */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&candidates, 0); + IGRAPH_CHECK(igraph_vector_int_list_reserve(&candidates, rank)); + + mark = 0; + for (igraph_int_t i=0; i < no_of_nodes; ++i) { + igraph_int_t degree = VECTOR(degrees)[i]; + igraph_bool_t vis = VECTOR(visited)[i] % 3 != 0; /* was vertex i visited already? */ + + /* Generally, we only need to run a BFS from vertices of degree 3 or greater. + * The exception is a connected component which is itself a cycle, and therefore + * only contains vertices of degree 2. Thus from unvisited vertices we always run + * a full BFS while from already visited ones only if their degree is at least 3. */ + + /* TODO: mark entire component as visited, not just vertex. */ + if (degree <= 1 || (vis && degree < 3)) { + continue; + } + + /* TODO: BFS is only necessary from a feedback vertex set, find fast FVS approximation algorithm. */ + + IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs( + graph, &candidates, i, (vis || !complete) ? bfs_cutoff : -1, &inclist, &visited, mark)); + mark += 3; + } + + igraph_inclist_destroy(&inclist); + igraph_vector_int_destroy(&visited); + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(3); + } + + /* Sort candidates by size (= weight) and remove duplicates. */ + { + igraph_int_t cand_count = igraph_vector_int_list_size(&candidates); + + for (igraph_int_t i=0; i < cand_count; ++i) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(&candidates, i)); + } + igraph_vector_int_list_sort(&candidates, &cycle_cmp); + igraph_vector_int_list_remove_consecutive_duplicates(&candidates, igraph_vector_int_all_e); + } + + igraph_vector_int_list_clear(result); + IGRAPH_CHECK(igraph_vector_int_list_reserve(result, rank)); + + /* Find a complete basis, starting with smallest elements. */ + /* This is typically the slowest part of the algorithm. */ + { + igraph_int_t cand_len = igraph_vector_int_list_size(&candidates); + igraph_vector_int_list_t reduced_matrix; + igraph_bool_t independent; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&reduced_matrix, 0); + + for (igraph_int_t i=0; i < cand_len; ++i) { + const igraph_vector_int_t *cycle = igraph_vector_int_list_get_ptr(&candidates, i); + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(gaussian_elimination(&reduced_matrix, cycle, &independent)); + if (independent) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(result, cycle)); + } + + if (igraph_vector_int_list_size(&reduced_matrix) == rank) { + /* We have a complete basis. */ + break; + } + } + + igraph_vector_int_list_destroy(&reduced_matrix); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_list_destroy(&candidates); + IGRAPH_FINALLY_CLEAN(1); + + if (use_cycle_order) { + igraph_int_t result_size = igraph_vector_int_list_size(result); + igraph_vector_int_t tmp; + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + for (igraph_int_t i=0; i < result_size; ++i) { + igraph_vector_int_t *cycle = igraph_vector_int_list_get_ptr(result, i); + IGRAPH_CHECK(igraph_vector_int_update(&tmp, cycle)); + IGRAPH_CHECK(igraph_i_order_cycle(graph, &tmp, cycle)); + } + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/cycles/feedback_sets.c b/src/cycles/feedback_sets.c new file mode 100644 index 0000000..09f5c7a --- /dev/null +++ b/src/cycles/feedback_sets.c @@ -0,0 +1,1326 @@ +/* + igraph library. + Copyright (C) 2011-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_cycles.h" +#include "cycles/feedback_sets.h" + +#include "igraph_bitset.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_stack.h" +#include "igraph_structural.h" +#include "igraph_vector.h" +#include "igraph_vector_list.h" +#include "igraph_visitor.h" + +#include "internal/glpk_support.h" +#include "math/safe_intop.h" + +#include + +/** + * \param found If not NULL, will indicate if any cycles were found. + * \param A bitset the same length as the edge count, indicating which edges + * to consider non-existent. + * + * See igraph_find_cycle() for the other parameters. + * + * \return Error code. + */ +static igraph_error_t igraph_i_find_cycle(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_bool_t *found, + igraph_neimode_t mode, + const igraph_bitset_t *removed) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_stack_int_t stack; + igraph_vector_int_t inc; + igraph_vector_int_t vpath, epath; + igraph_vector_char_t seen; /* 0 = unseen, 1 = acestor of current, 2 = seen, non-ancestor */ + igraph_int_t ea, va; + igraph_int_t depth; + + if (vertices) { + igraph_vector_int_clear(vertices); + } + if (edges) { + igraph_vector_int_clear(edges); + } + if (found) { + *found = false; + } + + if (ecount == 0) { + return IGRAPH_SUCCESS; + } + +#define PATH_PUSH(v, e) \ + do { \ + IGRAPH_CHECK(igraph_vector_int_push_back(&epath, e)); \ + IGRAPH_CHECK(igraph_vector_int_push_back(&vpath, v)); \ + VECTOR(seen)[v] = 1; \ + } while (0) + +#define PATH_POP() \ + do { \ + igraph_vector_int_pop_back(&epath); \ + VECTOR(seen)[igraph_vector_int_pop_back(&vpath)] = 2; \ + } while (0) + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vpath, 100); + igraph_vector_int_clear(&vpath); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&epath, 100); + igraph_vector_int_clear(&epath); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inc, 10); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&seen, vcount); + IGRAPH_STACK_INT_INIT_FINALLY(&stack, 200); + + for (igraph_int_t v=0; v < vcount; v++) { + if (VECTOR(seen)[v]) { + continue; + } + + IGRAPH_CHECK(igraph_stack_int_push(&stack, -1)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, v)); + + while (! igraph_stack_int_empty(&stack)) { + igraph_int_t x = igraph_stack_int_pop(&stack); + if (x == -1) { + PATH_POP(); + continue; + } else { + va = x; + ea = igraph_stack_int_pop(&stack); + + if (VECTOR(seen)[va] == 1) { + goto finish; + } else if (VECTOR(seen)[va] == 2) { + continue; + } + } + + PATH_PUSH(va, ea); + + IGRAPH_CHECK(igraph_stack_int_push(&stack, -1)); + IGRAPH_CHECK(igraph_incident(graph, &inc, va, mode, IGRAPH_LOOPS)); + igraph_int_t n = igraph_vector_int_size(&inc); + for (igraph_int_t i=0; i < n; i++) { + igraph_int_t eb = VECTOR(inc)[i]; + igraph_int_t vb = IGRAPH_OTHER(graph, eb, va); + if (eb == ea) continue; + if (VECTOR(seen)[vb] == 2) continue; + if (removed && IGRAPH_BIT_TEST(*removed, eb)) continue; + IGRAPH_CHECK(igraph_stack_int_push(&stack, eb)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, vb)); + } + } + } + + +finish: + + igraph_stack_int_destroy(&stack); + igraph_vector_char_destroy(&seen); + igraph_vector_int_destroy(&inc); + IGRAPH_FINALLY_CLEAN(3); + + depth = igraph_vector_int_size(&vpath); + + if (depth > 0) { + igraph_int_t i = depth; + while (VECTOR(vpath)[i-1] != va) i--; + for (; i < depth; i++) { + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_push_back(vertices, VECTOR(vpath)[i])); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, VECTOR(epath)[i])); + } + } + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_push_back(vertices, va)); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, ea)); + } + if (found) { + *found = true; + } + } + + igraph_vector_int_destroy(&epath); + igraph_vector_int_destroy(&vpath); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; + +#undef PATH_PUSH +#undef PATH_POP +} + + +/** + * \function igraph_find_cycle + * \brief Finds a single cycle in the graph. + * + * This function returns a cycle of the graph, if there is one. If the graph + * is acyclic, it returns empty vectors. + * + * \param graph The input graph. + * \param vertices Pointer to an integer vector. If a cycle is found, its + * vertices will be stored here. Otherwise the vector will be empty. + * \param edges Pointer to an integer vector. If a cycle is found, its + * edges will be stored here. Otherwise the vector will be empty. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + * + * \sa \ref igraph_is_acyclic() to determine if a graph is acyclic, + * without returning a specific cycle; \ref igraph_simple_cycles() + * to list all cycles in a graph. + */ +igraph_error_t igraph_find_cycle(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_neimode_t mode) { + + /* If the graph is cached to be acyclic, we don't need to run the algorithm. */ + + igraph_bool_t known_acyclic = false; + igraph_bool_t found; + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for finding cycles.", IGRAPH_EINVAL); + } + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (mode == IGRAPH_ALL) /* undirected */ { + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST)) { + known_acyclic = true; + } + } else /* directed */ { + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_DAG) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_DAG)) { + known_acyclic = true; + } + } + + if (known_acyclic) { + if (vertices) { + igraph_vector_int_clear(vertices); + } + if (edges) { + igraph_vector_int_clear(edges); + } + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_i_find_cycle(graph, vertices, edges, &found, mode, NULL)); + + if (! found) { + if (mode == IGRAPH_ALL) /* undirected */ { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true); + } else /* directed */ { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_DAG, true); + } + } + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_feedback_arc_set + * \brief Feedback arc set of a graph using exact or heuristic methods. + * + * A feedback arc set is a set of edges whose removal makes the graph acyclic. + * We are usually interested in \em minimum feedback arc sets, i.e. sets of edges + * whose total weight is the smallest among all the feedback arc sets. + * + * + * For undirected graphs, the solution is simple: one has to find a maximum weight + * spanning tree and then remove all the edges not in the spanning tree. For directed + * graphs, this is an NP-complete problem, and various heuristics are usually used to + * find an approximate solution to the problem. This function implements both exact + * methods and heuristics, selectable with the \p algo parameter. + * + * + * References: + * + * + * Eades P, Lin X and Smyth WF: + * A fast and effective heuristic for the feedback arc set problem. + * Information Processing Letters 47(6), pp 319-323 (1993). + * https://doi.org/10.1016/0020-0190(93)90079-O + * + * + * Baharev A, Hermann S, Arnold N and Tobias A: + * An Exact Method for the Minimum Feedback Arc Set Problem. + * ACM Journal of Experimental Algorithmics 26, 1–28 (2021). + * https://doi.org/10.1145/3446429. + * + * \param graph The graph object. + * \param result An initialized vector, the result will be written here. + * \param weights Weight vector or \c NULL if no weights are specified. + * \param algo The algorithm to use to solve the problem if the graph is directed. + * Possible values: + * \clist + * \cli IGRAPH_FAS_EXACT_IP + * Finds a \em minimum feedback arc set using integer programming (IP), + * automatically selecting the best method of this type (currently + * always \c IGRAPH_FAS_EXACT_IP_CG). The complexity is of course + * at least exponential. + * \cli IGRAPH_FAS_EXACT_IP_CG + * This is an integer programming approach based on a minimum set cover + * formulation and using incremental constraint generation (CG), added + * in igraph 0.10.14. We minimize sum_e w_e b_e subject to + * the constraints sum_e c_e b_e >= 1 for all cycles \c c. + * Here \c w_e is the weight of edge \c e, \c b_e is a binary variable + * (0 or 1) indicating whether edge \c e is in the feedback set, + * and \c c_e is a binary coefficient indicating whether edge \c e + * is in cycle \c c. The constraint expresses the requirement that all + * cycles must intersect with (be broken by) the edge set represented + * by \c b. Since there are a very large number of cycles in the graph, + * constraints are generated incrementally, iteratively adding some cycles + * that do not intersect with the current edge set \c b, then solving for + * \c b again, until finally no unbroken cycles remain. This approach is + * similar to that described by Baharev et al (though with a simpler + * cycle generation scheme), and to what is implemented by SageMath's. + * \c feedback_edge_set function. + * \cli IGRAPH_FAS_EXACT_IP_TI + * This is another integer programming approach based on finding a + * maximum (largest weight) edge set that adheres to a topological + * order. It uses the common formulation through triangle inequalities + * (TI), see Section 3.1 of Baharev et al (2021) for an overview. This + * method was used before igraph 0.10.14, and is typically much slower + * than \c IGRAPH_FAS_EXACT_IP_CG. + * \cli IGRAPH_FAS_APPROX_EADES + * Finds a feedback arc set using the heuristic of Eades, Lin and + * Smyth (1993). This is guaranteed to be smaller than |E|/2 - |V|/6, + * and it is linear in the number of edges (i.e. O(|E|)) to compute. + * \endclist + * + * \return Error code: + * \c IGRAPH_EINVAL if an unknown method was specified or the weight vector + * is invalid. + * + * \example examples/simple/igraph_feedback_arc_set.c + * \example examples/simple/igraph_feedback_arc_set_ip.c + * + * Time complexity: depends on \p algo, see the time complexities there. + */ +igraph_error_t igraph_feedback_arc_set( + const igraph_t *graph, + igraph_vector_int_t *result, + const igraph_vector_t *weights, + igraph_fas_algorithm_t algo) { + + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Weight vector length must match the number of edges.", IGRAPH_EINVAL); + } + if (! igraph_vector_is_all_finite(weights)) { + IGRAPH_ERROR("Weights must not be infinite or NaN.", IGRAPH_EINVAL); + } + } + + if (!igraph_is_directed(graph)) { + return igraph_i_feedback_arc_set_undirected(graph, result, weights, NULL); + } + + switch (algo) { + case IGRAPH_FAS_EXACT_IP: + case IGRAPH_FAS_EXACT_IP_CG: + return igraph_i_feedback_arc_set_ip_cg(graph, result, weights); + + case IGRAPH_FAS_EXACT_IP_TI: + return igraph_i_feedback_arc_set_ip_ti(graph, result, weights); + + case IGRAPH_FAS_APPROX_EADES: + return igraph_i_feedback_arc_set_eades(graph, result, weights, NULL); + + default: + IGRAPH_ERROR("Invalid feedback arc set algorithm.", IGRAPH_EINVAL); + } +} + + +/** + * \function igraph_feedback_vertex_set + * \brief Feedback vertex set of a graph. + * + * A feedback vertex set is a set of vertices whose removal makes the graph + * acyclic. Finding a \em minimum feedback vertex set is an NP-complete + * problem, both on directed and undirected graphs. + * + * \param graph The graph. + * \param result An initialized vector, the result will be written here. + * \param vertex_weights Vertex weight vector or \c NULL if no weights are specified. + * \param algo Algorithm to use. Possible values: + * \clist + * \cli IGRAPH_FVS_EXACT_IP + * Finds a \em miniumum feedback vertex set using integer programming + * (IP). The complexity is of course at least exponential. Currently + * this method uses an approach analogous to that of the + * \c IGRAPH_FAS_EXACT_IP_CG algorithm of \ref igraph_feedback_arc_set(). + * \endclist + * + * \return Error code. + * + * Time complexity: depends on \p algo, see the time complexities there. + */ +igraph_error_t igraph_feedback_vertex_set( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *vertex_weights, igraph_fvs_algorithm_t algo) { + + if (vertex_weights) { + if (igraph_vector_size(vertex_weights) != igraph_vcount(graph)) { + IGRAPH_ERROR("Vertex weight vector length must match the number of vertices.", IGRAPH_EINVAL); + } + if (! igraph_vector_is_all_finite(vertex_weights)) { + IGRAPH_ERROR("Vertex weights must not be infinite or NaN.", IGRAPH_EINVAL); + } + } + + switch (algo) { + case IGRAPH_FVS_EXACT_IP: + return igraph_i_feedback_vertex_set_ip_cg(graph, result, vertex_weights); + + default: + IGRAPH_ERROR("Invalid feedback vertex set algorithm.", IGRAPH_EINVAL); + } +} + + +/** + * Solves the feedback arc set problem for undirected graphs. + */ +igraph_error_t igraph_i_feedback_arc_set_undirected(const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights, igraph_vector_int_t *layering) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_nodes > 0 ? no_of_nodes - 1 : 0); + if (weights) { + /* Find a maximum weight spanning tree. igraph has a routine for minimum + * spanning trees, so we negate the weights */ + igraph_vector_t vcopy; + IGRAPH_CHECK(igraph_vector_init_copy(&vcopy, weights)); + IGRAPH_FINALLY(igraph_vector_destroy, &vcopy); + igraph_vector_scale(&vcopy, -1); + IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, &vcopy, IGRAPH_MST_AUTOMATIC)); + igraph_vector_destroy(&vcopy); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Any spanning tree will do */ + IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, NULL, IGRAPH_MST_AUTOMATIC)); + } + + /* Now we have a bunch of edges that constitute a spanning forest. We have + * to come up with a layering, and return those edges that are not in the + * spanning forest */ + igraph_vector_int_sort(&edges); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, -1)); /* guard element */ + + if (result) { + igraph_vector_int_clear(result); + for (igraph_int_t i = 0, j = 0; i < no_of_edges; i++) { + if (i == VECTOR(edges)[j]) { + j++; + continue; + } + IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); + } + } + + if (layering) { + igraph_vector_t degrees; + igraph_vector_int_t roots; + + IGRAPH_VECTOR_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&roots, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, °rees, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_NO_LOOPS, weights)); + IGRAPH_CHECK(igraph_vector_sort_ind(°rees, &roots, IGRAPH_DESCENDING)); + + IGRAPH_CHECK(igraph_bfs(graph, + /* root = */ 0, + /* roots = */ &roots, + /* mode = */ IGRAPH_OUT, + /* unreachable = */ false, + /* restricted = */ NULL, + /* order = */ NULL, + /* rank = */ NULL, + /* parents = */ NULL, + /* pred = */ NULL, + /* succ = */ NULL, + /* dist = */ layering, + /* callback = */ NULL, + /* extra = */ NULL)); + + igraph_vector_destroy(°rees); + igraph_vector_int_destroy(&roots); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * Solves the feedback arc set problem using the heuristics of Eades et al. + */ +igraph_error_t igraph_i_feedback_arc_set_eades(const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights, igraph_vector_int_t *layers) { + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t nodes_left; + igraph_int_t neis_size; + igraph_dqueue_int_t sources, sinks; + igraph_vector_int_t neis; + igraph_vector_int_t indegrees, outdegrees; + igraph_vector_t instrengths, outstrengths; + igraph_vector_int_t ordering; + igraph_int_t order_next_pos = 0, order_next_neg = -1; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ordering, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&sources, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&sinks, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&indegrees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outdegrees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&instrengths, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outstrengths, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, &indegrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph, &outdegrees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS)); + + if (weights) { + IGRAPH_CHECK(igraph_strength(graph, &instrengths, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outstrengths, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS, weights)); + } else { + for (igraph_int_t u = 0; u < no_of_nodes; u++) { + VECTOR(instrengths)[u] = VECTOR(indegrees)[u]; + VECTOR(outstrengths)[u] = VECTOR(outdegrees)[u]; + } + } + + /* Find initial sources and sinks */ + nodes_left = no_of_nodes; + for (igraph_int_t u = 0; u < no_of_nodes; u++) { + if (VECTOR(indegrees)[u] == 0) { + if (VECTOR(outdegrees)[u] == 0) { + /* Isolated vertex, we simply ignore it */ + nodes_left--; + VECTOR(ordering)[u] = order_next_pos++; + VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; + } else { + /* This is a source */ + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, u)); + } + } else if (VECTOR(outdegrees)[u] == 0) { + /* This is a sink */ + IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, u)); + } + } + + /* While we have any nodes left... */ + while (nodes_left > 0) { + + /* (1) Remove the sources one by one */ + while (!igraph_dqueue_int_empty(&sources)) { + const igraph_int_t u = igraph_dqueue_int_pop(&sources); + /* Add the node to the ordering */ + VECTOR(ordering)[u] = order_next_pos++; + /* Exclude the node from further searches */ + VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; + /* Get the neighbors and decrease their degrees */ + IGRAPH_CHECK(igraph_incident(graph, &neis, u, IGRAPH_OUT, IGRAPH_LOOPS)); + neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < neis_size; i++) { + const igraph_int_t eid = VECTOR(neis)[i]; + const igraph_int_t w = IGRAPH_TO(graph, eid); + if (VECTOR(indegrees)[w] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(indegrees)[w]--; + VECTOR(instrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(indegrees)[w] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, w)); + } + } + nodes_left--; + } + + /* (2) Remove the sinks one by one */ + while (!igraph_dqueue_int_empty(&sinks)) { + const igraph_int_t u = igraph_dqueue_int_pop(&sinks); + /* Maybe the vertex became sink and source at the same time, hence it + * was already removed in the previous iteration. Check it. */ + if (VECTOR(indegrees)[u] < 0) { + continue; + } + /* Add the node to the ordering */ + VECTOR(ordering)[u] = order_next_neg--; + /* Exclude the node from further searches */ + VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; + /* Get the neighbors and decrease their degrees */ + IGRAPH_CHECK(igraph_incident(graph, &neis, u, IGRAPH_IN, IGRAPH_LOOPS)); + neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < neis_size; i++) { + const igraph_int_t eid = VECTOR(neis)[i]; + const igraph_int_t w = IGRAPH_FROM(graph, eid); + if (VECTOR(outdegrees)[w] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(outdegrees)[w]--; + VECTOR(outstrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(outdegrees)[w] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, w)); + } + } + nodes_left--; + } + + /* (3) No more sources or sinks. Find the node with the largest + * difference between its out-strength and in-strength */ + igraph_int_t v = -1; + igraph_real_t maxdiff = -IGRAPH_INFINITY; + for (igraph_int_t u = 0; u < no_of_nodes; u++) { + if (VECTOR(outdegrees)[u] < 0) { + continue; + } + igraph_real_t diff = VECTOR(outstrengths)[u] - VECTOR(instrengths)[u]; + if (diff > maxdiff) { + maxdiff = diff; + v = u; + } + } + if (v >= 0) { + /* Remove vertex v */ + VECTOR(ordering)[v] = order_next_pos++; + /* Remove outgoing edges */ + IGRAPH_CHECK(igraph_incident(graph, &neis, v, IGRAPH_OUT, IGRAPH_LOOPS)); + neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < neis_size; i++) { + const igraph_int_t eid = VECTOR(neis)[i]; + const igraph_int_t w = IGRAPH_TO(graph, eid); + if (VECTOR(indegrees)[w] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(indegrees)[w]--; + VECTOR(instrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(indegrees)[w] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, w)); + } + } + /* Remove incoming edges */ + IGRAPH_CHECK(igraph_incident(graph, &neis, v, IGRAPH_IN, IGRAPH_LOOPS)); + neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < neis_size; i++) { + const igraph_int_t eid = VECTOR(neis)[i]; + const igraph_int_t w = IGRAPH_FROM(graph, eid); + if (VECTOR(outdegrees)[w] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(outdegrees)[w]--; + VECTOR(outstrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(outdegrees)[w] == 0 && VECTOR(indegrees)[w] > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, w)); + } + } + + VECTOR(outdegrees)[v] = -1; + VECTOR(indegrees)[v] = -1; + nodes_left--; + } + } + + igraph_vector_destroy(&outstrengths); + igraph_vector_destroy(&instrengths); + igraph_vector_int_destroy(&outdegrees); + igraph_vector_int_destroy(&indegrees); + igraph_dqueue_int_destroy(&sinks); + igraph_dqueue_int_destroy(&sources); + IGRAPH_FINALLY_CLEAN(6); + + /* Tidy up the ordering */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(ordering)[i] < 0) { + VECTOR(ordering)[i] += no_of_nodes; + } + } + + /* Find the feedback edges based on the ordering */ + if (result) { + igraph_vector_int_clear(result); + for (igraph_int_t eid = 0; eid < no_of_edges; eid++) { + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + if (from == to || VECTOR(ordering)[from] > VECTOR(ordering)[to]) { + IGRAPH_CHECK(igraph_vector_int_push_back(result, eid)); + } + } + } + + /* If we have also requested a layering, return that as well */ + if (layers) { + igraph_vector_int_t ranks; + + IGRAPH_CHECK(igraph_vector_int_resize(layers, no_of_nodes)); + igraph_vector_int_null(layers); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ranks, 0); + + IGRAPH_CHECK(igraph_vector_int_sort_ind(&ordering, &ranks, IGRAPH_ASCENDING)); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t from = VECTOR(ranks)[i]; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, from, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < neis_size; j++) { + igraph_int_t to = VECTOR(neis)[j]; + if (from == to) { + continue; + } + if (VECTOR(ordering)[from] > VECTOR(ordering)[to]) { + continue; + } + if (VECTOR(*layers)[to] < VECTOR(*layers)[from] + 1) { + VECTOR(*layers)[to] = VECTOR(*layers)[from] + 1; + } + } + } + + igraph_vector_int_destroy(&ranks); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Free the ordering vector */ + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&ordering); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * Solves the feedback arc set problem with integer programming, + * using the triangle inequalities formulation. + */ +igraph_error_t igraph_i_feedback_arc_set_ip_ti( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights) { +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); +#else + + const igraph_int_t no_of_vertices = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_components; + igraph_vector_int_t membership, *vec; + igraph_vector_int_t ordering, vertex_remapping; + igraph_vector_int_list_t vertices_by_components, edges_by_components; + igraph_int_t i, j, k, l, m, n, from, to, no_of_rows, n_choose_2; + igraph_real_t weight; + glp_prob *ip; + glp_iocp parm; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ordering, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_remapping, no_of_vertices); + + igraph_vector_int_clear(result); + + /* Decompose the graph into connected components */ + IGRAPH_CHECK(igraph_connected_components(graph, &membership, NULL, &no_of_components, IGRAPH_WEAK)); + + /* Construct vertex and edge lists for each of the components */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&vertices_by_components, no_of_components); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&edges_by_components, no_of_components); + for (i = 0; i < no_of_vertices; i++) { + j = VECTOR(membership)[i]; + vec = igraph_vector_int_list_get_ptr(&vertices_by_components, j); + IGRAPH_CHECK(igraph_vector_int_push_back(vec, i)); + } + for (i = 0; i < no_of_edges; i++) { + j = VECTOR(membership)[IGRAPH_FROM(graph, i)]; + vec = igraph_vector_int_list_get_ptr(&edges_by_components, j); + IGRAPH_CHECK(igraph_vector_int_push_back(vec, i)); + } + +#define VAR2IDX(i, j) (i*(n-1)+j-(i+1)*i/2) + + /* Configure GLPK */ + IGRAPH_GLPK_SETUP(); + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_DTH; + parm.bt_tech = GLP_BT_BLB; + parm.pp_tech = GLP_PP_ALL; + parm.presolve = GLP_ON; + parm.binarize = GLP_OFF; + parm.cb_func = igraph_i_glpk_interruption_hook; + + /* Solve an IP for feedback arc sets in each of the components */ + for (i = 0; i < no_of_components; i++) { + igraph_vector_int_t *vertices_in_comp = igraph_vector_int_list_get_ptr(&vertices_by_components, i); + igraph_vector_int_t *edges_in_comp = igraph_vector_int_list_get_ptr(&edges_by_components, i); + + /* + * Let x_ij denote whether layer(i) < layer(j). + * + * The standard formulation of the problem is as follows: + * + * max sum_{i,j} w_ij x_ij + * + * subject to + * + * (1) x_ij + x_ji = 1 (i.e. either layer(i) < layer(j) or layer(i) > layer(j)) + * for all i < j + * (2) x_ij + x_jk + x_ki <= 2 for all i < j, i < k, j != k + * + * Note that x_ij = 1 implies that x_ji = 0 and vice versa; in other words, + * x_ij = 1 - x_ji. Thus, we can get rid of the (1) constraints and half of the + * x_ij variables (where j < i) if we rewrite constraints of type (2) as follows: + * + * (2a) x_ij + x_jk - x_ik <= 1 for all i < j, i < k, j < k + * (2b) x_ij - x_kj - x_ik <= 0 for all i < j, i < k, j > k + * + * The goal function then becomes: + * + * max sum_{i INT_MAX) { + IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); + } + + if (n_choose_2 > 0) { + glp_add_cols(ip, (int) n_choose_2); + for (j = 1; j <= n_choose_2; j++) { + glp_set_col_kind(ip, (int) j, GLP_BV); + } + } + + /* Set up coefficients in the goal function */ + k = igraph_vector_int_size(edges_in_comp); + for (j = 0; j < k; j++) { + l = VECTOR(*edges_in_comp)[j]; + from = VECTOR(vertex_remapping)[IGRAPH_FROM(graph, l)]; + to = VECTOR(vertex_remapping)[IGRAPH_TO(graph, l)]; + if (from == to) { + continue; + } + + weight = weights ? VECTOR(*weights)[l] : 1; + + if (from < to) { + l = VAR2IDX(from, to); + glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) + weight); + } else { + l = VAR2IDX(to, from); + glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) - weight); + } + } + + /* Add constraints */ + if (n > 1) { + { + /* Overflow-safe block for: + * no_of_rows = n * (n - 1) / 2 + n * (n - 1) * (n - 2) / 3 + */ + + /* res = n * (n - 1) * (n - 2) / 3 */ + igraph_int_t mod = n % 3; + igraph_int_t res = n / 3; /* same as (n - mod) / 3 */ + + mod = (mod + 1) % 3; + IGRAPH_SAFE_MULT(res, n - mod, &res); + mod = (mod + 1) % 3; + IGRAPH_SAFE_MULT(res, n - mod, &res); + + /* no_of_rows = n * (n - 1) / 2 + res */ + IGRAPH_SAFE_ADD(n_choose_2, res, &no_of_rows); + } + if (no_of_rows > INT_MAX) { + IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); + } + glp_add_rows(ip, (int) no_of_rows); + m = 1; + for (j = 0; j < n; j++) { + int ind[4]; + double val[4] = {0, 1, 1, -1}; + for (k = j + 1; k < n; k++) { + ind[1] = (int) VAR2IDX(j, k); + /* Type (2a) */ + val[2] = 1; + for (l = k + 1; l < n; l++, m++) { + ind[2] = (int) VAR2IDX(k, l); + ind[3] = (int) VAR2IDX(j, l); + glp_set_row_bnds(ip, (int) m, GLP_UP, 1, 1); + glp_set_mat_row(ip, (int) m, 3, ind, val); + } + /* Type (2b) */ + val[2] = -1; + for (l = j + 1; l < k; l++, m++) { + ind[2] = (int) VAR2IDX(l, k); + ind[3] = (int) VAR2IDX(j, l); + glp_set_row_bnds(ip, (int) m, GLP_UP, 0, 0); + glp_set_mat_row(ip, (int) m, 3, ind, val); + } + } + } + } + + /* Solve the problem */ + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), + "Feedback arc set using IP with triangle inequalities failed"); + + /* Find the ordering of the vertices */ + IGRAPH_CHECK(igraph_vector_int_resize(&ordering, n)); + igraph_vector_int_null(&ordering); + j = 0; k = 1; + for (l = 1; l <= n_choose_2; l++) { + /* variable l always corresponds to the (j, k) vertex pair */ + /* printf("(%ld, %ld) = %g\n", i, j, glp_mip_col_val(ip, l)); */ + if (glp_mip_col_val(ip, (int) l) > 0) { + /* j comes earlier in the ordering than k */ + VECTOR(ordering)[j]++; + } else { + /* k comes earlier in the ordering than j */ + VECTOR(ordering)[k]++; + } + k++; + if (k == n) { + j++; k = j + 1; + } + } + + /* Find the feedback edges */ + k = igraph_vector_int_size(edges_in_comp); + for (j = 0; j < k; j++) { + l = VECTOR(*edges_in_comp)[j]; + from = VECTOR(vertex_remapping)[IGRAPH_FROM(graph, l)]; + to = VECTOR(vertex_remapping)[IGRAPH_TO(graph, l)]; + if (from == to || VECTOR(ordering)[from] < VECTOR(ordering)[to]) { + IGRAPH_CHECK(igraph_vector_int_push_back(result, l)); + } + } + + /* Clean up */ + glp_delete_prob(ip); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_list_destroy(&vertices_by_components); + igraph_vector_int_list_destroy(&edges_by_components); + igraph_vector_int_destroy(&vertex_remapping); + igraph_vector_int_destroy(&ordering); + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +#endif +} + + +/** + * Incremental constraint generation based integer programming implementation + * for feedback arc set (FAS) and feedback vertex set (FVS). + * + * b_i are binary variables indicating the presence of edge/vertex i in the + * FAS/FVS. w_i is the weight of edge/vertex i. + * + * We minimize + * + * sum_i w_i b_i + * + * subject to the constraints + * + * sum_i c^k_i b_i >= 1 + * + * where c^k_i is a binary coefficient indicating if edge/vertex i is present + * in cycle k. + * + * While this must hold for all cycles (all cycles must be broken), + * we generate cycles incrementally, re-solving the problem after + * each step. New cycles are generated in such a way as to avoid + * the feedback set from the previous solution step. + */ + +#define VAR_TO_ID(j) ((j) - 1) + +/* Helper data structure for adding rows to GLPK problems. + * ind[] and val[] use one-based indexing, in line with GLPK's convention. + * Storing the zero-based ind0/val0 and the offset ind/val is necessary + * to avoid GCC warnings. */ +typedef struct { + int alloc_size; + int *ind0, *ind; + double *val0, *val; +} rowdata_t; + +static igraph_error_t rowdata_init(rowdata_t *rd, int size) { + int *ind0 = IGRAPH_CALLOC(size, int); + IGRAPH_CHECK_OOM(ind0, "Insufficient memory for feedback arc set."); + IGRAPH_FINALLY(igraph_free, ind0); + double *val0 = IGRAPH_CALLOC(size, double); + IGRAPH_CHECK_OOM(val0, "Insufficient memory for feedback arc set."); + for (int i=0; i < size; i++) { + val0[i] = 1.0; + } + rd->alloc_size = size; + rd->ind0 = ind0; + rd->ind = ind0 - 1; + rd->val0 = val0; + rd->val = val0 - 1; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +static igraph_error_t rowdata_set(rowdata_t *rd, const igraph_vector_int_t *idx) { + int size = igraph_vector_int_size(idx); + + /* Expand size if needed */ + if (size > rd->alloc_size) { + int new_alloc_size = 2 * rd->alloc_size; + if (size > new_alloc_size) { + new_alloc_size = size; + } + + int *ind0 = rd->ind0; + double *val0 = rd->val0; + + ind0 = IGRAPH_REALLOC(ind0, new_alloc_size, int); + IGRAPH_CHECK_OOM(ind0, "Insufficient memory for feedback arc set."); + rd->ind0 = ind0; + rd->ind = ind0 - 1; + + val0 = IGRAPH_REALLOC(val0, new_alloc_size, double); + IGRAPH_CHECK_OOM(val0, "Insufficient memory for feedback arc set."); + for (int i = rd->alloc_size; i < new_alloc_size; i++) { + val0[i] = 1.0; + } + rd->val0 = val0; + rd->val = val0 - 1; + + rd->alloc_size = new_alloc_size; + } + + for (int i = 0; i < size; i++) { + rd->ind0[i] = VECTOR(*idx)[i] + 1; + } + + return IGRAPH_SUCCESS; +} + +static void rowdata_destroy(rowdata_t *rd) { + igraph_free(rd->ind0); + igraph_free(rd->val0); +} + +igraph_error_t igraph_i_feedback_arc_set_ip_cg( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights) { + +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); +#else + const igraph_int_t ecount = igraph_ecount(graph); + igraph_bool_t is_dag; + igraph_bitset_t removed; + igraph_vector_int_t cycle; + glp_prob *ip; + glp_iocp parm; + rowdata_t rd; + int var_count; + + /* Avoid starting up the IP machinery for DAGs. */ + IGRAPH_CHECK(igraph_is_dag(graph, &is_dag)); + if (is_dag) { + igraph_vector_int_clear(result); + return IGRAPH_SUCCESS; + } + + if (ecount > INT_MAX) { + IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); + } + + var_count = (int) ecount; + + /* TODO: In-depth investigation of whether decomposing to SCCs helps performance. + * Basic benchmarking on sparse random graphs with mean degrees between 1-2 + * indicate no benefit from avoiding creating GLPK variables for non-cycle edges. */ + + IGRAPH_BITSET_INIT_FINALLY(&removed, ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, 0); + + IGRAPH_CHECK(rowdata_init(&rd, 20)); + IGRAPH_FINALLY(rowdata_destroy, &rd); + + /* Configure GLPK */ + IGRAPH_GLPK_SETUP(); + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_MFV; + parm.bt_tech = GLP_BT_BLB; + parm.pp_tech = GLP_PP_ALL; + parm.presolve = GLP_ON; + parm.cb_func = igraph_i_glpk_interruption_hook; + + ip = glp_create_prob(); + IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); + + glp_set_obj_dir(ip, GLP_MIN); + + glp_add_cols(ip, var_count); + for (int j = 1; j <= var_count; j++) { + glp_set_obj_coef(ip, j, weights ? VECTOR(*weights)[ VAR_TO_ID(j) ] : 1); + glp_set_col_kind(ip, j, GLP_BV); + } + + while (true) { + int cycle_size, row; + + IGRAPH_CHECK(igraph_i_find_cycle(graph, NULL, &cycle, NULL, IGRAPH_OUT, &removed)); + + cycle_size = (int) igraph_vector_int_size(&cycle); + + if (cycle_size == 0) break; /* no more cycles, we're done */ + + IGRAPH_CHECK(rowdata_set(&rd, &cycle)); + + row = glp_add_rows(ip, 1); + glp_set_row_bnds(ip, row, GLP_LO, 1, 0); + glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); + + /* Add as many edge-disjoint cycles at once as possible. */ + while (true) { + for (int i=0; i < cycle_size; i++) { + IGRAPH_BIT_SET(removed, VECTOR(cycle)[i]); + } + IGRAPH_CHECK(igraph_i_find_cycle(graph, NULL, &cycle, NULL, IGRAPH_OUT, &removed)); + + cycle_size = (int) igraph_vector_int_size(&cycle); + if (cycle_size == 0) break; /* no more edge disjoint cycles */ + + IGRAPH_CHECK(rowdata_set(&rd, &cycle)); + + row = glp_add_rows(ip, 1); + glp_set_row_bnds(ip, row, GLP_LO, 1, 0); + glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); + } + + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), + "Feedback arc set using IP with incremental cycle generation failed"); + + igraph_vector_int_clear(result); + igraph_bitset_null(&removed); + for (int j=1; j <= var_count; j++) { + if (glp_mip_col_val(ip, j) > 0) { + igraph_int_t i = VAR_TO_ID(j); + IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); + IGRAPH_BIT_SET(removed, i); + } + } + } + + /* Clean up */ + glp_delete_prob(ip); + rowdata_destroy(&rd); + igraph_vector_int_destroy(&cycle); + igraph_bitset_destroy(&removed); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +#endif +} + + +igraph_error_t igraph_i_feedback_vertex_set_ip_cg( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *vertex_weights) { +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); +#else + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_bool_t is_acyclic; + igraph_bitset_t removed; + igraph_vector_int_t cycle; + igraph_vector_int_t incident; + glp_prob *ip; + glp_iocp parm; + rowdata_t rd; + int var_count; + + /* Avoid starting up the IP machinery for acyclic graphs. */ + IGRAPH_CHECK(igraph_is_acyclic(graph, &is_acyclic)); + + if (is_acyclic) { + igraph_vector_int_clear(result); + return IGRAPH_SUCCESS; + } + + if (vcount > INT_MAX) { + IGRAPH_ERROR("Feedback vertex set problem too large for GLPK.", IGRAPH_EOVERFLOW); + } + + var_count = (int) vcount; + + IGRAPH_BITSET_INIT_FINALLY(&removed, ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&incident, 0); + IGRAPH_CHECK(rowdata_init(&rd, 20)); + IGRAPH_FINALLY(rowdata_destroy, &rd); + + /* Configure GLPK */ + IGRAPH_GLPK_SETUP(); + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_MFV; + parm.bt_tech = GLP_BT_BLB; + parm.pp_tech = GLP_PP_ALL; + parm.presolve = GLP_ON; + parm.cb_func = igraph_i_glpk_interruption_hook; + + ip = glp_create_prob(); + IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); + + glp_set_obj_dir(ip, GLP_MIN); + + glp_add_cols(ip, var_count); + for (int j = 1; j <= var_count; j++) { + glp_set_obj_coef(ip, j, vertex_weights ? VECTOR(*vertex_weights)[ VAR_TO_ID(j) ] : 1); + glp_set_col_kind(ip, j, GLP_BV); + } + + while (true) { + int cycle_size, row; + + IGRAPH_CHECK(igraph_i_find_cycle(graph, &cycle, NULL, NULL, IGRAPH_OUT, &removed)); + + cycle_size = (int) igraph_vector_int_size(&cycle); + + if (cycle_size == 0) break; /* no more cycles, we're done */ + + IGRAPH_CHECK(rowdata_set(&rd, &cycle)); + + row = glp_add_rows(ip, 1); + glp_set_row_bnds(ip, row, GLP_LO, 1, 0); + glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); + + /* Add as many vertex-disjoint cycles at once as possible. */ + while (true) { + for (int i=0; i < cycle_size; i++) { + IGRAPH_CHECK(igraph_incident(graph, &incident, VECTOR(cycle)[i], IGRAPH_ALL, IGRAPH_LOOPS)); + const igraph_int_t incident_size = igraph_vector_int_size(&incident); + for (igraph_int_t j = 0; j < incident_size; j++) { + igraph_int_t eid = VECTOR(incident)[j]; + IGRAPH_BIT_SET(removed, eid); + } + } + IGRAPH_CHECK(igraph_i_find_cycle(graph, &cycle, NULL, NULL, IGRAPH_OUT, &removed)); + + cycle_size = (int) igraph_vector_int_size(&cycle); + if (cycle_size == 0) break; /* no more vertex disjoint cycles */ + + IGRAPH_CHECK(rowdata_set(&rd, &cycle)); + + row = glp_add_rows(ip, 1); + glp_set_row_bnds(ip, row, GLP_LO, 1, 0); + glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); + } + + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), + "Feedback vertex set using IP with incremental cycle generation failed"); + + igraph_vector_int_clear(result); + igraph_bitset_null(&removed); + + for (int j=1; j <= var_count; j++) { + if (glp_mip_col_val(ip, j) > 0) { + igraph_int_t i = VAR_TO_ID(j); + IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); + + IGRAPH_CHECK(igraph_incident(graph, &incident, i, IGRAPH_ALL, IGRAPH_LOOPS)); + + const igraph_int_t incident_size = igraph_vector_int_size(&incident); + for (igraph_int_t k = 0; k < incident_size; k++) { + igraph_int_t eid = VECTOR(incident)[k]; + IGRAPH_BIT_SET(removed, eid); + } + } + } + } + + /* Clean up */ + glp_delete_prob(ip); + rowdata_destroy(&rd); + igraph_vector_int_destroy(&cycle); + igraph_vector_int_destroy(&incident); + igraph_bitset_destroy(&removed); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +#endif +} + +#undef VAR_TO_ID diff --git a/src/cycles/feedback_sets.h b/src/cycles/feedback_sets.h new file mode 100644 index 0000000..8218fe1 --- /dev/null +++ b/src/cycles/feedback_sets.h @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2009-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CYCLES_FEEDBACK_SETS_H +#define IGRAPH_CYCLES_FEEDBACK_SETS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_feedback_arc_set_eades( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights, igraph_vector_int_t *layering +); +igraph_error_t igraph_i_feedback_arc_set_ip_ti( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights); +igraph_error_t igraph_i_feedback_arc_set_ip_cg( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights); +igraph_error_t igraph_i_feedback_arc_set_ip_cb( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights); +igraph_error_t igraph_i_feedback_arc_set_undirected( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights, igraph_vector_int_t *layering +); + +igraph_error_t igraph_i_feedback_vertex_set_ip_cg( + const igraph_t *graph, igraph_vector_int_t *result, + const igraph_vector_t *weights); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_CYCLES_FEEDBACK_SETS_H */ diff --git a/src/cycles/order_cycle.cpp b/src/cycles/order_cycle.cpp new file mode 100644 index 0000000..5c15e05 --- /dev/null +++ b/src/cycles/order_cycle.cpp @@ -0,0 +1,100 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "cycles/order_cycle.h" + +#include "igraph_interface.h" + +#include "core/exceptions.h" + +#include +#include + +// Initialized to {-1, -1} +struct eid_pair_t : public std::pair { + eid_pair_t() : std::pair(-1, -1) { } +}; + +/** + * \function igraph_i_order_cycle + * \brief Reorders edges of a cycle in cycle order + * + * This function takes \p cycle, a vector of arbitrarily ordered edge IDs, + * representing a graph cycle. It produces a vector \p res containing the + * same IDs in cycle order. \p res must be initialized when calling this function. + */ +igraph_error_t igraph_i_order_cycle( + const igraph_t *graph, + const igraph_vector_int_t *cycle, + igraph_vector_int_t *res) { + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_int_t n = igraph_vector_int_size(cycle); + IGRAPH_ASSERT(n > 0); + + std::map inclist; + for (igraph_int_t i=0; i < n; ++i) { + igraph_int_t eid = VECTOR(*cycle)[i]; + + { + igraph_int_t from = IGRAPH_FROM(graph, eid); + auto &p = inclist[from]; + if (p.first < 0) { + p.first = eid; + } else { + IGRAPH_ASSERT(p.second < 0); + p.second = eid; + } + } + + { + igraph_int_t to = IGRAPH_TO(graph, eid); + auto &p = inclist[to]; + if (p.first < 0) { + p.first = eid; + } else { + IGRAPH_ASSERT(p.second < 0); + p.second = eid; + } + } + } + + igraph_vector_int_clear(res); + IGRAPH_CHECK(igraph_vector_int_reserve(res, igraph_vector_int_size(cycle))); + igraph_int_t current_e = VECTOR(*cycle)[0]; + igraph_int_t current_v = IGRAPH_FROM(graph, current_e); + for (igraph_int_t i=0; i < n; ++i) { + const auto &p = inclist.at(current_v); + igraph_vector_int_push_back(res, current_e); /* reserved */ + igraph_int_t next_e = p.first; + if (next_e == current_e) { + next_e = p.second; + } + current_e = next_e; + igraph_int_t next_v = IGRAPH_FROM(graph, current_e); + if (next_v == current_v) { + next_v = IGRAPH_TO(graph, current_e); + } + current_v = next_v; + } + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} diff --git a/src/cycles/order_cycle.h b/src/cycles/order_cycle.h new file mode 100644 index 0000000..291577b --- /dev/null +++ b/src/cycles/order_cycle.h @@ -0,0 +1,35 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_ORDER_CYCLE_H +#define IGRAPH_ORDER_CYCLE_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_order_cycle( + const igraph_t *graph, + const igraph_vector_int_t *cycle, + igraph_vector_int_t *res); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_ORDER_CYCLE_H */ diff --git a/src/cycles/simple_cycles.c b/src/cycles/simple_cycles.c new file mode 100644 index 0000000..1eb0c85 --- /dev/null +++ b/src/cycles/simple_cycles.c @@ -0,0 +1,651 @@ +/* + igraph library. + Copyright (C) 2024-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_cycles.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_stack.h" + +#include "core/interruption.h" + +/* Johnson's cycle detection algorithm + * + * Based on the original implementation in: + * Johnson DB: Finding all the elementary circuits of a directed graph. + * SIAM J Comput 4(1):77-84. + * https://doi.org/10.1137/0204007 + */ + +/** + * State of the cycle search algorithm. Storing all the state variables in a + * single struct allows us to resume the algorithm from any point and yield the + * cycles one by one in an iterator-like manner. + */ +typedef struct { + /* Number of vertices in the graph */ + igraph_int_t N; + + /* The incidence list of the graph */ + igraph_inclist_t IK; + + /* The adjacency list of the graph */ + igraph_adjlist_t AK; + + /* The set B from Johnson's paper */ + igraph_adjlist_t B; + + /* Stack in which the vertices of the current cycle are pushed */ + igraph_vector_int_t vertex_stack; + + /* Stack in which the edges of the current cycle are pushed */ + igraph_vector_int_t edge_stack; + + /* Boolean vector indicating which vertices are blocked */ + igraph_bitset_t v_blocked; + + /* Boolean vector indicating which vertices have ever been checked. + * The bigger picture here is that this allows a "lazy" community decomposition */ + igraph_bitset_t v_visited; + + /* Whether the graph is directed */ + igraph_bool_t directed; + + /* Boolean indicating whether the algorithm should stop searching for cycles */ + igraph_bool_t stop_search; +} simple_cycle_search_state_t; + +/** + * A struct to store one cycle found by the algorithm. + */ +typedef struct { + /* the vertices in the cycle */ + igraph_vector_int_list_t *vertices; + /* the edges in the cycle */ + igraph_vector_int_list_t *edges; + /* number of cycles found so far */ + igraph_int_t cycle_count; + /* how many cycles to record at most? */ + igraph_int_t max_results; +} simple_cycle_results_t; + +/** + * The implementation of procedure UNBLOCK from Johnson's paper + */ +static igraph_error_t simple_cycles_unblock( + simple_cycle_search_state_t *state, + igraph_int_t u) { + + // TODO: introduce stack for w & neis in order to reduce the number of + // iterations. + igraph_vector_int_t *neis; + igraph_stack_int_t u_stack; + + IGRAPH_STACK_INT_INIT_FINALLY(&u_stack, 0); + IGRAPH_CHECK(igraph_stack_int_push(&u_stack, u)); + + while (igraph_stack_int_size(&u_stack) > 0) { + igraph_bool_t recurse_deeper = false; + const igraph_int_t current_u = igraph_stack_int_top(&u_stack); + + IGRAPH_BIT_CLEAR(state->v_blocked, current_u); + + neis = igraph_adjlist_get(&state->B, current_u); + while (!igraph_vector_int_empty(neis) && !recurse_deeper) { + const igraph_int_t w = igraph_vector_int_pop_back(neis); + if (IGRAPH_BIT_TEST(state->v_blocked, w)) { + IGRAPH_CHECK(igraph_stack_int_push(&u_stack, w)); + recurse_deeper = true; + } + } + + if (!recurse_deeper) { + igraph_stack_int_pop(&u_stack); + } + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_stack_int_destroy(&u_stack); + + return IGRAPH_SUCCESS; +} + +/** + * The implementation of procedure CIRCUIT from Johnson's paper + * + * \param state Local state object of the search. + * \param V Vertex to start the search from. + * \param callback Callback function to handle the found cycles. + * \param max_cycle_length Limit the maximum length of cycles to search for. + * Pass a negative value for no limit + * \param arg Argument to pass to the callback function. + */ +static igraph_error_t simple_cycles_circuit( + simple_cycle_search_state_t *state, + igraph_int_t V, + igraph_int_t max_cycle_length, + igraph_int_t min_cycle_length, + igraph_cycle_handler_t *callback, + void *arg) { + + const igraph_vector_int_t *neighbors; + const igraph_vector_int_t *incident_edges; + igraph_int_t num_neighbors; + igraph_int_t S = V; // start + igraph_int_t E = -1; // edge start + + igraph_bool_t local_found = false; + igraph_bool_t loop_length_stop = false; + + // keep track of what we were doing (rather than recursing) + igraph_stack_int_t neigh_iteration_progress; + IGRAPH_STACK_INT_INIT_FINALLY(&neigh_iteration_progress, 0); + + igraph_stack_int_t v_stack; + IGRAPH_STACK_INT_INIT_FINALLY(&v_stack, 0); + + igraph_stack_int_t e_stack; + IGRAPH_STACK_INT_INIT_FINALLY(&e_stack, 0); + + igraph_bool_t recurse_deeper = true; + while ((recurse_deeper || + igraph_stack_int_size(&neigh_iteration_progress) > 0) && + !state->stop_search) { + IGRAPH_BIT_SET(state->v_visited, V); + + IGRAPH_ASSERT(igraph_stack_int_size(&neigh_iteration_progress) == + igraph_stack_int_size(&e_stack)); + IGRAPH_ASSERT(igraph_stack_int_size(&v_stack) == + igraph_stack_int_size(&e_stack)); + + igraph_int_t i0 = 0; + if (recurse_deeper) { + // stack v & e + IGRAPH_CHECK(igraph_vector_int_push_back(&state->vertex_stack, V)); + if (E >= 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&state->edge_stack, E)); + } + // printf("Pushing %" IGRAPH_PRId " to stack, stack size is %" IGRAPH_PRId + // ", result size is %" IGRAPH_PRId "\n", + // V, igraph_vector_int_size(&state->vertex_stack), + // igraph_stack_int_size(&v_stack)); + IGRAPH_BIT_SET(state->v_blocked, V); + } else { + // back to what we were doing before + i0 = igraph_stack_int_pop(&neigh_iteration_progress); + V = igraph_stack_int_pop(&v_stack); + E = igraph_stack_int_pop(&e_stack); + } + recurse_deeper = false; + + // L1 + neighbors = igraph_adjlist_get(&state->AK, V); + incident_edges = igraph_inclist_get(&state->IK, V); + num_neighbors = igraph_vector_int_size(neighbors); + IGRAPH_ASSERT(igraph_vector_int_size(incident_edges) == num_neighbors); + for (igraph_int_t i = i0; i < num_neighbors; ++i) { + igraph_int_t W = VECTOR(*neighbors)[i]; + igraph_int_t WE = VECTOR(*incident_edges)[i]; + + if (W == S) { + igraph_error_t ret; + + // need to unblock no matter whether we store the result or not (in + // undirected case, we may not necessarily) + local_found = true; + + if ((!state->directed && + igraph_vector_int_size(&state->edge_stack) == 1 && + VECTOR(state->edge_stack)[0] == WE)) { + // printf("Skipping cycle to %" IGRAPH_PRId " via %" IGRAPH_PRId " to prevent self-loop.\n", W, WE); + continue; + } + + // prevent duplicates in undirected graphs by forcing a direction for + // the closing edge + if ((!state->directed && + igraph_vector_int_size(&state->edge_stack) > 0 && + VECTOR(state->edge_stack)[0] > WE)) { + // printf("Skipping cycle to %" IGRAPH_PRId " via %" IGRAPH_PRId " to + // prevent duplicates.\n", W, WE); + continue; + } + + IGRAPH_CHECK(igraph_vector_int_push_back(&state->edge_stack, WE)); + + // output circuit composed of stack + // printf("Found cycle with size %" IGRAPH_PRId "\n", + // igraph_vector_int_size(&state->vertex_stack)); + + if (igraph_vector_int_size(&state->edge_stack) >= min_cycle_length) { + IGRAPH_CHECK_CALLBACK( + callback(&state->vertex_stack, &state->edge_stack, arg), &ret); + if (ret == IGRAPH_STOP) { + state->stop_search = true; + break; + } + } + igraph_vector_int_pop_back(&state->edge_stack); + } else if (! IGRAPH_BIT_TEST(state->v_blocked, W)) { + // printf("Recursing deeper from %" IGRAPH_PRId " to %" IGRAPH_PRId + // "\n", V, W); + recurse_deeper = ((max_cycle_length < 0) || + (igraph_vector_int_size(&state->vertex_stack) <= + max_cycle_length - 1)); + if (recurse_deeper) { + IGRAPH_CHECK(igraph_stack_int_push(&neigh_iteration_progress, i + 1)); + IGRAPH_CHECK(igraph_stack_int_push(&v_stack, V)); + IGRAPH_CHECK(igraph_stack_int_push(&e_stack, E)); + V = W; + E = WE; + break; + } else { + loop_length_stop = true; + } + } else { + // printf("Vertex %" IGRAPH_PRId ", neighbour of %" IGRAPH_PRId + // ", is blocked\n", + // W, V); + } + } + + if (!recurse_deeper) { + // L2 + if (local_found || loop_length_stop) { + IGRAPH_CHECK(simple_cycles_unblock(state, V)); + } else { + for (igraph_int_t i = 0; i < num_neighbors; ++i) { + const igraph_int_t W = VECTOR(*neighbors)[i]; + if (!igraph_vector_int_contains(igraph_adjlist_get(&state->B, W), V)) { + IGRAPH_CHECK( + igraph_vector_int_push_back(igraph_adjlist_get(&state->B, W), V)); + } + } + } + + IGRAPH_ASSERT(!igraph_vector_int_empty(&state->vertex_stack)); + + // unstack v + V = igraph_vector_int_pop_back(&state->vertex_stack); + if (!igraph_vector_int_empty(&state->edge_stack)) { + // can be empty for the starting point. + // alternatively, V == S + E = igraph_vector_int_pop_back(&state->edge_stack); + } + // printf("Unstacked v %" IGRAPH_PRId ", e %" IGRAPH_PRId "\n", V, E); + } + + IGRAPH_ALLOW_INTERRUPTION(); + } + + if (! state->stop_search) { + IGRAPH_ASSERT(igraph_stack_int_size(&v_stack) == 0); + IGRAPH_ASSERT(igraph_stack_int_size(&e_stack) == 0); + IGRAPH_ASSERT(igraph_stack_int_size(&neigh_iteration_progress) == 0); + } + + igraph_stack_int_destroy(&neigh_iteration_progress); + igraph_stack_int_destroy(&v_stack); + igraph_stack_int_destroy(&e_stack); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function simple_cycle_search_state_init + * \brief Initializes the cycle search state. + * + * \param state The state structure to initialize. + * \param graph The graph object. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|*|E|*log(|V|*|E|)) + * + * \ref simple_cycle_search_state_destroy() + */ +static igraph_error_t simple_cycle_search_state_init( + simple_cycle_search_state_t *state, + const igraph_t *graph, + igraph_neimode_t mode) { + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for finding cycles.", IGRAPH_EINVAL); + } + + state->N = igraph_vcount(graph); + state->directed = igraph_is_directed(graph) && mode != IGRAPH_ALL; + state->stop_search = false; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&state->vertex_stack, 8); + igraph_vector_int_clear(&state->vertex_stack); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&state->edge_stack, 8); + igraph_vector_int_clear(&state->edge_stack); + + // by default false + IGRAPH_BITSET_INIT_FINALLY(&state->v_blocked, state->N); + + IGRAPH_BITSET_INIT_FINALLY(&state->v_visited, state->N); + + IGRAPH_CHECK(igraph_inclist_init( + graph, &state->IK, mode, + IGRAPH_LOOPS_ONCE // each self-loop counts as a single cycle + )); + IGRAPH_FINALLY(igraph_inclist_destroy, &state->IK); + + IGRAPH_CHECK(igraph_adjlist_init_from_inclist(graph, &state->AK, &state->IK)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &state->AK); + + IGRAPH_CHECK(igraph_adjlist_init_empty(&state->B, state->N)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &state->B); + + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \function simple_cycle_search_state_destroy + * \brief Destroys the cycle search state. + * + * \param state The state structure to destroy + * \return Error code. + * + * Time complexity: O(1). + * + * \ref simple_cycle_search_state_init() + */ +static void simple_cycle_search_state_destroy(simple_cycle_search_state_t *state) { + + igraph_adjlist_destroy(&state->B); + igraph_adjlist_destroy(&state->AK); + igraph_inclist_destroy(&state->IK); + igraph_bitset_destroy(&state->v_visited); + igraph_bitset_destroy(&state->v_blocked); + igraph_vector_int_destroy(&state->edge_stack); + igraph_vector_int_destroy(&state->vertex_stack); +} + +/** + * A cycle handler that simply appends cycles to a vector list. + * Use by \ref igraph_simple_cycles() + */ +static igraph_error_t append_simple_cycle_result( + const igraph_vector_int_t *vertices, + const igraph_vector_int_t *edges, + void *arg) { + + simple_cycle_results_t *res_list = (simple_cycle_results_t *) arg; + + if (res_list->vertices != NULL) { + // copy output: from stack to vector. No need to reverse because + // we were putting vertices in the stack in reverse order anyway. + igraph_vector_int_t v_res; + IGRAPH_CHECK(igraph_vector_int_init_copy(&v_res, vertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &v_res); + /* v_res ownership transferred to 'vertices' */ + IGRAPH_CHECK(igraph_vector_int_list_push_back(res_list->vertices, &v_res)); + IGRAPH_FINALLY_CLEAN(1); + } + if (res_list->edges != NULL) { + // same for edges + igraph_vector_int_t e_res; + IGRAPH_CHECK(igraph_vector_int_init_copy(&e_res, edges)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &e_res); + /* e_res ownership transferred to 'edges' */ + IGRAPH_CHECK(igraph_vector_int_list_push_back(res_list->edges, &e_res)); + IGRAPH_FINALLY_CLEAN(1); + } + + res_list->cycle_count++; + + if (res_list->max_results >= 0 && res_list->cycle_count == res_list->max_results) { + return IGRAPH_STOP; + } else { + return IGRAPH_SUCCESS; + } +} + +/** + * \function simple_cycles_search_callback_from_one_vertex + * \brief Search simple cycles starting from one vertex. + * + * \param state The state structure to search on. + * \param s The vertex index to start search with. + * \param max_cycle_length Limit the maximum length of cycles to search for. + * Pass a negative value for no limit. + * \param callback The callback function to call when a cycle is found. + * See \ref igraph_cycle_handler_t() for details. + * \param arg The additional argument(s) for the callback function. + * + * \return Error code. + * + * https://en.wikipedia.org/wiki/Johnson%27s_algorithm + * https://stackoverflow.com/a/35922906/3909202 + * https://epubs.siam.org/doi/epdf/10.1137/0204007 + */ +static igraph_error_t simple_cycles_search_callback_from_one_vertex( + simple_cycle_search_state_t *state, + igraph_int_t s, + igraph_int_t min_cycle_length, + igraph_int_t max_cycle_length, + igraph_cycle_handler_t *callback, + void *arg) { + + // L3: + for (igraph_int_t i = s; i < state->N; ++i) { + IGRAPH_BIT_CLEAR(state->v_blocked, i); + igraph_vector_int_clear(igraph_adjlist_get(&state->B, i)); + } + + IGRAPH_CHECK(simple_cycles_circuit(state, s, max_cycle_length, + min_cycle_length, callback, arg)); + + for (igraph_int_t i = 0; i < state->N; ++i) { + // We want to remove the vertex with value s, not at position s. + // It's fine to use binary search since we never add to, only remove from + // an already sorted adjacency list. + igraph_int_t pos; + if (igraph_vector_int_binsearch(igraph_adjlist_get(&state->AK, i), s, + &pos)) { + igraph_vector_int_remove(igraph_adjlist_get(&state->AK, i), pos); + igraph_vector_int_remove(igraph_inclist_get(&state->IK, i), pos); + } + } + igraph_vector_int_clear(igraph_adjlist_get(&state->AK, s)); + igraph_vector_int_clear(igraph_inclist_get(&state->IK, s)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_simple_cycles_callback + * \brief Finds all simple cycles (callback version). + * + * \experimental + * + * This function searches for all simple cycles using Johnson's cycle + * detection algorithm, and calls a function for each. + * A simple cycle is a cycle (i.e. closed path) without repeated vertices. + * + * + * Reference: + * + * + * Johnson DB: Finding all the elementary circuits of a directed graph. + * SIAM J. Comput. 4(1):77-84. + * https://doi.org/10.1137/0204007 + * + * \param graph The graph to search for + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \param min_cycle_length Limit the minimum length of cycles to search for. + * Pass a negative value to search for all cycles. + * \param max_cycle_length Limit the maximum length of cycles to search for. + * Pass a negative value to search for all cycles. + * \param callback A function to call for each cycle that is found. + * See also \ref igraph_cycle_handler_t + * \param arg This parameter will be passed to \p callback. + * \return Error code. + * + * \sa \ref igraph_simple_cycles() to store the found cycles; + * \ref igraph_find_cycle() to find a single cycle; + * \ref igraph_fundamental_cycles() and igraph_minimum_cycle_basis() + * to find a cycle basis, a compact representation of the cycle structure + * of the graph. + */ +igraph_error_t igraph_simple_cycles_callback( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, + igraph_int_t max_cycle_length, + igraph_cycle_handler_t *callback, + void *arg) { + + if (max_cycle_length == 0) { + return IGRAPH_SUCCESS; + } + + simple_cycle_search_state_t state; + + IGRAPH_CHECK(simple_cycle_search_state_init(&state, graph, mode)); + IGRAPH_FINALLY(simple_cycle_search_state_destroy, &state); + + // Depending on the graph, it is rather unreasonable to search cycles + // from each and every node. Instead, we expect that each cycle must involve + // either: + // - a vertex with degree > 2 + // - or, if it's a freestanding cycle, be any vertex of this connected component; + // components are identified via the `state->v_visited` boolean mask. + // + // Thus, we iterate over the vertices, and check if they can be skipped as + // a starting point according to the rules laid out above. + for (igraph_int_t i = 0; i < state.N; i++) { + // Check if the vertex is a candidate for a cycle. + // Note that we call igraph_degree_1() here instead of retrieving the + // neighbor count from igraph_adjlist_get(&state.AK, i) because: + // - we need to the undirected degree in all cases, and + // - our algorithm modifies the adjlist state.AK + igraph_int_t degree; + IGRAPH_CHECK(igraph_degree_1(graph, °ree, i, IGRAPH_ALL, true)); + if (degree < 3 && IGRAPH_BIT_TEST(state.v_visited, i)) { + continue; + } + // Check if we find a cycle starting from this vertex. + if (!igraph_vector_int_empty(igraph_adjlist_get(&state.AK, i))) { + IGRAPH_CHECK(simple_cycles_search_callback_from_one_vertex( + &state, i, min_cycle_length, max_cycle_length, callback, arg)); + IGRAPH_ALLOW_INTERRUPTION(); + } + if (state.stop_search) { + state.stop_search = false; + break; + } + } + + simple_cycle_search_state_destroy(&state); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_simple_cycles + * \brief Finds all simple cycles. + * + * \experimental + * + * This function searches for all simple cycles using Johnson's cycle + * detection algorithm, and stores them in the provided vector lists. + * A simple cycle is a cycle (i.e. closed path) without repeated vertices. + * + * + * Reference: + * + * + * Johnson DB: Finding all the elementary circuits of a directed graph. + * SIAM J. Comput. 4(1):77-84. + * https://doi.org/10.1137/0204007 + * + * \param graph The graph to search for cycles in. + * \param vertices The vertex IDs of each cycle will be stored here. + * \param edges The edge IDs of each cycle will be stored here. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \param min_cycle_length Limit the minimum length of cycles to search for. + * Pass a negative value to search for all cycles. + * \param max_cycle_length Limit the maximum length of cycles to search for. + * Pass a negative value to search for all cycles. + * \param max_results At most this many cycles will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_simple_cycles_callback() to call a function for each found + * cycle; + * \ref igraph_find_cycle() to find a single cycle; + * \ref igraph_fundamental_cycles() and \ref igraph_minimum_cycle_basis() + * to find a cycle basis, a compact representation of the cycle structure + * of the graph. + */ +igraph_error_t igraph_simple_cycles( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_neimode_t mode, + igraph_int_t min_cycle_length, igraph_int_t max_cycle_length, + igraph_int_t max_results) { + + simple_cycle_results_t result_list; + result_list.vertices = vertices; + result_list.edges = edges; + result_list.cycle_count = 0; + result_list.max_results = max_results; + + if (vertices) { + igraph_vector_int_list_clear(vertices); + } + if (edges) { + igraph_vector_int_list_clear(edges); + } + + if (max_results == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_simple_cycles_callback(graph, mode, min_cycle_length, max_cycle_length, + &append_simple_cycle_result, + &result_list)); + + return IGRAPH_SUCCESS; +} diff --git a/src/flow/flow.c b/src/flow/flow.c new file mode 100644 index 0000000..fb6a376 --- /dev/null +++ b/src/flow/flow.c @@ -0,0 +1,2615 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_flow.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_conversion.h" +#include "igraph_constants.h" +#include "igraph_constructors.h" +#include "igraph_cycles.h" +#include "igraph_dqueue.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_operators.h" +#include "igraph_structural.h" + +#include "core/buckets.h" +#include "core/cutheap.h" +#include "core/interruption.h" +#include "flow/flow_internal.h" +#include "math/safe_intop.h" + +/* + * Some general remarks about the functions in this file. + * + * The following measures can be calculated: + * ( 1) s-t maximum flow value, directed graph + * ( 2) s-t maximum flow value, undirected graph + * ( 3) s-t maximum flow, directed graph + * ( 4) s-t maximum flow, undirected graph + * ( 5) s-t minimum cut value, directed graph + * ( 6) s-t minimum cut value, undirected graph + * ( 7) minimum cut value, directed graph + * ( 8) minimum cut value, undirected graph + * ( 9) s-t minimum cut, directed graph + * (10) s-t minimum cut, undirected graph + * (11) minimum cut, directed graph + * (12) minimum cut, undirected graph + * (13) s-t edge connectivity, directed graph + * (14) s-t edge connectivity, undirected graph + * (15) edge connectivity, directed graph + * (16) edge connectivity, undirected graph + * (17) s-t vertex connectivity, directed graph + * (18) s-t vertex connectivity, undirected graph + * (19) vertex connectivity, directed graph + * (20) vertex connectivity, undirected graph + * (21) s-t number of edge disjoint paths, directed graph + * (22) s-t number of edge disjoint paths, undirected graph + * (23) s-t number of vertex disjoint paths, directed graph + * (24) s-t number of vertex disjoint paths, undirected graph + * (25) graph adhesion, directed graph + * (26) graph adhesion, undirected graph + * (27) graph cohesion, directed graph + * (28) graph cohesion, undirected graph + * + * This is how they are calculated: + * ( 1) igraph_maxflow_value, calls igraph_maxflow. + * ( 2) igraph_maxflow_value, calls igraph_maxflow, this calls + * igraph_i_maxflow_undirected. This transforms the graph into a + * directed graph, including two mutual edges instead of every + * undirected edge, then igraph_maxflow is called again with the + * directed graph. + * ( 3) igraph_maxflow, does the push-relabel algorithm, optionally + * calculates the cut, the partitions and the flow itself. + * ( 4) igraph_maxflow calls igraph_i_maxflow_undirected, this converts + * the undirected graph into a directed one, adding two mutual edges + * for each undirected edge, then igraph_maxflow is called again, + * with the directed graph. After igraph_maxflow returns, we need + * to edit the flow (and the cut) to make it sense for the + * original graph. + * ( 5) igraph_st_mincut_value, we just call igraph_maxflow_value + * ( 6) igraph_st_mincut_value, we just call igraph_maxflow_value + * ( 7) igraph_mincut_value, we call igraph_maxflow_value (|V|-1)*2 + * times, from vertex 0 to all other vertices and from all other + * vertices to vertex 0 + * ( 8) We call igraph_i_mincut_value_undirected, that calls + * igraph_i_mincut_undirected with partition=partition2=cut=NULL + * The Stoer-Wagner algorithm is used. + * ( 9) igraph_st_mincut, just calls igraph_maxflow. + * (10) igraph_st_mincut, just calls igraph_maxflow. + * (11) igraph_mincut, calls igraph_i_mincut_directed, which runs + * the maximum flow algorithm 2(|V|-1) times, from vertex zero to + * and from all other vertices and stores the smallest cut. + * (12) igraph_mincut, igraph_i_mincut_undirected is called, + * this is the Stoer-Wagner algorithm + * (13) We just call igraph_maxflow_value, back to (1) + * (14) We just call igraph_maxflow_value, back to (2) + * (15) We just call igraph_mincut_value (possibly after some basic + * checks). Back to (7) + * (16) We just call igraph_mincut_value (possibly after some basic + * checks). Back to (8). + * (17) We call igraph_i_st_vertex_connectivity_directed. + * That creates a new graph with 2*|V| vertices and smartly chosen + * edges, so that the s-t edge connectivity of this graph is the + * same as the s-t vertex connectivity of the original graph. + * So finally it calls igraph_maxflow_value, go to (1) + * (18) We call igraph_i_st_vertex_connectivity_undirected. + * We convert the graph to a directed one, + * IGRAPH_TO_DIRECTED_MUTUAL method. Then we call + * igraph_i_st_vertex_connectivity_directed, see (17). + * (19) We call igraph_i_vertex_connectivity_directed. + * That calls igraph_st_vertex_connectivity for all pairs of + * vertices. Back to (17). + * (20) We call igraph_i_vertex_connectivity_undirected. + * That converts the graph into a directed one + * (IGRAPH_TO_DIRECTED_MUTUAL) and calls the directed version, + * igraph_i_vertex_connectivity_directed, see (19). + * (21) igraph_edge_disjoint_paths, we just call igraph_maxflow_value, (1). + * (22) igraph_edge_disjoint_paths, we just call igraph_maxflow_value, (2). + * (23) igraph_vertex_disjoint_paths, if there is a connection between + * the two vertices, then we remove that (or all of them if there + * are many), as this could mess up vertex connectivity + * calculation. The we call + * igraph_i_st_vertex_connectivity_directed, see (19). + * (24) igraph_vertex_disjoint_paths, if there is a connection between + * the two vertices, then we remove that (or all of them if there + * are many), as this could mess up vertex connectivity + * calculation. The we call + * igraph_i_st_vertex_connectivity_undirected, see (20). + * (25) We just call igraph_edge_connectivity, see (15). + * (26) We just call igraph_edge_connectivity, see (16). + * (27) We just call igraph_vertex_connectivity, see (19). + * (28) We just call igraph_vertex_connectivity, see (20). + */ + +/* + * This is an internal function that calculates the maximum flow value + * on undirected graphs, either for an s-t vertex pair or for the + * graph (i.e. all vertex pairs). + * + * It does it by converting the undirected graph to a corresponding + * directed graph, including reciprocal directed edges instead of each + * undirected edge. + */ + +static igraph_error_t igraph_i_maxflow_undirected( + const igraph_t *graph, + igraph_real_t *value, + igraph_vector_t *flow, + igraph_vector_int_t *cut, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_int_t source, + igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t edges; + igraph_vector_t newcapacity; + igraph_t newgraph; + igraph_int_t size; + + /* We need to convert this to directed by hand, since we need to be + sure that the edge IDs will be handled properly to build the new + capacity vector. */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&newcapacity, no_of_edges * 2); + IGRAPH_SAFE_MULT(no_of_edges, 4, &size); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, size)); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_int_resize(&edges, size)); + for (igraph_int_t i = 0; i < no_of_edges; i++) { + VECTOR(edges)[no_of_edges * 2 + i * 2] = VECTOR(edges)[i * 2 + 1]; + VECTOR(edges)[no_of_edges * 2 + i * 2 + 1] = VECTOR(edges)[i * 2]; + VECTOR(newcapacity)[i] = VECTOR(newcapacity)[no_of_edges + i] = + capacity ? VECTOR(*capacity)[i] : 1.0; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + IGRAPH_CHECK(igraph_maxflow(&newgraph, value, flow, cut, partition, + partition2, source, target, &newcapacity, stats)); + + if (cut) { + igraph_int_t cs = igraph_vector_int_size(cut); + for (igraph_int_t i = 0; i < cs; i++) { + if (VECTOR(*cut)[i] >= no_of_edges) { + VECTOR(*cut)[i] -= no_of_edges; + } + } + } + + /* The flow has one non-zero value for each real-nonreal edge pair, + by definition, we convert it to a positive-negative vector. If + for an edge the flow is negative that means that it is going + from the bigger vertex ID to the smaller one. For positive + values the direction is the opposite. */ + if (flow) { + for (igraph_int_t i = 0; i < no_of_edges; i++) { + VECTOR(*flow)[i] -= VECTOR(*flow)[i + no_of_edges]; + } + IGRAPH_CHECK(igraph_vector_resize(flow, no_of_edges)); + } + + igraph_destroy(&newgraph); + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&newcapacity); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +#define FIRST(i) (VECTOR(*first)[(i)]) +#define LAST(i) (VECTOR(*first)[(i)+1]) +#define CURRENT(i) (VECTOR(*current)[(i)]) +#define RESCAP(i) (VECTOR(*rescap)[(i)]) +#define REV(i) (VECTOR(*rev)[(i)]) +#define HEAD(i) (VECTOR(*to)[(i)]) +#define EXCESS(i) (VECTOR(*excess)[(i)]) +#define DIST(i) (VECTOR(*distance)[(i)]) +#define DISCHARGE(v) (igraph_i_mf_discharge((v), ¤t, &first, &rescap, \ + &to, &distance, &excess, \ + no_of_nodes, source, target, \ + &buckets, &ibuckets, \ + &rev, stats, &npushsince, \ + &nrelabelsince)) +#define PUSH(v,e,n) (igraph_i_mf_push((v), (e), (n), current, rescap, \ + excess, target, source, buckets, \ + ibuckets, distance, rev, stats, \ + npushsince)) +#define RELABEL(v) (igraph_i_mf_relabel((v), no_of_nodes, distance, \ + first, rescap, to, current, \ + stats, nrelabelsince)) +#define GAP(b) (igraph_i_mf_gap((b), stats, buckets, ibuckets, \ + no_of_nodes, distance)) +#define BFS() (igraph_i_mf_bfs(&bfsq, source, target, no_of_nodes, \ + &buckets, &ibuckets, &distance, \ + &first, ¤t, &to, &excess, \ + &rescap, &rev)) + +static void igraph_i_mf_gap(igraph_int_t b, igraph_maxflow_stats_t *stats, + igraph_buckets_t *buckets, igraph_dbuckets_t *ibuckets, + igraph_int_t no_of_nodes, + igraph_vector_int_t *distance) { + + IGRAPH_UNUSED(buckets); + + igraph_int_t bo; + (stats->nogap)++; + for (bo = b + 1; bo <= no_of_nodes; bo++) { + while (!igraph_dbuckets_empty_bucket(ibuckets, bo)) { + igraph_int_t n = igraph_dbuckets_pop(ibuckets, bo); + (stats->nogapnodes)++; + DIST(n) = no_of_nodes; + } + } +} + +static void igraph_i_mf_relabel(igraph_int_t v, igraph_int_t no_of_nodes, + igraph_vector_int_t *distance, + igraph_vector_int_t *first, + igraph_vector_t *rescap, igraph_vector_int_t *to, + igraph_vector_int_t *current, + igraph_maxflow_stats_t *stats, igraph_int_t *nrelabelsince) { + + igraph_int_t min = no_of_nodes; + igraph_int_t k, l, min_edge = 0; + (stats->norelabel)++; (*nrelabelsince)++; + DIST(v) = no_of_nodes; + for (k = FIRST(v), l = LAST(v); k < l; k++) { + if (RESCAP(k) > 0 && DIST(HEAD(k)) < min) { + min = DIST(HEAD(k)); + min_edge = k; + } + } + min++; + if (min < no_of_nodes) { + DIST(v) = min; + CURRENT(v) = min_edge; + } +} + +static void igraph_i_mf_push(igraph_int_t v, igraph_int_t e, igraph_int_t n, + igraph_vector_int_t *current, + igraph_vector_t *rescap, igraph_vector_t *excess, + igraph_int_t target, igraph_int_t source, + igraph_buckets_t *buckets, igraph_dbuckets_t *ibuckets, + igraph_vector_int_t *distance, + igraph_vector_int_t *rev, igraph_maxflow_stats_t *stats, + igraph_int_t *npushsince) { + + IGRAPH_UNUSED(current); + IGRAPH_UNUSED(source); + + igraph_real_t delta = + RESCAP(e) < EXCESS(v) ? RESCAP(e) : EXCESS(v); + (stats->nopush)++; (*npushsince)++; + if (EXCESS(n) == 0 && n != target) { + igraph_dbuckets_delete(ibuckets, DIST(n), n); + igraph_buckets_add(buckets, DIST(n), n); + } + RESCAP(e) -= delta; + RESCAP(REV(e)) += delta; + EXCESS(n) += delta; + EXCESS(v) -= delta; +} + +static void igraph_i_mf_discharge(igraph_int_t v, + igraph_vector_int_t *current, + igraph_vector_int_t *first, + igraph_vector_t *rescap, + igraph_vector_int_t *to, + igraph_vector_int_t *distance, + igraph_vector_t *excess, + igraph_int_t no_of_nodes, igraph_int_t source, + igraph_int_t target, igraph_buckets_t *buckets, + igraph_dbuckets_t *ibuckets, + igraph_vector_int_t *rev, + igraph_maxflow_stats_t *stats, + igraph_int_t *npushsince, igraph_int_t *nrelabelsince) { + + do { + igraph_int_t i; + igraph_int_t start = CURRENT(v); + igraph_int_t stop = LAST(v); + for (i = start; i < stop; i++) { + if (RESCAP(i) > 0) { + igraph_int_t nei = HEAD(i); + if (DIST(v) == DIST(nei) + 1) { + PUSH((v), i, nei); + if (EXCESS(v) == 0) { + break; + } + } + } + } + if (i == stop) { + igraph_int_t origdist = DIST(v); + RELABEL(v); + if (igraph_buckets_empty_bucket(buckets, origdist) && + igraph_dbuckets_empty_bucket(ibuckets, origdist)) { + GAP(origdist); + } + if (DIST(v) == no_of_nodes) { + break; + } + } else { + CURRENT(v) = i; + igraph_dbuckets_add(ibuckets, DIST(v), v); + break; + } + } while (1); +} + +static igraph_error_t igraph_i_mf_bfs(igraph_dqueue_int_t *bfsq, + igraph_int_t source, igraph_int_t target, + igraph_int_t no_of_nodes, igraph_buckets_t *buckets, + igraph_dbuckets_t *ibuckets, + igraph_vector_int_t *distance, + igraph_vector_int_t *first, igraph_vector_int_t *current, + igraph_vector_int_t *to, igraph_vector_t *excess, + igraph_vector_t *rescap, igraph_vector_int_t *rev) { + + igraph_int_t k, l; + + IGRAPH_UNUSED(source); + + igraph_buckets_clear(buckets); + igraph_dbuckets_clear(ibuckets); + igraph_vector_int_fill(distance, no_of_nodes); + DIST(target) = 0; + + IGRAPH_CHECK(igraph_dqueue_int_push(bfsq, target)); + while (!igraph_dqueue_int_empty(bfsq)) { + igraph_int_t node = igraph_dqueue_int_pop(bfsq); + igraph_int_t ndist = DIST(node) + 1; + for (k = FIRST(node), l = LAST(node); k < l; k++) { + if (RESCAP(REV(k)) > 0) { + igraph_int_t nei = HEAD(k); + if (DIST(nei) == no_of_nodes) { + DIST(nei) = ndist; + CURRENT(nei) = FIRST(nei); + if (EXCESS(nei) > 0) { + igraph_buckets_add(buckets, ndist, nei); + } else { + igraph_dbuckets_add(ibuckets, ndist, nei); + } + IGRAPH_CHECK(igraph_dqueue_int_push(bfsq, nei)); + } + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_maxflow + * \brief Maximum network flow between a pair of vertices. + * + * This function implements the Goldberg-Tarjan algorithm for + * calculating value of the maximum flow in a directed or undirected + * graph. The algorithm was given in Andrew V. Goldberg, Robert + * E. Tarjan: A New Approach to the Maximum-Flow Problem, Journal of + * the ACM, 35(4), 921-940, 1988 + * https://doi.org/10.1145/48014.61051. + * + * + * The input of the function is a graph, a vector + * of real numbers giving the capacity of the edges and two vertices + * of the graph, the source and the target. A flow is a function + * assigning positive real numbers to the edges and satisfying two + * requirements: (1) the flow value is less than the capacity of the + * edge and (2) at each vertex except the source and the target, the + * incoming flow (i.e. the sum of the flow on the incoming edges) is + * the same as the outgoing flow (i.e. the sum of the flow on the + * outgoing edges). The value of the flow is the incoming flow at the + * target vertex. The maximum flow is the flow with the maximum + * value. + * + * \param graph The input graph, either directed or undirected. + * \param value Pointer to a real number, the value of the maximum + * will be placed here, unless it is a null pointer. + * \param flow If not a null pointer, then it must be a pointer to an + * initialized vector. The vector will be resized, and the flow + * on each edge will be placed in it, in the order of the edge + * IDs. For undirected graphs this argument is bit trickier, + * since for these the flow direction is not predetermined by + * the edge direction. For these graphs the elements of the + * \p flow vector can be negative, this means that the flow + * goes from the bigger vertex ID to the smaller one. Positive + * values mean that the flow goes from the smaller vertex ID to + * the bigger one. + * \param cut A null pointer or a pointer to an initialized vector. + * If not a null pointer, then the minimum cut corresponding to + * the maximum flow is stored here, i.e. all edge IDs that are + * part of the minimum cut are stored in the vector. + * \param partition A null pointer or a pointer to an initialized + * vector. If not a null pointer, then the first partition of + * the minimum cut that corresponds to the maximum flow will be + * placed here. The first partition is always the one that + * contains the source vertex. + * \param partition2 A null pointer or a pointer to an initialized + * vector. If not a null pointer, then the second partition of + * the minimum cut that corresponds to the maximum flow will be + * placed here. The second partition is always the one that + * contains the target vertex. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If \c NULL, then + * every edge is considered to have capacity 1.0. + * \param stats Counts of the number of different operations + * performed by the algorithm are stored here. + * \return Error code. + * + * Time complexity: O(|V|^3). In practice it is much faster, but I + * cannot prove a better lower bound for the data structure I've + * used. In fact, this implementation runs much faster than the + * \c hi_pr implementation discussed in + * B. V. Cherkassky and A. V. Goldberg: On implementing the + * push-relabel method for the maximum flow problem, (Algorithmica, + * 19:390--410, 1997) on all the graph classes I've tried. + * + * \sa \ref igraph_mincut_value(), \ref igraph_edge_connectivity(), + * \ref igraph_vertex_connectivity() for + * properties based on the maximum flow. + * + * \example examples/simple/flow.c + * \example examples/simple/flow2.c + */ + +igraph_error_t igraph_maxflow(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *flow, igraph_vector_int_t *cut, + igraph_vector_int_t *partition, igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_orig_edges = igraph_ecount(graph); + const igraph_int_t no_of_edges = 2 * no_of_orig_edges; + + igraph_vector_t rescap, excess; + igraph_vector_int_t from, to, rev, distance; + igraph_vector_int_t edges, rank; + igraph_vector_int_t current, first; + igraph_buckets_t buckets; + igraph_dbuckets_t ibuckets; + + igraph_dqueue_int_t bfsq; + + igraph_int_t idx; + igraph_int_t npushsince = 0, nrelabelsince = 0; + + igraph_maxflow_stats_t local_stats; /* used if the user passed a null pointer for stats */ + + if (stats == NULL) { + stats = &local_stats; + } + + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_maxflow_undirected(graph, value, flow, cut, + partition, partition2, source, + target, capacity, stats)); + return IGRAPH_SUCCESS; + } + + if (capacity && igraph_vector_size(capacity) != no_of_orig_edges) { + IGRAPH_ERROR("Capacity vector must match number of edges in length.", IGRAPH_EINVAL); + } + if (source < 0 || source >= no_of_nodes || target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid source or target vertex.", IGRAPH_EINVVID); + } + if (source == target) { + IGRAPH_ERROR("Source and target vertices are the same.", IGRAPH_EINVAL); + } + + stats->nopush = stats->norelabel = stats->nogap = stats->nogapnodes = + stats->nobfs = 0; + + /* + * The data structure: + * - First of all, we consider every edge twice, first the edge + * itself, but also its opposite. + * - (from, to) contain all edges (original + opposite), ordered by + * the id of the source vertex. During the algorithm we just need + * 'to', so from is destroyed soon. We only need it in the + * beginning, to create the 'first' pointers. + * - 'first' is a pointer vector for 'to', first[i] points to the + * first neighbor of vertex i and first[i+1]-1 is the last + * neighbor of vertex i. (Unless vertex i is isolate, in which + * case first[i]==first[i+1]). + * - 'rev' contains a mapping from an edge to its opposite pair + * - 'rescap' contains the residual capacities of the edges, this is + * initially equal to the capacity of the edges for the original + * edges and it is zero for the opposite edges. + * - 'excess' contains the excess flow for the vertices. I.e. the flow + * that is coming in, but it is not going out. + * - 'current' stores the next neighboring vertex to check, for every + * vertex, when excess flow is being pushed to neighbors. + * - 'distance' stores the distance of the vertices from the source. + * - 'rank' and 'edges' are only needed temporarily, for ordering and + * storing the edges. + * - we use an igraph_buckets_t data structure ('buckets') to find + * the vertices with the highest 'distance' values quickly. + * This always contains the vertices that have a positive excess + * flow. + */ +#undef FIRST +#undef LAST +#undef CURRENT +#undef RESCAP +#undef REV +#undef HEAD +#undef EXCESS +#undef DIST +#define FIRST(i) (VECTOR(first)[(i)]) +#define LAST(i) (VECTOR(first)[(i)+1]) +#define CURRENT(i) (VECTOR(current)[(i)]) +#define RESCAP(i) (VECTOR(rescap)[(i)]) +#define REV(i) (VECTOR(rev)[(i)]) +#define HEAD(i) (VECTOR(to)[(i)]) +#define EXCESS(i) (VECTOR(excess)[(i)]) +#define DIST(i) (VECTOR(distance)[(i)]) + + IGRAPH_CHECK(igraph_dqueue_int_init(&bfsq, no_of_nodes)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &bfsq); + IGRAPH_VECTOR_INT_INIT_FINALLY(&to, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&rev, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&rescap, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&excess, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&distance, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&first, no_of_nodes + 1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&rank, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&from, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges); + + /* Create the basic data structure */ + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_i_vector_int_rank(&edges, &rank, no_of_nodes)); + + for (igraph_int_t i = 0; i < no_of_edges; i += 2) { + const igraph_int_t pos = VECTOR(rank)[i]; + const igraph_int_t pos2 = VECTOR(rank)[i + 1]; + VECTOR(from)[pos] = VECTOR(edges)[i]; + VECTOR(to)[pos] = VECTOR(edges)[i + 1]; + VECTOR(from)[pos2] = VECTOR(edges)[i + 1]; + VECTOR(to)[pos2] = VECTOR(edges)[i]; + VECTOR(rev)[pos] = pos2; + VECTOR(rev)[pos2] = pos; + VECTOR(rescap)[pos] = capacity ? VECTOR(*capacity)[i / 2] : 1.0; + VECTOR(rescap)[pos2] = 0.0; + } + + /* The first pointers. This is a but trickier, than one would + think, because of the possible isolate vertices. */ + + idx = -1; + for (igraph_int_t i = 0; i <= VECTOR(from)[0]; i++) { + idx++; VECTOR(first)[idx] = 0; + } + for (igraph_int_t i = 1; i < no_of_edges; i++) { + const igraph_int_t n = (VECTOR(from)[i] - + VECTOR(from)[ VECTOR(first)[idx] ]); + for (igraph_int_t j = 0; j < n; j++) { + idx++; VECTOR(first)[idx] = i; + } + } + idx++; + while (idx < no_of_nodes + 1) { + VECTOR(first)[idx++] = no_of_edges; + } + + igraph_vector_int_destroy(&from); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + if (!flow) { + igraph_vector_int_destroy(&rank); + IGRAPH_FINALLY_CLEAN(1); + } + + /* And the current pointers, initially the same as the first */ + IGRAPH_VECTOR_INT_INIT_FINALLY(¤t, no_of_nodes); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(current)[i] = VECTOR(first)[i]; + } + + /* OK, the graph is set up, initialization */ + + IGRAPH_CHECK(igraph_buckets_init(&buckets, no_of_nodes + 1, no_of_nodes)); + IGRAPH_FINALLY(igraph_buckets_destroy, &buckets); + IGRAPH_CHECK(igraph_dbuckets_init(&ibuckets, no_of_nodes + 1, no_of_nodes)); + IGRAPH_FINALLY(igraph_dbuckets_destroy, &ibuckets); + + /* Send as much flow as possible from the source to its neighbors */ + for (igraph_int_t i = FIRST(source), j = LAST(source); i < j; i++) { + if (HEAD(i) != source) { + const igraph_real_t delta = RESCAP(i); + RESCAP(i) = 0; + RESCAP(REV(i)) += delta; + EXCESS(HEAD(i)) += delta; + } + } + + IGRAPH_CHECK(BFS()); + (stats->nobfs)++; + + while (!igraph_buckets_empty(&buckets)) { + const igraph_int_t vertex = igraph_buckets_popmax(&buckets); + DISCHARGE(vertex); + if (npushsince > no_of_nodes / 2 && nrelabelsince > no_of_nodes) { + (stats->nobfs)++; + BFS(); + npushsince = nrelabelsince = 0; + } + } + + /* Store the result */ + if (value) { + *value = EXCESS(target); + } + + /* If we also need the minimum cut */ + if (cut || partition || partition2) { + /* We need to find all vertices from which the target is reachable + in the residual graph. We do a breadth-first search, going + backwards. */ + igraph_dqueue_int_t Q; + igraph_vector_bool_t added; + igraph_int_t marked = 0; + + IGRAPH_CHECK(igraph_vector_bool_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &added); + + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, target)); + VECTOR(added)[target] = true; + marked++; + while (!igraph_dqueue_int_empty(&Q)) { + const igraph_int_t actnode = igraph_dqueue_int_pop(&Q); + for (igraph_int_t i = FIRST(actnode), j = LAST(actnode); i < j; i++) { + igraph_int_t nei = HEAD(i); + if (!VECTOR(added)[nei] && RESCAP(REV(i)) > 0.0) { + VECTOR(added)[nei] = true; + marked++; + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + } + } + } + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(1); + + /* Now we marked each vertex that is on one side of the cut, + check the crossing edges */ + + if (cut) { + igraph_vector_int_clear(cut); + for (igraph_int_t i = 0; i < no_of_orig_edges; i++) { + igraph_int_t f = IGRAPH_FROM(graph, i); + igraph_int_t t = IGRAPH_TO(graph, i); + if (!VECTOR(added)[f] && VECTOR(added)[t]) { + IGRAPH_CHECK(igraph_vector_int_push_back(cut, i)); + } + } + } + + if (partition2) { + igraph_int_t x = 0; + IGRAPH_CHECK(igraph_vector_int_resize(partition2, marked)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(added)[i]) { + VECTOR(*partition2)[x++] = i; + } + } + } + + if (partition) { + igraph_int_t x = 0; + IGRAPH_CHECK(igraph_vector_int_resize(partition, + no_of_nodes - marked)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (!VECTOR(added)[i]) { + VECTOR(*partition)[x++] = i; + } + } + } + + igraph_vector_bool_destroy(&added); + IGRAPH_FINALLY_CLEAN(1); + } + + if (flow) { + /* Initialize the backward distances, with a breadth-first search + from the source */ + igraph_dqueue_int_t Q; + igraph_vector_int_t added; /* uses more than two values, cannot be bool */ + igraph_t flow_graph; + igraph_vector_int_t flow_edges; + igraph_bool_t dag; + + IGRAPH_CHECK(igraph_vector_int_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &added); + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, source)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); + VECTOR(added)[source] = 1; + while (!igraph_dqueue_int_empty(&Q)) { + const igraph_int_t actnode = igraph_dqueue_int_pop(&Q); + const igraph_int_t actdist = igraph_dqueue_int_pop(&Q); + DIST(actnode) = actdist; + + for (igraph_int_t i = FIRST(actnode), j = LAST(actnode); i < j; i++) { + const igraph_int_t nei = HEAD(i); + if (!VECTOR(added)[nei] && RESCAP(REV(i)) > 0.0) { + VECTOR(added)[nei] = 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, actdist + 1)); + } + } + } /* !igraph_dqueue_int_empty(&Q) */ + + igraph_vector_int_destroy(&added); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + /* Reinitialize the buckets */ + igraph_buckets_clear(&buckets); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (EXCESS(i) > 0.0 && i != source && i != target) { + igraph_buckets_add(&buckets, DIST(i), i); + } + } + + /* Now we return the flow to the source */ + while (!igraph_buckets_empty(&buckets)) { + const igraph_int_t vertex = igraph_buckets_popmax(&buckets); + + /* DISCHARGE(vertex) comes here */ + do { + igraph_int_t i, j; + for (i = CURRENT(vertex), j = LAST(vertex); i < j; i++) { + if (RESCAP(i) > 0) { + igraph_int_t nei = HEAD(i); + + if (DIST(vertex) == DIST(nei) + 1) { + igraph_real_t delta = + RESCAP(i) < EXCESS(vertex) ? RESCAP(i) : EXCESS(vertex); + RESCAP(i) -= delta; + RESCAP(REV(i)) += delta; + + if (nei != source && EXCESS(nei) == 0.0 && + DIST(nei) != no_of_nodes) { + igraph_buckets_add(&buckets, DIST(nei), nei); + } + + EXCESS(nei) += delta; + EXCESS(vertex) -= delta; + + if (EXCESS(vertex) == 0) { + break; + } + + } + } + } + + if (i == j) { + + /* RELABEL(vertex) comes here */ + igraph_int_t min; + igraph_int_t min_edge = 0; + DIST(vertex) = min = no_of_nodes; + for (igraph_int_t k = FIRST(vertex), l = LAST(vertex); k < l; k++) { + if (RESCAP(k) > 0) { + if (DIST(HEAD(k)) < min) { + min = DIST(HEAD(k)); + min_edge = k; + } + } + } + + min++; + + if (min < no_of_nodes) { + DIST(vertex) = min; + CURRENT(vertex) = min_edge; + /* Vertex is still active */ + igraph_buckets_add(&buckets, DIST(vertex), vertex); + } + + /* TODO: gap heuristics here ??? */ + + } else { + CURRENT(vertex) = FIRST(vertex); + } + + break; + + } while (true); + } + + /* We need to eliminate flow cycles now. Before that we check that + there is a cycle in the flow graph. + + First we do a couple of DFSes from the source vertex to the + target and factor out the paths we find. If there is no more + path to the target, then all remaining flow must be in flow + cycles, so we don't need it at all. + + Some details. 'stack' contains the whole path of the DFS, both + the vertices and the edges, they are alternating in the stack. + 'current' helps finding the next outgoing edge of a vertex + quickly, the next edge of 'v' is FIRST(v)+CURRENT(v). If this + is LAST(v), then there are no more edges to try. + + The 'added' vector contains 0 if the vertex was not visited + before, 1 if it is currently in 'stack', and 2 if it is not in + 'stack', but it was visited before. */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&flow_edges, 0); + for (igraph_int_t i = 0, j = 0; i < no_of_edges; i += 2, j++) { + const igraph_int_t pos = VECTOR(rank)[i]; + if ((capacity ? VECTOR(*capacity)[j] : 1.0) > RESCAP(pos)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&flow_edges, + IGRAPH_FROM(graph, j))); + IGRAPH_CHECK(igraph_vector_int_push_back(&flow_edges, + IGRAPH_TO(graph, j))); + } + } + IGRAPH_CHECK(igraph_create(&flow_graph, &flow_edges, no_of_nodes, + IGRAPH_DIRECTED)); + igraph_vector_int_destroy(&flow_edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, &flow_graph); + IGRAPH_CHECK(igraph_is_dag(&flow_graph, &dag)); + igraph_destroy(&flow_graph); + IGRAPH_FINALLY_CLEAN(1); + + if (!dag) { + igraph_vector_int_t stack; + igraph_vector_t mycap; + + IGRAPH_CHECK(igraph_vector_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &stack); + IGRAPH_CHECK(igraph_vector_int_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &added); + IGRAPH_VECTOR_INIT_FINALLY(&mycap, no_of_edges); + +#define MYCAP(i) (VECTOR(mycap)[(i)]) + + for (igraph_int_t i = 0; i < no_of_edges; i += 2) { + const igraph_int_t pos = VECTOR(rank)[i]; + const igraph_int_t pos2 = VECTOR(rank)[i + 1]; + MYCAP(pos) = (capacity ? VECTOR(*capacity)[i / 2] : 1.0) - RESCAP(pos); + MYCAP(pos2) = 0.0; + } + + do { + igraph_vector_int_null(¤t); + igraph_vector_int_clear(&stack); + igraph_vector_int_null(&added); + + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, -1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, source)); + VECTOR(added)[source] = 1; + while (!igraph_vector_int_empty(&stack) && + igraph_vector_int_tail(&stack) != target) { + const igraph_int_t actnode = igraph_vector_int_tail(&stack); + igraph_int_t edge = FIRST(actnode) + CURRENT(actnode); + igraph_int_t nei; + while (edge < LAST(actnode) && MYCAP(edge) == 0.0) { + edge++; + } + nei = edge < LAST(actnode) ? HEAD(edge) : -1; + + if (edge < LAST(actnode) && !VECTOR(added)[nei]) { + /* Go forward along next edge, if the vertex was not + visited before */ + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, edge)); + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, nei)); + VECTOR(added)[nei] = 1; + CURRENT(actnode) += 1; + } else if (edge < LAST(actnode) && VECTOR(added)[nei] == 1) { + /* We found a flow cycle, factor it out. Go back in stack + until we find 'nei' again, determine the flow along the + cycle. */ + igraph_real_t thisflow = MYCAP(edge); + for (igraph_int_t idx = igraph_vector_int_size(&stack) - 2; + idx >= 0 && VECTOR(stack)[idx + 1] != nei; idx -= 2) { + const igraph_int_t e = VECTOR(stack)[idx]; + const igraph_real_t rcap = e >= 0 ? MYCAP(e) : MYCAP(edge); + if (rcap < thisflow) { + thisflow = rcap; + } + } + MYCAP(edge) -= thisflow; RESCAP(edge) += thisflow; + for (igraph_int_t idx = igraph_vector_int_size(&stack) - 2; + idx >= 0 && VECTOR(stack)[idx + 1] != nei; idx -= 2) { + const igraph_int_t e = VECTOR(stack)[idx]; + if (e >= 0) { + MYCAP(e) -= thisflow; + RESCAP(e) += thisflow; + } + } + CURRENT(actnode) += 1; + } else if (edge < LAST(actnode)) { /* && VECTOR(added)[nei]==2 */ + /* The next edge leads to a vertex that was visited before, + but it is currently not in 'stack' */ + CURRENT(actnode) += 1; + } else { + /* Go backward, take out the node and the edge that leads to it */ + igraph_vector_int_pop_back(&stack); + igraph_vector_int_pop_back(&stack); + VECTOR(added)[actnode] = 2; + } + } + + /* If non-empty, then it contains a path from source to target + in the residual graph. We factor out this path from the flow. */ + if (!igraph_vector_int_empty(&stack)) { + const igraph_int_t pl = igraph_vector_int_size(&stack); + igraph_real_t thisflow = EXCESS(target); + for (igraph_int_t i = 2; i < pl; i += 2) { + const igraph_int_t edge = VECTOR(stack)[i]; + const igraph_real_t rcap = MYCAP(edge); + if (rcap < thisflow) { + thisflow = rcap; + } + } + for (igraph_int_t i = 2; i < pl; i += 2) { + const igraph_int_t edge = VECTOR(stack)[i]; + MYCAP(edge) -= thisflow; + } + } + + } while (!igraph_vector_int_empty(&stack)); + + igraph_vector_destroy(&mycap); + igraph_vector_int_destroy(&added); + igraph_vector_int_destroy(&stack); + IGRAPH_FINALLY_CLEAN(3); + } + + /* ----------------------------------------------------------- */ + + IGRAPH_CHECK(igraph_vector_resize(flow, no_of_orig_edges)); + for (igraph_int_t i = 0, j = 0; i < no_of_edges; i += 2, j++) { + const igraph_int_t pos = VECTOR(rank)[i]; + VECTOR(*flow)[j] = (capacity ? VECTOR(*capacity)[j] : 1.0) - + RESCAP(pos); + } + + igraph_vector_int_destroy(&rank); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_dbuckets_destroy(&ibuckets); + igraph_buckets_destroy(&buckets); + igraph_vector_int_destroy(¤t); + igraph_vector_int_destroy(&first); + igraph_vector_int_destroy(&distance); + igraph_vector_destroy(&excess); + igraph_vector_destroy(&rescap); + igraph_vector_int_destroy(&rev); + igraph_vector_int_destroy(&to); + igraph_dqueue_int_destroy(&bfsq); + IGRAPH_FINALLY_CLEAN(10); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_maxflow_value + * \brief Maximum flow in a network with the push/relabel algorithm. + * + * This function implements the Goldberg-Tarjan algorithm for + * calculating value of the maximum flow in a directed or undirected + * graph. The algorithm was given in Andrew V. Goldberg, Robert + * E. Tarjan: A New Approach to the Maximum-Flow Problem, Journal of + * the ACM, 35(4), 921-940, 1988 + * https://doi.org/10.1145/48014.61051. + * + * + * The input of the function is a graph, a vector + * of real numbers giving the capacity of the edges and two vertices + * of the graph, the source and the target. A flow is a function + * assigning positive real numbers to the edges and satisfying two + * requirements: (1) the flow value is less than the capacity of the + * edge and (2) at each vertex except the source and the target, the + * incoming flow (i.e. the sum of the flow on the incoming edges) is + * the same as the outgoing flow (i.e. the sum of the flow on the + * outgoing edges). The value of the flow is the incoming flow at the + * target vertex. The maximum flow is the flow with the maximum + * value. + * + * + * According to a theorem by Ford and Fulkerson + * (L. R. Ford Jr. and D. R. Fulkerson. Maximal flow through a + * network. Canadian J. Math., 8:399-404, 1956.) the maximum flow + * between two vertices is the same as the + * minimum cut between them (also called the minimum s-t cut). So \ref + * igraph_st_mincut_value() gives the same result in all cases as \ref + * igraph_maxflow_value(). + * + * + * Note that the value of the maximum flow is the same as the + * minimum cut in the graph. + * + * \param graph The input graph, either directed or undirected. + * \param value Pointer to a real number, the result will be placed here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If NULL, then + * every edge is considered to have capacity 1.0. + * \param stats Counts of the number of different operations + * preformed by the algorithm are stored here. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_maxflow() to calculate the actual flow. + * \ref igraph_mincut_value(), \ref igraph_edge_connectivity(), + * \ref igraph_vertex_connectivity() for + * properties based on the maximum flow. + */ + +igraph_error_t igraph_maxflow_value(const igraph_t *graph, igraph_real_t *value, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + + return igraph_maxflow(graph, value, /*flow=*/ NULL, /*cut=*/ NULL, + /*partition=*/ NULL, /*partition1=*/ NULL, + source, target, capacity, stats); +} + +/** + * \function igraph_st_mincut_value + * \brief The minimum s-t cut in a graph. + * + * The minimum s-t cut in a weighted (=valued) graph is the + * total minimum edge weight needed to remove from the graph to + * eliminate all paths from a given vertex (\p source) to + * another vertex (\p target). Directed paths are considered in + * directed graphs, and undirected paths in undirected graphs. + * + * The minimum s-t cut between two vertices is known to be same + * as the maximum flow between these two vertices. So this function + * calls \ref igraph_maxflow_value() to do the calculation. + * + * \param graph The input graph. + * \param value Pointer to a real variable, the result will be stored + * here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Pointer to the capacity vector, it should contain + * non-negative numbers and its length should be the same the + * the number of edges in the graph. It can be a null pointer, then + * every edge has unit capacity. + * \return Error code. + * + * Time complexity: O(|V|^3), see also the discussion for \ref + * igraph_maxflow_value(), |V| is the number of vertices. + */ + +igraph_error_t igraph_st_mincut_value(const igraph_t *graph, igraph_real_t *value, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity) { + + if (source == target) { + IGRAPH_ERROR("source and target vertices are the same", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, value, source, target, capacity, NULL)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_st_mincut + * \brief Minimum cut between a source and a target vertex. + * + * Finds the edge set that has the smallest total capacity among all + * edge sets that disconnect the source and target vertices. + * + * The calculation is performed using maximum flow + * techniques, by calling \ref igraph_maxflow(). + * + * \param graph The input graph. + * \param value Pointer to a real variable, the value of the cut is + * stored here. + * \param cut Pointer to an initialized vector, the edge IDs that are included + * in the cut are stored here. This argument is ignored if it + * is a null pointer. + * \param partition Pointer to an initialized vector, the vertex IDs of the + * vertices in the first partition of the cut are stored + * here. The first partition is always the one that contains the + * source vertex. This argument is ignored if it is a null pointer. + * \param partition2 Pointer to an initialized vector, the vertex IDs of the + * vertices in the second partition of the cut are stored here. + * The second partition is always the one that contains the + * target vertex. This argument is ignored if it is a null pointer. + * \param source Integer, the id of the source vertex. + * \param target Integer, the id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If a + * null pointer, then every edge is considered to have capacity + * 1.0. + * \return Error code. + * + * \sa \ref igraph_maxflow(). + * + * Time complexity: see \ref igraph_maxflow(). + */ + +igraph_error_t igraph_st_mincut(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_t *cut, igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity) { + + return igraph_maxflow(graph, value, /*flow=*/ NULL, + cut, partition, partition2, + source, target, capacity, NULL); +} + +/* + * This is the Stoer-Wagner algorithm, it works for calculating the + * minimum cut for undirected graphs, for the whole graph. + * I.e. this is basically the edge-connectivity of the graph. + * It can also calculate the cut itself, not just the cut value. + */ + +static igraph_error_t igraph_i_mincut_undirected( + const igraph_t *graph, + igraph_real_t *res, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_vector_int_t *cut, + const igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + igraph_i_cutheap_t heap; + igraph_real_t mincut = IGRAPH_INFINITY; /* infinity */ + igraph_int_t i; + + igraph_adjlist_t adjlist; + igraph_inclist_t inclist; + + igraph_vector_int_t mergehist; + igraph_bool_t calc_cut = partition || partition2 || cut; + igraph_int_t act_step = 0, mincut_step = 0; + + if (capacity && igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Invalid capacity vector size", IGRAPH_EINVAL); + } + + /* Check if the graph is connected at all */ + { + igraph_vector_int_t memb; + igraph_vector_int_t csize; + igraph_int_t no; + IGRAPH_VECTOR_INT_INIT_FINALLY(&memb, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0); + IGRAPH_CHECK(igraph_connected_components(graph, &memb, &csize, &no, IGRAPH_WEAK)); + if (no != 1) { + if (res) { + *res = 0; + } + if (cut) { + igraph_vector_int_clear(cut); + } + if (partition) { + igraph_int_t j = 0; + IGRAPH_CHECK(igraph_vector_int_resize(partition, + VECTOR(csize)[0])); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(memb)[i] == 0) { + VECTOR(*partition)[j++] = i; + } + } + } + if (partition2) { + igraph_int_t j = 0; + IGRAPH_CHECK(igraph_vector_int_resize(partition2, no_of_nodes - + VECTOR(csize)[0])); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(memb)[i] != 0) { + VECTOR(*partition2)[j++] = i; + } + } + } + } + igraph_vector_int_destroy(&csize); + igraph_vector_int_destroy(&memb); + IGRAPH_FINALLY_CLEAN(2); + + if (no != 1) { + return IGRAPH_SUCCESS; + } + } + + if (calc_cut) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mergehist, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&mergehist, no_of_nodes * 2)); + } + + IGRAPH_CHECK(igraph_i_cutheap_init(&heap, no_of_nodes)); + IGRAPH_FINALLY(igraph_i_cutheap_destroy, &heap); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + while (igraph_i_cutheap_size(&heap) >= 2) { + + igraph_int_t last; + igraph_real_t acut; + igraph_int_t a, n; + + igraph_vector_int_t *edges, *edges2; + igraph_vector_int_t *neis, *neis2; + + do { + a = igraph_i_cutheap_popmax(&heap); + + /* update the weights of the active vertices connected to a */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; i++) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t to = VECTOR(*neis)[i]; + igraph_real_t weight = capacity ? VECTOR(*capacity)[edge] : 1.0; + igraph_i_cutheap_update(&heap, to, weight); + } + + } while (igraph_i_cutheap_active_size(&heap) > 1); + + /* Now, there is only one active vertex left, + calculate the cut of the phase */ + acut = igraph_i_cutheap_maxvalue(&heap); + last = igraph_i_cutheap_popmax(&heap); + + if (acut < mincut) { + mincut = acut; + mincut_step = act_step; + } + + if (mincut == 0) { + break; + } + + /* And contract the last and the remaining vertex (a and last) */ + /* Before actually doing that, make some notes */ + act_step++; + if (calc_cut) { + IGRAPH_CHECK(igraph_vector_int_push_back(&mergehist, a)); + IGRAPH_CHECK(igraph_vector_int_push_back(&mergehist, last)); + } + /* First remove the a--last edge if there is one, a is still the + last deactivated vertex */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; ) { + if (VECTOR(*neis)[i] == last) { + VECTOR(*neis)[i] = VECTOR(*neis)[n - 1]; + VECTOR(*edges)[i] = VECTOR(*edges)[n - 1]; + igraph_vector_int_pop_back(neis); + igraph_vector_int_pop_back(edges); + n--; + } else { + i++; + } + } + + edges = igraph_inclist_get(&inclist, last); + neis = igraph_adjlist_get(&adjlist, last); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; ) { + if (VECTOR(*neis)[i] == a) { + VECTOR(*neis)[i] = VECTOR(*neis)[n - 1]; + VECTOR(*edges)[i] = VECTOR(*edges)[n - 1]; + igraph_vector_int_pop_back(neis); + igraph_vector_int_pop_back(edges); + n--; + } else { + i++; + } + } + + /* Now rewrite the edge lists of last's neighbors */ + neis = igraph_adjlist_get(&adjlist, last); + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + igraph_int_t n2, j; + neis2 = igraph_adjlist_get(&adjlist, nei); + n2 = igraph_vector_int_size(neis2); + for (j = 0; j < n2; j++) { + if (VECTOR(*neis2)[j] == last) { + VECTOR(*neis2)[j] = a; + } + } + } + + /* And append the lists of last to the lists of a */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + edges2 = igraph_inclist_get(&inclist, last); + neis2 = igraph_adjlist_get(&adjlist, last); + IGRAPH_CHECK(igraph_vector_int_append(edges, edges2)); + IGRAPH_CHECK(igraph_vector_int_append(neis, neis2)); + igraph_vector_int_clear(edges2); /* TODO: free it */ + igraph_vector_int_clear(neis2); /* TODO: free it */ + + /* Remove the deleted vertex from the heap entirely */ + igraph_i_cutheap_reset_undefine(&heap, last); + } + + *res = mincut; + + igraph_inclist_destroy(&inclist); + igraph_adjlist_destroy(&adjlist); + igraph_i_cutheap_destroy(&heap); + IGRAPH_FINALLY_CLEAN(3); + + if (calc_cut) { + igraph_int_t bignode = VECTOR(mergehist)[2 * mincut_step + 1]; + igraph_int_t i, idx; + igraph_int_t size = 1; + igraph_bitset_t mark; + + IGRAPH_BITSET_INIT_FINALLY(&mark, no_of_nodes); + + /* first count the vertices in the partition */ + IGRAPH_BIT_SET(mark, bignode); + for (i = mincut_step - 1; i >= 0; i--) { + if ( IGRAPH_BIT_TEST(mark, VECTOR(mergehist)[2 * i]) ) { + size++; + IGRAPH_BIT_SET(mark, VECTOR(mergehist)[2 * i + 1]); + } + } + + /* now store them, if requested */ + if (partition) { + IGRAPH_CHECK(igraph_vector_int_resize(partition, size)); + idx = 0; + VECTOR(*partition)[idx++] = bignode; + for (i = mincut_step - 1; i >= 0; i--) { + if (IGRAPH_BIT_TEST(mark, VECTOR(mergehist)[2 * i])) { + VECTOR(*partition)[idx++] = VECTOR(mergehist)[2 * i + 1]; + } + } + } + + /* The other partition too? */ + if (partition2) { + IGRAPH_CHECK(igraph_vector_int_resize(partition2, no_of_nodes - size)); + idx = 0; + for (i = 0; i < no_of_nodes; i++) { + if (!IGRAPH_BIT_TEST(mark, i)) { + VECTOR(*partition2)[idx++] = i; + } + } + } + + /* The edges in the cut are also requested? */ + /* We want as few memory allocated for 'cut' as possible, + so we first collect the edges in mergehist, we don't + need that anymore. Then we copy it to 'cut'; */ + if (cut) { + igraph_int_t from, to; + igraph_vector_int_clear(&mergehist); + for (i = 0; i < no_of_edges; i++) { + igraph_edge(graph, i, &from, &to); + if ((IGRAPH_BIT_TEST(mark, from) && !IGRAPH_BIT_TEST(mark, to)) || + (IGRAPH_BIT_TEST(mark, to) && !IGRAPH_BIT_TEST(mark, from))) { + IGRAPH_CHECK(igraph_vector_int_push_back(&mergehist, i)); + } + } + igraph_vector_int_clear(cut); + IGRAPH_CHECK(igraph_vector_int_append(cut, &mergehist)); + } + + igraph_bitset_destroy(&mark); + igraph_vector_int_destroy(&mergehist); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_mincut_directed( + const igraph_t *graph, + igraph_real_t *value, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_vector_int_t *cut, + const igraph_vector_t *capacity) { + + igraph_int_t i; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_real_t flow; + igraph_real_t minmaxflow = IGRAPH_INFINITY; + igraph_vector_int_t mypartition, mypartition2, mycut; + igraph_vector_int_t *ppartition = NULL, *ppartition2 = NULL, *pcut = NULL; + igraph_vector_int_t bestpartition, bestpartition2, bestcut; + + if (partition) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&bestpartition, 0); + } + if (partition2) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&bestpartition2, 0); + } + if (cut) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&bestcut, 0); + } + + if (partition) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mypartition, 0); + ppartition = &mypartition; + } + if (partition2) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mypartition2, 0); + ppartition2 = &mypartition2; + } + if (cut) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mycut, 0); + pcut = &mycut; + } + + for (i = 1; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_maxflow(graph, /*value=*/ &flow, /*flow=*/ NULL, + pcut, ppartition, ppartition2, /*source=*/ 0, + /*target=*/ i, capacity, NULL)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (cut) { + IGRAPH_CHECK(igraph_vector_int_update(&bestcut, &mycut)); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_int_update(&bestpartition, &mypartition)); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_int_update(&bestpartition2, &mypartition2)); + } + + if (minmaxflow == 0) { + break; + } + } + IGRAPH_CHECK(igraph_maxflow(graph, /*value=*/ &flow, /*flow=*/ NULL, + pcut, ppartition, ppartition2, + /*source=*/ i, + /*target=*/ 0, capacity, NULL)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (cut) { + IGRAPH_CHECK(igraph_vector_int_update(&bestcut, &mycut)); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_int_update(&bestpartition, &mypartition)); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_int_update(&bestpartition2, &mypartition2)); + } + + if (minmaxflow == 0) { + break; + } + } + } + + if (value) { + *value = minmaxflow; + } + + if (cut) { + igraph_vector_int_destroy(&mycut); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition) { + igraph_vector_int_destroy(&mypartition); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition2) { + igraph_vector_int_destroy(&mypartition2); + IGRAPH_FINALLY_CLEAN(1); + } + if (cut) { + IGRAPH_CHECK(igraph_vector_int_update(cut, &bestcut)); + igraph_vector_int_destroy(&bestcut); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_int_update(partition2, &bestpartition2)); + igraph_vector_int_destroy(&bestpartition2); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_int_update(partition, &bestpartition)); + igraph_vector_int_destroy(&bestpartition); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_mincut + * \brief Calculates the minimum cut in a graph. + * + * This function calculates the minimum cut in a graph. + * The minimum cut is the minimum set of edges which needs to be + * removed to disconnect the graph. The minimum is calculated using + * the weights (\p capacity) of the edges, so the cut with the minimum + * total capacity is calculated. + * + * For directed graphs an implementation based on + * calculating 2|V|-2 maximum flows is used. + * For undirected graphs we use the Stoer-Wagner + * algorithm, as described in M. Stoer and F. Wagner: A simple min-cut + * algorithm, Journal of the ACM, 44 585-591, 1997. + * + * + * The first implementation of the actual cut calculation for + * undirected graphs was made by Gregory Benison, thanks Greg. + * + * \param graph The input graph. + * \param value Pointer to a float, the value of the cut will be + * stored here. + * \param partition Pointer to an initialized vector, the ids + * of the vertices in the first partition after separating the + * graph will be stored here. The vector will be resized as + * needed. This argument is ignored if it is a NULL pointer. + * \param partition2 Pointer to an initialized vector the ids + * of the vertices in the second partition will be stored here. + * The vector will be resized as needed. This argument is ignored + * if it is a NULL pointer. + * \param cut Pointer to an initialized vector, the IDs of the edges + * in the cut will be stored here. This argument is ignored if it + * is a NULL pointer. + * \param capacity A numeric vector giving the capacities of the + * edges. If a null pointer then all edges have unit capacity. + * \return Error code. + * + * \sa \ref igraph_mincut_value(), a simpler interface for calculating + * the value of the cut only. + * + * Time complexity: for directed graphs it is O(|V|^4), but see the + * remarks at \ref igraph_maxflow(). For undirected graphs it is + * O(|V||E|+|V|^2 log|V|). |V| and |E| are the number of vertices and + * edges respectively. + * + * \example examples/simple/igraph_mincut.c + */ + +igraph_error_t igraph_mincut(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_int_t *partition, + igraph_vector_int_t *partition2, + igraph_vector_int_t *cut, + const igraph_vector_t *capacity) { + + if (igraph_is_directed(graph)) { + if (partition || partition2 || cut) { + igraph_i_mincut_directed(graph, value, partition, partition2, cut, + capacity); + } else { + return igraph_mincut_value(graph, value, capacity); + } + } else { + IGRAPH_CHECK(igraph_i_mincut_undirected(graph, value, partition, + partition2, cut, capacity)); + return IGRAPH_SUCCESS; + } + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_mincut_value_undirected( + const igraph_t *graph, + igraph_real_t *res, + const igraph_vector_t *capacity) { + return igraph_i_mincut_undirected(graph, res, 0, 0, 0, capacity); +} + +/** + * \function igraph_mincut_value + * \brief The minimum edge cut in a graph. + * + * The minimum edge cut in a graph is the total minimum + * weight of the edges needed to remove from the graph to make the + * graph \em not strongly connected. (If the original graph is not + * strongly connected then this is zero.) Note that in undirected + * graphs strong connectedness is the same as weak connectedness. + * + * The minimum cut can be calculated with maximum flow + * techniques, although the current implementation does this only for + * directed graphs and a separate non-flow based implementation is + * used for undirected graphs. See Mechthild Stoer and Frank Wagner: A + * simple min-cut algorithm, Journal of the ACM 44 585--591, 1997. + * For directed graphs + * the maximum flow is calculated between a fixed vertex and all the + * other vertices in the graph and this is done in both + * directions. Then the minimum is taken to get the minimum cut. + * + * \param graph The input graph. + * \param res Pointer to a real variable, the result will be stored + * here. + * \param capacity Pointer to the capacity vector, it should contain + * the same number of non-negative numbers as the number of edges in + * the graph. If a null pointer then all edges will have unit capacity. + * \return Error code. + * + * \sa \ref igraph_mincut(), \ref igraph_maxflow_value(), \ref + * igraph_st_mincut_value(). + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + */ + +igraph_error_t igraph_mincut_value(const igraph_t *graph, igraph_real_t *res, + const igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_real_t minmaxflow, flow; + igraph_int_t i; + + minmaxflow = IGRAPH_INFINITY; + + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_mincut_value_undirected(graph, res, capacity)); + return IGRAPH_SUCCESS; + } + + for (i = 1; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, 0, i, + capacity, NULL)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (flow == 0) { + break; + } + } + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, i, 0, + capacity, NULL)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (flow == 0) { + break; + } + } + } + + if (res) { + *res = minmaxflow; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_st_vertex_connectivity_check_errors( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors, + igraph_bool_t *done, + igraph_int_t *no_conn) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t eid; + igraph_bool_t conn; + *done = true; + *no_conn = 0; + + if (source == target) { + IGRAPH_ERROR("Source and target vertices are the same.", IGRAPH_EINVAL); + } + + if (source < 0 || source >= no_of_nodes || target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid source or target vertex.", IGRAPH_EINVAL); + } + + switch (neighbors) { + case IGRAPH_VCONN_NEI_ERROR: + IGRAPH_CHECK(igraph_are_adjacent(graph, source, target, &conn)); + if (conn) { + IGRAPH_ERROR("Source and target vertices connected.", IGRAPH_EINVAL); + } + break; + case IGRAPH_VCONN_NEI_NEGATIVE: + IGRAPH_CHECK(igraph_are_adjacent(graph, source, target, &conn)); + if (conn) { + *res = -1; + return IGRAPH_SUCCESS; + } + break; + case IGRAPH_VCONN_NEI_NUMBER_OF_NODES: + IGRAPH_CHECK(igraph_are_adjacent(graph, source, target, &conn)); + if (conn) { + *res = no_of_nodes; + return IGRAPH_SUCCESS; + } + break; + case IGRAPH_VCONN_NEI_IGNORE: + IGRAPH_CHECK(igraph_get_eid(graph, &eid, source, target, IGRAPH_DIRECTED, /*error=*/ false)); + if (eid >= 0) { + IGRAPH_CHECK(igraph_count_multiple_1(graph, no_conn, eid)); + } + break; + default: + IGRAPH_ERROR("Unknown `igraph_vconn_nei_t'.", IGRAPH_EINVAL); + break; + } + *done = false; + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_st_vertex_connectivity_directed( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges; + igraph_real_t real_res; + igraph_t newgraph; + igraph_int_t i, len; + igraph_bool_t done; + igraph_int_t no_conn; + igraph_vector_int_t incs; + igraph_vector_t capacity; + + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_check_errors(graph, res, source, target, neighbors, &done, &no_conn)); + if (done) { + return IGRAPH_SUCCESS; + } + + /* Create the new graph */ + IGRAPH_CHECK(igraph_i_split_vertices(graph, &newgraph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + /* Create the capacity vector, fill it with ones */ + no_of_edges = igraph_ecount(&newgraph); + IGRAPH_VECTOR_INIT_FINALLY(&capacity, no_of_edges); + igraph_vector_fill(&capacity, 1); + + /* "Disable" the edges incident on the input half of the source vertex + * and the output half of the target vertex */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&incs, 0); + IGRAPH_CHECK(igraph_incident(&newgraph, &incs, source + no_of_nodes, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (i = 0; i < len; i++) { + VECTOR(capacity)[VECTOR(incs)[i]] = 0; + } + IGRAPH_CHECK(igraph_incident(&newgraph, &incs, target, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (i = 0; i < len; i++) { + VECTOR(capacity)[VECTOR(incs)[i]] = 0; + } + igraph_vector_int_destroy(&incs); + IGRAPH_FINALLY_CLEAN(1); + + /* Do the maximum flow */ + IGRAPH_CHECK(igraph_maxflow_value(&newgraph, &real_res, + source, target + no_of_nodes, &capacity, NULL)); + *res = (igraph_int_t) real_res; + + *res -= no_conn; + + igraph_vector_destroy(&capacity); + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_st_vertex_connectivity_undirected( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors) { + + igraph_t newgraph; + igraph_bool_t done; + igraph_int_t no_conn; + + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_check_errors(graph, res, source, target, neighbors, &done, &no_conn)); + if (done) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_copy(&newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_to_directed(&newgraph, IGRAPH_TO_DIRECTED_MUTUAL)); + + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(&newgraph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_st_vertex_connectivity + * \brief The vertex connectivity of a pair of vertices. + * + * The vertex connectivity of two vertices (\p source and + * \p target) is the minimum number of vertices that must be + * deleted to eliminate all paths from \p source to \p + * target. Directed paths are considered in directed graphs. + * + * + * The vertex connectivity of a pair is the same as the number + * of different (i.e. node-independent) paths from source to + * target, assuming no direct edges between them. + * + * + * The current implementation uses maximum flow calculations to + * obtain the result. + * + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param neighbors A constant giving what to do if the two vertices + * are connected. Possible values: + * \c IGRAPH_VCONN_NEI_ERROR, stop with an error message, + * \c IGRAPH_VCONN_NEI_NEGATIVE, return -1. + * \c IGRAPH_VCONN_NEI_NUMBER_OF_NODES, return the number of nodes. + * \c IGRAPH_VCONN_NEI_IGNORE, ignore the fact that the two vertices + * are connected and calculate the number of vertices needed + * to eliminate all paths except for the trivial (direct) paths + * between \p source and \p vertex. + * \return Error code. + * + * Time complexity: O(|V|^3), but see the discussion at \ref + * igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_connectivity(), + * \ref igraph_edge_connectivity(), + * \ref igraph_maxflow_value(). + */ + +igraph_error_t igraph_st_vertex_connectivity( + const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target, + igraph_vconn_nei_t neighbors) { + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(graph, res, + source, target, + neighbors)); + } else { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_undirected(graph, res, + source, target, + neighbors)); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_vertex_connectivity_directed( + const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t all_edges_are_mutual) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges; + igraph_int_t i, j, k, len; + igraph_int_t minconn = no_of_nodes - 1, conn = 0; + igraph_t split_graph; + igraph_vector_t capacity; + igraph_bool_t done; + igraph_int_t dummy_num_connections; + igraph_vector_int_t incs; + igraph_real_t real_res; + + /* Create the new graph */ + IGRAPH_CHECK(igraph_i_split_vertices(graph, &split_graph)); + IGRAPH_FINALLY(igraph_destroy, &split_graph); + + /* Create the capacity vector, fill it with ones */ + no_of_edges = igraph_ecount(&split_graph); + IGRAPH_VECTOR_INIT_FINALLY(&capacity, no_of_edges); + igraph_vector_fill(&capacity, 1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&incs, 0); + + for (i = 0; i < no_of_nodes; i++) { + for (j = all_edges_are_mutual ? i + 1 : 0; j < no_of_nodes; j++) { + if (i == j) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Check for easy cases */ + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_check_errors( + graph, &conn, i, j, IGRAPH_VCONN_NEI_NUMBER_OF_NODES, &done, + &dummy_num_connections + )); + + /* 'done' will be set to true if the two vertices are already + * connected, and in this case 'res' will be set to the number of + * nodes-1. + * + * Also, since we used IGRAPH_VCONN_NEI_NUMBER_OF_NODES, + * dummy_num_connections will always be zero, no need to deal with + * it */ + IGRAPH_ASSERT(dummy_num_connections == 0); + + if (!done) { + /* "Disable" the edges incident on the input half of the source vertex + * and the output half of the target vertex */ + IGRAPH_CHECK(igraph_incident(&split_graph, &incs, i + no_of_nodes, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (k = 0; k < len; k++) { + VECTOR(capacity)[VECTOR(incs)[k]] = 0; + } + IGRAPH_CHECK(igraph_incident(&split_graph, &incs, j, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (k = 0; k < len; k++) { + VECTOR(capacity)[VECTOR(incs)[k]] = 0; + } + + /* Do the maximum flow */ + IGRAPH_CHECK(igraph_maxflow_value( + &split_graph, &real_res, i, j + no_of_nodes, &capacity, NULL + )); + + /* Restore the capacities */ + IGRAPH_CHECK(igraph_incident(&split_graph, &incs, i + no_of_nodes, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (k = 0; k < len; k++) { + VECTOR(capacity)[VECTOR(incs)[k]] = 1; + } + IGRAPH_CHECK(igraph_incident(&split_graph, &incs, j, IGRAPH_ALL, IGRAPH_LOOPS)); + len = igraph_vector_int_size(&incs); + for (k = 0; k < len; k++) { + VECTOR(capacity)[VECTOR(incs)[k]] = 1; + } + + conn = (igraph_int_t) real_res; + } + + if (conn < minconn) { + minconn = conn; + if (conn == 0) { + break; + } + } + } + + if (minconn == 0) { + break; + } + } + + if (res) { + *res = minconn; + } + + igraph_vector_int_destroy(&incs); + igraph_vector_destroy(&capacity); + igraph_destroy(&split_graph); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_vertex_connectivity_undirected( + const igraph_t *graph, + igraph_int_t *res) { + + igraph_t newgraph; + + IGRAPH_CHECK(igraph_copy(&newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_to_directed(&newgraph, IGRAPH_TO_DIRECTED_MUTUAL)); + + IGRAPH_CHECK(igraph_i_vertex_connectivity_directed(&newgraph, res, /* all_edges_are_mutual = */ true)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Use that vertex.connectivity(G) <= edge.connectivity(G) <= min(degree(G)) + * + * Check if the graph is connected, and if its minimum degree is 1. + * This is sufficient to determine both the vertex and edge connectivity, + * which are returned in 'res'. 'found' will indicate if the connectivity + * could be determined. + */ +static igraph_error_t igraph_i_connectivity_checks( + const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t *found) { + + igraph_bool_t conn; + *found = false; + + if (igraph_vcount(graph) == 0) { + *res = 0; + *found = true; + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_STRONG)); + if (!conn) { + *res = 0; + *found = true; + } else { + igraph_vector_int_t degree; + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, 0); + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + if (igraph_vector_int_min(°ree) == 1) { + *res = 1; + *found = true; + } + } else { + /* directed, check both in- & out-degree */ + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + if (igraph_vector_int_min(°ree) == 1) { + *res = 1; + *found = true; + } else { + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + if (igraph_vector_int_min(°ree) == 1) { + *res = 1; + *found = true; + } + } + } + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vertex_connectivity + * \brief The vertex connectivity of a graph. + * + * The vertex connectivity of a graph is the minimum + * vertex connectivity along each pairs of vertices in the graph. + * + * + * The vertex connectivity of a graph is the same as group + * cohesion as defined in Douglas R. White and Frank Harary: The + * cohesiveness of blocks in social networks: node connectivity and + * conditional density, Sociological Methodology 31:305--359, 2001 + * https://doi.org/10.1111/0081-1750.00098. + * + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Boolean constant. Whether to check if the graph is + * connected or complete and also the degree of the vertices. If the graph is + * not (strongly) connected then the connectivity is obviously zero. Otherwise + * if the minimum degree is 1 then the vertex connectivity is also + * 1. If the graph is complete, the connectivity is the vertex count + * minus one. It is a good idea to perform these checks, as they can be + * done quickly compared to the connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(|V|^5). + * + * \sa \ref igraph_st_vertex_connectivity(), \ref igraph_maxflow_value(), + * and \ref igraph_edge_connectivity(). + */ + +igraph_error_t igraph_vertex_connectivity( + const igraph_t *graph, igraph_int_t *res, + igraph_bool_t checks) { + + igraph_bool_t ret = false; + + if (checks) { + IGRAPH_CHECK(igraph_i_connectivity_checks(graph, res, &ret)); + } + + if (checks && !ret) { + /* The vertex connectivity of a complete graph is vcount-1 by definition. + * This check cannot be performed within igraph_i_connectivity_checks(), + * as that function is used both for vertex and edge connectivities. + * Checking if the graph is complete does not suffice to determine the + * edge connectivity of a complete multigraph. */ + igraph_bool_t complete; + IGRAPH_CHECK(igraph_is_complete(graph, &complete)); + if (complete) { + *res = igraph_vcount(graph) - 1; + ret = true; + } + } + + /* Are we done yet? */ + if (!ret) { + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_vertex_connectivity_directed(graph, res, /* all_edges_are_mutual = */ false)); + } else { + IGRAPH_CHECK(igraph_i_vertex_connectivity_undirected(graph, res)); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_st_edge_connectivity + * \brief Edge connectivity of a pair of vertices. + * + * The edge connectivity of two vertices (\p source and \p target) is the + * minimum number of edges that have to be deleted from the graph to eliminate + * all paths from \p source to \p target. + * + * This function uses the maximum flow algorithm to calculate + * the edge connectivity. + * + * \param graph The input graph, it has to be directed. + * \param res Pointer to an integer, the result will be stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_maxflow_value(), \ref igraph_edge_disjoint_paths(), + * \ref igraph_edge_connectivity(), + * \ref igraph_st_vertex_connectivity(), \ref + * igraph_vertex_connectivity(). + */ + +igraph_error_t igraph_st_edge_connectivity(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target) { + + igraph_real_t flow; + + if (source == target) { + IGRAPH_ERROR("The source and target vertices must be different.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, source, target, NULL, NULL)); + *res = (igraph_int_t) flow; + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_edge_connectivity + * \brief The minimum edge connectivity in a graph. + * + * This is the minimum of the edge connectivity over all + * pairs of vertices in the graph. + * + * + * The edge connectivity of a graph is the same as group adhesion as + * defined in Douglas R. White and Frank Harary: The cohesiveness of + * blocks in social networks: node connectivity and conditional + * density, Sociological Methodology 31:305--359, 2001 + * https://doi.org/10.1111/0081-1750.00098. + * + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Boolean constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the connectivity is obviously zero. Otherwise + * if the minimum degree is one then the edge connectivity is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + * + * \sa \ref igraph_st_edge_connectivity(), \ref igraph_maxflow_value(), + * \ref igraph_vertex_connectivity(). + */ + +igraph_error_t igraph_edge_connectivity(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks) { + + igraph_bool_t ret = false; + igraph_int_t number_of_nodes = igraph_vcount(graph); + + /* igraph_mincut_value returns infinity for the singleton graph, + * which cannot be cast to an integer. We catch this case early + * and postulate the edge-connectivity of this graph to be 0. + * This is consistent with what other software packages return. */ + if (number_of_nodes <= 1) { + *res = 0; + return IGRAPH_SUCCESS; + } + + /* Use that vertex.connectivity(G) <= edge.connectivity(G) <= min(degree(G)) */ + if (checks) { + IGRAPH_CHECK(igraph_i_connectivity_checks(graph, res, &ret)); + } + + if (!ret) { + igraph_real_t real_res; + IGRAPH_CHECK(igraph_mincut_value(graph, &real_res, 0)); + *res = (igraph_int_t)real_res; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_edge_disjoint_paths + * \brief The maximum number of edge-disjoint paths between two vertices. + * + * A set of paths between two vertices is called edge-disjoint if they do not + * share any edges. The maximum number of edge-disjoint paths are calculated + * by this function using maximum flow techniques. Directed paths are + * considered in directed graphs. + * + * Note that the number of disjoint paths is the same as the + * edge connectivity of the two vertices using uniform edge weights. + * + * \param graph The input graph, can be directed or undirected. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3), but see the discussion at \ref + * igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_disjoint_paths(), \ref + * igraph_st_edge_connectivity(), \ref igraph_maxflow_value(). + */ + +igraph_error_t igraph_edge_disjoint_paths(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target) { + + igraph_real_t flow; + + if (source == target) { + IGRAPH_ERROR("Not implemented when the source and target are the same.", + IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, source, target, NULL, NULL)); + + *res = (igraph_int_t) flow; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vertex_disjoint_paths + * \brief Maximum number of vertex-disjoint paths between two vertices. + * + * A set of paths between two vertices is called vertex-disjoint if + * they share no vertices, other than the endpoints. This function computes + * the largest number of such paths that can be constructed between + * a source and a target vertex. The calculation is performed by using maximum + * flow techniques. + * + * + * When there are no edges from the source to the target, the number of + * vertex-disjoint paths is the same as the vertex connectivity of + * the two vertices. When some edges are present, each one of them + * contributes one extra path. + * + * \param graph The input graph. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_edge_disjoint_paths(), + * \ref igraph_st_vertex_connectivity(), \ref igraph_maxflow_value(). + */ + +igraph_error_t igraph_vertex_disjoint_paths(const igraph_t *graph, + igraph_int_t *res, + igraph_int_t source, + igraph_int_t target) { + + igraph_vector_int_t eids; + + if (source == target) { + IGRAPH_ERROR("Not implemented when the source and target are the same.", + IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 4); + IGRAPH_CHECK(igraph_get_all_eids_between(graph, &eids, source, target, /*directed*/ true)); + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(graph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } else { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_undirected(graph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } + + *res += igraph_vector_int_size(&eids); + + igraph_vector_int_destroy(&eids); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adhesion + * \brief Graph adhesion, this is (almost) the same as edge connectivity. + * + * This quantity is defined by White and Harary in + * The cohesiveness of blocks in social networks: node connectivity and + * conditional density, (Sociological Methodology 31:305--359, 2001) + * and basically it is the edge connectivity of the graph + * with uniform edge weights. + * + * \param graph The input graph, either directed or undirected. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Boolean constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the adhesion is obviously zero. Otherwise + * if the minimum degree is one then the adhesion is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the edge connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. +* \return Error code. + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + * + * \sa \ref igraph_cohesion(), \ref igraph_maxflow_value(), \ref + * igraph_edge_connectivity(), \ref igraph_mincut_value(). + */ + +igraph_error_t igraph_adhesion(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks) { + return igraph_edge_connectivity(graph, res, checks); +} + +/** + * \function igraph_cohesion + * \brief Graph cohesion, this is the same as vertex connectivity. + * + * This quantity was defined by White and Harary in The + * cohesiveness of blocks in social networks: node connectivity and + * conditional density, (Sociological Methodology 31:305--359, 2001) + * and it is the same as the vertex connectivity of a graph. + * + * \param graph The input graph. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param checks Boolean constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the cohesion is obviously zero. Otherwise + * if the minimum degree is one then the cohesion is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the vertex connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(|V|^4), |V| is the number of vertices. In + * practice it is more like O(|V|^2), see \ref igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_connectivity(), \ref igraph_adhesion(), + * \ref igraph_maxflow_value(). + */ + +igraph_error_t igraph_cohesion(const igraph_t *graph, + igraph_int_t *res, + igraph_bool_t checks) { + + IGRAPH_CHECK(igraph_vertex_connectivity(graph, res, checks)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_gomory_hu_tree + * \brief Gomory-Hu tree of a graph. + * + * + * The Gomory-Hu tree is a concise representation of the value of all the + * maximum flows (or minimum cuts) in a graph. The vertices of the tree + * correspond exactly to the vertices of the original graph in the same order. + * Edges of the Gomory-Hu tree are annotated by flow values. The value of + * the maximum flow (or minimum cut) between an arbitrary (u,v) vertex + * pair in the original graph is then given by the minimum flow value (i.e. + * edge annotation) along the shortest path between u and v in the + * Gomory-Hu tree. + * + * This implementation uses Gusfield's algorithm to construct the + * Gomory-Hu tree. See the following paper for more details: + * + * + * Reference: + * + * + * Gusfield D: Very simple methods for all pairs network flow analysis. SIAM J + * Comput 19(1):143-155, 1990 + * https://doi.org/10.1137/0219009. + * + * \param graph The input graph. + * \param tree Pointer to an uninitialized graph; the result will be + * stored here. + * \param flows Pointer to an uninitialized vector; the flow values + * corresponding to each edge in the Gomory-Hu tree will + * be returned here. You may pass a NULL pointer here if you are + * not interested in the flow values. + * \param capacity Vector containing the capacity of the edges. If NULL, then + * every edge is considered to have capacity 1.0. + * \return Error code. + * + * Time complexity: O(|V|^4) since it performs a max-flow calculation + * between vertex zero and every other vertex and max-flow is + * O(|V|^3). + * + * \sa \ref igraph_maxflow() + */ +igraph_error_t igraph_gomory_hu_tree(const igraph_t *graph, + igraph_t *tree, + igraph_vector_t *flows, + const igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t source, target, mid, i, n; + igraph_vector_int_t neighbors; + igraph_vector_t flow_values; + igraph_vector_int_t partition; + igraph_vector_int_t partition2; + igraph_real_t flow_value; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Gomory-Hu tree can only be calculated for undirected graphs.", + IGRAPH_EINVAL); + } + + /* Allocate memory */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbors, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&flow_values, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&partition, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&partition2, 0); + + /* Initialize the tree: every edge points to node 0 */ + /* Actually, this is done implicitly since both 'neighbors' and 'flow_values' are + * initialized to zero already */ + + /* For each source vertex except vertex zero... */ + for (source = 1; source < no_of_nodes; source++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_PROGRESS("Gomory-Hu tree", (100.0 * (source - 1)) / (no_of_nodes - 1), 0); + + /* Find its current neighbor in the tree */ + target = VECTOR(neighbors)[source]; + + /* Find the maximum flow between source and target */ + IGRAPH_CHECK(igraph_maxflow(graph, &flow_value, NULL, NULL, &partition, &partition2, + source, target, capacity, 0)); + + /* Store the maximum flow */ + VECTOR(flow_values)[source] = flow_value; + + /* Update the tree */ + /* igraph_maxflow() guarantees that the source vertex will be in &partition + * and not in &partition2 so we need to iterate over &partition to find + * all the nodes that are of interest to us */ + n = igraph_vector_int_size(&partition); + for (i = 0; i < n; i++) { + mid = VECTOR(partition)[i]; + if (mid != source) { + if (VECTOR(neighbors)[mid] == target) { + VECTOR(neighbors)[mid] = source; + } else if (VECTOR(neighbors)[target] == mid) { + VECTOR(neighbors)[target] = source; + VECTOR(neighbors)[source] = mid; + VECTOR(flow_values)[source] = VECTOR(flow_values)[target]; + VECTOR(flow_values)[target] = flow_value; + } + } + } + } + + IGRAPH_PROGRESS("Gomory-Hu tree", 100.0, 0); + + /* Re-use the 'partition' vector as an edge list now */ + IGRAPH_CHECK(igraph_vector_int_resize(&partition, no_of_nodes > 0 ? 2 * (no_of_nodes - 1) : 0)); + for (i = 1, mid = 0; i < no_of_nodes; i++, mid += 2) { + VECTOR(partition)[mid] = i; + VECTOR(partition)[mid + 1] = VECTOR(neighbors)[i]; + } + + /* Create the tree graph; we use igraph_subgraph_from_edges here to keep the + * graph and vertex attributes */ + IGRAPH_CHECK(igraph_subgraph_from_edges(graph, tree, igraph_ess_none(), 0)); + IGRAPH_CHECK(igraph_add_edges(tree, &partition, 0)); + + /* Free the allocated memory */ + igraph_vector_int_destroy(&partition2); + igraph_vector_int_destroy(&partition); + igraph_vector_int_destroy(&neighbors); + IGRAPH_FINALLY_CLEAN(3); + + /* Return the flow values to the caller */ + if (flows != 0) { + IGRAPH_CHECK(igraph_vector_update(flows, &flow_values)); + if (no_of_nodes > 0) { + igraph_vector_remove(flows, 0); + } + } + + /* Free the remaining allocated memory */ + igraph_vector_destroy(&flow_values); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/flow/flow_conversion.c b/src/flow/flow_conversion.c new file mode 100644 index 0000000..1cbebaf --- /dev/null +++ b/src/flow/flow_conversion.c @@ -0,0 +1,92 @@ +/* + igraph library. + Copyright (C) 2010-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" + +#include "flow/flow_internal.h" + +/** + * \function igraph_i_split_vertices + * \brief Splits each vertex in the graph into an input and an output vertex. + * + * This function implements a transformation that allows us to calculate the + * vertex connectivity of a directed graph either for a specific s-t pair or + * for all s-t pairs using flows. The transformation splits each vertex into + * an input vertex and an output vertex. All inbound edges of the original + * vertex are rewired to point to the input vertex, and all outbound edges of + * the original vertex are rewired to original from the output vertex, while + * adding a single directed edge from the input vertex to the output vertex. + * + * + * s-t vertex connectivities can then be calculated on this modified graph by + * setting the capacity of each edge to 1, \em except for the following edges: + * the edges incident of the input half of the source vertex and the edges + * incident on the output half of the target vertex. The max flow on this + * modified graph will be equal to the s-t vertex connectivity of the original + * graph. + * + * + * This function prepares the graph only but does not supply a capacity vector; + * it is the responsibility of the caller to provide the capacities. + * + * + * If the original graph had \em n vertices, he function guarantees that the + * first \em n vertices of the result graph will correspond to the \em output + * halves of the vertices and the remaining \em n vertices will correspond to + * the \em input halves, in the same order as in the original graph. + * + * \param graph the input graph + * \param result an uninitialized graph object; the result will be returned here + */ +igraph_error_t igraph_i_split_vertices(const igraph_t* graph, igraph_t* result) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t i; + igraph_vector_int_t edges; + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Input graph must be directed.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 2 * (no_of_edges + no_of_nodes))); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_int_resize(&edges, 2 * (no_of_edges + no_of_nodes))); + + for (i = 0; i < 2 * no_of_edges; i += 2) { + igraph_int_t to = VECTOR(edges)[i + 1]; + VECTOR(edges)[i + 1] = no_of_nodes + to; + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[2 * (no_of_edges + i)] = no_of_nodes + i; + VECTOR(edges)[2 * (no_of_edges + i) + 1] = i; + } + + IGRAPH_CHECK(igraph_create(result, &edges, 2 * no_of_nodes, IGRAPH_DIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/flow/flow_internal.h b/src/flow/flow_internal.h new file mode 100644 index 0000000..6308baf --- /dev/null +++ b/src/flow/flow_internal.h @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_FLOW_INTERNAL_H +#define IGRAPH_FLOW_INTERNAL_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_types.h" + +#include "core/estack.h" +#include "core/marked_queue.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_all_st_cuts_pivot( + const igraph_t *graph, const igraph_marked_queue_int_t *S, + const igraph_estack_t *T, igraph_int_t source, igraph_int_t target, + igraph_int_t *v, igraph_vector_int_t *Isv, void *arg); + +igraph_error_t igraph_i_split_vertices(const igraph_t* graph, igraph_t* result); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/flow/st-cuts.c b/src/flow/st-cuts.c new file mode 100644 index 0000000..6af5665 --- /dev/null +++ b/src/flow/st-cuts.c @@ -0,0 +1,1575 @@ +/* + igraph library. + Copyright (C) 2010-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_flow.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_constants.h" +#include "igraph_constructors.h" +#include "igraph_components.h" +#include "igraph_cycles.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_operators.h" +#include "igraph_stack.h" +#include "igraph_visitor.h" + +#include "core/estack.h" +#include "core/marked_queue.h" +#include "flow/flow_internal.h" +#include "graph/attributes.h" +#include "math/safe_intop.h" + +typedef igraph_error_t igraph_provan_shier_pivot_t(const igraph_t *graph, + const igraph_marked_queue_int_t *S, + const igraph_estack_t *T, + igraph_int_t source, + igraph_int_t target, + igraph_int_t *v, + igraph_vector_int_t *Isv, + void *arg); + +/** + * \function igraph_even_tarjan_reduction + * \brief Even-Tarjan reduction of a graph. + * + * A digraph is created with twice as many vertices and edges. For each + * original vertex \c i, two vertices i' = i and + * i'' = i' + n are created, + * with a directed edge from i' to i''. + * For each original directed edge from \c i to \c j, two new edges are created, + * from i' to j'' and from i'' + * to j'. + * + * This reduction is used in the paper (observation 2): + * Arkady Kanevsky: Finding all minimum-size separating vertex sets in + * a graph, Networks 23, 533--541, 1993. + * + * The original paper where this reduction was conceived is + * Shimon Even and R. Endre Tarjan: Network Flow and Testing Graph + * Connectivity, SIAM J. Comput., 4(4), 507–518. + * https://doi.org/10.1137/0204043 + * + * \param graph A graph. Although directness is not checked, this function + * is commonly used only on directed graphs. + * \param graphbar Pointer to a new directed graph that will contain the + * reduction, with twice as many vertices and edges. + * \param capacity Pointer to an initialized vector or a null pointer. If + * not a null pointer, then it will be filled the capacity from + * the reduction: the first |E| elements are 1, the remaining |E| + * are equal to |V| (which is used to indicate infinity). + * \return Error code. + * + * Time complexity: O(|E|+|V|). + * + * \example examples/simple/even_tarjan.c + */ + +igraph_error_t igraph_even_tarjan_reduction(const igraph_t *graph, igraph_t *graphbar, + igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + igraph_int_t new_no_of_nodes; + igraph_int_t new_no_of_edges = no_of_edges * 2; + + igraph_vector_int_t edges; + igraph_int_t edgeptr = 0, capptr = 0; + igraph_int_t i; + + IGRAPH_SAFE_MULT(no_of_nodes, 2, &new_no_of_nodes); + IGRAPH_SAFE_ADD(new_no_of_edges, no_of_nodes, &new_no_of_edges); + + /* To ensure the size of the edges vector will not overflow. */ + if (new_no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, new_no_of_edges * 2); + + if (capacity) { + IGRAPH_CHECK(igraph_vector_resize(capacity, new_no_of_edges)); + } + + /* Every vertex 'i' is replaced by two vertices, i' and i'' */ + /* id[i'] := id[i] ; id[i''] := id[i] + no_of_nodes */ + + /* One edge for each original vertex, for i, we add (i',i'') */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = i + no_of_nodes; + if (capacity) { + VECTOR(*capacity)[capptr++] = 1.0; + } + } + + /* Two news edges for each original edge + (from,to) becomes (from'',to'), (to'',from') */ + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + VECTOR(edges)[edgeptr++] = from + no_of_nodes; + VECTOR(edges)[edgeptr++] = to; + VECTOR(edges)[edgeptr++] = to + no_of_nodes; + VECTOR(edges)[edgeptr++] = from; + if (capacity) { + VECTOR(*capacity)[capptr++] = no_of_nodes; /* TODO: should be Inf */ + VECTOR(*capacity)[capptr++] = no_of_nodes; /* TODO: should be Inf */ + } + } + + IGRAPH_CHECK(igraph_create(graphbar, &edges, new_no_of_nodes, + IGRAPH_DIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow, + igraph_vector_int_t *tmp) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t i, no_new_edges = 0; + igraph_int_t edgeptr = 0, capptr = 0; + + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(*flow)[i] < VECTOR(*capacity)[i]) { + no_new_edges++; + } + } + + IGRAPH_CHECK(igraph_vector_int_resize(tmp, no_new_edges * 2)); + if (residual_capacity) { + IGRAPH_CHECK(igraph_vector_resize(residual_capacity, no_new_edges)); + } + + for (i = 0; i < no_of_edges; i++) { + igraph_real_t c = VECTOR(*capacity)[i] - VECTOR(*flow)[i]; + if (c > 0) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + VECTOR(*tmp)[edgeptr++] = from; + VECTOR(*tmp)[edgeptr++] = to; + if (residual_capacity) { + VECTOR(*residual_capacity)[capptr++] = c; + } + } + } + + IGRAPH_CHECK(igraph_create(residual, tmp, no_of_nodes, + IGRAPH_DIRECTED)); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow) { + + igraph_vector_int_t tmp; + igraph_int_t no_of_edges = igraph_ecount(graph); + + if (igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Invalid `capacity' vector size", IGRAPH_EINVAL); + } + if (igraph_vector_size(flow) != no_of_edges) { + IGRAPH_ERROR("Invalid `flow' vector size", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + + IGRAPH_CHECK(igraph_i_residual_graph(graph, capacity, residual, + residual_capacity, flow, &tmp)); + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow, + igraph_vector_int_t *tmp) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t i, no_new_edges = 0; + igraph_int_t edgeptr = 0; + + for (i = 0; i < no_of_edges; i++) { + igraph_real_t cap = capacity ? VECTOR(*capacity)[i] : 1.0; + if (VECTOR(*flow)[i] > 0) { + no_new_edges++; + } + if (VECTOR(*flow)[i] < cap) { + no_new_edges++; + } + } + + IGRAPH_CHECK(igraph_vector_int_resize(tmp, no_new_edges * 2)); + + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_real_t cap = capacity ? VECTOR(*capacity)[i] : 1.0; + if (VECTOR(*flow)[i] > 0) { + VECTOR(*tmp)[edgeptr++] = from; + VECTOR(*tmp)[edgeptr++] = to; + } + if (VECTOR(*flow)[i] < cap) { + VECTOR(*tmp)[edgeptr++] = to; + VECTOR(*tmp)[edgeptr++] = from; + } + } + + IGRAPH_CHECK(igraph_create(residual, tmp, no_of_nodes, + IGRAPH_DIRECTED)); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow) { + igraph_vector_int_t tmp; + igraph_int_t no_of_edges = igraph_ecount(graph); + + if (capacity && igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Invalid `capacity' vector size", IGRAPH_EINVAL); + } + if (igraph_vector_size(flow) != no_of_edges) { + IGRAPH_ERROR("Invalid `flow' vector size", IGRAPH_EINVAL); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + + IGRAPH_CHECK(igraph_i_reverse_residual_graph(graph, capacity, residual, + flow, &tmp)); + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_dbucket_t { + igraph_vector_int_t head; + igraph_vector_int_t next; +} igraph_i_dbucket_t; + +static igraph_error_t igraph_i_dbucket_init(igraph_i_dbucket_t *buck, igraph_int_t size) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&buck->head, size); + IGRAPH_CHECK(igraph_vector_int_init(&buck->next, size)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +static void igraph_i_dbucket_destroy(igraph_i_dbucket_t *buck) { + igraph_vector_int_destroy(&buck->head); + igraph_vector_int_destroy(&buck->next); +} + +static igraph_error_t igraph_i_dbucket_insert(igraph_i_dbucket_t *buck, igraph_int_t bid, + igraph_int_t elem) { + /* Note: we can do this, since elem is not in any buckets */ + VECTOR(buck->next)[elem] = VECTOR(buck->head)[bid]; + VECTOR(buck->head)[bid] = elem + 1; + return IGRAPH_SUCCESS; +} + +static igraph_bool_t igraph_i_dbucket_empty(const igraph_i_dbucket_t *buck, + igraph_int_t bid) { + return VECTOR(buck->head)[bid] == 0; +} + +static igraph_int_t igraph_i_dbucket_delete(igraph_i_dbucket_t *buck, igraph_int_t bid) { + igraph_int_t elem = VECTOR(buck->head)[bid] - 1; + VECTOR(buck->head)[bid] = VECTOR(buck->next)[elem]; + return elem; +} + +static igraph_error_t igraph_i_dominator_LINK(igraph_int_t v, igraph_int_t w, + igraph_vector_int_t *ancestor) { + VECTOR(*ancestor)[w] = v + 1; + return IGRAPH_SUCCESS; +} + +/* TODO: don't always reallocate path */ + +static igraph_error_t igraph_i_dominator_COMPRESS(igraph_int_t v, + igraph_vector_int_t *ancestor, + igraph_vector_int_t *label, + igraph_vector_int_t *semi) { + igraph_stack_int_t path; + igraph_int_t w = v; + igraph_int_t top, pretop; + + IGRAPH_CHECK(igraph_stack_int_init(&path, 10)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &path); + + while (VECTOR(*ancestor)[w] != 0) { + IGRAPH_CHECK(igraph_stack_int_push(&path, w)); + w = VECTOR(*ancestor)[w] - 1; + } + + top = igraph_stack_int_pop(&path); + while (!igraph_stack_int_empty(&path)) { + pretop = igraph_stack_int_pop(&path); + + if (VECTOR(*semi)[VECTOR(*label)[top]] < + VECTOR(*semi)[VECTOR(*label)[pretop]]) { + VECTOR(*label)[pretop] = VECTOR(*label)[top]; + } + VECTOR(*ancestor)[pretop] = VECTOR(*ancestor)[top]; + + top = pretop; + } + + igraph_stack_int_destroy(&path); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_int_t igraph_i_dominator_EVAL(igraph_int_t v, + igraph_vector_int_t *ancestor, + igraph_vector_int_t *label, + igraph_vector_int_t *semi) { + if (VECTOR(*ancestor)[v] == 0) { + return v; + } else { + igraph_i_dominator_COMPRESS(v, ancestor, label, semi); + return VECTOR(*label)[v]; + } +} + +/* TODO: implement the faster version. */ + +/** + * \function igraph_dominator_tree + * \brief Calculates the dominator tree of a flowgraph. + * + * A flowgraph is a directed graph with a distinguished start (or + * root) vertex r, such that for any vertex v, there is a path from r + * to v. A vertex v dominates another vertex w (not equal to v), if + * every path from r to w contains v. Vertex v is the immediate + * dominator or w, v=idom(w), if v dominates w and every other + * dominator of w dominates v. The edges {(idom(w), w)| w is not r} + * form a directed tree, rooted at r, called the dominator tree of the + * graph. Vertex v dominates vertex w if and only if v is an ancestor + * of w in the dominator tree. + * + * This function implements the Lengauer-Tarjan algorithm + * to construct the dominator tree of a directed graph. For details + * please see Thomas Lengauer, Robert Endre Tarjan: A fast algorithm + * for finding dominators in a flowgraph, ACM Transactions on + * Programming Languages and Systems (TOPLAS) I/1, 121--141, 1979. + * https://doi.org/10.1145/357062.357071 + * + * \param graph A directed graph. If it is not a flowgraph, and it + * contains some vertices not reachable from the root vertex, + * then these vertices will be collected in the \p leftout + * vector. + * \param root The ID of the root (or source) vertex, this will be the + * root of the tree. + * \param dom Pointer to an initialized vector or a null pointer. If + * not a null pointer, then the immediate dominator of each + * vertex will be stored here. For vertices that are not + * reachable from the root, -2 is stored here. For + * the root vertex itself, -1 is added. + * \param domtree Pointer to an \em uninitialized igraph_t, + * or \c NULL. If not a null pointer, then the dominator tree + * is returned here. The graph contains the vertices that are unreachable + * from the root (if any), these will be isolates. + * Graph and vertex attributes are preserved, but edge attributes + * are discarded. + * \param leftout Pointer to an initialized vector object, or \c NULL. If + * not \c NULL, then the IDs of the vertices that are unreachable + * from the root vertex (and thus not part of the dominator + * tree) are stored here. + * \param mode Constant, must be \c IGRAPH_IN or \c IGRAPH_OUT. If it + * is \c IGRAPH_IN, then all directions are considered as + * opposite to the original one in the input graph. + * \return Error code. + * + * Time complexity: very close to O(|E|+|V|), linear in the number of + * edges and vertices. More precisely, it is O(|V|+|E|alpha(|E|,|V|)), + * where alpha(|E|,|V|) is a functional inverse of Ackermann's + * function. + * + * \example examples/simple/dominator_tree.c + */ + +igraph_error_t igraph_dominator_tree(const igraph_t *graph, + igraph_int_t root, + igraph_vector_int_t *dom, + igraph_t *domtree, + igraph_vector_int_t *leftout, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + + igraph_adjlist_t succ, pred; + igraph_vector_int_t parent; + igraph_vector_int_t semi; /* +1 always */ + igraph_vector_int_t vertex; /* +1 always */ + igraph_i_dbucket_t bucket; + igraph_vector_int_t ancestor; + igraph_vector_int_t label; + + igraph_neimode_t invmode = IGRAPH_REVERSE_MODE(mode); + + igraph_vector_int_t vdom, *mydom = dom; + + igraph_int_t component_size = 0; + + if (root < 0 || root >= no_of_nodes) { + IGRAPH_ERROR("Invalid root vertex ID for dominator tree.", + IGRAPH_EINVVID); + } + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Dominator tree of an undirected graph requested.", + IGRAPH_EINVAL); + } + + if (mode == IGRAPH_ALL) { + IGRAPH_ERROR("Invalid neighbor mode for dominator tree.", + IGRAPH_EINVAL); + } + + if (dom) { + IGRAPH_CHECK(igraph_vector_int_resize(dom, no_of_nodes)); + } else { + mydom = &vdom; + IGRAPH_VECTOR_INT_INIT_FINALLY(mydom, no_of_nodes); + } + igraph_vector_int_fill(mydom, -2); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&parent, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&semi, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ancestor, no_of_nodes); + IGRAPH_CHECK(igraph_vector_int_init_range(&label, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &label); + IGRAPH_CHECK(igraph_adjlist_init(graph, &succ, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &succ); + IGRAPH_CHECK(igraph_adjlist_init(graph, &pred, invmode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &pred); + IGRAPH_CHECK(igraph_i_dbucket_init(&bucket, no_of_nodes)); + IGRAPH_FINALLY(igraph_i_dbucket_destroy, &bucket); + + /* DFS first, to set semi, vertex and parent, step 1 */ + + IGRAPH_CHECK(igraph_dfs(graph, root, mode, /*unreachable=*/ false, + /*order=*/ &vertex, + /*order_out=*/ NULL, /*parents=*/ &parent, + /*dist=*/ NULL, /*in_callback=*/ NULL, + /*out_callback=*/ NULL, /*extra=*/ NULL)); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(vertex)[i] >= 0) { + igraph_int_t t = VECTOR(vertex)[i]; + VECTOR(semi)[t] = component_size + 1; + VECTOR(vertex)[component_size] = t + 1; + component_size++; + } + } + if (leftout) { + igraph_int_t n = no_of_nodes - component_size; + igraph_int_t p = 0, j; + IGRAPH_CHECK(igraph_vector_int_resize(leftout, n)); + for (j = 0; j < no_of_nodes && p < n; j++) { + if (VECTOR(parent)[j] < -1) { + VECTOR(*leftout)[p++] = j; + } + } + } + + /* We need to go over 'pred' because it should contain only the + edges towards the target vertex. */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *v = igraph_adjlist_get(&pred, i); + igraph_int_t n = igraph_vector_int_size(v); + for (igraph_int_t j = 0; j < n; ) { + igraph_int_t v2 = VECTOR(*v)[j]; + if (VECTOR(parent)[v2] >= -1) { + j++; + } else { + VECTOR(*v)[j] = VECTOR(*v)[n - 1]; + igraph_vector_int_pop_back(v); + n--; + } + } + } + + /* Now comes the main algorithm, steps 2 & 3 */ + + for (igraph_int_t i = component_size - 1; i > 0; i--) { + igraph_int_t w = VECTOR(vertex)[i] - 1; + igraph_vector_int_t *predw = igraph_adjlist_get(&pred, w); + igraph_int_t j, n = igraph_vector_int_size(predw); + for (j = 0; j < n; j++) { + igraph_int_t v = VECTOR(*predw)[j]; + igraph_int_t u = igraph_i_dominator_EVAL(v, &ancestor, &label, &semi); + if (VECTOR(semi)[u] < VECTOR(semi)[w]) { + VECTOR(semi)[w] = VECTOR(semi)[u]; + } + } + igraph_i_dbucket_insert(&bucket, VECTOR(vertex)[ VECTOR(semi)[w] - 1 ] - 1, w); + igraph_i_dominator_LINK(VECTOR(parent)[w], w, &ancestor); + while (!igraph_i_dbucket_empty(&bucket, VECTOR(parent)[w])) { + igraph_int_t v = igraph_i_dbucket_delete(&bucket, VECTOR(parent)[w]); + igraph_int_t u = igraph_i_dominator_EVAL(v, &ancestor, &label, &semi); + VECTOR(*mydom)[v] = VECTOR(semi)[u] < VECTOR(semi)[v] ? u : + VECTOR(parent)[w]; + } + } + + /* Finally, step 4 */ + + for (igraph_int_t i = 1; i < component_size; i++) { + igraph_int_t w = VECTOR(vertex)[i] - 1; + if (VECTOR(*mydom)[w] != VECTOR(vertex)[VECTOR(semi)[w] - 1] - 1) { + VECTOR(*mydom)[w] = VECTOR(*mydom)[VECTOR(*mydom)[w]]; + } + } + VECTOR(*mydom)[root] = -1; + + igraph_i_dbucket_destroy(&bucket); + igraph_adjlist_destroy(&pred); + igraph_adjlist_destroy(&succ); + igraph_vector_int_destroy(&label); + igraph_vector_int_destroy(&ancestor); + igraph_vector_int_destroy(&vertex); + igraph_vector_int_destroy(&semi); + igraph_vector_int_destroy(&parent); + IGRAPH_FINALLY_CLEAN(8); + + if (domtree) { + igraph_vector_int_t edges; + igraph_int_t ptr = 0; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, component_size * 2 - 2); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (i != root && VECTOR(*mydom)[i] >= 0) { + if (mode == IGRAPH_OUT) { + VECTOR(edges)[ptr++] = VECTOR(*mydom)[i]; + VECTOR(edges)[ptr++] = i; + } else { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = VECTOR(*mydom)[i]; + } + } + } + IGRAPH_CHECK(igraph_create(domtree, &edges, no_of_nodes, + IGRAPH_DIRECTED)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_i_attribute_copy(domtree, graph, true, true, /*edges=*/ false)); + } + + if (!dom) { + igraph_vector_int_destroy(&vdom); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_all_st_cuts_minimal_dfs_data_t { + igraph_stack_int_t *stack; + igraph_bitset_t *nomark; + const igraph_bitset_t *GammaX; + igraph_int_t root; + const igraph_vector_int_t *map; +} igraph_i_all_st_cuts_minimal_dfs_data_t; + +static igraph_error_t igraph_i_all_st_cuts_minimal_dfs_incb( + const igraph_t *graph, + igraph_int_t vid, + igraph_int_t dist, + void *extra) { + + igraph_i_all_st_cuts_minimal_dfs_data_t *data = extra; + igraph_stack_int_t *stack = data->stack; + igraph_bitset_t *nomark = data->nomark; + const igraph_bitset_t *GammaX = data->GammaX; + const igraph_vector_int_t *map = data->map; + igraph_int_t realvid = VECTOR(*map)[vid]; + + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(dist); + + if (IGRAPH_BIT_TEST(*GammaX, realvid)) { + if (!igraph_stack_int_empty(stack)) { + igraph_int_t top = igraph_stack_int_top(stack); + IGRAPH_BIT_SET(*nomark, top); /* we just found a smaller one */ + } + IGRAPH_CHECK(igraph_stack_int_push(stack, realvid)); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_all_st_cuts_minimal_dfs_outcb( + const igraph_t *graph, + igraph_int_t vid, + igraph_int_t dist, + void *extra) { + igraph_i_all_st_cuts_minimal_dfs_data_t *data = extra; + igraph_stack_int_t *stack = data->stack; + const igraph_vector_int_t *map = data->map; + igraph_int_t realvid = VECTOR(*map)[vid]; + + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(dist); + + if (!igraph_stack_int_empty(stack) && + igraph_stack_int_top(stack) == realvid) { + igraph_stack_int_pop(stack); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_all_st_cuts_minimal(const igraph_t *graph, + const igraph_t *domtree, + igraph_int_t root, + const igraph_marked_queue_int_t *X, + const igraph_bitset_t *GammaX, + const igraph_vector_int_t *invmap, + igraph_vector_int_t *minimal) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_stack_int_t stack; + igraph_bitset_t nomark; + igraph_i_all_st_cuts_minimal_dfs_data_t data; + igraph_int_t i; + + IGRAPH_UNUSED(X); + + IGRAPH_STACK_INT_INIT_FINALLY(&stack, 10); + IGRAPH_BITSET_INIT_FINALLY(&nomark, no_of_nodes); + + data.stack = &stack; + data.nomark = &nomark; + data.GammaX = GammaX; + data.root = root; + data.map = invmap; + + /* We mark all GammaX elements as minimal first. + TODO: actually, we could just use GammaX to return the minimal + elements. */ + igraph_bitset_not(&nomark, GammaX); + + /* We do a reverse DFS from root. If, along a path we find a GammaX + vertex after (=below) another GammaX vertex, we mark the higher + one as non-minimal. */ + + IGRAPH_CHECK(igraph_dfs(domtree, root, IGRAPH_IN, + /*unreachable=*/ false, /*order=*/ NULL, + /*order_out=*/ NULL, /*parents=*/ NULL, + /*dist=*/ NULL, + /*in_callback=*/ igraph_i_all_st_cuts_minimal_dfs_incb, + /*out_callback=*/ igraph_i_all_st_cuts_minimal_dfs_outcb, + /*extra=*/ &data)); + + igraph_vector_int_clear(minimal); + for (i = 0; i < no_of_nodes; i++) { + if (!IGRAPH_BIT_TEST(nomark, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(minimal, i)); + } + } + + igraph_bitset_destroy(&nomark); + igraph_stack_int_destroy(&stack); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/* not 'static' because used in igraph_all_st_cuts.c test program */ +igraph_error_t igraph_i_all_st_cuts_pivot( + const igraph_t *graph, const igraph_marked_queue_int_t *S, + const igraph_estack_t *T, igraph_int_t source, igraph_int_t target, + igraph_int_t *v, igraph_vector_int_t *Isv, void *arg +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_t Sbar; + igraph_vector_int_t Sbar_map, Sbar_invmap; + igraph_vector_int_t keep; + igraph_t domtree; + igraph_vector_int_t leftout; + igraph_int_t i, nomin, n; + igraph_int_t root; + igraph_vector_int_t M; + igraph_bitset_t GammaS; + igraph_vector_int_t Nuv; + igraph_vector_int_t Isv_min; + igraph_vector_int_t GammaS_vec; + igraph_int_t Sbar_size; + + IGRAPH_UNUSED(arg); + + /* We need to create the graph induced by Sbar */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&Sbar_map, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&Sbar_invmap, 0); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&keep, 0); + for (i = 0; i < no_of_nodes; i++) { + if (!igraph_marked_queue_int_iselement(S, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&keep, i)); + } + } + Sbar_size = igraph_vector_int_size(&keep); + + IGRAPH_CHECK(igraph_induced_subgraph_map(graph, &Sbar, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_AUTO, + /* map= */ &Sbar_map, + /* invmap= */ &Sbar_invmap)); + igraph_vector_int_destroy(&keep); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, &Sbar); + + root = VECTOR(Sbar_map)[target]; + + /* -------------------------------------------------------------*/ + /* Construct the dominator tree of Sbar */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&leftout, 0); + IGRAPH_CHECK(igraph_dominator_tree(&Sbar, root, + /*dom=*/ 0, &domtree, + &leftout, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_destroy, &domtree); + + /* -------------------------------------------------------------*/ + /* Identify the set M of minimal elements of Gamma(S) with respect + to the dominator relation. */ + + /* First we create GammaS */ + /* TODO: use the adjacency list, instead of neighbors() */ + IGRAPH_BITSET_INIT_FINALLY(&GammaS, no_of_nodes); + if (igraph_marked_queue_int_size(S) == 0) { + IGRAPH_BIT_SET(GammaS, VECTOR(Sbar_map)[source]); + } else { + for (i = 0; i < no_of_nodes; i++) { + if (igraph_marked_queue_int_iselement(S, i)) { + igraph_vector_int_t neis; + igraph_int_t j; + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, i, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + if (!igraph_marked_queue_int_iselement(S, nei)) { + IGRAPH_BIT_SET(GammaS, nei); + } + } + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + } + } + + /* Relabel left out vertices (set K in Provan & Shier) to + correspond to node labelling of graph instead of SBar. + At the same time ensure that GammaS is a proper subset of + L, where L are the nodes in the dominator tree. */ + n = igraph_vector_int_size(&leftout); + for (i = 0; i < n; i++) { + VECTOR(leftout)[i] = VECTOR(Sbar_invmap)[VECTOR(leftout)[i]]; + IGRAPH_BIT_CLEAR(GammaS, VECTOR(leftout)[i]); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&M, 0); + if (igraph_ecount(&domtree) > 0) { + IGRAPH_CHECK(igraph_i_all_st_cuts_minimal(graph, &domtree, root, S, + &GammaS, &Sbar_invmap, &M)); + } + + igraph_vector_int_clear(Isv); + IGRAPH_VECTOR_INT_INIT_FINALLY(&Nuv, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&Isv_min, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&GammaS_vec, 0); + for (i = 0; i < no_of_nodes; i++) { + if (IGRAPH_BIT_TEST(GammaS, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&GammaS_vec, i)); + } + } + + nomin = igraph_vector_int_size(&M); + for (i = 0; i < nomin; i++) { + /* -------------------------------------------------------------*/ + /* For each v in M find the set Nu(v)=dom(Sbar, v)-K + Nu(v) contains all vertices that are dominated by v, for every + v, this is a subtree of the dominator tree, rooted at v. The + different subtrees are disjoint. */ + igraph_int_t min = VECTOR(Sbar_map)[ VECTOR(M)[i] ]; + igraph_int_t nuvsize, isvlen, j; + IGRAPH_CHECK(igraph_dfs(&domtree, min, IGRAPH_IN, + /*unreachable=*/ false, /*order=*/ &Nuv, + /*order_out=*/ NULL, /*parents=*/ NULL, /*dist=*/ NULL, + /*in_callback=*/ NULL, /*out_callback=*/ NULL, + /*extra=*/ NULL)); + /* Remove the negative values from the end of the vector */ + for (nuvsize = 0; nuvsize < Sbar_size; nuvsize++) { + igraph_int_t t = VECTOR(Nuv)[nuvsize]; + if (t >= 0) { + VECTOR(Nuv)[nuvsize] = VECTOR(Sbar_invmap)[t]; + } else { + break; + } + } + igraph_vector_int_resize(&Nuv, nuvsize); /* shrinks, error safe */ + + /* -------------------------------------------------------------*/ + /* By a BFS search of determine I(S,v)-K. + I(S,v) contains all vertices that are in Nu(v) and that are + reachable from Gamma(S) via a path in Nu(v). */ + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ -1, /*roots=*/ &GammaS_vec, + /*mode=*/ IGRAPH_OUT, /*unreachable=*/ false, + /*restricted=*/ &Nuv, + /*order=*/ &Isv_min, /*rank=*/ NULL, + /*parents=*/ NULL, /*pred=*/ NULL, /*succ=*/ NULL, + /*dist=*/ NULL, /*callback=*/ NULL, /*extra=*/ NULL)); + for (isvlen = 0; isvlen < no_of_nodes; isvlen++) { + if (VECTOR(Isv_min)[isvlen] < 0) { + break; + } + } + igraph_vector_int_resize(&Isv_min, isvlen); + + /* -------------------------------------------------------------*/ + /* For each c in M check whether Isv-K is included in Tbar. If + such a v is found, compute Isv={x|v[Nu(v) U K]x} and return v and + Isv; otherwise return Isv={}. */ + for (j = 0; j < isvlen; j++) { + igraph_int_t u = VECTOR(Isv_min)[j]; + if (igraph_estack_iselement(T, u) || u == target) { + break; + } + } + /* We might have found one */ + if (j == isvlen) { + *v = VECTOR(M)[i]; + /* Calculate real Isv */ + IGRAPH_CHECK(igraph_vector_int_append(&Nuv, &leftout)); + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ *v, + /*roots=*/ NULL, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ false, /*restricted=*/ &Nuv, + /*order=*/ &Isv_min, /*rank=*/ NULL, + /*parents=*/ NULL, /*pred=*/ NULL, /*succ=*/ NULL, + /*dist=*/ NULL, /*callback=*/ NULL, /*extra=*/ NULL)); + for (isvlen = 0; isvlen < no_of_nodes; isvlen++) { + if (VECTOR(Isv_min)[isvlen] < 0) { + break; + } + } + igraph_vector_int_resize(&Isv_min, isvlen); + igraph_vector_int_update(Isv, &Isv_min); + + break; + } + } + + igraph_vector_int_destroy(&GammaS_vec); + igraph_vector_int_destroy(&Isv_min); + igraph_vector_int_destroy(&Nuv); + IGRAPH_FINALLY_CLEAN(3); + + igraph_vector_int_destroy(&M); + igraph_bitset_destroy(&GammaS); + igraph_destroy(&domtree); + igraph_vector_int_destroy(&leftout); + igraph_destroy(&Sbar); + igraph_vector_int_destroy(&Sbar_map); + igraph_vector_int_destroy(&Sbar_invmap); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/* TODO: This is a temporary recursive version */ + +static igraph_error_t igraph_i_provan_shier_list_recursive( + const igraph_t *graph, igraph_marked_queue_int_t *S, + igraph_estack_t *T, igraph_int_t source, igraph_int_t target, + igraph_vector_int_list_t *result, igraph_provan_shier_pivot_t *pivot, + igraph_vector_int_t *Isv, void *pivot_arg +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t v = 0; + igraph_int_t i, n; + + pivot(graph, S, T, source, target, &v, Isv, pivot_arg); + + if (igraph_vector_int_empty(Isv)) { + if (igraph_marked_queue_int_size(S) != 0 && igraph_marked_queue_int_size(S) != no_of_nodes) { + igraph_vector_int_t *vec; + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(result, &vec)); + IGRAPH_CHECK(igraph_marked_queue_int_as_vector(S, vec)); + } + } else { + /* Add Isv to S */ + IGRAPH_CHECK(igraph_marked_queue_int_start_batch(S)); + n = igraph_vector_int_size(Isv); + for (i = 0; i < n; i++) { + if (!igraph_marked_queue_int_iselement(S, VECTOR(*Isv)[i])) { + IGRAPH_CHECK(igraph_marked_queue_int_push(S, VECTOR(*Isv)[i])); + } + } + igraph_vector_int_clear(Isv); + + /* Go down right in the search tree */ + IGRAPH_CHECK(igraph_i_provan_shier_list_recursive( + graph, S, T, source, target, result, pivot, Isv, pivot_arg)); + + /* Take out Isv from S */ + igraph_marked_queue_int_pop_back_batch(S); + + /* Put v into T */ + IGRAPH_CHECK(igraph_estack_push(T, v)); + + /* Go down left in the search tree */ + IGRAPH_CHECK(igraph_i_provan_shier_list_recursive( + graph, S, T, source, target, result, pivot, Isv, pivot_arg)); + + /* Take out v from T */ + igraph_estack_pop(T); + + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_provan_shier_list( + const igraph_t *graph, igraph_marked_queue_int_t *S, + igraph_estack_t *T, igraph_int_t source, igraph_int_t target, + igraph_vector_int_list_t *result, igraph_provan_shier_pivot_t *pivot, + void *pivot_arg +) { + igraph_vector_int_t Isv; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&Isv, 0); + + IGRAPH_CHECK(igraph_i_provan_shier_list_recursive( + graph, S, T, source, target, result, pivot, &Isv, pivot_arg + )); + + /* Reverse the result to stay compatible with versions before 0.10.3 */ + IGRAPH_CHECK(igraph_vector_int_list_reverse(result)); + + igraph_vector_int_destroy(&Isv); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_all_st_cuts + * List all edge-cuts between two vertices in a directed graph + * + * This function lists all edge-cuts between a source and a target + * vertex. Every cut is listed exactly once. The implemented algorithm + * is described in JS Provan and DR Shier: A Paradigm for listing + * (s,t)-cuts in graphs, Algorithmica 15, 351--372, 1996. + * + * \param graph The input graph, is must be directed. + * \param cuts An initialized list of integer vectors, the cuts are stored + * here. Each vector will contain the IDs of the edges in + * the cut. This argument is ignored if it is a null pointer. + * \param partition1s An initialized list of integer vectors, the list of + * vertex sets generating the actual edge cuts are stored + * here. Each vector contains a set of vertex IDs. If X is such + * a set, then all edges going from X to the complement of X + * form an (s, t) edge-cut in the graph. This argument is + * ignored if it is a null pointer. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(n(|V|+|E|)), where |V| is the number of + * vertices, |E| is the number of edges, and n is the number of cuts. + */ + +igraph_error_t igraph_all_st_cuts(const igraph_t *graph, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target) { + + /* S is a special stack, in which elements are pushed in batches. + It is then possible to remove the whole batch in one step. + + T is a stack with an is-element operation. + Every element is included at most once. + */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_marked_queue_int_t S; + igraph_estack_t T; + igraph_vector_int_list_t *mypartition1s = partition1s, vpartition1s; + igraph_vector_int_t cut; + igraph_int_t i, nocuts; + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Listing all s-t cuts only implemented for " + "directed graphs", IGRAPH_UNIMPLEMENTED); + } + + if (!partition1s) { + mypartition1s = &vpartition1s; + IGRAPH_CHECK(igraph_vector_int_list_init(mypartition1s, 0)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, mypartition1s); + } else { + igraph_vector_int_list_clear(mypartition1s); + } + + IGRAPH_CHECK(igraph_marked_queue_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_marked_queue_int_destroy, &S); + IGRAPH_CHECK(igraph_estack_init(&T, no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_estack_destroy, &T); + IGRAPH_VECTOR_INT_INIT_FINALLY(&cut, 0); + + /* We call it with S={}, T={} */ + IGRAPH_CHECK(igraph_provan_shier_list(graph, &S, &T, + source, target, mypartition1s, + igraph_i_all_st_cuts_pivot, + /*pivot_arg=*/ 0)); + + nocuts = igraph_vector_int_list_size(mypartition1s); + + if (cuts) { + igraph_vector_int_t inS; + IGRAPH_CHECK(igraph_vector_int_init(&inS, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &inS); + igraph_vector_int_list_clear(cuts); + IGRAPH_CHECK(igraph_vector_int_list_reserve(cuts, nocuts)); + for (i = 0; i < nocuts; i++) { + igraph_vector_int_t *part = igraph_vector_int_list_get_ptr(mypartition1s, i); + igraph_int_t cutsize = 0; + igraph_int_t j, partlen = igraph_vector_int_size(part); + /* Mark elements */ + for (j = 0; j < partlen; j++) { + igraph_int_t v = VECTOR(*part)[j]; + VECTOR(inS)[v] = i + 1; + } + /* Check how many edges */ + for (j = 0; j < no_of_edges; j++) { + igraph_int_t from = IGRAPH_FROM(graph, j); + igraph_int_t to = IGRAPH_TO(graph, j); + igraph_int_t pfrom = VECTOR(inS)[from]; + igraph_int_t pto = VECTOR(inS)[to]; + if (pfrom == i + 1 && pto != i + 1) { + cutsize++; + } + } + /* Add the edges */ + IGRAPH_CHECK(igraph_vector_int_resize(&cut, cutsize)); + cutsize = 0; + for (j = 0; j < no_of_edges; j++) { + igraph_int_t from = IGRAPH_FROM(graph, j); + igraph_int_t to = IGRAPH_TO(graph, j); + igraph_int_t pfrom = VECTOR(inS)[from]; + igraph_int_t pto = VECTOR(inS)[to]; + if ((pfrom == i + 1 && pto != i + 1)) { + VECTOR(cut)[cutsize++] = j; + } + } + /* Add the vector to 'cuts' */ + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(cuts, &cut)); + } + + igraph_vector_int_destroy(&inS); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&cut); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + IGRAPH_FINALLY_CLEAN(3); + + if (!partition1s) { + igraph_vector_int_list_destroy(mypartition1s); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/* We need to find the minimal active elements of Sbar. I.e. all + active Sbar elements 'v', s.t. there is no other 'w' active Sbar + element from which 'v' is reachable. (Not necessarily through + active vertices.) + + We do so by traversing the nodes in topological sort order. The nodes that + are processed first and are not yet connected to any minimal nodes are then + marked as minimal (if active). Any subsequent nodes that are connected to + minimal nodes are then not marked as minimal. +*/ + +static igraph_error_t igraph_i_all_st_mincuts_minimal(const igraph_t *residual, + const igraph_marked_queue_int_t *S, + const igraph_bitset_t *active, + igraph_vector_int_t *minimal) { + + igraph_int_t no_of_nodes = igraph_vcount(residual); + igraph_int_t i; + igraph_vector_int_t neis; + igraph_bitset_t connected_to_minimal; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_BITSET_INIT_FINALLY(&connected_to_minimal, no_of_nodes); + + // Clear minimal nodes, we will push back to vector as required. + igraph_vector_int_clear(minimal); + + /* We will loop over the nodes in topological sort order, where residual is + * assumed to already be in topological sort order. This way, any node that + * we encounter that is not yet connected to a minimal node and that is + * active, should be marked as minimal. Any node that is connected to a + * minimal node should not be considered minimal, irrespective of it being + * active or not. + */ + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t j, n; + IGRAPH_CHECK(igraph_neighbors( + residual, &neis, i, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + + // Only consider nodes that are not in S. + if (!igraph_marked_queue_int_iselement(S, i)) { + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + /* If connected to node that is connected to node that is minimal, + * this node is also connected to node that is minimal. + */ + if (IGRAPH_BIT_TEST(connected_to_minimal, nei)) { + IGRAPH_BIT_SET(connected_to_minimal, i); + break; + } + } + + /* If this node is not connected to node that is minimal, and this node + * is minimal and active itself, set it as a minimal node. + */ + if (!IGRAPH_BIT_TEST(connected_to_minimal, i) && IGRAPH_BIT_TEST(*active, i)) { + + IGRAPH_CHECK(igraph_vector_int_push_back(minimal, i)); + /* Also mark this node as connected to minimal, to make sure that + * any descendants will be marked as being connected to a minimal + * node. + */ + IGRAPH_BIT_SET(connected_to_minimal, i); + } + } + } + + igraph_bitset_destroy(&connected_to_minimal); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_all_st_mincuts_data_t { + const igraph_bitset_t *active; +} igraph_i_all_st_mincuts_data_t; + +static igraph_error_t igraph_i_all_st_mincuts_pivot(const igraph_t *graph, + const igraph_marked_queue_int_t *S, + const igraph_estack_t *T, + igraph_int_t source, + igraph_int_t target, + igraph_int_t *v, + igraph_vector_int_t *Isv, + void *arg) { + + igraph_i_all_st_mincuts_data_t *data = arg; + const igraph_bitset_t *active = data->active; + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, j; + igraph_vector_int_t keep; + igraph_vector_int_t M; + igraph_int_t nomin; + + IGRAPH_UNUSED(source); IGRAPH_UNUSED(target); + + if (igraph_marked_queue_int_size(S) == no_of_nodes) { + igraph_vector_int_clear(Isv); + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&keep, 0); + for (i = 0; i < no_of_nodes; i++) { + if (!igraph_marked_queue_int_iselement(S, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&keep, i)); + } + } + + /* ------------------------------------------------------------- */ + /* Identify the set M of minimal elements that are active */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&M, 0); + IGRAPH_CHECK(igraph_i_all_st_mincuts_minimal(graph, S, active, &M)); + + /* ------------------------------------------------------------- */ + /* Now find a minimal element that is not in T */ + igraph_vector_int_clear(Isv); + nomin = igraph_vector_int_size(&M); + for (i = 0; i < nomin; i++) { + igraph_int_t min = VECTOR(M)[i]; + if (min != target) + if (!igraph_estack_iselement(T, min)) { + break; + } + } + if (i != nomin) { + /* OK, we found a pivot element. I(S,v) contains all elements + that can reach the pivot element */ + igraph_vector_int_t Isv_min; + IGRAPH_VECTOR_INT_INIT_FINALLY(&Isv_min, 0); + *v = VECTOR(M)[i]; + /* TODO: restricted == keep ? */ + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ *v, /*roots=*/ NULL, + /*mode=*/ IGRAPH_IN, /*unreachable=*/ false, + /*restricted=*/ &keep, /*order=*/ &Isv_min, + /*rank=*/ NULL, /*parents=*/ NULL, /*pred=*/ NULL, + /*succ=*/ NULL, /*dist=*/ NULL, /*callback=*/ NULL, + /*extra=*/ NULL)); + for (j = 0; j < no_of_nodes; j++) { + igraph_int_t u = VECTOR(Isv_min)[j]; + if (u < 0) { + break; + } + if (!igraph_marked_queue_int_iselement(S, u)) { + IGRAPH_CHECK(igraph_vector_int_push_back(Isv, u)); + } + } + igraph_vector_int_destroy(&Isv_min); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&M); + igraph_vector_int_destroy(&keep); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_all_st_mincuts + * \brief All minimum s-t cuts of a directed graph. + * + * This function lists all edge cuts between two vertices, in a directed graph, + * with minimum total capacity. Possibly, multiple cuts may have the same total + * capacity, although there is often only one minimum cut in weighted graphs. + * It is recommended to supply integer-values capacities. Otherwise, not all + * minimum cuts may be detected because of numerical roundoff errors. + * The implemented algorithm is described in JS Provan and DR + * Shier: A Paradigm for listing (s,t)-cuts in graphs, Algorithmica 15, + * 351--372, 1996. + * + * \param graph The input graph, it must be directed. + * \param value Pointer to a real number or \c NULL. The value of the minimum cut + * is stored here, unless it is a null pointer. + * \param cuts Pointer to initialized list of integer vectors or \c NULL. + * The cuts are stored here as lists of vertex IDs. + * \param partition1s Pointer to an initialized list of integer vectors or \c NULL. + * The list of vertex sets, generating the actual edge cuts, are stored + * here. Each vector contains a set of vertex IDs. If X is such + * a set, then all edges going from X to the complement of X + * form an (s,t) edge-cut in the graph. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector of edge capacities. All capacities must be + * strictly positive. If this is a null pointer, then all edges + * are assumed to have capacity one. + * \return Error code. + * + * Time complexity: O(n(|V|+|E|))+O(F), where |V| is the number of + * vertices, |E| is the number of edges, and n is the number of cuts; + * O(F) is the time complexity of the maximum flow algorithm, see \ref + * igraph_maxflow(). + * + * \example examples/simple/igraph_all_st_mincuts.c + */ + +igraph_error_t igraph_all_st_mincuts(const igraph_t *graph, igraph_real_t *value, + igraph_vector_int_list_t *cuts, + igraph_vector_int_list_t *partition1s, + igraph_int_t source, + igraph_int_t target, + const igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_t flow; + igraph_t residual; + igraph_vector_int_t NtoL; + igraph_vector_int_t cut; + igraph_int_t newsource, newtarget; + igraph_marked_queue_int_t S; + igraph_estack_t T; + igraph_i_all_st_mincuts_data_t pivot_data; + igraph_bitset_t VE1bool; + igraph_int_t i, nocuts; + igraph_int_t proj_nodes; + igraph_vector_int_t revmap_ptr, revmap_next; + igraph_vector_int_list_t closedsets; + igraph_vector_int_list_t *mypartition1s = partition1s, vpartition1s; + igraph_maxflow_stats_t stats; + + /* -------------------------------------------------------------------- */ + /* Error checks */ + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("s-t cuts can only be listed in directed graphs.", IGRAPH_UNIMPLEMENTED); + } + if (source < 0 || source >= no_of_nodes) { + IGRAPH_ERROR("Invalid source vertex.", IGRAPH_EINVVID); + } + if (target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid target vertex.", IGRAPH_EINVVID); + } + if (source == target) { + IGRAPH_ERROR("Source and target vertices are the same.", IGRAPH_EINVAL); + } + if (capacity && igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Capacity vector length must agree with number of edges.", IGRAPH_EINVAL); + } + if (capacity && no_of_edges > 0 && igraph_vector_min(capacity) <= 0) { + IGRAPH_ERROR("Not all capacities are strictly positive.", IGRAPH_EINVAL); + } + + if (!partition1s) { + mypartition1s = &vpartition1s; + IGRAPH_CHECK(igraph_vector_int_list_init(mypartition1s, 0)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, mypartition1s); + } + + /* -------------------------------------------------------------------- */ + /* We need to calculate the maximum flow first */ + IGRAPH_VECTOR_INIT_FINALLY(&flow, 0); + IGRAPH_CHECK(igraph_maxflow(graph, value, &flow, /*cut=*/ NULL, + /*partition1=*/ NULL, /*partition2=*/ NULL, + /*source=*/ source, /*target=*/ target, + capacity, &stats)); + + /* -------------------------------------------------------------------- */ + /* Then we need the reverse residual graph */ + IGRAPH_CHECK(igraph_reverse_residual_graph(graph, capacity, &residual, &flow)); + IGRAPH_FINALLY(igraph_destroy, &residual); + + /* -------------------------------------------------------------------- */ + /* We shrink it to its strongly connected components */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&NtoL, 0); + IGRAPH_CHECK(igraph_connected_components( + &residual, /*membership=*/ &NtoL, /*csize=*/ NULL, + /*no=*/ &proj_nodes, IGRAPH_STRONG + )); + IGRAPH_CHECK(igraph_contract_vertices(&residual, /*mapping=*/ &NtoL, /*vertex_comb=*/ NULL)); + IGRAPH_CHECK(igraph_simplify(&residual, /*remove_multiple=*/ true, /*remove_loops=*/ true, /*edge_comb=*/ NULL)); + + /* We relabel the residual graph so that it is in topological sort order. */ + igraph_int_t no_of_nodes_residual = igraph_vcount(&residual); + igraph_vector_int_t order; + igraph_t tmpgraph; + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes_residual); + IGRAPH_CHECK(igraph_topological_sorting(&residual, &order, IGRAPH_OUT)); + + /* Invert order to get permutation to ensure vertices follow topological order. */ + igraph_vector_int_t inv_order; + IGRAPH_VECTOR_INT_INIT_FINALLY(&inv_order, no_of_nodes_residual); + for (i = 0; i < no_of_nodes_residual; i++) { + VECTOR(inv_order)[VECTOR(order)[i]] = i; + } + + // Relabel mapping + for (i = 0; i < no_of_nodes; i++) { + VECTOR(NtoL)[i] = VECTOR(inv_order)[VECTOR(NtoL)[i]]; + } + + /* Permute vertices and replace residual with tmpgraph that is topologically + * sorted. + */ + IGRAPH_CHECK(igraph_permute_vertices(&residual, &tmpgraph, &order)); + + igraph_destroy(&residual); // We first free memory from original residual graph + residual = tmpgraph; // Then we replace it by allocated memory from tmpgraph + + igraph_vector_int_destroy(&inv_order); + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(2); + + newsource = VECTOR(NtoL)[source]; + newtarget = VECTOR(NtoL)[target]; + + /* TODO: handle the newsource == newtarget case */ + + /* -------------------------------------------------------------------- */ + /* Determine the active vertices in the projection */ + IGRAPH_BITSET_INIT_FINALLY(&VE1bool, proj_nodes); + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(flow)[i] > 0) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_int_t pfrom = VECTOR(NtoL)[from]; + igraph_int_t pto = VECTOR(NtoL)[to]; + if (!IGRAPH_BIT_TEST(VE1bool, pfrom)) { + IGRAPH_BIT_SET(VE1bool, pfrom); + } + if (!IGRAPH_BIT_TEST(VE1bool, pto)) { + IGRAPH_BIT_SET(VE1bool, pto); + } + } + } + + if (cuts) { + igraph_vector_int_list_clear(cuts); + } + if (partition1s) { + igraph_vector_int_list_clear(partition1s); + } + + /* -------------------------------------------------------------------- */ + /* Everything is ready, list the cuts, using the right PIVOT + function */ + IGRAPH_CHECK(igraph_marked_queue_int_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_marked_queue_int_destroy, &S); + IGRAPH_CHECK(igraph_estack_init(&T, no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_estack_destroy, &T); + IGRAPH_VECTOR_INT_INIT_FINALLY(&cut, 0); + + pivot_data.active = &VE1bool; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&closedsets, 0); + IGRAPH_CHECK(igraph_provan_shier_list(&residual, &S, &T, + newsource, newtarget, &closedsets, + igraph_i_all_st_mincuts_pivot, + &pivot_data)); + + /* Convert the closed sets in the contracted graphs to cutsets in the + original graph */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&revmap_ptr, igraph_vcount(&residual)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&revmap_next, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t id = VECTOR(NtoL)[i]; + VECTOR(revmap_next)[i] = VECTOR(revmap_ptr)[id]; + VECTOR(revmap_ptr)[id] = i + 1; + } + + /* Create partitions in original graph */ + nocuts = igraph_vector_int_list_size(&closedsets); + igraph_vector_int_list_clear(mypartition1s); + IGRAPH_CHECK(igraph_vector_int_list_reserve(mypartition1s, nocuts)); + for (i = 0; i < nocuts; i++) { + igraph_vector_int_t *supercut = igraph_vector_int_list_get_ptr(&closedsets, i); + igraph_int_t j, supercutsize = igraph_vector_int_size(supercut); + + igraph_vector_int_clear(&cut); + for (j = 0; j < supercutsize; j++) { + igraph_int_t vtx = VECTOR(*supercut)[j]; + igraph_int_t ovtx = VECTOR(revmap_ptr)[vtx]; + while (ovtx != 0) { + ovtx--; + IGRAPH_CHECK(igraph_vector_int_push_back(&cut, ovtx)); + ovtx = VECTOR(revmap_next)[ovtx]; + } + } + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(mypartition1s, &cut)); + + /* TODO: we could already reclaim the memory taken by 'supercut' here */ + } + + igraph_vector_int_destroy(&revmap_next); + igraph_vector_int_destroy(&revmap_ptr); + igraph_vector_int_list_destroy(&closedsets); + IGRAPH_FINALLY_CLEAN(3); + + /* Create cuts in original graph */ + if (cuts) { + igraph_vector_int_t memb; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&memb, no_of_nodes); + IGRAPH_CHECK(igraph_vector_int_list_reserve(cuts, nocuts)); + + for (i = 0; i < nocuts; i++) { + igraph_vector_int_t *part = igraph_vector_int_list_get_ptr(mypartition1s, i); + igraph_int_t j, n = igraph_vector_int_size(part); + + igraph_vector_int_clear(&cut); + for (j = 0; j < n; j++) { + igraph_int_t vtx = VECTOR(*part)[j]; + VECTOR(memb)[vtx] = i + 1; + } + for (j = 0; j < no_of_edges; j++) { + if (VECTOR(flow)[j] > 0) { + igraph_int_t from = IGRAPH_FROM(graph, j); + igraph_int_t to = IGRAPH_TO(graph, j); + if (VECTOR(memb)[from] == i + 1 && VECTOR(memb)[to] != i + 1) { + IGRAPH_CHECK(igraph_vector_int_push_back(&cut, j)); + } + } + } + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(cuts, &cut)); + } + + igraph_vector_int_destroy(&memb); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&cut); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_bitset_destroy(&VE1bool); + igraph_vector_int_destroy(&NtoL); + igraph_destroy(&residual); + igraph_vector_destroy(&flow); + IGRAPH_FINALLY_CLEAN(7); + + if (!partition1s) { + igraph_vector_int_list_destroy(mypartition1s); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/games/barabasi.c b/src/games/barabasi.c new file mode 100644 index 0000000..49e9129 --- /dev/null +++ b/src/games/barabasi.c @@ -0,0 +1,841 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_psumtree.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +/* Attraction function for barabasi_game. + * We special-case power == 0 to ensure that 0^0 is computed as 1 instead of NaN. */ +static igraph_real_t attraction(igraph_real_t degree, igraph_real_t power, igraph_real_t A) { + return ( power == 0 ? 1.0 : pow(degree, power) ) + A; +} + +static igraph_error_t igraph_i_barabasi_game_bag(igraph_t *graph, igraph_int_t n, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_bool_t directed, + const igraph_t *start_from); + +static igraph_error_t igraph_i_barabasi_game_psumtree_multiple(igraph_t *graph, + igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from); + +static igraph_error_t igraph_i_barabasi_game_psumtree(igraph_t *graph, + igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from); + +static igraph_error_t igraph_i_barabasi_game_bag(igraph_t *graph, igraph_int_t n, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_bool_t directed, + const igraph_t *start_from) { + + igraph_int_t no_of_nodes = n; + igraph_int_t no_of_neighbors = m; + igraph_int_t *bag; + igraph_int_t bagp = 0; + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t resp; + igraph_int_t i, j, k; + igraph_int_t bagsize, start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = true; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_int_size(outseq) > 1) { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &new_edges)); + new_edges -= VECTOR(*outseq)[0]; + } else { + new_edges = 0; + } + } else { + IGRAPH_SAFE_MULT(no_of_nodes - start_nodes, no_of_neighbors, &new_edges); + } + IGRAPH_SAFE_ADD(start_edges, new_edges, &no_of_edges); + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + resp = start_edges * 2; + bagsize = no_of_nodes; + IGRAPH_SAFE_ADD(bagsize, no_of_edges, &bagsize); + if (outpref) { + IGRAPH_SAFE_ADD(bagsize, no_of_edges, &bagsize); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + bag = IGRAPH_CALLOC(bagsize, igraph_int_t); + if (bag == 0) { + IGRAPH_ERROR("barabasi_game failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, bag); + + /* The first node(s) in the bag */ + if (start_from) { + igraph_vector_int_t deg; + igraph_int_t ii, jj, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°, sn); + IGRAPH_CHECK(igraph_degree(start_from, °, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + for (ii = 0; ii < sn; ii++) { + igraph_int_t d = VECTOR(deg)[ii]; + for (jj = 0; jj <= d; jj++) { + bag[bagp++] = ii; + } + } + + igraph_vector_int_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + } else { + bag[bagp++] = 0; + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ false)); + IGRAPH_CHECK(igraph_vector_int_resize(&edges, no_of_edges * 2)); + } + + /* and the others */ + + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + /* draw edges */ + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[k]; + } + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t to = bag[RNG_INTEGER(0, bagp - 1)]; + VECTOR(edges)[resp++] = i; + VECTOR(edges)[resp++] = to; + } + /* update bag */ + bag[bagp++] = i; + for (j = 0; j < no_of_neighbors; j++) { + bag[bagp++] = VECTOR(edges)[resp - 2 * j - 1]; + if (outpref) { + bag[bagp++] = i; + } + } + } + + IGRAPH_FREE(bag); + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_barabasi_game_psumtree_multiple(igraph_t *graph, + igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from) { + + igraph_int_t no_of_nodes = n; + igraph_int_t no_of_neighbors = m; + igraph_vector_int_t edges; + igraph_int_t i, j, k; + igraph_psumtree_t sumtree; + igraph_int_t edgeptr = 0; + igraph_vector_int_t degree; + igraph_int_t start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = true; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_int_size(outseq) > 1) { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &new_edges)); + new_edges -= VECTOR(*outseq)[0]; + } else { + new_edges = 0; + } + } else { + IGRAPH_SAFE_MULT(no_of_nodes - start_nodes, no_of_neighbors, &new_edges); + } + IGRAPH_SAFE_ADD(start_edges, new_edges, &no_of_edges); + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + edgeptr = start_edges * 2; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + + /* First node(s): */ + if (start_from) { + igraph_int_t ii, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + IGRAPH_CHECK(igraph_degree(start_from, °ree, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_vector_int_resize(°ree, no_of_nodes)); + for (ii = 0; ii < sn; ii++) { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, ii, attraction(VECTOR(degree)[ii], power, A))); + } + } else { + /* Any weight may be used for the first node. In the first step, it will be connected to + * with certainty, after which its weight will be set appropriately. */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, 1.0)); + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ false)); + IGRAPH_CHECK(igraph_vector_int_resize(&edges, no_of_edges * 2)); + } + + /* And the rest: */ + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + igraph_real_t sum = igraph_psumtree_sum(&sumtree); + igraph_int_t to; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[k]; + } + for (j = 0; j < no_of_neighbors; j++) { + if (sum == 0) { + /* If none of the so-far added nodes have positive weights, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t nn = VECTOR(edges)[edgeptr - 2 * j - 1]; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, nn, attraction(VECTOR(degree)[nn], power, A))); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, attraction(VECTOR(degree)[i], power, A))); + } else { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, attraction(0, power, A))); + } + } + + igraph_psumtree_destroy(&sumtree); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_barabasi_game_psumtree(igraph_t *graph, + igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from) { + + igraph_int_t no_of_nodes = n; + igraph_int_t no_of_neighbors = m; + igraph_vector_int_t edges; + igraph_int_t i, j, k; + igraph_psumtree_t sumtree; + igraph_int_t edgeptr = 0; + igraph_vector_int_t degree; + igraph_int_t start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = true; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_int_size(outseq) > 1) { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &new_edges)); + new_edges -= VECTOR(*outseq)[0]; + } else { + new_edges = 0; + } + } else { + IGRAPH_SAFE_MULT(no_of_nodes - start_nodes, no_of_neighbors, &new_edges); + } + IGRAPH_SAFE_ADD(start_edges, new_edges, &no_of_edges); + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + edgeptr = start_edges * 2; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + + /* First node(s): */ + if (start_from) { + igraph_int_t ii, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + IGRAPH_CHECK(igraph_degree(start_from, °ree, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_vector_int_resize(°ree, no_of_nodes)); + for (ii = 0; ii < sn; ii++) { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, ii, attraction(VECTOR(degree)[ii], power, A))); + } + } else { + /* Any weight may be used for the first node. In the first step, it will be connected to + * with certainty, after which its weight will be set appropriately. */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, 1.0)); + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ false)); + } + + /* And the rest: */ + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + igraph_real_t sum; + igraph_int_t to; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[k]; + } + if (no_of_neighbors >= i) { + /* All existing vertices are cited */ + for (to = 0; to < i; to++) { + VECTOR(degree)[to]++; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + edgeptr += 2; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, to, attraction(VECTOR(degree)[to], power, A))); + } + } else { + for (j = 0; j < no_of_neighbors; j++) { + sum = igraph_psumtree_sum(&sumtree); + if (sum == 0) { + /* If none of the so-far added nodes have positive weights, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + VECTOR(degree)[to]++; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + edgeptr += 2; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, to, 0.0)); + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t nn = VECTOR(edges)[edgeptr - 2 * j - 1]; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, nn, attraction(VECTOR(degree)[nn], power, A))); + } + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors > i ? i : no_of_neighbors; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, attraction(VECTOR(degree)[i], power, A))); + } else { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, attraction(0, power, A))); + } + } + + igraph_psumtree_destroy(&sumtree); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_barabasi_game + * \brief Generates a graph based on the Barabási-Albert model. + * + * This function implements several variants of the preferential attachment + * process, including linear and non-linear varieties of the Barabási-Albert + * and Price models. The graph construction starts with a single vertex, + * or an existing graph given by the \p start_from parameter. Then new vertices + * are added one at a time. Each new vertex connects to \p m existing vertices, + * choosing them with probabilities proportional to + * + * + * d^power + A, + * + * + * where \c d is the in- or total degree of the existing vertex (controlled + * by the \p outpref argument), while \p power and \p A are given by + * parameters. The constant attractiveness \p A + * is used to ensure that vertices with zero in-degree can also be + * connected to with non-zero probability. + * + * + * Barabási, A.-L. and Albert R. 1999. Emergence of scaling in + * random networks, Science, 286 509--512. + * https://doi.org/10.1126/science.286.5439.509 + * + * + * de Solla Price, D. J. 1965. Networks of Scientific Papers, Science, + * 149 510--515. + * https://doi.org/10.1126/science.149.3683.510 + * + * \param graph An uninitialized graph object. + * \param n The number of vertices in the graph. + * \param power Power of the preferential attachment. In the classic preferential + * attachment model power=1. Other values allow for + * sampling from a non-linear preferential attachment model. + * Negative values are only allowed when no zero-degree vertices + * are present during the construction process, i.e. when + * the starting graph has no isolated vertices and \p outpref + * is set to \c true. + * \param m The number of outgoing edges generated for each + * vertex. Only used when \p outseq is \c NULL. + * \param outseq Gives the (out-)degrees of the vertices. If this is + * constant, this can be a \c NULL pointer. + * In this case \p m contains the constant out-degree. + * The very first vertex has by definition no outgoing edges, + * so the first number in this vector is ignored. + * \param outpref Boolean, if true not only the in- but also the out-degree + * of a vertex increases its citation probability. I.e., the + * citation probability is determined by the total degree of + * the vertices. Ignored and assumed to be true if the graph + * being generated is undirected. + * \param A The constant attractiveness of vertices. When \p outpref + * is set to \c false, it should be positive to ensure that + * zero in-degree vertices can be connected to as well. + * \param directed Boolean, whether to generate a directed graph. + * When set to \c false, outpref is assumed to be \c true. + * \param algo The algorithm to use to generate the network. Possible + * values: + * \clist + * \cli IGRAPH_BARABASI_BAG + * This is the algorithm that was previously (before version + * 0.6) solely implemented in igraph. It works by putting the + * IDs of the vertices into a bag (multiset, really), exactly + * as many times as their (in-)degree, plus once more. Then + * the required number of cited vertices are drawn from the + * bag, with replacement. This method might generate multiple + * edges. It only works if power=1 and A=1. + * \cli IGRAPH_BARABASI_PSUMTREE + * This algorithm uses a partial prefix-sum tree to generate + * the graph. It does not generate multiple edges and + * works for any power and A values. + * \cli IGRAPH_BARABASI_PSUMTREE_MULTIPLE + * This algorithm also uses a partial prefix-sum tree to + * generate the graph. The difference is, that now multiple + * edges are allowed. This method was implemented under the + * name \c igraph_nonlinear_barabasi_game before version 0.6. + * \endclist + * \param start_from Either a \c NULL pointer, or a graph. In the former + * case, the starting configuration is a clique of size \p m. + * In the latter case, the graph is a starting configuration. + * The graph must be non-empty, i.e. it must have at least one + * vertex. If a graph is supplied here and the \p outseq + * argument is also given, then \p outseq should only contain + * information on the vertices that are not in the \p + * start_from graph. + * \return Error code: + * \c IGRAPH_EINVAL: invalid \p n, \p m, \p A or \p outseq parameter. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. + * + * \example examples/simple/igraph_barabasi_game.c + * \example examples/simple/igraph_barabasi_game2.c + */ +igraph_error_t igraph_barabasi_game(igraph_t *graph, igraph_int_t n, + igraph_real_t power, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + igraph_barabasi_algorithm_t algo, + const igraph_t *start_from) { + + igraph_int_t start_nodes = start_from ? igraph_vcount(start_from) : 0; + igraph_int_t newn = start_from ? n - start_nodes : n; + + /* In undirected graphs, always consider the total degree. */ + if (!directed) { + outpref = true; + } + + /* Check arguments */ + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices.", IGRAPH_EINVAL); + } else if (newn < 0) { + IGRAPH_ERROR("Starting graph has too many vertices.", IGRAPH_EINVAL); + } + if (start_from && start_nodes == 0) { + IGRAPH_ERROR("Cannot start from an empty graph.", IGRAPH_EINVAL); + } + if (outseq && igraph_vector_int_size(outseq) != newn) { + IGRAPH_ERROR("Invalid out-degree sequence length.", IGRAPH_EINVAL); + } + if (!outseq && m < 0) { + IGRAPH_ERROR("Number of edges added per step must not be negative.", IGRAPH_EINVAL); + } + if (outseq && newn > 0 && igraph_vector_int_min(outseq) < 0) { + IGRAPH_ERROR("Negative out-degree in sequence.", IGRAPH_EINVAL); + } + if (!outpref && A <= 0) { + IGRAPH_ERROR("Constant attractiveness (A) must be positive.", + IGRAPH_EINVAL); + } + if (outpref && A < 0) { + IGRAPH_ERROR("Constant attractiveness (A) must be non-negative.", + IGRAPH_EINVAL); + } + if (algo == IGRAPH_BARABASI_BAG) { + if (power != 1) { + IGRAPH_ERROR("Power must be one for bag algorithm.", IGRAPH_EINVAL); + } + if (A != 1) { + IGRAPH_ERROR("Constant attractiveness (A) must be one for bag algorithm.", + IGRAPH_EINVAL); + } + } + if (start_from && directed != igraph_is_directed(start_from)) { + IGRAPH_WARNING("Directedness of the start graph and the output graph mismatch."); + } + if (start_from && !igraph_is_directed(start_from) && !outpref) { + IGRAPH_ERROR("`outpref' must be true if starting from an undirected graph.", + IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_empty(graph, 0, directed); + } + + switch (algo) { + case IGRAPH_BARABASI_BAG: + return igraph_i_barabasi_game_bag(graph, n, m, outseq, outpref, directed, start_from); + + case IGRAPH_BARABASI_PSUMTREE: + return igraph_i_barabasi_game_psumtree(graph, n, power, m, outseq, + outpref, A, directed, start_from); + case IGRAPH_BARABASI_PSUMTREE_MULTIPLE: + return igraph_i_barabasi_game_psumtree_multiple(graph, n, power, m, + outseq, outpref, A, + directed, start_from); + default: + IGRAPH_ERROR("Invalid algorithm for Barabasi game.", IGRAPH_EINVAL); + } +} + +/* Attraction function for barabasi_aging_game. + * We special-case deg_exp == 0 to ensure that 0^0 is computed as 1 instead of NaN. */ +static igraph_real_t attraction_aging( + igraph_real_t deg, igraph_real_t age, + igraph_real_t deg_exp, igraph_real_t age_exp, + igraph_real_t deg_A, igraph_real_t age_A, + igraph_real_t deg_coef, igraph_real_t age_coef) { + + igraph_real_t dp = deg_exp == 0 ? 1.0 : pow(deg, deg_exp); + igraph_real_t ap = pow(age, age_exp); + return (deg_coef * dp + deg_A) * (age_coef * ap + age_A); +} + +/** + * \function igraph_barabasi_aging_game + * \brief Preferential attachment with aging of vertices. + * + * + * This game starts with one vertex (if \p nodes > 0). In each step + * a new node is added, and it is connected to \p m existing nodes. + * Existing nodes to connect to are chosen with probability dependent + * on their (in-)degree (\c k) and age (\c l). + * The degree-dependent part is + * deg_coef * k^pa_exp + zero_deg_appeal, + * while the age-dependent part is + * age_coef * l^aging_exp + zero_age_appeal, + * which are multiplied to obtain the final weight. + * + * + * The age \c l is based on the number of vertices in the + * network and the \p aging_bins argument: the age of a node + * is incremented by 1 after each + * floor(nodes / aging_bins) + 1 + * time steps. + * + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param m The number of edges to add in each time step. + * Ignored if \p outseq is a non-zero length vector. + * \param outseq The number of edges to add in each time step. If it + * is \c NULL or a zero-length vector then it is ignored + * and the \p m argument is used instead. + * \param outpref Boolean constant, whether the edges + * initiated by a vertex contribute to the probability to gain + * a new edge. + * \param pa_exp The exponent of the preferential attachment, a small + * positive number usually, the value 1 yields the classic + * linear preferential attachment. + * \param aging_exp The exponent of the aging, this is a negative + * number usually. + * \param aging_bins Integer constant, the number of age bins to use. + * \param zero_deg_appeal The degree dependent part of the + * attractiveness of the zero degree vertices. + * \param zero_age_appeal The age dependent part of the attractiveness + * of the vertices of age zero. This parameter is usually zero. + * \param deg_coef The coefficient for the degree. + * \param age_coef The coefficient for the age. + * \param directed Boolean constant, whether to generate a directed + * graph. + * \return Error code. + * + * Time complexity: O((|V|+|V|/aging_bins)*log(|V|)+|E|). |V| is the number + * of vertices, |E| the number of edges. + */ +igraph_error_t igraph_barabasi_aging_game(igraph_t *graph, + igraph_int_t nodes, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_int_t aging_bins, + igraph_real_t zero_deg_appeal, + igraph_real_t zero_age_appeal, + igraph_real_t deg_coef, + igraph_real_t age_coef, + igraph_bool_t directed) { + igraph_int_t no_of_nodes = nodes; + igraph_int_t no_of_neighbors = m; + igraph_int_t binwidth; + igraph_int_t no_of_edges; + igraph_vector_int_t edges; + igraph_int_t i, j, k; + igraph_psumtree_t sumtree; + igraph_int_t edgeptr = 0; + igraph_vector_int_t degree; + + if (no_of_nodes < 0) { + IGRAPH_ERRORF("Number of nodes must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, no_of_nodes); + } + if (outseq && igraph_vector_int_size(outseq) != no_of_nodes) { + IGRAPH_ERRORF("The length of the out-degree sequence (%" IGRAPH_PRId ") does not agree with the number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_int_size(outseq), no_of_nodes); + } + if (!outseq && m < 0) { + IGRAPH_ERRORF("The number of edges per time step must not be negative, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + m); + } + if (aging_bins <= 0) { + IGRAPH_ERRORF("Number of aging bins must be positive, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + aging_bins); + } + if (deg_coef < 0) { + IGRAPH_ERRORF("Degree coefficient must be non-negative, got %g.", + IGRAPH_EINVAL, + deg_coef); + } + if (age_coef < 0) { + IGRAPH_ERRORF("Age coefficient must be non-negative, got %g.", + IGRAPH_EINVAL, + deg_coef); + } + + if (zero_deg_appeal < 0) { + IGRAPH_ERRORF("Zero degree appeal must be non-negative, got %g.", + IGRAPH_EINVAL, + zero_deg_appeal); + } + if (zero_age_appeal < 0) { + IGRAPH_ERRORF("Zero age appeal must be non-negative, got %g.", + IGRAPH_EINVAL, + zero_age_appeal); + } + + /* This effectively also hanbdles an outseq size of 0. + * From here on we assume that outseq has a size of at least 1. */ + if (no_of_nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + return IGRAPH_SUCCESS; + } + + binwidth = no_of_nodes / aging_bins + 1; + + if (!outseq) { + no_of_neighbors = m; + IGRAPH_SAFE_MULT(no_of_nodes - 1, no_of_neighbors, &no_of_edges); + } else { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &no_of_edges)); + no_of_edges -= VECTOR(*outseq)[0]; + } + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + + /* First node: */ + /* Any weight may be used for the first node. In the first step, it will be connected to + * with certainty, after which its weight will be set appropriately. */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, 1.0)); + + /* And the rest: */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + igraph_int_t to; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[i]; + } + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + if (sum == 0) { + /* If none of the so-far added nodes have positive weights, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t n = VECTOR(edges)[edgeptr - 2 * j - 1]; + igraph_int_t age = (i - n) / binwidth; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, n, + attraction_aging(VECTOR(degree)[n], age+1, + pa_exp, aging_exp, + zero_deg_appeal, zero_age_appeal, + deg_coef, age_coef) + )); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, i, + attraction_aging(VECTOR(degree)[i], 1, + pa_exp, aging_exp, + zero_deg_appeal, zero_age_appeal, + deg_coef, age_coef) + )); + } else { + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, i, + attraction_aging(0, 1, + pa_exp, aging_exp, + zero_deg_appeal, zero_age_appeal, + deg_coef, age_coef) + )); + } + + /* aging */ + for (k = 1; binwidth * k <= i; k++) { + igraph_int_t shnode = i - binwidth * k; + igraph_int_t deg = VECTOR(degree)[shnode]; + igraph_int_t age = (i - shnode) / binwidth; + /* igraph_real_t old=igraph_psumtree_get(&sumtree, shnode); */ + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, shnode, + attraction_aging(deg, age + 2, + pa_exp, aging_exp, + zero_deg_appeal, zero_age_appeal, + deg_coef, age_coef) + )); + } + } + + igraph_vector_int_destroy(°ree); + igraph_psumtree_destroy(&sumtree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/callaway_traits.c b/src/games/callaway_traits.c new file mode 100644 index 0000000..7b93af4 --- /dev/null +++ b/src/games/callaway_traits.c @@ -0,0 +1,197 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_random.h" + +/** + * \function igraph_callaway_traits_game + * \brief Simulates a growing network with vertex types. + * + * The different types of vertices prefer to connect other types of + * vertices with a given probability. + * + * + * The simulation goes like this: in each discrete time step a new + * vertex is added to the graph. The type of this vertex is generated + * based on \p type_dist. Then two vertices are selected uniformly + * randomly from the graph. The probability that they will be + * connected depends on the types of these vertices and is taken from + * \p pref_matrix. Then another two vertices are selected and this is + * repeated \p edges_per_step times in each time step. + * + * + * References: + * + * + * D. S. Callaway, J. E. Hopcroft, J. M. Kleinberg, M. E. J. Newman, and S. H. Strogatz, + * Are randomly grown graphs really random? + * Phys. Rev. E 64, 041902 (2001). + * https://doi.org/10.1103/PhysRevE.64.041902 + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of nodes in the graph. + * \param types Number of node types. + * \param edges_per_step The number of connections tried in each time step. + * \param type_dist Vector giving the distribution of the vertex types. + * If \c NULL, the distribution is assumed to be uniform. + * \param pref_matrix Matrix giving the connection probabilities for + * the vertex types. + * \param directed Whether to generate a directed graph. + * \param node_type_vec An initialized vector or \c NULL. + * If not \c NULL, the type of each node will be stored here. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|*k*log(|V|)), |V| is the number of vertices, + * k is \p edges_per_step. + */ +igraph_error_t igraph_callaway_traits_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t edges_per_step, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec) { + igraph_int_t i, j; + igraph_vector_int_t edges; + igraph_vector_t cumdist; + igraph_real_t maxcum; + igraph_vector_int_t *nodetypes; + + /* Argument contracts */ + if (nodes < 0) { + IGRAPH_ERROR("The number of vertices must be non-negative.", IGRAPH_EINVAL); + } + + if (edges_per_step < 0) { + IGRAPH_ERRORF("Number of edges per step should be non-negative, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + edges_per_step); + } + + if (types < 1) { + IGRAPH_ERROR("The number of vertex types must be at least 1.", IGRAPH_EINVAL); + } + + if (type_dist) { + igraph_real_t lo; + + if (igraph_vector_size(type_dist) != types) { + IGRAPH_ERROR("The vertex type distribution vector must agree in length with the number of types.", + IGRAPH_EINVAL); + } + + lo = igraph_vector_min(type_dist); + if (lo < 0) { + IGRAPH_ERROR("The vertex type distribution vector must not contain negative values.", IGRAPH_EINVAL); + } + if (isnan(lo)) { + IGRAPH_ERROR("The vertex type distribution vector must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (igraph_matrix_nrow(pref_matrix) != types || igraph_matrix_ncol(pref_matrix) != types) { + IGRAPH_ERROR("The preference matrix must be square and agree in dimensions with the number of types.", IGRAPH_EINVAL); + } + + { + igraph_real_t lo, hi; + igraph_matrix_minmax(pref_matrix, &lo, &hi); /* matrix size is at least 1x1, safe to call minmax */ + + if (lo < 0 || hi > 1) { + IGRAPH_ERROR("The preference matrix must contain probabilities in [0, 1].", IGRAPH_EINVAL); + } + if (isnan(lo) || isnan(hi)) { + IGRAPH_ERROR("The preference matrix must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (! directed && ! igraph_matrix_is_symmetric(pref_matrix)) { + IGRAPH_ERROR("The preference matrix must be symmetric when generating undirected graphs.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + + if (type_dist) { + VECTOR(cumdist)[0] = 0; + for (i = 0; i < types; ++i) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + } else { + for (i = 0; i < types+1; ++i) { + VECTOR(cumdist)[i] = i; + } + } + maxcum = igraph_vector_tail(&cumdist); + + if (maxcum <= 0) { + IGRAPH_ERROR("The vertex type distribution vector must contain at least one positive value.", IGRAPH_EINVAL); + } + + if (node_type_vec) { + nodetypes = node_type_vec; + IGRAPH_CHECK(igraph_vector_int_resize(nodetypes, nodes)); + } else { + nodetypes = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(nodetypes, "Insufficient memory for Callaway traits game."); + IGRAPH_FINALLY(igraph_free, nodetypes); + IGRAPH_VECTOR_INT_INIT_FINALLY(nodetypes, nodes); + } + + for (i = 0; i < nodes; i++) { + igraph_real_t uni = RNG_UNIF(0, maxcum); + igraph_int_t type; + igraph_vector_binsearch(&cumdist, uni, &type); + VECTOR(*nodetypes)[i] = type - 1; + } + + for (i = 1; i < nodes; i++) { + for (j = 0; j < edges_per_step; j++) { + igraph_int_t node1 = RNG_INTEGER(0, i); + igraph_int_t node2 = RNG_INTEGER(0, i); + igraph_int_t type1 = VECTOR(*nodetypes)[node1]; + igraph_int_t type2 = VECTOR(*nodetypes)[node2]; + /* printf("unif: %f, %f, types: %li, %li\n", uni1, uni2, type1, type2); */ + if (RNG_UNIF01() < MATRIX(*pref_matrix, type1, type2)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, node1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, node2)); + } + } + } + + if (! node_type_vec) { + igraph_vector_int_destroy(nodetypes); + IGRAPH_FREE(nodetypes); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/chung_lu.c b/src/games/chung_lu.c new file mode 100644 index 0000000..235c7d6 --- /dev/null +++ b/src/games/chung_lu.c @@ -0,0 +1,321 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_vector.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +#include /* isfinite() */ + +/* This implementation follows the ideas in: + * + * Joel Miller and Aric Hagberg, + * Efficient generation of networks with given expected degrees + * (2011) + * + * It is analogous to the method used in igraph_erdos_renyi_game_gnp() + * and has linear complexity in the number of edges. + */ + +static igraph_error_t check_expected_degrees(const igraph_vector_t *weights) { + igraph_real_t minw, maxw; + + igraph_vector_minmax(weights, &minw, &maxw); + + if (minw < 0) { + IGRAPH_ERRORF("Vertex weights must not be negative in Chung-Lu model, got %g.", IGRAPH_EINVAL, minw); + } + + /* Catches both NaN and +Inf. */ + if (! isfinite(maxw)) { + IGRAPH_ERRORF("Vertex weights must be finite, got %g.", IGRAPH_EINVAL, maxw); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_chung_lu_game + * \brief Samples graphs from the Chung-Lu model. + * + * \experimental + * + * The Chung-Lu model is useful for generating random graphs with fixed + * expected degrees. This function implements both the original model of Chung + * and Lu, as well as some additional variants with useful properties. + * + * + * In the original Chung-Lu model, each pair of vertices \c i and \c j is + * connected with independent probability p_ij = w_i w_j / S, + * where \c w_i is a weight associated with vertex \c i and + * S = sum_k w_k is the sum of weights. In the directed variant, + * vertices have both out-weights, w^out, and in-weights, + * w^in, with equal sums, + * S = sum_k w^out_k = sum_k w^in_k. + * The connection probability between \c i and \c j is + * p_ij = w^out_i w^in_j / S. + * + * + * This model is commonly used to create random graphs with a fixed \em expected + * degree sequence. The expected degree of vertex \c i is approximately equal + * to the weight \c w_i. Specifically, if the graph is directed and self-loops + * are allowed, then the expected out- and in-degrees are precisely + * w^out and w^in. If self-loops are disallowed, + * then the expected out- and in-degrees are w^out (S - w^in) / S + * and w^in (S - w^out) / S, respectively. If the graph is + * undirected, then the expected degrees with and without self-loops are + * w (S + w) / S and w (S - w) / S, respectively. + * + * + * A limitation of the original Chung-Lu model is that when some of the + * weights are large, the formula for \c p_ij yields values larger than 1. + * Chung and Lu's original paper excludes the use of such weights. When + * p_ij > 1, this function simply issues a warning and creates + * a connection between \c i and \c j. However, in this case the expected degrees + * will no longer relate to the weights in the manner stated above. Thus the + * original Chung-Lu model cannot produce certain (large) expected degrees. + * + * + * The overcome this limitation, this function implements additional variants of + * the model, with modified expressions for the connection probability \c p_ij + * between vertices \c i and \c j. Let q_ij = w_i w_j / S, or + * q_ij = w^out_i w^in_j / S in the directed case. All model + * variants become equivalent in the limit of sparse graphs where \c q_ij + * approaches zero. In the original Chung-Lu model, selectable by setting + * \p variant to \c IGRAPH_CHUNG_LU_ORIGINAL, p_ij = min(q_ij, 1). + * The \c IGRAPH_CHUNG_LU_MAXENT variant, sometiems referred to a the generalized + * random graph, uses p_ij = q_ij / (1 + q_ij), and is equivalent + * to a maximum entropy model (i.e. exponential random graph model) with + * a constraint on expected degrees; see Park and Newman (2004), Section B, + * setting exp(-Theta_ij) = w_i w_j / S. This model is also + * discussed by Britton, Deijfen and Martin-Löf (2006). By virtue of being + * a degree-constrained maximum entropy model, it produces graphs with the + * same degree sequence with the same probability. + * A third variant can be requested with \c IGRAPH_CHUNG_LU_NR, and uses + * p_ij = 1 - exp(-q_ij). This is the underlying simple graph + * of a multigraph model introduced by Norros and Reittu (2006). + * For a discussion of these three model variants, see Section 16.4 of + * Bollobás, Janson, Riordan (2007), as well as Van Der Hofstad (2013). + * + * + * References: + * + * + * Chung F and Lu L: Connected components in a random graph with given + * degree sequences. Annals of Combinatorics 6, 125-145 (2002). + * https://doi.org/10.1007/PL00012580 + * + * + * Miller JC and Hagberg A: + * Efficient Generation of Networks with Given Expected Degrees (2011). + * https://doi.org/10.1007/978-3-642-21286-4_10 + * + * + * Park J and Newman MEJ: Statistical mechanics of networks. + * Physical Review E 70, 066117 (2004). + * https://doi.org/10.1103/PhysRevE.70.066117 + * + * + * Britton T, Deijfen M, Martin-Löf A: + * Generating Simple Random Graphs with Prescribed Degree Distribution. + * J Stat Phys 124, 1377–1397 (2006). + * https://doi.org/10.1007/s10955-006-9168-x + * + * + * Norros I and Reittu H: On a conditionally Poissonian graph process. + * Advances in Applied Probability 38, 59–75 (2006). + * https://doi.org/10.1239/aap/1143936140 + * + * + * Bollobás B, Janson S, Riordan O: + * The phase transition in inhomogeneous random graphs. + * Random Struct Algorithms 31, 3–122 (2007). + * https://doi.org/10.1002/rsa.20168 + * + * + * Van Der Hofstad R: Critical behavior in inhomogeneous random graphs. + * Random Struct Algorithms 42, 480–508 (2013). + * https://doi.org/10.1002/rsa.20450 + * + * \param graph Pointer to an uninitialized graph object. + * \param out_weights A vector of non-negative vertex weights (or out-weights). + * In sparse graphs these will be approximately equal to the expected + * (out-)degrees. + * \param in_weights A vector of non-negative in-weights, approximately equal + * to the expected in-degrees in sparse graphs. May be set to \c NULL, + * in which case undirected graphs are generated. + * \param loops Whether to allow the creation of self-loops. Since vertex + * pairs are connected independently, setting this to false is equivalent + * to simply discarding self-loops from an existing loopy Chung-Lu graph. + * \param variant The model variant to sample from, with different definitions + * of the connection probability between vertices \c i and \c j. Given + * q_ij = w_i w_j / S, the following formulations are available: + * \clist + * \cli IGRAPH_CHUNG_LU_ORIGINAL + * the original Chung-Lu model, p_ij = min(q_ij, 1). + * \cli IGRAPH_CHUNG_LU_MAXENT + * maximum entropy model with fixed expected degrees, + * p_ij = q_ij / (1 + q_ij). + * \cli IGRAPH_CHUNG_LU_NR + * Norros and Reittu's model, p_ij = 1 - exp(-q_ij). + * \endclist + * \return Error code. + * + * \sa \ref igraph_static_fitness_game() implements a similar model with + * a sharp constraint on the number of edges; + * \ref igraph_degree_sequence_game() samples random graphs with sharply + * specified degrees; \ref igraph_erdos_renyi_game_gnp() creates random + * graphs with a fixed connection probability \c p between all vertex pairs. + * + * Time complexity: O(|E| + |V|), linear in the number of edges. + */ +igraph_error_t igraph_chung_lu_game(igraph_t *graph, + const igraph_vector_t *out_weights, + const igraph_vector_t *in_weights, + igraph_bool_t loops, + igraph_chung_lu_t variant) { + + const igraph_int_t no_of_nodes = igraph_vector_size(out_weights); + const igraph_bool_t directed = in_weights != NULL; + igraph_vector_int_t edges, idx; + igraph_real_t wsum = igraph_vector_sum(out_weights); + igraph_bool_t warned = false; + int iter = 0; + + /* Necessitated by floating point arithmetic used in the implementation. */ + if (no_of_nodes >= IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Number of vertices is too large.", IGRAPH_EOVERFLOW); + } + + if (! directed) { + in_weights = out_weights; + } else if (igraph_vector_size(in_weights) != no_of_nodes) { + IGRAPH_ERROR("Vertex out- and in-weight vectors must have the same length.", IGRAPH_EINVAL); + } + + if (no_of_nodes == 0) { + return igraph_empty(graph, 0, directed); + } + + IGRAPH_CHECK(check_expected_degrees(out_weights)); + + if (directed) { + IGRAPH_CHECK(check_expected_degrees(in_weights)); + + if (igraph_vector_sum(in_weights) != wsum) { + IGRAPH_ERRORF("Sum of out- and in-weights must be the same, got %g and %g, respectively.", + IGRAPH_EINVAL, wsum, igraph_vector_sum(in_weights)); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, 0); + + IGRAPH_CHECK(igraph_vector_sort_ind(in_weights, &idx, IGRAPH_DESCENDING)); + + for (igraph_int_t i=0; i < no_of_nodes; i++) { + igraph_int_t vi, vj; + igraph_real_t wi, wj; + igraph_real_t p, q; + + igraph_int_t j = directed ? 0 : i; + + vi = VECTOR(idx)[i]; + wi = VECTOR(*out_weights)[vi]; + + if (wi == 0) { + if (directed) continue; else break; + } + + p = 1; + + while (true) { + igraph_real_t gap = RNG_GEOM(p); + + /* This formulation not only terminates the loop when necessary, + * but also protects against overflow when 'p' is very small + * and 'gap' becomes very large, perhaps larger than representable + * in an igraph_int_t. */ + if (gap >= no_of_nodes-j) { + break; + } + + j += gap; + + vj = VECTOR(idx)[j]; + wj = VECTOR(*in_weights)[vj]; + + q = wi * wj / wsum; + switch (variant) { + case IGRAPH_CHUNG_LU_ORIGINAL: + if (q > 1) { + q = 1; + if (! warned && (loops || vi != vj)) { + IGRAPH_WARNINGF( + "Expected degrees %g and %g lead to a calculated connection probability " + "larger than 1 in Chung-Lu model. The degrees of the resulting graph will " + "not be consistent with the given input.", wi, wj); + warned = true; + } + } + break; + case IGRAPH_CHUNG_LU_MAXENT: + q = q / (1 + q); + break; + case IGRAPH_CHUNG_LU_NR: + q = 1 - exp(-q); + break; + default: + IGRAPH_ERROR("Invalid Chung-Lu variant.", IGRAPH_EINVAL); + } + + /* A probability of zero must not be passed to RNG_GEOM(), + * so we catch this case here. */ + if (q == 0) { + break; + } + + if (RNG_UNIF01() < q/p && (loops || vi != vj)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, vi)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, vj)); + } + + p = q; + + j++; + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 16); + } + } + + igraph_vector_int_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/citations.c b/src/games/citations.c new file mode 100644 index 0000000..9ce6a41 --- /dev/null +++ b/src/games/citations.c @@ -0,0 +1,498 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_psumtree.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +typedef struct { + igraph_int_t no; + igraph_psumtree_t *sumtrees; +} igraph_i_citing_cited_type_game_struct_t; + +static void igraph_i_citing_cited_type_game_free ( + igraph_i_citing_cited_type_game_struct_t *s); + +/** + * \function igraph_lastcit_game + * \brief Simulates a citation network, based on time passed since the last citation. + * + * This is a quite special stochastic graph generator, it models an + * evolving graph. In each time step a single vertex is added to the + * network and it cites a number of other vertices (as specified by + * the \p edges_per_step argument). The cited vertices are selected + * based on the last time they were cited. Time is measured by the + * addition of vertices and it is binned into \p agebins bins. + * So if the current time step is \c t and the last citation to a + * given \c i vertex was made in time step \c t0, then + * (t-t0) / binwidth + * is calculated where binwidth is + * nodes/agebins + 1, + * in the last expression '/' denotes integer division, so the + * fraction part is omitted. + * + * + * The \p preference argument specifies the preferences for the + * citation lags, i.e. its first elements contains the attractivity + * of the very recently cited vertices, etc. The last element is + * special, it contains the attractivity of the vertices which were + * never cited. This element should be bigger than zero. + * + * + * Note that this function generates networks with multiple edges if + * \p edges_per_step is bigger than one, call \ref igraph_simplify() + * on the result to get rid of these edges. + * + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param nodes The number of vertices in the network. + * \param edges_per_node The number of edges to add in each time + * step. + * \param agebins The number of age bins to use. + * \param preference Pointer to an initialized vector of length + * agebins + 1. This contains the "attractivity" of the various + * age bins, the last element is the attractivity of the vertices + * which were never cited, and it should be greater than zero. + * It is a good idea to have all positive values in this vector. + * Preferences cannot be negative. + * \param directed Boolean constant, whether to create directed + * networks. + * \return Error code. + * + * \sa \ref igraph_barabasi_aging_game(). + * + * Time complexity: O(|V|*a+|E|*log|V|), |V| is the number of vertices, + * |E| is the total number of edges, a is the \p agebins parameter. + */ +igraph_error_t igraph_lastcit_game(igraph_t *graph, + igraph_int_t nodes, igraph_int_t edges_per_node, + igraph_int_t agebins, + const igraph_vector_t *preference, + igraph_bool_t directed) { + + igraph_int_t no_of_nodes = nodes; + igraph_psumtree_t sumtree; + igraph_vector_int_t edges; + igraph_int_t *lastcit; + igraph_int_t *index; + igraph_int_t binwidth; + + if (agebins != igraph_vector_size(preference) - 1) { + IGRAPH_ERRORF("The `preference' vector should be of length `agebins' plus one." + "Number of agebins is %" IGRAPH_PRId ", preference vector is of length %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + agebins, igraph_vector_size(preference)); + } + if (nodes < 0) { + IGRAPH_ERRORF("Number of nodes must not be negative, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + nodes); + } + if (edges_per_node < 0) { + IGRAPH_ERRORF("Number of edges per node must not be negative, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + edges_per_node); + } + if (agebins < 1) { + IGRAPH_ERRORF("Number of age bins must be at least 1, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + agebins); + } + if (VECTOR(*preference)[agebins] <= 0) { + IGRAPH_ERRORF("The last element of the `preference' vector must be strictly positive, but is %g.", + IGRAPH_EINVAL, + VECTOR(*preference)[agebins]); + } + if (igraph_vector_min(preference) < 0) { + IGRAPH_ERRORF("The preference vector must not contain negative values, but found %g.", + IGRAPH_EINVAL, + igraph_vector_min(preference)); + } + + if (nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, nodes, directed)); + return IGRAPH_SUCCESS; + } + + binwidth = no_of_nodes / agebins + 1; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + lastcit = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(lastcit, "Insufficient memory for lastcit game."); + IGRAPH_FINALLY(igraph_free, lastcit); + + index = IGRAPH_CALLOC(no_of_nodes + 1, igraph_int_t); + IGRAPH_CHECK_OOM(index, "Insufficient memory for lastcit game."); + IGRAPH_FINALLY(igraph_free, index); + + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, nodes * edges_per_node)); + + /* The first node */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, VECTOR(*preference)[agebins])); + index[0] = 0; + index[1] = 0; + + for (igraph_int_t i = 1; i < no_of_nodes; i++) { + + /* Add new edges */ + for (igraph_int_t j = 0; j < edges_per_node; j++) { + igraph_int_t to; + const igraph_real_t sum = igraph_psumtree_sum(&sumtree); + if (sum == 0) { + /* If none of the so-far added nodes have positive weight, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + lastcit[to] = i + 1; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, to, VECTOR(*preference)[0])); + } + + /* Add the node itself */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, VECTOR(*preference)[agebins])); + index[i + 1] = index[i] + edges_per_node; + + /* Update the preference of some vertices if they got to another bin. + We need to know the citations of some older vertices, this is in the index. */ + for (igraph_int_t k = 1; i - binwidth * k >= 1; k++) { + const igraph_int_t shnode = i - binwidth * k; + const igraph_int_t m = index[shnode], n = index[shnode + 1]; + for (igraph_int_t j = 2 * m; j < 2 * n; j += 2) { + const igraph_int_t cnode = VECTOR(edges)[j + 1]; + if (lastcit[cnode] == shnode + 1) { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, cnode, VECTOR(*preference)[k])); + } + } + } + + } + + igraph_psumtree_destroy(&sumtree); + IGRAPH_FREE(index); + IGRAPH_FREE(lastcit); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cited_type_game + * \brief Simulates a citation based on vertex types. + * + * Function to create a network based on some vertex categories. This + * function creates a citation network: in each step a single vertex + * and \p edges_per_step citing edges are added. Nodes with + * different categories may have different probabilities to get + * cited, as given by the \p pref vector. + * + * + * Note that this function might generate networks with multiple edges + * if \p edges_per_step is greater than one. You might want to call + * \ref igraph_simplify() on the result to remove multiple edges. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the network. + * \param types Numeric vector giving the categories of the vertices, + * so it should contain \p nodes non-negative integer + * numbers. Types are numbered from zero. + * \param pref The attractivity of the different vertex categories in + * a vector. Its length should be the maximum element in \p types + * plus one (types are numbered from zero). + * \param edges_per_step Integer constant, the number of edges to add + * in each time step. + * \param directed Boolean constant, whether to create a directed + * network. + * \return Error code. + * + * \sa \ref igraph_citing_cited_type_game() for a bit more general + * game. + * + * Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of + * vertices and edges, respectively. + */ + +igraph_error_t igraph_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_vector_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed) { + + igraph_vector_int_t edges; + igraph_vector_t cumsum; + igraph_real_t sum, nnval; + igraph_int_t i, j, type; + igraph_int_t pref_len = igraph_vector_size(pref); + + if (igraph_vector_int_size(types) != nodes) { + IGRAPH_ERRORF("Length of types vector (%" IGRAPH_PRId ") must match number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(types), nodes); + } + if (edges_per_step < 0) { + IGRAPH_ERRORF("Number of edges per step should be non-negative, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + edges_per_step); + } + + if (nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + return IGRAPH_SUCCESS; + } + + /* the case of zero-length type vector is caught above, safe to call vector_min here */ + if (igraph_vector_int_min(types) < 0) { + IGRAPH_ERRORF("Types should be non-negative, but found %" IGRAPH_PRId ".", + IGRAPH_EINVAL, igraph_vector_int_min(types)); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + IGRAPH_VECTOR_INIT_FINALLY(&cumsum, 2); + IGRAPH_CHECK(igraph_vector_reserve(&cumsum, nodes + 1)); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, nodes * edges_per_step)); + + /* first node */ + VECTOR(cumsum)[0] = 0; + type = VECTOR(*types)[0]; + if (type >= pref_len) { + goto err_pref_too_short; + } + nnval = VECTOR(*pref)[type]; + if (nnval < 0) { + goto err_pref_neg; + } + sum = VECTOR(cumsum)[1] = nnval; + + for (i = 1; i < nodes; i++) { + for (j = 0; j < edges_per_step; j++) { + igraph_int_t to; + if (sum > 0) { + igraph_vector_binsearch(&cumsum, RNG_UNIF(0, sum), &to); + } else { + to = i + 1; + } + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, to - 1); /* reserved */ + } + type = VECTOR(*types)[i]; + if (type >= pref_len) { + goto err_pref_too_short; + } + nnval = VECTOR(*pref)[type]; + if (nnval < 0) { + goto err_pref_neg; + } + sum += nnval; + igraph_vector_push_back(&cumsum, sum); /* reserved */ + } + + igraph_vector_destroy(&cumsum); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + +err_pref_too_short: + IGRAPH_ERRORF("Preference vector should have length at least %" IGRAPH_PRId " with the given types.", IGRAPH_EINVAL, + igraph_vector_int_max(types) + 1); + +err_pref_neg: + IGRAPH_ERRORF("Preferences should be non-negative, but found %g.", IGRAPH_EINVAL, + igraph_vector_min(pref)); +} + +static void igraph_i_citing_cited_type_game_free(igraph_i_citing_cited_type_game_struct_t *s) { + if (!s->sumtrees) { + return; + } + for (igraph_int_t i = 0; i < s->no; i++) { + igraph_psumtree_destroy(&s->sumtrees[i]); + } + IGRAPH_FREE(s->sumtrees); +} + +/** + * \function igraph_citing_cited_type_game + * \brief Simulates a citation network based on vertex types. + * + * This game is similar to \ref igraph_cited_type_game() but here the + * category of the citing vertex is also considered. + * + * + * An evolving citation network is modeled here, a single vertex and + * its \p edges_per_step citation are added in each time step. The + * odds the a given vertex is cited by the new vertex depends on the + * category of both the citing and the cited vertex and is given in + * the \p pref matrix. The categories of the citing vertex correspond + * to the rows, the categories of the cited vertex to the columns of + * this matrix. I.e. the element in row \c i and column \c j gives the + * probability that a \c j vertex is cited, if the category of the + * citing vertex is \c i. + * + * + * Note that this function might generate networks with multiple edges + * if \p edges_per_step is greater than one. You might want to call + * \ref igraph_simplify() on the result to remove multiple edges. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the network. + * \param types A numeric vector of length \p nodes, containing the + * categories of the vertices. The categories are numbered from + * zero. + * \param pref The preference matrix, a square matrix is required, + * both the number of rows and columns should be the maximum + * element in \p types plus one (types are numbered from zero). + * \param edges_per_step Integer constant, the number of edges to add + * in each time step. + * \param directed Boolean constant, whether to create a directed + * network. + * \return Error code. + * + * Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of + * vertices and edges, respectively. + */ + +igraph_error_t igraph_citing_cited_type_game(igraph_t *graph, igraph_int_t nodes, + const igraph_vector_int_t *types, + const igraph_matrix_t *pref, + igraph_int_t edges_per_step, + igraph_bool_t directed) { + + igraph_vector_int_t edges; + igraph_i_citing_cited_type_game_struct_t str = { 0, NULL }; + igraph_psumtree_t *sumtrees; + igraph_vector_t sums; + igraph_int_t no_of_types; + igraph_int_t i, j, no_of_edges, no_of_edge_endpoints; + + if (igraph_vector_int_size(types) != nodes) { + IGRAPH_ERRORF("Length of types vector (%" IGRAPH_PRId ") not equal to number" + " of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(types), nodes); + } + if (edges_per_step < 0 ) { + IGRAPH_ERRORF("Number of edges per step should be non-negative, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, + edges_per_step); + } + + /* avoid calling vector_max on empty vector */ + no_of_types = nodes == 0 ? 0 : igraph_vector_int_max(types) + 1; + + if (igraph_matrix_ncol(pref) != no_of_types) { + IGRAPH_ERRORF("Number of preference matrix columns (%" IGRAPH_PRId ") not " + "equal to number of types (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_matrix_ncol(pref), + no_of_types); + } + if (igraph_matrix_nrow(pref) != no_of_types) { + IGRAPH_ERRORF("Number of preference matrix rows (%" IGRAPH_PRId ") not " + "equal to number of types (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_matrix_nrow(pref), + no_of_types); + } + + /* return an empty graph if nodes is zero */ + if (nodes == 0) { + return igraph_empty(graph, 0, directed); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + str.sumtrees = sumtrees = IGRAPH_CALLOC(no_of_types, igraph_psumtree_t); + if (!sumtrees) { + IGRAPH_ERROR("Citing-cited type game failed.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_i_citing_cited_type_game_free, &str); + + for (i = 0; i < no_of_types; i++) { + IGRAPH_CHECK(igraph_psumtree_init(&sumtrees[i], nodes)); + str.no++; + } + IGRAPH_VECTOR_INIT_FINALLY(&sums, no_of_types); + + IGRAPH_SAFE_MULT(nodes, edges_per_step, &no_of_edges); + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edge_endpoints); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edge_endpoints)); + + /* First node */ + for (i = 0; i < no_of_types; i++) { + igraph_int_t type = VECTOR(*types)[0]; + if ( MATRIX(*pref, i, type) < 0) { + IGRAPH_ERRORF("Preference matrix contains negative entry: %g.", IGRAPH_EINVAL, MATRIX(*pref, i, type)); + } + IGRAPH_CHECK(igraph_psumtree_update(&sumtrees[i], 0, MATRIX(*pref, i, type))); + VECTOR(sums)[i] = MATRIX(*pref, i, type); + } + + for (i = 1; i < nodes; i++) { + igraph_int_t type = VECTOR(*types)[i]; + igraph_real_t sum = VECTOR(sums)[type]; + for (j = 0; j < edges_per_step; j++) { + igraph_int_t to; + if (sum == 0) { + /* If none of the so-far added nodes have positive weight, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtrees[type], &to, RNG_UNIF(0, sum)); + } + igraph_vector_int_push_back(&edges, i); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + } + + /* add i */ + for (j = 0; j < no_of_types; j++) { + if ( MATRIX(*pref, j, type) < 0) { + IGRAPH_ERRORF("Preference matrix contains negative entry: %g.", IGRAPH_EINVAL, MATRIX(*pref, j, type)); + } + IGRAPH_CHECK(igraph_psumtree_update(&sumtrees[j], i, MATRIX(*pref, j, type))); + VECTOR(sums)[j] += MATRIX(*pref, j, type); + } + } + + igraph_i_citing_cited_type_game_free(&str); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&sums); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/correlated.c b/src/games/correlated.c new file mode 100644 index 0000000..5965406 --- /dev/null +++ b/src/games/correlated.c @@ -0,0 +1,338 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_isomorphism.h" +#include "igraph_qsort.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +/* The "code" of an edge is a single index representing its location in the adjacency matrix, + * More specifically, the relevant parts of the adjacency matrix (i.e. non-diagonal in directed, + * upper triangular in undirected) are column-wise concatenated into an array. The "code" is + * the index in this array. We use floating point numbers for the code, as it can easily + * exceed integers representable on 32 bits. + */ +#define D_CODE(f,t) (((t)==no_of_nodes-1 ? (f) : (t)) * no_of_nodes + (f)) +#define U_CODE(f,t) ((t) * ((t)-1) / 2 + (f)) +#define CODE(f,t) (directed ? D_CODE((double)(f),(double)(t)) : U_CODE((double)(f),(double)(t))) + +/* TODO: Slight speedup may be possible if repeated vertex count queries are avoided. */ +static int code_cmp(void *graph, const void *va, const void *vb) { + const igraph_int_t *a = (const igraph_int_t *) va; + const igraph_int_t *b = (const igraph_int_t *) vb; + const igraph_int_t no_of_nodes = igraph_vcount((igraph_t *) graph); + const igraph_bool_t directed = igraph_is_directed((igraph_t *) graph); + const igraph_real_t codea = CODE(a[0], a[1]); + const igraph_real_t codeb = CODE(b[0], b[1]); + if (codea < codeb) { + return -1; + } else if (codea > codeb) { + return 1; + } else { + return 0; + } +} + +/* Sort an edge vector by edge codes. */ +static void sort_edges(igraph_vector_int_t *edges, const igraph_t *graph) { + igraph_qsort_r(VECTOR(*edges), igraph_vector_int_size(edges) / 2, 2*sizeof(igraph_int_t), (void *) graph, code_cmp); +} + +/** + * \function igraph_correlated_game + * \brief Generates a random graph correlated to an existing graph. + * + * Sample a new graph by perturbing the adjacency matrix of a + * given simple graph and shuffling its vertices. + * + * \param new_graph The new graph to initialize based on an existing graph. + * \param old_graph The original graph, which must be a simple graph. + * \param corr A value in the unit interval [0,1], the target Pearson + * correlation between the adjacency matrices of the original and the + * generated graph (the adjacency matrix being used as a vector). + * \param p The probability of an edge between two vertices. It must in the + * open (0,1) interval. Typically, the density of \p old_graph. + * \param permutation A permutation to apply to the vertices of the + * generated graph. The i-th element of the vector specifies the index + * of the vertex in the \em original graph that will become vertex i in the + * new graph. It can also be a null pointer, in which case the vertices + * will not be permuted. + * \return Error code + * + * \sa \ref igraph_correlated_pair_game() for generating a pair + * of correlated random graphs in one go. + */ +igraph_error_t igraph_correlated_game(igraph_t *new_graph, const igraph_t *old_graph, + igraph_real_t corr, igraph_real_t p, + const igraph_vector_int_t *permutation) { + + const igraph_int_t no_of_nodes = igraph_vcount(old_graph); + const igraph_int_t no_of_edges = igraph_ecount(old_graph); + const igraph_bool_t directed = igraph_is_directed(old_graph); + const igraph_real_t no_of_all = directed ? ((igraph_real_t) no_of_nodes) * (no_of_nodes - 1) : + ((igraph_real_t) no_of_nodes) * (no_of_nodes - 1) / 2; + const igraph_real_t no_of_missing = no_of_all - no_of_edges; + const igraph_real_t q = p + corr * (1 - p); + const igraph_real_t p_del = 1 - q; + const igraph_real_t p_add = ((1 - q) * (p / (1 - p))); + igraph_vector_t add, delete; + igraph_vector_int_t edges, newedges; + igraph_vector_int_t inverted_permutation; + igraph_real_t last; + igraph_int_t p_e = 0, p_a = 0, p_d = 0; + igraph_int_t no_add, no_del; + igraph_real_t next_e, next_a, next_d; + igraph_bool_t simple; + + if (corr < 0 || corr > 1) { + IGRAPH_ERRORF("Correlation must be in [0,1] in correlated Erdos-Renyi game, got %g.", + IGRAPH_EINVAL, corr); + } + if (p <= 0 || p >= 1) { + IGRAPH_ERRORF("Edge probability must be in (0,1) in correlated Erdos-Renyi game, got %g.", + IGRAPH_EINVAL, p); + } + if (permutation) { + if (igraph_vector_int_size(permutation) != no_of_nodes) { + IGRAPH_ERROR("Invalid permutation length in correlated Erdos-Renyi game.", + IGRAPH_EINVAL); + } + } + IGRAPH_CHECK(igraph_is_simple(old_graph, &simple, IGRAPH_DIRECTED)); + if (! simple) { + IGRAPH_ERROR("The original graph must be simple for correlated Erdos-Renyi game.", + IGRAPH_EINVAL); + } + + /* Special cases */ + + if (corr == 0) { + return igraph_erdos_renyi_game_gnp(new_graph, no_of_nodes, p, directed, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + } + if (corr == 1) { + /* We don't copy, because we don't need the attributes.... */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ false)); + if (permutation) { + const igraph_int_t newec = igraph_vector_int_size(&edges); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inverted_permutation, no_of_nodes); + /* Also checks that 'permutation' is valid: */ + IGRAPH_CHECK(igraph_invert_permutation(permutation, &inverted_permutation)); + + for (igraph_int_t i = 0; i < newec; i++) { + igraph_int_t tmp = VECTOR(edges)[i]; + VECTOR(edges)[i] = VECTOR(inverted_permutation)[tmp]; + } + + igraph_vector_int_destroy(&inverted_permutation); + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_CHECK(igraph_create(new_graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&newedges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&add, 0); + IGRAPH_VECTOR_INIT_FINALLY(&delete, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0)); + /* The sampling method used is analogous to the one in igraph_erdos_renyi_game_gnp(), + * and assumes that the edge list of the old graph is in order of increasing "codes". + * Even IGRAPH_EDGEORDER_TO does not guarantee this, therefore we sort explicitly. + */ + sort_edges(&edges, old_graph); + + if (p_del > 0) { + last = RNG_GEOM(p_del); + while (last < no_of_edges) { + IGRAPH_CHECK(igraph_vector_push_back(&delete, last)); + last += RNG_GEOM(p_del); + last += 1; + } + } + no_del = igraph_vector_size(&delete); + + if (p_add > 0) { + last = RNG_GEOM(p_add); + while (last < no_of_missing) { + IGRAPH_CHECK(igraph_vector_push_back(&add, last)); + last += RNG_GEOM(p_add); + last += 1; + } + } + no_add = igraph_vector_size(&add); + + /* Now we are merging the original edges, the edges that are removed, + and the new edges. We have the following pointers: + - p_a: the next edge to add + - p_d: the next edge to delete + - p_e: the next original edge + - next_e: the code of the next edge in 'edges' + - next_a: the code of the next edge to add + - next_d: the code of the next edge to delete */ + +#define CODEE() (CODE(VECTOR(edges)[2*p_e], VECTOR(edges)[2*p_e+1])) + + /* First we (re)code the edges to delete */ + + for (igraph_int_t i = 0; i < no_del; i++) { + igraph_int_t td = VECTOR(delete)[i]; + igraph_int_t from = VECTOR(edges)[2 * td]; + igraph_int_t to = VECTOR(edges)[2 * td + 1]; + VECTOR(delete)[i] = CODE(from, to); + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&newedges, + (no_of_edges - no_del + no_add) * 2)); + + /* Now we can do the merge. Additional edges are tricky, because + the code must be shifted by the edges in the original graph. */ + +#define UPD_E() \ + { if (p_e < no_of_edges) { next_e=CODEE(); } else { next_e = IGRAPH_INFINITY; } } +#define UPD_A() \ + { if (p_a < no_add) { \ + next_a = VECTOR(add)[p_a] + p_e; } else { next_a = IGRAPH_INFINITY; } } +#define UPD_D() \ + { if (p_d < no_del) { \ + next_d = VECTOR(delete)[p_d]; } else { next_d = IGRAPH_INFINITY; } } + + UPD_E(); UPD_A(); UPD_D(); + + while (next_e != IGRAPH_INFINITY || next_a != IGRAPH_INFINITY || next_d != IGRAPH_INFINITY) { + IGRAPH_ALLOW_INTERRUPTION(); + if (next_e <= next_a && next_e < next_d) { + + /* keep an edge */ + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, VECTOR(edges)[2 * p_e])); + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, VECTOR(edges)[2 * p_e + 1])); + p_e ++; UPD_E(); UPD_A() + + } else if (next_e <= next_a && next_e == next_d) { + + /* delete an edge */ + p_e ++; UPD_E(); UPD_A(); + p_d++; UPD_D(); + + } else { + + /* add an edge */ + igraph_int_t to, from; + IGRAPH_ASSERT(isfinite(next_a)); + if (directed) { + to = trunc(next_a / no_of_nodes); + from = next_a - ((igraph_real_t)to) * no_of_nodes; + if (from == to) { + to = no_of_nodes - 1; + } + } else { + to = trunc((sqrt(8 * next_a + 1) + 1) / 2); + from = next_a - (((igraph_real_t)to) * (to - 1)) / 2; + } + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&newedges, to)); + p_a++; UPD_A(); + + } + } + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&add); + igraph_vector_destroy(&delete); + IGRAPH_FINALLY_CLEAN(3); + + if (permutation) { + igraph_int_t newec = igraph_vector_int_size(&newedges); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inverted_permutation, no_of_nodes); + /* Also checks that 'permutation' is valid: */ + IGRAPH_CHECK(igraph_invert_permutation(permutation, &inverted_permutation)); + + for (igraph_int_t i = 0; i < newec; i++) { + igraph_int_t tmp = VECTOR(newedges)[i]; + VECTOR(newedges)[i] = VECTOR(inverted_permutation)[tmp]; + } + + igraph_vector_int_destroy(&inverted_permutation); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(new_graph, &newedges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#undef D_CODE +#undef U_CODE +#undef CODE +#undef CODEE +#undef UPD_E +#undef UPD_A +#undef UPD_D + +/** + * \function igraph_correlated_pair_game + * \brief Generates pairs of correlated random graphs. + * + * Sample two random graphs, with given correlation. + * + * \param graph1 The first graph will be stored here. + * \param graph2 The second graph will be stored here. + * \param n The number of vertices in both graphs. + * \param corr A scalar in the unit interval, the target Pearson + * correlation between the adjacency matrices of the original the + * generated graph (the adjacency matrix being used as a vector). + * \param p A numeric scalar, the probability of an edge between two + * vertices, it must in the open (0,1) interval. + * \param directed Whether to generate directed graphs. + * \param permutation A permutation to apply to the vertices of the + * generated graph. The i-th element of the vector specifies the index + * of the vertex in the \em first graph that will become vertex i in the + * second graph. It can also be a null pointer, in which case the vertices + * will not be permuted. + * \return Error code + * + * \sa \ref igraph_correlated_game() for generating a correlated pair + * to a given graph. + */ +igraph_error_t igraph_correlated_pair_game(igraph_t *graph1, igraph_t *graph2, + igraph_int_t n, igraph_real_t corr, igraph_real_t p, + igraph_bool_t directed, + const igraph_vector_int_t *permutation) { + + IGRAPH_CHECK(igraph_erdos_renyi_game_gnp(graph1, n, p, directed, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED)); + IGRAPH_CHECK(igraph_correlated_game(graph2, graph1, corr, p, permutation)); + return IGRAPH_SUCCESS; +} diff --git a/src/games/degree_sequence.c b/src/games/degree_sequence.c new file mode 100644 index 0000000..5addbff --- /dev/null +++ b/src/games/degree_sequence.c @@ -0,0 +1,864 @@ +/* + igraph library. + Copyright (C) 2003-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_games.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset_list.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_graphicality.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_operators.h" +#include "igraph_random.h" +#include "igraph_vector_ptr.h" + +#include "core/interruption.h" +#include "core/set.h" +#include "games/degree_sequence_vl/degree_sequence_vl.h" +#include "math/safe_intop.h" + +static igraph_error_t configuration( + igraph_t *graph, + const igraph_vector_int_t *out_seq, + const igraph_vector_int_t *in_seq) { + + const igraph_bool_t directed = (in_seq != NULL); + igraph_int_t outsum = 0, insum = 0; + igraph_bool_t graphical; + igraph_int_t no_of_nodes, no_of_edges; + igraph_int_t *bag1, *bag2; + igraph_int_t bagp1 = 0, bagp2 = 0; + igraph_vector_int_t edges; + + IGRAPH_CHECK(igraph_is_graphical(out_seq, in_seq, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, &graphical)); + if (!graphical) { + IGRAPH_ERROR(in_seq ? "No directed graph can realize the given degree sequences." : + "No undirected graph can realize the given degree sequence.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(out_seq, &outsum)); + if (directed) { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(in_seq, &insum)); + } + + no_of_nodes = igraph_vector_int_size(out_seq); + no_of_edges = directed ? outsum : outsum / 2; + + bag1 = IGRAPH_CALLOC(outsum, igraph_int_t); + IGRAPH_CHECK_OOM(bag1, "Insufficient memory for sampling from configuration model."); + IGRAPH_FINALLY(igraph_free, bag1); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + for (igraph_int_t j = 0; j < VECTOR(*out_seq)[i]; j++) { + bag1[bagp1++] = i; + } + } + if (directed) { + bag2 = IGRAPH_CALLOC(insum, igraph_int_t); + IGRAPH_CHECK_OOM(bag2, "Insufficient memory for sampling from configuration model."); + IGRAPH_FINALLY(igraph_free, bag2); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + for (igraph_int_t j = 0; j < VECTOR(*in_seq)[i]; j++) { + bag2[bagp2++] = i; + } + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + + if (directed) { + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_int_t from = RNG_INTEGER(0, bagp1 - 1); + igraph_int_t to = RNG_INTEGER(0, bagp2 - 1); + igraph_vector_int_push_back(&edges, bag1[from]); /* safe, already reserved */ + igraph_vector_int_push_back(&edges, bag2[to]); /* ditto */ + bag1[from] = bag1[bagp1 - 1]; + bag2[to] = bag2[bagp2 - 1]; + bagp1--; bagp2--; + } + } else { + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_int_t from = RNG_INTEGER(0, bagp1 - 1); + igraph_int_t to; + igraph_vector_int_push_back(&edges, bag1[from]); /* safe, already reserved */ + bag1[from] = bag1[bagp1 - 1]; + bagp1--; + to = RNG_INTEGER(0, bagp1 - 1); + igraph_vector_int_push_back(&edges, bag1[to]); /* ditto */ + bag1[to] = bag1[bagp1 - 1]; + bagp1--; + } + } + + IGRAPH_FREE(bag1); + IGRAPH_FINALLY_CLEAN(1); + if (directed) { + IGRAPH_FREE(bag2); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t fast_heur_undirected( + igraph_t *graph, + const igraph_vector_int_t *seq) { + + igraph_vector_int_t stubs; + igraph_vector_int_t *neis; + igraph_vector_int_t residual_degrees; + igraph_set_t incomplete_vertices; + igraph_adjlist_t al; + igraph_bool_t finished, failed; + igraph_int_t from, to, dummy; + igraph_int_t i, j, k; + igraph_int_t no_of_nodes, outsum = 0; + igraph_bool_t graphical; + int iter = 0; + + IGRAPH_CHECK(igraph_is_graphical(seq, 0, IGRAPH_SIMPLE_SW, &graphical)); + if (!graphical) { + IGRAPH_ERROR("No simple undirected graph can realize the given degree sequence.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(seq, &outsum)); + no_of_nodes = igraph_vector_int_size(seq); + + /* Allocate required data structures */ + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INT_INIT_FINALLY(&stubs, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&stubs, outsum)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&residual_degrees, no_of_nodes); + IGRAPH_CHECK(igraph_set_init(&incomplete_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_vertices); + + /* Outer loop; this will try to construct a graph several times from scratch + * until it finally succeeds. */ + finished = false; + while (!finished) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + + /* Be optimistic :) */ + failed = false; + + /* Clear the adjacency list to get rid of the previous attempt (if any) */ + igraph_adjlist_clear(&al); + + /* Initialize the residual degrees from the degree sequence */ + IGRAPH_CHECK(igraph_vector_int_update(&residual_degrees, seq)); + + /* While there are some unconnected stubs left... */ + while (!finished && !failed) { + /* Construct the initial stub vector */ + igraph_vector_int_clear(&stubs); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(residual_degrees)[i]; j++) { + igraph_vector_int_push_back(&stubs, i); /* reserved */ + } + } + + /* Clear the skipped stub counters and the set of incomplete vertices */ + igraph_vector_int_null(&residual_degrees); + igraph_set_clear(&incomplete_vertices); + + /* Shuffle the stubs in-place */ + igraph_vector_int_shuffle(&stubs); + + /* Connect the stubs where possible */ + k = igraph_vector_int_size(&stubs); + for (i = 0; i < k; ) { + from = VECTOR(stubs)[i++]; + to = VECTOR(stubs)[i++]; + + if (from > to) { + dummy = from; from = to; to = dummy; + } + + neis = igraph_adjlist_get(&al, from); + if (from == to || igraph_vector_int_binsearch(neis, to, &j)) { + /* Edge exists already */ + VECTOR(residual_degrees)[from]++; + VECTOR(residual_degrees)[to]++; + IGRAPH_CHECK(igraph_set_add(&incomplete_vertices, from)); + IGRAPH_CHECK(igraph_set_add(&incomplete_vertices, to)); + } else { + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, j, to)); + } + } + + finished = igraph_set_empty(&incomplete_vertices); + + if (!finished) { + /* We are not done yet; check if the remaining stubs are feasible. This + * is done by enumerating all possible pairs and checking whether at + * least one feasible pair is found. */ + i = 0; + failed = true; + while (failed && igraph_set_iterate(&incomplete_vertices, &i, &from)) { + j = 0; + while (igraph_set_iterate(&incomplete_vertices, &j, &to)) { + if (from == to) { + /* This is used to ensure that each pair is checked once only */ + break; + } + if (from > to) { + dummy = from; from = to; to = dummy; + } + neis = igraph_adjlist_get(&al, from); + if (!igraph_vector_int_binsearch(neis, to, 0)) { + /* Found a suitable pair, so we can continue */ + failed = false; + break; + } + } + } + } + } + } + + /* Clean up */ + igraph_set_destroy(&incomplete_vertices); + igraph_vector_int_destroy(&residual_degrees); + igraph_vector_int_destroy(&stubs); + IGRAPH_FINALLY_CLEAN(3); + + /* Create the graph. We cannot use IGRAPH_ALL here for undirected graphs + * because we did not add edges in both directions in the adjacency list. + * We will use igraph_to_undirected in an extra step. */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, 1)); + IGRAPH_CHECK(igraph_to_undirected(graph, IGRAPH_TO_UNDIRECTED_EACH, 0)); + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t fast_heur_directed( + igraph_t *graph, + const igraph_vector_int_t *out_seq, + const igraph_vector_int_t *in_seq) { + + igraph_adjlist_t al; + igraph_bool_t deg_seq_ok, failed, finished; + igraph_vector_int_t in_stubs; + igraph_vector_int_t out_stubs; + igraph_vector_int_t *neis; + igraph_vector_int_t residual_in_degrees, residual_out_degrees; + igraph_set_t incomplete_in_vertices, incomplete_out_vertices; + igraph_int_t from, to; + igraph_int_t i, j, k; + igraph_int_t no_of_nodes, outsum; + int iter = 0; + + IGRAPH_CHECK(igraph_is_graphical(out_seq, in_seq, IGRAPH_SIMPLE_SW, °_seq_ok)); + if (!deg_seq_ok) { + IGRAPH_ERROR("No simple directed graph can realize the given degree sequence.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(out_seq, &outsum)); + no_of_nodes = igraph_vector_int_size(out_seq); + + /* Allocate required data structures */ + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_stubs, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&out_stubs, outsum)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_stubs, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&in_stubs, outsum)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&residual_out_degrees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&residual_in_degrees, no_of_nodes); + IGRAPH_CHECK(igraph_set_init(&incomplete_out_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_out_vertices); + IGRAPH_CHECK(igraph_set_init(&incomplete_in_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_in_vertices); + + /* Outer loop; this will try to construct a graph several times from scratch + * until it finally succeeds. */ + finished = false; + while (!finished) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + + /* Be optimistic :) */ + failed = false; + + /* Clear the adjacency list to get rid of the previous attempt (if any) */ + igraph_adjlist_clear(&al); + + /* Initialize the residual degrees from the degree sequences */ + IGRAPH_CHECK(igraph_vector_int_update(&residual_out_degrees, out_seq)); + IGRAPH_CHECK(igraph_vector_int_update(&residual_in_degrees, in_seq)); + + /* While there are some unconnected stubs left... */ + while (!finished && !failed) { + /* Construct the initial stub vectors */ + igraph_vector_int_clear(&out_stubs); + igraph_vector_int_clear(&in_stubs); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(residual_out_degrees)[i]; j++) { + igraph_vector_int_push_back(&out_stubs, i); /* reserved */ + } + for (j = 0; j < VECTOR(residual_in_degrees)[i]; j++) { + igraph_vector_int_push_back(&in_stubs, i); /* reserved */ + } + } + + /* Clear the skipped stub counters and the set of incomplete vertices */ + igraph_vector_int_null(&residual_out_degrees); + igraph_vector_int_null(&residual_in_degrees); + igraph_set_clear(&incomplete_out_vertices); + igraph_set_clear(&incomplete_in_vertices); + + /* Shuffle the out-stubs in-place */ + igraph_vector_int_shuffle(&out_stubs); + + /* Connect the stubs where possible */ + k = igraph_vector_int_size(&out_stubs); + for (i = 0; i < k; i++) { + from = VECTOR(out_stubs)[i]; + to = VECTOR(in_stubs)[i]; + + neis = igraph_adjlist_get(&al, from); + if (from == to || igraph_vector_int_binsearch(neis, to, &j)) { + /* Edge exists already */ + VECTOR(residual_out_degrees)[from]++; + VECTOR(residual_in_degrees)[to]++; + IGRAPH_CHECK(igraph_set_add(&incomplete_out_vertices, from)); + IGRAPH_CHECK(igraph_set_add(&incomplete_in_vertices, to)); + } else { + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, j, to)); + } + } + + /* Are we finished? */ + finished = igraph_set_empty(&incomplete_out_vertices); + + if (!finished) { + /* We are not done yet; check if the remaining stubs are feasible. This + * is done by enumerating all possible pairs and checking whether at + * least one feasible pair is found. */ + i = 0; + failed = true; + while (failed && igraph_set_iterate(&incomplete_out_vertices, &i, &from)) { + j = 0; + while (igraph_set_iterate(&incomplete_in_vertices, &j, &to)) { + neis = igraph_adjlist_get(&al, from); + if (from != to && !igraph_vector_int_binsearch(neis, to, 0)) { + /* Found a suitable pair, so we can continue */ + failed = false; + break; + } + } + } + } + } + } + + /* Clean up */ + igraph_set_destroy(&incomplete_in_vertices); + igraph_set_destroy(&incomplete_out_vertices); + igraph_vector_int_destroy(&residual_in_degrees); + igraph_vector_int_destroy(&residual_out_degrees); + igraph_vector_int_destroy(&in_stubs); + igraph_vector_int_destroy(&out_stubs); + IGRAPH_FINALLY_CLEAN(6); + + /* Create the graph */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, true)); + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* swap two elements of a vector_int */ +#define SWAP_INT_ELEM(vec, i, j) \ + { \ + igraph_int_t temp; \ + temp = VECTOR(vec)[i]; \ + VECTOR(vec)[i] = VECTOR(vec)[j]; \ + VECTOR(vec)[j] = temp; \ + } + +/* Uses a set to check for multi-edges. Efficient for larger graphs, frugal with memory. */ +static igraph_error_t configuration_simple_undirected_set( + const igraph_vector_int_t *degseq, + igraph_vector_int_t *stubs, + igraph_int_t vcount, igraph_int_t stub_count) { + + const igraph_int_t ecount = stub_count / 2; + igraph_vector_ptr_t adjlist; + int iter = 0; + + /* Build an adjacency list in terms of sets; used to check for multi-edges. */ + IGRAPH_CHECK(igraph_vector_ptr_init(&adjlist, vcount)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&adjlist, igraph_set_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &adjlist); + for (igraph_int_t i = 0; i < vcount; ++i) { + igraph_set_t *set = IGRAPH_CALLOC(1, igraph_set_t); + IGRAPH_CHECK_OOM(set, "Insufficient memory for configuration model (simple graphs)."); + IGRAPH_CHECK(igraph_set_init(set, 0)); + VECTOR(adjlist)[i] = set; + IGRAPH_CHECK(igraph_set_reserve(set, VECTOR(*degseq)[i])); + } + + for (;;) { + igraph_bool_t success = true; + + /* Shuffle stubs vector with Fisher-Yates and check for self-loops and multi-edges as we go. */ + for (igraph_int_t i = 0; i < ecount; ++i) { + igraph_int_t k, from, to; + + k = RNG_INTEGER(2*i, stub_count-1); + SWAP_INT_ELEM(*stubs, 2*i, k); + + k = RNG_INTEGER(2*i+1, stub_count-1); + SWAP_INT_ELEM(*stubs, 2*i+1, k); + + from = VECTOR(*stubs)[2*i]; + to = VECTOR(*stubs)[2*i+1]; + + /* self-loop, fail */ + if (from == to) { + success = false; + break; + } + + /* multi-edge, fail */ + if (igraph_set_contains((igraph_set_t *) VECTOR(adjlist)[to], from)) { + success = false; + break; + } + + /* sets are already reserved */ + igraph_set_add((igraph_set_t *) VECTOR(adjlist)[to], from); + igraph_set_add((igraph_set_t *) VECTOR(adjlist)[from], to); + } + + if (success) { + break; + } + + /* Clear adjacency list. */ + for (igraph_int_t j = 0; j < vcount; ++j) { + igraph_set_clear((igraph_set_t *) VECTOR(adjlist)[j]); + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + } + + igraph_vector_ptr_destroy_all(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Uses a bitset to check for multi-edges. Efficient for smaller graphs. */ +static igraph_error_t configuration_simple_undirected_bitset( + igraph_vector_int_t *stubs, + igraph_int_t vcount, igraph_int_t stub_count) { + + const igraph_int_t ecount = stub_count / 2; + igraph_bitset_list_t adjlist; + int iter = 0; + + /* Build an adjacency list in terms of bitsets; used to check for multi-edges. */ + IGRAPH_BITSET_LIST_INIT_FINALLY(&adjlist, vcount); + for (igraph_int_t i = 0; i < vcount; ++i) { + IGRAPH_CHECK(igraph_bitset_resize(igraph_bitset_list_get_ptr(&adjlist, i), vcount)); + } + + for (;;) { + igraph_bool_t success = true; + + /* Shuffle stubs vector with Fisher-Yates and check for self-loops and multi-edges as we go. */ + for (igraph_int_t i = 0; i < ecount; ++i) { + igraph_int_t k, from, to; + + k = RNG_INTEGER(2*i, stub_count-1); + SWAP_INT_ELEM(*stubs, 2*i, k); + + k = RNG_INTEGER(2*i+1, stub_count-1); + SWAP_INT_ELEM(*stubs, 2*i+1, k); + + from = VECTOR(*stubs)[2*i]; + to = VECTOR(*stubs)[2*i+1]; + + /* self-loop, fail */ + if (from == to) { + success = false; + break; + } + + /* multi-edge, fail */ + if (IGRAPH_BIT_TEST(*igraph_bitset_list_get_ptr(&adjlist, to), from)) { + success = false; + break; + } + + /* sets are already reserved */ + IGRAPH_BIT_SET(*igraph_bitset_list_get_ptr(&adjlist, to), from); + IGRAPH_BIT_SET(*igraph_bitset_list_get_ptr(&adjlist, from), to); + } + + if (success) { + break; + } + + /* Clear adjacency list. */ + for (igraph_int_t j = 0; j < vcount; ++j) { + igraph_bitset_null(igraph_bitset_list_get_ptr(&adjlist, j)); + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + } + + igraph_bitset_list_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t configuration_simple_undirected( + igraph_t *graph, + const igraph_vector_int_t *degseq) { + + igraph_vector_int_t stubs; + igraph_bool_t graphical; + igraph_int_t vcount, stub_count; + + IGRAPH_CHECK(igraph_is_graphical(degseq, NULL, IGRAPH_SIMPLE_SW, &graphical)); + if (!graphical) { + IGRAPH_ERROR("No simple undirected graph can realize the given degree sequence.", IGRAPH_EINVAL); + } + + stub_count = igraph_vector_int_sum(degseq); + vcount = igraph_vector_int_size(degseq); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&stubs, stub_count); + + /* Fill stubs vector. */ + { + igraph_int_t k = 0; + for (igraph_int_t i = 0; i < vcount; ++i) { + igraph_int_t deg = VECTOR(*degseq)[i]; + for (igraph_int_t j = 0; j < deg; ++j) { + VECTOR(stubs)[k++] = i; + } + } + } + + /* Tradeoff between speed and memory: Choose set vs bitset implementation. */ + if (vcount > 1024) { + IGRAPH_CHECK(configuration_simple_undirected_set(degseq, &stubs, vcount, stub_count)); + } else { + IGRAPH_CHECK(configuration_simple_undirected_bitset(&stubs, vcount, stub_count)); + } + + IGRAPH_CHECK(igraph_create(graph, &stubs, vcount, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&stubs); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t configuration_simple_directed( + igraph_t *graph, + const igraph_vector_int_t *out_deg, + const igraph_vector_int_t *in_deg) { + + igraph_vector_int_t out_stubs, in_stubs; + igraph_vector_int_t edges; + igraph_vector_int_t vertex_done; + igraph_bool_t graphical; + igraph_int_t vcount, ecount; + int iter = 0; + + IGRAPH_CHECK(igraph_is_graphical(out_deg, in_deg, IGRAPH_SIMPLE_SW, &graphical)); + if (!graphical) { + IGRAPH_ERROR("No simple directed graph can realize the given degree sequence", IGRAPH_EINVAL); + } + + ecount = igraph_vector_int_sum(out_deg); + vcount = igraph_vector_int_size(out_deg); + + /* In the directed case, checking for multi-edges can be done efficiently for as + * long as only one of the in-/out-stub vectors is shuffled. Here we shuffle + * the out-stub vector, keeping in the in-stubs in their original order, thus + * processing vertices to connect *to* in order. For each vertex v, we mark + * vertex_done[v] to indicate that it has already been connected *to* the + * current vertex. When moving on to the next vertex, instead of nulling the + * vertex_done vector, we simply change the mark we use. */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2 * ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_stubs, ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_stubs, ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_done, vcount); + + /* Fill in- and out-stubs vectors. */ + { + igraph_int_t k = 0, l = 0; + for (igraph_int_t i = 0; i < vcount; ++i) { + igraph_int_t dout, din; + + dout = VECTOR(*out_deg)[i]; + for (igraph_int_t j = 0; j < dout; ++j) { + VECTOR(out_stubs)[k++] = i; + } + + din = VECTOR(*in_deg)[i]; + for (igraph_int_t j = 0; j < din; ++j) { + VECTOR(in_stubs)[l++] = i; + } + } + } + + igraph_int_t vertex_done_mark = 1; + + for (;;) { + igraph_bool_t success = true; + igraph_int_t previous_to = -1; + + /* Shuffle out-stubs vector with Fisher-Yates and check for self-loops and multi-edges as we go. */ + for (igraph_int_t i = 0; i < ecount; ++i) { + igraph_int_t k, from, to; + + k = RNG_INTEGER(i, ecount-1); + SWAP_INT_ELEM(out_stubs, i, k); + + from = VECTOR(out_stubs)[i]; + to = VECTOR(in_stubs)[i]; + + /* self-loop, fail */ + if (to == from) { + success = false; + break; + } + + /* have we moved on to the next vertex? */ + if (to != previous_to) { + vertex_done_mark++; + previous_to = to; + } + + /* multi-edge, fail */ + if (VECTOR(vertex_done)[from] == vertex_done_mark) { + success = false; + break; + } + + VECTOR(vertex_done)[from] = vertex_done_mark; + } + + if (success) { + break; + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + } + + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(edges)[2*i] = VECTOR(out_stubs)[i]; + VECTOR(edges)[2*i+1] = VECTOR(in_stubs)[i]; + } + + igraph_vector_int_destroy(&vertex_done); + igraph_vector_int_destroy(&in_stubs); + igraph_vector_int_destroy(&out_stubs); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, vcount, IGRAPH_DIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#undef SWAP_INT_ELEM + +igraph_error_t edge_switching( + igraph_t *graph, + const igraph_vector_int_t *out_seq, + const igraph_vector_int_t *in_seq) { + + IGRAPH_CHECK(igraph_realize_degree_sequence(graph, out_seq, in_seq, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_INDEX)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_rewire(graph, 10 * igraph_ecount(graph), IGRAPH_SIMPLE_SW, NULL)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_degree_sequence_game + * \brief Generates a random graph with a given degree sequence. + * + * This function generates random graphs with a prescribed degree sequence. + * Several sampling methods are available, which respect different constraints + * (simple graph or multigraphs, connected graphs, etc.), and provide different + * tradeoffs between performance and unbiased sampling. See Section 2.1 of + * Horvát and Modes (2021) for an overview of sampling techniques for graphs + * with fixed degrees. + * + * + * References: + * + * + * Fabien Viger, and Matthieu Latapy: + * Efficient and Simple Generation of Random Simple Connected Graphs with Prescribed Degree Sequence, + * Journal of Complex Networks 4, no. 1, pp. 15–37 (2015). + * https://doi.org/10.1093/comnet/cnv013. + * + * + * Szabolcs Horvát, and Carl D Modes: + * Connectedness Matters: Construction and Exact Random Sampling of Connected Networks, + * Journal of Physics: Complexity 2, no. 1, pp. 015008 (2021). + * https://doi.org/10.1088/2632-072x/abced5. + * + * \param graph Pointer to an uninitialized graph object. + * \param out_degrees A vector of integers specifying the degree sequence for + * undirected graphs or the out-degree sequence for directed graphs. + * \param in_degrees A vector of integers specifying the in-degree sequence for + * directed graphs. For undirected graphs, it must be \c NULL. + * \param method The method to generate the graph. Possible values: + * \clist + * \cli IGRAPH_DEGSEQ_CONFIGURATION + * This method implements the configuration model. + * For undirected graphs, it puts all vertex IDs in a bag + * such that the multiplicity of a vertex in the bag is the same as + * its degree. Then it draws pairs from the bag until the bag becomes + * empty. This method may generate both loop (self) edges and multiple + * edges. For directed graphs, the algorithm is basically the same, + * but two separate bags are used for the in- and out-degrees. + * Undirected graphs are generated with probability proportional to + * (\prod_{i<j} A_{ij} ! \prod_i A_{ii} !!)^{-1}, + * where \c A denotes the adjacency matrix and !! denotes + * the double factorial. Here \c A is assumed to have twice the number of + * self-loops on its diagonal. + * The corresponding expression for directed graphs is + * (\prod_{i,j} A_{ij}!)^{-1}. + * Thus the probability of all simple graphs (which only have 0s and 1s + * in the adjacency matrix) is the same, while that of + * non-simple ones depends on their edge and self-loop multiplicities. + * \cli IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE + * This method is identical to \c IGRAPH_DEGSEQ_CONFIGURATION, but if the + * generated graph is not simple, it rejects it and re-starts the + * generation. It generates all simple graphs with the same probability. + * \cli IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE + * This method generates simple graphs. + * It is similar to \c IGRAPH_DEGSEQ_CONFIGURATION + * but tries to avoid multiple and loop edges and restarts the + * generation from scratch if it gets stuck. It can generate all simple + * realizations of a degree sequence, but it is not guaranteed + * to sample them uniformly. This method is relatively fast and it will + * eventually succeed if the provided degree sequence is graphical, + * but there is no upper bound on the number of iterations. + * \cli IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE + * This is an MCMC sampler based on degree-preserving edge switches. + * It generates simple undirected or directed graphs. + * It uses \ref igraph_realize_degree_sequence() to construct an initial + * graph, then rewires it using \ref igraph_rewire(). + * \cli IGRAPH_DEGSEQ_VL + * This method samples undirected \em connected graphs approximately + * uniformly. It is a Monte Carlo method based on degree-preserving + * edge switches. + * This generator should be favoured if undirected and connected + * graphs are to be generated and execution time is not a concern. + * igraph uses the original implementation of Fabien Viger; for the algorithm, + * see https://www-complexnetworks.lip6.fr/~latapy/FV/generation.html + * and the paper https://arxiv.org/abs/cs/0502085 + * \endclist + * \return Error code: + * \c IGRAPH_ENOMEM: there is not enough + * memory to perform the operation. + * \c IGRAPH_EINVAL: invalid method parameter, or + * invalid in- and/or out-degree vectors. The degree vectors + * should be non-negative, \p out_deg should sum + * up to an even integer for undirected graphs; the length + * and sum of \p out_deg and + * \p in_deg + * should match for directed graphs. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges + * for \c IGRAPH_DEGSEQ_CONFIGURATION and \c IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE. + * The time complexity of the other modes is not known. + * + * \sa \ref igraph_is_graphical() to determine if there exist graphs with a certain + * degree sequence; \ref igraph_erdos_renyi_game_gnm() to generate graphs with a + * fixed number of edges, without any degree constraints; \ref igraph_chung_lu_game() + * and \ref igraph_static_fitness_game() to sample random graphs with a prescribed + * \em expected degree sequence (but variable actual degrees); + * \ref igraph_realize_degree_sequence() and \ref igraph_realize_bipartite_degree_sequence() + * to generate a single (non-random) graph with given degrees. + * + * \example examples/simple/igraph_degree_sequence_game.c + */ + +igraph_error_t igraph_degree_sequence_game( + igraph_t *graph, + const igraph_vector_int_t *out_degrees, + const igraph_vector_int_t *in_degrees, + igraph_degseq_t method) { + + switch (method) { + case IGRAPH_DEGSEQ_CONFIGURATION: + return configuration(graph, out_degrees, in_degrees); + + case IGRAPH_DEGSEQ_VL: + return igraph_i_degree_sequence_game_vl(graph, out_degrees, in_degrees); + + case IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE: + if (! in_degrees) { + return fast_heur_undirected(graph, out_degrees); + } else { + return fast_heur_directed(graph, out_degrees, in_degrees); + } + + case IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE: + if (! in_degrees) { + return configuration_simple_undirected(graph, out_degrees); + } else { + return configuration_simple_directed(graph, out_degrees, in_degrees); + } + + case IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE: + return edge_switching(graph, out_degrees, in_degrees); + + default: + IGRAPH_ERROR("Invalid degree sequence game method.", IGRAPH_EINVAL); + } +} diff --git a/src/games/degree_sequence_vl/degree_sequence_vl.h b/src/games/degree_sequence_vl/degree_sequence_vl.h new file mode 100644 index 0000000..9daa8f7 --- /dev/null +++ b/src/games/degree_sequence_vl/degree_sequence_vl.h @@ -0,0 +1,34 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GAMES_DEGREE_SEQUENCE_VL_H +#define IGRAPH_GAMES_DEGREE_SEQUENCE_VL_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_degree_sequence_game_vl(igraph_t *graph, + const igraph_vector_int_t *out_seq, + const igraph_vector_int_t *in_seq); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_GAMES_DEGREE_SEQUENCE_VL_H */ diff --git a/src/games/degree_sequence_vl/gengraph_definitions.h b/src/games/degree_sequence_vl/gengraph_definitions.h new file mode 100644 index 0000000..faf551c --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_definitions.h @@ -0,0 +1,195 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#include +#include +#include + +#include "igraph_types.h" +#include "internal/hacks.h" + +namespace gengraph { + +// Max line size in files +#define FBUFF_SIZE 1000000 + +// disable lousy VC++ warnings +#ifdef _ATL_VER_ + #pragma warning(disable : 4127) +#endif //_ATL_VER_ + +// Verbose +#define VERBOSE_NONE 0 +#define VERBOSE_SOME 1 +#define VERBOSE_LOTS 2 +int VERBOSE(); +void SET_VERBOSE(int v); + +// Random number generator +void my_srandom(int); +long my_random(); +int my_binomial(double pp, int n); +double my_random01(); // (0,1] + +#define MY_RAND_MAX 0x7FFFFFFF + +//inline double round(double x) throw () { return (floor(0.5+x)); } + +// Min & Max +#ifndef min + #define defmin(type) inline type min(type a, type b) { return ab ? a : b; } + defmax(int) + defmax(double) + defmax(unsigned long) + defmax(long long) +#endif //max + +// Debug definitions +//#define PERFORMANCE_MONITOR +//#define OPT_ISOLATED + +// Max Int +#ifndef MAX_INT + #define MAX_INT 0x7FFFFFFF +#endif //MAX_INT + +//Edge type +typedef struct { + igraph_int_t from; + igraph_int_t to; +} edge; + +// Tag Int +#define TAG_INT 0x40000000 + +// Oldies .... +#define S_VECTOR_RAW + +//********************* +// Routine definitions +//********************* + +/* log(1+x) +inline double logp(double x) { + if(fabs(x)<1e-6) return x+0.5*x*x+0.333333333333333*x*x*x; + else return log(1.0+x); +} +*/ + + +//Fast search or replace +inline igraph_int_t* fast_rpl(igraph_int_t *m, igraph_int_t a, igraph_int_t b) { + while (*m != a) { + m++; + } + *m = b; + return m; +} +inline igraph_int_t* fast_search(igraph_int_t *m, igraph_int_t size, igraph_int_t a) { + igraph_int_t *p = m + size; + while (m != p--) { + if (*p == a) { + return p; + } + } + return NULL; +} + +// Lovely percentage print +// inline void print_percent(double yo, FILE *f = stderr) { +// int arf = int(100.0*yo); +// if(double(arf)>100.0*yo) arf--; +// if(arf<100) fprintf(f," "); +// if(arf<10) fprintf(f," "); +// fprintf(f,"%d.%d%%",arf,int(1000.0*yo-double(10*arf))); +// } + +// Skips non-numerical chars, then numerical chars, then non-numerical chars. +inline char skip_int(char* &c) { + while (*c < '0' || *c > '9') { + c++; + } + while (*c >= '0' && *c <= '9') { + c++; + } + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + return *c; +} + +// distance+1 modulo 255 for breadth-first search +inline unsigned char next_dist(const unsigned char c) { + return c == 255 ? 1 : c + 1; +} +inline unsigned char prev_dist(const unsigned char c) { + return c == 1 ? 255 : c - 1; +} + +// 1/(RANDMAX+1) +#define inv_RANDMAX (1.0/(1.0+double(MY_RAND_MAX))) + +// random number in ]0,1[, _very_ accurate around 0 +inline double random_float() { + long r = my_random(); + double mul = inv_RANDMAX; + while (r <= 0x7FFFFF) { + r <<= 8; + r += (my_random() & 0xFF); + mul *= (1.0 / 256.0); + } + return double(r) * mul; +} + +// Return true with probability p. Very accurate when p is small. +#define test_proba(p) (random_float()<(p)) + +// Random bit generator, sparwise. +static int _random_bits_stored = 0; +static long _random_bits = 0; + +inline int random_bit() { + long a = _random_bits; + _random_bits = a >> 1; + if (_random_bits_stored--) { + return a & 0x1; + } + a = my_random(); + _random_bits = a >> 1; + _random_bits_stored = 30; + return a & 0x1; +} + +// Hash Profiling (see hash.h) +void _hash_prof(); + +} // namespace gengraph + +#endif //DEFINITIONS_H diff --git a/src/games/degree_sequence_vl/gengraph_degree_sequence.cpp b/src/games/degree_sequence_vl/gengraph_degree_sequence.cpp new file mode 100644 index 0000000..347083d --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_degree_sequence.cpp @@ -0,0 +1,140 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include "gengraph_degree_sequence.h" + +#include +#include +#include +#include +#include +#include + +// using namespace __gnu_cxx; +using namespace std; + +namespace gengraph { + +// shuffle an igraph_int_t[] randomly +void random_permute(igraph_int_t *a, igraph_int_t n); + +// sort an array of positive integers in time & place O(n + max) +void cumul_sort(igraph_int_t *q, igraph_int_t n); + + +degree_sequence::~degree_sequence() { + deg = NULL; +} + +void degree_sequence::compute_total() { + total = 0; + for (igraph_int_t i = 0; i < n; i++) { + total += deg[i]; + } +} + +degree_sequence:: +degree_sequence(igraph_int_t n0, igraph_int_t *degs) { + deg = degs; + n = n0; + compute_total(); +} + +degree_sequence:: +degree_sequence(const igraph_vector_int_t *out_seq) { + n = igraph_vector_int_size(out_seq); + deg = &VECTOR(*out_seq)[0]; + compute_total(); +} + +#ifndef FBUFF_SIZE + #define FBUFF_SIZE 999 +#endif //FBUFF_SIZE + +bool degree_sequence::havelhakimi() { + + igraph_int_t i; + igraph_int_t dm = dmax() + 1; + // Sort vertices using basket-sort, in descending degrees + igraph_int_t *nb = new igraph_int_t[dm]; + igraph_int_t *sorted = new igraph_int_t[n]; + // init basket + for (i = 0; i < dm; i++) { + nb[i] = 0; + } + // count basket + for (i = 0; i < n; i++) { + nb[deg[i]]++; + } + // cumul + igraph_int_t c = 0; + for (i = dm - 1; i >= 0; i--) { + igraph_int_t t = nb[i]; + nb[i] = c; + c += t; + } + // sort + for (i = 0; i < n; i++) { + sorted[nb[deg[i]]++] = i; + } + +// Binding process starts + igraph_int_t first = 0; // vertex with biggest residual degree + igraph_int_t d = dm - 1; // maximum residual degree available + + for (c = total / 2; c > 0; ) { + // We design by 'v' the vertex of highest degree (indexed by first) + // look for current degree of v + while (nb[d] <= first) { + d--; + } + // store it in dv + igraph_int_t dv = d; + // bind it ! + c -= dv; + igraph_int_t dc = d; // residual degree of vertices we bind to + igraph_int_t fc = ++first; // position of the first vertex with degree dc + + while (dv > 0 && dc > 0) { + igraph_int_t lc = nb[dc]; + if (lc != fc) { + while (dv > 0 && lc > fc) { + // binds v with sorted[--lc] + dv--; + lc--; + } + fc = nb[dc]; + nb[dc] = lc; + } + dc--; + } + if (dv != 0) { // We couldn't bind entirely v + delete[] nb; + delete[] sorted; + return false; + } + } + delete[] nb; + delete[] sorted; + return true; +} + +} // namespace gengraph diff --git a/src/games/degree_sequence_vl/gengraph_degree_sequence.h b/src/games/degree_sequence_vl/gengraph_degree_sequence.h new file mode 100644 index 0000000..3d9eb5c --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_degree_sequence.h @@ -0,0 +1,88 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DEGREE_SEQUENCE_H +#define DEGREE_SEQUENCE_H + +#include "igraph_types.h" +#include "igraph_vector.h" + +namespace gengraph { + +class degree_sequence { + +private: + igraph_int_t n; + igraph_int_t *deg; + igraph_int_t total; + +public : + // #vertices + inline igraph_int_t size() { + return n; + }; + inline igraph_int_t sum() { + return total; + }; + inline igraph_int_t operator[](igraph_int_t i) { + return deg[i]; + }; + inline igraph_int_t *seq() { + return deg; + }; + inline void assign(igraph_int_t n0, igraph_int_t* d0) { + n = n0; + deg = d0; + }; + inline igraph_int_t dmax() { + igraph_int_t dm = deg[0]; + for (igraph_int_t i = 1; i < n; i++) if (deg[i] > dm) { + dm = deg[i]; + } + return dm; + } + + degree_sequence(igraph_int_t n, igraph_int_t *degs); + + // igraph constructor + degree_sequence(const igraph_vector_int_t *out_seq); + + // destructor + ~degree_sequence(); + + // compute total number of arcs + void compute_total(); + +#if 0 + // raw print (vertex by vertex) + void print(); + + // distribution print (degree frequency) + void print_cumul(); +#endif + + // is degree sequence realizable ? + bool havelhakimi(); + +}; + +} // namespace gengraph + +#endif //DEGREE_SEQUENCE_H diff --git a/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.cpp b/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.cpp new file mode 100644 index 0000000..9d534fa --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.cpp @@ -0,0 +1,732 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gengraph_qsort.h" +#include "gengraph_hash.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_graph_molloy_hash.h" + +#include "igraph_constructors.h" +#include "igraph_error.h" +#include "igraph_progress.h" + +#include +#include +#include +#include +#include + +namespace gengraph { + +//_________________________________________________________________________ +void graph_molloy_hash::compute_neigh() { + igraph_int_t *p = links; + for (igraph_int_t i = 0; i < n; i++) { + neigh[i] = p; + p += HASH_SIZE(deg[i]); + } +} + +//_________________________________________________________________________ +void graph_molloy_hash::compute_size() { + size = 0; + for (igraph_int_t i = 0; i < n; i++) { + size += HASH_SIZE(deg[i]); + } +} + +//_________________________________________________________________________ +void graph_molloy_hash::init() { + for (igraph_int_t i = 0; i < size; i++) { + links[i] = HASH_NONE; + } +} + +//_________________________________________________________________________ +graph_molloy_hash::graph_molloy_hash(degree_sequence °s) { + alloc(degs); +} + +//_________________________________________________________________________ +igraph_int_t graph_molloy_hash::alloc(degree_sequence °s) { + n = degs.size(); + a = degs.sum(); + assert(a % 2 == 0); + + deg = degs.seq(); + compute_size(); + deg = new igraph_int_t[n + size]; + if (deg == NULL) { + return 0; + } + igraph_int_t i; + for (i = 0; i < n; i++) { + deg[i] = degs[i]; + } + links = deg + n; + init(); + neigh = new igraph_int_t*[n]; + if (neigh == NULL) { + return 0; + } + compute_neigh(); + return sizeof(igraph_int_t *)*n + sizeof(igraph_int_t) * (n + size); +} + +//_________________________________________________________________________ +graph_molloy_hash::~graph_molloy_hash() { + if (deg != NULL) { + delete[] deg; + } + if (neigh != NULL) { + delete[] neigh; + } + deg = NULL; + neigh = NULL; +} + +//_________________________________________________________________________ +graph_molloy_hash::graph_molloy_hash(igraph_int_t *svg) { + // Read n + n = *(svg++); + // Read a + a = *(svg++); + assert(a % 2 == 0); + // Read degree sequence + degree_sequence dd(n, svg); + // Build neigh[] and alloc links[] + alloc(dd); + // Read links[] + restore(svg + n); +} + +//_________________________________________________________________________ +igraph_int_t *graph_molloy_hash::hard_copy() { + igraph_int_t *hc = new igraph_int_t[2 + n + a / 2]; // to store n,a,deg[] and links[] + hc[0] = n; + hc[1] = a; + memcpy(hc + 2, deg, sizeof(igraph_int_t)*n); + igraph_int_t *p = hc + 2 + n; + igraph_int_t *l = links; + for (igraph_int_t i = 0; i < n; i++) for (igraph_int_t j = HASH_SIZE(deg[i]); j--; l++) { + igraph_int_t d; + if ((d = *l) != HASH_NONE && d >= i) { + *(p++) = d; + } + } + assert(p == hc + 2 + n + a / 2); + return hc; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::is_connected() { + bool *visited = new bool[n]; + igraph_int_t *buff = new igraph_int_t[n]; + igraph_int_t comp_size = depth_search(visited, buff); + delete[] visited; + delete[] buff; + return (comp_size == n); +} + +//_________________________________________________________________________ +igraph_int_t* graph_molloy_hash::backup() { + igraph_int_t *b = new igraph_int_t[a / 2]; + igraph_int_t *c = b; + igraph_int_t *p = links; + for (igraph_int_t i = 0; i < n; i++) + for (igraph_int_t d = HASH_SIZE(deg[i]); d--; p++) if (*p != HASH_NONE && *p > i) { + *(c++) = *p; + } + assert(c == b + (a / 2)); + return b; +} + +//_________________________________________________________________________ +void graph_molloy_hash::restore(igraph_int_t* b) { + init(); + igraph_int_t i; + igraph_int_t *dd = new igraph_int_t[n]; + memcpy(dd, deg, sizeof(igraph_int_t)*n); + for (i = 0; i < n; i++) { + deg[i] = 0; + } + for (i = 0; i < n - 1; i++) { + while (deg[i] < dd[i]) { + add_edge(i, *b, dd); + b++; + } + } + delete[] dd; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::isolated(igraph_int_t v, igraph_int_t K, igraph_int_t *Kbuff, bool *visited) { + if (K < 2) { + return false; + } +#ifdef OPT_ISOLATED + if (K <= deg[v] + 1) { + return false; + } +#endif //OPT_ISOLATED + igraph_int_t *seen = Kbuff; + igraph_int_t *known = Kbuff; + igraph_int_t *max = Kbuff + K; + *(known++) = v; + visited[v] = true; + bool is_isolated = true; + + while (known != seen) { + v = *(seen++); + igraph_int_t *ww = neigh[v]; + igraph_int_t w; + for (igraph_int_t d = HASH_SIZE(deg[v]); d--; ww++) if ((w = *ww) != HASH_NONE && !visited[w]) { +#ifdef OPT_ISOLATED + if (K <= deg[w] + 1 || known == max) { +#else //OPT_ISOLATED + if (known == max) { +#endif //OPT_ISOLATED + is_isolated = false; + goto end_isolated; + } + visited[w] = true; + *(known++) = w; + } + } +end_isolated: + // Undo the changes to visited[]... + while (known != Kbuff) { + visited[*(--known)] = false; + } + return is_isolated; +} + +//_________________________________________________________________________ +int graph_molloy_hash::random_edge_swap(igraph_int_t K, igraph_int_t *Kbuff, bool *visited) { + // Pick two random vertices a and c + igraph_int_t f1 = pick_random_vertex(); + igraph_int_t f2 = pick_random_vertex(); + // Check that f1 != f2 + if (f1 == f2) { + return 0; + } + // Get two random edges (f1,*f1t1) and (f2,*f2t2) + igraph_int_t *f1t1 = random_neighbour(f1); + igraph_int_t t1 = *f1t1; + igraph_int_t *f2t2 = random_neighbour(f2); + igraph_int_t t2 = *f2t2; + // Check simplicity + if (t1 == t2 || f1 == t2 || f2 == t1) { + return 0; + } + if (is_edge(f1, t2) || is_edge(f2, t1)) { + return 0; + } + // Swap + igraph_int_t *f1t2 = H_rpl(neigh[f1], deg[f1], f1t1, t2); + igraph_int_t *f2t1 = H_rpl(neigh[f2], deg[f2], f2t2, t1); + igraph_int_t *t1f2 = H_rpl(neigh[t1], deg[t1], f1, f2); + igraph_int_t *t2f1 = H_rpl(neigh[t2], deg[t2], f2, f1); + // isolation test + if (K <= 2) { + return 1; + } + if ( !isolated(f1, K, Kbuff, visited) && !isolated(f2, K, Kbuff, visited) ) { + return 1; + } + // undo swap + H_rpl(neigh[f1], deg[f1], f1t2, t1); + H_rpl(neigh[f2], deg[f2], f2t1, t2); + H_rpl(neigh[t1], deg[t1], t1f2, f1); + H_rpl(neigh[t2], deg[t2], t2f1, f2); + return 0; +} + +//_________________________________________________________________________ +igraph_int_t graph_molloy_hash::shuffle(igraph_int_t times, + igraph_int_t maxtimes, int type) { + igraph_progress("Shuffle", 0, 0); + // assert(verify()); + // counters + igraph_int_t nb_swaps = 0; + igraph_int_t all_swaps = 0; + unsigned long cost = 0; + // window + double T = double(((a < times) ? a : times) / 10); + if (type == OPTIMAL_HEURISTICS) { + T = double(optimal_window()); + } + if (type == BRUTE_FORCE_HEURISTICS) { + T = double(times * 2); + } + // isolation test parameter, and buffers + double K = 2.4; + igraph_int_t *Kbuff = new igraph_int_t[int(K) + 1]; + bool *visited = new bool[n]; + for (igraph_int_t i = 0; i < n; i++) { + visited[i] = false; + } + // Used for monitoring , active only if VERBOSE() + igraph_int_t failures = 0; + igraph_int_t successes = 0; + double avg_K = 0; + double avg_T = 0; + unsigned long next = times; + next = 0; + + // Shuffle: while #edge swap attempts validated by connectivity < times ... + while (times > nb_swaps && maxtimes > all_swaps) { + // Backup graph + igraph_int_t *save = backup(); + // Prepare counters, K, T + igraph_int_t swaps = 0; + igraph_int_t K_int = 0; + if (type == FINAL_HEURISTICS || type == BRUTE_FORCE_HEURISTICS) { + K_int = igraph_int_t(K); + } + igraph_int_t T_int = (igraph_int_t)(floor(T)); + if (T_int < 1) { + T_int = 1; + } + // compute cost + cost += T_int; + if (K_int > 2) { + cost += K_int + T_int; + } + // Perform T edge swap attempts + for (igraph_int_t i = T_int; i > 0; i--) { + // try one swap + swaps += random_edge_swap(K_int, Kbuff, visited); + all_swaps++; + // Verbose + if (nb_swaps + swaps > next) { + next = (nb_swaps + swaps) + (times / 1000 > 100 ? times / 1000 : 100); + int progress = int(double(nb_swaps + swaps) / double(times)); + igraph_progress("Shuffle", progress, 0); + } + } + // test connectivity + cost += (a / 2); + bool ok = is_connected(); + // performance monitor + { + avg_T += double(T_int); avg_K += double(K_int); + if (ok) { + successes++; + } else { + failures++; + } + } + // restore graph if needed, and count validated swaps + if (ok) { + nb_swaps += swaps; + } else { + restore(save); + next = nb_swaps; + } + delete[] save; + // Adjust K and T following the heuristics. + switch (type) { + int steps; + case GKAN_HEURISTICS: + if (ok) { + T += 1.0; + } else { + T *= 0.5; + } + break; + case FAB_HEURISTICS: + steps = 50 / (8 + failures + successes); + if (steps < 1) { + steps = 1; + } + while (steps--) if (ok) { + T *= 1.17182818; + } else { + T *= 0.9; + } + if (T > double(5 * a)) { + T = double(5 * a); + } + break; + case FINAL_HEURISTICS: + if (ok) { + if ((K + 10.0)*T > 5.0 * double(a)) { + K /= 1.03; + } else { + T *= 2; + } + } else { + K *= 1.35; + delete[] Kbuff; + Kbuff = new igraph_int_t[igraph_int_t(K) + 1]; + } + break; + case OPTIMAL_HEURISTICS: + if (ok) { + T = double(optimal_window()); + } + break; + case BRUTE_FORCE_HEURISTICS: + K *= 2; delete[] Kbuff; Kbuff = new igraph_int_t[igraph_int_t(K) + 1]; + break; + default: + throw std::invalid_argument("Error in graph_molloy_hash::shuffle(): Unknown heuristics type."); + } + } + + delete[] Kbuff; + delete[] visited; + + if (maxtimes <= all_swaps) { + IGRAPH_WARNING("Cannot shuffle graph, maybe it is the only realization of its degree sequence?"); + } + + return nb_swaps; +} + +//_________________________________________________________________________ + +/* +void graph_molloy_hash::print(FILE *f) { + igraph_int_t i, j; + for (i = 0; i < n; i++) { + fprintf(f, "%" IGRAPH_PRId, i); + for (j = 0; j < HASH_SIZE(deg[i]); j++) if (neigh[i][j] != HASH_NONE) { + fprintf(f, " %" IGRAPH_PRId, neigh[i][j]); + } + fprintf(f, "\n"); + } +} +*/ + +igraph_error_t graph_molloy_hash::print(igraph_t *graph) { + igraph_int_t i, j; + igraph_int_t ptr = 0; + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, a); // every edge is counted twice.... + + for (i = 0; i < n; i++) { + for (j = 0; j < HASH_SIZE(deg[i]); j++) { + if (neigh[i][j] != HASH_NONE) { + if (neigh[i][j] > i) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = neigh[i][j]; + } + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, /*undirected=*/ 0)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::try_shuffle(igraph_int_t T, igraph_int_t K, igraph_int_t *backup_graph) { + // init all + igraph_int_t *Kbuff = NULL; + bool *visited = NULL; + if (K > 2) { + Kbuff = new igraph_int_t[K]; + visited = new bool[n]; + for (igraph_int_t i = 0; i < n; i++) { + visited[i] = false; + } + } + igraph_int_t *back = backup_graph; + if (back == NULL) { + back = backup(); + } + // perform T edge swap attempts + while (T--) { + random_edge_swap(K, Kbuff, visited); + } + // clean + if (visited != NULL) { + delete[] visited; + } + if (Kbuff != NULL) { + delete[] Kbuff; + } + // check & restore + bool yo = is_connected(); + restore(back); + if (backup_graph == NULL) { + delete[] back; + } + return yo; +} + +//_________________________________________________________________________ +#define _TRUST_BERNOULLI_LOWER 0.01 + +bool bernoulli_param_is_lower(int success, int trials, double param) { + if (double(success) >= double(trials)*param) { + return false; + } + double comb = 1.0; + double fact = 1.0; + for (int i = 0; i < success; i++) { + comb *= double(trials - i); + fact *= double(i + 1); + } + comb /= fact; + comb *= pow(param, double(success)) * exp(double(trials - success) * log1p(-param)); + double sum = comb; + while (success && sum < _TRUST_BERNOULLI_LOWER) { + comb *= double(success) * (1.0 - param) / (double(trials - success) * param); + sum += comb; + success--; + } + // fprintf(stderr,"bernoulli test : %d/%d success against p=%f -> %s\n",success, trials, param, (sum < _TRUST_BERNOULLI_LOWER) ? "lower" : "can't say"); + return (sum < _TRUST_BERNOULLI_LOWER); +} + +//_________________________________________________________________________ +#define _MIN_SUCCESS_FOR_BERNOULLI_TRUST 100 +double graph_molloy_hash::average_cost(igraph_int_t T, igraph_int_t *backup, double min_cost) { + if (T < 1) { + return 1e+99; + } + int successes = 0; + int trials = 0; + while (successes < _MIN_SUCCESS_FOR_BERNOULLI_TRUST && + !bernoulli_param_is_lower(successes, trials, 1.0 / min_cost)) { + if (try_shuffle(T, 0, backup)) { + successes++; + } + trials++; + } + if (successes >= _MIN_SUCCESS_FOR_BERNOULLI_TRUST) { + return double(trials) / double(successes) * (1.0 + double(a / 2) / double(T)); + } else { + return 2.0 * min_cost; + } +} + +//_________________________________________________________________________ +igraph_int_t graph_molloy_hash::optimal_window() { + igraph_int_t Tmax; + igraph_int_t optimal_T = 1; + double min_cost = 1e+99; + igraph_int_t *back = backup(); + // on cherche une borne sup pour Tmax + int been_greater = 0; + for (Tmax = 1; Tmax <= 5 * a ; Tmax *= 2) { + double c = average_cost(Tmax, back, min_cost); + if (c > 1.5 * min_cost) { + break; + } + if (c > 1.2 * min_cost && ++been_greater >= 3) { + break; + } + if (c < min_cost) { + min_cost = c; + optimal_T = Tmax; + } + } + // on cherche autour + double span = 2.0; + int try_again = 4; + while (span > 1.05 && optimal_T <= 5 * a) { + igraph_int_t T_low = igraph_int_t(double(optimal_T) / span); + igraph_int_t T_high = igraph_int_t(double(optimal_T) * span); + double c_low = average_cost(T_low, back, min_cost); + double c_high = average_cost(T_high, back, min_cost); + if (c_low < min_cost && c_high < min_cost) { + if (try_again--) { + continue; + } + delete[] back; + return optimal_T; + } + if (c_low < min_cost) { + optimal_T = T_low; + min_cost = c_low; + } else if (c_high < min_cost) { + optimal_T = T_high; + min_cost = c_high; + }; + span = pow(span, 0.618); + } + delete[] back; + return optimal_T; +} + +//_________________________________________________________________________ +double graph_molloy_hash::eval_K(int quality) { + double K = 5.0; + double avg_K = 1.0; + for (int i = quality; i--; ) { + int int_K = int(floor(K + 0.5)); + if (try_shuffle(a / (int_K + 1), int_K)) { + K *= 0.8; /*fprintf(stderr,"+");*/ + } else { + K *= 1.25; /*fprintf(stderr,"-");*/ + } + if (i < quality / 2) { + avg_K *= K; + } + } + return pow(avg_K, 1.0 / double(quality / 2)); +} + +//_________________________________________________________________________ +double graph_molloy_hash::effective_K(igraph_int_t K, int quality) { + if (K < 3) { + return 0.0; + } + long sum_K = 0; + igraph_int_t *Kbuff = new igraph_int_t[K]; + bool *visited = new bool[n]; + igraph_int_t i; + for (i = 0; i < n; i++) { + visited[i] = false; + } + for (i = 0; i < quality; i++) { + // assert(verify()); + igraph_int_t f1, f2, t1, t2; + igraph_int_t *f1t1, *f2t2; + do { + // Pick two random vertices + do { + f1 = pick_random_vertex(); + f2 = pick_random_vertex(); + } while (f1 == f2); + // Pick two random neighbours + f1t1 = random_neighbour(f1); + t1 = *f1t1; + f2t2 = random_neighbour(f2); + t2 = *f2t2; + // test simplicity + } while (t1 == t2 || f1 == t2 || f2 == t1 || is_edge(f1, t2) || is_edge(f2, t1)); + // swap + swap_edges(f1, t2, f2, t1); + // assert(verify()); + sum_K += effective_isolated(deg[f1] > deg[t2] ? f1 : t2, K, Kbuff, visited); + // assert(verify()); + sum_K += effective_isolated(deg[f2] > deg[t1] ? f2 : t1, K, Kbuff, visited); + // assert(verify()); + // undo swap + swap_edges(f1, t2, f2, t1); + // assert(verify()); + } + delete[] Kbuff; + delete[] visited; + return double(sum_K) / double(2 * quality); +} + +//_________________________________________________________________________ +igraph_int_t graph_molloy_hash::effective_isolated(igraph_int_t v, igraph_int_t K, igraph_int_t *Kbuff, bool *visited) { + igraph_int_t i; + for (i = 0; i < K; i++) { + Kbuff[i] = -1; + } + igraph_int_t count = 0; + igraph_int_t left = K; + igraph_int_t *KB = Kbuff; + //yapido = (my_random()%1000 == 0); + depth_isolated(v, count, left, K, KB, visited); + while (KB-- != Kbuff) { + visited[*KB] = false; + } + //if(yapido) fprintf(stderr,"\n"); + return count; +} + +//_________________________________________________________________________ +void graph_molloy_hash::depth_isolated(igraph_int_t v, igraph_int_t &calls, igraph_int_t &left_to_explore, igraph_int_t dmax, igraph_int_t * &Kbuff, bool *visited) { + if (left_to_explore == 0) { + return; + } +// if(yapido) fprintf(stderr,"%d ",deg[v]); + if (--left_to_explore == 0) { + return; + } + if (deg[v] + 1 >= dmax) { + left_to_explore = 0; + return; + } + *(Kbuff++) = v; + visited[v] = true; +// print(); +// fflush(stdout); + calls++; + igraph_int_t *copy = NULL; + igraph_int_t *w = neigh[v]; + if (IS_HASH(deg[v])) { + copy = new igraph_int_t[deg[v]]; + H_copy(copy, w, deg[v]); + w = copy; + } + qsort(deg, w, deg[v]); + w += deg[v]; + for (igraph_int_t i = deg[v]; i--; ) { + if (visited[*--w]) { + calls++; + } else { + depth_isolated(*w, calls, left_to_explore, dmax, Kbuff, visited); + } + if (left_to_explore == 0) { + break; + } + } + if (copy != NULL) { + delete[] copy; + } +} + +//_________________________________________________________________________ +igraph_int_t graph_molloy_hash::depth_search(bool *visited, igraph_int_t *buff, igraph_int_t v0) { + for (igraph_int_t i = 0; i < n; i++) { + visited[i] = false; + } + igraph_int_t *to_visit = buff; + igraph_int_t nb_visited = 1; + visited[v0] = true; + *(to_visit++) = v0; + while (to_visit != buff && nb_visited < n) { + igraph_int_t v = *(--to_visit); + igraph_int_t *ww = neigh[v]; + igraph_int_t w; + for (igraph_int_t k = HASH_SIZE(deg[v]); k--; ww++) { + if (HASH_NONE != (w = *ww) && !visited[w]) { + visited[w] = true; + nb_visited++; + *(to_visit++) = w; + } + } + } + return nb_visited; +} + +//_________________________________________________________________________ +// bool graph_molloy_hash::verify() { +// fprintf(stderr,"Warning: graph_molloy_hash::verify() called..\n"); +// fprintf(stderr," try to convert graph into graph_molloy_opt() instead\n"); +// return true; +// } + +} // namespace gengraph diff --git a/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.h b/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.h new file mode 100644 index 0000000..ae19f71 --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_graph_molloy_hash.h @@ -0,0 +1,188 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef GRAPH_MOLLOY_HASH_H +#define GRAPH_MOLLOY_HASH_H + +#include "gengraph_definitions.h" +#include "gengraph_hash.h" +#include "gengraph_degree_sequence.h" + +#include "igraph_datatype.h" + +#include +#include +// This class handles graphs with a constant degree sequence. + +#define FINAL_HEURISTICS 0 +#define GKAN_HEURISTICS 1 +#define FAB_HEURISTICS 2 +#define OPTIMAL_HEURISTICS 3 +#define BRUTE_FORCE_HEURISTICS 4 + +namespace gengraph { + +//**************************** +// class graph_molloy_hash +//**************************** + +class graph_molloy_hash { + +private: + // Number of vertices + igraph_int_t n; + //Number of arcs ( = #edges * 2 ) + igraph_int_t a; + //Total size of links[] + igraph_int_t size; + // The degree sequence of the graph + igraph_int_t *deg; + // The array containing all links + igraph_int_t *links; + // The array containing pointers to adjacency list of every vertices + igraph_int_t **neigh; + // Counts total size + void compute_size(); + // Build neigh with deg and links + void compute_neigh(); + // Allocate memory according to degree_sequence (for constructor use only!!) + igraph_int_t alloc(degree_sequence &); + // Add edge (u,v). Return FALSE if vertex a is already full. + // WARNING : only to be used by havelhakimi(), restore() or constructors + inline bool add_edge(igraph_int_t u, igraph_int_t v, igraph_int_t *realdeg) { + igraph_int_t deg_u = realdeg[u]; + if (deg_u == deg[u]) { + return false; + } + // Check that edge was not already inserted + assert(fast_search(neigh[u], (u == n - 1 ? links + size : neigh[u + 1]) - neigh[u], v) == NULL); + assert(fast_search(neigh[v], (v == n - 1 ? links + size : neigh[v + 1]) - neigh[v], u) == NULL); + assert(deg[u] < deg_u); + igraph_int_t deg_v = realdeg[v]; + if (IS_HASH(deg_u)) { + *H_add(neigh[u], HASH_EXPAND(deg_u), v) = v; + } else { + neigh[u][deg[u]] = v; + } + if (IS_HASH(deg_v)) { + *H_add(neigh[v], HASH_EXPAND(deg_v), u) = u; + } else { + neigh[v][deg[v]] = u; + } + deg[u]++; + deg[v]++; + // Check that edge was actually inserted + assert(fast_search(neigh[u], int((u == n - 1 ? links + size : neigh[u + 1]) - neigh[u]), v) != NULL); + assert(fast_search(neigh[v], int((v == n - 1 ? links + size : neigh[v + 1]) - neigh[v]), u) != NULL); + return true; + } + // Swap edges + inline void swap_edges(igraph_int_t from1, igraph_int_t to1, igraph_int_t from2, igraph_int_t to2) { + H_rpl(neigh[from1], deg[from1], to1, to2); + H_rpl(neigh[from2], deg[from2], to2, to1); + H_rpl(neigh[to1], deg[to1], from1, from2); + H_rpl(neigh[to2], deg[to2], from2, from1); + } + // Backup graph [sizeof(igraph_int_t) bytes per edge] + igraph_int_t* backup(); + // Test if vertex is in an isolated component of size dmax. + void depth_isolated(igraph_int_t v, igraph_int_t &calls, igraph_int_t &left_to_explore, igraph_int_t dmax, igraph_int_t * &Kbuff, bool *visited); + + +public: + //degree of v + inline igraph_int_t degree(igraph_int_t v) { + return deg[v]; + }; + // For debug purposes : verify validity of the graph (symetry, simplicity) + //bool verify(); + // Destroy deg[], neigh[] and links[] + ~graph_molloy_hash(); + // Allocate memory for the graph. Create deg and links. No edge is created. + graph_molloy_hash(degree_sequence &); + // Create graph from hard copy + graph_molloy_hash(igraph_int_t *); + // Create hard copy of graph + igraph_int_t *hard_copy(); + // Restore from backup + void restore(igraph_int_t* back); + //Clear hash tables + void init(); + // nb arcs + inline igraph_int_t nbarcs() { + return a; + }; + // nb vertices + inline igraph_int_t nbvertices() { + return n; + }; + // print graph in SUCC_LIST mode, in stdout + /* void print(FILE *f = stdout); */ + igraph_error_t print(igraph_t *graph); + // Test if graph is connected + bool is_connected(); + // is edge ? + inline bool is_edge(igraph_int_t u, igraph_int_t v) { + assert(H_is(neigh[u], deg[u], v) == (fast_search(neigh[u], HASH_SIZE(deg[u]), v) != NULL)); + assert(H_is(neigh[v], deg[v], u) == (fast_search(neigh[v], HASH_SIZE(deg[v]), u) != NULL)); + assert(H_is(neigh[u], deg[u], v) == H_is(neigh[v], deg[v], u)); + if (deg[u] < deg[v]) { + return H_is(neigh[u], deg[u], v); + } else { + return H_is(neigh[v], deg[v], u); + } + } + // Random edge swap ATTEMPT. Return 1 if attempt was a succes, 0 otherwise + int random_edge_swap(igraph_int_t K = 0, igraph_int_t *Kbuff = NULL, bool *visited = NULL); + // Connected Shuffle + igraph_int_t shuffle(igraph_int_t, igraph_int_t, int type); + // Optimal window for the gkantsidis heuristics + igraph_int_t optimal_window(); + // Average unitary cost per post-validated edge swap, for some window + double average_cost(igraph_int_t T, igraph_int_t *back, double min_cost); + // Get caracteristic K + double eval_K(int quality = 100); + // Get effective K + double effective_K(igraph_int_t K, int quality = 10000); + // Try to shuffle T times. Return true if at the end, the graph was still connected. + bool try_shuffle(igraph_int_t T, igraph_int_t K, igraph_int_t *back = NULL); +}; + +} // namespace gengraph + +#endif //GRAPH_MOLLOY_HASH_H diff --git a/src/games/degree_sequence_vl/gengraph_graph_molloy_optimized.cpp b/src/games/degree_sequence_vl/gengraph_graph_molloy_optimized.cpp new file mode 100644 index 0000000..f5049ce --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_graph_molloy_optimized.cpp @@ -0,0 +1,1212 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include +#include +#include +#include +#include + +#include "gengraph_qsort.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_graph_molloy_optimized.h" + +#include "igraph_error.h" +#include "igraph_progress.h" + + +using namespace std; + +namespace gengraph { + +igraph_int_t graph_molloy_opt::max_degree() { + igraph_int_t m = 0; + for (igraph_int_t k = 0; k < n; k++) if (deg[k] > m) { + m = deg[k]; + } + return m; +} + +void graph_molloy_opt::compute_neigh() { + igraph_int_t *p = links; + for (igraph_int_t i = 0; i < n; i++) { + neigh[i] = p; + p += deg[i]; + } +} + +void graph_molloy_opt::alloc(degree_sequence °s) { + n = degs.size(); + a = degs.sum(); + assert(a % 2 == 0); + deg = new igraph_int_t[n + a]; + for (igraph_int_t i = 0; i < n; i++) { + deg[i] = degs[i]; + } + links = deg + n; + neigh = new igraph_int_t*[n]; + compute_neigh(); +} + +graph_molloy_opt::graph_molloy_opt(degree_sequence °s) { + alloc(degs); +} + +// graph_molloy_opt::graph_molloy_opt(FILE *f) { +// char *buff = new char[FBUFF_SIZE]; +// // How many vertices ? +// if(VERBOSE()) fprintf(stderr,"Read file: #vertices="); +// int i; +// int n=0; +// while(fgets(buff,FBUFF_SIZE,f)) if(sscanf(buff,"%d",&i)==1 && i>n) n=i; +// n++; +// // degrees ? +// if(VERBOSE()) fprintf(stderr,"%d, #edges=",n); +// int *degs = new int[n]; +// for(i=0; i= i) { + *(c++) = *p; + } + } + } + assert(c == b + (a / 2)); + return b; +} + +igraph_int_t *graph_molloy_opt::hard_copy() { + igraph_int_t *hc = new igraph_int_t[2 + n + a / 2]; // to store n,a,deg[] and links[] + hc[0] = n; + hc[1] = a; + memcpy(hc + 2, deg, sizeof(igraph_int_t)*n); + igraph_int_t *c = hc + 2 + n; + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t *p = neigh[i]; + for (igraph_int_t d = deg[i]; d--; p++) { + assert(*p != i); + if (*p >= i) { + *(c++) = *p; + } + } + } + assert(c == hc + 2 + n + a / 2); + return hc; +} + +void graph_molloy_opt::restore(igraph_int_t* b) { + igraph_int_t i; + for (i = 0; i < n; i++) { + deg[i] = 0; + } + igraph_int_t *p = links; + for (i = 0; i < n - 1; i++) { + p += deg[i]; + deg[i] = igraph_int_t(neigh[i + 1] - neigh[i]); + assert((neigh[i] + deg[i]) == neigh[i + 1]); + while (p != neigh[i + 1]) { + // b points to the current 'j' + neigh[*b][deg[*b]++] = i; + *(p++) = *(b++); + } + } +} + +igraph_int_t* graph_molloy_opt::backup_degs(igraph_int_t *b) { + if (b == NULL) { + b = new igraph_int_t[n]; + } + memcpy(b, deg, sizeof(igraph_int_t)*n); + return b; +} + +void graph_molloy_opt::restore_degs_only(igraph_int_t *b) { + memcpy(deg, b, sizeof(igraph_int_t)*n); + refresh_nbarcs(); +} + +void graph_molloy_opt::restore_degs_and_neigh(igraph_int_t *b) { + restore_degs_only(b); + compute_neigh(); +} + +void graph_molloy_opt::restore_degs(igraph_int_t last_degree) { + a = last_degree; + deg[n - 1] = last_degree; + for (igraph_int_t i = n - 2; i >= 0; i--) { + a += (deg[i] = igraph_int_t(neigh[i + 1] - neigh[i])); + } + refresh_nbarcs(); +} + +void graph_molloy_opt::clean() { + igraph_int_t *b = hard_copy(); + replace(b); + delete[] b; +} + +void graph_molloy_opt::replace(igraph_int_t *_hardcopy) { + delete[] deg; + n = *(_hardcopy++); + a = *(_hardcopy++); + deg = new igraph_int_t[a + n]; + memcpy(deg, _hardcopy, sizeof(igraph_int_t)*n); + links = deg + n; + compute_neigh(); + restore(_hardcopy + n); +} + +igraph_int_t* graph_molloy_opt::components(igraph_int_t *comp) { + igraph_int_t i; + // breadth-first search buffer + igraph_int_t *buff = new igraph_int_t[n]; + // comp[i] will contain the index of the component that contains vertex i + if (comp == NULL) { + comp = new igraph_int_t[n]; + } + memset(comp, 0, sizeof(igraph_int_t)*n); + // current component index + igraph_int_t curr_comp = 0; + // loop over all non-visited vertices... + for (igraph_int_t v0 = 0; v0 < n; v0++) if (comp[v0] == 0) { + curr_comp++; + // initiate breadth-first search + igraph_int_t *to_visit = buff; + igraph_int_t *visited = buff; + *(to_visit++) = v0; + comp[v0] = curr_comp; + // breadth-first search + while (visited != to_visit) { + igraph_int_t v = *(visited++); + igraph_int_t d = deg[v]; + for (igraph_int_t *w = neigh[v]; d--; w++) if (comp[*w] == 0) { + comp[*w] = curr_comp; + *(to_visit++) = *w; + } + } + } + // compute component sizes and store them in buff[] + igraph_int_t nb_comp = 0; + memset(buff, 0, sizeof(igraph_int_t)*n); + for (i = 0; i < n; i++) + if (buff[comp[i] - 1]++ == 0 && comp[i] > nb_comp) { + nb_comp = comp[i]; + } + // box-sort sizes + igraph_int_t offset = 0; + igraph_int_t *box = pre_boxsort(buff, nb_comp, offset); + for (i = nb_comp - 1; i >= 0; i--) { + buff[i] = --box[buff[i] - offset]; + } + delete[] box; + // reassign component indexes + for (igraph_int_t *c = comp + n; comp != c--; *c = buff[*c - 1]) { } + // clean.. at last! + delete[] buff; + return comp; +} + +bool graph_molloy_opt::havelhakimi() { + + igraph_int_t i; + igraph_int_t dmax = max_degree() + 1; + // Sort vertices using basket-sort, in descending degrees + igraph_int_t *nb = new igraph_int_t[dmax]; + igraph_int_t *sorted = new igraph_int_t[n]; + // init basket + for (i = 0; i < dmax; i++) { + nb[i] = 0; + } + // count basket + for (i = 0; i < n; i++) { + nb[deg[i]]++; + } + // cumul + igraph_int_t c = 0; + for (i = dmax - 1; i >= 0; i--) { + c += nb[i]; + nb[i] = -nb[i] + c; + } + // sort + for (i = 0; i < n; i++) { + sorted[nb[deg[i]]++] = i; + } + +// Binding process starts + igraph_int_t first = 0; // vertex with biggest residual degree + igraph_int_t d = dmax - 1; // maximum residual degree available + + for (c = a / 2; c > 0; ) { + // pick a vertex. we could pick any, but here we pick the one with biggest degree + igraph_int_t v = sorted[first]; + // look for current degree of v + while (nb[d] <= first) { + d--; + } + // store it in dv + igraph_int_t dv = d; + // bind it ! + c -= dv; + igraph_int_t dc = d; // residual degree of vertices we bind to + igraph_int_t fc = ++first; // position of the first vertex with degree dc + + while (dv > 0 && dc > 0) { + igraph_int_t lc = nb[dc]; + if (lc != fc) { + while (dv > 0 && lc > fc) { + // binds v with sorted[--lc] + dv--; + igraph_int_t w = sorted[--lc]; + *(neigh[v]++) = w; + *(neigh[w]++) = v; + } + fc = nb[dc]; + nb[dc] = lc; + } + dc--; + } + if (dv != 0) { // We couldn't bind entirely v + delete[] nb; + delete[] sorted; + compute_neigh(); + + /* Cannot use IGRAPH_ERRORF() as this function does not return + * an error code. This situation should only occur when the degree + * sequence is not graphical, but that is already checked at the top + * level. Therefore, we use IGRAPH_FATAL(), as triggering this + * indicates a bug. */ + IGRAPH_FATALF("Error in graph_molloy_opt::havelhakimi(): " + "Couldn't bind vertex %" IGRAPH_PRId " entirely (%" IGRAPH_PRId " edges remaining)", + v, dv); + return false; + } + } + assert(c == 0); + compute_neigh(); + delete[] nb; + delete[] sorted; + return true; +} + +bool graph_molloy_opt::is_connected() { + bool *visited = new bool[n]; + for (igraph_int_t i = n; i > 0; visited[--i] = false) { } + igraph_int_t *to_visit = new igraph_int_t[n]; + igraph_int_t *stop = to_visit; + igraph_int_t left = n - 1; + *(to_visit++) = 0; + visited[0] = true; + while (left > 0 && to_visit != stop) { + igraph_int_t v = *(--to_visit); + igraph_int_t *w = neigh[v]; + for (igraph_int_t k = deg[v]; k--; w++) { + if (!visited[*w]) { + visited[*w] = true; + left--; + *(to_visit++) = *w; + } + } + } + delete[] visited; + delete[] stop; + assert(left >= 0); + return (left == 0); +} + + +bool graph_molloy_opt::make_connected() { + //assert(verify()); + if (a / 2 < n - 1) { + // fprintf(stderr,"\ngraph::make_connected() failed : #edges < #vertices-1\n"); + return false; + } + igraph_int_t i; + +// Data struct for the visit : +// - buff[] contains vertices to visit +// - dist[V] is V's distance modulo 4 to the root of its comp, or -1 if it hasn't been visited yet +#define MC_BUFF_SIZE (n+2) + igraph_int_t *buff = new igraph_int_t[MC_BUFF_SIZE]; + unsigned char * dist = new unsigned char[n]; +#define NOT_VISITED 255 +#define FORBIDDEN 254 + for (i = n; i > 0; dist[--i] = NOT_VISITED) { } + +// Data struct to store components : either surplus trees or surplus edges are stored at buff[]'s end +// - A Tree is coded by one of its vertices +// - An edge (a,b) is coded by the TWO ints a and b + igraph_int_t *ffub = buff + MC_BUFF_SIZE; + edge *edges = (edge *) ffub; + igraph_int_t *trees = ffub; + igraph_int_t *min_ffub = buff + 1 + (MC_BUFF_SIZE % 2 ? 0 : 1); + +// There will be only one "fatty" component, and trees. + edge fatty_edge = { -1, -1 }; + bool enough_edges = false; + + // start main loop + for (igraph_int_t v0 = 0; v0 < n; v0++) if (dist[v0] == NOT_VISITED) { + // is v0 an isolated vertex? + if (deg[v0] == 0) { + delete[] dist; + delete[] buff; + // 0-degree vertex found, cannot create connected graph + return false; + } + dist[v0] = 0; // root + igraph_int_t *to_visit = buff; + igraph_int_t *current = buff; + *(to_visit++) = v0; + + // explore component connected to v0 + bool is_a_tree = true; + while (current != to_visit) { + igraph_int_t v = *(current++); + unsigned char current_dist = dist[v]; + unsigned char next_dist = (current_dist + 1) & 0x03; + //unsigned char prev_dist = (current_dist-1) & 0x03; + igraph_int_t* ww = neigh[v]; + igraph_int_t w; + for (igraph_int_t k = deg[v]; k--; ww++) { + if (dist[w = *ww] == NOT_VISITED) { + // we didn't visit *w yet + dist[w] = next_dist; + *(to_visit++) = w; + if (to_visit > min_ffub) { + min_ffub += 2; // update limit of ffub's storage + } + //assert(verify()); + } else if (dist[w] == next_dist || (w >= v && dist[w] == current_dist)) { + // we found a removable edge + if (trees != ffub) { + // some trees still.. Let's merge with them! + assert(trees >= min_ffub); + assert(edges == (edge *)ffub); + swap_edges(v, w, *trees, neigh[*trees][0]); + trees++; + //assert(verify()); + } else if (is_a_tree) { + // we must merge with the fatty component + is_a_tree = false; + if (fatty_edge.from < 0) { + // we ARE the first component! fatty is us + fatty_edge.from = v; + fatty_edge.to = w; + } else { + // we connect to fatty + swap_edges(fatty_edge.from, fatty_edge.to, v, w); + fatty_edge.to = w; + //assert(verify()); + } + } else if (!enough_edges) { + // Store the removable edge for future use + if (edges <= (edge *)min_ffub + 1) { + enough_edges = true; + } else { + edges--; + edges->from = v; + edges->to = w; + } + } + } + } + } + // Mark component + while (to_visit != buff) { + dist[*(--to_visit)] = FORBIDDEN; + } + // Check if it is a tree + if (is_a_tree ) { + assert(deg[v0] != 0); + if (edges != (edge *)ffub) { + // let's bind the tree we found with a removable edge in stock + assert(trees == ffub); + if (edges < (edge *)min_ffub) { + edges = (edge *)min_ffub; + } + swap_edges(v0, neigh[v0][0], edges->from, edges->to); + edges++; + assert(verify()); + } else if (fatty_edge.from >= 0) { + // if there is a fatty component, let's merge with it ! and discard fatty :-/ + assert(trees == ffub); + swap_edges(v0, neigh[v0][0], fatty_edge.from, fatty_edge.to); + fatty_edge.from = -1; + fatty_edge.to = -1; + assert(verify()); + } else { + // add the tree to the list of trees + assert(trees > min_ffub); + *(--trees) = v0; + assert(verify()); + } + } + } + delete[] buff; + delete[] dist; + // Should ALWAYS return true : either we have no tree left, or we are a unique, big tree + return (trees == ffub || ((trees + 1) == ffub && fatty_edge.from < 0)); +} + +bool graph_molloy_opt::swap_edges_simple(igraph_int_t from1, igraph_int_t to1, igraph_int_t from2, igraph_int_t to2) { + if (from1 == to1 || from1 == from2 || from1 == to2 || to1 == from2 || to1 == to2 || from2 == to2) { + return false; + } + if (is_edge(from1, to2) || is_edge(from2, to1)) { + return false; + } + swap_edges(from1, to1, from2, to2); + return true; +} + +void graph_molloy_opt::print(FILE *f, bool NOZERO) { + igraph_int_t i, j; + for (i = 0; i < n; i++) { + if (!NOZERO || deg[i] > 0) { + fprintf(f, "%" IGRAPH_PRId, i); + for (j = 0; j < deg[i]; j++) { + fprintf(f, " %" IGRAPH_PRId, neigh[i][j]); + } + fprintf(f, "\n"); + } + } +} + +igraph_int_t graph_molloy_opt::effective_isolated(igraph_int_t v, igraph_int_t K, igraph_int_t *Kbuff, bool *visited) { + igraph_int_t i; + for (i = 0; i < K; i++) { + Kbuff[i] = -1; + } + igraph_int_t count = 0; + igraph_int_t left = K; + igraph_int_t *KB = Kbuff; + //yapido = (my_random()%1000 == 0); + depth_isolated(v, count, left, K, KB, visited); + while (KB-- != Kbuff) { + visited[*KB] = false; + } + //if(yapido) fprintf(stderr,"\n"); + return count; +} + +void graph_molloy_opt::depth_isolated(igraph_int_t v, igraph_int_t &calls, igraph_int_t &left_to_explore, igraph_int_t dmax, igraph_int_t * &Kbuff, bool *visited) { + if (left_to_explore == 0) { + return; + } +// if(yapido) fprintf(stderr,"%d ",deg[v]); + if (--left_to_explore == 0) { + return; + } + if (deg[v] + 1 >= dmax) { + left_to_explore = 0; + return; + } + *(Kbuff++) = v; + visited[v] = true; + calls++; + igraph_int_t *w = neigh[v]; + qsort(deg, w, deg[v]); + w += deg[v]; + for (igraph_int_t i = deg[v]; i--; ) { + if (visited[*--w]) { + calls++; + } else { + depth_isolated(*w, calls, left_to_explore, dmax, Kbuff, visited); + } + if (left_to_explore == 0) { + break; + } + } +} + +igraph_int_t graph_molloy_opt::depth_search(bool *visited, igraph_int_t *buff, igraph_int_t v0) { + for (igraph_int_t i = 0; i < n; i++) { + visited[i] = false; + } + igraph_int_t *to_visit = buff; + igraph_int_t nb_visited = 1; + visited[v0] = true; + *(to_visit++) = v0; + while (to_visit != buff && nb_visited < n) { + igraph_int_t v = *(--to_visit); + igraph_int_t *ww = neigh[v]; + igraph_int_t w; + for (igraph_int_t k = deg[v]; k--; ww++) if (!visited[w = *ww]) { + visited[w] = true; + nb_visited++; + *(to_visit++) = w; + } + } + return nb_visited; +} + +igraph_int_t graph_molloy_opt::width_search(unsigned char *dist, igraph_int_t *buff, igraph_int_t v0, igraph_int_t toclear) { + if (toclear >= 0) for (igraph_int_t i = 0; i < toclear; i++) { + dist[buff[i]] = 0; + } else for (igraph_int_t i = 0; i < n; i++) { + dist[i] = 0; + } + igraph_int_t *to_visit = buff; + igraph_int_t *to_add = buff; + igraph_int_t nb_visited = 1; + dist[v0] = 1; + *(to_add++) = v0; + while (to_visit != to_add && nb_visited < n) { + igraph_int_t v = *(to_visit++); + igraph_int_t *ww = neigh[v]; + igraph_int_t w; + unsigned char d = next_dist(dist[v]); + for (igraph_int_t k = deg[v]; k--; ww++) if (dist[w = *ww] == 0) { + dist[w] = d; + nb_visited++; + *(to_add++) = w; + } + } + return nb_visited; +} + +// dist[] MUST be full of zeros !!!! +igraph_int_t graph_molloy_opt::breadth_path_search(igraph_int_t src, igraph_int_t *buff, double *paths, unsigned char *dist) { + unsigned char last_dist = 0; + unsigned char curr_dist = 1; + igraph_int_t *to_visit = buff; + igraph_int_t *visited = buff; + *(to_visit++) = src; + paths[src] = 1.0; + dist[src] = curr_dist; + igraph_int_t nb_visited = 1; + while (visited != to_visit) { + igraph_int_t v = *(visited++); + if (last_dist == (curr_dist = dist[v])) { + break; + } + unsigned char nd = next_dist(curr_dist); + igraph_int_t *ww = neigh[v]; + double p = paths[v]; + for (igraph_int_t k = deg[v]; k--;) { + igraph_int_t w = *(ww++); + unsigned char d = dist[w]; + if (d == 0) { + // not visited yet ! + *(to_visit++) = w; + dist[w] = nd; + paths[w] = p; + // is it the last one ? + if (++nb_visited == n) { + last_dist = nd; + } + } else if (d == nd) { + if ((paths[w] += p) == numeric_limits::infinity()) { + throw std::runtime_error("Fatal error: too many (>MAX_DOUBLE) possible paths in graph."); + } + } + } + } + assert(to_visit == buff + nb_visited); + return nb_visited; +} + +igraph_int_t *graph_molloy_opt::vertices_real(igraph_int_t &nb_v) { + igraph_int_t *yo; + if (nb_v < 0) { + nb_v = 0; + for (yo = deg; yo != deg + n; ) if (*(yo++) > 0) { + nb_v++; + } + } + if (nb_v == 0) { + IGRAPH_WARNING("graph is empty"); + return NULL; + } + igraph_int_t *buff = new igraph_int_t[nb_v]; + yo = buff; + for (igraph_int_t i = 0; i < n; i++) if (deg[i] > 0) { + *(yo++) = i; + } + if (yo != buff + nb_v) { + IGRAPH_WARNINGF("wrong #vertices in graph_molloy_opt::vertices_real(%" IGRAPH_PRId ")", nb_v); + delete[] buff; + return NULL; + } else { + return buff; + } +} + +bool graph_molloy_opt::isolated(igraph_int_t v, igraph_int_t K, igraph_int_t *Kbuff, bool *visited) { + if (K < 2) { + return false; + } +#ifdef OPT_ISOLATED + if (K <= deg[v] + 1) { + return false; + } +#endif //OPT_ISOLATED + igraph_int_t *seen = Kbuff; + igraph_int_t *known = Kbuff; + igraph_int_t *max = Kbuff + (K - 1); + *(known++) = v; + visited[v] = true; + bool is_isolated = true; + + while (known != seen) { + v = *(seen++); + igraph_int_t *w = neigh[v]; + for (igraph_int_t d = deg[v]; d--; w++) if (!visited[*w]) { +#ifdef OPT_ISOLATED + if (K <= deg[*w] + 1 || known == max) { +#else //OPT_ISOLATED + if (known == max) { +#endif //OPT_ISOLATED + is_isolated = false; + goto end_isolated; + } + visited[*w] = true; + *(known++) = *w; + } + } +end_isolated: + // Undo the changes to visited[]... + while (known != Kbuff) { + visited[*(--known)] = false; + } + return is_isolated; +} + +void graph_molloy_opt::sort() { + for (int v = 0; v < n; v++) { + qsort(neigh[v], deg[v]); + } +} + +// void graph_molloy_opt::remove_vertex(int v) { +// fprintf(stderr,"Warning : graph_molloy_opt::remove_vertex(%d) called",v); +// } + +bool graph_molloy_opt::verify(int mode) { + IGRAPH_UNUSED(mode); +#ifndef NDEBUG + igraph_int_t i, j, k; + assert(neigh[0] == links); + // verify edges count + if ((mode & VERIFY_NOARCS) == 0) { + int sum = 0; + for (i = 0; i < n; i++) { + sum += deg[i]; + } + assert(sum == a); + } + // verify neigh[] and deg[] compatibility + if ((mode & VERIFY_NONEIGH) == 0) + for (i = 0; i < n - 1; i++) { + assert(neigh[i] + deg[i] == neigh[i + 1]); + } + // verify vertex range + for (i = 0; i < a; i++) { + assert(links[i] >= 0 && links[i] < n); + } + // verify simplicity +// for(i=0; i 0); + } +#endif + return true; +} + +/*___________________________________________________________________________________ + Not to use anymore : use graph_molloy_hash class instead + +void graph_molloy_opt::shuffle(long times) { + while(times) { + int f1 = links[my_random()%a]; + int f2 = links[my_random()%a]; + int t1 = neigh[f1][my_random()%deg[f1]]; + int t2 = neigh[f2][my_random()%deg[f2]]; + if(swap_edges_simple(f1,t1,f2,t2)) times--; + } +} + + +long graph_molloy_opt::connected_shuffle(long times) { + //assert(verify()); +#ifdef PERFORMANCE_MONITOR + long failures = 0; + long successes = 0; + double avg_K = 0.0; + long avg_T = 0; +#endif //PERFORMANCE_MONITOR + + long nb_swaps = 0; + long T = min(a,times)/10; + double double_K = 1.0; + int K = int(double_K); + double Q1 = 1.35; + double Q2 = 1.01; + int *Kbuff = new int[K]; + bool *visited = new bool[n]; + for(int i=0; inb_swaps) { + // Backup graph +#ifdef PERFORMANCE_MONITOR + avg_K+=double_K; + avg_T+=T; +#endif //PERFORMANCE_MONITOR + int *save = backup(); + //assert(verify()); + // Swaps + long swaps = 0; + for(int i=T; i>0; i--) { + // Pick two random vertices + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && !is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + else swaps++; + } + } + //assert(verify()); + // test connectivity + bool ok = is_connected(); +#ifdef PERFORMANCE_MONITOR + if(ok) successes++; else failures++; +#endif //PERFORMANCE_MONITOR + if(ok) { + nb_swaps += swaps; + // adjust K and T + if((K+10)*T>5*a) { + double_K/=Q2; + K = int(double_K); + } + else T*=2; + } + else { + restore(save); + //assert(verify()); + double_K*=Q1; + K = int(double_K); + delete[] Kbuff; + Kbuff = new int[K]; + } + delete[] save; + } +#ifdef PERFORMANCE_MONITOR + fprintf(stderr,"\n*** Performance Monitor ***\n"); + fprintf(stderr," - Connectivity test successes : %ld\n",successes); + fprintf(stderr," - Connectivity test failures : %ld\n",failures); + fprintf(stderr," - Average window : %ld\n",avg_T/long(successes+failures)); + fprintf(stderr," - Average isolation test width : %f\n",avg_K/double(successes+failures)); +#endif //PERFORMANCE_MONITOR + return nb_swaps; +} + +bool graph_molloy_opt::try_shuffle(int T, int K) { + int i; + int *Kbuff = NULL; + if(K>0) Kbuff = new int[K]; + bool *visited = new bool[n]; + for(i=0; i0; i--) { + // Pick two random vertices + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + } + } + delete[] visited; + if(Kbuff != NULL) delete[] Kbuff; + bool yo = is_connected(); + restore(back); + delete[] back; + return yo; +} + +double graph_molloy_opt::window(int K, double ratio) { + int steps = 100; + double T = double(a*10); + double q2 = 0.1; + double q1 = pow(q2,(ratio-1.0)/ratio); + + int failures = 0; + int successes = 0; + int *Kbuff = new int[K]; + bool *visited = new bool[n]; + + while(successes<10*steps) { + int *back=backup(); + for(int i=int(T); i>0; i--) { + // Pick two random vertices + int f1 = links[my_random()%a]; + int f2 = links[my_random()%a]; + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = neigh[f1]+my_random()%deg[f1]; + int *f2t2 = neigh[f2]+my_random()%deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + } + } + if(is_connected()) { + T *= q1; + if(T>double(5*a)) T=double(5*a); + successes++; + if((successes%steps)==0) { + q2 = sqrt(q2); + q1 = sqrt(q1); + } + } + else { + T*=q2; + failures++; + } + if(VERBOSE()) fprintf(stderr,"."); + restore(back); + delete[] back; + } + delete[] Kbuff; + delete[] visited; + if(VERBOSE()) fprintf(stderr,"Failures:%d Successes:%d\n",failures, successes); + return T; +} + + +double graph_molloy_opt::eval_K(int quality) { + double K = 5.0; + double avg_K = 1.0; + for(int i=quality; i--; ) { + int int_K = int(floor(K+0.5)); + if(try_shuffle(a/(int_K+1),int_K)) { + K*=0.8; fprintf(stderr,"+"); } + else { + K*=1.25; fprintf(stderr,"-"); } + if(ideg[t2] ? f1 : t2, K, Kbuff, visited); + sum_K += effective_isolated(deg[f2]>deg[t1] ? f2 : t1, K, Kbuff, visited); + // undo swap + swap_edges(f1,t2,f2,t1); +// assert(verify()); + } + delete[] Kbuff; + delete[] visited; + return double(sum_K)/double(2*quality); +} + + +//___________________________________________________________________________________ +*/ + + + +/***** NOT USED ANYMORE (Modif 22/04/2005) ****** + +int64_t *graph_molloy_opt::vertex_betweenness_usp(bool trivial_paths) { + if(VERBOSE()) fprintf(stderr,"Computing vertex betweenness USP..."); + int i; + unsigned char *dist = new unsigned char[n]; + int *buff = new int[n]; + int64_t *b = new int64_t[n]; + int *bb = new int[n]; + int *dd = new int[max_degree()]; + for(i=0; i(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness USP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + int nv = nb_vertices; + for(i=0; i(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness RSP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + int nv = nb_vertices; + for(i=0; i1 && to_give>2*n_father) { + int o = rng.binomial(1.0/n_father,to_give); + to_give -= o; + bb[dd[--n_father]]+=o; + } + if(n_father==1) bb[dd[0]]+=to_give; + else { + while(to_give--) bb[dd[my_random()%n_father]]++; + } + } + if(trivial_paths) bb[v]++; + } + for(i=0; i0) { + if(VERBOSE()==VERBOSE_LOTS && v0>(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness ASP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + if(!trivial_paths) dist[v0]=2; + int nv = nb_vertices; + for(i=0; i. + */ +#ifndef GRAPH_MOLLOY_OPT_H +#define GRAPH_MOLLOY_OPT_H + +#include "gengraph_definitions.h" +#include "gengraph_degree_sequence.h" + +#include +#include "gengraph_random.h" + +namespace gengraph { + +// This class handles graphs with a constant degree sequence. + +class graph_molloy_opt { + +private: + // Random generator + KW_RNG::RNG rng; + // Number of vertices + igraph_int_t n; + //Number of arcs ( = #edges * 2 ) + igraph_int_t a; + // The degree sequence of the graph + igraph_int_t *deg; + // The array containing all links + igraph_int_t *links; + // The array containing pointers to adjacency list of every vertices + igraph_int_t **neigh; + // Allocate memory according to degree_sequence (for constructor use only!!) + void alloc(degree_sequence &); + // Compute #edges + inline void refresh_nbarcs() { + a = 0; + for (igraph_int_t* d = deg + n; d != deg; ) { + a += *(--d); + } + } + // Build neigh with deg and links + void compute_neigh(); + // Swap edges. The swap MUST be valid !!! + inline void swap_edges(igraph_int_t from1, igraph_int_t to1, igraph_int_t from2, igraph_int_t to2) { + fast_rpl(neigh[from1], to1, to2); + fast_rpl(neigh[from2], to2, to1); + fast_rpl(neigh[to1], from1, from2); + fast_rpl(neigh[to2], from2, from1); + } + + // Swap edges only if they are simple. return false if unsuccessful. + bool swap_edges_simple(igraph_int_t, igraph_int_t, igraph_int_t, igraph_int_t); + // Test if vertex is in an isolated component of size dmax. + void depth_isolated(igraph_int_t v, igraph_int_t &calls, igraph_int_t &left_to_explore, igraph_int_t dmax, igraph_int_t * &Kbuff, bool *visited); + // breadth-first search. Store the distance (modulo 3) in dist[]. Returns eplorated component size. + igraph_int_t width_search(unsigned char *dist, igraph_int_t *buff, igraph_int_t v0 = 0, igraph_int_t toclear = -1); + // depth-first search. + igraph_int_t depth_search(bool *visited, igraph_int_t *buff, igraph_int_t v0 = 0); + // breadth-first search that count the number of shortest paths going from src to each vertex + igraph_int_t breadth_path_search(igraph_int_t src, igraph_int_t *buff, double *paths, unsigned char *dist); + // Return component indexes where vertices belong to, starting from 0, + // sorted by size (biggest component has index 0) + igraph_int_t *components(igraph_int_t *comp = NULL); + +public: + // neigh[] + inline igraph_int_t** neighbors() { + return neigh; + }; + // deg[] + inline igraph_int_t* degrees() { + return deg; + }; + //adjacency list of v + inline igraph_int_t* operator[](const igraph_int_t v) { + return neigh[v]; + }; + //degree of v + inline igraph_int_t degree(const igraph_int_t v) { + return deg[v]; + }; + // Detach deg[] and neigh[] + void detach(); + // Destroy deg and links + ~graph_molloy_opt(); + // Create graph from file (stdin not supported unless rewind() possible) + //graph_molloy_opt(FILE *f); + // Allocate memory for the graph. Create deg and links. No edge is created. + graph_molloy_opt(degree_sequence &); + // Create graph from hard copy + graph_molloy_opt(igraph_int_t *); + // Create hard copy of graph + igraph_int_t *hard_copy(); + // Remove unused edges, updates neigh[], recreate links[] + void clean(); + // nb arcs + inline igraph_int_t nbarcs() { + return a; + }; + // last degree + inline igraph_int_t last_degree() { + return deg[n - 1]; + }; + // nb vertices + inline igraph_int_t nbvertices() { + return n; + }; + // nb vertices having degree > 0 + inline igraph_int_t nbvertices_real() { + igraph_int_t s = 0; + for (igraph_int_t *d = deg + n; d-- != deg; ) { + if (*d) { + s++; + } + } + return s; + }; + // return list of vertices with degree > 0. Compute #vertices, if not given. + igraph_int_t *vertices_real(igraph_int_t &nb_v); + // print graph in SUCC_LIST mode, in stdout + void print(FILE *f = stdout, bool NOZERO = true); + // Bind the graph avoiding multiple edges or self-edges (return false if fail) + bool havelhakimi(); + // Get the graph connected (return false if fail) + bool make_connected(); + // Test if graph is connected + bool is_connected(); + // Maximum degree + igraph_int_t max_degree(); + // is edge ? + inline bool is_edge(const igraph_int_t u, const igraph_int_t v) { + if (deg[v] < deg[u]) { + return (fast_search(neigh[v], deg[v], u) != NULL); + } else { + return (fast_search(neigh[u], deg[u], v) != NULL); + } + } + // Backup graph [sizeof(igraph_int_t) bytes per edge] + igraph_int_t* backup(igraph_int_t *here = NULL); + // Restore from backup. Assume that degrees haven't changed + void restore(igraph_int_t* back); + // Resplace with hard backup. + void replace(igraph_int_t* _hardbackup); + // Backup degs of graph + igraph_int_t* backup_degs(igraph_int_t *here = NULL); + // Restore degs from neigh[]. Need last degree, though + void restore_degs(igraph_int_t last_degree); + // Restore degs[] from backup. Assume that links[] has only been permuted + void restore_degs_only(igraph_int_t* backup_degs); + // Restore degs[] and neigh[]. Assume that links[] has only been permuted + void restore_degs_and_neigh(igraph_int_t* backup_degs); + // sort adjacency lists + void sort(); + // count cycles passing through vertex v + //int cycles(int v); + // remove vertex (i.e. remove all edges adjacent to vertex) + //void remove_vertex(int v); + + // For debug purposes : verify validity of the graph (symetry, simplicity) +#define VERIFY_NORMAL 0 +#define VERIFY_NONEIGH 1 +#define VERIFY_NOARCS 2 + bool verify(int mode = VERIFY_NORMAL); + + /*___________________________________________________________________________________ + Not to use anymore : use graph_molloy_hash class instead + + + public: + // Shuffle. returns number of swaps done. + void shuffle(long); + // Connected Shuffle + long connected_shuffle(long); + // Get caracteristic K + double eval_K(int quality = 100); + // Get effective K + double effective_K(int K, int quality = 10000); + // Test window + double window(int K, double ratio); + // Try to shuffle n times. Return true if at the end, the graph was still connected. + bool try_shuffle(int T, int K); + + //___________________________________________________________________________________ + */ + + /*___________________________________________________________________________________ + Not to use anymore : replaced by vertex_betweenness() 22/04/2005 + + // shortest paths where vertex is an extremity + long long *vertex_betweenness_usp(bool trivial_path); + // shortest paths where vertex is an extremity + long long *vertex_betweenness_rsp(bool trivial_path); + // same, but when multiple shortest path are possible, average the weights. + double *vertex_betweenness_asp(bool trivial_path); + //___________________________________________________________________________________ + */ + +}; + +} // namespace gengraph + +#endif //GRAPH_MOLLOY_OPT_H diff --git a/src/games/degree_sequence_vl/gengraph_hash.h b/src/games/degree_sequence_vl/gengraph_hash.h new file mode 100644 index 0000000..40601b8 --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_hash.h @@ -0,0 +1,315 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef HASH_H +#define HASH_H + +#include +#include "gengraph_definitions.h" + +//_________________________________________________________________________ +// Hash table profiling... Active only if definition below is uncommented +//_________________________________________________________________________ +//#define _HASH_PROFILE + +namespace gengraph { + +#ifdef _HASH_PROFILE + void _hash_add_iter(); + void _hash_add_call(); + void _hash_put_iter(); + void _hash_put_call(); + void _hash_rm_iter(); + void _hash_rm_call(); + void _hash_find_iter(); + void _hash_find_call(); + void _hash_rand_iter(); + void _hash_rand_call(); + void _hash_expand_call(); + void _hash_prof(); + #define _HASH_ADD_ITER() _hash_add_iter() + #define _HASH_ADD_CALL() _hash_add_call() + #define _HASH_PUT_ITER() _hash_put_iter() + #define _HASH_PUT_CALL() _hash_put_call() + #define _HASH_RM_ITER() _hash_rm_iter() + #define _HASH_RM_CALL() _hash_rm_call() + #define _HASH_FIND_ITER() _hash_find_iter() + #define _HASH_FIND_CALL() _hash_find_call() + #define _HASH_RAND_ITER() _hash_rand_iter() + #define _HASH_RAND_CALL() _hash_rand_call() + #define _HASH_EXP_CALL() _hash_expand_call() +#else + #define _HASH_ADD_ITER() {} + #define _HASH_ADD_CALL() {} + #define _HASH_PUT_ITER() {} + #define _HASH_PUT_CALL() {} + #define _HASH_RM_ITER() {} + #define _HASH_RM_CALL() {} + #define _HASH_FIND_ITER() {} + #define _HASH_FIND_CALL() {} + #define _HASH_RAND_ITER() {} + #define _HASH_RAND_CALL() {} + #define _HASH_EXP_CALL() {} +#endif + +//_________________________________________________________________________ +// Hash Table properties. Works best when HASH_SIZE_IS_POWER2 is uncommented +// but takes 2.25 times the needed space, in average (from 1.5 to 3) +// If you have memory issues, Try to comment it: tables will take 1.5 times +// the minimal space +//_________________________________________________________________________ + +#define HASH_SIZE_IS_POWER2 +#define MACRO_RATHER_THAN_INLINE + +// under HASH_MIN_SIZE, vectors are not hash table (just a simle array) +#define HASH_MIN_SIZE 100 +#define IS_HASH(x) ((x)>HASH_MIN_SIZE) +#define HASH_NONE (-1) + +#ifdef HASH_SIZE_IS_POWER2 +inline igraph_int_t HASH_EXPAND(igraph_int_t x) { + /* Returns pow(2, floor(log2(x)) + 2) if x > 0, 1 otherwise. Works up to + * x == 2^64, starts to break down afterwards */ + _HASH_EXP_CALL(); + x += x; + x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; +#if IGRAPH_INTEGER_SIZE == 64 + x |= x >> 32; +#endif + return x + 1; +} +#define HASH_KEY(x,size) ((x*2198737)&((size)-1)) +#endif //HASH_SIZE_IS_POWER2 + +#ifdef MACRO_RATHER_THAN_INLINE +#ifndef HASH_SIZE_IS_POWER2 + #define HASH_EXPAND(x) ((x)+((x)>>1)) + #define HASH_UNEXPAND(x) ((((x)<<1)+1)/3) + #define HASH_KEY(x,size) ((x)%(size)) +#endif //HASH_SIZE_IS_POWER2 +#define HASH_SIZE(x) (IS_HASH(x) ? HASH_EXPAND(x) : (x) ) +#define HASH_REKEY(k,size) ((k)==0 ? (size)-1 : (k)-1) +#else //MACRO_RATHER_THAN_INLINE +#ifndef HASH_SIZE_IS_POWER2 +inline igraph_int_t HASH_KEY(igraph_int_t x, igraph_int_t size) { + assert(x >= 0); + return x % size; +}; +inline igraph_int_t HASH_EXPAND(igraph_int_t x) { + _HASH_EXP_CALL(); + return x + (x >> 1); +}; +inline int HASH_UNEXPAND(igraph_int_t x) { + return ((x << 1) + 1) / 3; +}; +#endif //HASH_SIZE_IS_POWER2 +inline int HASH_REKEY(igraph_int_t k, igraph_int_t s) { + assert(k >= 0); + if (k == 0) { + return s - 1; + } else { + return k - 1; + } +}; +inline int HASH_SIZE(igraph_int_t x) { + if (IS_HASH(x)) { + return HASH_EXPAND(x); + } else { + return x; + } +}; +#endif //MACRO_RATHER_THAN_INLINE + +inline igraph_int_t HASH_PAIR_KEY(igraph_int_t x, igraph_int_t y, igraph_int_t size) { + return HASH_KEY(x * 1434879443 + y, size); +} + +//_________________________________________________________________________ +// Hash-only functions : table must NOT be Raw. +// the argument 'size' is the total size of the hash table +//_________________________________________________________________________ + +// copy hash table into raw vector +inline void H_copy(igraph_int_t *mem, igraph_int_t *h, igraph_int_t size) { + for (igraph_int_t i = HASH_EXPAND(size); i--; h++) { + if (*h != HASH_NONE) { + *(mem++) = *h; + } + } +} + +// Look for the place to add an element. Return NULL if element is already here. +inline igraph_int_t* H_add(igraph_int_t* h, igraph_int_t size, igraph_int_t a) { + _HASH_ADD_CALL(); + _HASH_ADD_ITER(); + igraph_int_t k = HASH_KEY(a, size); + if (h[k] == HASH_NONE) { + return h + k; + } + while (h[k] != a) { + _HASH_ADD_ITER(); + k = HASH_REKEY(k, size); + if (h[k] == HASH_NONE) { + return h + k; + } + } + return NULL; +} + +// would element be well placed in newk ? +inline bool H_better(igraph_int_t a, igraph_int_t size, igraph_int_t currentk, igraph_int_t newk) { + igraph_int_t k = HASH_KEY(a, size); + if (newk < currentk) { + return (k < currentk && k >= newk); + } else { + return (k < currentk || k >= newk); + } +} + +// removes h[k] +inline void H_rm(igraph_int_t* h, igraph_int_t size, igraph_int_t k) { + _HASH_RM_CALL(); + igraph_int_t lasthole = k; + do { + _HASH_RM_ITER(); + k = HASH_REKEY(k, size); + igraph_int_t next = h[k]; + if (next == HASH_NONE) { + break; + } + if (H_better(next, size, k, lasthole)) { + h[lasthole] = next; + lasthole = k; + } + } while (true); + h[lasthole] = HASH_NONE; +} + +//put a +inline igraph_int_t* H_put(igraph_int_t* h, igraph_int_t size, igraph_int_t a) { + assert(H_add(h, size, a) != NULL); + _HASH_PUT_CALL(); + _HASH_PUT_ITER(); + igraph_int_t k = HASH_KEY(a, size); + while (h[k] != HASH_NONE) { + k = HASH_REKEY(k, size); + _HASH_PUT_ITER(); + } + h[k] = a; + assert(H_add(h, size, a) == NULL); + return h + k; +} + +// find A +inline igraph_int_t H_find(igraph_int_t *h, igraph_int_t size, igraph_int_t a) { + assert(H_add(h, size, a) == NULL); + _HASH_FIND_CALL(); + _HASH_FIND_ITER(); + igraph_int_t k = HASH_KEY(a, size); + while (h[k] != a) { + k = HASH_REKEY(k, size); + _HASH_FIND_ITER(); + } + return k; +} + +// Look for the place to add an element. Return NULL if element is already here. +inline bool H_pair_insert(igraph_int_t* h, igraph_int_t size, igraph_int_t a, igraph_int_t b) { + _HASH_ADD_CALL(); + _HASH_ADD_ITER(); + igraph_int_t k = HASH_PAIR_KEY(a, b, size); + if (h[2 * k] == HASH_NONE) { + h[2 * k] = a; + h[2 * k + 1] = b; + return true; + } + while (h[2 * k] != a || h[2 * k + 1] != b) { + _HASH_ADD_ITER(); + k = HASH_REKEY(k, size); + if (h[2 * k] == HASH_NONE) { + h[2 * k] = a; + h[2 * k + 1] = b; + return true; + } + } + return false; +} + + +//_________________________________________________________________________ +// Generic functions : table can be either Hash or Raw. +// the argument 'size' is the number of elements +//_________________________________________________________________________ + +// Look for an element +inline bool H_is(igraph_int_t *mem, igraph_int_t size, igraph_int_t elem) { + if (IS_HASH(size)) { + return (H_add(mem, HASH_EXPAND(size), elem) == NULL); + } else { + return fast_search(mem, size, elem) != NULL; + } +} + +//pick random location (containing an element) +inline igraph_int_t* H_random(igraph_int_t* mem, igraph_int_t size) { + if (!IS_HASH(size)) { + return mem + (my_random() % size); + } + _HASH_RAND_CALL(); + size = HASH_EXPAND(size); + igraph_int_t* yo; + do { + yo = mem + HASH_KEY(my_random(), size); + _HASH_RAND_ITER(); + } while (*yo == HASH_NONE); + return yo; +} + +// replace *k by b +inline igraph_int_t* H_rpl(igraph_int_t *mem, igraph_int_t size, igraph_int_t* k, igraph_int_t b) { + assert(!H_is(mem, size, b)); + if (!IS_HASH(size)) { + *k = b; + return k; + } else { + size = HASH_EXPAND(size); + assert(mem + int(k - mem) == k); + H_rm(mem, size, int(k - mem)); + return H_put(mem, size, b); + } +} + +// replace a by b +inline igraph_int_t* H_rpl(igraph_int_t *mem, igraph_int_t size, igraph_int_t a, igraph_int_t b) { + assert(H_is(mem, size, a)); + assert(!H_is(mem, size, b)); + if (!IS_HASH(size)) { + return fast_rpl(mem, a, b); + } else { + size = HASH_EXPAND(size); + H_rm(mem, size, H_find(mem, size, a)); + return H_put(mem, size, b); + } +} + +} // namespace gengraph + +#endif //HASH_H diff --git a/src/games/degree_sequence_vl/gengraph_header.h b/src/games/degree_sequence_vl/gengraph_header.h new file mode 100644 index 0000000..99e7e14 --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_header.h @@ -0,0 +1,109 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include +#include + +#include "gengraph_random.h" + +namespace gengraph { + +static KW_RNG::RNG _my_random; +long my_random() { + return _my_random.rand_int31(); +} +void my_srandom(int x) { + _my_random.init(x, !x * 13, x * x + 1, (x >> 16) + (x << 16)); +} +int my_binomial(double pp, int n) { + return _my_random.binomial(pp, n); +} +double my_random01() { + return _my_random.rand_halfopen01(); +} + +} + +namespace gengraph { + +static int VERB; +int VERBOSE() { + return VERB; +} +void SET_VERBOSE(int v) { + VERB = v; +} + +//Hash profiling +static unsigned long _hash_rm_i = 0; +static unsigned long _hash_rm_c = 0; +static unsigned long _hash_add_i = 0; +static unsigned long _hash_add_c = 0; +static unsigned long _hash_put_i = 0; +static unsigned long _hash_put_c = 0; +static unsigned long _hash_find_i = 0; +static unsigned long _hash_find_c = 0; +static unsigned long _hash_rand_i = 0; +static unsigned long _hash_rand_c = 0; +static unsigned long _hash_expand = 0; +inline void _hash_add_iter() { + _hash_add_i++; +} +inline void _hash_add_call() { + _hash_add_c++; +} +inline void _hash_put_iter() { + _hash_put_i++; +} +inline void _hash_put_call() { + _hash_put_c++; +} +inline void _hash_rm_iter() { + _hash_rm_i++; +} +inline void _hash_rm_call() { + _hash_rm_c++; +} +inline void _hash_find_iter() { + _hash_find_i++; +} +inline void _hash_find_call() { + _hash_find_c++; +} +inline void _hash_rand_iter() { + _hash_rand_i++; +} +inline void _hash_rand_call() { + _hash_rand_c++; +} +inline void _hash_expand_call() { + _hash_expand++; +} +// void _hash_prof() { +// fprintf(stderr,"HASH_ADD : %lu / %lu\n", _hash_add_c , _hash_add_i); +// fprintf(stderr,"HASH_PUT : %lu / %lu\n", _hash_put_c , _hash_put_i); +// fprintf(stderr,"HASH_FIND: %lu / %lu\n", _hash_find_c, _hash_find_i); +// fprintf(stderr,"HASH_RM : %lu / %lu\n", _hash_rm_c , _hash_rm_i); +// fprintf(stderr,"HASH_RAND: %lu / %lu\n", _hash_rand_c, _hash_rand_i); +// fprintf(stderr,"HASH_EXPAND : %lu calls\n", _hash_expand); +// } + +} // namespace gengraph diff --git a/src/games/degree_sequence_vl/gengraph_mr-connected.cpp b/src/games/degree_sequence_vl/gengraph_mr-connected.cpp new file mode 100644 index 0000000..9dd5e2b --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_mr-connected.cpp @@ -0,0 +1,182 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_header.h" +#include "gengraph_graph_molloy_optimized.h" +#include "gengraph_graph_molloy_hash.h" +#include "gengraph_degree_sequence.h" + +#include "igraph_datatype.h" +#include "igraph_graphicality.h" +#include "igraph_types.h" +#include "igraph_error.h" + +#include "core/exceptions.h" +#include "games/degree_sequence_vl/degree_sequence_vl.h" + +namespace gengraph { + +// return negative number if program should exit +// int parse_options(int &argc, char** &argv); + +// options +// static const bool MONITOR_TIME = false; +static const int SHUFFLE_TYPE = FINAL_HEURISTICS; +// static const bool RAW_DEGREES = false; +// static const FILE *Fdeg = stdin; + +//_________________________________________________________________________ +// int main(int argc, char** argv) { + +// // options +// SET_VERBOSE(VERBOSE_NONE); +// if(parse_options(argc, argv) < 0) return -1; + +// //Read degree distribution +// degree_sequence dd(Fdeg, !RAW_DEGREES); + +// //Allocate memory +// if(VERBOSE()) fprintf(stderr,"Allocate memory for graph..."); +// graph_molloy_opt g(dd); +// dd.~degree_sequence(); +// //Realize degree sequence +// if(VERBOSE()) fprintf(stderr,"done\nRealize degree sequence..."); +// bool FAILED = !g.havelhakimi(); +// if(VERBOSE()) fprintf(stderr," %s\n", FAILED ? "Failed" : "Success"); +// if(FAILED) return 2; +// //Merge connected components together +// if(VERBOSE()) fprintf(stderr,"Connecting..."); +// FAILED = !g.make_connected(); +// if(VERBOSE()) fprintf(stderr," %s\n", FAILED ? "Failed" : "Success"); +// if(FAILED) return 3; +// //Convert graph_molloy_opt to graph_molloy_hash +// if(VERBOSE()) fprintf(stderr,"Convert adjacency lists into hash tables..."); +// int *hc = g.hard_copy(); +// g.~graph_molloy_opt(); +// graph_molloy_hash gh(hc); +// delete[] hc; +// if(VERBOSE()) fprintf(stderr,"Done\n"); +// //Shuffle +// gh.shuffle(5*gh.nbarcs(), SHUFFLE_TYPE); +// //Output +// gh.print(); +// if(MONITOR_TIME) { +// double t = double(clock()) / double(CLOCKS_PER_SEC); +// fprintf(stderr,"Time used: %f\n", t); +// } +// return 0; +// } + +//_________________________________________________________________________ +// int parse_options(int &argc, char** &argv) { +// bool HELP = false; +// int argc0 = argc; +// argc = 1; +// for(int a=1; a %s returns a graph in its standard output\n",argv[0]); +// fprintf(stderr," If no file is given, %s reads its standard input\n",argv[0]); +// fprintf(stderr," [-v] and [-vv] options causes extra verbose.\n"); +// fprintf(stderr," [-g] option uses the Gkantsidis heuristics.\n"); +// fprintf(stderr," [-b] option uses the Brute Force heuristics.\n"); +// fprintf(stderr," [-f] option uses the Modified Gkantsidis heuristics.\n"); +// fprintf(stderr," [-o] option uses the Optimal Gkantsidis heuristics.\n"); +// fprintf(stderr," [-t] option monitors computation time\n"); +// fprintf(stderr," [-s] does a srandom(0) to get a constant random graph\n"); +// fprintf(stderr," [-raw] is to take raw degree sequences as input\n"); +// return -1; +// } +// return 0; +// } + + +} // namespace gengraph + +using namespace gengraph; + +igraph_error_t igraph_i_degree_sequence_game_vl(igraph_t *graph, + const igraph_vector_int_t *out_seq, + const igraph_vector_int_t *in_seq) { + IGRAPH_HANDLE_EXCEPTIONS( + igraph_bool_t is_graphical; + + if (in_seq && igraph_vector_int_size(in_seq) != 0) { + IGRAPH_ERROR("The Viger-Latapy sampler supports only undirected graphs.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_graphical(out_seq, 0, IGRAPH_SIMPLE_SW, &is_graphical)); + if (!is_graphical) { + IGRAPH_ERROR("Cannot realize the given degree sequence as an undirected, simple graph.", + IGRAPH_EINVAL); + } + + degree_sequence *dd = new degree_sequence(out_seq); + + graph_molloy_opt *g = new graph_molloy_opt(*dd); + delete dd; + + if (!g->havelhakimi()) { + delete g; + IGRAPH_FATAL("g->havelhakimi() failed; please report as a bug."); + } + + if (!g->make_connected()) { + delete g; + IGRAPH_ERROR("Cannot make a connected graph from the given degree sequence.", + IGRAPH_EINVAL); + } + + igraph_int_t *hc = g->hard_copy(); + delete g; + graph_molloy_hash *gh = new graph_molloy_hash(hc); + delete [] hc; + + gh->shuffle(5 * gh->nbarcs(), 100 * gh->nbarcs(), SHUFFLE_TYPE); + + IGRAPH_CHECK(gh->print(graph)); + delete gh; + ); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/degree_sequence_vl/gengraph_qsort.h b/src/games/degree_sequence_vl/gengraph_qsort.h new file mode 100644 index 0000000..383bb63 --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_qsort.h @@ -0,0 +1,307 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QSORT_H +#define QSORT_H + +#include "igraph_types.h" + +#include +#include + +namespace gengraph { + +//___________________________________________________________________________ +// check if every element is zero +inline bool check_zero(igraph_int_t *mem, igraph_int_t n) { + for (igraph_int_t *v = mem + n; v != mem; ) { + if (*(--v) != 0) { + return false; + } + } + return true; +} + +//___________________________________________________________________________ +// Sort simple integer arrays in ASCENDING order +//___________________________________________________________________________ +inline igraph_int_t med3(igraph_int_t a, igraph_int_t b, igraph_int_t c) { + if (a < b) { + if (c < b) { + return (a < c) ? c : a; + } else { + return b; + } + } else { + if (c < a) { + return (b < c) ? c : b; + } else { + return a; + } + } +} + +inline void isort(igraph_int_t *v, igraph_int_t t) { + if (t < 2) { + return; + } + for (igraph_int_t i = 1; i < t; i++) { + igraph_int_t *w = v + i; + igraph_int_t tmp = *w; + while (w != v && *(w - 1) > tmp) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +inline igraph_int_t partitionne(igraph_int_t *v, igraph_int_t t, igraph_int_t p) { + igraph_int_t i = 0; + igraph_int_t j = t - 1; + while (i < j) { + while (i <= j && v[i] < p) { + i++; + } + while (i <= j && v[j] > p) { + j--; + } + if (i < j) { + igraph_int_t tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && v[i] < p) { + i++; + } + assert(i != 0 && i != t); + return i; +} + +inline void qsort(igraph_int_t *v, igraph_int_t t) { + if (t < 15) { + isort(v, t); + } else { + igraph_int_t x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + qsort(v, x); + qsort(v + x, t - x); + } +} + +inline igraph_int_t qsort_median(igraph_int_t *v, igraph_int_t t, igraph_int_t pos) { + if (t < 10) { + isort(v, t); + return v[pos]; + } + igraph_int_t x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + if (pos < x) { + return qsort_median(v, x, pos); + } else { + return qsort_median(v + x, t - x, pos - x); + } +} + +inline igraph_int_t qsort_median(igraph_int_t *v, igraph_int_t t) { + return qsort_median(v, t, t / 2); +} + +//___________________________________________________________________________ +// Sort simple double arrays in ASCENDING order +//___________________________________________________________________________ +inline double med3(double a, double b, double c) { + if (a < b) { + if (c < b) { + return (a < c) ? c : a; + } else { + return b; + } + } else { + if (c < a) { + return (b < c) ? c : b; + } else { + return a; + } + } +} + +inline void isort(double *v, igraph_int_t t) { + if (t < 2) { + return; + } + for (igraph_int_t i = 1; i < t; i++) { + double *w = v + i; + double tmp = *w; + while (w != v && *(w - 1) > tmp) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +inline igraph_int_t partitionne(double *v, igraph_int_t t, double p) { + igraph_int_t i = 0; + igraph_int_t j = t - 1; + while (i < j) { + while (i <= j && v[i] < p) { + i++; + } + while (i <= j && v[j] > p) { + j--; + } + if (i < j) { + double tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && v[i] < p) { + i++; + } + assert(i != 0 && i != t); + return i; +} + +inline void qsort(double *v, igraph_int_t t) { + if (t < 15) { + isort(v, t); + } else { + igraph_int_t x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + qsort(v, x); + qsort(v + x, t - x); + } +} + +inline double qsort_median(double *v, igraph_int_t t, igraph_int_t pos) { + if (t < 10) { + isort(v, t); + return v[pos]; + } + igraph_int_t x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + if (pos < x) { + return qsort_median(v, x, pos); + } else { + return qsort_median(v + x, t - x, pos - x); + } +} + +inline double qsort_median(double *v, igraph_int_t t) { + return qsort_median(v, t, t / 2); +} + +//___________________________________________________________________________ +// Sort integer arrays according to value stored in mem[], in ASCENDING order +inline void isort(igraph_int_t *mem, igraph_int_t *v, igraph_int_t t) { + if (t < 2) { + return; + } + for (igraph_int_t i = 1; i < t; i++) { + igraph_int_t vtmp = v[i]; + igraph_int_t tmp = mem[vtmp]; + igraph_int_t j; + for (j = i; j > 0 && tmp < mem[v[j - 1]]; j--) { + v[j] = v[j - 1]; + } + v[j] = vtmp; + } +} + +inline void qsort(igraph_int_t *mem, igraph_int_t *v, igraph_int_t t) { + if (t < 15) { + isort(mem, v, t); + } else { + igraph_int_t p = med3(mem[v[t >> 1]], mem[v[(t >> 2) + 3]], mem[v[t - (t >> 1) - 3]]); + igraph_int_t i = 0; + igraph_int_t j = t - 1; + while (i < j) { + while (i <= j && mem[v[i]] < p) { + i++; + } + while (i <= j && mem[v[j]] > p) { + j--; + } + if (i < j) { + igraph_int_t tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && mem[v[i]] < p) { + i++; + } + assert(i != 0 && i != t); + qsort(mem, v, i); + qsort(mem, v + i, t - i); + } +} + +//Box-Sort 1..n according to value stored in mem[], in DESCENDING order. +inline igraph_int_t *pre_boxsort(igraph_int_t *mem, igraph_int_t n, igraph_int_t &offset) { + igraph_int_t *yo; + // maximum and minimum + igraph_int_t mx = mem[0]; + igraph_int_t mn = mem[0]; + for (yo = mem + n - 1; yo != mem; yo--) { + igraph_int_t x = *yo; + if (x > mx) { + mx = x; + } + if (x < mn) { + mn = x; + } + } + // box + igraph_int_t c = mx - mn + 1; + igraph_int_t *box = new igraph_int_t[c]; + for (yo = box + c; yo != box; * (--yo) = 0) { } + for (yo = mem + n; yo != mem; box[*(--yo) - mn]++) { } + // cumul sum + igraph_int_t sum = 0; + for (yo = box + c; yo != box; ) { + sum += *(--yo); + *yo = sum; + } + offset = mn; + return box; +} + +inline igraph_int_t *boxsort(igraph_int_t *mem, igraph_int_t n, igraph_int_t *buff = NULL) { + igraph_int_t i; + if (n <= 0) { + return buff; + } + igraph_int_t offset = 0; + igraph_int_t *box = pre_boxsort(mem, n, offset); + // sort + if (buff == NULL) { + buff = new igraph_int_t[n]; + } + for (i = 0; i < n; i++) { + buff[--box[mem[i] - offset]] = i; + } + // clean + delete[] box; + return buff; +} + +} // namespace gengraph + +#endif //QSORT_H diff --git a/src/games/degree_sequence_vl/gengraph_random.cpp b/src/games/degree_sequence_vl/gengraph_random.cpp new file mode 100644 index 0000000..67fc0a7 --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_random.cpp @@ -0,0 +1,275 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#define RNG_C + +#ifdef RCSID + static const char rcsid[] = "$Id: random.cpp,v 1.15 2003/05/14 03:04:45 wilder Exp wilder $"; +#endif + +//________________________________________________________________________ +// See the header file random.h for a description of the contents of this +// file as well as references and credits. + +#include + +using namespace std; + +//________________________________________________________________________ +// RNG::RNOR generates normal variates with rejection. +// nfix() generates variates after rejection in RNOR. +// Despite rejection, this method is much faster than Box-Muller. + +// double RNG::nfix(slong h, ulong i) +// { +// const double r = 3.442620f; // The starting of the right tail +// static double x, y; + +// for(;;) { +// x = h * wn[i]; + +// // If i == 0, handle the base strip +// if (i==0){ +// do { +// x = -log(rand_open01()) * 0.2904764; // .2904764 is 1/r +// y = -log(rand_open01()); +// } while (y + y < x * x); +// return ((h > 0) ? r + x : -r - x); +// } + +// // If i > 0, handle the wedges of other strips +// if (fn[i] + rand_open01() * (fn[i - 1] - fn[i]) < exp(-.5 * x * x) ) +// return x; + +// // start all over +// h = rand_int32(); +// i = h & 127; +// if ((ulong) abs((sint) h) < kn[i]) +// return (h * wn[i]); +// } + +// } // RNG::nfix + +// // __________________________________________________________________________ +// // RNG::RNOR generates exponential variates with rejection. +// // efix() generates variates after rejection in REXP. + +// double RNG::efix(ulong j, ulong i) +// { +// double x; +// for (;;) +// { +// if (i == 0) +// return (7.69711 - log(rand_open01())); + +// x = j * we[i]; +// if (fe[i] + rand_open01() * (fe[i - 1] - fe[i]) < exp(-x)) +// return (x); + +// j = rand_int32(); +// i = (j & 255); +// if (j < ke[i]) +// return (j * we[i]); +// } + +// } // RNG::efix + +// // __________________________________________________________________________ +// // This procedure creates the tables used by RNOR and REXP + +// void RNG::zigset() +// { +// const double m1 = 2147483648.0; // 2^31 +// const double m2 = 4294967296.0; // 2^32 + +// const double vn = 9.91256303526217e-3; +// const double ve = 3.949659822581572e-3; + +// double dn = 3.442619855899, tn = dn; +// double de = 7.697117470131487, te = de; + +// int i; + +// // Set up tables for RNOR +// double q = vn / exp(-.5 * dn * dn); +// kn[0] = (ulong) ((dn / q) * m1); +// kn[1] = 0; +// wn[0] = q / m1; +// wn[127] = dn / m1; +// fn[0]=1.; +// fn[127] = exp(-.5 * dn * dn); +// for(i = 126; i >= 1; i--) +// { +// dn = sqrt(-2 * log(vn / dn + exp(-.5 * dn * dn))); +// kn[i + 1] = (ulong) ((dn / tn) * m1); +// tn = dn; +// fn[i] = exp(-.5 * dn * dn); +// wn[i] = dn / m1; +// } + +// // Set up tables for REXP +// q = ve / exp(-de); +// ke[0] = (ulong) ((de / q) * m2); +// ke[1] = 0; +// we[0] = q / m2; +// we[255] = de / m2; +// fe[0] = 1.; +// fe[255] = exp(-de); +// for (i = 254; i >= 1; i--) +// { +// de = -log(ve / de + exp(-de)); +// ke[i+1] = (ulong) ((de / te) * m2); +// te = de; +// fe[i] = exp(-de); +// we[i] = de / m2; +// } + +// } // RNG::zigset + +// // __________________________________________________________________________ +// // Generate a gamma variate with parameters 'shape' and 'scale' + +// double RNG::gamma(double shape, double scale) +// { +// if (shape < 1) +// return gamma(shape + 1, scale) * pow(rand_open01(), 1.0 / shape); + +// const double d = shape - 1.0 / 3.0; +// const double c = 1.0 / sqrt(9.0 * d); +// double x, v, u; +// for (;;) { +// do { +// x = RNOR(); +// v = 1.0 + c * x; +// } while (v <= 0.0); +// v = v * v * v; +// u = rand_open01(); +// if (u < 1.0 - 0.0331 * x * x * x * x) +// return (d * v / scale); +// if (log(u) < 0.5 * x * x + d * (1.0 - v + log(v))) +// return (d * v / scale); +// } + +// } // RNG::gamma + +// // __________________________________________________________________________ +// // gammalog returns the logarithm of the gamma function. From Numerical +// // Recipes. + +// double gammalog(double xx) +// { +// static double cof[6]={ +// 76.18009172947146, -86.50532032941677, 24.01409824083091, +// -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5}; + +// double x = xx; +// double y = xx; +// double tmp = x + 5.5; +// tmp -= (x + 0.5) * log(tmp); +// double ser=1.000000000190015; +// for (int j=0; j<=5; j++) +// ser += cof[j] / ++y; +// return -tmp + log(2.5066282746310005 * ser / x); +// } + +// // __________________________________________________________________________ +// // Generate a Poisson variate +// // This is essentially the algorithm from Numerical Recipes + +// double RNG::poisson(double lambda) +// { +// static double sq, alxm, g, oldm = -1.0; +// double em, t, y; + +// if (lambda < 12.0) { +// if (lambda != oldm) { +// oldm = lambda; +// g = exp(-lambda); +// } +// em = -1; +// t = 1.0; +// do { +// ++em; +// t *= rand_open01(); +// } while (t > g); +// } else { +// if (lambda != oldm) { +// oldm = lambda; +// sq = sqrt(2.0 * lambda); +// alxm = log(lambda); +// g = lambda * alxm - gammalog(lambda + 1.0); +// } +// do { +// do { +// y = tan(PI * rand_open01()); +// em = sq * y + lambda; +// } while (em < 0.0); +// em = floor(em); +// t = 0.9 * (1.0 + y * y) * exp(em * alxm - gammalog(em + 1.0)-g); +// } while (rand_open01() > t); +// } +// return em; + +// } // RNG::poisson + +// // __________________________________________________________________________ +// // Generate a binomial variate +// // This is essentially the algorithm from Numerical Recipes + +// int RNG::binomial(double pp, int n) +// { +// if(n==0) return 0; +// if(pp==0.0) return 0; +// if(pp==1.0) return n; +// double p = (pp<0.5 ? pp : 1.0-pp); +// double am = n*p; +// int bnl = 0; +// if(n<25) { +// for(int j=n; j--; ) if(rand_closed01()= en + 1.0); +// em = floor(em); +// t = 1.2 * sq * (1 + y * y) * exp(oldg - gammalog(em + 1.0) - +// gammalog(en - em + 1.0) + em * log(p) + (en - em) * log(pc)); +// } while (rand_closed01() > t); +// bnl = int(em); +// } +// if (p!=pp) bnl=n-bnl; +// return bnl; +// } // RNG::binomial + +// __________________________________________________________________________ +// rng.C diff --git a/src/games/degree_sequence_vl/gengraph_random.h b/src/games/degree_sequence_vl/gengraph_random.h new file mode 100644 index 0000000..db77c3a --- /dev/null +++ b/src/games/degree_sequence_vl/gengraph_random.h @@ -0,0 +1,213 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef RNG_H +#define RNG_H + +#include "igraph_random.h" + +namespace KW_RNG { + +typedef signed int sint; +typedef unsigned int uint; +typedef signed long slong; +typedef unsigned long ulong; + +class RNG { +public: + RNG() { } + RNG(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) { + IGRAPH_UNUSED(z_); IGRAPH_UNUSED(w_); IGRAPH_UNUSED(jsr_); + IGRAPH_UNUSED(jcong_); + }; + ~RNG() { } + + void init(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) { + IGRAPH_UNUSED(z_); IGRAPH_UNUSED(w_); IGRAPH_UNUSED(jsr_); + IGRAPH_UNUSED(jcong_); + } + long rand_int31() { + return RNG_INTEGER(0, 0x7fffffff); + } + double rand_halfopen01() { // (0,1] + return RNG_UNIF01(); + } + int binomial(double pp, int n) { + return RNG_BINOM(n, pp); + } +}; + +} // namespace KW_RNG + +/* This was the original RNG, but now we use the igraph version */ + +// __________________________________________________________________________ +// random.h - a Random Number Generator Class +// random.cpp - contains the non-inline class methods + +// __________________________________________________________________________ +// This C++ code uses the simple, very fast "KISS" (Keep It Simple +// Stupid) random number generator suggested by George Marsaglia in a +// Usenet posting from 1999. He describes it as "one of my favorite +// generators". It generates high-quality random numbers that +// apparently pass all commonly used tests for randomness. In fact, it +// generates random numbers by combining the results of three other good +// random number generators that have different periods and are +// constructed from completely different algorithms. It does not have +// the ultra-long period of some other generators - a "problem" that can +// be fixed fairly easily - but that seems to be its only potential +// problem. The period is about 2^123. + +// The ziggurat method of Marsaglia is used to generate exponential and +// normal variates. The method as well as source code can be found in +// the article "The Ziggurat Method for Generating Random Variables" by +// Marsaglia and Tsang, Journal of Statistical Software 5, 2000. + +// The method for generating gamma variables appears in "A Simple Method +// for Generating Gamma Variables" by Marsaglia and Tsang, ACM +// Transactions on Mathematical Software, Vol. 26, No 3, Sep 2000, pages +// 363-372. + +// The code for Poisson and Binomial random numbers comes from +// Numerical Recipes in C. + +// Some of this code is unlikely to work correctly as is on 64 bit +// machines. + +// #include +// #include +// #ifdef _WIN32 +// #include +// #define getpid _getpid +// #else +// #include +// #endif + +// //#ifdef _WIN32 +// static const double PI = 3.1415926535897932; +// static const double AD_l = 0.6931471805599453; +// static const double AD_a = 5.7133631526454228; +// static const double AD_b = 3.4142135623730950; +// static const double AD_c = -1.6734053240284925; +// static const double AD_p = 0.9802581434685472; +// static const double AD_A = 5.6005707569738080; +// static const double AD_B = 3.3468106480569850; +// static const double AD_H = 0.0026106723602095; +// static const double AD_D = 0.0857864376269050; +// //#endif //_WIN32 + +// namespace KW_RNG { + +// class RNG +// { +// private: +// ulong z, w, jsr, jcong; // Seeds + +// ulong kn[128], ke[256]; +// double wn[128],fn[128], we[256],fe[256]; + +// /* +// #ifndef _WIN32 +// static const double PI = 3.1415926535897932; +// static const double AD_l = 0.6931471805599453; +// static const double AD_a = 5.7133631526454228; +// static const double AD_b = 3.4142135623730950; +// static const double AD_c = -1.6734053240284925; +// static const double AD_p = 0.9802581434685472; +// static const double AD_A = 5.6005707569738080; +// static const double AD_B = 3.3468106480569850; +// static const double AD_H = 0.0026106723602095; +// static const double AD_D = 0.0857864376269050; +// #endif //_WIN32 +// */ + +// public: +// RNG() { init(); zigset(); } +// RNG(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) : +// z(z_), w(w_), jsr(jsr_), jcong(jcong_) { zigset(); } +// ~RNG() { } + + +// inline ulong znew() +// { return (z = 36969 * (z & 65535) + (z >> 16)); } +// inline ulong wnew() +// { return (w = 18000 * (w & 65535) + (w >> 16)); } +// inline ulong MWC() +// { return (((znew() & 65535) << 16) + wnew()); } +// inline ulong SHR3() +// { jsr ^= ((jsr & 32767) << 17); jsr ^= (jsr >> 13); return (jsr ^= ((jsr << 5) & 0xFFFFFFFF)); } +// inline ulong CONG() +// { return (jcong = (69069 * jcong + 1234567) & 0xFFFFFFFF); } +// inline double RNOR() { +// slong h = rand_int32(); +// ulong i = h & 127; +// return (((ulong) abs((sint) h) < kn[i]) ? h * wn[i] : nfix(h, i)); +// } +// inline double REXP() { +// ulong j = rand_int32(); +// ulong i = j & 255; +// return ((j < ke[i]) ? j * we[i] : efix(j, i)); +// } + +// double nfix(slong h, ulong i); +// double efix(ulong j, ulong i); +// void zigset(); + +// inline void init() +// { ulong yo = time(0) + getpid(); +// z = w = jsr = jcong = yo; } +// inline void init(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) +// { z = z_; w = w_; jsr = jsr_; jcong = jcong_; } + +// inline ulong rand_int32() // [0,2^32-1] +// { return ((MWC() ^ CONG()) + SHR3()) & 0xFFFFFFFF; } +// inline long rand_int31() // [0,2^31-1] +// { return long(rand_int32() >> 1);} +// inline double rand_closed01() // [0,1] +// { return ((double) rand_int32() / 4294967295.0); } +// inline double rand_open01() // (0,1) +// { return (((double) rand_int32() + 0.5) / 4294967296.0); } +// inline double rand_halfclosed01() // [0,1) +// { return ((double) rand_int32() / 4294967296.0); } +// inline double rand_halfopen01() // (0,1] +// { return (((double) rand_int32() + 0.5) / 4294967295.5); } + +// // Continuous Distributions +// inline double uniform(double x = 0.0, double y = 1.0) +// { return rand_closed01() * (y - x) + x; } +// inline double normal(double mu = 0.0, double sd = 1.0) +// { return RNOR() * sd + mu; } +// inline double exponential(double lambda = 1) +// { return REXP() / lambda; } +// double gamma(double shape = 1, double scale = 1); +// double chi_square(double df) +// { return gamma(df / 2.0, 0.5); } +// double beta(double a1, double a2) +// { double x1 = gamma(a1, 1); return (x1 / (x1 + gamma(a2, 1))); } + +// // Discrete Distributions +// double poisson(double lambda); +// int binomial(double pp, int n); + +// }; // class RNG + +// } // namespace + +#endif // RNG_H diff --git a/src/games/dotproduct.c b/src/games/dotproduct.c new file mode 100644 index 0000000..9281d3f --- /dev/null +++ b/src/games/dotproduct.c @@ -0,0 +1,102 @@ +/* + igraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_blas.h" +#include "igraph_constructors.h" +#include "igraph_random.h" + +/** + * \function igraph_dot_product_game + * \brief Generates a random dot product graph. + * + * In this model, each vertex is represented by a latent + * position vector. Probability of an edge between two vertices are given + * by the dot product of their latent position vectors. + * + * + * See also Christine Leigh Myers Nickel: Random dot product graphs, a + * model for social networks. Dissertation, Johns Hopkins University, + * Maryland, USA, 2006. + * + * \param graph The output graph is stored here. + * \param vecs A matrix in which each latent position vector is a + * column. The dot product of the latent position vectors should be + * in the [0,1] interval, otherwise a warning is given. For + * negative dot products, no edges are added; dot products that are + * larger than one always add an edge. + * \param directed Should the generated graph be directed? + * \return Error code. + * + * Time complexity: O(n*n*m), where n is the number of vertices, + * and m is the length of the latent vectors. + * + * \sa \ref igraph_rng_sample_dirichlet(), \ref + * igraph_rng_sample_sphere_volume(), \ref igraph_rng_sample_sphere_surface() + * for functions to generate the latent vectors. + */ + +igraph_error_t igraph_dot_product_game(igraph_t *graph, const igraph_matrix_t *vecs, + igraph_bool_t directed) { + + igraph_int_t nrow = igraph_matrix_nrow(vecs); + igraph_int_t ncol = igraph_matrix_ncol(vecs); + igraph_int_t i, j; + igraph_vector_int_t edges; + igraph_bool_t warned_neg = false, warned_big = false; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + for (i = 0; i < ncol; i++) { + igraph_int_t from = directed ? 0 : i + 1; + const igraph_vector_t v1 = igraph_vector_view(&MATRIX(*vecs, 0, i), nrow); + for (j = from; j < ncol; j++) { + igraph_real_t prob; + const igraph_vector_t v2 = igraph_vector_view(&MATRIX(*vecs, 0, j), nrow); + + if (i == j) { + continue; + } + IGRAPH_CHECK(igraph_blas_ddot(&v1, &v2, &prob)); + if (prob < 0 && ! warned_neg) { + warned_neg = true; + IGRAPH_WARNING("Negative connection probability in dot-product graph."); + } else if (prob > 1 && ! warned_big) { + warned_big = true; + IGRAPH_WARNING("Greater than 1 connection probability in dot-product graph."); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } else if (RNG_UNIF01() < prob) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, ncol, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/erdos_renyi.c b/src/games/erdos_renyi.c new file mode 100644 index 0000000..7d08e40 --- /dev/null +++ b/src/games/erdos_renyi.c @@ -0,0 +1,851 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "internal/utils.h" +#include "math/safe_intop.h" +#include "misc/graphicality.h" +#include "random/random_internal.h" + +/** + * \section about_erdos_renyi + * + * + * There are two classic random graph models referred to as the Erdős-Rényi + * random graph, or sometimes simply \em the random graph. Both fix the vertex + * count n, but while the G(n,m) model prescribes precisely m edges, the G(n,p) + * model connects all vertex pairs independently with probability p. While + * these models look superficially different, when n is large they behave in + * a similar manner. G(n,m) graphs have a density of exactly + * p = m / m_max, while G(n,p) graphs have m = p m_max + * edges on \em average, where \c m_max is the number of vertex pairs. Indeed, + * these two models turns out to be two sides of the same coin: both can be + * understood as maximum entropy models with a constraint on the number of + * edges. The G(n,m) is obtained from a sharp constraint, while G(n,p) from + * an average constraint (soft constraint). + * + * + * + * The maximum entropy framework allows for rigorous generalizations of these + * models to various scenarios, of which igraph supports many, such as models + * defined over directed graphs, bipartite graphs, multigraphs, or even over + * edge-labelled graphs. Constraining edge counts between various subsets of + * vertices yields further families of related models, such as + * \ref igraph_sbm_game() (given connection probabilities between categories) + * or \ref igraph_degree_sequence_game() (given incident edge counts, i.e. + * degrees, for each vertex). + * + */ + + +static igraph_error_t iea_game( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops) { + + igraph_vector_int_t edges; + int iter = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, m * 2)); + + for (igraph_int_t i = 0; i < m; i++) { + igraph_int_t from, to; + from = RNG_INTEGER(0, n - 1); + if (loops) { + to = RNG_INTEGER(0, n - 1); + } else { + to = RNG_INTEGER(0, n - 2); + if (from == to) { + to = n - 1; + } + } + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Uniform sampling of multigraphs from G(n,m) */ +static igraph_error_t gnm_multi( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops) { + + /* Conceptually, uniform multigraph sampling works as follows: + * + * - Consider a list containing all vertex pairs. Use unordered pairs for + * undirected graphs, ordered pairs for directed ones, and include + * self-pairs if desired. + * - Pick a list element uniformly at random with replacement and append + * it to the list. Add the corresponding edge to the graph. + * - Continue picking elements form the list uniformly at random and + * appending the pick to the list until we have sampled the desired + * number of edges. + * + * Let's illustrate how this is implemented on the directed case with loops + * allowed. Each element of the n*n adjacency matrix corresponds to an + * ordered vertex pair. Uniformly sampling a matrix element is possible + * by generating two random matrix indices. As an analog of appending to the + * list of pairs, we extend the matrix with additional rows. The last row + * will generally be incomplete, so we use rejection sampling to avoid + * exceeding its length. + * + * In the undirected case, we still sample _ordered_ vertex pairs for + * simplicity. To account for the resulting duplication, we append the + * sampled pair _twice_. In the undirected case with loops, we must also + * duplicate the matrix diagonal. + */ + + igraph_vector_int_t edges; + igraph_int_t nrow, ncol; + igraph_int_t last; /* column index of last element in last row */ + int iter = 0; + + /* Constraining n and m by IGRAPH_VCOUNT_MAX and IGRAPH_ECOUNT_MAX, + * as done in the caller, is sufficient to prevent overflow, + * except for the one special case below: + */ + + if (!directed && !loops && + n == IGRAPH_VCOUNT_MAX && m == IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Too many edges or vertices for G(n,m) multigraph.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*m); + + if (directed && loops) { + nrow = ncol = n; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= n) { + igraph_int_t j = (r - n) * ncol + c; + if (IGRAPH_UNLIKELY(j >= i)) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*j]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*j+1]; + } else { + VECTOR(edges)[2*i] = r; + VECTOR(edges)[2*i+1] = c; + } + + last += 1; + if (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (directed && !loops) { + nrow = n; + ncol = n-1; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= n) { + igraph_int_t j = (r - n) * ncol + c; + if (IGRAPH_UNLIKELY(j >= i)) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*j]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*j+1]; + } else { + + /* Eliminate self-loops. */ + if (c == r) { + c = n-1; + } + + VECTOR(edges)[2*i] = r; + VECTOR(edges)[2*i+1] = c; + } + + last += 1; + if (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (!directed && loops) { + nrow = n; + ncol = n+1; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= n) { + igraph_int_t j = (r - n) * ncol + c; + if (IGRAPH_UNLIKELY(j >= 2*i)) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*(j/2)]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*(j/2)+1]; + } else { + + /* Two chances to sample from matrix diagonal, + * when c == r and when c == n. */ + if (c == n) { + c = r; + } + + VECTOR(edges)[2*i] = r; + VECTOR(edges)[2*i+1] = c; + } + + last += 2; + while (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else /* !directed && !loops */ { + nrow = n; + ncol = n-1; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= n) { + igraph_int_t j = (r - n) * ncol + c; + if (IGRAPH_UNLIKELY(j >= 2*i)) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*(j/2)]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*(j/2)+1]; + } else { + + /* Eliminate self-loops. */ + if (c == r) { + c = n-1; + } + + VECTOR(edges)[2*i] = r; + VECTOR(edges)[2*i+1] = c; + } + + last += 2; + while (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Uniform sampling of simple graphs (with loops) from G(n,m) */ +static igraph_error_t gnm_simple( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops, + igraph_bool_t edge_labeled) { + + /* This function uses doubles in its `s` vector, and for `maxedges` and `last`. + * This is because on a system with 32-bit ints, maxedges will be larger than + * IGRAPH_INTEGER_MAX and this will cause overflows when calculating `from` and `to` + * for tests on large graphs. This is also why we need a 'real' version of random_sample. + */ + igraph_real_t n_real = (igraph_real_t) n; /* for divisions below */ + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + int iter = 0; + + + igraph_real_t maxedges = n; + if (directed && loops) { + maxedges *= n; + } else if (directed && !loops) { + maxedges *= (n - 1); + } else if (!directed && loops) { + maxedges *= (n + 1) / 2.0; + } else { + maxedges *= (n - 1) / 2.0; + } + + if (m > maxedges) { + IGRAPH_ERROR( + "Too many edges requested compared to the number of vertices for G(n,m) model.", + IGRAPH_EINVAL); + } + + if (maxedges == m && ! edge_labeled) { + /* TODO: Cannot use igraph_full() when edge_labeled as we must shuffle edges. */ + IGRAPH_CHECK(igraph_full(graph, n, directed, loops)); + } else { + igraph_int_t slen; + + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_i_random_sample_real(&s, 0, maxedges - 1, m)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, igraph_vector_size(&s) * 2)); + + slen = igraph_vector_size(&s); + if (directed && loops) { + for (igraph_int_t i = 0; i < slen; i++) { + igraph_int_t to = trunc(VECTOR(s)[i] / n_real); + igraph_int_t from = VECTOR(s)[i] - to * n_real; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (directed && !loops) { + for (igraph_int_t i = 0; i < slen; i++) { + igraph_int_t from = trunc(VECTOR(s)[i] / (n_real - 1)); + igraph_int_t to = VECTOR(s)[i] - from * (n_real - 1); + if (from == to) { + to = n - 1; + } + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (!directed && loops) { + for (igraph_int_t i = 0; i < slen; i++) { + igraph_int_t to = trunc((sqrt(8 * VECTOR(s)[i] + 1) - 1) / 2); + igraph_int_t from = VECTOR(s)[i] - (((igraph_real_t)to) * (to + 1)) / 2; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else { /* !directed && !loops */ + for (igraph_int_t i = 0; i < slen; i++) { + igraph_int_t to = trunc((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2); + igraph_int_t from = VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + + if (edge_labeled) { + IGRAPH_CHECK(igraph_i_vector_int_shuffle_pairs(&edges)); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup generators + * \function igraph_erdos_renyi_game_gnm + * \brief Generates a random (Erdős-Rényi) graph with a fixed number of edges. + * + * In the G(n, m) Erdős-Rényi model, a graph with \p n vertices + * and \p m edges is generated uniformly at random. + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param m The number of edges in the graph. + * \param directed Whether to generate a directed graph. + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are generated. See \ref igraph_edge_type_sw_t. + * \param edge_labeled If true, the sampling is done uniformly from the set + * of ordered edge lists. See \ref igraph_iea_game() for more information. + * Set this to \c false to select the classic Erdős-Rényi model. + * The constants \c IGRAPH_EDGE_UNLABELED and \c IGRAPH_EDGE_LABELED + * may be used instead of \c false and \c true for better readability. + * \return Error code: + * \c IGRAPH_EINVAL: invalid \p n or \p m parameter. + * \c IGRAPH_ENOMEM: there is not enough memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_erdos_renyi_game_gnp() to sample from the related + * G(n, p) model, which constrains the \em expected edge count; + * \ref igraph_iea_game() to generate multigraph by assigning edges to vertex + * pairs uniformly and independently; + * \ref igraph_degree_sequence_game() to constrain the degree sequence; + * \ref igraph_bipartite_game_gnm() for the bipartite version of this model; + * \ref igraph_barabasi_game() and \ref igraph_growing_random_game() for other + * commonly used random graph models. + * + * \example examples/simple/igraph_erdos_renyi_game_gnm.c + */ +igraph_error_t igraph_erdos_renyi_game_gnm( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled) { + + igraph_bool_t loops, multiple; + + /* The multigraph implementation relies on the below checks to avoid overflow. */ + if (n < 0 || n > IGRAPH_VCOUNT_MAX) { + IGRAPH_ERROR("Invalid number of vertices for G(n,m) model.", IGRAPH_EINVAL); + } + if (m < 0 || m > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Invalid number of edges for G(n,m) model.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + /* Special cases of "too many edges" that also apply to multigraphs: + * - The null graph cannot have edges. + * - The singleton graph cannot have edges unless loops are allowed. + */ + if (m > 0 && ((n == 0) || (!loops && n == 1))) { + IGRAPH_ERROR( + "Too many edges requested compared to the number of vertices for G(n,m) model.", + IGRAPH_EINVAL); + } + + if (m == 0) { + return igraph_empty(graph, n, directed); + } + + if (edge_labeled) { + if (multiple) { + return iea_game(graph, n, m, directed, loops); + } else { + return gnm_simple(graph, n, m, directed, loops, /*edge_labeled=*/ true); + } + } else { + if (multiple) { + return gnm_multi(graph, n, m, directed, loops); + } else { + return gnm_simple(graph, n, m, directed, loops, /*edge_labeled=*/ false); + } + } +} + + +/** + * \ingroup generators + * \function igraph_iea_game + * \brief Generates a random multigraph through independent edge assignment. + * + * \experimental + * + * This model generates random multigraphs on \p n vertices with \p m edges + * through independent edge assignment (IEA). Each of the \p m edges is assigned + * uniformly at random to an \em ordered vertex pair, independently of each + * other. + * + * + * This model does not sample multigraphs uniformly. Undirected graphs are + * generated with probability proportional to + * + * + * (prod_(i<j) A_ij ! prod_i A_ii !!)^(-1), + * + * + * where \c A denotes the adjacency matrix and !! denotes + * the double factorial. Here \c A is assumed to have twice the number of + * self-loops on its diagonal. The corresponding expression for directed + * graphs is + * + * + * (prod_(i,j) A_ij !)^(-1). + * + * + * Thus the probability of all simple graphs (which only have 0s and 1s in + * the adjacency matrix) is the same, while that of non-simple ones depends + * on their edge and self-loop multiplicities. + * + * + * An alternative way to think of this model is that it performs uniform + * sampling of \em edge-labeled graphs, i.e. graphs in which not only vertices, + * but also edges carry unique identities and are distinguishable. + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param m The number of edges in the graph. + * \param directed Whether to generate a directed graph. + * \param loops Whether to generate self-loops. + * \return Error code: + * \c IGRAPH_EINVAL: invalid \p n or \p m parameter. + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_erdos_renyi_game_gnm() to uniformly sample graphs with + * a given number of vertices and edges. + */ +igraph_error_t igraph_iea_game( + igraph_t *graph, + igraph_int_t n, igraph_int_t m, + igraph_bool_t directed, igraph_bool_t loops) { + + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_MULTI_SW; + if (loops) { + allowed_edge_types |= IGRAPH_LOOPS_SW; + } + return igraph_erdos_renyi_game_gnm(graph, n, m, directed, allowed_edge_types, true); +} + +/* This G(n,p) implementation is used only with very large vertex counts, above + * sqrt(MAX_EXACT_REAL) ~ 100 million, when the default implementation would + * fail due to overflow. While this version avoids overflow and uses less memory, + * it is also slower than the default implementation. + * + * This function expects that when multiple=true, the p parameter has already + * been transformed by p = p / (1 + p). This is currently done by the caller. + */ +static igraph_error_t gnp_large( + igraph_t *graph, igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, igraph_bool_t loops, igraph_bool_t multiple, + igraph_int_t ecount_estimate +) { + + igraph_vector_int_t edges; + int iter = 0; + + /* Necessitated by floating point arithmetic used in the implementation. */ + if (n >= IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Number of vertices is too large.", IGRAPH_EOVERFLOW); + } + + if (ecount_estimate > IGRAPH_ECOUNT_MAX) { + ecount_estimate = IGRAPH_ECOUNT_MAX; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 2*ecount_estimate)); + + for (igraph_int_t i=0; i < n; i++) { + igraph_int_t j = directed ? 0 : i; + + while (true) { + igraph_real_t gap = RNG_GEOM(p); + + /* This formulation not only terminates the loop when necessary, + * but also protects against overflow when 'p' is very small + * and 'gap' becomes very large, perhaps larger than representable + * in an igraph_int_t. */ + if (gap >= n - j) { + break; + } + + j += gap; + + if (loops || i != j) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + + j += ! multiple; /* 1 for simple graph, 0 for multigraph */ + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t gnp_edge_labeled( + igraph_t *graph, + igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, + igraph_bool_t loops, igraph_bool_t multiple) { + + if (multiple) { + igraph_real_t maxedges = n; + + if (directed && loops) { + maxedges *= n; + } else if (directed && !loops) { + maxedges *= (n - 1); + } else if (!directed && loops) { + maxedges *= (n + 1) / 2.0; + } else { + maxedges *= (n - 1) / 2.0; + } + + igraph_real_t m; + do { + m = RNG_GEOM( 1.0 / (1.0 + maxedges * p) ); + } while (m > (igraph_real_t) IGRAPH_INTEGER_MAX); + + return iea_game(graph, n, m, directed, loops); + } else { + IGRAPH_ERROR("The edge-labeled G(n,p) model is not yet implemented for graphs without multi-edges.", + IGRAPH_UNIMPLEMENTED); + } +} + +/** + * \ingroup generators + * \function igraph_erdos_renyi_game_gnp + * \brief Generates a random (Erdős-Rényi) graph with fixed edge probabilities. + * + * In the G(n, p) Erdős-Rényi model, also known as the Gilbert model, + * or Bernoulli random graph, a graph with \p n vertices is generated such that + * every possible edge is included in the graph independently with probability + * \p p. This is equivalent to a maximum entropy random graph model model with + * a constraint on the \em expected edge count. The maximum entropy view allows + * for extending the model to multigraphs, as discussed by Park and Newman (2004), + * section III.D. In this case, \p p is interpreted as the expected number of + * edges between any vertex pair. + * + * + * Setting p = 1/2 and multiple = false generates all + * graphs without multi-edges on \p n vertices with the same probability. + * + * + * For both simple and multigraphs, the expected mean degree of the graph is + * approximately p n; set p = k/n when a mean degree + * of approximately \c k is desired. More precisely, the expected mean degree is + * p(n-1) in (undirected or directed) graphs without self-loops, + * p(n+1) in undirected graphs with self-loops, and + * p n in directed graphs with self-loops. + * + * + * When generating multigraphs, the distribution of the edge multiplicities is + * geometric, i.e. the probability of finding \c m edges between two vertices + * is q (1-q)^m, where q = 1 / (1+p). + * + * + * This function uses the sequential geometric sampling technique described in + * Batagelj and Brandes (2005), with a modification to handle multigraphs. + * + * + * References: + * + * + * J. Park and M. E. J. Newman: "Statistical Mechanics of Networks". + * Phys. Rev. E 70, 066117 (2004). + * https://doi.org/10.1103/PhysRevE.70.066117 + * + * + * V. Batagelj and U. Brandes: "Efficient Generation of Large Random Networks". + * Phys. Rev. E 71, 036113 (2005). + * https://doi.org/10.1103/PhysRevE.71.036113 + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph. + * \param p The expected number of edges between any vertex pair. + * When multi-edges are disallowed, this is equivalent to the probability + * of having a connection between any two vertices. + * \param directed Whether to generate a directed graph. + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are generated. See \ref igraph_edge_type_sw_t. + * \param edge_labeled If true, the model is defined over the set of ordered + * edge lists, i.e. over the set of edge-labeled graphs. Set it to + * \c false to select the classic Erdős-Rényi model. + * The constants \c IGRAPH_EDGE_UNLABELED and \c IGRAPH_EDGE_LABELED + * may be used instead of \c false and \c true for better readability. + * \return Error code: + * \c IGRAPH_EINVAL: invalid \p n or \p p parameter. + * \c IGRAPH_ENOMEM: there is not enough memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_erdos_renyi_game_gnm() to generate random graphs with + * a sharply fixed edge count; \ref igraph_chung_lu_game() and + * \ref igraph_static_fitness_game() to generate random graphs with a + * fixed expected degree sequence; \ref igraph_bipartite_game_gnm() for the + * bipartite version of this model; \ref igraph_barabasi_game() and + * \ref igraph_growing_random_game() for other commonly used random graph models. + * + * \example examples/simple/igraph_erdos_renyi_game_gnp.c + */ +igraph_error_t igraph_erdos_renyi_game_gnp( + igraph_t *graph, + igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled) { + + /* This function uses doubles in its `s` vector, and for `maxedges` and `last`. + * This is because on a system with 32-bit ints, maxedges will be larger than + * IGRAPH_INTEGER_MAX and this will cause overflows when calculating `from` and `to` + * for tests on large graphs. + */ + igraph_int_t no_of_nodes = n; + igraph_real_t no_of_nodes_real = (igraph_real_t) no_of_nodes; /* for divisions below */ + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + igraph_bool_t loops, multiple; + int iter = 0; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices for G(n,p) model.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + if (multiple) { + if (p < 0.0) { + IGRAPH_ERROR("Invalid expected edge multiplicity given for G(n,p) multigraph model.", IGRAPH_EINVAL); + } + } else { + if (p < 0.0 || p > 1.0) { + IGRAPH_ERROR("Invalid probability given for G(n,p) model.", IGRAPH_EINVAL); + } + } + + if (edge_labeled) { + return gnp_edge_labeled(graph, n, p, directed, loops, multiple); + } + + if (multiple) { + /* Convert the expected edge count to the appropriate probability parameter + * of the geometric distribution when sampling lengths of runs of 0s in the + * adjacency matrix. */ + p = p / (1 + p); + } + + if (p == 0.0 || no_of_nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + } else if (! multiple && p == 1.0) { + IGRAPH_CHECK(igraph_full(graph, n, directed, loops)); + } else { + igraph_real_t maxedges = n, last; + igraph_int_t ecount_estimate, ecount; + + if (directed && loops) { + maxedges *= n; + } else if (directed && !loops) { + maxedges *= (n - 1); + } else if (!directed && loops) { + maxedges *= (n + 1) / 2.0; + } else { + maxedges *= (n - 1) / 2.0; + } + + IGRAPH_CHECK(igraph_i_safe_floor(maxedges * p * 1.1, &ecount_estimate)); + + if (maxedges > IGRAPH_MAX_EXACT_REAL) { + /* Use a slightly slower, but overflow-free implementation. */ + return gnp_large(graph, n, p, directed, loops, multiple, ecount_estimate); + } + + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, ecount_estimate)); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */ + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + ecount = igraph_vector_size(&s); + if (ecount > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 2*ecount)); + + iter = 0; + if (directed && loops) { + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t to = trunc(VECTOR(s)[i] / no_of_nodes_real); + igraph_int_t from = VECTOR(s)[i] - to * no_of_nodes_real; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (directed && !loops) { + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t to = trunc(VECTOR(s)[i] / no_of_nodes_real); + igraph_int_t from = VECTOR(s)[i] - to * no_of_nodes_real; + if (from == to) { + to = no_of_nodes - 1; + } + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else if (!directed && loops) { + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t to = trunc((sqrt(8 * VECTOR(s)[i] + 1) - 1) / 2); + igraph_int_t from = VECTOR(s)[i] - (((igraph_real_t)to) * (to + 1)) / 2; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } else { /* !directed && !loops */ + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t to = trunc((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2); + igraph_int_t from = VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2; + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/games/establishment.c b/src/games/establishment.c new file mode 100644 index 0000000..5bbf08e --- /dev/null +++ b/src/games/establishment.c @@ -0,0 +1,180 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_nongraph.h" +#include "igraph_random.h" + +/** + * \function igraph_establishment_game + * \brief Generates a graph with a simple growing model with vertex types. + * + * + * The simulation goes like this: a single vertex is added at each + * time step. This new vertex tries to connect to \p k vertices in the + * graph. The probability that such a connection is realized depends + * on the types of the vertices involved. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param types The number of vertex types. + * \param k The number of connections tried in each time step. + * \param type_dist Vector giving the distribution of vertex types. + * If \c NULL, the distribution is assumed to be uniform. + * \param pref_matrix Matrix giving the connection probabilities for + * different vertex types. + * \param directed Whether to generate a directed graph. + * \param node_type_vec An initialized vector or \c NULL. + * If not \c NULL, the type of each node will be stored here. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|*k*log(|V|)), |V| is the number of vertices + * and k is the \p k parameter. + */ +igraph_error_t igraph_establishment_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, igraph_int_t k, + const igraph_vector_t *type_dist, + const igraph_matrix_t *pref_matrix, + igraph_bool_t directed, + igraph_vector_int_t *node_type_vec) { + igraph_int_t i, j; + igraph_vector_int_t edges; + igraph_vector_t cumdist; + igraph_vector_int_t potneis; + igraph_real_t maxcum; + igraph_vector_int_t *nodetypes; + + /* Argument contracts */ + if (nodes < 0) { + IGRAPH_ERROR("The number of vertices must be non-negative.", IGRAPH_EINVAL); + } + + if (types < 1) { + IGRAPH_ERROR("The number of vertex types must be at least 1.", IGRAPH_EINVAL); + } + + if (type_dist) { + igraph_real_t lo; + + if (igraph_vector_size(type_dist) != types) { + IGRAPH_ERROR("The vertex type distribution vector must agree in length with the number of types.", + IGRAPH_EINVAL); + } + + lo = igraph_vector_min(type_dist); + if (lo < 0) { + IGRAPH_ERROR("The vertex type distribution vector must not contain negative values.", IGRAPH_EINVAL); + } + if (isnan(lo)) { + IGRAPH_ERROR("The vertex type distribution vector must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (igraph_matrix_nrow(pref_matrix) != types || igraph_matrix_ncol(pref_matrix) != types) { + IGRAPH_ERROR("The preference matrix must be square and agree in dimensions with the number of types.", IGRAPH_EINVAL); + } + + { + igraph_real_t lo, hi; + igraph_matrix_minmax(pref_matrix, &lo, &hi); /* matrix size is at least 1x1, safe to call minmax */ + + if (lo < 0 || hi > 1) { + IGRAPH_ERROR("The preference matrix must contain probabilities in [0, 1].", IGRAPH_EINVAL); + } + if (isnan(lo) || isnan(hi)) { + IGRAPH_ERROR("The preference matrix must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (! directed && ! igraph_matrix_is_symmetric(pref_matrix)) { + IGRAPH_ERROR("The preference matrix must be symmetric when generating undirected graphs.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&potneis, k); + + if (type_dist) { + VECTOR(cumdist)[0] = 0; + for (i = 0; i < types; ++i) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + } else { + for (i = 0; i < types+1; ++i) { + VECTOR(cumdist)[i] = i; + } + } + maxcum = igraph_vector_tail(&cumdist); + + if (maxcum <= 0) { + IGRAPH_ERROR("The vertex type distribution vector must contain at least one positive value.", IGRAPH_EINVAL); + } + + if (node_type_vec) { + nodetypes = node_type_vec; + IGRAPH_CHECK(igraph_vector_int_resize(nodetypes, nodes)); + } else { + nodetypes = IGRAPH_CALLOC(1, igraph_vector_int_t); + if (! nodetypes) { + IGRAPH_ERROR("Insufficient memory for establishment_game.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, nodetypes); + IGRAPH_VECTOR_INT_INIT_FINALLY(nodetypes, nodes); + } + + for (i = 0; i < nodes; i++) { + igraph_real_t uni = RNG_UNIF(0, maxcum); + igraph_int_t type; + igraph_vector_binsearch(&cumdist, uni, &type); + VECTOR(*nodetypes)[i] = type - 1; + } + + for (i = k; i < nodes; i++) { + igraph_int_t type1 = VECTOR(*nodetypes)[i]; + igraph_random_sample(&potneis, 0, i - 1, k); + for (j = 0; j < k; j++) { + igraph_int_t type2 = VECTOR(*nodetypes)[VECTOR(potneis)[j]]; + if (RNG_UNIF01() < MATRIX(*pref_matrix, type1, type2)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, VECTOR(potneis)[j])); + } + } + } + + if (! node_type_vec) { + igraph_vector_int_destroy(nodetypes); + IGRAPH_FREE(nodetypes); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_int_destroy(&potneis); + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/forestfire.c b/src/games/forestfire.c new file mode 100644 index 0000000..9d85e94 --- /dev/null +++ b/src/games/forestfire.c @@ -0,0 +1,257 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_progress.h" + +#include "core/interruption.h" + +typedef struct igraph_i_forest_fire_data_t { + igraph_vector_int_t *inneis; + igraph_vector_int_t *outneis; + igraph_int_t no_of_nodes; +} igraph_i_forest_fire_data_t; + + +static void igraph_i_forest_fire_free(igraph_i_forest_fire_data_t *data) { + for (igraph_int_t i = 0; i < data->no_of_nodes; i++) { + igraph_vector_int_destroy(data->inneis + i); + igraph_vector_int_destroy(data->outneis + i); + } +} + +/** + * \function igraph_forest_fire_game + * \brief Generates a network according to the \quote forest fire game \endquote. + * + * The forest fire model intends to reproduce the following network + * characteristics, observed in real networks: + * \ilist + * \ili Heavy-tailed in- and out-degree distributions. + * \ili Community structure. + * \ili Densification power-law. The network is densifying in time, + * according to a power-law rule. + * \ili Shrinking diameter. The diameter of the network decreases in + * time. + * \endilist + * + * + * The network is generated in the following way. One vertex is added at + * a time. This vertex connects to (cites) ambs vertices already + * present in the network, chosen uniformly random. Now, for each cited + * vertex v we do the following procedure: + * \olist + * \oli We generate two random numbers, x and y, that are + * geometrically distributed with means p/(1-p) and + * rp(1-rp). (p is \p fw_prob, r is + * \p bw_factor.) The new vertex cites x outgoing neighbors + * and y incoming neighbors of v, from those which are + * not yet cited by the new vertex. If there are less than x or + * y such vertices available then we cite all of them. + * \oli The same procedure is applied to all the newly cited + * vertices. + * \endolist + * + * + * See also: + * Jure Leskovec, Jon Kleinberg and Christos Faloutsos. Graphs over time: + * densification laws, shrinking diameters and possible explanations. + * \emb KDD '05: Proceeding of the eleventh ACM SIGKDD international + * conference on Knowledge discovery in data mining \eme, 177--187, 2005. + * + * + * Note however, that the version of the model in the published paper is incorrect + * in the sense that it cannot generate the kind of graphs the authors + * claim. A corrected version is available from + * https://www.cs.cmu.edu/~jure/pubs/powergrowth-tkdd.pdf, our + * implementation is based on this. + * + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param fw_prob The forward burning probability. + * \param bw_factor The backward burning ratio. The backward burning + probability is calculated as bw_factor * fw_prob. + * \param pambs The number of ambassador vertices. + * \param directed Whether to create a directed graph. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_forest_fire_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t fw_prob, igraph_real_t bw_factor, + igraph_int_t pambs, igraph_bool_t directed) { + + igraph_vector_int_t visited; + igraph_int_t no_of_nodes = nodes, actnode, i; + igraph_vector_int_t edges; + igraph_vector_int_t *inneis, *outneis; + igraph_i_forest_fire_data_t data; + igraph_dqueue_int_t neiq; + igraph_int_t ambs = pambs; + igraph_real_t param_geom_out = 1 - fw_prob; + igraph_real_t param_geom_in = 1 - fw_prob * bw_factor; + + if (fw_prob < 0 || fw_prob >= 1) { + IGRAPH_ERROR("Forest fire model: 'fw_prob' must satisfy 0 <= fw_prob < 1.", + IGRAPH_EINVAL); + } + if (bw_factor * fw_prob < 0 || bw_factor * fw_prob >= 1) { + IGRAPH_ERROR("Forest fire model: 'bw_factor' must satisfy 0 <= bw_factor * fw_prob < 1.", + IGRAPH_EINVAL); + } + if (ambs < 0) { + IGRAPH_ERROR("Forest fire model: Number of ambassadors must not be negative.", + IGRAPH_EINVAL); + } + + if (ambs == 0) { + IGRAPH_CHECK(igraph_empty(graph, nodes, directed)); + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + inneis = IGRAPH_CALLOC(no_of_nodes, igraph_vector_int_t); + IGRAPH_CHECK_OOM(inneis, "Insufficient memory for forest fire model."); + IGRAPH_FINALLY(igraph_free, inneis); + + outneis = IGRAPH_CALLOC(no_of_nodes, igraph_vector_int_t); + IGRAPH_CHECK_OOM(outneis, "Insufficient memory for forest fire model."); + IGRAPH_FINALLY(igraph_free, outneis); + + data.inneis = inneis; + data.outneis = outneis; + data.no_of_nodes = no_of_nodes; + IGRAPH_FINALLY(igraph_i_forest_fire_free, &data); + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_vector_int_init(inneis + i, 0)); + IGRAPH_CHECK(igraph_vector_int_init(outneis + i, 0)); + } + + IGRAPH_CHECK(igraph_vector_int_init(&visited, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &visited); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&neiq, 10); + +#define ADD_EDGE_TO(nei) \ + if (VECTOR(visited)[(nei)] != actnode+1) { \ + VECTOR(visited)[(nei)] = actnode+1; \ + IGRAPH_CHECK(igraph_dqueue_int_push(&neiq, (nei))); \ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, actnode)); \ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, (nei))); \ + IGRAPH_CHECK(igraph_vector_int_push_back(outneis+actnode, (nei))); \ + IGRAPH_CHECK(igraph_vector_int_push_back(inneis+(nei), actnode)); \ + } + + IGRAPH_PROGRESS("Forest fire: ", 0.0, NULL); + + for (actnode = 1; actnode < no_of_nodes; actnode++) { + + IGRAPH_PROGRESS("Forest fire: ", 100.0 * actnode / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* We don't want to visit the current vertex */ + VECTOR(visited)[actnode] = actnode + 1; + + /* Choose ambassador(s) */ + for (i = 0; i < ambs; i++) { + igraph_int_t a = RNG_INTEGER(0, actnode - 1); + ADD_EDGE_TO(a); + } + + while (!igraph_dqueue_int_empty(&neiq)) { + igraph_int_t actamb = igraph_dqueue_int_pop(&neiq); + igraph_vector_int_t *outv = outneis + actamb; + igraph_vector_int_t *inv = inneis + actamb; + igraph_int_t no_in = igraph_vector_int_size(inv); + igraph_int_t no_out = igraph_vector_int_size(outv); + igraph_int_t neis_out = RNG_GEOM(param_geom_out); + igraph_int_t neis_in = RNG_GEOM(param_geom_in); + /* outgoing neighbors */ + if (neis_out >= no_out) { + for (i = 0; i < no_out; i++) { + igraph_int_t nei = VECTOR(*outv)[i]; + ADD_EDGE_TO(nei); + } + } else { + igraph_int_t oleft = no_out; + for (i = 0; i < neis_out && oleft > 0; ) { + igraph_int_t which = RNG_INTEGER(0, oleft - 1); + igraph_int_t nei = VECTOR(*outv)[which]; + VECTOR(*outv)[which] = VECTOR(*outv)[oleft - 1]; + VECTOR(*outv)[oleft - 1] = nei; + if (VECTOR(visited)[nei] != actnode + 1) { + ADD_EDGE_TO(nei); + i++; + } + oleft--; + } + } + /* incoming neighbors */ + if (neis_in >= no_in) { + for (i = 0; i < no_in; i++) { + igraph_int_t nei = VECTOR(*inv)[i]; + ADD_EDGE_TO(nei); + } + } else { + igraph_int_t ileft = no_in; + for (i = 0; i < neis_in && ileft > 0; ) { + igraph_int_t which = RNG_INTEGER(0, ileft - 1); + igraph_int_t nei = VECTOR(*inv)[which]; + VECTOR(*inv)[which] = VECTOR(*inv)[ileft - 1]; + VECTOR(*inv)[ileft - 1] = nei; + if (VECTOR(visited)[nei] != actnode + 1) { + ADD_EDGE_TO(nei); + i++; + } + ileft--; + } + } + + } /* while neiq not empty */ + + } /* actnode < no_of_nodes */ + +#undef ADD_EDGE_TO + + IGRAPH_PROGRESS("Forest fire: ", 100.0, NULL); + + igraph_dqueue_int_destroy(&neiq); + igraph_vector_int_destroy(&visited); + igraph_i_forest_fire_free(&data); + igraph_free(outneis); + igraph_free(inneis); + IGRAPH_FINALLY_CLEAN(5); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/grg.c b/src/games/grg.c new file mode 100644 index 0000000..246c9f7 --- /dev/null +++ b/src/games/grg.c @@ -0,0 +1,174 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_random.h" + +#include "core/interruption.h" + +/** + * \function igraph_grg_game + * \brief Generates a geometric random graph. + * + * A geometric random graph is created by dropping points (i.e. vertices) + * randomly on the unit square and then connecting all those pairs + * which are strictly less than \c radius apart in Euclidean distance. + * + * + * Original code contributed by Keith Briggs, thanks Keith. + * + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param radius The radius within which the vertices will be connected. + * \param torus Boolean constant. If true, periodic boundary conditions + * will be used, i.e. the vertices are assumed to be on a torus + * instead of a square. + * \param x An initialized vector or \c NULL. If not \c NULL, the points' + * x coordinates will be returned here. + * \param y An initialized vector or \c NULL. If not \c NULL, the points' + * y coordinates will be returned here. + * \return Error code. + * + * Time complexity: TODO, less than O(|V|^2+|E|). + * + * \example examples/simple/igraph_grg_game.c + */ +igraph_error_t igraph_grg_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t radius, igraph_bool_t torus, + igraph_vector_t *x, igraph_vector_t *y) { + + igraph_int_t i; + igraph_vector_t myx, myy, *xx = &myx, *yy = &myy; + igraph_vector_int_t edges; + igraph_real_t r2; + + if (nodes < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, nodes)); + + /* since we only connect nodes strictly closer than radius, + * radius < 0 is equivalent to radius == 0 */ + if (radius < 0) { + radius = 0; + } + r2 = radius*radius; + + if (x) { + xx = x; + IGRAPH_CHECK(igraph_vector_resize(xx, nodes)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(xx, nodes); + } + if (y) { + yy = y; + IGRAPH_CHECK(igraph_vector_resize(yy, nodes)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(yy, nodes); + } + + for (i = 0; i < nodes; i++) { + VECTOR(*xx)[i] = RNG_UNIF01(); + VECTOR(*yy)[i] = RNG_UNIF01(); + } + + igraph_vector_sort(xx); + + if (!torus) { + for (i = 0; i < nodes; i++) { + igraph_real_t xx1 = VECTOR(*xx)[i]; + igraph_real_t yy1 = VECTOR(*yy)[i]; + igraph_int_t j = i + 1; + igraph_real_t dx, dy; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* dx is always positive due to xx being sorted */ + while ( j < nodes && (dx = VECTOR(*xx)[j] - xx1) < radius) { + dy = VECTOR(*yy)[j] - yy1; + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + j++; + } + } + } else { + for (i = 0; i < nodes; i++) { + igraph_real_t xx1 = VECTOR(*xx)[i]; + igraph_real_t yy1 = VECTOR(*yy)[i]; + igraph_int_t j = i + 1; + igraph_real_t dx, dy; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* dx is always positive due to xx being sorted */ + while ( j < nodes && (dx = VECTOR(*xx)[j] - xx1) < radius) { + dy = fabs(VECTOR(*yy)[j] - yy1); + if (dx > 0.5) { + dx = 1 - dx; + } + if (dy > 0.5) { + dy = 1 - dy; + } + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + j++; + } + if (j == nodes) { + j = 0; + while (j < i && (dx = 1 - xx1 + VECTOR(*xx)[j]) < radius && + xx1 - VECTOR(*xx)[j] >= radius) { + dy = fabs(VECTOR(*yy)[j] - yy1); + if (dy > 0.5) { + dy = 1 - dy; + } + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + j++; + } + } + } + } + + if (!y) { + igraph_vector_destroy(yy); + IGRAPH_FINALLY_CLEAN(1); + } + if (!x) { + igraph_vector_destroy(xx); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, IGRAPH_UNDIRECTED)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/growing_random.c b/src/games/growing_random.c new file mode 100644 index 0000000..98954fa --- /dev/null +++ b/src/games/growing_random.c @@ -0,0 +1,105 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_random.h" + +#include "math/safe_intop.h" + +/** + * \ingroup generators + * \function igraph_growing_random_game + * \brief Generates a growing random graph. + * + * This function simulates a growing random graph. We start out with + * one vertex. In each step a new vertex is added and a number of new + * edges are also added. These graphs are known to be different + * from standard (not growing) random graphs. + * + * \param graph Uninitialized graph object. + * \param n The number of vertices in the graph. + * \param m The number of edges to add in a time step (i.e. after + * adding a vertex). + * \param directed Boolean, whether to generate a directed graph. + * \param citation Boolean, if \c true, the edges always + * originate from the most recently added vertex and are + * connected to a previous vertex. + * \return Error code: + * \c IGRAPH_EINVAL: invalid + * \p n or \p m + * parameter. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. + */ +igraph_error_t igraph_growing_random_game(igraph_t *graph, igraph_int_t n, + igraph_int_t m, igraph_bool_t directed, + igraph_bool_t citation) { + + igraph_int_t no_of_nodes = n; + igraph_int_t no_of_neighbors = m; + igraph_int_t no_of_edges; + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + + igraph_int_t resp = 0; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices.", IGRAPH_EINVAL); + } + if (m < 0) { + IGRAPH_ERROR("Invalid number of edges per step (m).", IGRAPH_EINVAL); + } + + if (no_of_nodes == 0) { + no_of_edges = 0; + } else { + IGRAPH_SAFE_MULT(no_of_nodes - 1, no_of_neighbors, &no_of_edges); + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Number of edges overflows.", IGRAPH_EOVERFLOW); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + for (igraph_int_t i = 1; i < no_of_nodes; i++) { + for (igraph_int_t j = 0; j < no_of_neighbors; j++) { + if (citation) { + igraph_int_t to = RNG_INTEGER(0, i - 1); + VECTOR(edges)[resp++] = i; + VECTOR(edges)[resp++] = to; + } else { + igraph_int_t from = RNG_INTEGER(0, i); + igraph_int_t to = RNG_INTEGER(1, i); + VECTOR(edges)[resp++] = from; + VECTOR(edges)[resp++] = to; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/islands.c b/src/games/islands.c new file mode 100644 index 0000000..181a436 --- /dev/null +++ b/src/games/islands.c @@ -0,0 +1,170 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_random.h" + +#include "math/safe_intop.h" +#include "random/random_internal.h" + +/** + * \ingroup generators + * \function igraph_simple_interconnected_islands_game + * \brief Generates a random graph made of several interconnected islands, each island being a random graph. + * + * All islands are of the same size. Within an island, each edge is generated + * with the same probability. A fixed number of additional edges are then + * generated for each unordered pair of islands to connect them. The generated + * graph is guaranteed to be simple. + * + * \param graph Pointer to an uninitialized graph object. + * \param islands_n The number of islands in the graph. + * \param islands_size The size of islands in the graph. + * \param islands_pin The probability to create each possible edge within islands. + * \param n_inter The number of edges to create between two islands. It may be + * larger than \p islands_size squared, but in this case it is assumed + * to be \p islands_size squared. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + */ +igraph_error_t igraph_simple_interconnected_islands_game( + igraph_t *graph, + igraph_int_t islands_n, + igraph_int_t islands_size, + igraph_real_t islands_pin, + igraph_int_t n_inter) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + igraph_int_t number_of_nodes; + igraph_real_t max_possible_edges_per_island; + igraph_real_t avg_edges_per_island; + igraph_int_t number_of_inter_island_edges; + igraph_int_t start_index_of_island, start_index_of_other_island; + igraph_int_t i, j, is, from, to; + igraph_real_t last; + igraph_int_t island_ecount; + igraph_real_t nr_edges_reserved; + + if (islands_n < 0) { + IGRAPH_ERRORF("Number of islands cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, islands_n); + } + if (islands_size < 0) { + IGRAPH_ERRORF("Size of islands cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, islands_size); + } + if (islands_pin < 0 || islands_pin > 1) { + IGRAPH_ERRORF("Edge probability within islands should be between 0 and 1, got %g.", IGRAPH_EINVAL, islands_pin); + } + if (n_inter < 0) { + IGRAPH_ERRORF("Number of inter-island links cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, n_inter); + } + + number_of_inter_island_edges = islands_size * islands_size; + if (n_inter > number_of_inter_island_edges) { + IGRAPH_ERRORF( + "Too many edges requested between islands, maximum possible " + "is %" IGRAPH_PRId ", got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, number_of_inter_island_edges, n_inter + ); + } + + /* how much memory ? */ + number_of_nodes = islands_n * islands_size; + max_possible_edges_per_island = ((igraph_real_t)islands_size * ((igraph_real_t)islands_size - 1.0)) / 2.0; + avg_edges_per_island = islands_pin * max_possible_edges_per_island; + number_of_inter_island_edges = n_inter * (islands_n * (islands_n - 1)) / 2; + + nr_edges_reserved = 1.1 * avg_edges_per_island * islands_n + number_of_inter_island_edges; + /* The cast of ECOUNT_MAX to double could change its value, which means in theory the size of + the edges vector could still overflow, but only for very rare cases. */ + if (nr_edges_reserved > (double) (IGRAPH_ECOUNT_MAX ) || nr_edges_reserved > IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Too many vertices, overflow in maximum number of edges.", IGRAPH_EOVERFLOW); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, nr_edges_reserved * 2)); + + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, 1.1 * avg_edges_per_island)); + + /* first create all the islands */ + for (is = 0; is < islands_n; is++) { /* for each island */ + /* index for start and end of nodes in this island, both inclusive */ + start_index_of_island = islands_size * is; + + igraph_vector_clear(&s); + + last = RNG_GEOM(islands_pin); + while (last < max_possible_edges_per_island) { /* avg_edges_per_island */ + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(islands_pin); + last += 1; + } + + island_ecount = igraph_vector_size(&s); + for (i = 0; i < island_ecount; i++) { + to = floor((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2.0); + from = VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2.0; + to += start_index_of_island; + from += start_index_of_island; + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } + + /* create the links with other islands */ + island_ecount = islands_size * islands_size; + number_of_inter_island_edges = n_inter; + for (i = is + 1; i < islands_n; i++) { /* for each other island (not the previous ones) */ + IGRAPH_CHECK(igraph_i_random_sample_real(&s, 0, island_ecount - 1, n_inter)); + + start_index_of_other_island = i * islands_size; + for (j = 0; j < n_inter; j++) { /* for each link between islands */ + from = VECTOR(s)[j] / islands_size; + to = VECTOR(s)[j] - from * islands_size; + from += start_index_of_island; + to += start_index_of_other_island; + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + + /* actually fill the graph object */ + IGRAPH_CHECK(igraph_create(graph, &edges, number_of_nodes, 0)); + + /* clean remaining things */ + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/k_regular.c b/src/games/k_regular.c new file mode 100644 index 0000000..0eab6e4 --- /dev/null +++ b/src/games/k_regular.c @@ -0,0 +1,83 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +/** + * \ingroup generators + * \function igraph_k_regular_game + * \brief Generates a random graph where each vertex has the same degree. + * + * This game generates a directed or undirected random graph where the + * degrees of vertices are equal to a predefined constant k. For undirected + * graphs, at least one of k and the number of vertices must be even. + * + * + * Currently, this game simply uses \ref igraph_degree_sequence_game with + * the \c IGRAPH_DEGSEQ_CONFIGURATION or the \c IGRAPH_DEGSEQ_FAST_SIMPLE + * method and appropriately constructed degree sequences. + * Thefore, it does not sample uniformly: while it can generate all k-regular + * graphs with the given number of vertices, it does not generate each one with + * the same probability. + * + * \param graph Pointer to an uninitialized graph object. + * \param no_of_nodes The number of nodes in the generated graph. + * \param k The degree of each vertex in an undirected graph, or + * the out-degree and in-degree of each vertex in a + * directed graph. + * \param directed Whether the generated graph will be directed. + * \param multiple Whether to allow multiple edges in the generated graph. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter; e.g., negative number of nodes, + * or odd number of nodes and odd k for undirected + * graphs. + * \c IGRAPH_ENOMEM: there is not enough memory for the operation. + * + * Time complexity: O(|V|+|E|) if \c multiple is true, otherwise not known. + */ +igraph_error_t igraph_k_regular_game(igraph_t *graph, + igraph_int_t no_of_nodes, igraph_int_t k, + igraph_bool_t directed, igraph_bool_t multiple) { + igraph_vector_int_t degseq; + igraph_degseq_t mode = multiple ? IGRAPH_DEGSEQ_CONFIGURATION : IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE; + + /* Note to self: we are not using IGRAPH_DEGSEQ_VL when multiple = false + * because the VL method is not really good at generating k-regular graphs, + * and it produces only connected graphs. + * Actually, that's why we have added FAST_HEUR_SIMPLE. */ + + if (no_of_nodes < 0) { + IGRAPH_ERROR("Number of nodes must be non-negative.", IGRAPH_EINVAL); + } + if (k < 0) { + IGRAPH_ERROR("Degree must be non-negative.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°seq, no_of_nodes); + igraph_vector_int_fill(°seq, k); + IGRAPH_CHECK(igraph_degree_sequence_game(graph, °seq, directed ? °seq : 0, mode)); + + igraph_vector_int_destroy(°seq); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/preference.c b/src/games/preference.c new file mode 100644 index 0000000..e7b98e8 --- /dev/null +++ b/src/games/preference.c @@ -0,0 +1,611 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_vector_list.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" + +#include /* for sqrt and floor */ + +/** + * \function igraph_preference_game + * \brief Generates a graph with vertex types and connection preferences. + * + * + * This is practically the nongrowing variant of + * \ref igraph_establishment_game(). A given number of vertices are + * generated. Every vertex is assigned to a vertex type according to + * the given type probabilities. Finally, every + * vertex pair is evaluated and an edge is created between them with a + * probability depending on the types of the vertices involved. + * + * + * In other words, this function generates a graph according to a + * block-model. Vertices are divided into groups (or blocks), and + * the probability the two vertices are connected depends on their + * groups only. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param types The number of vertex types. + * \param type_dist Vector giving the distribution of vertex types. If + * \c NULL, all vertex types will have equal probability. See also the + * \p fixed_sizes argument. + * \param fixed_sizes Boolean. If true, then the number of vertices with a + * given vertex type is fixed and the \p type_dist argument gives these + * numbers for each vertex type. If true, and \p type_dist is \c NULL, + * then the function tries to make vertex groups of the same size. If this + * is not possible, then some groups will have an extra vertex. + * \param pref_matrix Matrix giving the connection probabilities for + * different vertex types. This should be symmetric if the requested + * graph is undirected. + * \param node_type_vec A vector where the individual generated vertex types + * will be stored. If \c NULL, the vertex types won't be saved. + * \param directed Whether to generate a directed graph. If undirected + * graphs are requested, only the lower left triangle of the preference + * matrix is considered. + * \param loops Whether loop edges are allowed. + * \return Error code. + * + * Added in version 0.3. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_asymmetric_preference_game(), + * \ref igraph_establishment_game(), \ref igraph_callaway_traits_game() + */ + +igraph_error_t igraph_preference_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t types, + const igraph_vector_t *type_dist, + igraph_bool_t fixed_sizes, + const igraph_matrix_t *pref_matrix, + igraph_vector_int_t *node_type_vec, + igraph_bool_t directed, + igraph_bool_t loops) { + + igraph_int_t i, j, no_reserved_edges; + igraph_vector_int_t edges; + igraph_vector_t s; + igraph_vector_int_t* nodetypes; + igraph_vector_int_list_t vids_by_type; + igraph_real_t maxcum, maxedges; + + if (nodes < 0) { + IGRAPH_ERROR("The number of vertices must be non-negative.", IGRAPH_EINVAL); + } + + if (types < 1) { + IGRAPH_ERROR("The number of vertex types must be at least 1.", IGRAPH_EINVAL); + } + + if (type_dist) { + igraph_real_t lo; + + if (igraph_vector_size(type_dist) != types) { + IGRAPH_ERROR("The vertex type distribution vector must agree in length with the number of types.", + IGRAPH_EINVAL); + } + + lo = igraph_vector_min(type_dist); + if (lo < 0) { + IGRAPH_ERROR("The vertex type distribution vector must not contain negative values.", IGRAPH_EINVAL); + } + if (isnan(lo)) { + IGRAPH_ERROR("The vertex type distribution vector must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (igraph_matrix_nrow(pref_matrix) != types || igraph_matrix_ncol(pref_matrix) != types) { + IGRAPH_ERROR("The preference matrix must be square and agree in dimensions with the number of types.", IGRAPH_EINVAL); + } + + { + igraph_real_t lo, hi; + igraph_matrix_minmax(pref_matrix, &lo, &hi); /* matrix size is at least 1x1, safe to call minmax */ + + if (lo < 0 || hi > 1) { + IGRAPH_ERROR("The preference matrix must contain probabilities in [0, 1].", IGRAPH_EINVAL); + } + if (isnan(lo) || isnan(hi)) { + IGRAPH_ERROR("The preference matrix must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (! directed && ! igraph_matrix_is_symmetric(pref_matrix)) { + IGRAPH_ERROR("The preference matrix must be symmetric when generating undirected graphs.", IGRAPH_EINVAL); + } + + if (fixed_sizes && type_dist) { + if (igraph_vector_sum(type_dist) != nodes) { + IGRAPH_ERROR("Invalid group sizes, their sum must match the number of vertices.", IGRAPH_EINVAL); + } + } + + if (node_type_vec) { + IGRAPH_CHECK(igraph_vector_int_resize(node_type_vec, nodes)); + nodetypes = node_type_vec; + } else { + nodetypes = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(nodetypes, "Insufficient memory for preference_game."); + IGRAPH_FINALLY(igraph_free, nodetypes); + IGRAPH_VECTOR_INT_INIT_FINALLY(nodetypes, nodes); + } + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&vids_by_type, types); + + if (!fixed_sizes) { + + igraph_vector_t cumdist; + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + + VECTOR(cumdist)[0] = 0; + if (type_dist) { + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + } else { + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = i + 1; + } + } + maxcum = igraph_vector_tail(&cumdist); + + for (i = 0; i < nodes; i++) { + igraph_int_t type1; + igraph_real_t uni1 = RNG_UNIF(0, maxcum); + igraph_vector_binsearch(&cumdist, uni1, &type1); + VECTOR(*nodetypes)[i] = type1 - 1; + IGRAPH_CHECK(igraph_vector_int_push_back( + igraph_vector_int_list_get_ptr(&vids_by_type, type1 - 1), i + )); + } + + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + igraph_int_t an = 0; + if (type_dist) { + for (i = 0; i < types; i++) { + igraph_int_t no = VECTOR(*type_dist)[i]; + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&vids_by_type, i); + for (j = 0; j < no && an < nodes; j++) { + VECTOR(*nodetypes)[an] = i; + IGRAPH_CHECK(igraph_vector_int_push_back(v, an)); + an++; + } + } + } else { + igraph_int_t size_of_one_group = nodes / types; + igraph_int_t num_groups_with_one_extra_node = nodes - size_of_one_group * types; + for (i = 0; i < types; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&vids_by_type, i); + for (j = 0; j < size_of_one_group; j++) { + VECTOR(*nodetypes)[an] = i; + IGRAPH_CHECK(igraph_vector_int_push_back(v, an)); + an++; + } + if (i < num_groups_with_one_extra_node) { + VECTOR(*nodetypes)[an] = i; + IGRAPH_CHECK(igraph_vector_int_push_back(v, an)); + an++; + } + } + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + + for (i = 0; i < types; i++) { + for (j = 0; j < types; j++) { + /* Generating the random subgraph between vertices of type i and j */ + igraph_int_t k, l, l_x2; + igraph_real_t p, last; + igraph_vector_int_t *v1, *v2; + igraph_int_t v1_size, v2_size; + + IGRAPH_ALLOW_INTERRUPTION(); + + v1 = igraph_vector_int_list_get_ptr(&vids_by_type, i); + v2 = igraph_vector_int_list_get_ptr(&vids_by_type, j); + v1_size = igraph_vector_int_size(v1); + v2_size = igraph_vector_int_size(v2); + + p = MATRIX(*pref_matrix, i, j); + igraph_vector_clear(&s); + if (i != j) { + /* The two vertex sets are disjoint, this is the easier case */ + if (i > j && !directed) { + continue; + } + maxedges = ((igraph_real_t) v1_size) * v2_size; + } else { + if (directed && loops) { + maxedges = ((igraph_real_t) v1_size) * v1_size; + } else if (directed && !loops) { + maxedges = ((igraph_real_t) v1_size) * (v1_size - 1); + } else if (!directed && loops) { + maxedges = ((igraph_real_t) v1_size) * (v1_size + 1) / 2; + } else { + maxedges = ((igraph_real_t) v1_size) * (v1_size - 1) / 2; + } + } + + if (maxedges > IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Too many vertices, overflow in maximum number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_CHECK(igraph_i_safe_floor(maxedges * p * 1.1, &no_reserved_edges)); + IGRAPH_CHECK(igraph_vector_reserve(&s, no_reserved_edges)); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + l = igraph_vector_size(&s); + + IGRAPH_SAFE_MULT(l, 2, &l_x2); + IGRAPH_SAFE_ADD(igraph_vector_int_size(&edges), l_x2, &no_reserved_edges); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_reserved_edges)); + + if (i != j) { + /* Generating the subgraph between vertices of type i and j */ + for (k = 0; k < l; k++) { + igraph_int_t to = floor(VECTOR(s)[k] / v1_size); + igraph_int_t from = (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v2)[to]); + } + } else { + /* Generating the subgraph among vertices of type i */ + if (directed && loops) { + for (k = 0; k < l; k++) { + igraph_int_t to = floor(VECTOR(s)[k] / v1_size); + igraph_int_t from = (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[to]); + } + } else if (directed && !loops) { + for (k = 0; k < l; k++) { + igraph_int_t to = floor(VECTOR(s)[k] / v1_size); + igraph_int_t from = (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + if (from == to) { + to = v1_size - 1; + } + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[to]); + } + } else if (!directed && loops) { + for (k = 0; k < l; k++) { + igraph_int_t to = floor((sqrt(8 * VECTOR(s)[k] + 1) - 1) / 2); + igraph_int_t from = (VECTOR(s)[k] - (((igraph_real_t)to) * (to + 1)) / 2); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[to]); + } + } else { + for (k = 0; k < l; k++) { + igraph_int_t to = floor((sqrt(8 * VECTOR(s)[k] + 1) + 1) / 2); + igraph_int_t from = (VECTOR(s)[k] - (((igraph_real_t)to) * (to - 1)) / 2); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[to]); + } + } + } + } + } + + igraph_vector_destroy(&s); + igraph_vector_int_list_destroy(&vids_by_type); + IGRAPH_FINALLY_CLEAN(2); + + if (node_type_vec == 0) { + igraph_vector_int_destroy(nodetypes); + IGRAPH_FREE(nodetypes); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_asymmetric_preference_game + * \brief Generates a graph with asymmetric vertex types and connection preferences. + * + * + * This is the asymmetric variant of \ref igraph_preference_game(). + * A given number of vertices are generated. Every vertex is assigned to an + * "outgoing" and an "incoming " vertex type according to the given joint + * type probabilities. Finally, every vertex pair is evaluated and a + * directed edge is created between them with a probability depending on the + * "outgoing" type of the source vertex and the "incoming" type of the target + * vertex. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param no_out_types The number of vertex out-types. + * \param no_in_types The number of vertex in-types. + * \param type_dist_matrix Matrix of size out_types * in_types, + * giving the joint distribution of vertex types. + * If \c NULL, incoming and outgoing vertex types are independent and uniformly + * distributed. + * \param pref_matrix Matrix of size out_types * in_types, + * giving the connection probabilities for different vertex types. + * \param node_type_out_vec A vector where the individual generated "outgoing" + * vertex types will be stored. If \c NULL, the vertex types won't be saved. + * \param node_type_in_vec A vector where the individual generated "incoming" + * vertex types will be stored. If \c NULL, the vertex types won't be saved. + * \param loops Whether loop edges are allowed. + * \return Error code. + * + * Added in version 0.3. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_preference_game() + */ + +igraph_error_t igraph_asymmetric_preference_game(igraph_t *graph, igraph_int_t nodes, + igraph_int_t no_out_types, + igraph_int_t no_in_types, + const igraph_matrix_t *type_dist_matrix, + const igraph_matrix_t *pref_matrix, + igraph_vector_int_t *node_type_out_vec, + igraph_vector_int_t *node_type_in_vec, + igraph_bool_t loops) { + + igraph_int_t i, j, k, no_reserved_edges; + igraph_vector_int_t edges; + igraph_vector_t s; + igraph_vector_t cumdist; + igraph_vector_int_t intersect; + igraph_vector_int_t *nodetypes_in; + igraph_vector_int_t *nodetypes_out; + igraph_vector_int_list_t vids_by_intype, vids_by_outtype; + igraph_real_t maxcum, maxedges; + + if (nodes < 0) { + IGRAPH_ERROR("The number of vertices must not be negative.", IGRAPH_EINVAL); + } + + if (no_in_types < 1) { + IGRAPH_ERROR("The number of vertex in-types must be at least 1.", IGRAPH_EINVAL); + } + + if (no_out_types < 1) { + IGRAPH_ERROR("The number of vertex out-types must be at least 1.", IGRAPH_EINVAL); + } + + if (type_dist_matrix) { + igraph_real_t lo; + + if (igraph_matrix_nrow(type_dist_matrix) != no_out_types || + igraph_matrix_ncol(type_dist_matrix) != no_in_types) { + IGRAPH_ERROR("The type distribution matrix must have dimensions out_types * in_types.", IGRAPH_EINVAL); + } + + lo = igraph_matrix_min(type_dist_matrix); + if (lo < 0) { + IGRAPH_ERROR("The type distribution matrix must not contain negative values.", IGRAPH_EINVAL); + } + if (isnan(lo)) { + IGRAPH_ERROR("The type distribution matrix must not contain NaN.", IGRAPH_EINVAL); + } + } + + if (igraph_matrix_nrow(pref_matrix) != no_out_types || + igraph_matrix_ncol(pref_matrix) != no_in_types) { + IGRAPH_ERROR("The preference matrix must have dimensions out_types * in_types.", IGRAPH_EINVAL); + } + + { + igraph_real_t lo, hi; + igraph_matrix_minmax(pref_matrix, &lo, &hi); /* matrix size is at least 1x1, safe to call minmax */ + + if (lo < 0 || hi > 1) { + IGRAPH_ERROR("The preference matrix must contain probabilities in [0, 1].", IGRAPH_EINVAL); + } + if (isnan(lo) || isnan(hi)) { + IGRAPH_ERROR("The preference matrix must not contain NaN.", IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, no_in_types * no_out_types + 1); + + if (node_type_in_vec) { + nodetypes_in = node_type_in_vec; + IGRAPH_CHECK(igraph_vector_int_resize(nodetypes_in, nodes)); + } else { + nodetypes_in = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(nodetypes_in, "Insufficient memory for asymmetric preference game."); + IGRAPH_FINALLY(igraph_free, &nodetypes_in); + IGRAPH_VECTOR_INT_INIT_FINALLY(nodetypes_in, nodes); + } + + if (node_type_out_vec) { + nodetypes_out = node_type_out_vec; + IGRAPH_CHECK(igraph_vector_int_resize(nodetypes_out, nodes)); + } else { + nodetypes_out = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(nodetypes_out, "Insufficient memory for asymmetric preference game."); + IGRAPH_FINALLY(igraph_free, &nodetypes_out); + IGRAPH_VECTOR_INT_INIT_FINALLY(nodetypes_out, nodes); + } + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&vids_by_intype, no_in_types); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&vids_by_outtype, no_out_types); + + VECTOR(cumdist)[0] = 0; + k = 0; + if (type_dist_matrix) { + for (j = 0; j < no_in_types; j++) { + for (i = 0; i < no_out_types; i++) { + VECTOR(cumdist)[k + 1] = VECTOR(cumdist)[k] + MATRIX(*type_dist_matrix, i, j); + k++; + } + } + } else { + for (i = 0; i < no_out_types * no_in_types; i++) { + VECTOR(cumdist)[i + 1] = i + 1; + } + } + maxcum = igraph_vector_tail(&cumdist); + + for (i = 0; i < nodes; i++) { + igraph_int_t in_type, out_type; + igraph_real_t uni1 = RNG_UNIF(0, maxcum); + igraph_vector_binsearch(&cumdist, uni1, &in_type); + out_type = (in_type - 1) % no_out_types; + in_type = (in_type - 1) / no_out_types; + VECTOR(*nodetypes_in)[i] = in_type; + VECTOR(*nodetypes_out)[i] = out_type; + IGRAPH_CHECK(igraph_vector_int_push_back( + igraph_vector_int_list_get_ptr(&vids_by_intype, in_type), i + )); + IGRAPH_CHECK(igraph_vector_int_push_back( + igraph_vector_int_list_get_ptr(&vids_by_outtype, out_type), i + )); + } + + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&intersect, 0); + for (i = 0; i < no_out_types; i++) { + for (j = 0; j < no_in_types; j++) { + igraph_int_t kk, l, l_x2; + igraph_int_t c = 0; + igraph_real_t p, last; + igraph_vector_int_t *v1, *v2; + igraph_int_t v1_size, v2_size; + + IGRAPH_ALLOW_INTERRUPTION(); + + v1 = igraph_vector_int_list_get_ptr(&vids_by_outtype, i); + v2 = igraph_vector_int_list_get_ptr(&vids_by_intype, j); + v1_size = igraph_vector_int_size(v1); + v2_size = igraph_vector_int_size(v2); + + maxedges = ((igraph_real_t) v1_size) * v2_size; + + if (maxedges > IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Too many vertices, overflow in maximum number of edges.", IGRAPH_EOVERFLOW); + } + + if (!loops) { + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(v1, v2, &intersect)); + c = igraph_vector_int_size(&intersect); + maxedges -= c; + } + + p = MATRIX(*pref_matrix, i, j); + igraph_vector_clear(&s); + + IGRAPH_CHECK(igraph_i_safe_floor(maxedges * p * 1.1, &no_reserved_edges)); + IGRAPH_CHECK(igraph_vector_reserve(&s, no_reserved_edges)); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + l = igraph_vector_size(&s); + + IGRAPH_SAFE_MULT(l, 2, &l_x2); + IGRAPH_SAFE_ADD(igraph_vector_int_size(&edges), l_x2, &no_reserved_edges); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_reserved_edges)); + + + if (!loops && c > 0) { + for (kk = 0; kk < l; kk++) { + igraph_int_t to = floor(VECTOR(s)[kk] / v1_size); + igraph_int_t from = (VECTOR(s)[kk] - ((igraph_real_t) to) * v1_size); + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + /* remap loop edges */ + to = v2_size - 1; + igraph_vector_int_binsearch(&intersect, VECTOR(*v1)[from], &c); + from = v1_size - 1; + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + from--; + } + while (c > 0) { + c--; from--; + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + from--; + } + } + } + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v2)[to]); + } + } else { + for (kk = 0; kk < l; kk++) { + igraph_int_t to = floor(VECTOR(s)[kk] / v1_size); + igraph_int_t from = (VECTOR(s)[kk] - ((igraph_real_t)to) * v1_size); + igraph_vector_int_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_int_push_back(&edges, VECTOR(*v2)[to]); + } + } + } + } + + igraph_vector_destroy(&s); + igraph_vector_int_destroy(&intersect); + igraph_vector_int_list_destroy(&vids_by_intype); + igraph_vector_int_list_destroy(&vids_by_outtype); + IGRAPH_FINALLY_CLEAN(4); + + if (node_type_out_vec == 0) { + igraph_vector_int_destroy(nodetypes_out); + IGRAPH_FREE(nodetypes_out); + IGRAPH_FINALLY_CLEAN(2); + } + + if (node_type_in_vec == 0) { + igraph_vector_int_destroy(nodetypes_in); + IGRAPH_FREE(nodetypes_in); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, 1)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/recent_degree.c b/src/games/recent_degree.c new file mode 100644 index 0000000..9461fda --- /dev/null +++ b/src/games/recent_degree.c @@ -0,0 +1,381 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_psumtree.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_recent_degree_game + * \brief Stochastic graph generator based on the number of incident edges a node has gained recently. + * + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph, this is the same as + * the number of time steps. + * \param power The exponent, the probability that a node gains a + * new edge is proportional to the number of edges it has + * gained recently (in the last \p window time steps) to \p + * power. + * \param time_window Integer constant, the size of the time window to use + * to count the number of recent edges. + * \param m Integer constant, the number of edges to add per time + * step if the \p outseq parameter is a null pointer or a + * zero-length vector. + * \param outseq The number of edges to add in each time step. This + * argument is ignored if it is a null pointer or a zero length + * vector. In this case the constant \p m parameter is used. + * \param outpref Boolean constant, if true the edges originated by a + * vertex also count as recent incident edges. + * For most applications it is reasonable to set it to false. + * \param zero_appeal Constant giving the attractiveness of the + * vertices which haven't gained any edge recently. + * \param directed Boolean constant, whether to generate a directed + * graph. + * \return Error code. + * + * Time complexity: O(|V|*log(|V|)+|E|), |V| is the number of + * vertices, |E| is the number of edges in the graph. + * + */ +igraph_error_t igraph_recent_degree_game(igraph_t *graph, igraph_int_t nodes, + igraph_real_t power, + igraph_int_t time_window, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t zero_appeal, + igraph_bool_t directed) { + + igraph_int_t no_of_nodes = nodes; + igraph_int_t no_of_neighbors = 0; + igraph_int_t no_of_edges; + igraph_vector_int_t edges; + igraph_int_t i, j; + igraph_psumtree_t sumtree; + igraph_int_t edgeptr = 0; + igraph_vector_t degree; + igraph_dqueue_int_t history; + + if (no_of_nodes < 0) { + IGRAPH_ERRORF("Number of vertices cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, no_of_nodes); + } + if (outseq && igraph_vector_int_size(outseq) != no_of_nodes) { + IGRAPH_ERRORF("Out-degree sequence is specified, but its length (%" IGRAPH_PRId ") does not equal the number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(outseq), no_of_nodes); + } + if (!outseq && m < 0) { + IGRAPH_ERRORF("Number of edges per step cannot be negative, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, m); + } + if (time_window < 0) { + IGRAPH_ERRORF("Time window cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, time_window); + } + if (zero_appeal < 0) { + IGRAPH_ERRORF("The zero appeal cannot be negative, got %g.", IGRAPH_EINVAL, zero_appeal); + } + + /* This effectively also hanbdles an outseq size of 0. + * From here on we assume that outseq has a size of at least 1. */ + if (no_of_nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + return IGRAPH_SUCCESS; + } + + if (!outseq) { + no_of_neighbors = m; + IGRAPH_SAFE_MULT(no_of_nodes - 1, no_of_neighbors, &no_of_edges); + } else { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &no_of_edges)); + no_of_edges -= VECTOR(*outseq)[0]; + } + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_int_init(&history, + 1.5 * time_window * no_of_edges / no_of_nodes + 10)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &history); + + /* first node */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, zero_appeal)); + IGRAPH_CHECK(igraph_dqueue_int_push(&history, -1)); + + /* and the rest */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + igraph_int_t to; + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[i]; + } + + if (i >= time_window) { + while ((j = igraph_dqueue_int_pop(&history)) != -1) { + VECTOR(degree)[j] -= 1; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, j, pow(VECTOR(degree)[j], power) + zero_appeal)); + } + } + + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + if (sum == 0) { + /* If none of the so-far added nodes have positive weight, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + IGRAPH_CHECK(igraph_dqueue_int_push(&history, to)); + } + IGRAPH_CHECK(igraph_dqueue_int_push(&history, -1)); + + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t nn = VECTOR(edges)[edgeptr - 2 * j - 1]; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, nn, pow(VECTOR(degree)[nn], power) + zero_appeal)); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, pow(VECTOR(degree)[i], power) + zero_appeal)); + } else { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, zero_appeal)); + } + } + + igraph_dqueue_int_destroy(&history); + igraph_psumtree_destroy(&sumtree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_recent_degree_aging_game + * \brief Preferential attachment based on the number of edges gained recently, with aging of vertices. + * + * + * This game is very similar to \ref igraph_barabasi_aging_game(), + * except that instead of the total number of incident edges the + * number of edges gained in the last \p time_window time steps are + * counted. + * + * The degree dependent part of the attractiveness is + * given by k to the power of \p pa_exp plus \p zero_appeal; the age + * dependent part is l to the power to \p aging_exp. + * k is the number of edges gained in the last \p time_window time + * steps, l is the age of the vertex. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param m The number of edges to add in each time step. If the \p + * outseq argument is not a null vector or a zero-length vector + * then it is ignored. + * \param outseq Vector giving the number of edges to add in each time + * step. If it is a null pointer or a zero-length vector then + * it is ignored and the \p m argument is used. + * \param outpref Boolean constant, if true the edges initiated by a + * vertex are also counted. Normally it is false. + * \param pa_exp The exponent for the preferential attachment. + * \param aging_exp The exponent for the aging, normally it is + * negative: old vertices gain edges with less probability. + * \param aging_bins Integer constant, the number of age bins to use. + * \param time_window The time window to use to count the number of + * incident edges for the vertices. + * \param zero_appeal The degree dependent part of the attractiveness + * for zero degree vertices. + * \param directed Boolean constant, whether to create a directed + * graph. + * \return Error code. + * + * Time complexity: O((|V|+|V|/aging_bins)*log(|V|)+|E|). |V| is the number + * of vertices, |E| the number of edges. + */ +igraph_error_t igraph_recent_degree_aging_game(igraph_t *graph, + igraph_int_t nodes, + igraph_int_t m, + const igraph_vector_int_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_int_t aging_bins, + igraph_int_t time_window, + igraph_real_t zero_appeal, + igraph_bool_t directed) { + + igraph_int_t no_of_nodes = nodes; + igraph_int_t no_of_neighbors; + igraph_int_t binwidth; + igraph_int_t no_of_edges; + igraph_vector_int_t edges; + igraph_int_t i, j, k; + igraph_psumtree_t sumtree; + igraph_int_t edgeptr = 0; + igraph_vector_t degree; + igraph_dqueue_int_t history; + + if (no_of_nodes < 0) { + IGRAPH_ERRORF("Number of nodes should not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, no_of_nodes); + } + if (outseq && igraph_vector_int_size(outseq) != no_of_nodes) { + IGRAPH_ERRORF("Out-degree sequence is specified, but its length (%" IGRAPH_PRId ") does not equal the number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(outseq), no_of_nodes); + } + if (!outseq && m < 0) { + IGRAPH_ERRORF("Numer of edges per step cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, m); + } + if (aging_bins <= 0) { + IGRAPH_ERRORF("Aging bins should be positive, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, aging_bins); + } + if (time_window < 0) { + IGRAPH_ERRORF("Time window cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, time_window); + } + if (zero_appeal < 0) { + IGRAPH_ERRORF("The zero appeal cannot be negative, got %g.", IGRAPH_EINVAL, zero_appeal); + } + + /* This effectively also hanbdles an outseq size of 0. + * From here on we assume that outseq has a size of at least 1. */ + if (no_of_nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + return IGRAPH_SUCCESS; + } + + if (!outseq) { + no_of_neighbors = m; + IGRAPH_SAFE_MULT(no_of_nodes - 1, no_of_neighbors, &no_of_edges); + } else { + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outseq, &no_of_edges)); + no_of_edges -= VECTOR(*outseq)[0]; + } + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + binwidth = nodes / aging_bins + 1; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_int_init(&history, + 1.5 * time_window * no_of_edges / no_of_nodes + 10)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &history); + + /* first node */ + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, 0, zero_appeal)); + IGRAPH_CHECK(igraph_dqueue_int_push(&history, -1)); + + /* and the rest */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + igraph_int_t to; + + if (outseq) { + no_of_neighbors = VECTOR(*outseq)[i]; + } + + if (i >= time_window) { + while ((j = igraph_dqueue_int_pop(&history)) != -1) { + igraph_int_t age = (i - j) / binwidth; + VECTOR(degree)[j] -= 1; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, j, + (pow(VECTOR(degree)[j], pa_exp) + zero_appeal) * pow(age + 1, aging_exp) + )); + } + } + + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + if (sum == 0) { + /* If none of the so-far added nodes have positive weight, + * we choose one uniformly to connect to. */ + to = RNG_INTEGER(0, i-1); + } else { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + } + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + IGRAPH_CHECK(igraph_dqueue_int_push(&history, to)); + } + IGRAPH_CHECK(igraph_dqueue_int_push(&history, -1)); + + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + igraph_int_t n = VECTOR(edges)[edgeptr - 2 * j - 1]; + igraph_int_t age = (i - n) / binwidth; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, n, + (pow(VECTOR(degree)[n], pa_exp) + zero_appeal) * pow(age + 1, aging_exp) + )); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, i, + pow(VECTOR(degree)[i], pa_exp) + zero_appeal + )); + } else { + IGRAPH_CHECK(igraph_psumtree_update(&sumtree, i, zero_appeal)); + } + + /* aging */ + for (k = 1; binwidth * k <= i; k++) { + igraph_int_t shnode = i - binwidth * k; + igraph_int_t deg = VECTOR(degree)[shnode]; + igraph_int_t age = (i - shnode) / binwidth; + IGRAPH_CHECK(igraph_psumtree_update( + &sumtree, shnode, + (pow(deg, pa_exp) + zero_appeal) * pow(age + 2, aging_exp) + )); + } + } + + igraph_dqueue_int_destroy(&history); + igraph_vector_destroy(°ree); + igraph_psumtree_destroy(&sumtree); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/sbm.c b/src/games/sbm.c new file mode 100644 index 0000000..db8b1dd --- /dev/null +++ b/src/games/sbm.c @@ -0,0 +1,648 @@ +/* + igraph library. + Copyright (C) 2003-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_matrix.h" +#include "igraph_random.h" +#include "igraph_vector.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" +#include "misc/graphicality.h" + +#include /* for DBL_EPSILON */ +#include /* for sqrt and floor */ + +/** + * \function igraph_sbm_game + * \brief Sample from a stochastic block model. + * + * This function samples graphs from a stochastic block model, a generalization + * of the G(n,p) model where the connection probability p (or expected number + * of edges for multigraphs) is specified separately between and within a given + * group of vertices. + * + * + * The order of the vertex IDs in the generated graph corresponds to + * the \p block_sizes argument. + * + * + * Reference: + * + * + * Faust, K., & Wasserman, S. (1992a). + * Blockmodels: Interpretation and evaluation. + * Social Networks, 14, 5-–61. + * https://doi.org/10.1016/0378-8733(92)90013-W + * + * \param graph The output graph. This should be a pointer to an + * uninitialized graph. + * \param pref_matrix The matrix giving the connection probabilities + * (or expected edge multiplicities for multigraphs) between groups. + * This is a k-by-k matrix, where k is the number of groups. + * The probability of creating an edge between vertices from + * groups i and j is given by element (i,j). + * \param block_sizes An integer vector giving the number of + * vertices in each group. + * \param directed Boolean, whether to create a directed graph. If + * this argument is \c false, then \p pref_matrix must be symmetric. + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are generated. See \ref igraph_edge_type_sw_t. + * \return Error code. + * + * Time complexity: O(|V|+|E|+k^2), where |V| is the number of + * vertices, |E| is the number of edges, and k is the number of + * groups. + * + * \sa \ref igraph_erdos_renyi_game_gnp() for a simple Bernoulli graph. + * + */ + +igraph_error_t igraph_sbm_game( + igraph_t *graph, + const igraph_matrix_t *pref_matrix, + const igraph_vector_int_t *block_sizes, + igraph_bool_t directed, + igraph_edge_type_sw_t allowed_edge_types) { + +#define CHECK_MAXEDGES() \ + do {if (maxedges > IGRAPH_MAX_EXACT_REAL) { \ + IGRAPH_ERROR("Too many vertices, overflow in maximum number of edges.", IGRAPH_EOVERFLOW); \ + }} while (0) + + const igraph_int_t n = igraph_vector_int_sum(block_sizes); + const igraph_int_t no_blocks = igraph_matrix_nrow(pref_matrix); + igraph_int_t from, to, fromoff = 0; + igraph_real_t minp, maxp; + igraph_vector_int_t edges; + igraph_bool_t loops, multiple; + + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + /* ------------------------------------------------------------ */ + /* Check arguments */ + /* ------------------------------------------------------------ */ + + if (igraph_matrix_ncol(pref_matrix) != no_blocks) { + IGRAPH_ERROR("Preference matrix is not square.", IGRAPH_EINVAL); + } + + if (no_blocks > 0) { + igraph_matrix_minmax(pref_matrix, &minp, &maxp); + if (multiple) { + if (minp < 0.0) { + IGRAPH_ERRORF("Expected edge multiplicities must not be negative " + "for SBM multigraph model, got %g.", + IGRAPH_EINVAL, minp); + } + } else { + if (minp < 0 || maxp > 1) { + IGRAPH_ERROR("Connection probabilities must be in [0,1] for SBM model.", IGRAPH_EINVAL); + } + } + } + + if (!directed && !igraph_matrix_is_symmetric(pref_matrix)) { + IGRAPH_ERROR("Preference matrix must be symmetric for undirected graphs.", + IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(block_sizes) != no_blocks) { + IGRAPH_ERRORF("Block size vector length (%" IGRAPH_PRId ") does not agree with " + "preference matrix size (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + igraph_vector_int_size(block_sizes), no_blocks); + } + + if (no_blocks > 0) { + if (igraph_vector_int_min(block_sizes) < 0) { + IGRAPH_ERRORF("Block sizes must be non-negative, but got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, igraph_vector_int_min(block_sizes)); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + for (from = 0; from < no_blocks; from++) { + igraph_int_t fromsize = VECTOR(*block_sizes)[from]; + igraph_int_t start = directed ? 0 : from; + igraph_int_t i, tooff = 0; + + IGRAPH_ALLOW_INTERRUPTION(); + + for (i = 0; i < start; i++) { + tooff += VECTOR(*block_sizes)[i]; + } + for (to = start; to < no_blocks; to++) { + igraph_int_t tosize = VECTOR(*block_sizes)[to]; + + igraph_real_t prob = MATRIX(*pref_matrix, from, to); + if (multiple) { + prob = prob / (1 + prob); + } + + igraph_real_t maxedges; + igraph_real_t last = RNG_GEOM(prob); /* RNG_GEOM may return NaN so igraph_int_t is not suitable */ + igraph_int_t vfrom, vto; + + if (directed && loops) { + maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor(last / fromsize); + vfrom = last - ((igraph_real_t) vto) * fromsize; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else if (directed && !loops && from != to) { + maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor(last / fromsize); + vfrom = last - ((igraph_real_t) vto) * fromsize; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else if (directed && !loops && from == to) { + maxedges = ((igraph_real_t) fromsize) * (fromsize - 1.0); + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor(last / fromsize); + vfrom = last - ((igraph_real_t) vto) * fromsize; + if (vfrom == vto) { + vto = fromsize - 1; + } + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else if (!directed && loops && from != to) { + maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor(last / fromsize); + vfrom = last - ((igraph_real_t) vto) * fromsize; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else if (!directed && loops && from == to) { + maxedges = ((igraph_real_t) fromsize) * (fromsize + 1.0) / 2.0; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor((sqrt(8 * last + 1) - 1) / 2); + vfrom = last - (((igraph_real_t) vto) * (vto + 1.0)) / 2.0; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else if (!directed && !loops && from != to) { + maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor(last / fromsize); + vfrom = last - ((igraph_real_t) vto) * fromsize; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } else { /*!directed && !loops && from==to */ + maxedges = ((igraph_real_t) fromsize) * (fromsize - 1.0) / 2.0; + CHECK_MAXEDGES(); + while (last < maxedges) { + vto = floor((sqrt(8 * last + 1) + 1) / 2); + vfrom = last - (((igraph_real_t) vto) * (vto - 1.0)) / 2.0; + igraph_vector_int_push_back(&edges, fromoff + vfrom); + igraph_vector_int_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +#undef CHECK_MAXEDGES +} + +/** + * \function igraph_hsbm_game + * \brief Hierarchical stochastic block model. + * + * The function generates a random graph according to the hierarchical + * stochastic block model. + * + * \param graph The generated graph is stored here. + * \param n The number of vertices in the graph. + * \param m The number of vertices per block. n/m must be integer. + * \param rho The fraction of vertices per cluster, + * within a block. Must sum up to 1, and rho * m must be integer + * for all elements of rho. + * \param C A square, symmetric numeric matrix, the Bernoulli rates for + * the clusters within a block. Its size must mach the size of the + * \p rho vector. + * \param p The Bernoulli rate of connections between + * vertices in different blocks. + * \return Error code. + * + * \sa \ref igraph_sbm_game() for the classic stochastic block model, + * \ref igraph_hsbm_list_game() for a more general version. + */ + +igraph_error_t igraph_hsbm_game(igraph_t *graph, igraph_int_t n, + igraph_int_t m, const igraph_vector_t *rho, + const igraph_matrix_t *C, igraph_real_t p) { + +#define CHECK_MAXEDGES() \ + do {if (maxedges > IGRAPH_MAX_EXACT_REAL) { \ + IGRAPH_ERROR("Too many vertices, overflow in maximum number of edges.", IGRAPH_EOVERFLOW); \ + }} while (0) + igraph_int_t b, i, k = igraph_vector_size(rho); + igraph_vector_t csizes; + igraph_real_t sq_dbl_epsilon = sqrt(DBL_EPSILON); + igraph_int_t no_blocks = n / m; + igraph_vector_int_t edges; + igraph_int_t offset = 0; + + if (n < 1) { + IGRAPH_ERROR("`n' must be positive for HSBM", IGRAPH_EINVAL); + } + if (m < 1) { + IGRAPH_ERROR("`m' must be positive for HSBM", IGRAPH_EINVAL); + } + if (n % m) { + IGRAPH_ERROR("`n' must be a multiple of `m' for HSBM", IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(rho, 0, 1)) { + IGRAPH_ERROR("`rho' must be between zero and one for HSBM", + IGRAPH_EINVAL); + } + if (igraph_matrix_min(C) < 0 || igraph_matrix_max(C) > 1) { + IGRAPH_ERROR("`C' must be between zero and one for HSBM", IGRAPH_EINVAL); + } + if (fabs(igraph_vector_sum(rho) - 1.0) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' must sum up to 1 for HSBM", IGRAPH_EINVAL); + } + if (igraph_matrix_nrow(C) != k || igraph_matrix_ncol(C) != k) { + IGRAPH_ERROR("`C' dimensions must match `rho' dimensions in HSBM", + IGRAPH_EINVAL); + } + if (!igraph_matrix_is_symmetric(C)) { + IGRAPH_ERROR("`C' must be a symmetric matrix", IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("`p' must be a probability for HSBM", IGRAPH_EINVAL); + } + for (i = 0; i < k; i++) { + igraph_real_t s = VECTOR(*rho)[i] * m; + if (fabs(round(s) - s) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' * `m' is not integer in HSBM", IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&csizes, k); + for (i = 0; i < k; i++) { + VECTOR(csizes)[i] = round(VECTOR(*rho)[i] * m); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + /* Block models first */ + + for (b = 0; b < no_blocks; b++) { + igraph_int_t from, to, fromoff = 0; + + for (from = 0; from < k; from++) { + igraph_int_t fromsize = VECTOR(csizes)[from]; + igraph_int_t i, tooff = 0; + for (i = 0; i < from; i++) { + tooff += VECTOR(csizes)[i]; + } + for (to = from; to < k; to++) { + igraph_int_t tosize = VECTOR(csizes)[to]; + igraph_real_t prob = MATRIX(*C, from, to); + igraph_real_t maxedges; + igraph_real_t last = RNG_GEOM(prob); /* RNG_GEOM may return NaN so igraph_int_t is not suitable */ + if (from != to) { + maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + while (last < maxedges) { + igraph_int_t vto = floor(last / fromsize); + igraph_int_t vfrom = last - ((igraph_real_t) vto) * fromsize; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + tooff + vto)); + last += RNG_GEOM(prob); + last += 1; + } + } else { /* from==to */ + maxedges = ((igraph_real_t) fromsize) * (fromsize - 1.0) / 2.0; + CHECK_MAXEDGES(); + while (last < maxedges) { + igraph_int_t vto = floor((sqrt(8 * last + 1) + 1) / 2); + igraph_int_t vfrom = last - (((igraph_real_t) vto) * (vto - 1.0)) / 2.0; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + tooff + vto)); + last += RNG_GEOM(prob); + last += 1; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + offset += m; + } + + /* And now the rest, if not a special case */ + + if (p == 1) { + igraph_int_t fromoff = 0, tooff = m; + for (b = 0; b < no_blocks; b++) { + igraph_int_t fromsize = m; + igraph_int_t tosize = n - tooff; + igraph_int_t from, to; + for (from = 0; from < fromsize; from++) { + for (to = 0; to < tosize; to++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, fromoff + from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tooff + to)); + } + } + fromoff += m; + tooff += m; + } + } else if (p > 0) { + igraph_int_t fromoff = 0, tooff = m; + for (b = 0; b < no_blocks; b++) { + igraph_int_t fromsize = m; + igraph_int_t tosize = n - tooff; + igraph_real_t maxedges = ((igraph_real_t) fromsize) * tosize; + CHECK_MAXEDGES(); + igraph_real_t last = RNG_GEOM(p); /* RNG_GEOM may return NaN so igraph_int_t is not suitable */ + while (last < maxedges) { + igraph_int_t vto = floor(last / fromsize); + igraph_int_t vfrom = last - ((igraph_real_t) vto) * fromsize; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tooff + vto)); + last += RNG_GEOM(p); + last += 1; + } + + fromoff += m; + tooff += m; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, /*directed=*/ 0)); + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&csizes); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +#undef CHECK_MAXEDGES +} + +/** + * \function igraph_hsbm_list_game + * \brief Hierarchical stochastic block model, more general version. + * + * The function generates a random graph according to the hierarchical + * stochastic block model. + * + * \param graph The generated graph is stored here. + * \param n The number of vertices in the graph. + * \param mlist An integer vector of block sizes. + * \param rholist A list of rho vectors (\c igraph_vector_t objects), one + * for each block. + * \param Clist A list of square matrices (\c igraph_matrix_t objects), + * one for each block, specifying the Bernoulli rates of connections + * within the block. + * \param p The Bernoulli rate of connections between + * vertices in different blocks. + * \return Error code. + * + * \sa \ref igraph_sbm_game() for the classic stochastic block model, + * \ref igraph_hsbm_game() for a simpler general version. + */ + +igraph_error_t igraph_hsbm_list_game(igraph_t *graph, igraph_int_t n, + const igraph_vector_int_t *mlist, + const igraph_vector_list_t *rholist, + const igraph_matrix_list_t *Clist, + igraph_real_t p) { + + igraph_int_t i, no_blocks = igraph_vector_list_size(rholist); + igraph_real_t sq_dbl_epsilon = sqrt(DBL_EPSILON); + igraph_vector_int_t edges; + igraph_vector_t csizes; + igraph_int_t b, offset = 0; + + if (n < 1) { + IGRAPH_ERROR("`n' must be positive for HSBM.", IGRAPH_EINVAL); + } + if (no_blocks == 0) { + IGRAPH_ERROR("`rholist' empty for HSBM.", IGRAPH_EINVAL); + } + if (igraph_matrix_list_size(Clist) != no_blocks && + igraph_vector_int_size(mlist) != no_blocks) { + IGRAPH_ERROR("`rholist' must have same length as `Clist' and `m' " + "for HSBM.", IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("`p' must be a probability for HSBM.", IGRAPH_EINVAL); + } + /* Checks for m's */ + if (igraph_vector_int_sum(mlist) != n) { + IGRAPH_ERROR("`m' must sum up to `n' for HSBM.", IGRAPH_EINVAL); + } + if (igraph_vector_int_min(mlist) < 1) { + IGRAPH_ERROR("`m' must be positive for HSBM.", IGRAPH_EINVAL); + } + /* Checks for the rhos */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = igraph_vector_list_get_ptr(rholist, i); + if (!igraph_vector_isininterval(rho, 0, 1)) { + IGRAPH_ERROR("`rho' must be between zero and one for HSBM.", + IGRAPH_EINVAL); + } + if (fabs(igraph_vector_sum(rho) - 1.0) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' must sum up to 1 for HSBM.", IGRAPH_EINVAL); + } + } + /* Checks for the Cs */ + for (i = 0; i < no_blocks; i++) { + const igraph_matrix_t *C = igraph_matrix_list_get_ptr(Clist, i); + if (igraph_matrix_min(C) < 0 || igraph_matrix_max(C) > 1) { + IGRAPH_ERROR("Bernoulli rates must be between zero and one for HSBM.", + IGRAPH_EINVAL); + } + if (!igraph_matrix_is_symmetric(C)) { + IGRAPH_ERROR("Bernoulli rate matrices must be symmetric.", IGRAPH_EINVAL); + } + } + /* Check that C and rho sizes match */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = igraph_vector_list_get_ptr(rholist, i); + const igraph_matrix_t *C = igraph_matrix_list_get_ptr(Clist, i); + igraph_int_t k = igraph_vector_size(rho); + if (igraph_matrix_nrow(C) != k || igraph_matrix_ncol(C) != k) { + IGRAPH_ERROR("All Bernoulli rate matrix dimensions must match `rho' " + "dimensions in HSBM.", + IGRAPH_EINVAL); + } + } + /* Check that rho * m is integer */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = igraph_vector_list_get_ptr(rholist, i); + igraph_real_t m = VECTOR(*mlist)[i]; + igraph_int_t j, k = igraph_vector_size(rho); + for (j = 0; j < k; j++) { + igraph_real_t s = VECTOR(*rho)[j] * m; + if (fabs(round(s) - s) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' * `m' is not integer in HSBM.", IGRAPH_EINVAL); + } + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&csizes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + /* Block models first */ + + for (b = 0; b < no_blocks; b++) { + igraph_int_t from, to, fromoff = 0; + const igraph_vector_t *rho = igraph_vector_list_get_ptr(rholist, b); + const igraph_matrix_t *C = igraph_matrix_list_get_ptr(Clist, b); + igraph_int_t m = VECTOR(*mlist)[b]; + igraph_int_t k = igraph_vector_size(rho); + + IGRAPH_CHECK(igraph_vector_resize(&csizes, k)); + for (i = 0; i < k; i++) { + VECTOR(csizes)[i] = round(VECTOR(*rho)[i] * m); + } + + for (from = 0; from < k; from++) { + igraph_int_t fromsize = VECTOR(csizes)[from]; + igraph_int_t i, tooff = 0; + for (i = 0; i < from; i++) { + tooff += VECTOR(csizes)[i]; + } + for (to = from; to < k; to++) { + igraph_int_t tosize = VECTOR(csizes)[to]; + igraph_real_t prob = MATRIX(*C, from, to); + igraph_real_t maxedges; + igraph_real_t last = RNG_GEOM(prob); /* RNG_GEOM may return NaN so igraph_int_t is not suitable */ + if (from != to) { + maxedges = ((igraph_real_t) fromsize) * tosize; + while (last < maxedges) { + igraph_int_t vto = floor(last / fromsize); + igraph_int_t vfrom = last - ((igraph_real_t) vto) * fromsize; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + tooff + vto)); + last += RNG_GEOM(prob); + last += 1; + } + } else { /* from==to */ + maxedges = ((igraph_real_t) fromsize) * (fromsize - 1.0) / 2.0; + while (last < maxedges) { + igraph_int_t vto = floor((sqrt(8 * last + 1) + 1) / 2); + igraph_int_t vfrom = last - (((igraph_real_t) vto) * (vto - 1.0)) / 2.0; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, offset + tooff + vto)); + last += RNG_GEOM(prob); + last += 1; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + offset += m; + } + + /* And now the rest, if not a special case */ + + if (p == 1) { + igraph_int_t fromoff = 0, tooff = VECTOR(*mlist)[0]; + for (b = 0; b < no_blocks; b++) { + igraph_int_t fromsize = VECTOR(*mlist)[b]; + igraph_int_t tosize = n - tooff; + igraph_int_t from, to; + for (from = 0; from < fromsize; from++) { + for (to = 0; to < tosize; to++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, fromoff + from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tooff + to)); + } + } + fromoff += fromsize; + if (b + 1 < no_blocks) { + tooff += VECTOR(*mlist)[b + 1]; + } + } + } else if (p > 0) { + igraph_int_t fromoff = 0, tooff = VECTOR(*mlist)[0]; + for (b = 0; b < no_blocks; b++) { + igraph_int_t fromsize = VECTOR(*mlist)[b]; + igraph_int_t tosize = n - tooff; + igraph_real_t maxedges = ((igraph_real_t) fromsize) * tosize; + igraph_real_t last = RNG_GEOM(p); /* RNG_GEOM may return NaN so igraph_int_t is not suitable */ + while (last < maxedges) { + igraph_int_t vto = floor(last / fromsize); + igraph_int_t vfrom = last - ((igraph_real_t) vto) * fromsize; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, fromoff + vfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tooff + vto)); + last += RNG_GEOM(p); + last += 1; + } + + fromoff += fromsize; + if (b + 1 < no_blocks) { + tooff += VECTOR(*mlist)[b + 1]; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, /*directed=*/ 0)); + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&csizes); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/static_fitness.c b/src/games/static_fitness.c new file mode 100644 index 0000000..19177f3 --- /dev/null +++ b/src/games/static_fitness.c @@ -0,0 +1,464 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_adjlist.h" +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "core/math.h" /* M_SQRT2 */ +#include "misc/graphicality.h" + +/** + * \ingroup generators + * \function igraph_static_fitness_game + * \brief Non-growing random graph with edge probabilities proportional to node fitness scores. + * + * This game generates a directed or undirected random graph where the + * probability of an edge between vertices \c i and \c j depends on the fitness + * scores of the two vertices involved. For undirected graphs, each vertex + * has a single fitness score. For directed graphs, each vertex has an out- + * and an in-fitness, and the probability of an edge from \c i to \c j depends on + * the out-fitness of vertex \c i and the in-fitness of vertex \c j. + * + * + * The generation process goes as follows. We start from \c N disconnected nodes + * (where \c N is given by the length of the fitness vector). Then we randomly + * select two vertices \c i and \c j, with probabilities proportional to their + * fitnesses. (When the generated graph is directed, \c i is selected according to + * the out-fitnesses and \c j is selected according to the in-fitnesses). If the + * vertices are not connected yet (or if multiple edges are allowed), we + * connect them; otherwise we select a new pair. This is repeated until the + * desired number of links are created. + * + * + * The \em expected degree (though not the actual degree) of each vertex will be + * proportional to its fitness. This is exactly true when self-loops and multi-edges + * are allowed, and approximately true otherwise. If you need to generate a graph + * with an exact degree sequence, consider \ref igraph_degree_sequence_game() and + * \ref igraph_realize_degree_sequence() instead. + * + * + * To generate random undirected graphs with a given expected degree sequence, set + * \p fitness_out (and in the directed case \p fitness_out) to the desired expected + * degrees, and \p no_of_edges to the corresponding edge count, i.e. half the sum of + * expected degrees in the undirected case, and the sum of out- or in-degrees in the + * directed case. + * + * + * This model is similar to the better-known Chung-Lu model, implemented in igraph + * as \ref igraph_chung_lu_game(), but with a sharply fixed edge count. + * + * + * This model is commonly used to generate static scale-free networks. To + * achieve this, you have to draw the fitness scores from the desired power-law + * distribution. Alternatively, you may use \ref igraph_static_power_law_game() + * which generates the fitnesses for you with a given exponent. + * + * + * Reference: + * + * + * Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution + * in scale-free networks. Phys Rev Lett 87(27):278701, 2001 + * https://doi.org/10.1103/PhysRevLett.87.278701. + * + * \param graph Pointer to an uninitialized graph object. + * \param no_of_edges The number of edges in the generated graph. + * \param fitness_out A numeric vector containing the fitness of each vertex. + * For directed graphs, this specifies the out-fitness + * of each vertex. + * \param fitness_in If \c NULL, the generated graph will be undirected. + * If not \c NULL, this argument specifies the in-fitness + * of each vertex. + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are allowed in the generated graph. See \ref igraph_edge_type_sw_t. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * \sa \ref igraph_static_power_law_game(), \ref igraph_chung_lu_game(), + * \ref igraph_degree_sequence_game() + * + * Time complexity: O(|V| + |E| log |E|). + */ +igraph_error_t igraph_static_fitness_game(igraph_t *graph, igraph_int_t no_of_edges, + const igraph_vector_t *fitness_out, const igraph_vector_t *fitness_in, + igraph_edge_type_sw_t allowed_edge_types) { + + const igraph_int_t no_of_nodes = igraph_vector_size(fitness_out); + const igraph_bool_t directed = (fitness_in != NULL); + igraph_vector_int_t edges; + igraph_vector_t cum_fitness_in, cum_fitness_out; + igraph_vector_t *p_cum_fitness_in, *p_cum_fitness_out; + igraph_real_t x, max_in, max_out; + igraph_real_t max_no_of_edges; + igraph_real_t num_steps; + igraph_int_t from, to, pos; + igraph_bool_t loops, multiple; + int iter = 0; + + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + IGRAPH_ASSERT(fitness_out != NULL); + + if (no_of_edges < 0) { + IGRAPH_ERRORF("Number of edges cannot be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, no_of_edges); + } + + if (no_of_edges > IGRAPH_ECOUNT_MAX && (no_of_nodes == 0 && no_of_edges > 0)) { + IGRAPH_ERROR("Too many edges requested.", IGRAPH_EINVAL); + } + + if (no_of_nodes == 0) { + return igraph_empty(graph, no_of_nodes, directed); + } + + if (directed && igraph_vector_size(fitness_in) != no_of_nodes) { + IGRAPH_ERROR("The in-fitness vector must have the same length as the out-fitness vector.", IGRAPH_EINVAL); + } + + /* Sanity checks for the fitnesses */ + if (igraph_vector_min(fitness_out) < 0 && (!directed || igraph_vector_min(fitness_in) < 0)) { + IGRAPH_ERROR("Fitness scores must not be negative.", IGRAPH_EINVAL); + } + + /* Avoid getting into an infinite loop when too many edges are requested. */ + { + igraph_int_t nodes; + if (directed) { + igraph_int_t outnodes, innodes; + outnodes = innodes = nodes = 0; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fitness_out)[i] != 0) { + outnodes++; + } + if (VECTOR(*fitness_in)[i] != 0) { + innodes++; + } + if (VECTOR(*fitness_out)[i] != 0 && VECTOR(*fitness_in)[i] != 0) { + nodes++; + } + } + max_no_of_edges = ((igraph_real_t) outnodes) * innodes - (loops ? 0 : nodes); + } else { + nodes = 0; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fitness_out)[i] != 0) { + nodes++; + } + } + max_no_of_edges = loops + ? nodes * ((igraph_real_t)nodes + 1) / 2 + : nodes * ((igraph_real_t)nodes - 1) / 2; + } + if (max_no_of_edges == 0 && no_of_edges > 0) { + /* This check is necessary even when multiple edges are allowed, + * for example when all fitnesses are zero or only one is nonzero + * not self-loops are disallowed. */ + IGRAPH_ERRORF("No edges are possible with the given fitness scores, " + "but %" IGRAPH_PRId " edges were requested.", IGRAPH_EINVAL, no_of_edges); + } + if (!multiple && no_of_edges > max_no_of_edges) { + IGRAPH_ERROR("Too many edges requested.", IGRAPH_EINVAL); + } + } + + /* Calculate the cumulative fitness scores */ + IGRAPH_VECTOR_INIT_FINALLY(&cum_fitness_out, no_of_nodes); + IGRAPH_CHECK(igraph_vector_cumsum(&cum_fitness_out, fitness_out)); + max_out = igraph_vector_tail(&cum_fitness_out); + p_cum_fitness_out = &cum_fitness_out; + if (directed) { + IGRAPH_VECTOR_INIT_FINALLY(&cum_fitness_in, no_of_nodes); + IGRAPH_CHECK(igraph_vector_cumsum(&cum_fitness_in, fitness_in)); + max_in = igraph_vector_tail(&cum_fitness_in); + p_cum_fitness_in = &cum_fitness_in; + } else { + max_in = max_out; + p_cum_fitness_in = &cum_fitness_out; + } + + num_steps = no_of_edges; + if (multiple) { + /* Generating when multiple edges are allowed */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 2 * no_of_edges)); + + while (no_of_edges > 0) { + /* Report progress after every 10000 edges */ + if ((iter++) % 10000 == 0) { + IGRAPH_PROGRESS("Static fitness game", 100.0 * (1 - no_of_edges / num_steps), NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + x = RNG_UNIF(0, max_out); + igraph_vector_binsearch(p_cum_fitness_out, x, &from); + x = RNG_UNIF(0, max_in); + igraph_vector_binsearch(p_cum_fitness_in, x, &to); + + /* Skip if loop edge and loops = false */ + if (!loops && from == to) { + continue; + } + + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + + no_of_edges--; + } + + /* Create the graph */ + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + /* Clear the edge list */ + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Multiple edges are disallowed */ + igraph_adjlist_t al; + igraph_vector_int_t* neis; + + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + while (no_of_edges > 0) { + /* Report progress after every 10000 edges */ + if ((iter++) % 10000 == 0) { + IGRAPH_PROGRESS("Static fitness game", 100.0 * (1 - no_of_edges / num_steps), NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + x = RNG_UNIF(0, max_out); + igraph_vector_binsearch(p_cum_fitness_out, x, &from); + x = RNG_UNIF(0, max_in); + igraph_vector_binsearch(p_cum_fitness_in, x, &to); + + /* Skip if loop edge and loops = false */ + if (!loops && from == to) { + continue; + } + + /* For undirected graphs, ensure that from < to */ + if (!directed && from > to) { + pos = from; from = to; to = pos; + } + + /* Is there already an edge? If so, try again */ + neis = igraph_adjlist_get(&al, from); + if (igraph_vector_int_binsearch(neis, to, &pos)) { + continue; + } + + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, pos, to)); + + no_of_edges--; + } + + /* Create the graph. We cannot use IGRAPH_ALL here for undirected graphs + * because we did not add edges in both directions in the adjacency list. + * We will use igraph_to_undirected in an extra step. */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, 1)); + if (!directed) { + IGRAPH_CHECK(igraph_to_undirected(graph, IGRAPH_TO_UNDIRECTED_EACH, 0)); + } + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_PROGRESS("Static fitness game", 100.0, NULL); + + /* Cleanup before we create the graph */ + if (directed) { + igraph_vector_destroy(&cum_fitness_in); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&cum_fitness_out); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_static_power_law_game + * \brief Generates a non-growing random graph with expected power-law degree distributions. + * + * This game generates a directed or undirected random graph where the + * degrees of vertices follow power-law distributions with prescribed + * exponents. For directed graphs, the exponents of the in- and out-degree + * distributions may be specified separately. + * + * + * The game simply uses \ref igraph_static_fitness_game() with appropriately + * constructed fitness vectors. In particular, the fitness of vertex \c i + * is i^(-alpha), where alpha = 1/(gamma-1) + * and \c gamma is the exponent given in the arguments. + * + * + * To remove correlations between in- and out-degrees in case of directed + * graphs, the in-fitness vector will be shuffled after it has been set up + * and before \ref igraph_static_fitness_game() is called. + * + * + * Note that significant finite size effects may be observed for exponents + * smaller than 3 in the original formulation of the game. This function + * provides an argument that lets you remove the finite size effects by + * assuming that the fitness of vertex \c i is + * (i+i0-1)^(-alpha), + * where \c i0 is a constant chosen appropriately to ensure that the maximum + * degree is less than the square root of the number of edges times the + * average degree; see the paper of Chung and Lu, and Cho et al for more + * details. + * + * + * References: + * + * + * Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution + * in scale-free networks. Phys Rev Lett 87(27):278701, 2001. + * https://doi.org/10.1103/PhysRevLett.87.278701 + * + * + * Chung F and Lu L: Connected components in a random graph with given + * degree sequences. Annals of Combinatorics 6, 125-145, 2002. + * https://doi.org/10.1007/PL00012580 + * + * + * Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in + * scale-free networks under the Achlioptas process. Phys Rev Lett + * 103:135702, 2009. + * https://doi.org/10.1103/PhysRevLett.103.135702 + * + * \param graph Pointer to an uninitialized graph object. + * \param no_of_nodes The number of nodes in the generated graph. + * \param no_of_edges The number of edges in the generated graph. + * \param exponent_out The power law exponent of the degree distribution. + * For directed graphs, this specifies the exponent of the + * out-degree distribution. It must be greater than or + * equal to 2. If you pass \c IGRAPH_INFINITY here, you + * will get back an Erdős-Rényi random network. + * \param exponent_in If negative, the generated graph will be undirected. + * If greater than or equal to 2, this argument specifies + * the exponent of the in-degree distribution. If + * non-negative but less than 2, an error will be + * generated. + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are allowed in the generated graph. See \ref igraph_edge_type_sw_t. + * \param finite_size_correction Whether to use the proposed finite size + * correction of Cho et al. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V| + |E| log |E|). + */ +igraph_error_t igraph_static_power_law_game(igraph_t *graph, + igraph_int_t no_of_nodes, igraph_int_t no_of_edges, + igraph_real_t exponent_out, igraph_real_t exponent_in, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t finite_size_correction) { + + igraph_vector_t fitness_out, fitness_in; + igraph_real_t alpha_out, alpha_in; + igraph_real_t j; + + if (no_of_nodes < 0) { + IGRAPH_ERRORF("Number of nodes cannot be negative, got %" IGRAPH_PRId".", IGRAPH_EINVAL, no_of_nodes); + } + + /* Calculate alpha_out */ + if (exponent_out < 2) { + IGRAPH_ERRORF("Out-degree exponent must be >= 2, got %g.", IGRAPH_EINVAL, exponent_out); + } else if (isfinite(exponent_out)) { + alpha_out = -1.0 / (exponent_out - 1); + } else { + alpha_out = 0.0; + } + + /* Construct the out-fitnesses */ + IGRAPH_VECTOR_INIT_FINALLY(&fitness_out, no_of_nodes); + j = no_of_nodes; + if (finite_size_correction && alpha_out < -0.5) { + /* See the Cho et al paper, first page first column + footnote 7 */ + j += pow(no_of_nodes, 1 + 0.5 / alpha_out) * + pow(10 * M_SQRT2 * (1 + alpha_out), -1.0 / alpha_out) - 1; + } + if (j < no_of_nodes) { + j = no_of_nodes; + } + for (igraph_int_t i = 0; i < no_of_nodes; i++, j--) { + VECTOR(fitness_out)[i] = pow(j, alpha_out); + } + + if (exponent_in >= 0) { + if (exponent_in < 2) { + IGRAPH_ERRORF("For directed graphs the in-degree exponent must be >= 2, got %g.", + IGRAPH_EINVAL, exponent_in); + } else if (isfinite(exponent_in)) { + alpha_in = -1.0 / (exponent_in - 1); + } else { + alpha_in = 0.0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&fitness_in, no_of_nodes); + j = no_of_nodes; + if (finite_size_correction && alpha_in < -0.5) { + /* See the Cho et al paper, first page first column + footnote 7 */ + j += pow(no_of_nodes, 1 + 0.5 / alpha_in) * + pow(10 * M_SQRT2 * (1 + alpha_in), -1.0 / alpha_in) - 1; + } + if (j < no_of_nodes) { + j = no_of_nodes; + } + for (igraph_int_t i = 0; i < no_of_nodes; i++, j--) { + VECTOR(fitness_in)[i] = pow(j, alpha_in); + } + igraph_vector_shuffle(&fitness_in); + + IGRAPH_CHECK(igraph_static_fitness_game(graph, no_of_edges, + &fitness_out, &fitness_in, allowed_edge_types)); + + igraph_vector_destroy(&fitness_in); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_static_fitness_game(graph, no_of_edges, + &fitness_out, NULL, allowed_edge_types)); + } + + igraph_vector_destroy(&fitness_out); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/games/tree.c b/src/games/tree.c new file mode 100644 index 0000000..73fc097 --- /dev/null +++ b/src/games/tree.c @@ -0,0 +1,190 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_bitset.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "math/safe_intop.h" + +/* Uniform sampling of labelled trees (igraph_tree_game) */ + +/* The following implementation uniformly samples Prufer trees and converts + * them to trees. + */ + +static igraph_error_t igraph_i_tree_game_prufer(igraph_t *graph, igraph_int_t n, igraph_bool_t directed) { + igraph_vector_int_t prufer; + + if (directed) { + IGRAPH_ERROR("The Prufer method for random tree generation does not support directed trees", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&prufer, n - 2); + + for (igraph_int_t i = 0; i < n - 2; ++i) { + VECTOR(prufer)[i] = RNG_INTEGER(0, n - 1); + } + + IGRAPH_CHECK(igraph_from_prufer(graph, &prufer)); + + igraph_vector_int_destroy(&prufer); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* The following implementation is based on loop-erased random walks and Wilson's algorithm + * for uniformly sampling spanning trees. We effectively sample spanning trees of the complete + * graph. + */ + +/* swap two elements of a vector_int */ +#define SWAP_INT_ELEM(vec, i, j) \ + { \ + igraph_int_t temp; \ + temp = VECTOR(vec)[i]; \ + VECTOR(vec)[i] = VECTOR(vec)[j]; \ + VECTOR(vec)[j] = temp; \ + } + +static igraph_error_t igraph_i_tree_game_loop_erased_random_walk(igraph_t *graph, igraph_int_t n, igraph_bool_t directed) { + igraph_vector_int_t edges; + igraph_vector_int_t vertices; + igraph_bitset_t visited; + igraph_int_t i, j; + igraph_int_t no_edges; + + IGRAPH_SAFE_MULT(n - 1, 2, &no_edges); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_edges); + + IGRAPH_CHECK(igraph_bitset_init(&visited, n)); + IGRAPH_FINALLY(igraph_bitset_destroy, &visited); + + /* The vertices vector contains visited vertices between 0..k-1, unvisited ones between k..n-1. */ + IGRAPH_CHECK(igraph_vector_int_init_range(&vertices, 0, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vertices); + + /* A simple implementation could be as below. This is for illustration only. + * The actually implemented algorithm avoids unnecessary walking on the already visited + * portion of the vertex set. + */ + /* + // pick starting point for the walk + i = RNG_INTEGER(0, n-1); + VECTOR(visited)[i] = 1; + + k=1; + while (k < n) { + // pick next vertex in the walk + j = RNG_INTEGER(0, n-1); + // if it has not been visited before, connect to the previous vertex in the sequence + if (! VECTOR(visited)[j]) { + VECTOR(edges)[2*k - 2] = i; + VECTOR(edges)[2*k - 1] = j; + VECTOR(visited)[j] = 1; + k++; + } + i=j; + } + */ + + i = RNG_INTEGER(0, n - 1); + IGRAPH_BIT_SET(visited, i); + SWAP_INT_ELEM(vertices, 0, i); + + for (igraph_int_t k = 1; k < n; ++k) { + j = RNG_INTEGER(0, n - 1); + if (IGRAPH_BIT_TEST(visited, VECTOR(vertices)[j])) { + i = VECTOR(vertices)[j]; + j = RNG_INTEGER(k, n - 1); + } + IGRAPH_BIT_SET(visited, VECTOR(vertices)[j]); + SWAP_INT_ELEM(vertices, k, j); + VECTOR(edges)[2 * k - 2] = i; + i = VECTOR(vertices)[k]; + VECTOR(edges)[2 * k - 1] = i; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&vertices); + igraph_bitset_destroy(&visited); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +#undef SWAP_INT_ELEM + +/** + * \ingroup generators + * \function igraph_tree_game + * \brief Generates a random tree with the given number of nodes. + * + * This function samples uniformly from the set of labelled trees, + * i.e. it generates each labelled tree with the same probability. + * + * + * Note that for n=0, the null graph is returned, + * which is not considered to be a tree by \ref igraph_is_tree(). + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of nodes in the tree. + * \param directed Whether to create a directed tree. The edges are oriented away from the root. + * \param method The algorithm to use to generate the tree. Possible values: + * \clist + * \cli IGRAPH_RANDOM_TREE_PRUFER + * This algorithm samples Prüfer sequences uniformly, then converts them to trees. + * Directed trees are not currently supported. + * \cli IGRAPH_RANDOM_LERW + * This algorithm effectively performs a loop-erased random walk on the complete graph + * to uniformly sample its spanning trees (Wilson's algorithm). + * \endclist + * \return Error code: + * \c IGRAPH_ENOMEM: there is not enough + * memory to perform the operation. + * \c IGRAPH_EINVAL: invalid tree size + * + * \sa \ref igraph_from_prufer() + * + */ + +igraph_error_t igraph_tree_game(igraph_t *graph, igraph_int_t n, igraph_bool_t directed, igraph_random_tree_t method) { + if (n < 2) { + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + return IGRAPH_SUCCESS; + } + + switch (method) { + case IGRAPH_RANDOM_TREE_PRUFER: + return igraph_i_tree_game_prufer(graph, n, directed); + case IGRAPH_RANDOM_TREE_LERW: + return igraph_i_tree_game_loop_erased_random_walk(graph, n, directed); + default: + IGRAPH_ERROR("Invalid method for random tree construction", IGRAPH_EINVAL); + } +} diff --git a/src/games/watts_strogatz.c b/src/games/watts_strogatz.c new file mode 100644 index 0000000..24eb208 --- /dev/null +++ b/src/games/watts_strogatz.c @@ -0,0 +1,118 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +/** + * \function igraph_watts_strogatz_game + * \brief The Watts-Strogatz small-world model. + * + * This function generates networks with the small-world property + * based on a variant of the Watts-Strogatz model. The network is obtained + * by first creating a periodic undirected lattice, then rewiring both + * endpoints of each edge with probability \p p, while avoiding the + * creation of multi-edges. + * + * + * This process differs from the original model of Watts and Strogatz + * (see reference) in that it rewires \em both endpoints of edges. Thus in + * the limit of p=1, we obtain a G(n,m) random graph with the + * same number of vertices and edges as the original lattice. In comparison, + * the original Watts-Strogatz model only rewires a single endpoint of each edge, + * thus the network does not become fully random even for p=1. + * For appropriate choices of \p p, both models exhibit the property of + * simultaneously having short path lengths and high clustering. + * + * + * Reference: + * + * + * Duncan J Watts and Steven H Strogatz: + * Collective dynamics of small world networks, + * Nature 393, 440-442, 1998. + * https://doi.org/10.1038/30918 + * + * \param graph The graph to initialize. + * \param dim The dimension of the lattice. + * \param size The size of the lattice along each dimension. + * \param nei The size of the neighborhood for each vertex. This is + * the same as the \p order argument of \ref igraph_connect_neighborhood(). + * \param p The rewiring probability. A real number between zero and + * one (inclusive). + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are allowed in the generated graph. See \ref igraph_edge_type_sw_t. + * \return Error code. + * + * \sa \ref igraph_square_lattice(), \ref igraph_connect_neighborhood() and + * \ref igraph_rewire_edges() can be used if more flexibility is + * needed, e.g. a different type of lattice. + * + * Time complexity: O(|V|*d^o+|E|), |V| and |E| are the number of + * vertices and edges, d is the average degree, o is the \p nei + * argument. + */ +igraph_error_t igraph_watts_strogatz_game( + igraph_t *graph, igraph_int_t dim, + igraph_int_t size, igraph_int_t nei, + igraph_real_t p, + igraph_edge_type_sw_t allowed_edge_types) { + + igraph_vector_int_t dimvector; + igraph_vector_bool_t periodic; + + if (dim < 1) { + IGRAPH_ERROR("WS game: dimension should be at least one", IGRAPH_EINVAL); + } + if (size < 1) { + IGRAPH_ERROR("WS game: lattice size should be at least one", + IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("WS game: rewiring probability should be between 0 and 1", + IGRAPH_EINVAL); + } + + /* Create the lattice first */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&dimvector, dim); + igraph_vector_int_fill(&dimvector, size); + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&periodic, dim); + igraph_vector_bool_fill(&periodic, true); + + IGRAPH_CHECK(igraph_square_lattice(graph, &dimvector, nei, IGRAPH_UNDIRECTED, + /* mutual */ false, &periodic)); + + igraph_vector_bool_destroy(&periodic); + igraph_vector_int_destroy(&dimvector); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_FINALLY(igraph_destroy, graph); + + /* Rewire the edges then */ + + IGRAPH_CHECK(igraph_rewire_edges(graph, p, allowed_edge_types)); + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/graph/adjlist.c b/src/graph/adjlist.c new file mode 100644 index 0000000..5bf5894 --- /dev/null +++ b/src/graph/adjlist.c @@ -0,0 +1,1298 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_memory.h" +#include "igraph_interface.h" + +#include "core/interruption.h" + +#include + +/** + * Helper function that simplifies a sorted adjacency vector by removing + * duplicate elements and optionally self-loops. + * + * has_loops and has_multiple are pointers to booleans that will be updated + * to \c true if the function \em finds a loop or a multiple edge. These values will + * \em never be set back to zero by this function. The usage pattern for these + * arguments is that the caller should set them to zero, followed by one or + * multiple calls to this function; at the end of such a sequence the booleans + * will contain whether the function found at least one loop or multiple edge + * in the set of vertices that were investigated. + * + * Note the usage of the word "found" -- it might be the case that the + * function is not interested in loop or multiple edges due to how it is + * parameterized; in this case, we don't spend extra time in investigating the + * existence of loop or multiple edges, so the values of the has_loops and + * has_multiple arguments will stay as is. Therefore, upon exiting the + * function, finding \c false in one of these variables does \em not mean that + * there is no loop or multiple edge, only that the function hasn't found one. + */ +static igraph_error_t igraph_i_simplify_sorted_int_adjacency_vector_in_place( + igraph_vector_int_t *v, igraph_int_t index, igraph_neimode_t mode, + igraph_loops_t loops, igraph_bool_t multiple, igraph_bool_t *has_loops, + igraph_bool_t *has_multiple +); + +/** + * \section about_adjlists + * + * Sometimes it is easier to work with a graph which is in + * adjacency list format: a list of vectors; each vector contains the + * neighbor vertices or incident edges of a given vertex. Typically, + * this representation is good if we need to iterate over the neighbors + * of all vertices many times. E.g. when finding the shortest paths + * between all pairs of vertices or calculating closeness centrality + * for all the vertices. + * + * The igraph_adjlist_t stores the adjacency lists + * of a graph. After creation it is independent of the original graph, + * it can be modified freely with the usual vector operations, the + * graph is not affected. E.g. the adjacency list can be used to + * rewire the edges of a graph efficiently. If one used the + * straightforward \ref igraph_delete_edges() and \ref + * igraph_add_edges() combination for this that needs O(|V|+|E|) time + * for every single deletion and insertion operation, it is thus very + * slow if many edges are rewired. Extracting the graph into an + * adjacency list, do all the rewiring operations on the vectors of + * the adjacency list and then creating a new graph needs (depending + * on how exactly the rewiring is done) typically O(|V|+|E|) time for + * the whole rewiring process. + * + * Lazy adjacency lists are a bit different. When creating a + * lazy adjacency list, the neighbors of the vertices are not queried, + * only some memory is allocated for the vectors. When \ref + * igraph_lazy_adjlist_get() is called for vertex v the first time, + * the neighbors of v are queried and stored in a vector of the + * adjacency list, so they don't need to be queried again. Lazy + * adjacency lists are handy if you have an at least linear operation + * (because initialization is generally linear in terms of the number of + * vertices), but you don't know how many vertices you will visit + * during the computation. + * + * + * + * \example examples/simple/adjlist.c + * + */ + +/** + * \function igraph_adjlist_init + * \brief Constructs an adjacency list of vertices from a given graph. + * + * Creates a list of vectors containing the neighbors of all vertices + * in a graph. The adjacency list is independent of the graph after + * creation, e.g. the graph can be destroyed and modified, the + * adjacency list contains the state of the graph at the time of its + * initialization. + * + * + * This function returns each neighbor list in sorted order, just + * like \ref igraph_neighbors(). However, adjacency lists \em "in general" + * are not guaranteed to be sorted, and we reserve the right to change the + * ordering of vertices in the result in the future without considering this + * a breaking change. If you need to ensure that the adjacency lists are + * sorted, you can use \ref igraph_adjlist_sort() to sort all the adjacency + * lists, or call \ref igraph_vector_int_sort() on the individual adjacency + * vectors after the initialization. + * + * + * As of igraph 0.10, there is a small performance cost to setting \p loops + * to a different value than \c IGRAPH_LOOPS_TWICE or setting \p multiple to a + * different value from \c IGRAPH_MULTIPLE. + * + * \param graph The input graph. + * \param al Pointer to an uninitialized igraph_adjlist_t object. + * \param mode Constant specifying whether to include only outgoing + * (\c IGRAPH_OUT), only incoming (\c IGRAPH_IN), + * or both (\c IGRAPH_ALL) types of neighbors + * in the adjacency list. It is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges. \c IGRAPH_NO_LOOPS + * removes loop edges from the adjacency list. \c IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the adjacency list of the + * corresponding vertex. \c IGRAPH_LOOPS_TWICE makes loop edges + * appear \em twice in the adjacency list of the corresponding vertex, + * but only if the graph is undirected or \p mode is set to + * \c IGRAPH_ALL. + * \param multiple Specifies how to treat multiple (parallel) edges. + * \c IGRAPH_NO_MULTIPLE collapses parallel edges into a single one; + * \c IGRAPH_MULTIPLE keeps the multiplicities of parallel edges + * so the same vertex will appear as many times in the adjacency list of + * another vertex as the number of parallel edges going between the two + * vertices. + * \return Error code. + * + * \sa \ref igraph_neighbors() for getting the neighbor lists of individual + * vertices. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_adjlist_init(const igraph_t *graph, igraph_adjlist_t *al, + igraph_neimode_t mode, igraph_loops_t loops, + igraph_bool_t multiple) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degrees; + int iter = 0; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create adjacency list view.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + /* igraph_degree() is fast when loops=true */ + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), mode, IGRAPH_LOOPS)); + + al->length = no_of_nodes; + al->adjs = IGRAPH_CALLOC(al->length, igraph_vector_int_t); + IGRAPH_CHECK_OOM(al->adjs, "Insufficient memory for creating adjacency list view."); + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + + /* if we already know there are no multi-edges, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI)) { + multiple = IGRAPH_MULTIPLE; + } + + /* if we already know there are no loops, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + if (mode == IGRAPH_ALL) { + loops = IGRAPH_LOOPS_TWICE; + } else { + loops = IGRAPH_LOOPS_ONCE; + } + } + + igraph_bool_t has_loops = false; + igraph_bool_t has_multiple = false; + + /* In theory, we could just run igraph_neighbors() in a loop with 'loops' + * and 'multiple' set exactly how the caller wants it. However, we take the + * opportunity to also _cache_ whether the graph has multiple or loop edges + * if we are looping over all vertices anyway, and that requires us to query + * the neighbors in full */ + + for (igraph_int_t i = 0; i < al->length; i++) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1000); + + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], VECTOR(degrees)[i])); + IGRAPH_CHECK(igraph_neighbors(graph, &al->adjs[i], i, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + + /* Attention: This function will only set values for has_loops and has_multiple + * if it finds loops/multi-edges. Otherwise they are left at their original value. */ + IGRAPH_CHECK(igraph_i_simplify_sorted_int_adjacency_vector_in_place( + &al->adjs[i], i, mode, loops, multiple, &has_loops, &has_multiple + )); + } + if (has_loops) { + /* If we have found at least one loop above, set the cache to true */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, true); + } else if (loops == IGRAPH_NO_LOOPS) { + /* If we explicitly _checked_ for loops (to remove them) and haven't + * found one, set the cache to false. This is the only case when a + * definite "no" from has_loops really means that there are no loops at + * all */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false); + } + if (has_multiple) { + /* If we have found at least one multiedge above, set the cache to true */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, true); + } else if (multiple == IGRAPH_NO_MULTIPLE) { + /* If we explicitly _checked_ for multi-edges (to remove them) and + * haven't found one, set the cache to false. This is the only case + * when a definite "no" from has_multiple really means that there are + * no multi-edges at all all */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, false); + } + + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(2); /* + igraph_adjlist_destroy */ + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adjlist_init_empty + * \brief Initializes an empty adjacency list. + * + * Creates a list of vectors, one for each vertex. This is useful when you + * are \em constructing a graph using an adjacency list representation as + * it does not require your graph to exist yet. + * + * \param al Pointer to an uninitialized igraph_adjlist_t object. + * \param no_of_nodes The number of vertices + * \return Error code. + * + * Time complexity: O(n), linear in the number of vertices. + */ +igraph_error_t igraph_adjlist_init_empty(igraph_adjlist_t *al, igraph_int_t no_of_nodes) { + + al->length = no_of_nodes; + al->adjs = IGRAPH_CALLOC(al->length, igraph_vector_int_t); + IGRAPH_CHECK_OOM(al->adjs, "Insufficient memory for creating adjlist."); + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + + for (igraph_int_t i = 0; i < al->length; i++) { + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adjlist_init_complementer + * \brief Adjacency lists for the complementer graph. + * + * This function creates adjacency lists for the complementer + * of the input graph. In the complementer graph all edges are present + * which are not present in the original graph. Multiple edges in the + * input graph are ignored. + * + * + * This function returns each neighbor list in sorted order. + * + * \param graph The input graph. + * \param al Pointer to a not yet initialized adjacency list. + * \param mode Constant specifying whether outgoing + * (\c IGRAPH_OUT), incoming (\c IGRAPH_IN), + * or both (\c IGRAPH_ALL) types of neighbors (in the + * complementer graph) to include in the adjacency list. It is + * ignored for undirected networks. + * \param loops Specifies how to treat loop edges. \c IGRAPH_NO_LOOPS will not + * include loops edges in the returned adjacency list. \c IGRAPH_LOOPS_ONCE + * will include vertex \p i in the adjacency list of vetex \p i \em once if + * the original graph did not have a loop edge incident on vertex \p i, + * while \c IGRAPH_LOOPS_TWICE will include vertex \p i \em twice \em if + * \p mode is set to \c IGRAPH_ALL (otherwise it is treated the same way as + * \c IGRAPH_LOOPS_ONCE ). + * \return Error code. + * + * \sa \ref igraph_adjlist_init(), \ref igraph_complementer() + * + * Time complexity: O(|V|^2+|E|), quadratic in the number of vertices. + */ +igraph_error_t igraph_adjlist_init_complementer(const igraph_t *graph, + igraph_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops) { + + igraph_bitset_t seen; + igraph_vector_int_t neis; + int iter = 0; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid neighbor mode specified for complementer adjlist view.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (loops == IGRAPH_LOOPS_TWICE && mode != IGRAPH_ALL) { + loops = IGRAPH_LOOPS_ONCE; + } + + al->length = igraph_vcount(graph); + al->adjs = IGRAPH_CALLOC(al->length, igraph_vector_int_t); + IGRAPH_CHECK_OOM(al->adjs, "Insufficient memory for creating complementer adjlist view."); + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + + IGRAPH_BITSET_INIT_FINALLY(&seen, al->length); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + for (igraph_int_t i = 0; i < al->length; i++) { + /* For each vertex, we mark neighbors within the 'seen' bitset. + * Then we iterate over 'seen' and record non-marked vertices in + * the adjacency list. */ + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1000); + + /* Reset neighbor counter and 'seen' vector. */ + igraph_bitset_null(&seen); + igraph_int_t n = al->length; + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, i, mode, loops, IGRAPH_NO_MULTIPLE)); + + if (loops == IGRAPH_NO_LOOPS) { + /* If we want no loops, we pretend that we have always seen one */ + IGRAPH_BIT_SET(seen, i); + n--; + } + + igraph_int_t neis_size = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < neis_size; j++) { + if (! IGRAPH_BIT_TEST(seen, VECTOR(neis)[j]) ) { + n--; + IGRAPH_BIT_SET(seen, VECTOR(neis)[j]); + } + } + + if (loops == IGRAPH_LOOPS_TWICE) { + /* If we want loops twice and the bit corresponding to the loop + * edge is _not_ set, we need one extra slot in the allocated + * vector */ + if (!IGRAPH_BIT_TEST(seen, i)) { + n++; + } + } + + /* Produce "non-neighbor" list in sorted order. */ + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], n)); + for (igraph_int_t j = 0, k = 0; k < n; j++) { + if (!IGRAPH_BIT_TEST(seen, j)) { + VECTOR(al->adjs[i])[k++] = j; + if (loops == IGRAPH_LOOPS_TWICE && i == j) { + VECTOR(al->adjs[i])[k++] = j; + } + } + } + } + + igraph_bitset_destroy(&seen); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); /* +1 for the adjlist itself */ + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_adjlist_init_from_inclist + * \brief Constructs an adjacency list of vertices from an incidence list. + * + * In some algorithms it is useful to have an adjacency list \em and an incidence + * list representation of the same graph, and in many cases it is the most useful + * if they are consistent with each other, i.e. if can be guaranteed that the + * vertex ID in the i-th entry of the adjacency list of vertex v is the + * \em other endpoint of the edge in the i-th entry of the incidence list of the + * same vertex. This function creates such an adjacency list from the corresponding + * incidence list by looking up the endpoints of each edge in the incidence + * list and constructing the corresponding adjacenecy vectors. + * + * + * The adjacency list is independent of the graph or the incidence list after + * creation; in other words, modifications that are made to the graph or the + * incidence list are not reflected in the adjacency list. + * + * \param graph The input graph. + * \param al Pointer to an uninitialized igraph_adjlist_t object. + * \param il Pointer to an \em initialized igraph_inclist_t object + * that will be converted into an adjacency list. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_adjlist_init_from_inclist( + const igraph_t *graph, igraph_adjlist_t *al, const igraph_inclist_t *il) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, j, num_neis; + + igraph_vector_int_t *neis; + igraph_vector_int_t *incs; + + if (igraph_inclist_size(il) != no_of_nodes) { + IGRAPH_ERRORF( + "Incidence list has %" IGRAPH_PRId " entries but the graph has %" IGRAPH_PRId " vertices.", + IGRAPH_EINVAL, + igraph_inclist_size(il), + no_of_nodes + ); + } + + IGRAPH_CHECK(igraph_adjlist_init_empty(al, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + neis = igraph_adjlist_get(al, i); + incs = igraph_inclist_get(il, i); + + num_neis = igraph_vector_int_size(incs); + IGRAPH_CHECK(igraph_vector_int_resize(neis, num_neis)); + + for (j = 0; j < num_neis; j++) { + VECTOR(*neis)[j] = IGRAPH_OTHER(graph, VECTOR(*incs)[j], i); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adjlist_destroy + * \brief Deallocates an adjacency list. + * + * Free all memory allocated for an adjacency list. + * \param al The adjacency list to destroy. + * + * Time complexity: O(n), where n is the size of the adjacency list. + */ +void igraph_adjlist_destroy(igraph_adjlist_t *al) { + for (igraph_int_t i = 0; i < al->length; i++) { + /* This works if some igraph_vector_int_t's contain NULL, + because igraph_vector_int_destroy can handle this. */ + igraph_vector_int_destroy(&al->adjs[i]); + } + IGRAPH_FREE(al->adjs); +} + +/** + * \function igraph_adjlist_clear + * \brief Removes all edges from an adjacency list. + * + * The size of the adjacency list stays unchanged, but all adjacent + * vertices will be removed. + * + * \param al The adjacency list. + * + * Time complexity: O(n), where n is the size of the adjacency list. + */ +void igraph_adjlist_clear(igraph_adjlist_t *al) { + for (igraph_int_t i = 0; i < al->length; i++) { + igraph_vector_int_clear(&al->adjs[i]); + } +} + +/** + * \function igraph_adjlist_size + * \brief Returns the number of vertices in an adjacency list. + * + * \param al The adjacency list. + * \return The number of vertices in the adjacency list. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_adjlist_size(const igraph_adjlist_t *al) { + return al->length; +} + +/** + * \function igraph_adjlist_sort + * \brief Sorts each vector in an adjacency list. + * + * Sorts every vector of the adjacency list. Note that + * \ref igraph_adjlist_init() already produces sorted neighbor lists. + * This function is useful when the adjacency list is produced in + * a different manner, or is modified in a way that does not preserve + * the sorted order. + * + * \param al The adjacency list. + * + * Time complexity: O(m log m), m is the total number of neighbors stored + * in the adjacency list. + */ +void igraph_adjlist_sort(igraph_adjlist_t *al) { + igraph_int_t i; + for (i = 0; i < al->length; i++) { + igraph_vector_int_sort(&al->adjs[i]); + } +} + +/** + * \function igraph_adjlist_simplify + * \brief Simplifies an adjacency list. + * + * Simplifies an adjacency list, i.e. removes loop and multiple edges. + * + * + * When the adjacency list is created with \ref igraph_adjlist_init(), + * use the \c loops and \c multiple parameters of that function instead. + * + * \param al The adjacency list. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of edges and + * vertices. + */ +igraph_error_t igraph_adjlist_simplify(igraph_adjlist_t *al) { + igraph_int_t i; + igraph_int_t n = al->length; + igraph_vector_int_t mark; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&mark, n); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_int_t j, l = igraph_vector_int_size(v); + VECTOR(mark)[i] = i + 1; + for (j = 0; j < l; /* nothing */) { + igraph_int_t e = VECTOR(*v)[j]; + if (VECTOR(mark)[e] != i + 1) { + VECTOR(mark)[e] = i + 1; + j++; + } else { + VECTOR(*v)[j] = igraph_vector_int_tail(v); + igraph_vector_int_pop_back(v); + l--; + } + } + } + + igraph_vector_int_destroy(&mark); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +#ifndef USING_R +igraph_error_t igraph_adjlist_print(const igraph_adjlist_t *al) { + igraph_int_t i; + igraph_int_t n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_vector_int_print(v); + } + return IGRAPH_SUCCESS; +} +#endif + +igraph_error_t igraph_adjlist_fprint(const igraph_adjlist_t *al, FILE *outfile) { + igraph_int_t i; + igraph_int_t n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_vector_int_fprint(v, outfile); + } + return IGRAPH_SUCCESS; +} + +#define ADJLIST_CANON_EDGE(from, to, directed) \ + do { \ + igraph_int_t temp; \ + if ((!directed) && from < to) { \ + temp = to; \ + to = from; \ + from = temp; \ + } \ + } while (0); + +/** + * \function igraph_adjlist_has_edge + * \brief Checks if an adjacency list contains an edge. + * + * \param al The adjacency list. It must be sorted. + * \param from The source vertex of the edge. + * \param to The target vertex of the edge. + * \param directed Whether to treat the graph as directed. + * \return + */ + +igraph_bool_t igraph_adjlist_has_edge( + igraph_adjlist_t* al, + igraph_int_t from, igraph_int_t to, + igraph_bool_t directed) { + + const igraph_vector_int_t *fromvec; + ADJLIST_CANON_EDGE(from, to, directed); + fromvec = igraph_adjlist_get(al, from); + return igraph_vector_int_contains_sorted(fromvec, to); +} + +igraph_error_t igraph_adjlist_replace_edge( + igraph_adjlist_t* al, + igraph_int_t from, igraph_int_t oldto, igraph_int_t newto, + igraph_bool_t directed) { + + igraph_vector_int_t *oldfromvec, *newfromvec; + igraph_bool_t found_old, found_new; + igraph_int_t oldpos, newpos; + igraph_int_t oldfrom = from, newfrom = from; + + ADJLIST_CANON_EDGE(oldfrom, oldto, directed); + ADJLIST_CANON_EDGE(newfrom, newto, directed); + + oldfromvec = igraph_adjlist_get(al, oldfrom); + newfromvec = igraph_adjlist_get(al, newfrom); + + /* oldfrom -> oldto should exist; newfrom -> newto should not. */ + found_old = igraph_vector_int_binsearch(oldfromvec, oldto, &oldpos); + if (! found_old) { + IGRAPH_ERROR("Edge to replace does not exist.", IGRAPH_EINVAL); + } + found_new = igraph_vector_int_binsearch(newfromvec, newto, &newpos); + if (found_new) { + IGRAPH_ERROR("New edge already exists.", IGRAPH_EINVAL); + } + + if (oldfromvec != newfromvec) { + /* grow the new vector first and then remove the item from the old one + * to ensure that we don't end up in a situation where the removal + * succeeds but the addition does not */ + IGRAPH_CHECK(igraph_vector_int_insert(newfromvec, newpos, newto)); + igraph_vector_int_remove(oldfromvec, oldpos); + } else { + /* moving item within the same vector; here we can safely remove first + * and insert afterwards because there is no need to re-allocate memory */ + igraph_vector_int_remove(oldfromvec, oldpos); + if (oldpos < newpos) { + --newpos; + } + IGRAPH_CHECK(igraph_vector_int_insert(newfromvec, newpos, newto)); + } + + return IGRAPH_SUCCESS; + +} + +#ifndef USING_R +igraph_error_t igraph_inclist_print(const igraph_inclist_t *al) { + igraph_int_t i; + igraph_int_t n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->incs[i]; + igraph_vector_int_print(v); + } + return IGRAPH_SUCCESS; +} +#endif + +igraph_error_t igraph_inclist_fprint(const igraph_inclist_t *al, FILE *outfile) { + igraph_int_t i; + igraph_int_t n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->incs[i]; + igraph_vector_int_fprint(v, outfile); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_inclist_init + * \brief Initializes an incidence list. + * + * Creates a list of vectors containing the incident edges for all + * vertices. The incidence list is independent of the graph after + * creation, subsequent changes of the graph object do not update the + * incidence list, and changes to the incidence list do not update the + * graph. + * + * + * When \p mode is \c IGRAPH_IN or \c IGRAPH_OUT, each edge ID will appear + * in the incidence list \em once. When \p mode is \c IGRAPH_ALL, each edge ID + * will appear in the incidence list \em twice, once for the source vertex + * and once for the target edge. It also means that the edge IDs of loop edges + * may potentially appear \em twice for the \em same vertex. Use the \p loops + * argument to control whether this will be the case (\c IGRAPH_LOOPS_TWICE ) + * or not (\c IGRAPH_LOOPS_ONCE or \c IGRAPH_NO_LOOPS). + * + * + * As of igraph 0.10, there is a small performance cost to setting \p loops + * to a different value than \c IGRAPH_LOOPS_TWICE. + * + * \param graph The input graph. + * \param il Pointer to an uninitialized incidence list. + * \param mode Constant specifying whether incoming edges + * (IGRAPH_IN), outgoing edges (IGRAPH_OUT) or + * both (IGRAPH_ALL) to include in the incidence lists + * of directed graphs. It is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges. IGRAPH_NO_LOOPS + * removes loop edges from the incidence list. IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the incidence list of the + * corresponding vertex. IGRAPH_LOOPS_TWICE makes loop edges + * appear \em twice in the incidence list of the corresponding vertex, + * but only if the graph is undirected or mode is set to + * IGRAPH_ALL. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ +igraph_error_t igraph_inclist_init(const igraph_t *graph, + igraph_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degrees; + int iter = 0; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create incidence list view.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + /* igraph_degrees() is fast when loops=true */ + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), mode, IGRAPH_LOOPS)); + + il->length = no_of_nodes; + il->incs = IGRAPH_CALLOC(il->length, igraph_vector_int_t); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create incidence list view.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + IGRAPH_FINALLY(igraph_inclist_destroy, il); + for (igraph_int_t i = 0; i < il->length; i++) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1000); + + IGRAPH_CHECK(igraph_vector_int_init(&il->incs[i], VECTOR(degrees)[i])); + IGRAPH_CHECK(igraph_incident(graph, &il->incs[i], i, mode, loops)); + } + + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(2); /* + igraph_inclist_destroy */ + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_inclist_init_empty + * \brief Initializes an incidence list corresponding to an empty graph. + * + * This function essentially creates a list of empty vectors that may + * be treated as an incidence list for a graph with a given number of + * vertices. + * + * \param il Pointer to an uninitialized incidence list. + * \param n The number of vertices in the incidence list. + * \return Error code. + * + * Time complexity: O(n), linear in the number of vertices. + */ + +igraph_error_t igraph_inclist_init_empty(igraph_inclist_t *il, igraph_int_t n) { + igraph_int_t i; + + il->length = n; + il->incs = IGRAPH_CALLOC(il->length, igraph_vector_int_t); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create incidence list view", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + IGRAPH_FINALLY(igraph_inclist_destroy, il); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_vector_int_init(&il->incs[i], 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_inclist_destroy + * \brief Frees all memory allocated for an incidence list. + * + * \param il The incidence list to destroy. + * + * Time complexity: O(n), where n is the size of the incidence list. + */ + +void igraph_inclist_destroy(igraph_inclist_t *il) { + for (igraph_int_t i = 0; i < il->length; i++) { + /* This works if some igraph_vector_int_t's contain NULL, + because igraph_vector_int_destroy can handle this. */ + igraph_vector_int_destroy(&il->incs[i]); + } + IGRAPH_FREE(il->incs); +} + +/** + * \function igraph_inclist_clear + * \brief Removes all edges from an incidence list. + * + * The size of the incidence list stays unchanged, but all incident edges + * will be removed. + * + * \param il The incidence list. + * + * Time complexity: O(n), where n is the size of the incidence list. + */ +void igraph_inclist_clear(igraph_inclist_t *il) { + igraph_int_t i; + for (i = 0; i < il->length; i++) { + igraph_vector_int_clear(&il->incs[i]); + } +} + +/** + * \function igraph_inclist_size + * \brief Returns the number of vertices in an incidence list. + * + * \param il The incidence list. + * \return The number of vertices in the incidence list. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_inclist_size(const igraph_inclist_t *il) { + return il->length; +} + +/* See the prototype above for a description of this function. */ +static igraph_error_t igraph_i_simplify_sorted_int_adjacency_vector_in_place( + igraph_vector_int_t *v, igraph_int_t index, igraph_neimode_t mode, + igraph_loops_t loops, igraph_bool_t multiple, igraph_bool_t *has_loops, + igraph_bool_t *has_multiple + +) { + igraph_bool_t dummy1 = true, dummy2 = true; /* set dummies to avoid uninitialized read */ + if (has_loops == NULL) { + has_loops = &dummy1; + } + if (has_multiple == NULL) { + has_multiple = &dummy2; + } + igraph_int_t i, p = 0; + igraph_int_t n = igraph_vector_int_size(v); + + if ( + multiple == IGRAPH_MULTIPLE && + ( + loops == IGRAPH_LOOPS_TWICE || + (loops == IGRAPH_LOOPS_ONCE && (mode == IGRAPH_IN || mode == IGRAPH_OUT)) + ) + ) { + /* nothing to simplify */ + return IGRAPH_SUCCESS; + } + + if (loops == IGRAPH_NO_LOOPS) { + if (multiple == IGRAPH_NO_MULTIPLE) { + /* We need to get rid of loops and multiple edges completely */ + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != index && + (i == n - 1 || VECTOR(*v)[i + 1] != VECTOR(*v)[i])) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } else { + if (VECTOR(*v)[i] == index) { + *has_loops = true; + /* If we haven't found multi-edges yet, we also need to check + * if this is a multi-loop, to set 'has_multiple' correctly. */ + if (! *has_multiple) { + if (mode == IGRAPH_ALL) { + /* Undirected loops appear twice in the neighbour list, + * so we check two following items instead of one. */ + if (i < n - 2 && + VECTOR(*v)[i + 1] == VECTOR(*v)[i] && + VECTOR(*v)[i + 2] == VECTOR(*v)[i]) { + *has_multiple = true; + } + } else { + if (i != n - 1 && VECTOR(*v)[i + 1] == VECTOR(*v)[i]) { + *has_multiple = true; + } + } + } + } else if (i != n - 1 && VECTOR(*v)[i + 1] == VECTOR(*v)[i]) { + *has_multiple = true; + } + } + } + } else { + /* We need to get rid of loops but keep multiple edges */ + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != index) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } else { + *has_loops = true; + } + } + } + } else if (loops == IGRAPH_LOOPS_ONCE) { + if (multiple == IGRAPH_NO_MULTIPLE) { + /* We need to get rid of multiple edges completely (including + * multiple loop edges), but keep one edge from each loop edge */ + for (i = 0; i < n; i++) { + if (i == n - 1 || VECTOR(*v)[i + 1] != VECTOR(*v)[i]) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } else if ( + /* If this is not a loop then we have a multigraph. + Else we have at least two loops. + The v vector comes from a call to igraph_neighbors. + This will count loops twice if mode == IGRAPH_ALL. + So if mode != IGRAPH_ALL, + then we have a multigraph. + If mode == IGRAPH_ALL and we have three loops + then we also have a multigraph + */ + (VECTOR(*v)[i] != index) || + (mode != IGRAPH_ALL) || + (mode == IGRAPH_ALL && i < n - 2 && VECTOR(*v)[i + 2] == VECTOR(*v)[i]) + ){ + *has_multiple = true; + } + } + } else { + /* We need to keep one edge from each loop edge and we don't need to + * touch multiple edges. Note that we can get here only if + * mode == IGRAPH_ALL; if mode was IGRAPH_IN or IGRAPH_OUT, we would + * have bailed out earlier */ + for (i = 0; i < n; i++) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + if (VECTOR(*v)[i] == index) { + *has_loops = true; + /* this was a loop edge so if the next element is the same, we + * need to skip that */ + if (i < n-1 && VECTOR(*v)[i + 1] == index) { + i++; + } + } + p++; + } + } + } else if (loops == IGRAPH_LOOPS_TWICE && multiple == IGRAPH_NO_MULTIPLE) { + /* We need to get rid of multiple edges completely (including + * multiple loop edges), but keep both edge from each loop edge */ + for (i = 0; i < n; i++) { + if (i == n - 1 || VECTOR(*v)[i + 1] != VECTOR(*v)[i]) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } else { + *has_multiple = true; + /* Current item is the same as the next one, but if it is a + * loop edge, then the first one or two items are okay. We need + * to keep one if mode == IGRAPH_IN or mode == IGRAPH_OUT, + * otherwise we need to keep two */ + if (VECTOR(*v)[i] == index) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + if (mode == IGRAPH_ALL) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } + /* skip over all the items corresponding to the loop edges */ + while (i < n && VECTOR(*v)[i] == index) { + i++; + } + i--; /* because the for loop also increases i by 1 */ + } + } + } + } else { + /* TODO; we don't use this combination yet */ + return IGRAPH_UNIMPLEMENTED; + } + + /* always succeeds since we are never growing the vector */ + igraph_vector_int_resize(v, p); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_lazy_adjlist_init + * \brief Initializes a lazy adjacency list. + * + * Create a lazy adjacency list for vertices. This function only + * allocates some memory for storing the vectors of an adjacency list, + * but the neighbor vertices are not queried, only at the + * \ref igraph_lazy_adjlist_get() calls. Neighbor lists will be returned + * in sorted order. + * + * + * As of igraph 0.10, there is a small performance cost to setting \p loops + * to a different value than \c IGRAPH_LOOPS_TWICE or setting \p multiple to a + * different value from \c IGRAPH_MULTIPLE. + * + * \param graph The input graph. + * \param al Pointer to an uninitialized adjacency list object. + * \param mode Constant specifying whether to include only outgoing + * (\c IGRAPH_OUT), only incoming (\c IGRAPH_IN), + * or both (\c IGRAPH_ALL) types of neighbors + * in the adjacency list. It is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges. \c IGRAPH_NO_LOOPS + * removes loop edges from the adjacency list. \c IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the adjacency list of the + * corresponding vertex. \c IGRAPH_LOOPS_TWICE makes loop edges + * appear \em twice in the adjacency list of the corresponding vertex, + * but only if the graph is undirected or \p mode is set to + * \c IGRAPH_ALL. + * \param multiple Specifies how to treat multiple (parallel) edges. + * \c IGRAPH_NO_MULTIPLE collapses parallel edges into a single one; + * \c IGRAPH_MULTIPLE keeps the multiplicities of parallel edges + * so the same vertex will appear as many times in the adjacency list of + * another vertex as the number of parallel edges going between the two + * vertices. + * \return Error code. + * + * \sa \ref igraph_neighbors() for getting the neighbor lists of individual + * vertices. + * + * Time complexity: O(|V|), the number of vertices, possibly, but + * depends on the underlying memory management too. + */ + +igraph_error_t igraph_lazy_adjlist_init(const igraph_t *graph, + igraph_lazy_adjlist_t *al, + igraph_neimode_t mode, + igraph_loops_t loops, + igraph_bool_t multiple) { + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create lazy adjacency list view.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + /* if we already know there are no multi-edges, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI)) { + multiple = IGRAPH_MULTIPLE; + } + + /* if we already know there are no loops, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + if (mode == IGRAPH_ALL) { + loops = IGRAPH_LOOPS_TWICE; + } else { + loops = IGRAPH_LOOPS_ONCE; + } + } + + al->mode = mode; + al->loops = loops; + al->multiple = multiple; + al->graph = graph; + + al->length = igraph_vcount(graph); + al->adjs = IGRAPH_CALLOC(al->length, igraph_vector_int_t*); + IGRAPH_CHECK_OOM(al->adjs, "Insufficient memory for creating lazy adjacency list view."); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lazy_adjlist_destroy + * \brief Deallocate a lazt adjacency list. + * + * Free all allocated memory for a lazy adjacency list. + * \param al The adjacency list to deallocate. + * + * Time complexity: depends on the memory management. + */ + +void igraph_lazy_adjlist_destroy(igraph_lazy_adjlist_t *al) { + igraph_lazy_adjlist_clear(al); + IGRAPH_FREE(al->adjs); +} + +/** + * \function igraph_lazy_adjlist_clear + * \brief Removes all edges from a lazy adjacency list. + * + * \param al The lazy adjacency list. + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the adjacency list. + */ +void igraph_lazy_adjlist_clear(igraph_lazy_adjlist_t *al) { + igraph_int_t i, n = al->length; + for (i = 0; i < n; i++) { + if (al->adjs[i] != 0) { + igraph_vector_int_destroy(al->adjs[i]); + IGRAPH_FREE(al->adjs[i]); + } + } +} + +/** + * \function igraph_lazy_adjlist_size + * \brief Returns the number of vertices in a lazy adjacency list. + * + * \param al The lazy adjacency list. + * \return The number of vertices in the lazy adjacency list. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_lazy_adjlist_size(const igraph_lazy_adjlist_t *al) { + return al->length; +} + +igraph_vector_int_t *igraph_i_lazy_adjlist_get_real(igraph_lazy_adjlist_t *al, igraph_int_t no) { + igraph_error_t ret; + + if (al->adjs[no] == NULL) { + al->adjs[no] = IGRAPH_CALLOC(1, igraph_vector_int_t); + if (al->adjs[no] == NULL) { + return NULL; + } + + ret = igraph_vector_int_init(al->adjs[no], 0); + if (ret != IGRAPH_SUCCESS) { + IGRAPH_FREE(al->adjs[no]); + return NULL; + } + + ret = igraph_neighbors(al->graph, al->adjs[no], no, al->mode, al->loops, al->multiple); + if (ret != IGRAPH_SUCCESS) { + igraph_vector_int_destroy(al->adjs[no]); + IGRAPH_FREE(al->adjs[no]); + return NULL; + } + } + + return al->adjs[no]; +} + +/** + * \function igraph_lazy_inclist_init + * \brief Initializes a lazy incidence list of edges. + * + * Create a lazy incidence list for edges. This function only + * allocates some memory for storing the vectors of an incidence list, + * but the incident edges are not queried, only when \ref + * igraph_lazy_inclist_get() is called. + * + * + * When \p mode is \c IGRAPH_IN or \c IGRAPH_OUT, each edge ID will appear + * in the incidence list \em once. When \p mode is \c IGRAPH_ALL, each edge ID + * will appear in the incidence list \em twice, once for the source vertex + * and once for the target edge. It also means that the edge IDs of loop edges + * will appear \em twice for the \em same vertex. + * + * + * As of igraph 0.10, there is a small performance cost to setting \p loops + * to a different value than \c IGRAPH_LOOPS_TWICE. + * + * \param graph The input graph. + * \param il Pointer to an uninitialized incidence list. + * \param mode Constant, it gives whether incoming edges + * (IGRAPH_IN), outgoing edges + * (IGRAPH_OUT) or both types of edges + * (IGRAPH_ALL) are considered. It is ignored for + * undirected graphs. + * \param loops Specifies how to treat loop edges. IGRAPH_NO_LOOPS + * removes loop edges from the incidence list. IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the incidence list of the + * corresponding vertex. IGRAPH_LOOPS_TWICE makes loop edges + * appear \em twice in the incidence list of the corresponding vertex, + * but only if the graph is undirected or mode is set to + * IGRAPH_ALL. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices, possibly. But it + * also depends on the underlying memory management. + */ + +igraph_error_t igraph_lazy_inclist_init(const igraph_t *graph, + igraph_lazy_inclist_t *il, + igraph_neimode_t mode, + igraph_loops_t loops) { + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create lazy incidence list view", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + il->graph = graph; + il->loops = loops; + il->mode = mode; + + il->length = igraph_vcount(graph); + il->incs = IGRAPH_CALLOC(il->length, igraph_vector_int_t*); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create lazy incidence list view", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + return IGRAPH_SUCCESS; + +} + +/** + * \function igraph_lazy_inclist_destroy + * \brief Deallocates a lazy incidence list. + * + * Frees all allocated memory for a lazy incidence list. + * + * \param il The incidence list to deallocate. + * + * Time complexity: depends on memory management. + */ + +void igraph_lazy_inclist_destroy(igraph_lazy_inclist_t *il) { + igraph_lazy_inclist_clear(il); + IGRAPH_FREE(il->incs); +} + +/** + * \function igraph_lazy_inclist_clear + * \brief Removes all edges from a lazy incidence list. + * + * \param il The lazy incidence list. + * + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the incidence list. + */ +void igraph_lazy_inclist_clear(igraph_lazy_inclist_t *il) { + igraph_int_t i, n = il->length; + for (i = 0; i < n; i++) { + if (il->incs[i] != 0) { + igraph_vector_int_destroy(il->incs[i]); + IGRAPH_FREE(il->incs[i]); + } + } +} + +/** + * \function igraph_lazy_inclist_size + * \brief Returns the number of vertices in a lazy incidence list. + * + * \param il The lazy incidence list. + * \return The number of vertices in the lazy incidence list. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_lazy_inclist_size(const igraph_lazy_inclist_t *il) { + return il->length; +} + +igraph_vector_int_t *igraph_i_lazy_inclist_get_real(igraph_lazy_inclist_t *il, igraph_int_t no) { + igraph_error_t ret; + + if (il->incs[no] == NULL) { + il->incs[no] = IGRAPH_CALLOC(1, igraph_vector_int_t); + if (il->incs[no] == NULL) { + return NULL; + } + + ret = igraph_vector_int_init(il->incs[no], 0); + if (ret != IGRAPH_SUCCESS) { + IGRAPH_FREE(il->incs[no]); + return NULL; + } + + ret = igraph_incident(il->graph, il->incs[no], no, il->mode, il->loops); + if (ret != IGRAPH_SUCCESS) { + igraph_vector_int_destroy(il->incs[no]); + IGRAPH_FREE(il->incs[no]); + return NULL; + } + } + + return il->incs[no]; +} diff --git a/src/graph/attributes.c b/src/graph/attributes.c new file mode 100644 index 0000000..7b2a10b --- /dev/null +++ b/src/graph/attributes.c @@ -0,0 +1,1148 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_memory.h" + +#include "graph/attributes.h" +#include "internal/hacks.h" /* strdup */ + +#include +#include + +/** + * \section about_attributes + * + * Attributes are numbers, boolean values or strings associated with + * the vertices or edges of a graph, or with the graph itself. E.g. you may + * label vertices with symbolic names or attach numeric weights to the edges + * of a graph. In addition to these three basic types, a custom object + * type is supported as well. + * + * igraph attributes are designed to be flexible and extensible. + * In igraph attributes are implemented via an interface abstraction: + * any type implementing the functions in the interface can be used + * for storing vertex, edge and graph attributes. This means that + * different attribute implementations can be used together with + * igraph. This is reasonable: if igraph is used from Python attributes can be + * of any Python type, from R all R types are allowed. There is also an + * experimental attribute implementation to be used when programming + * in C, but by default it is currently turned off. + * + * First we briefly look over how attribute handlers can be + * implemented. This is not something a user does every day. It is + * rather typically the job of the high level interface writers. (But + * it is possible to write an interface without implementing + * attributes.) Then we show the experimental C attribute handler. + */ + +/** + * \section about_attribute_table + * It is possible to attach an attribute handling + * interface to \a igraph. This is simply a table of functions, of + * type \ref igraph_attribute_table_t. These functions are invoked to + * notify the attribute handling code about the structural changes in + * a graph. See the documentation of this type for details. + * + * By default there is no attribute interface attached to \a igraph. + * To attach one, call \ref igraph_set_attribute_table with your new + * table. This is normally done on program startup, and is kept untouched + * for the program's lifetime. It must be done before any graph object + * is created, as graphs created with a given attribute handler + * cannot be manipulated while a different attribute handler is + * active. + */ + +/** + * \section about_attribute_record + * + * Functions in the attribute handler interface may refer to + * \em "attribute records" or \em "attribute record lists". An attribute record + * is simply a triplet consisting of an attribute name, an attribute type and + * a vector containing the values of the attribute. Attribute record lists are + * typed containers that contain a sequence of attribute records. Attribute + * record lists own the attribute records that they contain, and similarly, + * attribute records own the vectors contained in them. Destroying an attribute + * record destroys the vector of values inside it, and destroying an attribute + * record list destroys all attribute records in the list. + */ + +/** + * \section about_attribute_combination + * + * Several graph operations may collapse multiple vertices or edges into + * a single one. Attribute combination lists are used to indicate to the attribute + * handler how to combine the attributes of the original vertices or edges and + * how to derive the final attribute value that is to be assigned to the collapsed + * vertex or edge. For example, \ref igraph_simplify() removes loops and combines + * multiple edges into a single one; in case of a graph with an edge attribute + * named \c weight the attribute combination list can tell the attribute handler + * whether the weight of a collapsed edge should be the sum, the mean or some other + * function of the weights of the original edges that were collapsed into one. + * + * One attribute combination list may contain several attribute combination + * records, one for each vertex or edge attribute that is to be handled during the + * operation. + */ + +static void igraph_i_attribute_record_set_type( + igraph_attribute_record_t *attr, igraph_attribute_type_t type, void *ptr +); +static void igraph_i_attribute_record_destroy_values(igraph_attribute_record_t *attr); + +/** + * \function igraph_attribute_record_init + * \brief Initializes an attribute record with a given name and type. + * + * \param attr the attribute record to initialize + * \param name name of the attribute + * \param type type of the attribute + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_attribute_record_init( + igraph_attribute_record_t *attr, const char *name, igraph_attribute_type_t type +) { + attr->name = NULL; + attr->type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + attr->value.as_raw = NULL; + attr->default_value.string = NULL; + + IGRAPH_CHECK(igraph_attribute_record_set_name(attr, name)); + IGRAPH_FINALLY(igraph_free, attr->name); + IGRAPH_CHECK(igraph_attribute_record_set_type(attr, type)); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_record_init_copy + * \brief Initializes an attribute record by copying another record. + * + * + * Copies made by this function are deep copies: a full copy of the value + * vector contained in the record is placed in the new record so they become + * independent of each other. + * + * \param to the attribute record to initialize + * \param from the attribute record to copy data from + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually O(n), where n is the + * size of the value vector in the attribute record. + */ +igraph_error_t igraph_attribute_record_init_copy( + igraph_attribute_record_t *to, const igraph_attribute_record_t *from +) { + IGRAPH_CHECK(igraph_attribute_record_init(to, from->name, from->type)); + + switch (from->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + IGRAPH_CHECK(igraph_vector_update(to->value.as_vector, from->value.as_vector)); + break; + + case IGRAPH_ATTRIBUTE_STRING: + IGRAPH_CHECK(igraph_strvector_update(to->value.as_strvector, from->value.as_strvector)); + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + IGRAPH_CHECK(igraph_vector_bool_update(to->value.as_vector_bool, from->value.as_vector_bool)); + break; + + case IGRAPH_ATTRIBUTE_UNSPECIFIED: + break; + + default: + break; + } + + return IGRAPH_SUCCESS; +} + +static void igraph_i_attribute_record_destroy_values(igraph_attribute_record_t *attr) { + IGRAPH_ASSERT(attr != NULL); + + if (attr->value.as_raw) { + switch (attr->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + igraph_vector_destroy(attr->value.as_vector); + break; + + case IGRAPH_ATTRIBUTE_STRING: + igraph_strvector_destroy(attr->value.as_strvector); + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + igraph_vector_bool_destroy(attr->value.as_vector_bool); + break; + + default: + break; + } + + igraph_free(attr->value.as_raw); + attr->value.as_raw = NULL; + } + + switch (attr->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + attr->default_value.numeric = 0; + break; + + case IGRAPH_ATTRIBUTE_STRING: + if (attr->default_value.string) { + igraph_free(attr->default_value.string); + attr->default_value.string = NULL; + } + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + attr->default_value.boolean = 0; + break; + + default: + break; + } + + attr->type = IGRAPH_ATTRIBUTE_UNSPECIFIED; +} + +/** + * \function igraph_attribute_record_destroy + * \brief Destroys an attribute record. + * + * \param attr the previously initialized attribute record to destroy. + * + * Time complexity: operating system dependent. + */ +void igraph_attribute_record_destroy(igraph_attribute_record_t *attr) { + igraph_i_attribute_record_destroy_values(attr); + + if (attr->name) { + igraph_free(attr->name); + attr->name = NULL; + } +} + +/** + * \function igraph_attribute_record_check_type + * \brief Checks whether the type of the attribute record is equal to an expected type. + * + * \param attr the attribute record to test + * \param type the expected type of the attribute record + * \return Error code: + * \c IGRAPH_EINVAL if the type of the attribute record is not equal to + * the expected type + * + * Time complexity: O(1). + */ +igraph_error_t igraph_attribute_record_check_type( + const igraph_attribute_record_t *attr, igraph_attribute_type_t type +) { + if (type != attr->type) { + switch (type) { + case IGRAPH_ATTRIBUTE_STRING: + IGRAPH_ERROR("String attribute expected.", IGRAPH_EINVAL); + break; + case IGRAPH_ATTRIBUTE_NUMERIC: + IGRAPH_ERROR("Numeric attribute expected.", IGRAPH_EINVAL); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + IGRAPH_ERROR("Boolean attribute expected.", IGRAPH_EINVAL); + break; + case IGRAPH_ATTRIBUTE_OBJECT: + IGRAPH_ERROR("Object attribute expected.", IGRAPH_EINVAL); + break; + default: + IGRAPH_ERROR("Attribute with unknown type expected.", IGRAPH_EINVAL); + break; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_record_size + * \brief Returns the size of the value vector in an attribute record. + * + * \param attr the attribute record to query + * \return the number of elements in the value vector of the attribute record + */ +igraph_int_t igraph_attribute_record_size(const igraph_attribute_record_t *attr) { + IGRAPH_ASSERT(attr != NULL); + + switch (attr->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + return igraph_vector_size(attr->value.as_vector); + + case IGRAPH_ATTRIBUTE_STRING: + return igraph_strvector_size(attr->value.as_strvector); + + case IGRAPH_ATTRIBUTE_BOOLEAN: + return igraph_vector_bool_size(attr->value.as_vector_bool); + + case IGRAPH_ATTRIBUTE_UNSPECIFIED: + return 0; + + default: + IGRAPH_ERRORF("Unsupported attribute type: %d", IGRAPH_EINVAL, (int) attr->type); + } +} + +/** + * \function igraph_attribute_record_resize + * \brief Resizes the value vector in an attribute record. + * + * When the value vector is shorter than the desired length, it + * will be expanded with \c IGRAPH_NAN for numeric vectors, \c false for Boolean + * vectors and empty strings for string vectors. + * + * \param attr the attribute record to update + * \param new_size the new size of the value vector + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * \c IGRAPH_EINVAL if the type of the attribute record is not specified yet. + */ +igraph_error_t igraph_attribute_record_resize( + igraph_attribute_record_t *attr, igraph_int_t new_size +) { + igraph_int_t i; + igraph_vector_t *vec; + igraph_vector_bool_t *log; + igraph_strvector_t *str; + + IGRAPH_ASSERT(attr != NULL); + + switch (attr->type) { + + case IGRAPH_ATTRIBUTE_NUMERIC: + vec = attr->value.as_vector; + i = igraph_vector_size(vec); + IGRAPH_CHECK(igraph_vector_resize(vec, new_size)); + while (i < new_size) { + VECTOR(*vec)[i++] = attr->default_value.numeric; + } + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + log = attr->value.as_vector_bool; + i = igraph_vector_bool_size(log); + IGRAPH_CHECK(igraph_vector_bool_resize(log, new_size)); + while (i < new_size) { + VECTOR(*log)[i++] = attr->default_value.boolean; + } + break; + + case IGRAPH_ATTRIBUTE_STRING: + str = attr->value.as_strvector; + if (attr->default_value.string == 0 || (*attr->default_value.string == 0)) { + IGRAPH_CHECK(igraph_strvector_resize(str, new_size)); + } else { + i = igraph_strvector_size(str); + IGRAPH_CHECK(igraph_strvector_resize(str, new_size)); + while (i < new_size) { + IGRAPH_CHECK(igraph_strvector_set(str, i++, attr->default_value.string)); + } + } + break; + + case IGRAPH_ATTRIBUTE_UNSPECIFIED: + IGRAPH_ERROR("Attribute record has no type yet.", IGRAPH_EINVAL); + break; + + default: + IGRAPH_ERRORF("Unsupported attribute type: %d", IGRAPH_EINVAL, (int) attr->type); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_record_set_default_numeric + * \brief Sets the default value of the attribute to the given number. + * + * + * This function must be called for numeric attribute records only. When not + * specified, the default value of numeric attributes is NaN. + * + * \param attr the attribute record to update + * \param value the new default value + * \return Error code: + * \c IGRAPH_EINVAL if the attribute record has a non-numeric type + */ +igraph_error_t igraph_attribute_record_set_default_numeric( + igraph_attribute_record_t *attr, igraph_real_t value +) { + if (attr->type != IGRAPH_ATTRIBUTE_NUMERIC) { + return IGRAPH_EINVAL; + } + + attr->default_value.numeric = value; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_record_set_default_boolean + * \brief Sets the default value of the attribute to the given logical value. + * + * + * This function must be called for Boolean attribute records only. When not + * specified, the default value of Boolean attributes is \c false. + * + * \param attr the attribute record to update + * \param value the new default value + * \return Error code: + * \c IGRAPH_EINVAL if the attribute record is not of Boolean type + */ +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_boolean( + igraph_attribute_record_t *attr, igraph_bool_t value +) { + if (attr->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + return IGRAPH_EINVAL; + } + + attr->default_value.boolean = value; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_record_set_default_string + * \brief Sets the default value of the attribute to the given string. + * + * + * This function must be called for string attribute records only. When not + * specified, the default value of string attributes is an empty string. + * + * \param attr the attribute record to update + * \param value the new default value. \c NULL means an empty string. + * + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory + * \c IGRAPH_EINVAL if the attribute record is not of string type + */ +IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_string( + igraph_attribute_record_t *attr, const char* value +) { + char* copy; + + if (attr->type != IGRAPH_ATTRIBUTE_STRING) { + return IGRAPH_EINVAL; + } + + if (value && (*value != 0)) { + copy = strdup(value); + IGRAPH_CHECK_OOM(copy, "Insufficient memory to duplicate default value."); + } else { + copy = NULL; + } + + if (attr->default_value.string) { + igraph_free(attr->default_value.string); + } + attr->default_value.string = copy; + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_attribute_record_set_name + * \brief Sets the attribute name in an attribute record. + * + * \param attr the attribute record to update + * \param name the new name + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + */ +igraph_error_t igraph_attribute_record_set_name( + igraph_attribute_record_t *attr, const char *name +) { + char *new_name; + + IGRAPH_ASSERT(attr != NULL); + + if (name != NULL) { + new_name = strdup(name); + IGRAPH_CHECK_OOM(new_name, "Insufficient memory for allocating attribute name."); + } else { + new_name = NULL; + } + + if (attr->name) { + igraph_free(attr->name); + } + + attr->name = new_name; + + return IGRAPH_SUCCESS; +} + +static void igraph_i_attribute_record_set_type( + igraph_attribute_record_t *attr, igraph_attribute_type_t type, void *ptr +) { + bool type_changed = attr->type != type; + + if (type_changed || attr->value.as_raw != ptr) { + igraph_i_attribute_record_destroy_values(attr); + attr->type = type; + attr->value.as_raw = ptr; + } + + if (type_changed && type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ASSERT( + igraph_attribute_record_set_default_numeric(attr, IGRAPH_NAN) == IGRAPH_SUCCESS + ); + } +} + +/** + * \function igraph_attribute_record_set_type + * \brief Sets the type of an attribute record. + * + * + * When the new type being set is different from the old type, any values already + * stored in the attribute record will be destroyed and a new, empty attribute + * value vector will be allocated. When the new type is the same as the old + * type, this function is a no-op. + * + * \param attr the attribute record to update + * \param type the new type + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + */ +igraph_error_t igraph_attribute_record_set_type( + igraph_attribute_record_t *attr, igraph_attribute_type_t type +) { + void *ptr; + + IGRAPH_ASSERT(attr != NULL); + + if (attr->type != type) { + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: { + igraph_vector_t *vec = IGRAPH_CALLOC(1, igraph_vector_t); + IGRAPH_CHECK_OOM(vec, "Insufficient memory for attribute record."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_VECTOR_INIT_FINALLY(vec, 0); + ptr = vec; + } + break; + + case IGRAPH_ATTRIBUTE_STRING: { + igraph_strvector_t *strvec = IGRAPH_CALLOC(1, igraph_strvector_t); + IGRAPH_CHECK_OOM(strvec, "Insufficient memory for attribute record."); + IGRAPH_FINALLY(igraph_free, strvec); + IGRAPH_STRVECTOR_INIT_FINALLY(strvec, 0); + ptr = strvec; + } + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: { + igraph_vector_bool_t *boolvec = IGRAPH_CALLOC(1, igraph_vector_bool_t); + IGRAPH_CHECK_OOM(boolvec, "Insufficient memory for attribute record."); + IGRAPH_FINALLY(igraph_free, boolvec); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(boolvec, 0); + ptr = boolvec; + } + break; + + default: + IGRAPH_FATALF("Unsupported attribute type: %d.", (int) type); + } + + igraph_i_attribute_record_set_type(attr, type, ptr); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} + +#define ATTRIBUTE_RECORD_LIST +#define BASE_ATTRIBUTE_RECORD +#define CUSTOM_INIT_DESTROY +#include "igraph_pmt.h" +#include "core/typed_list.pmt" +#include "igraph_pmt_off.h" +#undef CUSTOM_INIT_DESTROY +#undef BASE_ATTRIBUTE_RECORD +#undef ATTRIBUTE_RECORD_LIST + +static igraph_error_t igraph_i_attribute_record_list_init_item( + const igraph_attribute_record_list_t* list, igraph_attribute_record_t* item +) { + IGRAPH_UNUSED(list); + return igraph_attribute_record_init(item, NULL, IGRAPH_ATTRIBUTE_UNSPECIFIED); +} + +static igraph_error_t igraph_i_attribute_record_list_copy_item( + igraph_attribute_record_t* dest, const igraph_attribute_record_t* source +) { + return igraph_attribute_record_init_copy(dest, source); +} + +static void igraph_i_attribute_record_list_destroy_item(igraph_attribute_record_t* item) { + igraph_attribute_record_destroy(item); +} + +/* Should you ever want to have a thread-local attribute handler table, prepend + * IGRAPH_THREAD_LOCAL to the following declaration and #include "config.h". */ +igraph_attribute_table_t *igraph_i_attribute_table = NULL; + +igraph_error_t igraph_i_attribute_init( + igraph_t *graph, const igraph_attribute_record_list_t *attr +) { + graph->attr = NULL; + if (igraph_i_attribute_table) { + IGRAPH_CHECK(igraph_i_attribute_table->init(graph, attr)); + if (graph->attr == NULL) { + IGRAPH_ERROR("Attribute handler did not initialize attr pointer", IGRAPH_FAILURE); + } + } + return IGRAPH_SUCCESS; +} + +void igraph_i_attribute_destroy(igraph_t *graph) { + if (graph->attr && igraph_i_attribute_table) { + igraph_i_attribute_table->destroy(graph); + } + graph->attr = NULL; +} + +igraph_error_t igraph_i_attribute_copy(igraph_t *to, const igraph_t *from, igraph_bool_t ga, + igraph_bool_t va, igraph_bool_t ea) { + igraph_i_attribute_destroy(to); + if (from->attr && igraph_i_attribute_table) { + IGRAPH_CHECK(igraph_i_attribute_table->copy(to, from, ga, va, ea)); + if (to->attr == NULL) { + IGRAPH_ERROR("Attribute handler did not initialize attr pointer", IGRAPH_FAILURE); + } + } + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_attribute_add_vertices( + igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr +) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->add_vertices(graph, nv, attr); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_permute_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx) { + /* graph and newgraph may be the same, in which case we need to support + * in-place operations. If they are _not_ the same, it is assumed that the + * new graph has no vertex attributes yet */ + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->permute_vertices(graph, newgraph, idx); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_combine_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb) { + /* It is assumed that the two graphs are not the same and that the new + * graph has no vertex attributes yet. We cannot assert the latter but we + * can assert the former */ + IGRAPH_ASSERT(graph != newgraph); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->combine_vertices(graph, newgraph, + merges, + comb); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr +) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->add_edges(graph, edges, attr); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_permute_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx) { + /* graph and newgraph may be the same, in which case we need to support + * in-place operations. If they are _not_ the same, it is assumed that the + * new graph has no edge attributes yet */ + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->permute_edges(graph, newgraph, idx); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_combine_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb) { + /* It is assumed that the two graphs are not the same and that the new + * graph has no eedge attributes yet. We cannot assert the latter but we + * can assert the former */ + IGRAPH_ASSERT(graph != newgraph); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->combine_edges(graph, newgraph, + merges, + comb); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_info(const igraph_t *graph, + igraph_strvector_t *gnames, + igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, + igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, + igraph_vector_int_t *etypes) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_info(graph, gnames, gtypes, + vnames, vtypes, + enames, etypes); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_bool_t igraph_i_attribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->has_attr(graph, type, name); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_type(const igraph_t *graph, + igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, + const char *name) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_type(graph, type, elemtype, name); + } else { + return IGRAPH_SUCCESS; + } + +} + +igraph_error_t igraph_i_attribute_get_numeric_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_t *value) { + igraph_vector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_graph_attr(graph, name, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_numeric_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_t *value) { + igraph_vector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_vertex_attr(graph, name, vs, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_numeric_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_t *value) { + igraph_vector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_edge_attr(graph, name, es, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_string_graph_attr(const igraph_t *graph, + const char *name, + igraph_strvector_t *value) { + igraph_strvector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_graph_attr(graph, name, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_string_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_strvector_t *value) { + igraph_strvector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_vertex_attr(graph, name, vs, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_string_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_strvector_t *value) { + igraph_strvector_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_edge_attr(graph, name, es, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_bool_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_bool_t *value) { + igraph_vector_bool_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_graph_attr(graph, name, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_bool_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value) { + igraph_vector_bool_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_vertex_attr(graph, name, vs, value); + } else { + return IGRAPH_SUCCESS; + } +} + +igraph_error_t igraph_i_attribute_get_bool_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_bool_t *value) { + igraph_vector_bool_clear(value); + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_edge_attr(graph, name, es, value); + } else { + return IGRAPH_SUCCESS; + } +} + +/** + * \function igraph_set_attribute_table + * \brief Attach an attribute table. + * + * This function attaches attribute handling code to the igraph library. + * Note that the attribute handler table is \em not thread-local even if + * igraph is compiled in thread-local mode. In the vast majority of cases, + * this is not a significant restriction. + * + * + * Attribute handlers are normally attached on program startup, and are + * left active for the program's lifetime. This is because a graph object + * created with a given attribute handler must not be manipulated while + * a different attribute handler is active. + * + * \param table Pointer to an \ref igraph_attribute_table_t object + * containing the functions for attribute manipulation. Supply \c + * NULL here if you don't want attributes. + * \return Pointer to the old attribute handling table. + * + * Time complexity: O(1). + */ + +igraph_attribute_table_t * +igraph_set_attribute_table(const igraph_attribute_table_t * table) { + igraph_attribute_table_t *old = igraph_i_attribute_table; + igraph_i_attribute_table = (igraph_attribute_table_t*) table; + return old; +} + +igraph_bool_t igraph_has_attribute_table(void) { + return igraph_i_attribute_table != NULL; +} + + +/** + * \function igraph_attribute_combination_init + * \brief Initialize attribute combination list. + * + * \param comb The uninitialized attribute combination list. + * \return Error code. + * + * Time complexity: O(1) + */ +igraph_error_t igraph_attribute_combination_init(igraph_attribute_combination_t *comb) { + IGRAPH_CHECK(igraph_vector_ptr_init(&comb->list, 0)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_combination_destroy + * \brief Destroy attribute combination list. + * + * \param comb The attribute combination list. + * + * Time complexity: O(n), where n is the number of records in the + attribute combination list. + */ +void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb) { + igraph_int_t i, n = igraph_vector_ptr_size(&comb->list); + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i]; + if (rec->name) { + IGRAPH_FREE(rec->name); + } + IGRAPH_FREE(rec); + } + igraph_vector_ptr_destroy(&comb->list); +} + +/** + * \function igraph_attribute_combination_add + * \brief Add combination record to attribute combination list. + * + * \param comb The attribute combination list. + * \param name The name of the attribute. If the name already exists + * the attribute combination record will be replaced. + * Use NULL to add a default combination record for all + * atributes not in the list. + * \param type The type of the attribute combination. See \ref + * igraph_attribute_combination_type_t for the options. + * \param func Function to be used if \p type is + * \c IGRAPH_ATTRIBUTE_COMBINE_FUNCTION. This function is called + * by the concrete attribute handler attached to igraph, and its + * calling signature depends completely on the attribute handler. + * For instance, if you are using attributes from C and you have + * attached the C attribute handler, you need to follow the + * documentation of the C attribute handler + * for more details. + * \return Error code. + * + * Time complexity: O(n), where n is the number of current attribute + * combinations. + */ +igraph_error_t igraph_attribute_combination_add(igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t type, + igraph_function_pointer_t func) { + igraph_int_t i, n = igraph_vector_ptr_size(&comb->list); + + /* Search, in case it is already there */ + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + const char *n = r->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + r->type = type; + r->func = func; + break; + } + } + + if (i == n) { + /* This is a new attribute name */ + igraph_attribute_combination_record_t *rec = + IGRAPH_CALLOC(1, igraph_attribute_combination_record_t); + if (! rec) { + IGRAPH_ERROR("Cannot create attribute combination data.", + IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, rec); + if (! name) { + rec->name = NULL; + } else { + rec->name = strdup(name); + if (! rec->name) { + IGRAPH_ERROR("Cannot create attribute combination data.", + IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + } + IGRAPH_FINALLY(igraph_free, (char *) rec->name); /* free() is safe on NULL */ + rec->type = type; + rec->func = func; + + IGRAPH_CHECK(igraph_vector_ptr_push_back(&comb->list, rec)); + IGRAPH_FINALLY_CLEAN(2); /* ownership of 'rec' transferred to 'comb->list' */ + + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_combination_remove + * \brief Remove a record from an attribute combination list. + * + * \param comb The attribute combination list. + * \param name The attribute name of the attribute combination record + * to remove. It will be ignored if the named attribute + * does not exist. It can be NULL to remove the default + * combination record. + * \return Error code. This currently always returns IGRAPH_SUCCESS. + * + * Time complexity: O(n), where n is the number of records in the attribute + combination list. + */ +igraph_error_t igraph_attribute_combination_remove(igraph_attribute_combination_t *comb, + const char *name) { + igraph_int_t i, n = igraph_vector_ptr_size(&comb->list); + + /* Search, in case it is already there */ + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + const char *n = r->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + break; + } + } + + if (i != n) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + if (r->name) { + IGRAPH_FREE(r->name); + } + IGRAPH_FREE(r); + igraph_vector_ptr_remove(&comb->list, i); + } else { + /* It is not there, we don't do anything */ + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_attribute_combination_query(const igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t *type, + igraph_function_pointer_t *func) { + igraph_int_t i, def = -1, len = igraph_vector_ptr_size(&comb->list); + + for (i = 0; i < len; i++) { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i]; + const char *n = rec->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + *type = rec->type; + *func = rec->func; + return IGRAPH_SUCCESS; + } + if (!n) { + def = i; + } + } + + if (def == -1) { + /* Did not find anything */ + *type = IGRAPH_ATTRIBUTE_COMBINE_DEFAULT; + *func = 0; + } else { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[def]; + *type = rec->type; + *func = rec->func; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_attribute_combination + * \brief Initialize attribute combination list and add records. + * + * \param comb The uninitialized attribute combination list. + * \param ... A list of 'name, type[, func]', where: + * \param name The name of the attribute. If the name already exists + * the attribute combination record will be replaced. + * Use NULL to add a default combination record for all + * atributes not in the list. + * \param type The type of the attribute combination. See \ref + * igraph_attribute_combination_type_t for the options. + * \param func Function to be used if \p type is + * \c IGRAPH_ATTRIBUTE_COMBINE_FUNCTION. + * The list is closed by setting the name to \c IGRAPH_NO_MORE_ATTRIBUTES. + * \return Error code. + * + * Time complexity: O(n^2), where n is the number attribute + * combinations records to add. + * + * \example examples/simple/igraph_attribute_combination.c + */ +igraph_error_t igraph_attribute_combination( + igraph_attribute_combination_t *comb, ...) { + + va_list ap; + + IGRAPH_CHECK(igraph_attribute_combination_init(comb)); + + va_start(ap, comb); + while (true) { + igraph_function_pointer_t func = NULL; + igraph_attribute_combination_type_t type; + const char *name; + + name = va_arg(ap, const char *); + + if (name == IGRAPH_NO_MORE_ATTRIBUTES) { + break; + } + + type = (igraph_attribute_combination_type_t) va_arg(ap, int); + if (type == IGRAPH_ATTRIBUTE_COMBINE_FUNCTION) { + func = va_arg(ap, igraph_function_pointer_t); + } + + if (strlen(name) == 0) { + name = 0; + } + + igraph_error_t ret = igraph_attribute_combination_add(comb, name, type, func); + if (ret != IGRAPH_SUCCESS) { + va_end(ap); + return ret; + } + } + + va_end(ap); + + return IGRAPH_SUCCESS; +} diff --git a/src/graph/attributes.h b/src/graph/attributes.h new file mode 100644 index 0000000..91fdf48 --- /dev/null +++ b/src/graph/attributes.h @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GRAPH_ATTRIBUTES_H +#define IGRAPH_GRAPH_ATTRIBUTES_H + +#include "igraph_attributes.h" +#include "igraph_decls.h" +#include "igraph_strvector.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_attribute_init( + igraph_t *graph, const igraph_attribute_record_list_t *attr +); +void igraph_i_attribute_destroy(igraph_t *graph); +igraph_error_t igraph_i_attribute_copy( + igraph_t *to, const igraph_t *from, + igraph_bool_t ga, igraph_bool_t va, igraph_bool_t ea +); +igraph_error_t igraph_i_attribute_add_vertices( + igraph_t *graph, igraph_int_t nv, + const igraph_attribute_record_list_t *attr +); +igraph_error_t igraph_i_attribute_permute_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx); +igraph_error_t igraph_i_attribute_combine_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); +igraph_error_t igraph_i_attribute_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr +); +igraph_error_t igraph_i_attribute_permute_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx); +igraph_error_t igraph_i_attribute_combine_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_list_t *merges, + const igraph_attribute_combination_t *comb); + +igraph_error_t igraph_i_attribute_get_info(const igraph_t *graph, + igraph_strvector_t *gnames, + igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, + igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, + igraph_vector_int_t *etypes); +igraph_bool_t igraph_i_attribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name); +igraph_error_t igraph_i_attribute_get_type(const igraph_t *graph, + igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, + const char *name); + +igraph_error_t igraph_i_attribute_get_numeric_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_t *value); +igraph_error_t igraph_i_attribute_get_numeric_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_t *value); +igraph_error_t igraph_i_attribute_get_numeric_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_t *value); +igraph_error_t igraph_i_attribute_get_string_graph_attr(const igraph_t *graph, + const char *name, + igraph_strvector_t *value); +igraph_error_t igraph_i_attribute_get_string_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_strvector_t *value); +igraph_error_t igraph_i_attribute_get_string_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_strvector_t *value); +igraph_error_t igraph_i_attribute_get_bool_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_bool_t *value); +igraph_error_t igraph_i_attribute_get_bool_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value); +igraph_error_t igraph_i_attribute_get_bool_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_bool_t *value); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_GRAPH_ATTRIBUTES_H */ diff --git a/src/graph/basic_query.c b/src/graph/basic_query.c new file mode 100644 index 0000000..c58d19b --- /dev/null +++ b/src/graph/basic_query.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_interface.h" +#include "igraph_structural.h" + +/** + * \ingroup structural + * \function igraph_are_adjacent + * \brief Decides whether two vertices are adjacent. + * + * Decides whether there are any edges that have \p v1 and \p v2 + * as endpoints. This function is of course symmetric for undirected + * graphs. + * + * \param graph The graph object. + * \param v1 The first vertex. + * \param v2 The second vertex. + * \param res Boolean, \c true if there is an edge from + * \p v1 to \p v2, \c false otherwise. + * \return The error code \c IGRAPH_EINVVID is returned if an invalid + * vertex ID is given. + * + * Time complexity: O( min(log(d1), log(d2)) ), + * d1 is the (out-)degree of \p v1 and d2 is the (in-)degree of \p v2. + */ +igraph_error_t igraph_are_adjacent(const igraph_t *graph, + igraph_int_t v1, igraph_int_t v2, + igraph_bool_t *res) { + + igraph_int_t nov = igraph_vcount(graph); + igraph_int_t eid = -1; + + if (v1 < 0 || v2 < 0 || v1 > nov - 1 || v2 > nov - 1) { + IGRAPH_ERROR("Invalid vertex ID when checking if two vertices are connected.", IGRAPH_EINVVID); + } + + igraph_get_eid(graph, &eid, v1, v2, IGRAPH_DIRECTED, /*error=*/ false); + *res = (eid >= 0); + + return IGRAPH_SUCCESS; +} diff --git a/src/graph/caching.c b/src/graph/caching.c new file mode 100644 index 0000000..6710bf7 --- /dev/null +++ b/src/graph/caching.c @@ -0,0 +1,214 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_interface.h" + +#include "graph/caching.h" + +#include + +/****** Strictly internal functions ******/ + +/** + * \brief Initializes a property cache, ensuring that all values are unknown. + */ +igraph_error_t igraph_i_property_cache_init(igraph_i_property_cache_t *cache) { + IGRAPH_STATIC_ASSERT(IGRAPH_PROP_I_SIZE <= 32); + + memset(cache->value, 0, sizeof(cache->value)); + cache->known = 0; + return IGRAPH_SUCCESS; +} + +/** + * \brief Copies a property cache. + */ +igraph_error_t igraph_i_property_cache_copy( + igraph_i_property_cache_t *cache, + const igraph_i_property_cache_t *other_cache) { + *cache = *other_cache; + return IGRAPH_SUCCESS; +} + +/** + * \brief Destroys a property cache. + */ +void igraph_i_property_cache_destroy(igraph_i_property_cache_t *cache) { + IGRAPH_UNUSED(cache); + /* Nothing to do */ +} + +/***** Developer functions, exposed *****/ + +/** + * \brief Returns the value of a cached boolean property. + * + * This function provides valid results only when the property is already + * cached. Use \ref igraph_i_property_cache_has() to retrieve whether the + * property is cached. + * + * \param graph the graph whose cache is to be checked + * \param prop the property to retrieve from the cache + * \return the cached value of the property if the value is in the cache, or + * an undefined value otherwise + */ +igraph_bool_t igraph_i_property_cache_get_bool(const igraph_t *graph, igraph_cached_property_t prop) { + IGRAPH_ASSERT(prop >= 0 && prop < IGRAPH_PROP_I_SIZE); + assert(graph->cache != NULL); + return graph->cache->value[prop]; +} + +/** + * \brief Returns whether the cache contains a value for the given cached property. + * + * \param graph the graph whose cache is to be checked + * \param prop the property to check in the cache + */ +igraph_bool_t igraph_i_property_cache_has(const igraph_t *graph, igraph_cached_property_t prop) { + IGRAPH_ASSERT(prop >= 0 && prop < IGRAPH_PROP_I_SIZE); + assert(graph->cache != NULL); + return graph->cache->known & (1 << prop); +} + +/** + * \brief Stores a property value in the cache. + * + * \param graph the graph whose cache is to be modified + * \param prop the property to update in the cache + * \param value the value of the property to add to the cache + */ +void igraph_i_property_cache_set_bool(const igraph_t *graph, igraph_cached_property_t prop, igraph_bool_t value) { + IGRAPH_ASSERT(prop >= 0 && prop < IGRAPH_PROP_I_SIZE); + assert(graph->cache != NULL); + /* Even though graph is const, updating the cache is not considered modification. + * Functions that merely compute graph properties, and thus leave the graph structure + * intact, will often update the cache. */ + graph->cache->value[prop] = value; + graph->cache->known |= (1 << prop); +} + +/** + * \brief Stores a property value in the cache. + * + * This function asserts that if the value of \p prop was already known, + * then \p value is consistent with the previously stored value. + * If this is not the case, a fatal error is triggered, with the reasoning + * that the cache must have become invalid/inconsistent due to a bug. + * + * Therefore, this function cannot be used to change an already stored + * property to a different value. If this is your intention, invalidate + * the cache explicitly first. + * + * \param graph the graph whose cache is to be modified + * \param prop the property to update in the cache + * \param value the value of the property to add to the cache + */ +void igraph_i_property_cache_set_bool_checked(const igraph_t *graph, igraph_cached_property_t prop, igraph_bool_t value) { + IGRAPH_ASSERT(prop >= 0 && prop < IGRAPH_PROP_I_SIZE); + assert(graph->cache != NULL); + /* Even though graph is const, updating the cache is not considered modification. + * Functions that merely compute graph properties, and thus leave the graph structure + * intact, will often update the cache. */ + if (graph->cache->known & (1 << prop)) { + IGRAPH_ASSERT(graph->cache->value[prop] == value); + } else { + igraph_i_property_cache_set_bool(graph, prop, value); + } +} + +/** + * \brief Invalidates the cached value of a property in a graph. + * + * \param graph the graph whose cache is to be modified + * \param prop the property to invalidate in the cache + */ +void igraph_i_property_cache_invalidate(const igraph_t *graph, igraph_cached_property_t prop) { + IGRAPH_ASSERT(prop >= 0 && prop < IGRAPH_PROP_I_SIZE); + assert(graph->cache != NULL); + graph->cache->known &= ~(1 << prop); +} + +/** + * \brief Invalidates all cached properties of the graph. + * + * This function is typically called after the graph is modified. + * + * \param graph the graph whose cache is to be invalidated + */ +void igraph_i_property_cache_invalidate_all(const igraph_t *graph) { + assert(graph->cache != NULL); + graph->cache->known = 0; +} + +/** + * \brief Invalidates all but a few cached properties of the graph, subject to specific conditions. + * + * This function is typically called after the graph is modified if we know that + * the modification does not affect certain cached properties in certain cases. + * For instance, adding more vertices does not make a connected graph disconnected, + * so we can keep the cached properties related to graph connectivity if they + * were already cached as true, but we need to invalidate them if they were + * cached as false. + * + * + * Use 1 << IGRAPH_PROP_SOMETHING to encode an individual property + * in the bits of the bitmask used in the arguments of this function. + * + * \param graph the graph whose cache is to be invalidated + * \param keep_always bitmask where the i-th bit corresponds to cached property \em i + * and it should be set to 1 if the property should be \em kept , + * irrespectively of its current cached value. + */ +void igraph_i_property_cache_invalidate_conditionally( + const igraph_t *graph, uint32_t keep_always, uint32_t keep_when_false, + uint32_t keep_when_true +) { + uint32_t invalidate = ~keep_always; + uint32_t mask; + uint32_t maybe_keep; + igraph_bool_t cached_value; + + assert(graph->cache != NULL); + + /* The bits of maybe_keep are set to 1 for those properties that are: + * + * - currently cached + * - should _probably_ be invalidated + * - _but_ the current cached value of the property may change the decision + */ + maybe_keep = graph->cache->known & invalidate & (keep_when_false | keep_when_true); + + if (maybe_keep) { + for (igraph_cached_property_t prop = (igraph_cached_property_t ) 0; prop < IGRAPH_PROP_I_SIZE; ++prop) { + mask = 1 << prop; + if (maybe_keep & mask) { + /* if we get here, we know that the property is cached; we have + * masked maybe_keep with graph->cache->known */ + cached_value = igraph_i_property_cache_get_bool(graph, prop); + if ( + ((keep_when_false & mask) && !cached_value) || + ((keep_when_true & mask) && cached_value) + ) { + invalidate &= ~mask; + } + } + } + } + + graph->cache->known &= ~invalidate; +} diff --git a/src/graph/caching.h b/src/graph/caching.h new file mode 100644 index 0000000..d82c926 --- /dev/null +++ b/src/graph/caching.h @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_CACHING_H +#define IGRAPH_CACHING_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +#include "internal/hacks.h" + +#include /* memset */ + +IGRAPH_BEGIN_C_DECLS + +struct igraph_i_property_cache_t { + igraph_bool_t value[IGRAPH_PROP_I_SIZE]; + + /** Bit field that stores which of the properties are cached at the moment */ + uint32_t known; +}; + +igraph_error_t igraph_i_property_cache_init(igraph_i_property_cache_t *cache); +igraph_error_t igraph_i_property_cache_copy( + igraph_i_property_cache_t *cache, + const igraph_i_property_cache_t *other_cache); +void igraph_i_property_cache_destroy(igraph_i_property_cache_t *cache); + +void igraph_i_property_cache_invalidate_conditionally( + const igraph_t *graph, uint32_t keep_always, uint32_t keep_when_false, uint32_t keep_when_true +); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_CACHING_H */ diff --git a/src/graph/cattributes.c b/src/graph/cattributes.c new file mode 100644 index 0000000..70031f7 --- /dev/null +++ b/src/graph/cattributes.c @@ -0,0 +1,2909 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include + +/* An attribute is either a numeric vector (vector_t), a boolean vector + * (vector_bool_t) or a string vector (strvector_t). + * The attribute itself is stored in a struct igraph_attribute_record_t. There + * is one such object for each attribute. The igraph_t has a pointer to an + * igraph_i_cattribute_t, which contains three igraph_attribute_vector_list_t's, + * each holding pointers to igraph_attribute_record_t objects. */ + +typedef struct igraph_i_cattributes_t { + igraph_attribute_record_list_t gal; + igraph_attribute_record_list_t val; + igraph_attribute_record_list_t eal; +} igraph_i_cattributes_t; + +/* + * Finds an attribute with the given name in an attribute record list, and + * returns the index of the record in the list, or -1 if there was no such + * attribute. + */ +static igraph_int_t igraph_i_cattribute_find_index( + const igraph_attribute_record_list_t *attrs, const char *name +) { + igraph_int_t n = igraph_attribute_record_list_size(attrs); + for (igraph_int_t i = 0; i < n; i++) { + const igraph_attribute_record_t *rec = igraph_attribute_record_list_get_ptr(attrs, i); + if (!strcmp(rec->name, name)) { + return i; + } + } + return -1; +} + +/* + * Finds an attribute with the given name in an attribute record list, and + * returns the attribute record or NULL if there was no such attribute. + * Optionally, the type of the attribute can be enforced; use + * IGRAPH_ATTRIBUTE_UNSPECIFIED if you do not care about the type. + */ +static igraph_attribute_record_t* igraph_i_cattribute_find( + igraph_attribute_record_list_t *attrs, const char *name, + igraph_attribute_type_t type +) { + igraph_int_t index = igraph_i_cattribute_find_index(attrs, name); + igraph_attribute_record_t *rec; + + if (index >= 0) { + rec = igraph_attribute_record_list_get_ptr(attrs, index); + if (type == IGRAPH_ATTRIBUTE_UNSPECIFIED || type == rec->type) { + return rec; + } + } + + return NULL; +} + +/* + * Same as igraph_i_cattribute_find(), but suitable for const attribute record + * lists. + */ +static const igraph_attribute_record_t* igraph_i_cattribute_find_const( + const igraph_attribute_record_list_t *attrs, const char *name, + igraph_attribute_type_t type +) { + igraph_int_t index = igraph_i_cattribute_find_index(attrs, name); + const igraph_attribute_record_t *rec; + + if (index >= 0) { + rec = igraph_attribute_record_list_get_ptr(attrs, index); + if (type == IGRAPH_ATTRIBUTE_UNSPECIFIED || type == rec->type) { + return rec; + } + } + + return NULL; +} + +/* + * Finds an attribute with the given name in an attribute record list, and + * returns the attribute record in an output argument. Returns an error code + * if there is no such attribute. Optionally, the type of the attribute can be + * enforced; use IGRAPH_ATTRIBUTE_UNSPECIFIED if you do not care about the type. + */ +static igraph_error_t igraph_i_cattribute_find_or_return( + igraph_attribute_record_list_t *attrs, const char *name, + igraph_attribute_type_t type, igraph_attribute_record_t **ptr +) { + igraph_attribute_record_t *rec; + + rec = igraph_i_cattribute_find(attrs, name, IGRAPH_ATTRIBUTE_UNSPECIFIED); + if (!rec) { + IGRAPH_ERRORF("Attribute '%s' does not exist.", IGRAPH_EINVAL, name); + } + + if (type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_check_type(rec, type)); + } + + if (ptr) { + *ptr = rec; + } + + return IGRAPH_SUCCESS; +} + +/* + * Finds an attribute with the given name in an attribute record list, and + * returns the attribute record in an output argument. Creates a new attribute + * with the given name if there is no such attribute. The type of the attribute + * needs to be specified so we can create the appropriate value vector if needed. + * You can specify a length; when the existing value vector for the attribute + * is shorter than this length, it will be extended to ensure that it has at + * least this many elements. You can pass 0 as the length if you do not want to + * expand value vectors. + */ +static igraph_error_t igraph_i_cattribute_find_or_create( + igraph_attribute_record_list_t *attrs, + const char *name, igraph_attribute_type_t type, + igraph_int_t length, + igraph_attribute_record_t **ptr +) { + igraph_attribute_record_t *rec; + + rec = igraph_i_cattribute_find(attrs, name, IGRAPH_ATTRIBUTE_UNSPECIFIED); + if (rec) { + if (type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_check_type(rec, type)); + } + } else { + IGRAPH_CHECK(igraph_attribute_record_list_push_back_new(attrs, &rec)); + IGRAPH_CHECK(igraph_attribute_record_set_name(rec, name)); + IGRAPH_CHECK(igraph_attribute_record_set_type(rec, type)); + } + + if (length > 0 && igraph_attribute_record_size(rec) < length) { + IGRAPH_CHECK(igraph_attribute_record_resize(rec, length)); + } + + if (ptr) { + *ptr = rec; + } + + return IGRAPH_SUCCESS; +} + +/* + * Restores attribute vector lengths to their original size after a failure. + * This function assumes that none of the attribute vectors are shorter than origlen. + * Some may be longer due to a partially completed size extension: these will be + * shrunk to their original size. + */ +static void igraph_i_cattribute_revert_attribute_vector_sizes( + igraph_attribute_record_list_t *attrlist, igraph_int_t origlen) { + + igraph_int_t no_of_attrs = igraph_attribute_record_list_size(attrlist); + for (igraph_int_t i = 0; i < no_of_attrs; i++) { + igraph_attribute_record_t *rec = igraph_attribute_record_list_get_ptr(attrlist, i); + IGRAPH_ASSERT(igraph_attribute_record_size(rec) >= origlen); + if (igraph_attribute_record_resize(rec, origlen) != IGRAPH_SUCCESS) { + /* We should have succeeded for known attribute types because we + * always shrink the vector so throw a fatal error if this happens */ + IGRAPH_FATAL("Unknown attribute type encountered."); + } + } +} + +static igraph_error_t igraph_i_cattribute_init( + igraph_t *graph, const igraph_attribute_record_list_t *attr +) { + igraph_i_cattributes_t *nattr; + + nattr = IGRAPH_CALLOC(1, igraph_i_cattributes_t); + IGRAPH_CHECK_OOM(nattr, "Insufficient memory to allocate attribute storage."); + IGRAPH_FINALLY(igraph_free, nattr); + + if (attr) { + IGRAPH_CHECK(igraph_attribute_record_list_init_copy(&nattr->gal, attr)); + } else { + IGRAPH_CHECK(igraph_attribute_record_list_init(&nattr->gal, 0)); + } + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &nattr->gal); + + IGRAPH_CHECK(igraph_attribute_record_list_init(&nattr->val, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &nattr->val); + + IGRAPH_CHECK(igraph_attribute_record_list_init(&nattr->eal, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &nattr->eal); + + graph->attr = nattr; + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static void igraph_i_cattribute_destroy(igraph_t *graph) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_destroy(&attr->eal); + igraph_attribute_record_list_destroy(&attr->val); + igraph_attribute_record_list_destroy(&attr->gal); + IGRAPH_FREE(graph->attr); /* sets to NULL */ +} + +/* No reference counting here. If you use attributes in C you should + know what you're doing. */ + +static igraph_error_t igraph_i_cattribute_copy( + igraph_t *to, const igraph_t *from, + igraph_bool_t ga, igraph_bool_t va, igraph_bool_t ea +) { + igraph_i_cattributes_t *attrto, *attrfrom = from->attr; + igraph_attribute_record_list_t *alto[3], *alfrom[3] = { + &attrfrom->gal, &attrfrom->val, &attrfrom->eal + }; + igraph_bool_t copy[3] = { ga, va, ea }; + + attrto = IGRAPH_CALLOC(1, igraph_i_cattributes_t); + IGRAPH_CHECK_OOM(attrto, "Insufficient memory to copy attributes."); + IGRAPH_FINALLY(igraph_free, attrto); + + alto[0] = &attrto->gal; + alto[1] = &attrto->val; + alto[2] = &attrto->eal; + + for (igraph_int_t i = 0; i < 3; i++) { + if (copy[i]) { + IGRAPH_CHECK(igraph_attribute_record_list_init_copy(alto[i], alfrom[i])); + } else { + IGRAPH_CHECK(igraph_attribute_record_list_init(alto[i], 0)); + } + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, alto[i]); + } + + to->attr = attrto; + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_add_vertices_or_edges_inner( + igraph_attribute_record_list_t *val, + igraph_int_t newlen, igraph_int_t nv, + const igraph_attribute_record_list_t *nattr +) { + igraph_int_t length; + igraph_int_t nattrno = nattr == NULL ? 0 : igraph_attribute_record_list_size(nattr); + igraph_int_t origlen = newlen - nv; + + IGRAPH_ASSERT(origlen >= 0); + + /* Find all the attributes that are newly added, and create new value vectors + * for them in the original graph */ + for (igraph_int_t i = 0; i < nattrno; i++) { + const igraph_attribute_record_t *nattr_entry = igraph_attribute_record_list_get_ptr(nattr, i); + const char *nname = nattr_entry->name; + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(val, nname, nattr_entry->type, origlen, NULL)); + } + + /* Now append the new values */ + length = igraph_attribute_record_list_size(val); + for (igraph_int_t i = 0; i < length; i++) { + igraph_attribute_record_t *oldrec = igraph_attribute_record_list_get_ptr(val, i); + const igraph_attribute_record_t *newrec = nattr + ? igraph_i_cattribute_find_const(nattr, oldrec->name, oldrec->type) + : NULL; + + IGRAPH_ASSERT(igraph_attribute_record_size(oldrec) == origlen); + + if (newrec) { + /* This attribute is present in nattr */ + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + if (nv != igraph_vector_size(newrec->value.as_vector)) { + IGRAPH_ERROR("Invalid numeric attribute length.", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_append( + oldrec->value.as_vector, newrec->value.as_vector + )); + break; + case IGRAPH_ATTRIBUTE_STRING: + if (nv != igraph_strvector_size(newrec->value.as_strvector)) { + IGRAPH_ERROR("Invalid string attribute length.", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_strvector_append( + oldrec->value.as_strvector, newrec->value.as_strvector + )); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + if (nv != igraph_vector_bool_size(newrec->value.as_vector_bool)) { + IGRAPH_ERROR("Invalid boolean attribute length.", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_bool_append( + oldrec->value.as_vector_bool, newrec->value.as_vector_bool + )); + break; + default: + IGRAPH_WARNINGF( + "Attribute '%s' with unknown type %d ignored", + oldrec->name, (int) oldrec->type + ); + break; + } + } else { + /* No such attribute among the new ones so just extend the length + * of the current record */ + IGRAPH_CHECK(igraph_attribute_record_resize(oldrec, newlen)); + } + + IGRAPH_ASSERT(igraph_attribute_record_size(oldrec) == newlen); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_add_vertices_or_edges( + igraph_attribute_record_list_t *val, + igraph_int_t newlen, igraph_int_t nv, + const igraph_attribute_record_list_t *nattr +) { + igraph_int_t origlen = newlen - nv; + igraph_error_t err = igraph_i_cattribute_add_vertices_or_edges_inner( + val, newlen, nv, nattr + ); + + if (err != IGRAPH_SUCCESS) { + /* If unsuccessful, revert attribute vector sizes. + * The following function assumes that all attributes vectors that + * are present have a length at least as great as origlen. + * This is true at the moment because any new attributes that are + * added to the graph are created directly at 'origlen' instead of + * being created at smaller sizes and resized later. + * + * NOTE: While this ensures that all attribute vector lengths are + * correct, it does not ensure that no extra attributes have + * been added to the graph. However, the presence of extra + * attributes does not make the attribute table inconsistent + * like the incorrect attribute vector lengths would. + */ + igraph_i_cattribute_revert_attribute_vector_sizes(val, origlen); + } + + return err; +} + +static igraph_error_t igraph_i_cattribute_add_vertices( + igraph_t *graph, igraph_int_t nv, + const igraph_attribute_record_list_t *nattr +) { + igraph_i_cattributes_t *attr = graph->attr; + return igraph_i_cattribute_add_vertices_or_edges(&attr->val, igraph_vcount(graph), nv, nattr); +} + +typedef struct { + igraph_vector_t *numeric; + igraph_vector_bool_t *boolean; + igraph_vector_ptr_t *strings; + igraph_int_t length; +} igraph_i_attribute_permutation_work_area_t; + +static igraph_error_t igraph_i_attribute_permutation_work_area_init( + igraph_i_attribute_permutation_work_area_t *work_area, igraph_int_t length +) { + work_area->length = length; + work_area->numeric = NULL; + work_area->boolean = NULL; + work_area->strings = NULL; + return IGRAPH_SUCCESS; +} + +static void igraph_i_attribute_permutation_work_area_release_stored_strvectors( + igraph_i_attribute_permutation_work_area_t *work_area +) { + if (work_area->strings != NULL) { + igraph_vector_ptr_destroy_all(work_area->strings); + IGRAPH_FREE(work_area->strings); + work_area->strings = NULL; + } +} + +static void igraph_i_attribute_permutation_work_area_destroy( + igraph_i_attribute_permutation_work_area_t *work_area +) { + igraph_i_attribute_permutation_work_area_release_stored_strvectors(work_area); + if (work_area->numeric != NULL) { + igraph_vector_destroy(work_area->numeric); + IGRAPH_FREE(work_area->numeric); + work_area->numeric = NULL; + } + if (work_area->boolean != NULL) { + igraph_vector_bool_destroy(work_area->boolean); + IGRAPH_FREE(work_area->boolean); + work_area->boolean = NULL; + } +} + +static igraph_error_t igraph_i_attribute_permutation_work_area_alloc_for_numeric( + igraph_i_attribute_permutation_work_area_t *work_area +) { + igraph_vector_t* vec = work_area->numeric; + + if (vec == NULL) { + vec = IGRAPH_CALLOC(1, igraph_vector_t); + IGRAPH_CHECK_OOM(vec, "Cannot permute attributes."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_init(vec, work_area->length)); + work_area->numeric = vec; + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_attribute_permutation_work_area_alloc_for_boolean( + igraph_i_attribute_permutation_work_area_t *work_area +) { + igraph_vector_bool_t* vec = work_area->boolean; + + if (vec == NULL) { + vec = IGRAPH_CALLOC(1, igraph_vector_bool_t); + IGRAPH_CHECK_OOM(vec, "Cannot permute attributes."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_bool_init(vec, work_area->length)); + work_area->boolean = vec; + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_attribute_permutation_work_area_alloc_for_strings( + igraph_i_attribute_permutation_work_area_t *work_area +) { + igraph_vector_ptr_t* vec = work_area->strings; + + if (vec == NULL) { + vec = IGRAPH_CALLOC(1, igraph_vector_ptr_t); + IGRAPH_CHECK_OOM(vec, "Cannot permute attributes."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_ptr_init(vec, 0)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(vec, igraph_strvector_destroy); + work_area->strings = vec; + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_attribute_permutation_work_area_permute_and_store_strvector( + igraph_i_attribute_permutation_work_area_t *work_area, + const igraph_strvector_t *vec, + const igraph_vector_int_t *idx +) { + igraph_strvector_t *new_vec; + + new_vec = IGRAPH_CALLOC(1, igraph_strvector_t); + IGRAPH_CHECK_OOM(new_vec, "Cannot permute attributes."); + IGRAPH_FINALLY(igraph_free, new_vec); + IGRAPH_CHECK(igraph_strvector_init(new_vec, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, new_vec); + IGRAPH_CHECK(igraph_vector_ptr_push_back(work_area->strings, new_vec)); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_strvector_index(vec, new_vec, idx)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_permute_attribute_record_list( + igraph_attribute_record_list_t *attrs, + igraph_attribute_record_list_t *new_attrs, + const igraph_vector_int_t *idx +) { + igraph_int_t no_attrs, idxlen; + + no_attrs = igraph_attribute_record_list_size(attrs); + + /* When vertices or edges are permuted, we now assume that there are no + * attributes in the target attribute list yet */ + IGRAPH_ASSERT(igraph_attribute_record_list_empty(new_attrs)); + IGRAPH_FINALLY(igraph_attribute_record_list_clear, new_attrs); + + idxlen = igraph_vector_int_size(idx); + for (igraph_int_t i = 0; i < no_attrs; i++) { + igraph_attribute_record_t *oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + igraph_attribute_type_t type = oldrec->type; + + /* Create a record for the same attribute in the new graph */ + igraph_attribute_record_t *newrec; + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + new_attrs, oldrec->name, oldrec->type, idxlen, &newrec + )); + + /* The data */ + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + IGRAPH_CHECK(igraph_vector_index( + oldrec->value.as_vector, newrec->value.as_vector, idx + )); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + IGRAPH_CHECK(igraph_vector_bool_index( + oldrec->value.as_vector_bool, newrec->value.as_vector_bool, idx + )); + break; + case IGRAPH_ATTRIBUTE_STRING: + IGRAPH_CHECK(igraph_strvector_index( + oldrec->value.as_strvector, newrec->value.as_strvector, idx + )); + break; + default: + IGRAPH_WARNINGF( + "Attribute '%s' with unknown type %d ignored", + oldrec->name, (int) oldrec->type + ); + } + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_permute_attribute_record_list_in_place( + igraph_attribute_record_list_t *attrs, const igraph_vector_int_t *idx +) { + igraph_int_t no_attrs = igraph_attribute_record_list_size(attrs); + igraph_attribute_record_t *oldrec; + igraph_vector_t *num, *num_work; + igraph_strvector_t *str, str_work; + igraph_vector_bool_t *oldbool, *bool_work; + igraph_i_attribute_permutation_work_area_t work_area; + igraph_int_t idx_size = igraph_vector_int_size(idx); + + /* shortcut: don't allocate anything if there are no attributes */ + if (no_attrs == 0) { + return IGRAPH_SUCCESS; + } + + /* do all the allocations that can potentially fail before we actually + * start to permute the vertices to ensure that we will not ever need to + * back out from a permutation once we've started it */ + IGRAPH_CHECK(igraph_i_attribute_permutation_work_area_init(&work_area, idx_size)); + IGRAPH_FINALLY(igraph_i_attribute_permutation_work_area_destroy, &work_area); + for (igraph_int_t i = 0; i < no_attrs; i++) { + oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = oldrec->value.as_vector; + IGRAPH_CHECK(igraph_vector_reserve(num, idx_size)); + IGRAPH_CHECK(igraph_i_attribute_permutation_work_area_alloc_for_numeric(&work_area)); + break; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = oldrec->value.as_vector_bool; + IGRAPH_CHECK(igraph_vector_bool_reserve(oldbool, idx_size)); + IGRAPH_CHECK(igraph_i_attribute_permutation_work_area_alloc_for_boolean(&work_area)); + break; + + case IGRAPH_ATTRIBUTE_STRING: + str = oldrec->value.as_strvector; + IGRAPH_CHECK(igraph_strvector_reserve(str, idx_size)); + IGRAPH_CHECK(igraph_i_attribute_permutation_work_area_alloc_for_strings(&work_area)); + break; + + default: + IGRAPH_WARNINGF( + "Vertex attribute '%s' with unknown type %d ignored", + oldrec->name, (int) oldrec->type + ); + } + } + + /* let's do string attributes first because these might need extra + * allocations that can fail. The strategy is to build new igraph_strvector_t + * instances for the permuted attributes and store them in an + * igraph_vector_ptr_t until we are done with all of them. If any of the + * allocations fail, we can destroy the igraph_vector_ptr_t safely */ + for (igraph_int_t i = 0; i < no_attrs; i++) { + oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + if (oldrec->type == IGRAPH_ATTRIBUTE_STRING) { + str = oldrec->value.as_strvector; + IGRAPH_CHECK( + igraph_i_attribute_permutation_work_area_permute_and_store_strvector( + &work_area, str, idx + ) + ); + } + } + + /* strings are done, and now all vectors involved in the process are + * as large as they should be (or larger) so the operations below are not + * supposed to fail. We can safely replace the original string attribute + * vectors with the permuted ones, and then proceed to the remaining + * attributes */ + for (igraph_int_t i = 0, j = 0; i < no_attrs; i++) { + oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + if (oldrec->type != IGRAPH_ATTRIBUTE_STRING) { + continue; + } + + str = oldrec->value.as_strvector; + str_work = *((igraph_strvector_t*) VECTOR(*(work_area.strings))[j]); + *((igraph_strvector_t*) VECTOR(*(work_area.strings))[j]) = *str; + *str = str_work; + j++; + } + igraph_i_attribute_permutation_work_area_release_stored_strvectors(&work_area); + + for (igraph_int_t i = 0; i < no_attrs; i++) { + oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = oldrec->value.as_vector; + num_work = work_area.numeric; + IGRAPH_ASSERT(num_work != NULL); + IGRAPH_CHECK(igraph_vector_index(num, num_work, idx)); + igraph_vector_swap(num, num_work); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = oldrec->value.as_vector_bool; + bool_work = work_area.boolean; + IGRAPH_ASSERT(bool_work != NULL); + IGRAPH_CHECK(igraph_vector_bool_index(oldbool, bool_work, idx)); + igraph_vector_bool_swap(oldbool, bool_work); + break; + case IGRAPH_ATTRIBUTE_STRING: + /* nothing to do */ + break; + default: + /* already warned */ + break; + } + } + + igraph_i_attribute_permutation_work_area_destroy(&work_area); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_permute_vertices( + const igraph_t *graph, igraph_t *newgraph, const igraph_vector_int_t *idx +) { + igraph_i_cattributes_t *attr = graph->attr, *new_attr = newgraph->attr; + igraph_attribute_record_list_t *val = &attr->val, *new_val = &new_attr->val; + if (graph == newgraph) { + return igraph_i_cattribute_permute_attribute_record_list_in_place(val, idx); + } else { + return igraph_i_cattribute_permute_attribute_record_list(val, new_val, idx); + } +} + +typedef igraph_error_t igraph_cattributes_combine_num_t(const igraph_vector_t *input, + igraph_real_t *output); + +typedef igraph_error_t igraph_cattributes_combine_str_t(const igraph_strvector_t *input, + char **output); + +typedef igraph_error_t igraph_cattributes_combine_bool_t(const igraph_vector_bool_t *input, + igraph_bool_t *output); + +static igraph_error_t igraph_i_cattributes_cn_sum(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + igraph_real_t s = 0.0; + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + s += VECTOR(*oldv)[x]; + } + VECTOR(*newv)[i] = s; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_prod(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + igraph_real_t s = 1.0; + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + s *= VECTOR(*oldv)[x]; + } + VECTOR(*newv)[i] = s; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_min(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + igraph_real_t m = n > 0 ? VECTOR(*oldv)[ VECTOR(*idx)[0] ] : IGRAPH_NAN; + for (igraph_int_t j = 1; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + igraph_real_t val = VECTOR(*oldv)[x]; + if (val < m) { + m = val; + } + } + VECTOR(*newv)[i] = m; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_max(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + igraph_real_t m = n > 0 ? VECTOR(*oldv)[ VECTOR(*idx)[0] ] : IGRAPH_NAN; + for (igraph_int_t j = 1; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + igraph_real_t val = VECTOR(*oldv)[x]; + if (val > m) { + m = val; + } + } + VECTOR(*newv)[i] = m; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = IGRAPH_NAN; + } else if (n == 1) { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[0] ]; + } else { + igraph_int_t r = RNG_INTEGER(0, n - 1); + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[r] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = IGRAPH_NAN; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[0] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = IGRAPH_NAN; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[n - 1] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_mean(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + igraph_real_t s = n > 0 ? 0.0 : IGRAPH_NAN; + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + s += VECTOR(*oldv)[x]; + } + if (n > 0) { + s = s / n; + } + VECTOR(*newv)[i] = s; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cn_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges, + igraph_cattributes_combine_num_t *func) { + + const igraph_vector_t *oldv = oldrec->value.as_vector; + igraph_vector_t *newv = newrec->value.as_vector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + igraph_vector_t values; + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + + igraph_int_t n = igraph_vector_int_size(idx); + IGRAPH_CHECK(igraph_vector_resize(&values, n)); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + VECTOR(values)[j] = VECTOR(*oldv)[x]; + } + + igraph_real_t res; + IGRAPH_CHECK(func(&values, &res)); + VECTOR(*newv)[i] = res; + } + + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else if (n == 1) { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[0] ]; + } else { + igraph_int_t r = RNG_INTEGER(0, n - 1); + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[r] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[0] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ VECTOR(*idx)[n - 1] ]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_all_is_true(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + VECTOR(*newv)[i] = 1; + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + if (!VECTOR(*oldv)[x]) { + VECTOR(*newv)[i] = 0; + break; + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_any_is_true(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + VECTOR(*newv)[i] = 0; + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + if (VECTOR(*oldv)[x]) { + VECTOR(*newv)[i] = 1; + break; + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_majority(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + + igraph_int_t num_trues = 0; + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + if (VECTOR(*oldv)[x]) { + num_trues++; + } + } + + if (n % 2 != 0) { + VECTOR(*newv)[i] = (num_trues > n / 2); + } else { + if (num_trues == n / 2) { + VECTOR(*newv)[i] = RNG_BOOL(); + } else { + VECTOR(*newv)[i] = (num_trues > n / 2); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cb_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges, + igraph_cattributes_combine_bool_t *func) { + + const igraph_vector_bool_t *oldv = oldrec->value.as_vector_bool; + igraph_vector_bool_t *newv = newrec->value.as_vector_bool; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + igraph_vector_bool_t values; + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&values, 0); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + + igraph_int_t n = igraph_vector_int_size(idx); + IGRAPH_CHECK(igraph_vector_bool_resize(&values, n)); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + VECTOR(values)[j] = VECTOR(*oldv)[x]; + } + + igraph_bool_t res; + IGRAPH_CHECK(func(&values, &res)); + VECTOR(*newv)[i] = res; + } + + igraph_vector_bool_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_sn_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value.as_strvector; + igraph_strvector_t *newv = newrec->value.as_strvector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + const char *tmp; + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else if (n == 1) { + tmp = igraph_strvector_get(oldv, 0); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } else { + igraph_int_t r = RNG_INTEGER(0, n - 1); + tmp = igraph_strvector_get(oldv, r); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cs_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value.as_strvector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + igraph_strvector_t *newv = newrec->value.as_strvector; + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else { + const char *tmp = igraph_strvector_get(oldv, VECTOR(*idx)[0]); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cs_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value.as_strvector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + igraph_strvector_t *newv = newrec->value.as_strvector; + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else { + const char *tmp = igraph_strvector_get(oldv, VECTOR(*idx)[n - 1]); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cs_concat(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value.as_strvector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + igraph_strvector_t *newv = newrec->value.as_strvector; + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + igraph_int_t n = igraph_vector_int_size(idx); + size_t len = 0; + const char *tmp; + char *tmp2; + for (igraph_int_t j = 0; j < n; j++) { + tmp = igraph_strvector_get(oldv, j); + len += strlen(tmp); + } + tmp2 = IGRAPH_CALLOC(len + 1, char); + IGRAPH_CHECK_OOM(tmp2, "Cannot combine attributes."); + IGRAPH_FINALLY(igraph_free, tmp2); + len = 0; + for (igraph_int_t j = 0; j < n; j++) { + tmp = igraph_strvector_get(oldv, j); + strcpy(tmp2 + len, tmp); + len += strlen(tmp); + } + + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp2)); + IGRAPH_FREE(tmp2); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattributes_cs_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_int_list_t *merges, + igraph_cattributes_combine_str_t *func) { + + const igraph_strvector_t *oldv = oldrec->value.as_strvector; + igraph_strvector_t *newv = newrec->value.as_strvector; + igraph_int_t newlen = igraph_vector_int_list_size(merges); + + igraph_strvector_t values; + IGRAPH_STRVECTOR_INIT_FINALLY(&values, 0); + + for (igraph_int_t i = 0; i < newlen; i++) { + const igraph_vector_int_t *idx = igraph_vector_int_list_get_ptr(merges, i); + + igraph_int_t n = igraph_vector_int_size(idx); + IGRAPH_CHECK(igraph_strvector_resize(&values, n)); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t x = VECTOR(*idx)[j]; + const char *elem = igraph_strvector_get(oldv, x); + IGRAPH_CHECK(igraph_strvector_set(newv, j, elem)); + } + + char *res; + IGRAPH_CHECK(func(&values, &res)); + IGRAPH_FINALLY(igraph_free, res); + + IGRAPH_CHECK(igraph_strvector_set(newv, i, res)); + + IGRAPH_FREE(res); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \section c_attribute_combination_functions + * + * + * The C attribute handler supports combining the attributes of multiple + * vertices of edges into a single attribute during a vertex or edge contraction + * operation via a user-defined function. This is achieved by setting the + * type of the attribute combination to \c IGRAPH_ATTRIBUTE_COMBINE_FUNCTION + * and passing in a pointer to the custom combination function when specifying + * attribute combinations in \ref igraph_attribute_combination() or + * \ref igraph_attribute_combination_add() . For the C attribute handler, the + * signature of the function depends on the type of the underlying attribute. + * For numeric attributes, use: + * \verbatim igraph_error_t function(const igraph_vector_t *input, igraph_real_t *output); \endverbatim + * where \p input will receive a vector containing the value of the attribute + * for all the vertices or edges being combined, and \p output must be filled + * by the function to the combined value. Similarly, for Boolean attributes, the + * function takes a boolean vector in \p input and must return the combined Boolean + * value in \p output: + * \verbatim igraph_error_t function(const igraph_vector_bool_t *input, igraph_bool_t *output); \endverbatim + * For string attributes, the signature is slightly different: + * \verbatim igraph_error_t function(const igraph_strvector_t *input, char **output); \endverbatim + * In case of strings, all strings in the input vector are \em owned by igraph + * and must not be modified or freed in the combination handler. The string + * returned to the caller in \p output remains owned by the caller; igraph will + * make a copy it and store the copy in the appropriate part of the data + * structure holding the vertex or edge attributes. + * + */ + +typedef struct { + igraph_attribute_combination_type_t type; + union { + igraph_function_pointer_t as_void; + igraph_cattributes_combine_num_t *as_num; + igraph_cattributes_combine_str_t *as_str; + igraph_cattributes_combine_bool_t *as_bool; + } func; +} igraph_attribute_combination_todo_item_t; + +static igraph_error_t igraph_i_cattribute_combine_attribute_record_lists( + igraph_attribute_record_list_t *attrs, igraph_attribute_record_list_t *new_attrs, + const igraph_vector_int_list_t *merges, const igraph_attribute_combination_t *comb +) { + igraph_int_t no_attrs = igraph_attribute_record_list_size(attrs); + igraph_attribute_combination_todo_item_t *todo_items; + + IGRAPH_ASSERT(attrs != new_attrs); + IGRAPH_ASSERT(igraph_attribute_record_list_empty(new_attrs)); + + todo_items = IGRAPH_CALLOC(no_attrs, igraph_attribute_combination_todo_item_t); + IGRAPH_CHECK_OOM(todo_items, "Cannot combine attributes."); + IGRAPH_FINALLY(igraph_free, todo_items); + + for (igraph_int_t i = 0; i < no_attrs; i++) { + const igraph_attribute_record_t *oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + const char *name = oldrec->name; + igraph_attribute_combination_type_t type; + igraph_function_pointer_t voidfunc; + IGRAPH_CHECK(igraph_attribute_combination_query(comb, name, &type, &voidfunc)); + todo_items[i].type = type; + todo_items[i].func.as_void = voidfunc; + } + + IGRAPH_FINALLY(igraph_attribute_record_list_clear, new_attrs); + + for (igraph_int_t i = 0; i < no_attrs; i++) { + const igraph_attribute_record_t *oldrec = igraph_attribute_record_list_get_ptr(attrs, i); + igraph_attribute_record_t newrec; + const char *name = oldrec->name; + igraph_attribute_combination_todo_item_t todo_item = todo_items[i]; + igraph_attribute_type_t attr_type = oldrec->type; + + if (todo_item.type == IGRAPH_ATTRIBUTE_COMBINE_DEFAULT || + todo_item.type == IGRAPH_ATTRIBUTE_COMBINE_IGNORE) { + continue; + } + + IGRAPH_CHECK(igraph_attribute_record_init(&newrec, name, attr_type)); + IGRAPH_FINALLY(igraph_attribute_record_destroy, &newrec); + + IGRAPH_CHECK(igraph_attribute_record_resize(&newrec, igraph_vector_int_list_size(merges))); + + if (attr_type == IGRAPH_ATTRIBUTE_NUMERIC) { + switch (todo_item.type) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cn_func(oldrec, &newrec, merges, + todo_item.func.as_num)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_CHECK(igraph_i_cattributes_cn_sum(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_CHECK(igraph_i_cattributes_cn_prod(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cn_min(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cn_max(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cn_random(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cn_first(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cn_last(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_CHECK(igraph_i_cattributes_cn_mean(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Median calculation not implemented.", + IGRAPH_UNIMPLEMENTED); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot concatenate numeric attributes.", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute combination.", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (attr_type == IGRAPH_ATTRIBUTE_BOOLEAN) { + switch (todo_item.type) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cb_func(oldrec, &newrec, merges, + todo_item.func.as_bool)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cb_any_is_true(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cb_all_is_true(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_CHECK(igraph_i_cattributes_cb_majority(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cb_random(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cb_first(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cb_last(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot calculate concatenation of Booleans.", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute combination.", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (attr_type == IGRAPH_ATTRIBUTE_STRING) { + switch (todo_item.type) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cs_func(oldrec, &newrec, merges, + todo_item.func.as_str)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_ERROR("Cannot sum strings.", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_ERROR("Cannot multiply strings.", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_ERROR("Cannot find minimum of strings.", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_ERROR("Cannot find maximum of strings.", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_ERROR("Cannot calculate mean of strings.", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Cannot calculate median of strings.", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_sn_random(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cs_first(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cs_last(oldrec, &newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_CHECK(igraph_i_cattributes_cs_concat(oldrec, &newrec, merges)); + break; + default: + IGRAPH_ERROR("Unknown attribute combination.", + IGRAPH_UNIMPLEMENTED); + break; + } + } else { + IGRAPH_ERROR("Unknown attribute type, this should not happen.", + IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(igraph_attribute_record_list_push_back(new_attrs, &newrec)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of newrec transferred */ + } + + IGRAPH_FREE(todo_items); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_combine_vertices( + const igraph_t *graph, igraph_t *newgraph, + const igraph_vector_int_list_t *merges, const igraph_attribute_combination_t *comb +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_i_cattributes_t *toattr = newgraph->attr; + return igraph_i_cattribute_combine_attribute_record_lists( + &attr->val, &toattr->val, merges, comb + ); +} + +static igraph_error_t igraph_i_cattribute_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *nattr +) { + igraph_int_t ne = igraph_vector_int_size(edges) / 2; + igraph_i_cattributes_t *attr = graph->attr; + return igraph_i_cattribute_add_vertices_or_edges(&attr->eal, igraph_ecount(graph), ne, nattr); +} + +static igraph_error_t igraph_i_cattribute_permute_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_int_t *idx) { + igraph_i_cattributes_t *attr = graph->attr, *new_attr = newgraph->attr; + igraph_attribute_record_list_t *eal = &attr->eal, *new_eal = &new_attr->eal; + if (graph == newgraph) { + return igraph_i_cattribute_permute_attribute_record_list_in_place(eal, idx); + } else { + return igraph_i_cattribute_permute_attribute_record_list(eal, new_eal, idx); + } +} + +static igraph_error_t igraph_i_cattribute_combine_edges( + const igraph_t *graph, igraph_t *newgraph, + const igraph_vector_int_list_t *merges, const igraph_attribute_combination_t *comb +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_i_cattributes_t *toattr = newgraph->attr; + return igraph_i_cattribute_combine_attribute_record_lists( + &attr->eal, &toattr->eal, merges, comb + ); +} + +static igraph_error_t igraph_i_cattribute_get_info(const igraph_t *graph, + igraph_strvector_t *gnames, + igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, + igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, + igraph_vector_int_t *etypes) { + + igraph_strvector_t *names[3] = { gnames, vnames, enames }; + igraph_vector_int_t *types[3] = { gtypes, vtypes, etypes }; + igraph_i_cattributes_t *at = graph->attr; + igraph_attribute_record_list_t *attr[3] = { &at->gal, &at->val, &at->eal }; + + for (igraph_int_t i = 0; i < 3; i++) { + igraph_strvector_t *n = names[i]; + igraph_vector_int_t *t = types[i]; + const igraph_attribute_record_list_t *al = attr[i]; + igraph_int_t len = igraph_attribute_record_list_size(al); + + if (n) { + IGRAPH_CHECK(igraph_strvector_resize(n, len)); + } + if (t) { + IGRAPH_CHECK(igraph_vector_int_resize(t, len)); + } + + for (igraph_int_t j = 0; j < len; j++) { + const igraph_attribute_record_t *rec = igraph_attribute_record_list_get_ptr(al, j); + const char *name = rec->name; + igraph_attribute_type_t type = rec->type; + if (n) { + IGRAPH_CHECK(igraph_strvector_set(n, j, name)); + } + if (t) { + VECTOR(*t)[j] = type; + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_bool_t igraph_i_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + const igraph_i_cattributes_t *at = graph->attr; + switch (type) { + case IGRAPH_ATTRIBUTE_GRAPH: + return igraph_i_cattribute_find_index(&at->gal, name) >= 0; + case IGRAPH_ATTRIBUTE_VERTEX: + return igraph_i_cattribute_find_index(&at->val, name) >= 0; + case IGRAPH_ATTRIBUTE_EDGE: + return igraph_i_cattribute_find_index(&at->eal, name) >= 0; + default: + IGRAPH_ERROR("Unknown attribute element type.", IGRAPH_EINVAL); + break; + } + + return false; +} + +static igraph_error_t igraph_i_cattribute_get_type(const igraph_t *graph, + igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, + const char *name) { + igraph_attribute_record_t *rec; + igraph_i_cattributes_t *at = graph->attr; + igraph_attribute_record_list_t *al; + + switch (elemtype) { + case IGRAPH_ATTRIBUTE_GRAPH: + al = &at->gal; + break; + case IGRAPH_ATTRIBUTE_VERTEX: + al = &at->val; + break; + case IGRAPH_ATTRIBUTE_EDGE: + al = &at->eal; + break; + default: + IGRAPH_ERROR("Unknown attribute element type.", IGRAPH_EINVAL); + break; + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(al, name, IGRAPH_ATTRIBUTE_UNSPECIFIED, &rec)); + *type = rec->type; + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_numeric_graph_attr( + const igraph_t *graph, const char *name, igraph_vector_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *gal = &attr->gal; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(gal, name, IGRAPH_ATTRIBUTE_NUMERIC, &rec)); + IGRAPH_CHECK(igraph_vector_push_back(value, VECTOR(*rec->value.as_vector)[0])); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_bool_graph_attr( + const igraph_t *graph, const char *name, igraph_vector_bool_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *gal = &attr->gal; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(gal, name, IGRAPH_ATTRIBUTE_BOOLEAN, &rec)); + IGRAPH_CHECK(igraph_vector_bool_push_back(value, VECTOR(*rec->value.as_vector_bool)[0])); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_string_graph_attr( + const igraph_t *graph, const char *name, igraph_strvector_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *gal = &attr->gal; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(gal, name, IGRAPH_ATTRIBUTE_STRING, &rec)); + IGRAPH_CHECK(igraph_strvector_push_back(value, igraph_strvector_get(rec->value.as_strvector, 0))); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_numeric_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *val = &attr->val; + igraph_attribute_record_t *rec; + const igraph_vector_t *num; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(val, name, IGRAPH_ATTRIBUTE_NUMERIC, &rec)); + + num = rec->value.as_vector; + if (igraph_vs_is_all(&vs)) { + IGRAPH_CHECK(igraph_vector_append(value, num)); + } else { + igraph_vit_t it; + igraph_int_t i = igraph_vector_size(value); + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_vector_resize(value, i + IGRAPH_VIT_SIZE(it))); + for (; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + igraph_int_t v = IGRAPH_VIT_GET(it); + VECTOR(*value)[i] = VECTOR(*num)[v]; + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_bool_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *val = &attr->val; + igraph_attribute_record_t *rec; + const igraph_vector_bool_t *log; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(val, name, IGRAPH_ATTRIBUTE_BOOLEAN, &rec)); + + log = rec->value.as_vector_bool; + if (igraph_vs_is_all(&vs)) { + IGRAPH_CHECK(igraph_vector_bool_append(value, log)); + } else { + igraph_vit_t it; + igraph_int_t i = igraph_vector_bool_size(value); + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_vector_bool_resize(value, i + IGRAPH_VIT_SIZE(it))); + for (; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + igraph_int_t v = IGRAPH_VIT_GET(it); + VECTOR(*value)[i] = VECTOR(*log)[v]; + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_string_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_strvector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *val = &attr->val; + igraph_attribute_record_t *rec; + const igraph_strvector_t *str; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(val, name, IGRAPH_ATTRIBUTE_STRING, &rec)); + + str = rec->value.as_strvector; + if (igraph_vs_is_all(&vs)) { + igraph_strvector_clear(value); + IGRAPH_CHECK(igraph_strvector_append(value, str)); + } else { + igraph_vit_t it; + igraph_int_t i = igraph_strvector_size(value); + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_strvector_resize(value, i + IGRAPH_VIT_SIZE(it))); + for (; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + igraph_int_t v = IGRAPH_VIT_GET(it); + IGRAPH_CHECK(igraph_strvector_set(value, i, igraph_strvector_get(str, v))); + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_numeric_edge_attr( + const igraph_t *graph, const char *name, igraph_es_t es, igraph_vector_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *eal = &attr->eal; + igraph_attribute_record_t *rec; + const igraph_vector_t *num; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(eal, name, IGRAPH_ATTRIBUTE_NUMERIC, &rec)); + + num = rec->value.as_vector; + if (igraph_es_is_all(&es)) { + IGRAPH_CHECK(igraph_vector_append(value, num)); + } else { + igraph_eit_t it; + igraph_int_t i = igraph_vector_size(value); + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_vector_resize(value, i + IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + igraph_int_t e = IGRAPH_EIT_GET(it); + VECTOR(*value)[i] = VECTOR(*num)[e]; + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_string_edge_attr( + const igraph_t *graph, const char *name, igraph_es_t es, + igraph_strvector_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *eal = &attr->eal; + igraph_attribute_record_t *rec; + const igraph_strvector_t *str; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(eal, name, IGRAPH_ATTRIBUTE_STRING, &rec)); + + str = rec->value.as_strvector; + if (igraph_es_is_all(&es)) { + IGRAPH_CHECK(igraph_strvector_append(value, str)); + } else { + igraph_eit_t it; + igraph_int_t i = igraph_strvector_size(value); + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_strvector_resize(value, i + IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + igraph_int_t e = IGRAPH_EIT_GET(it); + IGRAPH_CHECK(igraph_strvector_set(value, i, igraph_strvector_get(str, e))); + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cattribute_get_bool_edge_attr( + const igraph_t *graph, const char *name, igraph_es_t es, + igraph_vector_bool_t *value +) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *eal = &attr->eal; + igraph_attribute_record_t *rec; + const igraph_vector_bool_t *log; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_return(eal, name, IGRAPH_ATTRIBUTE_BOOLEAN, &rec)); + + log = rec->value.as_vector_bool; + if (igraph_es_is_all(&es)) { + IGRAPH_CHECK(igraph_vector_bool_append(value, log)); + } else { + igraph_eit_t it; + igraph_int_t i = igraph_vector_bool_size(value); + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_vector_bool_resize(value, i + IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + igraph_int_t e = IGRAPH_EIT_GET(it); + VECTOR(*value)[i] = VECTOR(*log)[e]; + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/* -------------------------------------- */ + +const igraph_attribute_table_t igraph_cattribute_table = { + &igraph_i_cattribute_init, + &igraph_i_cattribute_destroy, + &igraph_i_cattribute_copy, + &igraph_i_cattribute_add_vertices, + &igraph_i_cattribute_permute_vertices, + &igraph_i_cattribute_combine_vertices, + &igraph_i_cattribute_add_edges, + &igraph_i_cattribute_permute_edges, + &igraph_i_cattribute_combine_edges, + &igraph_i_cattribute_get_info, + &igraph_i_cattribute_has_attr, + &igraph_i_cattribute_get_type, + &igraph_i_cattribute_get_numeric_graph_attr, + &igraph_i_cattribute_get_string_graph_attr, + &igraph_i_cattribute_get_bool_graph_attr, + &igraph_i_cattribute_get_numeric_vertex_attr, + &igraph_i_cattribute_get_string_vertex_attr, + &igraph_i_cattribute_get_bool_vertex_attr, + &igraph_i_cattribute_get_numeric_edge_attr, + &igraph_i_cattribute_get_string_edge_attr, + &igraph_i_cattribute_get_bool_edge_attr +}; + +/* -------------------------------------- */ + +/** + * \section cattributes + * There is an experimental attribute handler that can be used + * from C code. In this section we show how this works. This attribute + * handler is by default not attached (the default is no attribute + * handler), so we first need to attach it: + * + * igraph_set_attribute_table(&igraph_cattribute_table); + * + * + * Now the attribute functions are available. Please note that + * the attribute handler must be attached before you call any other + * igraph functions, otherwise you might end up with graphs without + * attributes and an active attribute handler, which might cause + * unexpected program behaviour. The rule is that you attach the + * attribute handler in the beginning of your + * main() and never touch it again. Detaching + * the attribute handler might lead to memory leaks. + * + * It is not currently possible to have attribute handlers on a + * per-graph basis. All graphs in an application must be managed with + * the same attribute handler. This also applies to the default case + * when there is no attribute handler at all. + * + * The C attribute handler supports attaching real numbers, boolean + * values and character strings as attributes. No vector values are allowed. + * For example, vertices have a name attribute holding a single + * string value for each vertex, but it is not possible to have a coords + * attribute which is a vector of numbers per vertex. + * + * The functions documented in this section are specific to the C + * attribute handler. Code using these functions will not function when + * a different attribute handler is attached. + * + * \example examples/simple/cattributes.c + * \example examples/simple/cattributes2.c + * \example examples/simple/cattributes3.c + * \example examples/simple/cattributes4.c + */ + +/** + * \function igraph_cattribute_GAN + * \brief Query a numeric graph attribute. + * + * Returns the value of the given numeric graph attribute. + * If the attribute does not exist, a warning is issued + * and NaN is returned. + * + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAN for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +igraph_real_t igraph_cattribute_GAN(const igraph_t *graph, const char *name) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_t *num; + + rec = igraph_i_cattribute_find(&attr->gal, name, IGRAPH_ATTRIBUTE_NUMERIC); + if (!rec) { + IGRAPH_WARNINGF("Graph attribute '%s' does not exist, returning default numeric attribute value.", name); + return IGRAPH_NAN; + } + + num = rec->value.as_vector; + return VECTOR(*num)[0]; +} + +/** + * \function igraph_cattribute_GAB + * \brief Query a boolean graph attribute. + * + * Returns the value of the given boolean graph attribute. + * If the attribute does not exist, a warning is issued + * and false is returned. + * + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAB for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +igraph_bool_t igraph_cattribute_GAB(const igraph_t *graph, const char *name) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_bool_t *log; + + rec = igraph_i_cattribute_find(&attr->gal, name, IGRAPH_ATTRIBUTE_BOOLEAN); + if (!rec) { + IGRAPH_WARNINGF("Graph attribute '%s' does not exist, returning default boolean attribute value.", name); + return false; + } + + log = rec->value.as_vector_bool; + return VECTOR(*log)[0]; +} + +/** + * \function igraph_cattribute_GAS + * \brief Query a string graph attribute. + * + * Returns a const pointer to the string graph attribute + * specified in \p name. The value must not be modified. + * If the attribute does not exist, a warning is issued and + * an empty string is returned. + * + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAS for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +const char *igraph_cattribute_GAS(const igraph_t *graph, const char *name) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_strvector_t *str; + + rec = igraph_i_cattribute_find(&attr->gal, name, IGRAPH_ATTRIBUTE_STRING); + if (!rec) { + IGRAPH_WARNINGF("Graph attribute '%s' does not exist, returning default string attribute value.", name); + return ""; + } + + str = rec->value.as_strvector; + return igraph_strvector_get(str, 0); +} + +/** + * \function igraph_cattribute_VAN + * \brief Query a numeric vertex attribute. + * + * If the attribute does not exist, a warning is issued and + * NaN is returned. See \ref igraph_cattribute_VANV() for + * an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa \ref VAN macro for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +igraph_real_t igraph_cattribute_VAN(const igraph_t *graph, const char *name, + igraph_int_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_t *num; + + rec = igraph_i_cattribute_find(&attr->val, name, IGRAPH_ATTRIBUTE_NUMERIC); + if (!rec) { + IGRAPH_WARNINGF("Vertex attribute '%s' does not exist, returning default numeric attribute value.", name); + return IGRAPH_NAN; + } + + num = rec->value.as_vector; + return VECTOR(*num)[vid]; +} + +/** + * \function igraph_cattribute_VAB + * \brief Query a boolean vertex attribute. + * + * If the vertex attribute does not exist, a warning is issued + * and false is returned. See \ref igraph_cattribute_VABV() for + * an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa \ref VAB macro for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +igraph_bool_t igraph_cattribute_VAB(const igraph_t *graph, const char *name, + igraph_int_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_bool_t *log; + + rec = igraph_i_cattribute_find(&attr->val, name, IGRAPH_ATTRIBUTE_BOOLEAN); + if (!rec) { + IGRAPH_WARNINGF("Vertex attribute '%s' does not exist, returning default boolean attribute value.", name); + return false; + } + + log = rec->value.as_vector_bool; + return VECTOR(*log)[vid]; +} + +/** + * \function igraph_cattribute_VAS + * \brief Query a string vertex attribute. + * + * Returns a const pointer to the string vertex attribute + * specified in \p name. The value must not be modified. + * If the vertex attribute does not exist, a warning is issued and + * an empty string is returned. See \ref igraph_cattribute_VASV() + * for an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa The macro \ref VAS for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +const char *igraph_cattribute_VAS(const igraph_t *graph, const char *name, + igraph_int_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_strvector_t *str; + + rec = igraph_i_cattribute_find(&attr->val, name, IGRAPH_ATTRIBUTE_STRING); + if (!rec) { + IGRAPH_WARNINGF("Vertex attribute '%s' does not exist, returning default string attribute value.", name); + return ""; + } + + str = rec->value.as_strvector; + return igraph_strvector_get(str, vid); +} + +/** + * \function igraph_cattribute_EAN + * \brief Query a numeric edge attribute. + * + * If the attribute does not exist, a warning is issued and + * NaN is returned. See \ref igraph_cattribute_EANV() for + * an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \sa \ref EAN for an easier interface. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +igraph_real_t igraph_cattribute_EAN(const igraph_t *graph, const char *name, + igraph_int_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_t *num; + + rec = igraph_i_cattribute_find(&attr->eal, name, IGRAPH_ATTRIBUTE_NUMERIC); + if (!rec) { + IGRAPH_WARNINGF("Edge attribute '%s' does not exist, returning default numeric attribute value.", name); + return IGRAPH_NAN; + } + + num = rec->value.as_vector; + return VECTOR(*num)[eid]; +} + +/** + * \function igraph_cattribute_EAB + * \brief Query a boolean edge attribute. + * + * If the edge attribute does not exist, a warning is issued and + * false is returned. See \ref igraph_cattribute_EABV() for + * an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \sa \ref EAB for an easier interface. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +igraph_bool_t igraph_cattribute_EAB(const igraph_t *graph, const char *name, + igraph_int_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_vector_bool_t *log; + + rec = igraph_i_cattribute_find(&attr->eal, name, IGRAPH_ATTRIBUTE_BOOLEAN); + if (!rec) { + IGRAPH_WARNINGF("Edge attribute '%s' does not exist, returning default boolean attribute value.", name); + return false; + } + + log = rec->value.as_vector_bool; + return VECTOR(*log)[eid]; +} + +/** + * \function igraph_cattribute_EAS + * \brief Query a string edge attribute. + * + * Returns a const pointer to the string edge attribute + * specified in \p name. The value must not be modified. + * If the edge attribute does not exist, a warning is issued and + * an empty string is returned. See \ref igraph_cattribute_EASV() for + * an error-checked version. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \se \ref EAS if you want to type less. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +const char *igraph_cattribute_EAS(const igraph_t *graph, const char *name, + igraph_int_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + const igraph_attribute_record_t *rec; + const igraph_strvector_t *str; + + rec = igraph_i_cattribute_find(&attr->eal, name, IGRAPH_ATTRIBUTE_STRING); + if (!rec) { + IGRAPH_WARNINGF("Edge attribute '%s' does not exist, returning default string attribute value.", name); + return ""; + } + + str = rec->value.as_strvector; + return igraph_strvector_get(str, eid); +} + +/** + * \function igraph_cattribute_VANV + * \brief Query a numeric vertex attribute for many vertices. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + */ + +igraph_error_t igraph_cattribute_VANV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_t *result) { + igraph_vector_clear(result); + return igraph_i_cattribute_get_numeric_vertex_attr(graph, name, vids, result); +} + +/** + * \function igraph_cattribute_VABV + * \brief Query a boolean vertex attribute for many vertices. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized boolean vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + */ + +igraph_error_t igraph_cattribute_VABV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_bool_t *result) { + igraph_vector_bool_clear(result); + return igraph_i_cattribute_get_bool_vertex_attr(graph, name, vids, result); +} + +/** + * \function igraph_cattribute_EANV + * \brief Query a numeric edge attribute for many edges. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eids The edges to query. + * \param result Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in 'eids'. + */ + +igraph_error_t igraph_cattribute_EANV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_t *result) { + igraph_vector_clear(result); + return igraph_i_cattribute_get_numeric_edge_attr(graph, name, eids, result); +} + +/** + * \function igraph_cattribute_EABV + * \brief Query a boolean edge attribute for many edges. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eids The edges to query. + * \param result Pointer to an initialized boolean vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in 'eids'. + */ + +igraph_error_t igraph_cattribute_EABV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_bool_t *result) { + igraph_vector_bool_clear(result); + return igraph_i_cattribute_get_bool_edge_attr(graph, name, eids, result); +} + +/** + * \function igraph_cattribute_VASV + * \brief Query a string vertex attribute for many vertices. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized string vector, the result + * is stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + * (We assume that the string attributes have a bounded length.) + */ + +igraph_error_t igraph_cattribute_VASV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_strvector_t *result) { + igraph_strvector_clear(result); + return igraph_i_cattribute_get_string_vertex_attr(graph, name, vids, result); +} + +/** + * \function igraph_cattribute_EASV + * \brief Query a string edge attribute for many edges. + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eids The edges to query. + * \param result Pointer to an initialized string vector, the result + * is stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in + * 'eids'. (We assume that the string attributes have a bounded length.) + */ + +igraph_error_t igraph_cattribute_EASV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_strvector_t *result) { + igraph_strvector_clear(result); + return igraph_i_cattribute_get_string_edge_attr(graph, name, eids, result); +} + +/** + * \function igraph_cattribute_list + * \brief List all attributes. + * + * See \ref igraph_attribute_type_t for the various attribute types. + * \param graph The input graph. + * \param gnames String vector, the names of the graph attributes. + * \param gtypes Numeric vector, the types of the graph attributes. + * \param vnames String vector, the names of the vertex attributes. + * \param vtypes Numeric vector, the types of the vertex attributes. + * \param enames String vector, the names of the edge attributes. + * \param etypes Numeric vector, the types of the edge attributes. + * \return Error code. + * + * Naturally, the string vector with the attribute names and the + * numeric vector with the attribute types are in the right order, + * i.e. the first name corresponds to the first type, etc. + * + * Time complexity: O(Ag+Av+Ae), the number of all attributes. + */ +igraph_error_t igraph_cattribute_list(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_int_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_int_t *vtypes, + igraph_strvector_t *enames, igraph_vector_int_t *etypes) { + return igraph_i_cattribute_get_info(graph, gnames, gtypes, vnames, vtypes, + enames, etypes); +} + +/** + * \function igraph_cattribute_has_attr + * \brief Checks whether a (graph, vertex or edge) attribute exists. + * + * \param graph The graph. + * \param type The type of the attribute, \c IGRAPH_ATTRIBUTE_GRAPH, + * \c IGRAPH_ATTRIBUTE_VERTEX or \c IGRAPH_ATTRIBUTE_EDGE. + * \param name Character constant, the name of the attribute. + * \return Boolean value, \c true if the attribute exists, \c false otherwise. + * + * Time complexity: O(A), the number of (graph, vertex or edge) + * attributes, assuming attribute names are not too long. + */ +igraph_bool_t igraph_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + return igraph_i_cattribute_has_attr(graph, type, name); +} + +/** + * \function igraph_cattribute_GAN_set + * \brief Set a numeric graph attribute. + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. + * \return Error code. + * + * \se \ref SETGAN if you want to type less. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_cattribute_GAN_set(igraph_t *graph, const char *name, + igraph_real_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->gal, name, IGRAPH_ATTRIBUTE_NUMERIC, 1, &rec + )); + + num = rec->value.as_vector; + VECTOR(*num)[0] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_GAB_set + * \brief Set a boolean graph attribute. + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. + * \return Error code. + * + * \se \ref SETGAN if you want to type less. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_cattribute_GAB_set(igraph_t *graph, const char *name, + igraph_bool_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->gal, name, IGRAPH_ATTRIBUTE_BOOLEAN, 1, &rec + )); + + log = rec->value.as_vector_bool; + VECTOR(*log)[0] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_GAS_set + * \brief Set a string graph attribute. + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. It will be + * copied. + * \return Error code. + * + * \se \ref SETGAS if you want to type less. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_cattribute_GAS_set(igraph_t *graph, const char *name, + const char *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->gal, name, IGRAPH_ATTRIBUTE_STRING, 1, &rec + )); + + str = rec->value.as_strvector; + IGRAPH_CHECK(igraph_strvector_set(str, 0, value)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAN_set + * \brief Set a numeric vertex attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAN for a simpler way. + * + * Time complexity: O(n), the number of vertices if the attribute is + * new, O(|vid|) otherwise. + */ +igraph_error_t igraph_cattribute_VAN_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_real_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->val, name, IGRAPH_ATTRIBUTE_NUMERIC, igraph_vcount(graph), &rec + )); + VECTOR(*rec->value.as_vector)[vid] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAB_set + * \brief Set a boolean vertex attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAB for a simpler way. + * + * Time complexity: O(n), the number of vertices if the attribute is + * new, O(|vid|) otherwise. + */ +igraph_error_t igraph_cattribute_VAB_set(igraph_t *graph, const char *name, + igraph_int_t vid, igraph_bool_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->val, name, IGRAPH_ATTRIBUTE_BOOLEAN, igraph_vcount(graph), &rec + )); + VECTOR(*rec->value.as_vector_bool)[vid] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAS_set + * \brief Set a string vertex attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAS for a simpler way. + * + * Time complexity: O(n*l), n is the number of vertices, l is the + * length of the string to set. If the attribute if not new then only + * O(|vid|*l). + */ +igraph_error_t igraph_cattribute_VAS_set(igraph_t *graph, const char *name, + igraph_int_t vid, const char *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->val, name, IGRAPH_ATTRIBUTE_STRING, igraph_vcount(graph), &rec + )); + IGRAPH_CHECK(igraph_strvector_set(rec->value.as_strvector, vid, value)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAN_set + * \brief Set a numeric edge attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAN for a simpler way. + * + * Time complexity: O(e), the number of edges if the attribute is + * new, O(|eid|) otherwise. + */ +igraph_error_t igraph_cattribute_EAN_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_real_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->eal, name, IGRAPH_ATTRIBUTE_NUMERIC, igraph_ecount(graph), &rec + )); + VECTOR(*rec->value.as_vector)[eid] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAB_set + * \brief Set a boolean edge attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAB for a simpler way. + * + * Time complexity: O(e), the number of edges if the attribute is + * new, O(|eid|) otherwise. + */ +igraph_error_t igraph_cattribute_EAB_set(igraph_t *graph, const char *name, + igraph_int_t eid, igraph_bool_t value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->eal, name, IGRAPH_ATTRIBUTE_BOOLEAN, igraph_ecount(graph), &rec + )); + VECTOR(*rec->value.as_vector_bool)[eid] = value; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAS_set + * \brief Set a string edge attribute. + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAS for a simpler way. + * + * Time complexity: O(e*l), n is the number of edges, l is the + * length of the string to set. If the attribute if not new then only + * O(|eid|*l). + */ +igraph_error_t igraph_cattribute_EAS_set(igraph_t *graph, const char *name, + igraph_int_t eid, const char *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create( + &attr->eal, name, IGRAPH_ATTRIBUTE_STRING, igraph_ecount(graph), &rec + )); + IGRAPH_CHECK(igraph_strvector_set(rec->value.as_strvector, eid, value)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAN_setv + * \brief Set a numeric vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVANV for a simpler way. + * + * Time complexity: O(n), the number of vertices. + */ + +igraph_error_t igraph_cattribute_VAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t nv = igraph_vcount(graph); + + /* Check length first */ + if (igraph_vector_size(v) != nv) { + IGRAPH_ERROR("Invalid vertex attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->val, name, IGRAPH_ATTRIBUTE_NUMERIC, nv, &rec)); + IGRAPH_CHECK(igraph_vector_update(rec->value.as_vector, v)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAB_setv + * \brief Set a boolean vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this boolean vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVANV for a simpler way. + * + * Time complexity: O(n), the number of vertices. + */ + +igraph_error_t igraph_cattribute_VAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t nv = igraph_vcount(graph); + + /* Check length first */ + if (igraph_vector_bool_size(v) != nv) { + IGRAPH_ERROR("Invalid vertex attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->val, name, IGRAPH_ATTRIBUTE_BOOLEAN, nv, &rec)); + IGRAPH_CHECK(igraph_vector_bool_update(rec->value.as_vector_bool, v)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_VAS_setv + * \brief Set a string vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param sv String vector, the new attribute values. The length of this vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVASV for a simpler way. + * + * Time complexity: O(n+l), n is the number of vertices, l is the + * total length of the strings. + */ +igraph_error_t igraph_cattribute_VAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t nv = igraph_vcount(graph); + + /* Check length first */ + if (igraph_strvector_size(sv) != nv) { + IGRAPH_ERROR("Invalid vertex attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->val, name, IGRAPH_ATTRIBUTE_STRING, nv, &rec)); + IGRAPH_CHECK(igraph_strvector_update(rec->value.as_strvector, sv)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAN_setv + * \brief Set a numeric edge attribute for all edges. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEANV for a simpler way. + * + * Time complexity: O(e), the number of edges. + */ +igraph_error_t igraph_cattribute_EAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t ne = igraph_ecount(graph); + + /* Check length first */ + if (igraph_vector_size(v) != ne) { + IGRAPH_ERROR("Invalid edge attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->eal, name, IGRAPH_ATTRIBUTE_NUMERIC, ne, &rec)); + IGRAPH_CHECK(igraph_vector_update(rec->value.as_vector, v)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAB_setv + * \brief Set a boolean edge attribute for all edges. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEABV for a simpler way. + * + * Time complexity: O(e), the number of edges. + */ +igraph_error_t igraph_cattribute_EAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t ne = igraph_ecount(graph); + + /* Check length first */ + if (igraph_vector_bool_size(v) != ne) { + IGRAPH_ERROR("Invalid edge attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->eal, name, IGRAPH_ATTRIBUTE_BOOLEAN, ne, &rec)); + IGRAPH_CHECK(igraph_vector_bool_update(rec->value.as_vector_bool, v)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_EAS_setv + * \brief Set a string edge attribute for all edges. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param sv String vector, the new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEASV for a simpler way. + * + * Time complexity: O(e+l), e is the number of edges, l is the + * total length of the strings. + */ +igraph_error_t igraph_cattribute_EAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_t *rec; + igraph_int_t ne = igraph_ecount(graph); + + /* Check length first */ + if (igraph_strvector_size(sv) != ne) { + IGRAPH_ERROR("Invalid edge attribute vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_cattribute_find_or_create(&attr->eal, name, IGRAPH_ATTRIBUTE_STRING, ne, &rec)); + IGRAPH_CHECK(igraph_strvector_update(rec->value.as_strvector, sv)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_cattribute_remove_g + * \brief Remove a graph attribute. + * + * \param graph The graph object. + * \param name Name of the graph attribute to remove. + * + * \sa \ref DELGA for a simpler way. + * + */ +void igraph_cattribute_remove_g(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *gal = &attr->gal; + igraph_int_t j = igraph_i_cattribute_find_index(gal, name); + + if (j >= 0) { + igraph_attribute_record_list_discard(gal, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_v + * \brief Remove a vertex attribute. + * + * \param graph The graph object. + * \param name Name of the vertex attribute to remove. + * + * \sa \ref DELVA for a simpler way. + * + */ +void igraph_cattribute_remove_v(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *val = &attr->val; + igraph_int_t j = igraph_i_cattribute_find_index(val, name); + + if (j >= 0) { + igraph_attribute_record_list_discard(val, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_e + * \brief Remove an edge attribute. + * + * \param graph The graph object. + * \param name Name of the edge attribute to remove. + * + * \sa \ref DELEA for a simpler way. + */ +void igraph_cattribute_remove_e(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_attribute_record_list_t *eal = &attr->eal; + igraph_int_t j = igraph_i_cattribute_find_index(eal, name); + + if (j >= 0) { + igraph_attribute_record_list_discard(eal, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_all + * \brief Remove all graph/vertex/edge attributes. + * + * \param graph The graph object. + * \param g Boolean, whether to remove graph attributes. + * \param v Boolean, whether to remove vertex attributes. + * \param e Boolean, whether to remove edge attributes. + * + * \sa \ref DELGAS, \ref DELVAS, \ref DELEAS, \ref DELALL for simpler + * ways. + */ +void igraph_cattribute_remove_all(igraph_t *graph, igraph_bool_t g, + igraph_bool_t v, igraph_bool_t e) { + + igraph_i_cattributes_t *attr = graph->attr; + + if (g) { + igraph_attribute_record_list_clear(&attr->gal); + } + if (v) { + igraph_attribute_record_list_clear(&attr->val); + } + if (e) { + igraph_attribute_record_list_clear(&attr->eal); + } +} diff --git a/src/graph/graph_list.c b/src/graph/graph_list.c new file mode 100644 index 0000000..e2afe84 --- /dev/null +++ b/src/graph/graph_list.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_graph_list.h" + +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_types.h" + +#define GRAPH_LIST +#define BASE_GRAPH +#define CUSTOM_INIT_DESTROY +#include "igraph_pmt.h" +#include "core/typed_list.pmt" +#include "igraph_pmt_off.h" +#undef CUSTOM_INIT_DESTROY +#undef BASE_GRAPH +#undef GRAPH_LIST + +void igraph_graph_list_set_directed( + igraph_graph_list_t* list, igraph_bool_t directed +) { + IGRAPH_ASSERT(list != 0); + list->directed = directed; +} + +static igraph_error_t igraph_i_graph_list_init_item( + const igraph_graph_list_t* list, igraph_t* item +) { + return igraph_empty(item, 0, list->directed); +} + +static igraph_error_t igraph_i_graph_list_copy_item( + igraph_t* dest, const igraph_t* source +) { + return igraph_copy(dest, source); +} + +static void igraph_i_graph_list_destroy_item(igraph_t* item) { + igraph_destroy(item); +} diff --git a/src/graph/internal.h b/src/graph/internal.h new file mode 100644 index 0000000..7acea1f --- /dev/null +++ b/src/graph/internal.h @@ -0,0 +1,32 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_GRAPH_INTERNAL_H +#define IGRAPH_GRAPH_INTERNAL_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_reverse(igraph_t *graph); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_GRAPH_INTERNAL_H */ diff --git a/src/graph/iterators.c b/src/graph/iterators.c new file mode 100644 index 0000000..d557b30 --- /dev/null +++ b/src/graph/iterators.c @@ -0,0 +1,2048 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_types.h" + +#include +#include + +/** + * \section about_iterators About selectors, iterators + * + * Everything about vertices and vertex selectors also applies + * to edges and edge selectors unless explicitly noted otherwise. + * + * The vertex (and edge) selector notion was introduced in igraph 0.2. + * It is a way to reference a sequence of vertices or edges + * independently of the graph. + * + * While this might sound quite mysterious, it is actually very + * simple. For example, all vertices of a graph can be selected by + * \ref igraph_vs_all() and the graph independence means that + * \ref igraph_vs_all() is not parametrized by a graph object. That is, + * \ref igraph_vs_all() is the general \em concept of selecting all vertices + * of a graph. A vertex selector is then a way to specify the class of vertices + * to be visited. The selector might specify that all vertices of a graph or + * all the neighbours of a vertex are to be visited. A vertex selector is a + * way of saying that you want to visit a bunch of vertices, as opposed to a + * vertex iterator which is a concrete plan for visiting each of the + * chosen vertices of a specific graph. + * + * To determine the actual vertex IDs implied by a vertex selector, you + * need to apply the concept of selecting vertices to a specific graph object. + * This can be accomplished by instantiating a vertex iterator using a + * specific vertex selection concept and a specific graph object. The notion + * of vertex iterators can be thought of in the following way. Given a + * specific graph object and the class of vertices to be visited, a vertex + * iterator is a road map, plan or route for how to visit the chosen + * vertices. + * + * Some vertex selectors have \em immediate versions. These have the + * prefix \c igraph_vss instead of \c igraph_vs, e.g. \ref igraph_vss_all() + * instead of \ref igraph_vs_all(). The immediate versions are to be used in + * the parameter list of the igraph functions, such as \ref igraph_degree(). + * These functions are not associated with any \type igraph_vs_t object, so + * they have no separate constructors and destructors + * (destroy functions). + */ + +/** + * \section about_vertex_selectors + * + * Vertex selectors are created by vertex selector constructors, + * can be instantiated with \ref igraph_vit_create(), and are + * destroyed with \ref igraph_vs_destroy(). + */ + +/** + * \function igraph_vs_all + * \brief Vertex set, all vertices of a graph. + * + * \param vs Pointer to an uninitialized \type igraph_vs_t object. + * \return Error code. + * \sa \ref igraph_vss_all(), \ref igraph_vs_destroy() + * + * This selector includes all vertices of a given graph in + * increasing vertex ID order. + * + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_vs_all(igraph_vs_t *vs) { + vs->type = IGRAPH_VS_ALL; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vss_all + * \brief All vertices of a graph (immediate version). + * + * Immediate vertex selector for all vertices in a graph. It can + * be used conveniently when some vertex property (e.g. betweenness, + * degree, etc.) should be calculated for all vertices. + * + * \return A vertex selector for all vertices in a graph. + * \sa \ref igraph_vs_all() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_all(void) { + igraph_vs_t allvs; + allvs.type = IGRAPH_VS_ALL; + return allvs; +} + +/** + * \function igraph_vs_adj + * \brief Adjacent vertices of a vertex. + * + * All neighboring vertices of a given vertex are selected by this + * selector. The \c mode argument controls the type of the neighboring + * vertices to be selected. The vertices are visited in increasing vertex + * ID order, as of igraph version 0.4. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid Vertex ID, the center of the neighborhood. + * \param mode Decides the type of the neighborhood for directed + * graphs. This parameter is ignored for undirected graphs. + * Possible values: + * \clist + * \cli IGRAPH_OUT + * All vertices to which there is a directed edge from \c vid. That + * is, all the out-neighbors of \c vid. + * \cli IGRAPH_IN + * All vertices from which there is a directed edge to \c vid. In + * other words, all the in-neighbors of \c vid. + * \cli IGRAPH_ALL + * All vertices to which or from which there is a directed edge + * from/to \c vid. That is, all the neighbors of \c vid considered + * as if the graph is undirected. + * \endclist + * \param loops Whether to include the vertex itself in the neighborhood if the + * vertex has a loop edge. If \c IGRAPH_NO_LOOPS, loop edges are + * excluded. If \c IGRAPH_LOOPS_ONCE, the vertex is included in its own + * neighborhood once for every loop edge that it has. If + * \c IGRAPH_LOOPS_TWICE, the vertex is included twice in its own + * neighborhood for every loop edge that it has, but only if the graph is + * undirected or \p mode is set to \c IGRAPH_ALL. + * \param multiple Whether to include multiple edges. If \c IGRAPH_NO_MULTIPLE, + * multiple edges are not included in the neighborhood. If + * \c IGRAPH_MULTIPLE, multiple edges are included in the neighborhood. + * + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_vs_adj( + igraph_vs_t *vs, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops, igraph_bool_t multiple +) { + vs->type = IGRAPH_VS_ADJ; + vs->data.adj.vid = vid; + vs->data.adj.mode = mode; + vs->data.adj.loops = loops; + vs->data.adj.multiple = multiple; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_nonadj + * \brief Non-adjacent vertices of a vertex. + * + * All non-neighboring vertices of a given vertex. The \p mode + * argument controls the type of neighboring vertices \em not to + * select. Instead of selecting immediate neighbors of \c vid as is done by + * \ref igraph_vs_adj(), the current function selects vertices that are \em not + * immediate neighbors of \c vid. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid Vertex ID, the \quote center \endquote of the + * non-neighborhood. + * \param mode The type of neighborhood not to select in directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * All vertices will be selected except those to which there is a + * directed edge from \c vid. That is, we select all vertices + * excluding the out-neighbors of \c vid. + * \cli IGRAPH_IN + * All vertices will be selected except those from which there is a + * directed edge to \c vid. In other words, we select all vertices + * but the in-neighbors of \c vid. + * \cli IGRAPH_ALL + * All vertices will be selected except those from or to which there + * is a directed edge to or from \c vid. That is, we select all + * vertices of \c vid except for its immediate neighbors. + * \endclist + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_nonadj.c + */ + +igraph_error_t igraph_vs_nonadj(igraph_vs_t *vs, igraph_int_t vid, + igraph_neimode_t mode) { + vs->type = IGRAPH_VS_NONADJ; + vs->data.adj.vid = vid; + vs->data.adj.mode = mode; + vs->data.adj.loops = IGRAPH_LOOPS; + vs->data.adj.multiple = IGRAPH_MULTIPLE; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_none + * \brief Empty vertex set. + * + * Creates an empty vertex selector. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \return Error code. + * \sa \ref igraph_vss_none(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_vs_none(igraph_vs_t *vs) { + vs->type = IGRAPH_VS_NONE; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vss_none + * \brief Empty vertex set (immediate version). + * + * The immediate version of the empty vertex selector. + * + * \return An empty vertex selector. + * \sa \ref igraph_vs_none() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_none(void) { + igraph_vs_t nonevs; + nonevs.type = IGRAPH_VS_NONE; + return nonevs; +} + +/** + * \function igraph_vs_1 + * \brief Vertex set with a single vertex. + * + * This vertex selector selects a single vertex. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid The vertex ID to be selected. + * \return Error Code. + * \sa \ref igraph_vss_1(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_vs_1(igraph_vs_t *vs, igraph_int_t vid) { + vs->type = IGRAPH_VS_1; + vs->data.vid = vid; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vss_1 + * \brief Vertex set with a single vertex (immediate version). + * + * The immediate version of the single-vertex selector. + * + * \param vid The vertex to be selected. + * \return A vertex selector containing a single vertex. + * \sa \ref igraph_vs_1() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_1(igraph_int_t vid) { + igraph_vs_t onevs; + onevs.type = IGRAPH_VS_1; + onevs.data.vid = vid; + return onevs; +} + +/** + * \function igraph_vs_vector + * \brief Vertex set based on a vector. + * + * This function makes it possible to handle an \type igraph_vector_int_t + * temporarily as a vertex selector. The vertex selector should be + * thought of as a \em view into the vector. If you make changes to + * the vector that also affects the vertex selector. Destroying the + * vertex selector does not destroy the vector. Do not destroy the + * vector before destroying the vertex selector, or you might get + * strange behavior. Since selectors are not tied to any specific + * graph, this function does not check whether the vertex IDs in + * the vector are valid. + * + * \param vs Pointer to an uninitialized vertex selector. + * \param v Pointer to a \type igraph_vector_int_t object. + * \return Error code. + * \sa \ref igraph_vss_vector(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_vector.c + */ + +igraph_error_t igraph_vs_vector(igraph_vs_t *vs, + const igraph_vector_int_t *v) { + vs->type = IGRAPH_VS_VECTORPTR; + vs->data.vecptr = v; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vss_vector + * \brief Vertex set based on a vector (immediate version). + * + * This is the immediate version of \ref igraph_vs_vector. + * + * \param v Pointer to a \type igraph_vector_int_t object. + * \return A vertex selector object containing the vertices in the + * vector. + * \sa \ref igraph_vs_vector() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_vector(const igraph_vector_int_t *v) { + igraph_vs_t vecvs; + vecvs.type = IGRAPH_VS_VECTORPTR; + vecvs.data.vecptr = v; + return vecvs; +} + +/** + * \function igraph_vs_vector_small + * \brief Create a vertex set by giving its elements. + * + * This function can be used to create a vertex selector with a few + * of vertices. Do not forget to include a -1 after the + * last vertex ID. The behavior of the function is undefined if you + * don't use a -1 properly. + * + * + * Note that the vertex IDs supplied will be parsed as value of type + * \type int so you cannot supply arbitrarily large (too + * large for \type int) vertex IDs here. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param ... Additional parameters, these will be the vertex IDs to + * be included in the vertex selector. Supply a -1 + * after the last vertex ID. + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(n), the number of vertex IDs supplied. + */ + +igraph_error_t igraph_vs_vector_small(igraph_vs_t *vs, ...) { + va_list ap; + igraph_int_t i, n = 0; + igraph_vector_int_t* vec; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create vertex selector."); + IGRAPH_FINALLY(igraph_free, vec); + + va_start(ap, vs); + while (1) { + int num = va_arg(ap, int); + if (num == -1) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, n); + + va_start(ap, vs); + for (i = 0; i < n; i++) { + VECTOR(*vec)[i] = va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(2); + + vs->type = IGRAPH_VS_VECTOR; + vs->data.vecptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_vector_copy + * \brief Vertex set based on a vector, with copying. + * + * This function makes it possible to handle an \type igraph_vector_int_t + * permanently as a vertex selector. The vertex selector creates a + * copy of the original vector, so the vector can safely be destroyed + * after creating the vertex selector. Changing the original vector + * will not affect the vertex selector. The vertex selector is + * responsible for deleting the copy made by itself. Since selectors + * are not tied to any specific graph, this function does not check whether + * the vertex IDs in the vector are valid. + * + * \param vs Pointer to an uninitialized vertex selector. + * \param v Pointer to a \type igraph_vector_int_t object. + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_vs_vector_copy(igraph_vs_t *vs, const igraph_vector_int_t *v) { + igraph_vector_int_t* vec; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create vertex selector."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, v)); + IGRAPH_FINALLY_CLEAN(1); + + vs->type = IGRAPH_VS_VECTOR; + vs->data.vecptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_range + * \brief Vertex set, an interval of vertices. + * + * Creates a vertex selector containing all vertices with vertex ID + * equal to or bigger than \p from and smaller than \p to. Note that the + * interval is closed from the left and open from the right, following C + * conventions. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param start The first vertex ID to be included in the vertex selector. + * \param end The first vertex ID \em not to be included in the vertex selector. + * \return Error code. + * \sa \ref igraph_vss_range(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_range.c + */ + +igraph_error_t igraph_vs_range(igraph_vs_t *vs, igraph_int_t start, igraph_int_t end) { + *vs = igraph_vss_range(start, end); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vss_range + * \brief An interval of vertices (immediate version). + * + * The immediate version of \ref igraph_vs_range(). + * + * \param start The first vertex ID to be included in the vertex selector. + * \param end The first vertex ID \em not to be included in the vertex selector. + * \return Error code. + * \sa \ref igraph_vs_range() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_range(igraph_int_t start, igraph_int_t end) { + igraph_vs_t vs; + vs.type = IGRAPH_VS_RANGE; + vs.data.range.start = start; + vs.data.range.end = end; + return vs; +} + +/** + * \function igraph_vs_destroy + * \brief Destroy a vertex set. + * + * This function should be called for all vertex selectors when they + * are not needed. The memory allocated for the vertex selector will + * be deallocated. Do not call this function on vertex selectors + * created with the immediate versions of the vertex selector + * constructors (starting with igraph_vss). + * + * \param vs Pointer to a vertex selector object. + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_vs_destroy(igraph_vs_t *vs) { + switch (vs->type) { + case IGRAPH_VS_ALL: + case IGRAPH_VS_ADJ: + case IGRAPH_VS_NONE: + case IGRAPH_VS_1: + case IGRAPH_VS_VECTORPTR: + case IGRAPH_VS_RANGE: + case IGRAPH_VS_NONADJ: + break; + case IGRAPH_VS_VECTOR: + igraph_vector_int_destroy((igraph_vector_int_t*) vs->data.vecptr); + IGRAPH_FREE(vs->data.vecptr); + break; + default: + break; + } +} + +/** + * \function igraph_vs_is_all + * \brief Check whether all vertices are included. + * + * This function checks whether the vertex selector object was created + * by \ref igraph_vs_all() or \ref igraph_vss_all(). Note that the + * vertex selector might contain all vertices in a given graph but if + * it wasn't created by the two constructors mentioned here the return + * value will be \c false. + * + * \param vs Pointer to a vertex selector object. + * \return \c true if the vertex selector contains all vertices and + * \c false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_vs_is_all(const igraph_vs_t *vs) { + return vs->type == IGRAPH_VS_ALL; +} + +igraph_error_t igraph_vs_as_vector(const igraph_t *graph, igraph_vs_t vs, + igraph_vector_int_t *v) { + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vs, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vit_as_vector(&vit, v)); + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_copy + * \brief Creates a copy of a vertex selector. + * + * \param dest An uninitialized selector that will contain the copy. + * \param src The selector being copied. + * \return Error code. + */ +igraph_error_t igraph_vs_copy(igraph_vs_t* dest, const igraph_vs_t* src) { + igraph_vector_int_t *vec; + + memcpy(dest, src, sizeof(igraph_vs_t)); + + switch (dest->type) { + case IGRAPH_VS_VECTOR: + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot copy vertex selector."); + IGRAPH_FINALLY(igraph_free, &vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, src->data.vecptr)); + dest->data.vecptr = vec; + IGRAPH_FINALLY_CLEAN(1); /* ownership of vec taken by 'dest' */ + break; + default: + break; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vs_type + * \brief Returns the type of the vertex selector. + */ +igraph_vs_type_t igraph_vs_type(const igraph_vs_t *vs) { + return vs->type; +} + +/** + * \function igraph_vs_size + * \brief Returns the size of the vertex selector. + * + * The size of the vertex selector is the number of vertices it will + * yield when it is iterated over. + * + * \param graph The graph over which we will iterate. + * \param vs the vertex selector. + * \param result The result will be returned here. + * \return Error code. + */ +igraph_error_t igraph_vs_size(const igraph_t *graph, const igraph_vs_t *vs, + igraph_int_t *result) { + igraph_vector_int_t vec; + igraph_bool_t *seen; + igraph_int_t i; + igraph_int_t vec_len; + + switch (vs->type) { + case IGRAPH_VS_NONE: + *result = 0; return IGRAPH_SUCCESS; + + case IGRAPH_VS_1: + *result = 0; + if (vs->data.vid < igraph_vcount(graph) && vs->data.vid >= 0) { + *result = 1; + } + return IGRAPH_SUCCESS; + + case IGRAPH_VS_RANGE: + *result = vs->data.range.end - vs->data.range.start; + return IGRAPH_SUCCESS; + + case IGRAPH_VS_ALL: + *result = igraph_vcount(graph); return IGRAPH_SUCCESS; + + case IGRAPH_VS_ADJ: + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &vec, vs->data.adj.vid, vs->data.adj.mode, + vs->data.adj.loops, vs->data.adj.multiple + )); + *result = igraph_vector_int_size(&vec); + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + + case IGRAPH_VS_NONADJ: + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &vec, vs->data.adj.vid, vs->data.adj.mode, + vs->data.adj.loops, vs->data.adj.multiple + )); + vec_len = igraph_vector_int_size(&vec); + *result = igraph_vcount(graph); + seen = IGRAPH_CALLOC(*result, igraph_bool_t); + IGRAPH_CHECK_OOM(seen, "Cannot calculate vertex selector length."); + IGRAPH_FINALLY(igraph_free, seen); + for (i = 0; i < vec_len; i++) { + if (!seen[ VECTOR(vec)[i] ]) { + (*result)--; + seen[ VECTOR(vec)[i] ] = true; + } + } + igraph_free(seen); + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; + + case IGRAPH_VS_VECTOR: + case IGRAPH_VS_VECTORPTR: + *result = igraph_vector_int_size(vs->data.vecptr); + return IGRAPH_SUCCESS; + } + + IGRAPH_ERROR("Cannot calculate selector length, invalid selector type", + IGRAPH_EINVAL); +} + +/***************************************************/ + +/** + * \function igraph_vit_create + * \brief Creates a vertex iterator from a vertex selector. + * + * This function instantiates a vertex selector object with a given + * graph. This is the step when the actual vertex IDs are created from + * the \em logical notion of the vertex selector based on the graph. + * E.g. a vertex selector created with \ref igraph_vs_all() contains + * knowledge that \em all vertices are included in a (yet indefinite) + * graph. When instantiating it a vertex iterator object is created, + * this contains the actual vertex IDs in the graph supplied as a + * parameter. + * + * + * The same vertex selector object can be used to instantiate any + * number vertex iterators. + * + * \param graph An \type igraph_t object, a graph. + * \param vs A vertex selector object. + * \param vit Pointer to an uninitialized vertex iterator object. + * \return Error code. + * \sa \ref igraph_vit_destroy(). + * + * Time complexity: it depends on the vertex selector type. O(1) for + * vertex selectors created with \ref igraph_vs_all(), \ref + * igraph_vs_none(), \ref igraph_vs_1, \ref igraph_vs_vector, \ref + * igraph_vs_range(), \ref igraph_vs_vector(), \ref + * igraph_vs_vector_small(). O(d) for \ref igraph_vs_adj(), d is the + * number of vertex IDs to be included in the iterator. O(|V|) for + * \ref igraph_vs_nonadj(), |V| is the number of vertices in the graph. + */ + +igraph_error_t igraph_vit_create(const igraph_t *graph, igraph_vs_t vs, igraph_vit_t *vit) { + igraph_vector_int_t vec; + igraph_vector_int_t *vec_int; + igraph_bool_t *seen; + igraph_int_t i, j, n; + igraph_int_t vec_len; + + switch (vs.type) { + case IGRAPH_VS_ALL: + vit->type = IGRAPH_VIT_RANGE; + vit->pos = 0; + vit->start = 0; + vit->end = igraph_vcount(graph); + break; + case IGRAPH_VS_ADJ: + vec_int = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec_int, "Cannot create vertex iterator."); + IGRAPH_FINALLY(igraph_free, vec_int); + IGRAPH_VECTOR_INT_INIT_FINALLY(vec_int, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &vec, vs.data.adj.vid, vs.data.adj.mode, + vs.data.adj.loops, vs.data.adj.multiple + )); + n = igraph_vector_int_size(&vec); + IGRAPH_CHECK(igraph_vector_int_resize(vec_int, n)); + for (i = 0; i < n; i++) { + VECTOR(*vec_int)[i] = VECTOR(vec)[i]; + } + + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(3); + + vit->type = IGRAPH_VIT_VECTOR; + vit->pos = 0; + vit->start = 0; + vit->vec = vec_int; + vit->end = n; + + break; + case IGRAPH_VS_NONADJ: + vec_int = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec_int, "Cannot create vertex iterator."); + IGRAPH_FINALLY(igraph_free, vec_int); + IGRAPH_VECTOR_INT_INIT_FINALLY(vec_int, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &vec, vs.data.adj.vid, vs.data.adj.mode, + vs.data.adj.loops, vs.data.adj.multiple + )); + vec_len = igraph_vector_int_size(&vec); + n = igraph_vcount(graph); + seen = IGRAPH_CALLOC(n, igraph_bool_t); + IGRAPH_CHECK_OOM(seen, "Cannot create vertex iterator."); + IGRAPH_FINALLY(igraph_free, seen); + for (i = 0; i < vec_len; i++) { + if (! seen [ VECTOR(vec)[i] ] ) { + n--; + seen[ VECTOR(vec)[i] ] = true; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(vec_int, n)); + for (i = 0, j = 0; j < n; i++) { + if (!seen[i]) { + VECTOR(*vec_int)[j++] = i; + } + } + + IGRAPH_FREE(seen); + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(4); + + vit->type = IGRAPH_VIT_VECTOR; + vit->pos = 0; + vit->start = 0; + vit->vec = vec_int; + vit->end = n; + break; + case IGRAPH_VS_NONE: + vit->type = IGRAPH_VIT_RANGE; + vit->pos = 0; + vit->start = 0; + vit->end = 0; + break; + case IGRAPH_VS_1: + vit->type = IGRAPH_VIT_RANGE; + vit->pos = vs.data.vid; + vit->start = vs.data.vid; + vit->end = vs.data.vid + 1; + if (vit->pos >= igraph_vcount(graph)) { + IGRAPH_ERROR("Cannot create iterator, invalid vertex ID.", IGRAPH_EINVVID); + } + break; + case IGRAPH_VS_VECTORPTR: + case IGRAPH_VS_VECTOR: + vit->type = IGRAPH_VIT_VECTORPTR; + vit->pos = 0; + vit->start = 0; + vit->vec = vs.data.vecptr; + vit->end = igraph_vector_int_size(vit->vec); + if (!igraph_vector_int_isininterval(vit->vec, 0, igraph_vcount(graph) - 1)) { + IGRAPH_ERROR("Cannot create iterator, invalid vertex ID.", IGRAPH_EINVVID); + } + break; + case IGRAPH_VS_RANGE: + { + igraph_int_t no_of_nodes = igraph_vcount(graph); + if (vs.data.range.start < 0 || + vs.data.range.start > no_of_nodes || + (no_of_nodes > 0 && vs.data.range.start == no_of_nodes)) { + IGRAPH_ERROR("Cannot create range iterator, starting vertex ID out of range.", IGRAPH_EINVAL); + } + if (vs.data.range.end < 0 || vs.data.range.end > no_of_nodes) { + IGRAPH_ERROR("Cannot create range iterator, ending vertex ID out of range.", IGRAPH_EINVAL); + } + } + vit->type = IGRAPH_VIT_RANGE; + vit->pos = vs.data.range.start; + vit->start = vs.data.range.start; + vit->end = vs.data.range.end; + break; + default: + IGRAPH_ERROR("Cannot create iterator, invalid selector.", IGRAPH_EINVAL); + break; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vit_destroy + * \brief Destroys a vertex iterator. + * + * + * Deallocates memory allocated for a vertex iterator. + * + * \param vit Pointer to an initialized vertex iterator object. + * \sa \ref igraph_vit_create() + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_vit_destroy(const igraph_vit_t *vit) { + switch (vit->type) { + case IGRAPH_VIT_RANGE: + case IGRAPH_VIT_VECTORPTR: + break; + case IGRAPH_VIT_VECTOR: + igraph_vector_int_destroy((igraph_vector_int_t*) vit->vec); + igraph_free((igraph_vector_int_t*) vit->vec); + break; + default: + /* IGRAPH_ERROR("Cannot destroy iterator, unknown type", IGRAPH_EINVAL); */ + break; + } +} + +igraph_error_t igraph_vit_as_vector(const igraph_vit_t *vit, igraph_vector_int_t *v) { + igraph_int_t i; + + IGRAPH_CHECK(igraph_vector_int_resize(v, IGRAPH_VIT_SIZE(*vit))); + + switch (vit->type) { + case IGRAPH_VIT_RANGE: + for (i = 0; i < IGRAPH_VIT_SIZE(*vit); i++) { + VECTOR(*v)[i] = vit->start + i; + } + break; + case IGRAPH_VIT_VECTOR: + case IGRAPH_VIT_VECTORPTR: + for (i = 0; i < IGRAPH_VIT_SIZE(*vit); i++) { + VECTOR(*v)[i] = VECTOR(*vit->vec)[i]; + } + break; + default: + IGRAPH_ERROR("Cannot convert to vector, unknown iterator type", + IGRAPH_EINVAL); + break; + } + + return IGRAPH_SUCCESS; +} + +/*******************************************************/ + +/** + * \function igraph_es_all + * \brief Edge set, all edges. + * + * \param es Pointer to an uninitialized edge selector object. + * \param order Constant giving the order in which the edges will be + * included in the selector. Possible values: + * \clist + * \cli IGRAPH_EDGEORDER_ID + * Edge ID order; currently performs the fastest. + * \cli IGRAPH_EDGEORDER_FROM + * Vertex ID order, the id of the \em source vertex counts for directed + * graphs. The order of the incident edges of a given vertex is arbitrary. + * \cli IGRAPH_EDGEORDER_TO + * Vertex ID order, the ID of the \em target vertex counts for directed + * graphs. The order of the incident edges of a given vertex is arbitrary. + * \endclist + * For undirected graph the latter two is the same. + * \return Error code. + * \sa \ref igraph_ess_all(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_all(igraph_es_t *es, + igraph_edgeorder_type_t order) { + switch (order) { + case IGRAPH_EDGEORDER_ID: + es->type = IGRAPH_ES_ALL; + break; + case IGRAPH_EDGEORDER_FROM: + es->type = IGRAPH_ES_ALLFROM; + break; + case IGRAPH_EDGEORDER_TO: + es->type = IGRAPH_ES_ALLTO; + break; + default: + IGRAPH_ERROR("Invalid edge order, cannot create selector.", IGRAPH_EINVAL); + break; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_ess_all + * \brief Edge set, all edges (immediate version). + * + * The immediate version of the all-edges selector. + * + * \param order Constant giving the order of the edges in the edge + * selector. See \ref igraph_es_all() for the possible values. + * \return The edge selector. + * \sa \ref igraph_es_all() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_all(igraph_edgeorder_type_t order) { + igraph_es_t es; + igraph_es_all(&es, order); /* cannot fail */ + return es; +} + +/** + * \function igraph_es_incident + * \brief Edges incident on a given vertex. + * + * \param es Pointer to an uninitialized edge selector object. + * \param vid Vertex ID, of which the incident edges will be + * selected. + * \param mode Constant giving the type of the incident edges to + * select. This is ignored for undirected graphs. Possible values: + * \c IGRAPH_OUT, outgoing edges; + * \c IGRAPH_IN, incoming edges; + * \c IGRAPH_ALL, all edges. + * \param loops Whether to include loop edges in the result. If + * \c IGRAPH_NO_LOOPS, loop edges are excluded. If \c IGRAPH_LOOPS_ONCE, + * loop edges are included once. If \c IGRAPH_LOOPS_TWICE, loop edges + * are included twice, but only if the graph is undirected or \p mode is + * set to \c IGRAPH_ALL. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_incident( + igraph_es_t *es, igraph_int_t vid, igraph_neimode_t mode, + igraph_loops_t loops +) { + es->type = IGRAPH_ES_INCIDENT; + es->data.incident.vid = vid; + es->data.incident.mode = mode; + es->data.incident.loops = loops; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_none + * \brief Empty edge selector. + * + * \param es Pointer to an uninitialized edge selector object to + * initialize. + * \return Error code. + * \sa \ref igraph_ess_none(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_none(igraph_es_t *es) { + es->type = IGRAPH_ES_NONE; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_ess_none + * \brief Immediate empty edge selector. + * + * + * Immediate version of the empty edge selector. + * + * \return Initialized empty edge selector. + * \sa \ref igraph_es_none() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_none(void) { + igraph_es_t es; + es.type = IGRAPH_ES_NONE; + return es; +} + +/** + * \function igraph_es_1 + * \brief Edge selector containing a single edge. + * + * \param es Pointer to an uninitialized edge selector object. + * \param eid Edge ID of the edge to select. + * \return Error code. + * \sa \ref igraph_ess_1(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_1(igraph_es_t *es, igraph_int_t eid) { + es->type = IGRAPH_ES_1; + es->data.eid = eid; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_ess_1 + * \brief Immediate version of the single edge edge selector. + * + * \param eid The ID of the edge. + * \return The edge selector. + * \sa \ref igraph_es_1() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_1(igraph_int_t eid) { + igraph_es_t es; + es.type = IGRAPH_ES_1; + es.data.eid = eid; + return es; +} + +/** + * \function igraph_es_vector + * \brief Handle a vector as an edge selector. + * + * Creates an edge selector which serves as a view into a vector + * containing edge IDs. Do not destroy the vector before destroying + * the edge selector. Since selectors are not tied to any specific + * graph, this function does not check whether the edge IDs in + * the vector are valid. + * + * \param es Pointer to an uninitialized edge selector. + * \param v Vector containing edge IDs. + * \return Error code. + * \sa \ref igraph_ess_vector(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_vector(igraph_es_t *es, const igraph_vector_int_t *v) { + es->type = IGRAPH_ES_VECTORPTR; + es->data.vecptr = v; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_vector_copy + * \brief Edge set, based on a vector, with copying. + * + * This function makes it possible to handle an \type igraph_vector_int_t + * permanently as an edge selector. The edge selector creates a + * copy of the original vector, so the vector can safely be destroyed + * after creating the edge selector. Changing the original vector + * will not affect the edge selector. The edge selector is + * responsible for deleting the copy made by itself. Since selectors + * are not tied to any specific graph, this function does not check + * whether the edge IDs in the vector are valid. + * + * \param es Pointer to an uninitialized edge selector. + * \param v Pointer to a \type igraph_vector_int_t object. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_vector_copy(igraph_es_t *es, const igraph_vector_int_t *v) { + igraph_vector_int_t* vec; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge selector."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, v)); + IGRAPH_FINALLY_CLEAN(1); + + es->type = IGRAPH_ES_VECTOR; + es->data.vecptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_ess_vector + * \brief Immediate vector view edge selector. + * + * This is the immediate version of the vector of edge IDs edge + * selector. + * + * \param v The vector of edge IDs. + * \return Edge selector, initialized. + * \sa \ref igraph_es_vector() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_vector(const igraph_vector_int_t *v) { + igraph_es_t es; + es.type = IGRAPH_ES_VECTORPTR; + es.data.vecptr = v; + return es; +} + +/** + * \function igraph_es_range + * \brief Edge selector, a sequence of edge IDs. + * + * Creates an edge selector containing all edges with edge ID + * equal to or bigger than \p from and smaller than \p to. Note that the + * interval is closed from the left and open from the right, following C + * conventions. + * + * \param es Pointer to an uninitialized edge selector object. + * \param start The first edge ID to be included in the edge selector. + * \param end The first edge ID \em not to be included in the edge selector. + * \return Error code. + * \sa \ref igraph_ess_range(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_es_range(igraph_es_t *es, igraph_int_t start, igraph_int_t end) { + *es = igraph_ess_range(start, end); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_ess_range + * \brief Immediate version of the sequence edge selector. + * + * \param start The first edge ID to be included in the edge selector. + * \param end The first edge ID \em not to be included in the edge selector. + * \return The initialized edge selector. + * \sa \ref igraph_es_range() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_range(igraph_int_t start, igraph_int_t end) { + igraph_es_t es; + es.type = IGRAPH_ES_RANGE; + es.data.range.start = start; + es.data.range.end = end; + return es; +} + +/** + * \function igraph_es_pairs + * \brief Edge selector, multiple edges defined by their endpoints in a vector. + * + * The edges between the given pairs of vertices will be included in the + * edge selection. The vertex pairs must be defined in the vector v, + * the first element of the vector is the first vertex of the first edge + * to be selected, the second element is the second vertex of the first + * edge, the third element is the first vertex of the second edge and + * so on. + * + * \param es Pointer to an uninitialized edge selector object. + * \param v The vector containing the endpoints of the edges. + * \param directed Whether the graph is directed or not. + * \return Error code. + * \sa \ref igraph_es_pairs_small(), \ref igraph_es_destroy() + * + * Time complexity: O(n), the number of edges being selected. + * + * \example examples/simple/igraph_es_pairs.c + */ + +igraph_error_t igraph_es_pairs(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed) { + igraph_vector_int_t* vec; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge selector."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, v)); + IGRAPH_FINALLY_CLEAN(1); + + es->type = IGRAPH_ES_PAIRS; + es->data.path.mode = directed; + es->data.path.ptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_pairs_small + * \brief Edge selector, multiple edges defined by their endpoints as arguments. + * + * The edges between the given pairs of vertices will be included in the + * edge selection. The vertex pairs must be given as the arguments of the + * function call, the third argument is the first vertex of the first edge, + * the fourth argument is the second vertex of the first edge, the fifth + * is the first vertex of the second edge and so on. The last element of the + * argument list must be -1 to denote the end of the argument list. + * + * + * Note that the vertex IDs supplied will be parsed as + * int's so you cannot supply arbitrarily large (too + * large for int) vertex IDs here. + * + * \param es Pointer to an uninitialized edge selector object. + * \param directed Whether the graph is directed or not. + * \param ... The additional arguments give the edges to be included in the + * selector, as pairs of vertex IDs. The last argument must be -1. + * The \p first parameter is present for technical reasons and represents + * the first variadic argument. + * \return Error code. + * \sa \ref igraph_es_pairs(), \ref igraph_es_destroy() + * + * Time complexity: O(n), the number of edges being selected. + */ + +igraph_error_t igraph_es_pairs_small(igraph_es_t *es, igraph_bool_t directed, int first, ...) { + va_list ap; + igraph_int_t i, n = 0; + igraph_vector_int_t *vec; + int num; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge selector."); + IGRAPH_FINALLY(igraph_free, vec); + + va_start(ap, first); + num = first; + while (num != -1) { + n++; + num = va_arg(ap, int); + } + va_end(ap); + + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, n); + + if (n > 0) { + va_start(ap, first); + VECTOR(*vec)[0] = first; + for (i = 1; i < n; i++) { + VECTOR(*vec)[i] = va_arg(ap, int); + } + va_end(ap); + } + + IGRAPH_FINALLY_CLEAN(2); + + es->type = IGRAPH_ES_PAIRS; + es->data.path.mode = directed; + es->data.path.ptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_path + * \brief Edge selector, edge IDs on a path. + * + * This function takes a vector of vertices and creates a selector of + * edges between those vertices. Vector {0, 3, 4, 7} will select edges + * (0 -> 3), (3 -> 4), (4 -> 7). If these edges don't exist then trying + * to create an iterator using this selector will fail. + * + * \param es Pointer to an uninitialized edge selector object. + * \param v Pointer to a vector of vertex IDs along the path. + * \param directed If edge directions should be taken into account. This + * will be ignored if the graph to select from is undirected. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(n), the number of vertices. + */ +igraph_error_t igraph_es_path(igraph_es_t *es, const igraph_vector_int_t *v, + igraph_bool_t directed) { + igraph_vector_int_t *vec; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge selector."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, v)); + IGRAPH_FINALLY_CLEAN(1); + + es->type = IGRAPH_ES_PATH; + es->data.path.mode = directed; + es->data.path.ptr = vec; + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_es_path_small(igraph_es_t *es, igraph_bool_t directed, int first, ...) { + va_list ap; + igraph_int_t i, n = 0; + igraph_vector_int_t *vec; + int num; + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge selector."); + IGRAPH_FINALLY(igraph_free, vec); + + va_start(ap, first); + num = first; + while (num != -1) { + n++; + num = va_arg(ap, int); + } + va_end(ap); + + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, n); + + if (n > 0) { + va_start(ap, first); + VECTOR(*vec)[0] = first; + for (i = 1; i < n; i++) { + VECTOR(*vec)[i] = va_arg(ap, int); + } + va_end(ap); + } + + IGRAPH_FINALLY_CLEAN(2); + + es->type = IGRAPH_ES_PATH; + es->data.path.mode = directed; + es->data.path.ptr = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_all_between + * \brief Edge selector, all edge IDs between a pair of vertices. + * + * This function takes a pair of vertices and creates a selector that matches + * all edges between those vertices. + * + * \param es Pointer to an uninitialized edge selector object. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param directed If edge directions should be taken into account. This + * will be ignored if the graph to select from is undirected. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ +igraph_error_t igraph_es_all_between( + igraph_es_t *es, igraph_int_t from, igraph_int_t to, + igraph_bool_t directed +) { + es->type = IGRAPH_ES_ALL_BETWEEN; + es->data.between.from = from; + es->data.between.to = to; + es->data.between.directed = directed; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_destroy + * \brief Destroys an edge selector object. + * + * Call this function on an edge selector when it is not needed any + * more. Do \em not call this function on edge selectors created by + * immediate constructors, those don't need to be destroyed. + * + * \param es Pointer to an edge selector object. + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_es_destroy(igraph_es_t *es) { + switch (es->type) { + case IGRAPH_ES_ALL: + case IGRAPH_ES_ALLFROM: + case IGRAPH_ES_ALLTO: + case IGRAPH_ES_INCIDENT: + case IGRAPH_ES_NONE: + case IGRAPH_ES_1: + case IGRAPH_ES_VECTORPTR: + case IGRAPH_ES_RANGE: + case IGRAPH_ES_ALL_BETWEEN: + break; + case IGRAPH_ES_VECTOR: + igraph_vector_int_destroy((igraph_vector_int_t*)es->data.vecptr); + IGRAPH_FREE(es->data.vecptr); + break; + case IGRAPH_ES_PAIRS: + case IGRAPH_ES_PATH: + igraph_vector_int_destroy((igraph_vector_int_t*)es->data.path.ptr); + IGRAPH_FREE(es->data.path.ptr); + break; + default: + break; + } +} + +/** + * \function igraph_es_is_all + * \brief Check whether an edge selector includes all edges. + * + * \param es Pointer to an edge selector object. + * \return \c true if \p es was created with \ref + * igraph_es_all() or \ref igraph_ess_all(), and \c false otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_es_is_all(const igraph_es_t *es) { + return es->type == IGRAPH_ES_ALL; +} + +/** + * \function igraph_es_copy + * \brief Creates a copy of an edge selector. + * + * \param dest An uninitialized selector that will contain the copy. + * \param src The selector being copied. + * \return Error code. + * + * \sa \ref igraph_es_destroy() + */ +igraph_error_t igraph_es_copy(igraph_es_t* dest, const igraph_es_t* src) { + igraph_vector_int_t *vec; + + memcpy(dest, src, sizeof(igraph_es_t)); + switch (dest->type) { + case IGRAPH_ES_VECTOR: + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot copy edge selector."); + IGRAPH_FINALLY(igraph_free, &vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, src->data.vecptr)); + dest->data.vecptr = vec; + IGRAPH_FINALLY_CLEAN(1); /* ownership of vec taken by 'dest' */ + break; + case IGRAPH_ES_PATH: + case IGRAPH_ES_PAIRS: + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot copy edge selector."); + IGRAPH_FINALLY(igraph_free, &vec); + IGRAPH_CHECK(igraph_vector_int_init_copy(vec, src->data.path.ptr)); + dest->data.path.ptr = vec; + IGRAPH_FINALLY_CLEAN(1); /* ownership of vec taken by 'dest' */ + break; + default: + break; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_as_vector + * \brief Transform edge selector into vector. + * + * + * Call this function on an edge selector to transform it into a vector. + * This is only implemented for sequence and vector selectors. If the + * edges do not exist in the graph, this will result in an error. + * + * \param graph Pointer to a graph to check if the edges in the selector exist. + * \param es An edge selector object. + * \param v Pointer to initialized vector. The result will be stored here. + * \return Error code. + * + * Time complexity: O(n), the number of edges in the selector. + */ +igraph_error_t igraph_es_as_vector(const igraph_t *graph, igraph_es_t es, + igraph_vector_int_t *v) { + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_eit_as_vector(&eit, v)); + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_es_type + * \brief Returns the type of the edge selector. + */ +igraph_es_type_t igraph_es_type(const igraph_es_t *es) { + return es->type; +} + +static igraph_error_t igraph_i_es_pairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result); +static igraph_error_t igraph_i_es_path_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result); +static igraph_error_t igraph_i_es_all_between_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result); + +/** + * \function igraph_es_size + * \brief Returns the size of the edge selector. + * + * The size of the edge selector is the number of edges it will + * yield when it is iterated over. + * + * \param graph The graph over which we will iterate. + * \param es The edge selector. + * \param result The result will be returned here. + * \return Error code. + */ +igraph_error_t igraph_es_size(const igraph_t *graph, const igraph_es_t *es, + igraph_int_t *result) { + igraph_vector_int_t v; + + switch (es->type) { + case IGRAPH_ES_ALL: + *result = igraph_ecount(graph); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_ALLFROM: + *result = igraph_ecount(graph); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_ALLTO: + *result = igraph_ecount(graph); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_INCIDENT: + IGRAPH_VECTOR_INT_INIT_FINALLY(&v, 0); + IGRAPH_CHECK(igraph_incident( + graph, &v, es->data.incident.vid, es->data.incident.mode, + es->data.incident.loops + )); + *result = igraph_vector_int_size(&v); + igraph_vector_int_destroy(&v); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_NONE: + *result = 0; + return IGRAPH_SUCCESS; + + case IGRAPH_ES_1: + if (es->data.eid < igraph_ecount(graph) && es->data.eid >= 0) { + *result = 1; + } else { + *result = 0; + } + return IGRAPH_SUCCESS; + + case IGRAPH_ES_VECTOR: + case IGRAPH_ES_VECTORPTR: + *result = igraph_vector_int_size(es->data.vecptr); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_RANGE: + *result = es->data.range.end - es->data.range.start; + return IGRAPH_SUCCESS; + + case IGRAPH_ES_PAIRS: + IGRAPH_CHECK(igraph_i_es_pairs_size(graph, es, result)); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_PATH: + IGRAPH_CHECK(igraph_i_es_path_size(graph, es, result)); + return IGRAPH_SUCCESS; + + case IGRAPH_ES_ALL_BETWEEN: + IGRAPH_CHECK(igraph_i_es_all_between_size(graph, es, result)); + return IGRAPH_SUCCESS; + + default: + IGRAPH_ERROR("Cannot calculate selector length, invalid selector type.", + IGRAPH_EINVAL); + } +} + +static igraph_error_t igraph_i_es_pairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result) { + igraph_int_t i, n = igraph_vector_int_size(es->data.path.ptr); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot calculate edge selector length from odd number of vertices.", + IGRAPH_EINVAL); + } + if (!igraph_vector_int_isininterval(es->data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot calculate edge selector length.", IGRAPH_EINVVID); + } + + *result = n / 2; + /* Check for the existence of all edges */ + for (i = 0; i < *result; i++) { + igraph_int_t from = VECTOR(*es->data.path.ptr)[2 * i]; + igraph_int_t to = VECTOR(*es->data.path.ptr)[2 * i + 1]; + igraph_int_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, from, to, es->data.path.mode, + /*error=*/ 1)); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_es_path_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result) { + igraph_int_t i, n = igraph_vector_int_size(es->data.path.ptr); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (!igraph_vector_int_isininterval(es->data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot calculate selector length.", IGRAPH_EINVVID); + } + + if (n <= 1) { + *result = 0; + } else { + *result = n - 1; + } + for (i = 0; i < *result; i++) { + igraph_int_t from = VECTOR(*es->data.path.ptr)[i]; + igraph_int_t to = VECTOR(*es->data.path.ptr)[i + 1]; + igraph_int_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, from, to, es->data.path.mode, + /*error=*/ 1)); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_es_all_between_size(const igraph_t *graph, + const igraph_es_t *es, igraph_int_t *result) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t from = es->data.between.from; + igraph_int_t to = es->data.between.to; + igraph_bool_t directed = es->data.between.directed; + igraph_vector_int_t vec; + + if (from < 0 || from >= no_of_nodes || to < 0 || to >= no_of_nodes) { + IGRAPH_ERROR("Cannot calculate selector length.", IGRAPH_EINVVID); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_get_all_eids_between(graph, &vec, from, to, directed)); + *result = igraph_vector_int_size(&vec); + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/**************************************************/ + +static igraph_error_t igraph_i_eit_create_allfromto(const igraph_t *graph, + igraph_eit_t *eit, + igraph_neimode_t mode); +static igraph_error_t igraph_i_eit_create_incident(const igraph_t* graph, + igraph_es_t es, igraph_eit_t *eit); +static igraph_error_t igraph_i_eit_pairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); +static igraph_error_t igraph_i_eit_path(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); + +static igraph_error_t igraph_i_eit_create_allfromto(const igraph_t *graph, + igraph_eit_t *eit, + igraph_neimode_t mode) { + igraph_vector_int_t *vec; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, vec); + + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(vec, no_of_edges)); + + if (igraph_is_directed(graph)) { + igraph_vector_int_t adj; + IGRAPH_VECTOR_INT_INIT_FINALLY(&adj, 0); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_incident(graph, &adj, i, mode, IGRAPH_LOOPS)); + igraph_vector_int_append(vec, &adj); /* reserved */ + } + igraph_vector_int_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_vector_int_t adj; + igraph_bool_t *added; + IGRAPH_VECTOR_INT_INIT_FINALLY(&adj, 0); + added = IGRAPH_CALLOC(no_of_edges, igraph_bool_t); + IGRAPH_CHECK_OOM(added, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, added); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_incident(graph, &adj, i, IGRAPH_ALL, IGRAPH_LOOPS)); + const igraph_int_t length = igraph_vector_int_size(&adj); + for (igraph_int_t j = 0; j < length; j++) { + if (!added[ VECTOR(adj)[j] ]) { + igraph_vector_int_push_back(vec, VECTOR(adj)[j]); /* reserved */ + added[ VECTOR(adj)[j] ] = true; + } + } + } + igraph_vector_int_destroy(&adj); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(2); + } + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->vec = vec; + eit->end = igraph_vector_int_size(eit->vec); + + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eit_create_incident(const igraph_t* graph, + igraph_es_t es, igraph_eit_t *eit) { + igraph_vector_int_t vec; + igraph_vector_int_t* vec_int; + igraph_int_t i, n; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_incident( + graph, &vec, es.data.incident.vid, es.data.incident.mode, + es.data.incident.loops + )); + + vec_int = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec_int, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, vec_int); + + n = igraph_vector_int_size(&vec); + IGRAPH_VECTOR_INT_INIT_FINALLY(vec_int, n); + + for (i = 0; i < n; i++) { + VECTOR(*vec_int)[i] = VECTOR(vec)[i]; + } + + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(3); + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->vec = vec_int; + eit->end = igraph_vector_int_size(vec_int); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eit_pairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + igraph_int_t n = igraph_vector_int_size(es.data.path.ptr); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i; + igraph_vector_int_t* vec; + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot create edge iterator from odd number of vertices.", + IGRAPH_EINVAL); + } + if (!igraph_vector_int_isininterval(es.data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot create edge iterator.", IGRAPH_EINVVID); + } + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, n / 2); + + for (i = 0; i < n / 2; i++) { + igraph_int_t from = VECTOR(*es.data.path.ptr)[2 * i]; + igraph_int_t to = VECTOR(*es.data.path.ptr)[2 * i + 1]; + igraph_int_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, from, to, es.data.path.mode, + /*error=*/ 1)); + VECTOR(*vec)[i] = eid; + } + + IGRAPH_FINALLY_CLEAN(2); + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = n / 2; + eit->vec = vec; + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eit_path(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + igraph_int_t n = igraph_vector_int_size(es.data.path.ptr); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, len; + igraph_vector_int_t* vec; + + if (!igraph_vector_int_isininterval(es.data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot create edge iterator.", IGRAPH_EINVVID); + } + + if (n <= 1) { + len = 0; + } else { + len = n - 1; + } + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, vec); + + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, len); + + for (i = 0; i < len; i++) { + igraph_int_t from = VECTOR(*es.data.path.ptr)[i]; + igraph_int_t to = VECTOR(*es.data.path.ptr)[i + 1]; + igraph_int_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, from, to, es.data.path.mode, + /*error=*/ 1)); + VECTOR(*vec)[i] = eid; + } + + IGRAPH_FINALLY_CLEAN(2); + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = len; + eit->vec = vec; + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eit_all_between( + const igraph_t *graph, igraph_es_t es, igraph_eit_t *eit +) { + igraph_int_t from = es.data.between.from; + igraph_int_t to = es.data.between.to; + igraph_bool_t directed = es.data.between.directed; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t* vec; + + if (from < 0 || from >= no_of_nodes || to < 0 || to >= no_of_nodes) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_EINVVID); + } + + vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(vec, "Cannot create edge iterator."); + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_VECTOR_INT_INIT_FINALLY(vec, 0); + IGRAPH_CHECK(igraph_get_all_eids_between(graph, vec, from, to, directed)); + IGRAPH_FINALLY_CLEAN(2); + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = igraph_vector_int_size(vec); + eit->vec = vec; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eit_create + * \brief Creates an edge iterator from an edge selector. + * + * + * This function creates an edge iterator based on an edge selector + * and a graph. + * + * + * The same edge selector can be used to create many edge iterators, + * also for different graphs. + * + * \param graph An \type igraph_t object for which the edge selector + * will be instantiated. + * \param es The edge selector to instantiate. + * \param eit Pointer to an uninitialized edge iterator. + * \return Error code. + * \sa \ref igraph_eit_destroy() + * + * Time complexity: depends on the type of the edge selector. For edge + * selectors created by \ref igraph_es_all(), \ref igraph_es_none(), + * \ref igraph_es_1(), \ref igraph_es_vector(), \ref igraph_es_range() it is + * O(1). For \ref igraph_es_incident() it is O(d) where d is the number of + * incident edges of the vertex. + */ + +igraph_error_t igraph_eit_create(const igraph_t *graph, igraph_es_t es, igraph_eit_t *eit) { + switch (es.type) { + case IGRAPH_ES_ALL: + eit->type = IGRAPH_EIT_RANGE; + eit->pos = 0; + eit->start = 0; + eit->end = igraph_ecount(graph); + break; + case IGRAPH_ES_ALLFROM: + IGRAPH_CHECK(igraph_i_eit_create_allfromto(graph, eit, IGRAPH_OUT)); + break; + case IGRAPH_ES_ALLTO: + IGRAPH_CHECK(igraph_i_eit_create_allfromto(graph, eit, IGRAPH_IN)); + break; + case IGRAPH_ES_INCIDENT: + IGRAPH_CHECK(igraph_i_eit_create_incident(graph, es, eit)); + break; + case IGRAPH_ES_NONE: + eit->type = IGRAPH_EIT_RANGE; + eit->pos = 0; + eit->start = 0; + eit->end = 0; + break; + case IGRAPH_ES_1: + eit->type = IGRAPH_EIT_RANGE; + eit->pos = es.data.eid; + eit->start = es.data.eid; + eit->end = es.data.eid + 1; + if (eit->pos >= igraph_ecount(graph)) { + IGRAPH_ERROR("Cannot create iterator.", IGRAPH_EINVEID); + } + break; + case IGRAPH_ES_VECTOR: + case IGRAPH_ES_VECTORPTR: + eit->type = IGRAPH_EIT_VECTORPTR; + eit->pos = 0; + eit->start = 0; + eit->vec = es.data.vecptr; + eit->end = igraph_vector_int_size(eit->vec); + if (!igraph_vector_int_isininterval(eit->vec, 0, igraph_ecount(graph) - 1)) { + IGRAPH_ERROR("Cannot create iterator.", IGRAPH_EINVEID); + } + break; + case IGRAPH_ES_RANGE: + { + igraph_int_t no_of_edges = igraph_ecount(graph); + if (es.data.range.start < 0 || + es.data.range.start > no_of_edges || + (no_of_edges > 0 && es.data.range.start == no_of_edges)) { + IGRAPH_ERROR("Cannot create range iterator, starting edge ID out of range.", IGRAPH_EINVEID); + } + if (es.data.range.end < 0 || es.data.range.end > no_of_edges) { + IGRAPH_ERROR("Cannot create range iterator, ending edge ID out of range.", IGRAPH_EINVEID); + } + } + eit->type = IGRAPH_EIT_RANGE; + eit->pos = es.data.range.start; + eit->start = es.data.range.start; + eit->end = es.data.range.end; + break; + case IGRAPH_ES_PAIRS: + IGRAPH_CHECK(igraph_i_eit_pairs(graph, es, eit)); + break; + case IGRAPH_ES_PATH: + IGRAPH_CHECK(igraph_i_eit_path(graph, es, eit)); + break; + case IGRAPH_ES_ALL_BETWEEN: + IGRAPH_CHECK(igraph_i_eit_all_between(graph, es, eit)); + break; + default: + IGRAPH_ERROR("Cannot create iterator, invalid selector.", IGRAPH_EINVAL); + break; + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eit_destroy + * \brief Destroys an edge iterator. + * + * \param eit Pointer to an edge iterator to destroy. + * \sa \ref igraph_eit_create() + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_eit_destroy(const igraph_eit_t *eit) { + switch (eit->type) { + case IGRAPH_EIT_RANGE: + case IGRAPH_EIT_VECTORPTR: + break; + case IGRAPH_EIT_VECTOR: + igraph_vector_int_destroy((igraph_vector_int_t*)eit->vec); + igraph_free((igraph_vector_int_t*)eit->vec); + break; + default: + /* IGRAPH_ERROR("Cannot destroy iterator, unknown type", IGRAPH_EINVAL); */ + break; + } +} + +igraph_error_t igraph_eit_as_vector(const igraph_eit_t *eit, igraph_vector_int_t *v) { + + igraph_int_t i; + + IGRAPH_CHECK(igraph_vector_int_resize(v, IGRAPH_EIT_SIZE(*eit))); + + switch (eit->type) { + case IGRAPH_EIT_RANGE: + for (i = 0; i < IGRAPH_EIT_SIZE(*eit); i++) { + VECTOR(*v)[i] = eit->start + i; + } + break; + case IGRAPH_EIT_VECTOR: + case IGRAPH_EIT_VECTORPTR: + for (i = 0; i < IGRAPH_EIT_SIZE(*eit); i++) { + VECTOR(*v)[i] = VECTOR(*eit->vec)[i]; + } + break; + default: + IGRAPH_ERROR("Cannot convert to vector, unknown iterator type", + IGRAPH_EINVAL); + break; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/graph/type_common.c b/src/graph/type_common.c new file mode 100644 index 0000000..44bf27a --- /dev/null +++ b/src/graph/type_common.c @@ -0,0 +1,243 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_interface.h" + +/* Internal functions */ + +/* The functions in this file are sensible "default" implementations for some + * of the core API functions that simply call other core API functions. If + * you are implementing your own data type, chances are that you can use these + * as is. */ + +/** + * \ingroup interface + * \function igraph_empty + * \brief Creates an empty graph with some vertices and no edges. + * + * + * The most basic constructor, all the other constructors should call + * this to create a minimal graph object. Our use of the term "empty graph" + * in the above description should be distinguished from the mathematical + * definition of the empty or null graph. Strictly speaking, the empty or null + * graph in graph theory is the graph with no vertices and no edges. However + * by "empty graph" as used in \a igraph we mean a graph having zero or more + * vertices, but no edges. + * \param graph Pointer to a not-yet initialized graph object. + * \param n The number of vertices in the graph, a non-negative + * integer number is expected. + * \param directed Boolean; whether the graph is directed or not. Supported + * values are: + * \clist + * \cli IGRAPH_DIRECTED + * The graph will be \em directed. + * \cli IGRAPH_UNDIRECTED + * The graph will be \em undirected. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|) for a graph with + * |V| vertices (and no edges). + * + * \example examples/simple/creation.c + */ +igraph_error_t igraph_empty(igraph_t *graph, igraph_int_t n, igraph_bool_t directed) { + return igraph_empty_attrs(graph, n, directed, 0); +} + +/** + * \ingroup interface + * \function igraph_delete_vertices + * \brief Removes some vertices (with all their edges) from the graph. + * + * + * This function changes the IDs of the vertices (except in some very + * special cases, but these should not be relied on anyway). + * + * + * This function invalidates all iterators. + * + * \param graph The graph to work on. + * \param vertices The IDs of the vertices to remove, in a vector. The vector + * may contain the same ID more than once. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * + * Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices and + * edges in the original graph. + * + * \example examples/simple/igraph_delete_vertices.c + */ +igraph_error_t igraph_delete_vertices(igraph_t *graph, const igraph_vs_t vertices) { + return igraph_delete_vertices_map(graph, vertices, /* idx= */ 0, /* invidx= */ 0); +} + +/** + * \function igraph_delete_vertices_idx + * \brief Removes some vertices (with all their edges) from the graph (deprecated alias). + * + * \deprecated-by igraph_delete_vertices_map 0.11.0 + */ +igraph_error_t igraph_delete_vertices_idx( + igraph_t *graph, const igraph_vs_t vertices, igraph_vector_int_t *idx, + igraph_vector_int_t *invidx +) { + return igraph_delete_vertices_map(graph, vertices, idx, invidx); +} + +/** + * \function igraph_edge + * \brief Returns the head and tail vertices of an edge. + * + * \param graph The graph object. + * \param eid The edge ID. + * \param from Pointer to an \type igraph_int_t. The tail (source) of + * the edge will be placed here. + * \param to Pointer to an \type igraph_int_t. The head (target) of the + * edge will be placed here. + * \return Error code. + * + * \sa \ref igraph_get_eid() for the opposite operation; + * \ref igraph_edges() to get the endpoints of several edges; + * \ref IGRAPH_TO(), \ref IGRAPH_FROM() and \ref IGRAPH_OTHER() for + * a faster but non-error-checked version. + * + * Added in version 0.2. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_edge( + const igraph_t *graph, igraph_int_t eid, + igraph_int_t *from, igraph_int_t *to +) { + + if (eid < 0 || eid >= igraph_ecount(graph)) { + IGRAPH_ERROR("Cannot retrieve edge endpoints.", IGRAPH_EINVEID); + } + + if (igraph_is_directed(graph)) { + *from = IGRAPH_FROM(graph, eid); + *to = IGRAPH_TO(graph, eid); + } else { + *from = IGRAPH_TO(graph, eid); + *to = IGRAPH_FROM(graph, eid); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_edges + * \brief Gives the head and tail vertices of a series of edges. + * + * \param graph The graph object. + * \param eids Edge selector, the series of edges. + * \param edges Pointer to an initialized vector. The start and endpoints of + * each edge will be placed here. + * \param bycol Boolean constant. If true, the edges will be returned + * columnwise, e.g. the first edge is + * res[0]->res[|E|], the second is + * res[1]->res[|E|+1], etc. Supply false to get + * the edge list in a format compatible with \ref igraph_add_edges(). + * \return Error code. + * \sa \ref igraph_get_eids() for the opposite operation; + * \ref igraph_edge() for getting the endpoints of a single edge; + * \ref IGRAPH_TO(), \ref IGRAPH_FROM() and \ref IGRAPH_OTHER() for + * a faster but non-error-checked method. + * + * Time complexity: O(k) where k is the number of edges in the selector. + */ +igraph_error_t igraph_edges( + const igraph_t *graph, igraph_es_t eids, igraph_vector_int_t *edges, + igraph_bool_t bycol +) { + igraph_eit_t eit; + igraph_int_t n, ptr = 0, ptr2; + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + n = IGRAPH_EIT_SIZE(eit); + IGRAPH_CHECK(igraph_vector_int_resize(edges, n * 2)); + + if (bycol) { + ptr2 = n; + if (igraph_is_directed(graph)) { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_FROM(graph, e); + VECTOR(*edges)[ptr2++] = IGRAPH_TO(graph, e); + } + } else { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_TO(graph, e); + VECTOR(*edges)[ptr2++] = IGRAPH_FROM(graph, e); + } + } + } else { + if (igraph_is_directed(graph)) { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_FROM(graph, e); + VECTOR(*edges)[ptr++] = IGRAPH_TO(graph, e); + } + } else { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_TO(graph, e); + VECTOR(*edges)[ptr++] = IGRAPH_FROM(graph, e); + } + } + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_invalidate_cache + * \brief Invalidates the internal cache of an igraph graph. + * + * + * igraph graphs cache some basic properties about themselves in an internal + * data structure. This function invalidates the contents of the cache and + * forces a recalculation of the cached properties the next time they are + * needed. + * + * + * You should not need to call this function during normal usage; however, we + * might ask you to call this function explicitly if we suspect that you are + * running into a bug in igraph's cache handling. A tell-tale sign of an invalid + * cache entry is that the result of a cached igraph function (such as + * \ref igraph_is_dag() or \ref igraph_is_simple()) is different before and + * after a cache invalidation. + * + * \param graph The graph whose cache is to be invalidated. + * + * Time complexity: O(1). + */ +void igraph_invalidate_cache(const igraph_t* graph) { + igraph_i_property_cache_invalidate_all(graph); +} diff --git a/src/graph/type_indexededgelist.c b/src/graph/type_indexededgelist.c new file mode 100644 index 0000000..b8c4207 --- /dev/null +++ b/src/graph/type_indexededgelist.c @@ -0,0 +1,2008 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "graph/attributes.h" +#include "graph/caching.h" +#include "graph/internal.h" +#include "math/safe_intop.h" + +/* Internal functions */ + +static igraph_error_t igraph_i_create_start_vectors( + igraph_vector_int_t *res, igraph_vector_int_t *el, + igraph_vector_int_t *index, igraph_int_t nodes); + +/** + * \section about_basic_interface + * + * This is the very minimal API in \a igraph. All the other + * functions use this minimal set for creating and manipulating + * graphs. + * + * This is a very important principle since it makes possible to + * implement other data representations by implementing only this + * minimal set. + * + * This section lists all the functions and macros that are considered + * as part of the core API from the point of view of the \em users + * of igraph. Some of these functions and macros have sensible default + * implementations that simply call some other core function (e.g., + * \ref igraph_empty() calls \ref igraph_empty_attrs() with a null attribute + * table pointer). If you wish to experiment with implementing an alternative + * data type, the actual number of functions that you need to replace is lower + * as you can rely on the same default implementations in most cases. + */ + +/** + * \ingroup interface + * \function igraph_empty_attrs + * \brief Creates an empty graph with some vertices, no edges and some graph attributes. + * + * Use this instead of \ref igraph_empty() if you wish to add some graph + * attributes right after initialization. This function is currently + * not very interesting for the ordinary user. Just supply 0 here or + * use \ref igraph_empty(). + * + * + * This function does not set any vertex attributes. To create a graph which has + * vertex attributes, call this function specifying 0 vertices, then use + * \ref igraph_add_vertices() to add vertices and their attributes. + * + * \param graph Pointer to a not-yet initialized graph object. + * \param n The number of vertices in the graph; a non-negative + * integer number is expected. + * \param directed Boolean; whether the graph is directed or not. Supported + * values are: + * \clist + * \cli IGRAPH_DIRECTED + * Create a \em directed graph. + * \cli IGRAPH_UNDIRECTED + * Create an \em undirected graph. + * \endclist + * \param attr The graph attributes. Supply \c NULL if not graph attributes + * are to be set. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * \sa \ref igraph_empty() to create an empty graph without attributes; + * \ref igraph_add_vertices() and \ref igraph_add_edges() to add vertices + * and edges, possibly with associated attributes. + * + * Time complexity: O(|V|) for a graph with + * |V| vertices (and no edges). + */ +igraph_error_t igraph_empty_attrs( + igraph_t *graph, igraph_int_t n, igraph_bool_t directed, + const igraph_attribute_record_list_t *attr +) { + + if (n < 0) { + IGRAPH_ERROR("Number of vertices must not be negative.", IGRAPH_EINVAL); + } + + memset(graph, 0, sizeof(igraph_t)); + + graph->n = 0; + graph->directed = directed; + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->from, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->to, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->oi, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->ii, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->os, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->is, 1); + + /* init cache */ + graph->cache = IGRAPH_CALLOC(1, igraph_i_property_cache_t); + IGRAPH_CHECK_OOM(graph->cache, "Cannot create graph."); + IGRAPH_FINALLY(igraph_free, graph->cache); + IGRAPH_CHECK(igraph_i_property_cache_init(graph->cache)); + IGRAPH_FINALLY(igraph_i_property_cache_destroy, graph->cache); + + VECTOR(graph->os)[0] = 0; + VECTOR(graph->is)[0] = 0; + + /* init attributes */ + IGRAPH_CHECK(igraph_i_attribute_init(graph, attr)); + + /* add the vertices */ + IGRAPH_CHECK(igraph_add_vertices(graph, n, 0)); + + IGRAPH_FINALLY_CLEAN(8); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_destroy + * \brief Frees the memory allocated for a graph object. + * + * + * This function should be called for every graph object exactly once. + * + * + * This function invalidates all iterators (of course), but the + * iterators of a graph should be destroyed before the graph itself + * anyway. + * \param graph Pointer to the graph to free. + * + * Time complexity: operating system specific. + */ +void igraph_destroy(igraph_t *graph) { + igraph_i_attribute_destroy(graph); + + igraph_i_property_cache_destroy(graph->cache); + IGRAPH_FREE(graph->cache); + + igraph_vector_int_destroy(&graph->from); + igraph_vector_int_destroy(&graph->to); + igraph_vector_int_destroy(&graph->oi); + igraph_vector_int_destroy(&graph->ii); + igraph_vector_int_destroy(&graph->os); + igraph_vector_int_destroy(&graph->is); +} + +/** + * \ingroup interface + * \function igraph_copy + * \brief Creates an exact (deep) copy of a graph. + * + * + * This function deeply copies a graph object to create an exact + * replica of it. The new replica should be destroyed by calling + * \ref igraph_destroy() on it when not needed any more. + * + * + * You can also create a shallow copy of a graph by simply using the + * standard assignment operator, but be careful and do \em not + * destroy a shallow replica. To avoid this mistake, creating shallow + * copies is not recommended. + * \param to Pointer to an uninitialized graph object. + * \param from Pointer to the graph object to copy. + * \return Error code. + * + * Time complexity: O(|V|+|E|) for a + * graph with |V| vertices and + * |E| edges. + * + * \example examples/simple/igraph_copy.c + */ + +igraph_error_t igraph_copy(igraph_t *to, const igraph_t *from) { + memset(to, 0, sizeof(igraph_t)); + + to->n = from->n; + to->directed = from->directed; + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->from, &from->from)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->from); + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->to, &from->to)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->to); + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->oi, &from->oi)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->oi); + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->ii, &from->ii)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->ii); + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->os, &from->os)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->os); + IGRAPH_CHECK(igraph_vector_int_init_copy(&to->is, &from->is)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &to->is); + + to->cache = IGRAPH_CALLOC(1, igraph_i_property_cache_t); + IGRAPH_CHECK_OOM(to->cache, "Cannot copy graph."); + IGRAPH_FINALLY(igraph_free, to->cache); + IGRAPH_CHECK(igraph_i_property_cache_copy(to->cache, from->cache)); + IGRAPH_FINALLY(igraph_i_property_cache_destroy, to->cache); + + IGRAPH_CHECK(igraph_i_attribute_copy(to, from, true, true, true)); + + IGRAPH_FINALLY_CLEAN(8); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_add_edges + * \brief Adds edges to a graph object. + * + * + * The edges are given in a vector, the + * first two elements define the first edge (the order is + * from, to for directed + * graphs). The vector + * should contain even number of integer numbers between zero and the + * number of vertices in the graph minus one (inclusive). If you also + * want to add new vertices, call \ref igraph_add_vertices() first. + * \param graph The graph to which the edges will be added. + * \param edges The edges themselves. + * \param attr The attributes of the new edges. You can supply a null pointer + * here if you do not need edge attributes. + * \return Error code: + * \c IGRAPH_EINVAL: invalid (odd) edges vector length, + * \c IGRAPH_EINVVID: invalid vertex ID in edges vector. + * + * This function invalidates all iterators. + * + * + * Time complexity: O(|V|+|E|) where |V| is the number of vertices and + * |E| is the number of edges in the \em new, extended graph. + * + * \example examples/simple/creation.c + */ +igraph_error_t igraph_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, + const igraph_attribute_record_list_t *attr +) { + igraph_int_t no_of_edges = igraph_vector_int_size(&graph->from); + igraph_int_t edges_to_add = igraph_vector_int_size(edges) / 2; + igraph_int_t new_no_of_edges; + igraph_int_t i = 0; + igraph_vector_int_t newoi, newii; + igraph_bool_t directed = igraph_is_directed(graph); + + if (igraph_vector_int_size(edges) % 2 != 0) { + IGRAPH_ERROR("Invalid (odd) length of edges vector.", IGRAPH_EINVAL); + } + if (!igraph_vector_int_isininterval(edges, 0, igraph_vcount(graph) - 1)) { + IGRAPH_ERROR("Out-of-range vertex IDs when adding edges.", IGRAPH_EINVVID); + } + + /* from & to */ + IGRAPH_SAFE_ADD(no_of_edges, edges_to_add, &new_no_of_edges); + if (new_no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERRORF("Maximum edge count (%" IGRAPH_PRId ") exceeded.", IGRAPH_ERANGE, + IGRAPH_ECOUNT_MAX); + } + IGRAPH_CHECK(igraph_vector_int_reserve(&graph->from, no_of_edges + edges_to_add)); + IGRAPH_CHECK(igraph_vector_int_reserve(&graph->to, no_of_edges + edges_to_add)); + + while (i < edges_to_add * 2) { + if (directed || VECTOR(*edges)[i] > VECTOR(*edges)[i + 1]) { + igraph_vector_int_push_back(&graph->from, VECTOR(*edges)[i++]); /* reserved */ + igraph_vector_int_push_back(&graph->to, VECTOR(*edges)[i++]); /* reserved */ + } else { + igraph_vector_int_push_back(&graph->to, VECTOR(*edges)[i++]); /* reserved */ + igraph_vector_int_push_back(&graph->from, VECTOR(*edges)[i++]); /* reserved */ + } + } + + /* If an error occurs while the edges are being added, we make the necessary fixup + * to ensure that the graph is still in a consistent state when this function returns. + * The graph may already be on the finally stack when calling this function. We use + * a separate finally stack level to avoid its destructor from being called on error, + * so that the fixup can succeed. + */ + +#define CHECK_ERR(expr) \ + do { \ + igraph_error_t err = (expr); \ + if (err != IGRAPH_SUCCESS) { \ + igraph_vector_int_resize(&graph->from, no_of_edges); /* gets smaller, error safe */ \ + igraph_vector_int_resize(&graph->to, no_of_edges); /* gets smaller, error safe */ \ + IGRAPH_FINALLY_EXIT(); \ + IGRAPH_ERROR("Cannot add edges.", err); \ + } \ + } while (0) + + /* oi & ii */ + IGRAPH_FINALLY_ENTER(); + { + CHECK_ERR(igraph_vector_int_init(&newoi, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &newoi); + CHECK_ERR(igraph_vector_int_init(&newii, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &newii); + CHECK_ERR(igraph_vector_int_pair_order(&graph->from, &graph->to, &newoi, graph->n)); + CHECK_ERR(igraph_vector_int_pair_order(&graph->to, &graph->from, &newii, graph->n)); + + /* Attributes */ + if (graph->attr) { + /* TODO: Does this keep the attribute table in a consistent state upon failure? */ + CHECK_ERR(igraph_i_attribute_add_edges(graph, edges, attr)); + } + + /* os & is, its length does not change, error safe */ + igraph_i_create_start_vectors(&graph->os, &graph->from, &newoi, graph->n); + igraph_i_create_start_vectors(&graph->is, &graph->to, &newii, graph->n); + + /* everything went fine */ + igraph_vector_int_destroy(&graph->oi); + igraph_vector_int_destroy(&graph->ii); + IGRAPH_FINALLY_CLEAN(2); + + graph->oi = newoi; + graph->ii = newii; + } + IGRAPH_FINALLY_EXIT(); + +#undef CHECK_ERR + + /* modification successful, clear the cached properties of the graph. + * + * Adding one or more edges cannot make a strongly or weakly connected + * graph disconnected, so we keep those flags if they are cached as true. + * + * Adding one or more edges may turn a DAG into a non-DAG or a forest into + * a non-forest, so we can keep those flags only if they are cached as + * false. + * + * Also, adding one or more edges does not change HAS_LOOP, HAS_MULTI and + * HAS_MUTUAL if they were already true. + */ + igraph_i_property_cache_invalidate_conditionally( + graph, + /* keep_always = */ 0, + /* keep_when_false = */ + (1 << IGRAPH_PROP_IS_DAG) | (1 << IGRAPH_PROP_IS_FOREST), + /* keep_when_true = */ + (1 << IGRAPH_PROP_IS_WEAKLY_CONNECTED) | + (1 << IGRAPH_PROP_IS_STRONGLY_CONNECTED) | + (1 << IGRAPH_PROP_HAS_LOOP) | + (1 << IGRAPH_PROP_HAS_MULTI) | + (1 << IGRAPH_PROP_HAS_MUTUAL) + ); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_add_vertices + * \brief Adds vertices to a graph. + * + * + * This function invalidates all iterators. + * + * \param graph The graph object to extend. + * \param nv Non-negative integer specifying the number of vertices to add. + * \param attr The attributes of the new vertices. You can supply a null pointer + * here if you do not need vertex attributes. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of new vertices. + * + * Time complexity: O(|V|) where |V| is the number of vertices in the \em new, + * extended graph. + * + * \example examples/simple/creation.c + */ +igraph_error_t igraph_add_vertices( + igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr +) { + igraph_int_t ec = igraph_ecount(graph); + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t new_vc; + igraph_int_t i; + + if (nv < 0) { + IGRAPH_ERROR("Cannot add negative number of vertices.", IGRAPH_EINVAL); + } + + IGRAPH_SAFE_ADD(graph->n, nv, &new_vc); + if (new_vc > IGRAPH_VCOUNT_MAX) { + IGRAPH_ERRORF("Maximum vertex count (%" IGRAPH_PRId ") exceeded.", IGRAPH_ERANGE, + IGRAPH_VCOUNT_MAX); + } + IGRAPH_CHECK(igraph_vector_int_reserve(&graph->os, new_vc + 1)); + IGRAPH_CHECK(igraph_vector_int_reserve(&graph->is, new_vc + 1)); + + igraph_vector_int_resize(&graph->os, new_vc + 1); /* reserved */ + igraph_vector_int_resize(&graph->is, new_vc + 1); /* reserved */ + for (i = graph->n + 1; i < new_vc + 1; i++) { + VECTOR(graph->os)[i] = ec; + VECTOR(graph->is)[i] = ec; + } + + graph->n += nv; + + /* Add attributes if necessary. This section is protected with + * FINALLY_ENTER/EXIT so that the graph would not be accidentally + * free upon error until it could be restored to a consistent state. */ + + if (graph->attr) { + igraph_error_t err; + IGRAPH_FINALLY_ENTER(); + err = igraph_i_attribute_add_vertices(graph, nv, attr); + if (err != IGRAPH_SUCCESS) { + /* Restore original vertex count on failure */ + graph->n = vc; + igraph_vector_int_resize(&graph->os, vc + 1); /* shrinks */ + igraph_vector_int_resize(&graph->is, vc + 1); /* shrinks */ + } + IGRAPH_FINALLY_EXIT(); + if (err != IGRAPH_SUCCESS) { + IGRAPH_ERROR("Cannot add vertices.", err); + } + } + + /* modification successful, clear the cached properties of the graph. + * + * Adding one or more nodes does not change the following cached properties: + * + * - IGRAPH_PROP_HAS_LOOP + * - IGRAPH_PROP_HAS_MULTI + * - IGRAPH_PROP_HAS_MUTUAL + * - IGRAPH_PROP_IS_DAG (adding a node does not create/destroy cycles) + * - IGRAPH_PROP_IS_FOREST (same) + * + * Adding one or more nodes without any edges incident on them is sure to + * make the graph disconnected (weakly or strongly), so we can keep the + * connectivity-related properties if they are currently cached as false. + * (Actually, even if they weren't cached as false, we could still set them + * to false, but we don't have that functionality yet). The only exception + * is when the graph had zero vertices and gained only one vertex, because + * it then becomes connected. That's why we have the condition below in the + * keep_when_false section. + */ + igraph_i_property_cache_invalidate_conditionally( + graph, + /* keep_always = */ + (1 << IGRAPH_PROP_HAS_LOOP) | + (1 << IGRAPH_PROP_HAS_MULTI) | + (1 << IGRAPH_PROP_HAS_MUTUAL) | + (1 << IGRAPH_PROP_IS_DAG) | + (1 << IGRAPH_PROP_IS_FOREST), + /* keep_when_false = */ + igraph_vcount(graph) >= 2 ? ( + (1 << IGRAPH_PROP_IS_STRONGLY_CONNECTED) | + (1 << IGRAPH_PROP_IS_WEAKLY_CONNECTED) + ) : 0, + /* keep_when_true = */ + 0 + ); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_delete_edges + * \brief Removes edges from a graph. + * + * + * The edges to remove are specified as an edge selector. + * + * + * This function cannot remove vertices; vertices will be kept even if they lose + * all their edges. + * + * + * This function invalidates all iterators. + * \param graph The graph to work on. + * \param edges The edges to remove. + * \return Error code. + * + * Time complexity: O(|V|+|E|) where |V| and |E| are the number of vertices + * and edges in the \em original graph, respectively. + * + * \example examples/simple/igraph_delete_edges.c + */ +igraph_error_t igraph_delete_edges(igraph_t *graph, igraph_es_t edges) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t edges_to_remove = 0; + igraph_int_t remaining_edges; + igraph_eit_t eit; + + igraph_vector_int_t newfrom, newto; + igraph_vector_int_t newoi, newii; + + igraph_bool_t *mark; + igraph_int_t i, j; + + mark = IGRAPH_CALLOC(no_of_edges, igraph_bool_t); + IGRAPH_CHECK_OOM(mark, "Cannot delete edges."); + IGRAPH_FINALLY(igraph_free, mark); + + IGRAPH_CHECK(igraph_eit_create(graph, edges, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + if (! mark[e]) { + edges_to_remove++; + mark[e] = true; + } + } + remaining_edges = no_of_edges - edges_to_remove; + + /* We don't need the iterator any more */ + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&newfrom, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newto, remaining_edges); + + /* Actually remove the edges, move from pos i to pos j in newfrom/newto */ + for (i = 0, j = 0; j < remaining_edges; i++) { + if (! mark[i]) { + VECTOR(newfrom)[j] = VECTOR(graph->from)[i]; + VECTOR(newto)[j] = VECTOR(graph->to)[i]; + j++; + } + } + + /* Create index, this might require additional memory */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&newoi, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newii, remaining_edges); + IGRAPH_CHECK(igraph_vector_int_pair_order(&newfrom, &newto, &newoi, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_int_pair_order(&newto, &newfrom, &newii, no_of_nodes)); + + /* Edge attributes, we need an index that gives the IDs of the + original edges for every new edge. + */ + if (graph->attr) { + igraph_vector_int_t idx; + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, remaining_edges); + for (i = 0, j = 0; i < no_of_edges; i++) { + if (! mark[i]) { + VECTOR(idx)[j++] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, graph, &idx)); + igraph_vector_int_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Ok, we've all memory needed, free the old structure */ + igraph_vector_int_destroy(&graph->from); + igraph_vector_int_destroy(&graph->to); + igraph_vector_int_destroy(&graph->oi); + igraph_vector_int_destroy(&graph->ii); + graph->from = newfrom; + graph->to = newto; + graph->oi = newoi; + graph->ii = newii; + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_FREE(mark); + IGRAPH_FINALLY_CLEAN(1); + + /* Create start vectors, no memory is needed for this */ + igraph_i_create_start_vectors(&graph->os, &graph->from, &graph->oi, no_of_nodes); + igraph_i_create_start_vectors(&graph->is, &graph->to, &graph->ii, no_of_nodes); + + /* modification successful, clear the cached properties of the graph. + * + * Deleting one or more edges cannot make a directed acyclic graph cyclic, + * or an undirected forest into a cyclic graph, so we keep those flags if + * they are cached as true. + * + * Similarly, deleting one or more edges cannot make a disconnected graph + * connected, so we keep the connectivity flags if they are cached as false. + * + * Also, if the graph had no loop edges before the deletion, it will have + * no loop edges after the deletion either. The same applies to reciprocal + * edges or multiple edges as well. + */ + igraph_i_property_cache_invalidate_conditionally( + graph, + /* keep_always = */ 0, + /* keep_when_false = */ + (1 << IGRAPH_PROP_HAS_LOOP) | + (1 << IGRAPH_PROP_HAS_MULTI) | + (1 << IGRAPH_PROP_HAS_MUTUAL) | + (1 << IGRAPH_PROP_IS_STRONGLY_CONNECTED) | + (1 << IGRAPH_PROP_IS_WEAKLY_CONNECTED), + /* keep_when_true = */ + (1 << IGRAPH_PROP_IS_DAG) | + (1 << IGRAPH_PROP_IS_FOREST) + ); + + /* Nothing to deallocate... */ + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_delete_vertices_map + * \brief Removes some vertices (with all their edges) from the graph. + * + * + * This function changes the IDs of the vertices (except in some very + * special cases, but these should not be relied on anyway). You can use the + * \p map argument to obtain the mapping from old vertex IDs to the new ones, + * and the \p newmap argument to obtain the reverse mapping. + * + * + * This function invalidates all iterators. + * + * \param graph The graph to work on. + * \param vertices The IDs of the vertices to remove, in a vector. The vector + * may contain the same ID more than once. + * \param map An optional pointer to a vector that provides the mapping from + * the vertex IDs \em before the removal to the vertex IDs \em after + * the removal. You can supply \c NULL here if you are not interested. + * \param invmap An optional pointer to a vector that provides the mapping from + * the vertex IDs \em after the removal to the vertex IDs \em before + * the removal. You can supply \c NULL here if you are not interested. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * + * Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices and + * edges in the original graph. + */ +igraph_error_t igraph_delete_vertices_map( + igraph_t *graph, const igraph_vs_t vertices, igraph_vector_int_t *map, + igraph_vector_int_t *invmap +) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t edge_recoding, vertex_recoding; + igraph_vector_int_t *my_vertex_recoding = &vertex_recoding; + igraph_vit_t vit; + igraph_t newgraph; + igraph_int_t i, j; + igraph_int_t remaining_vertices, remaining_edges; + + if (map) { + my_vertex_recoding = map; + IGRAPH_CHECK(igraph_vector_int_resize(map, no_of_nodes)); + igraph_vector_int_null(map); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_recoding, no_of_nodes); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edge_recoding, no_of_edges); + + IGRAPH_CHECK(igraph_vit_create(graph, vertices, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* mark the vertices to delete */ + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit) ) { + igraph_int_t vertex = IGRAPH_VIT_GET(vit); + if (vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("Cannot delete vertices.", IGRAPH_EINVVID); + } + VECTOR(*my_vertex_recoding)[vertex] = 1; + } + /* create vertex recoding vector */ + for (remaining_vertices = 0, i = 0; i < no_of_nodes; i++) { + if (VECTOR(*my_vertex_recoding)[i] == 0) { + VECTOR(*my_vertex_recoding)[i] = remaining_vertices; + remaining_vertices++; + } else { + VECTOR(*my_vertex_recoding)[i] = -1; + } + } + /* create edge recoding vector */ + for (remaining_edges = 0, i = 0; i < no_of_edges; i++) { + igraph_int_t from = VECTOR(graph->from)[i]; + igraph_int_t to = VECTOR(graph->to)[i]; + if (VECTOR(*my_vertex_recoding)[from] >= 0 && + VECTOR(*my_vertex_recoding)[to ] >= 0) { + VECTOR(edge_recoding)[i] = remaining_edges + 1; + remaining_edges++; + } + } + + /* start creating the graph */ + memset(&newgraph, 0, sizeof(igraph_t)); + newgraph.n = remaining_vertices; + newgraph.directed = graph->directed; + + /* allocate vectors */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.from, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.to, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.oi, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.ii, remaining_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.os, remaining_vertices + 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&newgraph.is, remaining_vertices + 1); + + /* Add the edges */ + for (i = 0, j = 0; j < remaining_edges; i++) { + if (VECTOR(edge_recoding)[i] > 0) { + igraph_int_t from = VECTOR(graph->from)[i]; + igraph_int_t to = VECTOR(graph->to )[i]; + VECTOR(newgraph.from)[j] = VECTOR(*my_vertex_recoding)[from]; + VECTOR(newgraph.to )[j] = VECTOR(*my_vertex_recoding)[to]; + j++; + } + } + + /* update oi & ii */ + IGRAPH_CHECK(igraph_vector_int_pair_order(&newgraph.from, &newgraph.to, &newgraph.oi, + remaining_vertices)); + IGRAPH_CHECK(igraph_vector_int_pair_order(&newgraph.to, &newgraph.from, &newgraph.ii, + remaining_vertices)); + + IGRAPH_CHECK(igraph_i_create_start_vectors(&newgraph.os, &newgraph.from, + &newgraph.oi, remaining_vertices)); + IGRAPH_CHECK(igraph_i_create_start_vectors(&newgraph.is, &newgraph.to, + &newgraph.ii, remaining_vertices)); + + newgraph.cache = IGRAPH_CALLOC(1, igraph_i_property_cache_t); + IGRAPH_CHECK_OOM(newgraph.cache, "Cannot delete vertices."); + IGRAPH_FINALLY(igraph_free, newgraph.cache); + IGRAPH_CHECK(igraph_i_property_cache_init(newgraph.cache)); + IGRAPH_FINALLY(igraph_i_property_cache_destroy, newgraph.cache); + + /* attributes */ + IGRAPH_CHECK(igraph_i_attribute_copy( + &newgraph, graph, /* graph= */ true, /* vertex= */ false, /* edge= */ false + )); + + /* at this point igraph_destroy can take over the responsibility of + * deallocating the graph */ + IGRAPH_FINALLY_CLEAN(8); /* 2 for the property cache, 6 for the vectors */ + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + if (newgraph.attr) { + igraph_vector_int_t iidx; + IGRAPH_VECTOR_INT_INIT_FINALLY(&iidx, remaining_vertices); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t jj = VECTOR(*my_vertex_recoding)[i]; + if (jj >= 0) { + VECTOR(iidx)[ jj ] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, &newgraph, &iidx)); + IGRAPH_CHECK(igraph_vector_int_resize(&iidx, remaining_edges)); + for (i = 0; i < no_of_edges; i++) { + igraph_int_t jj = VECTOR(edge_recoding)[i]; + if (jj != 0) { + VECTOR(iidx)[ jj - 1 ] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, &newgraph, &iidx)); + igraph_vector_int_destroy(&iidx); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&edge_recoding); + igraph_destroy(graph); + *graph = newgraph; + + IGRAPH_FINALLY_CLEAN(3); + + if (invmap) { + IGRAPH_CHECK(igraph_vector_int_resize(invmap, remaining_vertices)); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t newid = VECTOR(*my_vertex_recoding)[i]; + if (newid >= 0) { + VECTOR(*invmap)[newid] = i; + } + } + } + + if (!map) { + igraph_vector_int_destroy(my_vertex_recoding); + IGRAPH_FINALLY_CLEAN(1); + } + + /* modification successful, clear the cached properties of the graph. + * + * Deleting one or more vertices cannot make a directed acyclic graph cyclic, + * or an undirected forest into a cyclic graph, so we keep those flags if + * they are cached as true. + * + * Also, if the graph had no loop edges before the deletion, it will have + * no loop edges after the deletion either. The same applies to reciprocal + * edges or multiple edges as well. + */ + igraph_i_property_cache_invalidate_conditionally( + graph, + /* keep_always = */ 0, + /* keep_when_false = */ + (1 << IGRAPH_PROP_HAS_LOOP) | + (1 << IGRAPH_PROP_HAS_MULTI) | + (1 << IGRAPH_PROP_HAS_MUTUAL), + /* keep_when_true = */ + (1 << IGRAPH_PROP_IS_DAG) | + (1 << IGRAPH_PROP_IS_FOREST) + ); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_vcount + * \brief The number of vertices in a graph. + * + * \param graph The graph. + * \return Number of vertices. + * + * Time complexity: O(1) + */ +igraph_int_t igraph_vcount(const igraph_t *graph) { + return graph->n; +} + +/** + * \ingroup interface + * \function igraph_ecount + * \brief The number of edges in a graph. + * + * \param graph The graph. + * \return Number of edges. + * + * Time complexity: O(1) + */ +igraph_int_t igraph_ecount(const igraph_t *graph) { + return igraph_vector_int_size(&graph->from); +} + +/** + * \ingroup interface + * \function igraph_neighbors + * \brief Adjacent vertices to a vertex. + * + * \param graph The graph to work on. + * \param neis This vector will contain the result. The vector should + * be initialized beforehand and will be resized. Starting from igraph + * version 0.4 this vector is always sorted, the vertex IDs are + * in increasing order. If one neighbor is connected with multiple + * edges, the neighbor will be returned multiple times. + * \param pnode The id of the node for which the adjacent vertices are + * to be searched. + * \param mode Defines the way adjacent vertices are searched in + * directed graphs. It can have the following values: + * \c IGRAPH_OUT, vertices reachable by an + * edge from the specified vertex are searched; + * \c IGRAPH_IN, vertices from which the + * specified vertex is reachable are searched; + * \c IGRAPH_ALL, both kinds of vertices are + * searched. + * This parameter is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges. \c IGRAPH_NO_LOOPS + * removes loop edges from the result. \c IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the result. + * \c IGRAPH_LOOPS_TWICE makes loop edges appear \em twice in the + * result if the graph is undirected or \p mode is set to \c IGRAPH_ALL + * (and once otherwise as returning them twice does not make sense for + * directed graphs). + * \param multiple Specifies how to treat multiple (parallel) edges. + * \c IGRAPH_NO_MULTIPLE collapses parallel edges into a single one; + * \c IGRAPH_MULTIPLE keeps the multiplicities of parallel edges + * so the same neighbor will appear as many times in the result as the + * number of parallel edges going between the two vertices. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * \c IGRAPH_EINVMODE: invalid mode argument. + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(d), d is the number of adjacent vertices to the queried + * vertex. + * + * \example examples/simple/igraph_neighbors.c + */ +igraph_error_t igraph_neighbors( + const igraph_t *graph, igraph_vector_int_t *neis, igraph_int_t pnode, + igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple +) { +#define DEDUPLICATE_IF_NEEDED(vertex, n) \ + if (should_filter_duplicates) { \ + if (vertex == pnode) { \ + /* This is a loop edge */ \ + if (loops == IGRAPH_NO_LOOPS) { \ + /* Filtering loop edges unconditionally */ \ + length -= n; \ + continue; \ + } else if (loops == IGRAPH_LOOPS_ONCE && vertex == last_added) { \ + /* Filtering every second endpoint of loop edges */ \ + length -= n; \ + last_added = -1; \ + seen_loop = true; \ + continue; \ + } else if (multiple == IGRAPH_NO_MULTIPLE && seen_loop) { \ + /* Filtering multi-loop edges */ \ + length -= n; \ + continue; \ + } else { \ + seen_loop = (loops != IGRAPH_LOOPS_TWICE || last_added == vertex);\ + last_added = vertex; \ + } \ + } else { \ + /* Not a loop edge */ \ + if (multiple == IGRAPH_NO_MULTIPLE && vertex == last_added) { \ + /* Filtering multi-edges */ \ + length -= n; \ + continue; \ + } else { \ + last_added = vertex; \ + } \ + } \ + } + + + igraph_int_t length = 0, idx = 0; + igraph_int_t i, j; + + igraph_int_t node = pnode; + igraph_int_t last_added = -1; + igraph_bool_t should_filter_duplicates; + + /* seen_loop stores whether we have already seen at least one full loop + * edge while iterating over the neighbor lists. This is needed to handle + * multi-loop edges properly. Since internally we always store loop edges + * twice, this flag should become true only if we processed both endpoints + * for a loop edge */ + igraph_bool_t seen_loop = false; + + if (node < 0 || node > igraph_vcount(graph) - 1) { + IGRAPH_ERRORF("Vertex %" IGRAPH_PRId " is not in the graph.", IGRAPH_EINVVID, node); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Mode should be either IGRAPH_OUT, IGRAPH_IN or IGRAPH_ALL.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (mode != IGRAPH_ALL && loops == IGRAPH_LOOPS_TWICE) { + /* + IGRAPH_ERROR("For a directed graph (with directions not ignored), " + "IGRAPH_LOOPS_TWICE does not make sense.", IGRAPH_EINVAL); + */ + loops = IGRAPH_LOOPS_ONCE; + } + + /* Calculate needed space first & allocate it */ + /* Note that 'mode' is treated as a bit field here; it's okay because + * IGRAPH_ALL = IGRAPH_IN | IGRAPH_OUT, bit-wise */ + if (mode & IGRAPH_OUT) { + length += (VECTOR(graph->os)[node + 1] - VECTOR(graph->os)[node]); + } + if (mode & IGRAPH_IN) { + length += (VECTOR(graph->is)[node + 1] - VECTOR(graph->is)[node]); + } + + IGRAPH_CHECK(igraph_vector_int_resize(neis, length)); + + /* We are dealing with two sorted lists; one for the successors and one + * for the predecessors. If we have requested only one of them, we have + * an easy job. If we have requested both, we need to merge the two lists + * to ensure that the output is sorted by the vertex IDs of the "other" + * endpoint of the affected edges. We don't need to merge if the graph + * is undirected, because in that case the data structure guarantees that + * the "out-edges" contain only (u, v) pairs where u <= v and the + * "in-edges" contains the rest, so the result is sorted even without + * merging. */ + if (!igraph_is_directed(graph) || mode != IGRAPH_ALL) { + /* graph is undirected or we did not ask for both directions in a + * directed graph; this is the easy case */ + + should_filter_duplicates = !(multiple == IGRAPH_MULTIPLE && + ((!igraph_is_directed(graph) && loops == IGRAPH_LOOPS_TWICE) || + (igraph_is_directed(graph) && loops != IGRAPH_NO_LOOPS))); + + if (mode & IGRAPH_OUT) { + j = VECTOR(graph->os)[node + 1]; + for (i = VECTOR(graph->os)[node]; i < j; i++) { + igraph_int_t to = VECTOR(graph->to)[ VECTOR(graph->oi)[i] ]; + DEDUPLICATE_IF_NEEDED(to, 1); + VECTOR(*neis)[idx++] = to; + } + } + + if (mode & IGRAPH_IN) { + j = VECTOR(graph->is)[node + 1]; + for (i = VECTOR(graph->is)[node]; i < j; i++) { + igraph_int_t from = VECTOR(graph->from)[ VECTOR(graph->ii)[i] ]; + DEDUPLICATE_IF_NEEDED(from, 1); + VECTOR(*neis)[idx++] = from; + } + } + } else { + /* Both in- and out- neighbors in a directed graph, + we need to merge the two 'vectors' so the result is + correctly ordered. */ + igraph_int_t j1 = VECTOR(graph->os)[node + 1]; + igraph_int_t j2 = VECTOR(graph->is)[node + 1]; + igraph_int_t i1 = VECTOR(graph->os)[node]; + igraph_int_t i2 = VECTOR(graph->is)[node]; + igraph_int_t eid1, eid2; + igraph_int_t n1, n2; + + should_filter_duplicates = !(multiple == IGRAPH_MULTIPLE && + loops == IGRAPH_LOOPS_TWICE); + + while (i1 < j1 && i2 < j2) { + eid1 = VECTOR(graph->oi)[i1]; + eid2 = VECTOR(graph->ii)[i2]; + n1 = VECTOR(graph->to)[eid1]; + n2 = VECTOR(graph->from)[eid2]; + if (n1 < n2) { + i1++; + DEDUPLICATE_IF_NEEDED(n1, 1); + VECTOR(*neis)[idx++] = n1; + } else if (n1 > n2) { + i2++; + DEDUPLICATE_IF_NEEDED(n2, 1); + VECTOR(*neis)[idx++] = n2; + } else { + i1++; + i2++; + DEDUPLICATE_IF_NEEDED(n1, 2); + VECTOR(*neis)[idx++] = n1; + DEDUPLICATE_IF_NEEDED(n2, 1); + VECTOR(*neis)[idx++] = n2; + } + } + + while (i1 < j1) { + eid1 = VECTOR(graph->oi)[i1++]; + igraph_int_t to = VECTOR(graph->to)[eid1]; + DEDUPLICATE_IF_NEEDED(to, 1); + VECTOR(*neis)[idx++] = to; + } + + while (i2 < j2) { + eid2 = VECTOR(graph->ii)[i2++]; + igraph_int_t from = VECTOR(graph->from)[eid2]; + DEDUPLICATE_IF_NEEDED(from, 1); + VECTOR(*neis)[idx++] = from; + } + + } + IGRAPH_CHECK(igraph_vector_int_resize(neis, length)); + + return IGRAPH_SUCCESS; +#undef DEDUPLICATE_IF_NEEDED +} + +/** + * \ingroup internal + */ + +static igraph_error_t igraph_i_create_start_vectors( + igraph_vector_int_t *res, igraph_vector_int_t *el, + igraph_vector_int_t *iindex, igraph_int_t nodes) { + +# define EDGE(i) (VECTOR(*el)[ VECTOR(*iindex)[(i)] ]) + + igraph_int_t no_of_nodes; + igraph_int_t no_of_edges; + igraph_int_t i, j, idx; + + no_of_nodes = nodes; + no_of_edges = igraph_vector_int_size(el); + + /* result */ + + IGRAPH_CHECK(igraph_vector_int_resize(res, nodes + 1)); + + /* create the index */ + + if (no_of_edges == 0) { + /* empty graph */ + igraph_vector_int_null(res); + } else { + idx = -1; + for (i = 0; i <= EDGE(0); i++) { + idx++; VECTOR(*res)[idx] = 0; + } + for (i = 1; i < no_of_edges; i++) { + igraph_int_t n = EDGE(i) - EDGE(VECTOR(*res)[idx]); + for (j = 0; j < n; j++) { + idx++; VECTOR(*res)[idx] = i; + } + } + j = EDGE(VECTOR(*res)[idx]); + for (i = 0; i < no_of_nodes - j; i++) { + idx++; VECTOR(*res)[idx] = no_of_edges; + } + } + + /* clean */ + +# undef EDGE + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_is_directed + * \brief Is this a directed graph? + * + * \param graph The graph. + * \return Boolean value, \c true if the graph is directed, + * \c false otherwise. + * + * Time complexity: O(1) + * + * \example examples/simple/igraph_is_directed.c + */ + +igraph_bool_t igraph_is_directed(const igraph_t *graph) { + return graph->directed; +} + +/** + * \ingroup interface + * \function igraph_degree_1 + * \brief The degree of of a single vertex in the graph. + * + * This function calculates the in-, out- or total degree of a single vertex. + * For a single vertex, it is more efficient than calling \ref igraph_degree(). + * + * \param graph The graph. + * \param deg Pointer to the integer where the computed degree will be stored. + * \param vid The vertex for which the degree will be calculated. + * \param mode Defines the type of the degree for directed graphs. Valid modes are: + * \c IGRAPH_OUT, out-degree; + * \c IGRAPH_IN, in-degree; + * \c IGRAPH_ALL, total degree (sum of the in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Boolean, gives whether the self-loops should be + * counted. + * \return Error code. + * + * \sa \ref igraph_degree() to compute the degree of several vertices at once. + * + * Time complexity: O(1) if \p loops is \c true, and + * O(d) otherwise, where d is the degree. + */ +igraph_error_t igraph_degree_1( + const igraph_t *graph, igraph_int_t *deg, igraph_int_t vid, + igraph_neimode_t mode, igraph_loops_t loops +) { + igraph_int_t loop_counter; + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (loops != IGRAPH_NO_LOOPS && loops != IGRAPH_LOOPS_ONCE && + loops != IGRAPH_LOOPS_TWICE) { + IGRAPH_ERROR("Invalid loops argument.", IGRAPH_EINVAL); + } + + if (loops == IGRAPH_LOOPS_ONCE && (mode & IGRAPH_ALL) != IGRAPH_ALL) { + loops = IGRAPH_LOOPS_TWICE; + } + + *deg = 0; + if (mode & IGRAPH_OUT) { + *deg += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + } + if (mode & IGRAPH_IN) { + *deg += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + } + + if (loops != IGRAPH_LOOPS_TWICE) { + /* When loops should not be counted, we remove their contribution from the + * previously computed degree. */ + loop_counter = 0; + if (mode & IGRAPH_OUT) { + for (igraph_int_t i = VECTOR(graph->os)[vid]; i < VECTOR(graph->os)[vid + 1]; i++) { + if (VECTOR(graph->to)[ VECTOR(graph->oi)[i] ] == vid) { + loop_counter++; + } + } + } + if (mode & IGRAPH_IN) { + for (igraph_int_t i = VECTOR(graph->is)[vid]; i < VECTOR(graph->is)[vid + 1]; i++) { + if (VECTOR(graph->from)[ VECTOR(graph->ii)[i] ] == vid) { + loop_counter++; + } + } + } + + if (loops == IGRAPH_NO_LOOPS || mode != IGRAPH_ALL) { + *deg -= loop_counter; + } else { + /* loops == IGRAPH_LOOPS_ONCE && mode == IGRAPH_ALL */ + *deg -= loop_counter / 2; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup interface + * \function igraph_degree + * \brief The degree of some vertices in a graph. + * + * + * This function calculates the in-, out- or total degree of the + * specified vertices. + * + * + * This function returns the result as a vector of \c igraph_int_t + * values. In applications where \c igraph_real_t is desired, use + * \ref igraph_strength() with \c NULL weights. + * + * \param graph The graph. + * \param res Integer vector, this will contain the result. It should be + * initialized and will be resized to be the appropriate size. + * \param vids Vertex selector, giving the vertex IDs of which the degree will + * be calculated. + * \param mode Defines the type of the degree for directed graphs. Valid modes are: + * \c IGRAPH_OUT, out-degree; + * \c IGRAPH_IN, in-degree; + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Constant of type \ref igraph_loops_t, specifies how to treat + * loop edges when calculating the degree. \c IGRAPH_NO_LOOPS ignores + * loop edges; \c IGRAPH_LOOPS_ONCE counts each loop edge only once; + * \c IGRAPH_LOOPS_TWICE counts each loop edge twice in undirected + * graphs and once in directed graphs. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + * Time complexity: O(v) if \p loops is \c true, and + * O(v*d) otherwise. v is the number of + * vertices for which the degree will be calculated, and + * d is their (average) degree. + * + * \sa \ref igraph_strength() for the version that takes into account + * edge weights; \ref igraph_degree_1() to efficiently compute the + * degree of a single vertex; \ref igraph_maxdegree() if you only need + * the largest degree. + * + * \example examples/simple/igraph_degree.c + */ +igraph_error_t igraph_degree( + const igraph_t *graph, igraph_vector_int_t *res, const igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops +) { + + igraph_int_t nodes_to_calc; + igraph_int_t i, j; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for degree calculation.", IGRAPH_EINVMODE); + } + + if (loops == IGRAPH_NO_LOOPS || loops == IGRAPH_LOOPS_ONCE) { + /* If the graph is known not to have loops, we can use the faster + * loops == IGRAPH_LOOPS_TWICE code path, which has O(1) complexity + * instead of of O(d). */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + loops = IGRAPH_LOOPS_TWICE; + } + } + + if (loops == IGRAPH_LOOPS_ONCE && mode != IGRAPH_ALL) { + /* We can use the faster loops == IGRAPH_LOOPS_TWICE path again */ + loops = IGRAPH_LOOPS_TWICE; + } + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_vector_int_resize(res, nodes_to_calc)); + igraph_vector_int_null(res); + + if (loops == IGRAPH_LOOPS_TWICE) { + if (mode & IGRAPH_OUT) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + } + } + if (mode & IGRAPH_IN) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + } + } + } else if (loops == IGRAPH_LOOPS_ONCE) { + IGRAPH_ASSERT((mode & IGRAPH_ALL) == IGRAPH_ALL); + + /* We arbitrarily count loop edges in the (mode & IGRAPH_OUT) branch but + * not in the (mode & IGRAPH_IN) branch */ + + if (igraph_vs_is_all(&vids)) { + // When calculating degree for all vertices, iterating over edges is faster + igraph_int_t no_of_edges = igraph_ecount(graph); + + /* mode & IGRAPH_OUT branch */ + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + VECTOR(*res)[from]++; + } + + /* mode & IGRAPH_IN branch */ + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t to = IGRAPH_TO(graph, edge); + if (IGRAPH_FROM(graph, edge) != to) { + VECTOR(*res)[to]++; + } + } + } else { + /* mode & IGRAPH_OUT branch */ + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + } + + /* mode & IGRAPH_IN branch */ + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + for (j = VECTOR(graph->is)[vid]; + j < VECTOR(graph->is)[vid + 1]; j++) { + if (VECTOR(graph->from)[ VECTOR(graph->ii)[j] ] == vid) { + VECTOR(*res)[i] -= 1; + } + } + } + } + } else { + /* no loops should be counted */ + if (igraph_vs_is_all(&vids)) { + // When calculating degree for all vertices, iterating over edges is faster + igraph_int_t no_of_edges = igraph_ecount(graph); + + if (mode & IGRAPH_OUT) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + if (from != IGRAPH_TO(graph, edge)) { + VECTOR(*res)[from]++; + } + } + } + if (mode & IGRAPH_IN) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t to = IGRAPH_TO(graph, edge); + if (IGRAPH_FROM(graph, edge) != to) { + VECTOR(*res)[to]++; + } + } + } + } else { + if (mode & IGRAPH_OUT) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + for (j = VECTOR(graph->os)[vid]; + j < VECTOR(graph->os)[vid + 1]; j++) { + if (VECTOR(graph->to)[ VECTOR(graph->oi)[j] ] == vid) { + VECTOR(*res)[i] -= 1; + } + } + } + } + if (mode & IGRAPH_IN) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + for (j = VECTOR(graph->is)[vid]; + j < VECTOR(graph->is)[vid + 1]; j++) { + if (VECTOR(graph->from)[ VECTOR(graph->ii)[j] ] == vid) { + VECTOR(*res)[i] -= 1; + } + } + } + } + } + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* These are unsafe macros. Only supply variable names, i.e. no + expressions as parameters, otherwise nasty things can happen. + + BINSEARCH is an inline binary search in the 'edgelist' vector, which is + assumed to be sorted in the order of indices stored in the 'iindex' vector. + (So, [edgelist[iindex[x]] for x in 0..] is assumed to be sorted). 'N' must + be the same as 'end' when invoking the macro but it must be a separate + variable as we want to modify 'end' independently of 'N'. Upon exiting the + macro, 'result' is the index of the _leftmost_ item in the sorted 'edgelist' + (i.e. indexed by 'iindex') where the value was found, if it was found; + otherwise 'pos' is left intact. + + FIND_DIRECTED_EDGE looks for an edge from 'xfrom' to 'xto' in the graph, and + stores the ID of the edge in 'eid' if it is found; otherwise 'eid' is left + intact. + + FIND_UNDIRECTED_EDGE looks for an edge between 'xfrom' and 'xto' in an + undirected graph, swapping them if necessary. It stores the ID of the edge + in 'eid' if it is found; otherwise 'eid' is left intact. + */ + +#define BINSEARCH(start, end, value, iindex, edgelist, N, result, result_pos) \ + do { \ + while ((start) < (end)) { \ + igraph_int_t mid =(start)+((end)-(start))/2; \ + igraph_int_t e = VECTOR((iindex))[mid]; \ + if (VECTOR((edgelist))[e] < (value)) { \ + (start) = mid+1; \ + } else { \ + (end) = mid; \ + } \ + } \ + if ((start) < (N)) { \ + igraph_int_t e = VECTOR((iindex))[(start)]; \ + if (VECTOR((edgelist))[e] == (value)) { \ + *(result) = e; \ + if (result_pos != 0) { *(result_pos) = start; } \ + } \ + } \ + } while (0) + +#define FIND_DIRECTED_EDGE(graph,xfrom,xto,eid) \ + do { \ + igraph_int_t start = VECTOR(graph->os)[xfrom]; \ + igraph_int_t end = VECTOR(graph->os)[xfrom+1]; \ + igraph_int_t N = end; \ + igraph_int_t start2 = VECTOR(graph->is)[xto]; \ + igraph_int_t end2 = VECTOR(graph->is)[xto+1]; \ + igraph_int_t N2 = end2; \ + igraph_int_t *nullpointer = NULL; \ + if (end-start < end2-start2) { \ + BINSEARCH(start, end, xto, graph->oi, graph->to, N, eid, nullpointer); \ + } else { \ + BINSEARCH(start2, end2, xfrom, graph->ii, graph->from, N2, eid, nullpointer); \ + } \ + } while (0) + +#define FIND_UNDIRECTED_EDGE(graph, from, to, eid) \ + do { \ + igraph_int_t xfrom1 = from > to ? from : to; \ + igraph_int_t xto1 = from > to ? to : from; \ + FIND_DIRECTED_EDGE(graph, xfrom1, xto1, eid); \ + } while (0) + +/** + * \function igraph_get_eid + * \brief Get the edge ID from the endpoints of an edge. + * + * For undirected graphs \c from and \c to are exchangeable. + * + * \param graph The graph object. + * \param eid Pointer to an integer, the edge ID will be stored here. + * If \p error is false and no edge was found, -1 + * will be returned. + * \param from The starting point of the edge. + * \param to The end point of the edge. + * \param directed Boolean, whether to search for directed + * edges in a directed graph. Ignored for undirected graphs. + * \param error Boolean, whether to report an error if the edge + * was not found. If it is false, then -1 will be + * assigned to \p eid. Note that invalid vertex IDs in input + * arguments (\p from or \p to) always trigger an error, + * regardless of this setting. + * \return Error code. + * \sa \ref igraph_edge() for the opposite operation, \ref igraph_get_all_eids_between() + * to retrieve all edge IDs between a pair of vertices. + * + * Time complexity: O(log (d)), where d is smaller of the out-degree + * of \c from and in-degree of \c to if \p directed is true. If \p directed + * is false, then it is O(log(d)+log(d2)), where d is the same as before and + * d2 is the minimum of the out-degree of \c to and the in-degree of \c from. + * + * \example examples/simple/igraph_get_eid.c + */ + +igraph_error_t igraph_get_eid(const igraph_t *graph, igraph_int_t *eid, + igraph_int_t from, igraph_int_t to, + igraph_bool_t directed, igraph_bool_t error) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (from < 0 || to < 0 || from >= no_of_nodes || to >= no_of_nodes) { + IGRAPH_ERROR("Cannot get edge ID.", IGRAPH_EINVVID); + } + + *eid = -1; + if (igraph_is_directed(graph)) { + + /* Directed graph */ + FIND_DIRECTED_EDGE(graph, from, to, eid); + if (!directed && *eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, eid); + } + + } else { + + /* Undirected graph, they only have one mode */ + FIND_UNDIRECTED_EDGE(graph, from, to, eid); + + } + + if (*eid < 0) { + if (error) { + IGRAPH_ERROR("Cannot get edge ID, no such edge.", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_eids + * Return edge IDs based on the adjacent vertices. + * + * The pairs of vertex IDs for which the edges are looked up are taken + * consecutively from the \c pairs vector, i.e. VECTOR(pairs)[0] + * and VECTOR(pairs)[1] specify the first pair, + * VECTOR(pairs)[2] and VECTOR(pairs)[3] the second + * pair, etc. + * + * + * If you have a sequence of vertex IDs that describe a \em path on the graph, + * use \ref igraph_expand_path_to_pairs() to convert them to a list of vertex + * pairs along the path. + * + * + * If the \c error argument is true, then it is an error to specify pairs + * of vertices that are not connected. Otherwise -1 is reported for vertex pairs + * without at least one edge between them. + * + * + * If there are multiple edges in the graph, then these are ignored; + * i.e. for a given pair of vertex IDs, igraph always returns the same edge ID, + * even if the pair appears multiple times in \c pairs. + * + * \param graph The input graph. + * \param eids Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param pairs Vector giving pairs of vertices to fetch the edges for. + * \param directed Boolean, whether to consider edge directions + * in directed graphs. This is ignored for undirected graphs. + * \param error Boolean, whether it is an error to supply + * non-connected vertices. If false, then -1 is + * returned for non-connected pairs. + * \return Error code. + * + * Time complexity: O(n log(d)), where n is the number of queried + * edges and d is the average degree of the vertices. + * + * \sa \ref igraph_get_eid() for a single edge. + * + * \example examples/simple/igraph_get_eids.c + */ +igraph_error_t igraph_get_eids(const igraph_t *graph, igraph_vector_int_t *eids, + const igraph_vector_int_t *pairs, + igraph_bool_t directed, igraph_bool_t error) { + + igraph_int_t n = pairs ? igraph_vector_int_size(pairs) : 0; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i; + igraph_int_t eid = -1; + + if (n == 0) { + igraph_vector_int_clear(eids); + return IGRAPH_SUCCESS; + } + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot get edge IDs, invalid length of vertex pair vector.", + IGRAPH_EINVAL); + } + + if (!igraph_vector_int_isininterval(pairs, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot get edge IDs, invalid vertex ID.", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_vector_int_resize(eids, n / 2)); + + if (igraph_is_directed(graph)) { + for (i = 0; i < n / 2; i++) { + igraph_int_t from = VECTOR(*pairs)[2 * i]; + igraph_int_t to = VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_DIRECTED_EDGE(graph, from, to, &eid); + if (!directed && eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, &eid); + } + + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge ID, no such edge.", IGRAPH_EINVAL); + } + } + } else { + for (i = 0; i < n / 2; i++) { + igraph_int_t from = VECTOR(*pairs)[2 * i]; + igraph_int_t to = VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_UNDIRECTED_EDGE(graph, from, to, &eid); + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge ID, no such edge.", IGRAPH_EINVAL); + } + } + } + + return IGRAPH_SUCCESS; +} + +#undef FIND_DIRECTED_EDGE +#undef FIND_UNDIRECTED_EDGE + +#define FIND_ALL_DIRECTED_EDGES(graph, xfrom, xto, eidvec) \ + do { \ + igraph_int_t start = VECTOR(graph->os)[xfrom]; \ + igraph_int_t end = VECTOR(graph->os)[xfrom+1]; \ + igraph_int_t N = end; \ + igraph_int_t start2 = VECTOR(graph->is)[xto]; \ + igraph_int_t end2 = VECTOR(graph->is)[xto+1]; \ + igraph_int_t N2 = end2; \ + igraph_int_t eid = -1; \ + igraph_int_t pos = -1; \ + if (end-start < end2-start2) { \ + BINSEARCH(start, end, xto, graph->oi, graph->to, N, &eid, &pos); \ + while (pos >= 0 && pos < N) { \ + eid = VECTOR(graph->oi)[pos++]; \ + if (VECTOR(graph->to)[eid] != xto) { break; } \ + IGRAPH_CHECK(igraph_vector_int_push_back(eidvec, eid)); \ + } \ + } else { \ + BINSEARCH(start2, end2, xfrom, graph->ii, graph->from, N2, &eid, &pos); \ + while (pos >= 0 && pos < N2) { \ + eid = VECTOR(graph->ii)[pos++]; \ + if (VECTOR(graph->from)[eid] != xfrom) { break; } \ + IGRAPH_CHECK(igraph_vector_int_push_back(eidvec, eid)); \ + } \ + } \ + } while (0) + +#define FIND_ALL_UNDIRECTED_EDGES(graph, from, to, eidvec) \ + do { \ + igraph_int_t xfrom1 = from > to ? from : to; \ + igraph_int_t xto1 = from > to ? to : from; \ + FIND_ALL_DIRECTED_EDGES(graph, xfrom1, xto1, eidvec); \ + } while (0) + +/** + * \function igraph_get_all_eids_between + * \brief Returns all edge IDs between a pair of vertices. + * + * + * For undirected graphs \c source and \c target are exchangeable. + * + * \param graph The input graph. + * \param eids Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param source The ID of the source vertex + * \param target The ID of the target vertex + * \param directed Boolean, whether to consider edge directions + * in directed graphs. This is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: TODO + * + * \sa \ref igraph_get_eid() for a single edge. + */ +igraph_error_t igraph_get_all_eids_between( + const igraph_t *graph, igraph_vector_int_t *eids, + igraph_int_t source, igraph_int_t target, igraph_bool_t directed +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (source < 0 || source >= no_of_nodes) { + IGRAPH_ERROR("Cannot get edge IDs, invalid source vertex ID.", IGRAPH_EINVVID); + } + + if (target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Cannot get edge IDs, invalid target vertex ID.", IGRAPH_EINVVID); + } + + igraph_vector_int_clear(eids); + + if (igraph_is_directed(graph)) { + /* look in the specified direction first */ + FIND_ALL_DIRECTED_EDGES(graph, source, target, eids); + if (!directed) { + /* look in the reverse direction as well */ + FIND_ALL_DIRECTED_EDGES(graph, target, source, eids); + } + } else { + FIND_ALL_UNDIRECTED_EDGES(graph, source, target, eids); + } + + return IGRAPH_SUCCESS; +} + +#undef FIND_DIRECTED_EDGE +#undef FIND_UNDIRECTED_EDGE +#undef BINSEARCH + +/** + * \function igraph_incident + * \brief Gives the incident edges of a vertex. + * + * \param graph The graph object. + * \param eids An initialized vector. It will be resized to hold the result. + * \param pnode A vertex ID. + * \param mode Specifies what kind of edges to include for directed + * graphs. \c IGRAPH_OUT means only outgoing edges, \c IGRAPH_IN means + * only incoming edges, \c IGRAPH_ALL means both. This parameter is + * ignored for undirected graphs. + * \param loops Specifies how to treat loop edges. \c IGRAPH_NO_LOOPS + * removes loop edges from the result. \c IGRAPH_LOOPS_ONCE + * makes each loop edge appear only once in the result. + * \c IGRAPH_LOOPS_TWICE makes loop edges appear \em twice in the + * result if the graph is undirected or \p mode is set to \c IGRAPH_ALL + * (and once otherwise as returning them twice does not make sense for + * directed graphs). + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * \c IGRAPH_EINVMODE: invalid mode argument. + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(d), the number of incident edges to \p pnode. + */ + +igraph_error_t igraph_incident( + const igraph_t *graph, igraph_vector_int_t *eids, igraph_int_t pnode, + igraph_neimode_t mode, igraph_loops_t loops +) { + igraph_int_t length = 0, idx = 0; + igraph_int_t i, j; + igraph_int_t node = pnode; + igraph_bool_t directed = igraph_is_directed(graph); + + if (node < 0 || node > igraph_vcount(graph) - 1) { + IGRAPH_ERRORF("Vertex %" IGRAPH_PRId " is not in the graph.", IGRAPH_EINVVID, node); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Mode should be either IGRAPH_OUT, IGRAPH_IN or IGRAPH_ALL.", IGRAPH_EINVMODE); + } + + if (!directed) { + mode = IGRAPH_ALL; + } + + if (mode != IGRAPH_ALL && loops == IGRAPH_LOOPS_TWICE) { + /* + IGRAPH_ERROR("For a directed graph (with directions not ignored), " + "IGRAPH_LOOPS_TWICE does not make sense.", IGRAPH_EINVAL); + */ + loops = IGRAPH_LOOPS_ONCE; + } + + /* Calculate needed space first & allocate it */ + /* Note that 'mode' is treated as a bit field here; it's okay because + * IGRAPH_ALL = IGRAPH_IN | IGRAPH_OUT, bit-wise */ + if (mode & IGRAPH_OUT) { + length += (VECTOR(graph->os)[node + 1] - VECTOR(graph->os)[node]); + } + if (mode & IGRAPH_IN) { + length += (VECTOR(graph->is)[node + 1] - VECTOR(graph->is)[node]); + } + + IGRAPH_CHECK(igraph_vector_int_resize(eids, length)); + + /* The loops below produce an ordering what is consistent with the + * ordering returned by igraph_neighbors(), and this should be preserved. + * We are dealing with two sorted lists; one for the successors and one + * for the predecessors. If we have requested only one of them, we have + * an easy job. If we have requested both, we need to merge the two lists + * to ensure that the output is sorted by the vertex IDs of the "other" + * endpoint of the affected edges */ + if (!directed || mode != IGRAPH_ALL) { + /* We did not ask for both directions; this is the easy case */ + + if (mode & IGRAPH_OUT) { + j = VECTOR(graph->os)[node + 1]; + for (i = VECTOR(graph->os)[node]; i < j; i++) { + igraph_int_t edge = VECTOR(graph->oi)[i]; + igraph_int_t other = VECTOR(graph->to)[edge]; + if (loops == IGRAPH_NO_LOOPS && other == pnode) { + length--; + } else { + VECTOR(*eids)[idx++] = edge; + } + } + } + + if (mode & IGRAPH_IN) { + j = VECTOR(graph->is)[node + 1]; + for (i = VECTOR(graph->is)[node]; i < j; i++) { + igraph_int_t edge = VECTOR(graph->ii)[i]; + igraph_int_t other = VECTOR(graph->from)[edge]; + if ((loops == IGRAPH_NO_LOOPS || (loops == IGRAPH_LOOPS_ONCE && !directed)) && other == pnode) { + length--; + } else { + VECTOR(*eids)[idx++] = edge; + } + } + } + } else { + /* both in- and out- neighbors in a directed graph, + we need to merge the two 'vectors' */ + igraph_int_t j1 = VECTOR(graph->os)[node + 1]; + igraph_int_t j2 = VECTOR(graph->is)[node + 1]; + igraph_int_t i1 = VECTOR(graph->os)[node]; + igraph_int_t i2 = VECTOR(graph->is)[node]; + igraph_int_t eid1, eid2; + igraph_int_t n1, n2; + igraph_bool_t seen_loop_edge = false; + + while (i1 < j1 && i2 < j2) { + eid1 = VECTOR(graph->oi)[i1]; + eid2 = VECTOR(graph->ii)[i2]; + n1 = VECTOR(graph->to)[eid1]; + n2 = VECTOR(graph->from)[eid2]; + if (n1 < n2) { + i1++; + VECTOR(*eids)[idx++] = eid1; + } else if (n1 > n2) { + i2++; + VECTOR(*eids)[idx++] = eid2; + } else if (n1 != pnode) { + /* multiple edge */ + i1++; + i2++; + VECTOR(*eids)[idx++] = eid1; + VECTOR(*eids)[idx++] = eid2; + } else { + /* loop edge */ + i1++; + i2++; + if (loops == IGRAPH_NO_LOOPS) { + length -= 2; + } else if (loops == IGRAPH_LOOPS_ONCE) { + length--; + if (!seen_loop_edge) { + VECTOR(*eids)[idx++] = eid1; + } else { + VECTOR(*eids)[idx++] = eid2; + } + seen_loop_edge = !seen_loop_edge; + } else { + VECTOR(*eids)[idx++] = eid1; + VECTOR(*eids)[idx++] = eid2; + } + } + } + + while (i1 < j1) { + eid1 = VECTOR(graph->oi)[i1++]; + VECTOR(*eids)[idx++] = eid1; + } + + while (i2 < j2) { + eid2 = VECTOR(graph->ii)[i2++]; + VECTOR(*eids)[idx++] = eid2; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(eids, length)); + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_is_same_graph + * \brief Are two graphs identical as labelled graphs? + * + * Two graphs are considered to be the same if they have the same vertex and edge sets. + * Graphs which are the same may have multiple different representations in igraph, + * hence the need for this function. + * + * + * This function verifies that the two graphs have the same directedness, the same + * number of vertices, and that they contain precisely the same edges (regardless of their ordering) + * when written in terms of vertex indices. Graph attributes are not taken into account. + * + * + * This concept is different from isomorphism. For example, the graphs + * 0-1, 2-1 and 1-2, 0-1 are considered the same + * because they only differ in the ordering of their edge lists and the ordering + * of vertices in an undirected edge. However, they are not the same as + * 0-2, 1-2, even though they are isomorphic to it. + * Note that this latter graph contains the edge 0-2 + * while the former two do not — thus their edge sets differ. + * + * \param graph1 The first graph object. + * \param graph2 The second graph object. + * \param res The result will be stored here. + * \return Error code. + * + * Time complexity: O(E), the number of edges in the graphs. + * + * \sa \ref igraph_isomorphic() to test if two graphs are isomorphic. + */ + +igraph_error_t igraph_is_same_graph(const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *res) { + igraph_int_t nv1 = igraph_vcount(graph1); + igraph_int_t nv2 = igraph_vcount(graph2); + igraph_int_t ne1 = igraph_ecount(graph1); + igraph_int_t ne2 = igraph_ecount(graph2); + igraph_int_t i, eid1, eid2; + + *res = false; /* Assume that the graphs differ */ + + /* Check for same number of vertices/edges */ + if ((nv1 != nv2) || (ne1 != ne2)) { + return IGRAPH_SUCCESS; + } + + /* Check for same directedness */ + if (igraph_is_directed(graph1) != igraph_is_directed(graph2)) { + return IGRAPH_SUCCESS; + } + + /* Vertices have no names, so they must be 0 to nv - 1 */ + + /* Edges are double sorted in the current representations ii/oi of + * igraph_t (ii: by incoming, then outgoing, oi: vice versa), so + * we just need to check them one by one. If that representation + * changes, this part will need to change too. + * + * Furthermore, in the current representation the "source" of undirected + * edges always has a vertex index that is no larger than that of the + * "target". + */ + for (i = 0; i < ne1; i++) { + eid1 = VECTOR(graph1->ii)[i]; + eid2 = VECTOR(graph2->ii)[i]; + + /* Check they have the same source */ + if (IGRAPH_FROM(graph1, eid1) != IGRAPH_FROM(graph2, eid2)) { + return IGRAPH_SUCCESS; + } + + /* Check they have the same target */ + if (IGRAPH_TO(graph1, eid1) != IGRAPH_TO(graph2, eid2)) { + return IGRAPH_SUCCESS; + } + } + + *res = true; /* No difference was found, graphs are the same */ + return IGRAPH_SUCCESS; +} + + +/* Reverses the direction of all edges in a directed graph. + * The graph is modified in-place. + * Attributes are preserved. + */ +igraph_error_t igraph_i_reverse(igraph_t *graph) { + + /* Nothing to do for undirected graphs. */ + if (! igraph_is_directed(graph)) { + return IGRAPH_SUCCESS; + } + + igraph_vector_int_swap(&graph->to, &graph->from); + igraph_vector_int_swap(&graph->oi, &graph->ii); + igraph_vector_int_swap(&graph->os, &graph->is); + + return IGRAPH_SUCCESS; +} diff --git a/src/graph/visitors.c b/src/graph/visitors.c new file mode 100644 index 0000000..1520d4b --- /dev/null +++ b/src/graph/visitors.c @@ -0,0 +1,661 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_visitor.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" +#include "igraph_dqueue.h" +#include "igraph_stack.h" + +/** + * \function igraph_bfs + * \brief Breadth-first search. + * + * A simple breadth-first search, with a lot of different results and + * the possibility to call a callback whenever a vertex is visited. + * It is allowed to supply null pointers as the output arguments the + * user is not interested in, in this case they will be ignored. + * + * + * If not all vertices can be reached from the supplied root vertex, + * then additional root vertices will be used, in the order of their + * vertex IDs. + * + * + * Consider using \ref igraph_bfs_simple instead if you set most of the output + * arguments provided by this function to a null pointer. + * + * \param graph The input graph. + * \param root The id of the root vertex. It is ignored if the \c + * roots argument is not a null pointer. + * \param roots Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then it is a vector + * containing root vertices to start the BFS from. The vertices + * are considered in the order they appear. If a root vertex + * was already found while searching from another one, then no + * search is conducted from it. + * \param mode For directed graphs, it defines which edges to follow. + * \c IGRAPH_OUT means following the direction of the edges, + * \c IGRAPH_IN means the opposite, and + * \c IGRAPH_ALL ignores the direction of the edges. + * This parameter is ignored for undirected graphs. + * \param unreachable Boolean, whether the search should visit + * the vertices that are unreachable from the given root + * node(s). If true, then additional searches are performed + * until all vertices are visited. + * \param restricted If not a null pointer, then it must be a pointer + * to a vector containing vertex IDs. The BFS is carried out + * only on these vertices. + * \param order If not null pointer, then the vertex IDs of the graph are + * stored here, in the same order as they were visited. + * \param rank If not a null pointer, then the rank of each vertex is + * stored here. + * \param parents If not a null pointer, then the id of the parent of + * each vertex is stored here. When a vertex was not visited + * during the traversal, -2 will be stored as the ID of its parent. + * When a vertex was visited during the traversal and it was one of + * the roots of the search trees, -1 will be stored as the ID of + * its parent. + * \param pred If not a null pointer, then the id of vertex that was + * visited before the current one is stored here. If there is + * no such vertex (the current vertex is the root of a search + * tree), then -1 is stored as the predecessor of the vertex. + * If the vertex was not visited at all, then -2 is stored for + * the predecessor of the vertex. + * \param succ If not a null pointer, then the id of the vertex that + * was visited after the current one is stored here. If there + * is no such vertex (the current one is the last in a search + * tree), then -1 is stored as the successor of the vertex. + * If the vertex was not visited at all, then -2 is stored for + * the successor of the vertex. + * \param dist If not a null pointer, then the distance from the root of + * the current search tree is stored here for each vertex. If a + * vertex was not reached during the traversal, its distance will + * be -1 in this vector. + * \param callback If not null, then it should be a pointer to a + * function of type \ref igraph_bfshandler_t. This function + * will be called, whenever a new vertex is visited. + * \param extra Extra argument to pass to the callback function. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_bfs.c + * \example examples/simple/igraph_bfs_callback.c + */ +igraph_error_t igraph_bfs(const igraph_t *graph, + igraph_int_t root, const igraph_vector_int_t *roots, + igraph_neimode_t mode, igraph_bool_t unreachable, + const igraph_vector_int_t *restricted, + igraph_vector_int_t *order, igraph_vector_int_t *rank, + igraph_vector_int_t *parents, + igraph_vector_int_t *pred, igraph_vector_int_t *succ, + igraph_vector_int_t *dist, igraph_bfshandler_t *callback, + void *extra) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + + igraph_error_t ret; + + igraph_dqueue_int_t Q; + igraph_int_t actroot = 0; + igraph_bitset_t added; + + igraph_lazy_adjlist_t adjlist; + + igraph_int_t act_rank = 0; + igraph_int_t pred_vec = -1; + + igraph_int_t rootpos = 0; + igraph_int_t noroots = roots ? igraph_vector_int_size(roots) : 1; + + if (!roots && (root < 0 || root >= no_of_nodes)) { + IGRAPH_ERROR("Invalid root vertex in BFS.", IGRAPH_EINVVID); + } + + if (roots && !igraph_vector_int_isininterval(roots, 0, no_of_nodes-1)) { + IGRAPH_ERROR("Invalid root vertex in BFS.", IGRAPH_EINVVID); + } + + if (restricted && !igraph_vector_int_isininterval(restricted, 0, no_of_nodes-1)) { + IGRAPH_ERROR("Invalid vertex ID in restricted set.", IGRAPH_EINVVID); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_BITSET_INIT_FINALLY(&added, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + /* Mark the vertices that are not in the restricted set, as already + found. Special care must be taken for vertices that are not in + the restricted set, but are to be used as 'root' vertices. */ + if (restricted) { + igraph_int_t i, n = igraph_vector_int_size(restricted); + igraph_bitset_fill(&added, true); + for (i = 0; i < n; i++) { + igraph_int_t v = VECTOR(*restricted)[i]; + IGRAPH_BIT_CLEAR(added, v); + } + } + + /* Resize result vectors, and fill them with the initial value. */ + +# define VINIT(v, initial) \ + if (v) { \ + IGRAPH_CHECK(igraph_vector_int_resize((v), no_of_nodes)); \ + igraph_vector_int_fill((v), initial); \ + } + + VINIT(order, -1); + VINIT(rank, -1); + VINIT(parents, -2); + VINIT(pred, -2); + VINIT(succ, -2); + VINIT(dist, -1); +# undef VINIT + + while (1) { + + /* Get the next root vertex, if any */ + + if (roots && rootpos < noroots) { + /* We are still going through the 'roots' vector */ + actroot = VECTOR(*roots)[rootpos++]; + } else if (!roots && rootpos == 0) { + /* We have a single root vertex given, and start now */ + actroot = root; + rootpos++; + } else if (rootpos == noroots && unreachable) { + /* We finished the given root(s), but other vertices are also + tried as root */ + actroot = 0; + rootpos++; + } else if (unreachable && actroot + 1 < no_of_nodes) { + /* We are already doing the other vertices, take the next one */ + actroot++; + } else { + /* No more root nodes to do */ + break; + } + + /* OK, we have a new root, start BFS */ + if (IGRAPH_BIT_TEST(added, actroot)) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, actroot)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); + IGRAPH_BIT_SET(added, actroot); + if (parents) { + VECTOR(*parents)[actroot] = -1; + } + + pred_vec = -1; + + while (!igraph_dqueue_int_empty(&Q)) { + igraph_int_t actvect = igraph_dqueue_int_pop(&Q); + igraph_int_t actdist = igraph_dqueue_int_pop(&Q); + igraph_int_t succ_vec; + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, actvect); + + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + const igraph_int_t n = igraph_vector_int_size(neis); + + if (pred) { + VECTOR(*pred)[actvect] = pred_vec; + } + if (rank) { + VECTOR(*rank)[actvect] = act_rank; + } + if (order) { + VECTOR(*order)[act_rank++] = actvect; + } + if (dist) { + VECTOR(*dist)[actvect] = actdist; + } + + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + if (! IGRAPH_BIT_TEST(added, nei)) { + IGRAPH_BIT_SET(added, nei); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, actdist + 1)); + if (parents) { + VECTOR(*parents)[nei] = actvect; + } + } + } + + succ_vec = igraph_dqueue_int_empty(&Q) + ? -1 + : igraph_dqueue_int_head(&Q); + if (callback) { + IGRAPH_CHECK_CALLBACK( + callback(graph, actvect, pred_vec, succ_vec, act_rank - 1, actdist, extra), + &ret + ); + + if (ret == IGRAPH_STOP) { + goto cleanup; + } + } + + if (succ) { + VECTOR(*succ)[actvect] = succ_vec; + } + pred_vec = actvect; + + } /* while Q !empty */ + + } /* for actroot < no_of_nodes */ + +cleanup: + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_dqueue_int_destroy(&Q); + igraph_bitset_destroy(&added); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bfs_simple + * Breadth-first search, single-source version + * + * An alternative breadth-first search implementation to cater for the + * simpler use-cases when only a single breadth-first search has to be conducted + * from a source node and most of the output arguments from \ref igraph_bfs + * are not needed. It is allowed to supply null pointers as + * the output arguments the user is not interested in, in this case they will + * be ignored. + * + * \param graph The input graph. + * \param root The id of the root vertex. + * \param mode For directed graphs, it defines which edges to follow. + * \c IGRAPH_OUT means following the direction of the edges, + * \c IGRAPH_IN means the opposite, and + * \c IGRAPH_ALL ignores the direction of the edges. + * This parameter is ignored for undirected graphs. + * \param order If not a null pointer, then an initialized vector must be passed + * here. The IDs of the vertices visited during the traversal will be + * stored here, in the same order as they were visited. + * \param layers If not a null pointer, then an initialized vector must be + * passed here. The i-th element of the vector will contain the index + * into \c order where the vertices that are at distance i from the root + * are stored. In other words, if you are interested in the vertices that + * are at distance i from the root, you need to look in the \c order + * vector from \c layers[i] to \c layers[i+1]. + * \param parents If not a null pointer, then an initialized vector must be + * passed here. The vector will be resized so its length is equal to the + * number of nodes, and it will contain the index of the parent node for + * each \em visited node. The values in the vector are set to -2 for + * vertices that were \em not visited, and -1 for the root vertex. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_bfs_simple.c + */ +igraph_error_t igraph_bfs_simple( + const igraph_t *graph, igraph_int_t root, igraph_neimode_t mode, + igraph_vector_int_t *order, igraph_vector_int_t *layers, + igraph_vector_int_t *parents +) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_int_t num_visited = 0; + igraph_vector_int_t neis; + igraph_bitset_t added; + igraph_int_t lastlayer = -1; + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + /* temporary storage */ + + IGRAPH_BITSET_INIT_FINALLY(&added, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + + /* results */ + if (order) { + igraph_vector_int_clear(order); + } + if (layers) { + igraph_vector_int_clear(layers); + } + if (parents) { + IGRAPH_CHECK(igraph_vector_int_resize(parents, no_of_nodes)); + igraph_vector_int_fill(parents, -2); + } + + /* ok start with root */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, root)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + if (layers) { + IGRAPH_CHECK(igraph_vector_int_push_back(layers, num_visited)); + } + if (order) { + IGRAPH_CHECK(igraph_vector_int_push_back(order, root)); + } + if (parents) { + VECTOR(*parents)[root] = -1; + } + num_visited++; + IGRAPH_BIT_SET(added, root); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actvect = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, actvect, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t nei_count = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < nei_count; i++) { + const igraph_int_t neighbor = VECTOR(neis)[i]; + if (! IGRAPH_BIT_TEST(added, neighbor)) { + IGRAPH_BIT_SET(added, neighbor); + if (parents) { + VECTOR(*parents)[neighbor] = actvect; + } + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (layers && lastlayer != actdist + 1) { + IGRAPH_CHECK(igraph_vector_int_push_back(layers, num_visited)); + } + if (order) { + IGRAPH_CHECK(igraph_vector_int_push_back(order, neighbor)); + } + num_visited++; + lastlayer = actdist + 1; + } + } /* for i in neis */ + } /* while ! dqueue_int_empty */ + + if (layers) { + IGRAPH_CHECK(igraph_vector_int_push_back(layers, num_visited)); + } + + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&q); + igraph_bitset_destroy(&added); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_dfs + * \brief Depth-first search. + * + * A simple depth-first search, with + * the possibility to call a callback whenever a vertex is discovered + * and/or whenever a subtree is finished. + * It is allowed to supply null pointers as the output arguments the + * user is not interested in, in this case they will be ignored. + * + * + * If not all vertices can be reached from the supplied root vertex, + * then additional root vertices will be used, in the order of their + * vertex IDs. + * + * \param graph The input graph. + * \param root The id of the root vertex. + * \param mode For directed graphs, it defines which edges to follow. + * \c IGRAPH_OUT means following the direction of the edges, + * \c IGRAPH_IN means the opposite, and + * \c IGRAPH_ALL ignores the direction of the edges. + * This parameter is ignored for undirected graphs. + * \param unreachable Boolean, whether the search should visit + * the vertices that are unreachable from the given root + * node(s). If true, then additional searches are performed + * until all vertices are visited. + * \param order If not null pointer, then the vertex IDs of the graph are + * stored here, in the same order as they were discovered. The tail of + * the vector will be padded with -1 to ensure that the length of the + * vector is the same as the number of vertices, even if some vertices + * were not visited during the traversal. + * \param order_out If not a null pointer, then the vertex IDs of the + * graphs are stored here, in the order of the completion of + * their subtree. The tail of the vector will be padded with -1 to ensure + * that the length of the vector is the same as the number of vertices, + * even if some vertices were not visited during the traversal. + * \param parents If not a null pointer, then the id of the parent of + * each vertex is stored here. -1 will be stored for the root of the + * search tree; -2 will be stored for vertices that were not visited. + * \param dist If not a null pointer, then the distance from the root of + * the current search tree is stored here. -1 will be stored for vertices + * that were not visited. + * \param in_callback If not null, then it should be a pointer to a + * function of type \ref igraph_dfshandler_t. This function + * will be called, whenever a new vertex is discovered. + * \param out_callback If not null, then it should be a pointer to a + * function of type \ref igraph_dfshandler_t. This function + * will be called, whenever the subtree of a vertex is completed. + * \param extra Extra argument to pass to the callback function(s). + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_dfs(const igraph_t *graph, igraph_int_t root, + igraph_neimode_t mode, igraph_bool_t unreachable, + igraph_vector_int_t *order, + igraph_vector_int_t *order_out, igraph_vector_int_t *parents, + igraph_vector_int_t *dist, igraph_dfshandler_t *in_callback, + igraph_dfshandler_t *out_callback, + void *extra) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_lazy_adjlist_t adjlist; + igraph_stack_int_t stack; + igraph_bitset_t added; + igraph_vector_int_t nptr; + igraph_error_t ret; + igraph_int_t act_rank = 0; + igraph_int_t rank_out = 0; + igraph_int_t act_dist = 0; + + if (root < 0 || root >= no_of_nodes) { + IGRAPH_ERROR("Invalid root vertex for DFS.", IGRAPH_EINVAL); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_BITSET_INIT_FINALLY(&added, no_of_nodes); + IGRAPH_STACK_INT_INIT_FINALLY(&stack, 100); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&nptr, no_of_nodes); + +# define FREE_ALL() do { \ + igraph_vector_int_destroy(&nptr); \ + igraph_lazy_adjlist_destroy(&adjlist); \ + igraph_stack_int_destroy(&stack); \ + igraph_bitset_destroy(&added); \ + IGRAPH_FINALLY_CLEAN(4); } while (0) + + /* Resize result vectors and fill them with the initial value */ + +# define VINIT(v, initial) if (v) { \ + IGRAPH_CHECK(igraph_vector_int_resize(v, no_of_nodes)); \ + igraph_vector_int_fill(v, initial); } + + VINIT(order, -1); + VINIT(order_out, -1); + VINIT(parents, -2); + VINIT(dist, -1); + +# undef VINIT + + IGRAPH_CHECK(igraph_stack_int_push(&stack, root)); + IGRAPH_BIT_SET(added, root); + if (parents) { + VECTOR(*parents)[root] = -1; + } + if (order) { + VECTOR(*order)[act_rank++] = root; + } + if (dist) { + VECTOR(*dist)[root] = 0; + } + if (in_callback) { + IGRAPH_CHECK_CALLBACK(in_callback(graph, root, 0, extra), &ret); + if (ret == IGRAPH_STOP) { + FREE_ALL(); + return IGRAPH_SUCCESS; + } + } + + for (igraph_int_t actroot = 0; actroot < no_of_nodes; ) { + + /* 'root' first, then all other vertices */ + if (igraph_stack_int_empty(&stack)) { + if (!unreachable) { + break; + } + if (IGRAPH_BIT_TEST(added, actroot)) { + actroot++; + continue; + } + IGRAPH_CHECK(igraph_stack_int_push(&stack, actroot)); + IGRAPH_BIT_SET(added, actroot); + if (parents) { + VECTOR(*parents)[actroot] = -1; + } + if (order) { + VECTOR(*order)[act_rank++] = actroot; + } + if (dist) { + VECTOR(*dist)[actroot] = 0; + } + + if (in_callback) { + IGRAPH_CHECK_CALLBACK(in_callback(graph, actroot, 0, extra), &ret); + if (ret == IGRAPH_STOP) { + FREE_ALL(); + return IGRAPH_SUCCESS; + } + } + + actroot++; + } + + while (!igraph_stack_int_empty(&stack)) { + igraph_int_t actvect = igraph_stack_int_top(&stack); + igraph_int_t *ptr = igraph_vector_int_get_ptr(&nptr, actvect); + + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, actvect); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + + const igraph_int_t n = igraph_vector_int_size(neis); + + /* Search for a neighbor that was not yet visited */ + igraph_bool_t any = false; + igraph_int_t nei = 0; + while (!any && (*ptr) < n) { + nei = VECTOR(*neis)[(*ptr)]; + any = !IGRAPH_BIT_TEST(added, nei); + (*ptr) ++; + } + if (any) { + /* There is such a neighbor, add it */ + IGRAPH_CHECK(igraph_stack_int_push(&stack, nei)); + IGRAPH_BIT_SET(added, nei); + if (parents) { + VECTOR(*parents)[ nei ] = actvect; + } + if (order) { + VECTOR(*order)[act_rank++] = nei; + } + act_dist++; + if (dist) { + VECTOR(*dist)[nei] = act_dist; + } + + if (in_callback) { + IGRAPH_CHECK_CALLBACK( + in_callback(graph, nei, act_dist, extra), + &ret + ); + if (ret == IGRAPH_STOP) { + FREE_ALL(); + return IGRAPH_SUCCESS; + } + } + + } else { + /* There is no such neighbor, finished with the subtree */ + igraph_stack_int_pop(&stack); + if (order_out) { + VECTOR(*order_out)[rank_out++] = actvect; + } + act_dist--; + + if (out_callback) { + IGRAPH_CHECK_CALLBACK( + out_callback(graph, actvect, act_dist, extra), + &ret + ); + + if (ret == IGRAPH_STOP) { + FREE_ALL(); + return IGRAPH_SUCCESS; + } + } + } + } + } + + FREE_ALL(); +# undef FREE_ALL + + return IGRAPH_SUCCESS; +} diff --git a/src/hrg/dendro.h b/src/hrg/dendro.h new file mode 100644 index 0000000..77d4003 --- /dev/null +++ b/src/hrg/dendro.h @@ -0,0 +1,296 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// dendro_eq.h - hierarchical random graph (hrg) data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 19 April 2006 +// Modified : 19 May 2007 +// : 19 May 2008 (cleaned up for public consumption) +// +// **************************************************************************************************** +// +// Maximum likelihood dendrogram data structure. This is the heart of the HRG algorithm: all +// manipulations are done here and all data is stored here. The data structure uses the separate +// graph data structure to store the basic adjacency information (in a dangerously mutable way). +// +// Note: This version (dendro_eq.h) differs from other versions because it includes methods for +// doing the consensus dendrogram calculation. +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_DENDRO +#define IGRAPH_HRG_DENDRO + +#include "hrg/graph.h" +#include "hrg/rbtree.h" +#include "hrg/splittree_eq.h" + +#include "igraph_hrg.h" + +#include +#include + +using std::string; + +namespace fitHRG { + +// *********************************************************************** +// ******** Basic Structures ********************************************* + +enum {DENDRO, GRAPH, LEFT, RIGHT}; + +struct block { + double x; + int y; +}; + +struct ipair { + int x; + int y; + short int t; + string sp; +}; + +struct child { + int index; + short int type; + child* next; +}; + +// *********************************************************************** +// ******** Cnode Class ************************************************** + +struct cnode { + int index = -1; // array index of this node + int degree = 0; // number of children in list + int parent = -1; // index of parent node + double weight = 0.0; // sampled posterior weight + child* children = nullptr; // list of children (and their types) + child* lastChild = nullptr; // pointer to last child in list + cnode() = default; + cnode(const cnode &) = delete; + cnode & operator = (const cnode &) = delete; + ~cnode() { + child *curr = children; + while (curr != nullptr) { + child *prev = curr; + curr = curr->next; + delete prev; + } + lastChild = nullptr; + } +}; + +// *********************************************************************** +// ******** Split Class ************************************************** + +class split { +public: + string s; // partition assignment of leaf vertices + split() = default; + void initializeSplit(const int n) { + s = ""; + for (int i = 0; i < n; i++) { + s += "-"; + } + } + bool checkSplit() const { + if (s.empty() || s.find('-', 0) != string::npos) { + return false; + } else { + return true; + } + } +}; + +// *********************************************************************** +// ******** Internal Edge Class ****************************************** +// The usefulness of this data structure is to provide an easy to way +// maintain the set of internal edges, and the corresponding splits, +// in the dendrogram D. It allows for the selection of a random +// internal edge in O(1) time, and it takes O(1) time to update its +// structure given an internal move. This structure does not provide +// any means to directly manipulate the splits, but does allow them to +// be replaced. A split has the form "int.int...int#int.int...int", +// where all ints on the left side of the # are in the left partition +// and all ints on the right side of the # marker are in the right +// partition defined by the split. + +class interns { + ipair* edgelist; // list of internal edges represented + string* splitlist; // split representation of the internal edges + int** indexLUT; // table of indices of internal edges in edgelist + int q; // number of internal edges + int count; // (for adding edges) edgelist index of new edge to add +public: + explicit interns(int); + ~interns(); + + // add an internal edge, O(1) + bool addEdge(int, int, short int); + // returns the ith edge of edgelist, O(1) + ipair* getEdge(int) const; + // returns a uniformly random internal edge, O(1) + ipair* getRandomEdge() const; + // returns the ith split of the splitlist, O(1) + string getSplit(int) const; + // replace an existing split, O(1) + bool replaceSplit(int i, const string &sp); + // swaps two edges, O(1) + bool swapEdges(int, int, short int, int, int, short int); +}; + +// *********************************************************************** +// ******** Tree elementd Class ****************************************** + +struct elementd { + short int type = DENDRO; // either DENDRO or GRAPH + double logL = 0.0; // log-likelihood contribution of this internal node + double p = 0.0; // probability p_i that an edge exists between L and + // R subtrees + int e = 0; // number of edges between L and R subtrees + int n = 0; // number of leafs in subtree rooted here + int label = -1; // subtree label: smallest leaf index + int index = -1; // index in containing array + + elementd *M = nullptr; // pointer to parent node + elementd *L = nullptr; // pointer for L subtree + elementd *R = nullptr; // pointer for R subtree +}; + +// *********************************************************************** +// ******** Dendrogram Class ********************************************* + +class dendro { + elementd* root = nullptr; // root of the dendrogram + elementd* internal = nullptr; // array of n-1 internal vertices (the dendrogram D) + elementd* leaf = nullptr; // array of n leaf vertices (the graph G) + int n; // number of leaf vertices to allocate + interns* d = nullptr; // list of internal edges of dendrogram D + splittree* splithist = nullptr; // histogram of cumulative split weights + list** paths = nullptr; // array of path-lists from root to leaf + double L; // log-likelihood of graph G given dendrogram D + rbtree subtreeL, subtreeR; // trees for computeEdgeCount() function + cnode* ctree = nullptr; // (consensus tree) array of internal tree nodes + int* cancestor = nullptr; // (consensus tree) oldest ancetor's index for + // each leaf + + // insert node i according to binary search property + void binarySearchInsert(elementd*, elementd*); + // return path to root from leaf + list* binarySearchFind(double) const; + // build split for this internal edge + string buildSplit(elementd*) const; + // compute number of edges between two internal subtrees + int computeEdgeCount(int, short int, int, short int); + // (consensus tree) counts children + static size_t countChildren(const string &s); + // find internal node of D that is common ancestor of i,j + elementd* findCommonAncestor(list**, int, int); + // return reverse of path to leaf from root + list* reversePathToRoot(int); + // quicksort functions + static void QsortMain(block*, int, int); + static int QsortPartition(block*, int, int, int); + + // underlying G (dangerously accessible) + graph* g = nullptr; + +public: + + // constructor / destructor + dendro() = default; + dendro(const dendro &) = delete; + dendro & operator = (const dendro &) = delete; + ~dendro(); + + igraph_error_t setGraph(const igraph_t *igraph); + void setGraph(graph *ig) { g = ig; } + const graph *getGraph() const { return g; } + + // build dendrogram from g + void buildDendrogram(); + // delete dendrograph in prep for importDendrogramStructure + void clearDendrograph(); + // read dendrogram structure from HRG structure + bool importDendrogramStructure(const igraph_hrg_t *hrg); + // (consensus tree) delete splits with less than 0.5 weight + void cullSplitHist(); + // return size of consensus split + int getConsensusSize(); + // return split tree with consensus splits + splittree* getConsensusSplits() const; + // return likelihood of G given D + double getLikelihood() const; + // store splits in this splittree + void getSplitList(splittree*) const; + // return total weight of splittree + double getSplitTotalWeight() const; + // make random G from D + void makeRandomGraph(); + // make single MCMC move + void monteCarloMove(double &, bool &, double); + // record consensus tree from splithist + void recordConsensusTree(igraph_vector_int_t *parents, + igraph_vector_t *weights); + // record D structure + void recordDendrogramStructure(igraph_hrg_t *hrg) const noexcept; + // record G structure to igraph graph + igraph_error_t recordGraphStructure(igraph_t *graph) const noexcept; + // force refresh of log-likelihood value + void refreshLikelihood(); + // sample dendrogram edge likelihoods and update edge histograms + void sampleAdjacencyLikelihoods(); + // reset the dendrograph structures + void resetDendrograph(); + // sample dendrogram's splits and update the split histogram + bool sampleSplitLikelihoods(); +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg/graph.h b/src/hrg/graph.h new file mode 100644 index 0000000..09f379d --- /dev/null +++ b/src/hrg/graph.h @@ -0,0 +1,156 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// graph.h - graph data structure for hierarchical random graphs +// Copyright (C) 2005-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 8 November 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// **************************************************************************************************** +// +// Graph data structure for hierarchical random graphs. The basic structure is an adjacency list of +// edges; however, many additional pieces of metadata are stored as well. Each node stores its +// external name, its degree and (if assigned) its group index. +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_GRAPH +#define IGRAPH_HRG_GRAPH + +#include +#include "hrg/rbtree.h" + +#include +#include +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +struct edge { + int x = -1; // stored integer value (edge terminator) + double* h = nullptr; // (histogram) weights of edge existence + double total_weight = 0.0; // (histogram) total weight observed + int obs_count = 0; // number of observations in histogram + edge* next = nullptr; // pointer to next elementd + edge() = default; + edge(const edge &) = delete; + edge & operator = (const edge &) = delete; + ~edge() { + delete [] h; + } +}; + +struct vert { + std::string name; // (external) name of vertex + int degree = 0; // degree of this vertex +}; + +// ******** Graph Class with Edge Statistics ***************************** + +class graph { +public: + explicit graph(int, bool predict = false); + ~graph(); + + // add (i,j) to graph + bool addLink(int, int); + // add weight to (i,j)'s histogram + bool addAdjacencyObs(int, int, double, double); + // add to obs_count and total_weight + void addAdjacencyEnd(); + // true if (i,j) is already in graph + bool doesLinkExist(int, int) const; + // returns degree of vertex i + int getDegree(int) const; + // returns name of vertex i + std::string getName(int) const; + // returns edge list of vertex i + const edge* getNeighborList(int) const noexcept; + // return ptr to histogram of edge (i,j) + double* getAdjacencyHist(int, int) const; + // return average value of adjacency A(i,j) + double getAdjacencyAverage(int, int) const; + // returns bin_resolution + double getBinResolution() const; + // returns num_bins + int getNumBins() const; + // returns m + int numLinks() const; + // returns n + int numNodes() const; + // returns total_weight + double getTotalWeight() const; + // reset edge (i,j)'s histogram + void resetAdjacencyHistogram(int, int); + // reset all edge histograms + void resetAllAdjacencies(); + // clear all links from graph + void resetLinks(); + // allocate edge histograms + void setAdjacencyHistograms(igraph_int_t); + // set name of vertex i + bool setName(int, const std::string &); + +private: + bool predict; // do we need prediction? + vert* nodes; // list of nodes + edge** nodeLink; // linked list of neighbors to vertex + edge** nodeLinkTail; // pointers to tail of neighbor list + double*** A = nullptr; // stochastic adjacency matrix for this graph + int obs_count; // number of observations in A + double total_weight; // total weight added to A + int n; // number of vertices + int m; // number of directed edges + int num_bins; // number of bins in edge histograms + double bin_resolution; // width of histogram bin +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg/graph_simp.h b/src/hrg/graph_simp.h new file mode 100644 index 0000000..2d658c4 --- /dev/null +++ b/src/hrg/graph_simp.h @@ -0,0 +1,142 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// graph_simp.h - graph data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 21 June 2006 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// ************************************************************************ +// +// Simple graph data structure. The basic structure is an adjacency +// list of edges, along with degree information for the vertices. +// +// ************************************************************************ + +#ifndef IGRAPH_HRG_SIMPLEGRAPH +#define IGRAPH_HRG_SIMPLEGRAPH + +#include "hrg/rbtree.h" +#include "hrg/dendro.h" + +#include + +#include +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +struct simpleEdge { + int x = -1; // index of edge terminator + simpleEdge* next = nullptr; // pointer to next elementd +}; + +struct simpleVert { + std::string name; // (external) name of vertex + int degree = 0; // degree of this vertex + int group_true = -1; // index of vertex's true group +}; + +struct twoEdge { + int o = -1; // index of edge originator + int x = -1; // index of edge terminator +}; + +// ******** Graph Class with Edge Statistics ***************************** + +class simpleGraph { +public: + explicit simpleGraph(int); + ~simpleGraph(); + + // add group label to vertex i + bool addGroup(int, int); + // add (i,j) to graph + bool addLink(int, int); + // true if (i,j) is already in graph + bool doesLinkExist(int, int) const; + // returns A(i,j) + double getAdjacency(int, int) const; + // returns degree of vertex i + int getDegree(int) const; + // returns group label of vertex i + int getGroupLabel(int) const; + // returns name of vertex i + std::string getName(int) const; + // returns edge list of vertex i + const simpleEdge* getNeighborList(int) const; + // return pointer to a node + const simpleVert* getNode(int) const; + // returns num_groups + int getNumGroups() const; + // returns m + int getNumLinks() const; + // returns n + int getNumNodes() const; + // set name of vertex i + bool setName(int i, const std::string &text); + +private: + simpleVert* nodes; // list of nodes + simpleEdge** nodeLink; // linked list of neighbors to vertex + simpleEdge** nodeLinkTail; // pointers to tail of neighbor list + double** A; // adjacency matrix for this graph + twoEdge* E; // list of all edges (array) + int n; // number of vertices + int m; // number of directed edges + int num_groups; // number of bins in node histograms + + // quicksort functions + static void QsortMain(block*, int, int); + static int QsortPartition(block*, int, int, int); +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg/hrg.cc b/src/hrg/hrg.cc new file mode 100644 index 0000000..a430d7f --- /dev/null +++ b/src/hrg/hrg.cc @@ -0,0 +1,1074 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_attributes.h" +#include "igraph_hrg.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "hrg/dendro.h" +#include "hrg/graph.h" +#include "hrg/graph_simp.h" + +#include "core/exceptions.h" + +#include +#include + +using namespace fitHRG; + +/** + * \section hrg_intro Introduction + * + * A hierarchical random graph is an ensemble of undirected + * graphs with \c n vertices. It is defined via a binary tree with \c + * n leaf and \c n-1 internal vertices, where the + * internal vertices are labeled with probabilities. + * The probability that two vertices are connected in the random graph + * is given by the probability label at their closest common + * ancestor. + * + * + * Please read the following two articles for more about + * hierarchical random graphs: A. Clauset, C. Moore, and M.E.J. Newman. + * Hierarchical structure and the prediction of missing links in networks. + * Nature 453, 98 - 101 (2008); and A. Clauset, C. Moore, and M.E.J. Newman. + * Structural Inference of Hierarchies in Networks. In E. M. Airoldi + * et al. (Eds.): ICML 2006 Ws, Lecture Notes in Computer Science + * 4503, 1-13. Springer-Verlag, Berlin Heidelberg (2007). + * + * + * + * igraph contains functions for fitting HRG models to a given network + * (\ref igraph_hrg_fit), for generating networks from a given HRG + * ensemble (\ref igraph_hrg_game, \ref igraph_hrg_sample), converting + * an igraph graph to a HRG and back (\ref igraph_hrg_create, \ref + * igraph_hrg_dendrogram), for calculating a consensus tree from a + * set of sampled HRGs (\ref igraph_hrg_consensus) and for predicting + * missing edges in a network based on its HRG models (\ref + * igraph_hrg_predict). + * + * + * The igraph HRG implementation is heavily based on the code + * published by Aaron Clauset, at his website, + * https://aaronclauset.github.io/hierarchy/ + * + */ + +namespace fitHRG { +struct pblock { + double L; + int i; + int j; +}; +} + +static void markovChainMonteCarlo(dendro &d, const igraph_int_t period, + igraph_hrg_t *hrg) { + + igraph_real_t bestL = d.getLikelihood(); + double dL; + bool flag_taken; + + // Because moves in the dendrogram space are chosen (Monte + // Carlo) so that we sample dendrograms with probability + // proportional to their likelihood, a likelihood-proportional + // sampling of the dendrogram models would be equivalent to a + // uniform sampling of the walk itself. We would still have to + // decide how often to sample the walk (at most once every n + // steps is recommended) but for simplicity, the code here + // simply runs the MCMC itself. To actually compute something + // over the set of sampled dendrogram models (in a Bayesian + // model averaging sense), you'll need to code that yourself. + + // do 'period' MCMC moves before doing anything else + for (igraph_int_t i = 0; i < period; i++) { + + // make a MCMC move + d.monteCarloMove(dL, flag_taken, 1.0); + + // get likelihood of this D given G + igraph_real_t cl = d.getLikelihood(); + if (cl > bestL) { + // store the current best likelihood + bestL = cl; + // record the HRG structure + d.recordDendrogramStructure(hrg); + } + } + // corrects floating-point errors O(n) + d.refreshLikelihood(); +} + +static void markovChainMonteCarlo2(dendro &d, const int num_samples) { + bool flag_taken; + double dL; + const double ptest = 1.0 / (50.0 * static_cast(d.getGraph()->numNodes())); + igraph_int_t sample_num = 0; + int t = 1; + const int thresh = 200 * d.getGraph()->numNodes(); + + // Since we're sampling uniformly at random over the equilibrium + // walk, we just need to do a bunch of MCMC moves and let the + // sampling happen on its own. + while (sample_num < num_samples) { + // Make a single MCMC move + d.monteCarloMove(dL, flag_taken, 1.0); + + // We sample the dendrogram space once every n MCMC moves (on + // average). Depending on the flags on the command line, we sample + // different aspects of the dendrograph structure. + if (t > thresh && RNG_UNIF01() < ptest) { + sample_num++; + d.sampleSplitLikelihoods(); + } + + t++; + + // correct floating-point errors O(n) + d.refreshLikelihood(); // TODO: less frequently + } +} + +static void MCMCEquilibrium_Find(dendro &d, igraph_hrg_t *hrg) { + + // We want to run the MCMC until we've found equilibrium; we + // use the heuristic of the average log-likelihood (which is + // exactly the entropy) over X steps being very close to the + // average log-likelihood (entropy) over the X steps that + // preceded those. In other words, we look for an apparent + // local convergence of the entropy measure of the MCMC. + + bool flag_taken; + igraph_real_t dL; + igraph_real_t newMeanL = -1e-49; + + while (true) { + const igraph_real_t oldMeanL = newMeanL; + newMeanL = 0.0; + for (int i = 0; i < 65536; i++) { + d.monteCarloMove(dL, flag_taken, 1.0); + const igraph_real_t Likeli = d.getLikelihood(); + newMeanL += Likeli; + } + // corrects floating-point errors O(n) + d.refreshLikelihood(); + if (fabs(newMeanL - oldMeanL) / 65536.0 < 1.0) { + break; + } + } + + // Record the result + if (hrg) { + d.recordDendrogramStructure(hrg); + } +} + +igraph_error_t dendro::setGraph(const igraph_t *igraph) { + igraph_int_t no_of_nodes = igraph_vcount(igraph); + igraph_int_t no_of_edges = igraph_ecount(igraph); + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph too large for the HRG module.", IGRAPH_EOVERFLOW); + } + + // TODO: Can this be relaxed? buildDendrogram() creates a tree with n-2 internal edges, + // i.e. zero internal edges for a 2-vertex graph. This is not handled at the moment. + if (no_of_nodes < 3) { + IGRAPH_ERROR("Graph must have at least 3 vertices for HRG, got only %" IGRAPH_PRId " vertices.", IGRAPH_EINVAL); + } + + // Create graph + g = new graph(no_of_nodes); + + // Add edges + for (igraph_int_t i = 0; i < no_of_edges; i++) { + int from = IGRAPH_FROM(igraph, i); + int to = IGRAPH_TO(igraph, i); + if (from == to) { + continue; + } + if (!g->doesLinkExist(from, to)) { + g->addLink(from, to); + } + if (!g->doesLinkExist(to, from)) { + g->addLink(to, from); + } + } + + buildDendrogram(); + + return IGRAPH_SUCCESS; +} + +static std::unique_ptr igraph_i_hrg_getsimplegraph(const igraph_t *igraph, + dendro &d, + igraph_int_t num_bins) { + + const igraph_int_t no_of_nodes = igraph_vcount(igraph); + const igraph_int_t no_of_edges = igraph_ecount(igraph); + + // TODO replace the following throw's with IGRAPH_ERROR + + if (no_of_nodes > INT_MAX) { + throw std::runtime_error("Graph too large for the HRG module."); + } + + // TODO: Can this be relaxed? buildDendrogram() creates a tree with n-2 internal edges, + // i.e. zero internal edges for a 2-vertex graph. This is not handled at the moment. + if (no_of_nodes < 3) { + throw std::runtime_error("Graph must have at least 3 vertices for HRG."); + } + + // Create graphs + std::unique_ptr g(new graph(no_of_nodes, true)); + g->setAdjacencyHistograms(num_bins); + + std::unique_ptr sg(new simpleGraph(no_of_nodes)); + + for (igraph_int_t i = 0; i < no_of_edges; i++) { + int from = (int) IGRAPH_FROM(igraph, i); + int to = (int) IGRAPH_TO(igraph, i); + if (from == to) { + continue; + } + if (!g->doesLinkExist(from, to)) { + g->addLink(from, to); + } + if (!g->doesLinkExist(to, from)) { + g->addLink(to, from); + } + if (! sg->doesLinkExist(from, to)) { + sg->addLink(from, to); + } + if (! sg->doesLinkExist(to, from)) { + sg->addLink(to, from); + } + } + + d.setGraph(g.release()); + d.buildDendrogram(); + + return sg; +} + +/** + * \function igraph_hrg_init + * \brief Allocate memory for a HRG. + * + * This function must be called before passing an \ref igraph_hrg_t to + * an igraph function. + * + * \param hrg Pointer to the HRG data structure to initialize. + * \param n The number of vertices in the graph that is modeled by + * this HRG. It can be zero, if this is not yet known. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the graph. + */ + +igraph_error_t igraph_hrg_init(igraph_hrg_t *hrg, igraph_int_t n) { + if (n < 0) { + IGRAPH_ERRORF("Number of vertices should not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, n); + } + if (n == 0) { + n = 1; + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&hrg->left, n - 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&hrg->right, n - 1); + IGRAPH_VECTOR_INIT_FINALLY (&hrg->prob, n - 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&hrg->edges, n - 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&hrg->vertices, n - 1); + IGRAPH_FINALLY_CLEAN(5); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hrg_destroy + * \brief Deallocate memory for an HRG. + * + * The HRG data structure can be reinitialized again with an \ref + * igraph_hrg_destroy call. + * + * \param hrg Pointer to the HRG data structure to deallocate. + * + * Time complexity: operating system dependent. + */ + +void igraph_hrg_destroy(igraph_hrg_t *hrg) { + igraph_vector_int_destroy(&hrg->left); + igraph_vector_int_destroy(&hrg->right); + igraph_vector_destroy(&hrg->prob); + igraph_vector_int_destroy(&hrg->edges); + igraph_vector_int_destroy(&hrg->vertices); +} + +/** + * \function igraph_hrg_size + * \brief Returns the size of the HRG, the number of leaf nodes. + * + * \param hrg Pointer to the HRG. + * \return The number of leaf nodes in the HRG. + * + * Time complexity: O(1). + */ + +igraph_int_t igraph_hrg_size(const igraph_hrg_t *hrg) { + return igraph_vector_int_size(&hrg->left) + 1; +} + +/** + * \function igraph_hrg_resize + * \brief Resize a HRG. + * + * \param hrg Pointer to an initialized (see \ref igraph_hrg_init) + * HRG. + * \param newsize The new size, i.e. the number of leaf nodes. + * \return Error code. + * + * Time complexity: O(n), n is the new size. + */ + +igraph_error_t igraph_hrg_resize(igraph_hrg_t *hrg, igraph_int_t newsize) { + igraph_int_t origsize = igraph_hrg_size(hrg); + + /* The data structure must be left in a consistent state if resizing fails. */ + +#define CHECK_ERR(expr) \ + do { \ + igraph_error_t err = (expr); \ + if (err != IGRAPH_SUCCESS) { \ + igraph_vector_int_resize(&hrg->left, origsize); \ + igraph_vector_int_resize(&hrg->right, origsize); \ + igraph_vector_resize(&hrg->prob, origsize); \ + igraph_vector_int_resize(&hrg->edges, origsize); \ + igraph_vector_int_resize(&hrg->vertices, origsize); \ + IGRAPH_FINALLY_EXIT(); \ + IGRAPH_ERROR("Cannot resize HRG.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + } \ + } while (0) + + IGRAPH_FINALLY_ENTER(); + { + CHECK_ERR(igraph_vector_int_resize(&hrg->left, newsize - 1)); + CHECK_ERR(igraph_vector_int_resize(&hrg->right, newsize - 1)); + CHECK_ERR(igraph_vector_resize(&hrg->prob, newsize - 1)); + CHECK_ERR(igraph_vector_int_resize(&hrg->edges, newsize - 1)); + CHECK_ERR(igraph_vector_int_resize(&hrg->vertices, newsize - 1)); + } + IGRAPH_FINALLY_EXIT(); + +#undef CHECK_ERR + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hrg_fit + * \brief Fit a hierarchical random graph model to a network. + * + * \param graph The igraph graph to fit the model to. Edge directions + * are ignored in directed graphs. + * \param hrg Pointer to an initialized HRG, the result of the fitting + * is stored here. It can also be used to pass a HRG to the + * function, that can be used as the starting point of the Markov + * Chain Monte Carlo fitting, if the \p start argument is true. + * \param start Whether to start the fitting from the given + * HRG model. + * \param steps Integer, the number of MCMC steps to take in the + * fitting procedure. If this is zero, then the fitting stops if a + * convergence criteria is fulfilled. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_fit(const igraph_t *graph, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t steps) { + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + + dendro d; + + // If we want to start from HRG + if (start) { + if (igraph_hrg_size(hrg) != no_of_nodes) { + IGRAPH_ERROR("Invalid HRG to start from.", IGRAPH_EINVAL); + } + // Convert the igraph graph + IGRAPH_CHECK(d.setGraph(graph)); + d.clearDendrograph(); + d.importDendrogramStructure(hrg); + } else { + // Convert the igraph graph + IGRAPH_CHECK(d.setGraph(graph)); + IGRAPH_CHECK(igraph_hrg_resize(hrg, no_of_nodes)); + } + + // Run fixed number of steps, or until convergence + if (steps > 0) { + markovChainMonteCarlo(d, steps, hrg); + } else { + MCMCEquilibrium_Find(d, hrg); + } + + return IGRAPH_SUCCESS; + + IGRAPH_HANDLE_EXCEPTIONS_END +} + +/** + * \function igraph_hrg_sample + * \brief Sample from a hierarchical random graph model. + * + * This function draws a single sample from a hierarchical random graph model. + * + * \param hrg A HRG model to sample from + * \param sample Pointer to an uninitialized graph; the sample is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_sample(const igraph_hrg_t *hrg, igraph_t *sample) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN + dendro d; + + // TODO: error handling + + d.clearDendrograph(); + d.importDendrogramStructure(hrg); + d.makeRandomGraph(); + IGRAPH_CHECK(d.recordGraphStructure(sample)); + + return IGRAPH_SUCCESS; + IGRAPH_HANDLE_EXCEPTIONS_END +} + +/** + * \function igraph_hrg_sample_many + * \brief Draw multiple samples from a hierarchical random graph model. + * + * This function draws multiple samples from the hierarchical random graph + * ensemble \p hrg. + * + * \param hrg A HRG model to sample from + * \param samples An initialized graph list that will contain the sampled + * graphs. Note that existing graphs in the graph list are \em not removed + * so make sure you supply an empty list if you do not need the old contents + * of the list. + * \param num_samples The number of samples to generate. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_sample_many( + const igraph_hrg_t *hrg, igraph_graph_list_t *samples, + igraph_int_t num_samples +) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN + igraph_t g; + dendro d; + + if (num_samples < 0) { + IGRAPH_ERROR("Number of samples must be non-negative.", IGRAPH_EINVAL); + } + + if (num_samples == 0) { + return IGRAPH_SUCCESS; + } + + d.clearDendrograph(); + d.importDendrogramStructure(hrg); + while (num_samples-- > 0) { + d.makeRandomGraph(); + IGRAPH_CHECK(d.recordGraphStructure(&g)); + IGRAPH_FINALLY(igraph_destroy, &g); + IGRAPH_CHECK(igraph_graph_list_push_back(samples, &g)); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; + IGRAPH_HANDLE_EXCEPTIONS_END +} + +/** + * \function igraph_hrg_game + * \brief Generate a hierarchical random graph. + * + * This function is a simple shortcut to \ref igraph_hrg_sample. + * It creates a single graph from the given HRG. + * + * \param graph Pointer to an uninitialized graph, the new graph is + * created here. + * \param hrg The hierarchical random graph model to sample from. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_game(igraph_t *graph, + const igraph_hrg_t *hrg) { + return igraph_hrg_sample(hrg, graph); +} + +/** + * \function igraph_from_hrg_dendrogram + * \brief Create a graph representation of the dendrogram of a hierarchical random graph model. + * + * Creates the igraph graph equivalent of the dendrogram encoded in an + * \ref igraph_hrg_t data structure. The probabilities associated to the + * nodes are returned in a vector so this function works without an + * attribute handler. + * + * \param graph Pointer to an uninitialized graph, the result is + * stored here. + * \param hrg The hierarchical random graph to convert. + * \param prob Pointer to an \em initialized vector; the probabilities + * associated to the nodes of the dendrogram will be stored here. Leaf nodes + * will have an associated probability of \c IGRAPH_NAN . + * You may set this to \c NULL if you do not need the probabilities. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the graph. + */ + +igraph_error_t igraph_from_hrg_dendrogram( + igraph_t *graph, const igraph_hrg_t *hrg, igraph_vector_t *prob +) { + const igraph_int_t orig_nodes = igraph_hrg_size(hrg); + const igraph_int_t no_of_nodes = orig_nodes * 2 - 1; + const igraph_int_t no_of_edges = no_of_nodes > 0 ? no_of_nodes - 1 : 0; + igraph_vector_int_t edges; + igraph_int_t i, idx = 0; + + // Probability labels, for leaf nodes they are IGRAPH_NAN + if (prob) { + IGRAPH_CHECK(igraph_vector_resize(prob, no_of_nodes)); + for (i = 0; i < orig_nodes; i++) { + VECTOR(*prob)[i] = IGRAPH_NAN; + } + for (i = 0; i < orig_nodes - 1; i++) { + VECTOR(*prob)[orig_nodes + i] = VECTOR(hrg->prob)[i]; + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + for (i = 0; i < orig_nodes - 1; i++) { + igraph_int_t left = VECTOR(hrg->left)[i]; + igraph_int_t right = VECTOR(hrg->right)[i]; + + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = left < 0 ? orig_nodes - left - 1 : left; + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = right < 0 ? orig_nodes - right - 1 : right; + } + + IGRAPH_CHECK(igraph_empty(graph, 0, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, NULL)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); // + 1 for graph + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hrg_dendrogram + * \brief Create a dendrogram from a hierarchical random graph. + * + * Creates the igraph graph equivalent of an \ref igraph_hrg_t data + * structure. + * + * \param graph Pointer to an uninitialized graph, the result is + * stored here. + * \param hrg The hierarchical random graph to convert. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the graph. + * + * \deprecated-by igraph_from_hrg_dendrogram 0.10.5 + */ +igraph_error_t igraph_hrg_dendrogram(igraph_t *graph, const igraph_hrg_t *hrg) { + const igraph_int_t orig_nodes = igraph_hrg_size(hrg); + const igraph_int_t no_of_nodes = orig_nodes * 2 - 1; + const igraph_int_t no_of_edges = no_of_nodes > 0 ? no_of_nodes - 1 : 0; + igraph_vector_int_t edges; + igraph_int_t i, idx = 0; + igraph_attribute_record_list_t vattrs; + igraph_vector_t prob; + igraph_attribute_record_t* rec; + + // Probability labels, for leaf nodes they are IGRAPH_NAN + IGRAPH_VECTOR_INIT_FINALLY(&prob, no_of_nodes); + for (i = 0; i < orig_nodes; i++) { + VECTOR(prob)[i] = IGRAPH_NAN; + } + for (i = 0; i < orig_nodes - 1; i++) { + VECTOR(prob)[orig_nodes + i] = VECTOR(hrg->prob)[i]; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_attribute_record_list_init(&vattrs, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &vattrs); + + rec = igraph_attribute_record_list_get_ptr(&vattrs, 1); + IGRAPH_CHECK(igraph_attribute_record_set_name(rec, "probability")); + IGRAPH_CHECK(igraph_attribute_record_set_type(rec, IGRAPH_ATTRIBUTE_NUMERIC)); + igraph_vector_swap(rec->value.as_vector, &prob); + + for (i = 0; i < orig_nodes - 1; i++) { + igraph_int_t left = VECTOR(hrg->left)[i]; + igraph_int_t right = VECTOR(hrg->right)[i]; + + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = left < 0 ? orig_nodes - left - 1 : left; + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = right < 0 ? orig_nodes - right - 1 : right; + } + + IGRAPH_CHECK(igraph_empty(graph, 0, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + + igraph_attribute_record_list_destroy(&vattrs); + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&prob); + IGRAPH_FINALLY_CLEAN(4); // + 1 for graph + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hrg_consensus + * \brief Calculate a consensus tree for a HRG. + * + * The calculation can be started from the given HRG (\p hrg), or (if + * \p start is false), a HRG is first fitted to the given graph. + * + * \param graph The input graph. + * \param parents An initialized vector, the results are stored + * here. For each vertex, the id of its parent vertex is stored, or + * -1, if the vertex is the root vertex in the tree. The first n + * vertex IDs (from 0) refer to the original vertices of the graph, + * the other IDs refer to vertex groups. + * \param weights Numeric vector, counts the number of times a given + * tree split occured in the generated network samples, for each + * internal vertices. The order is the same as in \p parents. + * \param hrg A hierarchical random graph. It is used as a starting + * point for the sampling, if the \p start argument is true. It is + * modified along the MCMC. + * \param start Whether to use the supplied HRG (in \p hrg) + * as a starting point for the MCMC. + * \param num_samples The number of samples to generate for creating + * the consensus tree. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_consensus(const igraph_t *graph, + igraph_vector_int_t *parents, + igraph_vector_t *weights, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN + + if (start && !hrg) { + IGRAPH_ERROR("`hrg' must be given if `start' is true.", IGRAPH_EINVAL); + } + + dendro d; + + if (start) { + IGRAPH_CHECK(d.setGraph(graph)); + d.clearDendrograph(); + d.importDendrogramStructure(hrg); + } else { + IGRAPH_CHECK(d.setGraph(graph)); + if (hrg) { + igraph_hrg_resize(hrg, igraph_vcount(graph)); + } + MCMCEquilibrium_Find(d, hrg); + } + + markovChainMonteCarlo2(d, num_samples); + + d.recordConsensusTree(parents, weights); + + return IGRAPH_SUCCESS; + + IGRAPH_HANDLE_EXCEPTIONS_END +} + +static void MCMCEquilibrium_Sample(dendro &d, igraph_int_t num_samples) { + + // Because moves in the dendrogram space are chosen (Monte + // Carlo) so that we sample dendrograms with probability + // proportional to their likelihood, a likelihood-proportional + // sampling of the dendrogram models would be equivalent to a + // uniform sampling of the walk itself. We would still have to + // decide how often to sample the walk (at most once every n steps + // is recommended) but for simplicity, the code here simply runs the + // MCMC itself. To actually compute something over the set of + // sampled dendrogram models (in a Bayesian model averaging sense), + // you'll need to code that yourself. + + double dL; + bool flag_taken; + igraph_int_t sample_num = 0; + igraph_int_t t = 1, thresh = 100 * d.getGraph()->numNodes(); + double ptest = 1.0 / 10.0 / d.getGraph()->numNodes(); + + while (sample_num < num_samples) { + d.monteCarloMove(dL, flag_taken, 1.0); + if (t > thresh && RNG_UNIF01() < ptest) { + sample_num++; + d.sampleAdjacencyLikelihoods(); + } + d.refreshLikelihood(); // TODO: less frequently + t++; + } +} + +static igraph_int_t QsortPartition (pblock* array, igraph_int_t left, igraph_int_t right, igraph_int_t index) { + pblock p_value = array[index]; + + std::swap(array[right], array[index]); + + igraph_int_t stored = left; + for (igraph_int_t i = left; i < right; i++) { + if (array[i].L <= p_value.L) { + std::swap(array[i], array[stored]); + stored++; + } + } + std::swap(array[right], array[stored]); + + return stored; +} + +static void QsortMain (pblock* array, igraph_int_t left, igraph_int_t right) { + if (right > left) { + igraph_int_t pivot = left; + igraph_int_t part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } +} + +static void rankCandidatesByProbability(const simpleGraph &sg, const dendro &d, + pblock *br_list, int mk) { + int mkk = 0; + int n = sg.getNumNodes(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (sg.getAdjacency(i, j) < 0.5) { + double temp = d.getGraph()->getAdjacencyAverage(i, j); + br_list[mkk].L = temp * (1.0 + RNG_UNIF01() / 1000.0); + br_list[mkk].i = i; + br_list[mkk].j = j; + mkk++; + } + } + } + + // Sort the candidates by their average probability + QsortMain(br_list, 0, mk - 1); +} + +static igraph_error_t recordPredictions(const pblock *br_list, igraph_vector_int_t *edges, + igraph_vector_t *prob, int mk) { + + IGRAPH_CHECK(igraph_vector_int_resize(edges, mk * 2)); + IGRAPH_CHECK(igraph_vector_resize(prob, mk)); + + for (int i = mk - 1, idx = 0, idx2 = 0; i >= 0; i--) { + VECTOR(*edges)[idx++] = br_list[i].i; + VECTOR(*edges)[idx++] = br_list[i].j; + VECTOR(*prob)[idx2++] = br_list[i].L; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_hrg_predict + * \brief Predict missing edges in a graph, based on HRG models. + * + * Samples HRG models for a network, and estimated the probability + * that an edge was falsely observed as non-existent in the network. + * + * \param graph The input graph. + * \param edges The list of missing edges is stored here, the first + * two elements are the first edge, the next two the second edge, + * etc. + * \param prob Vector of probabilies for the existence of missing + * edges, in the order corresponding to \c edges. + * \param hrg A HRG, it is used as a starting point if \c start is + * true. It is also modified during the MCMC sampling. + * \param start Whether to start the MCMC from the given HRG. + * \param num_samples The number of samples to generate. + * \param num_bins Controls the resolution of the edge + * probabilities. Higher numbers result higher resolution. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_hrg_predict(const igraph_t *graph, + igraph_vector_int_t *edges, + igraph_vector_t *prob, + igraph_hrg_t *hrg, + igraph_bool_t start, + igraph_int_t num_samples, + igraph_int_t num_bins) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN + + if (start && !hrg) { + IGRAPH_ERROR("`hrg' must be given when `start' is true", IGRAPH_EINVAL); + } + + dendro d; + + std::unique_ptr sg = igraph_i_hrg_getsimplegraph(graph, d, num_bins); + + int mk = sg->getNumNodes() * (sg->getNumNodes() - 1) / 2 - sg->getNumLinks() / 2; + std::unique_ptr br_list(new pblock[mk]); + for (int i = 0; i < mk; i++) { + br_list[i].L = 0.0; + br_list[i].i = -1; + br_list[i].j = -1; + } + + if (start) { + d.clearDendrograph(); + d.importDendrogramStructure(hrg); + } else { + if (hrg) { + igraph_hrg_resize(hrg, igraph_vcount(graph)); + } + MCMCEquilibrium_Find(d, hrg); + } + + MCMCEquilibrium_Sample(d, num_samples); + rankCandidatesByProbability(*sg, d, br_list.get(), mk); + IGRAPH_CHECK(recordPredictions(br_list.get(), edges, prob, mk)); + + return IGRAPH_SUCCESS; + + IGRAPH_HANDLE_EXCEPTIONS_END +} + +/** + * \function igraph_hrg_create + * \brief Create a HRG from an igraph graph. + * + * \param hrg Pointer to an initialized \ref igraph_hrg_t. The result + * is stored here. + * \param graph The igraph graph to convert. It must be a directed + * binary tree, with n-1 internal and n leaf vertices. The root + * vertex must have in-degree zero. + * \param prob The vector of probabilities, this is used to label the + * internal nodes of the hierarchical random graph. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the tree. + */ + +igraph_error_t igraph_hrg_create(igraph_hrg_t *hrg, + const igraph_t *graph, + const igraph_vector_t *prob) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_internal = no_of_nodes > 0 ? (no_of_nodes - 1) / 2 : 0; + igraph_vector_int_t deg, idx; + igraph_int_t root = 0; + igraph_int_t d0 = 0, d1 = 0, d2 = 0; + igraph_int_t ii = 0, il = 0; + igraph_vector_int_t neis; + igraph_vector_int_t path; + igraph_bool_t simple; + + // -------------------------------------------------------- + // CHECKS + // -------------------------------------------------------- + + // At least three vertices are required + if (no_of_nodes < 3) { + IGRAPH_ERROR("HRG tree must have at least three vertices.", + IGRAPH_EINVAL); + } + + // Prob vector was given + if (!prob) { + IGRAPH_ERROR("Probability vector must be given for HRG.", + IGRAPH_EINVAL); + } + + // Length of prob vector + if (igraph_vector_size(prob) != no_of_nodes / 2) { + IGRAPH_ERRORF("HRG probability vector size (%" IGRAPH_PRId ") should be equal " + "to the number of internal nodes (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + igraph_vector_size(prob), no_of_nodes / 2); + } + + // Must be a directed graph + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("HRG graph must be directed.", IGRAPH_EINVAL); + } + + // Number of nodes must be odd + if (no_of_nodes % 2 == 0) { + IGRAPH_ERROR("Complete HRG graph must have odd number of vertices.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &simple, IGRAPH_DIRECTED)); + if (!simple) { + IGRAPH_ERROR("HRG graph must be a simple graph.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°, 0); + + // Every vertex, except for the root must have in-degree one. + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_IN, + IGRAPH_LOOPS)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t d = VECTOR(deg)[i]; + switch (d) { + case 0: d0++; root = i; break; + case 1: d1++; break; + default: + IGRAPH_ERROR("HRG nodes must have in-degree one, except for the " + "root vertex.", IGRAPH_EINVAL); + } + } + if (d1 != no_of_nodes - 1 || d0 != 1) { + IGRAPH_ERROR("HRG nodes must have in-degree one, except for the " + "root vertex.", IGRAPH_EINVAL); + } + + // Every internal vertex must have out-degree two, + // leaves out-degree zero + d0 = d1 = d2 = 0; + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_OUT, + IGRAPH_LOOPS)); + for (int i = 0; i < no_of_nodes; i++) { + igraph_int_t d = VECTOR(deg)[i]; + switch (d) { + case 0: d0++; break; + case 2: d2++; break; + default: + IGRAPH_ERROR("HRG nodes must have out-degree 2 (internal nodes) or " + "degree 0 (leaves).", IGRAPH_EINVAL); + } + } + + // Number of internal and external nodes is correct + // This basically checks that the graph has one component + if (d0 != d2 + 1) { + IGRAPH_ERROR("HRG degrees are incorrect, maybe multiple components?", + IGRAPH_EINVAL); + } + + // -------------------------------------------------------- + // Graph is good, do the conversion + // -------------------------------------------------------- + + // Create an index, that maps the root node as first, then + // the internal nodes, then the leaf nodes + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, no_of_nodes); + VECTOR(idx)[root] = - (ii++) - 1; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t d = VECTOR(deg)[i]; + if (i == root) { + continue; + } + if (d == 2) { + VECTOR(idx)[i] = - (ii++) - 1; + } + if (d == 0) { + VECTOR(idx)[i] = (il++); + } + } + + IGRAPH_CHECK(igraph_hrg_resize(hrg, no_of_internal + 1)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t ri = VECTOR(idx)[i]; + if (ri >= 0) { + continue; + } + IGRAPH_CHECK(igraph_neighbors(graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + VECTOR(hrg->left )[-ri - 1] = VECTOR(idx)[ VECTOR(neis)[0] ]; + VECTOR(hrg->right)[-ri - 1] = VECTOR(idx)[ VECTOR(neis)[1] ]; + VECTOR(hrg->prob )[-ri - 1] = VECTOR(*prob)[i]; + } + + // Calculate the number of vertices and edges in each subtree + igraph_vector_int_null(&hrg->edges); + igraph_vector_int_null(&hrg->vertices); + IGRAPH_VECTOR_INT_INIT_FINALLY(&path, 0); + IGRAPH_CHECK(igraph_vector_int_push_back(&path, VECTOR(idx)[root])); + while (!igraph_vector_int_empty(&path)) { + igraph_int_t ri = igraph_vector_int_tail(&path); + igraph_int_t lc = VECTOR(hrg->left)[-ri - 1]; + igraph_int_t rc = VECTOR(hrg->right)[-ri - 1]; + if (lc < 0 && VECTOR(hrg->vertices)[-lc - 1] == 0) { + // Go left + IGRAPH_CHECK(igraph_vector_int_push_back(&path, lc)); + } else if (rc < 0 && VECTOR(hrg->vertices)[-rc - 1] == 0) { + // Go right + IGRAPH_CHECK(igraph_vector_int_push_back(&path, rc)); + } else { + // Subtrees are done, update node and go up + VECTOR(hrg->vertices)[-ri - 1] += + lc < 0 ? VECTOR(hrg->vertices)[-lc - 1] : 1; + VECTOR(hrg->vertices)[-ri - 1] += + rc < 0 ? VECTOR(hrg->vertices)[-rc - 1] : 1; + VECTOR(hrg->edges)[-ri - 1] += lc < 0 ? VECTOR(hrg->edges)[-lc - 1] + 1 : 1; + VECTOR(hrg->edges)[-ri - 1] += rc < 0 ? VECTOR(hrg->edges)[-rc - 1] + 1 : 1; + igraph_vector_int_pop_back(&path); + } + } + + igraph_vector_int_destroy(&path); + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&idx); + igraph_vector_int_destroy(°); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} diff --git a/src/hrg/hrg_types.cc b/src/hrg/hrg_types.cc new file mode 100644 index 0000000..0c736af --- /dev/null +++ b/src/hrg/hrg_types.cc @@ -0,0 +1,3589 @@ +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// rbtree - red-black tree (self-balancing binary tree data structure) +// Copyright (C) 2004 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : Spring 2004 +// Modified : many, many times +// +// *********************************************************************** + +#include "igraph_hrg.h" +#include "igraph_constructors.h" +#include "igraph_random.h" + +#include "hrg/rbtree.h" +#include "hrg/dendro.h" +#include "hrg/graph.h" +#include "hrg/splittree_eq.h" +#include "hrg/graph_simp.h" + +#include +#include +#include + +using std::string; + +using namespace fitHRG; + +// ******** Red-Black Tree Methods *************************************** + +rbtree::rbtree() { + root = new elementrb; + leaf = new elementrb; + + leaf->parent = root; + + root->left = leaf; + root->right = leaf; + support = 0; +} + +rbtree::~rbtree() { + if (root != nullptr) { + if (root->left != leaf || root->right != leaf) { + deleteSubTree(root); + } else { + delete root; + } + } + delete leaf; +} + +void rbtree::deleteTree() { + if (root != nullptr) { + deleteSubTree(root); root = nullptr; + } +} // does not leak memory + +void rbtree::deleteSubTree(elementrb *z) { + if (z->left != leaf) { + deleteSubTree(z->left); + } + if (z->right != leaf) { + deleteSubTree(z->right); + } + delete z; +} + +// ******** Search Functions ********************************************* +// public search function - if there exists a elementrb in the tree +// with key=searchKey, it returns TRUE and foundNode is set to point +// to the found node; otherwise, it sets foundNode=nullptr and returns +// FALSE +elementrb* rbtree::findItem(const int searchKey) const { + elementrb *current = root; + + // empty tree; bail out + if (current->key == -1) { + return nullptr; + } + + while (current != leaf) { + // left-or-right? + if (searchKey < current->key) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // failure; bail out + return nullptr; + } + } else { + // left-or-right? + if (searchKey > current->key) { + // try moving down-left + if (current->right != leaf) { + current = current->right; + } else { + // failure; bail out + return nullptr; + } + } else { + // found (searchKey==current->key) + return current; + } + } + } + return nullptr; +} + +int rbtree::returnValue(const int searchKey) const { + const elementrb* test = findItem(searchKey); + if (!test) { + return 0; + } else { + return test->value; + } +} + + +// ******** Return Item Functions **************************************** + +int* rbtree::returnArrayOfKeys() const { + IGRAPH_ASSERT(support >= 0); + int* array = new int [support]; + bool flag_go = true; + int index = 0; + elementrb *curr; + + if (support == 1) { + array[0] = root->key; + } else if (support == 2) { + array[0] = root->key; + if (root->left == leaf) { + array[1] = root->right->key; + } else { + array[1] = root->left->key; + } + } else { + for (int i = 0; i < support; i++) { + array[i] = -1; + } + // non-recursive traversal of tree structure + curr = root; + curr->mark = 1; + while (flag_go) { + // - is it time, and is left child the leaf node? + if (curr->mark == 1 && curr->left == leaf) { + curr->mark = 2; + } + // - is it time, and is right child the leaf node? + if (curr->mark == 2 && curr->right == leaf) { + curr->mark = 3; + } + if (curr->mark == 1) { + // - go left + curr->mark = 2; + curr = curr->left; + curr->mark = 1; + } else if (curr->mark == 2) { + // - else go right + curr->mark = 3; + curr = curr->right; + curr->mark = 1; + } else { + // - else go up a level + curr->mark = 0; + array[index++] = curr->key; + curr = curr->parent; + if (curr == nullptr) { + flag_go = false; + } + } + } + } + + return array; +} + +list* rbtree::returnListOfKeys() const { + keyValuePair *curr, *prev; + list *head = nullptr, *tail = nullptr, *newlist; + + curr = returnTreeAsList(); + while (curr != nullptr) { + newlist = new list; + newlist->x = curr->x; + if (head == nullptr) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + prev = curr; + curr = curr->next; + delete prev; + } + return head; +} + +keyValuePair* rbtree::returnTreeAsList() const { + if (root->key == -1) { + return nullptr; /* empty tree */ + } + + // pre-order traversal + keyValuePair *head, *tail; + + head = new keyValuePair; + head->x = root->key; + head->y = root->value; + tail = head; + + if (root->left != leaf) { + tail = returnSubtreeAsList(root->left, tail); + } + if (root->right != leaf) { + tail = returnSubtreeAsList(root->right, tail); + } + + return head; +} + +keyValuePair* rbtree::returnSubtreeAsList(const elementrb *z, keyValuePair *head) const { + keyValuePair *newnode, *tail; + + newnode = new keyValuePair; + newnode->x = z->key; + newnode->y = z->value; + head->next = newnode; + tail = newnode; + + if (z->left != leaf) { + tail = returnSubtreeAsList(z->left, tail); + } + if (z->right != leaf) { + tail = returnSubtreeAsList(z->right, tail); + } + + return tail; +} + +keyValuePair rbtree::returnMaxKey() const { + keyValuePair themax; + elementrb *current; + current = root; + + // search to bottom-right corner of tree + while (current->right != leaf) { + current = current->right; + } + themax.x = current->key; + themax.y = current->value; + + return themax; +} + +keyValuePair rbtree::returnMinKey() const { + keyValuePair themin; + elementrb *current; + current = root; + // search to bottom-left corner of tree + while (current->left != leaf) { + current = current->left; + } + themin.x = current->key; + themin.y = current->value; + + return themin; +} + +// private functions for deleteItem() (although these could easily be +// made public, I suppose) +elementrb* rbtree::returnMinKey(elementrb *z) const { + elementrb *current; + + current = z; + // search to bottom-right corner of tree + while (current->left != leaf) { + current = current->left; + } + return current; +} + +elementrb* rbtree::returnSuccessor(elementrb *z) const { + elementrb *current, *w; + + w = z; + // if right-subtree exists, return min of it + if (w->right != leaf) { + return returnMinKey(w->right); + } + // else search up in tree + current = w->parent; + while ((current != nullptr) && (w == current->right)) { + w = current; + // move up in tree until find a non-right-child + current = current->parent; + } + return current; +} + +int rbtree::returnNodecount() const { + return support; +} + +// ******** Insert Functions ********************************************* +// public insert function +void rbtree::insertItem(int newKey, int newValue) { + + // first we check to see if newKey is already present in the tree; + // if so, we do nothing; if not, we must find where to insert the + // key + elementrb *newNode, *current; + + // find newKey in tree; return pointer to it O(log k) + current = findItem(newKey); + if (current == nullptr) { + newNode = new elementrb; // elementrb for the rbtree + newNode->key = newKey; + newNode->value = newValue; + newNode->color = true; // new nodes are always RED + newNode->parent = nullptr; // new node initially has no parent + newNode->left = leaf; // left leaf + newNode->right = leaf; // right leaf + support++; // increment node count in rbtree + + // must now search for where to insert newNode, i.e., find the + // correct parent and set the parent and child to point to each + // other properly + current = root; + if (current->key == -1) { // insert as root + delete root; // delete old root + root = newNode; // set root to newNode + leaf->parent = newNode; // set leaf's parent + current = leaf; // skip next loop + } + + // search for insertion point + while (current != leaf) { + // left-or-right? + if (newKey < current->key) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // else found new parent + newNode->parent = current; // set parent + current->left = newNode; // set child + current = leaf; // exit search + } + } else { + // try moving down-right + if (current->right != leaf) { + current = current->right; + } else { + // else found new parent + newNode->parent = current; // set parent + current->right = newNode; // set child + current = leaf; // exit search + } + } + } + + // now do the house-keeping necessary to preserve the red-black + // properties + insertCleanup(newNode); + } +} + +// private house-keeping function for insertion +void rbtree::insertCleanup(elementrb *z) { + + // fix now if z is root + if (z->parent == nullptr) { + z->color = false; + return; + } + + elementrb *temp; + + // while z is not root and z's parent is RED + while (z->parent != nullptr && z->parent->color) { + if (z->parent == z->parent->parent->left) { + + // z's parent is LEFT-CHILD + + temp = z->parent->parent->right; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpar. RED (Case 1) + z = z->parent->parent; // set z = z's grandparent (Case 1) + } else { + if (z == z->parent->right) { + // z is RIGHT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateLeft(z); // perform left-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpar. RED (Case 3) + rotateRight(z->parent->parent); // perform right-rotation (Case 3) + } + } else { + + // z's parent is RIGHT-CHILD + + temp = z->parent->parent->left; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpar. RED (Case 1) + z = z->parent->parent; // set z = z's grandparent (Case 1) + } else { + if (z == z->parent->left) { + // z is LEFT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateRight(z); // perform right-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpar. RED (Case 3) + rotateLeft(z->parent->parent); // perform left-rotation (Case 3) + } + } + } + + root->color = false; // color the root BLACK +} + +// ******** Delete +// ******** Functions ********************************************* + +void rbtree::replaceItem(int key, int newValue) { + elementrb* ptr; + ptr = findItem(key); + ptr->value = newValue; +} + +void rbtree::incrementValue(int key) { + elementrb* ptr; + ptr = findItem(key); + ptr->value = 1 + ptr->value; +} + +// public delete function +void rbtree::deleteItem(int killKey) { + elementrb *x, *y, *z; + + z = findItem(killKey); + if (z == nullptr) { + return; // item not present; bail out + } + + if (support == 1) { // attempt to delete the root + root->key = -1; // restore root node to default state + root->value = -1; + root->color = false; + root->parent = nullptr; + root->left = leaf; + root->right = leaf; + support--; // set support to zero + return; // exit - no more work to do + } + + if (z != nullptr) { + support--; // decrement node count + if ((z->left == leaf) || (z->right == leaf)) { + y = z; // case of less than two children, + // set y to be z + } else { + y = returnSuccessor(z); // set y to be z's key-successor + } + + if (y->left != leaf) { + x = y->left; // pick y's one child (left-child) + } else { + x = y->right; // (right-child) + } + x->parent = y->parent; // make y's child's parent be y's parent + + if (y->parent == nullptr) { + root = x; // if y is the root, x is now root + } else { + if (y == y->parent->left) { // decide y's relationship with y's parent + y->parent->left = x; // replace x as y's parent's left child + } else { + y->parent->right = x; // replace x as y's parent's left child + } + } + + if (y != z) { // insert y into z's spot + z->key = y->key; // copy y data into z + z->value = y->value; + } + + // do house-keeping to maintain balance + if (y->color == false) { + deleteCleanup(x); + } + + delete y; + y = nullptr; + } +} + +void rbtree::deleteCleanup(elementrb *x) { + elementrb *w, *t; + + // until x is the root, or x is RED + while ((x != root) && (x->color == false)) { + if (x == x->parent->left) { // branch on x being a LEFT-CHILD + w = x->parent->right; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateLeft(x->parent); // left rotation on x's parent (case 1) + w = x->parent->right; // make w be x's right sibling (case 1) + } + if ((w->left->color == false) && (w->right->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { + if (w->right->color == false) { + w->left->color = false; // color w's left child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent (case 3) + rotateRight(w); // right rotation on w (case 3) + x->parent = t; // restore x's parent (case 3) + w = x->parent->right; // make w be x's right sibling (case 3) + } + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->right->color = false; // color w's right child BLACK (case 4) + rotateLeft(x->parent); // left rotation on x's parent (case 4) + x = root; // finished work. bail out (case 4) + } + } else { // x is RIGHT-CHILD + w = x->parent->left; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateRight(x->parent); // right rotation on x's parent (case 1) + w = x->parent->left; // make w be x's left sibling (case 1) + } + if ((w->right->color == false) && (w->left->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { + if (w->left->color == false) { + w->right->color = false; // color w's right child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent (case 3) + rotateLeft(w); // left rotation on w (case 3) + x->parent = t; // restore x's parent (case 3) + w = x->parent->left; // make w be x's left sibling (case 3) + } + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->left->color = false; // color w's left child BLACK (case 4) + rotateRight(x->parent); // right rotation on x's parent (case 4) + x = root; // x is now the root (case 4) + } + } + } + x->color = false; // color x (the root) BLACK (exit) +} + +// ******** Rotation Functions ****************************************** + +void rbtree::rotateLeft(elementrb *x) { + elementrb *y; + // do pointer-swapping operations for left-rotation + y = x->right; // grab right child + x->right = y->left; // make x's RIGHT-CHILD be y's LEFT-CHILD + y->left->parent = x; // make x be y's LEFT-CHILD's parent + y->parent = x->parent; // make y's new parent be x's old parent + + if (x->parent == nullptr) { + root = y; // if x was root, make y root + } else { + // if x is LEFT-CHILD, make y be x's parent's + if (x == x->parent->left) { + x->parent->left = y; // left-child + } else { + x->parent->right = y; // right-child + } + } + y->left = x; // make x be y's LEFT-CHILD + x->parent = y; // make y be x's parent +} + +void rbtree::rotateRight(elementrb *y) { + elementrb *x; + // do pointer-swapping operations for right-rotation + x = y->left; // grab left child + y->left = x->right; // replace left child yith x's right subtree + x->right->parent = y; // replace y as x's right subtree's parent + + x->parent = y->parent; // make x's new parent be y's old parent + + // if y was root, make x root + if (y->parent == nullptr) { + root = x; + } else { + // if y is RIGHT-CHILD, make x be y's parent's + if (y == y->parent->right) { + // right-child + y->parent->right = x; + } else { + // left-child + y->parent->left = x; + } + } + x->right = y; // make y be x's RIGHT-CHILD + y->parent = x; // make x be y's parent +} + +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// dendro.h - hierarchical random graph (hrg) data structure +// Copyright (C) 2005-2009 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 26 October 2005 - 7 December 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Maximum likelihood dendrogram data structure. This is the heart of +// the HRG algorithm: all manipulations are done here and all data is +// stored here. The data structure uses the separate graph data +// structure to store the basic adjacency information (in a +// dangerously mutable way). +// +// *********************************************************************** + +// ******** Dendrogram Methods ******************************************* + +dendro::~dendro() { + list *curr, *prev; + + delete g; // O(m) + delete [] internal; // O(n) + delete [] leaf; // O(n) + delete d; // O(n) + delete splithist; // potentially long + + if (paths) { + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr) { + prev = curr; + curr = curr->next; + delete prev; + } + paths[i] = nullptr; + } + delete [] paths; + } + + delete [] ctree; // O(n) + delete [] cancestor; // O(n) +} + +// ********************************************************************* + +void dendro::binarySearchInsert(elementd* x, elementd* y) { + if (y->p < x->p) { // go to left subtree + if (x->L == nullptr) { // check if left subtree is empty + x->L = y; // make x left child + y->M = x; // make y parent of child + return; + } else { + binarySearchInsert(x->L, y); + } + } else { // go to right subtree + if (x->R == nullptr) { // check if right subtree is empty + x->R = y; // make x right child + y->M = x; // make y parent of child + return; + } else { + binarySearchInsert(x->R, y); + } + } +} + +// ********************************************************************** + +list* dendro::binarySearchFind(const double v) const { + list *head = nullptr, *tail = nullptr, *newlist; + elementd *current = root; + bool flag_stopSearch = false; + + while (!flag_stopSearch) { // continue until we're finished + newlist = new list; // add this node to the path + newlist->x = current->label; + if (current == root) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + if (v < current->p) { // now try left subtree + if (current->L->type == GRAPH) { + flag_stopSearch = true; + } else { + current = current->L; + } + } else { // else try right subtree + if (current->R->type == GRAPH) { + flag_stopSearch = true; + } else { + current = current->R; + } + } + } + return head; +} + +// *********************************************************************** + +string dendro::buildSplit(elementd* thisNode) const { + // A "split" is defined as the bipartition of vertices into the sets + // of leaves below the internal vertex in the tree (denoted by "C"), + // and those above it (denoted as "M"). For simplicity, we represent + // this bipartition as a character string of length n, where the ith + // character denotes the partition membership (C,M) of the ith leaf + // node. + + bool flag_go = true; + const short int k = 1 + DENDRO + GRAPH; + elementd* curr; + split sp; + + sp.initializeSplit(n); // default split string O(n) + + curr = thisNode; // - set start node as top this sub-tree + curr->type = k + 1; // - initialize in-order tree traversal + while (flag_go) { + + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + sp.s[curr->L->index] = 'C'; // - mark this leaf + curr->type = k + 2; + } + + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + sp.s[curr->R->index] = 'C'; // - mark this leaf + curr->type = k + 3; + } + if (curr->type == k + 1) { // - go left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - else go right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + if (curr->index == thisNode->index || curr->M == nullptr) { + flag_go = false; curr = nullptr; + } else { + curr = curr->M; + } + } + } + + // any leaf that was not already marked must be in the remainder of + // the tree + for (int i = 0; i < n; i++) { + if (sp.s[i] != 'C') { + sp.s[i] = 'M'; + } + } + + return sp.s; +} + +// ********************************************************************** + +void dendro::buildDendrogram() { + + /* the initialization of the dendrogram structure goes like this: + * 1) we allocate space for the n-1 internal nodes of the + * dendrogram, and then the n leaf nodes + * 2) we build a random binary tree structure out of the internal + * nodes by assigning each a uniformly random value over [0,1] and + * then inserting it into the tree according to the + * binary-search rule. + * 3) next, we make a random permutation of the n leaf nodes and add + * them to the dendrogram D by replacing the emptpy spots in-order + * 4) then, we compute the path from the root to each leaf and store + * that in each leaf (this is prep work for the next step) + * 5) finally, we compute the values for nL, nR, e (and thus p) and + * the label for each internal node by allocating each of the m + * edges in g to the appropriate internal node + */ + + // --- Initialization and memory allocation for data structures + // After allocating the memory for D and G, we need to mark the + // nodes for G as being non-internal vertices, and then insert them + // into a random binary tree structure. For simplicity, we make the + // first internal node in the array the root. + + n = g->numNodes(); // size of graph + leaf = new elementd [n]; // allocate memory for G, O(n) + internal = new elementd [n - 1]; // allocate memory for D, O(n) + d = new interns(n - 2); // allocate memory for internal + // edges of D, O(n) + for (int i = 0; i < n; i++) { // initialize leaf nodes + leaf[i].type = GRAPH; + leaf[i].label = i; + leaf[i].index = i; + leaf[i].n = 1; + } + +// initialize internal nodes + root = &internal[0]; + root->label = 0; + root->index = 0; + root->p = RNG_UNIF01(); + + // insert remaining internal vertices, O(n log n) + for (int i = 1; i < (n - 1); i++) { + internal[i].label = i; + internal[i].index = i; + internal[i].p = RNG_UNIF01(); + binarySearchInsert(root, &internal[i]); + } + + // --- Hang leaf nodes off end of dendrogram O(n log n) + // To impose this random hierarchical relationship on G, we first + // take a random permutation of the leaf vertices and then replace + // the NULLs at the bottom of the tree in-order with the leafs. As a + // hack to ensure that we can find the leafs later using a binary + // search, we assign each of them the p value of their parent, + // perturbed slightly so as to preserve the binary search property. + + block* array; array = new block [n]; + for (int i = 0; i < n; i++) { + array[i].x = RNG_UNIF01(); + array[i].y = i; + } + QsortMain(array, 0, n - 1); + + int k = 0; // replace NULLs with leaf nodes, and + for (int i = 0; i < (n - 1); i++) { // maintain binary search property, O(n) + if (internal[i].L == nullptr) { + internal[i].L = &leaf[array[k].y]; + leaf[array[k].y].M = &internal[i]; + leaf[array[k++].y].p = internal[i].p - 0.0000000000001; + } + if (internal[i].R == nullptr) { + internal[i].R = &leaf[array[k].y]; + leaf[array[k].y].M = &internal[i]; + leaf[array[k++].y].p = internal[i].p + 0.0000000000001; + } + } + delete [] array; + + // --- Compute the path from root -> leaf for each leaf O(n log n) + // Using the binary search property, we can find each leaf node in + // O(log n) time. The binarySearchFind() function returns the list + // of internal node indices that the search crossed, in the order of + // root -> ... -> leaf, for use in the subsequent few operations. + + if (paths != nullptr) { + list *curr, *prev; + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != nullptr) { + prev = curr; + curr = curr->next; + delete prev; + prev = nullptr; + } + paths[i] = nullptr; + } + delete [] paths; + } + paths = new list* [n]; + for (int i = 0; i < n; i++) { + paths[i] = binarySearchFind(leaf[i].p); + } + + // --- Count e for each internal node O(m) + // To count the number of edges that span the L and R subtrees for + // each internal node, we use the path information we just + // computed. Then, we loop over all edges in G and find the common + // ancestor in D of the two endpoints and increment that internal + // node's e count. This process takes O(m) time because in a roughly + // balanced binary tree (given by our random dendrogram), the vast + // majority of vertices take basically constant time to find their + // common ancestor. Note that because our adjacency list is + // symmetric, we overcount each e by a factor of 2, so we need to + // correct this after. + + elementd* ancestor; + const edge* curr; + for (int i = 0; i < (n - 1); i++) { + internal[i].e = 0; + internal[i].label = -1; + } + for (int i = 0; i < n; i++) { + curr = g->getNeighborList(i); + while (curr != nullptr) { + ancestor = findCommonAncestor(paths, i, curr->x); + ancestor->e += 1; + curr = curr->next; + } + } + for (int i = 0; i < (n - 1); i++) { + internal[i].e /= 2; + } + + // --- Count n for each internal node O(n log n) + // To tabulate the number of leafs in each subtree rooted at an + // internal node, we use the path information computed above. + for (int i = 0; i < n; i++) { + ancestor = &leaf[i]; + ancestor = ancestor->M; + while (ancestor != nullptr) { + ancestor->n++; + ancestor = ancestor->M; + } + } + + // --- Label all internal vertices O(n log n) + // We want to label each internal vertex with the smallest leaf + // index of its children. This will allow us to collapse many + // leaf-orderings into a single dendrogram structure that is + // independent of child-exhanges (since these have no impact on the + // likelihood of the hierarchical structure). To do this, we loop + // over the leaf vertices from smallest to largest and walk along + // that leaf's path from the root. If we find an unlabeled internal + // node, then we mark it with this leaf's index. + + for (int i = 0; i < n; i++) { + ancestor = &leaf[i]; + while (ancestor != nullptr) { + if (ancestor->label == -1 || ancestor->label > leaf[i].label) { + ancestor->label = leaf[i].label; + } + ancestor = ancestor->M; + } + } + + // --- Exchange children to enforce order-property O(n) + // We state that the order-property requires that an internal node's + // label is the smallest index of its left subtree. The dendrogram + // so far doesn't reflect this, so we need to step through each + // internal vertex and make that adjustment (swapping nL and nR if + // we make a change). + + elementd *tempe; + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->label > internal[i].label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // --- Tabulate internal dendrogram edges O(n^2) + // For the MCMC moves later on, we'll need to be able to choose, + // uniformly at random, an internal edge of the dendrogram to + // manipulate. There are always n-2 of them, and we can find them + // simply by scanning across the internal vertices and observing + // which have children that are also internal vertices. Note: very + // important that the order property be enforced before this step is + // taken; otherwise, the internal edges wont reflect the actual + // dendrogram structure. + + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->type == DENDRO) { + d->addEdge(i, internal[i].L->index, LEFT); + } + if (internal[i].R->type == DENDRO) { + d->addEdge(i, internal[i].R->index, RIGHT); + } + } + + // --- Clear memory for paths O(n log n) + // Now that we're finished using the paths, we need to deallocate + // them manually. + + list *current, *previous; + for (int i = 0; i < n; i++) { + current = paths[i]; + while (current) { + previous = current; + current = current->next; + delete previous; + } + paths[i] = nullptr; + } + delete [] paths; + paths = nullptr; + + // --- Compute p_i for each internal node O(n) + // Each internal node's p_i = e_i / (nL_i*nR_i), and now that we + // have each of those pieces, we may calculate this value for each + // internal node. Given these, we can then calculate the + // log-likelihood of the entire dendrogram structure \log(L) = + // \sum_{i=1}^{n} ( ( e_i \log[p_i] ) + ( (nL_i*nR_i - e_i) + // \log[1-p_i] ) ) + + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + internal[i].p = (double)(ei) / (double)(nL_nR); + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = ei * log(internal[i].p) + (nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } + + for (int i = 0; i < (n - 1); i++) { + if (internal[i].label > internal[i].L->label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // Dendrogram is now built +} + +// *********************************************************************** + +void dendro::clearDendrograph() { + // Clear out the memory and references used by the dendrograph + // structure - this is intended to be called just before an + // importDendrogramStructure call so as to avoid memory leaks and + // overwriting the references therein. + + delete [] leaf; // O(n) + leaf = nullptr; + + delete [] internal; // O(n) + internal = nullptr; + + delete d; // O(n) + d = nullptr; + + root = nullptr; +} + +// ********************************************************************** + +int dendro::computeEdgeCount(const int a, const short int atype, + const int b, const short int btype) { + // This function computes the number of edges that cross between the + // subtree internal[a] and the subtree internal[b]. To do this, we + // use an array A[1..n] integers which take values -1 if A[i] is in + // the subtree defined by internal[a], +1 if A[i] is in the subtree + // internal[b], and 0 otherwise. Taking the smaller of the two sets, + // we then scan over the edges attached to that set of vertices and + // count the number of endpoints we see in the other set. + + bool flag_go = true; + int nA, nB; + int count = 0; + const short int k = 1 + DENDRO + GRAPH; + + elementd* curr; + + // First, we push the leaf nodes in the L and R subtrees into + // balanced binary tree structures so that we can search them + // quickly later on. + + if (atype == GRAPH) { + // default case, subtree A is size 1 + // insert single node as member of left subtree + subtreeL.insertItem(a, -1); + nA = 1; // + } else { + // explore subtree A, O(|A|) + curr = &internal[a]; + curr->type = k + 1; + nA = 0; + while (flag_go) { + if (curr->index == internal[a].M->index) { + internal[a].type = DENDRO; + flag_go = false; + } else { + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + subtreeL.insertItem(curr->L->index, -1); + curr->type = k + 2; + nA++; + } + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + subtreeL.insertItem(curr->R->index, -1); + curr->type = k + 3; + nA++; + } + if (curr->type == k + 1) { // - go left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - else go right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + curr = curr->M; + if (curr == nullptr) { + flag_go = false; + } + } + } + } + } + + if (btype == GRAPH) { + // default case, subtree A is size 1 + // insert node as single member of right subtree + subtreeR.insertItem(b, 1); + nB = 1; + } else { + flag_go = true; + // explore subtree B, O(|B|) + curr = &internal[b]; + curr->type = k + 1; + nB = 0; + while (flag_go) { + if (curr->index == internal[b].M->index) { + internal[b].type = DENDRO; + flag_go = false; + } else { + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + subtreeR.insertItem(curr->L->index, 1); + curr->type = k + 2; + nB++; + } + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + subtreeR.insertItem(curr->R->index, 1); + curr->type = k + 3; + nB++; + } + if (curr->type == k + 1) { // - look left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - look right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + curr = curr->M; + if (curr == nullptr) { + flag_go = false; + } + } + } + } + } + + // Now, we take the smaller subtree and ask how many of its + // emerging edges have their partner in the other subtree. O(|A| log + // |A|) time + + const edge* current; + int* treeList; + if (nA < nB) { + // subtreeL is smaller + treeList = subtreeL.returnArrayOfKeys(); + for (int i = 0; i < nA; i++) { + current = g->getNeighborList(treeList[i]); + // loop over each of its neighbors v_j + while (current != nullptr) { + // to see if v_j is in A + if (subtreeR.findItem(current->x) != nullptr) { + count++; + } + current = current->next; + } + subtreeL.deleteItem(treeList[i]); + } + delete [] treeList; + treeList = subtreeR.returnArrayOfKeys(); + for (int i = 0; i < nB; i++) { + subtreeR.deleteItem(treeList[i]); + } + delete [] treeList; + } else { + // subtreeR is smaller + treeList = subtreeR.returnArrayOfKeys(); + for (int i = 0; i < nB; i++) { + current = g->getNeighborList(treeList[i]); + // loop over each of its neighbors v_j + while (current != nullptr) { + // to see if v_j is in B + if (subtreeL.findItem(current->x) != nullptr) { + count++; + } + current = current->next; + } + subtreeR.deleteItem(treeList[i]); + } + delete [] treeList; + treeList = subtreeL.returnArrayOfKeys(); + for (int i = 0; i < nA; i++) { + subtreeL.deleteItem(treeList[i]); + } + delete [] treeList; + } + + return count; +} + +// *********************************************************************** + +size_t dendro::countChildren(const string &s) { + size_t len = s.size(); + size_t numC = 0; + for (size_t i = 0; i < len; i++) { + if (s[i] == 'C') { + numC++; + } + } + return numC; +} + +// *********************************************************************** + +void dendro::cullSplitHist() { + string *array = splithist->returnArrayOfKeys(); + double tot = splithist->returnTotal(); + int leng = splithist->returnNodecount(); + for (int i = 0; i < leng; i++) { + if ((splithist->returnValue(array[i]) / tot) < 0.5) { + splithist->deleteItem(array[i]); + } + } + delete [] array; array = nullptr; +} + +// ********************************************************************** + +elementd* dendro::findCommonAncestor(list** paths_, const int i, const int j) { + list* headOne = paths_[i]; + list* headTwo = paths_[j]; + elementd* lastStep = nullptr; + while (headOne->x == headTwo->x) { + lastStep = &internal[headOne->x]; + headOne = headOne->next; + headTwo = headTwo->next; + if (headOne == nullptr || headTwo == nullptr) { + break; + } + } + return lastStep; // Returns address of an internal node; do not deallocate +} + +// ********************************************************************** + +int dendro::getConsensusSize() { + string *array; + double value, tot; + int numSplits, numCons; + numSplits = splithist->returnNodecount(); + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + numCons = 0; + for (int i = 0; i < numSplits; i++) { + value = splithist->returnValue(array[i]); + if (value / tot > 0.5) { + numCons++; + } + } + delete [] array; array = nullptr; + return numCons; +} + +// ********************************************************************** + +splittree* dendro::getConsensusSplits() const { + string *array; + splittree *consensusTree; + double value, tot; + consensusTree = new splittree; + int numSplits; + + // We look at all of the splits in our split histogram and add any + // one that's in the majority to our consensusTree, which we then + // return (note that consensusTree needs to be deallocated by the + // user). + numSplits = splithist->returnNodecount(); + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + for (int i = 0; i < numSplits; i++) { + value = splithist->returnValue(array[i]); + if (value / tot > 0.5) { + consensusTree->insertItem(array[i], value / tot); + } + } + delete [] array; array = nullptr; + return consensusTree; +} + +// *********************************************************************** + +double dendro::getLikelihood() const { + return L; +} + +// *********************************************************************** + +void dendro::getSplitList(splittree* split_tree) const { + string sp; + for (int i = 0; i < (n - 1); i++) { + sp = d->getSplit(i); + if (!sp.empty() && sp[1] != '-') { + split_tree->insertItem(sp, 0.0); + } + } +} + +// *********************************************************************** + +double dendro::getSplitTotalWeight() const { + if (splithist) { + return splithist->returnTotal(); + } else { + return 0; + } +} + +// *********************************************************************** + +bool dendro::importDendrogramStructure(const igraph_hrg_t *hrg) { + igraph_int_t size = igraph_hrg_size(hrg); + + if (size > INT_MAX) { + throw std::range_error("Hierarchical random graph too large for the HRG module"); + } + + n = (int) size; + + // allocate memory for G, O(n) + leaf = new elementd[n]; + // allocate memory for D, O(n) + internal = new elementd[n - 1]; + // allocate memory for internal edges of D, O(n) + d = new interns(n - 2); + + // initialize leaf nodes + for (int i = 0; i < n; i++) { + leaf[i].type = GRAPH; + leaf[i].label = i; + leaf[i].index = i; + leaf[i].n = 1; + } + + // initialize internal nodes + root = &internal[0]; + root->label = 0; + for (int i = 1; i < n - 1; i++) { + internal[i].index = i; + internal[i].label = -1; + } + + // import basic structure from hrg object, O(n) + for (int i = 0; i < n - 1; i++) { + int left_index = VECTOR(hrg->left)[i]; + int right_index = VECTOR(hrg->right)[i]; + + if (left_index < 0) { + internal[i].L = &internal[-left_index - 1]; + internal[-left_index - 1].M = &internal[i]; + } else { + internal[i].L = &leaf[left_index]; + leaf[left_index].M = &internal[i]; + } + + if (right_index < 0) { + internal[i].R = &internal[-right_index - 1]; + internal[-right_index - 1].M = &internal[i]; + } else { + internal[i].R = &leaf[right_index]; + leaf[right_index].M = &internal[i]; + } + + internal[i].p = VECTOR(hrg->prob)[i]; + internal[i].e = VECTOR(hrg->edges)[i]; + internal[i].n = VECTOR(hrg->vertices)[i]; + internal[i].index = i; + } + + // --- Label all internal vertices O(n log n) + elementd *curr; + for (int i = 0; i < n; i++) { + curr = &leaf[i]; + while (curr) { + if (curr->label == -1 || curr->label > leaf[i].label) { + curr->label = leaf[i].label; + } + curr = curr -> M; + } + } + + // --- Exchange children to enforce order-property O(n) + elementd *tempe; + for (int i = 0; i < n - 1; i++) { + if (internal[i].L->label > internal[i].label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // --- Tabulate internal dendrogram edges O(n) + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->type == DENDRO) { + d->addEdge(i, internal[i].L->index, LEFT); + } + if (internal[i].R->type == DENDRO) { + d->addEdge(i, internal[i].R->index, RIGHT); + } + } + + // --- Compute p_i for each internal node O(n) + // Each internal node's p_i = e_i / (nL_i*nR_i), and now that we + // have each of those pieces, we may calculate this value for each + // internal node. Given these, we can then calculate the + // log-likelihood of the entire dendrogram structure + // \log(L) = \sum_{i=1}^{n} ( ( e_i \log[p_i] ) + + // ( (nL_i*nR_i - e_i) \log[1-p_i] ) ) + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = (double)(ei) * log(internal[i].p) + + (double)(nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } + + return true; +} + +// *********************************************************************** + +void dendro::makeRandomGraph() { + delete g; + g = new graph(n); + + if (paths) { + for (int i = 0; i < n; i++) { + list *curr = paths[i]; + while (curr != nullptr) { + list *prev = curr; + curr = curr->next; + delete prev; + } + paths[i] = nullptr; + } + delete [] paths; + } +// build paths from root O(n d) + paths = new list* [n]; + for (int i = 0; i < n; i++) { + paths[i] = reversePathToRoot(i); + } + +// O((h+d)*n^2) - h: height of D; d: average degree in G + for (int i = 0; i < n; i++) { + // decide neighbors of v_i + for (int j = (i + 1); j < n; j++) { + const elementd* commonAncestor = findCommonAncestor(paths, i, j); + if (RNG_UNIF01() < commonAncestor->p) { + if (!(g->doesLinkExist(i, j))) { + g->addLink(i, j); + } + if (!(g->doesLinkExist(j, i))) { + g->addLink(j, i); + } + } + } + } + + for (int i = 0; i < n; i++) { + list *curr = paths[i]; + while (curr != nullptr) { + list *prev = curr; + curr = curr->next; + delete prev; + } + paths[i] = nullptr; + } + delete [] paths; // delete paths data structure O(n log n) + paths = nullptr; +} + +// ********************************************************************** + +void dendro::monteCarloMove(double &delta, bool &ftaken, const double T) { + // A single MC move begins with the selection of a random internal + // edge (a,b) of the dendrogram. This also determines the three + // subtrees i, j, k that we will rearrange, and we choose uniformly + // from among the options. + // + // If (a,b) is a left-edge, then we have ((i,j),k), and moves + // ((i,j),k) -> ((i,k),j) (alpha move) + // -> (i,(j,k)) + enforce order-property for (j,k) (beta move) + // + // If (a,b) is a right-edge, then we have (i,(j,k)), and moves + // (i,(j,k)) -> ((i,k),j) (alpha move) + // -> ((i,j),k) (beta move) + // + // For each of these moves, we need to know what the change in + // likelihood will be, so that we can determine with what + // probability we execute the move. + + elementd *temp; + const ipair *tempPair; + int x, y, e_x, e_y, n_i, n_j, n_k, n_x, n_y; + short int t; + double p_x, p_y, L_x, L_y, dLogL; + string new_split; + + // The remainder of the code executes a single MCMC move, where we + // sample the dendrograms proportionally to their likelihoods (i.e., + // temperature=1, if you're comparing it to the usual MCMC + // framework). + + delta = 0.0; + ftaken = false; + tempPair = d->getRandomEdge(); // returns address; no need to deallocate + x = tempPair->x; // copy contents of referenced random edge + y = tempPair->y; // into local variables + t = tempPair->t; + + if (t == LEFT) { + if (RNG_BOOL()) { // ## LEFT ALPHA move: ((i,j),k) -> ((i,k),j) + // We need to calculate the change in the likelihood (dLogL) + // that would result from this move. Most of the information + // needed to do this is already available, the exception being + // e_ik, the number of edges that span the i and k subtrees. I + // use a slow algorithm O(n) to do this, since I don't know of a + // better way at this point. (After several attempts to find a + // faster method, no luck.) + + n_i = internal[y].L->n; + n_j = internal[y].R->n; + n_k = internal[x].R->n; + + n_y = n_i * n_k; + e_y = computeEdgeCount(internal[y].L->index, internal[y].L->type, + internal[x].R->index, internal[x].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_k) * n_j; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make LEFT ALPHA move + + ftaken = true; + d->swapEdges(x, internal[x].R->index, RIGHT, y, + internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap j and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + internal[y].n = n_i + n_k; // - update n for [y] + internal[x].e = e_x; // - update e_i for [x] and [y] + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i for [x] and [y] + internal[y].p = p_y; + internal[x].logL = L_x; // - update L_i for [x] and [y] + internal[y].logL = L_y; + // - order-property maintained + L += dLogL; // - update LogL + delta = dLogL; + + } + } else { + + // ## LEFT BETA move: ((i,j),k) -> (i,(j,k)) + + n_i = internal[y].L->n; + n_j = internal[y].R->n; + n_k = internal[x].R->n; + + n_y = n_j * n_k; + e_y = computeEdgeCount(internal[y].R->index, internal[y].R->type, + internal[x].R->index, internal[x].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_j + n_k) * n_i; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make LEFT BETA move + + ftaken = true; + d->swapEdges(y, internal[y].L->index, LEFT, y, + internal[y].R->index, RIGHT); + temp = internal[y].L; // - swap L and R of [y] + internal[y].L = internal[y].R; + internal[y].R = temp; + d->swapEdges(x, internal[x].R->index, RIGHT, + y, internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap i and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + internal[y].n = n_j + n_k; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + if (internal[y].R->label < internal[y].L->label) { + // - enforce order-property if necessary + d->swapEdges(y, internal[y].L->index, LEFT, + y, internal[y].R->index, RIGHT); + temp = internal[y].L; + internal[y].L = internal[y].R; + internal[y].R = temp; + } // + internal[y].label = internal[y].L->label; + L += dLogL; // - update LogL + delta = dLogL; + } + } + } else { + + // right-edge: t == RIGHT + + if (RNG_BOOL()) { + + // alpha move: (i,(j,k)) -> ((i,k),j) + + n_i = internal[x].L->n; + n_j = internal[y].L->n; + n_k = internal[y].R->n; + + n_y = n_i * n_k; + e_y = computeEdgeCount(internal[x].L->index, internal[x].L->type, + internal[y].R->index, internal[y].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_k) * n_j; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make RIGHT ALPHA move + + ftaken = true; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + d->swapEdges(y, internal[y].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[y].L; // - swap i and j + internal[y].L = internal[x].R; + internal[x].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].L->M = &internal[y]; + internal[y].n = n_i + n_k; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + internal[y].label = internal[x].label; // - update order property + L += dLogL; // - update LogL + delta = dLogL; + } + } else { + + // beta move: (i,(j,k)) -> ((i,j),k) + + n_i = internal[x].L->n; + n_j = internal[y].L->n; + n_k = internal[y].R->n; + + n_y = n_i * n_j; + e_y = computeEdgeCount(internal[x].L->index, internal[x].L->type, + internal[y].L->index, internal[y].L->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_j) * n_k; + e_x = internal[x].e + internal[y].e - e_y; // e_yk + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make RIGHT BETA move + + ftaken = true; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + d->swapEdges(x, internal[x].R->index, RIGHT, + y, internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap i and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + d->swapEdges(y, internal[y].L->index, LEFT, + y, internal[y].R->index, RIGHT); + temp = internal[y].L; // - swap L and R of [y] + internal[y].L = internal[y].R; + internal[y].R = temp; + internal[y].n = n_i + n_j; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + internal[y].label = internal[x].label; // - order-property + L += dLogL; // - update LogL + delta = dLogL; + } + } + } +} + +// ********************************************************************** + +void dendro::refreshLikelihood() { + // recalculates the log-likelihood of the dendrogram structure + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + internal[i].p = (double)(ei) / (double)(nL_nR); + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = ei * log(internal[i].p) + (nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } +} + +// ********************************************************************** + +void dendro::QsortMain (block* array, int left, int right) { + if (right > left) { + int pivot = left; + int part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } +} + +int dendro::QsortPartition (block* array, int left, int right, int index) { + block p_value = array[index]; + + std::swap(array[index], array[right]); + + int stored = left; + for (int i = left; i < right; i++) { + if (array[i].x <= p_value.x) { + std::swap(array[stored], array[i]); + stored++; + } + } + std::swap(array[right], array[stored]); + + return stored; +} + +void dendro::recordConsensusTree(igraph_vector_int_t *parents, + igraph_vector_t *weights) { + + keyValuePairSplit *curr, *prev; + child *newChild; + int orig_nodes = g->numNodes(); + + // First, cull the split hist so that only splits with weight >= 0.5 + // remain + cullSplitHist(); + int treesize = splithist->returnNodecount(); + + // Now, initialize the various arrays we use to keep track of the + // internal structure of the consensus tree. + ctree = new cnode[treesize]; + cancestor = new int[n]; + for (int i = 0; i < treesize; i++) { + ctree[i].index = i; + } + for (int i = 0; i < n; i++) { + cancestor[i] = -1; + } + int ii = 0; + + // To build the majority consensus tree, we do the following: For + // each possible number of Ms in the split string (a number that + // ranges from n-2 down to 0), and for each split with that number + // of Ms, we create a new internal node of the tree, and connect the + // oldest ancestor of each C to that node (at most once). Then, we + // update our list of oldest ancestors to reflect this new join, and + // proceed. + for (int i = n - 2; i >= 0; i--) { + // First, we get a list of all the splits with this exactly i Ms + curr = splithist->returnTheseSplits(i); + + // Now we loop over that list + while (curr != nullptr) { + splithist->deleteItem(curr->x); + // add weight to this internal node + ctree[ii].weight = curr->y; + // examine each letter of this split + for (int j = 0; j < n; j++) { + if (curr->x[j] == 'C') { + // - node is child of this internal node + if (cancestor[j] == -1) { + // - first time this leaf has ever been seen + newChild = new child; + newChild->type = GRAPH; + newChild->index = j; + newChild->next = nullptr; + // - attach child to list + if (ctree[ii].lastChild == nullptr) { + ctree[ii].children = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree = 1; + } else { + ctree[ii].lastChild->next = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree += 1; + } + } else { + // - this leaf has been seen before + // If the parent of the ancestor of this leaf is the + // current internal node then this leaf is already a + // descendant of this internal node, and we can move on; + // otherwise, we need to add that ancestor to this + // internal node's child list, and update various + // relations + if (ctree[cancestor[j]].parent != ii) { + ctree[cancestor[j]].parent = ii; + newChild = new child; + newChild->type = DENDRO; + newChild->index = cancestor[j]; + newChild->next = nullptr; + // - attach child to list + if (ctree[ii].lastChild == nullptr) { + ctree[ii].children = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree = 1; + } else { + ctree[ii].lastChild->next = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree += 1; + } + } + } + // note new ancestry for this leaf + cancestor[j] = ii; + } + } + // update internal node index + ii++; + prev = curr; + curr = curr->next; + delete prev; + } + } + + // Return the consensus tree + igraph_vector_int_resize(parents, ii + orig_nodes); + if (weights) { + igraph_vector_resize(weights, ii); + } + + for (int i = 0; i < ii; i++) { + child *sat, *sit = ctree[i].children; + while (sit) { + VECTOR(*parents)[orig_nodes + i] = + ctree[i].parent < 0 ? -1 : orig_nodes + ctree[i].parent; + if (sit->type == GRAPH) { + VECTOR(*parents)[sit->index] = orig_nodes + i; + } + sat = sit; + sit = sit->next; + delete sat; + } + if (weights) { + VECTOR(*weights)[i] = ctree[i].weight; + } + ctree[i].children = nullptr; + } + + // Plus the isolate nodes + for (int i = 0; i < n; i++) { + if (cancestor[i] == -1) { + VECTOR(*parents)[i] = -1; + } + } +} + +// ********************************************************************** + +void dendro::recordDendrogramStructure(igraph_hrg_t *hrg) const noexcept { + for (int i = 0; i < n - 1; i++) { + int li = internal[i].L->index; + int ri = internal[i].R->index; + VECTOR(hrg->left )[i] = internal[i].L->type == DENDRO ? -li - 1 : li; + VECTOR(hrg->right)[i] = internal[i].R->type == DENDRO ? -ri - 1 : ri; + VECTOR(hrg->prob )[i] = internal[i].p; + VECTOR(hrg->edges)[i] = internal[i].e; + VECTOR(hrg->vertices)[i] = internal[i].n; + } +} + +igraph_error_t dendro::recordGraphStructure(igraph_t *graph) const noexcept { + igraph_vector_int_t edges; + int no_of_nodes = g->numNodes(); + int no_of_edges = g->numLinks() / 2; + int idx = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + for (int i = 0; i < n; i++) { + const edge *curr = g->getNeighborList(i); + while (curr) { + if (i < curr->x) { + VECTOR(edges)[idx++] = i; + VECTOR(edges)[idx++] = curr->x; + } + curr = curr->next; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, /* directed= */ false)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +// ********************************************************************** + +list* dendro::reversePathToRoot(const int leafIndex) { + list *head, *subhead, *newlist; + head = subhead = newlist = nullptr; + elementd *current = &leaf[leafIndex]; + + // continue until we're finished + while (current != nullptr) { + // add this node to the path + newlist = new list; + newlist->x = current->index; + newlist->next = nullptr; + if (head == nullptr) { + head = newlist; + } else { + subhead = head; + head = newlist; + head->next = subhead; + } + current = current->M; + } + return head; +} + +// *********************************************************************** + +bool dendro::sampleSplitLikelihoods() { + // In order to compute the majority agreement dendrogram at + // equilibrium, we need to calculate the leaf partition defined by + // each split (internal edge) of the tree. Because splits are only + // defined on a Cayley tree, the buildSplit() function returns the + // default "--...--" string for the root and the root's left + // child. When tabulating the frequency of splits, one of these + // needs to be excluded. + + string* array; + int k; + double tot; + + string new_split; + // To decompose the tree into its splits, we simply loop over all + // the internal nodes and replace the old split for the ith internal + // node with its new split. This is a bit time consuming to do + // O(n^2), so try not to do this very often. Once the decomposition + // is had, we insert them into the split histogram, which tracks the + // cumulative weight for each respective split observed. + + if (splithist == nullptr) { + splithist = new splittree; + } + for (int i = 0; i < (n - 1); i++) { + new_split = buildSplit(&internal[i]); + d->replaceSplit(i, new_split); + if (!new_split.empty() && new_split[1] != '-') { + if (!splithist->insertItem(new_split, 1.0)) { + return false; + } + } + } + splithist->finishedThisRound(); + + // For large graphs, the split histogram can get extremely large, so + // we need to employ some measures to prevent it from swamping the + // available memory. When the number of splits exceeds a threshold + // (say, a million), we progressively delete splits that have a + // weight less than a rising (k*0.001 of the total weight) fraction + // of the splits, on the assumption that losing such weight is + // unlikely to effect the ultimate split statistics. This deletion + // procedure is slow O(m lg m), but should only happen very rarely. + + int split_max = n * 500; + int leng; + if (splithist->returnNodecount() > split_max) { + k = 1; + while (splithist->returnNodecount() > split_max) { + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + leng = splithist->returnNodecount(); + for (int i = 0; i < leng; i++) { + if ((splithist->returnValue(array[i]) / tot) < k * 0.001) { + splithist->deleteItem(array[i]); + } + } + delete [] array; array = nullptr; + k++; + } + } + + return true; +} + +void dendro::sampleAdjacencyLikelihoods() { + // Here, we sample the probability values associated with every + // adjacency in A, weighted by their likelihood. The weighted + // histogram is stored in the graph data structure, so we simply + // need to add an observation to each node-pair that corresponds to + // the associated branch point's probability and the dendrogram's + // overall likelihood. + + double nn; + double norm = ((double)(n) * (double)(n)) / 4.0; + + if (L > 0.0) { + L = 0.0; + } + const elementd* ancestor; + list *currL, *prevL; + if (paths != nullptr) { + for (int i = 0; i < n; i++) { + currL = paths[i]; + while (currL != nullptr) { + prevL = currL; + currL = currL->next; + delete prevL; + prevL = nullptr; + } + paths[i] = nullptr; + } + delete [] paths; + } + paths = nullptr; + paths = new list* [n]; + for (int i = 0; i < n; i++) { + // construct paths from root, O(n^2) at worst + paths[i] = reversePathToRoot(i); + } + + // add obs for every node-pair, always O(n^2) + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + // find internal node, O(n) at worst + ancestor = findCommonAncestor(paths, i, j); + nn = ((double)(ancestor->L->n) * (double)(ancestor->R->n)) / norm; + // add obs of ->p to (i,j) histogram, and + g->addAdjacencyObs(i, j, ancestor->p, nn); + // add obs of ->p to (j,i) histogram + g->addAdjacencyObs(j, i, ancestor->p, nn); + } + } + + // finish-up: update total weight in histograms + g->addAdjacencyEnd(); +} + +void dendro::resetDendrograph() { + // Reset the dendrograph structure for the next trial + if (leaf != nullptr) { + delete [] leaf; // O(n) + leaf = nullptr; + } + if (internal != nullptr) { + delete [] internal; // O(n) + internal = nullptr; + } + if (d != nullptr) { + delete d; // O(n) + d = nullptr; + } + root = nullptr; + if (paths != nullptr) { + list *curr, *prev; + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != nullptr) { + prev = curr; + curr = curr->next; + delete prev; + prev = nullptr; + } + paths[i] = nullptr; + } + delete [] paths; + } + paths = nullptr; + L = 1.0; +} + +// ********************************************************************** +// *** COPYRIGHT NOTICE ************************************************* +// graph.h - graph data structure for hierarchical random graphs +// Copyright (C) 2005-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// ********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 8 November 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Graph data structure for hierarchical random graphs. The basic +// structure is an adjacency list of edges; however, many additional +// pieces of metadata are stored as well. Each node stores its +// external name, its degree and (if assigned) its group index. +// +// *********************************************************************** + +// ******** Constructor / Destructor ************************************* + +graph::graph(const int size, bool predict) : + predict(predict), n(size), m(0) +{ + IGRAPH_ASSERT(n >= 0); + nodes = new vert [n]; + nodeLink = new edge* [n]; + nodeLinkTail = new edge* [n]; + for (int i = 0; i < n; i++) { + nodeLink[i] = nullptr; + nodeLinkTail[i] = nullptr; + } + if (predict) { + A = new double** [n]; + for (int i = 0; i < n; i++) { + A[i] = new double* [n]; + } + obs_count = 0; + total_weight = 0.0; + bin_resolution = 0.0; + num_bins = 0; + } +} + +graph::~graph() { + edge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + while (curr != nullptr) { + prev = curr; + curr = curr->next; + delete prev; + } + } + delete [] nodeLink; + delete [] nodeLinkTail; + delete [] nodes; + + if (predict) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + delete [] A[i][j]; + } + delete [] A[i]; + } + delete [] A; + } +} + +// ********************************************************************** + +bool graph::addLink(const int i, const int j) { + // Adds the directed edge (i,j) to the adjacency list for v_i + edge* newedge; + if (i >= 0 && i < n && j >= 0 && j < n) { + newedge = new edge; + newedge->x = j; + if (nodeLink[i] == nullptr) { + // first neighbor + nodeLink[i] = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree = 1; + } else { + // subsequent neighbor + nodeLinkTail[i]->next = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree++; + } + // increment edge count + m++; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool graph::addAdjacencyObs(const int i, const int j, + const double probability, const double size) { + // Adds the observation obs to the histogram of the edge (i,j) + // Note: user must manually add observation to edge (j,i) by calling + // this function with that argument + if (bin_resolution > 0.0 && probability >= 0.0 && probability <= 1.0 + && size >= 0.0 && size <= 1.0 + && i >= 0 && i < n && j >= 0 && j < n) { + int index = (int)(probability / bin_resolution + 0.5); + if (index < 0) { + index = 0; + } else if (index > num_bins) { + index = num_bins; + } + + // Add the weight to the proper probability bin + if (A[i][j][index] < 0.5) { + A[i][j][index] = 1.0; + } else { + A[i][j][index] += 1.0; + } + return true; + } + return false; +} + +// ********************************************************************** + +void graph::addAdjacencyEnd() { + // We need to also keep a running total of how much weight has been added + // to the histogram, and the number of observations in the histogram. + if (obs_count == 0) { + total_weight = 1.0; obs_count = 1; + } else { + total_weight += 1.0; obs_count++; + } +} + +bool graph::doesLinkExist(const int i, const int j) const { + // This function determines if the edge (i,j) already exists in the + // adjacency list of v_i + edge* curr; + if (i >= 0 && i < n && j >= 0 && j < n) { + curr = nodeLink[i]; + while (curr != nullptr) { + if (curr->x == j) { + return true; + } + curr = curr->next; + } + } + return false; +} + +// ********************************************************************** + +int graph::getDegree(const int i) const { + if (i >= 0 && i < n) { + return nodes[i].degree; + } else { + return -1; + } +} + +string graph::getName(const int i) const { + if (i >= 0 && i < n) { + return nodes[i].name; + } else { + return ""; + } +} + +// NOTE: Returns address; deallocation of returned object is dangerous +const edge* graph::getNeighborList(const int i) const noexcept { + if (i >= 0 && i < n) { + return nodeLink[i]; + } else { + return nullptr; + } +} + +double* graph::getAdjacencyHist(const int i, const int j) const { + if (i >= 0 && i < n && j >= 0 && j < n) { + return A[i][j]; + } else { + return nullptr; + } +} + +// ********************************************************************** + +double graph::getAdjacencyAverage(const int i, const int j) const { + double average = 0.0; + if (i != j) { + for (int k = 0; k < num_bins; k++) { + if (A[i][j][k] > 0.0) { + average += (A[i][j][k] / total_weight) * ((double)(k) * bin_resolution); + } + } + } + return average; +} + +int graph::numLinks() const { + return m; +} + +int graph::numNodes() const { + return n; +} + +double graph::getBinResolution() const { + return bin_resolution; +} + +int graph::getNumBins() const { + return num_bins; +} + +double graph::getTotalWeight() const { + return total_weight; +} + +// *********************************************************************** + +void graph::resetAllAdjacencies() { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } + } + obs_count = 0; + total_weight = 0.0; +} + +// ********************************************************************** + +void graph::resetAdjacencyHistogram(const int i, const int j) { + if (i >= 0 && i < n && j >= 0 && j < n) { + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } +} + +// ********************************************************************** + +void graph::resetLinks() { + edge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + while (curr != nullptr) { + prev = curr; + curr = curr->next; + delete prev; + } + nodeLink[i] = nullptr; + nodeLinkTail[i] = nullptr; + nodes[i].degree = 0; + } + m = 0; +} + +// ********************************************************************** + +void graph::setAdjacencyHistograms(const igraph_int_t bin_count) { + // For all possible adjacencies, setup an edge histograms + num_bins = bin_count + 1; + bin_resolution = 1.0 / (double)(bin_count); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + A[i][j] = new double [num_bins]; + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } + } +} + +bool graph::setName(const int i, const string &text) { + if (i >= 0 && i < n) { + nodes[i].name = text; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +interns::interns(const int n) : + q(n), count(0) +{ + IGRAPH_ASSERT(n >= 0); + edgelist = new ipair [q]; + IGRAPH_ASSUME(q >= 0); // work around false positive GCC warning + splitlist = new string [q + 1]; + indexLUT = new int* [q + 1]; + for (int i = 0; i < (q + 1); i++) { + indexLUT[i] = new int [2]; + indexLUT[i][0] = indexLUT[i][1] = -1; + } +} +interns::~interns() { + delete [] edgelist; + delete [] splitlist; + for (int i = 0; i < (q + 1); i++) { + delete [] indexLUT[i]; + } + delete [] indexLUT; +} + +// *********************************************************************** + +// NOTE: Returns an address to another object -- do not deallocate +ipair* interns::getEdge(const int i) const { + return &edgelist[i]; +} + +// *********************************************************************** + +// NOTE: Returns an address to another object -- do not deallocate +ipair* interns::getRandomEdge() const { + return &edgelist[(int)(floor((double)(q) * RNG_UNIF01()))]; +} + +// *********************************************************************** + +string interns::getSplit(const int i) const { + if (i >= 0 && i <= q) { + return splitlist[i]; + } else { + return ""; + } +} + +// ********************************************************************** + +bool interns::addEdge(const int new_x, const int new_y, + const short int new_type) { + // This function adds a new edge (i,j,t,sp) to the list of internal + // edges. After checking that the inputs fall in the appropriate + // range of values, it records the new edgelist index in the + // indexLUT and then puts the input values into that edgelist + // location. + + if (count < q && new_x >= 0 && new_x < (q + 1) && new_y >= 0 && + new_y < (q + 2) && (new_type == LEFT || new_type == RIGHT)) { + if (new_type == LEFT) { + indexLUT[new_x][0] = count; + } else { + indexLUT[new_x][1] = count; + } + edgelist[count].x = new_x; + edgelist[count].y = new_y; + edgelist[count].t = new_type; + count++; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +bool interns::replaceSplit(const int i, const string &sp) { + // When an internal edge is changed, its split must be replaced as + // well. This function provides that access; it stores the split + // defined by an internal edge (x,y) at the location [y], which + // is unique. + + if (i >= 0 && i <= q) { + splitlist[i] = sp; + return true; + } + return false; +} + +// *********************************************************************** + +bool interns::swapEdges(const int one_x, const int one_y, + const short int one_type, const int two_x, + const int two_y, const short int two_type) { + // The moves on the dendrogram always swap edges, either of which + // (or both, or neither) can by internal edges. So, this function + // mirrors that operation for the internal edgelist and indexLUT. + + int index, jndex, temp; + bool one_isInternal = false; + bool two_isInternal = false; + + if (one_x >= 0 && one_x < (q + 1) && two_x >= 0 && two_x < (q + 1) && + (two_type == LEFT || two_type == RIGHT) && + one_y >= 0 && one_y < (q + 2) && two_y >= 0 && + two_y < (q + 2) && (one_type == LEFT || one_type == RIGHT)) { + + if (one_type == LEFT) { + temp = 0; + } else { + temp = 1; + } + if (indexLUT[one_x][temp] > -1) { + one_isInternal = true; + } + if (two_type == LEFT) { + temp = 0; + } else { + temp = 1; + } + if (indexLUT[two_x][temp] > -1) { + two_isInternal = true; + } + + if (one_isInternal && two_isInternal) { + if (one_type == LEFT) { + index = indexLUT[one_x][0]; + } else { + index = indexLUT[one_x][1]; + } + if (two_type == LEFT) { + jndex = indexLUT[two_x][0]; + } else { + jndex = indexLUT[two_x][1]; + } + temp = edgelist[index].y; + edgelist[index].y = edgelist[jndex].y; + edgelist[jndex].y = temp; + + } else if (one_isInternal) { + if (one_type == LEFT) { + index = indexLUT[one_x][0]; indexLUT[one_x][0] = -1; + } else { + index = indexLUT[one_x][1]; indexLUT[one_x][1] = -1; + } + edgelist[index].x = two_x; + edgelist[index].t = two_type; + if (two_type == LEFT) { + indexLUT[two_x][0] = index; + } else { + indexLUT[two_x][1] = index; + } // add new + + } else if (two_isInternal) { + if (two_type == LEFT) { + index = indexLUT[two_x][0]; indexLUT[two_x][0] = -1; + } else { + index = indexLUT[two_x][1]; indexLUT[two_x][1] = -1; + } + edgelist[index].x = one_x; + edgelist[index].t = one_type; + if (one_type == LEFT) { + indexLUT[one_x][0] = index; + } else { + indexLUT[one_x][1] = index; + } // add new + } else { + // do nothing + } // else neither is internal + + return true; + } else { + return false; + } +} + +// ******** Red-Black Tree Methods *************************************** + +splittree::splittree() { + root = new elementsp; + leaf = new elementsp; + + leaf->parent = root; + + root->left = leaf; + root->right = leaf; +} + +splittree::~splittree() { + if (root != nullptr && (root->left != leaf || root->right != leaf)) { + deleteSubTree(root); root = nullptr; + } + delete root; + delete leaf; +} + +void splittree::deleteTree() { + if (root != nullptr) { + deleteSubTree(root); + root = nullptr; + } +} + +void splittree::deleteSubTree(elementsp *z) { + if (z->left != leaf) { + deleteSubTree(z->left); + z->left = nullptr; + } + if (z->right != leaf) { + deleteSubTree(z->right); + z->right = nullptr; + } + delete z; +} + +// ******** Reset Functions ********************************************* + +// O(n lg n) +void splittree::clearTree() { + string *array = returnArrayOfKeys(); + for (int i = 0; i < support; i++) { + deleteItem(array[i]); + } + delete [] array; +} + +// ******** Search Functions ********************************************* +// public search function - if there exists a elementsp in the tree +// with key=searchKey, it returns TRUE and foundNode is set to point +// to the found node; otherwise, it sets foundNode=nullptr and returns +// FALSE +elementsp* splittree::findItem(const string &searchKey) { + + elementsp *current = root; + if (current->split.empty()) { + return nullptr; // empty tree; bail out + } + while (current != leaf) { + if (searchKey.compare(current->split) < 0) { // left-or-right? + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // failure; bail out + return nullptr; + } + } else { + if (searchKey.compare(current->split) > 0) { + // left-or-right? + if (current->right != leaf) { + // try moving down-left + current = current->right; + } else { + // failure; bail out + return nullptr; + } + } else { + // found (searchKey==current->split) + return current; + } + } + } + return nullptr; +} + +double splittree::returnValue(const string &searchKey) { + const elementsp* test = findItem(searchKey); + if (test == nullptr) { + return 0.0; + } else { + return test->weight; + } +} + + +// ******** Return Item Functions *************************************** +// public function which returns the tree, via pre-order traversal, as +// a linked list + +string* splittree::returnArrayOfKeys() { + IGRAPH_ASSERT(support >= 0); + string* array = new string [support]; + bool flag_go = true; + int index = 0; + elementsp *curr; + + if (support == 1) { + array[0] = root->split; + } else if (support == 2) { + array[0] = root->split; + if (root->left == leaf) { + array[1] = root->right->split; + } else { + array[1] = root->left->split; + } + } else { + /* TODO: This is present in the original consensusHRG code, + * but it makes no sense to assign -1 to a string. */ + /* + for (int i = 0; i < support; i++) { + array[i] = -1; + } + */ + // non-recursive traversal of tree structure + curr = root; + curr->mark = 1; + while (flag_go) { + + // - is it time, and is left child the leaf node? + if (curr->mark == 1 && curr->left == leaf) { + curr->mark = 2; + } + // - is it time, and is right child the leaf node? + if (curr->mark == 2 && curr->right == leaf) { + curr->mark = 3; + } + if (curr->mark == 1) { // - go left + curr->mark = 2; + curr = curr->left; + curr->mark = 1; + } else if (curr->mark == 2) { // - else go right + curr->mark = 3; + curr = curr->right; + curr->mark = 1; + } else { // - else go up a level + curr->mark = 0; + array[index++] = curr->split; + curr = curr->parent; + if (curr == nullptr) { + flag_go = false; + } + } + } + } + + return array; +} + +slist* splittree::returnListOfKeys() { + keyValuePairSplit *curr, *prev; + slist *head = nullptr, *tail = nullptr, *newlist; + + curr = returnTreeAsList(); + while (curr != nullptr) { + newlist = new slist; + newlist->x = curr->x; + if (head == nullptr) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + prev = curr; + curr = curr->next; + delete prev; + prev = nullptr; + } + return head; +} + +// pre-order traversal +keyValuePairSplit* splittree::returnTreeAsList() { + keyValuePairSplit *head, *tail; + + head = new keyValuePairSplit; + head->x = root->split; + head->y = root->weight; + head->c = root->count; + tail = head; + + if (root->left != leaf) { + tail = returnSubtreeAsList(root->left, tail); + } + if (root->right != leaf) { + tail = returnSubtreeAsList(root->right, tail); + } + + if (head->x.empty()) { + return nullptr; /* empty tree */ + } else { + return head; + } +} + +keyValuePairSplit* splittree::returnSubtreeAsList(elementsp *z, + keyValuePairSplit *head) { + keyValuePairSplit *newnode, *tail; + + newnode = new keyValuePairSplit; + newnode->x = z->split; + newnode->y = z->weight; + newnode->c = z->count; + head->next = newnode; + tail = newnode; + + if (z->left != leaf) { + tail = returnSubtreeAsList(z->left, tail); + } + if (z->right != leaf) { + tail = returnSubtreeAsList(z->right, tail); + } + + return tail; +} + +keyValuePairSplit splittree::returnMaxKey() { + keyValuePairSplit themax; + elementsp *current; + current = root; + // search to bottom-right corner of tree + while (current->right != leaf) { + current = current->right; + } + themax.x = current->split; + themax.y = current->weight; + + return themax; +} + +keyValuePairSplit splittree::returnMinKey() { + keyValuePairSplit themin; + elementsp *current; + current = root; + // search to bottom-left corner of tree + while (current->left != leaf) { + current = current->left; + } + themin.x = current->split; + themin.y = current->weight; + + return themin; +} + +// private functions for deleteItem() (although these could easily be +// made public, I suppose) +elementsp* splittree::returnMinKey(elementsp *z) { + elementsp *current; + + current = z; + // search to bottom-right corner of tree + while (current->left != leaf) { + current = current->left; + } + // return pointer to the minimum + return current; +} + +elementsp* splittree::returnSuccessor(elementsp *z) { + elementsp *current, *w; + + w = z; +// if right-subtree exists, return min of it + if (w->right != leaf) { + return returnMinKey(w->right); + } + // else search up in tree + // move up in tree until find a non-right-child + current = w->parent; + while ((current != nullptr) && (w == current->right)) { + w = current; + current = current->parent; + } + return current; +} + +int splittree::returnNodecount() { + IGRAPH_ASSERT(support > 0); + return support; +} + +keyValuePairSplit* splittree::returnTheseSplits(const int target) { + keyValuePairSplit *head, *curr, *prev, *newhead, *newtail, *newpair; + int count; + + head = returnTreeAsList(); + prev = newhead = newtail = newpair = nullptr; + curr = head; + + while (curr != nullptr) { + count = 0; + for (auto c : curr->x) { + if (c == 'M') count++; + } + if (count == target && curr->x[1] != '*') { + newpair = new keyValuePairSplit; + newpair->x = curr->x; + newpair->y = curr->y; + newpair->next = nullptr; + if (newhead == nullptr) { + newhead = newpair; newtail = newpair; + } else { + newtail->next = newpair; newtail = newpair; + } + } + prev = curr; + curr = curr->next; + delete prev; + } + + return newhead; +} + +double splittree::returnTotal() const { + return total_weight; +} + +// ******** Insert Functions ********************************************* + +void splittree::finishedThisRound() { + // We need to also keep a running total of how much weight has been + // added to the histogram. + if (total_count == 0) { + total_weight = 1.0; total_count = 1; + } else { + total_weight += 1.0; total_count++; + } +} + +// public insert function +bool splittree::insertItem(const string &newKey, double newValue) { + + // first we check to see if newKey is already present in the tree; + // if so, we do nothing; if not, we must find where to insert the + // key + elementsp *newNode, *current; + + // find newKey in tree; return pointer to it O(log k) + current = findItem(newKey); + if (current != nullptr) { + current->weight += 1.0; + // And finally, we keep track of how many observations went into + // the histogram + current->count++; + return true; + } else { + newNode = new elementsp; // elementsp for the splittree + newNode->split = newKey; // store newKey + newNode->weight = newValue; // store newValue + newNode->color = true; // new nodes are always RED + newNode->parent = nullptr; // new node initially has no parent + newNode->left = leaf; // left leaf + newNode->right = leaf; // right leaf + newNode->count = 1; + support++; // increment node count in splittree + + // must now search for where to insert newNode, i.e., find the + // correct parent and set the parent and child to point to each + // other properly + current = root; + if (current->split.empty()) { // insert as root + delete root; // delete old root + root = newNode; // set root to newNode + leaf->parent = newNode; // set leaf's parent + current = leaf; // skip next loop + } + + // search for insertion point + while (current != leaf) { + // left-or-right? + if (newKey.compare(current->split) < 0) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // else found new parent + newNode->parent = current; // set parent + current->left = newNode; // set child + current = leaf; // exit search + } + } else { // + if (current->right != leaf) { + // try moving down-right + current = current->right; + } else { + // else found new parent + newNode->parent = current; // set parent + current->right = newNode; // set child + current = leaf; // exit search + } + } + } + + // now do the house-keeping necessary to preserve the red-black + // properties + insertCleanup(newNode); + + } + return true; +} + +// private house-keeping function for insertion +void splittree::insertCleanup(elementsp *z) { + + // fix now if z is root + if (z->parent == nullptr) { + z->color = false; + return; + } + elementsp *temp; + // while z is not root and z's parent is RED + while (z->parent != nullptr && z->parent->color) { + if (z->parent == z->parent->parent->left) { // z's parent is LEFT-CHILD + temp = z->parent->parent->right; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpa RED (Case 1) + z = z->parent->parent; // set z = z's grandpa (Case 1) + } else { + if (z == z->parent->right) { // z is RIGHT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateLeft(z); // perform left-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpa RED (Case 3) + rotateRight(z->parent->parent); // perform right-rotation (Case 3) + } + } else { // z's parent is RIGHT-CHILD + temp = z->parent->parent->left; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpa RED (Case 1) + z = z->parent->parent; // set z = z's grandpa (Case 1) + } else { + if (z == z->parent->left) { // z is LEFT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateRight(z); // perform right-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpa RED (Case 3) + rotateLeft(z->parent->parent); // perform left-rotation (Case 3) + } + } + } + + root->color = false; // color the root BLACK +} + +// ******** Delete Functions ******************************************** +// public delete function +void splittree::deleteItem(const string &killKey) { + elementsp *x, *y, *z; + + z = findItem(killKey); + if (z == nullptr) { + return; // item not present; bail out + } + + if (support == 1) { // -- attempt to delete the root + root->split = ""; // restore root node to default state + root->weight = 0.0; // + root->color = false; // + root->parent = nullptr; // + root->left = leaf; // + root->right = leaf; // + support--; // set support to zero + total_weight = 0.0; // set total weight to zero + total_count--; // + return; // exit - no more work to do + } + + if (z != nullptr) { + support--; // decrement node count + if ((z->left == leaf) || (z->right == leaf)) { + // case of less than two children + y = z; // set y to be z + } else { + y = returnSuccessor(z); // set y to be z's key-successor + } + + if (y->left != leaf) { + x = y->left; // pick y's one child (left-child) + } else { + x = y->right; // (right-child) + } + x->parent = y->parent; // make y's child's parent be y's parent + + if (y->parent == nullptr) { + root = x; // if y is the root, x is now root + } else { + if (y == y->parent->left) {// decide y's relationship with y's parent + y->parent->left = x; // replace x as y's parent's left child + } else { + y->parent->right = x; + } // replace x as y's parent's left child + } + + if (y != z) { // insert y into z's spot + z->split = y->split; // copy y data into z + z->weight = y->weight; // + z->count = y->count; // + } // + + // do house-keeping to maintain balance + if (y->color == false) { + deleteCleanup(x); + } + delete y; // deallocate y + y = nullptr; // point y to nullptr for safety + } +} + +void splittree::deleteCleanup(elementsp *x) { + elementsp *w, *t; + // until x is the root, or x is RED + while ((x != root) && (x->color == false)) { + if (x == x->parent->left) { // branch on x being a LEFT-CHILD + w = x->parent->right; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateLeft(x->parent); // left rotation on x's parent (case 1) + w = x->parent->right; // make w be x's right sibling (case 1) + } + if ((w->left->color == false) && (w->right->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { // + if (w->right->color == false) { + w->left->color = false; // color w's left child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent + rotateRight(w); // right rotation on w (case 3) + x->parent = t; // restore x's parent + w = x->parent->right; // make w be x's right sibling (case 3) + } // + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->right->color = false; // color w's right child BLACK (case 4) + rotateLeft(x->parent); // left rotation on x's parent (case 4) + x = root; // finished work. bail out (case 4) + } // + } else { // x is RIGHT-CHILD + w = x->parent->left; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateRight(x->parent); // right rotation on x's parent (case 1) + w = x->parent->left; // make w be x's left sibling (case 1) + } + if ((w->right->color == false) && (w->left->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { // + if (w->left->color == false) { // + w->right->color = false; // color w's right child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent + rotateLeft(w); // left rotation on w (case 3) + x->parent = t; // restore x's parent + w = x->parent->left; // make w be x's left sibling (case 3) + } // + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->left->color = false; // color w's left child BLACK (case 4) + rotateRight(x->parent); // right rotation on x's parent (case 4) + x = root; // x is now the root (case 4) + } + } + } + x->color = false; // color x (the root) BLACK (exit) +} + +// ******** Rotation Functions ******************************************* + +void splittree::rotateLeft(elementsp *x) { + // do pointer-swapping operations for left-rotation + elementsp *y = x->right; // grab right child + x->right = y->left; // make x's RIGHT-CHILD be y's LEFT-CHILD + y->left->parent = x; // make x be y's LEFT-CHILD's parent + y->parent = x->parent; // make y's new parent be x's old parent + + if (x->parent == nullptr) { + root = y; // if x was root, make y root + } else { // + if (x == x->parent->left) { // if x is LEFT-CHILD, make y be x's parent's + x->parent->left = y; // left-child + } else { + x->parent->right = y; // right-child + } + } + y->left = x; // make x be y's LEFT-CHILD + x->parent = y; // make y be x's parent +} + +void splittree::rotateRight(elementsp *y) { + // do pointer-swapping operations for right-rotation + elementsp *x = y->left; // grab left child + y->left = x->right; // replace left child yith x's right subtree + x->right->parent = y; // replace y as x's right subtree's parent + + x->parent = y->parent; // make x's new parent be y's old parent + if (y->parent == nullptr) { + root = x; // if y was root, make x root + } else { + if (y == y->parent->right) { // if y is R-CHILD, make x be y's parent's + y->parent->right = x; // right-child + } else { + y->parent->left = x; // left-child + } + } + x->right = y; // make y be x's RIGHT-CHILD + y->parent = x; // make x be y's parent +} + +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// graph_simp.h - graph data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 21 June 2006 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// ************************************************************************ + +// ******** Constructor / Destructor ************************************* + +simpleGraph::simpleGraph(const int size) : + n(size), m(0), num_groups(0) +{ + nodes = new simpleVert [n]; + nodeLink = new simpleEdge* [n]; + nodeLinkTail = new simpleEdge* [n]; + A = new double* [n]; + for (int i = 0; i < n; i++) { + nodeLink[i] = nullptr; nodeLinkTail[i] = nullptr; + A[i] = new double [n]; + for (int j = 0; j < n; j++) { + A[i][j] = 0.0; + } + } + E = nullptr; +} + +simpleGraph::~simpleGraph() { + simpleEdge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + delete [] A[i]; + while (curr != nullptr) { + prev = curr; + curr = curr->next; + delete prev; + } + } + delete [] E; + delete [] A; + delete [] nodeLink; + delete [] nodeLinkTail; + delete [] nodes; +} + +// *********************************************************************** + +bool simpleGraph::addGroup(const int i, const int group_index) { + if (i >= 0 && i < n) { + nodes[i].group_true = group_index; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool simpleGraph::addLink(const int i, const int j) { + // Adds the directed edge (i,j) to the adjacency list for v_i + simpleEdge* newedge; + if (i >= 0 && i < n && j >= 0 && j < n) { + A[i][j] = 1.0; + newedge = new simpleEdge; + newedge->x = j; + if (nodeLink[i] == nullptr) { // first neighbor + nodeLink[i] = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree = 1; + } else { // subsequent neighbor + nodeLinkTail[i]->next = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree++; + } + m++; // increment edge count + newedge = nullptr; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool simpleGraph::doesLinkExist(const int i, const int j) const { + // This function determines if the edge (i,j) already exists in the + // adjacency list of v_i + if (i >= 0 && i < n && j >= 0 && j < n) { + if (A[i][j] > 0.1) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +// ********************************************************************** + +double simpleGraph::getAdjacency(const int i, const int j) const { + if (i >= 0 && i < n && j >= 0 && j < n) { + return A[i][j]; + } else { + return -1.0; + } +} + +int simpleGraph::getDegree(const int i) const { + if (i >= 0 && i < n) { + return nodes[i].degree; + } else { + return -1; + } +} + +int simpleGraph::getGroupLabel(const int i) const { + if (i >= 0 && i < n) { + return nodes[i].group_true; + } else { + return -1; + } +} + +string simpleGraph::getName(const int i) const { + if (i >= 0 && i < n) { + return nodes[i].name; + } else { + return ""; + } +} + +// NOTE: The following three functions return addresses; deallocation +// of returned object is dangerous +const simpleEdge* simpleGraph::getNeighborList(const int i) const { + if (i >= 0 && i < n) { + return nodeLink[i]; + } else { + return nullptr; + } +} +// END-NOTE + +// ********************************************************************* + +int simpleGraph::getNumGroups() const { + return num_groups; +} +int simpleGraph::getNumLinks() const { + return m; +} +int simpleGraph::getNumNodes() const { + return n; +} +const simpleVert* simpleGraph::getNode(const int i) const { + if (i >= 0 && i < n) { + return &nodes[i]; + } else { + return nullptr; + } +} + +// ********************************************************************** + +bool simpleGraph::setName(const int i, const string &text) { + if (i >= 0 && i < n) { + nodes[i].name = text; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +void simpleGraph::QsortMain (block* array, int left, int right) { + if (right > left) { + int pivot = left; + int part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } +} + +int simpleGraph::QsortPartition (block* array, int left, int right, + int index) { + block p_value = array[index]; + + std::swap(array[index], array[right]); + + int stored = left; + for (int i = left; i < right; i++) { + if (array[i].x <= p_value.x) { + std::swap(array[stored], array[i]); + stored++; + } + } + std::swap(array[right], array[stored]); + + return stored; +} + +// *********************************************************************** diff --git a/src/hrg/rbtree.h b/src/hrg/rbtree.h new file mode 100644 index 0000000..309db8f --- /dev/null +++ b/src/hrg/rbtree.h @@ -0,0 +1,144 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// rbtree - red-black tree (self-balancing binary tree data structure) +// Copyright (C) 2004 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : Spring 2004 +// Modified : many, many times +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_RBTREE +#define IGRAPH_HRG_RBTREE + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +struct list { + int x = -1; // stored elementd in linked-list + list* next = nullptr; // pointer to next elementd +}; + +struct keyValuePair { + int x = -1; // elementrb key (int) + int y = -1; // stored value (int) + keyValuePair* next = nullptr; // linked-list pointer +}; + +// ******** Tree elementrb Class ***************************************** + +struct elementrb { + int key = -1; // search key (int) + int value = -1; // stored value (int) + + bool color = false; // F: BLACK, T: RED + short int mark = 0; // marker + + elementrb *parent = nullptr; // pointer to parent node + elementrb *left = nullptr; // pointer for left subtree + elementrb *right = nullptr; // pointer for right subtree +}; + +// ******** Red-Black Tree Class ***************************************** +// This vector implementation is a red-black balanced binary tree data +// structure. It provides find a stored elementrb in time O(log n), +// find the maximum elementrb in time O(1), delete an elementrb in +// time O(log n), and insert an elementrb in time O(log n). +// +// Note that the key=0 is assumed to be a special value, and thus you +// cannot insert such an item. Beware of this limitation. + +class rbtree { + elementrb* root; // binary tree root + elementrb* leaf; // all leaf nodes + int support; // number of nodes in the tree + + void rotateLeft(elementrb *x); // left-rotation operator + void rotateRight(elementrb *y); // right-rotation operator + void insertCleanup(elementrb *z); // house-keeping after insertion + void deleteCleanup(elementrb *x); // house-keeping after deletion + keyValuePair* returnSubtreeAsList(const elementrb *z, keyValuePair *head) const; + void deleteSubTree(elementrb *z); // delete subtree rooted at z + elementrb* returnMinKey(elementrb *z) const; // returns minimum of subtree + // rooted at z + elementrb* returnSuccessor(elementrb *z) const; // returns successor of z's key + +public: + rbtree(); ~rbtree(); // default constructor/destructor + + // returns value associated with searchKey + int returnValue(int searchKey) const; + // returns T if searchKey found, and points foundNode at the + // corresponding node + elementrb* findItem(int searchKey) const; + // insert a new key with stored value + void insertItem(int newKey, int newValue); + // delete a node with given key + void deleteItem(int killKey); + // replace value of a node with given key + void replaceItem(int key, int newValue); + // increment the value of the given key + void incrementValue(int key); + // delete the entire tree + void deleteTree(); + // return array of keys in tree + int* returnArrayOfKeys() const; + // return list of keys in tree + list* returnListOfKeys() const; + // return the tree as a list of keyValuePairs + keyValuePair* returnTreeAsList() const; + // returns the maximum key in the tree + keyValuePair returnMaxKey() const; + // returns the minimum key in the tree + keyValuePair returnMinKey() const; + // returns number of items in tree + int returnNodecount() const; +}; + +} +#endif diff --git a/src/hrg/splittree_eq.h b/src/hrg/splittree_eq.h new file mode 100644 index 0000000..28d03b2 --- /dev/null +++ b/src/hrg/splittree_eq.h @@ -0,0 +1,168 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// splittree_eq.h - a binary search tree data structure for storing dendrogram split frequencies +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 19 April 2006 +// Modified : 19 May 2007 +// : 20 May 2008 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Data structure for storing the split frequences in the sampled +// dendrograms. Data is stored efficiently as a red-black binary +// search tree (this is a modified version of the rbtree.h file). +// +// *********************************************************************** + +#ifndef IGRAPH_HRG_SPLITTREE +#define IGRAPH_HRG_SPLITTREE + +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +struct slist { + std::string x; // stored elementd in linked-list + slist* next = nullptr; // pointer to next elementd +}; + +struct keyValuePairSplit { + std::string x; // elementsp split (string) + double y = 0.0; // stored weight (double) + int c = 0; // stored count (int) + keyValuePairSplit* next = nullptr; // linked-list pointer +}; + +// ******** Tree elementsp Class ***************************************** + +struct elementsp { + std::string split; // split represented as a string + double weight = 0.0; // total weight of this split + int count = 0; // number of observations of this split + + bool color = false; // F: BLACK, T: RED + short int mark = 0; // marker + + elementsp *parent = nullptr; // pointer to parent node + elementsp *left = nullptr; // pointer for left subtree + elementsp *right = nullptr; // pointer for right subtree +}; + +// ******** Red-Black Tree Class ***************************************** +// This vector implementation is a red-black balanced binary tree data +// structure. It provides find a stored elementsp in time O(log n), +// find the maximum elementsp in time O(1), delete an elementsp in +// time O(log n), and insert an elementsp in time O(log n). +// +// Note that the split="" is assumed to be a special value, and thus +// you cannot insert such an item. Beware of this limitation. +// + +class splittree { + elementsp* root; // binary tree root + elementsp* leaf; // all leaf nodes + int support = 0; // number of nodes in the tree + double total_weight = 0.0; // total weight stored + int total_count = 0; // total number of observations stored + + // left-rotation operator + void rotateLeft(elementsp*); + // right-rotation operator + void rotateRight(elementsp*); + // house-keeping after insertion + void insertCleanup(elementsp*); + // house-keeping after deletion + void deleteCleanup(elementsp*); + keyValuePairSplit* returnSubtreeAsList(elementsp*, keyValuePairSplit*); + // delete subtree rooted at z + void deleteSubTree(elementsp*); + // returns minimum of subtree rooted at z + elementsp* returnMinKey(elementsp*); + // returns successor of z's key + elementsp* returnSuccessor(elementsp*); + +public: + // default constructor/destructor + splittree(); ~splittree(); + // returns value associated with searchKey + double returnValue(const std::string &); + // returns T if searchKey found, and points foundNode at the + // corresponding node + elementsp* findItem(const std::string &); + // update total_count and total_weight + void finishedThisRound(); + // insert a new key with stored value + bool insertItem(const std::string &, double); + void clearTree(); + // delete a node with given key + void deleteItem(const std::string &); + // delete the entire tree + void deleteTree(); + // return array of keys in tree + std::string* returnArrayOfKeys(); + // return list of keys in tree + slist* returnListOfKeys(); + // return the tree as a list of keyValuePairSplits + keyValuePairSplit* returnTreeAsList(); + // returns the maximum key in the tree + keyValuePairSplit returnMaxKey(); + // returns the minimum key in the tree + keyValuePairSplit returnMinKey(); + // returns number of items in tree + int returnNodecount(); + // returns list of splits with given number of Ms + keyValuePairSplit* returnTheseSplits(int); + // returns sum of stored values + double returnTotal() const; +}; + +} // namespace fitHRG + +#endif diff --git a/src/internal/glpk_support.c b/src/internal/glpk_support.c new file mode 100644 index 0000000..ab0f61c --- /dev/null +++ b/src/internal/glpk_support.c @@ -0,0 +1,169 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "internal/glpk_support.h" + +#ifdef HAVE_GLPK + +#include "igraph_error.h" + +#include "core/interruption.h" + +#include + +IGRAPH_THREAD_LOCAL igraph_i_glpk_error_info_t igraph_i_glpk_error_info; + +/* glp_at_error() was added in GLPK 4.57. Due to the R interface, we need to + * support ancient GLPK versions like GLPK 4.38 so we need to guard the + * invocation of glp_at_error(). Note that this is a temporary workaround only + * for sake of supporting R 4.1, so it is enabled only if USING_R is defined */ +#ifdef USING_R +# define HAS_GLP_AT_ERROR (GLP_MAJOR_VERSION > 4 || (GLP_MAJOR_VERSION == 4 && GLP_MINOR_VERSION >= 57)) +#else +# define HAS_GLP_AT_ERROR 1 +#endif + +int igraph_i_glpk_terminal_hook(void *info, const char *s) { + IGRAPH_UNUSED(info); + + if (igraph_i_interruption_handler && + !igraph_i_glpk_error_info.is_interrupted && + igraph_allow_interruption() != IGRAPH_SUCCESS) { + /* If an interruption has already occurred, do not set another error, + to avoid an infinite loop between the term_hook (this function) + and the error_hook. */ + igraph_i_glpk_error_info.is_interrupted = true; + glp_error("GLPK was interrupted."); /* This dummy message is never printed */ +#if HAS_GLP_AT_ERROR + } else if (glp_at_error()) { + /* Copy the error messages into a buffer for later reporting */ + /* We must use glp_at_error() instead of igraph_i_glpk_error_info.is_error + * to determine if a message is an error message, as the reporting function is + * called before the error function. */ + const size_t n = sizeof(igraph_i_glpk_error_info.msg) / sizeof(char) - 1; + while (*s != '\0' && igraph_i_glpk_error_info.msg_ptr < igraph_i_glpk_error_info.msg + n) { + *(igraph_i_glpk_error_info.msg_ptr++) = *(s++); + } + *igraph_i_glpk_error_info.msg_ptr = '\0'; +#endif + } + + return 1; /* Non-zero return value signals to GLPK not to print to the terminal */ +} + +void igraph_i_glpk_error_hook(void *info) { + IGRAPH_UNUSED(info); + igraph_i_glpk_error_info.is_error = true; + glp_free_env(); + longjmp(igraph_i_glpk_error_info.jmp, 1); +} + +void igraph_i_glpk_interruption_hook(glp_tree *tree, void *info) { + IGRAPH_UNUSED(info); + + /* This is a callback function meant to be used with glp_intopt(), + in order to support interruption. It is essentially a GLPK-compatible + replacement for IGRAPH_ALLOW_INTERRUPTION(). + Calling glp_ios_terminate() from glp_intopt()'s callback function + signals to GLPK that it should terminate the optimization and return + with the code GLP_ESTOP. + */ + if (igraph_i_interruption_handler) { + if (igraph_allow_interruption() != IGRAPH_SUCCESS) { + glp_ios_terminate(tree); + } + } +} + +/** + * \ingroup internal + * \function igraph_i_glp_delete_prob + * \brief Safe replacement for glp_delete_prob(). + * + * This function is meant to be used with IGRAPH_FINALLY() + * in conjunction with glp_create_prob(). + * + * When using GLPK, normally glp_delete_prob() is used to free + * problems created with glp_create_prob(). However, when GLPK + * encounters an error, the error handler installed by igraph + * will call glp_free_env() which invalidates all problems. + * Calling glp_delete_prob() would then lead to a crash. + * This replacement function avoids this situation by first + * checking if GLPK is at an error state. + */ +void igraph_i_glp_delete_prob(glp_prob *p) { + if (! igraph_i_glpk_error_info.is_error) { + glp_delete_prob(p); + } +} + +igraph_error_t igraph_i_glpk_check(int retval, const char* message) { + const char *code = "none"; + char message_and_code[4096]; + igraph_error_t ret; + + if (retval == 0) { + return IGRAPH_SUCCESS; + } + + /* handle errors */ +#define HANDLE_CODE(c) case c: code = #c; ret = IGRAPH_FAILURE; break; +#define HANDLE_CODE2(c) case c: code = #c; ret = IGRAPH_INTERRUPTED; break; + switch (retval) { + HANDLE_CODE(GLP_EBOUND); + HANDLE_CODE(GLP_EROOT); + HANDLE_CODE(GLP_ENOPFS); + HANDLE_CODE(GLP_ENODFS); + HANDLE_CODE(GLP_EFAIL); + HANDLE_CODE(GLP_EMIPGAP); + HANDLE_CODE(GLP_ETMLIM); + + HANDLE_CODE2(GLP_ESTOP); + + HANDLE_CODE(GLP_EBADB); + HANDLE_CODE(GLP_ESING); + HANDLE_CODE(GLP_ECOND); + HANDLE_CODE(GLP_EOBJLL); + HANDLE_CODE(GLP_EOBJUL); + HANDLE_CODE(GLP_EITLIM); + + default: + IGRAPH_ERROR("Unknown GLPK error.", IGRAPH_FAILURE); + } +#undef HANDLE_CODE +#undef HANDLE_CODE2 +#undef HANDLE_CODE3 + + snprintf(message_and_code, sizeof(message_and_code) / sizeof(message_and_code[0]), + "%s (%s)", message, code); + IGRAPH_ERROR(message_and_code, ret); +} + +#else + +int igraph_glpk_dummy(void) { + /* get rid of "ISO C requires a translation unit to contain at least one + * declaration" warning */ + return 'd' + 'u' + 'm' + 'm' + 'y'; +} + +#endif diff --git a/src/internal/glpk_support.h b/src/internal/glpk_support.h new file mode 100644 index 0000000..3f05fa7 --- /dev/null +++ b/src/internal/glpk_support.h @@ -0,0 +1,147 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_GLPK_SUPPORT_H +#define IGRAPH_GLPK_SUPPORT_H + +#include "config.h" /* HAVE_GLPK, IGRAPH_THREAD_LOCAL */ + +/* Note: only files calling the GLPK routines directly need to + include this header. +*/ + +#ifdef HAVE_GLPK + +#include "igraph_decls.h" +#include "igraph_error.h" + +#include +#include +#include + +IGRAPH_BEGIN_C_DECLS + +typedef struct { + jmp_buf jmp; /* used for bailing when there is a GLPK error */ + bool is_interrupted; /* Boolean; true if there was an interruption */ + bool is_error; /* Boolean; true if the error hook was called */ + char msg[4096]; /* GLPK error messages are collected here */ + char *msg_ptr; /* Points to the end (null terminator) of msg */ +} igraph_i_glpk_error_info_t; + +extern IGRAPH_THREAD_LOCAL igraph_i_glpk_error_info_t igraph_i_glpk_error_info; + +igraph_error_t igraph_i_glpk_check(int retval, const char* message); +void igraph_i_glpk_interruption_hook(glp_tree *tree, void *info); +IGRAPH_FUNCATTR_NORETURN void igraph_i_glpk_error_hook(void *info); +int igraph_i_glpk_terminal_hook(void *info, const char *s); +void igraph_i_glp_delete_prob(glp_prob *p); + +#define IGRAPH_GLPK_CHECK(func, message) do { \ + igraph_error_t igraph_i_ret = igraph_i_glpk_check(func, message); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != IGRAPH_SUCCESS)) { \ + return igraph_i_ret; \ + } } while (0) + +/** + * \ingroup internal + * \define IGRAPH_GLPK_SETUP + * + * Use this macro at the start of igraph functions that use GLPK routines + * directly. + * + * - IGRAPH_GLPK_SETUP() must be called in all top-level functions that + * use GLPK, before beginning to use any GLPK functions. + * + * - Do NOT call glp_term_out(OFF) as interruption support relies on + * the terminal hook being called. + * + * - This must be a macro and not a function, as jumping into a function + * that has already returned with longjmp() is not possible. + * + * This setup step is necessary in order to support interruption, as + * well as to handle fatal GLPK errors gracefully. See here for details: + * + * https://lists.gnu.org/archive/html/help-glpk/2019-10/msg00000.html + * + * Interruption support for GLPK is essential because it is practically + * impossible to predict how long it will take to solve a problem. It + * may take less than a second or it may never finish in practice. + * + * It does the following: + * + * - Initialize the data structure where we keep track of GLPK's current + * error and interruption state, \c igraph_i_glpk_error_info. + * - Set an error hook and a terminal hook for GLPK. + * - Provide a return point for the longjmp() called from the error hook. + * + * There are two interruption mechanisms we can use with GLPK. glp_intopt() + * supports a callback function which can signal a request for interruption. + * However, glp_intopt() internally calls glp_simplex(), which may again + * take a very long time. + * + * The recommended way to interrupt glp_simplex() is to check for interruption + * from the terminal hook, which is normally meant for intercepting output. + * This interruption is possible only as often as there is output, which may + * be at intervals of a few seconds in practice. + * + * Interruption is achieved by setting an error with glp_error(), which + * triggers a call to the error hook. From the error hook, we free all + * GLPK resources using glp_free_env() and do a longjmp(). + * + * The use of these mechanisms makes it unsafe to use igraph's GLPK-reliant + * functions from a process which also uses GLPK for other purposes. + * To avoid this problem, GLPK should ideally be linked to igraph statically. + */ +#define IGRAPH_GLPK_SETUP() \ + do { \ + glp_error_hook(igraph_i_glpk_error_hook, NULL); \ + glp_term_hook(igraph_i_glpk_terminal_hook, NULL); \ + igraph_i_glpk_error_info.is_interrupted = false; \ + igraph_i_glpk_error_info.is_error = false; \ + igraph_i_glpk_error_info.msg_ptr = igraph_i_glpk_error_info.msg; \ + if (setjmp(igraph_i_glpk_error_info.jmp)) { \ + if (igraph_i_glpk_error_info.is_interrupted) { \ + return IGRAPH_INTERRUPTED; \ + } else { \ + if (igraph_i_glpk_error_info.msg_ptr != igraph_i_glpk_error_info.msg) { \ + while ( *(igraph_i_glpk_error_info.msg_ptr - 1) == '\n' && \ + igraph_i_glpk_error_info.msg_ptr > igraph_i_glpk_error_info.msg ) { \ + igraph_i_glpk_error_info.msg_ptr--; \ + } \ + *igraph_i_glpk_error_info.msg_ptr = '\0'; \ + igraph_error(igraph_i_glpk_error_info.msg, IGRAPH_FILE_BASENAME, __LINE__, IGRAPH_FAILURE); \ + } else if (igraph_i_glpk_error_info.is_error) { \ + /* This branch can never be reached unless compiled with USING_R and using */ \ + /* the hack to support pre-4.57 GLPK versions. See comments in glpk_support.c. */ \ + igraph_error("Error while running GLPK solver.", IGRAPH_FILE_BASENAME, __LINE__, IGRAPH_FAILURE); \ + } \ + return IGRAPH_FAILURE; \ + } \ + } \ + } while (0) + +IGRAPH_END_C_DECLS + +#endif /* HAVE_GLPK */ + +#endif /* IGRAPH_GLPK_SUPPORT_H */ diff --git a/src/internal/gmp_internal.h b/src/internal/gmp_internal.h new file mode 100644 index 0000000..e36970b --- /dev/null +++ b/src/internal/gmp_internal.h @@ -0,0 +1,32 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_GMP_H +#define IGRAPH_GMP_H + +#include "config.h" /* INTERNAL_GMP */ + +#ifdef INTERNAL_GMP +#include "mini-gmp/mini-gmp.h" +#else +#include +#endif + +#endif diff --git a/src/internal/hacks.c b/src/internal/hacks.c new file mode 100644 index 0000000..18c7ef6 --- /dev/null +++ b/src/internal/hacks.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "internal/hacks.h" + +#include +#include + +/* These are implementations of common C functions that may be missing from some compilers. */ + +/** + * Drop-in replacement for strdup. + * Used only in compilers that do not have strdup or _strdup + */ +char *igraph_i_strdup(const char *s) { + size_t n = strlen(s) + 1; + char *result = malloc(sizeof(char) * n); + if (result) { + memcpy(result, s, n); + } + return result; +} + +/** + * Drop-in replacement for strndup. + * Used only in compilers that do not have strndup or _strndup + */ +char *igraph_i_strndup(const char *s1, size_t n) { + size_t i; + /* We need to check if the string is shorter than n characters. + * We could use strlen, but that would do more work for long s1 and small n. + * TODO: Maybe memchr would be nicer here. + */ + for (i = 0; s1[i] != '\0' && i < n; i++) {} + n = i; + char *result = malloc(sizeof(char) * (n + 1)); + if (result) { + memcpy(result, s1, n); + result[n] = '\0'; + } + return result; +} diff --git a/src/internal/hacks.h b/src/internal/hacks.h new file mode 100644 index 0000000..270c073 --- /dev/null +++ b/src/internal/hacks.h @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_HACKS_INTERNAL_H +#define IGRAPH_HACKS_INTERNAL_H + +#include "igraph_decls.h" + +#include "config.h" + +/* The CMake feature test looks for strcasecmp/strncasecmp in strings.h */ +#if defined(HAVE_STRCASECMP) || defined(HAVE_STRNCASECMP) +#include +#endif + +#include + +IGRAPH_BEGIN_C_DECLS + +#ifndef HAVE_STRDUP + #define strdup igraph_i_strdup + char* igraph_i_strdup(const char *s); +#endif + +#ifndef HAVE_STRNDUP + #define strndup igraph_i_strndup + char* igraph_i_strndup(const char *s, size_t n); +#endif + +#ifndef HAVE_STRCASECMP + #ifdef HAVE__STRICMP + #define strcasecmp _stricmp + #else + #error "igraph needs strcasecmp() or _stricmp()" + #endif +#endif + +#ifndef HAVE_STRNCASECMP + #ifdef HAVE__STRNICMP + #define strncasecmp _strnicmp + #else + #error "igraph needs strncasecmp() or _strnicmp()" + #endif +#endif + +/* Magic macro to fail the build if certain condition does not hold. See: + * https://stackoverflow.com/questions/4079243/how-can-i-use-sizeof-in-a-preprocessor-macro + */ +#define IGRAPH_STATIC_ASSERT(condition) ((void)sizeof(char[1 - 2*!(condition)])) + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/internal/lsap.c b/src/internal/lsap.c new file mode 100644 index 0000000..7494419 --- /dev/null +++ b/src/internal/lsap.c @@ -0,0 +1,689 @@ + +#include "igraph_lsap.h" + +#include "igraph_error.h" +#include "igraph_memory.h" + +/* #include */ +#include +#include /* DBL_MAX */ + +/* constants used for improving readability of code */ + +typedef enum covered_t { + COVERED = 1, + UNCOVERED = 0 +} covered_t; + +typedef enum assigned_t { + ASSIGNED = 1, + UNASSIGNED = 0 +} assigned_t; + +typedef enum marked_t { + MARKED = 1, + UNMARKED = 0 +} marked_t; + +typedef enum reduce_t { + REDUCE = 1, + NOREDUCE = 0 +} reduce_t; + +typedef struct { + igraph_int_t n; /* order of problem */ + double **C; /* cost matrix */ + double **c; /* reduced cost matrix */ + igraph_int_t *s; /* assignment */ + igraph_int_t *f; /* column i is assigned to f[i] */ + igraph_int_t na; /* number of assigned items; */ + igraph_int_t runs; /* number of iterations */ + double cost; /* minimum cost */ +} AP; + +/* public interface */ + +/* constructors and destructor */ +static igraph_error_t ap_create_problem(AP **problem, const double *t, const igraph_int_t n); +/* static AP *ap_create_problem_from_matrix(double **t, int n); */ +/* static AP *ap_read_problem(char *file); */ +static void ap_free(AP *p); + +static igraph_int_t ap_get_result(AP *p, igraph_int_t *res); +/* static int ap_costmatrix(AP *p, double **m); */ +/* static int ap_datamatrix(AP *p, double **m); */ +/* static int ap_iterations(AP *p); */ +static igraph_error_t ap_hungarian(AP *p); +/* static double ap_mincost(AP *p); */ +/* static void ap_print_solution(AP *p); */ +/* static void ap_show_data(AP *p); */ +/* static int ap_size(AP *p); */ +/* static int ap_time(AP *p); */ + +/* error reporting */ +/* static void ap_error(char *message); */ + +/* private functions */ +static void preprocess(AP *p); +static igraph_error_t preassign(AP *p); +static igraph_error_t cover(AP *p, covered_t *ri, covered_t *ci, reduce_t *res); +static void reduce(AP *p, const covered_t *ri, const covered_t *ci); + +/* Error message used on memory allocation failure. */ +static const char *memerr = "Insufficient memory for LSAP."; + +igraph_error_t ap_hungarian(AP *p) { + covered_t *ri; /* covered rows */ + covered_t *ci; /* covered columns */ + + const igraph_int_t n = p->n; /* size of problem */ + p->runs = 0; + + /* allocate memory */ + /* Note: p is already on the finally stack here. */ + p->s = IGRAPH_CALLOC(1 + n, igraph_int_t); + IGRAPH_CHECK_OOM(p->s, memerr); + + p->f = IGRAPH_CALLOC(1 + n, igraph_int_t); + IGRAPH_CHECK_OOM(p->f, memerr); + + ri = IGRAPH_CALLOC(1 + n, covered_t); + IGRAPH_CHECK_OOM(ri, memerr); + IGRAPH_FINALLY(igraph_free, ri); + + ci = IGRAPH_CALLOC(1 + n, covered_t); + IGRAPH_CHECK_OOM(ci, memerr); + IGRAPH_FINALLY(igraph_free, ci); + + preprocess(p); + IGRAPH_CHECK(preassign(p)); + + while (p->na < n) { + reduce_t res; + IGRAPH_CHECK(cover(p, ri, ci, &res)); + if (REDUCE == res) { + reduce(p, ri, ci); + } + ++p->runs; + } + + /* check if assignment is a permutation of (1..n) */ + for (igraph_int_t i = 1; i <= n; i++) { + igraph_int_t ok = 0; + for (igraph_int_t j = 1; j <= n; j++) + if (p->s[j] == i) { + ++ok; + } + if (ok != 1) + IGRAPH_ERROR("ap_hungarian: error in assignment, is not a permutation", + IGRAPH_EINVAL); + } + + /* calculate cost of assignment */ + p->cost = 0; + for (igraph_int_t i = 1; i <= n; i++) { + p->cost += p->C[i][p->s[i]]; + } + + /* reset result back to base-0 indexing */ + for (igraph_int_t i = 1; i <= n; i++) { + p->s[i - 1] = p->s[i] - 1; + } + + /* free memory */ + + IGRAPH_FREE(ri); + IGRAPH_FREE(ci); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/* abbreviated interface */ +igraph_int_t ap_get_result(AP *p, igraph_int_t *res) { + for (igraph_int_t i = 0; i < p->n; i++) { + res[i] = p->s[i]; + } + + return p->n; +} + + +/*******************************************************************/ +/* constructors */ +/* read data from file */ +/*******************************************************************/ + +#if 0 +AP *ap_read_problem(char *file) { + FILE *f; + int i, j, c; + int m, n; + double x; + double **t; + int nrow, ncol; + AP *p; + + f = fopen(file, "r"); + if (f == NULL) { + return NULL; + } + + t = (double **)malloc(sizeof(double*)); + + m = 0; + n = 0; + + nrow = 0; + ncol = 0; + + while (EOF != (i = fscanf(f, "%lf", &x))) { + if (i == 1) { + if (n == 0) { + t = (double **) realloc(t, (m + 1) * sizeof(double *)); + t[m] = (double *) malloc(sizeof(double)); + } else { + t[m] = (double *) realloc(t[m], (n + 1) * sizeof(double)); + } + + t[m][n++] = x; + + ncol = (ncol < n) ? n : ncol; + c = fgetc(f); + if (c == '\n') { + n = 0; + ++m; + nrow = (nrow < m) ? m : nrow; + } + } + } + fclose(f); + + /* prepare data */ + + if (nrow != ncol) { + /* + fprintf(stderr,"ap_read_problem: problem not quadratic\nrows =%d, cols = %d\n",nrow,ncol); + */ + IGRAPH_WARNINGF("ap_read_problem: problem not quadratic; rows = %d, cols = %d.", nrow, ncol); + return NULL; + } + + p = (AP*) malloc(sizeof(AP)); + p->n = ncol; + + p->C = (double **) malloc((1 + nrow) * sizeof(double *)); + p->c = (double **) malloc((1 + nrow) * sizeof(double *)); + if (p->C == NULL || p->c == NULL) { + return NULL; + } + + for (i = 1; i <= nrow; i++) { + p->C[i] = (double *) calloc(ncol + 1, sizeof(double)); + p->c[i] = (double *) calloc(ncol + 1, sizeof(double)); + if (p->C[i] == NULL || p->c[i] == NULL) { + return NULL; + } + } + + for (i = 1; i <= nrow; i++) + for ( j = 1; j <= ncol; j++) { + p->C[i][j] = t[i - 1][j - 1]; + p->c[i][j] = t[i - 1][j - 1]; + } + + for (i = 0; i < nrow; i++) { + free(t[i]); + } + free(t); + + p->cost = 0; + p->s = NULL; + p->f = NULL; + return p; +} +#endif + +#if 0 +AP *ap_create_problem_from_matrix(double **t, int n) { + int i, j; + AP *p; + + p = (AP*) malloc(sizeof(AP)); + if (p == NULL) { + return NULL; + } + + p->n = n; + + p->C = (double **) malloc((n + 1) * sizeof(double *)); + p->c = (double **) malloc((n + 1) * sizeof(double *)); + if (p->C == NULL || p->c == NULL) { + return NULL; + } + + for (i = 1; i <= n; i++) { + p->C[i] = (double *) calloc(n + 1, sizeof(double)); + p->c[i] = (double *) calloc(n + 1, sizeof(double)); + if (p->C[i] == NULL || p->c[i] == NULL) { + return NULL; + } + } + + + for (i = 1; i <= n; i++) + for ( j = 1; j <= n; j++) { + p->C[i][j] = t[i - 1][j - 1]; + p->c[i][j] = t[i - 1][j - 1]; + } + p->cost = 0; + p->s = NULL; + p->f = NULL; + return p; +} +#endif + +/* read data from vector */ +igraph_error_t ap_create_problem(AP **problem, const double *t, const igraph_int_t n) { + *problem = IGRAPH_CALLOC(1, AP); + IGRAPH_CHECK_OOM(*problem, memerr); + IGRAPH_FINALLY(ap_free, *problem); + + AP *p = *problem; + + p->n = n; + + p->C = IGRAPH_CALLOC(n+1, double *); + IGRAPH_CHECK_OOM(p->C, memerr); + + p->c = IGRAPH_CALLOC(n+1, double *); + IGRAPH_CHECK_OOM(p->c, memerr); + + for (igraph_int_t i = 1; i <= n; i++) { + p->C[i] = IGRAPH_CALLOC(n+1, double); + IGRAPH_CHECK_OOM(p->C[i], memerr); + p->c[i] = IGRAPH_CALLOC(n+1, double); + IGRAPH_CHECK_OOM(p->c[i], memerr); + } + + for (igraph_int_t i = 1; i <= n; i++) { + for (igraph_int_t j = 1; j <= n; j++) { + p->C[i][j] = t[n * (j - 1) + i - 1]; + p->c[i][j] = t[n * (j - 1) + i - 1]; + } + } + p->cost = 0; + p->s = NULL; + p->f = NULL; + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* destructor */ +void ap_free(AP *p) { + IGRAPH_FREE(p->s); + IGRAPH_FREE(p->f); + + for (igraph_int_t i = 1; i <= p->n; i++) { + IGRAPH_FREE(p->C[i]); + IGRAPH_FREE(p->c[i]); + } + + IGRAPH_FREE(p->C); + IGRAPH_FREE(p->c); + IGRAPH_FREE(p); +} + +/* set + get functions */ + +/* +void ap_show_data(AP *p) +{ + igraph_int_t i, j; + + for(i = 1; i <= p->n; i++){ + for(j = 1; j <= p->n; j++) + printf("%6.2f ", p->c[i][j]); + printf("\n"); + } +} + +double ap_mincost(AP *p) { + if (p->s == NULL) { + ap_hungarian(p); + } + + return p->cost; +} + +igraph_int_t ap_size(AP *p) { + return p->n; +} + +int ap_time(AP *p) { + return (int) p->rtime; +} + +int ap_iterations(AP *p) { + return p->runs; +} + +void ap_print_solution(AP *p) +{ + igraph_int_t i; + + printf("%d itertations, %d secs.\n",p->runs, (int)p->rtime); + printf("Min Cost: %10.4f\n",p->cost); + + for(i = 0; i < p->n; i++) + printf("%4d",p->s[i]); + printf("\n"); +} + +int ap_costmatrix(AP *p, double **m) { + igraph_int_t i, j; + + for (i = 0; i < p->n; i++) + for (j = 0; j < p->n; j++) { + m[i][j] = p->C[i + 1][j + 1]; + } + + return p->n; +} + +int ap_datamatrix(AP *p, double **m) { + igraph_int_t i, j; + + for (i = 0; i < p->n; i++) + for (j = 0; j < p->n; j++) { + m[i][j] = p->c[i + 1][j + 1]; + } + + return p->n; +} +*/ + +/* error reporting */ + +/* +void ap_error(char *message) +{ + fprintf(stderr,"%s\n",message); + exit(1); +} +*/ + +/*************************************************************/ +/* these functions are used internally */ +/* by ap_hungarian */ +/*************************************************************/ + +igraph_error_t cover(AP *p, covered_t *ri, covered_t *ci, reduce_t *res) { + marked_t *mr; + igraph_int_t r; + + const igraph_int_t n = p->n; + + mr = IGRAPH_CALLOC(1 + p->n, marked_t); + IGRAPH_CHECK_OOM(mr, memerr); + + /* reset cover indices */ + for (igraph_int_t i = 1; i <= n; i++) { + if (p->s[i] == UNASSIGNED) { + ri[i] = UNCOVERED; + mr[i] = MARKED; + } else { + ri[i] = COVERED; + } + ci[i] = UNCOVERED; + } + + while (true) { + /* find marked row */ + r = 0; + for (igraph_int_t i = 1; i <= n; i++) + if (mr[i] == MARKED) { + r = i; + break; + } + + if (r == 0) { + break; + } + for (igraph_int_t i = 1; i <= n; i++) + if (p->c[r][i] == 0 && ci[i] == UNCOVERED) { + if (p->f[i]) { + ri[p->f[i]] = UNCOVERED; + mr[p->f[i]] = MARKED; + ci[i] = COVERED; + } else { + if (p->s[r] == UNASSIGNED) { + ++p->na; + } + + p->f[p->s[r]] = 0; + p->f[i] = r; + p->s[r] = i; + + IGRAPH_FREE(mr); + *res = NOREDUCE; + return IGRAPH_SUCCESS; + } + } + mr[r] = UNMARKED; + } + + IGRAPH_FREE(mr); + *res = REDUCE; + return IGRAPH_SUCCESS; +} + +void reduce(AP *p, const covered_t *ri, const covered_t *ci) { + double min; + const igraph_int_t n = p->n; + + /* find minimum in uncovered c-matrix */ + min = DBL_MAX; + for (igraph_int_t i = 1; i <= n; i++) + for (igraph_int_t j = 1; j <= n; j++) + if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) { + if (p->c[i][j] < min) { + min = p->c[i][j]; + } + } + + /* subtract min from each uncovered element and add it to each element */ + /* which is covered twice */ + for (igraph_int_t i = 1; i <= n; i++) + for (igraph_int_t j = 1; j <= n; j++) { + if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) { + p->c[i][j] -= min; + } + if (ri[i] == COVERED && ci[j] == COVERED) { + p->c[i][j] += min; + } + } +} + +igraph_error_t preassign(AP *p) { + igraph_int_t min, r, c, n, count; + assigned_t *ri, *ci; + igraph_int_t *rz, *cz; + + n = p->n; + p->na = 0; + + /* row and column markers */ + ri = IGRAPH_CALLOC(1 + n, assigned_t); + IGRAPH_CHECK_OOM(ri, memerr); + IGRAPH_FINALLY(igraph_free, ri); + + ci = IGRAPH_CALLOC(1 + n, assigned_t); + IGRAPH_CHECK_OOM(ci, memerr); + IGRAPH_FINALLY(igraph_free, ci); + + /* row and column counts of zeroes */ + rz = IGRAPH_CALLOC(1 + n, igraph_int_t); + IGRAPH_CHECK_OOM(rz, memerr); + IGRAPH_FINALLY(igraph_free, rz); + + cz = IGRAPH_CALLOC(1 + n, igraph_int_t); + IGRAPH_CHECK_OOM(cz, memerr); + IGRAPH_FINALLY(igraph_free, cz); + + for (igraph_int_t i = 1; i <= n; i++) { + count = 0; + for (igraph_int_t j = 1; j <= n; j++) + if (p->c[i][j] == 0) { + ++count; + } + rz[i] = count; + } + + for (igraph_int_t i = 1; i <= n; i++) { + count = 0; + for (igraph_int_t j = 1; j <= n; j++) + if (p->c[j][i] == 0) { + ++count; + } + cz[i] = count; + } + + while (true) { + /* find unassigned row with least number of zeroes > 0 */ + min = IGRAPH_INTEGER_MAX; + r = 0; + for (igraph_int_t i = 1; i <= n; i++) + if (rz[i] > 0 && rz[i] < min && ri[i] == UNASSIGNED) { + min = rz[i]; + r = i; + } + /* check if we are done */ + if (r == 0) { + break; + } + + /* find unassigned column in row r with least number of zeroes */ + c = 0; + min = IGRAPH_INTEGER_MAX; + for (igraph_int_t i = 1; i <= n; i++) + if (p->c[r][i] == 0 && cz[i] < min && ci[i] == UNASSIGNED) { + min = cz[i]; + c = i; + } + + if (c) { + ++p->na; + p->s[r] = c; + p->f[c] = r; + + ri[r] = ASSIGNED; + ci[c] = ASSIGNED; + + /* adjust zero counts */ + cz[c] = 0; + for (igraph_int_t i = 1; i <= n; i++) + if (p->c[i][c] == 0) { + --rz[i]; + } + } + } + + /* free memory */ + IGRAPH_FREE(ri); + IGRAPH_FREE(ci); + IGRAPH_FREE(rz); + IGRAPH_FREE(cz); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +void preprocess(AP *p) { + double min; + const igraph_int_t n = p->n; + + /* subtract column minima in each row */ + for (igraph_int_t i = 1; i <= n; i++) { + min = p->c[i][1]; + for (igraph_int_t j = 2; j <= n; j++) + if (p->c[i][j] < min) { + min = p->c[i][j]; + } + for (igraph_int_t j = 1; j <= n; j++) { + p->c[i][j] -= min; + } + } + + /* subtract row minima in each column */ + for (igraph_int_t i = 1; i <= n; i++) { + min = p->c[1][i]; + for (igraph_int_t j = 2; j <= n; j++) + if (p->c[j][i] < min) { + min = p->c[j][i]; + } + for (igraph_int_t j = 1; j <= n; j++) { + p->c[j][i] -= min; + } + } +} + +/** + * \function igraph_solve_lsap + * \brief Solve a balanced linear assignment problem. + * + * This functions solves a linear assignment problem using the Hungarian + * method. A number of tasks, an equal number of agents, and the cost + * of each agent to perform the tasks is given. This function then + * assigns one task to each agent in such a way that the total cost is + * minimized. + * + * + * If the cost should be maximized instead of minimized, the cost matrix + * should be negated. + * + * + * To solve an unbalanced assignment problem, where the number of agents + * is greater than the number of tasks, extra tasks with zero costs + * should be added. + * + * \param c The assignment problem, where the number of rows is the + * number of agents, the number of columns is the number of + * tasks, and each element is the cost of an agent to perform + * the task. + * \param n The number of rows and columns of \p c. + * \param p An initialized vector which will store the result. The nth + * entry gives the task the nth agent is assigned to minimize + * the total cost. + * \return Error code. + * + * Time complexity: O(n^3), where n is the number of agents. + */ +igraph_error_t igraph_solve_lsap(const igraph_matrix_t *c, igraph_int_t n, + igraph_vector_int_t *p) { + AP *ap; + + if (n != igraph_matrix_nrow(c)) { + IGRAPH_ERRORF("n (%" IGRAPH_PRId ") " + "not equal to number of agents (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + n, igraph_matrix_nrow(c)); + } + if (n != igraph_matrix_ncol(c)) { + IGRAPH_ERRORF("n (%" IGRAPH_PRId ") " + "not equal to number of tasks (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + n, igraph_matrix_ncol(c)); + } + IGRAPH_CHECK(igraph_vector_int_resize(p, n)); + igraph_vector_int_null(p); + + IGRAPH_CHECK(ap_create_problem(&ap, &MATRIX(*c, 0, 0), n)); + IGRAPH_FINALLY(ap_free, ap); + IGRAPH_CHECK(ap_hungarian(ap)); + ap_get_result(ap, VECTOR(*p)); + ap_free(ap); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/internal/qsort.c b/src/internal/qsort.c new file mode 100644 index 0000000..191b9f2 --- /dev/null +++ b/src/internal/qsort.c @@ -0,0 +1,267 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This file originates from the following URL: + * + * https://cgit.freebsd.org/src/commit/lib/libc/stdlib/qsort.c?id=7f8f79a5c444a565a32b0c6578b07f8d496f6c49 + * + * Create a diff against the revision given above to see what we have changed + * to facilitate inclusion into igraph + */ + +#include "igraph_qsort.h" +#include "igraph_error.h" + +#ifdef _MSC_VER + /* MSVC does not have inline when compiling C source files */ + #define inline __inline + #define __unused +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1927 + /* MSVC does not understand restrict before version 19.27 */ + #define restrict __restrict +#endif + +#ifndef __unused + #define __unused __attribute__ ((unused)) +#endif + +#include + +#if defined(I_AM_QSORT_R) +typedef int cmp_t(void *, const void *, const void *); +#elif defined(I_AM_QSORT_S) +typedef int cmp_t(const void *, const void *, void *); +#else +typedef int cmp_t(const void *, const void *); +#endif +static inline char *med3(char *, char *, char *, cmp_t *, void *); + +#define MIN(a, b) ((a) < (b) ? a : b) + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ + +static inline void +swapfunc(char * restrict a, char * restrict b, size_t es) +{ + char t; + + do { + t = *a; + *a++ = *b; + *b++ = t; + } while (--es > 0); +} + +#define vecswap(a, b, n) \ + if ((n) > 0) swapfunc(a, b, n) + +#if defined(I_AM_QSORT_R) +#define CMP(t, x, y) (cmp((t), (x), (y))) +#elif defined(I_AM_QSORT_S) +#define CMP(t, x, y) (cmp((x), (y), (t))) +#else +#define CMP(t, x, y) (cmp((x), (y))) +#endif + +static inline char * +med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk +#if !defined(I_AM_QSORT_R) && !defined(I_AM_QSORT_S) +__unused +#endif +) +{ + return CMP(thunk, a, b) < 0 ? + (CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a )) + :(CMP(thunk, b, c) > 0 ? b : (CMP(thunk, a, c) < 0 ? a : c )); +} + +/* + * The actual qsort() implementation is static to avoid preemptible calls when + * recursing. Also give them different names for improved debugging. + */ +#if defined(I_AM_QSORT_R) +#define local_qsort local_qsort_r +#elif defined(I_AM_QSORT_S) +#define local_qsort local_qsort_s +#endif +static void +local_qsort(void *a, size_t n, size_t es, cmp_t *cmp, void *thunk) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + size_t d1, d2; + int cmp_result; + int swap_cnt; + + /* if there are less than 2 elements, then sorting is not needed */ + if (IGRAPH_UNLIKELY(n < 2)) + return; +loop: + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swapfunc(pl, pl - es, es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + size_t d = (n / 8) * es; + + pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk); + pm = med3(pm - d, pm, pm + d, cmp, thunk); + pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk); + } + pm = med3(pl, pm, pn, cmp, thunk); + } + swapfunc(a, pm, es); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (cmp_result = CMP(thunk, pb, a)) <= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swapfunc(pa, pb, es); + pa += es; + } + pb += es; + } + while (pb <= pc && (cmp_result = CMP(thunk, pc, a)) >= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swapfunc(pc, pd, es); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swapfunc(pb, pc, es); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swapfunc(pl, pl - es, es); + return; + } + + pn = (char *)a + n * es; + d1 = MIN(pa - (char *)a, pb - pa); + vecswap(a, pb - d1, d1); + /* + * Cast es to preserve signedness of right-hand side of MIN() + * expression, to avoid sign ambiguity in the implied comparison. es + * is safely within [0, SSIZE_MAX]. + */ + d1 = MIN(pd - pc, pn - pd - (ptrdiff_t)es); + vecswap(pb, pn - d1, d1); + + d1 = pb - pa; + d2 = pd - pc; + if (d1 <= d2) { + /* Recurse on left partition, then iterate on right partition */ + if (d1 > es) { + local_qsort(a, d1 / es, es, cmp, thunk); + } + if (d2 > es) { + /* Iterate rather than recurse to save stack space */ + /* qsort(pn - d2, d2 / es, es, cmp); */ + a = pn - d2; + n = d2 / es; + goto loop; + } + } else { + /* Recurse on right partition, then iterate on left partition */ + if (d2 > es) { + local_qsort(pn - d2, d2 / es, es, cmp, thunk); + } + if (d1 > es) { + /* Iterate rather than recurse to save stack space */ + /* qsort(a, d1 / es, es, cmp); */ + n = d1 / es; + goto loop; + } + } +} + +#if defined(I_AM_QSORT_R) +void +igraph_qsort_r(void *a, size_t n, size_t es, void *thunk, cmp_t *cmp) +{ + local_qsort_r(a, n, es, cmp, thunk); +} +#elif defined(I_AM_QSORT_S) +errno_t +igraph_qsort_s(void *a, rsize_t n, rsize_t es, cmp_t *cmp, void *thunk) +{ + if (n > RSIZE_MAX) { + __throw_constraint_handler_s("qsort_s : n > RSIZE_MAX", EINVAL); + return (EINVAL); + } else if (es > RSIZE_MAX) { + __throw_constraint_handler_s("qsort_s : es > RSIZE_MAX", + EINVAL); + return (EINVAL); + } else if (n != 0) { + if (a == NULL) { + __throw_constraint_handler_s("qsort_s : a == NULL", + EINVAL); + return (EINVAL); + } else if (cmp == NULL) { + __throw_constraint_handler_s("qsort_s : cmp == NULL", + EINVAL); + return (EINVAL); + } + } + + local_qsort_s(a, n, es, cmp, thunk); + return (0); +} +#else +void +igraph_qsort(void *a, size_t n, size_t es, cmp_t *cmp) +{ + local_qsort(a, n, es, cmp, NULL); +} +#endif diff --git a/src/internal/qsort_r.c b/src/internal/qsort_r.c new file mode 100644 index 0000000..f7c0e54 --- /dev/null +++ b/src/internal/qsort_r.c @@ -0,0 +1,8 @@ +/* + * This file is in the public domain. Originally written by Garrett + * A. Wollman. + * + * $FreeBSD: src/lib/libc/stdlib/qsort_r.c,v 1.1 2002/09/10 02:04:49 wollman Exp $ + */ +#define I_AM_QSORT_R +#include "qsort.c" diff --git a/src/internal/utils.c b/src/internal/utils.c new file mode 100644 index 0000000..058bd12 --- /dev/null +++ b/src/internal/utils.c @@ -0,0 +1,224 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_interface.h" +#include "igraph_qsort.h" +#include "igraph_random.h" + +#include "internal/utils.h" + +/** + * \function igraph_i_matrix_subset_vertices + * \brief Subsets a matrix whose rows/columns correspond to graph vertices. + * + * This is a convenience function to subset a matrix computed from a graph. + * It takes a matrix whose rows and columns correspond to the vertices + * of a graph, and subsets it in-place to retain only some of the vertices. + * + * \param m A square matrix with the same number of rows/columns as the vertex + * count of \p graph. It will be modified in-place, deleting rows \em not present + * in \p from and columns \em not present in \p to. + * \param graph The corresponding graph. m[u,v] is assumed to contain + * a value associated with vertices \c u and \c v of \p graph, e.g. the graph + * distance between them, their similarity, etc. + * \param from Vertex set, these rows of the matrix will be retained. + * \param to Vertex set, these columns of the matrix will be retained. + * \return Error code. + * + * Time complexity: + * O(1) when taking all vertices, + * O(|from|*|to|) otherwise where |from| and |to| denote the size + * of the source and target vertex sets. + */ +igraph_error_t igraph_i_matrix_subset_vertices( + igraph_matrix_t *m, + const igraph_t *graph, + igraph_vs_t from, + igraph_vs_t to) { + + /* Assertion: the size of 'm' agrees with 'graph': */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t ncol = igraph_matrix_ncol(m); + igraph_int_t nrow = igraph_matrix_nrow(m); + + IGRAPH_ASSERT(nrow == no_of_nodes && nrow == ncol); + + /* When taking all vertices, nothing needs to be done: */ + + if (igraph_vs_is_all(&from) && igraph_vs_is_all(&to)) { + return IGRAPH_SUCCESS; + } + + /* Otherwise, allocate a temporary matrix to copy the data into: */ + + igraph_vit_t fromvit, tovit; + igraph_matrix_t tmp; + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + + IGRAPH_MATRIX_INIT_FINALLY(&tmp, IGRAPH_VIT_SIZE(fromvit), IGRAPH_VIT_SIZE(tovit)); + + for (igraph_int_t j=0; ! IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit), j++) { + igraph_int_t i; + for (IGRAPH_VIT_RESET(fromvit), i=0; ! IGRAPH_VIT_END(fromvit); IGRAPH_VIT_NEXT(fromvit), i++) { + MATRIX(tmp, i, j) = MATRIX(*m, IGRAPH_VIT_GET(fromvit), IGRAPH_VIT_GET(tovit)); + } + } + + /* This is O(1) time */ + igraph_matrix_swap(m, &tmp); + + igraph_matrix_destroy(&tmp); + igraph_vit_destroy(&tovit); + igraph_vit_destroy(&fromvit); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Lexicographic edge comparator, used with igraph_qsort() in igraph_i_simplify_edge_list() */ +static int edge_comparator(const void *a, const void *b) { + igraph_int_t *A = (igraph_int_t *) a; + igraph_int_t *B = (igraph_int_t *) b; + + if (A[0] < B[0]) { + return -1; + } + if (A[0] > B[0]) { + return 1; + } + + /* first are equal */ + if (A[1] < B[1]) { + return -1; + } + if (A[1] > B[1]) { + return 1; + } + + /* second are equal, so the edges must be equal */ + return 0; +} + +/** + * Simplify an edge list in-place. Edges may be reordered by this function. + * + * TODO: Refactor this to take the number of vertices as input and use linear-time radix sort. + * + * \param edges The edge list vector, as a consecutive list of pairs. It will be modified in-place. + * \param self_loops Set to \c false to remove self-loops. + * \param multi_edges Set to \c false to eliminate multi-edges. + * \param directed Whether to treat edges as directed. + */ +void igraph_i_simplify_edge_list( + igraph_vector_int_t *edges, + igraph_bool_t remove_loops, igraph_bool_t remove_multiple, + igraph_bool_t directed) { + + igraph_int_t size = igraph_vector_int_size(edges); + + if (size == 0 || (!remove_loops && !remove_multiple)) { + return; + } + + /* Canonicalize undirected edges. */ + if (!directed) { + for (igraph_int_t i = 0; i < size; i += 2) { + if (VECTOR(*edges)[i] > VECTOR(*edges)[i + 1]) { + igraph_int_t temp = VECTOR(*edges)[i]; + VECTOR(*edges)[i] = VECTOR(*edges)[i + 1]; + VECTOR(*edges)[i + 1] = temp; + } + } + } + + /* Sort edge list. Not needed if multi edges are allowed. */ + if (remove_multiple) { + igraph_qsort(VECTOR(*edges), size / 2, 2 * sizeof(igraph_int_t), + &edge_comparator); + } + + /* Remove self-loops and duplicate edges from the sorted edge list, in place. + * i points to the current edge being examined, j points to the last edge copied. */ + + igraph_int_t j = -2; + for (igraph_int_t i = 0 ; i < size; i += 2) { + if (remove_multiple && + /* If we've already copied some edges, */ + j != -2 && + /* and the current edge is equal to the previously copied one: */ + VECTOR(*edges)[i] == VECTOR(*edges)[j] && + VECTOR(*edges)[i + 1] == VECTOR(*edges)[j + 1]) + { + /* This edge is a duplicate, and should be skipped */ + continue; + } + + if (remove_loops && + VECTOR(*edges)[i] == VECTOR(*edges)[i + 1]) + { + /* This edge is a self loop, and should be skipped */ + continue; + } + + j += 2; + VECTOR(*edges)[j] = VECTOR(*edges)[i]; + VECTOR(*edges)[j + 1] = VECTOR(*edges)[i + 1]; + } + + igraph_vector_int_resize(edges, j + 2); /* shrinks */ +} + +/** + * Shuffle a list of pairs, such as an edge list. + * + * \param pairs Vector of pairs, will be modified in-place. + * \return Error code, when the input is not of even length. + */ +igraph_error_t igraph_i_vector_int_shuffle_pairs(igraph_vector_int_t *pairs) { + igraph_int_t pair_count = igraph_vector_int_size(pairs); + + if (pair_count % 2 == 1) { + IGRAPH_ERROR("A vector of pairs must have an even length.", IGRAPH_EINVAL); + } + + pair_count /= 2; + while (pair_count > 1) { + igraph_int_t dummy, k; + + pair_count--; + k = RNG_INTEGER(0, pair_count); + + dummy = VECTOR(*pairs)[pair_count * 2]; + VECTOR(*pairs)[pair_count * 2] = VECTOR(*pairs)[k * 2]; + + VECTOR(*pairs)[k * 2] = dummy; + dummy = VECTOR(*pairs)[pair_count * 2 + 1]; + + VECTOR(*pairs)[pair_count * 2] = VECTOR(*pairs)[k * 2 + 1]; + VECTOR(*pairs)[k * 2 + 1] = dummy; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/internal/utils.h b/src/internal/utils.h new file mode 100644 index 0000000..f3be6b2 --- /dev/null +++ b/src/internal/utils.h @@ -0,0 +1,39 @@ +/* + igraph library. + Copyright (C) 2008-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_INTERNAL_UTILS_H +#define IGRAPH_INTERNAL_UTILS_H + +#include "igraph_datatype.h" +#include "igraph_iterators.h" +#include "igraph_matrix.h" + +igraph_error_t igraph_i_matrix_subset_vertices( + igraph_matrix_t *m, + const igraph_t *graph, + igraph_vs_t from, + igraph_vs_t to); + +void igraph_i_simplify_edge_list( + igraph_vector_int_t *edges, + igraph_bool_t self_loops, igraph_bool_t multi_edges, + igraph_bool_t directed); + +igraph_error_t igraph_i_vector_int_shuffle_pairs(igraph_vector_int_t *pairs); + +#endif /* IGRAPH_INTERNAL_UTILS_H */ diff --git a/src/io/dimacs.c b/src/io/dimacs.c new file mode 100644 index 0000000..3154351 --- /dev/null +++ b/src/io/dimacs.c @@ -0,0 +1,363 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" + +#include "core/interruption.h" + +#include + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +/* Limit maximum vertex count when using a fuzzer, to avoid out-of-memory failure. */ +#define IGRAPH_DIMACS_MAX_VERTEX_COUNT (1 << 20) +#define IGRAPH_DIMACS_MAX_EDGE_COUNT (1 << 20) +#else +#define IGRAPH_DIMACS_MAX_VERTEX_COUNT INT32_MAX +#define IGRAPH_DIMACS_MAX_EDGE_COUNT INT32_MAX +#endif + +#define EXPECT(actual, expected) \ + do { \ + if ((actual) != (expected)) { \ + IGRAPH_ERROR("Reading DIMACS flow problem file failed.", IGRAPH_PARSEERROR); \ + } \ + } while (0) + +#define CHECK_VID(vid) \ + do { \ + if (vid > IGRAPH_DIMACS_MAX_VERTEX_COUNT) { \ + IGRAPH_ERRORF("Vertex ID %" IGRAPH_PRId " too large in DIMACS file.", IGRAPH_PARSEERROR, vid); \ + } \ + } while(0) + +/** + * \function igraph_read_graph_dimacs_flow + * \brief Read a graph in DIMACS format. + * + * This function reads the DIMACS file format, more specifically the + * version for network flow problems, see the files at + * http://archive.dimacs.rutgers.edu/pub/netflow/general-info/ + * + * + * This is a line-oriented text file (ASCII) format. The first + * character of each line defines the type of the line. If the first + * character is \c c the line is a comment line and it is + * ignored. There is one problem line (\c p in the file), it + * must appear before any node and arc descriptor lines. The problem + * line has three fields separated by spaces: the problem type + * (\c max or \c edge), the number of vertices, + * and number of edges in the graph. In MAX problems, + * exactly two node identification lines are expected + * (\c n), one for the source, and one for the target vertex. + * These have two fields: the ID of the vertex and the type of the + * vertex, either \c s ( = source) or \c t ( = target). + * Arc lines start with \c a and have three fields: the source vertex, + * the target vertex and the edge capacity. In EDGE problems, + * there may be a node line (\c n) for each node. It specifies the + * node index and an integer node label. Nodes for which no explicit + * label was specified will use their index as label. In EDGE problems, + * each edge is specified as an edge line (\c e). + * + * + * Within DIMACS files, vertex IDs are numbered from 1. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream The file to read from. + * \param problem If not \c NULL, it will contain the problem type. + * \param label If not \c NULL, node labels will be stored here for \c edge + * problems. Ignored for \c max problems. + * \param source Pointer to an integer, the ID of the source node will + * be stored here. (The igraph vertex ID, which is one less than + * the actual number in the file.) It is ignored if \c NULL. + * \param target Pointer to an integer, the (igraph) ID of the target + * node will be stored here. It is ignored if \c NULL. + * \param capacity Pointer to an initialized vector, the capacity of + * the edges will be stored here if not \ NULL. + * \param directed Boolean, whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|+c), the number of vertices plus the + * number of edges, plus the size of the file in characters. + * + * \sa \ref igraph_write_graph_dimacs_flow() + */ +igraph_error_t igraph_read_graph_dimacs_flow( + igraph_t *graph, FILE *instream, + igraph_strvector_t *problem, + igraph_vector_int_t *label, + igraph_int_t *source, + igraph_int_t *target, + igraph_vector_t *capacity, + igraph_bool_t directed) { + + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = -1; + igraph_int_t no_of_edges = -1; + igraph_int_t tsource = -1; + igraph_int_t ttarget = -1; + char prob[21]; + enum { + PROBLEM_NONE, + PROBLEM_EDGE, + PROBLEM_MAX + } problem_type = PROBLEM_NONE; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + if (capacity) { + igraph_vector_clear(capacity); + } + + while (!feof(instream)) { + int read; + char str[2]; + + IGRAPH_ALLOW_INTERRUPTION(); + + read = fscanf(instream, "%2c", str); + if (feof(instream)) { + break; + } + EXPECT(read, 1); + switch (str[0]) { + igraph_int_t tmp, tmp2; + igraph_int_t from, to; + igraph_real_t cap; + + case 'c': + /* comment */ + break; + + case 'p': + if (no_of_nodes != -1) { + IGRAPH_ERROR("Reading DIMACS file failed, double 'p' line.", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%20s %" IGRAPH_PRId " %" IGRAPH_PRId "", prob, + &no_of_nodes, &no_of_edges); + EXPECT(read, 3); + if (no_of_nodes > IGRAPH_DIMACS_MAX_VERTEX_COUNT) { + IGRAPH_ERROR("Vertex count too large in DIMACS file.", IGRAPH_PARSEERROR); + } + if (no_of_nodes < 0) { + IGRAPH_ERROR("Invalid (negative) vertex count in DIMACS file.", IGRAPH_PARSEERROR); + } + if (no_of_edges > IGRAPH_DIMACS_MAX_EDGE_COUNT) { + IGRAPH_ERROR("Edge count too large in DIMACS file.", IGRAPH_PARSEERROR); + } + if (no_of_edges < 0) { + IGRAPH_ERROR("Invalid (negative) edge count in DIMACS file.", IGRAPH_PARSEERROR); + } + if (!strcmp(prob, "edge")) { + /* edge list */ + problem_type = PROBLEM_EDGE; + if (label) { + IGRAPH_CHECK(igraph_vector_int_range(label, 1, no_of_nodes+1)); + } + } else if (!strcmp(prob, "max")) { + /* maximum flow problem */ + problem_type = PROBLEM_MAX; + if (capacity) { + IGRAPH_CHECK(igraph_vector_reserve(capacity, no_of_edges)); + } + } else { + IGRAPH_ERROR("Unknown problem type, should be 'edge' or 'max'.", + IGRAPH_PARSEERROR); + } + if (problem) { + igraph_strvector_clear(problem); + IGRAPH_CHECK(igraph_strvector_push_back(problem, prob)); + } + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + break; + + case 'n': + /* for MAX this is either the source or target vertex, + for EDGE this is a vertex label */ + if (problem_type == PROBLEM_MAX) { + str[0] = 'x'; + read = fscanf(instream, "%" IGRAPH_PRId " %1s", &tmp, str); + EXPECT(read, 2); + if (str[0] == 's') { + if (tsource != -1) { + IGRAPH_ERROR("Reading DIMACS file: multiple source vertex line.", + IGRAPH_PARSEERROR); + } else { + tsource = tmp; + } + } else if (str[0] == 't') { + if (ttarget != -1) { + IGRAPH_ERROR("Reading DIMACS file: multiple target vertex line.", + IGRAPH_PARSEERROR); + } else { + ttarget = tmp; + } + } else { + IGRAPH_ERROR("Invalid node descriptor line in DIMACS file.", + IGRAPH_PARSEERROR); + } + } else { /* PROBLEM_EDGE */ + read = fscanf(instream, "%" IGRAPH_PRId " %" IGRAPH_PRId "", &tmp, &tmp2); + EXPECT(read, 1); + if (label) { + if (tmp < 0 || tmp >= no_of_nodes) { + IGRAPH_ERRORF("Invalid node index %" IGRAPH_PRId " in DIMACS file. " + "Number of nodes was given as %" IGRAPH_PRId".", + IGRAPH_PARSEERROR, tmp, no_of_nodes); + } + VECTOR(*label)[tmp] = tmp2; + } + } + + break; + + case 'a': + /* This is valid only for MAX, a weighted edge */ + if (problem_type != PROBLEM_MAX) { + IGRAPH_ERROR("'a' lines are allowed only in MAX problem files.", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%" IGRAPH_PRId " %" IGRAPH_PRId " %lf", &from, &to, &cap); + EXPECT(read, 3); + CHECK_VID(from); + CHECK_VID(to); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from - 1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to - 1)); + if (capacity) { + IGRAPH_CHECK(igraph_vector_push_back(capacity, cap)); + } + break; + + case 'e': + /* Edge line, only in EDGE */ + if (problem_type != PROBLEM_EDGE) { + IGRAPH_ERROR("'e' lines are allowed only in EDGE problem files.", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%" IGRAPH_PRId " %" IGRAPH_PRId "", &from, &to); + EXPECT(read, 2); + CHECK_VID(from); + CHECK_VID(to); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from - 1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to - 1)); + break; + + default: + IGRAPH_ERROR("Unknown line type in DIMACS file.", IGRAPH_PARSEERROR); + } + + /* Go to next line */ + while (!feof(instream) && getc(instream) != '\n') ; + } + + if (source) { + *source = tsource - 1; + } + if (target) { + *target = ttarget - 1; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_write_graph_dimacs_flow + * \brief Write a graph in DIMACS format. + * + * This function writes a graph to an output stream in DIMACS format, + * describing a maximum flow problem. + * See ftp://dimacs.rutgers.edu/pub/netflow/general-info/ + * + * + * This file format is discussed in the documentation of \ref + * igraph_read_graph_dimacs_flow(), see that for more information. + * + * \param graph The graph to write to the stream. + * \param outstream The stream. + * \param source Integer, the id of the source vertex for the maximum + * flow. + * \param target Integer, the id of the target vertex. + * \param capacity Pointer to an initialized vector containing the + * edge capacity values. + * \return Error code. + * + * Time complexity: O(|E|), the number of edges in the graph. + * + * \sa \ref igraph_read_graph_dimacs_flow() + */ +igraph_error_t igraph_write_graph_dimacs_flow(const igraph_t *graph, FILE *outstream, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_eit_t it; + igraph_int_t i = 0; + int ret, ret1, ret2, ret3; + + if (igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERRORF("Capacity vector length (%" IGRAPH_PRId ") " + "does not match edge count (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(capacity), no_of_edges); + } + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + ret = fprintf(outstream, + "c created by igraph\n" + "p max %" IGRAPH_PRId " %" IGRAPH_PRId "\n" + "n %" IGRAPH_PRId " s\n" + "n %" IGRAPH_PRId " t\n", + no_of_nodes, no_of_edges, source + 1, target + 1); + if (ret < 0) { + IGRAPH_ERROR("Error while writing DIMACS flow file.", IGRAPH_EFILE); + } + + while (!IGRAPH_EIT_END(it)) { + igraph_int_t from, to; + igraph_real_t cap; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + cap = VECTOR(*capacity)[i++]; + ret1 = fprintf(outstream, "a %" IGRAPH_PRId " %" IGRAPH_PRId " ", + from + 1, to + 1); + ret2 = igraph_real_fprintf_precise(outstream, cap); + ret3 = fputc('\n', outstream); + if (ret1 < 0 || ret2 < 0 || ret3 == EOF) { + IGRAPH_ERROR("Error while writing DIMACS flow file.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/io/dl-header.h b/src/io/dl-header.h new file mode 100644 index 0000000..1da687e --- /dev/null +++ b/src/io/dl-header.h @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_types.h" + +#include "core/trie.h" + +/* TODO: Find out maximum supported vertex count. */ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +/* Limit maximum vertex count when using a fuzzer, to avoid out-of-memory failure. */ +#define IGRAPH_DL_MAX_VERTEX_COUNT (1 << 20) +#else +#define IGRAPH_DL_MAX_VERTEX_COUNT INT32_MAX +#endif + +typedef enum { IGRAPH_DL_MATRIX, + IGRAPH_DL_EDGELIST1, IGRAPH_DL_NODELIST1 + } igraph_i_dl_type_t; + +typedef struct { + void *scanner; + int eof; + char errmsg[300]; + igraph_error_t igraph_errno; + int mode; + igraph_int_t n; + igraph_int_t from, to; + igraph_vector_int_t edges; + igraph_vector_t weights; + igraph_strvector_t labels; + igraph_trie_t trie; + igraph_i_dl_type_t type; +} igraph_i_dl_parsedata_t; diff --git a/src/io/dl.c b/src/io/dl.c new file mode 100644 index 0000000..7d0267c --- /dev/null +++ b/src/io/dl.c @@ -0,0 +1,207 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" + +#include "io/dl-header.h" +#include "io/parsers/dl-parser.h" + +int igraph_dl_yylex_init_extra (igraph_i_dl_parsedata_t *user_defined, + void *scanner); +int igraph_dl_yylex_destroy(void *scanner); +int igraph_dl_yyparse(igraph_i_dl_parsedata_t *context); +void igraph_dl_yyset_in(FILE *in_str, void *yyscanner); + +/* for IGRAPH_FINALLY, which assumes that destructor functions return void */ +void igraph_dl_yylex_destroy_wrapper (void *scanner ) { + (void) igraph_dl_yylex_destroy(scanner); +} + +/** + * \function igraph_read_graph_dl + * \brief Reads a file in the DL format of UCINET. + * + * This is a simple textual file format used by UCINET. See + * http://www.analytictech.com/networks/dataentry.htm for + * examples. All the forms described here are supported by + * igraph. Vertex names and edge weights are also supported and they + * are added as attributes. (If an attribute handler is attached.) + * + * Note the specification does not mention whether the + * format is case sensitive or not. For igraph DL files are case + * sensitive, i.e. \c Larry and \c larry are not the same. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read the DL file from. + * \param directed Boolean, whether to create a directed file. + * \return Error code. + * + * Time complexity: linear in terms of the number of edges and + * vertices, except for the matrix format, which is quadratic in the + * number of vertices. + * + * \example examples/simple/igraph_read_graph_dl.c + */ + +igraph_error_t igraph_read_graph_dl(igraph_t *graph, FILE *instream, + igraph_bool_t directed) { + + igraph_int_t n, n2; + const igraph_strvector_t *namevec = 0; + igraph_attribute_record_list_t name, weight; + igraph_attribute_record_list_t *pname = NULL; + igraph_attribute_record_list_t *pweight = NULL; + igraph_attribute_record_t *namerec, *weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_dl_parsedata_t context; + + context.eof = 0; + context.mode = 0; + context.n = -1; + context.from = 0; + context.to = 0; + context.errmsg[0] = '\0'; + context.igraph_errno = IGRAPH_SUCCESS; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&context.edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&context.weights, 0); + IGRAPH_CHECK(igraph_strvector_init(&context.labels, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, &context.labels); + IGRAPH_TRIE_INIT_FINALLY(&context.trie, /*names=*/ 1); + + igraph_dl_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_dl_yylex_destroy_wrapper, context.scanner); + + igraph_dl_yyset_in(instream, context.scanner); + + /* Use ENTER/EXIT to avoid destroying context.scanner before this function returns */ + IGRAPH_FINALLY_ENTER(); + int err = igraph_dl_yyparse(&context); + IGRAPH_FINALLY_EXIT(); + switch (err) { + case 0: /* success */ + break; + case 1: /* parse error */ + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else if (context.igraph_errno != IGRAPH_SUCCESS) { + IGRAPH_ERROR("", context.igraph_errno); + } else { + IGRAPH_ERROR("Cannot read DL file.", IGRAPH_PARSEERROR); + } + break; + case 2: /* out of memory */ + IGRAPH_ERROR("Cannot read DL file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + default: /* must never reach here */ + /* Hint: This will usually be triggered if an IGRAPH_CHECK() is used in a Bison + * action instead of an IGRAPH_YY_CHECK(), resulting in an igraph errno being + * returned in place of a Bison error code. + * TODO: What if future Bison versions introduce error codes other than 0, 1 and 2? + */ + IGRAPH_FATALF("Parser returned unexpected error code (%d) when reading DL file.", err); + } + + /* Extend the weight vector, if needed */ + n = igraph_vector_size(&context.weights); + n2 = igraph_vector_int_size(&context.edges) / 2; + if (n != 0) { + IGRAPH_CHECK(igraph_vector_resize(&context.weights, n2)); + for (; n < n2; n++) { + VECTOR(context.weights)[n] = IGRAPH_NAN; + } + } + + /* Check number of vertices */ + if (n2 > 0) { + n = igraph_vector_int_max(&context.edges); + } else { + n = 0; + } + if (n >= context.n) { + IGRAPH_WARNING("More vertices than specified in `DL' file"); + context.n = n; + } + + /* Prepare attributes */ + + /* Labels */ + if (igraph_strvector_size(&context.labels) != 0) { + namevec = (const igraph_strvector_t*) &context.labels; + } else if (igraph_trie_size(&context.trie) != 0) { + namevec = igraph_i_trie_borrow_keys(&context.trie); + } + if (namevec) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&name, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &name); + + namerec = igraph_attribute_record_list_get_ptr(&name, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(namerec, namestr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(namerec, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_CHECK(igraph_strvector_update(namerec->value.as_strvector, namevec)); + + pname = &name; + } + + /* Weights */ + if (igraph_vector_size(&context.weights) != 0) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&weight, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &weight); + + weightrec = igraph_attribute_record_list_get_ptr(&weight, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(weightrec, weightstr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(weightrec, IGRAPH_ATTRIBUTE_NUMERIC)); + igraph_vector_swap(weightrec->value.as_vector, &context.weights); + + pweight = &weight; + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, context.n, pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &context.edges, pweight)); + + if (pweight) { + igraph_attribute_record_list_destroy(pweight); + IGRAPH_FINALLY_CLEAN(1); + } + + if (pname) { + igraph_attribute_record_list_destroy(pname); + IGRAPH_FINALLY_CLEAN(1); + } + + /* don't destroy the graph itself but pop it from the finally stack */ + IGRAPH_FINALLY_CLEAN(1); + + igraph_trie_destroy(&context.trie); + igraph_strvector_destroy(&context.labels); + igraph_vector_int_destroy(&context.edges); + igraph_vector_destroy(&context.weights); + igraph_dl_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} diff --git a/src/io/dot.c b/src/io/dot.c new file mode 100644 index 0000000..899dbd9 --- /dev/null +++ b/src/io/dot.c @@ -0,0 +1,322 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_version.h" + +#include "graph/attributes.h" +#include "internal/hacks.h" /* strcasecmp & strdup */ +#include "math/safe_intop.h" /* IGRAPH_MAX_EXACT_REAL */ + +#include +#include + +#define CHECK(cmd) do { int ret=cmd; if (ret<0) IGRAPH_ERROR("Writing DOT format failed.", IGRAPH_EFILE); } while (0) + +static igraph_error_t dot_escape(const char *orig, igraph_vector_char_t* result) { + /* do we have to escape the string at all? */ + igraph_int_t i, j, len = strlen(orig), newlen = 0; + igraph_bool_t need_quote = false, is_number = true; + + /* first, check whether the string is equal to some reserved word, or empty */ + if (!strcasecmp(orig, "graph") || !strcasecmp(orig, "digraph") || + !strcasecmp(orig, "node") || !strcasecmp(orig, "edge") || + !strcasecmp(orig, "strict") || !strcasecmp(orig, "subgraph") || len == 0) { + need_quote = true; + is_number = false; + } + + /* next, check whether we need to escape the string for any other reason. + * Also update is_number and newlen */ + for (i = 0; i < len; i++) { + if (isdigit(orig[i])) { + newlen++; + } else if (orig[i] == '-' && i == 0) { + newlen++; + } else if (orig[i] == '.') { + if (is_number) { + newlen++; + } else { + need_quote = true; + newlen++; + } + } else if (orig[i] == '_') { + is_number = false; newlen++; + } else if (orig[i] == '\\' || orig[i] == '"' || orig[i] == '\n') { + need_quote = true; is_number = false; newlen += 2; /* will be escaped */ + } else if (isalpha(orig[i])) { + is_number = false; newlen++; + } else { + is_number = false; need_quote = true; newlen++; + } + } + if (is_number && len > 0 && orig[len - 1] == '.') { + is_number = false; + } + if (!is_number && isdigit(orig[0])) { + need_quote = true; + } + + if (is_number || !need_quote) { + IGRAPH_CHECK(igraph_vector_char_resize(result, newlen + 1)); + memcpy(VECTOR(*result), orig, newlen); + } else { + newlen += 2; + IGRAPH_CHECK(igraph_vector_char_resize(result, newlen + 1)); + VECTOR(*result)[0] = '"'; + VECTOR(*result)[newlen - 1] = '"'; + + /* Escape quotes, backslashes and newlines. + * Even though the format spec at https://graphviz.org/doc/info/lang.html + * claims that only quotes need escaping, escaping backslashes appears to + * be necessary as well for GraphViz to render labels correctly. + * Tested with GraphViz 2.50. */ + for (i = 0, j = 1; i < len; i++) { + if (orig[i] == '\n') { + VECTOR(*result)[j++] = '\\'; + VECTOR(*result)[j++] = 'n'; + continue; + } + if (orig[i] == '\\' || orig[i] == '"') { + VECTOR(*result)[j++] = '\\'; + } + VECTOR(*result)[j++] = orig[i]; + } + } + VECTOR(*result)[newlen] = 0; + + return IGRAPH_SUCCESS; +} + +/* Writes exactly representable integral values in standard integer notation, without decimal points or e-notation. + * Floating point values that are written with e-notation are quoted, otherwise the Graphviz parser cannot handle them. + */ +static igraph_error_t fprint_integral_or_precise(FILE *file, igraph_real_t x, igraph_vector_char_t *buf) { + if (fabs(x) <= IGRAPH_MAX_EXACT_REAL && floor(x) == x) { + /* write exactly representable integral values in standard integer notation; + * the above conditional skips +-Inf and NaN */ + CHECK(fprintf(file, "%.f", x)); + } else { + /* write as precise float and quote if necessary */ + char str[50]; /* large enough to hold any precisely printed real */ + CHECK(igraph_real_snprintf_precise(str, sizeof(str) / sizeof(str[0]), x)); + IGRAPH_CHECK(dot_escape(str, buf)); + CHECK(fputs(VECTOR(*buf), file)); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_write_graph_dot + * \brief Write the graph to a stream in DOT format. + * + * + * DOT is the format used by the widely known GraphViz software, see + * http://www.graphviz.org for details. The grammar of the DOT format + * can be found here: http://www.graphviz.org/doc/info/lang.html + * + * + * This is only a preliminary implementation, no visualization + * information is written. + * + * + * This format is meant solely for interoperability with Graphviz. + * It is not recommended for data exchange or archival. + * + * \param graph The graph to write to the stream. + * \param outstream The stream to write the file to. + * \return Error code. + * + * Time complexity: should be proportional to the number of characters written + * to the file. + * + * \sa \ref igraph_write_graph_graphml() for a more modern format. + * + * \example examples/simple/dot.c + */ +igraph_error_t igraph_write_graph_dot(const igraph_t *graph, FILE* outstream) { + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_bool_t directed = igraph_is_directed(graph); + const char *edgeop = directed ? "->" : "--"; + igraph_strvector_t gnames, vnames, enames; + igraph_vector_int_t gtypes, vtypes, etypes; + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + igraph_vector_char_t buf, buf2; + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&etypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes)); + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 0); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 0); + + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&buf, 0); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&buf2, 0); + + CHECK(fprintf(outstream, "/* Created by igraph %s */\n", IGRAPH_VERSION)); + + if (directed) { + CHECK(fprintf(outstream, "digraph {\n")); + } else { + CHECK(fprintf(outstream, "graph {\n")); + } + + /* Write the graph attributes */ + if (igraph_vector_int_size(>ypes) > 0) { + CHECK(fprintf(outstream, " graph [\n")); + for (igraph_int_t i = 0; i < igraph_vector_int_size(>ypes); i++) { + const char *name; + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(dot_escape(name, &buf)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + CHECK(fprintf(outstream, " %s=", VECTOR(buf))); + IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); + CHECK(fputc('\n', outstream)); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(dot_escape(s, &buf2)); + CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("Boolean graph attribute was converted to numeric."); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean graph attribute was ignored."); + } + } + CHECK(fprintf(outstream, " ];\n")); + } + + /* Write the vertices */ + if (igraph_vector_int_size(&vtypes) > 0) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, " %" IGRAPH_PRId " [\n", i)); + for (igraph_int_t j = 0; j < igraph_vector_int_size(&vtypes); j++) { + const char *name; + name = igraph_strvector_get(&vnames, j); + IGRAPH_CHECK(dot_escape(name, &buf)); + if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, igraph_vss_1(i), &numv)); + CHECK(fprintf(outstream, " %s=", VECTOR(buf))); + IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); + CHECK(fputc('\n', outstream)); + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, igraph_vss_1(i), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(dot_escape(s, &buf2)); + CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, igraph_vss_1(i), &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean vertex attribute was converted to numeric."); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean vertex attribute was ignored."); + } + } + CHECK(fprintf(outstream, " ];\n")); + } + } else { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, " %" IGRAPH_PRId ";\n", i)); + } + } + CHECK(fprintf(outstream, "\n")); + + /* Write the edges */ + if (igraph_vector_int_size(&etypes) > 0) { + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + CHECK(fprintf(outstream, " %" IGRAPH_PRId " %s %" IGRAPH_PRId " [\n", from, edgeop, to)); + for (igraph_int_t j = 0; j < igraph_vector_int_size(&etypes); j++) { + const char *name; + name = igraph_strvector_get(&enames, j); + IGRAPH_CHECK(dot_escape(name, &buf)); + if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, + name, igraph_ess_1(i), &numv)); + CHECK(fprintf(outstream, " %s=", VECTOR(buf))); + IGRAPH_CHECK(fprint_integral_or_precise(outstream, VECTOR(numv)[0], &buf)); + CHECK(fputc('\n', outstream)); + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, + name, igraph_ess_1(i), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(dot_escape(s, &buf2)); + CHECK(fprintf(outstream, " %s=%s\n", VECTOR(buf), VECTOR(buf2))); + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, + name, igraph_ess_1(i), &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", VECTOR(buf), VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean edge attribute was converted to numeric."); + } else { + IGRAPH_WARNING("A non-numeric, non-string graph attribute ignored."); + } + } + CHECK(fprintf(outstream, " ];\n")); + } + } else { + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + CHECK(fprintf(outstream, " %" IGRAPH_PRId " %s %" IGRAPH_PRId ";\n", from, edgeop, to)); + } + } + CHECK(fprintf(outstream, "}\n")); + + igraph_vector_char_destroy(&buf2); + igraph_vector_char_destroy(&buf); + igraph_vector_bool_destroy(&boolv); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + igraph_vector_int_destroy(&etypes); + igraph_vector_int_destroy(&vtypes); + igraph_vector_int_destroy(>ypes); + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + IGRAPH_FINALLY_CLEAN(11); + + return IGRAPH_SUCCESS; +} + +#undef CHECK diff --git a/src/io/edgelist.c b/src/io/edgelist.c new file mode 100644 index 0000000..d0bb089 --- /dev/null +++ b/src/io/edgelist.c @@ -0,0 +1,160 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" + +#include "core/interruption.h" +#include "io/parse_utils.h" + +/** + * \section about_loadsave + * + * These functions can write a graph to a file, or read a graph + * from a file. + * + * They assume that the current locale uses a decimal point and not + * a decimal comma. See \ref igraph_enter_safelocale() and + * \ref igraph_exit_safelocale() for more information. + * + * Note that as \a igraph uses the traditional C streams, it is + * possible to read/write files from/to memory, at least on GNU + * operating systems supporting \quote non-standard\endquote streams. + */ + +/** + * \ingroup loadsave + * \function igraph_read_graph_edgelist + * \brief Reads an edge list from a file and creates a graph. + * + * This format is simply a series of an even number of non-negative integers separated by + * whitespace. The integers represent vertex IDs. Placing each edge (i.e. pair of integers) + * on a separate line is not required, but it is recommended for readability. + * Edges of directed graphs are assumed to be in "from, to" order. + * + * + * The largest vertex ID plus one, or the parameter \p n determines the vertex count, + * whichever is larger. See \ref igraph_read_graph_ncol() for reading files where + * vertices are specified by name instead of by a numerical vertex ID. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream Pointer to a stream, it should be readable. + * \param n The number of vertices in the graph. If smaller than the + * largest integer in the file it will be ignored. It is thus + * safe to supply zero here. + * \param directed If true the graph is directed, if false it + * will be undirected. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. It is assumed that + * reading an integer requires O(1) time. + */ +igraph_error_t igraph_read_graph_edgelist(igraph_t *graph, FILE *instream, + igraph_int_t n, igraph_bool_t directed) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_int_t from, to; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 100)); + + for (;;) { + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_i_fskip_whitespace(instream)); + + if (feof(instream)) break; + + IGRAPH_CHECK(igraph_i_fget_integer(instream, &from)); + IGRAPH_CHECK(igraph_i_fget_integer(instream, &to)); + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + /* Protect from very large memory allocations when fuzzing. */ +#define IGRAPH_EDGELIST_MAX_VERTEX_COUNT (1L << 20) + if (from > IGRAPH_EDGELIST_MAX_VERTEX_COUNT || to > IGRAPH_EDGELIST_MAX_VERTEX_COUNT) { + IGRAPH_ERROR("Vertex count too large in edgelist file.", IGRAPH_EINVAL); + } +#endif + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_edgelist + * \brief Writes the edge list of a graph to a file. + * + * + * Edges are represented as pairs of 0-based vertex indices. + * One edge is written per line, separated by a single space. + * For directed graphs edges are written in from, to order. + * + * \param graph The graph object to write. + * \param outstream Pointer to a stream, it should be writable. + * \return Error code: + * \c IGRAPH_EFILE if there is an error writing the + * file. + * + * Time complexity: O(|E|), the + * number of edges in the graph. It is assumed that writing an + * integer to the file requires O(1) + * time. + */ +igraph_error_t igraph_write_graph_edgelist(const igraph_t *graph, FILE *outstream) { + + igraph_eit_t it; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + while (!IGRAPH_EIT_END(it)) { + igraph_int_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + ret = fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId "\n", + from, + to); + if (ret < 0) { + IGRAPH_ERROR("Failed writing edgelist.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/io/gml-header.h b/src/io/gml-header.h new file mode 100644 index 0000000..ef9b5d3 --- /dev/null +++ b/src/io/gml-header.h @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2011-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_error.h" + +#include "io/gml-tree.h" + +typedef struct { + void *scanner; + char errmsg[300]; + igraph_error_t igraph_errno; + int depth; + igraph_gml_tree_t *tree; +} igraph_i_gml_parsedata_t; + +/** + * Initializes a GML parser context. + */ +igraph_error_t igraph_i_gml_parsedata_init(igraph_i_gml_parsedata_t* context); + +/** + * Destroys a GML parser context, freeing all memory currently used by the + * context. + */ +void igraph_i_gml_parsedata_destroy(igraph_i_gml_parsedata_t* context); diff --git a/src/io/gml-tree.c b/src/io/gml-tree.c new file mode 100644 index 0000000..05b1d1d --- /dev/null +++ b/src/io/gml-tree.c @@ -0,0 +1,279 @@ +/* + igraph library. + Copyright (C) 2007-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" + +#include "io/gml-tree.h" + +#include + +igraph_error_t igraph_gml_tree_init_integer(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_int_t value) { + + igraph_int_t *p; + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&t->types, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->lines, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* line number */ + VECTOR(t->lines)[0] = line; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_INTEGER; + + /* children */ + p = IGRAPH_CALLOC(1, igraph_int_t); + IGRAPH_CHECK_OOM(p, "Cannot create integer GML tree node."); + *p = value; + VECTOR(t->children)[0] = p; + + IGRAPH_FINALLY_CLEAN(4); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_gml_tree_init_real(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_real_t value) { + + igraph_real_t *p; + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&t->types, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->lines, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* line number */ + VECTOR(t->lines)[0] = line; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_REAL; + + /* children */ + p = IGRAPH_CALLOC(1, igraph_real_t); + IGRAPH_CHECK_OOM(p, "Cannot create real GML tree node."); + *p = value; + VECTOR(t->children)[0] = p; + + IGRAPH_FINALLY_CLEAN(4); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_gml_tree_init_string(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + const char *value) { + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&t->types, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->lines, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* line number */ + VECTOR(t->lines)[0] = line; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_STRING; + + /* children */ + VECTOR(t->children)[0] = (void*) value; + + IGRAPH_FINALLY_CLEAN(4); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_gml_tree_init_tree(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_gml_tree_t *value) { + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&t->types, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->lines, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* line number */ + VECTOR(t->lines)[0] = line; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_TREE; + + /* children */ + VECTOR(t->children)[0] = value; + + IGRAPH_FINALLY_CLEAN(4); + return IGRAPH_SUCCESS; + +} + +igraph_error_t igraph_gml_tree_init_empty(igraph_gml_tree_t *t) { + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 0); + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&t->types, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&t->lines, 0); + IGRAPH_FINALLY_CLEAN(4); + return IGRAPH_SUCCESS; +} + +/* merge is destructive, the _second_ tree is destroyed */ +igraph_error_t igraph_gml_tree_mergedest(igraph_gml_tree_t *t1, igraph_gml_tree_t *t2) { + igraph_int_t i, n = igraph_vector_ptr_size(&t2->children); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(&t1->names, VECTOR(t2->names)[i])); + IGRAPH_CHECK(igraph_vector_char_push_back(&t1->types, VECTOR(t2->types)[i])); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&t1->children, VECTOR(t2->children)[i])); + IGRAPH_CHECK(igraph_vector_int_push_back(&t1->lines, VECTOR(t2->lines)[i])); + } + + igraph_vector_ptr_destroy(&t2->names); + igraph_vector_char_destroy(&t2->types); + igraph_vector_ptr_destroy(&t2->children); + igraph_vector_int_destroy(&t2->lines); + + return IGRAPH_SUCCESS; +} + +void igraph_gml_tree_destroy(igraph_gml_tree_t *t) { + + igraph_int_t i, n = igraph_vector_ptr_size(&t->children); + for (i = 0; i < n; i++) { + igraph_i_gml_tree_type_t type = (igraph_i_gml_tree_type_t) VECTOR(t->types)[i]; + switch (type) { + case IGRAPH_I_GML_TREE_TREE: + igraph_gml_tree_destroy(VECTOR(t->children)[i]); + IGRAPH_FREE(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_INTEGER: + IGRAPH_FREE(VECTOR(t->children)[i]); + IGRAPH_FREE(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_REAL: + IGRAPH_FREE(VECTOR(t->children)[i]); + IGRAPH_FREE(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_STRING: + IGRAPH_FREE(VECTOR(t->children)[i]); + IGRAPH_FREE(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_DELETED: + break; + } + } + igraph_vector_ptr_destroy(&t->names); + igraph_vector_char_destroy(&t->types); + igraph_vector_ptr_destroy(&t->children); + igraph_vector_int_destroy(&t->lines); + IGRAPH_FREE(t); +} + +igraph_int_t igraph_gml_tree_length(const igraph_gml_tree_t *t) { + return igraph_vector_ptr_size(&t->names); +} + +igraph_int_t igraph_gml_tree_find( + const igraph_gml_tree_t *t, const char *name, igraph_int_t from +) { + igraph_int_t size = igraph_vector_ptr_size(&t->names); + while ( from < size && (! VECTOR(t->names)[from] || + strcmp(VECTOR(t->names)[from], name)) ) { + from++; + } + + if (from == size) { + from = -1; + } + return from; +} + +igraph_int_t igraph_gml_tree_findback( + const igraph_gml_tree_t *t, const char *name, igraph_int_t from +) { + while ( from >= 0 && (! VECTOR(t->names)[from] || + strcmp(VECTOR(t->names)[from], name)) ) { + from--; + } + + return from; +} + +igraph_i_gml_tree_type_t igraph_gml_tree_type(const igraph_gml_tree_t *t, igraph_int_t pos) { + return (igraph_i_gml_tree_type_t) VECTOR(t->types)[pos]; +} + +const char *igraph_gml_tree_name(const igraph_gml_tree_t *t, igraph_int_t pos) { + return VECTOR(t->names)[pos]; +} + +igraph_int_t igraph_gml_tree_line(const igraph_gml_tree_t *t, igraph_int_t pos) { + return VECTOR(t->lines)[pos]; +} + +igraph_int_t igraph_gml_tree_get_integer(const igraph_gml_tree_t *t, + igraph_int_t pos) { + igraph_int_t *i = VECTOR(t->children)[pos]; + return *i; +} + +igraph_real_t igraph_gml_tree_get_real(const igraph_gml_tree_t *t, + igraph_int_t pos) { + igraph_real_t *d = VECTOR(t->children)[pos]; + return *d; +} + +const char *igraph_gml_tree_get_string(const igraph_gml_tree_t *t, + igraph_int_t pos) { + const char *s = VECTOR(t->children)[pos]; + return s; +} + +igraph_gml_tree_t *igraph_gml_tree_get_tree(const igraph_gml_tree_t *t, + igraph_int_t pos) { + igraph_gml_tree_t *tree = VECTOR(t->children)[pos]; + return tree; +} + +void igraph_gml_tree_delete(igraph_gml_tree_t *t, igraph_int_t pos) { + if (VECTOR(t->types)[pos] == IGRAPH_I_GML_TREE_TREE) { + igraph_gml_tree_destroy(VECTOR(t->children)[pos]); + } + IGRAPH_FREE(VECTOR(t->names)[pos]); + IGRAPH_FREE(VECTOR(t->children)[pos]); + VECTOR(t->children)[pos] = 0; + VECTOR(t->names)[pos] = 0; + VECTOR(t->types)[pos] = IGRAPH_I_GML_TREE_DELETED; +} diff --git a/src/io/gml-tree.h b/src/io/gml-tree.h new file mode 100644 index 0000000..fbae24a --- /dev/null +++ b/src/io/gml-tree.h @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2007-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#ifndef REST_GML_TREE_H +#define REST_GML_TREE_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +typedef enum { IGRAPH_I_GML_TREE_TREE = 0, + IGRAPH_I_GML_TREE_INTEGER, + IGRAPH_I_GML_TREE_REAL, + IGRAPH_I_GML_TREE_STRING, + IGRAPH_I_GML_TREE_DELETED + } igraph_i_gml_tree_type_t; + +typedef struct igraph_gml_tree_t { + igraph_vector_ptr_t names; + igraph_vector_char_t types; + igraph_vector_ptr_t children; + igraph_vector_int_t lines; /* line numbers where names appear */ +} igraph_gml_tree_t; + +igraph_error_t igraph_gml_tree_init_integer(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_int_t value); +igraph_error_t igraph_gml_tree_init_real(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_real_t value); +igraph_error_t igraph_gml_tree_init_string(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + const char *value); +igraph_error_t igraph_gml_tree_init_tree(igraph_gml_tree_t *t, + const char *name, + igraph_int_t line, + igraph_gml_tree_t *value); +igraph_error_t igraph_gml_tree_init_empty(igraph_gml_tree_t *t); +void igraph_gml_tree_destroy(igraph_gml_tree_t *t); + +void igraph_gml_tree_delete(igraph_gml_tree_t *t, igraph_int_t pos); +igraph_error_t igraph_gml_tree_mergedest(igraph_gml_tree_t *t1, igraph_gml_tree_t *t2); + +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gml_tree_length(const igraph_gml_tree_t *t); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gml_tree_find(const igraph_gml_tree_t *t, + const char *name, igraph_int_t from); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gml_tree_findback(const igraph_gml_tree_t *t, + const char *name, igraph_int_t from); +IGRAPH_FUNCATTR_PURE igraph_i_gml_tree_type_t igraph_gml_tree_type(const igraph_gml_tree_t *t, igraph_int_t pos); +IGRAPH_FUNCATTR_PURE const char *igraph_gml_tree_name(const igraph_gml_tree_t *t, igraph_int_t pos); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gml_tree_line(const igraph_gml_tree_t *t, igraph_int_t pos); +IGRAPH_FUNCATTR_PURE igraph_int_t igraph_gml_tree_get_integer(const igraph_gml_tree_t *t, + igraph_int_t pos); +IGRAPH_FUNCATTR_PURE igraph_real_t igraph_gml_tree_get_real(const igraph_gml_tree_t *t, + igraph_int_t pos); +IGRAPH_FUNCATTR_PURE const char *igraph_gml_tree_get_string(const igraph_gml_tree_t *t, + igraph_int_t pos); + +IGRAPH_FUNCATTR_PURE igraph_gml_tree_t *igraph_gml_tree_get_tree(const igraph_gml_tree_t *t, + igraph_int_t pos); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/io/gml.c b/src/io/gml.c new file mode 100644 index 0000000..85df1fc --- /dev/null +++ b/src/io/gml.c @@ -0,0 +1,1290 @@ +/* + igraph library. + Copyright (C) 2005-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_version.h" + +#include "core/trie.h" +#include "graph/attributes.h" +#include "internal/hacks.h" /* strdup, strncasecmp */ +#include "math/safe_intop.h" + +#include "io/gml-header.h" +#include "io/parsers/gml-parser.h" + +#include +#include +#include + +int igraph_gml_yylex_init_extra(igraph_i_gml_parsedata_t *user_defined, void *scanner); +int igraph_gml_yylex_destroy(void *scanner); +int igraph_gml_yyparse(igraph_i_gml_parsedata_t *context); +void igraph_gml_yyset_in(FILE *in_str, void *yyscanner); + +/* Checks if a null-terminated string needs encoding or decoding. + * + * Encoding is needed when an " or & character is present. + * + * Decoding is needed when an &xyz; style entity is present, so it's sufficient to look + * for & characters. " characters are never present in the raw strings returned by the + * GML parser, so we can use the same function to detect the need for either encoding + * or decoding. + */ +static igraph_bool_t needs_coding(const char *str) { + while (*str) { + if (*str == '&' || *str == '"') { + return true; + } + str++; + } + return false; +} + +/* Encode & and " character in 'src' to & and " + * '*dest' must be deallocated by the caller. + */ +static igraph_error_t entity_encode(const char *src, char **dest, igraph_bool_t only_quot) { + igraph_int_t destlen = 0; + const char *s; + char *d; + + for (s = src; *s != '\0'; s++, destlen++) { + switch (*s) { + case '&': /* & */ + if (! only_quot) { + destlen += 4; + } + break; + case '"': /* " */ + destlen += 5; break; + } + } + *dest = IGRAPH_CALLOC(destlen + 1, char); + IGRAPH_CHECK_OOM(dest, "Not enough memory to encode string for GML export."); + for (s = src, d = *dest; *s != '\0'; s++, d++) { + switch (*s) { + case '&': + if (! only_quot) { + strcpy(d, "&"); + d += 4; + } else { + *d = *s; + } + break; + case '"': + strcpy(d, """); d += 5; break; + default: + *d = *s; + } + } + *d = '\0'; + return IGRAPH_SUCCESS; +} + +/* Decode the five standard predefined XML entities. Unknown entities or stray & characters + * will be passed through unchanged. '*dest' must be deallocated by the caller. + * If '*warned' is false, warnings will be issued for unsupported entities and + * '*warned' will be set to true. This is to prevent a flood of warnings in some files. + */ +static igraph_error_t entity_decode(const char *src, char **dest, igraph_bool_t *warned) { + const char *entity_names[] = { + """, "&", "'", "<", ">" + }; + + const char entity_values[] = { + '"', '&', '\'', '<', '>' + }; + + const int entity_count = sizeof entity_values / sizeof entity_values[0]; + + const char *s; + char *d; + size_t len = strlen(src); + *dest = IGRAPH_CALLOC(len+1, char); /* at most as much storage needed as for 'src' */ + IGRAPH_CHECK_OOM(dest, "Not enough memory to decode string during GML import."); + + for (s = src, d = *dest; *s != '\0';) { + if (*s == '&') { + int i; + for (i=0; i < entity_count; i++) { + size_t entity_len = strlen(entity_names[i]); + if (!strncasecmp(s, entity_names[i], entity_len)) { + *d++ = entity_values[i]; + s += entity_len; + break; + } + } + /* None of the known entities match, report warning and pass through unchanged. */ + if (i == entity_count) { + if (! *warned) { + const int max_entity_name_length = 34; + int j = 0; + while (s[j] != '\0' && s[j] != ';' && j < max_entity_name_length) { + j++; + } + if (s[j] == '\0' || j == max_entity_name_length) { + IGRAPH_WARNING("Unterminated entity or stray & character found, will be returned verbatim."); + } else { + IGRAPH_WARNINGF("One or more unknown entities will be returned verbatim (%.*s).", j+1, s); + } + *warned = true; /* warn only once */ + } + *d++ = *s++; + } + } else { + *d++ = *s++; + } + } + *d = '\0'; + + return IGRAPH_SUCCESS; +} + +static igraph_real_t igraph_i_gml_toreal(igraph_gml_tree_t *node, igraph_int_t pos) { + igraph_i_gml_tree_type_t type = igraph_gml_tree_type(node, pos); + + switch (type) { + case IGRAPH_I_GML_TREE_INTEGER: + return igraph_gml_tree_get_integer(node, pos); + case IGRAPH_I_GML_TREE_REAL: + return igraph_gml_tree_get_real(node, pos); + case IGRAPH_I_GML_TREE_TREE: + return IGRAPH_NAN; /* default value of NaN when composite is ignored */ + default: + /* Must never reach here, regardless of the contents of the GML file. */ + IGRAPH_FATALF("Unexpected node type in GML tree, line %" IGRAPH_PRId ".", + igraph_gml_tree_line(node, pos)); /* LCOV_EXCL_LINE */ + } +} + +static const char *igraph_i_gml_tostring(igraph_gml_tree_t *node, igraph_int_t pos) { + igraph_i_gml_tree_type_t type = igraph_gml_tree_type(node, pos); + static char tmp[100]; + const char *p = tmp; + igraph_int_t i; + igraph_real_t d; + + switch (type) { + case IGRAPH_I_GML_TREE_INTEGER: + i = igraph_gml_tree_get_integer(node, pos); + snprintf(tmp, sizeof(tmp) / sizeof(char), "%" IGRAPH_PRId, i); + break; + case IGRAPH_I_GML_TREE_REAL: + d = igraph_gml_tree_get_real(node, pos); + igraph_real_snprintf_precise(tmp, sizeof(tmp) / sizeof(char), d); + break; + case IGRAPH_I_GML_TREE_STRING: + p = igraph_gml_tree_get_string(node, pos); + break; + case IGRAPH_I_GML_TREE_TREE: + tmp[0] = '\0'; /* default value of "" when composite is ignored */ + break; + default: + /* Must never reach here, regardless of the contents of the GML file. */ + IGRAPH_FATALF("Unexpected node type in GML tree, line %" IGRAPH_PRId ".", + igraph_gml_tree_line(node, pos)); /* LCOV_EXCL_LINE */ + } + + return p; +} + +igraph_error_t igraph_i_gml_parsedata_init(igraph_i_gml_parsedata_t *context) { + context->depth = 0; + context->scanner = NULL; + context->tree = NULL; + context->errmsg[0] = '\0'; + context->igraph_errno = IGRAPH_SUCCESS; + + return IGRAPH_SUCCESS; +} + +void igraph_i_gml_parsedata_destroy(igraph_i_gml_parsedata_t *context) { + if (context->tree != NULL) { + igraph_gml_tree_destroy(context->tree); + context->tree = NULL; + } + + if (context->scanner != NULL) { + (void) igraph_gml_yylex_destroy(context->scanner); + context->scanner = NULL; + } +} + +/* Takes a vector of attribute records and removes those elements + * whose type is unspecified, i.e. IGRAPH_ATTRIBUTE_UNSPECIFIED. */ +static igraph_error_t prune_unknown_attributes(igraph_attribute_record_list_t *attrs) { + igraph_int_t i, n; + igraph_vector_int_t to_remove; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&to_remove, 0); + + n = igraph_attribute_record_list_size(attrs); + for (i = 0; i < n; i++) { + igraph_attribute_record_t *atrec = igraph_attribute_record_list_get_ptr(attrs, i); + if (atrec->type == IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_vector_int_push_back(&to_remove, i)); + } + } + + n = igraph_vector_int_size(&to_remove); + for (i = n - 1; i >= 0; i--) { + igraph_attribute_record_list_discard(attrs, VECTOR(to_remove)[i]); + } + + igraph_vector_int_destroy(&to_remove); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Converts an integer id to an optionally prefixed string id. */ +static const char *strid(igraph_int_t id, const char *prefix) { + static char name[100]; + snprintf(name, sizeof(name) / sizeof(char) - 1, "%s%" IGRAPH_PRId, prefix, id); + return name; +} + +/* Creates an empty attribute record or if it exists, updates its type as needed. + * 'name' is the attribute name. 'type' is the current type in the GML tree, + * which will determine the igraph attribute type to use. */ +static igraph_error_t create_or_update_attribute(const char *name, + igraph_i_gml_tree_type_t type, + igraph_trie_t *attrnames, + igraph_attribute_record_list_t *attrs) { + + igraph_int_t trieid, triesize = igraph_trie_size(attrnames); + igraph_attribute_type_t desired_type; + + IGRAPH_CHECK(igraph_trie_get(attrnames, name, &trieid)); + if (trieid == triesize) { + /* new attribute */ + igraph_attribute_record_t atrec; + + if (type == IGRAPH_I_GML_TREE_INTEGER || type == IGRAPH_I_GML_TREE_REAL) { + desired_type = IGRAPH_ATTRIBUTE_NUMERIC; + } else if (type == IGRAPH_I_GML_TREE_STRING) { + desired_type = IGRAPH_ATTRIBUTE_STRING; + } else { + desired_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + } + + IGRAPH_CHECK(igraph_attribute_record_init(&atrec, name, desired_type)); + IGRAPH_FINALLY(igraph_attribute_record_destroy, &atrec); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(attrs, &atrec)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of 'atrec' taken by 'attrs' */ + } else { + /* already seen, should we update type? */ + igraph_attribute_record_t *atrec = igraph_attribute_record_list_get_ptr(attrs, trieid); + igraph_attribute_type_t existing_type = atrec->type; + if (type == IGRAPH_I_GML_TREE_STRING) { + if (existing_type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_attribute_record_set_type(atrec, IGRAPH_ATTRIBUTE_STRING)); + } + } else if (existing_type == IGRAPH_ATTRIBUTE_UNSPECIFIED) { + if (type == IGRAPH_I_GML_TREE_INTEGER || type == IGRAPH_I_GML_TREE_REAL) { + IGRAPH_CHECK(igraph_attribute_record_set_type(atrec, IGRAPH_ATTRIBUTE_NUMERIC)); + } + } + } + + return IGRAPH_SUCCESS; +} + +/* Allocates the contents of attribute records stored in 'attrs'. + * 'no_of_items' is the length of attribute vectors, i.e. no_of_nodes, + * no_of_edges, or 1 for vertex, edge and graph attributes. + * The 'kind' parameter can be "vertex", "edge" or "graph", and + * is used solely for showing better warning messages. */ +static igraph_error_t allocate_attributes( + igraph_attribute_record_list_t *attrs, igraph_int_t no_of_items, + const char *kind +) { + igraph_int_t i, n = igraph_attribute_record_list_size(attrs); + for (i = 0; i < n; i++) { + igraph_attribute_record_t *atrec = igraph_attribute_record_list_get_ptr(attrs, i); + + /* We have unspecified attribute types in the attribute record list at + * this point because we need to keep the same order in the attribute + * record list as it was in the trie that we use to look up an + * attribute record by name. However, we cannot resize unknown attributes + * so we need to take care of this */ + if (atrec->type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_resize(atrec, no_of_items)); + } else { + IGRAPH_WARNINGF("Composite %s attribute '%s' ignored in GML file.", kind, atrec->name); + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_read_graph_gml + * \brief Read a graph in GML format. + * + * GML is a simple textual format, see + * https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1 + * for details. + * + * + * Although all syntactically correct GML can be parsed, + * we implement only a subset of this format. Some attributes might be + * ignored. Here is a list of all the differences: + * \olist + * \oli Only attributes with a simple type are used: integer, real or + * string. If an attribute is composite, i.e. an array or a record, + * then it is ignored. When some values of the attribute are simple and + * some compound, the composite ones are replaced with a default value + * (NaN for numeric, "" for string). + * \oli comment fields are not ignored. They are treated as any + * other field and converted to attributes. + * \oli Top level attributes except for Version and the + * first graph attribute are completely ignored. + * \oli There is no maximum line length or maximum keyword length. + * \oli Only the \c quot, \c amp, \c apos, \c lt and \c gt character entities + * are supported. Any other entity is passed through unchanged by the reader + * after issuing a warning, and is expected to be decoded by the user. + * \oli We allow inf, -inf and nan + * (not a number) as a real number. This is case insensitive, so + * nan, NaN and NAN are equivalent. + * \endolist + * + * Please contact us if you cannot live with these + * limitations of the GML parser. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read the GML file from. + * \return Error code. + * + * Time complexity: should be proportional to the length of the file. + * + * \sa \ref igraph_read_graph_graphml() for a more modern format, + * \ref igraph_write_graph_gml() for writing GML files. + * + * \example examples/simple/gml.c + */ +igraph_error_t igraph_read_graph_gml(igraph_t *graph, FILE *instream) { + + igraph_int_t i; + igraph_int_t no_of_nodes = 0, no_of_edges = 0; + igraph_int_t node_no; + igraph_trie_t trie; + igraph_vector_int_t edges; + igraph_bool_t directed = IGRAPH_UNDIRECTED; + igraph_bool_t has_directed = false; + igraph_gml_tree_t *gtree; + igraph_int_t gidx; + igraph_trie_t vattrnames; + igraph_trie_t eattrnames; + igraph_trie_t gattrnames; + igraph_attribute_record_list_t gattrs, vattrs, eattrs; + igraph_attribute_record_list_t *attrs[3]; + igraph_int_t edgeptr = 0; + igraph_i_gml_parsedata_t context; + igraph_bool_t entity_warned = false; /* used to warn at most once about unsupported entities */ + + attrs[0] = &gattrs; attrs[1] = &vattrs; attrs[2] = &eattrs; + + IGRAPH_CHECK(igraph_i_gml_parsedata_init(&context)); + IGRAPH_FINALLY(igraph_i_gml_parsedata_destroy, &context); + + igraph_gml_yylex_init_extra(&context, &context.scanner); + + igraph_gml_yyset_in(instream, context.scanner); + + /* Protect 'context' from being destroyed before returning from yyparse() */ + IGRAPH_FINALLY_ENTER(); + int err = igraph_gml_yyparse(&context); + IGRAPH_FINALLY_EXIT(); + switch (err) { + case 0: /* success */ + break; + case 1: /* parse error */ + if (context.errmsg[0] != '\0') { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else if (context.igraph_errno != IGRAPH_SUCCESS) { + IGRAPH_ERROR("", context.igraph_errno); + } else { + IGRAPH_ERROR("Cannot read GML file.", IGRAPH_PARSEERROR); + } + break; + case 2: /* out of memory */ + IGRAPH_ERROR("Cannot read GML file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + default: /* must never reach here */ + /* Hint: This will usually be triggered if an IGRAPH_CHECK() is used in a Bison + * action instead of an IGRAPH_YY_CHECK(), resulting in an igraph errno being + * returned in place of a Bison error code. + * TODO: What if future Bison versions introduce error codes other than 0, 1 and 2? + */ + IGRAPH_FATALF("Parser returned unexpected error code (%d) when reading GML file.", err); /* LCOV_EXCL_LINE */ + } + + /* Check version, if present, integer and not '1' then ignored */ + i = igraph_gml_tree_find(context.tree, "Version", 0); + if (i >= 0 && + igraph_gml_tree_type(context.tree, i) == IGRAPH_I_GML_TREE_INTEGER && + igraph_gml_tree_get_integer(context.tree, i) != 1) { + IGRAPH_WARNINGF("Unknown GML version: %" IGRAPH_PRId ". " + "Parsing will continue assuming GML version 1, but may fail.", + igraph_gml_tree_get_integer(context.tree, i)); + } + + /* Get the graph */ + gidx = igraph_gml_tree_find(context.tree, "graph", 0); + if (gidx == -1) { + IGRAPH_ERROR("No 'graph' object in GML file.", IGRAPH_PARSEERROR); + } + if (igraph_gml_tree_type(context.tree, gidx) != + IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERRORF("Invalid type for 'graph' object in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(context.tree, gidx)); + } + gtree = igraph_gml_tree_get_tree(context.tree, gidx); + + IGRAPH_CHECK(igraph_attribute_record_list_init(&gattrs, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &gattrs); + IGRAPH_CHECK(igraph_attribute_record_list_init(&vattrs, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &vattrs); + IGRAPH_CHECK(igraph_attribute_record_list_init(&eattrs, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &eattrs); + + IGRAPH_TRIE_INIT_FINALLY(&trie, 0); + IGRAPH_TRIE_INIT_FINALLY(&vattrnames, 0); + IGRAPH_TRIE_INIT_FINALLY(&eattrnames, 0); + IGRAPH_TRIE_INIT_FINALLY(&gattrnames, 0); + + /* Now we go over all objects in the graph to + * - collect the attribute names and types + * - collect node IDs + * - set directedness + * - do some checks which the following code relies on + * + * The 'id' fields of 'node' objects are converted into strings, so that they + * can be inserted into a trie and re-encoded as consecutive integers starting + * at 0. The GML spec allows isolated nodes with no 'id' field. These get a + * generated string id of the form "n123" consisting of "n" and their count + * (i.e. ordinal position) within the GML file. + * + * We use an attribute type value of IGRAPH_ATTRIBUTE_UNSPECIFIED to mark attribute + * records which correspond to composite GML values and must therefore be removed + * before creating the graph. + */ + node_no = 0; + for (i = 0; i < igraph_gml_tree_length(gtree); i++) { + const char *name = igraph_gml_tree_name(gtree, i); + if (!strcmp(name, "node")) { + igraph_gml_tree_t *node; + igraph_bool_t hasid; + node_no++; + no_of_nodes++; + if (igraph_gml_tree_type(gtree, i) != IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERRORF("'node' is not a list in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(gtree, i)); + } + node = igraph_gml_tree_get_tree(gtree, i); + hasid = false; + for (igraph_int_t j = 0; j < igraph_gml_tree_length(node); j++) { + const char *name = igraph_gml_tree_name(node, j); + igraph_i_gml_tree_type_t type = igraph_gml_tree_type(node, j); + IGRAPH_CHECK(create_or_update_attribute(name, type, &vattrnames, &vattrs)); + /* check id */ + if (!strcmp(name, "id")) { + igraph_int_t id, trie_id; + igraph_int_t trie_size = igraph_trie_size(&trie); + if (hasid) { + /* A 'node' must not have more than one 'id' field. + * This error cannot be relaxed into a warning because all ids we find are + * added to the trie, and eventually converted to igraph vertex ids. */ + IGRAPH_ERRORF("Node has multiple 'id' fields in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, + igraph_gml_tree_line(node, j)); + } + if (type != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERRORF("Non-integer node id in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(node, j)); + } + id = igraph_gml_tree_get_integer(node, j); + IGRAPH_CHECK(igraph_trie_get(&trie, strid(id, ""), &trie_id)); + if (trie_id != trie_size) { + /* This id has already been seen in a previous node. */ + IGRAPH_ERRORF("Duplicate node id in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(node, j)); + } + hasid = true; + } + } + if (!hasid) { + /* Isolated nodes are allowed not to have an id. + * We generate an "n"-prefixed string id to be used in the trie. */ + igraph_int_t trie_id; + IGRAPH_CHECK(igraph_trie_get(&trie, strid(node_no, "n"), &trie_id)); + } + } else if (!strcmp(name, "edge")) { + igraph_gml_tree_t *edge; + igraph_bool_t has_source = false, has_target = false; + no_of_edges++; + if (igraph_gml_tree_type(gtree, i) != IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERRORF("'edge' is not a list in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(gtree, i)); + } + edge = igraph_gml_tree_get_tree(gtree, i); + for (igraph_int_t j = 0; j < igraph_gml_tree_length(edge); j++) { + const char *name = igraph_gml_tree_name(edge, j); + igraph_i_gml_tree_type_t type = igraph_gml_tree_type(edge, j); + if (!strcmp(name, "source")) { + if (has_source) { + /* An edge must not have more than one 'source' field. + * This could be relaxed to a warning, but we keep it as an error + * for consistency with the handling of duplicate node 'id' field, + * and because it indicates a serious corruption in the GML file. */ + IGRAPH_ERRORF("Duplicate 'source' in an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, + igraph_gml_tree_line(edge, j)); + } + has_source = true; + if (type != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERRORF("Non-integer 'source' for an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, + igraph_gml_tree_line(edge, j)); + } + } else if (!strcmp(name, "target")) { + if (has_target) { + /* An edge must not have more than one 'target' field. */ + IGRAPH_ERRORF("Duplicate 'target' in an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, + igraph_gml_tree_line(edge, j)); + } + has_target = true; + if (type != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERRORF("Non-integer 'target' for an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, + igraph_gml_tree_line(edge, j)); + } + } else { + IGRAPH_CHECK(create_or_update_attribute(name, type, &eattrnames, &eattrs)); + } + } /* for */ + if (!has_source) { + IGRAPH_ERRORF("No 'source' for edge in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(gtree, i)); + } + if (!has_target) { + IGRAPH_ERRORF("No 'target' for edge in GML file, line %" IGRAPH_PRId ".", IGRAPH_PARSEERROR, + igraph_gml_tree_line(gtree, i)); + } + } else if (! strcmp(name, "directed")) { + /* Set directedness of graph. */ + if (has_directed) { + /* Be tolerant of duplicate entries, but do show a warning. */ + IGRAPH_WARNINGF("Duplicate 'directed' field in 'graph', line %" IGRAPH_PRId ". " + "Ignoring previous 'directed' fields.", + igraph_gml_tree_line(gtree, i)); + } + if (igraph_gml_tree_type(gtree, i) == IGRAPH_I_GML_TREE_INTEGER) { + igraph_int_t dir = igraph_gml_tree_get_integer(gtree, i); + if (dir != 0 && dir != 1) { + IGRAPH_WARNINGF( + "Invalid value %" IGRAPH_PRId " for 'directed' attribute on line %" IGRAPH_PRId ", should be 0 or 1.", + dir, igraph_gml_tree_line(gtree, i)); + } + if (dir) { + directed = IGRAPH_DIRECTED; + } + has_directed = true; + } else { + IGRAPH_WARNINGF("Invalid type for 'directed' attribute on line %" IGRAPH_PRId ", assuming undirected.", + igraph_gml_tree_line(gtree, i)); + } + } else { + /* Add the rest of items as graph attributes. */ + igraph_i_gml_tree_type_t type = igraph_gml_tree_type(gtree, i); + IGRAPH_CHECK(create_or_update_attribute(name, type, &gattrnames, &gattrs)); + } + } + + /* At this point, all nodes must have an id (from the file or generated) stored + * in the trie. Any condition that violates this should have been caught during + * the preceding checks. */ + IGRAPH_ASSERT(igraph_trie_size(&trie) == no_of_nodes); + + /* Now we allocate the vectors and strvectors for the attributes */ + IGRAPH_CHECK(allocate_attributes(&vattrs, no_of_nodes, "vertex")); + IGRAPH_CHECK(allocate_attributes(&eattrs, no_of_edges, "edge")); + IGRAPH_CHECK(allocate_attributes(&gattrs, 1, "graph")); + + /* Add edges, edge attributes and vertex attributes */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + node_no = 0; + for (i = 0; i < igraph_gml_tree_length(gtree); i++) { + const char *name; + name = igraph_gml_tree_name(gtree, i); + if (!strcmp(name, "node")) { + igraph_gml_tree_t *node = igraph_gml_tree_get_tree(gtree, i); + igraph_int_t iidx = igraph_gml_tree_find(node, "id", 0); + igraph_int_t trie_id; + const char *sid; + node_no++; + if (iidx < 0) { + /* Isolated node with no id field, use n-prefixed generated id */ + sid = strid(node_no, "n"); + } else { + sid = strid(igraph_gml_tree_get_integer(node, iidx), ""); + } + IGRAPH_CHECK(igraph_trie_get(&trie, sid, &trie_id)); + for (igraph_int_t j = 0; j < igraph_gml_tree_length(node); j++) { + const char *aname = igraph_gml_tree_name(node, j); + igraph_attribute_record_t *atrec; + igraph_attribute_type_t type; + igraph_int_t ai; + IGRAPH_CHECK(igraph_trie_get(&vattrnames, aname, &ai)); + atrec = igraph_attribute_record_list_get_ptr(&vattrs, ai); + type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + VECTOR(*atrec->value.as_vector)[trie_id] = igraph_i_gml_toreal(node, j); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *v = atrec->value.as_strvector; + const char *value = igraph_i_gml_tostring(node, j); + if (needs_coding(value)) { + char *value_decoded; + IGRAPH_CHECK(entity_decode(value, &value_decoded, &entity_warned)); + IGRAPH_FINALLY(igraph_free, value_decoded); + IGRAPH_CHECK(igraph_strvector_set(v, trie_id, value_decoded)); + IGRAPH_FREE(value_decoded); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_strvector_set(v, trie_id, value)); + } + } else { + /* Ignored composite attribute */ + } + } + } else if (!strcmp(name, "edge")) { + igraph_gml_tree_t *edge; + igraph_int_t from, to, fromidx = 0, toidx = 0; + edge = igraph_gml_tree_get_tree(gtree, i); + for (igraph_int_t j = 0; j < igraph_gml_tree_length(edge); j++) { + const char *aname = igraph_gml_tree_name(edge, j); + if (!strcmp(aname, "source")) { + fromidx = igraph_gml_tree_find(edge, "source", 0); + } else if (!strcmp(aname, "target")) { + toidx = igraph_gml_tree_find(edge, "target", 0); + } else { + igraph_int_t edgeid = edgeptr / 2; + igraph_int_t ai; + igraph_attribute_record_t *atrec; + igraph_attribute_type_t type; + IGRAPH_CHECK(igraph_trie_get(&eattrnames, aname, &ai)); + atrec = igraph_attribute_record_list_get_ptr(&eattrs, ai); + type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + VECTOR(*atrec->value.as_vector)[edgeid] = igraph_i_gml_toreal(edge, j); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *v = atrec->value.as_strvector; + const char *value = igraph_i_gml_tostring(edge, j); + if (needs_coding(value)) { + char *value_decoded; + IGRAPH_CHECK(entity_decode(value, &value_decoded, &entity_warned)); + IGRAPH_FINALLY(igraph_free, value_decoded); + IGRAPH_CHECK(igraph_strvector_set(v, edgeid, value_decoded)); + IGRAPH_FREE(value_decoded); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_strvector_set(v, edgeid, value)); + } + } else { + /* Ignored composite attribute */ + } + } + } + from = igraph_gml_tree_get_integer(edge, fromidx); + to = igraph_gml_tree_get_integer(edge, toidx); + IGRAPH_CHECK(igraph_trie_check(&trie, strid(from, ""), &from)); + if (from < 0) { + IGRAPH_ERRORF("Unknown source node id found in an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, igraph_gml_tree_line(edge, fromidx)); + } + IGRAPH_CHECK(igraph_trie_check(&trie, strid(to, ""), &to)); + if (to < 0) { + IGRAPH_ERRORF("Unknown target node id found in an edge in GML file, line %" IGRAPH_PRId ".", + IGRAPH_PARSEERROR, igraph_gml_tree_line(edge, toidx)); + } + VECTOR(edges)[edgeptr++] = from; + VECTOR(edges)[edgeptr++] = to; + } else if (! strcmp(name, "directed")) { + /* Nothing to do for 'directed' field, already handled earlier. */ + } else { + /* Set the rest as graph attributes */ + igraph_int_t ai; + igraph_attribute_record_t *atrec; + igraph_attribute_type_t type; + IGRAPH_CHECK(igraph_trie_get(&gattrnames, name, &ai)); + atrec = igraph_attribute_record_list_get_ptr(&gattrs, ai); + type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + VECTOR(*atrec->value.as_vector)[0] = igraph_i_gml_toreal(gtree, i); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *v = atrec->value.as_strvector; + const char *value = igraph_i_gml_tostring(gtree, i); + if (needs_coding(value)) { + char *value_decoded; + IGRAPH_CHECK(entity_decode(value, &value_decoded, &entity_warned)); + IGRAPH_FINALLY(igraph_free, value_decoded); + IGRAPH_CHECK(igraph_strvector_set(v, 0, value_decoded)); + IGRAPH_FREE(value_decoded); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_strvector_set(v, 0, value)); + } + } else { + /* Ignored composite attribute */ + } + } + } + + /* Remove composite attributes that we cannot represent in igraph */ + IGRAPH_CHECK(prune_unknown_attributes(&vattrs)); + IGRAPH_CHECK(prune_unknown_attributes(&eattrs)); + IGRAPH_CHECK(prune_unknown_attributes(&gattrs)); + + igraph_trie_destroy(&trie); + igraph_trie_destroy(&gattrnames); + igraph_trie_destroy(&vattrnames); + igraph_trie_destroy(&eattrnames); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_empty_attrs(graph, 0, directed, &gattrs)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &eattrs)); + IGRAPH_FINALLY_CLEAN(1); /* do not destroy 'graph', just pop it from the stack */ + + igraph_vector_int_destroy(&edges); + igraph_attribute_record_list_destroy(&eattrs); + igraph_attribute_record_list_destroy(&vattrs); + igraph_attribute_record_list_destroy(&gattrs); + igraph_i_gml_parsedata_destroy(&context); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_convert_to_key(const char *orig, char **key) { + char strno[50]; + size_t i, len = strlen(orig), newlen = 0, plen = 0; + + /* do we need a prefix? */ + if (len == 0 || !isalpha(orig[0])) { + snprintf(strno, sizeof(strno) - 1, "igraph"); + plen = newlen = strlen(strno); + } + for (i = 0; i < len; i++) { + if (isalnum(orig[i])) { + newlen++; + } + } + *key = IGRAPH_CALLOC(newlen + 1, char); + IGRAPH_CHECK_OOM(*key, "Writing GML format failed."); + memcpy(*key, strno, plen * sizeof(char)); + for (i = 0; i < len; i++) { + if (isalnum(orig[i])) { + (*key)[plen++] = orig[i]; + } + } + (*key)[newlen] = '\0'; + + return IGRAPH_SUCCESS; +} + +/* Checks if a vector is free of duplicates. Since NaN == NaN is false, duplicate NaN values + * will not be detected. */ +static igraph_error_t igraph_i_vector_is_duplicate_free(const igraph_vector_t *v, igraph_bool_t *res) { + igraph_vector_t u; + igraph_int_t n = igraph_vector_size(v); + + if (n < 2) { + *res = true; + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_vector_init_copy(&u, v)); + IGRAPH_FINALLY(igraph_vector_destroy, &u); + igraph_vector_sort(&u); + + *res = true; + for (igraph_int_t i=1; i < n; i++) { + if (VECTOR(u)[i-1] == VECTOR(u)[i]) { + *res = false; + break; + } + } + + igraph_vector_destroy(&u); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#define CHECK(cmd) do { int ret=cmd; if (ret<0) IGRAPH_ERROR("Writing GML format failed.", IGRAPH_EFILE); } while (0) + +/** + * \function igraph_write_graph_gml + * \brief Write the graph to a stream in GML format. + * + * GML is a quite general textual format, see + * https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297%26L=1 + * for details. + * + * + * The graph, vertex and edges attributes are written to the + * file as well, if they are numeric or string. Boolean attributes are converted + * to numeric, with 0 and 1 used for false and true, respectively. + * NaN values of numeric attributes are skipped, as NaN is not part of the GML + * specification and other software may not be able to read files containing them. + * This is consistent with \ref igraph_read_graph_gml(), which produces NaN + * when an attribute value is missing. In contrast with NaN, infinite values + * are retained. Ensure that none of the numeric attributes values are infinite + * to produce a conformant GML file that can be read by other software. + * + * + * As igraph is more forgiving about attribute names, it might + * be necessary to simplify the them before writing to the GML file. + * This way we'll have a syntactically correct GML file. The following + * simple procedure is performed on each attribute name: first the alphanumeric + * characters are extracted, the others are ignored. Then if the first character + * is not a letter then the attribute name is prefixed with igraph. + * Note that this might result identical names for two attributes, igraph + * does not check this. + * + * + * The id vertex attribute is treated specially. + * If the id argument is not \c NULL then it should be a numeric + * vector with the vertex IDs and the id vertex attribute is + * ignored (if there is one). If id is \c NULL and there is a + * numeric id vertex attribute, it will be used instead. If ids + * are not specified in either way then the regular igraph vertex IDs are used. + * If some of the supplied id values are invalid (non-integer or NaN), all supplied + * id are ignored and igraph vertex IDs are used instead. + * + * + * Note that whichever way vertex IDs are specified, their uniqueness is not checked. + * + * + * If the graph has edge attributes that become source + * or target after encoding, or the graph has an attribute that becomes + * directed, they will be ignored with a warning. GML uses these attributes + * to specify the edge endpoints, and the graph directedness, so we cannot write them + * to the file. Rename them before calling this function if you want to preserve them. + * + * \param graph The graph to write to the stream. + * \param outstream The stream to write the file to. + * \param options Set of |-combinable boolean flags for writing the GML file. + * \clist + * \cli 0 + * All options turned off. + * \cli IGRAPH_WRITE_GML_DEFAULT_SW + * Default options, currently equivalent to 0. May change in future versions. + * \cli IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW + * Do not encode any other characters than " as entities. Specifically, this + * option prevents the encoding of &. Useful when re-exporting a graph + * that was read from a GML file in which igraph could not interpret all entities, + * and thus passed them through without decoding. + * \endclist + * \param id Either NULL or a numeric vector with the vertex IDs. + * See details above. + * \param creator An optional string to write to the stream in the creator line. + * If \c NULL, the igraph version with the current date and time is added. + * If "", the creator line is omitted. Otherwise, the + * supplied string is used verbatim. + * \return Error code. + * + * Time complexity: should be proportional to the number of characters written + * to the file. + * + * \sa \ref igraph_read_graph_gml() for reading GML files, + * \ref igraph_read_graph_graphml() for a more modern format. + * + * \example examples/simple/gml.c + */ + +igraph_error_t igraph_write_graph_gml(const igraph_t *graph, FILE *outstream, + igraph_write_gml_sw_t options, + const igraph_vector_t *id, const char *creator) { + igraph_strvector_t gnames, vnames, enames; /* attribute names */ + igraph_vector_int_t gtypes, vtypes, etypes; /* attribute types */ + igraph_int_t gattr_no, vattr_no, eattr_no; /* attribute counts */ + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + igraph_int_t i; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + /* Each element is a bit field used to prevent showing more + * than one warning for each vertex or edge attribute. */ + igraph_vector_int_t warning_shown; + + igraph_vector_t v_myid; + const igraph_vector_t *myid = id; + + /* Creator line */ + if (creator == NULL) { + time_t curtime = time(0); + char *timestr = ctime(&curtime); + timestr[strlen(timestr) - 1] = '\0'; /* nicely remove \n */ + + CHECK(fprintf(outstream, + "Creator \"igraph version %s %s\"\n", + IGRAPH_VERSION, timestr)); + } else if (creator[0] == '\0') { + /* creator == "", omit Creator line */ + } else { + if (needs_coding(creator)) { + char *d; + IGRAPH_CHECK(entity_encode(creator, &d, IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW & options)); + IGRAPH_FINALLY(igraph_free, d); + CHECK(fprintf(outstream, + "Creator \"%s\"\n", + creator)); + IGRAPH_FREE(d); + IGRAPH_FINALLY_CLEAN(1); + } else { + CHECK(fprintf(outstream, + "Creator \"%s\"\n", + creator)); + } + } + + /* Version line */ + CHECK(fprintf(outstream, "Version 1\n")); + + /* The graph */ + CHECK(fprintf(outstream, "graph\n[\n")); + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&etypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes)); + gattr_no = igraph_vector_int_size(>ypes); + vattr_no = igraph_vector_int_size(&vtypes); + eattr_no = igraph_vector_int_size(&etypes); + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 1); + + /* Check whether there is an 'id' node attribute if the supplied is 0 */ + if (!id) { + igraph_bool_t found = false; + for (i = 0; i < igraph_vector_int_size(&vtypes); i++) { + const char *n = igraph_strvector_get(&vnames, i); + if (!strcmp(n, "id") && VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + found = true; break; + } + } + if (found) { + IGRAPH_VECTOR_INIT_FINALLY(&v_myid, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, "id", + igraph_vss_all(), + &v_myid)); + myid = &v_myid; + } + } + + /* Scan id vector for invalid values. If any are found, all ids are ignored. + * Invalid values may occur as a result of reading a GML file in which some + * nodes did not have an id, or by adding new vertices to a graph with an "id" + * attribute. In this case, the "id" attribute will contain NaN values. + */ + if (myid) { + if (igraph_vector_size(myid) != no_of_nodes) { + IGRAPH_ERROR("Size of id vector must match vertex count.", IGRAPH_EINVAL); + } + for (i = 0; i < no_of_nodes; ++i) { + igraph_real_t val = VECTOR(*myid)[i]; + igraph_real_t trunc_val = trunc(val); + if (! (val == trunc_val && igraph_i_is_real_representable_as_integer(trunc_val))) { + IGRAPH_WARNINGF("%g is not a valid integer id for GML files, ignoring all supplied ids.", val); + if (myid == &v_myid) { + igraph_vector_destroy(&v_myid); + IGRAPH_FINALLY_CLEAN(1); + } + myid = NULL; + break; + } + } + } + + if (myid) { + igraph_bool_t duplicate_free; + IGRAPH_CHECK(igraph_i_vector_is_duplicate_free(myid, &duplicate_free)); + if (! duplicate_free) { + IGRAPH_WARNING("Duplicate id values found, ignoring supplies ids."); + if (myid == &v_myid) { + igraph_vector_destroy(&v_myid); + IGRAPH_FINALLY_CLEAN(1); + } + myid = NULL; + } + } + + /* directedness */ + CHECK(fprintf(outstream, " directed %i\n", igraph_is_directed(graph) ? 1 : 0)); + + /* Graph attributes first */ + for (i = 0; i < gattr_no; i++) { + const char *name; + char *newname; + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + IGRAPH_FINALLY(igraph_free, newname); + if (!strcmp(newname, "directed")|| !strcmp(newname, "edge") || !strcmp(newname, "node")) { + IGRAPH_WARNINGF("The graph attribute '%s' was ignored while writing GML format.", name); + } else { + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + /* Treat NaN as missing, skip writing it. GML does not officially support NaN. */ + if (! isnan(VECTOR(numv)[0])) { + if (! isfinite(VECTOR(numv)[0])) { + IGRAPH_WARNINGF("Infinite value in numeric graph attribute '%s'. " + "Produced GML file will not be conformant.", name); + } + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + s = igraph_strvector_get(&strv, 0); + if (needs_coding(s)) { + char *d; + IGRAPH_CHECK(entity_encode(s, &d, IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW & options)); + IGRAPH_FINALLY(igraph_free, d); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, d)); + IGRAPH_FREE(d); + IGRAPH_FINALLY_CLEAN(1); + } else { + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean graph attribute was converted to numeric."); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean graph attribute ignored."); + } + } + IGRAPH_FREE(newname); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Macros used to work with the bit fiels in 'warning_shown', + * and avoid showing warnings more than once for each attribute. */ +#define GETBIT(k, i) ((k) & (1 << i)) +#define SETBIT(k, i) ((k) |= (1 << i)) +#define WARN_ONCE(attrno, bit, warn) \ + do { \ + igraph_int_t *p = &VECTOR(warning_shown)[attrno]; \ + if (! GETBIT(*p, bit)) { \ + warn; \ + SETBIT(*p, bit); \ + } \ + } while (0) + + + /* Now come the vertices */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&warning_shown, vattr_no); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t j; + CHECK(fprintf(outstream, " node\n [\n")); + /* id */ + CHECK(fprintf(outstream, " id %" IGRAPH_PRId "\n", myid ? (igraph_int_t)VECTOR(*myid)[i] : i)); + /* other attributes */ + for (j = 0; j < vattr_no; j++) { + igraph_attribute_type_t type = (igraph_attribute_type_t) VECTOR(vtypes)[j]; + const char *name; + char *newname; + name = igraph_strvector_get(&vnames, j); + if (!strcmp(name, "id")) { + /* No warning, the presence of this attribute is expected, and is handled specially. */ + continue; + } + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + IGRAPH_FINALLY(igraph_free, newname); + if (!strcmp(newname, "id")) { + /* In case an attribute name would conflict with 'id' only after encoding. */ + WARN_ONCE(j, 0, + IGRAPH_WARNINGF("The vertex attribute '%s' was ignored while writing GML format.", name)); + } else { + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, + igraph_vss_1(i), &numv)); + /* Treat NaN as missing, skip writing it. GML does not officially support NaN. */ + if (! isnan(VECTOR(numv)[0])) { + if (! isfinite(VECTOR(numv)[0])) { + WARN_ONCE(j, 3, + IGRAPH_WARNINGF("Infinite value in numeric vertex attribute '%s'. " + "Produced GML file will not be conformant.", name)); + } + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, + igraph_vss_1(i), &strv)); + s = igraph_strvector_get(&strv, 0); + if (needs_coding(s)) { + char *d; + IGRAPH_CHECK(entity_encode(s, &d, IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW & options)); + IGRAPH_FINALLY(igraph_free, d); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, d)); + IGRAPH_FREE(d); + IGRAPH_FINALLY_CLEAN(1); + } else { + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, + igraph_vss_1(i), &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + WARN_ONCE(j, 1, + IGRAPH_WARNINGF("The boolean vertex attribute '%s' was converted to numeric.", name)); + } else { + WARN_ONCE(j, 2, + IGRAPH_WARNINGF("The non-numeric, non-string, non-boolean vertex attribute '%s' was ignored.", name)); + } + } + IGRAPH_FREE(newname); + IGRAPH_FINALLY_CLEAN(1); + } + CHECK(fprintf(outstream, " ]\n")); + } + + /* The edges too */ + IGRAPH_CHECK(igraph_vector_int_resize(&warning_shown, eattr_no)); + igraph_vector_int_null(&warning_shown); + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_int_t j; + CHECK(fprintf(outstream, " edge\n [\n")); + /* source and target */ + CHECK(fprintf(outstream, " source %" IGRAPH_PRId "\n", + myid ? (igraph_int_t)VECTOR(*myid)[from] : from)); + CHECK(fprintf(outstream, " target %" IGRAPH_PRId "\n", + myid ? (igraph_int_t)VECTOR(*myid)[to] : to)); + + /* other attributes */ + for (j = 0; j < eattr_no; j++) { + igraph_attribute_type_t type = (igraph_attribute_type_t) VECTOR(etypes)[j]; + const char *name; + char *newname; + name = igraph_strvector_get(&enames, j); + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + IGRAPH_FINALLY(igraph_free, newname); + if (!strcmp(newname, "source") || !strcmp(newname, "target")) { + WARN_ONCE(j, 0, + IGRAPH_WARNINGF("The edge attribute '%s' was ignored while writing GML format.", name)); + } else { + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, name, + igraph_ess_1(i), &numv)); + /* Treat NaN as missing, skip writing it. GML does not officially support NaN. */ + if (! isnan(VECTOR(numv)[0])) { + if (! isfinite(VECTOR(numv)[0])) { + WARN_ONCE(j, 3, + IGRAPH_WARNINGF("Infinite value in numeric edge attribute '%s'. " + "Produced GML file will not be conformant.", name)); + } + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, name, + igraph_ess_1(i), &strv)); + s = igraph_strvector_get(&strv, 0); + if (needs_coding(s)) { + char *d; + IGRAPH_CHECK(entity_encode(s, &d, IGRAPH_WRITE_GML_ENCODE_ONLY_QUOT_SW & options)); + IGRAPH_FINALLY(igraph_free, d); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, d)); + IGRAPH_FREE(d); + IGRAPH_FINALLY_CLEAN(1); + } else { + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, name, + igraph_ess_1(i), &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + WARN_ONCE(j, 1, + IGRAPH_WARNINGF("The boolean edge attribute '%s' was converted to numeric.", name)); + } else { + WARN_ONCE(j, 2, + IGRAPH_WARNINGF("The non-numeric, non-string, non-boolean edge attribute '%s' was ignored.", name)); + } + } + IGRAPH_FREE(newname); + IGRAPH_FINALLY_CLEAN(1); + } + CHECK(fprintf(outstream, " ]\n")); + } + + CHECK(fprintf(outstream, "]\n")); + +#undef GETBIT +#undef SETBIT +#undef WARN_ONCE + + if (&v_myid == myid) { + igraph_vector_destroy(&v_myid); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&warning_shown); + igraph_vector_bool_destroy(&boolv); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + igraph_vector_int_destroy(&etypes); + igraph_vector_int_destroy(&vtypes); + igraph_vector_int_destroy(>ypes); + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + IGRAPH_FINALLY_CLEAN(10); + + return IGRAPH_SUCCESS; +} + +#undef CHECK diff --git a/src/io/graphdb.c b/src/io/graphdb.c new file mode 100644 index 0000000..63d52a5 --- /dev/null +++ b/src/io/graphdb.c @@ -0,0 +1,146 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_constructors.h" +#include "core/interruption.h" + +/* Read a little-endian encoded 16-bit unsigned word. + * Returns negative value on failure. */ +static int igraph_i_read_graph_graphdb_getword(FILE *instream) { + int b1, b2; + unsigned char c1, c2; + b1 = fgetc(instream); + b2 = fgetc(instream); + if (b1 != EOF && b2 != EOF) { + c1 = (unsigned char) b1; c2 = (unsigned char) b2; + return c1 | (c2 << 8); + } else { + return -1; + } +} + +/* Determine whether the read failed due to an input error or end-of-file condition. + * Must only be called after a read failure, always returns a non-success error code. */ +static igraph_error_t handle_input_error(FILE *instream) { + if (feof(instream)) { + IGRAPH_ERROR("Unexpected end of file, truncated graphdb file.", IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read from file.", IGRAPH_EFILE); + } +} + +/** + * \function igraph_read_graph_graphdb + * \brief Read a graph in the binary graph database format. + * + * This is a binary format, used in the ARG Graph Database + * for isomorphism testing. For more information, see + * https://mivia.unisa.it/datasets/graph-database/arg-database/ + * + * + * From the graph database homepage: + * + * + * \blockquote + * The graphs are stored in a compact binary format, one graph per + * file. The file is composed of 16 bit words, which are represented + * using the so-called little-endian convention, i.e. the least + * significant byte of the word is stored first. + * + * + * Then, for each node, the file contains the list of edges coming + * out of the node itself. The list is represented by a word encoding + * its length, followed by a word for each edge, representing the + * destination node of the edge. Node numeration is 0-based, so the + * first node of the graph has index 0. \endblockquote + * + * + * As of igraph 0.10, only unlabelled graphs are implemented. + * + * + * + * References: + * + * + * + * M. De Santo, P. Foggia, C. Sansone, and M. Vento: + * A large database of graphs and its use for benchmarking graph isomorphism algorithms. + * Pattern Recognition Letters, 24(8), 1067-1079 (2003). + * https://doi.org/10.1016/S0167-8655(02)00253-2 + * + * + * + * MIVIA ARG Dataset, + * https://zenodo.org/records/11204020, + * https://mivia.unisa.it/datasets/graph-database/arg-database/ + * + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read from. It should be opened + * in binary mode. + * \param directed Whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the + * number of edges. + * + * \example examples/simple/igraph_read_graph_graphdb.c + */ + +igraph_error_t igraph_read_graph_graphdb(igraph_t *graph, FILE *instream, + igraph_bool_t directed) { + + const igraph_int_t nodes = igraph_i_read_graph_graphdb_getword(instream); + if (nodes < 0) { + IGRAPH_CHECK(handle_input_error(instream)); + } + + igraph_vector_int_t edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 100); + igraph_vector_int_clear(&edges); + + for (igraph_int_t i = 0; i < nodes; i++) { + igraph_int_t len = igraph_i_read_graph_graphdb_getword(instream); + if (len < 0) { + IGRAPH_CHECK(handle_input_error(instream)); + } + for (igraph_int_t j = 0; j < len; j++) { + igraph_int_t to = igraph_i_read_graph_graphdb_getword(instream); + if (to < 0) { + IGRAPH_CHECK(handle_input_error(instream)); + } + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + if (fgetc(instream) != EOF) { + IGRAPH_ERROR("Extra bytes at end of graphdb file.", IGRAPH_PARSEERROR); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/io/graphml.c b/src/io/graphml.c new file mode 100644 index 0000000..05630b2 --- /dev/null +++ b/src/io/graphml.c @@ -0,0 +1,2029 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/interruption.h" +#include "core/trie.h" +#include "graph/attributes.h" +#include "internal/hacks.h" /* strcasecmp & strdup */ +#include "io/parse_utils.h" + +#include "config.h" /* HAVE_LIBXML */ + +#include /* isdigit */ +#include /* isnan */ +#include +#include /* va_start & co */ + +#define GRAPHML_NAMESPACE_URI "http://graphml.graphdrawing.org/xmlns" + +#if HAVE_LIBXML == 1 +#include +#include + +static xmlEntity blankEntity = { +#ifndef XML_WITHOUT_CORBA + NULL, /* _private */ +#endif + XML_ENTITY_DECL, /* type */ + NULL, /* name */ + NULL, /* children */ + NULL, /* last */ + NULL, /* parent */ + NULL, /* next */ + NULL, /* prev */ + NULL, /* doc */ + + NULL, /* orig */ + NULL, /* content */ + 0, /* length */ + XML_EXTERNAL_GENERAL_PARSED_ENTITY, /* etype */ + NULL, /* ExternalID */ + NULL, /* SystemID */ + + NULL, /* nexte */ + NULL, /* URI */ + 0, /* owner */ +#if LIBXML_VERSION < 21100 /* Versions < 2.11.0: */ + 1 /* checked */ +#else /* Starting with verson 2.11.0: */ + 1, /* flags */ + 0 /* expandedSize */ +#endif +}; + +#define toXmlChar(a) (BAD_CAST(a)) +#define fromXmlChar(a) ((char *)(a)) /* not the most elegant way... */ + +#define GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code) \ + do { \ + if (state->successful) { \ + igraph_i_graphml_sax_handler_error(state, msg); \ + } \ + } while (0) +#define RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code) \ + do { \ + GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code); \ + return; \ + } while (0) + +typedef struct igraph_i_graphml_attribute_record_t { + char *id; /* GraphML id */ + enum { I_GRAPHML_BOOLEAN, I_GRAPHML_INTEGER, I_GRAPHML_LONG, + I_GRAPHML_FLOAT, I_GRAPHML_DOUBLE, I_GRAPHML_STRING, + I_GRAPHML_UNKNOWN_TYPE + } type; /* GraphML type */ + igraph_attribute_record_t record; + igraph_bool_t record_disowned; /* true if ownership of the record was taken over */ +} igraph_i_graphml_attribute_record_t; + +typedef enum { + START, INSIDE_GRAPHML, INSIDE_GRAPH, INSIDE_NODE, INSIDE_EDGE, + INSIDE_KEY, INSIDE_DEFAULT, INSIDE_DATA, FINISH, UNKNOWN, ERROR +} igraph_i_graphml_parser_state_index_t; + +struct igraph_i_graphml_parser_state { + igraph_i_graphml_parser_state_index_t st; + igraph_t *g; + igraph_trie_t node_trie; + igraph_strvector_t edgeids; + igraph_vector_int_t edgelist; + igraph_vector_int_t prev_state_stack; + unsigned int unknown_depth; + igraph_int_t index; + igraph_bool_t successful; + igraph_bool_t edges_directed; + igraph_trie_t v_attr_ids; + igraph_vector_ptr_t v_attrs; + igraph_trie_t e_attr_ids; + igraph_vector_ptr_t e_attrs; + igraph_trie_t g_attr_ids; + igraph_vector_ptr_t g_attrs; + igraph_i_graphml_attribute_record_t* current_attr_record; + xmlChar *data_key; + igraph_attribute_elemtype_t data_type; + char *error_message; + igraph_vector_char_t data_char; + igraph_int_t act_node; + igraph_bool_t ignore_namespaces; +}; + +static void igraph_i_report_unhandled_attribute_target(const char* target, + const char* file, int line) { + igraph_warningf("Attribute target '%s' is not handled; ignoring corresponding " + "attribute specifications.", file, line, target); +} + +static igraph_error_t igraph_i_graphml_parse_numeric( + const char* char_data, igraph_real_t* result, igraph_real_t default_value +) { + const char* trimmed; + size_t trimmed_length; + + if (char_data == 0) { + *result = default_value; + return IGRAPH_SUCCESS; + } + + igraph_i_trim_whitespace(char_data, strlen(char_data), &trimmed, &trimmed_length); + if (trimmed_length > 0) { + IGRAPH_CHECK(igraph_i_parse_real(trimmed, trimmed_length, result)); + } else { + *result = default_value; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_graphml_parse_boolean( + const char* char_data, igraph_bool_t* result, igraph_bool_t default_value +) { + igraph_int_t value; + const char* trimmed; + size_t trimmed_length; + + if (char_data == 0) { + *result = default_value; + return IGRAPH_SUCCESS; + } + + igraph_i_trim_whitespace(char_data, strlen(char_data), &trimmed, &trimmed_length); + + if (trimmed_length == 4 && !strncasecmp(trimmed, "true", trimmed_length)) { + *result = 1; + return IGRAPH_SUCCESS; + } + + if (trimmed_length == 3 && !strncasecmp(trimmed, "yes", trimmed_length)) { + *result = 1; + return IGRAPH_SUCCESS; + } + + if (trimmed_length == 5 && !strncasecmp(trimmed, "false", trimmed_length)) { + *result = 0; + return IGRAPH_SUCCESS; + } + + if (trimmed_length == 2 && !strncasecmp(trimmed, "no", trimmed_length)) { + *result = 0; + return IGRAPH_SUCCESS; + } + + if (trimmed_length > 0) { + if (isdigit(trimmed[0])) { + IGRAPH_CHECK(igraph_i_parse_integer(trimmed, trimmed_length, &value)); + } else { + IGRAPH_ERRORF("Cannot parse '%.*s' as Boolean value.", IGRAPH_PARSEERROR, + (int) trimmed_length, trimmed); + } + } else { + *result = default_value; + return IGRAPH_SUCCESS; + } + + *result = value != 0; + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphml_attribute_record_destroy(igraph_i_graphml_attribute_record_t* rec) { + if (!rec->record_disowned) { + igraph_attribute_record_destroy(&rec->record); + } + + if (rec->id != NULL) { + igraph_free(rec->id); + rec->id = NULL; + } + + memset(rec, 0, sizeof(igraph_i_graphml_attribute_record_t)); +} + +static igraph_error_t igraph_i_graphml_parser_state_init(struct igraph_i_graphml_parser_state* state, igraph_t* graph, igraph_int_t index) { + memset(state, 0, sizeof(struct igraph_i_graphml_parser_state)); + + state->g = graph; + state->index = index < 0 ? 0 : index; + state->successful = 1; + state->error_message = NULL; + + IGRAPH_CHECK(igraph_vector_int_init(&state->prev_state_stack, 0)); + IGRAPH_CHECK(igraph_vector_int_reserve(&state->prev_state_stack, 32)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &state->prev_state_stack); + + IGRAPH_CHECK(igraph_vector_ptr_init(&state->v_attrs, 0)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->v_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &state->v_attrs); + + IGRAPH_CHECK(igraph_vector_ptr_init(&state->e_attrs, 0)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->e_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &state->e_attrs); + + IGRAPH_CHECK(igraph_vector_ptr_init(&state->g_attrs, 0)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->g_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &state->g_attrs); + + IGRAPH_CHECK(igraph_vector_int_init(&state->edgelist, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &state->edgelist); + + IGRAPH_CHECK(igraph_trie_init(&state->node_trie, 1)); + IGRAPH_FINALLY(igraph_trie_destroy, &state->node_trie); + + IGRAPH_CHECK(igraph_strvector_init(&state->edgeids, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, &state->edgeids); + + IGRAPH_CHECK(igraph_trie_init(&state->v_attr_ids, 0)); + IGRAPH_FINALLY(igraph_trie_destroy, &state->v_attr_ids); + + IGRAPH_CHECK(igraph_trie_init(&state->e_attr_ids, 0)); + IGRAPH_FINALLY(igraph_trie_destroy, &state->e_attr_ids); + + IGRAPH_CHECK(igraph_trie_init(&state->g_attr_ids, 0)); + IGRAPH_FINALLY(igraph_trie_destroy, &state->g_attr_ids); + + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&state->data_char, 0); + + IGRAPH_FINALLY_CLEAN(11); + + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphml_parser_state_destroy(struct igraph_i_graphml_parser_state* state) { + igraph_trie_destroy(&state->node_trie); + igraph_strvector_destroy(&state->edgeids); + igraph_trie_destroy(&state->v_attr_ids); + igraph_trie_destroy(&state->e_attr_ids); + igraph_trie_destroy(&state->g_attr_ids); + igraph_vector_int_destroy(&state->edgelist); + igraph_vector_int_destroy(&state->prev_state_stack); + + igraph_vector_ptr_destroy_all(&state->v_attrs); + igraph_vector_ptr_destroy_all(&state->e_attrs); + igraph_vector_ptr_destroy_all(&state->g_attrs); + + igraph_vector_char_destroy(&state->data_char); + + if (state->data_key) { + xmlFree((void *) state->data_key); + state->data_key = NULL; + } + + if (state->error_message) { + IGRAPH_FREE(state->error_message); + } +} + +static void igraph_i_graphml_parser_state_set_error_from_varargs( + struct igraph_i_graphml_parser_state *state, const char* msg, va_list args +) { + const size_t max_error_message_length = 4096; + + state->successful = 0; + state->st = ERROR; + + if (state->error_message == 0) { + /* ownership of state->error_message passed on immediately to + * state so the state destructor is responsible for freeing it */ + state->error_message = IGRAPH_CALLOC(max_error_message_length, char); + } + + /* we need to guard against state->error_message == 0, which may happen + * if the memory allocation for the error message itself failed */ + if (state->error_message != 0) { + vsnprintf(state->error_message, max_error_message_length, msg, args); + } +} + +static void igraph_i_graphml_parser_state_set_error_from_xmlerror( + struct igraph_i_graphml_parser_state *state, const xmlError *error +) { + const size_t max_error_message_length = 4096; + + state->successful = 0; + state->st = ERROR; + + if (state->error_message == 0) { + /* ownership of state->error_message passed on immediately to + * state so the state destructor is responsible for freeing it */ + state->error_message = IGRAPH_CALLOC(max_error_message_length, char); + } + + /* we need to guard against state->error_message == 0, which may happen + * if the memory allocation for the error message itself failed */ + if (state->error_message != 0) { + snprintf(state->error_message, max_error_message_length, "Line %d: %s", + error->line, error->message); + } +} + +static void igraph_i_graphml_sax_handler_error(void *state0, const char* msg, ...) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + va_list args; + va_start(args, msg); + igraph_i_graphml_parser_state_set_error_from_varargs(state, msg, args); + va_end(args); +} + +static xmlEntityPtr igraph_i_graphml_sax_handler_get_entity(void *state0, + const xmlChar* name) { + xmlEntityPtr predef = xmlGetPredefinedEntity(name); + const char* entityName; + + IGRAPH_UNUSED(state0); + if (predef != NULL) { + return predef; + } + + entityName = fromXmlChar(name); + IGRAPH_WARNINGF("Unknown XML entity found: '%s'.", entityName); + + return &blankEntity; +} + +static igraph_error_t igraph_i_graphml_handle_unknown_start_tag(struct igraph_i_graphml_parser_state *state) { + if (state->st != UNKNOWN) { + IGRAPH_CHECK(igraph_vector_int_push_back(&state->prev_state_stack, state->st)); + state->st = UNKNOWN; + state->unknown_depth = 1; + } else { + state->unknown_depth++; + } + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphml_sax_handler_start_document(void *state0) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + + state->st = START; + state->successful = 1; + state->edges_directed = 0; + state->data_key = NULL; + state->unknown_depth = 0; + state->ignore_namespaces = 0; +} + +static igraph_error_t igraph_i_graphml_parser_state_finish_parsing(struct igraph_i_graphml_parser_state *state) { + igraph_int_t i; + const char *idstr = "id"; + igraph_bool_t already_has_vertex_id = false, already_has_edge_id = false; + igraph_attribute_record_list_t vattr, eattr, gattr; + igraph_attribute_record_t *idrec; + + IGRAPH_ASSERT(state->successful); + + /* check that we have found and parsed the graph the user is interested in */ + IGRAPH_ASSERT(state->index < 0); + + IGRAPH_CHECK(igraph_attribute_record_list_init(&vattr, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &vattr); + IGRAPH_CHECK(igraph_attribute_record_list_init(&eattr, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &eattr); + IGRAPH_CHECK(igraph_attribute_record_list_init(&gattr, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &gattr); + + /* Add default attribute values for vertices where needed */ + for (i = 0; i < igraph_vector_ptr_size(&state->v_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->v_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + + /* Check that the name of the vertex attribute is not 'id'. + * If it is then we cannot add the complementary 'id' attribute. */ + if (!strcmp(rec->name, idstr)) { + already_has_vertex_id = 1; + } + + if (rec->type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_resize(rec, igraph_trie_size(&state->node_trie))); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(&vattr, rec)); + graphmlrec->record_disowned = true; + } + } + + /* Add vertex ID attribute if needed */ + if (!already_has_vertex_id) { + IGRAPH_CHECK(igraph_attribute_record_list_push_back_new(&vattr, &idrec)); + IGRAPH_CHECK(igraph_attribute_record_set_name(idrec, idstr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(idrec, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_CHECK(igraph_strvector_update(idrec->value.as_strvector, igraph_i_trie_borrow_keys(&state->node_trie))); + } else { + IGRAPH_WARNING("Could not add vertex ids, there is already an 'id' vertex attribute."); + } + + /* Add default attribute values for edges where needed */ + for (i = 0; i < igraph_vector_ptr_size(&state->e_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->e_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + + if (!strcmp(rec->name, idstr)) { + already_has_edge_id = 1; + } + + if (rec->type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_resize(rec, igraph_vector_int_size(&state->edgelist) / 2)); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(&eattr, rec)); + graphmlrec->record_disowned = true; + } + } + + /* Add edge ID attribute if needed */ + if (igraph_strvector_size(&state->edgeids) != 0) { + if (!already_has_edge_id) { + IGRAPH_CHECK(igraph_attribute_record_list_push_back_new(&eattr, &idrec)); + IGRAPH_CHECK(igraph_attribute_record_set_name(idrec, idstr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(idrec, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_CHECK(igraph_strvector_update(idrec->value.as_strvector, igraph_i_trie_borrow_keys(&state->node_trie))); + IGRAPH_CHECK(igraph_attribute_record_resize(idrec, igraph_vector_int_size(&state->edgelist) / 2)); + } else { + IGRAPH_WARNING("Could not add edge ids, there is already an 'id' edge attribute."); + } + } + + /* Add default graph attribute values where needed */ + for (i = 0; i < igraph_vector_ptr_size(&state->g_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->g_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + + if (rec->type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_resize(rec, 1)); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(&gattr, rec)); + graphmlrec->record_disowned = true; + } + } + + IGRAPH_CHECK(igraph_empty_attrs(state->g, 0, state->edges_directed, &gattr)); + IGRAPH_FINALLY(igraph_destroy, state->g); /* because the next two lines may fail as well */ + IGRAPH_CHECK(igraph_add_vertices(state->g, igraph_trie_size(&state->node_trie), &vattr)); + IGRAPH_CHECK(igraph_add_edges(state->g, &state->edgelist, &eattr)); + IGRAPH_FINALLY_CLEAN(1); /* graph construction completed successfully */ + + igraph_attribute_record_list_destroy(&vattr); + igraph_attribute_record_list_destroy(&eattr); + igraph_attribute_record_list_destroy(&gattr); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* See https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-parser.html#startElementNsSAX2Func */ +#define XML_ATTR_LOCALNAME(it) it[0] +#define XML_ATTR_PREFIX(it) it[1] +#define XML_ATTR_URI(it) it[2] +#define XML_ATTR_VALUE_START(it) it[3] +#define XML_ATTR_VALUE_END(it) it[4] +#define XML_ATTR_VALUE_LENGTH(it) (size_t)(it[4] - it[3]) +#define XML_ATTR_VALUE(it) it[3], (int)(it[4] - it[3]) /* for use in strnxxx()-style functions that take a char * and a length */ +#define XML_ATTR_VALUE_PF(it) (int)(it[4] - it[3]), it[3] /* for use in printf-style function with "%.*s" */ + +static igraph_error_t safely_convert_xml_attribute_to_string(xmlChar** it, char** str) { + /* This is quite convoluted but we need to go safely from xmlChar*-world + * to char*-world */ + xmlChar *xml_str; + char *c_str; + + xml_str = xmlStrndup(XML_ATTR_VALUE(it)); + IGRAPH_CHECK_OOM(xml_str, "Insufficient memory to duplicate attribute value."); + IGRAPH_FINALLY(xmlFree, xml_str); + + c_str = strdup(fromXmlChar(xml_str)); + IGRAPH_CHECK_OOM(c_str, "Insufficient memory to duplicate attribute value."); + + *str = c_str; + + xmlFree(xml_str); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static void safely_free_optional_string(char** str) { + if (*str != NULL) { + igraph_free(*str); + *str = NULL; + } +} + +static igraph_bool_t xmlAttrValueEqual(xmlChar** attr, const char* expected) { + size_t expected_length = strlen(expected); + return ( + expected_length == XML_ATTR_VALUE_LENGTH(attr) && + !xmlStrncmp(toXmlChar(expected), XML_ATTR_VALUE(attr)) + ); +} + +static igraph_error_t igraph_i_graphml_add_attribute_key( + igraph_i_graphml_attribute_record_t** record, + const xmlChar** attrs, int nb_attrs, + struct igraph_i_graphml_parser_state *state +) { + + /* This function must return in three possible ways: + * + * - a proper newly allocated attribute record is returned in 'record' and + * the function returns IGRAPH_SUCCESS; the parser will process the attribute + * - NULL is returned in 'record' and the function returns an igraph error + * code; the parser will handle the error + * - NULL is returned in 'record', but the function itself returns + * IGRAPH_SUCCESS; the parser will skip the attribute + * + * The caller should be prepared to handle all three cases. + */ + + xmlChar **it; + xmlChar *localname; + igraph_trie_t *trie = NULL; + igraph_vector_ptr_t *ptrvector = NULL; + igraph_int_t i, n; + igraph_int_t id; + igraph_i_graphml_attribute_record_t *rec = NULL; + char *attr_name; + igraph_attribute_type_t igraph_attr_type; + igraph_bool_t skip = false; + + attr_name = NULL; + IGRAPH_FINALLY(safely_free_optional_string, &attr_name); + + if (!state->successful) { + /* Parser is already in an error state */ + goto exit; + } + + rec = IGRAPH_CALLOC(1, igraph_i_graphml_attribute_record_t); + IGRAPH_CHECK_OOM(rec, "Insufficient memory to allocate attribute record."); + IGRAPH_FINALLY(igraph_free, rec); + IGRAPH_FINALLY(igraph_i_graphml_attribute_record_destroy, rec); + memset(rec, 0, sizeof(igraph_i_graphml_attribute_record_t)); + + igraph_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + + for (i = 0, it = (xmlChar**)attrs; i < nb_attrs; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + continue; + } + + localname = XML_ATTR_LOCALNAME(it); + + if (xmlStrEqual(localname, toXmlChar("id"))) { + safely_free_optional_string(&rec->id); + IGRAPH_CHECK(safely_convert_xml_attribute_to_string(it, &rec->id)); + } else if (xmlStrEqual(localname, toXmlChar("attr.name"))) { + safely_free_optional_string(&attr_name); + IGRAPH_CHECK(safely_convert_xml_attribute_to_string(it, &attr_name)); + } else if (xmlStrEqual(localname, toXmlChar("attr.type"))) { + if (xmlAttrValueEqual(it, "boolean")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_BOOLEAN; + rec->type = I_GRAPHML_BOOLEAN; + } else if (xmlAttrValueEqual(it, "string")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_STRING; + rec->type = I_GRAPHML_STRING; + } else if (xmlAttrValueEqual(it, "float")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->type = I_GRAPHML_FLOAT; + } else if (xmlAttrValueEqual(it, "double")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->type = I_GRAPHML_DOUBLE; + } else if (xmlAttrValueEqual(it, "int")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->type = I_GRAPHML_INTEGER; + } else if (xmlAttrValueEqual(it, "long")) { + igraph_attr_type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->type = I_GRAPHML_LONG; + } else { + IGRAPH_ERRORF("Unknown attribute type '%.*s'.", IGRAPH_PARSEERROR, + XML_ATTR_VALUE_PF(it)); + } + } else if (xmlStrEqual(*it, toXmlChar("for"))) { + /* graph, vertex or edge attribute? */ + if (xmlAttrValueEqual(it, "graph")) { + trie = &state->g_attr_ids; + ptrvector = &state->g_attrs; + } else if (xmlAttrValueEqual(it, "node")) { + trie = &state->v_attr_ids; + ptrvector = &state->v_attrs; + } else if (xmlAttrValueEqual(it, "edge")) { + trie = &state->e_attr_ids; + ptrvector = &state->e_attrs; + } else if (xmlAttrValueEqual(it, "graphml")) { + igraph_i_report_unhandled_attribute_target("graphml", IGRAPH_FILE_BASENAME, __LINE__); + skip = 1; + } else if (xmlAttrValueEqual(it, "hyperedge")) { + igraph_i_report_unhandled_attribute_target("hyperedge", IGRAPH_FILE_BASENAME, __LINE__); + skip = 1; + } else if (xmlAttrValueEqual(it, "port")) { + igraph_i_report_unhandled_attribute_target("port", IGRAPH_FILE_BASENAME, __LINE__); + skip = 1; + } else if (xmlAttrValueEqual(it, "endpoint")) { + igraph_i_report_unhandled_attribute_target("endpoint", IGRAPH_FILE_BASENAME, __LINE__); + skip = 1; + } else if (xmlAttrValueEqual(it, "all")) { + /* TODO: we should handle this */ + igraph_i_report_unhandled_attribute_target("all", IGRAPH_FILE_BASENAME, __LINE__); + skip = 1; + } else { + IGRAPH_ERRORF("Unknown value '%.*s' in the 'for' attribute of a tag.", IGRAPH_PARSEERROR, + XML_ATTR_VALUE_PF(it)); + } + } + } + + /* throw an error if there is no ID; this is a clear violation of the GraphML DTD */ + if (rec->id == NULL) { + IGRAPH_ERROR("Found tag with no 'id' attribute.", IGRAPH_PARSEERROR); + } + + /* throw an error if the ID is an empty string; this is also a clear violation of the GraphML DTD */ + if (*(rec->id) == '\0') { + IGRAPH_ERROR("Found tag with an empty 'id' attribute.", IGRAPH_PARSEERROR); + } + + /* in case of a missing attr.name attribute, use the id as the attribute name */ + if (attr_name == NULL) { + /* Allocation here is protected by safely_free_optional_string(&attr_name), + * which is already in the finally stack */ + attr_name = strdup(rec->id); + IGRAPH_CHECK_OOM(attr_name, "Cannot duplicate attribute ID as name."); + } + + /* if the attribute type is missing, ignore the attribute with a warning */ + if (!skip && rec->type == I_GRAPHML_UNKNOWN_TYPE) { + IGRAPH_WARNINGF("Ignoring because of a missing 'attr.type' attribute.", rec->id); + skip = 1; + } + + /* if the value of the 'for' attribute was missing, ignore the attribute with a warning */ + if (!skip && trie == 0) { + IGRAPH_WARNINGF("Ignoring because of a missing 'for' attribute.", rec->id); + skip = 1; + } + + /* If attribute is skipped, proceed according to the type of the associated graph element. */ + if (skip) { + if (trie == 0) { + /* Attribute was skipped because it is not for a node, edge or the graph. + * Free everything and return. */ + if (rec) { + igraph_i_graphml_attribute_record_destroy(rec); + IGRAPH_FREE(rec); + } + IGRAPH_FINALLY_CLEAN(2); + goto exit; + } else { + /* If the skipped attribute was for a supported graph element, we add it + * as "UNSPECIFIED" so that we can avoid reporting "unknown attribute" warnings + * later. */ + igraph_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + } + } + + /* check if we have already seen this ID */ + IGRAPH_CHECK(igraph_trie_check(trie, rec->id, &id)); + if (id >= 0) { + IGRAPH_ERRORF("Duplicate attribute ID found: '%s'.", IGRAPH_PARSEERROR, rec->id); + } + + /* check if we have already seen this attribute name */ + n = igraph_vector_ptr_size(ptrvector); + for (i = 0; i < n; i++) { + if (!strcmp( + attr_name, + ((igraph_i_graphml_attribute_record_t*) igraph_vector_ptr_get(ptrvector, i))->record.name + )) { + IGRAPH_ERRORF( + "Duplicate attribute name found: '%s' (for ).", + IGRAPH_PARSEERROR, attr_name, rec->id + ); + } + } + + /* add to trie, attributes */ + IGRAPH_CHECK(igraph_trie_get(trie, rec->id, &id)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(ptrvector, rec)); + + /* Ownership of 'rec' is now taken by ptrvector so we are not responsible + * for destroying and freeing it any more */ + IGRAPH_FINALLY_CLEAN(2); + + /* create the attribute values */ + IGRAPH_CHECK(igraph_attribute_record_init(&rec->record, attr_name, igraph_attr_type)); + +exit: + safely_free_optional_string(&attr_name); + IGRAPH_FINALLY_CLEAN(1); + + *record = rec; + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_graphml_attribute_data_setup( + struct igraph_i_graphml_parser_state *state, const xmlChar **attrs, + int nb_attrs, igraph_attribute_elemtype_t type +) { + xmlChar **it; + int i; + + if (!state->successful) { + return IGRAPH_SUCCESS; + } + + for (i = 0, it = (xmlChar**)attrs; i < nb_attrs; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + continue; + } + + if (xmlStrEqual(*it, toXmlChar("key"))) { + if (state->data_key) { + xmlFree((void *) state->data_key); + state->data_key = NULL; + } + state->data_key = xmlStrndup(XML_ATTR_VALUE(it)); + IGRAPH_CHECK_OOM(state->data_key, "Insufficient memory to read GraphML file."); + igraph_vector_char_clear(&state->data_char); + state->data_type = type; + } else { + /* ignore */ + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_graphml_append_to_data_char( + struct igraph_i_graphml_parser_state *state, const xmlChar *data, int len +) { + const igraph_vector_char_t data_vec = + igraph_vector_char_view((char *) data, len); + + if (!state->successful) { + return IGRAPH_SUCCESS; + } + + IGRAPH_STATIC_ASSERT(sizeof(char) == sizeof(xmlChar)); + IGRAPH_CHECK(igraph_vector_char_append( + &state->data_char, + &data_vec + )); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_graphml_attribute_data_finish(struct igraph_i_graphml_parser_state *state) { + const char *key = fromXmlChar(state->data_key); + igraph_attribute_elemtype_t type = state->data_type; + igraph_trie_t *trie = NULL; + igraph_vector_ptr_t *ptrvector = NULL; + igraph_i_graphml_attribute_record_t *graphmlrec; + igraph_attribute_record_t *rec; + igraph_int_t recid, id = 0; + igraph_error_t result = IGRAPH_SUCCESS; + + switch (type) { + case IGRAPH_ATTRIBUTE_GRAPH: + trie = &state->g_attr_ids; + ptrvector = &state->g_attrs; + id = 0; + break; + case IGRAPH_ATTRIBUTE_VERTEX: + trie = &state->v_attr_ids; + ptrvector = &state->v_attrs; + id = state->act_node; + break; + case IGRAPH_ATTRIBUTE_EDGE: + trie = &state->e_attr_ids; + ptrvector = &state->e_attrs; + id = igraph_vector_int_size(&state->edgelist) / 2 - 1; /* hack */ + break; + default: + IGRAPH_FATAL("Unexpected attribute element type."); + } + + if (key == 0) { + /* no key specified, issue a warning */ + IGRAPH_WARNING("Missing attribute key in a tag, ignoring attribute."); + goto exit; + } + + IGRAPH_CHECK(igraph_trie_check(trie, key, &recid)); + if (recid < 0) { + /* no such attribute key, issue a warning */ + IGRAPH_WARNINGF( + "Unknown attribute key '%s' in a tag, ignoring attribute.", + key + ); + goto exit; + } + + graphmlrec = VECTOR(*ptrvector)[recid]; + rec = &graphmlrec->record; + + if (id >= igraph_attribute_record_size(rec) && rec->type != IGRAPH_ATTRIBUTE_UNSPECIFIED) { + IGRAPH_CHECK(igraph_attribute_record_resize(rec, id + 1)); + } + + switch (rec->type) { + igraph_vector_bool_t *boolvec; + igraph_vector_t *vec; + igraph_strvector_t *strvec; + const char* strvalue; + + case IGRAPH_ATTRIBUTE_BOOLEAN: + /* Add null terminator */ + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + boolvec = rec->value.as_vector_bool; + IGRAPH_CHECK(igraph_i_graphml_parse_boolean( + VECTOR(state->data_char), VECTOR(*boolvec) + id, rec->default_value.boolean + )); + break; + + case IGRAPH_ATTRIBUTE_NUMERIC: + /* Add null terminator */ + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + vec = rec->value.as_vector; + IGRAPH_CHECK(igraph_i_graphml_parse_numeric( + VECTOR(state->data_char), VECTOR(*vec) + id, rec->default_value.numeric + )); + break; + + case IGRAPH_ATTRIBUTE_STRING: + /* Ensure that the vector ends with a null terminator */ + strvec = rec->value.as_strvector; + if (igraph_vector_char_size(&state->data_char) > 0) { + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + strvalue = VECTOR(state->data_char); + } else { + strvalue = rec->default_value.string; + } + IGRAPH_CHECK(igraph_strvector_set(strvec, id, strvalue ? strvalue : "")); + break; + + case IGRAPH_ATTRIBUTE_UNSPECIFIED: + break; + + default: + IGRAPH_FATAL("Unexpected attribute type."); + } + +exit: + igraph_vector_char_clear(&state->data_char); + + return result; +} + +static igraph_error_t igraph_i_graphml_attribute_default_value_finish(struct igraph_i_graphml_parser_state *state) { + igraph_i_graphml_attribute_record_t *graphmlrec = state->current_attr_record; + igraph_attribute_record_t *rec; + igraph_error_t result = IGRAPH_SUCCESS; + igraph_real_t default_num; + igraph_bool_t default_bool; + + IGRAPH_ASSERT(state->current_attr_record != NULL); + + if (igraph_vector_char_size(&state->data_char) == 0) { + return IGRAPH_SUCCESS; + } + + rec = &graphmlrec->record; + + switch (rec->type) { + case IGRAPH_ATTRIBUTE_BOOLEAN: + /* Add null terminator */ + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + IGRAPH_CHECK(igraph_i_graphml_parse_boolean(VECTOR(state->data_char), &default_bool, false)); + IGRAPH_CHECK(igraph_attribute_record_set_default_boolean(rec, default_bool)); + break; + case IGRAPH_ATTRIBUTE_NUMERIC: + /* Add null terminator */ + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + IGRAPH_CHECK(igraph_i_graphml_parse_numeric(VECTOR(state->data_char), &default_num, IGRAPH_NAN)); + IGRAPH_CHECK(igraph_attribute_record_set_default_numeric(rec, default_num)); + break; + case IGRAPH_ATTRIBUTE_STRING: + /* Add null terminator */ + IGRAPH_CHECK(igraph_vector_char_push_back(&state->data_char, '\x00')); + IGRAPH_CHECK(igraph_attribute_record_set_default_string(rec, VECTOR(state->data_char))); + break; + case IGRAPH_ATTRIBUTE_UNSPECIFIED: + break; + default: + IGRAPH_FATAL("Unexpected attribute type."); + } + + igraph_vector_char_clear(&state->data_char); + + return result; +} + +static igraph_error_t igraph_i_graphml_sax_handler_start_element_ns_inner( + struct igraph_i_graphml_parser_state* state, const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri, int nb_namespaces, const xmlChar** namespaces, + int nb_attributes, int nb_defaulted, const xmlChar** attributes) { + xmlChar** it; + xmlChar* attr_value = 0; + igraph_int_t id1, id2; + int i; + igraph_bool_t tag_is_unknown = false; + + IGRAPH_UNUSED(prefix); + IGRAPH_UNUSED(nb_namespaces); + IGRAPH_UNUSED(namespaces); + IGRAPH_UNUSED(nb_defaulted); + + if (uri) { + if (!xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), uri)) { + /* Tag is in a different namespace, so treat it as an unknown start + * tag irrespectively of our state */ + tag_is_unknown = 1; + } + } else { + /* No namespace URI. If we are in lenient mode, accept it and proceed + * as if we are in the GraphML namespace to handle lots of naive + * non-namespace-aware GraphML files floating out there. If we are not + * in lenient mode _but_ we are in the START state, accept it as well + * and see whether the root tag is (in which case we will + * enter lenient mode). Otherwise, reject the tag */ + if (!state->ignore_namespaces && state->st != START) { + tag_is_unknown = 1; + } + } + + if (tag_is_unknown) { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + goto exit; + } + + switch (state->st) { + case START: + /* If we are in the START state and received a graphml tag, + * change to INSIDE_GRAPHML state. Otherwise, change to UNKNOWN. */ + if (xmlStrEqual(localname, toXmlChar("graphml"))) { + if (uri == 0) { + state->ignore_namespaces = 1; + } + state->st = INSIDE_GRAPHML; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_GRAPHML: + /* If we are in the INSIDE_GRAPHML state and received a graph tag, + * change to INSIDE_GRAPH state if the state->index counter reached + * zero (this is to handle multiple graphs in the same file). + * Otherwise, change to UNKNOWN. */ + if (xmlStrEqual(localname, toXmlChar("graph"))) { + if (state->index == 0) { + state->st = INSIDE_GRAPH; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(*it, toXmlChar("edgedefault"))) { + if (xmlAttrValueEqual(it, "directed")) { + state->edges_directed = 1; + } else if (xmlAttrValueEqual(it, "undirected")) { + state->edges_directed = 0; + } + } + } + } + state->index--; + } else if (xmlStrEqual(localname, toXmlChar("key"))) { + IGRAPH_CHECK( + igraph_i_graphml_add_attribute_key( + &state->current_attr_record, + attributes, nb_attributes, state + ) + ); + /* NULL is okay here for state->current_attr_record -- we should have + * triggered an error in the parser already if we returned NULL, and + * the rest of the code is prepared to handle NULLs */ + state->st = INSIDE_KEY; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_KEY: + /* If we are in the INSIDE_KEY state and we are not skipping the current + * attribute, check for default tag */ + if (state->current_attr_record != NULL && xmlStrEqual(localname, toXmlChar("default"))) { + state->st = INSIDE_DEFAULT; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_DEFAULT: + /* If we are in the INSIDE_DEFAULT state, every further tag will be unknown */ + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + break; + + case INSIDE_GRAPH: + /* If we are in the INSIDE_GRAPH state, check for node and edge tags */ + if (xmlStrEqual(localname, toXmlChar("edge"))) { + id1 = -1; id2 = -1; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(*it, toXmlChar("source"))) { + attr_value = xmlStrndup(XML_ATTR_VALUE(it)); + if (attr_value == 0) { + IGRAPH_ERROR("Cannot copy value of edge source attribute.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(xmlFree, attr_value); + + IGRAPH_CHECK(igraph_trie_get(&state->node_trie, fromXmlChar(attr_value), &id1)); + + xmlFree(attr_value); attr_value = NULL; + IGRAPH_FINALLY_CLEAN(1); + } else if (xmlStrEqual(*it, toXmlChar("target"))) { + attr_value = xmlStrndup(XML_ATTR_VALUE(it)); + if (attr_value == 0) { + IGRAPH_ERROR("Cannot copy value of edge target attribute.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(xmlFree, attr_value); + + IGRAPH_CHECK(igraph_trie_get(&state->node_trie, fromXmlChar(attr_value), &id2)); + + xmlFree(attr_value); attr_value = NULL; + IGRAPH_FINALLY_CLEAN(1); + } else if (xmlStrEqual(*it, toXmlChar("id"))) { + igraph_int_t edges = igraph_vector_int_size(&state->edgelist) / 2 + 1; + igraph_int_t origsize = igraph_strvector_size(&state->edgeids); + + attr_value = xmlStrndup(XML_ATTR_VALUE(it)); + if (attr_value == 0) { + IGRAPH_ERROR("Cannot copy value of edge ID attribute.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(xmlFree, attr_value); + + IGRAPH_CHECK(igraph_strvector_resize(&state->edgeids, edges)); + + for (; origsize < edges - 1; origsize++) { + IGRAPH_CHECK(igraph_strvector_set(&state->edgeids, origsize, "")); + } + + IGRAPH_CHECK(igraph_strvector_set(&state->edgeids, edges - 1, fromXmlChar(attr_value))); + + xmlFree(attr_value); attr_value = NULL; + IGRAPH_FINALLY_CLEAN(1); + } + } + if (id1 >= 0 && id2 >= 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&state->edgelist, id1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&state->edgelist, id2)); + } else { + IGRAPH_ERROR("Edge with missing source or target encountered.", IGRAPH_PARSEERROR); + } + state->st = INSIDE_EDGE; + } else if (xmlStrEqual(localname, toXmlChar("node"))) { + id1 = -1; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(XML_ATTR_LOCALNAME(it), toXmlChar("id"))) { + attr_value = xmlStrndup(XML_ATTR_VALUE(it)); + if (attr_value == 0) { + IGRAPH_ERROR("Cannot copy value of node ID attribute.", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(xmlFree, attr_value); + + IGRAPH_CHECK(igraph_trie_get(&state->node_trie, fromXmlChar(attr_value), &id1)); + + xmlFree(attr_value); attr_value = NULL; + IGRAPH_FINALLY_CLEAN(1); + break; + } + } + if (id1 >= 0) { + state->act_node = id1; + } else { + state->act_node = -1; + IGRAPH_ERROR("Node with missing ID encountered.", IGRAPH_PARSEERROR); + } + state->st = INSIDE_NODE; + } else if (xmlStrEqual(localname, toXmlChar("data"))) { + IGRAPH_CHECK(igraph_i_graphml_attribute_data_setup( + state, attributes, nb_attributes, IGRAPH_ATTRIBUTE_GRAPH + )); + IGRAPH_CHECK(igraph_vector_int_push_back(&state->prev_state_stack, state->st)); + state->st = INSIDE_DATA; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_NODE: + if (xmlStrEqual(localname, toXmlChar("data"))) { + IGRAPH_CHECK(igraph_i_graphml_attribute_data_setup( + state, attributes, nb_attributes, IGRAPH_ATTRIBUTE_VERTEX + )); + IGRAPH_CHECK(igraph_vector_int_push_back(&state->prev_state_stack, state->st)); + state->st = INSIDE_DATA; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_EDGE: + if (xmlStrEqual(localname, toXmlChar("data"))) { + IGRAPH_CHECK(igraph_i_graphml_attribute_data_setup( + state, attributes, nb_attributes, IGRAPH_ATTRIBUTE_EDGE + )); + IGRAPH_CHECK(igraph_vector_int_push_back(&state->prev_state_stack, state->st)); + state->st = INSIDE_DATA; + } else { + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + } + break; + + case INSIDE_DATA: + /* We do not expect any new tags within a tag */ + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + break; + + case UNKNOWN: + IGRAPH_CHECK(igraph_i_graphml_handle_unknown_start_tag(state)); + break; + + case FINISH: + break; + + default: + IGRAPH_FATALF("Unexpected GraphML reader state %d.", (int) state->st); + } + +exit: + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphml_sax_handler_start_element_ns( + void *state0, const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri, int nb_namespaces, const xmlChar** namespaces, + int nb_attributes, int nb_defaulted, const xmlChar** attributes) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + igraph_error_t result; + + if (!state->successful) { + return; + } + + result = igraph_i_graphml_sax_handler_start_element_ns_inner( + state, localname, prefix, uri, nb_namespaces, namespaces, + nb_attributes, nb_defaulted, attributes + ); + + if (result != IGRAPH_SUCCESS) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file.", result); + } +} + +static igraph_error_t igraph_i_graphml_sax_handler_end_element_ns_inner( + struct igraph_i_graphml_parser_state* state, + const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri +) { + IGRAPH_UNUSED(localname); + IGRAPH_UNUSED(prefix); + IGRAPH_UNUSED(uri); + + switch (state->st) { + case INSIDE_GRAPHML: + state->st = FINISH; + break; + + case INSIDE_GRAPH: + state->st = INSIDE_GRAPHML; + break; + + case INSIDE_KEY: + state->current_attr_record = NULL; + state->st = INSIDE_GRAPHML; + break; + + case INSIDE_DEFAULT: + IGRAPH_CHECK(igraph_i_graphml_attribute_default_value_finish(state)); + state->st = INSIDE_KEY; + break; + + case INSIDE_NODE: + state->st = INSIDE_GRAPH; + break; + + case INSIDE_EDGE: + state->st = INSIDE_GRAPH; + break; + + case INSIDE_DATA: + IGRAPH_CHECK(igraph_i_graphml_attribute_data_finish(state)); + IGRAPH_ASSERT(!igraph_vector_int_empty(&state->prev_state_stack)); + state->st = (igraph_i_graphml_parser_state_index_t) igraph_vector_int_pop_back(&state->prev_state_stack); + break; + + case UNKNOWN: + state->unknown_depth--; + if (!state->unknown_depth) { + IGRAPH_ASSERT(!igraph_vector_int_empty(&state->prev_state_stack)); + state->st = (igraph_i_graphml_parser_state_index_t) igraph_vector_int_pop_back(&state->prev_state_stack); + } + break; + + case FINISH: + break; + + default: + IGRAPH_FATALF("Unexpected GraphML reader state %d.", (int) state->st); + } + + return IGRAPH_SUCCESS; +} + +static void igraph_i_graphml_sax_handler_end_element_ns( + void *state0, + const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + igraph_error_t result; + + if (!state->successful) { + return; + } + + result = igraph_i_graphml_sax_handler_end_element_ns_inner( + state, localname, prefix, uri + ); + + if (result != IGRAPH_SUCCESS) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file.", result); + } +} + +static void igraph_i_graphml_sax_handler_chars(void* state0, const xmlChar* ch, int len) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + igraph_error_t result = IGRAPH_SUCCESS; + + if (!state->successful) { + return; + } + + switch (state->st) { + case INSIDE_KEY: + break; + + case INSIDE_DATA: + case INSIDE_DEFAULT: + result = igraph_i_graphml_append_to_data_char(state, ch, len); + break; + + default: + /* just ignore it */ + break; + } + + if (result != IGRAPH_SUCCESS) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file.", result); + } +} + +static xmlSAXHandler igraph_i_graphml_sax_handler = { + /* internalSubset = */ 0, + /* isStandalone = */ 0, + /* hasInternalSubset = */ 0, + /* hasExternalSubset = */ 0, + /* resolveEntity = */ 0, + /* getEntity = */ igraph_i_graphml_sax_handler_get_entity, + /* entityDecl = */ 0, + /* notationDecl = */ 0, + /* attributeDecl = */ 0, + /* elementDecl = */ 0, + /* unparsedEntityDecl = */ 0, + /* setDocumentLocator = */ 0, + /* startDocument = */ igraph_i_graphml_sax_handler_start_document, + /* endDocument = */ 0, + /* startElement = */ 0, + /* endElement = */ 0, + /* reference = */ 0, + /* characters = */ igraph_i_graphml_sax_handler_chars, + /* ignorableWhitespaceFunc = */ 0, + /* processingInstruction = */ 0, + /* comment = */ 0, + /* warning = */ igraph_i_graphml_sax_handler_error, + /* error = */ igraph_i_graphml_sax_handler_error, + /* fatalError = */ igraph_i_graphml_sax_handler_error, + /* getParameterEntity = */ 0, + /* cdataBlock = */ 0, + /* externalSubset = */ 0, + /* initialized = */ XML_SAX2_MAGIC, + /* _private = */ 0, + /* startElementNs = */ igraph_i_graphml_sax_handler_start_element_ns, + /* endElementNs = */ igraph_i_graphml_sax_handler_end_element_ns, + /* serror = */ 0 +}; + +#endif // HAVE_LIBXML == 1 + +#define IS_FORBIDDEN_CONTROL_CHAR(x) ((x) < ' ' && (x) != '\t' && (x) != '\r' && (x) != '\n') + +/** + * \param src The string to encode. Control characters are forbidden, in accordance with XML 1.0. + * \param dest The result will be stored here. It is the caller's responsibility to free this buffer. + * \param what Must name the value that is being encoded. Used only for better error reporting. + * \return Error code. + */ +static igraph_error_t igraph_i_xml_escape(const char *src, char **dest, const char *what) { + igraph_int_t destlen = 0; + const char *s; + char *d; + unsigned char ch; + + for (s = src; *s; s++, destlen++) { + ch = (unsigned char)(*s); + if (ch == '&') { + destlen += 4; + } else if (ch == '<') { + destlen += 3; + } else if (ch == '>') { + destlen += 3; + } else if (ch == '"') { + destlen += 5; + } else if (ch == '\'') { + destlen += 5; + } else if (IS_FORBIDDEN_CONTROL_CHAR(ch)) { + IGRAPH_ERRORF("Forbidden control character 0x%02X found while writing %s to GraphML.", + IGRAPH_EINVAL, ch, what); + } + } + *dest = IGRAPH_CALLOC(destlen + 1, char); + IGRAPH_CHECK_OOM(dest, "Insufficient memory to write GraphML file."); + for (s = src, d = *dest; *s; s++, d++) { + ch = (unsigned char)(*s); + switch (ch) { + case '&': + strcpy(d, "&"); d += 4; break; + case '<': + strcpy(d, "<"); d += 3; break; + case '>': + strcpy(d, ">"); d += 3; break; + case '"': + strcpy(d, """); d += 5; break; + case '\'': + strcpy(d, "'"); d += 5; break; + default: + *d = ch; + } + } + *d = 0; + return IGRAPH_SUCCESS; +} + +#if HAVE_LIBXML == 1 +static void igraph_i_libxml_generic_error_handler(void* ctx, const char* msg, ...) { + struct igraph_i_graphml_parser_state* state = (struct igraph_i_graphml_parser_state*) ctx; + va_list args; + va_start(args, msg); + igraph_i_graphml_parser_state_set_error_from_varargs(state, msg, args); + va_end(args); +} + +#if LIBXML_VERSION < 21200 +static void igraph_i_libxml_structured_error_handler(void* ctx, xmlError *error) { +#else +static void igraph_i_libxml_structured_error_handler(void* ctx, const xmlError *error) { +#endif + struct igraph_i_graphml_parser_state* state = (struct igraph_i_graphml_parser_state*) ctx; + igraph_i_graphml_parser_state_set_error_from_xmlerror(state, error); +} +#endif // HAVE_LIBXML == 1 + +/** + * \ingroup loadsave + * \function igraph_read_graph_graphml + * \brief Reads a graph from a GraphML file. + * + * + * GraphML is an XML-based file format for representing various types of + * graphs. Currently only the most basic import functionality is implemented + * in igraph: it can read GraphML files without nested graphs and hyperedges. + * Attributes of the graph are loaded only if an attribute interface + * is attached, see \ref igraph_set_attribute_table(). String attrribute values + * are returned in UTF-8 encoding. + * + * + * Graph attribute names are taken from the attr.name attributes of the + * \c key tags in the GraphML file. Since attr.name is not mandatory, + * igraph will fall back to the \c id attribute of the \c key tag if + * attr.name is missing. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream A stream, it should be readable. + * \param index If the GraphML file contains more than one graph, the one + * specified by this index will be loaded. Indices start from + * zero, so supply zero here if your GraphML file contains only + * a single graph. + * + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * \c IGRAPH_UNIMPLEMENTED: the GraphML functionality was disabled + * at compile-time + * + * \example examples/simple/graphml.c + */ +igraph_error_t igraph_read_graph_graphml(igraph_t *graph, FILE *instream, igraph_int_t index) { + +#if HAVE_LIBXML == 1 + xmlParserCtxtPtr ctxt; + xmlGenericErrorFunc libxml_old_generic_error_handler; + void* libxml_old_generic_error_context; + xmlStructuredErrorFunc libxml_old_structured_error_handler; + void* libxml_old_structured_error_context; + xmlDocPtr doc; + + struct igraph_i_graphml_parser_state state; + int res; + char buffer[4096]; + igraph_bool_t parsing_successful; + char* error_message; + + if (index < 0) { + IGRAPH_ERROR("Graph index must be non-negative.", IGRAPH_EINVAL); + } + + xmlInitParser(); + + IGRAPH_CHECK(igraph_i_graphml_parser_state_init(&state, graph, index)); + IGRAPH_FINALLY(igraph_i_graphml_parser_state_destroy, &state); + + /* Create a progressive parser context and use the first 4K to detect the + * encoding */ + res = (int) fread(buffer, 1, sizeof(buffer), instream); + if (res < (int) sizeof buffer && !feof(instream)) { + IGRAPH_ERROR("IO error while reading GraphML data.", IGRAPH_EFILE); + } + + /* Retrieve the current libxml2 error handlers and temporarily replace them + * with ones that do not print anything to stdout/stderr */ + libxml_old_generic_error_handler = xmlGenericError; + libxml_old_generic_error_context = xmlGenericErrorContext; + libxml_old_structured_error_handler = xmlStructuredError; + libxml_old_structured_error_context = xmlStructuredErrorContext; + xmlSetGenericErrorFunc(&state, &igraph_i_libxml_generic_error_handler); + xmlSetStructuredErrorFunc(&state, &igraph_i_libxml_structured_error_handler); + + /* Okay, parsing will start now. The parser might do things that eventually + * trigger the igraph error handler, but we want the parser state to + * survive whatever happens here. So, we put a barrier on the FINALLY stack + * that prevents IGRAPH_ERROR() from freeing the parser state, and then we + * do this ourselves when needed */ + IGRAPH_FINALLY_ENTER(); + { + ctxt = xmlCreatePushParserCtxt(&igraph_i_graphml_sax_handler, + &state, + buffer, + res, + NULL); + if (ctxt) { + if (xmlCtxtUseOptions(ctxt, + XML_PARSE_NOBLANKS | + XML_PARSE_NONET | XML_PARSE_NSCLEAN | + XML_PARSE_NOCDATA | XML_PARSE_HUGE + )) { + xmlFreeParserCtxt(ctxt); + ctxt = NULL; + } + } + + /* Do the parsing */ + if (ctxt) { + while ((res = (int) fread(buffer, 1, sizeof buffer, instream)) > 0) { + xmlParseChunk(ctxt, buffer, res, 0); + if (!state.successful) { + break; + } + IGRAPH_ALLOW_INTERRUPTION(); + } + xmlParseChunk(ctxt, buffer, res, 1); + } + } + IGRAPH_FINALLY_EXIT(); + + /* Restore error handlers */ + xmlSetGenericErrorFunc(libxml_old_generic_error_context, libxml_old_generic_error_handler); + xmlSetStructuredErrorFunc(libxml_old_structured_error_context, libxml_old_structured_error_handler); + + /* Free the context */ + if (ctxt) { + doc = ctxt->myDoc; + xmlFreeParserCtxt(ctxt); + if (doc) { + /* In theory this should not be necessary, but it looks like certain malformed + * GraphML files leave a partially-parsed doc in memory */ + xmlFreeDoc(doc); + } + } else { + /* We could not create the context earlier so no parsing was done */ + IGRAPH_ERROR("Cannot create XML parser context.", IGRAPH_FAILURE); + } + + /* Extract the error message from the parser state (if any), and make a + * copy so we can safely destroy the parser state before triggering the + * error */ + parsing_successful = state.successful; + error_message = parsing_successful || state.error_message == NULL ? NULL : strdup(state.error_message); + + /* ...and we can also put the error message pointer on the FINALLY stack */ + if (error_message != NULL) { + IGRAPH_FINALLY(igraph_free, error_message); + } + + /* Trigger the stored error if needed */ + if (!parsing_successful) { + if (error_message != NULL) { + size_t len = strlen(error_message); + if (error_message[len-1] == '\n') { + error_message[len-1] = '\0'; + } + IGRAPH_ERROR(error_message, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Malformed GraphML file.", IGRAPH_PARSEERROR); + } + } + + /* Did we actually manage to reach the graph to be parsed, given its index? + * If not, that's an error as well. */ + if (state.index >= 0) { + IGRAPH_ERROR("Graph index was too large.", IGRAPH_EINVAL); + } + + /* Okay, everything seems good. We can now take the parser state and + * construct our graph from the data gathered during the parsing */ + IGRAPH_CHECK(igraph_i_graphml_parser_state_finish_parsing(&state)); + + igraph_i_graphml_parser_state_destroy(&state); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +#else // HAVE_LIBXML == 1 + IGRAPH_UNUSED(graph); + IGRAPH_UNUSED(instream); + IGRAPH_UNUSED(index); + + IGRAPH_ERROR("GraphML support is disabled.", IGRAPH_UNIMPLEMENTED); +#endif // HAVE_LIBXML == 1 +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_graphml + * \brief Writes the graph to a file in GraphML format. + * + * GraphML is an XML-based file format for representing various types of + * graphs. See the GraphML Primer (http://graphml.graphdrawing.org/primer/graphml-primer.html) + * for the detailed format description. + * + * + * When a numerical attribute value is NaN, it will be omitted from the file. + * + * + * This function assumes that non-ASCII characters in attribute names and string + * attribute values are UTF-8 encoded. If this is not the case, the resulting + * XML file will be invalid. Control characters, i.e. character codes up to and + * including 31 (with the exception of tab, cr and lf), are not allowed. + * + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param prefixattr Boolean value. Whether to put a prefix in front of the + * attribute names to ensure uniqueness if the graph has vertex and + * edge (or graph) attributes with the same name. + * \return Error code: + * \c IGRAPH_EFILE if there is an error + * writing the file. + * + * Time complexity: O(|V|+|E|) otherwise. All + * file operations are expected to have time complexity + * O(1). + * + * \example examples/simple/graphml.c + */ +igraph_error_t igraph_write_graph_graphml(const igraph_t *graph, FILE *outstream, + igraph_bool_t prefixattr) { + int ret; + igraph_int_t l, vc; + igraph_eit_t it; + igraph_strvector_t gnames, vnames, enames; + igraph_vector_int_t gtypes, vtypes, etypes; + igraph_int_t i; + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + const char *gprefix = prefixattr ? "g_" : ""; + const char *vprefix = prefixattr ? "v_" : ""; + const char *eprefix = prefixattr ? "e_" : ""; + char what[100]; + + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n", GRAPHML_NAMESPACE_URI); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + + /* dump the elements if any */ + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 1); + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&etypes, 0); + igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes); + + /* graph attributes */ + for (i = 0; i < igraph_vector_int_size(>ypes); i++) { + const char *name; char *name_escaped; + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "graph attribute name")); + IGRAPH_FINALLY(igraph_free, name_escaped); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + IGRAPH_FREE(name_escaped); + IGRAPH_FINALLY_CLEAN(1); + } + + /* vertex attributes */ + for (i = 0; i < igraph_vector_int_size(&vtypes); i++) { + const char *name; char *name_escaped; + name = igraph_strvector_get(&vnames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "vertex attribute name")); + IGRAPH_FINALLY(igraph_free, name_escaped); + if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + IGRAPH_FREE(name_escaped); + IGRAPH_FINALLY_CLEAN(1); + } + + /* edge attributes */ + for (i = 0; i < igraph_vector_int_size(&etypes); i++) { + const char *name; char *name_escaped; + name = igraph_strvector_get(&enames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "edge attribute name")); + IGRAPH_FINALLY(igraph_free, name_escaped); + if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + IGRAPH_FREE(name_escaped); + IGRAPH_FINALLY_CLEAN(1); + } + + ret = fprintf(outstream, " \n", (igraph_is_directed(graph) ? "directed" : "undirected")); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + + /* Write the graph atributes before anything else */ + + for (i = 0; i < igraph_vector_int_size(>ypes); i++) { + const char *name; char *name_escaped; + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "graph attribute name")); + ret = fprintf(outstream, " ", gprefix, name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + char *s_escaped; + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "graph attribute name")); + ret = fprintf(outstream, " ", gprefix, + name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + s = igraph_strvector_get(&strv, 0); + snprintf(what, sizeof(what) / sizeof(what[0]), "the value of the '%s' graph attribute", name); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped, what)); + ret = fprintf(outstream, "%s", s_escaped); + IGRAPH_FREE(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + name = igraph_strvector_get(&gnames, i); + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "graph attribute name")); + ret = fprintf(outstream, " %s\n", + gprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } + + /* Let's dump the nodes first */ + vc = igraph_vcount(graph); + for (l = 0; l < vc; l++) { + const char *name; char *name_escaped; + ret = fprintf(outstream, " \n", l); + + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + + for (i = 0; i < igraph_vector_int_size(&vtypes); i++) { + if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + name = igraph_strvector_get(&vnames, i); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, + igraph_vss_1(l), &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "vertex attribute name")); + ret = fprintf(outstream, " ", vprefix, name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + char *s_escaped; + name = igraph_strvector_get(&vnames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "vertex attribute name")); + ret = fprintf(outstream, " ", vprefix, + name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, + igraph_vss_1(l), &strv)); + s = igraph_strvector_get(&strv, 0); + snprintf(what, sizeof(what) / sizeof(what[0]), "a value of the '%s' vertex attribute", name); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped, what)); + ret = fprintf(outstream, "%s", s_escaped); + IGRAPH_FREE(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + name = igraph_strvector_get(&vnames, i); + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, + igraph_vss_1(l), &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "vertex attribute name")); + ret = fprintf(outstream, " %s\n", + vprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + + /* Now the edges */ + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t from, to; + const char *name; char *name_escaped; + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_edge(graph, edge, &from, &to); + ret = fprintf(outstream, " \n", + from, to); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + + for (i = 0; i < igraph_vector_int_size(&etypes); i++) { + if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + name = igraph_strvector_get(&enames, i); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, name, + igraph_ess_1(edge), &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "edge attribute name")); + ret = fprintf(outstream, " ", eprefix, name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + const char *s; + char *s_escaped; + name = igraph_strvector_get(&enames, i); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "edge attribute name")); + ret = fprintf(outstream, " ", eprefix, + name_escaped); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, name, + igraph_ess_1(edge), &strv)); + s = igraph_strvector_get(&strv, 0); + snprintf(what, sizeof(what) / sizeof(what[0]), "a value of the '%s' edge attribute", name); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped, what)); + ret = fprintf(outstream, "%s", s_escaped); + IGRAPH_FREE(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + name = igraph_strvector_get(&enames, i); + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, name, + igraph_ess_1(edge), &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped, "edge attribute name")); + ret = fprintf(outstream, " %s\n", + eprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + IGRAPH_FREE(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + } + } + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed.", IGRAPH_EFILE); + } + + igraph_strvector_destroy(&gnames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&enames); + igraph_vector_int_destroy(>ypes); + igraph_vector_int_destroy(&vtypes); + igraph_vector_int_destroy(&etypes); + igraph_vector_destroy(&numv); + igraph_strvector_destroy(&strv); + igraph_vector_bool_destroy(&boolv); + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} diff --git a/src/io/leda.c b/src/io/leda.c new file mode 100644 index 0000000..465cf65 --- /dev/null +++ b/src/io/leda.c @@ -0,0 +1,309 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" + +#include "graph/attributes.h" + +#include + +#define CHECK(cmd) \ + do { \ + int ret=(cmd); \ + if (ret<0) IGRAPH_ERROR("Writing LEDA format failed.", IGRAPH_EFILE); \ + } while (0) + +/** + * \function igraph_write_graph_leda + * \brief Write a graph in LEDA native graph format. + * + * This function writes a graph to an output stream in LEDA format. + * See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html + * + * + * The support for the LEDA format is very basic at the moment; igraph + * writes only the LEDA graph section which supports one selected vertex + * and edge attribute and no layout information or visual attributes. + * + * \param graph The graph to write to the stream. + * \param outstream The stream. + * \param vertex_attr_name The name of the vertex attribute whose values + * are to be stored in the output, or \c NULL if no + * vertex attribute should be stored. + * \param edge_attr_name The name of the edge attribute whose values + * are to be stored in the output, or \c NULL if no + * edge attribute should be stored. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices and edges in the + * graph. + */ + +igraph_error_t igraph_write_graph_leda(const igraph_t *graph, FILE *outstream, + const char *vertex_attr_name, + const char *edge_attr_name) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_eit_t it; + igraph_int_t i = 0; + igraph_attribute_type_t vertex_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + igraph_attribute_type_t edge_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + igraph_int_t from, to, rev; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the vertex attribute */ + if (vertex_attr_name && + !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, vertex_attr_name)) { + IGRAPH_WARNINGF("The vertex attribute '%s' does not exist. No vertex values will be written.", + vertex_attr_name); + vertex_attr_name = NULL; + } + if (vertex_attr_name) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &vertex_attr_type, + IGRAPH_ATTRIBUTE_VERTEX, vertex_attr_name)); + if (vertex_attr_type != IGRAPH_ATTRIBUTE_NUMERIC && + vertex_attr_type != IGRAPH_ATTRIBUTE_STRING && + vertex_attr_type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_WARNINGF("The vertex attribute '%s' is not numeric, string or boolean. " + "No vertex values will be written.", + vertex_attr_name); + vertex_attr_name = NULL; vertex_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + } + } + + /* Check if we have the edge attribute */ + if (edge_attr_name && + !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, edge_attr_name)) { + IGRAPH_WARNINGF("The edge attribute '%s' does not exist. No edge values will be written.", + edge_attr_name); + edge_attr_name = NULL; + } + if (edge_attr_name) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &edge_attr_type, + IGRAPH_ATTRIBUTE_EDGE, edge_attr_name)); + if (edge_attr_type != IGRAPH_ATTRIBUTE_NUMERIC && + edge_attr_type != IGRAPH_ATTRIBUTE_STRING && + edge_attr_type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_WARNINGF("The edge attribute '%s' is not numeric, string or boolean. " + "No edge values will be written.", + edge_attr_name); + edge_attr_name = NULL; edge_attr_type = IGRAPH_ATTRIBUTE_UNSPECIFIED; + } + } + + /* Start writing header */ + CHECK(fprintf(outstream, "LEDA.GRAPH\n")); + + switch (vertex_attr_type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + CHECK(fprintf(outstream, "double\n")); + break; + case IGRAPH_ATTRIBUTE_STRING: + CHECK(fprintf(outstream, "string\n")); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + CHECK(fprintf(outstream, "bool\n")); + break; + default: + CHECK(fprintf(outstream, "void\n")); + } + + switch (edge_attr_type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + CHECK(fprintf(outstream, "double\n")); + break; + case IGRAPH_ATTRIBUTE_STRING: + CHECK(fprintf(outstream, "string\n")); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + CHECK(fprintf(outstream, "bool\n")); + break; + default: + CHECK(fprintf(outstream, "void\n")); + } + + CHECK(fprintf(outstream, "%d\n", (igraph_is_directed(graph) ? -1 : -2))); + + /* Start writing vertices */ + CHECK(fprintf(outstream, "# Vertices\n")); + CHECK(fprintf(outstream, "%" IGRAPH_PRId "\n", no_of_nodes)); + + if (vertex_attr_type == IGRAPH_ATTRIBUTE_NUMERIC) { + /* Vertices with numeric attributes */ + igraph_vector_t values; + + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vertex_attr_name, igraph_vss_all(), &values)); + + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, "|{")); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(values)[i])); + CHECK(fprintf(outstream, "}|\n")); + } + + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (vertex_attr_type == IGRAPH_ATTRIBUTE_STRING) { + /* Vertices with string attributes */ + igraph_strvector_t values; + + IGRAPH_CHECK(igraph_strvector_init(&values, no_of_nodes)); + IGRAPH_FINALLY(igraph_strvector_destroy, &values); + + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr( + graph, vertex_attr_name, igraph_vss_all(), &values)); + + for (i = 0; i < no_of_nodes; i++) { + const char *str = igraph_strvector_get(&values, i); + if (strchr(str, '\n') != 0) { + IGRAPH_ERROR("Vertex attribute values cannot contain newline characters.", + IGRAPH_EINVAL); + } + CHECK(fprintf(outstream, "|{%s}|\n", str)); + } + + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (vertex_attr_type == IGRAPH_ATTRIBUTE_BOOLEAN) { + /* Vertices with boolean attributes */ + igraph_vector_bool_t values; + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr( + graph, vertex_attr_name, igraph_vss_all(), &values)); + + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, "|{%s|}\n", VECTOR(values)[i] ? "true" : "false")); + } + + igraph_vector_bool_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Vertices with no attributes */ + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, "|{}|\n")); + } + } + + CHECK(fprintf(outstream, "# Edges\n")); + CHECK(fprintf(outstream, "%" IGRAPH_PRId "\n", no_of_edges)); + + if (edge_attr_type == IGRAPH_ATTRIBUTE_NUMERIC) { + /* Edges with numeric attributes */ + igraph_vector_t values; + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr( + graph, edge_attr_name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &values)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t eid = IGRAPH_EIT_GET(it); + igraph_edge(graph, eid, &from, &to); + igraph_get_eid(graph, &rev, to, from, IGRAPH_DIRECTED, false); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + CHECK(fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId " %" IGRAPH_PRId " |{", + from + 1, to + 1, + rev + 1)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(values)[eid])); + CHECK(fprintf(outstream, "}|\n")); + IGRAPH_EIT_NEXT(it); + } + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (edge_attr_type == IGRAPH_ATTRIBUTE_STRING) { + /* Edges with string attributes */ + igraph_strvector_t values; + IGRAPH_CHECK(igraph_strvector_init(&values, no_of_nodes)); + IGRAPH_FINALLY(igraph_strvector_destroy, &values); + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr( + graph, edge_attr_name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &values)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t eid = IGRAPH_EIT_GET(it); + const char *str = igraph_strvector_get(&values, eid); + igraph_edge(graph, eid, &from, &to); + igraph_get_eid(graph, &rev, to, from, IGRAPH_DIRECTED, false); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + if (strchr(str, '\n') != 0) { + IGRAPH_ERROR("Edge attribute values cannot contain newline characters.", + IGRAPH_EINVAL); + } + CHECK(fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId " %" IGRAPH_PRId " |{%s}|\n", + from + 1, to + 1, + rev + 1, str)); + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (vertex_attr_type == IGRAPH_ATTRIBUTE_BOOLEAN) { + /* Edges with boolean attributes */ + igraph_vector_bool_t values; + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&values, no_of_edges); + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr( + graph, vertex_attr_name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &values)); + + while (!IGRAPH_EIT_END(it)) { + igraph_int_t eid = IGRAPH_EIT_GET(it); + igraph_edge(graph, eid, &from, &to); + igraph_get_eid(graph, &rev, to, from, IGRAPH_DIRECTED, false); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + CHECK(fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId " %" IGRAPH_PRId " |{%s}|\n", + from + 1, to + 1, + rev + 1, + VECTOR(values)[eid] ? "true" : "false")); + IGRAPH_EIT_NEXT(it); + } + + igraph_vector_bool_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Edges with no attributes */ + while (!IGRAPH_EIT_END(it)) { + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + igraph_get_eid(graph, &rev, to, from, IGRAPH_DIRECTED, false); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + CHECK(fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId " %" IGRAPH_PRId " |{}|\n", + from + 1, to + 1, + rev + 1)); + IGRAPH_EIT_NEXT(it); + } + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#undef CHECK diff --git a/src/io/lgl-header.h b/src/io/lgl-header.h new file mode 100644 index 0000000..b67e772 --- /dev/null +++ b/src/io/lgl-header.h @@ -0,0 +1,37 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_vector.h" + +#include "core/trie.h" + +typedef struct { + void *scanner; + char errmsg[300]; + igraph_error_t igraph_errno; + igraph_bool_t has_weights; + igraph_vector_int_t *vector; + igraph_vector_t *weights; + igraph_trie_t *trie; + igraph_int_t actvertex; +} igraph_i_lgl_parsedata_t; diff --git a/src/io/lgl.c b/src/io/lgl.c new file mode 100644 index 0000000..e6ad1e2 --- /dev/null +++ b/src/io/lgl.c @@ -0,0 +1,462 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" + +#include "io/lgl-header.h" +#include "io/parsers/lgl-parser.h" + +int igraph_lgl_yylex_init_extra (igraph_i_lgl_parsedata_t *user_defined, + void *scanner); +int igraph_lgl_yylex_destroy(void *scanner); +int igraph_lgl_yyparse(igraph_i_lgl_parsedata_t *context); +void igraph_lgl_yyset_in(FILE *in_str, void *yyscanner); + +/* for IGRAPH_FINALLY, which assumes that destructor functions return void */ +void igraph_lgl_yylex_destroy_wrapper (void *scanner ) { + (void) igraph_lgl_yylex_destroy(scanner); +} + +/** + * \ingroup loadsave + * \function igraph_read_graph_lgl + * \brief Reads a graph from an .lgl file. + * + * The .lgl format is used by the Large Graph + * Layout visualization software + * (https://lgl.sourceforge.net), it can + * describe undirected optionally weighted graphs. From the LGL + * manual: + * + * \blockquote The second format is the LGL file format + * (.lgl file + * suffix). This is yet another graph file format that tries to be as + * stingy as possible with space, yet keeping the edge file in a human + * readable (not binary) format. The format itself is like the + * following: + * \verbatim # vertex1name +vertex2name [optionalWeight] +vertex3name [optionalWeight] \endverbatim + * Here, the first vertex of an edge is preceded with a pound sign + * '#'. Then each vertex that shares an edge with that vertex is + * listed one per line on subsequent lines. \endblockquote + * + * + * LGL cannot handle loop and multiple edges or directed graphs, but + * in \a igraph it is not an error to have multiple and loop edges. + * \param graph Pointer to an uninitialized graph object. + * \param instream A stream, it should be readable. + * \param names Boolean value, if \c true the symbolic names of the + * vertices will be added to the graph as a vertex attribute + * called \quote name\endquote. + * \param weights Whether to add the weights of the edges to the + * graph as an edge attribute called \quote weight\endquote. + * \c IGRAPH_ADD_WEIGHTS_YES adds the weights (even if they + * are not present in the file, in this case they are assumed + * to be 1). \c IGRAPH_ADD_WEIGHTS_NO does not add any + * edge attribute. \c IGRAPH_ADD_WEIGHTS_IF_PRESENT adds the + * attribute if and only if there is at least one explicit + * edge weight in the input file, and edges without an explicit + * weight are assumed to have a weight of 1. + * \param directed Whether to create a directed graph. As this format + * was originally used only for undirected graphs there is no + * information in the file about the directedness of the graph. + * Set this parameter to \c IGRAPH_DIRECTED or \c + * IGRAPH_UNDIRECTED to create a directed or undirected graph. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * + * Time complexity: + * O(|V|+|E|log(|V|)) if we neglect + * the time required by the parsing. As usual + * |V| is the number of vertices, + * while |E| is the number of edges. + * + * \sa \ref igraph_read_graph_ncol(), \ref igraph_write_graph_lgl() + * + * \example examples/simple/igraph_read_graph_lgl.c + */ +igraph_error_t igraph_read_graph_lgl(igraph_t *graph, FILE *instream, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed) { + + igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t ws = IGRAPH_VECTOR_NULL; + igraph_trie_t trie = IGRAPH_TRIE_NULL; + igraph_attribute_record_list_t name, weight; + igraph_attribute_record_list_t *pname = NULL, *pweight = NULL; + igraph_attribute_record_t *namerec, *weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_lgl_parsedata_t context; + + IGRAPH_VECTOR_INIT_FINALLY(&ws, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_TRIE_INIT_FINALLY(&trie, names); + + context.has_weights = false; + context.vector = &edges; + context.weights = &ws; + context.trie = ≜ + context.errmsg[0] = '\0'; + context.igraph_errno = IGRAPH_SUCCESS; + + igraph_lgl_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_lgl_yylex_destroy_wrapper, context.scanner); + + igraph_lgl_yyset_in(instream, context.scanner); + + /* Use ENTER/EXIT to avoid destroying context.scanner before this function returns */ + IGRAPH_FINALLY_ENTER(); + int err = igraph_lgl_yyparse(&context); + IGRAPH_FINALLY_EXIT(); + switch (err) { + case 0: /* success */ + break; + case 1: /* parse error */ + if (context.errmsg[0] != '\0') { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else if (context.igraph_errno != IGRAPH_SUCCESS) { + IGRAPH_ERROR("", context.igraph_errno); + } else { + IGRAPH_ERROR("Cannot read LGL file.", IGRAPH_PARSEERROR); + } + break; + case 2: /* out of memory */ + IGRAPH_ERROR("Cannot read LGL file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + default: /* must never reach here */ + /* Hint: This will usually be triggered if an IGRAPH_CHECK() is used in a Bison + * action instead of an IGRAPH_YY_CHECK(), resulting in an igraph errno being + * returned in place of a Bison error code. + * TODO: What if future Bison versions introduce error codes other than 0, 1 and 2? + */ + IGRAPH_FATALF("Parser returned unexpected error code (%d) when reading LGL file.", err); + } + + /* Prepare attributes if needed */ + if (names) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&name, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &name); + + namerec = igraph_attribute_record_list_get_ptr(&name, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(namerec, namestr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(namerec, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_CHECK(igraph_strvector_update( + namerec->value.as_strvector, igraph_i_trie_borrow_keys(context.trie)) + ); + + pname = &name; + } + + if (weights == IGRAPH_ADD_WEIGHTS_YES || + (weights == IGRAPH_ADD_WEIGHTS_IF_PRESENT && context.has_weights)) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&weight, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &weight); + + weightrec = igraph_attribute_record_list_get_ptr(&weight, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(weightrec, weightstr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(weightrec, IGRAPH_ATTRIBUTE_NUMERIC)); + igraph_vector_swap(weightrec->value.as_vector, context.weights); + + pweight = &weight; + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, igraph_trie_size(&trie), pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, pweight)); + + if (pweight) { + igraph_attribute_record_list_destroy(pweight); + IGRAPH_FINALLY_CLEAN(1); + } + if (pname) { + igraph_attribute_record_list_destroy(pname); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_trie_destroy(&trie); + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&ws); + igraph_lgl_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t check_name(const char *name) { + size_t len = 0; + + for (; *name != '\0'; name++, len++) { + if ( *name <= 0x020 /* space or non-printable */ + || *name == 0x7f /* del */ + || *name == '#') { + IGRAPH_ERRORF("The LGL format does not allow non-printable characters, spaces or '#' in vertex names. " + "Character code 0x%02X found.", IGRAPH_EINVAL, + *name); + } + } + if (len == 0) { + IGRAPH_ERROR("The LGL format does not support empty vertex names.", IGRAPH_EINVAL); + } + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup loadsave + * \function igraph_write_graph_lgl + * \brief Writes the graph to a file in .lgl format. + * + * .lgl is a format used by LGL, see \ref + * igraph_read_graph_lgl() for details. + * + * + * Note that having multiple or loop edges in an + * .lgl file breaks the LGL software but \a igraph + * does not check for this condition. + * + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param names The name of a string vertex attribute, if symbolic names + * are to be written to the file. Supply \c NULL to write vertex + * ids instead. + * \param weights The name of a numerical edge attribute, which will be + * written as weights to the file. Supply \c NULL to skip writing + * edge weights. + * \param isolates If \c true, isolated vertices are also written + * to the file. If \c false, they will be omitted. + * \return Error code: + * \c IGRAPH_EFILE if there is an error + * writing the file. + * + * Time complexity: O(|E|), the number of edges if \p isolates is \c false, + * O(|V|+|E|) otherwise. All file operations are expected to have + * time complexity O(1). + * + * \sa \ref igraph_read_graph_lgl(), \ref igraph_write_graph_ncol() + * + * \example examples/simple/igraph_write_graph_lgl.c + */ +igraph_error_t igraph_write_graph_lgl(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights, + igraph_bool_t isolates) { + igraph_eit_t it; + igraph_int_t actvertex = -1; + igraph_attribute_type_t nametype, weighttype; + const igraph_int_t vcount = igraph_vcount(graph), ecount = igraph_ecount(graph); + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the names attribute */ + if (names && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + names)) { + IGRAPH_WARNINGF("Names attribute '%s' does not exist.", names); + names = NULL; + } + if (names) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &nametype, + IGRAPH_ATTRIBUTE_VERTEX, names)); + if (nametype != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_WARNINGF("Ignoring names attribute '%s', unknown attribute type.", names); + names = NULL; + } + } + + /* Check the weights as well */ + if (weights && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, weights)) { + IGRAPH_WARNINGF("Weights attribute '%s' does not exist.", weights); + weights = NULL; + } + if (weights) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &weighttype, + IGRAPH_ATTRIBUTE_EDGE, weights)); + if (weighttype != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_WARNINGF("Ignoring weights attribute '%s', unknown attribute type.", weights); + weights = NULL; + } + } + + if (names == NULL && weights == NULL) { + /* No names, no weights */ + while (!IGRAPH_EIT_END(it)) { + igraph_int_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + if (from == actvertex) { + ret = fprintf(outstream, "%" IGRAPH_PRId "\n", to); + } else { + actvertex = from; + ret = fprintf(outstream, "# %" IGRAPH_PRId "\n%" IGRAPH_PRId "\n", from, to); + } + if (ret < 0) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + } else if (weights == NULL) { + /* No weights but use names */ + igraph_strvector_t nvec; + IGRAPH_STRVECTOR_INIT_FINALLY(&nvec, vcount); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret = 0; + const char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + str2 = igraph_strvector_get(&nvec, to); + IGRAPH_CHECK(check_name(str2)); + + if (from == actvertex) { + ret = fprintf(outstream, "%s\n", str2); + } else { + actvertex = from; + str1 = igraph_strvector_get(&nvec, from); + IGRAPH_CHECK(check_name(str1)); + ret = fprintf(outstream, "# %s\n%s\n", str1, str2); + } + if (ret < 0) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + IGRAPH_FINALLY_CLEAN(1); + } else if (names == NULL) { + /* No names but weights */ + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, ecount); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret1, ret2, ret3; + igraph_edge(graph, edge, &from, &to); + if (from == actvertex) { + ret1 = fprintf(outstream, "%" IGRAPH_PRId " ", to); + } else { + actvertex = from; + ret1 = fprintf(outstream, "# %" IGRAPH_PRId "\n%" IGRAPH_PRId " ", from, to); + } + ret2 = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[edge]); + ret3 = fputc('\n', outstream); + if (ret1 < 0 || ret2 < 0 || ret3 == EOF) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Both names and weights */ + igraph_strvector_t nvec; + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, ecount); + IGRAPH_STRVECTOR_INIT_FINALLY(&nvec, vcount); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret = 0, ret2; + const char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + str2 = igraph_strvector_get(&nvec, to); + IGRAPH_CHECK(check_name(str2)); + + if (from == actvertex) { + ret = fprintf(outstream, "%s ", str2); + } else { + actvertex = from; + str1 = igraph_strvector_get(&nvec, from); + IGRAPH_CHECK(check_name(str1)); + ret = fprintf(outstream, "# %s\n%s ", str1, str2); + } + if (ret < 0) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[edge]); + ret2 = fputc('\n', outstream); + if (ret < 0 || ret2 == EOF) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(2); + } + + if (isolates) { + igraph_int_t nov = vcount; + igraph_int_t i; + int ret = 0; + igraph_int_t deg; + igraph_strvector_t nvec; + const char *str; + + IGRAPH_STRVECTOR_INIT_FINALLY(&nvec, 1); + for (i = 0; i < nov; i++) { + IGRAPH_CHECK(igraph_degree_1(graph, °, i, IGRAPH_ALL, IGRAPH_LOOPS)); + if (deg == 0) { + if (names == NULL) { + ret = fprintf(outstream, "# %" IGRAPH_PRId "\n", i); + } else { + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_1(i), &nvec)); + str = igraph_strvector_get(&nvec, 0); + IGRAPH_CHECK(check_name(str)); + ret = fprintf(outstream, "# %s\n", str); + } + } + if (ret < 0) { + IGRAPH_ERROR("Writing LGL file failed.", IGRAPH_EFILE); + } + } + igraph_strvector_destroy(&nvec); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/io/ncol-header.h b/src/io/ncol-header.h new file mode 100644 index 0000000..230a995 --- /dev/null +++ b/src/io/ncol-header.h @@ -0,0 +1,36 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_vector.h" + +#include "core/trie.h" + +typedef struct { + void *scanner; + char errmsg[300]; + igraph_error_t igraph_errno; + igraph_bool_t has_weights; + igraph_vector_int_t *vector; + igraph_vector_t *weights; + igraph_trie_t *trie; +} igraph_i_ncol_parsedata_t; diff --git a/src/io/ncol.c b/src/io/ncol.c new file mode 100644 index 0000000..0804f92 --- /dev/null +++ b/src/io/ncol.c @@ -0,0 +1,442 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" + +#include "io/ncol-header.h" +#include "io/parsers/ncol-parser.h" + +int igraph_ncol_yylex_init_extra (igraph_i_ncol_parsedata_t *user_defined, + void *scanner); +int igraph_ncol_yylex_destroy(void *scanner); +int igraph_ncol_yyparse(igraph_i_ncol_parsedata_t *context); +void igraph_ncol_yyset_in(FILE *in_str, void *yyscanner); + +/* for IGRAPH_FINALLY, which assumes that destructor functions return void */ +void igraph_ncol_yylex_destroy_wrapper (void *scanner ) { + (void) igraph_ncol_yylex_destroy(scanner); +} + +/** + * \ingroup loadsave + * \function igraph_read_graph_ncol + * \brief Reads an .ncol file used by LGL. + * + * Also useful for creating graphs from \quote named\endquote (and + * optionally weighted) edge lists. + * + * + * This format is used by the Large Graph Layout program + * (https://lgl.sourceforge.net), and it is simply a + * symbolic weighted edge list. It is a simple text file with one edge + * per line. An edge is defined by two symbolic vertex names separated + * by whitespace. The vertex names themselves cannot contain + * whitespace. They may be followed by an optional number, + * the weight of the edge; the number can be negative and can be in + * scientific notation. If there is no weight specified to an edge it + * is assumed to be zero. + * + * + * The resulting graph is always undirected. + * LGL cannot deal with files which contain multiple or loop edges, + * this is however not checked here, as \a igraph is happy with + * these. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream Pointer to a stream, it should be readable. + * \param predefnames Pointer to the symbolic names of the vertices in + * the file. If \c NULL is given here then vertex IDs will be + * assigned to vertex names in the order of their appearance in + * the .ncol file. If it is not \c NULL and some unknown + * vertex names are found in the .ncol file then new vertex + * ids will be assigned to them. + * \param names Boolean value. If \c true, the symbolic names of the + * vertices will be added to the graph as a vertex attribute + * called \quote name\endquote. + * \param weights Whether to add the weights of the edges to the + * graph as an edge attribute called \quote weight\endquote. + * \c IGRAPH_ADD_WEIGHTS_YES adds the weights (even if they + * are not present in the file, in this case they are assumed + * to be 1). \c IGRAPH_ADD_WEIGHTS_NO does not add any + * edge attribute. \c IGRAPH_ADD_WEIGHTS_IF_PRESENT adds the + * attribute if and only if there is at least one explicit + * edge weight in the input file, and edges without an explicit + * weight are assumed to have a weight of 1. + * \param directed Whether to create a directed graph. As this format + * was originally used only for undirected graphs there is no + * information in the file about the directedness of the graph. + * Set this parameter to \c IGRAPH_DIRECTED or \c + * IGRAPH_UNDIRECTED to create a directed or undirected graph. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading + * the file, or the file is syntactically incorrect. + * + * Time complexity: + * O(|V|+|E|log(|V|)) if we neglect + * the time required by the parsing. As usual + * |V| is the number of vertices, + * while |E| is the number of edges. + * + * \sa \ref igraph_read_graph_lgl(), \ref igraph_write_graph_ncol() + */ +igraph_error_t igraph_read_graph_ncol(igraph_t *graph, FILE *instream, + const igraph_strvector_t *predefnames, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed) { + + igraph_vector_int_t edges; + igraph_vector_t ws; + igraph_trie_t trie = IGRAPH_TRIE_NULL; + igraph_int_t no_of_nodes; + igraph_int_t no_predefined = 0; + igraph_attribute_record_list_t name, weight; + igraph_attribute_record_list_t *pname = NULL, *pweight = NULL; + igraph_attribute_record_t *namerec, *weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_ncol_parsedata_t context; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + IGRAPH_TRIE_INIT_FINALLY(&trie, names); + IGRAPH_VECTOR_INIT_FINALLY(&ws, 0); + + /* Add the predefined names, if any */ + if (predefnames != 0) { + igraph_int_t i, id, n; + const char *key; + n = no_predefined = igraph_strvector_size(predefnames); + for (i = 0; i < n; i++) { + key = igraph_strvector_get(predefnames, i); + IGRAPH_CHECK(igraph_trie_get(&trie, key, &id)); + if (id != i) { + IGRAPH_WARNING("Reading NCOL file, duplicate entry in predefined names."); + no_predefined--; + } + } + } + + context.has_weights = false; + context.vector = &edges; + context.weights = &ws; + context.trie = ≜ + context.errmsg[0] = '\0'; + context.igraph_errno = IGRAPH_SUCCESS; + + igraph_ncol_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_ncol_yylex_destroy_wrapper, context.scanner); + + igraph_ncol_yyset_in(instream, context.scanner); + + /* Use ENTER/EXIT to avoid destroying context.scanner before this function returns */ + IGRAPH_FINALLY_ENTER(); + int err = igraph_ncol_yyparse(&context); + IGRAPH_FINALLY_EXIT(); + switch (err) { + case 0: /* success */ + break; + case 1: /* parse error */ + if (context.errmsg[0] != '\0') { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else if (context.igraph_errno != IGRAPH_SUCCESS) { + IGRAPH_ERROR("", context.igraph_errno); + } else { + IGRAPH_ERROR("Cannot read NCOL file.", IGRAPH_PARSEERROR); + } + break; + case 2: /* out of memory */ + IGRAPH_ERROR("Cannot read NCOL file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + default: /* must never reach here */ + /* Hint: This will usually be triggered if an IGRAPH_CHECK() is used in a Bison + * action instead of an IGRAPH_YY_CHECK(), resulting in an igraph errno being + * returned in place of a Bison error code. + * TODO: What if future Bison versions introduce error codes other than 0, 1 and 2? + */ + IGRAPH_FATALF("Parser returned unexpected error code (%d) when reading NCOL file.", err); + } + + if (predefnames != 0 && + igraph_trie_size(&trie) != no_predefined) { + IGRAPH_WARNING("Unknown vertex/vertices found in NCOL file, predefined names extended."); + } + + /* Prepare attributes if needed */ + if (names) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&name, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &name); + + namerec = igraph_attribute_record_list_get_ptr(&name, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(namerec, namestr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(namerec, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_CHECK(igraph_strvector_update( + namerec->value.as_strvector, igraph_i_trie_borrow_keys(context.trie)) + ); + + pname = &name; + } + + if (weights == IGRAPH_ADD_WEIGHTS_YES || + (weights == IGRAPH_ADD_WEIGHTS_IF_PRESENT && context.has_weights)) { + IGRAPH_CHECK(igraph_attribute_record_list_init(&weight, 1)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &weight); + + weightrec = igraph_attribute_record_list_get_ptr(&weight, 0); + IGRAPH_CHECK(igraph_attribute_record_set_name(weightrec, weightstr)); + IGRAPH_CHECK(igraph_attribute_record_set_type(weightrec, IGRAPH_ATTRIBUTE_NUMERIC)); + igraph_vector_swap(weightrec->value.as_vector, context.weights); + + pweight = &weight; + } + + if (igraph_vector_int_empty(&edges)) { + no_of_nodes = 0; + } else { + no_of_nodes = igraph_vector_int_max(&edges) + 1; + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, pweight)); + + if (pname) { + igraph_attribute_record_list_destroy(pname); + IGRAPH_FINALLY_CLEAN(1); + } + if (pweight) { + igraph_attribute_record_list_destroy(pweight); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&ws); + igraph_trie_destroy(&trie); + igraph_vector_int_destroy(&edges); + igraph_ncol_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); /* +1 for 'graph' */ + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t check_name(const char *name) { + size_t len = 0; + + for (; *name != '\0'; name++, len++) { + if ( *name <= 0x020 /* space or non-printable */ + || *name == 0x7f /* del */) { + IGRAPH_ERRORF("The NCOL format does not allow non-printable characters or spaces in vertex names. " + "Character code 0x%02X found.", IGRAPH_EINVAL, + *name); + } + } + if (len == 0) { + IGRAPH_ERROR("The NCOL format does not support empty vertex names.", IGRAPH_EINVAL); + } + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup loadsave + * \function igraph_write_graph_ncol + * \brief Writes the graph to a file in .ncol format. + * + * + * .ncol is a format used by LGL, see \ref + * igraph_read_graph_ncol() for details. + * + * + * Note that having multiple or loop edges in an + * .ncol file breaks the LGL software but + * \a igraph does not check for this condition. + * + * + * This format cannot represent zero-degree vertices. + * + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param names The name of a string vertex attribute, if symbolic names + * are to be written to the file. Supply \c NULL to write vertex + * ids instead. + * \param weights The name of a numerical edge attribute, which will be + * written as weights to the file. Supply \c NULL to skip writing + * edge weights. + * \return Error code: + * \c IGRAPH_EFILE if there is an error writing the + * file. + * + * Time complexity: O(|E|), the + * number of edges. All file operations are expected to have time + * complexity O(1). + * + * \sa \ref igraph_read_graph_ncol(), \ref igraph_write_graph_lgl() + */ +igraph_error_t igraph_write_graph_ncol(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights) { + igraph_eit_t it; + igraph_attribute_type_t nametype, weighttype; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the names attribute */ + if (names && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + names)) { + IGRAPH_WARNINGF("Names attribute '%s' does not exist.", names); + names = NULL; + } + if (names) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &nametype, + IGRAPH_ATTRIBUTE_VERTEX, names)); + if (nametype != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_WARNINGF("Ignoring names attribute '%s', " + "attribute type is not a string.", names); + names = NULL; + } + } + + /* Check the weights as well */ + if (weights && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, weights)) { + IGRAPH_WARNINGF("Weights attribute '%s' does not exist.", weights); + weights = NULL; + } + if (weights) { + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &weighttype, + IGRAPH_ATTRIBUTE_EDGE, weights)); + if (weighttype != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_WARNINGF("Ignoring weights attribute '%s', " + "attribute type is not numeric.", weights); + weights = NULL; + } + } + + if (names == NULL && weights == NULL) { + /* No names, no weights */ + while (!IGRAPH_EIT_END(it)) { + igraph_int_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + ret = fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId "\n", from, to); + if (ret < 0) { + IGRAPH_ERROR("Writing NCOL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + } else if (weights == NULL) { + /* No weights, but use names */ + igraph_strvector_t nvec; + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret = 0; + const char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + str1 = igraph_strvector_get(&nvec, from); + IGRAPH_CHECK(check_name(str1)); + str2 = igraph_strvector_get(&nvec, to); + IGRAPH_CHECK(check_name(str2)); + ret = fprintf(outstream, "%s %s\n", str1, str2); + if (ret < 0) { + IGRAPH_ERROR("Writing NCOL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + IGRAPH_FINALLY_CLEAN(1); + } else if (names == NULL) { + /* No names but weights */ + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph)); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret1, ret2, ret3; + igraph_edge(graph, edge, &from, &to); + ret1 = fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId " ", from, to); + ret2 = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[edge]); + ret3 = fputc('\n', outstream); + if (ret1 < 0 || ret2 < 0 || ret3 == EOF) { + IGRAPH_ERROR("Writing NCOL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Both names and weights */ + igraph_strvector_t nvec; + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph)); + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_int_t edge = IGRAPH_EIT_GET(it); + igraph_int_t from, to; + int ret = 0, ret2 = 0; + const char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + str1 = igraph_strvector_get(&nvec, from); + IGRAPH_CHECK(check_name(str1)); + str2 = igraph_strvector_get(&nvec, to); + IGRAPH_CHECK(check_name(str2)); + ret = fprintf(outstream, "%s %s ", str1, str2); + if (ret < 0) { + IGRAPH_ERROR("Writing NCOL file failed.", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[edge]); + ret2 = fputc('\n', outstream); + if (ret < 0 || ret2 == EOF) { + IGRAPH_ERROR("Writing NCOL file failed.", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/io/pajek-header.h b/src/io/pajek-header.h new file mode 100644 index 0000000..d6d16dd --- /dev/null +++ b/src/io/pajek-header.h @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_bitset.h" +#include "igraph_error.h" +#include "igraph_vector.h" + +#include "core/trie.h" + +/* According to Pajek's author, limits of the Pajek program as of 2022-1-1 are: + * "At the moment regular Pajek has limit one billion vertices, + * PajekXXL two billions, while Pajek 3XL ten billions." + * Hard-coding the limit INT32_MAX is safe when compiling wiht 32-bit integers, + * and likely sufficient for practical applications. + */ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +/* Limit maximum vertex count when using a fuzzer, to avoid out-of-memory failure. */ +#define IGRAPH_PAJEK_MAX_VERTEX_COUNT (1 << 18) +#else +#define IGRAPH_PAJEK_MAX_VERTEX_COUNT INT32_MAX +#endif + +#define CHECK_OOM_RP(p) IGRAPH_CHECK_OOM((p), "Not enough memory to read Pajek format.") +#define CHECK_OOM_WP(p) IGRAPH_CHECK_OOM((p), "Not enough memory to write Pajek format.") + +typedef struct { + void *scanner; + igraph_bool_t eof; + char errmsg[300]; + igraph_error_t igraph_errno; + igraph_vector_int_t *vector; + igraph_bitset_t *seen; + igraph_bool_t directed; + igraph_int_t vcount, vcount2; + igraph_int_t actfrom; + igraph_int_t actto; + igraph_trie_t *vertex_attribute_names; + igraph_attribute_record_list_t *vertex_attributes; + igraph_trie_t *edge_attribute_names; + igraph_attribute_record_list_t *edge_attributes; + igraph_int_t vertexid; + igraph_int_t actvertex; + igraph_int_t actedge; +} igraph_i_pajek_parsedata_t; diff --git a/src/io/pajek.c b/src/io/pajek.c new file mode 100644 index 0000000..0b59398 --- /dev/null +++ b/src/io/pajek.c @@ -0,0 +1,746 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" + +#include "igraph_attributes.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "graph/attributes.h" + +#include "internal/hacks.h" /* IGRAPH_STATIC_ASSERT */ + +#include "io/pajek-header.h" +#include "io/parsers/pajek-parser.h" + +#include +#include + +int igraph_pajek_yylex_init_extra(igraph_i_pajek_parsedata_t *user_defined, + void *scanner); +int igraph_pajek_yylex_destroy(void *scanner); +int igraph_pajek_yyparse(igraph_i_pajek_parsedata_t *context); +void igraph_pajek_yyset_in(FILE *in_str, void *yyscanner); + +/* for IGRAPH_FINALLY, which assumes that destructor functions return void */ +void igraph_pajek_yylex_destroy_wrapper (void *scanner ) { + (void) igraph_pajek_yylex_destroy(scanner); +} + +/** + * \function igraph_read_graph_pajek + * \brief Reads a file in Pajek format. + * + * Only a subset of the Pajek format is implemented. This is partially + * because there is no formal specification for this format, but also because + * igraph does not support some Pajek features, like + * mixed graphs. + * + * + * Starting from version 0.6.1 igraph reads bipartite (two-mode) + * graphs from Pajek files and adds the \c type Boolean vertex attribute for + * them. Warnings are given for invalid edges, i.e. edges connecting + * vertices of the same type. + * + * + * The list of the current limitations: + * \olist + * \oli Only .net files are supported, Pajek + * project files (.paj) are not. + * \oli Temporal networks (i.e. with time events) are not supported. + * \oli Graphs with both directed and non-directed edges are not + * supported, as they cannot be represented in igraph. + * \oli Only Pajek networks are supported; permutations, hierarchies, + * clusters and vectors are not. + * \oli Multi-relational networks (i.e. networks with multiple edge + * types) are not supported. + * \oli Unicode characters encoded as &#dddd;, or newlines + * encoded as \n will not be decoded. + * \endolist + * + * + * If an attribute handler is installed, + * igraph also reads the vertex and edge attributes + * from the file. Most attributes are renamed to be more informative: + * \c color instead of \c c, \c xfact instead of \c x_fact, + * \c yfact instead of y_fact, \c labeldist instead of \c lr, + * \c labeldegree2 instead of \c lphi, \c framewidth instead of \c bw, + * \c fontsize instead of \c fos, \c rotation instead of \c phi, + * \c radius instead of \c r, \c diamondratio instead of \c q, + * \c labeldegree instead of \c la, + * \c color instead of \c ic, \c framecolor instead of \c bc, + * \c labelcolor instead of \c lc; these belong to vertices. + * + * + * Edge attributes are also renamed, \c s to \c arrowsize, + * \c w to \c edgewidth, \c h1 to \c hook1, \c h2 to \c hook2, + * \c a1 to \c angle1, \c a2 to \c angle2, \c k1 to + * \c velocity1, \c k2 to \c velocity2, \c ap to \c arrowpos, + * \c lp to \c labelpos, \c lr to \c labelangle, + * \c lphi to \c labelangle2, \c la to \c labeldegree, + * \c fos to \c fontsize, \c a to \c arrowtype, \c p to \c linepattern, + * \c l to \c label, \c lc to \c labelcolor, \c c to \c color. + * + * + * Unknown vertex or edge parameters are read as string vertex + * or edge attributes. If the parameter name conflicts with one + * the standard attribute names mentioned above, a _ + * character is appended to it to avoid conflict. + * + * + * In addition the following vertex attributes might be added: \c name is added + * (with the same value) if there are vertex IDs in the file. + * \c x and \c y, and potentially \c z are also added if there are vertex + * coordinates in the file. + * + * + * The \c weight edge attribute will be added if there are edge weights present. + * + * + * See the Pajek homepage: + * http://vlado.fmf.uni-lj.si/pub/networks/pajek/ for more info on + * Pajek. The Pajek manual, + * http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf, + * and http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm + * have information on the Pajek file format. There is additional + * useful information and sample files at + * http://mrvar.fdv.uni-lj.si/pajek/history.htm + * + * \param graph Pointer to an uninitialized graph object. + * \param instream An already opened file handler. + * \return Error code. + * + * Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| + * the number of edges, |A| the number of attributes (vertex + edge) + * in the graph if there are attribute handlers installed. + * + * \sa \ref igraph_write_graph_pajek() for writing Pajek files, \ref + * igraph_read_graph_graphml() for reading GraphML files. + * + * \example examples/simple/foreign.c + */ + +igraph_error_t igraph_read_graph_pajek(igraph_t *graph, FILE *instream) { + + igraph_vector_int_t edges; + igraph_trie_t vattrnames; + igraph_attribute_record_list_t vattrs; + igraph_trie_t eattrnames; + igraph_attribute_record_list_t eattrs; + igraph_int_t i; + igraph_i_pajek_parsedata_t context; + igraph_bitset_t seen; /* used to mark already seen vertex IDs */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + IGRAPH_TRIE_INIT_FINALLY(&vattrnames, 1); + IGRAPH_CHECK(igraph_attribute_record_list_init(&vattrs, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &vattrs); + + IGRAPH_TRIE_INIT_FINALLY(&eattrnames, 1); + IGRAPH_CHECK(igraph_attribute_record_list_init(&eattrs, 0)); + IGRAPH_FINALLY(igraph_attribute_record_list_destroy, &eattrs); + + IGRAPH_BITSET_INIT_FINALLY(&seen, 0); + + context.directed = false; /* assume undirected until an element implying directedness is encountered */ + context.vector = &edges; + context.seen = &seen; + context.vcount = -1; + context.vertexid = 0; + context.vertex_attribute_names = &vattrnames; + context.vertex_attributes = &vattrs; + context.edge_attribute_names = &eattrnames; + context.edge_attributes = &eattrs; + context.actedge = 0; + context.eof = false; + context.errmsg[0] = '\0'; + context.igraph_errno = IGRAPH_SUCCESS; + + igraph_pajek_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_pajek_yylex_destroy_wrapper, context.scanner); + + igraph_pajek_yyset_in(instream, context.scanner); + + /* Use ENTER/EXIT to avoid destroying context.scanner before this function returns */ + IGRAPH_FINALLY_ENTER(); + int err = igraph_pajek_yyparse(&context); + IGRAPH_FINALLY_EXIT(); + switch (err) { + case 0: /* success */ + break; + case 1: /* parse error */ + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else if (context.igraph_errno != IGRAPH_SUCCESS) { + IGRAPH_ERROR("", context.igraph_errno); + } else { + IGRAPH_ERROR("Cannot read Pajek file.", IGRAPH_PARSEERROR); + } + break; + case 2: /* out of memory */ + IGRAPH_ERROR("Cannot read Pajek file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + default: /* must never reach here */ + /* Hint: This will usually be triggered if an IGRAPH_CHECK() is used in a Bison + * action instead of an IGRAPH_YY_CHECK(), resulting in an igraph errno being + * returned in place of a Bison error code. + * TODO: What if future Bison versions introduce error codes other than 0, 1 and 2? + */ + IGRAPH_FATALF("Parser returned unexpected error code (%d) when reading Pajek file.", err); + } + + igraph_pajek_yylex_destroy(context.scanner); + igraph_bitset_destroy(&seen); + IGRAPH_FINALLY_CLEAN(2); + + /* Prepare attributes */ + const igraph_int_t eattr_count = igraph_attribute_record_list_size(&eattrs); + for (i = 0; i < eattr_count; i++) { + igraph_attribute_record_t *rec = igraph_attribute_record_list_get_ptr(&eattrs, i); + IGRAPH_CHECK(igraph_attribute_record_resize(rec, context.actedge)); + } + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, 0, context.directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, context.vcount, &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &eattrs)); + + igraph_vector_int_destroy(&edges); + igraph_attribute_record_list_destroy(&eattrs); + igraph_trie_destroy(&eattrnames); + igraph_attribute_record_list_destroy(&vattrs); + igraph_trie_destroy(&vattrnames); + IGRAPH_FINALLY_CLEAN(6); /* +1 for 'graph' */ + + return IGRAPH_SUCCESS; +} + +/***** Writing Pajek files *****/ + +/* Order matters here! */ +#define V_ID 0 +#define V_X 1 +#define V_Y 2 +#define V_Z 3 +#define V_SHAPE 4 +#define V_XFACT 5 +#define V_YFACT 6 +#define V_LABELDIST 7 +#define V_LABELDEGREE2 8 +#define V_FRAMEWIDTH 9 +#define V_FONTSIZE 10 +#define V_ROTATION 11 +#define V_RADIUS 12 +#define V_DIAMONDRATIO 13 +#define V_LABELDEGREE 14 +#define V_FONT 15 +#define V_URL 16 +#define V_COLOR 17 +#define V_FRAMECOLOR 18 +#define V_LABELCOLOR 19 +#define V_LAST 20 + +#define E_WEIGHT 0 +#define E_LAST 1 + +/* Pajek encodes newlines as \n, and any unicode character can be encoded + * in the form &#hhhh;. Therefore we encode quotation marks as " */ +static igraph_error_t pajek_escape(const char* src, char** dest) { + igraph_int_t destlen = 0; + igraph_bool_t need_escape = false; + + /* Determine whether the string contains characters to be escaped */ + const char *s; + char *d; + for (s = src; *s; s++, destlen++) { + if (*s == '\n' || *s == '\r') { + need_escape = true; + destlen++; + } else if (*s == '"') { + need_escape = true; + destlen += 4; + } else if (!isalnum(*s)) { + need_escape = true; + } + } + + if (!need_escape) { + /* At this point, we know that the string does not contain any chars + * that would warrant encoding. Therefore, we simply quote it and + * return the quoted string. This is necessary because Pajek uses some + * reserved words in its format (like 'c' standing for color) and they + * have to be quoted as well. + */ + *dest = IGRAPH_CALLOC(destlen + 3, char); + CHECK_OOM_WP(*dest); + + d = *dest; + strcpy(d + 1, src); + d[0] = d[destlen + 1] = '"'; + d[destlen + 2] = 0; + return IGRAPH_SUCCESS; + } + + *dest = IGRAPH_CALLOC(destlen + 3, char); + CHECK_OOM_WP(*dest); + + d = *dest; + *d = '"'; d++; + + for (s = src; *s; s++, d++) { + switch (*s) { + /* Encode quotation marks as ", as they would otherwise signify + the end/beginning of a string. */ + case '"': + strcpy(d, """); d += 4; break; + break; + /* Encode both CR and LF as \n, as neither should apear in a quoted string. + \n is the _only_ escape sequence Pajek understands. */ + case '\n': + case '\r': + *d = '\\'; d++; + *d = 'n'; + break; + default: + *d = *s; + } + } + *d = '"'; d++; *d = 0; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_write_graph_pajek + * \brief Writes a graph to a file in Pajek format. + * + * Writes files in the native format of the Pajek software. This format + * is not recommended for data exchange or archival. It is meant solely + * for interoperability with Pajek. + * + * + * The Pajek vertex and edge parameters (like color) are determined by + * the attributes of the vertices and edges. Of course this requires + * an attribute handler to be installed. The names of the + * corresponding vertex and edge attributes are listed at \ref + * igraph_read_graph_pajek(), e.g. the \c color vertex attributes + * determines the color (\c c in Pajek) parameter. + * + * + * Vertex and edge attributes that do not correspond to any documented + * Pajek parameter are discarded. + * + * + * As of version 0.6.1 igraph writes bipartite graphs into Pajek files + * correctly, i.e. they will be also bipartite when read into Pajek. + * As Pajek is less flexible for bipartite graphs (the numeric IDs of + * the vertices must be sorted according to vertex type), igraph might + * need to reorder the vertices when writing a bipartite Pajek file. + * This effectively means that numeric vertex IDs usually change when + * a bipartite graph is written to a Pajek file, and then read back + * into igraph. + * + * + * Early versions of Pajek supported only Windows-style line endings + * in Pajek files, but recent versions support both Windows and Unix + * line endings. igraph therefore uses the platform-native line endings + * when the input file is opened in text mode, and uses Unix-style + * line endings when the input file is opened in binary mode. If you + * are using an old version of Pajek, you are on Unix and you are having + * problems reading files written by igraph on a Windows machine, convert the + * line endings manually with a text editor or with \c unix2dos or \c iconv + * from the command line). + * + * + * Pajek will only interpret UTF-8 encoded files if they contain a byte-order + * mark (BOM) at the beginning. igraph is agnostic of string attribute encodings + * and therefore it will never write a BOM. You need to add this manually + * if/when necessary. + * + * \param graph The graph object to write. + * \param outstream The file to write to. It should be opened and writable. + * \return Error code. + * + * Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| + * is the number of edges, |A| the number of attributes (vertex + + * edge) in the graph if there are attribute handlers installed. + * + * \sa \ref igraph_read_graph_pajek() for reading Pajek graphs, \ref + * igraph_write_graph_graphml() for writing a graph in GraphML format, + * this suites igraph graphs better. + * + * \example examples/simple/igraph_write_graph_pajek.c + */ + +igraph_error_t igraph_write_graph_pajek(const igraph_t *graph, FILE *outstream) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + + igraph_attribute_type_t vtypes[V_LAST], etypes[E_LAST]; + igraph_bool_t write_vertex_attrs = false; + + /* Same order as the #define's */ + const char *vnames[] = { "name", "x", "y", "z", "shape", "xfact", "yfact", + "labeldist", "labeldegree2", "framewidth", + "fontsize", "rotation", "radius", + "diamondratio", "labeldegree", + "font", "url", "color", "framecolor", + "labelcolor" + }; + IGRAPH_STATIC_ASSERT(sizeof(vnames) / sizeof(vnames[0]) == V_LAST); + + /* Arrays called xxx[] are igraph attribute names, + * xxx2[] are the corresponding Pajek names. */ + const char *vnumnames[] = { "xfact", "yfact", "labeldist", + "labeldegree2", "framewidth", "fontsize", + "rotation", "radius", "diamondratio", + "labeldegree" + }; + const char *vnumnames2[] = { "x_fact", "y_fact", "lr", "lphi", "bw", + "fos", "phi", "r", "q", "la" + }; + IGRAPH_STATIC_ASSERT(sizeof(vnumnames) == sizeof(vnumnames2)); + + const char *vstrnames[] = { "font", "url", "color", "framecolor", + "labelcolor" + }; + const char *vstrnames2[] = { "font", "url", "ic", "bc", "lc" }; + IGRAPH_STATIC_ASSERT(sizeof(vstrnames) == sizeof(vstrnames2)); + + /* Same order as the #define's */ + const char *enames[] = { "weight" }; + IGRAPH_STATIC_ASSERT(sizeof(enames) / sizeof(enames[0]) == E_LAST); + + const char *enumnames[] = { "arrowsize", "edgewidth", "hook1", "hook2", + "angle1", "angle2", "velocity1", "velocity2", + "arrowpos", "labelpos", "labelangle", + "labelangle2", "labeldegree", "fontsize" + }; + const char *enumnames2[] = { "s", "w", "h1", "h2", "a1", "a2", "k1", "k2", + "ap", "lp", "lr", "lphi", "la", "fos" + }; + IGRAPH_STATIC_ASSERT(sizeof(enumnames) == sizeof(enumnames2)); + + const char *estrnames[] = { "arrowtype", "linepattern", "label", + "labelcolor", "color", "font" + }; + const char *estrnames2[] = { "a", "p", "l", "lc", "c", "font" }; + IGRAPH_STATIC_ASSERT(sizeof(estrnames) == sizeof(estrnames2)); + + /* Newer Pajek versions support both Unix and Windows-style line endings, + * so we just use Unix style. This will get converted to CRLF on Windows + * when the file is opened in text mode */ + const char *newline = "\n"; + + igraph_es_t es; + igraph_eit_t eit; + + igraph_vector_t numv; + igraph_strvector_t strv; + + igraph_vector_int_t ex_numa; + igraph_vector_int_t ex_stra; + igraph_vector_int_t vx_numa; + igraph_vector_int_t vx_stra; + + const char *s; + char *escaped; + + igraph_bool_t bipartite = false; + igraph_vector_int_t bip_index, bip_index2; + igraph_vector_bool_t bvec; + igraph_int_t notop = 0, nobottom = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ex_numa, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ex_stra, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vx_numa, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vx_stra, 0); + + /* Check if graph is bipartite, i.e. whether it has a Boolean 'type' vertex attribute. */ + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, "type")) { + igraph_attribute_type_t type_type; + IGRAPH_CHECK(igraph_i_attribute_get_type(graph, &type_type, IGRAPH_ATTRIBUTE_VERTEX, "type")); + if (type_type == IGRAPH_ATTRIBUTE_BOOLEAN) { + bipartite = true; write_vertex_attrs = true; + /* Count top and bottom vertices, we go over them twice, + because we want to keep their original order */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&bip_index, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&bip_index2, no_of_nodes); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&bvec, 1); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, + "type", igraph_vss_1(i), &bvec)); + if (VECTOR(bvec)[0]) { + notop++; + } else { + nobottom++; + } + } + for (igraph_int_t i = 0, bptr = 0, tptr = nobottom; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, + "type", igraph_vss_1(i), &bvec)); + if (VECTOR(bvec)[0]) { + VECTOR(bip_index)[tptr] = i; + VECTOR(bip_index2)[i] = tptr; + tptr++; + } else { + VECTOR(bip_index)[bptr] = i; + VECTOR(bip_index2)[i] = bptr; + bptr++; + } + } + igraph_vector_bool_destroy(&bvec); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* Write header */ + if (bipartite) { + if (fprintf(outstream, "*Vertices %" IGRAPH_PRId " %" IGRAPH_PRId "%s", no_of_nodes, nobottom, + newline) < 0) { + IGRAPH_ERROR("Cannot write pajek file.", IGRAPH_EFILE); + } + } else { + if (fprintf(outstream, "*Vertices %" IGRAPH_PRId "%s", no_of_nodes, newline) < 0) { + IGRAPH_ERROR("Cannot write pajek file.", IGRAPH_EFILE); + } + } + + /* Check the vertex attributes, and determine if we need to write them. */ + memset(vtypes, 0, sizeof(vtypes[0])*V_LAST); + for (igraph_int_t i = 0; i < V_LAST; i++) { + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, vnames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &vtypes[i], IGRAPH_ATTRIBUTE_VERTEX, vnames[i])); + write_vertex_attrs = true; + } else { + vtypes[i] = (igraph_attribute_type_t) -1; + } + } + for (igraph_int_t i = 0; i < (igraph_int_t) (sizeof(vnumnames) / sizeof(vnumnames[0])); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, vnumnames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &type, IGRAPH_ATTRIBUTE_VERTEX, vnumnames[i])); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_vector_int_push_back(&vx_numa, i)); + } + } + } + for (igraph_int_t i = 0; i < (igraph_int_t) (sizeof(vstrnames) / sizeof(vstrnames[0])); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, vstrnames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &type, IGRAPH_ATTRIBUTE_VERTEX, vstrnames[i])); + if (type == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_vector_int_push_back(&vx_stra, i)); + } + } + } + + /* Write vertices */ + if (write_vertex_attrs) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t id = bipartite ? VECTOR(bip_index)[i] : i; + + /* vertex ID */ + fprintf(outstream, "%" IGRAPH_PRId, i + 1); + if (vtypes[V_ID] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vnames[V_ID], igraph_vss_1(id), &numv)); + fputs(" \"", outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + fputc('"', outstream); + } else if (vtypes[V_ID] == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr( + graph, vnames[V_ID], igraph_vss_1(id), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(pajek_escape(s, &escaped)); + fprintf(outstream, " %s", escaped); + IGRAPH_FREE(escaped); + } else { + fprintf(outstream, " \"%" IGRAPH_PRId "\"", id + 1); + } + + /* coordinates */ + if (vtypes[V_X] == IGRAPH_ATTRIBUTE_NUMERIC && + vtypes[V_Y] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vnames[V_X], igraph_vss_1(id), &numv)); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vnames[V_Y], igraph_vss_1(id), &numv)); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (vtypes[V_Z] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, vnames[V_Z], + igraph_vss_1(id), &numv)); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + } + + /* shape */ + if (vtypes[V_SHAPE] == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr( + graph, vnames[V_SHAPE], igraph_vss_1(id), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(pajek_escape(s, &escaped)); + fprintf(outstream, " %s", escaped); + IGRAPH_FREE(escaped); + } + + /* numeric parameters */ + for (igraph_int_t j = 0; j < igraph_vector_int_size(&vx_numa); j++) { + igraph_int_t idx = VECTOR(vx_numa)[j]; + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vnumnames[idx], igraph_vss_1(id), &numv)); + fprintf(outstream, " %s ", vnumnames2[idx]); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* string parameters */ + for (igraph_int_t j = 0; j < igraph_vector_int_size(&vx_stra); j++) { + igraph_int_t idx = VECTOR(vx_stra)[j]; + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr( + graph, vstrnames[idx], igraph_vss_1(id), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(pajek_escape(s, &escaped)); + fprintf(outstream, " %s %s", vstrnames2[idx], escaped); + IGRAPH_FREE(escaped); + } + + /* trailing newline */ + fprintf(outstream, "%s", newline); + } + } + + /* edges header */ + if (igraph_is_directed(graph)) { + fprintf(outstream, "*Arcs%s", newline); + } else { + fprintf(outstream, "*Edges%s", newline); + } + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + /* Check edge attributes */ + /* TODO: refactor and simplify since only "weight" is relevant */ + for (igraph_int_t i = 0; i < E_LAST; i++) { + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, enames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &etypes[i], IGRAPH_ATTRIBUTE_EDGE, enames[i])); + } else { + etypes[i] = (igraph_attribute_type_t) -1; + } + } + for (igraph_int_t i = 0; i < (igraph_int_t) (sizeof(enumnames) / sizeof(enumnames[0])); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, enumnames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &type, IGRAPH_ATTRIBUTE_EDGE, enumnames[i])); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_vector_int_push_back(&ex_numa, i)); + } + } + } + for (igraph_int_t i = 0; i < (igraph_int_t) (sizeof(estrnames) / sizeof(estrnames[0])); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, estrnames[i])) { + IGRAPH_CHECK(igraph_i_attribute_get_type( + graph, &type, IGRAPH_ATTRIBUTE_EDGE, estrnames[i])); + if (type == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_vector_int_push_back(&ex_stra, i)); + } + } + } + + for (igraph_int_t i = 0; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit), i++) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + igraph_edge(graph, edge, &from, &to); + if (bipartite) { + from = VECTOR(bip_index2)[from]; + to = VECTOR(bip_index2)[to]; + } + fprintf(outstream, "%" IGRAPH_PRId " %" IGRAPH_PRId , from + 1, to + 1); + + /* Weights */ + if (etypes[E_WEIGHT] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr( + graph, enames[E_WEIGHT], igraph_ess_1(edge), &numv)); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* numeric parameters */ + for (igraph_int_t j = 0; j < igraph_vector_int_size(&ex_numa); j++) { + igraph_int_t idx = VECTOR(ex_numa)[j]; + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr( + graph, enumnames[idx], igraph_ess_1(edge), &numv)); + fprintf(outstream, " %s ", enumnames2[idx]); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* string parameters */ + for (igraph_int_t j = 0; j < igraph_vector_int_size(&ex_stra); j++) { + igraph_int_t idx = VECTOR(ex_stra)[j]; + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr( + graph, estrnames[idx], igraph_ess_1(edge), &strv)); + s = igraph_strvector_get(&strv, 0); + IGRAPH_CHECK(pajek_escape(s, &escaped)); + fprintf(outstream, " %s %s", estrnames2[idx], escaped); + IGRAPH_FREE(escaped); + } + + /* trailing newline */ + fprintf(outstream, "%s", newline); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + if (bipartite) { + igraph_vector_int_destroy(&bip_index2); + igraph_vector_int_destroy(&bip_index); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_int_destroy(&ex_numa); + igraph_vector_int_destroy(&ex_stra); + igraph_vector_int_destroy(&vx_numa); + igraph_vector_int_destroy(&vx_stra); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + IGRAPH_FINALLY_CLEAN(6); + return IGRAPH_SUCCESS; +} diff --git a/src/io/parse_utils.c b/src/io/parse_utils.c new file mode 100644 index 0000000..6be36aa --- /dev/null +++ b/src/io/parse_utils.c @@ -0,0 +1,377 @@ + +#include "parse_utils.h" + +#include "igraph_foreign.h" +#include "igraph_memory.h" + +#include "config.h" /* HAVE_XLOCALE */ + +#include +#include +#include +#include + +#if defined(HAVE_XLOCALE) +/* On some systems, xlocale.h exists, but uselocale() is still in locale.h. + * Thus we include both. */ +#include +#include +#else +#include +#endif + +/* Trims whitespace from the beginning and the end of a string with a specified length. + * A pointer to the first character of the result substring, as well as its length, are returned. + * + * If you have a null-terminated string, call this function as + * + * igraph_i_trim_whitespace(str, strlen(str), &res, &len); + * + * This does not carry a performance penalty, as the end of the string would need to be + * determined anyway. + */ +void igraph_i_trim_whitespace(const char *str, size_t str_len, const char **res, size_t *res_len) { + const char *beg = str, *end = str + str_len; + while (beg < end && isspace(beg[0]) ) beg++; + while (end > beg && isspace(end[-1])) end--; + *res = beg; + *res_len = end - beg; +} + + +/* TODO: Support for reporting line number where parse error occurred. */ + +/* Converts a string to an integer. Throws an error if the result is not representable. + * + * The input is a not-necessarily-null-terminated string that must contain only the number. + * Any additional characters at the end of the string, such as whitespace, will trigger + * a parsing error. + * + * An error is returned if the input is an empty string. + */ +igraph_error_t igraph_i_parse_integer(const char *str, size_t length, igraph_int_t *value) { + char buffer[128]; + char *tmp, *end; + char last_char; + igraph_bool_t out_of_range, dynamic_alloc; + long long val; + + if (length == 0) { + IGRAPH_ERROR("Cannot parse integer from empty string.", IGRAPH_PARSEERROR); + } + + dynamic_alloc = length+1 > sizeof(buffer) / sizeof(buffer[0]); + + if (dynamic_alloc) { + tmp = IGRAPH_CALLOC(length+1, char); + IGRAPH_CHECK_OOM(tmp, "Failed to parse integer."); + } else { + tmp = buffer; + } + + strncpy(tmp, str, length); + tmp[length]='\0'; + + /* To avoid having to choose the appropriate strto?() function based on + * the definition of igraph_int_t, we first use a long long variable + * which should be at least as large as igraph_int_t on any platform. */ + errno = 0; + val = strtoll(tmp, &end, 10); + out_of_range = errno == ERANGE; + *value = (igraph_int_t) val; + last_char = *end; + if (*value != val) { + out_of_range = true; + } + + /* Free memory before raising any errors. */ + if (dynamic_alloc) { + IGRAPH_FREE(tmp); + } + + if (out_of_range) { + IGRAPH_ERROR("Failed to parse integer.", val > 0 ? IGRAPH_EOVERFLOW : IGRAPH_EUNDERFLOW); + } + + /* Did we parse to the end of the string? */ + if (last_char) { + IGRAPH_ERRORF("Unexpected character '%c' while parsing integer.", IGRAPH_PARSEERROR, last_char); + } + + return IGRAPH_SUCCESS; +} + + +/* Converts a string to a real number. Throws an error if the result is not representable. + * + * The input is a not-necessarily-null-terminated string that must contain only the number. + * Any additional characters at the end of the string, such as whitespace, will trigger + * a parsing error. + * + * NaN and Inf are supported. An error is returned if the input is an empty string. + */ +igraph_error_t igraph_i_parse_real(const char *str, size_t length, igraph_real_t *value) { + char buffer[128]; + char *tmp, *end; + char last_char; + igraph_bool_t out_of_range, dynamic_alloc; + + if (length == 0) { + IGRAPH_ERROR("Cannot parse real number from empty string.", IGRAPH_PARSEERROR); + } + + dynamic_alloc = length+1 > sizeof(buffer) / sizeof(buffer[0]); + + if (dynamic_alloc) { + tmp = IGRAPH_CALLOC(length+1, char); + IGRAPH_CHECK_OOM(tmp, "Failed to parse real number."); + } else { + tmp = buffer; + } + + strncpy(tmp, str, length); + tmp[length]='\0'; + + errno = 0; + *value = strtod(tmp, &end); + out_of_range = errno == ERANGE; /* This does not trigger when reading +-Inf. */ + last_char = *end; + + /* Free memory before raising any errors. */ + if (dynamic_alloc) { + IGRAPH_FREE(tmp); + } + + if (out_of_range) { + IGRAPH_ERROR("Failed to parse real number.", *value > 0 ? IGRAPH_EOVERFLOW : IGRAPH_EUNDERFLOW); + } + + /* Did we parse to the end of the string? */ + if (last_char) { + IGRAPH_ERRORF("Unexpected character '%c' while parsing real number.", IGRAPH_PARSEERROR, last_char); + } + + return IGRAPH_SUCCESS; +} + + +/* Skips all whitespace in a file. */ +igraph_error_t igraph_i_fskip_whitespace(FILE *file) { + int ch; + + do { + ch = fgetc(file); + } while (isspace(ch)); + if (ferror(file)) { + IGRAPH_ERROR("Error reading file.", IGRAPH_EFILE); + } + ungetc(ch, file); + + return IGRAPH_SUCCESS; +} + + +/* Reads an integer from a file. Throws an error if the result is not representable. + * + * Any initial whitespace is skipped. If no number is found, an error is raised. + * + * This function assumes that the number is followed by whitespace or the end of the file. + * If this is not the case, an error will be raised. + */ +igraph_error_t igraph_i_fget_integer(FILE *file, igraph_int_t *value) { + /* The value requiring the most characters on 64-bit is -2^63, i.e. "-9223372036854775808". + * This is 20 characters long, plus one for the null terminator, requiring a buffer of + * at least 21 characters. We use a slightly larger buffer to allow for leading zeros and + * clearer error messages. + * + * Note: The string held in this buffer is not null-terminated. + */ + char buf[32]; + int ch; + + IGRAPH_CHECK(igraph_i_fskip_whitespace(file)); + + int i = 0; /* must be 'int' due to use in printf format specifier */ + while (1) { + ch = fgetc(file); + if (ch == EOF) break; + if (isspace(ch)) { + ungetc(ch, file); + break; + } + if (i == sizeof(buf)) { + /* Reached the end of the buffer. */ + IGRAPH_ERRORF("'%.*s' is not a valid integer value.", IGRAPH_PARSEERROR, i, buf); + } + buf[i++] = ch; + } + if (ferror(file)) { + IGRAPH_ERROR("Error while reading integer.", IGRAPH_EFILE); + } + + if (i == 0) { + IGRAPH_ERROR("Integer expected, reached end of file instead.", IGRAPH_PARSEERROR); + } + + IGRAPH_CHECK(igraph_i_parse_integer(buf, i, value)); + + return IGRAPH_SUCCESS; +} + + +/* Reads a real number from a file. Throws an error if the result is not representable. + * + * Any initial whitespace is skipped. If no number is found, an error is raised. + * + * This function assumes that the number is followed by whitespace or the end of the file. + * If this is not the case, an error will be raised. + */ +igraph_error_t igraph_i_fget_real(FILE *file, igraph_real_t *value) { + /* The value requiring the most characters with an IEEE-754 double is the smallest + * representable number, with signs added, "-2.2250738585072014e-308" + * + * This is 24 characters long, plus one for the null terminator, requiring a buffer of + * at least 25 characters. This is 17 mantissa digits for lossless representation, + * 3 exponent digits, "e", and up to two minus signs. We use a larger buffer as some + * files may have more digits specified than necessary for exact representation. + * + * Note: The string held in this buffer is not null-terminated. + */ + char buf[64]; + int ch; + + IGRAPH_CHECK(igraph_i_fskip_whitespace(file)); + + int i = 0; /* must be 'int' due to use in printf format specifier */ + while (1) { + ch = fgetc(file); + if (ch == EOF) break; + if (isspace(ch)) { + ungetc(ch, file); + break; + } + if (i == sizeof(buf)) { + /* Reached the end of the buffer. */ + IGRAPH_ERRORF("'%.*s' is not a valid real value.", IGRAPH_PARSEERROR, i, buf); + } + buf[i++] = ch; + } + if (ferror(file)) { + IGRAPH_ERROR("Error while reading real number.", IGRAPH_EFILE); + } + + if (i == 0) { + IGRAPH_ERROR("Real number expected, reached end of file instead.", IGRAPH_PARSEERROR); + } + + IGRAPH_CHECK(igraph_i_parse_real(buf, i, value)); + + return IGRAPH_SUCCESS; +} + + +/* igraph_i_safelocale() and igraph_i_unsafelocale() will set the numeric locale to "C" + * and re-set it to its original value. This is to ensure that parsing and writing + * numbers uses a decimal point instead of a comma. + * + * These functions attempt to set the locale only for the current thread on a best-effort + * basis. On some platforms this is not possible, so the global locale will be changed. + * This is not safe to do in multi-threaded programs (not even if igraph runs only in + * a single thread). + */ + +struct igraph_safelocale_s { +#ifdef HAVE_USELOCALE + locale_t original_locale; + locale_t c_locale; +#else + char *original_locale; +# ifdef HAVE__CONFIGTHREADLOCALE + int per_thread_locale; +# endif +#endif +}; + +/** + * \function igraph_enter_safelocale + * \brief Temporarily set the C locale. + * + * igraph's foreign format readers and writers require a locale that uses a + * decimal point instead of a decimal comma. This is a convenience function + * that temporarily sets the C locale so that readers and writers would work + * correctly. It \em must be paired with a call to \ref igraph_exit_safelocale(), + * otherwise a memory leak will occur. + * + * + * This function tries to set the locale for the current thread only on a + * best-effort basis. Restricting the locale change to a single thread is not + * supported on all platforms. In these cases, this function falls back to using + * the standard setlocale() function, which affects the entire process + * and is not safe to use from concurrent threads. + * + * + * It is generally recommended to run igraph within a thread that has been + * permanently set to the C locale using system-specific means. This is a convenience + * function for situations when this is not easily possible because the programmer + * is not in control of the process, such as when developing plugins/extensions. + * Note that processes start up in the C locale by default, thus nothing needs to + * be done unless the locale has been changed away from the default. + * + * \param loc Pointer to a variable of type \c igraph_safelocale_t. The current + * locale will be stored here, so that it can be restored using + * \ref igraph_exit_safelocale(). + * \return Error code. + * + * \example examples/simple/safelocale.c + */ + +igraph_error_t igraph_enter_safelocale(igraph_safelocale_t *loc) { + *loc = IGRAPH_CALLOC(1, struct igraph_safelocale_s); + IGRAPH_CHECK_OOM(loc, "Could not set C locale."); + igraph_safelocale_t l = *loc; +#ifdef HAVE_USELOCALE + l->c_locale = newlocale(LC_NUMERIC_MASK, "C", NULL); + if (! l->c_locale) { + IGRAPH_ERROR("Could not set C locale.", IGRAPH_FAILURE); + } + l->original_locale = uselocale(l->c_locale); +#else + l->original_locale = strdup(setlocale(LC_NUMERIC, NULL)); + IGRAPH_CHECK_OOM(l->original_locale, "Not enough memory."); +# ifdef HAVE__CONFIGTHREADLOCALE + /* On Windows, we can enable per-thread locale */ + l->per_thread_locale = _configthreadlocale(0); + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); +# endif + setlocale(LC_NUMERIC, "C"); +#endif + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_exit_safelocale + * \brief Temporarily set the C locale. + * + * Restores a locale saved by \ref igraph_enter_safelocale() and deallocates + * all associated data. This function \em must be paired with a call to + * \ref igraph_enter_safelocale(). + * + * \param loc A variable of type \c igraph_safelocale_t, originally set + * by \ref igraph_enter_safelocale(). + */ + +void igraph_exit_safelocale(igraph_safelocale_t *loc) { + igraph_safelocale_t l = *loc; +#ifdef HAVE_USELOCALE + uselocale(l->original_locale); + freelocale(l->c_locale); +#else + setlocale(LC_NUMERIC, l->original_locale); + IGRAPH_FREE(l->original_locale); +# ifdef HAVE__CONFIGTHREADLOCALE + /* Restore per-thread locale setting on Windows */ + _configthreadlocale(l->per_thread_locale); +# endif +#endif + IGRAPH_FREE(*loc); +} diff --git a/src/io/parse_utils.h b/src/io/parse_utils.h new file mode 100644 index 0000000..b94a3b1 --- /dev/null +++ b/src/io/parse_utils.h @@ -0,0 +1,41 @@ + +#ifndef IGRAPH_PARSE_UTILS_H +#define IGRAPH_PARSE_UTILS_H + +#include "igraph_error.h" +#include "igraph_types.h" + +/* This macro must be used only in Bison actions, in place of IGRAPH_CHECK(). */ +#define IGRAPH_YY_CHECK(expr) \ + do { \ + igraph_error_t igraph_i_ret = (expr); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != IGRAPH_SUCCESS)) { \ + context->igraph_errno = igraph_i_ret; \ + yyerror(&yylloc, context, "failed"); \ + YYABORT; \ + } \ + } while (0) + +/* This macro must be used only in Bison actions, in place of IGRAPH_CHECK(). */ +/* Note: + * Don't name macro argument 'igraph_errno' due to use of context->igraph_errno, + * or 'errno' due to use of #include in parse_utils.c. */ +#define IGRAPH_YY_ERRORF(reason, error_code, ...) \ + do { \ + igraph_errorf(reason, IGRAPH_FILE_BASENAME, __LINE__, \ + error_code, __VA_ARGS__) ; \ + context->igraph_errno = error_code; \ + YYABORT; \ + } while (0) + +void igraph_i_trim_whitespace(const char *str, size_t str_len, const char **res, size_t *res_len); + +igraph_error_t igraph_i_fskip_whitespace(FILE *file); + +igraph_error_t igraph_i_parse_integer(const char *str, size_t length, igraph_int_t *value); +igraph_error_t igraph_i_parse_real(const char *str, size_t length, igraph_real_t *value); + +igraph_error_t igraph_i_fget_integer(FILE *file, igraph_int_t *value); +igraph_error_t igraph_i_fget_real(FILE *file, igraph_real_t *value); + +#endif /* IGRAPH_PARSE_UTILS_H */ diff --git a/src/io/parsers/dl-lexer.c b/src/io/parsers/dl-lexer.c new file mode 100644 index 0000000..a53a0f1 --- /dev/null +++ b/src/io/parsers/dl-lexer.c @@ -0,0 +1,2532 @@ + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_dl_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_dl_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_dl_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_dl_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_dl_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_dl_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_dl_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_dl_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_dl_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_dl_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_dl_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_dl_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_dl_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_dl_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_dl_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_dl_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_dl_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_dl_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_dl_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_dl_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_dl_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_dl_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_dl_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_dl_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_dl_yylex_ALREADY_DEFINED +#else +#define yylex igraph_dl_yylex +#endif + +#ifdef yyrestart +#define igraph_dl_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_dl_yyrestart +#endif + +#ifdef yylex_init +#define igraph_dl_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_dl_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_dl_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_dl_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_dl_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_dl_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_dl_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_dl_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_dl_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_dl_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_dl_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_dl_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_dl_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_dl_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_dl_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_dl_yyget_in +#endif + +#ifdef yyset_in +#define igraph_dl_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_dl_yyset_in +#endif + +#ifdef yyget_out +#define igraph_dl_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_dl_yyget_out +#endif + +#ifdef yyset_out +#define igraph_dl_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_dl_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_dl_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_dl_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_dl_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_dl_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_dl_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_dl_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_dl_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_dl_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_dl_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_dl_yyget_column +#endif + +#ifdef yyset_column +#define igraph_dl_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_dl_yyset_column +#endif + +#ifdef yywrap +#define igraph_dl_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_dl_yywrap +#endif + +#ifdef yyget_lval +#define igraph_dl_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_dl_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_dl_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_dl_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_dl_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_dl_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_dl_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_dl_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_dl_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_dl_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_dl_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_dl_yyrealloc +#endif + +#ifdef yyfree +#define igraph_dl_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_dl_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + yy_size_t yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +#define igraph_dl_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 24 +#define YY_END_OF_BUFFER 25 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[129] = + { 0, + 0, 0, 0, 0, 0, 0, 18, 18, 21, 21, + 25, 23, 22, 1, 1, 4, 23, 23, 23, 23, + 12, 11, 12, 12, 14, 15, 13, 17, 18, 17, + 16, 20, 21, 19, 22, 1, 4, 0, 0, 0, + 0, 0, 3, 12, 12, 12, 12, 14, 13, 17, + 18, 16, 17, 17, 20, 21, 19, 0, 2, 0, + 0, 3, 12, 12, 16, 17, 16, 0, 0, 0, + 12, 12, 5, 0, 0, 5, 12, 0, 0, 12, + 0, 0, 0, 6, 12, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 7, 9, 0, + 10, 7, 7, 9, 8, 10, 8, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 6, 7, 8, 9, 1, 10, 11, 10, + 10, 10, 10, 10, 10, 10, 10, 12, 1, 1, + 13, 1, 1, 1, 14, 15, 1, 16, 17, 18, + 19, 1, 20, 1, 1, 21, 22, 23, 24, 1, + 1, 25, 26, 27, 28, 1, 1, 29, 1, 1, + 1, 1, 1, 1, 1, 1, 30, 31, 1, 32, + + 33, 34, 35, 1, 36, 1, 1, 37, 38, 39, + 40, 1, 1, 41, 42, 43, 44, 1, 1, 45, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[47] = + { 0, + 1, 2, 3, 3, 2, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3 + } ; + +static const flex_int16_t yy_base[138] = + { 0, + 0, 38, 76, 121, 166, 211, 256, 301, 346, 391, + 101, 493, 4, 72, 64, 2, 1, 4, 0, 22, + 15, 493, 55, 60, 0, 493, 24, 0, 31, 35, + 77, 0, 45, 41, 53, 493, 53, 39, 66, 48, + 69, 91, 93, 97, 101, 107, 112, 0, 113, 0, + 114, 121, 110, 138, 0, 131, 129, 77, 150, 107, + 118, 154, 158, 173, 169, 151, 154, 41, 127, 145, + 179, 187, 493, 156, 159, 191, 193, 192, 198, 202, + 215, 436, 221, 493, 232, 0, 193, 183, 205, 208, + 212, 211, 220, 224, 223, 232, 251, 253, 250, 250, + + 252, 258, 255, 262, 257, 262, 253, 253, 255, 265, + 256, 260, 273, 294, 14, 293, 8, 239, 312, 286, + 316, 317, 318, 322, 323, 328, 330, 493, 475, 478, + 481, 484, 487, 490, 7, 6, 0 + } ; + +static const flex_int16_t yy_def[138] = + { 0, + 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 134, 128, 134, 134, 135, 128, 135, 136, 128, 136, + 136, 137, 128, 137, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 134, 128, 134, 134, 135, 128, 136, + 128, 136, 136, 136, 137, 128, 137, 128, 128, 128, + 128, 128, 134, 134, 136, 136, 136, 128, 128, 128, + 134, 134, 128, 128, 128, 134, 134, 128, 128, 134, + 128, 128, 128, 128, 128, 82, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 0, 128, 128, + 128, 128, 128, 128, 128, 128, 128 + } ; + +static const flex_int16_t yy_nxt[540] = + { 0, + 55, 13, 14, 15, 13, 35, 50, 48, 35, 16, + 16, 37, 37, 41, 38, 17, 45, 18, 121, 45, + 19, 39, 20, 42, 119, 49, 42, 40, 49, 41, + 38, 17, 51, 18, 43, 51, 19, 39, 20, 13, + 14, 15, 13, 40, 52, 52, 56, 16, 16, 56, + 57, 57, 73, 17, 35, 18, 45, 35, 19, 45, + 20, 45, 37, 37, 45, 58, 36, 59, 46, 17, + 59, 18, 60, 47, 19, 36, 20, 12, 14, 15, + 22, 58, 22, 61, 46, 53, 52, 52, 60, 47, + 68, 23, 42, 54, 62, 42, 24, 62, 45, 61, + + 128, 45, 45, 43, 128, 45, 68, 23, 45, 54, + 128, 45, 24, 45, 49, 51, 45, 49, 51, 65, + 65, 12, 12, 14, 15, 22, 64, 22, 69, 53, + 52, 52, 56, 63, 70, 56, 23, 54, 57, 57, + 74, 24, 64, 66, 69, 66, 128, 67, 67, 63, + 70, 59, 23, 54, 59, 62, 74, 24, 62, 45, + 67, 67, 45, 67, 67, 75, 12, 26, 14, 15, + 26, 71, 12, 128, 45, 27, 27, 45, 65, 65, + 45, 75, 78, 45, 79, 54, 128, 71, 45, 72, + 76, 45, 45, 81, 45, 45, 81, 45, 78, 83, + + 79, 54, 83, 85, 82, 72, 85, 77, 91, 84, + 92, 12, 26, 14, 15, 26, 81, 12, 80, 81, + 27, 27, 83, 77, 91, 83, 92, 82, 93, 94, + 95, 96, 128, 85, 80, 97, 85, 90, 98, 99, + 122, 128, 128, 122, 93, 94, 95, 96, 90, 128, + 123, 97, 100, 90, 98, 99, 12, 29, 14, 15, + 29, 30, 12, 30, 90, 31, 31, 101, 100, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 101, 116, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 117, + + 116, 12, 29, 14, 15, 29, 30, 12, 30, 118, + 31, 31, 120, 124, 125, 117, 124, 126, 122, 122, + 126, 122, 122, 124, 127, 118, 124, 127, 120, 126, + 125, 127, 126, 128, 127, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 12, 33, 14, 15, + 33, 128, 12, 128, 128, 34, 34, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 12, 33, 14, 15, 33, 128, 12, 128, 128, + + 34, 34, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 12, 86, 128, 128, + 86, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 87, 88, 128, 128, 128, 128, 89, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 87, 88, + 128, 128, 128, 128, 89, 12, 12, 12, 21, 21, + 21, 25, 25, 25, 28, 28, 28, 32, 32, 32, + 44, 44, 11, 128, 128, 128, 128, 128, 128, 128, + + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128 + } ; + +static const flex_int16_t yy_chk[540] = + { 0, + 137, 1, 1, 1, 1, 13, 136, 135, 13, 1, + 1, 16, 16, 19, 17, 1, 21, 1, 117, 21, + 1, 17, 1, 20, 115, 27, 20, 18, 27, 19, + 17, 1, 29, 1, 20, 29, 1, 17, 1, 2, + 2, 2, 2, 18, 30, 30, 33, 2, 2, 33, + 34, 34, 68, 2, 35, 2, 23, 35, 2, 23, + 2, 24, 37, 37, 24, 38, 15, 39, 23, 2, + 39, 2, 40, 24, 2, 14, 2, 3, 3, 3, + 3, 38, 3, 41, 23, 31, 31, 31, 40, 24, + 58, 3, 42, 31, 43, 42, 3, 43, 44, 41, + + 11, 44, 45, 42, 0, 45, 58, 3, 46, 31, + 0, 46, 3, 47, 49, 51, 47, 49, 51, 53, + 53, 3, 4, 4, 4, 4, 47, 4, 60, 52, + 52, 52, 56, 46, 61, 56, 4, 52, 57, 57, + 69, 4, 47, 54, 60, 54, 0, 54, 54, 46, + 61, 59, 4, 52, 59, 62, 69, 4, 62, 63, + 66, 66, 63, 67, 67, 70, 4, 5, 5, 5, + 5, 63, 5, 0, 64, 5, 5, 64, 65, 65, + 71, 70, 74, 71, 75, 65, 0, 63, 72, 64, + 71, 72, 76, 78, 77, 76, 78, 77, 74, 79, + + 75, 65, 79, 80, 78, 64, 80, 72, 87, 79, + 88, 5, 6, 6, 6, 6, 81, 6, 77, 81, + 6, 6, 83, 72, 87, 83, 88, 81, 89, 90, + 91, 92, 0, 85, 77, 93, 85, 83, 94, 95, + 118, 0, 0, 118, 89, 90, 91, 92, 85, 0, + 118, 93, 96, 83, 94, 95, 6, 7, 7, 7, + 7, 7, 7, 7, 85, 7, 7, 97, 96, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 97, 112, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, + + 112, 7, 8, 8, 8, 8, 8, 8, 8, 114, + 8, 8, 116, 119, 120, 113, 119, 121, 122, 123, + 121, 122, 123, 124, 125, 114, 124, 125, 116, 126, + 120, 127, 126, 0, 127, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 8, 9, 9, 9, + 9, 0, 9, 0, 0, 9, 9, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 9, 10, 10, 10, 10, 0, 10, 0, 0, + + 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 10, 82, 0, 0, + 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 82, 82, 0, 0, 0, 0, 82, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 82, 82, + 0, 0, 0, 0, 82, 129, 129, 129, 130, 130, + 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, + 134, 134, 128, 128, 128, 128, 128, 128, 128, 128, + + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[25] = + { 0, +1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "io/dl-header.h" +#include "io/parsers/dl-parser.h" + +#include + +#define YY_EXTRA_TYPE igraph_i_dl_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +#define YY_FATAL_ERROR(msg) IGRAPH_FATAL("Error in DL parser: " # msg) +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#endif +#define YY_NO_INPUT 1 + +#define INITIAL 0 +#define LABELM 1 +#define FULLMATRIX 2 +#define EDGELIST 3 +#define NODELIST 4 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + yy_size_t yy_n_chars; + yy_size_t yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + yy_size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 493 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + yy_size_t yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +{ return NEWLINE; } + YY_BREAK +case 2: +YY_RULE_SETUP +{ return DL; } + YY_BREAK +case 3: +YY_RULE_SETUP +{ return NEQ; } + YY_BREAK +case 4: +YY_RULE_SETUP +{ return NUM; } + YY_BREAK +case 5: +YY_RULE_SETUP +{ + switch (yyextra->mode) { + case 0: BEGIN(FULLMATRIX); + break; + case 1: BEGIN(EDGELIST); + break; + case 2: BEGIN(NODELIST); + break; + } + return DATA; } + YY_BREAK +case 6: +YY_RULE_SETUP +{ BEGIN(LABELM); return LABELS; } + YY_BREAK +case 7: +YY_RULE_SETUP +{ + return LABELSEMBEDDED; } + YY_BREAK +case 8: +YY_RULE_SETUP +{ + yyextra->mode=0; return FORMATFULLMATRIX; } + YY_BREAK +case 9: +YY_RULE_SETUP +{ + yyextra->mode=1; return FORMATEDGELIST1; } + YY_BREAK +case 10: +YY_RULE_SETUP +{ + yyextra->mode=2; return FORMATNODELIST1; } + YY_BREAK +case 11: +YY_RULE_SETUP +{ /* eaten up */ } + YY_BREAK +case 12: +YY_RULE_SETUP +{ return LABEL; } + YY_BREAK +case 13: +YY_RULE_SETUP +{ return DIGIT; } + YY_BREAK +case 14: +YY_RULE_SETUP +{ return LABEL; } + YY_BREAK +case 15: +YY_RULE_SETUP +{ } + YY_BREAK +case 16: +YY_RULE_SETUP +{ return NUM; } + YY_BREAK +case 17: +YY_RULE_SETUP +{ return LABEL; } + YY_BREAK +case 18: +YY_RULE_SETUP +{ } + YY_BREAK +case 19: +YY_RULE_SETUP +{ return NUM; } + YY_BREAK +case 20: +YY_RULE_SETUP +{ return LABEL; } + YY_BREAK +case 21: +YY_RULE_SETUP +{ } + YY_BREAK +case 22: +YY_RULE_SETUP +{ /* eaten up */ } + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(LABELM): +case YY_STATE_EOF(FULLMATRIX): +case YY_STATE_EOF(EDGELIST): +case YY_STATE_EOF(NODELIST): +{ + if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + BEGIN(INITIAL); + return EOFF; + } + } + YY_BREAK +case 23: +YY_RULE_SETUP +{ return 0; } + YY_BREAK +case 24: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + yy_size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 46); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 46; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 128); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + if ( c == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + yy_size_t i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yy_size_t yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +yy_size_t yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + diff --git a/src/io/parsers/dl-lexer.h b/src/io/parsers/dl-lexer.h new file mode 100644 index 0000000..9d4cfa6 --- /dev/null +++ b/src/io/parsers/dl-lexer.h @@ -0,0 +1,731 @@ +#ifndef igraph_dl_yyHEADER_H +#define igraph_dl_yyHEADER_H 1 +#define igraph_dl_yyIN_HEADER 1 + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_dl_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_dl_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_dl_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_dl_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_dl_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_dl_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_dl_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_dl_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_dl_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_dl_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_dl_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_dl_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_dl_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_dl_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_dl_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_dl_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_dl_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_dl_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_dl_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_dl_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_dl_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_dl_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_dl_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_dl_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_dl_yylex_ALREADY_DEFINED +#else +#define yylex igraph_dl_yylex +#endif + +#ifdef yyrestart +#define igraph_dl_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_dl_yyrestart +#endif + +#ifdef yylex_init +#define igraph_dl_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_dl_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_dl_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_dl_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_dl_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_dl_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_dl_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_dl_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_dl_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_dl_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_dl_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_dl_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_dl_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_dl_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_dl_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_dl_yyget_in +#endif + +#ifdef yyset_in +#define igraph_dl_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_dl_yyset_in +#endif + +#ifdef yyget_out +#define igraph_dl_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_dl_yyget_out +#endif + +#ifdef yyset_out +#define igraph_dl_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_dl_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_dl_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_dl_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_dl_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_dl_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_dl_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_dl_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_dl_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_dl_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_dl_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_dl_yyget_column +#endif + +#ifdef yyset_column +#define igraph_dl_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_dl_yyset_column +#endif + +#ifdef yywrap +#define igraph_dl_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_dl_yywrap +#endif + +#ifdef yyget_lval +#define igraph_dl_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_dl_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_dl_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_dl_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_dl_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_dl_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_dl_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_dl_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_dl_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_dl_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_dl_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_dl_yyrealloc +#endif + +#ifdef yyfree +#define igraph_dl_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_dl_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define igraph_dl_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 +#define LABELM 1 +#define FULLMATRIX 2 +#define EDGELIST 3 +#define NODELIST 4 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#ifndef igraph_dl_yy_create_buffer_ALREADY_DEFINED +#undef yy_create_buffer +#endif +#ifndef igraph_dl_yy_delete_buffer_ALREADY_DEFINED +#undef yy_delete_buffer +#endif +#ifndef igraph_dl_yy_scan_buffer_ALREADY_DEFINED +#undef yy_scan_buffer +#endif +#ifndef igraph_dl_yy_scan_string_ALREADY_DEFINED +#undef yy_scan_string +#endif +#ifndef igraph_dl_yy_scan_bytes_ALREADY_DEFINED +#undef yy_scan_bytes +#endif +#ifndef igraph_dl_yy_init_buffer_ALREADY_DEFINED +#undef yy_init_buffer +#endif +#ifndef igraph_dl_yy_flush_buffer_ALREADY_DEFINED +#undef yy_flush_buffer +#endif +#ifndef igraph_dl_yy_load_buffer_state_ALREADY_DEFINED +#undef yy_load_buffer_state +#endif +#ifndef igraph_dl_yy_switch_to_buffer_ALREADY_DEFINED +#undef yy_switch_to_buffer +#endif +#ifndef igraph_dl_yypush_buffer_state_ALREADY_DEFINED +#undef yypush_buffer_state +#endif +#ifndef igraph_dl_yypop_buffer_state_ALREADY_DEFINED +#undef yypop_buffer_state +#endif +#ifndef igraph_dl_yyensure_buffer_stack_ALREADY_DEFINED +#undef yyensure_buffer_stack +#endif +#ifndef igraph_dl_yylex_ALREADY_DEFINED +#undef yylex +#endif +#ifndef igraph_dl_yyrestart_ALREADY_DEFINED +#undef yyrestart +#endif +#ifndef igraph_dl_yylex_init_ALREADY_DEFINED +#undef yylex_init +#endif +#ifndef igraph_dl_yylex_init_extra_ALREADY_DEFINED +#undef yylex_init_extra +#endif +#ifndef igraph_dl_yylex_destroy_ALREADY_DEFINED +#undef yylex_destroy +#endif +#ifndef igraph_dl_yyget_debug_ALREADY_DEFINED +#undef yyget_debug +#endif +#ifndef igraph_dl_yyset_debug_ALREADY_DEFINED +#undef yyset_debug +#endif +#ifndef igraph_dl_yyget_extra_ALREADY_DEFINED +#undef yyget_extra +#endif +#ifndef igraph_dl_yyset_extra_ALREADY_DEFINED +#undef yyset_extra +#endif +#ifndef igraph_dl_yyget_in_ALREADY_DEFINED +#undef yyget_in +#endif +#ifndef igraph_dl_yyset_in_ALREADY_DEFINED +#undef yyset_in +#endif +#ifndef igraph_dl_yyget_out_ALREADY_DEFINED +#undef yyget_out +#endif +#ifndef igraph_dl_yyset_out_ALREADY_DEFINED +#undef yyset_out +#endif +#ifndef igraph_dl_yyget_leng_ALREADY_DEFINED +#undef yyget_leng +#endif +#ifndef igraph_dl_yyget_text_ALREADY_DEFINED +#undef yyget_text +#endif +#ifndef igraph_dl_yyget_lineno_ALREADY_DEFINED +#undef yyget_lineno +#endif +#ifndef igraph_dl_yyset_lineno_ALREADY_DEFINED +#undef yyset_lineno +#endif +#ifndef igraph_dl_yyget_column_ALREADY_DEFINED +#undef yyget_column +#endif +#ifndef igraph_dl_yyset_column_ALREADY_DEFINED +#undef yyset_column +#endif +#ifndef igraph_dl_yywrap_ALREADY_DEFINED +#undef yywrap +#endif +#ifndef igraph_dl_yyget_lval_ALREADY_DEFINED +#undef yyget_lval +#endif +#ifndef igraph_dl_yyset_lval_ALREADY_DEFINED +#undef yyset_lval +#endif +#ifndef igraph_dl_yyget_lloc_ALREADY_DEFINED +#undef yyget_lloc +#endif +#ifndef igraph_dl_yyset_lloc_ALREADY_DEFINED +#undef yyset_lloc +#endif +#ifndef igraph_dl_yyalloc_ALREADY_DEFINED +#undef yyalloc +#endif +#ifndef igraph_dl_yyrealloc_ALREADY_DEFINED +#undef yyrealloc +#endif +#ifndef igraph_dl_yyfree_ALREADY_DEFINED +#undef yyfree +#endif +#ifndef igraph_dl_yytext_ALREADY_DEFINED +#undef yytext +#endif +#ifndef igraph_dl_yyleng_ALREADY_DEFINED +#undef yyleng +#endif +#ifndef igraph_dl_yyin_ALREADY_DEFINED +#undef yyin +#endif +#ifndef igraph_dl_yyout_ALREADY_DEFINED +#undef yyout +#endif +#ifndef igraph_dl_yy_flex_debug_ALREADY_DEFINED +#undef yy_flex_debug +#endif +#ifndef igraph_dl_yylineno_ALREADY_DEFINED +#undef yylineno +#endif +#ifndef igraph_dl_yytables_fload_ALREADY_DEFINED +#undef yytables_fload +#endif +#ifndef igraph_dl_yytables_destroy_ALREADY_DEFINED +#undef yytables_destroy +#endif +#ifndef igraph_dl_yyTABLES_NAME_ALREADY_DEFINED +#undef yyTABLES_NAME +#endif + +#undef igraph_dl_yyIN_HEADER +#endif /* igraph_dl_yyHEADER_H */ diff --git a/src/io/parsers/dl-parser.c b/src/io/parsers/dl-parser.c new file mode 100644 index 0000000..a066c9f --- /dev/null +++ b/src/io/parsers/dl-parser.c @@ -0,0 +1,2192 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse igraph_dl_yyparse +#define yylex igraph_dl_yylex +#define yyerror igraph_dl_yyerror +#define yylval igraph_dl_yylval +#define yychar igraph_dl_yychar +#define yydebug igraph_dl_yydebug +#define yynerrs igraph_dl_yynerrs +#define yylloc igraph_dl_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + NUM = 258, + NEWLINE = 259, + DL = 260, + NEQ = 261, + DATA = 262, + LABELS = 263, + LABELSEMBEDDED = 264, + FORMATFULLMATRIX = 265, + FORMATEDGELIST1 = 266, + FORMATNODELIST1 = 267, + DIGIT = 268, + LABEL = 269, + EOFF = 270, + ERROR = 271 + }; +#endif +/* Tokens. */ +#define END 0 +#define NUM 258 +#define NEWLINE 259 +#define DL 260 +#define NEQ 261 +#define DATA 262 +#define LABELS 263 +#define LABELSEMBEDDED 264 +#define FORMATFULLMATRIX 265 +#define FORMATEDGELIST1 266 +#define FORMATNODELIST1 267 +#define DIGIT 268 +#define LABEL 269 +#define EOFF 270 +#define ERROR 271 + + + + +/* Copy the first part of user declarations. */ + + + +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "internal/hacks.h" +#include "io/dl-header.h" +#include "io/parsers/dl-parser.h" +#include "io/parsers/dl-lexer.h" +#include "io/parse_utils.h" + +int igraph_dl_yyerror(YYLTYPE* locp, igraph_i_dl_parsedata_t* context, + const char *s); +static igraph_error_t igraph_i_dl_add_str(char *newstr, yy_size_t length, + igraph_i_dl_parsedata_t *context); +static igraph_error_t igraph_i_dl_add_edge(igraph_int_t from, igraph_int_t to, + igraph_i_dl_parsedata_t *context); +static igraph_error_t igraph_i_dl_add_edge_w(igraph_int_t from, igraph_int_t to, + igraph_real_t weight, + igraph_i_dl_parsedata_t *context); +static igraph_error_t igraph_i_dl_check_vid(igraph_int_t dl_vid); + +#define scanner context->scanner + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t integer; + igraph_real_t real; +} +/* Line 193 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ + + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 4 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 118 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 17 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 37 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 66 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 138 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 271 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 11, 12, 15, 16, 18, 20, 22, + 24, 28, 30, 31, 33, 37, 45, 51, 52, 56, + 57, 61, 62, 65, 67, 69, 73, 74, 78, 80, + 82, 85, 89, 93, 97, 105, 111, 121, 131, 132, + 135, 140, 144, 146, 147, 150, 155, 159, 161, 163, + 167, 170, 178, 184, 194, 204, 205, 208, 212, 214, + 215, 218, 219, 222, 226, 228, 229 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 18, 0, -1, 5, 6, 39, 4, 21, 19, 20, + -1, -1, 19, 23, -1, -1, 15, -1, 22, -1, + 35, -1, 44, -1, 10, 23, 24, -1, 24, -1, + -1, 4, -1, 7, 23, 26, -1, 8, 23, 25, + 23, 7, 23, 26, -1, 9, 23, 7, 23, 29, + -1, -1, 25, 23, 14, -1, -1, 26, 27, 4, + -1, -1, 27, 28, -1, 13, -1, 30, -1, 31, + 4, 33, -1, -1, 31, 23, 32, -1, 14, -1, + 34, -1, 33, 34, -1, 14, 27, 4, -1, 11, + 23, 36, -1, 7, 23, 37, -1, 8, 23, 25, + 23, 7, 23, 37, -1, 9, 23, 7, 23, 40, + -1, 8, 23, 25, 23, 9, 23, 7, 23, 40, + -1, 9, 23, 8, 23, 25, 23, 7, 23, 40, + -1, -1, 37, 38, -1, 39, 39, 42, 4, -1, + 39, 39, 4, -1, 3, -1, -1, 40, 41, -1, + 43, 43, 42, 4, -1, 43, 43, 4, -1, 3, + -1, 14, -1, 12, 23, 45, -1, 7, 46, -1, + 8, 23, 25, 23, 7, 23, 46, -1, 9, 23, + 7, 23, 50, -1, 8, 23, 25, 23, 9, 23, + 7, 23, 50, -1, 9, 23, 8, 23, 25, 23, + 7, 23, 50, -1, -1, 46, 47, -1, 48, 49, + 4, -1, 3, -1, -1, 49, 39, -1, -1, 50, + 51, -1, 52, 53, 4, -1, 43, -1, -1, 53, + 43, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 104, 104, 114, 114, 116, 116, 118, 119, 120, + 123, 123, 125, 125, 127, 128, 129, 132, 133, 139, + 139, 144, 144, 146, 161, 163, 165, 165, 167, 171, + 175, 180, 184, 186, 187, 188, 189, 190, 193, 194, + 197, 202, 209, 217, 218, 221, 223, 227, 235, 254, + 256, 257, 258, 259, 260, 263, 264, 267, 269, 276, + 276, 284, 285, 288, 290, 294, 294 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "$undefined", "\"number\"", + "\"end of line\"", "\"DL\"", "\"n=vertexcount\"", "\"data:\"", + "\"labels:\"", "\"labels embedded:\"", "FORMATFULLMATRIX", + "FORMATEDGELIST1", "FORMATNODELIST1", "\"binary digit\"", "\"label\"", + "EOFF", "ERROR", "$accept", "input", "trail", "eof", "rest", + "formfullmatrix", "newline", "fullmatrix", "labels", "fullmatrixdata", + "zerooneseq", "zeroone", "labeledfullmatrixdata", + "reallabeledfullmatrixdata", "labelseq", "label", "labeledmatrixlines", + "labeledmatrixline", "edgelist1", "edgelist1rest", "edgelist1data", + "edgelist1dataline", "integer", "labelededgelist1data", + "labelededgelist1dataline", "weight", "elabel", "nodelist1", + "nodelist1rest", "nodelist1data", "nodelist1dataline", "from", "tolist", + "labelednodelist1data", "labelednodelist1dataline", "fromelabel", + "labeltolist", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 17, 18, 19, 19, 20, 20, 21, 21, 21, + 22, 22, 23, 23, 24, 24, 24, 25, 25, 26, + 26, 27, 27, 28, 29, 30, 31, 31, 32, 33, + 33, 34, 35, 36, 36, 36, 36, 36, 37, 37, + 38, 38, 39, 40, 40, 41, 41, 42, 43, 44, + 45, 45, 45, 45, 45, 46, 46, 47, 48, 49, + 49, 50, 50, 51, 52, 53, 53 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 7, 0, 2, 0, 1, 1, 1, 1, + 3, 1, 0, 1, 3, 7, 5, 0, 3, 0, + 3, 0, 2, 1, 1, 3, 0, 3, 1, 1, + 2, 3, 3, 3, 7, 5, 9, 9, 0, 2, + 4, 3, 1, 0, 2, 4, 3, 1, 1, 3, + 2, 7, 5, 9, 9, 0, 2, 3, 1, 0, + 2, 0, 2, 3, 1, 0, 2 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 0, 0, 1, 42, 0, 0, 12, 12, + 12, 12, 12, 12, 3, 7, 11, 8, 9, 13, + 19, 17, 0, 0, 0, 0, 5, 14, 12, 12, + 10, 12, 12, 12, 32, 55, 12, 12, 49, 6, + 2, 4, 0, 0, 26, 38, 17, 0, 50, 17, + 0, 20, 23, 22, 12, 18, 16, 24, 12, 33, + 12, 12, 12, 58, 56, 59, 12, 12, 12, 19, + 0, 0, 39, 0, 0, 43, 17, 0, 0, 61, + 17, 15, 21, 25, 29, 28, 27, 0, 12, 12, + 35, 12, 57, 60, 12, 12, 52, 12, 0, 30, + 47, 41, 0, 38, 0, 48, 44, 0, 0, 55, + 0, 64, 62, 65, 0, 31, 40, 34, 12, 0, + 12, 51, 12, 0, 12, 43, 46, 0, 43, 61, + 63, 66, 61, 36, 45, 37, 53, 54 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 2, 26, 40, 14, 15, 20, 16, 28, 27, + 42, 53, 56, 57, 58, 86, 83, 84, 17, 34, + 59, 72, 73, 90, 106, 102, 107, 18, 38, 48, + 64, 65, 77, 96, 112, 113, 123 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -114 +static const yytype_int8 yypact[] = +{ + 8, 38, 11, 43, -114, -114, 44, 57, 46, 46, + 46, 46, 46, 46, -114, -114, -114, -114, -114, -114, + -114, -114, 69, 53, 63, 66, 6, 65, 46, 46, + -114, 46, 46, 46, -114, -114, 46, 46, -114, -114, + -114, -114, 5, 19, -114, -114, -114, 76, 84, -114, + 82, -114, -114, -114, 46, -114, -114, -114, 93, 43, + 46, 46, 46, -114, -114, -114, 46, 46, 46, -114, + 85, 86, -114, 43, 23, -114, -114, 88, 33, -114, + -114, 65, -114, 85, -114, -114, -114, 90, 46, 46, + 87, 46, -114, -114, 46, 46, 87, 46, 25, -114, + -114, -114, 94, -114, 95, -114, -114, 87, 29, -114, + 96, -114, -114, -114, 49, -114, -114, 43, 46, 92, + 46, 84, 46, 2, 46, -114, -114, 100, -114, -114, + -114, -114, -114, 87, -114, 87, 87, 87 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -114, -114, -114, -114, -114, -114, -9, 83, -41, 36, + 26, -114, -114, -114, -114, -114, -114, 24, -114, -114, + 7, -114, 4, -113, -114, -7, -82, -114, -114, 9, + -114, -114, -114, -98, -114, -114, -114 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -22 +static const yytype_int16 yytable[] = +{ + 21, 22, 23, 24, 25, 60, 130, 6, 66, 51, + 19, 4, 133, 1, 111, 135, 105, 41, 52, 43, + 44, 39, 45, 46, 47, 119, 54, 49, 50, 115, + 88, 136, 89, 55, 137, 91, 120, 55, 52, 97, + 94, 131, 95, 55, 3, 69, 5, 55, 7, 71, + 19, 74, 75, 76, 111, 111, 124, 78, 79, 80, + 8, 9, 10, 55, 8, 9, 10, 11, 12, 13, + 31, 32, 33, 35, 36, 37, 29, 87, -21, 103, + 104, 93, 108, 61, 62, 109, 110, 63, 114, 67, + 68, 5, 92, 100, 101, 100, 126, 70, 116, 82, + 85, 105, 118, 122, 134, 81, 30, 99, 98, 125, + 117, 128, 127, 129, 0, 132, 0, 0, 121 +}; + +static const yytype_int16 yycheck[] = +{ + 9, 10, 11, 12, 13, 46, 4, 3, 49, 4, + 4, 0, 125, 5, 96, 128, 14, 26, 13, 28, + 29, 15, 31, 32, 33, 107, 7, 36, 37, 4, + 7, 129, 9, 14, 132, 76, 7, 14, 13, 80, + 7, 123, 9, 14, 6, 54, 3, 14, 4, 58, + 4, 60, 61, 62, 136, 137, 7, 66, 67, 68, + 7, 8, 9, 14, 7, 8, 9, 10, 11, 12, + 7, 8, 9, 7, 8, 9, 7, 73, 13, 88, + 89, 77, 91, 7, 8, 94, 95, 3, 97, 7, + 8, 3, 4, 3, 4, 3, 4, 4, 4, 14, + 14, 14, 7, 7, 4, 69, 23, 83, 82, 118, + 103, 120, 119, 122, -1, 124, -1, -1, 109 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 5, 18, 6, 0, 3, 39, 4, 7, 8, + 9, 10, 11, 12, 21, 22, 24, 35, 44, 4, + 23, 23, 23, 23, 23, 23, 19, 26, 25, 7, + 24, 7, 8, 9, 36, 7, 8, 9, 45, 15, + 20, 23, 27, 23, 23, 23, 23, 23, 46, 23, + 23, 4, 13, 28, 7, 14, 29, 30, 31, 37, + 25, 7, 8, 3, 47, 48, 25, 7, 8, 23, + 4, 23, 38, 39, 23, 23, 23, 49, 23, 23, + 23, 26, 14, 33, 34, 14, 32, 39, 7, 9, + 40, 25, 4, 39, 7, 9, 50, 25, 27, 34, + 3, 4, 42, 23, 23, 14, 41, 43, 23, 23, + 23, 43, 51, 52, 23, 4, 4, 37, 7, 43, + 7, 46, 7, 53, 7, 23, 4, 42, 23, 23, + 4, 43, 23, 40, 4, 40, 50, 50 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_dl_parsedata_t* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_dl_parsedata_t* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_dl_parsedata_t* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_dl_parsedata_t* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, igraph_i_dl_parsedata_t* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + igraph_i_dl_parsedata_t* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, igraph_i_dl_parsedata_t* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + igraph_i_dl_parsedata_t* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (igraph_i_dl_parsedata_t* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (igraph_i_dl_parsedata_t* context) +#else +int +yyparse (context) + igraph_i_dl_parsedata_t* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + + { + context->n=(yyvsp[(3) - (7)].integer); + if (context->n < 0) { + IGRAPH_YY_ERRORF("Invalid vertex count in DL file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->n); + } + if (context->n > IGRAPH_DL_MAX_VERTEX_COUNT) { + IGRAPH_YY_ERRORF("Vertex count too large in DL file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->n); + } +;} + break; + + case 7: + + { context->type=IGRAPH_DL_MATRIX; ;} + break; + + case 8: + + { context->type=IGRAPH_DL_EDGELIST1; ;} + break; + + case 9: + + { context->type=IGRAPH_DL_NODELIST1; ;} + break; + + case 10: + + {;} + break; + + case 11: + + {;} + break; + + case 14: + + { ;} + break; + + case 15: + + { ;} + break; + + case 16: + + { ;} + break; + + case 17: + + {;} + break; + + case 18: + + { + IGRAPH_YY_CHECK(igraph_i_dl_add_str(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + context)); ;} + break; + + case 19: + + {;} + break; + + case 20: + + { + context->from += 1; + context->to = 0; + ;} + break; + + case 22: + + { ;} + break; + + case 23: + + { + /* TODO: What if the digit is neither 0 or 1? Are multigraphs allowed? */ + char c = igraph_dl_yyget_text(scanner)[0]; + if (c == '1') { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, + context->from)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, + context->to)); + } else if (c != '0') { + IGRAPH_YY_ERRORF("Unexpected digit '%c' in adjacency matrix in DL file.", + IGRAPH_EINVAL, c); + } + context->to += 1; +;} + break; + + case 24: + + {;} + break; + + case 25: + + {;} + break; + + case 28: + + { IGRAPH_YY_CHECK(igraph_i_dl_add_str(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + context)); ;} + break; + + case 29: + + { + context->from += 1; + context->to = 0; + ;} + break; + + case 30: + + { + context->from += 1; + context->to = 0; + ;} + break; + + case 31: + + { ;} + break; + + case 32: + + {;} + break; + + case 33: + + {;} + break; + + case 34: + + {;} + break; + + case 35: + + {;} + break; + + case 36: + + {;} + break; + + case 37: + + {;} + break; + + case 38: + + {;} + break; + + case 39: + + {;} + break; + + case 40: + + { + igraph_int_t from = (yyvsp[(1) - (4)].integer), to = (yyvsp[(2) - (4)].integer); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(from)); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(to)); + IGRAPH_YY_CHECK(igraph_i_dl_add_edge_w(from-1, to-1, (yyvsp[(3) - (4)].real), context)); ;} + break; + + case 41: + + { + igraph_int_t from = (yyvsp[(1) - (3)].integer), to = (yyvsp[(2) - (3)].integer); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(from)); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(to)); + IGRAPH_YY_CHECK(igraph_i_dl_add_edge(from-1, to-1, context)); +;} + break; + + case 42: + + { + igraph_int_t val; + IGRAPH_YY_CHECK(igraph_i_parse_integer(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + &val)); + (yyval.integer)=val; +;} + break; + + case 43: + + {;} + break; + + case 44: + + {;} + break; + + case 45: + + { + IGRAPH_YY_CHECK(igraph_i_dl_add_edge_w((yyvsp[(1) - (4)].integer), (yyvsp[(2) - (4)].integer), (yyvsp[(3) - (4)].real), context)); ;} + break; + + case 46: + + { + IGRAPH_YY_CHECK(igraph_i_dl_add_edge((yyvsp[(1) - (3)].integer), (yyvsp[(2) - (3)].integer), context)); + ;} + break; + + case 47: + + { + igraph_real_t val; + IGRAPH_YY_CHECK(igraph_i_parse_real(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + &val)); + (yyval.real)=val; +;} + break; + + case 48: + + { + igraph_int_t trie_id; + + /* Copy label list to trie, if needed */ + if (igraph_strvector_size(&context->labels) != 0) { + igraph_int_t i, id, n=igraph_strvector_size(&context->labels); + for (i=0; itrie, igraph_strvector_get(&context->labels, i), &id)); + } + igraph_strvector_clear(&context->labels); + } + IGRAPH_YY_CHECK(igraph_trie_get_len(&context->trie, igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), &trie_id)); + IGRAPH_ASSERT(0 <= trie_id && trie_id < IGRAPH_DL_MAX_VERTEX_COUNT); + (yyval.integer) = trie_id; + ;} + break; + + case 49: + + {;} + break; + + case 50: + + {;} + break; + + case 51: + + {;} + break; + + case 52: + + {;} + break; + + case 53: + + {;} + break; + + case 54: + + {;} + break; + + case 55: + + {;} + break; + + case 56: + + {;} + break; + + case 57: + + {;} + break; + + case 58: + + { + IGRAPH_YY_CHECK(igraph_i_parse_integer(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + &context->from)); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(context->from)); +;} + break; + + case 59: + + {;} + break; + + case 60: + + { + igraph_int_t to = (yyvsp[(2) - (2)].integer); + IGRAPH_YY_CHECK(igraph_i_dl_check_vid(to)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, + context->from-1)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, to-1)); + ;} + break; + + case 61: + + {;} + break; + + case 62: + + {;} + break; + + case 63: + + { ;} + break; + + case 64: + + { + context->from=(yyvsp[(1) - (1)].integer); + ;} + break; + + case 66: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, + context->from)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(&context->edges, (yyvsp[(2) - (2)].integer))); + ;} + break; + + +/* Line 1267 of yacc.c. */ + + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + + + +int igraph_dl_yyerror(YYLTYPE* locp, + igraph_i_dl_parsedata_t* context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in DL file, line %i (%s)", + locp->first_line, s); + return 0; +} + +static igraph_error_t igraph_i_dl_add_str(char *newstr, yy_size_t length, + igraph_i_dl_parsedata_t *context) { + IGRAPH_CHECK(igraph_strvector_push_back_len(&context->labels, newstr, length)); + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_dl_add_edge(igraph_int_t from, igraph_int_t to, + igraph_i_dl_parsedata_t *context) { + //IGRAPH_CHECK(igraph_i_dl_check_vid(from+1)); + //IGRAPH_CHECK(igraph_i_dl_check_vid(to+1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&context->edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&context->edges, to)); + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_dl_add_edge_w(igraph_int_t from, igraph_int_t to, + igraph_real_t weight, + igraph_i_dl_parsedata_t *context) { + igraph_int_t n=igraph_vector_size(&context->weights); + igraph_int_t n2=igraph_vector_int_size(&context->edges)/2; + if (n != n2) { + IGRAPH_CHECK(igraph_vector_resize(&context->weights, n2)); + for (; nweights)[n]=IGRAPH_NAN; + } + } + IGRAPH_CHECK(igraph_i_dl_add_edge(from, to, context)); + IGRAPH_CHECK(igraph_vector_push_back(&context->weights, weight)); + return IGRAPH_SUCCESS; +} + +/* Raise an error if the vertex index is invalid in the DL file. + * DL files use 1-based vertex indices. */ +static igraph_error_t igraph_i_dl_check_vid(igraph_int_t dl_vid) { + if (dl_vid < 1) { + IGRAPH_ERRORF("Invalid vertex index in DL file: %" IGRAPH_PRId ".", + IGRAPH_EINVAL, dl_vid); + } + if (dl_vid > IGRAPH_DL_MAX_VERTEX_COUNT) { + IGRAPH_ERRORF("Vertex index too large in DL file: %" IGRAPH_PRId ".", + IGRAPH_EINVAL, dl_vid); + } + return IGRAPH_SUCCESS; +} + diff --git a/src/io/parsers/dl-parser.h b/src/io/parsers/dl-parser.h new file mode 100644 index 0000000..f9a412d --- /dev/null +++ b/src/io/parsers/dl-parser.h @@ -0,0 +1,109 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + NUM = 258, + NEWLINE = 259, + DL = 260, + NEQ = 261, + DATA = 262, + LABELS = 263, + LABELSEMBEDDED = 264, + FORMATFULLMATRIX = 265, + FORMATEDGELIST1 = 266, + FORMATNODELIST1 = 267, + DIGIT = 268, + LABEL = 269, + EOFF = 270, + ERROR = 271 + }; +#endif +/* Tokens. */ +#define END 0 +#define NUM 258 +#define NEWLINE 259 +#define DL 260 +#define NEQ 261 +#define DATA 262 +#define LABELS 263 +#define LABELSEMBEDDED 264 +#define FORMATFULLMATRIX 265 +#define FORMATEDGELIST1 266 +#define FORMATNODELIST1 267 +#define DIGIT 268 +#define LABEL 269 +#define EOFF 270 +#define ERROR 271 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t integer; + igraph_real_t real; +} +/* Line 1529 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/src/io/parsers/gml-lexer.c b/src/io/parsers/gml-lexer.c new file mode 100644 index 0000000..abbbaf8 --- /dev/null +++ b/src/io/parsers/gml-lexer.c @@ -0,0 +1,2346 @@ + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_gml_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_gml_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_gml_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_gml_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_gml_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_gml_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_gml_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_gml_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_gml_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_gml_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_gml_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_gml_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_gml_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_gml_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_gml_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_gml_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_gml_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_gml_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_gml_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_gml_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_gml_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_gml_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_gml_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_gml_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_gml_yylex_ALREADY_DEFINED +#else +#define yylex igraph_gml_yylex +#endif + +#ifdef yyrestart +#define igraph_gml_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_gml_yyrestart +#endif + +#ifdef yylex_init +#define igraph_gml_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_gml_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_gml_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_gml_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_gml_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_gml_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_gml_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_gml_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_gml_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_gml_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_gml_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_gml_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_gml_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_gml_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_gml_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_gml_yyget_in +#endif + +#ifdef yyset_in +#define igraph_gml_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_gml_yyset_in +#endif + +#ifdef yyget_out +#define igraph_gml_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_gml_yyget_out +#endif + +#ifdef yyset_out +#define igraph_gml_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_gml_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_gml_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_gml_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_gml_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_gml_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_gml_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_gml_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_gml_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_gml_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_gml_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_gml_yyget_column +#endif + +#ifdef yyset_column +#define igraph_gml_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_gml_yyset_column +#endif + +#ifdef yywrap +#define igraph_gml_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_gml_yywrap +#endif + +#ifdef yyget_lval +#define igraph_gml_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_gml_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_gml_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_gml_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_gml_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_gml_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_gml_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_gml_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_gml_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_gml_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_gml_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_gml_yyrealloc +#endif + +#ifdef yyfree +#define igraph_gml_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_gml_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + yy_size_t yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define igraph_gml_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 11 +#define YY_END_OF_BUFFER 12 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[43] = + { 0, + 0, 0, 0, 0, 12, 10, 8, 9, 9, 10, + 10, 4, 5, 6, 7, 1, 10, 5, 5, 8, + 9, 0, 2, 4, 0, 0, 5, 1, 0, 0, + 5, 5, 4, 0, 4, 0, 0, 3, 3, 3, + 3, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 5, 6, 1, 1, 1, 1, 1, + 1, 1, 7, 1, 8, 9, 1, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 1, 1, 1, + 1, 1, 1, 1, 11, 12, 12, 12, 13, 14, + 12, 12, 15, 12, 12, 12, 12, 16, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 17, 1, 18, 1, 12, 1, 19, 12, 12, 12, + + 13, 20, 12, 12, 21, 12, 12, 12, 12, 22, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[24] = + { 0, + 1, 1, 2, 2, 1, 1, 1, 1, 1, 3, + 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, + 3, 3, 4 + } ; + +static const flex_int16_t yy_base[46] = + { 0, + 0, 78, 17, 77, 82, 85, 79, 23, 25, 74, + 63, 21, 0, 85, 85, 0, 30, 19, 25, 70, + 39, 66, 85, 40, 59, 47, 0, 0, 40, 47, + 45, 45, 50, 38, 37, 50, 52, 0, 0, 85, + 85, 85, 74, 34, 77 + } ; + +static const flex_int16_t yy_def[46] = + { 0, + 42, 1, 1, 3, 42, 42, 42, 42, 42, 43, + 42, 42, 44, 42, 42, 45, 42, 44, 44, 42, + 42, 43, 42, 42, 42, 42, 44, 45, 42, 42, + 44, 44, 42, 42, 42, 42, 42, 44, 44, 42, + 42, 0, 42, 42, 42 + } ; + +static const flex_int16_t yy_nxt[109] = + { 0, + 6, 7, 8, 9, 10, 6, 11, 11, 6, 12, + 13, 13, 13, 13, 13, 13, 14, 15, 13, 13, + 13, 13, 6, 17, 17, 21, 21, 21, 21, 25, + 24, 18, 19, 26, 31, 32, 27, 18, 19, 24, + 31, 21, 21, 32, 29, 30, 35, 35, 25, 24, + 29, 30, 26, 34, 34, 36, 35, 37, 38, 33, + 39, 36, 26, 40, 38, 37, 39, 41, 33, 40, + 23, 20, 24, 41, 22, 22, 22, 28, 23, 28, + 20, 42, 16, 16, 5, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + + 42, 42, 42, 42, 42, 42, 42, 42 + } ; + +static const flex_int16_t yy_chk[109] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 3, 8, 8, 9, 9, 12, + 12, 3, 3, 12, 18, 19, 44, 3, 3, 17, + 18, 21, 21, 19, 17, 17, 35, 34, 24, 24, + 17, 17, 24, 26, 26, 29, 26, 30, 31, 33, + 32, 29, 33, 36, 31, 30, 32, 37, 25, 36, + 22, 20, 11, 37, 43, 43, 43, 45, 10, 45, + 7, 5, 4, 2, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + + 42, 42, 42, 42, 42, 42, 42, 42 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[12] = + { 0, +0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* + igraph library. + Copyright (C) 2007-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "io/gml-header.h" +#include "io/parsers/gml-parser.h" + +#include + +#define YY_EXTRA_TYPE igraph_i_gml_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +#define YY_FATAL_ERROR(msg) IGRAPH_FATAL("Error in GML parser: " # msg) +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#endif +#define YY_NO_INPUT 1 +/* Use to parse inf/nan as number only when expecting a value, i.e. after a keyword. + * Otherwise they are parsed as a keyword. */ + +#define INITIAL 0 +#define VALUE 1 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + yy_size_t yy_n_chars; + yy_size_t yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + yy_size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + if ( yyleng > 0 ) \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = \ + (yytext[yyleng - 1] == '\n'); \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; + yy_current_state += YY_AT_BOL(); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 43 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 85 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + yy_size_t yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +{ /* comments ignored */ } + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +{ BEGIN(INITIAL); return STRING; } + YY_BREAK +case 3: +YY_RULE_SETUP +{ BEGIN(INITIAL); return NUM; } + YY_BREAK +case 4: +YY_RULE_SETUP +{ BEGIN(INITIAL); return NUM; } + YY_BREAK +case 5: +YY_RULE_SETUP +{ BEGIN(VALUE); return KEYWORD; } + YY_BREAK +case 6: +YY_RULE_SETUP +{ + BEGIN(INITIAL); + yyextra->depth++; + if (yyextra->depth >= 32) { + return ERROR; + } else { + return LISTOPEN; + } + } + YY_BREAK +case 7: +YY_RULE_SETUP +{ + yyextra->depth--; + return LISTCLOSE; + } + YY_BREAK +case 8: +YY_RULE_SETUP +{ /* other whitespace ignored */ } + YY_BREAK +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +{ yy_set_bol(true); /* set "beginning of line" even after \r */ } + YY_BREAK +case 10: +YY_RULE_SETUP +{ return ERROR; } + YY_BREAK +case 11: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(VALUE): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + yy_size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + yy_current_state += YY_AT_BOL(); + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 23); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 43 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 23; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 43 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 42); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = (c == '\n'); + if ( YY_CURRENT_BUFFER_LVALUE->yy_at_bol ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + yy_size_t i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yy_size_t yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +yy_size_t yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + diff --git a/src/io/parsers/gml-lexer.h b/src/io/parsers/gml-lexer.h new file mode 100644 index 0000000..f3267a5 --- /dev/null +++ b/src/io/parsers/gml-lexer.h @@ -0,0 +1,730 @@ +#ifndef igraph_gml_yyHEADER_H +#define igraph_gml_yyHEADER_H 1 +#define igraph_gml_yyIN_HEADER 1 + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_gml_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_gml_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_gml_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_gml_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_gml_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_gml_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_gml_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_gml_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_gml_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_gml_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_gml_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_gml_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_gml_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_gml_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_gml_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_gml_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_gml_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_gml_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_gml_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_gml_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_gml_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_gml_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_gml_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_gml_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_gml_yylex_ALREADY_DEFINED +#else +#define yylex igraph_gml_yylex +#endif + +#ifdef yyrestart +#define igraph_gml_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_gml_yyrestart +#endif + +#ifdef yylex_init +#define igraph_gml_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_gml_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_gml_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_gml_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_gml_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_gml_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_gml_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_gml_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_gml_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_gml_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_gml_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_gml_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_gml_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_gml_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_gml_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_gml_yyget_in +#endif + +#ifdef yyset_in +#define igraph_gml_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_gml_yyset_in +#endif + +#ifdef yyget_out +#define igraph_gml_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_gml_yyget_out +#endif + +#ifdef yyset_out +#define igraph_gml_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_gml_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_gml_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_gml_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_gml_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_gml_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_gml_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_gml_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_gml_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_gml_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_gml_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_gml_yyget_column +#endif + +#ifdef yyset_column +#define igraph_gml_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_gml_yyset_column +#endif + +#ifdef yywrap +#define igraph_gml_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_gml_yywrap +#endif + +#ifdef yyget_lval +#define igraph_gml_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_gml_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_gml_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_gml_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_gml_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_gml_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_gml_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_gml_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_gml_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_gml_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_gml_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_gml_yyrealloc +#endif + +#ifdef yyfree +#define igraph_gml_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_gml_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +/* Begin user sect3 */ + +#define igraph_gml_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 +#define VALUE 1 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#ifndef igraph_gml_yy_create_buffer_ALREADY_DEFINED +#undef yy_create_buffer +#endif +#ifndef igraph_gml_yy_delete_buffer_ALREADY_DEFINED +#undef yy_delete_buffer +#endif +#ifndef igraph_gml_yy_scan_buffer_ALREADY_DEFINED +#undef yy_scan_buffer +#endif +#ifndef igraph_gml_yy_scan_string_ALREADY_DEFINED +#undef yy_scan_string +#endif +#ifndef igraph_gml_yy_scan_bytes_ALREADY_DEFINED +#undef yy_scan_bytes +#endif +#ifndef igraph_gml_yy_init_buffer_ALREADY_DEFINED +#undef yy_init_buffer +#endif +#ifndef igraph_gml_yy_flush_buffer_ALREADY_DEFINED +#undef yy_flush_buffer +#endif +#ifndef igraph_gml_yy_load_buffer_state_ALREADY_DEFINED +#undef yy_load_buffer_state +#endif +#ifndef igraph_gml_yy_switch_to_buffer_ALREADY_DEFINED +#undef yy_switch_to_buffer +#endif +#ifndef igraph_gml_yypush_buffer_state_ALREADY_DEFINED +#undef yypush_buffer_state +#endif +#ifndef igraph_gml_yypop_buffer_state_ALREADY_DEFINED +#undef yypop_buffer_state +#endif +#ifndef igraph_gml_yyensure_buffer_stack_ALREADY_DEFINED +#undef yyensure_buffer_stack +#endif +#ifndef igraph_gml_yylex_ALREADY_DEFINED +#undef yylex +#endif +#ifndef igraph_gml_yyrestart_ALREADY_DEFINED +#undef yyrestart +#endif +#ifndef igraph_gml_yylex_init_ALREADY_DEFINED +#undef yylex_init +#endif +#ifndef igraph_gml_yylex_init_extra_ALREADY_DEFINED +#undef yylex_init_extra +#endif +#ifndef igraph_gml_yylex_destroy_ALREADY_DEFINED +#undef yylex_destroy +#endif +#ifndef igraph_gml_yyget_debug_ALREADY_DEFINED +#undef yyget_debug +#endif +#ifndef igraph_gml_yyset_debug_ALREADY_DEFINED +#undef yyset_debug +#endif +#ifndef igraph_gml_yyget_extra_ALREADY_DEFINED +#undef yyget_extra +#endif +#ifndef igraph_gml_yyset_extra_ALREADY_DEFINED +#undef yyset_extra +#endif +#ifndef igraph_gml_yyget_in_ALREADY_DEFINED +#undef yyget_in +#endif +#ifndef igraph_gml_yyset_in_ALREADY_DEFINED +#undef yyset_in +#endif +#ifndef igraph_gml_yyget_out_ALREADY_DEFINED +#undef yyget_out +#endif +#ifndef igraph_gml_yyset_out_ALREADY_DEFINED +#undef yyset_out +#endif +#ifndef igraph_gml_yyget_leng_ALREADY_DEFINED +#undef yyget_leng +#endif +#ifndef igraph_gml_yyget_text_ALREADY_DEFINED +#undef yyget_text +#endif +#ifndef igraph_gml_yyget_lineno_ALREADY_DEFINED +#undef yyget_lineno +#endif +#ifndef igraph_gml_yyset_lineno_ALREADY_DEFINED +#undef yyset_lineno +#endif +#ifndef igraph_gml_yyget_column_ALREADY_DEFINED +#undef yyget_column +#endif +#ifndef igraph_gml_yyset_column_ALREADY_DEFINED +#undef yyset_column +#endif +#ifndef igraph_gml_yywrap_ALREADY_DEFINED +#undef yywrap +#endif +#ifndef igraph_gml_yyget_lval_ALREADY_DEFINED +#undef yyget_lval +#endif +#ifndef igraph_gml_yyset_lval_ALREADY_DEFINED +#undef yyset_lval +#endif +#ifndef igraph_gml_yyget_lloc_ALREADY_DEFINED +#undef yyget_lloc +#endif +#ifndef igraph_gml_yyset_lloc_ALREADY_DEFINED +#undef yyset_lloc +#endif +#ifndef igraph_gml_yyalloc_ALREADY_DEFINED +#undef yyalloc +#endif +#ifndef igraph_gml_yyrealloc_ALREADY_DEFINED +#undef yyrealloc +#endif +#ifndef igraph_gml_yyfree_ALREADY_DEFINED +#undef yyfree +#endif +#ifndef igraph_gml_yytext_ALREADY_DEFINED +#undef yytext +#endif +#ifndef igraph_gml_yyleng_ALREADY_DEFINED +#undef yyleng +#endif +#ifndef igraph_gml_yyin_ALREADY_DEFINED +#undef yyin +#endif +#ifndef igraph_gml_yyout_ALREADY_DEFINED +#undef yyout +#endif +#ifndef igraph_gml_yy_flex_debug_ALREADY_DEFINED +#undef yy_flex_debug +#endif +#ifndef igraph_gml_yylineno_ALREADY_DEFINED +#undef yylineno +#endif +#ifndef igraph_gml_yytables_fload_ALREADY_DEFINED +#undef yytables_fload +#endif +#ifndef igraph_gml_yytables_destroy_ALREADY_DEFINED +#undef yytables_destroy +#endif +#ifndef igraph_gml_yyTABLES_NAME_ALREADY_DEFINED +#undef yyTABLES_NAME +#endif + +#undef igraph_gml_yyIN_HEADER +#endif /* igraph_gml_yyHEADER_H */ diff --git a/src/io/parsers/gml-parser.c b/src/io/parsers/gml-parser.c new file mode 100644 index 0000000..f585c11 --- /dev/null +++ b/src/io/parsers/gml-parser.c @@ -0,0 +1,1859 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse igraph_gml_yyparse +#define yylex igraph_gml_yylex +#define yyerror igraph_gml_yyerror +#define yylval igraph_gml_yylval +#define yychar igraph_gml_yychar +#define yydebug igraph_gml_yydebug +#define yynerrs igraph_gml_yynerrs +#define yylloc igraph_gml_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + STRING = 258, + NUM = 259, + KEYWORD = 260, + LISTOPEN = 261, + LISTCLOSE = 262, + ERROR = 263 + }; +#endif +/* Tokens. */ +#define END 0 +#define STRING 258 +#define NUM 259 +#define KEYWORD 260 +#define LISTOPEN 261 +#define LISTCLOSE 262 +#define ERROR 263 + + + + +/* Copy the first part of user declarations. */ + + + +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_error.h" +#include "igraph_memory.h" + +#include "io/gml-header.h" +#include "io/gml-tree.h" +#include "io/parsers/gml-parser.h" +#include "io/parsers/gml-lexer.h" +#include "io/parse_utils.h" +#include "internal/hacks.h" /* strcasecmp & strndup */ +#include "math/safe_intop.h" + +#include +#include +#include + +int igraph_gml_yyerror(YYLTYPE* locp, igraph_i_gml_parsedata_t *context, + const char *s); +static igraph_error_t igraph_i_gml_get_keyword(const char *s, size_t len, char **res); +static igraph_error_t igraph_i_gml_get_string(const char *s, size_t len, char **res); +static igraph_error_t igraph_i_gml_make_numeric(const char *name, + int line, + igraph_real_t value, + igraph_gml_tree_t **tree); +static igraph_error_t igraph_i_gml_make_string(const char *name, + int line, + char *value, + igraph_gml_tree_t **tree); +static igraph_error_t igraph_i_gml_make_list(const char *name, + int line, + igraph_gml_tree_t *list, + igraph_gml_tree_t **tree); +static igraph_error_t igraph_i_gml_make_empty(igraph_gml_tree_t **tree); +static igraph_error_t igraph_i_gml_merge(igraph_gml_tree_t *t1, igraph_gml_tree_t* t2); + +#define scanner context->scanner + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + char *str; + igraph_gml_tree_t *tree; + igraph_real_t real; +} +/* Line 193 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ + + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 6 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 10 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 9 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 7 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 11 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 15 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 263 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 5, 6, 8, 11, 14, 17, 22, + 24, 26 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 10, 0, -1, 11, -1, -1, 12, -1, 11, 12, + -1, 13, 14, -1, 13, 15, -1, 13, 6, 11, + 7, -1, 5, -1, 4, -1, 3, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 122, 122, 124, 125, 126, 128, 130, 132, 136, + 139, 147 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "$undefined", "\"string\"", "\"number\"", + "\"keyword\"", "\"[\"", "\"]\"", "ERROR", "$accept", "input", "list", + "keyvalue", "key", "num", "string", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 9, 10, 11, 11, 11, 12, 12, 12, 13, + 14, 15 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 0, 1, 2, 2, 2, 4, 1, + 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 3, 9, 0, 2, 4, 0, 1, 5, 11, 10, + 3, 6, 7, 0, 8 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 2, 3, 4, 5, 11, 12 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -4 +static const yytype_int8 yypact[] = +{ + 1, -4, 3, 1, -4, -2, -4, -4, -4, -4, + 1, -4, -4, 0, -4 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -4, -4, -1, -3, -4, -4, -4 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 7, 8, 9, 6, 10, 1, 1, 14, 0, 13, + 7 +}; + +static const yytype_int8 yycheck[] = +{ + 3, 3, 4, 0, 6, 5, 5, 7, -1, 10, + 13 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 5, 10, 11, 12, 13, 0, 12, 3, 4, + 6, 14, 15, 11, 7 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_gml_parsedata_t* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_gml_parsedata_t* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_gml_parsedata_t* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_gml_parsedata_t* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, igraph_i_gml_parsedata_t* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + igraph_i_gml_parsedata_t* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, igraph_i_gml_parsedata_t* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + igraph_i_gml_parsedata_t* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 11: /* "list" */ + + { igraph_gml_tree_destroy((yyvaluep->tree)); }; + + break; + case 12: /* "keyvalue" */ + + { igraph_gml_tree_destroy((yyvaluep->tree)); }; + + break; + case 13: /* "key" */ + + { free((yyvaluep->str)); }; + + break; + case 15: /* "string" */ + + { free((yyvaluep->str)); }; + + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (igraph_i_gml_parsedata_t* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (igraph_i_gml_parsedata_t* context) +#else +int +yyparse (context) + igraph_i_gml_parsedata_t* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + + { context->tree=(yyvsp[(1) - (1)].tree); ;} + break; + + case 3: + + { IGRAPH_YY_CHECK(igraph_i_gml_make_empty(&(yyval.tree))); ;} + break; + + case 4: + + { (yyval.tree)=(yyvsp[(1) - (1)].tree); ;} + break; + + case 5: + + { IGRAPH_YY_CHECK(igraph_i_gml_merge((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree))); (yyval.tree) = (yyvsp[(1) - (2)].tree); ;} + break; + + case 6: + + { IGRAPH_YY_CHECK(igraph_i_gml_make_numeric((yyvsp[(1) - (2)].str), (yylsp[(1) - (2)]).first_line, (yyvsp[(2) - (2)].real), &(yyval.tree))); ;} + break; + + case 7: + + { IGRAPH_YY_CHECK(igraph_i_gml_make_string((yyvsp[(1) - (2)].str), (yylsp[(1) - (2)]).first_line, (yyvsp[(2) - (2)].str), &(yyval.tree))); ;} + break; + + case 8: + + { IGRAPH_YY_CHECK(igraph_i_gml_make_list((yyvsp[(1) - (4)].str), (yylsp[(1) - (4)]).first_line, (yyvsp[(3) - (4)].tree), &(yyval.tree))); ;} + break; + + case 9: + + { IGRAPH_YY_CHECK(igraph_i_gml_get_keyword(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner), + &(yyval.str))); ;} + break; + + case 10: + + { + igraph_real_t val; + IGRAPH_YY_CHECK(igraph_i_parse_real(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner), + &val)); + (yyval.real)=val; +;} + break; + + case 11: + + { IGRAPH_YY_CHECK(igraph_i_gml_get_string(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner), + &(yyval.str))); ;} + break; + + +/* Line 1267 of yacc.c. */ + + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + + + +int igraph_gml_yyerror(YYLTYPE* locp, igraph_i_gml_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in GML file, line %i (%s)", + locp->first_line, s); + return 0; +} + +static igraph_error_t igraph_i_gml_get_keyword(const char *s, size_t len, char **res) { + *res = strndup(s, len); + if (! *res) { + IGRAPH_ERROR("Cannot read GML file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_get_string(const char *s, size_t len, char **res) { + *res = strndup(s+1, len-2); + if (! *res) { + IGRAPH_ERROR("Cannot read GML file.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_make_numeric(const char *name, + int line, + igraph_real_t value, + igraph_gml_tree_t **tree) { + + igraph_gml_tree_t *t = IGRAPH_CALLOC(1, igraph_gml_tree_t); + if (!t) { + IGRAPH_ERROR("Cannot build GML tree.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, t); + + /* The GML spec only requires support for 32-bit signed integers, + * but igraph tries to support the same range as igraph_int_t, + * so that it can read/write all graphs it can represent. + * We treat anything out of that range as real. These values end + * up as igraph_real_t anyway, as igraph does not currently support + * integer-typed attributes. */ + igraph_real_t trunc_value = trunc(value); + if (value == trunc_value && igraph_i_is_real_representable_as_integer(trunc_value)) { + IGRAPH_CHECK(igraph_gml_tree_init_integer(t, name, line, value)); + } else { + IGRAPH_CHECK(igraph_gml_tree_init_real(t, name, line, value)); + } + + *tree = t; + IGRAPH_FINALLY_CLEAN(1); /* t */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_make_string(const char *name, + int line, + char *value, + igraph_gml_tree_t **tree) { + + igraph_gml_tree_t *t = IGRAPH_CALLOC(1, igraph_gml_tree_t); + if (!t) { + IGRAPH_ERROR("Cannot build GML tree.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, t); + + /* if igraph_gml_tree_init_string succeeds, the newly created tree node takes + * ownership of 'value'. If it fails, we need to free 'value' ourselves in order + * not to leak memory */ + IGRAPH_FINALLY(igraph_free, value); + IGRAPH_CHECK(igraph_gml_tree_init_string(t, name, line, value)); + + IGRAPH_FINALLY_CLEAN(1); /* value */ + + *tree = t; + IGRAPH_FINALLY_CLEAN(1); /* t */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_make_list(const char *name, + int line, + igraph_gml_tree_t *list, + igraph_gml_tree_t **tree) { + + igraph_gml_tree_t *t = IGRAPH_CALLOC(1, igraph_gml_tree_t); + if (!t) { + IGRAPH_ERROR("Cannot build GML tree.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, t); + + IGRAPH_CHECK(igraph_gml_tree_init_tree(t, name, line, list)); + + *tree = t; + IGRAPH_FINALLY_CLEAN(1); /* t */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_make_empty(igraph_gml_tree_t **tree) { + igraph_gml_tree_t *t = IGRAPH_CALLOC(1, igraph_gml_tree_t); + if (!t) { + IGRAPH_ERROR("Cannot build GML tree.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, t); + + IGRAPH_CHECK(igraph_gml_tree_init_empty(t)); + + *tree = t; + IGRAPH_FINALLY_CLEAN(1); /* t */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_gml_merge(igraph_gml_tree_t *t1, igraph_gml_tree_t* t2) { + + IGRAPH_CHECK(igraph_gml_tree_mergedest(t1, t2)); + IGRAPH_FREE(t2); + + return IGRAPH_SUCCESS; +} + diff --git a/src/io/parsers/gml-parser.h b/src/io/parsers/gml-parser.h new file mode 100644 index 0000000..7b24236 --- /dev/null +++ b/src/io/parsers/gml-parser.h @@ -0,0 +1,94 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + STRING = 258, + NUM = 259, + KEYWORD = 260, + LISTOPEN = 261, + LISTCLOSE = 262, + ERROR = 263 + }; +#endif +/* Tokens. */ +#define END 0 +#define STRING 258 +#define NUM 259 +#define KEYWORD 260 +#define LISTOPEN 261 +#define LISTCLOSE 262 +#define ERROR 263 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + char *str; + igraph_gml_tree_t *tree; + igraph_real_t real; +} +/* Line 1529 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/src/io/parsers/lgl-lexer.c b/src/io/parsers/lgl-lexer.c new file mode 100644 index 0000000..75cc566 --- /dev/null +++ b/src/io/parsers/lgl-lexer.c @@ -0,0 +1,2284 @@ + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_lgl_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_lgl_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_lgl_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_lgl_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_lgl_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_lgl_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_lgl_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_lgl_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_lgl_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_lgl_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_lgl_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_lgl_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_lgl_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_lgl_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_lgl_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_lgl_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_lgl_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_lgl_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_lgl_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_lgl_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_lgl_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_lgl_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_lgl_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_lgl_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_lgl_yylex_ALREADY_DEFINED +#else +#define yylex igraph_lgl_yylex +#endif + +#ifdef yyrestart +#define igraph_lgl_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_lgl_yyrestart +#endif + +#ifdef yylex_init +#define igraph_lgl_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_lgl_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_lgl_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_lgl_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_lgl_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_lgl_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_lgl_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_lgl_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_lgl_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_lgl_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_lgl_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_lgl_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_lgl_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_lgl_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_lgl_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_lgl_yyget_in +#endif + +#ifdef yyset_in +#define igraph_lgl_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_lgl_yyset_in +#endif + +#ifdef yyget_out +#define igraph_lgl_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_lgl_yyget_out +#endif + +#ifdef yyset_out +#define igraph_lgl_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_lgl_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_lgl_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_lgl_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_lgl_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_lgl_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_lgl_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_lgl_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_lgl_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_lgl_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_lgl_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_lgl_yyget_column +#endif + +#ifdef yyset_column +#define igraph_lgl_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_lgl_yyset_column +#endif + +#ifdef yywrap +#define igraph_lgl_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_lgl_yywrap +#endif + +#ifdef yyget_lval +#define igraph_lgl_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_lgl_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_lgl_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_lgl_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_lgl_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_lgl_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_lgl_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_lgl_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_lgl_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_lgl_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_lgl_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_lgl_yyrealloc +#endif + +#ifdef yyfree +#define igraph_lgl_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_lgl_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + yy_size_t yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define igraph_lgl_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 6 +#define YY_END_OF_BUFFER 7 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[16] = + { 0, + 0, 0, 0, 0, 7, 5, 1, 4, 4, 3, + 2, 1, 4, 3, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 5, 6, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 1, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5 + } ; + +static const YY_CHAR yy_meta[7] = + { 0, + 1, 2, 3, 4, 5, 1 + } ; + +static const flex_int16_t yy_base[20] = + { 0, + 0, 0, 0, 0, 11, 12, 0, 0, 0, 0, + 12, 0, 12, 0, 12, 8, 5, 5, 2 + } ; + +static const flex_int16_t yy_def[20] = + { 0, + 15, 1, 1, 1, 15, 15, 16, 17, 18, 19, + 15, 16, 15, 19, 0, 15, 15, 15, 15 + } ; + +static const flex_int16_t yy_nxt[19] = + { 0, + 6, 7, 8, 9, 10, 11, 14, 13, 13, 12, + 15, 5, 15, 15, 15, 15, 15, 15 + } ; + +static const flex_int16_t yy_chk[19] = + { 0, + 1, 1, 1, 1, 1, 1, 19, 18, 17, 16, + 5, 15, 15, 15, 15, 15, 15, 15 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[7] = + { 0, +0, 0, 0, 1, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "io/lgl-header.h" +#include "io/parsers/lgl-parser.h" + +#include + +#define YY_EXTRA_TYPE igraph_i_lgl_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +#define YY_FATAL_ERROR(msg) IGRAPH_FATAL("Error in LGL parser: " # msg) +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#endif +#define YY_NO_INPUT 1 +/* Anything except non-printable (00-1F), space (20), del (7F) and # */ + +#define INITIAL 0 +#define LINE 1 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + yy_size_t yy_n_chars; + yy_size_t yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + yy_size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + /* ------------------------------------------------whitespace------*/ + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 16 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 12 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + yy_size_t yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +{ /* skip space */ } + YY_BREAK +/* --------------------------------------------------hashmark------*/ +case 2: +YY_RULE_SETUP +{ BEGIN(LINE); return HASH; } + YY_BREAK +/* ----------------------------------------------alphanumeric------*/ +case 3: +YY_RULE_SETUP +{ BEGIN(LINE); return ALNUM; } + YY_BREAK +/* ---------------------------------------------------newline------*/ +case 4: +/* rule 4 can match eol */ +YY_RULE_SETUP +case YY_STATE_EOF(LINE): +{ BEGIN(INITIAL); return NEWLINE; } + YY_BREAK +/* ---------------------------------------------anything else------*/ +case 5: +YY_RULE_SETUP +{ return ERROR; } + YY_BREAK +case 6: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + yy_size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 16 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 16 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 15); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + if ( c == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + yy_size_t i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yy_size_t yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +yy_size_t yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + diff --git a/src/io/parsers/lgl-lexer.h b/src/io/parsers/lgl-lexer.h new file mode 100644 index 0000000..5c8bf40 --- /dev/null +++ b/src/io/parsers/lgl-lexer.h @@ -0,0 +1,730 @@ +#ifndef igraph_lgl_yyHEADER_H +#define igraph_lgl_yyHEADER_H 1 +#define igraph_lgl_yyIN_HEADER 1 + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_lgl_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_lgl_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_lgl_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_lgl_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_lgl_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_lgl_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_lgl_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_lgl_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_lgl_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_lgl_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_lgl_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_lgl_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_lgl_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_lgl_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_lgl_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_lgl_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_lgl_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_lgl_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_lgl_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_lgl_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_lgl_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_lgl_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_lgl_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_lgl_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_lgl_yylex_ALREADY_DEFINED +#else +#define yylex igraph_lgl_yylex +#endif + +#ifdef yyrestart +#define igraph_lgl_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_lgl_yyrestart +#endif + +#ifdef yylex_init +#define igraph_lgl_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_lgl_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_lgl_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_lgl_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_lgl_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_lgl_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_lgl_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_lgl_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_lgl_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_lgl_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_lgl_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_lgl_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_lgl_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_lgl_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_lgl_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_lgl_yyget_in +#endif + +#ifdef yyset_in +#define igraph_lgl_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_lgl_yyset_in +#endif + +#ifdef yyget_out +#define igraph_lgl_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_lgl_yyget_out +#endif + +#ifdef yyset_out +#define igraph_lgl_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_lgl_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_lgl_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_lgl_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_lgl_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_lgl_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_lgl_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_lgl_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_lgl_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_lgl_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_lgl_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_lgl_yyget_column +#endif + +#ifdef yyset_column +#define igraph_lgl_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_lgl_yyset_column +#endif + +#ifdef yywrap +#define igraph_lgl_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_lgl_yywrap +#endif + +#ifdef yyget_lval +#define igraph_lgl_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_lgl_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_lgl_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_lgl_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_lgl_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_lgl_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_lgl_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_lgl_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_lgl_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_lgl_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_lgl_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_lgl_yyrealloc +#endif + +#ifdef yyfree +#define igraph_lgl_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_lgl_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +/* Begin user sect3 */ + +#define igraph_lgl_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 +#define LINE 1 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#ifndef igraph_lgl_yy_create_buffer_ALREADY_DEFINED +#undef yy_create_buffer +#endif +#ifndef igraph_lgl_yy_delete_buffer_ALREADY_DEFINED +#undef yy_delete_buffer +#endif +#ifndef igraph_lgl_yy_scan_buffer_ALREADY_DEFINED +#undef yy_scan_buffer +#endif +#ifndef igraph_lgl_yy_scan_string_ALREADY_DEFINED +#undef yy_scan_string +#endif +#ifndef igraph_lgl_yy_scan_bytes_ALREADY_DEFINED +#undef yy_scan_bytes +#endif +#ifndef igraph_lgl_yy_init_buffer_ALREADY_DEFINED +#undef yy_init_buffer +#endif +#ifndef igraph_lgl_yy_flush_buffer_ALREADY_DEFINED +#undef yy_flush_buffer +#endif +#ifndef igraph_lgl_yy_load_buffer_state_ALREADY_DEFINED +#undef yy_load_buffer_state +#endif +#ifndef igraph_lgl_yy_switch_to_buffer_ALREADY_DEFINED +#undef yy_switch_to_buffer +#endif +#ifndef igraph_lgl_yypush_buffer_state_ALREADY_DEFINED +#undef yypush_buffer_state +#endif +#ifndef igraph_lgl_yypop_buffer_state_ALREADY_DEFINED +#undef yypop_buffer_state +#endif +#ifndef igraph_lgl_yyensure_buffer_stack_ALREADY_DEFINED +#undef yyensure_buffer_stack +#endif +#ifndef igraph_lgl_yylex_ALREADY_DEFINED +#undef yylex +#endif +#ifndef igraph_lgl_yyrestart_ALREADY_DEFINED +#undef yyrestart +#endif +#ifndef igraph_lgl_yylex_init_ALREADY_DEFINED +#undef yylex_init +#endif +#ifndef igraph_lgl_yylex_init_extra_ALREADY_DEFINED +#undef yylex_init_extra +#endif +#ifndef igraph_lgl_yylex_destroy_ALREADY_DEFINED +#undef yylex_destroy +#endif +#ifndef igraph_lgl_yyget_debug_ALREADY_DEFINED +#undef yyget_debug +#endif +#ifndef igraph_lgl_yyset_debug_ALREADY_DEFINED +#undef yyset_debug +#endif +#ifndef igraph_lgl_yyget_extra_ALREADY_DEFINED +#undef yyget_extra +#endif +#ifndef igraph_lgl_yyset_extra_ALREADY_DEFINED +#undef yyset_extra +#endif +#ifndef igraph_lgl_yyget_in_ALREADY_DEFINED +#undef yyget_in +#endif +#ifndef igraph_lgl_yyset_in_ALREADY_DEFINED +#undef yyset_in +#endif +#ifndef igraph_lgl_yyget_out_ALREADY_DEFINED +#undef yyget_out +#endif +#ifndef igraph_lgl_yyset_out_ALREADY_DEFINED +#undef yyset_out +#endif +#ifndef igraph_lgl_yyget_leng_ALREADY_DEFINED +#undef yyget_leng +#endif +#ifndef igraph_lgl_yyget_text_ALREADY_DEFINED +#undef yyget_text +#endif +#ifndef igraph_lgl_yyget_lineno_ALREADY_DEFINED +#undef yyget_lineno +#endif +#ifndef igraph_lgl_yyset_lineno_ALREADY_DEFINED +#undef yyset_lineno +#endif +#ifndef igraph_lgl_yyget_column_ALREADY_DEFINED +#undef yyget_column +#endif +#ifndef igraph_lgl_yyset_column_ALREADY_DEFINED +#undef yyset_column +#endif +#ifndef igraph_lgl_yywrap_ALREADY_DEFINED +#undef yywrap +#endif +#ifndef igraph_lgl_yyget_lval_ALREADY_DEFINED +#undef yyget_lval +#endif +#ifndef igraph_lgl_yyset_lval_ALREADY_DEFINED +#undef yyset_lval +#endif +#ifndef igraph_lgl_yyget_lloc_ALREADY_DEFINED +#undef yyget_lloc +#endif +#ifndef igraph_lgl_yyset_lloc_ALREADY_DEFINED +#undef yyset_lloc +#endif +#ifndef igraph_lgl_yyalloc_ALREADY_DEFINED +#undef yyalloc +#endif +#ifndef igraph_lgl_yyrealloc_ALREADY_DEFINED +#undef yyrealloc +#endif +#ifndef igraph_lgl_yyfree_ALREADY_DEFINED +#undef yyfree +#endif +#ifndef igraph_lgl_yytext_ALREADY_DEFINED +#undef yytext +#endif +#ifndef igraph_lgl_yyleng_ALREADY_DEFINED +#undef yyleng +#endif +#ifndef igraph_lgl_yyin_ALREADY_DEFINED +#undef yyin +#endif +#ifndef igraph_lgl_yyout_ALREADY_DEFINED +#undef yyout +#endif +#ifndef igraph_lgl_yy_flex_debug_ALREADY_DEFINED +#undef yy_flex_debug +#endif +#ifndef igraph_lgl_yylineno_ALREADY_DEFINED +#undef yylineno +#endif +#ifndef igraph_lgl_yytables_fload_ALREADY_DEFINED +#undef yytables_fload +#endif +#ifndef igraph_lgl_yytables_destroy_ALREADY_DEFINED +#undef yytables_destroy +#endif +#ifndef igraph_lgl_yyTABLES_NAME_ALREADY_DEFINED +#undef yyTABLES_NAME +#endif + +#undef igraph_lgl_yyIN_HEADER +#endif /* igraph_lgl_yyHEADER_H */ diff --git a/src/io/parsers/lgl-parser.c b/src/io/parsers/lgl-parser.c new file mode 100644 index 0000000..de5c600 --- /dev/null +++ b/src/io/parsers/lgl-parser.c @@ -0,0 +1,1691 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse igraph_lgl_yyparse +#define yylex igraph_lgl_yylex +#define yyerror igraph_lgl_yyerror +#define yylval igraph_lgl_yylval +#define yychar igraph_lgl_yychar +#define yydebug igraph_lgl_yydebug +#define yynerrs igraph_lgl_yynerrs +#define yylloc igraph_lgl_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + ALNUM = 258, + NEWLINE = 259, + HASH = 260, + ERROR = 261 + }; +#endif +/* Tokens. */ +#define END 0 +#define ALNUM 258 +#define NEWLINE 259 +#define HASH 260 +#define ERROR 261 + + + + +/* Copy the first part of user declarations. */ + + + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" + +#include "io/lgl-header.h" +#include "io/parsers/lgl-parser.h" +#include "io/parsers/lgl-lexer.h" +#include "io/parse_utils.h" +#include "internal/hacks.h" + +#include +#include + +int igraph_lgl_yyerror(YYLTYPE* locp, igraph_i_lgl_parsedata_t *context, + const char *s); + +#define scanner context->scanner + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t edgenum; + igraph_real_t weightnum; +} +/* Line 193 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ + + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 2 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 10 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 7 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 8 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 12 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 17 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 261 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 4, 7, 10, 13, 17, 18, 21, + 24, 28, 30 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 8, 0, -1, -1, 8, 4, -1, 8, 9, -1, + 10, 11, -1, 5, 13, 4, -1, -1, 11, 12, + -1, 13, 4, -1, 13, 14, 4, -1, 3, -1, + 3, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 92, 92, 93, 94, 97, 99, 101, 101, 103, + 108, 117, 127 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "$undefined", "\"alphanumeric\"", + "\"end of line\"", "\"#\"", "ERROR", "$accept", "input", "vertex", + "vertexdef", "edges", "edge", "edgeid", "weight", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 7, 8, 8, 8, 9, 10, 11, 11, 12, + 12, 13, 14 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 0, 2, 2, 2, 3, 0, 2, 2, + 3, 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 2, 0, 1, 3, 0, 4, 7, 11, 0, 5, + 6, 8, 0, 12, 9, 0, 10 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 5, 6, 9, 11, 8, 15 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -3 +static const yytype_int8 yypact[] = +{ + -3, 0, -3, -3, 3, -3, -3, -3, -1, 3, + -3, -3, -2, -3, -3, 4, -3 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -3, -3, -3, -3, -3, -3, 1, -3 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 2, 13, 14, 10, 3, 4, 7, 0, 16, 0, + 12 +}; + +static const yytype_int8 yycheck[] = +{ + 0, 3, 4, 4, 4, 5, 3, -1, 4, -1, + 9 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 8, 0, 4, 5, 9, 10, 3, 13, 11, + 4, 12, 13, 3, 4, 14, 4 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_lgl_parsedata_t* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_lgl_parsedata_t* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_lgl_parsedata_t* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_lgl_parsedata_t* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, igraph_i_lgl_parsedata_t* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + igraph_i_lgl_parsedata_t* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, igraph_i_lgl_parsedata_t* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + igraph_i_lgl_parsedata_t* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (igraph_i_lgl_parsedata_t* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (igraph_i_lgl_parsedata_t* context) +#else +int +yyparse (context) + igraph_i_lgl_parsedata_t* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 6: + + { context->actvertex=(yyvsp[(2) - (3)].edgenum); ;} + break; + + case 9: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actvertex)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (2)].edgenum))); + IGRAPH_YY_CHECK(igraph_vector_push_back(context->weights, 1.0)); + ;} + break; + + case 10: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actvertex)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (3)].edgenum))); + IGRAPH_YY_CHECK(igraph_vector_push_back(context->weights, (yyvsp[(2) - (3)].weightnum))); + context->has_weights = 1; + ;} + break; + + case 11: + + { + igraph_int_t trie_id; + IGRAPH_YY_CHECK(igraph_trie_get_len(context->trie, + igraph_lgl_yyget_text(scanner), + igraph_lgl_yyget_leng(scanner), + &trie_id + )); + (yyval.edgenum) = trie_id; +;} + break; + + case 12: + + { + igraph_real_t val; + IGRAPH_YY_CHECK(igraph_i_parse_real(igraph_lgl_yyget_text(scanner), + igraph_lgl_yyget_leng(scanner), + &val)); + (yyval.weightnum)=val; +;} + break; + + +/* Line 1267 of yacc.c. */ + + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + + + +int igraph_lgl_yyerror(YYLTYPE* locp, igraph_i_lgl_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char), + "Parse error in LGL file, line %i (%s)", + locp->first_line, s); + return 0; +} + diff --git a/src/io/parsers/lgl-parser.h b/src/io/parsers/lgl-parser.h new file mode 100644 index 0000000..57cb80c --- /dev/null +++ b/src/io/parsers/lgl-parser.h @@ -0,0 +1,89 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + ALNUM = 258, + NEWLINE = 259, + HASH = 260, + ERROR = 261 + }; +#endif +/* Tokens. */ +#define END 0 +#define ALNUM 258 +#define NEWLINE 259 +#define HASH 260 +#define ERROR 261 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t edgenum; + igraph_real_t weightnum; +} +/* Line 1529 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/src/io/parsers/ncol-lexer.c b/src/io/parsers/ncol-lexer.c new file mode 100644 index 0000000..07b70d3 --- /dev/null +++ b/src/io/parsers/ncol-lexer.c @@ -0,0 +1,2279 @@ + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_ncol_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_ncol_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_ncol_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_ncol_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_ncol_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_ncol_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_ncol_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_ncol_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_ncol_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_ncol_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_ncol_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_ncol_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_ncol_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_ncol_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_ncol_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_ncol_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_ncol_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_ncol_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_ncol_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_ncol_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_ncol_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_ncol_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_ncol_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_ncol_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_ncol_yylex_ALREADY_DEFINED +#else +#define yylex igraph_ncol_yylex +#endif + +#ifdef yyrestart +#define igraph_ncol_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_ncol_yyrestart +#endif + +#ifdef yylex_init +#define igraph_ncol_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_ncol_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_ncol_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_ncol_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_ncol_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_ncol_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_ncol_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_ncol_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_ncol_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_ncol_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_ncol_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_ncol_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_ncol_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_ncol_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_ncol_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_ncol_yyget_in +#endif + +#ifdef yyset_in +#define igraph_ncol_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_ncol_yyset_in +#endif + +#ifdef yyget_out +#define igraph_ncol_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_ncol_yyget_out +#endif + +#ifdef yyset_out +#define igraph_ncol_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_ncol_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_ncol_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_ncol_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_ncol_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_ncol_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_ncol_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_ncol_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_ncol_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_ncol_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_ncol_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_ncol_yyget_column +#endif + +#ifdef yyset_column +#define igraph_ncol_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_ncol_yyset_column +#endif + +#ifdef yywrap +#define igraph_ncol_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_ncol_yywrap +#endif + +#ifdef yyget_lval +#define igraph_ncol_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_ncol_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_ncol_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_ncol_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_ncol_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_ncol_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_ncol_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_ncol_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_ncol_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_ncol_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_ncol_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_ncol_yyrealloc +#endif + +#ifdef yyfree +#define igraph_ncol_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_ncol_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + yy_size_t yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define igraph_ncol_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 5 +#define YY_END_OF_BUFFER 6 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[15] = + { 0, + 0, 0, 0, 0, 6, 4, 1, 3, 3, 2, + 1, 3, 2, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 1, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5 + } ; + +static const YY_CHAR yy_meta[6] = + { 0, + 1, 2, 3, 4, 5 + } ; + +static const flex_int16_t yy_base[19] = + { 0, + 0, 0, 0, 0, 10, 11, 0, 0, 0, 0, + 0, 11, 0, 11, 7, 4, 4, 1 + } ; + +static const flex_int16_t yy_def[19] = + { 0, + 14, 1, 1, 1, 14, 14, 15, 16, 17, 18, + 15, 14, 18, 0, 14, 14, 14, 14 + } ; + +static const flex_int16_t yy_nxt[17] = + { 0, + 6, 7, 8, 9, 10, 13, 12, 12, 11, 14, + 5, 14, 14, 14, 14, 14 + } ; + +static const flex_int16_t yy_chk[17] = + { 0, + 1, 1, 1, 1, 1, 18, 17, 16, 15, 5, + 14, 14, 14, 14, 14, 14 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[6] = + { 0, +0, 0, 1, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "io/ncol-header.h" +#include "io/parsers/ncol-parser.h" + +#include + +#define YY_EXTRA_TYPE igraph_i_ncol_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +#define YY_FATAL_ERROR(msg) IGRAPH_FATAL("Error in NCOL parser: " # msg) +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#endif +#define YY_NO_INPUT 1 +/* Anything except non-printable (00-1F), space (20) and del (7F) */ + +#define INITIAL 0 +#define LINE 1 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + yy_size_t yy_n_chars; + yy_size_t yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + yy_size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + /* ------------------------------------------------whitespace------*/ + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 15 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 11 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + yy_size_t yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +{ /* skip space */ } + YY_BREAK +/* ----------------------------------------------alphanumeric------*/ +case 2: +YY_RULE_SETUP +{ BEGIN(LINE); return ALNUM; } + YY_BREAK +/* ---------------------------------------------------newline------*/ +case 3: +/* rule 3 can match eol */ +YY_RULE_SETUP +case YY_STATE_EOF(LINE): +{ BEGIN(INITIAL); return NEWLINE; } + YY_BREAK +/* ---------------------------------------------anything else------*/ +case 4: +YY_RULE_SETUP +{ return ERROR; } + YY_BREAK +case 5: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + yy_size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 15 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 15 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 14); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + if ( c == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + yy_size_t i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yy_size_t yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +yy_size_t yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + diff --git a/src/io/parsers/ncol-lexer.h b/src/io/parsers/ncol-lexer.h new file mode 100644 index 0000000..fd7630f --- /dev/null +++ b/src/io/parsers/ncol-lexer.h @@ -0,0 +1,730 @@ +#ifndef igraph_ncol_yyHEADER_H +#define igraph_ncol_yyHEADER_H 1 +#define igraph_ncol_yyIN_HEADER 1 + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_ncol_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_ncol_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_ncol_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_ncol_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_ncol_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_ncol_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_ncol_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_ncol_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_ncol_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_ncol_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_ncol_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_ncol_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_ncol_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_ncol_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_ncol_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_ncol_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_ncol_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_ncol_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_ncol_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_ncol_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_ncol_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_ncol_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_ncol_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_ncol_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_ncol_yylex_ALREADY_DEFINED +#else +#define yylex igraph_ncol_yylex +#endif + +#ifdef yyrestart +#define igraph_ncol_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_ncol_yyrestart +#endif + +#ifdef yylex_init +#define igraph_ncol_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_ncol_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_ncol_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_ncol_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_ncol_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_ncol_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_ncol_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_ncol_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_ncol_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_ncol_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_ncol_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_ncol_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_ncol_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_ncol_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_ncol_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_ncol_yyget_in +#endif + +#ifdef yyset_in +#define igraph_ncol_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_ncol_yyset_in +#endif + +#ifdef yyget_out +#define igraph_ncol_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_ncol_yyget_out +#endif + +#ifdef yyset_out +#define igraph_ncol_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_ncol_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_ncol_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_ncol_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_ncol_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_ncol_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_ncol_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_ncol_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_ncol_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_ncol_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_ncol_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_ncol_yyget_column +#endif + +#ifdef yyset_column +#define igraph_ncol_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_ncol_yyset_column +#endif + +#ifdef yywrap +#define igraph_ncol_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_ncol_yywrap +#endif + +#ifdef yyget_lval +#define igraph_ncol_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_ncol_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_ncol_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_ncol_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_ncol_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_ncol_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_ncol_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_ncol_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_ncol_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_ncol_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_ncol_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_ncol_yyrealloc +#endif + +#ifdef yyfree +#define igraph_ncol_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_ncol_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +/* Begin user sect3 */ + +#define igraph_ncol_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 +#define LINE 1 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#ifndef igraph_ncol_yy_create_buffer_ALREADY_DEFINED +#undef yy_create_buffer +#endif +#ifndef igraph_ncol_yy_delete_buffer_ALREADY_DEFINED +#undef yy_delete_buffer +#endif +#ifndef igraph_ncol_yy_scan_buffer_ALREADY_DEFINED +#undef yy_scan_buffer +#endif +#ifndef igraph_ncol_yy_scan_string_ALREADY_DEFINED +#undef yy_scan_string +#endif +#ifndef igraph_ncol_yy_scan_bytes_ALREADY_DEFINED +#undef yy_scan_bytes +#endif +#ifndef igraph_ncol_yy_init_buffer_ALREADY_DEFINED +#undef yy_init_buffer +#endif +#ifndef igraph_ncol_yy_flush_buffer_ALREADY_DEFINED +#undef yy_flush_buffer +#endif +#ifndef igraph_ncol_yy_load_buffer_state_ALREADY_DEFINED +#undef yy_load_buffer_state +#endif +#ifndef igraph_ncol_yy_switch_to_buffer_ALREADY_DEFINED +#undef yy_switch_to_buffer +#endif +#ifndef igraph_ncol_yypush_buffer_state_ALREADY_DEFINED +#undef yypush_buffer_state +#endif +#ifndef igraph_ncol_yypop_buffer_state_ALREADY_DEFINED +#undef yypop_buffer_state +#endif +#ifndef igraph_ncol_yyensure_buffer_stack_ALREADY_DEFINED +#undef yyensure_buffer_stack +#endif +#ifndef igraph_ncol_yylex_ALREADY_DEFINED +#undef yylex +#endif +#ifndef igraph_ncol_yyrestart_ALREADY_DEFINED +#undef yyrestart +#endif +#ifndef igraph_ncol_yylex_init_ALREADY_DEFINED +#undef yylex_init +#endif +#ifndef igraph_ncol_yylex_init_extra_ALREADY_DEFINED +#undef yylex_init_extra +#endif +#ifndef igraph_ncol_yylex_destroy_ALREADY_DEFINED +#undef yylex_destroy +#endif +#ifndef igraph_ncol_yyget_debug_ALREADY_DEFINED +#undef yyget_debug +#endif +#ifndef igraph_ncol_yyset_debug_ALREADY_DEFINED +#undef yyset_debug +#endif +#ifndef igraph_ncol_yyget_extra_ALREADY_DEFINED +#undef yyget_extra +#endif +#ifndef igraph_ncol_yyset_extra_ALREADY_DEFINED +#undef yyset_extra +#endif +#ifndef igraph_ncol_yyget_in_ALREADY_DEFINED +#undef yyget_in +#endif +#ifndef igraph_ncol_yyset_in_ALREADY_DEFINED +#undef yyset_in +#endif +#ifndef igraph_ncol_yyget_out_ALREADY_DEFINED +#undef yyget_out +#endif +#ifndef igraph_ncol_yyset_out_ALREADY_DEFINED +#undef yyset_out +#endif +#ifndef igraph_ncol_yyget_leng_ALREADY_DEFINED +#undef yyget_leng +#endif +#ifndef igraph_ncol_yyget_text_ALREADY_DEFINED +#undef yyget_text +#endif +#ifndef igraph_ncol_yyget_lineno_ALREADY_DEFINED +#undef yyget_lineno +#endif +#ifndef igraph_ncol_yyset_lineno_ALREADY_DEFINED +#undef yyset_lineno +#endif +#ifndef igraph_ncol_yyget_column_ALREADY_DEFINED +#undef yyget_column +#endif +#ifndef igraph_ncol_yyset_column_ALREADY_DEFINED +#undef yyset_column +#endif +#ifndef igraph_ncol_yywrap_ALREADY_DEFINED +#undef yywrap +#endif +#ifndef igraph_ncol_yyget_lval_ALREADY_DEFINED +#undef yyget_lval +#endif +#ifndef igraph_ncol_yyset_lval_ALREADY_DEFINED +#undef yyset_lval +#endif +#ifndef igraph_ncol_yyget_lloc_ALREADY_DEFINED +#undef yyget_lloc +#endif +#ifndef igraph_ncol_yyset_lloc_ALREADY_DEFINED +#undef yyset_lloc +#endif +#ifndef igraph_ncol_yyalloc_ALREADY_DEFINED +#undef yyalloc +#endif +#ifndef igraph_ncol_yyrealloc_ALREADY_DEFINED +#undef yyrealloc +#endif +#ifndef igraph_ncol_yyfree_ALREADY_DEFINED +#undef yyfree +#endif +#ifndef igraph_ncol_yytext_ALREADY_DEFINED +#undef yytext +#endif +#ifndef igraph_ncol_yyleng_ALREADY_DEFINED +#undef yyleng +#endif +#ifndef igraph_ncol_yyin_ALREADY_DEFINED +#undef yyin +#endif +#ifndef igraph_ncol_yyout_ALREADY_DEFINED +#undef yyout +#endif +#ifndef igraph_ncol_yy_flex_debug_ALREADY_DEFINED +#undef yy_flex_debug +#endif +#ifndef igraph_ncol_yylineno_ALREADY_DEFINED +#undef yylineno +#endif +#ifndef igraph_ncol_yytables_fload_ALREADY_DEFINED +#undef yytables_fload +#endif +#ifndef igraph_ncol_yytables_destroy_ALREADY_DEFINED +#undef yytables_destroy +#endif +#ifndef igraph_ncol_yyTABLES_NAME_ALREADY_DEFINED +#undef yyTABLES_NAME +#endif + +#undef igraph_ncol_yyIN_HEADER +#endif /* igraph_ncol_yyHEADER_H */ diff --git a/src/io/parsers/ncol-parser.c b/src/io/parsers/ncol-parser.c new file mode 100644 index 0000000..a6fe935 --- /dev/null +++ b/src/io/parsers/ncol-parser.c @@ -0,0 +1,1683 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse igraph_ncol_yyparse +#define yylex igraph_ncol_yylex +#define yyerror igraph_ncol_yyerror +#define yylval igraph_ncol_yylval +#define yychar igraph_ncol_yychar +#define yydebug igraph_ncol_yydebug +#define yynerrs igraph_ncol_yynerrs +#define yylloc igraph_ncol_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + ALNUM = 258, + NEWLINE = 259, + ERROR = 260 + }; +#endif +/* Tokens. */ +#define END 0 +#define ALNUM 258 +#define NEWLINE 259 +#define ERROR 260 + + + + +/* Copy the first part of user declarations. */ + + + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" + +#include "io/ncol-header.h" +#include "io/parsers/ncol-parser.h" +#include "io/parsers/ncol-lexer.h" +#include "io/parse_utils.h" +#include "internal/hacks.h" + +#include +#include + +int igraph_ncol_yyerror(YYLTYPE* locp, + igraph_i_ncol_parsedata_t *context, + const char *s); + +#define scanner context->scanner + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t edgenum; + igraph_real_t weightnum; +} +/* Line 193 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ + + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 2 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 7 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 6 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 6 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 9 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 13 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 260 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 4, 7, 10, 13, 17, 20, 22 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 7, 0, -1, -1, 7, 4, -1, 7, 8, -1, + 9, 4, -1, 9, 11, 4, -1, 10, 10, -1, + 3, -1, 3, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 92, 92, 93, 94, 97, 100, 106, 111, 121 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "$undefined", "\"alphanumeric\"", + "\"end of line\"", "ERROR", "$accept", "input", "edge", "endpoints", + "edgeid", "weight", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 6, 7, 7, 7, 8, 8, 9, 10, 11 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 0, 2, 2, 2, 3, 2, 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 2, 0, 1, 8, 3, 4, 0, 0, 9, 5, + 0, 7, 6 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 5, 6, 7, 10 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -3 +static const yytype_int8 yypact[] = +{ + -3, 0, -3, -3, -3, -3, -2, 2, -3, -3, + 3, -3, -3 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -3, -3, -3, -3, -1, -3 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 2, 8, 9, 3, 4, 3, 11, 12 +}; + +static const yytype_uint8 yycheck[] = +{ + 0, 3, 4, 3, 4, 3, 7, 4 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 7, 0, 3, 4, 8, 9, 10, 3, 4, + 11, 10, 4 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_ncol_parsedata_t* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_ncol_parsedata_t* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_ncol_parsedata_t* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_ncol_parsedata_t* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, igraph_i_ncol_parsedata_t* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + igraph_i_ncol_parsedata_t* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, igraph_i_ncol_parsedata_t* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + igraph_i_ncol_parsedata_t* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (igraph_i_ncol_parsedata_t* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (igraph_i_ncol_parsedata_t* context) +#else +int +yyparse (context) + igraph_i_ncol_parsedata_t* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 5: + + { + IGRAPH_YY_CHECK(igraph_vector_push_back(context->weights, 1.0)); + ;} + break; + + case 6: + + { + IGRAPH_YY_CHECK(igraph_vector_push_back(context->weights, (yyvsp[(2) - (3)].weightnum))); + context->has_weights = true; + ;} + break; + + case 7: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (2)].edgenum))); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(2) - (2)].edgenum))); +;} + break; + + case 8: + + { + igraph_int_t trie_id; + IGRAPH_YY_CHECK(igraph_trie_get_len(context->trie, + igraph_ncol_yyget_text(scanner), + igraph_ncol_yyget_leng(scanner), + &trie_id + )); + (yyval.edgenum) = trie_id; +;} + break; + + case 9: + + { + igraph_real_t val; + IGRAPH_YY_CHECK(igraph_i_parse_real(igraph_ncol_yyget_text(scanner), + igraph_ncol_yyget_leng(scanner), + &val)); + (yyval.weightnum)=val; +;} + break; + + +/* Line 1267 of yacc.c. */ + + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + + + +int igraph_ncol_yyerror(YYLTYPE* locp, + igraph_i_ncol_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in NCOL file, line %i (%s)", + locp->first_line, s); + return 0; +} + diff --git a/src/io/parsers/ncol-parser.h b/src/io/parsers/ncol-parser.h new file mode 100644 index 0000000..628a219 --- /dev/null +++ b/src/io/parsers/ncol-parser.h @@ -0,0 +1,87 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + ALNUM = 258, + NEWLINE = 259, + ERROR = 260 + }; +#endif +/* Tokens. */ +#define END 0 +#define ALNUM 258 +#define NEWLINE 259 +#define ERROR 260 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t edgenum; + igraph_real_t weightnum; +} +/* Line 1529 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/src/io/parsers/pajek-lexer.c b/src/io/parsers/pajek-lexer.c new file mode 100644 index 0000000..ff9e453 --- /dev/null +++ b/src/io/parsers/pajek-lexer.c @@ -0,0 +1,2683 @@ + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_pajek_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_pajek_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_pajek_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_pajek_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_pajek_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_pajek_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_pajek_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_pajek_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_pajek_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_pajek_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_pajek_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_pajek_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_pajek_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_pajek_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_pajek_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_pajek_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_pajek_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_pajek_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_pajek_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_pajek_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_pajek_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_pajek_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_pajek_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_pajek_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_pajek_yylex_ALREADY_DEFINED +#else +#define yylex igraph_pajek_yylex +#endif + +#ifdef yyrestart +#define igraph_pajek_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_pajek_yyrestart +#endif + +#ifdef yylex_init +#define igraph_pajek_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_pajek_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_pajek_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_pajek_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_pajek_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_pajek_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_pajek_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_pajek_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_pajek_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_pajek_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_pajek_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_pajek_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_pajek_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_pajek_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_pajek_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_pajek_yyget_in +#endif + +#ifdef yyset_in +#define igraph_pajek_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_pajek_yyset_in +#endif + +#ifdef yyget_out +#define igraph_pajek_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_pajek_yyget_out +#endif + +#ifdef yyset_out +#define igraph_pajek_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_pajek_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_pajek_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_pajek_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_pajek_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_pajek_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_pajek_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_pajek_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_pajek_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_pajek_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_pajek_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_pajek_yyget_column +#endif + +#ifdef yyset_column +#define igraph_pajek_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_pajek_yyset_column +#endif + +#ifdef yywrap +#define igraph_pajek_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_pajek_yywrap +#endif + +#ifdef yyget_lval +#define igraph_pajek_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_pajek_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_pajek_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_pajek_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_pajek_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_pajek_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_pajek_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_pajek_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_pajek_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_pajek_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_pajek_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_pajek_yyrealloc +#endif + +#ifdef yyfree +#define igraph_pajek_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_pajek_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + yy_size_t yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define igraph_pajek_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 57 +#define YY_END_OF_BUFFER 58 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[161] = + { 0, + 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, + 0, 0, 58, 56, 8, 17, 17, 55, 56, 55, + 19, 55, 56, 4, 5, 5, 4, 3, 6, 6, + 2, 2, 2, 2, 55, 55, 55, 55, 55, 24, + 23, 55, 55, 55, 40, 38, 55, 55, 55, 47, + 39, 41, 37, 8, 17, 55, 0, 18, 19, 55, + 55, 0, 7, 7, 55, 16, 16, 16, 16, 16, + 16, 4, 4, 5, 6, 6, 0, 26, 27, 55, + 25, 29, 28, 55, 30, 55, 55, 55, 55, 42, + 44, 46, 55, 35, 36, 43, 45, 52, 51, 48, + + 49, 19, 55, 19, 7, 16, 16, 16, 16, 16, + 1, 55, 32, 55, 22, 34, 55, 55, 55, 53, + 55, 16, 16, 16, 16, 16, 33, 31, 55, 55, + 54, 50, 11, 16, 16, 16, 16, 55, 55, 16, + 12, 16, 16, 16, 20, 21, 16, 16, 15, 16, + 16, 16, 16, 9, 16, 13, 16, 10, 14, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 6, 5, 5, 7, 5, 5, 5, + 5, 8, 9, 5, 10, 11, 5, 12, 13, 14, + 12, 12, 12, 12, 12, 12, 12, 5, 5, 5, + 5, 5, 5, 5, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 5, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 5, + 5, 5, 5, 5, 39, 5, 40, 41, 42, 43, + + 44, 45, 46, 47, 48, 5, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 5, 5, 5, 5, 5, 1, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 64, 5, 5, 5, + 65, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 66, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5 + } ; + +static const YY_CHAR yy_meta[68] = + { 0, + 1, 2, 3, 3, 4, 2, 4, 2, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 5 + } ; + +static const flex_int16_t yy_base[172] = + { 0, + 0, 67, 15, 23, 31, 35, 18, 26, 134, 33, + 201, 35, 337, 392, 331, 328, 328, 0, 243, 32, + 39, 53, 69, 237, 208, 197, 0, 392, 196, 0, + 392, 178, 392, 94, 30, 32, 45, 99, 42, 0, + 0, 47, 93, 86, 97, 0, 65, 35, 86, 152, + 0, 0, 0, 115, 392, 0, 109, 392, 252, 93, + 164, 119, 108, 98, 232, 0, 63, 90, 109, 108, + 112, 95, 0, 392, 84, 0, 0, 0, 0, 134, + 0, 0, 0, 123, 0, 137, 137, 175, 179, 0, + 0, 0, 190, 0, 0, 0, 0, 0, 0, 197, + + 0, 240, 215, 260, 392, 214, 211, 212, 227, 230, + 392, 234, 0, 246, 0, 0, 260, 261, 244, 0, + 255, 247, 261, 250, 246, 250, 0, 0, 270, 271, + 0, 0, 264, 258, 268, 265, 272, 264, 265, 276, + 284, 273, 280, 300, 0, 0, 287, 298, 0, 301, + 307, 294, 296, 0, 297, 0, 297, 0, 0, 392, + 355, 360, 365, 370, 3, 375, 378, 1, 381, 384, + 387 + } ; + +static const flex_int16_t yy_def[172] = + { 0, + 161, 161, 162, 162, 163, 163, 164, 164, 161, 9, + 161, 11, 160, 160, 160, 160, 160, 165, 166, 165, + 165, 167, 168, 169, 160, 160, 169, 160, 170, 170, + 160, 160, 160, 160, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 160, 160, 165, 166, 160, 165, 165, + 165, 171, 160, 160, 167, 168, 168, 168, 168, 168, + 168, 169, 169, 160, 170, 170, 160, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, + + 165, 165, 165, 165, 160, 168, 168, 168, 168, 168, + 160, 165, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 168, 168, 168, 168, 168, 165, 165, 165, 165, + 165, 165, 168, 168, 168, 168, 168, 165, 165, 168, + 168, 168, 168, 168, 165, 165, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, 168, 0, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160 + } ; + +static const flex_int16_t yy_nxt[460] = + { 0, + 14, 15, 16, 17, 66, 19, 56, 14, 20, 20, + 160, 21, 21, 21, 160, 14, 24, 25, 26, 32, + 33, 160, 14, 14, 24, 25, 26, 32, 33, 160, + 28, 14, 29, 25, 26, 14, 29, 25, 26, 22, + 23, 22, 23, 59, 59, 59, 78, 94, 95, 60, + 59, 59, 59, 160, 62, 63, 64, 61, 62, 80, + 62, 81, 160, 86, 111, 79, 14, 14, 15, 16, + 17, 78, 19, 22, 23, 20, 20, 87, 21, 21, + 21, 14, 61, 67, 80, 75, 81, 68, 86, 14, + 79, 34, 93, 106, 69, 70, 72, 14, 96, 97, + + 105, 14, 87, 71, 102, 102, 102, 107, 67, 90, + 91, 105, 68, 82, 58, 83, 54, 93, 106, 69, + 70, 63, 64, 108, 89, 92, 109, 84, 71, 85, + 110, 88, 107, 14, 14, 15, 16, 17, 82, 19, + 83, 14, 20, 20, 114, 21, 21, 21, 108, 35, + 92, 109, 84, 36, 85, 110, 37, 77, 38, 115, + 112, 116, 39, 40, 41, 113, 98, 42, 99, 114, + 43, 44, 103, 103, 35, 104, 104, 104, 36, 54, + 100, 37, 101, 38, 115, 112, 116, 39, 40, 41, + 113, 98, 42, 99, 117, 43, 44, 75, 118, 74, + + 14, 14, 15, 16, 17, 100, 19, 101, 14, 20, + 20, 74, 21, 21, 21, 45, 119, 46, 121, 117, + 47, 120, 48, 118, 49, 50, 104, 104, 104, 51, + 122, 123, 52, 62, 63, 64, 53, 62, 72, 62, + 45, 119, 46, 121, 124, 47, 120, 48, 58, 49, + 50, 102, 102, 102, 51, 122, 123, 52, 61, 125, + 126, 53, 60, 59, 59, 59, 127, 14, 128, 124, + 61, 104, 104, 104, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 61, 125, 126, 138, 139, 140, 141, + 142, 127, 143, 128, 144, 61, 145, 146, 147, 129, + + 130, 131, 132, 133, 134, 135, 136, 137, 148, 149, + 150, 138, 139, 140, 141, 142, 151, 143, 152, 144, + 153, 145, 146, 147, 154, 155, 156, 157, 158, 159, + 55, 55, 54, 148, 149, 150, 160, 160, 160, 160, + 160, 151, 160, 152, 160, 153, 160, 160, 160, 154, + 155, 156, 157, 158, 159, 18, 18, 18, 18, 18, + 27, 27, 27, 27, 27, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 57, 57, 160, 57, 65, + 65, 65, 73, 160, 73, 76, 160, 76, 62, 62, + 62, 13, 160, 160, 160, 160, 160, 160, 160, 160, + + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160 + } ; + +static const flex_int16_t yy_chk[460] = + { 0, + 1, 1, 1, 1, 168, 1, 165, 1, 1, 1, + 0, 1, 1, 1, 0, 3, 3, 3, 3, 7, + 7, 0, 3, 4, 4, 4, 4, 8, 8, 0, + 4, 5, 5, 5, 5, 6, 6, 6, 6, 10, + 10, 12, 12, 20, 20, 20, 35, 48, 48, 21, + 21, 21, 21, 0, 22, 22, 22, 21, 22, 36, + 22, 37, 0, 39, 77, 35, 1, 2, 2, 2, + 2, 35, 2, 2, 2, 2, 2, 42, 2, 2, + 2, 3, 21, 23, 36, 75, 37, 23, 39, 4, + 35, 8, 47, 67, 23, 23, 72, 5, 49, 49, + + 64, 6, 42, 23, 60, 60, 60, 68, 23, 45, + 45, 63, 23, 38, 57, 38, 54, 47, 67, 23, + 23, 62, 62, 69, 44, 45, 70, 38, 23, 38, + 71, 43, 68, 2, 9, 9, 9, 9, 38, 9, + 38, 9, 9, 9, 84, 9, 9, 9, 69, 9, + 45, 70, 38, 9, 38, 71, 9, 34, 9, 86, + 80, 87, 9, 9, 9, 80, 50, 9, 50, 84, + 9, 9, 61, 61, 9, 61, 61, 61, 9, 32, + 50, 9, 50, 9, 86, 80, 87, 9, 9, 9, + 80, 50, 9, 50, 88, 9, 9, 29, 89, 26, + + 9, 11, 11, 11, 11, 50, 11, 50, 11, 11, + 11, 25, 11, 11, 11, 11, 93, 11, 100, 88, + 11, 93, 11, 89, 11, 11, 103, 103, 103, 11, + 106, 107, 11, 65, 65, 65, 11, 65, 24, 65, + 11, 93, 11, 100, 108, 11, 93, 11, 19, 11, + 11, 102, 102, 102, 11, 106, 107, 11, 102, 109, + 110, 11, 59, 59, 59, 59, 112, 11, 114, 108, + 59, 104, 104, 104, 117, 118, 119, 121, 122, 123, + 124, 125, 126, 102, 109, 110, 129, 130, 133, 134, + 135, 112, 136, 114, 137, 59, 138, 139, 140, 117, + + 118, 119, 121, 122, 123, 124, 125, 126, 141, 142, + 143, 129, 130, 133, 134, 135, 144, 136, 147, 137, + 148, 138, 139, 140, 150, 151, 152, 153, 155, 157, + 17, 16, 15, 141, 142, 143, 13, 0, 0, 0, + 0, 144, 0, 147, 0, 148, 0, 0, 0, 150, + 151, 152, 153, 155, 157, 161, 161, 161, 161, 161, + 162, 162, 162, 162, 162, 163, 163, 163, 163, 163, + 164, 164, 164, 164, 164, 166, 166, 0, 166, 167, + 167, 167, 169, 0, 169, 170, 0, 170, 171, 171, + 171, 160, 160, 160, 160, 160, 160, 160, 160, 160, + + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[58] = + { 0, +0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "io/pajek-header.h" +#include "io/parsers/pajek-parser.h" + +#include + +#define YY_EXTRA_TYPE igraph_i_pajek_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +#define YY_FATAL_ERROR(msg) IGRAPH_FATAL("Error in Pajek parser: " # msg) +#define YY_USER_INIT BEGIN(bom) /* we start in the 'bom' start condition */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#endif +#define YY_NO_INPUT 1 +/* Any use of {newline} below must use yy_set_bol(true) in order to mark the character + following a single \r as the first on a new line, and allow the ^ pattern to match. + This pattern must match single newlines only, in order to follow Pajek's "no newline + after *Vertices" convention. */ +/* Anything except non-printable (00-1F), space (20), del (7F), '"' and '*'. */ +/* 'unknown' skips text at the beginning of the file, lines below an unknown *Word + * 'unknown_line' skips the rest of the line after an unknown *Word. */ + +/* Notes: + * - Unquoted '*' characters may only appear at the start of a line-initial word. + * - Both LF and CR LF line endings are allowed. + * - Pajek files do not allow empty lines after *Vertices (empty lines should signify the end of the file), + * therefore we are careful not to skip newlines in the lexer. + */ + +#define INITIAL 0 +#define unknown 1 +#define unknown_line 2 +#define bom 3 +#define vert 4 +#define edge 5 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + yy_size_t yy_n_chars; + yy_size_t yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + yy_size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + if ( yyleng > 0 ) \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = \ + (yytext[yyleng - 1] == '\n'); \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + /* Skip a UTF-8 BOM at the very beginning of the file, if present, then immediately switch to 'unknown'. */ + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; + yy_current_state += YY_AT_BOL(); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 161 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 392 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + yy_size_t yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +{ } + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +{ BEGIN(unknown); yyless(0); yy_set_bol(true); } + YY_BREAK +/* Skip all text until the next *Word at the beginning of a line. */ +case 3: +YY_RULE_SETUP +{ BEGIN(INITIAL); yyless(0); yy_set_bol(true); } + YY_BREAK +case 4: +YY_RULE_SETUP +{ } /* match cannot start with a * in order not to take precedence over ^\* above */ + YY_BREAK +case 5: +/* rule 5 can match eol */ +YY_RULE_SETUP +{ yy_set_bol(true); } + YY_BREAK +case 6: +YY_RULE_SETUP +{ BEGIN(unknown); } + YY_BREAK +case 7: +/* rule 7 can match eol */ +YY_RULE_SETUP +{ yy_set_bol(true); } /* comments */ + YY_BREAK +case 8: +YY_RULE_SETUP +{ } + YY_BREAK +case 9: +YY_RULE_SETUP +{ BEGIN(unknown_line); return NETWORKLINE; } + YY_BREAK +case 10: +YY_RULE_SETUP +{ BEGIN(vert); return VERTICESLINE; } + YY_BREAK +case 11: +YY_RULE_SETUP +{ BEGIN(edge); return ARCSLINE; } + YY_BREAK +case 12: +YY_RULE_SETUP +{ BEGIN(edge); return EDGESLINE; } + YY_BREAK +case 13: +YY_RULE_SETUP +{ BEGIN(INITIAL); return ARCSLISTLINE; } + YY_BREAK +case 14: +YY_RULE_SETUP +{ BEGIN(INITIAL);return EDGESLISTLINE; } + YY_BREAK +case 15: +YY_RULE_SETUP +{ BEGIN(INITIAL); return MATRIXLINE; } + YY_BREAK +case 16: +YY_RULE_SETUP +{ BEGIN(unknown_line); IGRAPH_WARNINGF("Skipping unknown section '%s' on line %d.", yytext, yylineno); } + YY_BREAK +case 17: +/* rule 17 can match eol */ +YY_RULE_SETUP +{ yy_set_bol(true); return NEWLINE; } + YY_BREAK +/* Newlines not allowed in strings. */ +case 18: +YY_RULE_SETUP +{ return QSTR; } + YY_BREAK +case 19: +YY_RULE_SETUP +{ return NUM; } + YY_BREAK + +/* http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm */ +case 20: +YY_RULE_SETUP +{ return VP_X_FACT; } + YY_BREAK +case 21: +YY_RULE_SETUP +{ return VP_Y_FACT; } + YY_BREAK +case 22: +YY_RULE_SETUP +{ return VP_PHI; } + YY_BREAK +case 23: +YY_RULE_SETUP +{ return VP_R; } + YY_BREAK +case 24: +YY_RULE_SETUP +{ return VP_Q; } + YY_BREAK +case 25: +YY_RULE_SETUP +{ return VP_IC; } + YY_BREAK +case 26: +YY_RULE_SETUP +{ return VP_BC; } + YY_BREAK +case 27: +YY_RULE_SETUP +{ return VP_BW; } + YY_BREAK +case 28: +YY_RULE_SETUP +{ return VP_LC; } + YY_BREAK +case 29: +YY_RULE_SETUP +{ return VP_LA; } + YY_BREAK +case 30: +YY_RULE_SETUP +{ return VP_LR; } + YY_BREAK +case 31: +YY_RULE_SETUP +{ return VP_LPHI; } + YY_BREAK +case 32: +YY_RULE_SETUP +{ return VP_FOS; } + YY_BREAK +case 33: +YY_RULE_SETUP +{ return VP_FONT; } + YY_BREAK +/* http://mrvar.fdv.uni-lj.si/pajek/history.htm */ +case 34: +YY_RULE_SETUP +{ return VP_URL; } + YY_BREAK + +/* http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm */ +case 35: +YY_RULE_SETUP +{ return EP_H1; } + YY_BREAK +case 36: +YY_RULE_SETUP +{ return EP_H2; } + YY_BREAK +case 37: +YY_RULE_SETUP +{ return EP_W; } + YY_BREAK +case 38: +YY_RULE_SETUP +{ return EP_C; } + YY_BREAK +case 39: +YY_RULE_SETUP +{ return EP_P; } + YY_BREAK +case 40: +YY_RULE_SETUP +{ return EP_A; } + YY_BREAK +case 41: +YY_RULE_SETUP +{ return EP_S; } + YY_BREAK +case 42: +YY_RULE_SETUP +{ return EP_A1; } + YY_BREAK +case 43: +YY_RULE_SETUP +{ return EP_K1; } + YY_BREAK +case 44: +YY_RULE_SETUP +{ return EP_A2; } + YY_BREAK +case 45: +YY_RULE_SETUP +{ return EP_K2; } + YY_BREAK +case 46: +YY_RULE_SETUP +{ return EP_AP; } + YY_BREAK +case 47: +YY_RULE_SETUP +{ return EP_L; } + YY_BREAK +case 48: +YY_RULE_SETUP +{ return EP_LP; } + YY_BREAK +case 49: +YY_RULE_SETUP +{ return EP_LR; } + YY_BREAK +case 50: +YY_RULE_SETUP +{ return EP_LPHI; } + YY_BREAK +case 51: +YY_RULE_SETUP +{ return EP_LC; } + YY_BREAK +case 52: +YY_RULE_SETUP +{ return EP_LA; } + YY_BREAK +case 53: +YY_RULE_SETUP +{ return EP_FOS; } + YY_BREAK +case 54: +YY_RULE_SETUP +{ return EP_FONT; } + YY_BREAK + +case 55: +YY_RULE_SETUP +{ return ALNUM; } + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(unknown): +case YY_STATE_EOF(unknown_line): +case YY_STATE_EOF(bom): +case YY_STATE_EOF(vert): +case YY_STATE_EOF(edge): +{ if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=true; + return NEWLINE; + } + } + YY_BREAK +case 56: +YY_RULE_SETUP +{ return ERROR; } + YY_BREAK +case 57: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + yy_size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + yy_size_t new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + yy_current_state += YY_AT_BOL(); + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 67); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 161 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 67; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 161 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 160); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = (c == '\n'); + if ( YY_CURRENT_BUFFER_LVALUE->yy_at_bol ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + yy_size_t i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + yy_size_t yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +yy_size_t yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + diff --git a/src/io/parsers/pajek-lexer.h b/src/io/parsers/pajek-lexer.h new file mode 100644 index 0000000..2f63580 --- /dev/null +++ b/src/io/parsers/pajek-lexer.h @@ -0,0 +1,734 @@ +#ifndef igraph_pajek_yyHEADER_H +#define igraph_pajek_yyHEADER_H 1 +#define igraph_pajek_yyIN_HEADER 1 + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define igraph_pajek_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer igraph_pajek_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define igraph_pajek_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer igraph_pajek_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define igraph_pajek_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer igraph_pajek_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define igraph_pajek_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string igraph_pajek_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define igraph_pajek_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes igraph_pajek_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define igraph_pajek_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer igraph_pajek_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define igraph_pajek_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer igraph_pajek_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define igraph_pajek_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state igraph_pajek_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define igraph_pajek_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer igraph_pajek_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define igraph_pajek_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state igraph_pajek_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define igraph_pajek_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state igraph_pajek_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define igraph_pajek_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack igraph_pajek_yyensure_buffer_stack +#endif + +#ifdef yylex +#define igraph_pajek_yylex_ALREADY_DEFINED +#else +#define yylex igraph_pajek_yylex +#endif + +#ifdef yyrestart +#define igraph_pajek_yyrestart_ALREADY_DEFINED +#else +#define yyrestart igraph_pajek_yyrestart +#endif + +#ifdef yylex_init +#define igraph_pajek_yylex_init_ALREADY_DEFINED +#else +#define yylex_init igraph_pajek_yylex_init +#endif + +#ifdef yylex_init_extra +#define igraph_pajek_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra igraph_pajek_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define igraph_pajek_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy igraph_pajek_yylex_destroy +#endif + +#ifdef yyget_debug +#define igraph_pajek_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug igraph_pajek_yyget_debug +#endif + +#ifdef yyset_debug +#define igraph_pajek_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug igraph_pajek_yyset_debug +#endif + +#ifdef yyget_extra +#define igraph_pajek_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra igraph_pajek_yyget_extra +#endif + +#ifdef yyset_extra +#define igraph_pajek_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra igraph_pajek_yyset_extra +#endif + +#ifdef yyget_in +#define igraph_pajek_yyget_in_ALREADY_DEFINED +#else +#define yyget_in igraph_pajek_yyget_in +#endif + +#ifdef yyset_in +#define igraph_pajek_yyset_in_ALREADY_DEFINED +#else +#define yyset_in igraph_pajek_yyset_in +#endif + +#ifdef yyget_out +#define igraph_pajek_yyget_out_ALREADY_DEFINED +#else +#define yyget_out igraph_pajek_yyget_out +#endif + +#ifdef yyset_out +#define igraph_pajek_yyset_out_ALREADY_DEFINED +#else +#define yyset_out igraph_pajek_yyset_out +#endif + +#ifdef yyget_leng +#define igraph_pajek_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng igraph_pajek_yyget_leng +#endif + +#ifdef yyget_text +#define igraph_pajek_yyget_text_ALREADY_DEFINED +#else +#define yyget_text igraph_pajek_yyget_text +#endif + +#ifdef yyget_lineno +#define igraph_pajek_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno igraph_pajek_yyget_lineno +#endif + +#ifdef yyset_lineno +#define igraph_pajek_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno igraph_pajek_yyset_lineno +#endif + +#ifdef yyget_column +#define igraph_pajek_yyget_column_ALREADY_DEFINED +#else +#define yyget_column igraph_pajek_yyget_column +#endif + +#ifdef yyset_column +#define igraph_pajek_yyset_column_ALREADY_DEFINED +#else +#define yyset_column igraph_pajek_yyset_column +#endif + +#ifdef yywrap +#define igraph_pajek_yywrap_ALREADY_DEFINED +#else +#define yywrap igraph_pajek_yywrap +#endif + +#ifdef yyget_lval +#define igraph_pajek_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval igraph_pajek_yyget_lval +#endif + +#ifdef yyset_lval +#define igraph_pajek_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval igraph_pajek_yyset_lval +#endif + +#ifdef yyget_lloc +#define igraph_pajek_yyget_lloc_ALREADY_DEFINED +#else +#define yyget_lloc igraph_pajek_yyget_lloc +#endif + +#ifdef yyset_lloc +#define igraph_pajek_yyset_lloc_ALREADY_DEFINED +#else +#define yyset_lloc igraph_pajek_yyset_lloc +#endif + +#ifdef yyalloc +#define igraph_pajek_yyalloc_ALREADY_DEFINED +#else +#define yyalloc igraph_pajek_yyalloc +#endif + +#ifdef yyrealloc +#define igraph_pajek_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc igraph_pajek_yyrealloc +#endif + +#ifdef yyfree +#define igraph_pajek_yyfree_ALREADY_DEFINED +#else +#define yyfree igraph_pajek_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +typedef uint64_t flex_uint64_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + yy_size_t yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +/* Begin user sect3 */ + +#define igraph_pajek_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define yytext_ptr yytext_r + +#ifdef YY_HEADER_EXPORT_START_CONDITIONS +#define INITIAL 0 +#define unknown 1 +#define unknown_line 2 +#define bom 3 +#define vert 4 +#define edge 5 + +#endif + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + yy_size_t yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + + YYLTYPE *yyget_lloc ( yyscan_t yyscanner ); + + void yyset_lloc ( YYLTYPE * yylloc_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +#ifndef igraph_pajek_yy_create_buffer_ALREADY_DEFINED +#undef yy_create_buffer +#endif +#ifndef igraph_pajek_yy_delete_buffer_ALREADY_DEFINED +#undef yy_delete_buffer +#endif +#ifndef igraph_pajek_yy_scan_buffer_ALREADY_DEFINED +#undef yy_scan_buffer +#endif +#ifndef igraph_pajek_yy_scan_string_ALREADY_DEFINED +#undef yy_scan_string +#endif +#ifndef igraph_pajek_yy_scan_bytes_ALREADY_DEFINED +#undef yy_scan_bytes +#endif +#ifndef igraph_pajek_yy_init_buffer_ALREADY_DEFINED +#undef yy_init_buffer +#endif +#ifndef igraph_pajek_yy_flush_buffer_ALREADY_DEFINED +#undef yy_flush_buffer +#endif +#ifndef igraph_pajek_yy_load_buffer_state_ALREADY_DEFINED +#undef yy_load_buffer_state +#endif +#ifndef igraph_pajek_yy_switch_to_buffer_ALREADY_DEFINED +#undef yy_switch_to_buffer +#endif +#ifndef igraph_pajek_yypush_buffer_state_ALREADY_DEFINED +#undef yypush_buffer_state +#endif +#ifndef igraph_pajek_yypop_buffer_state_ALREADY_DEFINED +#undef yypop_buffer_state +#endif +#ifndef igraph_pajek_yyensure_buffer_stack_ALREADY_DEFINED +#undef yyensure_buffer_stack +#endif +#ifndef igraph_pajek_yylex_ALREADY_DEFINED +#undef yylex +#endif +#ifndef igraph_pajek_yyrestart_ALREADY_DEFINED +#undef yyrestart +#endif +#ifndef igraph_pajek_yylex_init_ALREADY_DEFINED +#undef yylex_init +#endif +#ifndef igraph_pajek_yylex_init_extra_ALREADY_DEFINED +#undef yylex_init_extra +#endif +#ifndef igraph_pajek_yylex_destroy_ALREADY_DEFINED +#undef yylex_destroy +#endif +#ifndef igraph_pajek_yyget_debug_ALREADY_DEFINED +#undef yyget_debug +#endif +#ifndef igraph_pajek_yyset_debug_ALREADY_DEFINED +#undef yyset_debug +#endif +#ifndef igraph_pajek_yyget_extra_ALREADY_DEFINED +#undef yyget_extra +#endif +#ifndef igraph_pajek_yyset_extra_ALREADY_DEFINED +#undef yyset_extra +#endif +#ifndef igraph_pajek_yyget_in_ALREADY_DEFINED +#undef yyget_in +#endif +#ifndef igraph_pajek_yyset_in_ALREADY_DEFINED +#undef yyset_in +#endif +#ifndef igraph_pajek_yyget_out_ALREADY_DEFINED +#undef yyget_out +#endif +#ifndef igraph_pajek_yyset_out_ALREADY_DEFINED +#undef yyset_out +#endif +#ifndef igraph_pajek_yyget_leng_ALREADY_DEFINED +#undef yyget_leng +#endif +#ifndef igraph_pajek_yyget_text_ALREADY_DEFINED +#undef yyget_text +#endif +#ifndef igraph_pajek_yyget_lineno_ALREADY_DEFINED +#undef yyget_lineno +#endif +#ifndef igraph_pajek_yyset_lineno_ALREADY_DEFINED +#undef yyset_lineno +#endif +#ifndef igraph_pajek_yyget_column_ALREADY_DEFINED +#undef yyget_column +#endif +#ifndef igraph_pajek_yyset_column_ALREADY_DEFINED +#undef yyset_column +#endif +#ifndef igraph_pajek_yywrap_ALREADY_DEFINED +#undef yywrap +#endif +#ifndef igraph_pajek_yyget_lval_ALREADY_DEFINED +#undef yyget_lval +#endif +#ifndef igraph_pajek_yyset_lval_ALREADY_DEFINED +#undef yyset_lval +#endif +#ifndef igraph_pajek_yyget_lloc_ALREADY_DEFINED +#undef yyget_lloc +#endif +#ifndef igraph_pajek_yyset_lloc_ALREADY_DEFINED +#undef yyset_lloc +#endif +#ifndef igraph_pajek_yyalloc_ALREADY_DEFINED +#undef yyalloc +#endif +#ifndef igraph_pajek_yyrealloc_ALREADY_DEFINED +#undef yyrealloc +#endif +#ifndef igraph_pajek_yyfree_ALREADY_DEFINED +#undef yyfree +#endif +#ifndef igraph_pajek_yytext_ALREADY_DEFINED +#undef yytext +#endif +#ifndef igraph_pajek_yyleng_ALREADY_DEFINED +#undef yyleng +#endif +#ifndef igraph_pajek_yyin_ALREADY_DEFINED +#undef yyin +#endif +#ifndef igraph_pajek_yyout_ALREADY_DEFINED +#undef yyout +#endif +#ifndef igraph_pajek_yy_flex_debug_ALREADY_DEFINED +#undef yy_flex_debug +#endif +#ifndef igraph_pajek_yylineno_ALREADY_DEFINED +#undef yylineno +#endif +#ifndef igraph_pajek_yytables_fload_ALREADY_DEFINED +#undef yytables_fload +#endif +#ifndef igraph_pajek_yytables_destroy_ALREADY_DEFINED +#undef yytables_destroy +#endif +#ifndef igraph_pajek_yyTABLES_NAME_ALREADY_DEFINED +#undef yyTABLES_NAME +#endif + +#undef igraph_pajek_yyIN_HEADER +#endif /* igraph_pajek_yyHEADER_H */ diff --git a/src/io/parsers/pajek-parser.c b/src/io/parsers/pajek-parser.c new file mode 100644 index 0000000..438a115 --- /dev/null +++ b/src/io/parsers/pajek-parser.c @@ -0,0 +1,2919 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse igraph_pajek_yyparse +#define yylex igraph_pajek_yylex +#define yyerror igraph_pajek_yyerror +#define yylval igraph_pajek_yylval +#define yychar igraph_pajek_yychar +#define yydebug igraph_pajek_yydebug +#define yynerrs igraph_pajek_yynerrs +#define yylloc igraph_pajek_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + NEWLINE = 258, + NUM = 259, + ALNUM = 260, + QSTR = 261, + NETWORKLINE = 262, + VERTICESLINE = 263, + ARCSLINE = 264, + EDGESLINE = 265, + ARCSLISTLINE = 266, + EDGESLISTLINE = 267, + MATRIXLINE = 268, + ERROR = 269, + VP_X_FACT = 270, + VP_Y_FACT = 271, + VP_PHI = 272, + VP_R = 273, + VP_Q = 274, + VP_IC = 275, + VP_BC = 276, + VP_BW = 277, + VP_LC = 278, + VP_LA = 279, + VP_LR = 280, + VP_LPHI = 281, + VP_FOS = 282, + VP_FONT = 283, + VP_URL = 284, + EP_H1 = 285, + EP_H2 = 286, + EP_W = 287, + EP_C = 288, + EP_P = 289, + EP_A = 290, + EP_S = 291, + EP_A1 = 292, + EP_K1 = 293, + EP_A2 = 294, + EP_K2 = 295, + EP_AP = 296, + EP_L = 297, + EP_LP = 298, + EP_LR = 299, + EP_LPHI = 300, + EP_LC = 301, + EP_LA = 302, + EP_FOS = 303, + EP_FONT = 304 + }; +#endif +/* Tokens. */ +#define END 0 +#define NEWLINE 258 +#define NUM 259 +#define ALNUM 260 +#define QSTR 261 +#define NETWORKLINE 262 +#define VERTICESLINE 263 +#define ARCSLINE 264 +#define EDGESLINE 265 +#define ARCSLISTLINE 266 +#define EDGESLISTLINE 267 +#define MATRIXLINE 268 +#define ERROR 269 +#define VP_X_FACT 270 +#define VP_Y_FACT 271 +#define VP_PHI 272 +#define VP_R 273 +#define VP_Q 274 +#define VP_IC 275 +#define VP_BC 276 +#define VP_BW 277 +#define VP_LC 278 +#define VP_LA 279 +#define VP_LR 280 +#define VP_LPHI 281 +#define VP_FOS 282 +#define VP_FONT 283 +#define VP_URL 284 +#define EP_H1 285 +#define EP_H2 286 +#define EP_W 287 +#define EP_C 288 +#define EP_P 289 +#define EP_A 290 +#define EP_S 291 +#define EP_A1 292 +#define EP_K1 293 +#define EP_A2 294 +#define EP_K2 295 +#define EP_AP 296 +#define EP_L 297 +#define EP_LP 298 +#define EP_LR 299 +#define EP_LPHI 300 +#define EP_LC 301 +#define EP_LA 302 +#define EP_FOS 303 +#define EP_FONT 304 + + + + +/* Copy the first part of user declarations. */ + + + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_types.h" + +#include "io/pajek-header.h" +#include "io/parsers/pajek-parser.h" /* it must come first because of YYSTYPE */ +#include "io/parsers/pajek-lexer.h" +#include "io/parse_utils.h" +#include "internal/hacks.h" /* strdup */ + +#include +#include +#include + +int igraph_pajek_yyerror(YYLTYPE* locp, + igraph_i_pajek_parsedata_t *context, + const char *s); + +static igraph_error_t add_string_vertex_attribute(const char *name, + const char *value, + size_t len, + igraph_i_pajek_parsedata_t *context); +static igraph_error_t add_string_edge_attribute(const char *name, + const char *value, + size_t len, + igraph_i_pajek_parsedata_t *context); +static igraph_error_t add_numeric_vertex_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context); +static igraph_error_t add_numeric_edge_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context); +static igraph_error_t add_numeric_attribute(igraph_trie_t *names, + igraph_attribute_record_list_t *attrs, + igraph_int_t count, + const char *attrname, + igraph_real_t default_value, + igraph_int_t vid, + igraph_real_t number); +static igraph_error_t add_string_attribute(igraph_trie_t *names, + igraph_attribute_record_list_t *attrs, + igraph_int_t count, + const char *attrname, + const char *default_value, + igraph_int_t vid, + const char *str, + igraph_int_t str_len); + +static igraph_error_t add_bipartite_type(igraph_i_pajek_parsedata_t *context); +static igraph_error_t check_bipartite(igraph_i_pajek_parsedata_t *context); + +static igraph_error_t make_dynstr(const char *src, size_t len, char **res); +static igraph_bool_t is_standard_vattr(const char *attrname); +static igraph_bool_t is_standard_eattr(const char *attrname); +static igraph_error_t deconflict_attrname(char **attrname); +static igraph_real_t get_default_value_for_numeric_vattr(const char *attrname); +static igraph_real_t get_default_value_for_numeric_eattr(const char *attrname); +static const char* get_default_value_for_string_vattr(const char *attrname); +static const char* get_default_value_for_string_eattr(const char *attrname); + +#define scanner context->scanner + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t intnum; + igraph_real_t realnum; + struct { + char *str; + size_t len; + } string; + char *dynstr; +} +/* Line 193 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ + + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 4 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 215 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 50 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 52 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 115 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 178 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 304 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint16 yyprhs[] = +{ + 0, 0, 3, 8, 9, 12, 13, 15, 19, 22, + 26, 27, 30, 33, 34, 42, 44, 46, 47, 50, + 54, 55, 57, 58, 61, 63, 66, 69, 72, 75, + 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, + 108, 111, 112, 115, 118, 121, 124, 127, 131, 136, + 137, 140, 141, 148, 152, 157, 158, 161, 162, 169, + 170, 172, 173, 176, 178, 181, 184, 187, 190, 193, + 196, 199, 202, 205, 208, 211, 214, 217, 220, 223, + 226, 229, 232, 235, 238, 241, 245, 246, 249, 253, + 254, 257, 259, 261, 265, 266, 269, 273, 274, 277, + 279, 281, 285, 287, 288, 291, 294, 295, 298, 300, + 302, 304, 306, 308, 310, 312 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 51, 0, -1, 53, 54, 66, 52, -1, -1, 3, + 52, -1, -1, 7, -1, 55, 3, 56, -1, 8, + 97, -1, 8, 97, 97, -1, -1, 56, 57, -1, + 59, 3, -1, -1, 59, 58, 60, 61, 62, 63, + 3, -1, 97, -1, 101, -1, -1, 98, 98, -1, + 98, 98, 98, -1, -1, 101, -1, -1, 63, 64, + -1, 65, -1, 15, 98, -1, 16, 98, -1, 25, + 98, -1, 26, 98, -1, 22, 98, -1, 27, 98, + -1, 17, 98, -1, 18, 98, -1, 19, 98, -1, + 24, 98, -1, 28, 100, -1, 29, 100, -1, 20, + 100, -1, 21, 100, -1, 23, 100, -1, 99, 100, + -1, -1, 66, 67, -1, 66, 71, -1, 66, 79, + -1, 66, 85, -1, 66, 91, -1, 9, 3, 68, + -1, 9, 98, 3, 68, -1, -1, 68, 69, -1, + -1, 59, 59, 70, 75, 76, 3, -1, 10, 3, + 72, -1, 10, 98, 3, 72, -1, -1, 72, 73, + -1, -1, 59, 59, 74, 75, 76, 3, -1, -1, + 98, -1, -1, 76, 77, -1, 78, -1, 36, 98, + -1, 32, 98, -1, 30, 98, -1, 31, 98, -1, + 37, 98, -1, 39, 98, -1, 38, 98, -1, 40, + 98, -1, 41, 98, -1, 43, 98, -1, 44, 98, + -1, 45, 98, -1, 47, 98, -1, 48, 98, -1, + 35, 100, -1, 34, 100, -1, 42, 100, -1, 46, + 100, -1, 33, 100, -1, 49, 100, -1, 99, 100, + -1, 11, 3, 80, -1, -1, 80, 81, -1, 83, + 82, 3, -1, -1, 82, 84, -1, 59, -1, 59, + -1, 12, 3, 86, -1, -1, 86, 87, -1, 89, + 88, 3, -1, -1, 88, 90, -1, 59, -1, 59, + -1, 92, 3, 93, -1, 13, -1, -1, 93, 94, + -1, 95, 3, -1, -1, 96, 95, -1, 98, -1, + 4, -1, 4, -1, 101, -1, 101, -1, 5, -1, + 4, -1, 6, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 194, 194, 205, 205, 207, 207, 209, 211, 222, + 241, 241, 243, 244, 244, 254, 273, 277, 278, 282, + 288, 288, 292, 292, 295, 296, 299, 302, 305, 308, + 311, 314, 317, 320, 323, 328, 331, 334, 337, 340, + 343, 358, 358, 358, 358, 358, 358, 360, 361, 363, + 363, 365, 365, 370, 371, 373, 373, 375, 375, 380, + 380, 384, 384, 387, 388, 391, 394, 397, 400, 403, + 406, 409, 412, 415, 418, 421, 424, 427, 432, 435, + 438, 441, 444, 447, 450, 465, 467, 467, 469, 471, + 471, 473, 475, 480, 482, 482, 484, 486, 486, 488, + 490, 497, 499, 504, 504, 506, 508, 508, 510, 530, + 538, 546, 550, 552, 554, 556 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "$undefined", "\"end of line\"", + "\"number\"", "\"word\"", "\"quoted string\"", "\"*Network line\"", + "\"*Vertices line\"", "\"*Arcs line\"", "\"*Edges line\"", + "\"*Arcslist line\"", "\"*Edgeslist line\"", "\"*Matrix line\"", "ERROR", + "VP_X_FACT", "VP_Y_FACT", "VP_PHI", "VP_R", "VP_Q", "VP_IC", "VP_BC", + "VP_BW", "VP_LC", "VP_LA", "VP_LR", "VP_LPHI", "VP_FOS", "VP_FONT", + "VP_URL", "EP_H1", "EP_H2", "EP_W", "EP_C", "EP_P", "EP_A", "EP_S", + "EP_A1", "EP_K1", "EP_A2", "EP_K2", "EP_AP", "EP_L", "EP_LP", "EP_LR", + "EP_LPHI", "EP_LC", "EP_LA", "EP_FOS", "EP_FONT", "$accept", "input", + "final_newlines", "nethead", "vertices", "verticeshead", "vertdefs", + "vertexline", "@1", "vertex", "vertexid", "vertexcoords", "shape", + "vertparams", "vertparam", "vpword", "edgeblock", "arcs", "arcsdefs", + "arcsline", "@2", "edges", "edgesdefs", "edgesline", "@3", "weight", + "edgeparams", "edgeparam", "epword", "arcslist", "arcslistlines", + "arclistline", "arctolist", "arclistfrom", "arclistto", "edgeslist", + "edgelistlines", "edgelistline", "edgetolist", "edgelistfrom", + "edgelistto", "adjmatrix", "matrixline", "adjmatrixlines", + "adjmatrixline", "adjmatrixnumbers", "adjmatrixentry", "integer", + "number", "parname", "parstrval", "word", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 50, 51, 52, 52, 53, 53, 54, 55, 55, + 56, 56, 57, 58, 57, 59, 60, 61, 61, 61, + 62, 62, 63, 63, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, + 65, 66, 66, 66, 66, 66, 66, 67, 67, 68, + 68, 70, 69, 71, 71, 72, 72, 74, 73, 75, + 75, 76, 76, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 78, 78, + 78, 78, 78, 78, 78, 79, 80, 80, 81, 82, + 82, 83, 84, 85, 86, 86, 87, 88, 88, 89, + 90, 91, 92, 93, 93, 94, 95, 95, 96, 97, + 98, 99, 100, 101, 101, 101 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 4, 0, 2, 0, 1, 3, 2, 3, + 0, 2, 2, 0, 7, 1, 1, 0, 2, 3, + 0, 1, 0, 2, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 0, 2, 2, 2, 2, 2, 3, 4, 0, + 2, 0, 6, 3, 4, 0, 2, 0, 6, 0, + 1, 0, 2, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 0, 2, 3, 0, + 2, 1, 1, 3, 0, 2, 3, 0, 2, 1, + 1, 3, 1, 0, 2, 2, 0, 2, 1, 1, + 1, 1, 1, 1, 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 5, 6, 0, 0, 1, 0, 41, 0, 109, 8, + 3, 10, 9, 3, 0, 0, 0, 0, 102, 2, + 42, 43, 44, 45, 46, 0, 7, 4, 49, 110, + 0, 55, 0, 86, 94, 103, 11, 13, 15, 47, + 49, 53, 55, 85, 93, 101, 12, 0, 0, 50, + 48, 0, 56, 54, 91, 87, 89, 99, 95, 97, + 104, 0, 106, 108, 114, 113, 115, 17, 16, 51, + 57, 0, 0, 105, 107, 20, 0, 59, 59, 88, + 92, 90, 96, 100, 98, 22, 21, 18, 61, 60, + 61, 0, 19, 0, 0, 14, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 23, 24, 0, 111, 52, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 62, 63, 0, 58, + 25, 26, 31, 32, 33, 37, 112, 38, 29, 39, + 34, 27, 28, 30, 35, 36, 40, 66, 67, 65, + 82, 79, 78, 64, 68, 70, 69, 71, 72, 80, + 73, 74, 75, 81, 76, 77, 83, 84 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 2, 19, 3, 6, 7, 26, 36, 47, 48, + 67, 75, 85, 91, 111, 112, 10, 20, 39, 49, + 77, 21, 41, 52, 78, 88, 93, 136, 137, 22, + 43, 55, 71, 56, 81, 23, 44, 58, 72, 59, + 84, 24, 25, 45, 60, 61, 62, 38, 63, 138, + 145, 146 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -95 +static const yytype_int16 yypact[] = +{ + 2, -95, 20, 6, -95, 18, -95, 26, -95, 18, + 47, -95, -95, 29, 1, 14, 34, 39, -95, -95, + -95, -95, -95, -95, -95, 40, 18, -95, -95, -95, + 42, -95, 44, -95, -95, -95, -95, 51, -95, 18, + -95, 18, -95, 18, 18, 57, -95, 7, 18, -95, + 18, 18, -95, 18, -95, -95, -95, -95, -95, -95, + -95, 52, 57, -95, -95, -95, -95, 57, -95, -95, + -95, 32, 36, -95, -95, 7, 57, 57, 57, -95, + -95, -95, -95, -95, -95, -95, -95, 57, -95, -95, + -95, 186, -95, 92, 139, -95, 57, 57, 57, 57, + 57, 7, 7, 57, 7, 57, 57, 57, 57, 7, + 7, -95, -95, 7, -95, -95, 57, 57, 57, 7, + 7, 7, 57, 57, 57, 57, 57, 57, 7, 57, + 57, 57, 7, 57, 57, 7, -95, -95, 7, -95, + -95, -95, -95, -95, -95, -95, -95, -95, -95, -95, + -95, -95, -95, -95, -95, -95, -95, -95, -95, -95, + -95, -95, -95, -95, -95, -95, -95, -95, -95, -95, + -95, -95, -95, -95, -95, -95, -95, -95 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -95, -95, 53, -95, -95, -95, -95, -95, -95, -20, + -95, -95, -95, -95, -95, -95, -95, -95, 25, -95, + -95, -95, 27, -95, -95, -11, -22, -95, -95, -95, + -95, -95, -95, -95, -95, -95, -95, -95, -95, -95, + -95, -95, -95, -95, -95, 8, -95, -2, -14, -19, + -94, -45 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 30, 32, 68, 9, 28, 29, 37, 12, 147, 1, + 149, 64, 65, 66, 5, 154, 155, 31, 29, 156, + 4, 51, 8, 54, 57, 160, 161, 162, 69, 11, + 86, 70, 13, 51, 169, 79, 8, 33, 173, 82, + 8, 176, 34, 35, 177, 40, 114, 42, 114, 114, + 13, 80, 83, 76, 46, 73, 14, 15, 16, 17, + 18, 29, 87, 89, 89, 50, 27, 90, 94, 53, + 74, 0, 113, 92, 0, 0, 0, 0, 0, 0, + 0, 0, 140, 141, 142, 143, 144, 0, 0, 148, + 0, 150, 151, 152, 153, 115, 64, 65, 66, 0, + 0, 0, 157, 158, 159, 0, 0, 0, 163, 164, + 165, 166, 167, 168, 0, 170, 171, 172, 0, 174, + 175, 0, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, + 134, 135, 139, 64, 65, 66, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 95, + 64, 65, 66, 0, 0, 0, 0, 0, 0, 0, + 0, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110 +}; + +static const yytype_int16 yycheck[] = +{ + 14, 15, 47, 5, 3, 4, 26, 9, 102, 7, + 104, 4, 5, 6, 8, 109, 110, 3, 4, 113, + 0, 41, 4, 43, 44, 119, 120, 121, 48, 3, + 75, 51, 3, 53, 128, 3, 4, 3, 132, 3, + 4, 135, 3, 3, 138, 3, 91, 3, 93, 94, + 3, 71, 72, 67, 3, 3, 9, 10, 11, 12, + 13, 4, 76, 77, 78, 40, 13, 78, 90, 42, + 62, -1, 91, 87, -1, -1, -1, -1, -1, -1, + -1, -1, 96, 97, 98, 99, 100, -1, -1, 103, + -1, 105, 106, 107, 108, 3, 4, 5, 6, -1, + -1, -1, 116, 117, 118, -1, -1, -1, 122, 123, + 124, 125, 126, 127, -1, 129, 130, 131, -1, 133, + 134, -1, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 3, 4, 5, 6, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 3, + 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, + -1, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 7, 51, 53, 0, 8, 54, 55, 4, 97, + 66, 3, 97, 3, 9, 10, 11, 12, 13, 52, + 67, 71, 79, 85, 91, 92, 56, 52, 3, 4, + 98, 3, 98, 3, 3, 3, 57, 59, 97, 68, + 3, 72, 3, 80, 86, 93, 3, 58, 59, 69, + 68, 59, 73, 72, 59, 81, 83, 59, 87, 89, + 94, 95, 96, 98, 4, 5, 6, 60, 101, 59, + 59, 82, 88, 3, 95, 61, 98, 70, 74, 3, + 59, 84, 3, 59, 90, 62, 101, 98, 75, 98, + 75, 63, 98, 76, 76, 3, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 64, 65, 99, 101, 3, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 77, 78, 99, 3, + 98, 98, 98, 98, 98, 100, 101, 100, 98, 100, + 98, 98, 98, 98, 100, 100, 100, 98, 98, 98, + 100, 100, 100, 98, 98, 98, 98, 98, 98, 100, + 98, 98, 98, 100, 98, 98, 100, 100 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc, scanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_pajek_parsedata_t* context) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_pajek_parsedata_t* context; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (context); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, igraph_i_pajek_parsedata_t* context) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, context) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; + igraph_i_pajek_parsedata_t* context; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, context); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, igraph_i_pajek_parsedata_t* context) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, context) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + igraph_i_pajek_parsedata_t* context; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , context); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, context); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, igraph_i_pajek_parsedata_t* context) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, context) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + igraph_i_pajek_parsedata_t* context; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (context); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 99: /* "parname" */ + + { free((yyvaluep->dynstr)); }; + + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (igraph_i_pajek_parsedata_t* context); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (igraph_i_pajek_parsedata_t* context) +#else +int +yyparse (context) + igraph_i_pajek_parsedata_t* context; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + + { + if (context->vcount2 > 0) { check_bipartite(context); } + if (! context->eof) { + /* In Pajek files, an empty line after *Vertices signifies the end of the network data. + * If there is more data after one or more empty lines, we warn the user, as this + * may indicate file corruption, for example a stray empty lines before *Edges. */ + IGRAPH_WARNINGF("Empty line encountered, ignoring rest of file after line %d.", (yylsp[(4) - (4)]).first_line); + } + YYACCEPT; /* stop parsing even if there is more data in the file. */ + ;} + break; + + case 8: + + { + context->vcount=(yyvsp[(2) - (2)].intnum); + context->vcount2=0; + if (context->vcount < 0) { + IGRAPH_YY_ERRORF("Invalid vertex count in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount); + } + if (context->vcount > IGRAPH_PAJEK_MAX_VERTEX_COUNT) { + IGRAPH_YY_ERRORF("Vertex count too large in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount); + } + IGRAPH_YY_CHECK(igraph_bitset_resize(context->seen, context->vcount)); + ;} + break; + + case 9: + + { + context->vcount=(yyvsp[(2) - (3)].intnum); + context->vcount2=(yyvsp[(3) - (3)].intnum); + if (context->vcount < 0) { + IGRAPH_YY_ERRORF("Invalid vertex count in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount); + } + if (context->vcount > IGRAPH_PAJEK_MAX_VERTEX_COUNT) { + IGRAPH_YY_ERRORF("Vertex count too large in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount); + } + if (context->vcount2 < 0) { + IGRAPH_YY_ERRORF("Invalid two-mode vertex count in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount2); + } + if (context->vcount2 > IGRAPH_PAJEK_MAX_VERTEX_COUNT) { + IGRAPH_YY_ERRORF("2-mode vertex count too large in Pajek file (%" IGRAPH_PRId ").", IGRAPH_EINVAL, context->vcount2); + } + IGRAPH_YY_CHECK(add_bipartite_type(context)); + IGRAPH_YY_CHECK(igraph_bitset_resize(context->seen, context->vcount)); +;} + break; + + case 13: + + { context->actvertex=(yyvsp[(1) - (1)].intnum); ;} + break; + + case 14: + + { + igraph_int_t v = (yyvsp[(1) - (7)].intnum)-1; /* zero-based vertex ID */ + if (IGRAPH_BIT_TEST(*context->seen, v)) { + IGRAPH_WARNINGF("Vertex ID %" IGRAPH_PRId " appears twice in Pajek file. Duplicate attributes will be overwritten.", v+1); + } else { + IGRAPH_BIT_SET(*context->seen, v); + } + ;} + break; + + case 15: + + { + igraph_int_t v = (yyvsp[(1) - (1)].intnum); + /* Per feedback from Pajek's authors, negative signs should be ignored for vertex IDs. + * See https://nascol.discourse.group/t/pajek-arcslist-edgelist-format/44/2 + * This applies to all of *Edges, *Arcs, *Edgeslist, *Arcslist and *Vertices section. + * IGRAPH_INTEGER_MIN cannot be negated on typical platforms so we keep it as-is. + */ + if (v < 0 && v > IGRAPH_INTEGER_MIN) { + v = -v; + } + if (v < 1 || v > context->vcount) { + IGRAPH_YY_ERRORF( + "Invalid vertex ID (%" IGRAPH_PRId ") in Pajek file. " + "The number of vertices is %" IGRAPH_PRId ".", + IGRAPH_EINVAL, v, context->vcount); + } + (yyval.intnum) = v; +;} + break; + + case 16: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("name", (yyvsp[(1) - (1)].string).str, (yyvsp[(1) - (1)].string).len, context)); +;} + break; + + case 18: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("x", (yyvsp[(1) - (2)].realnum), context)); + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("y", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 19: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("x", (yyvsp[(1) - (3)].realnum), context)); + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("y", (yyvsp[(2) - (3)].realnum), context)); + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("z", (yyvsp[(3) - (3)].realnum), context)); + ;} + break; + + case 21: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("shape", (yyvsp[(1) - (1)].string).str, (yyvsp[(1) - (1)].string).len, context)); +;} + break; + + case 25: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("xfact", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 26: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("yfact", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 27: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("labeldist", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 28: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("labeldegree2", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 29: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("framewidth", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 30: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("fontsize", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 31: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("rotation", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 32: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("radius", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 33: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("diamondratio", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 34: + + { + IGRAPH_YY_CHECK(add_numeric_vertex_attribute("labeldegree", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 35: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("font", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 36: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("url", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 37: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("color", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 38: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("framecolor", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 39: + + { + IGRAPH_YY_CHECK(add_string_vertex_attribute("labelcolor", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 40: + + { + IGRAPH_FINALLY(igraph_free, (yyvsp[(1) - (2)].dynstr)); + if (is_standard_vattr((yyvsp[(1) - (2)].dynstr))) { + IGRAPH_YY_CHECK(deconflict_attrname(&(yyvsp[(1) - (2)].dynstr))); + /* update address on finally stack */ + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_free, (yyvsp[(1) - (2)].dynstr)); + } + IGRAPH_YY_CHECK(add_string_vertex_attribute( + (yyvsp[(1) - (2)].dynstr), (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + IGRAPH_FREE((yyvsp[(1) - (2)].dynstr)); + IGRAPH_FINALLY_CLEAN(1); + ;} + break; + + case 47: + + { context->directed=true; ;} + break; + + case 48: + + { context->directed=true; ;} + break; + + case 51: + + { context->actedge++; ;} + break; + + case 52: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (6)].intnum)-1)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(2) - (6)].intnum)-1)); ;} + break; + + case 53: + + { context->directed=0; ;} + break; + + case 54: + + { context->directed=0; ;} + break; + + case 57: + + { context->actedge++; ;} + break; + + case 58: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (6)].intnum)-1)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(2) - (6)].intnum)-1)); ;} + break; + + case 60: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("weight", (yyvsp[(1) - (1)].realnum), context)); +;} + break; + + case 64: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("arrowsize", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 65: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("edgewidth", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 66: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("hook1", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 67: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("hook2", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 68: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("angle1", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 69: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("angle2", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 70: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("velocity1", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 71: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("velocity2", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 72: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("arrowpos", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 73: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("labelpos", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 74: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("labelangle", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 75: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("labelangle2", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 76: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("labeldegree", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 77: + + { + IGRAPH_YY_CHECK(add_numeric_edge_attribute("fontsize", (yyvsp[(2) - (2)].realnum), context)); + ;} + break; + + case 78: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("arrowtype", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 79: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("linepattern", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 80: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("label", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 81: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("labelcolor", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 82: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("color", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 83: + + { + IGRAPH_YY_CHECK(add_string_edge_attribute("font", (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + ;} + break; + + case 84: + + { + IGRAPH_FINALLY(igraph_free, (yyvsp[(1) - (2)].dynstr)); + if (is_standard_eattr((yyvsp[(1) - (2)].dynstr))) { + IGRAPH_YY_CHECK(deconflict_attrname(&(yyvsp[(1) - (2)].dynstr))); + /* update address on finally stack */ + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_free, (yyvsp[(1) - (2)].dynstr)); + } + IGRAPH_YY_CHECK(add_string_edge_attribute( + (yyvsp[(1) - (2)].dynstr), (yyvsp[(2) - (2)].string).str, (yyvsp[(2) - (2)].string).len, context)); + IGRAPH_FREE((yyvsp[(1) - (2)].dynstr)); + IGRAPH_FINALLY_CLEAN(1); + ;} + break; + + case 85: + + { context->directed=true; ;} + break; + + case 91: + + { context->actfrom=(yyvsp[(1) - (1)].intnum)-1; ;} + break; + + case 92: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actfrom)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (1)].intnum)-1)); +;} + break; + + case 93: + + { context->directed=0; ;} + break; + + case 99: + + { context->actfrom=(yyvsp[(1) - (1)].intnum)-1; ;} + break; + + case 100: + + { + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actfrom)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, (yyvsp[(1) - (1)].intnum)-1)); +;} + break; + + case 102: + + { context->actfrom=0; + context->actto=0; + context->directed=(context->vcount2==0); + ;} + break; + + case 105: + + { context->actfrom++; context->actto=0; ;} + break; + + case 108: + + { + if ((yyvsp[(1) - (1)].realnum) != 0) { + if (context->vcount2==0) { + context->actedge++; + IGRAPH_YY_CHECK(add_numeric_edge_attribute("weight", (yyvsp[(1) - (1)].realnum), context)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actfrom)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actto)); + } else if (context->vcount2 + context->actto < context->vcount) { + context->actedge++; + IGRAPH_YY_CHECK(add_numeric_edge_attribute("weight", (yyvsp[(1) - (1)].realnum), context)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, context->actfrom)); + IGRAPH_YY_CHECK(igraph_vector_int_push_back(context->vector, + context->vcount2+context->actto)); + } + } + context->actto++; +;} + break; + + case 109: + + { + igraph_int_t val; + IGRAPH_YY_CHECK(igraph_i_parse_integer(igraph_pajek_yyget_text(scanner), + igraph_pajek_yyget_leng(scanner), + &val)); + (yyval.intnum)=val; +;} + break; + + case 110: + + { + igraph_real_t val; + IGRAPH_YY_CHECK(igraph_i_parse_real(igraph_pajek_yyget_text(scanner), + igraph_pajek_yyget_leng(scanner), + &val)); + (yyval.realnum)=val; +;} + break; + + case 111: + + { + IGRAPH_YY_CHECK(make_dynstr((yyvsp[(1) - (1)].string).str, (yyvsp[(1) - (1)].string).len, &(yyval.dynstr))); +;} + break; + + case 112: + + { (yyval.string)=(yyvsp[(1) - (1)].string); ;} + break; + + case 113: + + { (yyval.string).str=igraph_pajek_yyget_text(scanner); + (yyval.string).len=igraph_pajek_yyget_leng(scanner); ;} + break; + + case 114: + + { (yyval.string).str=igraph_pajek_yyget_text(scanner); + (yyval.string).len=igraph_pajek_yyget_leng(scanner); ;} + break; + + case 115: + + { (yyval.string).str=igraph_pajek_yyget_text(scanner)+1; + (yyval.string).len=igraph_pajek_yyget_leng(scanner)-2; ;} + break; + + +/* Line 1267 of yacc.c. */ + + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, context, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, context, yymsg); + } + else + { + yyerror (&yylloc, context, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, context); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, context, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, context); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, context); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + + + +int igraph_pajek_yyerror(YYLTYPE* locp, + igraph_i_pajek_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in Pajek file, line %i (%s)", + locp->first_line, s); + return 0; +} + +/* TODO: NA's */ + +static igraph_error_t add_numeric_attribute(igraph_trie_t *names, + igraph_attribute_record_list_t *attrs, + igraph_int_t count, + const char *attrname, + igraph_real_t default_value, + igraph_int_t elem_id, + igraph_real_t number) { + igraph_int_t attrsize = igraph_trie_size(names); + igraph_int_t id; + igraph_vector_t *na; + igraph_attribute_record_t *prec; + + IGRAPH_CHECK(igraph_trie_get(names, attrname, &id)); + if (id == attrsize) { + igraph_attribute_record_t rec; + + /* add a new attribute */ + IGRAPH_CHECK(igraph_attribute_record_init(&rec, attrname, IGRAPH_ATTRIBUTE_NUMERIC)); + IGRAPH_FINALLY(igraph_attribute_record_destroy, &rec); + + IGRAPH_CHECK(igraph_attribute_record_set_default_numeric(&rec, default_value)); + + IGRAPH_CHECK(igraph_attribute_record_resize(&rec, count)); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(attrs, &rec)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of rec transferred to attrs */ + } + + prec = igraph_attribute_record_list_get_ptr(attrs, id); + na = prec->value.as_vector; + if (igraph_vector_size(na) == elem_id) { + IGRAPH_CHECK(igraph_vector_push_back(na, number)); + } else if (igraph_vector_size(na) < elem_id) { + IGRAPH_CHECK(igraph_attribute_record_resize(prec, elem_id+1)); + VECTOR(*na)[elem_id] = number; + } else { + VECTOR(*na)[elem_id] = number; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t make_dynstr(const char *src, size_t len, char **res) { + *res = strndup(src, len); + CHECK_OOM_RP(*res); + return IGRAPH_SUCCESS; +} + +static igraph_error_t add_string_attribute(igraph_trie_t *names, + igraph_attribute_record_list_t *attrs, + igraph_int_t count, + const char *attrname, + const char *default_value, + igraph_int_t elem_id, + const char *str, + igraph_int_t str_len) { + igraph_int_t attrsize=igraph_trie_size(names); + igraph_int_t id; + igraph_strvector_t *na; + igraph_attribute_record_t *prec; + + if (attrname[0] == '\0') { + /* This is relevant only for custom attributes, which are always of string type. + No need to add the same check for numerical attributes. */ + IGRAPH_ERROR("\"\" is not allowed as a parameter name in Pajek files.", IGRAPH_PARSEERROR); + } + + IGRAPH_CHECK(igraph_trie_get(names, attrname, &id)); + if (id == attrsize) { + igraph_attribute_record_t rec; + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + /* There are 21 standard vertex attributes and 21 standard edge attributes. + * We refuse to allow more to reduce memory usage when fuzzing. */ + if (attrsize > 21) { + IGRAPH_ERROR("Too many attributes in Pajek file.", IGRAPH_PARSEERROR); + } +#endif + + IGRAPH_CHECK(igraph_attribute_record_init(&rec, attrname, IGRAPH_ATTRIBUTE_STRING)); + IGRAPH_FINALLY(igraph_attribute_record_destroy, &rec); + + IGRAPH_CHECK(igraph_attribute_record_set_default_string(&rec, default_value)); + + IGRAPH_CHECK(igraph_attribute_record_resize(&rec, count)); + IGRAPH_CHECK(igraph_attribute_record_list_push_back(attrs, &rec)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of rec transferred to attrs */ + } + + prec = igraph_attribute_record_list_get_ptr(attrs, id); + na = prec->value.as_strvector; + if (igraph_strvector_size(na) <= elem_id) { + IGRAPH_CHECK(igraph_strvector_resize(na, elem_id+1)); + } + IGRAPH_CHECK(igraph_strvector_set_len(na, elem_id, str, str_len)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t add_string_vertex_attribute(const char *name, + const char *value, + size_t len, + igraph_i_pajek_parsedata_t *context) { + + return add_string_attribute(context->vertex_attribute_names, + context->vertex_attributes, + context->vcount, + name, + get_default_value_for_string_vattr(name), + context->actvertex-1, + value, len); +} + +static igraph_error_t add_string_edge_attribute(const char *name, + const char *value, + size_t len, + igraph_i_pajek_parsedata_t *context) { + + return add_string_attribute(context->edge_attribute_names, + context->edge_attributes, + context->actedge, + name, + get_default_value_for_string_eattr(name), + context->actedge-1, + value, len); +} + +static igraph_error_t add_numeric_vertex_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context) { + + return add_numeric_attribute(context->vertex_attribute_names, + context->vertex_attributes, + context->vcount, + name, + get_default_value_for_numeric_vattr(name), + context->actvertex-1, + value); +} + +static igraph_error_t add_numeric_edge_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context) { + + return add_numeric_attribute(context->edge_attribute_names, + context->edge_attributes, + context->actedge, + name, + get_default_value_for_numeric_eattr(name), + context->actedge-1, + value); +} + +static igraph_error_t add_bipartite_type(igraph_i_pajek_parsedata_t *context) { + + const char *attrname="type"; + igraph_trie_t *names=context->vertex_attribute_names; + igraph_attribute_record_list_t *attrs=context->vertex_attributes; + igraph_int_t n=context->vcount, n1=context->vcount2; + igraph_int_t attrid, attrsize = igraph_trie_size(names); + igraph_attribute_record_t* rec; + igraph_vector_bool_t *na; + + if (n1 > n) { + IGRAPH_ERROR("Invalid number of vertices in bipartite Pajek file.", + IGRAPH_PARSEERROR); + } + + IGRAPH_CHECK(igraph_trie_get(names, attrname, &attrid)); + + /* It should not be possible for the "type" attribute to be already + * present at this point. */ + IGRAPH_ASSERT(attrid == attrsize); + + /* add a new attribute */ + IGRAPH_CHECK(igraph_attribute_record_list_push_back_new(attrs, &rec)); + IGRAPH_CHECK(igraph_attribute_record_set_name(rec, attrname)); + IGRAPH_CHECK(igraph_attribute_record_set_type(rec, IGRAPH_ATTRIBUTE_BOOLEAN)); + IGRAPH_CHECK(igraph_attribute_record_resize(rec, n)); + + na = rec->value.as_vector_bool; + for (igraph_int_t i = n1; i < n; i++) { + VECTOR(*na)[i] = true; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t check_bipartite(igraph_i_pajek_parsedata_t *context) { + const igraph_vector_int_t *edges=context->vector; + igraph_int_t n1=context->vcount2; + igraph_int_t ne=igraph_vector_int_size(edges); + + for (igraph_int_t i=0; i n1 && v2 > n1) ) { + IGRAPH_WARNING("Invalid edge in bipartite graph."); + } + } + + return IGRAPH_SUCCESS; +} + +/* Check if attrname is a standard vertex attribute name used by igraph + for Pajek data. All of these must be listed here to prevent overwriting + standard attributes, or crashes due to incompatible attribute types. */ +static igraph_bool_t is_standard_vattr(const char *attrname) { + const char *names[] = { + /* vertex names: */ + "id", /* TODO: remove for 0.11 */ "name", + /* other vertex attributes: */ + "type", "x", "y", "z", + /* vertex parameters: */ + "xfact", "yfact", + "labeldist", "labeldegree2", "framewidth", + "fontsize", "rotation", "radius", + "diamondratio", "labeldegree", + "font", "url", "color", "framecolor", + "labelcolor" + }; + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (strcmp(attrname, names[i]) == 0) { + return true; + } + } + return false; +} + +/* Check if attrname is a standard edge attribute name used by igraph + for Pajek data. All of these must be listed here to prevent overwriting + standard attributes, or crashes due to incompatible attribute types. */ +static igraph_bool_t is_standard_eattr(const char *attrname) { + const char *names[] = { + /* other edge attributes: */ + "weight", + /* edge parameters: */ + "arrowsize", "edgewidth", "hook1", "hook2", + "angle1", "angle2", "velocity1", "velocity2", + "arrowpos", "labelpos", "labelangle", + "labelangle2", "labeldegree", "fontsize", "font", + "arrowtype", "linepattern", "label", "labelcolor", + "color" + }; + for (size_t i=0; i < sizeof(names) / sizeof(names[0]); i++) { + if (strcmp(attrname, names[i]) == 0) { + return true; + } + } + return false; +} + +/* Add a _ character at the end of an attribute name to avoid conflict + * with standard Pajek attributes. */ +static igraph_error_t deconflict_attrname(char **attrname) { + size_t len = strlen(*attrname); + char *tmp = IGRAPH_REALLOC(*attrname, len+2, char); + CHECK_OOM_RP(tmp); + tmp[len] = '_'; + tmp[len+1] = '\0'; + *attrname = tmp; + return IGRAPH_SUCCESS; +} + +typedef struct { + const char* name; + igraph_real_t default_value; +} attribute_numeric_defaults_t; + +typedef struct { + const char* name; + const char* default_value; +} attribute_string_defaults_t; + +/* The defaults listed below are Pajek's built-in defaults unless the user + * overrides them. + * + * See: https://nascol.discourse.group/t/pajek-file-format-default-values-for-attributes/38/2 + */ + +const attribute_numeric_defaults_t vattr_numeric_defaults[] = { + { "xfact", 1 }, + { "yfact", 1 }, + { "labeldist", 20 }, + { "labeldegree2", 285 }, + { "framewidth", 1 }, + { "fontsize", 15 }, + { "rotation", 0 }, + { "radius", 0 }, + { "diamondratio", 0.01 }, + { "labeldegree", 0 }, + { 0 } +}; + +const attribute_string_defaults_t vattr_string_defaults[] = { + { "color", "LightOrange" }, + { "framecolor", "Brown" }, + { "labelcolor", "Maroon" }, + { 0 } +}; + +const attribute_numeric_defaults_t eattr_numeric_defaults[] = { + { "arrowsize", 1 }, + { "edgewidth", 2 }, + { "hook1", 0 }, + { "hook2", 0 }, + { "angle1", 0 }, + { "angle2", 0 }, + { "velocity1", 1 }, + { "velocity2", 1 }, + { "arrowpos", 0 }, + { "labelpos", 0.5 }, + { "labelangle", 10 }, + { "labelangle2", 90 }, + { "labeldegree", 0 }, + { "fontsize", 15 }, + { 0 } +}; + +const attribute_string_defaults_t eattr_string_defaults[] = { + { "color", "MidnightBlue" }, + { "labelcolor", "Black" }, + { 0 } +}; + +static igraph_real_t get_default_value_for_numeric_attr( + const char *attrname, const attribute_numeric_defaults_t* table +) { + const attribute_numeric_defaults_t* ptr; + + for (ptr = table; ptr->name != 0; ptr++) { + if (!strcmp(attrname, ptr->name)) { + return ptr->default_value; + } + } + + return IGRAPH_NAN; +} + +static const char* get_default_value_for_string_attr( + const char *attrname, const attribute_string_defaults_t* table +) { + const attribute_string_defaults_t* ptr; + + for (ptr = table; ptr->name != 0; ptr++) { + if (!strcmp(attrname, ptr->name)) { + return ptr->default_value; + } + } + + return ""; +} + +static igraph_real_t get_default_value_for_numeric_vattr(const char *attrname) { + return get_default_value_for_numeric_attr(attrname, vattr_numeric_defaults); +} + +static igraph_real_t get_default_value_for_numeric_eattr(const char *attrname) { + return get_default_value_for_numeric_attr(attrname, eattr_numeric_defaults); +} + +static const char* get_default_value_for_string_vattr(const char *attrname) { + return get_default_value_for_string_attr(attrname, vattr_string_defaults); +} + +static const char* get_default_value_for_string_eattr(const char *attrname) { + return get_default_value_for_string_attr(attrname, eattr_string_defaults); +} + diff --git a/src/io/parsers/pajek-parser.h b/src/io/parsers/pajek-parser.h new file mode 100644 index 0000000..e836220 --- /dev/null +++ b/src/io/parsers/pajek-parser.h @@ -0,0 +1,180 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + END = 0, + NEWLINE = 258, + NUM = 259, + ALNUM = 260, + QSTR = 261, + NETWORKLINE = 262, + VERTICESLINE = 263, + ARCSLINE = 264, + EDGESLINE = 265, + ARCSLISTLINE = 266, + EDGESLISTLINE = 267, + MATRIXLINE = 268, + ERROR = 269, + VP_X_FACT = 270, + VP_Y_FACT = 271, + VP_PHI = 272, + VP_R = 273, + VP_Q = 274, + VP_IC = 275, + VP_BC = 276, + VP_BW = 277, + VP_LC = 278, + VP_LA = 279, + VP_LR = 280, + VP_LPHI = 281, + VP_FOS = 282, + VP_FONT = 283, + VP_URL = 284, + EP_H1 = 285, + EP_H2 = 286, + EP_W = 287, + EP_C = 288, + EP_P = 289, + EP_A = 290, + EP_S = 291, + EP_A1 = 292, + EP_K1 = 293, + EP_A2 = 294, + EP_K2 = 295, + EP_AP = 296, + EP_L = 297, + EP_LP = 298, + EP_LR = 299, + EP_LPHI = 300, + EP_LC = 301, + EP_LA = 302, + EP_FOS = 303, + EP_FONT = 304 + }; +#endif +/* Tokens. */ +#define END 0 +#define NEWLINE 258 +#define NUM 259 +#define ALNUM 260 +#define QSTR 261 +#define NETWORKLINE 262 +#define VERTICESLINE 263 +#define ARCSLINE 264 +#define EDGESLINE 265 +#define ARCSLISTLINE 266 +#define EDGESLISTLINE 267 +#define MATRIXLINE 268 +#define ERROR 269 +#define VP_X_FACT 270 +#define VP_Y_FACT 271 +#define VP_PHI 272 +#define VP_R 273 +#define VP_Q 274 +#define VP_IC 275 +#define VP_BC 276 +#define VP_BW 277 +#define VP_LC 278 +#define VP_LA 279 +#define VP_LR 280 +#define VP_LPHI 281 +#define VP_FOS 282 +#define VP_FONT 283 +#define VP_URL 284 +#define EP_H1 285 +#define EP_H2 286 +#define EP_W 287 +#define EP_C 288 +#define EP_P 289 +#define EP_A 290 +#define EP_S 291 +#define EP_A1 292 +#define EP_K1 293 +#define EP_A2 294 +#define EP_K2 295 +#define EP_AP 296 +#define EP_L 297 +#define EP_LP 298 +#define EP_LR 299 +#define EP_LPHI 300 +#define EP_LC 301 +#define EP_LA 302 +#define EP_FOS 303 +#define EP_FONT 304 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE + +{ + igraph_int_t intnum; + igraph_real_t realnum; + struct { + char *str; + size_t len; + } string; + char *dynstr; +} +/* Line 1529 of yacc.c. */ + + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/src/isomorphism/bliss.cc b/src/isomorphism/bliss.cc new file mode 100644 index 0000000..d4c66f5 --- /dev/null +++ b/src/isomorphism/bliss.cc @@ -0,0 +1,764 @@ +/* + Copyright (C) 2003-2006 Tommi Junttila + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* FSF address fixed in the above notice on 1 Oct 2009 by Tamas Nepusz */ + +#include "bliss/graph.hh" + +#include "igraph_isomorphism.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_interrupt.h" +#include "igraph_memory.h" +#include "igraph_vector.h" + +#include "core/exceptions.h" + +#include +#include +#include + +using namespace bliss; +using namespace std; + +/** + * \section about_bliss + * + * + * Bliss is a successor of the famous NAUTY algorithm and + * implementation. While using the same ideas in general, with better + * heuristics and data structures Bliss outperforms NAUTY on most + * graphs. + * + * + * + * Bliss was developed and implemented by Tommi Junttila and Petteri Kaski at + * Helsinki University of Technology, Finland. For more information, + * see the Bliss homepage at https://users.aalto.fi/~tjunttil/bliss/ and the following + * publication: + * + * + * + * Tommi Junttila and Petteri Kaski: "Engineering an Efficient Canonical Labeling + * Tool for Large and Sparse Graphs" In ALENEX 2007, pages 135–149, 2007 + * https://doi.org/10.1137/1.9781611972870.13 + * + * + * + * Tommi Junttila and Petteri Kaski: "Conflict Propagation and Component Recursion + * for Canonical Labeling" in TAPAS 2011, pages 151–162, 2011. + * https://doi.org/10.1007/978-3-642-19754-3_16 + * + * + * + * Bliss works with both directed graphs and undirected graphs. It supports graphs with + * self-loops, but not graphs with multi-edges. + * + * + * + * Bliss version 0.75 is included in igraph. + * + */ + +namespace { // unnamed namespace + +inline AbstractGraph *bliss_from_igraph(const igraph_t *graph) { + igraph_int_t nof_vertices = igraph_vcount(graph); + igraph_int_t nof_edges = igraph_ecount(graph); + + if (nof_vertices > UINT_MAX || nof_edges > UINT_MAX) { + throw std::runtime_error("Graph too large for BLISS"); + } + + AbstractGraph *g; + + if (igraph_is_directed(graph)) { + g = new Digraph(static_cast(nof_vertices)); + } else { + g = new Graph(static_cast(nof_vertices)); + } + + /* g->set_verbose_level(0); */ + + for (unsigned int i = 0; i < static_cast(nof_edges); i++) { + g->add_edge( + static_cast(IGRAPH_FROM(graph, i)), + static_cast(IGRAPH_TO(graph, i)) + ); + } + + return g; +} + + +void bliss_free_graph(AbstractGraph *g) { + delete g; +} + + +inline igraph_error_t bliss_set_sh(AbstractGraph *g, igraph_bliss_sh_t sh, bool directed) { + if (directed) { + Digraph::SplittingHeuristic gsh = Digraph::shs_fsm; + switch (sh) { + case IGRAPH_BLISS_F: gsh = Digraph::shs_f; break; + case IGRAPH_BLISS_FL: gsh = Digraph::shs_fl; break; + case IGRAPH_BLISS_FS: gsh = Digraph::shs_fs; break; + case IGRAPH_BLISS_FM: gsh = Digraph::shs_fm; break; + case IGRAPH_BLISS_FLM: gsh = Digraph::shs_flm; break; + case IGRAPH_BLISS_FSM: gsh = Digraph::shs_fsm; break; + default: IGRAPH_ERROR("Invalid splitting heuristic.", IGRAPH_EINVAL); + } + static_cast(g)->set_splitting_heuristic(gsh); + } else { + Graph::SplittingHeuristic gsh = Graph::shs_fsm; + switch (sh) { + case IGRAPH_BLISS_F: gsh = Graph::shs_f; break; + case IGRAPH_BLISS_FL: gsh = Graph::shs_fl; break; + case IGRAPH_BLISS_FS: gsh = Graph::shs_fs; break; + case IGRAPH_BLISS_FM: gsh = Graph::shs_fm; break; + case IGRAPH_BLISS_FLM: gsh = Graph::shs_flm; break; + case IGRAPH_BLISS_FSM: gsh = Graph::shs_fsm; break; + default: IGRAPH_ERROR("Invalid splitting heuristic.", IGRAPH_EINVAL); + } + static_cast(g)->set_splitting_heuristic(gsh); + } + return IGRAPH_SUCCESS; +} + + +inline igraph_error_t bliss_set_colors(AbstractGraph *g, const igraph_vector_int_t *colors) { + if (colors == NULL) { + return IGRAPH_SUCCESS; + } + const int n = g->get_nof_vertices(); + if (n != igraph_vector_int_size(colors)) { + IGRAPH_ERROR("Invalid vertex color vector length.", IGRAPH_EINVAL); + } + for (int i = 0; i < n; ++i) { + igraph_int_t color = VECTOR(*colors)[i]; + if (color < INT_MIN || color > INT_MAX) { + IGRAPH_ERRORF("Invalid vertex color index %" IGRAPH_PRId " for vertex %d.", IGRAPH_EOVERFLOW, color, i); + } + g->change_color(i, static_cast(color)); + } + return IGRAPH_SUCCESS; +} + + +inline igraph_error_t bliss_info_to_igraph(igraph_bliss_info_t *info, const Stats &stats) { + if (info) { + size_t group_size_strlen; + + info->max_level = stats.get_max_level(); + info->nof_nodes = stats.get_nof_nodes(); + info->nof_leaf_nodes = stats.get_nof_leaf_nodes(); + info->nof_bad_nodes = stats.get_nof_bad_nodes(); + info->nof_canupdates = stats.get_nof_canupdates(); + info->nof_generators = stats.get_nof_generators(); + + mpz_t group_size; + mpz_init(group_size); + stats.get_group_size().get(group_size); + group_size_strlen = mpz_sizeinbase(group_size, /* base */ 10) + 2; + info->group_size = IGRAPH_CALLOC(group_size_strlen, char); + if (! info->group_size) { + IGRAPH_ERROR("Insufficient memory to retrieve automotphism group size.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + mpz_get_str(info->group_size, /* base */ 10, group_size); + mpz_clear(group_size); + } + + return IGRAPH_SUCCESS; +} + + +// This is the callback function that can tell Bliss to terminate early. +struct AbortChecker { + bool aborted; + + AbortChecker() : aborted(false) { } + bool operator()() { + if (igraph_allow_interruption() != IGRAPH_SUCCESS) { + aborted = true; + return true; + } + return false; + } +}; + + +// This is the callback function used with AbstractGraph::find_automorphisms(). +// It collects the automorphism group generators into a pointer vector. +class AutCollector { + igraph_vector_int_list_t *generators; + +public: + AutCollector(igraph_vector_int_list_t *generators_) : generators(generators_) { } + + void operator ()(unsigned int n, const unsigned int *aut) { + igraph_vector_int_t newvector; + igraph_error_t err; + + err = igraph_vector_int_init(&newvector, n); + if (err != IGRAPH_SUCCESS) { + throw bad_alloc(); + } + + copy(aut, aut + n, VECTOR(newvector)); // takes care of unsigned int -> igraph_int_t conversion + + err = igraph_vector_int_list_push_back(generators, &newvector); + if (err != IGRAPH_SUCCESS) { + throw bad_alloc(); + } + } +}; + +} // end unnamed namespace + + +/** + * \function igraph_canonical_permutation + * \brief Canonical permutation of a graph. + * + * This function computes the vertex permutation which transforms + * the graph into a canonical form. Two graphs have the same canonical form if + * and only if they are isomorphic. Use \ref igraph_is_same_graph() to compare + * two canonical forms. + * + * + * The current implementation uses the BLISS isomorphism algorithms with + * sensible defaults. Use \ref igraph_canonical_permutation_bliss() to fine-tune + * the parameters. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param labeling Pointer to a vector, the result is stored here. The + * permutation takes vertex 0 to the first element of the vector, + * vertex 1 to the second, etc. The vector will be resized as + * needed. + * \return Error code. + * + * \sa \ref igraph_is_same_graph() + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_canonical_permutation( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling +) { + return igraph_canonical_permutation_bliss( + graph, colors, labeling, IGRAPH_BLISS_FL, NULL + ); +} + +static igraph_error_t igraph_i_canonical_permutation_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info +); + +/** + * \function igraph_canonical_permutation_bliss + * \brief Canonical permutation using Bliss. + * + * This function computes the vertex permutation which transforms + * the graph into a canonical form, using the Bliss algorithm. + * Two graphs have the same canonical form if and only if they + * are isomorphic. Use \ref igraph_is_same_graph() to compare + * two canonical forms. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param labeling Pointer to a vector, the result is stored here. The + * permutation takes vertex 0 to the first element of the vector, + * vertex 1 to the second, etc. The vector will be resized as + * needed. + * \param sh The splitting heuristics to be used in Bliss. See \ref + * igraph_bliss_sh_t. + * \param info If not \c NULL then information on Bliss internals is + * stored here. The memory used by this structure must to be freed + * when no longer needed, see \ref igraph_bliss_info_t. + * \return Error code. + * + * \sa \ref igraph_is_same_graph() + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_canonical_permutation_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info +) { + igraph_vector_int_t inv_permutation; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inv_permutation, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_i_canonical_permutation_bliss(graph, colors, &inv_permutation, sh, info)); + IGRAPH_CHECK(igraph_invert_permutation(&inv_permutation, labeling)); + igraph_vector_int_destroy(&inv_permutation); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_canonical_permutation_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_t *labeling, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info +) { + IGRAPH_HANDLE_EXCEPTIONS( + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + const unsigned int N = g->get_nof_vertices(); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + AbortChecker checker; + const unsigned int *cl = g->canonical_form(stats, /* report */ nullptr, /* terminate */ checker); + if (checker.aborted) { + return IGRAPH_INTERRUPTED; + } + + IGRAPH_CHECK(igraph_vector_int_resize(labeling, N)); + for (unsigned int i = 0; i < N; i++) { + VECTOR(*labeling)[i] = cl[i]; + } + + IGRAPH_CHECK(bliss_info_to_igraph(info, stats)); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + ); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_automorphisms + * \brief Number of automorphisms of a graph. + * + * This function computes the number of automorphisms of a graph. Since the + * number of automorphisms may be very large, the result is returned as an + * \c igraph_real_t instead of an integer. If the number of automorphisms + * is larger than what can be represented in an \c igraph_real_t and you need + * the exact number, use \ref igraph_count_automorphisms_bliss(), which can + * return the number as a string. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param result Pointer to an \c igraph_real_t, the number of automorphisms + * will be returned here. + * \return Error code. \c IGRAPH_EOVERFLOW if the number of automorphisms is + * too large to be represented in an \c igraph_real_t . + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_count_automorphisms( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_real_t *result +) { + igraph_bliss_info_t info; + double x; + + IGRAPH_CHECK(igraph_count_automorphisms_bliss(graph, colors, IGRAPH_BLISS_FL, &info)); + + x = strtod(info.group_size, NULL); + igraph_free(info.group_size); + + if (x == 0) { + return IGRAPH_FAILURE; + } else if (x == HUGE_VAL) { + return IGRAPH_EOVERFLOW; + } else { + if (result) { + *result = x; + } + return IGRAPH_SUCCESS; + } +} + +/** + * \function igraph_count_automorphisms_bliss + * \brief Number of automorphisms using Bliss. + * + * The number of automorphisms of a graph is computed using Bliss. The + * result is returned as part of the \p info structure, in tag \c + * group_size. It is returned as a string, as it can be very high even + * for relatively small graphs. See also \ref igraph_bliss_info_t. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param sh The splitting heuristics to be used in Bliss. See \ref + * igraph_bliss_sh_t. + * \param info The result is stored here, in particular in the \c + * group_size tag of \p info. The memory used by this structure must be + * released when no longer needed, see \ref igraph_bliss_info_t. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_count_automorphisms_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info +) { + IGRAPH_HANDLE_EXCEPTIONS( + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + AbortChecker checker; + g->find_automorphisms(stats, /* report */ nullptr, /* terminate */ checker); + if (checker.aborted) { + return IGRAPH_INTERRUPTED; + } + + IGRAPH_CHECK(bliss_info_to_igraph(info, stats)); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + ); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_automorphism_group + * \brief Automorphism group generators of a graph. + * + * This function computes the generators of the automorphism group of a graph. + * The generator set may not be minimal and may depend on the specific parameters + * of the algorithm under the hood. The generators are permutations represented + * using zero-based indexing. + * + * + * The current implementation uses BLISS behind the scenes and the result may + * be dependent on the splitting heuristics. Use \ref igraph_automorphism_group_bliss() + * if you want to fine-tune the splitting heuristics. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param generators Must be an initialized interger vector list. + * The generators of the automorphism group will be stored here. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_automorphism_group( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators +) { + return igraph_automorphism_group_bliss( + graph, colors, generators, IGRAPH_BLISS_FL, NULL + ); +} + +/** + * \function igraph_automorphism_group_bliss + * \brief Automorphism group generators using Bliss. + * + * The generators of the automorphism group of a graph are computed + * using Bliss. The generator set may not be minimal and may depend on + * the splitting heuristics. The generators are permutations represented + * using zero-based indexing. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param generators Must be an initialized interger vector list. + * The generators of the automorphism group will be stored here. + * \param sh The splitting heuristics to be used in Bliss. See \ref + * igraph_bliss_sh_t. + * \param info If not \c NULL then information on Bliss internals is + * stored here. The memory used by this structure must to be freed + * when no longer needed, see \ref igraph_bliss_info_t. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +igraph_error_t igraph_automorphism_group_bliss( + const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_int_list_t *generators, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info +) { + IGRAPH_HANDLE_EXCEPTIONS( + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + igraph_vector_int_list_clear(generators); + AutCollector collector(generators); + AbortChecker checker; + g->find_automorphisms(stats, collector, checker); + if (checker.aborted) { + return IGRAPH_INTERRUPTED; + } + IGRAPH_CHECK(bliss_info_to_igraph(info, stats)); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + ); + + return IGRAPH_SUCCESS; +} + + +/* The following license notice applies to the rest of this file */ + +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * \function igraph_isomorphic_bliss + * \brief Graph isomorphism via Bliss. + * + * This function uses the Bliss graph isomorphism algorithm, a + * successor of the famous NAUTY algorithm and implementation. Bliss + * is open source and licensed according to the GNU LGPL. See + * https://users.aalto.fi/~tjunttil/bliss/ for + * details. Currently the 0.75 version of Bliss is included in igraph. + * + * + * Isomorphism testing is implemented by producing the canonical form + * of both graphs using \ref igraph_canonical_permutation_bliss() and + * comparing them. + * + * \param graph1 The first input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param graph2 The second input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors1 An optional vertex color vector for the first graph. Supply a + * null pointer if your graph is not colored. + * \param colors2 An optional vertex color vector for the second graph. Supply a + * null pointer if your graph is not colored. + * \param iso Pointer to a boolean, the result is stored here. + * \param map12 A vector or \c NULL pointer. If not \c NULL then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * If the input graphs are not isomorphic then this vector is + * cleared, i.e. it will have length zero. + * \param map21 Similar to \p map12, but for the mapping from \p + * graph2 to \p graph1. + * \param sh Splitting heuristics to be used for the graphs. See + * \ref igraph_bliss_sh_t. + * \param info1 If not \c NULL, information about the canonization of + * the first input graph is stored here. Note that if the two graphs + * have different number of vertices or edges, then this is only + * partially filled. The memory used by this structure should be + * released when no longer needed, see \ref igraph_bliss_info_t + * for details. + * \param info2 Same as \p info1, but for the second graph. + * \return Error code. + * + * Time complexity: exponential, but in practice it is quite fast. + */ +igraph_error_t igraph_isomorphic_bliss(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *colors1, const igraph_vector_int_t *colors2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info1, igraph_bliss_info_t *info2) { + + igraph_int_t no_of_nodes = igraph_vcount(graph1); + igraph_int_t no_of_edges = igraph_ecount(graph1); + igraph_vector_int_t perm1, perm2; + igraph_vector_int_t vmap12, *mymap12 = &vmap12; + igraph_vector_int_t from, to, index; + igraph_vector_int_t from2, to2, index2; + igraph_bool_t directed; + igraph_int_t i, j; + + *iso = 0; + if (info1) { + info1->nof_nodes = info1->nof_leaf_nodes = info1->nof_bad_nodes = + info1->nof_canupdates = info1->max_level = info1->nof_generators = 0; + info1->group_size = 0; + } + if (info2) { + info2->nof_nodes = info2->nof_leaf_nodes = info2->nof_bad_nodes = + info2->nof_canupdates = info2->max_level = info2->nof_generators = 0; + info2->group_size = 0; + } + + directed = igraph_is_directed(graph1); + if (igraph_is_directed(graph2) != directed) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs.", + IGRAPH_EINVAL); + } + if ((colors1 == NULL || colors2 == NULL) && colors1 != colors2) { + IGRAPH_WARNING("Only one of the graphs is vertex colored, colors will be ignored."); + colors1 = NULL; colors2 = NULL; + } + + if (no_of_nodes != igraph_vcount(graph2) || + no_of_edges != igraph_ecount(graph2)) { + if (map12) { + igraph_vector_int_clear(map12); + } + if (map21) { + igraph_vector_int_clear(map21); + } + return IGRAPH_SUCCESS; + } + + if (map12) { + mymap12 = map12; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(mymap12, 0); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&perm1, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&perm2, no_of_nodes); + + IGRAPH_CHECK(igraph_i_canonical_permutation_bliss(graph1, colors1, &perm1, sh, info1)); + IGRAPH_CHECK(igraph_i_canonical_permutation_bliss(graph2, colors2, &perm2, sh, info2)); + + IGRAPH_CHECK(igraph_vector_int_resize(mymap12, no_of_nodes)); + + /* The inverse of perm2 is produced in mymap12 */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*mymap12)[ VECTOR(perm2)[i] ] = i; + } + /* Now we produce perm2^{-1} o perm1 in perm2 */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(perm2)[i] = VECTOR(*mymap12)[ VECTOR(perm1)[i] ]; + } + /* Copy it to mymap12 */ + IGRAPH_CHECK(igraph_vector_int_update(mymap12, &perm2)); + + igraph_vector_int_destroy(&perm1); + igraph_vector_int_destroy(&perm2); + IGRAPH_FINALLY_CLEAN(2); + + /* Check isomorphism, we apply the permutation in mymap12 to graph1 + and should get graph2 */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&from, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&to, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&index, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&from2, no_of_edges * 2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&to2, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&index2, no_of_edges); + + for (i = 0; i < no_of_edges; i++) { + VECTOR(from)[i] = VECTOR(*mymap12)[ IGRAPH_FROM(graph1, i) ]; + VECTOR(to)[i] = VECTOR(*mymap12)[ IGRAPH_TO (graph1, i) ]; + if (! directed && VECTOR(from)[i] < VECTOR(to)[i]) { + igraph_int_t tmp = VECTOR(from)[i]; + VECTOR(from)[i] = VECTOR(to)[i]; + VECTOR(to)[i] = tmp; + } + } + igraph_vector_int_pair_order(&from, &to, &index, no_of_nodes); + + igraph_get_edgelist(graph2, &from2, /*bycol=*/ 1); + for (i = 0, j = no_of_edges; i < no_of_edges; i++, j++) { + VECTOR(to2)[i] = VECTOR(from2)[j]; + if (! directed && VECTOR(from2)[i] < VECTOR(to2)[i]) { + igraph_int_t tmp = VECTOR(from2)[i]; + VECTOR(from2)[i] = VECTOR(to2)[i]; + VECTOR(to2)[i] = tmp; + } + } + igraph_vector_int_resize(&from2, no_of_edges); + igraph_vector_int_pair_order(&from2, &to2, &index2, no_of_nodes); + + *iso = 1; + for (i = 0; i < no_of_edges; i++) { + igraph_int_t i1 = VECTOR(index)[i]; + igraph_int_t i2 = VECTOR(index2)[i]; + if (VECTOR(from)[i1] != VECTOR(from2)[i2] || + VECTOR(to)[i1] != VECTOR(to2)[i2]) { + *iso = 0; + break; + } + } + + /* If the graphs are coloured, we also need to check that applying the + permutation mymap12 to colors1 gives colors2. */ + + if (*iso && colors1 != NULL) { + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*colors1)[i] != VECTOR(*colors2)[ VECTOR(*mymap12)[i] ]) { + *iso = 0; + break; + } + } + } + + igraph_vector_int_destroy(&index2); + igraph_vector_int_destroy(&to2); + igraph_vector_int_destroy(&from2); + igraph_vector_int_destroy(&index); + igraph_vector_int_destroy(&to); + igraph_vector_int_destroy(&from); + IGRAPH_FINALLY_CLEAN(6); + + if (*iso) { + /* The inverse of mymap12 */ + if (map21) { + IGRAPH_CHECK(igraph_vector_int_resize(map21, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*map21)[ VECTOR(*mymap12)[i] ] = i; + } + } + } else { + if (map12) { + igraph_vector_int_clear(map12); + } + if (map21) { + igraph_vector_int_clear(map21); + } + } + + if (!map12) { + igraph_vector_int_destroy(mymap12); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/isomorphism/bliss/CMakeLists.txt b/src/isomorphism/bliss/CMakeLists.txt new file mode 100644 index 0000000..1378f9d --- /dev/null +++ b/src/isomorphism/bliss/CMakeLists.txt @@ -0,0 +1,43 @@ +# Declare the files needed to compile bliss +add_library( + bliss + OBJECT + EXCLUDE_FROM_ALL + defs.cc + graph.cc + heap.cc + orbit.cc + partition.cc + uintseqhash.cc + utils.cc +) + +target_include_directories( + bliss + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/vendor + ${PROJECT_BINARY_DIR}/include + ${PROJECT_BINARY_DIR}/src + $<$:${GMP_INCLUDE_DIR}> +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET bliss PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Since these are included as object files, they should call the +# function as is (without visibility specification) +target_compile_definitions(bliss PRIVATE IGRAPH_STATIC) + +use_all_warnings(bliss) + +if (MSVC) + target_compile_options(bliss PRIVATE /wd4100) # disable unreferenced parameter warning +else() + target_compile_options( + bliss PRIVATE + $<$:-Wno-unused-variable> + ) +endif() diff --git a/src/isomorphism/bliss/bignum.hh b/src/isomorphism/bliss/bignum.hh new file mode 100644 index 0000000..546da5b --- /dev/null +++ b/src/isomorphism/bliss/bignum.hh @@ -0,0 +1,103 @@ +#ifndef BLISS_BIGNUM_HH +#define BLISS_BIGNUM_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#define BLISS_USE_GMP + +#if defined(BLISS_USE_GMP) +#include "internal/gmp_internal.h" +#endif + +#include +#include "defs.hh" + +namespace bliss { + +/** + * \brief A simple wrapper class for big integers (or approximation of them). + * + * If the compile time flag BLISS_USE_GMP is set, + * then the GNU Multiple Precision Arithmetic library (GMP) is used to + * obtain arbitrary precision, otherwise "long double" is used to + * approximate big integers. + */ + +#if defined(BLISS_USE_GMP) + +class BigNum +{ + mpz_t v; +public: + /** + * \brief Create a new big number and set it to zero. + */ + BigNum() {mpz_init(v); } + + /** + * \brief Destroy the number. + */ + ~BigNum() {mpz_clear(v); } + + /** + * \brief Set the number to \a n. + */ + void assign(const int n) {mpz_set_si(v, n); } + + /** + * \brief Multiply the number with \a n. + */ + void multiply(const int n) {mpz_mul_si(v, v, n); } + + /** + * Get a copy of the internal GNU GMP integer. + * The caller is responsible for calling mpz_init before, + * and mpz_clear afterwards on the \a result variable. + */ + void get(mpz_t& result) const {mpz_set(result, v); } +}; + +#else + +class BigNum +{ + long double v; +public: + /** + * \brief Create a new big number and set it to zero. + */ + BigNum(): v(0.0) {} + + /** + * \brief Set the number to \a n. + */ + void assign(const int n) {v = (long double)n; } + + /** + * \brief Multiply the number with \a n. + */ + void multiply(const int n) {v *= (long double)n; } +}; + +#endif + +} //namespace bliss + +#endif // BLISS_BIGNUM_HH diff --git a/src/isomorphism/bliss/defs.cc b/src/isomorphism/bliss/defs.cc new file mode 100644 index 0000000..9760006 --- /dev/null +++ b/src/isomorphism/bliss/defs.cc @@ -0,0 +1,32 @@ +#include "igraph_error.h" + +#include "defs.hh" + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +void +fatal_error(const char* reason) +{ + IGRAPH_FATAL(reason); +} + +} diff --git a/src/isomorphism/bliss/defs.hh b/src/isomorphism/bliss/defs.hh new file mode 100644 index 0000000..51b8246 --- /dev/null +++ b/src/isomorphism/bliss/defs.hh @@ -0,0 +1,90 @@ +#ifndef BLISS_DEFS_HH +#define BLISS_DEFS_HH + +#include +#include + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +/** \file + * \brief Some common definitions. + */ + +namespace bliss { + +/** \brief The version number of bliss. */ +static const char * const version = "0.75"; + +/** + * If a fatal internal error is encountered, + * this function is called. + * There should no return from this function, but an exit or + * a jump/throw to code that deallocates the AbstractGraph instance calling this. + */ +void fatal_error(const char* fmt); + + +#if defined(BLISS_DEBUG) +#define BLISS_CONSISTENCY_CHECKS +#define BLISS_EXPENSIVE_CONSISTENCY_CHECKS +#endif + + +#if defined(BLISS_CONSISTENCY_CHECKS) +/* Force a check that the found automorphisms are valid */ +#define BLISS_VERIFY_AUTOMORPHISMS +#endif + + +#if defined(BLISS_CONSISTENCY_CHECKS) +/* Force a check that the generated partitions are equitable */ +#define BLISS_VERIFY_EQUITABLEDNESS +#endif + +} // namespace bliss + + +/*! \mainpage Outline + * + * This is the C++ API documentation of bliss, + * produced by running doxygen in + * the source directory. + * + * The algorithms and data structures used in bliss, + * the graph file format, as well as the compilation process + * can be found at the + * bliss web site. + * + * The C++ language API is the main API to bliss. + * It basically consists of the public methods in the classes + * * bliss::Graph and + * * bliss::Digraph. + * + * For an example of its use, + * see the \ref executable "source of the bliss executable". + * + * \section capi_sec The C language API + * + * The C language API is given in the file bliss_C.h. + * It is currently only a subset of the C++ API, + * so consider using the C++ API whenever possible. + */ + +#endif // BLISS_DEFS_HH diff --git a/src/isomorphism/bliss/graph.cc b/src/isomorphism/bliss/graph.cc new file mode 100644 index 0000000..7260423 --- /dev/null +++ b/src/isomorphism/bliss/graph.cc @@ -0,0 +1,5035 @@ +#include "igraph_error.h" + +#include +#include +#include +#include +#include +// #include +#include +#include + +#include "defs.hh" +#include "graph.hh" +#include "partition.hh" +#include "utils.hh" + +/* Allow using 'and' instead of '&&' with MSVC */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + + +namespace bliss { + +#define _INTERNAL_ERROR() IGRAPH_FATAL("Bliss internal error") + +/*------------------------------------------------------------------------- + * + * Constructor and destructor routines for the abstract graph class + * + *-------------------------------------------------------------------------*/ + + +AbstractGraph::AbstractGraph() +{ + /* Initialize stuff */ + first_path_labeling = nullptr; + first_path_labeling_inv = nullptr; + best_path_labeling = nullptr; + best_path_labeling_inv = nullptr; + first_path_automorphism = nullptr; + best_path_automorphism = nullptr; + in_search = false; + + /* Default value for using "long prune" */ + opt_use_long_prune = true; + /* Default value for using failure recording */ + opt_use_failure_recording = true; + /* Default value for using component recursion */ + opt_use_comprec = true; + + + /* + verbose_level = 0; + verbstr = stdout; + */ +} + + +AbstractGraph::~AbstractGraph() +{ + delete[] first_path_labeling; first_path_labeling = nullptr; + delete[] first_path_labeling_inv; first_path_labeling_inv = nullptr; + delete[] first_path_automorphism; first_path_automorphism = nullptr; + + delete[] best_path_labeling; best_path_labeling = nullptr; + delete[] best_path_labeling_inv; best_path_labeling_inv = nullptr; + delete[] best_path_automorphism; best_path_automorphism = nullptr; +} + + + +/*------------------------------------------------------------------------- + * + * Verbose output management routines + * + *-------------------------------------------------------------------------*/ + +/* +void +AbstractGraph::set_verbose_level(const unsigned int level) +{ + verbose_level = level; +} + +void +AbstractGraph::set_verbose_file(FILE* const fp) +{ + verbstr = fp; +} +*/ + + + +/*------------------------------------------------------------------------- + * + * Routines for refinement to equitable partition + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::refine_to_equitable() +{ + + /* Start refinement from all cells -> push 'em all in the splitting queue */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + p.splitting_queue_add(cell); + + do_refine_to_equitable(); + +} + +void +AbstractGraph::refine_to_equitable(Partition::Cell* const unit_cell) +{ + + p.splitting_queue_add(unit_cell); + + do_refine_to_equitable(); +} + + + +void +AbstractGraph::refine_to_equitable(Partition::Cell* const unit_cell1, + Partition::Cell* const unit_cell2) +{ + + p.splitting_queue_add(unit_cell1); + p.splitting_queue_add(unit_cell2); + + do_refine_to_equitable(); +} + + + +bool +AbstractGraph::do_refine_to_equitable() +{ + + eqref_hash.reset(); + + while(!p.splitting_queue_is_empty()) + { + Partition::Cell* const cell = p.splitting_queue_pop(); + + if(cell->is_unit()) + { + if(in_search) { + const unsigned int index = cell->first; + if(first_path_automorphism) + { + /* Build the (potential) automorphism on-the-fly */ + first_path_automorphism[first_path_labeling_inv[index]] = + p.elements[index]; + } + if(best_path_automorphism) + { + /* Build the (potential) automorphism on-the-fly */ + best_path_automorphism[best_path_labeling_inv[index]] = + p.elements[index]; + } + } + const bool worse = split_neighbourhood_of_unit_cell(cell); + if(in_search and worse) + goto worse_exit; + } + else + { + const bool worse = split_neighbourhood_of_cell(cell); + if(in_search and worse) + goto worse_exit; + } + } + + return true; + + worse_exit: + /* Clear splitting_queue */ + p.splitting_queue_clear(); + return false; +} + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Routines for handling the canonical labeling + * + *-------------------------------------------------------------------------*/ + +/** \internal + * Assign the labeling induced by the current partition 'this.p' to + * \a labeling. + * That is, if the partition is [[2,0],[1]], + * then \a labeling will map 0 to 1, 1 to 2, and 2 to 0. + */ +void +AbstractGraph::update_labeling(unsigned int* const labeling) +{ + const unsigned int N = get_nof_vertices(); + unsigned int* ep = p.elements; + for(unsigned int i = 0; i < N; i++, ep++) + labeling[*ep] = i; +} + +/** \internal + * The same as update_labeling() except that the inverse of the labeling + * is also produced and assigned to \a labeling_inv. + */ +void +AbstractGraph::update_labeling_and_its_inverse(unsigned int* const labeling, + unsigned int* const labeling_inv) +{ + const unsigned int N = get_nof_vertices(); + unsigned int* ep = p.elements; + unsigned int* clip = labeling_inv; + + for(unsigned int i = 0; i < N; ) { + labeling[*ep] = i; + i++; + *clip = *ep; + ep++; + clip++; + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Routines for handling automorphisms + * + *-------------------------------------------------------------------------*/ + + +/** \internal + * Reset the permutation \a perm to the identity permutation. + */ +void +AbstractGraph::reset_permutation(unsigned int* perm) +{ + const unsigned int N = get_nof_vertices(); + for(unsigned int i = 0; i < N; i++, perm++) + *perm = i; +} + +/* +bool +AbstractGraph::is_automorphism(unsigned int* const perm) +{ + _INTERNAL_ERROR(); + return false; +} +*/ + +/* +bool +AbstractGraph::is_automorphism(const std::vector& perm) const +{ + _INTERNAL_ERROR(); + return false; +} +*/ + + + +/*------------------------------------------------------------------------- + * + * Certificate building + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::cert_add(const unsigned int v1, + const unsigned int v2, + const unsigned int v3) +{ + if(refine_compare_certificate) + { + if(refine_equal_to_first) + { + /* So far equivalent to the first path... */ + unsigned int index = certificate_current_path.size(); + if(index >= refine_first_path_subcertificate_end) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[index] != v1) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[++index] != v2) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[++index] != v3) + { + refine_equal_to_first = false; + } + if(opt_use_failure_recording and !refine_equal_to_first) + { + /* We just became different from the first path, + * remember the deviation point tree-specific invariant + * for the use of failure recording */ + UintSeqHash h; + h.update(v1); + h.update(v2); + h.update(v3); + h.update(index); + h.update(eqref_hash.get_value()); + failure_recording_fp_deviation = h.get_value(); + } + } + if(refine_cmp_to_best == 0) + { + /* So far equivalent to the current best path... */ + unsigned int index = certificate_current_path.size(); + if(index >= refine_best_path_subcertificate_end) + { + refine_cmp_to_best = 1; + } + else if(v1 > certificate_best_path[index]) + { + refine_cmp_to_best = 1; + } + else if(v1 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + else if(v2 > certificate_best_path[++index]) + { + refine_cmp_to_best = 1; + } + else if(v2 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + else if(v3 > certificate_best_path[++index]) + { + refine_cmp_to_best = 1; + } + else if(v3 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + } + if((refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return; + } + /* Update the current path certificate */ + certificate_current_path.push_back(v1); + certificate_current_path.push_back(v2); + certificate_current_path.push_back(v3); +} + + +void +AbstractGraph::cert_add_redundant(const unsigned int v1, + const unsigned int v2, + const unsigned int v3) +{ + return cert_add(v1, v2, v3); +} + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Long prune code + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::long_prune_init() +{ + const unsigned int N = get_nof_vertices(); + long_prune_temp.clear(); + long_prune_temp.resize(N); + /* Of how many automorphisms we can store information in + the predefined, fixed amount of memory? */ + const unsigned int nof_fitting_in_max_mem = + (long_prune_options_max_mem * 1024 * 1024) / (((N * 2) / 8)+1); + long_prune_max_stored_autss = long_prune_options_max_stored_auts; + /* Had some problems with g++ in using (a* tmp = long_prune_fixed[real_i]; + long_prune_fixed[real_i] = long_prune_fixed[real_j]; + long_prune_fixed[real_j] = tmp; + tmp = long_prune_mcrs[real_i]; + long_prune_mcrs[real_i] = long_prune_mcrs[real_j]; + long_prune_mcrs[real_j] = tmp; +} + +std::vector& +AbstractGraph::long_prune_allocget_fixed(const unsigned int index) +{ + const unsigned int i = index % long_prune_max_stored_autss; + if(!long_prune_fixed[i]) + long_prune_fixed[i] = new std::vector(get_nof_vertices()); + return *long_prune_fixed[i]; +} + +std::vector& +AbstractGraph::long_prune_get_fixed(const unsigned int index) +{ + return *long_prune_fixed[index % long_prune_max_stored_autss]; +} + +std::vector& +AbstractGraph::long_prune_allocget_mcrs(const unsigned int index) +{ + const unsigned int i = index % long_prune_max_stored_autss; + if(!long_prune_mcrs[i]) + long_prune_mcrs[i] = new std::vector(get_nof_vertices()); + return *long_prune_mcrs[i]; +} + +std::vector& +AbstractGraph::long_prune_get_mcrs(const unsigned int index) +{ + return *long_prune_mcrs[index % long_prune_max_stored_autss]; +} + +void +AbstractGraph::long_prune_add_automorphism(const unsigned int* aut) +{ + if(long_prune_max_stored_autss == 0) + return; + + const unsigned int N = get_nof_vertices(); + + + /* If the buffer of stored auts is full, remove the oldest aut */ + if(long_prune_end - long_prune_begin == long_prune_max_stored_autss) + { + long_prune_begin++; + } + long_prune_end++; + std::vector& fixed = long_prune_allocget_fixed(long_prune_end-1); + std::vector& mcrs = long_prune_allocget_mcrs(long_prune_end-1); + /* Mark nodes that are (i) fixed or (ii) minimal orbit representatives + * under the automorphism 'aut' */ + for(unsigned int i = 0; i < N; i++) + { + fixed[i] = (aut[i] == i); + if(long_prune_temp[i] == false) + { + mcrs[i] = true; + unsigned int j = aut[i]; + while(j != i) + { + long_prune_temp[j] = true; + j = aut[j]; + } + } + else + { + mcrs[i] = false; + } + /* Clear the temp array on-the-fly... */ + long_prune_temp[i] = false; + } + + +} + + + +/*------------------------------------------------------------------------- + * + * Routines for handling orbit information + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::update_orbit_information(Orbit& o, const unsigned int* perm) +{ + const unsigned int N = get_nof_vertices(); + for(unsigned int i = 0; i < N; i++) + if(perm[i] != i) + o.merge_orbits(i, perm[i]); +} + + + + + + + + +/*------------------------------------------------------------------------- + * + * The actual backtracking search + * + *-------------------------------------------------------------------------*/ + +/** \internal \brief Search tree node information. + */ +class TreeNode +{ + //friend class AbstractGraph; +public: + unsigned int split_cell_first; + + int split_element; + static const int SPLIT_START = -1; + static const int SPLIT_END = -2; + + Partition::BacktrackPoint partition_bt_point; + + unsigned int certificate_index; + + static const char NO = -1; + static const char MAYBE = 0; + static const char YES = 1; + + /* First path stuff */ + bool fp_on; + bool fp_cert_equal; + char fp_extendable; + + /* Best path stuff */ + bool in_best_path; + int cmp_to_best_path; + + unsigned int failure_recording_ival; + + /* Component recursion related data */ + unsigned int cr_cep_stack_size; + unsigned int cr_cep_index; + unsigned int cr_level; + + bool needs_long_prune = false; /* igraph-specific patch: initialize to false to silence UBSan */ + unsigned int long_prune_begin; + std::set > long_prune_redundant; + + UintSeqHash eqref_hash; + unsigned int subcertificate_length; +}; + + + + + +void +AbstractGraph::search(const bool canonical, + Stats& stats, + const std::function& report, + const std::function& terminate) +{ + const unsigned int N = get_nof_vertices(); + + unsigned int all_same_level = UINT_MAX; + + p.graph = this; + + /* + * Must be done! + */ + remove_duplicate_edges(); + + /* + * Reset search statistics + */ + stats.reset(); + stats.nof_nodes = 1; + stats.nof_leaf_nodes = 1; + + /* Free old first path data structures */ + delete[] first_path_labeling; first_path_labeling = nullptr; + delete[] first_path_labeling_inv; first_path_labeling_inv = nullptr; + delete[] first_path_automorphism; first_path_automorphism = nullptr; + + /* Free old best path data structures */ + delete[] best_path_labeling; best_path_labeling = nullptr; + delete[] best_path_labeling_inv; best_path_labeling_inv = nullptr; + delete[] best_path_automorphism; best_path_automorphism = nullptr; + + if(N == 0) + { + /* Nothing to do, return... */ + return; + } + + /* Initialize the partition ... */ + p.init(N); + /* ... and the component recursion data structures in the partition */ + if(opt_use_comprec) + p.cr_init(); + + neighbour_heap.init(N); + + in_search = false; + /* Do not compute certificate when building the initial partition */ + refine_compare_certificate = false; + /* The 'eqref_hash' hash value is not computed when building + * the initial partition as it is not used for anything at the moment. + * This saves some cycles. */ + compute_eqref_hash = false; + + make_initial_equitable_partition(); + + /* + * Allocate space for the "first path" and "best path" labelings + */ + delete[] first_path_labeling; + first_path_labeling = new unsigned int[N]; + + delete[] best_path_labeling; + best_path_labeling = new unsigned int[N]; + for(unsigned int i = 0; i < N; i++) best_path_labeling[i] = i; + + /* + * Is the initial partition discrete? + */ + if(p.is_discrete()) + { + /* Make the best path labeling i.e. the canonical labeling */ + update_labeling(best_path_labeling); + /* Update statistics */ + stats.nof_leaf_nodes = 1; + /* Release component recursion data in partition */ + if(opt_use_comprec) + p.cr_free(); + return; + } + + /* + * Allocate the inverses of the "first path" and "best path" labelings + */ + delete[] first_path_labeling_inv; + first_path_labeling_inv = new unsigned int[N]; + std::fill_n(first_path_labeling_inv, N, 0); + delete[] best_path_labeling_inv; + best_path_labeling_inv = new unsigned int[N]; + std::fill_n(best_path_labeling_inv, N, 0); + + /* + * Allocate space for the automorphisms + */ + delete[] first_path_automorphism; + first_path_automorphism = new unsigned int[N]; + delete[] best_path_automorphism; + best_path_automorphism = new unsigned int[N]; + + /* + * Initialize orbit information so that all vertices are in their own orbits + */ + first_path_orbits.init(N); + best_path_orbits.init(N); + + /* + * Initialize certificate memory + */ + initialize_certificate(); + + std::vector search_stack; + std::vector first_path_info; + std::vector best_path_info; + + search_stack.clear(); + + /* Initialize "long prune" data structures */ + if(opt_use_long_prune) + long_prune_init(); + + /* + * Initialize failure recording data structures + */ + typedef std::set > FailureRecordingSet; + std::vector failure_recording_hashes; + + /* + * Initialize component recursion data structures + */ + cr_cep_stack.clear(); + unsigned int cr_cep_index = 0; + { + /* Inset a sentinel "component end point" */ + CR_CEP sentinel; + sentinel.creation_level = 0; + sentinel.discrete_cell_limit = get_nof_vertices(); + sentinel.next_cr_level = 0; + sentinel.next_cep_index = 0; + sentinel.first_checked = false; + sentinel.best_checked = false; + cr_cep_index = 0; + cr_cep_stack.push_back(sentinel); + } + cr_level = 0; + if(opt_use_comprec and + nucr_find_first_component(cr_level) == true and + p.nof_discrete_cells() + cr_component_elements < + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + cr_level = p.cr_split_level(0, cr_component); + CR_CEP cep; + cep.creation_level = 0; + cep.discrete_cell_limit = p.nof_discrete_cells() + cr_component_elements; + cep.next_cr_level = 0; + cep.next_cep_index = cr_cep_index; + cep.first_checked = false; + cep.best_checked = false; + cr_cep_index = cr_cep_stack.size(); + cr_cep_stack.push_back(cep); + } + + /* + * Build the root node of the search tree + */ + { + TreeNode root; + Partition::Cell* split_cell = find_next_cell_to_be_splitted(p.first_cell); + root.split_cell_first = split_cell->first; + root.split_element = TreeNode::SPLIT_START; + root.partition_bt_point = p.set_backtrack_point(); + root.certificate_index = 0; + root.fp_on = true; + root.fp_cert_equal = true; + root.fp_extendable = TreeNode::MAYBE; + root.in_best_path = false; + root.cmp_to_best_path = 0; + root.long_prune_begin = 0; + + root.failure_recording_ival = 0; + + /* Save component recursion info for backtracking */ + root.cr_level = cr_level; + root.cr_cep_stack_size = cr_cep_stack.size(); + root.cr_cep_index = cr_cep_index; + search_stack.push_back(root); + } + + /* + * Set status and global flags for search related procedures + */ + in_search = true; + /* Do not compare certificates during refinement until the first path has been traversed to the leaf */ + refine_compare_certificate = false; + + + + + /* + * The actual backtracking search + */ + while(!search_stack.empty()) + { + if(terminate and terminate()) { + break; + } + TreeNode& current_node = search_stack.back(); + const unsigned int current_level = (unsigned int)search_stack.size()-1; + + + if(opt_use_comprec) + { + CR_CEP& cep = cr_cep_stack[current_node.cr_cep_index]; + if(cep.first_checked == true and + current_node.fp_extendable == TreeNode::MAYBE and + !search_stack[cep.creation_level].fp_on) + { + current_node.fp_extendable = TreeNode::NO; + } + } + + if(current_node.fp_on) + { + if(current_node.split_element == TreeNode::SPLIT_END) + { + search_stack.pop_back(); + continue; + } + } + else + { + if(current_node.fp_extendable == TreeNode::YES) + { + search_stack.pop_back(); + continue; + } + if(current_node.split_element == TreeNode::SPLIT_END) + { + if(opt_use_failure_recording) + { + TreeNode& parent_node = search_stack[current_level-1]; + if(parent_node.fp_on) + failure_recording_hashes[current_level-1].insert(current_node.failure_recording_ival); + } + search_stack.pop_back(); + continue; + } + if(current_node.fp_extendable == TreeNode::NO and + (!canonical or current_node.cmp_to_best_path < 0)) + { + if(opt_use_failure_recording) + { + TreeNode& parent_node = search_stack[current_level-1]; + if(parent_node.fp_on) + failure_recording_hashes[current_level-1].insert(current_node.failure_recording_ival); + } + search_stack.pop_back(); + continue; + } + } + + /* Restore partition ... */ + p.goto_backtrack_point(current_node.partition_bt_point); + /* ... and re-remember backtracking point */ + current_node.partition_bt_point = p.set_backtrack_point(); + + /* Restore current path certificate */ + certificate_index = current_node.certificate_index; + refine_current_path_certificate_index = current_node.certificate_index; + certificate_current_path.resize(certificate_index); + + /* Fetch split cell information */ + Partition::Cell * const cell = + p.get_cell(p.elements[current_node.split_cell_first]); + + /* Restore component recursion information */ + cr_level = current_node.cr_level; + cr_cep_stack.resize(current_node.cr_cep_stack_size); + cr_cep_index = current_node.cr_cep_index; + + + /* + * Update long prune redundancy sets + */ + if(opt_use_long_prune and current_level >= 1 and !current_node.fp_on) + { + unsigned int begin = (current_node.long_prune_begin>long_prune_begin)?current_node.long_prune_begin:long_prune_begin; + for(unsigned int i = begin; i < long_prune_end; i++) + { + const std::vector& fixed = long_prune_get_fixed(i); +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int l = 0; l < search_stack.size()-2; l++) + assert(fixed[search_stack[l].split_element]); +#endif + if(fixed[search_stack[search_stack.size()-1-1].split_element] == + false) + { + long_prune_swap(begin, i); + begin++; + current_node.long_prune_begin = begin; + continue; + } + } + + if(current_node.split_element == TreeNode::SPLIT_START) + { + current_node.needs_long_prune = true; + } + else if(current_node.needs_long_prune) + { + current_node.needs_long_prune = false; + unsigned int begin = (current_node.long_prune_begin>long_prune_begin)?current_node.long_prune_begin:long_prune_begin; + for(unsigned int i = begin; i < long_prune_end; i++) + { + const std::vector& fixed = long_prune_get_fixed(i); +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int l = 0; l < search_stack.size()-2; l++) + assert(fixed[search_stack[l].split_element]); +#endif + assert(fixed[search_stack[current_level-1].split_element] == true); + if(fixed[search_stack[current_level-1].split_element] == false) + { + long_prune_swap(begin, i); + begin++; + current_node.long_prune_begin = begin; + continue; + } + const std::vector& mcrs = long_prune_get_mcrs(i); + unsigned int* ep = p.elements + cell->first; + for(unsigned int j = cell->length; j > 0; j--, ep++) { + if(mcrs[*ep] == false) + current_node.long_prune_redundant.insert(*ep); + } + } + } + } + + + /* + * Find the next smallest, non-isomorphic element in the cell and + * store it in current_node.split_element + */ + { + unsigned int next_split_element = UINT_MAX; + //unsigned int* next_split_element_pos = 0; + unsigned int* ep = p.elements + cell->first; + if(current_node.fp_on) + { + /* Find the next larger splitting element that is + * a minimal orbit representative w.r.t. first_path_orbits */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + first_path_orbits.is_minimal_representative(*ep)) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + else if(current_node.in_best_path) + { + /* Find the next larger splitting element that is + * a minimal orbit representative w.r.t. best_path_orbits */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + best_path_orbits.is_minimal_representative(*ep) and + (!opt_use_long_prune or + current_node.long_prune_redundant.find(*ep) == + current_node.long_prune_redundant.end())) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + else + { + /* Find the next larger splitting element */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + (!opt_use_long_prune or + current_node.long_prune_redundant.find(*ep) == + current_node.long_prune_redundant.end())) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + if(next_split_element == UINT_MAX) + { + /* No more (unexplored children) in the cell */ + current_node.split_element = TreeNode::SPLIT_END; + if(current_node.fp_on) + { + /* Update group size */ + const unsigned int index = first_path_orbits.orbit_size(first_path_info[search_stack.size()-1].splitting_element); + stats.group_size.multiply(index); + stats.group_size_approx *= (long double)index; + /* + * Update all_same_level + */ + if(index == cell->length and all_same_level == current_level+1) + all_same_level = current_level; + /* + if(verbstr and verbose_level >= 2) { + fprintf(verbstr, + "Level %u: orbits=%u, index=%u/%u, all_same_level=%u\n", + current_level, + first_path_orbits.nof_orbits(), + index, cell->length, + all_same_level); + fflush(verbstr); + } + */ + } + continue; + } + + /* Split on smallest */ + current_node.split_element = next_split_element; + } + + const unsigned int child_level = current_level+1; + /* Update some statistics */ + stats.nof_nodes++; + if(search_stack.size() > stats.max_level) + stats.max_level = search_stack.size(); + + + + /* Set flags and indices for the refiner certificate builder */ + refine_equal_to_first = current_node.fp_cert_equal; + refine_cmp_to_best = current_node.cmp_to_best_path; + if(!first_path_info.empty()) + { + if(refine_equal_to_first) + refine_first_path_subcertificate_end = + first_path_info[search_stack.size()-1].certificate_index + + first_path_info[search_stack.size()-1].subcertificate_length; + if(canonical) + { + if(refine_cmp_to_best == 0) + refine_best_path_subcertificate_end = + best_path_info[search_stack.size()-1].certificate_index + + best_path_info[search_stack.size()-1].subcertificate_length; + } + else + refine_cmp_to_best = -1; + } + + const bool was_fp_cert_equal = current_node.fp_cert_equal; + + /* Individualize, i.e. split the cell in two, the latter new cell + * will be a unit one containing info.split_element */ + Partition::Cell* const new_cell = + p.individualize(cell, current_node.split_element); + + /* + * Refine the new partition to equitable + */ + if(cell->is_unit()) + refine_to_equitable(cell, new_cell); + else + refine_to_equitable(new_cell); + + + + + /* Update statistics */ + if(p.is_discrete()) + stats.nof_leaf_nodes++; + + + if(!first_path_info.empty()) + { + /* We are no longer on the first path */ + const unsigned int subcertificate_length = + certificate_current_path.size() - certificate_index; + if(refine_equal_to_first) + { + /* Was equal to the first path so far */ + PathInfo& first_pinfo = first_path_info[current_level]; + assert(first_pinfo.certificate_index == certificate_index); + if(subcertificate_length != first_pinfo.subcertificate_length) + { + refine_equal_to_first = false; + if(opt_use_failure_recording) + failure_recording_fp_deviation = subcertificate_length; + } + else if(first_pinfo.eqref_hash.cmp(eqref_hash) != 0) + { + refine_equal_to_first = false; + if(opt_use_failure_recording) + failure_recording_fp_deviation = eqref_hash.get_value(); + } + } + if(canonical and (refine_cmp_to_best == 0)) + { + /* Was equal to the best path so far */ + PathInfo& bestp_info = best_path_info[current_level]; + assert(bestp_info.certificate_index == certificate_index); + if(subcertificate_length < bestp_info.subcertificate_length) + { + refine_cmp_to_best = -1; + } + else if(subcertificate_length > bestp_info.subcertificate_length) + { + refine_cmp_to_best = 1; + } + else if(bestp_info.eqref_hash.cmp(eqref_hash) > 0) + { + refine_cmp_to_best = -1; + } + else if(bestp_info.eqref_hash.cmp(eqref_hash) < 0) + { + refine_cmp_to_best = 1; + } + } + + if(opt_use_failure_recording and + was_fp_cert_equal and + !refine_equal_to_first) + { + UintSeqHash k; + k.update(failure_recording_fp_deviation); + k.update(eqref_hash.get_value()); + failure_recording_fp_deviation = k.get_value(); + + if(current_node.fp_on) + failure_recording_hashes[current_level].insert(failure_recording_fp_deviation); + else + { + for(unsigned int i = current_level; i > 0; i--) + { + if(search_stack[i].fp_on) + break; + const FailureRecordingSet& s = failure_recording_hashes[i]; + if(i == current_level and + s.find(failure_recording_fp_deviation) != s.end()) + break; + if(s.find(0) != s.end()) + break; + search_stack[i].fp_extendable = TreeNode::NO; + } + } + } + + + /* Check if no longer equal to the first path and, + * if canonical labeling is desired, also worse than the + * current best path */ + if(refine_equal_to_first == false and + (!canonical or (refine_cmp_to_best < 0))) + { + /* Yes, backtrack */ + stats.nof_bad_nodes++; + if(current_node.fp_cert_equal == true and + current_level+1 > all_same_level) + { + assert(all_same_level >= 1); + for(unsigned int i = all_same_level; + i < search_stack.size(); + i++) + { + search_stack[i].fp_extendable = TreeNode::NO; + } + } + + continue; + } + } + +#if defined(BLISS_VERIFY_EQUITABLEDNESS) + /* The new partition should be equitable */ + if(!is_equitable()) + fatal_error("consistency check failed - partition after refinement is not equitable"); +#endif + + /* + * Next level search tree node info + */ + TreeNode child_node; + + /* No more in the first path */ + child_node.fp_on = false; + /* No more in the best path */ + child_node.in_best_path = false; + + child_node.fp_cert_equal = refine_equal_to_first; + if(current_node.fp_extendable == TreeNode::NO or + (current_node.fp_extendable == TreeNode::MAYBE and + child_node.fp_cert_equal == false)) + child_node.fp_extendable = TreeNode::NO; + else + child_node.fp_extendable = TreeNode::MAYBE; + child_node.cmp_to_best_path = refine_cmp_to_best; + + child_node.failure_recording_ival = 0; + child_node.cr_cep_stack_size = current_node.cr_cep_stack_size; + child_node.cr_cep_index = current_node.cr_cep_index; + child_node.cr_level = current_node.cr_level; + + certificate_index = certificate_current_path.size(); + + current_node.eqref_hash = eqref_hash; + current_node.subcertificate_length = + certificate_index - current_node.certificate_index; + + + /* + * The first encountered leaf node at the end of the "first path"? + */ + if(p.is_discrete() and first_path_info.empty()) + { + //fprintf(stdout, "Level %u: FIRST\n", child_level); fflush(stdout); + stats.nof_canupdates++; + /* + * Update labelings and their inverses + */ + update_labeling_and_its_inverse(first_path_labeling, + first_path_labeling_inv); + update_labeling_and_its_inverse(best_path_labeling, + best_path_labeling_inv); + /* + * Reset automorphism array + */ + reset_permutation(first_path_automorphism); + reset_permutation(best_path_automorphism); + /* + * Reset orbit information + */ + first_path_orbits.reset(); + best_path_orbits.reset(); + /* + * Reset group size + */ + stats.group_size.assign(1); + stats.group_size_approx = 1.0; + /* + * Reset all_same_level + */ + all_same_level = child_level; + /* + * Mark the current path to be the first and best one and save it + */ + const unsigned int base_size = search_stack.size(); + best_path_info.clear(); + //fprintf(stdout, " New base is: "); + for(unsigned int i = 0; i < base_size; i++) { + search_stack[i].fp_on = true; + search_stack[i].fp_cert_equal = true; + search_stack[i].fp_extendable = TreeNode::YES; + search_stack[i].in_best_path = true; + search_stack[i].cmp_to_best_path = 0; + PathInfo path_info; + path_info.splitting_element = search_stack[i].split_element; + path_info.certificate_index = search_stack[i].certificate_index; + path_info.eqref_hash = search_stack[i].eqref_hash; + path_info.subcertificate_length = search_stack[i].subcertificate_length; + first_path_info.push_back(path_info); + best_path_info.push_back(path_info); + //fprintf(stdout, "%u ", search_stack[i].split_element); + } + //fprintf(stdout, "\n"); fflush(stdout); + /* Copy certificates */ + certificate_first_path = certificate_current_path; + certificate_best_path = certificate_current_path; + + /* From now on, compare certificates when refining */ + refine_compare_certificate = true; + + if(opt_use_failure_recording) + failure_recording_hashes.resize(base_size); + + /* + for(unsigned int j = 0; j < search_stack.size(); j++) + fprintf(stderr, "%u ", search_stack[j].split_element); + fprintf(stderr, "\n"); + p.print(stderr); fprintf(stderr, "\n"); + */ + + /* + * Backtrack to the previous level + */ + continue; + } + + + if(p.is_discrete() and child_node.fp_cert_equal) + { + /* + * A leaf node that is equal to the first one. + * An automorphism found: aut[i] = elements[first_path_labeling[i]] + */ + goto handle_first_path_automorphism; + } + + + if(!p.is_discrete()) + { + Partition::Cell* next_split_cell = 0; + /* + * An internal, non-leaf node + */ + if(opt_use_comprec) + { + assert(p.nof_discrete_cells() <= + cr_cep_stack[cr_cep_index].discrete_cell_limit); + assert(cr_level == child_node.cr_level); + + + if(p.nof_discrete_cells() == + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + /* We have reached the end of a component */ + assert(cr_cep_index != 0); + CR_CEP& cep = cr_cep_stack[cr_cep_index]; + + /* First, compare with respect to the first path */ + if(first_path_info.empty() or child_node.fp_cert_equal) { + if(cep.first_checked == false) + { + /* First time, go to the next component */ + cep.first_checked = true; + } + else + { + assert(!first_path_info.empty()); + assert(cep.creation_level < search_stack.size()); + TreeNode& old_info = search_stack[cep.creation_level]; + /* If the component was found when on the first path, + * handle the found automorphism as the other + * first path automorphisms */ + if(old_info.fp_on) + goto handle_first_path_automorphism; + } + } + + if(canonical and + !first_path_info.empty() and + child_node.cmp_to_best_path >= 0) { + if(cep.best_checked == false) + { + /* First time, go to the next component */ + cep.best_checked = true; + } + else + { + assert(cep.creation_level < search_stack.size()); + TreeNode& old_info = search_stack[cep.creation_level]; + if(child_node.cmp_to_best_path == 0) { + /* If the component was found when on the best path, + * handle the found automorphism as the other + * best path automorphisms */ + if(old_info.in_best_path) + goto handle_best_path_automorphism; + /* Otherwise, we do not remember the automorhism as + * we didn't memorize the path that was invariant + * equal to the best one and passed through the + * component. + * Thus we can only backtrack to the previous level */ + child_node.cmp_to_best_path = -1; + if(!child_node.fp_cert_equal) + { + continue; + } + } + else { + assert(child_node.cmp_to_best_path > 0); + if(old_info.in_best_path) + { + stats.nof_canupdates++; + /* + * Update canonical labeling and its inverse + */ + for(unsigned int i = 0; i < N; i++) { + if(p.get_cell(p.elements[i])->is_unit()) { + best_path_labeling[p.elements[i]] = i; + best_path_labeling_inv[i] = p.elements[i]; + } + } + //update_labeling_and_its_inverse(best_path_labeling, best_path_labeling_inv); + /* Reset best path automorphism */ + reset_permutation(best_path_automorphism); + /* Reset best path orbit structure */ + best_path_orbits.reset(); + /* Mark to be the best one and save prefix */ + unsigned int postfix_start = cep.creation_level; + assert(postfix_start < best_path_info.size()); + while(p.get_cell(best_path_info[postfix_start].splitting_element)->is_unit()) { + postfix_start++; + assert(postfix_start < best_path_info.size()); + } + unsigned int postfix_start_cert = best_path_info[postfix_start].certificate_index; + std::vector best_path_temp = best_path_info; + best_path_info.clear(); + for(unsigned int i = 0; i < search_stack.size(); i++) { + TreeNode& ss_info = search_stack[i]; + PathInfo bp_info; + ss_info.cmp_to_best_path = 0; + ss_info.in_best_path = true; + bp_info.splitting_element = ss_info.split_element; + bp_info.certificate_index = ss_info.certificate_index; + bp_info.subcertificate_length = ss_info.subcertificate_length; + bp_info.eqref_hash = ss_info.eqref_hash; + best_path_info.push_back(bp_info); + } + /* Copy the postfix of the previous best path */ + for(unsigned int i = postfix_start; + i < best_path_temp.size(); + i++) + { + best_path_info.push_back(best_path_temp[i]); + best_path_info[best_path_info.size()-1].certificate_index = + best_path_info[best_path_info.size()-2].certificate_index + + best_path_info[best_path_info.size()-2].subcertificate_length; + } + std::vector certificate_best_path_old = certificate_best_path; + certificate_best_path = certificate_current_path; + for(unsigned int i = postfix_start_cert; i < certificate_best_path_old.size(); i++) + certificate_best_path.push_back(certificate_best_path_old[i]); + assert(certificate_best_path.size() == best_path_info.back().certificate_index + best_path_info.back().subcertificate_length); + /* Backtrack to the previous level */ + continue; + } + } + } + } + + /* No backtracking performed, go to next componenet */ + cr_level = cep.next_cr_level; + cr_cep_index = cep.next_cep_index; + } + + /* Check if the current component has been split into + * new non-uniformity subcomponents */ + //if(nucr_find_first_component(cr_level) == true and + // p.nof_discrete_cells() + cr_component_elements < + // cr_cep_stack[cr_cep_index].discrete_cell_limit) + if(nucr_find_first_component(cr_level, cr_component, + cr_component_elements, + next_split_cell) == true and + p.nof_discrete_cells() + cr_component_elements < + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + const unsigned int next_cr_level = + p.cr_split_level(cr_level, cr_component); + CR_CEP cep; + cep.creation_level = search_stack.size(); + cep.discrete_cell_limit = + p.nof_discrete_cells() + cr_component_elements; + cep.next_cr_level = cr_level; + cep.next_cep_index = cr_cep_index; + cep.first_checked = false; + cep.best_checked = false; + cr_cep_index = cr_cep_stack.size(); + cr_cep_stack.push_back(cep); + cr_level = next_cr_level; + } + } + + + /* + * Build the next node info + */ + /* Find the next cell to be splitted */ + if(!next_split_cell) + next_split_cell = find_next_cell_to_be_splitted(p.get_cell(p.elements[current_node.split_cell_first])); + //Partition::Cell * const next_split_cell = find_next_cell_to_be_splitted(p.get_cell(p.elements[current_node.split_cell_first])); + child_node.split_cell_first = next_split_cell->first; + child_node.split_element = TreeNode::SPLIT_START; + child_node.certificate_index = certificate_index; + child_node.partition_bt_point = p.set_backtrack_point(); + child_node.long_prune_redundant.clear(); + child_node.long_prune_begin = current_node.long_prune_begin; + + /* Save component recursion info for backtracking */ + child_node.cr_level = cr_level; + child_node.cr_cep_stack_size = cr_cep_stack.size(); + child_node.cr_cep_index = cr_cep_index; + + search_stack.push_back(child_node); + continue; + } + + /* + * A leaf node not in the first path or equivalent to the first path + */ + + + + if(child_node.cmp_to_best_path > 0) + { + /* + * A new, better representative found + */ + //fprintf(stdout, "Level %u: NEW BEST\n", child_level); fflush(stdout); + stats.nof_canupdates++; + /* + * Update canonical labeling and its inverse + */ + update_labeling_and_its_inverse(best_path_labeling, + best_path_labeling_inv); + /* Reset best path automorphism */ + reset_permutation(best_path_automorphism); + /* Reset best path orbit structure */ + best_path_orbits.reset(); + /* + * Mark the current path to be the best one and save it + */ + const unsigned int base_size = search_stack.size(); + assert(current_level+1 == base_size); + best_path_info.clear(); + for(unsigned int i = 0; i < base_size; i++) { + search_stack[i].cmp_to_best_path = 0; + search_stack[i].in_best_path = true; + PathInfo path_info; + path_info.splitting_element = search_stack[i].split_element; + path_info.certificate_index = search_stack[i].certificate_index; + path_info.subcertificate_length = search_stack[i].subcertificate_length; + path_info.eqref_hash = search_stack[i].eqref_hash; + best_path_info.push_back(path_info); + } + certificate_best_path = certificate_current_path; + /* + * Backtrack to the previous level + */ + continue; + } + + + handle_best_path_automorphism: + /* + * + * Best path automorphism handling + * + */ + { + + /* + * Equal to the previous best path + */ + if(p.is_discrete()) + { +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Verify that the automorphism is correctly built */ + for(unsigned int i = 0; i < N; i++) + assert(best_path_automorphism[i] == + p.elements[best_path_labeling[i]]); +#endif + } + else + { + /* An automorphism that was found before the partition was discrete. + * Set the image of all elements in non-disrete cells accordingly */ + for(Partition::Cell* c = p.first_nonsingleton_cell; c; + c = c->next_nonsingleton) { + for(unsigned int i = c->first; i < c->first+c->length; i++) + if(p.get_cell(p.elements[best_path_labeling[p.elements[i]]])->is_unit()) + best_path_automorphism[p.elements[best_path_labeling[p.elements[i]]]] = p.elements[i]; + else + best_path_automorphism[p.elements[i]] = p.elements[i]; + } + } + +#if defined(BLISS_VERIFY_AUTOMORPHISMS) + /* Verify that it really is an automorphism */ + if(!is_automorphism(best_path_automorphism)) + fatal_error("Best path automorhism validation check failed"); +#endif + + unsigned int gca_level_with_first = 0; + for(unsigned int i = search_stack.size(); i > 0; i--) { + if((int)first_path_info[gca_level_with_first].splitting_element != + search_stack[gca_level_with_first].split_element) + break; + gca_level_with_first++; + } + + unsigned int gca_level_with_best = 0; + for(unsigned int i = search_stack.size(); i > 0; i--) { + if((int)best_path_info[gca_level_with_best].splitting_element != + search_stack[gca_level_with_best].split_element) + break; + gca_level_with_best++; + } + + if(opt_use_long_prune) + { + /* Record automorphism */ + long_prune_add_automorphism(best_path_automorphism); + } + + /* + * Update orbit information + */ + update_orbit_information(best_path_orbits, best_path_automorphism); + + /* + * Update orbit information + */ + const unsigned int nof_old_orbits = first_path_orbits.nof_orbits(); + update_orbit_information(first_path_orbits, best_path_automorphism); + if(nof_old_orbits != first_path_orbits.nof_orbits()) + { + /* Some orbits were merged */ + /* Report automorphism */ + if(report) + report(get_nof_vertices(), best_path_automorphism); + /* Update statistics */ + stats.nof_generators++; + } + + /* + * Compute backjumping level + */ + unsigned int backjumping_level = current_level+1-1; + if(!first_path_orbits.is_minimal_representative(search_stack[gca_level_with_first].split_element)) + { + backjumping_level = gca_level_with_first; + } + else + { + assert(!best_path_orbits.is_minimal_representative(search_stack[gca_level_with_best].split_element)); + backjumping_level = gca_level_with_best; + } + /* Backtrack */ + search_stack.resize(backjumping_level + 1); + continue; + } + + + _INTERNAL_ERROR(); + + + handle_first_path_automorphism: + /* + * + * A first-path automorphism: aut[i] = elements[first_path_labeling[i]] + * + */ + + + if(p.is_discrete()) + { +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Verify that the complete automorphism is correctly built */ + for(unsigned int i = 0; i < N; i++) + assert(first_path_automorphism[i] == + p.elements[first_path_labeling[i]]); +#endif + } + else + { + /* An automorphism that was found before the partition was discrete. + * Set the image of all elements in non-disrete cells accordingly */ + for(Partition::Cell* c = p.first_nonsingleton_cell; c; + c = c->next_nonsingleton) { + for(unsigned int i = c->first; i < c->first+c->length; i++) + if(p.get_cell(p.elements[first_path_labeling[p.elements[i]]])->is_unit()) + first_path_automorphism[p.elements[first_path_labeling[p.elements[i]]]] = p.elements[i]; + else + first_path_automorphism[p.elements[i]] = p.elements[i]; + } + } + +#if defined(BLISS_VERIFY_AUTOMORPHISMS) + /* Verify that it really is an automorphism */ + if(!is_automorphism(first_path_automorphism)) + fatal_error("First path automorphism validation check failed"); +#endif + + if(opt_use_long_prune) + { + long_prune_add_automorphism(first_path_automorphism); + } + + /* + * Update orbit information + */ + update_orbit_information(first_path_orbits, first_path_automorphism); + + /* + * Compute backjumping level + */ + for(unsigned int i = 0; i < search_stack.size(); i++) { + TreeNode& n = search_stack[i]; + if(n.fp_on) { + ; + } else { + n.fp_extendable = TreeNode::YES; + } + } + + /* Report automorphism by calling the user defined hook function */ + if(report) + report(get_nof_vertices(), first_path_automorphism); + /* Update statistics */ + stats.nof_generators++; + continue; + + } /* while(!search_stack.empty()) */ + + + + + /* Free "long prune" technique memory */ + if(opt_use_long_prune) + long_prune_deallocate(); + + /* Release component recursion data in partition */ + if(opt_use_comprec) + p.cr_free(); +} + + + + +void +AbstractGraph::find_automorphisms(Stats& stats, + const std::function& report, + const std::function& terminate) +{ + search(false, stats, report, terminate); + + delete[] first_path_labeling; first_path_labeling = nullptr; + delete[] best_path_labeling; best_path_labeling = nullptr; +} + + +const unsigned int * +AbstractGraph::canonical_form(Stats& stats, + const std::function& report, + const std::function& terminate) +{ + search(true, stats, report, terminate); + + return best_path_labeling; +} + + + + +/*------------------------------------------------------------------------- + * + * Routines for directed graphs + * + *-------------------------------------------------------------------------*/ + +Digraph::Vertex::Vertex() +{ + color = 0; +} + + +Digraph::Vertex::~Vertex() +{ + ; +} + + +void +Digraph::Vertex::add_edge_to(const unsigned int other_vertex) +{ + edges_out.push_back(other_vertex); +} + + +void +Digraph::Vertex::add_edge_from(const unsigned int other_vertex) +{ + edges_in.push_back(other_vertex); +} + + +void +Digraph::Vertex::remove_duplicate_edges(std::vector& tmp) +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Pre-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + for(std::vector::iterator iter = edges_out.begin(); + iter != edges_out.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges_out.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges_out.begin(); + iter != edges_out.end(); + iter++) + { + tmp[*iter] = false; + } + + for(std::vector::iterator iter = edges_in.begin(); + iter != edges_in.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges_in.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges_in.begin(); + iter != edges_in.end(); + iter++) + { + tmp[*iter] = false; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Post-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif +} + + +/** + * Sort the edges entering and leaving the vertex according to + * the vertex number of the other edge end. + * Time complexity: O(e log(e)), where e is the number of edges + * entering/leaving the vertex. + */ +void +Digraph::Vertex::sort_edges() +{ + std::sort(edges_in.begin(), edges_in.end()); + std::sort(edges_out.begin(), edges_out.end()); +} + + + + + +/*------------------------------------------------------------------------- + * + * Constructor and destructor for directed graphs + * + *-------------------------------------------------------------------------*/ + + +Digraph::Digraph(const unsigned int nof_vertices) +{ + vertices.resize(nof_vertices); + sh = shs_flm; +} + + +Digraph::~Digraph() +{ + ; +} + + +unsigned int +Digraph::add_vertex(const unsigned int color) +{ + const unsigned int new_vertex_num = vertices.size(); + vertices.resize(new_vertex_num + 1); + vertices.back().color = color; + return new_vertex_num; +} + + +void +Digraph::add_edge(const unsigned int vertex1, const unsigned int vertex2) +{ + if(vertex1 >= vertices.size() or vertex2 >= vertices.size()) + throw std::runtime_error("out of bounds vertex number"); + //assert(vertex1 < get_nof_vertices()); + //assert(vertex2 < get_nof_vertices()); + vertices[vertex1].add_edge_to(vertex2); + vertices[vertex2].add_edge_from(vertex1); +} + + +void +Digraph::change_color(const unsigned int vertex, const unsigned int new_color) +{ + assert(vertex < get_nof_vertices()); + vertices[vertex].color = new_color; +} + + +void +Digraph::sort_edges() +{ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + vertices[i].sort_edges(); +} + + +int +Digraph::cmp(Digraph& other) +{ + /* Compare the numbers of vertices */ + if(get_nof_vertices() < other.get_nof_vertices()) + return -1; + if(get_nof_vertices() > other.get_nof_vertices()) + return 1; + /* Compare vertex colors */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].color < other.vertices[i].color) + return -1; + if(vertices[i].color > other.vertices[i].color) + return 1; + } + /* Compare vertex degrees */ + remove_duplicate_edges(); + other.remove_duplicate_edges(); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].nof_edges_in() < other.vertices[i].nof_edges_in()) + return -1; + if(vertices[i].nof_edges_in() > other.vertices[i].nof_edges_in()) + return 1; + if(vertices[i].nof_edges_out() < other.vertices[i].nof_edges_out()) + return -1; + if(vertices[i].nof_edges_out() > other.vertices[i].nof_edges_out()) + return 1; + } + /* Compare edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v1 = vertices[i]; + Vertex& v2 = other.vertices[i]; + v1.sort_edges(); + v2.sort_edges(); + std::vector::const_iterator ei1 = v1.edges_in.begin(); + std::vector::const_iterator ei2 = v2.edges_in.begin(); + while(ei1 != v1.edges_in.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + ei1 = v1.edges_out.begin(); + ei2 = v2.edges_out.begin(); + while(ei1 != v1.edges_out.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + } + return 0; +} + + + + +Digraph* +Digraph::permute(const std::vector& perm) const +{ + Digraph* const g = new Digraph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + g->change_color(perm[i], v.color); + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + g->add_edge(perm[i], perm[*ei]); + } + } + g->sort_edges(); + return g; +} + + +Digraph* +Digraph::permute(const unsigned int* const perm) const +{ + Digraph* const g = new Digraph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex &v = vertices[i]; + g->change_color(perm[i], v.color); + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + g->add_edge(perm[i], perm[*ei]); + } + } + g->sort_edges(); + return g; +} + + +void +Digraph::remove_duplicate_edges() +{ + std::vector tmp(get_nof_vertices(), false); + + for(std::vector::iterator vi = vertices.begin(); + vi != vertices.end(); + vi++) + { +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + (*vi).remove_duplicate_edges(tmp); + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Get a hash value for the graph. + * + *-------------------------------------------------------------------------*/ + +unsigned int +Digraph::get_hash() +{ + remove_duplicate_edges(); + sort_edges(); + + UintSeqHash h; + + h.update(get_nof_vertices()); + + /* Hash the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + h.update(vertices[i].color); + } + + /* Hash the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + h.update(i); + h.update(*ei); + } + } + + return h.get_value(); +} + + +/*------------------------------------------------------------------------- + * + * Partition independent invariants + * + *-------------------------------------------------------------------------*/ + +unsigned int +Digraph::vertex_color_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].color; +} + +unsigned int +Digraph::indegree_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].nof_edges_in(); +} + +unsigned int +Digraph::outdegree_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].nof_edges_out(); +} + +unsigned int +Digraph::selfloop_invariant(const Digraph* const g, const unsigned int vnum) +{ + /* Quite inefficient but luckily not in the critical path */ + const Vertex& v = g->vertices[vnum]; + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + if(*ei == vnum) + return 1; + } + return 0; +} + + + + + +/*------------------------------------------------------------------------- + * + * Refine the partition p according to a partition independent invariant + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::refine_according_to_invariant(unsigned int (*inv)(const Digraph* const g, + const unsigned int v)) +{ + bool refined = false; + + for(Partition::Cell* cell = p.first_nonsingleton_cell; cell; ) + { + + Partition::Cell* const next_cell = cell->next_nonsingleton; + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + unsigned int ival = inv(this, *ep); + p.invariant_values[*ep] = ival; + if(ival > cell->max_ival) { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) { + cell->max_ival_count++; + } + } + Partition::Cell* const last_new_cell = p.zplit_cell(cell, true); + refined |= (last_new_cell != cell); + cell = next_cell; + } + + return refined; +} + + + + + +/*------------------------------------------------------------------------- + * + * Split the neighbourhood of a cell according to the equitable invariant + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::split_neighbourhood_of_cell(Partition::Cell* const cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(cell->first); + eqref_hash.update(cell->length); + } + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j != 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) + neighbour_heap.insert(neighbour_cell->first); + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + if(cell->is_in_splitting_queue()) + { + return false; + } + + + ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) + { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) + neighbour_heap.insert(neighbour_cell->first); + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival = 0; + neighbour_cell->max_ival_count = 0; + p.clear_ivs(neighbour_cell); + } + if(opt_use_failure_recording and was_equal_to_first) + { + for(unsigned int i = p.splitting_queue.size(); i > 0; i--) + { + Partition::Cell* const cell = p.splitting_queue.pop_front(); + rest.update(cell->first); + rest.update(cell->length); + p.splitting_queue.push_back(cell); + } + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + + return true; +} + + +bool +Digraph::split_neighbourhood_of_unit_cell(Partition::Cell* const unit_cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(0x87654321); + eqref_hash.update(unit_cell->first); + eqref_hash.update(1); + } + + const Vertex& v = vertices[p.elements[unit_cell->first]]; + + /* + * Phase 1 + * Refine neighbours according to the edges that leave the vertex v + */ + std::vector::const_iterator ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + /* Remember neighbour in order to generate certificate */ + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int* const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(neighbour_cell->first == start); + if(neighbour_cell->is_unit()) { + assert(neighbour_cell->max_ival_count == 0); + } else { + assert(neighbour_cell->max_ival_count > 0); + assert(neighbour_cell->max_ival_count <= neighbour_cell->length); + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + + Partition::Cell* const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int* ep = p.elements + new_cell->first; + unsigned int* const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) + { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + /* Update hash */ + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to have refinement to equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, unit_cell->first, i); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + /* + * Phase 2 + * Refine neighbours according to the edges that enter the vertex v + */ + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int* const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(neighbour_cell->first == start); + if(neighbour_cell->is_unit()) { + assert(neighbour_cell->max_ival_count == 0); + } else { + assert(neighbour_cell->max_ival_count > 0); + assert(neighbour_cell->max_ival_count <= neighbour_cell->length); + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + Partition::Cell* const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int* ep = p.elements + new_cell->first; + unsigned int* const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to have refinement to equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, i, unit_cell->first); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival_count = 0; + } + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::is_equitable() const +{ + const unsigned int N = get_nof_vertices(); + if(N == 0) + return true; + + std::vector first_count = std::vector(N, 0); + std::vector other_count = std::vector(N, 0); + + /* + * Check equitabledness w.r.t. outgoing edges + */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int* ep = p.elements + cell->first; + const Vertex& first_vertex = vertices[*ep++]; + + /* Count outgoing edges of the first vertex for cells */ + for(std::vector::const_iterator ei = + first_vertex.edges_out.begin(); + ei != first_vertex.edges_out.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare outgoing edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges_out.begin(); + ei != vertex.edges_out.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + + + /* + * Check equitabledness w.r.t. incoming edges + */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int* ep = p.elements + cell->first; + const Vertex& first_vertex = vertices[*ep++]; + + /* Count incoming edges of the first vertex for cells */ + for(std::vector::const_iterator ei = + first_vertex.edges_in.begin(); + ei != first_vertex.edges_in.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare incoming edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges_in.begin(); + ei != vertex.edges_in.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Build the initial equitable partition + * + *-------------------------------------------------------------------------*/ + +void +Digraph::make_initial_equitable_partition() +{ + refine_according_to_invariant(&vertex_color_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&selfloop_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&outdegree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&indegree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_to_equitable(); + //p.print_signature(stderr); fprintf(stderr, "\n"); +} + + + + + +/*------------------------------------------------------------------------- + * + * Find the next cell to be splitted + * + *-------------------------------------------------------------------------*/ + +Partition::Cell* +Digraph::find_next_cell_to_be_splitted(Partition::Cell* cell) +{ + switch(sh) { + case shs_f: return sh_first(); + case shs_fs: return sh_first_smallest(); + case shs_fl: return sh_first_largest(); + case shs_fm: return sh_first_max_neighbours(); + case shs_fsm: return sh_first_smallest_max_neighbours(); + case shs_flm: return sh_first_largest_max_neighbours(); + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first() +{ + Partition::Cell* best_cell = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + best_cell = cell; + break; + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first_smallest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = UINT_MAX; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length < best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first_largest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length > best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + int value = 0; + const Vertex &v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if(value > best_value) + { + best_value = value; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_smallest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = UINT_MAX; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + int value = 0; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell * const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell * const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if((value > best_value) or + (value == best_value and cell->length < best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_largest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = 0; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + int value = 0; + const Vertex &v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if((value > best_value) || + (value == best_value && cell->length > best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + + + + + + +/*------------------------------------------------------------------------ + * + * Initialize the certificate size and memory + * + *-------------------------------------------------------------------------*/ + +void +Digraph::initialize_certificate() +{ + certificate_index = 0; + certificate_current_path.clear(); + certificate_first_path.clear(); + certificate_best_path.clear(); +} + + + +/* + * Check whether perm is an automorphism. + * Slow, mainly for debugging and validation purposes. + */ +bool +Digraph::is_automorphism(unsigned int* const perm) const +{ + std::set > edges1; + std::set > edges2; + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + const Vertex& v2 = vertices[perm[i]]; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_in.cbegin(); + ei != v1.edges_in.cend(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_in.cbegin(); + ei != v2.edges_in.cend(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_out.cbegin(); + ei != v1.edges_out.cend(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_out.cbegin(); + ei != v2.edges_out.cend(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + } + + return true; +} + +bool +Digraph::is_automorphism(const std::vector& perm) const +{ + + if(!(perm.size() == get_nof_vertices() and is_permutation(perm))) + return false; + + std::set > edges1; + std::set > edges2; + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + const Vertex& v2 = vertices[perm[i]]; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_in.begin(); + ei != v1.edges_in.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_in.begin(); + ei != v2.edges_in.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_out.begin(); + ei != v1.edges_out.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_out.begin(); + ei != v2.edges_out.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + +bool +Digraph::nucr_find_first_component(const unsigned int level) +{ + + cr_component.clear(); + cr_component_elements = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + /* The component is discrete, return false */ + if(!first_cell) + return false; + + std::vector component; + first_cell->max_ival = 1; + component.push_back(first_cell); + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + } + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + cell->max_ival = 0; + cr_component.push_back(cell->first); + cr_component_elements += cell->length; + } + + /* + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)cr_component.size(), cr_component_elements); + fflush(verbstr); + } + */ + + return true; +} + + + + + +bool +Digraph::nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) +{ + + component.clear(); + component_elements = 0; + sh_return = 0; + unsigned int sh_first = 0; + unsigned int sh_size = 0; + unsigned int sh_nuconn = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + if(!first_cell) + { + /* The component is discrete, return false */ + return false; + } + + std::vector comp; + KStack neighbours; + neighbours.init(get_nof_vertices()); + + first_cell->max_ival = 1; + comp.push_back(first_cell); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + + unsigned int nuconn = 1; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + /*| Phase 1: outgoing edges */ + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + /*| Phase 2: incoming edges */ + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + /*| Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + /*| Phase 3: splitting heuristics */ + switch(sh) { + case shs_f: + if(sh_return == 0 or + cell->first <= sh_first) { + sh_return = cell; + sh_first = cell->first; + } + break; + case shs_fs: + if(sh_return == 0 or + cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fl: + if(sh_return == 0 or + cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_nuconn = nuconn; + } + break; + case shs_fsm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + case shs_flm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } + } + assert(sh_return); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + cell->max_ival = 0; + component.push_back(cell->first); + component_elements += cell->length; + } + + /* + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)component.size(), component_elements); + fflush(verbstr); + } + */ + + return true; +} + + + + +/*------------------------------------------------------------------------- + * + * Routines for undirected graphs + * + *-------------------------------------------------------------------------*/ + +Graph::Vertex::Vertex() +{ + color = 0; +} + + +Graph::Vertex::~Vertex() +{ + ; +} + + +void +Graph::Vertex::add_edge(const unsigned int other_vertex) +{ + edges.push_back(other_vertex); +} + + +void +Graph::Vertex::remove_duplicate_edges(std::vector& tmp) +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Pre-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + for(std::vector::iterator iter = edges.begin(); + iter != edges.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges.begin(); + iter != edges.end(); + iter++) + { + tmp[*iter] = false; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Post-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif +} + + +/** + * Sort the edges leaving the vertex according to + * the vertex number of the other edge end. + * Time complexity: O(e log(e)), where e is the number of edges + * leaving the vertex. + */ +void +Graph::Vertex::sort_edges() +{ + std::sort(edges.begin(), edges.end()); +} + + + +/*------------------------------------------------------------------------- + * + * Constructor and destructor for undirected graphs + * + *-------------------------------------------------------------------------*/ + + +Graph::Graph(const unsigned int nof_vertices) +{ + vertices.resize(nof_vertices); + sh = shs_flm; +} + + +Graph::~Graph() +{ + ; +} + + +unsigned int +Graph::add_vertex(const unsigned int color) +{ + const unsigned int vertex_num = vertices.size(); + vertices.resize(vertex_num + 1); + vertices.back().color = color; + return vertex_num; +} + + +void +Graph::add_edge(const unsigned int vertex1, const unsigned int vertex2) +{ + //fprintf(stderr, "(%u,%u) ", vertex1, vertex2); + if(vertex1 >= vertices.size() or vertex2 >= vertices.size()) + throw std::runtime_error("out of bounds vertex number"); + vertices[vertex1].add_edge(vertex2); + vertices[vertex2].add_edge(vertex1); +} + + +void +Graph::change_color(const unsigned int vertex, const unsigned int color) +{ + vertices[vertex].color = color; +} + + +void +Graph::sort_edges() +{ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + vertices[i].sort_edges(); +} + + +int +Graph::cmp(Graph& other) +{ + /* Compare the numbers of vertices */ + if(get_nof_vertices() < other.get_nof_vertices()) + return -1; + if(get_nof_vertices() > other.get_nof_vertices()) + return 1; + /* Compare vertex colors */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].color < other.vertices[i].color) + return -1; + if(vertices[i].color > other.vertices[i].color) + return 1; + } + /* Compare vertex degrees */ + remove_duplicate_edges(); + other.remove_duplicate_edges(); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].nof_edges() < other.vertices[i].nof_edges()) + return -1; + if(vertices[i].nof_edges() > other.vertices[i].nof_edges()) + return 1; + } + /* Compare edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v1 = vertices[i]; + Vertex &v2 = other.vertices[i]; + v1.sort_edges(); + v2.sort_edges(); + std::vector::const_iterator ei1 = v1.edges.begin(); + std::vector::const_iterator ei2 = v2.edges.begin(); + while(ei1 != v1.edges.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + } + return 0; +} + + +Graph* +Graph::permute(const std::vector& perm) const +{ +#if defined(BLISS_CONSISTENCY_CHECKS) +#endif + + Graph* const g = new Graph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + Vertex& permuted_v = g->vertices[perm[i]]; + permuted_v.color = v.color; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_v = *ei; + permuted_v.add_edge(perm[dest_v]); + } + permuted_v.sort_edges(); + } + return g; +} + +Graph* +Graph::permute(const unsigned int* perm) const +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + Graph* const g = new Graph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + Vertex& permuted_v = g->vertices[perm[i]]; + permuted_v.color = v.color; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_v = *ei; + permuted_v.add_edge(perm[dest_v]); + } + permuted_v.sort_edges(); + } + return g; +} + + +/*------------------------------------------------------------------------- + * + * Get a hash value for the graph. + * + *-------------------------------------------------------------------------*/ + +unsigned int +Graph::get_hash() +{ + remove_duplicate_edges(); + sort_edges(); + + UintSeqHash h; + + h.update(get_nof_vertices()); + + /* Hash the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + h.update(vertices[i].color); + } + + /* Hash the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_i = *ei; + if(dest_i < i) + continue; + h.update(i); + h.update(dest_i); + } + } + + return h.get_value(); +} + + + + + +void +Graph::remove_duplicate_edges() +{ + std::vector tmp(vertices.size(), false); + + for(std::vector::iterator vi = vertices.begin(); + vi != vertices.end(); + vi++) + { +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + (*vi).remove_duplicate_edges(tmp); + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Partition independent invariants + * + *-------------------------------------------------------------------------*/ + +/* + * Return the color of the vertex. + * Time complexity: O(1) + */ +unsigned int +Graph::vertex_color_invariant(const Graph* const g, const unsigned int v) +{ + return g->vertices[v].color; +} + +/* + * Return the degree of the vertex. + * Time complexity: O(1) + */ +unsigned int +Graph::degree_invariant(const Graph* const g, const unsigned int v) +{ + return g->vertices[v].nof_edges(); +} + +/* + * Return 1 if the vertex v has a self-loop, 0 otherwise + * Time complexity: O(E_v), where E_v is the number of edges leaving v + */ +unsigned int +Graph::selfloop_invariant(const Graph* const g, const unsigned int v) +{ + const Vertex& vertex = g->vertices[v]; + for(std::vector::const_iterator ei = vertex.edges.begin(); + ei != vertex.edges.end(); + ei++) + { + if(*ei == v) + return 1; + } + return 0; +} + + + +/*------------------------------------------------------------------------- + * + * Refine the partition p according to a partition independent invariant + * + *-------------------------------------------------------------------------*/ + +bool +Graph::refine_according_to_invariant(unsigned int (*inv)(const Graph* const g, + const unsigned int v)) +{ + bool refined = false; + + for(Partition::Cell* cell = p.first_nonsingleton_cell; cell; ) + { + + Partition::Cell* const next_cell = cell->next_nonsingleton; + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = inv(this, *ep); + p.invariant_values[*ep] = ival; + if(ival > cell->max_ival) + { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) + { + cell->max_ival_count++; + } + } + Partition::Cell* const last_new_cell = p.zplit_cell(cell, true); + refined |= (last_new_cell != cell); + cell = next_cell; + } + + return refined; +} + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Split the neighbourhood of a cell according to the equitable invariant + * + *-------------------------------------------------------------------------*/ + +bool +Graph::split_neighbourhood_of_cell(Partition::Cell* const cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(cell->first); + eqref_hash.update(cell->length); + } + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j != 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell * const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) + { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) { + neighbour_heap.insert(neighbour_cell->first); + } + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival = 0; + neighbour_cell->max_ival_count = 0; + p.clear_ivs(neighbour_cell); + } + if(opt_use_failure_recording and was_equal_to_first) + { + for(unsigned int i = p.splitting_queue.size(); i > 0; i--) + { + Partition::Cell* const cell = p.splitting_queue.pop_front(); + rest.update(cell->first); + rest.update(cell->length); + p.splitting_queue.push_back(cell); + } + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + + return true; +} + + + +bool +Graph::split_neighbourhood_of_unit_cell(Partition::Cell* const unit_cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(0x87654321); + eqref_hash.update(unit_cell->first); + eqref_hash.update(1); + } + + const Vertex& v = vertices[p.elements[unit_cell->first]]; + + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell * const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + /* Remember neighbour in order to generate certificate */ + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int * const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(neighbour_cell->is_unit()) { + } else { + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + Partition::Cell * const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int *ep = p.elements + new_cell->first; + unsigned int * const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) + { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + /* Update hash */ + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to ensure refinement into equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + /* neighbour_cell->length == 1 || + neighbour_cell->max_ival_count == neighbour_cell->length */ + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, unit_cell->first, i); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival_count = 0; + } + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + return true; +} + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + * + *-------------------------------------------------------------------------*/ + +bool Graph::is_equitable() const +{ + const unsigned int N = get_nof_vertices(); + if(N == 0) + return true; + + std::vector first_count = std::vector(N, 0); + std::vector other_count = std::vector(N, 0); + + for(Partition::Cell *cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int *ep = p.elements + cell->first; + const Vertex &first_vertex = vertices[*ep++]; + + /* Count how many edges lead from the first vertex to + * the neighbouring cells */ + for(std::vector::const_iterator ei = + first_vertex.edges.begin(); + ei != first_vertex.edges.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare to the edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges.begin(); + ei != vertex.edges.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Build the initial equitable partition + * + *-------------------------------------------------------------------------*/ + +void Graph::make_initial_equitable_partition() +{ + refine_according_to_invariant(&vertex_color_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&selfloop_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(°ree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_to_equitable(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + +} + + + + + + + +/*------------------------------------------------------------------------- + * + * Find the next cell to be splitted + * + *-------------------------------------------------------------------------*/ + + +Partition::Cell* +Graph::find_next_cell_to_be_splitted(Partition::Cell* cell) +{ + switch(sh) { + case shs_f: return sh_first(); + case shs_fs: return sh_first_smallest(); + case shs_fl: return sh_first_largest(); + case shs_fm: return sh_first_max_neighbours(); + case shs_fsm: return sh_first_smallest_max_neighbours(); + case shs_flm: return sh_first_largest_max_neighbours(); + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first() +{ + Partition::Cell* best_cell = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + best_cell = cell; + break; + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first_smallest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = UINT_MAX; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length < best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first_largest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length > best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if(value > best_value) + { + best_value = value; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_smallest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = UINT_MAX; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if((value > best_value) or + (value == best_value and cell->length < best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_largest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = 0; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if((value > best_value) or + (value == best_value and cell->length > best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + + + + + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Initialize the certificate size and memory + * + *-------------------------------------------------------------------------*/ + +void +Graph::initialize_certificate() +{ + certificate_index = 0; + certificate_current_path.clear(); + certificate_first_path.clear(); + certificate_best_path.clear(); +} + + + + + +/*------------------------------------------------------------------------- + * + * Check whether perm is an automorphism. + * Slow, mainly for debugging and validation purposes. + * + *-------------------------------------------------------------------------*/ + +bool +Graph::is_automorphism(unsigned int* const perm) const +{ + std::set > edges1; + std::set > edges2; + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges.cbegin(); + ei != v1.edges.cend(); + ei++) + edges1.insert(perm[*ei]); + + const Vertex& v2 = vertices[perm[i]]; + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges.cbegin(); + ei != v2.edges.cend(); + ei++) + edges2.insert(*ei); + + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + +bool +Graph::is_automorphism(const std::vector& perm) const +{ + + if(!(perm.size() == get_nof_vertices() and is_permutation(perm))) + return false; + + std::set > edges1; + std::set > edges2; + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges.begin(); + ei != v1.edges.end(); + ei++) + edges1.insert(perm[*ei]); + + const Vertex& v2 = vertices[perm[i]]; + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges.begin(); + ei != v2.edges.end(); + ei++) + edges2.insert(*ei); + + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + + + + +bool +Graph::nucr_find_first_component(const unsigned int level) +{ + + cr_component.clear(); + cr_component_elements = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + /* The component is discrete, return false */ + if(!first_cell) + return false; + + std::vector component; + first_cell->max_ival = 1; + component.push_back(first_cell); + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + } + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + cell->max_ival = 0; + cr_component.push_back(cell->first); + cr_component_elements += cell->length; + } + + /* + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)cr_component.size(), cr_component_elements); + fflush(verbstr); + } + */ + + return true; +} + + + + +bool +Graph::nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) +{ + + component.clear(); + component_elements = 0; + sh_return = 0; + unsigned int sh_first = 0; + unsigned int sh_size = 0; + unsigned int sh_nuconn = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + if(!first_cell) + { + /* The component is discrete, return false */ + return false; + } + + std::vector comp; + KStack neighbours; + neighbours.init(get_nof_vertices()); + + first_cell->max_ival = 1; + comp.push_back(first_cell); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + unsigned int nuconn = 1; + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + //neighbours.pop_back(); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + switch(sh) { + case shs_f: + if(sh_return == 0 or + cell->first <= sh_first) { + sh_return = cell; + sh_first = cell->first; + } + break; + case shs_fs: + if(sh_return == 0 or + cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fl: + if(sh_return == 0 or + cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_nuconn = nuconn; + } + break; + case shs_fsm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + case shs_flm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } + } + assert(sh_return); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + cell->max_ival = 0; + component.push_back(cell->first); + component_elements += cell->length; + } + + /* + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)component.size(), component_elements); + fflush(verbstr); + } + */ + + return true; +} + + + + +} diff --git a/src/isomorphism/bliss/graph.hh b/src/isomorphism/bliss/graph.hh new file mode 100644 index 0000000..5a60389 --- /dev/null +++ b/src/isomorphism/bliss/graph.hh @@ -0,0 +1,876 @@ +#ifndef BLISS_GRAPH_HH +#define BLISS_GRAPH_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +/** + * \namespace bliss + * The namespace bliss contains all the classes and functions of the bliss + * tool except for the C programming language API. + */ +namespace bliss { + class AbstractGraph; +} + +// #include +#include +#include +#include "stats.hh" +#include "kstack.hh" +#include "kqueue.hh" +#include "heap.hh" +#include "orbit.hh" +#include "partition.hh" +#include "uintseqhash.hh" + +namespace bliss { + + + + + +/** + * \brief An abstract base class for different types of graphs. + */ +class AbstractGraph +{ + friend class Partition; + +public: + AbstractGraph(); + virtual ~AbstractGraph(); + +#if 0 + /** + * Set the verbose output level for the algorithms. + * \param level the level of verbose output, 0 means no verbose output + */ + void set_verbose_level(const unsigned int level); + + /** + * Set the file stream for the verbose output. + * \param fp the file stream; if null, no verbose output is written + */ + void set_verbose_file(FILE * const fp); +#endif + + /** + * Add a new vertex with color \a color in the graph and return its index. + */ + virtual unsigned int add_vertex(const unsigned int color = 0) = 0; + + /** + * Add an edge between vertices \a source and \a target. + * Duplicate edges between vertices are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + virtual void add_edge(const unsigned int source, const unsigned int target) = 0; + + /** + * Change the color of the vertex \a vertex to \a color. + */ + virtual void change_color(const unsigned int vertex, const unsigned int color) = 0; + + /** + * Check whether \a perm is an automorphism of this graph. + * Unoptimized, mainly for debugging purposes. + */ + virtual bool is_automorphism(const std::vector& perm) const = 0; + + + /** Activate/deactivate failure recording. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate failure recording, deactivate otherwise + */ + void set_failure_recording(const bool active) {assert(!in_search); opt_use_failure_recording = active;} + + /** Activate/deactivate component recursion. + * The choice affects the computed canonical labelings; + * therefore, if you want to compare whether two graphs are isomorphic by + * computing and comparing (for equality) their canonical versions, + * be sure to use the same choice for both graphs. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate component recursion, deactivate otherwise + */ + void set_component_recursion(const bool active) {assert(!in_search); opt_use_comprec = active;} + + + + /** + * Return the number of vertices in the graph. + */ + virtual unsigned int get_nof_vertices() const = 0; + + /** + * Return a new graph that is the result of applying the permutation \a perm + * to this graph. This graph is not modified. + * \a perm must contain N=this.get_nof_vertices() elements and be a bijection + * on {0,1,...,N-1}, otherwise the result is undefined or a segfault. + */ + virtual AbstractGraph* permute(const unsigned int* const perm) const = 0; + virtual AbstractGraph* permute(const std::vector& perm) const = 0; + + /** + * Find a set of generators for the automorphism group of the graph. + * The function \a report (if non-null) is called each time a new generator + * for the automorphism group is found. + * The first argument \a n for the function + * is the length of the automorphism (equal to get_nof_vertices()), and + * the second argument \a aut is the automorphism + * (a bijection on {0,...,get_nof_vertices()-1}). + * The memory for the automorphism \a aut will be invalidated immediately + * after the return from the \a report function; + * if you want to use the automorphism later, you have to take a copy of it. + * Do not call any member functions from the \a report function. + * + * The search statistics are copied in \a stats. + * + * If the \a terminate function argument is given, + * it is called in each search tree node: if the function returns true, + * then the search is terminated and thus not all the automorphisms + * may have been generated. + * The \a terminate function may be used to limit the time spent in bliss + * in case the graph is too difficult under the available time constraints. + * If used, keep the function simple to evaluate so that + * it does not consume too much time. + */ + void find_automorphisms(Stats& stats, + const std::function& report = nullptr, + const std::function& terminate = nullptr); + + /** + * Otherwise the same as find_automorphisms() except that + * a canonical labeling of the graph (a bijection on + * {0,...,get_nof_vertices()-1}) is returned. + * The memory allocated for the returned canonical labeling will remain + * valid only until the next call to a member function with the exception + * that constant member functions (for example, bliss::Graph::permute()) can + * be called without invalidating the labeling. + * To compute the canonical version of an undirected graph, call this + * function and then bliss::Graph::permute() with the returned canonical + * labeling. + * Note that the computed canonical version may depend on the applied version + * of bliss as well as on some other options (for instance, the splitting + * heuristic selected with bliss::Graph::set_splitting_heuristic()). + * + * If the \a terminate function argument is given, + * it is called in each search tree node: if the function returns true, + * then the search is terminated and thus (i) not all the automorphisms + * may have been generated and (ii) the returned labeling may not + * be canonical. + * The \a terminate function may be used to limit the time spent in bliss + * in case the graph is too difficult under the available time constraints. + * If used, keep the function simple to evaluate so that + * it does not consume too much time. + */ + const unsigned int* canonical_form(Stats& stats, + const std::function& report = nullptr, + const std::function& terminate = nullptr); + + /** + * Get a hash value for the graph. + * \return the hash value + */ + virtual unsigned int get_hash() = 0; + + /** + * Disable/enable the "long prune" method. + * The choice affects the computed canonical labelings; + * therefore, if you want to compare whether two graphs are isomorphic by + * computing and comparing (for equality) their canonical versions, + * be sure to use the same choice for both graphs. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate "long prune", deactivate otherwise + */ + void set_long_prune_activity(const bool active) { + assert(!in_search); + opt_use_long_prune = active; + } + + + +protected: + /** \internal + * How much verbose output is produced (0 means none) */ + /* unsigned int verbose_level; */ + /** \internal + * The output stream for verbose output. */ + /* FILE *verbstr; */ +protected: + + /** \internal + * The ordered partition used in the search algorithm. */ + Partition p; + + /** \internal + * Whether the search for automorphisms and a canonical labeling is + * in progress. + */ + bool in_search; + + /** \internal + * Is failure recording in use? + */ + bool opt_use_failure_recording; + /* The "tree-specific" invariant value for the point when current path + * got different from the first path */ + unsigned int failure_recording_fp_deviation; + + /** \internal + * Is component recursion in use? + */ + bool opt_use_comprec; + + + unsigned int refine_current_path_certificate_index; + bool refine_compare_certificate = false; + bool refine_equal_to_first = false; + unsigned int refine_first_path_subcertificate_end; + int refine_cmp_to_best; + unsigned int refine_best_path_subcertificate_end; + + static const unsigned int CERT_SPLIT = 0; //UINT_MAX; + static const unsigned int CERT_EDGE = 1; //UINT_MAX-1; + /** \internal + * Add a triple (v1,v2,v3) in the certificate. + * May modify refine_equal_to_first and refine_cmp_to_best. + * May also update eqref_hash and failure_recording_fp_deviation. */ + void cert_add(const unsigned int v1, + const unsigned int v2, + const unsigned int v3); + + /** \internal + * Add a redundant triple (v1,v2,v3) in the certificate. + * Can also just dicard the triple. + * May modify refine_equal_to_first and refine_cmp_to_best. + * May also update eqref_hash and failure_recording_fp_deviation. */ + void cert_add_redundant(const unsigned int x, + const unsigned int y, + const unsigned int z); + + /**\internal + * Is the long prune method in use? + */ + bool opt_use_long_prune; + /**\internal + * Maximum amount of memory (in megabytes) available for + * the long prune method + */ + static const unsigned int long_prune_options_max_mem = 50; + /**\internal + * Maximum amount of automorphisms stored for the long prune method; + * less than this is stored if the memory limit above is reached first + */ + static const unsigned int long_prune_options_max_stored_auts = 100; + + unsigned int long_prune_max_stored_autss; + std::vector *> long_prune_fixed; + std::vector *> long_prune_mcrs; + std::vector long_prune_temp; + unsigned int long_prune_begin; + unsigned int long_prune_end; + /** \internal + * Initialize the "long prune" data structures. + */ + void long_prune_init(); + /** \internal + * Release the memory allocated for "long prune" data structures. + */ + void long_prune_deallocate(); + void long_prune_add_automorphism(const unsigned int *aut); + std::vector& long_prune_get_fixed(const unsigned int index); + std::vector& long_prune_allocget_fixed(const unsigned int index); + std::vector& long_prune_get_mcrs(const unsigned int index); + std::vector& long_prune_allocget_mcrs(const unsigned int index); + /** \internal + * Swap the i:th and j:th stored automorphism information; + * i and j must be "in window, i.e. in [long_prune_begin,long_prune_end[ + */ + void long_prune_swap(const unsigned int i, const unsigned int j); + + /* + * Data structures and routines for refining the partition p into equitable + */ + Heap neighbour_heap; + virtual bool split_neighbourhood_of_unit_cell(Partition::Cell * const) = 0; + virtual bool split_neighbourhood_of_cell(Partition::Cell * const) = 0; + void refine_to_equitable(); + void refine_to_equitable(Partition::Cell * const unit_cell); + void refine_to_equitable(Partition::Cell * const unit_cell1, + Partition::Cell * const unit_cell2); + + + /** \internal + * \return false if it was detected that the current certificate + * is different from the first and/or best (whether this is checked + * depends on in_search and refine_compare_certificate flags. + */ + bool do_refine_to_equitable(); + + unsigned int eqref_max_certificate_index; + /** \internal + * Whether eqref_hash is updated during equitable refinement process. + */ + bool compute_eqref_hash; + UintSeqHash eqref_hash; + + + /** \internal + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + */ + virtual bool is_equitable() const = 0; + + unsigned int *first_path_labeling; + unsigned int *first_path_labeling_inv; + Orbit first_path_orbits; + unsigned int *first_path_automorphism; + + unsigned int *best_path_labeling; + unsigned int *best_path_labeling_inv; + Orbit best_path_orbits; + unsigned int *best_path_automorphism; + + void update_labeling(unsigned int * const lab); + void update_labeling_and_its_inverse(unsigned int * const lab, + unsigned int * const lab_inv); + void update_orbit_information(Orbit &o, const unsigned int *perm); + + void reset_permutation(unsigned int *perm); + + /* Mainly for debugging purposes */ + virtual bool is_automorphism(unsigned int* const perm) const = 0; + + std::vector certificate_current_path; + std::vector certificate_first_path; + std::vector certificate_best_path; + + unsigned int certificate_index; + virtual void initialize_certificate() = 0; + + virtual void remove_duplicate_edges() = 0; + virtual void make_initial_equitable_partition() = 0; + virtual Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell) = 0; + + + /** \struct PathInfo + * + * A structure for holding first, current, and best path information. + */ + typedef struct { + unsigned int splitting_element; + unsigned int certificate_index; + unsigned int subcertificate_length; + UintSeqHash eqref_hash; + } PathInfo; + + void search(const bool canonical, Stats &stats, + const std::function& report_function = nullptr, + const std::function& terminate = nullptr); + + + void (*report_hook)(void *user_param, + unsigned int n, + const unsigned int *aut); + void *report_user_param; + + + /* + * + * Nonuniform component recursion (NUCR) + * + */ + + /* The currently traversed component */ + unsigned int cr_level; + + /** @internal @class CR_CEP + * The "Component End Point" data structure + */ + class CR_CEP { + public: + /** At which level in the search was this CEP created */ + unsigned int creation_level; + /** The current component has been fully traversed when the partition has + * this many discrete cells left */ + unsigned int discrete_cell_limit; + /** The component to be traversed after the current one */ + unsigned int next_cr_level; + /** The next component end point */ + unsigned int next_cep_index; + bool first_checked; + bool best_checked; + }; + /** \internal + * A stack for storing Component End Points + */ + std::vector cr_cep_stack; + + /** \internal + * Find the first non-uniformity component at the component recursion + * level \a level. + * The component is stored in \a cr_component. + * If no component is found, \a cr_component is empty. + * Returns false if all the cells in the component recursion level \a level + * were discrete. + * Modifies the max_ival and max_ival_count fields of Partition:Cell + * (assumes that they are 0 when called and + * quarantees that they are 0 when returned). + */ + virtual bool nucr_find_first_component(const unsigned int level) = 0; + virtual bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) = 0; + /** \internal + * The non-uniformity component found by nucr_find_first_component() + * is stored here. + */ + std::vector cr_component; + /** \internal + * The number of vertices in the component \a cr_component + */ + unsigned int cr_component_elements; + + + + + + +}; + + + +/** + * \brief The class for undirected, vertex colored graphs. + * + * Multiple edges between vertices are not allowed (i.e., are ignored). + */ +class Graph : public AbstractGraph +{ +public: + /** + * The possible splitting heuristics. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + typedef enum { + /** First non-unit cell. + * Very fast but may result in large search spaces on difficult graphs. + * Use for large but easy graphs. */ + shs_f = 0, + /** First smallest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fs, + /** First largest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fl, + /** First maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fm, + /** First smallest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fsm, + /** First largest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_flm + } SplittingHeuristic; + +protected: + class Vertex { + public: + Vertex(); + ~Vertex(); + void add_edge(const unsigned int other_vertex); + void remove_duplicate_edges(std::vector& tmp); + void sort_edges(); + + unsigned int color; + std::vector edges; + unsigned int nof_edges() const { + return static_cast(edges.size()); + } + }; + std::vector vertices; + void sort_edges(); + void remove_duplicate_edges(); + + /** \internal + * Partition independent invariant. + * Returns the color of the vertex. + * Time complexity: O(1). + */ + static unsigned int vertex_color_invariant(const Graph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the degree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int degree_invariant(const Graph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns 1 if there is an edge from the vertex to itself, 0 if not. + * Time complexity: O(k), where k is the number of edges leaving the vertex. + */ + static unsigned int selfloop_invariant(const Graph* const g, + const unsigned int v); + + + bool refine_according_to_invariant(unsigned int (*inv)(const Graph* const g, + const unsigned int v)); + + /* + * Routines needed when refining the partition p into equitable + */ + bool split_neighbourhood_of_unit_cell(Partition::Cell * const); + bool split_neighbourhood_of_cell(Partition::Cell * const); + + /** \internal + * \copydoc AbstractGraph::is_equitable() const + */ + bool is_equitable() const; + + /* Splitting heuristics, documented in more detail in graph.cc */ + SplittingHeuristic sh; + Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell); + Partition::Cell* sh_first(); + Partition::Cell* sh_first_smallest(); + Partition::Cell* sh_first_largest(); + Partition::Cell* sh_first_max_neighbours(); + Partition::Cell* sh_first_smallest_max_neighbours(); + Partition::Cell* sh_first_largest_max_neighbours(); + + + void make_initial_equitable_partition(); + + void initialize_certificate(); + + bool is_automorphism(unsigned int* const perm) const; + + + bool nucr_find_first_component(const unsigned int level); + bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return); + + + + +public: + /** + * Create a new graph with \a N vertices and no edges. + */ + Graph(const unsigned int N = 0); + + /** + * Destroy the graph. + */ + ~Graph(); + + /** + * \copydoc AbstractGraph::is_automorphism(const std::vector& perm) const + */ + bool is_automorphism(const std::vector& perm) const; + + + /** + * \copydoc AbstractGraph::get_hash() + */ + virtual unsigned int get_hash(); + + /** + * Return the number of vertices in the graph. + */ + unsigned int get_nof_vertices() const { + return static_cast(vertices.size()); + } + + /** + * \copydoc AbstractGraph::permute(const unsigned int* const perm) const + */ + Graph* permute(const unsigned int* const perm) const; + Graph* permute(const std::vector& perm) const; + + /** + * Add a new vertex with color \a color in the graph and return its index. + */ + unsigned int add_vertex(const unsigned int color = 0); + + /** + * Add an edge between vertices \a v1 and \a v2. + * Duplicate edges between vertices are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + void add_edge(const unsigned int v1, const unsigned int v2); + + /** + * Change the color of the vertex \a vertex to \a color. + */ + void change_color(const unsigned int vertex, const unsigned int color); + + /** + * Compare this graph with the graph \a other. + * Returns 0 if the graphs are equal, and a negative (positive) integer + * if this graph is "smaller than" ("greater than", resp.) than \a other. + */ + int cmp(Graph& other); + + /** + * Set the splitting heuristic used by the automorphism and canonical + * labeling algorithm. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + void set_splitting_heuristic(const SplittingHeuristic shs) {sh = shs; } + + +}; + + + +/** + * \brief The class for directed, vertex colored graphs. + * + * Multiple edges between vertices are not allowed (i.e., are ignored). + */ +class Digraph : public AbstractGraph +{ +public: + /** + * The possible splitting heuristics. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + typedef enum { + /** First non-unit cell. + * Very fast but may result in large search spaces on difficult graphs. + * Use for large but easy graphs. */ + shs_f = 0, + /** First smallest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fs, + /** First largest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fl, + /** First maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fm, + /** First smallest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fsm, + /** First largest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_flm + } SplittingHeuristic; + +protected: + class Vertex { + public: + Vertex(); + ~Vertex(); + void add_edge_to(const unsigned int dest_vertex); + void add_edge_from(const unsigned int source_vertex); + void remove_duplicate_edges(std::vector& tmp); + void sort_edges(); + unsigned int color; + std::vector edges_out; + std::vector edges_in; + unsigned int nof_edges_in() const { return static_cast(edges_in.size()); } + unsigned int nof_edges_out() const { return static_cast(edges_out.size()); } + }; + std::vector vertices; + void remove_duplicate_edges(); + + /** \internal + * Partition independent invariant. + * Returns the color of the vertex. + * Time complexity: O(1). + */ + static unsigned int vertex_color_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the indegree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int indegree_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the outdegree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int outdegree_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns 1 if there is an edge from the vertex to itself, 0 if not. + * Time complexity: O(k), where k is the number of edges leaving the vertex. + */ + static unsigned int selfloop_invariant(const Digraph* const g, + const unsigned int v); + + /** \internal + * Refine the partition \a p according to + * the partition independent invariant \a inv. + */ + bool refine_according_to_invariant(unsigned int (*inv)(const Digraph* const g, + const unsigned int v)); + + /* + * Routines needed when refining the partition p into equitable + */ + bool split_neighbourhood_of_unit_cell(Partition::Cell* const); + bool split_neighbourhood_of_cell(Partition::Cell* const); + + + /** \internal + * \copydoc AbstractGraph::is_equitable() const + */ + bool is_equitable() const; + + /* Splitting heuristics, documented in more detail in the cc-file. */ + SplittingHeuristic sh; + Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell); + Partition::Cell* sh_first(); + Partition::Cell* sh_first_smallest(); + Partition::Cell* sh_first_largest(); + Partition::Cell* sh_first_max_neighbours(); + Partition::Cell* sh_first_smallest_max_neighbours(); + Partition::Cell* sh_first_largest_max_neighbours(); + + void make_initial_equitable_partition(); + + void initialize_certificate(); + + bool is_automorphism(unsigned int* const perm) const; + + void sort_edges(); + + bool nucr_find_first_component(const unsigned int level); + bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return); + +public: + /** + * Create a new directed graph with \a N vertices and no edges. + */ + Digraph(const unsigned int N = 0); + + /** + * Destroy the graph. + */ + ~Digraph(); + + + /** + * \copydoc AbstractGraph::is_automorphism(const std::vector& perm) const + */ + bool is_automorphism(const std::vector& perm) const; + + + + /** + * \copydoc AbstractGraph::get_hash() + */ + virtual unsigned int get_hash(); + + /** + * Return the number of vertices in the graph. + */ + unsigned int get_nof_vertices() const { return static_cast(vertices.size()); } + + /** + * Add a new vertex with color 'color' in the graph and return its index. + */ + unsigned int add_vertex(const unsigned int color = 0); + + /** + * Add an edge from the vertex \a source to the vertex \a target. + * Duplicate edges are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + void add_edge(const unsigned int source, const unsigned int target); + + /** + * Change the color of the vertex 'vertex' to 'color'. + */ + void change_color(const unsigned int vertex, const unsigned int color); + + /** + * Compare this graph with the graph \a other. + * Returns 0 if the graphs are equal, and a negative (positive) integer + * if this graph is "smaller than" ("greater than", resp.) than \a other. + */ + int cmp(Digraph& other); + + /** + * Set the splitting heuristic used by the automorphism and canonical + * labeling algorithm. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + void set_splitting_heuristic(SplittingHeuristic shs) {sh = shs; } + + /** + * \copydoc AbstractGraph::permute(const unsigned int* const perm) const + */ + Digraph* permute(const unsigned int* const perm) const; + Digraph* permute(const std::vector& perm) const; +}; + + + + +} // namespace bliss + +#endif // BLISS_GRAPH_HH diff --git a/src/isomorphism/bliss/heap.cc b/src/isomorphism/bliss/heap.cc new file mode 100644 index 0000000..0e57893 --- /dev/null +++ b/src/isomorphism/bliss/heap.cc @@ -0,0 +1,111 @@ +#include "heap.hh" + +#include +#include + +/* Allow using 'and' instead of '&&' with MSVC */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Heap::Heap() { + array = nullptr; + n = 0; + N = 0; +} + +Heap::~Heap() +{ + delete[] array; + array = nullptr; + n = 0; + N = 0; +} + +void Heap::upheap(unsigned int index) +{ + assert(n >= 1); + assert(index >= 1 and index <= n); + const unsigned int v = array[index]; + array[0] = 0; + while(array[index/2] > v) + { + array[index] = array[index/2]; + index = index/2; + } + array[index] = v; +} + +void Heap::downheap(unsigned int index) +{ + const unsigned int v = array[index]; + const unsigned int lim = n/2; + while(index <= lim) + { + unsigned int new_index = index + index; + if((new_index < n) and (array[new_index] > array[new_index+1])) + new_index++; + if(v <= array[new_index]) + break; + array[index] = array[new_index]; + index = new_index; + } + array[index] = v; +} + +void Heap::init(const unsigned int size) +{ + assert(size > 0); + if(size > N) + { + delete[] array; + array = new unsigned int[size + 1]; + N = size; + } + n = 0; +} + +void Heap::insert(const unsigned int v) +{ + assert(n < N); + array[++n] = v; + upheap(n); +} + +unsigned int Heap::smallest() const +{ + assert(n >= 1 and n <= N); + return array[1]; +} + +unsigned int Heap::remove() +{ + assert(n >= 1 and n <= N); + const unsigned int v = array[1]; + array[1] = array[n--]; + downheap(1); + return v; +} + +} // namespace bliss diff --git a/src/isomorphism/bliss/heap.hh b/src/isomorphism/bliss/heap.hh new file mode 100644 index 0000000..bb84cd4 --- /dev/null +++ b/src/isomorphism/bliss/heap.hh @@ -0,0 +1,89 @@ +#ifndef BLISS_HEAP_HH +#define BLISS_HEAP_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** + * \brief A capacity bounded heap data structure. + */ + +class Heap +{ + unsigned int N; + unsigned int n; + unsigned int *array; + void upheap(unsigned int k); + void downheap(unsigned int k); +public: + /** + * Create a new heap. + * init() must be called after this. + */ + Heap(); + ~Heap(); + + /** + * Initialize the heap to have the capacity to hold \e size elements. + */ + void init(const unsigned int size); + + /** + * Is the heap empty? + * Time complexity is O(1). + */ + bool is_empty() const {return n == 0; } + + /** + * Remove all the elements in the heap. + * Time complexity is O(1). + */ + void clear() {n = 0; } + + /** + * Insert the element \a e in the heap. + * Time complexity is O(log(N)), where N is the number of elements + * currently in the heap. + */ + void insert(const unsigned int e); + + /** + * Return the smallest element in the heap. + * Time complexity is O(1). + */ + unsigned int smallest() const; + + /** + * Remove and return the smallest element in the heap. + * Time complexity is O(log(N)), where N is the number of elements + * currently in the heap. + */ + unsigned int remove(); + + /** + * Get the number of elements in the heap. + */ + unsigned int size() const {return n; } +}; + +} // namespace bliss + +#endif // BLISS_HEAP_HH diff --git a/src/isomorphism/bliss/igraph-changes.md b/src/isomorphism/bliss/igraph-changes.md new file mode 100644 index 0000000..3c7058b --- /dev/null +++ b/src/isomorphism/bliss/igraph-changes.md @@ -0,0 +1,34 @@ +This file lists changes that were made to the original Bliss package (version 0.75) to integrate it into igraph. + +Exclude `CMakeLists.txt`, `Doxyfile`, `Makefile-manual`, `readme.txt`. Make sure not to accidentally overwrite igraph's own `bliss/CMakeLists.txt`. + +Removed `bliss.cc`, `bliss_C.cc`, `bliss_C.h`. + +Remove `timer.hh`. Remove references to `timer.hh` and `Timer` class in `graph.cc`. + +Replace `#pragma once` by traditional header guards in all headers. + +### In `bignum.hh`: + +Replace `#include ` by `#include "internal/gmp_internal.h"`. + +At the beginning, add `#define BLISS_USE_GMP`. Verify that this macro is only used in this file. + +### In `defs.cc` and `defs.hh`: + +Remove the `...` argument from `fatal_error` for simplicity, and make the function simply invoke `IGRAPH_FATAL`. + +### In `graph.cc`: + +Define `_INTERNAL_ERROR` in terms of `IGRAPH_FATAL`. + +### MSVC compatibility + +Bliss uses `and`, `or`, etc. instead of `&&`, `||`, etc. These are not supported by MSVC by default. Bliss 0.74 uses the `/permissive` option to enable support in MSVC, but this option is only supported wit VS2019. Instead, in igraph we add the following where relevant: + +``` +/* Allow using 'and' instead of '&&' with MSVC */ +#if _MSC_VER +#include +#endif +``` diff --git a/src/isomorphism/bliss/kqueue.hh b/src/isomorphism/bliss/kqueue.hh new file mode 100644 index 0000000..06de3b4 --- /dev/null +++ b/src/isomorphism/bliss/kqueue.hh @@ -0,0 +1,168 @@ +#ifndef BLISS_KQUEUE_HH +#define BLISS_KQUEUE_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include +#include + +namespace bliss { + +/** + * \brief A simple implementation of queues with fixed maximum capacity. + */ +template +class KQueue +{ +public: + /** + * Create a new queue with capacity zero. + * The function init() should be called next. + */ + KQueue(); + + ~KQueue(); + + /** + * Initialize the queue to have the capacity to hold at most \a N elements. + */ + void init(const unsigned int N); + + /** Is the queue empty? */ + bool is_empty() const; + + /** Return the number of elements in the queue. */ + unsigned int size() const; + + /** Remove all the elements in the queue. */ + void clear(); + + /** Return (but don't remove) the first element in the queue. */ + Type front() const; + + /** Remove and return the first element of the queue. */ + Type pop_front(); + + /** Push the element \a e in the front of the queue. */ + void push_front(Type e); + + /** Remove and return the last element of the queue. */ + Type pop_back(); + + /** Push the element \a e in the back of the queue. */ + void push_back(Type e); +private: + Type *entries, *end; + Type *head, *tail; +}; + +template +KQueue::KQueue() +{ + entries = nullptr; + end = nullptr; + head = nullptr; + tail = nullptr; +} + +template +KQueue::~KQueue() +{ + delete[] entries; + entries = nullptr; + end = nullptr; + head = nullptr; + tail = nullptr; +} + +template +void KQueue::init(const unsigned int k) +{ + assert(k > 0); + delete[] entries; + entries = new Type[k+1]; + end = entries + k + 1; + head = entries; + tail = head; +} + +template +void KQueue::clear() +{ + head = entries; + tail = head; +} + +template +bool KQueue::is_empty() const +{ + return head == tail; +} + +template +unsigned int KQueue::size() const +{ + if(tail >= head) + return(tail - head); + return (end - head) + (tail - entries); +} + +template +Type KQueue::front() const +{ + assert(head != tail); + return *head; +} + +template +Type KQueue::pop_front() +{ + assert(head != tail); + Type *old_head = head; + head++; + if(head == end) + head = entries; + return *old_head; +} + +template +void KQueue::push_front(Type e) +{ + if(head == entries) + head = end - 1; + else + head--; + assert(head != tail); + *head = e; +} + +template +void KQueue::push_back(Type e) +{ + *tail = e; + tail++; + if(tail == end) + tail = entries; + assert(head != tail); +} + +} // namespace bliss + +#endif // BLISS_KQUEUE_HH diff --git a/src/isomorphism/bliss/kstack.hh b/src/isomorphism/bliss/kstack.hh new file mode 100644 index 0000000..ecb9b83 --- /dev/null +++ b/src/isomorphism/bliss/kstack.hh @@ -0,0 +1,145 @@ +#ifndef BLISS_KSTACK_HH +#define BLISS_KSTACK_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include +#include + +namespace bliss { + +/** + * \brief A simple implementation of a stack with fixed maximum capacity. + */ +template +class KStack { +public: + /** + * Create a new stack with zero capacity. + * The function init() should be called next. + */ + KStack(); + + /** + * Create a new stack with the capacity to hold at most \a N elements. + */ + KStack(int N); + + ~KStack(); + + /** + * Initialize the stack to have the capacity to hold at most \a N elements. + */ + void init(int N); + + /** + * Is the stack empty? + */ + bool is_empty() const {return cursor == entries; } + + /** + * Return (but don't remove) the top element of the stack. + */ + Type top() const {assert(cursor > entries); return *cursor; } + + /** + * Pop (remove) the top element of the stack. + */ + Type pop() + { + assert(cursor > entries); + return *cursor--; + } + + /** + * Push the element \a e in the stack. + */ + void push(Type e) + { + assert(cursor < entries + kapacity); + *(++cursor) = e; + } + + /** Remove all the elements in the stack. */ + void clean() {cursor = entries; } + + /** + * Get the number of elements in the stack. + */ + unsigned int size() const {return cursor - entries; } + + /** + * Return the i:th element in the stack, where \a i is in the range + * 0,...,this.size()-1; the 0:th element is the bottom element + * in the stack. + */ + Type element_at(unsigned int i) + { + assert(i < size()); + return entries[i+1]; + } + + /** Return the capacity (NOT the number of elements) of the stack. */ + int capacity() const {return kapacity; } +private: + int kapacity; + Type *entries; + Type *cursor; +}; + +template +KStack::KStack() +{ + kapacity = 0; + entries = nullptr; + cursor = nullptr; +} + +template +KStack::KStack(int k) +{ + assert(k > 0); + kapacity = k; + entries = new Type[k+1]; + cursor = entries; +} + +template +void KStack::init(int k) +{ + assert(k > 0); + delete[] entries; + kapacity = k; + entries = new Type[k+1]; + cursor = entries; +} + +template +KStack::~KStack() +{ + delete[] entries; + kapacity = 0; + entries = nullptr; + cursor = nullptr; +} + +} // namespace bliss + +#endif // BLISS_KSTACK_HH diff --git a/src/isomorphism/bliss/orbit.cc b/src/isomorphism/bliss/orbit.cc new file mode 100644 index 0000000..cb73862 --- /dev/null +++ b/src/isomorphism/bliss/orbit.cc @@ -0,0 +1,151 @@ +#include +#include "orbit.hh" + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Orbit::Orbit() +{ + orbits = 0; + in_orbit = 0; + nof_elements = 0; +} + + +Orbit::~Orbit() +{ + delete[] orbits; + orbits = 0; + /* + if(orbits) + { + free(orbits); + orbits = 0; + } + */ + delete[] in_orbit; + in_orbit = 0; + /* + if(in_orbit) + { + free(in_orbit); + in_orbit = 0; + } + */ + nof_elements = 0; + _nof_orbits = 0; +} + + +void Orbit::init(const unsigned int n) +{ + assert(n > 0); + if(orbits) delete[] orbits; + orbits = new OrbitEntry[n]; + delete[] in_orbit; + in_orbit = new OrbitEntry*[n]; + nof_elements = n; + + reset(); +} + + +void Orbit::reset() +{ + assert(orbits); + assert(in_orbit); + + for(unsigned int i = 0; i < nof_elements; i++) + { + orbits[i].element = i; + orbits[i].next = 0; + orbits[i].size = 1; + in_orbit[i] = &orbits[i]; + } + _nof_orbits = nof_elements; +} + + +void Orbit::merge_orbits(OrbitEntry *orbit1, OrbitEntry *orbit2) +{ + + if(orbit1 != orbit2) + { + _nof_orbits--; + /* Only update the elements in the smaller orbit */ + if(orbit1->size > orbit2->size) + { + OrbitEntry * const temp = orbit2; + orbit2 = orbit1; + orbit1 = temp; + } + /* Link the elements of orbit1 to the almost beginning of orbit2 */ + OrbitEntry *e = orbit1; + while(e->next) + { + in_orbit[e->element] = orbit2; + e = e->next; + } + in_orbit[e->element] = orbit2; + e->next = orbit2->next; + orbit2->next = orbit1; + /* Keep the minimal orbit representative in the beginning */ + if(orbit1->element < orbit2->element) + { + const unsigned int temp = orbit1->element; + orbit1->element = orbit2->element; + orbit2->element = temp; + } + orbit2->size += orbit1->size; + } +} + + +void Orbit::merge_orbits(unsigned int e1, unsigned int e2) +{ + + merge_orbits(in_orbit[e1], in_orbit[e2]); +} + + +bool Orbit::is_minimal_representative(unsigned int element) const +{ + return(get_minimal_representative(element) == element); +} + + +unsigned int Orbit::get_minimal_representative(unsigned int element) const +{ + + OrbitEntry * const orbit = in_orbit[element]; + + return(orbit->element); +} + + +unsigned int Orbit::orbit_size(unsigned int element) const +{ + + return(in_orbit[element]->size); +} + + +} // namespace bliss diff --git a/src/isomorphism/bliss/orbit.hh b/src/isomorphism/bliss/orbit.hh new file mode 100644 index 0000000..1d59445 --- /dev/null +++ b/src/isomorphism/bliss/orbit.hh @@ -0,0 +1,112 @@ +#ifndef BLISS_ORBIT_HH +#define BLISS_ORBIT_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** + * \brief A class for representing orbit information. + * + * Given a set {0,...,N-1} of N elements, represent equivalence + * classes (that is, unordered partitions) of the elements. + * Supports only equivalence class merging, not splitting. + * Merging two classes requires time O(k), where k is the number of + * the elements in the smaller of the merged classes. + * Getting the smallest representative in a class + * (and thus testing whether two elements belong to the same class) + * is a constant time operation. + */ +class Orbit +{ + class OrbitEntry + { + public: + unsigned int element; + OrbitEntry *next; + unsigned int size; + }; + + OrbitEntry *orbits; + OrbitEntry **in_orbit; + unsigned int nof_elements; + unsigned int _nof_orbits; + void merge_orbits(OrbitEntry *o1, OrbitEntry *o2); + +public: + /** + * Create a new orbit information object. + * The init() function must be called next to actually initialize + * the object. + */ + Orbit(); + ~Orbit(); + + /** + * Initialize the orbit information to consider sets of \a N elements. + * It is required that \a N > 0. + * The orbit information is reset so that each element forms + * an orbit of its own. + * Time complexity is O(N). + * \sa reset() + */ + void init(const unsigned int N); + + /** + * Reset the orbits so that each element forms an orbit of its own. + * Time complexity is O(N). + */ + void reset(); + + /** + * Merge the orbits of the elements \a e1 and \a e2. + * Time complexity is O(k), where k is the number of elements in + * the smaller of the merged orbits. + */ + void merge_orbits(unsigned int e1, unsigned int e2); + + /** + * Is the element \a e the smallest element in its orbit? + * Time complexity is O(1). + */ + bool is_minimal_representative(unsigned int e) const; + + /** + * Get the smallest element in the orbit of the element \a e. + * Time complexity is O(1). + */ + unsigned int get_minimal_representative(unsigned int e) const; + + /** + * Get the number of elements in the orbit of the element \a e. + * Time complexity is O(1). + */ + unsigned int orbit_size(unsigned int e) const; + + /** + * Get the number of orbits. + * Time complexity is O(1). + */ + unsigned int nof_orbits() const {return _nof_orbits; } +}; + +} // namespace bliss + +#endif // BLISS_ORBIT_HH diff --git a/src/isomorphism/bliss/partition.cc b/src/isomorphism/bliss/partition.cc new file mode 100644 index 0000000..d0b2d52 --- /dev/null +++ b/src/isomorphism/bliss/partition.cc @@ -0,0 +1,1127 @@ +#include +#include + +#include "graph.hh" +#include "partition.hh" + +#include "igraph_decls.h" + +/* Allow using 'and' instead of '&&' with MSVC */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Partition::Partition() +{ + N = 0; + elements = 0; + in_pos = 0; + invariant_values = 0; + cells = 0; + free_cells = 0; + element_to_cell_map = 0; + graph = 0; + discrete_cell_count = 0; + /* Initialize a distribution count sorting array. */ + for(unsigned int i = 0; i < 256; i++) + dcs_count[i] = 0; + + cr_enabled = false; + cr_cells = 0; + cr_levels = 0; +} + + + +Partition::~Partition() +{ + delete[] elements; elements = nullptr; + delete[] cells; cells = nullptr; + delete[] element_to_cell_map; element_to_cell_map = nullptr; + delete[] in_pos; in_pos = nullptr; + delete[] invariant_values; invariant_values = nullptr; + N = 0; +} + + + +void Partition::init(const unsigned int M) +{ + assert(M > 0); + N = M; + + delete[] elements; + elements = new unsigned int[N]; + for(unsigned int i = 0; i < N; i++) + elements[i] = i; + + delete[] in_pos; + in_pos = new unsigned int*[N]; + for(unsigned int i = 0; i < N; i++) + in_pos[i] = elements + i; + + delete[] invariant_values; + invariant_values = new unsigned int[N]; + for(unsigned int i = 0; i < N; i++) + invariant_values[i] = 0; + + delete[] cells; + cells = new Cell[N]; + + cells[0].first = 0; + cells[0].length = N; + cells[0].max_ival = 0; + cells[0].max_ival_count = 0; + cells[0].in_splitting_queue = false; + cells[0].in_neighbour_heap = false; + cells[0].prev = 0; + cells[0].next = 0; + cells[0].next_nonsingleton = 0; + cells[0].prev_nonsingleton = 0; + cells[0].split_level = 0; + first_cell = &cells[0]; + if(N == 1) + { + first_nonsingleton_cell = 0; + discrete_cell_count = 1; + } + else + { + first_nonsingleton_cell = &cells[0]; + discrete_cell_count = 0; + } + + for(unsigned int i = 1; i < N; i++) + { + cells[i].first = 0; + cells[i].length = 0; + cells[i].max_ival = 0; + cells[i].max_ival_count = 0; + cells[i].in_splitting_queue = false; + cells[i].in_neighbour_heap = false; + cells[i].prev = 0; + cells[i].next = (i < N-1)?&cells[i+1]:0; + cells[i].next_nonsingleton = 0; + cells[i].prev_nonsingleton = 0; + } + if(N > 1) + free_cells = &cells[1]; + else + free_cells = 0; + + delete[] element_to_cell_map; + element_to_cell_map = new Cell*[N]; + for(unsigned int i = 0; i < N; i++) + element_to_cell_map[i] = first_cell; + + splitting_queue.init(N); + refinement_stack.init(N); + + /* Reset the main backtracking stack */ + bt_stack.clear(); +} + + + + + + +Partition::BacktrackPoint +Partition::set_backtrack_point() +{ + BacktrackInfo info; + info.refinement_stack_size = refinement_stack.size(); + if(cr_enabled) + info.cr_backtrack_point = cr_get_backtrack_point(); + BacktrackPoint p = bt_stack.size(); + bt_stack.push_back(info); + return p; +} + + + +void +Partition::goto_backtrack_point(BacktrackPoint p) +{ + assert(p < bt_stack.size()); + BacktrackInfo info = bt_stack[p]; + bt_stack.resize(p); + + if(cr_enabled) + cr_goto_backtrack_point(info.cr_backtrack_point); + + const unsigned int dest_refinement_stack_size = info.refinement_stack_size; + + assert(refinement_stack.size() >= dest_refinement_stack_size); + while(refinement_stack.size() > dest_refinement_stack_size) + { + RefInfo i = refinement_stack.pop(); + const unsigned int first = i.split_cell_first; + Cell* cell = get_cell(elements[first]); + + if(cell->first != first) + { + assert(cell->first < first); + assert(cell->split_level <= dest_refinement_stack_size); + goto done; + } + assert(cell->split_level > dest_refinement_stack_size); + + while(cell->split_level > dest_refinement_stack_size) + { + assert(cell->prev); + cell = cell->prev; + } + while(cell->next and + cell->next->split_level > dest_refinement_stack_size) + { + /* Merge next cell */ + Cell* const next_cell = cell->next; + if(cell->length == 1) + discrete_cell_count--; + if(next_cell->length == 1) + discrete_cell_count--; + /* Update element_to_cell_map values of elements added in cell */ + unsigned int* ep = elements + next_cell->first; + unsigned int* const lp = ep + next_cell->length; + for( ; ep < lp; ep++) + element_to_cell_map[*ep] = cell; + /* Update cell parameters */ + cell->length += next_cell->length; + if(next_cell->next) + next_cell->next->prev = cell; + cell->next = next_cell->next; + /* (Pseudo)free next_cell */ + next_cell->first = 0; + next_cell->length = 0; + next_cell->prev = 0; + next_cell->next = free_cells; + free_cells = next_cell; + } + + done: + if(i.prev_nonsingleton_first >= 0) + { + Cell* const prev_cell = get_cell(elements[i.prev_nonsingleton_first]); + assert(prev_cell->length > 1); + cell->prev_nonsingleton = prev_cell; + prev_cell->next_nonsingleton = cell; + } + else + { + //assert(cell->prev_nonsingleton == 0); + cell->prev_nonsingleton = 0; + first_nonsingleton_cell = cell; + } + + if(i.next_nonsingleton_first >= 0) + { + Cell* const next_cell = get_cell(elements[i.next_nonsingleton_first]); + assert(next_cell->length > 1); + cell->next_nonsingleton = next_cell; + next_cell->prev_nonsingleton = cell; + } + else + { + //assert(cell->next_nonsingleton == 0); + cell->next_nonsingleton = 0; + } + } + +} + + + +Partition::Cell* +Partition::individualize(Partition::Cell * const cell, + const unsigned int element) +{ + assert(!cell->is_unit()); + + unsigned int * const pos = in_pos[element]; + assert((unsigned int)(pos - elements) >= cell->first); + assert((unsigned int)(pos - elements) < cell->first + cell->length); + assert(*pos == element); + + const unsigned int last = cell->first + cell->length - 1; + *pos = elements[last]; + in_pos[*pos] = pos; + elements[last] = element; + in_pos[element] = elements + last; + + Partition::Cell * const new_cell = aux_split_in_two(cell, cell->length-1); + assert(elements[new_cell->first] == element); + element_to_cell_map[element] = new_cell; + + return new_cell; +} + + + +Partition::Cell* +Partition::aux_split_in_two(Partition::Cell* const cell, + const unsigned int first_half_size) +{ + RefInfo i; + + assert(0 < first_half_size && first_half_size < cell->length); + + /* (Pseudo)allocate new cell */ + Cell * const new_cell = free_cells; + assert(new_cell != 0); + free_cells = new_cell->next; + /* Update new cell parameters */ + new_cell->first = cell->first + first_half_size; + new_cell->length = cell->length - first_half_size; + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = refinement_stack.size()+1; + /* Update old, splitted cell parameters */ + cell->length = first_half_size; + cell->next = new_cell; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + + /* Add cell in refinement_stack for backtracking */ + i.split_cell_first = new_cell->first; + if(cell->prev_nonsingleton) + i.prev_nonsingleton_first = cell->prev_nonsingleton->first; + else + i.prev_nonsingleton_first = -1; + if(cell->next_nonsingleton) + i.next_nonsingleton_first = cell->next_nonsingleton->first; + else + i.next_nonsingleton_first = -1; + refinement_stack.push(i); + + /* Modify nonsingleton cell list */ + if(new_cell->length > 1) + { + new_cell->prev_nonsingleton = cell; + new_cell->next_nonsingleton = cell->next_nonsingleton; + if(new_cell->next_nonsingleton) + new_cell->next_nonsingleton->prev_nonsingleton = new_cell; + cell->next_nonsingleton = new_cell; + } + else + { + new_cell->next_nonsingleton = 0; + new_cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + + if(cell->is_unit()) + { + if(cell->prev_nonsingleton) + cell->prev_nonsingleton->next_nonsingleton = cell->next_nonsingleton; + else + first_nonsingleton_cell = cell->next_nonsingleton; + if(cell->next_nonsingleton) + cell->next_nonsingleton->prev_nonsingleton = cell->prev_nonsingleton; + cell->next_nonsingleton = 0; + cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + + return new_cell; +} + + + +void +Partition::splitting_queue_add(Cell* const cell) +{ + static const unsigned int smallish_cell_threshold = 1; + assert(!cell->in_splitting_queue); + cell->in_splitting_queue = true; + if(cell->length <= smallish_cell_threshold) + splitting_queue.push_front(cell); + else + splitting_queue.push_back(cell); +} + + + +void +Partition::splitting_queue_clear() +{ + while(!splitting_queue_is_empty()) + splitting_queue_pop(); +} + + + + + +/* + * Assumes that the invariant values are NOT the same + * and that the cell contains more than one element + */ +Partition::Cell* +Partition::sort_and_split_cell1(Partition::Cell* const cell) +{ +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + assert(cell->length > 1); + assert(cell->first + cell->length <= N); + unsigned int nof_0_found = 0; + unsigned int nof_1_found = 0; + for(unsigned int i = cell->first; i < cell->first + cell->length; i++) + { + const unsigned int ival = invariant_values[elements[i]]; + assert(ival == 0 or ival == 1); + if(ival == 0) nof_0_found++; + else nof_1_found++; + } + assert(nof_0_found > 0); + assert(nof_1_found > 0); + assert(nof_1_found == cell->max_ival_count); + assert(nof_0_found + nof_1_found == cell->length); + assert(cell->max_ival == 1); +#endif + + + /* (Pseudo)allocate new cell */ + Cell* const new_cell = free_cells; + assert(new_cell != 0); + free_cells = new_cell->next; + +#define NEW_SORT1 +#ifdef NEW_SORT1 + unsigned int *ep0 = elements + cell->first; + unsigned int *ep1 = ep0 + cell->length - cell->max_ival_count; + if(cell->max_ival_count > cell->length / 2) + { + /* There are more ones than zeros, only move zeros */ + unsigned int * const end = ep0 + cell->length; + while(ep1 < end) + { + while(invariant_values[*ep1] == 0) + { + const unsigned int tmp = *ep1; + *ep1 = *ep0; + *ep0 = tmp; + in_pos[tmp] = ep0; + in_pos[*ep1] = ep1; + ep0++; + } + element_to_cell_map[*ep1] = new_cell; + invariant_values[*ep1] = 0; + ep1++; + } + } + else + { + /* There are more zeros than ones, only move ones */ + unsigned int * const end = ep1; + while(ep0 < end) + { + while(invariant_values[*ep0] != 0) + { + const unsigned int tmp = *ep0; + *ep0 = *ep1; + *ep1 = tmp; + in_pos[tmp] = ep1; + in_pos[*ep0] = ep0; + ep1++; + } + ep0++; + } + ep1 = end; + while(ep1 < elements + cell->first + cell->length) + { + element_to_cell_map[*ep1] = new_cell; + invariant_values[*ep1] = 0; + ep1++; + } + } + /* Update new cell parameters */ + new_cell->first = cell->first + cell->length - cell->max_ival_count; + new_cell->length = cell->length - (new_cell->first - cell->first); + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = refinement_stack.size()+1; + /* Update old, splitted cell parameters */ + cell->length = new_cell->first - cell->first; + cell->next = new_cell; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + +#else + /* Sort vertices in the cell according to the invariant values */ + unsigned int *ep0 = elements + cell->first; + unsigned int *ep1 = ep0 + cell->length; + while(ep1 > ep0) + { + const unsigned int element = *ep0; + const unsigned int ival = invariant_values[element]; + invariant_values[element] = 0; + assert(ival <= 1); + assert(element_to_cell_map[element] == cell); + assert(in_pos[element] == ep0); + if(ival == 0) + { + ep0++; + } + else + { + ep1--; + *ep0 = *ep1; + *ep1 = element; + element_to_cell_map[element] = new_cell; + in_pos[element] = ep1; + in_pos[*ep0] = ep0; + } + } + + assert(ep1 != elements + cell->first); + assert(ep0 != elements + cell->first + cell->length); + + /* Update new cell parameters */ + new_cell->first = ep1 - elements; + new_cell->length = cell->length - (new_cell->first - cell->first); + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = cell->split_level; + /* Update old, splitted cell parameters */ + cell->length = new_cell->first - cell->first; + cell->next = new_cell; + cell->split_level = refinement_stack.size()+1; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + +#endif /* ifdef NEW_SORT1*/ + + /* Add cell in refinement stack for backtracking */ + { + RefInfo i; + i.split_cell_first = new_cell->first; + if(cell->prev_nonsingleton) + i.prev_nonsingleton_first = cell->prev_nonsingleton->first; + else + i.prev_nonsingleton_first = -1; + if(cell->next_nonsingleton) + i.next_nonsingleton_first = cell->next_nonsingleton->first; + else + i.next_nonsingleton_first = -1; + /* Modify nonsingleton cell list */ + if(new_cell->length > 1) + { + new_cell->prev_nonsingleton = cell; + new_cell->next_nonsingleton = cell->next_nonsingleton; + if(new_cell->next_nonsingleton) + new_cell->next_nonsingleton->prev_nonsingleton = new_cell; + cell->next_nonsingleton = new_cell; + } + else + { + new_cell->next_nonsingleton = 0; + new_cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + if(cell->is_unit()) + { + if(cell->prev_nonsingleton) + cell->prev_nonsingleton->next_nonsingleton = cell->next_nonsingleton; + else + first_nonsingleton_cell = cell->next_nonsingleton; + if(cell->next_nonsingleton) + cell->next_nonsingleton->prev_nonsingleton = cell->prev_nonsingleton; + cell->next_nonsingleton = 0; + cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + refinement_stack.push(i); + } + + + /* Add cells in splitting queue */ + assert(!new_cell->in_splitting_queue); + if(cell->in_splitting_queue) { + /* Both cells must be included in splitting_queue in order to have + refinement to equitable partition */ + splitting_queue_add(new_cell); + } else { + Cell *min_cell, *max_cell; + if(cell->length <= new_cell->length) { + min_cell = cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = cell; + } + /* Put the smaller cell in splitting_queue */ + splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + splitting_queue_add(max_cell); + } + } + + + return new_cell; +} + + + + + +/** + * An auxiliary function for distribution count sorting. + * Build start array so that + * dcs_start[0] = 0 and dcs_start[i+1] = dcs_start[i] + dcs_count[i]. + */ +void +Partition::dcs_cumulate_count(const unsigned int max) +{ + assert(max <= 255); + unsigned int* count_p = dcs_count; + unsigned int* start_p = dcs_start; + unsigned int sum = 0; + for(unsigned int i = max+1; i > 0; i--) + { + *start_p = sum; + start_p++; + sum += *count_p; + count_p++; + } +} + + +/** + * Distribution count sorting of cells with invariant values less than 256. + */ +Partition::Cell* +Partition::sort_and_split_cell255(Partition::Cell* const cell, + const unsigned int max_ival) +{ + assert(max_ival <= 255); + + if(cell->is_unit()) + { + /* Reset invariant value */ + invariant_values[elements[cell->first]] = 0; + return cell; + } + +#ifdef BLISS_CONSISTENCY_CHECKS + for(unsigned int i = 0; i < 256; i++) + assert(dcs_count[i] == 0); +#endif + + /* + * Compute the distribution of invariant values to the count array + */ + { + const unsigned int *ep = elements + cell->first; + assert(element_to_cell_map[*ep] == cell); + const unsigned int ival = invariant_values[*ep]; + assert(ival <= 255); + dcs_count[ival]++; + ep++; +#if defined(BLISS_CONSISTENCY_CHECKS) + bool equal_invariant_values = true; +#endif + for(unsigned int i = cell->length - 1; i != 0; i--) + { + assert(element_to_cell_map[*ep] == cell); + const unsigned int ival2 = invariant_values[*ep]; + assert(ival2 <= 255); + assert(ival2 <= max_ival); + dcs_count[ival2]++; +#if defined(BLISS_CONSISTENCY_CHECKS) + if(ival2 != ival) { + equal_invariant_values = false; + } +#endif + ep++; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(!equal_invariant_values); + if(equal_invariant_values) { + assert(dcs_count[ival] == cell->length); + dcs_count[ival] = 0; + clear_ivs(cell); + return cell; + } +#endif + } + + /* Build start array */ + dcs_cumulate_count(max_ival); + + //assert(dcs_start[255] + dcs_count[255] == cell->length); + assert(dcs_start[max_ival] + dcs_count[max_ival] == cell->length); + + /* Do the sorting */ + for(unsigned int i = 0; i <= max_ival; i++) + { + unsigned int *ep = elements + cell->first + dcs_start[i]; + for(unsigned int j = dcs_count[i]; j > 0; j--) + { + while(true) + { + const unsigned int element = *ep; + const unsigned int ival = invariant_values[element]; + if(ival == i) + break; + assert(ival > i); + assert(dcs_count[ival] > 0); + *ep = elements[cell->first + dcs_start[ival]]; + elements[cell->first + dcs_start[ival]] = element; + dcs_start[ival]++; + dcs_count[ival]--; + } + ep++; + } + dcs_count[i] = 0; + } + +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < 256; i++) + assert(dcs_count[i] == 0); +#endif + + /* split cell */ + Cell* const new_cell = split_cell(cell); + assert(new_cell != cell); + return new_cell; +} + + + +/* + * Sort the elements in a cell according to their invariant values. + * The invariant values are not cleared. + * Warning: the in_pos array is left in incorrect state. + */ +bool +Partition::shellsort_cell(Partition::Cell* const cell) +{ + unsigned int h; + unsigned int* ep; + + //assert(cell->first + cell->length <= N); + + if(cell->is_unit()) + return false; + + /* Check whether all the elements have the same invariant value */ + bool equal_invariant_values = true; + { + ep = elements + cell->first; + const unsigned int ival = invariant_values[*ep]; + assert(element_to_cell_map[*ep] == cell); + ep++; + for(unsigned int i = cell->length - 1; i > 0; i--) + { + assert(element_to_cell_map[*ep] == cell); + if(invariant_values[*ep] != ival) { + equal_invariant_values = false; + break; + } + ep++; + } + } + if(equal_invariant_values) + return false; + + ep = elements + cell->first; + + for(h = 1; h <= cell->length/9; h = 3*h + 1) + ; + for( ; h > 0; h = h/3) { + for(unsigned int i = h; i < cell->length; i++) { + const unsigned int element = ep[i]; + const unsigned int ival = invariant_values[element]; + unsigned int j = i; + while(j >= h and invariant_values[ep[j-h]] > ival) { + ep[j] = ep[j-h]; + j -= h; + } + ep[j] = element; + } + } + return true; +} + + + +void +Partition::clear_ivs(Cell* const cell) +{ + unsigned int* ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + invariant_values[*ep] = 0; +} + + +/* + * Assumes that the elements in the cell are sorted according to their + * invariant values. + */ +Partition::Cell* +Partition::split_cell(Partition::Cell* const original_cell) +{ + Cell* cell = original_cell; + const bool original_cell_was_in_splitting_queue = + original_cell->in_splitting_queue; + Cell* largest_new_cell = 0; + + while(true) + { + unsigned int* ep = elements + cell->first; + const unsigned int* const lp = ep + cell->length; + const unsigned int ival = invariant_values[*ep]; + invariant_values[*ep] = 0; + element_to_cell_map[*ep] = cell; + in_pos[*ep] = ep; + ep++; + while(ep < lp) + { + const unsigned int e = *ep; + if(invariant_values[e] != ival) + break; + invariant_values[e] = 0; + in_pos[e] = ep; + ep++; + element_to_cell_map[e] = cell; + } + if(ep == lp) + break; + + Cell* const new_cell = aux_split_in_two(cell, + (ep - elements) - cell->first); + + if(graph and graph->compute_eqref_hash) + { + graph->eqref_hash.update(new_cell->first); + graph->eqref_hash.update(new_cell->length); + graph->eqref_hash.update(ival); + } + + /* Add cells in splitting_queue */ + assert(!new_cell->is_in_splitting_queue()); + if(original_cell_was_in_splitting_queue) + { + /* In this case, all new cells are inserted in splitting_queue */ + assert(cell->is_in_splitting_queue()); + splitting_queue_add(new_cell); + } + else + { + /* Otherwise, we can omit one new cell from splitting_queue */ + assert(!cell->is_in_splitting_queue()); + if(largest_new_cell == 0) { + largest_new_cell = cell; + } else { + assert(!largest_new_cell->is_in_splitting_queue()); + if(cell->length > largest_new_cell->length) { + splitting_queue_add(largest_new_cell); + largest_new_cell = cell; + } else { + splitting_queue_add(cell); + } + } + } + /* Process the rest of the cell */ + cell = new_cell; + } + + + if(original_cell == cell) { + /* All the elements in cell had the same invariant value */ + return cell; + } + + /* Add cells in splitting_queue */ + if(!original_cell_was_in_splitting_queue) + { + /* Also consider the last new cell */ + assert(largest_new_cell); + if(cell->length > largest_new_cell->length) + { + splitting_queue_add(largest_new_cell); + largest_new_cell = cell; + } + else + { + splitting_queue_add(cell); + } + if(largest_new_cell->is_unit()) + { + /* Needed in certificate computation */ + splitting_queue_add(largest_new_cell); + } + } + + return cell; +} + + +Partition::Cell* +Partition::zplit_cell(Partition::Cell* const cell, + const bool max_ival_info_ok) +{ + assert(cell != 0); + + Cell* last_new_cell = cell; + + if(!max_ival_info_ok) + { + /* Compute max_ival info */ + assert(cell->max_ival == 0); + assert(cell->max_ival_count == 0); + unsigned int *ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = invariant_values[*ep]; + if(ival > cell->max_ival) + { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) + { + cell->max_ival_count++; + } + } + } + +#ifdef BLISS_CONSISTENCY_CHECKS + /* Verify max_ival info */ + { + unsigned int nof_zeros = 0; + unsigned int max_ival = 0; + unsigned int max_ival_count = 0; + unsigned int *ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = invariant_values[*ep]; + if(ival == 0) + nof_zeros++; + if(ival > max_ival) + { + max_ival = ival; + max_ival_count = 1; + } + else if(ival == max_ival) + max_ival_count++; + } + assert(max_ival == cell->max_ival); + assert(max_ival_count == cell->max_ival_count); + } +#endif + + /* max_ival info has been computed */ + + if(cell->max_ival_count == cell->length) + { + /* All invariant values are the same, clear 'em */ + if(cell->max_ival > 0) + clear_ivs(cell); + } + else + { + /* All invariant values are not the same */ + if(cell->max_ival == 1) + { + /* Specialized splitting for cells with binary invariant values */ + last_new_cell = sort_and_split_cell1(cell); + } + else if(cell->max_ival < 256) + { + /* Specialized splitting for cells with invariant values < 256 */ + last_new_cell = sort_and_split_cell255(cell, cell->max_ival); + } + else + { + /* Generic sorting and splitting */ + const bool sorted = shellsort_cell(cell); + assert(sorted); + IGRAPH_UNUSED(sorted); + last_new_cell = split_cell(cell); + } + } + cell->max_ival = 0; + cell->max_ival_count = 0; + return last_new_cell; +} + + + +/* + * + * Component recursion specific code + * + */ +void +Partition::cr_init() +{ + assert(bt_stack.empty()); + + cr_enabled = true; + + delete[] cr_cells; + cr_cells = new CRCell[N]; + + delete[] cr_levels; + cr_levels = new CRCell*[N]; + + for(unsigned int i = 0; i < N; i++) { + cr_levels[i] = 0; + cr_cells[i].level = UINT_MAX; + cr_cells[i].next = 0; + cr_cells[i].prev_next_ptr = 0; + } + + for(const Cell *cell = first_cell; cell; cell = cell->next) + cr_create_at_level_trailed(cell->first, 0); + + cr_max_level = 0; +} + + +void +Partition::cr_free() +{ + delete[] cr_cells; cr_cells = nullptr; + delete[] cr_levels; cr_levels = nullptr; + + cr_created_trail.clear(); + cr_splitted_level_trail.clear(); + cr_bt_info.clear(); + cr_max_level = 0; + + cr_enabled = false; +} + + +unsigned int +Partition::cr_split_level(const unsigned int level, + const std::vector& splitted_cells) +{ + assert(cr_enabled); + assert(level <= cr_max_level); + cr_levels[++cr_max_level] = 0; + cr_splitted_level_trail.push_back(level); + + for(unsigned int i = 0; i < splitted_cells.size(); i++) + { + const unsigned int cell_index = splitted_cells[i]; + assert(cell_index < N); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level == level); + cr_cell.detach(); + cr_create_at_level(cell_index, cr_max_level); + } + + return cr_max_level; +} + + +unsigned int +Partition::cr_get_backtrack_point() +{ + assert(cr_enabled); + CR_BTInfo info; + info.created_trail_index = cr_created_trail.size(); + info.splitted_level_trail_index = cr_splitted_level_trail.size(); + cr_bt_info.push_back(info); + return cr_bt_info.size()-1; +} + + +void +Partition::cr_goto_backtrack_point(const unsigned int btpoint) +{ + assert(cr_enabled); + assert(btpoint < cr_bt_info.size()); + while(cr_created_trail.size() > cr_bt_info[btpoint].created_trail_index) + { + const unsigned int cell_index = cr_created_trail.back(); + cr_created_trail.pop_back(); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level != UINT_MAX); + assert(cr_cell.prev_next_ptr); + cr_cell.detach(); + } + + while(cr_splitted_level_trail.size() > + cr_bt_info[btpoint].splitted_level_trail_index) + { + const unsigned int dest_level = cr_splitted_level_trail.back(); + cr_splitted_level_trail.pop_back(); + assert(cr_max_level > 0); + assert(dest_level < cr_max_level); + while(cr_levels[cr_max_level]) { + CRCell *cr_cell = cr_levels[cr_max_level]; + cr_cell->detach(); + cr_create_at_level(cr_cell - cr_cells, dest_level); + } + cr_max_level--; + } + cr_bt_info.resize(btpoint); +} + + +void +Partition::cr_create_at_level(const unsigned int cell_index, + const unsigned int level) +{ + assert(cr_enabled); + assert(cell_index < N); + assert(level < N); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level == UINT_MAX); + assert(cr_cell.next == 0); + assert(cr_cell.prev_next_ptr == 0); + if(cr_levels[level]) + cr_levels[level]->prev_next_ptr = &(cr_cell.next); + cr_cell.next = cr_levels[level]; + cr_levels[level] = &cr_cell; + cr_cell.prev_next_ptr = &cr_levels[level]; + cr_cell.level = level; +} + + +void +Partition::cr_create_at_level_trailed(const unsigned int cell_index, + const unsigned int level) +{ + assert(cr_enabled); + cr_create_at_level(cell_index, level); + cr_created_trail.push_back(cell_index); +} + + +} // namespace bliss diff --git a/src/isomorphism/bliss/partition.hh b/src/isomorphism/bliss/partition.hh new file mode 100644 index 0000000..52cefda --- /dev/null +++ b/src/isomorphism/bliss/partition.hh @@ -0,0 +1,299 @@ +#ifndef BLISS_PARTITION_HH +#define BLISS_PARTITION_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + class Partition; +} + +#include +#include +#include "kstack.hh" +#include "kqueue.hh" +#include "graph.hh" + + +namespace bliss { + +/** + * \brief A class for refinable, backtrackable ordered partitions. + * + * This is rather a data structure with some helper functions than + * a proper self-contained class. + * That is, for efficiency reasons the fields of this class are directly + * manipulated from bliss::AbstractGraph and its subclasses. + * Conversely, some methods of this class modify the fields of + * bliss::AbstractGraph, too. + */ +class Partition +{ +public: + /** + * \brief Data structure for holding information about a cell in a Partition. + */ + class Cell + { + friend class Partition; + public: + unsigned int length; + /* Index of the first element of the cell in + the Partition::elements array */ + unsigned int first; + unsigned int max_ival; + unsigned int max_ival_count; + private: + bool in_splitting_queue; + public: + bool in_neighbour_heap; + /* Pointer to the next cell, null if this is the last one. */ + Cell* next; + Cell* prev; + Cell* next_nonsingleton; + Cell* prev_nonsingleton; + unsigned int split_level; + /** Is this a unit cell? */ + bool is_unit() const {return(length == 1); } + /** Is this cell in splitting queue? */ + bool is_in_splitting_queue() const {return(in_splitting_queue); } + }; + + +private: + + /** \internal + * Data structure for remembering information about splits in order to + * perform efficient backtracking over the splits. + */ + class RefInfo { + public: + unsigned int split_cell_first; + int prev_nonsingleton_first; + int next_nonsingleton_first; + }; + /** \internal + * A stack for remembering the splits, used for backtracking. + */ + KStack refinement_stack; + + class BacktrackInfo { + public: + unsigned int refinement_stack_size; + unsigned int cr_backtrack_point; + }; + + /** \internal + * The main stack for enabling backtracking. + */ + std::vector bt_stack; + +public: + AbstractGraph* graph; + + /* Used during equitable partition refinement */ + KQueue splitting_queue; + void splitting_queue_add(Cell* const cell); + Cell* splitting_queue_pop(); + bool splitting_queue_is_empty() const; + void splitting_queue_clear(); + + + /** Type for backtracking points. */ + typedef unsigned int BacktrackPoint; + + /** + * Get a new backtrack point for the current partition + */ + BacktrackPoint set_backtrack_point(); + + /** + * Backtrack to the point \a p and remove it. + */ + void goto_backtrack_point(BacktrackPoint p); + + /** + * Split the non-unit Cell \a cell = {\a element,e1,e2,...,en} containing + * the element \a element in two: + * \a cell = {e1,...,en} and \a newcell = {\a element}. + * @param cell a non-unit Cell + * @param element an element in \a cell + * @return the new unit Cell \a newcell + */ + Cell* individualize(Cell* const cell, + const unsigned int element); + + Cell* aux_split_in_two(Cell* const cell, + const unsigned int first_half_size); + + +private: + unsigned int N; + Cell* cells; + Cell* free_cells; + unsigned int discrete_cell_count; +public: + Cell* first_cell; + Cell* first_nonsingleton_cell; + unsigned int *elements; + /* invariant_values[e] gives the invariant value of the element e */ + unsigned int *invariant_values; + /* element_to_cell_map[e] gives the cell of the element e */ + Cell **element_to_cell_map; + /** Get the cell of the element \a e */ + Cell* get_cell(const unsigned int e) const { + assert(e < N); + return element_to_cell_map[e]; + } + /* in_pos[e] points to the elements array s.t. *in_pos[e] = e */ + unsigned int **in_pos; + + Partition(); + ~Partition(); + + /** + * Initialize the partition to the unit partition (all elements in one cell) + * over the \a N > 0 elements {0,...,\a N-1}. + */ + void init(const unsigned int N); + + /** + * Returns true iff the partition is discrete, meaning that all + * the elements are in their own cells. + */ + bool is_discrete() const {return(free_cells == 0); } + + unsigned int nof_discrete_cells() const {return(discrete_cell_count); } + + /* + * Splits the Cell \a cell into [cell_1,...,cell_n] + * according to the invariant_values of the elements in \a cell. + * After splitting, cell_1 == \a cell. + * Returns the pointer to the Cell cell_n; + * cell_n != cell iff the Cell \a cell was actually splitted. + * The flag \a max_ival_info_ok indicates whether the max_ival and + * max_ival_count fields of the Cell \a cell have consistent values + * when the method is called. + * Clears the invariant values of elements in the Cell \a cell as well as + * the max_ival and max_ival_count fields of the Cell \a cell. + */ + Cell *zplit_cell(Cell * const cell, const bool max_ival_info_ok); + + /* + * Routines for component recursion + */ + void cr_init(); + void cr_free(); + unsigned int cr_get_level(const unsigned int cell_index) const; + unsigned int cr_split_level(const unsigned int level, + const std::vector& cells); + + /** Clear the invariant_values of the elements in the Cell \a cell. */ + void clear_ivs(Cell* const cell); + +private: + /* + * Component recursion data structures + */ + + /* Is component recursion support in use? */ + bool cr_enabled; + + class CRCell { + public: + unsigned int level; + CRCell* next; + CRCell** prev_next_ptr; + void detach() { + if(next) + next->prev_next_ptr = prev_next_ptr; + *(prev_next_ptr) = next; + level = UINT_MAX; + next = 0; + prev_next_ptr = 0; + } + }; + CRCell* cr_cells; + CRCell** cr_levels; + class CR_BTInfo { + public: + unsigned int created_trail_index; + unsigned int splitted_level_trail_index; + }; + std::vector cr_created_trail; + std::vector cr_splitted_level_trail; + std::vector cr_bt_info; + unsigned int cr_max_level; + void cr_create_at_level(const unsigned int cell_index, unsigned int level); + void cr_create_at_level_trailed(const unsigned int cell_index, unsigned int level); + unsigned int cr_get_backtrack_point(); + void cr_goto_backtrack_point(const unsigned int btpoint); + + + /* + * + * Auxiliary routines for sorting and splitting cells + * + */ + Cell* sort_and_split_cell1(Cell* cell); + Cell* sort_and_split_cell255(Cell* const cell, const unsigned int max_ival); + bool shellsort_cell(Cell* cell); + Cell* split_cell(Cell* const cell); + + /* + * Some auxiliary stuff needed for distribution count sorting. + * To make the code thread-safe (modulo the requirement that each graph is + * only accessed in one thread at a time), the arrays are owned by + * the partition instance, not statically defined. + */ + unsigned int dcs_count[256]; + unsigned int dcs_start[256]; + void dcs_cumulate_count(const unsigned int max); +}; + + +inline Partition::Cell* +Partition::splitting_queue_pop() +{ + assert(!splitting_queue.is_empty()); + Cell* const cell = splitting_queue.pop_front(); + assert(cell->in_splitting_queue); + cell->in_splitting_queue = false; + return cell; +} + +inline bool +Partition::splitting_queue_is_empty() const +{ + return splitting_queue.is_empty(); +} + + +inline unsigned int +Partition::cr_get_level(const unsigned int cell_index) const +{ + assert(cr_enabled); + assert(cell_index < N); + assert(cr_cells[cell_index].level != UINT_MAX); + return(cr_cells[cell_index].level); +} + +} // namespace bliss + +#endif // BLISS_PARTITION_HH diff --git a/src/isomorphism/bliss/stats.hh b/src/isomorphism/bliss/stats.hh new file mode 100644 index 0000000..5294581 --- /dev/null +++ b/src/isomorphism/bliss/stats.hh @@ -0,0 +1,87 @@ +#ifndef BLISS_STATS_HH +#define BLISS_STATS_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include "graph.hh" +#include "bignum.hh" + +namespace bliss { + +/** + * \brief Statistics returned by the bliss search algorithm. + */ +class Stats +{ + friend class AbstractGraph; + /** \internal The size of the automorphism group. */ + BigNum group_size; + /** \internal An approximation (due to possible overflows) of + * the size of the automorphism group. */ + long double group_size_approx; + /** \internal The number of nodes in the search tree. */ + long unsigned int nof_nodes; + /** \internal The number of leaf nodes in the search tree. */ + long unsigned int nof_leaf_nodes; + /** \internal The number of bad nodes in the search tree. */ + long unsigned int nof_bad_nodes; + /** \internal The number of canonical representative updates. */ + long unsigned int nof_canupdates; + /** \internal The number of generator permutations. */ + long unsigned int nof_generators; + /** \internal The maximal depth of the search tree. */ + unsigned long int max_level; + /** \internal Reset the statistics. */ + void reset() + { + group_size.assign(1); + group_size_approx = 1.0; + nof_nodes = 0; + nof_leaf_nodes = 0; + nof_bad_nodes = 0; + nof_canupdates = 0; + nof_generators = 0; + max_level = 0; + } +public: + Stats() { reset(); } + + /** The size of the automorphism group. */ + const BigNum& get_group_size() const {return group_size;} + /** An approximation (due to possible overflows/rounding errors) of + * the size of the automorphism group. */ + long double get_group_size_approx() const {return group_size_approx;} + /** The number of nodes in the search tree. */ + long unsigned int get_nof_nodes() const {return nof_nodes;} + /** The number of leaf nodes in the search tree. */ + long unsigned int get_nof_leaf_nodes() const {return nof_leaf_nodes;} + /** The number of bad nodes in the search tree. */ + long unsigned int get_nof_bad_nodes() const {return nof_bad_nodes;} + /** The number of canonical representative updates. */ + long unsigned int get_nof_canupdates() const {return nof_canupdates;} + /** The number of generator permutations. */ + long unsigned int get_nof_generators() const {return nof_generators;} + /** The maximal depth of the search tree. */ + unsigned long int get_max_level() const {return max_level;} +}; + +} // namespace bliss + +#endif // BLISS_STATS_HH diff --git a/src/isomorphism/bliss/uintseqhash.cc b/src/isomorphism/bliss/uintseqhash.cc new file mode 100644 index 0000000..3e6f2b3 --- /dev/null +++ b/src/isomorphism/bliss/uintseqhash.cc @@ -0,0 +1,117 @@ +#include "uintseqhash.hh" + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/* + * Random bits generated by + * http://www.fourmilab.ch/hotbits/ + */ +static unsigned int rtab[256] = { + 0xAEAA35B8, 0x65632E16, 0x155EDBA9, 0x01349B39, + 0x8EB8BD97, 0x8E4C5367, 0x8EA78B35, 0x2B1B4072, + 0xC1163893, 0x269A8642, 0xC79D7F6D, 0x6A32DEA0, + 0xD4D2DA56, 0xD96D4F47, 0x47B5F48A, 0x2587C6BF, + 0x642B71D8, 0x5DBBAF58, 0x5C178169, 0xA16D9279, + 0x75CDA063, 0x291BC48B, 0x01AC2F47, 0x5416DF7C, + 0x45307514, 0xB3E1317B, 0xE1C7A8DE, 0x3ACDAC96, + 0x11B96831, 0x32DE22DD, 0x6A1DA93B, 0x58B62381, + 0x283810E2, 0xBC30E6A6, 0x8EE51705, 0xB06E8DFB, + 0x729AB12A, 0xA9634922, 0x1A6E8525, 0x49DD4E19, + 0xE5DB3D44, 0x8C5B3A02, 0xEBDE2864, 0xA9146D9F, + 0x736D2CB4, 0xF5229F42, 0x712BA846, 0x20631593, + 0x89C02603, 0xD5A5BF6A, 0x823F4E18, 0x5BE5DEFF, + 0x1C4EBBFA, 0x5FAB8490, 0x6E559B0C, 0x1FE528D6, + 0xB3198066, 0x4A965EB5, 0xFE8BB3D5, 0x4D2F6234, + 0x5F125AA4, 0xBCC640FA, 0x4F8BC191, 0xA447E537, + 0xAC474D3C, 0x703BFA2C, 0x617DC0E7, 0xF26299D7, + 0xC90FD835, 0x33B71C7B, 0x6D83E138, 0xCBB1BB14, + 0x029CF5FF, 0x7CBD093D, 0x4C9825EF, 0x845C4D6D, + 0x124349A5, 0x53942D21, 0x800E60DA, 0x2BA6EB7F, + 0xCEBF30D3, 0xEB18D449, 0xE281F724, 0x58B1CB09, + 0xD469A13D, 0x9C7495C3, 0xE53A7810, 0xA866C08E, + 0x832A038B, 0xDDDCA484, 0xD5FE0DDE, 0x0756002B, + 0x2FF51342, 0x60FEC9C8, 0x061A53E3, 0x47B1884E, + 0xDC17E461, 0xA17A6A37, 0x3158E7E2, 0xA40D873B, + 0x45AE2140, 0xC8F36149, 0x63A4EE2D, 0xD7107447, + 0x6F90994F, 0x5006770F, 0xC1F3CA9A, 0x91B317B2, + 0xF61B4406, 0xA8C9EE8F, 0xC6939B75, 0xB28BBC3B, + 0x36BF4AEF, 0x3B12118D, 0x4D536ECF, 0x9CF4B46B, + 0xE8AB1E03, 0x8225A360, 0x7AE4A130, 0xC4EE8B50, + 0x50651797, 0x5BB4C59F, 0xD120EE47, 0x24F3A386, + 0xBE579B45, 0x3A378EFC, 0xC5AB007B, 0x3668942B, + 0x2DBDCC3A, 0x6F37F64C, 0xC24F862A, 0xB6F97FCF, + 0x9E4FA23D, 0x551AE769, 0x46A8A5A6, 0xDC1BCFDD, + 0x8F684CF9, 0x501D811B, 0x84279F80, 0x2614E0AC, + 0x86445276, 0xAEA0CE71, 0x0812250F, 0xB586D18A, + 0xC68D721B, 0x44514E1D, 0x37CDB99A, 0x24731F89, + 0xFA72E589, 0x81E6EBA2, 0x15452965, 0x55523D9D, + 0x2DC47E14, 0x2E7FA107, 0xA7790F23, 0x40EBFDBB, + 0x77E7906B, 0x6C1DB960, 0x1A8B9898, 0x65FA0D90, + 0xED28B4D8, 0x34C3ED75, 0x768FD2EC, 0xFAB60BCB, + 0x962C75F4, 0x304F0498, 0x0A41A36B, 0xF7DE2A4A, + 0xF4770FE2, 0x73C93BBB, 0xD21C82C5, 0x6C387447, + 0x8CDB4CB9, 0x2CC243E8, 0x41859E3D, 0xB667B9CB, + 0x89681E8A, 0x61A0526C, 0x883EDDDC, 0x539DE9A4, + 0xC29E1DEC, 0x97C71EC5, 0x4A560A66, 0xBD7ECACF, + 0x576AE998, 0x31CE5616, 0x97172A6C, 0x83D047C4, + 0x274EA9A8, 0xEB31A9DA, 0x327209B5, 0x14D1F2CB, + 0x00FE1D96, 0x817DBE08, 0xD3E55AED, 0xF2D30AFC, + 0xFB072660, 0x866687D6, 0x92552EB9, 0xEA8219CD, + 0xF7927269, 0xF1948483, 0x694C1DF5, 0xB7D8B7BF, + 0xFFBC5D2F, 0x2E88B849, 0x883FD32B, 0xA0331192, + 0x8CB244DF, 0x41FAF895, 0x16902220, 0x97FB512A, + 0x2BEA3CC4, 0xAF9CAE61, 0x41ACD0D5, 0xFD2F28FF, + 0xE780ADFA, 0xB3A3A76E, 0x7112AD87, 0x7C3D6058, + 0x69E64FFF, 0xE5F8617C, 0x8580727C, 0x41F54F04, + 0xD72BE498, 0x653D1795, 0x1275A327, 0x14B499D4, + 0x4E34D553, 0x4687AA39, 0x68B64292, 0x5C18ABC3, + 0x41EABFCC, 0x92A85616, 0x82684CF8, 0x5B9F8A4E, + 0x35382FFE, 0xFB936318, 0x52C08E15, 0x80918B2E, + 0x199EDEE0, 0xA9470163, 0xEC44ACDD, 0x612D6735, + 0x8F88EA7D, 0x759F5EA4, 0xE5CC7240, 0x68CFEB8B, + 0x04725601, 0x0C22C23E, 0x5BC97174, 0x89965841, + 0x5D939479, 0x690F338A, 0x3C2D4380, 0xDAE97F2B +}; + + +void UintSeqHash::update(unsigned int i) +{ + i++; + while(i > 0) + { + h ^= rtab[i & 0xff]; +#if 1 + const unsigned int b = (h & 0x80000000) >> 31; + i = i >> 8; + h = (h << 1) | b; +#else + const unsigned int b = h & 0x80000000; + h = h << 1; + if(b != 0) + h++; + i = i >> 8; +#endif + } +} + + +} // namespace bliss diff --git a/src/isomorphism/bliss/uintseqhash.hh b/src/isomorphism/bliss/uintseqhash.hh new file mode 100644 index 0000000..4ac4e01 --- /dev/null +++ b/src/isomorphism/bliss/uintseqhash.hh @@ -0,0 +1,63 @@ +#ifndef BLISS_UINTSEQHASH_HH +#define BLISS_UINTSEQHASH_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** + * \brief A updatable hash for sequences of unsigned ints. + */ +class UintSeqHash +{ +protected: + unsigned int h; +public: + UintSeqHash() {h = 0; } + UintSeqHash(const UintSeqHash &other) {h = other.h; } + UintSeqHash& operator=(const UintSeqHash &other) {h = other.h; return *this; } + + /** Reset the hash value. */ + void reset() {h = 0; } + + /** Add the unsigned int \a n to the sequence. */ + void update(unsigned int n); + + /** Get the hash value of the sequence seen so far. */ + unsigned int get_value() const {return h; } + + /** Compare the hash values of this and \a other. + * Return -1/0/1 if the value of this is smaller/equal/greater than + * that of \a other. */ + int cmp(const UintSeqHash &other) const { + return (h < other.h)?-1:((h == other.h)?0:1); + } + /** An abbreviation for cmp(other) < 0 */ + bool is_lt(const UintSeqHash &other) const {return cmp(other) < 0; } + /** An abbreviation for cmp(other) <= 0 */ + bool is_le(const UintSeqHash &other) const {return cmp(other) <= 0; } + /** An abbreviation for cmp(other) == 0 */ + bool is_equal(const UintSeqHash &other) const {return cmp(other) == 0; } +}; + + +} // namespace bliss + +#endif // BLISS_UINTSEQHASH_HH diff --git a/src/isomorphism/bliss/utils.cc b/src/isomorphism/bliss/utils.cc new file mode 100644 index 0000000..e2913cb --- /dev/null +++ b/src/isomorphism/bliss/utils.cc @@ -0,0 +1,60 @@ +#include +#include "utils.hh" + +/* Allow using 'and' instead of '&&' with MSVC */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +bool +is_permutation(const unsigned int N, const unsigned int* perm) +{ + if(N == 0) + return true; + std::vector m(N, false); + for(unsigned int i = 0; i < N; i++) { + if(perm[i] >= N) return false; + if(m[perm[i]]) return false; + m[perm[i]] = true; + } + return true; +} + +bool +is_permutation(const std::vector& perm) +{ + const unsigned int N = perm.size(); + if(N == 0) + return true; + std::vector m(N, false); + for(unsigned int i = 0; i < N; i++) { + if(perm[i] >= N) return false; + if(m[perm[i]]) return false; + m[perm[i]] = true; + } + return true; +} + + +} // namespace bliss diff --git a/src/isomorphism/bliss/utils.hh b/src/isomorphism/bliss/utils.hh new file mode 100644 index 0000000..4f3514a --- /dev/null +++ b/src/isomorphism/bliss/utils.hh @@ -0,0 +1,46 @@ +#ifndef BLISS_UTILS_HH +#define BLISS_UTILS_HH + +/* + Copyright (c) 2003-2021 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +/** + * \file + * \brief Some small utilities. + */ + +#include + +namespace bliss { + +/** + * Check whether \a perm is a valid permutation on {0,...,N-1}. + * Slow, mainly for debugging and validation purposes. + */ +bool is_permutation(const unsigned int N, const unsigned int* perm); + +/** + * Check whether \a perm is a valid permutation on {0,...,N-1}. + * Slow, mainly for debugging and validation purposes. + */ +bool is_permutation(const std::vector& perm); + +} // namespace bliss + +#endif // BLISS_UTILS_HH diff --git a/src/isomorphism/isoclasses.c b/src/isomorphism/isoclasses.c new file mode 100644 index 0000000..a325953 --- /dev/null +++ b/src/isomorphism/isoclasses.c @@ -0,0 +1,2936 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_isomorphism.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "isomorphism/isoclasses.h" + +/* + * Small labelled graphs are encoded into a compact representation, a "code", + * that fits into a single integer value. Each non-loop edge corresponds to + * a specific bit of the integer. The edge-to-bit mappings are stored in + * the "isoclass_idx" arrays while the bit-to-edge mappings are in the "classedges" + * arrays. + * + * The "isoclass2" array is a mapping from the code of each possible labelled + * graph to its isomorphism class. A canonical representative of each isomorphism + * class is stored in "isographs". + * + * In the names of arrays, the number refers to the vertex count, while "u" + * indicates undirected graphs (the other arrays store directed ones). + * + * Description of each array for graphs of size n: + * + * isosclass_idx represents an n-by-n matrix stored in column-major order. + * Element i,j of the matrix is an integer with a single bit set. This bit, + * if set, represents edge i-j in the graph code. + * + * isoclass2[code] gives the isomorphism class of the graph represented by code. + * Classes are labelled by integers starting at 0, after ordering them by the + * graph code of their smallest-code representative. + * + * isographs[class] is the code of a graph belonging to the given class. For each + * class, the representative with the smallest code is chosen. + * + * classedges[2*i] - classedges[2*i+1] are the endpoints of the edge represented + * by bit i in the code. Bits are numbered from most to least significant, thus + * the most significant one has index i=0. + */ + +const unsigned int igraph_i_isoclass_3_idx[] = { 0, 4, 16, 1, 0, 32, 2, 8, 0 }; + +const unsigned int igraph_i_isoclass_4_idx[] = { + 0, 8, 64, 512, 1, 0, 128, 1024, 2, 16, 0, 2048, 4, 32, 256, 0 +}; + +const unsigned int igraph_i_isoclass_3u_idx[] = { 0, 1, 2, 1, 0, 4, 2, 4, 0 }; + +const unsigned int igraph_i_isoclass_4u_idx[] = { + 0, 1, 2, 8, 1, 0, 4, 16, 2, 4, 0, 32, 8, 16, 32, 0 +}; + +const unsigned int igraph_i_isoclass_5u_idx[] = { + 0, 1, 2, 8, 64, 1, 0, 4, 16, 128, 2, 4, 0, 32, 256, 8, 16, 32, 0, 512, 64, 128, 256, 512, 0 +}; + +const unsigned int igraph_i_isoclass_6u_idx[] = { + 0, 1, 2, 8, 64, 1024, 1, 0, 4, 16, 128, 2048, 2, 4, 0, 32, 256, 4096, + 8, 16, 32, 0, 512, 8192, 64, 128, 256, 512, 0, 16384, 1024, 2048, + 4096, 8192, 16384, 0 +}; + +const unsigned int igraph_i_isoclass2_3[] = { + 0, 1, 1, 2, 1, 3, 4, 5, 1, 4, 6, 7, 2, 5, 7, 8, 1, 4, 3, 5, 6, 9, 9, 10, 4, 11, + 9, 12, 7, 12, 13, 14, 1, 6, 4, 7, 4, 9, 11, 12, 3, 9, 9, 13, 5, 10, 12, 14, 2, 7, 5, 8, + 7, 13, 12, 14, 5, 12, 10, 14, 8, 14, 14, 15 +}; + +const unsigned int igraph_i_isoclass2_3u[] = { + 0, 1, 1, 2, 1, 2, 2, 3 +}; + +const unsigned int igraph_i_isoclass2_4u[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 4, 5, 6, 6, 7, 1, 2, 5, 6, 2, 4, 6, 7, 2, 3, + 6, 7, 6, 7, 8, 9, 1, 5, 2, 6, 2, 6, 4, 7, 2, 6, 3, 7, 6, 8, 7, 9, 2, 6, 6, 8, + 3, 7, 7, 9, 4, 7, 7, 9, 7, 9, 9, 10 +}; + +const unsigned int igraph_i_isoclass2_4[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 4, 5, 6, 5, 6, 7, 8, 1, 5, 9, 10, + 11, 12, 13, 14, 2, 6, 10, 15, 12, 16, 17, 18, 1, 5, 11, 12, 9, 10, 13, 14, + 2, 6, 12, 16, 10, 15, 17, 18, 2, 7, 13, 17, 13, 17, 19, 20, 3, 8, 14, 18, + 14, 18, 20, 21, 1, 5, 4, 6, 5, 7, 6, 8, 9, 22, 22, 23, 24, 25, 25, 26, + 5, 27, 22, 28, 29, 30, 31, 32, 10, 28, 33, 34, 35, 36, 37, 38, 11, 29, 39, 40, + 41, 42, 43, 44, 13, 31, 45, 46, 47, 48, 49, 50, 12, 30, 45, 51, 52, 53, 54, 55, + 14, 32, 56, 57, 58, 59, 60, 61, 1, 9, 5, 10, 11, 13, 12, 14, 5, 22, 27, 28, + 29, 31, 30, 32, 4, 22, 22, 33, 39, 45, 45, 56, 6, 23, 28, 34, 40, 46, 51, 57, + 5, 24, 29, 35, 41, 47, 52, 58, 7, 25, 30, 36, 42, 48, 53, 59, 6, 25, 31, 37, + 43, 49, 54, 60, 8, 26, 32, 38, 44, 50, 55, 61, 2, 10, 6, 15, 12, 17, 16, 18, + 10, 33, 28, 34, 35, 37, 36, 38, 6, 28, 23, 34, 40, 51, 46, 57, 15, 34, 34, 62, + 63, 64, 64, 65, 12, 35, 40, 63, 66, 67, 68, 69, 17, 37, 51, 64, 67, 70, 71, 72, + 16, 36, 46, 64, 68, 71, 73, 74, 18, 38, 57, 65, 69, 72, 74, 75, 1, 11, 5, 12, + 9, 13, 10, 14, 11, 39, 29, 40, 41, 43, 42, 44, 5, 29, 24, 35, 41, 52, 47, 58, + 12, 40, 35, 63, 66, 68, 67, 69, 9, 41, 41, 66, 76, 77, 77, 78, 13, 43, 52, 68, + 77, 79, 80, 81, 10, 42, 47, 67, 77, 80, 82, 83, 14, 44, 58, 69, 78, 81, 83, 84, + 2, 12, 6, 16, 10, 17, 15, 18, 13, 45, 31, 46, 47, 49, 48, 50, 7, 30, 25, 36, + 42, 53, 48, 59, 17, 51, 37, 64, 67, 71, 70, 72, 13, 52, 43, 68, 77, 80, 79, 81, + 19, 54, 54, 73, 82, 85, 85, 86, 17, 53, 49, 71, 80, 87, 85, 88, 20, 55, 60, 74, + 83, 88, 89, 90, 2, 13, 7, 17, 13, 19, 17, 20, 12, 45, 30, 51, 52, 54, 53, 55, + 6, 31, 25, 37, 43, 54, 49, 60, 16, 46, 36, 64, 68, 73, 71, 74, 10, 47, 42, 67, + 77, 82, 80, 83, 17, 49, 53, 71, 80, 85, 87, 88, 15, 48, 48, 70, 79, 85, 85, 89, + 18, 50, 59, 72, 81, 86, 88, 90, 3, 14, 8, 18, 14, 20, 18, 21, 14, 56, 32, 57, + 58, 60, 59, 61, 8, 32, 26, 38, 44, 55, 50, 61, 18, 57, 38, 65, 69, 74, 72, 75, + 14, 58, 44, 69, 78, 83, 81, 84, 20, 60, 55, 74, 83, 89, 88, 90, 18, 59, 50, 72, + 81, 88, 86, 90, 21, 61, 61, 75, 84, 90, 90, 91, 1, 5, 5, 7, 4, 6, 6, 8, + 9, 22, 24, 25, 22, 23, 25, 26, 11, 29, 41, 42, 39, 40, 43, 44, 13, 31, 47, 48, + 45, 46, 49, 50, 5, 27, 29, 30, 22, 28, 31, 32, 10, 28, 35, 36, 33, 34, 37, 38, + 12, 30, 52, 53, 45, 51, 54, 55, 14, 32, 58, 59, 56, 57, 60, 61, 9, 24, 22, 25, + 22, 25, 23, 26, 76, 92, 92, 93, 92, 93, 93, 94, 41, 95, 96, 97, 98, 99, 100, 101, + 77, 102, 103, 104, 105, 106, 107, 108, 41, 95, 98, 99, 96, 97, 100, 101, 77, 102, 105, 106, + 103, 104, 107, 108, 66, 109, 110, 111, 110, 111, 112, 113, 78, 114, 115, 116, 115, 116, 117, 118, + 11, 41, 29, 42, 39, 43, 40, 44, 41, 96, 95, 97, 98, 100, 99, 101, 39, 98, 98, 119, + 120, 121, 121, 122, 43, 100, 123, 124, 121, 125, 126, 127, 29, 95, 128, 129, 98, 123, 130, 131, + 42, 97, 129, 132, 119, 124, 133, 134, 40, 99, 130, 133, 121, 126, 135, 136, 44, 101, 131, 134, + 122, 127, 136, 137, 13, 47, 31, 48, 45, 49, 46, 50, 77, 103, 102, 104, 105, 107, 106, 108, + 43, 123, 100, 124, 121, 126, 125, 127, 79, 138, 138, 139, 140, 141, 141, 142, 52, 143, 130, 144, + 110, 145, 146, 147, 80, 148, 149, 150, 151, 152, 153, 154, 68, 155, 146, 156, 157, 158, 159, 160, + 81, 161, 162, 163, 164, 165, 166, 167, 5, 29, 27, 30, 22, 31, 28, 32, 41, 98, 95, 99, + 96, 100, 97, 101, 29, 128, 95, 129, 98, 130, 123, 131, 52, 130, 143, 144, 110, 146, 145, 147, + 24, 95, 95, 109, 92, 102, 102, 114, 47, 123, 143, 155, 103, 138, 148, 161, 35, 129, 143, 168, + 105, 149, 169, 170, 58, 131, 171, 172, 115, 162, 173, 174, 10, 35, 28, 36, 33, 37, 34, 38, + 77, 105, 102, 106, 103, 107, 104, 108, 42, 129, 97, 132, 119, 133, 124, 134, 80, 149, 148, 150, + 151, 153, 152, 154, 47, 143, 123, 155, 103, 148, 138, 161, 82, 169, 169, 175, 176, 177, 177, 178, + 67, 168, 145, 179, 151, 180, 181, 182, 83, 170, 173, 183, 184, 185, 186, 187, 12, 52, 30, 53, + 45, 54, 51, 55, 66, 110, 109, 111, 110, 112, 111, 113, 40, 130, 99, 133, 121, 135, 126, 136, + 68, 146, 155, 156, 157, 159, 158, 160, 35, 143, 129, 168, 105, 169, 149, 170, 67, 145, 168, 179, + 151, 181, 180, 182, 63, 144, 144, 188, 140, 189, 189, 190, 69, 147, 172, 191, 164, 192, 193, 194, + 14, 58, 32, 59, 56, 60, 57, 61, 78, 115, 114, 116, 115, 117, 116, 118, 44, 131, 101, 134, + 122, 136, 127, 137, 81, 162, 161, 163, 164, 166, 165, 167, 58, 171, 131, 172, 115, 173, 162, 174, + 83, 173, 170, 183, 184, 186, 185, 187, 69, 172, 147, 191, 164, 193, 192, 194, 84, 174, 174, 195, + 196, 197, 197, 198, 1, 9, 11, 13, 5, 10, 12, 14, 5, 22, 29, 31, 27, 28, 30, 32, + 5, 24, 41, 47, 29, 35, 52, 58, 7, 25, 42, 48, 30, 36, 53, 59, 4, 22, 39, 45, + 22, 33, 45, 56, 6, 23, 40, 46, 28, 34, 51, 57, 6, 25, 43, 49, 31, 37, 54, 60, + 8, 26, 44, 50, 32, 38, 55, 61, 11, 41, 39, 43, 29, 42, 40, 44, 41, 96, 98, 100, + 95, 97, 99, 101, 29, 95, 98, 123, 128, 129, 130, 131, 42, 97, 119, 124, 129, 132, 133, 134, + 39, 98, 120, 121, 98, 119, 121, 122, 43, 100, 121, 125, 123, 124, 126, 127, 40, 99, 121, 126, + 130, 133, 135, 136, 44, 101, 122, 127, 131, 134, 136, 137, 9, 76, 41, 77, 41, 77, 66, 78, + 24, 92, 95, 102, 95, 102, 109, 114, 22, 92, 96, 103, 98, 105, 110, 115, 25, 93, 97, 104, + 99, 106, 111, 116, 22, 92, 98, 105, 96, 103, 110, 115, 25, 93, 99, 106, 97, 104, 111, 116, + 23, 93, 100, 107, 100, 107, 112, 117, 26, 94, 101, 108, 101, 108, 113, 118, 13, 77, 43, 79, + 52, 80, 68, 81, 47, 103, 123, 138, 143, 148, 155, 161, 31, 102, 100, 138, 130, 149, 146, 162, + 48, 104, 124, 139, 144, 150, 156, 163, 45, 105, 121, 140, 110, 151, 157, 164, 49, 107, 126, 141, + 145, 152, 158, 165, 46, 106, 125, 141, 146, 153, 159, 166, 50, 108, 127, 142, 147, 154, 160, 167, + 5, 41, 29, 52, 24, 47, 35, 58, 29, 98, 128, 130, 95, 123, 129, 131, 27, 95, 95, 143, + 95, 143, 143, 171, 30, 99, 129, 144, 109, 155, 168, 172, 22, 96, 98, 110, 92, 103, 105, 115, + 31, 100, 130, 146, 102, 138, 149, 162, 28, 97, 123, 145, 102, 148, 169, 173, 32, 101, 131, 147, + 114, 161, 170, 174, 12, 66, 40, 68, 35, 67, 63, 69, 52, 110, 130, 146, 143, 145, 144, 147, + 30, 109, 99, 155, 129, 168, 144, 172, 53, 111, 133, 156, 168, 179, 188, 191, 45, 110, 121, 157, + 105, 151, 140, 164, 54, 112, 135, 159, 169, 181, 189, 192, 51, 111, 126, 158, 149, 180, 189, 193, + 55, 113, 136, 160, 170, 182, 190, 194, 10, 77, 42, 80, 47, 82, 67, 83, 35, 105, 129, 149, + 143, 169, 168, 170, 28, 102, 97, 148, 123, 169, 145, 173, 36, 106, 132, 150, 155, 175, 179, 183, + 33, 103, 119, 151, 103, 176, 151, 184, 37, 107, 133, 153, 148, 177, 180, 185, 34, 104, 124, 152, + 138, 177, 181, 186, 38, 108, 134, 154, 161, 178, 182, 187, 14, 78, 44, 81, 58, 83, 69, 84, + 58, 115, 131, 162, 171, 173, 172, 174, 32, 114, 101, 161, 131, 170, 147, 174, 59, 116, 134, 163, + 172, 183, 191, 195, 56, 115, 122, 164, 115, 184, 164, 196, 60, 117, 136, 166, 173, 186, 193, 197, + 57, 116, 127, 165, 162, 185, 192, 197, 61, 118, 137, 167, 174, 187, 194, 198, 2, 10, 12, 17, + 6, 15, 16, 18, 10, 33, 35, 37, 28, 34, 36, 38, 12, 35, 66, 67, 40, 63, 68, 69, + 17, 37, 67, 70, 51, 64, 71, 72, 6, 28, 40, 51, 23, 34, 46, 57, 15, 34, 63, 64, + 34, 62, 64, 65, 16, 36, 68, 71, 46, 64, 73, 74, 18, 38, 69, 72, 57, 65, 74, 75, + 13, 47, 45, 49, 31, 48, 46, 50, 77, 103, 105, 107, 102, 104, 106, 108, 52, 143, 110, 145, + 130, 144, 146, 147, 80, 148, 151, 152, 149, 150, 153, 154, 43, 123, 121, 126, 100, 124, 125, 127, + 79, 138, 140, 141, 138, 139, 141, 142, 68, 155, 157, 158, 146, 156, 159, 160, 81, 161, 164, 165, + 162, 163, 166, 167, 13, 77, 52, 80, 43, 79, 68, 81, 47, 103, 143, 148, 123, 138, 155, 161, + 45, 105, 110, 151, 121, 140, 157, 164, 49, 107, 145, 152, 126, 141, 158, 165, 31, 102, 130, 149, + 100, 138, 146, 162, 48, 104, 144, 150, 124, 139, 156, 163, 46, 106, 146, 153, 125, 141, 159, 166, + 50, 108, 147, 154, 127, 142, 160, 167, 19, 82, 54, 85, 54, 85, 73, 86, 82, 176, 169, 177, + 169, 177, 175, 178, 54, 169, 112, 181, 135, 189, 159, 192, 85, 177, 181, 199, 189, 200, 201, 202, + 54, 169, 135, 189, 112, 181, 159, 192, 85, 177, 189, 200, 181, 199, 201, 202, 73, 175, 159, 201, + 159, 201, 203, 204, 86, 178, 192, 202, 192, 202, 204, 205, 7, 42, 30, 53, 25, 48, 36, 59, + 42, 119, 129, 133, 97, 124, 132, 134, 30, 129, 109, 168, 99, 144, 155, 172, 53, 133, 168, 188, + 111, 156, 179, 191, 25, 97, 99, 111, 93, 104, 106, 116, 48, 124, 144, 156, 104, 139, 150, 163, + 36, 132, 155, 179, 106, 150, 175, 183, 59, 134, 172, 191, 116, 163, 183, 195, 17, 67, 51, 71, + 37, 70, 64, 72, 80, 151, 149, 153, 148, 152, 150, 154, 53, 168, 111, 179, 133, 188, 156, 191, + 87, 180, 180, 206, 180, 206, 206, 207, 49, 145, 126, 158, 107, 152, 141, 165, 85, 181, 189, 201, + 177, 199, 200, 202, 71, 179, 158, 208, 153, 206, 201, 209, 88, 182, 193, 209, 185, 210, 211, 212, + 17, 80, 53, 87, 49, 85, 71, 88, 67, 151, 168, 180, 145, 181, 179, 182, 51, 149, 111, 180, + 126, 189, 158, 193, 71, 153, 179, 206, 158, 201, 208, 209, 37, 148, 133, 180, 107, 177, 153, 185, + 70, 152, 188, 206, 152, 199, 206, 210, 64, 150, 156, 206, 141, 200, 201, 211, 72, 154, 191, 207, + 165, 202, 209, 212, 20, 83, 55, 88, 60, 89, 74, 90, 83, 184, 170, 185, 173, 186, 183, 187, + 55, 170, 113, 182, 136, 190, 160, 194, 88, 185, 182, 210, 193, 211, 209, 212, 60, 173, 136, 193, + 117, 186, 166, 197, 89, 186, 190, 211, 186, 213, 211, 214, 74, 183, 160, 209, 166, 211, 204, 215, + 90, 187, 194, 212, 197, 214, 215, 216, 1, 11, 9, 13, 5, 12, 10, 14, 11, 39, 41, 43, + 29, 40, 42, 44, 9, 41, 76, 77, 41, 66, 77, 78, 13, 43, 77, 79, 52, 68, 80, 81, + 5, 29, 41, 52, 24, 35, 47, 58, 12, 40, 66, 68, 35, 63, 67, 69, 10, 42, 77, 80, + 47, 67, 82, 83, 14, 44, 78, 81, 58, 69, 83, 84, 5, 29, 22, 31, 27, 30, 28, 32, + 41, 98, 96, 100, 95, 99, 97, 101, 24, 95, 92, 102, 95, 109, 102, 114, 47, 123, 103, 138, + 143, 155, 148, 161, 29, 128, 98, 130, 95, 129, 123, 131, 52, 130, 110, 146, 143, 144, 145, 147, + 35, 129, 105, 149, 143, 168, 169, 170, 58, 131, 115, 162, 171, 172, 173, 174, 5, 41, 24, 47, + 29, 52, 35, 58, 29, 98, 95, 123, 128, 130, 129, 131, 22, 96, 92, 103, 98, 110, 105, 115, + 31, 100, 102, 138, 130, 146, 149, 162, 27, 95, 95, 143, 95, 143, 143, 171, 30, 99, 109, 155, + 129, 144, 168, 172, 28, 97, 102, 148, 123, 145, 169, 173, 32, 101, 114, 161, 131, 147, 170, 174, + 7, 42, 25, 48, 30, 53, 36, 59, 42, 119, 97, 124, 129, 133, 132, 134, 25, 97, 93, 104, + 99, 111, 106, 116, 48, 124, 104, 139, 144, 156, 150, 163, 30, 129, 99, 144, 109, 168, 155, 172, + 53, 133, 111, 156, 168, 188, 179, 191, 36, 132, 106, 150, 155, 179, 175, 183, 59, 134, 116, 163, + 172, 191, 183, 195, 4, 39, 22, 45, 22, 45, 33, 56, 39, 120, 98, 121, 98, 121, 119, 122, + 22, 98, 92, 105, 96, 110, 103, 115, 45, 121, 105, 140, 110, 157, 151, 164, 22, 98, 96, 110, + 92, 105, 103, 115, 45, 121, 110, 157, 105, 140, 151, 164, 33, 119, 103, 151, 103, 151, 176, 184, + 56, 122, 115, 164, 115, 164, 184, 196, 6, 40, 23, 46, 28, 51, 34, 57, 43, 121, 100, 125, + 123, 126, 124, 127, 25, 99, 93, 106, 97, 111, 104, 116, 49, 126, 107, 141, 145, 158, 152, 165, + 31, 130, 100, 146, 102, 149, 138, 162, 54, 135, 112, 159, 169, 189, 181, 192, 37, 133, 107, 153, + 148, 180, 177, 185, 60, 136, 117, 166, 173, 193, 186, 197, 6, 43, 25, 49, 31, 54, 37, 60, + 40, 121, 99, 126, 130, 135, 133, 136, 23, 100, 93, 107, 100, 112, 107, 117, 46, 125, 106, 141, + 146, 159, 153, 166, 28, 123, 97, 145, 102, 169, 148, 173, 51, 126, 111, 158, 149, 189, 180, 193, + 34, 124, 104, 152, 138, 181, 177, 186, 57, 127, 116, 165, 162, 192, 185, 197, 8, 44, 26, 50, + 32, 55, 38, 61, 44, 122, 101, 127, 131, 136, 134, 137, 26, 101, 94, 108, 101, 113, 108, 118, + 50, 127, 108, 142, 147, 160, 154, 167, 32, 131, 101, 147, 114, 170, 161, 174, 55, 136, 113, 160, + 170, 190, 182, 194, 38, 134, 108, 154, 161, 182, 178, 187, 61, 137, 118, 167, 174, 194, 187, 198, + 2, 12, 10, 17, 6, 16, 15, 18, 13, 45, 47, 49, 31, 46, 48, 50, 13, 52, 77, 80, + 43, 68, 79, 81, 19, 54, 82, 85, 54, 73, 85, 86, 7, 30, 42, 53, 25, 36, 48, 59, + 17, 51, 67, 71, 37, 64, 70, 72, 17, 53, 80, 87, 49, 71, 85, 88, 20, 55, 83, 88, + 60, 74, 89, 90, 10, 35, 33, 37, 28, 36, 34, 38, 77, 105, 103, 107, 102, 106, 104, 108, + 47, 143, 103, 148, 123, 155, 138, 161, 82, 169, 176, 177, 169, 175, 177, 178, 42, 129, 119, 133, + 97, 132, 124, 134, 80, 149, 151, 153, 148, 150, 152, 154, 67, 168, 151, 180, 145, 179, 181, 182, + 83, 170, 184, 185, 173, 183, 186, 187, 12, 66, 35, 67, 40, 68, 63, 69, 52, 110, 143, 145, + 130, 146, 144, 147, 45, 110, 105, 151, 121, 157, 140, 164, 54, 112, 169, 181, 135, 159, 189, 192, + 30, 109, 129, 168, 99, 155, 144, 172, 53, 111, 168, 179, 133, 156, 188, 191, 51, 111, 149, 180, + 126, 158, 189, 193, 55, 113, 170, 182, 136, 160, 190, 194, 17, 67, 37, 70, 51, 71, 64, 72, + 80, 151, 148, 152, 149, 153, 150, 154, 49, 145, 107, 152, 126, 158, 141, 165, 85, 181, 177, 199, + 189, 201, 200, 202, 53, 168, 133, 188, 111, 179, 156, 191, 87, 180, 180, 206, 180, 206, 206, 207, + 71, 179, 153, 206, 158, 208, 201, 209, 88, 182, 185, 210, 193, 209, 211, 212, 6, 40, 28, 51, + 23, 46, 34, 57, 43, 121, 123, 126, 100, 125, 124, 127, 31, 130, 102, 149, 100, 146, 138, 162, + 54, 135, 169, 189, 112, 159, 181, 192, 25, 99, 97, 111, 93, 106, 104, 116, 49, 126, 145, 158, + 107, 141, 152, 165, 37, 133, 148, 180, 107, 153, 177, 185, 60, 136, 173, 193, 117, 166, 186, 197, + 15, 63, 34, 64, 34, 64, 62, 65, 79, 140, 138, 141, 138, 141, 139, 142, 48, 144, 104, 150, + 124, 156, 139, 163, 85, 189, 177, 200, 181, 201, 199, 202, 48, 144, 124, 156, 104, 150, 139, 163, + 85, 189, 181, 201, 177, 200, 199, 202, 70, 188, 152, 206, 152, 206, 199, 210, 89, 190, 186, 211, + 186, 211, 213, 214, 16, 68, 36, 71, 46, 73, 64, 74, 68, 157, 155, 158, 146, 159, 156, 160, + 46, 146, 106, 153, 125, 159, 141, 166, 73, 159, 175, 201, 159, 203, 201, 204, 36, 155, 132, 179, + 106, 175, 150, 183, 71, 158, 179, 208, 153, 201, 206, 209, 64, 156, 150, 206, 141, 201, 200, 211, + 74, 160, 183, 209, 166, 204, 211, 215, 18, 69, 38, 72, 57, 74, 65, 75, 81, 164, 161, 165, + 162, 166, 163, 167, 50, 147, 108, 154, 127, 160, 142, 167, 86, 192, 178, 202, 192, 204, 202, 205, + 59, 172, 134, 191, 116, 183, 163, 195, 88, 193, 182, 209, 185, 211, 210, 212, 72, 191, 154, 207, + 165, 209, 202, 212, 90, 194, 187, 212, 197, 215, 214, 216, 2, 13, 13, 19, 7, 17, 17, 20, + 12, 45, 52, 54, 30, 51, 53, 55, 10, 47, 77, 82, 42, 67, 80, 83, 17, 49, 80, 85, + 53, 71, 87, 88, 6, 31, 43, 54, 25, 37, 49, 60, 16, 46, 68, 73, 36, 64, 71, 74, + 15, 48, 79, 85, 48, 70, 85, 89, 18, 50, 81, 86, 59, 72, 88, 90, 12, 52, 45, 54, + 30, 53, 51, 55, 66, 110, 110, 112, 109, 111, 111, 113, 35, 143, 105, 169, 129, 168, 149, 170, + 67, 145, 151, 181, 168, 179, 180, 182, 40, 130, 121, 135, 99, 133, 126, 136, 68, 146, 157, 159, + 155, 156, 158, 160, 63, 144, 140, 189, 144, 188, 189, 190, 69, 147, 164, 192, 172, 191, 193, 194, + 10, 77, 47, 82, 42, 80, 67, 83, 35, 105, 143, 169, 129, 149, 168, 170, 33, 103, 103, 176, + 119, 151, 151, 184, 37, 107, 148, 177, 133, 153, 180, 185, 28, 102, 123, 169, 97, 148, 145, 173, + 36, 106, 155, 175, 132, 150, 179, 183, 34, 104, 138, 177, 124, 152, 181, 186, 38, 108, 161, 178, + 134, 154, 182, 187, 17, 80, 49, 85, 53, 87, 71, 88, 67, 151, 145, 181, 168, 180, 179, 182, + 37, 148, 107, 177, 133, 180, 153, 185, 70, 152, 152, 199, 188, 206, 206, 210, 51, 149, 126, 189, + 111, 180, 158, 193, 71, 153, 158, 201, 179, 206, 208, 209, 64, 150, 141, 200, 156, 206, 201, 211, + 72, 154, 165, 202, 191, 207, 209, 212, 6, 43, 31, 54, 25, 49, 37, 60, 40, 121, 130, 135, + 99, 126, 133, 136, 28, 123, 102, 169, 97, 145, 148, 173, 51, 126, 149, 189, 111, 158, 180, 193, + 23, 100, 100, 112, 93, 107, 107, 117, 46, 125, 146, 159, 106, 141, 153, 166, 34, 124, 138, 181, + 104, 152, 177, 186, 57, 127, 162, 192, 116, 165, 185, 197, 16, 68, 46, 73, 36, 71, 64, 74, + 68, 157, 146, 159, 155, 158, 156, 160, 36, 155, 106, 175, 132, 179, 150, 183, 71, 158, 153, 201, + 179, 208, 206, 209, 46, 146, 125, 159, 106, 153, 141, 166, 73, 159, 159, 203, 175, 201, 201, 204, + 64, 156, 141, 201, 150, 206, 200, 211, 74, 160, 166, 204, 183, 209, 211, 215, 15, 79, 48, 85, + 48, 85, 70, 89, 63, 140, 144, 189, 144, 189, 188, 190, 34, 138, 104, 177, 124, 181, 152, 186, + 64, 141, 150, 200, 156, 201, 206, 211, 34, 138, 124, 181, 104, 177, 152, 186, 64, 141, 156, 201, + 150, 200, 206, 211, 62, 139, 139, 199, 139, 199, 199, 213, 65, 142, 163, 202, 163, 202, 210, 214, + 18, 81, 50, 86, 59, 88, 72, 90, 69, 164, 147, 192, 172, 193, 191, 194, 38, 161, 108, 178, + 134, 182, 154, 187, 72, 165, 154, 202, 191, 209, 207, 212, 57, 162, 127, 192, 116, 185, 165, 197, + 74, 166, 160, 204, 183, 211, 209, 215, 65, 163, 142, 202, 163, 210, 202, 214, 75, 167, 167, 205, + 195, 212, 212, 216, 3, 14, 14, 20, 8, 18, 18, 21, 14, 56, 58, 60, 32, 57, 59, 61, + 14, 58, 78, 83, 44, 69, 81, 84, 20, 60, 83, 89, 55, 74, 88, 90, 8, 32, 44, 55, + 26, 38, 50, 61, 18, 57, 69, 74, 38, 65, 72, 75, 18, 59, 81, 88, 50, 72, 86, 90, + 21, 61, 84, 90, 61, 75, 90, 91, 14, 58, 56, 60, 32, 59, 57, 61, 78, 115, 115, 117, + 114, 116, 116, 118, 58, 171, 115, 173, 131, 172, 162, 174, 83, 173, 184, 186, 170, 183, 185, 187, + 44, 131, 122, 136, 101, 134, 127, 137, 81, 162, 164, 166, 161, 163, 165, 167, 69, 172, 164, 193, + 147, 191, 192, 194, 84, 174, 196, 197, 174, 195, 197, 198, 14, 78, 58, 83, 44, 81, 69, 84, + 58, 115, 171, 173, 131, 162, 172, 174, 56, 115, 115, 184, 122, 164, 164, 196, 60, 117, 173, 186, + 136, 166, 193, 197, 32, 114, 131, 170, 101, 161, 147, 174, 59, 116, 172, 183, 134, 163, 191, 195, + 57, 116, 162, 185, 127, 165, 192, 197, 61, 118, 174, 187, 137, 167, 194, 198, 20, 83, 60, 89, + 55, 88, 74, 90, 83, 184, 173, 186, 170, 185, 183, 187, 60, 173, 117, 186, 136, 193, 166, 197, + 89, 186, 186, 213, 190, 211, 211, 214, 55, 170, 136, 190, 113, 182, 160, 194, 88, 185, 193, 211, + 182, 210, 209, 212, 74, 183, 166, 211, 160, 209, 204, 215, 90, 187, 197, 214, 194, 212, 215, 216, + 8, 44, 32, 55, 26, 50, 38, 61, 44, 122, 131, 136, 101, 127, 134, 137, 32, 131, 114, 170, + 101, 147, 161, 174, 55, 136, 170, 190, 113, 160, 182, 194, 26, 101, 101, 113, 94, 108, 108, 118, + 50, 127, 147, 160, 108, 142, 154, 167, 38, 134, 161, 182, 108, 154, 178, 187, 61, 137, 174, 194, + 118, 167, 187, 198, 18, 69, 57, 74, 38, 72, 65, 75, 81, 164, 162, 166, 161, 165, 163, 167, + 59, 172, 116, 183, 134, 191, 163, 195, 88, 193, 185, 211, 182, 209, 210, 212, 50, 147, 127, 160, + 108, 154, 142, 167, 86, 192, 192, 204, 178, 202, 202, 205, 72, 191, 165, 209, 154, 207, 202, 212, + 90, 194, 197, 215, 187, 212, 214, 216, 18, 81, 59, 88, 50, 86, 72, 90, 69, 164, 172, 193, + 147, 192, 191, 194, 57, 162, 116, 185, 127, 192, 165, 197, 74, 166, 183, 211, 160, 204, 209, 215, + 38, 161, 134, 182, 108, 178, 154, 187, 72, 165, 191, 209, 154, 202, 207, 212, 65, 163, 163, 210, + 142, 202, 202, 214, 75, 167, 195, 212, 167, 205, 212, 216, 21, 84, 61, 90, 61, 90, 75, 91, + 84, 196, 174, 197, 174, 197, 195, 198, 61, 174, 118, 187, 137, 194, 167, 198, 90, 197, 187, 214, + 194, 215, 212, 216, 61, 174, 137, 194, 118, 187, 167, 198, 90, 197, 194, 215, 187, 214, 212, 216, + 75, 195, 167, 212, 167, 212, 205, 216, 91, 198, 198, 216, 198, 216, 216, 217 +}; + +const unsigned int igraph_i_isoclass2_5u[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 4, 5, 6, 6, 7, 1, 2, 5, 6, 2, 4, 6, + 7, 2, 3, 6, 7, 6, 7, 8, 9, 1, 5, 2, 6, 2, 6, 4, 7, 2, 6, 3, 7, 6, 8, + 7, 9, 2, 6, 6, 8, 3, 7, 7, 9, 4, 7, 7, 9, 7, 9, 9, 10, 1, 2, 2, 4, 5, + 6, 6, 7, 2, 4, 4, 11, 12, 13, 13, 14, 5, 6, 12, 13, 12, 13, 15, 16, + 6, 7, 13, 14, 15, 16, 17, 18, 5, 12, 6, 13, 12, 15, 13, 16, 6, 13, 7, + 14, 15, 17, 16, 18, 12, 15, 15, 17, 19, 20, 20, 21, 13, 16, 16, 18, + 20, 21, 21, 22, 1, 2, 5, 6, 2, 4, 6, 7, 5, 6, 12, 13, 12, 13, 15, 16, + 2, 4, 12, 13, 4, 11, 13, 14, 6, 7, 15, 16, 13, 14, 17, 18, 5, 12, 12, + 15, 6, 13, 13, 16, 12, 15, 19, 20, 15, 17, 20, 21, 6, 13, 15, 17, 7, + 14, 16, 18, 13, 16, 20, 21, 16, 18, 21, 22, 2, 3, 6, 7, 6, 7, 8, 9, + 6, 7, 13, 14, 15, 16, 17, 18, 6, 7, 15, 16, 13, 14, 17, 18, 8, 9, 17, + 18, 17, 18, 23, 24, 12, 19, 15, 20, 15, 20, 17, 21, 15, 20, 20, 25, + 26, 27, 27, 28, 15, 20, 26, 27, 20, 25, 27, 28, 17, 21, 27, 28, 27, + 28, 29, 30, 1, 5, 2, 6, 2, 6, 4, 7, 5, 12, 6, 13, 12, 15, 13, 16, 5, + 12, 12, 15, 6, 13, 13, 16, 12, 19, 15, 20, 15, 20, 17, 21, 2, 12, 4, + 13, 4, 13, 11, 14, 6, 15, 7, 16, 13, 17, 14, 18, 6, 15, 13, 17, 7, + 16, 14, 18, 13, 20, 16, 21, 16, 21, 18, 22, 2, 6, 3, 7, 6, 8, 7, 9, + 6, 13, 7, 14, 15, 17, 16, 18, 12, 15, 19, 20, 15, 17, 20, 21, 15, 20, + 20, 25, 26, 27, 27, 28, 6, 15, 7, 16, 13, 17, 14, 18, 8, 17, 9, 18, + 17, 23, 18, 24, 15, 26, 20, 27, 20, 27, 25, 28, 17, 27, 21, 28, 27, + 29, 28, 30, 2, 6, 6, 8, 3, 7, 7, 9, 12, 15, 15, 17, 19, 20, 20, 21, + 6, 13, 15, 17, 7, 14, 16, 18, 15, 20, 26, 27, 20, 25, 27, 28, 6, 15, + 13, 17, 7, 16, 14, 18, 15, 26, 20, 27, 20, 27, 25, 28, 8, 17, 17, 23, + 9, 18, 18, 24, 17, 27, 27, 29, 21, 28, 28, 30, 4, 7, 7, 9, 7, 9, 9, + 10, 13, 16, 16, 18, 20, 21, 21, 22, 13, 16, 20, 21, 16, 18, 21, 22, + 17, 21, 27, 28, 27, 28, 29, 30, 13, 20, 16, 21, 16, 21, 18, 22, 17, + 27, 21, 28, 27, 29, 28, 30, 17, 27, 27, 29, 21, 28, 28, 30, 23, 29, + 29, 31, 29, 31, 31, 32, 1, 5, 5, 12, 5, 12, 12, 19, 2, 6, 6, 13, 12, + 15, 15, 20, 2, 6, 12, 15, 6, 13, 15, 20, 4, 7, 13, 16, 13, 16, 17, + 21, 2, 12, 6, 15, 6, 15, 13, 20, 4, 13, 7, 16, 13, 17, 16, 21, 4, 13, + 13, 17, 7, 16, 16, 21, 11, 14, 14, 18, 14, 18, 18, 22, 2, 6, 6, 13, + 12, 15, 15, 20, 3, 7, 7, 14, 19, 20, 20, 25, 6, 8, 15, 17, 15, 17, + 26, 27, 7, 9, 16, 18, 20, 21, 27, 28, 6, 15, 8, 17, 15, 26, 17, 27, + 7, 16, 9, 18, 20, 27, 21, 28, 13, 17, 17, 23, 20, 27, 27, 29, 14, 18, + 18, 24, 25, 28, 28, 30, 2, 6, 12, 15, 6, 13, 15, 20, 6, 8, 15, 17, + 15, 17, 26, 27, 3, 7, 19, 20, 7, 14, 20, 25, 7, 9, 20, 21, 16, 18, + 27, 28, 6, 15, 15, 26, 8, 17, 17, 27, 13, 17, 20, 27, 17, 23, 27, 29, + 7, 16, 20, 27, 9, 18, 21, 28, 14, 18, 25, 28, 18, 24, 28, 30, 4, 7, + 13, 16, 13, 16, 17, 21, 7, 9, 16, 18, 20, 21, 27, 28, 7, 9, 20, 21, + 16, 18, 27, 28, 9, 10, 21, 22, 21, 22, 29, 30, 13, 20, 17, 27, 17, + 27, 23, 29, 16, 21, 21, 28, 27, 29, 29, 31, 16, 21, 27, 29, 21, 28, + 29, 31, 18, 22, 28, 30, 28, 30, 31, 32, 2, 12, 6, 15, 6, 15, 13, 20, + 6, 15, 8, 17, 15, 26, 17, 27, 6, 15, 15, 26, 8, 17, 17, 27, 13, 20, + 17, 27, 17, 27, 23, 29, 3, 19, 7, 20, 7, 20, 14, 25, 7, 20, 9, 21, + 16, 27, 18, 28, 7, 20, 16, 27, 9, 21, 18, 28, 14, 25, 18, 28, 18, 28, + 24, 30, 4, 13, 7, 16, 13, 17, 16, 21, 7, 16, 9, 18, 20, 27, 21, 28, + 13, 17, 20, 27, 17, 23, 27, 29, 16, 21, 21, 28, 27, 29, 29, 31, 7, + 20, 9, 21, 16, 27, 18, 28, 9, 21, 10, 22, 21, 29, 22, 30, 16, 27, 21, + 29, 21, 29, 28, 31, 18, 28, 22, 30, 28, 31, 30, 32, 4, 13, 13, 17, 7, + 16, 16, 21, 13, 17, 17, 23, 20, 27, 27, 29, 7, 16, 20, 27, 9, 18, 21, + 28, 16, 21, 27, 29, 21, 28, 29, 31, 7, 20, 16, 27, 9, 21, 18, 28, 16, + 27, 21, 29, 21, 29, 28, 31, 9, 21, 21, 29, 10, 22, 22, 30, 18, 28, + 28, 31, 22, 30, 30, 32, 11, 14, 14, 18, 14, 18, 18, 22, 14, 18, 18, + 24, 25, 28, 28, 30, 14, 18, 25, 28, 18, 24, 28, 30, 18, 22, 28, 30, + 28, 30, 31, 32, 14, 25, 18, 28, 18, 28, 24, 30, 18, 28, 22, 30, 28, + 31, 30, 32, 18, 28, 28, 31, 22, 30, 30, 32, 24, 30, 30, 32, 30, 32, + 32, 33 +}; + +const unsigned int igraph_i_isoclass2_6u[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 4, 5, 6, 6, 7, 1, 2, 5, 6, 2, 4, 6, + 7, 2, 3, 6, 7, 6, 7, 8, 9, 1, 5, 2, 6, 2, 6, 4, 7, 2, 6, 3, 7, 6, 8, + 7, 9, 2, 6, 6, 8, 3, 7, 7, 9, 4, 7, 7, 9, 7, 9, 9, 10, 1, 2, 2, 4, 5, + 6, 6, 7, 2, 4, 4, 11, 12, 13, 13, 14, 5, 6, 12, 13, 12, 13, 15, 16, + 6, 7, 13, 14, 15, 16, 17, 18, 5, 12, 6, 13, 12, 15, 13, 16, 6, 13, 7, + 14, 15, 17, 16, 18, 12, 15, 15, 17, 19, 20, 20, 21, 13, 16, 16, 18, + 20, 21, 21, 22, 1, 2, 5, 6, 2, 4, 6, 7, 5, 6, 12, 13, 12, 13, 15, 16, + 2, 4, 12, 13, 4, 11, 13, 14, 6, 7, 15, 16, 13, 14, 17, 18, 5, 12, 12, + 15, 6, 13, 13, 16, 12, 15, 19, 20, 15, 17, 20, 21, 6, 13, 15, 17, 7, + 14, 16, 18, 13, 16, 20, 21, 16, 18, 21, 22, 2, 3, 6, 7, 6, 7, 8, 9, + 6, 7, 13, 14, 15, 16, 17, 18, 6, 7, 15, 16, 13, 14, 17, 18, 8, 9, 17, + 18, 17, 18, 23, 24, 12, 19, 15, 20, 15, 20, 17, 21, 15, 20, 20, 25, + 26, 27, 27, 28, 15, 20, 26, 27, 20, 25, 27, 28, 17, 21, 27, 28, 27, + 28, 29, 30, 1, 5, 2, 6, 2, 6, 4, 7, 5, 12, 6, 13, 12, 15, 13, 16, 5, + 12, 12, 15, 6, 13, 13, 16, 12, 19, 15, 20, 15, 20, 17, 21, 2, 12, 4, + 13, 4, 13, 11, 14, 6, 15, 7, 16, 13, 17, 14, 18, 6, 15, 13, 17, 7, + 16, 14, 18, 13, 20, 16, 21, 16, 21, 18, 22, 2, 6, 3, 7, 6, 8, 7, 9, + 6, 13, 7, 14, 15, 17, 16, 18, 12, 15, 19, 20, 15, 17, 20, 21, 15, 20, + 20, 25, 26, 27, 27, 28, 6, 15, 7, 16, 13, 17, 14, 18, 8, 17, 9, 18, + 17, 23, 18, 24, 15, 26, 20, 27, 20, 27, 25, 28, 17, 27, 21, 28, 27, + 29, 28, 30, 2, 6, 6, 8, 3, 7, 7, 9, 12, 15, 15, 17, 19, 20, 20, 21, + 6, 13, 15, 17, 7, 14, 16, 18, 15, 20, 26, 27, 20, 25, 27, 28, 6, 15, + 13, 17, 7, 16, 14, 18, 15, 26, 20, 27, 20, 27, 25, 28, 8, 17, 17, 23, + 9, 18, 18, 24, 17, 27, 27, 29, 21, 28, 28, 30, 4, 7, 7, 9, 7, 9, 9, + 10, 13, 16, 16, 18, 20, 21, 21, 22, 13, 16, 20, 21, 16, 18, 21, 22, + 17, 21, 27, 28, 27, 28, 29, 30, 13, 20, 16, 21, 16, 21, 18, 22, 17, + 27, 21, 28, 27, 29, 28, 30, 17, 27, 27, 29, 21, 28, 28, 30, 23, 29, + 29, 31, 29, 31, 31, 32, 1, 5, 5, 12, 5, 12, 12, 19, 2, 6, 6, 13, 12, + 15, 15, 20, 2, 6, 12, 15, 6, 13, 15, 20, 4, 7, 13, 16, 13, 16, 17, + 21, 2, 12, 6, 15, 6, 15, 13, 20, 4, 13, 7, 16, 13, 17, 16, 21, 4, 13, + 13, 17, 7, 16, 16, 21, 11, 14, 14, 18, 14, 18, 18, 22, 2, 6, 6, 13, + 12, 15, 15, 20, 3, 7, 7, 14, 19, 20, 20, 25, 6, 8, 15, 17, 15, 17, + 26, 27, 7, 9, 16, 18, 20, 21, 27, 28, 6, 15, 8, 17, 15, 26, 17, 27, + 7, 16, 9, 18, 20, 27, 21, 28, 13, 17, 17, 23, 20, 27, 27, 29, 14, 18, + 18, 24, 25, 28, 28, 30, 2, 6, 12, 15, 6, 13, 15, 20, 6, 8, 15, 17, + 15, 17, 26, 27, 3, 7, 19, 20, 7, 14, 20, 25, 7, 9, 20, 21, 16, 18, + 27, 28, 6, 15, 15, 26, 8, 17, 17, 27, 13, 17, 20, 27, 17, 23, 27, 29, + 7, 16, 20, 27, 9, 18, 21, 28, 14, 18, 25, 28, 18, 24, 28, 30, 4, 7, + 13, 16, 13, 16, 17, 21, 7, 9, 16, 18, 20, 21, 27, 28, 7, 9, 20, 21, + 16, 18, 27, 28, 9, 10, 21, 22, 21, 22, 29, 30, 13, 20, 17, 27, 17, + 27, 23, 29, 16, 21, 21, 28, 27, 29, 29, 31, 16, 21, 27, 29, 21, 28, + 29, 31, 18, 22, 28, 30, 28, 30, 31, 32, 2, 12, 6, 15, 6, 15, 13, 20, + 6, 15, 8, 17, 15, 26, 17, 27, 6, 15, 15, 26, 8, 17, 17, 27, 13, 20, + 17, 27, 17, 27, 23, 29, 3, 19, 7, 20, 7, 20, 14, 25, 7, 20, 9, 21, + 16, 27, 18, 28, 7, 20, 16, 27, 9, 21, 18, 28, 14, 25, 18, 28, 18, 28, + 24, 30, 4, 13, 7, 16, 13, 17, 16, 21, 7, 16, 9, 18, 20, 27, 21, 28, + 13, 17, 20, 27, 17, 23, 27, 29, 16, 21, 21, 28, 27, 29, 29, 31, 7, + 20, 9, 21, 16, 27, 18, 28, 9, 21, 10, 22, 21, 29, 22, 30, 16, 27, 21, + 29, 21, 29, 28, 31, 18, 28, 22, 30, 28, 31, 30, 32, 4, 13, 13, 17, 7, + 16, 16, 21, 13, 17, 17, 23, 20, 27, 27, 29, 7, 16, 20, 27, 9, 18, 21, + 28, 16, 21, 27, 29, 21, 28, 29, 31, 7, 20, 16, 27, 9, 21, 18, 28, 16, + 27, 21, 29, 21, 29, 28, 31, 9, 21, 21, 29, 10, 22, 22, 30, 18, 28, + 28, 31, 22, 30, 30, 32, 11, 14, 14, 18, 14, 18, 18, 22, 14, 18, 18, + 24, 25, 28, 28, 30, 14, 18, 25, 28, 18, 24, 28, 30, 18, 22, 28, 30, + 28, 30, 31, 32, 14, 25, 18, 28, 18, 28, 24, 30, 18, 28, 22, 30, 28, + 31, 30, 32, 18, 28, 28, 31, 22, 30, 30, 32, 24, 30, 30, 32, 30, 32, + 32, 33, 1, 2, 2, 4, 5, 6, 6, 7, 2, 4, 4, 11, 12, 13, 13, 14, 5, 6, + 12, 13, 12, 13, 15, 16, 6, 7, 13, 14, 15, 16, 17, 18, 5, 12, 6, 13, + 12, 15, 13, 16, 6, 13, 7, 14, 15, 17, 16, 18, 12, 15, 15, 17, 19, 20, + 20, 21, 13, 16, 16, 18, 20, 21, 21, 22, 2, 4, 4, 11, 12, 13, 13, 14, + 4, 11, 11, 34, 35, 36, 36, 37, 12, 13, 35, 36, 38, 39, 40, 41, 13, + 14, 36, 37, 40, 41, 42, 43, 12, 35, 13, 36, 38, 40, 39, 41, 13, 36, + 14, 37, 40, 42, 41, 43, 38, 40, 40, 42, 44, 45, 45, 46, 39, 41, 41, + 43, 45, 46, 46, 47, 5, 6, 12, 13, 12, 13, 15, 16, 12, 13, 35, 36, 38, + 39, 40, 41, 12, 13, 38, 39, 35, 36, 40, 41, 15, 16, 40, 41, 40, 41, + 48, 49, 50, 51, 51, 52, 51, 52, 52, 53, 51, 52, 54, 55, 56, 57, 58, + 59, 51, 52, 56, 57, 54, 55, 58, 59, 52, 53, 58, 59, 58, 59, 60, 61, + 6, 7, 13, 14, 15, 16, 17, 18, 13, 14, 36, 37, 40, 41, 42, 43, 15, 16, + 40, 41, 40, 41, 48, 49, 17, 18, 42, 43, 48, 49, 62, 63, 51, 54, 52, + 55, 56, 58, 57, 59, 52, 55, 55, 64, 65, 66, 66, 67, 56, 58, 65, 66, + 68, 69, 70, 71, 57, 59, 66, 67, 70, 71, 72, 73, 5, 12, 6, 13, 12, 15, + 13, 16, 12, 35, 13, 36, 38, 40, 39, 41, 50, 51, 51, 52, 51, 52, 52, + 53, 51, 54, 52, 55, 56, 58, 57, 59, 12, 38, 13, 39, 35, 40, 36, 41, + 15, 40, 16, 41, 40, 48, 41, 49, 51, 56, 52, 57, 54, 58, 55, 59, 52, + 58, 53, 59, 58, 60, 59, 61, 6, 13, 7, 14, 15, 17, 16, 18, 13, 36, 14, + 37, 40, 42, 41, 43, 51, 52, 54, 55, 56, 57, 58, 59, 52, 55, 55, 64, + 65, 66, 66, 67, 15, 40, 16, 41, 40, 48, 41, 49, 17, 42, 18, 43, 48, + 62, 49, 63, 56, 65, 58, 66, 68, 70, 69, 71, 57, 66, 59, 67, 70, 72, + 71, 73, 12, 15, 15, 17, 19, 20, 20, 21, 38, 40, 40, 42, 44, 45, 45, + 46, 51, 52, 56, 57, 54, 55, 58, 59, 56, 58, 65, 66, 68, 69, 70, 71, + 51, 56, 52, 57, 54, 58, 55, 59, 56, 65, 58, 66, 68, 70, 69, 71, 74, + 75, 75, 76, 77, 78, 78, 79, 75, 80, 80, 81, 82, 83, 83, 84, 13, 16, + 16, 18, 20, 21, 21, 22, 39, 41, 41, 43, 45, 46, 46, 47, 52, 53, 58, + 59, 58, 59, 60, 61, 57, 59, 66, 67, 70, 71, 72, 73, 52, 58, 53, 59, + 58, 60, 59, 61, 57, 66, 59, 67, 70, 72, 71, 73, 75, 80, 80, 81, 82, + 83, 83, 84, 76, 81, 81, 85, 86, 87, 87, 88, 5, 12, 12, 35, 50, 51, + 51, 54, 6, 13, 13, 36, 51, 52, 52, 55, 12, 15, 38, 40, 51, 52, 56, + 58, 13, 16, 39, 41, 52, 53, 57, 59, 12, 38, 15, 40, 51, 56, 52, 58, + 13, 39, 16, 41, 52, 57, 53, 59, 35, 40, 40, 48, 54, 58, 58, 60, 36, + 41, 41, 49, 55, 59, 59, 61, 6, 13, 13, 36, 51, 52, 52, 55, 7, 14, 14, + 37, 54, 55, 55, 64, 15, 17, 40, 42, 56, 57, 65, 66, 16, 18, 41, 43, + 58, 59, 66, 67, 15, 40, 17, 42, 56, 65, 57, 66, 16, 41, 18, 43, 58, + 66, 59, 67, 40, 48, 48, 62, 68, 70, 70, 72, 41, 49, 49, 63, 69, 71, + 71, 73, 12, 15, 38, 40, 51, 52, 56, 58, 15, 17, 40, 42, 56, 57, 65, + 66, 19, 20, 44, 45, 54, 55, 68, 69, 20, 21, 45, 46, 58, 59, 70, 71, + 51, 56, 56, 65, 74, 75, 75, 80, 52, 57, 58, 66, 75, 76, 80, 81, 54, + 58, 68, 70, 77, 78, 82, 83, 55, 59, 69, 71, 78, 79, 83, 84, 13, 16, + 39, 41, 52, 53, 57, 59, 16, 18, 41, 43, 58, 59, 66, 67, 20, 21, 45, + 46, 58, 59, 70, 71, 21, 22, 46, 47, 60, 61, 72, 73, 52, 58, 57, 66, + 75, 80, 76, 81, 53, 59, 59, 67, 80, 81, 81, 85, 58, 60, 70, 72, 82, + 83, 86, 87, 59, 61, 71, 73, 83, 84, 87, 88, 12, 38, 15, 40, 51, 56, + 52, 58, 15, 40, 17, 42, 56, 65, 57, 66, 51, 56, 56, 65, 74, 75, 75, + 80, 52, 58, 57, 66, 75, 80, 76, 81, 19, 44, 20, 45, 54, 68, 55, 69, + 20, 45, 21, 46, 58, 70, 59, 71, 54, 68, 58, 70, 77, 82, 78, 83, 55, + 69, 59, 71, 78, 83, 79, 84, 13, 39, 16, 41, 52, 57, 53, 59, 16, 41, + 18, 43, 58, 66, 59, 67, 52, 57, 58, 66, 75, 76, 80, 81, 53, 59, 59, + 67, 80, 81, 81, 85, 20, 45, 21, 46, 58, 70, 59, 71, 21, 46, 22, 47, + 60, 72, 61, 73, 58, 70, 60, 72, 82, 86, 83, 87, 59, 71, 61, 73, 83, + 87, 84, 88, 35, 40, 40, 48, 54, 58, 58, 60, 40, 48, 48, 62, 68, 70, + 70, 72, 54, 58, 68, 70, 77, 78, 82, 83, 58, 60, 70, 72, 82, 83, 86, + 87, 54, 68, 58, 70, 77, 82, 78, 83, 58, 70, 60, 72, 82, 86, 83, 87, + 77, 82, 82, 86, 89, 90, 90, 91, 78, 83, 83, 87, 90, 91, 91, 92, 36, + 41, 41, 49, 55, 59, 59, 61, 41, 49, 49, 63, 69, 71, 71, 73, 55, 59, + 69, 71, 78, 79, 83, 84, 59, 61, 71, 73, 83, 84, 87, 88, 55, 69, 59, + 71, 78, 83, 79, 84, 59, 71, 61, 73, 83, 87, 84, 88, 78, 83, 83, 87, + 90, 91, 91, 92, 79, 84, 84, 88, 91, 92, 92, 93, 1, 2, 5, 6, 2, 4, 6, + 7, 5, 6, 12, 13, 12, 13, 15, 16, 2, 4, 12, 13, 4, 11, 13, 14, 6, 7, + 15, 16, 13, 14, 17, 18, 5, 12, 12, 15, 6, 13, 13, 16, 12, 15, 19, 20, + 15, 17, 20, 21, 6, 13, 15, 17, 7, 14, 16, 18, 13, 16, 20, 21, 16, 18, + 21, 22, 5, 6, 12, 13, 12, 13, 15, 16, 12, 13, 35, 36, 38, 39, 40, 41, + 12, 13, 38, 39, 35, 36, 40, 41, 15, 16, 40, 41, 40, 41, 48, 49, 50, + 51, 51, 52, 51, 52, 52, 53, 51, 52, 54, 55, 56, 57, 58, 59, 51, 52, + 56, 57, 54, 55, 58, 59, 52, 53, 58, 59, 58, 59, 60, 61, 2, 4, 12, 13, + 4, 11, 13, 14, 12, 13, 38, 39, 35, 36, 40, 41, 4, 11, 35, 36, 11, 34, + 36, 37, 13, 14, 40, 41, 36, 37, 42, 43, 12, 35, 38, 40, 13, 36, 39, + 41, 38, 40, 44, 45, 40, 42, 45, 46, 13, 36, 40, 42, 14, 37, 41, 43, + 39, 41, 45, 46, 41, 43, 46, 47, 6, 7, 15, 16, 13, 14, 17, 18, 15, 16, + 40, 41, 40, 41, 48, 49, 13, 14, 40, 41, 36, 37, 42, 43, 17, 18, 48, + 49, 42, 43, 62, 63, 51, 54, 56, 58, 52, 55, 57, 59, 56, 58, 68, 69, + 65, 66, 70, 71, 52, 55, 65, 66, 55, 64, 66, 67, 57, 59, 70, 71, 66, + 67, 72, 73, 5, 12, 12, 15, 6, 13, 13, 16, 50, 51, 51, 52, 51, 52, 52, + 53, 12, 35, 38, 40, 13, 36, 39, 41, 51, 54, 56, 58, 52, 55, 57, 59, + 12, 38, 35, 40, 13, 39, 36, 41, 51, 56, 54, 58, 52, 57, 55, 59, 15, + 40, 40, 48, 16, 41, 41, 49, 52, 58, 58, 60, 53, 59, 59, 61, 12, 15, + 19, 20, 15, 17, 20, 21, 51, 52, 54, 55, 56, 57, 58, 59, 38, 40, 44, + 45, 40, 42, 45, 46, 56, 58, 68, 69, 65, 66, 70, 71, 51, 56, 54, 58, + 52, 57, 55, 59, 74, 75, 77, 78, 75, 76, 78, 79, 56, 65, 68, 70, 58, + 66, 69, 71, 75, 80, 82, 83, 80, 81, 83, 84, 6, 13, 15, 17, 7, 14, 16, + 18, 51, 52, 56, 57, 54, 55, 58, 59, 13, 36, 40, 42, 14, 37, 41, 43, + 52, 55, 65, 66, 55, 64, 66, 67, 15, 40, 40, 48, 16, 41, 41, 49, 56, + 65, 68, 70, 58, 66, 69, 71, 17, 42, 48, 62, 18, 43, 49, 63, 57, 66, + 70, 72, 59, 67, 71, 73, 13, 16, 20, 21, 16, 18, 21, 22, 52, 53, 58, + 59, 58, 59, 60, 61, 39, 41, 45, 46, 41, 43, 46, 47, 57, 59, 70, 71, + 66, 67, 72, 73, 52, 58, 58, 60, 53, 59, 59, 61, 75, 80, 82, 83, 80, + 81, 83, 84, 57, 66, 70, 72, 59, 67, 71, 73, 76, 81, 86, 87, 81, 85, + 87, 88, 5, 12, 50, 51, 12, 35, 51, 54, 12, 15, 51, 52, 38, 40, 56, + 58, 6, 13, 51, 52, 13, 36, 52, 55, 13, 16, 52, 53, 39, 41, 57, 59, + 12, 38, 51, 56, 15, 40, 52, 58, 35, 40, 54, 58, 40, 48, 58, 60, 13, + 39, 52, 57, 16, 41, 53, 59, 36, 41, 55, 59, 41, 49, 59, 61, 12, 15, + 51, 52, 38, 40, 56, 58, 19, 20, 54, 55, 44, 45, 68, 69, 15, 17, 56, + 57, 40, 42, 65, 66, 20, 21, 58, 59, 45, 46, 70, 71, 51, 56, 74, 75, + 56, 65, 75, 80, 54, 58, 77, 78, 68, 70, 82, 83, 52, 57, 75, 76, 58, + 66, 80, 81, 55, 59, 78, 79, 69, 71, 83, 84, 6, 13, 51, 52, 13, 36, + 52, 55, 15, 17, 56, 57, 40, 42, 65, 66, 7, 14, 54, 55, 14, 37, 55, + 64, 16, 18, 58, 59, 41, 43, 66, 67, 15, 40, 56, 65, 17, 42, 57, 66, + 40, 48, 68, 70, 48, 62, 70, 72, 16, 41, 58, 66, 18, 43, 59, 67, 41, + 49, 69, 71, 49, 63, 71, 73, 13, 16, 52, 53, 39, 41, 57, 59, 20, 21, + 58, 59, 45, 46, 70, 71, 16, 18, 58, 59, 41, 43, 66, 67, 21, 22, 60, + 61, 46, 47, 72, 73, 52, 58, 75, 80, 57, 66, 76, 81, 58, 60, 82, 83, + 70, 72, 86, 87, 53, 59, 80, 81, 59, 67, 81, 85, 59, 61, 83, 84, 71, + 73, 87, 88, 12, 38, 51, 56, 15, 40, 52, 58, 51, 56, 74, 75, 56, 65, + 75, 80, 15, 40, 56, 65, 17, 42, 57, 66, 52, 58, 75, 80, 57, 66, 76, + 81, 19, 44, 54, 68, 20, 45, 55, 69, 54, 68, 77, 82, 58, 70, 78, 83, + 20, 45, 58, 70, 21, 46, 59, 71, 55, 69, 78, 83, 59, 71, 79, 84, 35, + 40, 54, 58, 40, 48, 58, 60, 54, 58, 77, 78, 68, 70, 82, 83, 40, 48, + 68, 70, 48, 62, 70, 72, 58, 60, 82, 83, 70, 72, 86, 87, 54, 68, 77, + 82, 58, 70, 78, 83, 77, 82, 89, 90, 82, 86, 90, 91, 58, 70, 82, 86, + 60, 72, 83, 87, 78, 83, 90, 91, 83, 87, 91, 92, 13, 39, 52, 57, 16, + 41, 53, 59, 52, 57, 75, 76, 58, 66, 80, 81, 16, 41, 58, 66, 18, 43, + 59, 67, 53, 59, 80, 81, 59, 67, 81, 85, 20, 45, 58, 70, 21, 46, 59, + 71, 58, 70, 82, 86, 60, 72, 83, 87, 21, 46, 60, 72, 22, 47, 61, 73, + 59, 71, 83, 87, 61, 73, 84, 88, 36, 41, 55, 59, 41, 49, 59, 61, 55, + 59, 78, 79, 69, 71, 83, 84, 41, 49, 69, 71, 49, 63, 71, 73, 59, 61, + 83, 84, 71, 73, 87, 88, 55, 69, 78, 83, 59, 71, 79, 84, 78, 83, 90, + 91, 83, 87, 91, 92, 59, 71, 83, 87, 61, 73, 84, 88, 79, 84, 91, 92, + 84, 88, 92, 93, 2, 3, 6, 7, 6, 7, 8, 9, 6, 7, 13, 14, 15, 16, 17, 18, + 6, 7, 15, 16, 13, 14, 17, 18, 8, 9, 17, 18, 17, 18, 23, 24, 12, 19, + 15, 20, 15, 20, 17, 21, 15, 20, 20, 25, 26, 27, 27, 28, 15, 20, 26, + 27, 20, 25, 27, 28, 17, 21, 27, 28, 27, 28, 29, 30, 6, 7, 13, 14, 15, + 16, 17, 18, 13, 14, 36, 37, 40, 41, 42, 43, 15, 16, 40, 41, 40, 41, + 48, 49, 17, 18, 42, 43, 48, 49, 62, 63, 51, 54, 52, 55, 56, 58, 57, + 59, 52, 55, 55, 64, 65, 66, 66, 67, 56, 58, 65, 66, 68, 69, 70, 71, + 57, 59, 66, 67, 70, 71, 72, 73, 6, 7, 15, 16, 13, 14, 17, 18, 15, 16, + 40, 41, 40, 41, 48, 49, 13, 14, 40, 41, 36, 37, 42, 43, 17, 18, 48, + 49, 42, 43, 62, 63, 51, 54, 56, 58, 52, 55, 57, 59, 56, 58, 68, 69, + 65, 66, 70, 71, 52, 55, 65, 66, 55, 64, 66, 67, 57, 59, 70, 71, 66, + 67, 72, 73, 8, 9, 17, 18, 17, 18, 23, 24, 17, 18, 42, 43, 48, 49, 62, + 63, 17, 18, 48, 49, 42, 43, 62, 63, 23, 24, 62, 63, 62, 63, 94, 95, + 74, 77, 75, 78, 75, 78, 76, 79, 75, 78, 96, 97, 98, 99, 100, 101, 75, + 78, 98, 99, 96, 97, 100, 101, 76, 79, 100, 101, 100, 101, 102, 103, + 12, 19, 15, 20, 15, 20, 17, 21, 51, 54, 52, 55, 56, 58, 57, 59, 51, + 54, 56, 58, 52, 55, 57, 59, 74, 77, 75, 78, 75, 78, 76, 79, 38, 44, + 40, 45, 40, 45, 42, 46, 56, 68, 58, 69, 65, 70, 66, 71, 56, 68, 65, + 70, 58, 69, 66, 71, 75, 82, 80, 83, 80, 83, 81, 84, 15, 20, 20, 25, + 26, 27, 27, 28, 52, 55, 55, 64, 65, 66, 66, 67, 56, 58, 68, 69, 65, + 66, 70, 71, 75, 78, 96, 97, 98, 99, 100, 101, 56, 68, 58, 69, 65, 70, + 66, 71, 75, 96, 78, 97, 98, 100, 99, 101, 104, 105, 105, 106, 105, + 106, 106, 107, 108, 109, 109, 110, 111, 112, 112, 113, 15, 20, 26, + 27, 20, 25, 27, 28, 56, 58, 65, 66, 68, 69, 70, 71, 52, 55, 65, 66, + 55, 64, 66, 67, 75, 78, 98, 99, 96, 97, 100, 101, 56, 68, 65, 70, 58, + 69, 66, 71, 104, 105, 105, 106, 105, 106, 106, 107, 75, 96, 98, 100, + 78, 97, 99, 101, 108, 109, 111, 112, 109, 110, 112, 113, 17, 21, 27, + 28, 27, 28, 29, 30, 57, 59, 66, 67, 70, 71, 72, 73, 57, 59, 70, 71, + 66, 67, 72, 73, 76, 79, 100, 101, 100, 101, 102, 103, 75, 82, 80, 83, + 80, 83, 81, 84, 108, 109, 109, 110, 111, 112, 112, 113, 108, 109, + 111, 112, 109, 110, 112, 113, 114, 115, 116, 117, 116, 117, 118, 119, + 12, 19, 51, 54, 51, 54, 74, 77, 15, 20, 52, 55, 56, 58, 75, 78, 15, + 20, 56, 58, 52, 55, 75, 78, 17, 21, 57, 59, 57, 59, 76, 79, 38, 44, + 56, 68, 56, 68, 75, 82, 40, 45, 58, 69, 65, 70, 80, 83, 40, 45, 65, + 70, 58, 69, 80, 83, 42, 46, 66, 71, 66, 71, 81, 84, 15, 20, 52, 55, + 56, 58, 75, 78, 20, 25, 55, 64, 68, 69, 96, 97, 26, 27, 65, 66, 65, + 66, 98, 99, 27, 28, 66, 67, 70, 71, 100, 101, 56, 68, 75, 96, 104, + 105, 108, 109, 58, 69, 78, 97, 105, 106, 109, 110, 65, 70, 98, 100, + 105, 106, 111, 112, 66, 71, 99, 101, 106, 107, 112, 113, 15, 20, 56, + 58, 52, 55, 75, 78, 26, 27, 65, 66, 65, 66, 98, 99, 20, 25, 68, 69, + 55, 64, 96, 97, 27, 28, 70, 71, 66, 67, 100, 101, 56, 68, 104, 105, + 75, 96, 108, 109, 65, 70, 105, 106, 98, 100, 111, 112, 58, 69, 105, + 106, 78, 97, 109, 110, 66, 71, 106, 107, 99, 101, 112, 113, 17, 21, + 57, 59, 57, 59, 76, 79, 27, 28, 66, 67, 70, 71, 100, 101, 27, 28, 70, + 71, 66, 67, 100, 101, 29, 30, 72, 73, 72, 73, 102, 103, 75, 82, 108, + 109, 108, 109, 114, 115, 80, 83, 109, 110, 111, 112, 116, 117, 80, + 83, 111, 112, 109, 110, 116, 117, 81, 84, 112, 113, 112, 113, 118, + 119, 38, 44, 56, 68, 56, 68, 75, 82, 56, 68, 75, 96, 104, 105, 108, + 109, 56, 68, 104, 105, 75, 96, 108, 109, 75, 82, 108, 109, 108, 109, + 114, 115, 44, 120, 68, 121, 68, 121, 96, 122, 68, 121, 82, 122, 105, + 123, 109, 124, 68, 121, 105, 123, 82, 122, 109, 124, 96, 122, 109, + 124, 109, 124, 115, 125, 40, 45, 58, 69, 65, 70, 80, 83, 58, 69, 78, + 97, 105, 106, 109, 110, 65, 70, 105, 106, 98, 100, 111, 112, 80, 83, + 109, 110, 111, 112, 116, 117, 68, 121, 82, 122, 105, 123, 109, 124, + 82, 122, 90, 126, 127, 128, 129, 130, 105, 123, 127, 128, 127, 128, + 131, 132, 109, 124, 129, 130, 131, 132, 133, 134, 40, 45, 65, 70, 58, + 69, 80, 83, 65, 70, 98, 100, 105, 106, 111, 112, 58, 69, 105, 106, + 78, 97, 109, 110, 80, 83, 111, 112, 109, 110, 116, 117, 68, 121, 105, + 123, 82, 122, 109, 124, 105, 123, 127, 128, 127, 128, 131, 132, 82, + 122, 127, 128, 90, 126, 129, 130, 109, 124, 131, 132, 129, 130, 133, + 134, 42, 46, 66, 71, 66, 71, 81, 84, 66, 71, 99, 101, 106, 107, 112, + 113, 66, 71, 106, 107, 99, 101, 112, 113, 81, 84, 112, 113, 112, 113, + 118, 119, 96, 122, 109, 124, 109, 124, 115, 125, 109, 124, 129, 130, + 131, 132, 133, 134, 109, 124, 131, 132, 129, 130, 133, 134, 115, 125, + 133, 134, 133, 134, 135, 136, 1, 5, 2, 6, 2, 6, 4, 7, 5, 12, 6, 13, + 12, 15, 13, 16, 5, 12, 12, 15, 6, 13, 13, 16, 12, 19, 15, 20, 15, 20, + 17, 21, 2, 12, 4, 13, 4, 13, 11, 14, 6, 15, 7, 16, 13, 17, 14, 18, 6, + 15, 13, 17, 7, 16, 14, 18, 13, 20, 16, 21, 16, 21, 18, 22, 5, 12, 6, + 13, 12, 15, 13, 16, 12, 35, 13, 36, 38, 40, 39, 41, 50, 51, 51, 52, + 51, 52, 52, 53, 51, 54, 52, 55, 56, 58, 57, 59, 12, 38, 13, 39, 35, + 40, 36, 41, 15, 40, 16, 41, 40, 48, 41, 49, 51, 56, 52, 57, 54, 58, + 55, 59, 52, 58, 53, 59, 58, 60, 59, 61, 5, 12, 12, 15, 6, 13, 13, 16, + 50, 51, 51, 52, 51, 52, 52, 53, 12, 35, 38, 40, 13, 36, 39, 41, 51, + 54, 56, 58, 52, 55, 57, 59, 12, 38, 35, 40, 13, 39, 36, 41, 51, 56, + 54, 58, 52, 57, 55, 59, 15, 40, 40, 48, 16, 41, 41, 49, 52, 58, 58, + 60, 53, 59, 59, 61, 12, 19, 15, 20, 15, 20, 17, 21, 51, 54, 52, 55, + 56, 58, 57, 59, 51, 54, 56, 58, 52, 55, 57, 59, 74, 77, 75, 78, 75, + 78, 76, 79, 38, 44, 40, 45, 40, 45, 42, 46, 56, 68, 58, 69, 65, 70, + 66, 71, 56, 68, 65, 70, 58, 69, 66, 71, 75, 82, 80, 83, 80, 83, 81, + 84, 2, 12, 4, 13, 4, 13, 11, 14, 12, 38, 13, 39, 35, 40, 36, 41, 12, + 38, 35, 40, 13, 39, 36, 41, 38, 44, 40, 45, 40, 45, 42, 46, 4, 35, + 11, 36, 11, 36, 34, 37, 13, 40, 14, 41, 36, 42, 37, 43, 13, 40, 36, + 42, 14, 41, 37, 43, 39, 45, 41, 46, 41, 46, 43, 47, 6, 15, 7, 16, 13, + 17, 14, 18, 15, 40, 16, 41, 40, 48, 41, 49, 51, 56, 54, 58, 52, 57, + 55, 59, 56, 68, 58, 69, 65, 70, 66, 71, 13, 40, 14, 41, 36, 42, 37, + 43, 17, 48, 18, 49, 42, 62, 43, 63, 52, 65, 55, 66, 55, 66, 64, 67, + 57, 70, 59, 71, 66, 72, 67, 73, 6, 15, 13, 17, 7, 16, 14, 18, 51, 56, + 52, 57, 54, 58, 55, 59, 15, 40, 40, 48, 16, 41, 41, 49, 56, 68, 65, + 70, 58, 69, 66, 71, 13, 40, 36, 42, 14, 41, 37, 43, 52, 65, 55, 66, + 55, 66, 64, 67, 17, 48, 42, 62, 18, 49, 43, 63, 57, 70, 66, 72, 59, + 71, 67, 73, 13, 20, 16, 21, 16, 21, 18, 22, 52, 58, 53, 59, 58, 60, + 59, 61, 52, 58, 58, 60, 53, 59, 59, 61, 75, 82, 80, 83, 80, 83, 81, + 84, 39, 45, 41, 46, 41, 46, 43, 47, 57, 70, 59, 71, 66, 72, 67, 73, + 57, 70, 66, 72, 59, 71, 67, 73, 76, 86, 81, 87, 81, 87, 85, 88, 5, + 50, 12, 51, 12, 51, 35, 54, 12, 51, 15, 52, 38, 56, 40, 58, 12, 51, + 38, 56, 15, 52, 40, 58, 35, 54, 40, 58, 40, 58, 48, 60, 6, 51, 13, + 52, 13, 52, 36, 55, 13, 52, 16, 53, 39, 57, 41, 59, 13, 52, 39, 57, + 16, 53, 41, 59, 36, 55, 41, 59, 41, 59, 49, 61, 12, 51, 15, 52, 38, + 56, 40, 58, 19, 54, 20, 55, 44, 68, 45, 69, 51, 74, 56, 75, 56, 75, + 65, 80, 54, 77, 58, 78, 68, 82, 70, 83, 15, 56, 17, 57, 40, 65, 42, + 66, 20, 58, 21, 59, 45, 70, 46, 71, 52, 75, 57, 76, 58, 80, 66, 81, + 55, 78, 59, 79, 69, 83, 71, 84, 12, 51, 38, 56, 15, 52, 40, 58, 51, + 74, 56, 75, 56, 75, 65, 80, 19, 54, 44, 68, 20, 55, 45, 69, 54, 77, + 68, 82, 58, 78, 70, 83, 15, 56, 40, 65, 17, 57, 42, 66, 52, 75, 58, + 80, 57, 76, 66, 81, 20, 58, 45, 70, 21, 59, 46, 71, 55, 78, 69, 83, + 59, 79, 71, 84, 35, 54, 40, 58, 40, 58, 48, 60, 54, 77, 58, 78, 68, + 82, 70, 83, 54, 77, 68, 82, 58, 78, 70, 83, 77, 89, 82, 90, 82, 90, + 86, 91, 40, 68, 48, 70, 48, 70, 62, 72, 58, 82, 60, 83, 70, 86, 72, + 87, 58, 82, 70, 86, 60, 83, 72, 87, 78, 90, 83, 91, 83, 91, 87, 92, + 6, 51, 13, 52, 13, 52, 36, 55, 15, 56, 17, 57, 40, 65, 42, 66, 15, + 56, 40, 65, 17, 57, 42, 66, 40, 68, 48, 70, 48, 70, 62, 72, 7, 54, + 14, 55, 14, 55, 37, 64, 16, 58, 18, 59, 41, 66, 43, 67, 16, 58, 41, + 66, 18, 59, 43, 67, 41, 69, 49, 71, 49, 71, 63, 73, 13, 52, 16, 53, + 39, 57, 41, 59, 20, 58, 21, 59, 45, 70, 46, 71, 52, 75, 58, 80, 57, + 76, 66, 81, 58, 82, 60, 83, 70, 86, 72, 87, 16, 58, 18, 59, 41, 66, + 43, 67, 21, 60, 22, 61, 46, 72, 47, 73, 53, 80, 59, 81, 59, 81, 67, + 85, 59, 83, 61, 84, 71, 87, 73, 88, 13, 52, 39, 57, 16, 53, 41, 59, + 52, 75, 57, 76, 58, 80, 66, 81, 20, 58, 45, 70, 21, 59, 46, 71, 58, + 82, 70, 86, 60, 83, 72, 87, 16, 58, 41, 66, 18, 59, 43, 67, 53, 80, + 59, 81, 59, 81, 67, 85, 21, 60, 46, 72, 22, 61, 47, 73, 59, 83, 71, + 87, 61, 84, 73, 88, 36, 55, 41, 59, 41, 59, 49, 61, 55, 78, 59, 79, + 69, 83, 71, 84, 55, 78, 69, 83, 59, 79, 71, 84, 78, 90, 83, 91, 83, + 91, 87, 92, 41, 69, 49, 71, 49, 71, 63, 73, 59, 83, 61, 84, 71, 87, + 73, 88, 59, 83, 71, 87, 61, 84, 73, 88, 79, 91, 84, 92, 84, 92, 88, + 93, 2, 6, 3, 7, 6, 8, 7, 9, 6, 13, 7, 14, 15, 17, 16, 18, 12, 15, 19, + 20, 15, 17, 20, 21, 15, 20, 20, 25, 26, 27, 27, 28, 6, 15, 7, 16, 13, + 17, 14, 18, 8, 17, 9, 18, 17, 23, 18, 24, 15, 26, 20, 27, 20, 27, 25, + 28, 17, 27, 21, 28, 27, 29, 28, 30, 6, 13, 7, 14, 15, 17, 16, 18, 13, + 36, 14, 37, 40, 42, 41, 43, 51, 52, 54, 55, 56, 57, 58, 59, 52, 55, + 55, 64, 65, 66, 66, 67, 15, 40, 16, 41, 40, 48, 41, 49, 17, 42, 18, + 43, 48, 62, 49, 63, 56, 65, 58, 66, 68, 70, 69, 71, 57, 66, 59, 67, + 70, 72, 71, 73, 12, 15, 19, 20, 15, 17, 20, 21, 51, 52, 54, 55, 56, + 57, 58, 59, 38, 40, 44, 45, 40, 42, 45, 46, 56, 58, 68, 69, 65, 66, + 70, 71, 51, 56, 54, 58, 52, 57, 55, 59, 74, 75, 77, 78, 75, 76, 78, + 79, 56, 65, 68, 70, 58, 66, 69, 71, 75, 80, 82, 83, 80, 81, 83, 84, + 15, 20, 20, 25, 26, 27, 27, 28, 52, 55, 55, 64, 65, 66, 66, 67, 56, + 58, 68, 69, 65, 66, 70, 71, 75, 78, 96, 97, 98, 99, 100, 101, 56, 68, + 58, 69, 65, 70, 66, 71, 75, 96, 78, 97, 98, 100, 99, 101, 104, 105, + 105, 106, 105, 106, 106, 107, 108, 109, 109, 110, 111, 112, 112, 113, + 6, 15, 7, 16, 13, 17, 14, 18, 15, 40, 16, 41, 40, 48, 41, 49, 51, 56, + 54, 58, 52, 57, 55, 59, 56, 68, 58, 69, 65, 70, 66, 71, 13, 40, 14, + 41, 36, 42, 37, 43, 17, 48, 18, 49, 42, 62, 43, 63, 52, 65, 55, 66, + 55, 66, 64, 67, 57, 70, 59, 71, 66, 72, 67, 73, 8, 17, 9, 18, 17, 23, + 18, 24, 17, 42, 18, 43, 48, 62, 49, 63, 74, 75, 77, 78, 75, 76, 78, + 79, 75, 96, 78, 97, 98, 100, 99, 101, 17, 48, 18, 49, 42, 62, 43, 63, + 23, 62, 24, 63, 62, 94, 63, 95, 75, 98, 78, 99, 96, 100, 97, 101, 76, + 100, 79, 101, 100, 102, 101, 103, 15, 26, 20, 27, 20, 27, 25, 28, 56, + 65, 58, 66, 68, 70, 69, 71, 56, 65, 68, 70, 58, 66, 69, 71, 104, 105, + 105, 106, 105, 106, 106, 107, 52, 65, 55, 66, 55, 66, 64, 67, 75, 98, + 78, 99, 96, 100, 97, 101, 75, 98, 96, 100, 78, 99, 97, 101, 108, 111, + 109, 112, 109, 112, 110, 113, 17, 27, 21, 28, 27, 29, 28, 30, 57, 66, + 59, 67, 70, 72, 71, 73, 75, 80, 82, 83, 80, 81, 83, 84, 108, 109, + 109, 110, 111, 112, 112, 113, 57, 70, 59, 71, 66, 72, 67, 73, 76, + 100, 79, 101, 100, 102, 101, 103, 108, 111, 109, 112, 109, 112, 110, + 113, 114, 116, 115, 117, 116, 118, 117, 119, 12, 51, 19, 54, 51, 74, + 54, 77, 15, 52, 20, 55, 56, 75, 58, 78, 38, 56, 44, 68, 56, 75, 68, + 82, 40, 58, 45, 69, 65, 80, 70, 83, 15, 56, 20, 58, 52, 75, 55, 78, + 17, 57, 21, 59, 57, 76, 59, 79, 40, 65, 45, 70, 58, 80, 69, 83, 42, + 66, 46, 71, 66, 81, 71, 84, 15, 52, 20, 55, 56, 75, 58, 78, 20, 55, + 25, 64, 68, 96, 69, 97, 56, 75, 68, 96, 104, 108, 105, 109, 58, 78, + 69, 97, 105, 109, 106, 110, 26, 65, 27, 66, 65, 98, 66, 99, 27, 66, + 28, 67, 70, 100, 71, 101, 65, 98, 70, 100, 105, 111, 106, 112, 66, + 99, 71, 101, 106, 112, 107, 113, 38, 56, 44, 68, 56, 75, 68, 82, 56, + 75, 68, 96, 104, 108, 105, 109, 44, 68, 120, 121, 68, 96, 121, 122, + 68, 82, 121, 122, 105, 109, 123, 124, 56, 104, 68, 105, 75, 108, 96, + 109, 75, 108, 82, 109, 108, 114, 109, 115, 68, 105, 121, 123, 82, + 109, 122, 124, 96, 109, 122, 124, 109, 115, 124, 125, 40, 58, 45, 69, + 65, 80, 70, 83, 58, 78, 69, 97, 105, 109, 106, 110, 68, 82, 121, 122, + 105, 109, 123, 124, 82, 90, 122, 126, 127, 129, 128, 130, 65, 105, + 70, 106, 98, 111, 100, 112, 80, 109, 83, 110, 111, 116, 112, 117, + 105, 127, 123, 128, 127, 131, 128, 132, 109, 129, 124, 130, 131, 133, + 132, 134, 15, 56, 20, 58, 52, 75, 55, 78, 26, 65, 27, 66, 65, 98, 66, + 99, 56, 104, 68, 105, 75, 108, 96, 109, 65, 105, 70, 106, 98, 111, + 100, 112, 20, 68, 25, 69, 55, 96, 64, 97, 27, 70, 28, 71, 66, 100, + 67, 101, 58, 105, 69, 106, 78, 109, 97, 110, 66, 106, 71, 107, 99, + 112, 101, 113, 17, 57, 21, 59, 57, 76, 59, 79, 27, 66, 28, 67, 70, + 100, 71, 101, 75, 108, 82, 109, 108, 114, 109, 115, 80, 109, 83, 110, + 111, 116, 112, 117, 27, 70, 28, 71, 66, 100, 67, 101, 29, 72, 30, 73, + 72, 102, 73, 103, 80, 111, 83, 112, 109, 116, 110, 117, 81, 112, 84, + 113, 112, 118, 113, 119, 40, 65, 45, 70, 58, 80, 69, 83, 65, 98, 70, + 100, 105, 111, 106, 112, 68, 105, 121, 123, 82, 109, 122, 124, 105, + 127, 123, 128, 127, 131, 128, 132, 58, 105, 69, 106, 78, 109, 97, + 110, 80, 111, 83, 112, 109, 116, 110, 117, 82, 127, 122, 128, 90, + 129, 126, 130, 109, 131, 124, 132, 129, 133, 130, 134, 42, 66, 46, + 71, 66, 81, 71, 84, 66, 99, 71, 101, 106, 112, 107, 113, 96, 109, + 122, 124, 109, 115, 124, 125, 109, 129, 124, 130, 131, 133, 132, 134, + 66, 106, 71, 107, 99, 112, 101, 113, 81, 112, 84, 113, 112, 118, 113, + 119, 109, 131, 124, 132, 129, 133, 130, 134, 115, 133, 125, 134, 133, + 135, 134, 136, 2, 6, 6, 8, 3, 7, 7, 9, 12, 15, 15, 17, 19, 20, 20, + 21, 6, 13, 15, 17, 7, 14, 16, 18, 15, 20, 26, 27, 20, 25, 27, 28, 6, + 15, 13, 17, 7, 16, 14, 18, 15, 26, 20, 27, 20, 27, 25, 28, 8, 17, 17, + 23, 9, 18, 18, 24, 17, 27, 27, 29, 21, 28, 28, 30, 12, 15, 15, 17, + 19, 20, 20, 21, 38, 40, 40, 42, 44, 45, 45, 46, 51, 52, 56, 57, 54, + 55, 58, 59, 56, 58, 65, 66, 68, 69, 70, 71, 51, 56, 52, 57, 54, 58, + 55, 59, 56, 65, 58, 66, 68, 70, 69, 71, 74, 75, 75, 76, 77, 78, 78, + 79, 75, 80, 80, 81, 82, 83, 83, 84, 6, 13, 15, 17, 7, 14, 16, 18, 51, + 52, 56, 57, 54, 55, 58, 59, 13, 36, 40, 42, 14, 37, 41, 43, 52, 55, + 65, 66, 55, 64, 66, 67, 15, 40, 40, 48, 16, 41, 41, 49, 56, 65, 68, + 70, 58, 66, 69, 71, 17, 42, 48, 62, 18, 43, 49, 63, 57, 66, 70, 72, + 59, 67, 71, 73, 15, 20, 26, 27, 20, 25, 27, 28, 56, 58, 65, 66, 68, + 69, 70, 71, 52, 55, 65, 66, 55, 64, 66, 67, 75, 78, 98, 99, 96, 97, + 100, 101, 56, 68, 65, 70, 58, 69, 66, 71, 104, 105, 105, 106, 105, + 106, 106, 107, 75, 96, 98, 100, 78, 97, 99, 101, 108, 109, 111, 112, + 109, 110, 112, 113, 6, 15, 13, 17, 7, 16, 14, 18, 51, 56, 52, 57, 54, + 58, 55, 59, 15, 40, 40, 48, 16, 41, 41, 49, 56, 68, 65, 70, 58, 69, + 66, 71, 13, 40, 36, 42, 14, 41, 37, 43, 52, 65, 55, 66, 55, 66, 64, + 67, 17, 48, 42, 62, 18, 49, 43, 63, 57, 70, 66, 72, 59, 71, 67, 73, + 15, 26, 20, 27, 20, 27, 25, 28, 56, 65, 58, 66, 68, 70, 69, 71, 56, + 65, 68, 70, 58, 66, 69, 71, 104, 105, 105, 106, 105, 106, 106, 107, + 52, 65, 55, 66, 55, 66, 64, 67, 75, 98, 78, 99, 96, 100, 97, 101, 75, + 98, 96, 100, 78, 99, 97, 101, 108, 111, 109, 112, 109, 112, 110, 113, + 8, 17, 17, 23, 9, 18, 18, 24, 74, 75, 75, 76, 77, 78, 78, 79, 17, 42, + 48, 62, 18, 43, 49, 63, 75, 96, 98, 100, 78, 97, 99, 101, 17, 48, 42, + 62, 18, 49, 43, 63, 75, 98, 96, 100, 78, 99, 97, 101, 23, 62, 62, 94, + 24, 63, 63, 95, 76, 100, 100, 102, 79, 101, 101, 103, 17, 27, 27, 29, + 21, 28, 28, 30, 75, 80, 80, 81, 82, 83, 83, 84, 57, 66, 70, 72, 59, + 67, 71, 73, 108, 109, 111, 112, 109, 110, 112, 113, 57, 70, 66, 72, + 59, 71, 67, 73, 108, 111, 109, 112, 109, 112, 110, 113, 76, 100, 100, + 102, 79, 101, 101, 103, 114, 116, 116, 118, 115, 117, 117, 119, 12, + 51, 51, 74, 19, 54, 54, 77, 38, 56, 56, 75, 44, 68, 68, 82, 15, 52, + 56, 75, 20, 55, 58, 78, 40, 58, 65, 80, 45, 69, 70, 83, 15, 56, 52, + 75, 20, 58, 55, 78, 40, 65, 58, 80, 45, 70, 69, 83, 17, 57, 57, 76, + 21, 59, 59, 79, 42, 66, 66, 81, 46, 71, 71, 84, 38, 56, 56, 75, 44, + 68, 68, 82, 44, 68, 68, 96, 120, 121, 121, 122, 56, 75, 104, 108, 68, + 96, 105, 109, 68, 82, 105, 109, 121, 122, 123, 124, 56, 104, 75, 108, + 68, 105, 96, 109, 68, 105, 82, 109, 121, 123, 122, 124, 75, 108, 108, + 114, 82, 109, 109, 115, 96, 109, 109, 115, 122, 124, 124, 125, 15, + 52, 56, 75, 20, 55, 58, 78, 56, 75, 104, 108, 68, 96, 105, 109, 20, + 55, 68, 96, 25, 64, 69, 97, 58, 78, 105, 109, 69, 97, 106, 110, 26, + 65, 65, 98, 27, 66, 66, 99, 65, 98, 105, 111, 70, 100, 106, 112, 27, + 66, 70, 100, 28, 67, 71, 101, 66, 99, 106, 112, 71, 101, 107, 113, + 40, 58, 65, 80, 45, 69, 70, 83, 68, 82, 105, 109, 121, 122, 123, 124, + 58, 78, 105, 109, 69, 97, 106, 110, 82, 90, 127, 129, 122, 126, 128, + 130, 65, 105, 98, 111, 70, 106, 100, 112, 105, 127, 127, 131, 123, + 128, 128, 132, 80, 109, 111, 116, 83, 110, 112, 117, 109, 129, 131, + 133, 124, 130, 132, 134, 15, 56, 52, 75, 20, 58, 55, 78, 56, 104, 75, + 108, 68, 105, 96, 109, 26, 65, 65, 98, 27, 66, 66, 99, 65, 105, 98, + 111, 70, 106, 100, 112, 20, 68, 55, 96, 25, 69, 64, 97, 58, 105, 78, + 109, 69, 106, 97, 110, 27, 70, 66, 100, 28, 71, 67, 101, 66, 106, 99, + 112, 71, 107, 101, 113, 40, 65, 58, 80, 45, 70, 69, 83, 68, 105, 82, + 109, 121, 123, 122, 124, 65, 98, 105, 111, 70, 100, 106, 112, 105, + 127, 127, 131, 123, 128, 128, 132, 58, 105, 78, 109, 69, 106, 97, + 110, 82, 127, 90, 129, 122, 128, 126, 130, 80, 111, 109, 116, 83, + 112, 110, 117, 109, 131, 129, 133, 124, 132, 130, 134, 17, 57, 57, + 76, 21, 59, 59, 79, 75, 108, 108, 114, 82, 109, 109, 115, 27, 66, 70, + 100, 28, 67, 71, 101, 80, 109, 111, 116, 83, 110, 112, 117, 27, 70, + 66, 100, 28, 71, 67, 101, 80, 111, 109, 116, 83, 112, 110, 117, 29, + 72, 72, 102, 30, 73, 73, 103, 81, 112, 112, 118, 84, 113, 113, 119, + 42, 66, 66, 81, 46, 71, 71, 84, 96, 109, 109, 115, 122, 124, 124, + 125, 66, 99, 106, 112, 71, 101, 107, 113, 109, 129, 131, 133, 124, + 130, 132, 134, 66, 106, 99, 112, 71, 107, 101, 113, 109, 131, 129, + 133, 124, 132, 130, 134, 81, 112, 112, 118, 84, 113, 113, 119, 115, + 133, 133, 135, 125, 134, 134, 136, 4, 7, 7, 9, 7, 9, 9, 10, 13, 16, + 16, 18, 20, 21, 21, 22, 13, 16, 20, 21, 16, 18, 21, 22, 17, 21, 27, + 28, 27, 28, 29, 30, 13, 20, 16, 21, 16, 21, 18, 22, 17, 27, 21, 28, + 27, 29, 28, 30, 17, 27, 27, 29, 21, 28, 28, 30, 23, 29, 29, 31, 29, + 31, 31, 32, 13, 16, 16, 18, 20, 21, 21, 22, 39, 41, 41, 43, 45, 46, + 46, 47, 52, 53, 58, 59, 58, 59, 60, 61, 57, 59, 66, 67, 70, 71, 72, + 73, 52, 58, 53, 59, 58, 60, 59, 61, 57, 66, 59, 67, 70, 72, 71, 73, + 75, 80, 80, 81, 82, 83, 83, 84, 76, 81, 81, 85, 86, 87, 87, 88, 13, + 16, 20, 21, 16, 18, 21, 22, 52, 53, 58, 59, 58, 59, 60, 61, 39, 41, + 45, 46, 41, 43, 46, 47, 57, 59, 70, 71, 66, 67, 72, 73, 52, 58, 58, + 60, 53, 59, 59, 61, 75, 80, 82, 83, 80, 81, 83, 84, 57, 66, 70, 72, + 59, 67, 71, 73, 76, 81, 86, 87, 81, 85, 87, 88, 17, 21, 27, 28, 27, + 28, 29, 30, 57, 59, 66, 67, 70, 71, 72, 73, 57, 59, 70, 71, 66, 67, + 72, 73, 76, 79, 100, 101, 100, 101, 102, 103, 75, 82, 80, 83, 80, 83, + 81, 84, 108, 109, 109, 110, 111, 112, 112, 113, 108, 109, 111, 112, + 109, 110, 112, 113, 114, 115, 116, 117, 116, 117, 118, 119, 13, 20, + 16, 21, 16, 21, 18, 22, 52, 58, 53, 59, 58, 60, 59, 61, 52, 58, 58, + 60, 53, 59, 59, 61, 75, 82, 80, 83, 80, 83, 81, 84, 39, 45, 41, 46, + 41, 46, 43, 47, 57, 70, 59, 71, 66, 72, 67, 73, 57, 70, 66, 72, 59, + 71, 67, 73, 76, 86, 81, 87, 81, 87, 85, 88, 17, 27, 21, 28, 27, 29, + 28, 30, 57, 66, 59, 67, 70, 72, 71, 73, 75, 80, 82, 83, 80, 81, 83, + 84, 108, 109, 109, 110, 111, 112, 112, 113, 57, 70, 59, 71, 66, 72, + 67, 73, 76, 100, 79, 101, 100, 102, 101, 103, 108, 111, 109, 112, + 109, 112, 110, 113, 114, 116, 115, 117, 116, 118, 117, 119, 17, 27, + 27, 29, 21, 28, 28, 30, 75, 80, 80, 81, 82, 83, 83, 84, 57, 66, 70, + 72, 59, 67, 71, 73, 108, 109, 111, 112, 109, 110, 112, 113, 57, 70, + 66, 72, 59, 71, 67, 73, 108, 111, 109, 112, 109, 112, 110, 113, 76, + 100, 100, 102, 79, 101, 101, 103, 114, 116, 116, 118, 115, 117, 117, + 119, 23, 29, 29, 31, 29, 31, 31, 32, 76, 81, 81, 85, 86, 87, 87, 88, + 76, 81, 86, 87, 81, 85, 87, 88, 114, 115, 116, 117, 116, 117, 118, + 119, 76, 86, 81, 87, 81, 87, 85, 88, 114, 116, 115, 117, 116, 118, + 117, 119, 114, 116, 116, 118, 115, 117, 117, 119, 137, 138, 138, 139, + 138, 139, 139, 140, 35, 54, 54, 77, 54, 77, 77, 89, 40, 58, 58, 78, + 68, 82, 82, 90, 40, 58, 68, 82, 58, 78, 82, 90, 48, 60, 70, 83, 70, + 83, 86, 91, 40, 68, 58, 82, 58, 82, 78, 90, 48, 70, 60, 83, 70, 86, + 83, 91, 48, 70, 70, 86, 60, 83, 83, 91, 62, 72, 72, 87, 72, 87, 87, + 92, 40, 58, 58, 78, 68, 82, 82, 90, 45, 69, 69, 97, 121, 122, 122, + 126, 65, 80, 105, 109, 105, 109, 127, 129, 70, 83, 106, 110, 123, + 124, 128, 130, 65, 105, 80, 109, 105, 127, 109, 129, 70, 106, 83, + 110, 123, 128, 124, 130, 98, 111, 111, 116, 127, 131, 131, 133, 100, + 112, 112, 117, 128, 132, 132, 134, 40, 58, 68, 82, 58, 78, 82, 90, + 65, 80, 105, 109, 105, 109, 127, 129, 45, 69, 121, 122, 69, 97, 122, + 126, 70, 83, 123, 124, 106, 110, 128, 130, 65, 105, 105, 127, 80, + 109, 109, 129, 98, 111, 127, 131, 111, 116, 131, 133, 70, 106, 123, + 128, 83, 110, 124, 130, 100, 112, 128, 132, 112, 117, 132, 134, 48, + 60, 70, 83, 70, 83, 86, 91, 70, 83, 106, 110, 123, 124, 128, 130, 70, + 83, 123, 124, 106, 110, 128, 130, 86, 91, 128, 130, 128, 130, 141, + 142, 98, 127, 111, 131, 111, 131, 116, 133, 111, 131, 131, 143, 144, + 145, 145, 146, 111, 131, 144, 145, 131, 143, 145, 146, 116, 133, 145, + 146, 145, 146, 147, 148, 40, 68, 58, 82, 58, 82, 78, 90, 65, 105, 80, + 109, 105, 127, 109, 129, 65, 105, 105, 127, 80, 109, 109, 129, 98, + 127, 111, 131, 111, 131, 116, 133, 45, 121, 69, 122, 69, 122, 97, + 126, 70, 123, 83, 124, 106, 128, 110, 130, 70, 123, 106, 128, 83, + 124, 110, 130, 100, 128, 112, 132, 112, 132, 117, 134, 48, 70, 60, + 83, 70, 86, 83, 91, 70, 106, 83, 110, 123, 128, 124, 130, 98, 111, + 127, 131, 111, 116, 131, 133, 111, 131, 131, 143, 144, 145, 145, 146, + 70, 123, 83, 124, 106, 128, 110, 130, 86, 128, 91, 130, 128, 141, + 130, 142, 111, 144, 131, 145, 131, 145, 143, 146, 116, 145, 133, 146, + 145, 147, 146, 148, 48, 70, 70, 86, 60, 83, 83, 91, 98, 111, 111, + 116, 127, 131, 131, 133, 70, 106, 123, 128, 83, 110, 124, 130, 111, + 131, 144, 145, 131, 143, 145, 146, 70, 123, 106, 128, 83, 124, 110, + 130, 111, 144, 131, 145, 131, 145, 143, 146, 86, 128, 128, 141, 91, + 130, 130, 142, 116, 145, 145, 147, 133, 146, 146, 148, 62, 72, 72, + 87, 72, 87, 87, 92, 100, 112, 112, 117, 128, 132, 132, 134, 100, 112, + 128, 132, 112, 117, 132, 134, 116, 133, 145, 146, 145, 146, 147, 148, + 100, 128, 112, 132, 112, 132, 117, 134, 116, 145, 133, 146, 145, 147, + 146, 148, 116, 145, 145, 147, 133, 146, 146, 148, 138, 149, 149, 150, + 149, 150, 150, 151, 1, 5, 5, 12, 5, 12, 12, 19, 2, 6, 6, 13, 12, 15, + 15, 20, 2, 6, 12, 15, 6, 13, 15, 20, 4, 7, 13, 16, 13, 16, 17, 21, 2, + 12, 6, 15, 6, 15, 13, 20, 4, 13, 7, 16, 13, 17, 16, 21, 4, 13, 13, + 17, 7, 16, 16, 21, 11, 14, 14, 18, 14, 18, 18, 22, 5, 12, 12, 35, 50, + 51, 51, 54, 6, 13, 13, 36, 51, 52, 52, 55, 12, 15, 38, 40, 51, 52, + 56, 58, 13, 16, 39, 41, 52, 53, 57, 59, 12, 38, 15, 40, 51, 56, 52, + 58, 13, 39, 16, 41, 52, 57, 53, 59, 35, 40, 40, 48, 54, 58, 58, 60, + 36, 41, 41, 49, 55, 59, 59, 61, 5, 12, 50, 51, 12, 35, 51, 54, 12, + 15, 51, 52, 38, 40, 56, 58, 6, 13, 51, 52, 13, 36, 52, 55, 13, 16, + 52, 53, 39, 41, 57, 59, 12, 38, 51, 56, 15, 40, 52, 58, 35, 40, 54, + 58, 40, 48, 58, 60, 13, 39, 52, 57, 16, 41, 53, 59, 36, 41, 55, 59, + 41, 49, 59, 61, 12, 19, 51, 54, 51, 54, 74, 77, 15, 20, 52, 55, 56, + 58, 75, 78, 15, 20, 56, 58, 52, 55, 75, 78, 17, 21, 57, 59, 57, 59, + 76, 79, 38, 44, 56, 68, 56, 68, 75, 82, 40, 45, 58, 69, 65, 70, 80, + 83, 40, 45, 65, 70, 58, 69, 80, 83, 42, 46, 66, 71, 66, 71, 81, 84, + 5, 50, 12, 51, 12, 51, 35, 54, 12, 51, 15, 52, 38, 56, 40, 58, 12, + 51, 38, 56, 15, 52, 40, 58, 35, 54, 40, 58, 40, 58, 48, 60, 6, 51, + 13, 52, 13, 52, 36, 55, 13, 52, 16, 53, 39, 57, 41, 59, 13, 52, 39, + 57, 16, 53, 41, 59, 36, 55, 41, 59, 41, 59, 49, 61, 12, 51, 19, 54, + 51, 74, 54, 77, 15, 52, 20, 55, 56, 75, 58, 78, 38, 56, 44, 68, 56, + 75, 68, 82, 40, 58, 45, 69, 65, 80, 70, 83, 15, 56, 20, 58, 52, 75, + 55, 78, 17, 57, 21, 59, 57, 76, 59, 79, 40, 65, 45, 70, 58, 80, 69, + 83, 42, 66, 46, 71, 66, 81, 71, 84, 12, 51, 51, 74, 19, 54, 54, 77, + 38, 56, 56, 75, 44, 68, 68, 82, 15, 52, 56, 75, 20, 55, 58, 78, 40, + 58, 65, 80, 45, 69, 70, 83, 15, 56, 52, 75, 20, 58, 55, 78, 40, 65, + 58, 80, 45, 70, 69, 83, 17, 57, 57, 76, 21, 59, 59, 79, 42, 66, 66, + 81, 46, 71, 71, 84, 35, 54, 54, 77, 54, 77, 77, 89, 40, 58, 58, 78, + 68, 82, 82, 90, 40, 58, 68, 82, 58, 78, 82, 90, 48, 60, 70, 83, 70, + 83, 86, 91, 40, 68, 58, 82, 58, 82, 78, 90, 48, 70, 60, 83, 70, 86, + 83, 91, 48, 70, 70, 86, 60, 83, 83, 91, 62, 72, 72, 87, 72, 87, 87, + 92, 2, 12, 12, 38, 12, 38, 38, 44, 4, 13, 13, 39, 35, 40, 40, 45, 4, + 13, 35, 40, 13, 39, 40, 45, 11, 14, 36, 41, 36, 41, 42, 46, 4, 35, + 13, 40, 13, 40, 39, 45, 11, 36, 14, 41, 36, 42, 41, 46, 11, 36, 36, + 42, 14, 41, 41, 46, 34, 37, 37, 43, 37, 43, 43, 47, 6, 15, 15, 40, + 51, 56, 56, 68, 7, 16, 16, 41, 54, 58, 58, 69, 13, 17, 40, 48, 52, + 57, 65, 70, 14, 18, 41, 49, 55, 59, 66, 71, 13, 40, 17, 48, 52, 65, + 57, 70, 14, 41, 18, 49, 55, 66, 59, 71, 36, 42, 42, 62, 55, 66, 66, + 72, 37, 43, 43, 63, 64, 67, 67, 73, 6, 15, 51, 56, 15, 40, 56, 68, + 13, 17, 52, 57, 40, 48, 65, 70, 7, 16, 54, 58, 16, 41, 58, 69, 14, + 18, 55, 59, 41, 49, 66, 71, 13, 40, 52, 65, 17, 48, 57, 70, 36, 42, + 55, 66, 42, 62, 66, 72, 14, 41, 55, 66, 18, 49, 59, 71, 37, 43, 64, + 67, 43, 63, 67, 73, 13, 20, 52, 58, 52, 58, 75, 82, 16, 21, 53, 59, + 58, 60, 80, 83, 16, 21, 58, 60, 53, 59, 80, 83, 18, 22, 59, 61, 59, + 61, 81, 84, 39, 45, 57, 70, 57, 70, 76, 86, 41, 46, 59, 71, 66, 72, + 81, 87, 41, 46, 66, 72, 59, 71, 81, 87, 43, 47, 67, 73, 67, 73, 85, + 88, 6, 51, 15, 56, 15, 56, 40, 68, 13, 52, 17, 57, 40, 65, 48, 70, + 13, 52, 40, 65, 17, 57, 48, 70, 36, 55, 42, 66, 42, 66, 62, 72, 7, + 54, 16, 58, 16, 58, 41, 69, 14, 55, 18, 59, 41, 66, 49, 71, 14, 55, + 41, 66, 18, 59, 49, 71, 37, 64, 43, 67, 43, 67, 63, 73, 13, 52, 20, + 58, 52, 75, 58, 82, 16, 53, 21, 59, 58, 80, 60, 83, 39, 57, 45, 70, + 57, 76, 70, 86, 41, 59, 46, 71, 66, 81, 72, 87, 16, 58, 21, 60, 53, + 80, 59, 83, 18, 59, 22, 61, 59, 81, 61, 84, 41, 66, 46, 72, 59, 81, + 71, 87, 43, 67, 47, 73, 67, 85, 73, 88, 13, 52, 52, 75, 20, 58, 58, + 82, 39, 57, 57, 76, 45, 70, 70, 86, 16, 53, 58, 80, 21, 59, 60, 83, + 41, 59, 66, 81, 46, 71, 72, 87, 16, 58, 53, 80, 21, 60, 59, 83, 41, + 66, 59, 81, 46, 72, 71, 87, 18, 59, 59, 81, 22, 61, 61, 84, 43, 67, + 67, 85, 47, 73, 73, 88, 36, 55, 55, 78, 55, 78, 78, 90, 41, 59, 59, + 79, 69, 83, 83, 91, 41, 59, 69, 83, 59, 79, 83, 91, 49, 61, 71, 84, + 71, 84, 87, 92, 41, 69, 59, 83, 59, 83, 79, 91, 49, 71, 61, 84, 71, + 87, 84, 92, 49, 71, 71, 87, 61, 84, 84, 92, 63, 73, 73, 88, 73, 88, + 88, 93, 2, 6, 6, 13, 12, 15, 15, 20, 3, 7, 7, 14, 19, 20, 20, 25, 6, + 8, 15, 17, 15, 17, 26, 27, 7, 9, 16, 18, 20, 21, 27, 28, 6, 15, 8, + 17, 15, 26, 17, 27, 7, 16, 9, 18, 20, 27, 21, 28, 13, 17, 17, 23, 20, + 27, 27, 29, 14, 18, 18, 24, 25, 28, 28, 30, 6, 13, 13, 36, 51, 52, + 52, 55, 7, 14, 14, 37, 54, 55, 55, 64, 15, 17, 40, 42, 56, 57, 65, + 66, 16, 18, 41, 43, 58, 59, 66, 67, 15, 40, 17, 42, 56, 65, 57, 66, + 16, 41, 18, 43, 58, 66, 59, 67, 40, 48, 48, 62, 68, 70, 70, 72, 41, + 49, 49, 63, 69, 71, 71, 73, 12, 15, 51, 52, 38, 40, 56, 58, 19, 20, + 54, 55, 44, 45, 68, 69, 15, 17, 56, 57, 40, 42, 65, 66, 20, 21, 58, + 59, 45, 46, 70, 71, 51, 56, 74, 75, 56, 65, 75, 80, 54, 58, 77, 78, + 68, 70, 82, 83, 52, 57, 75, 76, 58, 66, 80, 81, 55, 59, 78, 79, 69, + 71, 83, 84, 15, 20, 52, 55, 56, 58, 75, 78, 20, 25, 55, 64, 68, 69, + 96, 97, 26, 27, 65, 66, 65, 66, 98, 99, 27, 28, 66, 67, 70, 71, 100, + 101, 56, 68, 75, 96, 104, 105, 108, 109, 58, 69, 78, 97, 105, 106, + 109, 110, 65, 70, 98, 100, 105, 106, 111, 112, 66, 71, 99, 101, 106, + 107, 112, 113, 12, 51, 15, 52, 38, 56, 40, 58, 19, 54, 20, 55, 44, + 68, 45, 69, 51, 74, 56, 75, 56, 75, 65, 80, 54, 77, 58, 78, 68, 82, + 70, 83, 15, 56, 17, 57, 40, 65, 42, 66, 20, 58, 21, 59, 45, 70, 46, + 71, 52, 75, 57, 76, 58, 80, 66, 81, 55, 78, 59, 79, 69, 83, 71, 84, + 15, 52, 20, 55, 56, 75, 58, 78, 20, 55, 25, 64, 68, 96, 69, 97, 56, + 75, 68, 96, 104, 108, 105, 109, 58, 78, 69, 97, 105, 109, 106, 110, + 26, 65, 27, 66, 65, 98, 66, 99, 27, 66, 28, 67, 70, 100, 71, 101, 65, + 98, 70, 100, 105, 111, 106, 112, 66, 99, 71, 101, 106, 112, 107, 113, + 38, 56, 56, 75, 44, 68, 68, 82, 44, 68, 68, 96, 120, 121, 121, 122, + 56, 75, 104, 108, 68, 96, 105, 109, 68, 82, 105, 109, 121, 122, 123, + 124, 56, 104, 75, 108, 68, 105, 96, 109, 68, 105, 82, 109, 121, 123, + 122, 124, 75, 108, 108, 114, 82, 109, 109, 115, 96, 109, 109, 115, + 122, 124, 124, 125, 40, 58, 58, 78, 68, 82, 82, 90, 45, 69, 69, 97, + 121, 122, 122, 126, 65, 80, 105, 109, 105, 109, 127, 129, 70, 83, + 106, 110, 123, 124, 128, 130, 65, 105, 80, 109, 105, 127, 109, 129, + 70, 106, 83, 110, 123, 128, 124, 130, 98, 111, 111, 116, 127, 131, + 131, 133, 100, 112, 112, 117, 128, 132, 132, 134, 6, 15, 15, 40, 51, + 56, 56, 68, 7, 16, 16, 41, 54, 58, 58, 69, 13, 17, 40, 48, 52, 57, + 65, 70, 14, 18, 41, 49, 55, 59, 66, 71, 13, 40, 17, 48, 52, 65, 57, + 70, 14, 41, 18, 49, 55, 66, 59, 71, 36, 42, 42, 62, 55, 66, 66, 72, + 37, 43, 43, 63, 64, 67, 67, 73, 8, 17, 17, 42, 74, 75, 75, 96, 9, 18, + 18, 43, 77, 78, 78, 97, 17, 23, 48, 62, 75, 76, 98, 100, 18, 24, 49, + 63, 78, 79, 99, 101, 17, 48, 23, 62, 75, 98, 76, 100, 18, 49, 24, 63, + 78, 99, 79, 101, 42, 62, 62, 94, 96, 100, 100, 102, 43, 63, 63, 95, + 97, 101, 101, 103, 15, 26, 56, 65, 56, 65, 104, 105, 20, 27, 58, 66, + 68, 70, 105, 106, 20, 27, 68, 70, 58, 66, 105, 106, 25, 28, 69, 71, + 69, 71, 106, 107, 52, 65, 75, 98, 75, 98, 108, 111, 55, 66, 78, 99, + 96, 100, 109, 112, 55, 66, 96, 100, 78, 99, 109, 112, 64, 67, 97, + 101, 97, 101, 110, 113, 17, 27, 57, 66, 75, 80, 108, 109, 21, 28, 59, + 67, 82, 83, 109, 110, 27, 29, 70, 72, 80, 81, 111, 112, 28, 30, 71, + 73, 83, 84, 112, 113, 57, 70, 76, 100, 108, 111, 114, 116, 59, 71, + 79, 101, 109, 112, 115, 117, 66, 72, 100, 102, 109, 112, 116, 118, + 67, 73, 101, 103, 110, 113, 117, 119, 15, 56, 26, 65, 56, 104, 65, + 105, 20, 58, 27, 66, 68, 105, 70, 106, 52, 75, 65, 98, 75, 108, 98, + 111, 55, 78, 66, 99, 96, 109, 100, 112, 20, 68, 27, 70, 58, 105, 66, + 106, 25, 69, 28, 71, 69, 106, 71, 107, 55, 96, 66, 100, 78, 109, 99, + 112, 64, 97, 67, 101, 97, 110, 101, 113, 17, 57, 27, 66, 75, 108, 80, + 109, 21, 59, 28, 67, 82, 109, 83, 110, 57, 76, 70, 100, 108, 114, + 111, 116, 59, 79, 71, 101, 109, 115, 112, 117, 27, 70, 29, 72, 80, + 111, 81, 112, 28, 71, 30, 73, 83, 112, 84, 113, 66, 100, 72, 102, + 109, 116, 112, 118, 67, 101, 73, 103, 110, 117, 113, 119, 40, 65, 65, + 98, 68, 105, 105, 127, 45, 70, 70, 100, 121, 123, 123, 128, 58, 80, + 105, 111, 82, 109, 127, 131, 69, 83, 106, 112, 122, 124, 128, 132, + 58, 105, 80, 111, 82, 127, 109, 131, 69, 106, 83, 112, 122, 128, 124, + 132, 78, 109, 109, 116, 90, 129, 129, 133, 97, 110, 110, 117, 126, + 130, 130, 134, 42, 66, 66, 99, 96, 109, 109, 129, 46, 71, 71, 101, + 122, 124, 124, 130, 66, 81, 106, 112, 109, 115, 131, 133, 71, 84, + 107, 113, 124, 125, 132, 134, 66, 106, 81, 112, 109, 131, 115, 133, + 71, 107, 84, 113, 124, 132, 125, 134, 99, 112, 112, 118, 129, 133, + 133, 135, 101, 113, 113, 119, 130, 134, 134, 136, 2, 6, 12, 15, 6, + 13, 15, 20, 6, 8, 15, 17, 15, 17, 26, 27, 3, 7, 19, 20, 7, 14, 20, + 25, 7, 9, 20, 21, 16, 18, 27, 28, 6, 15, 15, 26, 8, 17, 17, 27, 13, + 17, 20, 27, 17, 23, 27, 29, 7, 16, 20, 27, 9, 18, 21, 28, 14, 18, 25, + 28, 18, 24, 28, 30, 12, 15, 38, 40, 51, 52, 56, 58, 15, 17, 40, 42, + 56, 57, 65, 66, 19, 20, 44, 45, 54, 55, 68, 69, 20, 21, 45, 46, 58, + 59, 70, 71, 51, 56, 56, 65, 74, 75, 75, 80, 52, 57, 58, 66, 75, 76, + 80, 81, 54, 58, 68, 70, 77, 78, 82, 83, 55, 59, 69, 71, 78, 79, 83, + 84, 6, 13, 51, 52, 13, 36, 52, 55, 15, 17, 56, 57, 40, 42, 65, 66, 7, + 14, 54, 55, 14, 37, 55, 64, 16, 18, 58, 59, 41, 43, 66, 67, 15, 40, + 56, 65, 17, 42, 57, 66, 40, 48, 68, 70, 48, 62, 70, 72, 16, 41, 58, + 66, 18, 43, 59, 67, 41, 49, 69, 71, 49, 63, 71, 73, 15, 20, 56, 58, + 52, 55, 75, 78, 26, 27, 65, 66, 65, 66, 98, 99, 20, 25, 68, 69, 55, + 64, 96, 97, 27, 28, 70, 71, 66, 67, 100, 101, 56, 68, 104, 105, 75, + 96, 108, 109, 65, 70, 105, 106, 98, 100, 111, 112, 58, 69, 105, 106, + 78, 97, 109, 110, 66, 71, 106, 107, 99, 101, 112, 113, 12, 51, 38, + 56, 15, 52, 40, 58, 51, 74, 56, 75, 56, 75, 65, 80, 19, 54, 44, 68, + 20, 55, 45, 69, 54, 77, 68, 82, 58, 78, 70, 83, 15, 56, 40, 65, 17, + 57, 42, 66, 52, 75, 58, 80, 57, 76, 66, 81, 20, 58, 45, 70, 21, 59, + 46, 71, 55, 78, 69, 83, 59, 79, 71, 84, 38, 56, 44, 68, 56, 75, 68, + 82, 56, 75, 68, 96, 104, 108, 105, 109, 44, 68, 120, 121, 68, 96, + 121, 122, 68, 82, 121, 122, 105, 109, 123, 124, 56, 104, 68, 105, 75, + 108, 96, 109, 75, 108, 82, 109, 108, 114, 109, 115, 68, 105, 121, + 123, 82, 109, 122, 124, 96, 109, 122, 124, 109, 115, 124, 125, 15, + 52, 56, 75, 20, 55, 58, 78, 56, 75, 104, 108, 68, 96, 105, 109, 20, + 55, 68, 96, 25, 64, 69, 97, 58, 78, 105, 109, 69, 97, 106, 110, 26, + 65, 65, 98, 27, 66, 66, 99, 65, 98, 105, 111, 70, 100, 106, 112, 27, + 66, 70, 100, 28, 67, 71, 101, 66, 99, 106, 112, 71, 101, 107, 113, + 40, 58, 68, 82, 58, 78, 82, 90, 65, 80, 105, 109, 105, 109, 127, 129, + 45, 69, 121, 122, 69, 97, 122, 126, 70, 83, 123, 124, 106, 110, 128, + 130, 65, 105, 105, 127, 80, 109, 109, 129, 98, 111, 127, 131, 111, + 116, 131, 133, 70, 106, 123, 128, 83, 110, 124, 130, 100, 112, 128, + 132, 112, 117, 132, 134, 6, 15, 51, 56, 15, 40, 56, 68, 13, 17, 52, + 57, 40, 48, 65, 70, 7, 16, 54, 58, 16, 41, 58, 69, 14, 18, 55, 59, + 41, 49, 66, 71, 13, 40, 52, 65, 17, 48, 57, 70, 36, 42, 55, 66, 42, + 62, 66, 72, 14, 41, 55, 66, 18, 49, 59, 71, 37, 43, 64, 67, 43, 63, + 67, 73, 15, 26, 56, 65, 56, 65, 104, 105, 20, 27, 58, 66, 68, 70, + 105, 106, 20, 27, 68, 70, 58, 66, 105, 106, 25, 28, 69, 71, 69, 71, + 106, 107, 52, 65, 75, 98, 75, 98, 108, 111, 55, 66, 78, 99, 96, 100, + 109, 112, 55, 66, 96, 100, 78, 99, 109, 112, 64, 67, 97, 101, 97, + 101, 110, 113, 8, 17, 74, 75, 17, 42, 75, 96, 17, 23, 75, 76, 48, 62, + 98, 100, 9, 18, 77, 78, 18, 43, 78, 97, 18, 24, 78, 79, 49, 63, 99, + 101, 17, 48, 75, 98, 23, 62, 76, 100, 42, 62, 96, 100, 62, 94, 100, + 102, 18, 49, 78, 99, 24, 63, 79, 101, 43, 63, 97, 101, 63, 95, 101, + 103, 17, 27, 75, 80, 57, 66, 108, 109, 27, 29, 80, 81, 70, 72, 111, + 112, 21, 28, 82, 83, 59, 67, 109, 110, 28, 30, 83, 84, 71, 73, 112, + 113, 57, 70, 108, 111, 76, 100, 114, 116, 66, 72, 109, 112, 100, 102, + 116, 118, 59, 71, 109, 112, 79, 101, 115, 117, 67, 73, 110, 113, 101, + 103, 117, 119, 15, 56, 56, 104, 26, 65, 65, 105, 52, 75, 75, 108, 65, + 98, 98, 111, 20, 58, 68, 105, 27, 66, 70, 106, 55, 78, 96, 109, 66, + 99, 100, 112, 20, 68, 58, 105, 27, 70, 66, 106, 55, 96, 78, 109, 66, + 100, 99, 112, 25, 69, 69, 106, 28, 71, 71, 107, 64, 97, 97, 110, 67, + 101, 101, 113, 40, 65, 68, 105, 65, 98, 105, 127, 58, 80, 82, 109, + 105, 111, 127, 131, 45, 70, 121, 123, 70, 100, 123, 128, 69, 83, 122, + 124, 106, 112, 128, 132, 58, 105, 82, 127, 80, 111, 109, 131, 78, + 109, 90, 129, 109, 116, 129, 133, 69, 106, 122, 128, 83, 112, 124, + 132, 97, 110, 126, 130, 110, 117, 130, 134, 17, 57, 75, 108, 27, 66, + 80, 109, 57, 76, 108, 114, 70, 100, 111, 116, 21, 59, 82, 109, 28, + 67, 83, 110, 59, 79, 109, 115, 71, 101, 112, 117, 27, 70, 80, 111, + 29, 72, 81, 112, 66, 100, 109, 116, 72, 102, 112, 118, 28, 71, 83, + 112, 30, 73, 84, 113, 67, 101, 110, 117, 73, 103, 113, 119, 42, 66, + 96, 109, 66, 99, 109, 129, 66, 81, 109, 115, 106, 112, 131, 133, 46, + 71, 122, 124, 71, 101, 124, 130, 71, 84, 124, 125, 107, 113, 132, + 134, 66, 106, 109, 131, 81, 112, 115, 133, 99, 112, 129, 133, 112, + 118, 133, 135, 71, 107, 124, 132, 84, 113, 125, 134, 101, 113, 130, + 134, 113, 119, 134, 136, 4, 7, 13, 16, 13, 16, 17, 21, 7, 9, 16, 18, + 20, 21, 27, 28, 7, 9, 20, 21, 16, 18, 27, 28, 9, 10, 21, 22, 21, 22, + 29, 30, 13, 20, 17, 27, 17, 27, 23, 29, 16, 21, 21, 28, 27, 29, 29, + 31, 16, 21, 27, 29, 21, 28, 29, 31, 18, 22, 28, 30, 28, 30, 31, 32, + 13, 16, 39, 41, 52, 53, 57, 59, 16, 18, 41, 43, 58, 59, 66, 67, 20, + 21, 45, 46, 58, 59, 70, 71, 21, 22, 46, 47, 60, 61, 72, 73, 52, 58, + 57, 66, 75, 80, 76, 81, 53, 59, 59, 67, 80, 81, 81, 85, 58, 60, 70, + 72, 82, 83, 86, 87, 59, 61, 71, 73, 83, 84, 87, 88, 13, 16, 52, 53, + 39, 41, 57, 59, 20, 21, 58, 59, 45, 46, 70, 71, 16, 18, 58, 59, 41, + 43, 66, 67, 21, 22, 60, 61, 46, 47, 72, 73, 52, 58, 75, 80, 57, 66, + 76, 81, 58, 60, 82, 83, 70, 72, 86, 87, 53, 59, 80, 81, 59, 67, 81, + 85, 59, 61, 83, 84, 71, 73, 87, 88, 17, 21, 57, 59, 57, 59, 76, 79, + 27, 28, 66, 67, 70, 71, 100, 101, 27, 28, 70, 71, 66, 67, 100, 101, + 29, 30, 72, 73, 72, 73, 102, 103, 75, 82, 108, 109, 108, 109, 114, + 115, 80, 83, 109, 110, 111, 112, 116, 117, 80, 83, 111, 112, 109, + 110, 116, 117, 81, 84, 112, 113, 112, 113, 118, 119, 35, 54, 40, 58, + 40, 58, 48, 60, 54, 77, 58, 78, 68, 82, 70, 83, 54, 77, 68, 82, 58, + 78, 70, 83, 77, 89, 82, 90, 82, 90, 86, 91, 40, 68, 48, 70, 48, 70, + 62, 72, 58, 82, 60, 83, 70, 86, 72, 87, 58, 82, 70, 86, 60, 83, 72, + 87, 78, 90, 83, 91, 83, 91, 87, 92, 40, 58, 45, 69, 65, 80, 70, 83, + 58, 78, 69, 97, 105, 109, 106, 110, 68, 82, 121, 122, 105, 109, 123, + 124, 82, 90, 122, 126, 127, 129, 128, 130, 65, 105, 70, 106, 98, 111, + 100, 112, 80, 109, 83, 110, 111, 116, 112, 117, 105, 127, 123, 128, + 127, 131, 128, 132, 109, 129, 124, 130, 131, 133, 132, 134, 40, 58, + 65, 80, 45, 69, 70, 83, 68, 82, 105, 109, 121, 122, 123, 124, 58, 78, + 105, 109, 69, 97, 106, 110, 82, 90, 127, 129, 122, 126, 128, 130, 65, + 105, 98, 111, 70, 106, 100, 112, 105, 127, 127, 131, 123, 128, 128, + 132, 80, 109, 111, 116, 83, 110, 112, 117, 109, 129, 131, 133, 124, + 130, 132, 134, 48, 60, 70, 83, 70, 83, 86, 91, 70, 83, 106, 110, 123, + 124, 128, 130, 70, 83, 123, 124, 106, 110, 128, 130, 86, 91, 128, + 130, 128, 130, 141, 142, 98, 127, 111, 131, 111, 131, 116, 133, 111, + 131, 131, 143, 144, 145, 145, 146, 111, 131, 144, 145, 131, 143, 145, + 146, 116, 133, 145, 146, 145, 146, 147, 148, 13, 20, 52, 58, 52, 58, + 75, 82, 16, 21, 53, 59, 58, 60, 80, 83, 16, 21, 58, 60, 53, 59, 80, + 83, 18, 22, 59, 61, 59, 61, 81, 84, 39, 45, 57, 70, 57, 70, 76, 86, + 41, 46, 59, 71, 66, 72, 81, 87, 41, 46, 66, 72, 59, 71, 81, 87, 43, + 47, 67, 73, 67, 73, 85, 88, 17, 27, 57, 66, 75, 80, 108, 109, 21, 28, + 59, 67, 82, 83, 109, 110, 27, 29, 70, 72, 80, 81, 111, 112, 28, 30, + 71, 73, 83, 84, 112, 113, 57, 70, 76, 100, 108, 111, 114, 116, 59, + 71, 79, 101, 109, 112, 115, 117, 66, 72, 100, 102, 109, 112, 116, + 118, 67, 73, 101, 103, 110, 113, 117, 119, 17, 27, 75, 80, 57, 66, + 108, 109, 27, 29, 80, 81, 70, 72, 111, 112, 21, 28, 82, 83, 59, 67, + 109, 110, 28, 30, 83, 84, 71, 73, 112, 113, 57, 70, 108, 111, 76, + 100, 114, 116, 66, 72, 109, 112, 100, 102, 116, 118, 59, 71, 109, + 112, 79, 101, 115, 117, 67, 73, 110, 113, 101, 103, 117, 119, 23, 29, + 76, 81, 76, 81, 114, 115, 29, 31, 81, 85, 86, 87, 116, 117, 29, 31, + 86, 87, 81, 85, 116, 117, 31, 32, 87, 88, 87, 88, 118, 119, 76, 86, + 114, 116, 114, 116, 137, 138, 81, 87, 115, 117, 116, 118, 138, 139, + 81, 87, 116, 118, 115, 117, 138, 139, 85, 88, 117, 119, 117, 119, + 139, 140, 40, 68, 65, 105, 65, 105, 98, 127, 58, 82, 80, 109, 105, + 127, 111, 131, 58, 82, 105, 127, 80, 109, 111, 131, 78, 90, 109, 129, + 109, 129, 116, 133, 45, 121, 70, 123, 70, 123, 100, 128, 69, 122, 83, + 124, 106, 128, 112, 132, 69, 122, 106, 128, 83, 124, 112, 132, 97, + 126, 110, 130, 110, 130, 117, 134, 48, 70, 70, 106, 98, 111, 111, + 131, 60, 83, 83, 110, 127, 131, 131, 143, 70, 86, 123, 128, 111, 116, + 144, 145, 83, 91, 124, 130, 131, 133, 145, 146, 70, 123, 86, 128, + 111, 144, 116, 145, 83, 124, 91, 130, 131, 145, 133, 146, 106, 128, + 128, 141, 131, 145, 145, 147, 110, 130, 130, 142, 143, 146, 146, 148, + 48, 70, 98, 111, 70, 106, 111, 131, 70, 86, 111, 116, 123, 128, 144, + 145, 60, 83, 127, 131, 83, 110, 131, 143, 83, 91, 131, 133, 124, 130, + 145, 146, 70, 123, 111, 144, 86, 128, 116, 145, 106, 128, 131, 145, + 128, 141, 145, 147, 83, 124, 131, 145, 91, 130, 133, 146, 110, 130, + 143, 146, 130, 142, 146, 148, 62, 72, 100, 112, 100, 112, 116, 133, + 72, 87, 112, 117, 128, 132, 145, 146, 72, 87, 128, 132, 112, 117, + 145, 146, 87, 92, 132, 134, 132, 134, 147, 148, 100, 128, 116, 145, + 116, 145, 138, 149, 112, 132, 133, 146, 145, 147, 149, 150, 112, 132, + 145, 147, 133, 146, 149, 150, 117, 134, 146, 148, 146, 148, 150, 151, + 2, 12, 6, 15, 6, 15, 13, 20, 6, 15, 8, 17, 15, 26, 17, 27, 6, 15, 15, + 26, 8, 17, 17, 27, 13, 20, 17, 27, 17, 27, 23, 29, 3, 19, 7, 20, 7, + 20, 14, 25, 7, 20, 9, 21, 16, 27, 18, 28, 7, 20, 16, 27, 9, 21, 18, + 28, 14, 25, 18, 28, 18, 28, 24, 30, 12, 38, 15, 40, 51, 56, 52, 58, + 15, 40, 17, 42, 56, 65, 57, 66, 51, 56, 56, 65, 74, 75, 75, 80, 52, + 58, 57, 66, 75, 80, 76, 81, 19, 44, 20, 45, 54, 68, 55, 69, 20, 45, + 21, 46, 58, 70, 59, 71, 54, 68, 58, 70, 77, 82, 78, 83, 55, 69, 59, + 71, 78, 83, 79, 84, 12, 38, 51, 56, 15, 40, 52, 58, 51, 56, 74, 75, + 56, 65, 75, 80, 15, 40, 56, 65, 17, 42, 57, 66, 52, 58, 75, 80, 57, + 66, 76, 81, 19, 44, 54, 68, 20, 45, 55, 69, 54, 68, 77, 82, 58, 70, + 78, 83, 20, 45, 58, 70, 21, 46, 59, 71, 55, 69, 78, 83, 59, 71, 79, + 84, 38, 44, 56, 68, 56, 68, 75, 82, 56, 68, 75, 96, 104, 105, 108, + 109, 56, 68, 104, 105, 75, 96, 108, 109, 75, 82, 108, 109, 108, 109, + 114, 115, 44, 120, 68, 121, 68, 121, 96, 122, 68, 121, 82, 122, 105, + 123, 109, 124, 68, 121, 105, 123, 82, 122, 109, 124, 96, 122, 109, + 124, 109, 124, 115, 125, 6, 51, 13, 52, 13, 52, 36, 55, 15, 56, 17, + 57, 40, 65, 42, 66, 15, 56, 40, 65, 17, 57, 42, 66, 40, 68, 48, 70, + 48, 70, 62, 72, 7, 54, 14, 55, 14, 55, 37, 64, 16, 58, 18, 59, 41, + 66, 43, 67, 16, 58, 41, 66, 18, 59, 43, 67, 41, 69, 49, 71, 49, 71, + 63, 73, 15, 56, 20, 58, 52, 75, 55, 78, 26, 65, 27, 66, 65, 98, 66, + 99, 56, 104, 68, 105, 75, 108, 96, 109, 65, 105, 70, 106, 98, 111, + 100, 112, 20, 68, 25, 69, 55, 96, 64, 97, 27, 70, 28, 71, 66, 100, + 67, 101, 58, 105, 69, 106, 78, 109, 97, 110, 66, 106, 71, 107, 99, + 112, 101, 113, 15, 56, 52, 75, 20, 58, 55, 78, 56, 104, 75, 108, 68, + 105, 96, 109, 26, 65, 65, 98, 27, 66, 66, 99, 65, 105, 98, 111, 70, + 106, 100, 112, 20, 68, 55, 96, 25, 69, 64, 97, 58, 105, 78, 109, 69, + 106, 97, 110, 27, 70, 66, 100, 28, 71, 67, 101, 66, 106, 99, 112, 71, + 107, 101, 113, 40, 68, 58, 82, 58, 82, 78, 90, 65, 105, 80, 109, 105, + 127, 109, 129, 65, 105, 105, 127, 80, 109, 109, 129, 98, 127, 111, + 131, 111, 131, 116, 133, 45, 121, 69, 122, 69, 122, 97, 126, 70, 123, + 83, 124, 106, 128, 110, 130, 70, 123, 106, 128, 83, 124, 110, 130, + 100, 128, 112, 132, 112, 132, 117, 134, 6, 51, 15, 56, 15, 56, 40, + 68, 13, 52, 17, 57, 40, 65, 48, 70, 13, 52, 40, 65, 17, 57, 48, 70, + 36, 55, 42, 66, 42, 66, 62, 72, 7, 54, 16, 58, 16, 58, 41, 69, 14, + 55, 18, 59, 41, 66, 49, 71, 14, 55, 41, 66, 18, 59, 49, 71, 37, 64, + 43, 67, 43, 67, 63, 73, 15, 56, 26, 65, 56, 104, 65, 105, 20, 58, 27, + 66, 68, 105, 70, 106, 52, 75, 65, 98, 75, 108, 98, 111, 55, 78, 66, + 99, 96, 109, 100, 112, 20, 68, 27, 70, 58, 105, 66, 106, 25, 69, 28, + 71, 69, 106, 71, 107, 55, 96, 66, 100, 78, 109, 99, 112, 64, 97, 67, + 101, 97, 110, 101, 113, 15, 56, 56, 104, 26, 65, 65, 105, 52, 75, 75, + 108, 65, 98, 98, 111, 20, 58, 68, 105, 27, 66, 70, 106, 55, 78, 96, + 109, 66, 99, 100, 112, 20, 68, 58, 105, 27, 70, 66, 106, 55, 96, 78, + 109, 66, 100, 99, 112, 25, 69, 69, 106, 28, 71, 71, 107, 64, 97, 97, + 110, 67, 101, 101, 113, 40, 68, 65, 105, 65, 105, 98, 127, 58, 82, + 80, 109, 105, 127, 111, 131, 58, 82, 105, 127, 80, 109, 111, 131, 78, + 90, 109, 129, 109, 129, 116, 133, 45, 121, 70, 123, 70, 123, 100, + 128, 69, 122, 83, 124, 106, 128, 112, 132, 69, 122, 106, 128, 83, + 124, 112, 132, 97, 126, 110, 130, 110, 130, 117, 134, 8, 74, 17, 75, + 17, 75, 42, 96, 17, 75, 23, 76, 48, 98, 62, 100, 17, 75, 48, 98, 23, + 76, 62, 100, 42, 96, 62, 100, 62, 100, 94, 102, 9, 77, 18, 78, 18, + 78, 43, 97, 18, 78, 24, 79, 49, 99, 63, 101, 18, 78, 49, 99, 24, 79, + 63, 101, 43, 97, 63, 101, 63, 101, 95, 103, 17, 75, 27, 80, 57, 108, + 66, 109, 27, 80, 29, 81, 70, 111, 72, 112, 57, 108, 70, 111, 76, 114, + 100, 116, 66, 109, 72, 112, 100, 116, 102, 118, 21, 82, 28, 83, 59, + 109, 67, 110, 28, 83, 30, 84, 71, 112, 73, 113, 59, 109, 71, 112, 79, + 115, 101, 117, 67, 110, 73, 113, 101, 117, 103, 119, 17, 75, 57, 108, + 27, 80, 66, 109, 57, 108, 76, 114, 70, 111, 100, 116, 27, 80, 70, + 111, 29, 81, 72, 112, 66, 109, 100, 116, 72, 112, 102, 118, 21, 82, + 59, 109, 28, 83, 67, 110, 59, 109, 79, 115, 71, 112, 101, 117, 28, + 83, 71, 112, 30, 84, 73, 113, 67, 110, 101, 117, 73, 113, 103, 119, + 42, 96, 66, 109, 66, 109, 99, 129, 66, 109, 81, 115, 106, 131, 112, + 133, 66, 109, 106, 131, 81, 115, 112, 133, 99, 129, 112, 133, 112, + 133, 118, 135, 46, 122, 71, 124, 71, 124, 101, 130, 71, 124, 84, 125, + 107, 132, 113, 134, 71, 124, 107, 132, 84, 125, 113, 134, 101, 130, + 113, 134, 113, 134, 119, 136, 4, 13, 7, 16, 13, 17, 16, 21, 7, 16, 9, + 18, 20, 27, 21, 28, 13, 17, 20, 27, 17, 23, 27, 29, 16, 21, 21, 28, + 27, 29, 29, 31, 7, 20, 9, 21, 16, 27, 18, 28, 9, 21, 10, 22, 21, 29, + 22, 30, 16, 27, 21, 29, 21, 29, 28, 31, 18, 28, 22, 30, 28, 31, 30, + 32, 13, 39, 16, 41, 52, 57, 53, 59, 16, 41, 18, 43, 58, 66, 59, 67, + 52, 57, 58, 66, 75, 76, 80, 81, 53, 59, 59, 67, 80, 81, 81, 85, 20, + 45, 21, 46, 58, 70, 59, 71, 21, 46, 22, 47, 60, 72, 61, 73, 58, 70, + 60, 72, 82, 86, 83, 87, 59, 71, 61, 73, 83, 87, 84, 88, 35, 40, 54, + 58, 40, 48, 58, 60, 54, 58, 77, 78, 68, 70, 82, 83, 40, 48, 68, 70, + 48, 62, 70, 72, 58, 60, 82, 83, 70, 72, 86, 87, 54, 68, 77, 82, 58, + 70, 78, 83, 77, 82, 89, 90, 82, 86, 90, 91, 58, 70, 82, 86, 60, 72, + 83, 87, 78, 83, 90, 91, 83, 87, 91, 92, 40, 45, 58, 69, 65, 70, 80, + 83, 58, 69, 78, 97, 105, 106, 109, 110, 65, 70, 105, 106, 98, 100, + 111, 112, 80, 83, 109, 110, 111, 112, 116, 117, 68, 121, 82, 122, + 105, 123, 109, 124, 82, 122, 90, 126, 127, 128, 129, 130, 105, 123, + 127, 128, 127, 128, 131, 132, 109, 124, 129, 130, 131, 132, 133, 134, + 13, 52, 16, 53, 39, 57, 41, 59, 20, 58, 21, 59, 45, 70, 46, 71, 52, + 75, 58, 80, 57, 76, 66, 81, 58, 82, 60, 83, 70, 86, 72, 87, 16, 58, + 18, 59, 41, 66, 43, 67, 21, 60, 22, 61, 46, 72, 47, 73, 53, 80, 59, + 81, 59, 81, 67, 85, 59, 83, 61, 84, 71, 87, 73, 88, 17, 57, 21, 59, + 57, 76, 59, 79, 27, 66, 28, 67, 70, 100, 71, 101, 75, 108, 82, 109, + 108, 114, 109, 115, 80, 109, 83, 110, 111, 116, 112, 117, 27, 70, 28, + 71, 66, 100, 67, 101, 29, 72, 30, 73, 72, 102, 73, 103, 80, 111, 83, + 112, 109, 116, 110, 117, 81, 112, 84, 113, 112, 118, 113, 119, 40, + 65, 58, 80, 45, 70, 69, 83, 68, 105, 82, 109, 121, 123, 122, 124, 65, + 98, 105, 111, 70, 100, 106, 112, 105, 127, 127, 131, 123, 128, 128, + 132, 58, 105, 78, 109, 69, 106, 97, 110, 82, 127, 90, 129, 122, 128, + 126, 130, 80, 111, 109, 116, 83, 112, 110, 117, 109, 131, 129, 133, + 124, 132, 130, 134, 48, 70, 60, 83, 70, 86, 83, 91, 70, 106, 83, 110, + 123, 128, 124, 130, 98, 111, 127, 131, 111, 116, 131, 133, 111, 131, + 131, 143, 144, 145, 145, 146, 70, 123, 83, 124, 106, 128, 110, 130, + 86, 128, 91, 130, 128, 141, 130, 142, 111, 144, 131, 145, 131, 145, + 143, 146, 116, 145, 133, 146, 145, 147, 146, 148, 13, 52, 20, 58, 52, + 75, 58, 82, 16, 53, 21, 59, 58, 80, 60, 83, 39, 57, 45, 70, 57, 76, + 70, 86, 41, 59, 46, 71, 66, 81, 72, 87, 16, 58, 21, 60, 53, 80, 59, + 83, 18, 59, 22, 61, 59, 81, 61, 84, 41, 66, 46, 72, 59, 81, 71, 87, + 43, 67, 47, 73, 67, 85, 73, 88, 17, 57, 27, 66, 75, 108, 80, 109, 21, + 59, 28, 67, 82, 109, 83, 110, 57, 76, 70, 100, 108, 114, 111, 116, + 59, 79, 71, 101, 109, 115, 112, 117, 27, 70, 29, 72, 80, 111, 81, + 112, 28, 71, 30, 73, 83, 112, 84, 113, 66, 100, 72, 102, 109, 116, + 112, 118, 67, 101, 73, 103, 110, 117, 113, 119, 40, 65, 68, 105, 65, + 98, 105, 127, 58, 80, 82, 109, 105, 111, 127, 131, 45, 70, 121, 123, + 70, 100, 123, 128, 69, 83, 122, 124, 106, 112, 128, 132, 58, 105, 82, + 127, 80, 111, 109, 131, 78, 109, 90, 129, 109, 116, 129, 133, 69, + 106, 122, 128, 83, 112, 124, 132, 97, 110, 126, 130, 110, 117, 130, + 134, 48, 70, 70, 106, 98, 111, 111, 131, 60, 83, 83, 110, 127, 131, + 131, 143, 70, 86, 123, 128, 111, 116, 144, 145, 83, 91, 124, 130, + 131, 133, 145, 146, 70, 123, 86, 128, 111, 144, 116, 145, 83, 124, + 91, 130, 131, 145, 133, 146, 106, 128, 128, 141, 131, 145, 145, 147, + 110, 130, 130, 142, 143, 146, 146, 148, 17, 75, 27, 80, 57, 108, 66, + 109, 27, 80, 29, 81, 70, 111, 72, 112, 57, 108, 70, 111, 76, 114, + 100, 116, 66, 109, 72, 112, 100, 116, 102, 118, 21, 82, 28, 83, 59, + 109, 67, 110, 28, 83, 30, 84, 71, 112, 73, 113, 59, 109, 71, 112, 79, + 115, 101, 117, 67, 110, 73, 113, 101, 117, 103, 119, 23, 76, 29, 81, + 76, 114, 81, 115, 29, 81, 31, 85, 86, 116, 87, 117, 76, 114, 86, 116, + 114, 137, 116, 138, 81, 115, 87, 117, 116, 138, 118, 139, 29, 86, 31, + 87, 81, 116, 85, 117, 31, 87, 32, 88, 87, 118, 88, 119, 81, 116, 87, + 118, 115, 138, 117, 139, 85, 117, 88, 119, 117, 139, 119, 140, 48, + 98, 70, 111, 70, 111, 106, 131, 70, 111, 86, 116, 123, 144, 128, 145, + 70, 111, 123, 144, 86, 116, 128, 145, 106, 131, 128, 145, 128, 145, + 141, 147, 60, 127, 83, 131, 83, 131, 110, 143, 83, 131, 91, 133, 124, + 145, 130, 146, 83, 131, 124, 145, 91, 133, 130, 146, 110, 143, 130, + 146, 130, 146, 142, 148, 62, 100, 72, 112, 100, 116, 112, 133, 72, + 112, 87, 117, 128, 145, 132, 146, 100, 116, 128, 145, 116, 138, 145, + 149, 112, 133, 132, 146, 145, 149, 147, 150, 72, 128, 87, 132, 112, + 145, 117, 146, 87, 132, 92, 134, 132, 147, 134, 148, 112, 145, 132, + 147, 133, 149, 146, 150, 117, 146, 134, 148, 146, 150, 148, 151, 4, + 13, 13, 17, 7, 16, 16, 21, 13, 17, 17, 23, 20, 27, 27, 29, 7, 16, 20, + 27, 9, 18, 21, 28, 16, 21, 27, 29, 21, 28, 29, 31, 7, 20, 16, 27, 9, + 21, 18, 28, 16, 27, 21, 29, 21, 29, 28, 31, 9, 21, 21, 29, 10, 22, + 22, 30, 18, 28, 28, 31, 22, 30, 30, 32, 35, 40, 40, 48, 54, 58, 58, + 60, 40, 48, 48, 62, 68, 70, 70, 72, 54, 58, 68, 70, 77, 78, 82, 83, + 58, 60, 70, 72, 82, 83, 86, 87, 54, 68, 58, 70, 77, 82, 78, 83, 58, + 70, 60, 72, 82, 86, 83, 87, 77, 82, 82, 86, 89, 90, 90, 91, 78, 83, + 83, 87, 90, 91, 91, 92, 13, 39, 52, 57, 16, 41, 53, 59, 52, 57, 75, + 76, 58, 66, 80, 81, 16, 41, 58, 66, 18, 43, 59, 67, 53, 59, 80, 81, + 59, 67, 81, 85, 20, 45, 58, 70, 21, 46, 59, 71, 58, 70, 82, 86, 60, + 72, 83, 87, 21, 46, 60, 72, 22, 47, 61, 73, 59, 71, 83, 87, 61, 73, + 84, 88, 40, 45, 65, 70, 58, 69, 80, 83, 65, 70, 98, 100, 105, 106, + 111, 112, 58, 69, 105, 106, 78, 97, 109, 110, 80, 83, 111, 112, 109, + 110, 116, 117, 68, 121, 105, 123, 82, 122, 109, 124, 105, 123, 127, + 128, 127, 128, 131, 132, 82, 122, 127, 128, 90, 126, 129, 130, 109, + 124, 131, 132, 129, 130, 133, 134, 13, 52, 39, 57, 16, 53, 41, 59, + 52, 75, 57, 76, 58, 80, 66, 81, 20, 58, 45, 70, 21, 59, 46, 71, 58, + 82, 70, 86, 60, 83, 72, 87, 16, 58, 41, 66, 18, 59, 43, 67, 53, 80, + 59, 81, 59, 81, 67, 85, 21, 60, 46, 72, 22, 61, 47, 73, 59, 83, 71, + 87, 61, 84, 73, 88, 40, 65, 45, 70, 58, 80, 69, 83, 65, 98, 70, 100, + 105, 111, 106, 112, 68, 105, 121, 123, 82, 109, 122, 124, 105, 127, + 123, 128, 127, 131, 128, 132, 58, 105, 69, 106, 78, 109, 97, 110, 80, + 111, 83, 112, 109, 116, 110, 117, 82, 127, 122, 128, 90, 129, 126, + 130, 109, 131, 124, 132, 129, 133, 130, 134, 17, 57, 57, 76, 21, 59, + 59, 79, 75, 108, 108, 114, 82, 109, 109, 115, 27, 66, 70, 100, 28, + 67, 71, 101, 80, 109, 111, 116, 83, 110, 112, 117, 27, 70, 66, 100, + 28, 71, 67, 101, 80, 111, 109, 116, 83, 112, 110, 117, 29, 72, 72, + 102, 30, 73, 73, 103, 81, 112, 112, 118, 84, 113, 113, 119, 48, 70, + 70, 86, 60, 83, 83, 91, 98, 111, 111, 116, 127, 131, 131, 133, 70, + 106, 123, 128, 83, 110, 124, 130, 111, 131, 144, 145, 131, 143, 145, + 146, 70, 123, 106, 128, 83, 124, 110, 130, 111, 144, 131, 145, 131, + 145, 143, 146, 86, 128, 128, 141, 91, 130, 130, 142, 116, 145, 145, + 147, 133, 146, 146, 148, 13, 52, 52, 75, 20, 58, 58, 82, 39, 57, 57, + 76, 45, 70, 70, 86, 16, 53, 58, 80, 21, 59, 60, 83, 41, 59, 66, 81, + 46, 71, 72, 87, 16, 58, 53, 80, 21, 60, 59, 83, 41, 66, 59, 81, 46, + 72, 71, 87, 18, 59, 59, 81, 22, 61, 61, 84, 43, 67, 67, 85, 47, 73, + 73, 88, 40, 65, 65, 98, 68, 105, 105, 127, 45, 70, 70, 100, 121, 123, + 123, 128, 58, 80, 105, 111, 82, 109, 127, 131, 69, 83, 106, 112, 122, + 124, 128, 132, 58, 105, 80, 111, 82, 127, 109, 131, 69, 106, 83, 112, + 122, 128, 124, 132, 78, 109, 109, 116, 90, 129, 129, 133, 97, 110, + 110, 117, 126, 130, 130, 134, 17, 57, 75, 108, 27, 66, 80, 109, 57, + 76, 108, 114, 70, 100, 111, 116, 21, 59, 82, 109, 28, 67, 83, 110, + 59, 79, 109, 115, 71, 101, 112, 117, 27, 70, 80, 111, 29, 72, 81, + 112, 66, 100, 109, 116, 72, 102, 112, 118, 28, 71, 83, 112, 30, 73, + 84, 113, 67, 101, 110, 117, 73, 103, 113, 119, 48, 70, 98, 111, 70, + 106, 111, 131, 70, 86, 111, 116, 123, 128, 144, 145, 60, 83, 127, + 131, 83, 110, 131, 143, 83, 91, 131, 133, 124, 130, 145, 146, 70, + 123, 111, 144, 86, 128, 116, 145, 106, 128, 131, 145, 128, 141, 145, + 147, 83, 124, 131, 145, 91, 130, 133, 146, 110, 130, 143, 146, 130, + 142, 146, 148, 17, 75, 57, 108, 27, 80, 66, 109, 57, 108, 76, 114, + 70, 111, 100, 116, 27, 80, 70, 111, 29, 81, 72, 112, 66, 109, 100, + 116, 72, 112, 102, 118, 21, 82, 59, 109, 28, 83, 67, 110, 59, 109, + 79, 115, 71, 112, 101, 117, 28, 83, 71, 112, 30, 84, 73, 113, 67, + 110, 101, 117, 73, 113, 103, 119, 48, 98, 70, 111, 70, 111, 106, 131, + 70, 111, 86, 116, 123, 144, 128, 145, 70, 111, 123, 144, 86, 116, + 128, 145, 106, 131, 128, 145, 128, 145, 141, 147, 60, 127, 83, 131, + 83, 131, 110, 143, 83, 131, 91, 133, 124, 145, 130, 146, 83, 131, + 124, 145, 91, 133, 130, 146, 110, 143, 130, 146, 130, 146, 142, 148, + 23, 76, 76, 114, 29, 81, 81, 115, 76, 114, 114, 137, 86, 116, 116, + 138, 29, 81, 86, 116, 31, 85, 87, 117, 81, 115, 116, 138, 87, 117, + 118, 139, 29, 86, 81, 116, 31, 87, 85, 117, 81, 116, 115, 138, 87, + 118, 117, 139, 31, 87, 87, 118, 32, 88, 88, 119, 85, 117, 117, 139, + 88, 119, 119, 140, 62, 100, 100, 116, 72, 112, 112, 133, 100, 116, + 116, 138, 128, 145, 145, 149, 72, 112, 128, 145, 87, 117, 132, 146, + 112, 133, 145, 149, 132, 146, 147, 150, 72, 128, 112, 145, 87, 132, + 117, 146, 112, 145, 133, 149, 132, 147, 146, 150, 87, 132, 132, 147, + 92, 134, 134, 148, 117, 146, 146, 150, 134, 148, 148, 151, 11, 14, + 14, 18, 14, 18, 18, 22, 14, 18, 18, 24, 25, 28, 28, 30, 14, 18, 25, + 28, 18, 24, 28, 30, 18, 22, 28, 30, 28, 30, 31, 32, 14, 25, 18, 28, + 18, 28, 24, 30, 18, 28, 22, 30, 28, 31, 30, 32, 18, 28, 28, 31, 22, + 30, 30, 32, 24, 30, 30, 32, 30, 32, 32, 33, 36, 41, 41, 49, 55, 59, + 59, 61, 41, 49, 49, 63, 69, 71, 71, 73, 55, 59, 69, 71, 78, 79, 83, + 84, 59, 61, 71, 73, 83, 84, 87, 88, 55, 69, 59, 71, 78, 83, 79, 84, + 59, 71, 61, 73, 83, 87, 84, 88, 78, 83, 83, 87, 90, 91, 91, 92, 79, + 84, 84, 88, 91, 92, 92, 93, 36, 41, 55, 59, 41, 49, 59, 61, 55, 59, + 78, 79, 69, 71, 83, 84, 41, 49, 69, 71, 49, 63, 71, 73, 59, 61, 83, + 84, 71, 73, 87, 88, 55, 69, 78, 83, 59, 71, 79, 84, 78, 83, 90, 91, + 83, 87, 91, 92, 59, 71, 83, 87, 61, 73, 84, 88, 79, 84, 91, 92, 84, + 88, 92, 93, 42, 46, 66, 71, 66, 71, 81, 84, 66, 71, 99, 101, 106, + 107, 112, 113, 66, 71, 106, 107, 99, 101, 112, 113, 81, 84, 112, 113, + 112, 113, 118, 119, 96, 122, 109, 124, 109, 124, 115, 125, 109, 124, + 129, 130, 131, 132, 133, 134, 109, 124, 131, 132, 129, 130, 133, 134, + 115, 125, 133, 134, 133, 134, 135, 136, 36, 55, 41, 59, 41, 59, 49, + 61, 55, 78, 59, 79, 69, 83, 71, 84, 55, 78, 69, 83, 59, 79, 71, 84, + 78, 90, 83, 91, 83, 91, 87, 92, 41, 69, 49, 71, 49, 71, 63, 73, 59, + 83, 61, 84, 71, 87, 73, 88, 59, 83, 71, 87, 61, 84, 73, 88, 79, 91, + 84, 92, 84, 92, 88, 93, 42, 66, 46, 71, 66, 81, 71, 84, 66, 99, 71, + 101, 106, 112, 107, 113, 96, 109, 122, 124, 109, 115, 124, 125, 109, + 129, 124, 130, 131, 133, 132, 134, 66, 106, 71, 107, 99, 112, 101, + 113, 81, 112, 84, 113, 112, 118, 113, 119, 109, 131, 124, 132, 129, + 133, 130, 134, 115, 133, 125, 134, 133, 135, 134, 136, 42, 66, 66, + 81, 46, 71, 71, 84, 96, 109, 109, 115, 122, 124, 124, 125, 66, 99, + 106, 112, 71, 101, 107, 113, 109, 129, 131, 133, 124, 130, 132, 134, + 66, 106, 99, 112, 71, 107, 101, 113, 109, 131, 129, 133, 124, 132, + 130, 134, 81, 112, 112, 118, 84, 113, 113, 119, 115, 133, 133, 135, + 125, 134, 134, 136, 62, 72, 72, 87, 72, 87, 87, 92, 100, 112, 112, + 117, 128, 132, 132, 134, 100, 112, 128, 132, 112, 117, 132, 134, 116, + 133, 145, 146, 145, 146, 147, 148, 100, 128, 112, 132, 112, 132, 117, + 134, 116, 145, 133, 146, 145, 147, 146, 148, 116, 145, 145, 147, 133, + 146, 146, 148, 138, 149, 149, 150, 149, 150, 150, 151, 36, 55, 55, + 78, 55, 78, 78, 90, 41, 59, 59, 79, 69, 83, 83, 91, 41, 59, 69, 83, + 59, 79, 83, 91, 49, 61, 71, 84, 71, 84, 87, 92, 41, 69, 59, 83, 59, + 83, 79, 91, 49, 71, 61, 84, 71, 87, 84, 92, 49, 71, 71, 87, 61, 84, + 84, 92, 63, 73, 73, 88, 73, 88, 88, 93, 42, 66, 66, 99, 96, 109, 109, + 129, 46, 71, 71, 101, 122, 124, 124, 130, 66, 81, 106, 112, 109, 115, + 131, 133, 71, 84, 107, 113, 124, 125, 132, 134, 66, 106, 81, 112, + 109, 131, 115, 133, 71, 107, 84, 113, 124, 132, 125, 134, 99, 112, + 112, 118, 129, 133, 133, 135, 101, 113, 113, 119, 130, 134, 134, 136, + 42, 66, 96, 109, 66, 99, 109, 129, 66, 81, 109, 115, 106, 112, 131, + 133, 46, 71, 122, 124, 71, 101, 124, 130, 71, 84, 124, 125, 107, 113, + 132, 134, 66, 106, 109, 131, 81, 112, 115, 133, 99, 112, 129, 133, + 112, 118, 133, 135, 71, 107, 124, 132, 84, 113, 125, 134, 101, 113, + 130, 134, 113, 119, 134, 136, 62, 72, 100, 112, 100, 112, 116, 133, + 72, 87, 112, 117, 128, 132, 145, 146, 72, 87, 128, 132, 112, 117, + 145, 146, 87, 92, 132, 134, 132, 134, 147, 148, 100, 128, 116, 145, + 116, 145, 138, 149, 112, 132, 133, 146, 145, 147, 149, 150, 112, 132, + 145, 147, 133, 146, 149, 150, 117, 134, 146, 148, 146, 148, 150, 151, + 42, 96, 66, 109, 66, 109, 99, 129, 66, 109, 81, 115, 106, 131, 112, + 133, 66, 109, 106, 131, 81, 115, 112, 133, 99, 129, 112, 133, 112, + 133, 118, 135, 46, 122, 71, 124, 71, 124, 101, 130, 71, 124, 84, 125, + 107, 132, 113, 134, 71, 124, 107, 132, 84, 125, 113, 134, 101, 130, + 113, 134, 113, 134, 119, 136, 62, 100, 72, 112, 100, 116, 112, 133, + 72, 112, 87, 117, 128, 145, 132, 146, 100, 116, 128, 145, 116, 138, + 145, 149, 112, 133, 132, 146, 145, 149, 147, 150, 72, 128, 87, 132, + 112, 145, 117, 146, 87, 132, 92, 134, 132, 147, 134, 148, 112, 145, + 132, 147, 133, 149, 146, 150, 117, 146, 134, 148, 146, 150, 148, 151, + 62, 100, 100, 116, 72, 112, 112, 133, 100, 116, 116, 138, 128, 145, + 145, 149, 72, 112, 128, 145, 87, 117, 132, 146, 112, 133, 145, 149, + 132, 146, 147, 150, 72, 128, 112, 145, 87, 132, 117, 146, 112, 145, + 133, 149, 132, 147, 146, 150, 87, 132, 132, 147, 92, 134, 134, 148, + 117, 146, 146, 150, 134, 148, 148, 151, 94, 102, 102, 118, 102, 118, + 118, 135, 102, 118, 118, 139, 141, 147, 147, 150, 102, 118, 141, 147, + 118, 139, 147, 150, 118, 135, 147, 150, 147, 150, 152, 153, 102, 141, + 118, 147, 118, 147, 139, 150, 118, 147, 135, 150, 147, 152, 150, 153, + 118, 147, 147, 152, 135, 150, 150, 153, 139, 150, 150, 153, 150, 153, + 153, 154, 1, 5, 5, 12, 5, 12, 12, 19, 5, 12, 12, 35, 50, 51, 51, 54, + 5, 12, 50, 51, 12, 35, 51, 54, 12, 19, 51, 54, 51, 54, 74, 77, 5, 50, + 12, 51, 12, 51, 35, 54, 12, 51, 19, 54, 51, 74, 54, 77, 12, 51, 51, + 74, 19, 54, 54, 77, 35, 54, 54, 77, 54, 77, 77, 89, 2, 6, 6, 13, 12, + 15, 15, 20, 6, 13, 13, 36, 51, 52, 52, 55, 12, 15, 51, 52, 38, 40, + 56, 58, 15, 20, 52, 55, 56, 58, 75, 78, 12, 51, 15, 52, 38, 56, 40, + 58, 15, 52, 20, 55, 56, 75, 58, 78, 38, 56, 56, 75, 44, 68, 68, 82, + 40, 58, 58, 78, 68, 82, 82, 90, 2, 6, 12, 15, 6, 13, 15, 20, 12, 15, + 38, 40, 51, 52, 56, 58, 6, 13, 51, 52, 13, 36, 52, 55, 15, 20, 56, + 58, 52, 55, 75, 78, 12, 51, 38, 56, 15, 52, 40, 58, 38, 56, 44, 68, + 56, 75, 68, 82, 15, 52, 56, 75, 20, 55, 58, 78, 40, 58, 68, 82, 58, + 78, 82, 90, 4, 7, 13, 16, 13, 16, 17, 21, 13, 16, 39, 41, 52, 53, 57, + 59, 13, 16, 52, 53, 39, 41, 57, 59, 17, 21, 57, 59, 57, 59, 76, 79, + 35, 54, 40, 58, 40, 58, 48, 60, 40, 58, 45, 69, 65, 80, 70, 83, 40, + 58, 65, 80, 45, 69, 70, 83, 48, 60, 70, 83, 70, 83, 86, 91, 2, 12, 6, + 15, 6, 15, 13, 20, 12, 38, 15, 40, 51, 56, 52, 58, 12, 38, 51, 56, + 15, 40, 52, 58, 38, 44, 56, 68, 56, 68, 75, 82, 6, 51, 13, 52, 13, 52, + 36, 55, 15, 56, 20, 58, 52, 75, 55, 78, 15, 56, 52, 75, 20, 58, 55, + 78, 40, 68, 58, 82, 58, 82, 78, 90, 4, 13, 7, 16, 13, 17, 16, 21, 13, + 39, 16, 41, 52, 57, 53, 59, 35, 40, 54, 58, 40, 48, 58, 60, 40, 45, + 58, 69, 65, 70, 80, 83, 13, 52, 16, 53, 39, 57, 41, 59, 17, 57, 21, + 59, 57, 76, 59, 79, 40, 65, 58, 80, 45, 70, 69, 83, 48, 70, 60, 83, + 70, 86, 83, 91, 4, 13, 13, 17, 7, 16, 16, 21, 35, 40, 40, 48, 54, 58, + 58, 60, 13, 39, 52, 57, 16, 41, 53, 59, 40, 45, 65, 70, 58, 69, 80, + 83, 13, 52, 39, 57, 16, 53, 41, 59, 40, 65, 45, 70, 58, 80, 69, 83, + 17, 57, 57, 76, 21, 59, 59, 79, 48, 70, 70, 86, 60, 83, 83, 91, 11, + 14, 14, 18, 14, 18, 18, 22, 36, 41, 41, 49, 55, 59, 59, 61, 36, 41, + 55, 59, 41, 49, 59, 61, 42, 46, 66, 71, 66, 71, 81, 84, 36, 55, 41, + 59, 41, 59, 49, 61, 42, 66, 46, 71, 66, 81, 71, 84, 42, 66, 66, 81, + 46, 71, 71, 84, 62, 72, 72, 87, 72, 87, 87, 92, 2, 12, 12, 38, 12, + 38, 38, 44, 6, 15, 15, 40, 51, 56, 56, 68, 6, 15, 51, 56, 15, 40, 56, + 68, 13, 20, 52, 58, 52, 58, 75, 82, 6, 51, 15, 56, 15, 56, 40, 68, + 13, 52, 20, 58, 52, 75, 58, 82, 13, 52, 52, 75, 20, 58, 58, 82, 36, + 55, 55, 78, 55, 78, 78, 90, 4, 13, 13, 39, 35, 40, 40, 45, 7, 16, 16, + 41, 54, 58, 58, 69, 13, 17, 52, 57, 40, 48, 65, 70, 16, 21, 53, 59, + 58, 60, 80, 83, 13, 52, 17, 57, 40, 65, 48, 70, 16, 53, 21, 59, 58, + 80, 60, 83, 39, 57, 57, 76, 45, 70, 70, 86, 41, 59, 59, 79, 69, 83, + 83, 91, 4, 13, 35, 40, 13, 39, 40, 45, 13, 17, 40, 48, 52, 57, 65, + 70, 7, 16, 54, 58, 16, 41, 58, 69, 16, 21, 58, 60, 53, 59, 80, 83, + 13, 52, 40, 65, 17, 57, 48, 70, 39, 57, 45, 70, 57, 76, 70, 86, 16, + 53, 58, 80, 21, 59, 60, 83, 41, 59, 69, 83, 59, 79, 83, 91, 11, 14, + 36, 41, 36, 41, 42, 46, 14, 18, 41, 49, 55, 59, 66, 71, 14, 18, 55, + 59, 41, 49, 66, 71, 18, 22, 59, 61, 59, 61, 81, 84, 36, 55, 42, 66, + 42, 66, 62, 72, 41, 59, 46, 71, 66, 81, 72, 87, 41, 59, 66, 81, 46, + 71, 72, 87, 49, 61, 71, 84, 71, 84, 87, 92, 4, 35, 13, 40, 13, 40, + 39, 45, 13, 40, 17, 48, 52, 65, 57, 70, 13, 40, 52, 65, 17, 48, 57, + 70, 39, 45, 57, 70, 57, 70, 76, 86, 7, 54, 16, 58, 16, 58, 41, 69, + 16, 58, 21, 60, 53, 80, 59, 83, 16, 58, 53, 80, 21, 60, 59, 83, 41, + 69, 59, 83, 59, 83, 79, 91, 11, 36, 14, 41, 36, 42, 41, 46, 14, 41, + 18, 49, 55, 66, 59, 71, 36, 42, 55, 66, 42, 62, 66, 72, 41, 46, 59, + 71, 66, 72, 81, 87, 14, 55, 18, 59, 41, 66, 49, 71, 18, 59, 22, 61, + 59, 81, 61, 84, 41, 66, 59, 81, 46, 72, 71, 87, 49, 71, 61, 84, 71, + 87, 84, 92, 11, 36, 36, 42, 14, 41, 41, 46, 36, 42, 42, 62, 55, 66, + 66, 72, 14, 41, 55, 66, 18, 49, 59, 71, 41, 46, 66, 72, 59, 71, 81, + 87, 14, 55, 41, 66, 18, 59, 49, 71, 41, 66, 46, 72, 59, 81, 71, 87, + 18, 59, 59, 81, 22, 61, 61, 84, 49, 71, 71, 87, 61, 84, 84, 92, 34, + 37, 37, 43, 37, 43, 43, 47, 37, 43, 43, 63, 64, 67, 67, 73, 37, 43, + 64, 67, 43, 63, 67, 73, 43, 47, 67, 73, 67, 73, 85, 88, 37, 64, 43, + 67, 43, 67, 63, 73, 43, 67, 47, 73, 67, 85, 73, 88, 43, 67, 67, 85, + 47, 73, 73, 88, 63, 73, 73, 88, 73, 88, 88, 93, 2, 6, 6, 13, 12, 15, + 15, 20, 6, 13, 13, 36, 51, 52, 52, 55, 12, 15, 51, 52, 38, 40, 56, + 58, 15, 20, 52, 55, 56, 58, 75, 78, 12, 51, 15, 52, 38, 56, 40, 58, + 15, 52, 20, 55, 56, 75, 58, 78, 38, 56, 56, 75, 44, 68, 68, 82, 40, + 58, 58, 78, 68, 82, 82, 90, 3, 7, 7, 14, 19, 20, 20, 25, 7, 14, 14, + 37, 54, 55, 55, 64, 19, 20, 54, 55, 44, 45, 68, 69, 20, 25, 55, 64, + 68, 69, 96, 97, 19, 54, 20, 55, 44, 68, 45, 69, 20, 55, 25, 64, 68, + 96, 69, 97, 44, 68, 68, 96, 120, 121, 121, 122, 45, 69, 69, 97, 121, + 122, 122, 126, 6, 8, 15, 17, 15, 17, 26, 27, 15, 17, 40, 42, 56, 57, + 65, 66, 15, 17, 56, 57, 40, 42, 65, 66, 26, 27, 65, 66, 65, 66, 98, + 99, 51, 74, 56, 75, 56, 75, 65, 80, 56, 75, 68, 96, 104, 108, 105, + 109, 56, 75, 104, 108, 68, 96, 105, 109, 65, 80, 105, 109, 105, 109, + 127, 129, 7, 9, 16, 18, 20, 21, 27, 28, 16, 18, 41, 43, 58, 59, 66, + 67, 20, 21, 58, 59, 45, 46, 70, 71, 27, 28, 66, 67, 70, 71, 100, 101, + 54, 77, 58, 78, 68, 82, 70, 83, 58, 78, 69, 97, 105, 109, 106, 110, + 68, 82, 105, 109, 121, 122, 123, 124, 70, 83, 106, 110, 123, 124, + 128, 130, 6, 15, 8, 17, 15, 26, 17, 27, 15, 40, 17, 42, 56, 65, 57, + 66, 51, 56, 74, 75, 56, 65, 75, 80, 56, 68, 75, 96, 104, 105, 108, + 109, 15, 56, 17, 57, 40, 65, 42, 66, 26, 65, 27, 66, 65, 98, 66, 99, + 56, 104, 75, 108, 68, 105, 96, 109, 65, 105, 80, 109, 105, 127, 109, + 129, 7, 16, 9, 18, 20, 27, 21, 28, 16, 41, 18, 43, 58, 66, 59, 67, + 54, 58, 77, 78, 68, 70, 82, 83, 58, 69, 78, 97, 105, 106, 109, 110, + 20, 58, 21, 59, 45, 70, 46, 71, 27, 66, 28, 67, 70, 100, 71, 101, 68, + 105, 82, 109, 121, 123, 122, 124, 70, 106, 83, 110, 123, 128, 124, + 130, 13, 17, 17, 23, 20, 27, 27, 29, 40, 48, 48, 62, 68, 70, 70, 72, + 52, 57, 75, 76, 58, 66, 80, 81, 65, 70, 98, 100, 105, 106, 111, 112, + 52, 75, 57, 76, 58, 80, 66, 81, 65, 98, 70, 100, 105, 111, 106, 112, + 75, 108, 108, 114, 82, 109, 109, 115, 98, 111, 111, 116, 127, 131, + 131, 133, 14, 18, 18, 24, 25, 28, 28, 30, 41, 49, 49, 63, 69, 71, 71, + 73, 55, 59, 78, 79, 69, 71, 83, 84, 66, 71, 99, 101, 106, 107, 112, + 113, 55, 78, 59, 79, 69, 83, 71, 84, 66, 99, 71, 101, 106, 112, 107, + 113, 96, 109, 109, 115, 122, 124, 124, 125, 100, 112, 112, 117, 128, + 132, 132, 134, 6, 15, 15, 40, 51, 56, 56, 68, 8, 17, 17, 42, 74, 75, + 75, 96, 15, 26, 56, 65, 56, 65, 104, 105, 17, 27, 57, 66, 75, 80, + 108, 109, 15, 56, 26, 65, 56, 104, 65, 105, 17, 57, 27, 66, 75, 108, + 80, 109, 40, 65, 65, 98, 68, 105, 105, 127, 42, 66, 66, 99, 96, 109, + 109, 129, 7, 16, 16, 41, 54, 58, 58, 69, 9, 18, 18, 43, 77, 78, 78, + 97, 20, 27, 58, 66, 68, 70, 105, 106, 21, 28, 59, 67, 82, 83, 109, + 110, 20, 58, 27, 66, 68, 105, 70, 106, 21, 59, 28, 67, 82, 109, 83, + 110, 45, 70, 70, 100, 121, 123, 123, 128, 46, 71, 71, 101, 122, 124, + 124, 130, 13, 17, 40, 48, 52, 57, 65, 70, 17, 23, 48, 62, 75, 76, 98, + 100, 20, 27, 68, 70, 58, 66, 105, 106, 27, 29, 70, 72, 80, 81, 111, + 112, 52, 75, 65, 98, 75, 108, 98, 111, 57, 76, 70, 100, 108, 114, + 111, 116, 58, 80, 105, 111, 82, 109, 127, 131, 66, 81, 106, 112, 109, + 115, 131, 133, 14, 18, 41, 49, 55, 59, 66, 71, 18, 24, 49, 63, 78, + 79, 99, 101, 25, 28, 69, 71, 69, 71, 106, 107, 28, 30, 71, 73, 83, + 84, 112, 113, 55, 78, 66, 99, 96, 109, 100, 112, 59, 79, 71, 101, + 109, 115, 112, 117, 69, 83, 106, 112, 122, 124, 128, 132, 71, 84, + 107, 113, 124, 125, 132, 134, 13, 40, 17, 48, 52, 65, 57, 70, 17, 48, + 23, 62, 75, 98, 76, 100, 52, 65, 75, 98, 75, 98, 108, 111, 57, 70, + 76, 100, 108, 111, 114, 116, 20, 68, 27, 70, 58, 105, 66, 106, 27, + 70, 29, 72, 80, 111, 81, 112, 58, 105, 80, 111, 82, 127, 109, 131, + 66, 106, 81, 112, 109, 131, 115, 133, 14, 41, 18, 49, 55, 66, 59, 71, + 18, 49, 24, 63, 78, 99, 79, 101, 55, 66, 78, 99, 96, 100, 109, 112, + 59, 71, 79, 101, 109, 112, 115, 117, 25, 69, 28, 71, 69, 106, 71, + 107, 28, 71, 30, 73, 83, 112, 84, 113, 69, 106, 83, 112, 122, 128, + 124, 132, 71, 107, 84, 113, 124, 132, 125, 134, 36, 42, 42, 62, 55, + 66, 66, 72, 42, 62, 62, 94, 96, 100, 100, 102, 55, 66, 96, 100, 78, + 99, 109, 112, 66, 72, 100, 102, 109, 112, 116, 118, 55, 96, 66, 100, + 78, 109, 99, 112, 66, 100, 72, 102, 109, 116, 112, 118, 78, 109, 109, + 116, 90, 129, 129, 133, 99, 112, 112, 118, 129, 133, 133, 135, 37, + 43, 43, 63, 64, 67, 67, 73, 43, 63, 63, 95, 97, 101, 101, 103, 64, + 67, 97, 101, 97, 101, 110, 113, 67, 73, 101, 103, 110, 113, 117, 119, + 64, 97, 67, 101, 97, 110, 101, 113, 67, 101, 73, 103, 110, 117, 113, + 119, 97, 110, 110, 117, 126, 130, 130, 134, 101, 113, 113, 119, 130, + 134, 134, 136, 2, 6, 12, 15, 6, 13, 15, 20, 12, 15, 38, 40, 51, 52, + 56, 58, 6, 13, 51, 52, 13, 36, 52, 55, 15, 20, 56, 58, 52, 55, 75, + 78, 12, 51, 38, 56, 15, 52, 40, 58, 38, 56, 44, 68, 56, 75, 68, 82, + 15, 52, 56, 75, 20, 55, 58, 78, 40, 58, 68, 82, 58, 78, 82, 90, 6, 8, + 15, 17, 15, 17, 26, 27, 15, 17, 40, 42, 56, 57, 65, 66, 15, 17, 56, + 57, 40, 42, 65, 66, 26, 27, 65, 66, 65, 66, 98, 99, 51, 74, 56, 75, + 56, 75, 65, 80, 56, 75, 68, 96, 104, 108, 105, 109, 56, 75, 104, 108, + 68, 96, 105, 109, 65, 80, 105, 109, 105, 109, 127, 129, 3, 7, 19, 20, + 7, 14, 20, 25, 19, 20, 44, 45, 54, 55, 68, 69, 7, 14, 54, 55, 14, 37, + 55, 64, 20, 25, 68, 69, 55, 64, 96, 97, 19, 54, 44, 68, 20, 55, 45, + 69, 44, 68, 120, 121, 68, 96, 121, 122, 20, 55, 68, 96, 25, 64, 69, + 97, 45, 69, 121, 122, 69, 97, 122, 126, 7, 9, 20, 21, 16, 18, 27, 28, + 20, 21, 45, 46, 58, 59, 70, 71, 16, 18, 58, 59, 41, 43, 66, 67, 27, + 28, 70, 71, 66, 67, 100, 101, 54, 77, 68, 82, 58, 78, 70, 83, 68, 82, + 121, 122, 105, 109, 123, 124, 58, 78, 105, 109, 69, 97, 106, 110, 70, + 83, 123, 124, 106, 110, 128, 130, 6, 15, 15, 26, 8, 17, 17, 27, 51, + 56, 56, 65, 74, 75, 75, 80, 15, 40, 56, 65, 17, 42, 57, 66, 56, 68, + 104, 105, 75, 96, 108, 109, 15, 56, 40, 65, 17, 57, 42, 66, 56, 104, + 68, 105, 75, 108, 96, 109, 26, 65, 65, 98, 27, 66, 66, 99, 65, 105, + 105, 127, 80, 109, 109, 129, 13, 17, 20, 27, 17, 23, 27, 29, 52, 57, + 58, 66, 75, 76, 80, 81, 40, 48, 68, 70, 48, 62, 70, 72, 65, 70, 105, + 106, 98, 100, 111, 112, 52, 75, 58, 80, 57, 76, 66, 81, 75, 108, 82, + 109, 108, 114, 109, 115, 65, 98, 105, 111, 70, 100, 106, 112, 98, + 111, 127, 131, 111, 116, 131, 133, 7, 16, 20, 27, 9, 18, 21, 28, 54, + 58, 68, 70, 77, 78, 82, 83, 16, 41, 58, 66, 18, 43, 59, 67, 58, 69, + 105, 106, 78, 97, 109, 110, 20, 58, 45, 70, 21, 59, 46, 71, 68, 105, + 121, 123, 82, 109, 122, 124, 27, 66, 70, 100, 28, 67, 71, 101, 70, + 106, 123, 128, 83, 110, 124, 130, 14, 18, 25, 28, 18, 24, 28, 30, 55, + 59, 69, 71, 78, 79, 83, 84, 41, 49, 69, 71, 49, 63, 71, 73, 66, 71, + 106, 107, 99, 101, 112, 113, 55, 78, 69, 83, 59, 79, 71, 84, 96, 109, + 122, 124, 109, 115, 124, 125, 66, 99, 106, 112, 71, 101, 107, 113, + 100, 112, 128, 132, 112, 117, 132, 134, 6, 15, 51, 56, 15, 40, 56, + 68, 15, 26, 56, 65, 56, 65, 104, 105, 8, 17, 74, 75, 17, 42, 75, 96, + 17, 27, 75, 80, 57, 66, 108, 109, 15, 56, 56, 104, 26, 65, 65, 105, + 40, 65, 68, 105, 65, 98, 105, 127, 17, 57, 75, 108, 27, 66, 80, 109, + 42, 66, 96, 109, 66, 99, 109, 129, 13, 17, 52, 57, 40, 48, 65, 70, + 20, 27, 58, 66, 68, 70, 105, 106, 17, 23, 75, 76, 48, 62, 98, 100, + 27, 29, 80, 81, 70, 72, 111, 112, 52, 75, 75, 108, 65, 98, 98, 111, + 58, 80, 82, 109, 105, 111, 127, 131, 57, 76, 108, 114, 70, 100, 111, + 116, 66, 81, 109, 115, 106, 112, 131, 133, 7, 16, 54, 58, 16, 41, 58, + 69, 20, 27, 68, 70, 58, 66, 105, 106, 9, 18, 77, 78, 18, 43, 78, 97, + 21, 28, 82, 83, 59, 67, 109, 110, 20, 58, 68, 105, 27, 66, 70, 106, + 45, 70, 121, 123, 70, 100, 123, 128, 21, 59, 82, 109, 28, 67, 83, + 110, 46, 71, 122, 124, 71, 101, 124, 130, 14, 18, 55, 59, 41, 49, 66, + 71, 25, 28, 69, 71, 69, 71, 106, 107, 18, 24, 78, 79, 49, 63, 99, + 101, 28, 30, 83, 84, 71, 73, 112, 113, 55, 78, 96, 109, 66, 99, 100, + 112, 69, 83, 122, 124, 106, 112, 128, 132, 59, 79, 109, 115, 71, 101, + 112, 117, 71, 84, 124, 125, 107, 113, 132, 134, 13, 40, 52, 65, 17, + 48, 57, 70, 52, 65, 75, 98, 75, 98, 108, 111, 17, 48, 75, 98, 23, 62, + 76, 100, 57, 70, 108, 111, 76, 100, 114, 116, 20, 68, 58, 105, 27, + 70, 66, 106, 58, 105, 82, 127, 80, 111, 109, 131, 27, 70, 80, 111, + 29, 72, 81, 112, 66, 106, 109, 131, 81, 112, 115, 133, 36, 42, 55, + 66, 42, 62, 66, 72, 55, 66, 78, 99, 96, 100, 109, 112, 42, 62, 96, + 100, 62, 94, 100, 102, 66, 72, 109, 112, 100, 102, 116, 118, 55, 96, + 78, 109, 66, 100, 99, 112, 78, 109, 90, 129, 109, 116, 129, 133, 66, + 100, 109, 116, 72, 102, 112, 118, 99, 112, 129, 133, 112, 118, 133, + 135, 14, 41, 55, 66, 18, 49, 59, 71, 55, 66, 96, 100, 78, 99, 109, + 112, 18, 49, 78, 99, 24, 63, 79, 101, 59, 71, 109, 112, 79, 101, 115, + 117, 25, 69, 69, 106, 28, 71, 71, 107, 69, 106, 122, 128, 83, 112, + 124, 132, 28, 71, 83, 112, 30, 73, 84, 113, 71, 107, 124, 132, 84, + 113, 125, 134, 37, 43, 64, 67, 43, 63, 67, 73, 64, 67, 97, 101, 97, + 101, 110, 113, 43, 63, 97, 101, 63, 95, 101, 103, 67, 73, 110, 113, + 101, 103, 117, 119, 64, 97, 97, 110, 67, 101, 101, 113, 97, 110, 126, + 130, 110, 117, 130, 134, 67, 101, 110, 117, 73, 103, 113, 119, 101, + 113, 130, 134, 113, 119, 134, 136, 4, 7, 13, 16, 13, 16, 17, 21, 13, + 16, 39, 41, 52, 53, 57, 59, 13, 16, 52, 53, 39, 41, 57, 59, 17, 21, + 57, 59, 57, 59, 76, 79, 35, 54, 40, 58, 40, 58, 48, 60, 40, 58, 45, + 69, 65, 80, 70, 83, 40, 58, 65, 80, 45, 69, 70, 83, 48, 60, 70, 83, + 70, 83, 86, 91, 7, 9, 16, 18, 20, 21, 27, 28, 16, 18, 41, 43, 58, 59, + 66, 67, 20, 21, 58, 59, 45, 46, 70, 71, 27, 28, 66, 67, 70, 71, 100, + 101, 54, 77, 58, 78, 68, 82, 70, 83, 58, 78, 69, 97, 105, 109, 106, + 110, 68, 82, 105, 109, 121, 122, 123, 124, 70, 83, 106, 110, 123, + 124, 128, 130, 7, 9, 20, 21, 16, 18, 27, 28, 20, 21, 45, 46, 58, 59, + 70, 71, 16, 18, 58, 59, 41, 43, 66, 67, 27, 28, 70, 71, 66, 67, 100, + 101, 54, 77, 68, 82, 58, 78, 70, 83, 68, 82, 121, 122, 105, 109, 123, + 124, 58, 78, 105, 109, 69, 97, 106, 110, 70, 83, 123, 124, 106, 110, + 128, 130, 9, 10, 21, 22, 21, 22, 29, 30, 21, 22, 46, 47, 60, 61, 72, + 73, 21, 22, 60, 61, 46, 47, 72, 73, 29, 30, 72, 73, 72, 73, 102, 103, + 77, 89, 82, 90, 82, 90, 86, 91, 82, 90, 122, 126, 127, 129, 128, 130, + 82, 90, 127, 129, 122, 126, 128, 130, 86, 91, 128, 130, 128, 130, + 141, 142, 13, 20, 17, 27, 17, 27, 23, 29, 52, 58, 57, 66, 75, 80, 76, + 81, 52, 58, 75, 80, 57, 66, 76, 81, 75, 82, 108, 109, 108, 109, 114, + 115, 40, 68, 48, 70, 48, 70, 62, 72, 65, 105, 70, 106, 98, 111, 100, + 112, 65, 105, 98, 111, 70, 106, 100, 112, 98, 127, 111, 131, 111, + 131, 116, 133, 16, 21, 21, 28, 27, 29, 29, 31, 53, 59, 59, 67, 80, + 81, 81, 85, 58, 60, 82, 83, 70, 72, 86, 87, 80, 83, 109, 110, 111, + 112, 116, 117, 58, 82, 60, 83, 70, 86, 72, 87, 80, 109, 83, 110, 111, + 116, 112, 117, 105, 127, 127, 131, 123, 128, 128, 132, 111, 131, 131, + 143, 144, 145, 145, 146, 16, 21, 27, 29, 21, 28, 29, 31, 58, 60, 70, + 72, 82, 83, 86, 87, 53, 59, 80, 81, 59, 67, 81, 85, 80, 83, 111, 112, + 109, 110, 116, 117, 58, 82, 70, 86, 60, 83, 72, 87, 105, 127, 123, + 128, 127, 131, 128, 132, 80, 109, 111, 116, 83, 110, 112, 117, 111, + 131, 144, 145, 131, 143, 145, 146, 18, 22, 28, 30, 28, 30, 31, 32, + 59, 61, 71, 73, 83, 84, 87, 88, 59, 61, 83, 84, 71, 73, 87, 88, 81, + 84, 112, 113, 112, 113, 118, 119, 78, 90, 83, 91, 83, 91, 87, 92, + 109, 129, 124, 130, 131, 133, 132, 134, 109, 129, 131, 133, 124, 130, + 132, 134, 116, 133, 145, 146, 145, 146, 147, 148, 13, 20, 52, 58, 52, + 58, 75, 82, 17, 27, 57, 66, 75, 80, 108, 109, 17, 27, 75, 80, 57, 66, + 108, 109, 23, 29, 76, 81, 76, 81, 114, 115, 40, 68, 65, 105, 65, 105, + 98, 127, 48, 70, 70, 106, 98, 111, 111, 131, 48, 70, 98, 111, 70, + 106, 111, 131, 62, 72, 100, 112, 100, 112, 116, 133, 16, 21, 53, 59, + 58, 60, 80, 83, 21, 28, 59, 67, 82, 83, 109, 110, 27, 29, 80, 81, 70, + 72, 111, 112, 29, 31, 81, 85, 86, 87, 116, 117, 58, 82, 80, 109, 105, + 127, 111, 131, 60, 83, 83, 110, 127, 131, 131, 143, 70, 86, 111, 116, + 123, 128, 144, 145, 72, 87, 112, 117, 128, 132, 145, 146, 16, 21, 58, + 60, 53, 59, 80, 83, 27, 29, 70, 72, 80, 81, 111, 112, 21, 28, 82, 83, + 59, 67, 109, 110, 29, 31, 86, 87, 81, 85, 116, 117, 58, 82, 105, 127, + 80, 109, 111, 131, 70, 86, 123, 128, 111, 116, 144, 145, 60, 83, 127, + 131, 83, 110, 131, 143, 72, 87, 128, 132, 112, 117, 145, 146, 18, 22, + 59, 61, 59, 61, 81, 84, 28, 30, 71, 73, 83, 84, 112, 113, 28, 30, 83, + 84, 71, 73, 112, 113, 31, 32, 87, 88, 87, 88, 118, 119, 78, 90, 109, + 129, 109, 129, 116, 133, 83, 91, 124, 130, 131, 133, 145, 146, 83, + 91, 131, 133, 124, 130, 145, 146, 87, 92, 132, 134, 132, 134, 147, + 148, 39, 45, 57, 70, 57, 70, 76, 86, 57, 70, 76, 100, 108, 111, 114, + 116, 57, 70, 108, 111, 76, 100, 114, 116, 76, 86, 114, 116, 114, 116, + 137, 138, 45, 121, 70, 123, 70, 123, 100, 128, 70, 123, 86, 128, 111, + 144, 116, 145, 70, 123, 111, 144, 86, 128, 116, 145, 100, 128, 116, + 145, 116, 145, 138, 149, 41, 46, 59, 71, 66, 72, 81, 87, 59, 71, 79, + 101, 109, 112, 115, 117, 66, 72, 109, 112, 100, 102, 116, 118, 81, + 87, 115, 117, 116, 118, 138, 139, 69, 122, 83, 124, 106, 128, 112, + 132, 83, 124, 91, 130, 131, 145, 133, 146, 106, 128, 131, 145, 128, + 141, 145, 147, 112, 132, 133, 146, 145, 147, 149, 150, 41, 46, 66, + 72, 59, 71, 81, 87, 66, 72, 100, 102, 109, 112, 116, 118, 59, 71, + 109, 112, 79, 101, 115, 117, 81, 87, 116, 118, 115, 117, 138, 139, + 69, 122, 106, 128, 83, 124, 112, 132, 106, 128, 128, 141, 131, 145, + 145, 147, 83, 124, 131, 145, 91, 130, 133, 146, 112, 132, 145, 147, + 133, 146, 149, 150, 43, 47, 67, 73, 67, 73, 85, 88, 67, 73, 101, 103, + 110, 113, 117, 119, 67, 73, 110, 113, 101, 103, 117, 119, 85, 88, + 117, 119, 117, 119, 139, 140, 97, 126, 110, 130, 110, 130, 117, 134, + 110, 130, 130, 142, 143, 146, 146, 148, 110, 130, 143, 146, 130, 142, + 146, 148, 117, 134, 146, 148, 146, 148, 150, 151, 2, 12, 6, 15, 6, + 15, 13, 20, 12, 38, 15, 40, 51, 56, 52, 58, 12, 38, 51, 56, 15, 40, + 52, 58, 38, 44, 56, 68, 56, 68, 75, 82, 6, 51, 13, 52, 13, 52, 36, + 55, 15, 56, 20, 58, 52, 75, 55, 78, 15, 56, 52, 75, 20, 58, 55, 78, + 40, 68, 58, 82, 58, 82, 78, 90, 6, 15, 8, 17, 15, 26, 17, 27, 15, 40, + 17, 42, 56, 65, 57, 66, 51, 56, 74, 75, 56, 65, 75, 80, 56, 68, 75, + 96, 104, 105, 108, 109, 15, 56, 17, 57, 40, 65, 42, 66, 26, 65, 27, + 66, 65, 98, 66, 99, 56, 104, 75, 108, 68, 105, 96, 109, 65, 105, 80, + 109, 105, 127, 109, 129, 6, 15, 15, 26, 8, 17, 17, 27, 51, 56, 56, + 65, 74, 75, 75, 80, 15, 40, 56, 65, 17, 42, 57, 66, 56, 68, 104, 105, + 75, 96, 108, 109, 15, 56, 40, 65, 17, 57, 42, 66, 56, 104, 68, 105, + 75, 108, 96, 109, 26, 65, 65, 98, 27, 66, 66, 99, 65, 105, 105, 127, + 80, 109, 109, 129, 13, 20, 17, 27, 17, 27, 23, 29, 52, 58, 57, 66, + 75, 80, 76, 81, 52, 58, 75, 80, 57, 66, 76, 81, 75, 82, 108, 109, + 108, 109, 114, 115, 40, 68, 48, 70, 48, 70, 62, 72, 65, 105, 70, 106, + 98, 111, 100, 112, 65, 105, 98, 111, 70, 106, 100, 112, 98, 127, 111, + 131, 111, 131, 116, 133, 3, 19, 7, 20, 7, 20, 14, 25, 19, 44, 20, 45, + 54, 68, 55, 69, 19, 44, 54, 68, 20, 45, 55, 69, 44, 120, 68, 121, 68, + 121, 96, 122, 7, 54, 14, 55, 14, 55, 37, 64, 20, 68, 25, 69, 55, 96, + 64, 97, 20, 68, 55, 96, 25, 69, 64, 97, 45, 121, 69, 122, 69, 122, + 97, 126, 7, 20, 9, 21, 16, 27, 18, 28, 20, 45, 21, 46, 58, 70, 59, + 71, 54, 68, 77, 82, 58, 70, 78, 83, 68, 121, 82, 122, 105, 123, 109, + 124, 16, 58, 18, 59, 41, 66, 43, 67, 27, 70, 28, 71, 66, 100, 67, + 101, 58, 105, 78, 109, 69, 106, 97, 110, 70, 123, 83, 124, 106, 128, + 110, 130, 7, 20, 16, 27, 9, 21, 18, 28, 54, 68, 58, 70, 77, 82, 78, + 83, 20, 45, 58, 70, 21, 46, 59, 71, 68, 121, 105, 123, 82, 122, 109, + 124, 16, 58, 41, 66, 18, 59, 43, 67, 58, 105, 69, 106, 78, 109, 97, + 110, 27, 70, 66, 100, 28, 71, 67, 101, 70, 123, 106, 128, 83, 124, + 110, 130, 14, 25, 18, 28, 18, 28, 24, 30, 55, 69, 59, 71, 78, 83, 79, + 84, 55, 69, 78, 83, 59, 71, 79, 84, 96, 122, 109, 124, 109, 124, 115, + 125, 41, 69, 49, 71, 49, 71, 63, 73, 66, 106, 71, 107, 99, 112, 101, + 113, 66, 106, 99, 112, 71, 107, 101, 113, 100, 128, 112, 132, 112, + 132, 117, 134, 6, 51, 15, 56, 15, 56, 40, 68, 15, 56, 26, 65, 56, + 104, 65, 105, 15, 56, 56, 104, 26, 65, 65, 105, 40, 68, 65, 105, 65, + 105, 98, 127, 8, 74, 17, 75, 17, 75, 42, 96, 17, 75, 27, 80, 57, 108, + 66, 109, 17, 75, 57, 108, 27, 80, 66, 109, 42, 96, 66, 109, 66, 109, + 99, 129, 13, 52, 17, 57, 40, 65, 48, 70, 20, 58, 27, 66, 68, 105, 70, + 106, 52, 75, 75, 108, 65, 98, 98, 111, 58, 82, 80, 109, 105, 127, + 111, 131, 17, 75, 23, 76, 48, 98, 62, 100, 27, 80, 29, 81, 70, 111, + 72, 112, 57, 108, 76, 114, 70, 111, 100, 116, 66, 109, 81, 115, 106, + 131, 112, 133, 13, 52, 40, 65, 17, 57, 48, 70, 52, 75, 65, 98, 75, + 108, 98, 111, 20, 58, 68, 105, 27, 66, 70, 106, 58, 82, 105, 127, 80, + 109, 111, 131, 17, 75, 48, 98, 23, 76, 62, 100, 57, 108, 70, 111, 76, + 114, 100, 116, 27, 80, 70, 111, 29, 81, 72, 112, 66, 109, 106, 131, + 81, 115, 112, 133, 36, 55, 42, 66, 42, 66, 62, 72, 55, 78, 66, 99, + 96, 109, 100, 112, 55, 78, 96, 109, 66, 99, 100, 112, 78, 90, 109, + 129, 109, 129, 116, 133, 42, 96, 62, 100, 62, 100, 94, 102, 66, 109, + 72, 112, 100, 116, 102, 118, 66, 109, 100, 116, 72, 112, 102, 118, + 99, 129, 112, 133, 112, 133, 118, 135, 7, 54, 16, 58, 16, 58, 41, 69, + 20, 68, 27, 70, 58, 105, 66, 106, 20, 68, 58, 105, 27, 70, 66, 106, + 45, 121, 70, 123, 70, 123, 100, 128, 9, 77, 18, 78, 18, 78, 43, 97, + 21, 82, 28, 83, 59, 109, 67, 110, 21, 82, 59, 109, 28, 83, 67, 110, + 46, 122, 71, 124, 71, 124, 101, 130, 14, 55, 18, 59, 41, 66, 49, 71, + 25, 69, 28, 71, 69, 106, 71, 107, 55, 96, 78, 109, 66, 100, 99, 112, + 69, 122, 83, 124, 106, 128, 112, 132, 18, 78, 24, 79, 49, 99, 63, + 101, 28, 83, 30, 84, 71, 112, 73, 113, 59, 109, 79, 115, 71, 112, + 101, 117, 71, 124, 84, 125, 107, 132, 113, 134, 14, 55, 41, 66, 18, + 59, 49, 71, 55, 96, 66, 100, 78, 109, 99, 112, 25, 69, 69, 106, 28, + 71, 71, 107, 69, 122, 106, 128, 83, 124, 112, 132, 18, 78, 49, 99, + 24, 79, 63, 101, 59, 109, 71, 112, 79, 115, 101, 117, 28, 83, 71, + 112, 30, 84, 73, 113, 71, 124, 107, 132, 84, 125, 113, 134, 37, 64, + 43, 67, 43, 67, 63, 73, 64, 97, 67, 101, 97, 110, 101, 113, 64, 97, + 97, 110, 67, 101, 101, 113, 97, 126, 110, 130, 110, 130, 117, 134, + 43, 97, 63, 101, 63, 101, 95, 103, 67, 110, 73, 113, 101, 117, 103, + 119, 67, 110, 101, 117, 73, 113, 103, 119, 101, 130, 113, 134, 113, + 134, 119, 136, 4, 13, 7, 16, 13, 17, 16, 21, 13, 39, 16, 41, 52, 57, + 53, 59, 35, 40, 54, 58, 40, 48, 58, 60, 40, 45, 58, 69, 65, 70, 80, + 83, 13, 52, 16, 53, 39, 57, 41, 59, 17, 57, 21, 59, 57, 76, 59, 79, + 40, 65, 58, 80, 45, 70, 69, 83, 48, 70, 60, 83, 70, 86, 83, 91, 7, + 16, 9, 18, 20, 27, 21, 28, 16, 41, 18, 43, 58, 66, 59, 67, 54, 58, + 77, 78, 68, 70, 82, 83, 58, 69, 78, 97, 105, 106, 109, 110, 20, 58, + 21, 59, 45, 70, 46, 71, 27, 66, 28, 67, 70, 100, 71, 101, 68, 105, + 82, 109, 121, 123, 122, 124, 70, 106, 83, 110, 123, 128, 124, 130, + 13, 17, 20, 27, 17, 23, 27, 29, 52, 57, 58, 66, 75, 76, 80, 81, 40, + 48, 68, 70, 48, 62, 70, 72, 65, 70, 105, 106, 98, 100, 111, 112, 52, + 75, 58, 80, 57, 76, 66, 81, 75, 108, 82, 109, 108, 114, 109, 115, 65, + 98, 105, 111, 70, 100, 106, 112, 98, 111, 127, 131, 111, 116, 131, + 133, 16, 21, 21, 28, 27, 29, 29, 31, 53, 59, 59, 67, 80, 81, 81, 85, + 58, 60, 82, 83, 70, 72, 86, 87, 80, 83, 109, 110, 111, 112, 116, 117, + 58, 82, 60, 83, 70, 86, 72, 87, 80, 109, 83, 110, 111, 116, 112, 117, + 105, 127, 127, 131, 123, 128, 128, 132, 111, 131, 131, 143, 144, 145, + 145, 146, 7, 20, 9, 21, 16, 27, 18, 28, 20, 45, 21, 46, 58, 70, 59, + 71, 54, 68, 77, 82, 58, 70, 78, 83, 68, 121, 82, 122, 105, 123, 109, + 124, 16, 58, 18, 59, 41, 66, 43, 67, 27, 70, 28, 71, 66, 100, 67, + 101, 58, 105, 78, 109, 69, 106, 97, 110, 70, 123, 83, 124, 106, 128, + 110, 130, 9, 21, 10, 22, 21, 29, 22, 30, 21, 46, 22, 47, 60, 72, 61, + 73, 77, 82, 89, 90, 82, 86, 90, 91, 82, 122, 90, 126, 127, 128, 129, + 130, 21, 60, 22, 61, 46, 72, 47, 73, 29, 72, 30, 73, 72, 102, 73, + 103, 82, 127, 90, 129, 122, 128, 126, 130, 86, 128, 91, 130, 128, + 141, 130, 142, 16, 27, 21, 29, 21, 29, 28, 31, 58, 70, 60, 72, 82, + 86, 83, 87, 58, 70, 82, 86, 60, 72, 83, 87, 105, 123, 127, 128, 127, + 128, 131, 132, 53, 80, 59, 81, 59, 81, 67, 85, 80, 111, 83, 112, 109, + 116, 110, 117, 80, 111, 109, 116, 83, 112, 110, 117, 111, 144, 131, + 145, 131, 145, 143, 146, 18, 28, 22, 30, 28, 31, 30, 32, 59, 71, 61, + 73, 83, 87, 84, 88, 78, 83, 90, 91, 83, 87, 91, 92, 109, 124, 129, + 130, 131, 132, 133, 134, 59, 83, 61, 84, 71, 87, 73, 88, 81, 112, 84, + 113, 112, 118, 113, 119, 109, 131, 129, 133, 124, 132, 130, 134, 116, + 145, 133, 146, 145, 147, 146, 148, 13, 52, 20, 58, 52, 75, 58, 82, + 17, 57, 27, 66, 75, 108, 80, 109, 40, 65, 68, 105, 65, 98, 105, 127, + 48, 70, 70, 106, 98, 111, 111, 131, 17, 75, 27, 80, 57, 108, 66, 109, + 23, 76, 29, 81, 76, 114, 81, 115, 48, 98, 70, 111, 70, 111, 106, 131, + 62, 100, 72, 112, 100, 116, 112, 133, 16, 53, 21, 59, 58, 80, 60, 83, + 21, 59, 28, 67, 82, 109, 83, 110, 58, 80, 82, 109, 105, 111, 127, + 131, 60, 83, 83, 110, 127, 131, 131, 143, 27, 80, 29, 81, 70, 111, + 72, 112, 29, 81, 31, 85, 86, 116, 87, 117, 70, 111, 86, 116, 123, + 144, 128, 145, 72, 112, 87, 117, 128, 145, 132, 146, 39, 57, 45, 70, + 57, 76, 70, 86, 57, 76, 70, 100, 108, 114, 111, 116, 45, 70, 121, + 123, 70, 100, 123, 128, 70, 86, 123, 128, 111, 116, 144, 145, 57, + 108, 70, 111, 76, 114, 100, 116, 76, 114, 86, 116, 114, 137, 116, + 138, 70, 111, 123, 144, 86, 116, 128, 145, 100, 116, 128, 145, 116, + 138, 145, 149, 41, 59, 46, 71, 66, 81, 72, 87, 59, 79, 71, 101, 109, + 115, 112, 117, 69, 83, 122, 124, 106, 112, 128, 132, 83, 91, 124, + 130, 131, 133, 145, 146, 66, 109, 72, 112, 100, 116, 102, 118, 81, + 115, 87, 117, 116, 138, 118, 139, 106, 131, 128, 145, 128, 145, 141, + 147, 112, 133, 132, 146, 145, 149, 147, 150, 16, 58, 21, 60, 53, 80, + 59, 83, 27, 70, 29, 72, 80, 111, 81, 112, 58, 105, 82, 127, 80, 111, + 109, 131, 70, 123, 86, 128, 111, 144, 116, 145, 21, 82, 28, 83, 59, + 109, 67, 110, 29, 86, 31, 87, 81, 116, 85, 117, 60, 127, 83, 131, 83, + 131, 110, 143, 72, 128, 87, 132, 112, 145, 117, 146, 18, 59, 22, 61, + 59, 81, 61, 84, 28, 71, 30, 73, 83, 112, 84, 113, 78, 109, 90, 129, + 109, 116, 129, 133, 83, 124, 91, 130, 131, 145, 133, 146, 28, 83, 30, + 84, 71, 112, 73, 113, 31, 87, 32, 88, 87, 118, 88, 119, 83, 131, 91, + 133, 124, 145, 130, 146, 87, 132, 92, 134, 132, 147, 134, 148, 41, + 66, 46, 72, 59, 81, 71, 87, 66, 100, 72, 102, 109, 116, 112, 118, 69, + 106, 122, 128, 83, 112, 124, 132, 106, 128, 128, 141, 131, 145, 145, + 147, 59, 109, 71, 112, 79, 115, 101, 117, 81, 116, 87, 118, 115, 138, + 117, 139, 83, 131, 124, 145, 91, 133, 130, 146, 112, 145, 132, 147, + 133, 149, 146, 150, 43, 67, 47, 73, 67, 85, 73, 88, 67, 101, 73, 103, + 110, 117, 113, 119, 97, 110, 126, 130, 110, 117, 130, 134, 110, 130, + 130, 142, 143, 146, 146, 148, 67, 110, 73, 113, 101, 117, 103, 119, + 85, 117, 88, 119, 117, 139, 119, 140, 110, 143, 130, 146, 130, 146, + 142, 148, 117, 146, 134, 148, 146, 150, 148, 151, 4, 13, 13, 17, 7, + 16, 16, 21, 35, 40, 40, 48, 54, 58, 58, 60, 13, 39, 52, 57, 16, 41, + 53, 59, 40, 45, 65, 70, 58, 69, 80, 83, 13, 52, 39, 57, 16, 53, 41, + 59, 40, 65, 45, 70, 58, 80, 69, 83, 17, 57, 57, 76, 21, 59, 59, 79, + 48, 70, 70, 86, 60, 83, 83, 91, 13, 17, 17, 23, 20, 27, 27, 29, 40, + 48, 48, 62, 68, 70, 70, 72, 52, 57, 75, 76, 58, 66, 80, 81, 65, 70, + 98, 100, 105, 106, 111, 112, 52, 75, 57, 76, 58, 80, 66, 81, 65, 98, + 70, 100, 105, 111, 106, 112, 75, 108, 108, 114, 82, 109, 109, 115, + 98, 111, 111, 116, 127, 131, 131, 133, 7, 16, 20, 27, 9, 18, 21, 28, + 54, 58, 68, 70, 77, 78, 82, 83, 16, 41, 58, 66, 18, 43, 59, 67, 58, + 69, 105, 106, 78, 97, 109, 110, 20, 58, 45, 70, 21, 59, 46, 71, 68, + 105, 121, 123, 82, 109, 122, 124, 27, 66, 70, 100, 28, 67, 71, 101, + 70, 106, 123, 128, 83, 110, 124, 130, 16, 21, 27, 29, 21, 28, 29, 31, + 58, 60, 70, 72, 82, 83, 86, 87, 53, 59, 80, 81, 59, 67, 81, 85, 80, + 83, 111, 112, 109, 110, 116, 117, 58, 82, 70, 86, 60, 83, 72, 87, + 105, 127, 123, 128, 127, 131, 128, 132, 80, 109, 111, 116, 83, 110, + 112, 117, 111, 131, 144, 145, 131, 143, 145, 146, 7, 20, 16, 27, 9, + 21, 18, 28, 54, 68, 58, 70, 77, 82, 78, 83, 20, 45, 58, 70, 21, 46, + 59, 71, 68, 121, 105, 123, 82, 122, 109, 124, 16, 58, 41, 66, 18, 59, + 43, 67, 58, 105, 69, 106, 78, 109, 97, 110, 27, 70, 66, 100, 28, 71, + 67, 101, 70, 123, 106, 128, 83, 124, 110, 130, 16, 27, 21, 29, 21, + 29, 28, 31, 58, 70, 60, 72, 82, 86, 83, 87, 58, 70, 82, 86, 60, 72, + 83, 87, 105, 123, 127, 128, 127, 128, 131, 132, 53, 80, 59, 81, 59, + 81, 67, 85, 80, 111, 83, 112, 109, 116, 110, 117, 80, 111, 109, 116, + 83, 112, 110, 117, 111, 144, 131, 145, 131, 145, 143, 146, 9, 21, 21, + 29, 10, 22, 22, 30, 77, 82, 82, 86, 89, 90, 90, 91, 21, 46, 60, 72, + 22, 47, 61, 73, 82, 122, 127, 128, 90, 126, 129, 130, 21, 60, 46, 72, + 22, 61, 47, 73, 82, 127, 122, 128, 90, 129, 126, 130, 29, 72, 72, + 102, 30, 73, 73, 103, 86, 128, 128, 141, 91, 130, 130, 142, 18, 28, + 28, 31, 22, 30, 30, 32, 78, 83, 83, 87, 90, 91, 91, 92, 59, 71, 83, + 87, 61, 73, 84, 88, 109, 124, 131, 132, 129, 130, 133, 134, 59, 83, + 71, 87, 61, 84, 73, 88, 109, 131, 124, 132, 129, 133, 130, 134, 81, + 112, 112, 118, 84, 113, 113, 119, 116, 145, 145, 147, 133, 146, 146, + 148, 13, 52, 52, 75, 20, 58, 58, 82, 40, 65, 65, 98, 68, 105, 105, + 127, 17, 57, 75, 108, 27, 66, 80, 109, 48, 70, 98, 111, 70, 106, 111, + 131, 17, 75, 57, 108, 27, 80, 66, 109, 48, 98, 70, 111, 70, 111, 106, + 131, 23, 76, 76, 114, 29, 81, 81, 115, 62, 100, 100, 116, 72, 112, + 112, 133, 39, 57, 57, 76, 45, 70, 70, 86, 45, 70, 70, 100, 121, 123, + 123, 128, 57, 76, 108, 114, 70, 100, 111, 116, 70, 86, 111, 116, 123, + 128, 144, 145, 57, 108, 76, 114, 70, 111, 100, 116, 70, 111, 86, 116, + 123, 144, 128, 145, 76, 114, 114, 137, 86, 116, 116, 138, 100, 116, + 116, 138, 128, 145, 145, 149, 16, 53, 58, 80, 21, 59, 60, 83, 58, 80, + 105, 111, 82, 109, 127, 131, 21, 59, 82, 109, 28, 67, 83, 110, 60, + 83, 127, 131, 83, 110, 131, 143, 27, 80, 70, 111, 29, 81, 72, 112, + 70, 111, 123, 144, 86, 116, 128, 145, 29, 81, 86, 116, 31, 85, 87, + 117, 72, 112, 128, 145, 87, 117, 132, 146, 41, 59, 66, 81, 46, 71, + 72, 87, 69, 83, 106, 112, 122, 124, 128, 132, 59, 79, 109, 115, 71, + 101, 112, 117, 83, 91, 131, 133, 124, 130, 145, 146, 66, 109, 100, + 116, 72, 112, 102, 118, 106, 131, 128, 145, 128, 145, 141, 147, 81, + 115, 116, 138, 87, 117, 118, 139, 112, 133, 145, 149, 132, 146, 147, + 150, 16, 58, 53, 80, 21, 60, 59, 83, 58, 105, 80, 111, 82, 127, 109, + 131, 27, 70, 80, 111, 29, 72, 81, 112, 70, 123, 111, 144, 86, 128, + 116, 145, 21, 82, 59, 109, 28, 83, 67, 110, 60, 127, 83, 131, 83, + 131, 110, 143, 29, 86, 81, 116, 31, 87, 85, 117, 72, 128, 112, 145, + 87, 132, 117, 146, 41, 66, 59, 81, 46, 72, 71, 87, 69, 106, 83, 112, + 122, 128, 124, 132, 66, 100, 109, 116, 72, 102, 112, 118, 106, 128, + 131, 145, 128, 141, 145, 147, 59, 109, 79, 115, 71, 112, 101, 117, + 83, 131, 91, 133, 124, 145, 130, 146, 81, 116, 115, 138, 87, 118, + 117, 139, 112, 145, 133, 149, 132, 147, 146, 150, 18, 59, 59, 81, 22, + 61, 61, 84, 78, 109, 109, 116, 90, 129, 129, 133, 28, 71, 83, 112, + 30, 73, 84, 113, 83, 124, 131, 145, 91, 130, 133, 146, 28, 83, 71, + 112, 30, 84, 73, 113, 83, 131, 124, 145, 91, 133, 130, 146, 31, 87, + 87, 118, 32, 88, 88, 119, 87, 132, 132, 147, 92, 134, 134, 148, 43, + 67, 67, 85, 47, 73, 73, 88, 97, 110, 110, 117, 126, 130, 130, 134, + 67, 101, 110, 117, 73, 103, 113, 119, 110, 130, 143, 146, 130, 142, + 146, 148, 67, 110, 101, 117, 73, 113, 103, 119, 110, 143, 130, 146, + 130, 146, 142, 148, 85, 117, 117, 139, 88, 119, 119, 140, 117, 146, + 146, 150, 134, 148, 148, 151, 11, 14, 14, 18, 14, 18, 18, 22, 36, 41, + 41, 49, 55, 59, 59, 61, 36, 41, 55, 59, 41, 49, 59, 61, 42, 46, 66, + 71, 66, 71, 81, 84, 36, 55, 41, 59, 41, 59, 49, 61, 42, 66, 46, 71, + 66, 81, 71, 84, 42, 66, 66, 81, 46, 71, 71, 84, 62, 72, 72, 87, 72, + 87, 87, 92, 14, 18, 18, 24, 25, 28, 28, 30, 41, 49, 49, 63, 69, 71, + 71, 73, 55, 59, 78, 79, 69, 71, 83, 84, 66, 71, 99, 101, 106, 107, + 112, 113, 55, 78, 59, 79, 69, 83, 71, 84, 66, 99, 71, 101, 106, 112, + 107, 113, 96, 109, 109, 115, 122, 124, 124, 125, 100, 112, 112, 117, + 128, 132, 132, 134, 14, 18, 25, 28, 18, 24, 28, 30, 55, 59, 69, 71, + 78, 79, 83, 84, 41, 49, 69, 71, 49, 63, 71, 73, 66, 71, 106, 107, 99, + 101, 112, 113, 55, 78, 69, 83, 59, 79, 71, 84, 96, 109, 122, 124, + 109, 115, 124, 125, 66, 99, 106, 112, 71, 101, 107, 113, 100, 112, + 128, 132, 112, 117, 132, 134, 18, 22, 28, 30, 28, 30, 31, 32, 59, 61, + 71, 73, 83, 84, 87, 88, 59, 61, 83, 84, 71, 73, 87, 88, 81, 84, 112, + 113, 112, 113, 118, 119, 78, 90, 83, 91, 83, 91, 87, 92, 109, 129, + 124, 130, 131, 133, 132, 134, 109, 129, 131, 133, 124, 130, 132, 134, + 116, 133, 145, 146, 145, 146, 147, 148, 14, 25, 18, 28, 18, 28, 24, + 30, 55, 69, 59, 71, 78, 83, 79, 84, 55, 69, 78, 83, 59, 71, 79, 84, + 96, 122, 109, 124, 109, 124, 115, 125, 41, 69, 49, 71, 49, 71, 63, + 73, 66, 106, 71, 107, 99, 112, 101, 113, 66, 106, 99, 112, 71, 107, + 101, 113, 100, 128, 112, 132, 112, 132, 117, 134, 18, 28, 22, 30, 28, + 31, 30, 32, 59, 71, 61, 73, 83, 87, 84, 88, 78, 83, 90, 91, 83, 87, + 91, 92, 109, 124, 129, 130, 131, 132, 133, 134, 59, 83, 61, 84, 71, + 87, 73, 88, 81, 112, 84, 113, 112, 118, 113, 119, 109, 131, 129, 133, + 124, 132, 130, 134, 116, 145, 133, 146, 145, 147, 146, 148, 18, 28, + 28, 31, 22, 30, 30, 32, 78, 83, 83, 87, 90, 91, 91, 92, 59, 71, 83, + 87, 61, 73, 84, 88, 109, 124, 131, 132, 129, 130, 133, 134, 59, 83, + 71, 87, 61, 84, 73, 88, 109, 131, 124, 132, 129, 133, 130, 134, 81, + 112, 112, 118, 84, 113, 113, 119, 116, 145, 145, 147, 133, 146, 146, + 148, 24, 30, 30, 32, 30, 32, 32, 33, 79, 84, 84, 88, 91, 92, 92, 93, + 79, 84, 91, 92, 84, 88, 92, 93, 115, 125, 133, 134, 133, 134, 135, + 136, 79, 91, 84, 92, 84, 92, 88, 93, 115, 133, 125, 134, 133, 135, + 134, 136, 115, 133, 133, 135, 125, 134, 134, 136, 138, 149, 149, 150, + 149, 150, 150, 151, 36, 55, 55, 78, 55, 78, 78, 90, 42, 66, 66, 99, + 96, 109, 109, 129, 42, 66, 96, 109, 66, 99, 109, 129, 62, 72, 100, + 112, 100, 112, 116, 133, 42, 96, 66, 109, 66, 109, 99, 129, 62, 100, + 72, 112, 100, 116, 112, 133, 62, 100, 100, 116, 72, 112, 112, 133, + 94, 102, 102, 118, 102, 118, 118, 135, 41, 59, 59, 79, 69, 83, 83, + 91, 46, 71, 71, 101, 122, 124, 124, 130, 66, 81, 109, 115, 106, 112, + 131, 133, 72, 87, 112, 117, 128, 132, 145, 146, 66, 109, 81, 115, + 106, 131, 112, 133, 72, 112, 87, 117, 128, 145, 132, 146, 100, 116, + 116, 138, 128, 145, 145, 149, 102, 118, 118, 139, 141, 147, 147, 150, + 41, 59, 69, 83, 59, 79, 83, 91, 66, 81, 106, 112, 109, 115, 131, 133, + 46, 71, 122, 124, 71, 101, 124, 130, 72, 87, 128, 132, 112, 117, 145, + 146, 66, 109, 106, 131, 81, 115, 112, 133, 100, 116, 128, 145, 116, + 138, 145, 149, 72, 112, 128, 145, 87, 117, 132, 146, 102, 118, 141, + 147, 118, 139, 147, 150, 49, 61, 71, 84, 71, 84, 87, 92, 71, 84, 107, + 113, 124, 125, 132, 134, 71, 84, 124, 125, 107, 113, 132, 134, 87, + 92, 132, 134, 132, 134, 147, 148, 99, 129, 112, 133, 112, 133, 118, + 135, 112, 133, 132, 146, 145, 149, 147, 150, 112, 133, 145, 149, 132, + 146, 147, 150, 118, 135, 147, 150, 147, 150, 152, 153, 41, 69, 59, + 83, 59, 83, 79, 91, 66, 106, 81, 112, 109, 131, 115, 133, 66, 106, + 109, 131, 81, 112, 115, 133, 100, 128, 116, 145, 116, 145, 138, 149, + 46, 122, 71, 124, 71, 124, 101, 130, 72, 128, 87, 132, 112, 145, 117, + 146, 72, 128, 112, 145, 87, 132, 117, 146, 102, 141, 118, 147, 118, + 147, 139, 150, 49, 71, 61, 84, 71, 87, 84, 92, 71, 107, 84, 113, 124, + 132, 125, 134, 99, 112, 129, 133, 112, 118, 133, 135, 112, 132, 133, + 146, 145, 147, 149, 150, 71, 124, 84, 125, 107, 132, 113, 134, 87, + 132, 92, 134, 132, 147, 134, 148, 112, 145, 133, 149, 132, 147, 146, + 150, 118, 147, 135, 150, 147, 152, 150, 153, 49, 71, 71, 87, 61, 84, + 84, 92, 99, 112, 112, 118, 129, 133, 133, 135, 71, 107, 124, 132, 84, + 113, 125, 134, 112, 132, 145, 147, 133, 146, 149, 150, 71, 124, 107, + 132, 84, 125, 113, 134, 112, 145, 132, 147, 133, 149, 146, 150, 87, + 132, 132, 147, 92, 134, 134, 148, 118, 147, 147, 152, 135, 150, 150, + 153, 63, 73, 73, 88, 73, 88, 88, 93, 101, 113, 113, 119, 130, 134, + 134, 136, 101, 113, 130, 134, 113, 119, 134, 136, 117, 134, 146, 148, + 146, 148, 150, 151, 101, 130, 113, 134, 113, 134, 119, 136, 117, 146, + 134, 148, 146, 150, 148, 151, 117, 146, 146, 150, 134, 148, 148, 151, + 139, 150, 150, 153, 150, 153, 153, 154, 2, 12, 12, 38, 12, 38, 38, + 44, 6, 15, 15, 40, 51, 56, 56, 68, 6, 15, 51, 56, 15, 40, 56, 68, 13, + 20, 52, 58, 52, 58, 75, 82, 6, 51, 15, 56, 15, 56, 40, 68, 13, 52, + 20, 58, 52, 75, 58, 82, 13, 52, 52, 75, 20, 58, 58, 82, 36, 55, 55, + 78, 55, 78, 78, 90, 6, 15, 15, 40, 51, 56, 56, 68, 8, 17, 17, 42, 74, + 75, 75, 96, 15, 26, 56, 65, 56, 65, 104, 105, 17, 27, 57, 66, 75, 80, + 108, 109, 15, 56, 26, 65, 56, 104, 65, 105, 17, 57, 27, 66, 75, 108, + 80, 109, 40, 65, 65, 98, 68, 105, 105, 127, 42, 66, 66, 99, 96, 109, + 109, 129, 6, 15, 51, 56, 15, 40, 56, 68, 15, 26, 56, 65, 56, 65, 104, + 105, 8, 17, 74, 75, 17, 42, 75, 96, 17, 27, 75, 80, 57, 66, 108, 109, + 15, 56, 56, 104, 26, 65, 65, 105, 40, 65, 68, 105, 65, 98, 105, 127, + 17, 57, 75, 108, 27, 66, 80, 109, 42, 66, 96, 109, 66, 99, 109, 129, + 13, 20, 52, 58, 52, 58, 75, 82, 17, 27, 57, 66, 75, 80, 108, 109, 17, + 27, 75, 80, 57, 66, 108, 109, 23, 29, 76, 81, 76, 81, 114, 115, 40, + 68, 65, 105, 65, 105, 98, 127, 48, 70, 70, 106, 98, 111, 111, 131, + 48, 70, 98, 111, 70, 106, 111, 131, 62, 72, 100, 112, 100, 112, 116, + 133, 6, 51, 15, 56, 15, 56, 40, 68, 15, 56, 26, 65, 56, 104, 65, 105, + 15, 56, 56, 104, 26, 65, 65, 105, 40, 68, 65, 105, 65, 105, 98, 127, + 8, 74, 17, 75, 17, 75, 42, 96, 17, 75, 27, 80, 57, 108, 66, 109, 17, + 75, 57, 108, 27, 80, 66, 109, 42, 96, 66, 109, 66, 109, 99, 129, 13, + 52, 20, 58, 52, 75, 58, 82, 17, 57, 27, 66, 75, 108, 80, 109, 40, 65, + 68, 105, 65, 98, 105, 127, 48, 70, 70, 106, 98, 111, 111, 131, 17, + 75, 27, 80, 57, 108, 66, 109, 23, 76, 29, 81, 76, 114, 81, 115, 48, + 98, 70, 111, 70, 111, 106, 131, 62, 100, 72, 112, 100, 116, 112, 133, + 13, 52, 52, 75, 20, 58, 58, 82, 40, 65, 65, 98, 68, 105, 105, 127, + 17, 57, 75, 108, 27, 66, 80, 109, 48, 70, 98, 111, 70, 106, 111, 131, + 17, 75, 57, 108, 27, 80, 66, 109, 48, 98, 70, 111, 70, 111, 106, 131, + 23, 76, 76, 114, 29, 81, 81, 115, 62, 100, 100, 116, 72, 112, 112, + 133, 36, 55, 55, 78, 55, 78, 78, 90, 42, 66, 66, 99, 96, 109, 109, + 129, 42, 66, 96, 109, 66, 99, 109, 129, 62, 72, 100, 112, 100, 112, + 116, 133, 42, 96, 66, 109, 66, 109, 99, 129, 62, 100, 72, 112, 100, + 116, 112, 133, 62, 100, 100, 116, 72, 112, 112, 133, 94, 102, 102, + 118, 102, 118, 118, 135, 3, 19, 19, 44, 19, 44, 44, 120, 7, 20, 20, + 45, 54, 68, 68, 121, 7, 20, 54, 68, 20, 45, 68, 121, 14, 25, 55, 69, + 55, 69, 96, 122, 7, 54, 20, 68, 20, 68, 45, 121, 14, 55, 25, 69, 55, + 96, 69, 122, 14, 55, 55, 96, 25, 69, 69, 122, 37, 64, 64, 97, 64, 97, + 97, 126, 7, 20, 20, 45, 54, 68, 68, 121, 9, 21, 21, 46, 77, 82, 82, + 122, 16, 27, 58, 70, 58, 70, 105, 123, 18, 28, 59, 71, 78, 83, 109, + 124, 16, 58, 27, 70, 58, 105, 70, 123, 18, 59, 28, 71, 78, 109, 83, + 124, 41, 66, 66, 100, 69, 106, 106, 128, 43, 67, 67, 101, 97, 110, + 110, 130, 7, 20, 54, 68, 20, 45, 68, 121, 16, 27, 58, 70, 58, 70, + 105, 123, 9, 21, 77, 82, 21, 46, 82, 122, 18, 28, 78, 83, 59, 71, + 109, 124, 16, 58, 58, 105, 27, 70, 70, 123, 41, 66, 69, 106, 66, 100, + 106, 128, 18, 59, 78, 109, 28, 71, 83, 124, 43, 67, 97, 110, 67, 101, + 110, 130, 14, 25, 55, 69, 55, 69, 96, 122, 18, 28, 59, 71, 78, 83, + 109, 124, 18, 28, 78, 83, 59, 71, 109, 124, 24, 30, 79, 84, 79, 84, + 115, 125, 41, 69, 66, 106, 66, 106, 100, 128, 49, 71, 71, 107, 99, + 112, 112, 132, 49, 71, 99, 112, 71, 107, 112, 132, 63, 73, 101, 113, + 101, 113, 117, 134, 7, 54, 20, 68, 20, 68, 45, 121, 16, 58, 27, 70, + 58, 105, 70, 123, 16, 58, 58, 105, 27, 70, 70, 123, 41, 69, 66, 106, + 66, 106, 100, 128, 9, 77, 21, 82, 21, 82, 46, 122, 18, 78, 28, 83, + 59, 109, 71, 124, 18, 78, 59, 109, 28, 83, 71, 124, 43, 97, 67, 110, + 67, 110, 101, 130, 14, 55, 25, 69, 55, 96, 69, 122, 18, 59, 28, 71, + 78, 109, 83, 124, 41, 66, 69, 106, 66, 100, 106, 128, 49, 71, 71, + 107, 99, 112, 112, 132, 18, 78, 28, 83, 59, 109, 71, 124, 24, 79, 30, + 84, 79, 115, 84, 125, 49, 99, 71, 112, 71, 112, 107, 132, 63, 101, + 73, 113, 101, 117, 113, 134, 14, 55, 55, 96, 25, 69, 69, 122, 41, 66, + 66, 100, 69, 106, 106, 128, 18, 59, 78, 109, 28, 71, 83, 124, 49, 71, + 99, 112, 71, 107, 112, 132, 18, 78, 59, 109, 28, 83, 71, 124, 49, 99, + 71, 112, 71, 112, 107, 132, 24, 79, 79, 115, 30, 84, 84, 125, 63, + 101, 101, 117, 73, 113, 113, 134, 37, 64, 64, 97, 64, 97, 97, 126, + 43, 67, 67, 101, 97, 110, 110, 130, 43, 67, 97, 110, 67, 101, 110, + 130, 63, 73, 101, 113, 101, 113, 117, 134, 43, 97, 67, 110, 67, 110, + 101, 130, 63, 101, 73, 113, 101, 117, 113, 134, 63, 101, 101, 117, + 73, 113, 113, 134, 95, 103, 103, 119, 103, 119, 119, 136, 4, 13, 13, + 39, 35, 40, 40, 45, 7, 16, 16, 41, 54, 58, 58, 69, 13, 17, 52, 57, + 40, 48, 65, 70, 16, 21, 53, 59, 58, 60, 80, 83, 13, 52, 17, 57, 40, + 65, 48, 70, 16, 53, 21, 59, 58, 80, 60, 83, 39, 57, 57, 76, 45, 70, + 70, 86, 41, 59, 59, 79, 69, 83, 83, 91, 7, 16, 16, 41, 54, 58, 58, + 69, 9, 18, 18, 43, 77, 78, 78, 97, 20, 27, 58, 66, 68, 70, 105, 106, + 21, 28, 59, 67, 82, 83, 109, 110, 20, 58, 27, 66, 68, 105, 70, 106, + 21, 59, 28, 67, 82, 109, 83, 110, 45, 70, 70, 100, 121, 123, 123, + 128, 46, 71, 71, 101, 122, 124, 124, 130, 13, 17, 52, 57, 40, 48, 65, + 70, 20, 27, 58, 66, 68, 70, 105, 106, 17, 23, 75, 76, 48, 62, 98, + 100, 27, 29, 80, 81, 70, 72, 111, 112, 52, 75, 75, 108, 65, 98, 98, + 111, 58, 80, 82, 109, 105, 111, 127, 131, 57, 76, 108, 114, 70, 100, + 111, 116, 66, 81, 109, 115, 106, 112, 131, 133, 16, 21, 53, 59, 58, + 60, 80, 83, 21, 28, 59, 67, 82, 83, 109, 110, 27, 29, 80, 81, 70, 72, + 111, 112, 29, 31, 81, 85, 86, 87, 116, 117, 58, 82, 80, 109, 105, + 127, 111, 131, 60, 83, 83, 110, 127, 131, 131, 143, 70, 86, 111, 116, + 123, 128, 144, 145, 72, 87, 112, 117, 128, 132, 145, 146, 13, 52, 17, + 57, 40, 65, 48, 70, 20, 58, 27, 66, 68, 105, 70, 106, 52, 75, 75, + 108, 65, 98, 98, 111, 58, 82, 80, 109, 105, 127, 111, 131, 17, 75, + 23, 76, 48, 98, 62, 100, 27, 80, 29, 81, 70, 111, 72, 112, 57, 108, + 76, 114, 70, 111, 100, 116, 66, 109, 81, 115, 106, 131, 112, 133, 16, + 53, 21, 59, 58, 80, 60, 83, 21, 59, 28, 67, 82, 109, 83, 110, 58, 80, + 82, 109, 105, 111, 127, 131, 60, 83, 83, 110, 127, 131, 131, 143, 27, + 80, 29, 81, 70, 111, 72, 112, 29, 81, 31, 85, 86, 116, 87, 117, 70, + 111, 86, 116, 123, 144, 128, 145, 72, 112, 87, 117, 128, 145, 132, + 146, 39, 57, 57, 76, 45, 70, 70, 86, 45, 70, 70, 100, 121, 123, 123, + 128, 57, 76, 108, 114, 70, 100, 111, 116, 70, 86, 111, 116, 123, 128, + 144, 145, 57, 108, 76, 114, 70, 111, 100, 116, 70, 111, 86, 116, 123, + 144, 128, 145, 76, 114, 114, 137, 86, 116, 116, 138, 100, 116, 116, + 138, 128, 145, 145, 149, 41, 59, 59, 79, 69, 83, 83, 91, 46, 71, 71, + 101, 122, 124, 124, 130, 66, 81, 109, 115, 106, 112, 131, 133, 72, + 87, 112, 117, 128, 132, 145, 146, 66, 109, 81, 115, 106, 131, 112, + 133, 72, 112, 87, 117, 128, 145, 132, 146, 100, 116, 116, 138, 128, + 145, 145, 149, 102, 118, 118, 139, 141, 147, 147, 150, 7, 20, 20, 45, + 54, 68, 68, 121, 9, 21, 21, 46, 77, 82, 82, 122, 16, 27, 58, 70, 58, + 70, 105, 123, 18, 28, 59, 71, 78, 83, 109, 124, 16, 58, 27, 70, 58, + 105, 70, 123, 18, 59, 28, 71, 78, 109, 83, 124, 41, 66, 66, 100, 69, + 106, 106, 128, 43, 67, 67, 101, 97, 110, 110, 130, 9, 21, 21, 46, 77, + 82, 82, 122, 10, 22, 22, 47, 89, 90, 90, 126, 21, 29, 60, 72, 82, 86, + 127, 128, 22, 30, 61, 73, 90, 91, 129, 130, 21, 60, 29, 72, 82, 127, + 86, 128, 22, 61, 30, 73, 90, 129, 91, 130, 46, 72, 72, 102, 122, 128, + 128, 141, 47, 73, 73, 103, 126, 130, 130, 142, 16, 27, 58, 70, 58, + 70, 105, 123, 21, 29, 60, 72, 82, 86, 127, 128, 21, 29, 82, 86, 60, + 72, 127, 128, 28, 31, 83, 87, 83, 87, 131, 132, 53, 80, 80, 111, 80, + 111, 111, 144, 59, 81, 83, 112, 109, 116, 131, 145, 59, 81, 109, 116, + 83, 112, 131, 145, 67, 85, 110, 117, 110, 117, 143, 146, 18, 28, 59, + 71, 78, 83, 109, 124, 22, 30, 61, 73, 90, 91, 129, 130, 28, 31, 83, + 87, 83, 87, 131, 132, 30, 32, 84, 88, 91, 92, 133, 134, 59, 83, 81, + 112, 109, 131, 116, 145, 61, 84, 84, 113, 129, 133, 133, 146, 71, 87, + 112, 118, 124, 132, 145, 147, 73, 88, 113, 119, 130, 134, 146, 148, + 16, 58, 27, 70, 58, 105, 70, 123, 21, 60, 29, 72, 82, 127, 86, 128, + 53, 80, 80, 111, 80, 111, 111, 144, 59, 83, 81, 112, 109, 131, 116, + 145, 21, 82, 29, 86, 60, 127, 72, 128, 28, 83, 31, 87, 83, 131, 87, + 132, 59, 109, 81, 116, 83, 131, 112, 145, 67, 110, 85, 117, 110, 143, + 117, 146, 18, 59, 28, 71, 78, 109, 83, 124, 22, 61, 30, 73, 90, 129, + 91, 130, 59, 81, 83, 112, 109, 116, 131, 145, 61, 84, 84, 113, 129, + 133, 133, 146, 28, 83, 31, 87, 83, 131, 87, 132, 30, 84, 32, 88, 91, + 133, 92, 134, 71, 112, 87, 118, 124, 145, 132, 147, 73, 113, 88, 119, + 130, 146, 134, 148, 41, 66, 66, 100, 69, 106, 106, 128, 46, 72, 72, + 102, 122, 128, 128, 141, 59, 81, 109, 116, 83, 112, 131, 145, 71, 87, + 112, 118, 124, 132, 145, 147, 59, 109, 81, 116, 83, 131, 112, 145, + 71, 112, 87, 118, 124, 145, 132, 147, 79, 115, 115, 138, 91, 133, + 133, 149, 101, 117, 117, 139, 130, 146, 146, 150, 43, 67, 67, 101, + 97, 110, 110, 130, 47, 73, 73, 103, 126, 130, 130, 142, 67, 85, 110, + 117, 110, 117, 143, 146, 73, 88, 113, 119, 130, 134, 146, 148, 67, + 110, 85, 117, 110, 143, 117, 146, 73, 113, 88, 119, 130, 146, 134, + 148, 101, 117, 117, 139, 130, 146, 146, 150, 103, 119, 119, 140, 142, + 148, 148, 151, 4, 13, 35, 40, 13, 39, 40, 45, 13, 17, 40, 48, 52, 57, + 65, 70, 7, 16, 54, 58, 16, 41, 58, 69, 16, 21, 58, 60, 53, 59, 80, + 83, 13, 52, 40, 65, 17, 57, 48, 70, 39, 57, 45, 70, 57, 76, 70, 86, + 16, 53, 58, 80, 21, 59, 60, 83, 41, 59, 69, 83, 59, 79, 83, 91, 13, + 17, 40, 48, 52, 57, 65, 70, 17, 23, 48, 62, 75, 76, 98, 100, 20, 27, + 68, 70, 58, 66, 105, 106, 27, 29, 70, 72, 80, 81, 111, 112, 52, 75, + 65, 98, 75, 108, 98, 111, 57, 76, 70, 100, 108, 114, 111, 116, 58, + 80, 105, 111, 82, 109, 127, 131, 66, 81, 106, 112, 109, 115, 131, + 133, 7, 16, 54, 58, 16, 41, 58, 69, 20, 27, 68, 70, 58, 66, 105, 106, + 9, 18, 77, 78, 18, 43, 78, 97, 21, 28, 82, 83, 59, 67, 109, 110, 20, + 58, 68, 105, 27, 66, 70, 106, 45, 70, 121, 123, 70, 100, 123, 128, + 21, 59, 82, 109, 28, 67, 83, 110, 46, 71, 122, 124, 71, 101, 124, + 130, 16, 21, 58, 60, 53, 59, 80, 83, 27, 29, 70, 72, 80, 81, 111, + 112, 21, 28, 82, 83, 59, 67, 109, 110, 29, 31, 86, 87, 81, 85, 116, + 117, 58, 82, 105, 127, 80, 109, 111, 131, 70, 86, 123, 128, 111, 116, + 144, 145, 60, 83, 127, 131, 83, 110, 131, 143, 72, 87, 128, 132, 112, + 117, 145, 146, 13, 52, 40, 65, 17, 57, 48, 70, 52, 75, 65, 98, 75, + 108, 98, 111, 20, 58, 68, 105, 27, 66, 70, 106, 58, 82, 105, 127, 80, + 109, 111, 131, 17, 75, 48, 98, 23, 76, 62, 100, 57, 108, 70, 111, 76, + 114, 100, 116, 27, 80, 70, 111, 29, 81, 72, 112, 66, 109, 106, 131, + 81, 115, 112, 133, 39, 57, 45, 70, 57, 76, 70, 86, 57, 76, 70, 100, + 108, 114, 111, 116, 45, 70, 121, 123, 70, 100, 123, 128, 70, 86, 123, + 128, 111, 116, 144, 145, 57, 108, 70, 111, 76, 114, 100, 116, 76, + 114, 86, 116, 114, 137, 116, 138, 70, 111, 123, 144, 86, 116, 128, + 145, 100, 116, 128, 145, 116, 138, 145, 149, 16, 53, 58, 80, 21, 59, + 60, 83, 58, 80, 105, 111, 82, 109, 127, 131, 21, 59, 82, 109, 28, 67, + 83, 110, 60, 83, 127, 131, 83, 110, 131, 143, 27, 80, 70, 111, 29, + 81, 72, 112, 70, 111, 123, 144, 86, 116, 128, 145, 29, 81, 86, 116, + 31, 85, 87, 117, 72, 112, 128, 145, 87, 117, 132, 146, 41, 59, 69, + 83, 59, 79, 83, 91, 66, 81, 106, 112, 109, 115, 131, 133, 46, 71, + 122, 124, 71, 101, 124, 130, 72, 87, 128, 132, 112, 117, 145, 146, + 66, 109, 106, 131, 81, 115, 112, 133, 100, 116, 128, 145, 116, 138, + 145, 149, 72, 112, 128, 145, 87, 117, 132, 146, 102, 118, 141, 147, + 118, 139, 147, 150, 7, 20, 54, 68, 20, 45, 68, 121, 16, 27, 58, 70, + 58, 70, 105, 123, 9, 21, 77, 82, 21, 46, 82, 122, 18, 28, 78, 83, 59, + 71, 109, 124, 16, 58, 58, 105, 27, 70, 70, 123, 41, 66, 69, 106, 66, + 100, 106, 128, 18, 59, 78, 109, 28, 71, 83, 124, 43, 67, 97, 110, 67, + 101, 110, 130, 16, 27, 58, 70, 58, 70, 105, 123, 21, 29, 60, 72, 82, + 86, 127, 128, 21, 29, 82, 86, 60, 72, 127, 128, 28, 31, 83, 87, 83, + 87, 131, 132, 53, 80, 80, 111, 80, 111, 111, 144, 59, 81, 83, 112, + 109, 116, 131, 145, 59, 81, 109, 116, 83, 112, 131, 145, 67, 85, 110, + 117, 110, 117, 143, 146, 9, 21, 77, 82, 21, 46, 82, 122, 21, 29, 82, + 86, 60, 72, 127, 128, 10, 22, 89, 90, 22, 47, 90, 126, 22, 30, 90, + 91, 61, 73, 129, 130, 21, 60, 82, 127, 29, 72, 86, 128, 46, 72, 122, + 128, 72, 102, 128, 141, 22, 61, 90, 129, 30, 73, 91, 130, 47, 73, + 126, 130, 73, 103, 130, 142, 18, 28, 78, 83, 59, 71, 109, 124, 28, + 31, 83, 87, 83, 87, 131, 132, 22, 30, 90, 91, 61, 73, 129, 130, 30, + 32, 91, 92, 84, 88, 133, 134, 59, 83, 109, 131, 81, 112, 116, 145, + 71, 87, 124, 132, 112, 118, 145, 147, 61, 84, 129, 133, 84, 113, 133, + 146, 73, 88, 130, 134, 113, 119, 146, 148, 16, 58, 58, 105, 27, 70, + 70, 123, 53, 80, 80, 111, 80, 111, 111, 144, 21, 60, 82, 127, 29, 72, + 86, 128, 59, 83, 109, 131, 81, 112, 116, 145, 21, 82, 60, 127, 29, + 86, 72, 128, 59, 109, 83, 131, 81, 116, 112, 145, 28, 83, 83, 131, + 31, 87, 87, 132, 67, 110, 110, 143, 85, 117, 117, 146, 41, 66, 69, + 106, 66, 100, 106, 128, 59, 81, 83, 112, 109, 116, 131, 145, 46, 72, + 122, 128, 72, 102, 128, 141, 71, 87, 124, 132, 112, 118, 145, 147, + 59, 109, 83, 131, 81, 116, 112, 145, 79, 115, 91, 133, 115, 138, 133, + 149, 71, 112, 124, 145, 87, 118, 132, 147, 101, 117, 130, 146, 117, + 139, 146, 150, 18, 59, 78, 109, 28, 71, 83, 124, 59, 81, 109, 116, + 83, 112, 131, 145, 22, 61, 90, 129, 30, 73, 91, 130, 61, 84, 129, + 133, 84, 113, 133, 146, 28, 83, 83, 131, 31, 87, 87, 132, 71, 112, + 124, 145, 87, 118, 132, 147, 30, 84, 91, 133, 32, 88, 92, 134, 73, + 113, 130, 146, 88, 119, 134, 148, 43, 67, 97, 110, 67, 101, 110, 130, + 67, 85, 110, 117, 110, 117, 143, 146, 47, 73, 126, 130, 73, 103, 130, + 142, 73, 88, 130, 134, 113, 119, 146, 148, 67, 110, 110, 143, 85, + 117, 117, 146, 101, 117, 130, 146, 117, 139, 146, 150, 73, 113, 130, + 146, 88, 119, 134, 148, 103, 119, 142, 148, 119, 140, 148, 151, 11, + 14, 36, 41, 36, 41, 42, 46, 14, 18, 41, 49, 55, 59, 66, 71, 14, 18, + 55, 59, 41, 49, 66, 71, 18, 22, 59, 61, 59, 61, 81, 84, 36, 55, 42, + 66, 42, 66, 62, 72, 41, 59, 46, 71, 66, 81, 72, 87, 41, 59, 66, 81, + 46, 71, 72, 87, 49, 61, 71, 84, 71, 84, 87, 92, 14, 18, 41, 49, 55, + 59, 66, 71, 18, 24, 49, 63, 78, 79, 99, 101, 25, 28, 69, 71, 69, 71, + 106, 107, 28, 30, 71, 73, 83, 84, 112, 113, 55, 78, 66, 99, 96, 109, + 100, 112, 59, 79, 71, 101, 109, 115, 112, 117, 69, 83, 106, 112, 122, + 124, 128, 132, 71, 84, 107, 113, 124, 125, 132, 134, 14, 18, 55, 59, + 41, 49, 66, 71, 25, 28, 69, 71, 69, 71, 106, 107, 18, 24, 78, 79, 49, + 63, 99, 101, 28, 30, 83, 84, 71, 73, 112, 113, 55, 78, 96, 109, 66, + 99, 100, 112, 69, 83, 122, 124, 106, 112, 128, 132, 59, 79, 109, 115, + 71, 101, 112, 117, 71, 84, 124, 125, 107, 113, 132, 134, 18, 22, 59, + 61, 59, 61, 81, 84, 28, 30, 71, 73, 83, 84, 112, 113, 28, 30, 83, 84, + 71, 73, 112, 113, 31, 32, 87, 88, 87, 88, 118, 119, 78, 90, 109, 129, + 109, 129, 116, 133, 83, 91, 124, 130, 131, 133, 145, 146, 83, 91, + 131, 133, 124, 130, 145, 146, 87, 92, 132, 134, 132, 134, 147, 148, + 36, 55, 42, 66, 42, 66, 62, 72, 55, 78, 66, 99, 96, 109, 100, 112, + 55, 78, 96, 109, 66, 99, 100, 112, 78, 90, 109, 129, 109, 129, 116, + 133, 42, 96, 62, 100, 62, 100, 94, 102, 66, 109, 72, 112, 100, 116, + 102, 118, 66, 109, 100, 116, 72, 112, 102, 118, 99, 129, 112, 133, + 112, 133, 118, 135, 41, 59, 46, 71, 66, 81, 72, 87, 59, 79, 71, 101, + 109, 115, 112, 117, 69, 83, 122, 124, 106, 112, 128, 132, 83, 91, + 124, 130, 131, 133, 145, 146, 66, 109, 72, 112, 100, 116, 102, 118, + 81, 115, 87, 117, 116, 138, 118, 139, 106, 131, 128, 145, 128, 145, + 141, 147, 112, 133, 132, 146, 145, 149, 147, 150, 41, 59, 66, 81, 46, + 71, 72, 87, 69, 83, 106, 112, 122, 124, 128, 132, 59, 79, 109, 115, + 71, 101, 112, 117, 83, 91, 131, 133, 124, 130, 145, 146, 66, 109, + 100, 116, 72, 112, 102, 118, 106, 131, 128, 145, 128, 145, 141, 147, + 81, 115, 116, 138, 87, 117, 118, 139, 112, 133, 145, 149, 132, 146, + 147, 150, 49, 61, 71, 84, 71, 84, 87, 92, 71, 84, 107, 113, 124, 125, + 132, 134, 71, 84, 124, 125, 107, 113, 132, 134, 87, 92, 132, 134, + 132, 134, 147, 148, 99, 129, 112, 133, 112, 133, 118, 135, 112, 133, + 132, 146, 145, 149, 147, 150, 112, 133, 145, 149, 132, 146, 147, 150, + 118, 135, 147, 150, 147, 150, 152, 153, 14, 25, 55, 69, 55, 69, 96, + 122, 18, 28, 59, 71, 78, 83, 109, 124, 18, 28, 78, 83, 59, 71, 109, + 124, 24, 30, 79, 84, 79, 84, 115, 125, 41, 69, 66, 106, 66, 106, 100, + 128, 49, 71, 71, 107, 99, 112, 112, 132, 49, 71, 99, 112, 71, 107, + 112, 132, 63, 73, 101, 113, 101, 113, 117, 134, 18, 28, 59, 71, 78, + 83, 109, 124, 22, 30, 61, 73, 90, 91, 129, 130, 28, 31, 83, 87, 83, + 87, 131, 132, 30, 32, 84, 88, 91, 92, 133, 134, 59, 83, 81, 112, 109, + 131, 116, 145, 61, 84, 84, 113, 129, 133, 133, 146, 71, 87, 112, 118, + 124, 132, 145, 147, 73, 88, 113, 119, 130, 134, 146, 148, 18, 28, 78, + 83, 59, 71, 109, 124, 28, 31, 83, 87, 83, 87, 131, 132, 22, 30, 90, + 91, 61, 73, 129, 130, 30, 32, 91, 92, 84, 88, 133, 134, 59, 83, 109, + 131, 81, 112, 116, 145, 71, 87, 124, 132, 112, 118, 145, 147, 61, 84, + 129, 133, 84, 113, 133, 146, 73, 88, 130, 134, 113, 119, 146, 148, + 24, 30, 79, 84, 79, 84, 115, 125, 30, 32, 84, 88, 91, 92, 133, 134, + 30, 32, 91, 92, 84, 88, 133, 134, 32, 33, 92, 93, 92, 93, 135, 136, + 79, 91, 115, 133, 115, 133, 138, 149, 84, 92, 125, 134, 133, 135, + 149, 150, 84, 92, 133, 135, 125, 134, 149, 150, 88, 93, 134, 136, + 134, 136, 150, 151, 41, 69, 66, 106, 66, 106, 100, 128, 59, 83, 81, + 112, 109, 131, 116, 145, 59, 83, 109, 131, 81, 112, 116, 145, 79, 91, + 115, 133, 115, 133, 138, 149, 46, 122, 72, 128, 72, 128, 102, 141, + 71, 124, 87, 132, 112, 145, 118, 147, 71, 124, 112, 145, 87, 132, + 118, 147, 101, 130, 117, 146, 117, 146, 139, 150, 49, 71, 71, 107, + 99, 112, 112, 132, 61, 84, 84, 113, 129, 133, 133, 146, 71, 87, 124, + 132, 112, 118, 145, 147, 84, 92, 125, 134, 133, 135, 149, 150, 71, + 124, 87, 132, 112, 145, 118, 147, 84, 125, 92, 134, 133, 149, 135, + 150, 107, 132, 132, 147, 132, 147, 147, 152, 113, 134, 134, 148, 146, + 150, 150, 153, 49, 71, 99, 112, 71, 107, 112, 132, 71, 87, 112, 118, + 124, 132, 145, 147, 61, 84, 129, 133, 84, 113, 133, 146, 84, 92, 133, + 135, 125, 134, 149, 150, 71, 124, 112, 145, 87, 132, 118, 147, 107, + 132, 132, 147, 132, 147, 147, 152, 84, 125, 133, 149, 92, 134, 135, + 150, 113, 134, 146, 150, 134, 148, 150, 153, 63, 73, 101, 113, 101, + 113, 117, 134, 73, 88, 113, 119, 130, 134, 146, 148, 73, 88, 130, + 134, 113, 119, 146, 148, 88, 93, 134, 136, 134, 136, 150, 151, 101, + 130, 117, 146, 117, 146, 139, 150, 113, 134, 134, 148, 146, 150, 150, + 153, 113, 134, 146, 150, 134, 148, 150, 153, 119, 136, 148, 151, 148, + 151, 153, 154, 4, 35, 13, 40, 13, 40, 39, 45, 13, 40, 17, 48, 52, 65, + 57, 70, 13, 40, 52, 65, 17, 48, 57, 70, 39, 45, 57, 70, 57, 70, 76, + 86, 7, 54, 16, 58, 16, 58, 41, 69, 16, 58, 21, 60, 53, 80, 59, 83, + 16, 58, 53, 80, 21, 60, 59, 83, 41, 69, 59, 83, 59, 83, 79, 91, 13, + 40, 17, 48, 52, 65, 57, 70, 17, 48, 23, 62, 75, 98, 76, 100, 52, 65, + 75, 98, 75, 98, 108, 111, 57, 70, 76, 100, 108, 111, 114, 116, 20, + 68, 27, 70, 58, 105, 66, 106, 27, 70, 29, 72, 80, 111, 81, 112, 58, + 105, 80, 111, 82, 127, 109, 131, 66, 106, 81, 112, 109, 131, 115, + 133, 13, 40, 52, 65, 17, 48, 57, 70, 52, 65, 75, 98, 75, 98, 108, + 111, 17, 48, 75, 98, 23, 62, 76, 100, 57, 70, 108, 111, 76, 100, 114, + 116, 20, 68, 58, 105, 27, 70, 66, 106, 58, 105, 82, 127, 80, 111, + 109, 131, 27, 70, 80, 111, 29, 72, 81, 112, 66, 106, 109, 131, 81, + 112, 115, 133, 39, 45, 57, 70, 57, 70, 76, 86, 57, 70, 76, 100, 108, + 111, 114, 116, 57, 70, 108, 111, 76, 100, 114, 116, 76, 86, 114, 116, + 114, 116, 137, 138, 45, 121, 70, 123, 70, 123, 100, 128, 70, 123, 86, + 128, 111, 144, 116, 145, 70, 123, 111, 144, 86, 128, 116, 145, 100, + 128, 116, 145, 116, 145, 138, 149, 7, 54, 16, 58, 16, 58, 41, 69, 20, + 68, 27, 70, 58, 105, 66, 106, 20, 68, 58, 105, 27, 70, 66, 106, 45, + 121, 70, 123, 70, 123, 100, 128, 9, 77, 18, 78, 18, 78, 43, 97, 21, + 82, 28, 83, 59, 109, 67, 110, 21, 82, 59, 109, 28, 83, 67, 110, 46, + 122, 71, 124, 71, 124, 101, 130, 16, 58, 21, 60, 53, 80, 59, 83, 27, + 70, 29, 72, 80, 111, 81, 112, 58, 105, 82, 127, 80, 111, 109, 131, + 70, 123, 86, 128, 111, 144, 116, 145, 21, 82, 28, 83, 59, 109, 67, + 110, 29, 86, 31, 87, 81, 116, 85, 117, 60, 127, 83, 131, 83, 131, + 110, 143, 72, 128, 87, 132, 112, 145, 117, 146, 16, 58, 53, 80, 21, + 60, 59, 83, 58, 105, 80, 111, 82, 127, 109, 131, 27, 70, 80, 111, 29, + 72, 81, 112, 70, 123, 111, 144, 86, 128, 116, 145, 21, 82, 59, 109, + 28, 83, 67, 110, 60, 127, 83, 131, 83, 131, 110, 143, 29, 86, 81, + 116, 31, 87, 85, 117, 72, 128, 112, 145, 87, 132, 117, 146, 41, 69, + 59, 83, 59, 83, 79, 91, 66, 106, 81, 112, 109, 131, 115, 133, 66, + 106, 109, 131, 81, 112, 115, 133, 100, 128, 116, 145, 116, 145, 138, + 149, 46, 122, 71, 124, 71, 124, 101, 130, 72, 128, 87, 132, 112, 145, + 117, 146, 72, 128, 112, 145, 87, 132, 117, 146, 102, 141, 118, 147, + 118, 147, 139, 150, 7, 54, 20, 68, 20, 68, 45, 121, 16, 58, 27, 70, + 58, 105, 70, 123, 16, 58, 58, 105, 27, 70, 70, 123, 41, 69, 66, 106, + 66, 106, 100, 128, 9, 77, 21, 82, 21, 82, 46, 122, 18, 78, 28, 83, + 59, 109, 71, 124, 18, 78, 59, 109, 28, 83, 71, 124, 43, 97, 67, 110, + 67, 110, 101, 130, 16, 58, 27, 70, 58, 105, 70, 123, 21, 60, 29, 72, + 82, 127, 86, 128, 53, 80, 80, 111, 80, 111, 111, 144, 59, 83, 81, + 112, 109, 131, 116, 145, 21, 82, 29, 86, 60, 127, 72, 128, 28, 83, + 31, 87, 83, 131, 87, 132, 59, 109, 81, 116, 83, 131, 112, 145, 67, + 110, 85, 117, 110, 143, 117, 146, 16, 58, 58, 105, 27, 70, 70, 123, + 53, 80, 80, 111, 80, 111, 111, 144, 21, 60, 82, 127, 29, 72, 86, 128, + 59, 83, 109, 131, 81, 112, 116, 145, 21, 82, 60, 127, 29, 86, 72, + 128, 59, 109, 83, 131, 81, 116, 112, 145, 28, 83, 83, 131, 31, 87, + 87, 132, 67, 110, 110, 143, 85, 117, 117, 146, 41, 69, 66, 106, 66, + 106, 100, 128, 59, 83, 81, 112, 109, 131, 116, 145, 59, 83, 109, 131, + 81, 112, 116, 145, 79, 91, 115, 133, 115, 133, 138, 149, 46, 122, 72, + 128, 72, 128, 102, 141, 71, 124, 87, 132, 112, 145, 118, 147, 71, + 124, 112, 145, 87, 132, 118, 147, 101, 130, 117, 146, 117, 146, 139, + 150, 9, 77, 21, 82, 21, 82, 46, 122, 21, 82, 29, 86, 60, 127, 72, + 128, 21, 82, 60, 127, 29, 86, 72, 128, 46, 122, 72, 128, 72, 128, + 102, 141, 10, 89, 22, 90, 22, 90, 47, 126, 22, 90, 30, 91, 61, 129, + 73, 130, 22, 90, 61, 129, 30, 91, 73, 130, 47, 126, 73, 130, 73, 130, + 103, 142, 18, 78, 28, 83, 59, 109, 71, 124, 28, 83, 31, 87, 83, 131, + 87, 132, 59, 109, 83, 131, 81, 116, 112, 145, 71, 124, 87, 132, 112, + 145, 118, 147, 22, 90, 30, 91, 61, 129, 73, 130, 30, 91, 32, 92, 84, + 133, 88, 134, 61, 129, 84, 133, 84, 133, 113, 146, 73, 130, 88, 134, + 113, 146, 119, 148, 18, 78, 59, 109, 28, 83, 71, 124, 59, 109, 81, + 116, 83, 131, 112, 145, 28, 83, 83, 131, 31, 87, 87, 132, 71, 124, + 112, 145, 87, 132, 118, 147, 22, 90, 61, 129, 30, 91, 73, 130, 61, + 129, 84, 133, 84, 133, 113, 146, 30, 91, 84, 133, 32, 92, 88, 134, + 73, 130, 113, 146, 88, 134, 119, 148, 43, 97, 67, 110, 67, 110, 101, + 130, 67, 110, 85, 117, 110, 143, 117, 146, 67, 110, 110, 143, 85, + 117, 117, 146, 101, 130, 117, 146, 117, 146, 139, 150, 47, 126, 73, + 130, 73, 130, 103, 142, 73, 130, 88, 134, 113, 146, 119, 148, 73, + 130, 113, 146, 88, 134, 119, 148, 103, 142, 119, 148, 119, 148, 140, + 151, 11, 36, 14, 41, 36, 42, 41, 46, 14, 41, 18, 49, 55, 66, 59, 71, + 36, 42, 55, 66, 42, 62, 66, 72, 41, 46, 59, 71, 66, 72, 81, 87, 14, + 55, 18, 59, 41, 66, 49, 71, 18, 59, 22, 61, 59, 81, 61, 84, 41, 66, + 59, 81, 46, 72, 71, 87, 49, 71, 61, 84, 71, 87, 84, 92, 14, 41, 18, + 49, 55, 66, 59, 71, 18, 49, 24, 63, 78, 99, 79, 101, 55, 66, 78, 99, + 96, 100, 109, 112, 59, 71, 79, 101, 109, 112, 115, 117, 25, 69, 28, + 71, 69, 106, 71, 107, 28, 71, 30, 73, 83, 112, 84, 113, 69, 106, 83, + 112, 122, 128, 124, 132, 71, 107, 84, 113, 124, 132, 125, 134, 36, + 42, 55, 66, 42, 62, 66, 72, 55, 66, 78, 99, 96, 100, 109, 112, 42, + 62, 96, 100, 62, 94, 100, 102, 66, 72, 109, 112, 100, 102, 116, 118, + 55, 96, 78, 109, 66, 100, 99, 112, 78, 109, 90, 129, 109, 116, 129, + 133, 66, 100, 109, 116, 72, 102, 112, 118, 99, 112, 129, 133, 112, + 118, 133, 135, 41, 46, 59, 71, 66, 72, 81, 87, 59, 71, 79, 101, 109, + 112, 115, 117, 66, 72, 109, 112, 100, 102, 116, 118, 81, 87, 115, + 117, 116, 118, 138, 139, 69, 122, 83, 124, 106, 128, 112, 132, 83, + 124, 91, 130, 131, 145, 133, 146, 106, 128, 131, 145, 128, 141, 145, + 147, 112, 132, 133, 146, 145, 147, 149, 150, 14, 55, 18, 59, 41, 66, + 49, 71, 25, 69, 28, 71, 69, 106, 71, 107, 55, 96, 78, 109, 66, 100, + 99, 112, 69, 122, 83, 124, 106, 128, 112, 132, 18, 78, 24, 79, 49, + 99, 63, 101, 28, 83, 30, 84, 71, 112, 73, 113, 59, 109, 79, 115, 71, + 112, 101, 117, 71, 124, 84, 125, 107, 132, 113, 134, 18, 59, 22, 61, + 59, 81, 61, 84, 28, 71, 30, 73, 83, 112, 84, 113, 78, 109, 90, 129, + 109, 116, 129, 133, 83, 124, 91, 130, 131, 145, 133, 146, 28, 83, 30, + 84, 71, 112, 73, 113, 31, 87, 32, 88, 87, 118, 88, 119, 83, 131, 91, + 133, 124, 145, 130, 146, 87, 132, 92, 134, 132, 147, 134, 148, 41, + 66, 59, 81, 46, 72, 71, 87, 69, 106, 83, 112, 122, 128, 124, 132, 66, + 100, 109, 116, 72, 102, 112, 118, 106, 128, 131, 145, 128, 141, 145, + 147, 59, 109, 79, 115, 71, 112, 101, 117, 83, 131, 91, 133, 124, 145, + 130, 146, 81, 116, 115, 138, 87, 118, 117, 139, 112, 145, 133, 149, + 132, 147, 146, 150, 49, 71, 61, 84, 71, 87, 84, 92, 71, 107, 84, 113, + 124, 132, 125, 134, 99, 112, 129, 133, 112, 118, 133, 135, 112, 132, + 133, 146, 145, 147, 149, 150, 71, 124, 84, 125, 107, 132, 113, 134, + 87, 132, 92, 134, 132, 147, 134, 148, 112, 145, 133, 149, 132, 147, + 146, 150, 118, 147, 135, 150, 147, 152, 150, 153, 14, 55, 25, 69, 55, + 96, 69, 122, 18, 59, 28, 71, 78, 109, 83, 124, 41, 66, 69, 106, 66, + 100, 106, 128, 49, 71, 71, 107, 99, 112, 112, 132, 18, 78, 28, 83, + 59, 109, 71, 124, 24, 79, 30, 84, 79, 115, 84, 125, 49, 99, 71, 112, + 71, 112, 107, 132, 63, 101, 73, 113, 101, 117, 113, 134, 18, 59, 28, + 71, 78, 109, 83, 124, 22, 61, 30, 73, 90, 129, 91, 130, 59, 81, 83, + 112, 109, 116, 131, 145, 61, 84, 84, 113, 129, 133, 133, 146, 28, 83, + 31, 87, 83, 131, 87, 132, 30, 84, 32, 88, 91, 133, 92, 134, 71, 112, + 87, 118, 124, 145, 132, 147, 73, 113, 88, 119, 130, 146, 134, 148, + 41, 66, 69, 106, 66, 100, 106, 128, 59, 81, 83, 112, 109, 116, 131, + 145, 46, 72, 122, 128, 72, 102, 128, 141, 71, 87, 124, 132, 112, 118, + 145, 147, 59, 109, 83, 131, 81, 116, 112, 145, 79, 115, 91, 133, 115, + 138, 133, 149, 71, 112, 124, 145, 87, 118, 132, 147, 101, 117, 130, + 146, 117, 139, 146, 150, 49, 71, 71, 107, 99, 112, 112, 132, 61, 84, + 84, 113, 129, 133, 133, 146, 71, 87, 124, 132, 112, 118, 145, 147, + 84, 92, 125, 134, 133, 135, 149, 150, 71, 124, 87, 132, 112, 145, + 118, 147, 84, 125, 92, 134, 133, 149, 135, 150, 107, 132, 132, 147, + 132, 147, 147, 152, 113, 134, 134, 148, 146, 150, 150, 153, 18, 78, + 28, 83, 59, 109, 71, 124, 28, 83, 31, 87, 83, 131, 87, 132, 59, 109, + 83, 131, 81, 116, 112, 145, 71, 124, 87, 132, 112, 145, 118, 147, 22, + 90, 30, 91, 61, 129, 73, 130, 30, 91, 32, 92, 84, 133, 88, 134, 61, + 129, 84, 133, 84, 133, 113, 146, 73, 130, 88, 134, 113, 146, 119, + 148, 24, 79, 30, 84, 79, 115, 84, 125, 30, 84, 32, 88, 91, 133, 92, + 134, 79, 115, 91, 133, 115, 138, 133, 149, 84, 125, 92, 134, 133, + 149, 135, 150, 30, 91, 32, 92, 84, 133, 88, 134, 32, 92, 33, 93, 92, + 135, 93, 136, 84, 133, 92, 135, 125, 149, 134, 150, 88, 134, 93, 136, + 134, 150, 136, 151, 49, 99, 71, 112, 71, 112, 107, 132, 71, 112, 87, + 118, 124, 145, 132, 147, 71, 112, 124, 145, 87, 118, 132, 147, 107, + 132, 132, 147, 132, 147, 147, 152, 61, 129, 84, 133, 84, 133, 113, + 146, 84, 133, 92, 135, 125, 149, 134, 150, 84, 133, 125, 149, 92, + 135, 134, 150, 113, 146, 134, 150, 134, 150, 148, 153, 63, 101, 73, + 113, 101, 117, 113, 134, 73, 113, 88, 119, 130, 146, 134, 148, 101, + 117, 130, 146, 117, 139, 146, 150, 113, 134, 134, 148, 146, 150, 150, + 153, 73, 130, 88, 134, 113, 146, 119, 148, 88, 134, 93, 136, 134, + 150, 136, 151, 113, 146, 134, 150, 134, 150, 148, 153, 119, 148, 136, + 151, 148, 153, 151, 154, 11, 36, 36, 42, 14, 41, 41, 46, 36, 42, 42, + 62, 55, 66, 66, 72, 14, 41, 55, 66, 18, 49, 59, 71, 41, 46, 66, 72, + 59, 71, 81, 87, 14, 55, 41, 66, 18, 59, 49, 71, 41, 66, 46, 72, 59, + 81, 71, 87, 18, 59, 59, 81, 22, 61, 61, 84, 49, 71, 71, 87, 61, 84, + 84, 92, 36, 42, 42, 62, 55, 66, 66, 72, 42, 62, 62, 94, 96, 100, 100, + 102, 55, 66, 96, 100, 78, 99, 109, 112, 66, 72, 100, 102, 109, 112, + 116, 118, 55, 96, 66, 100, 78, 109, 99, 112, 66, 100, 72, 102, 109, + 116, 112, 118, 78, 109, 109, 116, 90, 129, 129, 133, 99, 112, 112, + 118, 129, 133, 133, 135, 14, 41, 55, 66, 18, 49, 59, 71, 55, 66, 96, + 100, 78, 99, 109, 112, 18, 49, 78, 99, 24, 63, 79, 101, 59, 71, 109, + 112, 79, 101, 115, 117, 25, 69, 69, 106, 28, 71, 71, 107, 69, 106, + 122, 128, 83, 112, 124, 132, 28, 71, 83, 112, 30, 73, 84, 113, 71, + 107, 124, 132, 84, 113, 125, 134, 41, 46, 66, 72, 59, 71, 81, 87, 66, + 72, 100, 102, 109, 112, 116, 118, 59, 71, 109, 112, 79, 101, 115, + 117, 81, 87, 116, 118, 115, 117, 138, 139, 69, 122, 106, 128, 83, + 124, 112, 132, 106, 128, 128, 141, 131, 145, 145, 147, 83, 124, 131, + 145, 91, 130, 133, 146, 112, 132, 145, 147, 133, 146, 149, 150, 14, + 55, 41, 66, 18, 59, 49, 71, 55, 96, 66, 100, 78, 109, 99, 112, 25, + 69, 69, 106, 28, 71, 71, 107, 69, 122, 106, 128, 83, 124, 112, 132, + 18, 78, 49, 99, 24, 79, 63, 101, 59, 109, 71, 112, 79, 115, 101, 117, + 28, 83, 71, 112, 30, 84, 73, 113, 71, 124, 107, 132, 84, 125, 113, + 134, 41, 66, 46, 72, 59, 81, 71, 87, 66, 100, 72, 102, 109, 116, 112, + 118, 69, 106, 122, 128, 83, 112, 124, 132, 106, 128, 128, 141, 131, + 145, 145, 147, 59, 109, 71, 112, 79, 115, 101, 117, 81, 116, 87, 118, + 115, 138, 117, 139, 83, 131, 124, 145, 91, 133, 130, 146, 112, 145, + 132, 147, 133, 149, 146, 150, 18, 59, 59, 81, 22, 61, 61, 84, 78, + 109, 109, 116, 90, 129, 129, 133, 28, 71, 83, 112, 30, 73, 84, 113, + 83, 124, 131, 145, 91, 130, 133, 146, 28, 83, 71, 112, 30, 84, 73, + 113, 83, 131, 124, 145, 91, 133, 130, 146, 31, 87, 87, 118, 32, 88, + 88, 119, 87, 132, 132, 147, 92, 134, 134, 148, 49, 71, 71, 87, 61, + 84, 84, 92, 99, 112, 112, 118, 129, 133, 133, 135, 71, 107, 124, 132, + 84, 113, 125, 134, 112, 132, 145, 147, 133, 146, 149, 150, 71, 124, + 107, 132, 84, 125, 113, 134, 112, 145, 132, 147, 133, 149, 146, 150, + 87, 132, 132, 147, 92, 134, 134, 148, 118, 147, 147, 152, 135, 150, + 150, 153, 14, 55, 55, 96, 25, 69, 69, 122, 41, 66, 66, 100, 69, 106, + 106, 128, 18, 59, 78, 109, 28, 71, 83, 124, 49, 71, 99, 112, 71, 107, + 112, 132, 18, 78, 59, 109, 28, 83, 71, 124, 49, 99, 71, 112, 71, 112, + 107, 132, 24, 79, 79, 115, 30, 84, 84, 125, 63, 101, 101, 117, 73, + 113, 113, 134, 41, 66, 66, 100, 69, 106, 106, 128, 46, 72, 72, 102, + 122, 128, 128, 141, 59, 81, 109, 116, 83, 112, 131, 145, 71, 87, 112, + 118, 124, 132, 145, 147, 59, 109, 81, 116, 83, 131, 112, 145, 71, + 112, 87, 118, 124, 145, 132, 147, 79, 115, 115, 138, 91, 133, 133, + 149, 101, 117, 117, 139, 130, 146, 146, 150, 18, 59, 78, 109, 28, 71, + 83, 124, 59, 81, 109, 116, 83, 112, 131, 145, 22, 61, 90, 129, 30, + 73, 91, 130, 61, 84, 129, 133, 84, 113, 133, 146, 28, 83, 83, 131, + 31, 87, 87, 132, 71, 112, 124, 145, 87, 118, 132, 147, 30, 84, 91, + 133, 32, 88, 92, 134, 73, 113, 130, 146, 88, 119, 134, 148, 49, 71, + 99, 112, 71, 107, 112, 132, 71, 87, 112, 118, 124, 132, 145, 147, 61, + 84, 129, 133, 84, 113, 133, 146, 84, 92, 133, 135, 125, 134, 149, + 150, 71, 124, 112, 145, 87, 132, 118, 147, 107, 132, 132, 147, 132, + 147, 147, 152, 84, 125, 133, 149, 92, 134, 135, 150, 113, 134, 146, + 150, 134, 148, 150, 153, 18, 78, 59, 109, 28, 83, 71, 124, 59, 109, + 81, 116, 83, 131, 112, 145, 28, 83, 83, 131, 31, 87, 87, 132, 71, + 124, 112, 145, 87, 132, 118, 147, 22, 90, 61, 129, 30, 91, 73, 130, + 61, 129, 84, 133, 84, 133, 113, 146, 30, 91, 84, 133, 32, 92, 88, + 134, 73, 130, 113, 146, 88, 134, 119, 148, 49, 99, 71, 112, 71, 112, + 107, 132, 71, 112, 87, 118, 124, 145, 132, 147, 71, 112, 124, 145, + 87, 118, 132, 147, 107, 132, 132, 147, 132, 147, 147, 152, 61, 129, + 84, 133, 84, 133, 113, 146, 84, 133, 92, 135, 125, 149, 134, 150, 84, + 133, 125, 149, 92, 135, 134, 150, 113, 146, 134, 150, 134, 150, 148, + 153, 24, 79, 79, 115, 30, 84, 84, 125, 79, 115, 115, 138, 91, 133, + 133, 149, 30, 84, 91, 133, 32, 88, 92, 134, 84, 125, 133, 149, 92, + 134, 135, 150, 30, 91, 84, 133, 32, 92, 88, 134, 84, 133, 125, 149, + 92, 135, 134, 150, 32, 92, 92, 135, 33, 93, 93, 136, 88, 134, 134, + 150, 93, 136, 136, 151, 63, 101, 101, 117, 73, 113, 113, 134, 101, + 117, 117, 139, 130, 146, 146, 150, 73, 113, 130, 146, 88, 119, 134, + 148, 113, 134, 146, 150, 134, 148, 150, 153, 73, 130, 113, 146, 88, + 134, 119, 148, 113, 146, 134, 150, 134, 150, 148, 153, 88, 134, 134, + 150, 93, 136, 136, 151, 119, 148, 148, 153, 136, 151, 151, 154, 34, + 37, 37, 43, 37, 43, 43, 47, 37, 43, 43, 63, 64, 67, 67, 73, 37, 43, + 64, 67, 43, 63, 67, 73, 43, 47, 67, 73, 67, 73, 85, 88, 37, 64, 43, + 67, 43, 67, 63, 73, 43, 67, 47, 73, 67, 85, 73, 88, 43, 67, 67, 85, + 47, 73, 73, 88, 63, 73, 73, 88, 73, 88, 88, 93, 37, 43, 43, 63, 64, + 67, 67, 73, 43, 63, 63, 95, 97, 101, 101, 103, 64, 67, 97, 101, 97, + 101, 110, 113, 67, 73, 101, 103, 110, 113, 117, 119, 64, 97, 67, 101, + 97, 110, 101, 113, 67, 101, 73, 103, 110, 117, 113, 119, 97, 110, + 110, 117, 126, 130, 130, 134, 101, 113, 113, 119, 130, 134, 134, 136, + 37, 43, 64, 67, 43, 63, 67, 73, 64, 67, 97, 101, 97, 101, 110, 113, + 43, 63, 97, 101, 63, 95, 101, 103, 67, 73, 110, 113, 101, 103, 117, + 119, 64, 97, 97, 110, 67, 101, 101, 113, 97, 110, 126, 130, 110, 117, + 130, 134, 67, 101, 110, 117, 73, 103, 113, 119, 101, 113, 130, 134, + 113, 119, 134, 136, 43, 47, 67, 73, 67, 73, 85, 88, 67, 73, 101, 103, + 110, 113, 117, 119, 67, 73, 110, 113, 101, 103, 117, 119, 85, 88, + 117, 119, 117, 119, 139, 140, 97, 126, 110, 130, 110, 130, 117, 134, + 110, 130, 130, 142, 143, 146, 146, 148, 110, 130, 143, 146, 130, 142, + 146, 148, 117, 134, 146, 148, 146, 148, 150, 151, 37, 64, 43, 67, 43, + 67, 63, 73, 64, 97, 67, 101, 97, 110, 101, 113, 64, 97, 97, 110, 67, + 101, 101, 113, 97, 126, 110, 130, 110, 130, 117, 134, 43, 97, 63, + 101, 63, 101, 95, 103, 67, 110, 73, 113, 101, 117, 103, 119, 67, 110, + 101, 117, 73, 113, 103, 119, 101, 130, 113, 134, 113, 134, 119, 136, + 43, 67, 47, 73, 67, 85, 73, 88, 67, 101, 73, 103, 110, 117, 113, 119, + 97, 110, 126, 130, 110, 117, 130, 134, 110, 130, 130, 142, 143, 146, + 146, 148, 67, 110, 73, 113, 101, 117, 103, 119, 85, 117, 88, 119, + 117, 139, 119, 140, 110, 143, 130, 146, 130, 146, 142, 148, 117, 146, + 134, 148, 146, 150, 148, 151, 43, 67, 67, 85, 47, 73, 73, 88, 97, + 110, 110, 117, 126, 130, 130, 134, 67, 101, 110, 117, 73, 103, 113, + 119, 110, 130, 143, 146, 130, 142, 146, 148, 67, 110, 101, 117, 73, + 113, 103, 119, 110, 143, 130, 146, 130, 146, 142, 148, 85, 117, 117, + 139, 88, 119, 119, 140, 117, 146, 146, 150, 134, 148, 148, 151, 63, + 73, 73, 88, 73, 88, 88, 93, 101, 113, 113, 119, 130, 134, 134, 136, + 101, 113, 130, 134, 113, 119, 134, 136, 117, 134, 146, 148, 146, 148, + 150, 151, 101, 130, 113, 134, 113, 134, 119, 136, 117, 146, 134, 148, + 146, 150, 148, 151, 117, 146, 146, 150, 134, 148, 148, 151, 139, 150, + 150, 153, 150, 153, 153, 154, 37, 64, 64, 97, 64, 97, 97, 126, 43, + 67, 67, 101, 97, 110, 110, 130, 43, 67, 97, 110, 67, 101, 110, 130, + 63, 73, 101, 113, 101, 113, 117, 134, 43, 97, 67, 110, 67, 110, 101, + 130, 63, 101, 73, 113, 101, 117, 113, 134, 63, 101, 101, 117, 73, + 113, 113, 134, 95, 103, 103, 119, 103, 119, 119, 136, 43, 67, 67, + 101, 97, 110, 110, 130, 47, 73, 73, 103, 126, 130, 130, 142, 67, 85, + 110, 117, 110, 117, 143, 146, 73, 88, 113, 119, 130, 134, 146, 148, + 67, 110, 85, 117, 110, 143, 117, 146, 73, 113, 88, 119, 130, 146, + 134, 148, 101, 117, 117, 139, 130, 146, 146, 150, 103, 119, 119, 140, + 142, 148, 148, 151, 43, 67, 97, 110, 67, 101, 110, 130, 67, 85, 110, + 117, 110, 117, 143, 146, 47, 73, 126, 130, 73, 103, 130, 142, 73, 88, + 130, 134, 113, 119, 146, 148, 67, 110, 110, 143, 85, 117, 117, 146, + 101, 117, 130, 146, 117, 139, 146, 150, 73, 113, 130, 146, 88, 119, + 134, 148, 103, 119, 142, 148, 119, 140, 148, 151, 63, 73, 101, 113, + 101, 113, 117, 134, 73, 88, 113, 119, 130, 134, 146, 148, 73, 88, + 130, 134, 113, 119, 146, 148, 88, 93, 134, 136, 134, 136, 150, 151, + 101, 130, 117, 146, 117, 146, 139, 150, 113, 134, 134, 148, 146, 150, + 150, 153, 113, 134, 146, 150, 134, 148, 150, 153, 119, 136, 148, 151, + 148, 151, 153, 154, 43, 97, 67, 110, 67, 110, 101, 130, 67, 110, 85, + 117, 110, 143, 117, 146, 67, 110, 110, 143, 85, 117, 117, 146, 101, + 130, 117, 146, 117, 146, 139, 150, 47, 126, 73, 130, 73, 130, 103, + 142, 73, 130, 88, 134, 113, 146, 119, 148, 73, 130, 113, 146, 88, + 134, 119, 148, 103, 142, 119, 148, 119, 148, 140, 151, 63, 101, 73, + 113, 101, 117, 113, 134, 73, 113, 88, 119, 130, 146, 134, 148, 101, + 117, 130, 146, 117, 139, 146, 150, 113, 134, 134, 148, 146, 150, 150, + 153, 73, 130, 88, 134, 113, 146, 119, 148, 88, 134, 93, 136, 134, + 150, 136, 151, 113, 146, 134, 150, 134, 150, 148, 153, 119, 148, 136, + 151, 148, 153, 151, 154, 63, 101, 101, 117, 73, 113, 113, 134, 101, + 117, 117, 139, 130, 146, 146, 150, 73, 113, 130, 146, 88, 119, 134, + 148, 113, 134, 146, 150, 134, 148, 150, 153, 73, 130, 113, 146, 88, + 134, 119, 148, 113, 146, 134, 150, 134, 150, 148, 153, 88, 134, 134, + 150, 93, 136, 136, 151, 119, 148, 148, 153, 136, 151, 151, 154, 95, + 103, 103, 119, 103, 119, 119, 136, 103, 119, 119, 140, 142, 148, 148, + 151, 103, 119, 142, 148, 119, 140, 148, 151, 119, 136, 148, 151, 148, + 151, 153, 154, 103, 142, 119, 148, 119, 148, 140, 151, 119, 148, 136, + 151, 148, 153, 151, 154, 119, 148, 148, 153, 136, 151, 151, 154, 140, + 151, 151, 154, 151, 154, 154, 155 +}; + +const unsigned int igraph_i_isographs_3[] = { 0, 1, 3, 5, 6, 7, 10, 11, 15, 21, + 23, 25, 27, 30, 31, 63 + }; +const unsigned int igraph_i_isographs_3u[] = { 0, 1, 3, 7 }; +const unsigned int igraph_i_isographs_4[] = { + 0, 1, 3, 7, 9, 10, 11, 14, 15, 18, 19, 20, 21, + 22, 23, 27, 29, 30, 31, 54, 55, 63, 73, 75, 76, 77, + 79, 81, 83, 84, 85, 86, 87, 90, 91, 92, 93, 94, 95, + 98, 99, 100, 101, 102, 103, 106, 107, 108, 109, 110, 111, 115, + 116, 117, 118, 119, 122, 123, 124, 125, 126, 127, 219, 220, 221, + 223, 228, 229, 230, 231, 237, 238, 239, 246, 247, 255, 292, 293, + 295, 301, 302, 303, 310, 311, 319, 365, 367, 373, 375, 382, 383, + 511, 585, 587, 591, 593, 594, 595, 596, 597, 598, 599, 601, 602, + 603, 604, 605, 606, 607, 625, 626, 627, 630, 631, 633, 634, 635, + 638, 639, 659, 660, 661, 663, 666, 667, 669, 670, 671, 674, 675, + 678, 679, 683, 686, 687, 694, 695, 703, 729, 731, 732, 733, 735, + 737, 739, 741, 742, 743, 745, 746, 747, 748, 749, 750, 751, 753, + 755, 756, 757, 758, 759, 761, 762, 763, 764, 765, 766, 767, 819, + 822, 823, 826, 827, 830, 831, 875, 876, 877, 879, 883, 885, 886, + 887, 891, 892, 893, 894, 895, 947, 949, 951, 955, 957, 958, 959, + 1019, 1020, 1021, 1023, 1755, 1757, 1758, 1759, 1782, 1783, 1791, 1883, 1887, + 1907, 1911, 1917, 1918, 1919, 2029, 2031, 2039, 2047, 4095 +}; +const unsigned int igraph_i_isographs_4u[] = { 0, 1, 3, 7, 11, 12, 13, + 15, 30, 31, 63 + }; +const unsigned int igraph_i_isographs_5u[] = { + 0, 1, 3, 7, 11, 12, 13, 15, 30, 31, 63, 75, 76, 77, 79, 86, 87, 94, + 95, 116, 117, 119, 127, 222, 223, 235, 236, 237, 239, 254, 255, 507, + 511, 1023 +}; +const unsigned int igraph_i_isographs_6u[] = { + 0, 1, 3, 7, 11, 12, 13, 15, 30, 31, 63, 75, 76, 77, 79, 86, 87, 94, + 95, 116, 117, 119, 127, 222, 223, 235, 236, 237, 239, 254, 255, 507, + 511, 1023, 1099, 1100, 1101, 1103, 1108, 1109, 1110, 1111, 1118, + 1119, 1140, 1141, 1143, 1151, 1182, 1183, 1184, 1185, 1187, 1191, + 1194, 1195, 1196, 1197, 1198, 1199, 1214, 1215, 1246, 1247, 1259, + 1260, 1261, 1263, 1268, 1269, 1270, 1271, 1278, 1279, 1456, 1457, + 1459, 1460, 1461, 1463, 1465, 1467, 1468, 1469, 1471, 1531, 1532, + 1533, 1535, 1972, 1973, 1975, 1983, 2047, 3294, 3295, 3306, 3307, + 3308, 3309, 3310, 3311, 3326, 3327, 3440, 3441, 3443, 3447, 3448, + 3449, 3451, 3452, 3453, 3455, 3576, 3577, 3578, 3579, 3582, 3583, + 3873, 3875, 3879, 3885, 3887, 3903, 3947, 3948, 3949, 3950, 3951, + 3958, 3959, 3966, 3967, 4094, 4095, 7672, 7673, 7675, 7679, 7902, + 7903, 7915, 7916, 7917, 7919, 7934, 7935, 8185, 8187, 8191, 16350, + 16351, 16383, 32767 +}; + +const unsigned int igraph_i_classedges_3[] = { 1, 2, 0, 2, 2, 1, 0, 1, 2, 0, 1, 0 }; +const unsigned int igraph_i_classedges_3u[] = { 1, 2, 0, 2, 0, 1 }; +const unsigned int igraph_i_classedges_4[] = { 2, 3, 1, 3, 0, 3, 3, 2, 1, 2, 0, 2, + 3, 1, 2, 1, 0, 1, 3, 0, 2, 0, 1, 0 + }; +const unsigned int igraph_i_classedges_4u[] = { 2, 3, 1, 3, 0, 3, 1, 2, 0, 2, 0, 1 }; +const unsigned int igraph_i_classedges_5u[] = { 3, 4, 2, 4, 1, 4, 0, 4, 2, 3, 1, 3, + 0, 3, 1, 2, 0, 2, 0, 1 }; +const unsigned int igraph_i_classedges_6u[] = { 4, 5, 3, 5, 2, 5, 1, 5, 0, 5, 3, 4, + 2, 4, 1, 4, 0, 4, 2, 3, 1, 3, 0, 3, + 1, 2, 0, 2, 0, 1 }; + +/** + * \function igraph_isoclass + * \brief Determine the isomorphism class of small graphs. + * + * + * All graphs with a given number of vertices belong to a number of + * isomorphism classes, with every graph in a given class being + * isomorphic to each other. + * + * + * This function gives the isomorphism class (a number) of a + * graph. Two graphs have the same isomorphism class if and only if + * they are isomorphic. + * + * + * The first isomorphism class is numbered zero and it contains the edgeless + * graph. The last isomorphism class contains the full graph. The number of + * isomorphism classes for directed graphs with three vertices is 16 + * (between 0 and 15), for undirected graph it is only 4. For graphs + * with four vertices it is 218 (directed) and 11 (undirected). + * For 5 and 6 vertex undirected graphs, it is 34 and 156, respectively. + * These values can also be retrieved using \ref igraph_graph_count(). + * For more information, see https://oeis.org/A000273 and https://oeis.org/A000088. + * + * + * At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex + * undirected graphs are supported. + * + * + * Multi-edges and self-loops are ignored by this function. + * + * \param graph The graph object. + * \param isoclass Pointer to an integer, the isomorphism class will + * be stored here. + * \return Error code. + * \sa \ref igraph_isomorphic(), \ref igraph_isoclass_subgraph(), + * \ref igraph_isoclass_create(), \ref igraph_motifs_randesu(). + * + * Because of some limitations this function works only for graphs + * with three of four vertices. + * + * + * Time complexity: O(|E|), the number of edges in the graph. + */ +igraph_error_t igraph_isoclass(const igraph_t *graph, igraph_int_t *isoclass) { + igraph_int_t e; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + unsigned int idx, mul; + const unsigned int *arr_idx, *arr_code; + unsigned int code; + + if (igraph_is_directed(graph)) { + switch (no_of_nodes) { + case 3: + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + mul = 4; + break; + default: + IGRAPH_ERROR("Directed isoclass is only implemented for graphs with 3 or 4 vertices.", + IGRAPH_UNIMPLEMENTED); + } + } else { + switch (no_of_nodes) { + case 3: + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + mul = 4; + break; + case 5: + arr_idx = igraph_i_isoclass_5u_idx; + arr_code = igraph_i_isoclass2_5u; + mul = 5; + break; + case 6: + arr_idx = igraph_i_isoclass_6u_idx; + arr_code = igraph_i_isoclass2_6u; + mul = 6; + break; + default: + IGRAPH_ERROR("Undirected isoclass is only implemented for graphs with 3 to 6 vertices.", + IGRAPH_UNIMPLEMENTED); + } + } + + code = 0; + for (e = 0; e < no_of_edges; e++) { + idx = mul * IGRAPH_FROM(graph, e) + IGRAPH_TO(graph, e); + code |= arr_idx[idx]; + } + + *isoclass = (igraph_int_t) arr_code[code]; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_isoclass_subgraph + * \brief The isomorphism class of a subgraph of a graph. + * + * This function identifies the isomorphism class of the subgraph + * induced the vertices specified in \p vids. + * + * + * At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex + * undirected graphs are supported. + * + * + * Multi-edges and self-loops are ignored by this function. + * + * \param graph The graph object. + * \param vids The vertices of the subgraph. Each vertex must be included at most once. + * \param isoclass Pointer to an integer, this will be set to the + * isomorphism class. + * \return Error code. + * \sa \ref igraph_isoclass(), \ref igraph_isomorphic(), + * \ref igraph_isoclass_create(). + * + * Time complexity: O((d+n)*n), d is the average degree in the network, + * and n is the number of vertices in \c vids. + */ +igraph_error_t igraph_isoclass_subgraph(const igraph_t *graph, igraph_vs_t vids, + igraph_int_t *isoclass) { + igraph_int_t subgraph_size; + igraph_vector_int_t vertices, neis; + + unsigned int mul, idx; + const unsigned int *arr_idx, *arr_code; + unsigned int code = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertices, 0); + IGRAPH_CHECK(igraph_vs_as_vector(graph, vids, &vertices)); + subgraph_size = igraph_vector_int_size(&vertices); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + if (igraph_is_directed(graph)) { + switch (subgraph_size) { + case 3: + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + mul = 4; + break; + default: + IGRAPH_ERROR("Directed isoclass is only implemented for graphs with 3 or 4 vertices.", + IGRAPH_UNIMPLEMENTED); + } + } else { + switch (subgraph_size) { + case 3: + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + mul = 4; + break; + case 5: + arr_idx = igraph_i_isoclass_5u_idx; + arr_code = igraph_i_isoclass2_5u; + mul = 5; + break; + case 6: + arr_idx = igraph_i_isoclass_6u_idx; + arr_code = igraph_i_isoclass2_6u; + mul = 6; + break; + default: + IGRAPH_ERROR("Undirected isoclass is only implemented for graphs with 3 to 6 vertices.", + IGRAPH_UNIMPLEMENTED); + } + } + + for (igraph_int_t i = 0; i < subgraph_size; i++) { + igraph_int_t from = VECTOR(vertices)[i]; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, from, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + const igraph_int_t s = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < s; j++) { + igraph_int_t nei = VECTOR(neis)[j], to; + if (igraph_vector_int_search(&vertices, 0, nei, &to)) { + idx = (mul * i + to); + code |= arr_idx[idx]; + } + } + } + + *isoclass = arr_code[code]; + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&vertices); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_isoclass_create + * \brief Creates a graph from the given isomorphism class. + * + * + * This function creates the canonical representative graph of the + * given isomorphism class. + * + * + * The isomorphism class is an integer between 0 and the number of + * unique unlabeled (i.e. non-isomorphic) graphs on the given number + * of vertices and give directedness. See https://oeis.org/A000273 + * and https://oeis.org/A000088 for the number of directed and + * undirected graphs on \p size nodes. + * + * + * At the moment, 3- and 4-vertex directed graphs and 3 to 6 vertex + * undirected graphs are supported. + * + * \param graph Pointer to an uninitialized graph object. + * \param size The number of vertices to add to the graph. + * \param number The isomorphism class. + * \param directed Boolean constant, whether to create a directed + * graph. + * \return Error code. + * \sa \ref igraph_isoclass(), + * \ref igraph_isoclass_subgraph(), + * \ref igraph_isomorphic(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph to create. + */ +igraph_error_t igraph_isoclass_create(igraph_t *graph, igraph_int_t size, + igraph_int_t number, igraph_bool_t directed) { + igraph_vector_int_t edges; + const unsigned int *classedges; + igraph_int_t graphcount; + igraph_int_t pos; + unsigned int power; + unsigned int code; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + +#define CHECK_ISOCLASS(number, directed, size, graphcount) \ + IGRAPH_ERRORF( \ + "Isoclass %" IGRAPH_PRId " requested, but there are only %" \ + IGRAPH_PRId " %s graphs of size %" IGRAPH_PRId ".", IGRAPH_EINVAL, \ + number, graphcount, directed ? "directed" : "undirected", size) + + if (directed) { + switch (size) { + case 3: { + classedges = igraph_i_classedges_3; + graphcount = sizeof(igraph_i_isographs_3) / sizeof(igraph_i_isographs_3[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_3[number]; + power = 32; + + break; + } + case 4: { + classedges = igraph_i_classedges_4; + graphcount = sizeof(igraph_i_isographs_4) / sizeof(igraph_i_isographs_4[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_4[number]; + power = 2048; + + break; + } + default: + IGRAPH_ERROR("Directed isoclasses are supported only for graphs with 3 or 4 vertices.", + IGRAPH_UNIMPLEMENTED); + } + + } else { + switch (size) { + case 3: { + classedges = igraph_i_classedges_3u; + graphcount = sizeof(igraph_i_isographs_3u) / sizeof(igraph_i_isographs_3u[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_3u[number]; + power = 4; + + break; + } + case 4: { + classedges = igraph_i_classedges_4u; + graphcount = sizeof(igraph_i_isographs_4u) / sizeof(igraph_i_isographs_4u[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_4u[number]; + power = 32; + + break; + } + case 5: { + classedges = igraph_i_classedges_5u; + graphcount = sizeof(igraph_i_isographs_5u) / sizeof(igraph_i_isographs_5u[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_5u[number]; + power = 512; + + break; + } + case 6: { + classedges = igraph_i_classedges_6u; + graphcount = sizeof(igraph_i_isographs_6u) / sizeof(igraph_i_isographs_6u[0]); + + if (number < 0 || number >= graphcount) { + CHECK_ISOCLASS(number, directed, size, graphcount); + } + + code = igraph_i_isographs_6u[number]; + power = 16384; + + break; + } + default: + IGRAPH_ERROR("Undirected isoclasses are supported only for graphs with 3 to 6 vertices.", + IGRAPH_UNIMPLEMENTED); + } + } + +#undef CHECK_ISOCLASS + + pos = 0; + while (code > 0) { + if (code >= power) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, classedges[2 * pos])); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, classedges[2 * pos + 1])); + code -= power; + } + power /= 2; + pos++; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, size, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* https://oeis.org/A000088 */ +static igraph_int_t undirected_graph_counts[] = { + 1, 1, 2, 4, 11, 34, 156, 1044, 12346, 274668, 12005168, 1018997864, +#if IGRAPH_INTEGER_SIZE == 64 + 165091172592, 50502031367952, 29054155657235488 +#endif +}; + +/* https://oeis.org/A000273 */ +static igraph_int_t directed_graph_counts[] = { + 1, 1, 3, 16, 218, 9608, 1540944, 882033440, +#if IGRAPH_INTEGER_SIZE == 64 + 1793359192848, 13027956824399552 +#endif +}; + +/** + * \function igraph_graph_count + * \brief The number of unlabelled graphs on the given number of vertices. + * + * Gives the number of unlabelled \em simple graphs on the specified number of vertices. + * The "isoclass" of a graph of this size is at most one less than this value. + * + * + * This function is meant to be used in conjunction with isoclass and motif finder + * functions. It will only work for small \p n values for which the result is + * represetable in an \type igraph_int_t. For larger \p n values, an overflow + * error is raised. + * + * \param n The number of vertices. + * \param directed Boolean, whether to consider directed graphs. + * \param count Pointer to an integer, the result will be stored here. + * \return Error code. + * + * \sa \ref igraph_isoclass(), \ref igraph_motifs_randesu_callback(). + * + * Time complexity: O(1). + */ +igraph_error_t igraph_graph_count(igraph_int_t n, igraph_bool_t directed, igraph_int_t *count) { + if (n < 0) { + IGRAPH_ERROR("Graph size must not be negative.", IGRAPH_EINVAL); + } + if (directed) { + if (n >= (igraph_int_t) (sizeof directed_graph_counts / sizeof directed_graph_counts[0])) { + IGRAPH_ERRORF("Graph size of % " IGRAPH_PRId " too large.", IGRAPH_EOVERFLOW, n); + } + *count = directed_graph_counts[n]; + } else { + if (n >= (igraph_int_t) (sizeof undirected_graph_counts / sizeof undirected_graph_counts[0])) { + IGRAPH_ERRORF("Graph size of % " IGRAPH_PRId " too large.", IGRAPH_EOVERFLOW, n); + } + *count = undirected_graph_counts[n]; + } + return IGRAPH_SUCCESS; +} diff --git a/src/isomorphism/isoclasses.h b/src/isomorphism/isoclasses.h new file mode 100644 index 0000000..9d543b6 --- /dev/null +++ b/src/isomorphism/isoclasses.h @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2008-2020 The igraph development team + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_ISOCLASSES_H +#define IGRAPH_ISOCLASSES_H + +#include "igraph_decls.h" + +IGRAPH_BEGIN_C_DECLS + +extern const unsigned int igraph_i_isoclass2_3[]; +extern const unsigned int igraph_i_isoclass2_4[]; +extern const unsigned int igraph_i_isoclass2_3u[]; +extern const unsigned int igraph_i_isoclass2_4u[]; +extern const unsigned int igraph_i_isoclass2_5u[]; +extern const unsigned int igraph_i_isoclass2_6u[]; +extern const unsigned int igraph_i_isoclass_3_idx[]; +extern const unsigned int igraph_i_isoclass_4_idx[]; +extern const unsigned int igraph_i_isoclass_3u_idx[]; +extern const unsigned int igraph_i_isoclass_4u_idx[]; +extern const unsigned int igraph_i_isoclass_5u_idx[]; +extern const unsigned int igraph_i_isoclass_6u_idx[]; + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/isomorphism/isomorphism_misc.c b/src/isomorphism/isomorphism_misc.c new file mode 100644 index 0000000..7133e81 --- /dev/null +++ b/src/isomorphism/isomorphism_misc.c @@ -0,0 +1,114 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_isomorphism.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" + +/** + * \function igraph_simplify_and_colorize + * \brief Simplify the graph and compute self-loop and edge multiplicities. + * + * + * This function creates a vertex and edge colored simple graph from the input + * graph. The vertex colors are computed as the number of incident self-loops + * to each vertex in the input graph. The edge colors are computed as the number of + * parallel edges in the input graph that were merged to create each edge + * in the simple graph. + * + * + * The resulting colored simple graph is suitable for use by isomorphism checking + * algorithms such as VF2, which only support simple graphs, but can consider + * vertex and edge colors. + * + * \param graph The graph object, typically having self-loops or multi-edges. + * \param res An uninitialized graph object. The result will be stored here + * \param vertex_color Computed vertex colors corresponding to self-loop multiplicities. + * \param edge_color Computed edge colors corresponding to edge multiplicities + * \return Error code. + * + * \sa \ref igraph_simplify(), \ref igraph_isomorphic_vf2(), \ref igraph_subisomorphic_vf2() + * + */ +igraph_error_t igraph_simplify_and_colorize( + const igraph_t *graph, igraph_t *res, + igraph_vector_int_t *vertex_color, igraph_vector_int_t *edge_color) { + igraph_es_t es; + igraph_eit_t eit; + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t pto = -1, pfrom = -1; + igraph_int_t i; + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + + IGRAPH_CHECK(igraph_vector_int_resize(vertex_color, no_of_nodes)); + igraph_vector_int_null(vertex_color); + + IGRAPH_CHECK(igraph_vector_int_resize(edge_color, no_of_edges)); + igraph_vector_int_null(edge_color); + + i = -1; + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + + if (to == from) { + VECTOR(*vertex_color)[to]++; + continue; + } + + if (to == pto && from == pfrom) { + VECTOR(*edge_color)[i]++; + } else { + igraph_vector_int_push_back(&edges, from); + igraph_vector_int_push_back(&edges, to); + i++; + VECTOR(*edge_color)[i] = 1; + } + + pfrom = from; pto = to; + } + + igraph_vector_int_resize(edge_color, i + 1); + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, igraph_is_directed(graph))); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/isomorphism/lad.c b/src/isomorphism/lad.c new file mode 100644 index 0000000..3c2cc40 --- /dev/null +++ b/src/isomorphism/lad.c @@ -0,0 +1,1646 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + The contents of this file was originally taken from the LAD + homepage: http://liris.cnrs.fr/csolnon/LAD.html and then + modified to fit better into igraph. + + Unfortunately LAD seems to have no version numbers. The files + were apparently last changed on the 29th of June, 2010. + + The original copyright message follows here. The CeCILL-B V1 license + is GPL compatible, because instead of V1, one can freely choose to + use V2, and V2 is explicitly GPL compatible. +*/ + +/* This software has been written by Christine Solnon. + It is distributed under the CeCILL-B FREE SOFTWARE LICENSE + see http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html + for more details +*/ + +/* Several modifications had to be made to the original LAD implementation + to make it compile with non-C99-compliant compilers such as MSVC. In + particular, I had to remove all the variable-sized arrays. + -- Tamas Nepusz, 11 July 2013 +*/ + +#include "igraph_isomorphism.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_matrix.h" +#include "igraph_qsort.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +#include "core/interruption.h" + +#include +#include +#include +#include + +/* helper to allocate an array of given size and free it using IGRAPH_FINALLY + * when needed */ +#define ALLOC_ARRAY(VAR, SIZE, TYPE) { \ + VAR = IGRAPH_CALLOC(SIZE, TYPE); \ + IGRAPH_CHECK_OOM(VAR, "Cannot allocate '" #VAR "' array in LAD isomorphism search."); \ + IGRAPH_FINALLY(igraph_free, VAR); \ + } + +/* helper to allocate an array of given size and store its address in a + * pointer array */ +#define ALLOC_ARRAY_IN_HISTORY(VAR, SIZE, TYPE, HISTORY) { \ + VAR = IGRAPH_CALLOC(SIZE, TYPE); \ + IGRAPH_CHECK_OOM(VAR, "Cannot allocate '" #VAR "' array in LAD isomorphism search."); \ + IGRAPH_FINALLY(igraph_free, VAR); \ + IGRAPH_CHECK(igraph_vector_ptr_push_back(HISTORY, VAR)); \ + IGRAPH_FINALLY_CLEAN(1); \ + } + +/* ---------------------------------------------------------*/ +/* Coming from graph.c */ +/* ---------------------------------------------------------*/ + +#define ISEDGE(G, i, j) IGRAPH_BIT_TEST(G->isEdge, i * G->nbVertices + j) +#define SETEDGE(G, i, j) IGRAPH_BIT_SET(G->isEdge, i * G->nbVertices + j) + +typedef struct { + igraph_int_t nbVertices; /* Number of vertices */ + igraph_vector_int_t nbSucc; + igraph_adjlist_t succ; + igraph_bitset_t isEdge; +} Tgraph; + +static igraph_error_t igraph_i_lad_createGraph(const igraph_t *igraph, Tgraph* graph) { + igraph_int_t i, j, n; + igraph_int_t no_of_nodes = igraph_vcount(igraph); + igraph_vector_int_t *neis; + + graph->nbVertices = no_of_nodes; + + IGRAPH_CHECK(igraph_adjlist_init(igraph, &graph->succ, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &graph->succ); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&graph->nbSucc, no_of_nodes); + for (i=0; i < no_of_nodes; ++i) { + VECTOR(graph->nbSucc)[i] = igraph_vector_int_size(igraph_adjlist_get(&graph->succ, i)); + } + + IGRAPH_BITSET_INIT_FINALLY(&graph->isEdge, no_of_nodes * no_of_nodes); + + for (i = 0; i < no_of_nodes; i++) { + neis = igraph_adjlist_get(&graph->succ, i); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + igraph_int_t v = VECTOR(*neis)[j]; + if (ISEDGE(graph, i, v)) { + IGRAPH_ERROR("LAD functions do not support graphs with multi-edges.", IGRAPH_EINVAL); + } + SETEDGE(graph, i, v); + } + } + + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static void igraph_i_lad_destroyGraph(Tgraph *graph) { + igraph_bitset_destroy(&graph->isEdge); + igraph_adjlist_destroy(&graph->succ); + igraph_vector_int_destroy(&graph->nbSucc); +} + + +/* ---------------------------------------------------------*/ +/* Coming from domains.c */ +/* ---------------------------------------------------------*/ + +typedef struct { + igraph_vector_int_t nbVal; /* nbVal[u] = number of values in D[u] */ + igraph_vector_int_t firstVal; /* firstVal[u] = pos in val of the + first value of D[u] */ + igraph_vector_int_t val; /* val[firstVal[u]..firstVal[u]+nbVal[u]-1] = + values of D[u] */ + igraph_matrix_int_t posInVal; + /* If v in D[u] then firstVal[u] <= posInVal[u][v] < firstVal[u]+nbVal[u] + and val[posInVal[u][v]] = v + otherwise posInVal[u][v] >= firstVal[u]+nbVal[u] */ + igraph_int_t valSize; /* size of val */ + igraph_matrix_int_t firstMatch; + /* firstMatch[u][v] = pos in match of the first vertex + of the covering matching of G_(u, v) */ + igraph_vector_int_t matching; + /* matching[firstMatch[u][v]..firstMatch[u][v]+nbSucc[u]-1] + = covering matching of G_(u, v) */ + igraph_int_t nextOutToFilter; /* position in toFilter of the next pattern node whose + domain should be filtered (-1 if no domain to + filter) */ + igraph_int_t lastInToFilter; /* position in toFilter of the last pattern node whose + domain should be filtered */ + igraph_vector_int_t toFilter; /* contain all pattern nodes whose + domain should be filtered */ + igraph_bitset_t markedToFilter; /* markedToFilter[u]=true if u + is in toFilter; false otherwise */ + igraph_vector_int_t globalMatchingP; /* globalMatchingP[u] = node of Gt + matched to u in globalAllDiff(Np) */ + igraph_vector_int_t globalMatchingT; + /* globalMatchingT[v] = node of Gp matched to v in globalAllDiff(Np) + or -1 if v is not matched */ +} Tdomain; + +static bool igraph_i_lad_toFilterEmpty(Tdomain* D) { + /* return true if there is no more nodes in toFilter */ + return (D->nextOutToFilter < 0); +} + +static void igraph_i_lad_resetToFilter(Tdomain *D) { + /* empty to filter and unmark the vertices that are marked to be filtered */ + igraph_bitset_null(&D->markedToFilter); + D->nextOutToFilter = -1; +} + + +static igraph_int_t igraph_i_lad_nextToFilter(Tdomain* D, igraph_int_t size) { + /* precondition: emptyToFilter = false + remove a node from toFilter (FIFO) + unmark this node and return it */ + igraph_int_t u = VECTOR(D->toFilter)[D->nextOutToFilter]; + IGRAPH_BIT_CLEAR(D->markedToFilter, u); + if (D->nextOutToFilter == D->lastInToFilter) { + /* u was the last node in tofilter */ + D->nextOutToFilter = -1; + } else if (D->nextOutToFilter == size - 1) { + D->nextOutToFilter = 0; + } else { + D->nextOutToFilter++; + } + return u; +} + +static void igraph_i_lad_addToFilter(igraph_int_t u, Tdomain* D, igraph_int_t size) { + /* if u is not marked, then add it to toFilter and mark it */ + if (IGRAPH_BIT_TEST(D->markedToFilter, u)) { + return; + } + IGRAPH_BIT_SET(D->markedToFilter, u); + if (D->nextOutToFilter < 0) { + D->lastInToFilter = 0; + D->nextOutToFilter = 0; + } else if (D->lastInToFilter == size - 1) { + D->lastInToFilter = 0; + } else { + D->lastInToFilter++; + } + VECTOR(D->toFilter)[D->lastInToFilter] = u; +} + +static bool igraph_i_lad_isInD(igraph_int_t u, igraph_int_t v, Tdomain* D) { + /* returns true if v belongs to D(u); false otherwise */ + return (MATRIX(D->posInVal, u, v) < + VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]); +} + +static igraph_error_t igraph_i_lad_augmentingPath(igraph_int_t u, Tdomain* D, igraph_int_t nbV, bool* result) { + /* return true if there exists an augmenting path starting from u and + ending on a free vertex v in the bipartite directed graph G=(U, + V, E) such that U=pattern nodes, V=target nodes, and + E={(u, v), v in D(u)} U {(v, u), D->globalMatchingP[u]=v} + update D-globalMatchingP and D->globalMatchingT consequently */ + igraph_int_t *fifo, *pred; + igraph_bitset_t marked; + igraph_int_t nextIn = 0; + igraph_int_t nextOut = 0; + igraph_int_t i, v, v2, u2; + + *result = false; + + /* Allocate memory */ + ALLOC_ARRAY(fifo, nbV, igraph_int_t); + ALLOC_ARRAY(pred, nbV, igraph_int_t); + IGRAPH_BITSET_INIT_FINALLY(&marked, nbV); + + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + if (VECTOR(D->globalMatchingT)[v] < 0) { + /* v is free => augmenting path found */ + VECTOR(D->globalMatchingP)[u] = v; + VECTOR(D->globalMatchingT)[v] = u; + *result = true; + goto cleanup; + } + /* v is not free => add it to fifo */ + pred[v] = u; + fifo[nextIn++] = v; + IGRAPH_BIT_SET(marked, v); + } + while (nextOut < nextIn) { + u2 = VECTOR(D->globalMatchingT)[fifo[nextOut++]]; + for (i = 0; i < VECTOR(D->nbVal)[u2]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u2] + i ]; /* v in D(u2) */ + if (VECTOR(D->globalMatchingT)[v] < 0) { + /* v is free => augmenting path found */ + while (u2 != u) { /* update global matching wrt path */ + v2 = VECTOR(D->globalMatchingP)[u2]; + VECTOR(D->globalMatchingP)[u2] = v; + VECTOR(D->globalMatchingT)[v] = u2; + v = v2; + u2 = pred[v]; + } + VECTOR(D->globalMatchingP)[u] = v; + VECTOR(D->globalMatchingT)[v] = u; + *result = true; + goto cleanup; + } + if (!IGRAPH_BIT_TEST(marked, v)) { /* v is not free and not marked => add it to fifo */ + pred[v] = u2; + fifo[nextIn++] = v; + IGRAPH_BIT_SET(marked, v); + } + } + } + +cleanup: + igraph_free(fifo); + igraph_free(pred); + igraph_bitset_destroy(&marked); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_lad_removeAllValuesButOne(igraph_int_t u, igraph_int_t v, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, bool* result) { + /* remove all values but v from D(u) and add all successors of u in + toFilter return false if an inconsistency is detected wrt to + global all diff */ + igraph_int_t j, oldPos, newPos; + igraph_vector_int_t *uneis = igraph_adjlist_get(&Gp->succ, u); + igraph_int_t n = igraph_vector_int_size(uneis); + /* add all successors of u in toFilter */ + for (j = 0; j < n; j++) { + igraph_i_lad_addToFilter(VECTOR(*uneis)[j], D, + Gp->nbVertices); + } + /* remove all values but v from D[u] */ + oldPos = MATRIX(D->posInVal, u, v); + newPos = VECTOR(D->firstVal)[u]; + VECTOR(D->val)[oldPos] = VECTOR(D->val)[newPos]; + VECTOR(D->val)[newPos] = v; + MATRIX(D->posInVal, u, VECTOR(D->val)[newPos]) = newPos; + MATRIX(D->posInVal, u, VECTOR(D->val)[oldPos]) = oldPos; + VECTOR(D->nbVal)[u] = 1; + /* update global matchings that support the global all different + constraint */ + if (VECTOR(D->globalMatchingP)[u] != v) { + VECTOR(D->globalMatchingT)[ VECTOR(D->globalMatchingP)[u] ] = -1; + VECTOR(D->globalMatchingP)[u] = -1; + IGRAPH_CHECK(igraph_i_lad_augmentingPath(u, D, Gt->nbVertices, result)); + } else { + *result = true; + } + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_lad_removeValue(igraph_int_t u, igraph_int_t v, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, bool* result) { + /* remove v from D(u) and add all successors of u in toFilter + return false if an inconsistency is detected wrt global all diff */ + igraph_int_t j; + igraph_vector_int_t *uneis = igraph_adjlist_get(&Gp->succ, u); + igraph_int_t n = igraph_vector_int_size(uneis); + igraph_int_t oldPos, newPos; + + /* add all successors of u in toFilter */ + for (j = 0; j < n; j++) { + igraph_i_lad_addToFilter(VECTOR(*uneis)[j], D, + Gp->nbVertices); + } + /* remove v from D[u] */ + oldPos = MATRIX(D->posInVal, u, v); + VECTOR(D->nbVal)[u]--; + newPos = VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]; + VECTOR(D->val)[oldPos] = VECTOR(D->val)[newPos]; + VECTOR(D->val)[newPos] = v; + MATRIX(D->posInVal, u, VECTOR(D->val)[oldPos]) = oldPos; + MATRIX(D->posInVal, u, VECTOR(D->val)[newPos]) = newPos; + /* update global matchings that support the global all different + constraint */ + if (VECTOR(D->globalMatchingP)[u] == v) { + VECTOR(D->globalMatchingP)[u] = -1; + VECTOR(D->globalMatchingT)[v] = -1; + IGRAPH_CHECK(igraph_i_lad_augmentingPath(u, D, Gt->nbVertices, result)); + } else { + *result = true; + } + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_lad_matchVertices(igraph_int_t nb, igraph_vector_int_t* toBeMatched, + bool induced, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, igraph_bool_t *invalid) { + /* for each u in toBeMatched[0..nb-1], match u to + D->val[D->firstVal[u] and filter domains of other non matched + vertices wrt FC(Edges) and FC(diff) (this is not mandatory, as + LAD is stronger than FC(Edges) and GAC(allDiff) is stronger than + FC(diff), but this speeds up the solution process). + return false if an inconsistency is detected by FC(Edges) or + FC(diff); true otherwise; */ + igraph_int_t j, u, v, u2, oldNbVal; + igraph_vector_int_t *vneis; + bool result = false; + + while (nb > 0) { + u = VECTOR(*toBeMatched)[--nb]; + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + vneis = igraph_adjlist_get(&Gt->succ, v); + /* match u to v */ + for (u2 = 0; u2 < Gp->nbVertices; u2++) { + if (u != u2) { + oldNbVal = VECTOR(D->nbVal)[u2]; + if (igraph_i_lad_isInD(u2, v, D)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, v, D, Gp, Gt, &result)); + if (!result) { + *invalid = true; + return IGRAPH_SUCCESS; + } + } + if (ISEDGE(Gp, u, u2)) { + /* remove from D[u2] vertices which are not adjacent to v */ + j = VECTOR(D->firstVal)[u2]; + while (j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]) { + if (ISEDGE(Gt, v, VECTOR(D->val)[j])) { + j++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, VECTOR(D->val)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = true; + return IGRAPH_SUCCESS; + } + } + } + } else if (induced) { + /* (u, u2) is not an edge => remove neighbors of v from D[u2] */ + if (VECTOR(D->nbVal)[u2] < VECTOR(Gt->nbSucc)[v]) { + j = VECTOR(D->firstVal)[u2]; + while (j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]) { + if (!ISEDGE(Gt, v, VECTOR(D->val)[j])) { + j++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, VECTOR(D->val)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = true; + return IGRAPH_SUCCESS; + } + } + } + } else { + for (j = 0; j < VECTOR(Gt->nbSucc)[v]; j++) { + if (igraph_i_lad_isInD(u2, VECTOR(*vneis)[j], D)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, VECTOR(*vneis)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = true; + return IGRAPH_SUCCESS; + } + } + } + } + } + if (VECTOR(D->nbVal)[u2] == 0) { + *invalid = true; /* D[u2] is empty */ + return IGRAPH_SUCCESS; + } + if ((VECTOR(D->nbVal)[u2] == 1) && (oldNbVal > 1)) { + VECTOR(*toBeMatched)[nb++] = u2; + } + } + } + } + *invalid = false; + return IGRAPH_SUCCESS; +} + + +static bool igraph_i_lad_matchVertex(igraph_int_t u, bool induced, Tdomain* D, Tgraph* Gp, + Tgraph *Gt) { + igraph_bool_t invalid; + /* match u to D->val[D->firstVal[u]] and filter domains of other non + matched vertices wrt FC(Edges) and FC(diff) (this is not + mandatory, as LAD is stronger than FC(Edges) and GAC(allDiff) + is stronger than FC(diff), but this speeds up the solution process). + return false if an inconsistency is detected by FC(Edges) or + FC(diff); true otherwise; */ + igraph_vector_int_t toBeMatched; + IGRAPH_VECTOR_INT_INIT_FINALLY(&toBeMatched, Gp->nbVertices); + VECTOR(toBeMatched)[0] = u; + IGRAPH_CHECK(igraph_i_lad_matchVertices(1, &toBeMatched, induced, D, Gp, Gt, + &invalid)); + igraph_vector_int_destroy(&toBeMatched); + IGRAPH_FINALLY_CLEAN(1); + + return ! invalid; +} + + +static int igraph_i_lad_qcompare (void const *a, void const *b) { + /* function used by the qsort function */ + igraph_int_t pa = ((*((igraph_int_t*)a) - *((igraph_int_t*)b))); + if (pa < 0) { + return -1; + } else if (pa > 0) { + return 1; + } + return 0; +} + +static bool igraph_i_lad_compare(igraph_int_t size_mu, igraph_int_t* mu, igraph_int_t size_mv, igraph_int_t* mv) { + /* return true if for every element u of mu there exists + a different element v of mv such that u <= v; + return false otherwise */ + igraph_int_t i, j; + igraph_qsort(mu, (size_t) size_mu, sizeof(mu[0]), igraph_i_lad_qcompare); + igraph_qsort(mv, (size_t) size_mv, sizeof(mv[0]), igraph_i_lad_qcompare); + i = size_mv - 1; + for (j = size_mu - 1; j >= 0; j--) { + if (mu[j] > mv[i]) { + return false; + } + i--; + } + return true; +} + +static igraph_error_t igraph_i_lad_initDomains(bool initialDomains, + const igraph_vector_int_list_t *domains, Tdomain *D, + const Tgraph *Gp, const Tgraph *Gt, igraph_bool_t *empty) { + /* for every pattern node u, initialize D(u) with every vertex v + such that for every neighbor u' of u there exists a different + neighbor v' of v such that degree(u) <= degree(v) + if initialDomains, then filter initial domains wrt + compatibilities given in file + return false if a domain is empty and true otherwise */ + igraph_int_t *val; + igraph_bitset_t dom; + igraph_int_t *mu, *mv; + igraph_int_t matchingSize, u, v, i, j; + igraph_vector_int_t *vec; + + ALLOC_ARRAY(val, Gp->nbVertices * Gt->nbVertices, igraph_int_t); + IGRAPH_BITSET_INIT_FINALLY(&dom, Gt->nbVertices); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->globalMatchingP, Gp->nbVertices); + igraph_vector_int_fill(&D->globalMatchingP, -1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->globalMatchingT, Gt->nbVertices); + igraph_vector_int_fill(&D->globalMatchingT, -1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->nbVal, Gp->nbVertices); + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->firstVal, Gp->nbVertices); + IGRAPH_MATRIX_INT_INIT_FINALLY(&D->posInVal, + Gp->nbVertices, Gt->nbVertices); + IGRAPH_MATRIX_INT_INIT_FINALLY(&D->firstMatch, + Gp->nbVertices, Gt->nbVertices); + IGRAPH_BITSET_INIT_FINALLY(&D->markedToFilter, Gp->nbVertices); + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->toFilter, Gp->nbVertices); + + D->valSize = 0; + matchingSize = 0; + + for (u = 0; u < Gp->nbVertices; u++) { + igraph_vector_int_t *Gp_uneis = igraph_adjlist_get(&Gp->succ, u); + if (initialDomains) { + /* read the list of target vertices which are compatible with u */ + vec = igraph_vector_int_list_get_ptr(domains, u); + i = igraph_vector_int_size(vec); + igraph_bitset_null(&dom); + for (j = 0; j < i; j++) { + v = VECTOR(*vec)[j]; + IGRAPH_BIT_SET(dom, v); + } + } + IGRAPH_BIT_SET(D->markedToFilter, u); + VECTOR(D->toFilter)[u] = u; + VECTOR(D->nbVal)[u] = 0; + VECTOR(D->firstVal)[u] = D->valSize; + for (v = 0; v < Gt->nbVertices; v++) { + igraph_vector_int_t *Gt_vneis = igraph_adjlist_get(&Gt->succ, v); + if ((initialDomains) && (!IGRAPH_BIT_TEST(dom, v))) { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = VECTOR(D->firstVal)[u] + + Gt->nbVertices; + } else { + MATRIX(D->firstMatch, u, v) = matchingSize; + matchingSize += VECTOR(Gp->nbSucc)[u]; + if (VECTOR(Gp->nbSucc)[u] <= VECTOR(Gt->nbSucc)[v]) { + mu = IGRAPH_CALLOC(VECTOR(Gp->nbSucc)[u], igraph_int_t); + IGRAPH_CHECK_OOM(mu, "Insufficient memory for subisomorphism search with LAD."); + IGRAPH_FINALLY(igraph_free, mu); + + mv = IGRAPH_CALLOC(VECTOR(Gt->nbSucc)[v], igraph_int_t); + IGRAPH_CHECK_OOM(mu, "Insufficient memory for subisomorphism search with LAD."); + IGRAPH_FINALLY(igraph_free, mv); + + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + mu[i] = VECTOR(Gp->nbSucc)[VECTOR(*Gp_uneis)[i]]; + } + for (i = 0; i < VECTOR(Gt->nbSucc)[v]; i++) { + mv[i] = VECTOR(Gt->nbSucc)[VECTOR(*Gt_vneis)[i]]; + } + if (igraph_i_lad_compare(VECTOR(Gp->nbSucc)[u], mu, + VECTOR(Gt->nbSucc)[v], mv)) { + val[D->valSize] = v; + VECTOR(D->nbVal)[u]++; + MATRIX(D->posInVal, u, v) = D->valSize++; + } else { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = + VECTOR(D->firstVal)[u] + Gt->nbVertices; + } + IGRAPH_FREE(mu); + IGRAPH_FREE(mv); + IGRAPH_FINALLY_CLEAN(2); + } else { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = VECTOR(D->firstVal)[u] + Gt->nbVertices; + } + } + } + if (VECTOR(D->nbVal)[u] == 0) { + *empty = true; /* empty domain */ + + igraph_free(val); + igraph_bitset_destroy(&dom); + + /* On this branch, 'val' and 'matching' are unused. + * We init them anyway so that we can have a consistent destructor. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->val, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->matching, 0); + IGRAPH_FINALLY_CLEAN(12); + + return IGRAPH_SUCCESS; + } + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->val, D->valSize); + for (i = 0; i < D->valSize; i++) { + VECTOR(D->val)[i] = val[i]; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&D->matching, matchingSize); + igraph_vector_int_fill(&D->matching, -1); + + D->nextOutToFilter = 0; + D->lastInToFilter = Gp->nbVertices - 1; + + *empty = false; + + igraph_free(val); + igraph_bitset_destroy(&dom); + + IGRAPH_FINALLY_CLEAN(12); + + return IGRAPH_SUCCESS; +} + +static void igraph_i_lad_destroyDomains(Tdomain *D) { + igraph_vector_int_destroy(&D->globalMatchingP); + igraph_vector_int_destroy(&D->globalMatchingT); + igraph_vector_int_destroy(&D->nbVal); + igraph_vector_int_destroy(&D->firstVal); + igraph_matrix_int_destroy(&D->posInVal); + igraph_matrix_int_destroy(&D->firstMatch); + igraph_bitset_destroy(&D->markedToFilter); + igraph_vector_int_destroy(&D->toFilter); + + igraph_vector_int_destroy(&D->val); + igraph_vector_int_destroy(&D->matching); +} + + +/* ---------------------------------------------------------*/ +/* Coming from allDiff.c */ +/* ---------------------------------------------------------*/ + +#define white 0 +#define grey 1 +#define black 2 +#define toBeDeleted 3 +#define deleted 4 + +static void igraph_i_lad_addToDelete(igraph_int_t u, igraph_int_t* list, igraph_int_t* nb, igraph_int_t* marked) { + if (marked[u] < toBeDeleted) { + list[(*nb)++] = u; + marked[u] = toBeDeleted; + } +} + +static igraph_error_t igraph_i_lad_updateMatching(igraph_int_t sizeOfU, igraph_int_t sizeOfV, + igraph_vector_int_t *degree, + igraph_vector_int_t *firstAdj, + igraph_vector_int_t *adj, + igraph_vector_int_t * matchedWithU, + igraph_bool_t *invalid) { + /* input: + sizeOfU = number of vertices in U + sizeOfV = number of vertices in V + degree[u] = number of vertices of V which are adjacent to u + firstAdj[u] = pos in adj of the first vertex of V adjacent to u + adj[firstAdj[u]..firstAdj[u]+sizeOfU[u]-1] = vertices of V adjacent to u + + input/output: + matchedWithU[u] = vertex of V matched with u + + returns true if there exists a matching that covers U, i.e., if + for every u in 0..nbU-1, there exists a different v in 0..nb-1 + such that v is adjacent to u; returns false otherwise */ + + igraph_int_t *matchedWithV; /* matchedWithV[matchedWithU[u]]=u */ + igraph_int_t *nbPred; /* nbPred[i] = nb of predecessors of the ith + vertex of V in the DAG */ + igraph_int_t *pred; /* pred[i][j] = jth predecessor the ith + vertex of V in the DAG */ + igraph_int_t *nbSucc; /* nbSucc[i] = nb of successors of the ith + vertex of U in the DAG */ + igraph_int_t *succ; /* succ[i][j] = jth successor of the ith + vertex of U in the DAG */ + igraph_int_t *listV, *listU, *listDV, *listDU; + igraph_int_t nbV, nbU, nbDV, nbDU; + igraph_int_t i, j, k, stop, u, v; + igraph_int_t *markedV, *markedU; + /* markedX[i]=white if X[i] is not in the DAG + markedX[i]=grey if X[i] has been added to the DAG, but not its successors + markedX[i]=black if X[i] and its successors have been added to the DAG + markedX[i]=toBeDeleted if X[i] must be deleted from the DAG + markedX[i]=deleted if X[i] has been deleted from the DAG */ + igraph_int_t nbUnmatched = 0; /* number of vertices of U that are not matched */ + igraph_int_t *unmatched; /* vertices of U that are not matched */ + igraph_int_t *posInUnmatched; /* unmatched[posInUnmatched[u]]=u */ + igraph_vector_int_t path; + + if (sizeOfU > sizeOfV) { + *invalid = true; /* trivial case of infeasibility */ + return IGRAPH_SUCCESS; + } + + ALLOC_ARRAY(matchedWithV, sizeOfV, igraph_int_t); + ALLOC_ARRAY(nbPred, sizeOfV, igraph_int_t); + ALLOC_ARRAY(pred, sizeOfV * sizeOfU, igraph_int_t); + ALLOC_ARRAY(nbSucc, sizeOfU, igraph_int_t); + ALLOC_ARRAY(succ, sizeOfU * sizeOfV, igraph_int_t); + ALLOC_ARRAY(listV, sizeOfV, igraph_int_t); + ALLOC_ARRAY(listU, sizeOfU, igraph_int_t); + ALLOC_ARRAY(listDV, sizeOfV, igraph_int_t); + ALLOC_ARRAY(listDU, sizeOfU, igraph_int_t); + ALLOC_ARRAY(markedV, sizeOfV, igraph_int_t); + ALLOC_ARRAY(markedU, sizeOfU, igraph_int_t); + ALLOC_ARRAY(unmatched, sizeOfU, igraph_int_t); + ALLOC_ARRAY(posInUnmatched, sizeOfU, igraph_int_t); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&path, 0); + + /* initialize matchedWithV and unmatched */ + memset(matchedWithV, -1, (size_t)sizeOfV * sizeof(matchedWithV[0])); + for (u = 0; u < sizeOfU; u++) { + if (VECTOR(*matchedWithU)[u] >= 0) { + matchedWithV[VECTOR(*matchedWithU)[u]] = u; + } else { + posInUnmatched[u] = nbUnmatched; + unmatched[nbUnmatched++] = u; + } + } + /* try to match unmatched vertices of U with free vertices of V */ + j = 0; + while (j < nbUnmatched) { + u = unmatched[j]; + for (i = VECTOR(*firstAdj)[u]; + ((i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]) && + (matchedWithV[VECTOR(*adj)[i]] >= 0)); i++) { } + if (i == VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]) { + j++; /* no free vertex for u */ + } else { + v = VECTOR(*adj)[i]; /* v is free => match u with v */ + VECTOR(*matchedWithU)[u] = v; + matchedWithV[v] = u; + unmatched[j] = unmatched[--nbUnmatched]; + posInUnmatched[unmatched[j]] = j; + } + } + + while (nbUnmatched > 0) { + /* Try to increase the number of matched vertices */ + /* step 1 : build the DAG */ + memset(markedU, white, (size_t) sizeOfU * sizeof(markedU[0])); + memset(nbSucc, 0, (size_t) sizeOfU * sizeof(nbSucc[0])); + memset(markedV, white, (size_t) sizeOfV * sizeof(markedV[0])); + memset(nbPred, 0, (size_t) sizeOfV * sizeof(nbPred[0])); + /* first layer of the DAG from the free nodes of U */ + nbV = 0; + for (j = 0; j < nbUnmatched; j++) { + u = unmatched[j]; /* u is a free node of U */ + markedU[u] = black; + for (i = VECTOR(*firstAdj)[u]; + i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]; i++) { + v = VECTOR(*adj)[i]; /* add edge (u, v) to the DAG */ + pred[v * sizeOfU + (nbPred[v]++)] = u; + succ[u * sizeOfV + (nbSucc[u]++)] = v; + if (markedV[v] == white) { /* first time v is added to the DAG*/ + markedV[v] = grey; + listV[nbV++] = v; + } + } + } + stop = 0; + while ((stop == 0) && (nbV > 0)) { + /* build next layer from nodes of V to nodes of U */ + nbU = 0; + for (i = 0; i < nbV; i++) { + v = listV[i]; + markedV[v] = black; + u = matchedWithV[v]; + if (markedU[u] == white) { /* edge (v, u) belongs to the DAG */ + markedU[u] = grey; + listU[nbU++] = u; + } + } + /* build next layer from nodes of U to nodes of V */ + nbV = 0; + for (j = 0; j < nbU; j++) { + u = listU[j]; + markedU[u] = black; + for (i = VECTOR(*firstAdj)[u]; + i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]; i++) { + v = VECTOR(*adj)[i]; + if (markedV[v] != black) { /* add edge (u, v) to the DAG */ + pred[v * sizeOfU + (nbPred[v]++)] = u; + succ[u * sizeOfV + (nbSucc[u]++)] = v; + if (markedV[v] == white) { /* first time v is added to the DAG */ + markedV[v] = grey; + listV[nbV++] = v; + } + if (matchedWithV[v] == -1) { /* we have found a free node ! */ + stop = 1; + } + } + } + } + } + if (nbV == 0) { + *invalid = true; + /* I know it's ugly. */ + goto cleanup; + } + + /* step 2: look for augmenting paths */ + for (k = 0; k < nbV; k++) { + v = listV[k]; + if ((matchedWithV[v] == -1) && (nbPred[v] > 0)) { + /* v is the final node of an augmenting path */ + IGRAPH_CHECK(igraph_vector_int_resize(&path, 1)); + VECTOR(path)[0] = v; + nbDV = 0; + nbDU = 0; + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + do { + u = pred[v * sizeOfU + 0]; /* (u, v) belongs to the augmenting path */ + IGRAPH_CHECK(igraph_vector_int_push_back(&path, u)); + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + if (VECTOR(*matchedWithU)[u] != -1) { + /* u is not the initial node of the augmenting path */ + v = VECTOR(*matchedWithU)[u]; /* (v, u) belongs to the + augmenting path */ + IGRAPH_CHECK(igraph_vector_int_push_back(&path, v)); + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + } while (VECTOR(*matchedWithU)[u] != -1); + + /* delete nodes of listDV and listDU */ + while ((nbDV > 0) || (nbDU > 0)) { + while (nbDV > 0) { /* delete v */ + v = listDV[--nbDV]; markedV[v] = deleted; + u = matchedWithV[v]; + if (u != -1) { + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + } + for (i = 0; i < nbPred[v]; i++) { + u = pred[v * sizeOfU + i]; /* delete edge (u, v) */ + for (j = 0; ((j < nbSucc[u]) && (v != succ[u * sizeOfV + j])); j++) { } + succ[u * sizeOfV + j] = succ[u * sizeOfV + (--nbSucc[u])]; + if (nbSucc[u] == 0) { + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + } + } + } + while (nbDU > 0) { /* delete u */ + u = listDU[--nbDU]; markedU[u] = deleted; + v = VECTOR(*matchedWithU)[u]; + if (v != -1) { + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + for (i = 0; i < nbSucc[u]; i++) { /* delete edge (u, v) */ + v = succ[u * sizeOfV + i]; + for (j = 0; ((j < nbPred[v]) && (u != pred[v * sizeOfU + j])); j++) { } + pred[v * sizeOfU + j] = pred[v * sizeOfU + (--nbPred[v])]; + if (nbPred[v] == 0) { + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + } + } + } + /* Remove the last node of the augmenting path from the set of + unmatched vertices */ + u = VECTOR(path)[igraph_vector_int_size(&path) - 1]; + i = posInUnmatched[u]; + unmatched[i] = unmatched[--nbUnmatched]; + posInUnmatched[unmatched[i]] = i; + /* Update the matching wrt the augmenting path */ + while (igraph_vector_int_size(&path) > 1) { + u = igraph_vector_int_pop_back(&path); + v = igraph_vector_int_pop_back(&path); + VECTOR(*matchedWithU)[u] = v; + matchedWithV[v] = u; + } + } + } + } + *invalid = false; + +cleanup: + /* Free the allocated arrays */ + igraph_vector_int_destroy(&path); + igraph_free(posInUnmatched); + igraph_free(unmatched); + igraph_free(markedU); + igraph_free(markedV); + igraph_free(listDU); + igraph_free(listDV); + igraph_free(listU); + igraph_free(listV); + igraph_free(succ); + igraph_free(nbSucc); + igraph_free(pred); + igraph_free(nbPred); + igraph_free(matchedWithV); + IGRAPH_FINALLY_CLEAN(14); + return IGRAPH_SUCCESS; +} + +static void igraph_i_lad_DFS(igraph_int_t nbU, igraph_int_t nbV, igraph_int_t u, igraph_bitset_t *marked, igraph_int_t* nbSucc, + igraph_int_t* succ, igraph_vector_int_t * matchedWithU, + igraph_int_t* order, igraph_int_t* nb) { + /* perform a depth first search, starting from u, in the bipartite + graph Go=(U, V, E) such that + U = vertices of Gp + V = vertices of Gt + E = { (u, matchedWithU[u]) / u is a vertex of Gp } U + { (v, u) / v is a vertex of D[u] which is not matched to v} + + Given a vertex v of Gt, nbSucc[v]=number of successors of v and + succ[v]=list of successors of v. order[nb^out+1..nb^in] contains + the vertices discovered by the DFS */ + igraph_int_t i; + igraph_int_t v = VECTOR(*matchedWithU)[u]; /* the only one predecessor of v is u */ + IGRAPH_BIT_SET(*marked, u); + if (v >= 0) { + for (i = 0; i < nbSucc[v]; i++) { + if (!IGRAPH_BIT_TEST(*marked, succ[v * nbU + i])) { + igraph_i_lad_DFS(nbU, nbV, succ[v * nbU + i], marked, nbSucc, succ, + matchedWithU, order, nb); + } + } + } + /* we have finished with u => number it */ + order[*nb] = u; (*nb)--; +} + +static igraph_error_t igraph_i_lad_SCC(igraph_int_t nbU, igraph_int_t nbV, igraph_int_t* numV, igraph_int_t* numU, + igraph_int_t* nbSucc, igraph_int_t* succ, + igraph_int_t* nbPred, igraph_int_t* pred, + igraph_vector_int_t * matchedWithU, + igraph_vector_int_t * matchedWithV) { + /* postrelation: numV[v]==numU[u] iff they belong to the same + strongly connected component in the bipartite graph Go=(U, V, E) + such that + U = vertices of Gp + V = vertices of Gt + E = { (u, matchedWithU[u]) / u is a vertex of Gp } U + { (v, u) / v is a vertex of D[u] which is not matched to v} + + Given a vertex v of Gt, nbSucc[v]=number of sucessors of v and + succ[v]=list of successors of v */ + igraph_int_t *order; + igraph_bitset_t marked; + igraph_int_t *fifo; + igraph_int_t u, v, i, j, k, nbSCC, nb; + + /* Allocate memory */ + ALLOC_ARRAY(order, nbU, igraph_int_t); + IGRAPH_BITSET_INIT_FINALLY(&marked, nbU); + ALLOC_ARRAY(fifo, nbV, igraph_int_t); + + /* Order vertices of Gp wrt DFS */ + nb = nbU - 1; + for (u = 0; u < nbU; u++) { + if (!IGRAPH_BIT_TEST(marked, u)) { + igraph_i_lad_DFS(nbU, nbV, u, &marked, nbSucc, succ, matchedWithU, + order, &nb); + } + } + + /* traversal starting from order[0], then order[1], ... */ + nbSCC = 0; + memset(numU, -1, (size_t) nbU * sizeof(numU[0])); + memset(numV, -1, (size_t) nbV * sizeof(numV[0])); + for (i = 0; i < nbU; i++) { + u = order[i]; + v = VECTOR(*matchedWithU)[u]; + if (v == -1) { + continue; + } + if (numV[v] == -1) { /* v belongs to a new SCC */ + nbSCC++; + k = 1; fifo[0] = v; + numV[v] = nbSCC; + while (k > 0) { + v = fifo[--k]; + u = VECTOR(*matchedWithV)[v]; + if (u != -1) { + numU[u] = nbSCC; + for (j = 0; j < nbPred[u]; j++) { + v = pred[u * nbV + j]; + if (numV[v] == -1) { + numV[v] = nbSCC; + fifo[k++] = v; + } + } + } + } + } + } + + /* Free memory */ + igraph_free(fifo); + igraph_bitset_destroy(&marked); + igraph_free(order); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_lad_ensureGACallDiff(bool induced, Tgraph* Gp, Tgraph* Gt, + Tdomain* D, igraph_bool_t *invalid) { + /* precondition: D->globalMatchingP is an all different matching of + the pattern vertices + postcondition: filter domains wrt GAC(allDiff) + return false if an inconsistency is detected; true otherwise + + Build the bipartite directed graph Go=(U, V, E) such that + E = { (u, v) / u is a vertex of Gp which is matched to v (i.e., + v=D->globalMatchingP[u])} U + { (v, u) / v is a vertex of Gt which is in D(u) but is not + matched to u} */ + igraph_int_t *nbPred; /* nbPred[u] = nb of predecessors of u in Go */ + igraph_int_t *pred; /* pred[u][i] = ith + predecessor of u in Go */ + igraph_int_t *nbSucc; /* nbSucc[v] = nb of successors of v in Go */ + igraph_int_t *succ; /* succ[v][i] = ith + successor of v in Go */ + igraph_int_t u, v, i, w, oldNbVal, nbToMatch; + igraph_int_t *numV, *numU; + igraph_vector_int_t toMatch; + igraph_bitset_t used; + igraph_int_t *list; + igraph_int_t nb = 0; + bool result; + + /* Allocate memory */ + ALLOC_ARRAY(nbPred, Gp->nbVertices, igraph_int_t); + ALLOC_ARRAY(pred, Gp->nbVertices * Gt->nbVertices, igraph_int_t); + ALLOC_ARRAY(nbSucc, Gt->nbVertices, igraph_int_t); + ALLOC_ARRAY(succ, Gt->nbVertices * Gp->nbVertices, igraph_int_t); + ALLOC_ARRAY(numV, Gt->nbVertices, igraph_int_t); + ALLOC_ARRAY(numU, Gp->nbVertices, igraph_int_t); + IGRAPH_BITSET_INIT_FINALLY(&used, Gp->nbVertices * Gt->nbVertices); + ALLOC_ARRAY(list, Gt->nbVertices, igraph_int_t); + IGRAPH_VECTOR_INT_INIT_FINALLY(&toMatch, Gp->nbVertices); + + for (u = 0; u < Gp->nbVertices; u++) { + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + IGRAPH_BIT_CLEAR(used, u * Gt->nbVertices + v); + if (v != VECTOR(D->globalMatchingP)[u]) { + pred[u * Gt->nbVertices + (nbPred[u]++)] = v; + succ[v * Gp->nbVertices + (nbSucc[v]++)] = u; + } + } + } + + /* mark as used all edges of paths starting from free vertices */ + for (v = 0; v < Gt->nbVertices; v++) { + if (VECTOR(D->globalMatchingT)[v] < 0) { /* v is free */ + list[nb++] = v; + numV[v] = true; + } + } + while (nb > 0) { + v = list[--nb]; + for (i = 0; i < nbSucc[v]; i++) { + u = succ[v * Gp->nbVertices + i]; + IGRAPH_BIT_SET(used, u * Gt->nbVertices + v); + if (numU[u] == false) { + numU[u] = true; + w = VECTOR(D->globalMatchingP)[u]; + IGRAPH_BIT_SET(used, u * Gt->nbVertices + w); + if (numV[w] == false) { + list[nb++] = w; + numV[w] = true; + } + } + } + } + + /* look for strongly connected components in Go */ + IGRAPH_CHECK( + igraph_i_lad_SCC(Gp->nbVertices, Gt->nbVertices, numV, numU, + nbSucc, succ, nbPred, pred, &D->globalMatchingP, &D->globalMatchingT)); + + /* remove v from D[u] if (u, v) is not marked as used + and u and v are not in the same SCC + and D->globalMatchingP[u] != v */ + nbToMatch = 0; + for (u = 0; u < Gp->nbVertices; u++) { + oldNbVal = VECTOR(D->nbVal)[u]; + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + if ((!IGRAPH_BIT_TEST(used, u * Gt->nbVertices + v)) && (numV[v] != numU[u]) && + (VECTOR(D->globalMatchingP)[u] != v)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u, v, D, Gp, Gt, &result)); + if (!result) { + *invalid = true; + /* Yes, this is ugly. */ + goto cleanup; + } + } + } + if (VECTOR(D->nbVal)[u] == 0) { + *invalid = true; + /* Yes, this is ugly. */ + goto cleanup; + } + if ((oldNbVal > 1) && (VECTOR(D->nbVal)[u] == 1)) { + VECTOR(toMatch)[nbToMatch++] = u; + } + } + IGRAPH_CHECK(igraph_i_lad_matchVertices(nbToMatch, &toMatch, induced, + D, Gp, Gt, invalid)); + +cleanup: + igraph_vector_int_destroy(&toMatch); + igraph_free(list); + igraph_bitset_destroy(&used); + igraph_free(numU); + igraph_free(numV); + igraph_free(succ); + igraph_free(nbSucc); + igraph_free(pred); + igraph_free(nbPred); + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} + +/* ---------------------------------------------------------*/ +/* Coming from lad.c */ +/* ---------------------------------------------------------*/ + +static igraph_error_t igraph_i_lad_checkLAD(igraph_int_t u, igraph_int_t v, Tdomain* D, Tgraph* Gp, Tgraph* Gt, + bool *result) { + /* return true if G_(u, v) has a adj(u)-covering matching; false + otherwise */ + igraph_int_t u2, v2, i, j; + igraph_int_t nbMatched = 0; + igraph_vector_int_t *Gp_uneis = igraph_adjlist_get(&Gp->succ, u); + + igraph_int_t *num, *numInv; + igraph_vector_int_t nbComp; + igraph_vector_int_t firstComp; + igraph_vector_int_t comp; + igraph_int_t nbNum = 0; + igraph_int_t posInComp = 0; + igraph_vector_int_t matchedWithU; + igraph_bool_t invalid; + + /* special case when u has only 1 adjacent node => no need to call + Hopcroft and Karp */ + if (VECTOR(Gp->nbSucc)[u] == 1) { + u2 = VECTOR(*Gp_uneis)[0]; /* u2 is the only node adjacent to u */ + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) ]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + *result = true; + return IGRAPH_SUCCESS; + } + /* look for a support of edge (u, u2) for v */ + for (i = VECTOR(D->firstVal)[u2]; + i < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]; i++) { + if (ISEDGE(Gt, v, VECTOR(D->val)[i])) { + VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) ] = + VECTOR(D->val)[i]; + *result = true; + return IGRAPH_SUCCESS; + } + } + *result = false; + return IGRAPH_SUCCESS; + } + + /* general case (when u has more than 1 adjacent node) */ + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + /* remove from the matching of G_(u, v) edges which no longer + belong to G_(u, v) */ + u2 = VECTOR(*Gp_uneis)[i]; + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + nbMatched++; + } + } + if (nbMatched == VECTOR(Gp->nbSucc)[u]) { + *result = true; + return IGRAPH_SUCCESS; + } /* The matching still covers adj(u) */ + + /* Allocate memory */ + ALLOC_ARRAY(num, Gt->nbVertices, igraph_int_t); + ALLOC_ARRAY(numInv, Gt->nbVertices, igraph_int_t); + + /* Build the bipartite graph + let U be the set of nodes adjacent to u + let V be the set of nodes that are adjacent to v, and that belong + to domains of nodes of U */ + /* nbComp[u]=number of elements of V that are compatible with u */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&nbComp, VECTOR(Gp->nbSucc)[u]); + IGRAPH_VECTOR_INT_INIT_FINALLY(&firstComp, VECTOR(Gp->nbSucc)[u]); + /* comp[firstComp[u]..firstComp[u]+nbComp[u]-1] = nodes of Gt that + are compatible with u */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&comp, (VECTOR(Gp->nbSucc)[u] * Gt->nbVertices)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&matchedWithU, VECTOR(Gp->nbSucc)[u]); + memset(num, -1, (size_t) (Gt->nbVertices) * sizeof(num[0])); + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + u2 = VECTOR(*Gp_uneis)[i]; /* u2 is adjacent to u */ + /* search for all nodes v2 in D[u2] which are adjacent to v */ + VECTOR(nbComp)[i] = 0; + VECTOR(firstComp)[i] = posInComp; + if (VECTOR(D->nbVal)[u2] > VECTOR(Gt->nbSucc)[v]) { + for (j = VECTOR(D->firstVal)[u2]; + j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]; j++) { + v2 = VECTOR(D->val)[j]; /* v2 belongs to D[u2] */ + if (ISEDGE(Gt, v, v2)) { /* v2 is a successor of v */ + if (num[v2] < 0) { /* v2 has not yet been added to V */ + num[v2] = nbNum; + numInv[nbNum++] = v2; + } + VECTOR(comp)[posInComp++] = num[v2]; + VECTOR(nbComp)[i]++; + } + } + } else { + igraph_vector_int_t *Gt_vneis = igraph_adjlist_get(&Gt->succ, v); + for (j = 0; j < VECTOR(Gt->nbSucc)[v]; j++) { + v2 = VECTOR(*Gt_vneis)[j]; /* v2 is a successor of v */ + if (igraph_i_lad_isInD(u2, v2, D)) { /* v2 belongs to D[u2] */ + if (num[v2] < 0) { /* v2 has not yet been added to V */ + num[v2] = nbNum; + numInv[nbNum++] = v2; + } + VECTOR(comp)[posInComp++] = num[v2]; + VECTOR(nbComp)[i]++; + } + } + } + if (VECTOR(nbComp)[i] == 0) { + *result = false; /* u2 has no compatible vertex in succ[v] */ + goto cleanup; + } + /* u2 is matched to v2 in the matching that supports (u, v) */ + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + VECTOR(matchedWithU)[i] = num[v2]; + } else { + VECTOR(matchedWithU)[i] = -1; + } + } + /* Call Hopcroft Karp to update the matching */ + IGRAPH_CHECK( + igraph_i_lad_updateMatching(VECTOR(Gp->nbSucc)[u], nbNum, &nbComp, + &firstComp, &comp, &matchedWithU, &invalid) + ); + if (invalid) { + *result = false; + goto cleanup; + } + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i] = + numInv[ VECTOR(matchedWithU)[i] ]; + } + *result = true; + +cleanup: + igraph_free(numInv); + igraph_free(num); + igraph_vector_int_destroy(&matchedWithU); + igraph_vector_int_destroy(&comp); + igraph_vector_int_destroy(&firstComp); + igraph_vector_int_destroy(&nbComp); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/* ---------------------------------------------------------*/ +/* Coming from main.c */ +/* ---------------------------------------------------------*/ + +static igraph_error_t igraph_i_lad_filter(bool induced, Tdomain* D, Tgraph* Gp, Tgraph* Gt, + bool *result) { + /* filter domains of all vertices in D->toFilter wrt LAD and ensure + GAC(allDiff) + return false if some domain becomes empty; true otherwise */ + igraph_int_t u, v, i, oldNbVal; + igraph_bool_t invalid; + bool result2; + while (!igraph_i_lad_toFilterEmpty(D)) { + while (!igraph_i_lad_toFilterEmpty(D)) { + u = igraph_i_lad_nextToFilter(D, Gp->nbVertices); + oldNbVal = VECTOR(D->nbVal)[u]; + i = VECTOR(D->firstVal)[u]; + while (i < VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]) { + /* for every target node v in D(u), check if G_(u, v) has a + covering matching */ + v = VECTOR(D->val)[i]; + IGRAPH_CHECK(igraph_i_lad_checkLAD(u, v, D, Gp, Gt, &result2)); + if (result2) { + i++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u, v, D, Gp, Gt, &result2)); + if (!result2) { + *result = false; + return IGRAPH_SUCCESS; + } + } + } + if ((VECTOR(D->nbVal)[u] == 1) && (oldNbVal > 1) && + (!igraph_i_lad_matchVertex(u, induced, D, Gp, Gt))) { + *result = false; + return IGRAPH_SUCCESS; + } + if (VECTOR(D->nbVal)[u] == 0) { + *result = false; + return IGRAPH_SUCCESS; + } + } + igraph_i_lad_ensureGACallDiff(induced, Gp, Gt, D, &invalid); + if (invalid) { + *result = false; + return IGRAPH_SUCCESS; + } + } + *result = true; + return IGRAPH_SUCCESS; +} + + + +static igraph_error_t igraph_i_lad_solve(bool firstSol, bool induced, + Tdomain* D, Tgraph* Gp, Tgraph* Gt, + igraph_bool_t *invalid, igraph_bool_t *iso, + igraph_vector_int_t *vec, igraph_vector_int_t *map, igraph_vector_int_list_t *maps, + igraph_int_t *nbNodes, igraph_int_t *nbFail, igraph_int_t *nbSol, + clock_t *begin, igraph_vector_ptr_t *alloc_history) { + /* if firstSol then search for the first solution; otherwise search + for all solutions if induced then search for induced subgraphs; + otherwise search for partial subgraphs + return false if CPU time limit exceeded before the search is + completed, return true otherwise */ + + igraph_int_t u, v, minDom, i; + igraph_int_t* nbVal; + igraph_int_t* globalMatching; + igraph_int_t* val; + bool result; + + (*nbNodes)++; + + /* Allocate memory */ + ALLOC_ARRAY_IN_HISTORY(nbVal, Gp->nbVertices, igraph_int_t, alloc_history); + ALLOC_ARRAY_IN_HISTORY(globalMatching, Gp->nbVertices, igraph_int_t, alloc_history); + + IGRAPH_CHECK(igraph_i_lad_filter(induced, D, Gp, Gt, &result)); + if (!result) { + /* filtering has detected an inconsistency */ + (*nbFail)++; + igraph_i_lad_resetToFilter(D); + *invalid = false; + goto cleanup; + } + + /* The current node of the search tree is consistent wrt to LAD and + GAC(allDiff) Save domain sizes and global all different matching + and search for the non matched vertex minDom with smallest domain */ + minDom = -1; + for (u = 0; u < Gp->nbVertices; u++) { + nbVal[u] = VECTOR(D->nbVal)[u]; + if ((nbVal[u] > 1) && ((minDom < 0) || (nbVal[u] < nbVal[minDom]))) { + minDom = u; + } + globalMatching[u] = VECTOR(D->globalMatchingP)[u]; + } + + if (minDom == -1) { + /* All vertices are matched => Solution found */ + if (iso) { + *iso = true; + } + (*nbSol)++; + if (map && igraph_vector_int_size(map) == 0) { + IGRAPH_CHECK(igraph_vector_int_resize(map, Gp->nbVertices)); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(*map)[u] = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + } + } + if (maps) { + IGRAPH_CHECK(igraph_vector_int_resize(vec, Gp->nbVertices)); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(*vec)[u] = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + } + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(maps, vec)); + } + igraph_i_lad_resetToFilter(D); + *invalid = false; + goto cleanup; + } + + /* save the domain of minDom to iterate on its values */ + ALLOC_ARRAY_IN_HISTORY(val, VECTOR(D->nbVal)[minDom], igraph_int_t, alloc_history); + for (i = 0; i < VECTOR(D->nbVal)[minDom]; i++) { + val[i] = VECTOR(D->val)[ VECTOR(D->firstVal)[minDom] + i ]; + } + + /* branch on minDom=v, for every target node v in D(u) */ + for (i = 0; ((i < nbVal[minDom]) && ((firstSol == 0) || (*nbSol == 0))); i++) { + IGRAPH_ALLOW_INTERRUPTION(); + v = val[i]; + IGRAPH_CHECK(igraph_i_lad_removeAllValuesButOne(minDom, v, D, Gp, Gt, &result)); + if (!result || (!igraph_i_lad_matchVertex(minDom, induced, D, Gp, Gt))) { + (*nbFail)++; + (*nbNodes)++; + igraph_i_lad_resetToFilter(D); + } else { + IGRAPH_CHECK(igraph_i_lad_solve(firstSol, induced, + D, Gp, Gt, invalid, iso, vec, map, maps, + nbNodes, nbFail, nbSol, begin, + alloc_history)); + } + /* restore domain sizes and global all different matching */ + igraph_vector_int_fill(&D->globalMatchingT, -1); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(D->nbVal)[u] = nbVal[u]; + VECTOR(D->globalMatchingP)[u] = globalMatching[u]; + VECTOR(D->globalMatchingT)[globalMatching[u]] = u; + } + } + *invalid = false; + + igraph_free(val); + igraph_vector_ptr_pop_back(alloc_history); + +cleanup: + igraph_free(globalMatching); + igraph_vector_ptr_pop_back(alloc_history); + igraph_free(nbVal); + igraph_vector_ptr_pop_back(alloc_history); + + return IGRAPH_SUCCESS; +} + +/** + * \section about_lad + * + * + * The LAD algorithm can search for a subgraph in a larger graph, or check + * if two graphs are isomorphic. + * See Christine Solnon: AllDifferent-based Filtering for Subgraph + * Isomorphism. Artificial Intelligence, 174(12-13):850-864, 2010. + * https://doi.org/10.1016/j.artint.2010.05.002 + * as well as the homepage of the LAD library at http://liris.cnrs.fr/csolnon/LAD.html + * The implementation in igraph is based on LADv1, but it is + * modified to use igraph's own memory allocation and error handling. + * + * + * + * LAD uses the concept of domains to indicate vertex compatibility when matching the + * pattern graph. Domains can be used to implement matching of colored vertices. + * + * + * + * LAD works with both directed and undirected graphs. Graphs with multi-edges are not supported. + * + */ + +/** + * \function igraph_subisomorphic_lad + * Check subgraph isomorphism with the LAD algorithm + * + * Check whether \p pattern is isomorphic to a subgraph os \p target. + * The original LAD implementation by Christine Solnon was used as the + * basis of this code. + * + * + * See more about LAD at http://liris.cnrs.fr/csolnon/LAD.html and in + * Christine Solnon: AllDifferent-based Filtering for Subgraph + * Isomorphism. Artificial Intelligence, 174(12-13):850-864, 2010. + * https://doi.org/10.1016/j.artint.2010.05.002 + * + * \param pattern The smaller graph, it can be directed or undirected. + * \param target The bigger graph, it can be directed or undirected. + * \param domains An integer vector list of \c NULL. The length of each + * vector must match the number of vertices in the \p pattern graph. + * For each vertex, the IDs of the compatible vertices in the target + * graph are listed. + * \param iso Pointer to a boolean, or a null pointer. If not a null + * pointer, then the boolean is set to \c true if a subgraph + * isomorphism is found, and to \c false otherwise. + * \param map Pointer to a vector or a null pointer. If not a null + * pointer and a subgraph isomorphism is found, the matching + * vertices from the target graph are listed here, for each vertex + * (in vertex ID order) from the pattern graph. + * \param maps Pointer to a list of integer vectors or a null pointer. If not + * a null pointer, then all subgraph isomorphisms are stored in the + * vector list, in \ref igraph_vector_int_t objects. + * \param induced Boolean, whether to search for induced matching + * subgraphs. + * \param time_limit Processor time limit in seconds. Supply zero + * here for no limit. If the time limit is over, then the function + * signals an error. + * \return Error code + * + * \sa \ref igraph_subisomorphic_vf2() for the VF2 algorithm. + * + * Time complexity: exponential. + * + * \example examples/simple/igraph_subisomorphic_lad.c + */ + +igraph_error_t igraph_subisomorphic_lad(const igraph_t *pattern, const igraph_t *target, + const igraph_vector_int_list_t *domains, + igraph_bool_t *iso, igraph_vector_int_t *map, + igraph_vector_int_list_t *maps, + igraph_bool_t induced) { + + bool firstSol = maps == NULL; + bool initialDomains = domains != NULL; + Tgraph Gp, Gt; + Tdomain D; + igraph_bool_t invalidDomain; + igraph_int_t u, nbToMatch = 0; + igraph_vector_int_t toMatch; + /* Helper vector in which we build the current subisomorphism mapping */ + igraph_vector_int_t vec; + /* Number of nodes in the search tree */ + igraph_int_t nbNodes = 0; + /* number of failed nodes in the search tree */ + igraph_int_t nbFail = 0; + /* number of solutions found */ + igraph_int_t nbSol = 0; + /* reusable structure to get CPU time usage */ + clock_t begin = clock(); + /* Stack to store memory blocks that are allocated during igraph_i_lad_solve */ + igraph_vector_ptr_t alloc_history; + + if (!iso && !map && !maps) { + IGRAPH_ERROR("Please specify at least one of `iso', `map' or `maps'", + IGRAPH_EINVAL); + } + + if (igraph_is_directed(pattern) != igraph_is_directed(target)) { + IGRAPH_ERROR("Cannot search for a directed pattern in an undirected target " + "or vice versa", IGRAPH_EINVAL); + } + + if (iso) { + *iso = (igraph_vcount(pattern) == 0); + } + if (map) { + igraph_vector_int_clear(map); + } + if (maps) { + igraph_vector_int_list_clear(maps); + } + + if (igraph_vcount(pattern) == 0) { + /* Special case for null patterns */ + if (maps) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(maps, NULL)); + } + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec, 0); + + IGRAPH_CHECK(igraph_i_lad_createGraph(pattern, &Gp)); + IGRAPH_FINALLY(igraph_i_lad_destroyGraph, &Gp); + + IGRAPH_CHECK(igraph_i_lad_createGraph(target, &Gt)); + IGRAPH_FINALLY(igraph_i_lad_destroyGraph, &Gt); + + if (Gp.nbVertices > Gt.nbVertices) { + goto exit3; + } + + IGRAPH_CHECK(igraph_i_lad_initDomains(initialDomains, domains, &D, &Gp, &Gt, &invalidDomain)); + IGRAPH_FINALLY(igraph_i_lad_destroyDomains, &D); + + if (invalidDomain) { + goto exit2; + } + + IGRAPH_CHECK(igraph_i_lad_updateMatching(Gp.nbVertices, + Gt.nbVertices, + &D.nbVal, &D.firstVal, &D.val, + &D.globalMatchingP, + &invalidDomain)); + if (invalidDomain) { + goto exit; + } + + IGRAPH_CHECK(igraph_i_lad_ensureGACallDiff((bool) induced, &Gp, &Gt, &D, + &invalidDomain)); + if (invalidDomain) { + goto exit; + } + + for (u = 0; u < Gp.nbVertices; u++) { + VECTOR(D.globalMatchingT)[ VECTOR(D.globalMatchingP)[u] ] = u; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&toMatch, Gp.nbVertices); + + for (u = 0; u < Gp.nbVertices; u++) { + if (VECTOR(D.nbVal)[u] == 1) { + VECTOR(toMatch)[nbToMatch++] = u; + } + } + IGRAPH_CHECK(igraph_i_lad_matchVertices(nbToMatch, &toMatch, (bool) induced, + &D, &Gp, &Gt, &invalidDomain)); + igraph_vector_int_destroy(&toMatch); + IGRAPH_FINALLY_CLEAN(1); + if (invalidDomain) { + goto exit; + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&alloc_history, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &alloc_history); + + IGRAPH_CHECK(igraph_i_lad_solve(firstSol, (bool) induced, &D, + &Gp, &Gt, &invalidDomain, iso, &vec, map, maps, + &nbNodes, &nbFail, &nbSol, &begin, + &alloc_history)); + + igraph_vector_ptr_destroy_all(&alloc_history); + IGRAPH_FINALLY_CLEAN(1); + +exit: +exit2: + + igraph_i_lad_destroyDomains(&D); + IGRAPH_FINALLY_CLEAN(1); + +exit3: + + igraph_i_lad_destroyGraph(&Gt); + igraph_i_lad_destroyGraph(&Gp); + igraph_vector_int_destroy(&vec); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/isomorphism/queries.c b/src/isomorphism/queries.c new file mode 100644 index 0000000..c632960 --- /dev/null +++ b/src/isomorphism/queries.c @@ -0,0 +1,215 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_isomorphism.h" + +#include "igraph_interface.h" +#include "igraph_structural.h" + +/** + * \section about_graph_isomorphism + * + * igraph provides four set of functions to deal with graph + * isomorphism problems. + * + * The \ref igraph_isomorphic() and \ref igraph_subisomorphic() + * functions make up the first set (in addition with the \ref + * igraph_permute_vertices() function). These functions choose the + * algorithm which is best for the supplied input graph. (The choice is + * not very sophisticated though, see their documentation for + * details.) + * + * The VF2 graph (and subgraph) isomorphism algorithm is implemented in + * igraph, these functions are the second set. See \ref + * igraph_isomorphic_vf2() and \ref igraph_subisomorphic_vf2() for + * starters. + * + * Functions for the Bliss algorithm constitute the third set, + * see \ref igraph_isomorphic_bliss(). + * + * Finally, the isomorphism classes of all directed graphs with three and + * four vertices and all undirected graphs with 3-6 vertices are precomputed + * and stored in igraph, so for these small graphs there is a separate fast + * path in the code that does not use more complex, generic isomorphism + * algorithms. + */ + +static igraph_error_t igraph_i_isomorphic_small( + const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *iso +); + +/** + * \function igraph_isomorphic + * \brief Are two graphs isomorphic? + * + * In simple terms, two graphs are isomorphic if they become indistinguishable + * from each other once their vertex labels are removed (rendering the vertices + * within each graph indistiguishable). More precisely, two graphs are isomorphic + * if there is a one-to-one mapping from the vertices of the first one + * to the vertices of the second such that it transforms the edge set of the + * first graph into the edge set of the second. This mapping is called + * an \em isomorphism. + * + * This function decides which graph isomorphism algorithm to be + * used based on the input graphs. Right now it does the following: + * \olist + * \oli If one graph is directed and the other undirected then an + * error is triggered. + * \oli If one of the graphs has multi-edges then both graphs are + * simplified and colorized using \ref igraph_simplify_and_colorize() and sent to VF2. + * \oli If the two graphs does not have the same number of vertices + * and edges it returns with \c false. + * \oli Otherwise, if the \ref igraph_isoclass() function supports both + * graphs (which is true for directed graphs with 3 and 4 vertices, and + * undirected graphs with 3-6 vertices), an O(1) algorithm is used with + * precomputed data. + * \oli Otherwise Bliss is used, see \ref igraph_isomorphic_bliss(). + * \endolist + * + * Please call the VF2 and Bliss functions directly if you need + * something more sophisticated, e.g. you need the isomorphic mapping. + * + * \param graph1 The first graph. + * \param graph2 The second graph. + * \param iso Pointer to a Boolean variable, will be set to \c true + * if the two graphs are isomorphic, and \c false otherwise. + * \return Error code. + * \sa \ref igraph_isoclass(), \ref igraph_isoclass_subgraph(), + * \ref igraph_isoclass_create(). + * + * Time complexity: exponential. + */ +igraph_error_t igraph_isomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso) { + + igraph_int_t nodes1 = igraph_vcount(graph1), nodes2 = igraph_vcount(graph2); + igraph_int_t edges1 = igraph_ecount(graph1), edges2 = igraph_ecount(graph2); + igraph_bool_t dir1 = igraph_is_directed(graph1), dir2 = igraph_is_directed(graph2); + igraph_bool_t loop1, loop2, multi1, multi2; + + if (dir1 != dir2) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs for isomorphism.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_has_multiple(graph1, &multi1)); + IGRAPH_CHECK(igraph_has_multiple(graph2, &multi2)); + + if (multi1 || multi2) { + igraph_t r1; + igraph_t r2; + igraph_vector_int_t vc1; + igraph_vector_int_t vc2; + igraph_vector_int_t ec1; + igraph_vector_int_t ec2; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vc1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vc2, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ec1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ec2, 0); + IGRAPH_CHECK(igraph_simplify_and_colorize(graph1, &r1, &vc1, &ec1)); + IGRAPH_FINALLY(igraph_destroy, &r1); + IGRAPH_CHECK(igraph_simplify_and_colorize(graph2, &r2, &vc2, &ec2)); + IGRAPH_FINALLY(igraph_destroy, &r2); + IGRAPH_CHECK(igraph_isomorphic_vf2(&r1, &r2, &vc1, &vc2, &ec1, &ec2, iso, + NULL, NULL, NULL, NULL, NULL)); + igraph_destroy(&r2); + igraph_destroy(&r1); + igraph_vector_int_destroy(&ec2); + igraph_vector_int_destroy(&ec1); + igraph_vector_int_destroy(&vc2); + igraph_vector_int_destroy(&vc1); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; + } + + if (nodes1 != nodes2 || edges1 != edges2) { + *iso = false; + } else if (nodes1 >= 3 && nodes1 <= (dir1 ? 4 : 6)) { + IGRAPH_CHECK(igraph_has_loop(graph1, &loop1)); + IGRAPH_CHECK(igraph_has_loop(graph2, &loop2)); + if (!loop1 && !loop2) { + IGRAPH_CHECK(igraph_i_isomorphic_small(graph1, graph2, iso)); + } else { + IGRAPH_CHECK(igraph_isomorphic_bliss(graph1, graph2, NULL, NULL, iso, + NULL, NULL, /*sh=*/ IGRAPH_BLISS_FL, NULL, NULL)); + } + } else { + IGRAPH_CHECK(igraph_isomorphic_bliss(graph1, graph2, NULL, NULL, iso, + NULL, NULL, /*sh=*/ IGRAPH_BLISS_FL, NULL, NULL)); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_i_isomorphic_small + * \brief Graph isomorphism for small graphs. + * + * This function uses precomputed indices to decide isomorphism + * problems for directed graphs with only 3 or 4 vertices, or for undirected + * graphs with 3, 4, 5 or 6 vertices. Multi-edges and self-loops are ignored by + * this function. + * + * \param graph1 The first input graph. + * \param graph2 The second input graph. Must have the same + * directedness as \p graph1. + * \param iso Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_i_isomorphic_small( + const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *iso +) { + igraph_int_t class1, class2; + IGRAPH_CHECK(igraph_isoclass(graph1, &class1)); + IGRAPH_CHECK(igraph_isoclass(graph2, &class2)); + *iso = (class1 == class2); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_subisomorphic + * \brief Decide subgraph isomorphism. + * + * Check whether \p graph2 is isomorphic to a subgraph of \p graph1. + * Currently this function just calls \ref igraph_subisomorphic_vf2() + * for all graphs. + * + * + * Currently this function does not support non-simple graphs. + * + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the bigger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph2, or an error is triggered. This is + * supposed to be the smaller graph. + * \param iso Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: exponential. + */ +igraph_error_t igraph_subisomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso) { + + return igraph_subisomorphic_vf2(graph1, graph2, NULL, NULL, NULL, NULL, iso, NULL, NULL, NULL, NULL, NULL); +} diff --git a/src/isomorphism/vf2.c b/src/isomorphism/vf2.c new file mode 100644 index 0000000..7f3075f --- /dev/null +++ b/src/isomorphism/vf2.c @@ -0,0 +1,1741 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_isomorphism.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_stack.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +/** + * \section about_vf2 + * + * + * The VF2 algorithm can search for a subgraph in a larger graph, or check if two + * graphs are isomorphic. See P. Foggia, C. Sansone, M. Vento, An Improved algorithm for + * matching large graphs, Proc. of the 3rd IAPR-TC-15 International + * Workshop on Graph-based Representations, Italy, 2001. + * + * + * + * VF2 supports both vertex and edge-colored graphs, as well as custom vertex or edge + * compatibility functions. + * + * + * + * VF2 works with both directed and undirected graphs. Only simple graphs are supported. + * Self-loops or multi-edges must not be present in the graphs. Currently, the VF2 + * functions do not check that the input graph is simple: it is the responsibility + * of the user to pass in valid input. + * + */ + +static igraph_error_t igraph_i_perform_vf2_pre_checks( + const igraph_t* graph1, const igraph_t* graph2 +) { + igraph_bool_t has_loops; + + if (igraph_is_directed(graph1) != igraph_is_directed(graph2)) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_has_loop(graph1, &has_loops)); + if (!has_loops) { + IGRAPH_CHECK(igraph_has_loop(graph2, &has_loops)); + } + + if (has_loops) { + IGRAPH_ERROR("The VF2 algorithm does not support graphs with loop edges.", + IGRAPH_EINVAL); + } + + /* TODO: VF2 does not support graphs with multiple edges either, but we + * don't check for this as the check would be complex, comparable to + * the runtime of the algorithm itself */ + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_isomorphisms_vf2_callback + * The generic VF2 interface + * + * + * This function is an implementation of the VF2 isomorphism algorithm, + * see P. Foggia, C. Sansone, M. Vento, An Improved algorithm for + * matching large graphs, Proc. of the 3rd IAPR-TC-15 International + * Workshop on Graph-based Representations, Italy, 2001. + * + * For using it you need to define a callback function of type + * \ref igraph_isohandler_t. This function will be called whenever VF2 + * finds an isomorphism between the two graphs. The mapping between + * the two graphs will be also provided to this function. If the + * callback returns \c IGRAPH_SUCCESS, then the search is continued, + * otherwise it stops. \c IGRAPH_STOP as a return value can be used to + * indicate normal premature termination; any other return value will be + * treated as an igraph error code, making the caller function return the + * same error code as well. The callback function must not destroy the + * mapping vectors that are passed to it. + * \param graph1 The first input graph. + * \param graph2 The second input graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param map12 Pointer to an initialized vector or \c NULL. If not \c + * NULL and the supplied graphs are isomorphic then the permutation + * taking \p graph1 to \p graph is stored here. If not \c NULL and the + * graphs are not isomorphic then a zero-length vector is returned. + * \param map21 This is the same as \p map12, but for the permutation + * taking \p graph2 to \p graph1. + * \param isohandler_fn The callback function to be called if an + * isomorphism is found. See also \ref igraph_isohandler_t. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p isohandler_fn, \p + * node_compat_fn and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_get_isomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph1); + igraph_int_t no_of_edges = igraph_ecount(graph1); + igraph_vector_int_t mycore_1, mycore_2, *core_1 = &mycore_1, *core_2 = &mycore_2; + igraph_vector_int_t in_1, in_2, out_1, out_2; + igraph_int_t in_1_size = 0, in_2_size = 0, out_1_size = 0, out_2_size = 0; + igraph_vector_int_t *inneis_1, *inneis_2, *outneis_1, *outneis_2; + igraph_int_t matched_nodes = 0; + igraph_int_t depth; + igraph_int_t cand1, cand2; + igraph_int_t last1, last2; + igraph_stack_int_t path; + igraph_lazy_adjlist_t inadj1, inadj2, outadj1, outadj2; + igraph_vector_int_t indeg1, indeg2, outdeg1, outdeg2; + igraph_int_t vsize; + + IGRAPH_CHECK(igraph_i_perform_vf2_pre_checks(graph1, graph2)); + + if ( (vertex_color1 && !vertex_color2) || (!vertex_color1 && vertex_color2) ) { + IGRAPH_WARNING("Only one graph is vertex-colored, vertex colors will be ignored"); + vertex_color1 = vertex_color2 = 0; + } + + if ( (edge_color1 && !edge_color2) || (!edge_color1 && edge_color2)) { + IGRAPH_WARNING("Only one graph is edge-colored, edge colors will be ignored"); + edge_color1 = edge_color2 = 0; + } + + if (no_of_nodes != igraph_vcount(graph2) || + no_of_edges != igraph_ecount(graph2)) { + return IGRAPH_SUCCESS; + } + + if (vertex_color1) { + if (igraph_vector_int_size(vertex_color1) != no_of_nodes || + igraph_vector_int_size(vertex_color2) != no_of_nodes) { + IGRAPH_ERROR("Invalid vertex color vector length", IGRAPH_EINVAL); + } + } + + if (edge_color1) { + if (igraph_vector_int_size(edge_color1) != no_of_edges || + igraph_vector_int_size(edge_color2) != no_of_edges) { + IGRAPH_ERROR("Invalid edge color vector length", IGRAPH_EINVAL); + } + } + + /* Check color distribution */ + if (vertex_color1) { + igraph_bool_t ret = false; + igraph_vector_int_t tmp1, tmp2; + IGRAPH_CHECK(igraph_vector_int_init_copy(&tmp1, vertex_color1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp1); + IGRAPH_CHECK(igraph_vector_int_init_copy(&tmp2, vertex_color2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp2); + igraph_vector_int_sort(&tmp1); + igraph_vector_int_sort(&tmp2); + ret = !igraph_vector_int_all_e(&tmp1, &tmp2); + igraph_vector_int_destroy(&tmp1); + igraph_vector_int_destroy(&tmp2); + IGRAPH_FINALLY_CLEAN(2); + if (ret) { + return IGRAPH_SUCCESS; + } + } + + /* Check edge color distribution */ + if (edge_color1) { + igraph_bool_t ret = false; + igraph_vector_int_t tmp1, tmp2; + IGRAPH_CHECK(igraph_vector_int_init_copy(&tmp1, edge_color1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp1); + IGRAPH_CHECK(igraph_vector_int_init_copy(&tmp2, edge_color2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp2); + igraph_vector_int_sort(&tmp1); + igraph_vector_int_sort(&tmp2); + ret = !igraph_vector_int_all_e(&tmp1, &tmp2); + igraph_vector_int_destroy(&tmp1); + igraph_vector_int_destroy(&tmp2); + IGRAPH_FINALLY_CLEAN(2); + if (ret) { + return IGRAPH_SUCCESS; + } + } + + if (map12) { + core_1 = map12; + IGRAPH_CHECK(igraph_vector_int_resize(core_1, no_of_nodes)); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(core_1, no_of_nodes); + } + igraph_vector_int_fill(core_1, -1); + if (map21) { + core_2 = map21; + IGRAPH_CHECK(igraph_vector_int_resize(core_2, no_of_nodes)); + igraph_vector_int_null(core_2); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(core_2, no_of_nodes); + } + igraph_vector_int_fill(core_2, -1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_1, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_2, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_1, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_2, no_of_nodes); + IGRAPH_CHECK(igraph_stack_int_init(&path, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &path); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &inadj1, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &outadj1, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &inadj2, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj2); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &outadj2, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&indeg1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&indeg2, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outdeg1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outdeg2, 0); + + IGRAPH_CHECK(igraph_stack_int_reserve(&path, no_of_nodes * 2)); + IGRAPH_CHECK(igraph_degree(graph1, &indeg1, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &indeg2, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph1, &outdeg1, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &outdeg2, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + + depth = 0; last1 = -1; last2 = -1; + while (depth >= 0) { + igraph_int_t i; + + IGRAPH_ALLOW_INTERRUPTION(); + + cand1 = -1; cand2 = -1; + /* Search for the next pair to try */ + if ((in_1_size != in_2_size) || + (out_1_size != out_2_size)) { + /* step back, nothing to do */ + } else if (out_1_size > 0 && out_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(out_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, it should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(out_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else if (in_1_size > 0 && in_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(in_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(in_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } + + /* Ok, we have cand1, cand2 as candidates. Or not? */ + if (cand1 < 0 || cand2 < 0) { + /**************************************************************/ + /* dead end, step back, if possible. Otherwise we'll terminate */ + if (depth >= 1) { + last2 = igraph_stack_int_pop(&path); + last1 = igraph_stack_int_pop(&path); + matched_nodes -= 1; + VECTOR(*core_1)[last1] = -1; + VECTOR(*core_2)[last2] = -1; + + if (VECTOR(in_1)[last1] != 0) { + in_1_size += 1; + } + if (VECTOR(out_1)[last1] != 0) { + out_1_size += 1; + } + if (VECTOR(in_2)[last2] != 0) { + in_2_size += 1; + } + if (VECTOR(out_2)[last2] != 0) { + out_2_size += 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, last1); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == depth) { + VECTOR(in_1)[node] = 0; + in_1_size -= 1; + } + } + + outneis_1 = igraph_lazy_adjlist_get(&outadj1, last1); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == depth) { + VECTOR(out_1)[node] = 0; + out_1_size -= 1; + } + } + + inneis_2 = igraph_lazy_adjlist_get(&inadj2, last2); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == depth) { + VECTOR(in_2)[node] = 0; + in_2_size -= 1; + } + } + + outneis_2 = igraph_lazy_adjlist_get(&outadj2, last2); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == depth) { + VECTOR(out_2)[node] = 0; + out_2_size -= 1; + } + } + + } /* end of stepping back */ + + depth -= 1; + + } else { + /**************************************************************/ + /* step forward if worth, check if worth first */ + igraph_int_t xin1 = 0, xin2 = 0, xout1 = 0, xout2 = 0; + igraph_bool_t end = false; + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, cand1); + outneis_1 = igraph_lazy_adjlist_get(&outadj1, cand1); + inneis_2 = igraph_lazy_adjlist_get(&inadj2, cand2); + outneis_2 = igraph_lazy_adjlist_get(&outadj2, cand2); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + if (VECTOR(indeg1)[cand1] != VECTOR(indeg2)[cand2] || + VECTOR(outdeg1)[cand1] != VECTOR(outdeg2)[cand2]) { + end = true; + } + if (vertex_color1 && VECTOR(*vertex_color1)[cand1] != VECTOR(*vertex_color2)[cand2]) { + end = true; + } + if (node_compat_fn && !node_compat_fn(graph1, graph2, cand1, cand2, arg)) { + end = true; + } + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(*core_1)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_1)[node]; + /* check if there is a node2->cand2 edge */ + if (!igraph_vector_int_contains_sorted(inneis_2, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, node, cand1, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, node2, cand2, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(*core_1)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_1)[node]; + /* check if there is a cand2->node2 edge */ + if (!igraph_vector_int_contains_sorted(outneis_2, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, cand1, node, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, cand2, node2, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_2)[node]; + /* check if there is a node2->cand1 edge */ + if (!igraph_vector_int_contains_sorted(inneis_1, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, node2, cand1, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, node, cand2, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_2)[node]; + /* check if there is a cand1->node2 edge */ + if (!igraph_vector_int_contains_sorted(outneis_1, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, cand1, node2, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, cand2, node, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + + if (!end && (xin1 == xin2 && xout1 == xout2)) { + /* Ok, we add the (cand1, cand2) pair to the mapping */ + depth += 1; + IGRAPH_CHECK(igraph_stack_int_push(&path, cand1)); + IGRAPH_CHECK(igraph_stack_int_push(&path, cand2)); + matched_nodes += 1; + VECTOR(*core_1)[cand1] = cand2; + VECTOR(*core_2)[cand2] = cand1; + + /* update in_*, out_* */ + if (VECTOR(in_1)[cand1] != 0) { + in_1_size -= 1; + } + if (VECTOR(out_1)[cand1] != 0) { + out_1_size -= 1; + } + if (VECTOR(in_2)[cand2] != 0) { + in_2_size -= 1; + } + if (VECTOR(out_2)[cand2] != 0) { + out_2_size -= 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, cand1); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(in_1)[node] = depth; + in_1_size += 1; + } + } + + outneis_1 = igraph_lazy_adjlist_get(&outadj1, cand1); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(out_1)[node] = depth; + out_1_size += 1; + } + } + + inneis_2 = igraph_lazy_adjlist_get(&inadj2, cand2); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(in_2)[node] = depth; + in_2_size += 1; + } + } + + outneis_2 = igraph_lazy_adjlist_get(&outadj2, cand2); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(out_2)[node] = depth; + out_2_size += 1; + } + } + + last1 = -1; last2 = -1; /* this the first time here */ + } else { + last1 = cand1; + last2 = cand2; + } + + } + + if (matched_nodes == no_of_nodes && isohandler_fn) { + igraph_error_t ret; + IGRAPH_CHECK_CALLBACK(isohandler_fn(core_1, core_2, arg), &ret); + if (ret == IGRAPH_STOP) { + break; + } + } + } + + igraph_vector_int_destroy(&outdeg2); + igraph_vector_int_destroy(&outdeg1); + igraph_vector_int_destroy(&indeg2); + igraph_vector_int_destroy(&indeg1); + igraph_lazy_adjlist_destroy(&outadj2); + igraph_lazy_adjlist_destroy(&inadj2); + igraph_lazy_adjlist_destroy(&outadj1); + igraph_lazy_adjlist_destroy(&inadj1); + igraph_stack_int_destroy(&path); + igraph_vector_int_destroy(&out_2); + igraph_vector_int_destroy(&out_1); + igraph_vector_int_destroy(&in_2); + igraph_vector_int_destroy(&in_1); + IGRAPH_FINALLY_CLEAN(13); + if (!map21) { + igraph_vector_int_destroy(core_2); + IGRAPH_FINALLY_CLEAN(1); + } + if (!map12) { + igraph_vector_int_destroy(core_1); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +typedef struct { + igraph_isocompat_t *node_compat_fn, *edge_compat_fn; + void *arg, *carg; +} igraph_i_iso_cb_data_t; + +static igraph_bool_t igraph_i_isocompat_node_cb( + const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + return data->node_compat_fn(graph1, graph2, g1_num, g2_num, data->carg); +} + +static igraph_bool_t igraph_i_isocompat_edge_cb( + const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + return data->edge_compat_fn(graph1, graph2, g1_num, g2_num, data->carg); +} + +static igraph_error_t igraph_i_isomorphic_vf2_cb( + const igraph_vector_int_t *map12, const igraph_vector_int_t *map21, + void *arg +) { + igraph_i_iso_cb_data_t *data = arg; + igraph_bool_t *iso = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *iso = true; + return IGRAPH_STOP; +} + +/** + * \function igraph_isomorphic_vf2 + * \brief Isomorphism via VF2. + * + * + * This function performs the VF2 algorithm via calling \ref + * igraph_get_isomorphisms_vf2_callback(). + * + * Note that this function cannot be used for + * deciding subgraph isomorphism, use \ref igraph_subisomorphic_vf2() + * for that. + * \param graph1 The first graph, may be directed or undirected. + * \param graph2 The second graph. It must have the same directedness + * as \p graph1, otherwise an error is reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param iso Pointer to a Boolean constant, the result of the + * algorithm will be placed here. + * \param map12 Pointer to an initialized vector or a NULL pointer. If not + * a NULL pointer then the mapping from \p graph1 to \p graph2 is + * stored here. If the graphs are not isomorphic then the vector is + * cleared (i.e. has zero elements). + * \param map21 Pointer to an initialized vector or a NULL pointer. If not + * a NULL pointer then the mapping from \p graph2 to \p graph1 is + * stored here. If the graphs are not isomorphic then the vector is + * cleared (i.e. has zero elements). + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * \sa \ref igraph_subisomorphic_vf2(), + * \ref igraph_count_isomorphisms_vf2(), + * \ref igraph_get_isomorphisms_vf2(), + * + * Time complexity: exponential, what did you expect? + * + * \example examples/simple/igraph_isomorphic_vf2.c + */ + +igraph_error_t igraph_isomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, iso, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *iso = false; + IGRAPH_CHECK(igraph_get_isomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + map12, map21, + (igraph_isohandler_t*) igraph_i_isomorphic_vf2_cb, + ncb, ecb, &data)); + if (! *iso) { + if (map12) { + igraph_vector_int_clear(map12); + } + if (map21) { + igraph_vector_int_clear(map21); + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_count_isomorphisms_vf2_cb( + const igraph_vector_int_t *map12, const igraph_vector_int_t *map21, + void *arg +) { + igraph_i_iso_cb_data_t *data = arg; + igraph_int_t *count = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *count += 1; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_isomorphisms_vf2 + * \brief Number of isomorphisms via VF2. + * + * This function counts the number of isomorphic mappings between two + * graphs. It uses the generic \ref igraph_get_isomorphisms_vf2_callback() + * function. + * + * \param graph1 The first input graph, may be directed or undirected. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1, or an error will be reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param count Point to an integer, the result will be stored here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn and + * \p edge_compat_fn. + * \return Error code. + * + * \sa igraph_count_automorphisms_bliss() + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_count_isomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, + count, arg + }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *count = 0; + IGRAPH_CHECK(igraph_get_isomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) igraph_i_count_isomorphisms_vf2_cb, + ncb, ecb, &data)); + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_store_mapping_vf2_cb( + const igraph_vector_int_t *map12, const igraph_vector_int_t *map21, + void *arg +) { + igraph_i_iso_cb_data_t *data = arg; + igraph_vector_int_list_t *ptrvector = data->arg; + IGRAPH_UNUSED(map12); + return igraph_vector_int_list_push_back_copy(ptrvector, map21); +} + +/** + * \function igraph_get_isomorphisms_vf2 + * \brief Collect all isomorphic mappings of two graphs. + * + * This function finds all the isomorphic mappings between two simple + * graphs. It uses the \ref igraph_get_isomorphisms_vf2_callback() + * function. Call the function with the same graph as \p graph1 and \p + * graph2 to get automorphisms. + * \param graph1 The first input graph, may be directed or undirected. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1, or an error will be reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param maps Pointer to a list of integer vectors. On return it is empty if + * the input graphs are not isomorphic. Otherwise it contains pointers to + * \ref igraph_vector_int_t objects, each vector is an + * isomorphic mapping of \p graph2 to \p graph1. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_get_isomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, maps, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : NULL; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : NULL; + + igraph_vector_int_list_clear(maps); + IGRAPH_CHECK(igraph_get_isomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + NULL, NULL, + (igraph_isohandler_t*) igraph_i_store_mapping_vf2_cb, + ncb, ecb, &data)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_subisomorphisms_vf2_callback + * \brief Generic VF2 function for subgraph isomorphism problems. + * + * This function is the pair of \ref igraph_get_isomorphisms_vf2_callback(), + * for subgraph isomorphism problems. It searches for subgraphs of \p + * graph1 which are isomorphic to \p graph2. When it founds an + * isomorphic mapping it calls the supplied callback \p isohandler_fn. + * The mapping (and its inverse) and the additional \p arg argument + * are supplied to the callback. + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param map12 Pointer to a vector or \c NULL. If not \c NULL, then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * \param map21 Pointer to a vector ot \c NULL. If not \c NULL, then + * an isomorphic mapping from \p graph2 to \p graph1 is stored + * here. + * \param isohandler_fn A pointer to a function of type \ref + * igraph_isohandler_t. This will be called whenever a subgraph + * isomorphism is found. If the function returns \c IGRAPH_SUCCESS, + * then the search is continued. If the function returns \c IGRAPH_STOP, + * the search is terminated normally. Any other value is treated as an + * igraph error code. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p isohandler_fn, \p + * node_compat_fn and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_get_subisomorphisms_vf2_callback( + const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, const igraph_vector_int_t *edge_color2, + igraph_vector_int_t *map12, igraph_vector_int_t *map21, + igraph_isohandler_t *isohandler_fn, igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, void *arg +) { + + igraph_int_t no_of_nodes1 = igraph_vcount(graph1), + no_of_nodes2 = igraph_vcount(graph2); + igraph_int_t no_of_edges1 = igraph_ecount(graph1), + no_of_edges2 = igraph_ecount(graph2); + igraph_vector_int_t mycore_1, mycore_2, *core_1 = &mycore_1, *core_2 = &mycore_2; + igraph_vector_int_t in_1, in_2, out_1, out_2; + igraph_int_t in_1_size = 0, in_2_size = 0, out_1_size = 0, out_2_size = 0; + igraph_vector_int_t *inneis_1, *inneis_2, *outneis_1, *outneis_2; + igraph_int_t matched_nodes = 0; + igraph_int_t depth; + igraph_int_t cand1, cand2; + igraph_int_t last1, last2; + igraph_stack_int_t path; + igraph_lazy_adjlist_t inadj1, inadj2, outadj1, outadj2; + igraph_vector_int_t indeg1, indeg2, outdeg1, outdeg2; + igraph_int_t vsize; + + IGRAPH_CHECK(igraph_i_perform_vf2_pre_checks(graph1, graph2)); + + if (no_of_nodes1 < no_of_nodes2 || no_of_edges1 < no_of_edges2) { + return IGRAPH_SUCCESS; + } + + if ( (vertex_color1 && !vertex_color2) || (!vertex_color1 && vertex_color2) ) { + IGRAPH_WARNING("Only one graph is vertex colored, colors will be ignored"); + vertex_color1 = vertex_color2 = 0; + } + + if ( (edge_color1 && !edge_color2) || (!edge_color1 && edge_color2) ) { + IGRAPH_WARNING("Only one graph is edge colored, colors will be ignored"); + edge_color1 = edge_color2 = 0; + } + + if (vertex_color1) { + if (igraph_vector_int_size(vertex_color1) != no_of_nodes1 || + igraph_vector_int_size(vertex_color2) != no_of_nodes2) { + IGRAPH_ERROR("Invalid vertex color vector length", IGRAPH_EINVAL); + } + } + + if (edge_color1) { + if (igraph_vector_int_size(edge_color1) != no_of_edges1 || + igraph_vector_int_size(edge_color2) != no_of_edges2) { + IGRAPH_ERROR("Invalid edge color vector length", IGRAPH_EINVAL); + } + } + + /* Check color distribution */ + if (vertex_color1) { + /* TODO */ + } + + /* Check edge color distribution */ + if (edge_color1) { + /* TODO */ + } + + if (map12) { + core_1 = map12; + IGRAPH_CHECK(igraph_vector_int_resize(core_1, no_of_nodes1)); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(core_1, no_of_nodes1); + } + igraph_vector_int_fill(core_1, -1); + if (map21) { + core_2 = map21; + IGRAPH_CHECK(igraph_vector_int_resize(core_2, no_of_nodes2)); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(core_2, no_of_nodes2); + } + igraph_vector_int_fill(core_2, -1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_1, no_of_nodes1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_2, no_of_nodes2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_1, no_of_nodes1); + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_2, no_of_nodes2); + IGRAPH_CHECK(igraph_stack_int_init(&path, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &path); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &inadj1, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &outadj1, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &inadj2, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj2); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &outadj2, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&indeg1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&indeg2, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outdeg1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outdeg2, 0); + + IGRAPH_CHECK(igraph_stack_int_reserve(&path, no_of_nodes2 * 2)); + IGRAPH_CHECK(igraph_degree(graph1, &indeg1, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &indeg2, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph1, &outdeg1, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &outdeg2, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + + depth = 0; last1 = -1; last2 = -1; + while (depth >= 0) { + igraph_int_t i; + + IGRAPH_ALLOW_INTERRUPTION(); + + cand1 = -1; cand2 = -1; + /* Search for the next pair to try */ + if ((in_1_size < in_2_size) || + (out_1_size < out_2_size)) { + /* step back, nothing to do */ + } else if (out_1_size > 0 && out_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(out_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, it should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(out_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else if (in_1_size > 0 && in_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(in_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(in_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } + + /* Ok, we have cand1, cand2 as candidates. Or not? */ + if (cand1 < 0 || cand2 < 0) { + /**************************************************************/ + /* dead end, step back, if possible. Otherwise we'll terminate */ + if (depth >= 1) { + last2 = igraph_stack_int_pop(&path); + last1 = igraph_stack_int_pop(&path); + matched_nodes -= 1; + VECTOR(*core_1)[last1] = -1; + VECTOR(*core_2)[last2] = -1; + + if (VECTOR(in_1)[last1] != 0) { + in_1_size += 1; + } + if (VECTOR(out_1)[last1] != 0) { + out_1_size += 1; + } + if (VECTOR(in_2)[last2] != 0) { + in_2_size += 1; + } + if (VECTOR(out_2)[last2] != 0) { + out_2_size += 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, last1); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == depth) { + VECTOR(in_1)[node] = 0; + in_1_size -= 1; + } + } + + outneis_1 = igraph_lazy_adjlist_get(&outadj1, last1); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == depth) { + VECTOR(out_1)[node] = 0; + out_1_size -= 1; + } + } + + inneis_2 = igraph_lazy_adjlist_get(&inadj2, last2); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == depth) { + VECTOR(in_2)[node] = 0; + in_2_size -= 1; + } + } + + outneis_2 = igraph_lazy_adjlist_get(&outadj2, last2); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == depth) { + VECTOR(out_2)[node] = 0; + out_2_size -= 1; + } + } + + } /* end of stepping back */ + + depth -= 1; + + } else { + /**************************************************************/ + /* step forward if worth, check if worth first */ + igraph_int_t xin1 = 0, xin2 = 0, xout1 = 0, xout2 = 0; + igraph_bool_t end = false; + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, cand1); + outneis_1 = igraph_lazy_adjlist_get(&outadj1, cand1); + inneis_2 = igraph_lazy_adjlist_get(&inadj2, cand2); + outneis_2 = igraph_lazy_adjlist_get(&outadj2, cand2); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + if (VECTOR(indeg1)[cand1] < VECTOR(indeg2)[cand2] || + VECTOR(outdeg1)[cand1] < VECTOR(outdeg2)[cand2]) { + end = true; + } + if (vertex_color1 && VECTOR(*vertex_color1)[cand1] != VECTOR(*vertex_color2)[cand2]) { + end = true; + } + if (node_compat_fn && !node_compat_fn(graph1, graph2, cand1, cand2, arg)) { + end = true; + } + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(*core_1)[node] < 0) { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(*core_1)[node] < 0) { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_2)[node]; + /* check if there is a node2->cand1 edge */ + if (!igraph_vector_int_contains_sorted(inneis_1, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, node2, cand1, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, node, cand2, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; !end && i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + igraph_int_t node2 = VECTOR(*core_2)[node]; + /* check if there is a cand1->node2 edge */ + if (!igraph_vector_int_contains_sorted(outneis_1, node2)) { + end = true; + } else if (edge_color1 || edge_compat_fn) { + igraph_int_t eid1, eid2; + igraph_get_eid(graph1, &eid1, cand1, node2, IGRAPH_DIRECTED, + /*error=*/ true); + igraph_get_eid(graph2, &eid2, cand2, node, IGRAPH_DIRECTED, + /*error=*/ true); + if (edge_color1 && VECTOR(*edge_color1)[eid1] != + VECTOR(*edge_color2)[eid2]) { + end = true; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = true; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + + if (!end && (xin1 >= xin2 && xout1 >= xout2)) { + /* Ok, we add the (cand1, cand2) pair to the mapping */ + depth += 1; + IGRAPH_CHECK(igraph_stack_int_push(&path, cand1)); + IGRAPH_CHECK(igraph_stack_int_push(&path, cand2)); + matched_nodes += 1; + VECTOR(*core_1)[cand1] = cand2; + VECTOR(*core_2)[cand2] = cand1; + + /* update in_*, out_* */ + if (VECTOR(in_1)[cand1] != 0) { + in_1_size -= 1; + } + if (VECTOR(out_1)[cand1] != 0) { + out_1_size -= 1; + } + if (VECTOR(in_2)[cand2] != 0) { + in_2_size -= 1; + } + if (VECTOR(out_2)[cand2] != 0) { + out_2_size -= 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, cand1); + IGRAPH_CHECK_OOM(inneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(in_1)[node] = depth; + in_1_size += 1; + } + } + + outneis_1 = igraph_lazy_adjlist_get(&outadj1, cand1); + IGRAPH_CHECK_OOM(outneis_1, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_1); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(out_1)[node] = depth; + out_1_size += 1; + } + } + + inneis_2 = igraph_lazy_adjlist_get(&inadj2, cand2); + IGRAPH_CHECK_OOM(inneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(inneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(in_2)[node] = depth; + in_2_size += 1; + } + } + + outneis_2 = igraph_lazy_adjlist_get(&outadj2, cand2); + IGRAPH_CHECK_OOM(outneis_2, "Failed to query neighbors."); + + vsize = igraph_vector_int_size(outneis_2); + for (i = 0; i < vsize; i++) { + igraph_int_t node = VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(out_2)[node] = depth; + out_2_size += 1; + } + } + + last1 = -1; last2 = -1; /* this the first time here */ + } else { + last1 = cand1; + last2 = cand2; + } + + } + + if (matched_nodes == no_of_nodes2 && isohandler_fn) { + igraph_error_t ret; + IGRAPH_CHECK_CALLBACK(isohandler_fn(core_1, core_2, arg), &ret); + if (ret == IGRAPH_STOP) { + break; + } + } + } + + igraph_vector_int_destroy(&outdeg2); + igraph_vector_int_destroy(&outdeg1); + igraph_vector_int_destroy(&indeg2); + igraph_vector_int_destroy(&indeg1); + igraph_lazy_adjlist_destroy(&outadj2); + igraph_lazy_adjlist_destroy(&inadj2); + igraph_lazy_adjlist_destroy(&outadj1); + igraph_lazy_adjlist_destroy(&inadj1); + igraph_stack_int_destroy(&path); + igraph_vector_int_destroy(&out_2); + igraph_vector_int_destroy(&out_1); + igraph_vector_int_destroy(&in_2); + igraph_vector_int_destroy(&in_1); + IGRAPH_FINALLY_CLEAN(13); + if (!map21) { + igraph_vector_int_destroy(core_2); + IGRAPH_FINALLY_CLEAN(1); + } + if (!map12) { + igraph_vector_int_destroy(core_1); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_subisomorphic_vf2_cb( + const igraph_vector_int_t *map12, const igraph_vector_int_t *map21, + void *arg +) { + igraph_i_iso_cb_data_t *data = arg; + igraph_bool_t *iso = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *iso = true; + return IGRAPH_STOP; +} + +/** + * \function igraph_subisomorphic_vf2 + * Decide subgraph isomorphism using VF2 + * + * Decides whether a subgraph of \p graph1 is isomorphic to \p + * graph2. It uses \ref igraph_get_subisomorphisms_vf2_callback(). + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param iso Pointer to a boolean. The result of the decision problem + * is stored here. + * \param map12 Pointer to a vector or \c NULL. If not \c NULL, then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * \param map21 Pointer to a vector ot \c NULL. If not \c NULL, then + * an isomorphic mapping from \p graph2 to \p graph1 is stored + * here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_subisomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_int_t *map12, + igraph_vector_int_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, iso, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + + *iso = false; + IGRAPH_CHECK(igraph_get_subisomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + map12, map21, + (igraph_isohandler_t *) igraph_i_subisomorphic_vf2_cb, + ncb, ecb, &data)); + if (! *iso) { + if (map12) { + igraph_vector_int_clear(map12); + } + if (map21) { + igraph_vector_int_clear(map21); + } + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_count_subisomorphisms_vf2_cb( + const igraph_vector_int_t *map12, const igraph_vector_int_t *map21, + void *arg +) { + igraph_i_iso_cb_data_t *data = arg; + igraph_int_t *count = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *count += 1; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_subisomorphisms_vf2 + * Number of subgraph isomorphisms using VF2 + * + * Count the number of isomorphisms between subgraphs of \p graph1 and + * \p graph2. This function uses \ref igraph_get_subisomorphisms_vf2_callback(). + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param count Pointer to an integer. The number of subgraph + * isomorphisms is stored here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn and + * \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_count_subisomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_int_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, + count, arg + }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *count = 0; + IGRAPH_CHECK(igraph_get_subisomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) igraph_i_count_subisomorphisms_vf2_cb, + ncb, ecb, &data)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_subisomorphisms_vf2 + * \brief Return all subgraph isomorphic mappings. + * + * This function collects all isomorphic mappings of \p graph2 to a + * subgraph of \p graph1. It uses the \ref + * igraph_get_subisomorphisms_vf2_callback() function. The graphs should be simple. + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param maps Pointer to a list of integer vectors. On return it contains + * pointers to \ref igraph_vector_int_t objects, each vector is an isomorphic + * mapping of \p graph2 to a subgraph of \p graph1. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +igraph_error_t igraph_get_subisomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_int_list_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, maps, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : NULL; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : NULL; + + igraph_vector_int_list_clear(maps); + IGRAPH_CHECK(igraph_get_subisomorphisms_vf2_callback(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + NULL, NULL, + (igraph_isohandler_t*) igraph_i_store_mapping_vf2_cb, + ncb, ecb, &data)); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/align.c b/src/layout/align.c new file mode 100644 index 0000000..7496b68 --- /dev/null +++ b/src/layout/align.c @@ -0,0 +1,320 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_layout.h" + +#include "igraph_blas.h" +#include "igraph_interface.h" +#include "igraph_lapack.h" + +#include "core/interruption.h" + + +/** + * \ingroup layout + * \function igraph_layout_align + * \brief Aligns a graph layout with the coordinate axes. + * + * This function centers a vertex layout on the coordinate system origin and + * rotates the layout to achieve a visually pleasing alignment with the coordinate + * axes. Doing this is particularly useful with force-directed layouts such as + * \ref igraph_layout_fruchterman_reingold(). Layouts in arbitrary dimensional + * spaces are supported. + * + * \param graph The graph whose layout is to be aligned. + * \param layout A matrix whose rows are the coordinates of vertices. It will + * be modified in-place. + * \return Error code. + * + * Time complexity: O(|E| + |V|), linear in the number of edges and vertices. + */ + +igraph_error_t igraph_layout_align(const igraph_t *graph, igraph_matrix_t *layout) { + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_int_t dim = igraph_matrix_ncol(layout); + igraph_matrix_t M, Q; /* nematic tensor */ + igraph_real_t norm2_sum; /* sum of squared norms of alignment vectors */ + igraph_matrix_t R; /* rotation matrix consisting of Q's eigenvectors */ + igraph_vector_t lambda; /* Q's eigenvalues */ + igraph_matrix_t temp_layout; + + /* The correction terms are used to remove one vector/edge from the computation + * of the nematic tensor when it is necessary to break symmetries. */ + igraph_bool_t correction_saved = false; + igraph_matrix_t M_correction; + igraph_real_t norm2_sum_correction; + + igraph_bool_t retried = false; + int iter = 0; + + /* General approach: + * + * - Shift the center of mass of the vertices to the origin of our coordinate + * system. + * + * - Compute the nematic tensor Q_ij = - delta_ij / d, where v + * are d-dimensional unit vectors used for the alignment, usually along + * the edges of the graph, <...> denotes averaging over all such vectors + * (i.e. all edges), and delta_ij is the Kronecker symbol. + * + * We use a weighted average, using the squared edge lengths as weights. + * + * - Find the principal axes of the nematic tensor and align them to our + * coordinate axes. + * + * - If there are no edges of non-zero length then we use the vector + * representing vertex positions (after centering) to compute the nematic + * tensor. + * + * - If the nematic tensor is close to zero (i.e. the layout is rotationally + * symmetric), we remove a vector v to break the symmetry and re-try. + * (See the "correction" terms.) + */ + + if (igraph_matrix_nrow(layout) != vcount) { + IGRAPH_ERROR("Number of points in layout does not match vertex count.", IGRAPH_EINVAL); + } + + if (vcount == 0) { + /* Null graph, nothing to do. */ + return IGRAPH_SUCCESS; + } + + /* This check is not done for the null graph, which was handled above. + * Skipping the check is necessary due to the different handling of + * zero-by-n matrices i various systems. */ + if (dim == 0) { + IGRAPH_ERROR("Vertex coordinates must be at least one dimensional, " + "but received a zero-dimensional input.", IGRAPH_EINVAL); + } + + /* Shift layout to origin. */ + { + igraph_vector_t center; + IGRAPH_VECTOR_INIT_FINALLY(¢er, dim); + + IGRAPH_CHECK(igraph_matrix_colsum(layout, ¢er)); + igraph_vector_scale(¢er, 1.0 / vcount); + + for (igraph_int_t j=0; j < dim; j++) { + for (igraph_int_t i=0; i < vcount; i++) { + MATRIX(*layout, i, j) -= VECTOR(center)[j]; + } + } + + igraph_vector_destroy(¢er); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Nothing more to do for a 1D layout. */ + if (dim == 1) { + return IGRAPH_SUCCESS; + } + + IGRAPH_MATRIX_INIT_FINALLY(&M, dim, dim); + IGRAPH_MATRIX_INIT_FINALLY(&M_correction, dim, dim); + + /* Compute M_ij = sum v_i v_j based on edge vectors. */ + { + igraph_vector_t edge_vec; + IGRAPH_VECTOR_INIT_FINALLY(&edge_vec, dim); + + norm2_sum = 0; + for (igraph_int_t eid=0; eid < ecount; eid++) { + const igraph_int_t from = IGRAPH_FROM(graph, eid); + const igraph_int_t to = IGRAPH_TO(graph, eid); + + if (from == to) continue; /* skip self-loops */ + + for (igraph_int_t i=0; i < dim; i++) { + VECTOR(edge_vec)[i] = MATRIX(*layout, from, i) - MATRIX(*layout, to, i); + } + + for (igraph_int_t i=0; i < dim; i++) { + for (igraph_int_t j=0; j < dim; j++) { + igraph_real_t m = VECTOR(edge_vec)[i] * VECTOR(edge_vec)[j]; + MATRIX(M, i, j) += m; + if (i == j) { + norm2_sum += m; + } + } + } + + /* Save first non-zero term as a "correction" for later potential removal. */ + if (!correction_saved && norm2_sum > 0) { + correction_saved = true; + norm2_sum_correction = norm2_sum; + IGRAPH_CHECK(igraph_matrix_update(&M_correction, &M)); + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 12); + } + + igraph_vector_destroy(&edge_vec); + IGRAPH_FINALLY_CLEAN(1); + } + + /* If there are no edges, or all edges are of length zero, the sum of + * squared norms is zero and cannot be used for normalizing M. We resort + * to using the position vectors of vertices relative to the center of mass + * to compute M_ij. Note that the layout has already been centered. */ + if (norm2_sum == 0) { + /* If norm2_sum == 0 then M is also all-zero, no need to null it explicitly. */ + for (igraph_int_t vid=0; vid < vcount; vid++) { + for (igraph_int_t i=0; i < dim; i++) { + for (igraph_int_t j=0; j < dim; j++) { + igraph_real_t m = MATRIX(*layout, vid, i) * MATRIX(*layout, vid, j); + MATRIX(M, i, j) += m; + if (i == j) { + norm2_sum += m; + } + } + } + + /* Save first non-zero term as a "correction" for later potential removal. */ + if (!correction_saved && norm2_sum > 0) { + correction_saved = true; + norm2_sum_correction = norm2_sum; + IGRAPH_CHECK(igraph_matrix_update(&M_correction, &M)); + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 12); + } + } + + /* If all vertices are at the origin, no alignment is possible or necessary. + * We are done. */ + if (norm2_sum == 0) { + goto done; + } + + IGRAPH_ASSERT(correction_saved); + + IGRAPH_MATRIX_INIT_FINALLY(&Q, dim, dim); + IGRAPH_MATRIX_INIT_FINALLY(&R, dim, dim); + IGRAPH_VECTOR_INIT_FINALLY(&lambda, dim); + + while (true) { + + /* Finish computing the nematic tensor as + * Q_ij = M_ij / norm2 - delta_ij / d. */ + IGRAPH_CHECK(igraph_matrix_update(&Q, &M)); + igraph_matrix_scale(&Q, 1.0 / norm2_sum); + for (igraph_int_t i=0; i < dim; i++) { + MATRIX(Q, i, i) -= 1.0 / dim; + } + + /* Compute the eigenvectors of the nematic tensor, which together form + * the appropriate rotation matrix for alignment. Note that the nematic + * tensor is symmetric, so its eigenvectors form an orthogonal basis. + * LAPACK's DSYEVR guarantees that eigenvectors are normalized, + * "the first M columns of Z contain the orthonormal eigenvectors of the + * matrix A". + */ + IGRAPH_CHECK(igraph_lapack_dsyevr( + &Q, IGRAPH_LAPACK_DSYEV_ALL, + 0, 0, 0, 0, 0, /* ignored when computing all eigenvectors */ + /* abstol */ 1e-6, + /* eigenvalues */ &lambda, /* eigenvectors */ &R, + NULL)); + + /* Compute the matrix norm, i.e. the largest eigenvalue magnitude, + * to determine if the nematic tensor Q is close to zero. */ + igraph_real_t matrix_norm = 0; + for (igraph_int_t i=0; i < dim; i++) { + igraph_real_t magnitude = fabs(VECTOR(lambda)[i]); + if (magnitude > matrix_norm) { + matrix_norm = magnitude; + } + } + + /* If the nematic tensor is close to zero, we try to remove a vector once + * and recompute. The 'retried' variable prevents more than a single retry. */ + if (matrix_norm > 1e-3 || retried) { + break; + } + + IGRAPH_CHECK(igraph_matrix_sub(&M, &M_correction)); + norm2_sum -= norm2_sum_correction; + + retried = true; + } + + /* Rotate the layout. */ + IGRAPH_MATRIX_INIT_FINALLY(&temp_layout, vcount, dim); + IGRAPH_CHECK(igraph_blas_dgemm(/* transpose 'layout' */ false, + /* transpose 'R' */ false, + /* alpha */ 1.0, + layout, &R, + /* beta */ 0.0, + &temp_layout)); + + /* We compute the extent of the coordinate values along each axis, + * and re-order axes from greatest to smallest extent. This ensures + * that with a standard plotting setup, plots will be wide rather + * than tall. + * + * At the same time, we copy back 'temp_layout' to 'layout'. + * + * Note that the extents are not necessarily ordered the same way + * as the eigenvalues of the nematic tensor, as there may be isolated + * vertices which do not contribute to the nematic tensor. + */ + { + igraph_vector_t extent; + igraph_vector_int_t permutation; + + IGRAPH_VECTOR_INIT_FINALLY(&extent, dim); + IGRAPH_VECTOR_INT_INIT_FINALLY(&permutation, dim); + + for (igraph_int_t j=0; j < dim; j++) { + igraph_real_t min = IGRAPH_INFINITY, max = -IGRAPH_INFINITY; + for (igraph_int_t i=0; i < vcount; i++) { + igraph_real_t c = MATRIX(temp_layout, i, j); + if (c < min) min = c; + if (c > max) max = c; + } + VECTOR(extent)[j] = max - min; + } + IGRAPH_CHECK(igraph_vector_sort_ind(&extent, &permutation, IGRAPH_DESCENDING)); + + for (igraph_int_t j=0; j < dim; j++) { + for (igraph_int_t i=0; i < vcount; i++) { + MATRIX(*layout, i, j) = MATRIX(temp_layout, i, VECTOR(permutation)[j]); + } + } + + igraph_vector_int_destroy(&permutation); + igraph_vector_destroy(&extent); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_matrix_destroy(&temp_layout); + igraph_vector_destroy(&lambda); + igraph_matrix_destroy(&R); + igraph_matrix_destroy(&Q); + IGRAPH_FINALLY_CLEAN(4); + +done: + igraph_matrix_destroy(&M_correction); + igraph_matrix_destroy(&M); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/circular.c b/src/layout/circular.c new file mode 100644 index 0000000..5a474de --- /dev/null +++ b/src/layout/circular.c @@ -0,0 +1,186 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "core/math.h" + +/** + * \ingroup layout + * \function igraph_layout_circle + * \brief Places the vertices uniformly on a circle in arbitrary order. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param order The order of the vertices on the circle. The vertices + * not included here, will be placed at (0,0). Supply + * \ref igraph_vss_all() here to place vertices in the + * order of their vertex IDs. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t order) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t vs_size; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vs_size(graph, &order, &vs_size)); + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + igraph_matrix_null(res); + + IGRAPH_CHECK(igraph_vit_create(graph, order, &vit)); + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t phi = 2 * M_PI / vs_size * i; + igraph_int_t idx = IGRAPH_VIT_GET(vit); + MATRIX(*res, idx, 0) = cos(phi); + MATRIX(*res, idx, 1) = sin(phi); + } + igraph_vit_destroy(&vit); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_star + * \brief Generates a star-like layout. + * + * \param graph The input graph. Its edges are ignored by this function. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param center The id of the vertex to put in the center. You can set it to + * any arbitrary value for the special case when the input graph has no + * vertices; otherwise it must be between 0 and the number of vertices + * minus 1. + * \param order A numeric vector giving the order of the vertices + * (including the center vertex!). If a null pointer, then the + * vertices are placed in increasing vertex ID order. + * \return Error code. + * + * Time complexity: O(|V|), linear in the number of vertices. + * + * \sa \ref igraph_layout_circle() and other layout generators. + */ +igraph_error_t igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t center, const igraph_vector_int_t *order) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (no_of_nodes > 0 && (center < 0 || center >= no_of_nodes)) { + IGRAPH_ERROR("The given center is not a vertex of the graph.", IGRAPH_EINVAL); + } + if (order && igraph_vector_int_size(order) != no_of_nodes) { + IGRAPH_ERROR("Invalid order vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + if (no_of_nodes == 1) { + MATRIX(*res, 0, 0) = MATRIX(*res, 0, 1) = 0.0; + } else if (no_of_nodes > 1) { + igraph_real_t phi = 0.0; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t node = order ? VECTOR(*order)[i] : i; + if (order && (node < 0 || node >= no_of_nodes)) { + IGRAPH_ERROR("Elements in the order vector are not all vertices of the graph.", IGRAPH_EINVAL); + } + if (node != center) { + MATRIX(*res, node, 0) = cos(phi); + MATRIX(*res, node, 1) = sin(phi); + phi += 2.0 * M_PI / (no_of_nodes - 1); + } else { + MATRIX(*res, node, 0) = MATRIX(*res, node, 1) = 0.0; + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_sphere + * \brief Places vertices (more or less) uniformly on a sphere. + * + * The vertices are placed with approximately equal spacing on a spiral + * wrapped around a sphere, in the order of their vertex IDs. Vertices + * with consecutive vertex IDs are placed near each other. + * + * + * The algorithm was described in the following paper: + * + * + * Distributing many points on a sphere by E.B. Saff and + * A.B.J. Kuijlaars, \emb Mathematical Intelligencer \eme 19.1 (1997) + * 5--11. https://doi.org/10.1007/BF03024331 + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \return Error code. The current implementation always returns with + * success. + * + * Added in version 0.2. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ +igraph_error_t igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_real_t sqrt_no_of_nodes = sqrt(no_of_nodes); + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3)); + + igraph_real_t phi = 0; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_real_t r, z; + + /* The first and last point are handled separately to avoid + * division by zero or 1-z*z becoming slightly negative due + * to roundoff errors. */ + if (i == 0) { + z = -1; r = 0; + } else if (i == no_of_nodes-1) { + z = 1; r = 0; + } else { + z = -1.0 + 2.0 * i / (no_of_nodes - 1); + r = sqrt(1 - z*z); + phi += 3.6 / (sqrt_no_of_nodes*r); + } + + igraph_real_t x = r*cos(phi); + igraph_real_t y = r*sin(phi); + + MATRIX(*res, i, 0) = x; + MATRIX(*res, i, 1) = y; + MATRIX(*res, i, 2) = z; + + IGRAPH_ALLOW_INTERRUPTION(); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/davidson_harel.c b/src/layout/davidson_harel.c new file mode 100644 index 0000000..2b24b06 --- /dev/null +++ b/src/layout/davidson_harel.c @@ -0,0 +1,454 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "core/math.h" /* M_PI */ +#include "layout/layout_internal.h" + +#include + +/* not 'static', used in tests */ +igraph_bool_t igraph_i_layout_segments_intersect(igraph_real_t p0_x, igraph_real_t p0_y, + igraph_real_t p1_x, igraph_real_t p1_y, + igraph_real_t p2_x, igraph_real_t p2_y, + igraph_real_t p3_x, igraph_real_t p3_y) { + igraph_real_t s1_x = p1_x - p0_x; + igraph_real_t s1_y = p1_y - p0_y; + igraph_real_t s2_x = p3_x - p2_x; + igraph_real_t s2_y = p3_y - p2_y; + + igraph_real_t s1, s2, t1, t2, s, t; + s1 = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)); + s2 = (-s2_x * s1_y + s1_x * s2_y); + if (s2 == 0) { + return false; + } + t1 = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)); + t2 = (-s2_x * s1_y + s1_x * s2_y); + s = s1 / s2; + t = t1 / t2; + + return s >= 0 && s <= 1 && t >= 0 && t <= 1; +} + +/* not 'static', used in tests */ +igraph_real_t igraph_i_layout_point_segment_dist2(igraph_real_t v_x, igraph_real_t v_y, + igraph_real_t u1_x, igraph_real_t u1_y, + igraph_real_t u2_x, igraph_real_t u2_y) { + + igraph_real_t dx = u2_x - u1_x; + igraph_real_t dy = u2_y - u1_y; + igraph_real_t l2 = dx * dx + dy * dy; + igraph_real_t t, p_x, p_y; + if (l2 == 0) { + return (v_x - u1_x) * (v_x - u1_x) + (v_y - u1_y) * (v_y - u1_y); + } + t = ((v_x - u1_x) * dx + (v_y - u1_y) * dy) / l2; + if (t < 0.0) { + return (v_x - u1_x) * (v_x - u1_x) + (v_y - u1_y) * (v_y - u1_y); + } else if (t > 1.0) { + return (v_x - u2_x) * (v_x - u2_x) + (v_y - u2_y) * (v_y - u2_y); + } + p_x = u1_x + t * dx; + p_y = u1_y + t * dy; + return (v_x - p_x) * (v_x - p_x) + (v_y - p_y) * (v_y - p_y); +} + +/** + * \function igraph_layout_davidson_harel + * \brief Davidson-Harel layout algorithm. + * + * This function implements the algorithm by Davidson and Harel, + * see Ron Davidson, David Harel: Drawing Graphs Nicely Using + * Simulated Annealing. ACM Transactions on Graphics 15(4), + * pp. 301-331, 1996. + * https://doi.org/10.1145/234535.234538 + * + * + * The algorithm uses simulated annealing and a sophisticated + * energy function, which is unfortunately hard to parameterize + * for different graphs. The original publication did not disclose any + * parameter values, and the ones below were determined by + * experimentation. + * + * + * The algorithm consists of two phases, an annealing phase, and a + * fine-tuning phase. There is no simulated annealing in the second + * phase. + * + * + * Our implementation tries to follow the original publication, as + * much as possible. The only major difference is that coordinates are + * explicitly kept within the bounds of the rectangle of the layout. + * + * \param graph The input graph, edge directions are ignored. + * \param res A matrix, the result is stored here. It can be used to + * supply start coordinates, see \p use_seed. + * \param use_seed Boolean, whether to use the supplied \p res as + * start coordinates. + * \param maxiter The maximum number of annealing iterations. A + * reasonable value for smaller graphs is 10. + * \param fineiter The number of fine tuning iterations. A reasonable + * value is max(10, log2(n)) where \c n is the + * number of vertices. + * \param cool_fact Cooling factor. A reasonable value is 0.75. + * \param weight_node_dist Weight for the node-node distances + * component of the energy function. Reasonable value: 1.0. + * \param weight_border Weight for the distance from the border + * component of the energy function. It can be set to zero, if + * vertices are allowed to sit on the border. + * \param weight_edge_lengths Weight for the edge length component + * of the energy function, a reasonable value is the density of + * the graph divided by 10. + * \param weight_edge_crossings Weight for the edge crossing component + * of the energy function, a reasonable default is 1 minus the + * square root of the density of the graph. + * \param weight_node_edge_dist Weight for the node-edge distance + * component of the energy function. A reasonable value is + * 1 minus the density, divided by 5. + * \return Error code. + * + * Time complexity: one first phase iteration has time complexity + * O(n^2+m^2), one fine tuning iteration has time complexity O(mn). + * Time complexity might be smaller if some of the weights of the + * components of the energy function are set to zero. + * + */ + +igraph_error_t igraph_layout_davidson_harel(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_int_t fineiter, igraph_real_t cool_fact, + igraph_real_t weight_node_dist, igraph_real_t weight_border, + igraph_real_t weight_edge_lengths, + igraph_real_t weight_edge_crossings, + igraph_real_t weight_node_edge_dist) { + + igraph_int_t no_nodes = igraph_vcount(graph); + igraph_int_t no_edges = igraph_ecount(graph); + igraph_real_t width = sqrt(no_nodes) * 10, height = width; + igraph_vector_int_t perm; + igraph_bool_t fine_tuning = false; + igraph_vector_t try_x, try_y; + igraph_vector_int_t try_idx; + igraph_real_t move_radius = width / 2; + igraph_real_t fine_tuning_factor = 0.01; + igraph_vector_int_t neis; + igraph_real_t min_x = width / 2, max_x = -width / 2, min_y = height / 2, max_y = -height / 2; + + igraph_int_t no_tries = 30; + igraph_real_t w_node_dist = weight_node_dist ; /* 1.0 */ + igraph_real_t w_borderlines = weight_border; /* 0.0 */ + igraph_real_t w_edge_lengths = weight_edge_lengths; /* 0.0001; */ + igraph_real_t w_edge_crossings = weight_edge_crossings; /* 1.0 */ + igraph_real_t w_node_edge_dist = weight_node_edge_dist; /* 0.2 */ + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must not be negative for the Davidson-Harel layout.", IGRAPH_EINVAL); + } + if (fineiter < 0) { + IGRAPH_ERROR("Number of fine tuning iterations must not be negative for the Davidson-Harel layout.", + IGRAPH_EINVAL); + } + if (cool_fact <= 0 || cool_fact >= 1) { + IGRAPH_ERROR("Cooling factor must be in (0,1) for the Davidson-Harel layout.", IGRAPH_EINVAL); + } + if (use_seed) { + if (igraph_matrix_nrow(res) != no_nodes || igraph_matrix_ncol(res) != 2) { + IGRAPH_ERROR("Invalid start position matrix size in Davidson-Harel layout.", IGRAPH_EINVAL); + } + } else { + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + } + + if (no_nodes == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_vector_int_init_range(&perm, 0, no_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &perm); + IGRAPH_VECTOR_INIT_FINALLY(&try_x, no_tries); + IGRAPH_VECTOR_INIT_FINALLY(&try_y, no_tries); + IGRAPH_CHECK(igraph_vector_int_init_range(&try_idx, 0, no_tries)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &try_idx); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 100); + + if (!use_seed) { + for (igraph_int_t i = 0; i < no_nodes; i++) { + igraph_real_t x, y; + x = MATRIX(*res, i, 0) = RNG_UNIF(-width / 2, width / 2); + y = MATRIX(*res, i, 1) = RNG_UNIF(-height / 2, height / 2); + if (x < min_x) { + min_x = x; + } else if (x > max_x) { + max_x = x; + } + if (y < min_y) { + min_y = y; + } else if (y > max_y) { + max_y = y; + } + } + } else { + min_x = IGRAPH_INFINITY; max_x = -IGRAPH_INFINITY; + min_y = IGRAPH_INFINITY; max_y = -IGRAPH_INFINITY; + for (igraph_int_t i = 0; i < no_nodes; i++) { + igraph_real_t x = MATRIX(*res, i, 0); + igraph_real_t y = MATRIX(*res, i, 1); + if (x < min_x) { + min_x = x; + } else if (x > max_x) { + max_x = x; + } + if (y < min_y) { + min_y = y; + } else if (y > max_y) { + max_y = y; + } + } + } + + for (igraph_int_t i = 0; i < no_tries; i++) { + double phi = 2 * M_PI / no_tries * i; + VECTOR(try_x)[i] = cos(phi); + VECTOR(try_y)[i] = sin(phi); + } + + for (igraph_int_t round = 0; round < maxiter + fineiter; round++) { + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_int_shuffle(&perm); + + fine_tuning = round >= maxiter; + if (fine_tuning) { + igraph_real_t fx = fine_tuning_factor * (max_x - min_x); + igraph_real_t fy = fine_tuning_factor * (max_y - min_y); + move_radius = fx < fy ? fx : fy; + } + + for (igraph_int_t p = 0; p < no_nodes; p++) { + igraph_int_t v = VECTOR(perm)[p]; + igraph_vector_int_shuffle(&try_idx); + + for (igraph_int_t t = 0; t < no_tries; t++) { + igraph_real_t diff_energy = 0.0; + igraph_int_t ti = VECTOR(try_idx)[t]; + + /* Try moving it */ + igraph_real_t old_x = MATRIX(*res, v, 0); + igraph_real_t old_y = MATRIX(*res, v, 1); + igraph_real_t new_x = old_x + move_radius * VECTOR(try_x)[ti]; + igraph_real_t new_y = old_y + move_radius * VECTOR(try_y)[ti]; + + if (new_x < -width / 2) { + new_x = -width / 2 - 1e-6; + } + if (new_x > width / 2) { + new_x = width / 2 - 1e-6; + } + if (new_y < -height / 2) { + new_y = -height / 2 - 1e-6; + } + if (new_y > height / 2) { + new_y = height / 2 - 1e-6; + } + + if (w_node_dist != 0) { + for (igraph_int_t u = 0; u < no_nodes; u++) { + igraph_real_t odx, ody, odist2, dx, dy, dist2; + if (u == v) { + continue; + } + odx = old_x - MATRIX(*res, u, 0); + ody = old_y - MATRIX(*res, u, 1); + dx = new_x - MATRIX(*res, u, 0); + dy = new_y - MATRIX(*res, u, 1); + odist2 = odx * odx + ody * ody; + dist2 = dx * dx + dy * dy; + diff_energy += w_node_dist / dist2 - w_node_dist / odist2; + } + } + + if (w_borderlines != 0) { + igraph_real_t odx1 = width / 2 - old_x, odx2 = old_x + width / 2; + igraph_real_t ody1 = height / 2 - old_y, ody2 = old_y + height / 2; + igraph_real_t dx1 = width / 2 - new_x, dx2 = new_x + width / 2; + igraph_real_t dy1 = height / 2 - new_y, dy2 = new_y + height / 2; + if (odx1 < 0) { + odx1 = 2; + } if (odx2 < 0) { + odx2 = 2; + } + if (ody1 < 0) { + ody1 = 2; + } if (ody2 < 0) { + ody2 = 2; + } + if (dx1 < 0) { + dx1 = 2; + } if (dx2 < 0) { + dx2 = 2; + } + if (dy1 < 0) { + dy1 = 2; + } if (dy2 < 0) { + dy2 = 2; + } + diff_energy -= w_borderlines * + (1.0 / (odx1 * odx1) + 1.0 / (odx2 * odx2) + + 1.0 / (ody1 * ody1) + 1.0 / (ody2 * ody2)); + diff_energy += w_borderlines * + (1.0 / (dx1 * dx1) + 1.0 / (dx2 * dx2) + + 1.0 / (dy1 * dy1) + 1.0 / (dy2 * dy2)); + } + + if (w_edge_lengths != 0) { + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t len = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < len; j++) { + igraph_int_t u = VECTOR(neis)[j]; + igraph_real_t odx = old_x - MATRIX(*res, u, 0); + igraph_real_t ody = old_y - MATRIX(*res, u, 1); + igraph_real_t odist2 = odx * odx + ody * ody; + igraph_real_t dx = new_x - MATRIX(*res, u, 0); + igraph_real_t dy = new_y - MATRIX(*res, u, 1); + igraph_real_t dist2 = dx * dx + dy * dy; + diff_energy += w_edge_lengths * (dist2 - odist2); + } + } + + if (w_edge_crossings != 0) { + igraph_int_t no = 0; + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + igraph_int_t len = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < len; j++) { + igraph_int_t u = VECTOR(neis)[j]; + igraph_real_t u_x = MATRIX(*res, u, 0); + igraph_real_t u_y = MATRIX(*res, u, 1); + igraph_int_t e; + for (e = 0; e < no_edges; e++) { + igraph_int_t u1 = IGRAPH_FROM(graph, e); + igraph_int_t u2 = IGRAPH_TO(graph, e); + igraph_real_t u1_x, u1_y, u2_x, u2_y; + if (u1 == v || u2 == v || u1 == u || u2 == u) { + continue; + } + u1_x = MATRIX(*res, u1, 0); + u1_y = MATRIX(*res, u1, 1); + u2_x = MATRIX(*res, u2, 0); + u2_y = MATRIX(*res, u2, 1); + no -= igraph_i_layout_segments_intersect(old_x, old_y, u_x, u_y, + u1_x, u1_y, u2_x, u2_y); + no += igraph_i_layout_segments_intersect(new_x, new_y, u_x, u_y, + u1_x, u1_y, u2_x, u2_y); + } + } + diff_energy += w_edge_crossings * no; + } + + if (w_node_edge_dist != 0 && fine_tuning) { + /* All non-incident edges from the moved 'v' */ + for (igraph_int_t e = 0; e < no_edges; e++) { + igraph_int_t u1 = IGRAPH_FROM(graph, e); + igraph_int_t u2 = IGRAPH_TO(graph, e); + igraph_real_t u1_x, u1_y, u2_x, u2_y, d_ev; + if (u1 == v || u2 == v) { + continue; + } + u1_x = MATRIX(*res, u1, 0); + u1_y = MATRIX(*res, u1, 1); + u2_x = MATRIX(*res, u2, 0); + u2_y = MATRIX(*res, u2, 1); + d_ev = igraph_i_layout_point_segment_dist2( + old_x, old_y, u1_x, u1_y, u2_x, u2_y); + diff_energy -= w_node_edge_dist / d_ev; + d_ev = igraph_i_layout_point_segment_dist2( + new_x, new_y, u1_x, u1_y, u2_x, u2_y); + diff_energy += w_node_edge_dist / d_ev; + } + + /* All other nodes from all of v's incident edges */ + IGRAPH_CHECK(igraph_incident(graph, &neis, v, IGRAPH_ALL, IGRAPH_NO_LOOPS)); + igraph_int_t no = igraph_vector_int_size(&neis); + for (igraph_int_t e = 0; e < no; e++) { + igraph_int_t mye = VECTOR(neis)[e]; + igraph_int_t u = IGRAPH_OTHER(graph, mye, v); + igraph_real_t u_x = MATRIX(*res, u, 0); + igraph_real_t u_y = MATRIX(*res, u, 1); + for (igraph_int_t w = 0; w < no_nodes; w++) { + igraph_real_t w_x, w_y, d_ev; + if (w == v || w == u) { + continue; + } + w_x = MATRIX(*res, w, 0); + w_y = MATRIX(*res, w, 1); + d_ev = igraph_i_layout_point_segment_dist2( + w_x, w_y, old_x, old_y, u_x, u_y); + diff_energy -= w_node_edge_dist / d_ev; + d_ev = igraph_i_layout_point_segment_dist2( + w_x, w_y, new_x, new_y, u_x, u_y); + diff_energy += w_node_edge_dist / d_ev; + } + } + } /* w_node_edge_dist != 0 && fine_tuning */ + + if (diff_energy < 0 || + (!fine_tuning && RNG_UNIF01() < exp(-diff_energy / move_radius))) { + MATRIX(*res, v, 0) = new_x; + MATRIX(*res, v, 1) = new_y; + if (new_x < min_x) { + min_x = new_x; + } else if (new_x > max_x) { + max_x = new_x; + } + if (new_y < min_y) { + min_y = new_y; + } else if (new_y > max_y) { + max_y = new_y; + } + } + + } /* t < no_tries */ + + } /* p < no_nodes */ + + move_radius *= cool_fact; + + } /* round < maxiter */ + + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&try_idx); + igraph_vector_destroy(&try_x); + igraph_vector_destroy(&try_y); + igraph_vector_int_destroy(&perm); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/drl/DensityGrid.cpp b/src/layout/drl/DensityGrid.cpp new file mode 100644 index 0000000..ce60292 --- /dev/null +++ b/src/layout/drl/DensityGrid.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the DensityGrid.h class +// This code is modified from the original code by B.N. Wylie + +#include "drl_Node.h" +#include "DensityGrid.h" + +#include +#include +#include + +using namespace std; + +#define GET_BIN(y, x) (Bins[y*GRID_SIZE+x]) + +namespace drl { + +//******************************************************* +// Density Grid Destructor -- deallocates memory used +// for Density matrix, fall_off matrix, and node deque. + +DensityGrid::~DensityGrid () { + delete[] Density; + delete[] fall_off; + delete[] Bins; +} + +/********************************************* +* Function: Density_Grid::Reset * +* Description: Reset the density grid * +*********************************************/ +// changed from reset to init since we will only +// call this once in the parallel version of layout + +void DensityGrid::Init() { + + Density = new float[GRID_SIZE][GRID_SIZE]; + fall_off = new float[RADIUS * 2 + 1][RADIUS * 2 + 1]; + Bins = new deque[GRID_SIZE * GRID_SIZE]; + + // Clear Grid + int i; + for (i = 0; i < GRID_SIZE; i++) + for (int j = 0; j < GRID_SIZE; j++) { + Density[i][j] = 0; + GET_BIN(i, j).erase(GET_BIN(i, j).begin(), GET_BIN(i, j).end()); + } + + // Compute fall off + for (i = -RADIUS; i <= RADIUS; i++) + for (int j = -RADIUS; j <= RADIUS; j++) { + fall_off[i + RADIUS][j + RADIUS] = (float)((RADIUS - fabs((float)i)) / RADIUS) * + (float)((RADIUS - fabs((float)j)) / RADIUS); + } + +} + +/*************************************************** + * Function: DensityGrid::GetDensity * + * Description: Get_Density from density grid * + **************************************************/ +float DensityGrid::GetDensity(float Nx, float Ny, bool fineDensity) { + deque::iterator BI; + int x_grid, y_grid; + float x_dist, y_dist, distance, density = 0; + int boundary = 10; // boundary around plane + + + /* Where to look */ + x_grid = (int)((Nx + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((Ny + HALF_VIEW + .5) * VIEW_TO_GRID); + + // Check for edges of density grid (10000 is arbitrary high density) + if (x_grid > GRID_SIZE - boundary || x_grid < boundary) { + return 10000; + } + if (y_grid > GRID_SIZE - boundary || y_grid < boundary) { + return 10000; + } + + // Fine density? + if (fineDensity) { + + // Go through nearest bins + for (int i = y_grid - 1; i <= y_grid + 1; i++) + for (int j = x_grid - 1; j <= x_grid + 1; j++) { + + // Look through bin and add fine repulsions + for (BI = GET_BIN(i, j).begin(); BI != GET_BIN(i, j).end(); ++BI) { + x_dist = Nx - (BI->x); + y_dist = Ny - (BI->y); + distance = x_dist * x_dist + y_dist * y_dist; + density += 1e-4 / (distance + 1e-50); + } + } + // Course density + } else { + + // Add rough estimate + density = Density[y_grid][x_grid]; + density *= density; + } + + return density; +} + +/// Wrapper functions for the Add and subtract methods +/// Nodes should all be passed by constant ref + +void DensityGrid::Add(Node &n, bool fineDensity) { + if (fineDensity) { + fineAdd(n); + } else { + Add(n); + } +} + +void DensityGrid::Subtract( Node &n, bool first_add, + bool fine_first_add, bool fineDensity) { + if ( fineDensity && !fine_first_add ) { + fineSubtract (n); + } else if ( !first_add ) { + Subtract(n); + } +} + + +/*************************************************** + * Function: DensityGrid::Subtract * + * Description: Subtract a node from density grid * + **************************************************/ +void DensityGrid::Subtract(Node &N) { + int x_grid, y_grid, diam; + float *den_ptr, *fall_ptr; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + x_grid -= RADIUS; + y_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) ) { + throw runtime_error("Exceeded density grid in DrL."); + } + + /* Subtract density values */ + den_ptr = &Density[y_grid][x_grid]; + fall_ptr = &fall_off[0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) { + *den_ptr++ -= *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } +} + +/*************************************************** + * Function: DensityGrid::Add * + * Description: Add a node to the density grid * + **************************************************/ +void DensityGrid::Add(Node &N) { + + int x_grid, y_grid, diam; + float *den_ptr, *fall_ptr; + + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + + N.sub_x = N.x; + N.sub_y = N.y; + + x_grid -= RADIUS; + y_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) ) { + throw runtime_error("Exceeded density grid in DrL."); + } + + /* Add density values */ + den_ptr = &Density[y_grid][x_grid]; + fall_ptr = &fall_off[0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) { + *den_ptr++ += *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } + +} + +/*************************************************** + * Function: DensityGrid::fineSubtract * + * Description: Subtract a node from bins * + **************************************************/ +void DensityGrid::fineSubtract(Node &N) { + int x_grid, y_grid; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + GET_BIN(y_grid, x_grid).pop_front(); +} + +/*************************************************** + * Function: DensityGrid::fineAdd * + * Description: Add a node to the bins * + **************************************************/ +void DensityGrid::fineAdd(Node &N) { + int x_grid, y_grid; + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + N.sub_x = N.x; + N.sub_y = N.y; + GET_BIN(y_grid, x_grid).push_back(N); +} + +} // namespace drl diff --git a/src/layout/drl/DensityGrid.h b/src/layout/drl/DensityGrid.h new file mode 100644 index 0000000..def0c7a --- /dev/null +++ b/src/layout/drl/DensityGrid.h @@ -0,0 +1,81 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DENSITY_GRID_H__ +#define __DENSITY_GRID_H__ + + +// Compile time adjustable parameters + +#include "drl_layout.h" +#include "drl_Node.h" + +#include + +namespace drl { + +class DensityGrid { + +public: + + // Methods + void Init(); + void Subtract(Node &n, bool first_add, bool fine_first_add, bool fineDensity); + void Add(Node &n, bool fineDensity ); + float GetDensity(float Nx, float Ny, bool fineDensity); + + // Contructor/Destructor + DensityGrid() {}; + ~DensityGrid(); + +private: + + // Private Members + void Subtract( Node &N ); + void Add( Node &N ); + void fineSubtract( Node &N ); + void fineAdd( Node &N ); + + // new dynamic variables -- SBM + float (*fall_off)[RADIUS * 2 + 1]; + float (*Density)[GRID_SIZE]; + std::deque* Bins; + + // old static variables + //float fall_off[RADIUS*2+1][RADIUS*2+1]; + //float Density[GRID_SIZE][GRID_SIZE]; + //deque Bins[GRID_SIZE][GRID_SIZE]; +}; + +} // namespace drl + +#endif // __DENSITY_GRID_H__ diff --git a/src/layout/drl/DensityGrid_3d.cpp b/src/layout/drl/DensityGrid_3d.cpp new file mode 100644 index 0000000..7d06cae --- /dev/null +++ b/src/layout/drl/DensityGrid_3d.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the DensityGrid.h class +// This code is modified from the original code by B.N. Wylie + +#include "drl_Node_3d.h" +#include "DensityGrid_3d.h" + +#include +#include +#include + +using namespace std; + +#define GET_BIN(z, y, x) (Bins[(z*GRID_SIZE+y)*GRID_SIZE+x]) + +namespace drl3d { + +//******************************************************* +// Density Grid Destructor -- deallocates memory used +// for Density matrix, fall_off matrix, and node deque. + +DensityGrid::~DensityGrid () { + delete[] Density; + delete[] fall_off; + delete[] Bins; +} + +/********************************************* +* Function: Density_Grid::Reset * +* Description: Reset the density grid * +*********************************************/ +// changed from reset to init since we will only +// call this once in the parallel version of layout + +void DensityGrid::Init() { + + Density = new float[GRID_SIZE][GRID_SIZE][GRID_SIZE]; + fall_off = new float[RADIUS * 2 + 1][RADIUS * 2 + 1][RADIUS * 2 + 1]; + Bins = new deque[GRID_SIZE * GRID_SIZE * GRID_SIZE]; + + // Clear Grid + int i; + for (i = 0; i < GRID_SIZE; i++) + for (int j = 0; j < GRID_SIZE; j++) + for (int k = 0; k < GRID_SIZE; k++) { + Density[i][j][k] = 0; + GET_BIN(i, j, k).erase(GET_BIN(i, j, k).begin(), GET_BIN(i, j, k).end()); + } + + // Compute fall off + for (i = -RADIUS; i <= RADIUS; i++) + for (int j = -RADIUS; j <= RADIUS; j++) + for (int k = -RADIUS; k <= RADIUS; k++) { + fall_off[i + RADIUS][j + RADIUS][k + RADIUS] = + (float)((RADIUS - fabs((float)i)) / RADIUS) * + (float)((RADIUS - fabs((float)j)) / RADIUS) * + (float)((RADIUS - fabs((float)k)) / RADIUS); + } + +} + + +/*************************************************** + * Function: DensityGrid::GetDensity * + * Description: Get_Density from density grid * + **************************************************/ +float DensityGrid::GetDensity(float Nx, float Ny, float Nz, bool fineDensity) { + deque::iterator BI; + int x_grid, y_grid, z_grid; + float x_dist, y_dist, z_dist, distance, density = 0; + int boundary = 10; // boundary around plane + + + /* Where to look */ + x_grid = (int)((Nx + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((Ny + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((Nz + HALF_VIEW + .5) * VIEW_TO_GRID); + + // Check for edges of density grid (10000 is arbitrary high density) + if (x_grid > GRID_SIZE - boundary || x_grid < boundary) { + return 10000; + } + if (y_grid > GRID_SIZE - boundary || y_grid < boundary) { + return 10000; + } + if (z_grid > GRID_SIZE - boundary || z_grid < boundary) { + return 10000; + } + + // Fine density? + if (fineDensity) { + + // Go through nearest bins + for (int k = z_grid - 1; k <= z_grid + 1; k++) + for (int i = y_grid - 1; i <= y_grid + 1; i++) + for (int j = x_grid - 1; j <= x_grid + 1; j++) { + + // Look through bin and add fine repulsions + for (BI = GET_BIN(k, i, j).begin(); BI < GET_BIN(k, i, j).end(); ++BI) { + x_dist = Nx - (BI->x); + y_dist = Ny - (BI->y); + z_dist = Nz - (BI->z); + distance = x_dist * x_dist + y_dist * y_dist + z_dist * z_dist; + density += 1e-4 / (distance + 1e-50); + } + } + + // Course density + } else { + + // Add rough estimate + density = Density[z_grid][y_grid][x_grid]; + density *= density; + } + + return density; +} + +/// Wrapper functions for the Add and subtract methods +/// Nodes should all be passed by constant ref + +void DensityGrid::Add(Node &n, bool fineDensity) { + if (fineDensity) { + fineAdd(n); + } else { + Add(n); + } +} + +void DensityGrid::Subtract( Node &n, bool first_add, + bool fine_first_add, bool fineDensity) { + if ( fineDensity && !fine_first_add ) { + fineSubtract (n); + } else if ( !first_add ) { + Subtract(n); + } +} + + +/*************************************************** + * Function: DensityGrid::Subtract * + * Description: Subtract a node from density grid * + **************************************************/ +void DensityGrid::Subtract(Node &N) { + int x_grid, y_grid, z_grid, diam; + float *den_ptr, *fall_ptr; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.sub_z + HALF_VIEW + .5) * VIEW_TO_GRID); + x_grid -= RADIUS; + y_grid -= RADIUS; + z_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) || + (z_grid >= GRID_SIZE) || (z_grid < 0) ) { + throw runtime_error("Exceeded density grid in DrL."); + } + + /* Subtract density values */ + den_ptr = &Density[z_grid][y_grid][x_grid]; + fall_ptr = &fall_off[0][0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) + for (int k = 0; k <= diam; k++) { + *den_ptr++ -= *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } +} + +/*************************************************** + * Function: DensityGrid::Add * + * Description: Add a node to the density grid * + **************************************************/ +void DensityGrid::Add(Node &N) { + + int x_grid, y_grid, z_grid, diam; + float *den_ptr, *fall_ptr; + + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.z + HALF_VIEW + .5) * VIEW_TO_GRID); + + N.sub_x = N.x; + N.sub_y = N.y; + N.sub_z = N.z; + + x_grid -= RADIUS; + y_grid -= RADIUS; + z_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) || + (z_grid >= GRID_SIZE) || (z_grid < 0) ) { + throw runtime_error("Exceeded density grid in DrL."); + } + + /* Add density values */ + den_ptr = &Density[z_grid][y_grid][x_grid]; + fall_ptr = &fall_off[0][0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) + for (int k = 0; k <= diam; k++) { + *den_ptr++ += *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } + +} + +/*************************************************** + * Function: DensityGrid::fineSubtract * + * Description: Subtract a node from bins * + **************************************************/ +void DensityGrid::fineSubtract(Node &N) { + int x_grid, y_grid, z_grid; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.sub_z + HALF_VIEW + .5) * VIEW_TO_GRID); + GET_BIN(z_grid, y_grid, x_grid).pop_front(); +} + +/*************************************************** + * Function: DensityGrid::fineAdd * + * Description: Add a node to the bins * + **************************************************/ +void DensityGrid::fineAdd(Node &N) { + int x_grid, y_grid, z_grid; + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.z + HALF_VIEW + .5) * VIEW_TO_GRID); + N.sub_x = N.x; + N.sub_y = N.y; + N.sub_z = N.z; + GET_BIN(z_grid, y_grid, x_grid).push_back(N); +} + +} // namespace drl3d diff --git a/src/layout/drl/DensityGrid_3d.h b/src/layout/drl/DensityGrid_3d.h new file mode 100644 index 0000000..6ad1b49 --- /dev/null +++ b/src/layout/drl/DensityGrid_3d.h @@ -0,0 +1,81 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DENSITY_GRID_H__ +#define __DENSITY_GRID_H__ + + +// Compile time adjustable parameters + +#include "drl_layout_3d.h" +#include "drl_Node_3d.h" + +#include + +namespace drl3d { + +class DensityGrid { + +public: + + // Methods + void Init(); + void Subtract(Node &n, bool first_add, bool fine_first_add, bool fineDensity); + void Add(Node &n, bool fineDensity ); + float GetDensity(float Nx, float Ny, float Nz, bool fineDensity); + + // Contructor/Destructor + DensityGrid() {}; + ~DensityGrid(); + +private: + + // Private Members + void Subtract( Node &N ); + void Add( Node &N ); + void fineSubtract( Node &N ); + void fineAdd( Node &N ); + + // new dynamic variables -- SBM + float (*fall_off)[RADIUS * 2 + 1][RADIUS * 2 + 1]; + float (*Density)[GRID_SIZE][GRID_SIZE]; + std::deque* Bins; + + // old static variables + //float fall_off[RADIUS*2+1][RADIUS*2+1]; + //float Density[GRID_SIZE][GRID_SIZE]; + //deque Bins[GRID_SIZE][GRID_SIZE]; +}; + +} // namespace drl3d + +#endif // __DENSITY_GRID_H__ diff --git a/src/layout/drl/drl_Node.h b/src/layout/drl/drl_Node.h new file mode 100644 index 0000000..f7a6cce --- /dev/null +++ b/src/layout/drl/drl_Node.h @@ -0,0 +1,70 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __NODE_H__ +#define __NODE_H__ + +#include + +// The node class contains information about a given node for +// use by the density server process. + +// structure coord used to pass position information between +// density server and graph class + +namespace drl { + +class Node { + +public: + + bool fixed; // if true do not change the + igraph_int_t id; + + // position of this node + float x, y; + float sub_x, sub_y; + float energy; + +public: + + Node( igraph_int_t node_id ) { + x = y = 0.0; fixed = false; + id = node_id; + } + ~Node() { } + +}; + +} // namespace drl + +#endif //__NODE_H__ diff --git a/src/layout/drl/drl_Node_3d.h b/src/layout/drl/drl_Node_3d.h new file mode 100644 index 0000000..4be0eab --- /dev/null +++ b/src/layout/drl/drl_Node_3d.h @@ -0,0 +1,70 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __NODE_H__ +#define __NODE_H__ + +#include + +// The node class contains information about a given node for +// use by the density server process. + +// structure coord used to pass position information between +// density server and graph class + +namespace drl3d { + +class Node { + +public: + + bool fixed; // if true do not change the + igraph_int_t id; + + // position of this node + float x, y, z; + float sub_x, sub_y, sub_z; + float energy; + +public: + + Node( igraph_int_t node_id ) { + x = y = z = 0.0; fixed = false; + id = node_id; + } + ~Node() { } + +}; + +} // namespace drl3d + +#endif //__NODE_H__ diff --git a/src/layout/drl/drl_graph.cpp b/src/layout/drl/drl_graph.cpp new file mode 100644 index 0000000..64028a6 --- /dev/null +++ b/src/layout/drl/drl_graph.cpp @@ -0,0 +1,1270 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the master class + + +#include +#include +#include + +using namespace std; + +#include "drl_graph.h" +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "core/interruption.h" + +namespace drl { + +// constructor -- initializes the schedule variables (as in +// graph constructor) + +// graph::graph ( int proc_id, int tot_procs, char *int_file ) +// { + +// // MPI parameters +// myid = proc_id; +// num_procs = tot_procs; + +// // initial annealing parameters +// STAGE = 0; +// iterations = 0; +// temperature = 2000; +// attraction = 10; +// damping_mult = 1.0; +// min_edges = 20; +// first_add = fine_first_add = true; +// fineDensity = false; + +// // Brian's original Vx schedule +// liquid.iterations = 200; +// liquid.temperature = 2000; +// liquid.attraction = 2; +// liquid.damping_mult = 1.0; +// liquid.time_elapsed = 0; + +// expansion.iterations = 200; +// expansion.temperature = 2000; +// expansion.attraction = 10; +// expansion.damping_mult = 1.0; +// expansion.time_elapsed = 0; + +// cooldown.iterations = 200; +// cooldown.temperature = 2000; +// cooldown.attraction = 1; +// cooldown.damping_mult = .1; +// cooldown.time_elapsed = 0; + +// crunch.iterations = 50; +// crunch.temperature = 250; +// crunch.attraction = 1; +// crunch. damping_mult = .25; +// crunch.time_elapsed = 0; + +// simmer.iterations = 100; +// simmer.temperature = 250; +// simmer.attraction = .5; +// simmer.damping_mult = 0.0; +// simmer.time_elapsed = 0; + +// // scan .int file for node info +// scan_int ( int_file ); + +// // populate node positions and ids +// positions.reserve ( num_nodes ); +// map < int, int >::iterator cat_iter; +// for ( cat_iter = id_catalog.begin(); +// cat_iter != id_catalog.end(); +// cat_iter++ ) +// positions.push_back ( Node( cat_iter->first ) ); + +// /* +// // output positions .ids for debugging +// for ( int id = 0; id < num_nodes; id++ ) +// cout << positions[id].id << endl; +// */ + +// // read .int file for graph info +// read_int ( int_file ); + +// // initialize density server +// density_server.Init(); + +// } + +graph::graph(const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + myid = 0; + num_procs = 1; + + STAGE = 0; + iterations = options->init_iterations; + temperature = options->init_temperature; + attraction = options->init_attraction; + damping_mult = options->init_damping_mult; + min_edges = 20; + first_add = fine_first_add = true; + fineDensity = false; + + // Brian's original Vx schedule + liquid.iterations = options->liquid_iterations; + liquid.temperature = options->liquid_temperature; + liquid.attraction = options->liquid_attraction; + liquid.damping_mult = options->liquid_damping_mult; + liquid.time_elapsed = 0; + + expansion.iterations = options->expansion_iterations; + expansion.temperature = options->expansion_temperature; + expansion.attraction = options->expansion_attraction; + expansion.damping_mult = options->expansion_damping_mult; + expansion.time_elapsed = 0; + + cooldown.iterations = options->cooldown_iterations; + cooldown.temperature = options->cooldown_temperature; + cooldown.attraction = options->cooldown_attraction; + cooldown.damping_mult = options->cooldown_damping_mult; + cooldown.time_elapsed = 0; + + crunch.iterations = options->crunch_iterations; + crunch.temperature = options->crunch_temperature; + crunch.attraction = options->crunch_attraction; + crunch.damping_mult = options->crunch_damping_mult; + crunch.time_elapsed = 0; + + simmer.iterations = options->simmer_iterations; + simmer.temperature = options->simmer_temperature; + simmer.attraction = options->simmer_attraction; + simmer.damping_mult = options->simmer_damping_mult; + simmer.time_elapsed = 0; + + // scan .int file for node info + highest_sim = 1.0; + num_nodes = igraph_vcount(igraph); + igraph_int_t no_of_edges = igraph_ecount(igraph); + for (igraph_int_t i = 0; i < num_nodes; i++) { + id_catalog[i] = 1; + } + map::iterator cat_iter; + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); cat_iter++) { + cat_iter->second = cat_iter->first; + } + + // populate node positions and ids + positions.reserve ( num_nodes ); + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); + cat_iter++ ) { + positions.push_back ( Node( cat_iter->first ) ); + } + + // read .int file for graph info + igraph_int_t node_1, node_2; + igraph_real_t weight; + for (igraph_int_t i = 0; i < no_of_edges; i++) { + node_1 = IGRAPH_FROM(igraph, i); + node_2 = IGRAPH_TO(igraph, i); + weight = weights ? VECTOR(*weights)[i] : 1.0 ; + (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; + (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; + } + + // initialize density server + density_server.Init(); + +} + +// The following subroutine scans the .int file for the following +// information: number nodes, node ids, and highest similarity. The +// corresponding graph globals are populated: num_nodes, id_catalog, +// and highest_sim. + +// void graph::scan_int ( char *filename ) +// { + +// cout << "Proc. " << myid << " scanning .int file ..." << endl; + +// // Open (sim) File +// ifstream fp ( filename ); +// if ( !fp ) +// { +// cout << "Error: could not open " << filename << ". Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // Read file, parse, and add into data structure +// int id1, id2; +// float edge_weight; +// highest_sim = -1.0; +// while ( !fp.eof () ) +// { +// fp >> id1 >> id2 >> edge_weight; + +// // ignore negative weights! +// if ( edge_weight <= 0 ) +// { +// cout << "Error: found negative edge weight in " << filename << ". Program stopped." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// if ( highest_sim < edge_weight ) +// highest_sim = edge_weight; + +// id_catalog[id1] = 1; +// id_catalog[id2] = 1; +// } + +// fp.close(); + +// if ( id_catalog.size() == 0 ) +// { +// cout << "Error: Proc. " << myid << ": " << filename << " is empty. Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // label nodes with sequential integers starting at 0 +// map< int, int>::iterator cat_iter; +// int id_label; +// for ( cat_iter = id_catalog.begin(), id_label = 0; +// cat_iter != id_catalog.end(); cat_iter++, id_label++ ) +// cat_iter->second = id_label; + +// /* +// // output id_catalog for debugging: +// for ( cat_iter = id_catalog.begin(); +// cat_iter != id_catalog.end(); +// cat_iter++ ) +// cout << cat_iter->first << "\t" << cat_iter->second << endl; +// */ + +// num_nodes = id_catalog.size(); +// } + +// read in .parms file, if present + +/* +void graph::read_parms ( char *parms_file ) +{ + + // read from .parms file + ifstream parms_in ( parms_file ); + if ( !parms_in ) + { + cout << "Error: could not open .parms file! Program stopped." << endl; + #ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); + #else + exit (1); + #endif + } + + cout << "Processor " << myid << " reading .parms file." << endl; + + // read in stage parameters + string parm_label; // this is ignored in the .parms file + + // initial parameters + parms_in >> parm_label >> iterations; + parms_in >> parm_label >> temperature; + parms_in >> parm_label >> attraction; + parms_in >> parm_label >> damping_mult; + + // liquid stage + parms_in >> parm_label >> liquid.iterations; + parms_in >> parm_label >> liquid.temperature; + parms_in >> parm_label >> liquid.attraction; + parms_in >> parm_label >> liquid.damping_mult; + + // expansion stage + parms_in >> parm_label >> expansion.iterations; + parms_in >> parm_label >> expansion.temperature; + parms_in >> parm_label >> expansion.attraction; + parms_in >> parm_label >> expansion.damping_mult; + + // cooldown stage + parms_in >> parm_label >> cooldown.iterations; + parms_in >> parm_label >> cooldown.temperature; + parms_in >> parm_label >> cooldown.attraction; + parms_in >> parm_label >> cooldown.damping_mult; + + // crunch stage + parms_in >> parm_label >> crunch.iterations; + parms_in >> parm_label >> crunch.temperature; + parms_in >> parm_label >> crunch.attraction; + parms_in >> parm_label >> crunch.damping_mult; + + // simmer stage + parms_in >> parm_label >> simmer.iterations; + parms_in >> parm_label >> simmer.temperature; + parms_in >> parm_label >> simmer.attraction; + parms_in >> parm_label >> simmer.damping_mult; + + parms_in.close(); + + // print out parameters for double checking + if ( myid == 0 ) + { + cout << "Processor 0 reports the following inputs:" << endl; + cout << "inital.iterations = " << iterations << endl; + cout << "initial.temperature = " << temperature << endl; + cout << "initial.attraction = " << attraction << endl; + cout << "initial.damping_mult = " << damping_mult << endl; + cout << " ..." << endl; + cout << "liquid.iterations = " << liquid.iterations << endl; + cout << "liquid.temperature = " << liquid.temperature << endl; + cout << "liquid.attraction = " << liquid.attraction << endl; + cout << "liquid.damping_mult = " << liquid.damping_mult << endl; + cout << " ..." << endl; + cout << "simmer.iterations = " << simmer.iterations << endl; + cout << "simmer.temperature = " << simmer.temperature << endl; + cout << "simmer.attraction = " << simmer.attraction << endl; + cout << "simmer.damping_mult = " << simmer.damping_mult << endl; + } + +} +*/ + +// init_parms -- this subroutine initializes the edge_cut variables +// used in the original VxOrd starting with the edge_cut parameter. +// In our version, edge_cut = 0 means no cutting, 1 = maximum cut. +// We also set the random seed here. + +void graph::init_parms ( int rand_seed, float edge_cut, float real_parm ) { + IGRAPH_UNUSED(rand_seed); + + // first we translate edge_cut the former tcl sliding scale + //CUT_END = cut_length_end = 39000.0 * (1.0 - edge_cut) + 1000.0; + CUT_END = cut_length_end = 40000.0 * (1.0 - edge_cut); + + // cut_length_end cannot actually be 0 + if ( cut_length_end <= 1.0 ) { + cut_length_end = 1.0; + } + + float cut_length_start = 4.0 * cut_length_end; + + // now we set the parameters used by ReCompute + cut_off_length = cut_length_start; + cut_rate = ( cut_length_start - cut_length_end ) / 400.0; + + // finally set the number of iterations to leave .real coords fixed + igraph_int_t full_comp_iters; + full_comp_iters = liquid.iterations + expansion.iterations + + cooldown.iterations + crunch.iterations + 3; + + // adjust real parm to iterations (do not enter simmer halfway) + if ( real_parm < 0 ) { + real_iterations = (igraph_int_t)real_parm; + } else if ( real_parm == 1) { + real_iterations = full_comp_iters + simmer.iterations + 100; + } else { + real_iterations = (igraph_int_t)(real_parm * full_comp_iters); + } + + tot_iterations = 0; + if ( real_iterations > 0 ) { + real_fixed = true; + } else { + real_fixed = false; + } + + // calculate total expected iterations (for progress bar display) + tot_expected_iterations = liquid.iterations + + expansion.iterations + cooldown.iterations + + crunch.iterations + simmer.iterations; + + /* + // output edge_cutting parms (for debugging) + cout << "Processor " << myid << ": " + << "cut_length_end = CUT_END = " << cut_length_end + << ", cut_length_start = " << cut_length_start + << ", cut_rate = " << cut_rate << endl; + */ + + // set random seed + // srand ( rand_seed ); // Don't need this in igraph + +} + +void graph::init_parms(const igraph_layout_drl_options_t *options) { + double rand_seed = 0.0; + double real_in = -1.0; + init_parms(rand_seed, options->edge_cut, real_in); +} + +// The following subroutine reads a .real file to obtain initial +// coordinates. If a node is missing coordinates the coordinates +// are computed + +// void graph::read_real ( char *real_file ) +// { +// cout << "Processor " << myid << " reading .real file ..." << endl; + +// // read in .real file and mark as fixed +// ifstream real_in ( real_file ); +// if ( !real_in ) +// { +// cout << "Error: proc. " << myid << " could not open .real file." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// int real_id; +// float real_x, real_y; +// while ( !real_in.eof () ) +// { +// real_id = -1; +// real_in >> real_id >> real_x >> real_y; +// if ( real_id >= 0 ) +// { +// positions[id_catalog[real_id]].x = real_x; +// positions[id_catalog[real_id]].y = real_y; +// positions[id_catalog[real_id]].fixed = true; + +// /* +// // output positions read (for debugging) +// cout << id_catalog[real_id] << " (" << positions[id_catalog[real_id]].x +// << ", " << positions[id_catalog[real_id]].y << ") " +// << positions[id_catalog[real_id]].fixed << endl; +// */ + +// // add node to density grid +// if ( real_iterations > 0 ) +// density_server.Add ( positions[id_catalog[real_id]], fineDensity ); +// } + +// } + +// real_in.close(); +// } + +int graph::read_real(const igraph_matrix_t *real_mat) { + igraph_int_t n = igraph_matrix_nrow(real_mat); + for (igraph_int_t i = 0; i < n; i++) { + positions[id_catalog[i]].x = MATRIX(*real_mat, i, 0); + positions[id_catalog[i]].y = MATRIX(*real_mat, i, 1); + positions[id_catalog[i]].fixed = false; + + if ( real_iterations > 0 ) { + density_server.Add ( positions[id_catalog[i]], fineDensity ); + } + } + + return 0; +} + +// The read_part_int subroutine reads the .int +// file produced by convert_sim and gathers the nodes and their +// neighbors in the range start_ind to end_ind. + +// void graph::read_int ( char *file_name ) +// { + +// ifstream int_file; + +// int_file.open ( file_name ); +// if ( !int_file ) +// { +// cout << "Error (worker process " << myid << "): could not open .int file." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// cout << "Processor " << myid << " reading .int file ..." << endl; + +// int node_1, node_2; +// float weight; + +// while ( !int_file.eof() ) +// { +// weight = 0; // all weights should be >= 0 +// int_file >> node_1 >> node_2 >> weight; +// if ( weight ) // otherwise we are at end of file +// // or it is a self-connected node +// { +// // normalization from original vxord +// weight /= highest_sim; +// weight = weight*fabs(weight); + +// // initialize graph +// if ( ( node_1 % num_procs ) == myid ) +// (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; +// if ( ( node_2 % num_procs ) == myid ) +// (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; +// } +// } +// int_file.close(); + +// /* +// // the following code outputs the contents of the neighbors structure +// // (to be used for debugging) + +// map >::iterator i; +// map::iterator j; + +// for ( i = neighbors.begin(); i != neighbors.end(); i++ ) { +// cout << myid << ": " << i->first << " "; +// for (j = (i->second).begin(); j != (i->second).end(); j++ ) +// cout << j->first << " (" << j->second << ") "; +// cout << endl; +// } +// */ + +// } + +/********************************************* + * Function: ReCompute * + * Description: Compute the graph locations * + * Modified from original code by B. Wylie * + ********************************************/ + +int graph::ReCompute( ) { + + // carryover from original VxOrd + int MIN = 1; + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + /* igraph progress report */ + float progress = (tot_iterations * 100.0 / tot_expected_iterations); + + switch (STAGE) { + case 0: + if (iterations == 0) { + IGRAPH_PROGRESS("DrL layout (initialization stage)", progress, 0); + } else { + IGRAPH_PROGRESS("DrL layout (liquid stage)", progress, 0); + } + break; + case 1: + IGRAPH_PROGRESS("DrL layout (expansion stage)", progress, 0); break; + case 2: + IGRAPH_PROGRESS("DrL layout (cooldown and cluster phase)", progress, 0); break; + case 3: + IGRAPH_PROGRESS("DrL layout (crunch phase)", progress, 0); break; + case 5: + IGRAPH_PROGRESS("DrL layout (simmer phase)", progress, 0); break; + case 6: + IGRAPH_PROGRESS("DrL layout (final phase)", 100.0, 0); break; + default: + IGRAPH_PROGRESS("DrL layout (unknown phase)", 0.0, 0); break; + } + + /* Compute Energies for individual nodes */ + update_nodes (); + + // check to see if we need to free fixed nodes + tot_iterations++; + if ( tot_iterations >= real_iterations ) { + real_fixed = false; + } + + + // **************************************** + // AUTOMATIC CONTROL SECTION + // **************************************** + + // STAGE 0: LIQUID + if (STAGE == 0) { + + if ( iterations == 0 ) { + start_time = time( NULL ); +// if ( myid == 0 ) +// cout << "Entering liquid stage ..."; + } + + if (iterations < liquid.iterations) { + temperature = liquid.temperature; + attraction = liquid.attraction; + damping_mult = liquid.damping_mult; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + liquid.time_elapsed = liquid.time_elapsed + (stop_time - start_time); + temperature = expansion.temperature; + attraction = expansion.attraction; + damping_mult = expansion.damping_mult; + iterations = 0; + + // go to next stage + STAGE = 1; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering expansion stage ..."; + } + } + + // STAGE 1: EXPANSION + if (STAGE == 1) { + + if (iterations < expansion.iterations) { + + // Play with vars + if (attraction > 1) { + attraction -= .05f; + } + if (min_edges > 12) { + min_edges -= .05f; + } + cut_off_length -= cut_rate; + if (damping_mult > .1) { + damping_mult -= .005f; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + + } else { + + stop_time = time( NULL ); + expansion.time_elapsed = expansion.time_elapsed + (stop_time - start_time); + min_edges = 12; + damping_mult = cooldown.damping_mult; + + STAGE = 2; + attraction = cooldown.attraction; + temperature = cooldown.temperature; + iterations = 0; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering cool-down stage ..."; + } + } + + // STAGE 2: Cool down and cluster + else if (STAGE == 2) { + + if (iterations < cooldown.iterations) { + + // Reduce temperature + if (temperature > 50) { + temperature -= 10; + } + + // Reduce cut length + if (cut_off_length > cut_length_end) { + cut_off_length -= cut_rate * 2; + } + if (min_edges > MIN) { + min_edges -= .2f; + } + //min_edges = 99; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + cooldown.time_elapsed = cooldown.time_elapsed + (stop_time - start_time); + cut_off_length = cut_length_end; + temperature = crunch.temperature; + damping_mult = crunch.damping_mult; + min_edges = MIN; + //min_edges = 99; // In other words: no more cutting + + STAGE = 3; + iterations = 0; + attraction = crunch.attraction; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering crunch stage ..."; + } + } + + // STAGE 3: Crunch + else if (STAGE == 3) { + + if (iterations < crunch.iterations) { + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + + stop_time = time( NULL ); + crunch.time_elapsed = crunch.time_elapsed + (stop_time - start_time); + iterations = 0; + temperature = simmer.temperature; + attraction = simmer.attraction; + damping_mult = simmer.damping_mult; + min_edges = 99; + fineDensity = true; + + STAGE = 5; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering simmer stage ..."; + } + } + + // STAGE 5: Simmer + else if ( STAGE == 5 ) { + + if (iterations < simmer.iterations) { + if (temperature > 50) { + temperature -= 2; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + stop_time = time( NULL ); + simmer.time_elapsed = simmer.time_elapsed + (stop_time - start_time); + + STAGE = 6; + +// if ( myid == 0 ) +// cout << "Layout calculation completed in " << +// ( liquid.time_elapsed + expansion.time_elapsed + +// cooldown.time_elapsed + crunch.time_elapsed + +// simmer.time_elapsed ) +// << " seconds (not including I/O)." +// << endl; + } + } + + // STAGE 6: All Done! + else if ( STAGE == 6) { + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + return 0; + } + + // **************************************** + // END AUTOMATIC CONTROL SECTION + // **************************************** + + // Still need more recomputation + return 1; + +} + +// update_nodes -- this function will complete the primary node update +// loop in layout's recompute routine. It follows exactly the same +// sequence to ensure similarity of parallel layout to the standard layout + +void graph::update_nodes ( ) { + + vector node_indices; // node list of nodes currently being updated + float old_positions[2 * MAX_PROCS]; // positions before update + float new_positions[2 * MAX_PROCS]; // positions after update + + bool all_fixed; // check if all nodes are fixed + + // initial node list consists of 0,1,...,num_procs + for ( int i = 0; i < num_procs; i++ ) { + node_indices.push_back( i ); + } + + // next we calculate the number of nodes there would be if the + // num_nodes by num_procs schedule grid were perfectly square + igraph_int_t square_num_nodes = (igraph_int_t)(num_procs + num_procs * floor ((float)(num_nodes - 1) / (float)num_procs )); + + for ( igraph_int_t i = myid; i < square_num_nodes; i += num_procs ) { + + // get old positions + get_positions ( node_indices, old_positions ); + + // default new position is old position + get_positions ( node_indices, new_positions ); + + if ( i < num_nodes ) { + // calculate node energy possibilities + if ( !(positions[i].fixed && real_fixed) ) { + update_node_pos ( i, old_positions, new_positions ); + } + } + + // check if anything was actually updated (e.g. everything was fixed) + all_fixed = true; + for ( size_t j = 0; j < node_indices.size (); j++ ) + if ( !(positions [ node_indices[j] ].fixed && real_fixed) ) { + all_fixed = false; + } + + // update positions across processors (if not all fixed) + if ( !all_fixed ) { + // update positions (old to new) + update_density ( node_indices, old_positions, new_positions ); + } + + /* + if ( myid == 0 ) + { + // output node list (for debugging) + for ( unsigned int j = 0; j < node_indices.size(); j++ ) + cout << node_indices[j] << " "; + cout << endl; + } + */ + + // compute node list for next update + for ( size_t j = 0; j < node_indices.size(); j++ ) { + node_indices [j] += num_procs; + } + + while ( !node_indices.empty() && node_indices.back() >= num_nodes ) { + node_indices.pop_back ( ); + } + + } + + // update first_add and fine_first_add + first_add = false; + if ( fineDensity ) { + fine_first_add = false; + } + +} + +// The get_positions function takes the node_indices list +// and returns the corresponding positions in an array. + +void graph::get_positions ( vector &node_indices, + float return_positions[2 * MAX_PROCS] ) { + + // fill positions + for (size_t i = 0; i < node_indices.size(); i++) { + return_positions[2 * i] = positions[ node_indices[i] ].x; + return_positions[2 * i + 1] = positions[ node_indices[i] ].y; + } + +} + +// update_node_pos -- this subroutine does the actual work of computing +// the new position of a given node. num_act_proc gives the number +// of active processes at this level for use by the random number +// generators. + +void graph::update_node_pos ( igraph_int_t node_ind, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ) { + + float energies[2]; // node energies for possible positions + float updated_pos[2][2]; // possible positions + float pos_x, pos_y; + + // old VxOrd parameter + float jump_length = .010 * temperature; + + // subtract old node + density_server.Subtract ( positions[node_ind], first_add, fine_first_add, fineDensity ); + + // compute node energy for old solution + energies[0] = Compute_Node_Energy ( node_ind ); + + // move node to centroid position + Solve_Analytic ( node_ind, pos_x, pos_y ); + positions[node_ind].x = updated_pos[0][0] = pos_x; + positions[node_ind].y = updated_pos[0][1] = pos_y; + + /* + // ouput random numbers (for debugging) + int rand_0, rand_1; + rand_0 = rand(); + rand_1 = rand(); + cout << myid << ": " << rand_0 << ", " << rand_1 << endl; + */ + + // Do random method (RAND_MAX is C++ maximum random number) + updated_pos[1][0] = updated_pos[0][0] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][1] = updated_pos[0][1] + (.5 - RNG_UNIF01()) * jump_length; + + // compute node energy for random position + positions[node_ind].x = updated_pos[1][0]; + positions[node_ind].y = updated_pos[1][1]; + energies[1] = Compute_Node_Energy ( node_ind ); + + /* + // output update possiblities (debugging): + cout << node_ind << ": (" << updated_pos[0][0] << "," << updated_pos[0][1] + << "), " << energies[0] << "; (" << updated_pos[1][0] << "," + << updated_pos[1][1] << "), " << energies[1] << endl; + */ + + // add back old position + positions[node_ind].x = old_positions[2 * myid]; + positions[node_ind].y = old_positions[2 * myid + 1]; + if ( !fineDensity && !first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } else if ( !fine_first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } + + // choose updated node position with lowest energy + if ( energies[0] < energies[1] ) { + new_positions[2 * myid] = updated_pos[0][0]; + new_positions[2 * myid + 1] = updated_pos[0][1]; + positions[node_ind].energy = energies[0]; + } else { + new_positions[2 * myid] = updated_pos[1][0]; + new_positions[2 * myid + 1] = updated_pos[1][1]; + positions[node_ind].energy = energies[1]; + } + +} + +// update_density takes a sequence of node_indices and their positions and +// updates the positions by subtracting the old positions and adding the +// new positions to the density grid. + +void graph::update_density ( vector &node_indices, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ) { + + // go through each node and subtract old position from + // density grid before adding new position + for ( size_t i = 0; i < node_indices.size(); i++ ) { + positions[node_indices[i]].x = old_positions[2 * i]; + positions[node_indices[i]].y = old_positions[2 * i + 1]; + density_server.Subtract ( positions[node_indices[i]], + first_add, fine_first_add, fineDensity ); + + positions[node_indices[i]].x = new_positions[2 * i]; + positions[node_indices[i]].y = new_positions[2 * i + 1]; + density_server.Add ( positions[node_indices[i]], fineDensity ); + } + +} + +/******************************************** +* Function: Compute_Node_Energy * +* Description: Compute the node energy * +* This code has been modified from the * +* original code by B. Wylie. * +*********************************************/ + +float graph::Compute_Node_Energy( igraph_int_t node_ind ) { + + /* Want to expand 4th power range of attraction */ + float attraction_factor = attraction * attraction * + attraction * attraction * 2e-2; + + map ::iterator EI; + float x_dis, y_dis; + float energy_distance, weight; + float node_energy = 0; + + // Add up all connection energies + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + + // Get edge weight + weight = EI->second; + + // Compute x,y distance + x_dis = positions[ node_ind ].x - positions[ EI->first ].x; + y_dis = positions[ node_ind ].y - positions[ EI->first ].y; + + // Energy Distance + energy_distance = x_dis * x_dis + y_dis * y_dis; + if (STAGE < 2) { + energy_distance *= energy_distance; + } + + // In the liquid phase we want to discourage long link distances + if (STAGE == 0) { + energy_distance *= energy_distance; + } + + node_energy += weight * attraction_factor * energy_distance; + } + + // output effect of density (debugging) + //cout << "[before: " << node_energy; + + // add density + node_energy += density_server.GetDensity ( positions[ node_ind ].x, positions[ node_ind ].y, + fineDensity ); + + // after calling density server (debugging) + //cout << ", after: " << node_energy << "]" << endl; + + // return computated energy + return node_energy; +} + + +/********************************************* +* Function: Solve_Analytic * +* Description: Compute the node position * +* This is a modified version of the function * +* originally written by B. Wylie * +*********************************************/ + +void graph::Solve_Analytic( igraph_int_t node_ind, float &pos_x, float &pos_y ) { + + map ::iterator EI; + float total_weight = 0; + float x_dis, y_dis, x_cen = 0, y_cen = 0; + float x = 0, y = 0, dis; + float damping, weight; + + // Sum up all connections + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + weight = EI->second; + total_weight += weight; + x += weight * positions[ EI->first ].x; + y += weight * positions[ EI->first ].y; + } + + // Now set node position + if (total_weight > 0) { + + // Compute centriod + x_cen = x / total_weight; + y_cen = y / total_weight; + damping = 1.0 - damping_mult; + pos_x = damping * positions[ node_ind ].x + (1.0 - damping) * x_cen; + pos_y = damping * positions[ node_ind ].y + (1.0 - damping) * y_cen; + } else { + pos_x = positions[ node_ind ].x; + pos_y = positions[ node_ind ].y; + } + + // No cut edge flag (?) + if (min_edges == 99) { + return; + } + + // Don't cut at end of scale + if ( CUT_END >= 39500 ) { + return; + } + + float num_connections = sqrt((double)neighbors[node_ind].size()); + float maxLength = 0; + + map::iterator maxIndex; + + // Go through nodes edges... cutting if necessary + for (EI = maxIndex = neighbors[node_ind].begin(); + EI != neighbors[node_ind].end(); ++EI) { + + // Check for at least min edges + if (neighbors[node_ind].size() < min_edges) { + continue; + } + + x_dis = x_cen - positions[ EI->first ].x; + y_dis = y_cen - positions[ EI->first ].y; + dis = x_dis * x_dis + y_dis * y_dis; + dis *= num_connections; + + // Store maximum edge + if (dis > maxLength) { + maxLength = dis; + maxIndex = EI; + } + } + + // If max length greater than cut_length then cut + if (maxLength > cut_off_length) { + neighbors[ node_ind ].erase( maxIndex ); + } + +} + + +// write_coord writes out the coordinate file of the final solutions + +// void graph::write_coord( const char *file_name ) +// { + +// ofstream coordOUT( file_name ); +// if ( !coordOUT ) +// { +// cout << "Could not open " << file_name << ". Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// cout << "Writing out solution to " << file_name << " ..." << endl; + +// for (unsigned int i = 0; i < positions.size(); i++) { +// coordOUT << positions[i].id << "\t" << positions[i].x << "\t" << positions[i].y < >::iterator i; + map::iterator j; + + for ( i = neighbors.begin(); i != neighbors.end(); i++ ) + for (j = (i->second).begin(); j != (i->second).end(); j++ ) + simOUT << positions[i->first].id << "\t" + << positions[j->first].id << "\t" + << j->second << endl; + + simOUT.close(); + +} +*/ + +// get_tot_energy adds up the energy for each node to give an estimate of the +// quality of the minimization. + +float graph::get_tot_energy ( ) { + + float my_tot_energy, tot_energy; + my_tot_energy = 0; + for ( int i = myid; i < num_nodes; i += num_procs ) { + my_tot_energy += positions[i].energy; + } + + //vector::iterator i; + //for ( i = positions.begin(); i != positions.end(); i++ ) + // tot_energy += i->energy; + + tot_energy = my_tot_energy; + + return tot_energy; + +} + + +// The following subroutine draws the graph with possible intermediate +// output (int_out is set to 0 if not proc. 0). int_out is the parameter +// passed by the user, and coord_file is the .coord file. + +// void graph::draw_graph ( int int_out, char *coord_file ) +// { + +// // layout graph (with possible intermediate output) +// int count_iter = 0, count_file = 1; +// char int_coord_file [MAX_FILE_NAME + MAX_INT_LENGTH]; +// while ( ReCompute( ) ) +// if ( (int_out > 0) && (count_iter == int_out) ) +// { +// // output intermediate solution +// sprintf ( int_coord_file, "%s.%d", coord_file, count_file ); +// write_coord ( int_coord_file ); + +// count_iter = 0; +// count_file++; +// } +// else +// count_iter++; + +// } + +igraph_error_t graph::draw_graph(igraph_matrix_t *res) { + while (ReCompute()) { + IGRAPH_ALLOW_INTERRUPTION(); + } + igraph_int_t n = positions.size(); + IGRAPH_CHECK(igraph_matrix_resize(res, n, 2)); + for (igraph_int_t i = 0; i < n; i++) { + MATRIX(*res, i, 0) = positions[i].x; + MATRIX(*res, i, 1) = positions[i].y; + } + return IGRAPH_SUCCESS; +} + +} // namespace drl diff --git a/src/layout/drl/drl_graph.h b/src/layout/drl/drl_graph.h new file mode 100644 index 0000000..0ff95b5 --- /dev/null +++ b/src/layout/drl/drl_graph.h @@ -0,0 +1,132 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The graph class contains the methods necessary to draw the +// graph. It calls on the density server class to obtain +// position and density information + +#include "DensityGrid.h" +#include "igraph_layout.h" + +#include +#include +#include + +namespace drl { + +// layout schedule information +struct layout_schedule { + igraph_int_t iterations; + float temperature; + float attraction; + float damping_mult; + time_t time_elapsed; +}; + +class graph { + +public: + + // Methods + void init_parms ( int rand_seed, float edge_cut, float real_parm ); + void init_parms ( const igraph_layout_drl_options_t *options ); + void read_parms ( char *parms_file ); + void read_real ( char *real_file ); + int read_real ( const igraph_matrix_t *real_mat ); + void scan_int ( char *filename ); + void read_int ( char *file_name ); + void draw_graph ( int int_out, char *coord_file ); + igraph_error_t draw_graph(igraph_matrix_t *res); + void write_coord ( const char *file_name ); + void write_sim ( const char *file_name ); + float get_tot_energy ( ); + + // Con/Decon + graph( int proc_id, int tot_procs, char *int_file ); + ~graph( ) { } + graph( const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + +private: + + // Methods + int ReCompute ( ); + void update_nodes ( ); + float Compute_Node_Energy ( igraph_int_t node_ind ); + void Solve_Analytic ( igraph_int_t node_ind, float &pos_x, float &pos_y ); + void get_positions ( std::vector &node_indices, float return_positions[2 * MAX_PROCS] ); + void update_density ( std::vector &node_indices, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ); + void update_node_pos ( igraph_int_t node_ind, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ); + + // MPI information + int myid, num_procs; + + // graph decomposition information + igraph_int_t num_nodes; // number of nodes in graph + float highest_sim; // highest sim for normalization + std::map id_catalog; // id_catalog[file id] = internal id + std::map > neighbors; // neighbors of nodes on this proc. + + // graph layout information + std::vector positions; + DensityGrid density_server; + + // original VxOrd information + int STAGE; + igraph_int_t iterations; + float temperature, attraction, damping_mult; + float min_edges, CUT_END, cut_length_end, cut_off_length, cut_rate; + bool first_add, fine_first_add, fineDensity; + + // scheduling variables + layout_schedule liquid; + layout_schedule expansion; + layout_schedule cooldown; + layout_schedule crunch; + layout_schedule simmer; + + // timing statistics + time_t start_time, stop_time; + + // online clustering information + igraph_int_t real_iterations; // number of iterations to hold .real input fixed + igraph_int_t tot_iterations; + igraph_int_t tot_expected_iterations; // for progress bar + bool real_fixed; +}; + +} // namespace drl diff --git a/src/layout/drl/drl_graph_3d.cpp b/src/layout/drl/drl_graph_3d.cpp new file mode 100644 index 0000000..0600fc5 --- /dev/null +++ b/src/layout/drl/drl_graph_3d.cpp @@ -0,0 +1,837 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the master class + +#include +#include +#include + +using namespace std; + +#include "drl_graph_3d.h" +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "core/interruption.h" + +namespace drl3d { + +graph::graph(const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + myid = 0; + num_procs = 1; + + STAGE = 0; + iterations = options->init_iterations; + temperature = options->init_temperature; + attraction = options->init_attraction; + damping_mult = options->init_damping_mult; + min_edges = 20; + first_add = fine_first_add = true; + fineDensity = false; + + // Brian's original Vx schedule + liquid.iterations = options->liquid_iterations; + liquid.temperature = options->liquid_temperature; + liquid.attraction = options->liquid_attraction; + liquid.damping_mult = options->liquid_damping_mult; + liquid.time_elapsed = 0; + + expansion.iterations = options->expansion_iterations; + expansion.temperature = options->expansion_temperature; + expansion.attraction = options->expansion_attraction; + expansion.damping_mult = options->expansion_damping_mult; + expansion.time_elapsed = 0; + + cooldown.iterations = options->cooldown_iterations; + cooldown.temperature = options->cooldown_temperature; + cooldown.attraction = options->cooldown_attraction; + cooldown.damping_mult = options->cooldown_damping_mult; + cooldown.time_elapsed = 0; + + crunch.iterations = options->crunch_iterations; + crunch.temperature = options->crunch_temperature; + crunch.attraction = options->crunch_attraction; + crunch.damping_mult = options->crunch_damping_mult; + crunch.time_elapsed = 0; + + simmer.iterations = options->simmer_iterations; + simmer.temperature = options->simmer_temperature; + simmer.attraction = options->simmer_attraction; + simmer.damping_mult = options->simmer_damping_mult; + simmer.time_elapsed = 0; + + // scan .int file for node info + highest_sim = 1.0; + num_nodes = igraph_vcount(igraph); + igraph_int_t no_of_edges = igraph_ecount(igraph); + for (igraph_int_t i = 0; i < num_nodes; i++) { + id_catalog[i] = 1; + } + map< igraph_int_t, igraph_int_t>::iterator cat_iter; + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); cat_iter++) { + cat_iter->second = cat_iter->first; + } + + // populate node positions and ids + positions.reserve ( num_nodes ); + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); + cat_iter++ ) { + positions.push_back ( Node( cat_iter->first ) ); + } + + // read .int file for graph info + igraph_int_t node_1, node_2; + igraph_real_t weight; + for (igraph_int_t i = 0; i < no_of_edges; i++) { + node_1 = IGRAPH_FROM(igraph, i); + node_2 = IGRAPH_TO(igraph, i); + weight = weights ? VECTOR(*weights)[i] : 1.0 ; + (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; + (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; + } + + // initialize density server + density_server.Init(); + +} + +// init_parms -- this subroutine initializes the edge_cut variables +// used in the original VxOrd starting with the edge_cut parameter. +// In our version, edge_cut = 0 means no cutting, 1 = maximum cut. +// We also set the random seed here. + +void graph::init_parms ( int rand_seed, float edge_cut, float real_parm ) { + + IGRAPH_UNUSED(rand_seed); + // first we translate edge_cut the former tcl sliding scale + //CUT_END = cut_length_end = 39000.0 * (1.0 - edge_cut) + 1000.0; + CUT_END = cut_length_end = 40000.0 * (1.0 - edge_cut); + + // cut_length_end cannot actually be 0 + if ( cut_length_end <= 1.0 ) { + cut_length_end = 1.0; + } + + float cut_length_start = 4.0 * cut_length_end; + + // now we set the parameters used by ReCompute + cut_off_length = cut_length_start; + cut_rate = ( cut_length_start - cut_length_end ) / 400.0; + + // finally set the number of iterations to leave .real coords fixed + igraph_int_t full_comp_iters; + full_comp_iters = liquid.iterations + expansion.iterations + + cooldown.iterations + crunch.iterations + 3; + + // adjust real parm to iterations (do not enter simmer halfway) + if ( real_parm < 0 ) { + real_iterations = (int)real_parm; + } else if ( real_parm == 1) { + real_iterations = full_comp_iters + simmer.iterations + 100; + } else { + real_iterations = (int)(real_parm * full_comp_iters); + } + + tot_iterations = 0; + if ( real_iterations > 0 ) { + real_fixed = true; + } else { + real_fixed = false; + } + + // calculate total expected iterations (for progress bar display) + tot_expected_iterations = liquid.iterations + + expansion.iterations + cooldown.iterations + + crunch.iterations + simmer.iterations; + + /* + // output edge_cutting parms (for debugging) + cout << "Processor " << myid << ": " + << "cut_length_end = CUT_END = " << cut_length_end + << ", cut_length_start = " << cut_length_start + << ", cut_rate = " << cut_rate << endl; + */ + + // set random seed + // srand ( rand_seed ); // Don't need this in igraph + +} + +void graph::init_parms(const igraph_layout_drl_options_t *options) { + double rand_seed = 0.0; + double real_in = -1.0; + init_parms(rand_seed, options->edge_cut, real_in); +} + +int graph::read_real(const igraph_matrix_t *real_mat) { + igraph_int_t n = igraph_matrix_nrow(real_mat); + for (igraph_int_t i = 0; i < n; i++) { + positions[id_catalog[i]].x = MATRIX(*real_mat, i, 0); + positions[id_catalog[i]].y = MATRIX(*real_mat, i, 1); + positions[id_catalog[i]].z = MATRIX(*real_mat, i, 2); + positions[id_catalog[i]].fixed = false; + + if ( real_iterations > 0 ) { + density_server.Add ( positions[id_catalog[i]], fineDensity ); + } + } + + return 0; +} + +/********************************************* + * Function: ReCompute * + * Description: Compute the graph locations * + * Modified from original code by B. Wylie * + ********************************************/ + +int graph::ReCompute( ) { + + // carryover from original VxOrd + int MIN = 1; + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + /* igraph progress report */ + float progress = (tot_iterations * 100.0 / tot_expected_iterations); + + switch (STAGE) { + case 0: + if (iterations == 0) { + IGRAPH_PROGRESS("DrL layout (initialization stage)", progress, 0); + } else { + IGRAPH_PROGRESS("DrL layout (liquid stage)", progress, 0); + } + break; + case 1: + IGRAPH_PROGRESS("DrL layout (expansion stage)", progress, 0); break; + case 2: + IGRAPH_PROGRESS("DrL layout (cooldown and cluster phase)", progress, 0); break; + case 3: + IGRAPH_PROGRESS("DrL layout (crunch phase)", progress, 0); break; + case 5: + IGRAPH_PROGRESS("DrL layout (simmer phase)", progress, 0); break; + case 6: + IGRAPH_PROGRESS("DrL layout (final phase)", 100.0, 0); break; + default: + IGRAPH_PROGRESS("DrL layout (unknown phase)", 0.0, 0); break; + } + + /* Compute Energies for individual nodes */ + update_nodes (); + + // check to see if we need to free fixed nodes + tot_iterations++; + if ( tot_iterations >= real_iterations ) { + real_fixed = false; + } + + + // **************************************** + // AUTOMATIC CONTROL SECTION + // **************************************** + + // STAGE 0: LIQUID + if (STAGE == 0) { + + if ( iterations == 0 ) { + start_time = time( NULL ); +// if ( myid == 0 ) +// cout << "Entering liquid stage ..."; + } + + if (iterations < liquid.iterations) { + temperature = liquid.temperature; + attraction = liquid.attraction; + damping_mult = liquid.damping_mult; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + liquid.time_elapsed = liquid.time_elapsed + (stop_time - start_time); + temperature = expansion.temperature; + attraction = expansion.attraction; + damping_mult = expansion.damping_mult; + iterations = 0; + + // go to next stage + STAGE = 1; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering expansion stage ..."; + } + } + + // STAGE 1: EXPANSION + if (STAGE == 1) { + + if (iterations < expansion.iterations) { + + // Play with vars + if (attraction > 1) { + attraction -= .05f; + } + if (min_edges > 12) { + min_edges -= .05f; + } + cut_off_length -= cut_rate; + if (damping_mult > .1) { + damping_mult -= .005f; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + + } else { + + stop_time = time( NULL ); + expansion.time_elapsed = expansion.time_elapsed + (stop_time - start_time); + min_edges = 12; + damping_mult = cooldown.damping_mult; + + STAGE = 2; + attraction = cooldown.attraction; + temperature = cooldown.temperature; + iterations = 0; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering cool-down stage ..."; + } + } + + // STAGE 2: Cool down and cluster + else if (STAGE == 2) { + + if (iterations < cooldown.iterations) { + + // Reduce temperature + if (temperature > 50) { + temperature -= 10; + } + + // Reduce cut length + if (cut_off_length > cut_length_end) { + cut_off_length -= cut_rate * 2; + } + if (min_edges > MIN) { + min_edges -= .2f; + } + //min_edges = 99; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + cooldown.time_elapsed = cooldown.time_elapsed + (stop_time - start_time); + cut_off_length = cut_length_end; + temperature = crunch.temperature; + damping_mult = crunch.damping_mult; + min_edges = MIN; + //min_edges = 99; // In other words: no more cutting + + STAGE = 3; + iterations = 0; + attraction = crunch.attraction; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering crunch stage ..."; + } + } + + // STAGE 3: Crunch + else if (STAGE == 3) { + + if (iterations < crunch.iterations) { + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + + stop_time = time( NULL ); + crunch.time_elapsed = crunch.time_elapsed + (stop_time - start_time); + iterations = 0; + temperature = simmer.temperature; + attraction = simmer.attraction; + damping_mult = simmer.damping_mult; + min_edges = 99; + fineDensity = true; + + STAGE = 5; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering simmer stage ..."; + } + } + + // STAGE 5: Simmer + else if ( STAGE == 5 ) { + + if (iterations < simmer.iterations) { + if (temperature > 50) { + temperature -= 2; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + stop_time = time( NULL ); + simmer.time_elapsed = simmer.time_elapsed + (stop_time - start_time); + + STAGE = 6; + +// if ( myid == 0 ) +// cout << "Layout calculation completed in " << +// ( liquid.time_elapsed + expansion.time_elapsed + +// cooldown.time_elapsed + crunch.time_elapsed + +// simmer.time_elapsed ) +// << " seconds (not including I/O)." +// << endl; + } + } + + // STAGE 6: All Done! + else if ( STAGE == 6) { + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + return 0; + } + + // **************************************** + // END AUTOMATIC CONTROL SECTION + // **************************************** + + // Still need more recomputation + return 1; + +} + +// update_nodes -- this function will complete the primary node update +// loop in layout's recompute routine. It follows exactly the same +// sequence to ensure similarity of parallel layout to the standard layout + +void graph::update_nodes ( ) { + + vector node_indices; // node list of nodes currently being updated + float old_positions[2 * MAX_PROCS]; // positions before update + float new_positions[2 * MAX_PROCS]; // positions after update + + bool all_fixed; // check if all nodes are fixed + + // initial node list consists of 0,1,...,num_procs + for ( int i = 0; i < num_procs; i++ ) { + node_indices.push_back( i ); + } + + // next we calculate the number of nodes there would be if the + // num_nodes by num_procs schedule grid were perfectly square + igraph_int_t square_num_nodes = (igraph_int_t)(num_procs + num_procs * floor ((float)(num_nodes - 1) / (float)num_procs )); + + for ( igraph_int_t i = myid; i < square_num_nodes; i += num_procs ) { + + // get old positions + get_positions ( node_indices, old_positions ); + + // default new position is old position + get_positions ( node_indices, new_positions ); + + if ( i < num_nodes ) { + // calculate node energy possibilities + if ( !(positions[i].fixed && real_fixed) ) { + update_node_pos ( i, old_positions, new_positions ); + } + } + + // check if anything was actually updated (e.g. everything was fixed) + all_fixed = true; + for ( size_t j = 0; j < node_indices.size (); j++ ) + if ( !(positions [ node_indices[j] ].fixed && real_fixed) ) { + all_fixed = false; + } + + // update positions across processors (if not all fixed) + if ( !all_fixed ) { + // update positions (old to new) + update_density ( node_indices, old_positions, new_positions ); + } + + /* + if ( myid == 0 ) + { + // output node list (for debugging) + for ( size_t j = 0; j < node_indices.size(); j++ ) + cout << node_indices[j] << " "; + cout << endl; + } + */ + + // compute node list for next update + for ( size_t j = 0; j < node_indices.size(); j++ ) { + node_indices [j] += num_procs; + } + + while ( !node_indices.empty() && node_indices.back() >= num_nodes ) { + node_indices.pop_back ( ); + } + + } + + // update first_add and fine_first_add + first_add = false; + if ( fineDensity ) { + fine_first_add = false; + } + +} + +// The get_positions function takes the node_indices list +// and returns the corresponding positions in an array. + +void graph::get_positions ( vector &node_indices, + float return_positions[3 * MAX_PROCS] ) { + + // fill positions + for (size_t i = 0; i < node_indices.size(); i++) { + return_positions[3 * i] = positions[ node_indices[i] ].x; + return_positions[3 * i + 1] = positions[ node_indices[i] ].y; + return_positions[3 * i + 2] = positions[ node_indices[i] ].z; + } + +} + +// update_node_pos -- this subroutine does the actual work of computing +// the new position of a given node. num_act_proc gives the number +// of active processes at this level for use by the random number +// generators. + +void graph::update_node_pos ( igraph_int_t node_ind, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ) { + + float energies[2]; // node energies for possible positions + float updated_pos[2][3]; // possible positions + float pos_x, pos_y, pos_z; + + // old VxOrd parameter + float jump_length = .010 * temperature; + + // subtract old node + density_server.Subtract ( positions[node_ind], first_add, fine_first_add, fineDensity ); + + // compute node energy for old solution + energies[0] = Compute_Node_Energy ( node_ind ); + + // move node to centroid position + Solve_Analytic ( node_ind, pos_x, pos_y, pos_z ); + positions[node_ind].x = updated_pos[0][0] = pos_x; + positions[node_ind].y = updated_pos[0][1] = pos_y; + positions[node_ind].z = updated_pos[0][2] = pos_z; + + /* + // ouput random numbers (for debugging) + int rand_0, rand_1; + rand_0 = rand(); + rand_1 = rand(); + cout << myid << ": " << rand_0 << ", " << rand_1 << endl; + */ + + // Do random method (RAND_MAX is C++ maximum random number) + updated_pos[1][0] = updated_pos[0][0] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][1] = updated_pos[0][1] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][2] = updated_pos[0][2] + (.5 - RNG_UNIF01()) * jump_length; + + // compute node energy for random position + positions[node_ind].x = updated_pos[1][0]; + positions[node_ind].y = updated_pos[1][1]; + positions[node_ind].z = updated_pos[1][2]; + energies[1] = Compute_Node_Energy ( node_ind ); + + /* + // output update possiblities (debugging): + cout << node_ind << ": (" << updated_pos[0][0] << "," << updated_pos[0][1] + << "), " << energies[0] << "; (" << updated_pos[1][0] << "," + << updated_pos[1][1] << "), " << energies[1] << endl; + */ + + // add back old position + positions[node_ind].x = old_positions[3 * myid]; + positions[node_ind].y = old_positions[3 * myid + 1]; + positions[node_ind].z = old_positions[3 * myid + 2]; + if ( !fineDensity && !first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } else if ( !fine_first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } + + // choose updated node position with lowest energy + if ( energies[0] < energies[1] ) { + new_positions[3 * myid] = updated_pos[0][0]; + new_positions[3 * myid + 1] = updated_pos[0][1]; + new_positions[3 * myid + 2] = updated_pos[0][2]; + positions[node_ind].energy = energies[0]; + } else { + new_positions[3 * myid] = updated_pos[1][0]; + new_positions[3 * myid + 1] = updated_pos[1][1]; + new_positions[3 * myid + 2] = updated_pos[1][2]; + positions[node_ind].energy = energies[1]; + } + +} + +// update_density takes a sequence of node_indices and their positions and +// updates the positions by subtracting the old positions and adding the +// new positions to the density grid. + +void graph::update_density ( vector &node_indices, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ) { + + // go through each node and subtract old position from + // density grid before adding new position + for ( size_t i = 0; i < node_indices.size(); i++ ) { + positions[node_indices[i]].x = old_positions[3 * i]; + positions[node_indices[i]].y = old_positions[3 * i + 1]; + positions[node_indices[i]].z = old_positions[3 * i + 2]; + density_server.Subtract ( positions[node_indices[i]], + first_add, fine_first_add, fineDensity ); + + positions[node_indices[i]].x = new_positions[3 * i]; + positions[node_indices[i]].y = new_positions[3 * i + 1]; + positions[node_indices[i]].z = new_positions[3 * i + 2]; + density_server.Add ( positions[node_indices[i]], fineDensity ); + } + +} + +/******************************************** +* Function: Compute_Node_Energy * +* Description: Compute the node energy * +* This code has been modified from the * +* original code by B. Wylie. * +*********************************************/ + +float graph::Compute_Node_Energy( igraph_int_t node_ind ) { + + /* Want to expand 4th power range of attraction */ + float attraction_factor = attraction * attraction * + attraction * attraction * 2e-2; + + map ::iterator EI; + float x_dis, y_dis, z_dis; + float energy_distance, weight; + float node_energy = 0; + + // Add up all connection energies + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + + // Get edge weight + weight = EI->second; + + // Compute x,y distance + x_dis = positions[ node_ind ].x - positions[ EI->first ].x; + y_dis = positions[ node_ind ].y - positions[ EI->first ].y; + z_dis = positions[ node_ind ].z - positions[ EI->first ].z; + + // Energy Distance + energy_distance = x_dis * x_dis + y_dis * y_dis + z_dis * z_dis; + if (STAGE < 2) { + energy_distance *= energy_distance; + } + + // In the liquid phase we want to discourage long link distances + if (STAGE == 0) { + energy_distance *= energy_distance; + } + + node_energy += weight * attraction_factor * energy_distance; + } + + // output effect of density (debugging) + //cout << "[before: " << node_energy; + + // add density + node_energy += density_server.GetDensity ( positions[ node_ind ].x, positions[ node_ind ].y, + positions[ node_ind ].z, fineDensity ); + + // after calling density server (debugging) + //cout << ", after: " << node_energy << "]" << endl; + + // return computated energy + return node_energy; +} + + +/********************************************* +* Function: Solve_Analytic * +* Description: Compute the node position * +* This is a modified version of the function * +* originally written by B. Wylie * +*********************************************/ + +void graph::Solve_Analytic( igraph_int_t node_ind, float &pos_x, float &pos_y, + float &pos_z) { + + map ::iterator EI; + float total_weight = 0; + float x_dis, y_dis, z_dis, x_cen = 0, y_cen = 0, z_cen = 0; + float x = 0, y = 0, z = 0, dis; + float damping, weight; + + // Sum up all connections + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + weight = EI->second; + total_weight += weight; + x += weight * positions[ EI->first ].x; + y += weight * positions[ EI->first ].y; + z += weight * positions[ EI->first ].z; + } + + // Now set node position + if (total_weight > 0) { + + // Compute centriod + x_cen = x / total_weight; + y_cen = y / total_weight; + z_cen = z / total_weight; + damping = 1.0 - damping_mult; + pos_x = damping * positions[ node_ind ].x + (1.0 - damping) * x_cen; + pos_y = damping * positions[ node_ind ].y + (1.0 - damping) * y_cen; + pos_z = damping * positions[ node_ind ].z + (1.0 - damping) * z_cen; + } + + // No cut edge flag (?) + if (min_edges == 99) { + return; + } + + // Don't cut at end of scale + if ( CUT_END >= 39500 ) { + return; + } + + float num_connections = (float)sqrt((float)neighbors[node_ind].size()); + float maxLength = 0; + + map::iterator maxIndex; + + // Go through nodes edges... cutting if necessary + for (EI = maxIndex = neighbors[node_ind].begin(); + EI != neighbors[node_ind].end(); ++EI) { + + // Check for at least min edges + if (neighbors[node_ind].size() < min_edges) { + continue; + } + + x_dis = x_cen - positions[ EI->first ].x; + y_dis = y_cen - positions[ EI->first ].y; + z_dis = z_cen - positions[ EI->first ].z; + dis = x_dis * x_dis + y_dis * y_dis + z_dis * z_dis; + dis *= num_connections; + + // Store maximum edge + if (dis > maxLength) { + maxLength = dis; + maxIndex = EI; + } + } + + // If max length greater than cut_length then cut + if (maxLength > cut_off_length) { + neighbors[ node_ind ].erase( maxIndex ); + } + +} + + +// get_tot_energy adds up the energy for each node to give an estimate of the +// quality of the minimization. + +float graph::get_tot_energy ( ) { + + float my_tot_energy, tot_energy; + my_tot_energy = 0; + for ( int i = myid; i < num_nodes; i += num_procs ) { + my_tot_energy += positions[i].energy; + } + + //vector::iterator i; + //for ( i = positions.begin(); i != positions.end(); i++ ) + // tot_energy += i->energy; + + tot_energy = my_tot_energy; + + return tot_energy; + +} + + +igraph_error_t graph::draw_graph(igraph_matrix_t *res) { + while (ReCompute()) { + IGRAPH_ALLOW_INTERRUPTION(); + } + size_t n = positions.size(); + IGRAPH_CHECK(igraph_matrix_resize(res, n, 3)); + for (size_t i = 0; i < n; i++) { + MATRIX(*res, i, 0) = positions[i].x; + MATRIX(*res, i, 1) = positions[i].y; + MATRIX(*res, i, 2) = positions[i].z; + } + return IGRAPH_SUCCESS; +} + +} // namespace drl3d diff --git a/src/layout/drl/drl_graph_3d.h b/src/layout/drl/drl_graph_3d.h new file mode 100644 index 0000000..18e7e39 --- /dev/null +++ b/src/layout/drl/drl_graph_3d.h @@ -0,0 +1,124 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The graph class contains the methods necessary to draw the +// graph. It calls on the density server class to obtain +// position and density information + +#include "DensityGrid_3d.h" +#include "igraph_layout.h" + +#include +#include +#include + +namespace drl3d { + +// layout schedule information +struct layout_schedule { + igraph_int_t iterations; + float temperature; + float attraction; + float damping_mult; + time_t time_elapsed; +}; + +class graph { + +public: + + // Methods + void init_parms ( int rand_seed, float edge_cut, float real_parm ); + void init_parms ( const igraph_layout_drl_options_t *options ); + int read_real ( const igraph_matrix_t *real_mat ); + igraph_error_t draw_graph(igraph_matrix_t *res); + float get_tot_energy ( ); + + // Con/Decon + graph( const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + ~graph( ) { } + +private: + + // Methods + int ReCompute ( ); + void update_nodes ( ); + float Compute_Node_Energy ( igraph_int_t node_ind ); + void Solve_Analytic ( igraph_int_t node_ind, float &pos_x, float &pos_y, float &pos_z ); + void get_positions ( std::vector &node_indices, float return_positions[3 * MAX_PROCS] ); + void update_density ( std::vector &node_indices, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ); + void update_node_pos ( igraph_int_t node_ind, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ); + + // MPI information + int myid, num_procs; + + // graph decomposition information + igraph_int_t num_nodes; // number of nodes in graph + float highest_sim; // highest sim for normalization + std::map id_catalog; // id_catalog[file id] = internal id + std::map > neighbors; // neighbors of nodes on this proc. + + // graph layout information + std::vector positions; + DensityGrid density_server; + + // original VxOrd information + int STAGE; + igraph_int_t iterations; + float temperature, attraction, damping_mult; + float min_edges, CUT_END, cut_length_end, cut_off_length, cut_rate; + bool first_add, fine_first_add, fineDensity; + + // scheduling variables + layout_schedule liquid; + layout_schedule expansion; + layout_schedule cooldown; + layout_schedule crunch; + layout_schedule simmer; + + // timing statistics + time_t start_time, stop_time; + + // online clustering information + igraph_int_t real_iterations; // number of iterations to hold .real input fixed + igraph_int_t tot_iterations; + igraph_int_t tot_expected_iterations; // for progress bar + bool real_fixed; +}; + +} // namespace drl3d diff --git a/src/layout/drl/drl_layout.cpp b/src/layout/drl/drl_layout.cpp new file mode 100644 index 0000000..f998fc6 --- /dev/null +++ b/src/layout/drl/drl_layout.cpp @@ -0,0 +1,481 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// Layout +// +// This program implements a parallel force directed graph drawing +// algorithm. The algorithm used is based upon a random decomposition +// of the graph and simulated shared memory of node position and density. +// In this version, the simulated shared memory is spread among all processors +// +// The structure of the inputs and outputs of this code will be displayed +// if the program is called without parameters, or if an erroneous +// parameter is passed to the program. +// +// S. Martin +// 5/6/2005 + +// layout routines and constants +#include "drl_layout.h" +#include "drl_parse.h" +#include "drl_graph.h" + +using namespace drl; +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +#include "core/exceptions.h" + +namespace drl { + +// int main(int argc, char **argv) { + + +// // initialize MPI +// int myid, num_procs; + +// #ifdef MUSE_MPI +// MPI_Init ( &argc, &argv ); +// MPI_Comm_size ( MPI_COMM_WORLD, &num_procs ); +// MPI_Comm_rank ( MPI_COMM_WORLD, &myid ); +// #else +// myid = 0; +// num_procs = 1; +// #endif + +// // parameters that must be broadcast to all processors +// int rand_seed; +// float edge_cut; + +// char int_file[MAX_FILE_NAME]; +// char coord_file[MAX_FILE_NAME]; +// char real_file[MAX_FILE_NAME]; +// char parms_file[MAX_FILE_NAME]; + +// int int_out = 0; +// int edges_out = 0; +// int parms_in = 0; +// float real_in = -1.0; + +// // user interaction is handled by processor 0 +// if ( myid == 0 ) +// { +// if ( num_procs > MAX_PROCS ) +// { +// cout << "Error: Maximum number of processors is " << MAX_PROCS << "." << endl; +// cout << "Adjust compile time parameter." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // get user input +// parse command_line ( argc, argv ); +// rand_seed = command_line.rand_seed; +// edge_cut = command_line.edge_cut; +// int_out = command_line.int_out; +// edges_out = command_line.edges_out; +// parms_in = command_line.parms_in; +// real_in = command_line.real_in; +// strcpy ( coord_file, command_line.coord_file.c_str() ); +// strcpy ( int_file, command_line.sim_file.c_str() ); +// strcpy ( real_file, command_line.real_file.c_str() ); +// strcpy ( parms_file, command_line.parms_file.c_str() ); + +// } + +// // now we initialize all processors by reading .int file +// #ifdef MUSE_MPI +// MPI_Bcast ( &int_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// graph neighbors ( myid, num_procs, int_file ); + +// // check for user supplied parameters +// #ifdef MUSE_MPI +// MPI_Bcast ( &parms_in, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// if ( parms_in ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &parms_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.read_parms ( parms_file ); +// } + +// // set random seed, edge cutting, and real iterations parameters +// #ifdef MUSE_MPI +// MPI_Bcast ( &rand_seed, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// MPI_Bcast ( &edge_cut, 1, MPI_FLOAT, 0, MPI_COMM_WORLD ); +// MPI_Bcast ( &real_in, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.init_parms ( rand_seed, edge_cut, real_in ); + +// // check for .real file with existing coordinates +// if ( real_in >= 0 ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &real_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.read_real ( real_file ); +// } + +// neighbors.draw_graph ( int_out, coord_file ); + +// // do we have to write out the edges? +// #ifdef MUSE_MPI +// MPI_Bcast ( &edges_out, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// if ( edges_out ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &coord_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// for ( int i = 0; i < num_procs; i++ ) +// { +// if ( myid == i ) +// neighbors.write_sim ( coord_file ); +// #ifdef MUSE_MPI +// MPI_Barrier ( MPI_COMM_WORLD ); +// #endif +// } +// } + +// // finally we output file and quit +// float tot_energy; +// tot_energy = neighbors.get_tot_energy (); +// if ( myid == 0 ) +// { +// neighbors.write_coord ( coord_file ); +// cout << "Total Energy: " << tot_energy << "." << endl +// << "Program terminated successfully." << endl; +// } + +// // MPI finalize +// #ifdef MUSE_MPI +// MPI_Finalize (); +// #endif + +// return 0; +// } + +} // namespace drl + +/** + * \section about_drl + * + * + * DrL is a sophisticated layout generator developed and implemented by + * Shawn Martin et al. As of October 2012 the original DrL homepage is + * unfortunately not available. You can read more about this algorithm + * in the following technical report: Martin, S., Brown, W.M., + * Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) + * Layout. SAND Reports, 2008. 2936: p. 1-10. + * + * + * + * Only a subset of the complete DrL functionality is + * included in igraph, parallel runs and recursive, multi-level + * layouting is not supported. + * + * + * + * The parameters of the layout are stored in an \ref + * igraph_layout_drl_options_t structure, this can be initialized by + * calling the function \ref igraph_layout_drl_options_init(). + * The fields of this structure can then be adjusted by hand if needed. + * The layout is calculated by an \ref igraph_layout_drl() call. + * + */ + +/** + * \function igraph_layout_drl_options_init + * Initialize parameters for the DrL layout generator + * + * This function can be used to initialize the struct holding the + * parameters for the DrL layout generator. There are a number of + * predefined templates available, it is a good idea to start from one + * of these by modifying some parameters. + * \param options The struct to initialize. + * \param templ The template to use. Currently the following templates + * are supplied: \c IGRAPH_LAYOUT_DRL_DEFAULT, \c + * IGRAPH_LAYOUT_DRL_COARSEN, \c IGRAPH_LAYOUT_DRL_COARSEST, + * \c IGRAPH_LAYOUT_DRL_REFINE and \c IGRAPH_LAYOUT_DRL_FINAL. + * \return Error code. + * + * Time complexity: O(1). + */ + +igraph_error_t igraph_layout_drl_options_init(igraph_layout_drl_options_t *options, + igraph_layout_drl_default_t templ) { + + options->edge_cut = 32.0 / 40.0; + + switch (templ) { + case IGRAPH_LAYOUT_DRL_DEFAULT: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 10; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 2; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_COARSEN: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 10; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_COARSEST: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 10; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 200; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_REFINE: + options->init_iterations = 0; + options->init_temperature = 50; + options->init_attraction = .5; + options->init_damping_mult = 0; + + options->liquid_iterations = 0; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 50; + options->expansion_temperature = 500; + options->expansion_attraction = .1; + options->expansion_damping_mult = .25; + + options->cooldown_iterations = 50; + options->cooldown_temperature = 200; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 0; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_FINAL: + options->init_iterations = 0; + options->init_temperature = 50; + options->init_attraction = .5; + options->init_damping_mult = 0; + + options->liquid_iterations = 0; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 50; + options->expansion_temperature = 50; + options->expansion_attraction = .1; + options->expansion_damping_mult = .25; + + options->cooldown_iterations = 50; + options->cooldown_temperature = 200; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 25; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + default: + IGRAPH_ERROR("Unknown DrL template", IGRAPH_EINVAL); + break; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_drl + * The DrL layout generator + * + * This function implements the force-directed DrL layout generator. + * Please see more in the following technical report: Martin, S., + * Brown, W.M., Klavans, R., Boyack, K.W., DrL: Distributed Recursive + * (Graph) Layout. SAND Reports, 2008. 2936: p. 1-10. + * \param graph The input graph. + * \param use_seed Boolean, if true, then the coordinates + * supplied in the \p res argument are used as starting points. + * \param res Pointer to a matrix, the result layout is stored + * here. It will be resized as needed. + * \param options The parameters to pass to the layout generator. + * \param weights Edge weights, pointer to a vector. If this is a null + * pointer then every edge will have the same weight. + * \return Error code. + * + * Time complexity: ???. + */ + +igraph_error_t igraph_layout_drl(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + const char msg[] = "Damping multipliers cannot be negative, got %g."; + + if (options->init_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->init_damping_mult); + } + if (options->liquid_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->liquid_damping_mult); + } + if (options->expansion_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->expansion_damping_mult); + } + if (options->cooldown_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->cooldown_damping_mult); + } + if (options->crunch_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->crunch_damping_mult); + } + if (options->simmer_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->simmer_damping_mult); + } + + if (weights) { + igraph_int_t no_of_edges = igraph_ecount(graph); + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Length of weight vector does not match number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for DrL layout.", IGRAPH_EINVAL); + } + } + + IGRAPH_HANDLE_EXCEPTIONS( + drl::graph neighbors(graph, options, weights); + neighbors.init_parms(options); + if (use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, igraph_vcount(graph), 2)); + neighbors.read_real(res); + } + IGRAPH_CHECK(neighbors.draw_graph(res)); + ); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/drl/drl_layout.h b/src/layout/drl/drl_layout.h new file mode 100644 index 0000000..8d3cd29 --- /dev/null +++ b/src/layout/drl/drl_layout.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains compile time parameters which affect the entire +// DrL program. + +#define DRL_VERSION "3.2 5/5/2006" + +// compile time parameters for MPI message passing +#define MAX_PROCS 256 // maximum number of processors +#define MAX_FILE_NAME 250 // max length of filename +#define MAX_INT_LENGTH 4 // max length of integer suffix of intermediate .coord file + +// Compile time adjustable parameters for the Density grid + +#define GRID_SIZE 1000 // size of Density grid +#define VIEW_SIZE 4000.0 // actual physical size of layout plane +// these values use more memory but have +// little effect on performance or layout + +#define RADIUS 10 // radius for density fall-off: +// larger values tends to slow down +// the program and clump the data + +#define HALF_VIEW 2000 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE + +/* +// original values for VxOrd +#define GRID_SIZE 400 // size of VxOrd Density grid +#define VIEW_SIZE 1600.0 // actual physical size of VxOrd plane +#define RADIUS 10 // radius for density fall-off + +#define HALF_VIEW 800 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE +*/ diff --git a/src/layout/drl/drl_layout_3d.cpp b/src/layout/drl/drl_layout_3d.cpp new file mode 100644 index 0000000..3f8008d --- /dev/null +++ b/src/layout/drl/drl_layout_3d.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// Layout +// +// This program implements a parallel force directed graph drawing +// algorithm. The algorithm used is based upon a random decomposition +// of the graph and simulated shared memory of node position and density. +// In this version, the simulated shared memory is spread among all processors +// +// The structure of the inputs and outputs of this code will be displayed +// if the program is called without parameters, or if an erroneous +// parameter is passed to the program. +// +// S. Martin +// 5/6/2005 + +// layout routines and constants +#include "drl_layout_3d.h" +#include "drl_parse.h" +#include "drl_graph_3d.h" + +using namespace drl3d; +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +#include "core/exceptions.h" + +/** + * \function igraph_layout_drl_3d + * The DrL layout generator, 3d version. + * + * This function implements the force-directed DrL layout generator. + * Please see more in the technical report: Martin, S., Brown, W.M., + * Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) + * Layout. SAND Reports, 2008. 2936: p. 1-10. + * + * This function uses a modified DrL generator that does + * the layout in three dimensions. + * \param graph The input graph. + * \param use_seed Boolean, if true, then the coordinates + * supplied in the \p res argument are used as starting points. + * \param res Pointer to a matrix, the result layout is stored + * here. It will be resized as needed. + * \param options The parameters to pass to the layout generator. + * \param weights Edge weights, pointer to a vector. If this is a null + * pointer then every edge will have the same weight. + * \return Error code. + * + * Time complexity: ???. + * + * \sa \ref igraph_layout_drl() for the standard 2d version. + */ + +igraph_error_t igraph_layout_drl_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + + const char msg[] = "Damping multipliers cannot be negative, got %g."; + + if (options->init_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->init_damping_mult); + } + if (options->liquid_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->liquid_damping_mult); + } + if (options->expansion_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->expansion_damping_mult); + } + if (options->cooldown_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->cooldown_damping_mult); + } + if (options->crunch_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->crunch_damping_mult); + } + if (options->simmer_damping_mult < 0) { + IGRAPH_ERRORF(msg, IGRAPH_EINVAL, options->simmer_damping_mult); + } + + if (weights) { + igraph_int_t no_of_edges = igraph_ecount(graph); + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Length of weight vector does not match number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for DrL layout.", IGRAPH_EINVAL); + } + } + + IGRAPH_HANDLE_EXCEPTIONS( + drl3d::graph neighbors(graph, options, weights); + neighbors.init_parms(options); + if (use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, igraph_vcount(graph), 3)); + neighbors.read_real(res); + } + IGRAPH_CHECK(neighbors.draw_graph(res)); + ); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/drl/drl_layout_3d.h b/src/layout/drl/drl_layout_3d.h new file mode 100644 index 0000000..d9b0095 --- /dev/null +++ b/src/layout/drl/drl_layout_3d.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains compile time parameters which affect the entire +// DrL program. + +#define DRL_VERSION "3.2 5/5/2006" + +// compile time parameters for MPI message passing +#define MAX_PROCS 256 // maximum number of processors +#define MAX_FILE_NAME 250 // max length of filename +#define MAX_INT_LENGTH 4 // max length of integer suffix of intermediate .coord file + +// Compile time adjustable parameters for the Density grid + +#define GRID_SIZE 100 // size of Density grid +#define VIEW_SIZE 250.0 // actual physical size of layout plane +// these values use more memory but have +// little effect on performance or layout + +#define RADIUS 10 // radius for density fall-off: +// larger values tends to slow down +// the program and clump the data + +#define HALF_VIEW 125.0 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .4 // ratio of GRID_SIZE to VIEW_SIZE + +/* +// original values for VxOrd +#define GRID_SIZE 400 // size of VxOrd Density grid +#define VIEW_SIZE 1600.0 // actual physical size of VxOrd plane +#define RADIUS 10 // radius for density fall-off + +#define HALF_VIEW 800 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE +*/ diff --git a/src/layout/drl/drl_parse.cpp b/src/layout/drl/drl_parse.cpp new file mode 100644 index 0000000..c0e98cd --- /dev/null +++ b/src/layout/drl/drl_parse.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the methods for the parse.h class + +#include "drl_layout.h" +#include "drl_parse.h" + +namespace drl { + +// void parse::print_syntax( const char *error_string ) +// { +// cout << endl << "Error: " << error_string << endl; +// cout << endl << "Layout" << endl +// << "------" << endl +// << "S. Martin" << endl +// << "Version " << DRL_VERSION << endl << endl +// << "This program provides a parallel adaptation of a force directed" << endl +// << "graph layout algorithm for use with large datasets." << endl << endl +// << "Usage: layout [options] root_file" << endl << endl +// << "root_file -- the root name of the file being processed." << endl << endl +// << "INPUT" << endl +// << "-----" << endl +// << "root_file.int -- the input file containing the graph to draw using layout." << endl +// << " The .int file must have the suffix \".int\" and each line of .int file" << endl +// << " should have the form" << endl +// << "\tnode_id node_id weight" << endl +// << " where node_id's are integers in sequence starting from 0, and" << endl +// << " weight is a float > 0." << endl << endl +// << "OUTPUT" << endl +// << "------" << endl +// << "root_file.icoord -- the resulting output file, containing an ordination" << endl +// << " of the graph. The .icoord file will have the suffix \".icoord\" and" << endl +// << " each line of the .icoord file will be of the form" << endl +// << "\tnode_id x-coord y-coord" << endl << endl +// << "Options:" << endl << endl +// << "\t-s {int>=0} random seed (default value is 0)" << endl +// << "\t-c {real[0,1]} edge cutting (default 32/40 = .8)" << endl +// << "\t (old max was 39/40 = .975)" << endl +// << "\t-p input parameters from .parms file" << endl +// << "\t-r {real[0,1]} input coordinates from .real file" << endl +// << "\t (hold fixed until fraction of optimization schedule reached)" << endl +// << "\t-i {int>=0} intermediate output interval (default 0: no output)" << endl +// << "\t-e output .iedges file (same prefix as .coord file)" << endl << endl; + +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// parse::parse ( int argc, char** argv) +// { +// map m; + +// // make sure there is at least one argument +// if ( argc < 2) +// print_syntax ( "not enough arguments!" ); + +// // make sure coord_file ends in ".coord" +// parms_file = real_file = sim_file = coord_file = argv[argc-1]; +// parms_file = parms_file + ".parms"; +// real_file = real_file + ".real"; +// sim_file = sim_file + ".int"; +// coord_file = coord_file + ".icoord"; + +// char error_string[200]; +// sprintf ( error_string, "%s %d %s", "root file name cannot be longer than", MAX_FILE_NAME-7, +// "characters."); +// if ( coord_file.length() > MAX_FILE_NAME ) +// print_syntax ( error_string ); + +// // echo sim_file and coord_file +// cout << "Using " << sim_file << " for .int file, and " << coord_file << " for .icoord file." << endl; + +// // set defaults +// rand_seed = 0; +// //edge_cut = 32.0/39.0; // (old default) +// edge_cut = 32.0/40.0; +// int_out = 0; +// edges_out = 0; +// parms_in = 0; +// real_in = -1.0; + +// // now check for optional arguments +// string arg; +// for( int i = 1; i= (argc-1) ) +// print_syntax ( "-s flag has no argument." ); +// else +// { +// rand_seed = atoi ( argv[i] ); +// if ( rand_seed < 0 ) +// print_syntax ( "random seed must be >= 0." ); +// } +// } +// // check for edge cutting +// else if ( arg == "-c" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-c flag has no argument." ); +// else +// { +// edge_cut = atof ( argv[i] ); +// if ( (edge_cut < 0) || (edge_cut > 1) ) +// print_syntax ( "edge cut must be between 0 and 1." ); +// } +// } +// // check for intermediate output +// else if ( arg == "-i" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-i flag has no argument." ); +// else +// { +// int_out = atoi ( argv[i] ); +// if ( int_out < 0 ) +// print_syntax ( "intermediate output must be >= 0." ); +// } +// } +// // check for .real input +// else if ( arg == "-r" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-r flag has no argument." ); +// else +// { +// real_in = atof ( argv[i] ); +// if ( (real_in < 0) || (real_in > 1) ) +// print_syntax ( "real iteration fraction must be from 0 to 1." ); +// } +// } +// else if ( arg == "-e" ) +// edges_out = 1; +// else if ( arg == "-p" ) +// parms_in = 1; +// else +// print_syntax ( "unrecongized option!" ); +// } + +// if ( parms_in ) +// cout << "Using " << parms_file << " for .parms file." << endl; + +// if ( real_in >= 0 ) +// cout << "Using " << real_file << " for .real file." << endl; + +// // echo arguments input or default +// cout << "Using random seed = " << rand_seed << endl +// << " edge_cutting = " << edge_cut << endl +// << " intermediate output = " << int_out << endl +// << " output .iedges file = " << edges_out << endl; +// if ( real_in >= 0 ) +// cout << " holding .real fixed until iterations = " << real_in << endl; + +// } + +} // namespace drl diff --git a/src/layout/drl/drl_parse.h b/src/layout/drl/drl_parse.h new file mode 100644 index 0000000..b1e2761 --- /dev/null +++ b/src/layout/drl/drl_parse.h @@ -0,0 +1,68 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The parse class contains the methods necessary to parse +// the command line, print help, and do error checking + +#include + +namespace drl { + +class parse { + +public: + + // Methods + + parse ( int argc, char **argv ); + ~parse () {} + + // user parameters + std::string sim_file; // .sim file + std::string coord_file; // .coord file + std::string parms_file; // .parms file + std::string real_file; // .real file + + int rand_seed; // random seed int >= 0 + float edge_cut; // edge cutting real [0,1] + int int_out; // intermediate output, int >= 1 + int edges_out; // true if .edges file is requested + int parms_in; // true if .parms file is to be read + float real_in; // true if .real file is to be read + +private: + + void print_syntax ( const char *error_string ); + +}; + +} // namespace drl diff --git a/src/layout/fruchterman_reingold.c b/src/layout/fruchterman_reingold.c new file mode 100644 index 0000000..0b2abfa --- /dev/null +++ b/src/layout/fruchterman_reingold.c @@ -0,0 +1,676 @@ +/* + igraph library. + Copyright (C) 2003-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_layout.h" + +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_components.h" + +#include "core/grid.h" +#include "core/interruption.h" +#include "layout/layout_internal.h" + +static igraph_error_t igraph_layout_i_fr(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_t dispx, dispy; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + igraph_bool_t conn = true; + igraph_real_t C = 0; + + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + C = vcount * sqrt(vcount); + } + + if (!use_seed) { + igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy); + } + + IGRAPH_VECTOR_INIT_FINALLY(&dispx, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&dispy, vcount); + + for (igraph_int_t i = 0; i < niter; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* calculate repulsive forces, we have a special version + for unconnected graphs */ + igraph_vector_null(&dispx); + igraph_vector_null(&dispy); + if (conn) { + for (igraph_int_t v = 0; v < vcount; v++) { + for (igraph_int_t u = v + 1; u < vcount; u++) { + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dlen = dx * dx + dy * dy; + + while (dlen == 0) { + dx = RNG_UNIF(-1e-9, 1e-9); + dy = RNG_UNIF(-1e-9, 1e-9); + dlen = dx * dx + dy * dy; + } + + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + } + } + } else { + for (igraph_int_t v = 0; v < vcount; v++) { + for (igraph_int_t u = v + 1; u < vcount; u++) { + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dlen, rdlen; + + dlen = dx * dx + dy * dy; + while (dlen == 0) { + dx = RNG_UNIF(-1e-9, 1e-9); + dy = RNG_UNIF(-1e-9, 1e-9); + dlen = dx * dx + dy * dy; + } + + rdlen = sqrt(dlen); + + VECTOR(dispx)[v] += dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispx)[u] -= dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[u] -= dy * (C - dlen * rdlen) / (dlen * C); + } + } + } + + /* calculate attractive forces */ + for (igraph_int_t e = 0; e < ecount; e++) { + /* each edge is an ordered pair of vertices v and u */ + igraph_int_t v = IGRAPH_FROM(graph, e); + igraph_int_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t w = weight ? VECTOR(*weight)[e] : 1.0; + igraph_real_t dlen = sqrt(dx*dx + dy*dy) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + } + + /* limit max displacement to temperature t and prevent from + displacement outside frame */ + for (igraph_int_t v = 0; v < vcount; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t displen = sqrt(dx * dx + dy * dy); + + if (displen > temp) { + dx *= temp/displen; + dy *= temp/displen; + } + + if (displen > 0) { + MATRIX(*res, v, 0) += dx; + MATRIX(*res, v, 1) += dy; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + } + + temp -= difftemp; + } + + igraph_vector_destroy(&dispx); + igraph_vector_destroy(&dispy); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_layout_i_grid_fr( + const igraph_t *graph, + igraph_matrix_t *res, igraph_bool_t use_seed, + igraph_int_t niter, igraph_real_t start_temp, + const igraph_vector_t *weight, const igraph_vector_t *minx, + const igraph_vector_t *maxx, const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_real_t width = sqrt(vcount), height = width; + igraph_2dgrid_t grid; + igraph_vector_t dispx, dispy; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + igraph_2dgrid_iterator_t vidit; + const igraph_real_t cellsize = 2.0; + + if (!use_seed) { + igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy); + } + + /* make grid */ + IGRAPH_CHECK(igraph_2dgrid_init(&grid, res, -width / 2, width / 2, cellsize, + -height / 2, height / 2, cellsize)); + IGRAPH_FINALLY(igraph_2dgrid_destroy, &grid); + + /* place vertices on grid */ + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_2dgrid_add2(&grid, i); + } + + IGRAPH_VECTOR_INIT_FINALLY(&dispx, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&dispy, vcount); + + for (igraph_int_t i = 0; i < niter; i++) { + igraph_int_t v, u; + + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_null(&dispx); + igraph_vector_null(&dispy); + + /* repulsion */ + igraph_2dgrid_reset(&grid, &vidit); + while ( (v = igraph_2dgrid_next(&grid, &vidit) - 1) != -1) { + while ( (u = igraph_2dgrid_next_nei(&grid, &vidit) - 1) != -1) { + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dlen = dx * dx + dy * dy; + while (dlen == 0) { + dx = RNG_UNIF(-1e-9, 1e-9); + dy = RNG_UNIF(-1e-9, 1e-9); + dlen = dx * dx + dy * dy; + } + if (dlen < cellsize * cellsize) { + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + } + } + } + + /* attraction */ + for (igraph_int_t e = 0; e < ecount; e++) { + igraph_int_t v = IGRAPH_FROM(graph, e); + igraph_int_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t w = weight ? VECTOR(*weight)[e] : 1.0; + igraph_real_t dlen = sqrt(dx*dx + dy*dy) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + } + + /* update */ + for (v = 0; v < vcount; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t displen = sqrt(dx * dx + dy * dy); + + if (displen > temp) { + dx *= temp/displen; + dy *= temp/displen; + } + + if (displen > 0) { + MATRIX(*res, v, 0) += dx; + MATRIX(*res, v, 1) += dy; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + } + + temp -= difftemp; + } + + igraph_vector_destroy(&dispx); + igraph_vector_destroy(&dispy); + igraph_2dgrid_destroy(&grid); + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +/** + * \ingroup layout + * \function igraph_layout_fruchterman_reingold + * \brief Places the vertices on a plane according to the Fruchterman-Reingold algorithm. + * + * + * This is a force-directed layout that simulates an attractive force \c f_a between + * connected vertex pairs and a repulsive force \c f_r between all vertex pairs. + * The forces are computed as a function of the distance \c d between the two vertices as + * + * + * f_a(d) = -w * d^2 and f_r(d) = 1/d, + * + * + * where \c w represents the edge weight. The equilibrium distance of two connected + * vertices is thus 1/w^3, assuming no other forces acting on them. + * + * + * In disconnected graphs, igraph effectively inserts a weak connection of weight + * n^(-3/2) between all pairs of vertices, where \c n is the vertex count. + * This ensures that components are kept near each other. + * + * + * Reference: + * + * + * Fruchterman, T.M.J. and Reingold, E.M.: + * Graph Drawing by Force-directed Placement. + * Software -- Practice and Experience, 21/11, 1129--1164, + * 1991. https://doi.org/10.1002/spe.4380211102 + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param use_seed If true the supplied values in the + * \p res argument are used as an initial layout, if + * false a random initial layout is used. + * \param niter The number of iterations to do. A reasonable + * default value is 500. + * \param start_temp Start temperature. This is the maximum amount + * of movement allowed along one axis, within one step, for a + * vertex. Currently it is decreased linearly to zero during + * the iteration. + * \param grid Whether to use the (fast but less accurate) grid based + * version of the algorithm. Possible values: \c + * IGRAPH_LAYOUT_GRID, \c IGRAPH_LAYOUT_NOGRID, \c + * IGRAPH_LAYOUT_AUTOGRID. The last one uses the grid based + * version only for large graphs, currently the ones with + * more than 1000 vertices. + * \param weights Pointer to a vector containing edge weights. Weights must + * be positive. If \c NULL, all edges are assumed to have weight 1. + * The attraction along the edges will be multiplied by the weights, + * resulting in vertices connected by a high-weight edge being placed + * closer together. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|^2) in each + * iteration, |V| is the number of + * vertices in the graph. + */ + +igraph_error_t igraph_layout_fruchterman_reingold(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + igraph_layout_grid_t grid, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Fruchterman-Reingold layout.", IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != vcount || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Fruchterman-Reingold layout.", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + if (weights && ecount > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for Fruchterman-Reingold layout.", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != vcount) { + IGRAPH_ERROR("Invalid minx vector length.", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != vcount) { + IGRAPH_ERROR("Invalid maxx vector length.", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx.", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != vcount) { + IGRAPH_ERROR("Invalid miny vector length.", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != vcount) { + IGRAPH_ERROR("Invalid maxy vector length.", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy.", IGRAPH_EINVAL); + } + + if (grid == IGRAPH_LAYOUT_AUTOGRID) { + if (vcount > 1000) { + grid = IGRAPH_LAYOUT_GRID; + } else { + grid = IGRAPH_LAYOUT_NOGRID; + } + } + + if (grid == IGRAPH_LAYOUT_GRID) { + return igraph_layout_i_grid_fr(graph, res, use_seed, niter, start_temp, + weights, minx, maxx, miny, maxy); + } else { + return igraph_layout_i_fr(graph, res, use_seed, niter, start_temp, + weights, minx, maxx, miny, maxy); + } +} + +/** + * \function igraph_layout_fruchterman_reingold_3d + * \brief 3D Fruchterman-Reingold algorithm. + * + * This is the 3D version of the force based Fruchterman-Reingold layout. + * See \ref igraph_layout_fruchterman_reingold() for the 2D version. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param use_seed If true the supplied values in the + * \p res argument are used as an initial layout, if + * false a random initial layout is used. + * \param niter The number of iterations to do. A reasonable + * default value is 500. + * \param start_temp Start temperature. This is the maximum amount + * of movement alloved along one axis, within one step, for a + * vertex. Currently it is decreased linearly to zero during + * the iteration. + * \param weights Pointer to a vector containing edge weights. Weights must + * be positive. If \c NULL, all edges are assumed to have weight 1. + * The attraction along the edges will be multiplied by the weights, + * resulting in vertices connected by a high-weight edge being placed + * closer together. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \param minz Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote z \endquote coordinate for every vertex. + * \param maxz Same as \p minz, but the maximum \quote z \endquote + * coordinates. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|^2) in each + * iteration, |V| is the number of + * vertices in the graph. + * + */ + +igraph_error_t igraph_layout_fruchterman_reingold_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_int_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weights, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy, + const igraph_vector_t *minz, + const igraph_vector_t *maxz) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_t dispx, dispy, dispz; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + igraph_bool_t conn = true; + igraph_real_t C = 0; + + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != vcount || + igraph_matrix_ncol(res) != 3)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + if (weights && ecount > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for Fruchterman-Reingold layout.", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != vcount) { + IGRAPH_ERROR("Invalid minx vector length", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != vcount) { + IGRAPH_ERROR("Invalid maxx vector length", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != vcount) { + IGRAPH_ERROR("Invalid miny vector length", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != vcount) { + IGRAPH_ERROR("Invalid maxy vector length", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy", IGRAPH_EINVAL); + } + if (minz && igraph_vector_size(minz) != vcount) { + IGRAPH_ERROR("Invalid minz vector length", IGRAPH_EINVAL); + } + if (maxz && igraph_vector_size(maxz) != vcount) { + IGRAPH_ERROR("Invalid maxz vector length", IGRAPH_EINVAL); + } + if (minz && maxz && !igraph_vector_all_le(minz, maxz)) { + IGRAPH_ERROR("minz must not be greater than maxz", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + C = vcount * sqrt(vcount); + } + + if (!use_seed) { + igraph_i_layout_random_bounded_3d(graph, res, minx, maxx, miny, maxy, minz, maxz); + } + + IGRAPH_VECTOR_INIT_FINALLY(&dispx, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&dispy, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&dispz, vcount); + + for (igraph_int_t i = 0; i < niter; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* calculate repulsive forces, we have a special version + for unconnected graphs */ + igraph_vector_null(&dispx); + igraph_vector_null(&dispy); + igraph_vector_null(&dispz); + if (conn) { + for (igraph_int_t v = 0; v < vcount; v++) { + for (igraph_int_t u = v + 1; u < vcount; u++) { + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + igraph_real_t dlen = dx * dx + dy * dy + dz * dz; + + while (dlen == 0) { + dx = RNG_UNIF(-1e-9, 1e-9); + dy = RNG_UNIF(-1e-9, 1e-9); + dz = RNG_UNIF(-1e-9, 1e-9); + dlen = dx * dx + dy * dy + dz * dz; + } + + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispz)[v] += dz / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + VECTOR(dispz)[u] -= dz / dlen; + } + } + } else { + for (igraph_int_t v = 0; v < vcount; v++) { + for (igraph_int_t u = v + 1; u < vcount; u++) { + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + igraph_real_t dlen, rdlen; + + dlen = dx * dx + dy * dy + dz * dz; + while (dlen == 0) { + dx = RNG_UNIF(-1e-9, 1e-9); + dy = RNG_UNIF(-1e-9, 1e-9); + dz = RNG_UNIF(-1e-9, 1e-9); + dlen = dx * dx + dy * dy + dz * dz; + } + + rdlen = sqrt(dlen); + + VECTOR(dispx)[v] += dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dz * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispx)[u] -= dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[u] -= dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispz)[u] -= dz * (C - dlen * rdlen) / (dlen * C); + } + } + } + + /* calculate attractive forces */ + for (igraph_int_t e = 0; e < ecount; e++) { + /* each edges is an ordered pair of vertices v and u */ + igraph_int_t v = IGRAPH_FROM(graph, e); + igraph_int_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1.0; + igraph_real_t dlen = sqrt(dx * dx + dy * dy + dz * dz) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispz)[v] -= (dz * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + VECTOR(dispz)[u] += (dz * dlen); + } + + /* limit max displacement to temperature t and prevent from + displacement outside frame */ + for (igraph_int_t v = 0; v < vcount; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t dz = VECTOR(dispz)[v] + RNG_UNIF(-1e-9, 1e-9); + igraph_real_t displen = sqrt(dx * dx + dy * dy + dz * dz); + + if (displen > temp) { + dx *= temp/displen; + dy *= temp/displen; + dz *= temp/displen; + } + + if (displen > 0) { + MATRIX(*res, v, 0) += dx; + MATRIX(*res, v, 1) += dy; + MATRIX(*res, v, 2) += dz; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + if (minz && MATRIX(*res, v, 2) < VECTOR(*minz)[v]) { + MATRIX(*res, v, 2) = VECTOR(*minz)[v]; + } + if (maxz && MATRIX(*res, v, 2) > VECTOR(*maxz)[v]) { + MATRIX(*res, v, 2) = VECTOR(*maxz)[v]; + } + } + + temp -= difftemp; + } + + igraph_vector_destroy(&dispx); + igraph_vector_destroy(&dispy); + igraph_vector_destroy(&dispz); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/gem.c b/src/layout/gem.c new file mode 100644 index 0000000..866e5c6 --- /dev/null +++ b/src/layout/gem.c @@ -0,0 +1,249 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "core/interruption.h" +#include "core/math.h" /* M_PI */ + +/** + * \ingroup layout + * \function igraph_layout_gem + * \brief Layout graph according to GEM algorithm. + * + * The GEM layout algorithm, as described in Arne Frick, Andreas Ludwig, + * Heiko Mehldau: A Fast Adaptive Layout Algorithm for Undirected Graphs, + * Proc. Graph Drawing 1994, LNCS 894, pp. 388-403, 1995. + * \param graph The input graph. Edge directions are ignored in + * directed graphs. + * \param res The result is stored here. If the \p use_seed argument + * is true, then this matrix is also used as the + * starting point of the algorithm. + * \param use_seed Boolean, whether to use the supplied coordinates in + * \p res as the starting point. If false (zero), then a + * uniform random starting point is used. + * \param maxiter The maximum number of iterations to + * perform. Updating a single vertex counts as an iteration. + * A reasonable default is 40 * n * n, where n is the number of + * vertices. The original paper suggests 4 * n * n, but this + * usually only works if the other parameters are set up carefully. + * \param temp_max The maximum allowed local temperature. A reasonable + * default is the number of vertices. + * \param temp_min The global temperature at which the algorithm + * terminates (even before reaching \p maxiter iterations). A + * reasonable default is 1/10. + * \param temp_init Initial local temperature of all vertices. A + * reasonable default is the square root of the number of + * vertices. + * \return Error code. + * + * Time complexity: O(t * n * (n+e)), where n is the number of vertices, + * e is the number of edges and t is the number of time steps + * performed. + */ + +igraph_error_t igraph_layout_gem(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t temp_max, igraph_real_t temp_min, + igraph_real_t temp_init) { + + igraph_int_t no_nodes = igraph_vcount(graph); + igraph_vector_int_t perm; + igraph_vector_t impulse_x, impulse_y, temp, skew_gauge; + igraph_int_t i; + igraph_real_t temp_global; + igraph_int_t perm_pointer = 0; + igraph_real_t barycenter_x = 0, barycenter_y = 0; + igraph_vector_t phi; + igraph_vector_int_t neis; + const igraph_real_t elen_des2 = 128 * 128; + const igraph_real_t gamma = 1 / 16.0; + const igraph_real_t alpha_o = M_PI; + const igraph_real_t alpha_r = M_PI / 3.0; + const igraph_real_t sigma_o = 1.0 / 3.0; + const igraph_real_t sigma_r = 1.0 / 2.0 / no_nodes; + + if (maxiter < 0) { + IGRAPH_ERRORF("Number of iterations must be non-negative in GEM layout, " + "got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, maxiter); + } + if (use_seed && igraph_matrix_nrow(res) != no_nodes) { + IGRAPH_ERRORF("In GEM layout, seed matrix number of rows should equal number of nodes (%" IGRAPH_PRId "), got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, no_nodes, igraph_matrix_nrow(res)); + } + if (use_seed && igraph_matrix_ncol(res) != 2) { + IGRAPH_ERRORF("In GEM layout, seed matrix number of columns should be 2, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, igraph_matrix_ncol(res)); + } + if (temp_max <= 0) { + IGRAPH_ERRORF("Maximum temperature should be positive in GEM layout, got %g.", + IGRAPH_EINVAL, temp_max); + } + if (temp_min <= 0) { + IGRAPH_ERRORF("Minimum temperature should be positive in GEM layout, got %g.", + IGRAPH_EINVAL, temp_min); + } + if (temp_init <= 0) { + IGRAPH_ERRORF("Initial temperature should be positive in GEM layout, got %g.", + IGRAPH_EINVAL, temp_init); + } + if (temp_max < temp_init || temp_init < temp_min) { + IGRAPH_ERRORF("Minimum <= Initial <= Maximum temperature is required " + "in GEM layout, but %g is not larger than %g and smaller than %g.", IGRAPH_EINVAL, + temp_init, temp_min, temp_max); + } + + if (no_nodes == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&impulse_x, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&impulse_y, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&temp, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&skew_gauge, no_nodes); + IGRAPH_CHECK(igraph_vector_int_init_range(&perm, 0, no_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &perm); + IGRAPH_VECTOR_INIT_FINALLY(&phi, no_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 10); + + /* Initialization */ + IGRAPH_CHECK(igraph_strength(graph, &phi, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, /* weights = */ 0)); + + if (!use_seed) { + const igraph_real_t width_half = no_nodes * 100, height_half = width_half; + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (i = 0; i < no_nodes; i++) { + MATRIX(*res, i, 0) = RNG_UNIF(-width_half, width_half); + MATRIX(*res, i, 1) = RNG_UNIF(-height_half, height_half); + barycenter_x += MATRIX(*res, i, 0); + barycenter_y += MATRIX(*res, i, 1); + VECTOR(phi)[i] *= (VECTOR(phi)[i] / 2.0 + 1.0); + } + } else { + for (i = 0; i < no_nodes; i++) { + barycenter_x += MATRIX(*res, i, 0); + barycenter_y += MATRIX(*res, i, 1); + VECTOR(phi)[i] *= (VECTOR(phi)[i] / 2.0 + 1.0); + } + } + igraph_vector_fill(&temp, temp_init); + temp_global = temp_init * no_nodes; + + while (temp_global > temp_min * no_nodes && maxiter > 0) { + igraph_int_t u, v, nlen, j; + igraph_real_t px, py, pvx, pvy; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* choose a vertex v to update */ + if (perm_pointer <= 0) { + igraph_vector_int_shuffle(&perm); + perm_pointer = no_nodes - 1; + } + v = VECTOR(perm)[perm_pointer--]; + + /* compute v's impulse */ + px = (barycenter_x / no_nodes - MATRIX(*res, v, 0)) * gamma * VECTOR(phi)[v]; + py = (barycenter_y / no_nodes - MATRIX(*res, v, 1)) * gamma * VECTOR(phi)[v]; + px += RNG_UNIF(-32.0, 32.0); + py += RNG_UNIF(-32.0, 32.0); + + for (u = 0; u < no_nodes; u++) { + igraph_real_t dx, dy, dist2; + if (u == v) { + continue; + } + dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + dist2 = dx * dx + dy * dy; + if (dist2 != 0) { + px += dx * elen_des2 / dist2; + py += dy * elen_des2 / dist2; + } + } + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + nlen = igraph_vector_int_size(&neis); + for (j = 0; j < nlen; j++) { + igraph_int_t u = VECTOR(neis)[j]; + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dist2 = dx * dx + dy * dy; + px -= dx * dist2 / (elen_des2 * VECTOR(phi)[v]); + py -= dy * dist2 / (elen_des2 * VECTOR(phi)[v]); + } + + /* update v's position and temperature */ + if (px != 0 || py != 0) { + igraph_real_t plen = sqrt(px * px + py * py); + px *= VECTOR(temp)[v] / plen; + py *= VECTOR(temp)[v] / plen; + MATRIX(*res, v, 0) += px; + MATRIX(*res, v, 1) += py; + barycenter_x += px; + barycenter_y += py; + } + + pvx = VECTOR(impulse_x)[v]; pvy = VECTOR(impulse_y)[v]; + if (pvx != 0 || pvy != 0) { + igraph_real_t beta = atan2(pvy - py, pvx - px); + igraph_real_t sin_beta = sin(beta); + igraph_real_t sign_sin_beta = (sin_beta > 0) ? 1 : ((sin_beta < 0) ? -1 : 0); + igraph_real_t cos_beta = cos(beta); + igraph_real_t abs_cos_beta = fabs(cos_beta); + igraph_real_t old_temp = VECTOR(temp)[v]; + if (sin(beta) >= sin(M_PI_2 + alpha_r / 2.0)) { + VECTOR(skew_gauge)[v] += sigma_r * sign_sin_beta; + } + if (abs_cos_beta >= cos(alpha_o / 2.0)) { + VECTOR(temp)[v] *= sigma_o * cos_beta; + } + VECTOR(temp)[v] *= (1 - fabs(VECTOR(skew_gauge)[v])); + if (VECTOR(temp)[v] > temp_max) { + VECTOR(temp)[v] = temp_max; + } + VECTOR(impulse_x)[v] = px; + VECTOR(impulse_y)[v] = py; + temp_global += VECTOR(temp)[v] - old_temp; + } + + maxiter--; + + } /* while temp && iter */ + + igraph_vector_int_destroy(&neis); + igraph_vector_destroy(&phi); + igraph_vector_int_destroy(&perm); + igraph_vector_destroy(&skew_gauge); + igraph_vector_destroy(&temp); + igraph_vector_destroy(&impulse_y); + igraph_vector_destroy(&impulse_x); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/graphopt.c b/src/layout/graphopt.c new file mode 100644 index 0000000..16e11b1 --- /dev/null +++ b/src/layout/graphopt.c @@ -0,0 +1,436 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_progress.h" + +#include "core/interruption.h" + +#define COULOMBS_CONSTANT 8987500000.0 + + +static igraph_real_t igraph_i_distance_between( + const igraph_matrix_t *c, + igraph_int_t a, igraph_int_t b); + +static void igraph_i_determine_electric_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, + igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + igraph_int_t other_node, + igraph_int_t this_node); + +static void igraph_i_apply_electrical_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + igraph_int_t other_node, igraph_int_t this_node, + igraph_real_t node_charge, + igraph_real_t distance); + +static void igraph_i_determine_spring_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + igraph_real_t spring_length, + igraph_int_t other_node, + igraph_int_t this_node); + +static void igraph_i_apply_spring_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + igraph_int_t other_node, + igraph_int_t this_node, igraph_real_t spring_length, + igraph_real_t spring_constant); + +static void igraph_i_move_nodes( + igraph_matrix_t *pos, + const igraph_vector_t *pending_forces_x, + const igraph_vector_t *pending_forces_y, + igraph_real_t node_mass, + igraph_real_t max_sa_movement); + +static igraph_real_t igraph_i_distance_between( + const igraph_matrix_t *c, + igraph_int_t a, igraph_int_t b) { + igraph_real_t diffx = MATRIX(*c, a, 0) - MATRIX(*c, b, 0); + igraph_real_t diffy = MATRIX(*c, a, 1) - MATRIX(*c, b, 1); + return sqrt(diffx*diffx + diffy*diffy); +} + +static void igraph_i_determine_electric_axal_forces(const igraph_matrix_t *pos, + igraph_real_t *x, + igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + igraph_int_t other_node, + igraph_int_t this_node) { + + // We know what the directed force is. We now need to translate it + // into the appropriate x and y components. + // First, assume: + // other_node + // /| + // directed_force / | + // / | y + // /______| + // this_node x + // + // other_node.x > this_node.x + // other_node.y > this_node.y + // the force will be on this_node away from other_node + + // the proportion (distance/y_distance) is equal to the proportion + // (directed_force/y_force), as the two triangles are similar. + // therefore, the magnitude of y_force = (directed_force*y_distance)/distance + // the sign of y_force is negative, away from other_node + + igraph_real_t x_distance, y_distance; + y_distance = MATRIX(*pos, other_node, 1) - MATRIX(*pos, this_node, 1); + if (y_distance < 0) { + y_distance = -y_distance; + } + *y = -1 * ((directed_force * y_distance) / distance); + + // the x component works in exactly the same way. + x_distance = MATRIX(*pos, other_node, 0) - MATRIX(*pos, this_node, 0); + if (x_distance < 0) { + x_distance = -x_distance; + } + *x = -1 * ((directed_force * x_distance) / distance); + + // Now we need to reverse the polarity of our answers based on the falsness + // of our assumptions. + if (MATRIX(*pos, other_node, 0) < MATRIX(*pos, this_node, 0)) { + *x = *x * -1; + } + if (MATRIX(*pos, other_node, 1) < MATRIX(*pos, this_node, 1)) { + *y = *y * -1; + } +} + +static void igraph_i_apply_electrical_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + igraph_int_t other_node, igraph_int_t this_node, + igraph_real_t node_charge, + igraph_real_t distance) { + + igraph_real_t directed_force = COULOMBS_CONSTANT * + ((node_charge * node_charge) / (distance * distance)); + + igraph_real_t x_force, y_force; + igraph_i_determine_electric_axal_forces(pos, &x_force, &y_force, + directed_force, distance, + other_node, this_node); + + VECTOR(*pending_forces_x)[this_node] += x_force; + VECTOR(*pending_forces_y)[this_node] += y_force; + VECTOR(*pending_forces_x)[other_node] -= x_force; + VECTOR(*pending_forces_y)[other_node] -= y_force; +} + +static void igraph_i_determine_spring_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + igraph_real_t spring_length, + igraph_int_t other_node, igraph_int_t this_node) { + + // if the spring is just the right size, the forces will be 0, so we can + // skip the computation. + // + // if the spring is too long, our forces will be identical to those computed + // by determine_electrical_axal_forces() (this_node will be pulled toward + // other_node). + // + // if the spring is too short, our forces will be the opposite of those + // computed by determine_electrical_axal_forces() (this_node will be pushed + // away from other_node) + // + // finally, since both nodes are movable, only one-half of the total force + // should be applied to each node, so half the forces for our answer. + + if (distance == spring_length) { + *x = 0.0; + *y = 0.0; + } else { + igraph_i_determine_electric_axal_forces(pos, x, y, directed_force, distance, + other_node, this_node); + if (distance < spring_length) { + *x = -1 * *x; + *y = -1 * *y; + } + *x = 0.5 * *x; + *y = 0.5 * *y; + } +} + +static void igraph_i_apply_spring_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + igraph_int_t other_node, + igraph_int_t this_node, igraph_real_t spring_length, + igraph_real_t spring_constant) { + + // determined using Hooke's Law: + // force = -kx + // where: + // k = spring constant + // x = displacement from ideal length in meters + + igraph_real_t distance, displacement, directed_force, x_force, y_force; + distance = igraph_i_distance_between(pos, other_node, this_node); + // let's protect ourselves from division by zero by ignoring two nodes that + // happen to be in the same place. Since we separate all nodes before we + // work on any of them, this will only happen in extremely rare circumstances, + // and when it does, electrical force will probably push one or both of them + // one way or another anyway. + if (distance == 0.0) { + return; + } + + displacement = distance - spring_length; + if (displacement < 0) { + displacement = -displacement; + } + directed_force = -1 * spring_constant * displacement; + // remember, this is force directed away from the spring; + // a negative number is back towards the spring (or, in our case, back towards + // the other node) + + // get the force that should be applied to >this< node + igraph_i_determine_spring_axal_forces(pos, &x_force, &y_force, + directed_force, distance, spring_length, + other_node, this_node); + + VECTOR(*pending_forces_x)[this_node] += x_force; + VECTOR(*pending_forces_y)[this_node] += y_force; + VECTOR(*pending_forces_x)[other_node] -= x_force; + VECTOR(*pending_forces_y)[other_node] -= y_force; +} + +static void igraph_i_move_nodes( + igraph_matrix_t *pos, + const igraph_vector_t *pending_forces_x, + const igraph_vector_t *pending_forces_y, + igraph_real_t node_mass, + igraph_real_t max_sa_movement) { + + // Since each iteration is isolated, time is constant at 1. + // Therefore: + // Force effects acceleration. + // acceleration (d(velocity)/time) = velocity + // velocity (d(displacement)/time) = displacement + // displacement = acceleration + + // determined using Newton's second law: + // sum(F) = ma + // therefore: + // acceleration = force / mass + // velocity = force / mass + // displacement = force / mass + + igraph_int_t this_node, no_of_nodes = igraph_vector_size(pending_forces_x); + + for (this_node = 0; this_node < no_of_nodes; this_node++) { + + igraph_real_t x_movement, y_movement; + + x_movement = VECTOR(*pending_forces_x)[this_node] / node_mass; + if (x_movement > max_sa_movement) { + x_movement = max_sa_movement; + } else if (x_movement < -max_sa_movement) { + x_movement = -max_sa_movement; + } + + y_movement = VECTOR(*pending_forces_y)[this_node] / node_mass; + if (y_movement > max_sa_movement) { + y_movement = max_sa_movement; + } else if (y_movement < -max_sa_movement) { + y_movement = -max_sa_movement; + } + + MATRIX(*pos, this_node, 0) += x_movement; + MATRIX(*pos, this_node, 1) += y_movement; + + } +} + +/** + * \function igraph_layout_graphopt + * \brief Optimizes vertex layout via the graphopt algorithm. + * + * This is a port of the graphopt layout algorithm by Michael Schmuhl. + * graphopt version 0.4.1 was rewritten in C, the support for + * layers was removed and the code was reorganized to avoid some + * unnecessary steps when the node charge (see below) is zero. + * + * + * Graphopt uses physical analogies for defining attracting and repelling + * forces among the vertices and then the physical system is simulated + * until it reaches an equilibrium. (There is no simulated annealing or + * anything like that, so a stable fixed point is not guaranteed.) + * + * + * See also + * https://web.archive.org/web/20220611030748/http://www.schmuhl.org/graphopt/ + * and + * https://sourceforge.net/projects/graphopt/ + * for the original graphopt. + * + * \param graph The input graph. + * \param res Pointer to an initialized matrix, the result will be stored here + * and its initial contents are used as the starting point of the simulation + * if the \p use_seed argument is true. Note that in this case the + * matrix should have the proper size, otherwise a warning is issued and + * the supplied values are ignored. If no starting positions are given + * (or they are invalid) then a random starting position is used. + * The matrix will be resized if needed. + * \param niter Integer constant, the number of iterations to perform. + * Should be a couple of hundred in general. If you have a large graph + * then you might want to only do a few iterations and then check the + * result. If it is not good enough you can feed it in again in + * the \p res argument. The original graphopt default is 500. + * \param node_charge The charge of the vertices, used to calculate electric + * repulsion. The original graphopt default is 0.001. + * \param node_mass The mass of the vertices, used for the spring forces. + * The original graphopt defaults to 30. + * \param spring_length The length of the springs. + * The original graphopt defaults to zero. + * \param spring_constant The spring constant, the original graphopt defaults + * to one. + * \param max_sa_movement Real constant, it gives the maximum amount of movement + * allowed in a single step along a single axis. The original graphopt + * default is 5. + * \param use_seed Boolean, whether to use the positions in \p res as + * a starting configuration. See also \p res above. + * \return Error code. + * + * Time complexity: O(n (|V|^2+|E|) ), n is the number of iterations, + * |V| is the number of vertices, |E| the number + * of edges. If \p node_charge is zero then it is only O(n|E|). + */ +igraph_error_t igraph_layout_graphopt(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t niter, + igraph_real_t node_charge, igraph_real_t node_mass, + igraph_real_t spring_length, + igraph_real_t spring_constant, + igraph_real_t max_sa_movement, + igraph_bool_t use_seed) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_t pending_forces_x, pending_forces_y; + /* Set a flag to calculate (or not) the electrical forces that the nodes */ + /* apply on each other based on if both node types' charges are zero. */ + igraph_bool_t apply_electric_charges = (node_charge != 0); + + igraph_int_t this_node, other_node, edge; + igraph_real_t distance; + igraph_int_t i; + + IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_x, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_y, no_of_nodes); + + if (use_seed) { + if (igraph_matrix_nrow(res) != no_of_nodes || + igraph_matrix_ncol(res) != 2) { + IGRAPH_WARNING("Invalid size for initial matrix, starting from random layout."); + IGRAPH_CHECK(igraph_layout_random(graph, res)); + } + } else { + IGRAPH_CHECK(igraph_layout_random(graph, res)); + } + + IGRAPH_PROGRESS("Graphopt layout", 0, NULL); + for (i = niter; i > 0; i--) { + /* Report progress in approx. every 100th step */ + if (i % 10 == 0) { + IGRAPH_PROGRESS("Graphopt layout", 100.0 - 100.0 * i / niter, NULL); + } + + /* Clear pending forces on all nodes */ + igraph_vector_null(&pending_forces_x); + igraph_vector_null(&pending_forces_y); + + // Apply electrical force applied by all other nodes + if (apply_electric_charges) { + // Iterate through all nodes + for (this_node = 0; this_node < no_of_nodes; this_node++) { + IGRAPH_ALLOW_INTERRUPTION(); + for (other_node = this_node + 1; + other_node < no_of_nodes; + other_node++) { + distance = igraph_i_distance_between(res, this_node, other_node); + // let's protect ourselves from division by zero by ignoring + // two nodes that happen to be in the same place. Since we + // separate all nodes before we work on any of them, this + // will only happen in extremely rare circumstances, and when + // it does, springs will probably pull them apart anyway. + // also, if we are more than 50 away, the electric force + // will be negligible. + // ***** may not always be desirable **** + if ((distance != 0.0) && (distance < 500.0)) { + // if (distance != 0.0) { + // Apply electrical force from node(counter2) on + // node(counter) + igraph_i_apply_electrical_force(res, &pending_forces_x, + &pending_forces_y, + other_node, this_node, + node_charge, + distance); + } + } + } + } + + // Apply force from springs + for (edge = 0; edge < no_of_edges; edge++) { + igraph_int_t tthis_node = IGRAPH_FROM(graph, edge); + igraph_int_t oother_node = IGRAPH_TO(graph, edge); + // Apply spring force on both nodes + igraph_i_apply_spring_force(res, &pending_forces_x, &pending_forces_y, + oother_node, tthis_node, spring_length, + spring_constant); + } + + // Effect the movement of the nodes based on all pending forces + igraph_i_move_nodes(res, &pending_forces_x, &pending_forces_y, node_mass, + max_sa_movement); + } + IGRAPH_PROGRESS("Graphopt layout", 100, NULL); + + igraph_vector_destroy(&pending_forces_y); + igraph_vector_destroy(&pending_forces_x); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/kamada_kawai.c b/src/layout/kamada_kawai.c new file mode 100644 index 0000000..d7b7085 --- /dev/null +++ b/src/layout/kamada_kawai.c @@ -0,0 +1,702 @@ +/* + igraph library. + Copyright (C) 2003-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_paths.h" + +#include "core/interruption.h" +#include "layout/layout_internal.h" + +/* Energy gradient values below this threshold are considered to be zero, + * for the 2D and 3D cases, respectively. */ +#define KK_EPS 1e-13 +#define KK3D_EPS 1e-8 + +/** + * \ingroup layout + * \function igraph_layout_kamada_kawai + * \brief Places the vertices on a plane according to the Kamada-Kawai algorithm. + * + * This is a force-directed layout. A spring is inserted between all pairs + * of vertices, both those which are directly connected and those that are not. + * The unstretched length of springs is chosen based on the undirected graph distance + * between the corresponding pair of vertices. Thus, in a weighted graph, increasing + * the weight between two vertices pushes them apart. The Young modulus of springs + * is inversely proportional to the graph distance, ensuring that springs between + * far-apart veritces will have a smaller effect on the layout. + * + * + * Disconnected graphs are handled by assuming that the graph distance between + * vertices in different components is the same as the graph diameter. + * + * + * This layout works particularly well for locally connected spatial networks + * such as lattices. + * + * + * This layout algorithm is not suitable for large graphs. The memory + * requirements are of the order O(|V|^2). + * + * + * Reference: + * + * + * Kamada, T. and Kawai, S.: + * An Algorithm for Drawing General Undirected Graphs. + * Information Processing Letters, 31/1, 7--15, 1989. + * https://doi.org/10.1016/0020-0190(89)90102-6 + * + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result (x-positions in column zero and + * y-positions in column one) and will be resized if needed. + * \param use_seed Boolean, whether to use the values supplied in the + * \p res argument as the initial configuration. If zero and there + * are any limits on the X or Y coordinates, then a random initial + * configuration is used. Otherwise the vertices are placed on a + * circle of radius 1 as the initial configuration. + * \param maxiter The maximum number of iterations to perform. A reasonable + * default value is at least ten (or more) times the number of + * vertices. + * \param epsilon Stop the iteration, if the maximum delta value of the + * algorithm is smaller than this. It is safe to leave it at zero, + * and then \p maxiter iterations are performed. + * \param kkconst The Kamada-Kawai vertex attraction constant. + * Typical value: number of vertices. + * \param weights A vector of edge weights. Weights are interpreted as edge + * \em lengths in the shortest path calculation used by the + * Kamada-Kawai algorithm. Therefore, vertices connected by high-weight + * edges will be placed further apart. Pass \c NULL to assume unit weights + * for all edges. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|) for each iteration, after an O(|V|^2 + * log|V|) initialization step. |V| is the number of vertices in the + * graph. + */ + +igraph_error_t igraph_layout_kamada_kawai(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy) { + + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + igraph_real_t L, L0 = sqrt(vcount); + igraph_matrix_t dij, lij, kij; + igraph_real_t max_dij; + igraph_vector_t D1, D2; + igraph_int_t m; + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Kamada-Kawai layout.", IGRAPH_EINVAL); + } + if (kkconst <= 0) { + IGRAPH_ERROR("`K' constant must be positive in Kamada-Kawai layout.", + IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != vcount || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Kamada-Kawai layout.", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + if (weights && ecount > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for Kamada-Kawai layout.", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != vcount) { + IGRAPH_ERROR("Invalid minx vector length.", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != vcount) { + IGRAPH_ERROR("Invalid maxx vector length.", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx.", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != vcount) { + IGRAPH_ERROR("Invalid miny vector length.", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != vcount) { + IGRAPH_ERROR("Invalid maxy vector length.", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy.", IGRAPH_EINVAL); + } + + if (!use_seed) { + if (minx || maxx || miny || maxy) { + igraph_i_layout_random_bounded(graph, res, minx, maxx, miny, maxy); + } else { + igraph_layout_circle(graph, res, /* order= */ igraph_vss_all()); + /* The original paper recommends using a radius of 0.5*L0 here. + * The coefficient of 0.36 was chosen empirically so that this initial + * layout would be as close as possible to the equilibrium layout + * when the graph is a cycle graph. */ + igraph_matrix_scale(res, 0.36 * L0); + } + } + + if (vcount <= 1) { + return IGRAPH_SUCCESS; + } + + IGRAPH_MATRIX_INIT_FINALLY(&dij, vcount, vcount); + IGRAPH_MATRIX_INIT_FINALLY(&kij, vcount, vcount); + IGRAPH_MATRIX_INIT_FINALLY(&lij, vcount, vcount); + + IGRAPH_CHECK(igraph_distances_dijkstra(graph, &dij, igraph_vss_all(), + igraph_vss_all(), weights, IGRAPH_ALL)); + + /* Find largest finite distance */ + max_dij = 0.0; + for (igraph_int_t j = 0; j < vcount; j++) { + for (igraph_int_t i = j + 1; i < vcount; i++) { + if (!isfinite(MATRIX(dij, i, j))) { + continue; + } + if (MATRIX(dij, i, j) > max_dij) { + max_dij = MATRIX(dij, i, j); + } + } + } + + /* Replace infinite distances by the largest finite distance, + * effectively making the graph connected. */ + for (igraph_int_t j = 0; j < vcount; j++) { + for (igraph_int_t i = 0; i < vcount; i++) { + if (MATRIX(dij, i, j) > max_dij) { + MATRIX(dij, i, j) = max_dij; + } + } + } + + L = L0 / max_dij; + for (igraph_int_t j = 0; j < vcount; j++) { + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == j) { + continue; + } + igraph_real_t tmp = MATRIX(dij, i, j) * MATRIX(dij, i, j); + MATRIX(kij, i, j) = kkconst / tmp; + MATRIX(lij, i, j) = L * MATRIX(dij, i, j); + } + } + + /* Initialize delta */ + IGRAPH_VECTOR_INIT_FINALLY(&D1, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&D2, vcount); + for (igraph_int_t i = 0; i < vcount; i++) { + for (m = 0; m < vcount; m++) { + igraph_real_t dx, dy, mi_dist; + if (i == m) { + continue; + } + dx = MATRIX(*res, m, 0) - MATRIX(*res, i, 0); + dy = MATRIX(*res, m, 1) - MATRIX(*res, i, 1); + mi_dist = sqrt(dx*dx + dy*dy); + VECTOR(D1)[m] += MATRIX(kij, m, i) * (dx - MATRIX(lij, m, i) * dx / mi_dist); + VECTOR(D2)[m] += MATRIX(kij, m, i) * (dy - MATRIX(lij, m, i) * dy / mi_dist); + } + } + + for (igraph_int_t j = 0; j < maxiter; j++) { + igraph_real_t myD1, myD2, A, B, C; + igraph_real_t max_delta, delta_x, delta_y; + igraph_real_t old_x, old_y, new_x, new_y; + + IGRAPH_ALLOW_INTERRUPTION(); + + myD1 = 0.0, myD2 = 0.0, A = 0.0, B = 0.0, C = 0.0; + + /* Select maximal delta */ + m = 0; max_delta = -1; + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t delta = (VECTOR(D1)[i] * VECTOR(D1)[i] + + VECTOR(D2)[i] * VECTOR(D2)[i]); + if (delta > max_delta) { + m = i; max_delta = delta; + } + } + if (max_delta < epsilon) { + break; + } + old_x = MATRIX(*res, m, 0); + old_y = MATRIX(*res, m, 1); + + /* Calculate D1 and D2, A, B, C */ + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t dx, dy, dist, den; + if (i == m) { + continue; + } + dx = old_x - MATRIX(*res, i, 0); + dy = old_y - MATRIX(*res, i, 1); + dist = sqrt(dx*dx + dy*dy); + den = dist * (dx * dx + dy * dy); + A += MATRIX(kij, m, i) * (1 - MATRIX(lij, m, i) * dy * dy / den); + B += MATRIX(kij, m, i) * MATRIX(lij, m, i) * dx * dy / den; + C += MATRIX(kij, m, i) * (1 - MATRIX(lij, m, i) * dx * dx / den); + } + myD1 = VECTOR(D1)[m]; + myD2 = VECTOR(D2)[m]; + + /* We need to solve the following linear equations, corresponding to + * eqs. (11) and (12) in the paper. + * + * A * delta_x + B * delta_y == myD1 + * B * delta_x + C * delta_y == myD2 + * + * We special-case the equilibrium case, i.e. when the energy gradient + * is zero and no displacement is necessary. This is important for the + * case of path graphs, where the determinant of the LHS will be + * zero in equilibrium, causing numerical problems. + */ + if (myD1*myD1 + myD2*myD2 < KK_EPS*KK_EPS) { + delta_x = 0; + delta_y = 0; + } else { + igraph_real_t det = C * A - B * B; + delta_y = (B * myD1 - A * myD2) / det; + delta_x = (B * myD2 - C * myD1) / det; + } + + new_x = old_x + delta_x; + new_y = old_y + delta_y; + + /* Limits, if given */ + if (minx && new_x < VECTOR(*minx)[m]) { + new_x = VECTOR(*minx)[m]; + } + if (maxx && new_x > VECTOR(*maxx)[m]) { + new_x = VECTOR(*maxx)[m]; + } + if (miny && new_y < VECTOR(*miny)[m]) { + new_y = VECTOR(*miny)[m]; + } + if (maxy && new_y > VECTOR(*maxy)[m]) { + new_y = VECTOR(*maxy)[m]; + } + + /* Update delta, only with/for the affected node */ + VECTOR(D1)[m] = VECTOR(D2)[m] = 0.0; + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t old_dx, old_dy, new_dx, new_dy, new_mi_dist, old_mi_dist; + if (i == m) { + continue; + } + old_dx = old_x - MATRIX(*res, i, 0); + old_dy = old_y - MATRIX(*res, i, 1); + old_mi_dist = sqrt(old_dx*old_dx + old_dy*old_dy); + new_dx = new_x - MATRIX(*res, i, 0); + new_dy = new_y - MATRIX(*res, i, 1); + new_mi_dist = sqrt(new_dx*new_dx + new_dy*new_dy); + + VECTOR(D1)[i] -= MATRIX(kij, m, i) * + (-old_dx + MATRIX(lij, m, i) * old_dx / old_mi_dist); + VECTOR(D2)[i] -= MATRIX(kij, m, i) * + (-old_dy + MATRIX(lij, m, i) * old_dy / old_mi_dist); + VECTOR(D1)[i] += MATRIX(kij, m, i) * + (-new_dx + MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[i] += MATRIX(kij, m, i) * + (-new_dy + MATRIX(lij, m, i) * new_dy / new_mi_dist); + + VECTOR(D1)[m] += MATRIX(kij, m, i) * + (new_dx - MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[m] += MATRIX(kij, m, i) * + (new_dy - MATRIX(lij, m, i) * new_dy / new_mi_dist); + } + + /* Update coordinates*/ + MATRIX(*res, m, 0) = new_x; + MATRIX(*res, m, 1) = new_y; + } + + igraph_vector_destroy(&D2); + igraph_vector_destroy(&D1); + igraph_matrix_destroy(&lij); + igraph_matrix_destroy(&kij); + igraph_matrix_destroy(&dij); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup layout + * \function igraph_layout_kamada_kawai_3d + * \brief 3D version of the Kamada-Kawai layout generator. + * + * This is the 3D version of \ref igraph_layout_kamada_kawai(). + * See the documentation of that function for more information. + * + * + * This layout algorithm is not suitable for large graphs. The memory + * requirements are of the order O(|V|^2). + * + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result (x-, y- and z-positions in columns one + * through three) and will be resized if needed. + * \param use_seed Boolean, whether to use the values supplied in the + * \p res argument as the initial configuration. If zero and there + * are any limits on the z, y or z coordinates, then a random initial + * configuration is used. Otherwise the vertices are placed uniformly + * on a sphere of radius 1 as the initial configuration. + * \param maxiter The maximum number of iterations to perform. A reasonable + * default value is at least ten (or more) times the number of + * vertices. + * \param epsilon Stop the iteration, if the maximum delta value of the + * algorithm is smaller than this. It is safe to leave it at zero, + * and then \p maxiter iterations are performed. + * \param kkconst The Kamada-Kawai vertex attraction constant. + * Typical value: number of vertices. + * \param weights A vector of edge weights. Weights are interpreted as edge + * \em lengths in the shortest path calculation used by the + * Kamada-Kawai algorithm. Therefore, vertices connected by high-weight + * edges will be placed further apart. Pass \c NULL to assume unit weights + * for all edges. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \param minz Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote z \endquote coordinate for every vertex. + * \param maxz Same as \p minz, but the maximum \quote z \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|) for each iteration, after an O(|V|^2 + * log|V|) initialization step. |V| is the number of vertices in the + * graph. + */ + +igraph_error_t igraph_layout_kamada_kawai_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_int_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_real_t L, L0 = sqrt(vcount); + igraph_matrix_t dij, lij, kij; + igraph_real_t max_dij; + igraph_vector_t D1, D2, D3; + igraph_int_t m; + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "3D Kamada-Kawai layout.", IGRAPH_EINVAL); + } + if (kkconst <= 0) { + IGRAPH_ERROR("`K' constant must be positive in Kamada-Kawai layout.", + IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != vcount || + igraph_matrix_ncol(res) != 3)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "3D Kamada-Kawai layout.", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + if (weights && ecount > 0 && igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("Weights must be positive for 3D Kamada-Kawai layout.", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != vcount) { + IGRAPH_ERROR("Invalid minx vector length.", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != vcount) { + IGRAPH_ERROR("Invalid maxx vector length.", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx.", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != vcount) { + IGRAPH_ERROR("Invalid miny vector length.", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != vcount) { + IGRAPH_ERROR("Invalid maxy vector length.", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy.", IGRAPH_EINVAL); + } + if (minz && igraph_vector_size(minz) != vcount) { + IGRAPH_ERROR("Invalid minz vector length.", IGRAPH_EINVAL); + } + if (maxz && igraph_vector_size(maxz) != vcount) { + IGRAPH_ERROR("Invalid maxz vector length.", IGRAPH_EINVAL); + } + if (minz && maxz && !igraph_vector_all_le(minz, maxz)) { + IGRAPH_ERROR("minz must not be greater than maxz.", IGRAPH_EINVAL); + } + + if (!use_seed) { + if (minx || maxx || miny || maxy || minz || maxz) { + igraph_i_layout_random_bounded_3d(graph, res, minx, maxx, miny, maxy, minz, maxz); + } else { + igraph_layout_sphere(graph, res); + /* The coefficient of 0.36 was chosen empirically so that this initial layout + * would be as close as possible to the equilibrium layout when the graph is + * a Goldberg polyhedron, i.e. having a naturally spherical layout. */ + igraph_matrix_scale(res, 0.36*L0); + } + } + + if (vcount <= 1) { + return IGRAPH_SUCCESS; + } + + IGRAPH_MATRIX_INIT_FINALLY(&dij, vcount, vcount); + IGRAPH_MATRIX_INIT_FINALLY(&kij, vcount, vcount); + IGRAPH_MATRIX_INIT_FINALLY(&lij, vcount, vcount); + IGRAPH_CHECK(igraph_distances_dijkstra(graph, &dij, igraph_vss_all(), + igraph_vss_all(), weights, IGRAPH_ALL)); + + max_dij = 0.0; + for (igraph_int_t i = 0; i < vcount; i++) { + for (igraph_int_t j = i + 1; j < vcount; j++) { + if (!isfinite(MATRIX(dij, i, j))) { + continue; + } + if (MATRIX(dij, i, j) > max_dij) { + max_dij = MATRIX(dij, i, j); + } + } + } + for (igraph_int_t i = 0; i < vcount; i++) { + for (igraph_int_t j = 0; j < vcount; j++) { + if (MATRIX(dij, i, j) > max_dij) { + MATRIX(dij, i, j) = max_dij; + } + } + } + + L = L0 / max_dij; + for (igraph_int_t i = 0; i < vcount; i++) { + for (igraph_int_t j = 0; j < vcount; j++) { + igraph_real_t tmp = MATRIX(dij, i, j) * MATRIX(dij, i, j); + if (i == j) { + continue; + } + MATRIX(kij, i, j) = kkconst / tmp; + MATRIX(lij, i, j) = L * MATRIX(dij, i, j); + } + } + + /* Initialize delta */ + IGRAPH_VECTOR_INIT_FINALLY(&D1, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&D2, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&D3, vcount); + for (m = 0; m < vcount; m++) { + igraph_real_t dx, dy, dz, mi_dist; + igraph_real_t myD1 = 0.0, myD2 = 0.0, myD3 = 0.0; + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == m) { + continue; + } + dx = MATRIX(*res, m, 0) - MATRIX(*res, i, 0); + dy = MATRIX(*res, m, 1) - MATRIX(*res, i, 1); + dz = MATRIX(*res, m, 2) - MATRIX(*res, i, 2); + mi_dist = sqrt(dx * dx + dy * dy + dz * dz); + myD1 += MATRIX(kij, m, i) * (dx - MATRIX(lij, m, i) * dx / mi_dist); + myD2 += MATRIX(kij, m, i) * (dy - MATRIX(lij, m, i) * dy / mi_dist); + myD3 += MATRIX(kij, m, i) * (dz - MATRIX(lij, m, i) * dz / mi_dist); + } + VECTOR(D1)[m] = myD1; + VECTOR(D2)[m] = myD2; + VECTOR(D3)[m] = myD3; + } + + for (igraph_int_t j = 0; j < maxiter; j++) { + + igraph_real_t Ax = 0.0, Ay = 0.0, Az = 0.0; + igraph_real_t Axx = 0.0, Axy = 0.0, Axz = 0.0, Ayy = 0.0, Ayz = 0.0, Azz = 0.0; + igraph_real_t max_delta, delta_x, delta_y, delta_z; + igraph_real_t old_x, old_y, old_z, new_x, new_y, new_z; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Select maximal delta */ + m = 0; max_delta = -1; + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t delta = (VECTOR(D1)[i] * VECTOR(D1)[i] + + VECTOR(D2)[i] * VECTOR(D2)[i] + + VECTOR(D3)[i] * VECTOR(D3)[i]); + if (delta > max_delta) { + m = i; max_delta = delta; + } + } + if (max_delta < epsilon) { + break; + } + old_x = MATRIX(*res, m, 0); + old_y = MATRIX(*res, m, 1); + old_z = MATRIX(*res, m, 2); + + /* Calculate D1, D2 and D3, and other coefficients */ + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t dx, dy, dz, dist, den, k_mi, l_mi; + if (i == m) { + continue; + } + dx = old_x - MATRIX(*res, i, 0); + dy = old_y - MATRIX(*res, i, 1); + dz = old_z - MATRIX(*res, i, 2); + dist = sqrt(dx * dx + dy * dy + dz * dz); + den = dist * (dx * dx + dy * dy + dz * dz); + + k_mi = MATRIX(kij, m, i); + l_mi = MATRIX(lij, m, i); + Axx += k_mi * (1 - l_mi * (dy * dy + dz * dz) / den); + Ayy += k_mi * (1 - l_mi * (dx * dx + dz * dz) / den); + Azz += k_mi * (1 - l_mi * (dx * dx + dy * dy) / den); + Axy += k_mi * l_mi * dx * dy / den; + Axz += k_mi * l_mi * dx * dz / den; + Ayz += k_mi * l_mi * dy * dz / den; + } + Ax = -VECTOR(D1)[m]; + Ay = -VECTOR(D2)[m]; + Az = -VECTOR(D3)[m]; + + /* Need to solve some linear equations, we just use Cramer's rule */ +#define DET(a,b,c,d,e,f,g,h,i) ((a*e*i+b*f*g+c*d*h)-(c*e*g+b*d*i+a*f*h)) + + /* See comments in 2D version for the reason for this check. + * In the 3D case, a different threshold is needed (KK3D_EPS vs KK_EPS). + * See https://github.com/igraph/igraph/issues/2782 */ + if (Ax*Ax + Ay*Ay + Az*Az < KK3D_EPS*KK3D_EPS) { + delta_x = delta_y = delta_z = 0; + } else { + igraph_real_t detnum; + detnum = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Axz, Ayz, Azz); + delta_x = DET(Ax, Ay, Az, Axy, Ayy, Ayz, Axz, Ayz, Azz) / detnum; + delta_y = DET(Axx, Axy, Axz, Ax, Ay, Az, Axz, Ayz, Azz) / detnum; + delta_z = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Ax, Ay, Az ) / detnum; + } + + new_x = old_x + delta_x; + new_y = old_y + delta_y; + new_z = old_z + delta_z; + + /* Limits, if given */ + if (minx && new_x < VECTOR(*minx)[m]) { + new_x = VECTOR(*minx)[m]; + } + if (maxx && new_x > VECTOR(*maxx)[m]) { + new_x = VECTOR(*maxx)[m]; + } + if (miny && new_y < VECTOR(*miny)[m]) { + new_y = VECTOR(*miny)[m]; + } + if (maxy && new_y > VECTOR(*maxy)[m]) { + new_y = VECTOR(*maxy)[m]; + } + if (minz && new_z < VECTOR(*minz)[m]) { + new_z = VECTOR(*minz)[m]; + } + if (maxz && new_z > VECTOR(*maxz)[m]) { + new_z = VECTOR(*maxz)[m]; + } + + /* Update delta, only with/for the affected node */ + VECTOR(D1)[m] = VECTOR(D2)[m] = VECTOR(D3)[m] = 0.0; + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_real_t old_dx, old_dy, old_dz, old_mi_dist, new_dx, new_dy, new_dz, new_mi_dist; + if (i == m) { + continue; + } + old_dx = old_x - MATRIX(*res, i, 0); + old_dy = old_y - MATRIX(*res, i, 1); + old_dz = old_z - MATRIX(*res, i, 2); + old_mi_dist = sqrt(old_dx * old_dx + old_dy * old_dy + + old_dz * old_dz); + new_dx = new_x - MATRIX(*res, i, 0); + new_dy = new_y - MATRIX(*res, i, 1); + new_dz = new_z - MATRIX(*res, i, 2); + new_mi_dist = sqrt(new_dx * new_dx + new_dy * new_dy + + new_dz * new_dz); + + VECTOR(D1)[i] -= MATRIX(kij, m, i) * + (-old_dx + MATRIX(lij, m, i) * old_dx / old_mi_dist); + VECTOR(D2)[i] -= MATRIX(kij, m, i) * + (-old_dy + MATRIX(lij, m, i) * old_dy / old_mi_dist); + VECTOR(D3)[i] -= MATRIX(kij, m, i) * + (-old_dz + MATRIX(lij, m, i) * old_dz / old_mi_dist); + + VECTOR(D1)[i] += MATRIX(kij, m, i) * + (-new_dx + MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[i] += MATRIX(kij, m, i) * + (-new_dy + MATRIX(lij, m, i) * new_dy / new_mi_dist); + VECTOR(D3)[i] += MATRIX(kij, m, i) * + (-new_dz + MATRIX(lij, m, i) * new_dz / new_mi_dist); + + VECTOR(D1)[m] += MATRIX(kij, m, i) * + (new_dx - MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[m] += MATRIX(kij, m, i) * + (new_dy - MATRIX(lij, m, i) * new_dy / new_mi_dist); + VECTOR(D3)[m] += MATRIX(kij, m, i) * + (new_dz - MATRIX(lij, m, i) * new_dz / new_mi_dist); + } + + /* Update coordinates*/ + MATRIX(*res, m, 0) = new_x; + MATRIX(*res, m, 1) = new_y; + MATRIX(*res, m, 2) = new_z; + } + + igraph_vector_destroy(&D3); + igraph_vector_destroy(&D2); + igraph_vector_destroy(&D1); + igraph_matrix_destroy(&lij); + igraph_matrix_destroy(&kij); + igraph_matrix_destroy(&dij); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/large_graph.c b/src/layout/large_graph.c new file mode 100644 index 0000000..27ae2ee --- /dev/null +++ b/src/layout/large_graph.c @@ -0,0 +1,389 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_random.h" +#include "igraph_visitor.h" + +#include "core/grid.h" +#include "core/interruption.h" +#include "core/math.h" /* M_PI */ + +static void igraph_i_norm2d(igraph_real_t *x, igraph_real_t *y) { + igraph_real_t len = sqrt(*x * *x + *y * *y); + if (len != 0) { + *x /= len; + *y /= len; + } +} + +/** + * \function igraph_layout_lgl + * \brief Force based layout algorithm for large graphs. + * + * + * This is a layout generator similar to the Large Graph Layout + * algorithm and program (http://lgl.sourceforge.net/). But unlike LGL, this + * version uses a Fruchterman-Reingold style simulated annealing + * algorithm for placing the vertices. The speedup is achieved by + * placing the vertices on a grid and calculating the repulsion only + * for vertices which are closer to each other than a limit. + * + * \param graph The (initialized) graph object to place. It must be connnected; + * disconnected graphs are not handled by the algorithm. + * \param res Pointer to an initialized matrix object to hold the + * result. It will be resized if needed. + * \param maxit The maximum number of cooling iterations to perform + * for each layout step. A reasonable default is 150. + * \param maxdelta The maximum length of the move allowed for a vertex + * in a single iteration. A reasonable default is the number of + * vertices. + * \param area This parameter gives the area of the square on which + * the vertices will be placed. A reasonable default value is the + * number of vertices squared. + * \param coolexp The cooling exponent. A reasonable default value is + * 1.5. + * \param repulserad Determines the radius at which vertex-vertex + * repulsion cancels out attraction of adjacent vertices. A + * reasonable default value is \p area times the number of vertices. + * \param cellsize The size of the grid cells, one side of the + * square. A reasonable default value is the fourth root of + * \p area (or the square root of the number of vertices if \p area + * is also left at its default value). + * \param proot The root vertex, this is placed first, its neighbors + * in the first iteration, second neighbors in the second, etc. If + * negative then a random vertex is chosen. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: ideally O(dia*maxit*(|V|+|E|)), |V| is the number + * of vertices, + * dia is the diameter of the graph, worst case complexity is still + * O(dia*maxit*(|V|^2+|E|)), this is the case when all vertices happen to be + * in the same grid cell. + */ + +igraph_error_t igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t maxit, igraph_real_t maxdelta, + igraph_real_t area, igraph_real_t coolexp, + igraph_real_t repulserad, igraph_real_t cellsize, + igraph_int_t proot) { + + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t root; + igraph_int_t no_of_layers, actlayer = 0; + igraph_vector_int_t vids; + igraph_vector_int_t layers; + igraph_vector_int_t parents; + igraph_vector_int_t edges; + igraph_2dgrid_t grid; + igraph_vector_int_t eids; + igraph_vector_t forcex; + igraph_vector_t forcey; + + igraph_real_t frk = sqrt(area / no_of_nodes); + igraph_real_t H_n = 0; + + if (no_of_nodes == 0) { + /* We skip parameter checks for the null graph, as following the recommendations + * for parameter choices in the documentation would lead to zero values that are + * considered invalid in general, but don't cause problems for the null graph. */ + IGRAPH_CHECK(igraph_matrix_resize(res, 0, 2)); + return IGRAPH_SUCCESS; + } + + /* TODO: is zero okay? */ + if (maxit < 0) { + IGRAPH_ERRORF("Maximum number of iterations must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, maxit); + } + + if (maxdelta <= 0) { + IGRAPH_ERRORF("Maximum delta must be positive, got %g.", IGRAPH_EINVAL, maxdelta); + } + + if (area <= 0) { + IGRAPH_ERRORF("Placement area size must be positive, got %g.", IGRAPH_EINVAL, area); + } + + if (coolexp <= 0) { + IGRAPH_ERRORF("Cooling exponent must be positive, got %g.", IGRAPH_EINVAL, coolexp); + } + + if (repulserad <= 0) { + IGRAPH_ERRORF("Repulsion cutoff radius must be positive, got %g.", IGRAPH_EINVAL, repulserad); + } + + if (cellsize <= 0) { + IGRAPH_ERRORF("Cell size must be positive, got %g.", IGRAPH_EINVAL, cellsize); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + /* Note: The LGL paper describes an algorithm that uses weights, and + * determines the layers by traversing the minimum spanning tree (MST) + * of the weighted graph starting from a chosen root. This function + * does not currently use weights, so all spanning trees are of the + * same weight. Therefore, we currently use a BFS traversal of the + * original graph from the root. + * + * TODO: If this function is updated to handle weights, it should + * construct the MST and traverse that instead. */ + + /* Determine the root vertex, random pick right now */ + if (proot < 0) { + root = RNG_INTEGER(0, no_of_nodes - 1); + } else { + root = proot; + } + + /* Assign the layers */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&layers, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&parents, 0); + if (no_of_nodes > 0) { + IGRAPH_CHECK(igraph_bfs_simple(graph, root, IGRAPH_ALL, &vids, &layers, &parents)); + } + no_of_layers = igraph_vector_int_size(&layers) - 1; + + /* Check whether we have reached all the nodes -- if not, the graph is + * disconnected */ + if (no_of_nodes > 0 && igraph_vector_int_min(&parents) <= -2) { + IGRAPH_WARNING("LGL layout does not support disconnected graphs yet."); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&forcex, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&forcey, no_of_nodes); + + /* Place the vertices randomly */ + IGRAPH_CHECK(igraph_layout_random(graph, res)); + igraph_matrix_scale(res, sqrt(area / M_PI)); + + /* This is the grid for calculating the vertices near to a given vertex */ + IGRAPH_CHECK(igraph_2dgrid_init(&grid, res, + -sqrt(area / M_PI), sqrt(area / M_PI), cellsize, + -sqrt(area / M_PI), sqrt(area / M_PI), cellsize)); + IGRAPH_FINALLY(igraph_2dgrid_destroy, &grid); + + /* Place the root vertex */ + igraph_2dgrid_add(&grid, root, 0, 0); + + for (actlayer = 1; actlayer < no_of_layers; actlayer++) { + H_n += 1.0 / actlayer; + } + + for (actlayer = 1; actlayer < no_of_layers; actlayer++) { + + igraph_real_t c = 1; + igraph_int_t i, j; + igraph_real_t massx, massy; + igraph_real_t px, py; + igraph_real_t sx, sy; + + igraph_int_t it = 0; + igraph_real_t epsilon = 10e-6; + igraph_real_t maxchange = epsilon + 1; + /* igraph_int_t pairs; */ + igraph_real_t sconst = sqrt(area / M_PI) / H_n; + igraph_2dgrid_iterator_t vidit; + + /* printf("Layer %li:\n", actlayer); */ + + /*-----------------------------------------*/ + /* Step 1: place the next layer on spheres */ + /*-----------------------------------------*/ + + j = VECTOR(layers)[actlayer]; + for (i = VECTOR(layers)[actlayer - 1]; + i < VECTOR(layers)[actlayer]; i++) { + + igraph_int_t vid = VECTOR(vids)[i]; + igraph_int_t par = VECTOR(parents)[vid]; + + if (par < 0) { + if (par == -1) { + /* this is either the root vertex ... */ + MATRIX(*res, vid, 0) = 0; + MATRIX(*res, vid, 1) = 0; + } else { + /* ... or an unreachable node */ + } + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + igraph_2dgrid_getcenter(&grid, &massx, &massy); + igraph_i_norm2d(&massx, &massy); + px = MATRIX(*res, vid, 0) - MATRIX(*res, par, 0); + py = MATRIX(*res, vid, 1) - MATRIX(*res, par, 1); + igraph_i_norm2d(&px, &py); + sx = c * (massx + px) + MATRIX(*res, vid, 0); + sy = c * (massy + py) + MATRIX(*res, vid, 1); + + /* The neighbors of 'vid' */ + while (j < VECTOR(layers)[actlayer + 1] && VECTOR(parents)[VECTOR(vids)[j]] == vid) { + igraph_real_t rx, ry; + if (actlayer == 1) { + igraph_real_t phi = 2 * M_PI / (VECTOR(layers)[2] - 1) * (j - 1); + rx = cos(phi); + ry = sin(phi); + } else { + rx = RNG_UNIF(-1, 1); + ry = RNG_UNIF(-1, 1); + } + igraph_i_norm2d(&rx, &ry); + rx = rx / actlayer * sconst; + ry = ry / actlayer * sconst; + igraph_2dgrid_add(&grid, VECTOR(vids)[j], sx + rx, sy + ry); + j++; + } + } + + /*-----------------------------------------*/ + /* Step 2: add the edges of the next layer */ + /*-----------------------------------------*/ + + for (j = VECTOR(layers)[actlayer]; + j < VECTOR(layers)[actlayer + 1]; j++) { + igraph_int_t vid = VECTOR(vids)[j]; + igraph_int_t k; + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_incident(graph, &eids, vid, IGRAPH_ALL, IGRAPH_LOOPS)); + for (k = 0; k < igraph_vector_int_size(&eids); k++) { + igraph_int_t eid = VECTOR(eids)[k]; + igraph_int_t from = IGRAPH_FROM(graph, eid), to = IGRAPH_TO(graph, eid); + if ((from != vid && igraph_2dgrid_in(&grid, from)) || + (to != vid && igraph_2dgrid_in(&grid, to))) { + igraph_vector_int_push_back(&edges, eid); /* reserved */ + } + } + } + + /*-----------------------------------------*/ + /* Step 3: let the springs spring */ + /*-----------------------------------------*/ + + maxchange = epsilon + 1; + while (it < maxit && maxchange > epsilon) { + igraph_int_t jj; + igraph_real_t t = maxdelta * pow((maxit - it) / (igraph_real_t) maxit, coolexp); + igraph_int_t vid, nei; + + IGRAPH_PROGRESS("Large graph layout", + 100.0 * ((actlayer - 1.0) / (no_of_layers - 1.0) + (it) / (maxit * (no_of_layers - 1.0))), + 0); + + /* init */ + igraph_vector_null(&forcex); + igraph_vector_null(&forcey); + maxchange = 0; + + /* attractive "forces" along the edges */ + for (jj = 0; jj < igraph_vector_int_size(&edges); jj++) { + igraph_int_t from = IGRAPH_FROM(graph, VECTOR(edges)[jj]); + igraph_int_t to = IGRAPH_TO(graph, VECTOR(edges)[jj]); + igraph_real_t xd, yd, dist, force; + IGRAPH_ALLOW_INTERRUPTION(); + xd = MATRIX(*res, from, 0) - MATRIX(*res, to, 0); + yd = MATRIX(*res, from, 1) - MATRIX(*res, to, 1); + dist = sqrt(xd*xd + yd*yd); + if (dist != 0) { + xd /= dist; + yd /= dist; + } + force = dist * dist / frk; + VECTOR(forcex)[from] -= xd * force; + VECTOR(forcex)[to] += xd * force; + VECTOR(forcey)[from] -= yd * force; + VECTOR(forcey)[to] += yd * force; + } + + /* repulsive "forces" of the vertices nearby */ + /* pairs = 0; */ + igraph_2dgrid_reset(&grid, &vidit); + while ( (vid = igraph_2dgrid_next(&grid, &vidit) - 1) != -1) { + while ( (nei = igraph_2dgrid_next_nei(&grid, &vidit) - 1) != -1) { + igraph_real_t xd = MATRIX(*res, vid, 0) - MATRIX(*res, nei, 0); + igraph_real_t yd = MATRIX(*res, vid, 1) - MATRIX(*res, nei, 1); + igraph_real_t dist = sqrt(xd*xd + yd*yd); + igraph_real_t force; + if (dist < cellsize) { + /* pairs++; */ + if (dist == 0) { + dist = epsilon; + }; + xd /= dist; yd /= dist; + force = frk * frk * (1.0 / dist - dist * dist / repulserad); + VECTOR(forcex)[vid] += xd * force; + VECTOR(forcex)[nei] -= xd * force; + VECTOR(forcey)[vid] += yd * force; + VECTOR(forcey)[nei] -= yd * force; + } + } + } + + /* printf("verties: %li iterations: %li\n", */ + /* VECTOR(layers)[actlayer+1], pairs); */ + + /* apply the changes */ + for (jj = 0; jj < VECTOR(layers)[actlayer + 1]; jj++) { + igraph_int_t vvid = VECTOR(vids)[jj]; + igraph_real_t fx = VECTOR(forcex)[vvid]; + igraph_real_t fy = VECTOR(forcey)[vvid]; + igraph_real_t ded = sqrt(fx*fx + fy*fy); + if (ded > t) { + ded = t / ded; + fx *= ded; fy *= ded; + } + igraph_2dgrid_move(&grid, vvid, fx, fy); + if (fx > maxchange) { + maxchange = fx; + } + if (fy > maxchange) { + maxchange = fy; + } + } + it++; + /* printf("%li iterations, maxchange: %f\n", it, (double)maxchange); */ + } + } + + IGRAPH_PROGRESS("Large graph layout", 100.0, 0); + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&layers); + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&edges); + igraph_2dgrid_destroy(&grid); + igraph_vector_int_destroy(&eids); + igraph_vector_destroy(&forcex); + igraph_vector_destroy(&forcey); + IGRAPH_FINALLY_CLEAN(8); + return IGRAPH_SUCCESS; + +} diff --git a/src/layout/layout_bipartite.c b/src/layout/layout_bipartite.c new file mode 100644 index 0000000..6e92102 --- /dev/null +++ b/src/layout/layout_bipartite.c @@ -0,0 +1,78 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" + +/** + * \function igraph_layout_bipartite + * Simple layout for bipartite graphs. + * + * The layout is created by first placing the vertices in two rows, + * according to their types. Then the positions within the rows are + * optimized to minimize edge crossings, by calling \ref + * igraph_layout_sugiyama(). + * + * \param graph The input graph. + * \param types A boolean vector containing ones and zeros, the vertex + * types. Its length must match the number of vertices in the graph. + * \param res Pointer to an initialized matrix, the result, the x and + * y coordinates are stored here. + * \param hgap The preferred minimum horizontal gap between vertices + * in the same layer (i.e. vertices of the same type). + * \param vgap The distance between layers. + * \param maxiter Maximum number of iterations in the crossing + * minimization stage. 100 is a reasonable default; if you feel + * that you have too many edge crossings, increase this. + * \return Error code. + * + * \sa \ref igraph_layout_sugiyama(). + */ +igraph_error_t igraph_layout_bipartite(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, igraph_real_t hgap, + igraph_real_t vgap, igraph_int_t maxiter) { + + igraph_int_t i, no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t layers; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERRORF("The vertex type vector size (%" IGRAPH_PRId ") should be equal to the number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_bool_size(types), no_of_nodes); + } + if (hgap < 0) { + IGRAPH_ERRORF("The horizontal gap cannot be negative, got %g.", IGRAPH_EINVAL, hgap); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&layers, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(layers)[i] = VECTOR(*types)[i] ? 0 : 1; + } + + IGRAPH_CHECK(igraph_layout_sugiyama(graph, res, /* routing= */ 0, + &layers, hgap, vgap, maxiter, /* weights= */ 0)); + + igraph_vector_int_destroy(&layers); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/layout_grid.c b/src/layout/layout_grid.c new file mode 100644 index 0000000..ab35e0c --- /dev/null +++ b/src/layout/layout_grid.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" + +/** + * \ingroup layout + * \function igraph_layout_grid + * \brief Places the vertices on a regular grid on the plane. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param width The number of vertices in a single row of the grid. + * When zero or negative, the width of the grid will be the + * square root of the number of vertices, rounded up if needed. + * \return Error code. The current implementation always returns with + * success. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, igraph_int_t width) { + igraph_int_t i, no_of_nodes = igraph_vcount(graph); + igraph_real_t x, y; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + if (width <= 0) { + width = ceil(sqrt(no_of_nodes)); + } + + x = y = 0; + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = x++; + MATRIX(*res, i, 1) = y; + if (x == width) { + x = 0; y++; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup layout + * \function igraph_layout_grid_3d + * \brief Places the vertices on a regular grid in the 3D space. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param width The number of vertices in a single row of the grid. When + * zero or negative, the width is determined automatically. + * \param height The number of vertices in a single column of the grid. When + * zero or negative, the height is determined automatically. + * + * \return Error code. The current implementation always returns with + * success. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_int_t width, igraph_int_t height) { + igraph_int_t i, no_of_nodes = igraph_vcount(graph); + igraph_real_t x, y, z; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3)); + + if (width <= 0 && height <= 0) { + width = height = ceil(pow(no_of_nodes, 1.0 / 3)); + } else if (width <= 0) { + width = ceil(sqrt(no_of_nodes / (double)height)); + } else if (height <= 0) { + height = ceil(sqrt(no_of_nodes / (double)width)); + } + + x = y = z = 0; + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = x++; + MATRIX(*res, i, 1) = y; + MATRIX(*res, i, 2) = z; + if (x == width) { + x = 0; y++; + if (y == height) { + y = 0; z++; + } + } + } + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/layout_internal.h b/src/layout/layout_internal.h new file mode 100644 index 0000000..72aeaae --- /dev/null +++ b/src/layout/layout_internal.h @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_LAYOUT_INTERNAL_H +#define IGRAPH_LAYOUT_INTERNAL_H + +#include "igraph_datatype.h" +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_matrix.h" + +#include "layout/merge_grid.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid, + igraph_int_t actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r, + igraph_real_t cx, igraph_real_t cy, igraph_real_t startr, + igraph_real_t killr); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_layout_sphere_2d(igraph_matrix_t *coords, igraph_real_t *x, + igraph_real_t *y, igraph_real_t *r); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_layout_sphere_3d(igraph_matrix_t *coords, igraph_real_t *x, + igraph_real_t *y, igraph_real_t *z, + igraph_real_t *r); + +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_CONST igraph_real_t igraph_i_layout_point_segment_dist2( + igraph_real_t v_x, igraph_real_t v_y, + igraph_real_t u1_x, igraph_real_t u1_y, + igraph_real_t u2_x, igraph_real_t u2_y); + +IGRAPH_PRIVATE_EXPORT IGRAPH_FUNCATTR_CONST igraph_bool_t igraph_i_layout_segments_intersect( + igraph_real_t p0_x, igraph_real_t p0_y, + igraph_real_t p1_x, igraph_real_t p1_y, + igraph_real_t p2_x, igraph_real_t p2_y, + igraph_real_t p3_x, igraph_real_t p3_y); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_umap_fit_ab(igraph_real_t min_dist, + igraph_real_t *a_p, + igraph_real_t *b_p); + +igraph_error_t igraph_i_layout_random_bounded( + const igraph_t *graph, igraph_matrix_t *res, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy); + +igraph_error_t igraph_i_layout_random_bounded_3d( + const igraph_t *graph, igraph_matrix_t *res, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/layout/layout_random.c b/src/layout/layout_random.c new file mode 100644 index 0000000..499b103 --- /dev/null +++ b/src/layout/layout_random.c @@ -0,0 +1,272 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "layout/layout_internal.h" + +/** + * \ingroup layout + * \function igraph_layout_random + * \brief Places the vertices uniformly randomly within a square. + * + * Vertex coordinates range from -1 to 1, and are placed in two columns + * of a matrix, with a row for each vertex. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + + IGRAPH_CHECK(igraph_matrix_resize(res, vcount, 2)); + + for (igraph_int_t j = 0; j < 2; j++) { + for (igraph_int_t i = 0; i < vcount; i++) { + MATRIX(*res, i, j) = RNG_UNIF(-1, 1); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_random_3d + * \brief Places the vertices uniformly randomly in a cube. + * + * Vertex coordinates range from -1 to 1, and are placed in three columns + * of a matrix, with a row for each vertex. + * + * \param graph The graph to place. + * \param res Pointer to an initialized matrix object. It will be + * resized to hold the result. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|), the number of vertices. + */ +igraph_error_t igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + + IGRAPH_CHECK(igraph_matrix_resize(res, vcount, 3)); + + for (igraph_int_t j = 0; j < 3; j++) { + for (igraph_int_t i = 0; i < vcount; i++) { + MATRIX(*res, i, j) = RNG_UNIF(-1, 1); + } + } + + return IGRAPH_SUCCESS; +} + + +/* The following functions generate suitable initial random layouts for + * the Fruchterman-Reingold and Kamada-Kawai algorithms. */ + +igraph_error_t igraph_i_layout_random_bounded( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy) { + + const igraph_int_t no_nodes = igraph_vcount(graph); + const igraph_real_t width = sqrt(no_nodes), height = width; + + igraph_real_t dminx = -width/2, dmaxx = width/2, + dminy = -height/2, dmaxy = height/2; /* default values */ + + /* Caller should ensure that minx, etc. do not contain NaN. */ + + if (minx && !igraph_vector_empty(minx)) { + igraph_real_t m = igraph_vector_max(minx); + if (m == IGRAPH_INFINITY) { + IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m > dmaxx) { + dmaxx += m; + } + } + if (maxx && !igraph_vector_empty(maxx)) { + igraph_real_t m = igraph_vector_min(maxx); + if (m == -IGRAPH_INFINITY) { + IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m < dminx) { + dminx -= m; + } + } + if (miny && !igraph_vector_empty(miny)) { + igraph_real_t m = igraph_vector_max(miny); + if (m == IGRAPH_INFINITY) { + IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m > dmaxy) { + dmaxy += m; + } + } + if (maxy && !igraph_vector_empty(maxy)) { + igraph_real_t m = igraph_vector_min(maxy); + if (m == -IGRAPH_INFINITY) { + IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m < dminy) { + dminy -= m; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (igraph_int_t i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : dminx; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : dmaxx; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : dminy; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : dmaxy; + if (!isfinite(x1)) { + x1 = -width / 2; + } + if (!isfinite(x2)) { + x2 = width / 2; + } + if (!isfinite(y1)) { + y1 = -height / 2; + } + if (!isfinite(y2)) { + y2 = height / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_layout_random_bounded_3d( + const igraph_t *graph, igraph_matrix_t *res, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz) { + + const igraph_int_t no_nodes = igraph_vcount(graph); + const igraph_real_t width = sqrt(no_nodes), height = width, depth = width; + + igraph_real_t dminx = -width/2, dmaxx = width/2, + dminy = -height/2, dmaxy = height/2, + dminz = -depth/2, dmaxz = depth/2; /* default values */ + + /* Caller should ensure that minx, etc. do not contain NaN. */ + + if (minx && !igraph_vector_empty(minx)) { + igraph_real_t m = igraph_vector_max(minx); + if (m == IGRAPH_INFINITY) { + IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m > dmaxx) { + dmaxx += m; + } + } + if (maxx && !igraph_vector_empty(maxx)) { + igraph_real_t m = igraph_vector_min(maxx); + if (m == -IGRAPH_INFINITY) { + IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m < dminx) { + dminx -= m; + } + } + if (miny && !igraph_vector_empty(miny)) { + igraph_real_t m = igraph_vector_max(miny); + if (m == IGRAPH_INFINITY) { + IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m > dmaxy) { + dmaxy += m; + } + } + if (maxy && !igraph_vector_empty(maxy)) { + igraph_real_t m = igraph_vector_min(maxy); + if (m == -IGRAPH_INFINITY) { + IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m < dminy) { + dminy -= m; + } + } + if (minz && !igraph_vector_empty(minz)) { + igraph_real_t m = igraph_vector_max(minz); + if (m == IGRAPH_INFINITY) { + IGRAPH_ERROR("Infinite lower coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m > dmaxz) { + dmaxz += m; + } + } + if (maxz && !igraph_vector_empty(maxz)) { + igraph_real_t m = igraph_vector_min(maxz); + if (m == -IGRAPH_INFINITY) { + IGRAPH_ERROR("Negative infinite upper coordinate bound for graph layout.", IGRAPH_EINVAL); + } + if (m < dminz) { + dminz -= m; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3)); + for (igraph_int_t i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : dminx; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : dmaxx; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : dminy; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : dmaxy; + igraph_real_t z1 = minz ? VECTOR(*minz)[i] : dminz; + igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] : dmaxz; + if (!isfinite(x1)) { + x1 = -width / 2; + } + if (!isfinite(x2)) { + x2 = width / 2; + } + if (!isfinite(y1)) { + y1 = -height / 2; + } + if (!isfinite(y2)) { + y2 = height / 2; + } + if (!isfinite(z1)) { + z1 = -depth / 2; + } + if (!isfinite(z2)) { + z2 = depth / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + MATRIX(*res, i, 2) = RNG_UNIF(z1, z2); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/mds.c b/src/layout/mds.c new file mode 100644 index 0000000..5cdc87c --- /dev/null +++ b/src/layout/mds.c @@ -0,0 +1,295 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_bitset.h" +#include "igraph_blas.h" +#include "igraph_components.h" +#include "igraph_eigen.h" +#include "igraph_interface.h" +#include "igraph_operators.h" +#include "igraph_paths.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include + +static igraph_error_t igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra); + +static igraph_error_t igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res, + igraph_matrix_t *dist, igraph_int_t dim); + +static igraph_error_t igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_matrix_t* matrix = (igraph_matrix_t*)extra; + IGRAPH_UNUSED(n); + IGRAPH_CHECK(igraph_blas_dgemv_array(0, 1, matrix, from, 0, to)); + return IGRAPH_SUCCESS; +} + +/* MDS layout for a connected graph, with no error checking on the + * input parameters. The distance matrix will be modified in-place. */ +igraph_error_t igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res, + igraph_matrix_t *dist, igraph_int_t dim) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t nev = dim; + igraph_matrix_t vectors; + igraph_vector_t values, row_means; + igraph_real_t grand_mean; + igraph_int_t i, j, k; + igraph_eigen_which_t which; + + if (no_of_nodes > INT_MAX) { + IGRAPH_ERROR("Graph too large for eigenvector calculations", IGRAPH_EOVERFLOW); + } + + if (nev > INT_MAX) { + IGRAPH_ERROR("Dimensionality too large for eigenvector calculations", IGRAPH_EOVERFLOW); + } + + /* Handle the trivial cases */ + if (no_of_nodes == 1) { + IGRAPH_CHECK(igraph_matrix_resize(res, 1, dim)); + igraph_matrix_null(res); + return IGRAPH_SUCCESS; + } + if (no_of_nodes == 2) { + IGRAPH_CHECK(igraph_matrix_resize(res, 2, dim)); + igraph_matrix_null(res); + for (j = 0; j < dim; j++) { + MATRIX(*res, 1, j) = 1; + } + return IGRAPH_SUCCESS; + } + + /* Initialize some stuff */ + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_matrix_init(&vectors, no_of_nodes, dim)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vectors); + + /* Take the square of the distance matrix */ + for (j = 0; j < no_of_nodes; j++) { + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*dist, i, j) *= MATRIX(*dist, i, j); + } + } + + /* Double centering of the distance matrix */ + IGRAPH_VECTOR_INIT_FINALLY(&row_means, no_of_nodes); + igraph_vector_fill(&values, 1.0 / no_of_nodes); + IGRAPH_CHECK(igraph_blas_dgemv(0, 1, dist, &values, 0, &row_means)); + grand_mean = igraph_vector_sum(&row_means) / no_of_nodes; + igraph_matrix_add_constant(dist, grand_mean); + for (j = 0; j < no_of_nodes; j++) { + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*dist, i, j) -= VECTOR(row_means)[i] + VECTOR(row_means)[j]; + MATRIX(*dist, i, j) *= -0.5; + } + } + igraph_vector_destroy(&row_means); + IGRAPH_FINALLY_CLEAN(1); + + /* Calculate the top `dim` eigenvectors. */ + which.pos = IGRAPH_EIGEN_LA; + which.howmany = (int) nev; + IGRAPH_CHECK(igraph_eigen_matrix_symmetric(/*A=*/ 0, /*sA=*/ 0, + /*fun=*/ igraph_i_layout_mds_step, + /*n=*/ (int) no_of_nodes, /*extra=*/ dist, + /*algorithm=*/ IGRAPH_EIGEN_LAPACK, + &which, /*options=*/ 0, /*storage=*/ 0, + &values, &vectors)); + + /* Calculate and normalize the final coordinates */ + for (j = 0; j < nev; j++) { + VECTOR(values)[j] = sqrt(fabs(VECTOR(values)[j])); + } + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, dim)); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0, k = nev - 1; j < nev; j++, k--) { + MATRIX(*res, i, k) = VECTOR(values)[j] * MATRIX(vectors, i, j); + } + } + + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_mds + * \brief Place the vertices on a plane using multidimensional scaling. + * + * This layout requires a distance matrix, where the intersection of + * row i and column j specifies the desired distance between vertex i + * and vertex j. The algorithm will try to place the vertices in a + * space having a given number of dimensions in a way that approximates + * the distance relations prescribed in the distance matrix. igraph + * uses the classical multidimensional scaling by Torgerson; for more + * details, see Cox & Cox: Multidimensional Scaling (1994), Chapman + * and Hall, London. + * + * + * If the input graph is disconnected, igraph will decompose it + * first into its subgraphs, lay out the subgraphs one by one + * using the appropriate submatrices of the distance matrix, and + * then merge the layouts using \ref igraph_layout_merge_dla(). + * Since \ref igraph_layout_merge_dla() works for 2D layouts only, + * you cannot run the MDS layout on disconnected graphs for + * more than two dimensions. + * + * + * Warning: if the graph is symmetric to the exchange of two vertices + * (as is the case with leaves of a tree connecting to the same parent), + * classical multidimensional scaling may assign the same coordinates to + * these vertices. + * + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized if needed. + * \param dist The distance matrix. It must be symmetric and this + * function does not check whether the matrix is indeed + * symmetric. Results are unspecified if you pass a non-symmetric + * matrix here. You can set this parameter to null; in this + * case, the undirected shortest path lengths between vertices + * will be used as distances. + * \param dim The number of dimensions in the embedding space. For + * 2D layouts, supply 2 here. + * \return Error code. + * + * Added in version 0.6. + * + * + * Time complexity: usually around O(|V|^2 dim). + */ + +igraph_error_t igraph_layout_mds(const igraph_t *graph, igraph_matrix_t *res, + const igraph_matrix_t *dist, igraph_int_t dim) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_matrix_t m; + igraph_bool_t conn; + + /* Check the distance matrix */ + if (dist && (igraph_matrix_nrow(dist) != no_of_nodes || + igraph_matrix_ncol(dist) != no_of_nodes)) { + IGRAPH_ERROR("invalid distance matrix size", IGRAPH_EINVAL); + } + + /* Check the number of dimensions */ + if (dim <= 1) { + IGRAPH_ERROR("The dimension must be positive.", IGRAPH_EINVAL); + } + if (no_of_nodes > 0 && dim > no_of_nodes) { + IGRAPH_ERROR("The number of nodes must be at least as large as the dimension.", + IGRAPH_EINVAL); + } + + /* Copy or obtain the distance matrix */ + if (dist == 0) { + IGRAPH_MATRIX_INIT_FINALLY(&m, no_of_nodes, no_of_nodes); + IGRAPH_CHECK(igraph_distances(graph, NULL, &m, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL)); + } else { + IGRAPH_CHECK(igraph_matrix_init_copy(&m, dist)); + IGRAPH_FINALLY(igraph_matrix_destroy, &m); + /* Make sure that the diagonal contains zeroes only */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + MATRIX(m, i, i) = 0.0; + } + } + + /* Check whether the graph is connected */ + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (conn) { + /* Yes, it is, just do the MDS */ + IGRAPH_CHECK(igraph_i_layout_mds_single(graph, res, &m, dim)); + } else { + /* The graph is not connected, lay out the components one by one */ + igraph_matrix_list_t layouts; + igraph_vector_int_t vertex_order; + igraph_vector_int_t comp; + igraph_t subgraph; + igraph_matrix_t layout; + igraph_matrix_t dist_submatrix; + igraph_bitset_t seen_vertices; + igraph_int_t n, processed_vertex_count = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&comp, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_order, no_of_nodes); + + IGRAPH_MATRIX_LIST_INIT_FINALLY(&layouts, 0); + IGRAPH_MATRIX_INIT_FINALLY(&layout, 0, 0); + + IGRAPH_CHECK(igraph_matrix_init(&dist_submatrix, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &dist_submatrix); + + IGRAPH_BITSET_INIT_FINALLY(&seen_vertices, no_of_nodes); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (IGRAPH_BIT_TEST(seen_vertices, i)) { + continue; + } + + /* This is a vertex whose component we did not lay out so far */ + IGRAPH_CHECK(igraph_subcomponent(graph, &comp, i, IGRAPH_ALL)); + /* Take the subgraph */ + IGRAPH_CHECK(igraph_induced_subgraph(graph, &subgraph, igraph_vss_vector(&comp), + IGRAPH_SUBGRAPH_AUTO)); + IGRAPH_FINALLY(igraph_destroy, &subgraph); + /* Calculate the submatrix of the distances */ + IGRAPH_CHECK(igraph_matrix_select_rows_cols(&m, &dist_submatrix, &comp, &comp)); + /* Lay out the subgraph */ + IGRAPH_CHECK(igraph_i_layout_mds_single(&subgraph, &layout, &dist_submatrix, dim)); + /* Store the layout */ + IGRAPH_CHECK(igraph_matrix_list_push_back_copy(&layouts, &layout)); + /* Free the newly created subgraph */ + igraph_destroy(&subgraph); + IGRAPH_FINALLY_CLEAN(1); + /* Mark all the vertices in the component as visited */ + n = igraph_vector_int_size(&comp); + for (igraph_int_t j = 0; j < n; j++) { + IGRAPH_BIT_SET(seen_vertices, VECTOR(comp)[j]); + VECTOR(vertex_order)[VECTOR(comp)[j]] = processed_vertex_count++; + } + } + /* Merge the layouts - reusing dist_submatrix here */ + IGRAPH_CHECK(igraph_layout_merge_dla(0, &layouts, &dist_submatrix)); + /* Reordering the rows of res to match the original graph */ + IGRAPH_CHECK(igraph_matrix_select_rows(&dist_submatrix, res, &vertex_order)); + + igraph_bitset_destroy(&seen_vertices); + igraph_matrix_destroy(&dist_submatrix); + igraph_matrix_destroy(&layout); + igraph_matrix_list_destroy(&layouts); + igraph_vector_int_destroy(&vertex_order); + igraph_vector_int_destroy(&comp); + IGRAPH_FINALLY_CLEAN(6); + } + + igraph_matrix_destroy(&m); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/merge_dla.c b/src/layout/merge_dla.c new file mode 100644 index 0000000..93a0287 --- /dev/null +++ b/src/layout/merge_dla.c @@ -0,0 +1,301 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_progress.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "core/math.h" /* M_PI */ +#include "layout/merge_grid.h" +#include "layout/layout_internal.h" + +static igraph_error_t vector_order(igraph_vector_t *v) { + const igraph_int_t n = igraph_vector_size(v); + igraph_vector_int_t ind; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&ind, n); + IGRAPH_CHECK(igraph_vector_sort_ind(v, &ind, IGRAPH_DESCENDING)); + + for (igraph_int_t i=0; i < n; i++) { + VECTOR(*v)[i] = VECTOR(ind)[i]; + } + + igraph_vector_int_destroy(&ind); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_merge_dla + * \brief Merges multiple layouts by using a DLA algorithm. + * + * First each layout is covered by a circle. Then the layout of the + * largest graph is placed at the origin. Then the other layouts are + * placed by the DLA algorithm, larger ones first and smaller ones + * last. + * + * \param thegraphs Pointer vector containing the graph objects of + * which the layouts will be merged. + * \param coords List of matrices with the 2D layouts of the graphs in \p thegraphs. + * \param res Pointer to an initialized matrix object, the result will + * be stored here. It will be resized if needed. + * \return Error code. + * + * Added in version 0.2. + * + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_layout_merge_dla( + const igraph_vector_ptr_t *thegraphs, const igraph_matrix_list_t *coords, + igraph_matrix_t *res +) { + igraph_int_t coords_len = igraph_matrix_list_size(coords); + igraph_vector_t sizes; + igraph_vector_t x, y, r; + igraph_vector_t nx, ny, nr; + igraph_int_t allnodes = 0; + igraph_int_t i, j; + igraph_int_t actg; + igraph_i_layout_mergegrid_t grid; + igraph_int_t jpos = 0; + igraph_real_t minx, maxx, miny, maxy; + igraph_real_t area = 0; + igraph_real_t maxr = 0; + igraph_int_t respos; + + /* Graphs are currently not used, only the coordinates */ + IGRAPH_UNUSED(thegraphs); + + IGRAPH_VECTOR_INIT_FINALLY(&sizes, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&x, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&y, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&r, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&nx, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&ny, coords_len); + IGRAPH_VECTOR_INIT_FINALLY(&nr, coords_len); + + for (i = 0; i < coords_len; i++) { + igraph_matrix_t *mat = igraph_matrix_list_get_ptr(coords, i); + igraph_int_t size = igraph_matrix_nrow(mat); + + if (igraph_matrix_ncol(mat) != 2) { + IGRAPH_ERROR("igraph_layout_merge_dla works for 2D layouts only", + IGRAPH_EINVAL); + } + + IGRAPH_ALLOW_INTERRUPTION(); + allnodes += size; + VECTOR(sizes)[i] = size; + VECTOR(r)[i] = pow(size, .75); + area += VECTOR(r)[i] * VECTOR(r)[i]; + if (VECTOR(r)[i] > maxr) { + maxr = VECTOR(r)[i]; + } + + igraph_i_layout_sphere_2d(mat, + igraph_vector_get_ptr(&nx, i), + igraph_vector_get_ptr(&ny, i), + igraph_vector_get_ptr(&nr, i)); + } + vector_order(&sizes); /* largest first */ + + /* 0. create grid */ + minx = miny = -sqrt(5 * area); + maxx = maxy = sqrt(5 * area); + igraph_i_layout_mergegrid_init(&grid, minx, maxx, 200, + miny, maxy, 200); + IGRAPH_FINALLY(igraph_i_layout_mergegrid_destroy, &grid); + + /* fprintf(stderr, "Ok, starting DLA\n"); */ + + /* 1. place the largest */ + actg = VECTOR(sizes)[jpos++]; + igraph_i_layout_merge_place_sphere(&grid, 0, 0, VECTOR(r)[actg], actg); + + IGRAPH_PROGRESS("Merging layouts via DLA", 0.0, NULL); + while (jpos < coords_len) { + IGRAPH_ALLOW_INTERRUPTION(); + /* fprintf(stderr, "comp: %li", jpos); */ + IGRAPH_PROGRESS("Merging layouts via DLA", (100.0 * jpos) / coords_len, NULL); + + actg = VECTOR(sizes)[jpos++]; + /* 2. random walk, TODO: tune parameters */ + igraph_i_layout_merge_dla(&grid, actg, + igraph_vector_get_ptr(&x, actg), + igraph_vector_get_ptr(&y, actg), + VECTOR(r)[actg], 0, 0, + maxx, maxx + 5); + + /* 3. place sphere */ + igraph_i_layout_merge_place_sphere(&grid, VECTOR(x)[actg], VECTOR(y)[actg], + VECTOR(r)[actg], actg); + } + IGRAPH_PROGRESS("Merging layouts via DLA", 100.0, NULL); + + /* Create the result */ + IGRAPH_CHECK(igraph_matrix_resize(res, allnodes, 2)); + respos = 0; + for (i = 0; i < coords_len; i++) { + igraph_matrix_t *mat = igraph_matrix_list_get_ptr(coords, i); + igraph_int_t size = igraph_matrix_nrow(mat); + igraph_real_t xx = VECTOR(x)[i]; + igraph_real_t yy = VECTOR(y)[i]; + igraph_real_t rr = VECTOR(r)[i] / VECTOR(nr)[i]; + IGRAPH_ALLOW_INTERRUPTION(); + if (VECTOR(nr)[i] == 0) { + rr = 1; + } + for (j = 0; j < size; j++) { + MATRIX(*res, respos, 0) = rr * (MATRIX(*mat, j, 0) - VECTOR(nx)[i]); + MATRIX(*res, respos, 1) = rr * (MATRIX(*mat, j, 1) - VECTOR(ny)[i]); + MATRIX(*res, respos, 0) += xx; + MATRIX(*res, respos, 1) += yy; + ++respos; + } + } + + igraph_i_layout_mergegrid_destroy(&grid); + igraph_vector_destroy(&sizes); + igraph_vector_destroy(&x); + igraph_vector_destroy(&y); + igraph_vector_destroy(&r); + igraph_vector_destroy(&nx); + igraph_vector_destroy(&ny); + igraph_vector_destroy(&nr); + IGRAPH_FINALLY_CLEAN(8); + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_layout_sphere_2d(igraph_matrix_t *coords, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t *r) { + igraph_int_t nodes = igraph_matrix_nrow(coords); + igraph_int_t i; + igraph_real_t xmin, xmax, ymin, ymax; + + xmin = xmax = MATRIX(*coords, 0, 0); + ymin = ymax = MATRIX(*coords, 0, 1); + for (i = 1; i < nodes; i++) { + + if (MATRIX(*coords, i, 0) < xmin) { + xmin = MATRIX(*coords, i, 0); + } else if (MATRIX(*coords, i, 0) > xmax) { + xmax = MATRIX(*coords, i, 0); + } + + if (MATRIX(*coords, i, 1) < ymin) { + ymin = MATRIX(*coords, i, 1); + } else if (MATRIX(*coords, i, 1) > ymax) { + ymax = MATRIX(*coords, i, 1); + } + + } + + *x = (xmin + xmax) / 2; + *y = (ymin + ymax) / 2; + *r = sqrt((xmax - xmin)*(xmax - xmin) + (ymax - ymin)*(ymax - ymin)) / 2; + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_layout_sphere_3d(igraph_matrix_t *coords, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t *z, igraph_real_t *r) { + igraph_int_t nodes = igraph_matrix_nrow(coords); + igraph_int_t i; + igraph_real_t xmin, xmax, ymin, ymax, zmin, zmax; + + xmin = xmax = MATRIX(*coords, 0, 0); + ymin = ymax = MATRIX(*coords, 0, 1); + zmin = zmax = MATRIX(*coords, 0, 2); + for (i = 1; i < nodes; i++) { + + if (MATRIX(*coords, i, 0) < xmin) { + xmin = MATRIX(*coords, i, 0); + } else if (MATRIX(*coords, i, 0) > xmax) { + xmax = MATRIX(*coords, i, 0); + } + + if (MATRIX(*coords, i, 1) < ymin) { + ymin = MATRIX(*coords, i, 1); + } else if (MATRIX(*coords, i, 1) > ymax) { + ymax = MATRIX(*coords, i, 1); + } + + if (MATRIX(*coords, i, 2) < zmin) { + zmin = MATRIX(*coords, i, 2); + } else if (MATRIX(*coords, i, 2) > zmax) { + zmax = MATRIX(*coords, i, 2); + } + + } + + *x = (xmin + xmax) / 2; + *y = (ymin + ymax) / 2; + *z = (zmin + zmax) / 2; + *r = sqrt( (xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin) + + (zmax - zmin) * (zmax - zmin) ) / 2; + + return IGRAPH_SUCCESS; +} + +#define DIST(x,y) (sqrt(pow((x)-cx,2)+pow((y)-cy,2))) + +igraph_error_t igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid, + igraph_int_t actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r, + igraph_real_t cx, igraph_real_t cy, igraph_real_t startr, + igraph_real_t killr) { + igraph_int_t sp = -1; + igraph_real_t angle, len; + + /* The graph is not used, only its coordinates */ + IGRAPH_UNUSED(actg); + + while (sp < 0) { + /* start particle */ + do { + angle = RNG_UNIF(0, 2 * M_PI); + len = RNG_UNIF(.5 * startr, startr); + *x = cx + len * cos(angle); + *y = cy + len * sin(angle); + sp = igraph_i_layout_mergegrid_get_sphere(grid, *x, *y, r); + } while (sp >= 0); + + while (sp < 0 && DIST(*x, *y) < killr) { + igraph_real_t nx, ny; + angle = RNG_UNIF(0, 2 * M_PI); + len = RNG_UNIF(0, startr / 100); + nx = *x + len * cos(angle); + ny = *y + len * sin(angle); + sp = igraph_i_layout_mergegrid_get_sphere(grid, nx, ny, r); + if (sp < 0) { + *x = nx; *y = ny; + } + } + } + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/merge_grid.c b/src/layout/merge_grid.c new file mode 100644 index 0000000..b56f38a --- /dev/null +++ b/src/layout/merge_grid.c @@ -0,0 +1,203 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "layout/merge_grid.h" + +#include "igraph_memory.h" + + +static igraph_error_t igraph_i_layout_mergegrid_which(igraph_i_layout_mergegrid_t *grid, + igraph_real_t xc, igraph_real_t yc, + igraph_int_t *x, igraph_int_t *y) { + if (xc <= grid->minx) { + *x = 0; + } else if (xc >= grid->maxx) { + *x = grid->stepsx - 1; + } else { + *x = floor((xc - (grid->minx)) / (grid->deltax)); + } + + if (yc <= grid->miny) { + *y = 0; + } else if (yc >= grid->maxy) { + *y = grid->stepsy - 1; + } else { + *y = floor((yc - (grid->miny)) / (grid->deltay)); + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_layout_mergegrid_init(igraph_i_layout_mergegrid_t *grid, + igraph_real_t minx, igraph_real_t maxx, igraph_int_t stepsx, + igraph_real_t miny, igraph_real_t maxy, igraph_int_t stepsy) { + grid->minx = minx; + grid->maxx = maxx; + grid->stepsx = stepsx; + grid->deltax = (maxx - minx) / stepsx; + grid->miny = miny; + grid->maxy = maxy; + grid->stepsy = stepsy; + grid->deltay = (maxy - miny) / stepsy; + + grid->data = IGRAPH_CALLOC(stepsx * stepsy, igraph_int_t); + if (grid->data == 0) { + IGRAPH_ERROR("Cannot create grid", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + return IGRAPH_SUCCESS; +} + +void igraph_i_layout_mergegrid_destroy(igraph_i_layout_mergegrid_t *grid) { + IGRAPH_FREE(grid->data); +} + +#define MAT(i,j) (grid->data[(grid->stepsy)*(j)+(i)]) +#define DIST2(x2,y2) (sqrt(pow(x-(x2),2)+pow(y-(y2), 2))) + +igraph_error_t igraph_i_layout_merge_place_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r, + igraph_int_t id) { + igraph_int_t cx, cy; + igraph_int_t i, j; + + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + + MAT(cx, cy) = id + 1; + +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 0; cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 0; cy + j < grid->stepsy && DIST(i, j) < r; j++) { + MAT(cx + i, cy + j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 0; cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 1; cy - j > 0 && DIST(i, j) < r; j++) { + MAT(cx + i, cy - j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 1; cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 0; cy + j < grid->stepsy && DIST(i, j) < r; j++) { + MAT(cx - i, cy + j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 1; cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 1; cy - j > 0 && DIST(i, j) < r; j++) { + MAT(cx - i, cy - j) = id + 1; + } + } + +#undef DIST +#undef DIST2 + + return IGRAPH_SUCCESS; +} + +igraph_int_t igraph_i_layout_mergegrid_get(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y) { + igraph_int_t cx, cy; + igraph_int_t res; + + if (x <= grid->minx || x >= grid->maxx || + y <= grid->miny || y >= grid->maxy) { + res = -1; + } else { + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + res = MAT(cx, cy) - 1; + } + + return res; +} + +#define DIST2(x2,y2) (sqrt(pow(x-(x2),2)+pow(y-(y2), 2))) + +igraph_int_t igraph_i_layout_mergegrid_get_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r) { + igraph_int_t cx, cy; + igraph_int_t i, j; + igraph_int_t ret; + + if (x - r <= grid->minx || x + r >= grid->maxx || + y - r <= grid->miny || y + r >= grid->maxy) { + ret = -1; + } else { + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + + ret = MAT(cx, cy) - 1; + +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 0; ret < 0 && cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 0; ret < 0 && cy + j < grid->stepsy && DIST(i, j) < r; j++) { + ret = MAT(cx + i, cy + j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 0; ret < 0 && cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 1; ret < 0 && cy - j > 0 && DIST(i, j) < r; j++) { + ret = MAT(cx + i, cy - j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 1; ret < 0 && cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 0; ret < 0 && cy + j < grid->stepsy && DIST(i, j) < r; j++) { + ret = MAT(cx - i, cy + j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 1; ret < 0 && cx + i > 0 && DIST(i, 0) < r; i++) { + for (j = 1; ret < 0 && cy + i > 0 && DIST(i, j) < r; j++) { + ret = MAT(cx - i, cy - j) - 1; + } + } + +#undef DIST + + } + + return ret; +} diff --git a/src/layout/merge_grid.h b/src/layout/merge_grid.h new file mode 100644 index 0000000..333b473 --- /dev/null +++ b/src/layout/merge_grid.h @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2009-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_LAYOUT_MERGE_GRID_H +#define IGRAPH_LAYOUT_MERGE_GRID_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" + +IGRAPH_BEGIN_C_DECLS + +/* A type of grid used for merging layouts; each cell is owned by exactly one graph */ + +typedef struct igraph_i_layout_mergegrid_t { + igraph_int_t *data; + igraph_int_t stepsx, stepsy; + igraph_real_t minx, maxx, deltax; + igraph_real_t miny, maxy, deltay; +} igraph_i_layout_mergegrid_t; + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_layout_mergegrid_init(igraph_i_layout_mergegrid_t *grid, + igraph_real_t minx, igraph_real_t maxx, igraph_int_t stepsx, + igraph_real_t miny, igraph_real_t maxy, igraph_int_t stepsy); + +IGRAPH_PRIVATE_EXPORT void igraph_i_layout_mergegrid_destroy(igraph_i_layout_mergegrid_t *grid); + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_layout_merge_place_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r, + igraph_int_t id); + +igraph_int_t igraph_i_layout_mergegrid_get(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y); + +igraph_int_t igraph_i_layout_mergegrid_get_sphere(igraph_i_layout_mergegrid_t *g, + igraph_real_t x, igraph_real_t y, igraph_real_t r); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/layout/reingold_tilford.c b/src/layout/reingold_tilford.c new file mode 100644 index 0000000..e1e28ad --- /dev/null +++ b/src/layout/reingold_tilford.c @@ -0,0 +1,1010 @@ +/* + igraph library. + Copyright (C) 2003-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_paths.h" +#include "igraph_progress.h" +#include "igraph_structural.h" + +#include "core/math.h" /* M_PI */ + +static igraph_error_t layout_reingold_tilford_unreachable( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_int_t real_root, + igraph_int_t no_of_nodes, + igraph_vector_int_t *pnewedges) { + + igraph_int_t no_of_newedges; + igraph_vector_bool_t visited; + igraph_int_t i, j, n; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_adjlist_t allneis; + igraph_vector_int_t *neis; + + igraph_vector_int_clear(pnewedges); + + /* traverse from real_root and see what nodes you cannot reach */ + no_of_newedges = 0; + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&visited, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + /* start from real_root and go BFS */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, real_root)); + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + VECTOR(visited)[actnode] = true; + for (j = 0; j < n; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (!VECTOR(visited)[neighbor]) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + } + } + } + + for (j = 0; j < no_of_nodes; j++) { + no_of_newedges += VECTOR(visited)[j] ? 0 : 1; + } + + /* if any nodes are unreachable, add edges between them and real_root */ + if (no_of_newedges != 0) { + + IGRAPH_CHECK(igraph_vector_int_resize(pnewedges, no_of_newedges * 2)); + j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(visited)[i]) { + if (mode != IGRAPH_IN) { + VECTOR(*pnewedges)[2 * j] = real_root; + VECTOR(*pnewedges)[2 * j + 1] = i; + } else { + VECTOR(*pnewedges)[2 * j] = i; + VECTOR(*pnewedges)[2 * j + 1] = real_root; + } + j++; + } + } + } + + igraph_dqueue_int_destroy(&q); + igraph_adjlist_destroy(&allneis); + igraph_vector_bool_destroy(&visited); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Internal structure for Reingold-Tilford layout */ +struct reingold_tilford_vertex { + igraph_int_t parent; /* Parent node index */ + igraph_int_t level; /* Level of the node */ + igraph_real_t offset; /* X offset from parent node */ + igraph_int_t left_contour; /* Next left node of the contour + of the subtree rooted at this node */ + igraph_int_t right_contour; /* Next right node of the contour + of the subtree rooted at this node */ + igraph_real_t offset_to_left_contour; /* X offset when following the left contour */ + igraph_real_t offset_to_right_contour; /* X offset when following the right contour */ + igraph_int_t left_extreme; /* Leftmost node on the deepest layer of the subtree rooted at this node */ + igraph_int_t right_extreme; /* Rightmost node on the deepest layer of the subtree rooted at this node */ + igraph_real_t offset_to_left_extreme; /* X offset when jumping to the left extreme node */ + igraph_real_t offset_to_right_extreme; /* X offset when jumping to the right extreme node */ +}; + +static void layout_reingold_tilford_postorder(struct reingold_tilford_vertex *vdata, + igraph_int_t node, igraph_int_t vcount); +static void layout_reingold_tilford_calc_coords(struct reingold_tilford_vertex *vdata, + igraph_matrix_t *res, igraph_int_t node, + igraph_int_t vcount, igraph_real_t xpos); + +/* uncomment the next line for debugging the Reingold-Tilford layout */ +/* #define LAYOUT_RT_DEBUG 1 */ + +static igraph_error_t layout_reingold_tilford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + igraph_int_t root) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, n, j; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_adjlist_t allneis; + igraph_vector_int_t *neis; + struct reingold_tilford_vertex *vdata; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + vdata = IGRAPH_CALLOC(no_of_nodes, struct reingold_tilford_vertex); + if (vdata == 0) { + IGRAPH_ERROR("igraph_layout_reingold_tilford failed", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, vdata); + + for (i = 0; i < no_of_nodes; i++) { + vdata[i].parent = -1; + vdata[i].level = -1; + vdata[i].offset = 0.0; + vdata[i].left_contour = -1; + vdata[i].right_contour = -1; + vdata[i].offset_to_left_contour = 0.0; + vdata[i].offset_to_right_contour = 0.0; + vdata[i].left_extreme = i; + vdata[i].right_extreme = i; + vdata[i].offset_to_left_extreme = 0.0; + vdata[i].offset_to_right_extreme = 0.0; + } + vdata[root].parent = root; + vdata[root].level = 0; + MATRIX(*res, root, 1) = 0; + + /* Step 1: assign Y coordinates based on BFS and setup parents vector */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, root)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + + for (j = 0; j < n; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (vdata[neighbor].parent >= 0) { + continue; + } + MATRIX(*res, neighbor, 1) = actdist + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + vdata[neighbor].parent = actnode; + vdata[neighbor].level = actdist + 1; + } + } + + /* Step 2: postorder tree traversal, determines the appropriate X + * offsets for every node */ + layout_reingold_tilford_postorder(vdata, root, no_of_nodes); + + /* Step 3: calculate real coordinates based on X offsets */ + layout_reingold_tilford_calc_coords(vdata, res, root, no_of_nodes, vdata[root].offset); + + igraph_dqueue_int_destroy(&q); + igraph_adjlist_destroy(&allneis); + igraph_free(vdata); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_PROGRESS("Reingold-Tilford tree layout", 100.0, NULL); + +#ifdef LAYOUT_RT_DEBUG + for (i = 0; i < no_of_nodes; i++) { + printf( + "%3" IGRAPH_PRId ": offset = %.2f, contours = [%" IGRAPH_PRId ", %" IGRAPH_PRId "], contour offsets = [%.2f, %.2f]\n", + i, vdata[i].offset, + vdata[i].left_contour, vdata[i].right_contour, + vdata[i].offset_to_left_contour, vdata[i].offset_to_right_contour + ); + if (vdata[i].left_extreme != i || vdata[i].right_extreme != i) { + printf( + " extrema = [%" IGRAPH_PRId ", %" IGRAPH_PRId "], offsets to extrema = [%.2f, %.2f]\n", + vdata[i].left_extreme, vdata[i].right_extreme, + vdata[i].offset_to_left_extreme, vdata[i].offset_to_right_extreme + ); + } + } +#endif + + return IGRAPH_SUCCESS; +} + +static void layout_reingold_tilford_calc_coords( + struct reingold_tilford_vertex *vdata, + igraph_matrix_t *res, igraph_int_t node, + igraph_int_t vcount, igraph_real_t xpos) { + + MATRIX(*res, node, 0) = xpos; + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + layout_reingold_tilford_calc_coords(vdata, res, i, vcount, + xpos + vdata[i].offset); + } + } +} + +static void layout_reingold_tilford_postorder( + struct reingold_tilford_vertex *vdata, + igraph_int_t node, igraph_int_t vcount) { + + igraph_int_t childcount, leftroot, leftrootidx; + const igraph_real_t minsep = 1; + igraph_real_t avg; + +#ifdef LAYOUT_RT_DEBUG + printf("Starting visiting node %" IGRAPH_PRId "\n", node); +#endif + + /* Check whether this node is a leaf node */ + childcount = 0; + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + /* Node i is a child, so visit it recursively */ + childcount++; + layout_reingold_tilford_postorder(vdata, i, vcount); + } + } + + if (childcount == 0) { + return; + } + + /* Here we can assume that all of the subtrees have been placed and their + * left and right contours are calculated. Let's place them next to each + * other as close as we can. + * We will take each subtree in an arbitrary order. The root of the + * first one will be placed at offset 0, the next ones will be placed + * as close to each other as possible. leftroot stores the root of the + * rightmost subtree of the already placed subtrees - its right contour + * will be checked against the left contour of the next subtree */ + leftroot = leftrootidx = -1; + avg = 0.0; +#ifdef LAYOUT_RT_DEBUG + printf("Visited node %" IGRAPH_PRId " and arranged its subtrees\n", node); +#endif + for (igraph_int_t i = 0, j = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + if (leftroot >= 0) { + /* Now we will follow the right contour of leftroot and the + * left contour of the subtree rooted at i */ + igraph_int_t lnode, rnode, auxnode; + igraph_real_t loffset, roffset, rootsep, newoffset; + +#ifdef LAYOUT_RT_DEBUG + printf(" Placing child %" IGRAPH_PRId " on level %" IGRAPH_PRId ", to the right of %" IGRAPH_PRId "\n", i, vdata[i].level, leftroot); +#endif + lnode = leftroot; rnode = i; + rootsep = vdata[leftroot].offset + minsep; + loffset = vdata[leftroot].offset; roffset = loffset + minsep; + + /* Keep on updating the right contour now that we have attached + * a new node to the subtree being built */ + vdata[node].right_contour = i; + vdata[node].offset_to_right_contour = rootsep; + +#ifdef LAYOUT_RT_DEBUG + printf(" Contour: [%" IGRAPH_PRId ", %" IGRAPH_PRId "], offsets: [%lf, %lf], rootsep: %lf\n", + lnode, rnode, loffset, roffset, rootsep); +#endif + while ((lnode >= 0) && (rnode >= 0)) { + /* Step to the next level on the right contour of the left subtree */ + if (vdata[lnode].right_contour >= 0) { + loffset += vdata[lnode].offset_to_right_contour; + lnode = vdata[lnode].right_contour; + } else { + /* Left subtree ended there. The left and right contour + * of the left subtree will continue to the next step + * on the right subtree. */ + if (vdata[rnode].left_contour >= 0) { + auxnode = vdata[node].left_extreme; + + /* this is the "threading" step that the original + * paper is talking about */ + newoffset = (vdata[node].offset_to_right_extreme - vdata[node].offset_to_left_extreme) + minsep + vdata[rnode].offset_to_left_contour; + vdata[auxnode].left_contour = vdata[rnode].left_contour; + vdata[auxnode].right_contour = vdata[rnode].left_contour; + vdata[auxnode].offset_to_left_contour = vdata[auxnode].offset_to_right_contour = newoffset; + + /* since we attached a larger subtree to the + * already placed left subtree, we need to update + * the extrema of the subtree rooted at 'node' */ + vdata[node].left_extreme = vdata[i].left_extreme; + vdata[node].right_extreme = vdata[i].right_extreme; + vdata[node].offset_to_left_extreme = vdata[i].offset_to_left_extreme + rootsep; + vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme + rootsep; +#ifdef LAYOUT_RT_DEBUG + printf(" Left subtree ended earlier, continuing left subtree's left and right contour on right subtree (node %" IGRAPH_PRId " gets connected to node %" IGRAPH_PRId ")\n", auxnode, vdata[rnode].left_contour); + printf(" New contour following offset for node %" IGRAPH_PRId " is %lf\n", auxnode, vdata[auxnode].offset_to_left_contour); +#endif + } else { + /* Both subtrees are ending at the same time; the + * left extreme node of the subtree rooted at + * 'node' remains the same but the right extreme + * will change */ + vdata[node].right_extreme = vdata[i].right_extreme; + vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme + rootsep; + } + lnode = -1; + } + /* Step to the next level on the left contour of the right subtree */ + if (vdata[rnode].left_contour >= 0) { + roffset += vdata[rnode].offset_to_left_contour; + rnode = vdata[rnode].left_contour; + } else { + /* Right subtree ended here. The right contour of the right + * subtree will continue to the next step on the left subtree. + * Note that lnode has already been advanced here */ + if (lnode >= 0) { + auxnode = vdata[i].right_extreme; + + /* this is the "threading" step that the original + * paper is talking about */ + newoffset = loffset - rootsep - vdata[i].offset_to_right_extreme; + vdata[auxnode].left_contour = lnode; + vdata[auxnode].right_contour = lnode; + vdata[auxnode].offset_to_left_contour = vdata[auxnode].offset_to_right_contour = newoffset; + + /* no need to update the extrema of the subtree + * rooted at 'node' because the right subtree was + * smaller */ +#ifdef LAYOUT_RT_DEBUG + printf(" Right subtree ended earlier, continuing right subtree's left and right contour on left subtree (node %" IGRAPH_PRId " gets connected to node %" IGRAPH_PRId ")\n", auxnode, lnode); + printf(" New contour following offset for node %" IGRAPH_PRId " is %lf\n", auxnode, vdata[auxnode].offset_to_left_contour); +#endif + } + rnode = -1; + } +#ifdef LAYOUT_RT_DEBUG + printf(" Contour: [%" IGRAPH_PRId ", %" IGRAPH_PRId "], offsets: [%lf, %lf], rootsep: %lf\n", + lnode, rnode, loffset, roffset, rootsep); +#endif + + /* Push subtrees away if necessary */ + if ((lnode >= 0) && (rnode >= 0) && (roffset - loffset < minsep)) { +#ifdef LAYOUT_RT_DEBUG + printf(" Pushing right subtree away by %lf\n", minsep-roffset+loffset); +#endif + rootsep += minsep - roffset + loffset; + roffset = loffset + minsep; + vdata[node].offset_to_right_contour = rootsep; + } + } + +#ifdef LAYOUT_RT_DEBUG + printf(" Offset of subtree with root node %" IGRAPH_PRId " will be %lf\n", i, rootsep); +#endif + vdata[i].offset = rootsep; + vdata[node].offset_to_right_contour = rootsep; + avg = (avg * j) / (j + 1) + rootsep / (j + 1); + leftrootidx = j; + leftroot = i; + } else { + /* This is the first child of the node being considered, + * so we can simply place the subtree on our virtual canvas. */ +#ifdef LAYOUT_RT_DEBUG + printf(" Placing child %" IGRAPH_PRId " on level %" IGRAPH_PRId " as first child\n", i, vdata[i].level); +#endif + leftrootidx = j; + leftroot = i; + vdata[node].left_contour = i; + vdata[node].right_contour = i; + vdata[node].offset_to_left_contour = 0.0; + vdata[node].offset_to_right_contour = 0.0; + vdata[node].left_extreme = vdata[i].left_extreme; + vdata[node].right_extreme = vdata[i].right_extreme; + vdata[node].offset_to_left_extreme = vdata[i].offset_to_left_extreme; + vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme; + avg = vdata[i].offset; + } + j++; + } + } +#ifdef LAYOUT_RT_DEBUG + printf("Shifting node %" IGRAPH_PRId " to be centered above children. Shift amount: %lf\n", node, avg); +#endif + vdata[node].offset_to_left_contour -= avg; + vdata[node].offset_to_right_contour -= avg; + vdata[node].offset_to_left_extreme -= avg; + vdata[node].offset_to_right_extreme -= avg; + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + vdata[i].offset -= avg; + } + } +} + +/* This function computes the number of outgoing (or incoming) connections + * of clusters, represented as a membership vector. It only works with + * directed graphs. */ +static igraph_error_t layout_reingold_tilford_cluster_degrees_directed( + const igraph_t *graph, + const igraph_vector_int_t *membership, + igraph_int_t no_comps, + igraph_neimode_t mode, + igraph_vector_int_t *degrees) { + + igraph_eit_t eit; + + if (! igraph_is_directed(graph) || (mode != IGRAPH_OUT && mode != IGRAPH_IN)) { + IGRAPH_ERROR("Directed graph expected.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_resize(degrees, no_comps)); + igraph_vector_int_null(degrees); + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + + igraph_int_t from_cl = VECTOR(*membership)[from]; + igraph_int_t to_cl = VECTOR(*membership)[to]; + + igraph_int_t cl = mode == IGRAPH_OUT ? from_cl : to_cl; + + if (from_cl != to_cl) { + VECTOR(*degrees)[cl] += 1; + } + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Heuristic method to choose "nice" roots for the Reingold-Tilford layout algorithm. + * + * The principle is to select a minimal set of roots so that all other vertices + * will be reachable from them. + * + * In the undirected case, one root is chosen from each connected component. + * In the directed case, one root is chosen from each strongly connected component + * that has no incoming (or outgoing) edges (depending on 'mode'). + * When more than one root choice is possible, nodes are prioritized based on + * either lowest eccentricity (if 'use_eccentricity' is true) or based on + * highest degree (out- or in-degree in directed mode). + */ + +/** + * \function igraph_roots_for_tree_layout + * \brief Roots suitable for a nice tree layout. + * + * This function chooses a root, or a set of roots suitable for visualizing a tree, + * or a tree-like graph. It is typically used with \ref igraph_layout_reingold_tilford(). + * The principle is to select a minimal set of roots so that all other vertices + * will be reachable from them. + * + * + * In the undirected case, one root is chosen from each connected component. + * In the directed case, one root is chosen from each strongly connected component + * that has no incoming (or outgoing) edges (depending on 'mode'). When more than + * one root choice is possible, vertices are prioritized based on the given \p heuristic. + * + * \param graph The graph, typically a tree, but any graph is accepted. + * \param mode Whether to interpret the input as undirected, a directed out-tree or in-tree. + * \param roots An initialized integer vector, the roots will be returned here. + * \param heuristic The heuristic to use for breaking ties when multiple root + * choices are possible. + * \clist + * \cli IGRAPH_ROOT_CHOICE_DEGREE + * Choose the vertices with the highest degree (out- or in-degree + * in directed mode). This simple heuristic is fast even in large graphs. + * \cli IGRAPH_ROOT_CHOICE_ECCENTRICITY + * Choose the vertices with the lowest eccentricity. This usually results + * in a "wide and shallow" tree layout. While this heuristic produces + * high-quality results, it is slow for large graphs: computing the + * eccentricities has quadratic complexity in the number of vertices. + * \endclist + * \return Error code. + * + * Time complexity: depends on the heuristic. + */ +igraph_error_t igraph_roots_for_tree_layout( + const igraph_t *graph, + igraph_neimode_t mode, + igraph_vector_int_t *roots, + igraph_root_choice_t heuristic) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t order, membership; + igraph_int_t no_comps; + igraph_int_t i, j; + igraph_bool_t use_eccentricity; + + switch (heuristic) { + case IGRAPH_ROOT_CHOICE_DEGREE: + use_eccentricity = false; break; + case IGRAPH_ROOT_CHOICE_ECCENTRICITY: + use_eccentricity = true; break; + default: + IGRAPH_ERROR("Invalid root choice heuristic given.", IGRAPH_EINVAL); + } + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (no_of_nodes == 0) { + igraph_vector_int_clear(roots); + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); + if (use_eccentricity) { + /* Sort vertices by decreasing eccentricity. */ + + igraph_vector_t ecc; + + IGRAPH_VECTOR_INIT_FINALLY(&ecc, no_of_nodes); + IGRAPH_CHECK(igraph_eccentricity(graph, NULL, &ecc, igraph_vss_all(), mode)); + IGRAPH_CHECK(igraph_vector_sort_ind(&ecc, &order, IGRAPH_ASCENDING)); + + igraph_vector_destroy(&ecc); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Sort vertices by decreasing degree (out- or in-degree in directed case). */ + + IGRAPH_CHECK(igraph_sort_vertex_ids_by_degree(graph, &order, + igraph_vss_all(), mode, 0, IGRAPH_DESCENDING, 0)); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, no_of_nodes); + IGRAPH_CHECK(igraph_connected_components( + graph, &membership, /*csize=*/ NULL, + &no_comps, mode == IGRAPH_ALL ? IGRAPH_WEAK : IGRAPH_STRONG + )); + + IGRAPH_CHECK(igraph_vector_int_resize(roots, no_comps)); + igraph_vector_int_fill(roots, -1); /* -1 signifies a not-yet-determined root for a component */ + + if (mode != IGRAPH_ALL) { + /* Directed case: + * + * We break the graph into strongly-connected components and find those components + * which have no incoming (outgoing) edges. The largest out-degree (in-degree) + * nodes from these components will be chosen as roots. When the graph is a DAG, + * these will simply be the source (sink) nodes. */ + + igraph_vector_int_t cluster_degrees; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&cluster_degrees, no_of_nodes); + IGRAPH_CHECK(layout_reingold_tilford_cluster_degrees_directed( + graph, &membership, no_comps, + mode == IGRAPH_OUT ? IGRAPH_IN : IGRAPH_OUT, /* reverse direction */ + &cluster_degrees)); + + /* Iterate through nodes in decreasing out-degree (or in-degree) order + * and record largest degree node in each strongly-connected component + * which has no incoming (outgoing) edges. */ + for (i = 0; i < no_of_nodes; ++i) { + igraph_int_t v = VECTOR(order)[i]; + igraph_int_t cl = VECTOR(membership)[v]; + if (VECTOR(cluster_degrees)[cl] == 0 && VECTOR(*roots)[cl] == -1) { + VECTOR(*roots)[cl] = v; + } + } + + igraph_vector_int_destroy(&cluster_degrees); + IGRAPH_FINALLY_CLEAN(1); + + /* Remove remaining -1 indices. These correspond to components that + * did have some incoming edges. */ + for (i=0, j=0; i < no_comps; ++i) { + if (VECTOR(*roots)[i] == -1) { + continue; + } + VECTOR(*roots)[j++] = VECTOR(*roots)[i]; + } + igraph_vector_int_resize(roots, j); /* shrinks */ + + } else { + /* Undirected case: + * + * Select the highest degree node from each component. + */ + + igraph_int_t no_seen = 0; + + for (i=0; i < no_of_nodes; ++i) { + igraph_int_t v = VECTOR(order)[i]; + igraph_int_t cl = VECTOR(membership)[v]; + if (VECTOR(*roots)[cl] == -1) { + no_seen += 1; + VECTOR(*roots)[cl] = v; + } + if (no_seen == no_comps) { + /* All components have roots now. */ + break; + } + } + } + + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_reingold_tilford + * \brief Reingold-Tilford layout for tree graphs. + * + * + * Arranges the nodes in a tree where the given node is used as the root. + * The tree is directed downwards and the parents are centered above its + * children. For the exact algorithm, see: + * + * + * Reingold, E and Tilford, J: Tidier drawing of trees. + * IEEE Trans. Softw. Eng., SE-7(2):223--228, 1981. + * https://doi.org/10.1109/TSE.1981.234519 + * + * + * If the given graph is not a tree, a breadth-first search is executed + * first to obtain a possible spanning tree. + * + * \param graph The graph object. + * \param res The result, the coordinates in a matrix. The parameter + * should point to an initialized matrix object and will be resized. + * \param mode Specifies which edges to consider when building the tree. + * If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN + * then only the incoming edges of a parent are considered. If it is + * \c IGRAPH_ALL then all edges are used (this was the behavior in + * igraph 0.5 and before). This parameter also influences how the root + * vertices are calculated, if they are not given. See the \p roots parameter. + * \param roots The index of the root vertex or root vertices. The set of roots + * should be specified so that all vertices of the graph are reachable from them. + * Simply put, in the undirected case, one root should be given from each + * connected component. If \p roots is \c NULL or a pointer to an empty vector, + * then the roots will be selected automatically. Currently, automatic root + * selection prefers low eccentricity vertices in graphs with fewer than + * 500 vertices, and high degree vertices (according to \p mode) in larger graphs. + * The root selection heuristic may change without notice. To ensure a consistent + * output, please specify the roots manually. The \ref igraph_roots_for_tree_layout() + * function gives more control over automatic root selection. + * \param rootlevel This argument can be useful when drawing forests which are + * not trees (i.e. they are unconnected and have tree components). It specifies + * the level of the root vertices for every tree in the forest. It is only + * considered if not a null pointer and the \p roots argument is also given + * (and it is not a null pointer of an empty vector). + * \return Error code. + * + * Added in version 0.2. + * + * \sa \ref igraph_layout_reingold_tilford_circular(), \ref igraph_roots_for_tree_layout() + * + * \example examples/simple/igraph_layout_reingold_tilford.c + */ +igraph_error_t igraph_layout_reingold_tilford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel) { + + const igraph_int_t no_of_nodes_orig = igraph_vcount(graph); + igraph_int_t no_of_nodes = no_of_nodes_orig; + igraph_int_t real_root; + igraph_t extended; + const igraph_t *pextended = graph; + igraph_vector_int_t myroots; + const igraph_vector_int_t *proots = roots; + igraph_vector_int_t newedges; + + + /* TODO: possible speedup could be achieved if we use a table for storing + * the children of each node in the tree. (Now the implementation uses a + * single array containing the parent of each node and a node's children + * are determined by looking for other nodes that have this node as parent) + */ + + /* at various steps it might be necessary to add edges to the graph */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&newedges, 0); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if ( (!roots || igraph_vector_int_size(roots) == 0) && + rootlevel && igraph_vector_int_size(rootlevel) != 0 ) { + IGRAPH_WARNING("Reingold-Tilford layout: 'rootlevel' ignored"); + } + + /* ----------------------------------------------------------------------- */ + /* If root vertices are not given, perform automated root selection. */ + + if (!roots || igraph_vector_int_size(roots) == 0) { + + IGRAPH_VECTOR_INT_INIT_FINALLY(&myroots, 0); + igraph_roots_for_tree_layout(graph, mode, &myroots, + no_of_nodes < 500 ? IGRAPH_ROOT_CHOICE_DEGREE : IGRAPH_ROOT_CHOICE_ECCENTRICITY); + proots = &myroots; + + } else if (rootlevel && igraph_vector_int_size(rootlevel) > 0 && + igraph_vector_int_size(roots) > 1) { + + /* ----------------------------------------------------------------------- */ + /* Many roots were given to us, check 'rootlevel' */ + + igraph_int_t plus_levels = 0; + + if (igraph_vector_int_size(roots) != igraph_vector_int_size(rootlevel)) { + IGRAPH_ERROR("Reingold-Tilford: 'roots' and 'rootlevel' lengths differ", + IGRAPH_EINVAL); + } + + /* count the rootlevels that are not zero */ + for (igraph_int_t i = 0; i < igraph_vector_int_size(roots); i++) { + plus_levels += VECTOR(*rootlevel)[i]; + } + + /* make copy of graph, add vertices/edges */ + if (plus_levels != 0) { + igraph_int_t edgeptr = 0; + + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + IGRAPH_CHECK(igraph_add_vertices(&extended, plus_levels, 0)); + + IGRAPH_CHECK(igraph_vector_int_resize(&newedges, plus_levels * 2)); + + for (igraph_int_t i = 0; i < igraph_vector_int_size(roots); i++) { + igraph_int_t rl = VECTOR(*rootlevel)[i]; + igraph_int_t rn = VECTOR(*roots)[i]; + igraph_int_t j; + + /* zero-level roots don't get anything special */ + if (rl == 0) { + continue; + } + + /* for each nonzero-level root, add vertices + and edges at all levels [1, 2, .., rl] + piercing through the graph. If mode=="in" + they pierce the other way */ + if (mode != IGRAPH_IN) { + VECTOR(newedges)[edgeptr++] = no_of_nodes; + VECTOR(newedges)[edgeptr++] = rn; + for (j = 0; j < rl - 1; j++) { + VECTOR(newedges)[edgeptr++] = no_of_nodes + 1; + VECTOR(newedges)[edgeptr++] = no_of_nodes; + no_of_nodes++; + } + } else { + VECTOR(newedges)[edgeptr++] = rn; + VECTOR(newedges)[edgeptr++] = no_of_nodes; + for (j = 0; j < rl - 1; j++) { + VECTOR(newedges)[edgeptr++] = no_of_nodes; + VECTOR(newedges)[edgeptr++] = no_of_nodes + 1; + no_of_nodes++; + } + } + + /* move on to the next root */ + VECTOR(*roots)[i] = no_of_nodes++; + } + + /* actually add the edges to the graph */ + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + } + + /* We have root vertices now. If one or more nonzero-level roots were + chosen by the user, we have copied the graph and added a few vertices + and (directed) edges to connect those floating roots to nonfloating, + zero-level equivalent roots. + + Below, the function + + igraph_i_layout_reingold_tilford(pextended, res, mode, real_root) + + calculates the actual rt coordinates of the graph. However, for + simplicity that function requires a connected graph and a single root. + For directed graphs, it needs not be strongly connected, however all + nodes must be reachable from the root following the stream (i.e. the + root must be a "mother vertex"). + + So before we call that function we have to make sure the (copied) graph + satisfies that condition. That requires: + 1. if there is more than one root, defining a single real_root + 2. if a real_root is defined, adding edges to connect all roots to it + 3. ensure real_root is mother of the whole graph. If it is not, + add shortcut edges from real_root to any disconnected node for now. + + NOTE: 3. could be done better, e.g. by topological sorting of some kind. + But for now it's ok like this. + */ + /* if there is only one root, no need for real_root */ + if (igraph_vector_int_size(proots) == 1) { + real_root = VECTOR(*proots)[0]; + if (real_root < 0 || real_root >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex ID.", IGRAPH_EINVVID); + } + + /* else, we need to make real_root */ + } else { + igraph_int_t no_of_newedges; + + /* Make copy of the graph unless it exists already */ + if (pextended == graph) { + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + } + + /* add real_root to the vertices */ + real_root = no_of_nodes; + IGRAPH_CHECK(igraph_add_vertices(&extended, 1, 0)); + no_of_nodes++; + + /* add edges from the roots to real_root */ + no_of_newedges = igraph_vector_int_size(proots); + IGRAPH_CHECK(igraph_vector_int_resize(&newedges, no_of_newedges * 2)); + for (igraph_int_t i = 0; i < no_of_newedges; i++) { + VECTOR(newedges)[2 * i] = no_of_nodes - 1; + VECTOR(newedges)[2 * i + 1] = VECTOR(*proots)[i]; + } + + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + + /* prepare edges to unreachable parts of the graph */ + IGRAPH_CHECK(layout_reingold_tilford_unreachable(pextended, mode, real_root, no_of_nodes, &newedges)); + + if (igraph_vector_int_size(&newedges) != 0) { + /* Make copy of the graph unless it exists already */ + if (pextended == graph) { + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + } + + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + igraph_vector_int_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(1); + + /* ----------------------------------------------------------------------- */ + /* Layout */ + IGRAPH_CHECK(layout_reingold_tilford(pextended, res, mode, real_root)); + + /* Remove the new vertices from the layout */ + if (no_of_nodes != no_of_nodes_orig) { + if (no_of_nodes - 1 == no_of_nodes_orig) { + IGRAPH_CHECK(igraph_matrix_remove_row(res, no_of_nodes_orig)); + } else { + igraph_matrix_t tmp; + igraph_int_t i; + IGRAPH_MATRIX_INIT_FINALLY(&tmp, no_of_nodes_orig, 2); + for (i = 0; i < no_of_nodes_orig; i++) { + MATRIX(tmp, i, 0) = MATRIX(*res, i, 0); + MATRIX(tmp, i, 1) = MATRIX(*res, i, 1); + } + IGRAPH_CHECK(igraph_matrix_update(res, &tmp)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + } + + if (pextended != graph) { + igraph_destroy(&extended); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Remove the roots vector if it was created by us */ + if (proots != roots) { + igraph_vector_int_destroy(&myroots); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_reingold_tilford_circular + * \brief Circular Reingold-Tilford layout for trees. + * + * This layout is almost the same as \ref igraph_layout_reingold_tilford(), but + * the tree is drawn in a circular way, with the root vertex in the center. + * + * \param graph The graph object. + * \param res The result, the coordinates in a matrix. The parameter + * should point to an initialized matrix object and will be resized. + * \param mode Specifies which edges to consider when building the tree. + * If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN + * then only the incoming edges of a parent are considered. If it is + * \c IGRAPH_ALL then all edges are used (this was the behavior in + * igraph 0.5 and before). This parameter also influences how the root + * vertices are calculated, if they are not given. See the \p roots parameter. + * \param roots The index of the root vertex or root vertices. The set of roots + * should be specified so that all vertices of the graph are reachable from them. + * Simply put, in the undirected case, one root should be given from each + * connected component. If \p roots is \c NULL or a pointer to an empty vector, + * then the roots will be selected automatically. Currently, automatic root + * selection prefers low eccentricity vertices in graphs with fewer than + * 500 vertices, and high degree vertices (according to \p mode) in larger graphs. + * The root selection heuristic may change without notice. To ensure a consistent + * output, please specify the roots manually. + * \param rootlevel This argument can be useful when drawing forests which are + * not trees (i.e. they are unconnected and have tree components). It specifies + * the level of the root vertices for every tree in the forest. It is only + * considered if not a null pointer and the \p roots argument is also given + * (and it is not a null pointer or an empty vector). + * \return Error code. + * + * \sa \ref igraph_layout_reingold_tilford(). + */ +igraph_error_t igraph_layout_reingold_tilford_circular(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_int_t *roots, + const igraph_vector_int_t *rootlevel) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_real_t ratio; + igraph_real_t minx, maxx; + + IGRAPH_CHECK(igraph_layout_reingold_tilford(graph, res, mode, roots, rootlevel)); + + if (no_of_nodes == 0) { + return IGRAPH_SUCCESS; + } + + ratio = 2 * M_PI * (no_of_nodes - 1.0) / no_of_nodes; + + minx = maxx = MATRIX(*res, 0, 0); + for (igraph_int_t i = 1; i < no_of_nodes; i++) { + if (MATRIX(*res, i, 0) > maxx) { + maxx = MATRIX(*res, i, 0); + } + if (MATRIX(*res, i, 0) < minx) { + minx = MATRIX(*res, i, 0); + } + } + if (maxx > minx) { + ratio /= (maxx - minx); + } + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_real_t phi = (MATRIX(*res, i, 0) - minx) * ratio; + igraph_real_t r = MATRIX(*res, i, 1); + MATRIX(*res, i, 0) = r * cos(phi); + MATRIX(*res, i, 1) = r * sin(phi); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/layout/sugiyama.c b/src/layout/sugiyama.c new file mode 100644 index 0000000..d30c8d9 --- /dev/null +++ b/src/layout/sugiyama.c @@ -0,0 +1,1309 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" + +#include "igraph_components.h" +#include "igraph_constants.h" +#include "igraph_constructors.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_structural.h" +#include "igraph_types.h" + +#include "internal/glpk_support.h" +#include "cycles/feedback_sets.h" + +#include + +/* #define SUGIYAMA_DEBUG */ + + +#ifdef SUGIYAMA_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif + +/* MSVC uses __forceinline instead of inline */ +#ifdef _MSC_VER + #define INLINE __forceinline +#else + #define INLINE inline +#endif + +/* + * Implementation of the Sugiyama layout algorithm as described in: + * + * [1] K. Sugiyama, S. Tagawa and M. Toda, "Methods for Visual Understanding of + * Hierarchical Systems". IEEE Transactions on Systems, Man and Cybernetics + * 11(2):109-125, 1981. + * + * The layering (if not given in advance) is calculated by ... TODO + * + * [2] TODO + * + * The X coordinates of nodes within a layer are calculated using the method of + * Brandes & Köpf: + * + * [3] U. Brandes and B. Köpf, "Fast and Simple Horizontal Coordinate + * Assignment". In: Lecture Notes in Computer Science 2265:31-44, 2002. + * + * Layer compaction is done according to: + * + * [4] N.S. Nikolov and A. Tarassov, "Graph layering by promotion of nodes". + * Journal of Discrete Applied Mathematics, special issue: IV ALIO/EURO + * workshop on applied combinatorial optimization, 154(5). + * + * The steps of the algorithm are as follows: + * + * 1. Cycle removal by finding an approximately minimal feedback arc set + * and reversing the direction of edges in the set. Algorithms for + * finding minimal feedback arc sets are as follows: + * + * - Find a cycle and find its minimum weight edge. Decrease the weight + * of all the edges by w. Remove those edges whose weight became zero. + * Repeat until there are no cycles. Re-introduce removed edges in + * decreasing order of weights, ensuring that no cycles are created. + * + * - Order the vertices somehow and remove edges which point backwards + * in the ordering. Eades et al proposed the following procedure: + * + * 1. Iteratively remove sinks and prepend them to a vertex sequence + * s2. + * + * 2. Iteratively remove sources and append them to a vertex sequence + * s1. + * + * 3. Choose a vertex u s.t. the difference between the number of + * rightward arcs and the number of leftward arcs is the largest, + * remove u and append it to s1. Goto step 1 if there are still + * more vertices. + * + * 4. Concatenate s1 with s2. + * + * This algorithm is known to produce feedback arc sets at most the + * size of m/2 - n/6, where m is the number of edges. Further + * improvements are possible in step 3 which bring down the size of + * the set to at most m/4 for cubic directed graphs, see Eades (1995). + * + * - For undirected graphs, find a maximum weight spanning tree and + * remove all the edges not in the spanning tree. For directed graphs, + * find minimal cuts iteratively and remove edges pointing from A to + * B or from B to A in the cut, depending on which one is smaller. Yes, + * this is time-consuming. + * + * 2. Assigning vertices to layers according to [2]. + * + * 3. Extracting weakly connected components. The remaining steps are + * executed for each component. + * + * 4. Compacting the layering using the method of [4]. TODO + * Steps 2-4 are performed only when no layering is given in advance. + * + * 5. Adding dummy nodes to ensure that each edge spans at most one layer + * only. + * + * 6. Finding an optimal ordering of vertices within a layer using the + * Sugiyama framework [1]. + * + * 7. Assigning horizontal coordinates to each vertex using [3]. + * + * 8. ??? + * + * 9. Profit! + */ + +/** + * Data structure to store a layering of the graph. + */ +typedef struct { + igraph_vector_int_list_t layers; +} igraph_i_layering_t; + +/** + * Initializes a layering. + */ +static igraph_error_t igraph_i_layering_init(igraph_i_layering_t* layering, + const igraph_vector_int_t* membership) { + igraph_int_t i, n, num_layers; + + if (igraph_vector_int_size(membership) == 0) { + num_layers = 0; + } else { + num_layers = igraph_vector_int_max(membership) + 1; + } + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&layering->layers, num_layers); + + n = igraph_vector_int_size(membership); + for (i = 0; i < n; i++) { + igraph_int_t l = VECTOR(*membership)[i]; + igraph_vector_int_t* vec = igraph_vector_int_list_get_ptr(&layering->layers, l); + IGRAPH_CHECK(igraph_vector_int_push_back(vec, i)); + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Destroys a layering. + */ +static void igraph_i_layering_destroy(igraph_i_layering_t* layering) { + igraph_vector_int_list_destroy(&layering->layers); +} + +/** + * Returns the number of layers in a layering. + */ +static igraph_int_t igraph_i_layering_num_layers(const igraph_i_layering_t* layering) { + return igraph_vector_int_list_size(&layering->layers); +} + +/** + * Returns the list of vertices in a given layer + */ +static igraph_vector_int_t* igraph_i_layering_get(const igraph_i_layering_t* layering, + igraph_int_t index) { + return igraph_vector_int_list_get_ptr(&layering->layers, index); +} + + +/** + * Forward declarations + */ + +static igraph_error_t igraph_i_layout_sugiyama_place_nodes_vertically(const igraph_t* graph, + const igraph_vector_t* weights, igraph_vector_int_t* membership); +static igraph_error_t igraph_i_layout_sugiyama_order_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_int_t maxiter); +static igraph_error_t igraph_i_layout_sugiyama_place_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_real_t hgap, igraph_int_t no_of_real_nodes); + +/** + * Calculated the median of four numbers (not necessarily sorted). + */ +static INLINE igraph_real_t igraph_i_median_4(igraph_real_t x1, + igraph_real_t x2, igraph_real_t x3, igraph_real_t x4) { + igraph_real_t arr[4] = { x1, x2, x3, x4 }; + igraph_vector_t vec = igraph_vector_view(arr, 4); + igraph_vector_sort(&vec); + return (arr[1] + arr[2]) / 2.0; +} + + +/** + * \ingroup layout + * \function igraph_layout_sugiyama + * \brief Sugiyama layout algorithm for layered directed acyclic graphs. + * + * + * This layout algorithm is designed for directed acyclic graphs where each + * vertex is assigned to a layer. Layers are indexed from zero, and vertices + * of the same layer will be placed on the same horizontal line. The X coordinates + * of vertices within each layer are decided by the heuristic proposed by + * Sugiyama et al to minimize edge crossings. + * + * + * You can also try to lay out undirected graphs, graphs containing cycles, or + * graphs without an a priori layered assignment with this algorithm. igraph + * will try to eliminate cycles and assign vertices to layers, but there is no + * guarantee on the quality of the layout in such cases. + * + * + * The Sugiyama layout may introduce "bends" on the edges in order to obtain a + * visually more pleasing layout. The additional control points of the edges are + * returned in a separate list of matrices, one matrix per edge in the original + * graph. If an edge requires no additional control points, the corresponding + * matrix will be empty, otherwise the matrix will contain the coordinates of + * the control points, one point per row. When drawing the graph, edges should + * be drawn in a way that the curve representing the edge passes through the + * control points. + * + * + * For more details, see K. Sugiyama, S. Tagawa and M. Toda, "Methods for Visual + * Understanding of Hierarchical Systems". IEEE Transactions on Systems, Man and + * Cybernetics 11(2):109-125, 1981. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will contain + * the result and will be resized as needed. The coordinates of the + * vertices in the layout will be stored in the rows of the matrix, + * one row per vertex. + * \param routing Pointer to an uninitialized list of matrices or \c NULL. + * When not \c NULL, the list will be resized as needed such + * that there will be one matrix for each edge of the graph, + * and the matrix will hold the additional control points that + * the edge must pass through, starting from the source vertex + * of the edge and ending at the target vertex. The matrix will + * have zero rows if an edge does not require control points. + * \param layers The layer index for each vertex or \c NULL if the layers should + * be determined automatically by igraph. + * \param hgap The preferred minimum horizontal gap between vertices in the same + * layer. + * \param vgap The distance between layers. + * \param maxiter Maximum number of iterations in the crossing minimization stage. + * 100 is a reasonable default; if you feel that you have too + * many edge crossings, increase this. + * \param weights Weights of the edges. These are used only if the graph contains + * cycles; igraph will tend to reverse edges with smaller + * weights when breaking the cycles. + * \return Error code. + */ +igraph_error_t igraph_layout_sugiyama( + const igraph_t *graph, igraph_matrix_t *res, igraph_matrix_list_t *routing, + const igraph_vector_int_t* layers, igraph_real_t hgap, igraph_real_t vgap, + igraph_int_t maxiter, const igraph_vector_t *weights +) { + igraph_int_t i, j, k, l, nei; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t comp_idx; + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t no_of_components; /* number of components of the original graph */ + igraph_vector_int_t membership; /* components of the original graph */ + igraph_vector_int_t layers_own; /* layer indices after having eliminated empty layers */ + igraph_real_t dx = 0; /* displacement of the current component on the X axis */ + igraph_vector_t layer_to_y; /* mapping from layer indices to final Y coordinates */ + igraph_matrix_t *control_points; + + if (layers && igraph_vector_int_size(layers) != no_of_nodes) { + IGRAPH_ERROR("layer vector too short or too long", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INIT_FINALLY(&layer_to_y, 0); + + if (routing != 0) { + IGRAPH_CHECK(igraph_matrix_list_resize(routing, no_of_edges)); + for (i = 0; i < no_of_edges; i++) { + control_points = igraph_matrix_list_get_ptr(routing, i); + IGRAPH_CHECK(igraph_matrix_resize(control_points, 0, 2)); + } + } + + /* 1. Find a feedback arc set if we don't have a layering yet. If we do have + * a layering, we can leave all the edges as is as they will be re-oriented + * to point downwards only anyway. */ + if (layers == 0) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&layers_own, no_of_nodes); + IGRAPH_CHECK(igraph_i_layout_sugiyama_place_nodes_vertically(graph, weights, &layers_own)); + } else { + IGRAPH_CHECK(igraph_vector_int_init_copy(&layers_own, layers)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &layers_own); + } + + /* Normalize layering, eliminate empty layers */ + if (no_of_nodes > 0) { + igraph_vector_int_t inds; + IGRAPH_VECTOR_INT_INIT_FINALLY(&inds, 0); + IGRAPH_CHECK(igraph_vector_int_sort_ind(&layers_own, &inds, IGRAPH_ASCENDING)); + j = -1; dx = VECTOR(layers_own)[VECTOR(inds)[0]] - 1; + for (i = 0; i < no_of_nodes; i++) { + k = VECTOR(inds)[i]; + if (VECTOR(layers_own)[k] > dx) { + /* New layer starts here */ + dx = VECTOR(layers_own)[k]; + j++; + IGRAPH_CHECK(igraph_vector_push_back(&layer_to_y, dx * vgap)); + } + VECTOR(layers_own)[k] = j; + } + igraph_vector_int_destroy(&inds); + IGRAPH_FINALLY_CLEAN(1); + } + + /* 2. Find the connected components. */ + IGRAPH_CHECK(igraph_connected_components(graph, &membership, 0, &no_of_components, IGRAPH_WEAK)); + + /* 3. For each component... */ + dx = 0; + for (comp_idx = 0; comp_idx < no_of_components; comp_idx++) { + /* Extract the edges of the comp_idx'th component and add dummy nodes for edges + * spanning more than one layer. */ + igraph_int_t component_size, next_new_vertex_id; + igraph_vector_int_t old2new_vertex_ids; + igraph_vector_int_t new2old_vertex_ids; + igraph_vector_int_t new_vertex_id_to_edge_id; + igraph_vector_int_t new_layers; + igraph_vector_int_t edgelist; + igraph_vector_int_t neis; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edgelist, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&new2old_vertex_ids, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&old2new_vertex_ids, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_vertex_id_to_edge_id, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_layers, 0); + + igraph_vector_int_fill(&old2new_vertex_ids, -1); + igraph_vector_int_fill(&new_vertex_id_to_edge_id, -1); + + /* Construct a mapping from the old vertex IDs to the new ones */ + for (i = 0, next_new_vertex_id = 0; i < no_of_nodes; i++) { + if (VECTOR(membership)[i] == comp_idx) { + IGRAPH_CHECK(igraph_vector_int_push_back(&new_layers, VECTOR(layers_own)[i])); + VECTOR(new2old_vertex_ids)[next_new_vertex_id] = i; + VECTOR(old2new_vertex_ids)[i] = next_new_vertex_id; + next_new_vertex_id++; + } + } + component_size = next_new_vertex_id; + + /* Construct a proper layering of the component in new_graph where each edge + * points downwards and spans exactly one layer. */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(membership)[i] != comp_idx) { + continue; + } + + /* Okay, this vertex is in the component we are considering. + * Add the neighbors of this vertex, excluding loops */ + IGRAPH_CHECK(igraph_incident(graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS)); + j = igraph_vector_int_size(&neis); + for (k = 0; k < j; k++) { + igraph_int_t eid = VECTOR(neis)[k]; + if (directed) { + nei = IGRAPH_TO(graph, eid); + } else { + nei = IGRAPH_OTHER(graph, eid, i); + if (nei < i) { /* to avoid considering edges twice */ + continue; + } + } + if (VECTOR(layers_own)[i] == VECTOR(layers_own)[nei]) { + /* Edge goes within the same layer, we don't need this in the + * layered graph */ + } else if (VECTOR(layers_own)[i] > VECTOR(layers_own)[nei]) { + /* Edge goes upwards, we have to flip it and then later on we need to traverse + * its control points in reverse order. We remember that we need to flip it + * by storing -eid-1 in `new_vertex_id_to_edge_id` instead of eid */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[nei])); + for (l = VECTOR(layers_own)[nei] + 1; + l < VECTOR(layers_own)[i]; l++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&new_layers, l)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, next_new_vertex_id)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, next_new_vertex_id++)); + IGRAPH_CHECK(igraph_vector_int_push_back(&new_vertex_id_to_edge_id, -eid-1)); + } + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[i])); + } else { + /* Edge goes downwards */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[i])); + for (l = VECTOR(layers_own)[i] + 1; + l < VECTOR(layers_own)[nei]; l++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&new_layers, l)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, next_new_vertex_id)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, next_new_vertex_id++)); + IGRAPH_CHECK(igraph_vector_int_push_back(&new_vertex_id_to_edge_id, eid)); + } + IGRAPH_CHECK(igraph_vector_int_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[nei])); + } + } + } + + /* At this point, we have the subgraph with the dummy nodes and + * edges, so we can run Sugiyama's algorithm on it. */ + { + igraph_matrix_t layout; + igraph_i_layering_t layering; + igraph_t subgraph; + igraph_int_t eid; + igraph_real_t max_x; + igraph_bool_t flipped; + igraph_matrix_t *control_points; + + IGRAPH_CHECK(igraph_matrix_init(&layout, next_new_vertex_id, 2)); + IGRAPH_FINALLY(igraph_matrix_destroy, &layout); + IGRAPH_CHECK(igraph_create(&subgraph, &edgelist, next_new_vertex_id, 1)); + IGRAPH_FINALLY(igraph_destroy, &subgraph); + + /* + igraph_vector_int_print(&edgelist); + igraph_vector_int_print(&new_layers); + */ + + /* Assign the vertical coordinates */ + for (i = 0; i < next_new_vertex_id; i++) { + MATRIX(layout, i, 1) = VECTOR(new_layers)[i]; + } + + /* Create a layering */ + IGRAPH_CHECK(igraph_i_layering_init(&layering, &new_layers)); + IGRAPH_FINALLY(igraph_i_layering_destroy, &layering); + + /* Find the order in which the nodes within a layer should be placed */ + IGRAPH_CHECK(igraph_i_layout_sugiyama_order_nodes_horizontally(&subgraph, &layout, + &layering, maxiter)); + + /* Assign the horizontal coordinates. This is according to the algorithm + * of Brandes & Köpf */ + IGRAPH_CHECK(igraph_i_layout_sugiyama_place_nodes_horizontally(&subgraph, &layout, + &layering, hgap, component_size)); + + /* Arrange rows of the layout into the result matrix, and at the same time, */ + /* adjust dx so that the next component does not overlap this one */ + + /* First we arrange the "real" vertices */ + for (i = 0; i < component_size; i++) { + k = VECTOR(new2old_vertex_ids)[i]; + MATRIX(*res, k, 0) = MATRIX(layout, i, 0) + dx; + MATRIX(*res, k, 1) = VECTOR(layer_to_y)[(igraph_int_t) MATRIX(layout, i, 1)]; + } + + /* Next we arrange the "dummy" vertices that become control points in the + * routing matrix list. Note that the dummy vertices were added in a way that + * the vertices that will become control points on an edge with a particular ID + * are always consecutive in `new_vertex_id_to_edge_id` so we can count the number + * of waypoints in an edg easily */ + if (routing) { + IGRAPH_CHECK(igraph_vector_int_push_back(&new_vertex_id_to_edge_id, -1)); /* sentinel */ + while (i < next_new_vertex_id) { + eid = VECTOR(new_vertex_id_to_edge_id)[i]; + + /* Find out how many control points we have for this edge */ + for (j = i; VECTOR(new_vertex_id_to_edge_id)[j] == eid; j++); + + /* Is this edge flipped in the layered graph? If so, recover the original eid */ + flipped = eid < 0; + if (flipped) { + eid = -eid-1; + } + + control_points = igraph_matrix_list_get_ptr(routing, eid); + IGRAPH_CHECK(igraph_matrix_resize(control_points, j - i, 2)); + + if (flipped) { + for (k = j - i - 1; i < j; k--, i++) { + MATRIX(*control_points, k, 0) = MATRIX(layout, i, 0) + dx; + MATRIX(*control_points, k, 1) = VECTOR(layer_to_y)[(igraph_int_t) MATRIX(layout, i, 1)]; + } + } else { + for (k = 0; i < j; k++, i++) { + MATRIX(*control_points, k, 0) = MATRIX(layout, i, 0) + dx; + MATRIX(*control_points, k, 1) = VECTOR(layer_to_y)[(igraph_int_t) MATRIX(layout, i, 1)]; + } + } + } + } + + /* Update the left margin for the next component */ + max_x = 0; + for (i = 0; i < next_new_vertex_id; i++) { + if (MATRIX(layout, i, 0) > max_x) { + max_x = MATRIX(layout, i, 0); + } + } + dx += max_x + hgap; + + igraph_destroy(&subgraph); + igraph_i_layering_destroy(&layering); + igraph_matrix_destroy(&layout); + IGRAPH_FINALLY_CLEAN(3); + } + + igraph_vector_int_destroy(&new_layers); + igraph_vector_int_destroy(&new_vertex_id_to_edge_id); + igraph_vector_int_destroy(&old2new_vertex_ids); + igraph_vector_int_destroy(&new2old_vertex_ids); + igraph_vector_int_destroy(&edgelist); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(6); + } + + igraph_vector_int_destroy(&layers_own); + igraph_vector_destroy(&layer_to_y); + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_layout_sugiyama_place_nodes_vertically(const igraph_t* graph, + const igraph_vector_t* weights, igraph_vector_int_t* membership) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + + if (no_of_edges == 0) { + igraph_vector_int_null(membership); + return IGRAPH_SUCCESS; + } + +#ifdef HAVE_GLPK + if (igraph_is_directed(graph) && no_of_nodes <= 1000) { + /* Network simplex algorithm of Gansner et al, using the original linear + * programming formulation */ + igraph_int_t i, j; + igraph_vector_t outdegs, indegs; + igraph_vector_int_t feedback_edges; + glp_prob *ip; + glp_smcp parm; + + if (no_of_edges > INT_MAX) { + IGRAPH_ERROR("Number of edges in graph too large for GLPK.", IGRAPH_EOVERFLOW); + } + + /* Allocate storage and create the problem */ + ip = glp_create_prob(); + IGRAPH_FINALLY(glp_delete_prob, ip); + IGRAPH_VECTOR_INT_INIT_FINALLY(&feedback_edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdegs, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&indegs, no_of_nodes); + + /* Find an approximate feedback edge set */ + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, &feedback_edges, weights, 0)); + igraph_vector_int_sort(&feedback_edges); + + /* Calculate in- and out-strengths for the remaining edges */ + IGRAPH_CHECK(igraph_strength(graph, &indegs, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outdegs, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS, weights)); + j = igraph_vector_int_size(&feedback_edges); + for (i = 0; i < j; i++) { + igraph_int_t eid = VECTOR(feedback_edges)[i]; + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + VECTOR(outdegs)[from] -= weights ? VECTOR(*weights)[eid] : 1; + VECTOR(indegs)[to] -= weights ? VECTOR(*weights)[eid] : 1; + } + + /* Configure GLPK */ + glp_term_out(GLP_OFF); + glp_init_smcp(&parm); + parm.msg_lev = GLP_MSG_OFF; + parm.presolve = GLP_OFF; + + /* Set up variables and objective function coefficients */ + glp_set_obj_dir(ip, GLP_MIN); + glp_add_cols(ip, (int) no_of_nodes); + IGRAPH_CHECK(igraph_vector_sub(&outdegs, &indegs)); + for (i = 1; i <= no_of_nodes; i++) { + glp_set_col_kind(ip, (int) i, GLP_IV); + glp_set_col_bnds(ip, (int) i, GLP_LO, 0.0, 0.0); + glp_set_obj_coef(ip, (int) i, VECTOR(outdegs)[i - 1]); + } + igraph_vector_destroy(&indegs); + igraph_vector_destroy(&outdegs); + IGRAPH_FINALLY_CLEAN(2); + + /* Add constraints */ + glp_add_rows(ip, (int) no_of_edges); + IGRAPH_CHECK(igraph_vector_int_push_back(&feedback_edges, -1)); + j = 0; + for (i = 0; i < no_of_edges; i++) { + int ind[3]; + double val[3] = {0, -1, 1}; + ind[1] = (int) IGRAPH_FROM(graph, i) + 1; + ind[2] = (int) IGRAPH_TO(graph, i) + 1; + + if (ind[1] == ind[2]) { + if (VECTOR(feedback_edges)[j] == i) { + j++; + } + continue; + } + + if (VECTOR(feedback_edges)[j] == i) { + /* This is a feedback edge, add it reversed */ + glp_set_row_bnds(ip, (int) i + 1, GLP_UP, -1, -1); + j++; + } else { + glp_set_row_bnds(ip, (int) i + 1, GLP_LO, 1, 1); + } + glp_set_mat_row(ip, (int) i + 1, 2, ind, val); + } + + /* Solve the problem */ + IGRAPH_GLPK_CHECK(glp_simplex(ip, &parm), + "Vertical arrangement step using IP failed"); + + /* The problem is totally unimodular, therefore the output of the simplex + * solver can be converted to an integer solution easily */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = floor(glp_get_col_prim(ip, (int) i + 1)); + } + + glp_delete_prob(ip); + igraph_vector_int_destroy(&feedback_edges); + IGRAPH_FINALLY_CLEAN(2); + } else if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, 0, weights, membership)); + } else { + IGRAPH_CHECK(igraph_i_feedback_arc_set_undirected(graph, 0, weights, membership)); + } +#else + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, 0, weights, membership)); + } else { + IGRAPH_CHECK(igraph_i_feedback_arc_set_undirected(graph, 0, weights, membership)); + } +#endif + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_layout_sugiyama_calculate_barycenters(const igraph_t* graph, + const igraph_i_layering_t* layering, igraph_int_t layer_index, + igraph_neimode_t direction, const igraph_matrix_t* layout, + igraph_vector_t* barycenters) { + igraph_int_t i, j, m, n; + igraph_vector_int_t* layer_members = igraph_i_layering_get(layering, layer_index); + igraph_vector_int_t neis; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + n = igraph_vector_int_size(layer_members); + IGRAPH_CHECK(igraph_vector_resize(barycenters, n)); + igraph_vector_null(barycenters); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, VECTOR(*layer_members)[i], direction, + IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + m = igraph_vector_int_size(&neis); + if (m == 0) { + /* No neighbors in this direction. Just use the current X coordinate */ + VECTOR(*barycenters)[i] = MATRIX(*layout, i, 0); + } else { + for (j = 0; j < m; j++) { + VECTOR(*barycenters)[i] += MATRIX(*layout, VECTOR(neis)[j], 0); + } + VECTOR(*barycenters)[i] /= m; + } + } + + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Given a properly layered graph where each edge points downwards and spans + * exactly one layer, arranges the nodes in each layer horizontally in a way + * that strives to minimize edge crossings. + */ +static igraph_error_t igraph_i_layout_sugiyama_order_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_int_t maxiter) { + igraph_int_t i, n, nei; + igraph_int_t no_of_vertices = igraph_vcount(graph); + igraph_int_t no_of_layers = igraph_i_layering_num_layers(layering); + igraph_int_t iter, layer_index; + igraph_vector_int_t* layer_members; + igraph_vector_int_t new_layer_members; + igraph_vector_int_t neis; + igraph_vector_t barycenters; + igraph_vector_int_t sort_indices; + igraph_bool_t changed; + + /* The first column of the matrix will serve as the ordering */ + /* Start with a first-seen ordering within each layer */ + { + igraph_int_t *xs = IGRAPH_CALLOC(no_of_layers, igraph_int_t); + IGRAPH_CHECK_OOM(xs, "Cannot order nodes horizontally during Sugiyama layout."); + for (i = 0; i < no_of_vertices; i++) { + MATRIX(*layout, i, 0) = xs[(igraph_int_t)MATRIX(*layout, i, 1)]++; + } + free(xs); + } + + IGRAPH_VECTOR_INIT_FINALLY(&barycenters, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_layer_members, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&sort_indices, 0); + + /* Start the effective part of the Sugiyama algorithm */ + iter = 0; changed = 1; + while (changed && iter < maxiter) { + changed = 0; + + /* Phase 1 */ + + /* Moving downwards and sorting by upper barycenters */ + for (layer_index = 1; layer_index < no_of_layers; layer_index++) { + layer_members = igraph_i_layering_get(layering, layer_index); + n = igraph_vector_int_size(layer_members); + IGRAPH_CHECK(igraph_vector_int_resize(&new_layer_members, n)); + + igraph_i_layout_sugiyama_calculate_barycenters(graph, + layering, layer_index, IGRAPH_IN, layout, &barycenters); + +#ifdef SUGIYAMA_DEBUG + printf("Layer %" IGRAPH_PRId ", aligning to upper barycenters\n", layer_index); + printf("Vertices: "); igraph_vector_int_print(layer_members); + printf("Barycenters: "); igraph_vector_print(&barycenters); +#endif + IGRAPH_CHECK(igraph_vector_sort_ind(&barycenters, &sort_indices, IGRAPH_ASCENDING)); + for (i = 0; i < n; i++) { + nei = VECTOR(*layer_members)[VECTOR(sort_indices)[i]]; + VECTOR(new_layer_members)[i] = nei; + MATRIX(*layout, nei, 0) = i; + } + if (!igraph_vector_int_all_e(layer_members, &new_layer_members)) { + IGRAPH_CHECK(igraph_vector_int_update(layer_members, &new_layer_members)); +#ifdef SUGIYAMA_DEBUG + printf("New vertex order: "); igraph_vector_int_print(layer_members); +#endif + changed = 1; + } else { +#ifdef SUGIYAMA_DEBUG + printf("Order did not change.\n"); +#endif + } + } + + /* Moving upwards and sorting by lower barycenters */ + for (layer_index = no_of_layers - 2; layer_index >= 0; layer_index--) { + layer_members = igraph_i_layering_get(layering, layer_index); + n = igraph_vector_int_size(layer_members); + IGRAPH_CHECK(igraph_vector_int_resize(&new_layer_members, n)); + + igraph_i_layout_sugiyama_calculate_barycenters(graph, + layering, layer_index, IGRAPH_OUT, layout, &barycenters); + +#ifdef SUGIYAMA_DEBUG + printf("Layer %" IGRAPH_PRId ", aligning to lower barycenters\n", layer_index); + printf("Vertices: "); igraph_vector_int_print(layer_members); + printf("Barycenters: "); igraph_vector_print(&barycenters); +#endif + + IGRAPH_CHECK(igraph_vector_sort_ind(&barycenters, &sort_indices, IGRAPH_ASCENDING)); + for (i = 0; i < n; i++) { + nei = VECTOR(*layer_members)[VECTOR(sort_indices)[i]]; + VECTOR(new_layer_members)[i] = nei; + MATRIX(*layout, nei, 0) = i; + } + if (!igraph_vector_int_all_e(layer_members, &new_layer_members)) { + IGRAPH_CHECK(igraph_vector_int_update(layer_members, &new_layer_members)); +#ifdef SUGIYAMA_DEBUG + printf("New vertex order: "); igraph_vector_int_print(layer_members); +#endif + changed = 1; + } else { +#ifdef SUGIYAMA_DEBUG + printf("Order did not change.\n"); +#endif + } + } + +#ifdef SUGIYAMA_DEBUG + printf("==== Finished iteration %" IGRAPH_PRId "\n", iter); +#endif + + iter++; + } + + igraph_vector_destroy(&barycenters); + igraph_vector_int_destroy(&new_layer_members); + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&sort_indices); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +#define IS_DUMMY(v) ((v >= no_of_real_nodes)) +#define IS_INNER_SEGMENT(u, v) (IS_DUMMY(u) && IS_DUMMY(v)) +#define X_POS(v) (MATRIX(*layout, v, 0)) + +static igraph_error_t igraph_i_layout_sugiyama_vertical_alignment(const igraph_t* graph, + const igraph_i_layering_t* layering, const igraph_matrix_t* layout, + const igraph_vector_bool_t* ignored_edges, + igraph_bool_t reverse, igraph_bool_t align_right, + igraph_vector_int_t* roots, igraph_vector_int_t* align); +static igraph_error_t igraph_i_layout_sugiyama_horizontal_compaction(const igraph_t* graph, + const igraph_vector_int_t* vertex_to_the_left, + const igraph_vector_int_t* roots, const igraph_vector_int_t* align, + igraph_real_t hgap, igraph_vector_t* xs); +static void igraph_i_layout_sugiyama_horizontal_compaction_place_block(igraph_int_t v, + const igraph_vector_int_t* vertex_to_the_left, + const igraph_vector_int_t* roots, const igraph_vector_int_t* align, + igraph_vector_int_t* sinks, igraph_vector_t* shifts, + igraph_real_t hgap, igraph_vector_t* xs); + +static igraph_error_t igraph_i_layout_sugiyama_place_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_real_t hgap, igraph_int_t no_of_real_nodes) { + + igraph_int_t i, j, k, l, n; + igraph_int_t no_of_layers = igraph_i_layering_num_layers(layering); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t neis1, neis2; + igraph_vector_t xs[4]; + igraph_vector_int_t roots, align; + igraph_vector_int_t vertex_to_the_left; + igraph_vector_bool_t ignored_edges; + + /* + { + igraph_vector_int_t edgelist; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edgelist, 0); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edgelist, 0)); + igraph_vector_int_print(&edgelist); + igraph_vector_int_destroy(&edgelist); + IGRAPH_FINALLY_CLEAN(1); + + for (i = 0; i < no_of_layers; i++) { + igraph_vector_int_t* layer = igraph_i_layering_get(layering, i); + igraph_vector_int_print(layer); + } + } + */ + + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&ignored_edges, no_of_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_to_the_left, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis2, 0); + + /* First, find all type 1 conflicts and mark one of the edges participating + * in the conflict as being ignored. If one of the edges in the conflict + * is a non-inner segment and the other is an inner segment, we ignore the + * non-inner segment as we want to keep inner segments vertical. + */ + for (i = 0; i < no_of_layers - 1; i++) { + igraph_vector_int_t* vertices = igraph_i_layering_get(layering, i); + n = igraph_vector_int_size(vertices); + + /* Find all the edges from this layer to the next */ + igraph_vector_int_clear(&neis1); + for (j = 0; j < n; j++) { + IGRAPH_CHECK(igraph_neighbors( + graph, &neis2, VECTOR(*vertices)[j], IGRAPH_OUT, + IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + IGRAPH_CHECK(igraph_vector_int_append(&neis1, &neis2)); + } + + /* Consider all pairs of edges and check whether they are in a type 1 + * conflict */ + n = igraph_vector_int_size(&neis1); + for (j = 0; j < n; j++) { + igraph_int_t u = IGRAPH_FROM(graph, j); + igraph_int_t v = IGRAPH_TO(graph, j); + igraph_bool_t j_inner = IS_INNER_SEGMENT(u, v); + igraph_bool_t crossing; + + for (k = j + 1; k < n; k++) { + igraph_int_t w = IGRAPH_FROM(graph, k); + igraph_int_t x = IGRAPH_TO(graph, k); + if (IS_INNER_SEGMENT(w, x) == j_inner) { + continue; + } + /* Do the u --> v and w --> x edges cross? */ + crossing = (u == w || v == x); + if (!crossing) { + if (X_POS(u) <= X_POS(w)) { + crossing = X_POS(v) >= X_POS(x); + } else { + crossing = X_POS(v) <= X_POS(x); + } + } + if (crossing) { + if (j_inner) { + VECTOR(ignored_edges)[k] = true; + } else { + VECTOR(ignored_edges)[j] = true; + } + } + } + } + } + + igraph_vector_int_destroy(&neis1); + igraph_vector_int_destroy(&neis2); + IGRAPH_FINALLY_CLEAN(2); + + /* + * Prepare vertex_to_the_left where the ith element stores + * the index of the vertex to the left of vertex i, or i itself if the + * vertex is the leftmost vertex in a layer. + */ + for (i = 0; i < no_of_layers; i++) { + igraph_vector_int_t* vertices = igraph_i_layering_get(layering, i); + n = igraph_vector_int_size(vertices); + if (n == 0) { + continue; + } + + k = l = VECTOR(*vertices)[0]; + VECTOR(vertex_to_the_left)[k] = k; + for (j = 1; j < n; j++) { + k = VECTOR(*vertices)[j]; + VECTOR(vertex_to_the_left)[k] = l; + l = k; + } + } + + /* Type 1 conflicts found, ignored edges chosen, vertex_to_the_left + * prepared. Run vertical alignment for all four combinations */ + for (i = 0; i < 4; i++) { + IGRAPH_VECTOR_INIT_FINALLY(&xs[i], no_of_nodes); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&roots, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&align, no_of_nodes); + + for (i = 0; i < 4; i++) { + IGRAPH_CHECK(igraph_i_layout_sugiyama_vertical_alignment(graph, + layering, layout, &ignored_edges, + /* reverse = */ i / 2, /* align_right = */ i % 2, + &roots, &align)); + IGRAPH_CHECK(igraph_i_layout_sugiyama_horizontal_compaction(graph, + &vertex_to_the_left, &roots, &align, hgap, &xs[i])); + } + + { + igraph_real_t width, min_width, mins[4], maxs[4], diff; + /* Find the alignment with the minimum width */ + min_width = IGRAPH_INFINITY; j = 0; + for (i = 0; i < 4; i++) { + mins[i] = igraph_vector_min(&xs[i]); + maxs[i] = igraph_vector_max(&xs[i]); + width = maxs[i] - mins[i]; + if (width < min_width) { + min_width = width; + j = i; + } + } + + /* Leftmost alignments: align them s.t. the min X coordinate is equal to + * the minimum X coordinate of the alignment with the smallest width. + * Rightmost alignments: align them s.t. the max X coordinate is equal to + * the max X coordinate of the alignment with the smallest width. + */ + for (i = 0; i < 4; i++) { + if (j == i) { + continue; + } + if (i % 2 == 0) { + /* Leftmost alignment */ + diff = mins[j] - mins[i]; + } else { + /* Rightmost alignment */ + diff = maxs[j] - maxs[i]; + } + igraph_vector_add_constant(&xs[i], diff); + } + } + + /* For every vertex, find the median of the X coordinates in the four + * alignments */ + for (i = 0; i < no_of_nodes; i++) { + X_POS(i) = igraph_i_median_4(VECTOR(xs[0])[i], VECTOR(xs[1])[i], + VECTOR(xs[2])[i], VECTOR(xs[3])[i]); + } + + igraph_vector_int_destroy(&roots); + igraph_vector_int_destroy(&align); + IGRAPH_FINALLY_CLEAN(2); + + for (i = 0; i < 4; i++) { + igraph_vector_destroy(&xs[i]); + } + IGRAPH_FINALLY_CLEAN(4); + + igraph_vector_int_destroy(&vertex_to_the_left); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_bool_destroy(&ignored_edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_layout_sugiyama_vertical_alignment(const igraph_t* graph, + const igraph_i_layering_t* layering, const igraph_matrix_t* layout, + const igraph_vector_bool_t* ignored_edges, + igraph_bool_t reverse, igraph_bool_t align_right, + igraph_vector_int_t* roots, igraph_vector_int_t* align) { + igraph_int_t i, j, k, n, di, dj, i_limit, j_limit, r; + igraph_int_t no_of_layers = igraph_i_layering_num_layers(layering); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_neimode_t neimode = (reverse ? IGRAPH_OUT : IGRAPH_IN); + igraph_vector_int_t neis; + igraph_vector_t xs; + igraph_vector_int_t inds; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&xs, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&inds, 0); + + IGRAPH_CHECK(igraph_vector_int_resize(roots, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_int_resize(align, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*roots)[i] = VECTOR(*align)[i] = i; + } + + /* When reverse = False, we are aligning "upwards" in the tree, hence we + * have to loop i from 1 to no_of_layers-1 (inclusive) and use neimode=IGRAPH_IN. + * When reverse = True, we are aligning "downwards", hence we have to loop + * i from no_of_layers-2 to 0 (inclusive) and use neimode=IGRAPH_OUT. + */ + i = reverse ? (no_of_layers - 2) : 1; + di = reverse ? -1 : 1; + i_limit = reverse ? -1 : no_of_layers; + for (; i != i_limit; i += di) { + igraph_vector_int_t *layer = igraph_i_layering_get(layering, i); + + /* r = 0 in the paper, but C arrays are indexed from 0 */ + r = align_right ? IGRAPH_INTEGER_MAX : -1; + + /* If align_right is 1, we have to process the layer in reverse order */ + j = align_right ? (igraph_vector_int_size(layer) - 1) : 0; + dj = align_right ? -1 : 1; + j_limit = align_right ? -1 : igraph_vector_int_size(layer); + for (; j != j_limit; j += dj) { + igraph_int_t medians[2]; + igraph_int_t vertex = VECTOR(*layer)[j]; + igraph_int_t pos; + + if (VECTOR(*align)[vertex] != vertex) + /* This vertex is already aligned with some other vertex, + * so there's nothing to do */ + { + continue; + } + + /* Find the neighbors of vertex j in layer i */ + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, vertex, neimode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE + )); + + n = igraph_vector_int_size(&neis); + if (n == 0) + /* No neighbors in this direction, continue */ + { + continue; + } + if (n == 1) { + /* Just one neighbor; the median is trivial */ + medians[0] = VECTOR(neis)[0]; + medians[1] = -1; + } else { + /* Sort the neighbors by their X coordinates */ + IGRAPH_CHECK(igraph_vector_resize(&xs, n)); + for (k = 0; k < n; k++) { + VECTOR(xs)[k] = X_POS(VECTOR(neis)[k]); + } + IGRAPH_CHECK(igraph_vector_sort_ind(&xs, &inds, IGRAPH_ASCENDING)); + + if (n % 2 == 1) { + /* Odd number of neighbors, so the median is unique */ + medians[0] = VECTOR(neis)[VECTOR(inds)[n / 2]]; + medians[1] = -1; + } else { + /* Even number of neighbors, so we have two medians. The order + * depends on whether we are processing the layer in leftmost + * or rightmost fashion. */ + if (align_right) { + medians[0] = VECTOR(neis)[VECTOR(inds)[n / 2]]; + medians[1] = VECTOR(neis)[VECTOR(inds)[n / 2 - 1]]; + } else { + medians[0] = VECTOR(neis)[VECTOR(inds)[n / 2 - 1]]; + medians[1] = VECTOR(neis)[VECTOR(inds)[n / 2]]; + } + } + } + + /* Try aligning with the medians */ + for (k = 0; k < 2; k++) { + igraph_int_t eid; + if (medians[k] < 0) { + continue; + } + if (VECTOR(*align)[vertex] != vertex) { + /* Vertex already aligned, continue */ + continue; + } + /* Is the edge between medians[k] and vertex ignored + * because of a type 1 conflict? */ + IGRAPH_CHECK(igraph_get_eid(graph, &eid, vertex, medians[k], IGRAPH_UNDIRECTED, /* error= */ true)); + if (VECTOR(*ignored_edges)[eid]) { + continue; + } + /* Okay, align with the median if possible */ + pos = X_POS(medians[k]); + if ((align_right && r > pos) || (!align_right && r < pos)) { + VECTOR(*align)[medians[k]] = vertex; + VECTOR(*roots)[vertex] = VECTOR(*roots)[medians[k]]; + VECTOR(*align)[vertex] = VECTOR(*roots)[medians[k]]; + r = pos; + } + } + } + } + + igraph_vector_int_destroy(&inds); + igraph_vector_int_destroy(&neis); + igraph_vector_destroy(&xs); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* + * Runs a horizontal compaction given a vertical alignment (in `align`) + * and the roots (in `roots`). These come out directly from + * igraph_i_layout_sugiyama_vertical_alignment. + * + * Returns the X coordinates for each vertex in `xs`. + * + * `graph` is the input graph, `layering` is the layering on which we operate. + * `hgap` is the preferred horizontal gap between vertices. + */ +static igraph_error_t igraph_i_layout_sugiyama_horizontal_compaction(const igraph_t* graph, + const igraph_vector_int_t* vertex_to_the_left, + const igraph_vector_int_t* roots, const igraph_vector_int_t* align, + igraph_real_t hgap, igraph_vector_t* xs) { + igraph_int_t i; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_t shifts, old_xs; + igraph_vector_int_t sinks; + igraph_real_t shift; + + /* Initialization */ + + IGRAPH_CHECK(igraph_vector_int_init_range(&sinks, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &sinks); + + IGRAPH_VECTOR_INIT_FINALLY(&shifts, no_of_nodes); + igraph_vector_fill(&shifts, IGRAPH_INFINITY); + + IGRAPH_VECTOR_INIT_FINALLY(&old_xs, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(xs, no_of_nodes)); + igraph_vector_fill(xs, -1); + + /* Calculate the coordinates of the vertices relative to their sinks + * in their own class. At the end of this for loop, xs will contain the + * relative displacement of a vertex from its sink, while the shifts list + * will contain the absolute displacement of the sinks. + * (For the sinks only, of course, the rest is undefined and unused) + */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*roots)[i] == i) { + igraph_i_layout_sugiyama_horizontal_compaction_place_block(i, + vertex_to_the_left, roots, align, &sinks, &shifts, hgap, xs); + } + } + + /* In "sinks", only those indices `i` matter for which `i` is in `roots`. + * All the other values will never be touched. + */ + + /* Calculate the absolute coordinates */ + IGRAPH_CHECK(igraph_vector_update(&old_xs, xs)); + for (i = 0; i < no_of_nodes; i++) { + igraph_int_t root = VECTOR(*roots)[i]; + VECTOR(*xs)[i] = VECTOR(old_xs)[root]; + shift = VECTOR(shifts)[VECTOR(sinks)[root]]; + if (shift < IGRAPH_INFINITY) { + VECTOR(*xs)[i] += shift; + } + } + + igraph_vector_int_destroy(&sinks); + igraph_vector_destroy(&shifts); + igraph_vector_destroy(&old_xs); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static void igraph_i_layout_sugiyama_horizontal_compaction_place_block(igraph_int_t v, + const igraph_vector_int_t* vertex_to_the_left, + const igraph_vector_int_t* roots, const igraph_vector_int_t* align, + igraph_vector_int_t* sinks, igraph_vector_t* shifts, + igraph_real_t hgap, igraph_vector_t* xs) { + igraph_int_t u, w; + igraph_int_t u_sink, v_sink; + + if (VECTOR(*xs)[v] >= 0) { + return; + } + + VECTOR(*xs)[v] = 0; + + w = v; + do { + /* Check whether vertex w is the leftmost in its own layer */ + u = VECTOR(*vertex_to_the_left)[w]; + if (u != w) { + /* Get the root of u (proceeding all the way upwards in the block) */ + u = VECTOR(*roots)[u]; + /* Place the block of u recursively */ + igraph_i_layout_sugiyama_horizontal_compaction_place_block(u, + vertex_to_the_left, roots, align, sinks, shifts, hgap, xs); + + u_sink = VECTOR(*sinks)[u]; + v_sink = VECTOR(*sinks)[v]; + /* If v is its own sink yet, set its sink to the sink of u */ + if (v_sink == v) { + VECTOR(*sinks)[v] = v_sink = u_sink; + } + /* If v and u have different sinks (i.e. they are in different classes), + * shift the sink of u so that the two blocks are separated by the + * preferred gap + */ + if (v_sink != u_sink) { + if (VECTOR(*shifts)[u_sink] > VECTOR(*xs)[v] - VECTOR(*xs)[u] - hgap) { + VECTOR(*shifts)[u_sink] = VECTOR(*xs)[v] - VECTOR(*xs)[u] - hgap; + } + } else { + /* v and u have the same sink, i.e. they are in the same class. Make sure + * that v is separated from u by at least hgap. + */ + if (VECTOR(*xs)[v] < VECTOR(*xs)[u] + hgap) { + VECTOR(*xs)[v] = VECTOR(*xs)[u] + hgap; + } + } + } + + /* Follow the alignment */ + w = VECTOR(*align)[w]; + } while (w != v); +} + +#undef IS_INNER_SEGMENT +#undef IS_DUMMY +#undef X_POS + +#ifdef SUGIYAMA_DEBUG + #undef SUGIYAMA_DEBUG +#endif diff --git a/src/layout/umap.c b/src/layout/umap.c new file mode 100644 index 0000000..de5627e --- /dev/null +++ b/src/layout/umap.c @@ -0,0 +1,1285 @@ +/* + igraph library. + Copyright (C) 2022-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_layout.h" + +#include "igraph_interface.h" +#include "igraph_lapack.h" +#include "igraph_matrix.h" +#include "igraph_nongraph.h" +#include "igraph_random.h" +#include "igraph_vector_list.h" + +#include "layout/layout_internal.h" +#include "core/interruption.h" + +#include + +/* This file contains the implementation of the UMAP algorithm. + * + * UMAP is typically used as a to reduce dimensionality of vectors, embedding them in + * 2D (or, less commonly, in 3D). Despite this geometric flair, UMAP heavily relies on + * graphs as intermediate data structures and is therefore a useful graph layout + * algorithm in its own right. Conceptually, there are three steps: + * + * 1. Compute a sparse graph with edges connecting similar vectors, e.g. a k-nearest + * neighbor graph. A vector of distances is associated with the graph edges. This + * file does *not* perform this part of the computation since there are many + * libraries out there that can compute knn or other sparse graphs efficiently + * starting from vector spaces (e.g. faiss). + * 2. Convert the distances into weights, which are weights between 0 and 1 + * that are larger for short-distance edges. This step is exposed via + * igraph_layout_umap_compute_weights. + * 3. Compute a layout for the graph, using its associated weights as edge + * weights. This step is exposed via igraph_layout_umap and its 3D counterpart. + * These two functions can also compute steps 2 and 3 in one go, since that's the + * most common use case: the argument "distances_are_weights" should be + * set to false. + * + * A few more details w/r/t steps 2 and 3, since they are computed in detail below. + * + * STEP 2 + * For each vertex, the distance to its closest neighbor, called rho, is "forfeited": + * that edge begets weight 1 (in principle, at least). Farther neighbors beget + * lower weights according to an exponential decay. The scale factor of this + * decay is called sigma and is computed from the graph itself. + * + * STEP 3 + * The layout is computed via stochastic gradient descent, i.e. applying stochastic + * forces along high-weight edges and, more rarely, low-weight edges. + * To compute the stochastic forces, one needs a smooth function that approximates + * weights but in the embedded space: + * Q(d) = ( 1 + a*d^2b )^-1 + * where d is the 2D/3D distance between the vertices and a and b are constants that + * are computed globally based on a user-chosen fudge parameter called min_dist. + * Smaller min_dist will give rise to slightly more compact embeddings. We find a + * and b via gradient descent, which is implemented de novo below. + * + * Repulsion is computed via negative sampling, typically a few nodes are picked + * at random as repulsive sources each time an attractive force is computed. + * + * During the stochastic gradient descent, the learning rate - a multiplicative factor + * on top of the stochastic forces themselves - is reduced linearly from 1 to 0. At + * the end, the stochastic forces can be strong but their effect is reduced to almost + * nothing by the small learning rate. Notice that UMAP does not formally converge: + * instead, we reduce the forces' impact steadily to a trickle and finally quench it + * altogether. + * + * FINAL COMMENTS + * This implementation uses a few more tricks to improve the result: + * - a few constants are defined to limit the force applied to vertices at each step + * and other geometric corrections + * - the layout is centered at the end of the computation. + * - a seed layout can be used. Notice that since UMAP runs for an essentially fixed + * time rather than until convergence, using a good/bad seed does not affect + * runtimes significantly. + * */ +#define UMAP_FORCE_LIMIT 4 +#define UMAP_MIN_DISTANCE_ATTRACTION 0.0001 +#define UMAP_CORRECT_DISTANCE_REPULSION 0.01 + +/* Find sigma for this vertex by binary search */ +static igraph_error_t igraph_i_umap_find_sigma(const igraph_vector_t *distances, + const igraph_vector_int_t *eids, + igraph_real_t rho, igraph_real_t *sigma_p, igraph_real_t target) { + + igraph_real_t sigma = 1; + igraph_real_t sum; + igraph_real_t tol = 0.01; + igraph_int_t maxiter = 100; + igraph_int_t no_of_neis = igraph_vector_int_size(eids); + igraph_int_t eid; + igraph_real_t step = sigma; + igraph_int_t seen_max = 0; + + /* Binary search */ + for (igraph_int_t iter = 0; iter < maxiter; iter++) { + sum = 0; + for (igraph_int_t j = 0; j < no_of_neis; j++) { + eid = VECTOR(*eids)[j]; + sum += exp(-(VECTOR(*distances)[eid] - rho) / sigma); + } + +#ifdef UMAP_DEBUG + printf("SIGMA function (no_of_neis = %" IGRAPH_PRId ")- sum: %g, " + "target: %g, rho: %g, sigma: %g\n", no_of_neis, sum, target, rho, sigma); +#endif + + if (sum < target) { + /* going back up after having seen an upper bound */ + if (seen_max == 1) { + step /= 2; + /* we need to go up but have not seen an upper bound yet + * first iteration we want to increase by sigma, else we must come from + * below, so we are sitting at 2 * step, we want to move to 4 * step */ + } else if (iter > 0) { + step *= 2; + } + sigma += step; + /* overshooting, we have definitely seen the max */ + } else { + seen_max = 1; + step /= 2; + sigma -= step; + } + + /* Check for convergence */ + if (fabs(sum - target) < tol) { + break; + } + } + + *sigma_p = sigma; + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_layout_umap_compute_weights + * \brief Compute weights for a UMAP layout starting from distances. + * + * \experimental + * + * UMAP is used to embed high-dimensional vectors in a low-dimensional space + * (most commonly 2D). It uses a distance graph as an intermediate data structure, + * making it also a useful graph layout algorithm. See \ref igraph_layout_umap() + * for more information. + * + * + * An early step in UMAP is to compute exponentially decaying "weights" from the + * distance graph. Connectivities can also be viewed as edge weights that quantify + * similarity between two vertices. This function computes weights from the + * distance graph. To compute the layout from precomputed weights, call + * \ref igraph_layout_umap() with the \p distances_are_weights argument set to \c true. + * + * + * While the distance graph can be directed (e.g. in a k-nearest neighbors, it is + * clear \em whom you are a neighbor of), the weights are usually undirected. Whenever two + * vertices are doubly connected in the distance graph, the resulting weight \c W is set as: + * + * + * W = W1 + W2 - W1 * W2 + * + * Because UMAP weights are interpreted as probabilities, this is just the probability + * that either edge is present, without double counting. It is called "fuzzy union" in + * the original UMAP implementation and is the default. One could also require that both + * edges are there, i.e. W = W1 * W2: this would represent the fuzzy intersection and is + * not implemented in igraph. As a consequence of this symmetrization, information is lost, + * i.e. one needs fewer weights than one had distances. To keep things efficient, here + * we set the weight for one of the two edges as above and the weight for its opposite edge + * as 0, so that it will be skipped in the UMAP gradient descent later on. + * + * + * Technical note: For each vertex, this function computes its scale factor (sigma), + * its connectivity correction (rho), and finally the weights themselves. + * + * + * References: + * + * + * Leland McInnes, John Healy, and James Melville: + * UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction (2020) + * https://arxiv.org/abs/1802.03426 + * + * \param graph Pointer to the distance graph. This can be directed (e.g. connecting + * each vertex to its neighbors in a k-nearest neighbor) or undirected, but must + * have no loops nor parallel edges. The only exception is: if the graph is directed, + * having pairs of edges with opposite direction is accepted. + * \param distances Pointer to the vector with the vertex-to-vertex distance associated with + * each edge. This argument can be NULL, in which case all edges are assumed to have the + * same distance. + * \param weights Pointer to an initialized vector where the result will be stored. If the + * input graph is directed, the weights represent a symmetrized version which contains + * less information. Therefore, whenever two edges between the same vertices and opposite + * direction are present in the input graph, only one of the weights is set and the other + * is fixed to zero. That format is accepted by \ref igraph_layout_umap(), which skips + * all zero-weight edges from the layout optimization. + * \return Error code. + * + * \sa \ref igraph_layout_umap(), \ref igraph_layout_umap_3d() + */ +igraph_error_t igraph_layout_umap_compute_weights( + const igraph_t *graph, + const igraph_vector_t *distances, + igraph_vector_t *weights) { + + igraph_int_t no_of_vertices = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_neis; + igraph_vector_int_t eids; + igraph_vector_int_list_t neighbors_seen; + igraph_vector_list_t weights_seen; + igraph_vector_int_t* neighbors_seen_elt; + igraph_vector_t* weights_seen_elt; + igraph_real_t rho, dist_max, dist, sigma, weight, weight_inv, sigma_target, dist_min; + + /* reserve memory for the weights */ + IGRAPH_CHECK(igraph_vector_resize(weights, no_of_edges)); + + /* UMAP is sometimes used on unweighted graphs, otherwise check distance vector. */ + if (distances != NULL) { + if (igraph_vector_size(distances) != no_of_edges) { + IGRAPH_ERROR("Distances must be the same number as the edges in the graph.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + dist_min = igraph_vector_min(distances); + if (dist_min < 0) { + IGRAPH_ERROR("Distance values must not be negative.", IGRAPH_EINVAL); + } else if (isnan(dist_min)) { + IGRAPH_ERROR("Distance values must not be NaN.", IGRAPH_EINVAL); + } + } + } + + /* Initialize auxiliary vectors */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 0); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&neighbors_seen, no_of_vertices); + IGRAPH_VECTOR_LIST_INIT_FINALLY(&weights_seen, no_of_vertices); + + /* Iterate over vertices x, like in the paper */ + for (igraph_int_t i = 0; i < no_of_vertices; i++) { + /* Edges out of this vertex, e.g. to its k-nearest neighbors */ + IGRAPH_CHECK(igraph_incident(graph, &eids, i, IGRAPH_OUT, IGRAPH_LOOPS)); + no_of_neis = igraph_vector_int_size(&eids); + + /* Vertex has no neighbors */ + if (no_of_neis == 0) { + continue; + } + + /* Find rho for this vertex, i.e. the minimal non-self distance */ + if (distances != NULL) { + rho = VECTOR(*distances)[VECTOR(eids)[0]]; + dist_max = rho; + for (igraph_int_t j = 1; j < no_of_neis; j++) { + const igraph_int_t eid = VECTOR(eids)[j]; + dist = VECTOR(*distances)[eid]; + rho = fmin(rho, dist); + dist_max = fmax(dist_max, dist); + } + } else { + rho = dist_max = 0; + } + + /* If the maximal distance is rho, all neighbors are identical to + * each other. This can happen e.g. if distances == NULL. */ + if (dist_max == rho) { + /* This is a special flag for later on */ + sigma = -1; + + /* Else, find sigma for this vertex, from its rho plus binary search */ + } else { + sigma_target = log2(no_of_neis); + IGRAPH_CHECK(igraph_i_umap_find_sigma(distances, + &eids, rho, &sigma, + sigma_target)); + } + + /* Convert to weights */ + for (igraph_int_t j = 0; j < no_of_neis; j++) { + const igraph_int_t eid = VECTOR(eids)[j]; + + /* Basically, nodes closer than rho have probability 1, the rest is + * exponentially penalized keeping rough cardinality */ + weight = sigma < 0 ? 1 : exp(-(VECTOR(*distances)[eid] - rho) / sigma); + + #ifdef UMAP_DEBUG + if (distances != NULL) + printf("distance: %g\n", VECTOR(*distances)[eid]); + printf("weight: %g\n", weight); + #endif + + /* Store in vector lists for later symmetrization */ + const igraph_int_t k = IGRAPH_OTHER(graph, eid, i); + if (k == i) { + IGRAPH_ERROR("Input graph must contain no self-loops.", IGRAPH_EINVAL); + } + + neighbors_seen_elt = igraph_vector_int_list_get_ptr(&neighbors_seen, i); + IGRAPH_CHECK(igraph_vector_int_push_back(neighbors_seen_elt, k)); + + weights_seen_elt = igraph_vector_list_get_ptr(&weights_seen, i); + IGRAPH_CHECK(igraph_vector_push_back(weights_seen_elt, weight)); + } + + } + + /* Symmetrize the weights. UMAP weights are probabilities of that edge being a + * "real" connection. Unlike the distances, which can represent a directed graph, + * weights are usually symmetric. We symmetrize via fuzzy union. */ + for (igraph_int_t eid=0; eid < no_of_edges; eid++) { + const igraph_int_t i = IGRAPH_FROM(graph, eid); + const igraph_int_t k = IGRAPH_TO(graph, eid); + + /* Direct weight, if found */ + /* NOTE: this and the subsequent loop could be faster if we sorted the vectors + * beforehand. Probably not such a big deal. */ + weight = 0; + neighbors_seen_elt = igraph_vector_int_list_get_ptr(&neighbors_seen, i); + weights_seen_elt = igraph_vector_list_get_ptr(&weights_seen, i); + no_of_neis = igraph_vector_int_size(neighbors_seen_elt); + for (igraph_int_t l=0; l < no_of_neis; l++) { + if (VECTOR(*neighbors_seen_elt)[l] == k) { + weight = VECTOR(*weights_seen_elt)[l]; + /* Tag this weight so we can ignore it later on if the opposite + * directed edge is found. It's ok to retag */ + VECTOR(*weights_seen_elt)[l] = -1; + break; + } + } + + /* The opposite edge has already been union-ed, set this one to -1 */ + if (weight < 0) { + VECTOR(*weights)[eid] = 0; + continue; + } + + /* Weight of the opposite edge, if found */ + weight_inv = 0; + neighbors_seen_elt = igraph_vector_int_list_get_ptr(&neighbors_seen, k); + weights_seen_elt = igraph_vector_list_get_ptr(&weights_seen, k); + no_of_neis = igraph_vector_int_size(neighbors_seen_elt); + for (igraph_int_t l=0; l < no_of_neis; l++) { + if (VECTOR(*neighbors_seen_elt)[l] == i) { + weight_inv = VECTOR(*weights_seen_elt)[l]; + /* Tag this weight so we can ignore it later on if the opposite + * directed edge is found. It's ok to retag */ + VECTOR(*weights_seen_elt)[l] = -1; + break; + } + } + + /* The opposite edge has already been union-ed, set this one to -1 */ + if (weight_inv < 0) { + VECTOR(*weights)[eid] = 0; + continue; + } + + /* First time this edge or its opposite are seen, set the W */ + VECTOR(*weights)[eid] = weight + weight_inv - weight * weight_inv; + } + + igraph_vector_list_destroy(&weights_seen); + igraph_vector_int_list_destroy(&neighbors_seen); + igraph_vector_int_destroy(&eids); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Helper function to compute a and b parameters (smoothing probability metric in embedding space) */ +static igraph_error_t igraph_i_umap_get_ab_residuals(igraph_vector_t *residuals, + igraph_real_t *squared_sum_res, igraph_int_t nr_points, igraph_real_t a, + igraph_real_t b, igraph_vector_t *powb, const igraph_vector_t *x, igraph_real_t min_dist) +{ + igraph_real_t tmp; + + *squared_sum_res = 0; + for (igraph_int_t i = 0; i < nr_points; i++) { + /* The ideal probability is: + * + * P(d) = d < min_dist ? 1 : e^{-(d - min_dist)} + * + * which is the same as the high-dimensional probability, except + * min_dist plays the role of rho and sigma is fixed at 1. However, + * this function has a kink at min_dist (first derivative is not + * continuous). So we smoothen it with: + * + * Q(d) = ( 1 + a*d^2b )^-1 + * + * which is quite similar throughout for appropriate a and b. Notice + * that we do not need to smoothen the high-dimensional probability + * function because the vertices are not moved in the high-dimensional + * space, so there is no need for differentiating that function. + * + * The residual is of course: + * + * Q(d) - P(d) = ( 1 + a*d^2b )^-1 - [ d < min_dist ? 1 : e^{-(d - min_dist)} ] + * + * This function also sets the auxiliary vector powb. + * */ + VECTOR(*powb)[i] = pow(VECTOR(*x)[i], 2 * b); + tmp = 1 / (1 + a * VECTOR(*powb)[i]); + tmp -= VECTOR(*x)[i] <= min_dist ? 1 : exp(-(VECTOR(*x)[i] - min_dist)); + VECTOR(*residuals)[i] = tmp; + *squared_sum_res += tmp * tmp; + } + return IGRAPH_SUCCESS; +} + +/* UMAP minimizes the cross-entropy between probability of being a true edge in + * high and low dimensions. For the low-dimensional computation, it uses a smooth + * function of the Euclidean distance between two vertices: + * + * P(d) = (1 + a*d^2b)^-1 + * + * where d is the distance and a and b are hyperparameters that basically determine + * the cutoff distance at which the probability starts to decrease. + * + * We fit these two parameters using nonlinear least squares (Gauss-Newton + line search) + * on a grid of artificial distances. There is only one user-chosen input argument that + * determines this fit, called min_dist, which is approximately the cutoff distance we + * are trying to achieve. + * + * ADVANCED NOTE: + * In a way, the whole UMAP layout is invariant upon scaling transformations, of course, + * so min_dist is basically meaningless. Another way to see this is that for any pair + * (a,b) that minimize the least squares for dist_min, we can easily find a solution for + * a new dist_min2 := alpha * dist_min: + * + * P(d, a, b) = (1 + a*d^2b)^-1 + * + * P(alpha * d, a', b') = (1 + a'*(alpha * d)^2b' )^-1 + * + * that is: + * + * a*d^2b = a'*alpha^2b'*d^2b' for each d >= 0. + * + * So for d = 1 -> a = a'*alpha^2b' + * and for d = sqrt(2) -> a*2^b = a'*alpha^2b'*2^b' + * + * which solves as: + * + * b' = b + * a' = a / alpha^2b + * + * For instance, if b = 1, a -> 0.01*a moves the fit a decade towards larger min_dist, + * and a -> 100*a moves the fit a decade towards smaller min_dist. + * */ +igraph_error_t igraph_i_umap_fit_ab(igraph_real_t min_dist, igraph_real_t *a_p, igraph_real_t *b_p) +{ + /* Grid points */ + igraph_vector_t x; + /* Make a lattice from 0 to 3 * sigma with 300 points. This is what + * umap.umap_.fit_ab_params does, but sigma is fixed to 1.0 here since + * that's the default value used in scanpy and by virtually everyone */ + igraph_int_t nr_points = 300; + igraph_real_t end_point = 3.0; + /* Initial values takes as reasonable assumptions from typical min_dist values */ + igraph_real_t b = 0.8; + igraph_real_t a = 1.8; + /* deltas */ + igraph_real_t da, db; + /* Residuals */ + igraph_vector_t residuals; + igraph_real_t squared_sum_res, squared_sum_res_old, squared_sum_res_tmp; + /* Needed for the Gauss-Newton search */ + igraph_matrix_t jacobian, jTj, jTr; + igraph_real_t tol = 0.001; + igraph_real_t maxiter = 100; + /* Auxiliary vars */ + igraph_real_t tmp; + igraph_vector_t powb; + int lapack_info; + + /* Distance lattice */ + IGRAPH_VECTOR_INIT_FINALLY(&x, nr_points); + /* Residuals */ + IGRAPH_VECTOR_INIT_FINALLY(&residuals, nr_points); + /* First derivatives, for the fitting (direction) */ + IGRAPH_MATRIX_INIT_FINALLY(&jacobian, nr_points, 2); + /* Composite matrices/vectors for linear least squares at each iteration */ + IGRAPH_MATRIX_INIT_FINALLY(&jTj, 2, 2); + IGRAPH_MATRIX_INIT_FINALLY(&jTr, 2, 1); + /* Auxiliary vars for convenience */ + IGRAPH_VECTOR_INIT_FINALLY(&powb, nr_points); + + /* Distance |x-y| (this is a lattice, there are no actual x and y) */ + for (igraph_int_t i = 0; i < nr_points; i++) { + VECTOR(x)[i] = (end_point / nr_points) * i + 0.001; /* added a 0.001 to prevent NaNs */ + } + + /* Initialize squared_sum_res_old to a dummy value to prevent some compilers + * from complaining about uninitialized values */ + squared_sum_res_old = IGRAPH_INFINITY; + +#ifdef UMAP_DEBUG + printf("start fit_ab\n"); +#endif + for (igraph_int_t iter = 0; iter < maxiter; iter++) { + IGRAPH_CHECK(igraph_i_umap_get_ab_residuals(&residuals, &squared_sum_res, nr_points, a, b, + &powb, &x, min_dist)); + + /* break if good fit (conergence to truth) */ + if (squared_sum_res < tol * tol) { +#ifdef UMAP_DEBUG + printf("convergence to zero (wow!)\n"); +#endif + break; + } + /* break if no change (convergence) */ + if ((iter > 0) && fabs(sqrt(squared_sum_res_old) - sqrt(squared_sum_res)) < tol) { +#ifdef UMAP_DEBUG + printf("no-change absolute convergence\n"); +#endif + break; + } + + /* Jacobian (first derivatives) of squared residuals at (a, b) */ + for (igraph_int_t i = 0; i < nr_points; i++) { + tmp = 1 + a * VECTOR(powb)[i]; + MATRIX(jacobian, i, 0) = - 2 * VECTOR(powb)[i] / tmp / tmp; + MATRIX(jacobian, i, 1) = MATRIX(jacobian, i, 0) * a * log(VECTOR(x)[i]) * 2; + } + + /* At each iteration, we want to minimize the linear approximation of the sum of squared + * residuals: + * + * sum_i (Ji @ d(a,b) -r_i)^2 + * + * Putting the first derivative to zero results in a linear system of 2 equations + * (for a and b): + * + * sum_i J_i^T @ J_i @ d(a,b) = sum_i J_i^T r_i + * * + * or more compactly: + * + * J^T @ J @ d(a,b) = J^T @ r + * + * where J_T is the transpose of the Jacobian. Defining A := J^T @ J, B = J^T @ r: + * + * A @ d(a,b) = B + * + * This can be solved for d(a,b) using LAPACK within igraph + * */ + /* Compute A and B, i.e. J^T @ J and J^T @ r */ + MATRIX(jTj, 0, 0) = MATRIX(jTj, 0, 1) = MATRIX(jTj, 1, 0) = MATRIX(jTj, 1, 1) = 0; + MATRIX(jTr, 0, 0) = MATRIX(jTr, 1, 0) = 0; + for (igraph_int_t i = 0; i < nr_points; i++) { + for (igraph_int_t j1 = 0; j1 < 2; j1++) { + for (igraph_int_t j2 = 0; j2 < 2; j2++) { + MATRIX(jTj, j1, j2) += MATRIX(jacobian, i, j1) * MATRIX(jacobian, i, j2); + } + MATRIX(jTr, j1, 0) += MATRIX(jacobian, i, j1) * VECTOR(residuals)[i]; + } + } + /* LAPACK puts solution into jTr */ + IGRAPH_CHECK(igraph_lapack_dgesv(&jTj, NULL, &jTr, &lapack_info)); + + /* This might go wrong, in which case we should fail graciously */ + if (lapack_info != 0) { + IGRAPH_ERROR("Singular matrix in the estimation of a and b for UMAP", IGRAPH_EINVAL); + } + + da = -MATRIX(jTr, 0, 0); + db = -MATRIX(jTr, 1, 0); + + /* Improvement over GN: rough exponential line search for best delta + * start from largest change, and keep shrinking as long as we are going down + * */ + squared_sum_res_old = squared_sum_res; + IGRAPH_CHECK(igraph_i_umap_get_ab_residuals(&residuals, &squared_sum_res, nr_points, a + da, + b + db, &powb, &x, min_dist)); + +#ifdef UMAP_DEBUG + printf("start line search, SSR before delta: %g, current SSR:, %g\n", squared_sum_res_old, + squared_sum_res); +#endif + for (igraph_int_t k = 0; k < 30; k++) { + /* Try new parameters */ + da /= 2.0; + db /= 2.0; + squared_sum_res_tmp = squared_sum_res; + IGRAPH_CHECK(igraph_i_umap_get_ab_residuals(&residuals, &squared_sum_res, nr_points, + a + da, b + db, &powb, &x, min_dist)); + + /* Compare and if we are going back uphill, undo last step and break */ +#ifdef UMAP_DEBUG + printf("during line search, k = %" IGRAPH_PRId ", old SSR:, %g, new SSR (half a,b):, %g\n", k, + squared_sum_res_tmp, squared_sum_res); +#endif + if (squared_sum_res > squared_sum_res_tmp - tol) { + da *= 2; + db *= 2; + break; + } + } +#ifdef UMAP_DEBUG + printf("end of line search and iteration, squared_sum_res: %g \n\n", squared_sum_res_tmp); +#endif + + /* assign a, b*/ + a += da; + b += db; + + } + + /* Free memory and tidy up stack */ + igraph_vector_destroy(&powb); + igraph_matrix_destroy(&jTr); + igraph_matrix_destroy(&jTj); + igraph_matrix_destroy(&jacobian); + igraph_vector_destroy(&residuals); + igraph_vector_destroy(&x); + IGRAPH_FINALLY_CLEAN(6); + +#ifdef UMAP_DEBUG + printf("a, b: %g %g\n", a, b); +#endif + + *a_p = a; + *b_p = b; + + return IGRAPH_SUCCESS; + +} + +/* cross-entropy */ +#ifdef UMAP_DEBUG +static igraph_error_t igraph_i_umap_compute_cross_entropy(const igraph_t *graph, + const igraph_vector_t *umap_weights, const igraph_matrix_t *layout, igraph_real_t a, igraph_real_t b, + igraph_real_t *cross_entropy) { + + igraph_real_t mu, nu, xd, yd, sqd; + igraph_int_t from, to; + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_vertices = igraph_vcount(graph); + igraph_matrix_t edge_seen; + + IGRAPH_MATRIX_INIT_FINALLY(&edge_seen, no_of_vertices, no_of_vertices); + + /* Measure the (variable part of the) cross-entropy terms for debugging: + * 1. - sum_edge_e mu(e) * log(nu(e)) + * 2. - sum_edge_e (1 - mu(e)) * log(1 - nu(e)) + * NOTE: the sum goes over the whole adjacency matrix, i.e. all potential edges, + * not just the actual edges. That is because otherwise there's no benefit from + * repelling unconnected edges. + * */ + *cross_entropy = 0; + for (igraph_int_t eid = 0; eid < no_of_edges; eid++) { + mu = VECTOR(*umap_weights)[eid]; + + /* Find vertices */ + from = IGRAPH_FROM(graph, eid); + to = IGRAPH_TO(graph, eid); + /* Find distance in layout space */ + xd = (MATRIX(*layout, from, 0) - MATRIX(*layout, to, 0)); + yd = (MATRIX(*layout, from, 1) - MATRIX(*layout, to, 1)); + sqd = xd * xd + yd * yd; + /* Find probability associated with distance using fitted Phi */ + nu = 1.0 / (1 + a * pow(sqd, b)); + + /* Term 1: entropy from the edges */ + if (mu > 0) + *cross_entropy -= mu * log(nu); + /* Term 2: entropy from the missing edges */ + if (mu < 1) + *cross_entropy -= (1 - mu) * log(1 - nu); + + MATRIX(edge_seen, from, to) = MATRIX(edge_seen, to, from) = 1; + } + /* Add the entropy from the missing edges */ + for (igraph_int_t from = 0; from < no_of_vertices; from++) { + for (igraph_int_t to = 0; to < from; to++) { + if (MATRIX(edge_seen, from, to) > 0) { + continue; + } + + /* Find distance in layout space */ + xd = (MATRIX(*layout, from, 0) - MATRIX(*layout, to, 0)); + yd = (MATRIX(*layout, from, 1) - MATRIX(*layout, to, 1)); + sqd = xd * xd + yd * yd; + + /* Find probability associated with distance using fitted Phi */ + nu = 1.0 / (1 + a * pow(sqd, b)); + + /* Term 2*/ + *cross_entropy -= log(1 - nu); + + MATRIX(edge_seen, from, to) = MATRIX(edge_seen, to, from) = 1; + } + } + + igraph_matrix_destroy(&edge_seen); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} +#endif /* UMAP_DEBUG */ + + +/* clip forces to avoid too rapid shifts */ +static IGRAPH_FUNCATTR_CONST igraph_real_t igraph_i_umap_clip_force(igraph_real_t force, igraph_real_t limit) { + return force > limit ? limit : (force < -limit ? -limit : force); +} + +static IGRAPH_FUNCATTR_CONST igraph_real_t igraph_i_umap_attract( + igraph_real_t dsq, + igraph_real_t a, + igraph_real_t b) +{ + return - (2 * a * b * pow(dsq, b - 1.)) / (1. + a * pow(dsq, b)); +} + +static IGRAPH_FUNCATTR_CONST igraph_real_t igraph_i_umap_repel( + igraph_real_t dsq, + igraph_real_t a, + igraph_real_t b) +{ + igraph_real_t dsq_min = UMAP_CORRECT_DISTANCE_REPULSION * UMAP_CORRECT_DISTANCE_REPULSION; + + return (2 * b) / (dsq_min + dsq) / (1. + a * pow(dsq, b)); +} + +static igraph_error_t igraph_i_umap_apply_forces( + const igraph_t *graph, + const igraph_vector_t *umap_weights, + igraph_matrix_t *layout, + igraph_real_t a, + igraph_real_t b, + igraph_real_t learning_rate, + igraph_bool_t avoid_neighbor_repulsion, + igraph_int_t negative_sampling_rate, + igraph_int_t epoch, + igraph_vector_t *next_epoch_sample_per_edge) +{ + const igraph_int_t no_of_vertices = igraph_matrix_nrow(layout); + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_int_t ndim = igraph_matrix_ncol(layout); + igraph_vector_t from_emb, to_emb, delta; + + /* The following is only used for small graphs, to avoid repelling your neighbors + * For large sparse graphs, it's not necessary. For large dense graphs, you should + * not be doing UMAP. */ + igraph_vector_int_t neis, negative_vertices; + const igraph_int_t n_negative_vertices = + (no_of_vertices - 1 < negative_sampling_rate) ? (no_of_vertices - 1) : negative_sampling_rate; + + /* Initialize vectors */ + IGRAPH_VECTOR_INIT_FINALLY(&from_emb, ndim); + IGRAPH_VECTOR_INIT_FINALLY(&to_emb, ndim); + IGRAPH_VECTOR_INIT_FINALLY(&delta, ndim); + IGRAPH_VECTOR_INT_INIT_FINALLY(&negative_vertices, 0); + if (avoid_neighbor_repulsion) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + } + + /* Iterate over edges. Stronger edges are sampled more often */ + for (igraph_int_t eid = 0; eid < no_of_edges; eid++) { + igraph_int_t from, to; + igraph_real_t force, dsq, force_d; + + /* Zero-weight edges do not affect vertex positions. They can + * also emerge during the weight symmetrization. */ + if (VECTOR(*umap_weights)[eid] <= 0) { + continue; + } + + /* We sample all and only edges that are supposed to be moved at this time */ + if ((VECTOR(*next_epoch_sample_per_edge)[eid] - epoch) >= 1) { + continue; + } + + /* set next epoch at which this edge will be sampled */ + VECTOR(*next_epoch_sample_per_edge)[eid] += 1.0 / VECTOR(*umap_weights)[eid]; + + /* we move all vertices on one end of the edges, then we come back for + * the vertices on the other end. This way we don't move both ends at the + * same time, which is almost a wasted move since they attract each other */ + int swapflag = (int)(RNG_BOOL()); + int swapflag_end = swapflag + 2; + for (; swapflag < swapflag_end; swapflag++) { + + /* half the time, swap the from/to, otherwise some vertices are never moved. + * This has to do with the graph representation within igraph */ + if (swapflag % 2) { + from = IGRAPH_FROM(graph, eid); + to = IGRAPH_TO(graph, eid); + } else { + to = IGRAPH_FROM(graph, eid); + from = IGRAPH_TO(graph, eid); + } + + + /* Current coordinates of both vertices */ + dsq = 0; + for (igraph_int_t d = 0; d != ndim; d++) { + VECTOR(from_emb)[d] = MATRIX(*layout, from, d); + VECTOR(to_emb)[d] = MATRIX(*layout, to, d); + VECTOR(delta)[d] = MATRIX(*layout, from, d) - MATRIX(*layout, to, d); + dsq += VECTOR(delta)[d] * VECTOR(delta)[d]; + } + + /* Apply attractive force since they are neighbors */ + /* NOTE: If they are already together, no force needed */ + if (dsq >= UMAP_MIN_DISTANCE_ATTRACTION * UMAP_MIN_DISTANCE_ATTRACTION) { + force = igraph_i_umap_attract(dsq, a, b); + for (igraph_int_t d = 0; d != ndim; d++) { + force_d = force * VECTOR(delta)[d]; + /* clip force to avoid too rapid change */ + force_d = igraph_i_umap_clip_force(force_d, UMAP_FORCE_LIMIT); + + #ifdef UMAP_DEBUG + fprintf(stderr, "force attractive: delta[%" IGRAPH_PRId "] = %g, forces[%" IGRAPH_PRId "] = %g\n", d, VECTOR(delta)[d], d, force_d); + #endif + + MATRIX(*layout, from, d) += learning_rate * force_d; + } + } + + /* Random other nodes repel the focal vertex */ + IGRAPH_CHECK(igraph_random_sample(&negative_vertices, + 0, no_of_vertices - 2, n_negative_vertices)); + for (igraph_int_t j = 0; j < n_negative_vertices; j++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Get random neighbor */ + to = VECTOR(negative_vertices)[j]; + /* obviously you cannot repel yourself */ + if (to >= from) { + to++; + } + + /* do not repel neighbors for small graphs, for big graphs this + * does not matter as long as the k in knn << number of vertices */ + if (avoid_neighbor_repulsion) { + /* NOTE: the efficiency of this step could be improved but it + * should be only used for small graphs anyway, so it's fine */ + igraph_bool_t skip = false; + IGRAPH_CHECK(igraph_incident(graph, &neis, from, IGRAPH_ALL, IGRAPH_LOOPS)); + const igraph_int_t nneis = igraph_vector_int_size(&neis); + for (igraph_int_t k = 0; k < nneis; k++) { + igraph_int_t eid2 = VECTOR(neis)[k]; + igraph_int_t from2, to2; + from2 = IGRAPH_FROM(graph, eid2); + to2 = IGRAPH_TO(graph, eid2); + if (((from2 == from) && (to2 == to)) || ((from2 == to) && (from == to2))) { + skip = true; + break; + } + } + if (skip) { + continue; + } + } + + /* Get layout of random neighbor and gradient in embedding */ + dsq = 0; + for (igraph_int_t d = 0; d != ndim; d++) { + VECTOR(to_emb)[d] = MATRIX(*layout, to, d); + VECTOR(delta)[d] = MATRIX(*layout, from, d) - MATRIX(*layout, to, d); + dsq += VECTOR(delta)[d] * VECTOR(delta)[d]; + } + + /* This repels the other vertex assuming it's a negative example + * that is no weight, no edge */ + force = igraph_i_umap_repel(dsq, a, b); + /* The repulsive force is already *away* from the other (non-neighbor) vertex */ + for (igraph_int_t d = 0; d != ndim; d++) { + force_d = force * VECTOR(delta)[d]; + + /* clip force to avoid too rapid change */ + force_d = igraph_i_umap_clip_force(force_d, UMAP_FORCE_LIMIT); + + #ifdef UMAP_DEBUG + fprintf(stderr, "force repulsive: delta[%" IGRAPH_PRId "] = %g, forces[%" IGRAPH_PRId "] = %g\n", d, VECTOR(delta)[d], d, force_d); + #endif + + MATRIX(*layout, from, d) += learning_rate * force_d; + } + } + } + } + + /* Free vector of neighbors if needed */ + if (avoid_neighbor_repulsion) { + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Free vectors */ + igraph_vector_int_destroy(&negative_vertices); + igraph_vector_destroy(&delta); + igraph_vector_destroy(&to_emb); + igraph_vector_destroy(&from_emb); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/* Edges with heavier weight/higher probability should be sampled more often. In + * other words, vertices at each end of those edges should be moved more often. If the + * edge weight is 1.0, which happens to each nearest neighbor due to the correction via + * rho, that vertices at the end of that edge are moved each single epoch. Conversely, + * vertices at the end of weak edges can be moved only once in a while. */ +static igraph_error_t igraph_i_umap_optimize_layout_stochastic_gradient( + const igraph_t *graph, + const igraph_vector_t *umap_weights, + igraph_real_t a, + igraph_real_t b, + igraph_matrix_t *layout, + igraph_int_t epochs, + igraph_int_t negative_sampling_rate) { + + igraph_real_t learning_rate = 1; + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_t next_epoch_sample_per_edge; + +#ifdef UMAP_DEBUG + igraph_real_t cross_entropy, cross_entropy_old; +#endif + + IGRAPH_VECTOR_INIT_FINALLY(&next_epoch_sample_per_edge, no_of_edges); + + /* Explicit avoidance of neighbor repulsion, only useful in small graphs + * which are never very sparse. This is because negative sampling as implemented + * relies on an approximation that only works if the graph is sparse, which is never + * quite true for small graphs (i.e. |V| << |E| << |V|^2 is hard to judge if + * |V| is small) */ + igraph_bool_t avoid_neighbor_repulsion = false; + if (igraph_vcount(graph) < 100) { + avoid_neighbor_repulsion = true; + } + + /* Measure the (variable part of the) cross-entropy terms for debugging: + * 1. - sum_edge_e mu(e) * log(nu(e)) + * 2. + sum_edge_e (1 - mu(e)) * log(1 - nu(e)) + * The latter is approximated by negative sampling as: + * 2b. + sum_random_ij 1 * log(1 - nu_ij) + * whereby the mu = 0 because we assume there's no edge between i and j, and nu_ij + * is basically their distance in embedding space, lensed through the probability + * function Phi. + * */ +#ifdef UMAP_DEBUG + igraph_i_umap_compute_cross_entropy( + graph, umap_weights, layout, a, b, &cross_entropy); +#endif + + for (igraph_int_t e = 0; e < epochs; e++) { + /* Apply (stochastic) forces */ + IGRAPH_CHECK(igraph_i_umap_apply_forces( + graph, + umap_weights, + layout, + a, b, + learning_rate, + avoid_neighbor_repulsion, + negative_sampling_rate, + e, + &next_epoch_sample_per_edge)); + +#ifdef UMAP_DEBUG + /* Recompute CE and check how it's going*/ + cross_entropy_old = cross_entropy; + igraph_i_umap_compute_cross_entropy( + graph, umap_weights, layout, a, b, &cross_entropy); + + printf("Cross-entropy before shift: %g, after shift: %g\n", cross_entropy_old, cross_entropy); +#endif + + /* Adjust learning rate */ + learning_rate = 1.0 - (igraph_real_t)(e + 1) / epochs; + } + + igraph_vector_destroy(&next_epoch_sample_per_edge); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Center 2D layout around (0,0) at the end, just for convenience */ +static void igraph_i_umap_center_layout(igraph_matrix_t *layout) { + igraph_int_t no_of_vertices = igraph_matrix_nrow(layout); + igraph_real_t xm = 0, ym = 0; + + /* Compute center */ + for (igraph_int_t i = 0; i < no_of_vertices; i++) { + xm += MATRIX(*layout, i, 0); + ym += MATRIX(*layout, i, 1); + } + xm /= no_of_vertices; + ym /= no_of_vertices; + + /* Shift vertices */ + for (igraph_int_t i = 0; i < no_of_vertices; i++) { + MATRIX(*layout, i, 0) -= xm; + MATRIX(*layout, i, 1) -= ym; + } +} + +/* Center 3D layout around (0,0,0) at the end, just for convenience */ +static void igraph_i_umap_center_layout_3d(igraph_matrix_t *layout) { + igraph_int_t no_of_vertices = igraph_matrix_nrow(layout); + igraph_real_t xm = 0, ym = 0, zm = 0; + + /* Compute center */ + for (igraph_int_t i = 0; i < no_of_vertices; i++) { + xm += MATRIX(*layout, i, 0); + ym += MATRIX(*layout, i, 1); + zm += MATRIX(*layout, i, 2); + } + xm /= no_of_vertices; + ym /= no_of_vertices; + zm /= no_of_vertices; + + /* Shift vertices */ + for (igraph_int_t i = 0; i < no_of_vertices; i++) { + MATRIX(*layout, i, 0) -= xm; + MATRIX(*layout, i, 1) -= ym; + MATRIX(*layout, i, 2) -= zm; + } +} + + +/* This is the main function that works for any dimensionality of the embedding + * (currently hard-constrained to 2 or 3 ONLY in the initialization). */ +static igraph_error_t igraph_i_layout_umap( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_int_t ndim, + igraph_bool_t distances_are_weights) { + + const igraph_int_t no_of_edges = igraph_ecount(graph); + const igraph_int_t no_of_vertices = igraph_vcount(graph); + /* probabilities of each edge being a real connection */ + igraph_vector_t weights; + igraph_vector_t *weightsp; + /* The smoothing parameters given min_dist */ + igraph_real_t a, b; + /* How many repulsions for each attraction */ + igraph_int_t negative_sampling_rate = 5; + + /* Check input arguments */ + if (min_dist < 0) { + IGRAPH_ERRORF("Minimum distance must not be negative, got %g.", + IGRAPH_EINVAL, min_dist); + } + + if (epochs < 0) { + IGRAPH_ERRORF("Number of epochs must be non-negative, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, epochs); + } + + if ((ndim != 2) && (ndim != 3)) { + IGRAPH_ERRORF("Number of dimensions must be 2 or 3, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, ndim); + + } + + if (distances == NULL) { + distances_are_weights = false; + } + + /* Compute weights (exponential weights) from distances if required. + * If the weights have already been computed, they are stored in + * the "distances" vector and we can recycle the pointer. */ + if (distances_are_weights) { + weightsp = (igraph_vector_t *) distances; + } else { + IGRAPH_VECTOR_INIT_FINALLY(&weights, no_of_edges); + IGRAPH_CHECK(igraph_layout_umap_compute_weights( + graph, distances, &weights)); + weightsp = &weights; + } + /* From now on everything lives in probability space, it does not matter whether + * the original graph was weighted/distanced or unweighted */ + + /* Compute initial layout if required. If a seed layout is used, then just + * check that the dimensions of the layout make sense. */ + if (use_seed) { + if ((igraph_matrix_nrow(res) != no_of_vertices) || (igraph_matrix_ncol(res) != ndim)) { + if (!distances_are_weights) { + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_ERRORF("Seed layout should have %" IGRAPH_PRId " points in %" IGRAPH_PRId " dimensions, got %" IGRAPH_PRId " points in %" IGRAPH_PRId " dimensions.", + IGRAPH_EINVAL, no_of_vertices, ndim, + igraph_matrix_nrow(res), + igraph_matrix_ncol(res)); + } + + /* Trivial graphs (0 or 1 nodes) with seed - do nothing */ + if (no_of_vertices <= 1) { + if (!distances_are_weights) { + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(1); + } + return IGRAPH_SUCCESS; + } + } else { + /* Trivial graphs (0 or 1 nodes) beget trivial - but valid - layouts */ + if (no_of_vertices <= 1) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_vertices, ndim)); + igraph_matrix_null(res); + if (!distances_are_weights) { + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(1); + } + return IGRAPH_SUCCESS; + } + + /* Skip spectral embedding for now (see #1971), initialize at random */ + if (ndim == 2) { + igraph_layout_random(graph, res); + } else { + igraph_layout_random_3d(graph, res); + } + } + + /* Fit a and b parameter to find smooth approximation to + * probability distribution in embedding space */ + IGRAPH_CHECK(igraph_i_umap_fit_ab(min_dist, &a, &b)); + + /* Minimize cross-entropy between high-d and low-d probability + * distributions */ + IGRAPH_CHECK(igraph_i_umap_optimize_layout_stochastic_gradient( + graph, + weightsp, + a, b, + res, + epochs, + negative_sampling_rate)); + + if (!distances_are_weights) { + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Center layout */ + if (ndim == 2) { + igraph_i_umap_center_layout(res); + } else { + igraph_i_umap_center_layout_3d(res); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_umap + * \brief Layout using Uniform Manifold Approximation and Projection (UMAP). + * + * \experimental + * + * UMAP is mostly used to embed high-dimensional vectors in a low-dimensional space + * (most commonly 2D). The algorithm is probabilistic and introduces nonlinearities, + * unlike e.g. PCA and similar to T-distributed Stochastic Neighbor Embedding (t-SNE). + * Nonlinearity helps "cluster" very similar vectors together without imposing a + * global geometry on the embedded space (e.g. a rigid rotation + compression in PCA). + * UMAP uses graphs as intermediate data structures, hence it can be used as a + * graph layout algorithm as well. + * + * + * The general UMAP workflow is to start from vectors, compute a sparse distance + * graph that only contains edges between simiar points (e.g. a k-nearest neighbors + * graph), and then convert these distances into exponentially decaying weights + * between 0 and 1 that are larger for points that are closest neighbors in the + * distance graph. If a graph without any distances associated to the edges is used, + * all weights will be set to 1. + * + * + * If you are trying to use this function to embed high-dimensional vectors, you should + * first compute a k-nearest neighbors graph between your vectors and compute the + * associated distances, and then call this function on that graph. If you already + * have a distance graph, or you have a graph with no distances, you can call this + * function directly. If you already have a graph with meaningful weights + * associated to each edge, you can also call this function, but set the argument + * \p distances_are_weights to true. To compute weights from distances + * without computing the layout, see \ref igraph_layout_umap_compute_weights(). + * + * + * References: + * + * + * Leland McInnes, John Healy, and James Melville: + * UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction (2020) + * https://arxiv.org/abs/1802.03426 + * + * \param graph Pointer to the graph to find a layout for (i.e. to embed). This is + * typically a sparse graph with only edges for the shortest distances stored, e.g. + * a k-nearest neighbors graph. + * \param res Pointer to the n by 2 matrix where the layout coordinates will be stored. + * \param use_seed If \c true the supplied values in the \p res argument are + * used as an initial layout, if \c false a random initial layout is used. + * \param distances Pointer to a vector of distances associated with the graph edges. + * If this argument is \c NULL, all weights will be set to 1. + * \param min_dist A fudge parameter that decides how close two unconnected vertices + * can be in the embedding before feeling a repulsive force. It must not be + * negative. Typical values are between 0 and 1. + * \param epochs Number of iterations of the main stochastic gradient descent loop on + * the cross-entropy. Typical values are between 30 and 500. + * \param distances_are_weights Whether to use precomputed weights. If + * true, the \p distances vector contains precomputed weights. If \c false (the + * typical use case), this function will compute weights from distances and + * then use them to compute the layout. + * \return Error code. + * + * \sa \ref igraph_layout_umap_3d() + */ +igraph_error_t igraph_layout_umap(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights) { + return igraph_i_layout_umap(graph, res, use_seed, + distances, min_dist, epochs, 2, distances_are_weights); +} + + +/** + * \function igraph_layout_umap_3d + * \brief 3D layout using UMAP. + * + * \experimental + * + * This is the 3D version of the UMAP algorithm + * (see \ref igraph_layout_umap() for the 2D version). + * + * \param graph Pointer to the graph to find a layout for (i.e. to embed). This is + * typically a directed, sparse graph with only edges for the shortest distances + * stored, e.g. a k-nearest neighbors graph with the edges going from each focal + * vertex to its neighbors. However, it can also be an undirected graph. If the + * \p distances_are_weights is \c true, this is treated as an undirected graph. + * \param res Pointer to the n by 3 matrix where the layout coordinates will be stored. + * \param use_seed If true the supplied values in the \p res argument are used + * as an initial layout, if false a random initial layout is used. + * \param distances Pointer to a vector of distances associated with the graph edges. + * If this argument is \c NULL, all edges are assumed to have the same distance. + * \param min_dist A fudge parameter that decides how close two unconnected vertices + * can be in the embedding before feeling a repulsive force. It must not be + * negative. Typical values are between 0 and 1. + * \param epochs Number of iterations of the main stochastic gradient descent loop on + * the cross-entropy. Typical values are between 30 and 500. + * \param distances_are_weights Whether to use precomputed weights. If \c false (the + * typical use case), this function will compute weights from distances and + * then use them to compute the layout. If \c true, the \p distances vector contains + * precomputed weights, including possibly some weights equal to zero that are + * inconsequential for the layout optimization. + * \return Error code. + * + * \sa \ref igraph_layout_umap() + */ +igraph_error_t igraph_layout_umap_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + const igraph_vector_t *distances, + igraph_real_t min_dist, + igraph_int_t epochs, + igraph_bool_t distances_are_weights) { + return igraph_i_layout_umap(graph, res, use_seed, + distances, min_dist, epochs, 3, distances_are_weights); +} diff --git a/src/linalg/arpack.c b/src/linalg/arpack.c new file mode 100644 index 0000000..0877d08 --- /dev/null +++ b/src/linalg/arpack.c @@ -0,0 +1,1634 @@ +/* vim:set ts=4 sw=4 sts=4 noet: */ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_arpack.h" + +#include "core/interruption.h" +#include "linalg/arpack_internal.h" + +#include "igraph_memory.h" +#include "igraph_random.h" + +#include +#include +#include +#include + +/* The ARPACK example file dssimp.f is used as a template */ + +static igraph_arpack_error_t igraph_i_arpack_err_dsaupd(int error); +static igraph_arpack_error_t igraph_i_arpack_err_dseupd(int error); +static igraph_arpack_error_t igraph_i_arpack_err_dnaupd(int error); +static igraph_arpack_error_t igraph_i_arpack_err_dneupd(int error); + +static igraph_arpack_error_t last_arpack_error = IGRAPH_ARPACK_NO_ERROR; + +#define IGRAPH_ARPACK_ERROR(code) { \ + last_arpack_error = code; \ + IGRAPH_ERROR(igraph_arpack_error_to_string(code), IGRAPH_EARPACK); \ +} + +/* Pristine ARPACK options object that is not exposed to the user; this is used + * as a template for \c igraph_i_arpack_options_default when the user requests + * a pointer to the default object */ +static const igraph_arpack_options_t igraph_i_arpack_options_pristine = { + /* .bmat = */ { 'I' }, + /* .n = */ 0, + /* .which = */ { 'X', 'X' }, + /* .nev = */ 1, + /* .tol = */ 0, + /* .ncv = */ 0, /* 0 means "automatic" */ + /* .ldv = */ 0, + /* .ishift = */ 1, + /* .mxiter = */ 3000, + /* .nb = */ 1, + /* .mode = */ 1, + /* .start = */ 0, + /* .lworl = */ 0, + /* .sigma = */ 0, + /* .sigmai = */ 0, + /* .info = */ 0, + /* .ierr = */ 0, + /* .noiter = */ 0, + /* .nconv = */ 0, + /* .numop = */ 0, + /* .numopb = */ 0, + /* .numreo = */ 0, + /* .iparam = */ { + /* same as ishift: */ 1, + 0, + /* same as mxiter: */ 3000, + /* same as nb: */ 1, + 0, + 0, + /* same as mode: */ 1 + /* the rest are all zeros */ + }, + /* .ipntr = */ { 0 /* the rest are all zeros */ } +}; + +static IGRAPH_THREAD_LOCAL igraph_arpack_options_t igraph_i_arpack_options_default; + +/** + * \function igraph_arpack_options_init + * \brief Initialize ARPACK options. + * + * Initializes ARPACK options, set them to default values. + * You can always pass the initialized \ref igraph_arpack_options_t + * object to built-in igraph functions without any modification. The + * built-in igraph functions modify the options to perform their + * calculation, e.g. \ref igraph_pagerank() always searches for the + * eigenvalue with the largest magnitude, regardless of the supplied + * value. + * + * + * If you want to implement your own function involving eigenvalue + * calculation using ARPACK, however, you will likely need to set up + * the fields for yourself. + * + * \param o The \ref igraph_arpack_options_t object to initialize. + * + * Time complexity: O(1). + */ + +void igraph_arpack_options_init(igraph_arpack_options_t *o) { + *o = igraph_i_arpack_options_pristine; + + o->bmat[0] = 'I'; + o->n = 0; /* needs to be updated! */ + o->which[0] = 'X'; o->which[1] = 'X'; + o->nev = 1; + o->tol = 0; + o->ncv = 0; /* 0 means "automatic" */ + o->ldv = o->n; /* will be updated to (real) n */ + o->ishift = 1; + o->mxiter = 3000; + o->nb = 1; + o->mode = 1; + o->start = 0; + o->lworkl = 0; + o->sigma = 0; + o->sigmai = 0; + o->info = o->start; + + o->iparam[0] = o->ishift; o->iparam[1] = 0; o->iparam[2] = o->mxiter; o->iparam[3] = o->nb; + o->iparam[4] = 0; o->iparam[5] = 0; o->iparam[6] = o->mode; o->iparam[7] = 0; + o->iparam[8] = 0; o->iparam[9] = 0; o->iparam[10] = 0; +} + +/** + * \function igraph_arpack_options_get_default + * \brief Returns a pointer to a "default" ARPACK options object. + * + * This function is used by other igraph functions taking an \ref igraph_arpack_options_t + * object as an argument to get a reference to a pre-initialized "default" + * ARPACK options object when the user passes \c NULL instead of a real ARPACK + * options object. The object returned from this function is reset to a pristine + * state with every call to \c igraph_arpack_options_get_default(). + * + * + * The object returned from this function must \em not be destroyed. + * + * Time complexity: O(1). + */ +igraph_arpack_options_t* igraph_arpack_options_get_default(void) { + igraph_i_arpack_options_default = igraph_i_arpack_options_pristine; + return &igraph_i_arpack_options_default; +} + +/** + * \function igraph_arpack_storage_init + * \brief Initialize ARPACK storage. + * + * You only need this function if you want to run multiple eigenvalue + * calculations using ARPACK, and want to spare the memory + * allocation/deallocation between each two runs. Otherwise it is safe + * to supply a null pointer as the \c storage argument of both \ref + * igraph_arpack_rssolve() and \ref igraph_arpack_rnsolve() to make + * memory allocated and deallocated automatically. + * + * + * Don't forget to call the \ref igraph_arpack_storage_destroy() + * function on the storage object if you don't need it any more. + * + * \param s The \ref igraph_arpack_storage_t object to initialize. + * \param maxn The maximum order of the matrices. + * \param maxncv The maximum NCV parameter intended to use. + * \param maxldv The maximum LDV parameter intended to use. + * \param symm Whether symmetric or non-symmetric problems will be + * solved using this \ref igraph_arpack_storage_t. (You cannot use + * the same storage both with symmetric and non-symmetric solvers.) + * \return Error code. + * + * Time complexity: O(maxncv*(maxldv+maxn)). + */ + +igraph_error_t igraph_arpack_storage_init(igraph_arpack_storage_t *s, igraph_int_t maxn, + igraph_int_t maxncv, igraph_int_t maxldv, + igraph_bool_t symm) { + + /* TODO: check arguments */ + if (maxn > INT_MAX) { + IGRAPH_ERROR("Maximum order of matrices too large for ARPACK.", IGRAPH_EOVERFLOW); + } + if (maxncv > INT_MAX) { + IGRAPH_ERROR("Maximum NCV parameter too large for ARPACK.", IGRAPH_EOVERFLOW); + } + if (maxldv > INT_MAX) { + IGRAPH_ERROR("Maximum LDV parameter too large for ARPACK.", IGRAPH_EOVERFLOW); + } + + s->maxn = (int) maxn; + s->maxncv = (int) maxncv; + s->maxldv = (int) maxldv; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + s->v = IGRAPH_CALLOC(maxldv * maxncv, igraph_real_t); CHECKMEM(s->v); + s->workd = IGRAPH_CALLOC(3 * maxn, igraph_real_t); CHECKMEM(s->workd); + s->d = IGRAPH_CALLOC(2 * maxncv, igraph_real_t); CHECKMEM(s->d); + s->resid = IGRAPH_CALLOC(maxn, igraph_real_t); CHECKMEM(s->resid); + s->ax = IGRAPH_CALLOC(maxn, igraph_real_t); CHECKMEM(s->ax); + s->select = IGRAPH_CALLOC(maxncv, int); CHECKMEM(s->select); + + if (symm) { + s->workl = IGRAPH_CALLOC(maxncv * (maxncv + 8), igraph_real_t); CHECKMEM(s->workl); + s->di = 0; + s->workev = 0; + } else { + s->workl = IGRAPH_CALLOC(3 * maxncv * (maxncv + 2), igraph_real_t); CHECKMEM(s->workl); + s->di = IGRAPH_CALLOC(2 * maxncv, igraph_real_t); CHECKMEM(s->di); + s->workev = IGRAPH_CALLOC(3 * maxncv, igraph_real_t); CHECKMEM(s->workev); + IGRAPH_FINALLY_CLEAN(2); + } + +#undef CHECKMEM + + IGRAPH_FINALLY_CLEAN(7); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_arpack_storage_destroy + * \brief Deallocate ARPACK storage. + * + * \param s The \ref igraph_arpack_storage_t object for which the + * memory will be deallocated. + * + * Time complexity: operating system dependent. + */ + +void igraph_arpack_storage_destroy(igraph_arpack_storage_t *s) { + + if (s->di) { + IGRAPH_FREE(s->di); + } + if (s->workev) { + IGRAPH_FREE(s->workev); + } + + IGRAPH_FREE(s->workl); + IGRAPH_FREE(s->select); + IGRAPH_FREE(s->ax); + IGRAPH_FREE(s->resid); + IGRAPH_FREE(s->d); + IGRAPH_FREE(s->workd); + IGRAPH_FREE(s->v); +} + +/** + * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with + * these. + */ +static igraph_error_t igraph_i_arpack_rssolve_1x1(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, + igraph_vector_t* values, igraph_matrix_t* vectors) { + igraph_real_t a, b; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS); + } + + /* Probe the value in the matrix */ + a = 1; + IGRAPH_CHECK(fun(&b, &a, 1, extra)); + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_vector_resize(values, 1)); + VECTOR(*values)[0] = b; + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1)); + MATRIX(*vectors, 0, 0) = 1; + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with + * these. + */ +static igraph_error_t igraph_i_arpack_rnsolve_1x1(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, + igraph_matrix_t* values, igraph_matrix_t* vectors) { + igraph_real_t a, b; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS); + } + + /* Probe the value in the matrix */ + a = 1; + IGRAPH_CHECK(fun(&b, &a, 1, extra)); + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_matrix_resize(values, 1, 2)); + MATRIX(*values, 0, 0) = b; MATRIX(*values, 0, 1) = 0; + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1)); + MATRIX(*vectors, 0, 0) = 1; + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for 2x2 nonsymmetric eigenvalue problems since ARPACK sometimes + * blows up with these. + */ +static igraph_error_t igraph_i_arpack_rnsolve_2x2(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, igraph_matrix_t* values, + igraph_matrix_t* vectors) { + igraph_real_t vec[2], mat[4]; + igraph_real_t a, b, c, d; + igraph_real_t trace, det, tsq4_minus_d; + igraph_complex_t eval1, eval2; + igraph_complex_t evec1[2], evec2[2]; + igraph_bool_t swap_evals = false; + igraph_bool_t complex_evals = false; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS); + } + if (nev > 2) { + nev = 2; + } + + /* Probe the values in the matrix */ + vec[0] = 1; vec[1] = 0; + IGRAPH_CHECK(fun(mat, vec, 2, extra)); + vec[0] = 0; vec[1] = 1; + IGRAPH_CHECK(fun(mat + 2, vec, 2, extra)); + a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3]; + + /* Get the trace and the determinant */ + trace = a + d; + det = a * d - b * c; + tsq4_minus_d = trace * trace / 4 - det; + + /* Calculate the eigenvalues */ + complex_evals = tsq4_minus_d < 0; + eval1 = igraph_complex_sqrt_real(tsq4_minus_d); + if (complex_evals) { + eval2 = igraph_complex_mul_real(eval1, -1); + } else { + /* to avoid having -0 in the imaginary part */ + eval2 = igraph_complex(-IGRAPH_REAL(eval1), 0); + } + eval1 = igraph_complex_add_real(eval1, trace / 2); + eval2 = igraph_complex_add_real(eval2, trace / 2); + + if (c != 0) { + evec1[0] = igraph_complex_sub_real(eval1, d); + evec1[1] = igraph_complex(c, 0); + evec2[0] = igraph_complex_sub_real(eval2, d); + evec2[1] = igraph_complex(c, 0); + } else if (b != 0) { + evec1[0] = igraph_complex(b, 0); + evec1[1] = igraph_complex_sub_real(eval1, a); + evec2[0] = igraph_complex(b, 0); + evec2[1] = igraph_complex_sub_real(eval2, a); + } else { + evec1[0] = igraph_complex(1, 0); + evec1[1] = igraph_complex(0, 0); + evec2[0] = igraph_complex(0, 0); + evec2[1] = igraph_complex(1, 0); + } + + /* Sometimes we have to swap eval1 with eval2 and evec1 with eval2; + * determine whether we have to do it now */ + if (options->which[0] == 'S') { + if (options->which[1] == 'M') { + /* eval1 must be the one with the smallest magnitude */ + swap_evals = (igraph_complex_abs(eval1) > igraph_complex_abs(eval2)); + } else if (options->which[1] == 'R') { + /* eval1 must be the one with the smallest real part */ + swap_evals = (IGRAPH_REAL(eval1) > IGRAPH_REAL(eval2)); + } else if (options->which[1] == 'I') { + /* eval1 must be the one with the smallest imaginary part */ + swap_evals = (IGRAPH_IMAG(eval1) > IGRAPH_IMAG(eval2)); + } else { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV); + } + } else if (options->which[0] == 'L') { + if (options->which[1] == 'M') { + /* eval1 must be the one with the largest magnitude */ + swap_evals = (igraph_complex_abs(eval1) < igraph_complex_abs(eval2)); + } else if (options->which[1] == 'R') { + /* eval1 must be the one with the largest real part */ + swap_evals = (IGRAPH_REAL(eval1) < IGRAPH_REAL(eval2)); + } else if (options->which[1] == 'I') { + /* eval1 must be the one with the largest imaginary part */ + swap_evals = (IGRAPH_IMAG(eval1) < IGRAPH_IMAG(eval2)); + } else { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV); + } + } else if (options->which[0] == 'X' && options->which[1] == 'X') { + /* No preference on the ordering of eigenvectors */ + } else { + /* fprintf(stderr, "%c%c\n", options->which[0], options->which[1]); */ + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV); + } + + options->nconv = nev; + + if (swap_evals) { + igraph_complex_t dummy; + dummy = eval1; eval1 = eval2; eval2 = dummy; + dummy = evec1[0]; evec1[0] = evec2[0]; evec2[0] = dummy; + dummy = evec1[1]; evec1[1] = evec2[1]; evec2[1] = dummy; + } + + if (complex_evals) { + /* The eigenvalues are conjugate pairs, so we store only the + * one with positive imaginary part */ + if (IGRAPH_IMAG(eval1) < 0) { + eval1 = eval2; + evec1[0] = evec2[0]; evec1[1] = evec2[1]; + } + } + + if (values != 0) { + IGRAPH_CHECK(igraph_matrix_resize(values, nev, 2)); + MATRIX(*values, 0, 0) = IGRAPH_REAL(eval1); + MATRIX(*values, 0, 1) = IGRAPH_IMAG(eval1); + if (nev > 1) { + MATRIX(*values, 1, 0) = IGRAPH_REAL(eval2); + MATRIX(*values, 1, 1) = IGRAPH_IMAG(eval2); + } + } + + if (vectors != 0) { + if (complex_evals) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, 2)); + MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]); + MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]); + MATRIX(*vectors, 0, 1) = IGRAPH_IMAG(evec1[0]); + MATRIX(*vectors, 1, 1) = IGRAPH_IMAG(evec1[1]); + } else { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev)); + MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]); + MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]); + if (nev > 1) { + MATRIX(*vectors, 0, 1) = IGRAPH_REAL(evec2[0]); + MATRIX(*vectors, 1, 1) = IGRAPH_REAL(evec2[1]); + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for symmetric 2x2 eigenvalue problems since ARPACK sometimes blows + * up with these. + */ +static igraph_error_t igraph_i_arpack_rssolve_2x2(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, igraph_vector_t* values, + igraph_matrix_t* vectors) { + igraph_real_t vec[2], mat[4]; + igraph_real_t a, b, c, d; + igraph_real_t trace, det, tsq4_minus_d; + igraph_real_t eval1, eval2; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS); + } + if (nev > 2) { + nev = 2; + } + + /* Probe the values in the matrix */ + vec[0] = 1; vec[1] = 0; + IGRAPH_CHECK(fun(mat, vec, 2, extra)); + vec[0] = 0; vec[1] = 1; + IGRAPH_CHECK(fun(mat + 2, vec, 2, extra)); + a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3]; + + /* Get the trace and the determinant */ + trace = a + d; + det = a * d - b * c; + tsq4_minus_d = trace * trace / 4 - det; + + if (tsq4_minus_d >= 0) { + /* Both eigenvalues are real */ + eval1 = trace / 2 + sqrt(tsq4_minus_d); + eval2 = trace / 2 - sqrt(tsq4_minus_d); + if (c != 0) { + mat[0] = eval1 - d; mat[2] = eval2 - d; + mat[1] = c; mat[3] = c; + } else if (b != 0) { + mat[0] = b; mat[2] = b; + mat[1] = eval1 - a; mat[3] = eval2 - a; + } else { + mat[0] = 1; mat[2] = 0; + mat[1] = 0; mat[3] = 1; + } + } else { + /* Both eigenvalues are complex. Should not happen with symmetric + * matrices. */ + IGRAPH_ERROR("ARPACK error, 2x2 matrix is not symmetric", IGRAPH_EINVAL); + } + + /* eval1 is always the larger eigenvalue. If we want the smaller + * one, we have to swap eval1 with eval2 and also the columns of mat */ + if (options->which[0] == 'S') { + trace = eval1; eval1 = eval2; eval2 = trace; + trace = mat[0]; mat[0] = mat[2]; mat[2] = trace; + trace = mat[1]; mat[1] = mat[3]; mat[3] = trace; + } else if (options->which[0] == 'L' || options->which[0] == 'B') { + /* Nothing to do here */ + } else if (options->which[0] == 'X' && options->which[1] == 'X') { + /* No preference on the ordering of eigenvectors */ + } else { + IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV); + } + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_vector_resize(values, nev)); + VECTOR(*values)[0] = eval1; + if (nev > 1) { + VECTOR(*values)[1] = eval2; + } + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev)); + MATRIX(*vectors, 0, 0) = mat[0]; + MATRIX(*vectors, 1, 0) = mat[1]; + if (nev > 1) { + MATRIX(*vectors, 0, 1) = mat[2]; + MATRIX(*vectors, 1, 1) = mat[3]; + } + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_arpack_rssort(igraph_vector_t *values, igraph_matrix_t *vectors, + const igraph_arpack_options_t *options, + igraph_real_t *d, const igraph_real_t *v) { + + igraph_vector_t order; + char sort[2]; + int apply = 1; + unsigned int n = (unsigned int) options->n; + int nconv = options->nconv; + int nev = options->nev; + unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev); + unsigned int i; + +#define which(a,b) (options->which[0]==a && options->which[1]==b) + + if (which('L', 'A')) { + sort[0] = 'S'; sort[1] = 'A'; + } else if (which('S', 'A')) { + sort[0] = 'L'; sort[1] = 'A'; + } else if (which('L', 'M')) { + sort[0] = 'S'; sort[1] = 'M'; + } else if (which('S', 'M')) { + sort[0] = 'L'; sort[1] = 'M'; + } else if (which('B', 'E')) { + sort[0] = 'L'; sort[1] = 'A'; + } else { + /* None of the above, no sorting. These 'X' values are + * ignored by ARPACK, but we set them anyway in order to + * avoid an uninitialized 'sort' which would trigger + * checkers such as MemorySanitizer. */ + sort[0] = 'X'; sort[1] = 'X'; + } + + IGRAPH_CHECK(igraph_vector_init_range(&order, 0, nconv)); + IGRAPH_FINALLY(igraph_vector_destroy, &order); +#ifdef HAVE_GFORTRAN + igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order), /*which_len=*/ 2); +#else + igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order)); +#endif + + /* BE is special */ + if (which('B', 'E')) { + int w = 0, l1 = 0, l2 = nev - 1; + igraph_vector_t order2, d2; + IGRAPH_VECTOR_INIT_FINALLY(&order2, nev); + IGRAPH_VECTOR_INIT_FINALLY(&d2, nev); + while (l1 <= l2) { + VECTOR(order2)[w] = VECTOR(order)[l1]; + VECTOR(d2)[w] = d[l1]; + w++; l1++; + if (l1 <= l2) { + VECTOR(order2)[w] = VECTOR(order)[l2]; + VECTOR(d2)[w] = d[l2]; + w++; l2--; + } + } + igraph_vector_update(&order, &order2); + igraph_vector_copy_to(&d2, d); + igraph_vector_destroy(&order2); + igraph_vector_destroy(&d2); + IGRAPH_FINALLY_CLEAN(2); + } + +#undef which + + /* Copy values */ + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, nans)); + memcpy(VECTOR(*values), d, sizeof(igraph_real_t) * nans); + } + + /* Reorder vectors */ + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, nans)); + for (i = 0; i < nans; i++) { + unsigned int idx = (unsigned int) VECTOR(order)[i]; + const igraph_real_t *ptr = v + n * idx; + memcpy(&MATRIX(*vectors, 0, i), ptr, sizeof(igraph_real_t) * n); + } + } + + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_arpack_rnsort(igraph_matrix_t *values, igraph_matrix_t *vectors, + const igraph_arpack_options_t *options, + igraph_real_t *dr, igraph_real_t *di, + igraph_real_t *v) { + + igraph_vector_t order; + char sort[2]; + int apply = 1; + unsigned int n = (unsigned int) options->n; + int nconv = options->nconv; + int nev = options->nev; + unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev); + unsigned int i; + +#define which(a,b) (options->which[0]==a && options->which[1]==b) + + if (which('L', 'M')) { + sort[0] = 'S'; sort[1] = 'M'; + } else if (which('S', 'M')) { + sort[0] = 'L'; sort[1] = 'M'; + } else if (which('L', 'R')) { + sort[0] = 'S'; sort[1] = 'R'; + } else if (which('S', 'R')) { + sort[0] = 'L'; sort[1] = 'R'; + } else if (which('L', 'I')) { + sort[0] = 'S'; sort[1] = 'I'; + } else if (which('S', 'I')) { + sort[0] = 'L'; sort[1] = 'I'; + } else { + /* None of the above, no sorting. These 'X' values are + * ignored by ARPACK, but we set them anyway in order to + * avoid an uninitialized 'sort' which would trigger + * checkers such as MemorySanitizer. */ + sort[0] = 'X'; sort[1] = 'X'; + } + +#undef which + + IGRAPH_CHECK(igraph_vector_init_range(&order, 0, nconv)); + IGRAPH_FINALLY(igraph_vector_destroy, &order); +#ifdef HAVE_GFORTRAN + igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order), /*which_len=*/ 2); +#else + igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order)); +#endif + + if (values) { + IGRAPH_CHECK(igraph_matrix_resize(values, nans, 2)); + memcpy(&MATRIX(*values, 0, 0), dr, sizeof(igraph_real_t) * nans); + memcpy(&MATRIX(*values, 0, 1), di, sizeof(igraph_real_t) * nans); + } + + if (vectors) { + int nc = 0, nr = 0, ncol, vx = 0; + for (i = 0; i < nans; i++) { + if (di[i] == 0) { + nr++; + } else { + nc++; + } + } + ncol = (nc / 2) * 2 + (nc % 2) * 2 + nr; + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, ncol)); + + for (i = 0; i < nans; i++) { + unsigned int idx; + + idx = (unsigned int) VECTOR(order)[i]; + + if (di[i] == 0) { + /* real eigenvalue, single eigenvector */ + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * n); + vx++; + } else if (di[i] > 0) { + /* complex eigenvalue, positive imaginary part encountered first. + * ARPACK stores its eigenvector directly in two consecutive columns. + * The complex conjugate pair of the eigenvalue (if any) will be in + * the next column and we will skip it because we advance 'i' below */ + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n); + vx += 2; + i++; + } else { + /* complex eigenvalue, negative imaginary part encountered first. + * The positive one will be the next one, but we need to copy the + * eigenvector corresponding to the eigenvalue with the positive + * imaginary part. */ + idx = (unsigned int) VECTOR(order)[i + 1]; + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n); + vx += 2; + i++; + } + } + } + + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + if (values) { + /* Strive to include complex conjugate eigenvalue pairs in a way that the + * positive imaginary part comes first */ + for (i = 0; i < nans; i++) { + if (MATRIX(*values, i, 1) == 0) { + /* Real eigenvalue, nothing to do */ + } else if (MATRIX(*values, i, 1) < 0) { + /* Negative imaginary part came first; negate the imaginary part for + * this eigenvalue and the next one (which is the complex conjugate + * pair), and skip it */ + MATRIX(*values, i, 1) *= -1; + i++; + if (i < nans) { + MATRIX(*values, i, 1) *= -1; + } + } else { + /* Positive imaginary part; skip the next eigenvalue, which is the + * complex conjugate pair */ + i++; + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_i_arpack_auto_ncv + * \brief Tries to set up the value of \c ncv in an \c igraph_arpack_options_t + * automagically. + */ +static void igraph_i_arpack_auto_ncv(igraph_arpack_options_t* options) { + /* This is similar to how Octave determines the value of ncv, with some + * modifications. */ + int min_ncv = options->nev * 2 + 1; + + /* Use twice the number of desired eigenvectors plus one by default */ + options->ncv = min_ncv; + /* ...but use at least 20 Lanczos vectors... */ + if (options->ncv < 20) { + options->ncv = 20; + } + /* ...but having ncv close to n leads to some problems with small graphs + * (example: PageRank of "A <--> C, D <--> E, B"), so we try to keep it + * no more than min(n/2 + 2, n - 1), bounds found empirically using the + * eigen_stress.c test... + */ + if (options->ncv > options->n / 2 + 2) { + options->ncv = options->n / 2 + 2; + } + if (options->ncv > options->n - 1) { + options->ncv = options->n - 1; + } + /* ...but we need at least min_ncv. */ + if (options->ncv < min_ncv) { + options->ncv = min_ncv; + } + /* ...but at most n */ + if (options->ncv > options->n) { + options->ncv = options->n; + } +} + +/** + * \function igraph_i_arpack_report_no_convergence + * \brief Prints a warning that informs the user that the ARPACK solver + * did not converge. + */ +static void igraph_i_arpack_report_no_convergence(const igraph_arpack_options_t* options) { + char buf[1024]; + snprintf(buf, sizeof(buf), "ARPACK solver failed to converge (%d iterations, " + "%d/%d eigenvectors converged)", options->iparam[2], + options->iparam[4], options->nev); + IGRAPH_WARNING(buf); +} + +/** + * \function igraph_arpack_rssolve + * \brief ARPACK solver for symmetric matrices. + * + * This is the ARPACK solver for symmetric matrices. Please use + * \ref igraph_arpack_rnsolve() for non-symmetric matrices. + * \param fun Pointer to an \ref igraph_arpack_function_t object, + * the function that performs the matrix-vector multiplication. + * \param extra An extra argument to be passed to \c fun. + * \param options An \ref igraph_arpack_options_t object. + * \param storage An \ref igraph_arpack_storage_t object, or a null + * pointer. In the latter case memory allocation and deallocation + * is performed automatically. Either this or the \p vectors argument + * must be non-null if the ARPACK iteration is started from a + * given starting vector. If both are given \p vectors take + * precedence. + * \param values If not a null pointer, then it should be a pointer to an + * initialized vector. The eigenvalues will be stored here. The + * vector will be resized as needed. + * \param vectors If not a null pointer, then it must be a pointer to + * an initialized matrix. The eigenvectors will be stored in the + * columns of the matrix. The matrix will be resized as needed. + * Either this or the \p storage argument must be non-null if the + * ARPACK iteration is started from a given starting vector. If + * both are given \p vectors take precedence. + * \return Error code. + * + * Time complexity: depends on the matrix-vector + * multiplication. Usually a small number of iterations is enough, so + * if the matrix is sparse and the matrix-vector multiplication can be + * done in O(n) time (the number of vertices), then the eigenvalues + * are found in O(n) time as well. + */ + +igraph_error_t igraph_arpack_rssolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, igraph_matrix_t *vectors) { + + igraph_real_t *v, *workl, *workd, *d, *resid, *ax; + igraph_bool_t free_them = false; + int *select, i; + + int ido = 0; + int rvec = vectors || storage ? 1 : 0; /* calculate eigenvectors? */ + char *all = "A"; + + int origldv = options->ldv, origlworkl = options->lworkl, + orignev = options->nev, origncv = options->ncv; + igraph_real_t origtol = options->tol; + char origwhich[2]; + + origwhich[0] = options->which[0]; + origwhich[1] = options->which[1]; + + /* Special case for 1x1 and 2x2 matrices in mode 1 */ + if (options->mode == 1 && options->n == 1) { + return igraph_i_arpack_rssolve_1x1(fun, extra, options, values, vectors); + } else if (options->mode == 1 && options->n == 2) { + return igraph_i_arpack_rssolve_2x2(fun, extra, options, values, vectors); + } + + /* Brush up options if needed */ + if (options->ldv == 0) { + options->ldv = options->n; + } + if (options->ncv == 0) { + igraph_i_arpack_auto_ncv(options); + } + if (options->lworkl == 0) { + options->lworkl = options->ncv * (options->ncv + 8); + } + if (options->which[0] == 'X') { + options->which[0] = 'L'; + options->which[1] = 'M'; + } + + if (storage) { + /* Storage provided */ + if (storage->maxn < options->n) { + IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL); + } + if (storage->maxncv < options->ncv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL); + } + if (storage->maxldv < options->ldv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL); + } + + v = storage->v; + workl = storage->workl; + workd = storage->workd; + d = storage->d; + resid = storage->resid; + ax = storage->ax; + select = storage->select; + + } else { + /* Storage not provided */ + free_them = true; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + v = IGRAPH_CALLOC(options->ldv * options->ncv, igraph_real_t); CHECKMEM(v); + workl = IGRAPH_CALLOC(options->lworkl, igraph_real_t); CHECKMEM(workl); + workd = IGRAPH_CALLOC(3 * options->n, igraph_real_t); CHECKMEM(workd); + d = IGRAPH_CALLOC(2 * options->ncv, igraph_real_t); CHECKMEM(d); + resid = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(resid); + ax = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(ax); + select = IGRAPH_CALLOC(options->ncv, int); CHECKMEM(select); + +#undef CHECKMEM + + } + + /* Set final bits */ + options->bmat[0] = 'I'; + options->iparam[0] = options->ishift; + options->iparam[1] = 0; // not referenced + options->iparam[2] = options->mxiter; + options->iparam[3] = 1; // currently dsaupd() works only for nb=1 + options->iparam[4] = 0; + options->iparam[5] = 0; // not referenced + options->iparam[6] = options->mode; + options->iparam[7] = 0; // return value + options->iparam[8] = 0; // return value + options->iparam[9] = 0; // return value + options->iparam[10] = 0; // return value + options->info = 1; // always use a provided starting vector + if (options->start) { + // user provided the starting vector so we just use that + if (!storage && !vectors) { + IGRAPH_ERROR("Starting vector not given", IGRAPH_EINVAL); + } + if (vectors && (igraph_matrix_nrow(vectors) != options->n || + igraph_matrix_ncol(vectors) < 1)) { + IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL); + } + if (vectors) { + for (i = 0; i < options->n; i++) { + resid[i] = MATRIX(*vectors, i, 0); + } + } + } else { + // we need to generate a random vector on our own; let's not rely on + // ARPACK to do so because we want to use our own RNG + for (i = 0; i < options->n; i++) { + resid[i] = RNG_UNIF(-1, 1); + } + } + + /* Ok, we have everything */ + while (1) { + igraph_real_t *from, *to; + + IGRAPH_ALLOW_INTERRUPTION(); + +#ifdef HAVE_GFORTRAN + igraphdsaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info, + /*bmat_len=*/ 1, /*which_len=*/ 2); +#else + igraphdsaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info); +#endif + /* When there is a non-zero error code in options->info, we expect that + * ARPACK requests a termination of the iteration by setting ido=99. */ + IGRAPH_ASSERT(ido == 99 || options->info == 0); + + if (ido == -1 || ido == 1) { + from = workd + options->ipntr[0] - 1; + to = workd + options->ipntr[1] - 1; + IGRAPH_CHECK(fun(to, from, options->n, extra)); + } else if (ido == 2) { + from = workd + options->ipntr[0] - 1; + to = workd + options->ipntr[1] - 1; + memcpy(to, from, sizeof(igraph_real_t) * options->n); + } else if (ido == 99) { + break; + } else { + IGRAPH_ERRORF("Unexpected IDO value %d when running ARPACK.", IGRAPH_FAILURE, ido); + } + } + + if (options->info == 1) { + igraph_i_arpack_report_no_convergence(options); + } + if (options->info != 0) { + IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dsaupd(options->info)); + } + + options->ierr = 0; +#ifdef HAVE_GFORTRAN + igraphdseupd_(&rvec, all, select, d, v, &options->ldv, + &options->sigma, options->bmat, &options->n, + options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1, + /*which_len=*/ 2); +#else + igraphdseupd_(&rvec, all, select, d, v, &options->ldv, + &options->sigma, options->bmat, &options->n, + options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr); +#endif + + if (options->ierr != 0) { + IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dseupd(options->ierr)); + } + + /* Save the result */ + + options->noiter = options->iparam[2]; + options->nconv = options->iparam[4]; + options->numop = options->iparam[8]; + options->numopb = options->iparam[9]; + options->numreo = options->iparam[10]; + + if (options->nconv < options->nev) { + IGRAPH_WARNING("Not enough eigenvalues/vectors in symmetric ARPACK " + "solver"); + } + + if (values || vectors) { + IGRAPH_CHECK(igraph_arpack_rssort(values, vectors, options, d, v)); + } + + options->ldv = origldv; + options->ncv = origncv; + options->lworkl = origlworkl; + options->which[0] = origwhich[0]; options->which[1] = origwhich[1]; + options->tol = origtol; + options->nev = orignev; + + /* Clean up if needed */ + if (free_them) { + IGRAPH_FREE(select); + IGRAPH_FREE(ax); + IGRAPH_FREE(resid); + IGRAPH_FREE(d); + IGRAPH_FREE(workd); + IGRAPH_FREE(workl); + IGRAPH_FREE(v); + IGRAPH_FINALLY_CLEAN(7); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_arpack_rnsolve + * \brief ARPACK solver for non-symmetric matrices. + * + * Please always consider calling \ref igraph_arpack_rssolve() if your + * matrix is symmetric, it is much faster. + * \ref igraph_arpack_rnsolve() for non-symmetric matrices. + * + * Note that ARPACK is not called for 2x2 matrices as an exact algebraic + * solution exists in these cases. + * + * \param fun Pointer to an \ref igraph_arpack_function_t object, + * the function that performs the matrix-vector multiplication. + * \param extra An extra argument to be passed to \c fun. + * \param options An \ref igraph_arpack_options_t object. + * \param storage An \ref igraph_arpack_storage_t object, or a null + * pointer. In the latter case memory allocation and deallocation + * is performed automatically. + * \param values If not a null pointer, then it should be a pointer to an + * initialized matrix. The (possibly complex) eigenvalues will be + * stored here. The matrix will have two columns, the first column + * contains the real, the second the imaginary parts of the + * eigenvalues. + * The matrix will be resized as needed. + * \param vectors If not a null pointer, then it must be a pointer to + * an initialized matrix. The eigenvectors will be stored in the + * columns of the matrix. The matrix will be resized as needed. + * Note that real eigenvalues will have real eigenvectors in a single + * column in this matrix; however, complex eigenvalues come in conjugate + * pairs and the result matrix will store the eigenvector corresponding to + * the eigenvalue with \em positive imaginary part only. Since in this case + * the eigenvector is also complex, it will occupy \em two columns in the + * eigenvector matrix (the real and the imaginary parts, in this order). + * Caveat: if the eigenvalue vector returns only the eigenvalue with the + * \em negative imaginary part for a complex conjugate eigenvalue pair, the + * result vector will \em still store the eigenvector corresponding to the + * eigenvalue with the positive imaginary part (since this is how ARPACK + * works). + * \return Error code. + * + * Time complexity: depends on the matrix-vector + * multiplication. Usually a small number of iterations is enough, so + * if the matrix is sparse and the matrix-vector multiplication can be + * done in O(n) time (the number of vertices), then the eigenvalues + * are found in O(n) time as well. + */ + +igraph_error_t igraph_arpack_rnsolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, igraph_matrix_t *vectors) { + + igraph_real_t *v, *workl, *workd, *dr, *di, *resid, *workev; + igraph_bool_t free_them = false; + int *select, i; + + int ido = 0; + int rvec = vectors || storage ? 1 : 0; + char *all = "A"; + + int origldv = options->ldv, origlworkl = options->lworkl, + orignev = options->nev, origncv = options->ncv; + igraph_real_t origtol = options->tol; + int d_size; + char origwhich[2]; + + origwhich[0] = options->which[0]; + origwhich[1] = options->which[1]; + + /* Special case for 1x1 and 2x2 matrices in mode 1 */ + if (options->mode == 1 && options->n == 1) { + return igraph_i_arpack_rnsolve_1x1(fun, extra, options, values, vectors); + } else if (options->mode == 1 && options->n == 2) { + return igraph_i_arpack_rnsolve_2x2(fun, extra, options, values, vectors); + } + + /* Brush up options if needed */ + if (options->ldv == 0) { + options->ldv = options->n; + } + if (options->ncv == 0) { + igraph_i_arpack_auto_ncv(options); + } + if (options->lworkl == 0) { + options->lworkl = 3 * options->ncv * (options->ncv + 2); + } + if (options->which[0] == 'X') { + options->which[0] = 'L'; + options->which[1] = 'M'; + } + + if (storage) { + /* Storage provided */ + if (storage->maxn < options->n) { + IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL); + } + if (storage->maxncv < options->ncv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL); + } + if (storage->maxldv < options->ldv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL); + } + + v = storage->v; + workl = storage->workl; + workd = storage->workd; + workev = storage->workev; + dr = storage->d; + di = storage->di; + d_size = options->n; + resid = storage->resid; + select = storage->select; + + } else { + /* Storage not provided */ + free_them = true; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + v = IGRAPH_CALLOC(options->n * options->ncv, igraph_real_t); CHECKMEM(v); + workl = IGRAPH_CALLOC(options->lworkl, igraph_real_t); CHECKMEM(workl); + workd = IGRAPH_CALLOC(3 * options->n, igraph_real_t); CHECKMEM(workd); + d_size = 2 * options->nev + 1 > options->ncv ? 2 * options->nev + 1 : options->ncv; + dr = IGRAPH_CALLOC(d_size, igraph_real_t); CHECKMEM(dr); + di = IGRAPH_CALLOC(d_size, igraph_real_t); CHECKMEM(di); + resid = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(resid); + select = IGRAPH_CALLOC(options->ncv, int); CHECKMEM(select); + workev = IGRAPH_CALLOC(3 * options->ncv, igraph_real_t); CHECKMEM(workev); + +#undef CHECKMEM + + } + + /* Set final bits */ + options->bmat[0] = 'I'; + options->iparam[0] = options->ishift; + options->iparam[1] = 0; // not referenced + options->iparam[2] = options->mxiter; + options->iparam[3] = 1; // currently dnaupd() works only for nb=1 + options->iparam[4] = 0; + options->iparam[5] = 0; // not referenced + options->iparam[6] = options->mode; + options->iparam[7] = 0; // return value + options->iparam[8] = 0; // return value + options->iparam[9] = 0; // return value + options->iparam[10] = 0; // return value + options->info = 1; // always use a provided starting vector + if (options->start) { + if (!storage && !vectors) { + IGRAPH_ERROR("Starting vector not given", IGRAPH_EINVAL); + } + if (vectors && (igraph_matrix_nrow(vectors) != options->n || + igraph_matrix_ncol(vectors) != 1)) { + IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL); + } + if (vectors) { + for (i = 0; i < options->n; i++) { + resid[i] = MATRIX(*vectors, i, 0); + } + } + } else { + // we need to generate a random vector on our own; let's not rely on + // ARPACK to do so because we want to use our own RNG + for (i = 0; i < options->n; i++) { + resid[i] = RNG_UNIF(-1, 1); + } + } + + /* Ok, we have everything */ + while (1) { + igraph_real_t *from, *to; + + IGRAPH_ALLOW_INTERRUPTION(); + +#ifdef HAVE_GFORTRAN + igraphdnaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info, + /*bmat_len=*/ 1, /*which_len=*/ 2); +#else + igraphdnaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info); +#endif + /* When there is a non-zero error code in options->info, we expect that + * ARPACK requests a termination of the iteration by setting ido=99. */ + IGRAPH_ASSERT(ido == 99 || options->info == 0); + + if (ido == -1 || ido == 1) { + from = workd + options->ipntr[0] - 1; + to = workd + options->ipntr[1] - 1; + IGRAPH_CHECK(fun(to, from, options->n, extra)); + } else if (ido == 2) { + from = workd + options->ipntr[0] - 1; + to = workd + options->ipntr[1] - 1; + memcpy(to, from, sizeof(igraph_real_t) * options->n); + } else if (ido == 4) { + /* same as ido == 1 but the arguments are at different places */ + from = workd + options->ipntr[0] - 1; + to = workd + options->ipntr[2] - 1; + IGRAPH_CHECK(fun(to, from, options->n, extra)); + } else if (ido == 99) { + break; + } else { + IGRAPH_ERRORF("Unexpected IDO value %d when running ARPACK.", IGRAPH_FAILURE, ido); + } + } + + if (options->info == 1) { + igraph_i_arpack_report_no_convergence(options); + } + if (options->info != 0 && options->info != -9999) { + IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dnaupd(options->info)); + } + + options->ierr = 0; +#ifdef HAVE_GFORTRAN + igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv, + &options->sigma, &options->sigmai, workev, options->bmat, + &options->n, options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1, + /*which_len=*/ 2); +#else + igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv, + &options->sigma, &options->sigmai, workev, options->bmat, + &options->n, options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr); +#endif + + if (options->ierr != 0) { + IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dneupd(options->ierr)); + } + + /* Save the result */ + + options->noiter = options->iparam[2]; + options->nconv = options->iparam[4]; + options->numop = options->iparam[8]; + options->numopb = options->iparam[9]; + options->numreo = options->iparam[10]; + + if (options->nconv < options->nev) { + IGRAPH_WARNING("Not enough eigenvalues/vectors in ARPACK " + "solver"); + } + + /* ARPACK might modify stuff in 'options' so reset everything that could + * potentially get modified */ + options->ldv = origldv; + options->ncv = origncv; + options->lworkl = origlworkl; + options->which[0] = origwhich[0]; options->which[1] = origwhich[1]; + options->tol = origtol; + options->nev = orignev; + + if (values || vectors) { + IGRAPH_CHECK(igraph_arpack_rnsort(values, vectors, options, + dr, di, v)); + } + + /* Clean up if needed */ + if (free_them) { + IGRAPH_FREE(workev); + IGRAPH_FREE(select); + IGRAPH_FREE(resid); + IGRAPH_FREE(di); + IGRAPH_FREE(dr); + IGRAPH_FREE(workd); + IGRAPH_FREE(workl); + IGRAPH_FREE(v); + IGRAPH_FINALLY_CLEAN(8); + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_arpack_unpack_complex + * \brief Makes the result of the non-symmetric ARPACK solver more readable. + * + * This function works on the output of \ref igraph_arpack_rnsolve and + * brushes it up a bit: it only keeps \p nev eigenvalues/vectors and + * every eigenvector is stored in two columns of the \p vectors + * matrix. + * + * + * The output of the non-symmetric ARPACK solver is somewhat hard to + * parse, as real eigenvectors occupy only one column in the matrix, + * and the complex conjugate eigenvectors are not stored at all + * (usually). The other problem is that the solver might return more + * eigenvalues than requested. The common use of this function is to + * call it directly after \ref igraph_arpack_rnsolve with its \p + * vectors and \p values argument and \c options->nev as \p nev. + * This will add the vectors for eigenvalues with a negative imaginary + * part and return all vectors as 2 columns, a real and imaginary part. + * \param vectors The eigenvector matrix, as returned by \ref + * igraph_arpack_rnsolve. It will be resized, typically it will be + * larger. + * \param values The eigenvalue matrix, as returned by \ref + * igraph_arpack_rnsolve. It will be resized, typically extra, + * unneeded rows (=eigenvalues) will be removed. + * \param nev The number of eigenvalues/vectors to keep. Can be less + * or equal than the number originally requested from ARPACK. + * \return Error code. + * + * Time complexity: linear in the number of elements in the \p vectors + * matrix. + */ + +igraph_error_t igraph_arpack_unpack_complex(igraph_matrix_t *vectors, igraph_matrix_t *values, + igraph_int_t nev) { + + igraph_int_t nodes = igraph_matrix_nrow(vectors); + igraph_int_t no_evs = igraph_matrix_nrow(values); + igraph_int_t i, j; + igraph_int_t new_vector_pos, vector_pos; + igraph_matrix_t new_vectors; + + /* Error checks */ + if (nev < 0) { + IGRAPH_ERROR("`nev' cannot be negative.", IGRAPH_EINVAL); + } + if (nev > no_evs) { + IGRAPH_ERROR("`nev' too large, we don't have that many in `values'.", + IGRAPH_EINVAL); + } + + for (i = no_evs -1; i >= nev; i--) { + IGRAPH_CHECK(igraph_matrix_remove_row(values, i)); + } + + IGRAPH_CHECK(igraph_matrix_init(&new_vectors, nodes, nev * 2)); + IGRAPH_FINALLY(igraph_matrix_destroy, &new_vectors); + + new_vector_pos = 0; + vector_pos = 0; + for (i = 0; i < nev && vector_pos < igraph_matrix_ncol(vectors); i++) { + if (MATRIX(*values, i, 1) == 0) { + /* Real eigenvalue */ + for (j = 0; j < nodes; j++) { + MATRIX(new_vectors, j, new_vector_pos) = MATRIX(*vectors, j, vector_pos); + } + new_vector_pos += 2; + vector_pos += 1; + } else { + /* complex eigenvalue */ + for (j = 0; j < nodes; j++) { + MATRIX(new_vectors, j, new_vector_pos) = MATRIX(*vectors, j, vector_pos); + MATRIX(new_vectors, j, new_vector_pos + 1) = MATRIX(*vectors, j, vector_pos + 1); + } + + /* handle the conjugate */ + + /* first check if the conjugate eigenvalue is there */ + i++; + if (i >= nev) { + break; + } + + if (MATRIX(*values, i, 1) != -MATRIX(*values, i-1, 1)) { + IGRAPH_ERROR("Complex eigenvalue not followed by its conjugate.", IGRAPH_EINVAL); + } + + /* then copy and negate */ + for (j = 0; j < nodes; j++) { + MATRIX(new_vectors, j, new_vector_pos + 2) = MATRIX(*vectors, j, vector_pos); + MATRIX(new_vectors, j, new_vector_pos + 3) = -MATRIX(*vectors, j, vector_pos + 1); + } + new_vector_pos += 4; + vector_pos += 2; + } + } + igraph_matrix_destroy(vectors); + IGRAPH_CHECK(igraph_matrix_init_copy(vectors, &new_vectors)); + igraph_matrix_destroy(&new_vectors); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* ************************************************************************* */ + +/* Helper functions to map ARPACK error codes to \c igraph_arpack_error_t */ + +static igraph_arpack_error_t igraph_i_arpack_err_dsaupd(int error) { + switch (error) { + case 0: return IGRAPH_ARPACK_NO_ERROR; + case 1: return IGRAPH_ARPACK_MAXIT; + case 3: return IGRAPH_ARPACK_NOSHIFT; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -4: return IGRAPH_ARPACK_NONPOSI; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_ISHIFT; + case -13: return IGRAPH_ARPACK_NEVBE; + case -9999: return IGRAPH_ARPACK_NOFACT; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +static igraph_arpack_error_t igraph_i_arpack_err_dseupd(int error) { + switch (error) { + case 0: return IGRAPH_ARPACK_NO_ERROR; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_NEVBE; + case -14: return IGRAPH_ARPACK_FAILED; + case -15: return IGRAPH_ARPACK_HOWMNY; + case -16: return IGRAPH_ARPACK_HOWMNYS; + case -17: return IGRAPH_ARPACK_EVDIFF; + default: return IGRAPH_ARPACK_UNKNOWN; + } + +} + +static igraph_arpack_error_t igraph_i_arpack_err_dnaupd(int error) { + switch (error) { + case 0: return IGRAPH_ARPACK_NO_ERROR; + case 1: return IGRAPH_ARPACK_MAXIT; + case 3: return IGRAPH_ARPACK_NOSHIFT; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -4: return IGRAPH_ARPACK_NONPOSI; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_ISHIFT; + case -9999: return IGRAPH_ARPACK_NOFACT; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +static igraph_arpack_error_t igraph_i_arpack_err_dneupd(int error) { + switch (error) { + case 0: return IGRAPH_ARPACK_NO_ERROR; + case 1: return IGRAPH_ARPACK_REORDER; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_SHUR; + case -9: return IGRAPH_ARPACK_LAPACK; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_HOWMNYS; + case -13: return IGRAPH_ARPACK_HOWMNY; + case -14: return IGRAPH_ARPACK_FAILED; + case -15: return IGRAPH_ARPACK_EVDIFF; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +static const char* igraph_i_arpack_error_strings[] = { + /* 15 */ "Matrix-vector product failed", + /* 16 */ "N must be positive", + /* 17 */ "NEV must be positive", + /* 18 */ "NCV must be greater than NEV and less than or equal to N " + "(and for the non-symmetric solver NCV-NEV >=2 must also hold)", + /* 19 */ "Maximum number of iterations should be positive", + /* 20 */ "Invalid WHICH parameter", + /* 21 */ "Invalid BMAT parameter", + /* 22 */ "WORKL is too small", + /* 23 */ "LAPACK error in tridiagonal eigenvalue calculation", + /* 24 */ "Starting vector is zero", + /* 25 */ "MODE is invalid", + /* 26 */ "MODE and BMAT are not compatible", + /* 27 */ "ISHIFT must be 0 or 1", + /* 28 */ "NEV and WHICH='BE' are incompatible", + /* 29 */ "Could not build an Arnoldi factorization", + /* 30 */ "No eigenvalues to sufficient accuracy", + /* 31 */ "HOWMNY is invalid", + /* 32 */ "HOWMNY='S' is not implemented", + /* 33 */ "Different number of converged Ritz values", + /* 34 */ "Error from calculation of a real Schur form", + /* 35 */ "LAPACK (dtrevc) error for calculating eigenvectors", + /* 36 */ "Unknown ARPACK error", + /* 37 */ 0, + /* 38 */ 0, + /* 39 */ "Maximum number of iterations reached", + /* 40 */ "No shifts could be applied during a cycle of the " + "Implicitly restarted Arnoldi iteration. One possibility " + "is to increase the size of NCV relative to NEV", + /* 41 */ "The Schur form computed by LAPACK routine dlahqr " + "could not be reordered by LAPACK routine dtrsen." +}; + +/** + * \function igraph_arpack_error_to_string + * \brief Convert an ARPACK error code to a human-readable representation. + */ +const char* igraph_arpack_error_to_string(igraph_arpack_error_t error) { + if (error == IGRAPH_ARPACK_NO_ERROR) { + return "No error"; + } + + int index = (error >= IGRAPH_ARPACK_PROD) + ? error - IGRAPH_ARPACK_PROD + : -1; + const char* message = 0; + + if (index >= 0 && index < sizeof(igraph_i_arpack_error_strings) / sizeof(igraph_i_arpack_error_strings[0])) { + message = igraph_i_arpack_error_strings[index]; + } + + return message ? message : "Unknown ARPACK error"; +} + +/** + * \function igraph_arpack_get_last_error + * \brief Returns the last error code returned from an ARPACK function. + */ +igraph_arpack_error_t igraph_arpack_get_last_error(void) { + return last_arpack_error; +} diff --git a/src/linalg/arpack_internal.h b/src/linalg/arpack_internal.h new file mode 100644 index 0000000..c7f7e54 --- /dev/null +++ b/src/linalg/arpack_internal.h @@ -0,0 +1,232 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef ARPACK_INTERNAL_H +#define ARPACK_INTERNAL_H + +/* Note: only files calling the arpack routines directly need to + include this header. +*/ + +#include "igraph_decls.h" +#include "config.h" /* INTERNAL_ARPACK */ + +IGRAPH_BEGIN_C_DECLS + +#ifndef INTERNAL_ARPACK + #define igraphdsaupd_ dsaupd_ + #define igraphdseupd_ dseupd_ + #define igraphdsaup2_ dsaup2_ + #define igraphdstats_ dstats_ + #define igraphdsesrt_ dsesrt_ + #define igraphdsortr_ dsortr_ + #define igraphdsortc_ dsortc_ + #define igraphdgetv0_ dgetv0_ + #define igraphdsaitr_ dsaitr_ + #define igraphdsapps_ dsapps_ + #define igraphdsconv_ dsconv_ + #define igraphdseigt_ dseigt_ + #define igraphdsgets_ dsgets_ + #define igraphdstqrb_ dstqrb_ + #define igraphdmout_ dmout_ + #define igraphivout_ ivout_ + #define igraphsecond_ second_ + #define igraphdvout_ dvout_ + #define igraphdnaitr_ dnaitr_ + #define igraphdnapps_ dnapps_ + #define igraphdnaup2_ dnaup2_ + #define igraphdnaupd_ dnaupd_ + #define igraphdnconv_ dnconv_ + #define igraphdlabad_ dlabad_ + #define igraphdlanhs_ dlanhs_ + #define igraphdsortc_ dsortc_ + #define igraphdneigh_ dneigh_ + #define igraphdngets_ dngets_ + #define igraphdstatn_ dstatn_ + #define igraphdlaqrb_ dlaqrb_ + + #define igraphdsaupd_ dsaupd_ + #define igraphdseupd_ dseupd_ + #define igraphdnaupd_ dnaupd_ + #define igraphdneupd_ dneupd_ +#endif + +#ifndef INTERNAL_LAPACK + #define igraphdlarnv_ dlarnv_ + #define igraphdlascl_ dlascl_ + #define igraphdlartg_ dlartg_ + #define igraphdlaset_ dlaset_ + #define igraphdlae2_ dlae2_ + #define igraphdlaev2_ dlaev2_ + #define igraphdlasr_ dlasr_ + #define igraphdlasrt_ dlasrt_ + #define igraphdgeqr2_ dgeqr2_ + #define igraphdlacpy_ dlacpy_ + #define igraphdorm2r_ dorm2r_ + #define igraphdsteqr_ dsteqr_ + #define igraphdlanst_ dlanst_ + #define igraphdlapy2_ dlapy2_ + #define igraphdlamch_ dlamch_ + #define igraphdlaruv_ dlaruv_ + #define igraphdlarfg_ dlarfg_ + #define igraphdlarf_ dlarf_ + #define igraphdlassq_ dlassq_ + #define igraphdlamc2_ dlamc2_ + #define igraphdlamc1_ dlamc1_ + #define igraphdlamc2_ dlamc2_ + #define igraphdlamc3_ dlamc3_ + #define igraphdlamc4_ dlamc4_ + #define igraphdlamc5_ dlamc5_ + #define igraphdlabad_ dlabad_ + #define igraphdlanhs_ dlanhs_ + #define igraphdtrevc_ dtrevc_ + #define igraphdlanv2_ dlanv2_ + #define igraphdlaln2_ dlaln2_ + #define igraphdladiv_ dladiv_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlahqr_ dlahqr_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlacon_ dlacon_ + #define igraphdtrsyl_ dtrsyl_ + #define igraphdtrexc_ dtrexc_ + #define igraphdlange_ dlange_ + #define igraphdlaexc_ dlaexc_ + #define igraphdlasy2_ dlasy2_ + #define igraphdlarfx_ dlarfx_ +#endif + +#if 0 /* internal f2c functions always used */ + #define igraphd_sign d_sign + #define igraphetime_ etime_ + #define igraphpow_dd pow_dd + #define igraphpow_di pow_di + #define igraphs_cmp s_cmp + #define igraphs_copy s_copy + #define igraphd_lg10_ d_lg10_ + #define igraphi_dnnt_ i_dnnt_ +#endif + +#ifdef HAVE_GFORTRAN + +/* GFortran-specific calling conventions, used when compiling the R interface. + * Derived with "gfortran -fc-prototypes-external", applied on the original + * Fortran sources of these functions. + * + * Caveats: + * + * 1) gfortran prints size_t for the "_len" arguments, but in fact they must be + * long int + * 2) gofrtran maps Fortran LOGICAL types to int_least32_t, but in fact they + * must be void* (anything else doesn't work, not even _Bool*) + * */ + +void igraphdsaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info, + long int bmat_len, long int which_len); + +void igraphdseupd_(void *rvec, char *howmny, void *select, + double *d, double *z, int *ldz, + double *sigma, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info, + long int howmny_len, long int bmat_len, long int which_len); + +void igraphdnaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info, + long int bmat_len, long int which_len); + +void igraphdneupd_(void *rvec, char *howmny, void *select, + double *dr, double *di, + double *z, int *ldz, + double *sigmar, double *sigmai, + double *workev, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info, + long int howmny_len, long int bmat_len, long int which_len); + +void igraphdsortr_(char *which, void *apply, int* n, double *x1, + double *x2, long int which_len); + +void igraphdsortc_(char *which, void *apply, int* n, double *xreal, + double *ximag, double *y, long int which_len); + +#else + +int igraphdsaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info); + +int igraphdseupd_(int *rvec, char *howmny, int *select, + double *d, double *z, int *ldz, + double *sigma, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info); + +int igraphdnaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info); + +int igraphdneupd_(int *rvec, char *howmny, int *select, + double *dr, double *di, + double *z, int *ldz, + double *sigmar, double *sigmai, + double *workev, char *bmat, int *n, + char *which, int *nev, double *tol, + double *resid, int *ncv, double *v, + int *ldv, int *iparam, int *ipntr, + double *workd, double *workl, + int *lworkl, int *info); + +int igraphdsortr_(char *which, int *apply, int* n, double *x1, + double *x2); + +int igraphdsortc_(char *which, int *apply, int* n, double *xreal, + double *ximag, double *y); + +#endif + +IGRAPH_END_C_DECLS + +#endif /* ARPACK_INTERNAL_H */ diff --git a/src/linalg/blas.c b/src/linalg/blas.c new file mode 100644 index 0000000..3e46ee1 --- /dev/null +++ b/src/linalg/blas.c @@ -0,0 +1,261 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_blas.h" +#include "linalg/blas_internal.h" + +#include + +/** + * \function igraph_blas_dgemv + * \brief Matrix-vector multiplication using BLAS, vector version. + * + * This function is a somewhat more user-friendly interface to + * the \c dgemv function in BLAS. \c dgemv performs the operation + * y = alpha*A*x + beta*y, where \p x and \p y are vectors + * and \p A is an appropriately sized matrix (symmetric or non-symmetric). + * + * \param transpose Whether to transpose the matrix \p A. + * \param alpha The constant \p alpha. + * \param a The matrix \p A. + * \param x The vector \p x. + * \param beta The constant \p beta. + * \param y The vector \p y (which will be modified in-place). + * It must always have the correct length, but its + * elements need not be set when beta=0. + * + * Time complexity: O(nk) if the matrix is of size n x k + * + * \return \c IGRAPH_EOVERFLOW if the matrix is too large for BLAS, + * \c IGRAPH_SUCCESS otherwise. + * \sa \ref igraph_blas_dgemv_array if you have arrays instead of + * vectors. + * + * \example examples/simple/blas.c + */ +igraph_error_t igraph_blas_dgemv(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t *a, const igraph_vector_t *x, + igraph_real_t beta, igraph_vector_t *y) { + char trans = transpose ? 'T' : 'N'; + int m, n; + int inc = 1; + + if (igraph_matrix_nrow(a) > INT_MAX || igraph_matrix_ncol(a) > INT_MAX) { + IGRAPH_ERROR("Matrix too large for BLAS.", IGRAPH_EOVERFLOW); + } + + m = (int) igraph_matrix_nrow(a); + n = (int) igraph_matrix_ncol(a); + + IGRAPH_ASSERT(igraph_vector_size(x) == transpose ? m : n); + IGRAPH_ASSERT(igraph_vector_size(y) == transpose ? n : m); + +#ifdef HAVE_GFORTRAN + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + VECTOR(*x), &inc, &beta, VECTOR(*y), &inc, /* trans_len = */ 1); +#else + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + VECTOR(*x), &inc, &beta, VECTOR(*y), &inc); +#endif + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_blas_dgemm + * \brief Matrix-matrix multiplication using BLAS. + * + * This function is a somewhat more user-friendly interface to + * the \c dgemm function in BLAS. \c dgemm calculates + * alpha*a*b + beta*c, where a, b and c are matrices, of which a and b + * can be transposed. + * + * \param transpose_a whether to transpose the matrix \p a + * \param transpose_b whether to transpose the matrix \p b + * \param alpha the constant \c alpha + * \param a the matrix \c a + * \param b the matrix \c b + * \param beta the constant \c beta + * \param c the matrix \c c. The result will also be stored here. + * If beta is zero, c will be resized to fit the result. + * + * Time complexity: O(n m k) where matrix a is of size n × k, and matrix b is of + * size k × m. + * + * \return \c IGRAPH_EOVERFLOW if the matrix is too large for BLAS, + * \c IGRAPH_EINVAL if the matrices have incompatible sizes, + * \c IGRAPH_SUCCESS otherwise. + * + * \example examples/simple/blas_dgemm.c + */ +igraph_error_t igraph_blas_dgemm(igraph_bool_t transpose_a, igraph_bool_t transpose_b, + igraph_real_t alpha, const igraph_matrix_t *a, const igraph_matrix_t *b, + igraph_real_t beta, igraph_matrix_t *c) { + char trans_a = transpose_a ? 'T' : 'N'; + char trans_b = transpose_b ? 'T' : 'N'; + int m, n, k, lda, ldb, ldc; + igraph_int_t nrow_oa = transpose_a ? igraph_matrix_ncol(a) : igraph_matrix_nrow(a); + igraph_int_t ncol_oa = transpose_a ? igraph_matrix_nrow(a) : igraph_matrix_ncol(a); + igraph_int_t nrow_ob = transpose_b ? igraph_matrix_ncol(b) : igraph_matrix_nrow(b); + igraph_int_t ncol_ob = transpose_b ? igraph_matrix_nrow(b) : igraph_matrix_ncol(b); + + if (ncol_oa != nrow_ob) { + IGRAPH_ERRORF("%" IGRAPH_PRId "-by-%" IGRAPH_PRId " and %" IGRAPH_PRId "-by-%" IGRAPH_PRId + " matrices cannot be multiplied, incompatible dimensions.", IGRAPH_EINVAL, + nrow_oa, ncol_oa, nrow_ob, ncol_ob); + } + if (beta != 0 && (ncol_oa != igraph_matrix_ncol(c) || nrow_oa != igraph_matrix_nrow(c))) { + IGRAPH_ERRORF("%" IGRAPH_PRId "-by-%" IGRAPH_PRId " and %" IGRAPH_PRId "-by-%" IGRAPH_PRId + " matrices cannot be added, incompatible dimensions.", IGRAPH_EINVAL, + nrow_oa, ncol_ob, igraph_matrix_nrow(c), igraph_matrix_ncol(c)); + } + if (nrow_oa > INT_MAX || ncol_oa > INT_MAX) { + IGRAPH_ERROR("Matrix A too large for BLAS.", IGRAPH_EOVERFLOW); + } + if (ncol_ob > INT_MAX) { + IGRAPH_ERROR("Matrix B too large for BLAS.", IGRAPH_EOVERFLOW); + } + if (beta == 0) { + IGRAPH_CHECK(igraph_matrix_resize(c, nrow_oa, ncol_ob)); + } + + m = (int) nrow_oa; + k = (int) ncol_oa; + n = (int) ncol_ob; + lda = (int) igraph_matrix_nrow(a); + ldb = (int) igraph_matrix_nrow(b); + ldc = (int) igraph_matrix_nrow(c); + + +#ifdef HAVE_GFORTRAN + igraphdgemm_(&trans_a, &trans_b, &m, &n, &k, &alpha, VECTOR(a->data), + &lda, VECTOR(b->data), &ldb, &beta, VECTOR(c->data), &ldc, + /*trans_a_len*/ 1, /*trans_b_len*/ 1); +#else + igraphdgemm_(&trans_a, &trans_b, &m, &n, &k, &alpha, VECTOR(a->data), + &lda, VECTOR(b->data), &ldb, &beta, VECTOR(c->data), &ldc); +#endif + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_blas_dgemv_array + * \brief Matrix-vector multiplication using BLAS, array version. + * + * This function is a somewhat more user-friendly interface to + * the \c dgemv function in BLAS. \c dgemv performs the operation + * y = alpha*A*x + beta*y, where x and y are vectors and A is an + * appropriately sized matrix (symmetric or non-symmetric). + * + * \param transpose whether to transpose the matrix \p A + * \param alpha the constant \p alpha + * \param a the matrix \p A + * \param x the vector \p x as a regular C array + * \param beta the constant \p beta + * \param y the vector \p y as a regular C array + * (which will be modified in-place) + * + * Time complexity: O(nk) if the matrix is of size n x k + * + * \return \c IGRAPH_EOVERFLOW if the matrix is too large for BLAS, + * \c IGRAPH_SUCCESS otherwise. + * + * \sa \ref igraph_blas_dgemv if you have vectors instead of + * arrays. + */ +igraph_error_t igraph_blas_dgemv_array(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_real_t* x, + igraph_real_t beta, igraph_real_t* y) { + char trans = transpose ? 'T' : 'N'; + int m, n; + int inc = 1; + + if (igraph_matrix_nrow(a) > INT_MAX || igraph_matrix_ncol(a) > INT_MAX) { + IGRAPH_ERROR("Matrix too large for BLAS.", IGRAPH_EOVERFLOW); + } + + m = (int) igraph_matrix_nrow(a); + n = (int) igraph_matrix_ncol(a); + +#ifdef HAVE_GFORTRAN + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + (igraph_real_t*)x, &inc, &beta, y, &inc, /* trans_len = */ 1); +#else + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + (igraph_real_t*)x, &inc, &beta, y, &inc); +#endif + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_blas_dnrm2 + * \brief Euclidean norm of a vector. + * + * \param v The vector. + * \return Real value, the norm of \p v. + * + * Time complexity: O(n) where n is the length of the vector. + */ +igraph_real_t igraph_blas_dnrm2(const igraph_vector_t *v) { + if (igraph_vector_size(v) > INT_MAX) { + IGRAPH_ERROR("Vector too large for BLAS.", IGRAPH_EOVERFLOW); + } + + int n = (int) igraph_vector_size(v); + int one = 1; + return igraphdnrm2_(&n, VECTOR(*v), &one); +} + +/** + * \function igraph_blas_ddot + * \brief Dot product of two vectors. + * + * \param v1 The first vector. + * \param v2 The second vector. + * \param res Pointer to a real, the result will be stored here. + * \return Error code. + * + * Time complexity: O(n) where n is the length of the vectors. + * + * \example examples/simple/blas.c + */ +igraph_error_t igraph_blas_ddot(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *res) { + + if (igraph_vector_size(v1) > INT_MAX) { + IGRAPH_ERROR("Vector too large for BLAS.", IGRAPH_EOVERFLOW); + } + + int n = (int) igraph_vector_size(v1); + int one = 1; + + if (igraph_vector_size(v2) != n) { + IGRAPH_ERROR("Dot product of vectors with different dimensions.", + IGRAPH_EINVAL); + } + + *res = igraphddot_(&n, VECTOR(*v1), &one, VECTOR(*v2), &one); + + return IGRAPH_SUCCESS; +} diff --git a/src/linalg/blas_internal.h b/src/linalg/blas_internal.h new file mode 100644 index 0000000..485fcad --- /dev/null +++ b/src/linalg/blas_internal.h @@ -0,0 +1,92 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef BLAS_INTERNAL_H +#define BLAS_INTERNAL_H + +/* Note: only files calling the BLAS routines directly need to + include this header. +*/ + +#include "igraph_decls.h" +#include "config.h" /* INTERNAL_BLAS */ + +IGRAPH_BEGIN_C_DECLS + +#ifndef INTERNAL_BLAS + #define igraphdaxpy_ daxpy_ + #define igraphdger_ dger_ + #define igraphdcopy_ dcopy_ + #define igraphdscal_ dscal_ + #define igraphdswap_ dswap_ + #define igraphdgemm_ dgemm_ + #define igraphdgemv_ dgemv_ + #define igraphddot_ ddot_ + #define igraphdnrm2_ dnrm2_ + #define igraphlsame_ lsame_ + #define igraphdrot_ drot_ + #define igraphidamax_ idamax_ + #define igraphdtrmm_ dtrmm_ + #define igraphdasum_ dasum_ + #define igraphdtrsm_ dtrsm_ + #define igraphdtrsv_ dtrsv_ + #define igraphdnrm2_ dnrm2_ + #define igraphdsymv_ dsymv_ + #define igraphdsyr2_ dsyr2_ + #define igraphdsyr2k_ dsyr2k_ + #define igraphdtrmv_ dtrmv_ + #define igraphdsyrk_ dsyrk_ +#endif + +#ifdef HAVE_GFORTRAN + +/* GFortran-specific calling conventions, used when compiling the R interface. + * Derived with "gfortran -fc-prototypes-external", applied on the original + * Fortran sources of these functions. */ + +void igraphdgemv_(char *trans, int *m, int *n, double *alpha, + double *a, int *lda, double *x, int *incx, + double *beta, double *y, int *incy, long int trans_len); + +void igraphdgemm_(char *transa, char *transb, int *m, int *n, int *k, + double *alpha, double *a, int *lda, double *b, int *ldb, + double *beta, double *c__, int *ldc, long int transa_len, long int transb_len); + +#else + +int igraphdgemv_(char *trans, int *m, int *n, double *alpha, + double *a, int *lda, double *x, int *incx, + double *beta, double *y, int *incy); + +int igraphdgemm_(char *transa, char *transb, int *m, int *n, int *k, + double *alpha, double *a, int *lda, double *b, int *ldb, + double *beta, double *c__, int *ldc); + +#endif + +double igraphdnrm2_(int *n, double *x, int *incx); + +double igraphddot_(int *n, double *dx, int *incx, double *dy, int *incy); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/linalg/eigen.c b/src/linalg/eigen.c new file mode 100644 index 0000000..d999d0a --- /dev/null +++ b/src/linalg/eigen.c @@ -0,0 +1,1466 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_eigen.h" + +#include "igraph_qsort.h" +#include "igraph_blas.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" + +#include +#include +#include +#include + +static igraph_error_t igraph_i_eigen_arpackfun_to_mat(igraph_arpack_function_t *fun, + int n, void *extra, + igraph_matrix_t *res) { + + int i; + igraph_vector_t v; + + IGRAPH_CHECK(igraph_matrix_init(res, n, n)); + IGRAPH_FINALLY(igraph_matrix_destroy, res); + IGRAPH_VECTOR_INIT_FINALLY(&v, n); + VECTOR(v)[0] = 1; + IGRAPH_CHECK(fun(/*to=*/ &MATRIX(*res, 0, 0), /*from=*/ VECTOR(v), n, + extra)); + for (i = 1; i < n; i++) { + VECTOR(v)[i - 1] = 0; + VECTOR(v)[i ] = 1; + IGRAPH_CHECK(fun(/*to=*/ &MATRIX(*res, 0, i), /*from=*/ VECTOR(v), n, + extra)); + } + igraph_vector_destroy(&v); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_lm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_matrix_t vec1, vec2; + igraph_vector_t val1, val2; + int p1 = 0, p2 = which->howmany - 1, pr = 0; + int n; + + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_matrix_nrow(A); + + IGRAPH_VECTOR_INIT_FINALLY(&val1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&val2, 0); + + if (vectors) { + IGRAPH_CHECK(igraph_matrix_init(&vec1, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + IGRAPH_CHECK(igraph_matrix_init(&vec2, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ which->howmany, + /*abstol=*/ 1e-14, &val1, + vectors ? &vec1 : 0, + /*support=*/ 0)); + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ n - which->howmany + 1, /*iu=*/ n, + /*abstol=*/ 1e-14, &val2, + vectors ? &vec2 : 0, + /*support=*/ 0)); + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (p2 < 0 || fabs(VECTOR(val1)[p1]) > fabs(VECTOR(val2)[p2])) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val1)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec1, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1++; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val2)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec2, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2--; + pr++; + } + } + + + if (vectors) { + igraph_matrix_destroy(&vec2); + igraph_matrix_destroy(&vec1); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&val2); + igraph_vector_destroy(&val1); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_sm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_vector_t val; + igraph_matrix_t vec; + int i, w = 0, n; + igraph_real_t small; + int p1, p2, pr = 0; + + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_matrix_nrow(A); + + IGRAPH_VECTOR_INIT_FINALLY(&val, 0); + + if (vectors) { + IGRAPH_MATRIX_INIT_FINALLY(&vec, 0, 0); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_ALL, /*vl=*/ 0, + /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, &val, + vectors ? &vec : 0, + /*support=*/ 0)); + + /* Look for smallest value */ + small = fabs(VECTOR(val)[0]); + for (i = 1; i < n; i++) { + igraph_real_t v = fabs(VECTOR(val)[i]); + if (v < small) { + small = v; + w = i; + } + } + p1 = w - 1; p2 = w; + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (p2 == n - 1 || fabs(VECTOR(val)[p1]) < fabs(VECTOR(val)[p2])) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1--; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2++; + pr++; + } + } + + if (vectors) { + igraph_matrix_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&val); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_la(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + + int n = (int) igraph_matrix_nrow(A); + int il = n - which->howmany + 1; + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ il, /*iu=*/ n, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_sa(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ which->howmany, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_be(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + + igraph_matrix_t vec1, vec2; + igraph_vector_t val1, val2; + int n; + int p1 = 0, p2 = which->howmany / 2, pr = 0; + + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_matrix_nrow(A); + + IGRAPH_VECTOR_INIT_FINALLY(&val1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&val2, 0); + + if (vectors) { + IGRAPH_CHECK(igraph_matrix_init(&vec1, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + IGRAPH_CHECK(igraph_matrix_init(&vec2, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ (which->howmany) / 2, + /*abstol=*/ 1e-14, &val1, + vectors ? &vec1 : 0, + /*support=*/ 0)); + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ n - (which->howmany) / 2, /*iu=*/ n, + /*abstol=*/ 1e-14, &val2, + vectors ? &vec2 : 0, + /*support=*/ 0)); + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (pr % 2) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val1)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec1, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1++; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val2)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec2, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2--; + pr++; + } + } + + if (vectors) { + igraph_matrix_destroy(&vec2); + igraph_matrix_destroy(&vec1); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&val2); + igraph_vector_destroy(&val1); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_all(const igraph_matrix_t *A, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_ALL, /*vl=*/ 0, + /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_iv(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_INTERVAL, + /*vl=*/ which->vl, /*vu=*/ which->vu, + /*vestimate=*/ which->vestimate, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack_sel(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ which->il, /*iu=*/ which->iu, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_lapack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + const igraph_matrix_t *myA = A; + igraph_matrix_t mA; + + /* First we need to create a dense square matrix */ + + if (A) { + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_matrix_nrow(A); /* TODO: n isn't used after this assignment */ + } else if (sA) { + if (igraph_sparsemat_nrow(sA) > INT_MAX) { + IGRAPH_ERROR("Number of rows in sparse matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_sparsemat_nrow(sA); /* TODO: n isn't used after this assignment */ + IGRAPH_CHECK(igraph_matrix_init(&mA, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + IGRAPH_CHECK(igraph_sparsemat_as_matrix(&mA, sA)); + myA = &mA; + } else if (fun) { + IGRAPH_CHECK(igraph_i_eigen_arpackfun_to_mat(fun, n, extra, &mA)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + myA = &mA; + } + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_lm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SM: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_LA: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_la(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SA: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sa(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_BE: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_be(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_ALL: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_all(myA, + values, + vectors)); + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_iv(myA, which, + values, + vectors)); + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sel(myA, which, + values, + vectors)); + break; + default: + /* This cannot happen */ + break; + } + + if (!A) { + igraph_matrix_destroy(&mA); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +typedef struct igraph_i_eigen_matrix_sym_arpack_data_t { + const igraph_matrix_t *A; + const igraph_sparsemat_t *sA; +} igraph_i_eigen_matrix_sym_arpack_data_t; + +static igraph_error_t igraph_i_eigen_matrix_sym_arpack_cb(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_eigen_matrix_sym_arpack_data_t *data = + (igraph_i_eigen_matrix_sym_arpack_data_t *) extra; + + if (data->A) { + IGRAPH_CHECK(igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, + data->A, from, /*beta=*/ 0.0, to)); + } else { /* data->sA */ + const igraph_vector_t vfrom = igraph_vector_view(from, n); + igraph_vector_t vto = igraph_vector_view(to, n); + igraph_vector_null(&vto); + igraph_sparsemat_gaxpy(data->sA, &vfrom, &vto); + } + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_arpack_be(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_vector_t tmpvalues, tmpvalues2; + igraph_matrix_t tmpvectors, tmpvectors2; + + int low = (int) floor(which->howmany / 2.0), high = (int) ceil(which->howmany / 2.0); + int l1, l2, w; + + igraph_i_eigen_matrix_sym_arpack_data_t myextra; + myextra.A = A; + myextra.sA = sA; + + if (low + high >= n) { + IGRAPH_ERROR("Requested too many eigenvalues/vectors", IGRAPH_EINVAL); + } + + if (!fun) { + fun = igraph_i_eigen_matrix_sym_arpack_cb; + extra = (void*) &myextra; + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmpvalues, high); + IGRAPH_MATRIX_INIT_FINALLY(&tmpvectors, n, high); + IGRAPH_VECTOR_INIT_FINALLY(&tmpvalues2, low); + IGRAPH_MATRIX_INIT_FINALLY(&tmpvectors2, n, low); + + options->n = n; + options->nev = high; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + options->which[0] = 'L'; options->which[1] = 'A'; + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + &tmpvalues, &tmpvectors)); + + options->nev = low; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + options->which[0] = 'S'; options->which[1] = 'A'; + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + &tmpvalues2, &tmpvectors2)); + + IGRAPH_CHECK(igraph_vector_resize(values, low + high)); + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, low + high)); + + l1 = 0; l2 = 0; w = 0; + while (w < which->howmany) { + VECTOR(*values)[w] = VECTOR(tmpvalues)[l1]; + memcpy(&MATRIX(*vectors, 0, w), &MATRIX(tmpvectors, 0, l1), + (size_t) n * sizeof(igraph_real_t)); + w++; l1++; + if (w < which->howmany) { + VECTOR(*values)[w] = VECTOR(tmpvalues2)[l2]; + memcpy(&MATRIX(*vectors, 0, w), &MATRIX(tmpvectors2, 0, l2), + (size_t) n * sizeof(igraph_real_t)); + w++; l2++; + } + } + + igraph_matrix_destroy(&tmpvectors2); + igraph_vector_destroy(&tmpvalues2); + igraph_matrix_destroy(&tmpvectors); + igraph_vector_destroy(&tmpvalues); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_symmetric_arpack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* For ARPACK we need a matrix multiplication operation. + This can be done in any format, so everything is fine, + we don't have to convert. */ + + igraph_i_eigen_matrix_sym_arpack_data_t myextra; + + myextra.A = A; + myextra.sA = sA; + + if (!options) { + IGRAPH_ERROR("`options' must be given for ARPACK algorithm", + IGRAPH_EINVAL); + } + + if (which->pos == IGRAPH_EIGEN_BE) { + return igraph_i_eigen_matrix_symmetric_arpack_be(A, sA, fun, n, extra, + which, options, storage, + values, vectors); + } else { + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SM: + options->which[0] = 'S'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = n; + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_ERROR("Interval of eigenvectors with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_ERROR("Selected eigenvalues with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + /* This cannot happen */ + break; + } + + options->n = n; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + + if (!fun) { + fun = igraph_i_eigen_matrix_sym_arpack_cb; + extra = (void*) &myextra; + } + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + values, vectors)); + return IGRAPH_SUCCESS; + } +} + +/* Get the eigenvalues and the eigenvectors from the compressed + form. Order them according to the ordering criteria. + Comparison functions for the reordering first */ + +typedef int (*igraph_i_eigen_matrix_lapack_cmp_t)(void*, const void*, + const void *); + +typedef struct igraph_i_eml_cmp_t { + const igraph_vector_t *mag, *real, *imag; +} igraph_i_eml_cmp_t; + +/* TODO: these should be defined in some header */ + +#define EPS (DBL_EPSILON*100) +#define LESS(a,b) ((a) < (b)-EPS) +#define MORE(a,b) ((a) > (b)+EPS) +#define ZERO(a) ((a) > -EPS && (a) < EPS) +#define NONZERO(a) ((a) < -EPS || (a) > EPS) + +/* Largest magnitude. Ordering is according to + 1 Larger magnitude + 2 Real eigenvalues before complex ones + 3 Larger real part + 4 Larger imaginary part */ + +static int igraph_i_eigen_matrix_lapack_cmp_lm(void *extra, const void *a, + const void *b) { + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_m = VECTOR(*myextra->mag)[*aa]; + igraph_real_t b_m = VECTOR(*myextra->mag)[*bb]; + + if (LESS(a_m, b_m)) { + return 1; + } else if (MORE(a_m, b_m)) { + return -1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_r, b_r)) { + return -1; + } + if (LESS(a_r, b_r)) { + return 1; + } + if (MORE(a_i, b_i)) { + return -1; + } + if (LESS(a_i, b_i)) { + return 1; + } + } + return 0; +} + +/* Smallest marginude. Ordering is according to + 1 Magnitude (smaller first) + 2 Complex eigenvalues before real ones + 3 Smaller real part + 4 Smaller imaginary part + This ensures that lm has exactly the opposite order to sm */ + +static int igraph_i_eigen_matrix_lapack_cmp_sm(void *extra, const void *a, + const void *b) { + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_m = VECTOR(*myextra->mag)[*aa]; + igraph_real_t b_m = VECTOR(*myextra->mag)[*bb]; + + if (MORE(a_m, b_m)) { + return 1; + } else if (LESS(a_m, b_m)) { + return -1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_r, b_r)) { + return -1; + } + if (MORE(a_r, b_r)) { + return 1; + } + if (LESS(a_i, b_i)) { + return -1; + } + if (MORE(a_i, b_i)) { + return 1; + } + } + return 0; +} + +/* Largest real part. Ordering is according to + 1 Larger real part + 2 Real eigenvalues come before complex ones + 3 Larger complex part */ + +static int igraph_i_eigen_matrix_lapack_cmp_lr(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + + if (MORE(a_r, b_r)) { + return -1; + } else if (LESS(a_r, b_r)) { + return 1; + } else { + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_i, b_i)) { + return -1; + } + if (LESS(a_i, b_i)) { + return 1; + } + } + + return 0; +} + +/* Largest real part. Ordering is according to + 1 Smaller real part + 2 Complex eigenvalues come before real ones + 3 Smaller complex part + This is opposite to LR +*/ + +static int igraph_i_eigen_matrix_lapack_cmp_sr(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + + if (LESS(a_r, b_r)) { + return -1; + } else if (MORE(a_r, b_r)) { + return 1; + } else { + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_i, b_i)) { + return -1; + } + if (MORE(a_i, b_i)) { + return 1; + } + } + + return 0; +} + +/* Order: + 1 Larger imaginary part + 2 Real eigenvalues before complex ones + 3 Larger real part */ + +static int igraph_i_eigen_matrix_lapack_cmp_li(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + + if (MORE(a_i, b_i)) { + return -1; + } else if (LESS(a_i, b_i)) { + return 1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_r, b_r)) { + return -1; + } + if (LESS(a_r, b_r)) { + return 1; + } + } + + return 0; +} + +/* Order: + 1 Smaller imaginary part + 2 Complex eigenvalues before real ones + 3 Smaller real part + Order is opposite to LI */ + +static int igraph_i_eigen_matrix_lapack_cmp_si(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + + if (LESS(a_i, b_i)) { + return -1; + } else if (MORE(a_i, b_i)) { + return 1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_r, b_r)) { + return -1; + } + if (MORE(a_r, b_r)) { + return 1; + } + } + + return 0; +} + +#undef EPS +#undef LESS +#undef MORE +#undef ZERO +#undef NONZERO + +#define INITMAG() \ + do { \ + int i; \ + IGRAPH_VECTOR_INIT_FINALLY(&mag, nev); \ + hasmag=1; \ + for (i=0; i INT_MAX) { + IGRAPH_ERROR("Number of eigenvalues too large for LAPACK.", IGRAPH_EOVERFLOW); + } + nev = (int) igraph_vector_size(real); + + vextra.mag = &mag; + vextra.real = real; + vextra.imag = imag; + extra = &vextra; + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_lm; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + howmany = nev; + break; + case IGRAPH_EIGEN_SM: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_LR: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_lr; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SR: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sr; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SELECT: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + start = which->il - 1; + howmany = which->iu - which->il + 1; + break; + case IGRAPH_EIGEN_LI: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_li; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SI: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_si; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_INTERVAL: + case IGRAPH_EIGEN_BE: + default: + IGRAPH_ERROR("Unimplemented eigenvalue ordering", IGRAPH_UNIMPLEMENTED); + break; + } + + IGRAPH_CHECK(igraph_vector_int_init_range(&idx, 0, nev)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &idx); + + igraph_qsort_r(VECTOR(idx), (size_t) nev, sizeof(VECTOR(idx)[0]), extra, + cmpfunc); + + if (hasmag) { + igraph_vector_destroy(&mag); + IGRAPH_FINALLY_CLEAN(1); + } + + if (values) { + IGRAPH_CHECK(igraph_vector_complex_resize(values, howmany)); + for (i = 0; i < howmany; i++) { + igraph_int_t x = VECTOR(idx)[start + i]; + VECTOR(*values)[i] = igraph_complex(VECTOR(*real)[x], + VECTOR(*imag)[x]); + } + } + + if (vectors) { + igraph_int_t n = igraph_matrix_nrow(compressed); + IGRAPH_CHECK(igraph_matrix_complex_resize(vectors, n, howmany)); + for (i = 0; i < howmany; i++) { + igraph_int_t j, x = VECTOR(idx)[start + i]; + if (VECTOR(*imag)[x] == 0) { + /* real eigenvalue */ + for (j = 0; j < n; j++) { + MATRIX(*vectors, j, i) = igraph_complex(MATRIX(*compressed, j, x), + 0.0); + } + } else { + /* complex eigenvalue */ + int neg = 1, co = 0; + if (VECTOR(*imag)[x] < 0) { + neg = -1; + co = 1; + } + for (j = 0; j < n; j++) { + MATRIX(*vectors, j, i) = + igraph_complex(MATRIX(*compressed, j, x - co), + neg * MATRIX(*compressed, j, x + 1 - co)); + } + } + } + } + + igraph_vector_int_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_matrix_lapack_common(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + igraph_vector_t valuesreal, valuesimag; + igraph_matrix_t vectorsright, *myvectors = vectors ? &vectorsright : 0; + igraph_int_t n = igraph_matrix_nrow(A); + int info = 1; + + IGRAPH_VECTOR_INIT_FINALLY(&valuesreal, n); + IGRAPH_VECTOR_INIT_FINALLY(&valuesimag, n); + if (vectors) { + IGRAPH_MATRIX_INIT_FINALLY(&vectorsright, n, n); + } + IGRAPH_CHECK(igraph_lapack_dgeev(A, &valuesreal, &valuesimag, + /*vectorsleft=*/ 0, myvectors, &info)); + + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_reorder(&valuesreal, + &valuesimag, + myvectors, which, values, + vectors)); + + if (vectors) { + igraph_matrix_destroy(&vectorsright); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&valuesimag); + igraph_vector_destroy(&valuesreal); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; + +} + +static igraph_error_t igraph_i_eigen_matrix_lapack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + const igraph_matrix_t *myA = A; + igraph_matrix_t mA; + + /* We need to create a dense square matrix first */ + + if (A) { + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_matrix_nrow(A); + } else if (sA) { + if (igraph_sparsemat_nrow(sA) > INT_MAX) { + IGRAPH_ERROR("Number of rows in sparse matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + n = (int) igraph_sparsemat_nrow(sA); + IGRAPH_CHECK(igraph_matrix_init(&mA, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + IGRAPH_CHECK(igraph_sparsemat_as_matrix(&mA, sA)); + myA = &mA; + } else if (fun) { + IGRAPH_CHECK(igraph_i_eigen_arpackfun_to_mat(fun, n, extra, &mA)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + } + + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_common(myA, which, + values, + vectors)); + + if (!A) { + igraph_matrix_destroy(&mA); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_checks(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n) { + + if ( (A ? 1 : 0) + (sA ? 1 : 0) + (fun ? 1 : 0) != 1) { + IGRAPH_ERROR("Exactly one of 'A', 'sA' and 'fun' must be given", + IGRAPH_EINVAL); + } + + if (A) { + if (n != igraph_matrix_ncol(A) || n != igraph_matrix_nrow(A)) { + IGRAPH_ERROR("Eigenvector calculations need a square matrix.", IGRAPH_EINVAL); + } + } else if (sA) { + if (n != igraph_sparsemat_ncol(sA) || n != igraph_sparsemat_nrow(sA)) { + IGRAPH_ERROR("Eigenvector calculations need a square matrix.", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eigen_matrix_symmetric + * + * \example examples/simple/igraph_eigen_matrix_symmetric.c + */ + +igraph_error_t igraph_eigen_matrix_symmetric(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_i_eigen_checks(A, sA, fun, n)); + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LA && + which->pos != IGRAPH_EIGEN_SA && + which->pos != IGRAPH_EIGEN_BE && + which->pos != IGRAPH_EIGEN_ALL && + which->pos != IGRAPH_EIGEN_INTERVAL && + which->pos != IGRAPH_EIGEN_SELECT) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + if (algorithm == IGRAPH_EIGEN_AUTO) { + if (which->howmany == n || n < 100) { + algorithm = IGRAPH_EIGEN_LAPACK; + } else { + algorithm = IGRAPH_EIGEN_ARPACK; + } + } + + switch (algorithm) { + case IGRAPH_EIGEN_LAPACK: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack(A, sA, fun, n, extra, + which, values, vectors)); + break; + case IGRAPH_EIGEN_ARPACK: + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_arpack(A, sA, fun, n, extra, + which, options, storage, values, vectors)); + break; + default: + IGRAPH_ERROR("Unknown 'algorithm'", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eigen_matrix + * + */ + +igraph_error_t igraph_eigen_matrix(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + IGRAPH_UNUSED(options); + IGRAPH_UNUSED(storage); + + IGRAPH_CHECK(igraph_i_eigen_checks(A, sA, fun, n)); + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LR && + which->pos != IGRAPH_EIGEN_SR && + which->pos != IGRAPH_EIGEN_LI && + which->pos != IGRAPH_EIGEN_SI && + which->pos != IGRAPH_EIGEN_SELECT && + which->pos != IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + switch (algorithm) { + case IGRAPH_EIGEN_AUTO: + IGRAPH_ERROR("'AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_LAPACK: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack(A, sA, fun, n, extra, which, + values, vectors)); + /* TODO */ + break; + case IGRAPH_EIGEN_ARPACK: + IGRAPH_ERROR("'ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_AUTO: + IGRAPH_ERROR("'COMP_AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_LAPACK: + IGRAPH_ERROR("'COMP_LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_ARPACK: + IGRAPH_ERROR("'COMP_ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + IGRAPH_ERROR("Unknown `algorithm'", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_adjacency_arpack_sym_cb(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_adjlist_t *adjlist = (igraph_adjlist_t *) extra; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += from[nei]; + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_eigen_adjacency_arpack(const igraph_t *graph, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t* storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors) { + + IGRAPH_UNUSED(cmplxvalues); + IGRAPH_UNUSED(cmplxvectors); + + igraph_adjlist_t adjlist; + void *extra = (void*) &adjlist; + igraph_int_t n = igraph_vcount(graph); + + if (!options) { + IGRAPH_ERROR("`options' must be given for ARPACK algorithm", + IGRAPH_EINVAL); + } + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("ARPACK adjacency eigensolver not implemented for " + "directed graphs", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_INTERVAL) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`INTERNAL' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_SELECT) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`SELECT' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`ALL' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + if (n > INT_MAX) { + IGRAPH_ERROR("Graph has too many vertices for ARPACK.", IGRAPH_EOVERFLOW); + } + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SM: + options->which[0] = 'S'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = (int) n; + break; + case IGRAPH_EIGEN_BE: + IGRAPH_ERROR("Eigenvectors from both ends with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_ERROR("Interval of eigenvectors with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_ERROR("Selected eigenvalues with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + /* This cannot happen */ + break; + } + + options->n = (int) n; + options->ncv = 2 * options->nev < options->n ? 2 * options->nev : options->n; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_eigen_adjacency_arpack_sym_cb, + extra, options, storage, values, vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eigen_adjacency + * + */ + +igraph_error_t igraph_eigen_adjacency(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors) { + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LA && + which->pos != IGRAPH_EIGEN_SA && + which->pos != IGRAPH_EIGEN_BE && + which->pos != IGRAPH_EIGEN_SELECT && + which->pos != IGRAPH_EIGEN_INTERVAL && + which->pos != IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + if (algorithm == IGRAPH_EIGEN_AUTO) { + /* Select ARPACK unconditionally because nothing else is implemented yet */ + algorithm = IGRAPH_EIGEN_ARPACK; + } else if (algorithm == IGRAPH_EIGEN_COMP_AUTO) { + /* Select ARPACK unconditionally because nothing else is implemented yet */ + algorithm = IGRAPH_EIGEN_COMP_ARPACK; + } + + switch (algorithm) { + case IGRAPH_EIGEN_LAPACK: + IGRAPH_ERROR("'LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_ARPACK: + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + IGRAPH_CHECK(igraph_i_eigen_adjacency_arpack(graph, which, options, + storage, values, vectors, + cmplxvalues, + cmplxvectors)); + break; + case IGRAPH_EIGEN_COMP_LAPACK: + IGRAPH_ERROR("'COMP_LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_ARPACK: + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + IGRAPH_ERROR("'COMP_ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + IGRAPH_ERROR("Unknown `algorithm'", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/linalg/lapack.c b/src/linalg/lapack.c new file mode 100644 index 0000000..40fc7da --- /dev/null +++ b/src/linalg/lapack.c @@ -0,0 +1,1057 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_lapack.h" +#include "linalg/lapack_internal.h" + +#include + +#define BASE_FORTRAN_INT +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_vector_pmt.h" +#include "core/vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_FORTRAN_INT + +/* Converts a Fortran integer vector to an igraph vector */ +static igraph_error_t igraph_vector_int_update_from_fortran( + igraph_vector_int_t* vec, const igraph_vector_fortran_int_t* fortran_vec +) { + igraph_int_t size = igraph_vector_fortran_int_size(fortran_vec); + + IGRAPH_CHECK(igraph_vector_int_resize(vec, size)); + + for (igraph_int_t i = 0; i < size; i++) { + VECTOR(*vec)[i] = VECTOR(*fortran_vec)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Allocates a Fortran integer vector from the contents of an igraph vector */ +static igraph_error_t igraph_vector_int_copy_to_fortran( + const igraph_vector_int_t* vec, igraph_vector_fortran_int_t* fortran_vec +) { + igraph_int_t i, size = igraph_vector_int_size(vec); + + IGRAPH_CHECK(igraph_vector_fortran_int_resize(fortran_vec, size)); + + for (i = 0; i < size; i++) { + if (VECTOR(*vec)[i] > INT_MAX) { + IGRAPH_ERROR( + "Overflow error while copying an igraph integer vector to a " + "Fortran integer vector.", IGRAPH_EOVERFLOW + ); + } + VECTOR(*fortran_vec)[i] = (int) VECTOR(*vec)[i]; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dgetrf + * \brief LU factorization of a general M-by-N matrix. + * + * The factorization has the form + * A = P * L * U + * where P is a permutation matrix, L is lower triangular with unit + * diagonal elements (lower trapezoidal if m > n), and U is upper + * triangular (upper trapezoidal if m < n). + * \param a The input/output matrix. On entry, the M-by-N matrix to be + * factored. On exit, the factors L and U from the factorization + * A = P * L * U; the unit diagonal elements of L are not + * stored. + * \param ipiv An integer vector, the pivot indices are stored here, + * unless it is a null pointer. Row \c i of the matrix was + * interchanged with row ipiv[i]. + * \param info LAPACK error code. Zero on successful exit. If its value is + * a positive number i, it indicates that U(i,i) is exactly zero. + * The factorization has been + * completed, but the factor U is exactly singular, and division + * by zero will occur if it is used to solve a system of + * equations. If LAPACK returns an error, i.e. a negative info + * value, then an igraph error is generated as well. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_lapack_dgetrf(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + int *info) { + int m; + int n; + size_t num_elts; + int lda; + igraph_vector_fortran_int_t vipiv; + + if (igraph_matrix_nrow(a) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + if (igraph_matrix_ncol(a) > INT_MAX) { + IGRAPH_ERROR("Number of columns in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + + m = (int) igraph_matrix_nrow(a); + n = (int) igraph_matrix_ncol(a); + num_elts = m < n ? m : n; + lda = m > 0 ? m : 1; + + IGRAPH_CHECK(igraph_vector_fortran_int_init(&vipiv, num_elts)); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &vipiv); + + igraphdgetrf_(&m, &n, VECTOR(a->data), &lda, VECTOR(vipiv), info); + + if (*info > 0) { + IGRAPH_WARNING("LU: factor is exactly singular."); + } else if (*info < 0) { + switch (*info) { + case -1: + IGRAPH_ERROR("Invalid number of rows.", IGRAPH_FAILURE); + break; + case -2: + IGRAPH_ERROR("Invalid number of columns.", IGRAPH_FAILURE); + break; + case -3: + IGRAPH_ERROR("Invalid input matrix.", IGRAPH_FAILURE); + break; + case -4: + IGRAPH_ERROR("Invalid LDA parameter.", IGRAPH_FAILURE); + break; + case -5: + IGRAPH_ERROR("Invalid pivot vector.", IGRAPH_FAILURE); + break; + case -6: + IGRAPH_ERROR("Invalid info argument.", IGRAPH_FAILURE); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error.", IGRAPH_FAILURE); + break; + } + } + + if (ipiv) { + IGRAPH_CHECK(igraph_vector_int_update_from_fortran(ipiv, &vipiv)); + } + + igraph_vector_fortran_int_destroy(&vipiv); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dgetrs + * \brief Solve general system of linear equations using LU factorization. + * + * This function calls LAPACK to solve a system of linear equations + * A * X = B or A' * X = B + * with a general N-by-N matrix A using the LU factorization + * computed by \ref igraph_lapack_dgetrf. + * \param transpose Boolean, whether to transpose the input + * matrix. + * \param a A matrix containing the L and U factors from the + * factorization A = P*L*U. L is expected to be unitriangular, + * diagonal entries are those of U. If A is singular, no warning or + * error wil be given and random output will be returned. + * \param ipiv An integer vector, the pivot indices from + * \ref igraph_lapack_dgetrf() must be given here. Row \c i of A was + * interchanged with row ipiv[i]. + * \param b The right hand side matrix must be given here. The solution + will also be placed here. + * \return Error code. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_lapack_dgetrs(igraph_bool_t transpose, const igraph_matrix_t *a, + const igraph_vector_int_t *ipiv, igraph_matrix_t *b) { + char trans = transpose ? 'T' : 'N'; + int n; + int nrhs; + int lda; + int ldb; + int info; + igraph_vector_fortran_int_t vipiv; + + if (igraph_matrix_nrow(a) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + if (igraph_matrix_ncol(a) > INT_MAX) { + IGRAPH_ERROR("Number of columns in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + + n = (int) igraph_matrix_nrow(a); + nrhs = (int) igraph_matrix_ncol(b); + lda = n > 0 ? n : 1; + ldb = n > 0 ? n : 1; + + if (n != igraph_matrix_ncol(a)) { + IGRAPH_ERROR("Cannot LU solve non-square matrix.", IGRAPH_EINVAL); + } + if (n != igraph_matrix_nrow(b)) { + IGRAPH_ERROR("Cannot LU solve matrix, RHS of wrong size.", IGRAPH_EINVAL); + } + if (! igraph_vector_int_isininterval(ipiv, 1, n)) { + IGRAPH_ERROR("Pivot index out of range.", IGRAPH_EINVAL); + } + if (igraph_vector_int_size(ipiv) != n) { + IGRAPH_ERROR("Pivot vector length must match number of matrix rows.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_fortran_int_init(&vipiv, igraph_vector_int_size(ipiv))); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &vipiv); + IGRAPH_CHECK(igraph_vector_int_copy_to_fortran(ipiv, &vipiv)); + + igraphdgetrs_(&trans, &n, &nrhs, VECTOR(a->data), &lda, VECTOR(vipiv), + VECTOR(b->data), &ldb, &info); + + igraph_vector_fortran_int_destroy(&vipiv); + IGRAPH_FINALLY_CLEAN(1); + + if (info < 0) { + switch (info) { + case -1: + IGRAPH_ERROR("Invalid transpose argument.", IGRAPH_FAILURE); + break; + case -2: + IGRAPH_ERROR("Invalid number of rows/columns.", IGRAPH_FAILURE); + break; + case -3: + IGRAPH_ERROR("Invalid number of RHS vectors.", IGRAPH_FAILURE); + break; + case -4: + IGRAPH_ERROR("Invalid LU matrix.", IGRAPH_FAILURE); + break; + case -5: + IGRAPH_ERROR("Invalid LDA parameter.", IGRAPH_FAILURE); + break; + case -6: + IGRAPH_ERROR("Invalid pivot vector.", IGRAPH_FAILURE); + break; + case -7: + IGRAPH_ERROR("Invalid RHS matrix.", IGRAPH_FAILURE); + break; + case -8: + IGRAPH_ERROR("Invalid LDB parameter.", IGRAPH_FAILURE); + break; + case -9: + IGRAPH_ERROR("Invalid info argument.", IGRAPH_FAILURE); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error.", IGRAPH_FAILURE); + break; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dgesv + * \brief Solve system of linear equations with LU factorization. + * + * This function computes the solution to a real system of linear + * equations A * X = B, where A is an N-by-N matrix and X and B are + * N-by-NRHS matrices. + * + * The LU decomposition with partial pivoting and row + * interchanges is used to factor A as + * A = P * L * U, + * where P is a permutation matrix, L is unit lower triangular, and U is + * upper triangular. The factored form of A is then used to solve the + * system of equations A * X = B. + * + * \param a Matrix. On entry the N-by-N coefficient matrix, on exit, + * the factors L and U from the factorization A=P*L*U; the unit + * diagonal elements of L are not stored. + * \param ipiv An integer vector or a null pointer. If not a null + * pointer, then the pivot indices that define the permutation + * matrix P, are stored here. Row i of the matrix was + * interchanged with row IPIV(i). + * \param b Matrix, on entry the right hand side matrix should be + * stored here. On exit, if there was no error, and the info + * argument is zero, then it contains the solution matrix X. + * \param info The LAPACK info code. If it is positive, then + * U(info,info) is exactly zero. In this case the factorization + * has been completed, but the factor U is exactly + * singular, so the solution could not be computed. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dgesv.c + */ + +igraph_error_t igraph_lapack_dgesv(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + igraph_matrix_t *b, int *info) { + + if (igraph_matrix_nrow(a) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + if (igraph_matrix_ncol(a) > INT_MAX) { + IGRAPH_ERROR("Number of columns in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + int n = (int) igraph_matrix_nrow(a); + int nrhs = (int) igraph_matrix_ncol(b); + int lda = n > 0 ? n : 1; + int ldb = n > 0 ? n : 1; + igraph_vector_fortran_int_t vipiv; + + if (n != igraph_matrix_ncol(a)) { + IGRAPH_ERROR("Cannot LU solve non-square matrix.", IGRAPH_EINVAL); + } + if (n != igraph_matrix_nrow(b)) { + IGRAPH_ERROR("Cannot LU solve matrix, RHS of wrong size.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_fortran_int_init(&vipiv, n)); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &vipiv); + + igraphdgesv_(&n, &nrhs, VECTOR(a->data), &lda, VECTOR(vipiv), + VECTOR(b->data), &ldb, info); + + if (*info > 0) { + IGRAPH_WARNING("LU: factor is exactly singular."); + } else if (*info < 0) { + switch (*info) { + case -1: + IGRAPH_ERROR("Invalid number of rows/column.", IGRAPH_FAILURE); + break; + case -2: + IGRAPH_ERROR("Invalid number of RHS vectors.", IGRAPH_FAILURE); + break; + case -3: + IGRAPH_ERROR("Invalid input matrix.", IGRAPH_FAILURE); + break; + case -4: + IGRAPH_ERROR("Invalid LDA parameter.", IGRAPH_FAILURE); + break; + case -5: + IGRAPH_ERROR("Invalid pivot vector.", IGRAPH_FAILURE); + break; + case -6: + IGRAPH_ERROR("Invalid RHS matrix.", IGRAPH_FAILURE); + break; + case -7: + IGRAPH_ERROR("Invalid LDB parameter.", IGRAPH_FAILURE); + break; + case -8: + IGRAPH_ERROR("Invalid info argument.", IGRAPH_FAILURE); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error.", IGRAPH_FAILURE); + break; + } + } + + if (ipiv) { + IGRAPH_CHECK(igraph_vector_int_update_from_fortran(ipiv, &vipiv)); + } + + igraph_vector_fortran_int_destroy(&vipiv); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dsyevr + * \brief Selected eigenvalues and optionally eigenvectors of a symmetric matrix. + * + * Calls the DSYEVR LAPACK function to compute selected eigenvalues + * and, optionally, eigenvectors of a real symmetric matrix \c A. + * Eigenvalues and eigenvectors can be selected by specifying either + * a range of values or a range of indices for the desired eigenvalues. + * + * + * See more in the LAPACK documentation. + * + * \param A Matrix, on entry it contains the symmetric input + * matrix. Only the leading N-by-N upper triangular part is + * used for the computation. + * \param which Constant that gives which eigenvalues (and possibly + * the corresponding eigenvectors) to calculate. Possible + * values are \c IGRAPH_LAPACK_DSYEV_ALL, all eigenvalues; + * \c IGRAPH_LAPACK_DSYEV_INTERVAL, all eigenvalues in the + * half-open interval (vl, vu]; + * \c IGRAPH_LAPACK_DSYEV_SELECT, the il-th through iu-th + * eigenvalues. + * \param vl If \p which is \c IGRAPH_LAPACK_DSYEV_INTERVAL, then + * this is the lower bound of the interval to be searched for + * eigenvalues. See also the \p vestimate argument. + * \param vu If \p which is \c IGRAPH_LAPACK_DSYEV_INTERVAL, then + * this is the upper bound of the interval to be searched for + * eigenvalues. See also the \p vestimate argument. + * \param vestimate An upper bound for the number of eigenvalues in + * the (vl, vu] interval, if \p which is \c + * IGRAPH_LAPACK_DSYEV_INTERVAL. Memory is allocated only for + * the given number of eigenvalues (and eigenvectors), so this + * upper bound must be correct. + * \param il The index of the smallest eigenvalue to return, if \p + * which is \c IGRAPH_LAPACK_DSYEV_SELECT. + * \param iu The index of the largets eigenvalue to return, if \p + * which is \c IGRAPH_LAPACK_DSYEV_SELECT. + * \param abstol The absolute error tolerance for the eigevalues. An + * approximate eigenvalue is accepted as converged when it is + * determined to lie in an interval [a,b] of width + * less than or equal to abstol + EPS * max(|a|,|b|), + * where \c EPS is the machine precision. + * \param values An initialized vector, the eigenvalues are stored + * here, unless it is a null pointer. It will be resized as + * needed. + * \param vectors An initialized matrix. A set of orthonormal eigenvectors + * are stored in its columns, unless it is a null pointer. It will be + * resized as needed. + * \param support An integer vector. If not a null pointer, then it + * will be resized to (2*max(1,M)) (M is a the total number of + * eigenvalues found). Then the support of the eigenvectors in + * \p vectors is stored here, i.e., the indices + * indicating the nonzero elements in \p vectors. + * The i-th eigenvector is nonzero only in elements + * support(2*i-1) through support(2*i). + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dsyevr.c + */ + +igraph_error_t igraph_lapack_dsyevr(const igraph_matrix_t *A, + igraph_lapack_dsyev_which_t which, + igraph_real_t vl, igraph_real_t vu, int vestimate, + int il, int iu, igraph_real_t abstol, + igraph_vector_t *values, igraph_matrix_t *vectors, + igraph_vector_int_t *support) { + + igraph_matrix_t Acopy; + char jobz = vectors ? 'V' : 'N', range, uplo = 'U'; + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + int n = (int) igraph_matrix_nrow(A), lda = n, ldz = n; + int m, info; + igraph_vector_t *myvalues = values, vvalues; + igraph_vector_fortran_int_t mysupport; + igraph_vector_t work; + igraph_vector_fortran_int_t iwork; + int lwork = -1, liwork = -1; + + if (n != igraph_matrix_ncol(A)) { + IGRAPH_ERROR("Cannot find eigenvalues/vectors of non-square matrix.", IGRAPH_EINVAL); + } + if (which == IGRAPH_LAPACK_DSYEV_INTERVAL && + (vestimate < 1 || vestimate > n)) { + IGRAPH_ERROR("Estimated (upper bound) number of eigenvalues must be " + "between 1 and n.", IGRAPH_EINVAL); + } + if (which == IGRAPH_LAPACK_DSYEV_SELECT && iu - il < 0) { + IGRAPH_ERROR("Invalid 'il' and/or 'iu' values.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_init_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + IGRAPH_CHECK(igraph_vector_fortran_int_init(&iwork, 1)); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &iwork); + + if (!values) { + IGRAPH_VECTOR_INIT_FINALLY(&vvalues, 0); + myvalues = &vvalues; + } + + IGRAPH_CHECK(igraph_vector_fortran_int_init(&mysupport, 0)); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &mysupport); + + IGRAPH_CHECK(igraph_vector_resize(myvalues, n)); + + switch (which) { + case IGRAPH_LAPACK_DSYEV_ALL: + range = 'A'; + IGRAPH_CHECK(igraph_vector_fortran_int_resize(&mysupport, 2 * n)); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, n)); + } + break; + case IGRAPH_LAPACK_DSYEV_INTERVAL: + range = 'V'; + IGRAPH_CHECK(igraph_vector_fortran_int_resize(&mysupport, 2 * vestimate)); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, vestimate)); + } + break; + case IGRAPH_LAPACK_DSYEV_SELECT: + range = 'I'; + IGRAPH_CHECK(igraph_vector_fortran_int_resize(&mysupport, 2 * (iu - il + 1))); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, iu - il + 1)); + } + break; + } + + igraphdsyevr_(&jobz, &range, &uplo, &n, &MATRIX(Acopy, 0, 0), &lda, + &vl, &vu, &il, &iu, &abstol, &m, VECTOR(*myvalues), + vectors ? &MATRIX(*vectors, 0, 0) : 0, &ldz, VECTOR(mysupport), + VECTOR(work), &lwork, VECTOR(iwork), &liwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Invalid argument to dsyevr in workspace query.", IGRAPH_EINVAL); + } + + lwork = (int) VECTOR(work)[0]; + liwork = VECTOR(iwork)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + IGRAPH_CHECK(igraph_vector_fortran_int_resize(&iwork, liwork)); + + igraphdsyevr_(&jobz, &range, &uplo, &n, &MATRIX(Acopy, 0, 0), &lda, + &vl, &vu, &il, &iu, &abstol, &m, VECTOR(*myvalues), + vectors ? &MATRIX(*vectors, 0, 0) : 0, &ldz, VECTOR(mysupport), + VECTOR(work), &lwork, VECTOR(iwork), &liwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Invalid argument to dsyevr in calculation.", IGRAPH_EINVAL); + } + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, m)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, m)); + } + if (support) { + IGRAPH_CHECK(igraph_vector_int_update_from_fortran(support, &mysupport)); + IGRAPH_CHECK(igraph_vector_int_resize(support, m)); + } + + igraph_vector_fortran_int_destroy(&mysupport); + IGRAPH_FINALLY_CLEAN(1); + + if (!values) { + igraph_vector_destroy(&vvalues); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_fortran_int_destroy(&iwork); + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dgeev + * \brief Eigenvalues and optionally eigenvectors of a non-symmetric matrix. + * + * This function calls LAPACK to compute, for an N-by-N real + * nonsymmetric matrix A, the eigenvalues and, optionally, the left + * and/or right eigenvectors. + * + * + * The right eigenvector v(j) of A satisfies + * A * v(j) = lambda(j) * v(j) + * where lambda(j) is its eigenvalue. + * The left eigenvector u(j) of A satisfies + * u(j)^H * A = lambda(j) * u(j)^H + * where u(j)^H denotes the conjugate transpose of u(j). + * + * + * The computed eigenvectors are normalized to have Euclidean norm + * equal to 1 and largest component real. + * + * \param A matrix. On entry it contains the N-by-N input matrix. + * \param valuesreal Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then the real parts of the + * eigenvalues are stored here. The vector will be resized as + * needed. + * \param valuesimag Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then the imaginary parts of + * the eigenvalues are stored here. The vector will be resized + * as needed. + * \param vectorsleft Pointer to an initialized matrix, or a null + * pointer. If not a null pointer, then the left eigenvectors + * are stored in the columns of the matrix. The matrix will be + * resized as needed. + * \param vectorsright Pointer to an initialized matrix, or a null + * pointer. If not a null pointer, then the right eigenvectors + * are stored in the columns of the matrix. The matrix will be + * resized as needed. + * \param info This argument is used for two purposes. As an input + * argument it gives whether an igraph error should be + * generated if the QR algorithm fails to compute all + * eigenvalues. If \p info is non-zero, then an error is + * generated, otherwise only a warning is given. + * On exit it contains the LAPACK error code. + * Zero means successful exit. + * A negative values means that some of the arguments had an + * illegal value, this always triggers an igraph error. An i + * positive value means that the QR algorithm failed to + * compute all the eigenvalues, and no eigenvectors have been + * computed; element i+1:N of \p valuesreal and \p valuesimag + * contain eigenvalues which have converged. This case only + * generates an igraph error, if \p info was non-zero on entry. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dgeev.c + */ + +igraph_error_t igraph_lapack_dgeev(const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *info) { + + char jobvl = vectorsleft ? 'V' : 'N'; + char jobvr = vectorsright ? 'V' : 'N'; + igraph_real_t dummy; /* to prevent some Clang sanitizer warnings */ + + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + int n = (int) igraph_matrix_nrow(A); + int lda = n, ldvl = n, ldvr = n, lwork = -1; + igraph_vector_t work; + igraph_vector_t *myreal = valuesreal, *myimag = valuesimag, vreal, vimag; + igraph_matrix_t Acopy; + int error = *info; + + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Cannot calculate eigenvalues of non-square matrix.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_init_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + + if (!valuesreal) { + IGRAPH_VECTOR_INIT_FINALLY(&vreal, n); + myreal = &vreal; + } else { + IGRAPH_CHECK(igraph_vector_resize(myreal, n)); + } + if (!valuesimag) { + IGRAPH_VECTOR_INIT_FINALLY(&vimag, n); + myimag = &vimag; + } else { + IGRAPH_CHECK(igraph_vector_resize(myimag, n)); + } + if (vectorsleft) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsleft, n, n)); + } + if (vectorsright) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsright, n, n)); + } + + igraphdgeev_(&jobvl, &jobvr, &n, &MATRIX(Acopy, 0, 0), &lda, + VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : &dummy, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : &dummy, &ldvr, + VECTOR(work), &lwork, info); + + lwork = (int) VECTOR(work)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + + igraphdgeev_(&jobvl, &jobvr, &n, &MATRIX(Acopy, 0, 0), &lda, + VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : &dummy, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : &dummy, &ldvr, + VECTOR(work), &lwork, info); + + if (*info < 0) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev).", IGRAPH_FAILURE); + } else if (*info > 0) { + if (error) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev).", IGRAPH_FAILURE); + } else { + IGRAPH_WARNING("Cannot calculate eigenvalues (dgeev)."); + } + } + + if (!valuesimag) { + igraph_vector_destroy(&vimag); + IGRAPH_FINALLY_CLEAN(1); + } + if (!valuesreal) { + igraph_vector_destroy(&vreal); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lapack_dgeevx + * \brief Eigenvalues/vectors of nonsymmetric matrices, expert mode. + * + * This function calculates the eigenvalues and optionally the left + * and/or right eigenvectors of a nonsymmetric N-by-N real matrix. + * + * + * Optionally also, it computes a balancing transformation to improve + * the conditioning of the eigenvalues and eigenvectors (\p ilo, \p ihi, + * \p scale, and \p abnrm), reciprocal condition numbers for the + * eigenvalues (\p rconde), and reciprocal condition numbers for the + * right eigenvectors (\p rcondv). + * + * + * The right eigenvector v(j) of A satisfies + * A * v(j) = lambda(j) * v(j) + * where lambda(j) is its eigenvalue. + * The left eigenvector u(j) of A satisfies + * u(j)^H * A = lambda(j) * u(j)^H + * where u(j)^H denotes the conjugate transpose of u(j). + * + * + * The computed eigenvectors are normalized to have Euclidean norm + * equal to 1 and largest component real. + * + * + * Balancing a matrix means permuting the rows and columns to make it + * more nearly upper triangular, and applying a diagonal similarity + * transformation D * A * D^(-1), where D is a diagonal matrix, to + * make its rows and columns closer in norm and the condition numbers + * of its eigenvalues and eigenvectors smaller. The computed + * reciprocal condition numbers correspond to the balanced matrix. + * Permuting rows and columns will not change the condition numbers + * (in exact arithmetic) but diagonal scaling will. For further + * explanation of balancing, see section 4.10.2 of the LAPACK + * Users' Guide. Note that the eigenvectors obtained for the balanced + * matrix are backtransformed to those of \p A. + * + * \param balance Indicates whether the input matrix should be balanced. + * Possible values: + * \clist + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_NONE + * no not diagonally scale or permute. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_PERM + * perform permutations to make the matrix more nearly upper + * triangular. Do not diagonally scale. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE + * diagonally scale the matrix, i.e. replace A by + * D*A*D^(-1), where D is a diagonal matrix, chosen to make + * the rows and columns of A more equal in norm. Do not + * permute. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH + * both diagonally scale and permute A. + * \endclist + * \param A The input matrix, must be square. + * \param valuesreal An initialized vector, or a \c NULL pointer. If not + * a \c NULL pointer, then the real parts of the eigenvalues are stored + * here. The vector will be resized, as needed. + * \param valuesimag An initialized vector, or a \c NULL pointer. If not + * a \c NULL pointer, then the imaginary parts of the eigenvalues are stored + * here. The vector will be resized, as needed. + * \param vectorsleft An initialized matrix or a \c NULL pointer. If not + * a null pointer, then the left eigenvectors are stored here. The + * order corresponds to the eigenvalues and the eigenvectors are + * stored in a compressed form. If the j-th eigenvalue is real then + * column j contains the corresponding eigenvector. If the j-th and + * (j+1)-th eigenvalues form a complex conjugate pair, then the j-th + * and (j+1)-th columns contain the real and imaginary parts of the + * corresponding eigenvectors. + * \param vectorsright An initialized matrix or a \c NULL pointer. If not + * a null pointer, then the right eigenvectors are stored here. The + * format is the same, as for the \p vectorsleft argument. + * \param ilo + * \param ihi if not NULL, \p ilo and \p ihi point to integer values + * determined when A was + * balanced. The balanced A(i,j) = 0 if I>J and + * J=1,...,ilo-1 or I=ihi+1,...,N. + * \param scale Pointer to an initialized vector or a NULL pointer. If + * not a NULL pointer, then details of the permutations and scaling + * factors applied when balancing \p A, are stored here. + * If P(j) is the index of the row and column + * interchanged with row and column j, and D(j) is the scaling + * factor applied to row and column j, then + * \clist + * \cli scale(J) = P(J), for J = 1,...,ilo-1 + * \cli scale(J) = D(J), for J = ilo,...,ihi + * \cli scale(J) = P(J) for J = ihi+1,...,N. + * \endclist + * The order in which the interchanges are made is N to \p ihi+1, + * then 1 to \p ilo-1. + * \param abnrm Pointer to a real variable, the one-norm of the + * balanced matrix is stored here. (The one-norm is the maximum of + * the sum of absolute values of elements in any column.) + * \param rconde An initialized vector or a NULL pointer. If not a + * null pointer, then the reciprocal condition numbers of the + * eigenvalues are stored here. + * \param rcondv An initialized vector or a NULL pointer. If not a + * null pointer, then the reciprocal condition numbers of the right + * eigenvectors are stored here. + * \param info This argument is used for two purposes. As an input + * argument it gives whether an igraph error should be + * generated if the QR algorithm fails to compute all + * eigenvalues. If \p info is non-zero, then an error is + * generated, otherwise only a warning is given. + * On exit it contains the LAPACK error code. + * Zero means successful exit. + * A negative values means that some of the arguments had an + * illegal value, this always triggers an igraph error. An i + * positive value means that the QR algorithm failed to + * compute all the eigenvalues, and no eigenvectors have been + * computed; element i+1:N of \p valuesreal and \p valuesimag + * contain eigenvalues which have converged. This case only + * generated an igraph error, if \p info was non-zero on entry. + * \return Error code. + * + * Time complexity: TODO + * + * \example examples/simple/igraph_lapack_dgeevx.c + */ + +igraph_error_t igraph_lapack_dgeevx(igraph_lapack_dgeevx_balance_t balance, + const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *ilo, int *ihi, igraph_vector_t *scale, + igraph_real_t *abnrm, + igraph_vector_t *rconde, + igraph_vector_t *rcondv, + int *info) { + + char balanc; + char jobvl = vectorsleft ? 'V' : 'N'; + char jobvr = vectorsright ? 'V' : 'N'; + char sense; + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + int n = (int) igraph_matrix_nrow(A); + int lda = n, ldvl = n, ldvr = n, lwork = -1; + igraph_vector_t work; + igraph_vector_fortran_int_t iwork; + igraph_matrix_t Acopy; + int error = *info; + igraph_vector_t *myreal = valuesreal, *myimag = valuesimag, vreal, vimag; + igraph_vector_t *myscale = scale, vscale; + igraph_real_t dummy; /* to prevent some Clang sanitizer warnings */ + int ilo_dummy; + int ihi_dummy; + + if (ilo == NULL) { + ilo = &ilo_dummy; + } + if (ihi == NULL) { + ihi = &ihi_dummy; + } + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Cannot calculate eigenvalues of non-square matrix.", IGRAPH_EINVAL); + } + + switch (balance) { + case IGRAPH_LAPACK_DGEEVX_BALANCE_NONE: + balanc = 'N'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_PERM: + balanc = 'P'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE: + balanc = 'S'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH: + balanc = 'B'; + break; + default: + IGRAPH_ERROR("Invalid 'balance' argument.", IGRAPH_EINVAL); + break; + } + + if (!rconde && !rcondv) { + sense = 'N'; + } else if (rconde && !rcondv) { + sense = 'E'; + } else if (!rconde && rcondv) { + sense = 'V'; + } else { + sense = 'B'; + } + + IGRAPH_CHECK(igraph_matrix_init_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + IGRAPH_CHECK(igraph_vector_fortran_int_init(&iwork, n)); + IGRAPH_FINALLY(igraph_vector_fortran_int_destroy, &iwork); + + if (!valuesreal) { + IGRAPH_VECTOR_INIT_FINALLY(&vreal, n); + myreal = &vreal; + } else { + IGRAPH_CHECK(igraph_vector_resize(myreal, n)); + } + if (!valuesimag) { + IGRAPH_VECTOR_INIT_FINALLY(&vimag, n); + myimag = &vimag; + } else { + IGRAPH_CHECK(igraph_vector_resize(myimag, n)); + } + if (!scale) { + IGRAPH_VECTOR_INIT_FINALLY(&vscale, n); + myscale = &vscale; + } else { + IGRAPH_CHECK(igraph_vector_resize(scale, n)); + } + if (vectorsleft) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsleft, n, n)); + } + if (vectorsright) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsright, n, n)); + } + + igraphdgeevx_(&balanc, &jobvl, &jobvr, &sense, &n, &MATRIX(Acopy, 0, 0), + &lda, VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : &dummy, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : &dummy, &ldvr, + ilo, ihi, VECTOR(*myscale), abnrm, + rconde ? VECTOR(*rconde) : &dummy, + rcondv ? VECTOR(*rcondv) : &dummy, + VECTOR(work), &lwork, VECTOR(iwork), info); + + lwork = (int) VECTOR(work)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + + igraphdgeevx_(&balanc, &jobvl, &jobvr, &sense, &n, &MATRIX(Acopy, 0, 0), + &lda, VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : &dummy, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : &dummy, &ldvr, + ilo, ihi, VECTOR(*myscale), abnrm, + rconde ? VECTOR(*rconde) : &dummy, + rcondv ? VECTOR(*rcondv) : &dummy, + VECTOR(work), &lwork, VECTOR(iwork), info); + + if (*info < 0) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev).", IGRAPH_FAILURE); + } else if (*info > 0) { + if (error) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev).", IGRAPH_FAILURE); + } else { + IGRAPH_WARNING("Cannot calculate eigenvalues (dgeev)."); + } + } + + if (!scale) { + igraph_vector_destroy(&vscale); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!valuesimag) { + igraph_vector_destroy(&vimag); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!valuesreal) { + igraph_vector_destroy(&vreal); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_fortran_int_destroy(&iwork); + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_lapack_dgehrd(const igraph_matrix_t *A, + int ilo, int ihi, + igraph_matrix_t *result) { + + if (igraph_matrix_nrow(A) > INT_MAX) { + IGRAPH_ERROR("Number of rows in matrix too large for LAPACK.", IGRAPH_EOVERFLOW); + } + int n = (int) igraph_matrix_nrow(A); + int lda = n; + int lwork = -1; + igraph_vector_t work; + igraph_real_t optwork; + igraph_vector_t tau; + igraph_matrix_t Acopy; + int info = 0; + int i; + + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Hessenberg reduction failed on non-square matrix.", IGRAPH_EINVAL); + } + + if (ilo < 1 || ihi > n || ilo > ihi) { + IGRAPH_ERROR("Invalid `ilo' and/or `ihi'.", IGRAPH_EINVAL); + } + + if (n <= 1) { + IGRAPH_CHECK(igraph_matrix_update(result, A)); + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_matrix_init_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + IGRAPH_VECTOR_INIT_FINALLY(&tau, n - 1); + + igraphdgehrd_(&n, &ilo, &ihi, &MATRIX(Acopy, 0, 0), &lda, VECTOR(tau), + &optwork, &lwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Internal Hessenberg transformation error.", + IGRAPH_EINTERNAL); + } + + lwork = (int) optwork; + IGRAPH_VECTOR_INIT_FINALLY(&work, lwork); + + igraphdgehrd_(&n, &ilo, &ihi, &MATRIX(Acopy, 0, 0), &lda, VECTOR(tau), + VECTOR(work), &lwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Internal Hessenberg transformation error.", + IGRAPH_EINTERNAL); + } + + igraph_vector_destroy(&work); + igraph_vector_destroy(&tau); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_matrix_update(result, &Acopy)); + + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(1); + + for (i = 0; i < n - 2; i++) { + int j; + for (j = i + 2; j < n; j++) { + MATRIX(*result, j, i) = 0.0; + } + } + + return IGRAPH_SUCCESS; +} diff --git a/src/linalg/lapack_internal.h b/src/linalg/lapack_internal.h new file mode 100644 index 0000000..459a64c --- /dev/null +++ b/src/linalg/lapack_internal.h @@ -0,0 +1,184 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef LAPACK_INTERNAL_H +#define LAPACK_INTERNAL_H + +/* Note: only files calling the LAPACK routines directly need to + include this header. +*/ + +#include "igraph_decls.h" +#include "config.h" /* INTERNAL_LAPACK */ + +IGRAPH_BEGIN_C_DECLS + +#ifndef INTERNAL_LAPACK + #define igraphdgeevx_ dgeevx_ + #define igraphdgeev_ dgeev_ + #define igraphdgebak_ dgebak_ + #define igraphxerbla_ xerbla_ + #define igraphdgebal_ dgebal_ + #define igraphdisnan_ disnan_ + #define igraphdlaisnan_ dlaisnan_ + #define igraphdgehrd_ dgehrd_ + #define igraphdgehd2_ dgehd2_ + #define igraphdlarf_ dlarf_ + #define igraphiladlc_ iladlc_ + #define igraphiladlr_ iladlr_ + #define igraphdlarfg_ dlarfg_ + #define igraphdlapy2_ dlapy2_ + #define igraphdlahr2_ dlahr2_ + #define igraphdlacpy_ dlacpy_ + #define igraphdlarfb_ dlarfb_ + #define igraphilaenv_ ilaenv_ + #define igraphieeeck_ ieeeck_ + #define igraphiparmq_ iparmq_ + #define igraphdhseqr_ dhseqr_ + #define igraphdlahqr_ dlahqr_ + #define igraphdlabad_ dlabad_ + #define igraphdlanv2_ dlanv2_ + #define igraphdlaqr0_ dlaqr0_ + #define igraphdlaqr3_ dlaqr3_ + #define igraphdlaqr4_ dlaqr4_ + #define igraphdlaqr2_ dlaqr2_ + #define igraphdlaset_ dlaset_ + #define igraphdormhr_ dormhr_ + #define igraphdormqr_ dormqr_ + #define igraphdlarft_ dlarft_ + #define igraphdorm2r_ dorm2r_ + #define igraphdtrexc_ dtrexc_ + #define igraphdlaexc_ dlaexc_ + #define igraphdlange_ dlange_ + #define igraphdlassq_ dlassq_ + #define igraphdlarfx_ dlarfx_ + #define igraphdlartg_ dlartg_ + #define igraphdlasy2_ dlasy2_ + #define igraphdlaqr5_ dlaqr5_ + #define igraphdlaqr1_ dlaqr1_ + #define igraphdlascl_ dlascl_ + #define igraphdorghr_ dorghr_ + #define igraphdorgqr_ dorgqr_ + #define igraphdorg2r_ dorg2r_ + #define igraphdtrevc_ dtrevc_ + #define igraphdlaln2_ dlaln2_ + #define igraphdladiv_ dladiv_ + #define igraphdsyevr_ dsyevr_ + #define igraphdsyrk_ dsyrk_ + #define igraphdlansy_ dlansy_ + #define igraphdormtr_ dormtr_ + #define igraphdormql_ dormql_ + #define igraphdorm2l_ dorm2l_ + #define igraphdstebz_ dstebz_ + #define igraphdlaebz_ dlaebz_ + #define igraphdstein_ dstein_ + #define igraphdlagtf_ dlagtf_ + #define igraphdlagts_ dlagts_ + #define igraphdlarnv_ dlarnv_ + #define igraphdlaruv_ dlaruv_ + #define igraphdstemr_ dstemr_ + #define igraphdlae2_ dlae2_ + #define igraphdlaev2_ dlaev2_ + #define igraphdlanst_ dlanst_ + #define igraphdlarrc_ dlarrc_ + #define igraphdlarre_ dlarre_ + #define igraphdlarra_ dlarra_ + #define igraphdlarrb_ dlarrb_ + #define igraphdlaneg_ dlaneg_ + #define igraphdlarrd_ dlarrd_ + #define igraphdlarrk_ dlarrk_ + #define igraphdlasq2_ dlasq2_ + #define igraphdlasq3_ dlasq3_ + #define igraphdlasq4_ dlasq4_ + #define igraphdlasq5_ dlasq5_ + #define igraphdlasq6_ dlasq6_ + #define igraphdlasrt_ dlasrt_ + #define igraphdlarrj_ dlarrj_ + #define igraphdlarrr_ dlarrr_ + #define igraphdlarrv_ dlarrv_ + #define igraphdlar1v_ dlar1v_ + #define igraphdlarrf_ dlarrf_ + #define igraphdpotrf_ dpotrf_ + #define igraphdsterf_ dsterf_ + #define igraphdsytrd_ dsytrd_ + #define igraphdlatrd_ dlatrd_ + #define igraphdsytd2_ dsytd2_ + #define igraphdlanhs_ dlanhs_ + #define igraphdgeqr2_ dgeqr2_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlacn2_ dlacn2_ + #define igraphdtrsyl_ dtrsyl_ + #define igraphdlasr_ dlasr_ + #define igraphdsteqr_ dsteqr_ + #define igraphdgesv_ dgesv_ + #define igraphdgetrf_ dgetrf_ + #define igraphdgetf2_ dgetf2_ + #define igraphdlaswp_ dlaswp_ + #define igraphdgetrs_ dgetrs_ + #define igraphlen_trim_ len_trim_ + #define igraph_dlamc1_ dlamc1_ + #define igraph_dlamc2_ dlamc2_ + #define igraph_dlamc3_ dlamc3_ + #define igraph_dlamc4_ dlamc4_ + #define igraph_dlamc5_ dlamc5_ +#endif + +int igraphdgetrf_(int *m, int *n, double *a, int *lda, int *ipiv, + int *info); +int igraphdgetrs_(char *trans, int *n, int *nrhs, double *a, + int *lda, int *ipiv, double *b, int *ldb, + int *info); +int igraphdgesv_(int *n, int *nrhs, double *a, int *lda, + int *ipiv, double *b, int *ldb, int *info); + +double igraphdlapy2_(double *x, double *y); + +int igraphdsyevr_(char *jobz, char *range, char *uplo, int *n, + double *a, int *lda, double *vl, + double *vu, int * il, int *iu, + double *abstol, int *m, double *w, + double *z, int *ldz, int *isuppz, + double *work, int *lwork, int *iwork, + int *liwork, int *info); + +int igraphdgeev_(char *jobvl, char *jobvr, int *n, double *a, + int *lda, double *wr, double *wi, + double *vl, int *ldvl, double *vr, int *ldvr, + double *work, int *lwork, int *info); + +int igraphdgeevx_(char *balanc, char *jobvl, char *jobvr, char *sense, + int *n, double *a, int *lda, double *wr, + double *wi, double *vl, int *ldvl, + double *vr, int *ldvr, int *ilo, int *ihi, + double *scale, double *abnrm, + double *rconde, double *rcondv, + double *work, int *lwork, int *iwork, int *info); + +int igraphdgehrd_(int *n, int *ilo, int *ihi, double *A, int *lda, + double *tau, double *work, int *lwork, + int *info); + +double igraphddot_(int *n, double *dx, int *incx, double *dy, int *incy); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/math/complex.c b/src/math/complex.c new file mode 100644 index 0000000..60f96e8 --- /dev/null +++ b/src/math/complex.c @@ -0,0 +1,375 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_complex.h" + +#include + +/** + * \example igraph_complex.c + */ + +igraph_complex_t igraph_complex(igraph_real_t x, igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = x; + IGRAPH_IMAG(res) = y; + return res; +} + +igraph_complex_t igraph_complex_polar(igraph_real_t r, igraph_real_t theta) { + igraph_complex_t res; + IGRAPH_REAL(res) = r * cos(theta); + IGRAPH_IMAG(res) = r * sin(theta); + return res; +} + +igraph_real_t igraph_complex_arg(igraph_complex_t z) { + igraph_real_t x = IGRAPH_REAL(z); + igraph_real_t y = IGRAPH_IMAG(z); + if (x == 0.0 && y == 0.0) { + return 0.0; + } + return atan2(y, x); +} + +igraph_complex_t igraph_complex_add(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) + IGRAPH_REAL(z2); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1) + IGRAPH_IMAG(z2); + return res; +} + +igraph_complex_t igraph_complex_sub(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) - IGRAPH_REAL(z2); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1) - IGRAPH_IMAG(z2); + return res; +} + +igraph_complex_t igraph_complex_mul(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) * IGRAPH_REAL(z2) - + IGRAPH_IMAG(z1) * IGRAPH_IMAG(z2); + IGRAPH_IMAG(res) = IGRAPH_REAL(z1) * IGRAPH_IMAG(z2) + + IGRAPH_IMAG(z1) * IGRAPH_REAL(z2); + return res; +} + +igraph_complex_t igraph_complex_div(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + igraph_real_t z1r = IGRAPH_REAL(z1), z1i = IGRAPH_IMAG(z1); + igraph_real_t z2r = IGRAPH_REAL(z2), z2i = IGRAPH_IMAG(z2); + igraph_real_t s = 1.0 / igraph_complex_abs(z2); + igraph_real_t sz2r = s * z2r; + igraph_real_t sz2i = s * z2i; + IGRAPH_REAL(res) = (z1r * sz2r + z1i * sz2i) * s; + IGRAPH_IMAG(res) = (z1i * sz2r - z1r * sz2i) * s; + return res; +} + +igraph_complex_t igraph_complex_add_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) + x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_add_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) + y; + return res; +} + +igraph_complex_t igraph_complex_sub_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) - x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_sub_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) - y; + return res; +} + +igraph_complex_t igraph_complex_mul_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) * x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) * x; + return res; +} + +igraph_complex_t igraph_complex_mul_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = - IGRAPH_IMAG(z) * y; + IGRAPH_IMAG(res) = IGRAPH_REAL(z) * y; + return res; +} + +igraph_complex_t igraph_complex_div_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) / x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) / x; + return res; +} + +igraph_complex_t igraph_complex_div_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_IMAG(z) / y; + IGRAPH_IMAG(res) = - IGRAPH_REAL(z) / y; + return res; +} + +igraph_complex_t igraph_complex_conj(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = - IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_neg(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = - IGRAPH_REAL(z); + IGRAPH_IMAG(res) = - IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_inv(igraph_complex_t z) { + igraph_complex_t res; + igraph_real_t s = 1.0 / igraph_complex_abs(z); + IGRAPH_REAL(res) = (IGRAPH_REAL(z) * s) * s; + IGRAPH_IMAG(res) = - (IGRAPH_IMAG(z) * s) * s; + return res; +} + +igraph_real_t igraph_complex_abs(igraph_complex_t z) { + /* hypot() avoids overflow at intermediate stages of the calculation */ + return hypot(IGRAPH_REAL(z), IGRAPH_IMAG(z)); +} + +igraph_real_t igraph_complex_logabs(igraph_complex_t z) { + igraph_real_t xabs = fabs(IGRAPH_REAL(z)); + igraph_real_t yabs = fabs(IGRAPH_IMAG(z)); + igraph_real_t max, u; + if (xabs >= yabs) { + max = xabs; + u = yabs / xabs; + } else { + max = yabs; + u = xabs / yabs; + } + return log (max) + 0.5 * log1p (u * u); +} + +igraph_complex_t igraph_complex_sqrt(igraph_complex_t z) { + igraph_complex_t res; + + if (IGRAPH_REAL(z) == 0.0 && IGRAPH_IMAG(z) == 0.0) { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } else { + igraph_real_t x = fabs (IGRAPH_REAL(z)); + igraph_real_t y = fabs (IGRAPH_IMAG(z)); + igraph_real_t w; + if (x >= y) { + igraph_real_t t = y / x; + w = sqrt (x) * sqrt (0.5 * (1.0 + sqrt (1.0 + t * t))); + } else { + igraph_real_t t = x / y; + w = sqrt (y) * sqrt (0.5 * (t + sqrt (1.0 + t * t))); + } + + if (IGRAPH_REAL(z) >= 0.0) { + igraph_real_t ai = IGRAPH_IMAG(z); + IGRAPH_REAL(res) = w; + IGRAPH_IMAG(res) = ai / (2.0 * w); + } else { + igraph_real_t ai = IGRAPH_IMAG(z); + igraph_real_t vi = (ai >= 0) ? w : -w; + IGRAPH_REAL(res) = ai / (2.0 * vi); + IGRAPH_IMAG(res) = vi; + } + } + + return res; +} + +igraph_complex_t igraph_complex_sqrt_real(igraph_real_t x) { + igraph_complex_t res; + if (x >= 0) { + IGRAPH_REAL(res) = sqrt(x); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = 0.0; + IGRAPH_IMAG(res) = sqrt(-x); + } + return res; +} + +igraph_complex_t igraph_complex_exp(igraph_complex_t z) { + igraph_real_t rho = exp(IGRAPH_REAL(z)); + igraph_real_t theta = IGRAPH_IMAG(z); + igraph_complex_t res; + IGRAPH_REAL(res) = rho * cos(theta); + IGRAPH_IMAG(res) = rho * sin(theta); + return res; +} + +igraph_complex_t igraph_complex_pow(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + + if (IGRAPH_REAL(z1) == 0 && IGRAPH_IMAG(z1) == 0.0) { + if (IGRAPH_REAL(z2) == 0 && IGRAPH_IMAG(z2) == 0.0) { + IGRAPH_REAL(res) = 1.0; + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } + } else if (IGRAPH_REAL(z2) == 1.0 && IGRAPH_IMAG(z2) == 0.0) { + IGRAPH_REAL(res) = IGRAPH_REAL(z1); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1); + } else if (IGRAPH_REAL(z2) == -1.0 && IGRAPH_IMAG(z2) == 0.0) { + res = igraph_complex_inv(z1); + } else { + igraph_real_t logr = igraph_complex_logabs (z1); + igraph_real_t theta = igraph_complex_arg (z1); + igraph_real_t z2r = IGRAPH_REAL(z2), z2i = IGRAPH_IMAG(z2); + igraph_real_t rho = exp (logr * z2r - z2i * theta); + igraph_real_t beta = theta * z2r + z2i * logr; + IGRAPH_REAL(res) = rho * cos(beta); + IGRAPH_IMAG(res) = rho * sin(beta); + } + + return res; +} + +igraph_complex_t igraph_complex_pow_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + if (IGRAPH_REAL(z) == 0.0 && IGRAPH_IMAG(z) == 0.0) { + if (x == 0) { + IGRAPH_REAL(res) = 1.0; + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } + } else { + igraph_real_t logr = igraph_complex_logabs(z); + igraph_real_t theta = igraph_complex_arg(z); + igraph_real_t rho = exp (logr * x); + igraph_real_t beta = theta * x; + IGRAPH_REAL(res) = rho * cos(beta); + IGRAPH_IMAG(res) = rho * sin(beta); + } + return res; +} + +igraph_complex_t igraph_complex_log(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = igraph_complex_logabs(z); + IGRAPH_IMAG(res) = igraph_complex_arg(z); + return res; +} + +igraph_complex_t igraph_complex_log10(igraph_complex_t z) { + return igraph_complex_mul_real(igraph_complex_log(z), 1 / log(10.0)); +} + +igraph_complex_t igraph_complex_log_b(igraph_complex_t z, + igraph_complex_t b) { + return igraph_complex_div (igraph_complex_log(z), igraph_complex_log(b)); +} + +igraph_complex_t igraph_complex_sin(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (zi == 0.0) { + IGRAPH_REAL(res) = sin(zr); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = sin(zr) * cosh(zi); + IGRAPH_IMAG(res) = cos(zr) * sinh(zi); + } + return res; +} + +igraph_complex_t igraph_complex_cos(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (zi == 0.0) { + IGRAPH_REAL(res) = cos(zr); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = cos(zr) * cosh(zi); + IGRAPH_IMAG(res) = sin(zr) * sinh(-zi); + } + return res; +} + +igraph_complex_t igraph_complex_tan(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (fabs (zi) < 1) { + igraph_real_t D = pow (cos (zr), 2.0) + pow (sinh (zi), 2.0); + IGRAPH_REAL(res) = 0.5 * sin (2 * zr) / D; + IGRAPH_IMAG(res) = 0.5 * sinh (2 * zi) / D; + } else { + igraph_real_t u = exp (-zi); + igraph_real_t C = 2 * u / (1 - pow (u, 2.0)); + igraph_real_t D = 1 + pow (cos (zr), 2.0) * pow (C, 2.0); + igraph_real_t S = pow (C, 2.0); + igraph_real_t T = 1.0 / tanh (zi); + IGRAPH_REAL(res) = 0.5 * sin (2 * zr) * S / D; + IGRAPH_IMAG(res) = T / D; + } + return res; +} + +igraph_complex_t igraph_complex_sec(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_cos(z)); +} + +igraph_complex_t igraph_complex_csc(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_sin(z)); +} + +igraph_complex_t igraph_complex_cot(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_tan(z)); +} diff --git a/src/math/safe_intop.c b/src/math/safe_intop.c new file mode 100644 index 0000000..f1aefbf --- /dev/null +++ b/src/math/safe_intop.c @@ -0,0 +1,186 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "math/safe_intop.h" + +/* Use IGRAPH_SAFE_ADD() instead unless there is a need to intercept errors. */ +igraph_error_t igraph_i_safe_add(igraph_int_t a, igraph_int_t b, igraph_int_t *res) { + IGRAPH_SAFE_ADD(a, b, res); + return IGRAPH_SUCCESS; +} + +/* Use IGRAPH_SAFE_MULT() instead unless there is a need to intercept errors. */ +igraph_error_t igraph_i_safe_mult(igraph_int_t a, igraph_int_t b, igraph_int_t *res) { + IGRAPH_SAFE_MULT(a, b, res); + return IGRAPH_SUCCESS; +} + +/* Overflow-safe sum of integer vector elements. */ +igraph_error_t igraph_i_safe_vector_int_sum(const igraph_vector_int_t *vec, igraph_int_t *res) { + const igraph_int_t n = igraph_vector_int_size(vec); + igraph_int_t sum = 0; + for (igraph_int_t i=0; i < n; ++i) { + IGRAPH_SAFE_ADD(sum, VECTOR(*vec)[i], &sum); + } + *res = sum; + return IGRAPH_SUCCESS; +} + +/* Overflow-safe product of integer vector elements. */ +igraph_error_t igraph_i_safe_vector_int_prod(const igraph_vector_int_t *vec, igraph_int_t *res) { + const igraph_int_t n = igraph_vector_int_size(vec); + igraph_int_t prod = 1; + for (igraph_int_t i=0; i < n; ++i) { + IGRAPH_SAFE_MULT(prod, VECTOR(*vec)[i], &prod); + } + *res = prod; + return IGRAPH_SUCCESS; +} + +/** + * Rounds up an integer to the next power of 2, with overflow check. + * The result for 2, 3 and 4, respectively, would be 2, 4, and 4. + * This function must not be called with negative input. + * Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + */ +igraph_error_t igraph_i_safe_next_pow_2(igraph_int_t k, igraph_int_t *res) { + IGRAPH_ASSERT(k >= 0); + if (k == 0) { + *res = 0; + return IGRAPH_SUCCESS; + } + k--; + k |= k >> 1; + k |= k >> 2; + k |= k >> 4; + k |= k >> 8; + k |= k >> 16; +#if IGRAPH_INTEGER_SIZE == 32 + /* Nothing else to do. */ +#elif IGRAPH_INTEGER_SIZE == 64 + k |= k >> 32; +#else + /* If values other than 32 or 64 become allowed, + * this code will need to be updated. */ +# error "Unexpected IGRAPH_INTEGER_SIZE value." +#endif + if (k < IGRAPH_INTEGER_MAX) { + *res = k+1; + return IGRAPH_SUCCESS; + } else { + IGRAPH_ERRORF("Overflow when computing next power of 2 for %" IGRAPH_PRId ".", + IGRAPH_EOVERFLOW, k); + } +} + +/** + * Computes 2^k as an integer, with overflow check. + * This function must not be called with negative input. + */ +igraph_error_t igraph_i_safe_exp2(igraph_int_t k, igraph_int_t *res) { + IGRAPH_ASSERT(k >= 0); + if (k > IGRAPH_INTEGER_SIZE-2) { + IGRAPH_ERRORF("Overflow when raising 2 to power %" IGRAPH_PRId ".", + IGRAPH_EOVERFLOW, k); + } + *res = (igraph_int_t) 1 << k; + return IGRAPH_SUCCESS; +} + +/** + * Checks if an igraph_real_t with no fractional part is representable as an igraph_int_t. + * Avoids invoking undefined behaviour. + * Must not be called with an input that has a non-zero fractional part. + */ +igraph_bool_t igraph_i_is_real_representable_as_integer(igraph_real_t value) { + /* IGRAPH_INTEGER_MAX is one less than a power of 2, and may not be representable as + * a floating point number. Thus we cannot safely check that value <= IGRAPH_INTEGER_MAX, + * as this would convert IGRAPH_INTEGER_MAX to floating point, potentially changing its value. + * Instead, we compute int_max_plus_1 = IGRAPH_INTEGER_MAX + 1, which is exactly representable + * since it is a power of 2, and check that value < int_max_plus_1. + * + * IGRAPH_INTEGER_MIN is a power of 2 (with negative sign), so there is no such issue. + * + * NaNs and infinities are correctly rejected. + */ + const igraph_real_t int_max_plus_1 = 2.0 * (IGRAPH_INTEGER_MAX / 2 + 1); + const igraph_real_t int_min = (igraph_real_t) IGRAPH_INTEGER_MIN; + if (IGRAPH_LIKELY(int_min <= value && value < int_max_plus_1)) { + return true; + } else { + return false; + } +} + +/** + * Converts an igraph_real_t into an igraph_int_t with range checks to + * protect from undefined behaviour. The input value is assumed to have no + * fractional part. + */ +static igraph_error_t igraph_i_safe_real_to_int(igraph_real_t value, igraph_int_t *result) { + if (igraph_i_is_real_representable_as_integer(value)) { + *result = (igraph_int_t) value; + return IGRAPH_SUCCESS; + } else if (isnan(value)) { + IGRAPH_ERROR("NaN cannot be converted to an integer.", IGRAPH_EINVAL); + } else { + /* %.f ensures exact printing, %g would not */ + IGRAPH_ERRORF("Cannot convert %.f to integer, outside of representable range.", IGRAPH_EOVERFLOW, value); + } +} + +/** + * Converts an igraph_real_t into an igraph_int_t with range checks to + * protect from undefined behaviour. The input value is converted into an + * integer with ceil(). + */ +igraph_error_t igraph_i_safe_ceil(igraph_real_t value, igraph_int_t *result) { + return igraph_i_safe_real_to_int(ceil(value), result); +} + +/** + * Converts an igraph_real_t into an igraph_int_t with range checks to + * protect from undefined behaviour. The input value is converted into an + * integer with floor(). + */ +igraph_error_t igraph_i_safe_floor(igraph_real_t value, igraph_int_t *result) { + return igraph_i_safe_real_to_int(floor(value), result); +} + +/** + * Converts an igraph_real_t into an igraph_int_t with range checks to + * protect from undefined behaviour. The input value is converted into an + * integer with round(). + * + * This is typically the slowest of this set of functions. + */ +igraph_error_t igraph_i_safe_round(igraph_real_t value, igraph_int_t* result) { + return igraph_i_safe_real_to_int(round(value), result); +} + +/** + * Converts an igraph_real_t into an igraph_int_t with range checks to + * protect from undefined behaviour. The input value is converted into an + * integer with trunc(). + * +* This is typically the fastest of this set of functions. + */ +igraph_error_t igraph_i_safe_trunc(igraph_real_t value, igraph_int_t* result) { + return igraph_i_safe_real_to_int(trunc(value), result); +} diff --git a/src/math/safe_intop.h b/src/math/safe_intop.h new file mode 100644 index 0000000..ed91723 --- /dev/null +++ b/src/math/safe_intop.h @@ -0,0 +1,139 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received _safe_a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#ifndef IGRAPH_MATH_SAFE_INTOP_H +#define IGRAPH_MATH_SAFE_INTOP_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "config.h" /* HAVE_BUILTIN_OVERFLOW */ + +#include + +IGRAPH_BEGIN_C_DECLS + +/* Largest positive value for igraph_real_t that can safely represent integers. */ +#define IGRAPH_MAX_EXACT_REAL ((double)(1LL << DBL_MANT_DIG)) + +/* These macros raise an error if the operation would result in an overflow. + * They must only be used in functions that return an igraph_error_t. + * + * This code is based on the recommendation of + * https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard + */ + +#ifdef HAVE_BUILTIN_OVERFLOW + +#define IGRAPH_SAFE_ADD(a, b, res) \ + do { \ + igraph_int_t _safe_a = (a), _safe_b = (b); \ + igraph_int_t _safe_sum; \ + if (__builtin_add_overflow(_safe_a, _safe_b, &_safe_sum)) { \ + IGRAPH_ERRORF("Overflow when adding %" IGRAPH_PRId " and %" IGRAPH_PRId ".", IGRAPH_EOVERFLOW, _safe_a, _safe_b); \ + } \ + *(res) = _safe_sum; \ + } while (0) + +#define IGRAPH_SAFE_MULT(a, b, res) \ + do { \ + igraph_int_t _safe_a = (a), _safe_b = (b); \ + igraph_int_t _safe_prod; \ + if (__builtin_mul_overflow(_safe_a, _safe_b, &_safe_prod)) { \ + IGRAPH_ERRORF("Overflow when multiplying %" IGRAPH_PRId " and %" IGRAPH_PRId ".", IGRAPH_EOVERFLOW, _safe_a, _safe_b); \ + } \ + *(res) = _safe_prod; \ + } while (0) + +#else + +#define IGRAPH_SAFE_ADD(a, b, res) \ + do { \ + igraph_int_t _safe_a = (a), _safe_b = (b); \ + igraph_int_t _safe_sum; \ + if (((_safe_b > 0) && (_safe_a > (IGRAPH_INTEGER_MAX - _safe_b))) || \ + ((_safe_b < 0) && (_safe_a < (IGRAPH_INTEGER_MIN - _safe_b)))) { \ + IGRAPH_ERRORF("Overflow when adding %" IGRAPH_PRId " and %" IGRAPH_PRId ".", IGRAPH_EOVERFLOW, _safe_a, _safe_b); \ + } \ + _safe_sum = _safe_a+_safe_b; \ + *(res) = _safe_sum; \ + } while (0) + +#define IGRAPH_SAFE_MULT(a, b, res) \ + do { \ + igraph_int_t _safe_a = (a), _safe_b = (b); \ + igraph_int_t _safe_prod; \ + int err=0; \ + if (_safe_a > 0) { /* _safe_a is positive */ \ + if (_safe_b > 0) { /* _safe_a and _safe_b are positive */ \ + if (_safe_a > (IGRAPH_INTEGER_MAX / _safe_b)) { \ + err=1; \ + } \ + } else { /* _safe_a positive, _safe_b nonpositive */ \ + if (_safe_b < (IGRAPH_INTEGER_MIN / _safe_a)) { \ + err=1; \ + } \ + } /* _safe_a positive, _safe_b nonpositive */ \ + } else { /* _safe_a is nonpositive */ \ + if (_safe_b > 0) { /* _safe_a is nonpositive, _safe_b is positive */ \ + if (_safe_a < (IGRAPH_INTEGER_MIN / _safe_b)) { \ + err=1; \ + } \ + } else { /* _safe_a and _safe_b are nonpositive */ \ + if ( (_safe_a != 0) && (_safe_b < (IGRAPH_INTEGER_MAX / _safe_a))) { \ + err=1; \ + } \ + } /* End if _safe_a and _safe_b are nonpositive */ \ + } /* End if _safe_a is nonpositive */ \ + if (err) { \ + IGRAPH_ERRORF("Overflow when multiplying %" IGRAPH_PRId " and %" IGRAPH_PRId ".", IGRAPH_EOVERFLOW, _safe_a, _safe_b); \ + } \ + _safe_prod = _safe_a*_safe_b; \ + *(res) = _safe_prod; \ + } while (0) + +#endif /* HAVE_BUILTIN_OVERFLOW */ + +/* Overflow-safe calculation of "n choose 2" = n*(n-1) / 2, assuming that n >= 0. */ +#define IGRAPH_SAFE_N_CHOOSE_2(n, res) \ + do { \ + igraph_int_t _safe_n = (n); \ + if (_safe_n % 2 == 0) IGRAPH_SAFE_MULT(_safe_n / 2, _safe_n - 1, res); \ + else IGRAPH_SAFE_MULT(_safe_n, (_safe_n - 1) / 2, res); \ + } while (0) + +IGRAPH_FUNCATTR_CONST igraph_bool_t igraph_i_is_real_representable_as_integer(igraph_real_t value); + +igraph_error_t igraph_i_safe_ceil(igraph_real_t value, igraph_int_t* result); +igraph_error_t igraph_i_safe_floor(igraph_real_t value, igraph_int_t* result); +igraph_error_t igraph_i_safe_round(igraph_real_t value, igraph_int_t* result); +igraph_error_t igraph_i_safe_trunc(igraph_real_t value, igraph_int_t* result); + +igraph_error_t igraph_i_safe_next_pow_2(igraph_int_t k, igraph_int_t *res); +igraph_error_t igraph_i_safe_exp2(igraph_int_t k, igraph_int_t *res); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_safe_add(igraph_int_t a, igraph_int_t b, igraph_int_t *res); +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_safe_mult(igraph_int_t a, igraph_int_t b, igraph_int_t *res); +igraph_error_t igraph_i_safe_vector_int_sum(const igraph_vector_int_t *vec, igraph_int_t *res); +igraph_error_t igraph_i_safe_vector_int_prod(const igraph_vector_int_t *vec, igraph_int_t *res); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_MATH_SAFE_INTOP_H */ diff --git a/src/math/utils.c b/src/math/utils.c new file mode 100644 index 0000000..48d822d --- /dev/null +++ b/src/math/utils.c @@ -0,0 +1,161 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_complex.h" +#include "igraph_nongraph.h" +#include "igraph_types.h" + +#include +#include + +/** + * \function igraph_almost_equals + * \brief Compare two double-precision floats with a tolerance. + * + * Determines whether two double-precision floats are "almost equal" + * to each other with a given level of tolerance on the relative error. + * + * \param a The first float. + * \param b The second float. + * \param eps The level of tolerance on the relative error. The relative + * error is defined as abs(a-b) / (abs(a) + abs(b)). The + * two numbers are considered equal if this is less than \c eps. + * + * \return True if the two floats are nearly equal to each other within + * the given level of tolerance, false otherwise. + */ +igraph_bool_t igraph_almost_equals(double a, double b, double eps) { + return igraph_cmp_epsilon(a, b, eps) == 0; +} + +/* Use value-safe floating point math for igraph_cmp_epsilon() with + * the Intel compiler. + * + * The Intel compiler rewrites arithmetic expressions for faster + * evaluation by default. In the below function, it will evaluate + * (eps * fabs(a) + eps * fabs(b)) as eps*(fabs(a) + fabs(b)). + * However, this code path is taken precisely when fabs(a) + fabs(b) + * overflows, thus this rearrangement of the expression causes + * the function to return incorrect results, and some test failures. + * To avoid this, we switch the Intel compiler to "precise" mode. + */ +#ifdef __INTEL_COMPILER +#pragma float_control(push) +#pragma float_control (precise, on) +#endif + +/** + * \function igraph_cmp_epsilon + * \brief Compare two double-precision floats with a tolerance. + * + * Determines whether two double-precision floats are "almost equal" + * to each other with a given level of tolerance on the relative error. + * + * + * The function supports infinities and NaN values. NaN values are considered + * not equal to any other value (even another NaN), but the ordering is + * arbitrary; in other words, we only guarantee that comparing a NaN with + * any other value will not return zero. Positive infinity is considered to + * be greater than any finite value with any tolerance. Negative infinity is + * considered to be smaller than any finite value with any tolerance. + * Positive infinity is considered to be equal to another positive infinity + * with any tolerance. Negative infinity is considered to be equal to another + * negative infinity with any tolerance. + * + * \param a The first float. + * \param b The second float. + * \param eps The level of tolerance on the relative error. The relative + * error is defined as abs(a-b) / (abs(a) + abs(b)). The + * two numbers are considered equal if this is less than \c eps. + * Negative epsilon values are not allowed; the returned value will + * be undefined in this case. Zero means to do an exact comparison + * without tolerance. + * + * \return Zero if the two floats are nearly equal to each other within + * the given level of tolerance, positive number if the first float is + * larger, negative number if the second float is larger. + */ +int igraph_cmp_epsilon(double a, double b, double eps) { + double diff; + double abs_diff; + double sum; + + if (a == b) { + /* shortcut, handles infinities */ + return 0; + } + + diff = a - b; + abs_diff = fabs(diff); + sum = fabs(a) + fabs(b); + + if (a == 0 || b == 0 || sum < DBL_MIN) { + /* a or b is zero or both are extremely close to it; relative + * error is less meaningful here so just compare it with + * epsilon */ + return abs_diff < (eps * DBL_MIN) ? 0 : (diff < 0 ? -1 : 1); + } else if (!isfinite(sum)) { + /* addition overflow, so presumably |a| and |b| are both large; use a + * different formulation */ + return (abs_diff < (eps * fabs(a) + eps * fabs(b))) ? 0 : (diff < 0 ? -1 : 1); + } else { + return (abs_diff / sum < eps) ? 0 : (diff < 0 ? -1 : 1); + } +} + +/** + * \function igraph_complex_almost_equals + * \brief Compare two complex numbers with a tolerance. + * + * Determines whether two complex numbers are "almost equal" + * to each other with a given level of tolerance on the relative error. + * + * \param a The first complex number. + * \param b The second complex number. + * \param eps The level of tolerance on the relative error. The relative + * error is defined as abs(a-b) / (abs(a) + abs(b)). The + * two numbers are considered equal if this is less than \c eps. + * + * \return True if the two complex numbers are nearly equal to each other within + * the given level of tolerance, false otherwise. + */ +igraph_bool_t igraph_complex_almost_equals(igraph_complex_t a, + igraph_complex_t b, + igraph_real_t eps) { + + igraph_real_t a_abs = igraph_complex_abs(a); + igraph_real_t b_abs = igraph_complex_abs(b); + igraph_real_t sum = a_abs + b_abs; + igraph_real_t abs_diff = igraph_complex_abs(igraph_complex_sub(a, b)); + + if (a_abs == 0 || b_abs == 0 || sum < DBL_MIN) { + return abs_diff < eps * DBL_MIN; + } else if (! isfinite(sum)) { + return abs_diff < (eps * a_abs + eps * b_abs); + } else { + return abs_diff/ sum < eps; + } +} + +#ifdef __INTEL_COMPILER +#pragma float_control(pop) +#endif diff --git a/src/misc/bipartite.c b/src/misc/bipartite.c new file mode 100644 index 0000000..acbe27d --- /dev/null +++ b/src/misc/bipartite.c @@ -0,0 +1,1784 @@ +/* + igraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_bipartite.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_random.h" + +#include "core/interruption.h" +#include "graph/attributes.h" +#include "internal/utils.h" +#include "math/safe_intop.h" +#include "misc/graphicality.h" +#include "random/random_internal.h" + +/** + * \section about_bipartite Bipartite networks in igraph + * + * + * A bipartite network contains two kinds of vertices and connections + * are only possible between two vertices of different kinds. There are + * many natural examples, e.g. movies and actors as vertices and a + * movie is connected to all participating actors, etc. + * + * + * igraph does not have direct support for bipartite networks, at + * least not at the C language level. In other words the igraph_t + * structure does not contain information about the vertex types. + * The C functions for bipartite networks usually have an additional + * input argument to graph, called \c types, a boolean vector giving + * the vertex types. + * + * + * Most functions creating bipartite networks are able to create this + * extra vector, you just need to supply an initialized boolean vector + * to them. + */ + +/** + * \function igraph_bipartite_projection_size + * \brief Calculate the number of vertices and edges in the bipartite projections. + * + * This function calculates the number of vertices and edges in the + * two projections of a bipartite network. This is useful if you have + * a big bipartite network and you want to estimate the amount of + * memory you would need to calculate the projections themselves. + * + * \param graph The input graph. + * \param types Boolean vector giving the vertex types of the graph. + * \param vcount1 Pointer to an \c igraph_int_t, the number of + * vertices in the first projection is stored here. May be \c NULL + * if not needed. + * \param ecount1 Pointer to an \c igraph_int_t, the number of + * edges in the first projection is stored here. May be \c NULL + * if not needed. + * \param vcount2 Pointer to an \c igraph_int_t, the number of + * vertices in the second projection is stored here. May be \c NULL + * if not needed. + * \param ecount2 Pointer to an \c igraph_int_t, the number of + * edges in the second projection is stored here. May be \c NULL + * if not needed. + * \return Error code. + * + * \sa \ref igraph_bipartite_projection() to calculate the actual + * projection. + * + * Time complexity: O(|V|*d^2+|E|), |V| is the number of vertices, |E| + * is the number of edges, d is the average (total) degree of the + * graphs. + */ + +igraph_error_t igraph_bipartite_projection_size(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_int_t *vcount1, + igraph_int_t *ecount1, + igraph_int_t *vcount2, + igraph_int_t *ecount2) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t vc1 = 0, ec1 = 0, vc2 = 0, ec2 = 0; + igraph_adjlist_t adjlist; + igraph_vector_int_t added; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid bipartite type vector length.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&added, no_of_nodes); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis1; + igraph_int_t neilen1; + igraph_int_t *ecptr; + if (VECTOR(*types)[i]) { + vc2++; + ecptr = &ec2; + } else { + vc1++; + ecptr = &ec1; + } + neis1 = igraph_adjlist_get(&adjlist, i); + neilen1 = igraph_vector_int_size(neis1); + for (igraph_int_t j = 0; j < neilen1; j++) { + igraph_int_t neilen2, nei = VECTOR(*neis1)[j]; + const igraph_vector_int_t *neis2 = igraph_adjlist_get(&adjlist, nei); + if (IGRAPH_UNLIKELY(VECTOR(*types)[i] == VECTOR(*types)[nei])) { + IGRAPH_ERROR("Non-bipartite edge found in bipartite projection.", + IGRAPH_EINVAL); + } + neilen2 = igraph_vector_int_size(neis2); + for (igraph_int_t k = 0; k < neilen2; k++) { + igraph_int_t nei2 = VECTOR(*neis2)[k]; + if (nei2 <= i) { + continue; + } + if (VECTOR(added)[nei2] == i + 1) { + continue; + } + VECTOR(added)[nei2] = i + 1; + (*ecptr)++; + } + } + } + + if (vcount1) { + *vcount1 = vc1; + } + + if (ecount1) { + *ecount1 = ec1; + } + + if (vcount2) { + *vcount2 = vc2; + } + + if (ecount2) { + *ecount2 = ec2; + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&added); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_bipartite_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_t *proj, + int which, + igraph_vector_int_t *multiplicity) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t remaining_nodes = 0; + igraph_vector_int_t vertex_perm, vertex_index; + igraph_vector_int_t edges; + igraph_adjlist_t adjlist; + const igraph_vector_int_t *neis1, *neis2; + igraph_int_t neilen1, neilen2; + igraph_vector_int_t added; + igraph_vector_int_t mult; + + if (which < 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_perm, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&vertex_perm, no_of_nodes)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_index, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&added, no_of_nodes); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* we won't need the 'mult' vector if 'multiplicity' is NULL, but MSVC will + * throw warnings in the compiler output if we initialize it conditionally */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&mult, multiplicity ? no_of_nodes : 1); + if (multiplicity) { + igraph_vector_int_clear(multiplicity); + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == which) { + VECTOR(vertex_index)[i] = remaining_nodes++; + igraph_vector_int_push_back(&vertex_perm, i); /* reserved */ + } + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == which) { + igraph_int_t new_i = VECTOR(vertex_index)[i]; + igraph_int_t iedges = 0; + neis1 = igraph_adjlist_get(&adjlist, i); + neilen1 = igraph_vector_int_size(neis1); + for (igraph_int_t j = 0; j < neilen1; j++) { + igraph_int_t nei = VECTOR(*neis1)[j]; + if (IGRAPH_UNLIKELY(VECTOR(*types)[i] == VECTOR(*types)[nei])) { + IGRAPH_ERROR("Non-bipartite edge found in bipartite projection.", IGRAPH_EINVAL); + } + neis2 = igraph_adjlist_get(&adjlist, nei); + neilen2 = igraph_vector_int_size(neis2); + for (igraph_int_t k = 0; k < neilen2; k++) { + igraph_int_t nei2 = VECTOR(*neis2)[k], new_nei2; + if (nei2 <= i) { + continue; + } + if (VECTOR(added)[nei2] == i + 1) { + if (multiplicity) { + VECTOR(mult)[nei2] += 1; + } + continue; + } + VECTOR(added)[nei2] = i + 1; + if (multiplicity) { + VECTOR(mult)[nei2] = 1; + } + iedges++; + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, new_i)); + if (multiplicity) { + /* If we need the multiplicity as well, then we put in the + old vertex IDs here and rewrite it later */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei2)); + } else { + new_nei2 = VECTOR(vertex_index)[nei2]; + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, new_nei2)); + } + } + } + if (multiplicity) { + /* OK, we need to go through all the edges added for vertex new_i + and check their multiplicity */ + igraph_int_t now = igraph_vector_int_size(&edges); + igraph_int_t from = now - iedges * 2; + for (igraph_int_t j = from; j < now; j += 2) { + igraph_int_t nei2 = VECTOR(edges)[j + 1]; + igraph_int_t new_nei2 = VECTOR(vertex_index)[nei2]; + igraph_int_t m = VECTOR(mult)[nei2]; + VECTOR(edges)[j + 1] = new_nei2; + IGRAPH_CHECK(igraph_vector_int_push_back(multiplicity, m)); + } + } + } /* if VECTOR(*type)[i] == which */ + } + + igraph_vector_int_destroy(&mult); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&added); + igraph_vector_int_destroy(&vertex_index); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_create(proj, &edges, remaining_nodes, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, proj); + + /* copy graph attributes */ + IGRAPH_CHECK(igraph_i_attribute_copy(proj, graph, true, /* vertex= */ false, /* edge= */ false)); + + /* copy vertex attributes */ + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, proj, &vertex_perm)); + + igraph_vector_int_destroy(&vertex_perm); + IGRAPH_FINALLY_CLEAN(2); /* +1 for proj1 */ + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bipartite_projection + * \brief Create one or both projections of a bipartite (two-mode) network. + * + * Creates one or both projections of a bipartite graph. + * + * + * A graph is called bipartite if its vertices can be partitioned into + * two sets, V1 and V2, so that connections only run between V1 and V2, + * but not within V1 or within V2. The \p types parameter specifies + * which vertex should be considered a member of one or the other + * partition. The projection to V1 has vertex set V1, and two vertices + * are connected if they have at least one common neighbour in V2. + * The number of common neighbours is returned in \p multiplicity1, + * if requested. + * + * \param graph The bipartite input graph. Directedness of the edges + * is ignored. + * \param types Boolean vector giving the vertex types of the graph. + * \param proj1 Pointer to an uninitialized graph object, the first + * projection will be created here. It a null pointer, then it is + * ignored, see also the \p probe1 argument. + * \param proj2 Pointer to an uninitialized graph object, the second + * projection is created here, if it is not a null pointer. See also + * the \p probe1 argument. + * \param multiplicity1 Pointer to a vector, or a null pointer. If not + * the latter, then the multiplicity of the edges is stored + * here. E.g. if there is an A-C-B and also an A-D-B triple in the + * bipartite graph (but no more X, such that A-X-B is also in the + * graph), then the multiplicity of the A-B edge in the projection + * will be 2. + * \param multiplicity2 The same as \c multiplicity1, but for the + * other projection. + * \param probe1 This argument can be used to specify the order of the + * projections in the resulting list. When it is non-negative, then + * it is considered as a vertex ID and the projection containing + * this vertex will be the first one in the result. Setting this + * argument to a non-negative value implies that \c proj1 must be + * a non-null pointer. If you don't care about the ordering of the + * projections, pass -1 here. + * \return Error code. + * + * \sa \ref igraph_bipartite_projection_size() to calculate the number + * of vertices and edges in the projections, without creating the + * projection graphs themselves. + * + * Time complexity: O(|V|*d^2+|E|), |V| is the number of vertices, |E| + * is the number of edges, d is the average (total) degree of the + * graphs. + */ + +igraph_error_t igraph_bipartite_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_t *proj1, + igraph_t *proj2, + igraph_vector_int_t *multiplicity1, + igraph_vector_int_t *multiplicity2, + igraph_int_t probe1) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + + /* t1 is -1 if proj1 is omitted, it is 0 if it belongs to type zero, + it is 1 if it belongs to type one. The same for t2 */ + int t1, t2; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid bipartite type vector length.", IGRAPH_EINVAL); + } + + if (probe1 >= no_of_nodes) { + IGRAPH_ERROR("No such vertex to probe.", IGRAPH_EINVAL); + } + + if (probe1 >= 0 && !proj1) { + IGRAPH_ERROR("`probe1' given, but `proj1' is a null pointer.", IGRAPH_EINVAL); + } + + if (probe1 >= 0) { + t1 = VECTOR(*types)[probe1]; + if (proj2) { + t2 = 1 - t1; + } else { + t2 = -1; + } + } else { + t1 = proj1 ? 0 : -1; + t2 = proj2 ? 1 : -1; + } + + if (proj1) { + IGRAPH_CHECK(igraph_i_bipartite_projection(graph, types, proj1, t1, multiplicity1)); + IGRAPH_FINALLY(igraph_destroy, proj1); + } + + if (proj2) { + IGRAPH_CHECK(igraph_i_bipartite_projection(graph, types, proj2, t2, multiplicity2)); + } + + if (proj1) { + IGRAPH_FINALLY_CLEAN(1); /* proj1 ownership change */ + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_full_bipartite + * \brief Creates a complete bipartite graph. + * + * A bipartite network contains two kinds of vertices and connections + * are only possible between two vertices of different kind. There are + * many natural examples, e.g. movies and actors as vertices and a + * movie is connected to all participating actors, etc. + * + * + * igraph does not have direct support for bipartite networks, at + * least not at the C language level. In other words the \type igraph_t + * structure does not contain information about the vertex types. + * The C functions for bipartite networks usually have an additional + * input argument to graph, called \p types, a boolean vector giving + * the vertex types. + * + * + * Most functions creating bipartite networks are able to create this + * extra vector, you just need to supply an initialized boolean vector + * to them. + * + * \param graph Pointer to an uninitialized graph object, the graph will be + * created here. + * \param types Pointer to a boolean vector. If not a null pointer, + * then the vertex types will be stored here. + * \param n1 Integer, the number of vertices of the first kind. + * \param n2 Integer, the number of vertices of the second kind. + * \param directed Boolean, whether to create a directed graph. + * \param mode A constant that gives the type of connections for + * directed graphs. If \c IGRAPH_OUT, then edges point from vertices + * of the first kind to vertices of the second kind; if \c + * IGRAPH_IN, then the opposite direction is realized; if \c + * IGRAPH_ALL, then mutual edges will be created. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa \ref igraph_full() for non-bipartite complete graphs, + * \ref igraph_full_multipartite() for complete multipartite graphs. + */ + +igraph_error_t igraph_full_bipartite(igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, + igraph_bool_t directed, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes, no_of_edges; + igraph_vector_int_t edges; + igraph_int_t ptr; + + if (n1 < 0 || n2 < 0) { + IGRAPH_ERROR("Invalid number of vertices for bipartite graph.", IGRAPH_EINVAL); + } + + IGRAPH_SAFE_ADD(n1, n2, &no_of_nodes); + + if (!directed) { + IGRAPH_SAFE_MULT(n1, n2, &no_of_edges); + } else if (mode == IGRAPH_OUT || mode == IGRAPH_IN) { + IGRAPH_SAFE_MULT(n1, n2, &no_of_edges); + } else { /* mode==IGRAPH_ALL */ + IGRAPH_SAFE_MULT(n1, n2, &no_of_edges); + IGRAPH_SAFE_MULT(no_of_edges, 2, &no_of_edges); + } + + /* To ensure the size of the edges vector will not overflow. */ + if (no_of_edges > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Overflow in number of edges.", IGRAPH_EOVERFLOW); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + ptr = 0; + + if (!directed || mode == IGRAPH_OUT) { + + for (igraph_int_t i = 0; i < n1; i++) { + for (igraph_int_t j = 0; j < n2; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = n1 + j; + } + } + + } else if (mode == IGRAPH_IN) { + + for (igraph_int_t i = 0; i < n1; i++) { + for (igraph_int_t j = 0; j < n2; j++) { + VECTOR(edges)[ptr++] = n1 + j; + VECTOR(edges)[ptr++] = i; + } + } + + } else { + + for (igraph_int_t i = 0; i < n1; i++) { + for (igraph_int_t j = 0; j < n2; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = n1 + j; + VECTOR(edges)[ptr++] = n1 + j; + VECTOR(edges)[ptr++] = i; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, graph); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + igraph_vector_bool_null(types); + for (igraph_int_t i = n1; i < no_of_nodes; i++) { + VECTOR(*types)[i] = true; + } + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_create_bipartite + * \brief Create a bipartite graph. + * + * This is a simple wrapper function to create a bipartite graph. It + * does a little more than \ref igraph_create(), e.g. it checks that + * the graph is indeed bipartite with respect to the given \p types + * vector. If there is an edge connecting two vertices of the same + * kind, then an error is reported. + * + * \param graph Pointer to an uninitialized graph object, the result is + * created here. + * \param types Boolean vector giving the vertex types. The length of + * the vector defines the number of vertices in the graph. + * \param edges Vector giving the edges of the graph. The highest + * vertex ID in this vector must be smaller than the length of the + * \p types vector. + * \param directed Boolean, whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_bipartite_create.c + */ + +igraph_error_t igraph_create_bipartite(igraph_t *graph, const igraph_vector_bool_t *types, + const igraph_vector_int_t *edges, + igraph_bool_t directed) { + + igraph_int_t no_of_nodes = igraph_vector_bool_size(types); + igraph_int_t no_of_edges = igraph_vector_int_size(edges); + igraph_int_t i; + + if (no_of_edges % 2 != 0) { + IGRAPH_ERROR("Invalid (odd length) edges vector.", IGRAPH_EINVAL); + } + no_of_edges /= 2; + + if (! igraph_vector_int_isininterval(edges, 0, no_of_nodes-1)) { + IGRAPH_ERRORF("Invalid vertex ID for a graph with %" IGRAPH_PRId " vertices.", + IGRAPH_EINVVID, + no_of_nodes); + } + + /* Check bipartiteness */ + for (i = 0; i < no_of_edges * 2; i += 2) { + igraph_int_t from = VECTOR(*edges)[i]; + igraph_int_t to = VECTOR(*edges)[i + 1]; + igraph_bool_t t1 = VECTOR(*types)[from]; + igraph_bool_t t2 = VECTOR(*types)[to]; + if ( (t1 && t2) || (!t1 && !t2) ) { + IGRAPH_ERROR("Invalid edges, not a bipartite graph.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_empty(graph, no_of_nodes, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_edges(graph, edges, 0)); + + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_biadjacency + * \brief Creates a bipartite graph from a bipartite adjacency matrix. + * + * A bipartite (or two-mode) graph contains two types of vertices and + * edges always connect vertices of different types. A bipartite adjacency + * matrix is an \em n x \em m matrix, \em n and \em m are the number of vertices + * of the two types, respectively. Nonzero elements in the matrix denote + * edges between the two corresponding vertices. + * + * + * This function can operate in two modes, depending on the + * \p multiple argument. If it is \c false, then a single edge is + * created for every non-zero element in the bipartite adjacency matrix. If + * \p multiple is \c true, then as many edges are created between two + * vertices as the corresponding matrix element. When \p multiple + * is set to \c true, matrix elements should be whole numbers. + * Otherwise their fractional part will be discarded. + * + * \param graph Pointer to an uninitialized graph object. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer, then the vertex types are stored + * here. It is resized as needed. + * \param biadjmatrix The bipartite adjacency matrix that serves as an input + * to this function. + * \param directed Specifies whether to create an undirected or a directed + * graph. + * \param mode Specifies the direction of the edges in a directed + * graph. If \c IGRAPH_OUT, then edges point from vertices + * of the first kind (corresponding to rows) to vertices of the + * second kind (corresponding to columns); if \c IGRAPH_IN, + * then the opposite direction is realized; if \c IGRAPH_ALL, + * then mutual edges will be created. + * \param multiple Whether to interpret matrix entries as edge multiplicities, + * see details above. + * \return Error code. + * + * Time complexity: O(n*m), the size of the bipartite adjacency matrix. + */ + +igraph_error_t igraph_biadjacency( + igraph_t *graph, + igraph_vector_bool_t *types, + const igraph_matrix_t *biadjmatrix, + igraph_bool_t directed, + igraph_neimode_t mode, + igraph_bool_t multiple) { + + const igraph_int_t n1 = igraph_matrix_nrow(biadjmatrix); + const igraph_int_t n2 = igraph_matrix_ncol(biadjmatrix); + const igraph_int_t no_of_nodes = n1 + n2; + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + if (multiple) { + + if (n1 > 0 && n2 > 0 && igraph_matrix_min(biadjmatrix) < 0) { + IGRAPH_ERRORF( + "Bipartite adjacency matrix elements should be non-negative, found %g.", + IGRAPH_EINVAL, igraph_matrix_min(biadjmatrix) + ); + } + + for (igraph_int_t j = 0; j < n2; j++) { + for (igraph_int_t i = 0; i < n1; i++) { + igraph_int_t elem = MATRIX(*biadjmatrix, i, j); + igraph_int_t from, to; + + if (elem == 0) { + continue; + } + + if (mode == IGRAPH_IN) { + from = n1 + j; + to = i; + } else { + from = i; + to = n1 + j; + } + + if (mode != IGRAPH_ALL || !directed) { + for (igraph_int_t k = 0; k < elem; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } + } else { + for (igraph_int_t k = 0; k < elem; k++) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + } + } + } + } + + } else { + + for (igraph_int_t j = 0; j < n2; j++) { + for (igraph_int_t i = 0; i < n1; i++) { + igraph_int_t from, to; + + if (MATRIX(*biadjmatrix, i, j) != 0) { + if (mode == IGRAPH_IN) { + from = n1 + j; + to = i; + } else { + from = i; + to = n1 + j; + } + if (mode != IGRAPH_ALL || !directed) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + } + } + } + } + + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, graph); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + igraph_vector_bool_null(types); + for (igraph_int_t i = n1; i < no_of_nodes; i++) { + VECTOR(*types)[i] = true; + } + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_weighted_biadjacency + * \brief Creates a bipartite graph from a weighted bipartite adjacency matrix. + * + * A bipartite (or two-mode) graph contains two types of vertices and + * edges always connect vertices of different types. A bipartite adjacency + * matrix is an \em n x \em m matrix, \em n and \em m are the number of vertices + * of the two types, respectively. Nonzero elements in the matrix denote + * edges between the two corresponding vertices. + * + * \param graph Pointer to an uninitialized graph object. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer, then the vertex types are stored + * here. It is resized as needed. + * \param weights Pointer to an initialized vector, the weights will be stored here. + * \param biadjmatrix The bipartite adjacency matrix that serves as an input + * to this function. + * \param directed Specifies whether to create an undirected or a directed + * graph. + * \param mode Specifies the direction of the edges in a directed + * graph. If \c IGRAPH_OUT, then edges point from vertices + * of the first kind (corresponding to rows) to vertices of the + * second kind (corresponding to columns); if \c IGRAPH_IN, + * then the opposite direction is realized; if \c IGRAPH_ALL, + * then mutual edges will be created. + * \return Error code. + * + * Time complexity: O(n*m), the size of the bipartite adjacency matrix. + */ + +igraph_error_t igraph_weighted_biadjacency( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_vector_t *weights, + const igraph_matrix_t *biadjmatrix, + igraph_bool_t directed, + igraph_neimode_t mode) { + + const igraph_int_t n1 = igraph_matrix_nrow(biadjmatrix); + const igraph_int_t n2 = igraph_matrix_ncol(biadjmatrix); + const igraph_int_t no_of_nodes = n1 + n2; + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + igraph_vector_clear(weights); + + for (igraph_int_t j = 0; j < n2; j++) { + for (igraph_int_t i = 0; i < n1; i++) { + igraph_real_t weight = MATRIX(*biadjmatrix, i, j); + igraph_int_t from, to; + + if (weight != 0) { + if (mode == IGRAPH_IN) { + from = n1 + j; + to = i; + } else { + from = i; + to = n1 + j; + } + if (mode != IGRAPH_ALL || !directed) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(weights, weight)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(weights, weight)); + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(weights, weight)); + } + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, graph); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + igraph_vector_bool_null(types); + for (igraph_int_t i = n1; i < no_of_nodes; i++) { + VECTOR(*types)[i] = true; + } + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_biadjacency + * \brief Converts a bipartite graph into a bipartite adjacency matrix. + * + * In a bipartite adjacency matrix \c A, element A_ij + * gives the number of edges between the ith vertex of the + * first partition and the jth vertex of the second partition. + * + * + * If the graph contains edges within the same partition, this function + * issues a warning. + * + * \param graph The input graph, edge directions are ignored. + * \param types Boolean vector containing the vertex types. Vertices belonging + * to the first partition have type \c false, the one in the second + * partition type \c true. + * \param weights A vector specifying a weight for each edge or \c NULL. + * If \c NULL, all edges are assumed to have weight 1. + * \param res Pointer to an initialized matrix, the result is stored + * here. An element of the matrix gives the number of edges + * (irrespectively of their direction), or sum of edge weights, + * between the two corresponding vertices. The rows will correspond + * to vertices with type \c false, the columns correspond to vertices + * with type \c true. + * \param row_ids Pointer to an initialized vector or \c NULL. + * If not a null pointer, then the IDs of vertices with type \c false + * are stored here, with the same ordering as the rows of the + * biadjacency matrix. + * \param col_ids Pointer to an initialized vector or \c NULL. + * If not a null pointer, then the IDs of vertices with type \c true + * are stored here, with the same ordering as the columns of the + * biadjacency matrix. + * \return Error code. + * + * Time complexity: O(|E|) where |E| is the number of edges. + * + * \sa \ref igraph_biadjacency() for the opposite operation. + */ + +igraph_error_t igraph_get_biadjacency( + const igraph_t *graph, const igraph_vector_bool_t *types, + const igraph_vector_t *weights, + igraph_matrix_t *res, igraph_vector_int_t *row_ids, + igraph_vector_int_t *col_ids +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t n1 = 0, n2 = 0; + igraph_int_t ignored_edges = 0; + igraph_vector_int_t perm; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERRORF("Vertex type vector size (%" IGRAPH_PRId ") not equal to number of vertices (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_bool_size(types), no_of_nodes); + } + + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Edge weight vector size (%" IGRAPH_PRId ") not equal to number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(weights), no_of_edges); + } + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + n1 += VECTOR(*types)[i] == false ? 1 : 0; + } + n2 = no_of_nodes - n1; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&perm, no_of_nodes); + + for (igraph_int_t i = 0, p1 = 0, p2 = n1; i < no_of_nodes; i++) { + VECTOR(perm)[i] = VECTOR(*types)[i] ? p2++ : p1++; + } + + IGRAPH_CHECK(igraph_matrix_resize(res, n1, n2)); + igraph_matrix_null(res); + for (igraph_int_t i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_int_t from2 = VECTOR(perm)[from]; + igraph_int_t to2 = VECTOR(perm)[to]; + if (VECTOR(*types)[from] == VECTOR(*types)[to]) { + ignored_edges++; + } else if (! VECTOR(*types)[from]) { + MATRIX(*res, from2, to2 - n1) += weights ? VECTOR(*weights)[i] : 1; + } else { + MATRIX(*res, to2, from2 - n1) += weights ? VECTOR(*weights)[i] : 1; + } + } + + if (ignored_edges > 0) { + IGRAPH_WARNINGF("%" IGRAPH_PRId " edges running within partitions were ignored.", ignored_edges); + } + + if (row_ids) { + IGRAPH_CHECK(igraph_vector_int_resize(row_ids, n1)); + } + if (col_ids) { + IGRAPH_CHECK(igraph_vector_int_resize(col_ids, n2)); + } + if (row_ids || col_ids) { + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (! VECTOR(*types)[i]) { + if (row_ids) { + igraph_int_t i2 = VECTOR(perm)[i]; + VECTOR(*row_ids)[i2] = i; + } + } else { + if (col_ids) { + igraph_int_t i2 = VECTOR(perm)[i]; + VECTOR(*col_ids)[i2 - n1] = i; + } + } + } + } + + igraph_vector_int_destroy(&perm); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_bipartite + * \brief Check whether a graph is bipartite. + * + * This function checks whether a graph is bipartite. It tries + * to find a mapping that gives a possible division of the vertices into two + * classes, such that no two vertices of the same class are connected by an + * edge. + * + * + * The existence of such a mapping is equivalent of having no circuits of + * odd length in the graph. A graph with loop edges cannot be bipartite. + * + * + * Note that the mapping is not necessarily unique, e.g. if the graph has + * at least two components, then the vertices in the separate components + * can be mapped independently. + * + * \param graph The input graph. + * \param res Pointer to a boolean, the result is stored here. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer and a mapping was found, then it + * is stored here. If not a null pointer, but no mapping was found, + * the contents of this vector is invalid. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa igraph_is_bipartite_coloring() to determine if all edges connect + * vertices of different types, given a specific type vector. + */ + +igraph_error_t igraph_is_bipartite(const igraph_t *graph, + igraph_bool_t *res, + igraph_vector_bool_t *types) { + + /* We basically do a breadth first search and label the + vertices along the way. We stop as soon as we can find a + contradiction. + + In the 'seen' vector 0 means 'not seen yet', 1 means type 1, + 2 means type 2. + */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_char_t seen; + igraph_dqueue_int_t Q; + igraph_vector_int_t neis; + igraph_bool_t bi = true; + + /* Shortcut: Graphs with self-loops are not bipartite. */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + if (res) { + *res = false; + } + return IGRAPH_SUCCESS; + } + + /* Shortcut: If the type vector is not requested, and the graph is a forest + * we can immediately return with the result that the graph is bipartite. */ + if (! types && + igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST)) { + if (res) { + *res = true; + } + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_CHAR_INIT_FINALLY(&seen, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + for (igraph_int_t i = 0; bi && i < no_of_nodes; i++) { + + if (VECTOR(seen)[i]) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, i)); + VECTOR(seen)[i] = 1; + + while (bi && !igraph_dqueue_int_empty(&Q)) { + igraph_int_t n, j; + igraph_int_t actnode = igraph_dqueue_int_pop(&Q); + char acttype = VECTOR(seen)[actnode]; + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, actnode, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + if (VECTOR(seen)[nei]) { + char neitype = VECTOR(seen)[nei]; + if (neitype == acttype) { + bi = false; + break; + } + } else { + VECTOR(seen)[nei] = 3 - acttype; + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + } + } + } + } + + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + /* Set the cache: A graph that is not bipartite has + * an odd-length cycle, therefore it cannot be a forest. */ + if (! bi) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, false); + } + + if (res) { + *res = bi; + } + + if (types && bi) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(*types)[i] = VECTOR(seen)[i] - 1; + } + } + + igraph_vector_char_destroy(&seen); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t bipartite_iea_game( + igraph_t *graph, + igraph_int_t n1, igraph_int_t n2, + igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode) { + + igraph_vector_int_t edges; + igraph_int_t n = n1 + n2; /* overflow checked by caller */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, m * 2)); + + for (igraph_int_t i = 0; i < m; i++) { + igraph_int_t to, from; + + to = RNG_INTEGER(n1, n - 1); + from = RNG_INTEGER(0, n1 - 1); + + /* flip unconditionally for IGRAPH_IN, + * or with probability 0.5 for IGRAPH_ALL */ + if (mode == IGRAPH_IN || (mode == IGRAPH_ALL && RNG_BOOL())) { + igraph_vector_int_push_back(&edges, to); /* reserved */ + igraph_vector_int_push_back(&edges, from); /* reserved */ + } else { + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + } + + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t bipartite_gnm_multi( + igraph_t *graph, + igraph_int_t n1, igraph_int_t n2, + igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode) { + + /* See igraph_erdos_renyi_game_gnm() for how the sampling works. */ + + igraph_vector_int_t edges; + igraph_int_t nrow, ncol; + igraph_int_t last; + igraph_int_t offset1 = 0, offset2 = n1; + igraph_int_t n = n1 + n2; /* overflow checked by caller */ + + /* The larger partition is associated with columns, the smaller + * with rows. This setup helps avoid integer overflow. We swap + * n1 and n2 so that n1 is smaller. */ + if (n1 > n2) { + igraph_int_t tmp = n1; + n1 = n2; + n2 = tmp; + + offset1 = n2; offset2 = 0; + + mode = IGRAPH_REVERSE_MODE(mode); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*m); + + if (!directed || mode != IGRAPH_ALL) { + nrow = n1; + ncol = n2; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= n1) { + igraph_int_t j = (r - n1) * ncol + c; + if (j >= i) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*j]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*j+1]; + } else { + if (directed && mode == IGRAPH_IN) { + VECTOR(edges)[2*i] = c + offset2; + VECTOR(edges)[2*i+1] = r + offset1; + } else { + VECTOR(edges)[2*i] = r + offset1; + VECTOR(edges)[2*i+1] = c + offset2; + } + } + + last += 1; + if (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + } + } else /* directed, mutual allowed */ { + nrow = 2*n1; + ncol = n2; + last = ncol-1; + for (igraph_int_t i=0; i < m; i++) { + while (true) { + igraph_int_t r = RNG_INTEGER(0, nrow-1); + igraph_int_t c = RNG_INTEGER(0, ncol-1); + + if (r >= 2*n1) { + igraph_int_t j = (r - 2*n1) * ncol + c; + if (j >= i) continue; /* rejection sampling */ + VECTOR(edges)[2*i] = VECTOR(edges)[2*j]; + VECTOR(edges)[2*i+1] = VECTOR(edges)[2*j+1]; + } else { + if (r < n1) { + VECTOR(edges)[2*i] = r + offset1; + VECTOR(edges)[2*i+1] = c + offset2; + } else { + VECTOR(edges)[2*i] = c + offset2; + VECTOR(edges)[2*i+1] = r - n1 + offset1; + } + } + + last += 1; + if (last >= ncol) { + last -= ncol; + nrow++; + } + + break; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t bipartite_gnm_simple( + igraph_t *graph, + igraph_int_t n1, igraph_int_t n2, + igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_bool_t edge_labeled) { + + igraph_vector_int_t edges; + igraph_vector_t s; + igraph_real_t n1_real = (igraph_real_t) n1, n2_real = (igraph_real_t) n2; /* for floating-point operations */ + igraph_int_t n = n1 + n2; /* overflow checked by caller */ + igraph_real_t maxedges; + int iter = 0; + + if (!directed || mode != IGRAPH_ALL) { + maxedges = n1_real * n2_real; + } else { + maxedges = 2.0 * n1_real * n2_real; + } + + if (m > maxedges) { + IGRAPH_ERROR("Too many edges requested compared to the number of vertices.", IGRAPH_EINVAL); + } + + if (maxedges == m && ! edge_labeled) { + /* TODO: Cannot use igraph_full_bipartite() when edge_labeled as we must shuffle edges. */ + IGRAPH_CHECK(igraph_full_bipartite(graph, NULL, n1, n2, directed, mode)); + } else { + igraph_int_t to, from; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_i_random_sample_real(&s, 0, maxedges - 1, m)); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, m * 2)); + + for (igraph_int_t i = 0; i < m; i++) { + if (!directed || mode != IGRAPH_ALL) { + to = trunc(VECTOR(s)[i] / n1_real); + from = VECTOR(s)[i] - to * n1_real; + to += n1; + } else { + igraph_real_t n1n2 = n1_real * n2_real; + if (VECTOR(s)[i] < n1n2) { + to = trunc(VECTOR(s)[i] / n1_real); + from = VECTOR(s)[i] - to * n1_real; + to += n1; + } else { + to = trunc((VECTOR(s)[i] - n1n2) / n2_real); + from = VECTOR(s)[i] - n1n2 - to * n2_real; + from += n1; + } + } + + if (mode != IGRAPH_IN) { + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + } else { + igraph_vector_int_push_back(&edges, to); /* reserved */ + igraph_vector_int_push_back(&edges, from); /* reserved */ + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + + if (edge_labeled) { + IGRAPH_CHECK(igraph_i_vector_int_shuffle_pairs(&edges)); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bipartite_game_gnm + * \brief Generate a random bipartite graph with a fixed number of edges. + * + * The G(n1, n2, m) model uniformly samples bipartite graphs with + * \p n1 bottom vertices and \p n2 top vertices, and precisely \p m edges. + * + * \param graph Pointer to an uninitialized igraph graph, the result + * is stored here. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer, then the vertex types are stored + * here. Bottom vertices come first, \p n1 of them, then \p n2 top + * vertices. + * \param n1 The number of bottom vertices. + * \param n2 The number of top vertices. + * \param m The number of edges. + * \param directed Boolean, whether to generate a directed graph. See + * also the \p mode argument. + * \param mode Specifies how to direct the edges in directed + * graphs. If it is \c IGRAPH_OUT, then directed edges point from + * bottom vertices to top vertices. If it is \c IGRAPH_IN, edges + * point from top vertices to bottom vertices. \c IGRAPH_OUT and + * \c IGRAPH_IN do not generate mutual edges. If this argument is + * \c IGRAPH_ALL, then each edge direction is considered + * independently and mutual edges might be generated. This + * argument is ignored for undirected graphs. +* \param allowed_edge_types The types of edges to allow in the graph. + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graph (i.e. no multi-edges allowed). + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed + * \endclist + * \param edge_labeled If true, the sampling is done uniformly from the set + * of ordered edge lists. See \ref igraph_bipartite_iea_game() for more + * information. Set this to \c false to select the classic Erdős-Rényi model. + * The constants \c IGRAPH_EDGE_UNLABELED and \c IGRAPH_EDGE_LABELED + * may be used instead of \c false and \c true for better readability. + * \return Error code. + * + * \sa \ref igraph_erdos_renyi_game_gnm() for the unipartite version, + * \ref igraph_bipartite_game_gnp() for the G(n1, n2, p) + * model. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_bipartite_game_gnm( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled) { + + igraph_int_t n; + igraph_bool_t loops, multiple; + + if (n1 < 0 || n2 < 0) { + IGRAPH_ERROR("Invalid number of vertices for bipartite G(n,m) model.", IGRAPH_EINVAL); + } + if (m < 0 || m > IGRAPH_ECOUNT_MAX) { + IGRAPH_ERROR("Invalid number of edges for bipartite G(n,m) model.", IGRAPH_EINVAL); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for bipartite G(n,m) model.", IGRAPH_EINVAL); + } + + /* Bipartite graphs cannot have self-loops. We ignore them. */ + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + IGRAPH_SAFE_ADD(n1, n2, &n); /* overflow check */ + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, n)); + igraph_vector_bool_null(types); + for (igraph_int_t i = n1; i < n; i++) { + VECTOR(*types)[i] = true; + } + } + + if (m == 0 || n1 == 0 || n2 == 0) { + if (m > 0) { + IGRAPH_ERROR("Too many edges requested compared to the number of vertices.", IGRAPH_EINVAL); + } + return igraph_empty(graph, n, directed); + } else if (multiple) { + if (edge_labeled) { + return bipartite_iea_game(graph, n1, n2, m, directed, mode); + } else { + return bipartite_gnm_multi(graph, n1, n2, m, directed, mode); + } + } else { + return bipartite_gnm_simple(graph, n1, n2, m, directed, mode, edge_labeled); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bipartite_iea_game + * \brief Generates a random bipartite multigraph through independent edge assignment. + * + * \experimental + * + * This model generates random multigraphs with \p n1 bottom vertices, + * \p n2 top vertices and \p m edges through independent edge assignment (IEA). + * Each of the \p m edges is assigned uniformly at random to a vertex pair, + * independently of each other. + * + * + * This model does not sample multigraphs uniformly. Undirected graphs are + * generated with probability proportional to + * + * + * (prod_(i<j) A_ij !)^(-1), + * + * + * where \c A denotes the adjacency matrix. The corresponding expression for + * directed graphs is + * + * + * (prod_(i,j) A_ij !)^(-1). + * + * + * Thus the probability of all simple graphs (which only have 0s and 1s in the + * adjacency matrix) is the same, while that of non-simple ones depends on + * their edge and self-loop multiplicities. + * + * \param graph Pointer to an uninitialized igraph graph, the result + * is stored here. + * \param types Pointer to an initialized boolean vector, or a \c NULL + * pointer. If not \c NULL, then the vertex types are stored + * here. Bottom vertices come first, \p n1 of them, then \p n2 top + * vertices. + * \param n1 The number of bottom vertices. + * \param n2 The number of top vertices. + * \param m The number of edges. + * \param directed Whether to generate a directed graph. See + * also the \p mode argument. + * \param mode Specifies how to direct the edges in directed + * graphs. If it is \c IGRAPH_OUT, then directed edges point from + * bottom vertices to top vertices. If it is \c IGRAPH_IN, edges + * point from top vertices to bottom vertices. \c IGRAPH_OUT and + * \c IGRAPH_IN do not generate mutual edges. If this argument is + * \c IGRAPH_ALL, then each edge direction is considered + * independently and mutual edges might be generated. This + * argument is ignored for undirected graphs. + * \return Error code. + * + * \sa \ref igraph_iea_game() for the unipartite version; + * \ref igraph_bipartite_game_gnm() to uniformly sample bipartite graphs + * with a given number of vertices and edges. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_bipartite_iea_game( + igraph_t *graph, igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode) { + + return igraph_bipartite_game_gnm( + graph, types, n1, n2, m, directed, mode, + IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); +} + + +static igraph_error_t bipartite_gnp_edge_labeled( + igraph_t *graph, + igraph_int_t n1, igraph_int_t n2, igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_bool_t multiple) { + + if (multiple) { + igraph_real_t maxedges; + + if (!directed || mode != IGRAPH_ALL) { + maxedges = (igraph_real_t) n1 * (igraph_real_t) n2; + } else { + maxedges = 2.0 * (igraph_real_t) n1 * (igraph_real_t) n2; + } + + igraph_real_t m; + do { + m = RNG_GEOM( 1.0 / (1.0 + maxedges * p) ); + } while (m > (igraph_real_t) IGRAPH_INTEGER_MAX); + + return bipartite_iea_game(graph, n1, n2, m, directed, mode); + } else { + IGRAPH_ERROR("The edge-labeled bipartite G(n,p) model is not yet implemented for graphs without multi-edges.", + IGRAPH_UNIMPLEMENTED); + } +} + +/* This implementation is used only with very large vertex counts, when the + * default implementation would fail due to overflow. While this version + * avoids overflow and uses less memory, it is also slower than the default + * implementation. + * + * This function expects that when multiple=true, the p parameter has already + * been transformed by p = p / (1 + p). This is currently done by the caller. + */ +static igraph_error_t gnp_bipartite_large( + igraph_t *graph, + igraph_int_t n1, igraph_int_t n2, + igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_bool_t multiple, + igraph_int_t ecount_estimate) { + + igraph_vector_int_t edges; + int iter = 0; + + /* Necessitated by floating point arithmetic used in the implementation. */ + if (n1 >= IGRAPH_MAX_EXACT_REAL || n2 >= IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Number of vertices is too large.", IGRAPH_EOVERFLOW); + } + + if (ecount_estimate > IGRAPH_ECOUNT_MAX) { + ecount_estimate = IGRAPH_ECOUNT_MAX; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, 2*ecount_estimate)); + + for (igraph_int_t i = 0; i < n1; i++) { + igraph_int_t j = 0; + + while (true) { + igraph_real_t gap = RNG_GEOM(p); + + if (gap >= n2 - j) { + break; + } + + j += gap; + + if (!directed) { + /* Undirected graph */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j + n1)); + } else if (mode == IGRAPH_IN) { + /* Incoming edges */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j + n1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + } else if (mode == IGRAPH_OUT) { + /* Outgoing edges */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j + n1)); + } else { + /* Both directions for IGRAPH_ALL */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j + n1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j + n1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + } + + j += ! multiple; /* 1 for simple graph, 0 for multigraph */ + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + } + + /* n1 + n2 has already been checked for overflow in the caller function. */ + IGRAPH_CHECK(igraph_create(graph, &edges, n1 + n2, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bipartite_game_gnp + * \brief Generates a random bipartite graph with a fixed connection probability. + * + * In the G(n1, n2, p) model, every possible edge between the \p n1 + * bottom vertices and \p n2 top vertices is realized independently with + * probability \p p. This is equivalent to a maximum entropy model with + * a constraint on the \em expected total edge count. This view allows + * a multigraph extension, in which case \p is interpreted as the expected + * number of edges between any vertex pair. See \ref igraph_erdos_renyi_game_gnp() + * for more details. + * + * \param graph Pointer to an uninitialized igraph graph, the result + * is stored here. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not \c NULL, then the vertex types are stored + * here. Bottom vertices come first, \p n1 of them, then \p n2 top + * vertices. + * \param n1 The number of bottom vertices. + * \param n2 The number of top vertices. + * \param p The expected number of edges between any vertex pair. + * When multi-edges are disallowed, this is equivalent to the probability + * of having a connection between any two vertices. + * \param directed Whether to generate a directed graph. See also + * the \p mode argument. + * \param mode Specifies how to direct the edges in directed + * graphs. If it is \c IGRAPH_OUT, then directed edges point from + * bottom vertices to top vertices. If it is \c IGRAPH_IN, edges + * point from top vertices to bottom vertices. \c IGRAPH_OUT and + * \c IGRAPH_IN do not generate mutual edges. If this argument is + * \c IGRAPH_ALL, then each edge direction is considered + * independently and mutual edges might be generated. This + * argument is ignored for undirected graphs. +* \param allowed_edge_types The types of edges to allow in the graph. + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graph (i.e. no multi-edges allowed). + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed + * \endclist + * \param edge_labeled If true, the model is defined over the set of ordered + * edge lists, i.e. over the set of edge-labeled graphs. Set it to + * \c false to select the classic bipartite Erdős-Rényi model. + * The constants \c IGRAPH_EDGE_UNLABELED and \c IGRAPH_EDGE_LABELED + * may be used instead of \c false and \c true for better readability. + * \return Error code. + * + * \sa \ref igraph_erdos_renyi_game_gnp() for the unipartite version, + * \ref igraph_bipartite_game_gnm() for the G(n1, n2, m) model. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +igraph_error_t igraph_bipartite_game_gnp( + igraph_t *graph, + igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t edge_labeled) { + + igraph_vector_int_t edges; + igraph_vector_t s; + igraph_int_t n; + igraph_real_t n1_real = (igraph_real_t) n1, n2_real = (igraph_real_t) n2; /* for floating-point operations */ + igraph_bool_t loops, multiple; + int iter = 0; + + if (n1 < 0 || n2 < 0) { + IGRAPH_ERROR("Invalid number of vertices for bipartite G(n,p) model.", IGRAPH_EINVAL); + } + + /* Bipartite graphs cannot have self-loops. We ignore them. */ + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + if (multiple) { + if (p < 0.0) { + IGRAPH_ERROR( + "Invalid expected edge multiplicity given for " + "bipartite G(n,p) multigraph model.", + IGRAPH_EINVAL); + } + } else { + if (p < 0.0 || p > 1.0) { + IGRAPH_ERROR( + "Invalid connection probability given for bipartite G(n,p) model.", + IGRAPH_EINVAL); + } + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for bipartite G(n,p) model.", IGRAPH_EINVAL); + } + + IGRAPH_SAFE_ADD(n1, n2, &n); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, n)); + igraph_vector_bool_null(types); + for (igraph_int_t i = n1; i < n; i++) { + VECTOR(*types)[i] = true; + } + } + + if (edge_labeled) { + return bipartite_gnp_edge_labeled(graph, n1, n2, p, directed, mode, multiple); + } + + if (multiple) { + /* Convert the expected edge count to the appropriate probability parameter + * of the geometric distribution when sampling lengths of runs of 0s in the + * adjacency matrix. */ + p = p / (1 + p); + } + + if (p == 0 || n1 == 0 || n2 == 0) { + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + } else if (p == 1.0) { + IGRAPH_CHECK(igraph_full_bipartite(graph, types, n1, n2, directed, mode)); + } else { + + igraph_int_t to, from, slen; + igraph_real_t maxedges, last; + igraph_int_t ecount_estimate; + + if (!directed || mode != IGRAPH_ALL) { + maxedges = n1_real * n2_real; + } else { + maxedges = 2.0 * n1_real * n2_real; + } + + IGRAPH_CHECK(igraph_i_safe_floor(maxedges * p * 1.1, &ecount_estimate)); + + if (maxedges > IGRAPH_MAX_EXACT_REAL) { + /* Use a slightly slower, but overflow-free implementation. */ + return gnp_bipartite_large(graph, n1, n2, p, directed, mode, multiple, ecount_estimate); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, ecount_estimate)); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += ! multiple; /* 1 for simple graph, 0 for multigraph */ + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + slen = igraph_vector_size(&s); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, slen * 2)); + + for (igraph_int_t i = 0; i < slen; i++) { + if (!directed || mode != IGRAPH_ALL) { + to = trunc(VECTOR(s)[i] / n1_real); + from = VECTOR(s)[i] - to * n1_real; + to += n1; + } else { + igraph_real_t n1n2 = n1_real * n2_real; + if (VECTOR(s)[i] < n1n2) { + to = trunc(VECTOR(s)[i] / n1_real); + from = VECTOR(s)[i] - to * n1_real; + to += n1; + } else { + to = trunc((VECTOR(s)[i] - n1n2) / n2_real); + from = VECTOR(s)[i] - n1n2 - to * n2_real; + from += n1; + } + } + + if (mode != IGRAPH_IN) { + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + } else { + igraph_vector_int_push_back(&edges, to); /* reserved */ + igraph_vector_int_push_back(&edges, from); /* reserved */ + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 14); + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/chordality.c b/src/misc/chordality.c new file mode 100644 index 0000000..ff90cb7 --- /dev/null +++ b/src/misc/chordality.c @@ -0,0 +1,477 @@ +/* + igraph library. + Copyright (C) 2008-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_structural.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" + +/** + * \function igraph_maximum_cardinality_search + * \brief Maximum cardinality search. + * + * This function implements the maximum cardinality search algorithm. + * It computes a rank \p alpha for each vertex, such that visiting + * vertices in decreasing rank order corresponds to always choosing + * the vertex with the most already visited neighbors as the next one + * to visit. + * + * + * Maximum cardinality search is useful in deciding the chordality + * of a graph. A graph is chordal if and only if any two neighbors + * of a vertex which are higher in rank than it are connected to + * each other. + * + * + * References: + * + * + * Robert E Tarjan and Mihalis Yannakakis: Simple linear-time + * algorithms to test chordality of graphs, test acyclicity of + * hypergraphs, and selectively reduce acyclic hypergraphs. + * SIAM Journal of Computation 13, 566--579, 1984. + * https://doi.org/10.1137/0213035 + * + * \param graph The input graph. Edge directions will be ignored. + * \param alpha Pointer to an initialized vector, the result is stored here. + * It will be resized, as needed. Upon return it contains + * the rank of the each vertex in the range 0 to n - 1, + * where \c n is the number of vertices. + * \param alpham1 Pointer to an initialized vector or a \c NULL + * pointer. If not \c NULL, then the inverse of \p alpha is stored + * here. In other words, the elements of \p alpham1 are vertex IDs + * in reverse maximum cardinality search order. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in terms of the number of + * vertices and edges. + * + * \sa \ref igraph_is_chordal(). + */ + +igraph_error_t igraph_maximum_cardinality_search(const igraph_t *graph, + igraph_vector_int_t *alpha, + igraph_vector_int_t *alpham1) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t size; + igraph_vector_int_t head, next, prev; /* doubly linked list with head */ + igraph_int_t i; + igraph_adjlist_t adjlist; + + /***************/ + /* local j, v; */ + /***************/ + + igraph_int_t j, v; + + if (no_of_nodes == 0) { + igraph_vector_int_clear(alpha); + if (alpham1) { + igraph_vector_int_clear(alpham1); + } + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&size, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&head, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&next, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&prev, no_of_nodes); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_vector_int_resize(alpha, no_of_nodes)); + if (alpham1) { + IGRAPH_CHECK(igraph_vector_int_resize(alpham1, no_of_nodes)); + } + + /***********************************************/ + /* for i in [0,n-1] -> set(i) := emptyset rof; */ + /***********************************************/ + + /* nothing to do, 'head' contains all zeros */ + + /*********************************************************/ + /* for v in vertices -> size(v):=0; add v to set(0) rof; */ + /*********************************************************/ + + VECTOR(head)[0] = 1; + for (v = 0; v < no_of_nodes; v++) { + VECTOR(next)[v] = v + 2; + VECTOR(prev)[v] = v; + } + VECTOR(next)[no_of_nodes - 1] = 0; + /* size is already all zero */ + + /***************/ + /* i:=n; j:=0; */ + /***************/ + + i = no_of_nodes; j = 0; + + /**************/ + /* do i>=1 -> */ + /**************/ + + while (i >= 1) { + igraph_int_t x, k, len; + igraph_vector_int_t *neis; + + /********************************/ + /* v := delete any from set(j) */ + /********************************/ + + v = VECTOR(head)[j] - 1; + x = VECTOR(next)[v]; + VECTOR(head)[j] = x; + if (x != 0) { + VECTOR(prev)[x - 1] = 0; + } + + /*************************************************/ + /* alpha(v) := i; alpham1(i) := v; size(v) := -1 */ + /*************************************************/ + + VECTOR(*alpha)[v] = i - 1; + if (alpham1) { + VECTOR(*alpham1)[i - 1] = v; + } + VECTOR(size)[v] = -1; + + /********************************************/ + /* for {v,w} in E such that size(w) >= 0 -> */ + /********************************************/ + + neis = igraph_adjlist_get(&adjlist, v); + len = igraph_vector_int_size(neis); + for (k = 0; k < len; k++) { + igraph_int_t w = VECTOR(*neis)[k]; + igraph_int_t ws = VECTOR(size)[w]; + if (ws >= 0) { + + /******************************/ + /* delete w from set(size(w)) */ + /******************************/ + + igraph_int_t nw = VECTOR(next)[w]; + igraph_int_t pw = VECTOR(prev)[w]; + if (nw != 0) { + VECTOR(prev)[nw - 1] = pw; + } + if (pw != 0) { + VECTOR(next)[pw - 1] = nw; + } else { + VECTOR(head)[ws] = nw; + } + + /******************************/ + /* size(w) := size(w)+1 */ + /******************************/ + + VECTOR(size)[w] += 1; + + /******************************/ + /* add w to set(size(w)) */ + /******************************/ + + ws = VECTOR(size)[w]; + nw = VECTOR(head)[ws]; + VECTOR(next)[w] = nw; + VECTOR(prev)[w] = 0; + if (nw != 0) { + VECTOR(prev)[nw - 1] = w + 1; + } + VECTOR(head)[ws] = w + 1; + + } + } + + /***********************/ + /* i := i-1; j := j+1; */ + /***********************/ + + i -= 1; + j += 1; + + /*********************************************/ + /* do j>=0 and set(j)=emptyset -> j:=j-1; od */ + /*********************************************/ + + if (j < no_of_nodes) { + while (j >= 0 && VECTOR(head)[j] == 0) { + j--; + } + } + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&prev); + igraph_vector_int_destroy(&next); + igraph_vector_int_destroy(&head); + igraph_vector_int_destroy(&size); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_chordal + * \brief Decides whether a graph is chordal. + * + * A graph is chordal if each of its cycles of four or more nodes + * has a chord, i.e. an edge joining two nodes that are not + * adjacent in the cycle. An equivalent definition is that any + * chordless cycles have at most three nodes. + * + * If either \p alpha or \p alpham1 is given, then the other is + * calculated by taking simply the inverse. If neither are given, + * then \ref igraph_maximum_cardinality_search() is called to calculate + * them. + * + * \param graph The input graph. Edge directions will be ignored. + * \param alpha Either an alpha vector coming from + * \ref igraph_maximum_cardinality_search() (on the same graph), or a + * \c NULL pointer. + * \param alpham1 Either an inverse alpha vector coming from \ref + * igraph_maximum_cardinality_search() (on the same graph) or a \c NULL + * pointer. + * \param chordal Pointer to a boolean. If not NULL the result is stored here. + * \param fill_in Pointer to an initialized vector, or a \c NULL + * pointer. If not a \c NULL pointer, then the fill-in, also called the + * chordal completion of the graph is stored here. + * The chordal completion is a set of edges that are needed to + * make the graph chordal. The vector is resized as needed. + * Note that the chordal completion returned by this function may not + * be minimal, i.e. some of the returned fill-in edges may not be needed + * to make the graph chordal. + * \param newgraph Pointer to an uninitialized graph, or a \c NULL + * pointer. If not a null pointer, then a new triangulated graph is + * created here. This essentially means adding the fill-in edges to + * the original graph. + * \return Error code. + * + * Time complexity: O(n). + * + * \sa \ref igraph_maximum_cardinality_search(). + */ + +igraph_error_t igraph_is_chordal(const igraph_t *graph, + const igraph_vector_int_t *alpha, + const igraph_vector_int_t *alpham1, + igraph_bool_t *chordal, + igraph_vector_int_t *fill_in, + igraph_t *newgraph) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_vector_int_t *my_alpha = alpha, *my_alpham1 = alpham1; + igraph_vector_int_t v_alpha, v_alpham1; + igraph_vector_int_t f, index; + igraph_int_t i; + igraph_adjlist_t adjlist; + igraph_vector_int_t mark; + igraph_bool_t calc_edges = fill_in || newgraph; + igraph_vector_int_t *my_fill_in = fill_in, v_fill_in; + + /*****************/ + /* local v, w, x */ + /*****************/ + + igraph_int_t v, w, x; + + if (alpha && (igraph_vector_int_size(alpha) != no_of_nodes)) { + IGRAPH_ERRORF("Alpha vector size (%" IGRAPH_PRId ") not equal to number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(alpha), no_of_nodes); + } + + if (alpham1 && (igraph_vector_int_size(alpham1) != no_of_nodes)) { + IGRAPH_ERRORF("Inverse alpha vector size (%" IGRAPH_PRId ") not equal to number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(alpham1), no_of_nodes); + } + + if (!chordal && !calc_edges) { + /* Nothing to calculate */ + return IGRAPH_SUCCESS; + } + + if (!alpha && !alpham1) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_alpha, no_of_nodes); + my_alpha = &v_alpha; + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_alpham1, no_of_nodes); + my_alpham1 = &v_alpham1; + IGRAPH_CHECK(igraph_maximum_cardinality_search(graph, + (igraph_vector_int_t*) my_alpha, + (igraph_vector_int_t*) my_alpham1)); + } else if (alpha && !alpham1) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_alpham1, no_of_nodes); + my_alpham1 = &v_alpham1; + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + igraph_int_t i = VECTOR(*my_alpha)[v]; + VECTOR(*my_alpham1)[i] = v; + } + } else if (!alpha && alpham1) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_alpha, no_of_nodes); + my_alpha = &v_alpha; + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t v = VECTOR(*my_alpham1)[i]; + VECTOR(*my_alpha)[v] = i; + } + } + + if (!fill_in && newgraph) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&v_fill_in, 0); + my_fill_in = &v_fill_in; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&f, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&index, no_of_nodes); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_VECTOR_INT_INIT_FINALLY(&mark, no_of_nodes); + if (my_fill_in) { + igraph_vector_int_clear(my_fill_in); + } + + if (chordal) { + *chordal = true; + } + + /*********************/ + /* for i in [1,n] -> */ + /*********************/ + + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis; + igraph_int_t j, len; + + /**********************************************/ + /* w := alpham1(i); f(w) := w; index(w) := i; */ + /**********************************************/ + + w = VECTOR(*my_alpham1)[i]; + VECTOR(f)[w] = w; + VECTOR(index)[w] = i; + + /******************************************/ + /* for {v,w} in E such that alpha(v) */ + /******************************************/ + + neis = igraph_adjlist_get(&adjlist, w); + len = igraph_vector_int_size(neis); + for (j = 0; j < len; j++) { + v = VECTOR(*neis)[j]; + VECTOR(mark)[v] = w + 1; + } + + for (j = 0; j < len; j++) { + v = VECTOR(*neis)[j]; + if (VECTOR(*my_alpha)[v] >= i) { + continue; + } + + /**********/ + /* x := v */ + /**********/ + + x = v; + + /********************/ + /* do index(x) */ + /********************/ + + while (VECTOR(index)[x] < i) { + + /******************/ + /* index(x) := i; */ + /******************/ + + VECTOR(index)[x] = i; + + /**********************************/ + /* add {x,w} to E union F(alpha); */ + /**********************************/ + + if (VECTOR(mark)[x] != w + 1) { + + if (chordal) { + *chordal = false; + } + + if (my_fill_in) { + IGRAPH_CHECK(igraph_vector_int_push_back(my_fill_in, x)); + IGRAPH_CHECK(igraph_vector_int_push_back(my_fill_in, w)); + } + + if (!calc_edges) { + /* make sure that we exit from all loops */ + i = no_of_nodes; + j = len; + break; + } + } + + /*************/ + /* x := f(x) */ + /*************/ + + x = VECTOR(f)[x]; + + } /* while (VECTOR(index)[x] < i) */ + + /*****************************/ + /* if (f(x)=x -> f(x):=w; fi */ + /*****************************/ + + if (VECTOR(f)[x] == x) { + VECTOR(f)[x] = w; + } + } + } + + igraph_vector_int_destroy(&mark); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&index); + igraph_vector_int_destroy(&f); + IGRAPH_FINALLY_CLEAN(4); + + if (newgraph) { + IGRAPH_CHECK(igraph_copy(newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, newgraph); + IGRAPH_CHECK(igraph_add_edges(newgraph, my_fill_in, 0)); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!fill_in && newgraph) { + igraph_vector_int_destroy(&v_fill_in); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!alpha && !alpham1) { + igraph_vector_int_destroy(&v_alpham1); + igraph_vector_int_destroy(&v_alpha); + IGRAPH_FINALLY_CLEAN(2); + } else if (alpha && !alpham1) { + igraph_vector_int_destroy(&v_alpham1); + IGRAPH_FINALLY_CLEAN(1); + } else if (!alpha && alpham1) { + igraph_vector_int_destroy(&v_alpha); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/cocitation.c b/src/misc/cocitation.c new file mode 100644 index 0000000..9eb4586 --- /dev/null +++ b/src/misc/cocitation.c @@ -0,0 +1,767 @@ +/* + igraph library. + Copyright (C) 2005-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_cocitation.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" + +#include "core/interruption.h" + +#include + +static igraph_error_t igraph_i_cocitation_real(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + igraph_vector_t *weights); + +/** + * \ingroup structural + * \function igraph_cocitation + * \brief Cocitation coupling. + * + * Two vertices are cocited if there is another vertex citing both of + * them. \ref igraph_cocitation() simply counts how many times two vertices are + * cocited. + * The cocitation score for each given vertex and all other vertices + * in the graph will be calculated. + * + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex IDs in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex IDs of the vertices for which the + * calculation will be done. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * + * Time complexity: O(|V|d^2), |V| is + * the number of vertices in the graph, + * d is the (maximum) degree of + * the vertices in the graph. + * + * \sa \ref igraph_bibcoupling() + * + * \example examples/simple/igraph_cocitation.c + */ + +igraph_error_t igraph_cocitation(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids) { + return igraph_i_cocitation_real(graph, res, vids, IGRAPH_OUT, NULL); +} + +/** + * \ingroup structural + * \function igraph_bibcoupling + * \brief Bibliographic coupling. + * + * The bibliographic coupling of two vertices is the number + * of other vertices they both cite, \ref igraph_bibcoupling() calculates + * this. + * The bibliographic coupling score for each given vertex and all + * other vertices in the graph will be calculated. + * + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex IDs in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex IDs of the vertices for which the + * calculation will be done. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * + * Time complexity: O(|V|d^2), + * |V| is the number of vertices in + * the graph, d is the (maximum) + * degree of the vertices in the graph. + * + * \sa \ref igraph_cocitation() + * + * \example examples/simple/igraph_cocitation.c + */ + +igraph_error_t igraph_bibcoupling(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids) { + return igraph_i_cocitation_real(graph, res, vids, IGRAPH_IN, NULL); +} + +/** + * \ingroup structural + * \function igraph_similarity_inverse_log_weighted + * \brief Vertex similarity based on the inverse logarithm of vertex degrees. + * + * The inverse log-weighted similarity of two vertices is the number of + * their common neighbors, weighted by the inverse logarithm of their degrees. + * It is based on the assumption that two vertices should be considered + * more similar if they share a low-degree common neighbor, since high-degree + * common neighbors are more likely to appear even by pure chance. + * + * + * Isolated vertices will have zero similarity to any other vertex. + * Self-similarities are not calculated. + * + * + * Note that the presence of loop edges may yield counter-intuitive + * results. A node with a loop edge is considered to be a neighbor of itself + * \em twice (because there are two edge stems incident on the node). Adding a + * loop edge to a node may decrease its similarity to other nodes, but it may + * also \em increase it. For instance, if nodes A and B are connected but share + * no common neighbors, their similarity is zero. However, if a loop edge is + * added to B, then B itself becomes a common neighbor of A and B and thus the + * similarity of A and B will be increased. Consider removing loop edges + * explicitly before invoking this function using \ref igraph_simplify(). + * + * + * See the following paper for more details: Lada A. Adamic and Eytan Adar: + * Friends and neighbors on the Web. Social Networks, 25(3):211-230, 2003. + * https://doi.org/10.1016/S0378-8733(03)00009-1 + * + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex IDs in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex IDs of the vertices for which the + * calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. Nodes + * will be weighted according to their in-degree. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. Nodes + * will be weighted according to their out-degree. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. Every node is weighted according to its undirected + * degree. + * \endclist + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * + * Time complexity: O(|V|d^2), + * |V| is the number of vertices in + * the graph, d is the (maximum) + * degree of the vertices in the graph. + * + * \example examples/simple/igraph_similarity.c + */ + +igraph_error_t igraph_similarity_inverse_log_weighted(const igraph_t *graph, + igraph_matrix_t *res, const igraph_vs_t vids, igraph_neimode_t mode) { + igraph_vector_t weights; + igraph_vector_int_t degrees; + igraph_neimode_t mode0 = IGRAPH_REVERSE_MODE(mode); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for inverse log weighted similarity.", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&weights, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), mode0, IGRAPH_LOOPS)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(weights)[i] = VECTOR(degrees)[i]; + if (VECTOR(weights)[i] > 1) { + VECTOR(weights)[i] = 1.0 / log(VECTOR(weights)[i]); + } + } + + IGRAPH_CHECK(igraph_i_cocitation_real(graph, res, vids, mode0, &weights)); + igraph_vector_int_destroy(°rees); + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_cocitation_real(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_vector_t *weights) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_vids; + igraph_int_t i; + igraph_vector_int_t neis; + igraph_vector_int_t vid_reverse_index; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + no_of_vids = IGRAPH_VIT_SIZE(vit); + + /* Create a mapping from vertex IDs to the row of the matrix where + * the result for this vertex will appear */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&vid_reverse_index, no_of_nodes); + igraph_vector_int_fill(&vid_reverse_index, -1); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t v = IGRAPH_VIT_GET(vit); + if (v < 0 || v >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex ID in vertex selector.", IGRAPH_EINVVID); + } + VECTOR(vid_reverse_index)[v] = i; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_vids, no_of_nodes)); + igraph_matrix_null(res); + + /* The result */ + + for (igraph_int_t from = 0; from < no_of_nodes; from++) { + IGRAPH_ALLOW_INTERRUPTION(); + + const igraph_real_t weight = weights ? VECTOR(*weights)[from] : 1; + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, from, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + const igraph_int_t nei_count = igraph_vector_int_size(&neis); + + for (i = 0; i < nei_count - 1; i++) { + igraph_int_t u = VECTOR(neis)[i]; + igraph_int_t k = VECTOR(vid_reverse_index)[u]; + for (igraph_int_t j = i + 1; j < nei_count; j++) { + igraph_int_t v = VECTOR(neis)[j]; + igraph_int_t l = VECTOR(vid_reverse_index)[v]; + if (k != -1) { + MATRIX(*res, k, v) += weight; + } + if (l != -1) { + MATRIX(*res, l, u) += weight; + } + } + } + } + + /* Clean up */ + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&vid_reverse_index); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_neisets_intersect( + const igraph_vector_int_t *v1, const igraph_vector_int_t *v2, + igraph_int_t *len_union, igraph_int_t *len_intersection +) { + /* ASSERT: v1 and v2 are sorted */ + igraph_int_t n1 = igraph_vector_int_size(v1), n2 = igraph_vector_int_size(v2); + *len_intersection = igraph_vector_int_intersection_size_sorted(v1, v2); + *len_union = n1 + n2 - *len_intersection; + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard + * \brief Jaccard similarity coefficient for the given vertices. + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for some (or all) of the vertices. + * + * \param graph The graph object to analyze + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows and columns is the same + * as the number of vertex IDs in \p from and \p to, respectively. + * \param from The vertex IDs of the first set of vertices of the + * pairs for which the calculation will be done. + * \param to The vertex IDs of the second set of vertices of the + * pairs for which the calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|^2 d), + * |V| is the number of vertices in the vertex iterator given, d is the + * (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice(), a measure very similar to the Jaccard + * coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_jaccard(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, igraph_neimode_t mode, igraph_bool_t loops) { + igraph_lazy_adjlist_t al; + igraph_vit_t vit_from, vit_to; + igraph_int_t i, j; + igraph_int_t len_union, len_intersection; + igraph_vector_int_t *v1, *v2; + igraph_int_t k; + + IGRAPH_CHECK(igraph_vit_create(graph, from, &vit_from)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit_from); + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit_to)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit_to); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + IGRAPH_CHECK(igraph_matrix_resize(res, IGRAPH_VIT_SIZE(vit_from), IGRAPH_VIT_SIZE(vit_to))); + + if (loops) { + for (IGRAPH_VIT_RESET(vit_from); !IGRAPH_VIT_END(vit_from); IGRAPH_VIT_NEXT(vit_from)) { + i = IGRAPH_VIT_GET(vit_from); + v1 = igraph_lazy_adjlist_get(&al, i); + IGRAPH_CHECK_OOM(v1, "Failed to query neighbors."); + if (!igraph_vector_int_binsearch(v1, i, &k)) { + IGRAPH_CHECK(igraph_vector_int_insert(v1, k, i)); + } + } + } + + for (IGRAPH_VIT_RESET(vit_from), i = 0; + !IGRAPH_VIT_END(vit_from); IGRAPH_VIT_NEXT(vit_from), i++) { + MATRIX(*res, i, i) = 1.0; + for (IGRAPH_VIT_RESET(vit_to), j = 0; + !IGRAPH_VIT_END(vit_to); IGRAPH_VIT_NEXT(vit_to), j++) { + if (j <= i) { + continue; + } + + v1 = igraph_lazy_adjlist_get(&al, IGRAPH_VIT_GET(vit_from)); + IGRAPH_CHECK_OOM(v1, "Failed to query neighbors."); + v2 = igraph_lazy_adjlist_get(&al, IGRAPH_VIT_GET(vit_to)); + IGRAPH_CHECK_OOM(v2, "Failed to query neighbors."); + + IGRAPH_CHECK(igraph_i_neisets_intersect(v1, v2, &len_union, &len_intersection)); + if (len_union > 0) { + MATRIX(*res, i, j) = ((igraph_real_t)len_intersection) / len_union; + } else { + MATRIX(*res, i, j) = 0.0; + } + MATRIX(*res, j, i) = MATRIX(*res, i, j); + } + } + + igraph_lazy_adjlist_destroy(&al); + igraph_vit_destroy(&vit_from); + igraph_vit_destroy(&vit_to); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard_pairs + * \brief Jaccard similarity coefficient for given vertex pairs. + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for a list of vertex pairs. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of pairs in \p pairs. + * \param pairs A vector that contains the pairs for which the similarity + * will be calculated. Each pair is defined by two consecutive elements, + * i.e. the first and second element of the vector specifies the first + * pair, the third and fourth element specifies the second pair and so on. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard() to calculate the Jaccard similarity + * between all pairs of a vertex set, or \ref igraph_similarity_dice() and + * \ref igraph_similarity_dice_pairs() for a measure very similar to the + * Jaccard coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_jaccard_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_lazy_adjlist_t al; + igraph_int_t u, v; + igraph_int_t len_union, len_intersection; + igraph_vector_int_t *v1, *v2; + + igraph_int_t k = igraph_vector_int_size(pairs); + if (k % 2 != 0) { + IGRAPH_ERROR("Number of elements in `pairs' must be even.", IGRAPH_EINVAL); + } + if (!igraph_vector_int_isininterval(pairs, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Invalid vertex ID in pairs.", IGRAPH_EINVVID); + } + IGRAPH_CHECK(igraph_vector_resize(res, k / 2)); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + if (loops) { + /* Add the loop edges */ + + igraph_bitset_t seen; + IGRAPH_BITSET_INIT_FINALLY(&seen, no_of_nodes); + + for (igraph_int_t i = 0; i < k; i++) { + igraph_int_t j = VECTOR(*pairs)[i]; + if (IGRAPH_BIT_TEST(seen, j)) { + continue; + } + IGRAPH_BIT_SET(seen, j); + v1 = igraph_lazy_adjlist_get(&al, j); + IGRAPH_CHECK_OOM(v1, "Failed to query neighbors."); + if (!igraph_vector_int_binsearch(v1, j, &u)) { + IGRAPH_CHECK(igraph_vector_int_insert(v1, u, j)); + } + } + + igraph_bitset_destroy(&seen); + IGRAPH_FINALLY_CLEAN(1); + } + + for (igraph_int_t i = 0, j = 0; i < k; i += 2, j++) { + u = VECTOR(*pairs)[i]; + v = VECTOR(*pairs)[i + 1]; + + if (u == v) { + VECTOR(*res)[j] = 1.0; + continue; + } + + v1 = igraph_lazy_adjlist_get(&al, u); + IGRAPH_CHECK_OOM(v1, "Failed to query neighbors."); + v2 = igraph_lazy_adjlist_get(&al, v); + IGRAPH_CHECK_OOM(v2, "Failed to query neighbors."); + + IGRAPH_CHECK(igraph_i_neisets_intersect(v1, v2, &len_union, &len_intersection)); + if (len_union > 0) { + VECTOR(*res)[j] = ((igraph_real_t)len_intersection) / len_union; + } else { + VECTOR(*res)[j] = 0.0; + } + } + + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard_es + * \brief Jaccard similarity coefficient for a given edge selector. + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for the endpoints of edges in a given edge + * selector. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of edges in \p es. + * \param es An edge selector that specifies the edges to be included in the + * result. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of edges in the edge selector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard() and \ref igraph_similarity_jaccard_pairs() + * to calculate the Jaccard similarity between all pairs of a vertex set or + * some selected vertex pairs, or \ref igraph_similarity_dice(), + * \ref igraph_similarity_dice_pairs() and \ref igraph_similarity_dice_es() for a + * measure very similar to the Jaccard coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_jaccard_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops) { + + igraph_vector_int_t pairs; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&pairs, 0); + IGRAPH_CHECK(igraph_edges(graph, es, &pairs, 0)); + IGRAPH_CHECK(igraph_similarity_jaccard_pairs(graph, res, &pairs, mode, loops)); + igraph_vector_int_destroy(&pairs); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice + * \brief Dice similarity coefficient. + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for some (or all) of the vertices. + * + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows and columns is the same + * as the number of vertex IDs in \p from and \p to, respectively. + * \param from The vertex IDs of the first vertices of the + * pairs for which the calculation will be done. + * \param to The vertex IDs of the second vertices of the + * pairs for which the calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|^2 d), + * where |V| is the number of vertices in the vertex iterator given, and + * d is the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard(), a measure very similar to the Dice + * coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_dice(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode, igraph_bool_t loops) { + + IGRAPH_CHECK(igraph_similarity_jaccard(graph, res, from, to, mode, loops)); + + igraph_int_t nr = igraph_matrix_nrow(res); + igraph_int_t nc = igraph_matrix_ncol(res); + for (igraph_int_t i = 0; i < nr; i++) { + for (igraph_int_t j = 0; j < nc; j++) { + igraph_real_t x = MATRIX(*res, i, j); + MATRIX(*res, i, j) = 2 * x / (1 + x); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice_pairs + * \brief Dice similarity coefficient for given vertex pairs. + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for a list of vertex pairs. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of pairs in \p pairs. + * \param pairs A vector that contains the pairs for which the similarity + * will be calculated. Each pair is defined by two consecutive elements, + * i.e. the first and second element of the vector specifies the first + * pair, the third and fourth element specifies the second pair and so on. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice() to calculate the Dice similarity + * between all pairs of a vertex set, or \ref igraph_similarity_jaccard(), + * \ref igraph_similarity_jaccard_pairs() and \ref igraph_similarity_jaccard_es() + * for a measure very similar to the Dice coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_dice_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_int_t *pairs, igraph_neimode_t mode, igraph_bool_t loops) { + + IGRAPH_CHECK(igraph_similarity_jaccard_pairs(graph, res, pairs, mode, loops)); + igraph_int_t n = igraph_vector_size(res); + for (igraph_int_t i = 0; i < n; i++) { + igraph_real_t x = VECTOR(*res)[i]; + VECTOR(*res)[i] = 2 * x / (1 + x); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice_es + * \brief Dice similarity coefficient for a given edge selector. + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for the endpoints of edges in a given + * edge selector. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of edges in \p es. + * \param es An edge selector that specifies the edges to be included in the + * result. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice() and \ref igraph_similarity_dice_pairs() + * to calculate the Dice similarity between all pairs of a vertex set or + * some selected vertex pairs, or \ref igraph_similarity_jaccard(), + * \ref igraph_similarity_jaccard_pairs() and \ref igraph_similarity_jaccard_es() + * for a measure very similar to the Dice coefficient + * + * \example examples/simple/igraph_similarity.c + */ +igraph_error_t igraph_similarity_dice_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops) { + + IGRAPH_CHECK(igraph_similarity_jaccard_es(graph, res, es, mode, loops)); + igraph_int_t n = igraph_vector_size(res); + for (igraph_int_t i = 0; i < n; i++) { + igraph_real_t x = VECTOR(*res)[i]; + VECTOR(*res)[i] = 2 * x / (1 + x); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/coloring.c b/src/misc/coloring.c new file mode 100644 index 0000000..a4081e8 --- /dev/null +++ b/src/misc/coloring.c @@ -0,0 +1,519 @@ +/* + Heuristic graph coloring algorithms. + Copyright (C) 2017 Szabolcs Horvat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_coloring.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" + +#include "core/genheap.h" +#include "core/indheap.h" +#include "core/interruption.h" + +/* COLORED_NEIGHBORS: Choose vertices based on the number of already coloured neighbours. */ + +static igraph_error_t igraph_i_vertex_coloring_greedy_cn(const igraph_t *graph, igraph_vector_int_t *colors) { + igraph_int_t i, vertex, maxdeg; + igraph_int_t vc = igraph_vcount(graph); + igraph_2wheap_t cn; /* indexed heap storing number of already coloured neighbours */ + igraph_vector_int_t neighbors, nei_colors; + + IGRAPH_CHECK(igraph_vector_int_resize(colors, vc)); + igraph_vector_int_null(colors); + + /* Nothing to do for 0 or 1 vertices. + * Remember that colours are integers starting from 0, + * and the 'colors' vector is already 0-initialized above. + */ + if (vc <= 1) { + return IGRAPH_SUCCESS; + } + + /* find maximum degree and a corresponding vertex */ + { + igraph_vector_int_t degree; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, 0); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS)); + + vertex = igraph_vector_int_which_max(°ree); + maxdeg = VECTOR(degree)[vertex]; + + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_colors, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&nei_colors, maxdeg)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbors, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&neighbors, maxdeg)); + + /* two-way indexed heap holding number of already colored neighbors of yet-uncolored vertices */ + IGRAPH_CHECK(igraph_2wheap_init(&cn, vc)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &cn); + for (i = 0; i < vc; ++i) { + if (i != vertex) { + igraph_2wheap_push_with_index(&cn, i, 0); /* should not fail since memory was already reserved */ + } + } + + /* Within this loop, a color of 0 means "uncolored", and valid color indices start at 1. + * At the beginning, all vertices are set as "uncolored", see the vector_int_fill() call above. + * Colors will be decremented to start at 0 later. */ + while (true) { + IGRAPH_CHECK(igraph_neighbors(graph, &neighbors, vertex, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + igraph_int_t nei_count = igraph_vector_int_size(&neighbors); + + /* Colour current vertex by finding the smallest available non-0 color. + * Note that self-loops are effectively skipped as they merely prevent + * the current vertex from being colored with the color value it presently + * has, which is 0 (meaning uncolored). */ + { + igraph_int_t col; + + IGRAPH_CHECK(igraph_vector_int_resize(&nei_colors, nei_count)); + for (i = 0; i < nei_count; ++i) { + VECTOR(nei_colors)[i] = VECTOR(*colors)[ VECTOR(neighbors)[i] ]; + } + igraph_vector_int_sort(&nei_colors); + + i = 0; + col = 0; + do { + while (i < nei_count && VECTOR(nei_colors)[i] == col) { + i++; + } + col++; + } while (i < nei_count && VECTOR(nei_colors)[i] == col); + + VECTOR(*colors)[vertex] = col; + } + + /* increment number of coloured neighbours for each neighbour of vertex */ + for (i = 0; i < nei_count; ++i) { + igraph_int_t idx = VECTOR(neighbors)[i]; + if (igraph_2wheap_has_elem(&cn, idx)) { + igraph_2wheap_modify(&cn, idx, igraph_2wheap_get(&cn, idx) + 1); + } + } + + /* stop if no more vertices left to colour */ + if (igraph_2wheap_empty(&cn)) { + break; + } + + igraph_2wheap_delete_max_index(&cn, &vertex); + + IGRAPH_ALLOW_INTERRUPTION(); + } + + /* subtract 1 from each colour value, so that colours start at 0 */ + igraph_vector_int_add_constant(colors, -1); + + /* free data structures */ + igraph_vector_int_destroy(&neighbors); + igraph_vector_int_destroy(&nei_colors); + igraph_2wheap_destroy(&cn); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* DSATUR: Choose vertices based on the number of adjacent colours, i.e. "saturation degree" */ + +typedef struct { + igraph_int_t saturation_degree; /* number of colors used by neighbors */ + igraph_int_t edge_degree; /* degree in the subgraph induced by uncolored vertices */ +} dsatur_t; + +static int dsatur_t_compare(const void *left, const void *right) { + const dsatur_t *left_d = left; + const dsatur_t *right_d = right; + if (left_d->saturation_degree == right_d->saturation_degree) { + if (left_d->edge_degree == right_d->edge_degree) { + return 0; + } else if (left_d->edge_degree > right_d->edge_degree) { + return 1; + } else { + return -1; + } + } + return left_d->saturation_degree > right_d->saturation_degree ? 1 : -1; +} + +static igraph_bool_t dsatur_is_color_used_by_neighbour( + const igraph_vector_int_t *colors, igraph_int_t color, + const igraph_vector_int_t *neighbors +) { + igraph_int_t nei_count = igraph_vector_int_size(neighbors); + + for (igraph_int_t i=0; i < nei_count; i++) { + igraph_int_t nei = VECTOR(*neighbors)[i]; + if (VECTOR(*colors)[nei] == color) { + return true; + } + } + + return false; +} + +static void dsatur_update_heap( + const igraph_adjlist_t *adjlist, igraph_gen2wheap_t *node_degrees_heap, + const igraph_vector_int_t *neighbors, const igraph_vector_int_t *colors, + igraph_int_t color +) { + igraph_gen2wheap_delete_max(node_degrees_heap); + igraph_int_t nei_count = igraph_vector_int_size(neighbors); + for (igraph_int_t i=0; i < nei_count; i++) { + igraph_int_t nei = VECTOR(*neighbors)[i]; + if (!igraph_gen2wheap_has_elem(node_degrees_heap, nei)) { + continue; + } + dsatur_t deg_data = *((dsatur_t*) igraph_gen2wheap_get(node_degrees_heap, nei)); + if (!dsatur_is_color_used_by_neighbour(colors, color, igraph_adjlist_get(adjlist, nei))) { + deg_data.saturation_degree++; + } + deg_data.edge_degree--; + igraph_gen2wheap_modify(node_degrees_heap, nei, °_data); + } +} + +static igraph_int_t dsatur_get_first_viable_color(const igraph_vector_int_t *used_colors_sorted) { + igraph_int_t color_count = igraph_vector_int_size(used_colors_sorted); + igraph_int_t i = 0; + igraph_int_t col = 0; + while (i < color_count && VECTOR(*used_colors_sorted)[i] == col) { + while (i < color_count && VECTOR(*used_colors_sorted)[i] == col) { + i++; + } + col++; + } + return col; +} + +static igraph_error_t igraph_i_vertex_coloring_dsatur( + const igraph_t *graph, igraph_vector_int_t *colors +) { + igraph_int_t vcount = igraph_vcount(graph); + IGRAPH_CHECK(igraph_vector_int_resize(colors, vcount)); + + if (vcount == 0) { + return IGRAPH_SUCCESS; + } + + if (vcount == 1) { + VECTOR(*colors)[0] = 0; + return IGRAPH_SUCCESS; + } + + igraph_vector_int_fill(colors, -1); /* -1 as a color means uncolored */ + + /* Multi-edges and self-loops are removed from the adjacency list in order to ensure the correct + * updating of a vertex's neighbors' saturation degrees when that vertex is colored. */ + igraph_adjlist_t adjlist; + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + igraph_gen2wheap_t node_degrees_heap; + IGRAPH_CHECK(igraph_gen2wheap_init(&node_degrees_heap, dsatur_t_compare, sizeof(dsatur_t), vcount)); + IGRAPH_FINALLY(igraph_gen2wheap_destroy, &node_degrees_heap); + + for (igraph_int_t vertex = 0; vertex < vcount; vertex++) { + dsatur_t dsatur; + dsatur.saturation_degree = 0; + dsatur.edge_degree = igraph_vector_int_size(igraph_adjlist_get(&adjlist, vertex)); + IGRAPH_CHECK(igraph_gen2wheap_push_with_index(&node_degrees_heap, vertex, &dsatur)); + } + + igraph_vector_int_t used_colors_sorted; + IGRAPH_VECTOR_INT_INIT_FINALLY(&used_colors_sorted, 0); + + while (! igraph_gen2wheap_empty(&node_degrees_heap)) { + igraph_int_t node_to_color = igraph_gen2wheap_max_index(&node_degrees_heap); + igraph_vector_int_t *neighbors = igraph_adjlist_get(&adjlist, node_to_color); + igraph_int_t nei_count = igraph_vector_int_size(neighbors); + igraph_vector_int_clear(&used_colors_sorted); + for (igraph_int_t i=0; i < nei_count; i++) { + igraph_int_t nei = VECTOR(*neighbors)[i]; + if (VECTOR(*colors)[nei] != -1) { + IGRAPH_CHECK(igraph_vector_int_push_back(&used_colors_sorted, VECTOR(*colors)[nei])); + } + } + igraph_vector_int_sort(&used_colors_sorted); + igraph_int_t color = dsatur_get_first_viable_color(&used_colors_sorted); + dsatur_update_heap(&adjlist, &node_degrees_heap, neighbors, colors, color); + VECTOR(*colors)[node_to_color] = color; + + IGRAPH_ALLOW_INTERRUPTION(); + } + + igraph_vector_int_destroy(&used_colors_sorted); + igraph_gen2wheap_destroy(&node_degrees_heap); + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vertex_coloring_greedy + * \brief Computes a vertex coloring using a greedy algorithm. + * + * This function assigns a "color"—represented as a non-negative integer—to + * each vertex of the graph in such a way that neighboring vertices never have + * the same color. The obtained coloring is not necessarily minimal. + * + * + * Vertices are colored greedily, one by one, always choosing the smallest color + * index that differs from that of already colored neighbors. Vertices are picked + * in an order determined by the speified heuristic. + * Colors are represented by non-negative integers 0, 1, 2, ... + * + * \param graph The input graph. + * \param colors Pointer to an initialized integer vector. The vertex colors will be stored here. + * \param heuristic The vertex ordering heuristic to use during greedy coloring. + * See \ref igraph_coloring_greedy_t for more information. + * + * \return Error code. + * + * \sa igraph_is_vertex_coloring() to check if a coloring is valid, i.e. if all + * edges connect vertices of different colors. + * + * \example examples/simple/coloring.c + */ +igraph_error_t igraph_vertex_coloring_greedy(const igraph_t *graph, igraph_vector_int_t *colors, igraph_coloring_greedy_t heuristic) { + switch (heuristic) { + case IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS: + return igraph_i_vertex_coloring_greedy_cn(graph, colors); + case IGRAPH_COLORING_GREEDY_DSATUR: + return igraph_i_vertex_coloring_dsatur(graph, colors); + default: + IGRAPH_ERROR("Invalid heuristic for greedy vertex coloring.", IGRAPH_EINVAL); + } +} + +/** + * \function igraph_is_vertex_coloring + * \brief Checks whether a vertex coloring is valid. + * + * This function checks whether the given vertex type/color assignment is a valid + * vertex coloring, i.e., no two adjacent vertices have the same color. + * Self-loops are ignored. + * + * \param graph The input graph. + * \param types The vertex types/colors as an integer vector. + * \param res Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: O(|E|), linear in the number of edges. + * + * \example examples/simple/coloring.c + */ +igraph_error_t igraph_is_vertex_coloring( + const igraph_t *graph, + const igraph_vector_int_t *types, + igraph_bool_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + int iter = 0; + + if (igraph_vector_int_size(types) != vcount) { + IGRAPH_ERROR("Invalid vertex type vector length.", IGRAPH_EINVAL); + } + + *res = true; + + for (igraph_int_t e = 0; e < ecount; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + + /* Skip self-loops */ + if (from == to) { + continue; + } + + if (VECTOR(*types)[from] == VECTOR(*types)[to]) { + *res = false; + break; + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_bipartite_coloring + * \brief Checks whether a bipartite vertex coloring is valid. + * + * This function checks whether the given vertex type assignment is a valid + * bipartite coloring, i.e., no two adjacent vertices have the same type. + * Additionally, for directed graphs, it determines the mode of edge directions. + * Self-loops are ignored. + * + * \param graph The input graph. + * \param types The vertex types as a boolean vector. + * \param res Pointer to a boolean, the result is stored here. + * \param mode Pointer to store the edge direction mode. Can be \c NULL if not needed. + * If all edges go from false to true vertices, \c IGRAPH_OUT is returned. + * If all edges go from true to false vertices, \c IGRAPH_IN is returned. + * If edges go in both directions or graph is undirected, \c IGRAPH_ALL is returned. + * \return Error code. + * + * Time complexity: O(|E|), linear in the number of edges. + * + * \sa igraph_is_bipartite() to determine whether a graph is bipartite, + * i.e. 2-colorable, and find such a coloring. + */ +igraph_error_t igraph_is_bipartite_coloring( + const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_bool_t *res, + igraph_neimode_t *mode) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + int iter = 0; + + if (igraph_vector_bool_size(types) != vcount) { + IGRAPH_ERROR("Invalid vertex type vector length.", IGRAPH_EINVAL); + } + + *res = true; + if (mode) { + *mode = IGRAPH_ALL; + } + + igraph_bool_t directed = igraph_is_directed(graph); + igraph_bool_t has_false_to_true = false; + igraph_bool_t has_true_to_false = false; + + for (igraph_int_t e = 0; e < ecount; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + + /* Skip self-loops */ + if (from == to) { + continue; + } + + igraph_bool_t from_type = VECTOR(*types)[from]; + igraph_bool_t to_type = VECTOR(*types)[to]; + + /* Check if adjacent vertices have the same type */ + if (from_type == to_type) { + *res = false; + break; + } + + /* Track edge directions for directed graphs */ + if (directed && mode) { + if (!from_type && to_type) { + has_false_to_true = true; + } else if (from_type && !to_type) { + has_true_to_false = true; + } + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + /* Determine the mode for directed graphs */ + if (*res && directed && mode) { + if (has_false_to_true && !has_true_to_false) { + *mode = IGRAPH_OUT; + } else if (!has_false_to_true && has_true_to_false) { + *mode = IGRAPH_IN; + } else { + *mode = IGRAPH_ALL; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_edge_coloring + * \brief Checks whether an edge coloring is valid. + * + * This function checks whether the given edge color assignment is a valid + * edge coloring, i.e., no two adjacent edges have the same color. + * + * Note that this function does not consider self-edges (loops) as being + * adjacent to themselves, so graphs with self-loops may still be considered + * to have a valid edge coloring. + * + * \param graph The input graph. + * \param types The edge colors as an integer vector. + * \param res Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: O(|V|*d*log(d)), where d is the maximum degree. + */ +igraph_error_t igraph_is_edge_coloring( + const igraph_t *graph, + const igraph_vector_int_t *types, + igraph_bool_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_int_t edges, edge_colors; + int iter = 0; + + if (igraph_vector_int_size(types) != ecount) { + IGRAPH_ERROR("Invalid edge type vector length.", IGRAPH_EINVAL); + } + + /* Be optimistic */ + *res = true; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edge_colors, 0); + + /* For each vertex, check that all incident edges have different colors */ + for (igraph_int_t v = 0; v < vcount; v++) { + IGRAPH_CHECK(igraph_incident(graph, &edges, v, IGRAPH_ALL, IGRAPH_LOOPS_ONCE)); + + /* Get sorted edge color list */ + IGRAPH_CHECK(igraph_vector_int_index(types, &edge_colors, &edges)); + igraph_vector_int_sort(&edge_colors); + + /* Look for consecutive duplicates in edge color list */ + igraph_int_t edge_color_count = igraph_vector_int_size(&edge_colors); + for (igraph_int_t i = 0; i < edge_color_count - 1; i++) { + if (VECTOR(edge_colors)[i] == VECTOR(edge_colors)[i + 1]) { + *res = false; + goto done; + } + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 7); + } + +done: + igraph_vector_int_destroy(&edge_colors); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/conversion.c b/src/misc/conversion.c new file mode 100644 index 0000000..02477dc --- /dev/null +++ b/src/misc/conversion.c @@ -0,0 +1,980 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_conversion.h" + +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_attributes.h" +#include "igraph_constructors.h" +#include "igraph_structural.h" +#include "igraph_sparsemat.h" +#include "igraph_random.h" + +#include "core/fixed_vectorlist.h" +#include "graph/attributes.h" +#include "math/safe_intop.h" + +#define WEIGHT_OF(eid) (weights ? VECTOR(*weights)[eid] : 1) + +/** + * \ingroup conversion + * \function igraph_get_adjacency + * \brief The adjacency matrix of a graph. + * + * + * The result is an adjacency matrix. Entry i, j of the matrix + * contains the number of edges connecting vertex i to vertex j in the unweighted + * case, or the total weight of edges connecting vertex i to vertex j in the + * weighted case. + * + * \param graph Pointer to the graph to convert + * \param res Pointer to an initialized matrix object, it will be + * resized if needed. + * \param type Constant specifying the type of the adjacency matrix to + * create for undirected graphs. It is ignored for directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_GET_ADJACENCY_UPPER + * the upper right triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_LOWER + * the lower left triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_BOTH + * the whole matrix is used, a symmetric matrix is returned + * if the graph is undirected. + * \endclist + * \param weights An optional vector containing the weight of each edge + * in the graph. Supply a null pointer here to make all edges have + * the same weight of 1. + * \param loops Constant specifying how loop edges should be handled. + * Possible values: + * \clist + * \cli IGRAPH_NO_LOOPS + * loop edges are ignored and the diagonal of the matrix will contain + * zeros only + * \cli IGRAPH_LOOPS_ONCE + * loop edges are counted once, i.e. a vertex with a single unweighted + * loop edge will have 1 in the corresponding diagonal entry + * \cli IGRAPH_LOOPS_TWICE + * loop edges are counted twice in \em undirected graphs, i.e. a vertex + * with a single unweighted loop edge in an undirected graph will have + * 2 in the corresponding diagonal entry. Loop edges in directed graphs + * are still counted as 1. Essentially, this means that the function is + * counting the incident edge \em stems , which makes more sense when + * using the adjacency matrix in linear algebra. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL invalid type argument. + * + * \sa \ref igraph_get_adjacency_sparse() if you want a sparse matrix representation + * + * Time complexity: O(|V||V|), |V| is the number of vertices in the graph. + */ + +igraph_error_t igraph_get_adjacency( + const igraph_t *graph, igraph_matrix_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t i, from, to; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + + if (directed) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (from != to || loops != IGRAPH_NO_LOOPS) { + MATRIX(*res, from, to) += WEIGHT_OF(i); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_UPPER) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (to < from) { + MATRIX(*res, to, from) += WEIGHT_OF(i); + } else { + MATRIX(*res, from, to) += WEIGHT_OF(i); + } + if (to == from && loops == IGRAPH_LOOPS_TWICE) { + MATRIX(*res, to, to) += WEIGHT_OF(i); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_LOWER) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (to < from) { + MATRIX(*res, from, to) += WEIGHT_OF(i); + } else { + MATRIX(*res, to, from) += WEIGHT_OF(i); + } + if (to == from && loops == IGRAPH_LOOPS_TWICE) { + MATRIX(*res, to, to) += WEIGHT_OF(i); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_BOTH) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + MATRIX(*res, from, to) += WEIGHT_OF(i); + if (from != to || loops == IGRAPH_LOOPS_TWICE) { + MATRIX(*res, to, from) += WEIGHT_OF(i); + } + } + } else { + IGRAPH_ERROR("Invalid adjacency matrix type requested.", IGRAPH_EINVAL); + } + + /* Erase the diagonal if we don't need loop edges */ + if (loops == IGRAPH_NO_LOOPS) { + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, i) = 0; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_adjacency_sparse + * \brief Returns the adjacency matrix of a graph in a sparse matrix format. + * + * \param graph The input graph. + * \param res Pointer to an \em initialized sparse matrix. The result + * will be stored here. The matrix will be resized as needed. + * \param type Constant specifying the type of the adjacency matrix to + * create for undirected graphs. It is ignored for directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_GET_ADJACENCY_UPPER + * the upper right triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_LOWER + * the lower left triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_BOTH + * the whole matrix is used, a symmetric matrix is returned + * if the graph is undirected. + * \endclist + * \param weights An optional vector containing the weight of each edge + * in the graph. Supply a null pointer here to make all edges have + * the same weight of 1. + * \param loops Constant specifying how loop edges should be handled. + * Possible values: + * \clist + * \cli IGRAPH_NO_LOOPS + * loop edges are ignored and the diagonal of the matrix will contain + * zeros only + * \cli IGRAPH_LOOPS_ONCE + * loop edges are counted once, i.e. a vertex with a single unweighted + * loop edge will have 1 in the corresponding diagonal entry + * \cli IGRAPH_LOOPS_TWICE + * loop edges are counted twice in \em undirected graphs, i.e. a vertex + * with a single unweighted loop edge in an undirected graph will have + * 2 in the corresponding diagonal entry. Loop edges in directed graphs + * are still counted as 1. Essentially, this means that the function is + * counting the incident edge \em stems , which makes more sense when + * using the adjacency matrix in linear algebra. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL invalid type argument. + * + * \sa \ref igraph_get_adjacency(), the dense version of this function. + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_get_adjacency_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_get_adjacency_t type, + const igraph_vector_t *weights, igraph_loops_t loops +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t nzmax = directed ? no_of_edges : no_of_edges * 2; + igraph_int_t i, from, to; + + IGRAPH_CHECK(igraph_sparsemat_resize(res, no_of_nodes, no_of_nodes, nzmax)); + + if (directed) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (from != to || loops != IGRAPH_NO_LOOPS) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, from, to, WEIGHT_OF(i))); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_UPPER) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (to < from) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, from, WEIGHT_OF(i))); + } else if (to == from) { + switch (loops) { + case IGRAPH_LOOPS_ONCE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, WEIGHT_OF(i))); + break; + case IGRAPH_LOOPS_TWICE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, 2 * WEIGHT_OF(i))); + break; + case IGRAPH_NO_LOOPS: + default: + break; + } + } else { + IGRAPH_CHECK(igraph_sparsemat_entry(res, from, to, WEIGHT_OF(i))); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_LOWER) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (to < from) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, from, to, WEIGHT_OF(i))); + } else if (to == from) { + switch (loops) { + case IGRAPH_LOOPS_ONCE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, WEIGHT_OF(i))); + break; + case IGRAPH_LOOPS_TWICE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, 2 * WEIGHT_OF(i))); + break; + case IGRAPH_NO_LOOPS: + default: + break; + } + } else { + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, from, WEIGHT_OF(i))); + } + } + } else if (type == IGRAPH_GET_ADJACENCY_BOTH) { + for (i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + if (to == from) { + switch (loops) { + case IGRAPH_LOOPS_ONCE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, WEIGHT_OF(i))); + break; + case IGRAPH_LOOPS_TWICE: + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, to, 2 * WEIGHT_OF(i))); + break; + case IGRAPH_NO_LOOPS: + default: + break; + } + } else { + IGRAPH_CHECK(igraph_sparsemat_entry(res, from, to, WEIGHT_OF(i))); + IGRAPH_CHECK(igraph_sparsemat_entry(res, to, from, WEIGHT_OF(i))); + } + } + } else { + IGRAPH_ERROR("Invalid adjacency matrix type requested.", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +#undef WEIGHT_OF + + +/** + * \ingroup conversion + * \function igraph_get_edgelist + * \brief The list of edges in a graph. + * + * The order of the edges is given by the edge IDs. + * + * \param graph Pointer to the graph object + * \param res Pointer to an initialized vector object, it will be + * resized. + * \param bycol Boolean constant. If true, the edges will be returned + * columnwise, e.g. the first edge is + * res[0]->res[|E|], the second is + * res[1]->res[|E|+1], etc. Supply false to get + * the edge list in a format compatible with \ref igraph_add_edges(). + * \return Error code. + * + * \sa \ref igraph_edges() to return the result only for some edge IDs. + * + * Time complexity: O(|E|), the number of edges in the graph. + */ + +igraph_error_t igraph_get_edgelist(const igraph_t *graph, igraph_vector_int_t *res, igraph_bool_t bycol) { + return igraph_edges(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), res, bycol); +} + +/** + * \function igraph_to_directed + * \brief Convert an undirected graph to a directed one. + * + * If the supplied graph is directed, this function does nothing. + * + * \param graph The graph object to convert. + * \param mode Constant, specifies the details of how exactly the + * conversion is done. Possible values: + * \clist + * \cli IGRAPH_TO_DIRECTED_ARBITRARY + * The number of edges in the + * graph stays the same, an arbitrarily directed edge is + * created for each undirected edge. + * \cli IGRAPH_TO_DIRECTED_MUTUAL + * Two directed edges are + * created for each undirected edge, one in each direction. + * \cli IGRAPH_TO_DIRECTED_RANDOM + * Each undirected edge is converted to a randomly oriented + * directed one. + * \cli IGRAPH_TO_DIRECTED_ACYCLIC + * Each undirected edge is converted to a directed edge oriented + * from a lower index vertex to a higher index one. If no self-loops + * were present, then the result is a directed acyclic graph. + * \endclist + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ + +igraph_error_t igraph_to_directed(igraph_t *graph, + igraph_to_directed_t mode) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (igraph_is_directed(graph)) { + return IGRAPH_SUCCESS; + } + + switch (mode) { + case IGRAPH_TO_DIRECTED_ARBITRARY: + case IGRAPH_TO_DIRECTED_RANDOM: + case IGRAPH_TO_DIRECTED_ACYCLIC: + { + igraph_t newgraph; + igraph_vector_int_t edges; + igraph_int_t size = no_of_edges * 2; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, size); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + + if (mode == IGRAPH_TO_DIRECTED_RANDOM) { + for (igraph_int_t i=0; i < no_of_edges; ++i) { + if (RNG_INTEGER(0,1)) { + igraph_int_t temp = VECTOR(edges)[2*i]; + VECTOR(edges)[2*i] = VECTOR(edges)[2*i+1]; + VECTOR(edges)[2*i+1] = temp; + } + } + } else if (mode == IGRAPH_TO_DIRECTED_ACYCLIC) { + /* Currently, the endpoints of undirected edges are ordered in the + internal graph datastructure, i.e. it is always true that from < to. + However, it is not guaranteed that this will not be changed in + the future, and this ordering should not be relied on outside of + the implementation of the minimal API in type_indexededgelist.c. + + Therefore, we order the edge endpoints anyway in the following loop: */ + for (igraph_int_t i=0; i < no_of_edges; ++i) { + if (VECTOR(edges)[2*i] > VECTOR(edges)[2*i+1]) { + igraph_int_t temp = VECTOR(edges)[2*i]; + VECTOR(edges)[2*i] = VECTOR(edges)[2*i+1]; + VECTOR(edges)[2*i+1] = temp; + } + } + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + no_of_nodes, + IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, true)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + igraph_destroy(graph); + *graph = newgraph; + + break; + } + case IGRAPH_TO_DIRECTED_MUTUAL: + { + igraph_t newgraph; + igraph_vector_int_t edges; + igraph_vector_int_t index; + igraph_int_t size; + + IGRAPH_SAFE_MULT(no_of_edges, 4, &size); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, size)); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_int_resize(&edges, size)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&index, no_of_edges * 2); + for (igraph_int_t i = 0; i < no_of_edges; i++) { + VECTOR(edges)[no_of_edges * 2 + i * 2] = VECTOR(edges)[i * 2 + 1]; + VECTOR(edges)[no_of_edges * 2 + i * 2 + 1] = VECTOR(edges)[i * 2]; + VECTOR(index)[i] = VECTOR(index)[no_of_edges + i] = i; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + no_of_nodes, + IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, /* edges= */ false)); + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, &newgraph, &index)); + + igraph_vector_int_destroy(&index); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); + + igraph_destroy(graph); + *graph = newgraph; + + break; + } + default: + IGRAPH_ERROR("Cannot direct graph, invalid mode.", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_to_undirected + * \brief Convert a directed graph to an undirected one. + * + * + * If the supplied graph is undirected, this function does nothing. + * + * \param graph The graph object to convert. + * \param mode Constant, specifies the details of how exactly the + * conversion is done. Possible values: \c + * IGRAPH_TO_UNDIRECTED_EACH: the number of edges remains + * constant, an undirected edge is created for each directed + * one, this version might create graphs with multiple edges; + * \c IGRAPH_TO_UNDIRECTED_COLLAPSE: one undirected edge will + * be created for each pair of vertices that are connected + * with at least one directed edge, no multiple edges will be + * created. \c IGRAPH_TO_UNDIRECTED_MUTUAL creates an undirected + * edge for each pair of mutual edges in the directed graph. + * Non-mutual edges are lost; loop edges are kept unconditionally. + * This mode might create multiple edges. + * \param edge_comb What to do with the edge attributes. See the igraph + * manual section about attributes for details. \c NULL means that + * the edge attributes are lost during the conversion, \em except + * when \c mode is \c IGRAPH_TO_UNDIRECTED_EACH, in which case the + * edge attributes are kept intact. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_to_undirected.c + */ + +igraph_error_t igraph_to_undirected(igraph_t *graph, + igraph_to_undirected_t mode, + const igraph_attribute_combination_t *edge_comb) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t edges; + igraph_t newgraph; + igraph_bool_t attr = edge_comb && igraph_has_attribute_table(); + + if (mode != IGRAPH_TO_UNDIRECTED_EACH && + mode != IGRAPH_TO_UNDIRECTED_COLLAPSE && + mode != IGRAPH_TO_UNDIRECTED_MUTUAL) { + IGRAPH_ERROR("Cannot undirect graph, invalid mode.", IGRAPH_EINVAL); + } + + if (!igraph_is_directed(graph)) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + if (mode == IGRAPH_TO_UNDIRECTED_EACH) { + igraph_es_t es; + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, IGRAPH_FROM(graph, edge))); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, IGRAPH_TO(graph, edge))); + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + no_of_nodes, + IGRAPH_UNDIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + igraph_vector_int_destroy(&edges); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, true)); + IGRAPH_FINALLY_CLEAN(2); + igraph_destroy(graph); + *graph = newgraph; + + } else if (mode == IGRAPH_TO_UNDIRECTED_COLLAPSE) { + igraph_vector_int_t inadj, outadj; + igraph_vector_int_t mergeinto; + igraph_int_t actedge = 0; + + if (attr) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mergeinto, no_of_edges); + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&inadj, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outadj, 0); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t n_out, n_in; + igraph_int_t p1 = -1, p2 = -1; + igraph_int_t e1 = 0, e2 = 0, n1 = 0, n2 = 0, last; + IGRAPH_CHECK(igraph_incident(graph, &outadj, i, IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_incident(graph, &inadj, i, IGRAPH_IN, IGRAPH_LOOPS)); + n_out = igraph_vector_int_size(&outadj); + n_in = igraph_vector_int_size(&inadj); + +#define STEPOUT() if ( (++p1) < n_out) { \ + e1 = VECTOR(outadj)[p1]; \ + n1 = IGRAPH_TO(graph, e1); \ + } +#define STEPIN() if ( (++p2) < n_in) { \ + e2 = VECTOR(inadj )[p2]; \ + n2 = IGRAPH_FROM(graph, e2); \ + } +#define ADD_NEW_EDGE() { \ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); \ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, last)); \ +} +#define MERGE_INTO_CURRENT_EDGE(which) { \ + if (attr) { \ + VECTOR(mergeinto)[which] = actedge; \ + } \ +} + + STEPOUT(); + STEPIN(); + + while (p1 < n_out && n1 <= i && p2 < n_in && n2 <= i) { + last = (n1 <= n2) ? n1 : n2; + ADD_NEW_EDGE(); + while (p1 < n_out && last == n1) { + MERGE_INTO_CURRENT_EDGE(e1); + STEPOUT(); + } + while (p2 < n_in && last == n2) { + MERGE_INTO_CURRENT_EDGE(e2); + STEPIN(); + } + actedge++; + } + + while (p1 < n_out && n1 <= i) { + last = n1; + ADD_NEW_EDGE(); + while (p1 < n_out && last == n1) { + MERGE_INTO_CURRENT_EDGE(e1); + STEPOUT(); + } + actedge++; + } + + while (p2 < n_in && n2 <= i) { + last = n2; + ADD_NEW_EDGE(); + while (p2 < n_in && last == n2) { + MERGE_INTO_CURRENT_EDGE(e2); + STEPIN(); + } + actedge++; + } + } + +#undef MERGE_INTO_CURRENT_EDGE +#undef ADD_NEW_EDGE +#undef STEPOUT +#undef STEPIN + + igraph_vector_int_destroy(&outadj); + igraph_vector_int_destroy(&inadj); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + no_of_nodes, + IGRAPH_UNDIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + igraph_vector_int_destroy(&edges); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, /* edges= */ false)); + + if (attr) { + igraph_fixed_vectorlist_t vl; + IGRAPH_CHECK(igraph_fixed_vectorlist_convert(&vl, &mergeinto, actedge)); + IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &vl); + + IGRAPH_CHECK(igraph_i_attribute_combine_edges(graph, &newgraph, &vl.vecs, edge_comb)); + + igraph_fixed_vectorlist_destroy(&vl); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_FINALLY_CLEAN(2); + igraph_destroy(graph); + *graph = newgraph; + + if (attr) { + igraph_vector_int_destroy(&mergeinto); + IGRAPH_FINALLY_CLEAN(1); + } + } else if (mode == IGRAPH_TO_UNDIRECTED_MUTUAL) { + igraph_vector_int_t inadj, outadj; + igraph_vector_int_t mergeinto; + igraph_int_t actedge = 0; + + if (attr) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mergeinto, no_of_edges); + igraph_vector_int_fill(&mergeinto, -1); + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&inadj, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outadj, 0); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t n_out, n_in; + igraph_int_t p1 = -1, p2 = -1; + igraph_int_t e1 = 0, e2 = 0, n1 = 0, n2 = 0; + IGRAPH_CHECK(igraph_incident(graph, &outadj, i, IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_incident(graph, &inadj, i, IGRAPH_IN, IGRAPH_LOOPS)); + n_out = igraph_vector_int_size(&outadj); + n_in = igraph_vector_int_size(&inadj); + +#define STEPOUT() if ( (++p1) < n_out) { \ + e1 = VECTOR(outadj)[p1]; \ + n1 = IGRAPH_TO(graph, e1); \ + } +#define STEPIN() if ( (++p2) < n_in) { \ + e2 = VECTOR(inadj )[p2]; \ + n2 = IGRAPH_FROM(graph, e2); \ + } + + STEPOUT(); + STEPIN(); + + while (p1 < n_out && n1 <= i && p2 < n_in && n2 <= i) { + if (n1 == n2) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, n1)); + if (attr) { + VECTOR(mergeinto)[e1] = actedge; + VECTOR(mergeinto)[e2] = actedge; + actedge++; + } + STEPOUT(); + STEPIN(); + } else if (n1 < n2) { + STEPOUT(); + } else { /* n2 + * When the out-degree (or in-degree) of a vertex is zero, the corresponding + * row (or column) of the row-wise (or column-wise) normalized stochastic + * matrix will be zero. + * + * \param graph The input graph. + * \param res Pointer to an initialized matrix, the result is stored here. + * It will be resized as needed. + * \param column_wise If \c false, row-wise normalization is used. + * If \c true, column-wise normalization is used. + * \param weights An optional vector containing the weight of each edge + * in the graph. Supply a null pointer here to make all edges have + * the same weight of 1. + * \return Error code. + * + * Time complexity: O(|V|^2), |V| is the number of vertices in the graph. + * + * \sa \ref igraph_get_stochastic_sparse(), the sparse version of this + * function. + */ + +igraph_error_t igraph_get_stochastic( + const igraph_t *graph, igraph_matrix_t *res, igraph_bool_t column_wise, + const igraph_vector_t *weights +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t from, to; + igraph_vector_t sums; + igraph_real_t sum; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + + IGRAPH_VECTOR_INIT_FINALLY(&sums, no_of_nodes); + + if (directed) { + /* Directed */ + + IGRAPH_CHECK(igraph_strength( + graph, &sums, igraph_vss_all(), + column_wise ? IGRAPH_IN : IGRAPH_OUT, + IGRAPH_LOOPS, weights + )); + + for (igraph_int_t i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + sum = VECTOR(sums)[column_wise ? to : from]; + MATRIX(*res, from, to) += WEIGHT_OF(i) / sum; + } + } else { + /* Undirected */ + + IGRAPH_CHECK(igraph_strength( + graph, &sums, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights + )); + + for (igraph_int_t i = 0; i < no_of_edges; i++) { + from = IGRAPH_FROM(graph, i); + to = IGRAPH_TO(graph, i); + MATRIX(*res, from, to) += WEIGHT_OF(i) / VECTOR(sums)[column_wise ? to : from]; + MATRIX(*res, to, from) += WEIGHT_OF(i) / VECTOR(sums)[column_wise ? from: to]; + } + } + + igraph_vector_destroy(&sums); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#undef WEIGHT_OF + +/** + * \function igraph_get_stochastic_sparse + * \brief The stochastic adjacency matrix of a graph. + * + * Stochastic matrix of a graph in sparse format. See \ref igraph_get_stochastic() + * for the information on stochastic matrices. + * + * \param graph The input graph. + * \param res Pointer to an \em initialized sparse matrix, the + * result is stored here. The matrix will be resized as needed. + * \param column_wise If \c false, row-wise normalization is used. + * If \c true, column-wise normalization is used. + * \param weights An optional vector containing the weight of each edge + * in the graph. Supply a null pointer here to make all edges have + * the same weight of 1. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa \ref igraph_get_stochastic(), the dense version of this function. + */ + +igraph_error_t igraph_get_stochastic_sparse( + const igraph_t *graph, igraph_sparsemat_t *res, igraph_bool_t column_wise, + const igraph_vector_t *weights +) { + IGRAPH_CHECK(igraph_get_adjacency_sparse(graph, res, IGRAPH_GET_ADJACENCY_BOTH, weights, IGRAPH_LOOPS_TWICE)); + + if (column_wise) { + IGRAPH_CHECK(igraph_sparsemat_normalize_cols(res, /* allow_zeros = */ true)); + } else { + IGRAPH_CHECK(igraph_sparsemat_normalize_rows(res, /* allow_zeros = */ true)); + } + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup conversion + * \function igraph_to_prufer + * \brief Converts a tree to its Prüfer sequence. + * + * A Prüfer sequence is a unique sequence of integers associated + * with a labelled tree. A tree on n >= 2 vertices can be represented by a + * sequence of n-2 integers, each between 0 and n-1 (inclusive). + * + * \param graph Pointer to an initialized graph object which + must be a tree on n >= 2 vertices. + * \param prufer A pointer to the integer vector that should hold the Prüfer sequence; + the vector must be initialized and will be resized to n - 2. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * there is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * the graph is not a tree or it is has less than vertices + * \endclist + * + * \sa \ref igraph_from_prufer() + * + */ +igraph_error_t igraph_to_prufer(const igraph_t *graph, igraph_vector_int_t* prufer) { + /* For generating the Prüfer sequence, we enumerate the vertices u of the tree. + We keep track of the degrees of all vertices, treating vertices + of degree 0 as removed. We maintain the invariant that all leafs + that are still contained in the tree are >= u. + If u is a leaf, we remove it and add its unique neighbor to the Prüfer + sequence. If the removal of u turns the neighbor into a leaf which is < u, + we repeat the procedure for the new leaf and so on. */ + igraph_int_t u; + igraph_vector_int_t degrees; + igraph_vector_int_t neighbors; + igraph_int_t prufer_index = 0; + igraph_int_t n = igraph_vcount(graph); + igraph_bool_t is_tree = false; + + IGRAPH_CHECK(igraph_is_tree(graph, &is_tree, NULL, IGRAPH_ALL)); + + if (!is_tree) { + IGRAPH_ERROR("The graph must be a tree.", IGRAPH_EINVAL); + } + + if (n < 2) { + IGRAPH_ERROR("The tree must have at least 2 vertices.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_resize(prufer, n - 2)); + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, n); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbors, 1); + + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS)); + + for (u = 0; u < n; ++u) { + igraph_int_t degree = VECTOR(degrees)[u]; + igraph_int_t leaf = u; + + while (degree == 1 && leaf <= u) { + igraph_int_t neighbor = 0; + igraph_int_t neighbor_count = 0; + + VECTOR(degrees)[leaf] = 0; /* mark leaf v as deleted */ + + IGRAPH_CHECK(igraph_neighbors( + graph, &neighbors, leaf, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + + /* Find the unique remaining neighbor of the leaf */ + neighbor_count = igraph_vector_int_size(&neighbors); + for (igraph_int_t i = 0; i < neighbor_count; i++) { + neighbor = VECTOR(neighbors)[i]; + if (VECTOR(degrees)[neighbor] > 0) { + break; + } + } + + /* remember that we have removed the leaf */ + VECTOR(degrees)[neighbor]--; + degree = VECTOR(degrees)[neighbor]; + + /* Add the neighbor to the prufer sequence unless it is the last vertex + (i.e. degree == 0) */ + if (degree > 0) { + VECTOR(*prufer)[prufer_index] = neighbor; + prufer_index++; + } + leaf = neighbor; + } + } + + igraph_vector_int_destroy(°rees); + igraph_vector_int_destroy(&neighbors); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/degree_sequence.cpp b/src/misc/degree_sequence.cpp new file mode 100644 index 0000000..5907383 --- /dev/null +++ b/src/misc/degree_sequence.cpp @@ -0,0 +1,1373 @@ +/* + igraph library. + Constructing realizations of degree sequences and bi-degree sequences. + Copyright (C) 2018-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_constructors.h" + +#include "core/exceptions.h" +#include "math/safe_intop.h" +#include "misc/graphicality.h" + +#include +#include +#include +#include +#include +#include + +/******************************/ +/***** Helper constructs ******/ +/******************************/ + +// (vertex, degree) pair +struct vd_pair { + igraph_int_t vertex; + igraph_int_t degree; + + vd_pair() = default; + vd_pair(igraph_int_t vertex, igraph_int_t degree) : vertex(vertex), degree(degree) {} +}; + +// (indegree, outdegree) +typedef std::pair bidegree; + +// (vertex, bidegree) pair +struct vbd_pair { + igraph_int_t vertex; + bidegree degree; + + vbd_pair(igraph_int_t vertex, bidegree degree) : vertex(vertex), degree(degree) {} +}; + +// Comparison function for vertex-degree pairs. +// Also used for lexicographic sorting of bi-degrees. +template inline bool degree_greater(const T &a, const T &b) { + return a.degree > b.degree; +} + +template inline bool degree_less(const T &a, const T &b) { + return a.degree < b.degree; +} + + +/*************************************/ +/***** Undirected simple graphs ******/ +/*************************************/ + +// "Bucket Node" for nodes of the same degree +struct BNode { + igraph_int_t count = 0; + std::stack nodes; + igraph_int_t next; // next bucket (higher degree) + igraph_int_t prev; // prev bucket (lower degree) + + bool is_empty() const { return count == 0; } +}; + +struct HavelHakimiList { + igraph_int_t n_buckets; // no of buckets, INCLUDING sentinels + std::vector buckets; + + // Given degree sequence, sets up linked list of BNodes (degree buckets) + // sentinel BNode [0] and [N] as bookends + // O(N) + explicit HavelHakimiList(const igraph_vector_int_t *degseq) : + n_buckets(igraph_vector_int_size(degseq)+1), buckets(n_buckets) + { + igraph_int_t n_nodes = igraph_vector_int_size(degseq); + for (igraph_int_t i = 0; i <= n_nodes; i++) { + if (i == 0) { + buckets[i].prev = -1; + } else { + buckets[i].prev = i - 1; + } + + if (i == n_nodes) { + buckets[i].next = -1; + } else { + buckets[i].next = i + 1; + } + } + + for (igraph_int_t i = 0; i < n_nodes; i++) { + igraph_int_t degree = VECTOR(*degseq)[i]; + buckets[degree].nodes.push(vd_pair{i, degree}); + buckets[degree].count++; + } + } + + // ----- O(1) convenience functions ----- // + const BNode & head() const { return buckets.front(); } + const BNode & tail() const { return buckets.back(); } + + // gets the largest non-empty bucket below 'degree', + // or 0 if one does not exist + igraph_int_t get_prev(igraph_int_t degree) { + assert(0 < degree && degree <= n_buckets - 1); // upper sentinel allowed as input + igraph_int_t curr = buckets[degree].prev; + while (curr > 0 && buckets[curr].is_empty()) { + remove_bucket(curr); + curr = buckets[degree].prev; + } + return curr; + } + + // returns max degree non-empty bucket, + // or 0 (sentinel) if all buckets are empty + igraph_int_t get_max_bucket() { + // TODO: either change get_prev to take a BNode, or change + // head()/tail() to return integers + return get_prev(n_buckets - 1); + } + + // returns min degree non-empty bucket, + // or n_buckets - 1 (sentinel) if all buckets are empty + igraph_int_t get_min_bucket() { + igraph_int_t curr = head().next; + while (curr < n_buckets - 1 && buckets[curr].is_empty()) { + remove_bucket(curr); + curr = head().next; + } + return curr; + } + + void remove_bucket(igraph_int_t degree) { + // bounds check and prevent accidental removal of sentinels + assert(0 < degree && degree < n_buckets - 1); + + igraph_int_t &prev_idx = buckets[degree].prev; + igraph_int_t &next_idx = buckets[degree].next; + if (prev_idx != -1) buckets[prev_idx].next = next_idx; + if (next_idx != -1) buckets[next_idx].prev = prev_idx; + + prev_idx = -1; + next_idx = -1; + } + + void insert_bucket(igraph_int_t degree) { + assert(0 <= degree && degree < n_buckets - 1); // can insert into zero-degree bucket + + igraph_int_t &prev_idx = buckets[degree].prev; + igraph_int_t &next_idx = buckets[degree].next; + + if (prev_idx == -1 && next_idx == -1) { + next_idx = degree + 1; + prev_idx = buckets[next_idx].prev; + + buckets[next_idx].prev = degree; + buckets[prev_idx].next = degree; + } + } + + void insert_node(vd_pair node) { + insert_bucket(node.degree); // does nothing if already exists + buckets[node.degree].nodes.push(vd_pair{node.vertex, node.degree}); + buckets[node.degree].count++; + } + + bool get_max_node(vd_pair &max_node) { + igraph_int_t max_bucket = get_max_bucket(); + if (max_bucket <= 0) { + return false; + } + max_node = buckets[max_bucket].nodes.top(); + return true; + } + + void remove_max_node() { + igraph_int_t max_bucket = get_max_bucket(); + if (max_bucket <= 0) return; + buckets[max_bucket].nodes.pop(); + buckets[max_bucket].count--; + } + + bool get_min_node(vd_pair &min_node) { + igraph_int_t min_bucket = get_min_bucket(); + if (min_bucket >= n_buckets - 1) { + return false; + } + min_node = buckets[min_bucket].nodes.top(); + return true; + } + + void remove_min_node() { + igraph_int_t min_bucket = get_min_bucket(); + if (min_bucket >= n_buckets - 1) return; + buckets[min_bucket].nodes.pop(); + buckets[min_bucket].count--; + } + + // Given degree of selected "hub" node, returns degree many "spoke" nodes to connect to + // amortized O(alpha(n)) + igraph_error_t get_spokes(igraph_int_t degree, const igraph_vector_int_t &seq, + igraph_vector_int_t &spokes) { + std::stack buckets_req; // stack of needed degree buckets + igraph_int_t num_nodes = 0; + igraph_int_t curr = get_max_bucket(); // starts with max_bucket + + igraph_vector_int_clear(&spokes); + IGRAPH_CHECK(igraph_vector_int_reserve(&spokes, degree)); + + while (num_nodes < degree && curr > 0) { + num_nodes += buckets[curr].count; + buckets_req.push(curr); + curr = get_prev(curr); // gets next smallest NON-EMPTY bucket + } + if (num_nodes < degree) { // not enough spokes for hub degree + IGRAPH_ERROR("The given degree sequence cannot be realized as a simple graph.", IGRAPH_EINVAL); + } + + igraph_int_t num_skip = num_nodes - degree; + while (!buckets_req.empty()) { // starting from the smallest degree + igraph_int_t bucket = buckets_req.top(); + buckets_req.pop(); + + igraph_int_t to_get = buckets[bucket].count - num_skip; + while (to_get > 0) { + vd_pair node = buckets[bucket].nodes.top(); + if (VECTOR(seq)[node.vertex] != 0) { // if "not marked for removal" + IGRAPH_CHECK(igraph_vector_int_push_back(&spokes, node.vertex)); // add as spoke + + node.degree--; + insert_node(node); // first, insert into bucket below + + buckets[bucket].count--; + to_get--; + } + buckets[bucket].nodes.pop(); // then pop from original bucket + } + num_skip = 0; + } + return IGRAPH_SUCCESS; + } +}; + +/* + * This implementation works by grouping nodes by their remaining "stubs" (degrees) into an + * array of "degree buckets" (see struct HavelHakimiList above) - i.e. each bucket holds + * all nodes with that degree. The array runs from index 0 to index N, with 0 and N as + * sentinel buckets (since any graphical sequence for a simple graph will not have degrees + * greater than N - 1, and nodes with degree 0 can be ignored). Thus, only O(V) time is + * needed to allocate each node to its starting degree bucket, after which no re-sorting is + * done - if a node is used up as a "hub", it is simply removed (or lazy deleted if not + * immediately accessible), and if it is chosen as a "spoke", it will only shift down one + * bucket (constant operation). + * + * Additionally, each degree bucket keeps track of its next largest and next smallest degree + * bucket via their index in the array. This makes it very time efficient to find the + * largest and/or smallest nodes as needed for both "hub" and "spoke" nodes (specifically, + * amortized near-constant time). + * + * Below is an example run-through using the degree sequence [3, 2, 3, 1, 1] and the + * IGRAPH_REALIZE_DEGSEQ_SMALLEST method: + * + * Initial list + * [0][1][2][3][4][5] <- degree buckets + * 4 1 2 <- node ID (index in the degseq) in each bucket + * 3 0 + * + * By the smallest first method, we must first choose a node with the smallest degree to + * serve as the "hub". get_min_node() retrieves a node from bucket [1], which sentinel [0] + * indicates as its next largest non-empty bucket in its .next field. + * [0][1][2][3][4][5] 4 <- retrieved "hub" node. It is removed from the bucket + * 3 1 2 + * 0 + * + * Node 4 has degree 1, therefore we need to select 1 "spoke" node to connect it to. Using + * get_max_node(), we go to retrieve a node from bucket [4], which sentinel [5] indicates + * as its next smallest non-empty bucket. Finding [4] empty, the bucket is removed, and + * node 2 is finally retrieved from bucket [3]. + * [0][1][2][3][5] 4-2 <- an edge is formed between them + * 3 1 0 + * + * After one "stub"/degree of node 2 is used up, it gets shifted down one bucket. + * [0][1][2][3][5] 4-2 + * 3 2 0 + * 1 + * + * The process repeats. We look at the smallest degree bucket for a "hub" and remove it. + * [0][1][2][3][5] 4-2 + * 2 0 3 + * 1 + * + * Node 3 has degree 1, so we pick 1 "spoke", and replace it into the bucket below. + * [0][1][2][3][5] 4-2 + * 0 3-0 + * 2 + * 1 + * + * The process continues until we are unable to find either a non-zero "hub" node + * (algorithm complete and sequence is graphical) or enough non-zero "spoke" nodes once a + * hub has been selected (sequence is non-graphical). + */ +static igraph_error_t igraph_i_havel_hakimi(const igraph_vector_int_t *degseq, + igraph_vector_int_t *edges, + igraph_realize_degseq_t method) { + igraph_int_t n_nodes = igraph_vector_int_size(degseq); + + // ----- upfront error/graphicality checks ----- // + if (n_nodes == 0 || (n_nodes == 1 && VECTOR(*degseq)[0] == 0)) { + return IGRAPH_SUCCESS; + } + + for (igraph_int_t i = 0; i < n_nodes; i++) { + igraph_int_t deg = VECTOR(*degseq)[i]; + if (deg >= n_nodes) { + IGRAPH_ERROR("The given degree sequence cannot be realized as a simple graph.", IGRAPH_EINVAL); + } + } + + // ----- main Havel-Hakimi loop ----- // + // O(V + alpha(V) * E) + // O(V + E) for the LARGEST_FIRST method + igraph_vector_int_t seq; + IGRAPH_CHECK(igraph_vector_int_init_copy(&seq, degseq)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &seq); + + HavelHakimiList vault(&seq); + + igraph_int_t n_edges_added = 0; + igraph_vector_int_t spokes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&spokes, 0); + + for (igraph_int_t i = 0; i < n_nodes; i++) { + // hub node selection + vd_pair hub; + if (method == IGRAPH_REALIZE_DEGSEQ_SMALLEST) { + if (!vault.get_min_node(/* out param */hub)) break; + vault.remove_min_node(); + } + else if (method == IGRAPH_REALIZE_DEGSEQ_LARGEST) { + if (!vault.get_max_node(/* out param */hub)) break; + vault.remove_max_node(); + } + else if (method == IGRAPH_REALIZE_DEGSEQ_INDEX) { + igraph_int_t degree = VECTOR(seq)[i]; + hub = vd_pair{i, degree}; + vault.buckets[degree].count--; + } else { + // The fatal error is effectively an assertion that this line + // should not be reachable: + IGRAPH_FATAL("Invalid degree sequence realization method."); + } + VECTOR(seq)[hub.vertex] = 0; + + // spoke nodes selection + IGRAPH_CHECK(vault.get_spokes(hub.degree, seq, spokes)); + + igraph_int_t n_spokes = igraph_vector_int_size(&spokes); + for (igraph_int_t j = 0; j < n_spokes; j++) { + igraph_int_t spoke_idx = VECTOR(spokes)[j]; + VECTOR(*edges)[2*n_edges_added] = hub.vertex; + VECTOR(*edges)[2*n_edges_added + 1] = spoke_idx; + n_edges_added++; + + VECTOR(seq)[spoke_idx]--; + } + } + igraph_vector_int_destroy(&spokes); + igraph_vector_int_destroy(&seq); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/***********************************/ +/***** Undirected multigraphs ******/ +/***********************************/ + +// Given a sequence that is sorted, except for its first element, +// move the first element to the correct position fully sort the sequence. +template +static void bubble_up(It first, It last, Compare comp) { + if (first == last) { + return; + } + It it = first; + it++; + while (it != last) { + if (comp(*first, *it)) { + break; + } else { + std::swap(*first, *it); + } + first = it; + it++; + } +} + +// In each step, choose a vertex (the largest degree one if largest=true, +// the smallest degree one otherwise) and connect it to the largest remaining degree vertex. +// This will create a connected loopless multigraph, if one exists. +// If loops=true, and a loopless multigraph does not exist, complete the procedure +// by adding loops on the last vertex. +// If largest=false, and the degree sequence was potentially connected, the resulting +// graph will be connected. +// O(V * E + V log V) +static igraph_error_t igraph_i_realize_undirected_multi(const igraph_vector_int_t *deg, igraph_vector_int_t *edges, bool loops, bool largest) { + igraph_int_t vcount = igraph_vector_int_size(deg); + + if (vcount == 0) { + return IGRAPH_SUCCESS; + } + + std::vector vertices; + vertices.reserve(vcount); + for (igraph_int_t i = 0; i < vcount; ++i) { + igraph_int_t d = VECTOR(*deg)[i]; + vertices.push_back(vd_pair(i, d)); + } + + // Initial sort in non-increasing order. + // O (V log V) + std::stable_sort(vertices.begin(), vertices.end(), degree_greater); + + igraph_int_t ec = 0; + while (! vertices.empty()) { + // Remove any zero degrees, and error on negative ones. + + vd_pair &w = vertices.back(); + + if (w.degree == 0) { + vertices.pop_back(); + continue; + } + + // If only one vertex remains, then the degree sequence cannot be realized as + // a loopless multigraph. We either complete the graph by adding loops on this vertex + // or throw an error, depending on the 'loops' setting. + if (vertices.size() == 1) { + if (loops) { + for (igraph_int_t i = 0; i < w.degree / 2; ++i) { + VECTOR(*edges)[2 * ec] = w.vertex; + VECTOR(*edges)[2 * ec + 1] = w.vertex; + ec++; + } + break; + } else { + IGRAPH_ERROR("The given degree sequence cannot be realized as a loopless multigraph.", IGRAPH_EINVAL); + } + } + + // At this point we are guaranteed to have at least two remaining vertices. + + vd_pair *u, *v; + if (largest) { + u = &vertices[0]; + v = &vertices[1]; + } else { + u = &vertices.front(); + v = &vertices.back(); + } + + u->degree -= 1; + v->degree -= 1; + + VECTOR(*edges)[2*ec] = u->vertex; + VECTOR(*edges)[2*ec+1] = v->vertex; + ec++; + + // Now the first element may be out of order. + // If largest=true, the first two elements may be out of order. + // Restore the sorted order using a single step of bubble sort. + if (largest) { + bubble_up(vertices.begin() + 1, vertices.end(), degree_greater); + } + bubble_up(vertices.begin(), vertices.end(), degree_greater); + } + + return IGRAPH_SUCCESS; +} + +// O(V * E + V log V) +static igraph_error_t igraph_i_realize_undirected_multi_index(const igraph_vector_int_t *deg, igraph_vector_int_t *edges, bool loops) { + igraph_int_t vcount = igraph_vector_int_size(deg); + + if (vcount == 0) { + return IGRAPH_SUCCESS; + } + + typedef std::list vlist; + vlist vertices; + for (igraph_int_t i = 0; i < vcount; ++i) { + vertices.push_back(vd_pair(i, VECTOR(*deg)[i])); + } + + std::vector pointers; + pointers.reserve(vcount); + for (auto it = vertices.begin(); it != vertices.end(); ++it) { + pointers.push_back(it); + } + + // Initial sort + vertices.sort(degree_greater); + + igraph_int_t ec = 0; + for (const auto &pt : pointers) { + vd_pair vd = *pt; + vertices.erase(pt); + + while (vd.degree > 0) { + auto uit = vertices.begin(); + + if (vertices.empty() || uit->degree == 0) { + // We are out of non-zero degree vertices to connect to. + if (loops) { + for (igraph_int_t i = 0; i < vd.degree / 2; ++i) { + VECTOR(*edges)[2 * ec] = vd.vertex; + VECTOR(*edges)[2 * ec + 1] = vd.vertex; + ec++; + } + return IGRAPH_SUCCESS; + } else { + IGRAPH_ERROR("The given degree sequence cannot be realized as a loopless multigraph.", IGRAPH_EINVAL); + } + } + + vd.degree -= 1; + uit->degree -= 1; + + VECTOR(*edges)[2*ec] = vd.vertex; + VECTOR(*edges)[2*ec+1] = uit->vertex; + ec++; + + // If there are at least two elements, and the first two are not in order, + // re-sort the list. A possible optimization would be a version of + // bubble_up() that can exchange list nodes instead of swapping their values. + if (vertices.size() > 1) { + auto wit = uit; + ++wit; + + if (wit->degree > uit->degree) { + vertices.sort(degree_greater); + } + } + } + } + + return IGRAPH_SUCCESS; +} + + +/***********************************/ +/***** Directed simple graphs ******/ +/***********************************/ + +inline bool is_nonzero_outdeg(const vbd_pair &vd) { + return (vd.degree.second != 0); +} + + +// The below implementations of the Kleitman-Wang algorithm follow the description in https://arxiv.org/abs/0905.4913 + +// Realize bi-degree sequence as edge list +// If smallest=true, always choose the vertex with "smallest" bi-degree for connecting up next, +// otherwise choose the "largest" (based on lexicographic bi-degree ordering). +// O(E + V^2 log V) +static igraph_error_t igraph_i_kleitman_wang(const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, igraph_vector_int_t *edges, bool smallest) { + igraph_int_t n = igraph_vector_int_size(indeg); // number of vertices + + igraph_int_t ec = 0; // number of edges added so far + + std::vector vertices; + vertices.reserve(n); + for (igraph_int_t i = 0; i < n; ++i) { + vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); + } + + while (true) { + // sort vertices by (in, out) degree pairs in decreasing order + // O(V log V) + std::stable_sort(vertices.begin(), vertices.end(), degree_greater); + + // remove (0,0)-degree vertices + while (!vertices.empty() && vertices.back().degree == bidegree(0, 0)) { + vertices.pop_back(); + } + + // if no vertices remain, stop + if (vertices.empty()) { + break; + } + + // choose a vertex the out-stubs of which will be connected + // note: a vertex with non-zero out-degree is guaranteed to exist + // because there are _some_ non-zero degrees and the sum of in- and out-degrees + // is the same + vbd_pair *vdp; + // O(V) + if (smallest) { + vdp = &*std::find_if(vertices.rbegin(), vertices.rend(), is_nonzero_outdeg); + } else { + vdp = &*std::find_if(vertices.begin(), vertices.end(), is_nonzero_outdeg); + } + + // are there a sufficient number of other vertices to connect to? + if (static_cast(vertices.size()) - 1 < vdp->degree.second) { + goto fail; + } + + // create the connections + igraph_int_t k = 0; + for (auto it = vertices.begin(); + k < vdp->degree.second; + ++it) { + if (it->vertex == vdp->vertex) { + continue; // do not create a self-loop + } + if (--(it->degree.first) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + k)] = vdp->vertex; + VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; + + k++; + } + + ec += vdp->degree.second; + vdp->degree.second = 0; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given directed degree sequences cannot be realized as a simple graph.", IGRAPH_EINVAL); +} + + +// Choose vertices in the order of their IDs. +// O(E + V^2 log V) +static igraph_error_t igraph_i_kleitman_wang_index(const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, igraph_vector_int_t *edges) { + igraph_int_t n = igraph_vector_int_size(indeg); // number of vertices + + igraph_int_t ec = 0; // number of edges added so far + + typedef std::list vlist; + vlist vertices; + for (igraph_int_t i = 0; i < n; ++i) { + vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); + } + + std::vector pointers; + pointers.reserve(n); + for (auto it = vertices.begin(); it != vertices.end(); ++it) { + pointers.push_back(it); + } + + for (const auto &pt : pointers) { + // sort vertices by (in, out) degree pairs in decreasing order + // note: std::list::sort does a stable sort + vertices.sort(degree_greater); + + // choose a vertex the out-stubs of which will be connected + vbd_pair &vd = *pt; + + if (vd.degree.second == 0) { + continue; + } + + igraph_int_t k = 0; + vlist::iterator it; + for (it = vertices.begin(); + k != vd.degree.second && it != vertices.end(); + ++it) { + if (it->vertex == vd.vertex) { + continue; + } + + if (--(it->degree.first) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + k)] = vd.vertex; + VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; + + ++k; + } + if (it == vertices.end() && k < vd.degree.second) { + goto fail; + } + + ec += vd.degree.second; + vd.degree.second = 0; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given directed degree sequences cannot be realized as a simple graph.", IGRAPH_EINVAL); +} + + +/**************************/ +/***** Main functions *****/ +/**************************/ + +static igraph_error_t igraph_i_realize_undirected_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *deg, + igraph_edge_type_sw_t allowed_edge_types, + igraph_realize_degseq_t method) { + igraph_int_t node_count = igraph_vector_int_size(deg); + igraph_int_t deg_sum; + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(deg, °_sum)); + + if (deg_sum % 2 != 0) { + IGRAPH_ERROR("The sum of degrees must be even for an undirected graph.", IGRAPH_EINVAL); + } + + if (node_count > 0 && igraph_vector_int_min(deg) < 0) { + IGRAPH_ERROR("Vertex degrees must be non-negative.", IGRAPH_EINVAL); + } + + igraph_vector_int_t edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, deg_sum); + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW ) ) + { + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, true, false)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, true, true)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_realize_undirected_multi_index(deg, &edges, true)); + break; + default: + IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); + } + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) + { + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, false, false)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, false, true)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_realize_undirected_multi_index(deg, &edges, false)); + break; + default: + IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); + } + } + else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) + { + IGRAPH_ERROR("Graph realization with at most one self-loop per vertex is not implemented.", IGRAPH_UNIMPLEMENTED); + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) + { + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_SMALLEST)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_LARGEST)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_INDEX)); + break; + default: + IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); + } + } + else + { + /* Remaining cases: + * - At most one self-loop per vertex but multi-edges between distinct vertices allowed. + * - At most one edge between distinct vertices but multi-self-loops allowed. + * These cases cannot currently be requested through the documented API, + * so no explanatory error message for now. */ + return IGRAPH_UNIMPLEMENTED; + } + IGRAPH_HANDLE_EXCEPTIONS_END; + + IGRAPH_CHECK(igraph_create(graph, &edges, node_count, false)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_realize_directed_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *outdeg, + const igraph_vector_int_t *indeg, + igraph_edge_type_sw_t allowed_edge_types, + igraph_realize_degseq_t method) { + igraph_int_t node_count = igraph_vector_int_size(outdeg); + igraph_int_t edge_count, edge_count2, indeg_sum; + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outdeg, &edge_count)); + + if (igraph_vector_int_size(indeg) != node_count) { + IGRAPH_ERROR("In- and out-degree sequences must have the same length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(indeg, &indeg_sum)); + if (indeg_sum != edge_count) { + IGRAPH_ERROR("In- and out-degree sequences do not sum to the same value.", IGRAPH_EINVAL); + } + + if (node_count > 0 && (igraph_vector_int_min(outdeg) < 0 || igraph_vector_int_min(indeg) < 0)) { + IGRAPH_ERROR("Vertex degrees must be non-negative.", IGRAPH_EINVAL); + } + + /* TODO implement loopless and loopy multigraph case */ + if (allowed_edge_types != IGRAPH_SIMPLE_SW) { + IGRAPH_ERROR("Realizing directed degree sequences as non-simple graphs is not implemented.", IGRAPH_UNIMPLEMENTED); + } + + igraph_vector_int_t edges; + IGRAPH_SAFE_MULT(edge_count, 2, &edge_count2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, edge_count2); + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, true)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, false)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_kleitman_wang_index(outdeg, indeg, &edges)); + break; + default: + IGRAPH_ERROR("Invalid directed degree sequence realization method.", IGRAPH_EINVAL); + } + IGRAPH_HANDLE_EXCEPTIONS_END; + + IGRAPH_CHECK(igraph_create(graph, &edges, node_count, true)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_realize_degree_sequence + * \brief Generates a graph with the given degree sequence. + * + * This function generates an undirected graph that realizes a given degree + * sequence, or a directed graph that realizes a given pair of out- and + * in-degree sequences. + * + * + * Simple undirected graphs are constructed using the Havel-Hakimi algorithm + * (undirected case), or the analogous Kleitman-Wang algorithm (directed case). + * These algorithms work by choosing an arbitrary vertex and connecting all its + * stubs to other vertices of highest degree. In the directed case, the + * "highest" (in, out) degree pairs are determined based on lexicographic + * ordering. This step is repeated until all degrees have been connected up. + * + * + * Loopless multigraphs are generated using an analogous algorithm: an arbitrary + * vertex is chosen, and it is connected with a single connection to a highest + * remaining degee vertex. If self-loops are also allowed, the same algorithm + * is used, but if a non-zero vertex remains at the end of the procedure, the + * graph is completed by adding self-loops to it. Thus, the result will contain + * at most one vertex with self-loops. + * + * + * The \c method parameter controls the order in which the vertices to be + * connected are chosen. In the undirected case, \c IGRAPH_REALIZE_DEGSEQ_SMALLEST + * produces a connected graph when one exists. This makes this method suitable + * for constructing trees with a given degree sequence. + * + * + * For a undirected simple graph, the time complexity is O(V + alpha(V) * E). + * For an undirected multi graph, the time complexity is O(V * E + V log V). + * For a directed graph, the time complexity is O(E + V^2 log V). + * + * + * References: + * + * + * V. Havel: + * Poznámka o existenci konečných grafů (A remark on the existence of finite graphs), + * Časopis pro pěstování matematiky 80, 477-480 (1955). + * http://eudml.org/doc/19050 + * + * + * S. L. Hakimi: + * On Realizability of a Set of Integers as Degrees of the Vertices of a Linear Graph, + * Journal of the SIAM 10, 3 (1962). + * https://www.jstor.org/stable/2098770 + * + * + * D. J. Kleitman and D. L. Wang: + * Algorithms for Constructing Graphs and Digraphs with Given Valences and Factors, + * Discrete Mathematics 6, 1 (1973). + * https://doi.org/10.1016/0012-365X%2873%2990037-X + * + * P. L. Erdős, I. Miklós, Z. Toroczkai: + * A simple Havel-Hakimi type algorithm to realize graphical degree sequences of directed graphs, + * The Electronic Journal of Combinatorics 17.1 (2010). + * http://eudml.org/doc/227072 + * + * + * Sz. Horvát and C. D. Modes: + * Connectedness matters: construction and exact random sampling of connected networks (2021). + * https://doi.org/10.1088/2632-072X/abced5 + * + * \param graph Pointer to an uninitialized graph object. + * \param outdeg The degree sequence of an undirected graph (if \p indeg is NULL), + * or the out-degree sequence of a directed graph (if \p indeg is given). + * \param indeg The in-degree sequence of a directed graph. Pass \c NULL to + * generate an undirected graph. + * \param allowed_edge_types The types of edges to allow in the graph. See \ref + * igraph_edge_type_sw_t. For directed graphs, only \c IGRAPH_SIMPLE_SW is + * implemented at this moment. + * For undirected graphs, the following values are valid: + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graphs (i.e. no self-loops or multi-edges allowed). + * \cli IGRAPH_LOOPS_SW + * single self-loops are allowed, but not multi-edges; currently not implemented. + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed, but not self-loops. + * \cli IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW + * both self-loops and multi-edges are allowed. + * \endclist + * \param method The method to generate the graph. Possible values: + * \clist + * \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST + * The vertex with smallest remaining degree is selected first. The + * result is usually a graph with high negative degree assortativity. + * In the undirected case, this method is guaranteed to generate a + * connected graph, regardless of whether multi-edges are allowed, + * provided that a connected realization exists (see Horvát and Modes, + * 2021, as well as http://szhorvat.net/pelican/hh-connected-graphs.html). + * This method can be used to construct a tree from its degrees. + * In the directed case it tends to generate weakly connected graphs, + * but this is not guaranteed. + * \cli IGRAPH_REALIZE_DEGSEQ_LARGEST + * The vertex with the largest remaining degree is selected first. The + * result is usually a graph with high positive degree assortativity, and + * is often disconnected. + * \cli IGRAPH_REALIZE_DEGSEQ_INDEX + * The vertices are selected in order of their index (i.e. their position + * in the degree vector). Note that sorting the degree vector and using + * the \c INDEX method is not equivalent to the \c SMALLEST method above, + * as \c SMALLEST uses the smallest \em remaining degree for selecting + * vertices, not the smallest \em initial degree. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_UNIMPLEMENTED + * The requested method is not implemented. + * \cli IGRAPH_ENOMEM + * There is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * Invalid method parameter, or invalid in- and/or out-degree vectors. + * The degree vectors should be non-negative, the length + * and sum of \p outdeg and \p indeg should match for directed graphs. + * \endclist + * + * \sa \ref igraph_is_graphical() to test graphicality without generating a graph; + * \ref igraph_realize_bipartite_degree_sequence() to create bipartite graphs + * from two degree sequence; + * \ref igraph_degree_sequence_game() to generate random graphs with a given + * degree sequence; + * \ref igraph_k_regular_game() to generate random regular graphs; + * \ref igraph_rewire() to randomly rewire the edges of a graph while + * preserving its degree sequence. + * + * \example examples/simple/igraph_realize_degree_sequence.c + */ + +igraph_error_t igraph_realize_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, + igraph_edge_type_sw_t allowed_edge_types, + igraph_realize_degseq_t method) +{ + bool directed = indeg != NULL; + + if (directed) { + return igraph_i_realize_directed_degree_sequence(graph, outdeg, indeg, allowed_edge_types, method); + } else { + return igraph_i_realize_undirected_degree_sequence(graph, outdeg, allowed_edge_types, method); + } +} + + +// Uses index order to construct an undirected bipartite graph. +// degree1 is considered to range from index [0, len(degree1)[, +// so for this implementation degree1 is always the source degree +// sequence and degree2 is always the dest degree sequence. +static igraph_error_t igraph_i_realize_undirected_bipartite_index( + igraph_t *graph, + const igraph_vector_int_t *degree1, const igraph_vector_int_t *degree2, + igraph_bool_t multiedges +) { + igraph_int_t ec = 0; // The number of edges added so far + igraph_int_t n1 = igraph_vector_int_size(degree1); + igraph_int_t n2 = igraph_vector_int_size(degree2); + igraph_vector_int_t edges; + igraph_int_t ds1_sum; + igraph_int_t ds2_sum; + + std::vector vertices1; + std::vector vertices2; + std::vector *src_vs = &vertices1; + std::vector *dest_vs = &vertices2; + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degree1, &ds1_sum)); + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degree2, &ds2_sum)); + + if (ds1_sum != ds2_sum) { + goto fail; + } + + // If both degree sequences are empty, it's bigraphical + if (!(n1 == 0 && n2 == 0)) { + if (igraph_vector_int_min(degree1) < 0 || igraph_vector_int_min(degree2) < 0) { + goto fail; + } + } + + vertices1.reserve(n1); + vertices2.reserve(n2); + + for (igraph_int_t i = 0; i < n1; i++) { + vertices1.push_back(vd_pair(i, VECTOR(*degree1)[i])); + } + for (igraph_int_t i = 0; i < n2; i++) { + vertices2.push_back(vd_pair(i + n1, VECTOR(*degree2)[i])); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ds1_sum + ds2_sum); + + while (!vertices1.empty() && !vertices2.empty()) { + // Go by index, so we start in ds1, so ds2 needs to be sorted. + std::stable_sort(vertices2.begin(), vertices2.end(), degree_greater); + // No sorting of ds1 needed for index case + vd_pair vd_src = vertices1.front(); + // No multiedges - Take the first vertex, connect to the largest delta in opposite partition + if (!multiedges) { + // Remove the source degrees + src_vs->erase(src_vs->begin()); + + if (vd_src.degree == 0) { + continue; + } + + if (dest_vs->size() < size_t(vd_src.degree)) { + goto fail; + } + + for (igraph_int_t i = 0; i < vd_src.degree; i++) { + if ((*dest_vs)[i].degree == 0) { + // Not enough non-zero remaining degree vertices in opposite partition. + // Not graphical. + goto fail; + } + + (*dest_vs)[i].degree--; + + VECTOR(edges)[2*(ec + i)] = vd_src.vertex; + VECTOR(edges)[2*(ec + i) + 1] = (*dest_vs)[i].vertex; + } + ec += vd_src.degree; + } + // If multiedges are allowed + else { + // If this is the last edge to be created from this vertex, we remove it. + if (src_vs->front().degree <= 1) { + src_vs->erase(src_vs->begin()); + } else { + src_vs->front().degree--; + } + + if (vd_src.degree == 0) { + continue; + } + + if (dest_vs->size() < size_t(1)) { + goto fail; + } + // We should never decrement below zero, but check just in case. + IGRAPH_ASSERT((*dest_vs)[0].degree - 1 >= 0); + + // Connect to the opposite partition + (*dest_vs)[0].degree--; + + VECTOR(edges)[2 * ec] = vd_src.vertex; + VECTOR(edges)[2 * ec + 1] = (*dest_vs)[0].vertex; + ec++; + } + } + IGRAPH_CHECK(igraph_create(graph, &edges, n1 + n2, false)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERRORF("The given bidegree sequence cannot be realized as a bipartite %sgraph.", + IGRAPH_EINVAL, multiedges ? "multi" : "simple "); +} + +/** + * \function igraph_realize_bipartite_degree_sequence + * \brief Generates a bipartite graph with the given bidegree sequence. + * + * This function generates a bipartite graph with the given bidegree sequence, + * using a Havel-Hakimi-like construction algorithm. The order in which vertices + * are connected up is controlled by the \p method parameter. When using the + * \c IGRAPH_REALIZE_DEGSEQ_SMALLEST method, it is ensured that the graph will be + * connected if and only if the given bidegree sequence is potentially connected. + * + * + * The vertices of the graph will be ordered so that those having \p degrees1 + * come first, followed by \p degrees2. + * + * \param graph Pointer to an uninitialized graph object. + * \param degrees1 The degree sequence of the first partition. + * \param degrees2 The degree sequence of the second partition. + * \param allowed_edge_types The types of edges to allow in the graph. + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graph (i.e. no multi-edges allowed). + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed + * \endclist + * \param method Controls the order in which vertices are selected for connection. + * Possible values: + * \clist + * \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST + * The vertex with smallest remaining degree is selected first, from either + * partition. The result is usually a graph with high negative degree + * assortativity. This method is guaranteed to generate a connected graph, + * if one exists. + * \cli IGRAPH_REALIZE_DEGSEQ_LARGEST + * The vertex with the largest remaining degree is selected first, from + * either parition. The result is usually a graph with high positive degree + * assortativity, and is often disconnected. + * \cli IGRAPH_REALIZE_DEGSEQ_INDEX + * The vertices are selected in order of their index. + * \endclist + * \return Error code. + * \sa \ref igraph_is_bigraphical() to test bigraphicality without generating a graph. + */ + +igraph_error_t igraph_realize_bipartite_degree_sequence( + igraph_t *graph, + const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, + const igraph_edge_type_sw_t allowed_edge_types, const igraph_realize_degseq_t method +) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_int_t ec = 0; // The number of edges added so far + igraph_int_t n1 = igraph_vector_int_size(degrees1); + igraph_int_t n2 = igraph_vector_int_size(degrees2); + igraph_vector_int_t edges; + igraph_int_t ds1_sum; + igraph_int_t ds2_sum; + igraph_bool_t multiedges; + igraph_bool_t largest; + std::vector vertices1; + std::vector vertices2; + + // Bipartite graphs can't have self loops, so we ignore those. + if (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) { + // Multiedges allowed + multiedges = true; + } else { + // No multiedges + multiedges = false; + } + + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + largest = false; + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + largest = true; + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + return igraph_i_realize_undirected_bipartite_index(graph, degrees1, degrees2, multiedges); + default: + IGRAPH_ERROR("Invalid bipartite degree sequence realization method.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degrees1, &ds1_sum)); + IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degrees2, &ds2_sum)); + + // Degree sequences of the two partitions must sum to the same value + if (ds1_sum != ds2_sum) { + goto fail; + } + + // If both degree sequences are empty, it's bigraphical + if (!(n1 == 0 && n2 == 0)) { + if (igraph_vector_int_min(degrees1) < 0 || igraph_vector_int_min(degrees2) < 0) { + goto fail; + } + } + + vertices1.reserve(n1); + vertices2.reserve(n2); + + for (igraph_int_t i = 0; i < n1; i++) { + vertices1.push_back(vd_pair(i, VECTOR(*degrees1)[i])); + } + for (igraph_int_t i = 0; i < n2; i++) { + vertices2.push_back(vd_pair(i + n1, VECTOR(*degrees2)[i])); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ds1_sum + ds2_sum); + + + std::vector *src_vs; + std::vector *dest_vs; + + while (!vertices1.empty() && !vertices2.empty()) { + // Sort in non-increasing order. + // Note: for the smallest method, we can skip sorting the smaller ds, minor optimization. + // (i.e., we only need to sort the dest partition, since we always just remove the back of the min partition) + std::stable_sort(vertices1.begin(), vertices1.end(), degree_greater); + std::stable_sort(vertices2.begin(), vertices2.end(), degree_greater); + + vd_pair vd_src(-1, -1); + + if (!largest) { + vd_pair min1 = vertices1.back(); + vd_pair min2 = vertices2.back(); + if (min1.degree <= min2.degree) { + src_vs = &vertices1; + dest_vs = &vertices2; + } else { + src_vs = &vertices2; + dest_vs = &vertices1; + } + + vd_src = src_vs->back(); + + } else { + vd_pair max1 = vertices1.front(); + vd_pair max2 = vertices2.front(); + + if (max1.degree >= max2.degree) { + src_vs = &vertices1; + dest_vs = &vertices2; + } else { + src_vs = &vertices2; + dest_vs = &vertices1; + } + + vd_src = src_vs->front(); + } + + IGRAPH_ASSERT(vd_src.degree >= 0); + + if (!multiedges) { + // Remove the smallest element + if (!largest) { + src_vs->pop_back(); + } else { + // Remove the largest element. + src_vs->erase(src_vs->begin()); + } + + if (vd_src.degree == 0) { + continue; + } + if (dest_vs->size() < size_t(vd_src.degree)) { + goto fail; + } + for (igraph_int_t i = 0; i < vd_src.degree; i++) { + // Decrement the degree of the delta largest vertices in the opposite partition + + if ((*dest_vs)[i].degree == 0) { + // Not enough non-zero remaining degree vertices in opposite partition. + // Not graphical. + goto fail; + } + + (*dest_vs)[i].degree--; + + VECTOR(edges)[2 * (ec + i)] = vd_src.vertex; + VECTOR(edges)[2 * (ec + i) + 1] = (*dest_vs)[i].vertex; + } + ec += vd_src.degree; + } + // If multiedges are allowed + else { + // The smallest degree is in the back, and we know it is in vertices1 + // If this is the last edge to be created from this vertex, we remove it. + if (!largest) { + if (src_vs->back().degree <= 1) { + src_vs->pop_back(); + } else { + // Otherwise we decrement its degrees by 1 for the edge we are about to create. + src_vs->back().degree--; + } + } else { + if (src_vs->front().degree <= 1) { + src_vs->erase(src_vs->begin()); + } else { + src_vs->front().degree--; + } + } + + if (vd_src.degree == 0) { + continue; + } + + if (dest_vs->size() < size_t(1)) { + goto fail; + } + // We should never decrement below zero, but check just in case. + IGRAPH_ASSERT((*dest_vs)[0].degree - 1 >= 0); + + // Connect to the opposite partition + (*dest_vs)[0].degree--; + + VECTOR(edges)[2 * ec] = vd_src.vertex; + VECTOR(edges)[2 * ec + 1] = (*dest_vs)[0].vertex; + ec++; + } + } + IGRAPH_CHECK(igraph_create(graph, &edges, n1 + n2, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERRORF("The given bidegree sequence cannot be realized as a bipartite %sgraph.", + IGRAPH_EINVAL, multiedges ? "multi" : "simple "); + + IGRAPH_HANDLE_EXCEPTIONS_END; +} diff --git a/src/misc/embedding.c b/src/misc/embedding.c new file mode 100644 index 0000000..2b42757 --- /dev/null +++ b/src/misc/embedding.c @@ -0,0 +1,1195 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_embedding.h" + +#include "igraph_adjlist.h" +#include "igraph_blas.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include + +typedef struct { + const igraph_t *graph; + const igraph_vector_t *cvec; + const igraph_vector_t *cvec2; + igraph_adjlist_t *outlist, *inlist; + igraph_inclist_t *eoutlist, *einlist; + igraph_vector_t *tmp; + const igraph_vector_t *weights; +} igraph_i_asembedding_data_t; + +/* Adjacency matrix, unweighted, undirected. + Eigendecomposition is used */ +static igraph_error_t igraph_i_asembeddingu(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* to = (A+cD) from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Adjacency matrix, weighted, undirected. + Eigendecomposition is used. */ +static igraph_error_t igraph_i_asembeddinguw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + igraph_int_t i, j, nlen; + + /* to = (A+cD) from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Adjacency matrix, unweighted, directed. SVD. */ +static igraph_error_t igraph_i_asembedding(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* tmp = (A+cD)' from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += from[nei]; + } + VECTOR(*tmp)[i] += VECTOR(*cvec)[i] * from[i]; + } + + /* to = (A+cD) tmp */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + to[i] += VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Adjacency matrix, unweighted, directed. SVD, right eigenvectors */ +static igraph_error_t igraph_i_asembedding_right(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* to = (A+cD)' from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Adjacency matrix, weighted, directed. SVD. */ +static igraph_error_t igraph_i_asembeddingw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *incs; + igraph_int_t i, j, nlen; + + /* tmp = (A+cD)' from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * from[nei]; + } + VECTOR(*tmp)[i] += VECTOR(*cvec)[i] * from[i]; + } + + /* to = (A+cD) tmp */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * VECTOR(*tmp)[nei]; + } + to[i] += VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Adjacency matrix, weighted, directed. SVD, right eigenvectors. */ +static igraph_error_t igraph_i_asembeddingw_right(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + igraph_int_t i, j, nlen; + + /* to = (A+cD)' from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian D-A, unweighted, undirected. Eigendecomposition. */ +static igraph_error_t igraph_i_lsembedding_da(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* to = (D-A) from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] -= from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian D-A, weighted, undirected. Eigendecomposition. */ +static igraph_error_t igraph_i_lsembedding_daw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + igraph_int_t i, j, nlen; + + /* to = (D-A) from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] -= w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian DAD, unweighted, undirected. Eigendecomposition. */ +static igraph_error_t igraph_i_lsembedding_dad(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* to = D^1/2 from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * from[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = D tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_lsembedding_dadw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *incs; + igraph_int_t i, j, nlen; + + /* to = D^-1/2 from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = D tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = D^-1/2 tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian I-DAD, unweighted, undirected. Eigendecomposition. */ +static igraph_error_t igraph_i_lsembedding_idad(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_lsembedding_dad(to, from, n, extra); + for (int i = 0; i < n; i++) { + to[i] = from[i] - to[i]; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_lsembedding_idadw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_lsembedding_dadw(to, from, n, extra); + for (int i = 0; i < n; i++) { + to[i] = from[i] - to[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian OAP, unweighted, directed. SVD. */ +static igraph_error_t igraph_i_lseembedding_oap(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* tmp = O' from */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* to = A' tmp */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + } + + /* tmp = P' to */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_in)[i] * to[i]; + } + + /* to = P tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = O tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian OAP, unweighted, directed. SVD, right eigenvectors. */ +static igraph_error_t igraph_i_lseembedding_oap_right(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + + /* to = O' from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = P' tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian OAP, weighted, directed. SVD. */ +static igraph_error_t igraph_i_lseembedding_oapw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + igraph_int_t edge, nei; + igraph_real_t w; + + /* tmp = O' from */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* to = A' tmp */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + edge = VECTOR(*neis)[j]; + nei = IGRAPH_OTHER(graph, edge, i); + w = VECTOR(*weights)[edge]; + to[i] += w * VECTOR(*tmp)[nei]; + } + } + + /* tmp = P' to */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_in)[i] * to[i]; + } + + /* to = P tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + edge = VECTOR(*neis)[j]; + nei = IGRAPH_OTHER(graph, edge, i); + w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = O tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +/* Laplacian OAP, weighted, directed. SVD, right eigenvectors. */ +static igraph_error_t igraph_i_lseembedding_oapw_right(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + igraph_int_t i, j, nlen; + igraph_int_t edge, nei; + igraph_real_t w; + + /* to = O' from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + edge = VECTOR(*neis)[j]; + nei = IGRAPH_OTHER(graph, edge, i); + w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = P' tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_spectral_embedding(const igraph_t *graph, + igraph_int_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + const igraph_vector_t *cvec2, + igraph_arpack_options_t *options, + igraph_arpack_function_t *callback, + igraph_arpack_function_t *callback_right, + igraph_bool_t symmetric, + igraph_bool_t eigen, + igraph_bool_t zapsmall) { + + igraph_int_t vc = igraph_vcount(graph); + igraph_vector_t tmp; + igraph_adjlist_t outlist, inlist; + igraph_inclist_t eoutlist, einlist; + igraph_int_t i, j, cveclen = igraph_vector_size(cvec); + igraph_i_asembedding_data_t data; + igraph_vector_t tmpD; + + data.graph = graph; + data.cvec = cvec; + data.cvec2 = cvec2; + data.outlist = &outlist; + data.inlist = &inlist; + data.eoutlist = &eoutlist; + data.einlist = &einlist; + data.tmp = &tmp; + data.weights = weights; + + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (which != IGRAPH_EIGEN_LM && + which != IGRAPH_EIGEN_LA && + which != IGRAPH_EIGEN_SA) { + IGRAPH_ERROR("Invalid eigenvalue chosen, must be one of " + "`largest magnitude', `largest algebraic' or " + "`smallest algebraic'", IGRAPH_EINVAL); + } + + if (no > vc) { + IGRAPH_ERROR("Too many singular values requested", IGRAPH_EINVAL); + } + if (no <= 0) { + IGRAPH_ERROR("No singular values requested", IGRAPH_EINVAL); + } + + if (cveclen != 1 && cveclen != vc) { + IGRAPH_ERROR("Augmentation vector size is invalid, it should be " + "the number of vertices or scalar", IGRAPH_EINVAL); + } + + if (vc > INT_MAX) { + IGRAPH_ERROR("Graph too large for ARPACK", IGRAPH_EOVERFLOW); + } + + if (no > INT_MAX) { + IGRAPH_ERROR("Too many eigenvectors requested from ARPACK", IGRAPH_EOVERFLOW); + } + + IGRAPH_CHECK(igraph_matrix_resize(X, vc, no)); + if (Y) { + IGRAPH_CHECK(igraph_matrix_resize(Y, vc, no)); + } + + /* empty graph */ + if (igraph_ecount(graph) == 0) { + igraph_matrix_null(X); + if (Y) { + igraph_matrix_null(Y); + } + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, vc); + if (!weights) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &outlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &outlist); + if (!symmetric) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &inlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &inlist); + } + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &eoutlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &eoutlist); + if (!symmetric) { + IGRAPH_CHECK(igraph_inclist_init(graph, &einlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &einlist); + } + } + IGRAPH_VECTOR_INIT_FINALLY(&tmpD, no); + + options->n = (int) vc; + options->start = 1; /* no random start vector */ + options->nev = (int) no; + switch (which) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + break; + default: + break; + } + options->ncv = options->nev + 3; + if (options->ncv > options->n) { + options->ncv = options->n; + } + + /* We provide a random start vector to ARPACK on our own to ensure that + * we use igraph's RNG and not the one from ARPACK (which relies on LAPACK) */ + for (i = 0; i < vc; i++) { + MATRIX(*X, i, 0) = RNG_UNIF(-1, 1); + } + + IGRAPH_CHECK(igraph_arpack_rssolve(callback, &data, options, 0, &tmpD, X)); + + if (!symmetric) { + /* calculate left eigenvalues */ + IGRAPH_CHECK(igraph_matrix_resize(Y, vc, no)); + for (i = 0; i < no; i++) { + igraph_real_t norm; + igraph_vector_t v = igraph_vector_view(&MATRIX(*Y, 0, i), vc); + callback_right(&MATRIX(*Y, 0, i), &MATRIX(*X, 0, i), (int) vc, &data); + norm = 1.0 / igraph_blas_dnrm2(&v); + igraph_vector_scale(&v, norm); + } + } else if (Y) { + IGRAPH_CHECK(igraph_matrix_update(Y, X)); + } + + if (zapsmall) { + igraph_vector_zapsmall(&tmpD, 0); + igraph_matrix_zapsmall(X, 0); + if (Y) { + igraph_matrix_zapsmall(Y, 0); + } + } + + if (D) { + igraph_vector_update(D, &tmpD); + if (!eigen) { + for (i = 0; i < no; i++) { + VECTOR(*D)[i] = sqrt(VECTOR(*D)[i]); + } + } + } + + if (scaled) { + if (eigen) { + /* eigenvalues were calculated */ + for (i = 0; i < no; i++) { + VECTOR(tmpD)[i] = sqrt(fabs(VECTOR(tmpD)[i])); + } + } else { + /* singular values were calculated */ + for (i = 0; i < no; i++) { + VECTOR(tmpD)[i] = sqrt(sqrt(VECTOR(tmpD)[i])); + } + } + + for (j = 0; j < vc; j++) { + for (i = 0; i < no; i++) { + MATRIX(*X, j, i) *= VECTOR(tmpD)[i]; + } + } + + if (Y) { + for (j = 0; j < vc; j++) { + for (i = 0; i < no; i++) { + MATRIX(*Y, j, i) *= VECTOR(tmpD)[i]; + } + } + } + } + + igraph_vector_destroy(&tmpD); + if (!weights) { + if (!symmetric) { + igraph_adjlist_destroy(&inlist); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_adjlist_destroy(&outlist); + } else { + if (!symmetric) { + igraph_inclist_destroy(&einlist); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_inclist_destroy(&eoutlist); + } + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_adjacency_spectral_embedding + * Adjacency spectral embedding + * + * Spectral decomposition of the adjacency matrices of graphs. + * This function computes an n-dimensional Euclidean + * representation of the graph based on its adjacency + * matrix, A. This representation is computed via the singular value + * decomposition of the adjacency matrix, A=U D V^T. In the case, + * where the graph is a random dot product graph generated using latent + * position vectors in R^n for each vertex, the embedding will + * provide an estimate of these latent vectors. + * + * + * For undirected graphs, the latent positions are calculated as + * X = U^n D^(1/2) where U^n equals to the first no columns of U, and + * D^(1/2) is a diagonal matrix containing the square root of the selected + * singular values on the diagonal. + * + * + * For directed graphs, the embedding is defined as the pair + * X = U^n D^(1/2), Y = V^n D^(1/2). + * (For undirected graphs U=V, so it is sufficient to keep one of them.) + * + * \param graph The input graph, can be directed or undirected. + * \param n An integer scalar. This value is the embedding dimension of + * the spectral embedding. Should be smaller than the number of + * vertices. The largest n-dimensional non-zero + * singular values are used for the spectral embedding. + * \param weights Optional edge weights. Supply a null pointer for + * unweighted graphs. + * \param which Which eigenvalues (or singular values, for directed + * graphs) to use, possible values: + * \clist + * \cli IGRAPH_EIGEN_LM + * the ones with the largest magnitude + * \cli IGRAPH_EIGEN_LA + * the (algebraic) largest ones + * \cli IGRAPH_EIGEN_SA + * the (algebraic) smallest ones. + * \endclist + * For directed graphs, IGRAPH_EIGEN_LM and + * IGRAPH_EIGEN_LA are the same because singular + * values are used for the ordering instead of eigenvalues. + * \param scaled Whether to return X and Y (if \c scaled is true), or + * U and V. + * \param X Initialized matrix, the estimated latent positions are + * stored here. + * \param Y Initialized matrix or a null pointer. If not a null + * pointer, then the second half of the latent positions are + * stored here. (For undirected graphs, this always equals X.) + * \param D Initialized vector or a null pointer. If not a null + * pointer, then the eigenvalues (for undirected graphs) or the + * singular values (for directed graphs) are stored here. + * \param cvec A numeric vector, its length is the number vertices in the + * graph. This vector is added to the diagonal of the adjacency + * matrix, before performing the SVD. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Supply \c NULL to use the defaults. Note that the + * function overwrites the n (number of vertices), + * nev and which parameters and it always + * starts the calculation from a random start vector. + * \return Error code. + * + */ + +igraph_error_t igraph_adjacency_spectral_embedding(const igraph_t *graph, + igraph_int_t n, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback, *callback_right; + igraph_bool_t directed = igraph_is_directed(graph); + + if (directed) { + callback = weights ? igraph_i_asembeddingw : igraph_i_asembedding; + callback_right = (weights ? igraph_i_asembeddingw_right : + igraph_i_asembedding_right); + } else { + callback = weights ? igraph_i_asembeddinguw : igraph_i_asembeddingu; + callback_right = 0; + } + + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + + return igraph_i_spectral_embedding(graph, n, weights, which, scaled, + X, Y, D, cvec, /* deg2=*/ 0, + options, callback, callback_right, + /*symmetric=*/ !directed, + /*eigen=*/ !directed, /*zapsmall=*/ 1); +} + +static igraph_error_t igraph_i_lse_und(const igraph_t *graph, + igraph_int_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback; + igraph_vector_t deg; + + switch (type) { + case IGRAPH_EMBEDDING_D_A: + callback = weights ? igraph_i_lsembedding_daw : igraph_i_lsembedding_da; + break; + case IGRAPH_EMBEDDING_DAD: + callback = weights ? igraph_i_lsembedding_dadw : igraph_i_lsembedding_dad; + break; + case IGRAPH_EMBEDDING_I_DAD: + callback = weights ? igraph_i_lsembedding_idadw : igraph_i_lsembedding_idad; + break; + default: + IGRAPH_ERROR("Invalid Laplacian spectral embedding type", + IGRAPH_EINVAL); + break; + } + + IGRAPH_VECTOR_INIT_FINALLY(°, 0); + IGRAPH_CHECK(igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, weights)); + + switch (type) { + case IGRAPH_EMBEDDING_D_A: + break; + case IGRAPH_EMBEDDING_DAD: + case IGRAPH_EMBEDDING_I_DAD: { + igraph_int_t i, n = igraph_vector_size(°); + for (i = 0; i < n; i++) { + VECTOR(deg)[i] = 1.0 / sqrt(VECTOR(deg)[i]); + } + } + break; + default: + break; + } + + IGRAPH_CHECK(igraph_i_spectral_embedding(graph, no, weights, which, + scaled, X, Y, D, /*cvec=*/ °, /*deg2=*/ 0, + options, callback, 0, /*symmetric=*/ 1, + /*eigen=*/ 1, /*zapsmall=*/ 1)); + + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_lse_dir(const igraph_t *graph, + igraph_int_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback = + weights ? igraph_i_lseembedding_oapw : igraph_i_lseembedding_oap; + igraph_arpack_function_t *callback_right = + weights ? igraph_i_lseembedding_oapw_right : + igraph_i_lseembedding_oap_right; + igraph_vector_t deg_in, deg_out; + igraph_int_t i, n = igraph_vcount(graph); + + if (type != IGRAPH_EMBEDDING_OAP) { + IGRAPH_ERROR("Invalid Laplacian spectral embedding type", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°_in, n); + IGRAPH_VECTOR_INIT_FINALLY(°_out, n); + IGRAPH_CHECK(igraph_strength(graph, °_in, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, °_out, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, weights)); + + for (i = 0; i < n; i++) { + VECTOR(deg_in)[i] = 1.0 / sqrt(VECTOR(deg_in)[i]); + VECTOR(deg_out)[i] = 1.0 / sqrt(VECTOR(deg_out)[i]); + } + + IGRAPH_CHECK(igraph_i_spectral_embedding(graph, no, weights, which, + scaled, X, Y, D, /*cvec=*/ °_in, + /*deg2=*/ °_out, options, callback, + callback_right, /*symmetric=*/ 0, /*eigen=*/ 0, + /*zapsmall=*/ 1)); + + igraph_vector_destroy(°_in); + igraph_vector_destroy(°_out); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_laplacian_spectral_embedding + * Spectral embedding of the Laplacian of a graph + * + * This function essentially does the same as + * \ref igraph_adjacency_spectral_embedding, but works on the Laplacian + * of the graph, instead of the adjacency matrix. + * \param graph The input graph. + * \param n The number of eigenvectors (or singular vectors if the graph + * is directed) to use for the embedding. + * \param weights Optional edge weights. Supply a null pointer for + * unweighted graphs. + * \param which Which eigenvalues (or singular values, for directed + * graphs) to use, possible values: + * \clist + * \cli IGRAPH_EIGEN_LM + * the ones with the largest magnitude + * \cli IGRAPH_EIGEN_LA + * the (algebraic) largest ones + * \cli IGRAPH_EIGEN_SA + * the (algebraic) smallest ones. + * \endclist + * For directed graphs, IGRAPH_EIGEN_LM and + * IGRAPH_EIGEN_LA are the same because singular + * values are used for the ordering instead of eigenvalues. + * \param type The type of the Laplacian to use. Various definitions + * exist for the Laplacian of a graph, and one can choose + * between them with this argument. Possible values: + * \clist + * \cli IGRAPH_EMBEDDING_D_A + * means D - A where D is the + * degree matrix and A is the adjacency matrix + * \cli IGRAPH_EMBEDDING_DAD + * means Di times A times Di, + * where Di is the inverse of the square root of the degree matrix; + * \cli IGRAPH_EMBEDDING_I_DAD + * means I - Di A Di, where I + * is the identity matrix. + * \endclist + * \param scaled Whether to return X and Y (if \c scaled is true), or + * U and V. + * \param X Initialized matrix, the estimated latent positions are + * stored here. + * \param Y Initialized matrix or a null pointer. If not a null + * pointer, then the second half of the latent positions are + * stored here. (For undirected graphs, this always equals X.) + * \param D Initialized vector or a null pointer. If not a null + * pointer, then the eigenvalues (for undirected graphs) or the + * singular values (for directed graphs) are stored here. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Supply \c NULL to use the defaults. Note that the + * function overwrites the n (number of vertices), + * nev and which parameters and it always + * starts the calculation from a random start vector. + * \return Error code. + * + * \sa \ref igraph_adjacency_spectral_embedding to embed the adjacency + * matrix. + */ + +igraph_error_t igraph_laplacian_spectral_embedding(const igraph_t *graph, + igraph_int_t n, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + if (options == 0) { + options = igraph_arpack_options_get_default(); + } + + if (igraph_is_directed(graph)) { + return igraph_i_lse_dir(graph, n, weights, which, type, scaled, + X, Y, D, options); + } else { + return igraph_i_lse_und(graph, n, weights, which, type, scaled, + X, Y, D, options); + } +} + +/** + * \function igraph_dim_select + * \brief Dimensionality selection. + * + * Dimensionality selection for singular values using + * profile likelihood. + * + * + * The input of the function is a numeric vector which contains + * the measure of "importance" for each dimension. + * + * + * For spectral embedding, these are the singular values of the adjacency + * matrix. The singular values are assumed to be generated from a + * Gaussian mixture distribution with two components that have different + * means and same variance. The dimensionality d is chosen to + * maximize the likelihood when the d largest singular values are + * assigned to one component of the mixture and the rest of the singular + * values assigned to the other component. + * + * + * This function can also be used for the general separation problem, + * where we assume that the left and the right of the vector are coming + * from two normal distributions, with different means, and we want + * to know their border. + * + * \param sv A numeric vector, the ordered singular values. + * \param dim The result is stored here. + * \return Error code. + * + * Time complexity: O(n), n is the number of values in sv. + * + * \sa \ref igraph_adjacency_spectral_embedding(). + */ + +igraph_error_t igraph_dim_select(const igraph_vector_t *sv, igraph_int_t *dim) { + + igraph_int_t i, n = igraph_vector_size(sv); + igraph_real_t x, x2, sum1 = 0.0, sum2 = igraph_vector_sum(sv); + igraph_real_t sumsq1 = 0.0, sumsq2 = 0.0; /* to be set */ + igraph_real_t oldmean1, oldmean2, mean1 = 0.0, mean2 = sum2 / n; + igraph_real_t varsq1 = 0.0, varsq2 = 0.0; /* to be set */ + igraph_real_t var1, var2, sd, profile, max = -IGRAPH_INFINITY; + + if (n == 0) { + IGRAPH_ERROR("Need at least one singular value for dimensionality " + "selection", IGRAPH_EINVAL); + } + + if (n == 1) { + *dim = 1; + return IGRAPH_SUCCESS; + } + + for (i = 0; i < n; i++) { + x = VECTOR(*sv)[i]; + sumsq2 += x * x; + varsq2 += (mean2 - x) * (mean2 - x); + } + + for (i = 0; i < n - 1; i++) { + igraph_int_t n1 = i + 1, n2 = n - i - 1, n1m1 = n1 - 1, n2m1 = n2 - 1; + x = VECTOR(*sv)[i]; x2 = x * x; + sum1 += x; sum2 -= x; + sumsq1 += x2; sumsq2 -= x2; + oldmean1 = mean1; oldmean2 = mean2; + mean1 = sum1 / n1; mean2 = sum2 / n2; + varsq1 += (x - oldmean1) * (x - mean1); + varsq2 -= (x - oldmean2) * (x - mean2); + var1 = i == 0 ? 0 : varsq1 / n1m1; + var2 = i == n - 2 ? 0 : varsq2 / n2m1; + sd = sqrt(( n1m1 * var1 + n2m1 * var2) / (n - 2)); + profile = /* - n * log(2.0*M_PI)/2.0 */ /* This is redundant */ + - n * log(sd) - + ((sumsq1 - 2 * mean1 * sum1 + n1 * mean1 * mean1) + + (sumsq2 - 2 * mean2 * sum2 + n2 * mean2 * mean2)) / 2.0 / sd / sd; + if (profile > max) { + max = profile; + *dim = n1; + } + } + + /* Plus the last case, all elements in one group */ + x = VECTOR(*sv)[n - 1]; + sum1 += x; + oldmean1 = mean1; + mean1 = sum1 / n; + sumsq1 += x * x; + varsq1 += (x - oldmean1) * (x - mean1); + var1 = varsq1 / (n - 1); + sd = sqrt(var1); + profile = /* - n * log(2.0*M_PI)/2.0 */ /* This is redundant */ + - n * log(sd) - + (sumsq1 - 2 * mean1 * sum1 + n * mean1 * mean1) / 2.0 / sd / sd; + if (profile > max) { + max = profile; + *dim = n; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/graphicality.c b/src/misc/graphicality.c new file mode 100644 index 0000000..30412a7 --- /dev/null +++ b/src/misc/graphicality.c @@ -0,0 +1,928 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_graphicality.h" + +#include "misc/graphicality.h" + + +igraph_error_t igraph_i_edge_type_to_loops_multiple( + igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *loops, igraph_bool_t *multiple) { + + *loops = (allowed_edge_types & IGRAPH_LOOPS_SW) ? true : false; + *multiple = (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ? true : false; + + if (*loops) { + igraph_bool_t multi_loops = (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW); + if (*multiple != multi_loops) { + IGRAPH_ERROR("Either both multi-edges and multi-loops should be allowed or neither.", + IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_is_graphical_undirected_multi_loops(const igraph_vector_int_t *degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_undirected_loopless_multi(const igraph_vector_int_t *degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_undirected_loopy_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_undirected_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res); + +static igraph_error_t igraph_i_is_graphical_directed_loopy_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_directed_loopless_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_directed_loopy_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res); +static igraph_error_t igraph_i_is_graphical_directed_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res); + +static igraph_error_t igraph_i_is_bigraphical_multi(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res); +static igraph_error_t igraph_i_is_bigraphical_simple(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res); + + +/** + * \function igraph_is_graphical + * \brief Is there a graph with the given degree sequence? + * + * Determines whether a sequence of integers can be the degree sequence of some graph. + * The classical concept of graphicality assumes simple graphs. This function can perform + * the check also when either self-loops, multi-edge, or both are allowed in the graph. + * + * + * For simple undirected graphs, the Erdős-Gallai conditions are checked using the linear-time + * algorithm of Cloteaux. If both self-loops and multi-edges are allowed, + * it is sufficient to chek that that sum of degrees is even. If only multi-edges are allowed, but + * not self-loops, there is an additional condition that the sum of degrees be no smaller than twice + * the maximum degree. If at most one self-loop is allowed per vertex, but no multi-edges, a modified + * version of the Erdős-Gallai conditions are used (see Cairns & Mendan). + * + * + * For simple directed graphs, the Fulkerson-Chen-Anstee theorem is used with the relaxation by Berger. + * If both self-loops and multi-edges are allowed, then it is sufficient to check that the sum of + * in- and out-degrees is the same. If only multi-edges are allowed, but not self loops, there is an + * additional condition that the sum of out-degrees (or equivalently, in-degrees) is no smaller than + * the maximum total degree. If single self-loops are allowed, but not multi-edges, the problem is equivalent + * to realizability as a simple bipartite graph, thus the Gale-Ryser theorem can be used; see + * \ref igraph_is_bigraphical() for more information. + * + * + * References: + * + * + * P. Erdős and T. Gallai, Gráfok előírt fokú pontokkal, Matematikai Lapok 11, pp. 264–274 (1960). + * https://users.renyi.hu/~p_erdos/1961-05.pdf + * + * + * Z. Király, Recognizing graphic degree sequences and generating all realizations. + * TR-2011-11, Egerváry Research Group, H-1117, Budapest, Hungary. ISSN 1587-4451 (2012). + * https://egres.elte.hu/tr/egres-11-11.pdf + * + * + * B. Cloteaux, Is This for Real? Fast Graphicality Testing, Comput. Sci. Eng. 17, 91 (2015). + * https://dx.doi.org/10.1109/MCSE.2015.125 + * + * + * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014). + * https://dx.doi.org/10.1016/j.disc.2013.09.010 + * + * + * G. Cairns and S. Mendan, Degree Sequence for Graphs with Loops (2013). + * https://arxiv.org/abs/1303.2145v1 + * + * \param out_degrees A vector of integers specifying the degree sequence for + * undirected graphs or the out-degree sequence for directed graphs. + * \param in_degrees A vector of integers specifying the in-degree sequence for + * directed graphs. For undirected graphs, it must be \c NULL. + * \param allowed_edge_types The types of edges to allow in the graph. See + * \ref igraph_edge_type_sw_t for details. + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graphs (i.e. no self-loops or multi-edges allowed). + * \cli IGRAPH_LOOPS_SW + * single self-loops are allowed, but not multi-edges. + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed, but not self-loops. + * \cli IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW + * both self-loops and multi-edges are allowed. + * \endclist + * \param res Pointer to a Boolean. The result will be stored here. + * + * \return Error code. + * + * \sa \ref igraph_is_bigraphical() to check if a bi-degree-sequence can be realized as a bipartite graph; + * \ref igraph_realize_degree_sequence() to construct a graph with a given degree sequence. + * + * Time complexity: O(n), where n is the length of the degree sequence(s). + */ +igraph_error_t igraph_is_graphical(const igraph_vector_int_t *out_degrees, + const igraph_vector_int_t *in_degrees, + const igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res) +{ + /* Undirected case: */ + if (in_degrees == NULL) + { + if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW )) { + /* Typically this case is used when multiple edges are allowed both as self-loops and + * between distinct vertices. However, the conditions are the same even if multi-edges + * are not allowed between distinct vertices (only as self-loops). Therefore, we + * do not test IGRAPH_I_MULTI_EDGES_SW in the if (...). */ + return igraph_i_is_graphical_undirected_multi_loops(out_degrees, res); + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_undirected_loopless_multi(out_degrees, res); + } + else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_undirected_loopy_simple(out_degrees, res); + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_undirected_simple(out_degrees, res); + } else { + /* Remaining case: + * - At most one self-loop per vertex but multi-edges between distinct vertices allowed. + * These cases cannot currently be requested through the documented API, + * so no explanatory error message for now. */ + return IGRAPH_UNIMPLEMENTED; + } + } + /* Directed case: */ + else + { + if (igraph_vector_int_size(in_degrees) != igraph_vector_int_size(out_degrees)) { + IGRAPH_ERROR("The length of out- and in-degree sequences must be the same.", IGRAPH_EINVAL); + } + + if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW ) ) { + return igraph_i_is_graphical_directed_loopy_multi(out_degrees, in_degrees, res); + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_directed_loopless_multi(out_degrees, in_degrees, res); + } + else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_directed_loopy_simple(out_degrees, in_degrees, res); + } + else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) { + return igraph_i_is_graphical_directed_simple(out_degrees, in_degrees, res); + } else { + /* Remaining cases: + * - At most one self-loop per vertex but multi-edges between distinct vertices allowed. + * - At most one edge between distinct vertices but multi-self-loops allowed. + * These cases cannot currently be requested through the documented API, + * so no explanatory error message for now. */ + return IGRAPH_UNIMPLEMENTED; + } + } + + /* can't reach here */ +} + +/** + * \function igraph_is_bigraphical + * \brief Is there a bipartite graph with the given bi-degree-sequence? + * + * Determines whether two sequences of integers can be the degree sequences of + * a bipartite graph. Such a pair of degree sequence is called \em bigraphical. + * + * + * When multi-edges are allowed, it is sufficient to check that the sum of degrees is the + * same in the two partitions. For simple graphs, the Gale-Ryser theorem is used + * with Berger's relaxation. + * + * + * References: + * + * + * H. J. Ryser, Combinatorial Properties of Matrices of Zeros and Ones, Can. J. Math. 9, 371 (1957). + * https://dx.doi.org/10.4153/cjm-1957-044-3 + * + * + * D. Gale, A theorem on flows in networks, Pacific J. Math. 7, 1073 (1957). + * https://dx.doi.org/10.2140/pjm.1957.7.1073 + * + * + * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014). + * https://dx.doi.org/10.1016/j.disc.2013.09.010 + * + * \param degrees1 A vector of integers specifying the degrees in the first partition + * \param degrees2 A vector of integers specifying the degrees in the second partition + * \param allowed_edge_types The types of edges to allow in the graph: + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graphs (i.e. no multi-edges allowed). + * \cli IGRAPH_MULTI_SW + * multi-edges are allowed. + * \endclist + * \param res Pointer to a Boolean. The result will be stored here. + * + * \return Error code. + * + * \sa \ref igraph_is_graphical() + * + * Time complexity: O(n), where n is the length of the larger degree sequence. + */ +igraph_error_t igraph_is_bigraphical(const igraph_vector_int_t *degrees1, + const igraph_vector_int_t *degrees2, + const igraph_edge_type_sw_t allowed_edge_types, + igraph_bool_t *res) +{ + /* Note: Bipartite graphs can't have self-loops so we ignore the IGRAPH_LOOPS_SW bit. */ + if (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) { + return igraph_i_is_bigraphical_multi(degrees1, degrees2, res); + } else { + return igraph_i_is_bigraphical_simple(degrees1, degrees2, res); + } +} + + +/***** Undirected case *****/ + +/* Undirected graph with multi-self-loops: + * - Degrees must be non-negative. + * - The sum of degrees must be even. + * + * These conditions are valid regardless of whether multi-edges are allowed between distinct vertices. + */ +static igraph_error_t igraph_i_is_graphical_undirected_multi_loops(const igraph_vector_int_t *degrees, igraph_bool_t *res) { + igraph_int_t sum_parity = 0; /* 0 if the degree sum is even, 1 if it is odd */ + igraph_int_t n = igraph_vector_int_size(degrees); + igraph_int_t i; + + for (i = 0; i < n; ++i) { + igraph_int_t d = VECTOR(*degrees)[i]; + + if (d < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + sum_parity = (sum_parity + d) & 1; + } + + *res = (sum_parity == 0); + + return IGRAPH_SUCCESS; +} + + +/* Undirected loopless multigraph: + * - Degrees must be non-negative. + * - The sum of degrees must be even. + * - The sum of degrees must be no smaller than 2*d_max. + */ +static igraph_error_t igraph_i_is_graphical_undirected_loopless_multi(const igraph_vector_int_t *degrees, igraph_bool_t *res) { + igraph_int_t i; + igraph_int_t n = igraph_vector_int_size(degrees); + igraph_int_t dsum, dmax; + + /* Zero-length sequences are considered graphical. */ + if (n == 0) { + *res = true; + return IGRAPH_SUCCESS; + } + + dsum = 0; dmax = 0; + for (i = 0; i < n; ++i) { + igraph_int_t d = VECTOR(*degrees)[i]; + + if (d < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + dsum += d; + if (d > dmax) { + dmax = d; + } + } + + *res = (dsum % 2 == 0) && (dsum >= 2*dmax); + + return IGRAPH_SUCCESS; +} + + +/* Undirected graph with no multi-edges and at most one self-loop per vertex: + * - Degrees must be non-negative. + * - The sum of degrees must be even. + * - Use the modification of the Erdős-Gallai theorem due to Cairns and Mendan. + */ +static igraph_error_t igraph_i_is_graphical_undirected_loopy_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res) { + igraph_vector_int_t num_degs; + igraph_int_t w, b, s, c, n, k, wd, kd; + + n = igraph_vector_int_size(degrees); + + /* Zero-length sequences are considered graphical. */ + if (n == 0) { + *res = true; + return IGRAPH_SUCCESS; + } + + /* The conditions from the loopy multigraph case are necessary here as well. */ + IGRAPH_CHECK(igraph_i_is_graphical_undirected_multi_loops(degrees, res)); + if (! *res) { + return IGRAPH_SUCCESS; + } + + /* + * We follow this paper: + * + * G. Cairns & S. Mendan: Degree Sequences for Graphs with Loops, 2013 + * https://arxiv.org/abs/1303.2145v1 + * + * They give the following modification of the Erdős-Gallai theorem: + * + * A non-increasing degree sequence d_1 >= ... >= d_n has a realization as + * a simple graph with loops (i.e. at most one self-loop allowed on each vertex) + * iff + * + * \sum_{i=1}^k d_i <= k(k+1) + \sum_{i=k+1}^{n} min(d_i, k) + * + * for each k=1..n + * + * The difference from Erdős-Gallai is that here we have the term + * k(k+1) instead of k(k-1). + * + * The implementation is analogous to igraph_i_is_graphical_undirected_simple(), + * which in turn is based on Király 2012. See comments in that function for details. + * w and k are zero-based here, unlike in the statement of the theorem above. + */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&num_degs, n+2); + + for (igraph_int_t i = 0; i < n; ++i) { + igraph_int_t degree = VECTOR(*degrees)[i]; + + /* Negative degrees are already checked in igraph_i_is_graphical_undirected_multi_loops() */ + if (degree > n+1) { + *res = false; + goto undirected_loopy_simple_finish; + } + + ++VECTOR(num_degs)[degree]; + } + + /* Convert num_degs to a cumulative sum array. */ + for (igraph_int_t d = n; d >= 0; --d) { + VECTOR(num_degs)[d] += VECTOR(num_degs)[d+1]; + } + + wd = 0, kd = n+1; + *res = true; + w = n - 1; b = 0; s = 0; c = 0; + for (k = 0; k < n; k++) { + while (k >= VECTOR(num_degs)[kd]) { + --kd; + } + b += kd; + c += w; + while (w > k) { + while (wd + 1 <= n + 1 && w < VECTOR(num_degs)[wd + 1]) { + wd++; + } + if (wd > k + 1) break; + s += wd; + c -= (k + 1); + w--; + } + if (b > c + s + 2*(k + 1)) { + *res = false; + break; + } + if (w == k) { + break; + } + } + +undirected_loopy_simple_finish: + igraph_vector_int_destroy(&num_degs); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Undirected simple graph: + * - Degrees must be non-negative. + * - The sum of degrees must be even. + * - Use the Erdős-Gallai theorem. + */ +static igraph_error_t igraph_i_is_graphical_undirected_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res) { + igraph_vector_int_t num_degs; /* num_degs[d] is the # of vertices with degree d */ + const igraph_int_t p = igraph_vector_int_size(degrees); + igraph_int_t dmin, dmax, dsum; + igraph_int_t n; /* number of non-zero degrees */ + igraph_int_t k, sum_deg, sum_ni, sum_ini; + igraph_int_t i, dk; + igraph_int_t zverovich_bound; + + if (p == 0) { + *res = true; + return IGRAPH_SUCCESS; + } + + /* The following implementation of the Erdős-Gallai test + * is mostly a direct translation of the Python code given in + * + * Brian Cloteaux, Is This for Real? Fast Graphicality Testing, + * Computing Prescriptions, pp. 91-95, vol. 17 (2015) + * https://dx.doi.org/10.1109/MCSE.2015.125 + * + * It uses counting sort to achieve linear runtime. + */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&num_degs, p); + + dmin = p; dmax = 0; dsum = 0; n = 0; + for (i = 0; i < p; ++i) { + igraph_int_t d = VECTOR(*degrees)[i]; + + if (d < 0 || d >= p) { + *res = false; + goto finish; + } + + if (d > 0) { + dmax = d > dmax ? d : dmax; + dmin = d < dmin ? d : dmin; + dsum += d; + n++; + VECTOR(num_degs)[d] += 1; + } + } + + if (dsum % 2 != 0) { + *res = false; + goto finish; + } + + if (n == 0) { + *res = true; + goto finish; /* all degrees are zero => graphical */ + } + + /* According to: + * + * G. Cairns, S. Mendan, and Y. Nikolayevsky, A sharp refinement of a result of Zverovich-Zverovich, + * Discrete Math. 338, 1085 (2015). + * https://dx.doi.org/10.1016/j.disc.2015.02.001 + * + * a sufficient but not necessary condition of graphicality for a sequence of + * n strictly positive integers is that + * + * dmin * n >= floor( (dmax + dmin + 1)^2 / 4 ) - 1 + * if dmin is odd or (dmax + dmin) mod 4 == 1 + * + * or + * + * dmin * n >= floor( (dmax + dmin + 1)^2 / 4 ) + * otherwise. + */ + + zverovich_bound = ((dmax + dmin + 1) * (dmax + dmin + 1)) / 4; + if (dmin % 2 == 1 || (dmax + dmin) % 4 == 1) { + zverovich_bound -= 1; + } + + if (dmin*n >= zverovich_bound) { + *res = true; + goto finish; + } + + k = 0; sum_deg = 0; sum_ni = 0; sum_ini = 0; + for (dk = dmax; dk >= dmin; --dk) { + igraph_int_t run_size, v; + + if (dk < k+1) { + *res = true; + goto finish; + } + + run_size = VECTOR(num_degs)[dk]; + if (run_size > 0) { + if (dk < k + run_size) { + run_size = dk - k; + } + sum_deg += run_size * dk; + for (v=0; v < run_size; ++v) { + sum_ni += VECTOR(num_degs)[k+v]; + sum_ini += (k+v) * VECTOR(num_degs)[k+v]; + } + k += run_size; + if (sum_deg > k*(n-1) - k*sum_ni + sum_ini) { + *res = false; + goto finish; + } + } + } + + *res = true; + +finish: + igraph_vector_int_destroy(&num_degs); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/***** Directed case *****/ + +/* Directed loopy multigraph: + * - Degrees must be non-negative. + * - The sum of in- and out-degrees must be the same. + */ +static igraph_error_t igraph_i_is_graphical_directed_loopy_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) { + igraph_int_t sumdiff; /* difference between sum of in- and out-degrees */ + igraph_int_t n = igraph_vector_int_size(out_degrees); + igraph_int_t i; + + IGRAPH_ASSERT(igraph_vector_int_size(in_degrees) == n); + + sumdiff = 0; + for (i = 0; i < n; ++i) { + igraph_int_t dout = VECTOR(*out_degrees)[i]; + igraph_int_t din = VECTOR(*in_degrees)[i]; + + if (dout < 0 || din < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + + sumdiff += din - dout; + } + + *res = sumdiff == 0; + + return IGRAPH_SUCCESS; +} + + +/* Directed loopless multigraph: + * - Degrees must be non-negative. + * - The sum of in- and out-degrees must be the same. + * - The sum of out-degrees must be no smaller than d_max, + * where d_max is the largest total degree. + */ +static igraph_error_t igraph_i_is_graphical_directed_loopless_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) { + igraph_int_t i, sumin, sumout, dmax; + igraph_int_t n = igraph_vector_int_size(out_degrees); + + IGRAPH_ASSERT(igraph_vector_int_size(in_degrees) == n); + + sumin = 0; sumout = 0; + dmax = 0; + for (i = 0; i < n; ++i) { + igraph_int_t dout = VECTOR(*out_degrees)[i]; + igraph_int_t din = VECTOR(*in_degrees)[i]; + igraph_int_t d = dout + din; + + if (dout < 0 || din < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + + sumin += din; sumout += dout; + + if (d > dmax) { + dmax = d; + } + } + + *res = (sumin == sumout) && (sumout >= dmax); + + return IGRAPH_SUCCESS; +} + + +/* Directed graph with no multi-edges and at most one self-loop per vertex: + * - Degrees must be non-negative. + * - Equivalent to bipartite simple graph. + */ +static igraph_error_t igraph_i_is_graphical_directed_loopy_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) { + return igraph_i_is_bigraphical_simple(out_degrees, in_degrees, res); +} + + +/* Directed simple graph: + * - Degrees must be non-negative. + * - The sum of in- and out-degrees must be the same. + * - Use the Fulkerson-Chen-Anstee theorem + */ +static igraph_error_t igraph_i_is_graphical_directed_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) { + igraph_vector_int_t in_degree_cumcounts, in_degree_counts; + igraph_vector_int_t sorted_in_degrees, sorted_out_degrees; + igraph_vector_int_t left_pq, right_pq; + igraph_int_t lhs, rhs, left_pq_size, right_pq_size, left_i, right_i, left_sum, right_sum; + + /* The conditions from the loopy multigraph case are necessary here as well. */ + IGRAPH_CHECK(igraph_i_is_graphical_directed_loopy_multi(out_degrees, in_degrees, res)); + if (! *res) { + return IGRAPH_SUCCESS; + } + + const igraph_int_t vcount = igraph_vector_int_size(out_degrees); + if (vcount == 0) { + *res = true; + return IGRAPH_SUCCESS; + } + + + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree_cumcounts, vcount+1); + + /* Compute in_degree_cumcounts[d+1] to be the no. of in-degrees == d */ + for (igraph_int_t v = 0; v < vcount; v++) { + igraph_int_t indeg = VECTOR(*in_degrees)[v]; + igraph_int_t outdeg = VECTOR(*out_degrees)[v]; + if (indeg >= vcount || outdeg >= vcount) { + *res = false; + igraph_vector_int_destroy(&in_degree_cumcounts); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + VECTOR(in_degree_cumcounts)[indeg + 1]++; + } + + /* Compute in_degree_cumcounts[d] to be the no. of in-degrees < d */ + for (igraph_int_t indeg = 0; indeg < vcount; indeg++) { + VECTOR(in_degree_cumcounts)[indeg+1] += VECTOR(in_degree_cumcounts)[indeg]; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&sorted_out_degrees, vcount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&sorted_in_degrees, vcount); + + /* In the following loop, in_degree_counts[d] keeps track of the number of vertices + * with in-degree d that were already placed. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree_counts, vcount); + + for (igraph_int_t v = 0; v < vcount; v++) { + igraph_int_t outdeg = VECTOR(*out_degrees)[v]; + igraph_int_t indeg = VECTOR(*in_degrees)[v]; + igraph_int_t idx = VECTOR(in_degree_cumcounts)[indeg] + VECTOR(in_degree_counts)[indeg]; + VECTOR(sorted_out_degrees)[vcount - idx - 1] = outdeg; + VECTOR(sorted_in_degrees)[vcount - idx - 1] = indeg; + VECTOR(in_degree_counts)[indeg]++; + } + + igraph_vector_int_destroy(&in_degree_counts); + igraph_vector_int_destroy(&in_degree_cumcounts); + IGRAPH_FINALLY_CLEAN(2); + + /* Be optimistic, then check whether the Fulkerson–Chen–Anstee condition + * holds for every k. In particular, for every k in [0; n), it must be true + * that: + * + * \sum_{i=0}^k indegree[i] <= + * \sum_{i=0}^k min(outdegree[i], k) + + * \sum_{i=k+1}^{n-1} min(outdegree[i], k + 1) + */ + +#define INDEGREE(x) (VECTOR(sorted_in_degrees)[x]) +#define OUTDEGREE(x) (VECTOR(sorted_out_degrees)[x]) + + IGRAPH_VECTOR_INT_INIT_FINALLY(&left_pq, vcount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&right_pq, vcount); + + left_pq_size = 0; + right_pq_size = vcount; + left_i = 0; + right_i = 0; + left_sum = 0; + right_sum = 0; + for (igraph_int_t i = 0; i < vcount; i++) { + VECTOR(right_pq)[OUTDEGREE(i)]++; + } + + *res = true; + lhs = 0; + rhs = 0; + for (igraph_int_t i = 0; i < vcount; i++) { + lhs += INDEGREE(i); + + /* It is enough to check for indexes where the in-degree is about to + * decrease in the next step; see "Stronger condition" in the Wikipedia + * entry for the Fulkerson-Chen-Anstee condition. However, this does not + * provide any noticeable benefits for the current implementation. */ + + if (OUTDEGREE(i) < i) { + left_sum += OUTDEGREE(i); + } + else { + VECTOR(left_pq)[OUTDEGREE(i)]++; + left_pq_size++; + } + while (left_i < i) { + while (VECTOR(left_pq)[left_i] > 0) { + VECTOR(left_pq)[left_i]--; + left_pq_size--; + left_sum += left_i; + } + left_i++; + } + + while (right_i < i + 1) { + while (VECTOR(right_pq)[right_i] > 0) { + VECTOR(right_pq)[right_i]--; + right_pq_size--; + right_sum += right_i; + } + right_i++; + } + if (OUTDEGREE(i) < i + 1) { + right_sum -= OUTDEGREE(i); + } + else { + VECTOR(right_pq)[OUTDEGREE(i)]--; + right_pq_size--; + } + + rhs = left_sum + i * left_pq_size + right_sum + (i + 1) * right_pq_size; + if (lhs > rhs) { + *res = false; + break; + } + } + +#undef INDEGREE +#undef OUTDEGREE + + igraph_vector_int_destroy(&sorted_in_degrees); + igraph_vector_int_destroy(&sorted_out_degrees); + igraph_vector_int_destroy(&left_pq); + igraph_vector_int_destroy(&right_pq); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + + +/***** Bipartite case *****/ + +/* Bipartite graph with multi-edges: + * - Degrees must be non-negative. + * - Sum of degrees must be the same in the two partitions. + */ +static igraph_error_t igraph_i_is_bigraphical_multi(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res) { + igraph_int_t i; + igraph_int_t sum1, sum2; + igraph_int_t n1 = igraph_vector_int_size(degrees1), n2 = igraph_vector_int_size(degrees2); + + sum1 = 0; + for (i = 0; i < n1; ++i) { + igraph_int_t d = VECTOR(*degrees1)[i]; + + if (d < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + + sum1 += d; + } + + sum2 = 0; + for (i = 0; i < n2; ++i) { + igraph_int_t d = VECTOR(*degrees2)[i]; + + if (d < 0) { + *res = false; + return IGRAPH_SUCCESS; + } + + sum2 += d; + } + + *res = (sum1 == sum2); + + return IGRAPH_SUCCESS; +} + + +/* Bipartite simple graph: + * - Degrees must be non-negative. + * - Sum of degrees must be the same in the two partitions. + * - Use the Gale-Ryser theorem. + */ +static igraph_error_t igraph_i_is_bigraphical_simple(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res) { + igraph_int_t n1 = igraph_vector_int_size(degrees1), n2 = igraph_vector_int_size(degrees2); + igraph_vector_int_t deg_freq1, deg_freq2; + igraph_int_t lhs_sum, partial_rhs_sum, partial_rhs_count; + igraph_int_t a, b, k; + + if (n1 == 0 && n2 == 0) { + *res = true; + return IGRAPH_SUCCESS; + } + + /* The conditions from the multigraph case are necessary here as well. */ + IGRAPH_CHECK(igraph_i_is_bigraphical_multi(degrees1, degrees2, res)); + if (! *res) { + return IGRAPH_SUCCESS; + } + + /* Ensure that degrees1 is the shorter vector as a minor optimization: */ + if (n2 < n1) { + const igraph_vector_int_t *tmp; + igraph_int_t n; + + tmp = degrees1; + degrees1 = degrees2; + degrees2 = tmp; + + n = n1; + n1 = n2; + n2 = n; + } + + /* Counting sort the degrees. */ + /* Since the graph is simple, a vertex's degree can be at most the size of the other partition. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(°_freq1, n2+1); + IGRAPH_VECTOR_INT_INIT_FINALLY(°_freq2, n1+1); + + for (igraph_int_t i = 0; i < n1; ++i) { + igraph_int_t degree = VECTOR(*degrees1)[i]; + if (degree > n2) { + *res = false; + goto bigraphical_simple_done; + } + ++VECTOR(deg_freq1)[degree]; + } + for (igraph_int_t i = 0; i < n2; ++i) { + igraph_int_t degree = VECTOR(*degrees2)[i]; + if (degree > n1) { + *res = false; + goto bigraphical_simple_done; + } + ++VECTOR(deg_freq2)[degree]; + } + + /* + * We follow the description of the Gale-Ryser theorem in: + * + * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014). + * https://doi.org/10.1016/j.disc.2013.09.010 + * + * Gale-Ryser condition with 0-based indexing: + * + * a_i and b_i denote the degree sequences of the two partitions. + * + * Assuming that a_0 >= a_1 >= ... >= a_{n_1 - 1}, + * + * \sum_{i=0}^k a_i <= \sum_{j=0}^{n_2} min(b_i, k+1) + * + * for all 0 <= k < n_1. + * + * Additionally, based on Theorem 3 in [Berger 2014], it is sufficient to do the check + * for k such that a_k > a_{k+1} and for k=(n_1-1). + */ + + /* While this formulation does not require sorting degree2, + * doing so allows for a linear-time incremental computation + * of the inequality's right-hand-side. + */ + + *res = true; /* be optimistic */ + lhs_sum = 0; + partial_rhs_sum = 0; /* the sum of those elements in the rhs which are <= (k+1) */ + partial_rhs_count = 0; /* number of elements in the rhs which are <= (k+1) */ + b = 0; /* index in deg_freq2 */ + k = -1; + for (a = n2; a >= 0; --a) { + igraph_int_t acount = VECTOR(deg_freq1)[a]; + lhs_sum += a * acount; + k += acount; + + while (b <= k + 1) { + igraph_int_t bcount = VECTOR(deg_freq2)[b]; + partial_rhs_sum += b * bcount; + partial_rhs_count += bcount; + + ++b; + } + + /* rhs_sum for a given k is partial_rhs_sum + (n2 - partial_rhs_count) * (k+1) */ + if (lhs_sum > partial_rhs_sum + (n2 - partial_rhs_count) * (k+1) ) { + *res = false; + break; + } + } + +bigraphical_simple_done: + igraph_vector_int_destroy(°_freq1); + igraph_vector_int_destroy(°_freq2); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/graphicality.h b/src/misc/graphicality.h new file mode 100644 index 0000000..eedb6d5 --- /dev/null +++ b/src/misc/graphicality.h @@ -0,0 +1,36 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_MISC_GRAPHICALITY_H +#define IGRAPH_MISC_GRAPHICALITY_H + +#include "igraph_decls.h" +#include "igraph_graphicality.h" + +#define IGRAPH_I_MULTI_EDGES_SW 0x02 /* 010, more than one edge allowed between distinct vertices */ +#define IGRAPH_I_MULTI_LOOPS_SW 0x04 /* 100, more than one self-loop allowed on the same vertex */ + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_edge_type_to_loops_multiple( + igraph_edge_type_sw_t allowed_edge_type, + igraph_bool_t *loops, igraph_bool_t *multiple); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_MISC_GRAPHICALITY_H */ diff --git a/src/misc/matching.c b/src/misc/matching.c new file mode 100644 index 0000000..6b8e179 --- /dev/null +++ b/src/misc/matching.c @@ -0,0 +1,1013 @@ +/* + igraph library. + Copyright (C) 2012 Tamas Nepusz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_matching.h" + +#include "igraph_adjlist.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_structural.h" + +#include + +/* #define MATCHING_DEBUG */ + +#ifdef MATCHING_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif + +/** + * \function igraph_is_matching + * Checks whether the given matching is valid for the given graph. + * + * This function checks a matching vector and verifies whether its length + * matches the number of vertices in the given graph, its values are between + * -1 (inclusive) and the number of vertices (exclusive), and whether there + * exists a corresponding edge in the graph for every matched vertex pair. + * For bipartite graphs, it also verifies whether the matched vertices are + * in different parts of the graph. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types If the graph is bipartite and you are interested in bipartite + * matchings only, pass the vertex types here. If the graph is + * non-bipartite, simply pass \c NULL. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param result Pointer to a boolean variable, the result will be returned + * here. + * + * \sa \ref igraph_is_maximal_matching() if you are also interested in whether + * the matching is maximal (i.e. non-extendable). + * + * Time complexity: O(|V|+|E|) where |V| is the number of vertices and + * |E| is the number of edges. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +igraph_error_t igraph_is_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, const igraph_vector_int_t *matching, + igraph_bool_t *result) { + igraph_int_t i, j, no_of_nodes = igraph_vcount(graph); + igraph_bool_t conn; + + /* Checking match vector length */ + if (igraph_vector_int_size(matching) != no_of_nodes) { + *result = false; return IGRAPH_SUCCESS; + } + + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + + /* Checking range of each element in the match vector */ + if (j < -1 || j >= no_of_nodes) { + *result = false; return IGRAPH_SUCCESS; + } + /* When i is unmatched, we're done */ + if (j == -1) { + continue; + } + /* Matches must be mutual */ + if (VECTOR(*matching)[j] != i) { + *result = false; return IGRAPH_SUCCESS; + } + /* Matched vertices must be connected */ + IGRAPH_CHECK(igraph_are_adjacent(graph, i, + j, &conn)); + if (!conn) { + /* Try the other direction -- for directed graphs */ + IGRAPH_CHECK(igraph_are_adjacent(graph, j, + i, &conn)); + if (!conn) { + *result = false; return IGRAPH_SUCCESS; + } + } + } + + if (types) { + /* Matched vertices must be of different types */ + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + if (j == -1) { + continue; + } + if (VECTOR(*types)[i] == VECTOR(*types)[j]) { + *result = false; return IGRAPH_SUCCESS; + } + } + } + + *result = true; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_maximal_matching + * \brief Checks whether a matching in a graph is maximal. + * + * A matching is maximal if and only if there exists no unmatched vertex in a + * graph such that one of its neighbors is also unmatched. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types If the graph is bipartite and you are interested in bipartite + * matchings only, pass the vertex types here. If the graph is + * non-bipartite, simply pass \c NULL. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param result Pointer to a boolean variable, the result will be returned + * here. + * \return Error code. + * + * \sa \ref igraph_is_matching() if you are only interested in whether a + * matching vector is valid for a given graph. + * + * Time complexity: O(|V|+|E|) where |V| is the number of vertices and + * |E| is the number of edges. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +igraph_error_t igraph_is_maximal_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, const igraph_vector_int_t *matching, + igraph_bool_t *result) { + + igraph_int_t i, j, n, no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t neis; + igraph_bool_t valid; + + IGRAPH_CHECK(igraph_is_matching(graph, types, matching, &valid)); + if (!valid) { + *result = false; return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + valid = true; + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + if (j != -1) { + continue; + } + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, i, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (j = 0; j < n; j++) { + if (VECTOR(*matching)[VECTOR(neis)[j]] == -1) { + if (types == NULL || + VECTOR(*types)[i] != VECTOR(*types)[VECTOR(neis)[j]]) { + valid = false; break; + } + } + } + } + + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + *result = valid; + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_maximum_bipartite_matching_unweighted( + const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_vector_int_t *matching); + +static igraph_error_t igraph_i_maximum_bipartite_matching_weighted( + const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_real_t *matching_weight, igraph_vector_int_t *matching, + const igraph_vector_t *weights, igraph_real_t eps); + +#define MATCHED(v) (VECTOR(match)[v] != -1) +#define UNMATCHED(v) (!MATCHED(v)) + +/** + * \function igraph_maximum_bipartite_matching + * Calculates a maximum matching in a bipartite graph. + * + * A matching in a bipartite graph is a partial assignment of vertices + * of the first kind to vertices of the second kind such that each vertex of + * the first kind is matched to at most one vertex of the second kind and + * vice versa, and matched vertices must be connected by an edge in the graph. + * The size (or cardinality) of a matching is the number of edges. + * A matching is a maximum matching if there exists no other matching with + * larger cardinality. For weighted graphs, a maximum matching is a matching + * whose edges have the largest possible total weight among all possible + * matchings. + * + * + * Maximum matchings in bipartite graphs are found by the push-relabel algorithm + * with greedy initialization and a global relabeling after every n/2 steps where + * n is the number of vertices in the graph. + * + * + * References: Cherkassky BV, Goldberg AV, Martin P, Setubal JC and Stolfi J: + * Augment or push: A computational study of bipartite matching and + * unit-capacity flow algorithms. ACM Journal of Experimental Algorithmics 3, + * 1998. + * + * + * Kaya K, Langguth J, Manne F and Ucar B: Experiments on push-relabel-based + * maximum cardinality matching algorithms for bipartite graphs. Technical + * Report TR/PA/11/33 of the Centre Europeen de Recherche et de Formation + * Avancee en Calcul Scientifique, 2011. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types Boolean vector giving the vertex types of the graph. + * \param matching_size The size of the matching (i.e. the number of matched + * vertex pairs will be returned here). It may be \c NULL + * if you don't need this. + * \param matching_weight The weight of the matching if the edges are weighted, + * or the size of the matching again if the edges are + * unweighted. It may be \c NULL if you don't need this. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param weights A null pointer (=no edge weights), or a vector giving the + * weights of the edges. Note that the algorithm is stable + * only for integer weights. + * \param eps A small real number used in equality tests in the weighted + * bipartite matching algorithm. Two real numbers are considered + * equal in the algorithm if their difference is smaller than + * \c eps. This is required to avoid the accumulation of numerical + * errors. It is advised to pass a value derived from the + * \c DBL_EPSILON constant in \c float.h here. If you are + * running the algorithm with no \c weights vector, this argument + * is ignored. + * \return Error code. + * + * Time complexity: O(sqrt(|V|) |E|) for unweighted graphs (according to the + * technical report referenced above), O(|V||E|) for weighted graphs. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +igraph_error_t igraph_maximum_bipartite_matching(const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_real_t *matching_weight, igraph_vector_int_t *matching, + const igraph_vector_t *weights, igraph_real_t eps) { + + /* Sanity checks */ + if (igraph_vector_bool_size(types) < igraph_vcount(graph)) { + IGRAPH_ERROR("types vector too short", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) < igraph_ecount(graph)) { + IGRAPH_ERROR("weights vector too short", IGRAPH_EINVAL); + } + + if (weights == 0) { + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted(graph, types, + matching_size, matching)); + if (matching_weight != 0) { + *matching_weight = *matching_size; + } + return IGRAPH_SUCCESS; + } else { + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_weighted(graph, types, + matching_size, matching_weight, matching, weights, eps)); + return IGRAPH_SUCCESS; + } +} + +static igraph_error_t igraph_i_maximum_bipartite_matching_unweighted_relabel( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_vector_int_t* labels, + igraph_vector_int_t* matching, igraph_bool_t smaller_set); + +/** + * Finding maximum bipartite matchings on bipartite graphs using the + * push-relabel algorithm. + * + * The implementation follows the pseudocode in Algorithm 1 of the + * following paper: + * + * Kaya K, Langguth J, Manne F and Ucar B: Experiments on push-relabel-based + * maximum cardinality matching algorithms for bipartite graphs. Technical + * Report TR/PA/11/33 of CERFACS (Centre Européen de Recherche et de Formation + * Avancée en Calcul Scientifique). + * http://www.cerfacs.fr/algor/reports/2011/TR_PA_11_33.pdf + */ +static igraph_error_t igraph_i_maximum_bipartite_matching_unweighted( + const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_vector_int_t *matching) { + igraph_int_t i, j, k, n, no_of_nodes = igraph_vcount(graph); + igraph_int_t num_matched; /* number of matched vertex pairs */ + igraph_vector_int_t match; /* will store the matching */ + igraph_vector_int_t labels; /* will store the labels */ + igraph_vector_int_t neis; /* used to retrieve the neighbors of a node */ + igraph_dqueue_int_t q; /* a FIFO for push ordering */ + igraph_bool_t smaller_set; /* denotes which part of the bipartite graph is smaller */ + igraph_int_t label_changed = 0; /* Counter to decide when to run a global relabeling */ + igraph_int_t relabeling_freq = no_of_nodes / 2; + + /* We will use: + * - FIFO push ordering + * - global relabeling frequency: n/2 steps where n is the number of nodes + * - simple greedy matching for initialization + */ + + /* (1) Initialize data structures */ + IGRAPH_CHECK(igraph_vector_int_init(&match, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &match); + IGRAPH_VECTOR_INT_INIT_FINALLY(&labels, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + + /* (2) Initially, every node is unmatched */ + igraph_vector_int_fill(&match, -1); + + /* (3) Find an initial matching in a greedy manner. + * At the same time, find which side of the graph is smaller. */ + num_matched = 0; j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i]) { + j++; + } + if (MATCHED(i)) { + continue; + } + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, i, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (j = 0; j < n; j++) { + k = VECTOR(neis)[j]; + if (VECTOR(*types)[k] == VECTOR(*types)[i]) { + IGRAPH_ERROR("Graph is not bipartite with supplied types vector", IGRAPH_EINVAL); + } + if (UNMATCHED(k)) { + /* We match vertex i to vertex VECTOR(neis)[j] */ + VECTOR(match)[k] = i; + VECTOR(match)[i] = k; + num_matched++; + break; + } + } + } + smaller_set = (j <= no_of_nodes / 2); + + /* (4) Set the initial labeling -- lines 1 and 2 in the tech report */ + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted_relabel( + graph, types, &labels, &match, smaller_set)); + + /* (5) Fill the push queue with the unmatched nodes from the smaller set. */ + for (i = 0; i < no_of_nodes; i++) { + if (UNMATCHED(i) && VECTOR(*types)[i] == smaller_set) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + } + } + + /* (6) Main loop from the referenced tech report -- lines 4--13 */ + label_changed = 0; + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t v = igraph_dqueue_int_pop(&q); /* Line 13 */ + igraph_int_t u = -1, label_u = 2 * no_of_nodes; + igraph_int_t w; + + if (label_changed >= relabeling_freq) { + /* Run global relabeling */ + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted_relabel( + graph, types, &labels, &match, smaller_set)); + label_changed = 0; + } + + debug("Considering vertex %ld\n", v); + + /* Line 5: find row u among the neighbors of v s.t. label(u) is minimal */ + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (i = 0; i < n; i++) { + if (VECTOR(labels)[VECTOR(neis)[i]] < label_u) { + u = VECTOR(neis)[i]; + label_u = VECTOR(labels)[u]; + label_changed++; + } + } + + debug(" Neighbor with smallest label: %ld (label=%ld)\n", u, label_u); + + if (label_u < no_of_nodes) { /* Line 6 */ + VECTOR(labels)[v] = VECTOR(labels)[u] + 1; /* Line 7 */ + if (MATCHED(u)) { /* Line 8 */ + w = VECTOR(match)[u]; + debug(" Vertex %ld is matched to %ld, performing a double push\n", u, w); + if (w != v) { + VECTOR(match)[u] = -1; VECTOR(match)[w] = -1; /* Line 9 */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, w)); /* Line 10 */ + debug(" Unmatching & activating vertex %ld\n", w); + num_matched--; + } + } + VECTOR(match)[u] = v; VECTOR(match)[v] = u; /* Line 11 */ + num_matched++; + VECTOR(labels)[u] += 2; /* Line 12 */ + label_changed++; + } + } + + /* Fill the output parameters */ + if (matching != 0) { + IGRAPH_CHECK(igraph_vector_int_update(matching, &match)); + } + if (matching_size != 0) { + *matching_size = num_matched; + } + + /* Release everything */ + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(&labels); + igraph_vector_int_destroy(&match); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_maximum_bipartite_matching_unweighted_relabel( + const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_vector_int_t *labels, + igraph_vector_int_t *match, igraph_bool_t smaller_set) { + igraph_int_t i, j, n, no_of_nodes = igraph_vcount(graph), matched_to; + igraph_dqueue_int_t q; + igraph_vector_int_t neis; + + debug("Running global relabeling.\n"); + + /* Set all the labels to no_of_nodes first */ + igraph_vector_int_fill(labels, no_of_nodes); + + /* Allocate vector for neighbors */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + /* Create a FIFO for the BFS and initialize it with the unmatched rows + * (i.e. members of the larger set) */ + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] != smaller_set && VECTOR(*match)[i] == -1) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + VECTOR(*labels)[i] = 0; + } + } + + /* Run the BFS */ + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t v = igraph_dqueue_int_pop(&q); + igraph_int_t w; + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, v, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + + n = igraph_vector_int_size(&neis); + for (j = 0; j < n; j++) { + w = VECTOR(neis)[j]; + if (VECTOR(*labels)[w] == no_of_nodes) { + VECTOR(*labels)[w] = VECTOR(*labels)[v] + 1; + matched_to = VECTOR(*match)[w]; + if (matched_to != -1 && VECTOR(*labels)[matched_to] == no_of_nodes) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, matched_to)); + VECTOR(*labels)[matched_to] = VECTOR(*labels)[w] + 1; + } + } + } + } + + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * Finding maximum bipartite matchings on bipartite graphs using the + * Hungarian algorithm (a.k.a. Kuhn-Munkres algorithm). + * + * The algorithm uses a maximum cardinality matching on a subset of + * tight edges as a starting point. This is achieved by + * \c igraph_i_maximum_bipartite_matching_unweighted on the restricted + * graph. + * + * The algorithm works reliably only if the weights are integers. The + * \c eps parameter should specity a very small number; if the slack on + * an edge falls below \c eps, it will be considered tight. If all your + * weights are integers, you can safely set \c eps to zero. + */ +static igraph_error_t igraph_i_maximum_bipartite_matching_weighted( + const igraph_t *graph, + const igraph_vector_bool_t *types, igraph_int_t *matching_size, + igraph_real_t *matching_weight, igraph_vector_int_t *matching, + const igraph_vector_t *weights, igraph_real_t eps) { + igraph_int_t i, j, k, n, no_of_nodes, no_of_edges; + igraph_int_t u, v, w, msize; + igraph_t newgraph; + igraph_vector_int_t match; /* will store the matching */ + igraph_vector_t slack; /* will store the slack on each edge */ + igraph_vector_int_t parent; /* parent vertices during a BFS */ + igraph_vector_int_t vec1, vec2; /* general temporary vectors */ + igraph_vector_t labels; /* will store the labels */ + igraph_dqueue_int_t q; /* a FIFO for BST */ + igraph_bool_t smaller_set_type; /* denotes which part of the bipartite graph is smaller */ + igraph_vector_t smaller_set; /* stores the vertex IDs of the smaller set */ + igraph_vector_t larger_set; /* stores the vertex IDs of the larger set */ + igraph_int_t smaller_set_size; /* size of the smaller set */ + igraph_int_t larger_set_size; /* size of the larger set */ + igraph_real_t dual; /* solution of the dual problem */ + IGRAPH_UNUSED(dual); /* We mark it as unused to prevent warnings about unused-but-set-variables. */ + igraph_adjlist_t tight_phantom_edges; /* adjacency list to manage tight phantom edges */ + igraph_int_t alternating_path_endpoint; + igraph_vector_int_t* neis; + igraph_vector_int_t *neis2; + igraph_inclist_t inclist; /* incidence list of the original graph */ + + /* The Hungarian algorithm is originally for complete bipartite graphs. + * For non-complete bipartite graphs, a phantom edge of weight zero must be + * added between every pair of non-connected vertices. We don't do this + * explicitly of course. See the comments below about how phantom edges + * are taken into account. */ + + no_of_nodes = igraph_vcount(graph); + no_of_edges = igraph_ecount(graph); + if (eps < 0) { + IGRAPH_WARNING("negative epsilon given, clamping to zero"); + eps = 0; + } + + /* (1) Initialize data structures */ + IGRAPH_CHECK(igraph_vector_int_init(&match, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &match); + IGRAPH_CHECK(igraph_vector_init(&slack, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_destroy, &slack); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vec2, 0); + IGRAPH_VECTOR_INIT_FINALLY(&labels, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + IGRAPH_VECTOR_INT_INIT_FINALLY(&parent, no_of_nodes); + IGRAPH_CHECK(igraph_adjlist_init_empty(&tight_phantom_edges, + no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &tight_phantom_edges); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_VECTOR_INIT_FINALLY(&smaller_set, 0); + IGRAPH_VECTOR_INIT_FINALLY(&larger_set, 0); + + /* (2) Find which set is the smaller one */ + j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == 0) { + j++; + } + } + smaller_set_type = (j > no_of_nodes / 2); + smaller_set_size = smaller_set_type ? (no_of_nodes - j) : j; + larger_set_size = no_of_nodes - smaller_set_size; + IGRAPH_CHECK(igraph_vector_reserve(&smaller_set, smaller_set_size)); + IGRAPH_CHECK(igraph_vector_reserve(&larger_set, larger_set_size)); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == smaller_set_type) { + IGRAPH_CHECK(igraph_vector_push_back(&smaller_set, i)); + } else { + IGRAPH_CHECK(igraph_vector_push_back(&larger_set, i)); + } + } + + /* (3) Calculate the initial labeling and the set of tight edges. Use the + * smaller set only. Here we can assume that there are no phantom edges + * among the tight ones. */ + dual = 0; + for (i = 0; i < no_of_nodes; i++) { + igraph_real_t max_weight = 0; + + if (VECTOR(*types)[i] != smaller_set_type) { + VECTOR(labels)[i] = 0; + continue; + } + + neis = igraph_inclist_get(&inclist, i); + n = igraph_vector_int_size(neis); + for (j = 0, k = 0; j < n; j++) { + k = VECTOR(*neis)[j]; + u = IGRAPH_OTHER(graph, k, i); + if (VECTOR(*types)[u] == VECTOR(*types)[i]) { + IGRAPH_ERROR("Graph is not bipartite with supplied types vector", IGRAPH_EINVAL); + } + if (VECTOR(*weights)[k] > max_weight) { + max_weight = VECTOR(*weights)[k]; + } + } + + VECTOR(labels)[i] = max_weight; + dual += max_weight; + } + + igraph_vector_int_clear(&vec1); + IGRAPH_CHECK(igraph_get_edgelist(graph, &vec2, 0)); +#define IS_TIGHT(i) (VECTOR(slack)[i] <= eps) + for (i = 0, j = 0; i < no_of_edges; i++, j += 2) { + u = VECTOR(vec2)[j]; + v = VECTOR(vec2)[j + 1]; + VECTOR(slack)[i] = VECTOR(labels)[u] + VECTOR(labels)[v] - VECTOR(*weights)[i]; + if (IS_TIGHT(i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&vec1, u)); + IGRAPH_CHECK(igraph_vector_int_push_back(&vec1, v)); + } + } + igraph_vector_int_clear(&vec2); + + /* (4) Construct a temporary graph on which the initial maximum matching + * will be calculated (only on the subset of tight edges) */ + IGRAPH_CHECK(igraph_create(&newgraph, &vec1, + no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_maximum_bipartite_matching(&newgraph, types, &msize, 0, &match, 0, 0)); + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + /* (5) Main loop until the matching becomes maximal */ + while (msize < smaller_set_size) { + igraph_real_t min_slack, min_slack_2; + igraph_int_t min_slack_u, min_slack_v; + + /* mark min_slack_u as unused; it is actually used when debugging, but + * gcc complains when we are not debugging */ + IGRAPH_UNUSED(min_slack_u); + + /* (7) Fill the push queue with the unmatched nodes from the smaller set. */ + igraph_vector_int_clear(&vec1); + igraph_vector_int_clear(&vec2); + igraph_vector_int_fill(&parent, -1); + for (j = 0; j < smaller_set_size; j++) { + i = VECTOR(smaller_set)[j]; + if (UNMATCHED(i)) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + VECTOR(parent)[i] = i; + IGRAPH_CHECK(igraph_vector_int_push_back(&vec1, i)); + } + } + +#ifdef MATCHING_DEBUG + debug("Matching:"); + igraph_vector_int_print(&match); + debug("Unmatched vertices are marked by non-negative numbers:\n"); + igraph_vector_print(&parent); + debug("Labeling:"); + igraph_vector_print(&labels); + debug("Slacks:"); + igraph_vector_print(&slack); +#endif + + /* (8) Run the BFS */ + alternating_path_endpoint = -1; + while (!igraph_dqueue_int_empty(&q)) { + v = igraph_dqueue_int_pop(&q); + + debug("Considering vertex %ld\n", v); + + /* v is always in the smaller set. Find the neighbors of v, which + * are all in the larger set. Find the pairs of these nodes in + * the smaller set and push them to the queue. Mark the traversed + * nodes as seen. + * + * Here we have to be careful as there are two types of incident + * edges on v: real edges and phantom ones. Real edges are + * given by igraph_inclist_get. Phantom edges are not given so we + * (ab)use an adjacency list data structure that lists the + * vertices connected to v by phantom edges only. */ + neis = igraph_inclist_get(&inclist, v); + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + j = VECTOR(*neis)[i]; + /* We only care about tight edges */ + if (!IS_TIGHT(j)) { + continue; + } + /* Have we seen the other endpoint already? */ + u = IGRAPH_OTHER(graph, j, v); + if (VECTOR(parent)[u] >= 0) { + continue; + } + debug(" Reached vertex %" IGRAPH_PRId " via edge %" IGRAPH_PRId "\n", u, j); + VECTOR(parent)[u] = v; + IGRAPH_CHECK(igraph_vector_int_push_back(&vec2, u)); + w = VECTOR(match)[u]; + if (w == -1) { + /* u is unmatched and it is in the larger set. Therefore, we + * could improve the matching by following the parents back + * from u to the root. + */ + alternating_path_endpoint = u; + break; /* since we don't need any more endpoints that come from v */ + } else { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, w)); + VECTOR(parent)[w] = u; + } + IGRAPH_CHECK(igraph_vector_int_push_back(&vec1, w)); + } + + /* Now do the same with the phantom edges */ + neis2 = igraph_adjlist_get(&tight_phantom_edges, v); + n = igraph_vector_int_size(neis2); + for (i = 0; i < n; i++) { + u = VECTOR(*neis2)[i]; + /* Have we seen u already? */ + if (VECTOR(parent)[u] >= 0) { + continue; + } + /* Check if the edge is really tight; it might have happened that the + * edge became non-tight in the meanwhile. We do not remove these from + * tight_phantom_edges at the moment, so we check them once again here. + */ + if (fabs(VECTOR(labels)[v] + VECTOR(labels)[u]) > eps) { + continue; + } + debug(" Reached vertex %" IGRAPH_PRId " via tight phantom edge\n", u); + VECTOR(parent)[u] = v; + IGRAPH_CHECK(igraph_vector_int_push_back(&vec2, u)); + w = VECTOR(match)[u]; + if (w == -1) { + /* u is unmatched and it is in the larger set. Therefore, we + * could improve the matching by following the parents back + * from u to the root. + */ + alternating_path_endpoint = u; + break; /* since we don't need any more endpoints that come from v */ + } else { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, w)); + VECTOR(parent)[w] = u; + } + IGRAPH_CHECK(igraph_vector_int_push_back(&vec1, w)); + } + } + + /* Okay; did we have an alternating path? */ + if (alternating_path_endpoint != -1) { +#ifdef MATCHING_DEBUG + debug("BFS parent tree:"); + igraph_vector_print(&parent); +#endif + /* Increase the size of the matching with the alternating path. */ + v = alternating_path_endpoint; + u = VECTOR(parent)[v]; + debug("Extending matching with alternating path ending in %ld.\n", v); + + while (u != v) { + w = VECTOR(match)[v]; + if (w != -1) { + VECTOR(match)[w] = -1; + } + VECTOR(match)[v] = u; + + VECTOR(match)[v] = u; + w = VECTOR(match)[u]; + if (w != -1) { + VECTOR(match)[w] = -1; + } + VECTOR(match)[u] = v; + + v = VECTOR(parent)[u]; + u = VECTOR(parent)[v]; + } + + msize++; + +#ifdef MATCHING_DEBUG + debug("New matching after update:"); + igraph_vector_int_print(&match); + debug("Matching size is now: %" IGRAPH_PRId "\n", msize); +#endif + continue; + } + +#ifdef MATCHING_DEBUG + debug("Vertices reachable from unmatched ones via tight edges:\n"); + igraph_vector_int_print(&vec1); + igraph_vector_print(&vec2); +#endif + + /* At this point, vec1 contains the nodes in the smaller set (A) + * reachable from unmatched nodes in A via tight edges only, while vec2 + * contains the nodes in the larger set (B) reachable from unmatched + * nodes in A via tight edges only. Also, parent[i] >= 0 if node i + * is reachable */ + + /* Check the edges between reachable nodes in A and unreachable + * nodes in B, and find the minimum slack on them. + * + * Since the weights are positive, we do no harm if we first + * assume that there are no "real" edges between the two sets + * mentioned above and determine an upper bound for min_slack + * based on this. */ + min_slack = IGRAPH_INFINITY; + min_slack_u = min_slack_v = 0; + n = igraph_vector_int_size(&vec1); + for (j = 0; j < larger_set_size; j++) { + i = VECTOR(larger_set)[j]; + if (VECTOR(labels)[i] < min_slack) { + min_slack = VECTOR(labels)[i]; + min_slack_v = i; + } + } + min_slack_2 = IGRAPH_INFINITY; + for (i = 0; i < n; i++) { + u = VECTOR(vec1)[i]; + /* u is surely from the smaller set, but we are interested in it + * only if it is reachable from an unmatched vertex */ + if (VECTOR(parent)[u] < 0) { + continue; + } + if (VECTOR(labels)[u] < min_slack_2) { + min_slack_2 = VECTOR(labels)[u]; + min_slack_u = u; + } + } + min_slack += min_slack_2; + debug("Starting approximation for min_slack = %.4f (based on vertex pair %ld--%ld)\n", + min_slack, min_slack_u, min_slack_v); + + n = igraph_vector_int_size(&vec1); + for (i = 0; i < n; i++) { + u = VECTOR(vec1)[i]; + /* u is a reachable node in A; get its incident edges. + * + * There are two types of incident edges: 1) real edges, + * 2) phantom edges. Phantom edges were treated earlier + * when we determined the initial value for min_slack. */ + debug("Trying to expand along vertex %" IGRAPH_PRId "\n", u); + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + /* v is the vertex sitting at the other end of an edge incident + * on u; check whether it was reached */ + v = IGRAPH_OTHER(graph, VECTOR(*neis)[j], u); + debug(" Edge %" IGRAPH_PRId " -- %" IGRAPH_PRId " (ID=%" IGRAPH_PRId ")\n", u, v, VECTOR(*neis)[j]); + if (VECTOR(parent)[v] >= 0) { + /* v was reached, so we are not interested in it */ + debug(" %" IGRAPH_PRId " was reached, so we are not interested in it\n", v); + continue; + } + /* v is the ID of the edge from now on */ + v = VECTOR(*neis)[j]; + if (VECTOR(slack)[v] < min_slack) { + min_slack = VECTOR(slack)[v]; + min_slack_u = u; + min_slack_v = IGRAPH_OTHER(graph, v, u); + } + debug(" Slack of this edge: %.4f, min slack is now: %.4f\n", + VECTOR(slack)[v], min_slack); + } + } + debug("Minimum slack: %.4f on edge %" IGRAPH_PRId "--%" IGRAPH_PRId "\n", min_slack, min_slack_u, min_slack_v); + + if (min_slack > 0) { + /* Decrease the label of reachable nodes in A by min_slack. + * Also update the dual solution */ + n = igraph_vector_int_size(&vec1); + for (i = 0; i < n; i++) { + u = VECTOR(vec1)[i]; + VECTOR(labels)[u] -= min_slack; + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + debug(" Decreasing slack of edge %" IGRAPH_PRId " (%" IGRAPH_PRId "--%" IGRAPH_PRId ") by %.4f\n", + VECTOR(*neis)[j], u, + IGRAPH_OTHER(graph, VECTOR(*neis)[j], u), min_slack); + VECTOR(slack)[VECTOR(*neis)[j]] -= min_slack; + } + dual -= min_slack; + } + + /* Increase the label of reachable nodes in B by min_slack. + * Also update the dual solution */ + n = igraph_vector_int_size(&vec2); + for (i = 0; i < n; i++) { + u = VECTOR(vec2)[i]; + VECTOR(labels)[u] += min_slack; + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + debug(" Increasing slack of edge %" IGRAPH_PRId " (%" IGRAPH_PRId"--%" IGRAPH_PRId ") by %.4f\n", + VECTOR(*neis)[j], u, + IGRAPH_OTHER(graph, VECTOR(*neis)[j], u), min_slack); + VECTOR(slack)[VECTOR(*neis)[j]] += min_slack; + } + dual += min_slack; + } + } + + /* Update the set of tight phantom edges. + * Note that we must do it even if min_slack is zero; the reason is that + * it can happen that min_slack is zero in the first step if there are + * isolated nodes in the input graph. + * + * TODO: this is O(n^2) here. Can we do it faster? */ + for (i = 0; i < smaller_set_size; i++) { + u = VECTOR(smaller_set)[i]; + for (j = 0; j < larger_set_size; j++) { + v = VECTOR(larger_set)[j]; + if (VECTOR(labels)[u] + VECTOR(labels)[v] <= eps) { + /* Tight phantom edge found. Note that we don't have to check whether + * u and v are connected; if they were, then the slack of this edge + * would be negative. */ + neis2 = igraph_adjlist_get(&tight_phantom_edges, u); + if (!igraph_vector_int_binsearch(neis2, v, &k)) { + debug("New tight phantom edge: %" IGRAPH_PRId " -- %" IGRAPH_PRId "\n", u, v); + IGRAPH_CHECK(igraph_vector_int_insert(neis2, k, v)); + } + } + } + } + +#ifdef MATCHING_DEBUG + debug("New labels:"); + igraph_vector_print(&labels); + debug("Slacks after updating with min_slack:"); + igraph_vector_print(&slack); +#endif + } + + /* Cleanup: remove phantom edges from the matching */ + for (i = 0; i < smaller_set_size; i++) { + u = VECTOR(smaller_set)[i]; + v = VECTOR(match)[u]; + if (v != -1) { + neis2 = igraph_adjlist_get(&tight_phantom_edges, u); + if (igraph_vector_int_binsearch(neis2, v, 0)) { + VECTOR(match)[u] = VECTOR(match)[v] = -1; + msize--; + } + } + } + + /* Fill the output parameters */ + if (matching != 0) { + IGRAPH_CHECK(igraph_vector_int_update(matching, &match)); + } + if (matching_size != 0) { + *matching_size = msize; + } + if (matching_weight != 0) { + *matching_weight = 0; + for (i = 0; i < no_of_edges; i++) { + if (IS_TIGHT(i)) { + IGRAPH_CHECK(igraph_edge(graph, i, &u, &v)); + if (VECTOR(match)[u] == v) { + *matching_weight += VECTOR(*weights)[i]; + } + } + } + } + + /* Release everything */ +#undef IS_TIGHT + igraph_vector_destroy(&larger_set); + igraph_vector_destroy(&smaller_set); + igraph_inclist_destroy(&inclist); + igraph_adjlist_destroy(&tight_phantom_edges); + igraph_vector_int_destroy(&parent); + igraph_dqueue_int_destroy(&q); + igraph_vector_destroy(&labels); + igraph_vector_int_destroy(&vec1); + igraph_vector_int_destroy(&vec2); + igraph_vector_destroy(&slack); + igraph_vector_int_destroy(&match); + IGRAPH_FINALLY_CLEAN(11); + + return IGRAPH_SUCCESS; +} + +#ifdef MATCHING_DEBUG + #undef MATCHING_DEBUG +#endif diff --git a/src/misc/mixing.c b/src/misc/mixing.c new file mode 100644 index 0000000..dffc763 --- /dev/null +++ b/src/misc/mixing.c @@ -0,0 +1,1008 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_mixing.h" + +#include "igraph_interface.h" +#include "igraph_structural.h" + +/** + * \function igraph_assortativity_nominal + * \brief Assortativity of a graph based on vertex categories. + * + * Assuming the vertices of the input graph belong to different + * categories, this function calculates the assortativity coefficient of + * the graph. The assortativity coefficient is between minus one and one + * and it is one if all connections stay within categories, it is + * minus one, if the network is perfectly disassortative. For a + * randomly connected network it is (asymptotically) zero. + * + * + * The unnormalized version, computed when \p normalized is set to false, + * is identical to the modularity, and is defined as follows for + * directed networks: + * + * 1/m sum_ij (A_ij - k^out_i k^in_j / m) d(i,j), + * + * where \c m denotes the number of edges, \c A_ij is the adjacency matrix, + * k^out and k^in are the out- and in-degrees, + * and d(i,j) is one if vertices \c i and \c j are in the same + * category and zero otherwise. + * + * + * The normalized assortativity coefficient is obtained by dividing the + * previous expression by + * + * 1/m sum_ij (m - k^out_i k^in_j d(i,j) / m). + * + * It can take any value within the interval [-1, 1]. + * + * + * Undirected graphs are effectively treated as directed ones with all-reciprocal + * edges. Thus, self-loops are taken into account twice in undirected graphs. + * + * + * References: + * + * + * M. E. J. Newman: Mixing patterns in networks, + * Phys. Rev. E 67, 026126 (2003) + * https://doi.org/10.1103/PhysRevE.67.026126. + * See section II and equation (2) for the definition of the concept. + * + * + * For an educational overview of assortativity, see + * M. E. J. Newman, + * Networks: An Introduction, Oxford University Press (2010). + * https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001. + * + * \param graph The input graph, it can be directed or undirected. + * \param weights Weighted nominal assortativity is not currently implemented. + * Pass \c NULL to ignore. + * \param types Integer vector giving the vertex categories. The types + * are represented by integers starting at zero. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, it gives whether to consider edge + * directions in a directed graph. It is ignored for undirected + * graphs. + * \param normalized Boolean, whether to compute the usual normalized + * assortativity. The unnormalized version is identical to + * modularity. Supply true here to compute the standard assortativity. + * \return Error code. + * + * Time complexity: O(|E|+t), |E| is the number of edges, t is the + * number of vertex types. + * + * \sa \ref igraph_assortativity() for computing the assortativity + * based on continuous vertex values instead of discrete categories. + * \ref igraph_modularity() to compute generalized modularity. + * \ref igraph_joint_type_distribution() to obtain the mixing matrix. + * + * \example examples/simple/igraph_assortativity_nominal.c + */ + +igraph_error_t igraph_assortativity_nominal( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_int_t *types, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_real_t no_of_edges_real = (igraph_real_t) no_of_edges; /* for divisions */ + igraph_int_t no_of_types; + igraph_vector_int_t ai, bi, eii; + igraph_real_t sumaibi = 0.0, sumeii = 0.0; + + if (weights) { + IGRAPH_ERROR("Weighted nominal assortativity is not yet implemented.", + IGRAPH_UNIMPLEMENTED); + } + + if (igraph_vector_int_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid types vector length.", IGRAPH_EINVAL); + } + + if (no_of_nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + /* 'types' length > 0 here, safe to call vector_min() */ + if (igraph_vector_int_min(types) < 0) { + IGRAPH_ERROR("Vertex types must not be negative.", IGRAPH_EINVAL); + } + + directed = directed && igraph_is_directed(graph); + + no_of_types = igraph_vector_int_max(types) + 1; + IGRAPH_VECTOR_INT_INIT_FINALLY(&ai, no_of_types); + IGRAPH_VECTOR_INT_INIT_FINALLY(&bi, no_of_types); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eii, no_of_types); + + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_int_t from_type = VECTOR(*types)[from]; + igraph_int_t to_type = VECTOR(*types)[to]; + + VECTOR(ai)[from_type] += 1; + VECTOR(bi)[to_type] += 1; + if (from_type == to_type) { + VECTOR(eii)[from_type] += 1; + } + if (!directed) { + if (from_type == to_type) { + VECTOR(eii)[from_type] += 1; + } + VECTOR(ai)[to_type] += 1; + VECTOR(bi)[from_type] += 1; + } + } + + for (igraph_int_t i = 0; i < no_of_types; i++) { + sumaibi += (VECTOR(ai)[i] / no_of_edges_real) * (VECTOR(bi)[i] / no_of_edges_real); + sumeii += (VECTOR(eii)[i] / no_of_edges_real); + } + + if (!directed) { + sumaibi /= 4.0; + sumeii /= 2.0; + } + + if (normalized) { + *res = (sumeii - sumaibi) / (1.0 - sumaibi); + } else { + *res = (sumeii - sumaibi); + } + + igraph_vector_int_destroy(&eii); + igraph_vector_int_destroy(&bi); + igraph_vector_int_destroy(&ai); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_assortativity + * \brief Assortativity based on numeric properties of vertices. + * + * This function calculates the assortativity coefficient of a + * graph based on given values \c x_i for each vertex \c i. This type of + * assortativity coefficient equals the Pearson correlation of the values + * at the two ends of the edges. + * + * + * The unnormalized covariance of values, computed when \p normalized is + * set to false, is defined as follows in a directed graph: + * + * cov(x_out, x_in) = 1/m sum_ij (A_ij - k^out_i k^in_j / m) x_i x_j, + * + * where \c m denotes the number of edges, \c A_ij is the adjacency matrix, and + * k^out and k^in are the out- and in-degrees. + * \c x_out and \c x_in refer to the sets of vertex values at the start and end of + * the directed edges. + * + * + * The normalized covariance, i.e. Pearson correlation, is obtained by dividing + * the previous expression by + * sqrt(var(x_out)) sqrt(var(x_in)), where + * + * var(x_out) = 1/m sum_i k^out_i x_i^2 - (1/m sum_i k^out_i x_i^2)^2 + * + * var(x_in) = 1/m sum_j k^in_j x_j^2 - (1/m sum_j k^in_j x_j^2)^2 + * + * + * Undirected graphs are effectively treated as directed graphs where all edges + * are reciprocal. Therefore, self-loops are effectively considered twice in + * undirected graphs. + * + * + * When edge weights are given, they are effectively treated as edge multiplicities. + * The above formulas are valid for weighted graph as well when \c m is interpreted + * as the total edge weight (instead of the edge count) and \c k as vertex strengths + * (instead of degrees). + * + * + * References: + * + * + * M. E. J. Newman: Mixing patterns + * in networks, Phys. Rev. E 67, 026126 (2003) + * https://doi.org/10.1103/PhysRevE.67.026126. + * See section III and equation (21) for the definition, and equation (26) for + * performing the calculation in directed graphs with the degrees as values. + * + * + * M. E. J. Newman: Assortative mixing in networks, + * Phys. Rev. Lett. 89, 208701 (2002) + * https://doi.org/10.1103/PhysRevLett.89.208701. + * See equation (4) for performing the calculation in undirected + * graphs with the degrees as values. + * + * + * For an educational overview of the concept of assortativity, see + * M. E. J. Newman, + * Networks: An Introduction, Oxford University Press (2010). + * https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001. + * + * \param graph The input graph, it can be directed or undirected. + * \param weights The edge weights. Pass \c NULL to compute unweighed + * assortativity, which in effect assumes all weights to be 1. + * \param values The vertex values, these can be arbitrary numeric + * values. + * \param values_in A second value vector to be used for the incoming + * edges when calculating assortativity for a directed graph. + * Supply \c NULL here if you want to use the same values + * for outgoing and incoming edges. This argument is ignored + * (with a warning) if it is not a null pointer and the undirected + * assortativity coefficient is being calculated. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, whether to consider edge directions for + * directed graphs. It is ignored for undirected graphs. + * \param normalized Boolean, whether to compute the normalized + * covariance, i.e. Pearson correlation. Supply true here to + * compute the standard assortativity. + * \return Error code. + * + * Time complexity: O(|E|), linear in the number of edges of the + * graph. + * + * \sa \ref igraph_assortativity_nominal() if you have discrete vertex + * categories instead of numeric labels, and \ref + * igraph_assortativity_degree() for the special case of assortativity + * based on vertex degrees. + */ + +igraph_error_t igraph_assortativity( + const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_t *values, const igraph_vector_t *values_in, + igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t normalized) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_real_t total_weight; + + directed = directed && igraph_is_directed(graph); + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (!directed && values_in) { + IGRAPH_WARNING( + "Incoming vertex values are ignored when calculating undirected assortativity."); + } + + if (igraph_vector_size(values) != no_of_nodes) { + IGRAPH_ERROR("Invalid vertex values vector length.", IGRAPH_EINVAL); + } + + if (values_in && igraph_vector_size(values_in) != no_of_nodes) { + IGRAPH_ERROR("Invalid incoming vertex values vector length.", IGRAPH_EINVAL); + } + + total_weight = weights ? igraph_vector_sum(weights) : no_of_edges; + + if (!directed) { + igraph_real_t num1 = 0.0, num2 = 0.0, den1 = 0.0; + + if (weights) { + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_real_t from_value = VECTOR(*values)[from]; + igraph_real_t to_value = VECTOR(*values)[to]; + igraph_real_t w = VECTOR(*weights)[e]; + + num1 += w * from_value * to_value; + num2 += w * (from_value + to_value); + if (normalized) { + den1 += w * (from_value * from_value + to_value * to_value); + } + } + } else { + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_real_t from_value = VECTOR(*values)[from]; + igraph_real_t to_value = VECTOR(*values)[to]; + + num1 += from_value * to_value; + num2 += from_value + to_value; + if (normalized) { + den1 += from_value * from_value + to_value * to_value; + } + } + } + + num1 /= total_weight; + if (normalized) { + den1 /= total_weight * 2.0; + } + num2 /= total_weight * 2.0; + num2 = num2 * num2; + + if (normalized) { + *res = (num1 - num2) / (den1 - num2); + } else { + *res = (num1 - num2); + } + + } else { + igraph_real_t num1 = 0.0, num2 = 0.0, num3 = 0.0, + den1 = 0.0, den2 = 0.0; + igraph_real_t num, den; + + if (!values_in) { + values_in = values; + } + + if (weights) { + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_real_t from_value = VECTOR(*values)[from]; + igraph_real_t to_value = VECTOR(*values_in)[to]; + igraph_real_t w = VECTOR(*weights)[e]; + + num1 += w * from_value * to_value; + num2 += w * from_value; + num3 += w * to_value; + if (normalized) { + den1 += w * (from_value * from_value); + den2 += w * (to_value * to_value); + } + } + } else { + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_real_t from_value = VECTOR(*values)[from]; + igraph_real_t to_value = VECTOR(*values_in)[to]; + + num1 += from_value * to_value; + num2 += from_value; + num3 += to_value; + if (normalized) { + den1 += from_value * from_value; + den2 += to_value * to_value; + } + } + } + + num = num1 - num2 * num3 / total_weight; + if (normalized) { + den = sqrt(den1 - num2 * num2 / total_weight) * + sqrt(den2 - num3 * num3 / total_weight); + + *res = num / den; + } else { + *res = num / total_weight; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_assortativity_degree + * \brief Assortativity of a graph based on vertex degree. + * + * Assortativity based on vertex degree, please see the discussion at + * the documentation of \ref igraph_assortativity() for details. + * This function simply calls \ref igraph_assortativity() with + * the degrees as the vertex values and normalization enabled. + * In the directed case, it uses out-degrees as out-values and + * in-degrees as in-values. + * + * + * For regular graphs, i.e. graphs in which all vertices have the + * same degree, computing degree correlations is not meaningful, + * and this function returns NaN. + * + * \param graph The input graph, it can be directed or undirected. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, whether to consider edge directions for + * directed graphs. This argument is ignored for undirected + * graphs. Supply true here to do the natural thing, i.e. use + * directed version of the measure for directed graphs and the + * undirected version for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|+|V|), |E| is the number of edges, |V| is + * the number of vertices. + * + * \sa \ref igraph_assortativity() for the general function + * calculating assortativity for any kind of numeric vertex values, + * and \ref igraph_joint_degree_distribution() to get the complete + * joint degree distribution. + * + * \example examples/simple/igraph_assortativity_degree.c + */ + +igraph_error_t igraph_assortativity_degree(const igraph_t *graph, + igraph_real_t *res, + igraph_bool_t directed) { + + directed = directed && igraph_is_directed(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + /* This function uses igraph_strength() instead of igraph_degree() in order to obtain + * a vector of reals instead of a vector of integers. */ + if (directed) { + igraph_vector_t indegree, outdegree; + IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, NULL)); + IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, NULL)); + IGRAPH_CHECK(igraph_assortativity(graph, NULL, &outdegree, &indegree, res, /* directed */ true, /* normalized */ true)); + igraph_vector_destroy(&indegree); + igraph_vector_destroy(&outdegree); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_vector_t degree; + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, NULL)); + IGRAPH_CHECK(igraph_assortativity(graph, NULL, °ree, 0, res, /* directed */ false, /* normalized */ true)); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_joint_degree_matrix + * \brief The joint degree matrix of a graph. + * + * In graph theory, the joint degree matrix \c J_ij of a graph gives the number + * of edges, or sum of edge weights, between vertices of degree \c i and degree + * \c j. This function stores \c J_ij into jdm[i-1, j-1]. + * Each edge, including self-loops, is counted precisely once, both in undirected + * and directed graphs. + * + * + * sum_(i,j) J_ij is the total number of edges (or total edge weight) + * \c m in the graph, where (i,j) refers to ordered or unordered + * pairs in directed and undirected graphs, respectively. Thus J_ij / m + * is the probability that an edge chosen at random (with probability proportional + * to its weight) connects vertices with degrees \c i and \c j. + * + * + * Note that \c J_ij is similar, but not identical to the joint degree + * \em distribution, computed by \ref igraph_joint_degree_distribution(), + * which is defined for \em ordered (i, j) degree + * pairs even in the undirected case. When considering undirected graphs, the + * diagonal of the joint degree distribution is twice that of the joint + * degree matrix. + * + * + * References: + * + * + * Isabelle Stanton and Ali Pinar: + * Constructing and sampling graphs with a prescribed joint degree distribution. + * ACM J. Exp. Algorithmics 17, Article 3.5 (2012). + * https://doi.org/10.1145/2133803.2330086 + * + * \param graph A pointer to an initialized graph object. + * \param weights A vector containing the weights of the edges. If passing a + * \c NULL pointer, edges will be assumed to have unit weights, i.e. + * the matrix entries will be connection counts. + * \param jdm A pointer to an initialized matrix that will be resized. The values + * will be written here. + * \param max_out_degree Number of rows in the result, i.e. the largest (out-)degree + * to consider. If negative or \ref IGRAPH_UNLIMITED, the largest (out-)degree + * of the graph will be used. + * \param max_in_degree Number of columns in the result, i.e. the largest (in-)degree + * to consider. If negative or \ref IGRAPH_UNLIMITED, the largest (in-)degree + * of the graph will be used. + * \return Error code. + * + * \sa \ref igraph_joint_degree_distribution() to count ordered vertex pairs instead of + * edges, or to obtain a normalized matrix. + * + * Time complexity: O(E), where E is the number of edges in input graph. + */ + +igraph_error_t igraph_joint_degree_matrix( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *jdm, + igraph_int_t max_out_degree, igraph_int_t max_in_degree) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_eit_t eit; + igraph_int_t eid; + igraph_int_t v1id; + igraph_int_t v2id; + igraph_int_t v1deg; + igraph_int_t v2deg; + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (igraph_is_directed(graph)) { + igraph_vector_int_t out_degrees; + igraph_vector_int_t in_degrees; + + // Compute max degrees + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_degrees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degrees, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, &out_degrees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph, &in_degrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); + + if (max_out_degree < 0) { + max_out_degree = no_of_nodes > 0 ? igraph_vector_int_max(&out_degrees) : 0; + } + + if (max_in_degree < 0) { + max_in_degree = no_of_nodes > 0 ? igraph_vector_int_max(&in_degrees) : 0; + } + + IGRAPH_CHECK(igraph_matrix_resize(jdm, max_out_degree, max_in_degree)); + igraph_matrix_null(jdm); + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + eid = IGRAPH_EIT_GET(eit); + v1id = IGRAPH_FROM(graph, eid); + v2id = IGRAPH_TO(graph, eid); + v1deg = VECTOR(out_degrees)[v1id]; + v2deg = VECTOR(in_degrees)[v2id]; + if (v1deg <= max_out_degree && v2deg <= max_in_degree) { + MATRIX(*jdm, v1deg-1, v2deg-1) += weights ? VECTOR(*weights)[eid] : 1; + } + } + + igraph_eit_destroy(&eit); + igraph_vector_int_destroy(&in_degrees); + igraph_vector_int_destroy(&out_degrees); + IGRAPH_FINALLY_CLEAN(3); + + } else { + igraph_vector_int_t degrees; + igraph_int_t maxdeg; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + // Compute max degree of the graph only if needed + if (max_out_degree < 0 || max_in_degree < 0) { + maxdeg = no_of_nodes > 0 ? igraph_vector_int_max(°rees) : 0; + } + + if (max_out_degree < 0) { + max_out_degree = maxdeg; + } + if (max_in_degree < 0) { + max_in_degree = maxdeg; + } + + IGRAPH_CHECK(igraph_matrix_resize(jdm, max_out_degree, max_in_degree)); + igraph_matrix_null(jdm); + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + while (!IGRAPH_EIT_END(eit)) { + eid = IGRAPH_EIT_GET(eit); + v1id = IGRAPH_FROM(graph, eid); + v2id = IGRAPH_TO(graph, eid); + v1deg = VECTOR(degrees)[v1id]; + v2deg = VECTOR(degrees)[v2id]; + + // Undirected JDMs are symmetrical, needs to be accounted for this when indexing. + if (v1deg <= max_out_degree && v2deg <= max_in_degree) { + MATRIX(*jdm, v1deg-1, v2deg-1) += weights ? VECTOR(*weights)[eid] : 1; + } + // Do not double-count connections between same-degree vertices. + if (v1deg != v2deg && v2deg <= max_out_degree && v1deg <= max_in_degree) { + MATRIX(*jdm, v2deg-1, v1deg-1) += weights ? VECTOR(*weights)[eid] : 1; + } + + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(2); + } + + return IGRAPH_SUCCESS; +} + +/** + * Common implementation for igraph_joint_type_distribution() and igraph_joint_degree_distribution() + * + * For the max_from/to_type parameters, negative values mean "automatic". These are used + * only with igraph_joint_degree_distribution(). + * + * check_types controls whether types should be validated to be non-negative. Validation + * is only necessary with igraph_joint_type_distribution() but not with igraph_joint_degree_distribution(). + * + * directed_neighbors must NOT be true when the graph is undirected. + */ +static igraph_error_t mixing_matrix( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, + igraph_bool_t directed_neighbors, igraph_bool_t normalized, + igraph_int_t max_from_type, igraph_int_t max_to_type, + igraph_bool_t check_types) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t nrow, ncol; + igraph_real_t sum; + igraph_bool_t negative_weight; + + if (igraph_vector_int_size(from_types) != no_of_nodes) { + IGRAPH_ERROR("Length of 'from' type vector must agree with vertex count.", IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(to_types) != no_of_nodes) { + IGRAPH_ERROR("Length of 'to' type vector must agree with vertex count.", IGRAPH_EINVAL); + } + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (max_from_type < 0) { + if (no_of_nodes == 0) { + nrow = 0; + } else { + nrow = igraph_vector_int_max(from_types) + 1; + } + } else { + nrow = max_from_type + 1; + } + + if (max_to_type < 0) { + if (no_of_nodes == 0) { + ncol = 0; + } else if (to_types == from_types) { + /* Avoid computing the maximum again if target vertex types + * are the same as source vertex types. */ + ncol = nrow; + } else { + ncol = igraph_vector_int_max(to_types) + 1; + } + } else { + ncol = max_to_type + 1; + } + + if (check_types && no_of_nodes > 0) { + igraph_int_t min; + + min = igraph_vector_int_min(from_types); + if (min < 0) { + IGRAPH_ERROR("Invalid source vertex type.", IGRAPH_EINVAL); + } + + if (to_types != from_types) { + min = igraph_vector_int_min(from_types); + if (min < 0) { + IGRAPH_ERROR("Invalid target vertex type.", IGRAPH_EINVAL); + } + } + } + + IGRAPH_CHECK(igraph_matrix_resize(p, nrow, ncol)); + igraph_matrix_null(p); + + sum = 0; + negative_weight = false; + for (igraph_int_t eid=0; eid < no_of_edges; eid++) { + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + igraph_int_t from_type = VECTOR(*from_types)[from]; + igraph_int_t to_type = VECTOR(*to_types)[to]; + igraph_real_t w = weights ? VECTOR(*weights)[eid] : 1; + + if (from_type >= nrow || to_type >= ncol) { + continue; + } + + MATRIX(*p, from_type, to_type) += w; + sum += w; + + if (! directed_neighbors) { + MATRIX(*p, to_type, from_type) += w; + sum += w; + } + + if (w < 0) { + negative_weight = true; + } + } + + if (normalized) { + if (negative_weight) { + /* When some edge weights are negative, they cannot be interpreted as sampling weights, + * and the sum of weights may be zero, potentially leading to Inf/NaN results. */ + IGRAPH_WARNING("Negative edge weights are present. Normalization may not be meaningful."); + } + if (no_of_edges > 0) { + /* Scale only when there are some edges, thus 'sum' can be non-zero. */ + igraph_matrix_scale(p, 1.0 / sum); + } + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_joint_degree_distribution + * \brief The joint degree distribution of a graph. + * + * Computes the joint degree distribution \c P_ij of a graph, used in the + * study of degree correlations. \c P_ij is the probability that a randomly + * chosen ordered pair of \em connected vertices have degrees \c i and \c j. + * + * + * In directed graphs, directionally connected u -> v pairs + * are considered. The joint degree distribution of an undirected graph is the + * same as that of the corresponding directed graph in which all connection are + * bidirectional, assuming that \p from_mode is \c IGRAPH_OUT, \p to_mode is + * \c IGRAPH_IN and \p directed_neighbors is true. + * + * + * When \p normalized is false, sum_ij P_ij gives the total + * number of connections in a directed graph, or twice that value in an + * undirected graph. The sum is taken over ordered (i,j) degree + * pairs. + * + * + * The joint degree distribution relates to other concepts used in the study of + * degree correlations. If \c P_ij is normalized then the degree correlation + * function k_nn(k) is obtained as + * + * + * k_nn(k) = (sum_j j P_kj) / (sum_j P_kj). + * + * + * The non-normalized degree assortativity is obtained as + * + * + * a = sum_ij i j (P_ij - q_i r_j), + * + * + * where q_i = sum_k P_ik and r_j = sum_k P_kj. + * + * + * Note that the joint degree distribution \c P_ij is similar, but not identical + * to the joint degree matrix \c J_ij computed by \ref igraph_joint_degree_matrix(). + * If the graph is undirected, then the diagonal entries of an unnormalized \c P_ij + * are double that of \c J_ij, as any undirected connection between same-degree vertices + * is counted in both directions. In contrast to \ref igraph_joint_degree_matrix(), + * this function returns matrices which include the row and column corresponding + * to zero degrees. In directed graphs, this row and column is not necessarily + * zero when \p from_mode is different from \c IGRAPH_OUT or \p to_mode is different + * from \c IGRAPH_IN. + * + * + * References: + * + * + * M. E. J. Newman: Mixing patterns in networks, + * Phys. Rev. E 67, 026126 (2003) + * https://doi.org/10.1103/PhysRevE.67.026126. + * + * \param graph A pointer to an initialized graph object. + * \param weights A vector containing the weights of the edges. If passing a + * \c NULL pointer, edges will be assumed to have unit weights. + * \param p A pointer to an initialized matrix that will be resized. The \c P_ij + * value will be written into p[i,j]. + * \param from_mode How to compute the degree of sources? Can be \c IGRAPH_OUT + * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. + * Ignored in undirected graphs. + * \param to_mode How to compute the degree of targets? Can be \c IGRAPH_OUT + * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. + * Ignored in undirected graphs. + * \param directed_neighbors Whether to consider u -> v connections + * to be directed. Undirected connections are treated as reciprocal directed ones, + * i.e. both u -> v and v -> u will be considered. + * Ignored in undirected graphs. + * \param normalized Whether to normalize the matrix so that entries sum to 1.0. + * If false, matrix entries will be connection counts. Normalization is not + * meaningful if some edge weights are negative. + * \param max_from_degree The largest source vertex degree to consider. If + * negative or \ref IGRAPH_UNLIMITED, the largest source degree will be used. + * The row count of the result matrix is one larger than this value. + * \param max_to_degree The largest target vertex degree to consider. If + * negative or \ref IGRAPH_UNLIMITED, the largest target degree will be used. + * The column count of the result matrix is one larger than this value. + * \return Error code. + * + * \sa \ref igraph_joint_degree_matrix() for computing the joint degree matrix; + * \ref igraph_assortativity_degree() and \ref igraph_assortativity() for + * degree correlations coefficients, and \ref igraph_degree_correlation_vector() + * for the degree correlation function. + * + * Time complexity: O(E), where E is the number of edges in the input graph. + */ +igraph_error_t igraph_joint_degree_distribution( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors, + igraph_bool_t normalized, + igraph_int_t max_from_degree, igraph_int_t max_to_degree) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t *deg_from, *deg_to, deg_out, deg_in, deg_all; + + /* Make sure directionality parameters are consistent for undirected graphs. */ + if (! igraph_is_directed(graph)) { + from_mode = to_mode = IGRAPH_ALL; + directed_neighbors = false; + } + + igraph_bool_t have_out = from_mode == IGRAPH_OUT || to_mode == IGRAPH_OUT; + igraph_bool_t have_in = from_mode == IGRAPH_IN || to_mode == IGRAPH_IN; + igraph_bool_t have_all = from_mode == IGRAPH_ALL || to_mode == IGRAPH_ALL; + + if (have_out) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_out, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_out, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); + } + + if (have_in) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_in, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_in, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); + } + + if (have_all) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_all, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_all, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + } + + switch (from_mode) { + case IGRAPH_OUT: deg_from = °_out; break; + case IGRAPH_IN: deg_from = °_in; break; + case IGRAPH_ALL: deg_from = °_all; break; + default: + IGRAPH_ERROR("Invalid 'from' degree mode.", IGRAPH_EINVMODE); + } + + switch (to_mode) { + case IGRAPH_OUT: deg_to = °_out; break; + case IGRAPH_IN: deg_to = °_in; break; + case IGRAPH_ALL: deg_to = °_all; break; + default: + IGRAPH_ERROR("Invalid 'to' degree mode.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(mixing_matrix(graph, + weights, p, + deg_from, deg_to, + directed_neighbors, normalized, + max_from_degree, max_to_degree, + /*check_types=*/ false)); + + if (have_all) { + igraph_vector_int_destroy(°_all); + IGRAPH_FINALLY_CLEAN(1); + } + + if (have_in) { + igraph_vector_int_destroy(°_in); + IGRAPH_FINALLY_CLEAN(1); + } + + if (have_out) { + igraph_vector_int_destroy(°_out); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_joint_type_distribution + * \brief Mixing matrix for vertex categories. + * + * Computes the mixing matrix M_ij, i.e. the joint distribution of vertex types + * at the endpoints directed of edges. Categories are represented by non-negative integer + * indices, passed in \p from_types and \p to_types. The row and column counts of \p m + * will be one larger than the largest source and target type, respectively. Re-index type + * vectors using \ref igraph_reindex_membership() if they are not contiguous integers, + * to avoid producing a very large matrix. + * + * + * M_ij is proportional to the probability that a randomly chosen ordered pair of vertices + * have types \c i and \c j. + * + * + * When there is a single categorization of vertices, i.e. \p from_types and \p to_types + * are the same, M_ij is related to the modularity (\ref igraph_modularity()) and nominal + * assortativity (\ref igraph_assortativity_nominal()). Let a_i = sum_j M_ij and + * b_j = sum_i M_ij. If M_ij is normalized, i.e. sum_ij M_ij = 1, + * and the types represent membership in vertex partitions, then the modularity of the + * partitioning can be computed as + * + * + * Q = sum_ii M_ii - sum_i a_i b_i + * + * + * The normalized nominal assortativity is + * + * + * Q / (1 - sum_i a_i b_i) + * + * + * \ref igraph_joint_degree_distribution() is a special case of this function, with + * categories consisting vertices of the same degree. + * + * + * References: + * + * + * M. E. J. Newman: Mixing patterns in networks, + * Phys. Rev. E 67, 026126 (2003) + * https://doi.org/10.1103/PhysRevE.67.026126. + * + * \param graph The input graph. + * \param weights A vector containing the weights of the edges. If passing a + * \c NULL pointer, edges will be assumed to have unit weights. + * \param p The mixing matrix \c M_ij will be stored here. + * \param from_types Vertex types for source vertices. These must be non-negative integers. + * \param to_types Vertex types for target vertices. These must be non-negative integers. + * If \c NULL, it is assumed to be the same as \p from_types. + * \param directed Whether to treat edges are directed. Ignored for undirected graphs. + * \param normalized Whether to normalize the matrix so that entries sum to 1.0. + * If false, matrix entries will be connection counts. Normalization is not + * meaningful if some edge weights are negative. + * \return Error code. + * + * \sa \ref igraph_joint_degree_distribution() to compute the joint distribution + * of vertex degrees; \ref igraph_modularity() to compute the modularity of + * a vertex partitioning; \ref igraph_assortativity_nominal() to compute + * assortativity based on vertex categories. + * + * Time complexity: O(E), where E is the number of edges in the input graph. + */ +igraph_error_t igraph_joint_type_distribution( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_matrix_t *p, + const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, + igraph_bool_t directed, igraph_bool_t normalized) { + + IGRAPH_ASSERT(from_types != NULL); + if (to_types == NULL) { + to_types = from_types; + } + if (! igraph_is_directed(graph)) { + directed = false; + } + return mixing_matrix(graph, weights, p, from_types, to_types, directed, normalized, -1, -1, true); +} diff --git a/src/misc/motifs.c b/src/misc/motifs.c new file mode 100644 index 0000000..d12e16c --- /dev/null +++ b/src/misc/motifs.c @@ -0,0 +1,1223 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_motifs.h" + +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_nongraph.h" +#include "igraph_stack.h" + +#include "core/interruption.h" +#include "isomorphism/isoclasses.h" +#include "graph/internal.h" + +/** + * Callback function for igraph_motifs_randesu that counts the motifs by + * isomorphism class in a histogram. + */ +static igraph_error_t igraph_i_motifs_randesu_update_hist( + const igraph_t *graph, + const igraph_vector_int_t *vids, + igraph_int_t isoclass, void* extra) { + + igraph_vector_t *hist = (igraph_vector_t*)extra; + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(vids); + VECTOR(*hist)[isoclass]++; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_motifs_randesu + * \brief Count the number of motifs in a graph. + * + * + * Motifs are small weakly connected induced subgraphs of a given structure in a + * graph. It is argued that the motif profile (i.e. the number of + * different motifs in the graph) is characteristic for different + * types of networks and network function is related to the motifs in + * the graph. + * + * + * This function is able to find directed motifs of sizes three + * and four and undirected motifs of sizes three to six + * (i.e. the number of different subgraphs with three to six + * vertices in the network). + * + * + * In a big network the total number of motifs can be very large, so + * it takes a lot of time to find all of them. In this case, a sampling + * method can be used. This function is capable of doing sampling via the + * \p cut_prob argument. This argument gives the probability that + * a branch of the motif search tree will not be explored. See + * S. Wernicke and F. Rasche: FANMOD: a tool for fast network motif + * detection, Bioinformatics 22(9), 1152--1153, 2006 for details. + * https://doi.org/10.1093/bioinformatics/btl038 + * + * + * Set the \p cut_prob argument to a zero vector for finding all + * motifs. + * + * + * Directed motifs will be counted in directed graphs and undirected + * motifs in undirected graphs. + * + * \param graph The graph to find the motifs in. + * \param hist The result of the computation, it gives the number of + * motifs found for each isomorphism class. See + * \ref igraph_isoclass() for help about isomorphism classes. + * Note that this function does \em not count isomorphism + * classes that are not connected and will report NaN (more + * precisely \c IGRAPH_NAN) for them. + * \param size The size of the motifs to search for. For directed graphs, + * only 3 and 4 are implemented, for undirected, 3 to 6. + * The limitation is not in the motif finding code, but the graph + * isomorphism code. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * To perform a complete search and find all motifs, supply + * either an all-zero vector of length \p size, or (since + * igraph 0.10.14) a \c NULL pointer. + * \return Error code. + * + * \sa \ref igraph_motifs_randesu_estimate() for estimating the number + * of motifs in a graph, this can help to set the \p cut_prob + * parameter; \ref igraph_motifs_randesu_no() to calculate the total + * number of motifs of a given size in a graph; + * \ref igraph_motifs_randesu_callback() for calling a callback function + * for every motif found; \ref igraph_subisomorphic_lad() for finding + * subgraphs on more than 4 (directed) or 6 (undirected) vertices; + * \ref igraph_graph_count() to find the number of graph on a given + * number of vertices, i.e. the length of the \p hist vector. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_motifs_randesu.c + */ +igraph_error_t igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *hist, + igraph_int_t size, const igraph_vector_t *cut_prob) { + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t histlen; + + if (directed) { + switch (size) { + case 3: + histlen = 16; + break; + case 4: + histlen = 218; + break; + default: + IGRAPH_ERROR("In directed graphs, only 3 and 4 vertex motifs are supported.", + IGRAPH_UNIMPLEMENTED); + } + } else { + switch (size) { + case 3: + histlen = 4; + break; + case 4: + histlen = 11; + break; + case 5: + histlen = 34; + break; + case 6: + histlen = 156; + break; + default: + IGRAPH_ERROR("In undirected graphs, only 3 to 6 vertex motifs are supported.", + IGRAPH_UNIMPLEMENTED); + } + } + + if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) { + IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(cut_prob), size); + } + + IGRAPH_CHECK(igraph_vector_resize(hist, histlen)); + igraph_vector_null(hist); + + IGRAPH_CHECK(igraph_motifs_randesu_callback(graph, size, cut_prob, + &igraph_i_motifs_randesu_update_hist, hist)); + + if (size == 3) { + if (directed) { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = VECTOR(*hist)[3] = IGRAPH_NAN; + } else { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = IGRAPH_NAN; + } + } else if (size == 4) { + if (directed) { + const int not_connected[] = { 0, 1, 2, 4, 5, 6, 9, 10, 11, 15, 22, 23, 27, + 28, 33, 34, 39, 62, 120 }; + size_t i, n = sizeof(not_connected) / sizeof(not_connected[0]); + for (i = 0; i < n; i++) { + VECTOR(*hist)[not_connected[i]] = IGRAPH_NAN; + } + } else { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = VECTOR(*hist)[2] = + VECTOR(*hist)[3] = VECTOR(*hist)[5] = IGRAPH_NAN; + } + } else if (size == 5) { + /* undirected only */ + const int not_connected[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 19 }; + size_t i, n = sizeof(not_connected) / sizeof(int); + for (i = 0; i < n; i++) { + VECTOR(*hist)[not_connected[i]] = IGRAPH_NAN; + } + } else if (size == 6) { + /* undirected only */ + const int not_connected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 35, 38, 44, 50, 51, 54, 74, 77, 89, 120}; + size_t i, n = sizeof(not_connected) / sizeof(int); + for (i = 0; i < n; i++) { + VECTOR(*hist)[not_connected[i]] = IGRAPH_NAN; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_motifs_randesu_callback + * \brief Finds motifs in a graph and calls a function for each of them. + * + * + * Similarly to \ref igraph_motifs_randesu(), this function is able to find + * directed motifs of sizes three and four and undirected motifs of sizes + * three to six (i.e. the number of different subgraphs with three to six + * vertices in the network). However, instead of + * counting them, the function will call a callback function for each motif + * found to allow further tests or post-processing. + * + * + * The \p cut_prob argument also allows sampling the motifs, just like for + * \ref igraph_motifs_randesu(). Set the \p cut_prob argument to a zero vector + * for finding all motifs. + * + * \param graph The graph to find the motifs in. + * \param size The size of the motifs to search for. Only three and + * four are implemented currently. The limitation is not in the + * motif finding code, but the graph isomorphism code. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * To perform a complete search and find all motifs, supply + * either an all-zero vector of length \p size, or (since + * igraph 0.10.14) a \c NULL pointer. + * \param callback A pointer to a function of type \ref igraph_motifs_handler_t. + * This function will be called whenever a new motif is found. + * \param extra Extra argument to pass to the callback function. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_motifs_randesu.c + */ + +igraph_error_t igraph_motifs_randesu_callback( + const igraph_t *graph, + igraph_int_t size, const igraph_vector_t *cut_prob, + igraph_motifs_handler_t *callback, void* extra) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_adjlist_t allneis, alloutneis; + igraph_vector_int_t *neis; + igraph_int_t parent; + igraph_int_t i, j, s; + igraph_int_t motifs = 0; + IGRAPH_UNUSED(motifs); /* We mark it as unused to prevent warnings about unused-but-set-variables. */ + + igraph_vector_int_t vids; /* this is G */ + igraph_vector_int_t adjverts; /* this is V_E */ + igraph_stack_int_t stack; /* this is S */ + igraph_int_t *added; + char *subg; + + const unsigned int *arr_idx, *arr_code; + unsigned int code = 0; + unsigned int mul, idx; + + igraph_bool_t terminate = false; + + if (igraph_is_directed(graph)) { + switch (size) { + case 3: + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + mul = 4; + break; + default: + IGRAPH_ERROR("In directed graphs, only 3 and 4 vertex motifs are supported.", + IGRAPH_UNIMPLEMENTED); + } + } else { + switch (size) { + case 3: + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + mul = 3; + break; + case 4: + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + mul = 4; + break; + case 5: + arr_idx = igraph_i_isoclass_5u_idx; + arr_code = igraph_i_isoclass2_5u; + mul = 5; + break; + case 6: + arr_idx = igraph_i_isoclass_6u_idx; + arr_code = igraph_i_isoclass2_6u; + mul = 6; + break; + default: + IGRAPH_ERROR("In undirected graphs, only 3 to 6 vertex motifs are supported.", + IGRAPH_UNIMPLEMENTED); + } + } + + if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) { + IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(cut_prob), size); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to find motifs."); + IGRAPH_FINALLY(igraph_free, added); + + subg = IGRAPH_CALLOC(no_of_nodes, char); + IGRAPH_CHECK_OOM(subg, "Insufficient memory to find motifs."); + IGRAPH_FINALLY(igraph_free, subg); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + IGRAPH_CHECK(igraph_adjlist_init(graph, &alloutneis, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &alloutneis); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + + for (parent = 0; parent < no_of_nodes; parent++) { + igraph_int_t level; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (cut_prob) { + if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + } + + /* init G */ + igraph_vector_int_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, parent)); + subg[parent] = 1; added[parent] += 1; level += 1; + + /* init V_E */ + igraph_vector_int_clear(&adjverts); + neis = igraph_adjlist_get(&allneis, parent); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + if (!added[nei] && nei > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, parent)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_int_clear(&stack); + + while (level > 1 || !igraph_vector_int_empty(&adjverts)) { + igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0; + + if (level == size - 1) { + s = igraph_vector_int_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + igraph_int_t k, s2; + igraph_int_t last; + igraph_error_t ret; + + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + motifs += 1; + + last = VECTOR(adjverts)[2 * i]; + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, last)); + subg[last] = (char) size; + + code = 0; idx = 0; + for (k = 0; k < size; k++) { + igraph_int_t from = VECTOR(vids)[k]; + neis = igraph_adjlist_get(&alloutneis, from); + s2 = igraph_vector_int_size(neis); + for (j = 0; j < s2; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (subg[nei] && k != subg[nei] - 1) { + idx = (unsigned char) (mul * k + (subg[nei] - 1)); + code |= arr_idx[idx]; + } + } + } + + IGRAPH_CHECK_CALLBACK( + callback(graph, &vids, arr_code[code], extra), + &ret + ); + + if (ret == IGRAPH_STOP) { + terminate = true; + break; + } + + igraph_vector_int_pop_back(&vids); + subg[last] = 0; + } + } + + /* did the callback function asked us to terminate the search? */ + if (terminate) { + break; + } + + /* can we step down? */ + if (level < size - 1 && + !igraph_vector_int_empty(&adjverts)) { + /* we might step down */ + igraph_int_t neiparent = igraph_vector_int_pop_back(&adjverts); + igraph_int_t nei = igraph_vector_int_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* yes, step down */ + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, nei)); + subg[nei] = (char) level + 1; added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_int_push(&stack, neiparent)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, level)); + + neis = igraph_adjlist_get(&allneis, nei); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + igraph_int_t nei2 = VECTOR(*neis)[i]; + if (!added[nei2] && nei2 > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + igraph_int_t nei, neiparent; + while (!igraph_stack_int_empty(&stack) && + level == igraph_stack_int_top(&stack) - 1) { + igraph_stack_int_pop(&stack); + nei = igraph_stack_int_pop(&stack); + neiparent = igraph_stack_int_pop(&stack); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, neiparent)); + } + + nei = igraph_vector_int_pop_back(&vids); + subg[nei] = 0; added[nei] -= 1; level -= 1; + neis = igraph_adjlist_get(&allneis, nei); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + added[ VECTOR(*neis)[i] ] -= 1; + } + while (!igraph_vector_int_empty(&adjverts) && + igraph_vector_int_tail(&adjverts) == nei) { + igraph_vector_int_pop_back(&adjverts); + igraph_vector_int_pop_back(&adjverts); + } + } + + } /* while */ + + /* did the callback function asked us to terminate the search? */ + if (terminate) { + break; + } + + /* clear the added vector */ + added[parent] -= 1; + subg[parent] = 0; + neis = igraph_adjlist_get(&allneis, parent); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + added[ VECTOR(*neis)[i] ] -= 1; + } + + } /* for parent */ + + IGRAPH_FREE(added); + IGRAPH_FREE(subg); + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&adjverts); + igraph_adjlist_destroy(&alloutneis); + igraph_adjlist_destroy(&allneis); + igraph_stack_int_destroy(&stack); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_motifs_randesu_estimate + * \brief Estimate the total number of motifs in a graph. + * + * This function estimates the total number of (weakly) connected induced + * subgraphs on \p size vertices. For example, an undirected complete graph + * on \c n vertices will have one motif of size \c n, and \c n motifs + * of \p size n - 1. As another example, one triangle + * and a separate vertex will have zero motifs of size four. + * + * + * This function is useful for large graphs for which it is not + * feasible to count all connected subgraphs, as there are too + * many of them. + * + * + * The estimate is made by taking a sample of vertices and counting all + * connected subgraphs in which these vertices are included. There is also + * a \p cut_prob parameter which gives the probabilities to cut a branch of + * the search tree. + * + * \param graph The graph object to study. + * \param est Pointer to an \c igraph_real_t, the result will be stored here. + * Note that even though the result is an integer, we need to use + * \c igraph_real_t to avoid overflow when igraph is compiled with + * 32-bit integers. + * \param size The size of the subgraphs to look for. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * To perform a complete search and find all motifs, supply + * either an all-zero vector of length \p size, or (since + * igraph 0.10.14) a \c NULL pointer. + * \param sample_size The number of vertices to use as the + * sample. This parameter is only used if the \p parsample + * argument is a null pointer. + * \param parsample Either pointer to an initialized vector or a null + * pointer. If a vector then the vertex IDs in the vector are + * used as a sample. If a null pointer then the \p sample_size + * argument is used to create a sample of vertices drawn with + * uniform probability. + * \return Error code. + * + * \sa \ref igraph_motifs_randesu(), \ref igraph_motifs_randesu_no(). + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_real_t *est, + igraph_int_t size, const igraph_vector_t *cut_prob, + igraph_int_t sample_size, + const igraph_vector_int_t *parsample) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t neis; + + igraph_vector_int_t vids; /* this is G */ + igraph_vector_int_t adjverts; /* this is V_E */ + igraph_stack_int_t stack; /* this is S */ + igraph_int_t *added; + igraph_vector_int_t *sample; + igraph_int_t sam; + igraph_int_t i; + + if (size < 3) { + IGRAPH_ERRORF("Motif size must be at least 3, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, size); + } + + if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) { + IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(cut_prob), size); + } + + if (parsample && !igraph_vector_int_isininterval(parsample, 0, no_of_nodes-1)) { + IGRAPH_ERROR("Sample vertex ID out of range.", IGRAPH_EINVVID); + } + + if (no_of_nodes == 0) { + *est = 0; + return IGRAPH_SUCCESS; + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to count motifs."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + if (parsample == NULL) { + sample = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(sample, "Insufficient memory to count motifs."); + IGRAPH_FINALLY(igraph_free, sample); + IGRAPH_VECTOR_INT_INIT_FINALLY(sample, 0); + IGRAPH_CHECK(igraph_random_sample(sample, 0, no_of_nodes - 1, sample_size)); + } else { + sample = (igraph_vector_int_t*) parsample; + sample_size = igraph_vector_int_size(sample); + } + + if (sample_size <= 0) { + IGRAPH_ERRORF("The number of vertices to use as a sample for motif count " + "estimation must be at least one, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, sample_size); + } + + *est = 0; + + for (sam = 0; sam < sample_size; sam++) { + igraph_int_t parent = VECTOR(*sample)[sam]; + igraph_int_t level, s; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (cut_prob) { + if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + } + + /* init G */ + igraph_vector_int_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, parent)); + added[parent] += 1; level += 1; + + /* init V_E */ + igraph_vector_int_clear(&adjverts); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, parent, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + igraph_int_t nei = VECTOR(neis)[i]; + if (!added[nei] && nei > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, parent)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_int_clear(&stack); + + while (level > 1 || !igraph_vector_int_empty(&adjverts)) { + igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0.0; + + if (level == size - 1) { + s = igraph_vector_int_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + (*est) += 1; + } + } + + if (level < size - 1 && + !igraph_vector_int_empty(&adjverts)) { + /* We might step down */ + igraph_int_t neiparent = igraph_vector_int_pop_back(&adjverts); + igraph_int_t nei = igraph_vector_int_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* Yes, step down */ + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, nei)); + added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_int_push(&stack, neiparent)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, level)); + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, nei, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + igraph_int_t nei2 = VECTOR(neis)[i]; + if (!added[nei2] && nei2 > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + igraph_int_t nei, neiparent; + while (!igraph_stack_int_empty(&stack) && + level == igraph_stack_int_top(&stack) - 1) { + igraph_stack_int_pop(&stack); + nei = igraph_stack_int_pop(&stack); + neiparent = igraph_stack_int_pop(&stack); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, neiparent)); + } + + nei = igraph_vector_int_pop_back(&vids); + added[nei] -= 1; level -= 1; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, nei, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + added[ VECTOR(neis)[i] ] -= 1; + } + while (!igraph_vector_int_empty(&adjverts) && + igraph_vector_int_tail(&adjverts) == nei) { + igraph_vector_int_pop_back(&adjverts); + igraph_vector_int_pop_back(&adjverts); + } + } + + } /* while */ + + /* clear the added vector */ + added[parent] -= 1; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, parent, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + added[ VECTOR(neis)[i] ] -= 1; + } + + } /* for parent */ + + (*est) *= ((igraph_real_t) no_of_nodes / sample_size); + + if (parsample == 0) { + igraph_vector_int_destroy(sample); + IGRAPH_FREE(sample); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_FREE(added); + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&adjverts); + igraph_stack_int_destroy(&stack); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(5); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_motifs_randesu_no + * \brief Count the total number of motifs in a graph. + * + * This function counts the total number of (weakly) connected + * induced subgraphs on \p size vertices, without assigning isomorphism + * classes to them. Arbitrarily large motif sizes are supported. + * + * \param graph The graph object to study. + * \param no Pointer to an \c igraph_real_t, the result will be stored here. + * Note that even though the result is an integer, we need to use + * \c igraph_real_t to avoid overflow when igraph is compiled with + * 32-bit integers. + * \param size The size of the motifs to count. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * To perform a complete search and find all connected subgraphs, + * supply either an all-zero vector of length \p size, or (since + * igraph 0.10.14) a \c NULL pointer. + * \return Error code. + * \sa \ref igraph_motifs_randesu(), \ref + * igraph_motifs_randesu_estimate(). + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_motifs_randesu_no( + const igraph_t *graph, igraph_real_t *no, igraph_int_t size, + const igraph_vector_t *cut_prob +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t neis; + igraph_vector_int_t vids; /* this is G */ + igraph_vector_int_t adjverts; /* this is V_E */ + igraph_stack_int_t stack; /* this is S */ + igraph_int_t *added; + igraph_int_t parent; + igraph_int_t i; + + if (size < 3) { + IGRAPH_ERRORF("Motif size must be at least 3, received %" IGRAPH_PRId ".", + IGRAPH_EINVAL, size); + } + + if (cut_prob != NULL && igraph_vector_size(cut_prob) != size) { + IGRAPH_ERRORF("Cut probability vector size (%" IGRAPH_PRId ") must agree with motif size (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(cut_prob), size); + } + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to count motifs."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + *no = 0; + + for (parent = 0; parent < no_of_nodes; parent++) { + igraph_int_t level, s; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (cut_prob) { + if (VECTOR(*cut_prob)[0] == 1 || RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + } + + /* init G */ + igraph_vector_int_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, parent)); + added[parent] += 1; level += 1; + + /* init V_E */ + igraph_vector_int_clear(&adjverts); + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, parent, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + igraph_int_t nei = VECTOR(neis)[i]; + if (!added[nei] && nei > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, parent)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_int_clear(&stack); + + while (level > 1 || !igraph_vector_int_empty(&adjverts)) { + igraph_real_t cp = cut_prob ? VECTOR(*cut_prob)[level] : 0.0; + + if (level == size - 1) { + s = igraph_vector_int_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + (*no) += 1; + } + } + + if (level < size - 1 && + !igraph_vector_int_empty(&adjverts)) { + /* We might step down */ + igraph_int_t neiparent = igraph_vector_int_pop_back(&adjverts); + igraph_int_t nei = igraph_vector_int_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* Yes, step down */ + IGRAPH_CHECK(igraph_vector_int_push_back(&vids, nei)); + added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_int_push(&stack, neiparent)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_int_push(&stack, level)); + + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, nei, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + igraph_int_t nei2 = VECTOR(neis)[i]; + if (!added[nei2] && nei2 > parent) { + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + igraph_int_t nei, neiparent; + while (!igraph_stack_int_empty(&stack) && + level == igraph_stack_int_top(&stack) - 1) { + igraph_stack_int_pop(&stack); + nei = igraph_stack_int_pop(&stack); + neiparent = igraph_stack_int_pop(&stack); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&adjverts, neiparent)); + } + + nei = igraph_vector_int_pop_back(&vids); + added[nei] -= 1; level -= 1; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, nei, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + added[ VECTOR(neis)[i] ] -= 1; + } + while (!igraph_vector_int_empty(&adjverts) && + igraph_vector_int_tail(&adjverts) == nei) { + igraph_vector_int_pop_back(&adjverts); + igraph_vector_int_pop_back(&adjverts); + } + } + + } /* while */ + + /* clear the added vector */ + added[parent] -= 1; + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, parent, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + s = igraph_vector_int_size(&neis); + for (i = 0; i < s; i++) { + added[ VECTOR(neis)[i] ] -= 1; + } + + } /* for parent */ + + IGRAPH_FREE(added); + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&adjverts); + igraph_stack_int_destroy(&stack); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(5); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_dyad_census + * \brief Dyad census, as defined by Holland and Leinhardt. + * + * Dyad census means classifying each pair of vertices of a directed + * graph into three categories: mutual (there is at least one edge from + * \c a to \c b and also from \c b to \c a); asymmetric (there is at least + * one edge either from \c a to \c b or from \c b to \c a, but not the other + * way) and null (no edges between \c a and \c b in either direction). + * + * + * Holland, P.W. and Leinhardt, S. (1970). A Method for Detecting + * Structure in Sociometric Data. American Journal of Sociology, + * 70, 492-513. + * + * \param graph The input graph. For an undirected graph, there are no + * asymmetric connections. + * \param mut Pointer to a real, the number of mutual dyads is + * stored here. + * \param asym Pointer to a real, the number of asymmetric dyads + * is stored here. + * \param null Pointer to a real, the number of null dyads is + * stored here. + * \return Error code. + * + * \sa \ref igraph_reciprocity(), \ref igraph_triad_census(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ +igraph_error_t igraph_dyad_census(const igraph_t *graph, igraph_real_t *mut, + igraph_real_t *asym, igraph_real_t *null) { + + /* This function operates with a floating point type instead of an + * integer type in order to avoid integer overflow, which is likely + * for 'null' in large graphs on 32-bit systems. */ + + igraph_real_t nonrec = 0, rec = 0; + igraph_vector_int_t inneis, outneis; + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t i; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inneis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outneis, 0); + + for (i = 0; i < vc; i++) { + igraph_int_t ideg, odeg; + igraph_int_t ip, op; + + IGRAPH_CHECK(igraph_neighbors(graph, &inneis, i, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_CHECK(igraph_neighbors(graph, &outneis, i, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + + ideg = igraph_vector_int_size(&inneis); + odeg = igraph_vector_int_size(&outneis); + + ip = op = 0; + while (ip < ideg && op < odeg) { + if (VECTOR(inneis)[ip] < VECTOR(outneis)[op]) { + nonrec += 1; + ip++; + } else if (VECTOR(inneis)[ip] > VECTOR(outneis)[op]) { + nonrec += 1; + op++; + } else { + rec += 1; + ip++; + op++; + } + } + nonrec += (ideg - ip) + (odeg - op); + } + + igraph_vector_int_destroy(&inneis); + igraph_vector_int_destroy(&outneis); + IGRAPH_FINALLY_CLEAN(2); + + *mut = rec / 2; + *asym = nonrec / 2; + *null = 0.5 * vc * (vc - 1.0) - (*mut + *asym); + if (*null == 0.0) *null = 0.0; /* avoid returning -0.0 */ + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_triad_census_24(const igraph_t *graph, + igraph_real_t *res2, + igraph_real_t *res4) { + + const igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_int_t seen; + igraph_vector_int_t *neis, *neis2; + igraph_int_t s, neilen, neilen2, ign; + igraph_adjlist_t adjlist; + int iter = 0; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&seen, vcount); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + *res2 = *res4 = 0; + + for (igraph_int_t i = 0; i < vcount; i++) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 12); + + neis = igraph_adjlist_get(&adjlist, i); + neilen = igraph_vector_int_size(neis); + /* mark neighbors of i & i itself */ + VECTOR(seen)[i] = i + 1; + ign = 0; + for (igraph_int_t j = 0; j < neilen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (VECTOR(seen)[nei] == i + 1 || VECTOR(seen)[nei] == -(i + 1)) { + /* multiple edges or loop edge */ + VECTOR(seen)[nei] = -(i + 1); + ign++; + } else { + VECTOR(seen)[nei] = i + 1; + } + } + + for (igraph_int_t j = 0; j < neilen; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (nei <= i || (j > 0 && nei == VECTOR(*neis)[j - 1])) { + continue; + } + neis2 = igraph_adjlist_get(&adjlist, nei); + neilen2 = igraph_vector_int_size(neis2); + s = 0; + for (igraph_int_t k = 0; k < neilen2; k++) { + igraph_int_t nei2 = VECTOR(*neis2)[k]; + if (k > 0 && nei2 == VECTOR(*neis2)[k - 1]) { + continue; + } + if (VECTOR(seen)[nei2] != i + 1 && VECTOR(seen)[nei2] != -(i + 1)) { + s++; + } + } + if (VECTOR(seen)[nei] > 0) { + *res2 += vcount - s - neilen + ign - 1; + } else { + *res4 += vcount - s - neilen + ign - 1; + } + } + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&seen); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_triad_census + * \brief Triad census, as defined by Davis and Leinhardt. + * + * Calculating the triad census means classifying every triple of + * vertices in a directed graph based on the type of pairwise + * connections it contains, i.e. mutual, asymmetric or no connection. + * A triple can be in one of 16 states, commonly described using + * Davis and Leinhardt's "MAN labels". The \p res vector will + * contain the counts of these in the following order: + * + * \clist + * \cli  0: 003 + * A, B, C, the empty graph. + * \cli  1: 012 + * A->B, C, a graph with a single directed edge. + * \cli  2: 102 + * A<->B, C, a graph with a mutual connection between two vertices. + * \cli  3: 021D + * A<-B->C, the binary out-tree. + * \cli  4: 021U + * A->B<-C, the binary in-tree. + * \cli  5: 021C + * A->B->C, the directed line. + * \cli  6: 111D + * A<->B<-C. + * \cli  7: 111U + * A<->B->C. + * \cli  8: 030T + * A->B<-C, A->C. + * \cli  9: 030C + * A<-B<-C, A->C. + * \cli 10: 201 + * A<->B<->C. + * \cli 11: 120D + * A<-B->C, A<->C. + * \cli 12: 120U + * A->B<-C, A<->C. + * \cli 13: 120C + * A->B->C, A<->C. + * \cli 14: 210 + * A->B<->C, A<->C. + * \cli 15: 300 + * A<->B<->C, A<->C, the complete graph. + * \endclist + * + * + * This function is intended for directed graphs. If the input is undirected, + * a warning is shown, and undirected edges will be interpreted as mutual. + * + * + * This function calls \ref igraph_motifs_randesu() which is an + * implementation of the FANMOD motif finder tool, see \ref + * igraph_motifs_randesu() for details. Note that the order of the + * triads is not the same for \ref igraph_triad_census() and \ref + * igraph_motifs_randesu(). + * + * + * References: + * + * + * Davis, J.A. and Leinhardt, S. (1972). The Structure of + * Positive Interpersonal Relations in Small Groups. In J. Berger + * (Ed.), Sociological Theories in Progress, Volume 2, 218-251. + * Boston: Houghton Mifflin. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here in the same order as given in the list above. Note that this + * order is different than the one used by \ref igraph_motifs_randesu(). + * \return Error code. + * + * \sa \ref igraph_motifs_randesu(), \ref igraph_dyad_census(). + * + * Time complexity: TODO. + */ + +igraph_error_t igraph_triad_census(const igraph_t *graph, igraph_vector_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_t cut_prob, tmp; + igraph_real_t m2, m4; + igraph_real_t total; + + if (!igraph_is_directed(graph)) { + IGRAPH_WARNING("Triad census called on an undirected graph. All connections will be treated as mutual."); + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cut_prob, 3); /* all zeros */ + IGRAPH_CHECK(igraph_vector_resize(res, 16)); + igraph_vector_null(res); + IGRAPH_CHECK(igraph_motifs_randesu(graph, &tmp, 3, &cut_prob)); + IGRAPH_CHECK(igraph_i_triad_census_24(graph, &m2, &m4)); + + total = ((igraph_real_t) vcount) * (vcount - 1); + total *= (vcount - 2); + total /= 6; + + /* Reorder */ + if (igraph_is_directed(graph)) { + VECTOR(tmp)[0] = 0; + VECTOR(tmp)[1] = m2; + VECTOR(tmp)[3] = m4; + VECTOR(tmp)[0] = total - igraph_vector_sum(&tmp); + + VECTOR(*res)[0] = VECTOR(tmp)[0]; + VECTOR(*res)[1] = VECTOR(tmp)[1]; + VECTOR(*res)[2] = VECTOR(tmp)[3]; + VECTOR(*res)[3] = VECTOR(tmp)[6]; + VECTOR(*res)[4] = VECTOR(tmp)[2]; + VECTOR(*res)[5] = VECTOR(tmp)[4]; + VECTOR(*res)[6] = VECTOR(tmp)[5]; + VECTOR(*res)[7] = VECTOR(tmp)[9]; + VECTOR(*res)[8] = VECTOR(tmp)[7]; + VECTOR(*res)[9] = VECTOR(tmp)[11]; + VECTOR(*res)[10] = VECTOR(tmp)[10]; + VECTOR(*res)[11] = VECTOR(tmp)[8]; + VECTOR(*res)[12] = VECTOR(tmp)[13]; + VECTOR(*res)[13] = VECTOR(tmp)[12]; + VECTOR(*res)[14] = VECTOR(tmp)[14]; + VECTOR(*res)[15] = VECTOR(tmp)[15]; + } else { + VECTOR(tmp)[0] = 0; + VECTOR(tmp)[1] = m2; + VECTOR(tmp)[0] = total - igraph_vector_sum(&tmp); + + VECTOR(*res)[0] = VECTOR(tmp)[0]; + VECTOR(*res)[2] = VECTOR(tmp)[1]; + VECTOR(*res)[10] = VECTOR(tmp)[2]; + VECTOR(*res)[15] = VECTOR(tmp)[3]; + } + + igraph_vector_destroy(&cut_prob); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/other.c b/src/misc/other.c new file mode 100644 index 0000000..845145a --- /dev/null +++ b/src/misc/other.c @@ -0,0 +1,261 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_nongraph.h" +#include "igraph_paths.h" + +#include "core/interruption.h" + +/** + * \ingroup nongraph + * \function igraph_running_mean + * \brief Calculates the running mean of a vector. + * + * + * The running mean is defined by the mean of the + * previous \p binwidth values. + * \param data The vector containing the data. + * \param res The vector containing the result. This should be + * initialized before calling this function and will be + * resized. + * \param binwidth Integer giving the width of the bin for the running + * mean calculation. + * \return Error code. + * + * Time complexity: O(n), + * n is the length of + * the data vector. + */ + +igraph_error_t igraph_running_mean(const igraph_vector_t *data, igraph_vector_t *res, + igraph_int_t binwidth) { + + double sum = 0; + igraph_int_t i; + + /* Check */ + if (igraph_vector_size(data) < binwidth) { + IGRAPH_ERRORF("Data vector length (%" IGRAPH_PRId ") smaller than bin width (%" IGRAPH_PRId ").", IGRAPH_EINVAL, igraph_vector_size(data), binwidth); + } + if (binwidth < 1) { + IGRAPH_ERRORF("Bin width for running mean should be at least 1, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, binwidth); + } + + /* Memory for result */ + + IGRAPH_CHECK(igraph_vector_resize(res, (igraph_vector_size(data) - binwidth + 1))); + + /* Initial bin */ + for (i = 0; i < binwidth; i++) { + sum += VECTOR(*data)[i]; + } + + VECTOR(*res)[0] = sum / binwidth; + + for (i = 1; i < igraph_vector_size(data) - binwidth + 1; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + sum -= VECTOR(*data)[i - 1]; + sum += VECTOR(*data)[ (i + binwidth - 1)]; + VECTOR(*res)[i] = sum / binwidth; + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_expand_path_to_pairs + * \brief Helper function to convert a sequence of vertex IDs describing a path into a "pairs" vector. + * + * + * This function is useful when you have a sequence of vertex IDs in a graph and + * you would like to retrieve the IDs of the edges between them. The function + * duplicates all but the first and the last elements in the vector, effectively + * converting the path into a vector of vertex IDs that can be passed to + * \ref igraph_get_eids(). + * + * \param path the input vector. It will be modified in-place and it will be + * resized as needed. When the vector contains less than two vertex IDs, + * it will be cleared. + * \return Error code: \c IGRAPH_ENOMEM if there is not enough memory to expand + * the vector. + */ +igraph_error_t igraph_expand_path_to_pairs(igraph_vector_int_t* path) { + igraph_int_t no_of_vertices = igraph_vector_int_size(path); + igraph_int_t i, j, no_of_items = (no_of_vertices - 1) * 2; + + if (no_of_vertices <= 1) { + igraph_vector_int_clear(path); + } else { + IGRAPH_CHECK(igraph_vector_int_resize(path, no_of_items)); + + i = no_of_vertices - 1; + j = no_of_items - 1; + VECTOR(*path)[j] = VECTOR(*path)[i]; + while (i > 1) { + i--; j--; + VECTOR(*path)[j] = VECTOR(*path)[i]; + j--; + VECTOR(*path)[j] = VECTOR(*path)[i]; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vertex_path_from_edge_path + * \brief Converts a walk of edge IDs to the traversed vertex IDs. + * + * This function is useful when you have a sequence of edge IDs representing a + * continuous walk in a graph and you would like to obtain the vertex IDs that + * the walk traverses. The function is used implicitly by several shortest path + * related functions to convert a path of edge IDs to the corresponding + * representation that describes the path in terms of vertex IDs instead. + * + * + * The result will always contain one more vertex than the number of provided + * edges. If no edges are given, the output will contain only the start vertex. + * + * + * The walk is allowed to traverse the same vertex more than once. It is + * suitable for use on paths, cycles, or arbitrary walks. + * + * \param graph The graph that the edge IDs refer to. + * \param start The start vertex of the path. If negative, it is determined + * automatically. This is only possible if the walk contains at least + * one edge. If only one edge is present in an undirected walk, + * one of its endpoints will be selected arbitrarily. + * \param edge_path The sequence of edge IDs that describe the path. + * \param vertex_path The sequence of vertex IDs traversed will be returned here. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. \c IGRAPH_OUT follows edge + * directions, \c IGRAPH_IN follows the opposite directions, + * and \c IGRAPH_ALL ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code: \c IGRAPH_ENOMEM if there is not enough memory, + * \c IGRAPH_EINVVID if the start vertex is invalid, + * \c IGRAPH_EINVAL if the edge walk does not start at the given vertex + * or if there is at least one edge whose start vertex does not match + * the end vertex of the previous edge. + * + * Time complexity: O(n) where n is the length of the walk. + */ +igraph_error_t igraph_vertex_path_from_edge_path( + const igraph_t *graph, igraph_int_t start, + const igraph_vector_int_t *edge_path, igraph_vector_int_t *vertex_path, + igraph_neimode_t mode +) { + const igraph_int_t no_of_edges = igraph_vector_int_size(edge_path); + + igraph_vector_int_clear(vertex_path); + IGRAPH_CHECK(igraph_vector_int_reserve(vertex_path, no_of_edges + 1)); + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + /* Detect start vertex automatically if necessary. */ + if (start < 0) { + if (no_of_edges == 0) { + IGRAPH_ERROR("The path must contain at least one edge in order to " + "determine its starting vertex automatically.", IGRAPH_EINVAL); + } + const igraph_int_t edge = VECTOR(*edge_path)[0]; + switch (mode) { + case IGRAPH_OUT: + start = IGRAPH_FROM(graph, edge); + break; + case IGRAPH_IN: + start = IGRAPH_TO(graph, edge); + break; + case IGRAPH_ALL: + if (no_of_edges > 1) { + const igraph_int_t from = IGRAPH_FROM(graph, edge); + const igraph_int_t to = IGRAPH_TO(graph, edge); + const igraph_int_t next_edge = VECTOR(*edge_path)[1]; + if (to == IGRAPH_FROM(graph, next_edge) || to == IGRAPH_TO(graph, next_edge)) { + start = from; + } else { + start = to; + /* If the walk is not continuous, this will be detected in the next stage. */ + } + } else { + /* There is a single undirected edge. + * Choose an arbitrary endpoint the start vertex. */ + start = IGRAPH_FROM(graph, edge); + } + } + } + + if (start >= igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid start vertex.", IGRAPH_EINVVID); + } + + for (igraph_int_t i = 0; i < no_of_edges; i++) { + const igraph_int_t edge = VECTOR(*edge_path)[i]; + const igraph_int_t from = IGRAPH_FROM(graph, edge); + const igraph_int_t to = IGRAPH_TO(graph, edge); + igraph_bool_t next_edge_ok; + igraph_int_t next_start; + + igraph_vector_int_push_back(vertex_path, start); /* reserved */ + + switch (mode) { + case IGRAPH_OUT: + next_edge_ok = from == start; + next_start = to; + break; + + case IGRAPH_IN: + next_edge_ok = to == start; + next_start = from; + break; + + case IGRAPH_ALL: + if (from == start) { + next_edge_ok = true; + next_start = to; + } else if (to == start) { + next_edge_ok = true; + next_start = from; + } else { + next_edge_ok = false; + } + break; + + default: + IGRAPH_ERROR("Invalid neighborhood mode.", IGRAPH_EINVMODE); + } + + if (!next_edge_ok) { + IGRAPH_ERROR("Edge IDs do not form a continuous path.", IGRAPH_EINVAL); + } + + start = next_start; + } + + igraph_vector_int_push_back(vertex_path, start); /* reserved */ + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/power_law_fit.c b/src/misc/power_law_fit.c new file mode 100644 index 0000000..1078315 --- /dev/null +++ b/src/misc/power_law_fit.c @@ -0,0 +1,319 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_nongraph.h" + +#include "igraph_random.h" +#include "igraph_types.h" + +#include "plfit/plfit_error.h" +#include "plfit/plfit.h" + +#include + +static const char* igraph_i_plfit_error_message = 0; + +static void igraph_i_plfit_error_handler_store(const char *reason, const char *file, + int line, int plfit_errno) { + + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(plfit_errno); + + igraph_i_plfit_error_message = reason; +} + +static void igraph_i_plfit_prepare_continuous_options( + plfit_continuous_options_t* options, igraph_bool_t finite_size_correction +) { + plfit_continuous_options_init(options); + options->p_value_method = PLFIT_P_VALUE_SKIP; + options->xmin_method = PLFIT_STRATIFIED_SAMPLING; + options->finite_size_correction = (plfit_bool_t) finite_size_correction; +} + +static void igraph_i_plfit_prepare_discrete_options( + plfit_discrete_options_t* options, igraph_bool_t finite_size_correction +) { + plfit_discrete_options_init(options); + options->p_value_method = PLFIT_P_VALUE_SKIP; + options->finite_size_correction = (plfit_bool_t) finite_size_correction; +} + +/* Decides whether to use finite size correction for the given input data */ +static igraph_bool_t igraph_i_plfit_should_use_finite_size_correction(const igraph_vector_t* data) { + return igraph_vector_size(data) < 50; +} + +static igraph_error_t igraph_i_handle_plfit_error(int code) { + switch (code) { + case PLFIT_SUCCESS: + return IGRAPH_SUCCESS; + + case PLFIT_FAILURE: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_FAILURE); + break; + + case PLFIT_EINVAL: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EINVAL); + break; + + case PLFIT_UNDRFLOW: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EUNDERFLOW); /* LCOV_EXCL_LINE */ + break; + + case PLFIT_OVERFLOW: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EOVERFLOW); /* LCOV_EXCL_LINE */ + break; + + case PLFIT_ENOMEM: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + break; + + case PLFIT_EMAXITER: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_DIVERGED); /* LCOV_EXCL_LINE */ + break; + + default: + IGRAPH_ERRORF("Unknown error code returned from plfit (%d).", IGRAPH_FAILURE, code); + break; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup nongraph + * \function igraph_power_law_fit + * \brief Fits a power-law distribution to a vector of numbers. + * + * This function fits a power-law distribution to a vector containing samples + * from a distribution (that is assumed to follow a power-law of course). In + * a power-law distribution, it is generally assumed that P(X=x) is + * proportional to x-alpha, where x is a positive number and alpha + * is greater than 1. In many real-world cases, the power-law behaviour kicks + * in only above a threshold value \em xmin. The goal of this functions is to + * determine \em alpha if \em xmin is given, or to determine \em xmin and the + * corresponding value of \em alpha. + * + * + * The function uses the maximum likelihood principle to determine \em alpha + * for a given \em xmin; in other words, the function will return the \em alpha + * value for which the probability of drawing the given sample is the highest. + * When \em xmin is not given in advance, the algorithm will attempt to find + * the optimal \em xmin value for which the p-value of a Kolmogorov-Smirnov + * test between the fitted distribution and the original sample is the largest. + * The function uses the method of Clauset, Shalizi and Newman to calculate the + * parameters of the fitted distribution. See the following reference for + * details: + * + * + * Aaron Clauset, Cosma R. Shalizi and Mark E.J. Newman: Power-law + * distributions in empirical data. SIAM Review 51(4):661-703, 2009. + * https://doi.org/10.1137/070710111 + * + * \param data vector containing the samples for which a power-law distribution + * is to be fitted. Note that you have to provide the \em samples, + * not the probability density function or the cumulative + * distribution function. For example, if you wish to fit + * a power-law to the degrees of a graph, you can use the output of + * \ref igraph_degree directly as an input argument to + * \ref igraph_power_law_fit + * \param result the result of the fitting algorithm. See \ref igraph_plfit_result_t + * for more details. Note that the p-value of the fit is \em not + * calculated by default as it is time-consuming; you need to call + * \ref igraph_plfit_result_calculate_p_value() to calculate the + * p-value itself + * \param xmin the minimum value in the sample vector where the power-law + * behaviour is expected to kick in. Samples smaller than \c xmin + * will be ignored by the algorithm. Pass zero here if you want to + * include all the samples. If \c xmin is negative, the algorithm + * will attempt to determine its best value automatically. + * \param force_continuous assume that the samples in the \c data argument come + * from a continuous distribution even if the sample vector + * contains integer values only (by chance). If this argument is + * false, igraph will assume a continuous distribution if at least + * one sample is non-integer and assume a discrete distribution + * otherwise. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory + * \c IGRAPH_EINVAL: one of the arguments is invalid + * \c IGRAPH_EOVERFLOW: overflow during the fitting process + * \c IGRAPH_EUNDERFLOW: underflow during the fitting process + * \c IGRAPH_FAILURE: the underlying algorithm signaled a failure + * without returning a more specific error code + * + * Time complexity: in the continuous case, O(n log(n)) if \c xmin is given. + * In the discrete case, the time complexity is dominated by the complexity of + * the underlying L-BFGS algorithm that is used to optimize alpha. If \c xmin + * is not given, the time complexity is multiplied by the number of unique + * samples in the input vector (although it should be faster in practice). + * + * \example examples/simple/igraph_power_law_fit.c + */ +igraph_error_t igraph_power_law_fit( + const igraph_vector_t* data, igraph_plfit_result_t* result, + igraph_real_t xmin, igraph_bool_t force_continuous +) { + plfit_error_handler_t* plfit_stored_error_handler; + plfit_result_t plfit_result; + plfit_continuous_options_t cont_options; + plfit_discrete_options_t disc_options; + igraph_bool_t discrete = force_continuous ? false : true; + igraph_bool_t finite_size_correction; + + int retval; + size_t i, n; + + finite_size_correction = igraph_i_plfit_should_use_finite_size_correction(data); + n = (size_t) igraph_vector_size(data); + + if (discrete) { + /* Does the vector contain discrete values only? */ + for (i = 0; i < n; i++) { + if (trunc(VECTOR(*data)[i]) != VECTOR(*data)[i]) { + discrete = false; + break; + } + } + } + + plfit_stored_error_handler = plfit_set_error_handler(igraph_i_plfit_error_handler_store); + if (discrete) { + igraph_i_plfit_prepare_discrete_options(&disc_options, finite_size_correction); + if (xmin >= 0) { + retval = plfit_estimate_alpha_discrete(VECTOR(*data), n, xmin, + &disc_options, &plfit_result); + } else { + retval = plfit_discrete(VECTOR(*data), n, &disc_options, &plfit_result); + } + } else { + igraph_i_plfit_prepare_continuous_options(&cont_options, finite_size_correction); + if (xmin >= 0) { + retval = plfit_estimate_alpha_continuous(VECTOR(*data), n, xmin, + &cont_options, &plfit_result); + } else { + retval = plfit_continuous(VECTOR(*data), n, &cont_options, &plfit_result); + } + } + plfit_set_error_handler(plfit_stored_error_handler); + + IGRAPH_CHECK(igraph_i_handle_plfit_error(retval)); + + if (result) { + result->data = data; + result->continuous = !discrete; + result->alpha = plfit_result.alpha; + result->xmin = plfit_result.xmin; + result->L = plfit_result.L; + result->D = plfit_result.D; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup nongraph + * \function igraph_plfit_result_calculate_p_value + * \brief Calculates the p-value of a fitted power-law model. + * + * + * The p-value is calculated by resampling the input data many times in a way + * that the part below the fitted \c x_min threshold is resampled from the + * input data itself, while the part above the fitted \c x_min threshold is + * drawn from the fitted power-law function. A Kolmogorov-Smirnov test is then + * performed for each resampled dataset and its test statistic is compared with the + * observed test statistic from the original dataset. The fraction of resampled + * datasets that have a \em higher test statistic is the returned p-value. + * + * + * Note that the precision of the returned p-value depends on the number of + * resampling attempts. The number of resampling trials is determined by + * 0.25 divided by the square of the required precision. For instance, a required + * precision of 0.01 means that 2500 samples will be drawn. + * + * + * If igraph is compiled with OpenMP support, this function will use parallel + * OpenMP threads for the resampling. Each OpenMP thread gets its own instance + * of a random number generator. However, since the scheduling of OpenMP threads + * is outside our control, we cannot guarantee how many resampling instances the + * threads are asked to execute, thus it may happen that the random number + * generators are used differently between runs. If you want to obtain + * reproducible results, seed igraph's master RNG appropriately, and force the + * number of OpenMP threads to 1 early in your program, either by calling + * omp_set_num_threads(1) or by setting the value of the \c OMP_NUM_THREADS + * environment variable to 1. + * + * \param model The fitted power-law model from the \ref igraph_power_law_fit() + * function + * \param result The calculated p-value is returned here + * \param precision The desired precision of the p-value. Higher values correspond + * to longer calculation time. + * \return Error code. + */ +igraph_error_t igraph_plfit_result_calculate_p_value( + const igraph_plfit_result_t* model, igraph_real_t* result, igraph_real_t precision +) { + int retval; + plfit_continuous_options_t cont_options; + plfit_discrete_options_t disc_options; + plfit_result_t plfit_result; + plfit_error_handler_t* plfit_stored_error_handler; + igraph_bool_t finite_size_correction; + + IGRAPH_ASSERT(model != NULL); + + plfit_result.alpha = model->alpha; + plfit_result.xmin = model->xmin; + plfit_result.L = model->L; + plfit_result.D = model->D; + + finite_size_correction = igraph_i_plfit_should_use_finite_size_correction(model->data); + + plfit_stored_error_handler = plfit_set_error_handler(igraph_i_plfit_error_handler_store); + if (model->continuous) { + igraph_i_plfit_prepare_continuous_options(&cont_options, finite_size_correction); + cont_options.p_value_method = PLFIT_P_VALUE_EXACT; + cont_options.p_value_precision = precision; + retval = plfit_calculate_p_value_continuous( + VECTOR(*model->data), (size_t) igraph_vector_size(model->data), + &cont_options, /* xmin_fixed = */ 0, &plfit_result + ); + } else { + igraph_i_plfit_prepare_discrete_options(&disc_options, finite_size_correction); + disc_options.p_value_method = PLFIT_P_VALUE_EXACT; + disc_options.p_value_precision = precision; + retval = plfit_calculate_p_value_discrete( + VECTOR(*model->data), (size_t) igraph_vector_size(model->data), + &disc_options, /* xmin_fixed = */ 0, &plfit_result + ); + } + plfit_set_error_handler(plfit_stored_error_handler); + + IGRAPH_CHECK(igraph_i_handle_plfit_error(retval)); + + if (result) { + *result = plfit_result.p; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/scan.c b/src/misc/scan.c new file mode 100644 index 0000000..dc24b67 --- /dev/null +++ b/src/misc/scan.c @@ -0,0 +1,766 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_scan.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_operators.h" +#include "igraph_stack.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +/** + * \section about_local_scan + * + * + * The scan statistic is a summary of the locality statistics that is computed + * from the local neighborhood of each vertex. For details, see + * Priebe, C. E., Conroy, J. M., Marchette, D. J., Park, Y. (2005). + * Scan Statistics on Enron Graphs. Computational and Mathematical Organization Theory. + * + */ + +/** + * \function igraph_local_scan_0 + * Local scan-statistics, k=0 + * + * K=0 scan-statistics is arbitrarily defined as the vertex degree for + * unweighted, and the vertex strength for weighted graphs. See \ref + * igraph_degree() and \ref igraph_strength(). + * + * \param graph The input graph + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +igraph_error_t igraph_local_scan_0(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + return igraph_strength(graph, res, igraph_vss_all(), mode, IGRAPH_LOOPS, weights); +} + +/* This one handles both weighted and unweighted cases */ + +static igraph_error_t igraph_i_local_scan_1_directed(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_inclist_t incs; + igraph_int_t i, node; + + igraph_vector_int_t neis; + + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *edges1 = igraph_inclist_get(&incs, node); + igraph_int_t edgeslen1 = igraph_vector_int_size(edges1); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors and self */ + VECTOR(neis)[node] = node + 1; + for (i = 0; i < edgeslen1; i++) { + igraph_int_t e = VECTOR(*edges1)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, e, node); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1; + VECTOR(neis)[nei] = node + 1; + VECTOR(*res)[node] += w; + } + + /* Crawl neighbors */ + for (i = 0; i < edgeslen1; i++) { + igraph_int_t e2 = VECTOR(*edges1)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, e2, node); + if (nei == node) { + break; + } + igraph_vector_int_t *edges2 = igraph_inclist_get(&incs, nei); + igraph_int_t j, edgeslen2 = igraph_vector_int_size(edges2); + for (j = 0; j < edgeslen2; j++) { + igraph_int_t e2 = VECTOR(*edges2)[j]; + igraph_int_t nei2 = IGRAPH_OTHER(graph, e2, nei); + igraph_real_t w2 = weights ? VECTOR(*weights)[e2] : 1; + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[node] += w2; + } + } + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_local_scan_1_directed_all(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_inclist_t incs; + igraph_int_t i, node; + + igraph_vector_int_t neis; + + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, IGRAPH_ALL, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *edges1 = igraph_inclist_get(&incs, node); + igraph_int_t edgeslen1 = igraph_vector_int_size(edges1); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors. We also count the edges that are incident on ego. + Note that this time we do not mark ego, because we don't want to + double count its incident edges later, when we are going over the + incident edges of ego's neighbors. */ + for (i = 0; i < edgeslen1; i++) { + igraph_int_t e = VECTOR(*edges1)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, e, node); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1; + VECTOR(neis)[nei] = node + 1; + VECTOR(*res)[node] += w; + } + + /* Explicitly unmark ego in case it had a loop edge */ + VECTOR(neis)[node] = 0; + + /* Crawl neighbors. We make sure that each neighbor of 'node' is + only crawled once. We count all qualifying edges of ego, and + then unmark ego to avoid double counting. */ + for (i = 0; i < edgeslen1; i++) { + igraph_int_t e2 = VECTOR(*edges1)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, e2, node); + igraph_vector_int_t *edges2; + igraph_int_t j, edgeslen2; + if (VECTOR(neis)[nei] != node + 1) { + continue; + } + edges2 = igraph_inclist_get(&incs, nei); + edgeslen2 = igraph_vector_int_size(edges2); + for (j = 0; j < edgeslen2; j++) { + igraph_int_t e2 = VECTOR(*edges2)[j]; + igraph_int_t nei2 = IGRAPH_OTHER(graph, e2, nei); + igraph_real_t w2 = weights ? VECTOR(*weights)[e2] : 1; + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[node] += w2; + } + } + VECTOR(neis)[nei] = 0; + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_1_ecount + * Local scan-statistics, k=1, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * 1-neighborhood of vertices. + * + * \param graph The input graph + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +igraph_error_t igraph_local_scan_1_ecount(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + if (igraph_is_directed(graph)) { + if (mode != IGRAPH_ALL) { + return igraph_i_local_scan_1_directed(graph, res, weights, mode); + } else { + return igraph_i_local_scan_1_directed_all(graph, res, weights); + } + } else { + return igraph_local_scan_k_ecount(graph, 1, res, weights, mode); + } +} + +static igraph_error_t igraph_i_local_scan_0_them_w(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_t is; + igraph_vector_int_t map2; + igraph_vector_t weights; + igraph_int_t i, m; + + if (!weights_them) { + IGRAPH_ERROR("Edge weights not given for weighted scan-0", + IGRAPH_EINVAL); + } + if (igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERROR("Invalid weights length for scan-0", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&map2, 0); + IGRAPH_CHECK(igraph_intersection(&is, us, them, /* edge_map1= */ 0, &map2)); + IGRAPH_FINALLY(igraph_destroy, &is); + + /* Rewrite the map as edge weights */ + m = igraph_vector_int_size(&map2); + IGRAPH_VECTOR_INIT_FINALLY(&weights, m); + for (i = 0; i < m; i++) { + VECTOR(weights)[i] = VECTOR(*weights_them)[ VECTOR(map2)[i] ]; + } + + IGRAPH_CHECK(igraph_strength(&is, res, igraph_vss_all(), mode, IGRAPH_LOOPS, + /*weights=*/ &weights)); + + igraph_destroy(&is); + igraph_vector_int_destroy(&map2); + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_0_them + * Local THEM scan-statistics, k=0 + * + * K=0 scan-statistics is arbitrarily defined as the vertex degree for + * unweighted, and the vertex strength for weighted graphs. See \ref + * igraph_degree() and \ref igraph_strength(). + * + * \param us The input graph, to use to extract the neighborhoods. + * \param them The input graph to use for the actually counting. + * \param res An initialized vector, the results are stored here. + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +igraph_error_t igraph_local_scan_0_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_t is; + + if (igraph_vcount(us) != igraph_vcount(them)) { + IGRAPH_ERROR("Number of vertices don't match in scan-0", IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness don't match in scan-0", IGRAPH_EINVAL); + } + + if (weights_them) { + return igraph_i_local_scan_0_them_w(us, them, res, weights_them, mode); + } + + IGRAPH_CHECK(igraph_intersection(&is, us, them, /*edge_map1=*/ 0, /*edge_map2=*/ 0)); + IGRAPH_FINALLY(igraph_destroy, &is); + + IGRAPH_CHECK(igraph_strength(&is, res, igraph_vss_all(), mode, IGRAPH_LOOPS, /* weights = */ 0)); + + igraph_destroy(&is); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_1_ecount_them + * Local THEM scan-statistics, k=1, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * 1-neighborhood of vertices. + * + * \param us The input graph to extract the neighborhoods. + * \param them The input graph to perform the counting. + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + * \sa \ref igraph_local_scan_1_ecount() for the US statistics. + */ + +igraph_error_t igraph_local_scan_1_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(us); + igraph_adjlist_t adj_us; + igraph_inclist_t incs_them; + igraph_vector_int_t neis; + igraph_int_t node; + + if (igraph_vcount(them) != no_of_nodes) { + IGRAPH_ERROR("Number of vertices must match in scan-1", IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness must match in scan-1", IGRAPH_EINVAL); + } + if (weights_them && + igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERROR("Invalid weight vector length in scan-1 (them)", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_adjlist_init( + us, &adj_us, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE + )); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adj_us); + IGRAPH_CHECK(igraph_inclist_init(them, &incs_them, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_them); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *neis_us = igraph_adjlist_get(&adj_us, node); + igraph_vector_int_t *edges1_them = igraph_inclist_get(&incs_them, node); + igraph_int_t len1_us = igraph_vector_int_size(neis_us); + igraph_int_t len1_them = igraph_vector_int_size(edges1_them); + igraph_int_t i; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors and self in us */ + VECTOR(neis)[node] = node + 1; + for (i = 0; i < len1_us; i++) { + igraph_int_t nei = VECTOR(*neis_us)[i]; + VECTOR(neis)[nei] = node + 1; + } + + /* Crawl neighbors in them, first ego */ + for (i = 0; i < len1_them; i++) { + igraph_int_t e = VECTOR(*edges1_them)[i]; + igraph_int_t nei = IGRAPH_OTHER(them, e, node); + if (VECTOR(neis)[nei] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[e] : 1; + VECTOR(*res)[node] += w; + } + } + /* Then the rest */ + for (i = 0; i < len1_us; i++) { + igraph_int_t nei = VECTOR(*neis_us)[i]; + igraph_vector_int_t *edges2_them = igraph_inclist_get(&incs_them, nei); + igraph_int_t j, len2_them = igraph_vector_int_size(edges2_them); + for (j = 0; j < len2_them; j++) { + igraph_int_t e2 = VECTOR(*edges2_them)[j]; + igraph_int_t nei2 = IGRAPH_OTHER(them, e2, nei); + if (VECTOR(neis)[nei2] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[e2] : 1; + VECTOR(*res)[node] += w; + } + } + } + + /* For undirected, it was double counted */ + if (mode == IGRAPH_ALL || ! igraph_is_directed(us)) { + VECTOR(*res)[node] /= 2.0; + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs_them); + igraph_adjlist_destroy(&adj_us); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_k_ecount + * \brief Sum the number of edges or the weights in k-neighborhood of every vertex. + * + * \param graph The input graph. + * \param k The size of the neighborhood, non-negative integer. + * The k=0 case is special, see \ref igraph_local_scan_0(). + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +igraph_error_t igraph_local_scan_k_ecount(const igraph_t *graph, igraph_int_t k, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t node; + igraph_dqueue_int_t Q; + igraph_vector_int_t marked; + igraph_inclist_t incs; + + if (k < 0) { + IGRAPH_ERROR("k must be non-negative in k-scan.", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERRORF("The weight vector length (%" IGRAPH_PRId ") in k-scan should equal " + "the number of edges of the graph (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(weights), + igraph_ecount(graph)); + } + + if (k == 0) { + return igraph_local_scan_0(graph, res, weights, mode); + } + if (k == 1 && igraph_is_directed(graph)) { + return igraph_local_scan_1_ecount(graph, res, weights, mode); + } + + /* We do a BFS form each node, and simply count the number + of edges on the way */ + + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + IGRAPH_VECTOR_INT_INIT_FINALLY(&marked, no_of_nodes); + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0 ; node < no_of_nodes ; node++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); + VECTOR(marked)[node] = node + 1; + while (!igraph_dqueue_int_empty(&Q)) { + igraph_int_t act = igraph_dqueue_int_pop(&Q); + igraph_int_t dist = igraph_dqueue_int_pop(&Q) + 1; + igraph_vector_int_t *edges = igraph_inclist_get(&incs, act); + igraph_int_t i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, act); + if (dist <= k || VECTOR(marked)[nei] == node + 1) { + igraph_real_t w = weights ? VECTOR(*weights)[edge] : 1; + VECTOR(*res)[node] += w; + } + if (dist <= k && VECTOR(marked)[nei] != node + 1) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, dist)); + VECTOR(marked)[nei] = node + 1; + } + } + } + + if (mode == IGRAPH_ALL || ! igraph_is_directed(graph)) { + VECTOR(*res)[node] /= 2.0; + } + + } /* node < no_of_nodes */ + + igraph_inclist_destroy(&incs); + igraph_vector_int_destroy(&marked); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_k_ecount_them + * \brief Local THEM scan-statistics, edge count or sum of weights. + * + * Count the number of edges or the sum the edge weights in the + * k-neighborhood of vertices. + * + * \param us The input graph to extract the neighborhoods. + * \param them The input graph to perform the counting. + * \param k The size of the neighborhood, non-negative integer. + * The k=0 case is special, see \ref igraph_local_scan_0_them(). + * \param res An initialized vector, the results are stored here. + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + * \sa \ref igraph_local_scan_1_ecount() for the US statistics. + */ + +igraph_error_t igraph_local_scan_k_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_int_t k, igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(us); + igraph_int_t node; + igraph_dqueue_int_t Q; + igraph_vector_int_t marked; + igraph_stack_int_t ST; + igraph_inclist_t incs_us, incs_them; + + if (igraph_vcount(them) != no_of_nodes) { + IGRAPH_ERROR("The number of vertices in the two graphs must " + "match in scan-k.", + IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness in the two graphs must match " + "in scan-k", IGRAPH_EINVAL); + } + if (k < 0) { + IGRAPH_ERRORF("k must be non-negative in k-scan, got %" IGRAPH_PRId + ".", IGRAPH_EINVAL, k); + } + if (weights_them && + igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERRORF("The weight vector length (%" IGRAPH_PRId + ") must be equal to the number of edges (%" IGRAPH_PRId + ").", IGRAPH_EINVAL, igraph_vector_size(weights_them), + igraph_ecount(them)); + } + + if (k == 0) { + return igraph_local_scan_0_them(us, them, res, weights_them, mode); + } + if (k == 1) { + return igraph_local_scan_1_ecount_them(us, them, res, weights_them, mode); + } + + /* We mark the nodes in US in a BFS. Then we check the outgoing edges + of all marked nodes in THEM. */ + + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + IGRAPH_VECTOR_INT_INIT_FINALLY(&marked, no_of_nodes); + IGRAPH_CHECK(igraph_inclist_init(us, &incs_us, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_us); + IGRAPH_CHECK(igraph_inclist_init(them, &incs_them, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_them); + IGRAPH_CHECK(igraph_stack_int_init(&ST, 100)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &ST); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + + /* BFS to mark the nodes in US */ + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); + IGRAPH_CHECK(igraph_stack_int_push(&ST, node)); + VECTOR(marked)[node] = node + 1; + while (!igraph_dqueue_int_empty(&Q)) { + igraph_int_t act = igraph_dqueue_int_pop(&Q); + igraph_int_t dist = igraph_dqueue_int_pop(&Q) + 1; + igraph_vector_int_t *edges = igraph_inclist_get(&incs_us, act); + igraph_int_t i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t nei = IGRAPH_OTHER(us, edge, act); + if (dist <= k && VECTOR(marked)[nei] != node + 1) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, dist)); + VECTOR(marked)[nei] = node + 1; + IGRAPH_CHECK(igraph_stack_int_push(&ST, nei)); + } + } + } + + /* Now check the edges of all nodes in THEM */ + while (!igraph_stack_int_empty(&ST)) { + igraph_int_t act = igraph_stack_int_pop(&ST); + igraph_vector_int_t *edges = igraph_inclist_get(&incs_them, act); + igraph_int_t i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + igraph_int_t edge = VECTOR(*edges)[i]; + igraph_int_t nei = IGRAPH_OTHER(them, edge, act); + if (VECTOR(marked)[nei] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[edge] : 1; + VECTOR(*res)[node] += w; + } + } + } + + if (mode == IGRAPH_ALL || ! igraph_is_directed(us)) { + VECTOR(*res)[node] /= 2; + } + + } /* node < no_of_nodes */ + + igraph_stack_int_destroy(&ST); + igraph_inclist_destroy(&incs_them); + igraph_inclist_destroy(&incs_us); + igraph_vector_int_destroy(&marked); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} +/** + * \function igraph_local_scan_subset_ecount + * \brief Local scan-statistics of subgraphs induced by subsets of vertices. + * + * Count the number of edges, or sum the edge weights in + * induced subgraphs from vertices given as a parameter. + * + * \param graph The graph to perform the counting/summing in. + * \param res Initialized vector, the result is stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param subsets List of \type igraph_vector_int_t + * objects, the vertex subsets. + * \return Error code. + */ + +igraph_error_t igraph_local_scan_subset_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *subsets) { + + igraph_int_t subset, no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_subsets = igraph_vector_int_list_size(subsets); + igraph_inclist_t incs; + igraph_vector_int_t marked; + igraph_bool_t directed = igraph_is_directed(graph); + + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length in local scan.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&marked, no_of_nodes); + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, IGRAPH_OUT, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_subsets)); + igraph_vector_null(res); + + for (subset = 0; subset < no_of_subsets; subset++) { + igraph_vector_int_t *nei = igraph_vector_int_list_get_ptr(subsets, subset); + igraph_int_t i, neilen = igraph_vector_int_size(nei); + for (i = 0; i < neilen; i++) { + igraph_int_t vertex = VECTOR(*nei)[i]; + if (vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex ID in neighborhood list in local scan.", + IGRAPH_EINVAL); + } + VECTOR(marked)[vertex] = subset + 1; + } + + for (i = 0; i < neilen; i++) { + igraph_int_t vertex = VECTOR(*nei)[i]; + igraph_vector_int_t *edges = igraph_inclist_get(&incs, vertex); + igraph_int_t j, edgeslen = igraph_vector_int_size(edges); + for (j = 0; j < edgeslen; j++) { + igraph_int_t edge = VECTOR(*edges)[j]; + igraph_int_t nei2 = IGRAPH_OTHER(graph, edge, vertex); + if (VECTOR(marked)[nei2] == subset + 1) { + igraph_real_t w = weights ? VECTOR(*weights)[edge] : 1; + VECTOR(*res)[subset] += w; + } + } + } + if (!directed) { + VECTOR(*res)[subset] /= 2.0; + } + } + + igraph_inclist_destroy(&incs); + igraph_vector_int_destroy(&marked); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_local_scan_neighborhood_ecount + * Local scan-statistics with pre-calculated neighborhoods + * + * Count the number of edges, or sum the edge weights in + * neighborhoods given as a parameter. + * + * \deprecated-by igraph_local_scan_subset_ecount 0.10.0 + * + * \param graph The graph to perform the counting/summing in. + * \param res Initialized vector, the result is stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param neighborhoods List of \type igraph_vector_int_t + * objects, the neighborhoods, one for each vertex in the + * graph. + * \return Error code. + */ + +igraph_error_t igraph_local_scan_neighborhood_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_int_list_t *neighborhoods) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (igraph_vector_int_list_size(neighborhoods) != no_of_nodes) { + IGRAPH_ERROR("Invalid neighborhood list length in local scan.", + IGRAPH_EINVAL); + } + + return igraph_local_scan_subset_ecount(graph, res, weights, neighborhoods); +} diff --git a/src/misc/sir.c b/src/misc/sir.c new file mode 100644 index 0000000..6800c32 --- /dev/null +++ b/src/misc/sir.c @@ -0,0 +1,265 @@ +/* + igraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_epidemics.h" + +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_psumtree.h" +#include "igraph_memory.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +igraph_error_t igraph_sir_init(igraph_sir_t *sir) { + IGRAPH_CHECK(igraph_vector_init(&sir->times, 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &sir->times); + IGRAPH_CHECK(igraph_vector_int_init(&sir->no_s, 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &sir->no_s); + IGRAPH_CHECK(igraph_vector_int_init(&sir->no_i, 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &sir->no_i); + IGRAPH_CHECK(igraph_vector_int_init(&sir->no_r, 1)); + IGRAPH_FINALLY_CLEAN(3); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_sir_destroy + * \brief Deallocates memory associated with a SIR simulation run. + * + * \param sir The \ref igraph_sir_t object storing the simulation. + */ + +void igraph_sir_destroy(igraph_sir_t *sir) { + igraph_vector_destroy(&sir->times); + igraph_vector_int_destroy(&sir->no_s); + igraph_vector_int_destroy(&sir->no_i); + igraph_vector_int_destroy(&sir->no_r); +} + +static void igraph_i_sir_destroy(igraph_vector_ptr_t *v) { + igraph_int_t i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + if ( VECTOR(*v)[i] ) { + igraph_sir_destroy( VECTOR(*v)[i]) ; + IGRAPH_FREE( VECTOR(*v)[i] ); /* this also sets the vector_ptr element to NULL */ + } + } +} + +#define S_S 0 +#define S_I 1 +#define S_R 2 + +/** + * \function igraph_sir + * \brief Performs a number of SIR epidemics model runs on a graph. + * + * The SIR model is a simple model from epidemiology. The individuals + * of the population might be in three states: susceptible, infected + * and recovered. Recovered people are assumed to be immune to the + * disease. Susceptibles become infected with a rate that depends on + * their number of infected neighbors. Infected people become recovered + * with a constant rate. See these parameters below. + * + * + * This function runs multiple simulations, all starting with a + * single uniformly randomly chosen infected individual. A simulation + * is stopped when no infected individuals are left. + * + * \param graph The graph to perform the model on. For directed graphs + * edge directions are ignored and a warning is given. + * \param beta The rate of infection of an individual that is + * susceptible and has a single infected neighbor. + * The infection rate of a susceptible individual with n + * infected neighbors is n times beta. Formally + * this is the rate parameter of an exponential distribution. + * \param gamma The rate of recovery of an infected individual. + * Formally, this is the rate parameter of an exponential + * distribution. + * \param no_sim The number of simulation runs to perform. + * \param result The result of the simulation is stored here, + * in a list of \ref igraph_sir_t objects. To deallocate + * memory, the user needs to call \ref igraph_sir_destroy on + * each element, before destroying the pointer vector itself + * using \ref igraph_vector_ptr_destroy_all(). + * \return Error code. + * + * Time complexity: O(no_sim * (|V| + |E| log(|V|))). + */ + +igraph_error_t igraph_sir(const igraph_t *graph, igraph_real_t beta, + igraph_real_t gamma, igraph_int_t no_sim, + igraph_vector_ptr_t *result) { + + igraph_int_t infected; + igraph_vector_int_t status; + igraph_adjlist_t adjlist; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t i, j, ns, ni, nr; + igraph_vector_int_t *neis; + igraph_psumtree_t tree; + igraph_real_t psum; + igraph_int_t neilen; + igraph_bool_t simple; + + if (no_of_nodes == 0) { + IGRAPH_ERROR("Cannot run SIR model on empty graph.", IGRAPH_EINVAL); + } + if (beta < 0) { + IGRAPH_ERROR("The infection rate beta must be non-negative in SIR model.", IGRAPH_EINVAL); + } + /* With a recovery rate of zero, the simulation would never stop. */ + if (gamma <= 0) { + IGRAPH_ERROR("The recovery rate gamma must be positive in SIR model.", IGRAPH_EINVAL); + } + if (no_sim <= 0) { + IGRAPH_ERROR("Number of SIR simulations must be positive.", IGRAPH_EINVAL); + } + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored in SIR model."); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &simple, IGRAPH_UNDIRECTED)); + if (!simple) { + IGRAPH_ERROR("SIR model only works with simple graphs.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_init(&status, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &status); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_psumtree_init(&tree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &tree); + + IGRAPH_CHECK(igraph_vector_ptr_resize(result, no_sim)); + igraph_vector_ptr_null(result); + IGRAPH_FINALLY(igraph_i_sir_destroy, result); + for (i = 0; i < no_sim; i++) { + igraph_sir_t *sir = IGRAPH_CALLOC(1, igraph_sir_t); + if (!sir) { + IGRAPH_ERROR("Cannot run SIR model.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_CHECK(igraph_sir_init(sir)); + VECTOR(*result)[i] = sir; + } + + for (j = 0; j < no_sim; j++) { + + igraph_sir_t *sir = VECTOR(*result)[j]; + igraph_vector_t *times_v = &sir->times; + igraph_vector_int_t *no_s_v = &sir->no_s; + igraph_vector_int_t *no_i_v = &sir->no_i; + igraph_vector_int_t *no_r_v = &sir->no_r; + + infected = RNG_INTEGER(0, no_of_nodes - 1); + + /* Initially infected */ + igraph_vector_int_null(&status); + VECTOR(status)[infected] = S_I; + ns = no_of_nodes - 1; + ni = 1; + nr = 0; + + VECTOR(*times_v)[0] = 0.0; + VECTOR(*no_s_v)[0] = ns; + VECTOR(*no_i_v)[0] = ni; + VECTOR(*no_r_v)[0] = nr; + + if (igraph_psumtree_sum(&tree) != 0) { + igraph_psumtree_reset(&tree); + } + + /* Rates */ + IGRAPH_CHECK(igraph_psumtree_update(&tree, infected, gamma)); + neis = igraph_adjlist_get(&adjlist, infected); + neilen = igraph_vector_int_size(neis); + for (i = 0; i < neilen; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + IGRAPH_CHECK(igraph_psumtree_update(&tree, nei, beta)); + } + + while (ni > 0) { + igraph_real_t tt; + igraph_real_t r; + igraph_int_t vchange; + + IGRAPH_ALLOW_INTERRUPTION(); + + psum = igraph_psumtree_sum(&tree); + tt = igraph_rng_get_exp(igraph_rng_default(), psum); + r = RNG_UNIF(0, psum); + + igraph_psumtree_search(&tree, &vchange, r); + neis = igraph_adjlist_get(&adjlist, vchange); + neilen = igraph_vector_int_size(neis); + + if (VECTOR(status)[vchange] == S_I) { + VECTOR(status)[vchange] = S_R; + ni--; nr++; + IGRAPH_CHECK(igraph_psumtree_update(&tree, vchange, 0.0)); + for (i = 0; i < neilen; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + if (VECTOR(status)[nei] == S_S) { + igraph_real_t rate = igraph_psumtree_get(&tree, nei); + rate -= beta; + /* Because of roundoff errors, it may happen that the rate + * becomes slightly negative, which would cause psumtree_update() + * to fail. We clip negative values to zero to avoid this. + * See https://github.com/igraph/igraph/issues/2779 */ + if (rate < 0) rate = 0; + IGRAPH_CHECK(igraph_psumtree_update(&tree, nei, rate)); + } + } + + } else { /* S_S */ + VECTOR(status)[vchange] = S_I; + ns--; ni++; + IGRAPH_CHECK(igraph_psumtree_update(&tree, vchange, gamma)); + for (i = 0; i < neilen; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + if (VECTOR(status)[nei] == S_S) { + igraph_real_t rate = igraph_psumtree_get(&tree, nei); + rate += beta; + IGRAPH_CHECK(igraph_psumtree_update(&tree, nei, rate)); + } + } + } + + IGRAPH_CHECK(igraph_vector_push_back(times_v, tt + igraph_vector_tail(times_v))); + IGRAPH_CHECK(igraph_vector_int_push_back(no_s_v, ns)); + IGRAPH_CHECK(igraph_vector_int_push_back(no_i_v, ni)); + IGRAPH_CHECK(igraph_vector_int_push_back(no_r_v, nr)); + + } /* psum > 0 */ + + } /* j < no_sim */ + + igraph_psumtree_destroy(&tree); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&status); + IGRAPH_FINALLY_CLEAN(4); /* + result */ + + return IGRAPH_SUCCESS; +} diff --git a/src/misc/spanning_trees.c b/src/misc/spanning_trees.c new file mode 100644 index 0000000..95c8516 --- /dev/null +++ b/src/misc/spanning_trees.c @@ -0,0 +1,620 @@ +/* + igraph library. + Copyright (C) 2011-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +static igraph_int_t max_tree_edges(igraph_int_t no_of_nodes, igraph_int_t no_of_edges) { + if (no_of_nodes == 0) return 0; + if (no_of_edges < no_of_nodes) return no_of_edges; + return no_of_nodes - 1; +} + + +/* Unweighted case -- just a simple BFS */ + +/** + * \ingroup structural + * \function igraph_i_minimum_spanning_tree_unweighted + * \brief A spanning tree of an unweighted graph. + * + * Produces an arbitrary spanning tree of the graph. + * + * + * Directed graphs are treated as undirected for this computation. + * + * + * If the graph is not connected then a spanning forest is returned. + * This is a set of spanning trees of each component. + * + * \param graph The graph object. Edge directions will be ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanning tree as + * a separate graph object. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * + * Time complexity: O(|V|+|E|), + * |V| is the + * number of vertices, |E| the number + * of edges in the graph. + * + * \sa \ref igraph_minimum_spanning_tree() for a common interface to all + * minimum spanning tree algorithms. + */ + +static igraph_error_t igraph_i_minimum_spanning_tree_unweighted( + const igraph_t* graph, + igraph_vector_int_t* res) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bitset_t already_added, added_edges; + + igraph_dqueue_int_t q; + igraph_vector_int_t eids; + + IGRAPH_BITSET_INIT_FINALLY(&added_edges, no_of_edges); + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + igraph_vector_int_clear(res); + IGRAPH_CHECK(igraph_vector_int_reserve(res, max_tree_edges(no_of_nodes, no_of_edges))); + + /* Perform a BFS */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (IGRAPH_BIT_TEST(already_added, i)) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_BIT_SET(already_added, i); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + while (! igraph_dqueue_int_empty(&q)) { + igraph_int_t eids_size; + igraph_int_t act_node = igraph_dqueue_int_pop(&q); + IGRAPH_CHECK(igraph_incident(graph, &eids, act_node, IGRAPH_ALL, IGRAPH_LOOPS)); + eids_size = igraph_vector_int_size(&eids); + for (igraph_int_t j = 0; j < eids_size; j++) { + igraph_int_t edge = VECTOR(eids)[j]; + if (! IGRAPH_BIT_TEST(added_edges, edge)) { + igraph_int_t to = IGRAPH_OTHER(graph, edge, act_node); + if (! IGRAPH_BIT_TEST(already_added, to)) { + IGRAPH_BIT_SET(already_added, to); + IGRAPH_BIT_SET(added_edges, edge); + igraph_vector_int_push_back(res, edge); /* reserved */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, to)); + } + } + } + } + } + + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&eids); + igraph_bitset_destroy(&already_added); + igraph_bitset_destroy(&added_edges); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/* Prims' algorithm */ + +/** + * \ingroup structural + * \function igraph_i_minimum_spanning_tree_prim + * \brief A minimum spanning tree of a weighted graph using Prim's method. + * + * Finds a spanning tree or spanning forest for which the sum of edge + * weights is the smallest. This function uses Prim's method for carrying + * out the computation. + * + * + * Directed graphs are treated as undirected for this computation. + * + * + * Reference: + * + * + * Prim, R.C.: Shortest connection networks and some + * generalizations, Bell System Technical + * Journal, Vol. 36, + * 1957, 1389--1401. + * https://doi.org/10.1002/j.1538-7305.1957.tb01515.x + * + * \param graph The graph object. Edge directions will be ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanning tree as + * a separate graph object. + * \param weights A vector containing the weights of the edges in the order + * of edge IDs. Weights must not be NaN. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory. + * \c IGRAPH_EINVAL, length of weight vector does not + * match number of edges, or NaN in weights. + * + * Time complexity: O(|E| log |V|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \sa \ref igraph_minimum_spanning_tree() for a common interface to all + * minimum spanning tree algorithms. + * + * \example examples/simple/igraph_minimum_spanning_tree.c + */ + +static igraph_error_t igraph_i_minimum_spanning_tree_prim( + const igraph_t* graph, + igraph_vector_int_t* res, + const igraph_vector_t *weights) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bitset_t already_added, added_edges; + + igraph_d_indheap_t heap; + igraph_vector_int_t adj; + + if (weights == NULL) { + return igraph_i_minimum_spanning_tree_unweighted(graph, res); + } + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Weight vector length does not match number of edges.", IGRAPH_EINVAL); + } + + if (igraph_vector_is_any_nan(weights)) { + IGRAPH_ERROR("Weights must not contain NaN values.", IGRAPH_EINVAL); + } + + igraph_vector_int_clear(res); + IGRAPH_CHECK(igraph_vector_int_reserve(res, max_tree_edges(no_of_nodes, no_of_edges))); + + IGRAPH_BITSET_INIT_FINALLY(&added_edges, no_of_edges); + IGRAPH_BITSET_INIT_FINALLY(&already_added, no_of_nodes); + + IGRAPH_CHECK(igraph_d_indheap_init(&heap, 0)); + IGRAPH_FINALLY(igraph_d_indheap_destroy, &heap); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&adj, 0); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t adj_size; + if (IGRAPH_BIT_TEST(already_added, i)) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_BIT_SET(already_added, i); + /* add all edges of the first vertex */ + IGRAPH_CHECK(igraph_incident(graph, &adj, i, IGRAPH_ALL, IGRAPH_LOOPS)); + adj_size = igraph_vector_int_size(&adj); + for (igraph_int_t j = 0; j < adj_size; j++) { + igraph_int_t edgeno = VECTOR(adj)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, edgeno, i); + if (! IGRAPH_BIT_TEST(already_added, neighbor)) { + IGRAPH_CHECK(igraph_d_indheap_push(&heap, -VECTOR(*weights)[edgeno], i, edgeno)); + } + } + + while (! igraph_d_indheap_empty(&heap)) { + /* Get minimal edge */ + igraph_int_t from, edge; + igraph_d_indheap_max_index(&heap, &from, &edge); + + /* Erase it */ + igraph_d_indheap_delete_max(&heap); + + /* Is this edge already included? */ + if (! IGRAPH_BIT_TEST(added_edges, edge)) { + const igraph_int_t to = IGRAPH_OTHER(graph, edge, from); + + /* Does it point to a visited node? */ + if (! IGRAPH_BIT_TEST(already_added, to)) { + IGRAPH_BIT_SET(already_added, to); + IGRAPH_BIT_SET(added_edges, edge); + igraph_vector_int_push_back(res, edge); /* reserved */ + /* add all outgoing edges */ + IGRAPH_CHECK(igraph_incident(graph, &adj, to, IGRAPH_ALL, IGRAPH_LOOPS)); + adj_size = igraph_vector_int_size(&adj); + for (igraph_int_t j = 0; j < adj_size; j++) { + const igraph_int_t edgeno = VECTOR(adj)[j]; + const igraph_int_t neighbor = IGRAPH_OTHER(graph, edgeno, to); + if (! IGRAPH_BIT_TEST(already_added, neighbor)) { + IGRAPH_CHECK(igraph_d_indheap_push(&heap, -VECTOR(*weights)[edgeno], to, edgeno)); + } + } + } /* for */ + } /* if !already_added */ + } /* while in the same component */ + } /* for all nodes */ + + igraph_vector_int_destroy(&adj); + igraph_d_indheap_destroy(&heap); + igraph_bitset_destroy(&already_added); + igraph_bitset_destroy(&added_edges); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/* Kruskal's algorithm */ + +static igraph_int_t get_comp(igraph_vector_int_t *comp, igraph_int_t i) { + igraph_int_t k = i; + for (;;) { + igraph_int_t next = VECTOR(*comp)[k]; + if (next == k) { + VECTOR(*comp)[i] = k; + return k; + } else { + k = next; + } + } +} + +static void merge_comp(igraph_vector_int_t *comp, igraph_int_t i, igraph_int_t j) { + igraph_int_t ci = get_comp(comp, i); + igraph_int_t cj = get_comp(comp, j); + VECTOR(*comp)[ci] = cj; +} + +/** + * \ingroup structural + * \function igraph_i_minimum_spanning_tree_kruskal + * \brief A minimum spanning tree of a weighted graph using Kruskal's method. + * + * Finds a spanning tree or spanning forest for which the sum of edge + * weights is the smallest. This function uses Kruskal's method for carrying + * out the computation. + * + * + * Directed graphs are treated as undirected for this computation. + * + * + * Reference: + * + * + * Kruskal, J. B.: + * On the shortest spanning subtree of a graph and the traveling salesman problem, + * Proc. Amer. Math. Soc. 7 (1956), 48-50 + * https://doi.org/10.1090%2FS0002-9939-1956-0078686-7 + * + * \param graph The graph object. Edge directions will be ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanning tree as + * a separate graph object. + * \param weights A vector containing the weights of the edges in the order + * of edge IDs. Weights must not be NaN. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory. + * \c IGRAPH_EINVAL, length of weight vector does not + * match number of edges, or NaN in weights. + * + * Time complexity: O(|E| log |E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \sa \ref igraph_minimum_spanning_tree() for a common interface to all + * minimum spanning tree algorithms. + * + * \example examples/simple/igraph_minimum_spanning_tree.c + */ + +static igraph_error_t igraph_i_minimum_spanning_tree_kruskal( + const igraph_t *graph, + igraph_vector_int_t *res, + const igraph_vector_t *weights) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t idx, comp; + igraph_int_t tree_edge_count; + int iter = 0; + + if (weights == NULL) { + return igraph_i_minimum_spanning_tree_unweighted(graph, res); + } + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Weight vector length does not match number of edges.", IGRAPH_EINVAL); + } + + if (igraph_vector_is_any_nan(weights)) { + IGRAPH_ERROR("Weights must not contain NaN values.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, no_of_edges); + IGRAPH_CHECK(igraph_vector_sort_ind(weights, &idx, IGRAPH_ASCENDING)); + + IGRAPH_CHECK(igraph_vector_int_init_range(&comp, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &comp); + + igraph_vector_int_clear(res); + IGRAPH_CHECK(igraph_vector_int_reserve(res, max_tree_edges(no_of_nodes, no_of_edges))); + + tree_edge_count = 0; + for (igraph_int_t i=0; i < no_of_edges; i++) { + igraph_int_t edge = VECTOR(idx)[i]; + igraph_int_t u = IGRAPH_FROM(graph, edge); + igraph_int_t v = IGRAPH_TO(graph, edge); + + igraph_int_t cu = get_comp(&comp, u); + igraph_int_t cv = get_comp(&comp, v); + + if (cu != cv) { + merge_comp(&comp, u, v); + igraph_vector_int_push_back(res, edge); /* reserved */ + tree_edge_count++; + } + + if (tree_edge_count == no_of_nodes - 1) { + /* We have enough edges for a tree. */ + break; + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 16); + } + + igraph_vector_int_destroy(&idx); + igraph_vector_int_destroy(&comp); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_minimum_spanning_tree + * \brief Calculates a minimum spanning tree of a graph. + * + * Finds a minimum weight spanning tree of the graph. If the graph is not + * connected then its minimum spanning forest is returned, i.e. the set + * of the minimum spanning trees of each component. + * + * + * Directed graphs are treated as undirected for this computation. + * + * + * This function is deterministic, i.e. it always returns the same + * spanning tree. See \ref igraph_random_spanning_tree() for the uniform + * random sampling of spanning trees of a graph. + * + * + * References: + * + * + * Prim, R.C.: Shortest connection networks and some + * generalizations, Bell System Technical + * Journal, Vol. 36, + * 1957, 1389--1401. + * https://doi.org/10.1002/j.1538-7305.1957.tb01515.x + * + * + * Kruskal, J. B.: + * On the shortest spanning subtree of a graph and the traveling salesman problem, + * Proc. Amer. Math. Soc. 7 (1956), 48-50 + * https://doi.org/10.1090%2FS0002-9939-1956-0078686-7 + * + * \param graph The graph object. Edge directions will be ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanning tree as + * a separate graph object. + * \param weights A vector containing the weights of the edges in the order + * of edge IDs. Weights must not be NaN. Supply \c NULL to treat all + * edges as having the same weight. + * \param method The type of the algorithm used. + * \clist + * \cli IGRAPH_MST_AUTOMATIC + * tries to select the best performing algorithm for the current graph. + * \cli IGRAPH_MST_UNWEIGHTED + * ignores edge weights and produces an arbitrary spanning tree. + * \cli IGRAPH_MST_PRIM + * uses Prim's algorithm. + * \cli IGRAPH_MST_KRUSKAL + * uses Kruskal's algorithm. + * \endclist + * \return Error code. + * + * Time complexity: See the functions implementing the specific algorithms. + * + * \sa \ref igraph_random_spanning_tree() to compute a random spanning tree + * instead of a minimum one. + * + * \example examples/simple/igraph_minimum_spanning_tree.c + */ +igraph_error_t igraph_minimum_spanning_tree( + const igraph_t *graph, igraph_vector_int_t *res, + const igraph_vector_t *weights, igraph_mst_algorithm_t method) +{ + if (method == IGRAPH_MST_AUTOMATIC) { + /* For now we use igraph_minimum_spanning_tree_kruskal() unconditionally + * for the weighted case; see benchmarks. */ + if (weights == NULL) { + method = IGRAPH_MST_UNWEIGHTED; + } else { + method = IGRAPH_MST_KRUSKAL; + } + } + switch (method) { + case IGRAPH_MST_UNWEIGHTED: + return igraph_i_minimum_spanning_tree_unweighted(graph, res); + case IGRAPH_MST_PRIM: + return igraph_i_minimum_spanning_tree_prim(graph, res, weights); + case IGRAPH_MST_KRUSKAL: + return igraph_i_minimum_spanning_tree_kruskal(graph, res, weights); + default: + IGRAPH_ERROR("Invalid method for minimum spanning tree.", IGRAPH_EINVAL); + } +} + + +/* igraph_random_spanning_tree */ + +/* Loop-erased random walk (LERW) implementation. + * res must be an initialized vector. The edge IDs of the spanning tree + * will be added to the end of it. res will not be cleared before doing this. + * + * The walk is started from vertex start. comp_size must be the size of the connected + * component containing start. + */ +static igraph_error_t igraph_i_lerw(const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t start, + igraph_int_t comp_size, igraph_vector_bool_t *visited, const igraph_inclist_t *il) { + igraph_int_t visited_count; + + IGRAPH_CHECK(igraph_vector_int_reserve(res, igraph_vector_int_size(res) + comp_size - 1)); + + VECTOR(*visited)[start] = true; + visited_count = 1; + + while (visited_count < comp_size) { + igraph_int_t degree, edge; + igraph_vector_int_t *edges; + + edges = igraph_inclist_get(il, start); + + /* choose a random edge */ + degree = igraph_vector_int_size(edges); + edge = VECTOR(*edges)[ RNG_INTEGER(0, degree - 1) ]; + + /* set 'start' to the next vertex */ + start = IGRAPH_OTHER(graph, edge, start); + + /* if the next vertex hasn't been visited yet, register the edge we just traversed */ + if (! VECTOR(*visited)[start]) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, edge)); + VECTOR(*visited)[start] = true; + visited_count++; + } + + IGRAPH_ALLOW_INTERRUPTION(); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_random_spanning_tree + * \brief Uniformly samples the spanning trees of a graph. + * + * Performs a loop-erased random walk on the graph to uniformly sample + * its spanning trees. Edge directions are ignored. + * + * + * Multi-graphs are supported, and edge multiplicities will affect the sampling + * frequency. For example, consider the 3-cycle graph 1=2-3-1, with two edges + * between vertices 1 and 2. Due to these parallel edges, the trees 1-2-3 + * and 3-1-2 will be sampled with multiplicity 2, while the tree + * 2-3-1 will be sampled with multiplicity 1. + * + * \param graph The input graph. Edge directions are ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanning tree as + * a separate graph object. + * \param vid This parameter is relevant if the graph is not connected. + * If negative, a random spanning forest of all components will be + * generated. Otherwise, it should be the ID of a vertex. A random + * spanning tree of the component containing the vertex will be + * generated. + * + * \return Error code. + * + * \sa \ref igraph_minimum_spanning_tree(), \ref igraph_random_walk() + * + */ +igraph_error_t igraph_random_spanning_tree(const igraph_t *graph, igraph_vector_int_t *res, igraph_int_t vid) { + igraph_inclist_t il; + igraph_vector_bool_t visited; + igraph_int_t vcount = igraph_vcount(graph); + + if (vid >= vcount) { + IGRAPH_ERROR("Invalid vertex ID given for random spanning tree.", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_CHECK(igraph_vector_bool_init(&visited, vcount)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &visited); + + igraph_vector_int_clear(res); + + if (vid < 0) { /* generate random spanning forest: consider each component separately */ + igraph_vector_int_t membership, csize; + igraph_int_t comp_count; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0); + + IGRAPH_CHECK(igraph_connected_components(graph, &membership, &csize, &comp_count, IGRAPH_WEAK)); + + /* for each component ... */ + for (igraph_int_t i = 0; i < comp_count; ++i) { + /* ... find a vertex to start the LERW from */ + igraph_int_t j = 0; + while (VECTOR(membership)[j] != i) { + ++j; + } + + IGRAPH_CHECK(igraph_i_lerw(graph, res, j, VECTOR(csize)[i], &visited, &il)); + } + + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&csize); + IGRAPH_FINALLY_CLEAN(2); + } else { /* consider the component containing vid */ + igraph_vector_int_t comp_vertices; + igraph_int_t comp_size; + + /* we measure the size of the component */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&comp_vertices, 0); + IGRAPH_CHECK(igraph_subcomponent(graph, &comp_vertices, vid, IGRAPH_ALL)); + comp_size = igraph_vector_int_size(&comp_vertices); + igraph_vector_int_destroy(&comp_vertices); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_i_lerw(graph, res, vid, comp_size, &visited, &il)); + } + + igraph_vector_bool_destroy(&visited); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/add_edge.c b/src/operators/add_edge.c new file mode 100644 index 0000000..5c993b8 --- /dev/null +++ b/src/operators/add_edge.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_interface.h" + +/** + * \function igraph_add_edge + * \brief Adds a single edge to a graph. + * + * + * For directed graphs the edge points from \p from to \p to. + * + * + * Note that if you want to add many edges to a big graph, then it is + * inefficient to add them one by one, it is better to collect them into + * a vector and add all of them via a single \ref igraph_add_edges() call. + * \param graph The graph. + * \param from The id of the first vertex of the edge. + * \param to The id of the second vertex of the edge. + * \return Error code. + * + * \sa \ref igraph_add_edges() to add many edges, \ref + * igraph_delete_edges() to remove edges and \ref + * igraph_add_vertices() to add vertices. + * + * Time complexity: O(|V|+|E|), the number of edges plus the number of + * vertices. + */ +igraph_error_t igraph_add_edge(igraph_t *graph, igraph_int_t from, igraph_int_t to) { + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2); + + VECTOR(edges)[0] = from; + VECTOR(edges)[1] = to; + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/operators/complementer.c b/src/operators/complementer.c new file mode 100644 index 0000000..6bbf9f1 --- /dev/null +++ b/src/operators/complementer.c @@ -0,0 +1,100 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" +#include "core/interruption.h" + +/** + * \function igraph_complementer + * \brief Creates the complementer of a graph. + * + * The complementer graph means that all edges which are + * not part of the original graph will be included in the result. + * + * \param res Pointer to an uninitialized graph object. + * \param graph The original graph. + * \param loops Whether to add loop edges to the complementer graph. + * \return Error code. + * \sa \ref igraph_union(), \ref igraph_intersection() and \ref + * igraph_difference(). + * + * Time complexity: O(|V|+|E1|+|E2|), |V| is the number of + * vertices in the graph, |E1| is the number of edges in the original + * and |E2| in the complementer graph. + * + * \example examples/simple/igraph_complementer.c + */ +igraph_error_t igraph_complementer(igraph_t *res, const igraph_t *graph, + igraph_bool_t loops) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t edges; + igraph_vector_int_t neis; + igraph_int_t i, j; + igraph_int_t zero = 0, *limit; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + if (igraph_is_directed(graph)) { + limit = &zero; + } else { + limit = &i; + } + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + if (loops) { + for (j = no_of_nodes - 1; j >= *limit; j--) { + if (igraph_vector_int_empty(&neis) || j > igraph_vector_int_tail(&neis)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } else { + igraph_vector_int_pop_back(&neis); + } + } + } else { + for (j = no_of_nodes - 1; j >= *limit; j--) { + if (igraph_vector_int_empty(&neis) || j > igraph_vector_int_tail(&neis)) { + if (i != j) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, j)); + } + } else { + igraph_vector_int_pop_back(&neis); + } + } + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, igraph_is_directed(graph))); + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&neis); + IGRAPH_CHECK(igraph_i_attribute_copy(res, graph, true, true, /* edges= */ false)); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} diff --git a/src/operators/compose.c b/src/operators/compose.c new file mode 100644 index 0000000..bf185b8 --- /dev/null +++ b/src/operators/compose.c @@ -0,0 +1,131 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "core/interruption.h" + +/** + * \function igraph_compose + * \brief Calculates the composition of two graphs. + * + * The composition of graphs contains the same number of vertices as + * the bigger graph of the two operands. It contains an (i,j) edge if + * and only if there is a k vertex, such that the first graph + * contains an (i,k) edge and the second graph a (k,j) edge. + * + * + * This is of course exactly the composition of two binary relations. + * + * + * The two graphs must have the same directedness, otherwise the function + * returns with an error. Note that for undirected graphs the two relations + * are by definition symmetric. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param g1 The first operand, a graph object. + * \param g2 The second operand, another graph object. + * \param edge_map1 If not a null pointer, then it must be a pointer + * to an initialized vector, and a mapping from the edges of + * the result graph to the edges of the first graph is stored + * here. + * \param edge_map2 If not a null pointer, then it must be a pointer + * to an initialized vector, and a mapping from the edges of + * the result graph to the edges of the second graph is stored + * here. + * \return Error code. + * + * Time complexity: O(|V|*d1*d2), |V| is the number of vertices in the + * first graph, d1 and d2 the average degree in the first and second + * graphs. + * + * \example examples/simple/igraph_compose.c + */ +igraph_error_t igraph_compose(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2, + igraph_vector_int_t *edge_map1, + igraph_vector_int_t *edge_map2) { + + const igraph_int_t no_of_nodes_left = igraph_vcount(g1); + const igraph_int_t no_of_nodes_right = igraph_vcount(g2); + const igraph_bool_t directed = igraph_is_directed(g1); + const igraph_int_t no_of_nodes = no_of_nodes_left > no_of_nodes_right ? + no_of_nodes_left : no_of_nodes_right; + igraph_vector_int_t edges; + igraph_vector_int_t neis1, neis2; + + if (directed != igraph_is_directed(g2)) { + IGRAPH_ERROR("Cannot compose directed and undirected graph.", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis1, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis2, 0); + + if (edge_map1) { + igraph_vector_int_clear(edge_map1); + } + if (edge_map2) { + igraph_vector_int_clear(edge_map2); + } + + for (igraph_int_t i = 0; i < no_of_nodes_left; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_incident(g1, &neis1, i, IGRAPH_OUT, IGRAPH_LOOPS)); + while (!igraph_vector_int_empty(&neis1)) { + const igraph_int_t con = igraph_vector_int_pop_back(&neis1); + const igraph_int_t v1 = IGRAPH_OTHER(g1, con, i); + if (v1 < no_of_nodes_right) { + IGRAPH_CHECK(igraph_incident(g2, &neis2, v1, IGRAPH_OUT, IGRAPH_LOOPS)); + } else { + continue; + } + while (!igraph_vector_int_empty(&neis2)) { + const igraph_int_t con2 = igraph_vector_int_pop_back(&neis2); + const igraph_int_t v2 = IGRAPH_OTHER(g2, con2, v1); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v2)); + if (edge_map1) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_map1, con)); + } + if (edge_map2) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_map2, con2)); + } + } + } + } + + igraph_vector_int_destroy(&neis1); + igraph_vector_int_destroy(&neis2); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/operators/connect_neighborhood.c b/src/operators/connect_neighborhood.c new file mode 100644 index 0000000..0697861 --- /dev/null +++ b/src/operators/connect_neighborhood.c @@ -0,0 +1,318 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_adjlist.h" +#include "igraph_error.h" +#include "igraph_operators.h" + +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "graph/attributes.h" + +/** + * \function igraph_connect_neighborhood + * \brief Connects each vertex to its neighborhood. + * + * This function adds new edges to the input graph. Each vertex is connected + * to all vertices reachable by at most \p order steps from it + * (unless a connection already existed). + * + * + * Note that the input graph is modified in place, no + * new graph is created. Call \ref igraph_copy() if you want to keep + * the original graph as well. + * + * + * For undirected graphs reachability is always + * symmetric: if vertex A can be reached from vertex B in at + * most \p order steps, then the opposite is also true. Only one + * undirected (A,B) edge will be added in this case. + * + * \param graph The input graph. It will be modified in-place. + * \param order Integer constant, it gives the distance within which + * the vertices will be connected to the source vertex. + * \param mode Constant, it specifies how the neighborhood search is + * performed for directed graphs. If \c IGRAPH_OUT then vertices + * reachable from the source vertex will be connected, \c IGRAPH_IN + * is the opposite. If \c IGRAPH_ALL then the directed graph is + * considered as an undirected one. + * \return Error code. + * + * \sa \ref igraph_graph_power() to compute the kth power of a graph; + * \ref igraph_square_lattice() uses this function to connect the + * neighborhood of the vertices. + * + * Time complexity: O(|V|*d^k), |V| is the number of vertices in the + * graph, d is the average degree and k is the \p order argument. + */ +igraph_error_t igraph_connect_neighborhood(igraph_t *graph, igraph_int_t order, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vector_int_t edges; + igraph_int_t i, j, in; + igraph_int_t *added; + igraph_vector_int_t neis; + + if (order < 0) { + IGRAPH_ERRORF("Order must not be negative, found %" IGRAPH_PRId ".", + IGRAPH_EINVAL, order); + } + + if (order < 2) { + IGRAPH_WARNING("Order smaller than two, graph will be unchanged."); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Cannot connect neighborhood."); + + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + for (i = 0; i < no_of_nodes; i++) { + added[i] = i + 1; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, i, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + in = igraph_vector_int_size(&neis); + if (order > 1) { + for (j = 0; j < in; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 1)); + } + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + igraph_int_t n; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + n = igraph_vector_int_size(&neis); + + if (actdist < order - 1) { + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (mode != IGRAPH_ALL || i < nei) { + if (mode == IGRAPH_IN) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + } + } + } + } + } else { + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (mode != IGRAPH_ALL || i < nei) { + if (mode == IGRAPH_IN) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + } + } + } + } + } + + } /* while q not empty */ + } /* for i < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&q); + igraph_free(added); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_add_edges(graph, &edges, NULL)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_graph_power + * \brief The k-th power of a graph. + * + * The k-th power of a graph G is a simple graph where vertex \c u is connected to + * \c v by a single edge if \c v is reachable from \c u in G within at most k steps. + * By convention, the zeroth power of a graph has no edges. The first power is + * identical to the original graph, except that multiple edges and self-loops + * are removed. + * + * + * Graph power is usually defined only for undirected graphs. igraph extends the concept + * to directed graphs. To ignore edge directions in the input, set the \p directed + * parameter to \c false. In this case, the result will be an undirected graph. + * + * + * Graph and vertex attributes are preserved, but edge attributes are discarded. + * + * \param graph The input graph. + * \param res The graph power of the given \p order. + * \param order Non-negative integer, the power to raise the graph to. + * In other words, vertices within a distance \p order will be connected. + * \param directed Whether to take edge directions into account. + * \return Error code. + * + * \sa \ref igraph_connect_neighborhood() to connect each vertex to its + * neighborhood, modifying a graph in-place. + * + * Time complexity: O(|V|*d^k), |V| is the number of vertices in the + * graph, d is the average degree and k is the \p order argument. + */ +igraph_error_t igraph_graph_power(const igraph_t *graph, igraph_t *res, + igraph_int_t order, igraph_bool_t directed) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t edges; + igraph_adjlist_t al; + igraph_bool_t dir = igraph_is_directed(graph) && directed; + igraph_neimode_t mode = dir ? IGRAPH_OUT : IGRAPH_ALL; + + if (order < 0) { + IGRAPH_ERRORF("Order must not be negative, found %" IGRAPH_PRId ".", + IGRAPH_EINVAL, order); + } + + IGRAPH_CHECK(igraph_empty(res, no_of_nodes, dir)); + IGRAPH_CHECK(igraph_i_attribute_copy(res, graph, true, true, /* edges= */ false)); + if (order == 0) { + return IGRAPH_SUCCESS; + } + + /* Initialize res with a copy of the graph, but with multi-edges and self-loops removed. + * Also convert the graph to undirected if this is requested. */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + /* Reserve initial space for no_of_edges. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges); + igraph_vector_int_clear(&edges); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *tmp = igraph_adjlist_get(&al, i); + for (igraph_int_t j = 0; j < igraph_vector_int_size(tmp); j++) { + if (dir || i < VECTOR(*tmp)[j]) { + igraph_vector_int_push_back(&edges, i); /* initial space reserved */ + igraph_vector_int_push_back(&edges, VECTOR(*tmp)[j]); /* initial space reserved */ + } + } + } + + if (order > 1) { + /* order > 1, so add more edges. */ + + igraph_int_t d_i, d_actnode; + igraph_int_t *added; + const igraph_vector_int_t *neis; + igraph_dqueue_int_t q; + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory for graph power."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + added[i] = i + 1; + neis = igraph_adjlist_get(&al, i); + d_i = igraph_vector_int_size(neis); + + for (igraph_int_t j = 0; j < d_i; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 1)); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + neis = igraph_adjlist_get(&al, actnode); + d_actnode = igraph_vector_int_size(neis); + + if (actdist < order - 1) { + for (igraph_int_t j = 0; j < d_actnode; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (dir || i < nei) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + } + } + } + } else { + for (igraph_int_t j = 0; j < d_actnode; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (dir || i < nei) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, nei)); + } + } + } + } + + } /* while q not empty */ + } /* for i < no_of_nodes */ + + igraph_dqueue_int_destroy(&q); + igraph_free(added); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_add_edges(res, &edges, 0)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/contract.c b/src/operators/contract.c new file mode 100644 index 0000000..1b32de5 --- /dev/null +++ b/src/operators/contract.c @@ -0,0 +1,148 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" + +/** + * \function igraph_contract_vertices + * \brief Replace multiple vertices with a single one. + * + * This function modifies the graph by merging several vertices + * into one. The vertices in the modified graph correspond + * to groups of vertices in the input graph. No edges are removed, + * thus the modified graph will typically have self-loops + * (corresponding to in-group edges) and multi-edges + * (corresponding to multiple connections between two groups). + * Use \ref igraph_simplify() to eliminate self-loops and + * merge multi-edges. + * + * \param graph The input graph. It will be modified in-place. + * \param mapping A vector giving the mapping. For each + * vertex in the original graph, it should contain + * its desired ID in the result graph. In order not to create + * "orphan vertices" that have no corresponding vertices + * in the original graph, ensure that the IDs are consecutive + * integers starting from zero. + * \param vertex_comb What to do with the vertex attributes. + * \c NULL means that vertex attributes are not kept + * after the contraction (not even for unaffected + * vertices). See the igraph manual section about attributes + * for details. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number + * or vertices plus edges. + * + * \example examples/simple/igraph_contract_vertices.c + */ + +igraph_error_t igraph_contract_vertices(igraph_t *graph, + const igraph_vector_int_t *mapping, + const igraph_attribute_combination_t *vertex_comb) { + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t vattr = vertex_comb && igraph_has_attribute_table(); + igraph_t res; + igraph_int_t last; + igraph_int_t no_new_vertices; + + if (igraph_vector_int_size(mapping) != no_of_nodes) { + IGRAPH_ERRORF("Mapping vector length (%" IGRAPH_PRId ") " + "not equal to number of nodes (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_int_size(mapping), no_of_nodes); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + + if (no_of_nodes > 0) { + last = igraph_vector_int_max(mapping); + } else { + /* Ensure that no_new_vertices will be zero + * when the input graph has no vertices. */ + last = -1; + } + + for (igraph_int_t edge = 0; edge < no_of_edges; edge++) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + + igraph_int_t nfrom = VECTOR(*mapping)[from]; + igraph_int_t nto = VECTOR(*mapping)[to]; + + igraph_vector_int_push_back(&edges, nfrom); /* reserved */ + igraph_vector_int_push_back(&edges, nto); /* reserved */ + + if (nfrom > last) { + last = nfrom; + } + if (nto > last) { + last = nto; + } + } + + no_new_vertices = last + 1; + + IGRAPH_CHECK(igraph_create(&res, &edges, no_new_vertices, + igraph_is_directed(graph))); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &res); + + IGRAPH_CHECK(igraph_i_attribute_copy(&res, graph, true, /* vertex= */ false, true)); + + if (vattr) { + igraph_vector_int_list_t merges; + igraph_vector_int_t sizes; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&merges, no_new_vertices); + IGRAPH_VECTOR_INT_INIT_FINALLY(&sizes, no_new_vertices); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t to = VECTOR(*mapping)[i]; + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&merges, to); + VECTOR(sizes)[to] += 1; + IGRAPH_CHECK(igraph_vector_int_push_back(v, i)); + } + + IGRAPH_CHECK(igraph_i_attribute_combine_vertices(graph, &res, + &merges, + vertex_comb)); + + igraph_vector_int_destroy(&sizes); + igraph_vector_int_list_destroy(&merges); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = res; + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/difference.c b/src/operators/difference.c new file mode 100644 index 0000000..ac6517d --- /dev/null +++ b/src/operators/difference.c @@ -0,0 +1,181 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_adjlist.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" +#include "core/interruption.h" + +/** + * \function igraph_difference + * \brief Calculates the difference of two graphs. + * + * The number of vertices in the result is the number of vertices in + * the original graph, i.e. the left, first operand. In the results + * graph only edges will be included from \p orig which are not + * present in \p sub. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param orig The left operand of the operator, a graph object. + * \param sub The right operand of the operator, a graph object. + * \return Error code. + * \sa \ref igraph_intersection() and \ref igraph_union() for other + * operators. + * + * Time complexity: O(|V|+|E|), |V| is the number vertices in + * the smaller graph, |E| is the + * number of edges in the result graph. + * + * \example examples/simple/igraph_difference.c + */ +igraph_error_t igraph_difference(igraph_t *res, + const igraph_t *orig, const igraph_t *sub) { + + /* Quite nasty, but we will use that an edge adjacency list + contains the vertices according to the order of the + vertex IDs at the "other" end of the edge. */ + + igraph_int_t no_of_nodes_orig = igraph_vcount(orig); + igraph_int_t no_of_nodes_sub = igraph_vcount(sub); + igraph_int_t no_of_nodes = no_of_nodes_orig; + igraph_int_t smaller_nodes; + igraph_bool_t directed = igraph_is_directed(orig); + igraph_vector_int_t edges; + igraph_vector_int_t edge_ids; + igraph_vector_int_t *nei1, *nei2; + igraph_inclist_t inc_orig, inc_sub; + igraph_int_t i; + igraph_int_t v1, v2; + + if (directed != igraph_is_directed(sub)) { + IGRAPH_ERROR("Cannot subtract directed and undirected graphs.", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edge_ids, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_inclist_init(orig, &inc_orig, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inc_orig); + IGRAPH_CHECK(igraph_inclist_init(sub, &inc_sub, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inc_sub); + + smaller_nodes = no_of_nodes_orig > no_of_nodes_sub ? + no_of_nodes_sub : no_of_nodes_orig; + + for (i = 0; i < smaller_nodes; i++) { + igraph_int_t n1, n2, e1, e2; + IGRAPH_ALLOW_INTERRUPTION(); + nei1 = igraph_inclist_get(&inc_orig, i); + nei2 = igraph_inclist_get(&inc_sub, i); + n1 = igraph_vector_int_size(nei1) - 1; + n2 = igraph_vector_int_size(nei2) - 1; + while (n1 >= 0 && n2 >= 0) { + e1 = VECTOR(*nei1)[n1]; + e2 = VECTOR(*nei2)[n2]; + v1 = IGRAPH_OTHER(orig, e1, i); + v2 = IGRAPH_OTHER(sub, e2, i); + + if (!directed && v1 < i) { + n1--; + } else if (!directed && v2 < i) { + n2--; + } else if (v1 > v2) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v1)); + n1--; + /* handle loop edges properly in undirected graphs */ + if (!directed && i == v1) { + n1--; + } + } else if (v2 > v1) { + n2--; + } else { + n1--; + n2--; + } + } + + /* Copy remaining edges */ + while (n1 >= 0) { + e1 = VECTOR(*nei1)[n1]; + v1 = IGRAPH_OTHER(orig, e1, i); + if (directed || v1 >= i) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v1)); + + /* handle loop edges properly in undirected graphs */ + if (!directed && v1 == i) { + n1--; + } + } + n1--; + } + } + + /* copy remaining edges, use the previous value of 'i' */ + for (; i < no_of_nodes_orig; i++) { + igraph_int_t n1, e1; + nei1 = igraph_inclist_get(&inc_orig, i); + n1 = igraph_vector_int_size(nei1) - 1; + while (n1 >= 0) { + e1 = VECTOR(*nei1)[n1]; + v1 = IGRAPH_OTHER(orig, e1, i); + if (directed || v1 >= i) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v1)); + + /* handle loop edges properly in undirected graphs */ + if (!directed && v1 == i) { + n1--; + } + } + n1--; + } + } + + igraph_inclist_destroy(&inc_sub); + igraph_inclist_destroy(&inc_orig); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Attributes */ + if (orig->attr) { + IGRAPH_CHECK(igraph_i_attribute_copy(res, orig, true, true, /* edge= */ false)); + IGRAPH_CHECK(igraph_i_attribute_permute_edges(orig, res, &edge_ids)); + } + + igraph_vector_int_destroy(&edge_ids); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/disjoint_union.c b/src/operators/disjoint_union.c new file mode 100644 index 0000000..f288925 --- /dev/null +++ b/src/operators/disjoint_union.c @@ -0,0 +1,195 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_disjoint_union + * \brief Creates the union of two disjoint graphs. + * + * First the vertices of the second graph will be relabeled with new + * vertex IDs to have two disjoint sets of vertex IDs, then the union + * of the two graphs will be formed. + * If the two graphs have |V1| and |V2| vertices and |E1| and |E2| + * edges respectively then the new graph will have |V1|+|V2| vertices + * and |E1|+|E2| edges. + * + * + * The vertex and edge ordering of the graphs will be preserved. + * In other words, the vertex and edge IDs of the first graph map to + * identical values in the new graph, while the vertex and edge IDs + * of the second graph map to IDs incremented by the vertex and edge + * count of the first graph. + * + * + * Both graphs need to have the same directedness, i.e. either both + * directed or both undirected. + * + * + * The current version of this function cannot handle graph, vertex + * and edge attributes, they will be lost. + * + * \param res Pointer to an uninitialized graph object, the result + * will stored here. + * \param left The first graph. + * \param right The second graph. + * \return Error code. + * \sa \ref igraph_disjoint_union_many() for creating the disjoint union + * of more than two graphs, \ref igraph_union() for non-disjoint + * union. + * + * Time complexity: O(|V1|+|V2|+|E1|+|E2|). + * + * \example examples/simple/igraph_disjoint_union.c + */ +igraph_error_t igraph_disjoint_union(igraph_t *res, + const igraph_t *left, + const igraph_t *right) { + + const igraph_int_t no_of_nodes_left = igraph_vcount(left); + const igraph_int_t no_of_nodes_right = igraph_vcount(right); + const igraph_int_t no_of_edges_left = igraph_ecount(left); + const igraph_int_t no_of_edges_right = igraph_ecount(right); + igraph_int_t no_of_nodes; /* vertex count of the result */ + igraph_int_t no_of_edges2; /* twice the edge count of the result */ + igraph_vector_int_t edges; + igraph_bool_t directed_left = igraph_is_directed(left); + igraph_int_t from, to; + + if (directed_left != igraph_is_directed(right)) { + IGRAPH_ERROR("Cannot create disjoint union of directed and undirected graphs.", + IGRAPH_EINVAL); + } + + /* The edge count of an existing graph object is always safe to multiply by 2. */ + IGRAPH_SAFE_ADD(no_of_nodes_left, no_of_nodes_right, &no_of_nodes); + IGRAPH_SAFE_ADD(2*no_of_edges_left, 2*no_of_edges_right, &no_of_edges2); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + for (igraph_int_t i = 0; i < no_of_edges_left; i++) { + from = IGRAPH_FROM(left, i); + to = IGRAPH_TO(left, i); + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + } + for (igraph_int_t i = 0; i < no_of_edges_right; i++) { + from = IGRAPH_FROM(right, i); + to = IGRAPH_TO(right, i); + igraph_vector_int_push_back(&edges, from + no_of_nodes_left); /* reserved */ + igraph_vector_int_push_back(&edges, to + no_of_nodes_left); /* reserved */ + } + + IGRAPH_CHECK(igraph_create(res, &edges, + no_of_nodes, + directed_left)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_disjoint_union_many + * \brief The disjoint union of many graphs. + * + * First the vertices in the graphs will be relabeled with new vertex + * IDs to have pairwise disjoint vertex ID sets and then the union of + * the graphs is formed. + * The number of vertices and edges in the result is the total number + * of vertices and edges in the graphs. + * + * + * The vertex and edge ordering of the input graphs is preserved in + * the output graph. + * + * + * All graphs need to have the same directedness, i.e. either all + * directed or all undirected. If the graph list has length zero, + * the result will be a \em directed graph with no vertices. + * + * + * The current version of this function cannot handle graph, vertex + * and edge attributes, they will be lost. + * + * \param res Pointer to an uninitialized graph object, the result of + * the operation will be stored here. + * \param graphs Pointer vector, contains pointers to initialized + * graph objects. + * \return Error code. + * \sa \ref igraph_disjoint_union() for an easier syntax if you have + * only two graphs, \ref igraph_union_many() for non-disjoint union. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the result. + */ +igraph_error_t igraph_disjoint_union_many(igraph_t *res, + const igraph_vector_ptr_t *graphs) { + igraph_int_t no_of_graphs = igraph_vector_ptr_size(graphs); + igraph_bool_t directed = true; + igraph_vector_int_t edges; + igraph_int_t no_of_edges2 = 0; /* twice the edge count of the result */ + igraph_int_t shift = 0; + igraph_t *graph; + igraph_int_t from, to; + + if (no_of_graphs != 0) { + graph = VECTOR(*graphs)[0]; + directed = igraph_is_directed(graph); + for (igraph_int_t i = 0; i < no_of_graphs; i++) { + graph = VECTOR(*graphs)[i]; + IGRAPH_SAFE_ADD(no_of_edges2, 2*igraph_ecount(graph), &no_of_edges2); + if (directed != igraph_is_directed(graph)) { + IGRAPH_ERROR("Cannot create disjoint union of directed and undirected graphs.", + IGRAPH_EINVAL); + } + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges2)); + + for (igraph_int_t i = 0; i < no_of_graphs; i++) { + igraph_int_t ec; + graph = VECTOR(*graphs)[i]; + ec = igraph_ecount(graph); + for (igraph_int_t j = 0; j < ec; j++) { + from = IGRAPH_FROM(graph, j); + to = IGRAPH_TO(graph, j); + igraph_vector_int_push_back(&edges, from + shift); /* reserved */ + igraph_vector_int_push_back(&edges, to + shift); /* reserved */ + } + IGRAPH_SAFE_ADD(shift, igraph_vcount(graph), &shift); + } + + IGRAPH_CHECK(igraph_create(res, &edges, shift, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/intersection.c b/src/operators/intersection.c new file mode 100644 index 0000000..dc936ab --- /dev/null +++ b/src/operators/intersection.c @@ -0,0 +1,286 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_qsort.h" +#include "igraph_vector_list.h" + +#include "operators/misc_internal.h" + +/** + * \function igraph_intersection + * \brief Collect the common edges from two graphs. + * + * The result graph contains only edges present both in the first and + * the second graph. The number of vertices in the result graph is the + * same as the larger from the two arguments. + * + * + * The directedness of the operand graphs must be the same. + * + * + * Edge multiplicities are handled by taking the \em smaller of the two + * multiplicities in the input graphs. In other words, if the first graph + * has N edges between a vertex pair (u, v) and the second graph has M edges, + * the result graph will have min(N, M) edges between them. + * + * \param res Pointer to an uninitialized graph object. This will + * contain the result of the operation. + * \param left The first operand, a graph object. + * \param right The second operand, a graph object. + * \param edge_map1 Null pointer, or an initialized vector. + * If the latter, then a mapping from the edges of the result graph, to + * the edges of the \p left input graph is stored here. For the edges that + * are not in the intersection, -1 is stored. + * \param edge_map2 Null pointer, or an initialized vector. The same + * as \p edge_map1, but for the \p right input graph. For the edges that + * are not in the intersection, -1 is stored. + * \return Error code. + * \sa \ref igraph_intersection_many() to calculate the intersection + * of many graphs at once, \ref igraph_union(), \ref + * igraph_difference() for other operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of nodes, |E| + * is the number of edges in the smaller graph of the two. (The one + * containing less vertices is considered smaller.) + * + * \example examples/simple/igraph_intersection.c + */ +igraph_error_t igraph_intersection( + igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2) { + return igraph_i_merge(res, IGRAPH_MERGE_MODE_INTERSECTION, left, right, + edge_map1, edge_map2); +} + +/** + * \function igraph_intersection_many + * \brief The intersection of more than two graphs. + * + * This function calculates the intersection of the graphs stored in + * the \p graphs argument. Only those edges will be included in the + * result graph which are part of every graph in \p graphs. + * + * + * The number of vertices in the result graph will be the maximum + * number of vertices in the argument graphs. + * + * + * The directedness of the argument graphs must be the same. + * If the graph list has length zero, the result will be a \em directed + * graph with no vertices. + * + * + * Edge multiplicities are handled by taking the \em minimum multiplicity of the + * all multiplicities for the same vertex pair (u, v) in the input graphs; this + * will be the multiplicity of (u, v) in the result graph. + * + * \param res Pointer to an uninitialized graph object, the result of + * the operation will be stored here. + * \param graphs Pointer vector, contains pointers to graphs objects, + * the operands of the intersection operator. + * \param edgemaps If not a null pointer, then it must be an initialized + * list of integer vectors, and the mappings of edges from the graphs to + * the result graph will be stored here, in the same order as + * \p graphs. Each mapping is stored in a separate + * \type igraph_vector_int_t object. For the edges that are not in + * the intersection, -1 is stored. + * \return Error code. + * \sa \ref igraph_intersection() for the intersection of two graphs, + * \ref igraph_union_many(), \ref igraph_union() and \ref + * igraph_difference() for other operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices, + * |E| is the number of edges in the smallest graph (i.e. the graph having + * the less vertices). + */ +igraph_error_t igraph_intersection_many( + igraph_t *res, const igraph_vector_ptr_t *graphs, + igraph_vector_int_list_t *edgemaps +) { + + igraph_int_t no_of_graphs = igraph_vector_ptr_size(graphs); + igraph_int_t no_of_nodes = 0; + igraph_bool_t directed = true; + igraph_vector_int_t edges; + igraph_vector_int_list_t edge_vects, order_vects; + igraph_int_t i, j, tailfrom = no_of_graphs > 0 ? 0 : -1, tailto = -1; + igraph_vector_int_t no_edges; + igraph_bool_t allne = no_of_graphs > 0; + igraph_bool_t allsame = false; + igraph_int_t idx = 0; + + /* Check directedness */ + if (no_of_graphs != 0) { + directed = igraph_is_directed(VECTOR(*graphs)[0]); + } + for (i = 1; i < no_of_graphs; i++) { + if (directed != igraph_is_directed(VECTOR(*graphs)[i])) { + IGRAPH_ERROR("Cannot create intersection of directed and undirected graphs.", + IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_init(&no_edges, no_of_graphs)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &no_edges); + + /* Calculate number of nodes, query number of edges */ + for (i = 0; i < no_of_graphs; i++) { + igraph_int_t n = igraph_vcount(VECTOR(*graphs)[i]); + if (n > no_of_nodes) { + no_of_nodes = n; + } + VECTOR(no_edges)[i] = igraph_ecount(VECTOR(*graphs)[i]); + allne = allne && VECTOR(no_edges)[i] > 0; + } + + if (edgemaps) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edgemaps, no_of_graphs)); + for (i = 0; i < no_of_graphs; i++) { + igraph_vector_int_t* v = igraph_vector_int_list_get_ptr(edgemaps, i); + IGRAPH_CHECK(igraph_vector_int_resize(v, VECTOR(no_edges)[i])); + igraph_vector_int_fill(v, -1); + } + } + + /* Allocate memory for the edge lists and their index vectors */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&edge_vects, no_of_graphs); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&order_vects, no_of_graphs); + + /* Query and sort the edge lists */ + for (i = 0; i < no_of_graphs; i++) { + igraph_int_t k, j, n = VECTOR(no_edges)[i]; + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, i); + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, i); + IGRAPH_CHECK(igraph_get_edgelist(VECTOR(*graphs)[i], ev, /*bycol=*/ false)); + if (!directed) { + for (k = 0, j = 0; k < n; k++, j += 2) { + if (VECTOR(*ev)[j] > VECTOR(*ev)[j + 1]) { + igraph_int_t tmp = VECTOR(*ev)[j]; + VECTOR(*ev)[j] = VECTOR(*ev)[j + 1]; + VECTOR(*ev)[j + 1] = tmp; + } + } + } + IGRAPH_CHECK(igraph_vector_int_resize(order, n)); + for (k = 0; k < n; k++) { + VECTOR(*order)[k] = k; + } + igraph_qsort_r(VECTOR(*order), n, sizeof(VECTOR(*order)[0]), ev, + igraph_i_order_edgelist_cmp); + } + + /* Do the merge. We work from the end of the edge lists, + because then we don't have to keep track of where we are right + now in the edge and order lists. We find the "largest" edge, + and if it is present in all graphs, then we copy it to the + result. We remove all instances of this edge. */ + + while (allne) { + + /* Look for the smallest tail element */ + for (j = 0, tailfrom = IGRAPH_INTEGER_MAX, tailto = IGRAPH_INTEGER_MAX; j < no_of_graphs; j++) { + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, j); + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, j); + igraph_int_t edge = igraph_vector_int_tail(order); + igraph_int_t from = VECTOR(*ev)[2 * edge]; + igraph_int_t to = VECTOR(*ev)[2 * edge + 1]; + if (from < tailfrom || (from == tailfrom && to < tailto)) { + tailfrom = from; tailto = to; + } + } + + /* OK, now remove all elements from the tail(s) that are bigger + than the smallest tail element. */ + for (j = 0, allsame = true; j < no_of_graphs; j++) { + igraph_int_t from = -1, to = -1; + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, j); + while (true) { + igraph_int_t edge = igraph_vector_int_tail(order); + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, j); + from = VECTOR(*ev)[2 * edge]; + to = VECTOR(*ev)[2 * edge + 1]; + if (from > tailfrom || (from == tailfrom && to > tailto)) { + igraph_vector_int_pop_back(order); + if (igraph_vector_int_empty(order)) { + allne = false; + break; + } + } else { + break; + } + } + if (from != tailfrom || to != tailto) { + allsame = false; + } + } + + /* Add the edge, if the smallest tail element was present + in all graphs. */ + if (allsame) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tailfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tailto)); + } + + /* Drop edges matching the smallest tail elements + from the order vectors, build edge maps */ + if (allne) { + for (j = 0; j < no_of_graphs; j++) { + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, j); + igraph_int_t edge = igraph_vector_int_tail(order); + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, j); + igraph_int_t from = VECTOR(*ev)[2 * edge]; + igraph_int_t to = VECTOR(*ev)[2 * edge + 1]; + if (from == tailfrom && to == tailto) { + igraph_vector_int_pop_back(order); + if (igraph_vector_int_empty(order)) { + allne = false; + } + if (edgemaps && allsame) { + igraph_vector_int_t *map = igraph_vector_int_list_get_ptr(edgemaps, j); + VECTOR(*map)[edge] = idx; + } + } + } + if (allsame) { + idx++; + } + } + + } /* while allne */ + + igraph_vector_int_list_destroy(&order_vects); + igraph_vector_int_list_destroy(&edge_vects); + igraph_vector_int_destroy(&no_edges); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/join.c b/src/operators/join.c new file mode 100644 index 0000000..7570b75 --- /dev/null +++ b/src/operators/join.c @@ -0,0 +1,106 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_operators.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +/** + * \function igraph_join + * \brief Creates the join of two disjoint graphs. + * + * First the vertices of the second graph will be relabeled with new + * vertex IDs to have two disjoint sets of vertex IDs, then the union + * of the two graphs will be formed. Finally, the vertces from the + * first graph will have edges added to each vertex from the second. + * If the two graphs have |V1| and |V2| vertices and |E1| and |E2| + * edges respectively then the new graph will have |V1|+|V2| vertices + * and |E1|+|E2|+|V1|*|V2| edges. + * + * + * The vertex ordering of the graphs will be preserved. + * In other words, the vertex IDs of the first graph map to + * identical values in the new graph, while the vertex IDs + * of the second graph map to IDs incremented by the vertex + * count of the first graph. The new edges will be grouped with the + * other edges that share a from vertex. + * + * + * Both graphs need to have the same directedness, i.e. either both + * directed or both undirected. If both graphs are directed, then for each + * vertex v, u in graphs G1, G2 we add edges (v, u), (u, v) to maintain + * completeness. + * + * + * The current version of this function cannot handle graph, vertex + * and edge attributes, they will be lost. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param left The first graph. + * \param right The second graph. + * \return Error code. + * + * Time complexity: O(|V1|*|V2|+|E1|+|E2|). + * + */ +igraph_error_t igraph_join(igraph_t *res, + const igraph_t *left, + const igraph_t *right) { + + igraph_int_t no_of_nodes_left = igraph_vcount(left); + igraph_int_t no_of_nodes_right = igraph_vcount(right); + igraph_int_t no_of_new_edges; + igraph_vector_int_t new_edges; + igraph_bool_t directed_left = igraph_is_directed(left); + igraph_int_t i; + igraph_int_t j; + + if (directed_left != igraph_is_directed(right)) { + IGRAPH_ERROR("Cannot create join of directed and undirected graphs.", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_disjoint_union(res,left,right)); + IGRAPH_SAFE_MULT(no_of_nodes_left, no_of_nodes_right ,&no_of_new_edges); + IGRAPH_SAFE_MULT(no_of_new_edges, 2 ,&no_of_new_edges); + if (directed_left) { + IGRAPH_SAFE_MULT(no_of_new_edges, 2 ,&no_of_new_edges); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&new_edges, no_of_new_edges)); + + for(i = 0; i < no_of_nodes_left; i++) { + for(j = 0; j < no_of_nodes_right; j++) { + igraph_vector_int_push_back(&new_edges, i); /* reserved */ + igraph_vector_int_push_back(&new_edges, j + no_of_nodes_left); /* reserved */ + if (directed_left) { + igraph_vector_int_push_back(&new_edges, j + no_of_nodes_left); /* reserved */ + igraph_vector_int_push_back(&new_edges, i); /* reserved */ + } + } + } + + IGRAPH_CHECK(igraph_add_edges(res, &new_edges, NULL)); + + igraph_vector_int_destroy(&new_edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/misc_internal.c b/src/operators/misc_internal.c new file mode 100644 index 0000000..f525afb --- /dev/null +++ b/src/operators/misc_internal.c @@ -0,0 +1,239 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "operators/misc_internal.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_qsort.h" + +int igraph_i_order_edgelist_cmp(void *edges, const void *e1, const void *e2) { + igraph_vector_int_t *edgelist = edges; + igraph_int_t edge1 = (*(const igraph_int_t*) e1) * 2; + igraph_int_t edge2 = (*(const igraph_int_t*) e2) * 2; + igraph_int_t from1 = VECTOR(*edgelist)[edge1]; + igraph_int_t from2 = VECTOR(*edgelist)[edge2]; + if (from1 < from2) { + return -1; + } else if (from1 > from2) { + return 1; + } else { + igraph_int_t to1 = VECTOR(*edgelist)[edge1 + 1]; + igraph_int_t to2 = VECTOR(*edgelist)[edge2 + 1]; + if (to1 < to2) { + return -1; + } else if (to1 > to2) { + return 1; + } else { + return 0; + } + } +} + +igraph_error_t igraph_i_merge(igraph_t *res, igraph_i_merge_mode_t mode, + const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2) { + + igraph_int_t no_of_nodes_left = igraph_vcount(left); + igraph_int_t no_of_nodes_right = igraph_vcount(right); + igraph_int_t no_of_nodes; + igraph_int_t no_edges_left = igraph_ecount(left); + igraph_int_t no_edges_right = igraph_ecount(right); + igraph_bool_t directed = igraph_is_directed(left); + igraph_vector_int_t edges; + igraph_vector_int_t edges1, edges2; + igraph_vector_int_t order1, order2; + igraph_int_t i, j, eptr = 0; + igraph_int_t idx1, idx2, edge1 = -1, edge2 = -1, from1 = -1, from2 = -1, to1 = -1, to2 = -1; + igraph_bool_t l; + + if (directed != igraph_is_directed(right)) { + IGRAPH_ERROR("Cannot create union or intersection of directed and undirected graph.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges1, no_edges_left * 2); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges2, no_edges_right * 2); + IGRAPH_CHECK(igraph_vector_int_init(&order1, no_edges_left)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order1); + IGRAPH_CHECK(igraph_vector_int_init(&order2, no_edges_right)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order2); + + if (edge_map1) { + switch (mode) { + case IGRAPH_MERGE_MODE_UNION: + IGRAPH_CHECK(igraph_vector_int_resize(edge_map1, no_edges_left)); + break; + case IGRAPH_MERGE_MODE_INTERSECTION: + igraph_vector_int_clear(edge_map1); + break; + default: + IGRAPH_FATAL("Invalid merge mode."); + } + } + if (edge_map2) { + switch (mode) { + case IGRAPH_MERGE_MODE_UNION: + IGRAPH_CHECK(igraph_vector_int_resize(edge_map2, no_edges_right)); + break; + case IGRAPH_MERGE_MODE_INTERSECTION: + igraph_vector_int_clear(edge_map2); + break; + default: + IGRAPH_FATAL("Invalid merge mode."); + } + } + + no_of_nodes = no_of_nodes_left > no_of_nodes_right ? + no_of_nodes_left : no_of_nodes_right; + + /* We merge the two edge lists. We need to sort them first. + For undirected graphs, we also need to make sure that + for every edge, the larger (non-smaller) vertex ID is in the + second column. */ + + IGRAPH_CHECK(igraph_get_edgelist(left, &edges1, /*bycol=*/ false)); + IGRAPH_CHECK(igraph_get_edgelist(right, &edges2, /*bycol=*/ false)); + if (!directed) { + for (i = 0, j = 0; i < no_edges_left; i++, j += 2) { + if (VECTOR(edges1)[j] > VECTOR(edges1)[j + 1]) { + igraph_int_t tmp = VECTOR(edges1)[j]; + VECTOR(edges1)[j] = VECTOR(edges1)[j + 1]; + VECTOR(edges1)[j + 1] = tmp; + } + } + for (i = 0, j = 0; i < no_edges_right; i++, j += 2) { + if (VECTOR(edges2)[j] > VECTOR(edges2)[j + 1]) { + igraph_int_t tmp = VECTOR(edges2)[j]; + VECTOR(edges2)[j] = VECTOR(edges2)[j + 1]; + VECTOR(edges2)[j + 1] = tmp; + } + } + } + + for (i = 0; i < no_edges_left; i++) { + VECTOR(order1)[i] = i; + } + for (i = 0; i < no_edges_right; i++) { + VECTOR(order2)[i] = i; + } + + igraph_qsort_r(VECTOR(order1), no_edges_left, sizeof(VECTOR(order1)[0]), + &edges1, igraph_i_order_edgelist_cmp); + igraph_qsort_r(VECTOR(order2), no_edges_right, sizeof(VECTOR(order2)[0]), + &edges2, igraph_i_order_edgelist_cmp); + +#define INC1() if ( (++idx1) < no_edges_left) { \ + edge1 = VECTOR(order1)[idx1]; \ + from1 = VECTOR(edges1)[2*edge1]; \ + to1 = VECTOR(edges1)[2*edge1+1]; \ + } +#define INC2() if ( (++idx2) < no_edges_right) { \ + edge2 = VECTOR(order2)[idx2]; \ + from2 = VECTOR(edges2)[2*edge2]; \ + to2 = VECTOR(edges2)[2*edge2+1]; \ + } + + idx1 = idx2 = -1; + INC1(); + INC2(); + +#define CONT() switch (mode) { \ + case IGRAPH_MERGE_MODE_UNION: \ + l = idx1 < no_edges_left || idx2 < no_edges_right; \ + break; \ + case IGRAPH_MERGE_MODE_INTERSECTION: \ + l = idx1 < no_edges_left && idx2 < no_edges_right; \ + break; \ + default: \ + IGRAPH_ASSERT(! "Invalid merge mode."); \ + } + + CONT(); + while (l) { + if (idx2 >= no_edges_right || + (idx1 < no_edges_left && from1 < from2) || + (idx1 < no_edges_left && from1 == from2 && to1 < to2)) { + /* Edge from first graph */ + if (mode == IGRAPH_MERGE_MODE_UNION) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to1)); + if (edge_map1) { + VECTOR(*edge_map1)[edge1] = eptr; + } + eptr++; + } + INC1(); + } else if (idx1 >= no_edges_left || + (idx2 < no_edges_right && from2 < from1) || + (idx2 < no_edges_right && from1 == from2 && to2 < to1)) { + /* Edge from second graph */ + if (mode == IGRAPH_MERGE_MODE_UNION) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from2)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to2)); + if (edge_map2) { + VECTOR(*edge_map2)[edge2] = eptr; + } + eptr++; + } + INC2(); + } else { + /* Edge from both */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from1)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to1)); + if (mode == IGRAPH_MERGE_MODE_UNION) { + if (edge_map1) { + VECTOR(*edge_map1)[edge1] = eptr; + } + if (edge_map2) { + VECTOR(*edge_map2)[edge2] = eptr; + } + } else if (mode == IGRAPH_MERGE_MODE_INTERSECTION) { + if (edge_map1) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_map1, edge1)); + } + if (edge_map2) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_map2, edge2)); + } + } + eptr++; + INC1(); + INC2(); + } + CONT(); + } + +#undef INC1 +#undef INC2 + + igraph_vector_int_destroy(&order2); + igraph_vector_int_destroy(&order1); + igraph_vector_int_destroy(&edges2); + igraph_vector_int_destroy(&edges1); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/misc_internal.h b/src/operators/misc_internal.h new file mode 100644 index 0000000..49d780f --- /dev/null +++ b/src/operators/misc_internal.h @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_OPERATORS_MISC_INTERNAL_H +#define IGRAPH_OPERATORS_MISC_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +IGRAPH_BEGIN_C_DECLS + +typedef enum { + IGRAPH_MERGE_MODE_UNION = 1, + IGRAPH_MERGE_MODE_INTERSECTION = 2 +} igraph_i_merge_mode_t; + +IGRAPH_FUNCATTR_PURE int igraph_i_order_edgelist_cmp(void *edges, const void *e1, const void *e2); +igraph_error_t igraph_i_merge(igraph_t *res, igraph_i_merge_mode_t mode, + const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/operators/permute.c b/src/operators/permute.c new file mode 100644 index 0000000..a0f6fbb --- /dev/null +++ b/src/operators/permute.c @@ -0,0 +1,127 @@ +/* + igraph library. + Copyright (C) 2006-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_operators.h" +#include "igraph_isomorphism.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "graph/attributes.h" + +/** + * \function igraph_invert_permutation + * \brief Inverts a permutation. + * + * Produces the inverse of \p permutation into \p inverse and at the same time it checks + * that the permutation vector is valid, i.e. all indices are within range and there are + * no duplicate entries. + * + * \param permutation A permutation vector containing 0-based integer indices. + * \param inverse An initialized vector. The inverse of \p permutation will be stored here. + * \return Error code. + */ +igraph_error_t igraph_invert_permutation(const igraph_vector_int_t *permutation, igraph_vector_int_t *inverse) { + const igraph_int_t n = igraph_vector_int_size(permutation); + + IGRAPH_CHECK(igraph_vector_int_resize(inverse, n)); + igraph_vector_int_fill(inverse, -1); + + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t j = VECTOR(*permutation)[i]; + if (j < 0 || j >= n) { + IGRAPH_ERROR("Invalid index in permutation vector.", IGRAPH_EINVAL); + } + if (VECTOR(*inverse)[j] != -1) { + /* This element of 'inverse' has already been set, 'j' is a duplicate value. */ + IGRAPH_ERROR("Duplicate entry in permutation vector.", IGRAPH_EINVAL); + } + VECTOR(*inverse)[j] = i; + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_permute_vertices + * \brief Permute the vertices. + * + * This function creates a new graph from the input graph by permuting + * its vertices according to the specified mapping. Call this function + * with the output of \ref igraph_canonical_permutation() to create + * the canonical form of a graph. + * + * \param graph The input graph. + * \param res Pointer to an uninitialized graph object. The new graph + * is created here. + * \param permutation The permutation to apply. The i-th element of the + * vector specifies the index of the vertex in the original graph that + * will become vertex i in the new graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in terms of the number of + * vertices and edges. + */ +igraph_error_t igraph_permute_vertices(const igraph_t *graph, igraph_t *res, + const igraph_vector_int_t *permutation) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vector_int_t edges; + igraph_vector_int_t index; + igraph_int_t p; + + if (igraph_vector_int_size(permutation) != no_of_nodes) { + IGRAPH_ERROR("Permute vertices: invalid permutation vector size.", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&index, no_of_nodes); + + /* Also checks that 'permutation' is valid: */ + IGRAPH_CHECK(igraph_invert_permutation(permutation, &index)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_edges * 2); + + p = 0; + for (igraph_int_t i = 0; i < no_of_edges; i++) { + VECTOR(edges)[p++] = VECTOR(index)[ IGRAPH_FROM(graph, i) ]; + VECTOR(edges)[p++] = VECTOR(index)[ IGRAPH_TO(graph, i) ]; + } + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, igraph_is_directed(graph))); + IGRAPH_FINALLY(igraph_destroy, res); + + /* Attributes */ + if (graph->attr) { + igraph_vector_int_t vtypes; + IGRAPH_CHECK(igraph_i_attribute_copy(res, graph, true, /* vertex= */ false, true)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vtypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, 0, 0, 0, &vtypes, 0, 0)); + if (igraph_vector_int_size(&vtypes) != 0) { + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, res, permutation)); + } + igraph_vector_int_destroy(&vtypes); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&index); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); /* +1 for res */ + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/products.c b/src/operators/products.c new file mode 100644 index 0000000..a7dc109 --- /dev/null +++ b/src/operators/products.c @@ -0,0 +1,652 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_structural.h" + +#include "math/safe_intop.h" + +static igraph_error_t cartesian_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Cartesian product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + const igraph_int_t vcount1 = igraph_vcount(g1); + const igraph_int_t vcount2 = igraph_vcount(g2); + const igraph_int_t ecount1 = igraph_ecount(g1); + const igraph_int_t ecount2 = igraph_ecount(g2); + igraph_int_t vcount; + igraph_int_t ecount, ecount_double; + igraph_vector_int_t edges; + + // New vertex count = vcount1 * vcount2 + IGRAPH_SAFE_MULT(vcount1, vcount2, &vcount); + + { + // New edge count = vcount1*ecount2 + vcount2*ecount1 + igraph_int_t temp; + IGRAPH_SAFE_MULT(vcount1, ecount2, &ecount); + IGRAPH_SAFE_MULT(vcount2, ecount1, &temp); + IGRAPH_SAFE_ADD(ecount, temp, &ecount); + } + + IGRAPH_SAFE_MULT(ecount, 2, &ecount_double); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ecount_double); + + // Vertex ((i, j)) with i from g1, and j from g2 + // will have new vertex id: i * vcount2 + j + + igraph_int_t edge_index = 0; + + // Edges from g1 + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from = IGRAPH_FROM(g1, i); + igraph_int_t to = IGRAPH_TO(g1, i); + + // For all edges (from, to) in g1, add edge from ((from, j)) to ((to, j)) + // for all vertex j in g2 + for (igraph_int_t j = 0; j < vcount2; ++j) { + VECTOR(edges)[edge_index++] = from * vcount2 + j; // ((from, j)) + VECTOR(edges)[edge_index++] = to * vcount2 + j; // ((to, j)) + } + } + + // Edges from g2 + for (igraph_int_t i = 0; i < ecount2; ++i) { + igraph_int_t from = IGRAPH_FROM(g2, i); + igraph_int_t to = IGRAPH_TO(g2, i); + + // For all edges (from, to) in g2, add edge from (j, from) to (j, to) + // for all vertex j in g1 + for (igraph_int_t j = 0; j < vcount1; ++j) { + VECTOR(edges)[edge_index++] = j * vcount2 + from; // ((j, from)) + VECTOR(edges)[edge_index++] = j * vcount2 + to; // ((j, to)) + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, vcount, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t lexicographic_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Lexicographic product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + const igraph_int_t vcount1 = igraph_vcount(g1); + const igraph_int_t vcount2 = igraph_vcount(g2); + const igraph_int_t ecount1 = igraph_ecount(g1); + const igraph_int_t ecount2 = igraph_ecount(g2); + igraph_int_t vcount; + igraph_int_t ecount, ecount_double; + igraph_vector_int_t edges; + + // New vertex count = vcount1 * vcount2 + IGRAPH_SAFE_MULT(vcount1, vcount2, &vcount); + + { + // New edge count = vcount1*ecount2 + (vcount2^2)*ecount1 + igraph_int_t temp; + IGRAPH_SAFE_MULT(vcount1, ecount2, &ecount); + IGRAPH_SAFE_MULT(vcount2, vcount2, &temp); + IGRAPH_SAFE_MULT(temp, ecount1, &temp); + + IGRAPH_SAFE_ADD(ecount, temp, &ecount); + } + + IGRAPH_SAFE_MULT(ecount, 2, &ecount_double); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ecount_double); + + // Vertex ((i, j)) with i from g1, and j from g2 + // will have new vertex id: i * vcount2 + j + igraph_int_t edge_index = 0; + + // edges of form u1=u2 and v1~v2 + for (igraph_int_t i = 0; i < ecount2; ++i) { + igraph_int_t from = IGRAPH_FROM(g2, i); + igraph_int_t to = IGRAPH_TO(g2, i); + + // For all edges (from, to) in g2, add edge from (j, from) to (j, to) + // for all vertex j in g1 + for (igraph_int_t j = 0; j < vcount1; ++j) { + VECTOR(edges)[edge_index++] = j * vcount2 + from; // ((j, from)) + VECTOR(edges)[edge_index++] = j * vcount2 + to; // ((j, to)) + } + } + + // edges of form u1~u2 + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from1 = IGRAPH_FROM(g1, i); + igraph_int_t to1 = IGRAPH_TO(g1, i); + + // each vertex pair irrespective of their connectivity + for (igraph_int_t from2 = 0; from2 < vcount2; ++from2) { + for (igraph_int_t to2 = 0; to2 < vcount2; ++to2) { + // ((from1, from2)) to ((to1, to2)) + VECTOR(edges)[edge_index++] = from1 * vcount2 + from2; // ((from1, from2)) + VECTOR(edges)[edge_index++] = to1 * vcount2 + to2; // ((to1, to2)) + } + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, vcount, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t strong_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Strong product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + const igraph_int_t vcount1 = igraph_vcount(g1); + const igraph_int_t vcount2 = igraph_vcount(g2); + const igraph_int_t ecount1 = igraph_ecount(g1); + const igraph_int_t ecount2 = igraph_ecount(g2); + igraph_int_t vcount; + igraph_int_t ecount, ecount_double; + igraph_vector_int_t edges; + + // New vertex count = vcount1 * vcount2 + IGRAPH_SAFE_MULT(vcount1, vcount2, &vcount); + + { + // New edge count = vcount1*ecount2 + vcount2*ecount1 + 2*e1*e2 for undirected graph + // = vcount1*ecount2 + vcount2*ecount1 + e1*e2 for directed graph + igraph_int_t temp; + IGRAPH_SAFE_MULT(vcount1, ecount2, &ecount); + IGRAPH_SAFE_MULT(vcount2, ecount1, &temp); + IGRAPH_SAFE_ADD(ecount, temp, &ecount); + + IGRAPH_SAFE_MULT(ecount1, ecount2, &temp); + if (!directed) { + IGRAPH_SAFE_MULT(temp, 2, &temp); + } + IGRAPH_SAFE_ADD(ecount, temp, &ecount); + } + + IGRAPH_SAFE_MULT(ecount, 2, &ecount_double); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ecount_double); + + igraph_int_t edge_index = 0; + // Strong graph product contains all the edges from both cartesian and tensor product + + // Edges of type cartesian products: v1=v2 and u1~u2; v1~v2 and u1=u2 + // v1=v2 and u1~u2 + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from = IGRAPH_FROM(g1, i); + igraph_int_t to = IGRAPH_TO(g1, i); + + // For all edges (from, to) in g1, add edge from ((from, j)) to ((to, j)) + // for all vertex j in g2 + for (igraph_int_t j = 0; j < vcount2; ++j) { + // SAFE MULT and SAFE ADD not needed as < vcount + VECTOR(edges)[edge_index++] = from * vcount2 + j; // ((from, j)) + VECTOR(edges)[edge_index++] = to * vcount2 + j; // ((to, j)) + } + } + + // v1~v2 and u1=u2 + for (igraph_int_t i = 0; i < ecount2; ++i) { + igraph_int_t from = IGRAPH_FROM(g2, i); + igraph_int_t to = IGRAPH_TO(g2, i); + + // For all edges (from, to) in g2, add edge from (j, from) to (j, to) + // for all vertex j in g1 + for (igraph_int_t j = 0; j < vcount1; ++j) { + VECTOR(edges)[edge_index++] = j * vcount2 + from; // ((j, from)) + VECTOR(edges)[edge_index++] = j * vcount2 + to; // ((j, to)) + } + } + + // Edges of type tensor product + // u1 ~ u2 and v1 ~ v2 + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from1 = IGRAPH_FROM(g1, i); + igraph_int_t to1 = IGRAPH_TO(g1, i); + + for (igraph_int_t j = 0; j < ecount2; ++j) { + igraph_int_t from2 = IGRAPH_FROM(g2, j); + igraph_int_t to2 = IGRAPH_TO(g2, j); + + // Create edge between ((from1, from2)) to ((to1, to2)) + VECTOR(edges)[edge_index++] = from1 * vcount2 + from2; // ((from1, from2)) + VECTOR(edges)[edge_index++] = to1 * vcount2 + to2; // ((to1, to2)) + + // In directed graphs, no edge is added because (from2, to2) are not adjacent + // respecting direction. + // For undirected graphs, add cross edges between ((from1, to2)) and ((to1, from2)). + if (!directed) { + VECTOR(edges)[edge_index++] = from1 * vcount2 + to2; // ((from1, to2)) + VECTOR(edges)[edge_index++] = to1 * vcount2 + from2; // ((to1, from2)) + } + } + } + IGRAPH_CHECK(igraph_create(res, &edges, vcount, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t tensor_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Tensor product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + const igraph_int_t vcount1 = igraph_vcount(g1); + const igraph_int_t vcount2 = igraph_vcount(g2); + const igraph_int_t ecount1 = igraph_ecount(g1); + const igraph_int_t ecount2 = igraph_ecount(g2); + igraph_int_t vcount; + igraph_int_t ecount, ecount_double; + igraph_vector_int_t edges; + + IGRAPH_SAFE_MULT(vcount1, vcount2, &vcount); + + // New edge count = 2*ecount1*ecount2 if undirected else ecount1*ecount2 + IGRAPH_SAFE_MULT(ecount1, ecount2, &ecount); + if (!directed) { + IGRAPH_SAFE_MULT(ecount, 2, &ecount); + } + IGRAPH_SAFE_MULT(ecount, 2, &ecount_double); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ecount_double); + + // Vertex ((i, j)) with i from g1, and j from g2 + // will have new vertex id: i * vcount2 + j + + igraph_int_t edge_index = 0; + + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from1 = IGRAPH_FROM(g1, i); + igraph_int_t to1 = IGRAPH_TO(g1, i); + + for (igraph_int_t j = 0; j < ecount2; ++j) { + igraph_int_t from2 = IGRAPH_FROM(g2, j); + igraph_int_t to2 = IGRAPH_TO(g2, j); + + // Create edge between ((from1, from2)) to ((to1, to2)) + VECTOR(edges)[edge_index++] = from1 * vcount2 + from2; // ((from1, from2)) + VECTOR(edges)[edge_index++] = to1 * vcount2 + to2; // ((to1, to2)) + + // In directed graphs, no edge is added because (from2, to2) are not adjacent + // respecting direction. + // For undirected graphs, add cross edges between ((from1, to2)) and ((to1, from2)). + if (!directed) { + VECTOR(edges)[edge_index++] = from1 * vcount2 + to2; // ((from1, to2)) + VECTOR(edges)[edge_index++] = to1 * vcount2 + from2; // ((to1, from2)) + } + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, vcount, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t modular_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Modular product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + igraph_bool_t is_simple1, is_simple2; + IGRAPH_CHECK(igraph_is_simple(g1, &is_simple1, IGRAPH_DIRECTED)); + IGRAPH_CHECK(igraph_is_simple(g2, &is_simple2, IGRAPH_DIRECTED)); + + if (!is_simple1 || !is_simple2) { + IGRAPH_ERROR("Modular product requires simple graphs as input.", IGRAPH_EINVAL); + } + + // See: https://en.wikipedia.org/wiki/Graph_product#Overview_table + + igraph_t g1_compl, g2_compl; + IGRAPH_CHECK(igraph_complementer(&g1_compl, g1, /*loops*/ false)); + IGRAPH_FINALLY(igraph_destroy, &g1_compl); + + IGRAPH_CHECK(igraph_complementer(&g2_compl, g2, /*loops*/ false)); + IGRAPH_FINALLY(igraph_destroy, &g2_compl); + + // Condition 2 of adjacency is same as tensor product of complements, without loop + igraph_t tensor_compl; + IGRAPH_CHECK(tensor_product(&tensor_compl, &g1_compl, &g2_compl)); + + igraph_destroy(&g2_compl); + igraph_destroy(&g1_compl); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_FINALLY(igraph_destroy, &tensor_compl); + + // Condition 1 of adjacency is same as tensor product + igraph_t tensor; + IGRAPH_CHECK(tensor_product(&tensor, g1, g2)); + IGRAPH_FINALLY(igraph_destroy, &tensor); + + IGRAPH_CHECK(igraph_union(res, &tensor, &tensor_compl, /*edge_map1*/ NULL, /*edge_map2*/ NULL)); + + igraph_destroy(&tensor); + igraph_destroy(&tensor_compl); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_product + * \brief The graph product of two graphs, according to the chosen product type. + * + * \experimental + * + * This function computes the product of two graphs using the graph + * product concept selected by the \p type parameter. The two graphs must be of + * the same type, either directed or undirected. If a product of an undirected + * and a directed graph is required, convert one of them to the appropriate type + * using \ref igraph_to_directed() or \ref igraph_to_undirected(). + * + * + * Each vertex of the product graph corresponds to a pair (u, v), + * where \c u is a vertex from the first graph and \c v is a vertex from the + * second graph. Thus the number of vertices in the product graph is + * |V1| |V2|, + * where |V1| and |V2| are the sizes of the vertex + * set of the operands. The pair (u, v) is mapped to a unique vertex + * index in the product graph using index = u |V2| + v. + * + * + * All implemented graph products are associative, but not all are commutative. + * + * + * The supported graph product types are detailed below. The notation + * u ~ v + * is used to indicate that vertices \c u and \c v are adjacent, i.e. there is + * a connection from \c u to \c v. + * \clist + * \cli IGRAPH_PRODUCT_CARTESIAN + * Computes the cartesian product of two graphs. In the product graph, + * there is a connection from (u1, v1) to (u2, v2) + * if and only if + * u1 = u2 and v1 ~ v2 or + * u1 ~ u2 and v1 = v2. + * Thus, the number of edges in the product graph is + * |V1| |E2| + |V2| |E1|. + * + * + * Time complexity: O(|V1| |V2| + |V1| |E2| + |V2| |E1|) + * where |V1| and |V2| are the number of vertices, and + * |E1| and |E2| are the number of edges of the operands. + * + * \cli IGRAPH_PRODUCT_LEXICOGRAPHIC + * Computes the lexicographic product of two graphs. In the product graph, + * there is a connection from (u1, v1) to (u2, v2) + * if and only if + * u1 = u2 and v1 ~ v2 or + * u1 ~ u2. + * Thus, the number of edges in the product graph is + * |V1| |E2| + |V2|^2 |E1|. Unlike most other graph products, + * the lexicographic product is not commutative. + * + * + * Time complexity: O(|V1| |V2| + |V1| |E2| + |V2|^2 |E1|) + * where |V1| and |V2| are the number of vertices, and + * |E1| and |E2| are the number of edges of the operands. + * + * \cli IGRAPH_PRODUCT_STRONG + * Computes the strong product (also known as normal product) of two graphs. + * In the product graph, there is a connection from (u1, v1) to + * (u2, v2) + * if and only if + * u1 = u2 and v1 ~ v2 or + * u1 ~ u2 and v1 = v2 or + * u1 ~ u2 and v1 ~ v2. + * Thus, the number of edges in the product graph is + * |V1| |E2| + |V2| |E1| + |E1| |E2| in the directed case and + * |V1| |E2| + |V2| |E1| + 2 |E1| |E2| in the undirected case. + * + * + * Time complexity: O(|V1| |V2| + |V1| |E2| + |V2| |E1| + |E1| |E2|) + * where |V1| and |V2| are the number of vertices, and + * |E1| and |E2| are the number of edges of the operands. + * + * \cli IGRAPH_PRODUCT_TENSOR + * Computes the tensor product (also known as categorial product) of two graphs. + * In the product graph, there is a connection from (u1, v1) to + * (u2, v2) + * if and only if + * u1 ~ u2 and v1 ~ v2. + * Thus, the number of edges in the product is + * |E1| |E2| in the directed case and + * 2 |E1| |E2| in the undirected case. + * + * + * Time complexity: O(|V1| |V2| + |E1| |E2|) + * where |V1| and |V2| are the number of vertices, and + * |E1| and |E2| are the number of edges of the operands. + * + * \cli IGRAPH_PRODUCT_MODULAR + * Computes the modular product of two graphs. In the product graph, + * there is a connection from (u1, v1) to (u2, v2) + * if and only if + * u1 ~ u2 and v1 ~ v2 or + * NOT (u1 ~ u2) and NOT (v1 ~ v2). + * The modular product requires both graphs to be simple. + * Thus, the number of edges in the product is + * |E1| |E2| + |E1'| |E2'| in the directed case and + * 2 |E1| |E2| + 2 |E1'| |E2'| in the undirected case. + * + * + * Time complexity: O(|V1| |V2| + |E1| |E2| + |E1'| |E2'|) + * where |V1| and |V2| are the number of vertices, + * |E1| and |E2| are the number of edges of the operands, and + * |E1'| and |E2'| are the number of edges of their complement. + * \endclist + * + * + * Reference: + * + * + * Richard Hammack, Wilfried Imrich, and Sandi Klavžar (2011). + * Handbook of Product Graphs (2nd ed.). CRC Press. + * https://doi.org/10.1201/b10959 + * + * \param res Pointer to an uninitialized graph object. The product graph will + * be stored here. + * \param g1 The first operand graph. + * \param g2 The second operand graph. It must have the same directedness as \p g1. + * \param type The type of graph product to compute. + * + * \return Error code: + * \c IGRAPH_EINVAL if the specified \p type is unsupported or the input + * graphs \p g1 and \p g2 are incompatible for the requested product. + * + * \sa \ref igraph_rooted_product() for the rooted product. + */ + +igraph_error_t igraph_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2, + igraph_product_t type) { + switch (type) { + case IGRAPH_PRODUCT_CARTESIAN: + return cartesian_product(res, g1, g2); + + case IGRAPH_PRODUCT_LEXICOGRAPHIC: + return lexicographic_product(res, g1, g2); + + case IGRAPH_PRODUCT_STRONG: + return strong_product(res, g1, g2); + + case IGRAPH_PRODUCT_TENSOR: + return tensor_product(res, g1, g2); + + case IGRAPH_PRODUCT_MODULAR: + return modular_product(res, g1, g2); + + default: + IGRAPH_ERROR("Unknown graph product type.", IGRAPH_EINVAL); + } +} + +/** + * \function igraph_rooted_product + * \brief The rooted graph product of two graphs. + * + * \experimental + * + * This function computes the rooted product of two graphs. The two graphs + * must be of the same type, either directed or undirected. If a product of + * an undirected and a directed graph is required, convert one of them to the + * appropriate type using \ref igraph_to_directed() or \ref igraph_to_undirected(). + * + * + * The vertex IDs in the product graph related to the IDs in the operands in + * the same convention as in \ref igraph_product(). + * + * + * In the rooted product graph of G and H, with root vertex ID \p root in H, + * there is a connection from (u1, v1) to (u2, v2) + * if and only if + * u1 = u2 and v1 ~ v2 or + * u1 ~ u2 and v1 = v2 = root. + * Thus, the number of edges in the product graph is + * |V1| |E2| + |E1|. + * + * \param res Pointer to an uninitialized graph object. The product graph will + * be stored here. + * \param g1 The first operand graph. + * \param g2 The second operand graph. It must have the same directedness as \p g1. + * \param root The root vertex id of the second graph. + * + * \return Error code: + * \c IGRAPH_EINVAL if the specified \p type is unsupported or the input + * graphs \p g1 and \p g2 are incompatible for the requested product. + * \c IGRAPH_EINVVID if invalid vertex ID passed as \p root. + * + * \sa \ref igraph_product() for other types of graph products. + * + * Time complexity: O(|V1| |V2| + |V1| |E2| + |E1|) + * where |V1| and |V2| are the number of vertices, and + * |E1| and |E2| are the number of edges of the operands. + */ + +igraph_error_t igraph_rooted_product(igraph_t *res, + const igraph_t *g1, + const igraph_t *g2, + igraph_int_t root) { + + const igraph_bool_t directed = igraph_is_directed(g1); + + if (igraph_is_directed(g2) != directed) { + IGRAPH_ERROR("Rooted product between a directed and an undirected graph is invalid.", + IGRAPH_EINVAL); + } + + const igraph_int_t vcount1 = igraph_vcount(g1); + const igraph_int_t vcount2 = igraph_vcount(g2); + + if (root < 0 || root >= vcount2) { // root must be in range [0, vcount2-1] + IGRAPH_ERROR("The given root vertex is not present in the second graph.", IGRAPH_EINVVID); + } + + const igraph_int_t ecount1 = igraph_ecount(g1); + const igraph_int_t ecount2 = igraph_ecount(g2); + igraph_int_t vcount; + igraph_int_t ecount, ecount_double; + igraph_vector_int_t edges; + + // New vertex count = vcount1 * vcount2 + IGRAPH_SAFE_MULT(vcount1, vcount2, &vcount); + + // New edge count = vcount1 * ecount2 + ecount1 + IGRAPH_SAFE_MULT(vcount1, ecount2, &ecount); + IGRAPH_SAFE_ADD(ecount, ecount1, &ecount); + + IGRAPH_SAFE_MULT(ecount, 2, &ecount_double); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ecount_double); + + // Vertex ((i, j)) with i from g1, and j from g2 + // will have new vertex id: i * vcount2 + j + + igraph_int_t edge_index = 0; + + // Edges of form ((u, root)) - ((v, root)) + for (igraph_int_t i = 0; i < ecount1; ++i) { + igraph_int_t from = IGRAPH_FROM(g1, i); + igraph_int_t to = IGRAPH_TO(g1, i); + + VECTOR(edges)[edge_index++] = from * vcount2 + root; // ((from, root)) + VECTOR(edges)[edge_index++] = to * vcount2 + root; // ((to, root)) + } + + // For all edges (from, to) in g2, add edge from ((j, from)) to ((j, to)) + // for all vertex j in g1 + for (igraph_int_t i = 0; i < ecount2; ++i) { + igraph_int_t from = IGRAPH_FROM(g2, i); + igraph_int_t to = IGRAPH_TO(g2, i); + + for (igraph_int_t j = 0; j < vcount1; ++j) { + VECTOR(edges)[edge_index++] = j * vcount2 + from; // ((j, from)) + VECTOR(edges)[edge_index++] = j * vcount2 + to; // ((j, to)) + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, vcount, directed)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/reverse.c b/src/operators/reverse.c new file mode 100644 index 0000000..1fd8e01 --- /dev/null +++ b/src/operators/reverse.c @@ -0,0 +1,101 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_vector.h" + +#include "graph/attributes.h" +#include "graph/internal.h" + +/** + * \function igraph_reverse_edges + * \brief Reverses some edges of a directed graph. + * + * This function reverses some edges of a directed graph. The modification is done in place. + * All attributes, as well as the ordering of edges and vertices are preserved. + * + * + * Note that is rarely necessary to reverse \em all edges, as almost all functions that + * handle directed graphs take a \c mode argument that can be set to \c IGRAPH_IN to + * effectively treat edges as reversed. + * + * \param graph The graph whose edges will be reversed. + * \param eids The edges to be reversed. + * Pass igraph_ess_all(IGRAPH_EDGEORDER_ID) to reverse all edges. + * \return Error code. + * + * Time complexity: O(1) if all edges are reversed, otherwise + * O(|E|) where |E| is the number of edges in the graph. + */ +igraph_error_t igraph_reverse_edges(igraph_t *graph, const igraph_es_t eids) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t edges; + igraph_eit_t eit; + igraph_t new_graph; + + /* Nothing to do on undirected graph. */ + if (! igraph_is_directed(graph)) { + return IGRAPH_SUCCESS; + } + + /* Use fast method when all edges are to be reversed. */ + if (igraph_es_is_all(&eids)) { + return igraph_i_reverse(graph); + } + + /* Convert graph to edge list. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*no_of_edges); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, /* bycol= */ false)); + + /* Reverse the edges. */ + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + igraph_int_t tmp = VECTOR(edges)[2*eid]; + VECTOR(edges)[2*eid] = VECTOR(edges)[2*eid + 1]; + VECTOR(edges)[2*eid + 1] = tmp; + } + + /* Re-create graph from edge list and transfer attributes. */ + IGRAPH_CHECK(igraph_create(&new_graph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &new_graph); + + IGRAPH_CHECK(igraph_i_attribute_copy(&new_graph, graph, true, true, true)); + + igraph_eit_destroy(&eit); + igraph_vector_int_destroy(&edges); + igraph_destroy(graph); + IGRAPH_FINALLY_CLEAN(3); + + *graph = new_graph; + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/rewire.c b/src/operators/rewire.c new file mode 100644 index 0000000..f964f04 --- /dev/null +++ b/src/operators/rewire.c @@ -0,0 +1,267 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_adjlist.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_progress.h" +#include "igraph_random.h" +#include "igraph_structural.h" + +#include "core/interruption.h" +#include "misc/graphicality.h" +#include "operators/rewire_internal.h" + +#include /* memset */ + +/* Threshold that defines when to switch over to using adjacency lists during + * rewiring */ +#define REWIRE_ADJLIST_THRESHOLD 10 + +/* Not declared static so that the testsuite can use it, but not part of the public API. */ +igraph_error_t igraph_i_rewire(igraph_t *graph, igraph_int_t n, igraph_bool_t loops, igraph_bool_t use_adjlist, igraph_rewiring_stats_t *stats) { + const igraph_int_t no_of_edges = igraph_ecount(graph); + char message[256]; + igraph_int_t a, b, c, d, dummy, num_swaps, num_successful_swaps; + igraph_vector_int_t eids; + igraph_vector_int_t edgevec, alledges; + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_bool_t ok; + igraph_es_t es; + igraph_adjlist_t al; + + if (no_of_edges < 2) { + /* There are no possible rewirings, return with the same graph. */ + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 2); + + if (use_adjlist) { + /* As well as the sorted adjacency list, we maintain an unordered + * list of edges for picking a random edge in constant time. + */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INT_INIT_FINALLY(&alledges, no_of_edges * 2); + igraph_get_edgelist(graph, &alledges, /*bycol=*/ false); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&edgevec, 4); + es = igraph_ess_vector(&eids); + } + + /* We count both successful and unsuccessful rewiring trials. + * This is necessary for uniform sampling. */ + + num_swaps = num_successful_swaps = 0; + while (num_swaps < n) { + + IGRAPH_ALLOW_INTERRUPTION(); + + if (num_swaps % 1000 == 0) { + snprintf(message, sizeof(message), + "Random rewiring (%.2f%% of the trials were successful)", + num_swaps > 0 ? ((100.0 * num_successful_swaps) / num_swaps) : 0.0); + IGRAPH_PROGRESS(message, (100.0 * num_swaps) / n, 0); + } + + ok = true; + + /* Choose two edges randomly */ + VECTOR(eids)[0] = RNG_INTEGER(0, no_of_edges - 1); + do { + VECTOR(eids)[1] = RNG_INTEGER(0, no_of_edges - 1); + } while (VECTOR(eids)[0] == VECTOR(eids)[1]); + + /* Get the endpoints */ + if (use_adjlist) { + a = VECTOR(alledges)[VECTOR(eids)[0] * 2]; + b = VECTOR(alledges)[VECTOR(eids)[0] * 2 + 1]; + c = VECTOR(alledges)[VECTOR(eids)[1] * 2]; + d = VECTOR(alledges)[VECTOR(eids)[1] * 2 + 1]; + } else { + IGRAPH_CHECK(igraph_edge(graph, VECTOR(eids)[0], &a, &b)); + IGRAPH_CHECK(igraph_edge(graph, VECTOR(eids)[1], &c, &d)); + } + + /* For an undirected graph, we have two "variants" of each edge, i.e. + * a -- b and b -- a. Since some rewirings can be performed only when we + * "swap" the endpoints, we do it now with probability 0.5 */ + if (!directed && RNG_BOOL()) { + dummy = c; c = d; d = dummy; + if (use_adjlist) { + /* Flip the edge in the unordered edge-list, so the update later on + * hits the correct end. */ + VECTOR(alledges)[VECTOR(eids)[1] * 2] = c; + VECTOR(alledges)[VECTOR(eids)[1] * 2 + 1] = d; + } + } + + /* If we do not touch loops, check whether a == b or c == d and disallow + * the swap if needed */ + if (!loops && (a == b || c == d)) { + ok = false; + } else { + /* Check whether they are suitable for rewiring */ + if (a == c || b == d) { + /* Swapping would have no effect */ + ok = false; + } else { + /* a != c && b != d */ + /* If a == d or b == c, the swap would generate at least one loop, so + * we disallow them unless we want to have loops */ + ok = loops || (a != d && b != c); + /* Also, if a == b and c == d and we allow loops, doing the swap + * would result in a multiple edge if the graph is undirected */ + ok = ok && (directed || a != b || c != d); + } + } + + /* All good so far. Now check for the existence of a --> d and c --> b to + * disallow the creation of multiple edges */ + if (ok) { + if (use_adjlist) { + if (igraph_adjlist_has_edge(&al, a, d, directed)) { + ok = false; + } + } else { + IGRAPH_CHECK(igraph_are_adjacent(graph, a, d, &ok)); + ok = !ok; + } + } + if (ok) { + if (use_adjlist) { + if (igraph_adjlist_has_edge(&al, c, b, directed)) { + ok = false; + } + } else { + IGRAPH_CHECK(igraph_are_adjacent(graph, c, b, &ok)); + ok = !ok; + } + } + + /* If we are still okay, we can perform the rewiring */ + if (ok) { + /* printf("Deleting: %" IGRAPH_PRId " -> %" IGRAPH_PRId ", %" IGRAPH_PRId " -> %" IGRAPH_PRId "\n", + a, b, c, d); */ + if (use_adjlist) { + /* Replace entry in sorted adjlist: */ + IGRAPH_CHECK(igraph_adjlist_replace_edge(&al, a, b, d, directed)); + IGRAPH_CHECK(igraph_adjlist_replace_edge(&al, c, d, b, directed)); + /* Also replace in unsorted edgelist: */ + VECTOR(alledges)[VECTOR(eids)[0] * 2 + 1] = d; + VECTOR(alledges)[VECTOR(eids)[1] * 2 + 1] = b; + } else { + IGRAPH_CHECK(igraph_delete_edges(graph, es)); + VECTOR(edgevec)[0] = a; VECTOR(edgevec)[1] = d; + VECTOR(edgevec)[2] = c; VECTOR(edgevec)[3] = b; + /* printf("Adding: %" IGRAPH_PRId " -> %" IGRAPH_PRId ", %" IGRAPH_PRId " -> %" IGRAPH_PRId "\n", + a, d, c, b); */ + IGRAPH_CHECK(igraph_add_edges(graph, &edgevec, 0)); + } + num_successful_swaps++; + } + + num_swaps++; + } + + if (use_adjlist) { + /* Replace graph edges with the adjlist current state */ + IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID))); + IGRAPH_CHECK(igraph_add_edges(graph, &alledges, 0)); + } + + IGRAPH_PROGRESS("Random rewiring: ", 100.0, 0); + + if (use_adjlist) { + igraph_vector_int_destroy(&alledges); + igraph_adjlist_destroy(&al); + } else { + igraph_vector_int_destroy(&edgevec); + } + + igraph_vector_int_destroy(&eids); + IGRAPH_FINALLY_CLEAN(use_adjlist ? 3 : 2); + + if (stats) { + memset(stats, 0, sizeof(igraph_rewiring_stats_t)); + stats->successful_swaps = num_successful_swaps; + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_rewire + * \brief Randomly rewires a graph while preserving its degree sequence. + * + * This function generates a new graph based on the original one by randomly + * "rewriting" edges while preserving the original graph's degree sequence. + * The rewiring is done "in place", so no new graph will be allocated. If you + * would like to keep the original graph intact, use \ref igraph_copy() + * beforehand. All graph attributes will be lost. + * + * + * The rewiring is performed with degree-preserving edge switches: + * Two arbitrary edges are picked uniformly at random, namely + * (a, b) and (c, d), then they are replaced + * by (a, d) and (b, c) if this preserves the + * constraints specified by \p mode. + * + * \param graph The graph object to be rewired. + * \param n Number of rewiring trials to perform. + * \param allowed_edge_types The types of edges that rewiring may create in the graph. + * See \ref igraph_edge_type_sw_t for details. + * Currently, the following are implemented: + * \clist + * \cli IGRAPH_SIMPLE_SW + * simple graphs (i.e. no self-loops or multi-edges allowed). + * \cli IGRAPH_LOOPS_SW + * single self-loops are allowed, but not multi-edges. + * \endclist + * Multigraphs are not yet supported. + * \param stats Counts of the number of different operations + * performed by the algorithm are stored here. + * + * \return Error code: + * \clist + * \cli IGRAPH_EINVMODE + * Invalid rewiring mode. + * \cli IGRAPH_ENOMEM + * Not enough memory for temporary data. + * \endclist + * + * Time complexity: TODO. + */ +igraph_error_t igraph_rewire(igraph_t *graph, igraph_int_t n, igraph_edge_type_sw_t allowed_edge_types, igraph_rewiring_stats_t *stats) { + igraph_bool_t use_adjlist = n >= REWIRE_ADJLIST_THRESHOLD; + + if ((allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) || + (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW)) { + IGRAPH_ERROR("Rewiring multigraphs is not yet implemented.", IGRAPH_UNIMPLEMENTED); + } + + return igraph_i_rewire(graph, n, allowed_edge_types & IGRAPH_LOOPS_SW, use_adjlist, stats); +} diff --git a/src/operators/rewire_edges.c b/src/operators/rewire_edges.c new file mode 100644 index 0000000..aa7a61b --- /dev/null +++ b/src/operators/rewire_edges.c @@ -0,0 +1,385 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" + +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "graph/attributes.h" +#include "misc/graphicality.h" + +static igraph_error_t igraph_i_rewire_edges_no_multiple(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, + igraph_vector_int_t *edges) { + + igraph_int_t no_verts = igraph_vcount(graph); + igraph_int_t no_edges = igraph_ecount(graph); + igraph_vector_int_t eorder, tmp; + igraph_vector_int_t first, next, prev, marked; + igraph_int_t i, to_rewire, last_other = -1; + + /* Create our special graph representation */ + +# define ADD_STUB(vertex, stub) do { \ + if (VECTOR(first)[(vertex)]) { \ + VECTOR(prev)[VECTOR(first)[(vertex)]-1]=(stub)+1; \ + } \ + VECTOR(next)[(stub)]=VECTOR(first)[(vertex)]; \ + VECTOR(prev)[(stub)]=0; \ + VECTOR(first)[(vertex)]=(stub)+1; \ + } while (0) + +# define DEL_STUB(vertex, stub) do { \ + if (VECTOR(next)[(stub)]) { \ + VECTOR(prev)[VECTOR(next)[(stub)]-1]=VECTOR(prev)[(stub)]; \ + } \ + if (VECTOR(prev)[(stub)]) { \ + VECTOR(next)[VECTOR(prev)[(stub)]-1]=VECTOR(next)[(stub)]; \ + } else { \ + VECTOR(first)[(vertex)]=VECTOR(next)[(stub)]; \ + } \ + } while (0) + +# define MARK_NEIGHBORS(vertex) do { \ + igraph_int_t xxx_ =VECTOR(first)[(vertex)]; \ + while (xxx_) { \ + igraph_int_t o= VECTOR(*edges)[xxx_ % 2 ? xxx_ : xxx_-2]; \ + VECTOR(marked)[o]=other+1; \ + xxx_=VECTOR(next)[xxx_-1]; \ + } \ + } while (0) + + IGRAPH_CHECK(igraph_vector_int_init(&first, no_verts)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &first); + IGRAPH_CHECK(igraph_vector_int_init(&next, no_edges * 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &next); + IGRAPH_CHECK(igraph_vector_int_init(&prev, no_edges * 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &prev); + IGRAPH_CHECK(igraph_get_edgelist(graph, edges, /*bycol=*/ 0)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eorder, no_edges); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, no_edges); + for (i = 0; i < no_edges; i++) { + igraph_int_t idx1 = 2 * i, idx2 = idx1 + 1; + igraph_int_t from = VECTOR(*edges)[idx1]; + igraph_int_t to = VECTOR(*edges)[idx2]; + VECTOR(tmp)[i] = from; + ADD_STUB(from, idx1); + ADD_STUB(to, idx2); + } + IGRAPH_CHECK(igraph_i_vector_int_order(&tmp, &eorder, no_verts)); + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_int_init(&marked, no_verts)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &marked); + + /* Rewire the stubs, part I */ + + to_rewire = (igraph_int_t) RNG_GEOM(prob); + while (to_rewire < no_edges) { + igraph_int_t stub = 2 * VECTOR(eorder)[to_rewire] + 1; + igraph_int_t v = VECTOR(*edges)[stub]; + igraph_int_t ostub = stub - 1; + igraph_int_t other = VECTOR(*edges)[ostub]; + igraph_int_t pot; + if (last_other != other) { + MARK_NEIGHBORS(other); + } + /* Do the rewiring */ + do { + if (loops) { + pot = RNG_INTEGER(0, no_verts - 1); + } else { + pot = RNG_INTEGER(0, no_verts - 2); + pot = pot != other ? pot : no_verts - 1; + } + } while (VECTOR(marked)[pot] == other + 1 && pot != v); + + if (pot != v) { + DEL_STUB(v, stub); + ADD_STUB(pot, stub); + VECTOR(marked)[v] = 0; + VECTOR(marked)[pot] = other + 1; + VECTOR(*edges)[stub] = pot; + } + + to_rewire += RNG_GEOM(prob) + 1; + last_other = other; + } + + /* Create the new index, from the potentially rewired stubs */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, no_edges); + for (i = 0; i < no_edges; i++) { + VECTOR(tmp)[i] = VECTOR(*edges)[2 * i + 1]; + } + IGRAPH_CHECK(igraph_i_vector_int_order(&tmp, &eorder, no_verts)); + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + /* Rewire the stubs, part II */ + + igraph_vector_int_null(&marked); + last_other = -1; + + to_rewire = (igraph_int_t) RNG_GEOM(prob); + while (to_rewire < no_edges) { + igraph_int_t stub = (2 * VECTOR(eorder)[to_rewire]); + igraph_int_t v = VECTOR(*edges)[stub]; + igraph_int_t ostub = stub + 1; + igraph_int_t other = VECTOR(*edges)[ostub]; + igraph_int_t pot; + if (last_other != other) { + MARK_NEIGHBORS(other); + } + /* Do the rewiring */ + do { + if (loops) { + pot = RNG_INTEGER(0, no_verts - 1); + } else { + pot = RNG_INTEGER(0, no_verts - 2); + pot = pot != other ? pot : no_verts - 1; + } + } while (VECTOR(marked)[pot] == other + 1 && pot != v); + if (pot != v) { + DEL_STUB(v, stub); + ADD_STUB(pot, stub); + VECTOR(marked)[v] = 0; + VECTOR(marked)[pot] = other + 1; + VECTOR(*edges)[stub] = pot; + } + + to_rewire += RNG_GEOM(prob) + 1; + last_other = other; + } + + igraph_vector_int_destroy(&marked); + igraph_vector_int_destroy(&prev); + igraph_vector_int_destroy(&next); + igraph_vector_int_destroy(&first); + igraph_vector_int_destroy(&eorder); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +#undef ADD_STUB +#undef DEL_STUB +#undef MARK_NEIGHBORS + +/** + * \function igraph_rewire_edges + * \brief Rewires the edges of a graph with constant probability. + * + * This function rewires the edges of a graph with a constant + * probability. More precisely each end point of each edge is rewired + * to a uniformly randomly chosen vertex with constant probability \p + * prob. + * + * Note that this function modifies the input \p graph, + * call \ref igraph_copy() if you want to keep it. + * + * \param graph The input graph, this will be rewired, it can be + * directed or undirected. + * \param prob The rewiring probability a constant between zero and + * one (inclusive). + * \param allowed_edge_types Controls whether multi-edges and self-loops + * are allowed in the new graph. See \ref igraph_edge_type_sw_t. + * \return Error code. + * + * \sa \ref igraph_watts_strogatz_game() uses this function for the + * rewiring. + * + * Time complexity: O(|V|+|E|). + */ +igraph_error_t igraph_rewire_edges(igraph_t *graph, igraph_real_t prob, + igraph_edge_type_sw_t allowed_edge_types) { + + igraph_t newgraph; + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t endpoints = no_of_edges * 2; + igraph_int_t to_rewire; + igraph_vector_int_t edges; + igraph_bool_t loops, multiple; + + IGRAPH_CHECK(igraph_i_edge_type_to_loops_multiple(allowed_edge_types, &loops, &multiple)); + + if (prob < 0 || prob > 1) { + IGRAPH_ERROR("Rewiring probability should be between zero and one", + IGRAPH_EINVAL); + } + + if (prob == 0) { + /* This is easy, just leave things as they are */ + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, endpoints); + + if (prob != 0 && no_of_edges > 0) { + if (multiple) { + /* If multiple edges are allowed, then there is an easy and fast + method. Each endpoint of an edge is rewired with probability p, + so the "skips" between the really rewired endpoints follow a + geometric distribution. */ + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + to_rewire = RNG_GEOM(prob); + while (to_rewire < endpoints) { + if (loops) { + VECTOR(edges)[to_rewire] = RNG_INTEGER(0, no_of_nodes - 1); + } else { + igraph_int_t opos = to_rewire % 2 ? to_rewire - 1 : to_rewire + 1; + igraph_int_t nei = VECTOR(edges)[opos]; + igraph_int_t r = RNG_INTEGER(0, no_of_nodes - 2); + VECTOR(edges)[ to_rewire ] = (r != nei ? r : no_of_nodes - 1); + } + to_rewire += RNG_GEOM(prob) + 1; + } + + } else { + IGRAPH_CHECK(igraph_i_rewire_edges_no_multiple(graph, prob, loops, + &edges)); + } + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, no_of_nodes, + igraph_is_directed(graph))); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, true)); + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = newgraph; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_rewire_directed_edges + * \brief Rewires the chosen endpoint of directed edges. + * + * This function rewires either the start or end of directed edges in a graph + * with a constant probability. Correspondingly, either the in-degree sequence + * or the out-degree sequence of the graph will be preserved. + * + * Note that this function modifies the input \p graph, + * call \ref igraph_copy() if you want to keep it. + * + * This function can produce multiple edges between two vertices. + * + * \param graph The input graph, this will be rewired, it can be + * directed or undirected. If it is undirected or \p mode is set to + * IGRAPH_ALL, \ref igraph_rewire_edges() will be called. + * \param prob The rewiring probability, a constant between zero and + * one (inclusive). + * \param loops Boolean, whether loop edges are allowed in the new + * graph, or not. + * \param mode The endpoints of directed edges to rewire. It is ignored for + * undirected graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * rewire the end of each directed edge + * \cli IGRAPH_IN + * rewire the start of each directed edge + * \cli IGRAPH_ALL + * rewire both endpoints of each edge + * \endclist + * \return Error code. + * + * \sa \ref igraph_rewire_edges(), \ref igraph_rewire() + * + * Time complexity: O(|E|). + */ +igraph_error_t igraph_rewire_directed_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_neimode_t mode) { + + if (prob < 0 || prob > 1) { + IGRAPH_ERROR("Rewiring probability should be between zero and one", + IGRAPH_EINVAL); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + if (prob == 0) { + return IGRAPH_SUCCESS; + } + + if (igraph_is_directed(graph) && mode != IGRAPH_ALL) { + igraph_t newgraph; + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t to_rewire; + igraph_int_t offset = 0; + igraph_vector_int_t edges; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2 * no_of_edges); + + switch (mode) { + case IGRAPH_IN: + offset = 0; + break; + case IGRAPH_OUT: + offset = 1; + break; + case IGRAPH_ALL: + break; /* suppress compiler warning */ + } + + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + + to_rewire = RNG_GEOM(prob); + while (to_rewire < no_of_edges) { + if (loops) { + VECTOR(edges)[2 * to_rewire + offset] = RNG_INTEGER(0, no_of_nodes - 1); + } else { + igraph_int_t nei = VECTOR(edges)[2 * to_rewire + (1 - offset)]; + igraph_int_t r = RNG_INTEGER(0, no_of_nodes - 2); + VECTOR(edges)[2 * to_rewire + offset] = (r != nei ? r : no_of_nodes - 1); + } + to_rewire += RNG_GEOM(prob) + 1; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, no_of_nodes, + igraph_is_directed(graph))); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_i_attribute_copy(&newgraph, graph, true, true, true)); + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = newgraph; + + } else { + IGRAPH_CHECK(igraph_rewire_edges(graph, prob, IGRAPH_MULTI_SW | (loops ? IGRAPH_LOOPS_SW : IGRAPH_SIMPLE_SW))); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/rewire_internal.h b/src/operators/rewire_internal.h new file mode 100644 index 0000000..145a79b --- /dev/null +++ b/src/operators/rewire_internal.h @@ -0,0 +1,17 @@ +#ifndef IGRAPH_OPERATORS_REWIRE_INTERNAL_H +#define IGRAPH_OPERATORS_REWIRE_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_error.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_rewire( + igraph_t *graph, igraph_int_t n, igraph_bool_t loops, + igraph_bool_t use_adjlist, igraph_rewiring_stats_t *stats); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/operators/simplify.c b/src/operators/simplify.c new file mode 100644 index 0000000..06257c8 --- /dev/null +++ b/src/operators/simplify.c @@ -0,0 +1,208 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "core/fixed_vectorlist.h" +#include "graph/attributes.h" + +/** + * \ingroup structural + * \function igraph_simplify + * \brief Removes loop and/or multiple edges from the graph. + * + * This function merges parallel edges and removes self-loops, according + * to the \p multiple and \p loops parameters. Note that this function + * may change the edge order, even if the input was already a simple graph. + * + * \param graph The graph object. + * \param remove_multiple If true, multiple edges will be removed. + * \param remove_loops If true, loops (self edges) will be removed. + * \param edge_comb What to do with the edge attributes. \c NULL means to + * discard the edge attributes after the operation, even for edges + * that were unaffected. See the igraph manual section about attributes + * for details. + * \return Error code: + * \c IGRAPH_ENOMEM if we are out of memory. + * + * Time complexity: O(|V|+|E|). + * + * \example examples/simple/igraph_simplify.c + */ + +igraph_error_t igraph_simplify(igraph_t *graph, + igraph_bool_t remove_multiple, igraph_bool_t remove_loops, + const igraph_attribute_combination_t *edge_comb) { + + igraph_vector_int_t edges; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t edge; + igraph_bool_t attr = edge_comb && igraph_has_attribute_table(); + igraph_int_t from, to, pfrom = -1, pto = -2; + igraph_t res; + igraph_es_t es; + igraph_eit_t eit; + igraph_vector_int_t mergeinto; + igraph_int_t actedge; + + /* if we already know there are no multi-edges, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI)) { + remove_multiple = false; + } + + /* if we already know there are no loops, they don't need to be removed */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + remove_loops = false; + } + + if (!remove_multiple && !remove_loops) + /* nothing to do */ + { + return IGRAPH_SUCCESS; + } + + if (!remove_multiple) { + igraph_vector_int_t edges_to_delete; + + /* removing loop edges only, this is simple. No need to combine anything + * and the whole process can be done in-place */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges_to_delete, 0); + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + while (!IGRAPH_EIT_END(eit)) { + edge = IGRAPH_EIT_GET(eit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + if (from == to) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges_to_delete, edge)); + } + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + if (igraph_vector_int_size(&edges_to_delete) > 0) { + IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_vector(&edges_to_delete))); + } + + igraph_vector_int_destroy(&edges_to_delete); + IGRAPH_FINALLY_CLEAN(1); + + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false); + + return IGRAPH_SUCCESS; + } + + if (attr) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&mergeinto, no_of_edges); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (actedge = -1; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + edge = IGRAPH_EIT_GET(eit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + + if (remove_loops && from == to) { + /* Loop edge to be removed */ + if (attr) { + VECTOR(mergeinto)[edge] = -1; + } + } else if (remove_multiple && from == pfrom && to == pto) { + /* Multiple edge to be contracted */ + if (attr) { + VECTOR(mergeinto)[edge] = actedge; + } + } else { + /* Edge to be kept */ + igraph_vector_int_push_back(&edges, from); /* reserved */ + igraph_vector_int_push_back(&edges, to); /* reserved */ + if (attr) { + actedge++; + VECTOR(mergeinto)[edge] = actedge; + } + } + pfrom = from; pto = to; + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(&res, &edges, no_of_nodes, igraph_is_directed(graph))); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &res); + + IGRAPH_CHECK(igraph_i_attribute_copy(&res, graph, true, true, /* edges= */ false)); + + if (attr) { + igraph_fixed_vectorlist_t vl; + IGRAPH_CHECK(igraph_fixed_vectorlist_convert(&vl, &mergeinto, actedge + 1)); + IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &vl); + + IGRAPH_CHECK(igraph_i_attribute_combine_edges(graph, &res, &vl.vecs, edge_comb)); + + igraph_fixed_vectorlist_destroy(&vl); + igraph_vector_int_destroy(&mergeinto); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = res; + + /* The cache must be set as the very last step, only after all functions that can + * potentially return with an error have finished. */ + + if (remove_loops) { + /* Loop edges were removed so we know for sure that there aren't any + * loop edges now */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false); + } + + if (remove_multiple) { + /* Multi-edges were removed so we know for sure that there aren't any + * multi-edges now */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, false); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/subgraph.c b/src/operators/subgraph.c new file mode 100644 index 0000000..d164755 --- /dev/null +++ b/src/operators/subgraph.c @@ -0,0 +1,615 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_operators.h" + +#include "igraph_bitset.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include "core/interruption.h" +#include "core/set.h" +#include "graph/attributes.h" +#include "operators/subgraph.h" + +/** + * Subgraph creation, old version: it copies the graph and then deletes + * unneeded vertices. + */ +static igraph_error_t igraph_i_induced_subgraph_copy_and_delete( + const igraph_t *graph, igraph_t *res, const igraph_vs_t vids, + igraph_vector_int_t *map, igraph_vector_int_t *invmap) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_new_nodes_estimate; + igraph_vector_int_t delete; + igraph_bitset_t remain; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&delete, 0); + IGRAPH_BITSET_INIT_FINALLY(&remain, no_of_nodes); + + /* Calculate how many nodes there will be in the new graph. The result is + * a lower bound only as 'vit' may contain the same vertex more than once. */ + no_of_new_nodes_estimate = no_of_nodes - IGRAPH_VIT_SIZE(vit); + if (no_of_new_nodes_estimate < 0) { + no_of_new_nodes_estimate = 0; + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&delete, no_of_new_nodes_estimate)); + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + IGRAPH_BIT_SET(remain, IGRAPH_VIT_GET(vit)); + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + if (! IGRAPH_BIT_TEST(remain, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&delete, i)); + } + } + + igraph_bitset_destroy(&remain); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_copy(res, graph)); + IGRAPH_FINALLY(igraph_destroy, res); + IGRAPH_CHECK(igraph_delete_vertices_map(res, igraph_vss_vector(&delete), + map, invmap)); + + igraph_vector_int_destroy(&delete); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * Subgraph creation, new version: creates the new graph instead of + * copying the old one. + * + * map_is_prepared is an indicator that the caller has already prepared the + * 'map' vector and that this function should not resize or clear it. This + * is used to spare an O(n) operation (where n is the number of vertices in + * the _original_ graph) in cases when induced_subgraph() is repeatedly + * called on the same graph; one example is igraph_decompose(). + */ +static igraph_error_t igraph_i_induced_subgraph_create_from_scratch( + const igraph_t *graph, igraph_t *res, const igraph_vs_t vids, + igraph_vector_int_t *map, igraph_vector_int_t *invmap, + igraph_bool_t map_is_prepared) { + + const igraph_bool_t directed = igraph_is_directed(graph); + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_new_nodes = 0; + igraph_int_t n; + igraph_int_t to; + igraph_int_t eid; + igraph_vector_int_t vids_old2new, vids_new2old; + igraph_vector_int_t eids_new2old; + igraph_vector_int_t vids_vec; + igraph_vector_int_t nei_edges; + igraph_vector_int_t new_edges; + igraph_vit_t vit; + igraph_vector_int_t *my_vids_old2new = &vids_old2new, + *my_vids_new2old = &vids_new2old; + + /* The order of initialization is important here, they will be destroyed in the + * opposite order */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids_new2old, 0); + if (invmap) { + my_vids_new2old = invmap; + igraph_vector_int_clear(my_vids_new2old); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids_new2old, 0); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_edges, 0); + if (map) { + my_vids_old2new = map; + if (!map_is_prepared) { + IGRAPH_CHECK(igraph_vector_int_resize(map, no_of_nodes)); + igraph_vector_int_fill(map, -1); + } + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids_old2new, no_of_nodes); + igraph_vector_int_fill(&vids_old2new, -1); + } + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids_vec, 0); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* Calculate the mapping from the old node IDs to the new ones. The other + * igraph_simplify implementation in igraph_i_simplify_copy_and_delete + * ensures that the order of vertex IDs is kept during remapping (i.e. + * if the old ID of vertex A is less than the old ID of vertex B, then + * the same will also be true for the new IDs). To ensure compatibility + * with the other implementation, we have to fetch the vertex IDs into + * a vector first and then sort it. + */ + IGRAPH_CHECK(igraph_vit_as_vector(&vit, &vids_vec)); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_int_sort(&vids_vec); + n = igraph_vector_int_size(&vids_vec); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t vid = VECTOR(vids_vec)[i]; + + /* Cater for duplicate vertex IDs in the input vertex selector; we use + * the first occurrence of each vertex ID and ignore the rest */ + if (VECTOR(*my_vids_old2new)[vid] < 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(my_vids_new2old, vid)); + VECTOR(*my_vids_old2new)[vid] = no_of_new_nodes; + no_of_new_nodes++; + } + } + igraph_vector_int_destroy(&vids_vec); + IGRAPH_FINALLY_CLEAN(1); + + /* Create the new edge list */ + for (igraph_int_t i = 0; i < no_of_new_nodes; i++) { + igraph_int_t old_vid = VECTOR(*my_vids_new2old)[i]; + igraph_int_t new_vid = i; + igraph_bool_t skip_loop_edge; + + IGRAPH_CHECK(igraph_incident(graph, &nei_edges, old_vid, IGRAPH_OUT, IGRAPH_LOOPS)); + n = igraph_vector_int_size(&nei_edges); + + if (directed) { + /* directed graph; this is easier */ + for (igraph_int_t j = 0; j < n; j++) { + eid = VECTOR(nei_edges)[j]; + + to = VECTOR(*my_vids_old2new)[ IGRAPH_TO(graph, eid) ]; + if (to < 0) { + continue; + } + + IGRAPH_CHECK(igraph_vector_int_push_back(&new_edges, new_vid)); + IGRAPH_CHECK(igraph_vector_int_push_back(&new_edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&eids_new2old, eid)); + } + } else { + /* undirected graph. We need to be careful with loop edges as each + * loop edge will appear twice. We use a boolean flag to skip every + * second loop edge */ + skip_loop_edge = false; + for (igraph_int_t j = 0; j < n; j++) { + eid = VECTOR(nei_edges)[j]; + + if (IGRAPH_FROM(graph, eid) != old_vid) { + /* avoid processing edges twice */ + continue; + } + + to = VECTOR(*my_vids_old2new)[ IGRAPH_TO(graph, eid) ]; + if (to < 0) { + continue; + } + + if (new_vid == to) { + /* this is a loop edge; check whether we need to skip it */ + skip_loop_edge = !skip_loop_edge; + if (skip_loop_edge) { + continue; + } + } + + IGRAPH_CHECK(igraph_vector_int_push_back(&new_edges, new_vid)); + IGRAPH_CHECK(igraph_vector_int_push_back(&new_edges, to)); + IGRAPH_CHECK(igraph_vector_int_push_back(&eids_new2old, eid)); + } + } + } + + /* Get rid of some vectors that are not needed anymore */ + if (!map) { + igraph_vector_int_destroy(&vids_old2new); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_int_destroy(&nei_edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Create the new graph */ + IGRAPH_CHECK(igraph_create(res, &new_edges, no_of_new_nodes, directed)); + + /* Now we can also get rid of the new_edges vector */ + igraph_vector_int_destroy(&new_edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Make sure that the newly created graph is destroyed if something happens from + * now on */ + IGRAPH_FINALLY(igraph_destroy, res); + + /* Copy the graph attributes */ + IGRAPH_CHECK(igraph_i_attribute_copy(res, graph, true, /* vertex= */ false, /* edge= */ false)); + + /* Copy the vertex attributes */ + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, res, my_vids_new2old)); + + /* Copy the edge attributes */ + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, res, &eids_new2old)); + + /* Get rid of the remaining stuff */ + if (!invmap) { + igraph_vector_int_destroy(my_vids_new2old); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_int_destroy(&eids_new2old); + IGRAPH_FINALLY_CLEAN(2); /* 1 + 1 since we don't need to destroy res */ + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_induced_subgraph + * \brief Creates a subgraph induced by the specified vertices. + * + * This function collects the specified vertices and all edges between + * them to a new graph. As vertex IDs are always contiguos integers starting + * at zero, the IDs in the created subgraph will be different from the IDs in + * the original graph. To get the mappings between them, use + * \ref igraph_induced_subgraph_map() + * + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param vids A vertex selector describing which vertices to keep. A vertex + * may appear more than once in the selector, but it will be considered + * only once (i.e. it is not possible to duplicate a vertex by adding + * its ID more than once to the selector). The order in which the + * vertices appear in the vertex selector is ignored; the returned + * subgraph will always contain the vertices of the original graph in + * increasing order of vertex IDs. + * \param impl This parameter selects which implementation should we + * use when constructing the new graph. Basically there are two + * possibilities: \c IGRAPH_SUBGRAPH_COPY_AND_DELETE copies the + * existing graph and deletes the vertices that are not needed + * in the new graph, while \c IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH + * constructs the new graph from scratch without copying the old + * one. The latter is more efficient if you are extracting a + * relatively small subpart of a very large graph, while the + * former is better if you want to extract a subgraph whose size + * is comparable to the size of the whole graph. There is a third + * possibility: \c IGRAPH_SUBGRAPH_AUTO will select one of the + * two methods automatically based on the ratio of the number + * of vertices in the new and the old graph. + * + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID in + * \p vids. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_induced_subgraph_map() to also retrieve the vertex ID + * mapping between the graph and the extracted subgraph; + * \ref igraph_delete_vertices() to delete the specified set of + * vertices from a graph, the opposite of this function. + */ +igraph_error_t igraph_induced_subgraph(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_subgraph_implementation_t impl) { + return igraph_induced_subgraph_map(graph, res, vids, impl, + /* map= */ NULL, /* invmap= */ NULL); +} + +static igraph_error_t igraph_i_induced_subgraph_suggest_implementation( + const igraph_t *graph, const igraph_vs_t vids, + igraph_subgraph_implementation_t *result) { + double ratio; + igraph_int_t num_vs; + + if (igraph_vs_is_all(&vids)) { + ratio = 1.0; + } else { + IGRAPH_CHECK(igraph_vs_size(graph, &vids, &num_vs)); + ratio = (igraph_real_t) num_vs / igraph_vcount(graph); + } + + /* The threshold of 0.5 is justified by the benchmarking done in + * https://github.com/igraph/igraph/pull/2708 + * Small improvements may be possible by using better heuristics. */ + if (ratio > 0.5) { + *result = IGRAPH_SUBGRAPH_COPY_AND_DELETE; + } else { + *result = IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH; + } + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_induced_subgraph_map(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_subgraph_implementation_t impl, + igraph_vector_int_t *map, + igraph_vector_int_t *invmap, + igraph_bool_t map_is_prepared) { + + if (impl == IGRAPH_SUBGRAPH_AUTO) { + IGRAPH_CHECK(igraph_i_induced_subgraph_suggest_implementation(graph, vids, &impl)); + } + + switch (impl) { + case IGRAPH_SUBGRAPH_COPY_AND_DELETE: + return igraph_i_induced_subgraph_copy_and_delete(graph, res, vids, map, invmap); + + case IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH: + return igraph_i_induced_subgraph_create_from_scratch(graph, res, vids, map, + invmap, /* map_is_prepared = */ map_is_prepared); + + default: + IGRAPH_ERROR("unknown subgraph implementation type", IGRAPH_EINVAL); + } +} + +/** + * \ingroup structural + * \function igraph_induced_subgraph_map + * \brief Creates an induced subraph and returns the mapping from the original. + * + * This function collects the specified vertices and all edges between + * them to a new graph. As vertex IDs are always contiguos integers starting + * at zero, the IDs in the created subgraph will be different from the IDs in + * the original graph. The mapping between the vertex IDs in the graph and the + * extracted subgraphs are returned in \p map and \p invmap. + * + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param vids A vertex selector describing which vertices to keep. + * \param impl This parameter selects which implementation should be + * used when constructing the new graph. Basically there are two + * possibilities: \c IGRAPH_SUBGRAPH_COPY_AND_DELETE copies the + * existing graph and deletes the vertices that are not needed + * in the new graph, while \c IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH + * constructs the new graph from scratch without copying the old + * one. The latter is more efficient if you are extracting a + * relatively small subpart of a very large graph, while the + * former is better if you want to extract a subgraph whose size + * is comparable to the size of the whole graph. There is a third + * possibility: \c IGRAPH_SUBGRAPH_AUTO will select one of the + * two methods automatically based on the ratio of the number + * of vertices in the new and the old graph. + * \param map Returns a map of the vertices in \p graph to the vertices + * in \p res. -1 indicates a vertex is not mapped. A value of \c i at + * position \c j indicates the vertex \c j in \p graph is mapped + * to vertex i in \p res. + * \param invmap Returns a map of the vertices in \p res to the vertices + * in \p graph. A value of \c i at position \c j indicates that + * vertex \c i in \p graph is mapped to vertex \c j in \p res. + * + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex ID in + * \p vids. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_delete_vertices() to delete the specified set of + * vertices from a graph, the opposite of this function. + */ +igraph_error_t igraph_induced_subgraph_map(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_subgraph_implementation_t impl, + igraph_vector_int_t *map, + igraph_vector_int_t *invmap) { + return igraph_i_induced_subgraph_map(graph, res, vids, impl, + map, invmap, + /* map_is_prepared = */ false); +} + +/** + * \function igraph_induced_subgraph_edges + * \brief The edges contained within an induced sugraph. + * + * This function finds the IDs of those edges which connect vertices from + * a given list, passed in the \p vids parameter. + * + * \param graph The graph. + * \param vids A vertex selector specifying the vertices that make up the subgraph. + * \param edges Integer vector. The IDs of edges within the subgraph induces by + * \p vids will be stored here. + * \return Error code. + * + * Time complexity: O(mv log(nv)) where nv is the number of vertices in \p vids + * and mv is the sum of degrees of vertices in \p vids. + */ +igraph_error_t igraph_induced_subgraph_edges(const igraph_t *graph, igraph_vs_t vids, igraph_vector_int_t *edges) { + /* TODO: When the size of \p vids is large, is it faster to use a boolean vector instead of a set + * to test membership within \p vids? Benchmark to find out at what size it is worth switching + * to the alternative implementation. + */ + igraph_vit_t vit; + igraph_set_t vids_set; + igraph_vector_int_t incedges; + + if (igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vector_int_range(edges, 0, igraph_ecount(graph))); + return IGRAPH_SUCCESS; + } + + igraph_vector_int_clear(edges); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_SET_INIT_FINALLY(&vids_set, IGRAPH_VIT_SIZE(vit)); + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + IGRAPH_CHECK(igraph_set_add(&vids_set, IGRAPH_VIT_GET(vit))); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&incedges, 0); + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + igraph_int_t v = IGRAPH_VIT_GET(vit); + IGRAPH_CHECK(igraph_incident(graph, &incedges, v, IGRAPH_ALL, IGRAPH_LOOPS_ONCE)); + + igraph_int_t d = igraph_vector_int_size(&incedges); + for (igraph_int_t i=0; i < d; i++) { + igraph_int_t e = VECTOR(incedges)[i]; + igraph_int_t u = IGRAPH_OTHER(graph, e, v); + /* The v <= u check avoids adding non-loop edges twice. + * Loop edges only appear once due to the use of + * IGRAPH_LOOPS_ONCE in igraph_incident() */ + if (v <= u && igraph_set_contains(&vids_set, u)) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, e)); + } + } + } + + IGRAPH_FINALLY_CLEAN(3); + igraph_vector_int_destroy(&incedges); + igraph_set_destroy(&vids_set); + igraph_vit_destroy(&vit); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_subgraph_from_edges + * \brief Creates a subgraph with the specified edges and their endpoints. + * + * + * This function collects the specified edges and their endpoints to a new + * graph. As the edge IDs in a graph are always contiguous integers starting at + * zero, the edge IDs in the extracted subgraph will be different from those + * in the original graph. Vertex IDs will also be reassigned if + * \p delete_vertices is set to \c true. Attributes are preserved. + * + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param eids An edge selector describing which edges to keep. + * \param delete_vertices Whether to delete the vertices not incident on any + * of the specified edges as well. If \c false, the number of vertices + * in the result graph will always be equal to the number of vertices + * in the input graph. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * \c IGRAPH_EINVEID, invalid edge ID in \p eids. + * + * Time complexity: O(|V|+|E|), |V| and |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_delete_edges() to delete the specified set of + * edges from a graph, the opposite of this function. + */ + +igraph_error_t igraph_subgraph_from_edges( + const igraph_t *graph, igraph_t *res, const igraph_es_t eids, + igraph_bool_t delete_vertices +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_edges_to_delete_estimate; + igraph_vector_int_t delete = IGRAPH_VECTOR_NULL; + igraph_bitset_t vremain, eremain; + igraph_eit_t eit; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&delete, 0); + IGRAPH_BITSET_INIT_FINALLY(&vremain, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&eremain, no_of_edges); + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + /* Calculate how many edges there will be in the new graph. The result is + * a lower bound only as 'eit' may contain the same edge more than once. */ + no_of_edges_to_delete_estimate = no_of_edges - IGRAPH_EIT_SIZE(eit); + if (no_of_edges_to_delete_estimate < 0) { + no_of_edges_to_delete_estimate = 0; + } + + IGRAPH_CHECK(igraph_vector_int_reserve(&delete, no_of_edges_to_delete_estimate)); + + /* Collect the vertex and edge IDs that will remain */ + for (IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + igraph_int_t from = IGRAPH_FROM(graph, eid), to = IGRAPH_TO(graph, eid); + IGRAPH_BIT_SET(eremain, eid); + IGRAPH_BIT_SET(vremain, from); + IGRAPH_BIT_SET(vremain, to); + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + /* Collect the edge IDs to be deleted */ + for (igraph_int_t i = 0; i < no_of_edges; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + if (! IGRAPH_BIT_TEST(eremain, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&delete, i)); + } + } + + igraph_bitset_destroy(&eremain); + IGRAPH_FINALLY_CLEAN(1); + + /* Delete the unnecessary edges */ + IGRAPH_CHECK(igraph_copy(res, graph)); + IGRAPH_FINALLY(igraph_destroy, res); + IGRAPH_CHECK(igraph_delete_edges(res, igraph_ess_vector(&delete))); + + if (delete_vertices) { + /* Collect the vertex IDs to be deleted */ + igraph_vector_int_clear(&delete); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + if (! IGRAPH_BIT_TEST(vremain, i)) { + IGRAPH_CHECK(igraph_vector_int_push_back(&delete, i)); + } + } + } + + igraph_bitset_destroy(&vremain); + IGRAPH_FINALLY_CLEAN(1); + + /* Delete the unnecessary vertices */ + if (delete_vertices) { + IGRAPH_CHECK(igraph_delete_vertices(res, igraph_vss_vector(&delete))); + } + + igraph_vector_int_destroy(&delete); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/operators/subgraph.h b/src/operators/subgraph.h new file mode 100644 index 0000000..bc17c96 --- /dev/null +++ b/src/operators/subgraph.h @@ -0,0 +1,40 @@ +/* + igraph library. + Copyright (C) 2003-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_OPERATORS_SUBGRAPH_INTERNAL_H +#define IGRAPH_OPERATORS_SUBGRAPH_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +IGRAPH_PRIVATE_EXPORT igraph_error_t igraph_i_induced_subgraph_map( + const igraph_t *graph, igraph_t *res, const igraph_vs_t vids, + igraph_subgraph_implementation_t impl, igraph_vector_int_t *map, + igraph_vector_int_t *invmap, igraph_bool_t map_is_prepared +); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/operators/union.c b/src/operators/union.c new file mode 100644 index 0000000..29392a2 --- /dev/null +++ b/src/operators/union.c @@ -0,0 +1,245 @@ +/* + igraph library. + Copyright (C) 2006-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_interface.h" +#include "igraph_qsort.h" +#include "igraph_vector_list.h" + +#include "operators/misc_internal.h" + +/** + * \function igraph_union + * \brief Calculates the union of two graphs. + * + * The number of vertices in the result is that of the larger graph + * from the two arguments. The result graph contains edges which are + * present in at least one of the operand graphs. + * + * + * The directedness of the operand graphs must be the same. + * + * + * Edge multiplicities are handled by taking the \em larger of the two + * multiplicities in the input graphs. In other words, if the first graph + * has N edges between a vertex pair (u, v) and the second graph has M edges, + * the result graph will have max(N, M) edges between them. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param left The first graph. + * \param right The second graph. + * \param edge_map1 Pointer to an initialized vector or a null pointer. + * If not a null pointer, it will contain a mapping from the edges + * of the first argument graph (\p left) to the edges of the + * result graph. + * \param edge_map2 The same as \p edge_map1, but for the second + * graph, \p right. + * \return Error code. + * \sa \ref igraph_union_many() for the union of many graphs, + * \ref igraph_intersection() and \ref igraph_difference() for other + * operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of + * vertices, |E| the number of edges in the result graph. + * + * \example examples/simple/igraph_union.c + */ +igraph_error_t igraph_union( + igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_int_t *edge_map1, igraph_vector_int_t *edge_map2) { + return igraph_i_merge(res, IGRAPH_MERGE_MODE_UNION, left, right, + edge_map1, edge_map2); +} + +/** + * \function igraph_union_many + * \brief Creates the union of many graphs. + * + * The result graph will contain as many vertices as the largest graph + * among the arguments does, and an edge will be included in it if it + * is part of at least one operand graph. + * + * + * The number of vertices in the result graph will be the maximum + * number of vertices in the argument graphs. + * + * + * The directedness of the argument graphs must be the same. + * If the graph list has length zero, the result will be a \em directed + * graph with no vertices. + * + * + * Edge multiplicities are handled by taking the \em maximum multiplicity of the + * all multiplicities for the same vertex pair (u, v) in the input graphs; this + * will be the multiplicity of (u, v) in the result graph. + * + * \param res Pointer to an uninitialized graph object, this will + * contain the result. + * \param graphs Pointer vector, contains pointers to the operands of + * the union operator, graph objects of course. + * \param edgemaps If not a null pointer, then it must be an initialized + * list of integer vectors, and the mappings of edges from the graphs to + * the result graph will be stored here, in the same order as + * \p graphs. Each mapping is stored in a separate + * \type igraph_vector_int_t object. + * \return Error code. + * \sa \ref igraph_union() for the union of two graphs, \ref + * igraph_intersection_many(), \ref igraph_intersection() and \ref + * igraph_difference for other operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices + * in largest graph and |E| is the number of edges in the result graph. + */ +igraph_error_t igraph_union_many( + igraph_t *res, const igraph_vector_ptr_t *graphs, + igraph_vector_int_list_t *edgemaps +) { + + igraph_int_t no_of_graphs = igraph_vector_ptr_size(graphs); + igraph_int_t no_of_nodes = 0; + igraph_bool_t directed = true; + igraph_vector_int_t edges; + igraph_vector_int_list_t edge_vects, order_vects; + igraph_vector_int_t no_edges; + igraph_int_t i, j, tailfrom = no_of_graphs > 0 ? 0 : -1, tailto = -1; + igraph_int_t idx = 0; + + /* Check directedness */ + if (no_of_graphs != 0) { + directed = igraph_is_directed(VECTOR(*graphs)[0]); + no_of_nodes = igraph_vcount(VECTOR(*graphs)[0]); + } + for (i = 1; i < no_of_graphs; i++) { + if (directed != igraph_is_directed(VECTOR(*graphs)[i])) { + IGRAPH_ERROR("Cannot create union of directed and undirected graphs.", + IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_init(&no_edges, no_of_graphs)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &no_edges); + + /* Calculate number of nodes, query number of edges */ + for (i = 0; i < no_of_graphs; i++) { + igraph_int_t n = igraph_vcount(VECTOR(*graphs)[i]); + if (n > no_of_nodes) { + no_of_nodes = n; + } + VECTOR(no_edges)[i] = igraph_ecount(VECTOR(*graphs)[i]); + } + + if (edgemaps) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edgemaps, no_of_graphs)); + for (i = 0; i < no_of_graphs; i++) { + igraph_vector_int_t* v = igraph_vector_int_list_get_ptr(edgemaps, i); + IGRAPH_CHECK(igraph_vector_int_resize(v, VECTOR(no_edges)[i])); + } + } + + /* Allocate memory for the edge lists and their index vectors */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&edge_vects, no_of_graphs); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&order_vects, no_of_graphs); + + /* Query and sort the edge lists */ + for (i = 0; i < no_of_graphs; i++) { + igraph_int_t k, j, n = VECTOR(no_edges)[i]; + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, i); + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, i); + IGRAPH_CHECK(igraph_get_edgelist(VECTOR(*graphs)[i], ev, /*bycol=*/ false)); + if (!directed) { + for (k = 0, j = 0; k < n; k++, j += 2) { + if (VECTOR(*ev)[j] > VECTOR(*ev)[j + 1]) { + igraph_int_t tmp = VECTOR(*ev)[j]; + VECTOR(*ev)[j] = VECTOR(*ev)[j + 1]; + VECTOR(*ev)[j + 1] = tmp; + } + } + } + IGRAPH_CHECK(igraph_vector_int_resize(order, n)); + for (k = 0; k < n; k++) { + VECTOR(*order)[k] = k; + } + igraph_qsort_r(VECTOR(*order), n, sizeof(VECTOR(*order)[0]), ev, + igraph_i_order_edgelist_cmp); + } + + while (tailfrom >= 0) { + + /* Get the largest tail element */ + tailfrom = tailto = -1; + for (j = 0; j < no_of_graphs; j++) { + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, j); + if (!igraph_vector_int_empty(order)) { + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, j); + igraph_int_t edge = igraph_vector_int_tail(order); + igraph_int_t from = VECTOR(*ev)[2 * edge]; + igraph_int_t to = VECTOR(*ev)[2 * edge + 1]; + if (from > tailfrom || (from == tailfrom && to > tailto)) { + tailfrom = from; tailto = to; + } + } + } + if (tailfrom < 0) { + continue; + } + + /* add the edge */ + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tailfrom)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, tailto)); + + /* update edge lists, we just modify the 'order' vectors */ + for (j = 0; j < no_of_graphs; j++) { + igraph_vector_int_t *order = igraph_vector_int_list_get_ptr(&order_vects, j); + if (!igraph_vector_int_empty(order)) { + igraph_vector_int_t *ev = igraph_vector_int_list_get_ptr(&edge_vects, j); + igraph_int_t edge = igraph_vector_int_tail(order); + igraph_int_t from = VECTOR(*ev)[2 * edge]; + igraph_int_t to = VECTOR(*ev)[2 * edge + 1]; + if (from == tailfrom && to == tailto) { + igraph_vector_int_pop_back(order); + if (edgemaps) { + igraph_vector_int_t *map = igraph_vector_int_list_get_ptr(edgemaps, j); + VECTOR(*map)[edge] = idx; + } + } + } + } + idx++; + + } + + igraph_vector_int_list_destroy(&order_vects); + igraph_vector_int_list_destroy(&edge_vects); + igraph_vector_int_destroy(&no_edges); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/all_shortest_paths.c b/src/paths/all_shortest_paths.c new file mode 100644 index 0000000..1b53bdb --- /dev/null +++ b/src/paths/all_shortest_paths.c @@ -0,0 +1,356 @@ +/* + igraph library. + Copyright (C) 2005-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/interruption.h" +#include "paths/paths_internal.h" + +#include /* memset */ + +/** + * \function igraph_get_all_shortest_paths + * \brief All shortest paths (geodesics) from a vertex. + * + * When there is more than one shortest path between two vertices, + * all of them will be returned. Every edge is considered separately, + * therefore in graphs with multi-edges, this function may produce + * a very large number of results. + * + * \param graph The graph object. + * \param vertices The result, the IDs of the vertices along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. Each vector object contains the vertices + * along a shortest path from \p from to another vertex. The vectors are + * ordered according to their target vertex: first the shortest paths to + * vertex 0, then to vertex 1, etc. No data is included for unreachable + * vertices. The list will be resized as needed. Supply a null pointer here + * if you don't need these vectors. + * \param edges The result, the IDs of the edges along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. Each vector object contains the edges + * along a shortest path from \p from to another vertex. The vectors are + * ordered according to their target vertex: first the shortest paths to + * vertex 0, then to vertex 1, etc. No data is included for unreachable + * vertices. The list will be resized as needed. Supply a null pointer here + * if you don't need these vectors. + * \param nrgeo Pointer to an initialized \ref igraph_vector_int_t object or + * \c NULL. If not \c NULL the number of shortest paths from \p from are + * stored here for every vertex in the graph. Note that the values + * will be accurate only for those vertices that are in the target + * vertex sequence (see \p to), since the search terminates as soon + * as all the target vertices have been found. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex ID. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Added in version 0.2. + * + * Time complexity: O(|V|+|E|) for most graphs, O(|V|^2) in the worst + * case. + */ +igraph_error_t igraph_get_all_shortest_paths( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode) { + + if (weights == NULL) { + /* Unweighted version */ + return igraph_i_get_all_shortest_paths_unweighted( + graph, vertices, edges, nrgeo, from, to, mode + ); + } else { + /* Weighted version */ + return igraph_get_all_shortest_paths_dijkstra( + graph, vertices, edges, nrgeo, from, to, weights, mode + ); + } +} + +igraph_error_t igraph_i_get_all_shortest_paths_unweighted( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t *geodist; + igraph_vector_int_list_t paths; + igraph_vector_int_list_t path_edge; + igraph_dqueue_int_t q; + igraph_vector_int_t *vptr; + igraph_vector_int_t *vptr_e; + igraph_vector_int_t neis; + igraph_vector_int_t ptrlist; + igraph_vector_int_t ptrhead; + igraph_int_t n; + igraph_int_t to_reach, reached = 0, maxdist = 0; + + igraph_vit_t vit; + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* paths will store the shortest paths during the search */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&paths, 0); + /* path_edge will store the shortest paths during the search */ + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&path_edge, 0); + /* neis is a temporary vector holding the neighbors of the + * node being examined */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + /* ptrlist stores indices into the paths vector, in the order + * of how they were found. ptrhead is a second-level index that + * will be used to find paths that terminate in a given vertex */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrlist, 0); + /* ptrhead contains indices into ptrlist. + * ptrhead[i] = j means that element #j-1 in ptrlist contains + * the shortest path from the root to node i. ptrhead[i] = 0 + * means that node i was not reached so far */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrhead, no_of_nodes); + /* geodist[i] == 0 if i was not reached yet and it is not in the + * target vertex sequence, or -1 if i was not reached yet and it + * is in the target vertex sequence. Otherwise it is + * one larger than the length of the shortest path from the + * source */ + geodist = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(geodist, "Insufficient memory for calculating shortest paths."); + IGRAPH_FINALLY(igraph_free, geodist); + /* dequeue to store the BFS queue -- odd elements are the vertex indices, + * even elements are the distances from the root */ + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + if (nrgeo) { + IGRAPH_CHECK(igraph_vector_int_resize(nrgeo, no_of_nodes)); + igraph_vector_int_null(nrgeo); + } + + /* use geodist to count how many vertices we have to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (geodist[ IGRAPH_VIT_GET(vit) ] == 0) { + geodist[ IGRAPH_VIT_GET(vit) ] = -1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + if (geodist[ from ] < 0) { + reached++; + } + + /* from -> from */ + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr)); + IGRAPH_CHECK(igraph_vector_int_push_back(vptr, from)); + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e)); + + geodist[from] = 1; + VECTOR(ptrhead)[from] = 1; + IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, 0)); + if (nrgeo) { + VECTOR(*nrgeo)[from] = 1; + } + + /* Init queue */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, from)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + IGRAPH_ALLOW_INTERRUPTION(); + + if (reached >= to_reach) { + /* all nodes were reached. Since we need all the shortest paths + * to all these nodes, we can stop the search only if the distance + * of the current node to the root is larger than the distance of + * any of the nodes we wanted to reach */ + if (actdist > maxdist) { + /* safety check, maxdist should have been set when we reached the last node */ + IGRAPH_ASSERT(maxdist >= 0); + break; + } + } + + /* If we need the edge-paths, we need to use igraph_incident() followed by an + * IGRAPH_OTHER() macro in the main loop. This is going to be slower than + * using igraph_neighbors() due to branch mispredictions in IGRAPH_OTHER(), so we + * use igraph_incident() only if the user needs the edge-paths */ + if (edges) { + IGRAPH_CHECK(igraph_incident(graph, &neis, actnode, mode, IGRAPH_LOOPS)); + } else { + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + } + + n = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t neighbor; + igraph_int_t parentptr; + + if (edges) { + /* user needs the edge-paths, so 'neis' contains edge IDs, we need to resolve + * the next edge ID into a vertex ID */ + neighbor = IGRAPH_OTHER(graph, VECTOR(neis)[j], actnode); + } else { + /* user does not need the edge-paths, so 'neis' contains vertex IDs */ + neighbor = VECTOR(neis)[j]; + } + + if (geodist[neighbor] > 0 && + geodist[neighbor] - 1 < actdist + 1) { + /* this node was reached via a shorter path before */ + continue; + } + + /* yay, found another shortest path to neighbor */ + + if (nrgeo) { + /* the number of geodesics leading to neighbor must be + * increased by the number of geodesics leading to actnode */ + VECTOR(*nrgeo)[neighbor] += VECTOR(*nrgeo)[actnode]; + } + if (geodist[neighbor] <= 0) { + /* this node was not reached yet, push it into the queue */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (geodist[neighbor] < 0) { + reached++; + } + if (reached == to_reach) { + maxdist = actdist; + } + } + geodist[neighbor] = actdist + 2; + + /* copy all existing paths to the parent */ + parentptr = VECTOR(ptrhead)[actnode]; + while (parentptr != 0) { + /* allocate a new igraph_vector_int_t at the end of paths */ + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr)); + IGRAPH_CHECK(igraph_vector_int_update(vptr, igraph_vector_int_list_get_ptr(&paths, parentptr - 1))); + IGRAPH_CHECK(igraph_vector_int_push_back(vptr, neighbor)); + + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e)); + if (actnode != from) { + /* If the previous vertex was the source then there is no edge to add*/ + IGRAPH_CHECK(igraph_vector_int_update(vptr_e, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1))); + } + IGRAPH_CHECK(igraph_vector_int_push_back(vptr_e, VECTOR(neis)[j])); + + IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, VECTOR(ptrhead)[neighbor])); + VECTOR(ptrhead)[neighbor] = igraph_vector_int_size(&ptrlist); + + parentptr = VECTOR(ptrlist)[parentptr - 1]; + } + } + } + + igraph_dqueue_int_destroy(&q); + IGRAPH_FINALLY_CLEAN(1); + + /* mark the nodes for which we need the result */ + memset(geodist, 0, sizeof(geodist[0]) * (size_t) no_of_nodes); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + geodist[ IGRAPH_VIT_GET(vit) ] = 1; + } + + if (vertices) { + igraph_vector_int_list_clear(vertices); + } + if (edges) { + igraph_vector_int_list_clear(edges); + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t parentptr = VECTOR(ptrhead)[i]; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* do we need the paths leading to vertex i? */ + if (geodist[i] > 0) { + /* yes, transfer them to the result vector */ + while (parentptr != 0) { + /* Given two vector lists, list1 and list2, an efficient way to transfer + * a vector from list1 to the end of list2 is to extend list2 with an + * empty vector, then swap that empty vector with the given element of + * list1. This approach avoids creating a full copy of the vector. */ + if (vertices) { + igraph_vector_int_t *p; + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(vertices, &p)); + igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&paths, parentptr - 1)); + } + if (edges) { + igraph_vector_int_t *p; + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(edges, &p)); + igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1)); + } + parentptr = VECTOR(ptrlist)[parentptr - 1]; + } + } + } + + IGRAPH_FREE(geodist); + igraph_vector_int_destroy(&ptrlist); + igraph_vector_int_destroy(&ptrhead); + igraph_vector_int_destroy(&neis); + igraph_vector_int_list_destroy(&paths); + igraph_vector_int_list_destroy(&path_edge); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/astar.c b/src/paths/astar.c new file mode 100644 index 0000000..2913b76 --- /dev/null +++ b/src/paths/astar.c @@ -0,0 +1,273 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_paths.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_memory.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +static igraph_error_t null_heuristic( + igraph_real_t *result, igraph_int_t from, igraph_int_t to, + void *extra +) { + IGRAPH_UNUSED(from); + IGRAPH_UNUSED(to); + IGRAPH_UNUSED(extra); + + *result = 0; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_shortest_path_astar + * \brief A* gives the shortest path from one vertex to another, with heuristic. + * + * Calculates a shortest path from a single source vertex to a single + * target, using the A* algorithm. A* tries to find a shortest path by + * starting at \p from and moving to vertices that lie on a path with + * the lowest estimated length. This length estimate is the sum of two + * numbers: the distance from the source (\p from) to the intermediate vertex, + * and the value returned by the heuristic function. The heuristic function + * provides an estimate the distance between intermediate candidate + * vertices and the target vertex \p to. The A* algorithm is guaranteed + * to give the correct shortest path (if one exists) only if the heuristic + * does not overestimate distances, i.e. if the heuristic function is + * \em admissible. + * + * \param graph The input graph, it can be directed or undirected. + * \param vertices Pointer to an initialized vector or the \c NULL + * pointer. If not \c NULL, then the vertex IDs along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an initialized vector or the \c NULL + * pointer. If not \c NULL, then the edge IDs along the + * path are stored here. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param weights Optional edge weights. Supply \c NULL for unweighted graphs. + * All edge weights must be non-negative. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. Edges with positive infinite weights are ignored. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. \c IGRAPH_OUT follows edge + * directions, \c IGRAPH_IN follows the opposite directions, + * and \c IGRAPH_ALL ignores edge directions. This argument is + * ignored for undirected graphs. + * \param heuristic A function that provides distance estimates to the + * target vertex. See \ref igraph_astar_heuristic_func_t for + * more information. + * \param extra This is passed on to the heuristic function. + * \return Error code. + * + * Time complexity: In the worst case, O(|E|log|V|+|V|), where + * |V| is the number of vertices and + * |E| is the number of edges in the graph. + * The running time depends on the accuracy of the distance estimates + * returned by the heuristic function. Assuming that the heuristic + * is admissible, the better the estimates, the shortert the running + * time. + */ + +igraph_error_t igraph_get_shortest_path_astar(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_astar_heuristic_func_t *heuristic, + void *extra) +{ + igraph_real_t heur_res; + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t dists; + igraph_int_t *parent_eids; + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Starting vertex out of range.", IGRAPH_EINVVID); + } + + if (to < 0 || to >= no_of_nodes) { + IGRAPH_ERROR("End vertex out of range.", IGRAPH_EINVVID); + } + + if (!heuristic) { + heuristic = null_heuristic; + } + + if (weights) { /* If there are no weights, they are treated as 1. */ + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(weights), no_of_edges); + } + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, found weight of %g.", IGRAPH_EINVAL, min); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + /* dists[v] is the length of the shortest path found so far between 'from' and 'v'. */ + IGRAPH_VECTOR_INIT_FINALLY(&dists, no_of_nodes); + igraph_vector_fill(&dists, IGRAPH_INFINITY); + + /* parent_eids[v] is the 1 + the ID of v's inbound edge in the shortest path tree. + * A value of 0 indicates unreachable vertices. */ + parent_eids = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(parent_eids, "Insufficient memory for shortest paths with A* algorithm."); + IGRAPH_FINALLY(igraph_free, parent_eids); + + VECTOR(dists)[from] = 0.0; + IGRAPH_CHECK(heuristic(&heur_res, from, to, extra)); + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, from, -heur_res)); + + igraph_bool_t found = false; + while (!igraph_2wheap_empty(&Q)) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* The from -> u -> to distance estimate is the sum of the + * from -> u distance and the u -> to distance estimate + * obtained from the heuristic. + * + * We use an indexed heap to process 'u' vertices in order + * of the smallest from -> u -> to distance estimate. Since + * we only have a maximum heap available, we store negated values + * in order to obtain smallest values first. The value taken off + * the heap is ignored, we just want the index of 'u'. */ + + igraph_int_t u; + igraph_2wheap_delete_max_index(&Q, &u); + + /* Reached the target vertex, the search can be stopped. */ + if (u == to) { + found = true; + break; + } + + /* Now we check all neighbors 'u' for a path with a shorter actual (not estimated) + * length than what was found so far. */ + + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, u); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + igraph_int_t nlen = igraph_vector_int_size(neis); + for (igraph_int_t i = 0; i < nlen; i++) { + igraph_int_t edge = VECTOR(*neis)[i]; + igraph_int_t v = IGRAPH_OTHER(graph, edge, u); + igraph_real_t altdist; /* candidate from -> v distance */ + if (weights) { + igraph_real_t weight = VECTOR(*weights)[edge]; + if (weight == IGRAPH_INFINITY) { + continue; + } + altdist = VECTOR(dists)[u] + weight; + } else { + altdist = VECTOR(dists)[u] + 1; + } + igraph_real_t curdist = VECTOR(dists)[v]; + if (curdist == IGRAPH_INFINITY) { + /* This is the first finite from -> v distance we found. + * Here we rely on infinite weight edges having been skipped, see TODO above. */ + VECTOR(dists)[v] = altdist; + parent_eids[v] = edge + 1; + IGRAPH_CHECK(heuristic(&heur_res, v, to, extra)); + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, v, -(altdist + heur_res))); + } else if (altdist < curdist) { + /* This is a shorter from -> v path than what was found before. */ + VECTOR(dists)[v] = altdist; + parent_eids[v] = edge + 1; + IGRAPH_CHECK(heuristic(&heur_res, v, to, extra)); + igraph_2wheap_modify(&Q, v, -(altdist + heur_res)); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + if (!found) { + IGRAPH_WARNING("Couldn't reach the target vertex."); + } + + /* Reconstruct the shortest paths based on vertex and/or edge IDs */ + if (vertices || edges) { + igraph_int_t size, act, edge; + + if (vertices) { + igraph_vector_int_clear(vertices); + } + if (edges) { + igraph_vector_int_clear(edges); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + size = 0; + act = to; + while (parent_eids[act]) { + size++; + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vertices && (size > 0 || to == from)) { + IGRAPH_CHECK(igraph_vector_int_resize(vertices, size + 1)); + VECTOR(*vertices)[size] = to; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_resize(edges, size)); + } + act = to; + while (parent_eids[act]) { + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + size--; + if (vertices) { + VECTOR(*vertices)[size] = act; + } + if (edges) { + VECTOR(*edges)[size] = edge; + } + } + } + + + IGRAPH_FREE(parent_eids); + igraph_vector_destroy(&dists); + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/bellman_ford.c b/src/paths/bellman_ford.c new file mode 100644 index 0000000..3ff84e4 --- /dev/null +++ b/src/paths/bellman_ford.c @@ -0,0 +1,591 @@ +/* + igraph library. + Copyright (C) 2005-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/interruption.h" +#include "paths/paths_internal.h" + +/** + * \function igraph_distances_bellman_ford + * \brief Weighted shortest path lengths between vertices, allowing negative weights. + * + * This function implements the Bellman-Ford algorithm to find the weighted + * shortest paths to all vertices from a single source, allowing negative weights. + * It is run independently for the given sources. If there are no negative + * weights, you are better off with \ref igraph_distances_dijkstra() . + * + * \param graph The input graph, can be directed. + * \param res The result, a matrix. A pointer to an initialized matrix + * should be passed here, the matrix will be resized if needed. + * Each row contains the distances from a single source, to all + * vertices in the graph, in the order of vertex IDs. For unreachable + * vertices the matrix contains \c IGRAPH_INFINITY. + * \param from The source vertices. + * \param to The target vertices. + * \param weights The edge weights. There must not be any cycle in + * the graph that has a negative total weight (since this would allow + * us to decrease the weight of any path containing at least a single + * vertex of this cycle infinitely). Additionally, no edge weight may + * be NaN. If either case does not hold, an error is returned. If this + * is a null pointer, then the unweighted version, + * \ref igraph_distances() is called. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(s*|E|*|V|), where |V| is the number of + * vertices, |E| the number of edges and s the number of sources. + * + * \sa \ref igraph_distances() for a non-algorithm-specific interface; + * \ref igraph_distances_dijkstra() if you do not have negative + * edge weights. + * + * \example examples/simple/bellman_ford.c + */ +igraph_error_t igraph_distances_bellman_ford( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + return igraph_i_distances_bellman_ford(graph, res, from, to, weights, mode); +} + +igraph_error_t igraph_i_distances_bellman_ford( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_lazy_inclist_t inclist; + igraph_int_t i; + igraph_int_t no_of_from, no_of_to; + igraph_dqueue_int_t Q; + igraph_bitset_t clean_vertices; + igraph_vector_int_t num_queued; + igraph_vit_t fromvit, tovit; + igraph_bool_t all_to; + igraph_vector_t dist; + int iter = 0; + + /* + - speedup: a vertex is marked clean if its distance from the source + did not change during the last phase. Neighbors of a clean vertex + are not relaxed again, since it would mean no change in the + shortest path values. Dirty vertices are queued. Negative cycles can + be detected by checking whether a vertex has been queued at least + n times. + */ + + if (!weights || no_of_edges == 0) { + return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, -1); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&clean_vertices, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&num_queued, no_of_nodes); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + all_to = igraph_vs_is_all(&to); + if (all_to) { + no_of_to = no_of_nodes; + } else { + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + + /* No need to check here whether the vertices in 'to' are unique because + * the loop below uses a temporary distance vector that is then copied + * into the result matrix at the end of the outer loop iteration, and + * this is safe even if 'to' contains the same vertex multiple times */ + } + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + igraph_int_t source = IGRAPH_VIT_GET(fromvit); + + igraph_vector_fill(&dist, IGRAPH_INFINITY); + VECTOR(dist)[source] = 0; + igraph_bitset_null(&clean_vertices); + igraph_vector_int_null(&num_queued); + + /* Fill the queue with vertices to be checked */ + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, j)); + } + + while (!igraph_dqueue_int_empty(&Q)) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 13); + + igraph_int_t j = igraph_dqueue_int_pop(&Q); + IGRAPH_BIT_SET(clean_vertices, j); + VECTOR(num_queued)[j] += 1; + if (VECTOR(num_queued)[j] > no_of_nodes) { + IGRAPH_ERROR("Negative cycle in graph while calculating distances with Bellman-Ford algorithm.", + IGRAPH_ENEGCYCLE); + } + + /* If we cannot get to j in finite time yet, there is no need to relax + * its edges */ + if (VECTOR(dist)[j] == IGRAPH_INFINITY) { + continue; + } + + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, j); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + igraph_int_t nlen = igraph_vector_int_size(neis); + for (igraph_int_t k = 0; k < nlen; k++) { + igraph_int_t nei = VECTOR(*neis)[k]; + igraph_int_t target = IGRAPH_OTHER(graph, nei, j); + igraph_real_t altdist = VECTOR(dist)[j] + VECTOR(*weights)[nei]; + if (VECTOR(dist)[target] > altdist) { + /* relax the edge */ + VECTOR(dist)[target] = altdist; + if (IGRAPH_BIT_TEST(clean_vertices, target)) { + IGRAPH_BIT_CLEAR(clean_vertices, target); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, target)); + } + } + } + } + + /* Copy it to the result */ + if (all_to) { + IGRAPH_CHECK(igraph_matrix_set_row(res, &dist, i)); + } else { + igraph_int_t j; + for (IGRAPH_VIT_RESET(tovit), j = 0; !IGRAPH_VIT_END(tovit); + IGRAPH_VIT_NEXT(tovit), j++) { + igraph_int_t v = IGRAPH_VIT_GET(tovit); + MATRIX(*res, i, j) = VECTOR(dist)[v]; + } + } + } + + igraph_vector_destroy(&dist); + IGRAPH_FINALLY_CLEAN(1); + + if (!all_to) { + igraph_vit_destroy(&tovit); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&fromvit); + igraph_dqueue_int_destroy(&Q); + igraph_bitset_destroy(&clean_vertices); + igraph_vector_int_destroy(&num_queued); + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_get_shortest_paths_bellman_ford + * \brief Weighted shortest paths from a vertex, allowing negative weights. + * + * This function calculates weighted shortest paths from or to a single vertex + * using the Bellman-Ford algorithm, whihc can handle negative weights. When + * there is more than one shortest path between two vertices, only one of them + * is returned. When there are no negative weights, + * \ref igraph_get_shortest_paths_dijkstra() is likely to be faster. + * + * \param graph The input graph, can be directed. + * \param vertices The result, the IDs of the vertices along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param edges The result, the IDs of the edges along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param weights The edge weights. There must not be any cycle in + * the graph that has a negative total weight (since this would allow + * us to decrease the weight of any path containing at least a single + * vertex of this cycle infinitely). If this is a null pointer, then the + * unweighted version, \ref igraph_get_shortest_paths() is called. + * Edges with positive infinite weights are ignored. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \param parents A pointer to an initialized igraph vector or null. + * If not null, a vector containing the parent of each vertex in + * the single source shortest path tree is returned here. The + * parent of vertex i in the tree is the vertex from which vertex i + * was reached. The parent of the start vertex (in the \c from + * argument) is -1. If the parent is -2, it means + * that the given vertex was not reached from the source during the + * search. Note that the search terminates if all the vertices in + * \c to are reached. + * \param inbound_edges A pointer to an initialized igraph vector or null. + * If not null, a vector containing the inbound edge of each vertex in + * the single source shortest path tree is returned here. The + * inbound edge of vertex i in the tree is the edge via which vertex i + * was reached. The start vertex and vertices that were not reached + * during the search will have -1 in the corresponding entry of the + * vector. Note that the search terminates if all the vertices in + * \c to are reached. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * Not enough memory for temporary data. + * \cli IGRAPH_EINVAL + * The weight vector doesn't math the number of edges. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex ID + * \cli IGRAPH_ENEGCYCLE + * Bellman-ford algorithm encounted a negative cycle. + * \endclist + * + * Time complexity: O(|E|*|V|), where |V| is the number of + * vertices, |E| the number of edges. + * + * \sa \ref igraph_distances_bellman_ford() to compute only shortest path + * lengths, but not the paths themselves; \ref igraph_get_shortest_paths() for + * a non-algorithm-specific interface. + */ +igraph_error_t igraph_get_shortest_paths_bellman_ford( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + return igraph_i_get_shortest_paths_bellman_ford(graph, vertices, edges, from, to, weights, mode, parents, inbound_edges); +} + +igraph_error_t igraph_i_get_shortest_paths_bellman_ford( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t *parent_eids; + igraph_lazy_inclist_t inclist; + igraph_int_t i, j, k; + igraph_dqueue_int_t Q; + igraph_bitset_t clean_vertices; + igraph_vector_int_t num_queued; + igraph_vit_t tovit; + igraph_vector_t dist; + int iter = 0; + + if (!weights) { + return igraph_i_get_shortest_paths_unweighted(graph, vertices, edges, from, to, mode, parents, inbound_edges); + } + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&clean_vertices, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&num_queued, no_of_nodes); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_resize(vertices, IGRAPH_VIT_SIZE(tovit))); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edges, IGRAPH_VIT_SIZE(tovit))); + } + + parent_eids = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(parent_eids, "Insufficient memory for shortest paths with Bellman-Ford."); + IGRAPH_FINALLY(igraph_free, parent_eids); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + + igraph_vector_fill(&dist, IGRAPH_INFINITY); + VECTOR(dist)[from] = 0; + + /* Fill the queue with vertices to be checked */ + for (j = 0; j < no_of_nodes; j++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, j)); + } + + while (!igraph_dqueue_int_empty(&Q)) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 13); + + j = igraph_dqueue_int_pop(&Q); + IGRAPH_BIT_SET(clean_vertices, j); + VECTOR(num_queued)[j] += 1; + if (VECTOR(num_queued)[j] > no_of_nodes) { + IGRAPH_ERROR("Negative cycle in graph while calculating distances with Bellman-Ford algorithm.", + IGRAPH_ENEGCYCLE); + } + + /* If we cannot get to j in finite time yet, there is no need to relax its edges */ + if (VECTOR(dist)[j] == IGRAPH_INFINITY) { + continue; + } + + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, j); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + igraph_int_t nlen = igraph_vector_int_size(neis); + for (k = 0; k < nlen; k++) { + igraph_int_t nei = VECTOR(*neis)[k]; + igraph_int_t target = IGRAPH_OTHER(graph, nei, j); + igraph_real_t weight = VECTOR(*weights)[nei]; + igraph_real_t altdist = VECTOR(dist)[j] + weight; + + /* infinite weights are handled correctly here; if an edge has + * infinite weight, altdist will also be infinite so the condition + * will never be true as if the edge was ignored */ + + if (VECTOR(dist)[target] > altdist) { + /* relax the edge */ + VECTOR(dist)[target] = altdist; + parent_eids[target] = nei + 1; + if (IGRAPH_BIT_TEST(clean_vertices, target)) { + IGRAPH_BIT_CLEAR(clean_vertices, target); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, target)); + } + } + } + } + + /* Create `parents' if needed */ + if (parents) { + IGRAPH_CHECK(igraph_vector_int_resize(parents, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (i == from) { + /* i is the start vertex */ + VECTOR(*parents)[i] = -1; + } else if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*parents)[i] = -2; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*parents)[i] = IGRAPH_OTHER(graph, parent_eids[i] - 1, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_int_resize(inbound_edges, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*inbound_edges)[i] = parent_eids[i] - 1; + } + } + } + + /* Reconstruct the shortest paths based on vertex and/or edge IDs */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(tovit), i = 0; !IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(tovit); + igraph_int_t size, act, edge; + igraph_vector_int_t *vvec = 0, *evec = 0; + if (vertices) { + vvec = igraph_vector_int_list_get_ptr(vertices, i); + igraph_vector_int_clear(vvec); + } + if (edges) { + evec = igraph_vector_int_list_get_ptr(edges, i); + igraph_vector_int_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + size = 0; + act = node; + while (parent_eids[act]) { + size++; + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec && (size > 0 || node == from)) { + IGRAPH_CHECK(igraph_vector_int_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = node; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_int_resize(evec, size)); + } + act = node; + while (parent_eids[act]) { + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + size--; + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + + igraph_vector_destroy(&dist); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vit_destroy(&tovit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FREE(parent_eids); + igraph_dqueue_int_destroy(&Q); + igraph_bitset_destroy(&clean_vertices); + igraph_vector_int_destroy(&num_queued); + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_shortest_path_bellman_ford + * \brief Weighted shortest path from one vertex to another one (Bellman-Ford). + * + * Finds a weighted shortest path from a single source vertex to + * a single target using the Bellman-Ford algorithm. + * + * + * This function is a special case (and a wrapper) to + * \ref igraph_get_shortest_paths_bellman_ford(). + * + * \param graph The input graph, it can be directed or undirected. + * \param vertices Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex IDs along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the edge IDs along the + * path are stored here. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param weights The edge weights. There must not be any cycle in + * the graph that has a negative total weight (since this would allow + * us to decrease the weight of any path containing at least a single + * vertex of this cycle infinitely). If this is a null pointer, then the + * unweighted version is called. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. \c IGRAPH_OUT follows edge + * directions, \c IGRAPH_IN follows the opposite directions, + * and \c IGRAPH_ALL ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|log|E|+|V|), |V| is the number of vertices, + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_get_shortest_paths_bellman_ford() for the version with + * more target vertices. + */ + +igraph_error_t igraph_get_shortest_path_bellman_ford(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_vector_int_list_t vertices2, *vp = &vertices2; + igraph_vector_int_list_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &vertices2); + } else { + vp = NULL; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &edges2); + } else { + ep = NULL; + } + + IGRAPH_CHECK(igraph_get_shortest_paths_bellman_ford(graph, vp, ep, + from, igraph_vss_1(to), + weights, mode, NULL, NULL)); + + /* We use the constant time vector_swap() instead of the linear-time vector_update() to move the + result to the output parameter. */ + if (edges) { + igraph_vector_int_swap(edges, igraph_vector_int_list_get_ptr(&edges2, 0)); + igraph_vector_int_list_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + igraph_vector_int_swap(vertices, igraph_vector_int_list_get_ptr(&vertices2, 0)); + igraph_vector_int_list_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/dijkstra.c b/src/paths/dijkstra.c new file mode 100644 index 0000000..a696048 --- /dev/null +++ b/src/paths/dijkstra.c @@ -0,0 +1,1235 @@ +/* + igraph library. + Copyright (C) 2005-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_nongraph.h" +#include "igraph_stack.h" +#include "igraph_vector_ptr.h" + +#include "core/indheap.h" +#include "core/interruption.h" +#include "paths/paths_internal.h" + +#include /* memset */ + +igraph_error_t igraph_i_validate_distance_weights( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_bool_t *negative_weights) { + + const igraph_int_t ecount = igraph_ecount(graph); + + *negative_weights = false; + + if (weights) { + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERRORF("Edge weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), ecount); + } + + if (ecount > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + *negative_weights = true; + } + if (isnan(min)) { + IGRAPH_ERROR("Edge weights must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_distances_dijkstra_cutoff + * \brief Weighted shortest path lengths between vertices, with cutoff. + * + * This function is similar to \ref igraph_distances_dijkstra(), but + * paths longer than \p cutoff will not be considered. + * + * \param graph The input graph, can be directed. + * \param res The result, a matrix. A pointer to an initialized matrix + * should be passed here. The matrix will be resized as needed. + * Each row contains the distances from a single source, to the + * vertices given in the \p to argument. + * Vertices that are not reachable within distance \p cutoff will + * be assigned distance \c IGRAPH_INFINITY. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_distances() is called. Edges with positive infinite + * weights are ignored. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \param cutoff The maximal length of paths that will be considered. + * When the distance of two vertices is greater than this value, + * it will be returned as \c IGRAPH_INFINITY. Negative cutoffs and + * \ref IGRAPH_UNLIMITED are treated as infinity. + * \return Error code. + * + * Time complexity: at most O(s |E| log|V| + |V|), where |V| is the number of + * vertices, |E| the number of edges and s the number of sources. The + * \p cutoff parameter will limit the number of edges traversed from each + * source vertex, which reduces the computation time. + * + * \sa \ref igraph_distances_cutoff() for a (slightly) faster unweighted + * version. + * + * \example examples/simple/distances.c + */ +igraph_error_t igraph_distances_dijkstra_cutoff( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_real_t cutoff) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + if (negative_weights) { + IGRAPH_ERRORF("Edge weights must not be negative when using Dijkstra's algorithm, got %g.", + IGRAPH_EINVAL, + igraph_vector_min(weights)); + } + return igraph_i_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, cutoff); +} + + +igraph_error_t igraph_i_distances_dijkstra_cutoff(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_real_t cutoff) { + + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. + + From now on we use a 2-way heap, so the distances can be queried + directly from the heap. + + Tricks: + - The opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + */ + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_2wheap_t Q; + igraph_vit_t fromvit, tovit; + igraph_int_t no_of_from, no_of_to; + igraph_lazy_inclist_t inclist; + igraph_int_t i, j; + igraph_bool_t all_to; + igraph_vector_int_t indexv; + + if (!weights) { + return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, cutoff); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + all_to = igraph_vs_is_all(&to); + if (all_to) { + no_of_to = no_of_nodes; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&indexv, no_of_nodes); + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + + /* We need to check whether the vertices in 'tovit' are unique; this is + * because the inner while loop of the main algorithm updates the + * distance matrix whenever a shorter path is encountered from the + * source vertex 'i' to a target vertex, and we need to be able to + * map a target vertex to its column in the distance matrix. The mapping + * is constructed by the loop below */ + for (i = 0; !IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit)) { + igraph_int_t v = IGRAPH_VIT_GET(tovit); + if (VECTOR(indexv)[v]) { + IGRAPH_ERROR("Target vertex list must not have any duplicates.", + IGRAPH_EINVAL); + } + VECTOR(indexv)[v] = ++i; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + igraph_matrix_fill(res, IGRAPH_INFINITY); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + + igraph_int_t reached = 0; + igraph_int_t source = IGRAPH_VIT_GET(fromvit); + + igraph_2wheap_clear(&Q); + + /* Many systems distinguish between +0.0 and -0.0. + * Since we store negative distances in the heap, + * we must insert -0.0 in order to get +0.0 as the + * final distance result. */ + igraph_2wheap_push_with_index(&Q, source, -0.0); + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_vector_int_t *neis; + igraph_int_t nlen; + + if (cutoff >= 0 && mindist > cutoff) { + continue; + } + + if (all_to) { + MATRIX(*res, i, minnei) = mindist; + } else { + if (VECTOR(indexv)[minnei]) { + MATRIX(*res, i, VECTOR(indexv)[minnei] - 1) = mindist; + reached++; + if (reached == no_of_to) { + igraph_2wheap_clear(&Q); + break; + } + } + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_real_t weight = VECTOR(*weights)[edge]; + + /* Optimization: do not follow infinite-weight edges. */ + if (weight == IGRAPH_INFINITY) { + continue; + } + + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + weight; + + if (! igraph_2wheap_has_elem(&Q, tto)) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (igraph_2wheap_has_active(&Q, tto)) { + igraph_real_t curdist = -igraph_2wheap_get(&Q, tto); + if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + } /* !IGRAPH_VIT_END(fromvit) */ + + if (!all_to) { + igraph_vit_destroy(&tovit); + igraph_vector_int_destroy(&indexv); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&fromvit); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_distances_dijkstra + * \brief Weighted shortest path lengths between vertices. + * + * This function implements Dijkstra's algorithm, which can find + * the weighted shortest path lengths from a source vertex to all + * other vertices. This function allows specifying a set of source + * and target vertices. The algorithm is run independently for each + * source and the results are retained only for the specified targets. + * This implementation uses a binary heap for efficiency. + * + * \param graph The input graph, can be directed. + * \param res The result, a matrix. A pointer to an initialized matrix + * should be passed here. The matrix will be resized as needed. + * Each row contains the distances from a single source, to the + * vertices given in the \p to argument. + * Unreachable vertices have distance \c IGRAPH_INFINITY. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_distances() is called. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(s*|E|log|V|+|V|), where |V| is the number of + * vertices, |E| the number of edges and s the number of sources. + * + * \sa \ref igraph_distances() for a non-algorithm-specific interface + * or \ref igraph_distances_bellman_ford() for a weighted + * variant that works in the presence of negative edge weights (but no + * negative loops) + * + * \example examples/simple/distances.c + */ +igraph_error_t igraph_distances_dijkstra( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + return igraph_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, -1); +} + + +/** + * \ingroup structural + * \function igraph_get_shortest_paths_dijkstra + * \brief Weighted shortest paths from a vertex. + * + * Finds weighted shortest paths from a single source vertex to the specified + * sets of target vertices using Dijkstra's algorithm. If there is more than + * one path with the smallest weight between two vertices, this function gives + * only one of them. To find all such paths, use + * \ref igraph_get_all_shortest_paths_dijkstra(). + * + * \param graph The graph object. + * \param vertices The result, the IDs of the vertices along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param edges The result, the IDs of the edges along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. +* \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_get_shortest_paths() is called. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param parents A pointer to an initialized igraph vector or null. + * If not null, a vector containing the parent of each vertex in + * the single source shortest path tree is returned here. The + * parent of vertex i in the tree is the vertex from which vertex i + * was reached. The parent of the start vertex (in the \c from + * argument) is -1. If the parent is -2, it means + * that the given vertex was not reached from the source during the + * search. Note that the search terminates if all the vertices in + * \c to are reached. + * \param inbound_edges A pointer to an initialized igraph vector or null. + * If not null, a vector containing the inbound edge of each vertex in + * the single source shortest path tree is returned here. The + * inbound edge of vertex i in the tree is the edge via which vertex i + * was reached. The start vertex and vertices that were not reached + * during the search will have -1 in the corresponding entry of the + * vector. Note that the search terminates if all the vertices in + * \c to are reached. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex ID + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|E|log|V|+|V|), where |V| is the number of + * vertices and |E| is the number of edges + * + * \sa \ref igraph_distances_dijkstra() if you only need the path lengths but + * not the paths themselves; \ref igraph_get_all_shortest_paths_dijkstra() to + * find all shortest paths between (source, target) pairs; + * \ref igraph_get_shortest_paths() for a non-algorithm-specific interface. + * + * \example examples/simple/igraph_get_shortest_paths_dijkstra.c + */ +igraph_error_t igraph_get_shortest_paths_dijkstra( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + if (negative_weights) { + IGRAPH_ERRORF("Edge weights must not be negative when using Dijkstra's algorithm, got %g.", + IGRAPH_EINVAL, + igraph_vector_min(weights)); + } + return igraph_i_get_shortest_paths_dijkstra(graph, vertices, edges, from, to, weights, mode, parents, inbound_edges); +} + + +igraph_error_t igraph_i_get_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. The other + mapping, i.e. getting the distance for a vertex is not in the + heap (that would by the double-indexed heap), but in the result + matrix. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY in the distance vector during the + computation, as isfinite() might involve a function call + and we want to spare that. So we store distance+1.0 instead of + distance, and zero denotes infinity. + - `parent_eids' assigns the inbound edge IDs of all vertices in the + shortest path tree to the vertices. In this implementation, the + edge ID + 1 is stored, zero means unreachable vertices. + */ + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t dists; + igraph_int_t *parent_eids; + igraph_bool_t *is_target; + igraph_int_t i, to_reach; + + if (!weights) { + return igraph_i_get_shortest_paths_unweighted(graph, vertices, edges, from, to, mode, parents, inbound_edges); + } + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_resize(vertices, IGRAPH_VIT_SIZE(vit))); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edges, IGRAPH_VIT_SIZE(vit))); + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&dists, no_of_nodes); + igraph_vector_fill(&dists, -1.0); + + parent_eids = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(parent_eids, "Insufficient memory for shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, parent_eids); + + is_target = IGRAPH_CALLOC(no_of_nodes, igraph_bool_t); + IGRAPH_CHECK_OOM(is_target, "Insufficient memory for shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, is_target); + + /* Mark the vertices we need to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (!is_target[ IGRAPH_VIT_GET(vit) ]) { + is_target[ IGRAPH_VIT_GET(vit) ] = true; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + VECTOR(dists)[from] = 0.0; /* zero distance */ + parent_eids[from] = 0; + igraph_2wheap_push_with_index(&Q, from, 0); + + while (!igraph_2wheap_empty(&Q) && to_reach > 0) { + igraph_int_t nlen, minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_int_t *neis; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (is_target[minnei]) { + is_target[minnei] = false; + to_reach--; + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (i = 0; i < nlen; i++) { + igraph_int_t edge = VECTOR(*neis)[i]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dists)[tto]; + if (curdist < 0) { + /* This is the first finite distance */ + VECTOR(dists)[tto] = altdist; + parent_eids[tto] = edge + 1; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + VECTOR(dists)[tto] = altdist; + parent_eids[tto] = edge + 1; + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + if (to_reach > 0) { + IGRAPH_WARNING("Couldn't reach some vertices."); + } + + /* Create `parents' if needed */ + if (parents) { + IGRAPH_CHECK(igraph_vector_int_resize(parents, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (i == from) { + /* i is the start vertex */ + VECTOR(*parents)[i] = -1; + } else if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*parents)[i] = -2; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*parents)[i] = IGRAPH_OTHER(graph, parent_eids[i] - 1, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_int_resize(inbound_edges, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*inbound_edges)[i] = parent_eids[i] - 1; + } + } + } + + /* Reconstruct the shortest paths based on vertex and/or edge IDs */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + igraph_int_t size, act, edge; + igraph_vector_int_t *vvec = 0, *evec = 0; + if (vertices) { + vvec = igraph_vector_int_list_get_ptr(vertices, i); + igraph_vector_int_clear(vvec); + } + if (edges) { + evec = igraph_vector_int_list_get_ptr(edges, i); + igraph_vector_int_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + size = 0; + act = node; + while (parent_eids[act]) { + size++; + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec && (size > 0 || node == from)) { + IGRAPH_CHECK(igraph_vector_int_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = node; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_int_resize(evec, size)); + } + act = node; + while (parent_eids[act]) { + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + size--; + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vector_destroy(&dists); + IGRAPH_FREE(is_target); + IGRAPH_FREE(parent_eids); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_shortest_path_dijkstra + * \brief Weighted shortest path from one vertex to another one (Dijkstra). + * + * Finds a weighted shortest path from a single source vertex to + * a single target, using Dijkstra's algorithm. If more than one + * shortest path exists, an arbitrary one is returned. + * + * + * This function is a special case (and a wrapper) to + * \ref igraph_get_shortest_paths_dijkstra(). + * + * \param graph The input graph, it can be directed or undirected. + * \param vertices Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex IDs along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the edge IDs along the + * path are stored here. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_get_shortest_paths() is called. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. \c IGRAPH_OUT follows edge + * directions, \c IGRAPH_IN follows the opposite directions, + * and \c IGRAPH_ALL ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|log|V|+|V|), |V| is the number of vertices, + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_get_shortest_paths_dijkstra() for the version with + * more target vertices. + */ + +igraph_error_t igraph_get_shortest_path_dijkstra(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_vector_int_list_t vertices2, *vp = &vertices2; + igraph_vector_int_list_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &vertices2); + } else { + vp = NULL; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &edges2); + } else { + ep = NULL; + } + + IGRAPH_CHECK(igraph_get_shortest_paths_dijkstra(graph, vp, ep, + from, igraph_vss_1(to), + weights, mode, NULL, NULL)); + + /* We use the constant time vector_swap() instead of the linear-time vector_update() to move the + result to the output parameter. */ + if (edges) { + igraph_vector_int_swap(edges, igraph_vector_int_list_get_ptr(&edges2, 0)); + igraph_vector_int_list_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + igraph_vector_int_swap(vertices, igraph_vector_int_list_get_ptr(&vertices2, 0)); + igraph_vector_int_list_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_get_all_shortest_paths_dijkstra + * \brief All weighted shortest paths (geodesics) from a vertex. + * + * \param graph The graph object. + * \param vertices Pointer to an initialized integer vector list or NULL. + * If not NULL, then each vector object contains the vertices along a + * shortest path from \p from to another vertex. The vectors are + * ordered according to their target vertex: first the shortest + * paths to vertex 0, then to vertex 1, etc. No data is included + * for unreachable vertices. + * \param edges Pointer to an initialized integer vector list or NULL. If + * not NULL, then each vector object contains the edges along a + * shortest path from \p from to another vertex. The vectors are + * ordered according to their target vertex: first the shortest + * paths to vertex 0, then to vertex 1, etc. No data is included for + * unreachable vertices. + * \param nrgeo Pointer to an initialized igraph_vector_int_t object or + * NULL. If not NULL the number of shortest paths from \p from are + * stored here for every vertex in the graph. Note that the values + * will be accurate only for those vertices that are in the target + * vertex sequence (see \p to), since the search terminates as soon + * as all the target vertices have been found. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_get_all_shortest_paths() is called. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is an invalid vertex ID + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|E|log|V|+|V|), where |V| is the number of + * vertices and |E| is the number of edges + * + * \sa \ref igraph_distances_dijkstra() if you only need the path + * lengths but not the paths themselves, \ref igraph_get_all_shortest_paths() + * if all edge weights are equal. + * + * \example examples/simple/igraph_get_all_shortest_paths_dijkstra.c + */ +igraph_error_t igraph_get_all_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + /* Implementation details: see igraph_get_shortest_paths_dijkstra(), + * it's basically the same. */ + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t dists; + igraph_vector_int_t index; + igraph_vector_int_t order; + igraph_vector_ptr_t parents, parents_edge; + igraph_bool_t negative_weights; + + unsigned char *is_target; /* uses more than two discrete values, can't be 'bool' */ + igraph_int_t i, n, to_reach; + igraph_bool_t free_vertices = false; + int cmp_result; + const double eps = IGRAPH_SHORTEST_PATH_EPSILON; + + if (!weights) { + return igraph_i_get_all_shortest_paths_unweighted(graph, vertices, edges, nrgeo, from, to, mode); + } + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + if (negative_weights) { + IGRAPH_ERRORF("Edge weights must not be negative when using Dijkstra's algorithm, got %g.", + IGRAPH_EINVAL, + igraph_vector_min(weights)); + } + + if (vertices == NULL && nrgeo == NULL && edges == NULL) { + return IGRAPH_SUCCESS; + } + + /* parents stores a vector for each vertex, listing the parent vertices + * of each vertex in the traversal. Right now we do not use an + * igraph_vector_int_list_t because that would pre-initialize vectors + * for all the nodes even if the traversal would involve only a small part + * of the graph */ + IGRAPH_CHECK(igraph_vector_ptr_init(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &parents); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&parents, igraph_vector_destroy); + + /* parents_edge stores a vector for each vertex, listing the parent edges + * of each vertex in the traversal */ + IGRAPH_CHECK(igraph_vector_ptr_init(&parents_edge, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &parents_edge); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&parents_edge, igraph_vector_destroy); + + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *parent_vec, *parent_edge_vec; + + parent_vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(parent_vec, "Insufficient memory for all shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, parent_vec); + IGRAPH_CHECK(igraph_vector_int_init(parent_vec, 0)); + VECTOR(parents)[i] = parent_vec; + IGRAPH_FINALLY_CLEAN(1); + + parent_edge_vec = IGRAPH_CALLOC(1, igraph_vector_int_t); + IGRAPH_CHECK_OOM(parent_edge_vec, "Insufficient memory for all shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, parent_edge_vec); + IGRAPH_CHECK(igraph_vector_int_init(parent_edge_vec, 0)); + VECTOR(parents_edge)[i] = parent_edge_vec; + IGRAPH_FINALLY_CLEAN(1); + } + + /* distance of each vertex from the root */ + IGRAPH_VECTOR_INIT_FINALLY(&dists, no_of_nodes); + igraph_vector_fill(&dists, -1.0); + + /* order lists the order of vertices in which they were found during + * the traversal */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, 0); + + /* boolean array to mark whether a given vertex is a target or not */ + is_target = IGRAPH_CALLOC(no_of_nodes, unsigned char); + IGRAPH_CHECK_OOM(is_target, "Insufficient memory for all shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, is_target); + + /* two-way heap storing vertices and distances */ + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + + /* lazy adjacency edge list to query neighbours efficiently */ + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + /* Mark the vertices we need to reach */ + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (!is_target[ IGRAPH_VIT_GET(vit) ]) { + is_target[ IGRAPH_VIT_GET(vit) ] = 1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + VECTOR(dists)[from] = 0.0; /* zero distance */ + igraph_2wheap_push_with_index(&Q, from, 0.0); + + while (!igraph_2wheap_empty(&Q) && to_reach > 0) { + igraph_int_t nlen, minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_int_t *neis; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (is_target[minnei]) { + is_target[minnei] = 0; + to_reach--; + } + + /* Mark that we have reached this vertex */ + IGRAPH_CHECK(igraph_vector_int_push_back(&order, minnei)); + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (i = 0; i < nlen; i++) { + igraph_int_t edge = VECTOR(*neis)[i]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dists)[tto]; + igraph_vector_int_t *parent_vec, *parent_edge_vec; + + cmp_result = igraph_cmp_epsilon(curdist, altdist, eps); + if (curdist < 0) { + /* This is the first non-infinite distance */ + VECTOR(dists)[tto] = altdist; + + parent_vec = (igraph_vector_int_t*)VECTOR(parents)[tto]; + IGRAPH_CHECK(igraph_vector_int_push_back(parent_vec, minnei)); + parent_edge_vec = (igraph_vector_int_t*)VECTOR(parents_edge)[tto]; + IGRAPH_CHECK(igraph_vector_int_push_back(parent_edge_vec, edge)); + + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (cmp_result == 0 /* altdist == curdist */ && VECTOR(*weights)[edge] > 0) { + /* This is an alternative path with exactly the same length. + * Note that we consider this case only if the edge via which we + * reached the node has a nonzero weight; otherwise we could create + * infinite loops in undirected graphs by traversing zero-weight edges + * back-and-forth */ + parent_vec = (igraph_vector_int_t*) VECTOR(parents)[tto]; + IGRAPH_CHECK(igraph_vector_int_push_back(parent_vec, minnei)); + parent_edge_vec = (igraph_vector_int_t*) VECTOR(parents_edge)[tto]; + IGRAPH_CHECK(igraph_vector_int_push_back(parent_edge_vec, edge)); + } else if (cmp_result > 0 /* altdist < curdist */) { + /* This is a shorter path */ + VECTOR(dists)[tto] = altdist; + + parent_vec = (igraph_vector_int_t*)VECTOR(parents)[tto]; + igraph_vector_int_clear(parent_vec); + IGRAPH_CHECK(igraph_vector_int_push_back(parent_vec, minnei)); + parent_edge_vec = (igraph_vector_int_t*)VECTOR(parents_edge)[tto]; + igraph_vector_int_clear(parent_edge_vec); + IGRAPH_CHECK(igraph_vector_int_push_back(parent_edge_vec, edge)); + + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + if (to_reach > 0) { + IGRAPH_WARNING("Couldn't reach some of the requested target vertices."); + } + + /* we don't need these anymore */ + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + /* + printf("Order:\n"); + igraph_vector_int_print(&order); + + printf("Parent vertices:\n"); + for (i = 0; i < no_of_nodes; i++) { + if (igraph_vector_int_size(VECTOR(parents)[i]) > 0) { + printf("[%ld]: ", i); + igraph_vector_int_print(VECTOR(parents)[i]); + } + } + */ + + if (nrgeo) { + IGRAPH_CHECK(igraph_vector_int_resize(nrgeo, no_of_nodes)); + igraph_vector_int_null(nrgeo); + + /* Theoretically, we could calculate nrgeo in parallel with the traversal. + * However, that way we would have to check whether nrgeo is null or not + * every time we want to update some element in nrgeo. Since we need the + * order vector anyway for building the final result, we could just as well + * build nrgeo here. + */ + VECTOR(*nrgeo)[from] = 1; + n = igraph_vector_int_size(&order); + for (i = 1; i < n; i++) { + igraph_int_t node, j, k; + igraph_vector_int_t *parent_vec; + + node = VECTOR(order)[i]; + /* now, take the parent vertices */ + parent_vec = (igraph_vector_int_t*)VECTOR(parents)[node]; + k = igraph_vector_int_size(parent_vec); + for (j = 0; j < k; j++) { + VECTOR(*nrgeo)[node] += VECTOR(*nrgeo)[VECTOR(*parent_vec)[j]]; + } + } + } + + if (vertices || edges) { + igraph_vector_int_t *path, *parent_vec, *parent_edge_vec; + igraph_vector_t *paths_index; + igraph_stack_int_t stack; + igraph_int_t j, node; + + /* a shortest path from the starting vertex to vertex i can be + * obtained by calculating the shortest paths from the "parents" + * of vertex i in the traversal. Knowing which of the vertices + * are "targets" (see is_target), we can collect for which other + * vertices do we need to calculate the shortest paths. We reuse + * is_target for that; is_target = 0 means that we don't need the + * vertex, is_target = 1 means that the vertex is a target (hence + * we need it), is_target = 2 means that the vertex is not a target + * but it stands between a shortest path between the root and one + * of the targets + */ + if (igraph_vs_is_all(&to)) { + memset(is_target, 1, sizeof(unsigned char) * (size_t) no_of_nodes); + } else { + memset(is_target, 0, sizeof(unsigned char) * (size_t) no_of_nodes); + + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + + /* Add the target vertices to the queue */ + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + i = IGRAPH_VIT_GET(vit); + if (!is_target[i]) { + is_target[i] = 1; + IGRAPH_CHECK(igraph_stack_int_push(&stack, i)); + } + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + while (!igraph_stack_int_empty(&stack)) { + /* For each parent of node i, get its parents */ + igraph_int_t el = igraph_stack_int_pop(&stack); + parent_vec = (igraph_vector_int_t*) VECTOR(parents)[el]; + i = igraph_vector_int_size(parent_vec); + + for (j = 0; j < i; j++) { + /* For each parent, check if it's already in the stack. + * If not, push it and mark it in is_target */ + n = VECTOR(*parent_vec)[j]; + if (!is_target[n]) { + is_target[n] = 2; + IGRAPH_CHECK(igraph_stack_int_push(&stack, n)); + } + } + } + igraph_stack_int_destroy(&stack); + IGRAPH_FINALLY_CLEAN(1); + } + + /* now, reconstruct the shortest paths from the parent list in the + * order we've found the nodes during the traversal. + * dists is being re-used as a vector where element i tells the + * index in vertices where the shortest paths leading to vertex i + * start, plus one (so that zero means that there are no paths + * for a given vertex). + */ + paths_index = &dists; + n = igraph_vector_int_size(&order); + igraph_vector_null(paths_index); + + if (edges) { + igraph_vector_int_list_clear(edges); + } + + if (vertices) { + igraph_vector_int_list_clear(vertices); + } else { + /* If the 'vertices' vector doesn't exist, then create one, in order + * for the algorithm to work. */ + vertices = IGRAPH_CALLOC(1, igraph_vector_int_list_t); + IGRAPH_CHECK_OOM(vertices, "Insufficient memory for all shortest paths with Dijkstra's algorithm."); + IGRAPH_FINALLY(igraph_free, vertices); + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(vertices, 0); + free_vertices = true; + } + + /* by definition, the shortest path leading to the starting vertex + * consists of the vertex itself only */ + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(vertices, &path)); + IGRAPH_CHECK(igraph_vector_int_push_back(path, from)); + + if (edges) { + /* the shortest path from the source to itself is empty */ + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(edges, &path)); + } + VECTOR(*paths_index)[from] = 1; + + for (i = 1; i < n; i++) { + igraph_int_t m, path_count; + igraph_vector_int_t *parent_path, *parent_path_edge; + + node = VECTOR(order)[i]; + + /* if we don't need the shortest paths for this node (because + * it is not standing in a shortest path between the source + * node and any of the target nodes), skip it */ + if (!is_target[node]) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + /* we are calculating the shortest paths of node now. */ + /* first, we update the paths_index */ + path_count = igraph_vector_int_list_size(vertices); + VECTOR(*paths_index)[node] = path_count + 1; + + /* now, take the parent vertices */ + parent_vec = (igraph_vector_int_t*) VECTOR(parents)[node]; + parent_edge_vec = (igraph_vector_int_t*) VECTOR(parents_edge)[node]; + m = igraph_vector_int_size(parent_vec); + + /* + printf("Calculating shortest paths to vertex %ld\n", node); + printf("Parents are: "); + igraph_vector_print(parent_vec); + */ + + for (j = 0; j < m; j++) { + /* for each parent, copy the shortest paths leading to that parent + * and add the current vertex in the end */ + igraph_int_t parent_node = VECTOR(*parent_vec)[j]; + igraph_int_t parent_edge = VECTOR(*parent_edge_vec)[j]; + igraph_int_t parent_path_idx = VECTOR(*paths_index)[parent_node] - 1; + /* + printf(" Considering parent: %ld\n", parent_node); + printf(" Paths to parent start at index %ld in vertices\n", parent_path_idx); + */ + IGRAPH_ASSERT(parent_path_idx >= 0); + for (; parent_path_idx < path_count; parent_path_idx++) { + parent_path = igraph_vector_int_list_get_ptr(vertices, parent_path_idx); + if (igraph_vector_int_tail(parent_path) != parent_node) { + break; + } + + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(vertices, &path)); + + /* We need to re-read parent_path because the previous push_back_new() + * call might have reallocated the entire vector list */ + parent_path = igraph_vector_int_list_get_ptr(vertices, parent_path_idx); + IGRAPH_CHECK(igraph_vector_int_update(path, parent_path)); + IGRAPH_CHECK(igraph_vector_int_push_back(path, node)); + + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_new(edges, &path)); + if (parent_node != from) { + parent_path_edge = igraph_vector_int_list_get_ptr(edges, parent_path_idx); + IGRAPH_CHECK(igraph_vector_int_update(path, parent_path_edge)); + } + IGRAPH_CHECK(igraph_vector_int_push_back(path, parent_edge)); + } + } + } + } + + /* free those paths from the result vector that we won't need */ + n = igraph_vector_int_list_size(vertices); + i = 0; + while (i < n) { + igraph_int_t tmp; + path = igraph_vector_int_list_get_ptr(vertices, i); + tmp = igraph_vector_int_tail(path); + if (is_target[tmp] == 1) { + /* we need this path, keep it */ + i++; + } else { + /* we don't need this path, free it */ + igraph_vector_int_list_discard_fast(vertices, i); + if (edges) { + igraph_vector_int_list_discard_fast(edges, i); + } + n--; + } + } + + /* sort the remaining paths by the target vertices */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&index, 0); + igraph_vector_int_list_sort_ind(vertices, &index, igraph_vector_int_colex_cmp); + IGRAPH_CHECK(igraph_vector_int_list_permute(vertices, &index)); + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_permute(edges, &index)); + } + igraph_vector_int_destroy(&index); + IGRAPH_FINALLY_CLEAN(1); + } + + /* free the allocated memory */ + if (free_vertices) { + igraph_vector_int_list_destroy(vertices); + IGRAPH_FREE(vertices); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_int_destroy(&order); + IGRAPH_FREE(is_target); + igraph_vector_destroy(&dists); + igraph_vector_ptr_destroy_all(&parents); + igraph_vector_ptr_destroy_all(&parents_edge); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/distances.c b/src/paths/distances.c new file mode 100644 index 0000000..106d7f9 --- /dev/null +++ b/src/paths/distances.c @@ -0,0 +1,864 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_datatype.h" +#include "igraph_dqueue.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_nongraph.h" +#include "igraph_random.h" +#include "igraph_vector.h" + +#include "core/interruption.h" +#include "core/indheap.h" + +/* When vid_ecc is not NULL, only one vertex ID should be passed in vids. + * vid_ecc will then return the id of the vertex farthest from the one in + * vids. If unconn == false and not all other vertices were reachable from + * the single given vertex, -1 is returned in vid_ecc. */ +static igraph_error_t igraph_i_eccentricity_unweighted( + const igraph_t *graph, igraph_vector_t *res, igraph_vs_t vids, + igraph_lazy_adjlist_t *adjlist, igraph_int_t *vid_ecc, + igraph_bool_t unconn +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_vector_int_t counted; + igraph_int_t i, mark = 1; + igraph_int_t min_degree = 0; + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&counted, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_fill(res, -1); + + for (i = 0, IGRAPH_VIT_RESET(vit); + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), mark++, i++) { + + igraph_int_t source; + igraph_int_t nodes_reached = 1; + source = IGRAPH_VIT_GET(vit); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, source)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + VECTOR(counted)[source] = mark; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act = igraph_dqueue_int_pop(&q); + igraph_int_t dist = igraph_dqueue_int_pop(&q); + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(adjlist, act); + igraph_int_t j, n; + + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (VECTOR(counted)[nei] != mark) { + VECTOR(counted)[nei] = mark; + nodes_reached++; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, dist + 1)); + } + } + if (vid_ecc) { + /* Return the vertex ID of the vertex which has the lowest + * degree of the vertices most distant from the starting + * vertex. Assumes there is only 1 vid in vids. Used for + * pseudo_diameter calculations. */ + if (dist > VECTOR(*res)[i] || (dist == VECTOR(*res)[i] && n < min_degree)) { + VECTOR(*res)[i] = dist; + *vid_ecc = act; + min_degree = n; + } + } else if (dist > VECTOR(*res)[i]) { + VECTOR(*res)[i] = dist; + } + } /* while !igraph_dqueue_int_empty(dqueue) */ + + if (nodes_reached != no_of_nodes && !unconn && vid_ecc) { + *vid_ecc = -1; + break; + } + } /* for IGRAPH_VIT_NEXT(vit) */ + + igraph_vector_int_destroy(&counted); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * This function finds the weighted eccentricity and returns it via \p ecc. + * It's used for igraph_pseudo_diameter() and igraph_eccentricity(). + * \p vid_ecc returns the vertex id of the ecc with the greatest + * distance from \p vid_start. If two vertices have the same greatest distance, + * the one with the lowest degree is chosen. + * When the graph is not (strongly) connected and \p unconn is false, then \p ecc + * wil be set to infinity, and \p vid_ecc to -1; + */ +static igraph_error_t igraph_i_eccentricity_dijkstra( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *ecc, + igraph_int_t vid_start, igraph_int_t *vid_ecc, igraph_bool_t unconn, + igraph_lazy_inclist_t *inclist +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_2wheap_t Q; + igraph_vector_t vec_dist; + igraph_int_t i; + igraph_real_t degree_ecc, dist; + igraph_int_t degree_i; + igraph_vector_int_t *neis; + + IGRAPH_VECTOR_INIT_FINALLY(&vec_dist, no_of_nodes); + igraph_vector_fill(&vec_dist, IGRAPH_INFINITY); + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, vid_start, -1.0); + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_int_t nlen; + + VECTOR(vec_dist)[minnei] = mindist - 1.0; + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (i = 0; i < nlen; i++) { + igraph_int_t edge = VECTOR(*neis)[i]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curdist = active ? -igraph_2wheap_get(&Q, tto) : 0.0; + + if (altdist == IGRAPH_INFINITY) { + /* Ignore edges with positive infinite weights */ + } else if (!has) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + } + + *ecc = 0; + *vid_ecc = vid_start; + degree_ecc = 0; + + for (i = 0; i < no_of_nodes; i++) { + if (i == vid_start) { + continue; + } + dist = VECTOR(vec_dist)[i]; + + /* inclist is used to ignore multiple edges when finding the degree */ + neis = igraph_lazy_inclist_get(inclist, i); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + degree_i = igraph_vector_int_size(neis); + + if (dist > *ecc) { + if (!isfinite(dist)) { + if (!unconn) { + *ecc = IGRAPH_INFINITY; + *vid_ecc = -1; + break; + } + } else { + *ecc = dist; + *vid_ecc = i; + degree_ecc = degree_i; + } + } else if (dist == *ecc) { + if (degree_i < degree_ecc) { + degree_ecc = degree_i; + *vid_ecc = i; + } + } + } + igraph_2wheap_destroy(&Q); + igraph_vector_destroy(&vec_dist); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_eccentricity + * \brief Eccentricity of some vertices. + * + * The eccentricity of a vertex is calculated by measuring the shortest + * distance from (or to) the vertex, to (or from) all vertices in the + * graph, and taking the maximum. + * + * + * This implementation ignores vertex pairs that are in different + * components. Isolated vertices have eccentricity zero. + * + * \param graph The input graph, it can be directed or undirected. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. Use a null pointer to calculate the unweighted + * eccentricities. Edges with positive infinite weights are ignored. + * \param res Pointer to an initialized vector, the result is stored + * here. + * \param vids The vertices for which the eccentricity is calculated. + * \param mode What kind of paths to consider for the calculation: + * \c IGRAPH_OUT, paths that follow edge directions; + * \c IGRAPH_IN, paths that follow the opposite directions; and + * \c IGRAPH_ALL, paths that ignore edge directions. This argument + * is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of + * vertices, |E| the number of edges. + * + * \example examples/simple/igraph_eccentricity.c + */ + +igraph_error_t igraph_eccentricity( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + igraph_vs_t vids, igraph_neimode_t mode +) { + igraph_lazy_inclist_t inclist; + igraph_vit_t vit; + igraph_int_t dump; + igraph_real_t ecc; + igraph_int_t no_of_edges = igraph_ecount(graph); + + if (weights == NULL) { + igraph_lazy_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, + IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_i_eccentricity_unweighted(graph, res, vids, &adjlist, + /*vid_ecc*/ NULL, /*unconn*/ true)); + igraph_lazy_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, got %g.", IGRAPH_EINVAL, min); + } else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, + IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vector_resize(res, 0)); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra( + graph, weights, &ecc, IGRAPH_VIT_GET(vit), + /*vid_ecc*/ &dump, /*unconn*/ true, &inclist + )); + IGRAPH_CHECK(igraph_vector_push_back(res, ecc)); + } + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_radius + * \brief Radius of a graph, using weighted edges. + * + * The radius of a graph is the defined as the minimum eccentricity of + * its vertices, see \ref igraph_eccentricity(). + * + * \param graph The input graph, it can be directed or undirected. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_radius() is called. Edges with positive + * infinite weights are ignored. + * \param radius Pointer to a real variable, the result is stored + * here. + * \param mode What kind of paths to consider for the calculation: + * \c IGRAPH_OUT, paths that follow edge directions; + * \c IGRAPH_IN, paths that follow the opposite directions; and + * \c IGRAPH_ALL, paths that ignore edge directions. This argument + * is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of + * vertices, |E| the number of edges. + * + * \sa \ref igraph_diameter() for the maximum eccentricity, + * \ref igraph_eccentricity() for eccentricities of all vertices. + */ + +igraph_error_t igraph_radius( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *radius, igraph_neimode_t mode +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (no_of_nodes == 0) { + *radius = IGRAPH_NAN; + } else { + igraph_vector_t ecc; + IGRAPH_VECTOR_INIT_FINALLY(&ecc, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_eccentricity(graph, weights, &ecc, igraph_vss_all(), mode)); + *radius = igraph_vector_min(&ecc); + igraph_vector_destroy(&ecc); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_pseudo_diameter_unweighted( + const igraph_t *graph, igraph_real_t *diameter, igraph_int_t vid_start, + igraph_int_t *from, igraph_int_t *to, + igraph_bool_t directed, igraph_bool_t unconn +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_real_t ecc_v; + igraph_real_t ecc_u; + igraph_int_t vid_ecc; + igraph_int_t ito, ifrom; + igraph_bool_t inf = false; + + if (vid_start >= no_of_nodes) { + IGRAPH_ERROR("Starting vertex ID for pseudo-diameter out of range.", IGRAPH_EINVAL); + } + + /* We will reach here when vid_start < 0 and the graph has no vertices. */ + if (no_of_nodes == 0) { + if (diameter) { + *diameter = IGRAPH_NAN; + } + if (from) { + *from = -1; + } + if (to) { + *to = -1; + } + return IGRAPH_SUCCESS; + } + + if (vid_start < 0) { + vid_start = RNG_INTEGER(0, no_of_nodes - 1); + } + + if (!igraph_is_directed(graph) || !directed) { + igraph_lazy_adjlist_t adjlist; + igraph_vector_t ecc_vec; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, + IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + ifrom = vid_start; + IGRAPH_VECTOR_INIT_FINALLY(&ecc_vec, no_of_nodes); + + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_vec, igraph_vss_1(vid_start), &adjlist, &vid_ecc, unconn + )); + ecc_u = VECTOR(ecc_vec)[0]; + + if (!unconn && vid_ecc == -1) { + inf = true; + } else { + while (true) { + IGRAPH_ALLOW_INTERRUPTION(); + + ito = vid_ecc; + + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_vec, igraph_vss_1(vid_ecc), &adjlist, &vid_ecc, true + )); + + ecc_v = VECTOR(ecc_vec)[0]; + + if (ecc_u < ecc_v) { + ecc_u = ecc_v; + ifrom = ito; + } else { + break; + } + } + } + igraph_vector_destroy(&ecc_vec); + igraph_lazy_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_vector_t ecc_out; + igraph_vector_t ecc_in; + igraph_int_t vid_ecc_in; + igraph_int_t vid_ecc_out; + igraph_int_t vid_end; + igraph_bool_t direction; + igraph_lazy_adjlist_t adjlist_in; + igraph_lazy_adjlist_t adjlist_out; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist_in, IGRAPH_IN, + IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist_in); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist_out, IGRAPH_OUT, + IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist_out); + + IGRAPH_VECTOR_INIT_FINALLY(&ecc_in, igraph_vcount(graph)); + IGRAPH_VECTOR_INIT_FINALLY(&ecc_out, igraph_vcount(graph)); + + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_out, igraph_vss_1(vid_start), &adjlist_out, + &vid_ecc_out, unconn + )); + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_in, igraph_vss_1(vid_start), &adjlist_in, + &vid_ecc_in, unconn + )); + + /* A directed graph is strongly connected iff all vertices are reachable + * from vid_start both when moving along or moving opposite the edge directions. */ + if (!unconn && (vid_ecc_out == -1 || vid_ecc_in == -1)) { + inf = true; + } else { + if (VECTOR(ecc_out)[0] > VECTOR(ecc_in)[0]) { + vid_ecc = vid_ecc_out; + ecc_u = VECTOR(ecc_out)[0]; + } else { + vid_ecc = vid_ecc_in; + ecc_u = VECTOR(ecc_in)[0]; + } + + while (1) { + IGRAPH_ALLOW_INTERRUPTION(); + + vid_end = vid_ecc; + + /* TODO: In the undirected case, we break ties between vertices at the + * same distance based on their degree. In te directed case, should we + * use in-, out- or total degree? */ + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_out, igraph_vss_1(vid_ecc), &adjlist_out, + &vid_ecc_out, true + )); + IGRAPH_CHECK(igraph_i_eccentricity_unweighted( + graph, &ecc_in, igraph_vss_1(vid_ecc), &adjlist_in, + &vid_ecc_in, true + )); + + if (VECTOR(ecc_out)[0] > VECTOR(ecc_in)[0]) { + vid_ecc = vid_ecc_out; + ecc_v = VECTOR(ecc_out)[0]; + direction = 1; + } else { + vid_ecc = vid_ecc_in; + ecc_v = VECTOR(ecc_in)[0]; + direction = 0; + } + + if (ecc_u < ecc_v) { + ecc_u = ecc_v; + vid_start = vid_end; + } else { + break; + } + } + + if (direction) { + ifrom = vid_end; + ito = vid_start; + } else { + ifrom = vid_start; + ito = vid_end; + } + + } + igraph_vector_destroy(&ecc_out); + igraph_vector_destroy(&ecc_in); + igraph_lazy_adjlist_destroy(&adjlist_in); + igraph_lazy_adjlist_destroy(&adjlist_out); + IGRAPH_FINALLY_CLEAN(4); + } + + if (inf) { + if (diameter) { + *diameter = IGRAPH_INFINITY; + } + if (from) { + *from = -1; + } + if (to) { + *to = -1; + } + } else { + if (diameter) { + *diameter = ecc_u; + } + if (from) { + *from = ifrom; + } + if (to) { + *to = ito; + } + } + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_pseudo_diameter_dijkstra( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *diameter, igraph_int_t vid_start, + igraph_int_t *from, igraph_int_t *to, + igraph_bool_t directed, igraph_bool_t unconn +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_real_t ecc_v; + igraph_real_t ecc_u; + igraph_int_t vid_ecc; + igraph_int_t ito, ifrom; + igraph_bool_t inf = false; + + if (vid_start >= no_of_nodes) { + IGRAPH_ERROR("Starting vertex ID for pseudo-diameter out of range.", IGRAPH_EINVAL); + } + + IGRAPH_ASSERT(weights != 0); + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, got %g.", IGRAPH_EINVAL, min); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + /* We will reach here when vid_start < 0 and the graph has no vertices. */ + if (no_of_nodes == 0) { + if (diameter) { + *diameter = IGRAPH_NAN; + } + if (from) { + *from = -1; + } + if (to) { + *to = -1; + } + return IGRAPH_SUCCESS; + } + + if (vid_start < 0) { + vid_start = RNG_INTEGER(0, no_of_nodes - 1); + } + + if (!igraph_is_directed(graph) || !directed) { + igraph_lazy_inclist_t inclist; + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + ifrom = vid_start; + + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_u, vid_start, &vid_ecc, unconn, &inclist)); + + inf = !isfinite(ecc_u); + + if (!inf) { + while (1) { + IGRAPH_ALLOW_INTERRUPTION(); + + ito = vid_ecc; + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_v, vid_ecc, &vid_ecc, unconn, &inclist)); + + if (ecc_u < ecc_v) { + ecc_u = ecc_v; + ifrom = ito; + } else { + break; + } + } + } + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_real_t ecc_out; + igraph_real_t ecc_in; + igraph_int_t vid_ecc_in; + igraph_int_t vid_ecc_out; + igraph_int_t vid_end; + igraph_bool_t direction; + igraph_lazy_inclist_t inclist_out; + igraph_lazy_inclist_t inclist_in; + + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist_out, IGRAPH_OUT, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist_out); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist_in, IGRAPH_IN, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist_in); + + + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_out, vid_start, &vid_ecc_out, unconn, &inclist_out)); + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_in, vid_start, &vid_ecc_in, unconn, &inclist_in)); + + /* A directed graph is strongly connected iff all vertices are reachable + * from vid_start both when moving along or moving opposite the edge directions. */ + if (!unconn && (vid_ecc_out == -1 || vid_ecc_in == -1)) { + inf = true; + } else { + if (ecc_out > ecc_in) { + vid_ecc = vid_ecc_out; + ecc_u = ecc_out; + } else { + vid_ecc = vid_ecc_in; + ecc_u = ecc_in; + } + + while (1) { + IGRAPH_ALLOW_INTERRUPTION(); + + vid_end = vid_ecc; + + /* TODO: In the undirected case, we break ties between vertices at the + * same distance based on their degree. In te directed case, should we + * use in-, out- or total degree? */ + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_out, vid_ecc, &vid_ecc_out, unconn, &inclist_out)); + IGRAPH_CHECK(igraph_i_eccentricity_dijkstra(graph, weights, &ecc_in, vid_ecc, &vid_ecc_in, unconn, &inclist_in)); + + if (ecc_out > ecc_in) { + vid_ecc = vid_ecc_out; + ecc_v = ecc_out; + direction = 1; + } else { + vid_ecc = vid_ecc_in; + ecc_v = ecc_in; + direction = 0; + } + + if (ecc_u < ecc_v) { + ecc_u = ecc_v; + vid_start = vid_end; + } else { + break; + } + } + + if (direction) { + ifrom = vid_end; + ito = vid_start; + } else { + ifrom = vid_start; + ito = vid_end; + } + } + igraph_lazy_inclist_destroy(&inclist_out); + igraph_lazy_inclist_destroy(&inclist_in); + IGRAPH_FINALLY_CLEAN(2); + } + + if (inf) { + if (diameter) { + *diameter = IGRAPH_INFINITY; + } + if (from) { + *from = -1; + } + if (to) { + *to = -1; + } + } else { + if (diameter) { + *diameter = ecc_u; + } + if (from) { + *from = ifrom; + } + if (to) { + *to = ito; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_pseudo_diameter + * \brief Approximation and lower bound of the diameter of a graph. + * + * This algorithm finds a pseudo-peripheral vertex and returns its + * eccentricity. This value can be used as an approximation + * and lower bound of the diameter of a graph. + * + * + * A pseudo-peripheral vertex is a vertex v, such that for every + * vertex u which is as far away from v as possible, v is also as + * far away from u as possible. The process of finding one depends + * on where the search starts, and for a disconnected graph the + * maximum diameter found will be that of the component \p vid_start + * is in. + * + * + * If the graph has no vertices, \c IGRAPH_NAN is returned. + * + * \param graph The input graph, can be directed or undirected. + * \param weights The edge weights of the graph. Can be \c NULL for an + * unweighted graph. All weights should be non-negative. Edges with + * positive infinite weights are ignored. + * \param diameter This will contain the pseudo-diameter. + * \param vid_start Id of the starting vertex. If this is negative, a + * random starting vertex is chosen. + * \param from If not \c NULL this will be set to the + * source vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param to If not \c NULL this will be set to the + * target vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param directed Boolean, whether to consider directed + * paths. Ignored for undirected graphs. + * \param unconn What to do if the graph is not connected. If + * \c true the longest geodesic within a component + * will be returned, otherwise \c IGRAPH_INFINITY is + * returned. + * \return Error code. + * + * Time complexity: O(|V| |E| log|E|), |V| is the number of vertices, + * |E| is the number of edges. + * + * \sa \ref igraph_diameter() + */ +igraph_error_t igraph_pseudo_diameter( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *diameter, igraph_int_t vid_start, + igraph_int_t *from, igraph_int_t *to, + igraph_bool_t directed, igraph_bool_t unconn +) { + if (weights) { + return igraph_i_pseudo_diameter_dijkstra( + graph, weights, diameter, vid_start, from, to, directed, unconn + ); + } else { + return igraph_i_pseudo_diameter_unweighted( + graph, diameter, vid_start, from, to, directed, unconn + ); + } +} + +/** + * \function igraph_graph_center + * \brief Central vertices of a graph. + * + * \experimental + * + * The central vertices of a graph are calculated by finding the vertices + * with the minimum eccentricity. The concept of the graph center is typically + * applied to (strongly) connected graphs. In disconnected graphs, the smallest + * eccentricity is taken across all components. + * + * \param graph The input graph, it can be directed or undirected. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. Pass a null pointer here if all edges have equal weight. + * Edges with positive infinite weights are ignored. + * \param res Pointer to an initialized vector, the result is stored + * here. + * \param mode What kind of paths to consider for the calculation: + * \c IGRAPH_OUT, paths that follow edge directions; + * \c IGRAPH_IN, paths that follow the opposite directions; and + * \c IGRAPH_ALL, paths that ignore edge directions. This argument + * is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V| |E| log|V| + |V|), where |V| is the number of + * vertices, |E| the number of edges. + * + * \sa \ref igraph_graph_center(), \ref igraph_eccentricity(), \ref igraph_radius() + * + */ +igraph_error_t igraph_graph_center( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_int_t *res, + igraph_neimode_t mode +) { + + igraph_vector_t ecc; + const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON; + + igraph_vector_int_clear(res); + if (igraph_vcount(graph) == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&ecc, 0); + IGRAPH_CHECK(igraph_eccentricity(graph, weights, &ecc, igraph_vss_all(), mode)); + + /* igraph_eccentricity() does not return infinity or NaN, and the null graph + * case was handled above, therefore calling vector_min() is safe. */ + igraph_real_t min_eccentricity = igraph_vector_min(&ecc); + igraph_int_t n = igraph_vector_size(&ecc); + for (igraph_int_t i = 0; i < n; i++) { + if (igraph_cmp_epsilon(VECTOR(ecc)[i], min_eccentricity, eps) == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(res, i)); + } + } + + igraph_vector_destroy(&ecc); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/eulerian.c b/src/paths/eulerian.c new file mode 100644 index 0000000..77acd2b --- /dev/null +++ b/src/paths/eulerian.c @@ -0,0 +1,681 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_eulerian.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" +#include "igraph_components.h" +#include "igraph_stack.h" + +/** + * \section about_eulerian + * + * These functions calculate whether an Eulerian path or cycle exists + * and if so, can find them. + */ + + +/* solution adapted from https://www.geeksforgeeks.org/eulerian-path-and-circuit/ +The function returns one of the following values +has_path is set to 1 if a path exists, 0 otherwise +has_cycle is set to 1 if a cycle exists, 0 otherwise +*/ +static igraph_error_t igraph_i_is_eulerian_undirected( + const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_int_t *start_of_path) { + igraph_int_t odd; + igraph_vector_int_t degree; + igraph_vector_int_t csize; + /* boolean vector to mark singletons: */ + igraph_vector_int_t nonsingleton; + igraph_int_t i, n, vsize; + igraph_int_t cluster_count; + /* number of self-looping singletons: */ + igraph_int_t es; + /* will be set to 1 if there are non-isolated vertices, otherwise 0: */ + igraph_int_t ens; + + n = igraph_vcount(graph); + + if (igraph_ecount(graph) == 0 || n <= 1) { + start_of_path = 0; /* in case the graph has one vertex with self-loops */ + *has_path = true; + *has_cycle = true; + return IGRAPH_SUCCESS; + } + + /* check for connectedness, but singletons are special since they affect + * the Eulerian nature only if there is a self-loop AND another edge + * somewhere else in the graph */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0); + IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK)); + cluster_count = 0; + vsize = igraph_vector_int_size(&csize); + for (i = 0; i < vsize; i++) { + if (VECTOR(csize)[i] > 1) { + cluster_count++; + if (cluster_count > 1) { + /* disconnected edges, they'll never reach each other */ + *has_path = false; + *has_cycle = false; + igraph_vector_int_destroy(&csize); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + } + } + } + + igraph_vector_int_destroy(&csize); + IGRAPH_FINALLY_CLEAN(1); + + /* the graph is connected except for singletons */ + /* find singletons (including those with self-loops) */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0); + IGRAPH_CHECK(igraph_degree(graph, &nonsingleton, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS)); + + /* check the degrees for odd/even: + * - >= 2 odd means no cycle (1 odd is impossible) + * - > 2 odd means no path + * plus there are a few corner cases with singletons + */ + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, 0); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + odd = 0; + es = 0; + ens = 0; + for (i = 0; i < n; i++) { + igraph_int_t deg = VECTOR(degree)[i]; + /* Eulerian is about edges, so skip free vertices */ + if (deg == 0) continue; + + if (!VECTOR(nonsingleton)[i]) { + /* singleton with self loops */ + es++; + } else { + /* at least one non-singleton */ + ens = 1; + /* note: self-loops count for two (in and out) */ + if (deg % 2) odd++; + } + + if (es + ens > 1) { + /* 2+ singletons with self loops or singleton with self-loops and + * 1+ edges in the non-singleton part of the graph. */ + *has_path = false; + *has_cycle = false; + igraph_vector_int_destroy(&nonsingleton); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; + } + } + + igraph_vector_int_destroy(&nonsingleton); + IGRAPH_FINALLY_CLEAN(1); + + /* this is the usual algorithm on the connected part of the graph */ + if (odd > 2) { + *has_path = false; + *has_cycle = false; + } else if (odd == 2) { + *has_path = true; + *has_cycle = false; + } else { + *has_path = true; + *has_cycle = true; + } + + /* set start of path if there is one but there is no cycle */ + /* note: we cannot do this in the previous loop because at that time we are + * not sure yet if a path exists */ + for (i = 0; i < n; i++) { + if ((*has_cycle && VECTOR(degree)[i] > 0) || (!*has_cycle && VECTOR(degree)[i] %2 == 1)) { + *start_of_path = i; + break; + } + } + + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_is_eulerian_directed( + const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle, igraph_int_t *start_of_path) { + igraph_int_t incoming_excess, outgoing_excess, n; + igraph_int_t i, vsize; + igraph_int_t cluster_count; + igraph_vector_int_t out_degree, in_degree; + igraph_vector_int_t csize; + /* boolean vector to mark singletons: */ + igraph_vector_int_t nonsingleton; + /* number of self-looping singletons: */ + igraph_int_t es; + /* will be set to 1 if there are non-isolated vertices, otherwise 0: */ + igraph_int_t ens; + + n = igraph_vcount(graph); + + if (igraph_ecount(graph) == 0 || n <= 1) { + start_of_path = 0; /* in case the graph has one vertex with self-loops */ + *has_path = true; + *has_cycle = true; + return IGRAPH_SUCCESS; + } + + incoming_excess = 0; + outgoing_excess = 0; + + /* check for weak connectedness, but singletons are special since they affect + * the Eulerian nature only if there is a self-loop AND another edge + * somewhere else in the graph */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&csize, 0); + + IGRAPH_CHECK(igraph_connected_components(graph, NULL, &csize, NULL, IGRAPH_WEAK)); + cluster_count = 0; + vsize = igraph_vector_int_size(&csize); + for (i = 0; i < vsize; i++) { + if (VECTOR(csize)[i] > 1) { + cluster_count++; + if (cluster_count > 1) { + /* weakly disconnected edges, they'll never reach each other */ + *has_path = false; + *has_cycle = false; + igraph_vector_int_destroy(&csize); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + } + } + } + + igraph_vector_int_destroy(&csize); + IGRAPH_FINALLY_CLEAN(1); + + /* the graph is weakly connected except for singletons */ + /* find the singletons (including those with self-loops) */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&nonsingleton, 0); + IGRAPH_CHECK(igraph_degree(graph, &nonsingleton, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS)); + + + /* checking if no. of incoming edges == outgoing edges + * plus there are a few corner cases with singletons */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_degree, 0); + IGRAPH_CHECK(igraph_degree(graph, &out_degree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree, 0); + IGRAPH_CHECK(igraph_degree(graph, &in_degree, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); + es = 0; + ens = 0; + *start_of_path = -1; + for (i = 0; i < n; i++) { + igraph_int_t degin = VECTOR(in_degree)[i]; + igraph_int_t degout = VECTOR(out_degree)[i]; + + /* Eulerian is about edges, so skip free vertices */ + if (degin + degout == 0) continue; + + if (!VECTOR(nonsingleton)[i]) { + /* singleton with self loops */ + es++; + /* if we ever want a path, it has to be this self-loop */ + *start_of_path = i; + } else { + /* at least one non-singleton */ + ens = 1; + } + + if (es + ens > 1) { + /* 2+ singletons with self loops or singleton with self-loops and + * 1+ edges in the non-singleton part of the graph. */ + *has_path = false; + *has_cycle = false; + igraph_vector_int_destroy(&nonsingleton); + igraph_vector_int_destroy(&in_degree); + igraph_vector_int_destroy(&out_degree); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; + } + + /* as long as we have perfect balance, you can start + * anywhere with an edge */ + if (*start_of_path == -1 && incoming_excess == 0 && outgoing_excess == 0) { + *start_of_path = i; + } + + /* same in and out (including self-loops, even in singletons) */ + if (degin == degout) { + continue; + } + + /* non-singleton, in != out */ + if (degin > degout) { + incoming_excess += degin - degout; + } else { + outgoing_excess += degout - degin; + if (outgoing_excess == 1) { + *start_of_path = i; + } + } + + /* too much imbalance, either of the following: + * 1. 1+ vertices have 2+ in/out + * 2. 2+ nodes have 1+ in/out */ + if (incoming_excess > 1 || outgoing_excess > 1) { + *has_path = false; + *has_cycle = false; + igraph_vector_int_destroy(&nonsingleton); + igraph_vector_int_destroy(&in_degree); + igraph_vector_int_destroy(&out_degree); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; + } + } + + *has_path = true; + /* perfect edge balance -> strong connectivity */ + *has_cycle = (incoming_excess == 0) && (outgoing_excess == 0); + /* either way, the start was set already */ + + igraph_vector_int_destroy(&nonsingleton); + igraph_vector_int_destroy(&in_degree); + igraph_vector_int_destroy(&out_degree); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup Eulerian + * \function igraph_is_eulerian + * \brief Checks whether an Eulerian path or cycle exists. + * + * An Eulerian path traverses each edge of the graph precisely once. A closed + * Eulerian path is referred to as an Eulerian cycle. + * + * \param graph The graph object. + * \param has_path Pointer to a Boolean, will be set to true if an Eulerian path exists. + * Must not be \c NULL. + * \param has_cycle Pointer to a Boolean, will be set to true if an Eulerian cycle exists. + * Must not be \c NULL. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for temporary data. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges. + * + */ + +igraph_error_t igraph_is_eulerian(const igraph_t *graph, igraph_bool_t *has_path, igraph_bool_t *has_cycle) { + igraph_int_t start_of_path = 0; + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, has_path, has_cycle, &start_of_path)); + } else { + IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, has_path, has_cycle, &start_of_path)); + } + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_eulerian_path_undirected( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res, + igraph_int_t start_of_path) { + + igraph_int_t curr; + igraph_int_t n, m; + igraph_inclist_t il; + igraph_stack_int_t path, tracker, edge_tracker, edge_path; + igraph_bitset_t visited_list; + igraph_vector_int_t degree; + + n = igraph_vcount(graph); + m = igraph_ecount(graph); + + if (edge_res) { + igraph_vector_int_clear(edge_res); + } + + if (vertex_res) { + igraph_vector_int_clear(vertex_res); + } + + if (m == 0 || n == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, 0); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + IGRAPH_STACK_INT_INIT_FINALLY(&path, n); + IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n); + IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n); + IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n); + IGRAPH_BITSET_INIT_FINALLY(&visited_list, m); + + IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path)); + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + curr = start_of_path; + + while (!igraph_stack_int_empty(&tracker)) { + + if (VECTOR(degree)[curr] != 0) { + igraph_vector_int_t *incedges; + igraph_int_t nc, edge = -1; + igraph_int_t j, next; + IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr)); + + incedges = igraph_inclist_get(&il, curr); + nc = igraph_vector_int_size(incedges); + IGRAPH_ASSERT(nc > 0); + + for (j = 0; j < nc; j++) { + edge = VECTOR(*incedges)[j]; + if (!IGRAPH_BIT_TEST(visited_list, edge)) { + break; + } + } + + next = IGRAPH_OTHER(graph, edge, curr); + + IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge)); + + /* remove edge here */ + VECTOR(degree)[curr]--; + VECTOR(degree)[next]--; + IGRAPH_BIT_SET(visited_list, edge); + + curr = next; + } else { /* back track to find remaining circuit */ + igraph_int_t curr_e; + IGRAPH_CHECK(igraph_stack_int_push(&path, curr)); + curr = igraph_stack_int_pop(&tracker); + if (!igraph_stack_int_empty(&edge_tracker)) { + curr_e = igraph_stack_int_pop(&edge_tracker); + IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e)); + } + } + } + + if (edge_res) { + IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m)); + while (!igraph_stack_int_empty(&edge_path)) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path))); + } + } + if (vertex_res) { + IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1)); + while (!igraph_stack_int_empty(&path)) { + IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path))); + } + } + + igraph_stack_int_destroy(&path); + igraph_stack_int_destroy(&tracker); + igraph_stack_int_destroy(&edge_path); + igraph_stack_int_destroy(&edge_tracker); + igraph_bitset_destroy(&visited_list); + igraph_inclist_destroy(&il); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/* solution adapted from https://www.geeksforgeeks.org/hierholzers-algorithm-directed-graph/ */ +static igraph_error_t igraph_i_eulerian_path_directed( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res, + igraph_int_t start_of_path) { + + igraph_int_t curr; + igraph_int_t n, m; + igraph_inclist_t il; + igraph_stack_int_t path, tracker, edge_tracker, edge_path; + igraph_bitset_t visited_list; + igraph_vector_int_t remaining_out_edges; + + n = igraph_vcount(graph); + m = igraph_ecount(graph); + + if (edge_res) { + igraph_vector_int_clear(edge_res); + } + + if (vertex_res) { + igraph_vector_int_clear(vertex_res); + } + + if (m == 0 || n == 0) { + return IGRAPH_SUCCESS; + } + + IGRAPH_STACK_INT_INIT_FINALLY(&path, n); + IGRAPH_STACK_INT_INIT_FINALLY(&tracker, n); + IGRAPH_STACK_INT_INIT_FINALLY(&edge_path, n); + IGRAPH_STACK_INT_INIT_FINALLY(&edge_tracker, n); + IGRAPH_BITSET_INIT_FINALLY(&visited_list, m); + + IGRAPH_CHECK(igraph_stack_int_push(&tracker, start_of_path)); + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&remaining_out_edges, 0); + IGRAPH_CHECK(igraph_degree(graph, &remaining_out_edges, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); + + curr = start_of_path; + + while (!igraph_stack_int_empty(&tracker)) { + + if (VECTOR(remaining_out_edges)[curr] != 0) { + igraph_vector_int_t *incedges; + igraph_int_t nc, edge = -1; + igraph_int_t j, next; + IGRAPH_CHECK(igraph_stack_int_push(&tracker, curr)); + + incedges = igraph_inclist_get(&il, curr); + nc = igraph_vector_int_size(incedges); + IGRAPH_ASSERT(nc > 0); + + for (j = 0; j < nc; j++) { + edge = VECTOR(*incedges)[j]; + if (!IGRAPH_BIT_TEST(visited_list, edge)) { + break; + } + } + + next = IGRAPH_TO(graph, edge); + + IGRAPH_CHECK(igraph_stack_int_push(&edge_tracker, edge)); + + /* remove edge here */ + VECTOR(remaining_out_edges)[curr]--; + IGRAPH_BIT_SET(visited_list, edge); + + curr = next; + } else { /* back track to find remaining circuit */ + igraph_int_t curr_e; + IGRAPH_CHECK(igraph_stack_int_push(&path, curr)); + curr = igraph_stack_int_pop(&tracker); + if (!igraph_stack_int_empty(&edge_tracker)) { + curr_e = igraph_stack_int_pop(&edge_tracker); + IGRAPH_CHECK(igraph_stack_int_push(&edge_path, curr_e)); + } + } + } + + if (edge_res) { + IGRAPH_CHECK(igraph_vector_int_reserve(edge_res, m)); + while (!igraph_stack_int_empty(&edge_path)) { + IGRAPH_CHECK(igraph_vector_int_push_back(edge_res, igraph_stack_int_pop(&edge_path))); + } + } + if (vertex_res) { + IGRAPH_CHECK(igraph_vector_int_reserve(vertex_res, m+1)); + while (!igraph_stack_int_empty(&path)) { + IGRAPH_CHECK(igraph_vector_int_push_back(vertex_res, igraph_stack_int_pop(&path))); + } + } + + igraph_stack_int_destroy(&path); + igraph_stack_int_destroy(&tracker); + igraph_stack_int_destroy(&edge_path); + igraph_stack_int_destroy(&edge_tracker); + igraph_bitset_destroy(&visited_list); + igraph_inclist_destroy(&il); + igraph_vector_int_destroy(&remaining_out_edges); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup Eulerian + * \function igraph_eulerian_cycle + * \brief Finds an Eulerian cycle. + * + * Finds an Eulerian cycle, if it exists. An Eulerian cycle is a closed path + * that traverses each edge precisely once. + * + * + * If the graph has no edges, a zero-length cycle is returned. + * + * + * This function uses Hierholzer's algorithm. + * + * \param graph The graph object. + * \param edge_res Pointer to an initialised vector. The indices of edges + * belonging to the cycle will be stored here. May be \c NULL + * if it is not needed by the caller. + * \param vertex_res Pointer to an initialised vector. The indices of vertices + * belonging to the cycle will be stored here. The first and + * last vertex in the vector will be the same. May be \c NULL + * if it is not needed by the caller. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_ENOSOL + * graph does not have an Eulerian cycle. + * \endclist + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges. + * + */ + +igraph_error_t igraph_eulerian_cycle( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) { + + igraph_bool_t has_cycle; + igraph_bool_t has_path; + igraph_int_t start_of_path = 0; + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path)); + + if (!has_cycle) { + IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL); + } + + IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path)); + } else { + IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path)); + + if (!has_cycle) { + IGRAPH_ERROR("The graph does not have an Eulerian cycle.", IGRAPH_ENOSOL); + } + + IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path)); + } + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup Eulerian + * \function igraph_eulerian_path + * \brief Finds an Eulerian path. + * + * Finds an Eulerian path, if it exists. An Eulerian path traverses + * each edge precisely once. + * + * + * If the graph has no edges, a zero-length path is returned. + * + * + * This function uses Hierholzer's algorithm. + * + * \param graph The graph object. + * \param edge_res Pointer to an initialised vector. The indices of edges + * belonging to the path will be stored here. May be \c NULL + * if it is not needed by the caller. + * \param vertex_res Pointer to an initialised vector. The indices of vertices + * belonging to the path will be stored here. May be \c NULL + * if it is not needed by the caller. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_ENOSOL + * graph does not have an Eulerian path. + * \endclist + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges. + * + */ + +igraph_error_t igraph_eulerian_path( + const igraph_t *graph, igraph_vector_int_t *edge_res, igraph_vector_int_t *vertex_res) { + + igraph_bool_t has_cycle; + igraph_bool_t has_path; + igraph_int_t start_of_path = 0; + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_is_eulerian_directed(graph, &has_path, &has_cycle, &start_of_path)); + + if (!has_path) { + IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL); + } + IGRAPH_CHECK(igraph_i_eulerian_path_directed(graph, edge_res, vertex_res, start_of_path)); + } else { + IGRAPH_CHECK(igraph_i_is_eulerian_undirected(graph, &has_path, &has_cycle, &start_of_path)); + + if (!has_path) { + IGRAPH_ERROR("The graph does not have an Eulerian path.", IGRAPH_ENOSOL); + } + + IGRAPH_CHECK(igraph_i_eulerian_path_undirected(graph, edge_res, vertex_res, start_of_path)); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/floyd_warshall.c b/src/paths/floyd_warshall.c new file mode 100644 index 0000000..5cb7b73 --- /dev/null +++ b/src/paths/floyd_warshall.c @@ -0,0 +1,365 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" +#include "igraph_interface.h" +#include "igraph_stack.h" + +#include "core/interruption.h" +#include "internal/utils.h" +#include "paths/paths_internal.h" + +static igraph_error_t distances_floyd_warshall_original(igraph_matrix_t *res) { + + igraph_int_t no_of_nodes = igraph_matrix_nrow(res); + + for (igraph_int_t k = 0; k < no_of_nodes; k++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Iteration order matters for performance! + * First j, then i, because matrices are stored as column-major. */ + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + igraph_real_t dkj = MATRIX(*res, k, j); + if (dkj == IGRAPH_INFINITY) { + continue; + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_real_t di = MATRIX(*res, i, k) + dkj; + igraph_real_t dd = MATRIX(*res, i, j); + if (di < dd) { + MATRIX(*res, i, j) = di; + } + if (i == j && MATRIX(*res, i, i) < 0) { + IGRAPH_ERROR("Negative cycle found while calculating distances with Floyd-Warshall.", + IGRAPH_ENEGCYCLE); + } + } + } + } + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t distances_floyd_warshall_tree(igraph_matrix_t *res) { + + /* This is the "Tree" algorithm of Brodnik et al. + * A difference from the paper is that instead of using the OUT_k tree of shortest + * paths _starting_ in k, we use the IN_k tree of shortest paths _ending_ in k. + * This makes it easier to iterate through matrices in column-major order, + * i.e. storage order, thus increasing performance. */ + + igraph_int_t no_of_nodes = igraph_matrix_nrow(res); + + /* successors[v][u] is the second vertex on the shortest path from v to u, + i.e. the parent of v in the IN_u tree. */ + igraph_matrix_int_t successors; + IGRAPH_MATRIX_INT_INIT_FINALLY(&successors, no_of_nodes, no_of_nodes); + + /* children[children_start[u] + i] is the i-th child of u in a tree of shortest paths + rooted at k, and ending in k, in the main loop below (IN_k). There are no_of_nodes-1 + child vertices in total, as the root vertex is excluded. This is essentially a contiguously + stored adjacency list representation of IN_k. */ + igraph_vector_int_t children; + IGRAPH_VECTOR_INT_INIT_FINALLY(&children, no_of_nodes-1); + + /* children_start[u] indicates where the children of u are stored in children[]. + These are effectively the cumulative sums of no_of_children[], with the first + element being 0. The last element, children_start[no_of_nodes], is equal to the + total number of children in the tree, i.e. no_of_nodes-1. */ + igraph_vector_int_t children_start; + IGRAPH_VECTOR_INT_INIT_FINALLY(&children_start, no_of_nodes+1); + + /* no_of_children[u] is the number of children that u has in IN_k in the main loop below. */ + igraph_vector_int_t no_of_children; + IGRAPH_VECTOR_INT_INIT_FINALLY(&no_of_children, no_of_nodes); + + /* dfs_traversal and dfs_skip arrays for running time optimization, + see "Practical improvement" in Section 3.1 of the paper */ + igraph_vector_int_t dfs_traversal; + IGRAPH_VECTOR_INT_INIT_FINALLY(&dfs_traversal, no_of_nodes); + igraph_vector_int_t dfs_skip; + IGRAPH_VECTOR_INT_INIT_FINALLY(&dfs_skip, no_of_nodes); + + igraph_stack_int_t stack; + IGRAPH_STACK_INT_INIT_FINALLY(&stack, no_of_nodes); + + for (igraph_int_t u = 0; u < no_of_nodes; u++) { + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + MATRIX(successors, v, u) = u; + } + } + + for (igraph_int_t k = 0; k < no_of_nodes; k++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Count the children of each node in the shortest path tree, assuming that at + this point all elements of no_of_children[] are zeros. */ + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + if (v == k) continue; + igraph_int_t parent = MATRIX(successors, v, k); + VECTOR(no_of_children)[parent]++; + } + + /* Note: we do not use igraph_vector_int_cumsum() here as that function produces + an output vector of the same length as the input vector. Here we need an output + one longer, with a 0 being prepended to what vector_cumsum() would produce. */ + igraph_int_t cumsum = 0; + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + VECTOR(children_start)[v] = cumsum; + cumsum += VECTOR(no_of_children)[v]; + } + VECTOR(children_start)[no_of_nodes] = cumsum; + + /* Constructing the tree IN_k (as in the paper) and representing it + as a contiguously stored adjacency list. The entries of the no_of_children + vector as re-used as an index of where to insert child node indices. + At the end of the calculation, all elements of no_of_children[] will be zeros, + making this vector ready for the next iteration of the outer loop. */ + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + if (v == k) continue; + igraph_int_t parent = MATRIX(successors, v, k); + VECTOR(no_of_children)[parent]--; + VECTOR(children)[ VECTOR(children_start)[parent] + VECTOR(no_of_children)[parent] ] = v; + } + + /* constructing dfs-traversal and dfs-skip arrays for the IN_k tree */ + IGRAPH_CHECK(igraph_stack_int_push(&stack, k)); + igraph_int_t counter = 0; + while (!igraph_stack_int_empty(&stack)) { + igraph_int_t parent = igraph_stack_int_pop(&stack); + if (parent >= 0) { + VECTOR(dfs_traversal)[counter] = parent; + counter++; + /* a negative marker -parent - 1 that is popped right after + all the descendants of the parent were processed */ + IGRAPH_CHECK(igraph_stack_int_push(&stack, -parent - 1)); + for (igraph_int_t l = VECTOR(children_start)[parent]; l < VECTOR(children_start)[parent + 1]; l++) { + IGRAPH_CHECK(igraph_stack_int_push(&stack, VECTOR(children)[l])); + } + } else { + VECTOR(dfs_skip)[-(parent + 1)] = counter; + } + } + + /* main inner loop */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_real_t dki = MATRIX(*res, k, i); + if (dki == IGRAPH_INFINITY || i == k) { + continue; + } + igraph_int_t counter = 1; + while (counter < no_of_nodes) { + igraph_int_t j = VECTOR(dfs_traversal)[counter]; + igraph_real_t di = MATRIX(*res, j, k) + dki; + igraph_real_t dd = MATRIX(*res, j, i); + if (di < dd) { + MATRIX(*res, j, i) = di; + MATRIX(successors, j, i) = MATRIX(successors, j, k); + counter++; + } else { + counter = VECTOR(dfs_skip)[j]; + } + if (i == j && MATRIX(*res, i, i) < 0) { + IGRAPH_ERROR("Negative cycle found while calculating distances with Floyd-Warshall.", + IGRAPH_ENEGCYCLE); + } + } + } + } + + igraph_stack_int_destroy(&stack); + igraph_vector_int_destroy(&dfs_traversal); + igraph_vector_int_destroy(&dfs_skip); + igraph_vector_int_destroy(&no_of_children); + igraph_vector_int_destroy(&children_start); + igraph_vector_int_destroy(&children); + igraph_matrix_int_destroy(&successors); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_distances_floyd_warshall + * \brief Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm. + * + * The Floyd-Warshall algorithm computes weighted shortest path lengths between + * all pairs of vertices at the same time. It is useful with very dense weighted graphs, + * as its running time is primarily determined by the vertex count, and is not sensitive + * to the graph density. In sparse graphs, other methods such as the Dijkstra or + * Bellman-Ford algorithms will perform significantly better. + * + * + * In addition to the original Floyd-Warshall algorithm, igraph contains implementations + * of variants that offer better asymptotic complexity as well as better practical + * running times for most instances. See the reference below for more information. + * + * + * Note that internally this function always computes the distance matrix + * for all pairs of vertices. The \p from and \p to parameters only serve + * to subset this matrix, but do not affect the time or memory taken by the + * calculation. + * + * + * Reference: + * + * + * Brodnik, A., Grgurovič, M., Požar, R.: + * Modifications of the Floyd-Warshall algorithm with nearly quadratic expected-time, + * Ars Mathematica Contemporanea, vol. 22, issue 1, p. #P1.01 (2021). + * https://doi.org/10.26493/1855-3974.2467.497 + * + * \param graph The graph object. + * \param res An intialized matrix, the distances will be stored here. + * \param from The source vertices. + * \param to The target vertices. + * \param weights The edge weights. If \c NULL, all weights are assumed to be 1. + * Negative weights are allowed, but the graph must not contain negative cycles. + * Edges with positive infinite weights are ignored. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param method The type of the algorithm used. + * \clist + * \cli IGRAPH_FLOYD_WARSHALL_AUTOMATIC + * tries to select the best performing variant for the current graph; + * presently this option always uses the "Tree" method. + * \cli IGRAPH_FLOYD_WARSHALL_ORIGINAL + * the basic Floyd-Warshall algorithm. + * \cli IGRAPH_FLOYD_WARSHALL_TREE + * the "Tree" speedup of Brodnik et al., faster than the original algorithm + * in most cases. + * \endclist + * \return Error code. \c IGRAPH_ENEGCYCLE is returned if a negative-weight + * cycle is found. + * + * \sa \ref igraph_distances(), \ref igraph_distances_dijkstra(), + * \ref igraph_distances_bellman_ford(), \ref igraph_distances_johnson() + * + * Time complexity: + * The original variant has complexity O(|V|^3 + |E|). + * The "Tree" variant has expected-case complexity of O(|V|^2 log^2 |V|) + * according to Brodnik et al., while its worst-time complexity remains O(|V|^3). + * Here |V| denotes the number of vertices and |E| is the number of edges. + */ +igraph_error_t igraph_distances_floyd_warshall( + const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, igraph_neimode_t mode, + const igraph_floyd_warshall_algorithm_t method) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + return igraph_i_distances_floyd_warshall(graph, res, from, to, weights, mode, method); +} + +igraph_error_t igraph_i_distances_floyd_warshall( + const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, igraph_neimode_t mode, + const igraph_floyd_warshall_algorithm_t method) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t in = false, out = false; + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + switch (mode) { + case IGRAPH_ALL: + in = out = true; + break; + case IGRAPH_OUT: + out = true; + break; + case IGRAPH_IN: + in = true; + break; + default: + IGRAPH_ERROR("Invalid mode for Floyd-Warshall shortest path calculation.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_fill(res, IGRAPH_INFINITY); + + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + MATRIX(*res, v, v) = 0; + } + + for (igraph_int_t e = 0; e < no_of_edges; e++) { + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1; + + if (w < 0) { + if (mode == IGRAPH_ALL) { + IGRAPH_ERRORF("Negative edge weight (%g) found in undirected graph " + "while calculating distances with Floyd-Warshall.", + IGRAPH_ENEGCYCLE, w); + } else if (to == from) { + IGRAPH_ERRORF("Self-loop with negative weight (%g) found " + "while calculating distances with Floyd-Warshall.", + IGRAPH_ENEGCYCLE, w); + } + } else if (w == IGRAPH_INFINITY) { + /* Ignore edges with infinite weight */ + continue; + } + + if (out && MATRIX(*res, from, to) > w) { + MATRIX(*res, from, to) = w; + } + if (in && MATRIX(*res, to, from) > w) { + MATRIX(*res, to, from) = w; + } + } + + /* If there are zero or one vertices, nothing needs to be done. + * This is special-cased so that at later stages we can rely on no_of_nodes - 1 >= 0. */ + if (no_of_nodes <= 1) { + return IGRAPH_SUCCESS; + } + + switch (method) { + case IGRAPH_FLOYD_WARSHALL_ORIGINAL: + IGRAPH_CHECK(distances_floyd_warshall_original(res)); + break; + case IGRAPH_FLOYD_WARSHALL_AUTOMATIC: + case IGRAPH_FLOYD_WARSHALL_TREE: + IGRAPH_CHECK(distances_floyd_warshall_tree(res)); + break; + default: + IGRAPH_ERROR("Invalid method.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_i_matrix_subset_vertices(res, graph, from, to)); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/histogram.c b/src/paths/histogram.c new file mode 100644 index 0000000..02bd706 --- /dev/null +++ b/src/paths/histogram.c @@ -0,0 +1,141 @@ +/* + igraph library. + Copyright (C) 2005-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_progress.h" + +#include "core/interruption.h" + +/** + * \function igraph_path_length_hist + * \brief Create a histogram of all shortest path lengths. + * + * This function calculates a histogram by calculating the shortest path + * length between all pairs of vertices. In directed graphs, both directions + * are considered, meaning that each vertex pair appears twice in the histogram. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored here. The + * first (i.e. index 0) element contains the number of shortest paths of + * length 1, the second of length 2, etc. The supplied vector is resized + * as needed. + * \param unconnected Pointer to a real number, the number of vertex + * pairs for which the second vertex is not reachable from the + * first is stored here. + * \param directed Whether to consider directed paths in a directed + * graph. This argument is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V||E|), the number of vertices times the number + * of edges. + * + * \sa \ref igraph_average_path_length() and \ref igraph_distances() + */ + +igraph_error_t igraph_path_length_hist(const igraph_t *graph, igraph_vector_t *res, + igraph_real_t *unconnected, igraph_bool_t directed) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t already_added; + igraph_int_t nodes_reached; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_adjlist_t allneis; + igraph_real_t unconn = 0; + igraph_int_t ressize; + + if (! igraph_is_directed(graph)) { + directed = false; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&already_added, no_of_nodes); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, + directed ? IGRAPH_OUT : IGRAPH_ALL, + IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + igraph_vector_clear(res); + ressize = 0; + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + nodes_reached = 1; /* itself */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + VECTOR(already_added)[i] = i + 1; + + IGRAPH_PROGRESS("Path length histogram: ", 100.0 * i / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + neis = igraph_adjlist_get(&allneis, actnode); + const igraph_int_t n = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (VECTOR(already_added)[neighbor] == i + 1) { + continue; + } + VECTOR(already_added)[neighbor] = i + 1; + nodes_reached++; + if (actdist + 1 > ressize) { + IGRAPH_CHECK(igraph_vector_resize(res, actdist + 1)); + for (; ressize < actdist + 1; ressize++) { + VECTOR(*res)[ressize] = 0; + } + } + VECTOR(*res)[actdist] += 1; + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } /* while !igraph_dqueue_int_empty */ + + unconn += (no_of_nodes - nodes_reached); + + } /* for i + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_conversion.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" +#include "paths/paths_internal.h" + + +/** + * \function igraph_distances_johnson + * \brief Weighted shortest path lengths between vertices, using Johnson's algorithm. + * + * This algorithm supports directed graphs with negative edge weights, and performs + * better than the Bellman-Ford method when distances are calculated from many different + * sources, the typical use case being all-pairs distance calculations. It works by using + * a single-source Bellman-Ford run to transform all edge weights to non-negative ones, + * then invoking Dijkstra's algorithm with the new weights. See the Wikipedia page + * for more details: http://en.wikipedia.org/wiki/Johnson's_algorithm. + * + * + * If no edge weights are supplied, then the unweighted version, \ref igraph_distances() + * is called. If none of the supplied edge weights are negative, then Dijkstra's algorithm + * is used by calling \ref igraph_distances_dijkstra(). + * + * + * Note that Johnson's algorithm applies only to directed graphs. This function rejects + * undirected graphs with \em any negative edge weights, even when the \p from and \p to + * vertices are all in connected components that are free of negative weights. + * + * + * References: + * + * + * Donald B. Johnson: Efficient Algorithms for Shortest Paths in Sparse Networks. + * J. ACM 24, 1 (1977), 1–13. + * https://doi.org/10.1145/321992.321993 + * + * \param graph The input graph. If negative weights are present, it + * should be directed. + * \param res Pointer to an initialized matrix, the result will be + * stored here, one line for each source vertex, one column for each + * target vertex. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param weights Optional edge weights. If it is a null-pointer, then + * the unweighted breadth-first search based \ref igraph_distances() will + * be called. Edges with positive infinite weights are ignored. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. \c IGRAPH_ALL should not be used with + * negative weights. + * \return Error code. + * + * Time complexity: O(s|V|log|V|+|V||E|), |V| and |E| are the number + * of vertices and edges, s is the number of source vertices. + * + * \sa \ref igraph_distances() for a faster unweighted version, + * \ref igraph_distances_dijkstra() if you do not have negative + * edge weights, \ref igraph_distances_bellman_ford() if you only + * need to calculate shortest paths from a couple of sources. + */ +igraph_error_t igraph_distances_johnson( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + if (!negative_weights) { + /* If no negative weights, then we can run Dijkstra's algorithm directly, without + * needing to go through Johnson's procedure to eliminate negative weights. */ + return igraph_i_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, -1); + } else { + return igraph_i_distances_johnson(graph, res, from, to, weights, mode); + } +} + +igraph_error_t igraph_i_distances_johnson( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_t newgraph; + igraph_vector_int_t edges; + igraph_vector_t newweights; + igraph_matrix_t bfres; + igraph_int_t i, ptr; + igraph_int_t nr, nc; + igraph_vit_t fromvit; + igraph_int_t no_edges_reserved; + + /* If no weights or no edges, then we can just run the unweighted version */ + if (!weights || no_of_edges == 0) { + return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, -1); + } + + if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) { + IGRAPH_ERROR("Undirected graph with negative weight.", + IGRAPH_ENEGCYCLE); + } + + /* ------------------------------------------------------------ */ + /* -------------------- Otherwise proceed --------------------- */ + + IGRAPH_MATRIX_INIT_FINALLY(&bfres, 0, 0); + IGRAPH_VECTOR_INIT_FINALLY(&newweights, 0); + + IGRAPH_CHECK(igraph_empty(&newgraph, no_of_nodes + 1, igraph_is_directed(graph))); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + IGRAPH_SAFE_MULT(no_of_nodes, 2, &no_edges_reserved); + IGRAPH_SAFE_ADD(no_edges_reserved, no_of_edges * 2, &no_edges_reserved); + + /* Add a new node to the graph, plus edges from it to all the others. */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_edges_reserved); + igraph_get_edgelist(graph, &edges, /*bycol=*/ 0); /* reserved */ + igraph_vector_int_resize(&edges, no_edges_reserved); /* reserved */ + if (mode == IGRAPH_OUT) { + for (i = 0, ptr = no_of_edges * 2; i < no_of_nodes; i++) { + VECTOR(edges)[ptr++] = no_of_nodes; + VECTOR(edges)[ptr++] = i; + } + } else { + for (i = 0, ptr = no_of_edges * 2; i < no_of_nodes; i++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = no_of_nodes; + } + } + IGRAPH_CHECK(igraph_add_edges(&newgraph, &edges, 0)); + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_reserve(&newweights, no_of_edges + no_of_nodes)); + igraph_vector_update(&newweights, weights); /* reserved */ + igraph_vector_resize(&newweights, no_of_edges + no_of_nodes); /* reserved */ + for (i = no_of_edges; i < no_of_edges + no_of_nodes; i++) { + VECTOR(newweights)[i] = 0; + } + + /* Run Bellman-Ford algorithm on the new graph, starting from the + new vertex. */ + + IGRAPH_CHECK(igraph_i_distances_bellman_ford( + &newgraph, &bfres, + igraph_vss_1(no_of_nodes), igraph_vss_all(), + &newweights, mode)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + /* Now the edges of the original graph are reweighted, using the + values from the BF algorithm. Instead of w(u,v) we will have + w(u,v) + h(u) - h(v) */ + + igraph_vector_resize(&newweights, no_of_edges); /* reserved */ + for (i = 0; i < no_of_edges; i++) { + igraph_int_t ffrom = IGRAPH_FROM(graph, i); + igraph_int_t tto = IGRAPH_TO(graph, i); + if (mode == IGRAPH_OUT) { + VECTOR(newweights)[i] += MATRIX(bfres, 0, ffrom) - MATRIX(bfres, 0, tto); + } else { + VECTOR(newweights)[i] += MATRIX(bfres, 0, tto) - MATRIX(bfres, 0, ffrom); + } + + /* If a weight becomes slightly negative due to roundoff errors, + snap it to exact zero. */ + if (VECTOR(newweights)[i] < 0) VECTOR(newweights)[i] = 0; + } + + /* Run Dijkstra's algorithm on the new weights */ + IGRAPH_CHECK(igraph_i_distances_dijkstra_cutoff( + graph, res, + from, to, + &newweights, + mode, -1)); + + igraph_vector_destroy(&newweights); + IGRAPH_FINALLY_CLEAN(1); + + /* Reweight the shortest paths */ + nr = igraph_matrix_nrow(res); + nc = igraph_matrix_ncol(res); + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + + for (i = 0; i < nr; i++, IGRAPH_VIT_NEXT(fromvit)) { + igraph_int_t v1 = IGRAPH_VIT_GET(fromvit); + if (igraph_vs_is_all(&to)) { + igraph_int_t v2; + for (v2 = 0; v2 < nc; v2++) { + igraph_real_t sub; + if (mode == IGRAPH_OUT) { + sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2); + MATRIX(*res, i, v2) -= sub; + } else { + sub = MATRIX(bfres, 0, v2) - MATRIX(bfres, 0, v1); + MATRIX(*res, v2, i) -= sub; + } + } + } else { + igraph_int_t j; + igraph_vit_t tovit; + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + for (j = 0, IGRAPH_VIT_RESET(tovit); j < nc; j++, IGRAPH_VIT_NEXT(tovit)) { + igraph_real_t sub; + igraph_int_t v2 = IGRAPH_VIT_GET(tovit); + if (mode == IGRAPH_OUT) { + sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2); + MATRIX(*res, i, j) -= sub; + } else { + sub = MATRIX(bfres, 0, v2) - MATRIX(bfres, 0, v1); + MATRIX(*res, j, i) -= sub; + } + } + igraph_vit_destroy(&tovit); + IGRAPH_FINALLY_CLEAN(1); + } + } + + igraph_vit_destroy(&fromvit); + igraph_matrix_destroy(&bfres); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/paths_internal.h b/src/paths/paths_internal.h new file mode 100644 index 0000000..477ef03 --- /dev/null +++ b/src/paths/paths_internal.h @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_PATHS_INTERNAL_H +#define IGRAPH_PATHS_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_paths.h" + +IGRAPH_BEGIN_C_DECLS + +/* Helper functions for validating input to shortest path functions. */ + +igraph_error_t igraph_i_validate_distance_weights( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_bool_t *negative_weights); + + +/* The following shortest path functions skip most input validation, + * and are meant for internal use in context where the validation + * has already been done. */ + +/* Distances */ + +igraph_error_t igraph_i_distances_unweighted_cutoff( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + igraph_neimode_t mode, + igraph_real_t cutoff); + +igraph_error_t igraph_i_distances_dijkstra_cutoff( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_real_t cutoff); + +igraph_error_t igraph_i_distances_bellman_ford( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +igraph_error_t igraph_i_distances_johnson( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +igraph_error_t igraph_i_distances_floyd_warshall( + const igraph_t *graph, + igraph_matrix_t *res, + igraph_vs_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_floyd_warshall_algorithm_t method); + +/* Get shortest paths */ + +igraph_error_t igraph_i_get_shortest_paths_unweighted( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + +igraph_error_t igraph_i_get_shortest_paths_dijkstra( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + +igraph_error_t igraph_i_get_shortest_paths_bellman_ford( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges); + +igraph_error_t igraph_i_get_all_shortest_paths_unweighted( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_vector_int_t *nrgeo, + igraph_int_t from, igraph_vs_t to, + igraph_neimode_t mode); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_PATHS_INTERNAL_H */ diff --git a/src/paths/random_walk.c b/src/paths/random_walk.c new file mode 100644 index 0000000..fde30bf --- /dev/null +++ b/src/paths/random_walk.c @@ -0,0 +1,340 @@ +/* + igraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_memory.h" +#include "igraph_vector_ptr.h" + +#include "core/interruption.h" + +/** + * This function performs a random walk with a given length on a graph, + * from the given start vertex. + * It's used for igraph_random_walk when the given graph is unweighted, + * and only vertex IDs of the vertices on the walk are needed (edge IDs are not needed). + * \param vertices An allocated vector, the result is stored here as + * a list of vertex IDs. It will be resized as needed. + * It includes the starting vertex id as well. + */ +static igraph_error_t igraph_i_random_walk_adjlist(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_int_t start, + igraph_neimode_t mode, + igraph_int_t steps, + igraph_random_walk_stuck_t stuck) { + igraph_int_t i; + igraph_lazy_adjlist_t adj; + + if (vertices == NULL) { + /* Nothing to do */ + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adj, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adj); + + IGRAPH_CHECK(igraph_vector_int_resize(vertices, steps + 1)); + + VECTOR(*vertices)[0] = start; + for (i = 1; i <= steps; i++) { + igraph_vector_int_t *neis; + igraph_int_t nn; + neis = igraph_lazy_adjlist_get(&adj, start); + + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + + nn = igraph_vector_int_size(neis); + if (IGRAPH_UNLIKELY(nn == 0)) { + igraph_vector_int_resize(vertices, i); /* shrinks */ + if (stuck == IGRAPH_RANDOM_WALK_STUCK_RETURN) { + break; + } else { + IGRAPH_ERROR("Random walk got stuck.", IGRAPH_ERWSTUCK); + } + } + start = VECTOR(*vertices)[i] = VECTOR(*neis)[RNG_INTEGER(0, nn - 1)]; + + IGRAPH_ALLOW_INTERRUPTION(); + } + + igraph_lazy_adjlist_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Used as item destructor for 'cdfs' in igraph_i_random_walk_inclist(). */ +static void vec_destr(igraph_vector_t *vec) { + if (vec != NULL) { + igraph_vector_destroy(vec); + } +} + + +/** + * This function performs a random walk with a given length on a graph, + * from the given start vertex. + * It's used for igraph_random_walk: + * - when weights are used or when edge IDs of the traversed edges + * and/or vertex IDs of the visited vertices are requested. + * \param weights A vector of non-negative edge weights. It is assumed + * that at least one strictly positive weight is found among the + * outgoing edges of each vertex. Additionally, no edge weight may + * be NaN. If either case does not hold, an error is returned. If it + * is a NULL pointer, all edges are considered to have equal weight. + * \param vertices An allocated vector, the result is stored here as + * a list of vertex IDs. It will be resized as needed. + * It includes the starting vertex id as well. + * \param edges An initialized vector, the indices of traversed + * edges are stored here. It will be resized as needed. + */ +static igraph_error_t igraph_i_random_walk_inclist( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t start, + igraph_neimode_t mode, + igraph_int_t steps, + igraph_random_walk_stuck_t stuck) { + + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t i, next; + igraph_vector_t weight_temp; + igraph_lazy_inclist_t il; + igraph_vector_ptr_t cdfs; /* cumulative distribution vectors for each node, used for weighted choice */ + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_resize(vertices, steps + 1)); /* size: steps + 1 because vertices includes start vertex */ + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_resize(edges, steps)); + } + + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &il, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &il); + + IGRAPH_VECTOR_INIT_FINALLY(&weight_temp, 0); + + /* cdf vectors will be computed lazily; that's why we are still using + * igraph_vector_ptr_t as it does not require us to pre-initialize all + * the vectors in the vector list */ + IGRAPH_CHECK(igraph_vector_ptr_init(&cdfs, vc)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &cdfs); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&cdfs, vec_destr); + for (i = 0; i < vc; ++i) { + VECTOR(cdfs)[i] = NULL; + } + + if (vertices) { + VECTOR(*vertices)[0] = start; + } + for (i = 0; i < steps; ++i) { + igraph_int_t degree, edge, idx; + igraph_vector_int_t *inc_edges = igraph_lazy_inclist_get(&il, start); + + IGRAPH_CHECK_OOM(inc_edges, "Failed to query incident edges."); + degree = igraph_vector_int_size(inc_edges); + + /* are we stuck? */ + if (IGRAPH_UNLIKELY(degree == 0)) { + /* can't fail since size is reduced, skip IGRAPH_CHECK */ + if (vertices) { + igraph_vector_int_resize(vertices, i + 1); /* size: i + 1 because vertices includes start vertex */ + } + if (edges) { + igraph_vector_int_resize(edges, i); + } + if (stuck == IGRAPH_RANDOM_WALK_STUCK_RETURN) { + break; + } else { + IGRAPH_ERROR("Random walk got stuck.", IGRAPH_ERWSTUCK); + } + } + + if (weights) { /* weighted: choose an out-edge with probability proportional to its weight */ + igraph_real_t r; + igraph_vector_t **cd = (igraph_vector_t**) &(VECTOR(cdfs)[start]); + + /* compute out-edge cdf for this node if not already done */ + if (IGRAPH_UNLIKELY(! *cd)) { + igraph_int_t j; + + *cd = IGRAPH_CALLOC(1, igraph_vector_t); + IGRAPH_CHECK_OOM(*cd, "Insufficient memory for random walk."); + IGRAPH_CHECK(igraph_vector_init(*cd, degree)); + + IGRAPH_CHECK(igraph_vector_resize(&weight_temp, degree)); + for (j = 0; j < degree; ++j) { + VECTOR(weight_temp)[j] = VECTOR(*weights)[VECTOR(*inc_edges)[j]]; + } + + IGRAPH_CHECK(igraph_vector_cumsum(*cd, &weight_temp)); + } + + r = RNG_UNIF(0, VECTOR(**cd)[degree - 1]); + igraph_vector_binsearch(*cd, r, &idx); + } + else { + idx = RNG_INTEGER(0, degree - 1); + } + + edge = VECTOR(*inc_edges)[idx]; + if (edges) { + VECTOR(*edges)[i] = edge; + } + + /* travel along edge in a direction specified by 'mode' */ + /* note: 'mode' is always set to IGRAPH_ALL for undirected graphs */ + switch (mode) { + case IGRAPH_OUT: + next = IGRAPH_TO(graph, edge); + break; + case IGRAPH_IN: + next = IGRAPH_FROM(graph, edge); + break; + case IGRAPH_ALL: + next = IGRAPH_OTHER(graph, edge, start); + break; + } + + if (vertices) { + VECTOR(*vertices)[i + 1] = next; /* index i + 1 because vertices includes start vertex at position 0 */ + } + start = next; + + IGRAPH_ALLOW_INTERRUPTION(); + } + + igraph_vector_ptr_destroy_all(&cdfs); + igraph_vector_destroy(&weight_temp); + igraph_lazy_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_random_walk + * \brief Performs a random walk on a graph. + * + * Performs a random walk with a given length on a graph, from the given + * start vertex. Edge directions are (potentially) considered, depending on + * the \p mode argument. + * + * \param graph The input graph, it can be directed or undirected. + * Multiple edges are respected, so are loop edges. + * \param weights A vector of non-negative edge weights. It is assumed + * that at least one strictly positive weight is found among the + * outgoing edges of each vertex. Additionally, no edge weight may + * be NaN. If either case does not hold, an error is returned. If it + * is \c NULL, all edges are considered to have equal weight. + * \param vertices An allocated vector, the result is stored here as + * a list of vertex IDs. It will be resized as needed. + * It includes the vertex IDs of starting and ending vertices. + * Length of the vertices vector: \p steps + 1 + * \param edges An initialized vector, the indices of traversed + * edges are stored here. It will be resized as needed. + * Length of the edges vector: \p steps + * \param start The start vertex for the walk. + * \param mode How to walk along the edges in directed graphs. + * \c IGRAPH_OUT means following edge directions, \c IGRAPH_IN means + * going opposite the edge directions, \c IGRAPH_ALL means ignoring + * edge directions. This argument is ignored for undirected graphs. + * \param steps The number of steps to take. If the random walk gets + * stuck, then the \p stuck argument specifies what happens. + * \p steps is the number of edges to traverse during the walk. + * \param stuck What to do if the random walk gets stuck. + * \c IGRAPH_RANDOM_WALK_STUCK_RETURN means that the function returns + * with a shorter walk; \c IGRAPH_RANDOM_WALK_STUCK_ERROR means + * that an \c IGRAPH_ERWSTUCK error is reported. + * In both cases, \p vertices and \p edges are truncated to contain + * the actual interrupted walk. + * \return Error code: \c IGRAPH_ERWSTUCK if the walk got stuck. + * + * Time complexity: + * O(l + d) for unweighted graphs and + * O(l * log(k) + d) for weighted graphs, + * where \c l is the length of the walk, \c d is the total degree of the visited nodes + * and \c k is the average degree of vertices of the given graph. + */ + + +igraph_error_t igraph_random_walk(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t start, + igraph_neimode_t mode, + igraph_int_t steps, + igraph_random_walk_stuck_t stuck) { + + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t ec = igraph_ecount(graph); + + if (!(mode == IGRAPH_ALL || mode == IGRAPH_IN || mode == IGRAPH_OUT)) { + IGRAPH_ERROR("Invalid mode parameter.", IGRAPH_EINVMODE); + } + + if (start < 0 || start >= vc) { + IGRAPH_ERRORF("Starting vertex must be between 0 and the " + "number of vertices in the graph (%" IGRAPH_PRId + "), got %" IGRAPH_PRId ".", IGRAPH_EINVAL, + vc, start); + } + if (steps < 0) { + IGRAPH_ERRORF("Number of steps should be non-negative, got %" + IGRAPH_PRId ".", IGRAPH_EINVAL, steps); + } + + if (weights) { + if (igraph_vector_size(weights) != ec) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + if (ec > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERROR("Weights must be non-negative.", IGRAPH_EINVAL); + } else if (isnan(min)) { + IGRAPH_ERROR("Weights must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (edges || weights) { + return igraph_i_random_walk_inclist(graph, weights, vertices, edges, + start, mode, steps, stuck); + } else { + return igraph_i_random_walk_adjlist(graph, vertices, + start, mode, steps, stuck); + } +} diff --git a/src/paths/shortest_paths.c b/src/paths/shortest_paths.c new file mode 100644 index 0000000..a443c00 --- /dev/null +++ b/src/paths/shortest_paths.c @@ -0,0 +1,1658 @@ +/* + igraph library. + Copyright (C) 2005-2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_dqueue.h" +#include "igraph_memory.h" +#include "igraph_progress.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +#include + +/*****************************************************/ +/***** Average path length and global efficiency *****/ +/*****************************************************/ + +/* Computes the average of pairwise distances (used for igraph_average_path_length), + * or of inverse pairwise distances (used for igraph_global_efficiency), in an unweighted graph. */ +static igraph_error_t igraph_i_average_path_length_unweighted( + const igraph_t *graph, + igraph_real_t *res, + igraph_real_t *unconnected_pairs, /* if not NULL, will be set to the no. of non-connected ordered vertex pairs */ + const igraph_bool_t directed, + const igraph_bool_t invert, /* average inverse distances instead of distances */ + const igraph_bool_t unconn /* average over connected pairs instead of all pairs */) +{ + const igraph_int_t no_of_nodes = igraph_vcount(graph); + + /* The number of ordered vertex pairs. + * While the conditional below is not mathematically necessary, it prevents + * returning -0.0 where 0.0 would be expected. */ + const igraph_real_t no_of_pairs = no_of_nodes > 0 ? no_of_nodes * (no_of_nodes - 1.0) : 0.0; + + igraph_int_t n; + igraph_int_t *already_added; + + igraph_real_t no_of_conn_pairs = 0.0; /* no. of ordered pairs between which there is a path */ + + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_adjlist_t allneis; + + *res = 0; + already_added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(already_added, "Insufficient memory for average path length."); + IGRAPH_FINALLY(igraph_free, already_added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init( + graph, &allneis, + directed ? IGRAPH_OUT : IGRAPH_ALL, + IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + for (igraph_int_t source = 0; source < no_of_nodes; source++) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, source)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + already_added[source] = source + 1; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (already_added[neighbor] == source + 1) { + continue; + } + already_added[neighbor] = source + 1; + if (invert) { + *res += 1.0/(actdist + 1.0); + } else { + *res += actdist + 1.0; + } + no_of_conn_pairs += 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } /* while !igraph_dqueue_int_empty */ + } /* for source < no_of_nodes */ + + + if (no_of_pairs == 0) { + *res = IGRAPH_NAN; /* can't average zero items */ + } else { + if (unconn) { /* average over connected pairs */ + if (no_of_conn_pairs == 0) { + *res = IGRAPH_NAN; /* can't average zero items */ + } else { + *res /= no_of_conn_pairs; + } + } else { /* average over all pairs */ + /* no_of_conn_pairs < no_of_pairs implies that the graph is disconnected */ + if (no_of_conn_pairs < no_of_pairs && ! invert) { + /* When invert=false, assume the distance between non-connected pairs to be infinity */ + *res = IGRAPH_INFINITY; + } else { + /* When invert=true, assume the inverse distance between non-connected pairs + * to be zero. Therefore, no special treatment is needed for disconnected graphs. */ + *res /= no_of_pairs; + } + } + } + + if (unconnected_pairs) + *unconnected_pairs = no_of_pairs - no_of_conn_pairs; + + /* clean */ + IGRAPH_FREE(already_added); + igraph_dqueue_int_destroy(&q); + igraph_adjlist_destroy(&allneis); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Computes the average of pairwise distances (used for igraph_average_path_length), + * or of inverse pairwise distances (used for igraph_global_efficiency), in a weighted graph. + * Uses Dijkstra's algorithm, therefore all weights must be non-negative. + */ +static igraph_error_t igraph_i_average_path_length_dijkstra( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *res, + igraph_real_t *unconnected_pairs, + const igraph_bool_t directed, + const igraph_bool_t invert, /* average inverse distances instead of distances */ + const igraph_bool_t unconn /* average over connected pairs instead of all pairs */) +{ + + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. + + From now on we use a 2-way heap, so the distances can be queried + directly from the heap. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY in the res matrix during the + computation, as isfinite() might involve a function call + and we want to spare that. -1 will denote infinity instead. + */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_real_t no_of_pairs; + igraph_real_t no_of_conn_pairs = 0.0; /* no. of ordered pairs between which there is a path */ + + IGRAPH_ASSERT(weights != 0); + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match the number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, igraph_vector_size(weights), no_of_edges); + } + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, got %g.", IGRAPH_EINVAL, min); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + /* Avoid returning a negative zero, which would be printed as -0 in tests. */ + if (no_of_nodes > 0) { + no_of_pairs = no_of_nodes * (no_of_nodes - 1.0); + } else { + no_of_pairs = 0; + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init( + graph, &inclist, directed ? IGRAPH_OUT : IGRAPH_ALL, IGRAPH_LOOPS + )); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + *res = 0.0; + + for (igraph_int_t source = 0; source < no_of_nodes; ++source) { + + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_vector_int_t *neis; + igraph_int_t nlen; + + if (minnei != source) { + if (invert) { + *res += 1.0/(mindist - 1.0); + } else { + *res += mindist - 1.0; + } + no_of_conn_pairs += 1; + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curdist = active ? -igraph_2wheap_get(&Q, tto) : 0.0; + if (altdist == IGRAPH_INFINITY) { + /* Ignore edges with positive infinite weight */ + } else if (!has) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + } /* !igraph_2wheap_empty(&Q) */ + } /* for source < no_of_nodes */ + + if (no_of_pairs == 0) { + *res = IGRAPH_NAN; /* can't average zero items */ + } else { + if (unconn) { /* average over connected pairs */ + if (no_of_conn_pairs == 0) { + *res = IGRAPH_NAN; /* can't average zero items */ + } else { + *res /= no_of_conn_pairs; + } + } else { /* average over all pairs */ + /* no_of_conn_pairs < no_of_pairs implies that the graph is disconnected */ + if (no_of_conn_pairs < no_of_pairs && ! invert) { + *res = IGRAPH_INFINITY; + } else { + *res /= no_of_pairs; + } + } + } + + if (unconnected_pairs) { + *unconnected_pairs = no_of_pairs - no_of_conn_pairs; + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_average_path_length + * \brief The average shortest path length between all vertex pairs. + * + * If no vertex pairs can be included in the calculation, for example because the graph + * has fewer than two vertices, or if the graph has no edges and \c unconn is set to \c true, + * NaN is returned. + * + * + * All distinct ordered vertex pairs are taken into account. + * + * \param graph The graph object. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. Passa a null pointer here if the graph is unweighted. + * Edges with positive infinite weight are ignored. + * \param res Pointer to a real number, this will contain the result. + * \param unconn_pairs Pointer to a real number. If not a null pointer, the number of + * ordered vertex pairs where the second vertex is unreachable from the first one + * will be stored here. + * \param directed Boolean, whether to consider directed paths. + * Ignored for undirected graphs. + * \param unconn If \c true, only those pairs are considered for the calculation + * between which there is a path. If \c false, \c IGRAPH_INFINITY is returned + * for disconnected graphs. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for data structures + * \cli IGRAPH_EINVAL + * invalid weight vector + * \endclist + * + * Time complexity: O(|V| |E| log|E| + |V|), where |V| is the number of + * vertices and |E| is the number of edges. + * + * \example examples/simple/igraph_grg_game.c + */ + +igraph_error_t igraph_average_path_length( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_real_t *unconn_pairs, igraph_bool_t directed, igraph_bool_t unconn +) { + if (weights) { + return igraph_i_average_path_length_dijkstra( + graph, weights, res, unconn_pairs, directed, /* invert= */ false, unconn + ); + } else { + return igraph_i_average_path_length_unweighted( + graph, res, unconn_pairs, directed, /* invert = */ false, unconn + ); + } +} + + +/** + * \ingroup structural + * \function igraph_global_efficiency + * \brief Calculates the global efficiency of a network. + * + * The global efficiency of a network is defined as the average of inverse + * distances between all pairs of vertices: + * E_g = 1/(N*(N-1)) sum_{i!=j} 1/d_ij, + * where \c N is the number of vertices. The inverse distance between pairs + * that are not reachable from each other is considered to be zero. For graphs + * with fewer than 2 vertices, NaN is returned. + * + * + * Reference: + * + * + * V. Latora and M. Marchiori, + * Efficient Behavior of Small-World Networks, + * Phys. Rev. Lett. 87, 198701 (2001). + * https://dx.doi.org/10.1103/PhysRevLett.87.198701 + * + * \param graph The graph object. + * \param weights The edge weights. All edge weights must be + * non-negative for Dijkstra's algorithm to work. Additionally, no + * edge weight may be NaN. If either case does not hold, an error + * is returned. If this is a null pointer, then the unweighted + * version, \ref igraph_average_path_length() is used in calculating + * the global efficiency. Edges with positive infinite weights are + * ignored. + * \param res Pointer to a real number, this will contain the result. + * \param directed Boolean, whether to consider directed paths. + * Ignored for undirected graphs. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for data structures + * \cli IGRAPH_EINVAL + * invalid weight vector + * \endclist + * + * Time complexity: O(|V| |E| log|E| + |V|) for weighted graphs and + * O(|V| |E|) for unweighted ones. |V| denotes the number of + * vertices and |E| denotes the number of edges. + * + * \sa \ref igraph_local_efficiency(), \ref igraph_average_local_efficiency() + */ + +igraph_error_t igraph_global_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t *res, igraph_bool_t directed +) { + if (weights) { + return igraph_i_average_path_length_dijkstra( + graph, weights, res, NULL, directed, /* invert= */ true, /* unconn= */ false + ); + } else { + return igraph_i_average_path_length_unweighted( + graph, res, NULL, directed, /* invert= */ true, /* unconn= */ false + ); + } +} + + +/****************************/ +/***** Local efficiency *****/ +/****************************/ + +static igraph_error_t igraph_i_local_efficiency_unweighted( + const igraph_t *graph, + const igraph_adjlist_t *adjlist, + igraph_dqueue_int_t *q, + igraph_int_t *already_counted, + igraph_vector_int_t *vertex_neis, + igraph_vector_char_t *nei_mask, + igraph_real_t *res, + igraph_int_t vertex, + igraph_neimode_t mode) +{ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t vertex_neis_size; + igraph_int_t neighbor_count; /* unlike 'vertex_neis_size', 'neighbor_count' does not count self-loops and multi-edges */ + + igraph_dqueue_int_clear(q); + + /* already_counted[i] is 0 iff vertex i was not reached so far, otherwise + * it is the index of the source vertex in vertex_neis that it was reached + * from, plus 1 */ + memset(already_counted, 0, no_of_nodes * sizeof(already_counted[0])); + + IGRAPH_CHECK(igraph_neighbors(graph, vertex_neis, vertex, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + vertex_neis_size = igraph_vector_int_size(vertex_neis); + + igraph_vector_char_null(nei_mask); + neighbor_count = 0; + for (igraph_int_t i=0; i < vertex_neis_size; ++i) { + igraph_int_t v = VECTOR(*vertex_neis)[i]; + if (v != vertex && ! VECTOR(*nei_mask)[v]) { + VECTOR(*nei_mask)[v] = 1; /* mark as unprocessed neighbour */ + neighbor_count++; + } + } + + *res = 0.0; + + /* when the neighbor count is smaller than 2, we return 0.0 */ + if (neighbor_count < 2) { + return IGRAPH_SUCCESS; + } + + for (igraph_int_t i=0; i < vertex_neis_size; ++i) { + igraph_int_t source = VECTOR(*vertex_neis)[i]; + igraph_int_t reached = 0; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (source == vertex) + continue; + + if (VECTOR(*nei_mask)[source] == 2) + continue; + + VECTOR(*nei_mask)[source] = 2; /* mark neighbour as already processed */ + + IGRAPH_CHECK(igraph_dqueue_int_push(q, source)); + IGRAPH_CHECK(igraph_dqueue_int_push(q, 0)); + already_counted[source] = i + 1; + + while (!igraph_dqueue_int_empty(q)) { + igraph_vector_int_t *act_neis; + igraph_int_t act_neis_size; + igraph_int_t act = igraph_dqueue_int_pop(q); + igraph_int_t actdist = igraph_dqueue_int_pop(q); + + if (act != source && VECTOR(*nei_mask)[act]) { + *res += 1.0 / actdist; + reached++; + if (reached == neighbor_count) { + igraph_dqueue_int_clear(q); + break; + } + } + + act_neis = igraph_adjlist_get(adjlist, act); + act_neis_size = igraph_vector_int_size(act_neis); + for (igraph_int_t j = 0; j < act_neis_size; j++) { + igraph_int_t neighbor = VECTOR(*act_neis)[j]; + + if (neighbor == vertex || already_counted[neighbor] == i + 1) + continue; + + already_counted[neighbor] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(q, actdist + 1)); + } + } + } + + *res /= neighbor_count * (neighbor_count - 1.0); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_local_efficiency_dijkstra( + const igraph_t *graph, + igraph_lazy_inclist_t *inclist, + igraph_2wheap_t *Q, + igraph_vector_int_t *vertex_neis, + igraph_vector_char_t *nei_mask, /* true if the corresponding node is a neighbour of 'vertex' */ + igraph_real_t *res, + igraph_int_t vertex, + igraph_neimode_t mode, + const igraph_vector_t *weights) +{ + + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. + + From now on we use a 2-way heap, so the distances can be queried + directly from the heap. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY in the res matrix during the + computation, as isfinite() might involve a function call + and we want to spare that. -1 will denote infinity instead. + */ + + igraph_int_t vertex_neis_size; + igraph_int_t neighbor_count; /* unlike 'inc_edges_size', 'neighbor_count' does not count self-loops or multi-edges */ + + IGRAPH_CHECK(igraph_neighbors(graph, vertex_neis, vertex, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + vertex_neis_size = igraph_vector_int_size(vertex_neis); + + igraph_vector_char_null(nei_mask); + neighbor_count = 0; + for (igraph_int_t i=0; i < vertex_neis_size; ++i) { + igraph_int_t v = VECTOR(*vertex_neis)[i]; + if (v != vertex && ! VECTOR(*nei_mask)[v]) { + VECTOR(*nei_mask)[v] = 1; /* mark as unprocessed neighbour */ + neighbor_count++; + } + } + + *res = 0.0; + + /* when the neighbor count is smaller than 2, we return 0.0 */ + if (neighbor_count < 2) { + return IGRAPH_SUCCESS; + } + + for (igraph_int_t i=0; i < vertex_neis_size; ++i) { + igraph_int_t source = VECTOR(*vertex_neis)[i]; + igraph_int_t reached = 0; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (source == vertex) + continue; + + /* avoid processing a neighbour twice in multigraphs */ + if (VECTOR(*nei_mask)[source] == 2) + continue; + VECTOR(*nei_mask)[source] = 2; /* mark as already processed */ + + igraph_2wheap_clear(Q); + igraph_2wheap_push_with_index(Q, source, -1.0); + + while (!igraph_2wheap_empty(Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(Q); + igraph_vector_int_t *neis; + igraph_int_t nlen; + + if (minnei != source && VECTOR(*nei_mask)[minnei]) { + *res += 1.0/(mindist - 1.0); + reached++; + if (reached == neighbor_count) { + igraph_2wheap_clear(Q); + break; + } + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(inclist, minnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_real_t altdist, curdist; + igraph_bool_t active, has; + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + + if (tto == vertex) { + continue; + } + + altdist = mindist + VECTOR(*weights)[edge]; + active = igraph_2wheap_has_active(Q, tto); + has = igraph_2wheap_has_elem(Q, tto); + curdist = active ? -igraph_2wheap_get(Q, tto) : 0.0; + if (!has) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(Q, tto, -altdist); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + } + + *res /= neighbor_count * (neighbor_count - 1.0); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_local_efficiency + * \brief Calculates the local efficiency around each vertex in a network. + * + * The local efficiency of a network around a vertex is defined as follows: + * We remove the vertex and compute the distances (shortest path lengths) between + * its neighbours through the rest of the network. The local efficiency around the + * removed vertex is the average of the inverse of these distances. + * + * + * The inverse distance between two vertices which are not reachable from each other + * is considered to be zero. The local efficiency around a vertex with fewer than two + * neighbours is taken to be zero by convention. + * + * + * Reference: + * + * + * I. Vragović, E. Louis, and A. Díaz-Guilera, + * Efficiency of informational transfer in regular and complex networks, + * Phys. Rev. E 71, 1 (2005). + * http://dx.doi.org/10.1103/PhysRevE.71.036122 + * + * \param graph The graph object. + * \param weights The edge weights. All edge weights must be + * non-negative. Additionally, no edge weight may be NaN. If either + * case does not hold, an error is returned. If this is a null + * pointer, then the unweighted version, + * \ref igraph_average_path_length() is called. Edges with positive + * infinite weights are ignored. + * \param res Pointer to an initialized vector, this will contain the result. + * \param vids The vertices around which the local efficiency will be calculated. + * \param directed Boolean, whether to consider directed paths. + * Ignored for undirected graphs. + * \param mode How to determine the local neighborhood of each vertex + * in directed graphs. Ignored in undirected graphs. + * \clist + * \cli IGRAPH_ALL + * take both in- and out-neighbours; + * this is a reasonable default for high-level interfaces. + * \cli IGRAPH_OUT + * take only out-neighbours + * \cli IGRAPH_IN + * take only in-neighbours + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for data structures + * \cli IGRAPH_EINVAL + * invalid weight vector + * \endclist + * + * Time complexity: O(|E|^2 log|E|) for weighted graphs and + * O(|E|^2) for unweighted ones. |E| denotes the number of edges. + * + * \sa \ref igraph_average_local_efficiency(), \ref igraph_global_efficiency() + * + */ + +igraph_error_t igraph_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_vector_t *res, + const igraph_vs_t vids, igraph_bool_t directed, igraph_neimode_t mode +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t nodes_to_calc; /* no. of vertices includes in computation */ + igraph_vit_t vit; + igraph_vector_int_t vertex_neis; + igraph_vector_char_t nei_mask; + igraph_int_t i; + + /* 'nei_mask' is a vector indexed by vertices. The meaning of its values is as follows: + * 0: not a neighbour of 'vertex' + * 1: a not-yet-processed neighbour of 'vertex' + * 2: an already processed neighbour of 'vertex' + * + * Marking neighbours of already processed is necessary to avoid processing them more + * than once in multigraphs. + */ + IGRAPH_CHECK(igraph_vector_char_init(&nei_mask, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &nei_mask); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_neis, 0); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + + if (! weights) /* unweighted case */ + { + igraph_int_t *already_counted; + igraph_adjlist_t adjlist; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + + already_counted = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(already_counted, "Insufficient memory for local efficiency calculation."); + IGRAPH_FINALLY(igraph_free, already_counted); + + IGRAPH_CHECK(igraph_adjlist_init( + graph, &adjlist, + directed ? IGRAPH_OUT : IGRAPH_ALL, + IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + for (IGRAPH_VIT_RESET(vit), i=0; + ! IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) + { + IGRAPH_CHECK(igraph_i_local_efficiency_unweighted( + graph, &adjlist, + &q, already_counted, &vertex_neis, &nei_mask, + &(VECTOR(*res)[i]), IGRAPH_VIT_GET(vit), mode)); + } + + igraph_dqueue_int_destroy(&q); + igraph_adjlist_destroy(&adjlist); + IGRAPH_FREE(already_counted); + IGRAPH_FINALLY_CLEAN(3); + } + else /* weighted case */ + { + igraph_lazy_inclist_t inclist; + igraph_2wheap_t Q; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match the number of edges.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weights must not be negative, got %g.", IGRAPH_EINVAL, min); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weights must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_lazy_inclist_init( + graph, &inclist, directed ? IGRAPH_OUT : IGRAPH_ALL, IGRAPH_LOOPS + )); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + + for (IGRAPH_VIT_RESET(vit), i=0; + ! IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) + { + IGRAPH_CHECK(igraph_i_local_efficiency_dijkstra( + graph, &inclist, + &Q, &vertex_neis, &nei_mask, + &(VECTOR(*res)[i]), IGRAPH_VIT_GET(vit), mode, weights)); + } + + igraph_2wheap_destroy(&Q); + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&vertex_neis); + igraph_vector_char_destroy(&nei_mask); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_average_local_efficiency + * \brief Calculates the average local efficiency in a network. + * + * For the null graph, zero is returned by convention. + * + * \param graph The graph object. + * \param weights The edge weights. They must be all non-negative. + * If a null pointer is given, all weights are assumed to be 1. Edges + * with positive infinite weight are ignored. + * \param res Pointer to a real number, this will contain the result. + * \param directed Boolean, whether to consider directed paths. + * Ignored for undirected graphs. + * \param mode How to determine the local neighborhood of each vertex + * in directed graphs. Ignored in undirected graphs. + * \clist + * \cli IGRAPH_ALL + * take both in- and out-neighbours; + * this is a reasonable default for high-level interfaces. + * \cli IGRAPH_OUT + * take only out-neighbours + * \cli IGRAPH_IN + * take only in-neighbours + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for data structures + * \cli IGRAPH_EINVAL + * invalid weight vector + * \endclist + * + * Time complexity: O(|E|^2 log|E|) for weighted graphs and + * O(|E|^2) for unweighted ones. |E| denotes the number of edges. + * + * \sa \ref igraph_local_efficiency() + * + */ + +igraph_error_t igraph_average_local_efficiency( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_bool_t directed, igraph_neimode_t mode +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_t local_eff; + + /* If there are fewer than 3 vertices, no vertex has more than one neighbour, thus all + local efficiencies are zero. For the null graph, we return zero by convention. */ + if (no_of_nodes < 3) { + *res = 0; + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&local_eff, no_of_nodes); + + IGRAPH_CHECK(igraph_local_efficiency(graph, weights, &local_eff, igraph_vss_all(),directed, mode)); + + *res = igraph_vector_sum(&local_eff); + *res /= no_of_nodes; + + igraph_vector_destroy(&local_eff); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/***************************/ +/***** Graph diameter ******/ +/***************************/ + +/** + * \ingroup structural + * \function igraph_diameter + * \brief Calculates the diameter of a graph (longest geodesic). + * + * The diameter of a graph is the length of the longest shortest path it has, + * i.e. the maximum eccentricity of the graph's vertices. + * This function computes both the diameter, as well as a corresponding path. + * The diameter of the null graph is considered be infinity by convention. + * + * If the graph has no vertices, \c IGRAPH_NAN is returned. + * + * \param graph The graph object. + * \param res Pointer to a real number, if not \c NULL then it will contain + * the diameter (the actual distance). + * \param from Pointer to an integer, if not \c NULL it will be set to the + * source vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param to Pointer to an integer, if not \c NULL it will be set to the + * target vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param vertex_path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path in terms of vertices will be stored here. The vector will be + * resized as needed. + * \param edge_path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path in terms of edges will be stored here. The vector will be + * resized as needed. + * \param directed Boolean, whether to consider directed + * paths. Ignored for undirected graphs. + * \param unconn What to do if the graph is not connected. If + * \c true the longest geodesic within a component + * will be returned, otherwise \c IGRAPH_INFINITY is returned. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), the + * number of vertices times the number of edges. + * + * \sa \ref igraph_radius() for the minimum eccentricity. + * + * \example examples/simple/igraph_diameter.c + */ + +static igraph_error_t igraph_i_diameter_unweighted( + const igraph_t *graph, igraph_real_t *res, igraph_int_t *from, + igraph_int_t *to, igraph_vector_int_t *vertex_path, + igraph_vector_int_t *edge_path, igraph_bool_t directed, igraph_bool_t unconn +) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t n; + igraph_int_t *already_added; + igraph_int_t nodes_reached; + /* from/to are initialized to 0 because in a singleton graph, or in an edgeless graph + * with unconn = true, the diameter path will be considered to consist of vertex 0 only. */ + igraph_int_t ifrom = 0, ito = 0; + igraph_real_t ires = 0; + + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_neimode_t dirmode; + igraph_adjlist_t allneis; + + /* See https://github.com/igraph/igraph/issues/1538#issuecomment-724071857 + * for why we return NaN for the null graph. */ + if (no_of_nodes == 0) { + if (res) { + *res = IGRAPH_NAN; + } + if (vertex_path) { + igraph_vector_int_clear(vertex_path); + } + if (edge_path) { + igraph_vector_int_clear(edge_path); + } + if (from) { + *from = -1; + } + if (to) { + *to = -1; + } + return IGRAPH_SUCCESS; + } + + if (directed) { + dirmode = IGRAPH_OUT; + } else { + dirmode = IGRAPH_ALL; + } + already_added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(already_added, "Insufficient memory for diameter calculation."); + IGRAPH_FINALLY(igraph_free, already_added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, dirmode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + nodes_reached = 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + already_added[i] = i + 1; + + IGRAPH_PROGRESS("Diameter: ", 100.0 * i / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + if (actdist > ires) { + ires = actdist; + ifrom = i; + ito = actnode; + } + + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (already_added[neighbor] == i + 1) { + continue; + } + already_added[neighbor] = i + 1; + nodes_reached++; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } /* while !igraph_dqueue_int_empty */ + + /* not connected, return IGRAPH_INFINITY */ + if (nodes_reached != no_of_nodes && !unconn) { + ires = IGRAPH_INFINITY; + ifrom = -1; + ito = -1; + break; + } + } /* for i 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, got %g.", IGRAPH_EINVAL, min); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, dirmode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + for (igraph_int_t source = 0; source < no_of_nodes; source++) { + + IGRAPH_PROGRESS("Weighted diameter: ", source * 100.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); + + nodes_reached = 0.0; + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_vector_int_t *neis; + igraph_int_t nlen; + + if (mindist > ires) { + ires = mindist; ifrom = source; ito = minnei; + } + nodes_reached++; + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_inclist_get(&inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curdist = active ? -igraph_2wheap_get(&Q, tto) : 0.0; + + if (!has) { + /* First finite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* A shorter path */ + igraph_2wheap_modify(&Q, tto, -altdist); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + /* not connected, return infinity */ + if (nodes_reached != no_of_nodes && !unconn) { + ires = IGRAPH_INFINITY; + ifrom = ito = -1; + break; + } + + } /* source < no_of_nodes */ + + /* Compensate for the +1 that we have added to distances */ + ires -= 1; + + igraph_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_PROGRESS("Weighted diameter: ", 100.0, NULL); + + if (res) { + *res = ires; + } + if (from) { + *from = ifrom; + } + if (to) { + *to = ito; + } + if ((vertex_path) || (edge_path)) { + if (!isfinite(ires)) { + if (vertex_path){ + igraph_vector_int_clear(vertex_path); + } + if (edge_path) { + igraph_vector_int_clear(edge_path); + } + } else { + IGRAPH_CHECK(igraph_get_shortest_path_dijkstra(graph, + /*vertices=*/ vertex_path, /*edges=*/ edge_path, + ifrom, ito, + weights, dirmode)); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_diameter + * \brief Calculates the weighted diameter of a graph using Dijkstra's algorithm. + * + * This function computes the weighted diameter of a graph, defined as the longest + * weighted shortest path, or the maximum weighted eccentricity of the graph's + * vertices. A corresponding shortest path, as well as its endpoints, + * can also be optionally computed. + * + * + * If the graph has no vertices, \c IGRAPH_NAN is returned. + * + * \param graph The input graph, can be directed or undirected. + * \param weights The edge weights of the graph. Can be \c NULL for an + * unweighted graph. Edges with positive infinite weight are ignored. + * \param res Pointer to a real number, if not \c NULL then it will contain + * the diameter (the actual distance). + * \param from Pointer to an integer, if not \c NULL it will be set to the + * source vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param to Pointer to an integer, if not \c NULL it will be set to the + * target vertex of the diameter path. If the graph has no diameter path, + * it will be set to -1. + * \param vertex_path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path in terms of vertices will be stored here. The vector will be + * resized as needed. + * \param edge_path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path in terms of edges will be stored here. The vector will be + * resized as needed. + * \param directed Boolean, whether to consider directed + * paths. Ignored for undirected graphs. + * \param unconn What to do if the graph is not connected. If + * \c true the longest geodesic within a component + * will be returned, otherwise \c IGRAPH_INFINITY is + * returned. + * \return Error code. + * + * Time complexity: O(|V||E|*log|E|), |V| is the number of vertices, + * |E| is the number of edges. + * + * \sa \ref igraph_radius() for the minimum eccentricity. + * + * \example examples/simple/igraph_diameter.c + */ +igraph_error_t igraph_diameter( + const igraph_t *graph, const igraph_vector_t *weights, igraph_real_t *res, + igraph_int_t *from, igraph_int_t *to, igraph_vector_int_t *vertex_path, + igraph_vector_int_t *edge_path, igraph_bool_t directed, igraph_bool_t unconn +) { + if (weights) { + return igraph_i_diameter_dijkstra( + graph, weights, res, from, to, vertex_path, edge_path, + directed, unconn + ); + } else { + return igraph_i_diameter_unweighted( + graph, res, from, to, vertex_path, edge_path, + directed, unconn + ); + } +} + +/** + * Temporarily removes all edges incident on the vertex with the given ID from + * the graph by setting the weights of these edges to infinity. + * + * \param graph the graph + * \param weights the weights of the edges of the graph + * \param vid the ID of the vertex to remove + * \param edges_removed vector that records the IDs of the edges that were + * "removed" (i.e. their weights were set to infinity) + * \param eids temporary vector that is used to retrieve the IDs of the + * incident edges, to make this function free of memory allocations + */ +static igraph_error_t igraph_i_semidelete_vertex( + const igraph_t *graph, igraph_vector_t *weights, + igraph_int_t vid, igraph_vector_int_t *edges_removed, + igraph_vector_int_t *eids +) { + IGRAPH_CHECK(igraph_incident(graph, eids, vid, IGRAPH_ALL, IGRAPH_LOOPS)); + + const igraph_int_t n = igraph_vector_int_size(eids); + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t eid = VECTOR(*eids)[j]; + IGRAPH_CHECK(igraph_vector_int_push_back(edges_removed, eid)); + VECTOR(*weights)[eid] = IGRAPH_INFINITY; + } + + return IGRAPH_SUCCESS; +} + +static igraph_bool_t igraph_i_has_edge_with_infinite_weight( + const igraph_vector_int_t* path, const igraph_vector_t* weights +) { + const igraph_int_t n = weights ? igraph_vector_int_size(path) : 0; + for (igraph_int_t i = 0; i < n; i++) { + const igraph_int_t edge = VECTOR(*path)[i]; + if (!isfinite(VECTOR(*weights)[edge])) { + return true; + } + } + + return false; +} + +static igraph_real_t igraph_i_get_total_weight_of_path( + igraph_vector_int_t* path, const igraph_vector_t* weights +) { + const igraph_int_t n = igraph_vector_int_size(path); + igraph_real_t result; + + if (weights) { + result = 0; + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t edge = VECTOR(*path)[i]; + result += VECTOR(*weights)[edge]; + } + } else { + result = n; + } + + return result; +} + +/** + * \function igraph_get_k_shortest_paths + * \brief k shortest paths between two vertices. + * + * This function returns the \p k shortest paths between two vertices, in order of + * increasing lengths. + * + * + * Reference: + * + * + * Yen, Jin Y.: + * An algorithm for finding shortest routes from all source nodes to a given + * destination in general networks. + * Quarterly of Applied Mathematics. 27 (4): 526–530. (1970) + * https://doi.org/10.1090/qam/253822 + * + * \param graph The graph object. + * \param weights The edge weights of the graph. Can be \c NULL for an + * unweighted graph. Infinite weights will be treated as missing + * edges. + * \param vertex_paths Pointer to an initialized list of integer vectors, the result + * will be stored here in \ref igraph_vector_int_t objects. Each vector + * object contains the vertex IDs along the kth shortest path + * between \p from and \p to, where \c k is the vector list index. May + * be \c NULL if the vertex paths are not needed. + * \param edge_paths Pointer to an initialized list of integer vectors, the result + * will be stored here in \ref igraph_vector_int_t objects. Each vector + * object contains the edge IDs along the kth shortest path + * between \p from and \p to, where \c k is the vector list index. May be + * \c NULL if the edge paths are not needed. + * \param k The number of paths. + * \param from The ID of the vertex from which the paths are calculated. + * \param to The ID of the vertex to which the paths are calculated. + * \param mode The type of paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * The outgoing paths of \p from are calculated. + * \cli IGRAPH_IN + * The incoming paths of \p from are calculated. + * \cli IGRAPH_ALL + * The directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * Not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from or \p to is an invalid vertex id. + * \cli IGRAPH_EINVMODE + * Invalid mode argument. + * \cli IGRAPH_EINVAL + * Invalid argument. + * \endclist + * + * Time complexity: k |V| (|V| log|V| + |E|), where |V| is the number of vertices, + * and |E| is the number of edges. + * + * \sa \ref igraph_get_all_simple_paths(), \ref igraph_get_shortest_paths(), + * \ref igraph_get_shortest_paths_dijkstra() + */ +igraph_error_t igraph_get_k_shortest_paths( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_int_list_t *vertex_paths, + igraph_vector_int_list_t *edge_paths, + igraph_int_t k, igraph_int_t from, igraph_int_t to, + igraph_neimode_t mode +) { + igraph_vector_int_list_t paths_pot; /* potential shortest paths */ + igraph_int_t vertex_spur; + igraph_vector_int_t path_spur, path_root, path_total, path_shortest; + igraph_int_t nr_edges_root, i_path_current, i_path, edge_path_root, vertex_root_del; + igraph_int_t n; + igraph_vector_t current_weights; + igraph_vector_int_t edges_removed; + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t infinite_path, already_in_potential_paths; + igraph_vector_int_t *path_0; + igraph_vector_int_t eids; + igraph_real_t path_weight, shortest_path_weight; + igraph_int_t edge_paths_owned = 0; + + if (!igraph_is_directed(graph) && (mode == IGRAPH_IN || mode == IGRAPH_OUT)) { + mode = IGRAPH_ALL; + } + + if (vertex_paths) { + igraph_vector_int_list_clear(vertex_paths); + } + + if (!edge_paths) { + /* We will need our own instance */ + edge_paths = IGRAPH_CALLOC(1, igraph_vector_int_list_t); + IGRAPH_CHECK_OOM(edge_paths, "Cannot allocate vector for storing edge paths."); + IGRAPH_FINALLY(igraph_free, edge_paths); + edge_paths_owned = 1; + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(edge_paths, 0); + edge_paths_owned = 2; + } + + igraph_vector_int_list_clear(edge_paths); + + if (k == 0) { + goto cleanup; + } + + IGRAPH_CHECK(igraph_vector_int_list_resize(edge_paths, 1)); + path_0 = igraph_vector_int_list_get_ptr(edge_paths, 0); + + IGRAPH_CHECK(igraph_get_shortest_path_dijkstra(graph, + NULL, + path_0, + from, + to, + weights, + mode)); + + /* Check if there's a path. */ + infinite_path = igraph_i_has_edge_with_infinite_weight(path_0, weights); + if (infinite_path || (from != to && igraph_vector_int_size(path_0) == 0)) { + /* No path found. */ + igraph_vector_int_list_clear(edge_paths); + goto cleanup; + } + + IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&paths_pot, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&path_spur, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&path_root, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&path_total, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges_removed, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&eids, 0); + IGRAPH_VECTOR_INIT_FINALLY(¤t_weights, no_of_edges); + + /* If weights are NULL we use a uniform weight vector where each edge has + * a weight of 1. Later on, we replace the weights of removed edges with + * infinities. Note that we work on a copy of the weight vector so the + * original vector remains intact. + */ + if (weights) { + igraph_vector_update(¤t_weights, weights); /* reserved */ + } else { + igraph_vector_fill(¤t_weights, 1); + } + + for (i_path_current = 1; i_path_current < k; i_path_current++) { + igraph_vector_int_t *path_previous = igraph_vector_int_list_tail_ptr(edge_paths); + igraph_int_t path_previous_length = igraph_vector_int_size(path_previous); + for (nr_edges_root = 0; nr_edges_root < path_previous_length; nr_edges_root++) { + /* Determine spur node. */ + if (mode == IGRAPH_OUT) { + vertex_spur = IGRAPH_FROM(graph, VECTOR(*path_previous)[nr_edges_root]); + } else if (mode == IGRAPH_IN) { + vertex_spur = IGRAPH_TO(graph, VECTOR(*path_previous)[nr_edges_root]); + } else { + igraph_int_t eid = VECTOR(*path_previous)[nr_edges_root]; + igraph_int_t vertex_spur_1 = IGRAPH_FROM(graph, eid); + igraph_int_t vertex_spur_2 = IGRAPH_TO(graph, eid); + igraph_int_t vertex_spur_3; + igraph_int_t vertex_spur_4; + if (nr_edges_root < path_previous_length-1) { + igraph_int_t eid_next = VECTOR(*path_previous)[nr_edges_root + 1]; + vertex_spur_3 = IGRAPH_FROM(graph, eid_next); + vertex_spur_4 = IGRAPH_TO(graph, eid_next); + } else { + vertex_spur_3 = vertex_spur_4 = to; + } + if (vertex_spur_1 == vertex_spur_3 || vertex_spur_1 == vertex_spur_4) { + vertex_spur = vertex_spur_2; + } else { + vertex_spur = vertex_spur_1; + } + } + + /* Determine root path. */ + IGRAPH_CHECK(igraph_vector_int_resize(&path_root, nr_edges_root)); + for (igraph_int_t i = 0; i < nr_edges_root; i++) { + VECTOR(path_root)[i] = VECTOR(*path_previous)[i]; + } + + /* Remove edges that are part of the previous shortest paths which share the same root path. */ + for (i_path = 0; i_path < i_path_current; i_path++) { + igraph_vector_int_t *path_check = igraph_vector_int_list_get_ptr(edge_paths, i_path); + igraph_bool_t equal = true; + for (igraph_int_t i = 0; i < nr_edges_root; i++) { + if (VECTOR(path_root)[i] != VECTOR(*path_check)[i]) { + equal = false; + break; + } + } + if (equal) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges_removed, VECTOR(*path_check)[nr_edges_root])); + VECTOR(current_weights)[VECTOR(*path_check)[nr_edges_root]] = IGRAPH_INFINITY; + } + } + + /* pseudocode: for each node rootPathNode in rootPath except spurNode: + * remove rootPathNode from Graph; + */ + for (edge_path_root = 0; edge_path_root < nr_edges_root; edge_path_root++) { + if (mode == IGRAPH_OUT) { + vertex_root_del = IGRAPH_FROM(graph, VECTOR(path_root)[edge_path_root]); + } else if (mode == IGRAPH_IN) { + vertex_root_del = IGRAPH_TO(graph, VECTOR(path_root)[edge_path_root]); + } else { + igraph_int_t eid = VECTOR(*path_previous)[edge_path_root]; + igraph_int_t eid_next = VECTOR(*path_previous)[edge_path_root + 1]; + igraph_int_t vertex_root_del_1 = IGRAPH_FROM(graph, eid); + igraph_int_t vertex_root_del_2 = IGRAPH_TO(graph, eid); + igraph_int_t vertex_root_del_3 = IGRAPH_FROM(graph, eid_next); + igraph_int_t vertex_root_del_4 = IGRAPH_TO(graph, eid_next); + if (vertex_root_del_1 == vertex_root_del_3 || vertex_root_del_1 == vertex_root_del_4) { + vertex_root_del = vertex_root_del_2; + } else { + vertex_root_del = vertex_root_del_1; + } + } + /* Remove vertex by setting incident edges to infinity */ + IGRAPH_CHECK(igraph_i_semidelete_vertex( + graph, ¤t_weights, vertex_root_del, &edges_removed, + &eids + )); + } + + /* Determine spur path */ + IGRAPH_CHECK(igraph_get_shortest_path_dijkstra(graph, + NULL, + &path_spur, + vertex_spur, + to, + ¤t_weights, + mode)); + infinite_path = igraph_i_has_edge_with_infinite_weight(&path_spur, ¤t_weights); + + /* Add total (root + spur) path to potential paths if it's not in there yet. */ + if (!infinite_path) { + IGRAPH_CHECK(igraph_vector_int_update(&path_total, &path_root)); + IGRAPH_CHECK(igraph_vector_int_append(&path_total, &path_spur)); + + already_in_potential_paths = false; + n = igraph_vector_int_list_size(&paths_pot); + for (igraph_int_t i = 0; i < n; i++) { + if (igraph_vector_int_all_e(&path_total, igraph_vector_int_list_get_ptr(&paths_pot, i))) { + already_in_potential_paths = true; + break; + } + } + + if (!already_in_potential_paths) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(&paths_pot, &path_total)); + } + } + + /* Cleanup */ + n = igraph_vector_int_size(&edges_removed); + for (igraph_int_t i = 0; i < n; i++) { + VECTOR(current_weights)[VECTOR(edges_removed)[i]] = + weights ? VECTOR(*weights)[VECTOR(edges_removed)[i]] : 1; + } + igraph_vector_int_clear(&edges_removed); + } + + /* Add shortest potential path to shortest paths */ + n = igraph_vector_int_list_size(&paths_pot); + if (n == 0) { + break; + } + + shortest_path_weight = igraph_i_get_total_weight_of_path( + igraph_vector_int_list_get_ptr(&paths_pot, 0), weights + ); + i_path = 0; + for (igraph_int_t i = 1; i < n; i++) { + path_weight = igraph_i_get_total_weight_of_path( + igraph_vector_int_list_get_ptr(&paths_pot, i), weights + ); + if (path_weight < shortest_path_weight) { + i_path = i; + shortest_path_weight = path_weight; + } + } + + IGRAPH_CHECK(igraph_vector_int_list_remove_fast(&paths_pot, i_path, &path_shortest)); + IGRAPH_CHECK(igraph_vector_int_list_push_back(edge_paths, &path_shortest)); + } + + igraph_vector_destroy(¤t_weights); + igraph_vector_int_destroy(&eids); + igraph_vector_int_destroy(&edges_removed); + igraph_vector_int_destroy(&path_total); + igraph_vector_int_destroy(&path_root); + igraph_vector_int_destroy(&path_spur); + igraph_vector_int_list_destroy(&paths_pot); + IGRAPH_FINALLY_CLEAN(7); + + if (vertex_paths) { + const igraph_int_t no_of_edge_paths = igraph_vector_int_list_size(edge_paths); + + IGRAPH_CHECK(igraph_vector_int_list_resize(vertex_paths, no_of_edge_paths)); + for (igraph_int_t i = 0; i < no_of_edge_paths; i++) { + igraph_vector_int_t* edge_path = igraph_vector_int_list_get_ptr(edge_paths, i); + igraph_vector_int_t* vertex_path = igraph_vector_int_list_get_ptr(vertex_paths, i); + IGRAPH_CHECK(igraph_vertex_path_from_edge_path(graph, from, edge_path, vertex_path, mode)); + } + } + +cleanup: + if (edge_paths_owned >= 2) { + igraph_vector_int_list_destroy(edge_paths); + IGRAPH_FINALLY_CLEAN(1); + } + if (edge_paths_owned >= 1) { + igraph_free(edge_paths); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/simple_paths.c b/src/paths/simple_paths.c new file mode 100644 index 0000000..630689a --- /dev/null +++ b/src/paths/simple_paths.c @@ -0,0 +1,189 @@ +/* + igraph library. + Copyright (C) 2014-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_vector_list.h" + +#include "core/interruption.h" + +/** + * \function igraph_get_all_simple_paths + * \brief List all simple paths from one source. + * + * A path is simple if its vertices are unique, i.e. no vertex is visited more + * than once. This function returns paths in terms of their vertices and + * ignores multi-edges. + * + * + * Note that potentially there are exponentially many paths between two + * vertices of a graph, and you may run out of memory when using this function + * when the graph has many cycles. Consider using the \p minlen and \p maxlen + * parameters to restrict the paths that are returned. + * + * \param graph The input graph. + * \param res Initialized integer vector list. The paths are returned here in + * terms of their vertices. The paths are included in arbitrary order, as + * they are found. + * \param from The start vertex. + * \param to The target vertices. + * \param mode The type of paths to be used for the calculation in directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * outgoing paths are calculated. + * \cli IGRAPH_IN + * incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for + * the computation. + * \endclist + * \param minlen Minimum length of paths that is considered. If negative + * or \ref IGRAPH_UNLIMITED, no lower bound is used on the path lengths. + * \param maxlen Maximum length of paths that is considered. If negative + * or \ref IGRAPH_UNLIMITED, no upper bound is used on the path lengths. + * \param max_results At most this many paths will be recorded. If + * negative, or \ref IGRAPH_UNLIMITED, no limit is applied. + * \return Error code. + * + * \sa \ref igraph_get_k_shortest_paths() + * + * Time complexity: O(n!) in the worst case, n is the number of + * vertices. + */ + +igraph_error_t igraph_get_all_simple_paths( + const igraph_t *graph, + igraph_vector_int_list_t *res, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_int_t minlen, igraph_int_t maxlen, + igraph_int_t max_results) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_bool_t toall = igraph_vs_is_all(&to); + igraph_vit_t vit; + igraph_lazy_adjlist_t adjlist; + igraph_vector_int_t stack, dist; /* used as a stack, but represented as a vector, + in order to be appendable the result vector list */ + igraph_bitset_t markto, added; + igraph_vector_int_t nptr; + int iter = 0; + + if (from < 0 || from >= vcount) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + + igraph_vector_int_list_clear(res); + + if (max_results == 0) { + return IGRAPH_SUCCESS; + } + + if (!toall) { + IGRAPH_BITSET_INIT_FINALLY(&markto, vcount); + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + IGRAPH_BIT_SET(markto, IGRAPH_VIT_GET(vit)); + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_BITSET_INIT_FINALLY(&added, vcount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&stack, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&dist, 100); + IGRAPH_CHECK(igraph_lazy_adjlist_init( + graph, &adjlist, mode, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE + )); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + IGRAPH_VECTOR_INT_INIT_FINALLY(&nptr, vcount); + + igraph_vector_int_clear(&stack); + igraph_vector_int_clear(&dist); + igraph_vector_int_push_back(&stack, from); /* reserved enough for initial push */ + igraph_vector_int_push_back(&dist, 0); /* reserved enough for initial push */ + IGRAPH_BIT_SET(added, from); + while (!igraph_vector_int_empty(&stack)) { + const igraph_int_t act = igraph_vector_int_tail(&stack); + const igraph_int_t curdist = igraph_vector_int_tail(&dist); + + const igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, act); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + + const igraph_int_t n = igraph_vector_int_size(neis); + igraph_int_t *ptr = igraph_vector_int_get_ptr(&nptr, act); + igraph_bool_t any; + igraph_bool_t within_dist; + igraph_int_t nei; + + within_dist = (curdist < maxlen || maxlen < 0); + if (within_dist) { + /* Search for a neighbor that was not yet visited */ + any = false; + while (!any && (*ptr) < n) { + nei = VECTOR(*neis)[(*ptr)]; + any = !IGRAPH_BIT_TEST(added, nei); + (*ptr) ++; + } + } + if (within_dist && any) { + /* There is such a neighbor, add it */ + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&dist, curdist + 1)); + IGRAPH_BIT_SET(added, nei); + /* Add to results */ + if (toall || IGRAPH_BIT_TEST(markto, nei)) { + if (curdist + 1 >= minlen) { + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &stack)); + if (max_results >= 0 && igraph_vector_int_list_size(res) == max_results) { + break; + } + } + } + } else { + /* There is no such neighbor, finished with the subtree */ + igraph_int_t up = igraph_vector_int_pop_back(&stack); + igraph_vector_int_pop_back(&dist); + IGRAPH_BIT_CLEAR(added, up); + VECTOR(nptr)[up] = 0; + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 13); + } + + igraph_vector_int_destroy(&nptr); + igraph_lazy_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&dist); + igraph_vector_int_destroy(&stack); + igraph_bitset_destroy(&added); + IGRAPH_FINALLY_CLEAN(5); + + if (!toall) { + igraph_bitset_destroy(&markto); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/sparsifier.c b/src/paths/sparsifier.c new file mode 100644 index 0000000..1d9a140 --- /dev/null +++ b/src/paths/sparsifier.c @@ -0,0 +1,455 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_bitset.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_random.h" + +#include "core/interruption.h" + +/* + * This internal function gets the adjacency and incidence list representation + * of the current residual graph, the weight vector, the current assignment of + * the vertices to clusters, whether the i-th cluster is sampled, and the + * index of a single node v. The function updates the given lightest_eid vector + * such that the i-th element contains the ID of the lightest edge that leads + * from node v to cluster i. Similarly, the lightest_weight vector is updated + * to contain the weights of these edges. + * + * When the is_cluster_sampled vector is provided, the + * nearest_neighboring_sampled_cluster pointer is also updated to the index of + * the cluster that has the smallest weight among the _sampled_ ones. + * + * As a pre-condition, this function requires the lightest_eid vector to be + * filled with -1 and the lightest_weight vector to be filled with infinity. + * This is _not_ checked within the function. + * + * Use the igraph_i_clean_lightest_edges_to_clusters() function to clear these vectors + * after you are done with them. Avoid using igraph_vector_fill() because that + * one is O(|V|), while igraph_i_clean_lightest_edge_vector() is O(d) where d + * is the degree of the vertex. + */ +static igraph_error_t collect_lightest_edges_to_clusters( + const igraph_adjlist_t *adjlist, + const igraph_inclist_t *inclist, + const igraph_vector_t *weights, + const igraph_vector_int_t *clustering, + const igraph_bitset_t *is_cluster_sampled, + igraph_int_t v, + igraph_vector_int_t *lightest_eid, + igraph_vector_t *lightest_weight, + igraph_vector_int_t *dirty_vids, + igraph_int_t *nearest_neighboring_sampled_cluster +) { + // This internal function gets the residual graph, the clustering, the sampled clustering and + // the vector and returns the lightest edge to each neighboring cluster and the index of the lightest + // sampled cluster (if any) + + igraph_real_t lightest_weight_to_sampled = IGRAPH_INFINITY; + const igraph_vector_int_t *adjacent_nodes = igraph_adjlist_get(adjlist, v); + const igraph_vector_int_t *incident_edges = igraph_inclist_get(inclist, v); + const igraph_int_t nlen = igraph_vector_int_size(incident_edges); + + for (igraph_int_t i = 0; i < nlen; i++) { + igraph_int_t neighbor_node = VECTOR(*adjacent_nodes)[i]; + igraph_int_t edge = VECTOR(*incident_edges)[i]; + igraph_int_t neighbor_cluster = VECTOR(*clustering)[neighbor_node]; + igraph_real_t weight = weights ? VECTOR(*weights)[edge] : 1; + + // If the weight of the edge being considered is smaller than the weight + // of the lightest edge found so far that connects v to the same + // cluster, remember the new minimum. + if (VECTOR(*lightest_weight)[neighbor_cluster] > weight) { + VECTOR(*lightest_weight)[neighbor_cluster] = weight; + VECTOR(*lightest_eid)[neighbor_cluster] = edge; + + IGRAPH_CHECK(igraph_vector_int_push_back(dirty_vids, neighbor_cluster)); + + // Also, if this cluster happens to be a sampled cluster, also update + // the variables that store which is the lightest edge that connects + // v to any of the sampled clusters. + if (is_cluster_sampled) { + if ((IGRAPH_BIT_TEST(*is_cluster_sampled, neighbor_cluster)) && (lightest_weight_to_sampled > weight)) { + lightest_weight_to_sampled = weight; + *nearest_neighboring_sampled_cluster = neighbor_cluster; + } + } + } + } + + return IGRAPH_SUCCESS; +} + +static void clear_lightest_edges_to_clusters( + igraph_vector_int_t *dirty_vids, + igraph_vector_int_t *lightest_eid, + igraph_vector_t *lightest_weight +) { + const igraph_int_t n = igraph_vector_int_size(dirty_vids); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t vid = VECTOR(*dirty_vids)[i]; + VECTOR(*lightest_weight)[vid] = IGRAPH_INFINITY; + VECTOR(*lightest_eid)[vid] = -1; + } + + igraph_vector_int_clear(dirty_vids); +} + +/** + * \ingroup structural + * \function igraph_spanner + * \brief Calculates a spanner of a graph with a given stretch factor. + * + * A spanner of a graph G = (V,E) with a stretch \c t is a + * subgraph H = (V,Es) such that \c Es is a subset of \c E + * and the distance between any pair of nodes in \c H is at most \c t + * times the distance in \c G. The returned graph is always a spanner of + * the given graph with the specified stretch. For weighted graphs the + * number of edges in the spanner is O(k n^(1 + 1 / k)), where + * \c k is k = (t + 1) / 2, \c m is the number of edges + * and \c n is the number of nodes in \c G. For unweighted graphs the number + * of edges is O(n^(1 + 1 / k) + kn). + * + * + * This function is based on the algorithm of Baswana and Sen: "A Simple and + * Linear Time Randomized Algorithm for Computing Sparse Spanners in + * Weighted Graphs". https://doi.org/10.1002/rsa.20130 + * + * \param graph An undirected connected graph object. If the graph + * is directed, the directions of the edges will be ignored. + * \param spanner An initialized vector, the IDs of the edges that constitute + * the calculated spanner will be returned here. Use + * \ref igraph_subgraph_from_edges() to extract the spanner as a separate + * graph object. + * \param stretch The stretch factor \c t of the spanner. + * \param weights The edge weights or \c NULL. + * \return Error code. + * + * Time complexity: The algorithm is a randomized Las Vegas algorithm. The expected + * running time is O(km) where k is the value mentioned above and m is the number + * of edges. + */ +igraph_error_t igraph_spanner(const igraph_t *graph, igraph_vector_int_t *spanner, + igraph_real_t stretch, const igraph_vector_t *weights) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + const igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t nlen, neighbor, cluster; + igraph_real_t sample_prob, k = (stretch + 1) / 2, weight, lightest_sampled_weight; + igraph_vector_int_t clustering, lightest_eid; + igraph_vector_t lightest_weight; + igraph_bitset_t is_cluster_sampled; + igraph_bitset_t is_edge_in_spanner; + igraph_vector_int_t new_clustering; + igraph_vector_int_t dirty_vids; + igraph_vector_int_t *adjacent_vertices; + igraph_vector_int_t *incident_edges; + igraph_adjlist_t adjlist; + igraph_inclist_t inclist; + igraph_int_t edge; + igraph_int_t index; + + if (spanner == NULL) { + return IGRAPH_SUCCESS; + } + + /* Test validity of stretch factor */ + if (stretch < 1) { + IGRAPH_ERROR("Stretch factor must be at least 1.", IGRAPH_EINVAL); + } + + /* Test validity of weights vector */ + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match.", IGRAPH_EINVAL); + } + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); + } + else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + } + + // Clear the vector that will contain the IDs of the edges in the spanner + igraph_vector_int_clear(spanner); + + // Create an incidence list representation of the graph and also create the + // corresponding adjacency list. The residual graph will not be constructed + // explicitly; it will only exist in terms of the incidence and the adjacency + // lists, maintained in parallel as the edges are removed from the residual + // graph. + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_NO_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_CHECK(igraph_adjlist_init_from_inclist(graph, &adjlist, &inclist)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + // Phase 1: forming the clusters + // Create a vector which maps the nodes to the centers of the corresponding + // clusters. At the beginning each node is its own cluster center. + IGRAPH_CHECK(igraph_vector_int_init_range(&clustering, 0, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &clustering); + + // A mapping vector which indicates the neighboring edge with the smallest + // weight for each cluster central, for a single vertex of interest. + // Preconditions needed by collect_lightest_edges_to_clusters() + // are enforced here. + IGRAPH_VECTOR_INT_INIT_FINALLY(&lightest_eid, no_of_nodes); + igraph_vector_int_fill(&lightest_eid, -1); + + // A mapping vector which indicated the minimum weight to each neighboring + // cluster, for a single vertex of interest. + // Preconditions needed by collect_lightest_edges_to_clusters() + // are enforced here. + IGRAPH_VECTOR_INIT_FINALLY(&lightest_weight, no_of_nodes); + igraph_vector_fill(&lightest_weight, IGRAPH_INFINITY); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&new_clustering, no_of_nodes); + + // A boolean vector whose i-th element is 1 if the i-th vertex is a cluster + // center that is sampled in the current iteration, 0 otherwise + IGRAPH_BITSET_INIT_FINALLY(&is_cluster_sampled, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&is_edge_in_spanner, no_of_edges); + + // Temporary vector used by collect_lightest_edges_to_clusters() + // to keep track of the nodes that it has written to + IGRAPH_VECTOR_INT_INIT_FINALLY(&dirty_vids, 0); + + sample_prob = pow((igraph_real_t) no_of_nodes, -1 / k); + +#define ADD_EDGE_TO_SPANNER \ + do { if (!IGRAPH_BIT_TEST(is_edge_in_spanner, edge)) { \ + IGRAPH_BIT_SET(is_edge_in_spanner, edge); \ + IGRAPH_CHECK(igraph_vector_int_push_back(spanner, edge)); \ + } } while (0) + + igraph_vector_fill(&lightest_weight, IGRAPH_INFINITY); + + for (igraph_int_t i = 0; i < k - 1; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_int_fill(&new_clustering, -1); + igraph_bitset_null(&is_cluster_sampled); + + // Step 1: sample cluster centers + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + if (VECTOR(clustering)[j] == j && RNG_UNIF01() < sample_prob) { + IGRAPH_BIT_SET(is_cluster_sampled, j); + } + } + + // Step 2 and 3 + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + // If v is inside a cluster and the cluster of v is sampled, then continue + cluster = VECTOR(clustering)[v]; + if (cluster != -1 && IGRAPH_BIT_TEST(is_cluster_sampled, cluster)) { + VECTOR(new_clustering)[v] = cluster; + continue; + } + + // Step 2: find the lightest edge that connects vertex v to its + // neighboring sampled clusters + igraph_int_t nearest_neighboring_sampled_cluster = -1; + IGRAPH_CHECK(collect_lightest_edges_to_clusters( + &adjlist, + &inclist, + weights, + &clustering, + &is_cluster_sampled, + v, + &lightest_eid, + &lightest_weight, + &dirty_vids, + &nearest_neighboring_sampled_cluster + )); + + // Step 3: add edges to spanner + if (nearest_neighboring_sampled_cluster == -1) { + // Case 3(a) from the paper: v is not adjacent to any of the + // sampled clusters. + + // Add lightest edge which connects vertex v to each neighboring + // cluster (none of which are sampled) + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + edge = VECTOR(lightest_eid)[j]; + if (edge != -1) { + ADD_EDGE_TO_SPANNER; + } + } + + // Remove all edges incident on v from the graph. Note that each + // edge being removed occurs twice in the adjacency / incidence + // lists + adjacent_vertices = igraph_adjlist_get(&adjlist, v); + incident_edges = igraph_inclist_get(&inclist, v); + nlen = igraph_vector_int_size(incident_edges); + for (igraph_int_t j = 0; j < nlen; j++) { + neighbor = VECTOR(*adjacent_vertices)[j]; + if (neighbor == v) { + /* should not happen as we did not ask for loop edges in + * the adjacency / incidence lists, but let's be defensive */ + continue; + } + + if (igraph_vector_int_search( + igraph_inclist_get(&inclist, neighbor), + 0, + VECTOR(*incident_edges)[j], + &index + )) { + igraph_vector_int_remove_fast(igraph_adjlist_get(&adjlist, neighbor), index); + igraph_vector_int_remove_fast(igraph_inclist_get(&inclist, neighbor), index); + } + } + igraph_vector_int_clear(adjacent_vertices); + igraph_vector_int_clear(incident_edges); + } else { + // Case 3(b) from the paper: v is adjacent to at least one of + // the sampled clusters + + // add the edge connecting to the lightest sampled cluster + edge = VECTOR(lightest_eid)[nearest_neighboring_sampled_cluster]; + ADD_EDGE_TO_SPANNER; + + // 'lightest_sampled_weight' is the weight of the lightest edge connecting v to + // one of the sampled clusters. This is where v will belong in + // the new clustering. + lightest_sampled_weight = VECTOR(lightest_weight)[nearest_neighboring_sampled_cluster]; + VECTOR(new_clustering)[v] = nearest_neighboring_sampled_cluster; + + // Add to the spanner light edges with weight less than 'lightest_sampled_weight' + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + if (VECTOR(lightest_weight)[j] < lightest_sampled_weight) { + edge = VECTOR(lightest_eid)[j]; + ADD_EDGE_TO_SPANNER; + } + } + + // Remove edges to centers with edge weight less than 'lightest_sampled_weight' + adjacent_vertices = igraph_adjlist_get(&adjlist, v); + incident_edges = igraph_inclist_get(&inclist, v); + nlen = igraph_vector_int_size(incident_edges); + for (igraph_int_t j = 0; j < nlen; j++) { + neighbor = VECTOR(*adjacent_vertices)[j]; + if (neighbor == v) { + /* should not happen as we did not ask for loop edges in + * the adjacency / incidence lists, but let's be defensive */ + continue; + } + + cluster = VECTOR(clustering)[neighbor]; + weight = VECTOR(lightest_weight)[cluster]; + if ((cluster == nearest_neighboring_sampled_cluster) || (weight < lightest_sampled_weight)) { + edge = VECTOR(*incident_edges)[j]; + + if (igraph_vector_int_search( + igraph_inclist_get(&inclist, neighbor), 0, edge, &index + )) { + igraph_vector_int_remove_fast(igraph_adjlist_get(&adjlist, neighbor), index); + igraph_vector_int_remove_fast(igraph_inclist_get(&inclist, neighbor), index); + } + + igraph_vector_int_remove_fast(adjacent_vertices, j); + igraph_vector_int_remove_fast(incident_edges, j); + + j--; + nlen--; + } + } + } + + // We don't need lightest_eids and lightest_weights anymore so + // clear them in O(d) time + clear_lightest_edges_to_clusters( + &dirty_vids, &lightest_eid, &lightest_weight + ); + } + + // Commit the new clustering + igraph_vector_int_update(&clustering, &new_clustering); /* reserved */ + + // Remove intra-cluster edges + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + adjacent_vertices = igraph_adjlist_get(&adjlist, v); + incident_edges = igraph_inclist_get(&inclist, v); + nlen = igraph_vector_int_size(incident_edges); + for (igraph_int_t j = 0; j < nlen; j++) { + neighbor = VECTOR(*adjacent_vertices)[j]; + edge = VECTOR(*incident_edges)[j]; + + if (VECTOR(clustering)[neighbor] == VECTOR(clustering)[v]) { + /* We don't need to bother with removing the other copy + * of the edge from the incidence lists (and the corresponding + * vertices from the adjacency lists) because we will find + * them anyway as we are iterating over all nodes */ + igraph_vector_int_remove_fast(adjacent_vertices, j); + igraph_vector_int_remove_fast(incident_edges, j); + j--; + nlen--; + } + } + } + } + + // Phase 2: vertex_clustering joining + for (igraph_int_t v = 0; v < no_of_nodes; v++) { + if (VECTOR(clustering)[v] != -1) { + IGRAPH_CHECK(collect_lightest_edges_to_clusters( + &adjlist, + &inclist, + weights, + &clustering, + /* is_cluster_sampled = */ NULL, + v, + &lightest_eid, + &lightest_weight, + &dirty_vids, + NULL + )); + for (igraph_int_t j = 0; j < no_of_nodes; j++) { + edge = VECTOR(lightest_eid)[j]; + if (edge != -1) { + ADD_EDGE_TO_SPANNER; + } + } + clear_lightest_edges_to_clusters(&dirty_vids, &lightest_eid, &lightest_weight); + } + } + + // Free memory + igraph_vector_int_destroy(&dirty_vids); + igraph_bitset_destroy(&is_edge_in_spanner); + igraph_bitset_destroy(&is_cluster_sampled); + igraph_vector_int_destroy(&new_clustering); + igraph_vector_destroy(&lightest_weight); + igraph_vector_int_destroy(&lightest_eid); + igraph_vector_int_destroy(&clustering); + igraph_adjlist_destroy(&adjlist); + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/unweighted.c b/src/paths/unweighted.c new file mode 100644 index 0000000..ad691c0 --- /dev/null +++ b/src/paths/unweighted.c @@ -0,0 +1,703 @@ +/* + igraph library. + Copyright (C) 2005-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/interruption.h" +#include "paths/paths_internal.h" + +/** + * \ingroup structural + * \function igraph_distances_cutoff + * \brief Length of the shortest paths between vertices, with cutoff. + * + * This function is similar to \ref igraph_distances(), but + * paths longer than \p cutoff will not be considered. + * + * \param graph The graph object. + * \param weights Optional edge weights. If \c NULL, the graph is considered + * unweighted, i.e. all edge weights are equal to 1. + * \param res The result of the calculation, a matrix. A pointer to an + * initialized matrix, to be more precise. The matrix will be + * resized if needed. It will have the same + * number of rows as the length of the \p from + * argument, and its number of columns is the number of + * vertices in the \p to argument. One row of the matrix shows the + * distances from/to a given vertex to the ones in \p to. + * For the unreachable vertices \c IGRAPH_INFINITY is returned. + * \param from The source vertices._d + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for + * the computation. + * \endclist + * \param cutoff The maximal length of paths that will be considered. + * When the distance of two vertices is greater than this value, + * it will be returned as \c IGRAPH_INFINITY. Negative cutoffs and + * \ref IGRAPH_UNLIMITED are treated as infinity. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary + * data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(s |E| + |V|), where s is the number of source vertices to use, + * and |V| and |E| are the number of vertices and edges in the graph. + * + * \sa \ref igraph_distances_dijkstra_cutoff() for the weighted version with non-negative + * weights. + * + * \example examples/simple/distances.c + */ +igraph_error_t igraph_distances_cutoff( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_real_t cutoff) { + + if (weights == NULL) { + /* Unweighted distances */ + return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, cutoff); + } else { + /* Dijkstra's algorithm; will return an error if there are negative weights */ + return igraph_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, cutoff); + } +} + +igraph_error_t igraph_i_distances_unweighted_cutoff( + const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_real_t cutoff) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_from, no_of_to; + igraph_int_t *already_counted; + igraph_adjlist_t adjlist; + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_bool_t all_to; + + igraph_int_t i, j; + igraph_vit_t fromvit, tovit; + igraph_vector_int_t indexv; + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + already_counted = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(already_counted, "Insufficient memory for graph distance calculation."); + IGRAPH_FINALLY(igraph_free, already_counted); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + all_to = igraph_vs_is_all(&to); + if (all_to) { + no_of_to = no_of_nodes; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&indexv, no_of_nodes); + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + for (i = 0; !IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit)) { + igraph_int_t v = IGRAPH_VIT_GET(tovit); + if (VECTOR(indexv)[v]) { + IGRAPH_ERROR("Target vertex list must not have any duplicates.", + IGRAPH_EINVAL); + } + VECTOR(indexv)[v] = ++i; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + igraph_matrix_fill(res, IGRAPH_INFINITY); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + igraph_int_t reached = 0; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, IGRAPH_VIT_GET(fromvit))); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + already_counted[ IGRAPH_VIT_GET(fromvit) ] = i + 1; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t act = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + if (cutoff >= 0 && actdist > cutoff) { + continue; + } + + if (all_to) { + MATRIX(*res, i, act) = actdist; + } else { + if (VECTOR(indexv)[act]) { + MATRIX(*res, i, VECTOR(indexv)[act] - 1) = actdist; + reached++; + if (reached == no_of_to) { + igraph_dqueue_int_clear(&q); + break; + } + } + } + + neis = igraph_adjlist_get(&adjlist, act); + igraph_int_t nei_count = igraph_vector_int_size(neis); + for (j = 0; j < nei_count; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (already_counted[neighbor] == i + 1) { + continue; + } + already_counted[neighbor] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + } + } + } + + /* Clean */ + if (!all_to) { + igraph_vit_destroy(&tovit); + igraph_vector_int_destroy(&indexv); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_FREE(already_counted); + igraph_dqueue_int_destroy(&q); + igraph_vit_destroy(&fromvit); + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_distances + * \brief Length of the shortest paths between vertices. + * + * \param graph The graph object. + * \param weights Optional edge weights. If \c NULL, the graph is considered + * unweighted, i.e. all edge weights are 1. + * \param res The result of the calculation, a matrix. A pointer to an + * initialized matrix, to be more precise. The matrix will be + * resized if needed. It will have the same + * number of rows as the length of the \p from + * argument, and its number of columns is the number of + * vertices in the \p to argument. One row of the matrix shows the + * distances from/to a given vertex to the ones in \p to. + * For the unreachable vertices \c IGRAPH_INFINITY is returned. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for + * the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary + * data. + * \cli IGRAPH_EINVVID + * invalid vertex ID passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(n(|V|+|E|)), + * n is the number of vertices to calculate, + * |V| and |E| are the number of vertices and edges in the graph. + * + * \sa \ref igraph_get_shortest_paths() to get the paths themselves, + * \ref igraph_distances_dijkstra() for the weighted version with non-negative + * weights, \ref igraph_distances_bellman_ford() if you also have negative + * weights. + * + * \example examples/simple/distances.c + */ +igraph_error_t igraph_distances( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode) { + + igraph_real_t vcount_real = igraph_vcount(graph); + igraph_real_t ecount_real = igraph_ecount(graph); + igraph_real_t ecount_threshold; + igraph_int_t from_size; + igraph_bool_t negative_weights = false; + + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + /* Edge count threshold for using Floyd-Warshall for all-to-all case. + * Based on experiments, this is faster than Dijkstra for densities + * above 0.1, provided that the graph has more than about 50 vertices. + * See also https://github.com/igraph/igraph/issues/2822 */ + ecount_threshold = vcount_real * vcount_real * 0.1; + if (ecount_threshold < 250) ecount_threshold = 250; + + if (!weights) { + /* Unweighted case */ + return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, -1); + } else if (igraph_vs_is_all(&from) && ecount_real > ecount_threshold) { + /* All-to-all distances with dense graph */ + return igraph_distances_floyd_warshall(graph, res, from, to, weights, mode, IGRAPH_FLOYD_WARSHALL_AUTOMATIC); + } else if (!negative_weights) { + /* Non-negative weights: use Dijkstra's algorithm. */ + return igraph_i_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, -1); + } else { + /* Negative weights: use Bellman-Ford or Johnson's algorithm. + * + * In the undirected case we always use Bellman-Ford. Normally, a negative weight + * undirected edge triggers an error as it is effectively a negative cycle. + * However, with Bellman-Ford the negative edge might be avoided if it is + * not reachable from the 'from' vertices. In cotrast, Johnson will always raise + * an error. + */ + if (mode != IGRAPH_ALL) { + IGRAPH_CHECK(igraph_vs_size(graph, &from, &from_size)); + if (from_size > 100) { + return igraph_i_distances_johnson(graph, res, from, to, weights, mode); + } + } + return igraph_i_distances_bellman_ford(graph, res, from, to, weights, mode); + } +} + +/** + * \ingroup structural + * \function igraph_get_shortest_paths + * \brief Shortest paths from a vertex. + * + * Finds unweighted shortest paths from a single source vertex to the specified + * sets of target vertices. If there is more than one geodesic between two vertices, + * this function gives only one of them. Use \ref igraph_get_all_shortest_paths() + * to find \em all shortest paths. + * + * \param graph The graph object. + * \param weights Optional edge weights. If \c NULL, the graph is considered + * unweighted, i.e. all edge weights are equal to 1. + * \param vertices The result, the IDs of the vertices along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param edges The result, the IDs of the edges along the paths. + * This is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param from The ID of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param parents A pointer to an initialized igraph vector or \c NULL. + * If not \c NULL, a vector containing the parent of each vertex in + * the single source shortest path tree is returned here. The + * parent of vertex \c i in the tree is the vertex from which vertex \c i + * was reached. The parent of the start vertex (in the \p from + * argument) is -1. If the parent is -2, it means + * that the given vertex was not reached from the source during the + * search. Note that the search terminates if all the vertices in + * \p to are reached. + * \param inbound_edges A pointer to an initialized igraph vector or \c NULL. + * If not \c NULL, a vector containing the inbound edge of each vertex in + * the single source shortest path tree is returned here. The + * inbound edge of vertex \c i in the tree is the edge via which vertex \c i + * was reached. The start vertex and vertices that were not reached + * during the search will have -1 in the corresponding entry of the + * vector. Note that the search terminates if all the vertices in + * \p to are reached. + * + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex ID + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|+|E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \sa \ref igraph_distances() if you only need the path lengths but + * not the paths themselves; \ref igraph_get_shortest_paths_dijkstra() + * for the weighted version; \ref igraph_get_all_shortest_paths() to + * return all shortest paths between (source, target) pairs. + * + * \example examples/simple/igraph_get_shortest_paths.c + */ +igraph_error_t igraph_get_shortest_paths( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + igraph_bool_t negative_weights; + IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights)); + + if (weights == NULL) { + return igraph_i_get_shortest_paths_unweighted(graph, vertices, edges, from, to, mode, parents, inbound_edges); + } else if (!negative_weights) { + /* Dijkstra's algorithm */ + return igraph_i_get_shortest_paths_dijkstra(graph, vertices, edges, from, to, weights, mode, parents, inbound_edges); + } else { + /* Negative weights; will use Bellman-Ford algorithm */ + return igraph_i_get_shortest_paths_bellman_ford(graph, vertices, edges, from, to, weights, mode, parents, inbound_edges); + } +} + +igraph_error_t igraph_i_get_shortest_paths_unweighted( + const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + /* TODO: use inclist_t if to is long (longer than 1?) */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t *parent_eids; + + igraph_dqueue_int_t q = IGRAPH_DQUEUE_NULL; + + igraph_int_t i, j, vsize; + igraph_vector_int_t tmp = IGRAPH_VECTOR_NULL; + + igraph_vit_t vit; + + igraph_int_t to_reach; + igraph_int_t reached = 0; + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_resize(vertices, IGRAPH_VIT_SIZE(vit))); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edges, IGRAPH_VIT_SIZE(vit))); + } + + parent_eids = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(parent_eids, "Insufficient memory for shortest path calculation."); + IGRAPH_FINALLY(igraph_free, parent_eids); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + + /* Mark the vertices we need to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (parent_eids[ IGRAPH_VIT_GET(vit) ] == 0) { + parent_eids[ IGRAPH_VIT_GET(vit) ] = -1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + /* Meaning of parent_eids[i]: + * + * - If parent_eids[i] < 0, it means that vertex i has to be reached and has not + * been reached yet. + * + * - If parent_eids[i] = 0, it means that vertex i does not have to be reached and + * it has not been reached yet. + * + * - If parent_eids[i] = 1, it means that vertex i is the start vertex. + * + * - Otherwise, parent_eids[i] is the ID of the edge from which vertex i was + * reached plus 2. + */ + + IGRAPH_CHECK(igraph_dqueue_int_push(&q, from + 1)); + if (parent_eids[ from ] < 0) { + reached++; + } + parent_eids[ from ] = 1; + + while (!igraph_dqueue_int_empty(&q) && reached < to_reach) { + igraph_int_t act = igraph_dqueue_int_pop(&q) - 1; + + IGRAPH_CHECK(igraph_incident(graph, &tmp, act, mode, IGRAPH_LOOPS)); + vsize = igraph_vector_int_size(&tmp); + for (j = 0; j < vsize; j++) { + igraph_int_t edge = VECTOR(tmp)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, act); + if (parent_eids[neighbor] > 0) { + continue; + } else if (parent_eids[neighbor] < 0) { + reached++; + } + parent_eids[neighbor] = edge + 2; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor + 1)); + } + } + + if (reached < to_reach) { + IGRAPH_WARNING("Couldn't reach some vertices"); + } + + /* Create `parents' if needed */ + if (parents) { + IGRAPH_CHECK(igraph_vector_int_resize(parents, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*parents)[i] = -2; + } else if (parent_eids[i] == 1) { + /* i is the start vertex */ + VECTOR(*parents)[i] = -1; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 2 */ + VECTOR(*parents)[i] = IGRAPH_OTHER(graph, parent_eids[i] - 2, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_int_resize(inbound_edges, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (parent_eids[i] <= 1) { + /* i was not reached or i is the start vertex */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 2 */ + VECTOR(*inbound_edges)[i] = parent_eids[i] - 2; + } + } + } + + /* Create `vertices' and `edges' if needed */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(vit), j = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), j++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + igraph_vector_int_t *vvec = 0, *evec = 0; + if (vertices) { + vvec = igraph_vector_int_list_get_ptr(vertices, j); + igraph_vector_int_clear(vvec); + } + if (edges) { + evec = igraph_vector_int_list_get_ptr(edges, j); + igraph_vector_int_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + if (parent_eids[node] > 0) { + igraph_int_t act = node; + igraph_int_t size = 0; + igraph_int_t edge; + while (parent_eids[act] > 1) { + size++; + edge = parent_eids[act] - 2; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec) { + IGRAPH_CHECK(igraph_vector_int_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = node; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_int_resize(evec, size)); + } + act = node; + while (parent_eids[act] > 1) { + size--; + edge = parent_eids[act] - 2; + act = IGRAPH_OTHER(graph, edge, act); + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + } + + /* Clean */ + IGRAPH_FREE(parent_eids); + igraph_dqueue_int_destroy(&q); + igraph_vector_int_destroy(&tmp); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_shortest_path + * \brief Shortest path from one vertex to another one. + * + * Calculates and returns a single unweighted shortest path from a + * given vertex to another one. If there is more than one shortest + * path between the two vertices, then an arbitrary one is returned. + * + * + * This function is a wrapper to \ref igraph_get_shortest_paths() + * for the special case when only one target vertex is considered. + * + * \param graph The input graph, it can be directed or + * undirected. Directed paths are considered in directed + * graphs. + * \param weights Optional edge weights. If \c NULL, the graph is considered + * unweighted, i.e. all edge weights are equal to 1. + * \param vertices Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex IDs along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the edge IDs along the + * path are stored here. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_get_shortest_paths() for the version with more target + * vertices. + */ +igraph_error_t igraph_get_shortest_path( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, igraph_int_t to, + igraph_neimode_t mode) { + + igraph_vector_int_list_t vertices2, *vp = &vertices2; + igraph_vector_int_list_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &vertices2); + } else { + vp = NULL; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &edges2); + } else { + ep = NULL; + } + + IGRAPH_CHECK(igraph_get_shortest_paths(graph, weights, vp, ep, from, + igraph_vss_1(to), mode, NULL, NULL)); + + /* We use the constant time vector_swap() instead of the linear-time vector_update() to move the + result to the output parameter. */ + if (edges) { + igraph_vector_int_swap(edges, igraph_vector_int_list_get_ptr(&edges2, 0)); + igraph_vector_int_list_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + igraph_vector_int_swap(vertices, igraph_vector_int_list_get_ptr(&vertices2, 0)); + igraph_vector_int_list_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/voronoi.c b/src/paths/voronoi.c new file mode 100644 index 0000000..4646b28 --- /dev/null +++ b/src/paths/voronoi.c @@ -0,0 +1,419 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_nongraph.h" +#include "igraph_random.h" + +#include "core/indheap.h" +#include "core/interruption.h" + +static igraph_error_t igraph_i_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_t *mindist, + const igraph_vector_int_t *generators, + igraph_neimode_t mode, + igraph_voronoi_tiebreaker_t tiebreaker) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_generators = igraph_vector_int_size(generators); + igraph_adjlist_t al; + igraph_dqueue_int_t q; + + igraph_vector_int_t already_counted; + + /* tie_count[vid] is the number of generators that vid is an equal distance from. + * This value is needed to pick one of these generators uniformly at random + * without needing to store all of them. */ + igraph_vector_int_t tie_count; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&already_counted, no_of_nodes); + + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&tie_count, no_of_nodes); + } + + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_fill(membership, -1); + + IGRAPH_CHECK(igraph_vector_resize(mindist, no_of_nodes)); + igraph_vector_fill(mindist, IGRAPH_INFINITY); + + /* Loop through all generator points and compute shortest paths to all other vertices. + * As we go, we keep track of the shortest distance from any generator to each vertex + * in 'mindist'. If we find that the distance from the current generator to a vertex + * is shorter than what was recorded so far in 'mindist', we update 'mindist' and + * assign that vertex to the current generator. + */ + for (igraph_int_t i=0; i < no_of_generators; i++) { + igraph_int_t g = VECTOR(*generators)[i]; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* BFS-based unweighted shortest path implementation */ + + igraph_dqueue_int_clear(&q); + + VECTOR(already_counted)[g] = i+1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, g)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t vid = igraph_dqueue_int_pop(&q); + igraph_int_t dist = igraph_dqueue_int_pop(&q); + + /* Attention! This must be igraph_real_t, not igraph_int_t + * because later it will be compared with another igraph_real_t + * whose value may be infinite. */ + igraph_real_t md = VECTOR(*mindist)[vid]; + + if (dist > md) { + /* This vertex is reachable at a shorter distance from + * another generator. Thus all its descendants in the shortest + * path tree are also reachable at a shorter distance from the + * other generator than from the current one. Therefore + * we do not need to search further from here. */ + continue; + } else if (dist < md) { + /* This vertex is closest to the current generator so far. + * Assign it to the current partition. */ + VECTOR(*mindist)[vid] = dist; + VECTOR(*membership)[vid] = i; + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + VECTOR(tie_count)[vid] = 1; + } + } else { /* md == dist, we have a tie */ + switch (tiebreaker) { + case IGRAPH_VORONOI_FIRST: + /* Never replace existing generator assignment. */ + break; + case IGRAPH_VORONOI_LAST: + /* Always replace existing generator assignment. */ + VECTOR(*membership)[vid] = i; + break; + case IGRAPH_VORONOI_RANDOM: + /* We replace the membership assignment with probability 1/k upon + * encountering the kth same-distance generator. This ensures + * that one of these generators is selected uniformly at random. */ + VECTOR(tie_count)[vid]++; + if (RNG_UNIF01() < 1.0 / VECTOR(tie_count)[vid]) { + VECTOR(*membership)[vid] = i; + } + break; + } + } + + igraph_vector_int_t *neis = igraph_adjlist_get(&al, vid); + igraph_int_t nei_count = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nei_count; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + if (VECTOR(already_counted)[neighbor] == i + 1) { + continue; + } + VECTOR(already_counted)[neighbor] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, dist + 1)); + } + } + } + + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + igraph_vector_int_destroy(&tie_count); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&already_counted); + igraph_dqueue_int_destroy(&q); + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +static igraph_error_t igraph_i_voronoi_dijkstra( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_t *mindist, + const igraph_vector_int_t *generators, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_voronoi_tiebreaker_t tiebreaker) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_generators = igraph_vector_int_size(generators); + igraph_inclist_t il; + igraph_2wheap_t q; + + /* tie_count[vid] is the number of generators that vid is an equal distance from. + * We use this value to be able to randomly select one of the generators. */ + igraph_vector_int_t tie_count; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (no_of_edges > 0) { + igraph_real_t min = igraph_vector_min(weights); + if (min < 0) { + IGRAPH_ERRORF("Weight vector must be non-negative, got %g.", IGRAPH_EINVAL, min); + } else if (isnan(min)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_CHECK(igraph_2wheap_init(&q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &q); + + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + IGRAPH_VECTOR_INT_INIT_FINALLY(&tie_count, no_of_nodes); + } + + IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes)); + igraph_vector_int_fill(membership, -1); + + IGRAPH_CHECK(igraph_vector_resize(mindist, no_of_nodes)); + igraph_vector_fill(mindist, IGRAPH_INFINITY); + + /* Loop through all generator points and compute shortest paths to all other vertices. + * As we go, we keep track of the shortest distance from any generator to each vertex + * in 'mindist'. If we find that the distance from the current generator to a vertex + * is shorter than what was recorded so far in 'mindist', we update 'mindist' and + * assign that vertex to the current generator. + */ + for (igraph_int_t i=0; i < no_of_generators; i++) { + igraph_int_t g = VECTOR(*generators)[i]; + + /* Weighted shortest path implementation using Dijkstra's algorithm */ + + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_2wheap_clear(&q); + + /* We store negative distances in the maximum heap. Since some systems + * distinguish between -0.0 and +0.0, we need -0.0 to ensure +0.0 as + * the final result. */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, g, -0.0)); + + while (!igraph_2wheap_empty(&q)) { + igraph_int_t vid = igraph_2wheap_max_index(&q); + igraph_real_t dist = -igraph_2wheap_deactivate_max(&q); + + igraph_real_t md = VECTOR(*mindist)[vid]; + + int cmp_result = igraph_cmp_epsilon(dist, md, IGRAPH_SHORTEST_PATH_EPSILON); + if (cmp_result > 0) { /* dist > md */ + /* This vertex is reachable at a shorter distance from + * another generator. Thus all its descendants in the shortest + * path tree are also reachable at a shorter distance from the + * other generator than from the current one. Therefore + * we do not need to search further from here. */ + continue; + } else if (cmp_result < 0) { /* dist < md */ + /* This vertex is closest to the current generator so far. + * Assign it to the current partition. */ + VECTOR(*mindist)[vid] = dist; + VECTOR(*membership)[vid] = i; + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + VECTOR(tie_count)[vid] = 1; + } + } else { /* md == dist, we have a tie */ + switch (tiebreaker) { + case IGRAPH_VORONOI_FIRST: + /* Never replace existing generator assignment. */ + break; + case IGRAPH_VORONOI_LAST: + /* Always replace existing generator assignment. */ + VECTOR(*membership)[vid] = i; + break; + case IGRAPH_VORONOI_RANDOM: + /* We replace the membership assignment with probability 1/k upon + * encountering the kth same-distance generator. This ensures + * that one of these generators is selected uniformly at random. */ + VECTOR(tie_count)[vid]++; + if (RNG_UNIF01() < 1.0 / VECTOR(tie_count)[vid]) { + VECTOR(*membership)[vid] = i; + } + break; + } + } + + igraph_vector_int_t *inc_edges = igraph_inclist_get(&il, vid); + igraph_int_t inc_count = igraph_vector_int_size(inc_edges); + for (igraph_int_t j=0; j < inc_count; j++) { + igraph_int_t edge = VECTOR(*inc_edges)[j]; + igraph_real_t weight = VECTOR(*weights)[edge]; + + /* Optimization: do not follow infinite-weight edges. */ + if (weight == IGRAPH_INFINITY) { + continue; + } + + igraph_int_t to = IGRAPH_OTHER(graph, edge, vid); + igraph_real_t altdist = dist + weight; + + if (! igraph_2wheap_has_elem(&q, to)) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, to, -altdist)); + } else if (igraph_2wheap_has_active(&q, to)) { + igraph_real_t curdist = -igraph_2wheap_get(&q, to); + if (altdist < curdist) { + /* This is a shorter path */ + igraph_2wheap_modify(&q, to, -altdist); + } + } + } + } /* !igraph_2wheap_empty(&q) */ + } + + if (tiebreaker == IGRAPH_VORONOI_RANDOM) { + igraph_vector_int_destroy(&tie_count); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_2wheap_destroy(&q); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_voronoi + * \brief Voronoi partitioning of a graph. + * + * To obtain a Voronoi partitioning of a graph, we start with a set of generator + * vertices, which will define the partitions. Each vertex is assigned to the generator + * vertex from (or to) which it is closest. + * + * + * This function uses a BFS search for unweighted graphs and Dijkstra's algorithm + * for weights ones. + * + * \param graph The graph to partition. + * \param membership If not \c NULL, the Voronoi partition of each vertex + * will be stored here. membership[v] will be set to the index + * in \p generators of the generator vertex that \c v belongs to. For vertices + * that are not reachable from any generator, -1 is returned. + * \param distances If not \c NULL, the distance of each vertex to its respective + * generator will be stored here. For vertices which are not reachable from + * any generator, \c IGRAPH_INFINITY is returned. + * \param generators Vertex IDs of the generator vertices. + * \param weights The edge weights, interpreted as lengths in the shortest + * path calculation. All weights must be non-negative. + * \param mode In directed graphs, whether to compute distances \em from + * generator vertices to other vertices (\c IGRAPH_OUT), \em to generator + * vertices from other vertices (\c IGRAPH_IN), or ignore edge directions + * entirely (\c IGRAPH_ALL). + * \param tiebreaker Controls which generator vertex to assign a vertex to + * when it is at equal distance from/to multiple generator vertices. + * \clist + * \cli IGRAPH_VORONOI_FIRST assign the vertex to the first generator vertex. + * \cli IGRAPH_VORONOI_LAST assign the vertex to the last generator vertex. + * \cli IGRAPH_VORONOI_RANDOM assign the vertex to a random generator vertex. + * \endclist + * Note that \c IGRAPH_VORONOI_RANDOM does not guarantee that all partitions + * will be contiguous. For example, if 1 and 2 are chosen as generators for the + * graph 1-3, 2-3, 3-4, then 3 and 4 are at equal distance from + * both generators. If 3 is assigned to 2 but 4 is assigned to 1, then the + * partition {1, 4} will not induce a connected subgraph. + * \return Error code. + * + * Time complexity: In weighted graphs, O((log |S|) |E| (log |V|) + |V|), and in + * unweighted graphs O((log |S|) |E| + |V|), where |S| is the number of generator + * vertices, and |V| and |E| are the number of vertices and edges in the graph. + * + * \sa \ref igraph_distances(), \ref igraph_distances_dijkstra(). + */ +igraph_error_t igraph_voronoi( + const igraph_t *graph, + igraph_vector_int_t *membership, + igraph_vector_t *distances, + const igraph_vector_int_t *generators, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_voronoi_tiebreaker_t tiebreaker) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t *pmembership; + igraph_vector_int_t imembership; + igraph_vector_t *pdistances; + igraph_vector_t idistances; + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (tiebreaker != IGRAPH_VORONOI_FIRST && + tiebreaker != IGRAPH_VORONOI_LAST && + tiebreaker != IGRAPH_VORONOI_RANDOM) { + IGRAPH_ERROR("Invalid tiebreaker specification during Voronoi partitioning.", IGRAPH_EINVAL); + } + + if (! igraph_vector_int_isininterval(generators, 0, igraph_vcount(graph)-1)) { + IGRAPH_ERROR("Invalid vertex ID given as Voronoi generator.", IGRAPH_EINVVID); + } + + if (membership) { + pmembership = membership; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&imembership, no_of_nodes); + pmembership = &imembership; + } + + if (distances) { + pdistances = distances; + } else { + IGRAPH_VECTOR_INIT_FINALLY(&idistances, no_of_nodes); + pdistances = &idistances; + } + + if (weights) { + IGRAPH_CHECK(igraph_i_voronoi_dijkstra(graph, pmembership, pdistances, generators, weights, mode, tiebreaker)); + } else { + IGRAPH_CHECK(igraph_i_voronoi(graph, pmembership, pdistances, generators, mode, tiebreaker)); + } + + if (! distances) { + igraph_vector_destroy(&idistances); + IGRAPH_FINALLY_CLEAN(1); + } + + if (! membership) { + igraph_vector_int_destroy(&imembership); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/paths/widest_paths.c b/src/paths/widest_paths.c new file mode 100644 index 0000000..02dab7e --- /dev/null +++ b/src/paths/widest_paths.c @@ -0,0 +1,741 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + + +#include "igraph_paths.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/indheap.h" +#include "core/interruption.h" +#include "internal/utils.h" + +/** + * \function igraph_get_widest_paths + * \brief Widest paths from a single vertex. + * + * Calculates the widest paths from a single vertex to all other specified + * vertices, using a modified Dijkstra's algorithm. The width of a path is + * defined as the width of the narrowest edge in the path.If there is more than + * one path with the largest width between two vertices, this function gives + * only one of them. + * + * \param graph The graph object. + * \param vertices The result, the IDs of the vertices along the paths. This + * is a list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The list will be resized as needed. + * Supply a null pointer here if you don't need these vectors. + * \param edges The result, the IDs of the edges along the paths. This is a + * list of integer vectors where each element is an + * \ref igraph_vector_int_t object. The vector list will be resized as + * needed. Supply a null pointer here if you don't need these vectors. + * \param from The ID of the vertex from/to which the widest paths are + * calculated. + * \param to Vertex sequence with the IDs of the vertices to/from which the + * widest paths will be calculated. A vertex may be given multiple times. + * \param weights The edge weights, interpreted as widths. Edge weights can be + * negative, but must not be NaN. Edges with negative infinite weight are + * ignored. The weight vector is required: if \c NULL is passed, an error is + * raised. + * \param mode The type of widest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param parents A pointer to an initialized igraph vector or null. If not + * null, a vector containing the parent of each vertex in the single source + * widest path tree is returned here. The parent of vertex \c i in the tree + * is the vertex from which vertex \c i was reached. The parent of the start + * vertex (in the \p from argument) is -1. If the parent is -2, it means + * that the given vertex was not reached from the source during the search. + * The search terminates when all the vertices in \p to have been reached. + * \param inbound_edges A pointer to an initialized igraph vector or \c NULL. + * If not \c NULL, a vector containing the inbound edge of each vertex in + * the single source widest path tree is returned here. The inbound edge of + * vertex \c i in the tree is the edge via which vertex \c i was reached. + * The start vertex and vertices that were not reached during the search + * will have -1 in the corresponding entry of the vector. The search + * terminates when all the vertices in \p to have been reached. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex ID + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|E|log|E|+|V|), where |V| is the number of + * vertices in the graph and |E| is the number of edges + * + * \sa \ref igraph_widest_path_widths_dijkstra() or + * \ref igraph_widest_path_widths_floyd_warshall() if you only need the + * widths of the paths but not the paths themselves. + */ +igraph_error_t igraph_get_widest_paths(const igraph_t *graph, + igraph_vector_int_list_t *vertices, + igraph_vector_int_list_t *edges, + igraph_int_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_int_t *parents, + igraph_vector_int_t *inbound_edges) { + + /* Implementation details: This is a Dijkstra algorithm with a + binary heap, modified to support widest paths. The heap is indexed, + so it stores both the widest path to a vertex, as well as it's index. We + use a 2 way heap so that we can query indexes directly in the heap. + + To adapt a Dijkstra to handle widest path, instead of prioritising candidate + vertices with the minimum distance, we prioritise those with the maximum + width instead. When adding a vertex into our set of 'completed' vertices, we + update all neighbouring vertices with a width that is equal to the min of the + width to the current vertex and the width of the edge. + + We denote the widest path from a vertex to itself as infinity, and the widest + path from a vertex to a vertex it cannot reach as negative infinity. + */ + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vit_t vit; + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t widths; + igraph_int_t *parent_eids; + bool *is_target; + igraph_int_t i, to_reach; + + if (!weights) { + IGRAPH_ERROR("Weight vector is required.", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), ecount); + } + + if (igraph_vector_is_any_nan(weights)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_resize(vertices, IGRAPH_VIT_SIZE(vit))); + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_resize(edges, IGRAPH_VIT_SIZE(vit))); + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, vcount)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&widths, vcount); + igraph_vector_fill(&widths, -IGRAPH_INFINITY); + + parent_eids = IGRAPH_CALLOC(vcount, igraph_int_t); + IGRAPH_CHECK_OOM(parent_eids, "Insufficient memory for widest paths."); + IGRAPH_FINALLY(igraph_free, parent_eids); + + is_target = IGRAPH_CALLOC(vcount, bool); + IGRAPH_CHECK_OOM(is_target, "Insufficient memory for widest paths."); + IGRAPH_FINALLY(igraph_free, is_target); + + /* Mark the vertices we need to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (!is_target[ IGRAPH_VIT_GET(vit) ]) { + is_target[ IGRAPH_VIT_GET(vit) ] = true; + } else { + to_reach--; /* this vertex was given multiple times */ + } + } + + VECTOR(widths)[from] = IGRAPH_INFINITY; + parent_eids[from] = 0; + igraph_2wheap_push_with_index(&Q, from, IGRAPH_INFINITY); + + while (!igraph_2wheap_empty(&Q) && to_reach > 0) { + igraph_int_t nlen, maxnei = igraph_2wheap_max_index(&Q); + igraph_real_t maxwidth = igraph_2wheap_delete_max(&Q); + igraph_vector_int_t *neis; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (is_target[maxnei]) { + is_target[maxnei] = false; + to_reach--; + } + + /* Now check all neighbors of 'maxnei' for a wider path */ + neis = igraph_lazy_inclist_get(&inclist, maxnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (i = 0; i < nlen; i++) { + igraph_int_t edge = VECTOR(*neis)[i]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, maxnei); + igraph_real_t edgewidth = VECTOR(*weights)[edge]; + igraph_real_t altwidth = maxwidth < edgewidth ? maxwidth : edgewidth; + igraph_real_t curwidth = VECTOR(widths)[tto]; + if (edgewidth == -IGRAPH_INFINITY) { + /* Ignore edges with negative infinite weight */ + } else if (curwidth < 0) { + /* This is the first assigning a width to this vertex */ + VECTOR(widths)[tto] = altwidth; + parent_eids[tto] = edge + 1; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, altwidth)); + } else if (altwidth > curwidth) { + /* This is a wider path */ + VECTOR(widths)[tto] = altwidth; + parent_eids[tto] = edge + 1; + igraph_2wheap_modify(&Q, tto, altwidth); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + + if (to_reach > 0) { + IGRAPH_WARNING("Couldn't reach some vertices."); + } + + /* Create `parents' if needed */ + if (parents) { + IGRAPH_CHECK(igraph_vector_int_resize(parents, vcount)); + + for (i = 0; i < vcount; i++) { + if (i == from) { + /* i is the start vertex */ + VECTOR(*parents)[i] = -1; + } else if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*parents)[i] = -2; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*parents)[i] = IGRAPH_OTHER(graph, parent_eids[i] - 1, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_int_resize(inbound_edges, vcount)); + + for (i = 0; i < vcount; i++) { + if (parent_eids[i] <= 0) { + /* i was not reached */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = parent_eids[i] - 1 */ + VECTOR(*inbound_edges)[i] = parent_eids[i] - 1; + } + } + } + /* Reconstruct the widest paths based on vertex and/or edge IDs */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t v = IGRAPH_VIT_GET(vit); + igraph_int_t size, act, edge; + igraph_vector_int_t *vvec = 0, *evec = 0; + + if (vertices) { + vvec = igraph_vector_int_list_get_ptr(vertices, i); + igraph_vector_int_clear(vvec); + } + if (edges) { + evec = igraph_vector_int_list_get_ptr(edges, i); + igraph_vector_int_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + size = 0; + act = v; + while (parent_eids[act]) { + size++; + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec && (size > 0 || v == from)) { + IGRAPH_CHECK(igraph_vector_int_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = v; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_int_resize(evec, size)); + } + act = v; + while (parent_eids[act]) { + edge = parent_eids[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + size--; + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vector_destroy(&widths); + IGRAPH_FREE(is_target); + IGRAPH_FREE(parent_eids); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_widest_path + * \brief Widest path from one vertex to another one. + * + * Calculates a single widest path from a single vertex to another + * one, using Dijkstra's algorithm. + * + * This function is a special case (and a wrapper) to + * \ref igraph_get_widest_paths(). + * + * \param graph The input graph, it can be directed or undirected. + * \param vertices Pointer to an initialized vector or \c NULL. If not \c NULL, + * then the vertex IDs along the path are stored here, including the source + * and target vertices. + * \param edges Pointer to an initialized vector or \c NULL. If not \c NULL, + * then the edge IDs along the path are stored here. + * \param from The ID of the source vertex. + * \param to The ID of the target vertex. + * \param weights The edge weights, interpreted as widths. Edge weights can be + * negative, but must not be NaN. Edges with negative infinite weight are + * ignored. The weight vector is required: if \c NULL is passed, an error is + * raised. + * \param mode The type of widest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code. + * + * Time complexity: O(|E|log|E|+|V|), |V| is the number of vertices, + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_get_widest_paths() for the version with + * more target vertices. + */ +igraph_error_t igraph_get_widest_path(const igraph_t *graph, + igraph_vector_int_t *vertices, + igraph_vector_int_t *edges, + igraph_int_t from, + igraph_int_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_vector_int_list_t vertices2, *vp = &vertices2; + igraph_vector_int_list_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_list_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &vertices2); + } else { + vp = NULL; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_int_list_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_int_list_destroy, &edges2); + } else { + ep = NULL; + } + + IGRAPH_CHECK(igraph_get_widest_paths(graph, vp, ep, + from, igraph_vss_1(to), + weights, mode, 0, 0)); + + if (edges) { + IGRAPH_CHECK(igraph_vector_int_update(edges, igraph_vector_int_list_get_ptr(&edges2, 0))); + igraph_vector_int_list_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + IGRAPH_CHECK(igraph_vector_int_update(vertices, igraph_vector_int_list_get_ptr(&vertices2, 0))); + igraph_vector_int_list_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_widest_path_widths_floyd_warshall + * \brief Widths of widest paths between vertices. + * + * This function implements a modified Floyd-Warshall algorithm, to find the + * widest path widths between a set of source and target vertices. The width + * of a path is defined as the width of the narrowest edge in the path. + * + * + * This algorithm is primarily useful for all-pairs path widths in very dense + * graphs, as its running time is manily determined by the vertex count, and + * is not sensitive to the graph density. In sparse graphs, other methods such + * as Dijkstra's algorithm, implemented in + * \ref igraph_widest_path_widths_dijkstra() will perform better. + * + * + * Note that internally this function always computes the path width matrix + * for all pairs of vertices. The \p from and \p to parameters only serve + * to subset this matrix, but do not affect the time taken by the + * calculation. + * + * \param graph The input graph, can be directed. + * \param res An initialized matrix, the result will be written here. The + * matrix will be resized as needed. Each row will contain the widths + * from a single source to the vertices given in the \p to argument. + * Unreachable vertices have width \c -IGRAPH_INFINITY, and vertices + * have a width of \c IGRAPH_INFINITY to themselves. + * \param from The source vertices. + * \param to The target vertices. + * \param weights The edge weights, interpreted as widths. Edge weights can be + * negative, but must not be NaN. Edges with negative infinite weight are + * ignored. The weight vector is required: if \c NULL is passed, an error is + * raised. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|^3), where |V| is the number of vertices in the graph. + * + * \sa \ref igraph_widest_path_widths_dijkstra() for a variant that runs faster + * on sparse graphs. + */ +igraph_error_t igraph_widest_path_widths_floyd_warshall(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + /* Implementation Details: This is a modified Floyd Warshall algorithm + which computes the widest path between every pair of vertices. The key + difference between this and the regular Floyd Warshall is that instead + of updating the distance between two vertices to be the minimum of itself + and the distance through an intermediate vertex, we instead set the width + to be the maximum of itself and the width through the intermediate vertex. + + We denote the widest path from a vertex to itself as infinity, and the widest + path from a vertex to a vertex it cannot reach as negative infinity. + */ + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_bool_t in = false, out = false; + + if (! weights) { + IGRAPH_ERROR("Weight vector is required.", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), ecount); + } + + if (igraph_vector_is_any_nan(weights)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + switch (mode) { + case IGRAPH_ALL: + in = out = true; + break; + case IGRAPH_OUT: + out = true; + break; + case IGRAPH_IN: + in = true; + break; + default: + IGRAPH_ERROR("Invalid mode for Floyd-Warshall shortest path calculation.", IGRAPH_EINVMODE); + } + + /* Fill out adjacency matrix */ + IGRAPH_CHECK(igraph_matrix_resize(res, vcount, vcount)); + igraph_matrix_fill(res, -IGRAPH_INFINITY); + for (igraph_int_t i=0; i < vcount; i++) { + MATRIX(*res, i, i) = IGRAPH_INFINITY; + } + + for (igraph_int_t edge=0; edge < ecount; edge++) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + igraph_real_t w = VECTOR(*weights)[edge]; + + if (w == -IGRAPH_INFINITY) { + /* Ignore edges with infinite weight */ + continue; + } + + if (out && MATRIX(*res, from, to) < w) MATRIX(*res, from, to) = w; + if (in && MATRIX(*res, to, from) < w) MATRIX(*res, to, from) = w; + } + + /* Run modified Floyd Warshall */ + for (igraph_int_t k = 0; k < vcount; k++) { + /* Iterate in column-major order for better performance */ + for (igraph_int_t j = 0; j < vcount; j++) { + igraph_real_t width_kj = MATRIX(*res, k, j); + if (j == k || width_kj == -IGRAPH_INFINITY) continue; + + IGRAPH_ALLOW_INTERRUPTION(); + + for (igraph_int_t i = 0; i < vcount; i++) { + if (i == j || i == k) continue; + + /* alternative_width := min(A(i,k), A(k,j)) + A(i,j) := max(A(i,j), alternative_width) */ + + igraph_real_t altwidth = MATRIX(*res, i, k); + if (width_kj < altwidth) { + altwidth = width_kj; + } + if (altwidth > MATRIX(*res, i, j)) { + MATRIX(*res, i, j) = altwidth; + } + } + } + } + + IGRAPH_CHECK(igraph_i_matrix_subset_vertices(res, graph, from, to)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_widest_path_widths_dijkstra + * \brief Widths of widest paths between vertices. + * + * This function implements a modified Dijkstra's algorithm, which can find + * the widest path widths from a source vertex to all other vertices. The width + * of a path is defined as the width of the narrowest edge in the path. + * + * + * This function allows specifying a set of source and target vertices. The + * algorithm is run independently for each source and the results are retained + * only for the specified targets. This implementation uses a binary heap for + * efficiency. + * + * \param graph The input graph, can be directed. + * \param res An initialized matrix, the result will be written here. The + * matrix will be resized as needed. Each row will contain the widths + * from a single source to the vertices given in the \p to argument. + * Unreachable vertices have width \c -IGRAPH_INFINITY, and vertices + * have a width of \c IGRAPH_INFINITY to themselves. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a vertex twice + * or more. + * \param weights The edge weights, interpreted as widths. Edge weights can be + * negative, but must not be NaN. Edges with negative infinite weight are + * ignored. The weight vector is required: if \c NULL is passed, an error is + * raised. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(s*(|E|log|E|+|V|)), where |V| is the number of + * vertices in the graph, |E| the number of edges and s the number of sources. + * + * \sa \ref igraph_widest_path_widths_floyd_warshall() for a variant that runs + * faster on dense graphs. + */ +igraph_error_t igraph_widest_path_widths_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + /* Implementation details: This is a Dijkstra algorithm with a + binary heap, modified to support widest paths. The heap is indexed, + so it stores both the widest path to a vertex, as well as it's index. We + use a 2 way heap so that we can query indexes directly in the heap. + + To adapt a Dijkstra to handle widest path, instead of prioritising candidate + vertices with the minimum distance, we prioritise those with the maximum + width instead. When adding a vertex into our set of 'completed' vertices, we + update all neighbouring vertices with a width that is equal to the min of the + width to the current vertex and the width of the edge. + + We denote the widest path from a vertex to itself as infinity, and the widest + path from a vertex to a vertex it cannot reach as negative infinity. + */ + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_vit_t fromvit, tovit; + igraph_int_t no_of_from, no_of_to; + igraph_lazy_inclist_t inclist; + igraph_int_t i, j; + igraph_bool_t all_to; + igraph_vector_int_t indexv; + + if (!weights) { + IGRAPH_ERROR("Weight vector is required.", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), ecount); + } + + if (igraph_vector_is_any_nan(weights)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, vcount)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + all_to = igraph_vs_is_all(&to); + if (all_to) { + no_of_to = vcount; + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&indexv, vcount); + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + for (i = 0; !IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit)) { + igraph_int_t v = IGRAPH_VIT_GET(tovit); + if (VECTOR(indexv)[v]) { + IGRAPH_ERROR("Duplicate vertices in target vertex set, this is not allowed.", + IGRAPH_EINVAL); + } + VECTOR(indexv)[v] = ++i; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + igraph_matrix_fill(res, -IGRAPH_INFINITY); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + + igraph_int_t reached = 0; + igraph_int_t source = IGRAPH_VIT_GET(fromvit); + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, IGRAPH_INFINITY); + + while (!igraph_2wheap_empty(&Q)) { + igraph_int_t maxnei = igraph_2wheap_max_index(&Q); + igraph_real_t maxwidth = igraph_2wheap_deactivate_max(&Q); + igraph_vector_int_t *neis; + igraph_int_t nlen; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (all_to) { + MATRIX(*res, i, maxnei) = maxwidth; + } else { + if (VECTOR(indexv)[maxnei]) { + MATRIX(*res, i, VECTOR(indexv)[maxnei] - 1) = maxwidth; + reached++; + if (reached == no_of_to) { + igraph_2wheap_clear(&Q); + break; + } + } + } + + /* Now check all neighbors of 'maxnei' for a wider path*/ + neis = igraph_lazy_inclist_get(&inclist, maxnei); + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t tto = IGRAPH_OTHER(graph, edge, maxnei); + igraph_real_t edgewidth = VECTOR(*weights)[edge]; + igraph_real_t altwidth = maxwidth < edgewidth ? maxwidth : edgewidth; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curwidth = active ? igraph_2wheap_get(&Q, tto) : IGRAPH_INFINITY; + if (edgewidth == -IGRAPH_INFINITY) { + /* Ignore edges with negative infinite weight */ + } else if (!has) { + /* This is the first time assigning a width to this vertex */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, altwidth)); + } else if (altwidth > curwidth) { + /* This is a wider path */ + igraph_2wheap_modify(&Q, tto, altwidth); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + } /* !IGRAPH_VIT_END(fromvit) */ + + if (!all_to) { + igraph_vit_destroy(&tovit); + igraph_vector_int_destroy(&indexv); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&fromvit); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/basic_properties.c b/src/properties/basic_properties.c new file mode 100644 index 0000000..d6e457b --- /dev/null +++ b/src/properties/basic_properties.c @@ -0,0 +1,406 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" + +#include "igraph_interface.h" + +/** + * \section about_structural + * + * These functions usually calculate some structural property + * of a graph, like its diameter, the degree of the nodes, etc. + */ + +/** + * \function igraph_density + * \brief Calculate the density of a graph. + * + * The density of a graph is simply the ratio of the actual number of its + * edges and the largest possible number of edges it could have. + * The maximum number of edges depends on interpretation: are vertices + * allowed to have a connection to themselves? This is controlled by the + * \p loops parameter. + * + * + * The classic definition of the density is formulated for unweighted + * graphs without multi-edges. This function allows multigraphs and + * weighted graphs as well. In this case, it computes the ratio of the + * total edge weight to the largest possible number of adjacent vertex + * pairs the graph could have. This value may be larger than 1. + * + * + * If you need the density concept for simple graphs, make sure to + * eliminate any multi-edges appropriately. This can be done using + * \ref igraph_simplify(). + * + * \param graph The input graph object. It must not have parallel edges. + * \param res Pointer to a real number, the result will be stored here. + * \param weights Vector of edge weights. Pass \c NULL to to perform + * an unweighted density calculation. + * \param loops Boolean constant, whether to include self-loops in the + * calculation. If this constant is \c true then + * loop edges are thought to be possible in the graph (this does not + * necessarily mean that the graph really contains any loops). If + * this is \c false then the result is only correct if the graph does not + * contain loops. This function does not check if loops are actually + * present. + * \return Error code. + * + * Time complexity: O(1). + */ +igraph_error_t igraph_density( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *res, + igraph_bool_t loops) { + + const igraph_bool_t directed = igraph_is_directed(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_real_t vcount = (igraph_real_t) igraph_vcount(graph); /* note real type */ + igraph_real_t total_weight; + + if (vcount == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + if (weights) { + if (igraph_vector_size(weights) != ecount) { + IGRAPH_ERROR("Weight vector length does not match edge count.", IGRAPH_EINVAL); + } + total_weight = igraph_vector_sum(weights); + } else { + total_weight = ecount; + } + + if (!loops) { + if (vcount == 1) { + *res = IGRAPH_NAN; + } else if (directed) { + *res = total_weight / vcount / (vcount - 1); + } else { + *res = total_weight / vcount * 2.0 / (vcount - 1); + } + } else { + if (directed) { + *res = total_weight / vcount / vcount; + } else { + *res = total_weight / vcount * 2.0 / (vcount + 1); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_mean_degree + * \brief The mean degree of a graph. + * + * This is a convenience function that computes the average of all vertex + * degrees. In directed graphs, the average of out-degrees and in-degrees is + * the same; this is the number that is returned. For the null graph, which + * has no vertices, NaN is returned. + * + * \param graph The input graph object. + * \param res Pointer to a real number, the result will be stored here. + * \param loops Whether to consider self-loops during the calculation. + * \return Error code. + * + * Time complexity: O(1) if self-loops are considered, + * O(|E|) where |E| is the number of edges if self-loops are ignored. + */ +igraph_error_t igraph_mean_degree(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t loops) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + if (no_of_nodes == 0) { + *res = IGRAPH_NAN; + return IGRAPH_SUCCESS; + } + + if (! loops) { + igraph_int_t loop_count; + IGRAPH_CHECK(igraph_count_loops(graph, &loop_count)); + no_of_edges -= loop_count; + } + + *res = (directed ? 1.0 : 2.0) * (igraph_real_t) no_of_edges / (igraph_real_t) no_of_nodes; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_diversity + * \brief Structural diversity index of the vertices. + * + * This measure was defined in Nathan Eagle, Michael Macy and Rob + * Claxton: Network Diversity and Economic Development, Science 328, + * 1029--1031, 2010. + * + * + * It is simply the (normalized) Shannon entropy of the + * incident edges' weights. + * D(i) = H(i) / log(k[i]), + * and + * H(i) = -sum(p[i,j] log(p[i,j]), j=1..k[i]), + * where p[i,j] = w[i,j] / sum(w[i,l], l=1..k[i]), + * k[i] is the (total) degree of vertex \c i, + * and w[i,j] is the weight of the edge(s) between + * vertex \c i and \c j. The diversity of isolated vertices will be NaN + * (not-a-number), while that of vertices with a single connection + * will be zero. + * + * + * The measure works only if the graph is undirected and has no multiple edges. + * If the graph has multiple edges, simplify it first using \ref + * igraph_simplify(). If the graph is directed, convert it into an undirected + * graph with \ref igraph_to_undirected() . + * + * \param graph The undirected input graph. + * \param weights The edge weights, in the order of the edge IDs, must + * have appropriate length. Weights must be non-negative. + * \param res An initialized vector, the results are stored here. + * \param vids Vertex selector that specifies the vertices which to calculate + * the measure. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear. + * + */ +igraph_error_t igraph_diversity(const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, const igraph_vs_t vids) { + + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t k, i; + igraph_vector_int_t incident; + igraph_bool_t has_multiple; + igraph_vit_t vit; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Diversity measure works with undirected graphs only.", IGRAPH_EINVAL); + } + + if (!weights) { + IGRAPH_ERROR("Edge weights must be given.", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid edge weight vector length.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple)); + if (has_multiple) { + IGRAPH_ERROR("Diversity measure works only if the graph has no multiple edges.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&incident, 10); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + igraph_vector_clear(res); + IGRAPH_CHECK(igraph_vector_reserve(res, IGRAPH_VIT_SIZE(vit))); + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + igraph_real_t d; + igraph_int_t v = IGRAPH_VIT_GET(vit); + + IGRAPH_CHECK(igraph_incident(graph, &incident, v, IGRAPH_ALL, IGRAPH_LOOPS)); + k = igraph_vector_int_size(&incident); /* degree */ + + /* + * Non-normalized diversity is defined as + * d = -sum_i w_i/s log (w_i/s) + * where s = sum_i w_i. In order to avoid two passes through the w vector, + * we use the equivalent formulation of + * d = log s - (sum_i w_i log w_i) / s + * However, this formulation may not give an exact 0.0 for some w when k=1, + * due to roundoff errors (examples: w=3 or w=7). For this reason, we + * special-case the computation for k=1 even for the unnormalized diversity + * insted of just setting the normalization factor to 1 for this case. + */ + if (k == 0) { + d = IGRAPH_NAN; + } else if (k == 1) { + if (VECTOR(*weights)[0] > 0) d = 0.0; /* s > 0 */ + else d = IGRAPH_NAN; /* s == 0 */ + } else { + igraph_real_t s = 0.0, ent = 0.0; + for (i = 0; i < k; i++) { + igraph_real_t w = VECTOR(*weights)[VECTOR(incident)[i]]; + if (w == 0) continue; + s += w; + ent += (w * log(w)); + } + d = (log(s) - ent / s) / log(k); + } + + igraph_vector_push_back(res, d); /* reserved */ + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&incident); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_reciprocity + * \brief Calculates the reciprocity of a directed graph. + * + * In a directed graph, the measure of reciprocity defines the proportion of + * mutual connections. It is most commonly defined as the probability that the + * opposite counterpart of a randomly chosen directed edge is also included in + * the graph. In adjacency matrix notation: + * 1 - (sum_ij |A_ij - A_ji|) / (2 sum_ij A_ij). + * In multigraphs, each parallel edge between two vertices must have its own + * separate reciprocal edge, in accordance with the above formula. This measure + * is calculated if the \p mode argument is \c IGRAPH_RECIPROCITY_DEFAULT. + * + * + * For directed graphs with no edges, NaN is returned. + * For undirected graphs, 1 is returned unconditionally. + * + * + * Prior to igraph version 0.6, another measure was implemented, defined as the + * probability of having mutual connections between a vertex pair if we know + * that there is a (possibly non-mutual) connection between them. In other + * words, (unordered) vertex pairs are classified into three groups: + * (1) disconnected, (2) non-reciprocally connected, (3) reciprocally connected. + * The result is the size of group (3), divided by the sum of group + * sizes (2)+(3). This measure is calculated if \p mode is + * \c IGRAPH_RECIPROCITY_RATIO. + * + * \param graph The graph object. + * \param res Pointer to an \c igraph_real_t which will contain the result. + * \param ignore_loops Whether to ignore self-loops when counting edges. + * Self-loops are considered as a mutual connection. + * \param mode Type of reciprocity to calculate, possible values are + * \c IGRAPH_RECIPROCITY_DEFAULT and \c IGRAPH_RECIPROCITY_RATIO, + * please see their description above. + * \return Error code: + * \c IGRAPH_EINVAL: graph has no edges + * \c IGRAPH_ENOMEM: not enough memory for + * temporary data. + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices, + * |E| is the number of edges. + * + * \example examples/simple/igraph_reciprocity.c + */ +igraph_error_t igraph_reciprocity(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t ignore_loops, + igraph_reciprocity_t mode) { + + igraph_int_t nonrec = 0, rec = 0, loops = 0; + igraph_vector_int_t inneis, outneis; + igraph_int_t no_of_nodes = igraph_vcount(graph); + + if (mode != IGRAPH_RECIPROCITY_DEFAULT && + mode != IGRAPH_RECIPROCITY_RATIO) { + IGRAPH_ERROR("Invalid reciprocity type.", IGRAPH_EINVAL); + } + + /* Undirected graphs have reciprocity 1.0 by definition. */ + if (!igraph_is_directed(graph)) { + *res = 1.0; + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&inneis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&outneis, 0); + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t ip, op, indeg, outdeg; + IGRAPH_CHECK(igraph_neighbors( + graph, &inneis, i, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE + )); + IGRAPH_CHECK(igraph_neighbors( + graph, &outneis, i, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE + )); + + indeg = igraph_vector_int_size(&inneis); + outdeg = igraph_vector_int_size(&outneis); + + ip = op = 0; + while (ip < indeg && op < outdeg) { + if (VECTOR(inneis)[ip] < VECTOR(outneis)[op]) { + nonrec += 1; + ip++; + } else if (VECTOR(inneis)[ip] > VECTOR(outneis)[op]) { + nonrec += 1; + op++; + } else { + + /* loop edge? */ + if (VECTOR(inneis)[ip] == i) { + loops += 1; + if (!ignore_loops) { + rec += 1; + } + } else { + rec += 1; + } + + ip++; + op++; + } + } + nonrec += (indeg - ip) + (outdeg - op); + } + + /* If we found non-loop mutual connections, we can set the cache. */ + if (rec - (ignore_loops ? 0 : loops) > 0) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MUTUAL, true); + } + + if (mode == IGRAPH_RECIPROCITY_DEFAULT) { + if (ignore_loops) { + *res = (igraph_real_t) rec / (igraph_ecount(graph) - loops); + } else { + *res = (igraph_real_t) rec / (igraph_ecount(graph)); + } + } else if (mode == IGRAPH_RECIPROCITY_RATIO) { + *res = (igraph_real_t) rec / (rec + nonrec); + } + + igraph_vector_int_destroy(&inneis); + igraph_vector_int_destroy(&outneis); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/complete.c b/src/properties/complete.c new file mode 100644 index 0000000..088a210 --- /dev/null +++ b/src/properties/complete.c @@ -0,0 +1,279 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + + +#include "igraph_interface.h" +#include "igraph_structural.h" + +#include "core/interruption.h" + +/** + * \ingroup structural + * \function igraph_is_complete + * \brief Decides whether the graph is complete. + * + * A graph is considered complete if all pairs of different vertices are + * adjacent. + * + * + * The null graph and the singleton graph are considered complete. + * + * \param graph The graph object to analyze. + * \param res Pointer to a Boolean variable, the result will be stored here. + * + * \return Error code. + * + * Time complexity: O(|V| + |E|) at worst. + */ + +igraph_error_t igraph_is_complete(const igraph_t *graph, igraph_bool_t *res) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t complete_ecount; + igraph_bool_t simple, directed = igraph_is_directed(graph); + igraph_vector_int_t neighbours; + int iter = 0; + + /* If the graph is the null graph or the singleton graph, return early */ + if (vcount == 0 || vcount == 1) { + *res = true; + return IGRAPH_SUCCESS; + } + + /* Compute the amount of edges a complete graph of vcount vertices would + have */ + + /* Depends on whether the graph is directed */ + + /* We have to take care of integer overflowing */ + +#if IGRAPH_INTEGER_SIZE == 32 + if (directed) { + /* Highest x s.t. x² - x < 2^31 - 1 */ + if (vcount > 46341) { + *res = false; + return IGRAPH_SUCCESS; + } else { + complete_ecount = vcount * (vcount - 1); + } + } else { + /* Highest x s.t. (x² - x) / 2 < 2^31 - 1 */ + if (vcount > 65536) { + *res = false; + return IGRAPH_SUCCESS; + } else { + complete_ecount = vcount % 2 == 0 ? + (vcount / 2) * (vcount - 1) : + vcount * ((vcount - 1) / 2); + } + } +#elif IGRAPH_INTEGER_SIZE == 64 + if (directed) { + /* Highest x s.t. x² - x < 2^63 - 1 */ + if (vcount > 3037000500) { + *res = false; + return IGRAPH_SUCCESS; + } else { + complete_ecount = vcount * (vcount - 1); + } + } else { + /* Highest x s.t. (x² - x) / 2 < 2^63 - 1 */ + if (vcount > 4294967296) { + *res = false; + return IGRAPH_SUCCESS; + } else { + complete_ecount = vcount % 2 == 0 ? + (vcount / 2) * (vcount - 1) : + vcount * ((vcount - 1) / 2); + } + } +#else + /* If values other than 32 or 64 become allowed, + * this code will need to be updated. */ +# error "Unexpected IGRAPH_INTEGER_SIZE value." +#endif + + /* If the amount of edges is strictly lower than what it should be for a + complete graph, return early */ + + if (ecount < complete_ecount) { + *res = false; + return IGRAPH_SUCCESS; + } + + /* If the graph is simple, compare and conclude */ + IGRAPH_CHECK(igraph_is_simple(graph, &simple, IGRAPH_DIRECTED)); + + if (simple) { + *res = (ecount == complete_ecount); + return IGRAPH_SUCCESS; + } + + /* Allocate memory for vector of size v */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&neighbours, vcount); + + for (igraph_int_t i = 0; i < vcount; ++i) { + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + + IGRAPH_CHECK(igraph_neighbors( + graph, &neighbours, i, IGRAPH_OUT, IGRAPH_NO_LOOPS, + IGRAPH_NO_MULTIPLE + )); + + if ((igraph_vector_int_size(&neighbours) < vcount - 1)) { + *res = false; + goto cleanup; + } + } + + /* If we arrive here, we have found no neighbour vector of size strictly + less than vcount - 1. The graph is therefore complete */ + + *res = true; + +cleanup: + + igraph_vector_int_destroy(&neighbours); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Test for cliques or independent sets, depending on whether independent_set == true. */ +static igraph_error_t is_clique(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t directed, igraph_bool_t *res, + igraph_bool_t independent_set) { + igraph_vector_int_t vids; + igraph_int_t n; /* clique size */ + igraph_bool_t result = true; /* be optimistic */ + int iter = 0; + + /* The following implementation is optimized for testing for small cliques + * in large graphs. */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_CHECK(igraph_vs_as_vector(graph, candidate, &vids)); + + n = igraph_vector_int_size(&vids); + + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t u = VECTOR(vids)[i]; + for (igraph_int_t j = directed ? 0 : i+1; j < n; j++) { + igraph_int_t v = VECTOR(vids)[j]; + /* Compare u and v for equality instead of i and j in case + * the vertex list contained duplicates. */ + if (u != v) { + igraph_int_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, u, v, directed, false)); + if (independent_set) { + if (eid != -1) { + result = false; + goto done; + } + } else { + if (eid == -1) { + result = false; + goto done; + } + } + } + } + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8); + } + +done: + + *res = result; + + igraph_vector_int_destroy(&vids); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_is_clique + * \brief Does a set of vertices form a clique? + * + * Tests if all pairs within a set of vertices are adjacent, i.e. whether they + * form a clique. An empty set and singleton set are considered to be a clique. + * + * \param graph The input graph. + * \param candidate The vertex set to test for being a clique. + * \param directed Whether to take edge directions into account in directed graphs. + * \param res The result will be stored here. + * \return Error code. + * + * \sa \ref igraph_is_complete() to test if a graph is complete; + * \ref igraph_is_independent_vertex_set() to test for independent vertex sets; + * \ref igraph_cliques(), \ref igraph_maximal_cliques() and + * \ref igraph_largest_cliques() to find cliques. + * + * Time complexity: O(n^2 log(d)) where n is the number of vertices in the + * candidate set and d is the typical vertex degree. + */ +igraph_error_t igraph_is_clique(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t directed, igraph_bool_t *res) { + + if (! igraph_is_directed(graph)) { + directed = false; + } + + if (igraph_is_directed(graph) == directed && igraph_vs_is_all(&candidate)) { + return igraph_is_complete(graph, res); + } + + return is_clique(graph, candidate, directed, res, /* independent_set */ false); +} + +/** + * \ingroup structural + * \function igraph_is_independent_vertex_set + * \brief Does a set of vertices form an independent set? + * + * Tests if no pairs within a set of vertices are adjacenct, i.e. whether they + * form an independent set. An empty set and singleton set are both considered + * to be an independent set. + * + * \param graph The input graph. + * \param candidate The vertex set to test for being an independent set. + * \param res The result will be stored here. + * \return Error code. + * + * \sa \ref igraph_is_clique() to test for cliques; \ref igraph_independent_vertex_sets(), + * \ref igraph_maximal_independent_vertex_sets() and + * \ref igraph_largest_independent_vertex_sets() to find independent vertex sets. + * + * Time complexity: O(n^2 log(d)) where n is the number of vertices in the + * candidate set and d is the typical vertex degree. + */ +igraph_error_t igraph_is_independent_vertex_set(const igraph_t *graph, igraph_vs_t candidate, + igraph_bool_t *res) { + + /* Note: igraph_count_loops() already makes use of the cache. */ + if (igraph_vs_is_all(&candidate)) { + igraph_int_t loop_count; + igraph_count_loops(graph, &loop_count); + *res = (igraph_ecount(graph) - loop_count) == 0; + return IGRAPH_SUCCESS; + } + + return is_clique(graph, candidate, /* directed */ false, res, /* independent_set */ true); +} diff --git a/src/properties/constraint.c b/src/properties/constraint.c new file mode 100644 index 0000000..357aeda --- /dev/null +++ b/src/properties/constraint.c @@ -0,0 +1,288 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_centrality.h" + +#include "igraph_interface.h" +#include "igraph_structural.h" + +/** + * \function igraph_constraint + * \brief Burt's constraint scores. + * + * + * This function calculates Burt's constraint scores for the given + * vertices, also known as structural holes. + * + * + * Burt's constraint is higher if ego has less, or mutually stronger + * related (i.e. more redundant) contacts. Burt's measure of + * constraint, C[i], of vertex i's ego network V[i], is defined for + * directed and valued graphs, + *
+ * C[i] = sum( sum( (p[i,q] p[q,j])^2, q in V[i], q != i,j ), j in + * V[], j != i) + *
+ * for a graph of order (i.e. number of vertices) N, where proportional + * tie strengths are defined as + *
+ * p[i,j]=(a[i,j]+a[j,i]) / sum(a[i,k]+a[k,i], k in V[i], k != i), + *
+ * a[i,j] are elements of A and + * the latter being the graph adjacency matrix. For isolated vertices, + * constraint is undefined. + * + *
+ * Burt, R.S. (2004). Structural holes and good ideas. American + * Journal of Sociology 110, 349-399. + * + * + * The first R version of this function was contributed by Jeroen + * Bruggeman. + * \param graph A graph object. + * \param res Pointer to an initialized vector, the result will be + * stored here. The vector will be resized to have the + * appropriate size for holding the result. + * \param vids Vertex selector containing the vertices for which the + * constraint should be calculated. + * \param weights Vector giving the weights of the edges. If it is + * \c NULL then each edge is supposed to have the same weight. + * \return Error code. + * + * Time complexity: O(|V|+E|+n*d^2), n is the number of vertices for + * which the constraint is calculated and d is the average degree, |V| + * is the number of vertices, |E| the number of edges in the + * graph. If the weights argument is \c NULL then the time complexity + * is O(|V|+n*d^2). + */ +igraph_error_t igraph_constraint(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, const igraph_vector_t *weights) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_vit_t vit; + igraph_int_t nodes_to_calc; + igraph_int_t a, b, c, i, j, q, vsize, vsize2; + igraph_int_t edge, edge2; + + igraph_vector_t contrib; + igraph_vector_t degree; + igraph_vector_int_t ineis_in, ineis_out, jneis_in, jneis_out; + + if (weights != 0 && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weight vector", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&contrib, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ineis_in, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&ineis_out, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&jneis_in, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&jneis_out, 0); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS, weights)); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (a = 0; a < nodes_to_calc; a++, IGRAPH_VIT_NEXT(vit)) { + i = IGRAPH_VIT_GET(vit); + + /* get neighbors of i */ + IGRAPH_CHECK(igraph_incident(graph, &ineis_in, i, IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_incident(graph, &ineis_out, i, IGRAPH_OUT, IGRAPH_LOOPS)); + + /* NaN for isolates */ + if (igraph_vector_int_size(&ineis_in) == 0 && + igraph_vector_int_size(&ineis_out) == 0) { + VECTOR(*res)[a] = IGRAPH_NAN; + } + + /* zero their contribution */ + vsize = igraph_vector_int_size(&ineis_in); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_in)[b]; + j = IGRAPH_OTHER(graph, edge, i); + VECTOR(contrib)[j] = 0.0; + } + vsize = igraph_vector_int_size(&ineis_out); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_out)[b]; + j = IGRAPH_OTHER(graph, edge, i); + VECTOR(contrib)[j] = 0.0; + } + + /* add the direct contributions, in-neighbors and out-neighbors */ + vsize = igraph_vector_int_size(&ineis_in); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_in)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i != j) { /* excluding loops */ + if (weights) { + VECTOR(contrib)[j] += + VECTOR(*weights)[edge] / VECTOR(degree)[i]; + } else { + VECTOR(contrib)[j] += 1.0 / VECTOR(degree)[i]; + } + } + } + if (igraph_is_directed(graph)) { + vsize = igraph_vector_int_size(&ineis_out); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_out)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i != j) { + if (weights) { + VECTOR(contrib)[j] += + VECTOR(*weights)[edge] / VECTOR(degree)[i]; + } else { + VECTOR(contrib)[j] += 1.0 / VECTOR(degree)[i]; + } + } + } + } + + /* add the indirect contributions, in-in, in-out, out-in, out-out */ + vsize = igraph_vector_int_size(&ineis_in); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_in)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i == j) { + continue; + } + IGRAPH_CHECK(igraph_incident(graph, &jneis_in, j, IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_incident(graph, &jneis_out, j, IGRAPH_OUT, IGRAPH_LOOPS)); + vsize2 = igraph_vector_int_size(&jneis_in); + for (c = 0; c < vsize2; c++) { + edge2 = VECTOR(jneis_in)[c]; + q = IGRAPH_OTHER(graph, edge2, j); + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[edge] * + VECTOR(*weights)[edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + if (igraph_is_directed(graph)) { + vsize2 = igraph_vector_int_size(&jneis_out); + for (c = 0; c < vsize2; c++) { + edge2 = VECTOR(jneis_out)[c]; + q = IGRAPH_OTHER(graph, edge2, j); + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[edge] * + VECTOR(*weights)[edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + } + } + if (igraph_is_directed(graph)) { + vsize = igraph_vector_int_size(&ineis_out); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_out)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i == j) { + continue; + } + IGRAPH_CHECK(igraph_incident(graph, &jneis_in, j, IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_incident(graph, &jneis_out, j, IGRAPH_OUT, IGRAPH_LOOPS)); + vsize2 = igraph_vector_int_size(&jneis_in); + for (c = 0; c < vsize2; c++) { + edge2 = VECTOR(jneis_in)[c]; + q = IGRAPH_OTHER(graph, edge2, j); + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[edge] * + VECTOR(*weights)[edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + vsize2 = igraph_vector_int_size(&jneis_out); + for (c = 0; c < vsize2; c++) { + edge2 = VECTOR(jneis_out)[c]; + q = IGRAPH_OTHER(graph, edge2, j); + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[edge] * + VECTOR(*weights)[edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + } + } + + /* squared sum of the contributions */ + vsize = igraph_vector_int_size(&ineis_in); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_in)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i == j) { + continue; + } + VECTOR(*res)[a] += VECTOR(contrib)[j] * VECTOR(contrib)[j]; + VECTOR(contrib)[j] = 0.0; + } + if (igraph_is_directed(graph)) { + vsize = igraph_vector_int_size(&ineis_out); + for (b = 0; b < vsize; b++) { + edge = VECTOR(ineis_out)[b]; + j = IGRAPH_OTHER(graph, edge, i); + if (i == j) { + continue; + } + VECTOR(*res)[a] += VECTOR(contrib)[j] * VECTOR(contrib)[j]; + VECTOR(contrib)[j] = 0.0; + } + } + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&jneis_out); + igraph_vector_int_destroy(&jneis_in); + igraph_vector_int_destroy(&ineis_out); + igraph_vector_int_destroy(&ineis_in); + igraph_vector_destroy(°ree); + igraph_vector_destroy(&contrib); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/convergence_degree.c b/src/properties/convergence_degree.c new file mode 100644 index 0000000..6bedb9d --- /dev/null +++ b/src/properties/convergence_degree.c @@ -0,0 +1,206 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_centrality.h" + +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" + +#include "core/interruption.h" + +#include + +/** + * \function igraph_convergence_degree + * \brief Calculates the convergence degree of each edge in a graph. + * + * Let us define the input set of an edge (i, j) as the set of vertices where + * the shortest paths passing through (i, j) originate, and similarly, let us + * defined the output set of an edge (i, j) as the set of vertices where the + * shortest paths passing through (i, j) terminate. The convergence degree of + * an edge is defined as the normalized value of the difference between the + * size of the input set and the output set, i.e. the difference of them + * divided by the sum of them. Convergence degrees are in the range (-1, 1); a + * positive value indicates that the edge is \em convergent since the shortest + * paths passing through it originate from a larger set and terminate in a + * smaller set, while a negative value indicates that the edge is \em divergent + * since the paths originate from a small set and terminate in a larger set. + * + * + * Note that the convergence degree as defined above does not make sense in + * undirected graphs as there is no distinction between the input and output + * set. Therefore, for undirected graphs, the input and output sets of an edge + * are determined by orienting the edge arbitrarily while keeping the remaining + * edges undirected, and then taking the absolute value of the convergence + * degree. + * + * \param graph The input graph, it can be either directed or undirected. + * \param result Pointer to an initialized vector; the convergence degrees of + * each edge will be stored here. May be \c NULL if we are not interested in + * the exact convergence degrees. + * \param ins Pointer to an initialized vector; the size of the input set of + * each edge will be stored here. May be \c NULL if we are not interested in + * the sizes of the input sets. + * \param outs Pointer to an initialized vector; the size of the output set of + * each edge will be stored here. May be \c NULL if we are not interested in + * the sizes of the output sets. + * \return Error code. + * + * Time complexity: O(|V||E|), the number of vertices times the number of edges. + */ +igraph_error_t igraph_convergence_degree(const igraph_t *graph, igraph_vector_t *result, + igraph_vector_t *ins, igraph_vector_t *outs) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t i, j, k, n; + igraph_int_t *geodist; + igraph_vector_int_t *eids; + igraph_vector_t *ins_p, *outs_p, ins_v, outs_v; + igraph_dqueue_int_t q; + igraph_inclist_t inclist; + igraph_bool_t directed = igraph_is_directed(graph); + + if (result != 0) { + IGRAPH_CHECK(igraph_vector_resize(result, no_of_edges)); + } + IGRAPH_CHECK(igraph_dqueue_int_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q); + + if (ins == 0) { + ins_p = &ins_v; + IGRAPH_VECTOR_INIT_FINALLY(ins_p, no_of_edges); + } else { + ins_p = ins; + IGRAPH_CHECK(igraph_vector_resize(ins_p, no_of_edges)); + igraph_vector_null(ins_p); + } + + if (outs == 0) { + outs_p = &outs_v; + IGRAPH_VECTOR_INIT_FINALLY(outs_p, no_of_edges); + } else { + outs_p = outs; + IGRAPH_CHECK(igraph_vector_resize(outs_p, no_of_edges)); + igraph_vector_null(outs_p); + } + + geodist = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + if (geodist == 0) { + IGRAPH_ERROR("Cannot calculate convergence degrees", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, geodist); + + /* Collect shortest paths originating from/to every node to correctly + * determine input and output field sizes */ + for (k = 0; k < (directed ? 2 : 1); k++) { + igraph_neimode_t neimode = (k == 0) ? IGRAPH_OUT : IGRAPH_IN; + igraph_real_t *vec; + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, neimode, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + vec = (k == 0) ? VECTOR(*ins_p) : VECTOR(*outs_p); + for (i = 0; i < no_of_nodes; i++) { + igraph_dqueue_int_clear(&q); + memset(geodist, 0, sizeof(geodist[0]) * (size_t) no_of_nodes); + geodist[i] = 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + IGRAPH_ALLOW_INTERRUPTION(); + eids = igraph_inclist_get(&inclist, actnode); + n = igraph_vector_int_size(eids); + for (j = 0; j < n; j++) { + igraph_int_t neighbor = IGRAPH_OTHER(graph, VECTOR(*eids)[j], actnode); + if (geodist[neighbor] != 0) { + /* we've already seen this node, another shortest path? */ + if (geodist[neighbor] - 1 == actdist + 1) { + /* Since this edge is in the BFS tree rooted at i, we must + * increase either the size of the infield or the outfield */ + if (!directed) { + if (actnode < neighbor) { + VECTOR(*ins_p)[VECTOR(*eids)[j]] += 1; + } else { + VECTOR(*outs_p)[VECTOR(*eids)[j]] += 1; + } + } else { + vec[VECTOR(*eids)[j]] += 1; + } + } else if (geodist[neighbor] - 1 < actdist + 1) { + continue; + } + } else { + /* we haven't seen this node yet */ + IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + /* Since this edge is in the BFS tree rooted at i, we must + * increase either the size of the infield or the outfield */ + if (!directed) { + if (actnode < neighbor) { + VECTOR(*ins_p)[VECTOR(*eids)[j]] += 1; + } else { + VECTOR(*outs_p)[VECTOR(*eids)[j]] += 1; + } + } else { + vec[VECTOR(*eids)[j]] += 1; + } + geodist[neighbor] = actdist + 2; + } + } + } + } + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (result != 0) { + for (i = 0; i < no_of_edges; i++) { + VECTOR(*result)[i] = (VECTOR(*ins_p)[i] - VECTOR(*outs_p)[i]) / + (VECTOR(*ins_p)[i] + VECTOR(*outs_p)[i]); + } + + if (!directed) { + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(*result)[i] < 0) { + VECTOR(*result)[i] = -VECTOR(*result)[i]; + } + } + } + } + + if (ins == 0) { + igraph_vector_destroy(ins_p); + IGRAPH_FINALLY_CLEAN(1); + } + if (outs == 0) { + igraph_vector_destroy(outs_p); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_FREE(geodist); + igraph_dqueue_int_destroy(&q); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/dag.c b/src/properties/dag.c new file mode 100644 index 0000000..0550cbd --- /dev/null +++ b/src/properties/dag.c @@ -0,0 +1,214 @@ +/* + igraph library. + Copyright (C) 2005-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + +#include "igraph_cycles.h" + +#include "igraph_dqueue.h" +#include "igraph_interface.h" + +/** + * \function igraph_topological_sorting + * \brief Calculate a possible topological sorting of the graph. + * + * + * A topological sorting of a directed acyclic graph (DAG) is a linear ordering + * of its vertices where each vertex comes before all nodes to which it has + * edges. Every DAG has at least one topological sort, and may have many. + * This function returns one possible topological sort among them. If the + * graph contains any cycles that are not self-loops, an error is raised. + * + * \param graph The input graph. + * \param res Pointer to a vector, the result will be stored here. + * It will be resized if needed. + * \param mode Specifies how to use the direction of the edges. + * For \c IGRAPH_OUT, the sorting order ensures that each vertex comes + * before all vertices to which it has edges, so vertices with no incoming + * edges go first. For \c IGRAPH_IN, it is quite the opposite: each + * vertex comes before all vertices from which it receives edges. Vertices + * with no outgoing edges go first. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + * + * \sa \ref igraph_is_dag() if you are only interested in whether a given + * graph is a DAG or not, or \ref igraph_feedback_arc_set() to find a + * set of edges whose removal makes the graph acyclic. + * + * \example examples/simple/igraph_topological_sorting.c + */ +igraph_error_t igraph_topological_sorting( + const igraph_t* graph, igraph_vector_int_t *res, igraph_neimode_t mode) { + + /* Note: This function ignores self-loops, there it cannot + * use the IGRAPH_PROP_IS_DAG property cache entry. */ + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degrees; + igraph_vector_int_t neis; + igraph_dqueue_int_t sources; + igraph_neimode_t deg_mode; + igraph_int_t node, i, j; + + if (mode == IGRAPH_ALL || !igraph_is_directed(graph)) { + IGRAPH_ERROR("Topological sorting does not make sense for undirected graphs.", + IGRAPH_EINVAL); + } else if (mode == IGRAPH_OUT) { + deg_mode = IGRAPH_IN; + } else if (mode == IGRAPH_IN) { + deg_mode = IGRAPH_OUT; + } else { + IGRAPH_ERROR("Invalid mode for topological sorting.", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_int_init(&sources, 0)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &sources); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), deg_mode, IGRAPH_NO_LOOPS)); + + igraph_vector_int_clear(res); + + /* Do we have nodes with no incoming vertices? */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(degrees)[i] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i)); + } + } + + /* Take all nodes with no incoming vertices and remove them */ + while (!igraph_dqueue_int_empty(&sources)) { + node = igraph_dqueue_int_pop(&sources); + /* Add the node to the result vector */ + IGRAPH_CHECK(igraph_vector_int_push_back(res, node)); + /* Exclude the node from further source searches */ + VECTOR(degrees)[node] = -1; + /* Get the neighbors and decrease their degrees by one */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); + j = igraph_vector_int_size(&neis); + for (i = 0; i < j; i++) { + VECTOR(degrees)[ VECTOR(neis)[i] ]--; + if (VECTOR(degrees)[ VECTOR(neis)[i] ] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, VECTOR(neis)[i])); + } + } + } + + igraph_vector_int_destroy(°rees); + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&sources); + IGRAPH_FINALLY_CLEAN(3); + + if (igraph_vector_int_size(res) < no_of_nodes) { + IGRAPH_ERROR("The graph has cycles; " + "topological sorting is only possible in acyclic graphs.", + IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_dag + * \brief Checks whether a graph is a directed acyclic graph (DAG). + * + * + * A directed acyclic graph (DAG) is a directed graph with no cycles. + * + * + * This function returns \c false for undirected graphs. + * + * + * The return value of this function is cached in the graph itself; calling + * the function multiple times with no modifications to the graph in between + * will return a cached value in O(1) time. + * + * \param graph The input graph. + * \param res Pointer to a boolean constant, the result + * is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + * + * \sa \ref igraph_topological_sorting() to get a possible topological + * sorting of a DAG. + */ +igraph_error_t igraph_is_dag(const igraph_t* graph, igraph_bool_t *res) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degrees; + igraph_vector_int_t neis; + igraph_dqueue_int_t sources; + + if (!igraph_is_directed(graph)) { + *res = false; + return IGRAPH_SUCCESS; + } + + IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_IS_DAG, res); + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&sources, 0); + + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); + + igraph_int_t vertices_left = no_of_nodes; + + /* Do we have nodes with no incoming edges? */ + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + if (VECTOR(degrees)[i] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i)); + } + } + + /* Take all nodes with no incoming edges and remove them */ + while (!igraph_dqueue_int_empty(&sources)) { + igraph_int_t node = igraph_dqueue_int_pop(&sources); + /* Exclude the node from further source searches */ + VECTOR(degrees)[node] = -1; + vertices_left--; + /* Get the neighbors and decrease their degrees by one */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + igraph_int_t n = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(neis)[i]; + if (nei == node) { + /* Found a self-loop, graph is not a DAG */ + *res = false; + goto finalize; + } + VECTOR(degrees)[nei]--; + if (VECTOR(degrees)[nei] == 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&sources, nei)); + } + } + } + + IGRAPH_ASSERT(vertices_left >= 0); + *res = (vertices_left == 0); + +finalize: + igraph_vector_int_destroy(°rees); + igraph_vector_int_destroy(&neis); + igraph_dqueue_int_destroy(&sources); + IGRAPH_FINALLY_CLEAN(3); + + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_DAG, *res); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/degrees.c b/src/properties/degrees.c new file mode 100644 index 0000000..1e3f702 --- /dev/null +++ b/src/properties/degrees.c @@ -0,0 +1,738 @@ +/* + igraph library. + Copyright (C) 2005-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_structural.h" + +#include "igraph_interface.h" + +/** + * \function igraph_maxdegree + * \brief The maximum degree in a graph (or set of vertices). + * + * The largest in-, out- or total degree of the specified vertices is + * calculated. If the graph has no vertices, or \p vids is empty, + * 0 is returned, as this is the smallest possible value for degrees. + * + * \param graph The input graph. + * \param res Pointer to an integer (\c igraph_int_t), the result + * will be stored here. + * \param vids Vector giving the vertex IDs for which the maximum degree will + * be calculated. + * \param mode Defines the type of the degree. + * \c IGRAPH_OUT, out-degree, + * \c IGRAPH_IN, in-degree, + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges when calculating the + * degree. \c IGRAPH_NO_LOOPS ignores loop edges; \c IGRAPH_LOOPS_ONCE + * counts each loop edge only once; \c IGRAPH_LOOPS_TWICE counts each + * loop edge twice in undirected graphs and once in directed graphs. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + * Time complexity: O(v) if \p loops is \c true, and O(v*d) otherwise. v is the number + * of vertices for which the degree will be calculated, and d is their + * (average) degree. + * + * \sa \ref igraph_degree() to retrieve the degrees for several vertices. + */ +igraph_error_t igraph_maxdegree( + const igraph_t *graph, igraph_int_t *res, igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops +) { + + igraph_vector_int_t tmp; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + + IGRAPH_CHECK(igraph_degree(graph, &tmp, vids, mode, loops)); + if (igraph_vector_int_size(&tmp) == 0) { + *res = 0; + } else { + *res = igraph_vector_int_max(&tmp); + } + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t avg_nearest_neighbor_degree_weighted(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t neis, edge_neis; + igraph_int_t no_vids; + igraph_vit_t vit; + igraph_vector_t my_knn_v, *my_knn = knn; + igraph_vector_t strength; + igraph_vector_int_t deg; + igraph_int_t maxdeg; + igraph_vector_t deghist; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector size.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + if (!knn) { + IGRAPH_VECTOR_INIT_FINALLY(&my_knn_v, no_vids); + my_knn = &my_knn_v; + } else { + IGRAPH_CHECK(igraph_vector_resize(knn, no_vids)); + } + + /* Get degree of neighbours */ + IGRAPH_VECTOR_INT_INIT_FINALLY(°, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), + neighbor_degree_mode, IGRAPH_LOOPS)); + IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes); + + /* Get strength of all nodes */ + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), + mode, IGRAPH_LOOPS, weights)); + + /* Get maximum degree for initialization */ + IGRAPH_CHECK(igraph_maxdegree(graph, &maxdeg, igraph_vss_all(), + mode, IGRAPH_LOOPS)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, maxdeg); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edge_neis, maxdeg); + igraph_vector_int_clear(&neis); + igraph_vector_int_clear(&edge_neis); + + if (knnk) { + IGRAPH_CHECK(igraph_vector_resize(knnk, maxdeg)); + igraph_vector_null(knnk); + IGRAPH_VECTOR_INIT_FINALLY(°hist, maxdeg); + } + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t sum = 0.0; + igraph_int_t v = IGRAPH_VIT_GET(vit); + igraph_int_t nv; + igraph_real_t str = VECTOR(strength)[v]; + /* Get neighbours and incident edges */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, v, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + IGRAPH_CHECK(igraph_incident(graph, &edge_neis, v, mode, IGRAPH_LOOPS)); + nv = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < nv; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + igraph_int_t e = VECTOR(edge_neis)[j]; + igraph_real_t w = VECTOR(*weights)[e]; + sum += w * VECTOR(deg)[nei]; + } + if (str != 0.0) { + VECTOR(*my_knn)[i] = sum / str; + } else { + VECTOR(*my_knn)[i] = IGRAPH_NAN; + } + if (knnk && nv > 0) { + VECTOR(*knnk)[nv - 1] += sum; + VECTOR(deghist)[nv - 1] += str; + } + } + + igraph_vector_int_destroy(&edge_neis); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + if (knnk) { + for (igraph_int_t i = 0; i < maxdeg; i++) { + igraph_real_t dh = VECTOR(deghist)[i]; + if (dh != 0) { + VECTOR(*knnk)[i] /= dh; + } else { + VECTOR(*knnk)[i] = IGRAPH_NAN; + } + } + + igraph_vector_destroy(°hist); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&strength); + igraph_vector_int_destroy(°); + IGRAPH_FINALLY_CLEAN(2); + + if (!knn) { + igraph_vector_destroy(&my_knn_v); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_avg_nearest_neighbor_degree + * \brief Average neighbor degree. + * + * Calculates the average degree of the neighbors for each vertex (\p knn), and + * optionally, the same quantity as a function of the vertex degree (\p knnk). + * + * + * For isolated vertices \p knn is set to NaN. The same is done in \p knnk for + * vertex degrees that don't appear in the graph. + * + * + * The weighted version computes a weighted average of the neighbor degrees as + * + * + * k_nn_u = 1/s_u sum_v w_uv k_v, + * + * + * where s_u = sum_v w_uv is the sum of the incident edge weights + * of vertex \c u, i.e. its strength. + * The sum runs over the neighbors \c v of vertex \c u + * as indicated by \p mode. w_uv denotes the weighted adjacency matrix + * and k_v is the neighbors' degree, specified by \p neighbor_degree_mode. + * This is equation (6) in the reference below. + * + * + * When only the k_nn(k) degree correlation function is needed, + * \ref igraph_degree_correlation_vector() can be used as well. This function provides + * more flexible control over how degree at each end of directed edges are computed. + * + * + * Reference: + * + * + * A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, + * The architecture of complex weighted networks, + * Proc. Natl. Acad. Sci. USA 101, 3747 (2004). + * https://dx.doi.org/10.1073/pnas.0400087101 + * + * \param graph The input graph. It may be directed. + * \param vids The vertices for which the calculation is performed. + * \param mode The type of neighbors to consider in directed graphs. + * \c IGRAPH_OUT considers out-neighbors, \c IGRAPH_IN in-neighbors + * and \c IGRAPH_ALL ignores edge directions. + * \param neighbor_degree_mode The type of degree to average in directed graphs. + * \c IGRAPH_OUT averages out-degrees, \c IGRAPH_IN averages in-degrees + * and \c IGRAPH_ALL ignores edge directions for the degree calculation. + * \param knn Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. Supply a \c NULL pointer + * here if you only want to calculate \c knnk. + * \param knnk Pointer to an initialized vector, the average + * neighbor degree as a function of the vertex degree is stored + * here. This is sometimes referred to as the k_nn(k) + * degree correlation function. The first (zeroth) element is for degree + * one vertices, etc. The calculation is done based only on the vertices + * \p vids. Supply a \c NULL pointer here if you don't want to calculate this. + * \param weights Optional edge weights. Supply a null pointer here + * for the non-weighted version. + * + * \return Error code. + * + * \sa \ref igraph_degree_correlation_vector() for computing only the degree correlation function, + * with more flexible control over degree computations. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_avg_nearest_neighbor_degree.c + */ +igraph_error_t igraph_avg_nearest_neighbor_degree(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t neis; + igraph_int_t no_vids; + igraph_vit_t vit; + igraph_vector_t my_knn_v, *my_knn = knn; + igraph_vector_int_t deg; + igraph_int_t maxdeg; + igraph_vector_int_t deghist; + + if (weights) { + return avg_nearest_neighbor_degree_weighted(graph, vids, + mode, neighbor_degree_mode, knn, knnk, weights); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + if (!knn) { + IGRAPH_VECTOR_INIT_FINALLY(&my_knn_v, no_vids); + my_knn = &my_knn_v; + } else { + IGRAPH_CHECK(igraph_vector_resize(knn, no_vids)); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(°, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), + neighbor_degree_mode, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_maxdegree(graph, &maxdeg, igraph_vss_all(), mode, IGRAPH_LOOPS)); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, maxdeg); + igraph_vector_int_clear(&neis); + + if (knnk) { + IGRAPH_CHECK(igraph_vector_resize(knnk, maxdeg)); + igraph_vector_null(knnk); + IGRAPH_VECTOR_INT_INIT_FINALLY(°hist, maxdeg); + } + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t sum = 0.0; + igraph_int_t v = IGRAPH_VIT_GET(vit); + igraph_int_t nv; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, v, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + nv = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < nv; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + sum += VECTOR(deg)[nei]; + } + if (nv != 0) { + VECTOR(*my_knn)[i] = sum / nv; + } else { + VECTOR(*my_knn)[i] = IGRAPH_NAN; + } + if (knnk && nv > 0) { + VECTOR(*knnk)[nv - 1] += VECTOR(*my_knn)[i]; + VECTOR(deghist)[nv - 1] += 1; + } + } + + if (knnk) { + for (igraph_int_t i = 0; i < maxdeg; i++) { + igraph_int_t dh = VECTOR(deghist)[i]; + if (dh != 0) { + VECTOR(*knnk)[i] /= dh; + } else { + VECTOR(*knnk)[i] = IGRAPH_NAN; + } + } + igraph_vector_int_destroy(°hist); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&neis); + igraph_vector_int_destroy(°); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + + if (!knn) { + igraph_vector_destroy(&my_knn_v); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_degree_correlation_vector + * \brief Degree correlation function. + * + * Computes the degree correlation function k_nn(k), defined as the + * mean degree of the targets of directed edges whose source has degree \c k. + * The averaging is done over all directed edges. The \p from_mode and \p to_mode + * parameters control how the source and target vertex degrees are computed. + * This way the out-in, out-out, in-in and in-out degree correlation functions + * can all be computed. + * + * + * In undirected graphs, edges are treated as if they were a pair of reciprocal directed + * ones. + * + * + * If P_ij is the joint degree distribution of the graph, computable with + * \ref igraph_joint_degree_distribution(), then + * k_nn(k) = (sum_j j P_kj) / (sum_j P_kj). + * + * + * The function \ref igraph_avg_nearest_neighbor_degree(), whose main purpose is to + * calculate the average neighbor degree for each vertex separately, can also compute + * k_nn(k). It differs from this function in that it can take a subset + * of vertices to base the calculation on, but it does not allow the same fine-grained + * control over how degrees are computed. + * + * + * References: + * + * + * R. Pastor-Satorras, A. Vazquez, A. Vespignani: + * Dynamical and Correlation Properties of the Internet, + * Phys. Rev. Lett., vol. 87, pp. 258701 (2001). + * https://doi.org/10.1103/PhysRevLett.87.258701 + * + * + * A. Vazquez, R. Pastor-Satorras, A. Vespignani: + * Large-scale topological and dynamical properties of the Internet, + * Phys. Rev. E, vol. 65, pp. 066130 (2002). + * https://doi.org/10.1103/PhysRevE.65.066130 + * + * + * A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, + * The architecture of complex weighted networks, + * Proc. Natl. Acad. Sci. USA 101, 3747 (2004). + * https://dx.doi.org/10.1073/pnas.0400087101 + * + * \param graph The input graph. + * \param weights An optional weight vector. If not \c NULL, weighted averages will be computed. + * \param knnk An initialized vector, the result will be written here. + * knnk[d] will contain the mean degree of vertices connected to + * by vertices of degree \c d. Note that in contrast to + * \ref igraph_avg_nearest_neighbor_degree(), d=0 is also + * included. + * \param from_mode How to compute the degree of sources? Can be \c IGRAPH_OUT + * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. + * Ignored in undirected graphs. + * \param to_mode How to compute the degree of sources? Can be \c IGRAPH_OUT + * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. + * Ignored in undirected graphs. + * \param directed_neighbors Whether to consider u -> v connections + * to be directed. Undirected connections are treated as reciprocal directed ones, + * i.e. both u -> v and v -> u will be considered. + * Ignored in undirected graphs. + * \return Error code. + * + * \sa \ref igraph_avg_nearest_neighbor_degree() for computing the average neighbour + * degree of a set of vertices, \ref igraph_joint_degree_distribution() to get the + * complete joint degree distribution, and \ref igraph_assortativity_degree() + * to compute the degree assortativity. + * + * Time complexity: O(|E| + |V|) + */ +igraph_error_t igraph_degree_correlation_vector( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *knnk, + igraph_neimode_t from_mode, igraph_neimode_t to_mode, + igraph_bool_t directed_neighbors) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t maxdeg; + igraph_vector_t weight_sums; + igraph_vector_int_t *deg_from, *deg_to, deg_out, deg_in, deg_all; + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (! igraph_is_directed(graph)) { + from_mode = to_mode = IGRAPH_ALL; + directed_neighbors = false; + } + + igraph_bool_t have_out = from_mode == IGRAPH_OUT || to_mode == IGRAPH_OUT; + igraph_bool_t have_in = from_mode == IGRAPH_IN || to_mode == IGRAPH_IN; + igraph_bool_t have_all = from_mode == IGRAPH_ALL || to_mode == IGRAPH_ALL; + + if (have_out) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_out, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_out, igraph_vss_all(), IGRAPH_OUT, /* loops */ true)); + } + + if (have_in) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_in, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_in, igraph_vss_all(), IGRAPH_IN, /* loops */ true)); + } + + if (have_all) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°_all, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °_all, igraph_vss_all(), IGRAPH_ALL, /* loops */ true)); + } + + switch (from_mode) { + case IGRAPH_OUT: deg_from = °_out; break; + case IGRAPH_IN: deg_from = °_in; break; + case IGRAPH_ALL: deg_from = °_all; break; + default: + IGRAPH_ERROR("Invalid 'from' mode.", IGRAPH_EINVMODE); + } + + switch (to_mode) { + case IGRAPH_OUT: deg_to = °_out; break; + case IGRAPH_IN: deg_to = °_in; break; + case IGRAPH_ALL: deg_to = °_all; break; + default: + IGRAPH_ERROR("Invalid 'to' mode.", IGRAPH_EINVMODE); + } + + maxdeg = no_of_edges > 0 ? igraph_vector_int_max(deg_from) : 0; + + IGRAPH_VECTOR_INIT_FINALLY(&weight_sums, maxdeg+1); + + IGRAPH_CHECK(igraph_vector_resize(knnk, maxdeg+1)); + igraph_vector_null(knnk); + + for (igraph_int_t eid=0; eid < no_of_edges; eid++) { + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + igraph_int_t fromdeg = VECTOR(*deg_from)[from]; + igraph_int_t todeg = VECTOR(*deg_to)[to]; + igraph_real_t w = weights ? VECTOR(*weights)[eid] : 1; + + VECTOR(weight_sums)[fromdeg] += w; + VECTOR(*knnk)[fromdeg] += w * todeg; + + /* Treat undirected edges as reciprocal directed ones */ + if (! directed_neighbors) { + VECTOR(weight_sums)[todeg] += w; + VECTOR(*knnk)[todeg] += w * fromdeg; + } + } + + IGRAPH_CHECK(igraph_vector_div(knnk, &weight_sums)); + + igraph_vector_destroy(&weight_sums); + IGRAPH_FINALLY_CLEAN(1); + + /* In reverse order of initialization: */ + + if (have_all) { + igraph_vector_int_destroy(°_all); + IGRAPH_FINALLY_CLEAN(1); + } + + if (have_in) { + igraph_vector_int_destroy(°_in); + IGRAPH_FINALLY_CLEAN(1); + } + + if (have_out) { + igraph_vector_int_destroy(°_out); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t strength_all( + const igraph_t *graph, igraph_vector_t *res, + igraph_neimode_t mode, igraph_bool_t loops, + const igraph_vector_t *weights) { + + // When calculating strength for all vertices, iterating over edges is faster + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (loops) { + if (mode & IGRAPH_OUT) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + VECTOR(*res)[IGRAPH_FROM(graph, edge)] += VECTOR(*weights)[edge]; + } + } + if (mode & IGRAPH_IN) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + VECTOR(*res)[IGRAPH_TO(graph, edge)] += VECTOR(*weights)[edge]; + } + } + } else { + if (mode & IGRAPH_OUT) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + if (from != IGRAPH_TO(graph, edge)) { + VECTOR(*res)[from] += VECTOR(*weights)[edge]; + } + } + } + if (mode & IGRAPH_IN) { + for (igraph_int_t edge = 0; edge < no_of_edges; ++edge) { + igraph_int_t to = IGRAPH_TO(graph, edge); + if (IGRAPH_FROM(graph, edge) != to) { + VECTOR(*res)[to] += VECTOR(*weights)[edge]; + } + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_strength + * \brief Strength of the vertices, also called weighted vertex degree. + * + * In a weighted network the strength of a vertex is the sum of the + * weights of all incident edges. In a non-weighted network this is + * exactly the vertex degree. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param mode Gives whether to count only outgoing (\c IGRAPH_OUT), + * incoming (\c IGRAPH_IN) edges or both (\c IGRAPH_ALL). + * This parameter is ignored for undirected graphs. + * \param loops Constant of type \ref igraph_loops_t. Specifies how to treat + * loop edges when calculating the strength. \c IGRAPH_NO_LOOPS ignores loop + * edges; \c IGRAPH_LOOPS_ONCE counts each loop edge only once; + * \c IGRAPH_LOOPS_TWICE counts each loop edge twice in undirected graphs and + * once in directed graphs. + * \param weights A vector giving the edge weights. If this is a \c NULL + * pointer, then \ref igraph_degree() is called to perform the + * calculation. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number vertices and + * edges. + * + * \sa \ref igraph_degree() for the traditional, non-weighted version. + */ +igraph_error_t igraph_strength( + const igraph_t *graph, igraph_vector_t *res, const igraph_vs_t vids, + igraph_neimode_t mode, igraph_loops_t loops, const igraph_vector_t *weights +) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_int_t no_vids; + igraph_vector_int_t degrees; + igraph_vector_int_t neis; + + if (! weights) { + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + IGRAPH_CHECK(igraph_degree(graph, °rees, vids, mode, loops)); + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + VECTOR(*res)[i] = VECTOR(degrees)[i]; + } + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode for vertex strength calculation.", IGRAPH_EINVMODE); + } + + if (igraph_vs_is_all(&vids)) { + return strength_all(graph, res, mode, loops, weights); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&neis, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_resize(res, no_vids)); + igraph_vector_null(res); + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + IGRAPH_CHECK(igraph_incident(graph, &neis, IGRAPH_VIT_GET(vit), mode, loops)); + const igraph_int_t n = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t edge = VECTOR(neis)[j]; + VECTOR(*res)[i] += VECTOR(*weights)[edge]; + } + } + + igraph_vit_destroy(&vit); + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_sort_vertex_ids_by_degree + * \brief Calculate a list of vertex IDs sorted by degree of the corresponding vertex. + * + * The list of vertex IDs is returned in a vector that is sorted + * in ascending or descending order of vertex degree. + * + * \param graph The input graph. + * \param outvids Pointer to an initialized vector that will be + * resized and will contain the ordered vertex IDs. + * \param vids Input vertex selector of vertex IDs to include in + * calculation. + * \param mode Defines the type of the degree. + * \c IGRAPH_OUT, out-degree, + * \c IGRAPH_IN, in-degree, + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Specifies how to treat loop edges when calculating the + * degrees. \c IGRAPH_NO_LOOPS ignores loop edges; \c IGRAPH_LOOPS_ONCE + * counts each loop edge only once; \c IGRAPH_LOOPS_TWICE counts each + * loop edge twice in undirected graphs and once in directed graphs. + * \param order Specifies whether the ordering should be ascending + * (\c IGRAPH_ASCENDING) or descending (\c IGRAPH_DESCENDING). + * \param only_indices If true, then return a sorted list of indices + * into a vector corresponding to \c vids, rather than a list + * of vertex IDs. This parameter is ignored if \c vids is set + * to all vertices via \ref igraph_vs_all() or \ref igraph_vss_all(), + * because in this case the indices and vertex IDs are the + * same. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex ID. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + */ +igraph_error_t igraph_sort_vertex_ids_by_degree( + const igraph_t *graph, igraph_vector_int_t *outvids, + igraph_vs_t vids, igraph_neimode_t mode, igraph_loops_t loops, + igraph_order_t order, igraph_bool_t only_indices +) { + igraph_int_t i, n; + igraph_vector_int_t degrees; + igraph_vector_int_t vs_vec; + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, 0); + IGRAPH_CHECK(igraph_degree(graph, °rees, vids, mode, loops)); + IGRAPH_CHECK(igraph_vector_int_sort_ind(°rees, outvids, order)); + if (only_indices || igraph_vs_is_all(&vids) ) { + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_VECTOR_INT_INIT_FINALLY(&vs_vec, 0); + IGRAPH_CHECK(igraph_vs_as_vector(graph, vids, &vs_vec)); + n = igraph_vector_int_size(outvids); + for (i = 0; i < n; i++) { + VECTOR(*outvids)[i] = VECTOR(vs_vec)[VECTOR(*outvids)[i]]; + } + igraph_vector_int_destroy(&vs_vec); + igraph_vector_int_destroy(°rees); + IGRAPH_FINALLY_CLEAN(2); + } + return IGRAPH_SUCCESS; +} diff --git a/src/properties/ecc.c b/src/properties/ecc.c new file mode 100644 index 0000000..4a5dd7b --- /dev/null +++ b/src/properties/ecc.c @@ -0,0 +1,385 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_transitivity.h" + +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "igraph_adjlist.h" + +#include "core/interruption.h" + +/* This implementation of ECC relies on (lazy_)adjlist_get() producing sorted + * neighbor lists and (lazy_)adjlist_init() being called with IGRAPH_NO_LOOPS + * and IGRAPH_NO_MULTIPLE to prevent duplicate entries. + */ + +/* Optimized for the case when computing ECC for all edges. */ +static igraph_error_t igraph_i_ecc3_1( + const igraph_t *graph, igraph_vector_t *res, const igraph_es_t eids, + igraph_bool_t offset, igraph_bool_t normalize) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degree; + igraph_adjlist_t al; + igraph_eit_t eit; + const igraph_real_t c = offset ? 1.0 : 0.0; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (igraph_int_t i=0; + ! IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), i++) { + + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t v1 = IGRAPH_FROM(graph, edge), v2 = IGRAPH_TO(graph, edge); + + igraph_real_t z; /* number of triangles the edge participates in */ + igraph_real_t s; /* max number of triangles the edge could be part of */ + + IGRAPH_ALLOW_INTERRUPTION(); + + if (v1 == v2) { + /* A self-loop isn't, and cannot be part of any triangles. */ + z = 0.0; + s = 0.0; + } else { + const igraph_vector_int_t *a1 = igraph_adjlist_get(&al, v1), *a2 = igraph_adjlist_get(&al, v2); + igraph_int_t d1 = VECTOR(degree)[v1], d2 = VECTOR(degree)[v2]; + + z = igraph_vector_int_intersection_size_sorted(a1, a2); + s = (d1 < d2 ? d1 : d2) - 1.0; + } + + VECTOR(*res)[i] = z + c; + if (normalize) VECTOR(*res)[i] /= s; + } + + igraph_eit_destroy(&eit); + igraph_vector_int_destroy(°ree); + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Optimized for computing ECC for a small subset of edges. */ +static igraph_error_t igraph_i_ecc3_2( + const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t eids, igraph_bool_t offset, igraph_bool_t normalize) { + + igraph_lazy_adjlist_t al; + igraph_eit_t eit; + const igraph_real_t c = offset ? 1.0 : 0.0; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (igraph_int_t i=0; + ! IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), i++) { + + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t v1 = IGRAPH_FROM(graph, edge), v2 = IGRAPH_TO(graph, edge); + + igraph_real_t z; /* number of triangles the edge participates in */ + igraph_real_t s; /* max number of triangles the edge could be part of */ + + IGRAPH_ALLOW_INTERRUPTION(); + + if (v1 == v2) { + /* A self-loop isn't, and cannot be part of any triangles. */ + z = 0.0; + s = 0.0; + } else { + igraph_vector_int_t *a1 = igraph_lazy_adjlist_get(&al, v1); + igraph_vector_int_t *a2 = igraph_lazy_adjlist_get(&al, v2); + + igraph_int_t d1, d2; + IGRAPH_CHECK(igraph_degree_1(graph, &d1, v1, IGRAPH_ALL, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree_1(graph, &d2, v2, IGRAPH_ALL, IGRAPH_LOOPS)); + + z = igraph_vector_int_intersection_size_sorted(a1, a2); + s = (d1 < d2 ? d1 : d2) - 1.0; + } + + VECTOR(*res)[i] = z + c; + if (normalize) VECTOR(*res)[i] /= s; + } + + igraph_eit_destroy(&eit); + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/* Optimized for the case when computing ECC for all edges. */ +static igraph_error_t igraph_i_ecc4_1( + const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t eids, igraph_bool_t offset, igraph_bool_t normalize) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t degree; + igraph_adjlist_t al; + igraph_eit_t eit; + igraph_real_t c = offset ? 1.0 : 0.0; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (igraph_int_t i=0; + ! IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), i++) { + + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t v1 = IGRAPH_FROM(graph, edge), v2 = IGRAPH_TO(graph, edge); + + igraph_real_t z; /* number of 4-cycles the edge participates in */ + igraph_real_t s; /* max number of 4-cycles the edge could be part of */ + + IGRAPH_ALLOW_INTERRUPTION(); + + if (v1 == v2) { + z = 0.0; + s = 0.0; + } else { + /* ensure that v1 is the vertex with the smaller degree */ + if (VECTOR(degree)[v1] > VECTOR(degree)[v2]) { + igraph_int_t tmp = v1; + v1 = v2; + v2 = tmp; + } + + z = 0.0; + const igraph_vector_int_t *a1 = igraph_adjlist_get(&al, v1); + const igraph_int_t n = igraph_vector_int_size(a1); + for (igraph_int_t j=0; j < n; j++) { + igraph_int_t v3 = VECTOR(*a1)[j]; + + /* It is not possible that v3 == v1 because self-loops have been removed from the adjlist. */ + + if (v3 == v2) continue; + + const igraph_vector_int_t *a2 = igraph_adjlist_get(&al, v2), *a3 = igraph_adjlist_get(&al, v3); + + z += igraph_vector_int_intersection_size_sorted(a2, a3) - 1.0; + } + + igraph_int_t d1 = VECTOR(degree)[v1], d2 = VECTOR(degree)[v2]; + s = (d1 - 1.0) * (d2 - 1.0); + } + + VECTOR(*res)[i] = z + c; + if (normalize) VECTOR(*res)[i] /= s; + } + + igraph_eit_destroy(&eit); + igraph_vector_int_destroy(°ree); + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Optimized for computing ECC for a small subset of edges. */ +static igraph_error_t igraph_i_ecc4_2( + const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t eids, igraph_bool_t offset, igraph_bool_t normalize) { + + igraph_lazy_adjlist_t al; + igraph_eit_t eit; + igraph_real_t c = offset ? 1.0 : 0.0; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (igraph_int_t i=0; + ! IGRAPH_EIT_END(eit); + IGRAPH_EIT_NEXT(eit), i++) { + + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t v1 = IGRAPH_FROM(graph, edge), v2 = IGRAPH_TO(graph, edge); + + igraph_real_t z; /* number of 4-cycles the edge participates in */ + igraph_real_t s; /* max number of 4-cycles the edge could be part of */ + + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_int_t d1, d2; + IGRAPH_CHECK(igraph_degree_1(graph, &d1, v1, IGRAPH_ALL, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree_1(graph, &d2, v2, IGRAPH_ALL, IGRAPH_LOOPS)); + + if (v1 == v2) { + z = 0.0; + s = 0.0; + } else { + /* ensure that v1 is the vertex with the smaller degree */ + if (d1 > d2) { + igraph_int_t tmp = v1; + v1 = v2; + v2 = tmp; + + tmp = d1; + d1 = d2; + d2 = tmp; + } + + z = 0.0; + + igraph_vector_int_t *a1 = igraph_lazy_adjlist_get(&al, v1); + + const igraph_int_t n = igraph_vector_int_size(a1); + for (igraph_int_t j=0; j < n; j++) { + igraph_int_t v3 = VECTOR(*a1)[j]; + + /* It is not possible that v3 == v1 because self-loops have been removed from the adjlist. */ + + if (v3 == v2) continue; + + igraph_vector_int_t *a2 = igraph_lazy_adjlist_get(&al, v2); + igraph_vector_int_t *a3 = igraph_lazy_adjlist_get(&al, v3); + + z += igraph_vector_int_intersection_size_sorted(a2, a3) - 1.0; + } + + s = (d1 - 1.0) * (d2 - 1.0); + } + + VECTOR(*res)[i] = z + c; + if (normalize) VECTOR(*res)[i] /= s; + } + + igraph_eit_destroy(&eit); + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_ecc + * \brief Edge clustering coefficient of some edges. + * + * The edge clustering coefficient C^(k)_ij of an edge (i, j) + * is defined based on the number of k-cycles the edge participates in, + * z^(k)_ij, and the largest number of such cycles it could + * participate in given the degrees of its endpoints, s^(k)_ij. + * The original definition given in the reference below is: + * + * + * C^(k)_ij = (z^(k)_ij + 1) / s^(k)_ij + * + * + * For k=3, s^(k)_ij = min(d_i - 1, d_j - 1), + * where \c d_i and \c d_j are the edge endpoint degrees. + * For k=4, s^(k)_ij = (d_i - 1) (d_j - 1). + * + * + * The \p normalize and \p offset parameters allow for skipping normalization + * by s^(k) and offsetting the cycle count z^(k) + * by one in the numerator of C^(k). Set both to \c true to + * compute the original definition of this metric. + * + * + * This function ignores edge multiplicities when listing k-cycles + * (i.e. z^(k)), but not when computing the maximum number of + * cycles an edge can participate in (s^(k)). + * + * + * Reference: + * + * + * F. Radicchi, C. Castellano, F. Cecconi, V. Loreto, and D. Parisi, + * PNAS 101, 2658 (2004). + * https://doi.org/10.1073/pnas.0400054101 + * + * \param graph The input graph. + * \param res Initialized vector, the result will be stored here. + * \param eids The edges for which the edge clustering coefficient will be computed. + * \param k Size of cycles to use in calculation. Must be at least 3. Currently + * only values of 3 and 4 are supported. + * \param offset Boolean, whether to add one to cycle counts. When \c false, + * z^(k) is used instead of z^(k) + 1. In this case + * the maximum value of the normalized metric is 1. For k=3 this + * is achieved for all edges in a complete graph. + * \param normalize Boolean, whether to normalize cycle counts by the maximum + * possible count s^(k) given the degrees. + * \return Error code. + * + * Time complexity: When \p k is 3, O(|V| d log d + |E| d). + * When \p k is 4, O(|V| d log d + |E| d^2). d denotes the degree of vertices. + */ +igraph_error_t igraph_ecc(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t eids, igraph_int_t k, + igraph_bool_t offset, igraph_bool_t normalize) { + + if (k < 3) { + IGRAPH_ERRORF("Cycle size for edge clustering coefficient must be at least 3, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, k); + } + + switch (k) { + case 3: + if (igraph_es_is_all(&eids)) { + return igraph_i_ecc3_1(graph, res, eids, offset, normalize); + } else { + return igraph_i_ecc3_2(graph, res, eids, offset, normalize); + } + case 4: + if (igraph_es_is_all(&eids)) { + return igraph_i_ecc4_1(graph, res, eids, offset, normalize); + } else { + return igraph_i_ecc4_2(graph, res, eids, offset, normalize); + } + default: + IGRAPH_ERROR("Edge clustering coefficient calculation is only implemented for cycle sizes 3 and 4.", + IGRAPH_UNIMPLEMENTED); + } +} diff --git a/src/properties/girth.c b/src/properties/girth.c new file mode 100644 index 0000000..c5195f5 --- /dev/null +++ b/src/properties/girth.c @@ -0,0 +1,216 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" + +#include "igraph_adjlist.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" + +#include "core/interruption.h" + +/** + * \function igraph_girth + * \brief The girth of a graph is the length of the shortest cycle in it. + * + * The current implementation works for undirected graphs only, + * directed graphs are treated as undirected graphs. Self-loops and + * multiple edges are ignored, i.e. cycles of length 1 or 2 are + * not considered. + * + * + * For graphs that contain no cycles, and only for such graphs, + * infinity is returned. + * + * + * The first implementation of this function was done by Keith Briggs, + * thanks Keith. + * + * + * Reference: + * + * + * Alon Itai and Michael Rodeh: + * Finding a minimum circuit in a graph + * \emb Proceedings of the ninth annual ACM symposium on Theory of + * computing \eme, 1-10, 1977. + * https://doi.org/10.1145/800105.803390 + * + * \param graph The input graph. Edge directions will be ignored. + * \param girth Pointer to an \c igraph_real_t, if not \c NULL then the result + * will be stored here. + * \param cycle Pointer to an initialized vector, the vertex IDs in + * the shortest cycle of length at least 3 will be stored here. + * If \c NULL then it is ignored. + * \return Error code. + * + * Time complexity: O((|V|+|E|)^2), |V| is the number of vertices, |E| + * is the number of edges in the general case. If the graph has no + * cycles at all then the function needs O(|V|+|E|) time to realize + * this and then it stops. + * + * \example examples/simple/igraph_girth.c + */ +igraph_error_t igraph_girth(const igraph_t *graph, + igraph_real_t *girth, + igraph_vector_int_t *cycle) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_lazy_adjlist_t adjlist; + igraph_int_t mincirc = IGRAPH_INTEGER_MAX, minvertex = 0; + igraph_int_t node; + igraph_bool_t triangle = false; + igraph_vector_int_t *neis; + igraph_vector_int_t level; + igraph_int_t stoplevel = no_of_nodes + 1; + igraph_bool_t anycircle = false; + igraph_int_t t1 = 0, t2 = 0; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&level, no_of_nodes); + + for (node = 0; !triangle && node < no_of_nodes; node++) { + + /* Are there circles in this graph at all? */ + if (node == 1 && anycircle == 0) { + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (conn) { + /* No, there are none */ + break; + } + } + + anycircle = 0; + igraph_dqueue_int_clear(&q); + igraph_vector_int_null(&level); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + VECTOR(level)[node] = 1; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actlevel = VECTOR(level)[actnode]; + igraph_int_t i, n; + + if (actlevel >= stoplevel) { + break; + } + + neis = igraph_lazy_adjlist_get(&adjlist, actnode); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + igraph_int_t neilevel = VECTOR(level)[nei]; + if (neilevel != 0) { + if (neilevel == actlevel - 1) { + continue; + } else { + /* found circle */ + stoplevel = neilevel; + anycircle = 1; + if (actlevel < mincirc) { + /* Is it a minimum circle? */ + mincirc = actlevel + neilevel - 1; + minvertex = node; + t1 = actnode; t2 = nei; + if (neilevel == 2) { + /* Is it a triangle? */ + triangle = 1; + } + } + if (neilevel == actlevel) { + break; + } + } + } else { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + VECTOR(level)[nei] = actlevel + 1; + } + } + + } /* while q !empty */ + } /* node */ + + if (girth) { + if (mincirc == IGRAPH_INTEGER_MAX) { + *girth = IGRAPH_INFINITY; + } else { + *girth = mincirc; + } + } + + if (mincirc == IGRAPH_INTEGER_MAX) { + mincirc = 0; + } + + /* Store the actual circle, if needed */ + if (cycle) { + IGRAPH_CHECK(igraph_vector_int_resize(cycle, mincirc)); + if (mincirc != 0) { + igraph_int_t i, n, idx = 0; + igraph_dqueue_int_clear(&q); + igraph_vector_int_null(&level); /* used for parent pointers */ +#define PARENT(x) (VECTOR(level)[(x)]) + IGRAPH_CHECK(igraph_dqueue_int_push(&q, minvertex)); + PARENT(minvertex) = minvertex; + while (PARENT(t1) == 0 || PARENT(t2) == 0) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + neis = igraph_lazy_adjlist_get(&adjlist, actnode); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + igraph_int_t nei = VECTOR(*neis)[i]; + if (PARENT(nei) == 0) { + PARENT(nei) = actnode + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + } + } + } /* while q !empty */ + /* Ok, now use PARENT to create the path */ + while (t1 != minvertex) { + VECTOR(*cycle)[idx++] = t1; + t1 = PARENT(t1) - 1; + } + VECTOR(*cycle)[idx] = minvertex; + idx = mincirc - 1; + while (t2 != minvertex) { + VECTOR(*cycle)[idx--] = t2; + t2 = PARENT(t2) - 1; + } + } /* anycircle */ + } /* circle */ +#undef PARENT + + igraph_vector_int_destroy(&level); + igraph_dqueue_int_destroy(&q); + igraph_lazy_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/loops.c b/src/properties/loops.c new file mode 100644 index 0000000..e77a7de --- /dev/null +++ b/src/properties/loops.c @@ -0,0 +1,161 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" + +#include "igraph_interface.h" + +/** + * \function igraph_has_loop + * \brief Returns whether the graph has at least one loop edge. + * + * + * A loop edge is an edge from a vertex to itself. + * + * + * The return value of this function is cached in the graph itself; calling + * the function multiple times with no modifications to the graph in between + * will return a cached value in O(1) time. + * + * \param graph The input graph. + * \param res Pointer to an initialized boolean vector for storing the result. + * \return Error code. + * + * \sa \ref igraph_simplify() to get rid of loop edges. + * + * Time complexity: O(e), the number of edges to check. + * + * \example examples/simple/igraph_is_loop.c + */ +igraph_error_t igraph_has_loop(const igraph_t *graph, igraph_bool_t *res) { + igraph_int_t i, m = igraph_ecount(graph); + + IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_HAS_LOOP, res); + + *res = false; + + for (i = 0; i < m; i++) { + if (IGRAPH_FROM(graph, i) == IGRAPH_TO(graph, i)) { + *res = true; + break; + } + } + + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, *res); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_loop + * \brief Find the loop edges in a graph. + * + * A loop edge, also called a self-loop, is an edge from a vertex to itself. + * + * \param graph The input graph. + * \param res Pointer to an initialized boolean vector for storing the result, + * it will be resized as needed. + * \param es The edges to check, for all edges supply \ref igraph_ess_all() here. + * \return Error code. + * + * \sa \ref igraph_simplify() to get rid of loop edges. + * + * Time complexity: O(e), the number of edges to check. + * + * \example examples/simple/igraph_is_loop.c + */ +igraph_error_t igraph_is_loop(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es) { + igraph_eit_t eit; + igraph_bool_t found_loop = false; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + ! igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + igraph_vector_bool_null(res); + goto done; + } + + for (igraph_int_t i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + igraph_bool_t is_loop = (IGRAPH_FROM(graph, e) == IGRAPH_TO(graph, e)); + VECTOR(*res)[i] = is_loop; + if (is_loop) { + found_loop = true; + } + } + + if (found_loop) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, true); + } else if (igraph_es_is_all(&es)) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false); + } + +done: + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_loops + * \brief Counts the self-loops in the graph. + * + * Counts loop edges, i.e. edges whose two endpoints coincide. + * + * \param graph The input graph. + * \param loop_count Pointer to an integer, the number of self-loops will be stored here. + * \return Error code. + * + * Time complexity: O(|E|), linear in the number of edges. + * + * \example examples/simple/igraph_is_loop.c + */ +igraph_error_t igraph_count_loops(const igraph_t *graph, igraph_int_t *loop_count) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t count; + + /* Nothing to do if we know that there are no loops. */ + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) && + !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) { + *loop_count = 0; + return IGRAPH_SUCCESS; + } + + count = 0; + for (igraph_int_t e=0; e < no_of_edges; e++) { + if (IGRAPH_FROM(graph, e) == IGRAPH_TO(graph, e)) { + count++; + } + } + + /* We already checked for loops, so take the opportunity to set the cache. */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, count > 0); + + *loop_count = count; + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/multiplicity.c b/src/properties/multiplicity.c new file mode 100644 index 0000000..51b4824 --- /dev/null +++ b/src/properties/multiplicity.c @@ -0,0 +1,563 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" + +#include "igraph_adjlist.h" +#include "igraph_interface.h" + +/** + * \function igraph_is_simple + * \brief Decides whether the input graph is a simple graph. + * + * A graph is a simple graph if it does not contain loop edges and + * multiple edges. + * + * \param graph The input graph. + * \param res Pointer to a boolean constant, the result + * is stored here. + * \param directed Whether to consider the directions of edges. \c IGRAPH_UNDIRECTED + * means that edge directions will be ignored and a directed graph with at + * least one mutual edge pair will be considered non-simple. \c IGRAPH_DIRECTED + * means that edge directions will be considered. Ignored for + * undirected graphs. + * \return Error code. + * + * \sa \ref igraph_is_loop() and \ref igraph_is_multiple() to + * find the loops and multiple edges, \ref igraph_simplify() to + * get rid of them, or \ref igraph_has_multiple() to decide whether + * there is at least one multiple edge. + * + * Time complexity: O(|V|+|E|). + */ +igraph_error_t igraph_is_simple(const igraph_t *graph, igraph_bool_t *res, igraph_bool_t directed) { + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t ec = igraph_ecount(graph); + + /* Is it already known whether the graph has loops or multi-edges? */ + igraph_bool_t known_loop, known_multi; + + /* If it is known, does the graph have them? */ + igraph_bool_t has_loop, has_multi; + + /* Will we need to check mutual edges explicitly? */ + igraph_bool_t need_to_check_mutual = directed == IGRAPH_UNDIRECTED && igraph_is_directed(graph); + + known_loop = igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP); + if (known_loop) { + has_loop = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP); + if (has_loop) { + *res = false; + goto early_exit; + } + } + + known_multi = igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI); + if (known_multi) { + has_multi = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI); + if (has_multi) { + *res = false; + goto early_exit; + } + } + + if (known_loop && known_multi && !has_loop && !has_multi) { + *res = true; + goto early_exit; + } + + /* Up to now, these variables were used to store the cache status. + * From here on, we re-use them to store the outcome of explicit + * checks. */ + + known_loop = known_multi = false; + has_loop = has_multi = false; /* be optimistic */ + + if (vc == 0 || ec == 0) { + *res = true; + known_loop = known_multi = true; + } else { + igraph_vector_int_t neis; + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + for (igraph_int_t i = 0; i < vc; i++) { + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + const igraph_int_t n = igraph_vector_int_size(&neis); + for (igraph_int_t j = 0; j < n; j++) { + if (VECTOR(neis)[j] == i) { + known_loop = true; has_loop = true; break; + } + /* Attention: If the graph is undirected, self-loops appears + * twice in the neighbour list. This does not mean that there + * are multi-edges. We do not need to worry about this as loop + * are already caught above. */ + if (j > 0 && VECTOR(neis)[j - 1] == VECTOR(neis)[j]) { + known_multi = true; has_multi = true; break; + } + } + } + *res = !has_loop && !has_multi; + if (*res) { + known_multi = known_loop = true; + } + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + if (known_loop) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, has_loop); + } + + if (known_multi) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, has_multi); + } + +early_exit: + /* If at this point we have concluded that the graph is simple, _but_ the user + * asked us to ignore edge directions, we need to look for mutual edge pairs + * and return false if we find one. */ + if (*res && need_to_check_mutual) { + /* If the graph is undirected, we also check for mutual edges. */ + igraph_bool_t has_mutual; + IGRAPH_CHECK(igraph_has_mutual(graph, &has_mutual, false)); + if (has_mutual) { + *res = false; + } + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_has_multiple + * \brief Check whether the graph has at least one multiple edge. + * + * An edge is a multiple edge if there is another + * edge with the same head and tail vertices in the graph. + * + * + * The return value of this function is cached in the graph itself; calling + * the function multiple times with no modifications to the graph in between + * will return a cached value in O(1) time. + * + * \param graph The input graph. + * \param res Pointer to a boolean variable, the result will be stored here. + * \return Error code. + * + * \sa \ref igraph_count_multiple(), \ref igraph_is_multiple() and \ref igraph_simplify(). + * + * Time complexity: O(e*d), e is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + * + * \example examples/simple/igraph_has_multiple.c + */ +igraph_error_t igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res) { + igraph_int_t vc = igraph_vcount(graph); + igraph_int_t ec = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_HAS_MULTI, res); + + if (vc == 0 || ec == 0) { + *res = false; + } else { + igraph_vector_int_t neis; + igraph_int_t i, j, n; + igraph_bool_t found = false; + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + for (i = 0; i < vc && !found; i++) { + IGRAPH_CHECK(igraph_neighbors( + graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + n = igraph_vector_int_size(&neis); + for (j = 1; j < n; j++) { + if (VECTOR(neis)[j - 1] == VECTOR(neis)[j]) { + /* If the graph is undirected, loop edges appear twice in the neighbor + * list, so check the next item as well */ + if (directed) { + /* Directed, so this is a real multiple edge */ + found = true; break; + } else if (VECTOR(neis)[j - 1] != i) { + /* Undirected, but not a loop edge */ + found = true; break; + } else if (j < n - 1 && VECTOR(neis)[j] == VECTOR(neis)[j + 1]) { + /* Undirected, loop edge, multiple times */ + found = true; break; + } + } + } + } + *res = found; + igraph_vector_int_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, *res); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_multiple + * \brief Find the multiple edges in a graph. + * + * An edge is a multiple edge if there is another + * edge with the same head and tail vertices in the graph. + * + * + * Note that this function returns true only for the second or more + * appearances of the multiple edges. + * + * \param graph The input graph. + * \param res Pointer to a boolean vector, the result will be stored + * here. It will be resized as needed. + * \param es The edges to check. Supply \ref igraph_ess_all() if you want + * to check all edges. + * \return Error code. + * + * \sa \ref igraph_count_multiple(), \ref igraph_count_multiple_1(), + * \ref igraph_has_multiple() and \ref igraph_simplify(). + * + * Time complexity: O(e*d), e is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + * + * \example examples/simple/igraph_is_multiple.c + */ +igraph_error_t igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es) { + igraph_eit_t eit; + igraph_int_t i, j, n; + igraph_lazy_inclist_t inclist; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, from); + + IGRAPH_CHECK_OOM(neis, "Failed to query incident edges."); + + VECTOR(*res)[i] = false; + + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + igraph_int_t e2 = VECTOR(*neis)[j]; + igraph_int_t to2 = IGRAPH_OTHER(graph, e2, from); + if (to2 == to && e2 < e) { + VECTOR(*res)[i] = true; + } + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_multiple + * \brief The multiplicity of some edges in a graph. + * + * An edge is called a multiple edge when there is one or more other + * edge between the same two vertices. The multiplicity of an edge + * is the number of edges between its endpoints. + * + * \param graph The input graph. + * \param res Pointer to a vector, the result will be stored + * here. It will be resized as needed. + * \param es The edges to check. Supply \ref igraph_ess_all() if you want + * to check all edges. + * \return Error code. + * + * \sa \ref igraph_count_multiple_1() if you only need the multiplicity of a + * single edge; \ref igraph_is_multiple() if you are only interested in whether + * the graph has at least one edge with multiplicity greater than one; + * \ref igraph_simplify() to ensure that the graph has no multiple edges. + * + * Time complexity: O(E d), E is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + */ +igraph_error_t igraph_count_multiple(const igraph_t *graph, igraph_vector_int_t *res, + igraph_es_t es) { + igraph_eit_t eit; + igraph_int_t i, j, n; + igraph_lazy_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_vector_int_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + igraph_int_t e = IGRAPH_EIT_GET(eit); + igraph_int_t from = IGRAPH_FROM(graph, e); + igraph_int_t to = IGRAPH_TO(graph, e); + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, from); + + IGRAPH_CHECK_OOM(neis, "Failed to query adjacent vertices."); + + VECTOR(*res)[i] = 0; + + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + if (VECTOR(*neis)[j] == to) { + VECTOR(*res)[i]++; + } + } + } + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_count_multiple_1 + * \brief The multiplicity of a single edge in a graph. + * + * \param graph The input graph. + * \param res Pointer to an iteger, the result will be stored here. + * \param eid The ID of the edge to check. + * \return Error code. + * + * \sa \ref igraph_count_multiple() if you need the multiplicity of multiple + * edges; \ref igraph_is_multiple() if you are only interested in whether the + * graph has at least one edge with multiplicity greater than one; + * \ref igraph_simplify() to ensure that the graph has no multiple edges. + * + * Time complexity: O(d), where d is the out-degree of the tail of the edge. + */ +igraph_error_t igraph_count_multiple_1(const igraph_t *graph, igraph_int_t *res, + igraph_int_t eid) +{ + igraph_int_t i, n, count; + igraph_int_t from = IGRAPH_FROM(graph, eid); + igraph_int_t to = IGRAPH_TO(graph, eid); + igraph_vector_int_t vids; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0); + IGRAPH_CHECK(igraph_neighbors( + graph, &vids, from, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE + )); + + count = 0; + n = igraph_vector_int_size(&vids); + for (i = 0; i < n; i++) { + if (VECTOR(vids)[i] == to) { + count++; + } + } + + igraph_vector_int_destroy(&vids); + IGRAPH_FINALLY_CLEAN(1); + + *res = count; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_mutual + * \brief Check whether some edges of a directed graph are mutual. + * + * An (A,B) non-loop directed edge is mutual if the graph contains + * the (B,A) edge too. Whether directed self-loops are considered mutual + * is controlled by the \p loops parameter. + * + * + * An undirected graph only has mutual edges, by definition. + * + * + * Edge multiplicity is not considered here, e.g. if there are two + * (A,B) edges and one (B,A) edge, then all three are considered to be + * mutual. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here. + * \param es The sequence of edges to check. Supply + * \ref igraph_ess_all() to check all edges. + * \param loops Boolean, whether to consider directed self-loops + * to be mutual. + * \return Error code. + * + * Time complexity: O(n log(d)), n is the number of edges supplied, d + * is the maximum in-degree of the vertices that are targets of the + * supplied edges. An upper limit of the time complexity is O(n log(|E|)), + * |E| is the number of edges in the graph. + */ +igraph_error_t igraph_is_mutual(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es, igraph_bool_t loops) { + + igraph_eit_t eit; + igraph_lazy_adjlist_t adjlist; + igraph_int_t i; + + /* How many edges do we have? */ + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + /* An undirected graph has mutual edges by definition, + res is already properly resized */ + if (! igraph_is_directed(graph)) { + igraph_vector_bool_fill(res, true); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + for (i = 0; ! IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + + if (from == to) { + VECTOR(*res)[i] = loops; + continue; /* no need to do binsearch for self-loops */ + } + + /* Check whether there is a to->from edge, search for from in the + out-list of to */ + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, to); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + VECTOR(*res)[i] = igraph_vector_int_contains_sorted(neis, from); + } + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_has_mutual + * \brief Check whether a directed graph has any mutual edges. + * + * An (A,B) non-loop directed edge is mutual if the graph contains + * the (B,A) edge too. Whether directed self-loops are considered mutual + * is controlled by the \p loops parameter. + * + * + * In undirected graphs, all edges are considered mutual by definition. + * Thus for undirected graph, this function returns false only when there + * are no edges. + * + * + * To check whether a graph is an oriented graph, use this function in + * conjunction with \ref igraph_is_directed(). + * + * \param graph The input graph. + * \param res Pointer to a boolean, the result will be stored here. + * \param loops Boolean, whether to consider directed self-loops + * to be mutual. + * \return Error code. + * + * Time complexity: O(|E| log(d)) where d is the maximum in-degree. + */ +igraph_error_t igraph_has_mutual(const igraph_t *graph, igraph_bool_t *res, + igraph_bool_t loops) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_lazy_adjlist_t adjlist; + + if (! igraph_is_directed(graph)) { + /* In undirected graphs, all edges are considered mutual, so we just check + * if there are any edges. */ + *res = no_of_edges > 0; + return IGRAPH_SUCCESS; + } + + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MUTUAL)) { + if (igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MUTUAL)) { + /* we know that the graph has at least one mutual non-loop edge + * (because the cache only stores non-loop edges) */ + *res = true; + return IGRAPH_SUCCESS; + } else if (loops) { + /* no non-loop mutual edges, but maybe we have loops? */ + return igraph_has_loop(graph, res); + } else { + /* no non-loop mutual edges, and loops are not to be treated as mutual */ + *res = false; + return IGRAPH_SUCCESS; + } + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + *res = false; /* assume no mutual edges */ + for (igraph_int_t edge=0; edge < no_of_edges; edge++) { + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + + if (from == to) { + if (loops) { + *res = true; + break; + } + continue; /* no need to do binsearch for self-loops */ + } + + /* Check whether there is a to->from edge, search for from in the + out-list of to */ + igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, to); + IGRAPH_CHECK_OOM(neis, "Failed to query neighbors."); + if (igraph_vector_int_contains_sorted(neis, from)) { + *res = true; + break; + } + } + + igraph_lazy_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + /* cache the result if loops are not treated as mutual */ + if (!loops) { + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MUTUAL, *res); + } + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/neighborhood.c b/src/properties/neighborhood.c new file mode 100644 index 0000000..58e44a8 --- /dev/null +++ b/src/properties/neighborhood.c @@ -0,0 +1,458 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_neighborhood.h" + +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_operators.h" +#include "igraph_reachability.h" + +/** + * \function igraph_neighborhood_size + * \brief Calculates the size of the neighborhood of a given vertex. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. I.e., order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * + * This function calculates the size of the neighborhood + * of the given order for the given vertices. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. Negative values + * are treated as infinity. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \c order steps are counted. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \c order steps are counted. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * Vertices reachable with a path shorter than this value are excluded. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood() for calculating the actual neighborhood; + * \ref igraph_neighborhood_graphs() for creating separate graphs from + * the neighborhoods. + * + * Time complexity: O(n*d*o), where n is the number vertices for which + * the calculation is performed, d is the average degree, o is the order. + */ +igraph_error_t igraph_neighborhood_size(const igraph_t *graph, igraph_vector_int_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, + igraph_int_t mindist) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t *added; + igraph_vector_int_t neis; + igraph_bool_t inf_order = false; + + if (order < 0) { + order = no_of_nodes; + inf_order = true; + + if (mindist == 0 && igraph_vs_is_all(&vids)) { + return igraph_count_reachable(graph, res, mode); + } + } + + if (mindist < 0) { + IGRAPH_ERRORF("Minimum distance must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, mindist); + } + if (!inf_order && mindist > order) { + IGRAPH_ERRORF("Minimum distance must not exceed the neighborhood order (%" IGRAPH_PRId "), got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, order, mindist); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to calculate neighborhood sizes."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_vector_int_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + const igraph_int_t node = IGRAPH_VIT_GET(vit); + igraph_int_t size = mindist == 0 ? 1 : 0; + added[node] = i + 1; + igraph_dqueue_int_clear(&q); + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + const igraph_int_t actnode = igraph_dqueue_int_pop(&q); + const igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + const igraph_int_t n = igraph_vector_int_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + size++; + } + } + } + } else { + /* we just count them, but don't add them */ + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + size++; + } + } + } + } + + } /* while q not empty */ + + VECTOR(*res)[i] = size; + } /* for VIT, i */ + + igraph_vector_int_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_neighborhood + * \brief Calculate the neighborhood of vertices. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. I.e., order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * + * This function calculates the vertices within the + * neighborhood of the specified vertices. + * + * \param graph The input graph. + * \param res An initialized list of integer vectors. The result of the + * calculation will be stored here. The list will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. Negative values + * are treated as infinity. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \p order steps are included. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \p order steps are included. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * Vertices reachable with a path shorter than this value are excluded. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood_size() to calculate the size of the + * neighborhood; \ref igraph_neighborhood_graphs() for creating + * graphs from the neighborhoods; \ref igraph_subcomponent() to find vertices + * reachable from a single vertex. + * + * Time complexity: O(n*d*o), n is the number of vertices for which + * the calculation is performed, d is the average degree, o is the + * order. + */ +igraph_error_t igraph_neighborhood(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist) { + + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t *added; + igraph_vector_int_t neis; + igraph_vector_int_t tmp; + igraph_bool_t inf_order = false; + + if (order < 0) { + order = no_of_nodes; + inf_order = true; + } + + if (mindist < 0) { + IGRAPH_ERRORF("Minimum distance must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, mindist); + } + if (!inf_order && mindist > order) { + IGRAPH_ERRORF("Minimum distance must not exceed the neighborhood order (%" IGRAPH_PRId "), got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, order, mindist); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to calculate neighborhood sizes."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_int_list_reserve(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_int_list_clear(res); + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + const igraph_int_t node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_int_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, node)); + } + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + const igraph_int_t actnode = igraph_dqueue_int_pop(&q); + const igraph_int_t actdist = igraph_dqueue_int_pop(&q); + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + const igraph_int_t n = igraph_vector_int_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &tmp)); + } + + igraph_vector_int_destroy(&tmp); + igraph_vector_int_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_neighborhood_graphs + * \brief Create graphs from the neighborhood(s) of some vertex/vertices. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. Ie. order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * + * This function finds every vertex in the neighborhood + * of a given parameter vertex and creates the induced subgraph from these + * vertices. + * + * + * The first version of this function was written by + * Vincent Matossian, thanks Vincent. + * + * \param graph The input graph. + * \param res Pointer to a list of graphs, the result will be stored + * here. Each item in the list is an \type igraph_t object. The list will be + * resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. Negative values + * are treated as infinity. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \p order steps are counted. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \p order steps are counted. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * Vertices reachable with a path shorter than this value are excluded. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood_size() for calculating the neighborhood + * sizes only; \ref igraph_neighborhood() for calculating the + * neighborhoods (but not creating graphs). + * + * Time complexity: O(n*(|V|+|E|)), where n is the number vertices for + * which the calculation is performed, |V| and |E| are the number of + * vertices and edges in the original input graph. + */ +igraph_error_t igraph_neighborhood_graphs(const igraph_t *graph, igraph_graph_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, + igraph_int_t mindist) { + const igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t *added; + igraph_vector_int_t neis; + igraph_vector_int_t tmp; + igraph_t newg; + igraph_bool_t inf_order = false; + + if (order < 0) { + order = no_of_nodes; + inf_order = true; + } + + if (mindist < 0) { + IGRAPH_ERRORF("Minimum distance must not be negative, got %" IGRAPH_PRId ".", IGRAPH_EINVAL, mindist); + } + if (!inf_order && mindist > order) { + IGRAPH_ERRORF("Minimum distance must not exceed the neighborhood order (%" IGRAPH_PRId "), got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, order, mindist); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + IGRAPH_CHECK_OOM(added, "Insufficient memory to calculate neighborhood graphs."); + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + + igraph_graph_list_clear(res); + + for (igraph_int_t i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + const igraph_int_t node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_int_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, node)); + } + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + igraph_int_t n; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + n = igraph_vector_int_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (igraph_int_t j = 0; j < n; j++) { + const igraph_int_t nei = VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + if (igraph_vector_int_size(&tmp) < no_of_nodes) { + IGRAPH_CHECK(igraph_induced_subgraph(graph, &newg, + igraph_vss_vector(&tmp), + IGRAPH_SUBGRAPH_AUTO)); + } else { + IGRAPH_CHECK(igraph_copy(&newg, graph)); + } + + IGRAPH_FINALLY(igraph_destroy, &newg); + IGRAPH_CHECK(igraph_graph_list_push_back(res, &newg)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of `newg' taken by `res' */ + } + + igraph_vector_int_destroy(&tmp); + igraph_vector_int_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/perfect.c b/src/properties/perfect.c new file mode 100644 index 0000000..f931427 --- /dev/null +++ b/src/properties/perfect.c @@ -0,0 +1,190 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_structural.h" + +#include "igraph_bipartite.h" +#include "igraph_constructors.h" +#include "igraph_interface.h" +#include "igraph_operators.h" +#include "igraph_isomorphism.h" + +#include "core/interruption.h" + +/** + * \function igraph_is_perfect + * \brief Checks if the graph is perfect. + * + * A perfect graph is an undirected graph in which the chromatic number of every induced + * subgraph equals the order of the largest clique of that subgraph. + * The chromatic number of a graph G is the smallest number of colors needed to + * color the vertices of G so that no two adjacent vertices share the same color. + * + * + * Warning: This function may create the complement of the graph internally, + * which consumes a lot of memory. For moderately sized graphs, consider + * decomposing them into biconnected components and running the check separately + * on each component. + * + * + * This implementation is based on the strong perfect graph theorem which was + * conjectured by Claude Berge and proved by Maria Chudnovsky, Neil Robertson, + * Paul Seymour, and Robin Thomas. + * + * \param graph The input graph. It is expected to be undirected and simple. + * \param perfect Pointer to an integer, the result will be stored here. + * \return Error code. + * + * Time complexity: worst case exponenital, often faster in practice. + */ +igraph_error_t igraph_is_perfect(const igraph_t *graph, igraph_bool_t *perfect) { + + igraph_bool_t is_bipartite, is_chordal, iso, is_simple; + igraph_real_t girth, comp_girth; + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t start; + igraph_int_t cycle_len; + igraph_t comp_graph, cycle; + + // If the graph is directed return error. + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("The concept of perfect graphs is only defined for undirected graphs.", IGRAPH_EINVAL); + } + + // If the graph isn't simple then return an error. + IGRAPH_CHECK(igraph_is_simple(graph, &is_simple, IGRAPH_DIRECTED)); + if (!is_simple) { + IGRAPH_ERROR("Perfect graph testing is implemented for simple graphs only. Simplify the graph.", IGRAPH_EINVAL); + } + + // All graphs with less than 5 vertices are perfect. + if (no_of_nodes < 5) { + *perfect = true; + return IGRAPH_SUCCESS; + } + + // Graphs with less than 5 edges or a complement with less than 5 edges + // are also perfect. The following check handles most 5-vertex graphs, + // but its usefulness quickly diminishes, with only 0.3% of unlabelled + // 8-vertex graphs handled. + // In order to avoid bad results due to integer overflow with large graphs, + // we limit this check for small graphs only. + if ( no_of_nodes < 10000 && + (no_of_edges < 5 || no_of_edges > (no_of_nodes - 1) * no_of_nodes / 2 - 5)) { + *perfect = true; + return IGRAPH_SUCCESS; + } + + // Chordal and bipartite graph types are perfect. + // Possibly more optimizations found here: http://www.or.uni-bonn.de/~hougardy/paper/ClassesOfPerfectGraphs.pdf + IGRAPH_CHECK(igraph_is_bipartite(graph, &is_bipartite, NULL)); + if (is_bipartite) { + *perfect = true; + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_is_chordal(graph, NULL, NULL, &is_chordal, NULL, NULL)); + if (is_chordal) { + *perfect = true; + return IGRAPH_SUCCESS; + } + + // The weak perfect graph theorem: + // A graph is perfect iff its complement is perfect. + IGRAPH_CHECK(igraph_complementer(&comp_graph, graph, 0)); + IGRAPH_FINALLY(igraph_destroy, &comp_graph); + + IGRAPH_CHECK(igraph_is_bipartite(&comp_graph, &is_bipartite, NULL)); + if (is_bipartite) { + *perfect = true; + goto clean1; + } + + IGRAPH_CHECK(igraph_is_chordal(&comp_graph, NULL, NULL, &is_chordal, NULL, NULL)); + if (is_chordal) { + *perfect = true; + goto clean1; + } + + // Since igraph_is_bipartite also catches trees, at this point the girth + // of the graph and its complementer (to be stored in girth and comp_girth) + // are both guaranteed to be finite. + + // If the girth (or the smallest circle in the graph) is bigger than 3 and have odd number of vertices then + // the graph isn't perfect. + IGRAPH_CHECK(igraph_girth(graph, &girth, NULL)); + if ((girth > 3) && (((igraph_int_t)girth) % 2 == 1)) { + *perfect = false; + goto clean1; + } + + IGRAPH_CHECK(igraph_girth(&comp_graph, &comp_girth, NULL)); + if ((comp_girth > 3) && (((igraph_int_t)comp_girth) % 2 == 1)) { + *perfect = false; + goto clean1; + } + + // At this point girth and comp_girth are both at least 3. + + // Strong perfect graph theorem: + // A graph is perfect iff neither it or its complement contains an induced odd cycle of length >= 5 + // (i.e. an odd hole). TODO: Find a more efficient way to check for odd holes. + start = (igraph_int_t) (girth < comp_girth ? girth : comp_girth); + start = start % 2 == 0 ? start + 1 : start + 2; + for (cycle_len = start; cycle_len <= no_of_nodes ; cycle_len += 2) { + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_ring(&cycle, cycle_len, IGRAPH_UNDIRECTED, /* mutual */ 0, /* circular */ 1)); + IGRAPH_FINALLY(igraph_destroy, &cycle); + + if (cycle_len > girth) { + IGRAPH_CHECK(igraph_subisomorphic_lad(&cycle, graph, NULL, &iso, NULL, NULL, /* induced */ 1)); + if (iso) { + *perfect = false; + goto clean2; + } + } + + if (cycle_len > comp_girth) { + IGRAPH_CHECK(igraph_subisomorphic_lad(&cycle, &comp_graph, NULL, &iso, NULL, NULL, /* induced */ 1)); + if (iso) { + *perfect = false; + goto clean2; + } + } + + igraph_destroy(&cycle); + IGRAPH_FINALLY_CLEAN(1); + } + + *perfect = true; + +clean1: + /* normal exit route */ + igraph_destroy(&comp_graph); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; + +clean2: + /* exit route if we also have a cycle to destroy */ + igraph_destroy(&cycle); + IGRAPH_FINALLY_CLEAN(1); + goto clean1; +} diff --git a/src/properties/properties_internal.h b/src/properties/properties_internal.h new file mode 100644 index 0000000..e16b6e5 --- /dev/null +++ b/src/properties/properties_internal.h @@ -0,0 +1,33 @@ +/* + igraph library. + Copyright (C) 2011-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_PROPERTIES_INTERNAL_H +#define IGRAPH_PROPERTIES_INTERNAL_H + +#include "igraph_adjlist.h" +#include "igraph_decls.h" +#include "igraph_iterators.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_trans4_al_simplify(igraph_adjlist_t *al, + const igraph_vector_int_t *rank); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/properties/rich_club.c b/src/properties/rich_club.c new file mode 100644 index 0000000..3007627 --- /dev/null +++ b/src/properties/rich_club.c @@ -0,0 +1,166 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_structural.h" + +#include "igraph_interface.h" +#include "igraph_isomorphism.h" + +/* Returns the total number of possible edges given the number of vertices in a graph, + * whether it is directed, and whether loops should be assumed possible. + * + * No loops, undirected: n * (n-1) / 2 + * No loops, directed: n * (n-1) + * Loops, undirected: n * (n+1) / 2 + * Loops, directed: n^2 + */ +static igraph_real_t total_possible_edges(igraph_int_t vcount, + igraph_bool_t directed, + igraph_bool_t loops) { + igraph_real_t nv = (igraph_real_t) vcount; + if (!loops) { + return (directed ? nv * (nv - 1) : nv * (nv - 1) / 2); + } else { + return (directed ? nv * nv : nv * (nv + 1) / 2); + } +} + +/** + * \function igraph_rich_club_sequence + * \brief Density sequence of subgraphs formed by sequential vertex removal. + * + * \experimental + * + * This function takes a graph and a vertex ordering as input, sequentially + * removes the vertices in the given order, and calculates the density of the + * remaining subgraph after each removal. + * + * + * Density is calculated as the ratio of the number of edges (or total edge + * weight, if weighted) to the number of total possible edges in the graph. + * The latter is dependent on whether the graph is directed and whether + * self-loops are assumed to be possible: for undirected graphs without + * self-loops, this total is given by n(n-1)/2, + * and for directed graphs by n(n-1). + * When self-loops are allowed, these are adjusted to n(n+1)/2 + * for undirected and n^2 for directed graphs. + * + * + * Vertex order can be sorted by degree so that the resulting density sequence + * helps reveal how interconnected a graph is across different degree levels, + * or the presence of a "rich-club" effect. + * + * \param graph The graph object to analyze. + * \param weights Vector of edge weights. If \c NULL all weights are + * assumed to be 1. + * \param res Initialized vector, the result will be written here. res[i] + * contain the density of the remaining graph after \c i vertices have been + * removed. If \p normalized is set to \c false, it contains the remaining + * edge count (or remaining total edge weights if weights were given). + * \param vertex_order Vector giving the order in which vertices are removed. + * \param normalized If \c false, return edge counts (or total edge weights). + * If \c true, divide by the largest possible edge count to obtain densities. + * \param loops Whether self-loops are assumed to be possible. Ignored when + * normalized is not requested. + * \param directed If false, directed graphs will be treated as undirected. + * Ignored with undirected graphs. + * + * \return Error code: \c IGRAPH_EINVAL: invalid vertex_order vector and/or weight vector + * lengths + * + * Time complexity: O(|V| + |E|) + * where |V| is the number of vertices and |E| the number of edges in the graph given. + * + * \sa \ref igraph_density(), which uses the same calculation of total possible edges. + */ +igraph_error_t igraph_rich_club_sequence( + const igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, + const igraph_vector_int_t *vertex_order, + igraph_bool_t normalized, + igraph_bool_t loops, igraph_bool_t directed) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_int_t order_of; + + // Error handling: invalid vertex_order and weights sizes + if (igraph_vector_int_size(vertex_order) != vcount) { + IGRAPH_ERRORF("Vertex order vector length (%" IGRAPH_PRId ") does not match " + "number of vertices (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_int_size(vertex_order), vcount); + } + if (weights && igraph_vector_size(weights) != ecount) { + IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match " + "number of edges (%" IGRAPH_PRId ").", + IGRAPH_EINVAL, + igraph_vector_size(weights), ecount); + } + + if (! igraph_is_directed(graph)) { + directed = false; + } + + IGRAPH_CHECK(igraph_vector_resize(res, vcount)); + igraph_vector_null(res); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&order_of, vcount); + IGRAPH_CHECK(igraph_invert_permutation(vertex_order, &order_of)); + + igraph_bool_t warning_issued = false; + + // remaining_total_weight vector: number of edges (or total edge weight) removed by index + for (igraph_int_t eid = 0; eid < ecount; eid++) { + igraph_int_t v1 = IGRAPH_FROM(graph, eid); + igraph_int_t v2 = IGRAPH_TO(graph, eid); + if (!loops && normalized && !warning_issued && v1 == v2) { + IGRAPH_WARNING("Self-loops were requested to be assumed absent, " + "but encountered a self-loop. Density calculations will proceed " + "with the assumption of no loops."); + warning_issued = true; + } + igraph_int_t order_v1 = VECTOR(order_of)[v1]; // order of endpoints + igraph_int_t order_v2 = VECTOR(order_of)[v2]; + + igraph_int_t edge_removal_index = (order_v1 < order_v2 ? order_v1 : order_v2); + VECTOR(*res)[edge_removal_index] += (weights ? VECTOR(*weights)[eid] : 1); + } + + // remaining_total_weight vector: edges (or total edge weight) remaining after i removals + igraph_real_t total = 0; + for (igraph_int_t i = vcount - 1; i >= 0; i--) { + total += VECTOR(*res)[i]; + VECTOR(*res)[i] = total; + } + + // Normalize edge counts to densities + if (normalized) { + for (igraph_int_t i = 0; i < vcount; i++) { + // (vcount - i) = the number of vertices left in this loop + VECTOR(*res)[i] = + VECTOR(*res)[i] / + total_possible_edges(vcount - i, directed, loops); + } + } + + igraph_vector_int_destroy(&order_of); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/spectral.c b/src/properties/spectral.c new file mode 100644 index 0000000..fa6df09 --- /dev/null +++ b/src/properties/spectral.c @@ -0,0 +1,373 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" +#include "igraph_interface.h" + +#include "math/safe_intop.h" + +#include + +static igraph_error_t igraph_i_laplacian_validate_weights( + const igraph_t* graph, const igraph_vector_t* weights +) { + igraph_int_t no_of_edges; + + if (weights == NULL) { + return IGRAPH_SUCCESS; + } + + no_of_edges = igraph_ecount(graph); + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); + } + + if (no_of_edges > 0) { + igraph_real_t minweight = igraph_vector_min(weights); + if (minweight < 0) { + IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); + } else if (isnan(minweight)) { + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_get_laplacian + * \brief Returns the Laplacian matrix of a graph. + * + * The Laplacian matrix \c L of a graph is defined as + * L_ij = - A_ij when i != j and + * L_ii = d_i - A_ii. Here \c A denotes the (possibly weighted) + * adjacency matrix and d_i is the degree (or strength, if weighted) + * of vertex \c i. In directed graphs, the \p mode parameter controls whether to use + * out- or in-degrees. Correspondingly, the rows or columns will sum to zero. + * In undirected graphs, A_ii is taken to be \em twice the number + * (or total weight) of self-loops, ensuring that d_i = \sum_j A_ij. + * Thus, the Laplacian of an undirected graph is the same as the Laplacian + * of a directed one obtained by replacing each undirected edge with two reciprocal + * directed ones. + * + * + * More compactly, L = D - A where the \c D is a diagonal matrix + * containing the degrees. The Laplacian matrix can also be normalized, with several + * conventional normalization methods. See \ref igraph_laplacian_normalization_t for + * the methods available in igraph. + * + * + * The first version of this function was written by Vincent Matossian. + * + * \param graph Pointer to the graph to convert. + * \param res Pointer to an initialized matrix object, the result is + * stored here. It will be resized if needed. + * \param mode Controls whether to use out- or in-degrees in directed graphs. + * If set to \c IGRAPH_ALL, edge directions will be ignored. + * \param normalization The normalization method to use when calculating the + * Laplacian matrix. See \ref igraph_laplacian_normalization_t for + * possible values. + * \param weights An optional vector containing non-negative edge weights, + * to calculate the weighted Laplacian matrix. Set it to a null pointer to + * calculate the unweighted Laplacian. + * \return Error code. + * + * Time complexity: O(|V|^2), |V| is the number of vertices in the graph. + * + * \example examples/simple/igraph_get_laplacian.c + */ + +igraph_error_t igraph_get_laplacian( + const igraph_t *graph, igraph_matrix_t *res, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t degree; + igraph_int_t i; + + IGRAPH_ASSERT(res != NULL); + + IGRAPH_CHECK(igraph_i_laplacian_validate_weights(graph, weights)); + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), mode, IGRAPH_LOOPS, weights)); + + /* Value of 'mode' is validated in igraph_strength() call above. */ + if (! directed) { + mode = IGRAPH_ALL; + } else if (mode == IGRAPH_ALL) { + directed = 0; + } + + for (i = 0; i < no_of_nodes; i++) { + switch (normalization) { + case IGRAPH_LAPLACIAN_UNNORMALIZED: + MATRIX(*res, i, i) = VECTOR(degree)[i]; + break; + + case IGRAPH_LAPLACIAN_SYMMETRIC: + if (VECTOR(degree)[i] > 0) { + MATRIX(*res, i, i) = 1; + VECTOR(degree)[i] = 1.0 / sqrt(VECTOR(degree)[i]); + } + break; + + case IGRAPH_LAPLACIAN_LEFT: + case IGRAPH_LAPLACIAN_RIGHT: + if (VECTOR(degree)[i] > 0) { + MATRIX(*res, i, i) = 1; + VECTOR(degree)[i] = 1.0 / VECTOR(degree)[i]; + } + break; + + default: + IGRAPH_ERROR("Invalid Laplacian normalization method.", IGRAPH_EINVAL); + } + } + + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_real_t weight = weights ? VECTOR(*weights)[i] : 1.0; + igraph_real_t norm; + + switch (normalization) { + case IGRAPH_LAPLACIAN_UNNORMALIZED: + MATRIX(*res, from, to) -= weight; + if (!directed) { + MATRIX(*res, to, from) -= weight; + } + break; + + case IGRAPH_LAPLACIAN_SYMMETRIC: + norm = VECTOR(degree)[from] * VECTOR(degree)[to]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero %s-%s, " + "cannot perform symmetric normalization of Laplacian with '%s' mode.", + IGRAPH_EINVAL, + mode == IGRAPH_OUT ? "out" : "in", weights ? "strength" : "degree", mode == IGRAPH_OUT ? "out" : "in"); + } + weight *= norm; + MATRIX(*res, from, to) -= weight; + if (!directed) { + MATRIX(*res, to, from) -= weight; + } + break; + + case IGRAPH_LAPLACIAN_LEFT: + norm = VECTOR(degree)[from]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero in-%s, " + "cannot perform left stochastic normalization of Laplacian with 'in' mode.", + IGRAPH_EINVAL, + weights ? "strength" : "degree"); + } + MATRIX(*res, from, to) -= weight * norm; + if (!directed) { + /* no failure possible in undirected case, as zero degrees occur only for isolated vertices */ + MATRIX(*res, to, from) -= weight * VECTOR(degree)[to]; + } + break; + + case IGRAPH_LAPLACIAN_RIGHT: + norm = VECTOR(degree)[to]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero out-%s, " + "cannot perform right stochastic normalization of Laplacian with 'out' mode.", + IGRAPH_EINVAL, + weights ? "strength" : "degree"); + } + MATRIX(*res, from, to) -= weight * norm; + if (!directed) { + /* no failure possible in undirected case, as zero degrees occur only for isolated vertices */ + MATRIX(*res, to, from) -= weight * VECTOR(degree)[from]; + } + break; + } + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_get_laplacian_sparse + * \brief Returns the Laplacian of a graph in a sparse matrix format. + * + * See \ref igraph_get_laplacian() for the definition of the Laplacian matrix. + * + * + * The first version of this function was written by Vincent Matossian. + * + * \param graph Pointer to the graph to convert. + * \param sparseres Pointer to an initialized sparse matrix object, the + * result is stored here. + * \param mode Controls whether to use out- or in-degrees in directed graphs. + * If set to \c IGRAPH_ALL, edge directions will be ignored. + * \param normalization The normalization method to use when calculating the + * Laplacian matrix. See \ref igraph_laplacian_normalization_t for + * possible values. + * \param weights An optional vector containing non-negative edge weights, + * to calculate the weighted Laplacian matrix. Set it to a null pointer to + * calculate the unweighted Laplacian. + * \return Error code. + * + * Time complexity: O(|E|), |E| is the number of edges in the graph. + * + * \example examples/simple/igraph_get_laplacian_sparse.c + */ + +igraph_error_t igraph_get_laplacian_sparse( + const igraph_t *graph, igraph_sparsemat_t *sparseres, igraph_neimode_t mode, + igraph_laplacian_normalization_t normalization, + const igraph_vector_t *weights +) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t degree; + igraph_int_t i; + igraph_int_t nz; + + if (directed) { + IGRAPH_SAFE_ADD(no_of_edges, no_of_nodes, &nz); + } else { + IGRAPH_SAFE_ADD(no_of_edges * 2, no_of_nodes, &nz); + } + + IGRAPH_ASSERT(sparseres != NULL); + + IGRAPH_CHECK(igraph_i_laplacian_validate_weights(graph, weights)); + + IGRAPH_CHECK(igraph_sparsemat_resize(sparseres, no_of_nodes, no_of_nodes, nz)); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), mode, IGRAPH_LOOPS, weights)); + + for (i = 0; i < no_of_nodes; i++) { + switch (normalization) { + case IGRAPH_LAPLACIAN_UNNORMALIZED: + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, VECTOR(degree)[i])); + break; + + case IGRAPH_LAPLACIAN_SYMMETRIC: + if (VECTOR(degree)[i] > 0) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, 1)); + VECTOR(degree)[i] = 1.0 / sqrt(VECTOR(degree)[i]); + } + break; + + case IGRAPH_LAPLACIAN_LEFT: + case IGRAPH_LAPLACIAN_RIGHT: + if (VECTOR(degree)[i] > 0) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, 1)); + VECTOR(degree)[i] = 1.0 / VECTOR(degree)[i]; + } + break; + + default: + IGRAPH_ERROR("Invalid Laplacian normalization method.", IGRAPH_EINVAL); + } + } + + for (i = 0; i < no_of_edges; i++) { + igraph_int_t from = IGRAPH_FROM(graph, i); + igraph_int_t to = IGRAPH_TO(graph, i); + igraph_real_t weight = weights ? VECTOR(*weights)[i] : 1.0; + igraph_real_t norm; + + switch (normalization) { + case IGRAPH_LAPLACIAN_UNNORMALIZED: + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -weight)); + if (!directed) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -weight)); + } + break; + + case IGRAPH_LAPLACIAN_SYMMETRIC: + norm = VECTOR(degree)[from] * VECTOR(degree)[to]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero %s-%s, " + "cannot perform symmetric normalization of Laplacian with '%s' mode.", + IGRAPH_EINVAL, + mode == IGRAPH_OUT ? "out" : "in", weights ? "strength" : "degree", mode == IGRAPH_OUT ? "out" : "in"); + } + weight *= norm; + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -weight)); + if (!directed) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -weight)); + } + break; + + case IGRAPH_LAPLACIAN_LEFT: + norm = VECTOR(degree)[from]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero in-%s, " + "cannot perform left stochastic normalization of Laplacian with 'in' mode.", + IGRAPH_EINVAL, + weights ? "strength" : "degree"); + } + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -weight * norm)); + if (!directed) { + /* no failure possible in undirected case, as zero degrees occur only for isolated vertices */ + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -weight * VECTOR(degree)[to])); + } + break; + + case IGRAPH_LAPLACIAN_RIGHT: + norm = VECTOR(degree)[to]; + if (norm == 0 && weight != 0) { + IGRAPH_ERRORF( + "Found non-isolated vertex with zero out-%s, " + "cannot perform right stochastic normalization of Laplacian with 'out' mode.", + IGRAPH_EINVAL, + weights ? "strength" : "degree"); + } + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -weight * norm)); + if (!directed) { + /* no failure possible in undirected case, as zero degrees occur only for isolated vertices */ + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -weight * VECTOR(degree)[from])); + } + break; + } + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/properties/trees.c b/src/properties/trees.c new file mode 100644 index 0000000..2ab6620 --- /dev/null +++ b/src/properties/trees.c @@ -0,0 +1,762 @@ +/* + igraph library. + Copyright (C) 2005-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" + +#include "igraph_bitset.h" +#include "igraph_constructors.h" +#include "igraph_cycles.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_stack.h" + +/** + * \function igraph_unfold_tree + * \brief Unfolding a graph into a tree, by possibly multiplicating its vertices. + * + * A graph is converted into a tree (or forest, if it is unconnected), + * by performing a breadth-first search on it, and replicating + * vertices that were found a second, third, etc. time. + * + * \param graph The input graph, it can be either directed or + * undirected. + * \param tree Pointer to an uninitialized graph object, the result is + * stored here. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \param roots A numeric vector giving the root vertex, or vertices + * (if the graph is not connected), to start from. + * \param vertex_index Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then a mapping from the vertices + * in the new graph to the ones in the original is created here. + * \return Error code. + * + * Time complexity: O(n+m), linear in the number vertices and edges. + * + */ +igraph_error_t igraph_unfold_tree(const igraph_t *graph, igraph_t *tree, + igraph_neimode_t mode, const igraph_vector_int_t *roots, + igraph_vector_int_t *vertex_index) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_roots = igraph_vector_int_size(roots); + igraph_int_t tree_vertex_count = no_of_nodes; + + igraph_vector_int_t edges; + igraph_bitset_t seen_vertices; + igraph_bitset_t seen_edges; + + igraph_dqueue_int_t Q; + igraph_vector_int_t neis; + + igraph_int_t v_ptr = no_of_nodes; + + if (! igraph_vector_int_isininterval(roots, 0, no_of_nodes-1)) { + IGRAPH_ERROR("All roots should be vertices of the graph.", IGRAPH_EINVVID); + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2)); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&Q, 100); + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + IGRAPH_BITSET_INIT_FINALLY(&seen_vertices, no_of_nodes); + IGRAPH_BITSET_INIT_FINALLY(&seen_edges, no_of_edges); + + if (vertex_index) { + IGRAPH_CHECK(igraph_vector_int_range(vertex_index, 0, no_of_nodes)); + } + + for (igraph_int_t r = 0; r < no_of_roots; r++) { + + igraph_int_t root = VECTOR(*roots)[r]; + IGRAPH_BIT_SET(seen_vertices, root); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, root)); + + while (!igraph_dqueue_int_empty(&Q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&Q); + + IGRAPH_CHECK(igraph_incident(graph, &neis, actnode, mode, IGRAPH_LOOPS)); + + igraph_int_t n = igraph_vector_int_size(&neis); + for (igraph_int_t i = 0; i < n; i++) { + + igraph_int_t edge = VECTOR(neis)[i]; + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO(graph, edge); + igraph_int_t nei = IGRAPH_OTHER(graph, edge, actnode); + + if (! IGRAPH_BIT_TEST(seen_edges, edge)) { + + IGRAPH_BIT_SET(seen_edges, edge); + + if (! IGRAPH_BIT_TEST(seen_vertices, nei)) { + + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + + IGRAPH_BIT_SET(seen_vertices, nei); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, nei)); + + } else { + + tree_vertex_count++; + if (vertex_index) { + IGRAPH_CHECK(igraph_vector_int_push_back(vertex_index, nei)); + } + + if (from == nei) { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v_ptr++)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, to)); + } else { + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v_ptr++)); + } + } + } + + } /* for i + * In the directed case, an additional requirement is that all edges + * are oriented away from a root (out-tree or arborescence) or all edges + * are oriented towards a root (in-tree or anti-arborescence). + * This test can be controlled using the \p mode parameter. + * + * + * By convention, the null graph (i.e. the graph with no vertices) is considered + * not to be connected, and therefore not a tree. + * + * \param graph The graph object to analyze. + * \param res Pointer to a Boolean variable, the result will be stored + * here. + * \param root If not \c NULL, the root node will be stored here. When \p mode + * is \c IGRAPH_ALL or the graph is undirected, any vertex can be the root + * and \p root is set to 0 (the first vertex). When \p mode is \c IGRAPH_OUT + * or \c IGRAPH_IN, the root is set to the vertex with zero in- or out-degree, + * respectively. + * \param mode For a directed graph this specifies whether to test for an + * out-tree, an in-tree or ignore edge directions. The respective + * possible values are: + * \c IGRAPH_OUT, \c IGRAPH_IN, \c IGRAPH_ALL. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVAL: invalid mode argument. + * + * Time complexity: At most O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_is_forest() to check if all components are trees, + * which is equivalent to the graph lacking undirected cycles; + * \ref igraph_is_connected(), \ref igraph_is_acyclic() + * + * \example examples/simple/igraph_kary_tree.c + */ +igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_int_t *root, igraph_neimode_t mode) { + igraph_bool_t is_tree = false; + igraph_bool_t treat_as_undirected = !igraph_is_directed(graph) || mode == IGRAPH_ALL; + igraph_int_t iroot = 0; + igraph_int_t visited_count; + igraph_int_t vcount, ecount; + + vcount = igraph_vcount(graph); + ecount = igraph_ecount(graph); + + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED)) { + igraph_bool_t weakly_connected = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED); + + if (weakly_connected) { + /* For undirected graphs and for directed graphs with mode == IGRAPH_ALL, + * we can return early if we know from the cache that the graph is weakly + * connected and is a forest. We can do this even if the user wants the + * root vertex because we always return zero as the root vertex for + * undirected graphs */ + if (treat_as_undirected && + igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST) + ) { + is_tree = true; + iroot = 0; + goto success; + } + } else /* ! weakly_connected */ { + /* If the graph is not weakly connected, then it is neither an undirected + * not a directed tree. There is no root, so we can return early. */ + is_tree = false; + goto success; + } + } + + /* A tree must have precisely vcount-1 edges. */ + /* By convention, the zero-vertex graph will not be considered a tree. */ + if (ecount != vcount - 1) { + is_tree = false; + goto success; + } + + /* The single-vertex graph is a tree, provided it has no edges (checked in the previous if (..)) */ + if (vcount == 1) { + is_tree = true; + iroot = 0; + goto success; + } + + /* For higher vertex counts we cannot short-circuit due to the possibility + * of loops or multi-edges even when the edge count is correct. */ + + /* Ignore mode for undirected graphs. */ + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + /* The main algorithm: + * We find a root and check that all other vertices are reachable from it. + * We have already checked the number of edges, so with the additional + * reachability condition we can verify if the graph is a tree. + * + * For directed graphs, the root is the node with no incoming/outgoing + * connections, depending on 'mode'. For undirected, it is arbitrary, so + * we choose 0. + */ + + is_tree = true; /* assume success */ + + switch (mode) { + case IGRAPH_ALL: + iroot = 0; + break; + + case IGRAPH_IN: + case IGRAPH_OUT: { + igraph_vector_int_t degree; + igraph_int_t i; + + IGRAPH_CHECK(igraph_vector_int_init(°ree, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, °ree); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_REVERSE_MODE(mode), IGRAPH_LOOPS)); + + for (i = 0; i < vcount; ++i) { + if (VECTOR(degree)[i] == 0) { + break; + } + if (VECTOR(degree)[i] > 1) { + /* In an out-tree, all vertices have in-degree 1, except for the root, + * which has in-degree 0. Thus, if we encounter a larger in-degree, + * the graph cannot be an out-tree. + * We could perform this check for all degrees, but that would not + * improve performance when the graph is indeed a tree, persumably + * the most common case. Thus we only check until finding the root. + */ + is_tree = false; + break; + } + } + + /* If no suitable root is found, the graph is not a tree. */ + if (is_tree && i == vcount) { + is_tree = false; + } else { + iroot = i; + } + + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + + break; + default: + IGRAPH_ERROR("Invalid mode.", IGRAPH_EINVMODE); + } + + /* if no suitable root was found, skip visiting vertices */ + if (is_tree) { + IGRAPH_CHECK(igraph_i_is_tree_visitor(graph, iroot, mode, &visited_count)); + is_tree = visited_count == vcount; + } + +success: + if (res) { + *res = is_tree; + } + + if (root) { + *root = iroot; + } + + if (is_tree) { + /* A graph that is a directed tree is also an undirected tree. + * An undirected tree is weakly connected and is a forest, + * so we can cache this. */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true); + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, true); + } + + return IGRAPH_SUCCESS; +} + +/* igraph_is_forest() -- check if a graph is a forest */ + +/* Verify that the graph has no cycles and count the number of reachable vertices. + * This function performs a DFS starting from 'root'. + * If it finds a cycle, it sets *res to false, otherwise it does not change it. + * *visited_count will be incremented by the number of vertices reachable from 'root', + * including 'root' itself. + */ +static igraph_error_t igraph_i_is_forest_visitor( + const igraph_t *graph, igraph_int_t root, igraph_neimode_t mode, + igraph_bitset_t *visited, igraph_stack_int_t *stack, igraph_vector_int_t *neis, + igraph_int_t *visited_count, igraph_bool_t *res) +{ + igraph_int_t i; + + igraph_stack_int_clear(stack); + + /* push the root onto the stack */ + IGRAPH_CHECK(igraph_stack_int_push(stack, root)); + + while (! igraph_stack_int_empty(stack)) { + igraph_int_t u; + igraph_int_t ncount; + + /* Take a vertex from stack and check if it is already visited. + * If yes, then we found a cycle: the graph is not a forest. + * Otherwise mark it as visited and continue. + */ + u = igraph_stack_int_pop(stack); + if (IGRAPH_LIKELY(! IGRAPH_BIT_TEST(*visited, u))) { + IGRAPH_BIT_SET(*visited, u); + *visited_count += 1; + } + else { + *res = false; + break; + } + + /* Vertex discovery: Register all its neighbours for future processing */ + IGRAPH_CHECK(igraph_neighbors(graph, neis, u, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE)); + ncount = igraph_vector_int_size(neis); + + for (i = 0; i < ncount; ++i) { + igraph_int_t v = VECTOR(*neis)[i]; + + if (mode == IGRAPH_ALL) { + /* In the undirected case, we avoid returning to the predecessor + * vertex of 'v' in the DFS tree by skipping visited vertices. + * + * Note that in order to succcessfully detect a cycle, a vertex + * within that cycle must end up on the stack more than once. + * Does skipping visited vertices preclude this sometimes? + * No, because any visited vertex can only be accessed through + * an already discovered vertex (i.e. one that has already been + * pushed onto the stack). + */ + if (IGRAPH_LIKELY(! IGRAPH_BIT_TEST(*visited, v))) { + IGRAPH_CHECK(igraph_stack_int_push(stack, v)); + } + /* To check for a self-loop in undirected graph */ + else if (v == u) { + *res = false; + break; + } + } + else { + IGRAPH_CHECK(igraph_stack_int_push(stack, v)); + } + } + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_is_forest( + const igraph_t *graph, igraph_bool_t *res, + igraph_vector_int_t *roots, igraph_neimode_t mode +); + +/** + * \ingroup structural + * \function igraph_is_forest + * \brief Decides whether the graph is a forest. + * + * An undirected graph is a forest if it has no cycles. Equivalently, + * a graph is a forest if all connected components are trees. + * + * + * In the directed case, an additional requirement is that edges in each + * tree are oriented away from the root (out-trees or arborescences) or all edges + * are oriented towards the root (in-trees or anti-arborescences). + * This test can be controlled using the \p mode parameter. + * + * + * By convention, the null graph (i.e. the graph with no vertices) is considered + * to be a forest. + * + * + * The \p res return value of this function is cached in the graph itself if + * \p mode is set to \c IGRAPH_ALL or if the graph is undirected. Calling the + * function multiple times with no modifications to the graph in between + * will return a cached value in O(1) time if the roots are not requested. + * + * \param graph The graph object to analyze. + * \param res Pointer to a Boolean variable. If not \c NULL, then the result will + * be stored here. + * \param roots If not \c NULL, the root nodes will be stored here. When \p mode + * is \c IGRAPH_ALL or the graph is undirected, any one vertex from each + * component can be the root. When \p mode is \c IGRAPH_OUT + * or \c IGRAPH_IN, all the vertices with zero in- or out-degree, + * respectively are considered as root nodes. + * \param mode For a directed graph this specifies whether to test for an + * out-forest, an in-forest or ignore edge directions. The respective + * possible values are: + * \c IGRAPH_OUT, \c IGRAPH_IN, \c IGRAPH_ALL. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVMODE: invalid mode argument. + * + * Time complexity: At most O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_is_tree() to check if a graph is a tree, i.e. a forest with + * a single component; \ref igraph_is_acyclic() to check if a graph lacks + * (undirected or directed) cycles. + */ +igraph_error_t igraph_is_forest(const igraph_t *graph, igraph_bool_t *res, + igraph_vector_int_t *roots, igraph_neimode_t mode) { + + const igraph_bool_t treat_as_undirected = !igraph_is_directed(graph) || mode == IGRAPH_ALL; + + if (!roots && !res) { + return IGRAPH_SUCCESS; + } + + /* Note on cache use: + * + * The IGRAPH_PROP_IS_FOREST cached property is equivalent to this function's + * result ONLY in the undirected case. Keep in mind that a graph that is not + * a directed forest may still be an undirected forest, i.e. may still be free + * of undirected cycles. Example: 1->2<-3->4. + */ + + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST)) { + const igraph_bool_t no_undirected_cycles = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST); + if (treat_as_undirected && res && ! roots) { + /* If the graph is treated as undirected and no roots are requested, + * we can directly use the cached IGRAPH_PROP_IS_FOREST value. */ + *res = no_undirected_cycles; + return IGRAPH_SUCCESS; + } else { + /* Otherwise we can use negative cached values (i.e. "false"): + * - A graph with undirected cycles cannot be a directed forest. + * - If the graph is not a forest, we don't need to look for roots. + */ + if (! no_undirected_cycles) { + if (res) { *res = false; } + if (roots) { igraph_vector_int_clear(roots); } + return IGRAPH_SUCCESS; + } + } + } + + IGRAPH_CHECK(igraph_i_is_forest(graph, res, roots, mode)); + + /* At this point we know whether the graph is an (undirected or directed) forest + * as we have at least one of 'res' or 'roots'. The case when both are NULL was + * caught above. */ + igraph_bool_t is_forest; + if (res != NULL) { + is_forest = *res; + } else /* roots != NULL */ { + is_forest = igraph_vcount(graph) == 0 || !igraph_vector_int_empty(roots); + } + if (is_forest) { + /* If the graph is a directed forest, then it has no undirected cycles. + * We can enter positive results in the cache unconditionally. */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true); + } else if (treat_as_undirected) { + /* However, if the graph is not a directed forest, it might still be + * an undirected forest. We can only enter negative results in the cache + * when edge directions were ignored, but NOT in the directed case. */ + igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, false); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_i_is_forest( + const igraph_t *graph, igraph_bool_t *res, + igraph_vector_int_t *roots, igraph_neimode_t mode +) { + igraph_bitset_t visited; + igraph_vector_int_t neis; + igraph_stack_int_t stack; + igraph_int_t visited_count = 0; + igraph_int_t vcount, ecount; + igraph_int_t v; + igraph_bool_t result; + + vcount = igraph_vcount(graph); + ecount = igraph_ecount(graph); + + if (roots) { + igraph_vector_int_clear(roots); + } + + /* Any graph with 0 edges is a forest. */ + if (ecount == 0) { + if (res) { + *res = true; + } + if (roots) { + for (v = 0; v < vcount; v++) { + IGRAPH_CHECK(igraph_vector_int_push_back(roots, v)); + } + } + return IGRAPH_SUCCESS; + } + + /* A forest can have at most vcount-1 edges. */ + if (ecount > vcount - 1) { + if (res) { + *res = false; + } + return IGRAPH_SUCCESS; + } + + /* Ignore mode for undirected graphs. */ + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + result = true; /* assume success */ + + IGRAPH_BITSET_INIT_FINALLY(&visited, vcount); + + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); + + /* The main algorithm: + * + * Undirected Graph:- We add each unvisited vertex to the roots vector, and + * mark all other vertices that are reachable from it as visited. + * + * Directed Graph:- For each tree, the root is the node with no + * incoming/outgoing connections, depending on 'mode'. We add each vertex + * with zero degree to the roots vector and mark all other vertices that are + * reachable from it as visited. + * + * If all the vertices are visited exactly once, then the graph is a forest. + */ + + switch (mode) { + case IGRAPH_ALL: + { + for (v = 0; v < vcount; ++v) { + if (!result) { + break; + } + if (! IGRAPH_BIT_TEST(visited, v)) { + if (roots) { + IGRAPH_CHECK(igraph_vector_int_push_back(roots, v)); + } + IGRAPH_CHECK(igraph_i_is_forest_visitor( + graph, v, mode, + &visited, &stack, &neis, + &visited_count, &result)); + } + } + break; + } + + case IGRAPH_IN: + case IGRAPH_OUT: + { + igraph_vector_int_t degree; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, 0); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_REVERSE_MODE(mode), IGRAPH_LOOPS)); + + for (v = 0; v < vcount; ++v) { + /* In an out-tree, roots have in-degree 0, + * and all other vertices have in-degree 1. */ + if (VECTOR(degree)[v] > 1 || !result) { + result = false; + break; + } + if (VECTOR(degree)[v] == 0) { + if (roots) { + IGRAPH_CHECK(igraph_vector_int_push_back(roots, v)); + } + IGRAPH_CHECK(igraph_i_is_forest_visitor( + graph, v, mode, + &visited, &stack, &neis, + &visited_count, &result)); + } + } + + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + break; + } + + default: + IGRAPH_ERROR("Invalid mode.", IGRAPH_EINVMODE); + } + + if (result) { + /* In a forest, all vertices are reachable from the roots. */ + result = (visited_count == vcount); + } + + if (res) { + *res = result; + } + + /* If the graph is not a forest then the root vector will be empty. */ + if (!result && roots) { + igraph_vector_int_clear(roots); + } + + igraph_vector_int_destroy(&neis); + igraph_stack_int_destroy(&stack); + igraph_bitset_destroy(&visited); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_is_acyclic + * \brief Checks whether a graph is acyclic or not. + * + * This function checks whether a graph has any cycles. Edge directions are + * considered, i.e. in directed graphs, only directed cycles are searched for. + * + * + * The result of this function is cached in the graph itself; calling + * the function multiple times with no modifications to the graph in between + * will return a cached value in O(1) time. + * + * \param graph The input graph. + * \param res Pointer to a boolean constant, the result + is stored here. + * \return Error code. + * + * \sa \ref igraph_find_cycle() to find a cycle that demonstrates + * that the graph is not acyclic; \ref igraph_is_forest() to look + * for undirected cycles even in directed graphs; \ref igraph_is_dag() + * to test specifically for directed acyclic graphs. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + */ +igraph_error_t igraph_is_acyclic(const igraph_t *graph, igraph_bool_t *res) { + if (igraph_is_directed(graph)) { + /* igraph_is_dag is cached */ + return igraph_is_dag(graph, res); + } else { + /* igraph_is_forest is cached if mode == IGRAPH_ALL and we don't need + * the roots */ + return igraph_is_forest(graph, res, NULL, IGRAPH_ALL); + } +} diff --git a/src/properties/triangles.c b/src/properties/triangles.c new file mode 100644 index 0000000..eaed274 --- /dev/null +++ b/src/properties/triangles.c @@ -0,0 +1,923 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_transitivity.h" + +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_memory.h" +#include "igraph_motifs.h" +#include "igraph_structural.h" + +#include "core/interruption.h" +#include "properties/properties_internal.h" + +/** + * \function igraph_transitivity_avglocal_undirected + * \brief Average local transitivity (clustering coefficient). + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. In case of the average local transitivity, + * this probability is calculated for each vertex and then the average + * is taken. Vertices with less than two neighbors require special treatment, + * they will either be left out from the calculation or they will be considered + * as having zero transitivity, depending on the \c mode argument. + * Edge directions and edge multiplicities are ignored. + * + * + * Note that this measure is different from the global transitivity measure + * (see \ref igraph_transitivity_undirected() ) as it simply takes the + * average local transitivity across the whole network. + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * + * References: + * + * + * D. J. Watts and S. Strogatz: Collective dynamics of small-world networks. + * Nature 393(6684):440-442 (1998). + * + * \param graph The input graph. Edge directions and multiplicites are ignored. + * \param res Pointer to a real variable, the result will be stored here. + * \param mode Defines how to treat vertices with degree less than two. + * \c IGRAPH_TRANSITIVITY_NAN leaves them out from averaging, + * \c IGRAPH_TRANSITIVITY_ZERO includes them with zero transitivity. + * The result will be \c NaN if the mode is \c IGRAPH_TRANSITIVITY_NAN + * and there are no vertices with more than one neighbor. + * + * \return Error code. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_local_undirected(). + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in the + * graph and d is the average degree. + */ + +igraph_error_t igraph_transitivity_avglocal_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode) { + + igraph_int_t i, no_of_nodes = igraph_vcount(graph), nans = 0; + igraph_real_t sum = 0.0; + igraph_vector_t vec; + + if (no_of_nodes == 0) { + if (mode == IGRAPH_TRANSITIVITY_ZERO) { + *res = 0; + } else { + *res = IGRAPH_NAN; + } + } else { + IGRAPH_VECTOR_INIT_FINALLY(&vec, no_of_nodes); + + IGRAPH_CHECK(igraph_transitivity_local_undirected(graph, &vec, igraph_vss_all(), mode)); + + for (i = 0, nans = 0; i < no_of_nodes; i++) { + if (!isnan(VECTOR(vec)[i])) { + sum += VECTOR(vec)[i]; + } else { + nans++; + } + } + + igraph_vector_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + + *res = sum / (no_of_nodes - nans); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t transitivity_local_undirected1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + +#define TRANSIT +#include "properties/triangles_template1.h" +#undef TRANSIT + + return IGRAPH_SUCCESS; +} + +static igraph_error_t transitivity_local_undirected2(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_int_t nodes_to_calc, affected_nodes; + igraph_int_t maxdegree = 0; + igraph_int_t i, j, k, nn; + igraph_lazy_adjlist_t adjlist; + igraph_vector_int_t degree; + igraph_vector_t indexv, avids, rank, triangles; + igraph_vector_int_t order; + igraph_int_t *neis; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + IGRAPH_VECTOR_INIT_FINALLY(&indexv, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&avids, 0); + IGRAPH_CHECK(igraph_vector_reserve(&avids, nodes_to_calc)); + k = 0; + for (i = 0; i < nodes_to_calc; IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t v = IGRAPH_VIT_GET(vit); + igraph_vector_int_t *neis2; + igraph_int_t neilen; + if (VECTOR(indexv)[v] == 0) { + VECTOR(indexv)[v] = k + 1; k++; + IGRAPH_CHECK(igraph_vector_push_back(&avids, v)); + } + + neis2 = igraph_lazy_adjlist_get(&adjlist, v); + IGRAPH_CHECK_OOM(neis2, "Failed to query neighbors."); + + neilen = igraph_vector_int_size(neis2); + for (j = 0; j < neilen; j++) { + igraph_int_t nei = VECTOR(*neis2)[j]; + if (VECTOR(indexv)[nei] == 0) { + VECTOR(indexv)[nei] = k + 1; k++; + IGRAPH_CHECK(igraph_vector_push_back(&avids, nei)); + } + } + } + + /* Degree, ordering, ranking */ + affected_nodes = igraph_vector_size(&avids); + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, affected_nodes); + for (i = 0; i < affected_nodes; i++) { + igraph_int_t v = VECTOR(avids)[i]; + igraph_vector_int_t *neis2; + igraph_int_t deg; + neis2 = igraph_lazy_adjlist_get(&adjlist, v); + IGRAPH_CHECK_OOM(neis2, "Failed to query neighbors."); + VECTOR(degree)[i] = deg = igraph_vector_int_size(neis2); + if (deg > maxdegree) { + maxdegree = deg; + } + } + IGRAPH_CHECK(igraph_i_vector_int_order(°ree, &order, maxdegree + 1)); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_VECTOR_INIT_FINALLY(&rank, affected_nodes); + for (i = 0; i < affected_nodes; i++) { + VECTOR(rank)[ VECTOR(order)[i] ] = affected_nodes - i - 1; + } + + neis = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + if (neis == 0) { + IGRAPH_ERROR("Insufficient memory for local transitivity calculation.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_free, neis); + + IGRAPH_VECTOR_INIT_FINALLY(&triangles, affected_nodes); + for (nn = affected_nodes - 1; nn >= 0; nn--) { + igraph_int_t node = VECTOR(avids) [ VECTOR(order)[nn] ]; + igraph_vector_int_t *neis1, *neis2; + igraph_int_t neilen1, neilen2; + igraph_int_t nodeindex = VECTOR(indexv)[node]; + igraph_int_t noderank = VECTOR(rank) [nodeindex - 1]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_lazy_adjlist_get(&adjlist, node); + IGRAPH_CHECK_OOM(neis1, "Failed to query neighbors."); + + neilen1 = igraph_vector_int_size(neis1); + for (i = 0; i < neilen1; i++) { + igraph_int_t nei = VECTOR(*neis1)[i]; + neis[nei] = node + 1; + } + for (i = 0; i < neilen1; i++) { + igraph_int_t nei = VECTOR(*neis1)[i]; + igraph_int_t neiindex = VECTOR(indexv)[nei]; + igraph_int_t neirank = VECTOR(rank)[neiindex - 1]; + + /* fprintf(stderr, " nei %li (indexv %li, rank %li)\n", nei, */ + /* neiindex, neirank); */ + if (neirank > noderank) { + neis2 = igraph_lazy_adjlist_get(&adjlist, nei); + IGRAPH_CHECK_OOM(neis2, "Failed to query neighbors."); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + igraph_int_t nei2 = VECTOR(*neis2)[j]; + igraph_int_t nei2index = VECTOR(indexv)[nei2]; + igraph_int_t nei2rank = VECTOR(rank)[nei2index - 1]; + /* fprintf(stderr, " triple %li %li %li\n", node, nei, nei2); */ + if (nei2rank < neirank) { + continue; + } + if (neis[nei2] == node + 1) { + /* fprintf(stderr, " triangle\n"); */ + VECTOR(triangles) [ nei2index - 1 ] += 1; + VECTOR(triangles) [ neiindex - 1 ] += 1; + VECTOR(triangles) [ nodeindex - 1 ] += 1; + } + } + } + } + } + + /* Ok, for all affected vertices the number of triangles were counted */ + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + IGRAPH_VIT_RESET(vit); + for (i = 0; i < nodes_to_calc; i++, IGRAPH_VIT_NEXT(vit)) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + igraph_int_t idx = VECTOR(indexv)[node] - 1; + igraph_vector_int_t *neis2 = igraph_lazy_adjlist_get(&adjlist, node); + igraph_int_t deg; + + IGRAPH_CHECK_OOM(neis2, "Failed to query neighbors."); + + deg = igraph_vector_int_size(neis2); + if (mode == IGRAPH_TRANSITIVITY_ZERO && deg < 2) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = VECTOR(triangles)[idx] / deg / (deg - 1) * 2.0; + } + /* fprintf(stderr, "%f %f\n", VECTOR(triangles)[idx], triples); */ + } + + igraph_vector_destroy(&triangles); + igraph_free(neis); + igraph_vector_destroy(&rank); + igraph_vector_int_destroy(&order); + igraph_vector_destroy(&avids); + igraph_vector_destroy(&indexv); + igraph_lazy_adjlist_destroy(&adjlist); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(8); + + return IGRAPH_SUCCESS; +} + +/* This removes loop, multiple edges and edges that point + "backwards" according to the rank vector. */ +/* Note: Also used in scan.c */ +igraph_error_t igraph_i_trans4_al_simplify(igraph_adjlist_t *al, + const igraph_vector_int_t *rank) { + igraph_int_t i; + igraph_int_t n = al->length; + igraph_vector_int_t mark; + + IGRAPH_CHECK(igraph_vector_int_init(&mark, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &mark); + + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_int_t j, l = igraph_vector_int_size(v); + igraph_int_t irank = VECTOR(*rank)[i]; + VECTOR(mark)[i] = i + 1; + for (j = 0; j < l; /* nothing */) { + igraph_int_t e = VECTOR(*v)[j]; + if (VECTOR(*rank)[e] > irank && VECTOR(mark)[e] != i + 1) { + VECTOR(mark)[e] = i + 1; + j++; + } else { + VECTOR(*v)[j] = igraph_vector_int_tail(v); + igraph_vector_int_pop_back(v); + l--; + } + } + } + + igraph_vector_int_destroy(&mark); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t transitivity_local_undirected4(const igraph_t *graph, + igraph_vector_t *res, + igraph_transitivity_mode_t mode) { + +#define TRANSIT 1 +#include "properties/triangles_template.h" +#undef TRANSIT + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_transitivity_local_undirected + * \brief The local transitivity (clustering coefficient) of some vertices. + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. In case of the local transitivity, this + * probability is calculated separately for each vertex. + * + * + * Note that this measure is different from the global transitivity measure + * (see \ref igraph_transitivity_undirected() ) as it calculates a transitivity + * value for each vertex individually. + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * + * References: + * + * + * D. J. Watts and S. Strogatz: Collective dynamics of small-world networks. + * Nature 393(6684):440-442 (1998). + * + * \param graph The input graph. Edge directions and multiplicities are ignored. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids Vertex set, the vertices for which the local + * transitivity will be calculated. + * \param mode Defines how to treat vertices with degree less than two. + * \c IGRAPH_TRANSITIVITY_NAN returns \c NaN for these vertices, + * \c IGRAPH_TRANSITIVITY_ZERO returns zero. + * \return Error code. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_avglocal_undirected(). + * + * Time complexity: O(n*d^2), n is the number of vertices for which + * the transitivity is calculated, d is the average vertex degree. + */ + +igraph_error_t igraph_transitivity_local_undirected(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + + if (igraph_vs_is_all(&vids)) { + return transitivity_local_undirected4(graph, res, mode); + } else { + igraph_vit_t vit; + igraph_int_t size; + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + size = IGRAPH_VIT_SIZE(vit); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + if (size < 100) { + return transitivity_local_undirected1(graph, res, vids, mode); + } else { + return transitivity_local_undirected2(graph, res, vids, mode); + } + } +} + +static igraph_error_t adjacent_triangles1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids) { +# include "properties/triangles_template1.h" + return IGRAPH_SUCCESS; +} + +static igraph_error_t adjacent_triangles4(const igraph_t *graph, + igraph_vector_t *res) { +# include "properties/triangles_template.h" + return IGRAPH_SUCCESS; +} + +static igraph_error_t count_triangles_and_triples( + const igraph_t *graph, igraph_real_t *triangles, igraph_real_t *connected_triples) +{ + const igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_int_t mark; + igraph_adjlist_t al; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&mark, vcount); + + *triangles = 0; + if (connected_triples) *connected_triples = 0; + + /* In a simple graph we could loop through all edges and count how many common + * neighbours their endpoints have. However, we only need to consider each + * connected vertex pair once, even if there are multiple edges between them. + * The nested for loop below uses an adjlist that already has multi-edges and + * self-loops filtered, and solves this problem. + * + * Performance trick: + * + * We only consider connected u-v pairs when v < u to avoid triple-counting + * and speed up the procedure. This effectively orients an undirected graph + * as directed acyclic. In an arbitrary orientation, an originally undirected + * triangle may appear as one of these two directed patterns: + * - A -> B -> C -> A. This is NOT acyclic, so we don't encounter it. + * - A -> B -> C, A -> C. This is the only one we need to count. + * This is implemented through the `if (v >= u) break;` lines below. + * + * Some other functions achieve the same via igraph_i_trans4_al_simplify(). + */ + for (igraph_int_t v1 = 0; v1 < vcount; v1++) { + const igraph_vector_int_t *nei1 = igraph_adjlist_get(&al, v1); + const igraph_int_t d1 = igraph_vector_int_size(nei1); + if (d1 > 1) { + if (connected_triples) { + *connected_triples += (igraph_real_t) d1 * (d1 - 1.0) / 2.0; + } + + for (igraph_int_t i=0; i < d1; i++) { + const igraph_int_t v2 = VECTOR(*nei1)[i]; + if (v2 >= v1) break; + + VECTOR(mark)[v2] = v1+1; + } + for (igraph_int_t i=0; i < d1; i++) { + const igraph_int_t v2 = VECTOR(*nei1)[i]; + if (v2 >= v1) break; + + const igraph_vector_int_t *nei2 = igraph_adjlist_get(&al, v2); + const igraph_int_t d2 = igraph_vector_int_size(nei2); + for (igraph_int_t j=0; j < d2; j++) { + const igraph_int_t v3 = VECTOR(*nei2)[j]; + if (v3 >= v2) break; + + if (VECTOR(mark)[v3] == v1+1) { + *triangles += 1; + } + } + } + } + } + + igraph_vector_int_destroy(&mark); + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_count_triangles + * \brief Counts triangles in a graph. + * + * This function computes the total number of triangles, i.e. fully connected + * vertex triples, in a graph. Edge directions, edge multiplicities, and self-loops + * are ignored. + * + * \param graph The graph object. Edge directions and multiplicites are ignored. + * \param res Pointer to a real variable, the result will be stored here. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory for + * temporary data. + * + * \sa \ref igraph_list_triangles(), \ref igraph_count_adjacent_triangles(), + * \ref igraph_transitivity_undirected(). + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in + * the graph, d is the average node degree. + */ + +igraph_error_t igraph_count_triangles(const igraph_t *graph, igraph_real_t *res) { + return count_triangles_and_triples(graph, res, NULL); +} + + +/** + * \function igraph_count_adjacent_triangles + * \brief Count the number of triangles a vertex is part of. + * + * \param graph The input graph. Edge directions and multiplicities are ignored. + * \param res Initiliazed vector, the results are stored here. + * \param vids The vertices to perform the calculation for. + * \return Error mode. + * + * \sa \ref igraph_list_triangles() to list triangles, + * \ref igraph_count_triangles() to count all triangles at once. + * + * Time complexity: O(d^2 n), d is the average vertex degree of the + * queried vertices, n is their number. + */ + +igraph_error_t igraph_count_adjacent_triangles(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids) { + if (igraph_vs_is_all(&vids)) { + return adjacent_triangles4(graph, res); + } else { + return adjacent_triangles1(graph, res, vids); + } +} + + +/** + * \function igraph_list_triangles + * \brief Find all triangles in a graph. + * + * + * The triangles are reported as a long list of vertex ID triplets. Use + * the \c int variant of \ref igraph_matrix_view_from_vector() to create a + * matrix view into the vector where each triangle is stored in a column of the + * matrix (see the example). + * + * \param graph The input graph, edge directions are ignored. + * Multiple edges are ignored. + * \param res Pointer to an initialized integer vector, the result + * is stored here, in a long list of triples of vertex IDs. + * Each triple is a triangle in the graph. Each triangle is + * listed exactly once. + * \return Error code. + * + * \sa \ref igraph_count_triangles() to count the triangles, + * \ref igraph_count_adjacent_triangles() to count the triangles a vertex + * participates in, \ref igraph_transitivity_undirected() to compute + * the global clustering coefficient. + * + * Time complexity: O(d^2 n), d is the average degree, n is the number + * of vertices. + * + * \example examples/simple/igraph_list_triangles.c + */ + +igraph_error_t igraph_list_triangles(const igraph_t *graph, + igraph_vector_int_t *res) { +# define TRIANGLES +# include "properties/triangles_template.h" +# undef TRIANGLES + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_transitivity_undirected + * \brief Calculates the transitivity (clustering coefficient) of a graph. + * + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. More precisely, this is the ratio of the + * triangles and connected triples in the graph, the result is a + * single real number. Directed graphs are considered as undirected ones + * and multi-edges are ignored. + * + * + * Note that this measure is different from the local transitivity measure + * (see \ref igraph_transitivity_local_undirected() ) as it calculates a single + * value for the whole graph. + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * + * References: + * + * + * S. Wasserman and K. Faust: Social Network Analysis: Methods and + * Applications. Cambridge: Cambridge University Press, 1994. + * + * \param graph The graph object. Edge directions and multiplicites are ignored. + * \param res Pointer to a real variable, the result will be stored here. + * \param mode Defines how to treat graphs with no connected triples. + * \c IGRAPH_TRANSITIVITY_NAN returns \c NaN in this case, + * \c IGRAPH_TRANSITIVITY_ZERO returns zero. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory for + * temporary data. + * + * \sa \ref igraph_transitivity_local_undirected(), + * \ref igraph_transitivity_avglocal_undirected(). + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in + * the graph, d is the average node degree. + * + * \example examples/simple/igraph_transitivity.c + */ + +igraph_error_t igraph_transitivity_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode) { + + igraph_real_t triangles, connected_triples; + + IGRAPH_CHECK(count_triangles_and_triples(graph, &triangles, &connected_triples)); + + if (connected_triples == 0 && mode == IGRAPH_TRANSITIVITY_ZERO) { + *res = 0; + } else { + *res = triangles / connected_triples * 3.0; + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t transitivity_barrat1( + const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_int_t nodes_to_calc; + igraph_vector_int_t *adj1, *adj2; + igraph_vector_int_t neis; + igraph_vector_t actw; + igraph_lazy_inclist_t incident; + igraph_int_t i; + igraph_vector_t strength; + + /* Precondition: weight vector is not null, its length equals the number of + * edges, and the graph has at least one vertex. The graph must not have + * multi-edges. These must be ensured by the caller. + */ + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_int_init(&neis, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + IGRAPH_VECTOR_INIT_FINALLY(&actw, no_of_nodes); + + IGRAPH_VECTOR_INIT_FINALLY(&strength, 0); + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &incident, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &incident); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + igraph_int_t adjlen1, adjlen2, j, k; + igraph_real_t triples, triangles; + + IGRAPH_ALLOW_INTERRUPTION(); + + adj1 = igraph_lazy_inclist_get(&incident, node); + IGRAPH_CHECK_OOM(adj1, "Failed to query incident edges."); + adjlen1 = igraph_vector_int_size(adj1); + /* Mark the neighbors of the node */ + for (j = 0; j < adjlen1; j++) { + igraph_int_t edge = VECTOR(*adj1)[j]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, node); + VECTOR(neis)[nei] = i + 1; + VECTOR(actw)[nei] = VECTOR(*weights)[edge]; + } + triples = VECTOR(strength)[node] * (adjlen1 - 1); + triangles = 0.0; + + for (j = 0; j < adjlen1; j++) { + igraph_int_t edge1 = VECTOR(*adj1)[j]; + igraph_real_t weight1 = VECTOR(*weights)[edge1]; + igraph_int_t v = IGRAPH_OTHER(graph, edge1, node); + adj2 = igraph_lazy_inclist_get(&incident, v); + IGRAPH_CHECK_OOM(adj2, "Failed to query incident edges."); + adjlen2 = igraph_vector_int_size(adj2); + for (k = 0; k < adjlen2; k++) { + igraph_int_t edge2 = VECTOR(*adj2)[k]; + igraph_int_t v2 = IGRAPH_OTHER(graph, edge2, v); + if (VECTOR(neis)[v2] == i + 1) { + triangles += (VECTOR(actw)[v2] + weight1) / 2.0; + } + } + } + if (mode == IGRAPH_TRANSITIVITY_ZERO && triples == 0) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = triangles / triples; + } + } + + igraph_lazy_inclist_destroy(&incident); + igraph_vector_destroy(&strength); + igraph_vector_destroy(&actw); + igraph_vector_int_destroy(&neis); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t transitivity_barrat4( + const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_vector_int_t order; + igraph_vector_int_t degree; + igraph_vector_t strength; + igraph_vector_t rank; + igraph_int_t maxdegree; + igraph_inclist_t incident; + igraph_vector_int_t neis; + igraph_vector_int_t *adj1, *adj2; + igraph_vector_t actw; + igraph_int_t i, nn; + + /* Precondition: weight vector is not null, its length equals the number of + * edges, and the graph has at least one vertex. The graph must not have + * multi-edges. These must be ensured by the caller. + */ + + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + maxdegree = igraph_vector_int_max(°ree) + 1; + IGRAPH_CHECK(igraph_i_vector_int_order(°ree, &order, maxdegree)); + + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + IGRAPH_VECTOR_INIT_FINALLY(&rank, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ VECTOR(order)[i] ] = no_of_nodes - i - 1; + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &incident, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incident); + + IGRAPH_CHECK(igraph_vector_int_init(&neis, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + IGRAPH_VECTOR_INIT_FINALLY(&actw, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (nn = no_of_nodes - 1; nn >= 0; nn--) { + igraph_int_t adjlen1, adjlen2; + igraph_real_t triples; + igraph_int_t node = VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + adj1 = igraph_inclist_get(&incident, node); + adjlen1 = igraph_vector_int_size(adj1); + triples = VECTOR(strength)[node] * (adjlen1 - 1) / 2.0; + /* Mark the neighbors of the node */ + for (i = 0; i < adjlen1; i++) { + igraph_int_t edge = VECTOR(*adj1)[i]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge, node); + VECTOR(neis)[nei] = node + 1; + VECTOR(actw)[nei] = VECTOR(*weights)[edge]; + } + + for (i = 0; i < adjlen1; i++) { + igraph_int_t edge1 = VECTOR(*adj1)[i]; + igraph_real_t weight1 = VECTOR(*weights)[edge1]; + igraph_int_t nei = IGRAPH_OTHER(graph, edge1, node); + igraph_int_t j; + if (VECTOR(rank)[nei] > VECTOR(rank)[node]) { + adj2 = igraph_inclist_get(&incident, nei); + adjlen2 = igraph_vector_int_size(adj2); + for (j = 0; j < adjlen2; j++) { + igraph_int_t edge2 = VECTOR(*adj2)[j]; + igraph_real_t weight2 = VECTOR(*weights)[edge2]; + igraph_int_t nei2 = IGRAPH_OTHER(graph, edge2, nei); + if (VECTOR(rank)[nei2] < VECTOR(rank)[nei]) { + continue; + } + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[nei2] += (VECTOR(actw)[nei2] + weight2) / 2.0; + VECTOR(*res)[nei] += (weight1 + weight2) / 2.0; + VECTOR(*res)[node] += (VECTOR(actw)[nei2] + weight1) / 2.0; + } + } + } + } + + if (mode == IGRAPH_TRANSITIVITY_ZERO && triples == 0) { + VECTOR(*res)[node] = 0.0; + } else { + VECTOR(*res)[node] /= triples; + } + } + + igraph_vector_destroy(&actw); + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incident); + igraph_vector_destroy(&rank); + igraph_vector_int_destroy(°ree); + igraph_vector_destroy(&strength); + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(7); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_transitivity_barrat + * \brief Weighted local transitivity of some vertices, as defined by A. Barrat. + * + * This is a local transitivity, i.e. a vertex-level index. For a + * given vertex \c i, from all triangles in which it participates we + * consider the weight of the edges incident on \c i. The transitivity + * is the sum of these weights divided by twice the strength of the + * vertex (see \ref igraph_strength()) and the degree of the vertex + * minus one. See equation (5) in Alain Barrat, Marc Barthelemy, Romualdo + * Pastor-Satorras, Alessandro Vespignani: The architecture of complex + * weighted networks, Proc. Natl. Acad. Sci. USA 101, 3747 (2004) at + * https://doi.org/10.1073/pnas.0400087101 for the exact formula. + * + * \param graph The input graph. Edge directions are ignored for + * directed graphs. Note that the function does \em not work for + * non-simple graphs. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param weights Edge weights. If this is a null pointer, then a + * warning is given and \ref igraph_transitivity_local_undirected() + * is called. + * \param mode Defines how to treat vertices with zero strength. + * \c IGRAPH_TRANSITIVITY_NAN says that the transitivity of these + * vertices is \c NaN, \c IGRAPH_TRANSITIVITY_ZERO says it is zero. + * + * \return Error code. + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in + * the graph, d is the average node degree. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_local_undirected() and \ref + * igraph_transitivity_avglocal_undirected() for other kinds of + * (non-weighted) transitivity. + */ + +igraph_error_t igraph_transitivity_barrat(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_bool_t has_multiple; + + /* Handle fallback to unweighted version and common cases */ + if (!weights) { + if (no_of_edges != 0) { + IGRAPH_WARNING("No weights given for Barrat's transitivity, unweighted version is used."); + } + return igraph_transitivity_local_undirected(graph, res, vids, mode); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERRORF("Edge weight vector length (%" IGRAPH_PRId ") not equal to " + "number of edges (%" IGRAPH_PRId ").", IGRAPH_EINVAL, + igraph_vector_size(weights), no_of_edges); + } + + if (no_of_nodes == 0) { + igraph_vector_clear(res); + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple)); + if (! has_multiple && igraph_is_directed(graph)) { + /* When the graph is directed, mutual edges are effectively multi-edges as we + * are ignoring edge directions. */ + IGRAPH_CHECK(igraph_has_mutual(graph, &has_multiple, false)); + } + if (has_multiple) { + IGRAPH_ERROR("Barrat's weighted transitivity measure works only if the graph has no multi-edges.", + IGRAPH_EINVAL); + } + + /* Preconditions validated, now we can call the real implementation */ + + if (igraph_vs_is_all(&vids)) { + return transitivity_barrat4(graph, res, weights, mode); + } else { + return transitivity_barrat1(graph, res, vids, weights, mode); + } +} diff --git a/src/properties/triangles_template.h b/src/properties/triangles_template.h new file mode 100644 index 0000000..c18bf97 --- /dev/null +++ b/src/properties/triangles_template.h @@ -0,0 +1,137 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifdef TRANSIT +#define TRANSIT_TRIEDGES +#endif + +igraph_int_t no_of_nodes = igraph_vcount(graph); +igraph_int_t node, i, j, nn; +igraph_adjlist_t allneis; +igraph_vector_int_t *neis1, *neis2; +igraph_int_t neilen1, neilen2; +igraph_int_t *neis; +igraph_int_t maxdegree; + +#ifdef TRANSIT_TRIEDGES +igraph_int_t deg1; +#endif + +igraph_vector_int_t order; +igraph_vector_int_t rank; +igraph_vector_int_t degree; + +if (no_of_nodes == 0) { +#ifndef TRIANGLES + igraph_vector_clear(res); +#else + igraph_vector_int_clear(res); +#endif + return IGRAPH_SUCCESS; +} + +IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); +IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, no_of_nodes); + +IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); +IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + +for (i = 0; i < no_of_nodes; i++) { + VECTOR(degree)[i] = igraph_vector_int_size(igraph_adjlist_get(&allneis, i)); +} + +maxdegree = igraph_vector_int_max(°ree) + 1; +IGRAPH_CHECK(igraph_i_vector_int_order(°ree, &order, maxdegree)); +IGRAPH_VECTOR_INT_INIT_FINALLY(&rank, no_of_nodes); +for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ VECTOR(order)[i] ] = no_of_nodes - i - 1; +} + +IGRAPH_CHECK(igraph_i_trans4_al_simplify(&allneis, &rank)); + +neis = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); +IGRAPH_CHECK_OOM(neis, "Insufficient memory to count triangles."); +IGRAPH_FINALLY(igraph_free, neis); + +#ifndef TRIANGLES + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); +#else + igraph_vector_int_clear(res); +#endif + +for (nn = no_of_nodes - 1; nn >= 0; nn--) { + node = VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_adjlist_get(&allneis, node); + neilen1 = igraph_vector_int_size(neis1); + +#ifdef TRANSIT_TRIEDGES + deg1 = VECTOR(degree)[node]; +#endif + + /* Mark the neighbors of the node */ + for (i = 0; i < neilen1; i++) { + neis[ VECTOR(*neis1)[i] ] = node + 1; + } + + for (i = 0; i < neilen1; i++) { + igraph_int_t nei = VECTOR(*neis1)[i]; + neis2 = igraph_adjlist_get(&allneis, nei); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + igraph_int_t nei2 = VECTOR(*neis2)[j]; + if (neis[nei2] == node + 1) { +#ifndef TRIANGLES + VECTOR(*res)[nei2] += 1; + VECTOR(*res)[nei] += 1; + VECTOR(*res)[node] += 1; +#else + IGRAPH_CHECK(igraph_vector_int_push_back(res, node)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, nei2)); +#endif + } + } + } + +#ifdef TRANSIT + if (mode == IGRAPH_TRANSITIVITY_ZERO && deg1 < 2) { + VECTOR(*res)[node] = 0.0; + } else { + VECTOR(*res)[node] = VECTOR(*res)[node] / deg1 / (deg1 - 1) * 2.0; + } +#endif +} + +igraph_free(neis); +igraph_adjlist_destroy(&allneis); +igraph_vector_int_destroy(&rank); +igraph_vector_int_destroy(°ree); +igraph_vector_int_destroy(&order); +IGRAPH_FINALLY_CLEAN(5); + +#ifdef TRANSIT_TRIEDGES +#undef TRANSIT_TRIEDGES +#endif diff --git a/src/properties/triangles_template1.h b/src/properties/triangles_template1.h new file mode 100644 index 0000000..8e33a53 --- /dev/null +++ b/src/properties/triangles_template1.h @@ -0,0 +1,93 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +igraph_int_t no_of_nodes = igraph_vcount(graph); +igraph_vit_t vit; +igraph_int_t nodes_to_calc; +igraph_vector_int_t *neis1, *neis2; +igraph_real_t triangles; +igraph_int_t i, j, k; +igraph_int_t neilen1, neilen2; +igraph_int_t *neis; +igraph_lazy_adjlist_t adjlist; + +IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); +IGRAPH_FINALLY(igraph_vit_destroy, &vit); +nodes_to_calc = IGRAPH_VIT_SIZE(vit); + +if (nodes_to_calc == 0) { + igraph_vector_clear(res); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +neis = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); +IGRAPH_CHECK_OOM(neis, "Insufficient memory to count triangles."); +IGRAPH_FINALLY(igraph_free, neis); + +IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + +IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE)); +IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + +for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_lazy_adjlist_get(&adjlist, node); + IGRAPH_CHECK_OOM(neis1, "Failed to query neighbors."); + neilen1 = igraph_vector_int_size(neis1); + for (j = 0; j < neilen1; j++) { + neis[ VECTOR(*neis1)[j] ] = i + 1; + } + triangles = 0; + + for (j = 0; j < neilen1; j++) { + igraph_int_t v = VECTOR(*neis1)[j]; + neis2 = igraph_lazy_adjlist_get(&adjlist, v); + IGRAPH_CHECK_OOM(neis2, "Failed to query neighbors."); + neilen2 = igraph_vector_int_size(neis2); + for (k = 0; k < neilen2; k++) { + igraph_int_t v2 = VECTOR(*neis2)[k]; + if (neis[v2] == i + 1) { + triangles += 1.0; + } + } + } + +#ifdef TRANSIT + if (mode == IGRAPH_TRANSITIVITY_ZERO && neilen1 < 2) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = triangles / neilen1 / (neilen1 - 1); + } +#else + VECTOR(*res)[i] = triangles / 2; +#endif +} + +igraph_lazy_adjlist_destroy(&adjlist); +IGRAPH_FREE(neis); +igraph_vit_destroy(&vit); +IGRAPH_FINALLY_CLEAN(3); diff --git a/src/random/random.c b/src/random/random.c new file mode 100644 index 0000000..708fba0 --- /dev/null +++ b/src/random/random.c @@ -0,0 +1,2229 @@ +/* + igraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + + +#include "igraph_random.h" + +#include "igraph_nongraph.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "core/interruption.h" +#include "math/safe_intop.h" +#include "random/random_internal.h" + +#include "config.h" /* IGRAPH_THREAD_LOCAL, HAVE___UINT128_T, HAVE__UMUL128 */ + +#if defined(HAVE__UMUL128) || defined(HAVE___UMULH) +#include /* _umul128() or __umulh() are defined in intrin.h */ +#endif + +#include +#include +#include /* DBL_MANT_DIG */ +#include + +/** + * \section about_rngs + * + *
+ * About random numbers in igraph + * + * + * Some algorithms in igraph, such as sampling from random graph models, + * require random number generators (RNGs). igraph includes a flexible + * RNG framework that allows hooking up arbitrary random number generators, + * and comes with several ready-to-use generators. This framework is used + * in igraph's high-level interfaces to integrate with the host language's + * own RNG. + * + *
+ * + */ + +/** + * \section rng_use_cases + * + *
Use cases + * + *
Normal (default) use + * + * If the user does not use any of the RNG functions explicitly, but calls + * some of the randomized igraph functions, then a default RNG is set + * up the first time an igraph function needs random numbers. The + * seed of this RNG is the output of the time(0) function + * call, using the time function from the standard C + * library. This ensures that igraph creates a different random graph, + * each time the C program is called. + * + * + * + * The created default generator is stored internally and can be + * queried with the \ref igraph_rng_default() function. + * + *
+ * + *
Reproducible simulations + * + * If reproducible results are needed, then the user should set the + * seed of the default random number generator explicitly, using the + * \ref igraph_rng_seed() function on the default generator, \ref + * igraph_rng_default(). When setting the seed to the same number, + * igraph generates exactly the same random graph (or series of random + * graphs). + * + *
+ * + *
Changing the default generator + * + * By default igraph uses the \ref igraph_rng_default() random number + * generator. This can be changed any time by calling \ref + * igraph_rng_set_default(), with an already initialized random number + * generator. Note that the old (replaced) generator is not + * destroyed, so no memory is deallocated. + * + *
+ * + *
Using multiple generators + * + * igraph also provides functions to set up multiple random number + * generators, using the \ref igraph_rng_init() function, and then + * generating random numbers from them, e.g. with \ref igraph_rng_get_integer() + * and/or \ref igraph_rng_get_unif() calls. + * + * + * + * Note that initializing a new random number generator is + * independent of the generator that the igraph functions themselves + * use. If you want to replace that, then please use \ref + * igraph_rng_set_default(). + * + *
+ * + *
Example + * + * \example examples/simple/random_seed.c + * + *
+ * + *
+ */ + +/* ------------------------------------ */ + +/** + * \var igraph_i_rng_default + * The default igraph random number generator + * + * This generator is used by all builtin igraph functions that need to + * generate random numbers; e.g. all random graph generators. + * + * You can use \ref igraph_i_rng_default with \ref igraph_rng_seed() + * to set its seed. + * + * You can change the default generator using the \ref + * igraph_rng_set_default() function. + */ + +extern igraph_rng_t igraph_i_rng_default; /* defined in rng_pcg32.c */ +IGRAPH_THREAD_LOCAL igraph_rng_t *igraph_i_rng_default_ptr = &igraph_i_rng_default; + +/** + * \function igraph_rng_set_default + * \brief Set the default igraph random number generator. + * + * This function updates the default RNG used by igraph to be the one + * pointed to by \p rng, and returns a pointer to the previous default + * RNG. Future calls to \ref igraph_rng_default() will return the same + * pointer as \p rng. The RNG pointed to by \p rng must not be destroyed + * for as long as it is used as the default. + * + * \param rng The random number generator to use as default from now + * on. Calling \ref igraph_rng_destroy() on it, while it is still + * being used as the default will result in crashes and/or + * unpredictable results. + * \return Pointer the previous default RNG. + * + * Time complexity: O(1). + */ + +igraph_rng_t *igraph_rng_set_default(igraph_rng_t *rng) { + igraph_rng_t *old_rng = igraph_i_rng_default_ptr; + igraph_i_rng_default_ptr = rng; + return old_rng; +} + + +/* ------------------------------------ */ + +/** + * \function igraph_rng_default + * \brief Query the default random number generator. + * + * \return A pointer to the default random number generator. + * + * \sa \ref igraph_rng_set_default() + */ + +igraph_rng_t *igraph_rng_default(void) { + return igraph_i_rng_default_ptr; +} + +/* ------------------------------------ */ + +static igraph_uint_t igraph_i_rng_get_random_bits(igraph_rng_t *rng, uint8_t bits); +static uint64_t igraph_i_rng_get_random_bits_uint64(igraph_rng_t *rng, uint8_t bits); + +static igraph_uint_t igraph_i_rng_get_uint(igraph_rng_t *rng); +static igraph_uint_t igraph_i_rng_get_uint_bounded(igraph_rng_t *rng, igraph_uint_t range); + +static uint32_t igraph_i_rng_get_uint32(igraph_rng_t *rng); +static uint32_t igraph_i_rng_get_uint32_bounded(igraph_rng_t *rng, uint32_t range); + +#if IGRAPH_INTEGER_SIZE == 64 +static uint64_t igraph_i_rng_get_uint64(igraph_rng_t *rng); +static uint64_t igraph_i_rng_get_uint64_bounded(igraph_rng_t *rng, uint64_t range); +#endif + +static double igraph_i_norm_rand(igraph_rng_t *rng); +static double igraph_i_exp_rand(igraph_rng_t *rng); +static double igraph_i_rbinom(igraph_rng_t *rng, igraph_int_t n, double pp); +static double igraph_i_rexp(igraph_rng_t *rng, double rate); +static double igraph_i_rgamma(igraph_rng_t *rng, double shape, double scale); +static double igraph_i_rpois(igraph_rng_t *rng, double rate); + +/** + * \function igraph_rng_init + * \brief Initializes a random number generator. + * + * This function allocates memory for a random number generator, with + * the given type, and sets its seed to the default. + * + * \param rng Pointer to an uninitialized RNG. + * \param type The type of the RNG, such as \ref igraph_rngtype_mt19937, + * \ref igraph_rngtype_glibc2, \ref igraph_rngtype_pcg32 or + * \ref igraph_rngtype_pcg64. + * \return Error code. + */ + +igraph_error_t igraph_rng_init(igraph_rng_t *rng, const igraph_rng_type_t *type) { + rng->type = type; + IGRAPH_CHECK(rng->type->init(&rng->state)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_rng_destroy + * \brief Deallocates memory associated with a random number generator. + * + * \param rng The RNG to destroy. Do not destroy an RNG that is used + * as the default igraph RNG. + * + * Time complexity: O(1). + */ + +void igraph_rng_destroy(igraph_rng_t *rng) { + rng->type->destroy(rng->state); +} + +/** + * \function igraph_rng_seed + * \brief Seeds a random number generator. + * + * \param rng The RNG. + * \param seed The new seed. + * \return Error code. + * + * Time complexity: usually O(1), but may depend on the type of the + * RNG. + */ +igraph_error_t igraph_rng_seed(igraph_rng_t *rng, igraph_uint_t seed) { + const igraph_rng_type_t *type = rng->type; + IGRAPH_CHECK(type->seed(rng->state, seed)); + rng->is_seeded = true; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_rng_bits + * \brief The number of random bits that a random number generator can produces in a single round. + * + * \param rng The RNG. + * \return The number of random bits that can be generated in a single round + * with the RNG. + * + * Time complexity: O(1). + */ +igraph_int_t igraph_rng_bits(const igraph_rng_t* rng) { + return rng->type->bits; +} + +/** + * \function igraph_rng_max + * \brief The maximum possible integer for a random number generator. + * + * Note that this number is only for informational purposes; it returns the + * maximum possible integer that can be generated with the RNG with a single + * call to its internals. It is derived directly from the number of random + * \em bits that the RNG can generate in a single round. When this is smaller + * than what would be needed by other RNG functions like \ref igraph_rng_get_integer(), + * igraph will call the RNG multiple times to generate more random bits. + * + * \param rng The RNG. + * \return The largest possible integer that can be generated in a single round + * with the RNG. + * + * Time complexity: O(1). + */ + +igraph_uint_t igraph_rng_max(const igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; +#if IGRAPH_INTEGER_SIZE == 64 + return (type->bits >= 64) ? 0xFFFFFFFFFFFFFFFFULL : ((1ULL << type->bits) - 1); +#else + return (type->bits >= 32) ? 0xFFFFFFFFUL : ((1ULL << type->bits) - 1); +#endif +} + +/** + * \function igraph_rng_name + * \brief The type of a random number generator. + * + * \param rng The RNG. + * \return The name of the type of the generator. Do not deallocate or + * change the returned string. + * + * Time complexity: O(1). + */ + +const char *igraph_rng_name(const igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + return type->name; +} + +/** + * Generates a given number of random bits, possibly invoking the underlying + * RNG multiple times if needed, and returns the result in an \c igraph_uint_t . + * + * \param rng The RNG. + * \param bits The number of random bits needed. Must be smaller than or equal + * to the size of the \c igraph_uint_t data type. Passing a value larger + * than the size of \c igraph_uint_t will throw away random bits except + * the last few that are needed to fill an \c igraph_uint_t . + * \return The random bits, packed into the low bits of an \c igraph_uint_t . + * The upper, unused bits of \c igraph_uint_t will be set to zero. + */ +static igraph_uint_t igraph_i_rng_get_random_bits(igraph_rng_t *rng, uint8_t bits) { + const igraph_rng_type_t *type = rng->type; + igraph_int_t rng_bitwidth = igraph_rng_bits(rng); + igraph_uint_t result; + + if (rng_bitwidth >= bits) { + /* keep the high bits as RNGs sometimes tend to have lower entropy in + * low bits than in high bits */ + result = type->get(rng->state) >> (rng_bitwidth - bits); + } else { + result = 0; + do { + result = (result << rng_bitwidth) + type->get(rng->state); + bits -= rng_bitwidth; + } while (bits > rng_bitwidth); + + /* and now the last piece */ + result = (result << bits) + (type->get(rng->state) >> (rng_bitwidth - bits)); + } + + return result; +} + +/** + * Generates a given number of random bits, possibly invoking the underlying + * RNG multiple times if needed, and returns the result in an \c uint64_t . + * + * Prefer \c igraph_i_rng_get_random_bits() if you know that you need at most + * 32 bits due to the type of the return value. This function might perform + * worse on 32-bit platforms because the result is always 64 bits. + * + * \param rng The RNG. + * \param bits The number of random bits needed. Must be smaller than or equal + * to the size of the \c uint64_t data type. Passing a value larger + * than the size of \c uint64_t will throw away random bits except + * the last few that are needed to fill an \c uint64_t . + * \return The random bits, packed into the low bits of an \c uint64_t . + * The upper, unused bits of \c uint64_t will be set to zero. + */ +static uint64_t igraph_i_rng_get_random_bits_uint64(igraph_rng_t *rng, uint8_t bits) { + const igraph_rng_type_t *type = rng->type; + igraph_int_t rng_bitwidth = igraph_rng_bits(rng); + uint64_t result; + + if (rng_bitwidth >= bits) { + /* keep the high bits as RNGs sometimes tend to have lower entropy in + * low bits than in high bits */ + result = type->get(rng->state) >> (rng_bitwidth - bits); + } else { + result = 0; + do { + result = (result << rng_bitwidth) + type->get(rng->state); + bits -= rng_bitwidth; + } while (bits > rng_bitwidth); + + /* and now the last piece */ + result = (result << bits) + (type->get(rng->state) >> (rng_bitwidth - bits)); + } + + return result; +} + +/** + * Generates a random integer in the full range of the \c igraph_uint_t + * data type. + * + * \param rng The RNG. + * \return The random integer. + */ +static igraph_uint_t igraph_i_rng_get_uint(igraph_rng_t *rng) { + return igraph_i_rng_get_random_bits(rng, sizeof(igraph_uint_t) * 8); +} + +/** + * Generates a random integer in the full range of the \c uint32_t + * data type. + * + * \param rng The RNG. + * \return The random integer. + */ +static uint32_t igraph_i_rng_get_uint32(igraph_rng_t *rng) { + return igraph_i_rng_get_random_bits(rng, 32); +} + +/** + * Generates a random integer in the range [0; range) (upper bound exclusive), + * restricted to at most 32 bits. + * + * \param rng The RNG. + * \param range The upper bound (exclusive). + * \return The random integer. + */ +static uint32_t igraph_i_rng_get_uint32_bounded(igraph_rng_t *rng, uint32_t range) { + /* Debiased integer multiplication -- Lemire's method + * from https://www.pcg-random.org/posts/bounded-rands.html */ + uint32_t x, l, t = (-range) % range; + uint64_t m; + do { + x = igraph_i_rng_get_uint32(rng); + m = (uint64_t)(x) * (uint64_t)(range); + l = (uint32_t)m; + } while (l < t); + return m >> 32; +} + +#if IGRAPH_INTEGER_SIZE == 64 +/** + * Generates a random integer in the full range of the \c uint64_t + * data type. + * + * \param rng The RNG. + * \param range The upper bound (inclusive). + * \return The random integer. + */ +static uint64_t igraph_i_rng_get_uint64(igraph_rng_t *rng) { + return igraph_i_rng_get_random_bits(rng, 64); +} + +#if !defined(HAVE___UINT128_T) +static uint64_t igraph_i_umul128(uint64_t a, uint64_t b, uint64_t *hi) { +#if defined(HAVE__UMUL128) + /* MSVC has _umul128() on x64 but not on arm64 */ + return _umul128(a, b, hi); +#elif defined(HAVE___UMULH) + /* MSVC has __umulh() on arm64 */ + *hi = __umulh(a, b); + return a*b; +#else + /* Portable but slow fallback implementation of unsigned + * 64-bit multiplication obtaining a 128-bit result. + * Based on https://stackoverflow.com/a/28904636/695132 + */ + + uint64_t a_lo = (uint32_t) a; + uint64_t a_hi = a >> 32; + uint64_t b_lo = (uint32_t) b; + uint64_t b_hi = b >> 32; + + uint64_t a_x_b_hi = a_hi * b_hi; + uint64_t a_x_b_mid = a_hi * b_lo; + uint64_t b_x_a_mid = b_hi * a_lo; + uint64_t a_x_b_lo = a_lo * b_lo; + + uint64_t carry_bit = ((uint64_t) (uint32_t) a_x_b_mid + + (uint64_t) (uint32_t) b_x_a_mid + + (a_x_b_lo >> 32) ) >> 32; + + *hi = a_x_b_hi + + (a_x_b_mid >> 32) + (b_x_a_mid >> 32) + + carry_bit; + + return a*b; +#endif +} +#endif /* !defined(HAVE___UINT128_T) */ + +/** + * Generates a random integer in the range [0; range) (upper bound exclusive), + * restricted to at most 64 bits. + * + * \param rng The RNG. + * \param range The upper bound (exclusive). + * \return The random integer. + */ +static uint64_t igraph_i_rng_get_uint64_bounded(igraph_rng_t *rng, uint64_t range) { + /* Debiased integer multiplication -- Lemire's method + * from https://www.pcg-random.org/posts/bounded-rands.html */ + uint64_t x, l, t = (-range) % range; +#if defined(HAVE___UINT128_T) + /* gcc and clang have __uint128_t */ + __uint128_t m; + do { + x = igraph_i_rng_get_uint64(rng); + m = (__uint128_t)(x) * (__uint128_t)(range); + l = (uint64_t)m; + } while (l < t); + return m >> 64; +#else + uint64_t hi; + do { + x = igraph_i_rng_get_uint64(rng); + l = igraph_i_umul128(x, range, &hi); + } while (l < t); + return hi; +#endif +} + +#endif /* IGRAPH_INTEGER_SIZE == 64 */ + +/** + * Generates a random integer in the range [0; range) (upper bound exclusive). + * + * \param rng The RNG. + * \param range The upper bound (exclusive). + * \return The random integer. + */ +static igraph_uint_t igraph_i_rng_get_uint_bounded(igraph_rng_t *rng, igraph_uint_t range) { + /* We must make this function behave the same way for range < 2^32 so igraph + * behaves the same way on 32-bit and 64-bit platforms as long as we stick + * to integers less than 2^32. This is to ensure that the unit tests are + * consistent */ + +#if IGRAPH_INTEGER_SIZE == 32 + return igraph_i_rng_get_uint32_bounded(rng, range); +#else + if (range <= UINT32_MAX) { + return igraph_i_rng_get_uint32_bounded(rng, range); + } else { + return igraph_i_rng_get_uint64_bounded(rng, range); + } +#endif +} + + +/** + * \function igraph_rng_get_bool + * \brief Generate a random boolean. + * + * Use this function only when a single random boolean, i.e. a single bit + * is needed at a time. It is not efficient for generating multiple bits. + * + * \param rng Pointer to the RNG to use for the generation. Use \ref + * igraph_rng_default() here to use the default igraph RNG. + * \return The generated bit, as a truth value. + */ + +igraph_bool_t igraph_rng_get_bool(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + const igraph_int_t rng_bitwidth = igraph_rng_bits(rng); + /* Keep the highest bit as RNGs sometimes tend to have lower entropy in + * low bits than in high bits. + * + * Ensure that the return value is stricly 0 or 1 even if igraph_bool_t is + * defined to something else than a native bool. + */ + return (type->get(rng->state) >> (rng_bitwidth - 1)) & (igraph_uint_t)1; +} + +/** + * \function igraph_rng_get_integer + * \brief Generate an integer random number from an interval. + * + * Generate uniformly distributed integers from the interval [l, h]. + * + * \param rng Pointer to the RNG to use for the generation. Use \ref + * igraph_rng_default() here to use the default igraph RNG. + * \param l Lower limit, inclusive, it can be negative as well. + * \param h Upper limit, inclusive, it can be negative as well, but it + * must be at least l. + * \return The generated random integer. + * + * Time complexity: O(log2(h-l+1) / bits) where bits is the value of + * \ref igraph_rng_bits(rng). + */ + +igraph_int_t igraph_rng_get_integer( + igraph_rng_t *rng, igraph_int_t l, igraph_int_t h +) { + const igraph_rng_type_t *type = rng->type; + igraph_uint_t range; + + assert(h >= l); + + if (h == l) { + return l; + } + + if (type->get_int) { + return type->get_int(rng->state, l, h); + } + + if (IGRAPH_UNLIKELY(l == IGRAPH_INTEGER_MIN && h == IGRAPH_INTEGER_MAX)) { + /* Full uint range is needed, we can just grab a random number from + * the uint range and cast it to a signed integer */ + return (igraph_int_t) igraph_i_rng_get_uint(rng); + } else if (l >= 0 || h < 0) { + /* this is okay, (h - l) will not overflow an igraph_int_t */ + range = (igraph_uint_t)(h - l) + 1; + } else { + /* (h - l) could potentially overflow so we need to play it safe. If we + * are here, l < 0 and h >= 0 so we can cast -l into an igraph_uint_t + * safely and do the subtraction that way */ + range = ((igraph_uint_t)(h)) + ((igraph_uint_t)(-l)) + 1; + } + + return l + igraph_i_rng_get_uint_bounded(rng, range); +} + +/** + * \function igraph_rng_get_normal + * \brief Samples from a normal distribution. + * + * Generates random variates from a normal distribution with probability + * density + * + *
+ * exp( -(x - m)^2 / (2 s^2) ). + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param m The mean. + * \param s The standard deviation. + * \return The generated normally distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_normal(igraph_rng_t *rng, + igraph_real_t m, igraph_real_t s) { + const igraph_rng_type_t *type = rng->type; + if (type->get_norm) { + return type->get_norm(rng->state) * s + m; + } else { + return igraph_i_norm_rand(rng) * s + m; + } +} + +/** + * \function igraph_rng_get_unif + * \brief Samples real numbers from a given interval. + * + * Generates uniformly distributed real numbers from the [l, h) + * half-open interval. + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param l The lower bound, it can be negative. + * \param h The upper bound, it can be negative, but it has to be + * larger than the lower bound. + * \return The generated uniformly distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_unif(igraph_rng_t *rng, + igraph_real_t l, igraph_real_t h) { + assert(h >= l); + + if (l == h) return h; + + /* Ensure that 'h' is never produced due to numerical roundoff errors, except when l == h. */ + igraph_real_t r; + do { + r = igraph_rng_get_unif01(rng) * (h - l) + l; + } while (IGRAPH_UNLIKELY(r == h)); + return r; +} + +/** + * \function igraph_rng_get_unif01 + * \brief Samples uniformly from the unit interval. + * + * Generates uniformly distributed real numbers from the [0, 1) + * half-open interval. + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \return The generated uniformly distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_unif01(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + if (type->get_real) { + return type->get_real(rng->state); + } else { + /* We extract 52 random bits from a 64-bit uint and fill that directly + * into the mantissa of a double, bit-by-bit, clear the sign bit and + * set the exponent to 2^0. This way we get a 52-bit random double + * between 1 (inclusive) and 2 (exclusive), uniformly distributed. + * Then we subtract 1 to arrive at the [0; 1) interval. This is fast + * but we lose one bit of precision as there are 2^53 possible doubles + * between 0 and 1. */ + union { + uint64_t as_uint64_t; + double as_double; + } value; + value.as_uint64_t = + (igraph_i_rng_get_random_bits_uint64(rng, 52) & 0xFFFFFFFFFFFFFull) | 0x3FF0000000000000ull; + return value.as_double - 1.0; + } +} + +/** + * \function igraph_rng_get_geom + * \brief Samples from a geometric distribution. + * + * Generates random variates from a geometric distribution. The number \c k is + * generated with probability + * + * + * (1 - p)^k p, k = 0, 1, 2, .... + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param p The probability of success in each trial. Must be larger + * than zero and smaller or equal to 1. + * \return The generated geometrically distributed random number. + * + * Time complexity: depends on the RNG. + */ + +igraph_real_t igraph_rng_get_geom(igraph_rng_t *rng, igraph_real_t p) { + const igraph_rng_type_t *type = rng->type; + if (!isfinite(p) || p <= 0 || p > 1) { + return IGRAPH_NAN; + } + if (type->get_geom) { + return type->get_geom(rng->state, p); + } else { + return igraph_rng_get_pois(rng, igraph_i_exp_rand(rng) * ((1 - p) / p)); + } +} + +/** + * \function igraph_rng_get_binom + * \brief Samples from a binomial distribution. + * + * Generates random variates from a binomial distribution. The number \c k is generated + * with probability + * + * + * (n \choose k) p^k (1-p)^(n-k), k = 0, 1, ..., n. + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param n Number of observations. + * \param p Probability of an event. + * \return The generated binomially distributed random number. + * + * Time complexity: depends on the RNG. + */ + +igraph_real_t igraph_rng_get_binom(igraph_rng_t *rng, igraph_int_t n, igraph_real_t p) { + const igraph_rng_type_t *type = rng->type; + if (type->get_binom) { + return type->get_binom(rng->state, n, p); + } else { + return igraph_i_rbinom(rng, n, p); + } +} + +/** + * \function igraph_rng_get_gamma + * \brief Samples from a gamma distribution. + * + * Generates random variates from a gamma distribution with probability + * density proportional to + * + * + * x^(shape-1) exp(-x / scale). + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param shape Shape parameter. + * \param scale Scale parameter. + * \return The generated sample. + * + * Time complexity: depends on the RNG. + */ + +igraph_real_t igraph_rng_get_gamma(igraph_rng_t *rng, igraph_real_t shape, + igraph_real_t scale) { + const igraph_rng_type_t *type = rng->type; + if (type->get_gamma) { + return type->get_gamma(rng->state, shape, scale); + } else { + return igraph_i_rgamma(rng, shape, scale); + } +} + +/** + * \function igraph_rng_get_exp + * \brief Samples from an exponential distribution. + * + * Generates random variates from an exponential distribution with probability + * density proportional to + * + * + * exp(-rate x). + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param rate Rate parameter. + * \return The generated sample. + * + * Time complexity: depends on the RNG. + */ + +igraph_real_t igraph_rng_get_exp(igraph_rng_t *rng, igraph_real_t rate) { + const igraph_rng_type_t *type = rng->type; + if (type->get_exp) { + return type->get_exp(rng->state, rate); + } else { + return igraph_i_rexp(rng, rate); + } +} + +/** + * \function igraph_rng_get_pois + * \brief Samples from a Poisson distribution. + * + * Generates random variates from a Poisson distribution. The number \c k is generated + * with probability + * + * + * rate^k * exp(-rate) / k!, k = 0, 1, 2, .... + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param rate The rate parameter of the Poisson distribution. Must not be negative. + * \return The generated geometrically distributed random number. + * + * Time complexity: depends on the RNG. + */ + +igraph_real_t igraph_rng_get_pois(igraph_rng_t *rng, igraph_real_t rate) { + const igraph_rng_type_t *type = rng->type; + if (isnan(rate) || rate < 0) { + return IGRAPH_NAN; + } else if (rate == 0) { + return 0; + } else if (type->get_pois) { + return type->get_pois(rng->state, rate); + } else { + return igraph_i_rpois(rng, rate); + } +} + + +/** + * \ingroup internal + * + * This function appends the rest of the needed random numbers to the + * result vector. It is Algoirthm A in Vitter's paper. + */ + +static void igraph_i_random_sample_alga(igraph_vector_int_t *res, + igraph_int_t l, igraph_int_t h, + igraph_int_t length) { + /* Vitter: Variables V, quot, Nreal, and top are of type real */ + + igraph_int_t N = h - l + 1; + igraph_int_t n = length; + + igraph_real_t top = N - n; + igraph_real_t Nreal = N; + igraph_int_t S = 0; + igraph_real_t V, quot; + + l = l - 1; + + while (n >= 2) { + V = RNG_UNIF01(); + S = 1; + quot = top / Nreal; + while (quot > V) { + S += 1; + top = -1.0 + top; + Nreal = -1.0 + Nreal; + quot = (quot * top) / Nreal; + } + l += S; + igraph_vector_int_push_back(res, l); /* allocated */ + Nreal = -1.0 + Nreal; n = -1 + n; + } + + S = trunc(round(Nreal) * RNG_UNIF01()); + l += S + 1; + igraph_vector_int_push_back(res, l); /* allocated */ +} + +/** + * \ingroup nongraph + * \function igraph_random_sample + * \brief Generates an increasing random sequence of integers. + * + * This function generates an increasing sequence of random integer + * numbers from a given interval. The algorithm is taken literally + * from (Vitter 1987). This method can be used for generating numbers from a + * \em very large interval. It is primarily created for randomly + * selecting some edges from the sometimes huge set of possible edges + * in a large graph. + * + * + * Reference: + * + * + * J. S. Vitter. An efficient algorithm for sequential random sampling. + * ACM Transactions on Mathematical Software, 13(1):58--67, 1987. + * https://doi.org/10.1145/23002.23003 + * + * \param res Pointer to an initialized vector. This will hold the + * result. It will be resized to the proper size. + * \param l The lower limit of the generation interval (inclusive). This must + * be less than or equal to the upper limit, and it must be integral. + * \param h The upper limit of the generation interval (inclusive). This must + * be greater than or equal to the lower limit, and it must be integral. + * \param length The number of random integers to generate. + * \return The error code \c IGRAPH_EINVAL is returned in each of the + * following cases: (1) The given lower limit is greater than the + * given upper limit, i.e. \c l > \c h. (2) Assuming that + * \c l < \c h and N is the sample size, the above error code is + * returned if N > |\c h - \c l|, i.e. the sample size exceeds the + * size of the candidate pool. + * + * Time complexity: according to (Vitter 1987), the expected + * running time is O(length). + * + * \example examples/simple/igraph_random_sample.c + */ + +igraph_error_t igraph_random_sample(igraph_vector_int_t *res, igraph_int_t l, igraph_int_t h, + igraph_int_t length) { + igraph_int_t N; /* := h - l + 1 */ + IGRAPH_SAFE_ADD(h, -l, &N); + IGRAPH_SAFE_ADD(N, 1, &N); + + igraph_int_t n = length; + + igraph_real_t nreal = length; + igraph_real_t ninv = (nreal != 0) ? 1.0 / nreal : 0.0; + igraph_real_t Nreal = N; + igraph_real_t Vprime; + igraph_int_t qu1 = -n + 1 + N; + igraph_real_t qu1real = -nreal + 1.0 + Nreal; + igraph_real_t negalphainv = -13; + igraph_real_t threshold = -negalphainv * n; + igraph_int_t S; + + /* getting back some sense of sanity */ + if (l > h) { + IGRAPH_ERROR("Lower limit is greater than upper limit.", IGRAPH_EINVAL); + } + /* now we know that l <= h */ + if (length > N) { + IGRAPH_ERROR("Sample size exceeds size of candidate pool.", IGRAPH_EINVAL); + } + + /* treat rare cases quickly */ + if (l == h) { + IGRAPH_CHECK(igraph_vector_int_resize(res, 1)); + VECTOR(*res)[0] = l; + return IGRAPH_SUCCESS; + } + if (length == 0) { + igraph_vector_int_clear(res); + return IGRAPH_SUCCESS; + } + if (length == N) { + IGRAPH_CHECK(igraph_vector_int_resize(res, length)); + for (igraph_int_t i = 0; i < length; i++) { + VECTOR(*res)[i] = l++; + } + return IGRAPH_SUCCESS; + } + + igraph_vector_int_clear(res); + IGRAPH_CHECK(igraph_vector_int_reserve(res, length)); + + Vprime = exp(log(RNG_UNIF01()) * ninv); + l = l - 1; + + while (n > 1 && threshold < N) { + igraph_real_t X, U; + igraph_real_t limit, t; + igraph_real_t negSreal, y1, y2, top, bottom; + igraph_real_t nmin1inv = 1.0 / (-1.0 + nreal); + while (1) { + while (1) { + X = Nreal * (-Vprime + 1.0); + S = floor(X); + /* if (S==0) { S=1; } */ + if (S < qu1) { + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + U = RNG_UNIF01(); + negSreal = -S; + + y1 = exp(log(U * Nreal / qu1real) * nmin1inv); + Vprime = y1 * (-X / Nreal + 1.0) * (qu1real / (negSreal + qu1real)); + if (Vprime <= 1.0) { + break; + } + + y2 = 1.0; + top = -1.0 + Nreal; + if (-1 + n > S) { + bottom = -nreal + Nreal; + limit = -S + N; + } else { + bottom = -1.0 + negSreal + Nreal; + limit = qu1; + } + for (t = -1 + N; t >= limit; t--) { + y2 = (y2 * top) / bottom; + top = -1.0 + top; + bottom = -1.0 + bottom; + } + if (Nreal / (-X + Nreal) >= y1 * exp(log(y2)*nmin1inv)) { + Vprime = exp(log(RNG_UNIF01()) * nmin1inv); + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + + l += S + 1; + igraph_vector_int_push_back(res, l); /* allocated */ + N = -S + (-1 + N); Nreal = negSreal + (-1.0 + Nreal); + n = -1 + n; nreal = -1.0 + nreal; ninv = nmin1inv; + qu1 = -S + qu1; qu1real = negSreal + qu1real; + threshold = threshold + negalphainv; + } + + if (n > 1) { + igraph_i_random_sample_alga(res, l + 1, h, n); + } else { + S = floor(N * Vprime); + l += S + 1; + igraph_vector_int_push_back(res, l); /* allocated */ + } + + return IGRAPH_SUCCESS; +} + +static void igraph_i_random_sample_alga_real(igraph_vector_t *res, + igraph_real_t l, igraph_real_t h, + igraph_real_t length) { + igraph_real_t N = h - l + 1; + igraph_real_t n = length; + + igraph_real_t top = N - n; + igraph_real_t Nreal = N; + igraph_real_t S = 0; + igraph_real_t V, quot; + + l = l - 1; + + while (n >= 2) { + V = RNG_UNIF01(); + S = 1; + quot = top / Nreal; + while (quot > V) { + S += 1; + top = -1.0 + top; + Nreal = -1.0 + Nreal; + quot = (quot * top) / Nreal; + } + l += S; + igraph_vector_push_back(res, l); /* allocated */ + Nreal = -1.0 + Nreal; n = -1 + n; + } + + S = trunc(round(Nreal) * RNG_UNIF01()); + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ +} + +/** + * \ingroup nongraph + * \function igraph_i_random_sample_real + * \brief Generates an increasing random sequence of integers (igraph_real_t version). + * + * This function is the 'real' version of \ref igraph_random_sample(), and was added + * so \ref igraph_erdos_renyi_game_gnm() and related functions can use a random sample + * of doubles instead of integers to prevent overflows on systems with 32-bit + * \type igraph_int_t. + * + * \param res Pointer to an initialized vector. This will hold the + * result. It will be resized to the proper size. + * \param l The lower limit of the generation interval (inclusive). This must + * be less than or equal to the upper limit, and it must be integral. + * Passing a fractional number here results in undefined behaviour. + * \param h The upper limit of the generation interval (inclusive). This must + * be greater than or equal to the lower limit, and it must be integral. + * Passing a fractional number here results in undefined behaviour. + * \param length The number of random integers to generate. + * \return The error code \c IGRAPH_EINVAL is returned in each of the + * following cases: (1) The given lower limit is greater than the + * given upper limit, i.e. \c l > \c h. (2) Assuming that + * \c l < \c h and N is the sample size, the above error code is + * returned if N > |\c h - \c l|, i.e. the sample size exceeds the + * size of the candidate pool. + */ + +igraph_error_t igraph_i_random_sample_real(igraph_vector_t *res, igraph_real_t l, + igraph_real_t h, igraph_int_t length) { + /* This function is the 'real' version of igraph_random_sample, and was added + * so erdos_renyi_game_gnm can use a random sample of doubles instead of integers + * to prevent overflows on systems with 32-bits igraph_int_t. + */ + igraph_real_t N = h - l + 1; + igraph_real_t n = length; + + igraph_real_t nreal = length; + igraph_real_t ninv = (nreal != 0) ? 1.0 / nreal : 0.0; + igraph_real_t Nreal = N; + igraph_real_t Vprime; + igraph_real_t qu1 = -n + 1 + N; + igraph_real_t qu1real = -nreal + 1.0 + Nreal; + igraph_real_t negalphainv = -13; + igraph_real_t threshold = -negalphainv * n; + igraph_real_t S; + int iter = 0; + + /* getting back some sense of sanity */ + if (l > h) { + IGRAPH_ERROR("Lower limit is greater than upper limit.", IGRAPH_EINVAL); + } + /* now we know that l <= h */ + if (length > N) { + IGRAPH_ERROR("Sample size exceeds size of candidate pool.", IGRAPH_EINVAL); + } + + /* ensure that we work in the range where igraph_real_t can represent integers exactly */ + if (h > IGRAPH_MAX_EXACT_REAL || l < -IGRAPH_MAX_EXACT_REAL || N > IGRAPH_MAX_EXACT_REAL) { + IGRAPH_ERROR("Sampling interval too large.", IGRAPH_EOVERFLOW); + } + + /* treat rare cases quickly */ + if (l == h) { + IGRAPH_CHECK(igraph_vector_resize(res, 1)); + VECTOR(*res)[0] = l; + return IGRAPH_SUCCESS; + } + if (length == 0) { + igraph_vector_clear(res); + return IGRAPH_SUCCESS; + } + if (length == N) { + IGRAPH_CHECK(igraph_vector_resize(res, length)); + for (igraph_int_t i = 0; i < length; i++) { + VECTOR(*res)[i] = l++; + } + return IGRAPH_SUCCESS; + } + + igraph_vector_clear(res); + IGRAPH_CHECK(igraph_vector_reserve(res, length)); + + Vprime = exp(log(RNG_UNIF01()) * ninv); + l = l - 1; + + while (n > 1 && threshold < N) { + igraph_real_t X, U; + igraph_real_t limit, t; + igraph_real_t negSreal, y1, y2, top, bottom; + igraph_real_t nmin1inv = 1.0 / (-1.0 + nreal); + while (1) { + while (1) { + X = Nreal * (-Vprime + 1.0); + S = floor(X); + /* if (S==0) { S=1; } */ + if (S < qu1) { + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + U = RNG_UNIF01(); + negSreal = -S; + + y1 = exp(log(U * Nreal / qu1real) * nmin1inv); + Vprime = y1 * (-X / Nreal + 1.0) * (qu1real / (negSreal + qu1real)); + if (Vprime <= 1.0) { + break; + } + + y2 = 1.0; + top = -1.0 + Nreal; + if (-1 + n > S) { + bottom = -nreal + Nreal; + limit = -S + N; + } else { + bottom = -1.0 + negSreal + Nreal; + limit = qu1; + } + for (t = -1 + N; t >= limit; t--) { + y2 = (y2 * top) / bottom; + top = -1.0 + top; + bottom = -1.0 + bottom; + } + if (Nreal / (-X + Nreal) >= y1 * exp(log(y2)*nmin1inv)) { + Vprime = exp(log(RNG_UNIF01()) * nmin1inv); + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ + N = -S + (-1 + N); Nreal = negSreal + (-1.0 + Nreal); + n = -1 + n; nreal = -1.0 + nreal; ninv = nmin1inv; + qu1 = -S + qu1; qu1real = negSreal + qu1real; + threshold = threshold + negalphainv; + + if (++iter >= (1 << 14)) { + iter = 0; + IGRAPH_ALLOW_INTERRUPTION(); + } + } + + if (n > 1) { + igraph_i_random_sample_alga_real(res, l + 1, h, n); + } else { + S = floor(N * Vprime); + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ + } + + return IGRAPH_SUCCESS; +} + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000 The R Development Core Team + * based on AS 111 (C) 1977 Royal Statistical Society + * and on AS 241 (C) 1988 Royal Statistical Society + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * SYNOPSIS + * + * double qnorm5(double p, double mu, double sigma, + * int lower_tail, int log_p) + * {qnorm (..) is synonymous and preferred inside R} + * + * DESCRIPTION + * + * Compute the quantile function for the normal distribution. + * + * For small to moderate probabilities, algorithm referenced + * below is used to obtain an initial approximation which is + * polished with a final Newton step. + * + * For very large arguments, an algorithm of Wichura is used. + * + * REFERENCE + * + * Beasley, J. D. and S. G. Springer (1977). + * Algorithm AS 111: The percentage points of the normal distribution, + * Applied Statistics, 26, 118-121. + * + * Wichura, M.J. (1988). + * Algorithm AS 241: The Percentage Points of the Normal Distribution. + * Applied Statistics, 37, 477-484. + */ + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998-2004 The R Development Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* The ISNAN macro is used in some of the code borrowed from R below. */ +#define ISNAN isnan + +/* Indicates that we use systems which support NaN values. */ +#define IEEE_754 1 + +/* Private header file for use during compilation of Mathlib */ +#ifndef MATHLIB_PRIVATE_H +#define MATHLIB_PRIVATE_H + +#define ML_POSINF IGRAPH_INFINITY +#define ML_NEGINF -IGRAPH_INFINITY +#define ML_NAN IGRAPH_NAN + +#define ML_ERROR(x) /* nothing */ +#define ML_UNDERFLOW (DBL_MIN * DBL_MIN) +#define ML_VALID(x) (!ISNAN(x)) + +#define ME_NONE 0 +/* no error */ +#define ME_DOMAIN 1 +/* argument out of domain */ +#define ME_RANGE 2 +/* value out of range */ +#define ME_NOCONV 4 +/* process did not converge */ +#define ME_PRECISION 8 +/* does not have "full" precision */ +#define ME_UNDERFLOW 16 +/* and underflow occurred (important for IEEE)*/ + +#define ML_ERR_return_NAN { ML_ERROR(ME_DOMAIN); return ML_NAN; } + +#endif /* MATHLIB_PRIVATE_H */ + + +/* Utilities for `dpq' handling (density/probability/quantile) */ + +/* give_log in "d"; log_p in "p" & "q" : */ +#define give_log log_p +/* "DEFAULT" */ +/* --------- */ +#define R_D__0 (log_p ? ML_NEGINF : 0.) /* 0 */ +#define R_D__1 (log_p ? 0. : 1.) /* 1 */ +#define R_DT_0 (lower_tail ? R_D__0 : R_D__1) /* 0 */ +#define R_DT_1 (lower_tail ? R_D__1 : R_D__0) /* 1 */ + +#define R_D_Lval(p) (lower_tail ? (p) : (1 - (p))) /* p */ +#define R_D_Cval(p) (lower_tail ? (1 - (p)) : (p)) /* 1 - p */ + +#define R_D_val(x) (log_p ? log(x) : (x)) /* x in pF(x,..) */ +#define R_D_qIv(p) (log_p ? exp(p) : (p)) /* p in qF(p,..) */ +#define R_D_exp(x) (log_p ? (x) : exp(x)) /* exp(x) */ +#define R_D_log(p) (log_p ? (p) : log(p)) /* log(p) */ +#define R_D_Clog(p) (log_p ? log1p(-(p)) : (1 - (p)))/* [log](1-p) */ + +/* log(1-exp(x)): R_D_LExp(x) == (log1p(- R_D_qIv(x))) but even more stable:*/ +#define R_D_LExp(x) (log_p ? R_Log1_Exp(x) : log1p(-x)) + +/*till 1.8.x: + * #define R_DT_val(x) R_D_val(R_D_Lval(x)) + * #define R_DT_Cval(x) R_D_val(R_D_Cval(x)) */ +#define R_DT_val(x) (lower_tail ? R_D_val(x) : R_D_Clog(x)) +#define R_DT_Cval(x) (lower_tail ? R_D_Clog(x) : R_D_val(x)) + +/*#define R_DT_qIv(p) R_D_Lval(R_D_qIv(p)) * p in qF ! */ +#define R_DT_qIv(p) (log_p ? (lower_tail ? exp(p) : - expm1(p)) \ + : R_D_Lval(p)) + +/*#define R_DT_CIv(p) R_D_Cval(R_D_qIv(p)) * 1 - p in qF */ +#define R_DT_CIv(p) (log_p ? (lower_tail ? -expm1(p) : exp(p)) \ + : R_D_Cval(p)) + +#define R_DT_exp(x) R_D_exp(R_D_Lval(x)) /* exp(x) */ +#define R_DT_Cexp(x) R_D_exp(R_D_Cval(x)) /* exp(1 - x) */ + +#define R_DT_log(p) (lower_tail? R_D_log(p) : R_D_LExp(p))/* log(p) in qF */ +#define R_DT_Clog(p) (lower_tail? R_D_LExp(p): R_D_log(p))/* log(1-p) in qF*/ +#define R_DT_Log(p) (lower_tail? (p) : R_Log1_Exp(p)) +/* == R_DT_log when we already "know" log_p == true :*/ + +#define R_Q_P01_check(p) \ + if ((log_p && p > 0) || \ + (!log_p && (p < 0 || p > 1)) ) \ + ML_ERR_return_NAN + +/* additions for density functions (C.Loader) */ +#define R_D_fexp(f,x) (give_log ? -0.5*log(f)+(x) : exp(x)/sqrt(f)) +#define R_D_forceint(x) floor((x) + 0.5) +#define R_D_nonint(x) (fabs((x) - floor((x)+0.5)) > 1e-7) +/* [neg]ative or [non int]eger : */ +#define R_D_negInonint(x) (x < 0. || R_D_nonint(x)) + +#define R_D_nonint_check(x) \ + if (R_D_nonint(x)) { \ + MATHLIB_WARNING("non-integer x = %f", x); \ + return R_D__0; \ + } + +static double igraph_i_qnorm5(double p, double mu, double sigma, igraph_bool_t lower_tail, igraph_bool_t log_p) { + double p_, q, r, val; + +#ifdef IEEE_754 + if (ISNAN(p) || ISNAN(mu) || ISNAN(sigma)) { + return p + mu + sigma; + } +#endif + if (p == R_DT_0) { + return ML_NEGINF; + } + if (p == R_DT_1) { + return ML_POSINF; + } + R_Q_P01_check(p); + + if (sigma < 0) { + ML_ERR_return_NAN; + } + if (sigma == 0) { + return mu; + } + + p_ = R_DT_qIv(p);/* real lower_tail prob. p */ + q = p_ - 0.5; + + /*-- use AS 241 --- */ + /* double ppnd16_(double *p, long *ifault)*/ + /* ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3 + + Produces the normal deviate Z corresponding to a given lower + tail area of P; Z is accurate to about 1 part in 10**16. + + (original fortran code used PARAMETER(..) for the coefficients + and provided hash codes for checking them...) + */ + if (fabs(q) <= .425) {/* 0.075 <= p <= 0.925 */ + r = .180625 - q * q; + val = + q * (((((((r * 2509.0809287301226727 + + 33430.575583588128105) * r + 67265.770927008700853) * r + + 45921.953931549871457) * r + 13731.693765509461125) * r + + 1971.5909503065514427) * r + 133.14166789178437745) * r + + 3.387132872796366608) + / (((((((r * 5226.495278852854561 + + 28729.085735721942674) * r + 39307.89580009271061) * r + + 21213.794301586595867) * r + 5394.1960214247511077) * r + + 687.1870074920579083) * r + 42.313330701600911252) * r + 1.); + } else { /* closer than 0.075 from {0,1} boundary */ + + /* r = min(p, 1-p) < 0.075 */ + if (q > 0) { + r = R_DT_CIv(p); /* 1-p */ + } else { + r = p_; /* = R_DT_Iv(p) ^= p */ + } + + r = sqrt(- ((log_p && + ((lower_tail && q <= 0) || (!lower_tail && q > 0))) ? + p : /* else */ log(r))); + /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ + + if (r <= 5.) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ + r += -1.6; + val = (((((((r * 7.7454501427834140764e-4 + + .0227238449892691845833) * r + .24178072517745061177) * + r + 1.27045825245236838258) * r + + 3.64784832476320460504) * r + 5.7694972214606914055) * + r + 4.6303378461565452959) * r + + 1.42343711074968357734) + / (((((((r * + 1.05075007164441684324e-9 + 5.475938084995344946e-4) * + r + .0151986665636164571966) * r + + .14810397642748007459) * r + .68976733498510000455) * + r + 1.6763848301838038494) * r + + 2.05319162663775882187) * r + 1.); + } else { /* very close to 0 or 1 */ + r += -5.; + val = (((((((r * 2.01033439929228813265e-7 + + 2.71155556874348757815e-5) * r + + .0012426609473880784386) * r + .026532189526576123093) * + r + .29656057182850489123) * r + + 1.7848265399172913358) * r + 5.4637849111641143699) * + r + 6.6579046435011037772) + / (((((((r * + 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * + r + 1.8463183175100546818e-5) * r + + 7.868691311456132591e-4) * r + .0148753612908506148525) + * r + .13692988092273580531) * r + + .59983220655588793769) * r + 1.); + } + + if (q < 0.0) { + val = -val; + } + /* return (q >= 0.)? r : -r ;*/ + } + return mu + sigma * val; +} + +static igraph_int_t imax2(igraph_int_t x, igraph_int_t y) { + return (x < y) ? y : x; +} + +static igraph_int_t imin2(igraph_int_t x, igraph_int_t y) { + return (x < y) ? x : y; +} + +static double igraph_i_norm_rand(igraph_rng_t *rng) { + double r; + + /* Use the inversion method based on uniform variates from (0, 1). + * We exclude 0.0 as it would lead to generating -infinity. + * It is assumed that unif01() provides sufficient accuracy. + * A resolution of 2^-32 may not be sufficient. igraph's default + * implementaton provides an accuracy of 2^-52. + */ + do { + r = igraph_rng_get_unif01(rng); + } while (r == 0.0); + + return igraph_i_qnorm5(r, 0.0, 1.0, true, false); +} + +/* + * The following function is igraph code (not R / Mathlib). + * + * We use simple inverse transform sampling, with the assumption that the + * quality/resolution of uniform variates is high (52 bits in the default + * implementation). The quantile function is -log(1 - r) but given that + * r is sampled uniformly form the unit interval, -log(r) is equivalent. + * r = 0 is disallowed as it would yield infinity. + */ + +static double igraph_i_exp_rand(igraph_rng_t *rng) { + igraph_real_t r = igraph_rng_get_unif01(rng); + if (r == 0.0) r = 1.0; /* sample from (0, 1] instead of [0, 1) */ + return -log(r); +} + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000-2001 The R Development Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * SYNOPSIS + * + * #include + * double rpois(double lambda) + * + * DESCRIPTION + * + * Random variates from the Poisson distribution. + * + * REFERENCE + * + * Ahrens, J.H. and Dieter, U. (1982). + * Computer generation of Poisson deviates + * from modified normal distributions. + * ACM Trans. Math. Software 8, 163-179. + */ + +#define a0 -0.5 +#define a1 0.3333333 +#define a2 -0.2500068 +#define a3 0.2000118 +#define a4 -0.1661269 +#define a5 0.1421878 +#define a6 -0.1384794 +#define a7 0.1250060 + +#define one_7 0.1428571428571428571 +#define one_12 0.0833333333333333333 +#define one_24 0.0416666666666666667 + +#define repeat for(;;) + +#define M_1_SQRT_2PI 0.398942280401432677939946059934 /* 1/sqrt(2pi) */ + +static double igraph_i_rpois(igraph_rng_t *rng, double mu) { + /* Factorial Table (0:9)! */ + const double fact[10] = { + 1., 1., 2., 6., 24., 120., 720., 5040., 40320., 362880. + }; + + /* These are static --- persistent between calls for same mu : */ + static IGRAPH_THREAD_LOCAL int l; + static IGRAPH_THREAD_LOCAL igraph_int_t m; + + static IGRAPH_THREAD_LOCAL double b1, b2, c, c0, c1, c2, c3; + static IGRAPH_THREAD_LOCAL double pp[36], p0, p, q, s, d, omega; + static IGRAPH_THREAD_LOCAL double big_l;/* integer "w/o overflow" */ + static IGRAPH_THREAD_LOCAL double muprev = 0., muprev2 = 0.;/*, muold = 0.*/ + + /* Local Vars [initialize some for -Wall]: */ + double del, difmuk = 0., E = 0., fk = 0., fx, fy, g, px, py, t, u = 0., v, x; + double pois = -1.; + int k, big_mu; + bool new_big_mu = false; + bool kflag; + + if (!isfinite(mu) || mu < 0) { + ML_ERR_return_NAN; + } + + if (mu <= 0.) { + return 0.; + } + + big_mu = mu >= 10.; + if (big_mu) { + new_big_mu = false; + } + + if (!(big_mu && mu == muprev)) {/* maybe compute new persistent par.s */ + + if (big_mu) { + new_big_mu = true; + /* Case A. (recalculation of s,d,l because mu has changed): + * The Poisson probabilities pk exceed the discrete normal + * probabilities fk whenever k >= m(mu). + */ + muprev = mu; + s = sqrt(mu); + d = 6. * mu * mu; + big_l = floor(mu - 1.1484); + /* = an upper bound to m(mu) for all mu >= 10.*/ + } else { /* Small mu ( < 10) -- not using normal approx. */ + + /* Case B. (start new table and calculate p0 if necessary) */ + + /*muprev = 0.;-* such that next time, mu != muprev ..*/ + if (mu != muprev) { + muprev = mu; + m = imax2(1, (igraph_int_t) mu); + l = 0; /* pp[] is already ok up to pp[l] */ + q = p0 = p = exp(-mu); + } + + repeat { + /* Step U. uniform sample for inversion method */ + u = igraph_rng_get_unif01(rng); + if (u <= p0) { + return 0.; + } + + /* Step T. table comparison until the end pp[l] of the + pp-table of cumulative Poisson probabilities + (0.458 > ~= pp[9](= 0.45792971447) for mu=10 ) */ + if (l != 0) { + for (k = (u <= 0.458) ? 1 : imin2(l, m); k <= l; k++) + if (u <= pp[k]) { + return (double)k; + } + if (l == 35) { /* u > pp[35] */ + continue; + } + } + /* Step C. creation of new Poisson + probabilities p[l..] and their cumulatives q =: pp[k] */ + l++; + for (k = l; k <= 35; k++) { + p *= mu / k; + q += p; + pp[k] = q; + if (u <= q) { + l = k; + return (double)k; + } + } + l = 35; + } /* end(repeat) */ + }/* mu < 10 */ + + } /* end {initialize persistent vars} */ + + /* Only if mu >= 10 : ----------------------- */ + + /* Step N. normal sample */ + g = mu + s * igraph_i_norm_rand(rng);/* norm_rand() ~ N(0,1), standard normal */ + + if (g >= 0.) { + pois = floor(g); + /* Step I. immediate acceptance if pois is large enough */ + if (pois >= big_l) { + return pois; + } + /* Step S. squeeze acceptance */ + fk = pois; + difmuk = mu - fk; + u = igraph_rng_get_unif01(rng); /* ~ U(0,1) - sample */ + if (d * u >= difmuk * difmuk * difmuk) { + return pois; + } + } + + /* Step P. preparations for steps Q and H. + (recalculations of parameters if necessary) */ + + if (new_big_mu || mu != muprev2) { + /* Careful! muprev2 is not always == muprev + because one might have exited in step I or S + */ + muprev2 = mu; + omega = M_1_SQRT_2PI / s; + /* The quantities b1, b2, c3, c2, c1, c0 are for the Hermite + * approximations to the discrete normal probabilities fk. */ + + b1 = one_24 / mu; + b2 = 0.3 * b1 * b1; + c3 = one_7 * b1 * b2; + c2 = b2 - 15. * c3; + c1 = b1 - 6. * b2 + 45. * c3; + c0 = 1. - b1 + 3. * b2 - 15. * c3; + c = 0.1069 / mu; /* guarantees majorization by the 'hat'-function. */ + } + + if (g >= 0.) { + /* 'Subroutine' F is called (kflag=0 for correct return) */ + kflag = false; + goto Step_F; + } + + + repeat { + /* Step E. Exponential Sample */ + + E = igraph_i_exp_rand(rng);/* ~ Exp(1) (standard exponential) */ + + /* sample t from the laplace 'hat' + (if t <= -0.6744 then pk < fk for all mu >= 10.) */ + u = 2 * igraph_rng_get_unif01(rng) - 1.; + t = 1.8 + copysign(E, u); + if (t > -0.6744) { + pois = floor(mu + s * t); + fk = pois; + difmuk = mu - fk; + + /* 'subroutine' F is called (kflag=1 for correct return) */ + kflag = true; + +Step_F: /* 'subroutine' F : calculation of px,py,fx,fy. */ + + if (pois < 10) { /* use factorials from table fact[] */ + px = -mu; + py = pow(mu, pois) / fact[(int)pois]; + } else { + /* Case pois >= 10 uses polynomial approximation + a0-a7 for accuracy when advisable */ + del = one_12 / fk; + del = del * (1. - 4.8 * del * del); + v = difmuk / fk; + if (fabs(v) <= 0.25) + px = fk * v * v * (((((((a7 * v + a6) * v + a5) * v + a4) * + v + a3) * v + a2) * v + a1) * v + a0) + - del; + else { /* |v| > 1/4 */ + px = fk * log(1. + v) - difmuk - del; + } + py = M_1_SQRT_2PI / sqrt(fk); + } + x = (0.5 - difmuk) / s; + x *= x;/* x^2 */ + fx = -0.5 * x; + fy = omega * (((c3 * x + c2) * x + c1) * x + c0); + if (kflag) { + /* Step H. Hat acceptance (E is repeated on rejection) */ + if (c * fabs(u) <= py * exp(px + E) - fy * exp(fx + E)) { + break; + } + } else + /* Step Q. Quotient acceptance (rare case) */ + if (fy - u * fy <= py * exp(px - fx)) { + break; + } + }/* t > -.67.. */ + } + return pois; +} + +#undef a1 +#undef a2 +#undef a3 +#undef a4 +#undef a5 +#undef a6 +#undef a7 + +/* This is from nmath/rbinom.c */ + +#define repeat for(;;) + +static double igraph_i_rbinom(igraph_rng_t *rng, igraph_int_t n, double pp) { + + static IGRAPH_THREAD_LOCAL double c, fm, npq, p1, p2, p3, p4, qn; + static IGRAPH_THREAD_LOCAL double xl, xll, xlr, xm, xr; + + static IGRAPH_THREAD_LOCAL double psave = -1.0; + static IGRAPH_THREAD_LOCAL igraph_int_t nsave = -1; + static IGRAPH_THREAD_LOCAL igraph_int_t m; + + double f, f1, f2, u, v, w, w2, x, x1, x2, z, z2; + double p, q, np, g, r, al, alv, amaxp, ffm, ynorm; + igraph_int_t i, ix, k; + + if (!isfinite(pp) || + /* n=0, p=0, p=1 are not errors */ + n < 0 || pp < 0. || pp > 1.) { + ML_ERR_return_NAN; + } + + if (n == 0 || pp == 0.) { + return 0; + } + if (pp == 1.) { + return n; + } + + p = fmin(pp, 1. - pp); + q = 1. - p; + np = n * p; + r = p / q; + g = r * (n + 1); + + /* Setup, perform only when parameters change [using static (globals): */ + + /* FIXING: Want this thread safe + -- use as little (thread globals) as possible + */ + if (pp != psave || n != nsave) { + psave = pp; + nsave = n; + if (np < 30.0) { + /* inverse cdf logic for mean less than 30 */ + qn = pow(q, (double) n); + goto L_np_small; + } else { + ffm = np + p; + m = ffm; + fm = m; + npq = np * q; + /* Note (igraph): Original code used a cast to (int) for rounding. However, + * the max npq = n*p*(1-p) value is 0.25*n, thus 2.195 * sqrt(npq) may be + * as large as 1.0975 * sqrt(n). This is not representable on a 32-bit signed + * integer when n is a 64-bit signed integer. Thus we use trunc() instead. */ + p1 = trunc(2.195 * sqrt(npq) - 4.6 * q) + 0.5; + xm = fm + 0.5; + xl = xm - p1; + xr = xm + p1; + c = 0.134 + 20.5 / (15.3 + fm); + al = (ffm - xl) / (ffm - xl * p); + xll = al * (1.0 + 0.5 * al); + al = (xr - ffm) / (xr * q); + xlr = al * (1.0 + 0.5 * al); + p2 = p1 * (1.0 + c + c); + p3 = p2 + c / xll; + p4 = p3 + c / xlr; + } + } else if (n == nsave) { + if (np < 30.0) { + goto L_np_small; + } + } + + /*-------------------------- np = n*p >= 30 : ------------------- */ + repeat { + u = igraph_rng_get_unif01(rng) * p4; + v = igraph_rng_get_unif01(rng); + /* triangular region */ + if (u <= p1) { + ix = xm - p1 * v + u; + goto finis; + } + /* parallelogram region */ + if (u <= p2) { + x = xl + (u - p1) / c; + v = v * c + 1.0 - fabs(xm - x) / p1; + if (v > 1.0 || v <= 0.) { + continue; + } + ix = x; + } else { + if (u > p3) { /* right tail */ + ix = xr - log(v) / xlr; + if (ix > n) { + continue; + } + v = v * (u - p3) * xlr; + } else {/* left tail */ + ix = xl + log(v) / xll; + if (ix < 0) { + continue; + } + v = v * (u - p2) * xll; + } + } + /* determine appropriate way to perform accept/reject test */ + k = imaxabs(ix - m); + if (k <= 20 || k >= npq / 2 - 1) { + /* explicit evaluation */ + f = 1.0; + if (m < ix) { + for (i = m + 1; i <= ix; i++) { + f *= (g / i - r); + } + } else if (m != ix) { + for (i = ix + 1; i <= m; i++) { + f /= (g / i - r); + } + } + if (v <= f) { + goto finis; + } + } else { + /* squeezing using upper and lower bounds on log(f(x)) */ + amaxp = (k / npq) * ((k * (k / 3. + 0.625) + 0.1666666666666) / npq + 0.5); + ynorm = -k * k / (2.0 * npq); + alv = log(v); + if (alv < ynorm - amaxp) { + goto finis; + } + if (alv <= ynorm + amaxp) { + /* Stirling's formula to machine accuracy */ + /* for the final acceptance/rejection test */ + x1 = ix + 1; + f1 = fm + 1.0; + z = n + 1 - fm; + w = n - ix + 1.0; + z2 = z * z; + x2 = x1 * x1; + f2 = f1 * f1; + w2 = w * w; + if (alv <= xm * log(f1 / x1) + (n - m + 0.5) * log(z / w) + (ix - m) * log(w * p / (x1 * q)) + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / f2) / f2) / f2) / f2) / f1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / z2) / z2) / z2) / z2) / z / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / x2) / x2) / x2) / x2) / x1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / w2) / w2) / w2) / w2) / w / 166320.) { + goto finis; + } + } + } + } + +L_np_small: + /*---------------------- np = n*p < 30 : ------------------------- */ + + repeat { + ix = 0; + f = qn; + u = igraph_rng_get_unif01(rng); + repeat { + if (u < f) { + goto finis; + } + if (ix > 110) { + break; + } + u -= f; + ix++; + f *= (g / ix - r); + } + } +finis: + if (psave > 0.5) { + ix = n - ix; + } + return (double)ix; +} + +static igraph_real_t igraph_i_rexp(igraph_rng_t *rng, double rate) { + igraph_real_t scale = 1.0 / rate; + if (!isfinite(scale) || scale <= 0.0) { + if (scale == 0.0) { + return 0.0; + } + return IGRAPH_NAN; + } + return scale * igraph_i_exp_rand(rng); +} + +/* This is from nmath/rgamma.c */ + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000--2008 The R Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, a copy is available at + * http://www.r-project.org/Licenses/ + * + * SYNOPSIS + * + * #include + * double rgamma(double a, double scale); + * + * DESCRIPTION + * + * Random variates from the gamma distribution. + * + * REFERENCES + * + * [1] Shape parameter a >= 1. Algorithm GD in: + * + * Ahrens, J.H. and Dieter, U. (1982). + * Generating gamma variates by a modified + * rejection technique. + * Comm. ACM, 25, 47-54. + * + * + * [2] Shape parameter 0 < a < 1. Algorithm GS in: + * + * Ahrens, J.H. and Dieter, U. (1974). + * Computer methods for sampling from gamma, beta, + * poisson and binomial distributions. + * Computing, 12, 223-246. + * + * Input: a = parameter (mean) of the standard gamma distribution. + * Output: a variate from the gamma(a)-distribution + */ + +static double igraph_i_rgamma(igraph_rng_t *rng, double a, double scale) { + /* Constants : */ + static const double sqrt32 = 5.656854; + static const double exp_m1 = 0.36787944117144232159;/* exp(-1) = 1/e */ + + /* Coefficients q[k] - for q0 = sum(q[k]*a^(-k)) + * Coefficients a[k] - for q = q0+(t*t/2)*sum(a[k]*v^k) + * Coefficients e[k] - for exp(q)-1 = sum(e[k]*q^k) + */ + static const double q1 = 0.04166669; + static const double q2 = 0.02083148; + static const double q3 = 0.00801191; + static const double q4 = 0.00144121; + static const double q5 = -7.388e-5; + static const double q6 = 2.4511e-4; + static const double q7 = 2.424e-4; + + static const double a1 = 0.3333333; + static const double a2 = -0.250003; + static const double a3 = 0.2000062; + static const double a4 = -0.1662921; + static const double a5 = 0.1423657; + static const double a6 = -0.1367177; + static const double a7 = 0.1233795; + + /* State variables: */ + static IGRAPH_THREAD_LOCAL double aa = 0.; + static IGRAPH_THREAD_LOCAL double aaa = 0.; + static IGRAPH_THREAD_LOCAL double s, s2, d; /* no. 1 (step 1) */ + static IGRAPH_THREAD_LOCAL double q0, b, si, c;/* no. 2 (step 4) */ + + double e, p, q, r, t, u, v, w, x, ret_val; + + if (!isfinite(a) || !isfinite(scale) || a < 0.0 || scale <= 0.0) { + if (scale == 0.) { + return 0.; + } + ML_ERR_return_NAN; + } + + if (a < 1.) { /* GS algorithm for parameters a < 1 */ + if (a == 0) { + return 0.; + } + e = 1.0 + exp_m1 * a; + repeat { + p = e * igraph_rng_get_unif01(rng); + if (p >= 1.0) { + x = -log((e - p) / a); + if (igraph_i_exp_rand(rng) >= (1.0 - a) * log(x)) { + break; + } + } else { + x = exp(log(p) / a); + if (igraph_i_exp_rand(rng) >= x) { + break; + } + } + } + return scale * x; + } + + /* --- a >= 1 : GD algorithm --- */ + + /* Step 1: Recalculations of s2, s, d if a has changed */ + if (a != aa) { + aa = a; + s2 = a - 0.5; + s = sqrt(s2); + d = sqrt32 - s * 12.0; + } + /* Step 2: t = standard normal deviate, + x = (s,1/2) -normal deviate. */ + + /* immediate acceptance (i) */ + t = igraph_i_norm_rand(rng); + x = s + 0.5 * t; + ret_val = x * x; + if (t >= 0.0) { + return scale * ret_val; + } + + /* Step 3: u = 0,1 - uniform sample. squeeze acceptance (s) */ + u = igraph_rng_get_unif01(rng); + if (d * u <= t * t * t) { + return scale * ret_val; + } + + /* Step 4: recalculations of q0, b, si, c if necessary */ + + if (a != aaa) { + aaa = a; + r = 1.0 / a; + q0 = ((((((q7 * r + q6) * r + q5) * r + q4) * r + q3) * r + + q2) * r + q1) * r; + + /* Approximation depending on size of parameter a */ + /* The constants in the expressions for b, si and c */ + /* were established by numerical experiments */ + + if (a <= 3.686) { + b = 0.463 + s + 0.178 * s2; + si = 1.235; + c = 0.195 / s - 0.079 + 0.16 * s; + } else if (a <= 13.022) { + b = 1.654 + 0.0076 * s2; + si = 1.68 / s + 0.275; + c = 0.062 / s + 0.024; + } else { + b = 1.77; + si = 0.75; + c = 0.1515 / s; + } + } + /* Step 5: no quotient test if x not positive */ + + if (x > 0.0) { + /* Step 6: calculation of v and quotient q */ + v = t / (s + s); + if (fabs(v) <= 0.25) + q = q0 + 0.5 * t * t * ((((((a7 * v + a6) * v + a5) * v + a4) * v + + a3) * v + a2) * v + a1) * v; + else { + q = q0 - s * t + 0.25 * t * t + (s2 + s2) * log(1.0 + v); + } + + + /* Step 7: quotient acceptance (q) */ + if (log(1.0 - u) <= q) { + return scale * ret_val; + } + } + + repeat { + /* Step 8: e = standard exponential deviate + * u = 0,1 -uniform deviate + * t = (b,si)-double exponential (laplace) sample */ + e = igraph_i_exp_rand(rng); + u = igraph_rng_get_unif01(rng); + u = u + u - 1.0; + if (u < 0.0) { + t = b - si * e; + } else { + t = b + si * e; + } + /* Step 9: rejection if t < tau(1) = -0.71874483771719 */ + if (t >= -0.71874483771719) { + /* Step 10: calculation of v and quotient q */ + v = t / (s + s); + if (fabs(v) <= 0.25) + q = q0 + 0.5 * t * t * + ((((((a7 * v + a6) * v + a5) * v + a4) * v + a3) * v + + a2) * v + a1) * v; + else { + q = q0 - s * t + 0.25 * t * t + (s2 + s2) * log(1.0 + v); + } + /* Step 11: hat acceptance (h) */ + /* (if q not positive go to step 8) */ + if (q > 0.0) { + w = expm1(q); + /* ^^^^^ original code had approximation with rel.err < 2e-7 */ + /* if t is rejected sample again at step 8 */ + if (c * fabs(u) <= w * exp(e - 0.5 * t * t)) { + break; + } + } + } + } /* repeat .. until `t' is accepted */ + x = s + 0.5 * t; + return scale * x * x; +} diff --git a/src/random/random_device.cpp b/src/random/random_device.cpp new file mode 100644 index 0000000..2a79a6b --- /dev/null +++ b/src/random/random_device.cpp @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + +#include "random/random_internal.h" + +#include +#include + +/* This function attempts to produce an unpredictable number suitable for + * seeding the RNG upon startup. It makes an effort to avoid producing the + * same number when called within different processes, even if called + * approximately at the same time. However, it cannot guarantee this on + * all systems under all circumstancs. + * + * This function cannot fail. + */ +igraph_uint_t igraph_i_get_random_seed(void) { + try { + // Try to use C++'s std::random_device. This may fail, either because + // the systems's random device ran out of entropy or because the system + // does not offer this functionality. + return std::random_device()(); + } catch (...) { + // If random_device fails, use a time-based seed. A combination of + // time() (calendar time, low resolution) and clock() (processor time + // used, higher resolution) reduces the risk of seed collisions. + return igraph_uint_t(std::clock()) + igraph_uint_t(std::time(NULL)); + } +} diff --git a/src/random/random_internal.h b/src/random/random_internal.h new file mode 100644 index 0000000..3a3c3af --- /dev/null +++ b/src/random/random_internal.h @@ -0,0 +1,35 @@ +/* + igraph library. + Copyright (C) 2021-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + +#ifndef IGRAPH_RANDOM_INTERNAL_H +#define IGRAPH_RANDOM_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_random_sample_real( + igraph_vector_t *res, igraph_real_t l, igraph_real_t h, + igraph_int_t length); + +igraph_uint_t igraph_i_get_random_seed(void); + +IGRAPH_END_C_DECLS + +#endif diff --git a/src/random/rng_glibc2.c b/src/random/rng_glibc2.c new file mode 100644 index 0000000..fd9cf41 --- /dev/null +++ b/src/random/rng_glibc2.c @@ -0,0 +1,144 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_random.h" + +#include "igraph_memory.h" +#include "igraph_types.h" + +typedef struct { + int i, j; + long int x[31]; +} igraph_i_rng_glibc2_state_t; + +static unsigned long int igraph_i_rng_glibc2_get(int *i, int *j, int n, long int *x) { + unsigned long int k; + + /* The original implementation used x[*i] += x[*j] here. Considering that + * x is signed, this is undefined behaviour according to the C standard. + * Therefore, we temporarily cast to unsigned long int to achieve what the + * original intention was */ + x[*i] = ((unsigned long int)x[*i]) + ((unsigned long int)x[*j]); + k = (x[*i] >> 1) & 0x7FFFFFFF; + + (*i)++; + if (*i == n) { + *i = 0; + } + + (*j)++ ; + if (*j == n) { + *j = 0; + } + + return k; +} + +static igraph_uint_t igraph_rng_glibc2_get(void *vstate) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + return igraph_i_rng_glibc2_get(&state->i, &state->j, 31, state->x); +} + +/* this function is independent of the bit size */ + +static void igraph_i_rng_glibc2_init(long int *x, int n, + unsigned long int s) { + int i; + + if (s == 0) { + s = 1; + } + + x[0] = (long) s; + for (i = 1 ; i < n ; i++) { + const long int h = s / 127773; + const long int t = 16807 * ((long) s - h * 127773) - h * 2836; + if (t < 0) { + s = (unsigned long) t + 2147483647 ; + } else { + s = (unsigned long) t ; + } + + x[i] = s ; + } +} + +static igraph_error_t igraph_rng_glibc2_seed(void *vstate, igraph_uint_t seed) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + int i; + + igraph_i_rng_glibc2_init(state->x, 31, (unsigned long) seed); + + state->i = 3; + state->j = 0; + + for (i = 0; i < 10 * 31; i++) { + igraph_rng_glibc2_get(state); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_rng_glibc2_init(void **state) { + igraph_i_rng_glibc2_state_t *st; + + st = IGRAPH_CALLOC(1, igraph_i_rng_glibc2_state_t); + IGRAPH_CHECK_OOM(st, "Cannot initialize GNU libc 2 RNG."); + (*state) = st; + + igraph_rng_glibc2_seed(st, 0); + + return IGRAPH_SUCCESS; +} + +static void igraph_rng_glibc2_destroy(void *vstate) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + IGRAPH_FREE(state); +} + +/** + * \var igraph_rngtype_glibc2 + * \brief The random number generator introduced in GNU libc 2. + * + * This is a linear feedback shift register generator with a 128-byte + * buffer. This generator was the default prior to igraph version 0.6, + * at least on systems relying on GNU libc. + * + * This generator was ported from the GNU Scientific Library. It is a + * reimplementation and does not call the system glibc generator. + */ + +const igraph_rng_type_t igraph_rngtype_glibc2 = { + /* name= */ "LIBC", + /* bits= */ 31, + /* init= */ igraph_rng_glibc2_init, + /* destroy= */ igraph_rng_glibc2_destroy, + /* seed= */ igraph_rng_glibc2_seed, + /* get= */ igraph_rng_glibc2_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL +}; diff --git a/src/random/rng_mt19937.c b/src/random/rng_mt19937.c new file mode 100644 index 0000000..c30988a --- /dev/null +++ b/src/random/rng_mt19937.c @@ -0,0 +1,179 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_random.h" + +#include "igraph_memory.h" +#include "igraph_types.h" + +#include /* memset() */ +#include + + +#define N 624 /* Period parameters */ +#define M 397 + +/* most significant w-r bits */ +static const uint32_t UPPER_MASK = UINT32_C(0x80000000); + +/* least significant r bits */ +static const uint32_t LOWER_MASK = UINT32_C(0x7fffffff); + +typedef struct { + uint32_t mt[N]; + int mti; +} igraph_i_rng_mt19937_state_t; + +static igraph_uint_t igraph_rng_mt19937_get(void *vstate) { + igraph_i_rng_mt19937_state_t *state = vstate; + + uint32_t k; + uint32_t *const mt = state->mt; + +#define MAGIC(y) (((y) & 0x1) ? UINT32_C(0x9908b0df) : 0) + + if (state->mti >= N) { + /* generate N words at one time */ + int kk; + + for (kk = 0; kk < N - M; kk++) { + uint32_t y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + M] ^ (y >> 1) ^ MAGIC(y); + } + for (; kk < N - 1; kk++) { + uint32_t y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ MAGIC(y); + } + + { + uint32_t y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK); + mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ MAGIC(y); + } + + state->mti = 0; + } + +#undef MAGIC + + /* Tempering */ + + k = mt[state->mti]; + k ^= (k >> 11); + k ^= (k << 7) & UINT32_C(0x9d2c5680); + k ^= (k << 15) & UINT32_C(0xefc60000); + k ^= (k >> 18); + + state->mti++; + + return k; +} + +static igraph_error_t igraph_rng_mt19937_seed(void *vstate, igraph_uint_t seed) { + igraph_i_rng_mt19937_state_t *state = vstate; + int i; + + memset(state, 0, sizeof(igraph_i_rng_mt19937_state_t)); + + if (seed == 0) { + seed = 4357; /* the default seed is 4357 */ + } + state->mt[0] = seed & UINT32_C(0xffffffff); + + for (i = 1; i < N; i++) { + /* See Knuth's "Art of Computer Programming" Vol. 2, 3rd + Ed. p.106 for multiplier. */ + state->mt[i] = + (UINT32_C(1812433253) * (state->mt[i - 1] ^ (state->mt[i - 1] >> 30)) + + (uint32_t) i); + state->mt[i] &= UINT32_C(0xffffffff); + } + + state->mti = i; + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_rng_mt19937_init(void **state) { + igraph_i_rng_mt19937_state_t *st; + + st = IGRAPH_CALLOC(1, igraph_i_rng_mt19937_state_t); + IGRAPH_CHECK_OOM(st, "Cannot initialize MT19937 RNG."); + (*state) = st; + + igraph_rng_mt19937_seed(st, 0); + + return IGRAPH_SUCCESS; +} + +static void igraph_rng_mt19937_destroy(void *vstate) { + igraph_i_rng_mt19937_state_t *state = + (igraph_i_rng_mt19937_state_t*) vstate; + IGRAPH_FREE(state); +} + +/** + * \var igraph_rngtype_mt19937 + * \brief The MT19937 random number generator. + * + * The MT19937 generator of Makoto Matsumoto and Takuji Nishimura is a + * variant of the twisted generalized feedback shift-register + * algorithm, and is known as the “Mersenne Twister” generator. It has + * a Mersenne prime period of 2^19937 - 1 (about 10^6000) and is + * equi-distributed in 623 dimensions. It has passed the diehard + * statistical tests. It uses 624 words of state per generator and is + * comparable in speed to the other generators. The original generator + * used a default seed of 4357 and choosing \c s equal to zero in + * \c igraph_rng_mt19937_seed() reproduces this. Later versions switched to + * 5489 as the default seed, you can choose this explicitly via + * \ref igraph_rng_seed() instead if you require it. + * + * + * For more information see, + * Makoto Matsumoto and Takuji Nishimura, “Mersenne Twister: A + * 623-dimensionally equidistributed uniform pseudorandom number + * generator”. ACM Transactions on Modeling and Computer Simulation, + * Vol. 8, No. 1 (Jan. 1998), Pages 3–30 + * + * + * The generator \c igraph_rngtype_mt19937 uses the second revision of the + * seeding procedure published by the two authors above in 2002. The + * original seeding procedures could cause spurious artifacts for some + * seed values. + * + * + * This generator was ported from the GNU Scientific Library. + */ + +const igraph_rng_type_t igraph_rngtype_mt19937 = { + /* name= */ "MT19937", + /* bits= */ 32, + /* init= */ igraph_rng_mt19937_init, + /* destroy= */ igraph_rng_mt19937_destroy, + /* seed= */ igraph_rng_mt19937_seed, + /* get= */ igraph_rng_mt19937_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL +}; + +#undef N +#undef M diff --git a/src/random/rng_pcg32.c b/src/random/rng_pcg32.c new file mode 100644 index 0000000..d8990a1 --- /dev/null +++ b/src/random/rng_pcg32.c @@ -0,0 +1,122 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_random.h" + +#include "igraph_memory.h" +#include "igraph_types.h" + +#include "pcg/pcg_variants.h" + +/* The original implementation of the 32-bit PCG random number generator in this + * file was obtained from https://github.com/imneme/pcg-c + * + * PCG is dual-licensed under Apache-2.0 and MIT Licenses. MIT is compatible + * with igraph's GPLv2 license. License notices for PCG are to be found in the + * pcg_variants.h header + */ + +static const pcg32_random_t pcg32_initializer = PCG32_INITIALIZER; + +static igraph_uint_t igraph_rng_pcg32_get(void *vstate) { + pcg32_random_t *state = (pcg32_random_t*) vstate; + return pcg32_random_r(state); +} + +static igraph_error_t igraph_rng_pcg32_seed(void *vstate, igraph_uint_t seed) { + pcg32_random_t *state = (pcg32_random_t*) vstate; + + /* PCG32 is seeded by a 64-bit state and a 64-bit sequence number (well, only + * 63 bits are used from the sequence number, though). Since the unified + * igraph RNG seeding interface provides a single igraph_uint_t as the seed, + * we use the seed to fill in the sequence number and use the state from + * PCG32_INITIALIZER */ + if (seed == 0) { + /* If you feel the temptation to unify the two branches by running + * seed = pcg32_initializer.inc >> 1, don't. + * seed is an igraph_uint_t, so it can be 32-bit or 64-bit. + * pcg32_initializer.inc is always 64-bit. + */ + pcg32_srandom_r(state, pcg32_initializer.state, pcg32_initializer.inc >> 1); + } else { + pcg32_srandom_r(state, pcg32_initializer.state, seed); + } + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_rng_pcg32_init(void **state) { + pcg32_random_t *st; + + st = IGRAPH_CALLOC(1, pcg32_random_t); + IGRAPH_CHECK_OOM(st, "Cannot initialize PCG32 RNG."); + (*state) = st; + + igraph_rng_pcg32_seed(st, 0); + + return IGRAPH_SUCCESS; +} + +static void igraph_rng_pcg32_destroy(void *vstate) { + pcg32_random_t *state = (pcg32_random_t*) vstate; + IGRAPH_FREE(state); +} + +/** + * \var igraph_rngtype_pcg32 + * \brief The PCG random number generator (32-bit version). + * + * This is an implementation of the PCG random number generator; see + * https://www.pcg-random.org for more details. This implementation returns + * 32 random bits in a single iteration. + * + * + * The generator was ported from the original source code published by the + * authors at https://github.com/imneme/pcg-c. + */ + +const igraph_rng_type_t igraph_rngtype_pcg32 = { + /* name= */ "PCG32", + /* bits= */ 32, + /* init= */ igraph_rng_pcg32_init, + /* destroy= */ igraph_rng_pcg32_destroy, + /* seed= */ igraph_rng_pcg32_seed, + /* get= */ igraph_rng_pcg32_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL +}; + +/***** Default RNG, used upon igraph startup *****/ + +#define addr(a) (&a) + +static pcg32_random_t igraph_i_rng_default_state = PCG32_INITIALIZER; + +igraph_rng_t igraph_i_rng_default = { + addr(igraph_rngtype_pcg32), + addr(igraph_i_rng_default_state), + /* is_seeded = */ false +}; + +#undef addr diff --git a/src/random/rng_pcg64.c b/src/random/rng_pcg64.c new file mode 100644 index 0000000..c504094 --- /dev/null +++ b/src/random/rng_pcg64.c @@ -0,0 +1,138 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_random.h" + +#include "igraph_memory.h" +#include "igraph_types.h" + +#include "config.h" + +/* The original implementation of the 64-bit PCG random number generator in this + * file was obtained from https://github.com/imneme/pcg-c + * + * PCG is dual-licensed under Apache-2.0 and MIT Licenses. MIT is compatible + * with igraph's GPLv2 license. License notices for PCG are to be found in the + * pcg_variants.h header + */ + +#if IGRAPH_INTEGER_SIZE == 64 && defined(HAVE___UINT128_T) + +#include "pcg/pcg_variants.h" + +static const pcg64_random_t pcg64_initializer = PCG64_INITIALIZER; + +static igraph_uint_t igraph_rng_pcg64_get(void *vstate) { + pcg64_random_t *state = (pcg64_random_t*) vstate; + return pcg64_random_r(state); +} + +static igraph_error_t igraph_rng_pcg64_seed(void *vstate, igraph_uint_t seed) { + pcg64_random_t *state = (pcg64_random_t*) vstate; + + if (seed == 0) { + seed = (pcg64_initializer.inc >> 1); + } + + /* PCG64 is seeded by a 128-bit state and a 128-bit sequence number (well, only + * 63 bits are used from the sequence number, though). Since the unified + * igraph RNG seeding interface provides a single igraph_uint_t as the seed, + * we use the seed to fill in the sequence number and use the state from + * PCG64_INITIALIZER */ + pcg64_srandom_r(state, pcg64_initializer.state, seed); + + return IGRAPH_SUCCESS; +} + +static igraph_error_t igraph_rng_pcg64_init(void **state) { + pcg64_random_t *st; + + st = IGRAPH_CALLOC(1, pcg64_random_t); + IGRAPH_CHECK_OOM(st, "Cannot initialize PCG64 RNG."); + (*state) = st; + + igraph_rng_pcg64_seed(st, 0); + + return IGRAPH_SUCCESS; +} + +static void igraph_rng_pcg64_destroy(void *vstate) { + pcg64_random_t *state = (pcg64_random_t*) vstate; + IGRAPH_FREE(state); +} + +#else + +/* Dummy implementation if the compiler does not support __uint128_t */ + +static igraph_uint_t igraph_rng_pcg64_get(void *vstate) { + IGRAPH_UNUSED(vstate); + return 0; +} + +static igraph_error_t igraph_rng_pcg64_seed(void *vstate, igraph_uint_t seed) { + IGRAPH_UNUSED(vstate); IGRAPH_UNUSED(seed); + IGRAPH_ERROR("64-bit PCG generator needs __uint128_t.", IGRAPH_UNIMPLEMENTED); +} + +static igraph_error_t igraph_rng_pcg64_init(void **state) { + IGRAPH_UNUSED(state); + IGRAPH_ERROR("64-bit PCG generator needs __uint128_t.", IGRAPH_UNIMPLEMENTED); +} + +static void igraph_rng_pcg64_destroy(void *vstate) { + IGRAPH_UNUSED(vstate); +} + +#endif + +/** + * \var igraph_rngtype_pcg64 + * \brief The PCG random number generator (64-bit version). + * + * This is an implementation of the PCG random number generator; see + * https://www.pcg-random.org for more details. This implementation returns + * 64 random bits in a single iteration. It is only available on 64-bit plaforms + * with compilers that provide the __uint128_t type. + * + * + * PCG64 typically provides better performance than PCG32 when sampling floating + * point numbers or very large integers, as it can provide twice as many random + * bits in a single generation round. + * + * + * The generator was ported from the original source code published by the + * authors at https://github.com/imneme/pcg-c. + */ + +const igraph_rng_type_t igraph_rngtype_pcg64 = { + /* name= */ "PCG64", + /* bits= */ 64, + /* init= */ igraph_rng_pcg64_init, + /* destroy= */ igraph_rng_pcg64_destroy, + /* seed= */ igraph_rng_pcg64_seed, + /* get= */ igraph_rng_pcg64_get, + /* get_int= */ NULL, + /* get_real= */ NULL, + /* get_norm= */ NULL, + /* get_geom= */ NULL, + /* get_binom= */ NULL, + /* get_exp= */ NULL, + /* get_gamma= */ NULL, + /* get_pois= */ NULL +}; diff --git a/src/random/sampling.c b/src/random/sampling.c new file mode 100644 index 0000000..3d51ab6 --- /dev/null +++ b/src/random/sampling.c @@ -0,0 +1,192 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_sampling.h" + +/** + * \function igraph_rng_sample_sphere_surface + * \brief Sample points uniformly from the surface of a sphere. + * + * The center of the sphere is at the origin. + * + * \param rng The random number generator to use. + * \param dim The dimension of the random vectors. + * \param n The number of vectors to sample. + * \param radius Radius of the sphere, it must be positive. + * \param positive Whether to restrict sampling to the positive + * orthant. + * \param res Pointer to an initialized matrix, the result is + * stored here, each column will be a sampled vector. The matrix is + * resized, as needed. + * \return Error code. + * + * Time complexity: O(n*dim*g), where g is the time complexity of + * generating a standard normal random number. + * + * \sa \ref igraph_rng_sample_sphere_volume(), \ref + * igraph_rng_sample_dirichlet() for other similar samplers. + */ +igraph_error_t igraph_rng_sample_sphere_surface( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res +) { + igraph_int_t i, j; + + if (dim < 2) { + IGRAPH_ERROR("Sphere must be at least two dimensional to sample from " + "surface.", IGRAPH_EINVAL); + } + if (n < 0) { + IGRAPH_ERROR("Number of samples must be non-negative.", IGRAPH_EINVAL); + } + if (radius <= 0) { + IGRAPH_ERROR("Sphere radius must be positive.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, dim, n)); + + for (i = 0; i < n; i++) { + igraph_real_t *col = &MATRIX(*res, 0, i); + igraph_real_t sum = 0.0; + for (j = 0; j < dim; j++) { + col[j] = igraph_rng_get_normal(rng, 0, 1); + sum += col[j] * col[j]; + } + sum = sqrt(sum); + for (j = 0; j < dim; j++) { + col[j] = radius * col[j] / sum; + } + if (positive) { + for (j = 0; j < dim; j++) { + col[j] = fabs(col[j]); + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_rng_sample_sphere_volume + * \brief Sample points uniformly from the volume of a sphere. + * + * The center of the sphere is at the origin. + * + * \param rng The random number generator to use. + * \param dim The dimension of the random vectors. + * \param n The number of vectors to sample. + * \param radius Radius of the sphere, it must be positive. + * \param positive Whether to restrict sampling to the positive + * orthant. + * \param res Pointer to an initialized matrix, the result is + * stored here, each column will be a sampled vector. The matrix is + * resized, as needed. + * \return Error code. + * + * Time complexity: O(n*dim*g), where g is the time complexity of + * generating a standard normal random number. + * + * \sa \ref igraph_rng_sample_sphere_surface(), \ref + * igraph_rng_sample_dirichlet() for other similar samplers. + */ +igraph_error_t igraph_rng_sample_sphere_volume( + igraph_rng_t* rng, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, + igraph_bool_t positive, igraph_matrix_t *res +) { + + igraph_int_t i, j; + + /* Arguments are checked by the following call */ + + IGRAPH_CHECK(igraph_rng_sample_sphere_surface(rng, dim, n, radius, positive, res)); + + for (i = 0; i < n; i++) { + igraph_real_t *col = &MATRIX(*res, 0, i); + igraph_real_t U = pow(igraph_rng_get_unif01(rng), 1.0 / dim); + for (j = 0; j < dim; j++) { + col[j] *= U; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_rng_sample_dirichlet + * \brief Sample points from a Dirichlet distribution. + * + * \param rng The random number generator to use. + * \param n The number of vectors to sample. + * \param alpha The parameters of the Dirichlet distribution. They + * must be positive. The length of this vector gives the dimension + * of the generated samples. + * \param res Pointer to an initialized matrix, the result is stored + * here, one sample in each column. It will be resized, as needed. + * \return Error code. + * + * Time complexity: O(n * dim * g), where dim is the dimension of the + * sample vectors, set by the length of alpha, and g is the time + * complexity of sampling from a Gamma distribution. + * + * \sa \ref igraph_rng_sample_sphere_surface() and + * \ref igraph_rng_sample_sphere_volume() for other methods to sample + * latent vectors. + */ +igraph_error_t igraph_rng_sample_dirichlet( + igraph_rng_t* rng, igraph_int_t n, const igraph_vector_t *alpha, + igraph_matrix_t *res +) { + + igraph_int_t len = igraph_vector_size(alpha); + igraph_int_t i, j; + igraph_real_t sum, num; + + if (n < 0) { + IGRAPH_ERRORF("Number of samples should be non-negative, got %" IGRAPH_PRId ".", + IGRAPH_EINVAL, n); + } + + if (len < 2) { + IGRAPH_ERRORF("Dirichlet parameter vector too short, must " + "have at least two entries, got %" IGRAPH_PRId + ".", IGRAPH_EINVAL, len); + } + + if (igraph_vector_min(alpha) <= 0) { + IGRAPH_ERRORF("Dirichlet concentration parameters must be positive, got %g.", + IGRAPH_EINVAL, igraph_vector_min(alpha)); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, len, n)); + + for (i = 0; i < n; i++) { + for (j = 0, sum = 0.0; j < len; j++) { + num = igraph_rng_get_gamma(rng, VECTOR(*alpha)[j], 1.0); + sum += num; + MATRIX(*res, j, i) = num; + } + for (j = 0; j < len; j++) { + MATRIX(*res, j, i) /= sum; + } + } + + return IGRAPH_SUCCESS; +} diff --git a/src/spatial/beta_skeleton.cpp b/src/spatial/beta_skeleton.cpp new file mode 100644 index 0000000..20baaa8 --- /dev/null +++ b/src/spatial/beta_skeleton.cpp @@ -0,0 +1,816 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_spatial.h" + +#include "igraph_constructors.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "core/exceptions.h" +#include "spatial/nanoflann_internal.hpp" +#include "spatial/spatial_internal.h" +#include + +#define TOLERANCE (128 * DBL_EPSILON) + +// Some methods to get distances between two vectors, including when one or both are embedded in a matrix. +static inline igraph_real_t ind_ind_sqr_distance(igraph_int_t a, igraph_int_t b, + const igraph_matrix_t *points) { + igraph_real_t distance = 0; + igraph_int_t dims = igraph_matrix_ncol(points); + for (igraph_int_t i = 0; i < dims; i++) { + igraph_real_t temp = MATRIX(*points, a, i) - MATRIX(*points, b, i); + distance += temp * temp; + } + return distance; +} + +static inline igraph_real_t vec_vec_sqr_dist(const igraph_vector_t *a, const igraph_vector_t *b) { + igraph_real_t distance = 0; + igraph_int_t dims = igraph_vector_size(a); + for (igraph_int_t i = 0; i < dims; i++) { + igraph_real_t temp = VECTOR(*a)[i] - VECTOR(*b)[i]; + distance += temp * temp; + } + return distance; +} + +static inline igraph_real_t vec_vec_sqr_dist(const std::vector &a, const std::vector &b) { + igraph_real_t distance = 0; + igraph_int_t dims = a.size(); + for (igraph_int_t i = 0; i < dims; i++) { + igraph_real_t temp = a[i] - b[i]; + distance += temp * temp; + } + return distance; +} + +static inline igraph_real_t vec_ind_sqr_dist(const igraph_vector_t *a, const igraph_int_t b, const igraph_matrix_t *points) { + igraph_real_t distance = 0; + igraph_int_t dims = igraph_matrix_ncol(points); + for (igraph_int_t i = 0; i < dims; i++) { + igraph_real_t temp = VECTOR(*a)[i] - MATRIX(*points, b, i); + distance += temp * temp; + } + return distance; +} +static inline igraph_real_t vec_ind_sqr_dist(const std::vector &a, const igraph_int_t b, const igraph_matrix_t *points) { + igraph_real_t distance = 0; + igraph_int_t dims = igraph_matrix_ncol(points); + for (igraph_int_t i = 0; i < dims; i++) { + igraph_real_t temp = a[i] - MATRIX(*points, b, i); + distance += temp * temp; + } + return distance; +} + +// Adapted from code by Szabolcs at https://github.com/szhorvat/IGraphM +// Used in is_union_empty +class NeighborCounts { + const igraph_real_t radius; // L2 search radius + const igraph_int_t a, b; // edge endpoints; excluded from the count + igraph_bool_t short_circuit; + igraph_int_t found; + +public: + // Boilerplate for nanoflann + using DistanceType = igraph_real_t; + NeighborCounts(igraph_real_t radius, igraph_int_t a, igraph_int_t b, igraph_bool_t short_circuit) + : radius(radius), a(a), b(b), short_circuit(short_circuit) { + init(); + } + + void init() { + clear(); + } + + void clear() { + found = 0; + } + + size_t size() const { + return found; + } + + bool full() const { + return true; + } + + void sort() const {} + + igraph_real_t worstDist() const { + return radius; + } + + // Business logic + // Add the point if it's close enough + bool addPoint(igraph_real_t dist, igraph_int_t index) { + if (dist < radius && index != a && index != b) { + found += 1; + if (short_circuit) { + // Don't continue searching if it only matters if there's one or more. + return false; + } + } + return true; + } +}; + +// Helper result class for listing the points in the intersection of two spheres +// and counting how many are within the lune. +// Used in is_intersection_empty. +// Adapted from code by Szabolcs at https://github.com/szhorvat/IGraphM. +class IntersectionCounts { + const igraph_real_t radius; // Half height of intersection lune + const igraph_real_t beta_radius; // Radius of circles + const igraph_bool_t short_circuit; // Whether to stop after one point has been found + const igraph_int_t a, b; // Edge endpoints; excluded from the count. + const std::vector &a_center, &b_center; // Circle centers. + const igraph_matrix_t *points; // List of points, used to test if points are in range + size_t count; // how many are found. +public: + // Boilerplate for nanoflann + using DistanceType = igraph_real_t; + IntersectionCounts( + igraph_real_t radius_, igraph_real_t beta_radius_, + bool short_circuit_, + igraph_int_t a, igraph_int_t b, const std::vector &a_center, const std::vector &b_center, const igraph_matrix_t *points) + : radius(radius_), beta_radius(beta_radius_), + short_circuit(short_circuit_), + a(a), b(b), + a_center(a_center), b_center(b_center), points(points) { + init(); + } + + void init() { + clear(); + } + + void clear() { + count = 0; + } + + size_t size() const { + return count; + } + + bool full() const { + return true; + } + + void sort() const {} + + igraph_real_t worstDist() const { + return radius; + } + + // Business logic is all contained here. + // Count the point if it is within the radius from both centers, and if it's not one of the endpoints. + bool addPoint(igraph_real_t dist, igraph_int_t index) { + if (dist < radius && index != a && index != b) { + igraph_real_t pd1 = vec_ind_sqr_dist(a_center, index, points); + igraph_real_t pd2 = vec_ind_sqr_dist(b_center, index, points); + if (pd1 < beta_radius && pd2 < beta_radius) { + count++; + // Stop searching if it only matters to have at least one point. + if (short_circuit) { + return false; + } + } + } + return true; + } +}; + +// Shrinks type signatures significantly. +template +using KDTree = nanoflann::KDTreeSingleIndexAdaptor < + nanoflann::L2_Adaptor, + ig_point_adaptor, Dimension, igraph_int_t >; + +// give a known good superset of edges for a given value of beta. +// In the case of beta < 1, that is a complete graph, for +// beta >= 1 it is the delaunay triangulation of the points. +static igraph_error_t beta_skeleton_edge_superset(igraph_vector_int_t *edges, + const igraph_matrix_t *points, + igraph_real_t beta) { + + igraph_int_t num_points = igraph_matrix_nrow(points); + igraph_int_t num_dims = igraph_matrix_ncol(points); + + if (beta >= 1 && num_points > num_dims) { + // Large beta and enough points, subset of delaunay. + IGRAPH_CHECK(igraph_i_delaunay_edges(edges, points)); + } else { + // Small beta, not subset of Delaunay, give complete graph. + // Or Delaunay not calculable due to point count + // TODO: update when/if Delaunay supports small numbers. + igraph_int_t numpoints = igraph_matrix_nrow(points); + for (igraph_int_t a = 0; a < numpoints - 1; a++) { + for (igraph_int_t b = a + 1; b < numpoints; b++) { + IGRAPH_CHECK(igraph_vector_int_push_back(edges, a)); + IGRAPH_CHECK(igraph_vector_int_push_back(edges, b)); + } + } + } + return IGRAPH_SUCCESS; +} + +static inline igraph_real_t calculate_r(igraph_real_t beta) { + if (beta < 1) { + return 0.5 / beta; + } + return 0.5 * beta; +} + + +/* -!- center construction interface -!- + * igraph_vector_t *a_center, b_center : Expects an initialized vector of the + * correct size, will be written to. + * + * igraph_int_t a, b: the indices of the points, + * + * igraph_real_t r: the circles will be constructed with radius r * (distance a-> b) + * + * const igraph_matrix_t *points: point set containing the points a and b + */ + +typedef void CenterConstructor( + std::vector &a_centre, + std::vector &b_centre, + igraph_int_t a, + igraph_int_t b, + igraph_real_t beta, + const igraph_matrix_t *points); + +// construct the centers of the points for lune based beta skeletons with beta +// >= 1. The points lie on the line from a to b, such that the points lie on one +// of the circles. +// formula is a_center = a + (r-1) * (a - b), similar for b_center +static void construct_lune_centers(std::vector &a_centre, + std::vector &b_centre, + igraph_int_t a, + igraph_int_t b, + igraph_real_t r, + const igraph_matrix_t *points) { + igraph_int_t dims = igraph_matrix_ncol(points); + a_centre.resize(dims); + b_centre.resize(dims); + for (igraph_int_t i = 0; i < dims; i++) { + a_centre[i] = MATRIX(*points, a, i) + (r - 1) * (MATRIX(*points, a, i) - MATRIX(*points, b, i)); + b_centre[i] = MATRIX(*points, b, i) + (r - 1) * (MATRIX(*points, b, i) - MATRIX(*points, a, i)); + } +} + +// construct the centers of the circles for beta < 1, or circle based beta +// skeletons. +// Since it relies on a 90-degree rotation around the axis perpendicular +// to the line AB, it is only well-defined in 2d. +static void construct_perp_centers(std::vector &a_centre, + std::vector &b_centre, + igraph_int_t a, + igraph_int_t b, + igraph_real_t r, + const igraph_matrix_t *points) { + igraph_real_t mid[2], perp[2]; + + for (igraph_int_t i = 0; i < 2; i++) { + mid[i] = (MATRIX(*points, a, i) + MATRIX(*points, b, i)) * 0.5; + perp[i] = (MATRIX(*points, a, i) - MATRIX(*points, b, i)) * sqrt(r * r - 0.25); + } + + // Since this is only well-defined for 2d, a manual 90-degree rotation works and is simpler. + // The rotation being x = -y, y = x, 90 degrees counter-clockwise. + igraph_real_t temp = perp[0]; + perp[0] = - perp[1]; + perp[1] = temp; + + a_centre.resize(2); + b_centre.resize(2); + for (igraph_int_t i = 0; i < 2; i++) { + a_centre[i] = mid[i] + perp[i]; + b_centre[i] = mid[i] - perp[i]; + } +} + +/* -!- Filter interface -!- + * igraph_bool_t *result : will be overwritten with the result + * kdTree <-1> &tree : used for nanoflann check, should be + * initialized and filed with points. igraph_int_t a, b : the + * endpoints of the edge being evaluated. igraph_real_t beta : + * parameter for beta-skeletons. const igraph_matrix_t *points: matrix + * containing all the points. + */ +typedef bool FilterFunc( + const KDTree<-1> &tree, + igraph_int_t a, + igraph_int_t b, + const igraph_matrix_t *points, + igraph_real_t beta +); + + +// Test whether the intersection of the two circles as generated +// by center_positions is empty of points except for a and b. +template +static bool is_intersection_empty( + const KDTree<-1> &tree, + igraph_int_t a, + igraph_int_t b, + const igraph_matrix_t *points, + igraph_real_t beta) { + + igraph_real_t r = calculate_r(beta); + igraph_real_t sqr_dist = ind_ind_sqr_distance(a, b, points); + + std::vector midpoint; + igraph_int_t dims = igraph_matrix_ncol(points); + + std::vector a_centre, b_centre; + center_positions(a_centre, b_centre, a, b, r, points); + + midpoint.resize(dims); + for (igraph_int_t i = 0; i < dims; i++) { + midpoint[i] = 0.5 * (a_centre[i] + b_centre[i]); + } + // squared half-height of lune + igraph_real_t lune_height = sqr_dist - vec_vec_sqr_dist(midpoint, a_centre); + igraph_real_t tol = is_closed ? 1 + TOLERANCE : 1 - TOLERANCE; + IntersectionCounts intersections(lune_height * tol * tol, sqr_dist * r * r * tol * tol, true, a, b, a_centre, b_centre, points); + + tree.findNeighbors(intersections, midpoint.data()); + return intersections.size() == 0; +} + +// Test whether the union of the circles given by center_positions +// is empty of other points except for a and b. +template +static bool is_union_empty( + const KDTree<-1> &tree, + igraph_int_t a, + igraph_int_t b, + const igraph_matrix_t *points, + igraph_real_t beta) { + + igraph_real_t sqr_dist = ind_ind_sqr_distance(a, b, points); + igraph_real_t r = calculate_r(beta); + std::vector a_centre, b_centre; + + center_positions(a_centre, b_centre, a, b, r, points); + + NeighborCounts neighbor_search(sqr_dist * r * r * (1 + TOLERANCE), a, b, true); + + tree.findNeighbors(neighbor_search, a_centre.data()); + + if (neighbor_search.size() > 0) { + return false; + } else { + neighbor_search.clear(); + tree.findNeighbors(neighbor_search, b_centre.data()); + return neighbor_search.size() == 0; + } +} + +// Iterate through the edges given, applying the provided filter. +// Currently used with is_intersection_empty and is_union_empty. +// TODO: specialize for multiple dimensions? +template +static igraph_error_t filter_edges(igraph_vector_int_t *edges, const igraph_matrix_t *points, igraph_real_t beta) { + igraph_int_t available_edges = igraph_vector_int_size(edges); + igraph_int_t added_edges = 0; + ig_point_adaptor adaptor(points); + igraph_int_t dim = igraph_matrix_ncol(points); + KDTree<-1> tree(dim, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10)); + tree.buildIndex(); + for (igraph_int_t i = 0; i * 2 < available_edges; i++) { + if (filter(tree, VECTOR(*edges)[2 * i], VECTOR(*edges)[2 * i + 1], points, beta)) { + VECTOR(*edges)[added_edges * 2] = VECTOR(*edges)[i * 2]; + VECTOR(*edges)[added_edges * 2 + 1] = VECTOR(*edges)[i * 2 + 1]; + added_edges += 1; + } + } + IGRAPH_CHECK(igraph_vector_int_resize(edges, added_edges * 2)); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_lune_beta_skeleton + * \brief The lune based β-skeleton of a spatial point set. + * + * \experimental + * + * This function constructs the lune-based β-skeleton of an n-dimensional + * spatial point set. + * + * + * A larger β results in a larger region, and a sparser graph. + * Values of β < 1 are only supported in 2D, and are considerably slower. + * + * + * The Gabriel graph is a special case of beta skeleton where β = 1. + * + * + * The Relative Neighborhood graph is a special case of beta skeleton where + * β approaches + * + * \param graph A pointer to the graph that will be created. + * \param points A matrix containing the points that will be used to create the + * graph. Each row is a point, dimensionality is inferred from the column count. + * + * \return Error code. + * + * Time Complexity: Around O(n^floor(d/2) log n), where n is the number of points + * and d is the dimensionality of the point set. + * + */ +igraph_error_t igraph_lune_beta_skeleton(igraph_t *graph, const igraph_matrix_t *points, igraph_real_t beta) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_vector_int_t potential_edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&potential_edges, 0); + + IGRAPH_CHECK(beta_skeleton_edge_superset(&potential_edges, points, beta)); + // determine filter required based on beta. + if (beta >= 1) { + IGRAPH_CHECK((filter_edges>(&potential_edges, points, beta))); + } else { + if (igraph_matrix_ncol(points) != 2) { + IGRAPH_ERROR("Beta skeletons with beta < 1 are only supported in 2 dimensions.", IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK((filter_edges>(&potential_edges, points, beta))); + } + + IGRAPH_CHECK(igraph_create(graph, &potential_edges, igraph_matrix_nrow(points), false)); + + igraph_vector_int_destroy(&potential_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_circle_beta_skeleton + * \brief The circle based β-skeleton of a 2D spatial point set. + * + * \experimental + * + * This function constructs the circle based β-skeleton of a 2D spatial point set. + * + * + * A larger \p beta value results in a larger region, and a sparser graph. + * Values of beta < 1 are considerably slower + * + * \param graph A pointer to the graph that will be created. + * \param points An n-by-2 matrix containing the points that will be used to + * create the graph. Each row is a point. + * \param beta A positive real value used to parameterize the graph. + * \return Error code. + * + * Time Complexity: Around O(n^floor(d/2) log n), where n is the number of points + * and d is the dimensionality of the point set. + * + */ +igraph_error_t igraph_circle_beta_skeleton(igraph_t *graph, const igraph_matrix_t *points, igraph_real_t beta) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_vector_int_t potential_edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&potential_edges, 0); + + if (igraph_matrix_ncol(points) != 2) { + IGRAPH_ERROR("Circle based beta skeletons are only supported in 2 dimensions.", IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(beta_skeleton_edge_superset(&potential_edges, points, beta)); + if (beta >= 1) { + IGRAPH_CHECK(filter_edges>(&potential_edges, points, beta)); + } else { + IGRAPH_CHECK((filter_edges>(&potential_edges, points, beta))); + } + + IGRAPH_CHECK(igraph_create(graph, &potential_edges, igraph_matrix_nrow(points), false)); + + igraph_vector_int_destroy(&potential_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} + +// Derived from code by Szabolcs at github.com/szhorvat/IGraphM + +class BetaFinder { + const igraph_real_t max_beta; + const igraph_real_t tol; + const igraph_int_t ai, bi; + const igraph_matrix_t *ps; + const igraph_real_t ab2; + + igraph_real_t smallest_beta; + igraph_real_t max_radius; + +public: + using DistanceType = igraph_real_t; + using IndexType = igraph_int_t; + BetaFinder(igraph_real_t max_beta, igraph_real_t tol, igraph_int_t v1, igraph_int_t v2, const igraph_matrix_t *ps) : + max_beta(max_beta), tol(tol), ai(v1), bi(v2), ps(ps), + ab2(ind_ind_sqr_distance(ai, bi, ps)) { + init(); + } + + void init() { + clear(); + } + + void clear() { + smallest_beta = IGRAPH_INFINITY; + max_radius = luneHalfHeight2(max_beta); + } + + bool full() const { + return true; + } + + size_t size() const { + return 1; + } + + igraph_real_t luneHalfHeight2(igraph_real_t beta) const { + if (beta == 0) { + return 0; + } + return (ab2 / 4) * (2 * beta - 1); + } + + // Calculate the beta at which point index would make the edge a-b disappear. + // If calculated beta is under 1, it would not appear in the gabriel graph, so a 0 is returned + // which is to be interpreted as the edge being missing. + igraph_real_t pointBeta(igraph_int_t index) const { + if (index == ai || index == bi) { + return IGRAPH_INFINITY; + } + + igraph_real_t ap2 = ind_ind_sqr_distance(ai, index, ps); + igraph_real_t bp2 = ind_ind_sqr_distance(bi, index, ps); + + if (ap2 > bp2) { + std::swap(ap2, bp2); + } + + igraph_real_t denom = ab2 + ap2 - bp2; + + if (denom <= 0) { + return IGRAPH_INFINITY; + } + + igraph_real_t beta = 2 * ap2 / denom; + return beta < 1 + tol ? 0 : beta; + } + + bool addPoint(igraph_real_t dist, igraph_int_t index) { + if (dist < max_radius) { + igraph_real_t beta = pointBeta(index); + if (beta < smallest_beta && beta < max_beta) { + smallest_beta = beta; + max_radius = luneHalfHeight2(beta); + } + } + + return true; + } + + igraph_real_t worstDist() const { + return max_radius; + } + + void sort() {} + + igraph_real_t thresholdBeta() const { + return smallest_beta; + } +}; + +/** + * \function igraph_beta_weighted_gabriel_graph + * \brief A Gabriel graph, with edges weighted by the β value at which it disappears. + * + * \experimental + * + * This function generates a Gabriel graph, and for each edge of this graph it + * computes the threshold β value at which the edge ceases to be part of the + * lune-based β-skeleton. For edges that continue to be part of β-skeletons + * for arbitrarily large β, \c IGRAPH_INFINITÎ¥ is returned. + * + * + * The \p max_beta cutoff parameter controls the largest β value to consider + * For edges that persist above this β value, \c IGRAPH_INFINITÎ¥ is returned. + * This parameter serves to improve performance: the smaller this cutoff, + * the faster the computation. Pass \c IGRAPH_INFINITY to use no cutoff. + * + * \param graph A pointer to the graph that will be created. + * \param weights Will contain the edge weights corresponding to the edge + * indices from the graph. + * \param points A matrix containing the points that will be used. + * Each row is a point, dimensionality is inferred from the column count. + * There must be no duplicate points. + * \param max_beta Maximum value of beta to search to, higher values will be + * represented as \c IGRAPH_INFINITY. + * \return Error code. + * + * \sa \ref igraph_lune_beta_skeleton() or \ref igraph_circle_beta_skeleton() + * to generate a graph with a given value of beta; \ref igraph_gabriel_graph() + * to only generate a Gabriel graph, without edge weights. + * + * + * Time Complexity: Around O(n^floor(d/2) log n), where n is the number of points + * and d is the dimensionality of the point set. Though large values of max_beta + * can cause long run times if there are edges that disappear only at large betas. + * + */ +igraph_error_t igraph_beta_weighted_gabriel_graph( + igraph_t *graph, + igraph_vector_t *weights, + const igraph_matrix_t *points, + igraph_real_t max_beta) { + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_vector_int_t edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + + igraph_int_t dim = igraph_matrix_ncol(points); + igraph_int_t point_count = igraph_matrix_nrow(points); + ig_point_adaptor adaptor(points); + + IGRAPH_CHECK(igraph_i_delaunay_edges(&edges, points)); + igraph_int_t edge_count = igraph_vector_int_size(&edges) / 2; + + IGRAPH_CHECK(igraph_vector_resize(weights, edge_count)); + + KDTree<-1> tree(dim, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10)); + tree.buildIndex(); + + igraph_vector_t midpoint; + IGRAPH_VECTOR_INIT_FINALLY(&midpoint, dim); + + for (igraph_int_t i = 0; i < edge_count; i++) { + BetaFinder finder(max_beta, TOLERANCE, VECTOR(edges)[2 * i], VECTOR(edges)[2 * i + 1], points); + + for (igraph_int_t axis = 0; axis < dim; axis++) { + VECTOR(midpoint)[axis] = 0.5 * ( + MATRIX(*points, VECTOR(edges)[2 * i], axis) + + MATRIX(*points, VECTOR(edges)[2 * i + 1], axis) + ); + } + + tree.findNeighbors(finder, VECTOR(midpoint)); + VECTOR(*weights)[i] = finder.thresholdBeta(); + } + + // Filter out edges with beta == 0. + igraph_int_t added_edges = 0; + for (igraph_int_t i = 0; i < edge_count; i++) { + if (VECTOR(*weights)[i] != 0) { + VECTOR(*weights)[added_edges] = VECTOR(*weights)[i]; + VECTOR(edges)[added_edges * 2] = VECTOR(edges)[i * 2]; + VECTOR(edges)[added_edges * 2 + 1] = VECTOR(edges)[i * 2 + 1]; + added_edges += 1; + } + } + + IGRAPH_CHECK(igraph_vector_int_resize(&edges, added_edges * 2)); + IGRAPH_CHECK(igraph_vector_resize(weights, added_edges)); + + IGRAPH_CHECK(igraph_create(graph, &edges, point_count, false)); + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&midpoint); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_gabriel_graph + * \brief The Gabriel graph of a point set. + * + * \experimental + * + * In the Gabriel graph of a point set, two points A and B are connected if + * there is no other point C within the closed ball of which AB is a diameter. + * The Gabriel graph is connected, and in 2D it is planar. igraph supports + * computing the Gabriel graph of arbitrary dimensional point sets. + * + * + * The Gabriel graph is a special case of lune-based and circle-based β-skeletons + * with β=1. + * + * \param graph A pointer to the graph to be created. + * \param points The point set that will be used. Each row is a point, + * dimensionality is inferred from column count. + * + * \return Error Code. + * \sa The Gabriel graph is a special case of + * \ref igraph_lune_beta_skeleton() and \ref igraph_circle_beta_skeleton() + * where β = 1. + * + * Time Complexity: Around O(n^floor(d/2) log n), where n is the number of points + * and d is the dimensionality of the point set. + * + */ +igraph_error_t igraph_gabriel_graph(igraph_t *graph, const igraph_matrix_t *points) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_vector_int_t potential_edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&potential_edges, 0); + + IGRAPH_CHECK(beta_skeleton_edge_superset(&potential_edges, points, 1)); + + IGRAPH_CHECK((filter_edges>(&potential_edges, points, 1))); + + IGRAPH_CHECK(igraph_create(graph, &potential_edges, igraph_matrix_nrow(points), false)); + + igraph_vector_int_destroy(&potential_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_relative_neighborhood_graph + * \brief The relative neighborhood graph of a point set. + * + * \experimental + * + * The relative neighborhood graph is constructed from a set of points in space. + * Two points A and B are connected if and only if there is no other point C so + * that AC < AB and BC < AB, with the inequalities being strict. + * + * + * Most authors define the relative neighborhood graph to coincide with a + * lune-based β-skeleton for β = 2. In igraph, there is a subtle + * difference: the β = 2 skeleton connects points A and B when there + * is no point C so that AC <= AB and BC <= AB. Therefore, three points + * forming an equilateral triangle are connected in the relative neighborhood graph, + * but disconnected in the β = 2 skeleton. + * + * + * With these definitions, the relative neighborhood graph is always connected, + * while the β = 2 skeleton is always triangle-free. + * + * \param graph A pointer to the graph that will be created. + * \param points The point set that will be used, each row is a point. + * Dimensionality is inferred from the column number. + * \return Error code. + * + * \sa \ref igraph_lune_beta_skeleton() to compute the lune based β-skeleton + * for β = 2 or other β values. + * + * Time Complexity: Around O(n^floor(d/2) log n), where n is the number of points + * and d is the dimensionality of the point set. + * + */ +igraph_error_t igraph_relative_neighborhood_graph(igraph_t *graph, const igraph_matrix_t *points) { + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + igraph_vector_int_t potential_edges; + IGRAPH_VECTOR_INT_INIT_FINALLY(&potential_edges, 0); + + IGRAPH_CHECK(beta_skeleton_edge_superset(&potential_edges, points, 1)); + + IGRAPH_CHECK((filter_edges>(&potential_edges, points, 2))); + + IGRAPH_CHECK(igraph_create(graph, &potential_edges, igraph_matrix_nrow(points), false)); + + igraph_vector_int_destroy(&potential_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_HANDLE_EXCEPTIONS_END; + + return IGRAPH_SUCCESS; +} diff --git a/src/spatial/convex_hull.c b/src/spatial/convex_hull.c new file mode 100644 index 0000000..f815c3d --- /dev/null +++ b/src/spatial/convex_hull.c @@ -0,0 +1,188 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_spatial.h" + +/** + * \function igraph_convex_hull_2d + * \brief Determines the convex hull of a given set of points in the 2D plane. + * + * + * The convex hull is determined by the Graham scan algorithm. + * See the following reference for details: + * + * + * Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford + * Stein. Introduction to Algorithms, Second Edition. MIT Press and + * McGraw-Hill, 2001. ISBN 0262032937. Pages 949-955 of section 33.3: + * Finding the convex hull. + * + * \param data vector containing the coordinates. The length of the + * vector must be even, since it contains X-Y coordinate pairs. + * \param resverts the vector containing the result, e.g. the vector of + * vertex indices used as the corners of the convex hull. Supply + * \c NULL here if you are only interested in the coordinates of + * the convex hull corners. + * \param rescoords the matrix containing the coordinates of the selected + * corner vertices. Supply \c NULL here if you are only interested in + * the vertex indices. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory + * + * Time complexity: O(n log(n)) where n is the number of vertices. + */ +igraph_error_t igraph_convex_hull_2d( + const igraph_matrix_t *data, + igraph_vector_int_t *resverts, + igraph_matrix_t *rescoords) +{ + igraph_int_t no_of_nodes; + igraph_int_t i, pivot_idx = 0, last_idx, before_last_idx, next_idx, j; + igraph_vector_t angles; + igraph_vector_int_t order, stack; + igraph_real_t px, py, cp; + + no_of_nodes = igraph_matrix_nrow(data); + if (igraph_matrix_ncol(data) != 2) { + IGRAPH_ERROR("Only two-dimensional point sets are supports, matrix must have two columns.", IGRAPH_EINVAL); + } + if (no_of_nodes == 0) { + if (resverts) { + igraph_vector_int_clear(resverts); + } + if (rescoords) { + IGRAPH_CHECK(igraph_matrix_resize(rescoords, 0, 2)); + } + /**************************** this is an exit here *********/ + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&angles, no_of_nodes); + IGRAPH_VECTOR_INT_INIT_FINALLY(&stack, 0); + + /* Search for the pivot vertex */ + for (i = 1; i < no_of_nodes; i++) { + if (MATRIX(*data, i, 1) < MATRIX(*data, pivot_idx, 1)) { + pivot_idx = i; + } else if (MATRIX(*data, i, 1) == MATRIX(*data, pivot_idx, 1) && + MATRIX(*data, i, 0) < MATRIX(*data, pivot_idx, 0)) { + pivot_idx = i; + } + } + px = MATRIX(*data, pivot_idx, 0); + py = MATRIX(*data, pivot_idx, 1); + + /* Create angle array */ + for (i = 0; i < no_of_nodes; i++) { + if (i == pivot_idx) { + /* We can't calculate the angle of the pivot point with itself, + * so we use 10 here. This way, after sorting the angle vector, + * the pivot point will always be the first one, since the range + * of atan2 is -3.14..3.14 */ + VECTOR(angles)[i] = 10; + } else { + VECTOR(angles)[i] = atan2(MATRIX(*data, i, 1) - py, MATRIX(*data, i, 0) - px); + } + } + + /* Sort points by angles */ + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_CHECK(igraph_vector_sort_ind(&angles, &order, IGRAPH_ASCENDING)); + + /* Check if two points have the same angle. If so, keep only the point that + * is farthest from the pivot */ + j = 0; + last_idx = VECTOR(order)[0]; + pivot_idx = VECTOR(order)[no_of_nodes - 1]; + for (i = 1; i < no_of_nodes; i++) { + next_idx = VECTOR(order)[i]; + if (VECTOR(angles)[last_idx] == VECTOR(angles)[next_idx]) { + /* Keep the vertex that is farther from the pivot, drop the one that is + * closer */ + px = pow(MATRIX(*data, last_idx, 0) - MATRIX(*data, pivot_idx, 0), 2) + + pow(MATRIX(*data, last_idx, 1) - MATRIX(*data, pivot_idx, 1), 2); + py = pow(MATRIX(*data, next_idx, 0) - MATRIX(*data, pivot_idx, 0), 2) + + pow(MATRIX(*data, next_idx, 1) - MATRIX(*data, pivot_idx, 1), 2); + if (px > py) { + VECTOR(order)[i] = -1; + } else { + VECTOR(order)[j] = -1; + last_idx = next_idx; + j = i; + } + } else { + last_idx = next_idx; + j = i; + } + } + + j = 0; + last_idx = -1; + before_last_idx = -1; + while (!igraph_vector_int_empty(&order)) { + next_idx = igraph_vector_int_tail(&order); + if (next_idx < 0) { + /* This vertex should be skipped; was excluded in an earlier step */ + igraph_vector_int_pop_back(&order); + continue; + } + /* Determine whether we are at a left or right turn */ + if (j < 2) { + /* Pretend that we are turning into the right direction if we have less + * than two items in the stack */ + cp = -1; + } else { + cp = (MATRIX(*data, last_idx, 0) - MATRIX(*data, before_last_idx, 0)) * + (MATRIX(*data, next_idx, 1) - MATRIX(*data, before_last_idx, 1)) - + (MATRIX(*data, next_idx, 0) - MATRIX(*data, before_last_idx, 0)) * + (MATRIX(*data, last_idx, 1) - MATRIX(*data, before_last_idx, 1)); + } + + if (cp < 0) { + /* We are turning into the right direction */ + igraph_vector_int_pop_back(&order); + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, next_idx)); + before_last_idx = last_idx; + last_idx = next_idx; + j++; + } else { + /* No, skip back and try again in the next iteration */ + igraph_vector_int_pop_back(&stack); + j--; + last_idx = before_last_idx; + before_last_idx = (j >= 2) ? VECTOR(stack)[j - 2] : -1; + } + } + + /* Create result vector */ + if (resverts != 0) { + igraph_vector_int_clear(resverts); + IGRAPH_CHECK(igraph_vector_int_append(resverts, &stack)); + } + if (rescoords != 0) { + igraph_matrix_select_rows(data, rescoords, &stack); + } + + /* Free everything */ + igraph_vector_int_destroy(&order); + igraph_vector_int_destroy(&stack); + igraph_vector_destroy(&angles); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/spatial/delaunay.c b/src/spatial/delaunay.c new file mode 100644 index 0000000..0668aa3 --- /dev/null +++ b/src/spatial/delaunay.c @@ -0,0 +1,280 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_spatial.h" + +#include "igraph_bitset.h" +#include "igraph_constructors.h" +#include "igraph_error.h" +#include "igraph_matrix.h" +#include "igraph_memory.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "internal/utils.h" +#include "spatial/spatial_internal.h" + +#include "qhull/libqhull_r/libqhull_r.h" + +/** + * Raises an error if a spatial point set is invalid. + * The coordinate matrix must have at least one column and must not + * contain NaN or infinities. + * + * \param points Matrix, each row is a spatial point. + * \return Error code. + */ +igraph_error_t igraph_i_check_spatial_points(const igraph_matrix_t *points) { + const igraph_int_t dim = igraph_matrix_ncol(points); + const igraph_int_t n = igraph_matrix_nrow(points); + + /* Special case: we allow zero columns when there are zero rows, i.e. no points. + * Some languages cannot represent size-zero matrices and length-zero point + * lists may translate as a 0-by-0 matrix to igraph. */ + if (dim == 0 && n > 0) { + IGRAPH_ERROR("Point sets must not be zero-dimensional.", IGRAPH_EINVAL); + } + + if (!igraph_vector_is_all_finite(&points->data)) { + IGRAPH_ERROR("Coordinates must not be NaN or infinite.", IGRAPH_EINVAL); + } + + return IGRAPH_SUCCESS; +} + +// Append an undirected clique of the indices in destination to source. +// Assumes that source is an initialized vector. +static igraph_error_t add_clique(igraph_vector_int_t *destination, const igraph_vector_int_t *source) { + igraph_int_t num_points = igraph_vector_int_size(source); + for (igraph_int_t a = 0; a < num_points - 1; a++) { + for (igraph_int_t b = a + 1; b < num_points; b++) { + IGRAPH_CHECK(igraph_vector_int_push_back(destination, VECTOR(*source)[a])); + IGRAPH_CHECK(igraph_vector_int_push_back(destination, VECTOR(*source)[b])); + } + } + return IGRAPH_SUCCESS; +} + + +// Helper that can go on the finally stack to free qhT Qhull data structure in case of an error. +static void destroy_qhull(qhT *qh) { + int curlong, totlong; + qh->NOerrexit = True; /* no more setjmp */ + qh_freeqhull(qh, !qh_ALL); + qh_memfreeshort(qh, &curlong, &totlong); +} + + +/* In the 1D case we simply connect points in sorted order. + * This function assumes that there is at least one point. */ +static igraph_error_t delaunay_edges_1d(igraph_vector_int_t *edges, const igraph_matrix_t *points) { + const igraph_int_t numpoints = igraph_matrix_nrow(points); + const igraph_vector_t coords = igraph_vector_view(&MATRIX(*points, 0, 0), numpoints); + igraph_vector_int_t order; + + IGRAPH_ASSERT(igraph_matrix_ncol(points) == 1); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&order, numpoints); + + IGRAPH_CHECK(igraph_vector_sort_ind(&coords, &order, IGRAPH_ASCENDING)); + + IGRAPH_CHECK(igraph_vector_int_resize(edges, 2*(numpoints-1))); + + for (igraph_int_t i=0; i < numpoints-1; i++) { + igraph_int_t from = VECTOR(order)[i]; + igraph_int_t to = VECTOR(order)[i+1]; + VECTOR(*edges)[2*i] = from; + VECTOR(*edges)[2*i + 1] = to; + if (VECTOR(coords)[from] == VECTOR(coords)[to]) { + IGRAPH_ERROR("Duplicate points for Delaunay triangulation.", IGRAPH_EINVAL); + } + } + + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_i_delaunay_edges(igraph_vector_int_t *edges, const igraph_matrix_t *points) { + const igraph_int_t numpoints = igraph_matrix_nrow(points) ; + const igraph_int_t dim = igraph_matrix_ncol(points); + int exitcode; + qhT qh_qh; /* Qhull's data structure. First argument of most Qhull calls. */ + qhT *qh = &qh_qh; /* Convenience pointer. */ + + /* Error checks */ + + /* Validate point set. */ + IGRAPH_CHECK(igraph_i_check_spatial_points(points)); + + /* No edges for one or zero points. */ + if (numpoints <= 1) { + igraph_vector_int_clear(edges); + return IGRAPH_SUCCESS; + } + + /* Qhull does not support the 1D case. */ + if (dim == 1) { + return delaunay_edges_1d(edges, points); + } + + if (dim >= numpoints) { + IGRAPH_ERRORF("Not enough points to create simplex, need at least %" IGRAPH_PRId ".", IGRAPH_EINVAL, dim); + } + + /* Prevent overflow in igraph_int_t -> int conversions below. + * Note that Qhull will likely already fail for a much smaller point count. */ + if (numpoints > INT_MAX) { + IGRAPH_ERROR("Too many points for Qhull.", IGRAPH_EOVERFLOW); + } + + /* Prepare point set in row-major format for Qhull */ + + coordT *qhull_points = IGRAPH_CALLOC(dim*numpoints, igraph_real_t); + IGRAPH_CHECK_OOM(qhull_points, "Insufficient memory for constructing Delaunay graph."); + IGRAPH_FINALLY(igraph_free, qhull_points); + igraph_matrix_copy_to(points, qhull_points, IGRAPH_ROW_MAJOR); + + /* Call Qhull. + * + * This is mainly based on qdelaunay/qdelaun_r.c and qh_new_qhull() in user_r.c. + * + * Note that output routines in userprintf_r.c are patched to not print + * when passing NULL as a file pointer, which is what we do here. */ + + /* Check for compatible library. Not technically necessary, as igraph + * vendors Qhull due to the need to override output routines. Would + * become necessary if linking to an external Qhull. */ + QHULL_LIB_CHECK + + /* Initializes qh, sets qh->qhull_command. */ + qh_init_A(qh, NULL, NULL, NULL, 0, NULL); + IGRAPH_FINALLY(destroy_qhull, qh); + + exitcode = setjmp(qh->errexit); + if (!exitcode) { + qh->NOerrexit = False; + + /* qh_option() does not change the operation of Qhull. It simply records options to be + * output with error messages. Here we manually keep it in sync with the settings below. */ + qh_option(qh, "delaunay Qz-infinity-point Q3-no-merge-vertices", NULL, NULL); + + qh->PROJECTdelaunay = True; // project points to parabola to calculate delaunay triangulation + qh->DELAUNAY = True; // 'd' + qh->ATinfinity = True; // 'Qz', required for cocircular points + qh->MERGEvertices = False; // 'Q3', do not merge identical vertices + + qh_initflags(qh, qh->qhull_command); + qh_init_B(qh, qhull_points, numpoints, dim, /*ismalloc=*/ False); // read points and project them to parabola. + qh_qhull(qh); // do the triangulation + qh_triangulate(qh); // this guarantees that everything is simplicial + + igraph_vector_int_t simplex; + IGRAPH_VECTOR_INT_INIT_FINALLY(&simplex, dim + 1); // a simplex in n dimensions has n+1 incident vertices. + + facetT *facet; // required for FORALLfacets + vertexT *vertex, **vertexp; // required for FOREACHvertex_ + + FORALLfacets { + if (!facet->upperdelaunay) { + igraph_int_t curr_vert = 0; + FOREACHvertex_(facet->vertices) { + VECTOR(simplex)[curr_vert++] = qh_pointid(qh, vertex->point); + } + IGRAPH_CHECK(add_clique(edges, &simplex)); + } + } + igraph_i_simplify_edge_list(edges, true, true, false); + + /* Check if there are any points/vertices that do not appear in the edge list. + * This happens when there are duplicate points, as Qhull ignores one of them. + * We raise an error when there are duplicates. */ + igraph_int_t edges_size = igraph_vector_int_size(edges); + igraph_bitset_t in_edge_list; + + IGRAPH_BITSET_INIT_FINALLY(&in_edge_list, numpoints); + for (igraph_int_t i = 0; i < edges_size; i++) { + IGRAPH_BIT_SET(in_edge_list, VECTOR(*edges)[i]); + } + if (igraph_bitset_is_any_zero(&in_edge_list)) { + IGRAPH_ERROR("Duplicate points for Delaunay triangulation.", IGRAPH_EINVAL); + } + + igraph_bitset_destroy(&in_edge_list); + igraph_vector_int_destroy(&simplex); + destroy_qhull(qh); + igraph_free(qhull_points); + IGRAPH_FINALLY_CLEAN(4); + } else { + switch (qh->last_errcode) { + /* TODO: More specific error descriptions for common Qhull errors. */ + default: + /* TODO: Report Qhull error text? */ + IGRAPH_ERRORF("Error while computing Delaunay triangulation, Qhull error code %d.", IGRAPH_EINVAL, qh->last_errcode); + } + } + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_delaunay_graph + * \brief Computes the Delaunay graph of a spatial point set. + * + * \experimental + * + * This function constructs the graph corresponding to the Delaunay triangulation + * of an n-dimensional spatial point set. + * + * + * The current implementation uses Qhull. + * + * + * Reference: + * + * + * Barber, C. Bradford, David P. Dobkin, and Hannu Huhdanpaa. + * The Quickhull Algorithm for Convex Hulls. + * ACM Transactions on Mathematical Software 22, no. 4 (1996): 469–83. + * https://doi.org/10.1145/235815.235821. + * + * \param graph A pointer to the graph that will be created. + * \param points A matrix containing the points that will be used to create the graph. + * Each row is a point, dimensionality is inferred from the column count. + * There must not be duplicate points. + * \return Error code. + * + * Time complexity: According to Theorem 3.2 in the Qhull paper, + * O(n log n) for d <= 3 and O(n^floor(d/2) / floor(d/2)!) where + * n is the number of points and d is the dimensionality of the point set. + */ +igraph_error_t igraph_delaunay_graph(igraph_t *graph, const igraph_matrix_t *points) { + igraph_vector_int_t edges; + const igraph_int_t numpoints = igraph_matrix_nrow(points); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_i_delaunay_edges(&edges, points)); + IGRAPH_CHECK(igraph_create(graph, &edges, numpoints, IGRAPH_UNDIRECTED)); + + igraph_vector_int_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/spatial/edge_lengths.c b/src/spatial/edge_lengths.c new file mode 100644 index 0000000..7b5f19e --- /dev/null +++ b/src/spatial/edge_lengths.c @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_spatial.h" + +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_matrix.h" +#include "igraph_vector.h" + +#include /* sqrt, fabs */ + +/** + * \function igraph_spatial_edge_lengths + * \brief Edge lengths based on spatial vertex coordinates. + * + * \experimental + * + * The length of each edge is computed based on spatial coordinates. The length + * can be employed by several igraph functions, such as \ref igraph_voronoi(), + * \ref igraph_betweenness(), \ref igraph_closeness() and others. + * + * \param graph The graph whose edge lengths are to be computed. + * \param lengths An initialized vector. Length will be stored here, in the + * order of edge IDs. It will be resized as needed. + * \param points A matrix of vertex coordinates. Each row contains the + * coordinates of the corresponding vertex, in the order of vertex IDs. + * Arbitrary dimensional point sets are supported. + * \param metric The distance metric to use. See \ref igraph_metric_t for + * valid values. + * \return Error code. + * + * \sa \ref igraph_nearest_neighbor_graph() computes a k nearest neighbor graph + * and \ref igraph_delaunay_graph() computes a Delaunay graph based on a set of + * spatial points. + * + * Time complexity: O(|E| d) where |E| is the number of edges and d is the + * dimensionality of the point set. + */ +igraph_error_t igraph_spatial_edge_lengths( + const igraph_t *graph, + igraph_vector_t *lengths, + const igraph_matrix_t *points, + igraph_metric_t metric) { + + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + const igraph_int_t dim = igraph_matrix_ncol(points); + + /* Validate input. + * + * We opt not to check for Inf/NaN, as these can safely propagate to the result. */ + + if (igraph_matrix_nrow(points) != vcount) { + IGRAPH_ERROR("Number of vertex coordinates must match the vertex count.", IGRAPH_EINVAL); + } + + /* Special case: we allow zero columns when there are zero rows, i.e. no points. + * Some languages cannot represent size-zero matrices and length-zero point + * lists may translate as a 0-by-0 matrix to igraph. */ + if (dim == 0 && vcount > 0) { + IGRAPH_ERROR("Vertex coordinates must not be zero-dimensional.", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_resize(lengths, ecount)); + + /* Validate distance metric. The switch statement is helpful because + * compilers tend to show warnings when some enum values are missing. */ + switch (metric) { + case IGRAPH_METRIC_EUCLIDEAN: + case IGRAPH_METRIC_MANHATTAN: + break; + default: + IGRAPH_ERROR("Invalid distance metric.", IGRAPH_EINVAL); + } + + /* Compute edge lengths. */ + + for (igraph_int_t eid=0; eid < ecount; eid++) { + const igraph_int_t from = IGRAPH_FROM(graph, eid); + const igraph_int_t to = IGRAPH_TO(graph, eid); + igraph_real_t length = 0.0; + + for (igraph_int_t i=0; i < dim; i++) { + igraph_real_t diff = MATRIX(*points, from, i) - MATRIX(*points, to, i); + + switch (metric) { + case IGRAPH_METRIC_EUCLIDEAN: + length += diff*diff; break; + case IGRAPH_METRIC_MANHATTAN: + length += fabs(diff); break; + } + } + + if (metric == IGRAPH_METRIC_EUCLIDEAN) { + length = sqrt(length); + } + + VECTOR(*lengths)[eid] = length; + } + + return IGRAPH_SUCCESS; +} diff --git a/src/spatial/nanoflann_internal.hpp b/src/spatial/nanoflann_internal.hpp new file mode 100644 index 0000000..93f62e0 --- /dev/null +++ b/src/spatial/nanoflann_internal.hpp @@ -0,0 +1,121 @@ +#ifndef SPATIAL_NANOFLANN_INTERNAL_H +#define SPATIAL_NANOFLANN_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +#include "igraph_matrix.h" + +#include "nanoflann/nanoflann.hpp" + +#include + + +class ig_point_adaptor { + const igraph_matrix_t *points; + const igraph_int_t point_count; + +public: + explicit ig_point_adaptor(const igraph_matrix_t *points) : + points(points), point_count(igraph_matrix_nrow(points)) { } + + size_t kdtree_get_point_count() const { + return point_count; + } + + igraph_real_t kdtree_get_pt(const size_t idx, const size_t dim) const { + return MATRIX(*points, idx, dim); + } + template + bool kdtree_get_bbox(BoundingBox &bb) const { + IGRAPH_UNUSED(bb); + return false; // indicates that it should use default + } +}; + + +class GraphBuildingResultSet { + igraph_int_t added_count = 0; + const igraph_real_t max_distance; + const igraph_int_t max_neighbors; + +public: + igraph_int_t current_vertex = 0; + std::vector neighbors; + std::vector distances; + + using DistanceType = igraph_real_t; + using IndexType = igraph_int_t; + + GraphBuildingResultSet(const igraph_int_t max_neighbors, const igraph_real_t max_distance) : + max_distance(max_distance), + max_neighbors(max_neighbors), + neighbors(0), + distances(0) { } + + bool addPoint(const igraph_real_t distance, const igraph_int_t index) { + igraph_int_t i; + + if (index == current_vertex) { + return true; + } + + for (i = added_count; i > 0; i--) { + // TODO: Stabilize result in case of multiple points at exactly the same distance? + // See NANOFLANN_FIRST_MATCH in RKNNResultSet in nanoflann.hpp for reference. + if (distances[i - 1] > distance) { + registerPoint(i, neighbors[i - 1], distances[i - 1]); + } else { + break; + } + } + if (i < max_neighbors) { + registerPoint(i, index, distance); + } + if (added_count != max_neighbors) { + added_count++; + } + return true; + } + + void registerPoint(igraph_int_t where, igraph_int_t index, igraph_real_t distance) { + if (neighbors.size() == where) { + neighbors.push_back(index); + distances.push_back(distance); + } else { + neighbors[where] = index; + distances[where] = distance; + } + + } + + void reset(igraph_int_t current_vertex_) { + added_count = 0; + current_vertex = current_vertex_; + } + + // Never called. Necessary to conform to the interface. + void sort() { } + + igraph_int_t size() const { + return added_count; + } + + bool full() const { + return added_count == max_neighbors; + } + + bool empty() const { + return added_count == 0; + } + + igraph_real_t worstDist() const { + if (added_count < max_neighbors || added_count == 0) { + return max_distance; + } + return distances[added_count - 1]; + } +}; + + +#endif // SPATIAL_NANOFLANN_INTERNAL_H diff --git a/src/spatial/nearest_neighbor.cpp b/src/spatial/nearest_neighbor.cpp new file mode 100644 index 0000000..e1a9c36 --- /dev/null +++ b/src/spatial/nearest_neighbor.cpp @@ -0,0 +1,189 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_spatial.h" + +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_matrix.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +#include "spatial/nanoflann_internal.hpp" + +#include "core/exceptions.h" +#include "core/interruption.h" +#include "spatial/spatial_internal.h" + +#include "nanoflann/nanoflann.hpp" + +#include + +template +static igraph_error_t neighbor_helper( + igraph_t *graph, + const igraph_matrix_t *points, + igraph_int_t k, + igraph_real_t cutoff, + igraph_int_t dimension, + igraph_bool_t directed) { + + const igraph_int_t point_count = igraph_matrix_nrow(points); + ig_point_adaptor adaptor(points); + int iter = 0; + + using kdTree = nanoflann::KDTreeSingleIndexAdaptor; + kdTree tree(dimension, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(10)); + + tree.buildIndex(); + + igraph_vector_t current_point; + IGRAPH_VECTOR_INIT_FINALLY(¤t_point, dimension); + + igraph_int_t neighbor_count = k >= 0 ? k : point_count; + + GraphBuildingResultSet results(neighbor_count, cutoff); + std::vector edges; + for (igraph_int_t i = 0; i < point_count; i++) { + results.reset(i); + IGRAPH_CHECK(igraph_matrix_get_row(points, ¤t_point, i)); + + tree.findNeighbors(results, VECTOR(current_point), nanoflann::SearchParameters(0, false)); + for (igraph_int_t j = 0; j < results.size(); j++) { + edges.push_back(i); + edges.push_back(results.neighbors[j]); + } + + IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 10); + } + + igraph_vector_destroy(¤t_point); + IGRAPH_FINALLY_CLEAN(1); + + // Overflow check, ensures that edges.size() is not too large for igraph vectors. + if (edges.size() > (size_t) IGRAPH_INTEGER_MAX) { + IGRAPH_ERROR("Too many edges.", IGRAPH_EOVERFLOW); + } + + const igraph_vector_int_t edge_view = igraph_vector_int_view(edges.data(), edges.size()); + IGRAPH_CHECK(igraph_create(graph, &edge_view, point_count, true)); + + if (! directed) { + IGRAPH_CHECK(igraph_to_undirected(graph, IGRAPH_TO_UNDIRECTED_COLLAPSE, NULL)); + } + + return IGRAPH_SUCCESS; +} + + +template +static igraph_error_t dimension_dispatcher( + igraph_t *graph, + const igraph_matrix_t *points, + igraph_int_t k, + igraph_real_t cutoff, + igraph_int_t dimension, + igraph_bool_t directed) { + + switch (dimension) { + case 0: + IGRAPH_ERROR("0-dimensional points are not supported.", IGRAPH_EINVAL); + case 1: + return neighbor_helper(graph, points, k, cutoff, dimension, directed); + case 2: + return neighbor_helper(graph, points, k, cutoff, dimension, directed); + case 3: + return neighbor_helper(graph, points, k, cutoff, dimension, directed); + default: + return neighbor_helper(graph, points, k, cutoff, dimension, directed); + } +} + + +/** + * \function igraph_nearest_neighbor_graph + * \brief Computes the nearest neighbor graph for a spatial point set. + * + * \experimental + * + * This function constructs the \p k nearest neighbor graph of a given point + * set. Each point is connected to at most \p k spatial neighbors within a + * radius of \p cutoff. + * + * \param graph A pointer to the graph that will be created. + * \param points A matrix containing the points that will be used to create + * the graph. Each row is a point, dimensionality is inferred from the + * column count. + * \param metric The distance metric to use. See \ref igraph_metric_t. + * \param k At most how many neighbors will be added for each vertex, set to + * a negative value to ignore. + * \param cutoff Maximum distance at which connections will be made, set to a + * negative value or \c IGRAPH_INFINITY to ignore. + * \param directed Whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(n log(n)) where n is the number of points. + */ +igraph_error_t igraph_nearest_neighbor_graph(igraph_t *graph, + const igraph_matrix_t *points, + igraph_metric_t metric, + igraph_int_t k, + igraph_real_t cutoff, + igraph_bool_t directed) { + + const igraph_int_t dimension = igraph_matrix_ncol(points); + + // Negative cutoff values signify that no cutoff should be used. + cutoff = cutoff >= 0 ? cutoff : IGRAPH_INFINITY; + + // Handle null graph separately. + // The number of matrix columns is not meaningful when there are zero rows. + // This prevents throwing an error when both the column and the row counts are zero. + if (igraph_matrix_nrow(points) == 0) { + return igraph_empty(graph, 0, directed); + } + + IGRAPH_CHECK(igraph_i_check_spatial_points(points)); + + IGRAPH_HANDLE_EXCEPTIONS_BEGIN; + + switch (metric) { + case IGRAPH_METRIC_L2: + return dimension_dispatcher >( + graph, + points, + k, + cutoff * cutoff, // L2 uses square distances, so adjust for that here. + dimension, + directed); + case IGRAPH_METRIC_L1: + return dimension_dispatcher >( + graph, + points, + k, + cutoff, + dimension, + directed); + default: + IGRAPH_ERROR("Invalid metric.", IGRAPH_EINVAL); + } + + IGRAPH_HANDLE_EXCEPTIONS_END; +} diff --git a/src/spatial/spatial_internal.h b/src/spatial/spatial_internal.h new file mode 100644 index 0000000..eb42367 --- /dev/null +++ b/src/spatial/spatial_internal.h @@ -0,0 +1,34 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_SPATIAL_INTERNAL_H +#define IGRAPH_SPATIAL_INTERNAL_H + +#include "igraph_decls.h" +#include "igraph_error.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +IGRAPH_BEGIN_C_DECLS + +igraph_error_t igraph_i_delaunay_edges(igraph_vector_int_t *edges, const igraph_matrix_t *points); +igraph_error_t igraph_i_check_spatial_points(const igraph_matrix_t *points); + +IGRAPH_END_C_DECLS + +#endif /* IGRAPH_SPATIAL_INTERNAL_H */ diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..7df7222 --- /dev/null +++ b/src/version.c @@ -0,0 +1,59 @@ +/* + igraph library. + Copyright (C) 2008-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "igraph_version.h" + +#include + +static const char *igraph_version_string = IGRAPH_VERSION; + +/** + * \function igraph_version + * \brief The version of the igraph C library. + * + * \param version_string Pointer to a string pointer. If not \c NULL, it + * is set to the igraph version string, e.g. "0.10.13", "1.2.0", or + * "0.10.13-14-g997f59ad7". It consists of three dot-separated numerical + * parts and potentially of a dash-separated suffix, used in prerelease + * versions. This string must not be modified or deallocated. + * \param major If not a \c NULL pointer, then it is set to the major + * igraph version. E.g. for version "0.10.13" this is 0. + * \param minor If not a \c NULL pointer, then it is set to the minor + * igraph version. E.g. for version "0.10.13" this is 10. + * \param patch If not a \c NULL pointer, then it is set to the + * subminor igraph version. E.g. for version "0.10.13" this is 13. + * + * \example examples/simple/igraph_version.c + */ + +void igraph_version(const char **version_string, + int *major, + int *minor, + int *patch) { + int i1, i2, i3; + int *p1 = major ? major : &i1; + int *p2 = minor ? minor : &i2; + int *p3 = patch ? patch : &i3; + + if (version_string) { + *version_string = igraph_version_string; + } + + *p1 = *p2 = *p3 = 0; + sscanf(IGRAPH_VERSION, "%i.%i.%i", p1, p2, p3); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..42cc502 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,1067 @@ +include(test_helpers) +include(benchmark_helpers) + +add_library(test_utilities OBJECT unit/test_utilities.c) +target_link_libraries(test_utilities PRIVATE igraph) +use_all_warnings(test_utilities) + +# Add a compatibility alias to the "test" target so it can also be invoked as +# "make check" - for people who have it in their muscle memories from autotools +add_custom_target(build_tests) +add_custom_target( + check + COMMAND ${CMAKE_CTEST_COMMAND} --progress --output-on-failure -C $ + COMMENT "Executing unit tests..." + USES_TERMINAL +) +add_dependencies(check build_tests) + +# Add a custom target for benchmarks and another one for building them +add_custom_target(build_benchmarks) +add_custom_target( + benchmark + COMMENT "Running benchmarks..." +) +add_dependencies(benchmark build_benchmarks) + +# Some newer gcc version have --enable-new-dtags on by default. This then leads +# to using RUNPATH instead of RPATH. Since RUNPATH is only considered after +# LD_LIBRARY_PATH, if another version of igraph is installed somewhere it will +# be linked to that library. +include(CheckLinkerFlag) +check_linker_flag(C "-Wl,--enable-new-dtags" HAVE_ENABLE_NEW_DTAGS) +if (HAVE_ENABLE_NEW_DTAGS AND BUILD_SHARED_LIBS) + message(STATUS "Disabling new dtags for testing to use RPATH to ensure the correct library is found.") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-new-dtags") +endif() + +# tutorial examples and other snippets from the documentation +add_examples( + FOLDER examples/tutorial NAMES + tutorial1 + tutorial2 + tutorial3 +) + +# version.at +add_examples( + FOLDER examples/simple NAMES + igraph_version +) + +# types.at +add_examples( + FOLDER examples/simple NAMES + dqueue + igraph_sparsemat + igraph_sparsemat3 + igraph_sparsemat4 + igraph_sparsemat6 + igraph_sparsemat7 + igraph_sparsemat8 + igraph_strvector + igraph_vector_int_list_sort +) + +add_legacy_tests( + FOLDER tests/unit NAMES + bitset + heap + igraph_complex + igraph_psumtree + igraph_sparsemat5 + igraph_sparsemat9 + igraph_sparsemat_droptol + igraph_sparsemat_fkeep + igraph_sparsemat_getelements_sorted + igraph_sparsemat_is_symmetric + igraph_sparsemat_iterator_idx + igraph_sparsemat_minmax + igraph_sparsemat_nonzero_storage + igraph_sparsemat_normalize + igraph_sparsemat_which_minmax + igraph_strvector + igraph_vector_floor + igraph_vector_lex_cmp + matrix + matrix2 + matrix3 + matrix_complex + stack + strvector_set_len_remove_print + vector + vector2 + vector3 + vector4 + vector_list + vector_ptr + vector_ptr_sort_ind + vector_sort_ind +) + +if ((NOT BUILD_SHARED_LIBS) OR (NOT BLAS_IS_VENDORED AND NOT ARPACK_IS_VENDORED)) + add_legacy_tests( + FOLDER tests/unit NAMES + igraph_sparsemat2 # Uses ARPACK and BLAS functions which are not publicly available when building with internal ARPACK/BLAS + ) +endif() + +add_legacy_tests( + FOLDER tests/unit NAMES + 2wheap + cutheap + d_indheap + marked_queue + gen2wheap + set + trie +) + +# basic.at +add_examples( + FOLDER examples/simple NAMES + creation + igraph_copy + igraph_degree + igraph_delete_edges + igraph_delete_vertices + igraph_get_eid + igraph_get_eids + igraph_is_directed + igraph_neighbors +) + +add_legacy_tests( + FOLDER tests/unit NAMES + adj + igraph_add_edges + igraph_add_vertices + igraph_degree + igraph_delete_edges + igraph_delete_vertices + igraph_edges + igraph_empty + igraph_get_eid + igraph_is_same_graph + igraph_incident + igraph_neighbors +) + +# iterators.at +add_examples( + FOLDER examples/simple NAMES + igraph_es_pairs + igraph_vs_nonadj + igraph_vs_range + igraph_vs_vector +) + +add_legacy_tests( + FOLDER tests/unit NAMES + edge_selectors + igraph_es_path + igraph_es_all_between + vertex_selectors +) + +# structure_generators.at +add_examples( + FOLDER examples/simple NAMES + igraph_atlas + igraph_barabasi_game + igraph_barabasi_game2 + igraph_create + igraph_degree_sequence_game + igraph_erdos_renyi_game_gnm + igraph_erdos_renyi_game_gnp + igraph_full + igraph_grg_game + igraph_kary_tree + igraph_lcf + igraph_realize_degree_sequence + igraph_regular_tree + igraph_ring + igraph_small + igraph_star + igraph_symmetric_tree + igraph_weighted_adjacency +) + +add_legacy_tests( + FOLDER tests/unit NAMES + constructor-failure + erdos_renyi_game_gnm + erdos_renyi_game_gnp + full + igraph_adjacency + igraph_atlas + igraph_barabasi_aging_game + igraph_barabasi_game + igraph_bipartite_game + igraph_callaway_traits_game + igraph_chung_lu_game + igraph_circulant + igraph_cited_type_game + igraph_citing_cited_type_game + igraph_correlated_game + igraph_correlated_pair_game + igraph_create + igraph_degree_sequence_game + igraph_dot_product_game + igraph_establishment_game + igraph_extended_chordal_ring + igraph_forest_fire_game + igraph_from_prufer + igraph_full_bipartite + igraph_full_citation + igraph_full_multipartite + igraph_generalized_petersen + igraph_grg_game + igraph_growing_random_game + igraph_hsbm_game + igraph_hsbm_list_game + igraph_k_regular_game + igraph_lastcit_game + igraph_linegraph + igraph_lcf + igraph_kautz + igraph_preference_game + igraph_perfect + igraph_realize_degree_sequence + igraph_recent_degree_aging_game + igraph_recent_degree_game + igraph_sbm_game + igraph_simple_interconnected_islands_game + igraph_square_lattice + igraph_static_power_law_game + igraph_tree_from_parent_vector + igraph_trussness + igraph_turan + igraph_wheel + igraph_weighted_adjacency + kary_tree + symmetric_tree + tree_game + ring + watts_strogatz_game +) + +# structural_properties.at +add_examples( + FOLDER examples/simple NAMES + bellman_ford + distances + igraph_assortativity_degree + igraph_assortativity_nominal + igraph_average_path_length + igraph_cocitation + igraph_diameter + igraph_eccentricity + igraph_feedback_arc_set + igraph_feedback_arc_set_ip + igraph_get_all_shortest_paths_dijkstra + igraph_get_shortest_paths + igraph_get_shortest_paths_dijkstra + igraph_girth + igraph_has_multiple + igraph_is_loop + igraph_is_multiple + igraph_list_triangles + igraph_avg_nearest_neighbor_degree + igraph_minimum_spanning_tree + igraph_pagerank + igraph_radius + igraph_reciprocity + igraph_similarity + igraph_simplify + igraph_topological_sorting + igraph_transitivity +) + +add_legacy_tests( + FOLDER tests/unit NAMES + all_shortest_paths + assortativity + igraph_product + components + coreness + efficiency +# The eigen_stress test is disabled by default. The chance of failure on at +# least some platforms, or with some BLAS/LAPACK/ARPACK versions is high +# due to ARPACK's fickleness. The test is still useful for local testing. + # eigen_stress + global_transitivity + harmonic_centrality + hub_and_authority + igraph_count_adjacent_triangles + igraph_are_adjacent + igraph_average_path_length + igraph_average_path_length_dijkstra + igraph_betweenness + igraph_betweenness_subset + igraph_closeness + igraph_constraint + igraph_convergence_degree + igraph_count_multiple + igraph_density + igraph_diameter + igraph_diameter_dijkstra + igraph_diversity + igraph_distances_floyd_warshall + igraph_distances_floyd_warshall_speedup + igraph_distances_johnson + igraph_ecc + igraph_eccentricity + igraph_eccentricity_dijkstra + igraph_edge_betweenness + igraph_edge_betweenness_subset + igraph_feedback_arc_set + igraph_feedback_vertex_set + igraph_get_all_simple_paths + igraph_get_all_shortest_paths_dijkstra + igraph_get_k_shortest_paths + igraph_get_shortest_paths2 + igraph_get_shortest_path_astar + igraph_get_shortest_path_bellman_ford + igraph_get_shortest_paths_bellman_ford + igraph_get_shortest_paths_dijkstra + igraph_graph_center + igraph_has_mutual + igraph_is_bipartite + igraph_is_connected + igraph_is_chordal + igraph_is_clique + igraph_is_complete + igraph_is_dag + igraph_is_mutual + igraph_is_tree + igraph_is_forest + igraph_is_forest2 + igraph_joint_degree_distribution + igraph_joint_type_distribution + igraph_is_acyclic + igraph_list_triangles + igraph_local_scan_k_ecount + igraph_local_scan_k_ecount_them + igraph_local_scan_subset_ecount + igraph_local_transitivity + igraph_mean_degree + igraph_neighborhood + igraph_neighborhood_graphs + igraph_neighborhood_size + igraph_pagerank + igraph_path_length_hist + igraph_pseudo_diameter + igraph_pseudo_diameter_dijkstra + igraph_random_walk + igraph_rewire # Uses internal igraph_i_rewire + igraph_rooted_product + igraph_simple_cycles + igraph_similarity + igraph_transitive_closure + igraph_transitivity_avglocal_undirected + igraph_transitivity_barrat + igraph_unfold_tree + igraph_voronoi + igraph_spanner + jdm + knn + minimum_spanning_tree + mycielskian + paths + random_spanning_tree + reachability + rich_club + single_target_shortest_path + topological_sorting + widest_paths +) + +add_legacy_tests( + FOLDER tests/regression NAMES + bug_1760 + bug_1814 + bug_1970 + bug_2150 + bug_2497 + bug_2506 + bug_2517 +) + +# components.at +add_examples( + FOLDER examples/simple NAMES + igraph_biconnected_components + igraph_decompose + igraph_is_biconnected +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_biconnected_components + igraph_bridges + igraph_decompose_strong + igraph_is_biconnected + igraph_subcomponent +) + +# layout.at +add_examples( + FOLDER examples/simple NAMES + igraph_layout_reingold_tilford +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_layout_align + igraph_layout_drl + igraph_layout_drl_3d + igraph_layout_bipartite + igraph_layout_fruchterman_reingold + igraph_layout_fruchterman_reingold_3d + igraph_layout_gem + igraph_layout_graphopt + igraph_layout_grid + igraph_layout_kamada_kawai + igraph_layout_lgl + igraph_layout_mds + igraph_layout_merge2 + igraph_layout_merge3 + igraph_layout_random_3d + igraph_layout_reingold_tilford_circular + igraph_layout_reingold_tilford_extended + igraph_layout_sphere + igraph_layout_star + igraph_layout_sugiyama + igraph_layout_umap +) +add_legacy_tests( + FOLDER tests/regression NAMES + igraph_layout_kamada_kawai_3d_bug_1462 + igraph_layout_reingold_tilford_bug_879 +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_i_layout_sphere + igraph_layout_davidson_harel # Uses igraph_i_layout_segments_intersect and igraph_i_layout_point_segment_dist2 + igraph_layout_merge # Uses igraph_i_layout_merge functions + igraph_i_umap_fit_ab +) + +# visitors.at +add_examples( + FOLDER examples/simple NAMES + igraph_bfs + igraph_bfs_callback + igraph_bfs_simple +) +add_legacy_tests( + FOLDER tests/unit NAMES + bfs + bfs_simple +) + +# topology.at +add_examples( + FOLDER examples/simple NAMES + igraph_isomorphic_vf2 + igraph_subisomorphic_lad +) + +add_legacy_tests( + FOLDER tests/unit NAMES + simplify_and_colorize + bliss_automorphisms + igraph_get_isomorphisms_vf2 + igraph_get_subisomorphisms_vf2 + igraph_isomorphic + igraph_isomorphic_vf2 + igraph_subisomorphic + igraph_subisomorphic_lad + igraph_isomorphic_bliss + isomorphism_test + isoclasses + isoclasses2 + VF2-compat +) + +# coloring.at +add_examples( + FOLDER examples/simple NAMES + coloring +) + +add_legacy_tests( + FOLDER tests/unit NAMES + coloring + is_coloring +) + + +# motifs.at +add_examples( + FOLDER examples/simple NAMES + igraph_motifs_randesu +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_dyad_census + igraph_motifs_randesu + igraph_motifs_randesu_estimate + igraph_motifs_randesu_no + triad_census +) + +# foreign.at +add_examples( + FOLDER examples/simple NAMES + dot + foreign + gml + graphml + igraph_read_graph_dl + igraph_read_graph_graphdb + igraph_read_graph_lgl + igraph_write_graph_lgl + igraph_write_graph_pajek + safelocale +) + +add_legacy_tests( + FOLDER tests/unit NAMES + foreign_empty + gml + igraph_read_graph_graphdb + igraph_read_graph_graphml + igraph_write_graph_leda + igraph_write_graph_dimacs_flow + igraph_write_graph_dot + lineendings + ncol + pajek + pajek2 + pajek_bipartite + pajek_bipartite2 + pajek_signed +) + +# other.at +add_examples( + FOLDER examples/simple NAMES + igraph_power_law_fit +) +# igraph_power_law_fit() output is only deterministic when running with 1 thread +set_property(TEST example::igraph_power_law_fit APPEND PROPERTY ENVIRONMENT "OMP_NUM_THREADS=1") + +add_legacy_tests( + FOLDER tests/unit NAMES + all_almost_e + cmp_epsilon + igraph_almost_equals + igraph_convex_hull_2d + igraph_power_law_fit + overflow + prop_caching + zapsmall +) +# igraph_power_law_fit() output is only deterministic when running with 1 thread +set_property(TEST test::igraph_power_law_fit APPEND PROPERTY ENVIRONMENT "OMP_NUM_THREADS=1") + +# operators.at +add_examples( + FOLDER examples/simple NAMES + igraph_complementer + igraph_compose + igraph_contract_vertices + igraph_difference + igraph_disjoint_union + igraph_join + igraph_intersection + igraph_union +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_contract_vertices + igraph_connect_neighborhood + igraph_disjoint_union + igraph_graph_power + igraph_induced_subgraph + igraph_induced_subgraph_map + igraph_induced_subgraph_edges + igraph_intersection + igraph_permute_vertices + igraph_reverse_edges + igraph_rewire_directed_edges + igraph_union +) + +# conversion.at +add_examples( + FOLDER examples/simple NAMES + adjlist + igraph_get_laplacian + igraph_get_laplacian_sparse + igraph_to_undirected +) + +add_legacy_tests( + FOLDER tests/unit NAMES + adjlist + igraph_adjlist_init_complementer + igraph_adjlist_simplify + igraph_get_adjacency + igraph_get_adjacency_sparse + igraph_get_laplacian + igraph_get_stochastic + igraph_get_stochastic_sparse + igraph_to_directed + igraph_to_prufer + inclist +) + +# flow.at +add_examples( + FOLDER examples/simple NAMES + dominator_tree + even_tarjan + flow + flow2 + igraph_all_st_mincuts + igraph_mincut +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_all_st_mincuts + igraph_dominator_tree + igraph_st_mincut_value + igraph_vertex_disjoint_paths + igraph_adhesion + igraph_cohesion + igraph_maxflow + igraph_residual_graph + igraph_edge_disjoint_paths + igraph_st_edge_connectivity + igraph_st_mincut + igraph_st_vertex_connectivity +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_all_st_cuts # Uses igraph_marked_queue, which is internal. +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_gomory_hu_tree +) + +# community.at +add_examples( + FOLDER examples/simple NAMES + igraph_community_edge_betweenness + igraph_community_fastgreedy + igraph_community_label_propagation + igraph_community_leading_eigenvector + igraph_community_leiden + igraph_community_multilevel + igraph_community_optimal_modularity + walktrap +) + +add_legacy_tests( + FOLDER tests/unit NAMES + community_indexing + community_leiden + community_label_propagation + community_label_propagation2 + community_label_propagation3 + community_walktrap + graphlets + igraph_community_eb_get_merges + igraph_community_edge_betweenness + igraph_community_fastgreedy + igraph_community_fluid_communities + igraph_community_infomap + igraph_community_leading_eigenvector2 + igraph_community_optimal_modularity + igraph_community_voronoi + igraph_compare_communities + igraph_le_community_to_membership + igraph_modularity + igraph_modularity_matrix + igraph_reindex_membership + igraph_split_join_distance + levc-stress + null_communities + spinglass +) +add_legacy_tests( + FOLDER tests/regression NAMES + bug-1149658 +) + +# use a higher test timeout for the Infomap algorithm +set_tests_properties("test::igraph_community_infomap" PROPERTIES TIMEOUT 150) + +# cliques.at +add_examples( + FOLDER examples/simple NAMES + igraph_cliques + igraph_independent_sets + igraph_maximal_cliques +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_clique_size_hist + igraph_maximal_cliques + igraph_maximal_cliques2 + igraph_maximal_cliques3 + igraph_maximal_cliques4 + igraph_maximal_cliques_file + igraph_weighted_cliques + maximal_cliques_callback + maximal_cliques_hist + max_results +) + +# eigen.at +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_eigen_matrix + igraph_eigen_matrix2 + igraph_eigen_matrix3 + igraph_eigen_matrix4 + igraph_eigen_matrix_symmetric + igraph_eigen_matrix_symmetric_arpack +) + +# attributes.at +add_examples( + FOLDER examples/simple NAMES + cattributes + cattributes2 + cattributes3 + cattributes4 + igraph_attribute_combination +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_attribute_combination_remove + cattributes5 + cattributes6 +) +add_legacy_tests( + FOLDER tests/regression NAMES + cattr_bool_bug + cattr_bool_bug2 +) + +# arpack.at +add_examples( + FOLDER examples/simple NAMES + blas + blas_dgemm + eigenvector_centrality + igraph_lapack_dgeev + igraph_lapack_dgeevx + igraph_lapack_dgesv + igraph_lapack_dsyevr +) + +add_legacy_tests( + FOLDER tests/unit NAMES + dgemv + igraph_arpack_rnsolve + igraph_arpack_unpack_complex + igraph_blas_dgemm + igraph_eigenvector_centrality + igraph_lapack_dgeev + igraph_lapack_dgeevx + igraph_lapack_dgehrd + igraph_lapack_dgetrf + igraph_lapack_dgetrs + igraph_lapack_dsyevr +) + +# bipartite.at +add_examples( + FOLDER examples/simple NAMES + igraph_bipartite_create + igraph_bipartite_projection +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_bipartite_create + igraph_bipartite_projection + igraph_biadjacency + igraph_get_biadjacency + igraph_weighted_biadjacency +) + +# centralization.at +add_examples( + FOLDER examples/simple NAMES + centralization +) + +add_legacy_tests( + FOLDER tests/unit NAMES + centralization +) + +# eulerian.at +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_is_eulerian + igraph_eulerian_cycle + igraph_eulerian_path + igraph_find_cycle +) + +# separators.at +add_examples( + FOLDER examples/simple NAMES + cohesive_blocks + igraph_is_minimal_separator + igraph_is_separator + igraph_minimal_separators + igraph_minimum_size_separators +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_is_separator +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_cohesive_blocks + igraph_minimum_size_separators +) + +add_legacy_tests( + FOLDER tests/regression NAMES + bug-1033045 +) + +# hrg.at +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_hrg + igraph_hrg2 + igraph_hrg3 + igraph_hrg_create +) + +# mt.at -- only if we have pthreads +if(CMAKE_USE_PTHREADS_INIT) + add_legacy_tests( + FOLDER tests/unit NAMES tls1 + LIBRARIES Threads::Threads + ) + + # tls2 should be added only if we use vendored ARPACK because a non-vendored + # ARPACK is not guaranteed to be thread-safe + if(ARPACK_IS_VENDORED AND NOT BUILD_SHARED_LIBS) + add_legacy_tests( + FOLDER tests/unit NAMES tls2 + LIBRARIES Threads::Threads + ) + endif() +endif() + +# random.at +add_examples( + FOLDER examples/simple NAMES + igraph_fisher_yates_shuffle + igraph_random_sample + random_seed +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_random_sample + igraph_rng_get_integer + random_sampling + rng_reproducibility + rng_init_destroy_max_bits_name_set_default +) + +# qsort.at +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_qsort + igraph_qsort_r +) + +# matching.at +add_examples( + FOLDER examples/simple NAMES + igraph_maximum_bipartite_matching +) + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_maximum_bipartite_matching +) + +# embedding.at +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_adjacency_spectral_embedding +) + +# graphicality + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_is_graphical + igraph_is_bigraphical + igraph_realize_bipartite_degree_sequence +) + +# spatial +add_legacy_tests( + FOLDER tests/unit NAMES + beta_skeletons + igraph_delaunay_graph + igraph_nearest_neighbor_graph + igraph_spatial_edge_lengths +) + + +# cycle bases +add_legacy_tests( + FOLDER tests/unit NAMES + cycle_bases +) + +# handlers + +add_legacy_tests( + FOLDER tests/unit NAMES + fatal_handler + igraph_progress_handler_stderr + igraph_set_progress_handler +) + +# error output + +add_legacy_tests( + FOLDER tests/unit NAMES + error_macros +) + +# GLPK + +add_legacy_tests( + FOLDER tests/unit NAMES + glpk_error +) + +# regression and fuzzing tests + +add_legacy_tests( + FOLDER tests/regression NAMES + igraph_read_graph_gml_invalid_inputs + igraph_read_graph_graphml_invalid_inputs + igraph_read_graph_pajek_invalid_inputs +) + +# non-graph + +add_legacy_tests( + FOLDER tests/unit NAMES + expand_path_to_pairs + igraph_running_mean + igraph_solve_lsap +) + +# memory allocation + +add_legacy_tests( + FOLDER tests/unit NAMES + zero_allocs +) + +# simulation + +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_sir +) + + +# benchmarks +add_benchmarks( + NAMES + community + connectivity + erdos_renyi + graphicality + igraph_adjacency + igraph_average_path_length_unweighted + beta_skeletons + igraph_betweenness + igraph_betweenness_weighted + igraph_cliques + igraph_closeness_weighted + coloring + igraph_decompose + igraph_degree + igraph_degree_sequence_game + igraph_delaunay_graph + igraph_distances + igraph_ecc + igraph_induced_subgraph + igraph_induced_subgraph_edges + igraph_layout_umap + igraph_matrix_transpose + igraph_maximal_cliques + igraph_nearest_neighbor_graph + igraph_neighborhood + igraph_pagerank + igraph_pagerank_weighted + igraph_power_law_fit + igraph_qsort + igraph_random_walk + igraph_realize_degree_sequence + igraph_rewire + igraph_strength + igraph_transitivity + igraph_tree_game + igraph_vertex_connectivity + igraph_voronoi + inc_vs_adj + intersection + lad + modularity + spanning_tree +) + +# sampling +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_rng_sample_dirichlet + igraph_rng_sample_sphere +) + +# lattices +add_legacy_tests( + FOLDER tests/unit NAMES + igraph_triangular_lattice + igraph_hexagonal_lattice +) + +# percolation +add_legacy_tests( + FOLDER tests/unit NAMES + percolation +) diff --git a/tests/benchmarks/bench.h b/tests/benchmarks/bench.h new file mode 100644 index 0000000..20664b6 --- /dev/null +++ b/tests/benchmarks/bench.h @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2013-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef IGRAPH_BENCH_H +#define IGRAPH_BENCH_H + +#include /* getrusage */ +#include /* gettimeofday */ +#include /* sleep */ + +static inline void igraph_get_cpu_time(double *data) { + + struct rusage self; + struct timeval real; + gettimeofday(&real, NULL); + getrusage(RUSAGE_SELF, &self); + data[0] = (double) real.tv_sec + 1e-6 * real.tv_usec; /* real */ + data[1] = (double) self.ru_utime.tv_sec + 1e-6 * self.ru_utime.tv_usec; /* user */ + data[2] = (double) self.ru_stime.tv_sec + 1e-6 * self.ru_stime.tv_usec; /* system */ +} + +#define BENCH_INIT() \ + do { \ + printf("\n|> Benchmark file: %s\n", IGRAPH_FILE_BASENAME); \ + sleep(1); \ + } while (0) + +#define REPEAT(CODE, N) \ + do { \ + igraph_int_t rep_i; \ + for (rep_i=0; rep_i < N; ++rep_i) { CODE; } \ + } while (0) + +#define BENCH(NAME, ...) do { \ + double start[3], stop[3]; \ + double r, u, s; \ + igraph_get_cpu_time(start); \ + { __VA_ARGS__; } \ + igraph_get_cpu_time(stop); \ + r = 1e-3 * round(1e3 * (stop[0] - start[0])); \ + u = 1e-3 * round(1e3 * (stop[1] - start[1])); \ + s = 1e-3 * round(1e3 * (stop[2] - start[2])); \ + printf("| %-80s %5.3gs %5.3gs %5.3gs\n", NAME, r, u, s); \ + } while (0) + +#endif diff --git a/tests/benchmarks/beta_skeletons.c b/tests/benchmarks/beta_skeletons.c new file mode 100644 index 0000000..eda9dd0 --- /dev/null +++ b/tests/benchmarks/beta_skeletons.c @@ -0,0 +1,150 @@ +/* + IGraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include + +#include "bench.h" +#include "igraph_spatial.h" + +#include + +void bench_lune(igraph_int_t test_nr, igraph_int_t point_count, igraph_int_t dimensions, igraph_real_t beta) { + igraph_matrix_t points; + igraph_t g; + char msg[200]; + + igraph_matrix_init(&points, point_count, dimensions); + + for (igraph_int_t point = 0; point < point_count; point++) { + for (igraph_int_t dim = 0; dim < dimensions; dim++) { + MATRIX(points, point, dim) = RNG_UNIF01(); + } + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%3"IGRAPH_PRId" Lune based beta skeleton in %dD, beta=%.3f, n=%6" IGRAPH_PRId, + test_nr, (int) dimensions, beta, point_count); + + BENCH(msg, igraph_lune_beta_skeleton(&g, &points, beta)); + + igraph_destroy(&g); + igraph_matrix_destroy(&points); + +} + +void bench_circle(igraph_int_t test_nr, igraph_int_t point_count, igraph_int_t dimensions, igraph_real_t beta) { + igraph_matrix_t points; + igraph_t g; + char msg[200]; + + igraph_matrix_init(&points, point_count, dimensions); + + for (igraph_int_t point = 0; point < point_count; point++) { + for (igraph_int_t dim = 0; dim < dimensions; dim++) { + MATRIX(points, point, dim) = RNG_UNIF01(); + } + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%3"IGRAPH_PRId" Circle based beta skeleton in %dD, beta=%3f, n=%6" IGRAPH_PRId, + test_nr, (int) dimensions, beta, point_count); + + BENCH(msg, igraph_circle_beta_skeleton(&g, &points, beta)); + + igraph_destroy(&g); + igraph_matrix_destroy(&points); + +} + +void bench_gabriel(igraph_int_t test_nr, igraph_int_t point_count, igraph_int_t dimensions, igraph_real_t beta_cutoff) { + igraph_matrix_t points; + igraph_vector_t edge_weights; + igraph_t g; + char msg[200]; + + igraph_matrix_init(&points, point_count, dimensions); + igraph_vector_init(&edge_weights, 0); + + for (igraph_int_t point = 0; point < point_count; point++) { + for (igraph_int_t dim = 0; dim < dimensions; dim++) { + MATRIX(points, point, dim) = RNG_UNIF01(); + } + } + + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%3"IGRAPH_PRId" Beta weighted gabriel graph in %dD, cutoff=%3f, n=%6" IGRAPH_PRId, + test_nr, (int) dimensions, beta_cutoff, point_count); + + BENCH(msg, igraph_beta_weighted_gabriel_graph(&g, &edge_weights, &points, beta_cutoff)); + + igraph_vector_destroy(&edge_weights); + igraph_destroy(&g); + igraph_matrix_destroy(&points); + +} + +int main(void) { + igraph_int_t test_nr = 0; + bench_lune(++test_nr, 100, 2, 2); + bench_lune(++test_nr, 1000, 2, 2); + bench_lune(++test_nr, 10000, 2, 2); + bench_lune(++test_nr, 100000, 2, 2); + bench_lune(++test_nr, 100, 3, 2); + bench_lune(++test_nr, 1000, 3, 2); + bench_lune(++test_nr, 10000, 3, 2); + bench_lune(++test_nr, 100000, 3, 2); + bench_lune(++test_nr, 1000, 4, 2); + + bench_lune(++test_nr, 10000, 2, 1); + bench_lune(++test_nr, 10000, 2, 2); + bench_lune(++test_nr, 10000, 2, 4); + bench_lune(++test_nr, 10000, 2, 8); + bench_lune(++test_nr, 10000, 2, 16); + bench_lune(++test_nr, 10000, 2, 32); + bench_lune(++test_nr, 10000, 2, 64); + bench_lune(++test_nr, 10000, 2, 128); + bench_lune(++test_nr, 10000, 2, 256); + bench_lune(++test_nr, 10000, 2, 512); + bench_lune(++test_nr, 10000, 2, 1024); + bench_lune(++test_nr, 1000, 2, 0.9); + bench_lune(++test_nr, 1000, 2, 0.5); + bench_lune(++test_nr, 1000, 2, 0.1); + + + bench_circle(++test_nr, 100, 2, 2); + bench_circle(++test_nr, 1000, 2, 2); + bench_circle(++test_nr, 10000, 2, 2); + bench_circle(++test_nr, 100000, 2, 2); + + bench_circle(++test_nr, 10000, 2, 2); + bench_circle(++test_nr, 10000, 2, 3); + bench_circle(++test_nr, 10000, 2, 5); + bench_circle(++test_nr, 10000, 2, 15); + bench_circle(++test_nr, 10000, 2, 50); + + bench_gabriel(++test_nr, 100, 2, 2); + bench_gabriel(++test_nr, 1000, 2, 2); + bench_gabriel(++test_nr, 10000, 2, 2); + bench_gabriel(++test_nr, 100000, 2, 2); + + bench_gabriel(++test_nr, 100, 3, 2); + bench_gabriel(++test_nr, 1000, 3, 2); + bench_gabriel(++test_nr, 10000, 3, 2); + bench_gabriel(++test_nr, 100000, 3, 2); + return 0; +} diff --git a/tests/benchmarks/coloring.c b/tests/benchmarks/coloring.c new file mode 100644 index 0000000..d780a31 --- /dev/null +++ b/tests/benchmarks/coloring.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t colors; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + + igraph_vector_int_init(&colors, 0); + + igraph_erdos_renyi_game_gnm(&g, 50000, 1000000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 1 COLORED_NEIGHBORS, random graph with 50,000 vertices and 2,000,000 edges", + igraph_vertex_coloring_greedy(&g, &colors, IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS) + ); + BENCH(" 2 DSATUR, random graph with 50,000 vertices and 2,000,000 edges", + igraph_vertex_coloring_greedy(&g, &colors, IGRAPH_COLORING_GREEDY_DSATUR) + ); + igraph_destroy(&g); + + printf("\n"); + + igraph_barabasi_game(&g, 100000, 1, 15, NULL, 0, 0, 0, IGRAPH_BARABASI_PSUMTREE, NULL); + BENCH(" 1 COLORED_NEIGHBORS, pref. attach. graph n=100,000 m=15", + igraph_vertex_coloring_greedy(&g, &colors, IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS) + ); + BENCH(" 2 DSATUR, pref. attach. graph n=100,000 m=15", + igraph_vertex_coloring_greedy(&g, &colors, IGRAPH_COLORING_GREEDY_DSATUR) + ); + igraph_destroy(&g); + + igraph_vector_int_destroy(&colors); + + + return 0; +} diff --git a/tests/benchmarks/community.c b/tests/benchmarks/community.c new file mode 100644 index 0000000..9a2aa05 --- /dev/null +++ b/tests/benchmarks/community.c @@ -0,0 +1,109 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void run_bench(const igraph_t *graph, const igraph_vector_t *weights, + const char *name, int rep) { + igraph_vector_int_t membership; + igraph_vector_t vertex_weight; + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + char msg[256], msg2[128]; + + igraph_vector_int_init(&membership, vcount); + igraph_vector_init(&vertex_weight, vcount); + + igraph_strength(graph, &vertex_weight, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, weights); + + snprintf(msg2, sizeof(msg2) / sizeof(msg2[0]), + "%s, vcount=%" IGRAPH_PRId ", ecount=%" IGRAPH_PRId ", %s, %dx", + name, vcount, ecount, weights == NULL ? "unweighted" : "weighted", + rep); + + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), "1 Louvain, %s", msg2); + BENCH(msg, REPEAT(igraph_community_multilevel(graph, weights, 1.0, &membership, NULL, NULL), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), "2 Leiden , %s", msg2); + BENCH(msg, REPEAT(igraph_community_leiden(graph, weights, &vertex_weight, NULL, 1.0 / igraph_vector_sum(weights), 0.01, false, 1, &membership, NULL, NULL), rep)); + + printf("\n"); + + igraph_vector_destroy(&vertex_weight); + igraph_vector_int_destroy(&membership); +} + +void rand_weights(const igraph_t *graph, igraph_vector_t *weights) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_resize(weights, ecount); + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(*weights)[i] = RNG_UNIF01(); + } +} + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_vector_init(&weights, 0); + + igraph_erdos_renyi_game_gnm(&graph, 100, 500, false, false, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "G(n,m)", 1000); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 1000, 5000, false, false, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "G(n,m)", 100); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 1000, 50000, false, false, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "G(n,m)", 10); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 10000, 50000, false, false, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "G(n,m)", 10); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100000, 500000, false, false, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "G(n,m)", 1); + igraph_destroy(&graph); + + igraph_forest_fire_game(&graph, 1000, 0.2, 1, 2, false); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "forest fire", 100); + igraph_destroy(&graph); + + igraph_barabasi_game(&graph, 1000, 1, 5, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&graph, &weights); + run_bench(&graph, &weights, "PA", 100); + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + + return 0; +} diff --git a/tests/benchmarks/connectivity.c b/tests/benchmarks/connectivity.c new file mode 100644 index 0000000..9a08605 --- /dev/null +++ b/tests/benchmarks/connectivity.c @@ -0,0 +1,110 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +/* Benchmarks related to biconnected components. */ + +void run_bench(igraph_int_t vcount, igraph_real_t meandeg, igraph_int_t rep) { + igraph_t g; + igraph_bool_t b; + igraph_vector_int_t ivec; + char msg[128]; + + igraph_erdos_renyi_game_gnm(&g, vcount, round(meandeg * vcount / 2), IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_int_init(&ivec, igraph_vcount(&g)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 1 Weakly connected? vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, 10*rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_is_connected(&g, &b, IGRAPH_WEAK), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 2 Weak components. vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_connected_components(&g, &ivec, NULL, NULL, IGRAPH_WEAK), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 3 Strongly connected? vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, 10*rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_is_connected(&g, &b, IGRAPH_STRONG), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 4 Strong components. vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_connected_components(&g, &ivec, NULL, NULL, IGRAPH_STRONG), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 5 Biconnected? vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_is_biconnected(&g, &b), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 6 Bridges. vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_bridges(&g, &ivec), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 7 Articulation pts. vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_invalidate_cache(&g); + BENCH(msg, REPEAT(igraph_articulation_points(&g, &ivec), rep)); + + igraph_vector_int_destroy(&ivec); + igraph_destroy(&g); + + printf("\n"); + +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + // Note that whether these random graphs end up being connected + // with high probability depends not only on their mean degree, + // but also their vertex count. + + run_bench(10000, 0.5, 100); // no giant component + run_bench(10000, 3, 100); // not weakly connected + run_bench(10000, 10, 100); // not strongly connected + run_bench(10000, 20, 100); // strongly connnected + + run_bench(100000, 0.5, 100); // no giant component + run_bench(100000, 3, 100); // not weakly connected + run_bench(100000, 10, 100); // not strongly connected + run_bench(100000, 20, 100); // strongly connnected + + return 0; +} diff --git a/tests/benchmarks/erdos_renyi.c b/tests/benchmarks/erdos_renyi.c new file mode 100644 index 0000000..9d581f4 --- /dev/null +++ b/tests/benchmarks/erdos_renyi.c @@ -0,0 +1,136 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void gnp(igraph_int_t n, igraph_real_t p, igraph_bool_t directed, igraph_edge_type_sw_t allowed_edge_types) { + igraph_t g; + igraph_erdos_renyi_game_gnp(&g, n, p, directed, allowed_edge_types, IGRAPH_EDGE_UNLABELED); + igraph_destroy(&g); +} + +void gnm(igraph_int_t n, igraph_real_t meandeg, igraph_bool_t directed, igraph_edge_type_sw_t allowed_edge_types) { + igraph_t g; + igraph_erdos_renyi_game_gnm(&g, n, round(directed ? n * meandeg : 0.5 * n * meandeg), directed, allowed_edge_types, IGRAPH_EDGE_UNLABELED); + igraph_destroy(&g); +} + +void chung_lu(const igraph_vector_t *outdeg, const igraph_vector_t *indeg, igraph_bool_t loops) { + igraph_t g; + igraph_chung_lu_game(&g, outdeg, indeg, loops, IGRAPH_CHUNG_LU_ORIGINAL); + igraph_destroy(&g); +} + +void run_bench(igraph_int_t vcount, igraph_real_t meandeg, igraph_int_t rep) { + igraph_t g; + igraph_real_t p = meandeg / vcount; + igraph_vector_t outdeg, indeg; + char msg[128], msg2[80]; + + snprintf(msg2, sizeof(msg2) / sizeof(msg2[0]), + "vcount=%" IGRAPH_PRId ", meandeg=%g, %" IGRAPH_PRId "x", + vcount, meandeg, rep); + + igraph_vector_init(&outdeg, 0); + igraph_vector_init(&indeg, 0); + + igraph_erdos_renyi_game_gnp(&g, vcount, p, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_strength(&g, &outdeg, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, NULL); + igraph_destroy(&g); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 1 G(n,p) undirected, no loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnp(vcount, p, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 2 G(n,m) undirected, no loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnm(vcount, meandeg, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 3 Chung-Lu undirected, no loops, %s", msg2); + BENCH(msg, REPEAT(chung_lu(&outdeg, NULL, IGRAPH_NO_LOOPS), rep)); + + printf("\n"); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 4 G(n,p) undirected, loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnp(vcount, p, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 5 G(n,m) undirected, loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnm(vcount, meandeg, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 6 Chung-Lu undirected, loops, %s", msg2); + BENCH(msg, REPEAT(chung_lu(&outdeg, NULL, IGRAPH_LOOPS), rep)); + + printf("\n"); + + igraph_erdos_renyi_game_gnp(&g, vcount, p, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_strength(&g, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, NULL); + igraph_strength(&g, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, NULL); + igraph_destroy(&g); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 7 G(n,p) directed, no loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnp(vcount, p, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 8 G(n,m) directed, no loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnm(vcount, meandeg, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 9 Chung-Lu directed, no loops, %s", msg2); + BENCH(msg, REPEAT(chung_lu(&outdeg, &indeg, IGRAPH_NO_LOOPS), rep)); + + printf("\n"); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "10 G(n,p) directed, loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnp(vcount, p, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "11 G(n,m) directed, loops / no multi, %s", msg2); + BENCH(msg, REPEAT(gnm(vcount, meandeg, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "12 Chung-Lu directed, loops, %s", msg2); + BENCH(msg, REPEAT(chung_lu(&outdeg, &indeg, IGRAPH_LOOPS), rep)); + + printf("\n\n"); + + igraph_vector_destroy(&indeg); + igraph_vector_destroy(&outdeg); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + run_bench(100, 3, 10000); + run_bench(10000, 3, 100); + run_bench(1000000, 3, 1); + + run_bench(10000, 1, 1000); + run_bench(10000, 100, 10); + + return 0; +} diff --git a/tests/benchmarks/graphicality.c b/tests/benchmarks/graphicality.c new file mode 100644 index 0000000..514721c --- /dev/null +++ b/tests/benchmarks/graphicality.c @@ -0,0 +1,144 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +#define BENCH_UNDIR(N, REP) \ + do { \ + igraph_barabasi_game(&graph, N, 1, 3, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); \ + igraph_degree(&graph, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); \ + igraph_destroy(&graph); \ + \ + BENCH("Undirected simple, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_SIMPLE_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected loops, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected multi, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_MULTI_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected multi, loops, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW, &graphical), 100); \ + ); \ + \ + igraph_erdos_renyi_game_gnp(&graph, N, 12.0/N, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); \ + igraph_degree(&graph, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); \ + igraph_destroy(&graph); \ + \ + BENCH("Undirected simple, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_SIMPLE_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected loops, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected multi, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_MULTI_SW, &graphical), REP); \ + ); \ + \ + BENCH("Undirected multi, loops, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(°, NULL, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW, &graphical), 100); \ + ); \ + \ + } while (0) + + +#define BENCH_DIR(N, REP) \ +do { \ + igraph_barabasi_game(&graph, N, 1, 3, NULL, true, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); \ + igraph_degree(&graph, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); \ + igraph_degree(&graph, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); \ + igraph_destroy(&graph); \ + \ + BENCH("Directed simple, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_SIMPLE_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed loops, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed multi, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_MULTI_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed multi, loops, PA, n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ + igraph_erdos_renyi_game_gnp(&graph, N, 12.0/N, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); \ + igraph_degree(&graph, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); \ + igraph_degree(&graph, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); \ + igraph_destroy(&graph); \ + \ + BENCH("Directed simple, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_SIMPLE_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed loops, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed multi, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_MULTI_SW, &graphical), REP); \ + ); \ + \ + BENCH("Directed multi, loops, G(n,p), n = " TOSTR(N) ", " TOSTR(REP) "x", \ + REPEAT(igraph_is_graphical(&outdeg, &indeg, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW, &graphical), REP); \ + ); \ + \ +} while (0) + + +int main(void) { + igraph_t graph; + igraph_vector_int_t deg, outdeg, indeg; + igraph_bool_t graphical; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(°, 0); + igraph_vector_int_init(&outdeg, 0); + igraph_vector_int_init(&indeg, 0); + + BENCH_UNDIR(50, 2000000); + BENCH_DIR(50, 2000000); + printf("\n"); + BENCH_UNDIR(1000, 100000); + BENCH_DIR(1000, 100000); + printf("\n"); + BENCH_UNDIR(1000000, 100); + BENCH_DIR(1000000, 100); + + igraph_vector_int_destroy(°); + igraph_vector_int_destroy(&outdeg); + igraph_vector_int_destroy(&indeg); + + return 0; +} diff --git a/tests/benchmarks/igraph_adjacency.c b/tests/benchmarks/igraph_adjacency.c new file mode 100644 index 0000000..1766c4c --- /dev/null +++ b/tests/benchmarks/igraph_adjacency.c @@ -0,0 +1,100 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +/* + * Benchmark creating graphs from dense adjacency matrices. + * + * When there are a small number of non-zero elements (low mean degree), + * iterating through the matrix dominates the timing. When there are + * many non-zero elements, creating the graphfrom its edge list dominates. + */ + +void adjacency(const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, igraph_loops_t loops) { + igraph_t g; + igraph_adjacency(&g, adjmatrix, mode, loops); + igraph_destroy(&g); +} + +void weighted_adjacency(const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, igraph_vector_t *weights, igraph_loops_t loops) { + igraph_t g; + igraph_weighted_adjacency(&g, adjmatrix, mode, weights, loops); + igraph_destroy(&g); +} + +void run_bench(igraph_int_t vcount, igraph_int_t meandeg, igraph_int_t rep) { + igraph_t g; + igraph_matrix_t mat; + igraph_vector_t weights; + char msg[128]; + igraph_adjacency_t types[] = { + IGRAPH_ADJ_DIRECTED, + IGRAPH_ADJ_MAX, + IGRAPH_ADJ_PLUS, + IGRAPH_ADJ_UPPER /* similar to DIRECTED when unweighted, similar to MAX when weighted */ + }; + const char *names[] = { "DIRECTED", "MAX", "PLUS", "UPPER" }; + + igraph_matrix_init(&mat, 0, 0); + + igraph_erdos_renyi_game_gnm(&g, vcount, meandeg * vcount / 2, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + false); + igraph_get_adjacency(&g, &mat, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + + igraph_vector_init(&weights, igraph_ecount(&g)); + + igraph_destroy(&g); + + for (size_t i=0; i < sizeof(types) / sizeof(types[0]); i++) { + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%2d vcount=%" IGRAPH_PRId ", meandeg=%3" IGRAPH_PRId ", %8s, unweighted, %" IGRAPH_PRId "x", + (int) i+1, vcount, meandeg, names[i], rep); + + BENCH(msg, REPEAT(adjacency(&mat, types[i], IGRAPH_LOOPS_ONCE), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%2d vcount=%" IGRAPH_PRId ", meandeg=%3" IGRAPH_PRId ", %8s, weighted, %" IGRAPH_PRId "x", + (int) i+1, vcount, meandeg, names[i], rep); + + BENCH(msg, REPEAT(weighted_adjacency(&mat, types[i], &weights, IGRAPH_LOOPS_ONCE), rep)); + } + printf("\n"); + + igraph_vector_destroy(&weights); + igraph_matrix_destroy(&mat); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + run_bench(100, 5, 10000); + run_bench(100, 50, 10000); + run_bench(1000, 5, 100); + run_bench(1000, 50, 100); + run_bench(1000, 500, 100); + run_bench(10000, 5, 1); + run_bench(10000, 50, 1); + run_bench(10000, 500, 1); + + return 0; +} diff --git a/tests/benchmarks/igraph_average_path_length_unweighted.c b/tests/benchmarks/igraph_average_path_length_unweighted.c new file mode 100644 index 0000000..5f87d84 --- /dev/null +++ b/tests/benchmarks/igraph_average_path_length_unweighted.c @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t graph; + igraph_real_t avglen; + igraph_matrix_t mat; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&mat, 0, 0); + + igraph_kautz(&graph, 4, 5); + igraph_matrix_resize(&mat, igraph_vcount(&graph), igraph_vcount(&graph)); /* preallocate matrix */ + + BENCH(" 1 Kautz(4, 5) average_path_length directed", + igraph_average_path_length(&graph, NULL, &avglen, NULL, IGRAPH_DIRECTED, 1); + ); + BENCH(" 2 Kautz(4, 5) distances directed", + igraph_distances(&graph, NULL, &mat, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + ); + BENCH(" 3 Kautz(4, 5) average_path_length undirected", + igraph_average_path_length(&graph, NULL, &avglen, NULL, IGRAPH_UNDIRECTED, 1); + ); + BENCH(" 4 Kautz(4, 5) distances undirected", + igraph_distances(&graph, NULL, &mat, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + ); + + igraph_destroy(&graph); + + { + igraph_vector_int_t dims; + igraph_vector_bool_t periodic; + igraph_vector_int_init_int(&dims, 3, 15, 15, 15); + igraph_vector_bool_init_int(&periodic, 3, 1, 1, 1); + igraph_square_lattice(&graph, &dims, 1, IGRAPH_UNDIRECTED, 0, &periodic); + igraph_vector_int_destroy(&dims); + igraph_vector_bool_destroy(&periodic); + igraph_rewire(&graph, 100, IGRAPH_SIMPLE_SW, NULL); + igraph_matrix_resize(&mat, igraph_vcount(&graph), igraph_vcount(&graph)); /* preallocate matrix */ + } + + BENCH(" 5 Rewired 15x15x15 lattice average_path_length", + igraph_average_path_length(&graph, NULL, &avglen, NULL, IGRAPH_UNDIRECTED, 1); + ); + BENCH(" 6 Rewired 15x15x15 lattice distances undirected", + igraph_distances(&graph, NULL, &mat, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + ); + + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 10000, 12000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + igraph_matrix_resize(&mat, igraph_vcount(&graph), igraph_vcount(&graph)); /* preallocate matrix */ + + BENCH(" 7 Erdos-Renyi n=10000 m=12000 average_path_length directed", + igraph_average_path_length(&graph, NULL, &avglen, NULL, IGRAPH_DIRECTED, 1); + ); + BENCH(" 8 Erdos-Renyi n=10000 m=12000 distances directed", + igraph_distances(&graph, NULL, &mat, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + ); + + /* The undirected computation will be much slower on this graph, as the largest weakly connected + * component is much larger. */ + BENCH(" 9 Erdos-Renyi n=10000 m=12000 average_path_length undirected", + igraph_average_path_length(&graph, NULL, &avglen, NULL, IGRAPH_UNDIRECTED, 1); + ); + BENCH("10 Erdos-Renyi n=10000 m=12000 distances undirected", + igraph_distances(&graph, NULL, &mat, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + ); + + igraph_destroy(&graph); + igraph_matrix_destroy(&mat); + + return 0; +} diff --git a/tests/benchmarks/igraph_betweenness.c b/tests/benchmarks/igraph_betweenness.c new file mode 100644 index 0000000..debf7b6 --- /dev/null +++ b/tests/benchmarks/igraph_betweenness.c @@ -0,0 +1,84 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t graph; + igraph_vector_t betweenness; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&betweenness, 0); + + /* Kautz and De Bruijn graphs are connected, therefore there should not be a dramatic difference + * in the performance of the directed and undirected calculations. */ + + igraph_kautz(&graph, 4, 5); + BENCH(" 1 Betweenness, Kautz(4,5), directed", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false)); + BENCH(" 2 Betweenness, Kautz(4,5), undirected", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false)); + igraph_destroy(&graph); + + igraph_de_bruijn(&graph, 6, 5); + BENCH(" 3 Betweenness, DeBruijn(6,5), directed", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false)); + igraph_destroy(&graph); + + { + igraph_vector_int_t dims; + + igraph_vector_int_init_int_end(&dims, -1, 8, 8, 8, 8, -1); + igraph_square_lattice(&graph, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + BENCH(" 4 Betweenness, Grid(8,8,8,8), undirected", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false)); + igraph_destroy(&graph); + igraph_vector_int_destroy(&dims); + + igraph_vector_int_init_int_end(&dims, -1, 10, 10, 10, 10, -1); + igraph_square_lattice(&graph, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + BENCH(" 5 Betweenness, Grid(10,10,10,10), cutoff 5", + igraph_betweenness_cutoff(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false, 5)); + BENCH(" 6 Betweenness, Grid(10,10,10,10), cutoff 8", + igraph_betweenness_cutoff(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false, 8)); + igraph_destroy(&graph); + igraph_vector_int_destroy(&dims); + } + + igraph_barabasi_game(&graph, 8000, 1, 1, NULL, 1, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + BENCH(" 7 Betweenness, Barabasi n=8000 m=1, undirected", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false)); + BENCH(" 8 Betweenness, Barabasi n=8000 m=1, undirected, cutoff 6", + igraph_betweenness_cutoff(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false, 6)); + igraph_destroy(&graph); + + igraph_barabasi_game(&graph, 30000, 1, 5, NULL, 1, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + BENCH(" 9 Betweenness, Barabasi n=30000 m=5, directed", + igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false)); + BENCH("10 Betweenness, Barabasi n=30000 m=5, directed, cutoff 5", + igraph_betweenness_cutoff(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false, 5)); + igraph_destroy(&graph); + + igraph_vector_destroy(&betweenness); + + return 0; +} diff --git a/tests/benchmarks/igraph_betweenness_weighted.c b/tests/benchmarks/igraph_betweenness_weighted.c new file mode 100644 index 0000000..03b2cfa --- /dev/null +++ b/tests/benchmarks/igraph_betweenness_weighted.c @@ -0,0 +1,164 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void rand_weight_vec(igraph_vector_t *vec, const igraph_t *graph) { + const igraph_int_t n = igraph_ecount(graph); + igraph_vector_resize(vec, n); + for (igraph_int_t i=0; i < n; ++i) { + VECTOR(*vec)[i] = RNG_UNIF(1, 10); + } +} + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +int main(void) { + igraph_t graph; + igraph_vector_t betweenness, weight; + + /* These betweenness benchmarks are identical to the weighted closeness ones. */ + + /* This benchmark compares directed/undirected and weighted/unweighted calculations + * on the same graphs. */ + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&betweenness, 0); + igraph_vector_init(&weight, 0); + + igraph_kautz(&graph, 4, 3); + + /* Kautz and De Bruijn graphs are connected, therefore there should not be a dramatic difference + * in the performance of the directed and undirected calculations. */ + +#define NAME "Kautz(4,3)" +#define REP 100 + + BENCH(" 1 Betweenness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH(" 2 Betweenness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH(" 3 Betweenness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH(" 4 Betweenness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + +#define NAME "DeBruijn(5,5)" +#define REP 1 + + igraph_de_bruijn(&graph, 5, 5); + + BENCH(" 5 Betweenness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH(" 6 Betweenness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH(" 7 Betweenness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH(" 8 Betweenness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + + /* Choose the parameters of the Erdős-Rényi model so that the graph will have a large strongly connected + * giant component. With 3000 vertices and 10000 edges, it is likely to contain over 90% of vertices. + * If the graph does not have a giant component, then the directed betweenness calculation will be very + * fast, and not directly comparable to the undirected one. */ + +#define NAME "GNM(3000,10000)" +#define REP 1 + + igraph_erdos_renyi_game_gnm(&graph, 3000, 10000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + BENCH(" 9 Betweenness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH("10 Betweenness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH("11 Betweenness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH("12 Betweenness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + + /* Benchmark a much denser Erdős-Rényi graph as well. */ + +#define NAME "GNM(3000,30000)" +#define REP 1 + + igraph_erdos_renyi_game_gnm(&graph, 3000, 30000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + BENCH("13 Betweenness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH("14 Betweenness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, NULL, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH("15 Betweenness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_DIRECTED, false), REP) + ); + BENCH("16 Betweenness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_betweenness(&graph, &weight, &betweenness, igraph_vss_all(), IGRAPH_UNDIRECTED, false), REP) + ); + + igraph_destroy(&graph); + + igraph_vector_destroy(&weight); + igraph_vector_destroy(&betweenness); + + return 0; +} diff --git a/tests/benchmarks/igraph_cliques.c b/tests/benchmarks/igraph_cliques.c new file mode 100644 index 0000000..43a6ec5 --- /dev/null +++ b/tests/benchmarks/igraph_cliques.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t g; + igraph_vector_int_list_t res; + igraph_int_t res_int; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + + igraph_vector_int_list_init(&res, 0); + + igraph_erdos_renyi_game_gnm(&g, 100, 3000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 1 Cliques in random graph with 100 vertices and 3000 edges", + igraph_cliques(&g, &res, /* min_size= */ 0, /* max_size= */ 0, -1); + ); + igraph_destroy(&g); + igraph_vector_int_list_clear(&res); + + igraph_erdos_renyi_game_gnm(&g, 200, 10000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 2 Cliques in random graph with 200 vertices and 10000 edges, up to size 5", + igraph_cliques(&g, &res, /* min_size= */ 0, /* max_size= */ 5, -1); + ); + igraph_vector_int_list_clear(&res); + BENCH(" 3 Clique number of the same graph with 200 vertices and 10000 edges", + igraph_clique_number(&g, &res_int); + ); + igraph_vector_int_list_clear(&res); + igraph_destroy(&g); + + igraph_vector_int_list_destroy(&res); + + return 0; +} diff --git a/tests/benchmarks/igraph_closeness_weighted.c b/tests/benchmarks/igraph_closeness_weighted.c new file mode 100644 index 0000000..c842f69 --- /dev/null +++ b/tests/benchmarks/igraph_closeness_weighted.c @@ -0,0 +1,164 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void rand_weight_vec(igraph_vector_t *vec, const igraph_t *graph) { + const igraph_int_t n = igraph_ecount(graph); + igraph_vector_resize(vec, n); + for (igraph_int_t i=0; i < n; ++i) { + VECTOR(*vec)[i] = RNG_UNIF(1, 10); + } +} + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +int main(void) { + igraph_t graph; + igraph_vector_t closeness, weight; + + /* These closeness benchmarks are identical to the weighted betweenness ones. */ + + /* This benchmark compares directed/undirected and weighted/unweighted calculations + * on the same graphs. */ + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&closeness, 0); + igraph_vector_init(&weight, 0); + + igraph_kautz(&graph, 4, 3); + + /* Kautz and De Bruijn graphs are connected, therefore there should not be a dramatic difference + * in the performance of the directed and undirected calculations. */ + +#define NAME "Kautz(4,3)" +#define REP 100 + + BENCH(" 1 Closeness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, NULL, 1), REP) + ); + BENCH(" 2 Closeness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, NULL, 1), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH(" 3 Closeness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, &weight, 1), REP) + ); + BENCH(" 4 Closeness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, &weight, 1), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + +#define NAME "DeBruijn(5,5)" +#define REP 1 + + igraph_de_bruijn(&graph, 5, 5); + + BENCH(" 5 Closeness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, NULL, 1), REP) + ); + BENCH(" 6 Closeness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, NULL, 1), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH(" 7 Closeness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, &weight, 1), REP) + ); + BENCH(" 8 Closeness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, &weight, 1), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + + /* Choose the parameters of the Erdős-Rényi model so that the graph will have a large strongly connected + * giant component. With 3000 vertices and 10000 edges, it is likely to contain over 90% of vertices. + * If the graph does not have a giant component, then the directed betweenness calculation will be very + * fast, and not directly comparable to the undirected one. */ + +#define NAME "GNM(3000,10000)" +#define REP 1 + + igraph_erdos_renyi_game_gnm(&graph, 3000, 10000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + BENCH(" 9 Closeness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, NULL, 1), REP) + ); + BENCH("10 Closeness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, NULL, 1), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH("11 Closeness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, &weight, 1), REP) + ); + BENCH("12 Closeness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, &weight, 1), REP) + ); + + igraph_destroy(&graph); + +#undef NAME +#undef REP + + /* Benchmark a much denser Erdős-Rényi graph as well. */ + +#define NAME "GNM(3000,30000)" +#define REP 1 + + igraph_erdos_renyi_game_gnm(&graph, 3000, 30000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + BENCH("13 Closeness, unweighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, NULL, 1), REP) + ); + BENCH("14 Closeness, unweighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, NULL, 1), REP) + ); + + rand_weight_vec(&weight, &graph); + + BENCH("15 Closeness, weighted, " NAME ", directed, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_OUT, &weight, 1), REP) + ); + BENCH("16 Closeness, weighted, " NAME ", undirected, " TOSTR(REP) "x", + REPEAT(igraph_closeness(&graph, &closeness, NULL, NULL, igraph_vss_all(), IGRAPH_ALL, &weight, 1), REP) + ); + + igraph_destroy(&graph); + + igraph_vector_destroy(&weight); + igraph_vector_destroy(&closeness); + + return 0; +} diff --git a/tests/benchmarks/igraph_decompose.c b/tests/benchmarks/igraph_decompose.c new file mode 100644 index 0000000..ebb82f3 --- /dev/null +++ b/tests/benchmarks/igraph_decompose.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t g; + igraph_graph_list_t res; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + + igraph_graph_list_init(&res, 0); + + igraph_empty(&g, 1000, IGRAPH_UNDIRECTED); + BENCH(" 1 Decompose graph with 1000 isolated vertices", + igraph_decompose(&g, &res, IGRAPH_WEAK, -1, -1); + ); + igraph_destroy(&g); + igraph_graph_list_clear(&res); + + igraph_empty(&g, 10000, IGRAPH_UNDIRECTED); + BENCH(" 2 Decompose graph with 10000 isolated vertices", + igraph_decompose(&g, &res, IGRAPH_WEAK, -1, -1); + ); + igraph_destroy(&g); + igraph_graph_list_clear(&res); + + igraph_empty(&g, 100000, IGRAPH_UNDIRECTED); + BENCH(" 3 Decompose graph with 100000 isolated vertices", + igraph_decompose(&g, &res, IGRAPH_WEAK, -1, -1); + ); + igraph_destroy(&g); + igraph_graph_list_clear(&res); + + igraph_graph_list_destroy(&res); + + return 0; +} diff --git a/tests/benchmarks/igraph_degree.c b/tests/benchmarks/igraph_degree.c new file mode 100644 index 0000000..9ae2dc2 --- /dev/null +++ b/tests/benchmarks/igraph_degree.c @@ -0,0 +1,142 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +/* Calls igraph_degree_1() separately for each vertex. */ +void deg1(const igraph_t *g, igraph_vector_int_t *res, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t n = igraph_vcount(g); + igraph_vector_int_resize(res, n); + for (igraph_int_t i=0; i < n; i++) { + igraph_degree_1(g, &VECTOR(*res)[i], i, mode, loops); + } +} + +/* Calls igraph_degree() separately for each vertex, pre-allocates work vector. */ +void degv(const igraph_t *g, igraph_vector_int_t *res, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t n = igraph_vcount(g); + igraph_vector_int_t work; + + igraph_vector_int_resize(res, n); + igraph_vector_int_init(&work, 1); + for (igraph_int_t i=0; i < n; i++) { + igraph_degree(g, &work, igraph_vss_1(i), mode, loops); + } + igraph_vector_int_destroy(&work); +} + +/* Calls igraph_degree() separately for each vertex, allocates a new work vector each time. */ +void degv2(const igraph_t *g, igraph_vector_int_t *res, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t n = igraph_vcount(g); + + for (igraph_int_t i=0; i < n; i++) { + igraph_vector_int_t work; + igraph_vector_int_init(&work, 1); + igraph_degree(g, &work, igraph_vss_1(i), mode, loops); + igraph_vector_int_destroy(&work); + } +} + +int main(void) { + igraph_t g; + igraph_vector_int_t degs; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_barabasi_game(&g, 100000, 1, 10, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + igraph_vector_int_init(°s, igraph_vcount(&g)); + +#define REP 1000 + + printf("Count loops twice, O(1) per degree, fast methods only.\n"); + + BENCH(" 1 igraph_degree(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(igraph_degree(&g, °s, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 2 deg1(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(deg1(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + +#undef REP + +#define REP 100 + + printf("\nCount loops twice, O(1) per degree.\n"); + + BENCH(" 1 igraph_degree(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(igraph_degree(&g, °s, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 2 deg1(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(deg1(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 3 degv(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 4 degv2(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv2(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + +#undef REP + +#define REP 100 + + printf("\nCount loops twice, O(1) per degree.\n"); + + BENCH(" 1 igraph_degree(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(igraph_degree(&g, °s, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 2 deg1(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(deg1(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 3 degv(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + BENCH(" 4 degv2(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv2(&g, °s, IGRAPH_ALL, IGRAPH_LOOPS), REP); + ); + +#undef REP + +#define REP 100 + + printf("\nDo not count loops, O(d) per degree.\n"); + + BENCH(" 1 igraph_degree(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(igraph_degree(&g, °s, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS), REP); + ); + BENCH(" 2 deg1(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(deg1(&g, °s, IGRAPH_ALL, IGRAPH_NO_LOOPS), REP); + ); + BENCH(" 3 degv(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv(&g, °s, IGRAPH_ALL, IGRAPH_NO_LOOPS), REP); + ); + BENCH(" 4 degv2(), preferential attachment n=100000, m=10, " TOSTR(REP) "x", + REPEAT(degv2(&g, °s, IGRAPH_ALL, IGRAPH_NO_LOOPS), REP); + ); + + igraph_vector_int_destroy(°s); + igraph_destroy(&g); + + return 0; +} diff --git a/tests/benchmarks/igraph_degree_sequence_game.c b/tests/benchmarks/igraph_degree_sequence_game.c new file mode 100644 index 0000000..8772cbc --- /dev/null +++ b/tests/benchmarks/igraph_degree_sequence_game.c @@ -0,0 +1,120 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t g, template; + igraph_vector_int_t degrees, outdeg, indeg; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + /* This benchmark indirectly tests the performance of igraph_set_t at the + * moment because igraph_degree_sequence_game() (especially the + * IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE mode) heavily relies on sets. + * This might change in future versions so the name of the benchmark + * reflects the name of the function that is tested directly */ + + igraph_vector_int_init(°rees, 1000); + igraph_vector_int_fill(°rees, 7); + BENCH(" 1 Degseq of undirected k-regular, N=1000, k=7, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, °rees, /* indeg = */ NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_vector_int_destroy(°rees); + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_int_init(°rees, 0); + igraph_barabasi_game(&template, 1000, /* power = */ 1, /* m = */ 1, + /* outseq = */ NULL, /* outpref = */ true, /* A = */ 0, + IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, /* start_from = */ NULL + ); + igraph_degree(&template, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + BENCH(" 2 Degseq of undirected BA, N=1000, m=1, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, °rees, /* indeg = */ NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_destroy(&template); + igraph_vector_int_destroy(°rees); + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_int_init(°rees, 0); + igraph_erdos_renyi_game_gnm(&template, 200, 600, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_degree(&template, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + BENCH(" 3 Degseq of undirected G(n,m), N=150, m=450, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, °rees, /* indeg = */ NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_destroy(&template); + igraph_vector_int_destroy(°rees); + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_int_init(°rees, 0); + igraph_grg_game(&template, 10000, 0.013, /* torus = */ false, /* x = */ NULL, /* y = */ NULL); + igraph_degree(&template, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + BENCH(" 4 Degseq of GRG, N=10000, r=0.013, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, °rees, /* indeg = */ NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_destroy(&template); + igraph_vector_int_destroy(°rees); + + igraph_vector_int_init(°rees, 1000); + igraph_vector_int_fill(°rees, 5); + BENCH(" 5 Degseq of directed k-regular, N=1000, k=5, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, °rees, °rees, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_vector_int_destroy(°rees); + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_int_init(&outdeg, 0); + igraph_vector_int_init(&indeg, 0); + igraph_barabasi_game(&template, 5000, /* power = */ 1, /* m = */ 2, + /* outseq = */ NULL, /* outpref = */ true, /* A = */ 0, + IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, /* start_from = */ NULL + ); + igraph_degree(&template, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_degree(&template, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + BENCH(" 6 Degseq of directed BA, N=500, m=2, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_destroy(&template); + igraph_vector_int_destroy(&indeg); + igraph_vector_int_destroy(&outdeg); + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_int_init(&outdeg, 0); + igraph_vector_int_init(&indeg, 0); + igraph_erdos_renyi_game_gnm(&template, 15000, 45000, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_degree(&template, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_degree(&template, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + BENCH(" 7 Degseq of directed G(n,m), N=15000, m=45000, CONFIGURATION_SIMPLE", + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE) + ); + igraph_destroy(&g); + igraph_destroy(&template); + igraph_vector_int_destroy(&indeg); + igraph_vector_int_destroy(&outdeg); + + return 0; +} diff --git a/tests/benchmarks/igraph_delaunay_graph.c b/tests/benchmarks/igraph_delaunay_graph.c new file mode 100644 index 0000000..980cbdb --- /dev/null +++ b/tests/benchmarks/igraph_delaunay_graph.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include + +#include "bench.h" + +void bench_delaunay(const char *message, igraph_int_t point_count, igraph_int_t dimensions) { + igraph_matrix_t points; + igraph_t g; + char msg[200]; + + igraph_matrix_init(&points, point_count, dimensions); + + for (igraph_int_t point = 0; point < point_count; point++) { + for (igraph_int_t dim = 0; dim < dimensions; dim++) { + MATRIX(points, point, dim) = RNG_UNIF01(); + } + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s Delaunay triangulation in %dD, n=%6" IGRAPH_PRId, + message, (int) dimensions, point_count); + + BENCH(msg, igraph_delaunay_graph(&g, &points)); + + igraph_destroy(&g); + igraph_matrix_destroy(&points); +} + +int main(void) { + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 20250814); + + bench_delaunay(" 1", 1000000, 1); + bench_delaunay(" 2", 100000, 2); + bench_delaunay(" 3", 10000, 2); + bench_delaunay(" 4", 1000, 2); + bench_delaunay(" 5", 100, 2); + bench_delaunay(" 6", 10000, 3); + bench_delaunay(" 7", 1000, 3); + bench_delaunay(" 8", 100, 3); + bench_delaunay(" 9", 2000, 4); + bench_delaunay("10", 500, 5); + bench_delaunay("11", 200, 6); + + return 0; +} diff --git a/tests/benchmarks/igraph_distances.c b/tests/benchmarks/igraph_distances.c new file mode 100644 index 0000000..050b0d8 --- /dev/null +++ b/tests/benchmarks/igraph_distances.c @@ -0,0 +1,168 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void bench_gnp(igraph_int_t n, igraph_real_t p, igraph_bool_t directed, igraph_real_t negloop, int rep) { + igraph_t g; + igraph_matrix_t res; + igraph_vector_t weights; + char msg[200]; + + igraph_matrix_init(&res, 0, 0); + igraph_vector_init(&weights, 0); + + /* IGRAPH_NO_LOOPS, as loops are not allowed with negative weights. */ + igraph_erdos_renyi_game_gnp(&g, n, p, directed, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_matrix_resize(&res, igraph_vcount(&g), igraph_vcount(&g)); + igraph_vector_resize(&weights, igraph_ecount(&g)); + + for (igraph_int_t i=0; i < igraph_ecount(&g); i++) { + VECTOR(weights)[i] = RNG_EXP(1); + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 1 n=%d, p=%g, %s, Unweighted, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances(&g, NULL, &res, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT), rep); + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 2 n=%d, p=%g, %s, Dijkstra, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_dijkstra(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 3 n=%d, p=%g, %s, Unweighted Floyd-Warshall, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 4 n=%d, p=%g, %s, Unweighted Floyd-Warshall-tree-speedup, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 5 n=%d, p=%g, %s, Floyd-Warshall, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 6 n=%d, p=%g, %s, Floyd-Warshall-tree-speedup, %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), rep) + ); + + /* Negative weights, only for directed graphs. */ + + if (! directed) { + return; + } + + /* Add a small number of negative weights, rely on luck, as well as tuning 'negloop', + * to avoid negative loops. */ + for (igraph_int_t i=0; i < trunc(negloop * igraph_ecount(&g)); i++) { + /* For reproducibility, do not write two RNG_...() calls within the same statement, + * as the C language does not guarantee any evaluation order between them. */ + igraph_real_t w = RNG_UNIF(-negloop, 0); + VECTOR(weights)[RNG_INTEGER(0, igraph_ecount(&g)-1)] = w; + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 7 n=%d, p=%g, %s, Bellman-Ford (negative), %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_bellman_ford(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 8 n=%d, p=%g, %s, Johnson (negative), %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_johnson(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + " 9 n=%d, p=%g, %s, Floyd-Warshall (negative), %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), rep) + ); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "10 n=%d, p=%g, %s, Floyd-Warshall-tree-speedup (negative), %dx", + (int) n, p, directed ? " directed" : "undirected", rep); + BENCH(msg, + REPEAT(igraph_distances_floyd_warshall(&g, &res, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), rep) + ); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + /* + bench_gnp(100, 0.5, IGRAPH_DIRECTED, 0.01, 200); printf("\n"); + bench_gnp(100, 0.5, IGRAPH_UNDIRECTED, 0.01, 200); printf("\n"); + + bench_gnp(30, 0.5, IGRAPH_DIRECTED, 0.01, 2000); printf("\n"); + bench_gnp(30, 0.5, IGRAPH_UNDIRECTED, 0.01, 2000); printf("\n"); + + bench_gnp(100, 0.1, IGRAPH_DIRECTED, 0.01, 1000); printf("\n"); + bench_gnp(100, 0.1, IGRAPH_UNDIRECTED, 0.01, 1000); printf("\n"); + + bench_gnp(500, 0.1, IGRAPH_DIRECTED, 0.005, 10); printf("\n"); + bench_gnp(500, 0.1, IGRAPH_UNDIRECTED, 0.005, 10); printf("\n"); + + bench_gnp(1500, 0.02, IGRAPH_DIRECTED, 0.01, 1); printf("\n"); + bench_gnp(1500, 0.02, IGRAPH_UNDIRECTED, 0.01, 1); printf("\n"); + */ + + bench_gnp(1000, 0.1, IGRAPH_DIRECTED, 0.002, 1); printf("\n"); + + bench_gnp(300, 0.1, IGRAPH_DIRECTED, 0.005, 50); printf("\n"); + + bench_gnp(100, 0.1, IGRAPH_DIRECTED, 0.01, 500); printf("\n"); + + bench_gnp(50, 0.1, IGRAPH_DIRECTED, 0.01, 10000); printf("\n"); + + bench_gnp(30, 0.1, IGRAPH_DIRECTED, 0.01, 20000); printf("\n"); + + bench_gnp(20, 0.2, IGRAPH_DIRECTED, 0.01, 40000); printf("\n"); + + bench_gnp(10, 0.2, IGRAPH_DIRECTED, 0.01, 100000); printf("\n"); + + bench_gnp(1500, 0.01, IGRAPH_DIRECTED, 0.01, 1); printf("\n"); + + bench_gnp(100, 0.5, IGRAPH_DIRECTED, 0.01, 1000); printf("\n"); + + return 0; +} diff --git a/tests/benchmarks/igraph_ecc.c b/tests/benchmarks/igraph_ecc.c new file mode 100644 index 0000000..423a5e3 --- /dev/null +++ b/tests/benchmarks/igraph_ecc.c @@ -0,0 +1,175 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +#define BENCH_BLOCK() \ + BENCH(" 1 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=3, all, " TOSTR(REP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_all(IGRAPH_EDGEORDER_ID), 3, false, true), REP); \ + ); \ + BENCH(" 2 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=3, subset: all, " TOSTR(REP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_range(0, igraph_ecount(&g)), 3, false, true), REP); \ + ); \ + \ + igraph_random_sample(&eids, 0, igraph_ecount(&g)-1, SS); \ + \ + BENCH(" 3 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=3, subset: " TOSTR(SS) ", " TOSTR(SREP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_vector(&eids), 3, false, true), SREP); \ + ); \ + \ + BENCH(" 4 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=4, all, " TOSTR(REP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_all(IGRAPH_EDGEORDER_ID), 4, false, true), REP); \ + ); \ + \ + BENCH(" 4 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=4, subset: all, " TOSTR(REP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_range(0, igraph_ecount(&g)), 4, false, true), REP); \ + ); \ + BENCH(" 5 vc=" TOSTR(VCOUNT) ", ec=" TOSTR(ECOUNT) ", k=4, subset: " TOSTR(SS) ", " TOSTR(SREP) "x", \ + REPEAT(igraph_ecc(&g, &ecc, igraph_ess_vector(&eids), 4, false, true), SREP); \ + ); + +int main(void) { + igraph_t g; + igraph_vector_t ecc; + igraph_vector_int_t eids; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_vector_init(&ecc, 0); + igraph_vector_int_init(&eids, 0); + + printf("Erdos-Renyi GNM:\n\n"); + +#define VCOUNT 100 +#define ECOUNT 5000 +#define REP 100 +#define SS 100 +#define SREP 1000 + + igraph_erdos_renyi_game_gnm(&g, VCOUNT, ECOUNT, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&ecc, igraph_ecount(&g)); + + BENCH_BLOCK() + + igraph_destroy(&g); + +#undef VCOUNT +#undef ECOUNT +#undef REP +#undef SS +#undef SREP + + printf("\n"); + +#define VCOUNT 1000 +#define ECOUNT 5000 +#define REP 100 +#define SS 100 +#define SREP 1000 + + igraph_erdos_renyi_game_gnm(&g, VCOUNT, ECOUNT, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&ecc, igraph_ecount(&g)); + + BENCH_BLOCK() + + igraph_destroy(&g); + +#undef VCOUNT +#undef ECOUNT +#undef REP +#undef SS +#undef SREP + + printf("\n"); + +#define VCOUNT 10000 +#define ECOUNT 10000 +#define REP 100 +#define SS 100 +#define SREP 1000 + + igraph_erdos_renyi_game_gnm(&g, VCOUNT, ECOUNT, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&ecc, igraph_ecount(&g)); + + BENCH_BLOCK() + + igraph_destroy(&g); + +#undef VCOUNT +#undef ECOUNT +#undef REP +#undef SS +#undef SREP + + printf("\n"); + +#define VCOUNT 10000 +#define ECOUNT 50000 +#define REP 100 +#define SS 100 +#define SREP 1000 + + igraph_erdos_renyi_game_gnm(&g, VCOUNT, ECOUNT, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&ecc, igraph_ecount(&g)); + + BENCH_BLOCK() + + igraph_destroy(&g); + +#undef VCOUNT +#undef ECOUNT +#undef REP +#undef SS +#undef SREP + + printf("\n"); + +#define VCOUNT 10000 +#define ECOUNT 50000 +#define REP 100 +#define SS 100 +#define SREP 1000 + + printf("Barabasi:\n\n"); + + igraph_barabasi_game(&g, + VCOUNT, 1, ECOUNT / VCOUNT, NULL, true, 0, + IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + igraph_vector_resize(&ecc, igraph_ecount(&g)); + + BENCH_BLOCK() + + igraph_destroy(&g); + +#undef VCOUNT +#undef ECOUNT +#undef REP +#undef SS +#undef SREP + + igraph_vector_int_destroy(&eids); + igraph_vector_destroy(&ecc); + + return 0; +} diff --git a/tests/benchmarks/igraph_induced_subgraph.c b/tests/benchmarks/igraph_induced_subgraph.c new file mode 100644 index 0000000..df19f29 --- /dev/null +++ b/tests/benchmarks/igraph_induced_subgraph.c @@ -0,0 +1,125 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +// Function to generate random vertex indices for subgraph extraction +void generate_random_vertices(igraph_vector_int_t *vertices, + igraph_int_t graph_size, + igraph_int_t subset_size) { + igraph_vector_int_init(vertices, 0); + igraph_vector_int_resize(vertices, subset_size); + + // Create a vector to track used vertices + igraph_vector_int_t used_vertices; + igraph_vector_int_init(&used_vertices, graph_size); + for (igraph_int_t i = 0; i < graph_size; i++) { + VECTOR(used_vertices)[i] = i; + } + + // Randomly select subset_size unique vertices + for (igraph_int_t i = 0; i < subset_size; i++) { + igraph_int_t index = RNG_INTEGER(0, graph_size - i - 1); + VECTOR(*vertices)[i] = VECTOR(used_vertices)[index]; + + // Remove the selected vertex from used_vertices + VECTOR(used_vertices)[index] = VECTOR(used_vertices)[graph_size - i - 1]; + } + + // Sort the vertices to simulate real-world scenarios + igraph_vector_int_sort(vertices); + + igraph_vector_int_destroy(&used_vertices); +} + +// Benchmark function +void bench_induced_subgraph(igraph_t *graph, igraph_vector_int_t *vertices, + igraph_subgraph_implementation_t impl) { + igraph_t subgraph; + igraph_vs_t vs; + + igraph_vs_vector(&vs, vertices); + igraph_induced_subgraph(graph, &subgraph, vs, impl); + igraph_vs_destroy(&vs); + igraph_destroy(&subgraph); +} + +void run_bench(int i, int n, int m, int subset_percentage) { + igraph_t graph; + igraph_vector_int_t vertices; + + // Calculate subset size based on percentage + igraph_int_t subset_size = (n * subset_percentage) / 100; + + // Prepare random graph and vertices + igraph_erdos_renyi_game_gnm(&graph, n, m, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + generate_random_vertices(&vertices, n, subset_size); + + char msg[255]; + int rep = 300000000 / (n * subset_percentage); + + // Benchmark method 1 (COPY_AND_DELETE) + snprintf(msg, sizeof(msg), + "Method 1 (COPY_AND_DELETE): n=%5d, subset=%3d%%, %dx", n, + subset_percentage, rep); + BENCH(msg, REPEAT(bench_induced_subgraph(&graph, &vertices, + IGRAPH_SUBGRAPH_COPY_AND_DELETE), + rep)); + + // Benchmark method 2 (CREATE_FROM_SCRATCH) + snprintf(msg, sizeof(msg), + "Method 2 (CREATE_FROM_SCRATCH): n=%5d, subset=%3d%%, %dx", n, + subset_percentage, rep); + BENCH(msg, REPEAT(bench_induced_subgraph(&graph, &vertices, + IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH), + rep)); + + // Cleanup + igraph_vector_int_destroy(&vertices); + igraph_destroy(&graph); +} + +int main(void) { + int i = 0; + + // Initialize random number generator + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + +// Macro to run benchmarks for different graph sizes and subset percentages +#define BENCHSET(n, m) \ + run_bench(++i, n, m, 20); /* 20% subset */ \ + run_bench(++i, n, m, 25); /* 25% subset */ \ + run_bench(++i, n, m, 30); /* 30% subset */ \ + run_bench(++i, n, m, 35); /* 35% subset */ \ + run_bench(++i, n, m, 40); /* 40% subset */ \ + run_bench(++i, n, m, 45); /* 45% subset */ \ + run_bench(++i, n, m, 50); /* 50% subset */ \ + run_bench(++i, n, m, 55); /* 55% subset */ \ + printf("\n"); + + // Benchmark different graph sizes + BENCHSET(100, 500); + BENCHSET(1000, 5000); + BENCHSET(10000, 50000); + BENCHSET(100000, 500000); + + return 0; +} diff --git a/tests/benchmarks/igraph_induced_subgraph_edges.c b/tests/benchmarks/igraph_induced_subgraph_edges.c new file mode 100644 index 0000000..a2cd53c --- /dev/null +++ b/tests/benchmarks/igraph_induced_subgraph_edges.c @@ -0,0 +1,100 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) +#define BARABASI_GRAPH_VERTEX_COUNT 100000 + +void unit_test_subgraph_edges( + const igraph_t *graph, igraph_vector_int_t *vertices, + igraph_int_t induced_subgraph_vertice_count, igraph_int_t benchmark_count, + const char* bench_message +) { + igraph_vector_int_t edges; + igraph_vector_int_init(&edges, 0); + + IGRAPH_UNUSED(vertices); + + BENCH(bench_message, + REPEAT( + igraph_induced_subgraph_edges(graph, igraph_vss_range(0, induced_subgraph_vertice_count), &edges), + benchmark_count + ) + ); + + igraph_vector_int_destroy(&edges); +} + +void bench_induced_subgraph_edges(void) { + igraph_t g; + igraph_vector_int_t vertices; + + igraph_vector_int_init_range(&vertices, 0, BARABASI_GRAPH_VERTEX_COUNT); + igraph_vector_int_shuffle(&vertices); + + igraph_barabasi_game(&g, BARABASI_GRAPH_VERTEX_COUNT, 1, 100, NULL, true, 0, + IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + + printf("induced_subgraph_edges() for BA graph\n"); + +#define REP 10 + +#define INDUCED_SUBGRAPH_VERTEX_COUNT 100 + unit_test_subgraph_edges(&g, &vertices, INDUCED_SUBGRAPH_VERTEX_COUNT, REP, + "n=" TOSTR(BARABASI_GRAPH_VERTEX_COUNT) ", " + "m=100, " + "e=" TOSTR(INDUCED_SUBGRAPH_VERTEX_COUNT) ", " + TOSTR(REP) " x" + ); +#undef INDUCED_SUBGRAPH_VERTEX_COUNT + +#define INDUCED_SUBGRAPH_VERTEX_COUNT 1000 + unit_test_subgraph_edges(&g, &vertices, INDUCED_SUBGRAPH_VERTEX_COUNT, REP, + "n=" TOSTR(BARABASI_GRAPH_VERTEX_COUNT) ", " + "m=100, " + "e=" TOSTR(INDUCED_SUBGRAPH_VERTEX_COUNT) ", " + TOSTR(REP) " x" + ); +#undef INDUCED_SUBGRAPH_VERTEX_COUNT + +#define INDUCED_SUBGRAPH_VERTEX_COUNT 10000 + unit_test_subgraph_edges(&g, &vertices, INDUCED_SUBGRAPH_VERTEX_COUNT, REP, + "n=" TOSTR(BARABASI_GRAPH_VERTEX_COUNT) ", " + "m=100, " + "e=" TOSTR(INDUCED_SUBGRAPH_VERTEX_COUNT) ", " + TOSTR(REP) " x" + ); +#undef INDUCED_SUBGRAPH_VERTEX_COUNT + +#undef REP + + igraph_destroy(&g); + igraph_vector_int_destroy(&vertices); +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + bench_induced_subgraph_edges(); + + return 0; +} diff --git a/tests/benchmarks/igraph_layout_umap.c b/tests/benchmarks/igraph_layout_umap.c new file mode 100644 index 0000000..3d36af3 --- /dev/null +++ b/tests/benchmarks/igraph_layout_umap.c @@ -0,0 +1,85 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +int main(void) { + igraph_t graph; + igraph_vector_t distances; + igraph_matrix_t layout; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + + igraph_matrix_init(&layout, 0, 0); + + + igraph_small(&graph, 12, IGRAPH_UNDIRECTED, + 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, + 3,4, 4,5, 5,6, + 6,7, 7,8, 6,8, 7,9, 6,9, 8,9, 7,10, 8,10, 9,10, 10,11, 9,11, 8,11, 7,11, + -1); + igraph_vector_init_real(&distances, + igraph_ecount(&graph), + 0.1, 0.09, 0.12, 0.09, 0.1, 0.1, + 0.9, 0.9, 0.9, + 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.08, 0.05, 0.1, 0.08, 0.12, 0.09, 0.11 + ); + +#define EPOCHS 5000 +#define REP 40 + + BENCH("Small graph, epochs: " TOSTR(EPOCHS) ", repetitions: " TOSTR(REP), REPEAT(igraph_layout_umap(&graph, &layout, 0, &distances, 0.01, EPOCHS, 0), REP); + ); + +#undef EPOCHS +#undef REP +#define EPOCHS 500 +#define REP 400 + + BENCH("Small graph, epochs: " TOSTR(EPOCHS) ", repetitions: " TOSTR(REP), REPEAT(igraph_layout_umap(&graph, &layout, 0, &distances, 0.01, EPOCHS, 0) , REP); + ); + +#undef EPOCHS +#undef REP +#define EPOCHS 60 +#define REP 1 +#define VCOUNT 10000 +#define DENS 0.001 + + igraph_destroy(&graph); + igraph_erdos_renyi_game_gnp(&graph, VCOUNT, DENS, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&distances, igraph_ecount(&graph)); + for (igraph_int_t i=0; i < igraph_ecount(&graph); i++) { + VECTOR(distances)[i] = RNG_UNIF(0.05, 0.15); + } + + BENCH("Larger graph, epochs: " TOSTR(EPOCHS) ", repetitions: " TOSTR(REP), REPEAT(igraph_layout_umap(&graph, &layout, 0, &distances, 0.01, EPOCHS, 0) , REP); + ); + + + igraph_matrix_destroy(&layout); + igraph_destroy(&graph); + igraph_vector_destroy(&distances); + return 0; +} diff --git a/tests/benchmarks/igraph_matrix_transpose.c b/tests/benchmarks/igraph_matrix_transpose.c new file mode 100644 index 0000000..2d422e9 --- /dev/null +++ b/tests/benchmarks/igraph_matrix_transpose.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +void bench(int m, int n, int rep) { + igraph_matrix_t mat; + + igraph_matrix_init(&mat, m, n); + for (igraph_int_t j=0; j < n; j++) { + for (igraph_int_t i=0; i < m; i++) { + MATRIX(mat, i, j) = RNG_UNIF(-1, 1); + } + } + + char name[200]; + sprintf(name, "Transpose %5d x %5d, %dx", m, n, rep); + BENCH( name, REPEAT(igraph_matrix_transpose(&mat), rep) ); + + igraph_matrix_destroy(&mat); +} + +int main(void) { + BENCH_INIT(); + + bench(30, 30, 100000); + bench(100, 100, 10000); + bench(1000, 1000, 100); + bench(1024, 1024, 100); /* naive implementation has bad cache behaviour with power of 2 sizes */ + bench(1023, 1025, 100); /* non-symmetric */ + bench(3000, 3000, 10); + /* skinny non-symmetric: */ + bench(100, 10000, 100); + bench(10000, 100, 100); + + return 0; +} diff --git a/tests/benchmarks/igraph_maximal_cliques.c b/tests/benchmarks/igraph_maximal_cliques.c new file mode 100644 index 0000000..28649af --- /dev/null +++ b/tests/benchmarks/igraph_maximal_cliques.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2013-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + + igraph_t g; + igraph_int_t toremovev[] = { + 2609, 2098, 14517, 7540, 19560, 8855, + 5939, 14947, 441, 16976, 19642, 4188, + 15447, 11837, 2333, 7309, 18539, 14099, + 14264, 9240 + }; + const igraph_vector_int_t toremove = + igraph_vector_int_view(toremovev, sizeof(toremovev) / sizeof(toremovev[0]));; + igraph_vector_int_list_t res; + + BENCH_INIT(); + + igraph_full(&g, 200, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_delete_edges(&g, igraph_ess_vector(&toremove)); + + igraph_vector_int_list_init(&res, 0); + + BENCH(" 1 Maximal cliques of almost complete graph", + igraph_maximal_cliques(&g, &res, + /* min_size= */ IGRAPH_UNLIMITED, /* max_size= */ IGRAPH_UNLIMITED, + /* max_results= */ IGRAPH_UNLIMITED); + ); + + igraph_destroy(&g); + + igraph_vector_int_list_destroy(&res); + + return 0; +} diff --git a/tests/benchmarks/igraph_nearest_neighbor_graph.c b/tests/benchmarks/igraph_nearest_neighbor_graph.c new file mode 100644 index 0000000..a0e57cc --- /dev/null +++ b/tests/benchmarks/igraph_nearest_neighbor_graph.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void bench_pointcloud(const char *message, igraph_metric_t metric, igraph_int_t point_count, igraph_int_t dimensions, igraph_int_t neighbors, igraph_real_t cutoff) { + igraph_matrix_t points; + igraph_t g; + char msg[200]; + const char *metricname; + + igraph_matrix_init(&points, point_count, dimensions); + + for (igraph_int_t point = 0; point < point_count; point++) { + for (igraph_int_t dim = 0; dim < dimensions; dim++) { + MATRIX(points, point, dim) = RNG_UNIF01(); + } + } + + switch (metric) { + case IGRAPH_METRIC_L2: metricname = "L2"; break; + case IGRAPH_METRIC_L1: metricname = "L1"; break; + } + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s RKNN with %s, %dD, n=%6" IGRAPH_PRId ", k=%2d, r=%g", + message, metricname, (int) dimensions, point_count, (int) neighbors, cutoff); + + BENCH(msg, igraph_nearest_neighbor_graph(&g, &points, metric, neighbors, cutoff, false)); + + igraph_destroy(&g); + igraph_matrix_destroy(&points); +} + +int main(void) { + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 20250728); + + bench_pointcloud(" 1", IGRAPH_METRIC_L2, 100000, 1, 10, 0.1); + bench_pointcloud(" 2", IGRAPH_METRIC_L2, 100000, 2, 10, 0.1); + bench_pointcloud(" 3", IGRAPH_METRIC_L2, 100000, 3, 10, 0.1); + bench_pointcloud(" 4", IGRAPH_METRIC_L2, 100000, 4, 10, 0.1); + + bench_pointcloud(" 5", IGRAPH_METRIC_L2, 10000, 1, -1, 0.001); + bench_pointcloud(" 6", IGRAPH_METRIC_L2, 10000, 2, -1, 0.01); + bench_pointcloud(" 7", IGRAPH_METRIC_L2, 10000, 3, -1, 0.01); + bench_pointcloud(" 8", IGRAPH_METRIC_L2, 10000, 4, -1, 0.1); + + bench_pointcloud(" 9", IGRAPH_METRIC_L2, 1000, 1, -1, -1); + bench_pointcloud("10", IGRAPH_METRIC_L2, 1000, 2, -1, -1); + bench_pointcloud("11", IGRAPH_METRIC_L2, 1000, 3, -1, -1); + bench_pointcloud("12", IGRAPH_METRIC_L2, 1000, 4, -1, -1); + + bench_pointcloud("13", IGRAPH_METRIC_L2, 100000, 1, 10, -1); + bench_pointcloud("14", IGRAPH_METRIC_L2, 100000, 2, 10, -1); + bench_pointcloud("15", IGRAPH_METRIC_L2, 100000, 3, 10, -1); + bench_pointcloud("16", IGRAPH_METRIC_L2, 100000, 4, 10, -1); + + return 0; +} diff --git a/tests/benchmarks/igraph_neighborhood.c b/tests/benchmarks/igraph_neighborhood.c new file mode 100644 index 0000000..ef08d0f --- /dev/null +++ b/tests/benchmarks/igraph_neighborhood.c @@ -0,0 +1,473 @@ +/* + igraph library. + Copyright (C) 2021-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +typedef struct igraph_lazy_adjlist2_t { + const igraph_t *graph; + igraph_int_t length; + igraph_int_t data_length; + igraph_vector_int_t *adjs; + igraph_int_t *data; + igraph_int_t next_data; + igraph_neimode_t mode; + igraph_loops_t loops; + igraph_bool_t multiple; +} igraph_lazy_adjlist2_t; + +igraph_error_t igraph_lazy_adjlist2_init(const igraph_t *graph, + igraph_lazy_adjlist2_t *al, + igraph_neimode_t mode, + igraph_loops_t loops, + igraph_bool_t multiple) { + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannor create lazy adjacency list view", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + al->mode = mode; + al->loops = loops; + al->multiple = multiple; + al->graph = graph; + + al->length = igraph_vcount(graph); + al->data_length = igraph_ecount(graph) * 2; + al->adjs = IGRAPH_CALLOC(al->length, igraph_vector_int_t); + al->data = IGRAPH_CALLOC(al->data_length, igraph_int_t); + al->next_data = 0; + + if (al->adjs == NULL || al->data == NULL) { + IGRAPH_ERROR("Cannot create lazy adjacency list view", IGRAPH_ENOMEM); + } + + return IGRAPH_SUCCESS; +} + +void igraph_lazy_adjlist2_destroy(igraph_lazy_adjlist2_t *al) { + IGRAPH_FREE(al->adjs); + IGRAPH_FREE(al->data); +} + +igraph_vector_int_t *igraph_i_lazy_adjlist2_get_real( + igraph_lazy_adjlist2_t *al, igraph_int_t pno +) { + igraph_int_t no = pno; + igraph_error_t ret; + + if (al->adjs[no].stor_begin == NULL) { + al->adjs[no] = igraph_vector_int_view(al->data + al->next_data, al->data_length - al->next_data); + /* igraph_neighbors calls a resize. Since we're handling our own + * vectors, we can't have our stor_begin be realloced. A realloc should only + * happen when the vector is too small to handle the neighbors, which + * should never happen, because the stor_end is set to one past the + * end of the latest integer. + */ + ret = igraph_neighbors(al->graph, &al->adjs[no], no, al->mode, al->loops, al->multiple); + if (ret != IGRAPH_SUCCESS) { + igraph_error("", IGRAPH_FILE_BASENAME, __LINE__, ret); + return NULL; + } + al->next_data += igraph_vector_int_size(&(al->adjs[no])); + } + + return &al->adjs[no]; +} + +igraph_error_t igraph_neighborhood_adjl2(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t i, j; + igraph_int_t *added; + igraph_vector_int_t *neis; + igraph_vector_int_t tmp; + igraph_lazy_adjlist2_t adjlist2; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_int_list_reserve(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_int_list_clear(res); + + if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) { + IGRAPH_CHECK(igraph_lazy_adjlist2_init(graph, &adjlist2, mode, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + } else { + IGRAPH_CHECK(igraph_lazy_adjlist2_init(graph, &adjlist2, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + } + IGRAPH_FINALLY(igraph_lazy_adjlist2_destroy, &adjlist2); + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_int_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, node)); + } + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + igraph_int_t n; + neis = igraph_i_lazy_adjlist2_get_real(&adjlist2, actnode); + n = igraph_vector_int_size(neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &tmp)); + } + + igraph_lazy_adjlist2_destroy(&adjlist2); + igraph_vector_int_destroy(&tmp); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_neighborhood_adjl(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t i, j; + igraph_int_t *added; + igraph_vector_int_t *neis; + igraph_vector_int_t tmp; + igraph_lazy_adjlist_t adjlist; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_int_list_reserve(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_int_list_clear(res); + + if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) { + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + } else { + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + } + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_int_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, node)); + } + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + igraph_int_t n; + neis = igraph_lazy_adjlist_get(&adjlist, actnode); + n = igraph_vector_int_size(neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &tmp)); + } + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&tmp); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +igraph_error_t igraph_neighborhood_adj(const igraph_t *graph, igraph_vector_int_list_t *res, + igraph_vs_t vids, igraph_int_t order, + igraph_neimode_t mode, igraph_int_t mindist) { + + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_dqueue_int_t q; + igraph_vit_t vit; + igraph_int_t i, j; + igraph_int_t *added; + igraph_vector_int_t *neis; + igraph_vector_int_t tmp; + igraph_adjlist_t adjlist; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = IGRAPH_CALLOC(no_of_nodes, igraph_int_t); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_int_list_reserve(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_int_list_clear(res); + + if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE)); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE)); + } + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_int_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, node)); + } + if (order > 0) { + IGRAPH_CHECK(igraph_dqueue_int_push(&q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); + } + + while (!igraph_dqueue_int_empty(&q)) { + igraph_int_t actnode = igraph_dqueue_int_pop(&q); + igraph_int_t actdist = igraph_dqueue_int_pop(&q); + igraph_int_t n; + neis = igraph_adjlist_get(&adjlist, actnode); + n = igraph_vector_int_size(neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_int_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (j = 0; j < n; j++) { + igraph_int_t nei = VECTOR(*neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_int_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(res, &tmp)); + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&tmp); + igraph_vit_destroy(&vit); + igraph_dqueue_int_destroy(&q); + IGRAPH_FREE(added); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +void do_benchmark(igraph_t *g, igraph_vs_t vids, igraph_int_t repeat) +{ + igraph_vector_int_list_t result_orig; + igraph_vector_int_list_t result_adj; + igraph_vector_int_list_t result_adjl; + igraph_vector_int_list_t result_adjl2; + + igraph_vector_int_list_init(&result_orig, 0); + igraph_vector_int_list_init(&result_adj, 0); + igraph_vector_int_list_init(&result_adjl, 0); + igraph_vector_int_list_init(&result_adjl2, 0); + + for (igraph_int_t order = 1; order <= 3; order++) { + printf("order %" IGRAPH_PRId ":\n", order); + BENCH("Original function:", + REPEAT(igraph_neighborhood(g, &result_orig, vids, order, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0), repeat)); + + BENCH("Using a lazy adjlist:", + REPEAT(igraph_neighborhood_adjl(g, &result_adjl, vids, order, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0), repeat)); + + BENCH("Using lazy adjlist 2:", + REPEAT(igraph_neighborhood_adjl2(g, &result_adjl2, vids, order, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0), repeat)); + + BENCH("Using adjlist:", + REPEAT(igraph_neighborhood_adj(g, &result_adj, vids, order, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0), repeat)); + for (igraph_int_t i = 0; i <= order; i++) { + IGRAPH_ASSERT(!igraph_vector_int_lex_cmp(&VECTOR(result_orig)[i], &VECTOR(result_adj)[i])); + IGRAPH_ASSERT(!igraph_vector_int_lex_cmp(&VECTOR(result_adj)[i], &VECTOR(result_adjl)[i])); + IGRAPH_ASSERT(!igraph_vector_int_lex_cmp(&VECTOR(result_adjl)[i], &VECTOR(result_adjl2)[i])); + } + } + igraph_vector_int_list_destroy(&result_orig); + igraph_vector_int_list_destroy(&result_adj); + igraph_vector_int_list_destroy(&result_adjl); + igraph_vector_int_list_destroy(&result_adjl2); +} + +int main(void) { + igraph_t g_full, g_ring, g_er; + igraph_vs_t vids_all, vids_50, vids_5000, vids_200; + + igraph_vs_all(&vids_all); + igraph_vs_range(&vids_50, 0, 50); + igraph_vs_range(&vids_5000, 0, 5000); + igraph_vs_range(&vids_200, 0, 200); + + igraph_full(&g_full, 500, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_ring(&g_ring, 50000, IGRAPH_UNDIRECTED, /* mutual */ 0, /*circular*/ 0); + igraph_erdos_renyi_game_gnm(&g_er, 2000, 20000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + printf("Select all vertices:\n\n"); + printf("Full graph:\n"); + do_benchmark(&g_full, vids_all, 10); + printf("\nRing graph:\n"); + do_benchmark(&g_ring, vids_all, 40); + printf("\nRandom graph:\n"); + do_benchmark(&g_er, vids_all, 15); + + printf("\n\nSelect 10%% of vertices:\n\n"); + printf("Full graph:\n"); + do_benchmark(&g_full, vids_50, 100); + printf("\nRing graph:\n"); + do_benchmark(&g_ring, vids_5000, 400); + printf("\nRandom graph:\n"); + do_benchmark(&g_er, vids_200, 150); + + igraph_destroy(&g_full); + igraph_destroy(&g_ring); + igraph_destroy(&g_er); + igraph_vs_destroy(&vids_all); + igraph_vs_destroy(&vids_50); + igraph_vs_destroy(&vids_5000); + igraph_vs_destroy(&vids_200); +} diff --git a/tests/benchmarks/igraph_pagerank.c b/tests/benchmarks/igraph_pagerank.c new file mode 100644 index 0000000..bc08f8d --- /dev/null +++ b/tests/benchmarks/igraph_pagerank.c @@ -0,0 +1,137 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + igraph_t graph; + igraph_vector_t res; + igraph_arpack_options_t arpack_opts; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_arpack_options_init(&arpack_opts); + + igraph_vector_init(&res, 0); + + igraph_barabasi_game(&graph, 100000, 1, 4, NULL, 1, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + BENCH(" 1 PageRank, Barabasi n=100000 m=4, PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH(" 2 PageRank, Barabasi n=100000 m=4, ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_barabasi_game(&graph, 100000, 1, 10, NULL, 1, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + BENCH(" 3 PageRank, Barabasi n=100000 m=10, PRPACK, 5x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 5) + ); + BENCH(" 4 PageRank, Barabasi n=100000 m=10, ARPACK, 5x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 5) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100, 1000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 5 PageRank, GNM(100,1000), PRPACK, 1000x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1000) + ); + BENCH(" 6 PageRank, GNM(100,1000), ARPACK, 1000x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1000) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 200, 4000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 7 PageRank, GNM(200,4000), PRPACK, 1000x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1000) + ); + BENCH(" 8 PageRank, GNM(200,4000), ARPACK, 1000x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1000) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 10000, 20000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + BENCH(" 9 PageRank, GNM(10000,20000), PRPACK, 100x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 100) + ); + BENCH("10 PageRank, GNM(10000,20000), ARPACK, 100x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 100) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100000, 100000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + BENCH("11 PageRank, GNM(100000,100000), PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH("12 PageRank, GNM(100000,100000), ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100000, 500000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + BENCH("13 PageRank, GNM(100000,500000), PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH("14 PageRank, GNM(100000,500000), ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_kautz(&graph, 6, 6); + BENCH("13 PageRank, Kautz(6,6), PRPACK, 1x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1) + ); + BENCH("14 PageRank, Kautz(6,6), ARPACK, 1x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1) + ); + igraph_destroy(&graph); + + igraph_de_bruijn(&graph, 7, 7); + BENCH("13 PageRank, DeBruijn(7,7), PRPACK, 1x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1) + ); + BENCH("14 PageRank, DeBruijn(7,7), ARPACK, 1x", + REPEAT(igraph_pagerank(&graph, NULL, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1) + ); + igraph_destroy(&graph); + + igraph_vector_destroy(&res); + + return 0; +} diff --git a/tests/benchmarks/igraph_pagerank_weighted.c b/tests/benchmarks/igraph_pagerank_weighted.c new file mode 100644 index 0000000..239dc9a --- /dev/null +++ b/tests/benchmarks/igraph_pagerank_weighted.c @@ -0,0 +1,156 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void rand_weight_vec(igraph_vector_t *vec, const igraph_t *graph) { + igraph_int_t i, n = igraph_ecount(graph); + igraph_vector_resize(vec, n); + for (i=0; i < n; ++i) { + VECTOR(*vec)[i] = RNG_UNIF(1, 10); + } +} + +int main(void) { + igraph_t graph; + igraph_vector_t res, weights; + igraph_arpack_options_t arpack_opts; + + BENCH_INIT(); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_arpack_options_init(&arpack_opts); + + igraph_vector_init(&res, 0); + igraph_vector_init(&weights, 0); + + igraph_barabasi_game(&graph, 100000, 1, 4, NULL, 1, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weight_vec(&weights, &graph); + BENCH(" 1 PageRank weighted, Barabasi n=100000 m=4, PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH(" 2 PageRank weighted, Barabasi n=100000 m=4, ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_barabasi_game(&graph, 100000, 1, 10, NULL, 1, 0, IGRAPH_DIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weight_vec(&weights, &graph); + BENCH(" 3 PageRank weighted, Barabasi n=100000 m=10, PRPACK, 5x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 5) + ); + BENCH(" 4 PageRank weighted, Barabasi n=100000 m=10, ARPACK, 5x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 5) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100, 1000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + rand_weight_vec(&weights, &graph); + BENCH(" 5 PageRank weighted, GNM(100,1000), PRPACK, 1000x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1000) + ); + BENCH(" 6 PageRank weighted, GNM(100,1000), ARPACK, 1000x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1000) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 200, 4000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + rand_weight_vec(&weights, &graph); + BENCH(" 7 PageRank weighted, GNM(200,4000), PRPACK, 1000x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1000) + ); + BENCH(" 8 PageRank weighted, GNM(200,4000), ARPACK, 1000x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1000) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 10000, 20000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + rand_weight_vec(&weights, &graph); + BENCH(" 9 PageRank weighted, GNM(10000,20000), PRPACK, 100x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 100) + ); + BENCH("10 PageRank weighted, GNM(10000,20000), ARPACK, 100x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 100) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100000, 100000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + rand_weight_vec(&weights, &graph); + BENCH("11 PageRank weighted, GNM(100000,100000), PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH("12 PageRank weighted, GNM(100000,100000), ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnm(&graph, 100000, 500000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + rand_weight_vec(&weights, &graph); + BENCH("13 PageRank weighted, GNM(100000,500000), PRPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 10) + ); + BENCH("14 PageRank weighted, GNM(100000,500000), ARPACK, 10x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 10) + ); + igraph_destroy(&graph); + + igraph_kautz(&graph, 6, 6); + rand_weight_vec(&weights, &graph); + BENCH("13 PageRank weighted, Kautz(6,6), PRPACK, 1x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1) + ); + BENCH("14 PageRank weighted, Kautz(6,6), ARPACK, 1x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1) + ); + igraph_destroy(&graph); + + igraph_de_bruijn(&graph, 7, 7); + rand_weight_vec(&weights, &graph); + BENCH("13 PageRank weighted, DeBruijn(7,7), PRPACK, 1x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_PRPACK, NULL), 1) + ); + BENCH("14 PageRank weighted, DeBruijn(7,7), ARPACK, 1x", + REPEAT(igraph_pagerank(&graph, &weights, &res, NULL, 0.85, IGRAPH_DIRECTED, igraph_vss_all(), + IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_opts), 1) + ); + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + igraph_vector_destroy(&res); + + return 0; +} diff --git a/tests/benchmarks/igraph_power_law_fit.c b/tests/benchmarks/igraph_power_law_fit.c new file mode 100644 index 0000000..0d8528e --- /dev/null +++ b/tests/benchmarks/igraph_power_law_fit.c @@ -0,0 +1,168 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bench.h" + +igraph_vector_t data; + +double rpareto(double xmin, double alpha) { + /* 1-u is used in the base here because we want to avoid the case of + * sampling zero */ + return pow(1 - RNG_UNIF01(), -1.0 / alpha) * xmin; +} + +double rzeta(igraph_int_t xmin, double alpha) { + double u, v, t; + igraph_int_t x; + double alpha_minus_1 = alpha-1; + double minus_1_over_alpha_minus_1 = -1.0 / (alpha-1); + double b; + double one_over_b_minus_1; + + xmin = (igraph_int_t) round(xmin); + + /* Rejection sampling for the win. We use Y=floor(U^{-1/alpha} * xmin) as the + * envelope distribution, similarly to Chapter X.6 of Luc Devroye's book + * (where xmin is assumed to be 1): http://luc.devroye.org/chapter_ten.pdf + * + * Some notes that should help me recover what I was doing: + * + * p_i = 1/zeta(alpha, xmin) * i^-alpha + * q_i = (xmin/i)^{alpha-1} - (xmin/(i+1))^{alpha-1} + * = (i/xmin)^{1-alpha} - ((i+1)/xmin)^{1-alpha} + * = [i^{1-alpha} - (i+1)^{1-alpha}] / xmin^{1-alpha} + * + * p_i / q_i attains its maximum at xmin=i, so the rejection constant is: + * + * c = p_xmin / q_xmin + * + * We have to accept the sample if V <= (p_i / q_i) * (q_xmin / p_xmin) = + * (i/xmin)^-alpha * [xmin^{1-alpha} - (xmin+1)^{1-alpha}] / [i^{1-alpha} - (i+1)^{1-alpha}] = + * [xmin - xmin^alpha / (xmin+1)^{alpha-1}] / [i - i^alpha / (i+1)^{alpha-1}] = + * xmin/i * [1-(xmin/(xmin+1))^{alpha-1}]/[1-(i/(i+1))^{alpha-1}] + * + * In other words (and substituting i with X, which is the same), + * + * V * (X/xmin) <= [1 - (1+1/xmin)^{1-alpha}] / [1 - (1+1/i)^{1-alpha}] + * + * Let b := (1+1/xmin)^{alpha-1} and let T := (1+1/i)^{alpha-1}. Then: + * + * V * (X/xmin) <= [(b-1)/b] / [(T-1)/T] + * V * (X/xmin) * (T-1) / (b-1) <= T / b + * + * which is the same as in Devroye's book, except for the X/xmin term, and + * the definition of b. + */ + b = pow(1 + 1.0/xmin, alpha_minus_1); + one_over_b_minus_1 = 1.0/(b-1); + do { + do { + u = RNG_UNIF01(); + v = RNG_UNIF01(); + /* 1-u is used in the base here because we want to avoid the case of + * having zero in x */ + x = (igraph_int_t) floor(pow(1-u, minus_1_over_alpha_minus_1) * xmin); + } while (x < xmin); + t = pow((x+1.0)/x, alpha_minus_1); + } while (v*x*(t-1)*one_over_b_minus_1*b > t*xmin); + + return x; +} + +int generate_continuous(double xmin, double alpha, size_t num_samples) { + IGRAPH_CHECK(igraph_vector_resize(&data, num_samples)); + + for (size_t i = 0; i < num_samples; i++) { + VECTOR(data)[i] = rpareto(xmin, alpha); + } + + return IGRAPH_SUCCESS; +} + +int generate_discrete(double xmin, double alpha, size_t num_samples) { + IGRAPH_CHECK(igraph_vector_resize(&data, num_samples)); + + for (size_t i = 0; i < num_samples; i++) { + VECTOR(data)[i] = rzeta(xmin, alpha); + } + + return IGRAPH_SUCCESS; +} + +int fit_continuous(double known_xmin) { + igraph_plfit_result_t result; + IGRAPH_CHECK(igraph_power_law_fit(&data, &result, known_xmin, /* force_continuous = */ 1)); + return IGRAPH_SUCCESS; +} + +int fit_discrete(double known_xmin) { + igraph_plfit_result_t result; + IGRAPH_CHECK(igraph_power_law_fit(&data, &result, known_xmin, 0)); + return IGRAPH_SUCCESS; +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&data, 0); + + BENCH_INIT(); + + generate_continuous(1, 3, 100000); + BENCH(" 1 Continuous, xmin = 1, alpha = 3, samples = 100K, fitting alpha only", fit_continuous(1)); + + generate_continuous(1, 3, 200000); + BENCH(" 2 Continuous, xmin = 1, alpha = 3, samples = 200K, fitting alpha only", fit_continuous(1)); + + generate_continuous(1, 3, 500000); + BENCH(" 3 Continuous, xmin = 1, alpha = 3, samples = 500K, fitting alpha only", fit_continuous(1)); + + generate_continuous(1, 3, 5000); + BENCH(" 4 Continuous, xmin = 1, alpha = 3, samples = 5K, fitting xmin and alpha", fit_continuous(-1)); + + generate_continuous(1, 3, 10000); + BENCH(" 5 Continuous, xmin = 1, alpha = 3, samples = 10K, fitting xmin and alpha", fit_continuous(-1)); + + generate_continuous(1, 3, 15000); + BENCH(" 6 Continuous, xmin = 1, alpha = 3, samples = 15K, fitting xmin and alpha", fit_continuous(-1)); + + generate_discrete(3, 3, 1000000); + BENCH(" 7 Discrete, xmin = 3, alpha = 3, samples = 1M, fitting alpha only", fit_discrete(3)); + + generate_discrete(3, 3, 5000000); + BENCH(" 8 Discrete, xmin = 3, alpha = 3, samples = 5M, fitting alpha only", fit_discrete(3)); + + generate_discrete(3, 3, 10000000); + BENCH(" 9 Discrete, xmin = 3, alpha = 3, samples = 10M, fitting alpha only", fit_discrete(3)); + + generate_discrete(3, 3, 1000000); + BENCH("10 Discrete, xmin = 3, alpha = 3, samples = 1M, fitting xmin and alpha", fit_discrete(-1)); + + generate_discrete(3, 3, 5000000); + BENCH("11 Discrete, xmin = 3, alpha = 3, samples = 5M, fitting xmin and alpha", fit_discrete(-1)); + + generate_discrete(3, 3, 10000000); + BENCH("12 Discrete, xmin = 3, alpha = 3, samples = 10M, fitting xmin and alpha", fit_discrete(-1)); + + igraph_vector_destroy(&data); + + return 0; +} diff --git a/tests/benchmarks/igraph_qsort.c b/tests/benchmarks/igraph_qsort.c new file mode 100644 index 0000000..b0ba7be --- /dev/null +++ b/tests/benchmarks/igraph_qsort.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +/* This program benchmarks igraph_qsort() indirectly through vector_sort() */ + +int main(void) { + igraph_vector_int_t vec; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + + igraph_vector_int_init(&vec, 0); + +#define N 10000000 + + igraph_vector_int_resize(&vec, N); + for (igraph_int_t i=0; i < N; i++) { + VECTOR(vec)[i] = RNG_INTEGER(0, N-1); + } + BENCH("Sort vector of length " IGRAPH_I_STRINGIFY(N), igraph_vector_int_sort(&vec)); + +#undef N +#define N 1000000 + + igraph_vector_int_resize(&vec, N); + for (igraph_int_t i=0; i < N; i++) { + VECTOR(vec)[i] = RNG_INTEGER(0, N-1); + } + BENCH("Sort vector of length " IGRAPH_I_STRINGIFY(N), igraph_vector_int_sort(&vec)); + +#undef N +#define N 100000 + + igraph_vector_int_resize(&vec, N); + for (igraph_int_t i=0; i < N; i++) { + VECTOR(vec)[i] = RNG_INTEGER(0, N-1); + } + BENCH("Sort vector of length " IGRAPH_I_STRINGIFY(N), igraph_vector_int_sort(&vec)); + + igraph_vector_int_destroy(&vec); + + return 0; +} diff --git a/tests/benchmarks/igraph_random_walk.c b/tests/benchmarks/igraph_random_walk.c new file mode 100644 index 0000000..a16f468 --- /dev/null +++ b/tests/benchmarks/igraph_random_walk.c @@ -0,0 +1,201 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "bench.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t vertices; + igraph_vector_int_t edges; + igraph_vector_t weights; + igraph_int_t ec, i; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + igraph_vector_init(&weights, 0); + + /* create a small graph, and a compatible weight vector */ + igraph_de_bruijn(&graph, 3, 2); /* 9 vertices, 27 edges, average degree: 6 */ + ec = igraph_ecount(&graph); + + igraph_vector_resize(&weights, ec); + for (i = 0; i < ec; ++i) { + VECTOR(weights)[i] = igraph_rng_get_unif01(igraph_rng_default()); + } + + /* Only edges */ + + BENCH(" 1 Random edge walk, directed, unweighted, small graph ", + igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 2 Random edge walk, directed, weighted, small graph ", + igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 3 Random edge walk, undirected, unweighted, small graph ", + igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 4 Random edge walk, undirected, weighted, small graph ", + igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + /* Only vertices */ + + BENCH(" 5 Random vertex walk, directed, unweighted, small graph ", + igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 6 Random vertex walk, directed, weighted, small graph ", + igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 7 Random vertex walk, undirected, unweighted, small graph ", + igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 8 Random vertex walk, undirected, weighted, small graph ", + igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + /* Both edges and vertices */ + + BENCH(" 9 Random walk, directed, unweighted, small graph ", + igraph_random_walk(&graph, NULL, &vertices, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 10 Random walk, directed, weighted, small graph ", + igraph_random_walk(&graph, &weights, &vertices, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + igraph_destroy(&graph); + + /* create a big graph, and a compatible weight vector */ + igraph_de_bruijn(&graph, 8, 5); /* 32768 vertices, 262144 edges, average degree: 16 */ + ec = igraph_ecount(&graph); + + igraph_vector_resize(&weights, ec); + for (i = 0; i < ec; ++i) { + VECTOR(weights)[i] = igraph_rng_get_unif01(igraph_rng_default()); + } + + /* Only edges */ + + BENCH(" 11 Random edge walk, directed, unweighted, large graph ", + igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 12 Random edge walk, directed, weighted, large graph ", + igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 13 Random edge walk, undirected, unweighted, large graph ", + igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 14 Random edge walk, undirected, weighted, large graph ", + igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + /* Only vertices */ + + BENCH(" 15 Random vertex walk, directed, unweighted, large graph ", + igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 16 Random vertex walk, directed, weighted, large graph ", + igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 17 Random vertex walk, undirected, unweighted, large graph ", + igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 18 Random vertex walk, undirected, weighted, large graph ", + igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_ALL, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + /* Both edges and vertices */ + + BENCH(" 19 Random walk, directed, unweighted, large graph ", + igraph_random_walk(&graph, NULL, &vertices, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + BENCH(" 20 Random walk, directed, weighted, large graph ", + igraph_random_walk(&graph, &weights, &vertices, &edges, 0, IGRAPH_OUT, 50000000, IGRAPH_RANDOM_WALK_STUCK_RETURN) + ); + + /* Only edges */ + + BENCH(" 21 Short edge walk, directed, unweighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 22 Short edge walk, directed, weighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 23 Short edge walk, undirected, unweighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, NULL, NULL, &edges, 0, IGRAPH_ALL, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 24 Short edge walk, undirected, weighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, &weights, NULL, &edges, 0, IGRAPH_ALL, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + /* Only vertices */ + + BENCH(" 25 Short vertex walk, directed, unweighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 26 Short vertex walk, directed, weighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 27 Short vertex walk, undirected, unweighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, NULL, &vertices, NULL, 0, IGRAPH_ALL, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 28 Short vertex walk, undirected, weighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, &weights, &vertices, NULL, 0, IGRAPH_ALL, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + /* Both edges and vertices */ + + BENCH(" 29 Short random walk, directed, unweighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, NULL, &vertices, &edges, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + BENCH(" 30 Short random walk, directed, weighted, large graph, x 100", + REPEAT(igraph_random_walk(&graph, &weights, &vertices, &edges, 0, IGRAPH_OUT, 10000, IGRAPH_RANDOM_WALK_STUCK_RETURN), 100) + ); + + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); + + return 0; +} diff --git a/tests/benchmarks/igraph_realize_degree_sequence.c b/tests/benchmarks/igraph_realize_degree_sequence.c new file mode 100644 index 0000000..f3fc1bf --- /dev/null +++ b/tests/benchmarks/igraph_realize_degree_sequence.c @@ -0,0 +1,85 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +void bench(const igraph_t *graph, const char *what, int rep) { + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_int_t degrees; + igraph_t new_graph; + + igraph_vector_int_init(°rees, 0); + igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + + char name[200]; + sprintf(name, "%s, n=%5" IGRAPH_PRId ", m=%5" IGRAPH_PRId ", LARGEST, %dx", what, vcount, ecount, rep); + BENCH(name, REPEAT(igraph_realize_degree_sequence(&new_graph, °rees, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_LARGEST), rep)); + igraph_destroy(&new_graph); + + sprintf(name, "%s, n=%5" IGRAPH_PRId ", m=%5" IGRAPH_PRId ", SMALLEST, %dx", what, vcount, ecount, rep); + BENCH(name, REPEAT(igraph_realize_degree_sequence(&new_graph, °rees, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST), rep)); + igraph_destroy(&new_graph); + + sprintf(name, "%s, n=%5" IGRAPH_PRId ", m=%5" IGRAPH_PRId ", INDEX, %dx", what, vcount, ecount, rep); + BENCH(name, REPEAT(igraph_realize_degree_sequence(&new_graph, °rees, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_INDEX), rep)); + igraph_destroy(&new_graph); + + printf("\n"); + + igraph_vector_int_destroy(°rees); +} + +void bench_gnm(igraph_int_t n, igraph_int_t m, int rep) { + igraph_t graph; + igraph_erdos_renyi_game_gnm(&graph, n, m, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + bench(&graph, "G(n,m)", rep); + igraph_destroy(&graph); +} + +void bench_ba(igraph_int_t n, igraph_int_t m, int rep) { + igraph_t graph; + igraph_barabasi_game(&graph, n, 1, m, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + bench(&graph, "BA", rep); + igraph_destroy(&graph); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 789); + BENCH_INIT(); + + bench_gnm(100, 200, 10000); + bench_gnm(100, 1000, 10000); + + bench_gnm(10000, 20000, 1); + bench_gnm(10000, 100000, 1); + + bench_ba(100, 1, 10000); + bench_ba(100, 10, 10000); + + bench_ba(10000, 1, 1); + bench_ba(10000, 10, 1); + + return 0; +} diff --git a/tests/benchmarks/igraph_rewire.c b/tests/benchmarks/igraph_rewire.c new file mode 100644 index 0000000..cdfad4a --- /dev/null +++ b/tests/benchmarks/igraph_rewire.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "bench.h" + +void bench(const char *name, igraph_t *graph, int rep) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t trials = 10*ecount; + char msg[200]; + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s, |V|=%6" IGRAPH_PRId ", |E|=%6" IGRAPH_PRId ", %6" IGRAPH_PRId " swaps, %dx", + name, vcount, ecount, trials, rep); + + BENCH(msg, REPEAT(igraph_rewire(graph, trials, IGRAPH_SIMPLE_SW, NULL), rep)); +} + +void bench_er(igraph_int_t n, igraph_int_t m, int rep) { + igraph_t graph; + + igraph_erdos_renyi_game_gnm(&graph, n, m, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + bench("G(n,m)", &graph, rep); + + igraph_destroy(&graph); +} + +void bench_ba(igraph_int_t n, igraph_int_t m, int rep) { + igraph_t graph; + + igraph_barabasi_game(&graph, n, 1, m ,NULL, true, 1, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_BAG, NULL); + + bench("BA", &graph, rep); + + igraph_destroy(&graph); +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + bench_er(100, 3000, 100); + bench_er(300, 3000, 100); + bench_er(1000, 3000, 100); + bench_er(3000, 3000, 100); + bench_er(10000, 3000, 100); + + printf("\n"); + bench_er(300, 30000, 10); + bench_er(1000, 30000, 10); + bench_er(3000, 30000, 10); + bench_er(10000, 30000, 10); + + printf("\n"); + bench_ba(1000, 3, 100); + bench_ba(10000, 3, 10); + bench_ba(100000, 3, 1); + + return 0; +} diff --git a/tests/benchmarks/igraph_strength.c b/tests/benchmarks/igraph_strength.c new file mode 100644 index 0000000..bac76e3 --- /dev/null +++ b/tests/benchmarks/igraph_strength.c @@ -0,0 +1,145 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +void rand_weight_vec(igraph_vector_t *vec, const igraph_t *graph) { + igraph_int_t i, n = igraph_ecount(graph); + igraph_vector_resize(vec, n); + for (i=0; i < n; ++i) { + VECTOR(*vec)[i] = RNG_UNIF(1, 10); + } +} + +int main(void) { + + igraph_t g; + igraph_vector_t strength, weights; + + igraph_rng_seed(igraph_rng_default(), 54); + BENCH_INIT(); + + igraph_vector_init(&strength, 0); + igraph_vector_init(&weights, 0); + + /* igraph_strength() uses an optimized, cache friendly code path when given + * a vertex selector for which igraph_vs_is_all() returns true (this is + * currently igraph_vs_all()). We pass both igraph_vss_all() and + * igraph_vss_range(0, igraph_vcount(graph)) as vertex selectors + * to compare the performance of the two code paths. + * + * NOTE: While currently igraph_vs_is_all() does not return true for + * a range-type vertex selector, this may change in the future. + * An altrenative is to to use igraph_vs_vector(), with the vector + * initialized to a range. + */ + +#define N 1000 +#define M 10 +#define REP 10000 + + igraph_barabasi_game(&g, N, 1, M, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + rand_weight_vec(&weights, &g); + + BENCH(" 1a igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + BENCH(" 1b igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_range(0, igraph_vcount(&g)), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + printf("\n"); + + igraph_destroy(&g); + +#undef N +#undef M +#undef REP + +#define N 10000 +#define M 10 +#define REP 1000 + + igraph_barabasi_game(&g, N, 1, M, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + rand_weight_vec(&weights, &g); + + BENCH(" 2a igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + BENCH(" 2b igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_range(0, igraph_vcount(&g)), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + printf("\n"); + + igraph_destroy(&g); + +#undef N +#undef M +#undef REP + +#define N 100000 +#define M 10 +#define REP 100 + + igraph_barabasi_game(&g, N, 1, M, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + rand_weight_vec(&weights, &g); + + BENCH(" 3a igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + BENCH(" 3b igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_range(0, igraph_vcount(&g)), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + printf("\n"); + + igraph_destroy(&g); + +#undef N +#undef M +#undef REP + +#define N 100000 +#define M 100 +#define REP 10 + + igraph_barabasi_game(&g, N, 1, M, NULL, true, 0, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE_MULTIPLE, NULL); + rand_weight_vec(&weights, &g); + + BENCH(" 4a igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + BENCH(" 4b igraph_strength(), preferential attachment n=" TOSTR(N) ", m=" TOSTR(M) ", " TOSTR(REP) "x", + REPEAT(igraph_strength(&g, &strength, igraph_vss_range(0, igraph_vcount(&g)), IGRAPH_ALL, IGRAPH_LOOPS, &weights), REP) + ); + printf("\n"); + + igraph_destroy(&g); + +#undef N +#undef M +#undef REP + + igraph_vector_destroy(&weights); + igraph_vector_destroy(&strength); + + return 0; +} diff --git a/tests/benchmarks/igraph_transitivity.c b/tests/benchmarks/igraph_transitivity.c new file mode 100644 index 0000000..c99acde --- /dev/null +++ b/tests/benchmarks/igraph_transitivity.c @@ -0,0 +1,252 @@ +/* + igraph library. + Copyright (C) 2013-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + + igraph_t g; + igraph_vector_t trans; + igraph_vs_t all_vertices; + igraph_real_t avg_trans, global_trans, tri_count; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + +#define N 6000 +#define M 2000000 + + igraph_erdos_renyi_game_gnm(&g, N, M, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_init(&trans, igraph_vcount(&g)); + igraph_vs_range(&all_vertices, 0, igraph_vcount(&g)); + + BENCH(" 1 Local transitivity, all vertices method, GNM", + igraph_transitivity_local_undirected(&g, &trans, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 2 Local transitivity, subset method, GNM", + igraph_transitivity_local_undirected(&g, &trans, all_vertices, + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 3 Average local transitivity GNM", + igraph_transitivity_avglocal_undirected(&g, &avg_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 4 Global transitivity GNM", + igraph_transitivity_undirected(&g, &global_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 5 Count triangles per vertex, all vertices method, GNM", + igraph_count_adjacent_triangles(&g, &trans, igraph_vss_all()); + ); + + BENCH(" 6 Count triangles per vertex, subset method, GNM", + igraph_count_adjacent_triangles(&g, &trans, all_vertices); + ); + + BENCH(" 7 Count all triangles, GNM", + igraph_count_triangles(&g, &tri_count); + ); + + igraph_vs_destroy(&all_vertices); + igraph_destroy(&g); + + printf("\n"); + + igraph_barabasi_game(&g, N, /*power=*/ 1, M / N, /*outseq=*/ 0, + /*outpref=*/ 0, /*A=*/ 1, IGRAPH_UNDIRECTED, + IGRAPH_BARABASI_PSUMTREE, /*start_from=*/ 0); + igraph_vector_resize(&trans, igraph_vcount(&g)); + igraph_vs_range(&all_vertices, 0, igraph_vcount(&g)); + + BENCH(" 1 Local transitivity, all vertices method, Barabasi", + igraph_transitivity_local_undirected(&g, &trans, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 2 Local transitivity, subset method, Barabasi", + igraph_transitivity_local_undirected(&g, &trans, all_vertices, + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 3 Average local transitivity, Barabasi", + igraph_transitivity_avglocal_undirected(&g, &avg_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 4 Global transitivity, Barabasi", + igraph_transitivity_undirected(&g, &global_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 5 Count triangles per vertex, all vertices method, Barabasi", + igraph_count_adjacent_triangles(&g, &trans, igraph_vss_all()); + ); + + BENCH(" 6 Count triangles per vertex, subset method, Barabasi", + igraph_count_adjacent_triangles(&g, &trans, all_vertices); + ); + + BENCH(" 7 Count all triangles, Barabasi", + igraph_count_triangles(&g, &tri_count); + ); + + igraph_vs_destroy(&all_vertices); + igraph_destroy(&g); + + printf("\n"); + +#undef N +#undef M + +#define N 1000000 +#define M 10000000 + + igraph_erdos_renyi_game_gnm(&g, N, M, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_init(&trans, igraph_vcount(&g)); + igraph_vs_range(&all_vertices, 0, igraph_vcount(&g)); + + BENCH(" 1 Local transitivity, all vertices method, large GNM", + igraph_transitivity_local_undirected(&g, &trans, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 2 Local transitivity, subset method, large GNM", + igraph_transitivity_local_undirected(&g, &trans, all_vertices, + IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 3 Average local transitivity, large GNM", + igraph_transitivity_avglocal_undirected(&g, &avg_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 4 Global transitivity, large GNM", + igraph_transitivity_undirected(&g, &global_trans, IGRAPH_TRANSITIVITY_NAN); + ); + + BENCH(" 5 Count triangles per vertex, all vertices method, large GNM", + igraph_count_adjacent_triangles(&g, &trans, igraph_vss_all()); + ); + + BENCH(" 6 Count triangles per vertex, subset method, large GNM", + igraph_count_adjacent_triangles(&g, &trans, all_vertices); + ); + + BENCH(" 7 Count all triangles, large GNM", + igraph_count_triangles(&g, &tri_count); + ); + + igraph_vs_destroy(&all_vertices); + igraph_destroy(&g); + + printf("\n"); + + igraph_erdos_renyi_game_gnm(&g, 500, 2000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&trans, igraph_vcount(&g)); + igraph_vs_range(&all_vertices, 0, igraph_vcount(&g)); + +#define REPS 1000 + + BENCH(" 1 Local transitivity, all vertices method, small GNM", + REPEAT(igraph_transitivity_local_undirected(&g, &trans, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN), REPS); + ); + + BENCH(" 2 Local transitivity, subset method, small GNM", + REPEAT(igraph_transitivity_local_undirected(&g, &trans, all_vertices, + IGRAPH_TRANSITIVITY_NAN), REPS); + ); + + BENCH(" 3 Average local transitivity, small GNM", + REPEAT(igraph_transitivity_avglocal_undirected(&g, &avg_trans, IGRAPH_TRANSITIVITY_NAN), + REPS); + ); + + BENCH(" 4 Global transitivity, small GNM", + REPEAT(igraph_transitivity_undirected(&g, &global_trans, IGRAPH_TRANSITIVITY_NAN), + REPS); + ); + + BENCH(" 5 Count triangles per vertex, all vertices method, small GNM", + REPEAT(igraph_count_adjacent_triangles(&g, &trans, igraph_vss_all()), REPS); + ); + + BENCH(" 6 Count triangles per vertex, subset method, small GNM", + REPEAT(igraph_count_adjacent_triangles(&g, &trans, all_vertices), REPS); + ); + + BENCH(" 7 Count all triangles, small GNM", + REPEAT(igraph_count_triangles(&g, &tri_count), REPS); + ); + + igraph_vs_destroy(&all_vertices); + igraph_destroy(&g); + +#undef REPS + + printf("\n"); + + igraph_erdos_renyi_game_gnm(&g, 50, 300, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_resize(&trans, igraph_vcount(&g)); + igraph_vs_range(&all_vertices, 0, igraph_vcount(&g)); + +#define REPS 10000 + + BENCH(" 1 Local transitivity, all vertices method, tiny GNM", + REPEAT(igraph_transitivity_local_undirected(&g, &trans, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN), REPS); + ); + + BENCH(" 2 Local transitivity, subset method, tiny GNM", + REPEAT(igraph_transitivity_local_undirected(&g, &trans, all_vertices, + IGRAPH_TRANSITIVITY_NAN), REPS); + ); + + BENCH(" 3 Average local transitivity, tiny GNM", + REPEAT(igraph_transitivity_avglocal_undirected(&g, &avg_trans, IGRAPH_TRANSITIVITY_NAN), + REPS); + ); + + BENCH(" 4 Global transitivity, tiny GNM", + REPEAT(igraph_transitivity_undirected(&g, &global_trans, IGRAPH_TRANSITIVITY_NAN), + REPS); + ); + + BENCH(" 5 Count triangles per vertex, all vertices method, tiny GNM", + REPEAT(igraph_count_adjacent_triangles(&g, &trans, igraph_vss_all()), REPS); + ); + + BENCH(" 6 Count triangles per vertex, subset method, tiny GNM", + REPEAT(igraph_count_adjacent_triangles(&g, &trans, all_vertices), REPS); + ); + + BENCH(" 7 Count all triangles, tiny GNM", + REPEAT(igraph_count_triangles(&g, &tri_count), REPS); + ); + + igraph_vs_destroy(&all_vertices); + igraph_destroy(&g); + +#undef REPS + + igraph_vector_destroy(&trans); + + return 0; +} diff --git a/tests/benchmarks/igraph_tree_game.c b/tests/benchmarks/igraph_tree_game.c new file mode 100644 index 0000000..fa932b8 --- /dev/null +++ b/tests/benchmarks/igraph_tree_game.c @@ -0,0 +1,104 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +void tree_game(igraph_int_t n, igraph_bool_t directed, igraph_random_tree_t method) { + igraph_t g; + igraph_tree_game(&g, n, directed, method); + igraph_destroy(&g); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + +#define VCOUNT 100 +#define REP 100000 + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", Prufer, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER), REP); + ); + + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", LERW, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW), REP); + ); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 1000 +#define REP 10000 + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", Prufer, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER), REP); + ); + + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", LERW, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW), REP); + ); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 10000 +#define REP 1000 + + BENCH(" 3 vcount=" TOSTR(VCOUNT) ", Prufer, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER), REP); + ); + + BENCH(" 4 vcount=" TOSTR(VCOUNT) ", LERW, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW), REP); + ); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 100000 +#define REP 100 + + BENCH(" 3 vcount=" TOSTR(VCOUNT) ", Prufer, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER), REP); + ); + + BENCH(" 4 vcount=" TOSTR(VCOUNT) ", LERW, " TOSTR(REP) "x", + REPEAT(tree_game(VCOUNT, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW), REP); + ); + +#undef VCOUNT +#undef DENS +#undef REP + + return 0; +} diff --git a/tests/benchmarks/igraph_vertex_connectivity.c b/tests/benchmarks/igraph_vertex_connectivity.c new file mode 100644 index 0000000..e4351bd --- /dev/null +++ b/tests/benchmarks/igraph_vertex_connectivity.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +int main(void) { + + igraph_t g; + igraph_int_t vconn; + + igraph_rng_seed(igraph_rng_default(), 54); + BENCH_INIT(); + + igraph_grg_game(&g, 100, 0.2, /* torus = */ false, /* x = */ NULL, /* y = */ NULL); + + BENCH(" 1 Vertex connectivity of geometric random graph, n=100, r=0.2", + igraph_vertex_connectivity(&g, &vconn, /* checks = */ false); + ); + + igraph_destroy(&g); + + igraph_grg_game(&g, 200, 0.141, /* torus = */ false, /* x = */ NULL, /* y = */ NULL); + + BENCH(" 2 Vertex connectivity of geometric random graph, n=200, r=0.141", + igraph_vertex_connectivity(&g, &vconn, /* checks = */ false); + ); + + igraph_destroy(&g); + + igraph_grg_game(&g, 400, 0.1, /* torus = */ false, /* x = */ NULL, /* y = */ NULL); + + BENCH(" 3 Vertex connectivity of geometric random graph, n=400, r=0.1", + igraph_vertex_connectivity(&g, &vconn, /* checks = */ false); + ); + + igraph_destroy(&g); + + return 0; +} diff --git a/tests/benchmarks/igraph_voronoi.c b/tests/benchmarks/igraph_voronoi.c new file mode 100644 index 0000000..f32c0e7 --- /dev/null +++ b/tests/benchmarks/igraph_voronoi.c @@ -0,0 +1,165 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +#define TOSTR1(x) #x +#define TOSTR(x) TOSTR1(x) + +int main(void) { + igraph_t g; + igraph_vector_int_t membership; + igraph_vector_t distances; + igraph_vector_t weights; + igraph_vector_int_t generators; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_vector_int_init(&membership, 0); + igraph_vector_init(&distances, 0); + igraph_vector_init(&weights, 0); + igraph_vector_int_init(&generators, 0); + +#define VCOUNT 100 +#define DENS 0.5 +#define REP 1000 + + igraph_erdos_renyi_game_gnp(&g, VCOUNT, DENS, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_int_resize(&membership, igraph_vcount(&g)); + igraph_vector_resize(&distances, igraph_vcount(&g)); + igraph_vector_resize(&weights, igraph_ecount(&g)); + + for (igraph_int_t i=0; i < igraph_ecount(&g); i++) { + VECTOR(weights)[i] = RNG_EXP(1); + } + + igraph_random_sample(&generators, 0, igraph_vcount(&g)-1, 20); + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Unweighted, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Dijkstra, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + + igraph_destroy(&g); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 500 +#define DENS 0.1 +#define REP 1000 + + igraph_erdos_renyi_game_gnp(&g, VCOUNT, DENS, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_int_resize(&membership, igraph_vcount(&g)); + igraph_vector_resize(&distances, igraph_vcount(&g)); + igraph_vector_resize(&weights, igraph_ecount(&g)); + + for (igraph_int_t i=0; i < igraph_ecount(&g); i++) { + VECTOR(weights)[i] = RNG_EXP(1); + } + + igraph_random_sample(&generators, 0, igraph_vcount(&g)-1, 20); + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Unweighted, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Dijkstra, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + + igraph_destroy(&g); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 5000 +#define DENS 0.01 +#define REP 100 + + igraph_erdos_renyi_game_gnp(&g, VCOUNT, DENS, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_int_resize(&membership, igraph_vcount(&g)); + igraph_vector_resize(&distances, igraph_vcount(&g)); + igraph_vector_resize(&weights, igraph_ecount(&g)); + + for (igraph_int_t i=0; i < igraph_ecount(&g); i++) { + VECTOR(weights)[i] = RNG_EXP(1); + } + + igraph_random_sample(&generators, 0, igraph_vcount(&g)-1, 20); + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Unweighted, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Dijkstra, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + + igraph_destroy(&g); + +#undef VCOUNT +#undef DENS +#undef REP + + printf("\n"); + +#define VCOUNT 100000 +#define DENS 0.0001 +#define REP 1 + + igraph_erdos_renyi_game_gnp(&g, VCOUNT, DENS, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_vector_int_resize(&membership, igraph_vcount(&g)); + igraph_vector_resize(&distances, igraph_vcount(&g)); + igraph_vector_resize(&weights, igraph_ecount(&g)); + + for (igraph_int_t i=0; i < igraph_ecount(&g); i++) { + VECTOR(weights)[i] = RNG_EXP(1); + } + + igraph_random_sample(&generators, 0, igraph_vcount(&g)-1, 100); + + BENCH(" 1 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Unweighted, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + BENCH(" 2 vcount=" TOSTR(VCOUNT) ", p=" TOSTR(DENS) ", Dijkstra, " TOSTR(REP) "x", + REPEAT(igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM), REP); + ); + + igraph_destroy(&g); + +#undef VCOUNT +#undef DENS +#undef REP + + igraph_vector_int_destroy(&generators); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&distances); + igraph_vector_int_destroy(&membership); + + return 0; +} diff --git a/tests/benchmarks/inc_vs_adj.c b/tests/benchmarks/inc_vs_adj.c new file mode 100644 index 0000000..2d4f774 --- /dev/null +++ b/tests/benchmarks/inc_vs_adj.c @@ -0,0 +1,456 @@ +/* + igraph library. + Copyright (C) 2013-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +typedef struct igraph_incadjlist_inter_t { + igraph_int_t length; + igraph_vector_int_t *incadjs; +} igraph_incadjlist_inter_t; + +#define igraph_incadjlist_inter_get(il,no) (&(il)->incadjs[(igraph_int_t)(no)]) +#define igraph_incadjlist_sep_get_inc(il,no) (&(il)->incs[(igraph_int_t)(no)]) +#define igraph_incadjlist_sep_get_adj(il,no) (&(il)->adjs[(igraph_int_t)(no)]) + +void igraph_incadjlist_inter_destroy(igraph_incadjlist_inter_t *il) { + igraph_int_t i; + for (i = 0; i < il->length; i++) { + /* This works if some igraph_vector_int_t's contain NULL, + because igraph_vector_int_destroy can handle this. */ + igraph_vector_int_destroy(&il->incadjs[i]); + } + IGRAPH_FREE(il->incadjs); +} + +igraph_error_t igraph_incadjlist_inter_init(const igraph_t *graph, + igraph_incadjlist_inter_t *il, + igraph_neimode_t mode) { + igraph_int_t i, j, n; + igraph_vector_int_t tmp; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create incadjlist.", IGRAPH_EINVMODE); + } + + igraph_vector_int_init(&tmp, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + il->length = igraph_vcount(graph); + il->incadjs = IGRAPH_CALLOC(il->length, igraph_vector_int_t); + if (il->incadjs == 0) { + IGRAPH_ERROR("Cannot create incadjlist.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + IGRAPH_FINALLY(igraph_incadjlist_inter_destroy, il); + + for (i = 0; i < il->length; i++) { + //IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_incident(graph, &tmp, i, mode, IGRAPH_LOOPS)); + + n = igraph_vector_int_size(&tmp); + IGRAPH_CHECK(igraph_vector_int_init(&il->incadjs[i], n * 2)); + + for (j = 0; j < n; j++) { + VECTOR(il->incadjs[i])[j * 2] = VECTOR(tmp)[j]; + VECTOR(il->incadjs[i])[j * 2 + 1] = + IGRAPH_OTHER(graph, VECTOR(tmp)[j], i); + } + } + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); /* + igraph_incadjlist_inter_destroy */ + + return IGRAPH_SUCCESS; +} + + +typedef struct igraph_incadjlist_sep_t { + igraph_int_t length; + igraph_vector_int_t *incs; + igraph_vector_int_t *adjs; +} igraph_incadjlist_sep_t; + +void igraph_incadjlist_sep_destroy(igraph_incadjlist_sep_t *il) { + igraph_int_t i; + for (i = 0; i < il->length; i++) { + /* This works if some igraph_vector_int_t's contain NULL, + because igraph_vector_int_destroy can handle this. */ + igraph_vector_int_destroy(&il->incs[i]); + igraph_vector_int_destroy(&il->adjs[i]); + } + IGRAPH_FREE(il->incs); + IGRAPH_FREE(il->adjs); +} + +igraph_error_t igraph_incadjlist_sep_init(const igraph_t *graph, + igraph_incadjlist_sep_t *il, + igraph_neimode_t mode) { + igraph_int_t i, j, n; + igraph_vector_int_t tmp; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create incadjlist.", IGRAPH_EINVMODE); + } + + igraph_vector_int_init(&tmp, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + il->length = igraph_vcount(graph); + il->incs = IGRAPH_CALLOC(il->length, igraph_vector_int_t); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create incadjlist.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + il->adjs = IGRAPH_CALLOC(il->length, igraph_vector_int_t); + if (il->adjs == 0) { + IGRAPH_FREE(il->incs); + IGRAPH_ERROR("Cannot create incadjlist.", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ + } + + IGRAPH_FINALLY(igraph_incadjlist_sep_destroy, il); + + for (i = 0; i < il->length; i++) { + //IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_incident(graph, &tmp, i, mode, IGRAPH_LOOPS)); + + n = igraph_vector_int_size(&tmp); + IGRAPH_CHECK(igraph_vector_int_init(&il->incs[i], n)); + IGRAPH_CHECK(igraph_vector_int_init(&il->adjs[i], n)); + + for (j = 0; j < n; j++) { + VECTOR(il->incs[i])[j] = VECTOR(tmp)[j]; + VECTOR(il->adjs[i])[j] = + IGRAPH_OTHER(graph, VECTOR(tmp)[j], i); + } + + } + + igraph_vector_int_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); /* + igraph_incadjlist_sep_destroy */ + + return IGRAPH_SUCCESS; +} + +/* In the below tests, the 'dummy' variable is used to prevent the compilers + * from optimizing away the entire side-effects-free function. We use XOR + * instead of e.g. addition in order to prevent triggering undefined behaviour. */ + +igraph_int_t test_direct(igraph_t *g) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + igraph_int_t ecount = igraph_ecount(g); + + igraph_int_t ei = 0; + igraph_int_t eo = 0; + for (igraph_int_t i = 0; i < vcount; i++) { + while (ei < ecount && VECTOR(g->from)[VECTOR(g->oi)[ei]] == i) { + igraph_int_t neighbor = VECTOR(g->to)[VECTOR(g->oi)[ei]]; + dummy ^= neighbor ^ VECTOR(g->oi)[ei]; + ei++; + } + while (eo < ecount && VECTOR(g->to)[VECTOR(g->ii)[eo]] == i) { + igraph_int_t neighbor = VECTOR(g->from)[VECTOR(g->ii)[eo]]; + dummy ^= neighbor ^ VECTOR(g->ii)[eo] ; + eo++; + } + } + return dummy; +} + +igraph_int_t test_adj(igraph_t *g, igraph_adjlist_t *adj) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *neis = igraph_adjlist_get(adj, i); + igraph_int_t nneis = igraph_vector_int_size(neis); + for (int j = 0; j < nneis; j++) { + igraph_int_t neighbor = VECTOR(*neis)[j]; + dummy ^= neighbor; + } + } + return dummy; +} + +igraph_int_t test_inc_adj(igraph_t *g, igraph_inclist_t *inc, igraph_adjlist_t *adj) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *adjs = igraph_adjlist_get(adj, i); + igraph_vector_int_t *incs = igraph_inclist_get(inc, i); + igraph_int_t nneis = igraph_vector_int_size(adjs); + for (int j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t neighbor = VECTOR(*adjs)[j]; + dummy ^= neighbor ^ edge; + } + } + return dummy; +} + +igraph_int_t test_inc_other(igraph_t *g, igraph_inclist_t *inc) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *neis = igraph_inclist_get(inc, i); + igraph_int_t nneis = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t neighbor = IGRAPH_OTHER(g, edge, i); + dummy ^= neighbor ^ edge; + } + } + return dummy; +} + +igraph_int_t test_incadj_sep(igraph_t *g, igraph_incadjlist_sep_t *inc) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *incs = igraph_incadjlist_sep_get_inc(inc, i); + igraph_vector_int_t *adjs = igraph_incadjlist_sep_get_adj(inc, i); + igraph_int_t nneis = igraph_vector_int_size(incs); + for (igraph_int_t j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*incs)[j]; + igraph_int_t neighbor = VECTOR(*adjs)[j]; + dummy ^= neighbor ^ edge; + } + } + return dummy; +} + +igraph_int_t test_incadj_inter(igraph_t *g, igraph_incadjlist_inter_t *inc) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *ias = igraph_incadjlist_inter_get(inc, i); + igraph_int_t nneis = igraph_vector_int_size(ias) / 2; + for (igraph_int_t j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*ias)[j * 2]; + igraph_int_t neighbor = VECTOR(*ias)[j * 2 + 1]; + dummy ^= neighbor ^ edge; + } + } + return dummy; +} + +igraph_int_t test_inc_to(igraph_t *g, igraph_inclist_t *inc) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *neis = igraph_inclist_get(inc, i); + igraph_int_t nneis = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + igraph_int_t neighbor = IGRAPH_TO(g, edge); + dummy ^= neighbor ^ edge; + } + } + return dummy; +} + +igraph_int_t test_inc_nop(igraph_t *g, igraph_inclist_t *inc) +{ + igraph_int_t dummy = 0; + igraph_int_t vcount = igraph_vcount(g); + + for (igraph_int_t i = 0; i < vcount; i++) { + igraph_vector_int_t *neis = igraph_inclist_get(inc, i); + igraph_int_t nneis = igraph_vector_int_size(neis); + for (igraph_int_t j = 0; j < nneis; j++) { + igraph_int_t edge = VECTOR(*neis)[j]; + dummy ^= edge; + } + } + return dummy; +} + +/* Used to prevent optimizing away the result. + * This must be a global variable to prevent "variable set but not used" + * warnings from some compilers. */ +volatile igraph_int_t result; + +void do_benchmarks(char *name, igraph_t *g, int repeat) { + igraph_adjlist_t adj; + igraph_inclist_t inc; + igraph_incadjlist_inter_t incadj_inter; + igraph_incadjlist_sep_t incadj_sep; + + /* The init() call is in a REPEAT loop, so we include destroy() as well to avoid a memory leak. + * This is representative of real use cases, where init/destroy should always come in pairs. */ + printf("%s", name); + BENCH("1 init/destroy adjlist.", + REPEAT( + do { + igraph_adjlist_init(g, &adj, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + igraph_adjlist_destroy(&adj); + } while (0), + repeat); + ); + + printf("%s", name); + BENCH("2 init/destroy inclist.", + REPEAT( + do { + igraph_inclist_init(g, &inc, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + igraph_inclist_destroy(&inc); + } while (0), + repeat); + ); + + printf("%s", name); + BENCH("3 init/destroy adjlist, remove loops and multiple (which aren't present).", + REPEAT( + do { + igraph_adjlist_init(g, &adj, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + igraph_adjlist_destroy(&adj); + } while (0), + repeat); + ); + + printf("%s", name); + BENCH("4 init/destroy inclist, remove loops (which aren't present).", + REPEAT( + do { + igraph_inclist_init(g, &inc, IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_inclist_destroy(&inc); + } while (0), + repeat); + ); + + /* Initialize adjlist / inclist for the following benchmarks. */ + igraph_adjlist_init(g, &adj, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + igraph_inclist_init(g, &inc, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("%s", name); + BENCH("5 go over vertices (multiple times) using adjlist.", + REPEAT(result = test_adj(g, &adj), repeat); + ); + + printf("%s", name); + BENCH("6 go over vertices (multiple times) using inclist, IGRAPH_OTHER.", + REPEAT(result = test_inc_other(g, &inc), repeat); + ); + + printf("%s", name); + BENCH("7 go over vertices (multiple times) using inclist, IGRAPH_TO.", + REPEAT(result = test_inc_to(g, &inc), repeat); + ); + + printf("%s", name); + BENCH("8 go over edges using inclist, don't retrieve vertex.", + REPEAT(result = test_inc_nop(g, &inc), repeat); + ); + + printf("%s", name); + BENCH("9 go over edges and vertices using adjlist and inclist.", + REPEAT(result = test_inc_adj(g, &inc, &adj), repeat); + ); + + igraph_adjlist_destroy(&adj); + igraph_inclist_destroy(&inc); + + printf("%s", name); + BENCH("10 go over edges and vertices using graph internals directly.", + REPEAT(result = test_direct(g), repeat); + ); + + printf("%s", name); + BENCH("11 init/destroy interleaved incadjlist.", + REPEAT( + do { + igraph_incadjlist_inter_init(g, &incadj_inter, IGRAPH_ALL); + igraph_incadjlist_inter_destroy(&incadj_inter); + } while (0), + repeat); + ); + + igraph_incadjlist_inter_init(g, &incadj_inter, IGRAPH_ALL); + + printf("%s", name); + BENCH("12 go over edges and vertices using interleaved incadjlist.", + REPEAT(result = test_incadj_inter(g, &incadj_inter), repeat); + ); + + igraph_incadjlist_inter_destroy(&incadj_inter); + + printf("%s", name); + BENCH("13 init/destroy incadjlist with two vectors.", + REPEAT( + do { + igraph_incadjlist_sep_init(g, &incadj_sep, IGRAPH_ALL); + igraph_incadjlist_sep_destroy(&incadj_sep); + } while (0), repeat); + ); + + igraph_incadjlist_sep_init(g, &incadj_sep, IGRAPH_ALL); + + printf("%s", name); + BENCH("14 go over edges and vertices using incadjlist.", + REPEAT(result = test_incadj_sep(g, &incadj_sep), repeat); + ); + + igraph_incadjlist_sep_destroy(&incadj_sep); +} + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + BENCH_INIT(); + + printf("Full graph tests:\n"); + igraph_full(&g, 10000, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + do_benchmarks(" fg - ", &g, 1); + igraph_destroy(&g); + + printf("\nRandom graph tests:\n"); + igraph_erdos_renyi_game_gnm(&g, 10000, 49994999, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + do_benchmarks(" rg - ", &g, 1); + igraph_destroy(&g); + + printf("\nSmall graph tests:\n"); + igraph_full(&g, 1000, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + do_benchmarks(" sg - ", &g, 100); + igraph_destroy(&g); + + return 0; +} diff --git a/tests/benchmarks/intersection.c b/tests/benchmarks/intersection.c new file mode 100644 index 0000000..f2a2f94 --- /dev/null +++ b/tests/benchmarks/intersection.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void rand_vec(igraph_vector_int_t *v, igraph_int_t n, igraph_int_t k) { + igraph_vector_int_resize(v, n); + for (igraph_int_t i=0; i < n; i++) { + VECTOR(*v)[i] = RNG_INTEGER(0, k); + } +} + +void run_bench(int i, int n, int r) { + igraph_vector_int_t a, b; + igraph_int_t na = n, nb = r*n; + int rep = 300000000 / nb; + char msg[255]; + + igraph_vector_int_init(&a, na); + igraph_vector_int_init(&b, nb); + + rand_vec(&a, na, nb); + igraph_vector_int_sort(&a); + rand_vec(&b, nb, nb); + igraph_vector_int_sort(&b); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%2d n = %5d, r = %3d, %dx", i, n, r, rep); + + /* 'volatile' is needed to prevent the compiler from optimizing + * away multiple calls to the function with the same parameters within REPEAT. + * This would normally happen due to the use of IGRAPH_FUNCATTR_PURE. + * ATTENTION! 'volatile', when used this way, may not prevent this optimization + * with future compiler versions. */ + volatile igraph_int_t res; + BENCH(msg, REPEAT(res = igraph_vector_int_intersection_size_sorted(&a, &b), rep)); + (void) res; /* silence unused-but-set-variable warning */ + + igraph_vector_int_destroy(&a); + igraph_vector_int_destroy(&b); +} + +int main(void) { + int i = 0; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + +#define BENCHSET(n) \ + run_bench(++i, n, 1); \ + run_bench(++i, n, 3); \ + run_bench(++i, n, 10); \ + run_bench(++i, n, 30); \ + run_bench(++i, n, 100); \ + printf("\n"); + + BENCHSET(1); + BENCHSET(3); + BENCHSET(10); + BENCHSET(30); + BENCHSET(100); + BENCHSET(300); + BENCHSET(1000); + BENCHSET(3000); + BENCHSET(10000); + BENCHSET(30000); + BENCHSET(100000); + + return 0; +} diff --git a/tests/benchmarks/lad.c b/tests/benchmarks/lad.c new file mode 100644 index 0000000..cd4a1a2 --- /dev/null +++ b/tests/benchmarks/lad.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void match(const igraph_t *graph, + const igraph_t patt[], igraph_int_t n, + igraph_vector_int_list_t *maps) { + + igraph_vector_int_list_clear(maps); + for (igraph_int_t i=0; i < n; i++) { + igraph_subisomorphic_lad(&patt[i], graph, NULL, NULL, NULL, maps, false); + } +} + +#define NP 8 + +int main(void) { + igraph_t graph; + igraph_t patt[NP]; + igraph_vector_int_list_t maps; + + BENCH_INIT(); + + igraph_vector_int_list_init(&maps, 0); + + for (igraph_int_t i=0; i < NP; i++) { + igraph_ring(&patt[i], i+1, IGRAPH_DIRECTED, false, true); + } + + igraph_kautz(&graph, 3, 3); + BENCH("1 Kautz(3,3) 10x", REPEAT(match(&graph, patt, NP, &maps), 10)); + igraph_destroy(&graph); + + igraph_kautz(&graph, 3, 4); + BENCH("2 Kautz(3,4) 3x", REPEAT(match(&graph, patt, NP, &maps), 3)); + igraph_destroy(&graph); + + igraph_kautz(&graph, 4, 3); + BENCH("3 Kautz(4,3) 1x", REPEAT(match(&graph, patt, NP, &maps), 1)); + igraph_destroy(&graph); + + for (igraph_int_t i=0; i < NP; i++) { + igraph_destroy(&patt[i]); + } + + igraph_vector_int_list_destroy(&maps); + + return 0; +} diff --git a/tests/benchmarks/modularity.c b/tests/benchmarks/modularity.c new file mode 100644 index 0000000..0c6127f --- /dev/null +++ b/tests/benchmarks/modularity.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void run_bench(int n) { + igraph_t graph; + igraph_matrix_t modmat; + int rep; + char msg[255]; + + rep = 20000 / n; + rep = rep*rep; + + igraph_matrix_init(&modmat, n, n); + + igraph_erdos_renyi_game_gnm(&graph, n, 10 * n, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "G(n,m), directed, n = %5d, m = %7" IGRAPH_PRId ", %dx", n, igraph_ecount(&graph), rep); + BENCH(msg, REPEAT(igraph_modularity_matrix(&graph, NULL, 1.0, &modmat, IGRAPH_DIRECTED), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "G(n,m), undirected, n = %5d, m = %7" IGRAPH_PRId ", %dx", n, igraph_ecount(&graph), rep); + BENCH(msg, REPEAT(igraph_modularity_matrix(&graph, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED), rep)); + + igraph_destroy(&graph); + + igraph_erdos_renyi_game_gnp(&graph, n, 0.1, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "G(n,p), directed, n = %5d, m = %7" IGRAPH_PRId ", %dx", n, igraph_ecount(&graph), rep); + BENCH(msg, REPEAT(igraph_modularity_matrix(&graph, NULL, 1.0, &modmat, IGRAPH_DIRECTED), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "G(n,p), undirected, n = %5d, m = %7" IGRAPH_PRId ", %dx", n, igraph_ecount(&graph), rep); + BENCH(msg, REPEAT(igraph_modularity_matrix(&graph, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED), rep)); + igraph_destroy(&graph); + + igraph_matrix_destroy(&modmat); + +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + run_bench(100); + run_bench(1000); + run_bench(10000); + + return 0; +} diff --git a/tests/benchmarks/spanning_tree.c b/tests/benchmarks/spanning_tree.c new file mode 100644 index 0000000..e937944 --- /dev/null +++ b/tests/benchmarks/spanning_tree.c @@ -0,0 +1,164 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bench.h" + +void run_bench(const igraph_t *graph, const igraph_vector_t *weights, int rep, const char *name) { + igraph_vector_int_t edges; + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t ecount = igraph_ecount(graph); + char msg[128]; + + igraph_vector_int_init(&edges, vcount - 1); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s, vcount=%" IGRAPH_PRId ", ecount=%" IGRAPH_PRId ", unweigthed, %dx", + name, vcount, ecount, rep); + BENCH(msg, REPEAT(igraph_minimum_spanning_tree(graph, &edges, NULL, IGRAPH_MST_UNWEIGHTED), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s, vcount=%" IGRAPH_PRId ", ecount=%" IGRAPH_PRId ", Prim, %dx", + name, vcount, ecount, rep); + BENCH(msg, REPEAT(igraph_minimum_spanning_tree(graph, &edges, weights, IGRAPH_MST_UNWEIGHTED), rep)); + + snprintf(msg, sizeof(msg) / sizeof(msg[0]), + "%s, vcount=%" IGRAPH_PRId ", ecount=%" IGRAPH_PRId ", Kruskal, %dx", + name, vcount, ecount, rep); + BENCH(msg, REPEAT(igraph_minimum_spanning_tree(graph, &edges, weights, IGRAPH_MST_UNWEIGHTED), rep)); + + printf("\n"); + + igraph_vector_int_destroy(&edges); +} + +void rand_weights(const igraph_t *graph, igraph_vector_t *weights) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_resize(weights, ecount); + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(*weights)[i] = RNG_UNIF01(); + } +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + + igraph_rng_seed(igraph_rng_default(), 137); + BENCH_INIT(); + + igraph_vector_init(&weights, 0); + + igraph_barabasi_game(&g, 100, 1, 1, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 50000, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 100, 1, 5, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 10000, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 1000, 1, 5, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 1000, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 10000, 1, 5, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 100, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 100, 1, 50, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 1000, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 1000, 1, 50, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 100, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 10000, 1, 50, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 10, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 1000, 1, 500, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 10, "PA"); + igraph_destroy(&g); + + igraph_barabasi_game(&g, 10000, 1, 500, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + rand_weights(&g, &weights); + run_bench(&g, &weights, 1, "PA"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 500, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 60000, "G(n,m)"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 1000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 30000, "G(n,m)"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 3000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 10000, "G(n,m)"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 10000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 3000, "G(n,m)"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 30000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 1000, "G(n,m)"); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1000, 100000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + run_bench(&g, &weights, 300, "G(n,m)"); + igraph_destroy(&g); + + igraph_full(&g, 100, false, false); + rand_weights(&g, &weights); + run_bench(&g, &weights, 10000, "K"); + igraph_destroy(&g); + + igraph_full(&g, 1000, false, false); + rand_weights(&g, &weights); + run_bench(&g, &weights, 100, "K"); + igraph_destroy(&g); + + { + igraph_t K; + igraph_full(&K, 100, false, false); + igraph_disjoint_union(&g, &K, &K); + igraph_destroy(&K); + } + rand_weights(&g, &weights); + run_bench(&g, &weights, 10000, "K + K"); + igraph_destroy(&g); + + return 0; +} diff --git a/tests/regression/bug-1033045.c b/tests/regression/bug-1033045.c new file mode 100644 index 0000000..adf79ca --- /dev/null +++ b/tests/regression/bug-1033045.c @@ -0,0 +1,43 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t separators; + + igraph_small(&graph, 0, /*directed=*/ 0, + 0, 1, 0, 2, 1, 3, 1, 4, 2, 3, 2, 5, 3, 4, 3, 5, 4, 6, 5, 6, -1); + igraph_vector_int_list_init(&separators, 0); + + igraph_all_minimal_st_separators(&graph, &separators); + + print_vector_int_list(&separators); + igraph_vector_int_list_destroy(&separators); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug-1033045.out b/tests/regression/bug-1033045.out new file mode 100644 index 0000000..04769a5 --- /dev/null +++ b/tests/regression/bug-1033045.out @@ -0,0 +1,11 @@ +{ + 0: ( 1 2 ) + 1: ( 0 3 4 ) + 2: ( 0 3 5 ) + 3: ( 4 5 ) + 4: ( 1 3 6 ) + 5: ( 2 3 6 ) + 6: ( 2 3 4 ) + 7: ( 1 3 5 ) + 8: ( 0 3 6 ) +} diff --git a/tests/regression/bug-1149658.c b/tests/regression/bug-1149658.c new file mode 100644 index 0000000..27b145e --- /dev/null +++ b/tests/regression/bug-1149658.c @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_vector_t mod; + + igraph_empty(&graph, 25, IGRAPH_UNDIRECTED); + igraph_vector_init(&mod, 0); + igraph_community_multilevel(&graph, /*weights=*/ 0, /*resolution=*/ 1, + /*membership=*/ 0, /*memberships=*/ 0, &mod); + + if (igraph_vector_size(&mod) != 1 || + !isnan(VECTOR(mod)[0])) { + return 1; + } + + igraph_vector_destroy(&mod); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_1760.c b/tests/regression/bug_1760.c new file mode 100644 index 0000000..7264a96 --- /dev/null +++ b/tests/regression/bug_1760.c @@ -0,0 +1,159 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "../unit/test_utilities.h" + +/* Regression test for https://github.com/igraph/igraph/issues/1760 */ + +int test_unweighted(const igraph_t* g, igraph_int_t from, const igraph_vs_t* to) { + igraph_vector_int_list_t vpath, epath; + igraph_int_t num_paths; + igraph_vector_int_t parents; + igraph_vector_int_t inbound_edges; + + printf("Unweighted case\n"); + printf("---------------\n\n"); + + IGRAPH_CHECK(igraph_vs_size(g, to, &num_paths)); + IGRAPH_CHECK(igraph_vector_int_list_init(&vpath, 0)); + IGRAPH_CHECK(igraph_vector_int_list_init(&epath, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&parents, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&inbound_edges, 0)); + + IGRAPH_CHECK(igraph_get_shortest_paths( + g, NULL, &vpath, &epath, from, *to, IGRAPH_IN, + &parents, &inbound_edges + )); + + printf("Vertices:\n"); + print_vector_int_list(&vpath); + printf("\n"); + + printf("Edges:\n"); + print_vector_int_list(&epath); + printf("\n"); + + printf("Parents:\n"); + print_vector_int(&parents); + printf("\n"); + + printf("Inbound edges:\n"); + print_vector_int(&inbound_edges); + printf("\n"); + + igraph_vector_int_destroy(&inbound_edges); + igraph_vector_int_destroy(&parents); + igraph_vector_int_list_destroy(&epath); + igraph_vector_int_list_destroy(&vpath); + + return IGRAPH_SUCCESS; +} + +int test_weighted( + const igraph_t* g, const igraph_vector_t* weights, igraph_int_t from, + const igraph_vs_t* to, igraph_bool_t use_bellman_ford +) { + igraph_vector_int_list_t vpath, epath; + igraph_int_t num_paths; + igraph_vector_int_t parents; + igraph_vector_int_t inbound_edges; + + printf("Weighted case\n"); + printf("-------------\n\n"); + + printf("Algorithm: %s\n\n", use_bellman_ford ? "Bellman-Ford" : "Dijkstra"); + + IGRAPH_CHECK(igraph_vs_size(g, to, &num_paths)); + IGRAPH_CHECK(igraph_vector_int_list_init(&vpath, 0)); + IGRAPH_CHECK(igraph_vector_int_list_init(&epath, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&parents, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&inbound_edges, 0)); + + if (use_bellman_ford) { + IGRAPH_CHECK(igraph_get_shortest_paths_bellman_ford( + g, &vpath, &epath, from, *to, weights, IGRAPH_IN, + &parents, &inbound_edges + )); + } else { + IGRAPH_CHECK(igraph_get_shortest_paths_dijkstra( + g, &vpath, &epath, from, *to, weights, IGRAPH_IN, + &parents, &inbound_edges + )); + } + + printf("Vertices:\n"); + print_vector_int_list(&vpath); + printf("\n"); + + printf("Edges:\n"); + print_vector_int_list(&epath); + printf("\n"); + + printf("Parents:\n"); + print_vector_int(&parents); + printf("\n"); + + printf("Inbound edges:\n"); + print_vector_int(&inbound_edges); + printf("\n"); + + igraph_vector_int_destroy(&inbound_edges); + igraph_vector_int_destroy(&parents); + igraph_vector_int_list_destroy(&epath); + igraph_vector_int_list_destroy(&vpath); + + return IGRAPH_SUCCESS; +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_vs_t to; + + igraph_set_warning_handler(igraph_warning_handler_ignore); + + igraph_small(&g, 4, /* directed = */ 1, 0, 1, 1, 2, 1, 3, -1); + igraph_vs_vector_small(&to, 0, 3, -1); + igraph_vector_init(&weights, 3); + VECTOR(weights)[0] = 1; + VECTOR(weights)[1] = 2; + VECTOR(weights)[2] = 2; + + /* Test unweighted case */ + if (test_unweighted(&g, 2, &to)) { + return 1; + } + + /* Test weighted case */ + if (test_weighted(&g, &weights, 2, &to, /* use_bellman_ford = */ 0)) { + return 2; + } + if (test_weighted(&g, &weights, 2, &to, /* use_bellman_ford = */ 1)) { + return 3; + } + + igraph_vector_destroy(&weights); + igraph_vs_destroy(&to); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_1760.out b/tests/regression/bug_1760.out new file mode 100644 index 0000000..d1722c2 --- /dev/null +++ b/tests/regression/bug_1760.out @@ -0,0 +1,67 @@ +Unweighted case +--------------- + +Vertices: +{ + 0: ( 2 1 0 ) + 1: ( ) +} + +Edges: +{ + 0: ( 1 0 ) + 1: ( ) +} + +Parents: +( 1 2 -1 -2 ) + +Inbound edges: +( 0 1 -1 -1 ) + +Weighted case +------------- + +Algorithm: Dijkstra + +Vertices: +{ + 0: ( 2 1 0 ) + 1: ( ) +} + +Edges: +{ + 0: ( 1 0 ) + 1: ( ) +} + +Parents: +( 1 2 -1 -2 ) + +Inbound edges: +( 0 1 -1 -1 ) + +Weighted case +------------- + +Algorithm: Bellman-Ford + +Vertices: +{ + 0: ( 2 1 0 ) + 1: ( ) +} + +Edges: +{ + 0: ( 1 0 ) + 1: ( ) +} + +Parents: +( 1 2 -1 -2 ) + +Inbound edges: +( 0 1 -1 -1 ) + diff --git a/tests/regression/bug_1814.c b/tests/regression/bug_1814.c new file mode 100644 index 0000000..7cd13e6 --- /dev/null +++ b/tests/regression/bug_1814.c @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "../unit/test_utilities.h" + +/* Regression test for https://github.com/igraph/igraph/issues/1814 */ + +void test_igraph_to_undirected(igraph_to_undirected_t mode) { + igraph_t g; + igraph_attribute_combination_t comb; + + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 0,1, 1,0, + 1,1, 1,1, + -1); + + SETEAN(&g, "weight", 0, 1); + SETEAN(&g, "weight", 1, 2); + SETEAN(&g, "weight", 2, 3); + SETEAN(&g, "weight", 3, 4); + SETEAN(&g, "weight", 4, 2); + + igraph_attribute_combination( + &comb, "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + IGRAPH_NO_MORE_ATTRIBUTES + ); + igraph_to_undirected(&g, mode, &comb); + igraph_attribute_combination_destroy(&comb); + + igraph_write_graph_gml(&g, stdout, IGRAPH_WRITE_GML_DEFAULT_SW, 0, ""); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + igraph_set_attribute_table(&igraph_cattribute_table); + + printf("to_undirected(COLLAPSE)\n"); + printf("=======================\n\n"); + test_igraph_to_undirected(IGRAPH_TO_UNDIRECTED_COLLAPSE); + printf("\n"); + + printf("to_undirected(MUTUAL)\n"); + printf("=====================\n\n"); + test_igraph_to_undirected(IGRAPH_TO_UNDIRECTED_MUTUAL); + printf("\n"); + + printf("to_undirected(EACH)\n"); + printf("===================\n\n"); + test_igraph_to_undirected(IGRAPH_TO_UNDIRECTED_EACH); + printf("\n"); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_1814.out b/tests/regression/bug_1814.out new file mode 100644 index 0000000..89cb99a --- /dev/null +++ b/tests/regression/bug_1814.out @@ -0,0 +1,135 @@ +to_undirected(COLLAPSE) +======================= + +Version 1 +graph +[ + directed 0 + node + [ + id 0 + ] + node + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + edge + [ + source 1 + target 0 + weight 6 + ] + edge + [ + source 1 + target 1 + weight 6 + ] +] + +to_undirected(MUTUAL) +===================== + +Version 1 +graph +[ + directed 0 + node + [ + id 0 + ] + node + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + edge + [ + source 1 + target 0 + weight 5 + ] + edge + [ + source 1 + target 1 + weight 2 + ] + edge + [ + source 1 + target 1 + weight 4 + ] +] + +to_undirected(EACH) +=================== + +Version 1 +graph +[ + directed 0 + node + [ + id 0 + ] + node + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + edge + [ + source 1 + target 0 + weight 1 + ] + edge + [ + source 1 + target 0 + weight 2 + ] + edge + [ + source 1 + target 0 + weight 3 + ] + edge + [ + source 1 + target 1 + weight 4 + ] + edge + [ + source 1 + target 1 + weight 2 + ] +] + diff --git a/tests/regression/bug_1970.c b/tests/regression/bug_1970.c new file mode 100644 index 0000000..b68f060 --- /dev/null +++ b/tests/regression/bug_1970.c @@ -0,0 +1,44 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + FILE *file; + igraph_t graph; + igraph_error_t err; + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_set_error_handler(igraph_error_handler_printignore); + + file = fopen("bug_1970.graphml", "r"); + IGRAPH_ASSERT(file != NULL); + + err = igraph_read_graph_graphml(&graph, file, 0); + fclose(file); + IGRAPH_ASSERT(err != IGRAPH_SUCCESS); + + /* graph creation should have failed, no need to destroy 'graph' */ + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_1970.graphml b/tests/regression/bug_1970.graphml new file mode 100644 index 0000000..f2f4a8c --- /dev/null +++ b/tests/regression/bug_1970.graphml @@ -0,0 +1 @@ + diff --git a/tests/regression/bug_2150.c b/tests/regression/bug_2150.c new file mode 100644 index 0000000..c17b8ba --- /dev/null +++ b/tests/regression/bug_2150.c @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "../unit/test_utilities.h" + +/* Regression test for https://github.com/igraph/igraph/issues/2150 */ + +int main(void) { + igraph_t graph; + igraph_vector_t w; + igraph_vector_int_list_t res; + + igraph_full(&graph, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_vector_init_int(&w, 3, 1, 2, 3); + igraph_vector_int_list_init(&res, 0); + + igraph_weighted_cliques(&graph, &w, &res, false, 1, 3, IGRAPH_UNLIMITED); + + igraph_vector_int_list_destroy(&res); + igraph_vector_destroy(&w); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_2497.c b/tests/regression/bug_2497.c new file mode 100644 index 0000000..137d516 --- /dev/null +++ b/tests/regression/bug_2497.c @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + FILE *file; + igraph_t graph; + + igraph_set_attribute_table(&igraph_cattribute_table); + igraph_set_error_handler(igraph_error_handler_ignore); + igraph_set_warning_handler(igraph_warning_handler_ignore); + + file = fopen("bug_2497.gml", "r"); + IGRAPH_ASSERT(file != NULL); + + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + IGRAPH_ASSERT(igraph_read_graph_gml(&graph, file) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + + fclose(file); + + igraph_write_graph_gml(&graph, stdout, IGRAPH_WRITE_GML_DEFAULT_SW, NULL, ""); + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_2497.gml b/tests/regression/bug_2497.gml new file mode 100644 index 0000000..5014ac6 --- /dev/null +++ b/tests/regression/bug_2497.gml @@ -0,0 +1,4 @@ +graph [ + node [ id 2 ] + node [ ] +] diff --git a/tests/regression/bug_2497.out b/tests/regression/bug_2497.out new file mode 100644 index 0000000..e6a1cd4 --- /dev/null +++ b/tests/regression/bug_2497.out @@ -0,0 +1,13 @@ +Version 1 +graph +[ + directed 0 + node + [ + id 0 + ] + node + [ + id 1 + ] +] diff --git a/tests/regression/bug_2506.c b/tests/regression/bug_2506.c new file mode 100644 index 0000000..43e8893 --- /dev/null +++ b/tests/regression/bug_2506.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +int main(void) { + igraph_t g; + igraph_error_t result; + FILE *ifile; + + igraph_set_error_handler(igraph_error_handler_ignore); + + igraph_set_attribute_table(&igraph_cattribute_table); + + ifile = fopen("bug_2506_1.graphml", "r"); + IGRAPH_ASSERT(ifile != NULL); + + result = igraph_read_graph_graphml(&g, ifile, 0); + fclose(ifile); + + if (result == IGRAPH_UNIMPLEMENTED) { + /* maybe it is simply disabled at compile-time */ + return 77; + } + + IGRAPH_ASSERT(result == IGRAPH_PARSEERROR); + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + + ifile = fopen("bug_2506_2.graphml", "r"); + IGRAPH_ASSERT(ifile != NULL); + + result = igraph_read_graph_graphml(&g, ifile, 0); + fclose(ifile); + + IGRAPH_ASSERT(result == IGRAPH_PARSEERROR); + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + + ifile = fopen("bug_2506_3.graphml", "r"); + IGRAPH_ASSERT(ifile != NULL); + + result = igraph_read_graph_graphml(&g, ifile, 0); + fclose(ifile); + + IGRAPH_ASSERT(result == IGRAPH_PARSEERROR); + IGRAPH_ASSERT(IGRAPH_FINALLY_STACK_EMPTY); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_2506_1.graphml b/tests/regression/bug_2506_1.graphml new file mode 100644 index 0000000..5ab8579 --- /dev/null +++ b/tests/regression/bug_2506_1.graphml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/regression/bug_2506_2.graphml b/tests/regression/bug_2506_2.graphml new file mode 100644 index 0000000..0e31209 --- /dev/null +++ b/tests/regression/bug_2506_2.graphml @@ -0,0 +1 @@ + diff --git a/tests/regression/bug_2506_3.graphml b/tests/regression/bug_2506_3.graphml new file mode 100644 index 0000000..a2297e0 --- /dev/null +++ b/tests/regression/bug_2506_3.graphml @@ -0,0 +1 @@ + diff --git a/tests/regression/bug_2517.c b/tests/regression/bug_2517.c new file mode 100644 index 0000000..e63b4e7 --- /dev/null +++ b/tests/regression/bug_2517.c @@ -0,0 +1,44 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_list_t result; + + igraph_vector_int_list_init(&result, 0); + + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_all_minimal_st_separators(&g, &result); + print_vector_int_list(&result); + igraph_destroy(&g); + + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_all_minimal_st_separators(&g, &result); + print_vector_int_list(&result); + igraph_destroy(&g); + + igraph_vector_int_list_destroy(&result); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_2517.out b/tests/regression/bug_2517.out new file mode 100644 index 0000000..8a0e2c0 --- /dev/null +++ b/tests/regression/bug_2517.out @@ -0,0 +1,4 @@ +{ +} +{ +} diff --git a/tests/regression/bug_2608.c b/tests/regression/bug_2608.c new file mode 100644 index 0000000..d3d5b8f --- /dev/null +++ b/tests/regression/bug_2608.c @@ -0,0 +1,49 @@ + +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t membership; + + /* label propagation is a stochastic method */ + igraph_rng_seed(igraph_rng_default(), 765); + + igraph_small(&g, 0, /* directed = */ 1, + 0, 1, 0, 2, 1, 0, 1, 0, 1, 1, 2, 0, 2, 1, 2, 1, 2, 1, -1); + igraph_vector_int_init(&membership, 0); + + igraph_community_label_propagation(&g, &membership, IGRAPH_OUT, NULL, NULL, NULL, IGRAPH_LPA_FAST); + + print_vector_int(&membership); + igraph_vector_int_destroy(&membership); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/bug_2608.out b/tests/regression/bug_2608.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/cattr_bool_bug.c b/tests/regression/cattr_bool_bug.c new file mode 100644 index 0000000..a7950b1 --- /dev/null +++ b/tests/regression/cattr_bool_bug.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +void check_attr(igraph_t *graph) { + IGRAPH_ASSERT(igraph_cattribute_has_attr(graph, IGRAPH_ATTRIBUTE_GRAPH, "name")); + IGRAPH_ASSERT(igraph_cattribute_has_attr(graph, IGRAPH_ATTRIBUTE_GRAPH, "type")); + IGRAPH_ASSERT(igraph_cattribute_has_attr(graph, IGRAPH_ATTRIBUTE_GRAPH, "p")); + IGRAPH_ASSERT(igraph_cattribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, "name")); + IGRAPH_ASSERT(igraph_cattribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, "weight")); +} + +int main(void) { + + igraph_t graph; + igraph_error_handler_t* oldhandler; + int result; + FILE *ifile = fopen("cattr_bool_bug.graphml", "r"); + + if (!ifile) { + printf("Cannot open input file\n"); + return 1; + } + + igraph_set_attribute_table(&igraph_cattribute_table); + + oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + if ((result = igraph_read_graph_graphml(&graph, ifile, 0))) { + /* Maybe it is simply disabled at compile-time. If so, skip test. */ + if (result == IGRAPH_UNIMPLEMENTED) { + return 77; + } + printf("Failed to read GraphML file\n"); + return 1; + } + igraph_set_error_handler(oldhandler); + + fclose(ifile); + + printf("Checkng attributes of original graph\n"); + check_attr(&graph); + + printf("Checkng attributes of directed version\n"); + igraph_to_directed(&graph, IGRAPH_TO_DIRECTED_ARBITRARY); + check_attr(&graph); + + IGRAPH_ASSERT(! GAB(&graph, "loops")); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/cattr_bool_bug.graphml b/tests/regression/cattr_bool_bug.graphml new file mode 100644 index 0000000..8ed39fe --- /dev/null +++ b/tests/regression/cattr_bool_bug.graphml @@ -0,0 +1,76 @@ + + + + + + + + + + + Erdos renyi (gnp) graph + gnp + false + 0.2 + + n0 + + + n1 + + + n2 + + + n3 + + + n4 + + + n5 + + + n6 + + + n7 + + + n8 + + + n9 + + + 10 + + + 11 + + + 12 + + + 13 + + + 14 + + + 15 + + + 16 + + + 17 + + + 18 + + + diff --git a/tests/regression/cattr_bool_bug2.c b/tests/regression/cattr_bool_bug2.c new file mode 100644 index 0000000..b7f0630 --- /dev/null +++ b/tests/regression/cattr_bool_bug2.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +#define FILENAME "mybool.graphml.xml" + +int main(void) { + + igraph_t graph; + igraph_error_handler_t* oldhandler; + int result; + FILE* ifile = fopen("cattr_bool_bug2.graphml", "r"); + + if (!ifile) { + printf("Cannot open input file\n"); + return 1; + } + + igraph_set_attribute_table(&igraph_cattribute_table); + + oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + if ((result = igraph_read_graph_graphml(&graph, ifile, 0))) { + /* maybe it is simply disabled at compile-time */ + if (result == IGRAPH_UNIMPLEMENTED) { + return 77; + } + printf("Failed to read GraphML file\n"); + return 1; + } + igraph_set_error_handler(oldhandler); + + fclose(ifile); + + IGRAPH_ASSERT(igraph_cattribute_has_attr(&graph, IGRAPH_ATTRIBUTE_GRAPH, "mybool")); + + /* Boolean attribute value is expected to be true */ + IGRAPH_ASSERT(igraph_cattribute_GAB(&graph, "mybool")); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/cattr_bool_bug2.graphml b/tests/regression/cattr_bool_bug2.graphml new file mode 100644 index 0000000..92c78f6 --- /dev/null +++ b/tests/regression/cattr_bool_bug2.graphml @@ -0,0 +1,7 @@ + + + + + True + + diff --git a/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.c b/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.c new file mode 100644 index 0000000..cae7220 --- /dev/null +++ b/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +void snap_to_zero(igraph_real_t* value) { + if (fabs(*value) < 1e-5) { + *value = 0.0; + } +} + +int main(void) { + igraph_t graph; + igraph_matrix_t layout; + igraph_int_t i; + + if (igraph_empty(&graph, 2, 0)) { + return 1; + } + + if (igraph_add_edge(&graph, 0, 1)) { + return 2; + } + + if (igraph_matrix_init(&layout, 0, 0)) { + return 3; + } + + if (igraph_layout_kamada_kawai_3d(&graph, &layout, 0, 200, 0, 2, 0, 0, 0, 0, 0, 0, 0)) { + return 4; + } + + /* Snap numbers close to zero in the layout; there are false failures on + * MinGW if we don't do so */ + for (i = 0; i < 2; i++) { + snap_to_zero(&MATRIX(layout, i, 0)); + snap_to_zero(&MATRIX(layout, i, 1)); + snap_to_zero(&MATRIX(layout, i, 2)); + } + print_matrix_format(&layout, stdout, "%.2f"); + + igraph_matrix_destroy(&layout); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out b/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out new file mode 100644 index 0000000..f50fbcb --- /dev/null +++ b/tests/regression/igraph_layout_kamada_kawai_3d_bug_1462.out @@ -0,0 +1,2 @@ +[ 0.00 0.00 -0.91 + 0.00 0.00 0.51 ] diff --git a/tests/regression/igraph_layout_reingold_tilford_bug_879.c b/tests/regression/igraph_layout_reingold_tilford_bug_879.c new file mode 100644 index 0000000..6139490 --- /dev/null +++ b/tests/regression/igraph_layout_reingold_tilford_bug_879.c @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include + +#include "../unit/test_utilities.h" + +int main(void) { + + igraph_t g; + FILE *f; + igraph_matrix_t coords; + igraph_vector_int_t roots; + igraph_int_t i, n; + + f = fopen("igraph_layout_reingold_tilford_bug_879.in", "r"); + IGRAPH_ASSERT(f != NULL); + igraph_read_graph_edgelist(&g, f, 0, IGRAPH_UNDIRECTED); + igraph_matrix_init(&coords, 0, 0); + igraph_vector_int_init(&roots, 0); + igraph_vector_int_push_back(&roots, 0); + + igraph_layout_reingold_tilford(&g, &coords, IGRAPH_OUT, &roots, 0); + + n = igraph_vcount(&g); + for (i = 0; i < n; i++) { + printf("%6.3f %6.3f\n", MATRIX(coords, i, 0), MATRIX(coords, i, 1)); + } + + igraph_matrix_destroy(&coords); + igraph_vector_int_destroy(&roots); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/igraph_layout_reingold_tilford_bug_879.in b/tests/regression/igraph_layout_reingold_tilford_bug_879.in new file mode 100644 index 0000000..6ea94ee --- /dev/null +++ b/tests/regression/igraph_layout_reingold_tilford_bug_879.in @@ -0,0 +1,15 @@ +0 1 +0 2 +0 3 +1 4 +2 5 +2 6 +3 7 +4 8 +4 9 +4 10 +4 11 +4 12 +7 13 +7 14 +7 15 diff --git a/tests/regression/igraph_layout_reingold_tilford_bug_879.out b/tests/regression/igraph_layout_reingold_tilford_bug_879.out new file mode 100644 index 0000000..b916553 --- /dev/null +++ b/tests/regression/igraph_layout_reingold_tilford_bug_879.out @@ -0,0 +1,16 @@ + 0.000 0.000 +-1.833 1.000 +-0.333 1.000 + 2.167 1.000 +-1.833 2.000 +-0.833 2.000 + 0.167 2.000 + 2.167 2.000 +-3.833 3.000 +-2.833 3.000 +-1.833 3.000 +-0.833 3.000 + 0.167 3.000 + 1.167 3.000 + 2.167 3.000 + 3.167 3.000 diff --git a/tests/regression/igraph_read_graph_gml_invalid_inputs.c b/tests/regression/igraph_read_graph_gml_invalid_inputs.c new file mode 100644 index 0000000..913329e --- /dev/null +++ b/tests/regression/igraph_read_graph_gml_invalid_inputs.c @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +int test_file(const char* fname) { + FILE *ifile; + igraph_t g; + int retval; + + ifile = fopen(fname, "r"); + if (ifile == 0) { + return 1; + } + + retval = igraph_read_graph_gml(&g, ifile); + if (!retval) { + /* input was accepted, this is a bug; attempt to clean up after + * ourselves nevertheless */ + igraph_destroy(&g); + fclose(ifile); + return 2; + } + + fclose(ifile); + + return 0; +} + +#undef RUN_TEST +#define RUN_TEST(fname) { \ + index++; \ + if (test_file(fname)) { \ + return index; \ + } \ + VERIFY_FINALLY_STACK(); \ +} + +int main(void) { + int index = 0; + + /* We do not care about errors; all we care about is that the library + * should not segfault and should not accept invalid input either */ + igraph_set_error_handler(igraph_error_handler_ignore); + + RUN_TEST("invalid1.gml"); + RUN_TEST("invalid2.gml"); + /* invalid3.gml was removed, parser now supports it */ + RUN_TEST("invalid4.gml"); + RUN_TEST("invalid5.gml"); + RUN_TEST("invalid6.gml"); + + return 0; +} diff --git a/tests/regression/igraph_read_graph_graphml_invalid_inputs.c b/tests/regression/igraph_read_graph_graphml_invalid_inputs.c new file mode 100644 index 0000000..4c9903c --- /dev/null +++ b/tests/regression/igraph_read_graph_graphml_invalid_inputs.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include +#include + +#include "../unit/test_utilities.h" + +int test_file(const char* fname, igraph_bool_t should_parse) { + FILE *ifile; + igraph_t g; + igraph_error_t retval; + + ifile = fopen(fname, "r"); + if (ifile == 0) { + printf("Cannot open input file: %s\n", fname); + return 1; + } + + retval = igraph_read_graph_graphml(&g, ifile, 0); + if (retval == IGRAPH_SUCCESS) { + /* destroy the graph if we managed to parse it */ + igraph_destroy(&g); + } + + fclose(ifile); + + if (!should_parse && retval == IGRAPH_SUCCESS) { + /* input was accepted but it should not have been, this is a bug */ + return 2; + } else { + return 0; + } +} + +#undef RUN_TEST +#define RUN_TEST(fname, should_parse) { \ + index++; \ + if (test_file(fname, should_parse)) { \ + return index; \ + } \ + VERIFY_FINALLY_STACK(); \ +} + +int main(void) { + int index = 0; + + /* We do not care about errors; all we care about is that the library + * should not segfault, should not accept invalid input and should not + * print anything to stdout or stderr while parsing, apart from warnings */ + igraph_set_error_handler(igraph_error_handler_ignore); + igraph_set_warning_handler(igraph_warning_handler_ignore); + + RUN_TEST("invalid1.graphml", /* should_parse = */ 0); + RUN_TEST("invalid2.graphml", /* should_parse = */ 1); + RUN_TEST("invalid3.graphml", /* should_parse = */ 0); + RUN_TEST("invalid4.graphml", /* should_parse = */ 0); + RUN_TEST("invalid5.graphml", /* should_parse = */ 0); + RUN_TEST("invalid6.graphml", /* should_parse = */ 0); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/regression/igraph_read_graph_graphml_invalid_inputs.out b/tests/regression/igraph_read_graph_graphml_invalid_inputs.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/igraph_read_graph_pajek_invalid_inputs.c b/tests/regression/igraph_read_graph_pajek_invalid_inputs.c new file mode 100644 index 0000000..7b00539 --- /dev/null +++ b/tests/regression/igraph_read_graph_pajek_invalid_inputs.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include +#include + +int test_file(const char* fname) { + FILE *ifile; + igraph_t g; + int retval; + + ifile = fopen(fname, "r"); + if (ifile == 0) { + return 1; + } + + retval = igraph_read_graph_pajek(&g, ifile); + if (!retval) { + /* input was accepted, this is a bug; attempt to clean up after + * ourselves nevertheless */ + igraph_destroy(&g); + fclose(ifile); + return 2; + } + + fclose(ifile); + + return 0; +} + +#define RUN_TEST(fname) { \ + index++; \ + if (test_file(fname)) { \ + return index; \ + } \ +} + +int main(void) { + int index = 0; + + /* Turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* We do not care about errors; all we care about is that the library + * should not segfault and should not accept invalid input either */ + igraph_set_error_handler(igraph_error_handler_ignore); + + RUN_TEST("invalid_pajek1.net"); + RUN_TEST("invalid_pajek2.net"); + RUN_TEST("invalid_pajek3.net"); + + return 0; +} diff --git a/tests/regression/invalid1.gml b/tests/regression/invalid1.gml new file mode 100644 index 0000000..38d8e42 --- /dev/null +++ b/tests/regression/invalid1.gml @@ -0,0 +1,463 @@ +Creator "Mark Newman on Fri Jul 21 12:39:27 2006" +graph +[ + ntde + [ + id 1 + ] + node + [ + id 2 + ] + node + [ + id 3 + ] + node + [ + id 4 + ] + node + [ + id 5 + ] + node + [ + id 6 + ] + node + [ + id 7 + ] + node + [ + id 8 + ] + node + [ + id 9 + ] + node + [ + id 10 + ] + node + [ + id 11 + ] + node + [ + id 12 + ] + node + [ + id 13 + ] + node + [ + id 14 + ] + node + [ + id 15 + ] + node + [ + id 16 + ] + node + [ + id 17 + ] + node + [ + id 18 + ] + node + [ + id 19 + ] + node + [ + id 20 + ] + node + [ + id 21 + ] + node + [ + id 22 + ] + node + [ + id 23 + ] + node + [ + id 24 + ] + node + [ + id 25 + ] + node + [ + id 26 + ] + node + [ + id 27 + ] + node + [ + id 28 + ] + node + [ + id 29 + ] + node + [ + id 30 + ] + node + [ + id 31 + ] + node + [ + id 32 + ] + node + [ + id 33 + ] + node + [ + id 34 + ] + edge + [ + source 2 + target 1 + ] + edge + [ + source 3 + target 1 + ] + edge + [ + source 3 + target 2 + ] + edge + [ + source 4 + target 1 + ] + edge + [ + source 4 + target 2 + ] + edge + [ + source 4 + target 3 + ] + edge + [ + source 5 + target 1 + ] + edge + [ + source 2 + target 1 + ] + edge + [ + source 7 + target 1 + ] + edge + [ + source 7 + target 5 + ] + edge + [ + source 7 + target 6 + ] + edge + [ + source 8 + target 1 + ] + edge + [ + source 8 + target 2 + ] + edge + [ + source 8 + target 3 + ] + edge + [ + source 8 + target 4 + ] + edge + [ + source 9 + target 1 + ] + edge + [ + source 9 + target 3 + ] + edge + [ + source 10 + target 3 + ] + edge + [ + source 11 + target 1 + + source 20 + target 1 + ] + edge + [ + source 20 + target 2 + ] + edge + [ + source 22 + target 1 + ] + edge + [ + source 22 + target 2 + ] + edge + [ + source 26 + target 24 + ] + edge + [ + source 26 + target 25 + ] + edge + [ + source 28 + target 3 + ] + edge + [ + source 28 + target 24 + ] + edge + [ + source 28 + target 25 + ] + edge + [ + source 29 + target 3 + ] + edge + [ + source 30 + target 24 + ] + edge + [ + source 30 + target 27 + ] + edge + [ + source 31 + target 2 + ] + edge + [ + source 31 + target 9 + ] + edge + [ + source 32 + target 1 + ] + edge + [ + source 32 + target 25 + ] + edge + [ + source 32 + target 26 + ] + edge + [ + source 32 + target 29 + ] + edge + [ + source 33 + target 3 + ] + edge + [ + source 33 + target 9 + ] + edge + [ + source 33 + target 15 + ] + edge + [ + source 33 + target 16 + ] + edge + [ + source 33 + target 19 + ] + edge + [ + source 33 + target 21 + ] + edge + [ + source 33 + target 23 + ] + edge + [ + source 33 + target 24 + ] + edge + [ + source 33 + target 30 + ] + edge + [ + source 33 + target 31 + ] + edge + [ + source 33 + target 32 + ] + edge + [ + source 34 + target 9 + ] + edge + [ + source 34 + target 10 + ] + edge + [ + source 34 + target 14 + ] + edge + [ + source 34 + target 15 + ] + edge + [ + source 34 + target 16 + ] + edge + [ + source 34 + target 19 + ] + edge + [ + source 34 + target 20 + ] + edge + [ + source 34 + target 21 + ] + edge + [ + source 34 + target 23 + ] + edge + [ + source 34 + target 24 + ] + edge + [ + source 34 + target 27 + ] + edge + [ + source 34 + target 28 + ] + edge + [ + source 34 + target 29 + ] + edge + [ + source 17 + target 30 + ] + edge + [ + source 34 + target 31 + ] + edge + [ + source 34 + target 32 + ] + edge + [ + source 34 + target 33 + ] +] diff --git a/tests/regression/invalid1.graphml b/tests/regression/invalid1.graphml new file mode 100644 index 0000000..c370a24 --- /dev/null +++ b/tests/regression/invalid1.graphml @@ -0,0 +1,18 @@ + + + + + yellow + + + + + + , 1 + + + 2006-11-12* + blue + + + diff --git a/tests/regression/invalid2.gml b/tests/regression/invalid2.gml new file mode 100644 index 0000000..bc4d7ba --- /dev/null +++ b/tests/regression/invalid2.gml @@ -0,0 +1 @@ + d 1 diff --git a/tests/regression/invalid2.graphml b/tests/regression/invalid2.graphml new file mode 100644 index 0000000..3f4ea6f --- /dev/null +++ b/tests/regression/invalid2.graphml @@ -0,0 +1,26 @@ + + + + + yellow + + + + @ yellowwe + +"http://graphml.graphdrawingg/2001/XMLrawing.org/xmlns/1.0/graphml.xsd"> + + yellow + + + + + @ yellowwe + + + 1 + + + /edge> + + diff --git a/tests/regression/invalid3.graphml b/tests/regression/invalid3.graphml new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/invalid4.gml b/tests/regression/invalid4.gml new file mode 100644 index 0000000..709a772 --- /dev/null +++ b/tests/regression/invalid4.gml @@ -0,0 +1 @@ +Version-0 diff --git a/tests/regression/invalid4.graphml b/tests/regression/invalid4.graphml new file mode 100644 index 0000000000000000000000000000000000000000..739907ac460acf51592c0c9adf36c00ade6c8bb2 GIT binary patch literal 92 lcmcCvKn9M;98|Uf3b(W*%>pd&3q=^hQ7FqxXK1iJ4FHX!1`+@O literal 0 HcmV?d00001 diff --git a/tests/regression/invalid5.gml b/tests/regression/invalid5.gml new file mode 100644 index 0000000..672d31f --- /dev/null +++ b/tests/regression/invalid5.gml @@ -0,0 +1,4 @@ +graph[node[a +r +r P p +r d v]] diff --git a/tests/regression/invalid5.graphml b/tests/regression/invalid5.graphml new file mode 100644 index 0000000..26c40d7 --- /dev/null +++ b/tests/regression/invalid5.graphml @@ -0,0 +1,30 @@ + + + + yellYw + + + + 1 + ta> + green + + true + + + + blue + 0 + red "w" + + false + + + t + + i + + + + + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "core/indheap.h" + +#include "test_utilities.h" + +int main(void) { + + igraph_vector_t elems; + igraph_2wheap_t Q; + igraph_int_t i; + igraph_real_t prev = IGRAPH_INFINITY; + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + igraph_vector_init(&elems, 100); + for (i = 0; i < igraph_vector_size(&elems); i++) { + VECTOR(elems)[i] = RNG_UNIF01(); + } + + igraph_2wheap_init(&Q, igraph_vector_size(&elems)); + for (i = 0; i < igraph_vector_size(&elems); i++) { + igraph_2wheap_push_with_index(&Q, i, VECTOR(elems)[i]); + } + + /*****/ + + for (i = 0; i < igraph_vector_size(&elems); i++) { + if (VECTOR(elems)[i] != igraph_2wheap_get(&Q, i)) { + return 1; + } + } + + /*****/ + + for (i = 0; i < igraph_vector_size(&elems); i++) { + igraph_int_t j; + igraph_real_t tmp = igraph_2wheap_max(&Q); + if (tmp > prev) { + return 2; + } + if (tmp != igraph_2wheap_delete_max_index(&Q, &j)) { + return 3; + } + if (VECTOR(elems)[j] != tmp) { + return 4; + } + prev = tmp; + } + + /*****/ + + for (i = 0; i < igraph_vector_size(&elems); i++) { + igraph_2wheap_push_with_index(&Q, i, VECTOR(elems)[i]); + } + if (igraph_2wheap_size(&Q) != igraph_vector_size(&elems)) { + return 5; + } + for (i = 0; i < igraph_vector_size(&elems); i++) { + VECTOR(elems)[i] = RNG_UNIF01(); + igraph_2wheap_modify(&Q, i, VECTOR(elems)[i]); + } + for (i = 0; i < igraph_vector_size(&elems); i++) { + if (VECTOR(elems)[i] != igraph_2wheap_get(&Q, i)) { + return 6; + } + } + prev = IGRAPH_INFINITY; + for (i = 0; i < igraph_vector_size(&elems); i++) { + igraph_int_t j; + igraph_real_t tmp = igraph_2wheap_max(&Q); + if (tmp > prev) { + return 7; + } + if (tmp != igraph_2wheap_delete_max_index(&Q, &j)) { + return 8; + } + if (VECTOR(elems)[j] != tmp) { + return 9; + } + prev = tmp; + } + if (!igraph_2wheap_empty(&Q)) { + return 10; + } + if (igraph_2wheap_size(&Q) != 0) { + return 11; + } + + igraph_2wheap_destroy(&Q); + igraph_vector_destroy(&elems); + + /* Hand-made example */ + +#define MAX do { igraph_2wheap_delete_max(&Q); igraph_2wheap_check(&Q); } while (0) +#define PUSH(i,e) do { igraph_2wheap_push_with_index(&Q, (i), -(e)); igraph_2wheap_check(&Q); } while (0); +#define MOD(i, e) do { igraph_2wheap_modify(&Q, (i), -(e)); igraph_2wheap_check(&Q); } while (0) + + igraph_2wheap_init(&Q, 21); + /* 0.00 [ 4] */ PUSH(4, 0); + /* MAX */ MAX; + /* 0.63 [11] */ PUSH(11, 0.63); + /* 0.05 [15] */ PUSH(15, 0.05); + /* MAX */ MAX; + /* 0.4 [12] */ PUSH(12, 0.4); + /* 0.4 [13] */ PUSH(13, 0.4); + /* 0.12 [16] */ PUSH(16, 0.12); + /* MAX */ MAX; + /* 1.1 [ 0] */ PUSH(0, 1.1); + /* 1.1 [14] */ PUSH(14, 1.1); + /* MAX */ MAX; + /* [11]/0.44 */ MOD(11, 0.44); + /* MAX */ MAX; + /* MAX */ MAX; + /* 1.1 [20] */ PUSH(20, 1.1); + /* MAX */ MAX; + /* 1.3 [ 7] */ PUSH(7, 1.3); + /* 1.7 [ 9] */ PUSH(9, 1.7); + /* MAX */ MAX; + /* 1.6 [19] */ PUSH(19, 1.6); + /* MAX */ MAX; + /* 2.1 [17] */ PUSH(17, 2.1); + /* 1.3 [18] */ PUSH(18, 1.3); + /* MAX */ MAX; + /* 2.3 [ 1] */ PUSH(1, 2.3); + /* 2.2 [ 5] */ PUSH(5, 2.2); + /* 2.3 [10] */ PUSH(10, 2.3); + /* MAX */ MAX; + /* [17]/1.5 */ MOD(17, 1.5); + /* MAX */ MAX; + /* 1.8 [ 6] */ PUSH(6, 1.8); + /* MAX */ MAX; + /* 1.3 [ 3] */ PUSH(3, 1.3); + /* [ 6]/1.3 */ MOD(6, 1.3); + /* MAX */ MAX; + /* 1.6 [ 8] */ PUSH(8, 1.6); + /* MAX */ MAX; + + igraph_2wheap_destroy(&Q); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/VF2-compat.c b/tests/unit/VF2-compat.c new file mode 100644 index 0000000..43d1074 --- /dev/null +++ b/tests/unit/VF2-compat.c @@ -0,0 +1,257 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +/* ----------------------------------------------------------- */ + +/* Vertices/edges with the same parity match */ +igraph_bool_t compat_parity(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + return (g1_num % 2) == (g2_num % 2); +} + +/* Nothing vertex/edge 0 in graph1 */ +igraph_bool_t compat_not0(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + IGRAPH_UNUSED(g2_num); + return g1_num != 0; +} + +int match_rings(void) { + + igraph_t r1, r2; + igraph_bool_t iso; + igraph_int_t count; + igraph_ring(&r1, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_ring(&r2, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + + igraph_isomorphic_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + if (!iso) { + exit(1); + } + + igraph_isomorphic_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + compat_parity, /*edge_compat_fn=*/ 0, /*arg=*/ 0); + if (!iso) { + exit(2); + } + + igraph_isomorphic_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + compat_not0, /*edge_compat_fn=*/ 0, /*arg=*/ 0); + if (iso) { + exit(3); + } + + /* ------- */ + + igraph_isomorphic_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, compat_parity, /*arg=*/ 0); + if (!iso) { + exit(4); + } + + igraph_isomorphic_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, compat_not0, /*arg=*/ 0); + if (iso) { + exit(5); + } + + /* ------- */ + + igraph_count_isomorphisms_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, + /*edge_compat_fn=*/ 0, /*arg=*/ 0); + + if (count != 20) { + exit(6); + } + + igraph_count_isomorphisms_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &count, compat_parity, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + + if (count != 10) { + exit(7); + } + + igraph_count_isomorphisms_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &count, compat_not0, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + + if (count != 0) { + exit(8); + } + + /* ------- */ + + igraph_count_isomorphisms_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, compat_parity, + /*arg=*/ 0); + + if (count != 10) { + exit(9); + } + + igraph_count_isomorphisms_vf2(&r1, &r2, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, compat_not0, + /*arg=*/ 0); + + if (count != 0) { + exit(10); + } + + igraph_destroy(&r1); + igraph_destroy(&r2); + return 0; +} + +int match_rings_open_closed(void) { + igraph_t ro, rc; + igraph_bool_t iso; + igraph_int_t count; + igraph_ring(&ro, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 0); + igraph_ring(&rc, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + + igraph_subisomorphic_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + if (!iso) { + exit(31); + } + + igraph_subisomorphic_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + compat_parity, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + if (!iso) { + exit(32); + } + + igraph_subisomorphic_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + compat_not0, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + if (iso) { + exit(33); + } + + /* ------- */ + + igraph_subisomorphic_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, compat_parity, + /*arg=*/ 0); + if (!iso) { + exit(34); + } + + igraph_subisomorphic_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &iso, /*map12=*/ 0, /*map21=*/ 0, + /*node_compat_fn=*/ 0, compat_not0, + /*arg=*/ 0); + if (!iso) { + exit(35); + } + + /* ------- */ + + igraph_count_subisomorphisms_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, + /*edge_compat_fn=*/ 0, /*arg=*/ 0); + + if (count != 20) { + exit(36); + } + + igraph_count_subisomorphisms_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &count, compat_parity, + /*edge_compat_fn=*/ 0, /*arg=*/ 0); + + if (count != 10) { + exit(37); + } + + igraph_count_subisomorphisms_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &count, compat_not0, /*edge_compat_fn=*/ 0, + /*arg=*/ 0); + + if (count != 0) { + exit(38); + } + + /* ------- */ + + igraph_count_subisomorphisms_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, + compat_parity, /*arg=*/ 0); + + if (count != 10) { + exit(39); + } + + igraph_count_subisomorphisms_vf2(&rc, &ro, /*colors(4x)*/ 0, 0, 0, 0, + &count, /*node_compat_fn=*/ 0, compat_not0, + /*arg=*/ 0); + + if (count != 2) { + exit(40); + } + + igraph_destroy(&ro); + igraph_destroy(&rc); + return 0; +} + +/* ----------------------------------------------------------- */ + +int main(void) { + match_rings(); + match_rings_open_closed(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/adj.c b/tests/unit/adj.c new file mode 100644 index 0000000..967b8a6 --- /dev/null +++ b/tests/unit/adj.c @@ -0,0 +1,305 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void print_params(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops) { + printf(igraph_is_directed(graph) ? "directed; " : "undirected; "); + switch (mode) { + case IGRAPH_ALL: printf("ALL; "); break; + case IGRAPH_OUT: printf("OUT; "); break; + case IGRAPH_IN : printf("IN; "); break; + } + switch (loops) { + case IGRAPH_NO_LOOPS: printf("NO_LOOPS; "); break; + case IGRAPH_LOOPS_ONCE: printf("LOOPS_ONCE; "); break; + case IGRAPH_LOOPS_TWICE: printf("LOOPS_TWICE; "); break; + } +} + +void print_multiple(igraph_bool_t multiple) { + printf(multiple ? "MULTIPLE; " : "NO_MULTIPLE; "); +} + +/* Print adjacency list based on igraph_neighbors() */ +void print_adj1(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_int_t neis; + + print_params(graph, mode, loops); + print_multiple(multiple); + printf("\n"); + + igraph_vector_int_init(&neis, 0); + for (igraph_int_t v=0; v < vcount; v++) { + printf("%3" IGRAPH_PRId ": ", v); + igraph_neighbors(graph, &neis, v, mode, loops, multiple); + print_vector_int(&neis); + } + igraph_vector_int_destroy(&neis); +} + +/* Print adjacency list based on igraph_adjlist_init() */ +void print_adj2(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_adjlist_t al; + + print_params(graph, mode, loops); + print_multiple(multiple); + printf("\n"); + + igraph_adjlist_init(graph, &al, mode, loops, multiple); + for (igraph_int_t v=0; v < vcount; v++) { + printf("%3" IGRAPH_PRId ": ", v); + print_vector_int(igraph_adjlist_get(&al, v)); + } + igraph_adjlist_destroy(&al); +} + +/* Print incidence list based on igraph_incident() */ +void print_inc1(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_int_t incs; + + print_params(graph, mode, loops); + printf("\n"); + + igraph_vector_int_init(&incs, 0); + for (igraph_int_t v=0; v < vcount; v++) { + printf("%3" IGRAPH_PRId ": ", v); + igraph_incident(graph, &incs, v, mode, loops); + print_vector_int(&incs); + } + igraph_vector_int_destroy(&incs); +} + +/* Print incidence list based on igraph_inclist_init() */ +void print_inc2(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t vcount = igraph_vcount(graph); + igraph_inclist_t il; + + print_params(graph, mode, loops); + printf("\n"); + + igraph_inclist_init(graph, &il, mode, loops); + for (igraph_int_t v=0; v < vcount; v++) { + printf("%3" IGRAPH_PRId ": ", v); + print_vector_int(igraph_inclist_get(&il, v)); + } + igraph_inclist_destroy(&il); +} + +/* Verifies that igraph_neighbors() and igraph_incident() produce results in the same order. */ +void verify_ordering1(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_vector_int_t neis, incs; + igraph_int_t vcount = igraph_vcount(graph); + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + igraph_vector_int_init(&neis, 0); + igraph_vector_int_init(&incs, 0); + + for (igraph_int_t v=0; v < vcount; v++) { + igraph_neighbors(graph, &neis, v, mode, loops, IGRAPH_MULTIPLE); + igraph_incident(graph, &incs, v, mode, loops); + + igraph_int_t n = igraph_vector_int_size(&neis); + IGRAPH_ASSERT(igraph_vector_int_size(&incs) == n); + + for (igraph_int_t i=0; i < n; i++) { + igraph_int_t u1, u2; + + u1 = VECTOR(neis)[i]; + switch (mode) { + case IGRAPH_ALL: u2 = IGRAPH_OTHER(graph, VECTOR(incs)[i], v); break; + case IGRAPH_OUT: u2 = IGRAPH_TO(graph, VECTOR(incs)[i]); break; + case IGRAPH_IN: u2 = IGRAPH_FROM(graph, VECTOR(incs)[i]); break; + } + + if (u1 != u2) { + IGRAPH_FATALF( + "For vertex %d, the %dth neighbor vertex between adj (%d) and inc (%d) differs.", + (int) v, (int) i, (int) u1, (int) u2 + ); + } + } + } + + igraph_vector_int_destroy(&incs); + igraph_vector_int_destroy(&neis); +} + +/* Verifies that igraph_adjlist_init() and igraph_inclist_init() produce results in the same order. */ +void verify_ordering2(const igraph_t *graph, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_adjlist_t al; + igraph_inclist_t il; + + igraph_int_t vcount = igraph_vcount(graph); + + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + igraph_adjlist_init(graph, &al, mode, loops, IGRAPH_MULTIPLE); + igraph_inclist_init(graph, &il, mode, loops); + + for (igraph_int_t v=0; v < vcount; v++) { + igraph_vector_int_t *neis = igraph_adjlist_get(&al, v); + igraph_vector_int_t *incs = igraph_inclist_get(&il, v); + + igraph_int_t n = igraph_vector_int_size(neis); + IGRAPH_ASSERT(igraph_vector_int_size(incs) == n); + + for (igraph_int_t i=0; i < n; i++) { + igraph_int_t u1, u2; + + u1 = VECTOR(*neis)[i]; + switch (mode) { + case IGRAPH_ALL: u2 = IGRAPH_OTHER(graph, VECTOR(*incs)[i], v); break; + case IGRAPH_OUT: u2 = IGRAPH_TO(graph, VECTOR(*incs)[i]); break; + case IGRAPH_IN: u2 = IGRAPH_FROM(graph, VECTOR(*incs)[i]); break; + } + + if (u1 != u2) { + IGRAPH_FATALF( + "For vertex %d, the %dth neighbor vertex between adj (%d) and inc (%d) differs.", + (int) v, (int) i, (int) u1, (int) u2 + ); + } + } + } + + igraph_adjlist_destroy(&al); + igraph_inclist_destroy(&il); +} + +int main(void) { + igraph_t dg, ug; + + igraph_small(&dg, 7, IGRAPH_DIRECTED, + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + 4,4, 0,3, 0,3, 2,3, 1,4, 2,4, 3,4, 3,2, 4,4, 2,3, 1,1, 6,6, 6,6, + -1); + + igraph_copy(&ug, &dg); + igraph_to_undirected(&ug, IGRAPH_TO_UNDIRECTED_EACH, NULL); + + printf("UNDIRECTED\n\n"); + + printf("igraph_neighbors()\n"); + print_adj1(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + print_adj1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("\nigraph_adjlist_init()\n"); + print_adj2(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + print_adj2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("\nigraph_incident()\n"); + print_inc1(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_inc1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + print_inc1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("\nigraph_inclist_init()\n"); + print_inc2(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_inc2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + print_inc2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("\nDIRECTED ALL\n\n"); + + printf("igraph_neighbors()\n"); + print_adj1(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + print_adj1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("\nigraph_adjlist_init()\n"); + print_adj2(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + print_adj2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("\nigraph_incident()\n"); + print_inc1(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_inc1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + print_inc1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("\nigraph_inclist_init()\n"); + print_inc2(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_inc2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + print_inc2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("\nDIRECTED OUT\n\n"); + + printf("igraph_neighbors()\n"); + print_adj1(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj1(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("\nigraph_adjlist_init()\n"); + print_adj2(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + print_adj2(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("\nigraph_incident()\n"); + print_inc1(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS); + print_inc1(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + printf("\nigraph_inclist_init()\n"); + print_inc2(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS); + print_inc2(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + /* Verify that neighbour lists (adjlist) and incident edge lists (inclist) + * have the same ordering with all parameter combinations. */ + + verify_ordering1(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS); + verify_ordering1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + verify_ordering1(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + verify_ordering1(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS); + verify_ordering1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + verify_ordering1(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + verify_ordering1(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS); + verify_ordering1(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + verify_ordering1(&dg, IGRAPH_IN, IGRAPH_NO_LOOPS); + verify_ordering1(&dg, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + + verify_ordering2(&ug, IGRAPH_ALL, IGRAPH_NO_LOOPS); + verify_ordering2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + verify_ordering2(&ug, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + verify_ordering2(&dg, IGRAPH_ALL, IGRAPH_NO_LOOPS); + verify_ordering2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + verify_ordering2(&dg, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + verify_ordering2(&dg, IGRAPH_OUT, IGRAPH_NO_LOOPS); + verify_ordering2(&dg, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + verify_ordering2(&dg, IGRAPH_IN, IGRAPH_NO_LOOPS); + verify_ordering2(&dg, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + + igraph_destroy(&ug); + igraph_destroy(&dg); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/adj.out b/tests/unit/adj.out new file mode 100644 index 0000000..0a14fab --- /dev/null +++ b/tests/unit/adj.out @@ -0,0 +1,285 @@ +UNDIRECTED + +igraph_neighbors() +undirected; ALL; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 ) + 5: ( ) + 6: ( ) +undirected; ALL; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 ) + 5: ( ) + 6: ( 6 6 ) +undirected; ALL; LOOPS_TWICE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 4 4 ) + 5: ( ) + 6: ( 6 6 6 6 ) + +igraph_adjlist_init() +undirected; ALL; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 ) + 5: ( ) + 6: ( ) +undirected; ALL; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 ) + 5: ( ) + 6: ( 6 6 ) +undirected; ALL; LOOPS_TWICE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 4 4 ) + 5: ( ) + 6: ( 6 6 6 6 ) + +igraph_incident() +undirected; ALL; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 ) + 5: ( ) + 6: ( ) +undirected; ALL; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 8 0 ) + 5: ( ) + 6: ( 12 11 ) +undirected; ALL; LOOPS_TWICE; + 0: ( 2 1 ) + 1: ( 10 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 8 0 8 0 ) + 5: ( ) + 6: ( 12 11 12 11 ) + +igraph_inclist_init() +undirected; ALL; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 ) + 5: ( ) + 6: ( ) +undirected; ALL; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 8 0 ) + 5: ( ) + 6: ( 12 11 ) +undirected; ALL; LOOPS_TWICE; + 0: ( 2 1 ) + 1: ( 10 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 9 7 3 6 ) + 4: ( 4 5 6 8 0 8 0 ) + 5: ( ) + 6: ( 12 11 12 11 ) + +DIRECTED ALL + +igraph_neighbors() +directed; ALL; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 ) + 5: ( ) + 6: ( ) +directed; ALL; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 ) + 5: ( ) + 6: ( 6 6 ) +directed; ALL; LOOPS_TWICE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 4 4 ) + 5: ( ) + 6: ( 6 6 6 6 ) + +igraph_adjlist_init() +directed; ALL; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 ) + 5: ( ) + 6: ( ) +directed; ALL; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 ) + 5: ( ) + 6: ( 6 6 ) +directed; ALL; LOOPS_TWICE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 1 4 ) + 2: ( 3 3 3 4 ) + 3: ( 0 0 2 2 2 4 ) + 4: ( 1 2 3 4 4 4 4 ) + 5: ( ) + 6: ( 6 6 6 6 ) + +igraph_incident() +directed; ALL; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 ) + 5: ( ) + 6: ( ) +directed; ALL; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 8 0 ) + 5: ( ) + 6: ( 12 11 ) +directed; ALL; LOOPS_TWICE; + 0: ( 2 1 ) + 1: ( 10 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 8 8 0 0 ) + 5: ( ) + 6: ( 12 12 11 11 ) + +igraph_inclist_init() +directed; ALL; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 ) + 5: ( ) + 6: ( ) +directed; ALL; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 8 0 ) + 5: ( ) + 6: ( 12 11 ) +directed; ALL; LOOPS_TWICE; + 0: ( 2 1 ) + 1: ( 10 10 4 ) + 2: ( 9 7 3 5 ) + 3: ( 2 1 7 9 3 6 ) + 4: ( 4 5 6 8 8 0 0 ) + 5: ( ) + 6: ( 12 12 11 11 ) + +DIRECTED OUT + +igraph_neighbors() +directed; OUT; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 4 ) + 3: ( 2 4 ) + 4: ( ) + 5: ( ) + 6: ( ) +directed; OUT; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 4 ) + 3: ( 2 4 ) + 4: ( 4 4 ) + 5: ( ) + 6: ( 6 6 ) + +igraph_adjlist_init() +directed; OUT; NO_LOOPS; MULTIPLE; + 0: ( 3 3 ) + 1: ( 4 ) + 2: ( 3 3 4 ) + 3: ( 2 4 ) + 4: ( ) + 5: ( ) + 6: ( ) +directed; OUT; LOOPS_ONCE; MULTIPLE; + 0: ( 3 3 ) + 1: ( 1 4 ) + 2: ( 3 3 4 ) + 3: ( 2 4 ) + 4: ( 4 4 ) + 5: ( ) + 6: ( 6 6 ) + +igraph_incident() +directed; OUT; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 3 5 ) + 3: ( 7 6 ) + 4: ( ) + 5: ( ) + 6: ( ) +directed; OUT; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 3 5 ) + 3: ( 7 6 ) + 4: ( 8 0 ) + 5: ( ) + 6: ( 12 11 ) + +igraph_inclist_init() +directed; OUT; NO_LOOPS; + 0: ( 2 1 ) + 1: ( 4 ) + 2: ( 9 3 5 ) + 3: ( 7 6 ) + 4: ( ) + 5: ( ) + 6: ( ) +directed; OUT; LOOPS_ONCE; + 0: ( 2 1 ) + 1: ( 10 4 ) + 2: ( 9 3 5 ) + 3: ( 7 6 ) + 4: ( 8 0 ) + 5: ( ) + 6: ( 12 11 ) diff --git a/tests/unit/adjlist.c b/tests/unit/adjlist.c new file mode 100644 index 0000000..a849d7d --- /dev/null +++ b/tests/unit/adjlist.c @@ -0,0 +1,348 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int test_simple_trees(void) { + igraph_t g, g2; + igraph_adjlist_t adjlist; + igraph_bool_t iso; + + /* Directed, out */ + igraph_kary_tree(&g, 42, 3, IGRAPH_TREE_OUT); + igraph_adjlist_init(&g, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + igraph_adjlist(&g2, &adjlist, IGRAPH_OUT, /*duplicate=*/ 0); + igraph_isomorphic(&g, &g2, &iso); + IGRAPH_ASSERT(iso); + igraph_adjlist_destroy(&adjlist); + igraph_destroy(&g2); + igraph_destroy(&g); + + /* Directed, in */ + igraph_kary_tree(&g, 42, 3, IGRAPH_TREE_OUT); + igraph_adjlist_init(&g, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + igraph_adjlist(&g2, &adjlist, IGRAPH_IN, /*duplicate=*/ 0); + igraph_isomorphic(&g, &g2, &iso); + IGRAPH_ASSERT(iso); + igraph_adjlist_destroy(&adjlist); + igraph_destroy(&g2); + igraph_destroy(&g); + + /* Undirected */ + igraph_kary_tree(&g, 42, 3, IGRAPH_TREE_UNDIRECTED); + igraph_adjlist_init(&g, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + igraph_adjlist(&g2, &adjlist, IGRAPH_ALL, /*duplicate=*/ 1); + igraph_isomorphic(&g, &g2, &iso); + IGRAPH_ASSERT(iso); + igraph_adjlist_destroy(&adjlist); + igraph_destroy(&g2); + igraph_destroy(&g); + + return 0; +} + +#define TEST_ADJLIST(label, mode, loops, multiple) { \ + igraph_adjlist_init(&g, &adjlist, mode, loops, multiple); \ + printf(label ": "); \ + print_adjlist(&adjlist); \ + printf("\n"); \ + igraph_adjlist_destroy(&adjlist); \ +} + +#define TEST_LAZY_ADJLIST(label, mode, loops, multiple) { \ + igraph_lazy_adjlist_init(&g, &lazy_adjlist, mode, loops, multiple); \ + printf(label ": "); \ + print_lazy_adjlist(&lazy_adjlist); \ + printf("\n"); \ + igraph_lazy_adjlist_destroy(&lazy_adjlist); \ +} + +int test_loop_elimination_for_undirected_graph(void) { + igraph_t g; + igraph_adjlist_t adjlist; + igraph_lazy_adjlist_t lazy_adjlist; + + igraph_small( + &g, 5, /* directed = */ 0, + 0, 1, 0, 3, + 1, 2, + 2, 2, 2, 3, + 3, 0, 3, 4, + 4, 4, 4, 4, + -1 + ); + + printf("Testing loop edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_ADJLIST("Loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_ADJLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_ADJLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("============================================================\n\n"); + + printf("Testing lazy loop edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_LAZY_ADJLIST("Loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_loop_elimination_for_directed_graph(void) { + igraph_t g; + igraph_adjlist_t adjlist; + igraph_lazy_adjlist_t lazy_adjlist; + + igraph_small( + &g, 5, /* directed = */ 1, + 0, 1, 0, 3, + 1, 2, + 2, 2, 2, 3, + 3, 0, 3, 4, + 4, 4, 4, 4, + -1 + ); + + printf("Testing loop edge elimination in directed graph\n\n"); + + TEST_ADJLIST("In-edges, loops eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_ADJLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_ADJLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + TEST_ADJLIST("Out-edges, loops eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_ADJLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_ADJLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + TEST_ADJLIST("In- and out-edges, loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_ADJLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_ADJLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("============================================================\n\n"); + + printf("Testing lazy loop edge elimination in directed graph\n\n"); + + TEST_LAZY_ADJLIST("In-edges, loops eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + TEST_LAZY_ADJLIST("Out-edges, loops eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + TEST_LAZY_ADJLIST("In- and out-edges, loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + TEST_LAZY_ADJLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_multiedge_elimination_for_undirected_graph(void) { + igraph_t g; + igraph_adjlist_t adjlist; + igraph_lazy_adjlist_t lazy_adjlist; + + igraph_small( + &g, 5, /* directed = */ 0, + 0, 1, 0, 3, 0, 8, + 1, 2, + 2, 2, 2, 3, + 3, 0, 3, 4, + 4, 4, 4, 4, 4, 5, 4, 5, 4, 5, + 5, 6, + 6, 7, 6, 8, + 8, 0, + -1 + ); + + printf("Testing multiple edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_ADJLIST("Loops also eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("============================================================\n\n"); + + printf("Testing lazy multiple edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_LAZY_ADJLIST("Loops also eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_multiedge_elimination_for_directed_graph(void) { + igraph_t g; + igraph_adjlist_t adjlist; + igraph_lazy_adjlist_t lazy_adjlist; + + igraph_small( + &g, 5, /* directed = */ 1, + 0, 1, 0, 3, 0, 8, + 1, 2, + 2, 2, 2, 3, + 3, 0, 3, 4, + 4, 4, 4, 4, 4, 5, 4, 5, 4, 5, + 5, 6, + 6, 7, 6, 8, + 8, 0, + -1 + ); + + printf("Testing multiple edge elimination in directed graph\n\n"); + + TEST_ADJLIST("In-edges, loops also eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + TEST_ADJLIST("Out-edges, loops also eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + TEST_ADJLIST("In- and out-edges, loops also eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_ADJLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("============================================================\n\n"); + + printf("Testing lazy multiple edge elimination in directed graph\n\n"); + + TEST_LAZY_ADJLIST("In-edges, loops also eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + TEST_LAZY_ADJLIST("Out-edges, loops also eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + TEST_LAZY_ADJLIST("In- and out-edges, loops also eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + TEST_LAZY_ADJLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_caching(void) { + igraph_t g_simple, g_loop, g_multiloop, g_multi, g_multi_and_loop; + char *g_desc[] = {"simple", "loop", "multiloop", "multi", "multi and loop"}; + igraph_adjlist_t adjlist; + igraph_loops_t loops[] = {IGRAPH_NO_LOOPS, IGRAPH_LOOPS_ONCE, IGRAPH_LOOPS_TWICE}; + igraph_bool_t multiple[] = {IGRAPH_NO_MULTIPLE, IGRAPH_MULTIPLE}; + igraph_neimode_t modes[] = {IGRAPH_OUT, IGRAPH_ALL}; + + igraph_vector_int_t edge; + igraph_int_t vloop[] = {0,0}; + igraph_int_t vmult[] = {0,1}; + + igraph_full(&g_simple, 5, IGRAPH_UNDIRECTED, /*loops*/ 0); + igraph_full(&g_loop, 5, IGRAPH_UNDIRECTED, /*loops*/ 0); + igraph_full(&g_multiloop, 5, IGRAPH_UNDIRECTED, /*loops*/ 0); + igraph_full(&g_multi, 5, IGRAPH_UNDIRECTED, /*loops*/ 0); + igraph_full(&g_multi_and_loop, 5, IGRAPH_UNDIRECTED, /*loops*/ 0); + + edge = igraph_vector_int_view(vloop, 2); + igraph_add_edges(&g_loop, &edge, NULL); + igraph_add_edges(&g_multiloop, &edge, NULL); + igraph_add_edges(&g_multiloop, &edge, NULL); + igraph_add_edges(&g_multi_and_loop, &edge, NULL); + + edge = igraph_vector_int_view(vmult, 2); + + igraph_add_edges(&g_multi, &edge, NULL); + igraph_add_edges(&g_multi_and_loop, &edge, NULL); + + igraph_t *graphs[] = {&g_simple, &g_loop, &g_multiloop, &g_multi, &g_multi_and_loop}; + + for (int g = 0; g < 5; g++) { + for (int loop = 0; loop < 3; loop++) { + for (int multi = 0; multi < 2; multi++) { + for (int mode = 0; mode < 2; mode++) { + printf("graph: %s, loop: %d multi: %d, mode: %d\n", g_desc[g], loop, multi, mode); + + igraph_invalidate_cache(graphs[g]); + igraph_adjlist_init(graphs[g], &adjlist, modes[mode], loops[loop], multiple[multi]); + if (!igraph_i_property_cache_has(graphs[g], IGRAPH_PROP_HAS_LOOP)) { + printf("loop not cached\n"); + } else { + printf("loop cached: %d\n", igraph_i_property_cache_get_bool(graphs[g], IGRAPH_PROP_HAS_LOOP)); + } + if (!igraph_i_property_cache_has(graphs[g], IGRAPH_PROP_HAS_MULTI)) { + printf("multi not cached\n"); + } else { + printf("multi cached: %d\n", igraph_i_property_cache_get_bool(graphs[g], IGRAPH_PROP_HAS_MULTI)); + } + igraph_adjlist_destroy(&adjlist); + } + } + } + igraph_destroy(graphs[g]); + } + + return 0; +} + +int main(void) { + + RUN_TEST(test_simple_trees); + + RUN_TEST(test_loop_elimination_for_undirected_graph); + RUN_TEST(test_loop_elimination_for_directed_graph); + RUN_TEST(test_multiedge_elimination_for_undirected_graph); + RUN_TEST(test_multiedge_elimination_for_directed_graph); + + RUN_TEST(test_caching); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/adjlist.out b/tests/unit/adjlist.out new file mode 100644 index 0000000..ffe6550 --- /dev/null +++ b/tests/unit/adjlist.out @@ -0,0 +1,692 @@ +Testing loop edge elimination in undirected graph + +Loops eliminated: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 ) +} + +Loops listed once: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 ) +} + +Loops listed twice: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 4 4 ) +} + +============================================================ + +Testing lazy loop edge elimination in undirected graph + +Loops eliminated: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 ) +} + +Loops listed once: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 ) +} + +Loops listed twice: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 4 4 ) +} + +============================================================ + +Testing loop edge elimination in directed graph + +In-edges, loops eliminated: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 ) + 3: ( 0 2 ) + 4: ( 3 ) +} + +In-edges, loops listed once: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 4 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 4 ) +} + +Out-edges, loops eliminated: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 0 4 ) + 4: ( ) +} + +Out-edges, loops listed once: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 4 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 4 ) +} + +In- and out-edges, loops eliminated: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 ) +} + +In- and out-edges, loops listed once: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 4 4 ) +} + +============================================================ + +Testing lazy loop edge elimination in directed graph + +In-edges, loops eliminated: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 ) + 3: ( 0 2 ) + 4: ( 3 ) +} + +In-edges, loops listed once: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 4 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 3 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 4 ) +} + +Out-edges, loops eliminated: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 0 4 ) + 4: ( ) +} + +Out-edges, loops listed once: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 4 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 1 3 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 4 ) +} + +In- and out-edges, loops eliminated: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 ) +} + +In- and out-edges, loops listed once: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 1 3 3 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 3 4 4 4 4 ) +} + +============================================================ + +Testing multiple edge elimination in undirected graph + +Loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 2 4 ) + 4: ( 3 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Loops listed once: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Loops listed twice: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +============================================================ + +Testing lazy multiple edge elimination in undirected graph + +Loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 2 4 ) + 4: ( 3 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Loops listed once: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Loops listed twice: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +============================================================ + +Testing multiple edge elimination in directed graph + +In-edges, loops also eliminated: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 ) + 3: ( 0 2 ) + 4: ( 3 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In-edges, loops listed once: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Out-edges, loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 0 4 ) + 4: ( 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +Out-edges, loops listed once: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +In- and out-edges, loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 2 4 ) + 4: ( 3 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In- and out-edges, loops listed once: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +============================================================ + +Testing lazy multiple edge elimination in directed graph + +In-edges, loops also eliminated: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 ) + 3: ( 0 2 ) + 4: ( 3 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In-edges, loops listed once: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 3 8 ) + 1: ( 0 ) + 2: ( 1 2 ) + 3: ( 0 2 ) + 4: ( 3 4 ) + 5: ( 4 ) + 6: ( 5 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +Out-edges, loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 0 4 ) + 4: ( 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +Out-edges, loops listed once: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 1 3 8 ) + 1: ( 2 ) + 2: ( 2 3 ) + 3: ( 0 4 ) + 4: ( 4 5 ) + 5: ( 6 ) + 6: ( 7 8 ) + 7: ( ) + 8: ( 0 ) +} + +In- and out-edges, loops also eliminated: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 3 ) + 3: ( 0 2 4 ) + 4: ( 3 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In- and out-edges, loops listed once: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 1 3 8 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 2 4 ) + 4: ( 3 4 4 5 ) + 5: ( 4 6 ) + 6: ( 5 7 8 ) + 7: ( 6 ) + 8: ( 0 6 ) +} + +============================================================ + +graph: simple, loop: 0 multi: 0, mode: 0 +loop cached: 0 +multi cached: 0 +graph: simple, loop: 0 multi: 0, mode: 1 +loop cached: 0 +multi cached: 0 +graph: simple, loop: 0 multi: 1, mode: 0 +loop cached: 0 +multi not cached +graph: simple, loop: 0 multi: 1, mode: 1 +loop cached: 0 +multi not cached +graph: simple, loop: 1 multi: 0, mode: 0 +loop not cached +multi cached: 0 +graph: simple, loop: 1 multi: 0, mode: 1 +loop not cached +multi cached: 0 +graph: simple, loop: 1 multi: 1, mode: 0 +loop not cached +multi not cached +graph: simple, loop: 1 multi: 1, mode: 1 +loop not cached +multi not cached +graph: simple, loop: 2 multi: 0, mode: 0 +loop not cached +multi cached: 0 +graph: simple, loop: 2 multi: 0, mode: 1 +loop not cached +multi cached: 0 +graph: simple, loop: 2 multi: 1, mode: 0 +loop not cached +multi not cached +graph: simple, loop: 2 multi: 1, mode: 1 +loop not cached +multi not cached +graph: loop, loop: 0 multi: 0, mode: 0 +loop cached: 1 +multi cached: 0 +graph: loop, loop: 0 multi: 0, mode: 1 +loop cached: 1 +multi cached: 0 +graph: loop, loop: 0 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: loop, loop: 0 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: loop, loop: 1 multi: 0, mode: 0 +loop not cached +multi cached: 0 +graph: loop, loop: 1 multi: 0, mode: 1 +loop not cached +multi cached: 0 +graph: loop, loop: 1 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: loop, loop: 1 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: loop, loop: 2 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: loop, loop: 2 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: loop, loop: 2 multi: 1, mode: 0 +loop not cached +multi not cached +graph: loop, loop: 2 multi: 1, mode: 1 +loop not cached +multi not cached +graph: multiloop, loop: 0 multi: 0, mode: 0 +loop cached: 1 +multi cached: 1 +graph: multiloop, loop: 0 multi: 0, mode: 1 +loop cached: 1 +multi cached: 1 +graph: multiloop, loop: 0 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: multiloop, loop: 0 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: multiloop, loop: 1 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multiloop, loop: 1 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multiloop, loop: 1 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: multiloop, loop: 1 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: multiloop, loop: 2 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multiloop, loop: 2 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multiloop, loop: 2 multi: 1, mode: 0 +loop not cached +multi not cached +graph: multiloop, loop: 2 multi: 1, mode: 1 +loop not cached +multi not cached +graph: multi, loop: 0 multi: 0, mode: 0 +loop cached: 0 +multi cached: 1 +graph: multi, loop: 0 multi: 0, mode: 1 +loop cached: 0 +multi cached: 1 +graph: multi, loop: 0 multi: 1, mode: 0 +loop cached: 0 +multi not cached +graph: multi, loop: 0 multi: 1, mode: 1 +loop cached: 0 +multi not cached +graph: multi, loop: 1 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multi, loop: 1 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multi, loop: 1 multi: 1, mode: 0 +loop not cached +multi not cached +graph: multi, loop: 1 multi: 1, mode: 1 +loop not cached +multi not cached +graph: multi, loop: 2 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multi, loop: 2 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multi, loop: 2 multi: 1, mode: 0 +loop not cached +multi not cached +graph: multi, loop: 2 multi: 1, mode: 1 +loop not cached +multi not cached +graph: multi and loop, loop: 0 multi: 0, mode: 0 +loop cached: 1 +multi cached: 1 +graph: multi and loop, loop: 0 multi: 0, mode: 1 +loop cached: 1 +multi cached: 1 +graph: multi and loop, loop: 0 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: multi and loop, loop: 0 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: multi and loop, loop: 1 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multi and loop, loop: 1 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multi and loop, loop: 1 multi: 1, mode: 0 +loop cached: 1 +multi not cached +graph: multi and loop, loop: 1 multi: 1, mode: 1 +loop cached: 1 +multi not cached +graph: multi and loop, loop: 2 multi: 0, mode: 0 +loop not cached +multi cached: 1 +graph: multi and loop, loop: 2 multi: 0, mode: 1 +loop not cached +multi cached: 1 +graph: multi and loop, loop: 2 multi: 1, mode: 0 +loop not cached +multi not cached +graph: multi and loop, loop: 2 multi: 1, mode: 1 +loop not cached +multi not cached diff --git a/tests/unit/ak-4102.max b/tests/unit/ak-4102.max new file mode 100644 index 0000000..0ff4ab6 --- /dev/null +++ b/tests/unit/ak-4102.max @@ -0,0 +1,24623 @@ +c very bad maxflow problem +p max 16414 24619 +n 1 s +n 2 t +a 3 4 4103 +a 3 4106 1 +a 4 5 4102 +a 4 4106 1 +a 5 6 4101 +a 5 4106 1 +a 6 7 4100 +a 6 4106 1 +a 7 8 4099 +a 7 4106 1 +a 8 9 4098 +a 8 4106 1 +a 9 10 4097 +a 9 4106 1 +a 10 11 4096 +a 10 4106 1 +a 11 12 4095 +a 11 4106 1 +a 12 13 4094 +a 12 4106 1 +a 13 14 4093 +a 13 4106 1 +a 14 15 4092 +a 14 4106 1 +a 15 16 4091 +a 15 4106 1 +a 16 17 4090 +a 16 4106 1 +a 17 18 4089 +a 17 4106 1 +a 18 19 4088 +a 18 4106 1 +a 19 20 4087 +a 19 4106 1 +a 20 21 4086 +a 20 4106 1 +a 21 22 4085 +a 21 4106 1 +a 22 23 4084 +a 22 4106 1 +a 23 24 4083 +a 23 4106 1 +a 24 25 4082 +a 24 4106 1 +a 25 26 4081 +a 25 4106 1 +a 26 27 4080 +a 26 4106 1 +a 27 28 4079 +a 27 4106 1 +a 28 29 4078 +a 28 4106 1 +a 29 30 4077 +a 29 4106 1 +a 30 31 4076 +a 30 4106 1 +a 31 32 4075 +a 31 4106 1 +a 32 33 4074 +a 32 4106 1 +a 33 34 4073 +a 33 4106 1 +a 34 35 4072 +a 34 4106 1 +a 35 36 4071 +a 35 4106 1 +a 36 37 4070 +a 36 4106 1 +a 37 38 4069 +a 37 4106 1 +a 38 39 4068 +a 38 4106 1 +a 39 40 4067 +a 39 4106 1 +a 40 41 4066 +a 40 4106 1 +a 41 42 4065 +a 41 4106 1 +a 42 43 4064 +a 42 4106 1 +a 43 44 4063 +a 43 4106 1 +a 44 45 4062 +a 44 4106 1 +a 45 46 4061 +a 45 4106 1 +a 46 47 4060 +a 46 4106 1 +a 47 48 4059 +a 47 4106 1 +a 48 49 4058 +a 48 4106 1 +a 49 50 4057 +a 49 4106 1 +a 50 51 4056 +a 50 4106 1 +a 51 52 4055 +a 51 4106 1 +a 52 53 4054 +a 52 4106 1 +a 53 54 4053 +a 53 4106 1 +a 54 55 4052 +a 54 4106 1 +a 55 56 4051 +a 55 4106 1 +a 56 57 4050 +a 56 4106 1 +a 57 58 4049 +a 57 4106 1 +a 58 59 4048 +a 58 4106 1 +a 59 60 4047 +a 59 4106 1 +a 60 61 4046 +a 60 4106 1 +a 61 62 4045 +a 61 4106 1 +a 62 63 4044 +a 62 4106 1 +a 63 64 4043 +a 63 4106 1 +a 64 65 4042 +a 64 4106 1 +a 65 66 4041 +a 65 4106 1 +a 66 67 4040 +a 66 4106 1 +a 67 68 4039 +a 67 4106 1 +a 68 69 4038 +a 68 4106 1 +a 69 70 4037 +a 69 4106 1 +a 70 71 4036 +a 70 4106 1 +a 71 72 4035 +a 71 4106 1 +a 72 73 4034 +a 72 4106 1 +a 73 74 4033 +a 73 4106 1 +a 74 75 4032 +a 74 4106 1 +a 75 76 4031 +a 75 4106 1 +a 76 77 4030 +a 76 4106 1 +a 77 78 4029 +a 77 4106 1 +a 78 79 4028 +a 78 4106 1 +a 79 80 4027 +a 79 4106 1 +a 80 81 4026 +a 80 4106 1 +a 81 82 4025 +a 81 4106 1 +a 82 83 4024 +a 82 4106 1 +a 83 84 4023 +a 83 4106 1 +a 84 85 4022 +a 84 4106 1 +a 85 86 4021 +a 85 4106 1 +a 86 87 4020 +a 86 4106 1 +a 87 88 4019 +a 87 4106 1 +a 88 89 4018 +a 88 4106 1 +a 89 90 4017 +a 89 4106 1 +a 90 91 4016 +a 90 4106 1 +a 91 92 4015 +a 91 4106 1 +a 92 93 4014 +a 92 4106 1 +a 93 94 4013 +a 93 4106 1 +a 94 95 4012 +a 94 4106 1 +a 95 96 4011 +a 95 4106 1 +a 96 97 4010 +a 96 4106 1 +a 97 98 4009 +a 97 4106 1 +a 98 99 4008 +a 98 4106 1 +a 99 100 4007 +a 99 4106 1 +a 100 101 4006 +a 100 4106 1 +a 101 102 4005 +a 101 4106 1 +a 102 103 4004 +a 102 4106 1 +a 103 104 4003 +a 103 4106 1 +a 104 105 4002 +a 104 4106 1 +a 105 106 4001 +a 105 4106 1 +a 106 107 4000 +a 106 4106 1 +a 107 108 3999 +a 107 4106 1 +a 108 109 3998 +a 108 4106 1 +a 109 110 3997 +a 109 4106 1 +a 110 111 3996 +a 110 4106 1 +a 111 112 3995 +a 111 4106 1 +a 112 113 3994 +a 112 4106 1 +a 113 114 3993 +a 113 4106 1 +a 114 115 3992 +a 114 4106 1 +a 115 116 3991 +a 115 4106 1 +a 116 117 3990 +a 116 4106 1 +a 117 118 3989 +a 117 4106 1 +a 118 119 3988 +a 118 4106 1 +a 119 120 3987 +a 119 4106 1 +a 120 121 3986 +a 120 4106 1 +a 121 122 3985 +a 121 4106 1 +a 122 123 3984 +a 122 4106 1 +a 123 124 3983 +a 123 4106 1 +a 124 125 3982 +a 124 4106 1 +a 125 126 3981 +a 125 4106 1 +a 126 127 3980 +a 126 4106 1 +a 127 128 3979 +a 127 4106 1 +a 128 129 3978 +a 128 4106 1 +a 129 130 3977 +a 129 4106 1 +a 130 131 3976 +a 130 4106 1 +a 131 132 3975 +a 131 4106 1 +a 132 133 3974 +a 132 4106 1 +a 133 134 3973 +a 133 4106 1 +a 134 135 3972 +a 134 4106 1 +a 135 136 3971 +a 135 4106 1 +a 136 137 3970 +a 136 4106 1 +a 137 138 3969 +a 137 4106 1 +a 138 139 3968 +a 138 4106 1 +a 139 140 3967 +a 139 4106 1 +a 140 141 3966 +a 140 4106 1 +a 141 142 3965 +a 141 4106 1 +a 142 143 3964 +a 142 4106 1 +a 143 144 3963 +a 143 4106 1 +a 144 145 3962 +a 144 4106 1 +a 145 146 3961 +a 145 4106 1 +a 146 147 3960 +a 146 4106 1 +a 147 148 3959 +a 147 4106 1 +a 148 149 3958 +a 148 4106 1 +a 149 150 3957 +a 149 4106 1 +a 150 151 3956 +a 150 4106 1 +a 151 152 3955 +a 151 4106 1 +a 152 153 3954 +a 152 4106 1 +a 153 154 3953 +a 153 4106 1 +a 154 155 3952 +a 154 4106 1 +a 155 156 3951 +a 155 4106 1 +a 156 157 3950 +a 156 4106 1 +a 157 158 3949 +a 157 4106 1 +a 158 159 3948 +a 158 4106 1 +a 159 160 3947 +a 159 4106 1 +a 160 161 3946 +a 160 4106 1 +a 161 162 3945 +a 161 4106 1 +a 162 163 3944 +a 162 4106 1 +a 163 164 3943 +a 163 4106 1 +a 164 165 3942 +a 164 4106 1 +a 165 166 3941 +a 165 4106 1 +a 166 167 3940 +a 166 4106 1 +a 167 168 3939 +a 167 4106 1 +a 168 169 3938 +a 168 4106 1 +a 169 170 3937 +a 169 4106 1 +a 170 171 3936 +a 170 4106 1 +a 171 172 3935 +a 171 4106 1 +a 172 173 3934 +a 172 4106 1 +a 173 174 3933 +a 173 4106 1 +a 174 175 3932 +a 174 4106 1 +a 175 176 3931 +a 175 4106 1 +a 176 177 3930 +a 176 4106 1 +a 177 178 3929 +a 177 4106 1 +a 178 179 3928 +a 178 4106 1 +a 179 180 3927 +a 179 4106 1 +a 180 181 3926 +a 180 4106 1 +a 181 182 3925 +a 181 4106 1 +a 182 183 3924 +a 182 4106 1 +a 183 184 3923 +a 183 4106 1 +a 184 185 3922 +a 184 4106 1 +a 185 186 3921 +a 185 4106 1 +a 186 187 3920 +a 186 4106 1 +a 187 188 3919 +a 187 4106 1 +a 188 189 3918 +a 188 4106 1 +a 189 190 3917 +a 189 4106 1 +a 190 191 3916 +a 190 4106 1 +a 191 192 3915 +a 191 4106 1 +a 192 193 3914 +a 192 4106 1 +a 193 194 3913 +a 193 4106 1 +a 194 195 3912 +a 194 4106 1 +a 195 196 3911 +a 195 4106 1 +a 196 197 3910 +a 196 4106 1 +a 197 198 3909 +a 197 4106 1 +a 198 199 3908 +a 198 4106 1 +a 199 200 3907 +a 199 4106 1 +a 200 201 3906 +a 200 4106 1 +a 201 202 3905 +a 201 4106 1 +a 202 203 3904 +a 202 4106 1 +a 203 204 3903 +a 203 4106 1 +a 204 205 3902 +a 204 4106 1 +a 205 206 3901 +a 205 4106 1 +a 206 207 3900 +a 206 4106 1 +a 207 208 3899 +a 207 4106 1 +a 208 209 3898 +a 208 4106 1 +a 209 210 3897 +a 209 4106 1 +a 210 211 3896 +a 210 4106 1 +a 211 212 3895 +a 211 4106 1 +a 212 213 3894 +a 212 4106 1 +a 213 214 3893 +a 213 4106 1 +a 214 215 3892 +a 214 4106 1 +a 215 216 3891 +a 215 4106 1 +a 216 217 3890 +a 216 4106 1 +a 217 218 3889 +a 217 4106 1 +a 218 219 3888 +a 218 4106 1 +a 219 220 3887 +a 219 4106 1 +a 220 221 3886 +a 220 4106 1 +a 221 222 3885 +a 221 4106 1 +a 222 223 3884 +a 222 4106 1 +a 223 224 3883 +a 223 4106 1 +a 224 225 3882 +a 224 4106 1 +a 225 226 3881 +a 225 4106 1 +a 226 227 3880 +a 226 4106 1 +a 227 228 3879 +a 227 4106 1 +a 228 229 3878 +a 228 4106 1 +a 229 230 3877 +a 229 4106 1 +a 230 231 3876 +a 230 4106 1 +a 231 232 3875 +a 231 4106 1 +a 232 233 3874 +a 232 4106 1 +a 233 234 3873 +a 233 4106 1 +a 234 235 3872 +a 234 4106 1 +a 235 236 3871 +a 235 4106 1 +a 236 237 3870 +a 236 4106 1 +a 237 238 3869 +a 237 4106 1 +a 238 239 3868 +a 238 4106 1 +a 239 240 3867 +a 239 4106 1 +a 240 241 3866 +a 240 4106 1 +a 241 242 3865 +a 241 4106 1 +a 242 243 3864 +a 242 4106 1 +a 243 244 3863 +a 243 4106 1 +a 244 245 3862 +a 244 4106 1 +a 245 246 3861 +a 245 4106 1 +a 246 247 3860 +a 246 4106 1 +a 247 248 3859 +a 247 4106 1 +a 248 249 3858 +a 248 4106 1 +a 249 250 3857 +a 249 4106 1 +a 250 251 3856 +a 250 4106 1 +a 251 252 3855 +a 251 4106 1 +a 252 253 3854 +a 252 4106 1 +a 253 254 3853 +a 253 4106 1 +a 254 255 3852 +a 254 4106 1 +a 255 256 3851 +a 255 4106 1 +a 256 257 3850 +a 256 4106 1 +a 257 258 3849 +a 257 4106 1 +a 258 259 3848 +a 258 4106 1 +a 259 260 3847 +a 259 4106 1 +a 260 261 3846 +a 260 4106 1 +a 261 262 3845 +a 261 4106 1 +a 262 263 3844 +a 262 4106 1 +a 263 264 3843 +a 263 4106 1 +a 264 265 3842 +a 264 4106 1 +a 265 266 3841 +a 265 4106 1 +a 266 267 3840 +a 266 4106 1 +a 267 268 3839 +a 267 4106 1 +a 268 269 3838 +a 268 4106 1 +a 269 270 3837 +a 269 4106 1 +a 270 271 3836 +a 270 4106 1 +a 271 272 3835 +a 271 4106 1 +a 272 273 3834 +a 272 4106 1 +a 273 274 3833 +a 273 4106 1 +a 274 275 3832 +a 274 4106 1 +a 275 276 3831 +a 275 4106 1 +a 276 277 3830 +a 276 4106 1 +a 277 278 3829 +a 277 4106 1 +a 278 279 3828 +a 278 4106 1 +a 279 280 3827 +a 279 4106 1 +a 280 281 3826 +a 280 4106 1 +a 281 282 3825 +a 281 4106 1 +a 282 283 3824 +a 282 4106 1 +a 283 284 3823 +a 283 4106 1 +a 284 285 3822 +a 284 4106 1 +a 285 286 3821 +a 285 4106 1 +a 286 287 3820 +a 286 4106 1 +a 287 288 3819 +a 287 4106 1 +a 288 289 3818 +a 288 4106 1 +a 289 290 3817 +a 289 4106 1 +a 290 291 3816 +a 290 4106 1 +a 291 292 3815 +a 291 4106 1 +a 292 293 3814 +a 292 4106 1 +a 293 294 3813 +a 293 4106 1 +a 294 295 3812 +a 294 4106 1 +a 295 296 3811 +a 295 4106 1 +a 296 297 3810 +a 296 4106 1 +a 297 298 3809 +a 297 4106 1 +a 298 299 3808 +a 298 4106 1 +a 299 300 3807 +a 299 4106 1 +a 300 301 3806 +a 300 4106 1 +a 301 302 3805 +a 301 4106 1 +a 302 303 3804 +a 302 4106 1 +a 303 304 3803 +a 303 4106 1 +a 304 305 3802 +a 304 4106 1 +a 305 306 3801 +a 305 4106 1 +a 306 307 3800 +a 306 4106 1 +a 307 308 3799 +a 307 4106 1 +a 308 309 3798 +a 308 4106 1 +a 309 310 3797 +a 309 4106 1 +a 310 311 3796 +a 310 4106 1 +a 311 312 3795 +a 311 4106 1 +a 312 313 3794 +a 312 4106 1 +a 313 314 3793 +a 313 4106 1 +a 314 315 3792 +a 314 4106 1 +a 315 316 3791 +a 315 4106 1 +a 316 317 3790 +a 316 4106 1 +a 317 318 3789 +a 317 4106 1 +a 318 319 3788 +a 318 4106 1 +a 319 320 3787 +a 319 4106 1 +a 320 321 3786 +a 320 4106 1 +a 321 322 3785 +a 321 4106 1 +a 322 323 3784 +a 322 4106 1 +a 323 324 3783 +a 323 4106 1 +a 324 325 3782 +a 324 4106 1 +a 325 326 3781 +a 325 4106 1 +a 326 327 3780 +a 326 4106 1 +a 327 328 3779 +a 327 4106 1 +a 328 329 3778 +a 328 4106 1 +a 329 330 3777 +a 329 4106 1 +a 330 331 3776 +a 330 4106 1 +a 331 332 3775 +a 331 4106 1 +a 332 333 3774 +a 332 4106 1 +a 333 334 3773 +a 333 4106 1 +a 334 335 3772 +a 334 4106 1 +a 335 336 3771 +a 335 4106 1 +a 336 337 3770 +a 336 4106 1 +a 337 338 3769 +a 337 4106 1 +a 338 339 3768 +a 338 4106 1 +a 339 340 3767 +a 339 4106 1 +a 340 341 3766 +a 340 4106 1 +a 341 342 3765 +a 341 4106 1 +a 342 343 3764 +a 342 4106 1 +a 343 344 3763 +a 343 4106 1 +a 344 345 3762 +a 344 4106 1 +a 345 346 3761 +a 345 4106 1 +a 346 347 3760 +a 346 4106 1 +a 347 348 3759 +a 347 4106 1 +a 348 349 3758 +a 348 4106 1 +a 349 350 3757 +a 349 4106 1 +a 350 351 3756 +a 350 4106 1 +a 351 352 3755 +a 351 4106 1 +a 352 353 3754 +a 352 4106 1 +a 353 354 3753 +a 353 4106 1 +a 354 355 3752 +a 354 4106 1 +a 355 356 3751 +a 355 4106 1 +a 356 357 3750 +a 356 4106 1 +a 357 358 3749 +a 357 4106 1 +a 358 359 3748 +a 358 4106 1 +a 359 360 3747 +a 359 4106 1 +a 360 361 3746 +a 360 4106 1 +a 361 362 3745 +a 361 4106 1 +a 362 363 3744 +a 362 4106 1 +a 363 364 3743 +a 363 4106 1 +a 364 365 3742 +a 364 4106 1 +a 365 366 3741 +a 365 4106 1 +a 366 367 3740 +a 366 4106 1 +a 367 368 3739 +a 367 4106 1 +a 368 369 3738 +a 368 4106 1 +a 369 370 3737 +a 369 4106 1 +a 370 371 3736 +a 370 4106 1 +a 371 372 3735 +a 371 4106 1 +a 372 373 3734 +a 372 4106 1 +a 373 374 3733 +a 373 4106 1 +a 374 375 3732 +a 374 4106 1 +a 375 376 3731 +a 375 4106 1 +a 376 377 3730 +a 376 4106 1 +a 377 378 3729 +a 377 4106 1 +a 378 379 3728 +a 378 4106 1 +a 379 380 3727 +a 379 4106 1 +a 380 381 3726 +a 380 4106 1 +a 381 382 3725 +a 381 4106 1 +a 382 383 3724 +a 382 4106 1 +a 383 384 3723 +a 383 4106 1 +a 384 385 3722 +a 384 4106 1 +a 385 386 3721 +a 385 4106 1 +a 386 387 3720 +a 386 4106 1 +a 387 388 3719 +a 387 4106 1 +a 388 389 3718 +a 388 4106 1 +a 389 390 3717 +a 389 4106 1 +a 390 391 3716 +a 390 4106 1 +a 391 392 3715 +a 391 4106 1 +a 392 393 3714 +a 392 4106 1 +a 393 394 3713 +a 393 4106 1 +a 394 395 3712 +a 394 4106 1 +a 395 396 3711 +a 395 4106 1 +a 396 397 3710 +a 396 4106 1 +a 397 398 3709 +a 397 4106 1 +a 398 399 3708 +a 398 4106 1 +a 399 400 3707 +a 399 4106 1 +a 400 401 3706 +a 400 4106 1 +a 401 402 3705 +a 401 4106 1 +a 402 403 3704 +a 402 4106 1 +a 403 404 3703 +a 403 4106 1 +a 404 405 3702 +a 404 4106 1 +a 405 406 3701 +a 405 4106 1 +a 406 407 3700 +a 406 4106 1 +a 407 408 3699 +a 407 4106 1 +a 408 409 3698 +a 408 4106 1 +a 409 410 3697 +a 409 4106 1 +a 410 411 3696 +a 410 4106 1 +a 411 412 3695 +a 411 4106 1 +a 412 413 3694 +a 412 4106 1 +a 413 414 3693 +a 413 4106 1 +a 414 415 3692 +a 414 4106 1 +a 415 416 3691 +a 415 4106 1 +a 416 417 3690 +a 416 4106 1 +a 417 418 3689 +a 417 4106 1 +a 418 419 3688 +a 418 4106 1 +a 419 420 3687 +a 419 4106 1 +a 420 421 3686 +a 420 4106 1 +a 421 422 3685 +a 421 4106 1 +a 422 423 3684 +a 422 4106 1 +a 423 424 3683 +a 423 4106 1 +a 424 425 3682 +a 424 4106 1 +a 425 426 3681 +a 425 4106 1 +a 426 427 3680 +a 426 4106 1 +a 427 428 3679 +a 427 4106 1 +a 428 429 3678 +a 428 4106 1 +a 429 430 3677 +a 429 4106 1 +a 430 431 3676 +a 430 4106 1 +a 431 432 3675 +a 431 4106 1 +a 432 433 3674 +a 432 4106 1 +a 433 434 3673 +a 433 4106 1 +a 434 435 3672 +a 434 4106 1 +a 435 436 3671 +a 435 4106 1 +a 436 437 3670 +a 436 4106 1 +a 437 438 3669 +a 437 4106 1 +a 438 439 3668 +a 438 4106 1 +a 439 440 3667 +a 439 4106 1 +a 440 441 3666 +a 440 4106 1 +a 441 442 3665 +a 441 4106 1 +a 442 443 3664 +a 442 4106 1 +a 443 444 3663 +a 443 4106 1 +a 444 445 3662 +a 444 4106 1 +a 445 446 3661 +a 445 4106 1 +a 446 447 3660 +a 446 4106 1 +a 447 448 3659 +a 447 4106 1 +a 448 449 3658 +a 448 4106 1 +a 449 450 3657 +a 449 4106 1 +a 450 451 3656 +a 450 4106 1 +a 451 452 3655 +a 451 4106 1 +a 452 453 3654 +a 452 4106 1 +a 453 454 3653 +a 453 4106 1 +a 454 455 3652 +a 454 4106 1 +a 455 456 3651 +a 455 4106 1 +a 456 457 3650 +a 456 4106 1 +a 457 458 3649 +a 457 4106 1 +a 458 459 3648 +a 458 4106 1 +a 459 460 3647 +a 459 4106 1 +a 460 461 3646 +a 460 4106 1 +a 461 462 3645 +a 461 4106 1 +a 462 463 3644 +a 462 4106 1 +a 463 464 3643 +a 463 4106 1 +a 464 465 3642 +a 464 4106 1 +a 465 466 3641 +a 465 4106 1 +a 466 467 3640 +a 466 4106 1 +a 467 468 3639 +a 467 4106 1 +a 468 469 3638 +a 468 4106 1 +a 469 470 3637 +a 469 4106 1 +a 470 471 3636 +a 470 4106 1 +a 471 472 3635 +a 471 4106 1 +a 472 473 3634 +a 472 4106 1 +a 473 474 3633 +a 473 4106 1 +a 474 475 3632 +a 474 4106 1 +a 475 476 3631 +a 475 4106 1 +a 476 477 3630 +a 476 4106 1 +a 477 478 3629 +a 477 4106 1 +a 478 479 3628 +a 478 4106 1 +a 479 480 3627 +a 479 4106 1 +a 480 481 3626 +a 480 4106 1 +a 481 482 3625 +a 481 4106 1 +a 482 483 3624 +a 482 4106 1 +a 483 484 3623 +a 483 4106 1 +a 484 485 3622 +a 484 4106 1 +a 485 486 3621 +a 485 4106 1 +a 486 487 3620 +a 486 4106 1 +a 487 488 3619 +a 487 4106 1 +a 488 489 3618 +a 488 4106 1 +a 489 490 3617 +a 489 4106 1 +a 490 491 3616 +a 490 4106 1 +a 491 492 3615 +a 491 4106 1 +a 492 493 3614 +a 492 4106 1 +a 493 494 3613 +a 493 4106 1 +a 494 495 3612 +a 494 4106 1 +a 495 496 3611 +a 495 4106 1 +a 496 497 3610 +a 496 4106 1 +a 497 498 3609 +a 497 4106 1 +a 498 499 3608 +a 498 4106 1 +a 499 500 3607 +a 499 4106 1 +a 500 501 3606 +a 500 4106 1 +a 501 502 3605 +a 501 4106 1 +a 502 503 3604 +a 502 4106 1 +a 503 504 3603 +a 503 4106 1 +a 504 505 3602 +a 504 4106 1 +a 505 506 3601 +a 505 4106 1 +a 506 507 3600 +a 506 4106 1 +a 507 508 3599 +a 507 4106 1 +a 508 509 3598 +a 508 4106 1 +a 509 510 3597 +a 509 4106 1 +a 510 511 3596 +a 510 4106 1 +a 511 512 3595 +a 511 4106 1 +a 512 513 3594 +a 512 4106 1 +a 513 514 3593 +a 513 4106 1 +a 514 515 3592 +a 514 4106 1 +a 515 516 3591 +a 515 4106 1 +a 516 517 3590 +a 516 4106 1 +a 517 518 3589 +a 517 4106 1 +a 518 519 3588 +a 518 4106 1 +a 519 520 3587 +a 519 4106 1 +a 520 521 3586 +a 520 4106 1 +a 521 522 3585 +a 521 4106 1 +a 522 523 3584 +a 522 4106 1 +a 523 524 3583 +a 523 4106 1 +a 524 525 3582 +a 524 4106 1 +a 525 526 3581 +a 525 4106 1 +a 526 527 3580 +a 526 4106 1 +a 527 528 3579 +a 527 4106 1 +a 528 529 3578 +a 528 4106 1 +a 529 530 3577 +a 529 4106 1 +a 530 531 3576 +a 530 4106 1 +a 531 532 3575 +a 531 4106 1 +a 532 533 3574 +a 532 4106 1 +a 533 534 3573 +a 533 4106 1 +a 534 535 3572 +a 534 4106 1 +a 535 536 3571 +a 535 4106 1 +a 536 537 3570 +a 536 4106 1 +a 537 538 3569 +a 537 4106 1 +a 538 539 3568 +a 538 4106 1 +a 539 540 3567 +a 539 4106 1 +a 540 541 3566 +a 540 4106 1 +a 541 542 3565 +a 541 4106 1 +a 542 543 3564 +a 542 4106 1 +a 543 544 3563 +a 543 4106 1 +a 544 545 3562 +a 544 4106 1 +a 545 546 3561 +a 545 4106 1 +a 546 547 3560 +a 546 4106 1 +a 547 548 3559 +a 547 4106 1 +a 548 549 3558 +a 548 4106 1 +a 549 550 3557 +a 549 4106 1 +a 550 551 3556 +a 550 4106 1 +a 551 552 3555 +a 551 4106 1 +a 552 553 3554 +a 552 4106 1 +a 553 554 3553 +a 553 4106 1 +a 554 555 3552 +a 554 4106 1 +a 555 556 3551 +a 555 4106 1 +a 556 557 3550 +a 556 4106 1 +a 557 558 3549 +a 557 4106 1 +a 558 559 3548 +a 558 4106 1 +a 559 560 3547 +a 559 4106 1 +a 560 561 3546 +a 560 4106 1 +a 561 562 3545 +a 561 4106 1 +a 562 563 3544 +a 562 4106 1 +a 563 564 3543 +a 563 4106 1 +a 564 565 3542 +a 564 4106 1 +a 565 566 3541 +a 565 4106 1 +a 566 567 3540 +a 566 4106 1 +a 567 568 3539 +a 567 4106 1 +a 568 569 3538 +a 568 4106 1 +a 569 570 3537 +a 569 4106 1 +a 570 571 3536 +a 570 4106 1 +a 571 572 3535 +a 571 4106 1 +a 572 573 3534 +a 572 4106 1 +a 573 574 3533 +a 573 4106 1 +a 574 575 3532 +a 574 4106 1 +a 575 576 3531 +a 575 4106 1 +a 576 577 3530 +a 576 4106 1 +a 577 578 3529 +a 577 4106 1 +a 578 579 3528 +a 578 4106 1 +a 579 580 3527 +a 579 4106 1 +a 580 581 3526 +a 580 4106 1 +a 581 582 3525 +a 581 4106 1 +a 582 583 3524 +a 582 4106 1 +a 583 584 3523 +a 583 4106 1 +a 584 585 3522 +a 584 4106 1 +a 585 586 3521 +a 585 4106 1 +a 586 587 3520 +a 586 4106 1 +a 587 588 3519 +a 587 4106 1 +a 588 589 3518 +a 588 4106 1 +a 589 590 3517 +a 589 4106 1 +a 590 591 3516 +a 590 4106 1 +a 591 592 3515 +a 591 4106 1 +a 592 593 3514 +a 592 4106 1 +a 593 594 3513 +a 593 4106 1 +a 594 595 3512 +a 594 4106 1 +a 595 596 3511 +a 595 4106 1 +a 596 597 3510 +a 596 4106 1 +a 597 598 3509 +a 597 4106 1 +a 598 599 3508 +a 598 4106 1 +a 599 600 3507 +a 599 4106 1 +a 600 601 3506 +a 600 4106 1 +a 601 602 3505 +a 601 4106 1 +a 602 603 3504 +a 602 4106 1 +a 603 604 3503 +a 603 4106 1 +a 604 605 3502 +a 604 4106 1 +a 605 606 3501 +a 605 4106 1 +a 606 607 3500 +a 606 4106 1 +a 607 608 3499 +a 607 4106 1 +a 608 609 3498 +a 608 4106 1 +a 609 610 3497 +a 609 4106 1 +a 610 611 3496 +a 610 4106 1 +a 611 612 3495 +a 611 4106 1 +a 612 613 3494 +a 612 4106 1 +a 613 614 3493 +a 613 4106 1 +a 614 615 3492 +a 614 4106 1 +a 615 616 3491 +a 615 4106 1 +a 616 617 3490 +a 616 4106 1 +a 617 618 3489 +a 617 4106 1 +a 618 619 3488 +a 618 4106 1 +a 619 620 3487 +a 619 4106 1 +a 620 621 3486 +a 620 4106 1 +a 621 622 3485 +a 621 4106 1 +a 622 623 3484 +a 622 4106 1 +a 623 624 3483 +a 623 4106 1 +a 624 625 3482 +a 624 4106 1 +a 625 626 3481 +a 625 4106 1 +a 626 627 3480 +a 626 4106 1 +a 627 628 3479 +a 627 4106 1 +a 628 629 3478 +a 628 4106 1 +a 629 630 3477 +a 629 4106 1 +a 630 631 3476 +a 630 4106 1 +a 631 632 3475 +a 631 4106 1 +a 632 633 3474 +a 632 4106 1 +a 633 634 3473 +a 633 4106 1 +a 634 635 3472 +a 634 4106 1 +a 635 636 3471 +a 635 4106 1 +a 636 637 3470 +a 636 4106 1 +a 637 638 3469 +a 637 4106 1 +a 638 639 3468 +a 638 4106 1 +a 639 640 3467 +a 639 4106 1 +a 640 641 3466 +a 640 4106 1 +a 641 642 3465 +a 641 4106 1 +a 642 643 3464 +a 642 4106 1 +a 643 644 3463 +a 643 4106 1 +a 644 645 3462 +a 644 4106 1 +a 645 646 3461 +a 645 4106 1 +a 646 647 3460 +a 646 4106 1 +a 647 648 3459 +a 647 4106 1 +a 648 649 3458 +a 648 4106 1 +a 649 650 3457 +a 649 4106 1 +a 650 651 3456 +a 650 4106 1 +a 651 652 3455 +a 651 4106 1 +a 652 653 3454 +a 652 4106 1 +a 653 654 3453 +a 653 4106 1 +a 654 655 3452 +a 654 4106 1 +a 655 656 3451 +a 655 4106 1 +a 656 657 3450 +a 656 4106 1 +a 657 658 3449 +a 657 4106 1 +a 658 659 3448 +a 658 4106 1 +a 659 660 3447 +a 659 4106 1 +a 660 661 3446 +a 660 4106 1 +a 661 662 3445 +a 661 4106 1 +a 662 663 3444 +a 662 4106 1 +a 663 664 3443 +a 663 4106 1 +a 664 665 3442 +a 664 4106 1 +a 665 666 3441 +a 665 4106 1 +a 666 667 3440 +a 666 4106 1 +a 667 668 3439 +a 667 4106 1 +a 668 669 3438 +a 668 4106 1 +a 669 670 3437 +a 669 4106 1 +a 670 671 3436 +a 670 4106 1 +a 671 672 3435 +a 671 4106 1 +a 672 673 3434 +a 672 4106 1 +a 673 674 3433 +a 673 4106 1 +a 674 675 3432 +a 674 4106 1 +a 675 676 3431 +a 675 4106 1 +a 676 677 3430 +a 676 4106 1 +a 677 678 3429 +a 677 4106 1 +a 678 679 3428 +a 678 4106 1 +a 679 680 3427 +a 679 4106 1 +a 680 681 3426 +a 680 4106 1 +a 681 682 3425 +a 681 4106 1 +a 682 683 3424 +a 682 4106 1 +a 683 684 3423 +a 683 4106 1 +a 684 685 3422 +a 684 4106 1 +a 685 686 3421 +a 685 4106 1 +a 686 687 3420 +a 686 4106 1 +a 687 688 3419 +a 687 4106 1 +a 688 689 3418 +a 688 4106 1 +a 689 690 3417 +a 689 4106 1 +a 690 691 3416 +a 690 4106 1 +a 691 692 3415 +a 691 4106 1 +a 692 693 3414 +a 692 4106 1 +a 693 694 3413 +a 693 4106 1 +a 694 695 3412 +a 694 4106 1 +a 695 696 3411 +a 695 4106 1 +a 696 697 3410 +a 696 4106 1 +a 697 698 3409 +a 697 4106 1 +a 698 699 3408 +a 698 4106 1 +a 699 700 3407 +a 699 4106 1 +a 700 701 3406 +a 700 4106 1 +a 701 702 3405 +a 701 4106 1 +a 702 703 3404 +a 702 4106 1 +a 703 704 3403 +a 703 4106 1 +a 704 705 3402 +a 704 4106 1 +a 705 706 3401 +a 705 4106 1 +a 706 707 3400 +a 706 4106 1 +a 707 708 3399 +a 707 4106 1 +a 708 709 3398 +a 708 4106 1 +a 709 710 3397 +a 709 4106 1 +a 710 711 3396 +a 710 4106 1 +a 711 712 3395 +a 711 4106 1 +a 712 713 3394 +a 712 4106 1 +a 713 714 3393 +a 713 4106 1 +a 714 715 3392 +a 714 4106 1 +a 715 716 3391 +a 715 4106 1 +a 716 717 3390 +a 716 4106 1 +a 717 718 3389 +a 717 4106 1 +a 718 719 3388 +a 718 4106 1 +a 719 720 3387 +a 719 4106 1 +a 720 721 3386 +a 720 4106 1 +a 721 722 3385 +a 721 4106 1 +a 722 723 3384 +a 722 4106 1 +a 723 724 3383 +a 723 4106 1 +a 724 725 3382 +a 724 4106 1 +a 725 726 3381 +a 725 4106 1 +a 726 727 3380 +a 726 4106 1 +a 727 728 3379 +a 727 4106 1 +a 728 729 3378 +a 728 4106 1 +a 729 730 3377 +a 729 4106 1 +a 730 731 3376 +a 730 4106 1 +a 731 732 3375 +a 731 4106 1 +a 732 733 3374 +a 732 4106 1 +a 733 734 3373 +a 733 4106 1 +a 734 735 3372 +a 734 4106 1 +a 735 736 3371 +a 735 4106 1 +a 736 737 3370 +a 736 4106 1 +a 737 738 3369 +a 737 4106 1 +a 738 739 3368 +a 738 4106 1 +a 739 740 3367 +a 739 4106 1 +a 740 741 3366 +a 740 4106 1 +a 741 742 3365 +a 741 4106 1 +a 742 743 3364 +a 742 4106 1 +a 743 744 3363 +a 743 4106 1 +a 744 745 3362 +a 744 4106 1 +a 745 746 3361 +a 745 4106 1 +a 746 747 3360 +a 746 4106 1 +a 747 748 3359 +a 747 4106 1 +a 748 749 3358 +a 748 4106 1 +a 749 750 3357 +a 749 4106 1 +a 750 751 3356 +a 750 4106 1 +a 751 752 3355 +a 751 4106 1 +a 752 753 3354 +a 752 4106 1 +a 753 754 3353 +a 753 4106 1 +a 754 755 3352 +a 754 4106 1 +a 755 756 3351 +a 755 4106 1 +a 756 757 3350 +a 756 4106 1 +a 757 758 3349 +a 757 4106 1 +a 758 759 3348 +a 758 4106 1 +a 759 760 3347 +a 759 4106 1 +a 760 761 3346 +a 760 4106 1 +a 761 762 3345 +a 761 4106 1 +a 762 763 3344 +a 762 4106 1 +a 763 764 3343 +a 763 4106 1 +a 764 765 3342 +a 764 4106 1 +a 765 766 3341 +a 765 4106 1 +a 766 767 3340 +a 766 4106 1 +a 767 768 3339 +a 767 4106 1 +a 768 769 3338 +a 768 4106 1 +a 769 770 3337 +a 769 4106 1 +a 770 771 3336 +a 770 4106 1 +a 771 772 3335 +a 771 4106 1 +a 772 773 3334 +a 772 4106 1 +a 773 774 3333 +a 773 4106 1 +a 774 775 3332 +a 774 4106 1 +a 775 776 3331 +a 775 4106 1 +a 776 777 3330 +a 776 4106 1 +a 777 778 3329 +a 777 4106 1 +a 778 779 3328 +a 778 4106 1 +a 779 780 3327 +a 779 4106 1 +a 780 781 3326 +a 780 4106 1 +a 781 782 3325 +a 781 4106 1 +a 782 783 3324 +a 782 4106 1 +a 783 784 3323 +a 783 4106 1 +a 784 785 3322 +a 784 4106 1 +a 785 786 3321 +a 785 4106 1 +a 786 787 3320 +a 786 4106 1 +a 787 788 3319 +a 787 4106 1 +a 788 789 3318 +a 788 4106 1 +a 789 790 3317 +a 789 4106 1 +a 790 791 3316 +a 790 4106 1 +a 791 792 3315 +a 791 4106 1 +a 792 793 3314 +a 792 4106 1 +a 793 794 3313 +a 793 4106 1 +a 794 795 3312 +a 794 4106 1 +a 795 796 3311 +a 795 4106 1 +a 796 797 3310 +a 796 4106 1 +a 797 798 3309 +a 797 4106 1 +a 798 799 3308 +a 798 4106 1 +a 799 800 3307 +a 799 4106 1 +a 800 801 3306 +a 800 4106 1 +a 801 802 3305 +a 801 4106 1 +a 802 803 3304 +a 802 4106 1 +a 803 804 3303 +a 803 4106 1 +a 804 805 3302 +a 804 4106 1 +a 805 806 3301 +a 805 4106 1 +a 806 807 3300 +a 806 4106 1 +a 807 808 3299 +a 807 4106 1 +a 808 809 3298 +a 808 4106 1 +a 809 810 3297 +a 809 4106 1 +a 810 811 3296 +a 810 4106 1 +a 811 812 3295 +a 811 4106 1 +a 812 813 3294 +a 812 4106 1 +a 813 814 3293 +a 813 4106 1 +a 814 815 3292 +a 814 4106 1 +a 815 816 3291 +a 815 4106 1 +a 816 817 3290 +a 816 4106 1 +a 817 818 3289 +a 817 4106 1 +a 818 819 3288 +a 818 4106 1 +a 819 820 3287 +a 819 4106 1 +a 820 821 3286 +a 820 4106 1 +a 821 822 3285 +a 821 4106 1 +a 822 823 3284 +a 822 4106 1 +a 823 824 3283 +a 823 4106 1 +a 824 825 3282 +a 824 4106 1 +a 825 826 3281 +a 825 4106 1 +a 826 827 3280 +a 826 4106 1 +a 827 828 3279 +a 827 4106 1 +a 828 829 3278 +a 828 4106 1 +a 829 830 3277 +a 829 4106 1 +a 830 831 3276 +a 830 4106 1 +a 831 832 3275 +a 831 4106 1 +a 832 833 3274 +a 832 4106 1 +a 833 834 3273 +a 833 4106 1 +a 834 835 3272 +a 834 4106 1 +a 835 836 3271 +a 835 4106 1 +a 836 837 3270 +a 836 4106 1 +a 837 838 3269 +a 837 4106 1 +a 838 839 3268 +a 838 4106 1 +a 839 840 3267 +a 839 4106 1 +a 840 841 3266 +a 840 4106 1 +a 841 842 3265 +a 841 4106 1 +a 842 843 3264 +a 842 4106 1 +a 843 844 3263 +a 843 4106 1 +a 844 845 3262 +a 844 4106 1 +a 845 846 3261 +a 845 4106 1 +a 846 847 3260 +a 846 4106 1 +a 847 848 3259 +a 847 4106 1 +a 848 849 3258 +a 848 4106 1 +a 849 850 3257 +a 849 4106 1 +a 850 851 3256 +a 850 4106 1 +a 851 852 3255 +a 851 4106 1 +a 852 853 3254 +a 852 4106 1 +a 853 854 3253 +a 853 4106 1 +a 854 855 3252 +a 854 4106 1 +a 855 856 3251 +a 855 4106 1 +a 856 857 3250 +a 856 4106 1 +a 857 858 3249 +a 857 4106 1 +a 858 859 3248 +a 858 4106 1 +a 859 860 3247 +a 859 4106 1 +a 860 861 3246 +a 860 4106 1 +a 861 862 3245 +a 861 4106 1 +a 862 863 3244 +a 862 4106 1 +a 863 864 3243 +a 863 4106 1 +a 864 865 3242 +a 864 4106 1 +a 865 866 3241 +a 865 4106 1 +a 866 867 3240 +a 866 4106 1 +a 867 868 3239 +a 867 4106 1 +a 868 869 3238 +a 868 4106 1 +a 869 870 3237 +a 869 4106 1 +a 870 871 3236 +a 870 4106 1 +a 871 872 3235 +a 871 4106 1 +a 872 873 3234 +a 872 4106 1 +a 873 874 3233 +a 873 4106 1 +a 874 875 3232 +a 874 4106 1 +a 875 876 3231 +a 875 4106 1 +a 876 877 3230 +a 876 4106 1 +a 877 878 3229 +a 877 4106 1 +a 878 879 3228 +a 878 4106 1 +a 879 880 3227 +a 879 4106 1 +a 880 881 3226 +a 880 4106 1 +a 881 882 3225 +a 881 4106 1 +a 882 883 3224 +a 882 4106 1 +a 883 884 3223 +a 883 4106 1 +a 884 885 3222 +a 884 4106 1 +a 885 886 3221 +a 885 4106 1 +a 886 887 3220 +a 886 4106 1 +a 887 888 3219 +a 887 4106 1 +a 888 889 3218 +a 888 4106 1 +a 889 890 3217 +a 889 4106 1 +a 890 891 3216 +a 890 4106 1 +a 891 892 3215 +a 891 4106 1 +a 892 893 3214 +a 892 4106 1 +a 893 894 3213 +a 893 4106 1 +a 894 895 3212 +a 894 4106 1 +a 895 896 3211 +a 895 4106 1 +a 896 897 3210 +a 896 4106 1 +a 897 898 3209 +a 897 4106 1 +a 898 899 3208 +a 898 4106 1 +a 899 900 3207 +a 899 4106 1 +a 900 901 3206 +a 900 4106 1 +a 901 902 3205 +a 901 4106 1 +a 902 903 3204 +a 902 4106 1 +a 903 904 3203 +a 903 4106 1 +a 904 905 3202 +a 904 4106 1 +a 905 906 3201 +a 905 4106 1 +a 906 907 3200 +a 906 4106 1 +a 907 908 3199 +a 907 4106 1 +a 908 909 3198 +a 908 4106 1 +a 909 910 3197 +a 909 4106 1 +a 910 911 3196 +a 910 4106 1 +a 911 912 3195 +a 911 4106 1 +a 912 913 3194 +a 912 4106 1 +a 913 914 3193 +a 913 4106 1 +a 914 915 3192 +a 914 4106 1 +a 915 916 3191 +a 915 4106 1 +a 916 917 3190 +a 916 4106 1 +a 917 918 3189 +a 917 4106 1 +a 918 919 3188 +a 918 4106 1 +a 919 920 3187 +a 919 4106 1 +a 920 921 3186 +a 920 4106 1 +a 921 922 3185 +a 921 4106 1 +a 922 923 3184 +a 922 4106 1 +a 923 924 3183 +a 923 4106 1 +a 924 925 3182 +a 924 4106 1 +a 925 926 3181 +a 925 4106 1 +a 926 927 3180 +a 926 4106 1 +a 927 928 3179 +a 927 4106 1 +a 928 929 3178 +a 928 4106 1 +a 929 930 3177 +a 929 4106 1 +a 930 931 3176 +a 930 4106 1 +a 931 932 3175 +a 931 4106 1 +a 932 933 3174 +a 932 4106 1 +a 933 934 3173 +a 933 4106 1 +a 934 935 3172 +a 934 4106 1 +a 935 936 3171 +a 935 4106 1 +a 936 937 3170 +a 936 4106 1 +a 937 938 3169 +a 937 4106 1 +a 938 939 3168 +a 938 4106 1 +a 939 940 3167 +a 939 4106 1 +a 940 941 3166 +a 940 4106 1 +a 941 942 3165 +a 941 4106 1 +a 942 943 3164 +a 942 4106 1 +a 943 944 3163 +a 943 4106 1 +a 944 945 3162 +a 944 4106 1 +a 945 946 3161 +a 945 4106 1 +a 946 947 3160 +a 946 4106 1 +a 947 948 3159 +a 947 4106 1 +a 948 949 3158 +a 948 4106 1 +a 949 950 3157 +a 949 4106 1 +a 950 951 3156 +a 950 4106 1 +a 951 952 3155 +a 951 4106 1 +a 952 953 3154 +a 952 4106 1 +a 953 954 3153 +a 953 4106 1 +a 954 955 3152 +a 954 4106 1 +a 955 956 3151 +a 955 4106 1 +a 956 957 3150 +a 956 4106 1 +a 957 958 3149 +a 957 4106 1 +a 958 959 3148 +a 958 4106 1 +a 959 960 3147 +a 959 4106 1 +a 960 961 3146 +a 960 4106 1 +a 961 962 3145 +a 961 4106 1 +a 962 963 3144 +a 962 4106 1 +a 963 964 3143 +a 963 4106 1 +a 964 965 3142 +a 964 4106 1 +a 965 966 3141 +a 965 4106 1 +a 966 967 3140 +a 966 4106 1 +a 967 968 3139 +a 967 4106 1 +a 968 969 3138 +a 968 4106 1 +a 969 970 3137 +a 969 4106 1 +a 970 971 3136 +a 970 4106 1 +a 971 972 3135 +a 971 4106 1 +a 972 973 3134 +a 972 4106 1 +a 973 974 3133 +a 973 4106 1 +a 974 975 3132 +a 974 4106 1 +a 975 976 3131 +a 975 4106 1 +a 976 977 3130 +a 976 4106 1 +a 977 978 3129 +a 977 4106 1 +a 978 979 3128 +a 978 4106 1 +a 979 980 3127 +a 979 4106 1 +a 980 981 3126 +a 980 4106 1 +a 981 982 3125 +a 981 4106 1 +a 982 983 3124 +a 982 4106 1 +a 983 984 3123 +a 983 4106 1 +a 984 985 3122 +a 984 4106 1 +a 985 986 3121 +a 985 4106 1 +a 986 987 3120 +a 986 4106 1 +a 987 988 3119 +a 987 4106 1 +a 988 989 3118 +a 988 4106 1 +a 989 990 3117 +a 989 4106 1 +a 990 991 3116 +a 990 4106 1 +a 991 992 3115 +a 991 4106 1 +a 992 993 3114 +a 992 4106 1 +a 993 994 3113 +a 993 4106 1 +a 994 995 3112 +a 994 4106 1 +a 995 996 3111 +a 995 4106 1 +a 996 997 3110 +a 996 4106 1 +a 997 998 3109 +a 997 4106 1 +a 998 999 3108 +a 998 4106 1 +a 999 1000 3107 +a 999 4106 1 +a 1000 1001 3106 +a 1000 4106 1 +a 1001 1002 3105 +a 1001 4106 1 +a 1002 1003 3104 +a 1002 4106 1 +a 1003 1004 3103 +a 1003 4106 1 +a 1004 1005 3102 +a 1004 4106 1 +a 1005 1006 3101 +a 1005 4106 1 +a 1006 1007 3100 +a 1006 4106 1 +a 1007 1008 3099 +a 1007 4106 1 +a 1008 1009 3098 +a 1008 4106 1 +a 1009 1010 3097 +a 1009 4106 1 +a 1010 1011 3096 +a 1010 4106 1 +a 1011 1012 3095 +a 1011 4106 1 +a 1012 1013 3094 +a 1012 4106 1 +a 1013 1014 3093 +a 1013 4106 1 +a 1014 1015 3092 +a 1014 4106 1 +a 1015 1016 3091 +a 1015 4106 1 +a 1016 1017 3090 +a 1016 4106 1 +a 1017 1018 3089 +a 1017 4106 1 +a 1018 1019 3088 +a 1018 4106 1 +a 1019 1020 3087 +a 1019 4106 1 +a 1020 1021 3086 +a 1020 4106 1 +a 1021 1022 3085 +a 1021 4106 1 +a 1022 1023 3084 +a 1022 4106 1 +a 1023 1024 3083 +a 1023 4106 1 +a 1024 1025 3082 +a 1024 4106 1 +a 1025 1026 3081 +a 1025 4106 1 +a 1026 1027 3080 +a 1026 4106 1 +a 1027 1028 3079 +a 1027 4106 1 +a 1028 1029 3078 +a 1028 4106 1 +a 1029 1030 3077 +a 1029 4106 1 +a 1030 1031 3076 +a 1030 4106 1 +a 1031 1032 3075 +a 1031 4106 1 +a 1032 1033 3074 +a 1032 4106 1 +a 1033 1034 3073 +a 1033 4106 1 +a 1034 1035 3072 +a 1034 4106 1 +a 1035 1036 3071 +a 1035 4106 1 +a 1036 1037 3070 +a 1036 4106 1 +a 1037 1038 3069 +a 1037 4106 1 +a 1038 1039 3068 +a 1038 4106 1 +a 1039 1040 3067 +a 1039 4106 1 +a 1040 1041 3066 +a 1040 4106 1 +a 1041 1042 3065 +a 1041 4106 1 +a 1042 1043 3064 +a 1042 4106 1 +a 1043 1044 3063 +a 1043 4106 1 +a 1044 1045 3062 +a 1044 4106 1 +a 1045 1046 3061 +a 1045 4106 1 +a 1046 1047 3060 +a 1046 4106 1 +a 1047 1048 3059 +a 1047 4106 1 +a 1048 1049 3058 +a 1048 4106 1 +a 1049 1050 3057 +a 1049 4106 1 +a 1050 1051 3056 +a 1050 4106 1 +a 1051 1052 3055 +a 1051 4106 1 +a 1052 1053 3054 +a 1052 4106 1 +a 1053 1054 3053 +a 1053 4106 1 +a 1054 1055 3052 +a 1054 4106 1 +a 1055 1056 3051 +a 1055 4106 1 +a 1056 1057 3050 +a 1056 4106 1 +a 1057 1058 3049 +a 1057 4106 1 +a 1058 1059 3048 +a 1058 4106 1 +a 1059 1060 3047 +a 1059 4106 1 +a 1060 1061 3046 +a 1060 4106 1 +a 1061 1062 3045 +a 1061 4106 1 +a 1062 1063 3044 +a 1062 4106 1 +a 1063 1064 3043 +a 1063 4106 1 +a 1064 1065 3042 +a 1064 4106 1 +a 1065 1066 3041 +a 1065 4106 1 +a 1066 1067 3040 +a 1066 4106 1 +a 1067 1068 3039 +a 1067 4106 1 +a 1068 1069 3038 +a 1068 4106 1 +a 1069 1070 3037 +a 1069 4106 1 +a 1070 1071 3036 +a 1070 4106 1 +a 1071 1072 3035 +a 1071 4106 1 +a 1072 1073 3034 +a 1072 4106 1 +a 1073 1074 3033 +a 1073 4106 1 +a 1074 1075 3032 +a 1074 4106 1 +a 1075 1076 3031 +a 1075 4106 1 +a 1076 1077 3030 +a 1076 4106 1 +a 1077 1078 3029 +a 1077 4106 1 +a 1078 1079 3028 +a 1078 4106 1 +a 1079 1080 3027 +a 1079 4106 1 +a 1080 1081 3026 +a 1080 4106 1 +a 1081 1082 3025 +a 1081 4106 1 +a 1082 1083 3024 +a 1082 4106 1 +a 1083 1084 3023 +a 1083 4106 1 +a 1084 1085 3022 +a 1084 4106 1 +a 1085 1086 3021 +a 1085 4106 1 +a 1086 1087 3020 +a 1086 4106 1 +a 1087 1088 3019 +a 1087 4106 1 +a 1088 1089 3018 +a 1088 4106 1 +a 1089 1090 3017 +a 1089 4106 1 +a 1090 1091 3016 +a 1090 4106 1 +a 1091 1092 3015 +a 1091 4106 1 +a 1092 1093 3014 +a 1092 4106 1 +a 1093 1094 3013 +a 1093 4106 1 +a 1094 1095 3012 +a 1094 4106 1 +a 1095 1096 3011 +a 1095 4106 1 +a 1096 1097 3010 +a 1096 4106 1 +a 1097 1098 3009 +a 1097 4106 1 +a 1098 1099 3008 +a 1098 4106 1 +a 1099 1100 3007 +a 1099 4106 1 +a 1100 1101 3006 +a 1100 4106 1 +a 1101 1102 3005 +a 1101 4106 1 +a 1102 1103 3004 +a 1102 4106 1 +a 1103 1104 3003 +a 1103 4106 1 +a 1104 1105 3002 +a 1104 4106 1 +a 1105 1106 3001 +a 1105 4106 1 +a 1106 1107 3000 +a 1106 4106 1 +a 1107 1108 2999 +a 1107 4106 1 +a 1108 1109 2998 +a 1108 4106 1 +a 1109 1110 2997 +a 1109 4106 1 +a 1110 1111 2996 +a 1110 4106 1 +a 1111 1112 2995 +a 1111 4106 1 +a 1112 1113 2994 +a 1112 4106 1 +a 1113 1114 2993 +a 1113 4106 1 +a 1114 1115 2992 +a 1114 4106 1 +a 1115 1116 2991 +a 1115 4106 1 +a 1116 1117 2990 +a 1116 4106 1 +a 1117 1118 2989 +a 1117 4106 1 +a 1118 1119 2988 +a 1118 4106 1 +a 1119 1120 2987 +a 1119 4106 1 +a 1120 1121 2986 +a 1120 4106 1 +a 1121 1122 2985 +a 1121 4106 1 +a 1122 1123 2984 +a 1122 4106 1 +a 1123 1124 2983 +a 1123 4106 1 +a 1124 1125 2982 +a 1124 4106 1 +a 1125 1126 2981 +a 1125 4106 1 +a 1126 1127 2980 +a 1126 4106 1 +a 1127 1128 2979 +a 1127 4106 1 +a 1128 1129 2978 +a 1128 4106 1 +a 1129 1130 2977 +a 1129 4106 1 +a 1130 1131 2976 +a 1130 4106 1 +a 1131 1132 2975 +a 1131 4106 1 +a 1132 1133 2974 +a 1132 4106 1 +a 1133 1134 2973 +a 1133 4106 1 +a 1134 1135 2972 +a 1134 4106 1 +a 1135 1136 2971 +a 1135 4106 1 +a 1136 1137 2970 +a 1136 4106 1 +a 1137 1138 2969 +a 1137 4106 1 +a 1138 1139 2968 +a 1138 4106 1 +a 1139 1140 2967 +a 1139 4106 1 +a 1140 1141 2966 +a 1140 4106 1 +a 1141 1142 2965 +a 1141 4106 1 +a 1142 1143 2964 +a 1142 4106 1 +a 1143 1144 2963 +a 1143 4106 1 +a 1144 1145 2962 +a 1144 4106 1 +a 1145 1146 2961 +a 1145 4106 1 +a 1146 1147 2960 +a 1146 4106 1 +a 1147 1148 2959 +a 1147 4106 1 +a 1148 1149 2958 +a 1148 4106 1 +a 1149 1150 2957 +a 1149 4106 1 +a 1150 1151 2956 +a 1150 4106 1 +a 1151 1152 2955 +a 1151 4106 1 +a 1152 1153 2954 +a 1152 4106 1 +a 1153 1154 2953 +a 1153 4106 1 +a 1154 1155 2952 +a 1154 4106 1 +a 1155 1156 2951 +a 1155 4106 1 +a 1156 1157 2950 +a 1156 4106 1 +a 1157 1158 2949 +a 1157 4106 1 +a 1158 1159 2948 +a 1158 4106 1 +a 1159 1160 2947 +a 1159 4106 1 +a 1160 1161 2946 +a 1160 4106 1 +a 1161 1162 2945 +a 1161 4106 1 +a 1162 1163 2944 +a 1162 4106 1 +a 1163 1164 2943 +a 1163 4106 1 +a 1164 1165 2942 +a 1164 4106 1 +a 1165 1166 2941 +a 1165 4106 1 +a 1166 1167 2940 +a 1166 4106 1 +a 1167 1168 2939 +a 1167 4106 1 +a 1168 1169 2938 +a 1168 4106 1 +a 1169 1170 2937 +a 1169 4106 1 +a 1170 1171 2936 +a 1170 4106 1 +a 1171 1172 2935 +a 1171 4106 1 +a 1172 1173 2934 +a 1172 4106 1 +a 1173 1174 2933 +a 1173 4106 1 +a 1174 1175 2932 +a 1174 4106 1 +a 1175 1176 2931 +a 1175 4106 1 +a 1176 1177 2930 +a 1176 4106 1 +a 1177 1178 2929 +a 1177 4106 1 +a 1178 1179 2928 +a 1178 4106 1 +a 1179 1180 2927 +a 1179 4106 1 +a 1180 1181 2926 +a 1180 4106 1 +a 1181 1182 2925 +a 1181 4106 1 +a 1182 1183 2924 +a 1182 4106 1 +a 1183 1184 2923 +a 1183 4106 1 +a 1184 1185 2922 +a 1184 4106 1 +a 1185 1186 2921 +a 1185 4106 1 +a 1186 1187 2920 +a 1186 4106 1 +a 1187 1188 2919 +a 1187 4106 1 +a 1188 1189 2918 +a 1188 4106 1 +a 1189 1190 2917 +a 1189 4106 1 +a 1190 1191 2916 +a 1190 4106 1 +a 1191 1192 2915 +a 1191 4106 1 +a 1192 1193 2914 +a 1192 4106 1 +a 1193 1194 2913 +a 1193 4106 1 +a 1194 1195 2912 +a 1194 4106 1 +a 1195 1196 2911 +a 1195 4106 1 +a 1196 1197 2910 +a 1196 4106 1 +a 1197 1198 2909 +a 1197 4106 1 +a 1198 1199 2908 +a 1198 4106 1 +a 1199 1200 2907 +a 1199 4106 1 +a 1200 1201 2906 +a 1200 4106 1 +a 1201 1202 2905 +a 1201 4106 1 +a 1202 1203 2904 +a 1202 4106 1 +a 1203 1204 2903 +a 1203 4106 1 +a 1204 1205 2902 +a 1204 4106 1 +a 1205 1206 2901 +a 1205 4106 1 +a 1206 1207 2900 +a 1206 4106 1 +a 1207 1208 2899 +a 1207 4106 1 +a 1208 1209 2898 +a 1208 4106 1 +a 1209 1210 2897 +a 1209 4106 1 +a 1210 1211 2896 +a 1210 4106 1 +a 1211 1212 2895 +a 1211 4106 1 +a 1212 1213 2894 +a 1212 4106 1 +a 1213 1214 2893 +a 1213 4106 1 +a 1214 1215 2892 +a 1214 4106 1 +a 1215 1216 2891 +a 1215 4106 1 +a 1216 1217 2890 +a 1216 4106 1 +a 1217 1218 2889 +a 1217 4106 1 +a 1218 1219 2888 +a 1218 4106 1 +a 1219 1220 2887 +a 1219 4106 1 +a 1220 1221 2886 +a 1220 4106 1 +a 1221 1222 2885 +a 1221 4106 1 +a 1222 1223 2884 +a 1222 4106 1 +a 1223 1224 2883 +a 1223 4106 1 +a 1224 1225 2882 +a 1224 4106 1 +a 1225 1226 2881 +a 1225 4106 1 +a 1226 1227 2880 +a 1226 4106 1 +a 1227 1228 2879 +a 1227 4106 1 +a 1228 1229 2878 +a 1228 4106 1 +a 1229 1230 2877 +a 1229 4106 1 +a 1230 1231 2876 +a 1230 4106 1 +a 1231 1232 2875 +a 1231 4106 1 +a 1232 1233 2874 +a 1232 4106 1 +a 1233 1234 2873 +a 1233 4106 1 +a 1234 1235 2872 +a 1234 4106 1 +a 1235 1236 2871 +a 1235 4106 1 +a 1236 1237 2870 +a 1236 4106 1 +a 1237 1238 2869 +a 1237 4106 1 +a 1238 1239 2868 +a 1238 4106 1 +a 1239 1240 2867 +a 1239 4106 1 +a 1240 1241 2866 +a 1240 4106 1 +a 1241 1242 2865 +a 1241 4106 1 +a 1242 1243 2864 +a 1242 4106 1 +a 1243 1244 2863 +a 1243 4106 1 +a 1244 1245 2862 +a 1244 4106 1 +a 1245 1246 2861 +a 1245 4106 1 +a 1246 1247 2860 +a 1246 4106 1 +a 1247 1248 2859 +a 1247 4106 1 +a 1248 1249 2858 +a 1248 4106 1 +a 1249 1250 2857 +a 1249 4106 1 +a 1250 1251 2856 +a 1250 4106 1 +a 1251 1252 2855 +a 1251 4106 1 +a 1252 1253 2854 +a 1252 4106 1 +a 1253 1254 2853 +a 1253 4106 1 +a 1254 1255 2852 +a 1254 4106 1 +a 1255 1256 2851 +a 1255 4106 1 +a 1256 1257 2850 +a 1256 4106 1 +a 1257 1258 2849 +a 1257 4106 1 +a 1258 1259 2848 +a 1258 4106 1 +a 1259 1260 2847 +a 1259 4106 1 +a 1260 1261 2846 +a 1260 4106 1 +a 1261 1262 2845 +a 1261 4106 1 +a 1262 1263 2844 +a 1262 4106 1 +a 1263 1264 2843 +a 1263 4106 1 +a 1264 1265 2842 +a 1264 4106 1 +a 1265 1266 2841 +a 1265 4106 1 +a 1266 1267 2840 +a 1266 4106 1 +a 1267 1268 2839 +a 1267 4106 1 +a 1268 1269 2838 +a 1268 4106 1 +a 1269 1270 2837 +a 1269 4106 1 +a 1270 1271 2836 +a 1270 4106 1 +a 1271 1272 2835 +a 1271 4106 1 +a 1272 1273 2834 +a 1272 4106 1 +a 1273 1274 2833 +a 1273 4106 1 +a 1274 1275 2832 +a 1274 4106 1 +a 1275 1276 2831 +a 1275 4106 1 +a 1276 1277 2830 +a 1276 4106 1 +a 1277 1278 2829 +a 1277 4106 1 +a 1278 1279 2828 +a 1278 4106 1 +a 1279 1280 2827 +a 1279 4106 1 +a 1280 1281 2826 +a 1280 4106 1 +a 1281 1282 2825 +a 1281 4106 1 +a 1282 1283 2824 +a 1282 4106 1 +a 1283 1284 2823 +a 1283 4106 1 +a 1284 1285 2822 +a 1284 4106 1 +a 1285 1286 2821 +a 1285 4106 1 +a 1286 1287 2820 +a 1286 4106 1 +a 1287 1288 2819 +a 1287 4106 1 +a 1288 1289 2818 +a 1288 4106 1 +a 1289 1290 2817 +a 1289 4106 1 +a 1290 1291 2816 +a 1290 4106 1 +a 1291 1292 2815 +a 1291 4106 1 +a 1292 1293 2814 +a 1292 4106 1 +a 1293 1294 2813 +a 1293 4106 1 +a 1294 1295 2812 +a 1294 4106 1 +a 1295 1296 2811 +a 1295 4106 1 +a 1296 1297 2810 +a 1296 4106 1 +a 1297 1298 2809 +a 1297 4106 1 +a 1298 1299 2808 +a 1298 4106 1 +a 1299 1300 2807 +a 1299 4106 1 +a 1300 1301 2806 +a 1300 4106 1 +a 1301 1302 2805 +a 1301 4106 1 +a 1302 1303 2804 +a 1302 4106 1 +a 1303 1304 2803 +a 1303 4106 1 +a 1304 1305 2802 +a 1304 4106 1 +a 1305 1306 2801 +a 1305 4106 1 +a 1306 1307 2800 +a 1306 4106 1 +a 1307 1308 2799 +a 1307 4106 1 +a 1308 1309 2798 +a 1308 4106 1 +a 1309 1310 2797 +a 1309 4106 1 +a 1310 1311 2796 +a 1310 4106 1 +a 1311 1312 2795 +a 1311 4106 1 +a 1312 1313 2794 +a 1312 4106 1 +a 1313 1314 2793 +a 1313 4106 1 +a 1314 1315 2792 +a 1314 4106 1 +a 1315 1316 2791 +a 1315 4106 1 +a 1316 1317 2790 +a 1316 4106 1 +a 1317 1318 2789 +a 1317 4106 1 +a 1318 1319 2788 +a 1318 4106 1 +a 1319 1320 2787 +a 1319 4106 1 +a 1320 1321 2786 +a 1320 4106 1 +a 1321 1322 2785 +a 1321 4106 1 +a 1322 1323 2784 +a 1322 4106 1 +a 1323 1324 2783 +a 1323 4106 1 +a 1324 1325 2782 +a 1324 4106 1 +a 1325 1326 2781 +a 1325 4106 1 +a 1326 1327 2780 +a 1326 4106 1 +a 1327 1328 2779 +a 1327 4106 1 +a 1328 1329 2778 +a 1328 4106 1 +a 1329 1330 2777 +a 1329 4106 1 +a 1330 1331 2776 +a 1330 4106 1 +a 1331 1332 2775 +a 1331 4106 1 +a 1332 1333 2774 +a 1332 4106 1 +a 1333 1334 2773 +a 1333 4106 1 +a 1334 1335 2772 +a 1334 4106 1 +a 1335 1336 2771 +a 1335 4106 1 +a 1336 1337 2770 +a 1336 4106 1 +a 1337 1338 2769 +a 1337 4106 1 +a 1338 1339 2768 +a 1338 4106 1 +a 1339 1340 2767 +a 1339 4106 1 +a 1340 1341 2766 +a 1340 4106 1 +a 1341 1342 2765 +a 1341 4106 1 +a 1342 1343 2764 +a 1342 4106 1 +a 1343 1344 2763 +a 1343 4106 1 +a 1344 1345 2762 +a 1344 4106 1 +a 1345 1346 2761 +a 1345 4106 1 +a 1346 1347 2760 +a 1346 4106 1 +a 1347 1348 2759 +a 1347 4106 1 +a 1348 1349 2758 +a 1348 4106 1 +a 1349 1350 2757 +a 1349 4106 1 +a 1350 1351 2756 +a 1350 4106 1 +a 1351 1352 2755 +a 1351 4106 1 +a 1352 1353 2754 +a 1352 4106 1 +a 1353 1354 2753 +a 1353 4106 1 +a 1354 1355 2752 +a 1354 4106 1 +a 1355 1356 2751 +a 1355 4106 1 +a 1356 1357 2750 +a 1356 4106 1 +a 1357 1358 2749 +a 1357 4106 1 +a 1358 1359 2748 +a 1358 4106 1 +a 1359 1360 2747 +a 1359 4106 1 +a 1360 1361 2746 +a 1360 4106 1 +a 1361 1362 2745 +a 1361 4106 1 +a 1362 1363 2744 +a 1362 4106 1 +a 1363 1364 2743 +a 1363 4106 1 +a 1364 1365 2742 +a 1364 4106 1 +a 1365 1366 2741 +a 1365 4106 1 +a 1366 1367 2740 +a 1366 4106 1 +a 1367 1368 2739 +a 1367 4106 1 +a 1368 1369 2738 +a 1368 4106 1 +a 1369 1370 2737 +a 1369 4106 1 +a 1370 1371 2736 +a 1370 4106 1 +a 1371 1372 2735 +a 1371 4106 1 +a 1372 1373 2734 +a 1372 4106 1 +a 1373 1374 2733 +a 1373 4106 1 +a 1374 1375 2732 +a 1374 4106 1 +a 1375 1376 2731 +a 1375 4106 1 +a 1376 1377 2730 +a 1376 4106 1 +a 1377 1378 2729 +a 1377 4106 1 +a 1378 1379 2728 +a 1378 4106 1 +a 1379 1380 2727 +a 1379 4106 1 +a 1380 1381 2726 +a 1380 4106 1 +a 1381 1382 2725 +a 1381 4106 1 +a 1382 1383 2724 +a 1382 4106 1 +a 1383 1384 2723 +a 1383 4106 1 +a 1384 1385 2722 +a 1384 4106 1 +a 1385 1386 2721 +a 1385 4106 1 +a 1386 1387 2720 +a 1386 4106 1 +a 1387 1388 2719 +a 1387 4106 1 +a 1388 1389 2718 +a 1388 4106 1 +a 1389 1390 2717 +a 1389 4106 1 +a 1390 1391 2716 +a 1390 4106 1 +a 1391 1392 2715 +a 1391 4106 1 +a 1392 1393 2714 +a 1392 4106 1 +a 1393 1394 2713 +a 1393 4106 1 +a 1394 1395 2712 +a 1394 4106 1 +a 1395 1396 2711 +a 1395 4106 1 +a 1396 1397 2710 +a 1396 4106 1 +a 1397 1398 2709 +a 1397 4106 1 +a 1398 1399 2708 +a 1398 4106 1 +a 1399 1400 2707 +a 1399 4106 1 +a 1400 1401 2706 +a 1400 4106 1 +a 1401 1402 2705 +a 1401 4106 1 +a 1402 1403 2704 +a 1402 4106 1 +a 1403 1404 2703 +a 1403 4106 1 +a 1404 1405 2702 +a 1404 4106 1 +a 1405 1406 2701 +a 1405 4106 1 +a 1406 1407 2700 +a 1406 4106 1 +a 1407 1408 2699 +a 1407 4106 1 +a 1408 1409 2698 +a 1408 4106 1 +a 1409 1410 2697 +a 1409 4106 1 +a 1410 1411 2696 +a 1410 4106 1 +a 1411 1412 2695 +a 1411 4106 1 +a 1412 1413 2694 +a 1412 4106 1 +a 1413 1414 2693 +a 1413 4106 1 +a 1414 1415 2692 +a 1414 4106 1 +a 1415 1416 2691 +a 1415 4106 1 +a 1416 1417 2690 +a 1416 4106 1 +a 1417 1418 2689 +a 1417 4106 1 +a 1418 1419 2688 +a 1418 4106 1 +a 1419 1420 2687 +a 1419 4106 1 +a 1420 1421 2686 +a 1420 4106 1 +a 1421 1422 2685 +a 1421 4106 1 +a 1422 1423 2684 +a 1422 4106 1 +a 1423 1424 2683 +a 1423 4106 1 +a 1424 1425 2682 +a 1424 4106 1 +a 1425 1426 2681 +a 1425 4106 1 +a 1426 1427 2680 +a 1426 4106 1 +a 1427 1428 2679 +a 1427 4106 1 +a 1428 1429 2678 +a 1428 4106 1 +a 1429 1430 2677 +a 1429 4106 1 +a 1430 1431 2676 +a 1430 4106 1 +a 1431 1432 2675 +a 1431 4106 1 +a 1432 1433 2674 +a 1432 4106 1 +a 1433 1434 2673 +a 1433 4106 1 +a 1434 1435 2672 +a 1434 4106 1 +a 1435 1436 2671 +a 1435 4106 1 +a 1436 1437 2670 +a 1436 4106 1 +a 1437 1438 2669 +a 1437 4106 1 +a 1438 1439 2668 +a 1438 4106 1 +a 1439 1440 2667 +a 1439 4106 1 +a 1440 1441 2666 +a 1440 4106 1 +a 1441 1442 2665 +a 1441 4106 1 +a 1442 1443 2664 +a 1442 4106 1 +a 1443 1444 2663 +a 1443 4106 1 +a 1444 1445 2662 +a 1444 4106 1 +a 1445 1446 2661 +a 1445 4106 1 +a 1446 1447 2660 +a 1446 4106 1 +a 1447 1448 2659 +a 1447 4106 1 +a 1448 1449 2658 +a 1448 4106 1 +a 1449 1450 2657 +a 1449 4106 1 +a 1450 1451 2656 +a 1450 4106 1 +a 1451 1452 2655 +a 1451 4106 1 +a 1452 1453 2654 +a 1452 4106 1 +a 1453 1454 2653 +a 1453 4106 1 +a 1454 1455 2652 +a 1454 4106 1 +a 1455 1456 2651 +a 1455 4106 1 +a 1456 1457 2650 +a 1456 4106 1 +a 1457 1458 2649 +a 1457 4106 1 +a 1458 1459 2648 +a 1458 4106 1 +a 1459 1460 2647 +a 1459 4106 1 +a 1460 1461 2646 +a 1460 4106 1 +a 1461 1462 2645 +a 1461 4106 1 +a 1462 1463 2644 +a 1462 4106 1 +a 1463 1464 2643 +a 1463 4106 1 +a 1464 1465 2642 +a 1464 4106 1 +a 1465 1466 2641 +a 1465 4106 1 +a 1466 1467 2640 +a 1466 4106 1 +a 1467 1468 2639 +a 1467 4106 1 +a 1468 1469 2638 +a 1468 4106 1 +a 1469 1470 2637 +a 1469 4106 1 +a 1470 1471 2636 +a 1470 4106 1 +a 1471 1472 2635 +a 1471 4106 1 +a 1472 1473 2634 +a 1472 4106 1 +a 1473 1474 2633 +a 1473 4106 1 +a 1474 1475 2632 +a 1474 4106 1 +a 1475 1476 2631 +a 1475 4106 1 +a 1476 1477 2630 +a 1476 4106 1 +a 1477 1478 2629 +a 1477 4106 1 +a 1478 1479 2628 +a 1478 4106 1 +a 1479 1480 2627 +a 1479 4106 1 +a 1480 1481 2626 +a 1480 4106 1 +a 1481 1482 2625 +a 1481 4106 1 +a 1482 1483 2624 +a 1482 4106 1 +a 1483 1484 2623 +a 1483 4106 1 +a 1484 1485 2622 +a 1484 4106 1 +a 1485 1486 2621 +a 1485 4106 1 +a 1486 1487 2620 +a 1486 4106 1 +a 1487 1488 2619 +a 1487 4106 1 +a 1488 1489 2618 +a 1488 4106 1 +a 1489 1490 2617 +a 1489 4106 1 +a 1490 1491 2616 +a 1490 4106 1 +a 1491 1492 2615 +a 1491 4106 1 +a 1492 1493 2614 +a 1492 4106 1 +a 1493 1494 2613 +a 1493 4106 1 +a 1494 1495 2612 +a 1494 4106 1 +a 1495 1496 2611 +a 1495 4106 1 +a 1496 1497 2610 +a 1496 4106 1 +a 1497 1498 2609 +a 1497 4106 1 +a 1498 1499 2608 +a 1498 4106 1 +a 1499 1500 2607 +a 1499 4106 1 +a 1500 1501 2606 +a 1500 4106 1 +a 1501 1502 2605 +a 1501 4106 1 +a 1502 1503 2604 +a 1502 4106 1 +a 1503 1504 2603 +a 1503 4106 1 +a 1504 1505 2602 +a 1504 4106 1 +a 1505 1506 2601 +a 1505 4106 1 +a 1506 1507 2600 +a 1506 4106 1 +a 1507 1508 2599 +a 1507 4106 1 +a 1508 1509 2598 +a 1508 4106 1 +a 1509 1510 2597 +a 1509 4106 1 +a 1510 1511 2596 +a 1510 4106 1 +a 1511 1512 2595 +a 1511 4106 1 +a 1512 1513 2594 +a 1512 4106 1 +a 1513 1514 2593 +a 1513 4106 1 +a 1514 1515 2592 +a 1514 4106 1 +a 1515 1516 2591 +a 1515 4106 1 +a 1516 1517 2590 +a 1516 4106 1 +a 1517 1518 2589 +a 1517 4106 1 +a 1518 1519 2588 +a 1518 4106 1 +a 1519 1520 2587 +a 1519 4106 1 +a 1520 1521 2586 +a 1520 4106 1 +a 1521 1522 2585 +a 1521 4106 1 +a 1522 1523 2584 +a 1522 4106 1 +a 1523 1524 2583 +a 1523 4106 1 +a 1524 1525 2582 +a 1524 4106 1 +a 1525 1526 2581 +a 1525 4106 1 +a 1526 1527 2580 +a 1526 4106 1 +a 1527 1528 2579 +a 1527 4106 1 +a 1528 1529 2578 +a 1528 4106 1 +a 1529 1530 2577 +a 1529 4106 1 +a 1530 1531 2576 +a 1530 4106 1 +a 1531 1532 2575 +a 1531 4106 1 +a 1532 1533 2574 +a 1532 4106 1 +a 1533 1534 2573 +a 1533 4106 1 +a 1534 1535 2572 +a 1534 4106 1 +a 1535 1536 2571 +a 1535 4106 1 +a 1536 1537 2570 +a 1536 4106 1 +a 1537 1538 2569 +a 1537 4106 1 +a 1538 1539 2568 +a 1538 4106 1 +a 1539 1540 2567 +a 1539 4106 1 +a 1540 1541 2566 +a 1540 4106 1 +a 1541 1542 2565 +a 1541 4106 1 +a 1542 1543 2564 +a 1542 4106 1 +a 1543 1544 2563 +a 1543 4106 1 +a 1544 1545 2562 +a 1544 4106 1 +a 1545 1546 2561 +a 1545 4106 1 +a 1546 1547 2560 +a 1546 4106 1 +a 1547 1548 2559 +a 1547 4106 1 +a 1548 1549 2558 +a 1548 4106 1 +a 1549 1550 2557 +a 1549 4106 1 +a 1550 1551 2556 +a 1550 4106 1 +a 1551 1552 2555 +a 1551 4106 1 +a 1552 1553 2554 +a 1552 4106 1 +a 1553 1554 2553 +a 1553 4106 1 +a 1554 1555 2552 +a 1554 4106 1 +a 1555 1556 2551 +a 1555 4106 1 +a 1556 1557 2550 +a 1556 4106 1 +a 1557 1558 2549 +a 1557 4106 1 +a 1558 1559 2548 +a 1558 4106 1 +a 1559 1560 2547 +a 1559 4106 1 +a 1560 1561 2546 +a 1560 4106 1 +a 1561 1562 2545 +a 1561 4106 1 +a 1562 1563 2544 +a 1562 4106 1 +a 1563 1564 2543 +a 1563 4106 1 +a 1564 1565 2542 +a 1564 4106 1 +a 1565 1566 2541 +a 1565 4106 1 +a 1566 1567 2540 +a 1566 4106 1 +a 1567 1568 2539 +a 1567 4106 1 +a 1568 1569 2538 +a 1568 4106 1 +a 1569 1570 2537 +a 1569 4106 1 +a 1570 1571 2536 +a 1570 4106 1 +a 1571 1572 2535 +a 1571 4106 1 +a 1572 1573 2534 +a 1572 4106 1 +a 1573 1574 2533 +a 1573 4106 1 +a 1574 1575 2532 +a 1574 4106 1 +a 1575 1576 2531 +a 1575 4106 1 +a 1576 1577 2530 +a 1576 4106 1 +a 1577 1578 2529 +a 1577 4106 1 +a 1578 1579 2528 +a 1578 4106 1 +a 1579 1580 2527 +a 1579 4106 1 +a 1580 1581 2526 +a 1580 4106 1 +a 1581 1582 2525 +a 1581 4106 1 +a 1582 1583 2524 +a 1582 4106 1 +a 1583 1584 2523 +a 1583 4106 1 +a 1584 1585 2522 +a 1584 4106 1 +a 1585 1586 2521 +a 1585 4106 1 +a 1586 1587 2520 +a 1586 4106 1 +a 1587 1588 2519 +a 1587 4106 1 +a 1588 1589 2518 +a 1588 4106 1 +a 1589 1590 2517 +a 1589 4106 1 +a 1590 1591 2516 +a 1590 4106 1 +a 1591 1592 2515 +a 1591 4106 1 +a 1592 1593 2514 +a 1592 4106 1 +a 1593 1594 2513 +a 1593 4106 1 +a 1594 1595 2512 +a 1594 4106 1 +a 1595 1596 2511 +a 1595 4106 1 +a 1596 1597 2510 +a 1596 4106 1 +a 1597 1598 2509 +a 1597 4106 1 +a 1598 1599 2508 +a 1598 4106 1 +a 1599 1600 2507 +a 1599 4106 1 +a 1600 1601 2506 +a 1600 4106 1 +a 1601 1602 2505 +a 1601 4106 1 +a 1602 1603 2504 +a 1602 4106 1 +a 1603 1604 2503 +a 1603 4106 1 +a 1604 1605 2502 +a 1604 4106 1 +a 1605 1606 2501 +a 1605 4106 1 +a 1606 1607 2500 +a 1606 4106 1 +a 1607 1608 2499 +a 1607 4106 1 +a 1608 1609 2498 +a 1608 4106 1 +a 1609 1610 2497 +a 1609 4106 1 +a 1610 1611 2496 +a 1610 4106 1 +a 1611 1612 2495 +a 1611 4106 1 +a 1612 1613 2494 +a 1612 4106 1 +a 1613 1614 2493 +a 1613 4106 1 +a 1614 1615 2492 +a 1614 4106 1 +a 1615 1616 2491 +a 1615 4106 1 +a 1616 1617 2490 +a 1616 4106 1 +a 1617 1618 2489 +a 1617 4106 1 +a 1618 1619 2488 +a 1618 4106 1 +a 1619 1620 2487 +a 1619 4106 1 +a 1620 1621 2486 +a 1620 4106 1 +a 1621 1622 2485 +a 1621 4106 1 +a 1622 1623 2484 +a 1622 4106 1 +a 1623 1624 2483 +a 1623 4106 1 +a 1624 1625 2482 +a 1624 4106 1 +a 1625 1626 2481 +a 1625 4106 1 +a 1626 1627 2480 +a 1626 4106 1 +a 1627 1628 2479 +a 1627 4106 1 +a 1628 1629 2478 +a 1628 4106 1 +a 1629 1630 2477 +a 1629 4106 1 +a 1630 1631 2476 +a 1630 4106 1 +a 1631 1632 2475 +a 1631 4106 1 +a 1632 1633 2474 +a 1632 4106 1 +a 1633 1634 2473 +a 1633 4106 1 +a 1634 1635 2472 +a 1634 4106 1 +a 1635 1636 2471 +a 1635 4106 1 +a 1636 1637 2470 +a 1636 4106 1 +a 1637 1638 2469 +a 1637 4106 1 +a 1638 1639 2468 +a 1638 4106 1 +a 1639 1640 2467 +a 1639 4106 1 +a 1640 1641 2466 +a 1640 4106 1 +a 1641 1642 2465 +a 1641 4106 1 +a 1642 1643 2464 +a 1642 4106 1 +a 1643 1644 2463 +a 1643 4106 1 +a 1644 1645 2462 +a 1644 4106 1 +a 1645 1646 2461 +a 1645 4106 1 +a 1646 1647 2460 +a 1646 4106 1 +a 1647 1648 2459 +a 1647 4106 1 +a 1648 1649 2458 +a 1648 4106 1 +a 1649 1650 2457 +a 1649 4106 1 +a 1650 1651 2456 +a 1650 4106 1 +a 1651 1652 2455 +a 1651 4106 1 +a 1652 1653 2454 +a 1652 4106 1 +a 1653 1654 2453 +a 1653 4106 1 +a 1654 1655 2452 +a 1654 4106 1 +a 1655 1656 2451 +a 1655 4106 1 +a 1656 1657 2450 +a 1656 4106 1 +a 1657 1658 2449 +a 1657 4106 1 +a 1658 1659 2448 +a 1658 4106 1 +a 1659 1660 2447 +a 1659 4106 1 +a 1660 1661 2446 +a 1660 4106 1 +a 1661 1662 2445 +a 1661 4106 1 +a 1662 1663 2444 +a 1662 4106 1 +a 1663 1664 2443 +a 1663 4106 1 +a 1664 1665 2442 +a 1664 4106 1 +a 1665 1666 2441 +a 1665 4106 1 +a 1666 1667 2440 +a 1666 4106 1 +a 1667 1668 2439 +a 1667 4106 1 +a 1668 1669 2438 +a 1668 4106 1 +a 1669 1670 2437 +a 1669 4106 1 +a 1670 1671 2436 +a 1670 4106 1 +a 1671 1672 2435 +a 1671 4106 1 +a 1672 1673 2434 +a 1672 4106 1 +a 1673 1674 2433 +a 1673 4106 1 +a 1674 1675 2432 +a 1674 4106 1 +a 1675 1676 2431 +a 1675 4106 1 +a 1676 1677 2430 +a 1676 4106 1 +a 1677 1678 2429 +a 1677 4106 1 +a 1678 1679 2428 +a 1678 4106 1 +a 1679 1680 2427 +a 1679 4106 1 +a 1680 1681 2426 +a 1680 4106 1 +a 1681 1682 2425 +a 1681 4106 1 +a 1682 1683 2424 +a 1682 4106 1 +a 1683 1684 2423 +a 1683 4106 1 +a 1684 1685 2422 +a 1684 4106 1 +a 1685 1686 2421 +a 1685 4106 1 +a 1686 1687 2420 +a 1686 4106 1 +a 1687 1688 2419 +a 1687 4106 1 +a 1688 1689 2418 +a 1688 4106 1 +a 1689 1690 2417 +a 1689 4106 1 +a 1690 1691 2416 +a 1690 4106 1 +a 1691 1692 2415 +a 1691 4106 1 +a 1692 1693 2414 +a 1692 4106 1 +a 1693 1694 2413 +a 1693 4106 1 +a 1694 1695 2412 +a 1694 4106 1 +a 1695 1696 2411 +a 1695 4106 1 +a 1696 1697 2410 +a 1696 4106 1 +a 1697 1698 2409 +a 1697 4106 1 +a 1698 1699 2408 +a 1698 4106 1 +a 1699 1700 2407 +a 1699 4106 1 +a 1700 1701 2406 +a 1700 4106 1 +a 1701 1702 2405 +a 1701 4106 1 +a 1702 1703 2404 +a 1702 4106 1 +a 1703 1704 2403 +a 1703 4106 1 +a 1704 1705 2402 +a 1704 4106 1 +a 1705 1706 2401 +a 1705 4106 1 +a 1706 1707 2400 +a 1706 4106 1 +a 1707 1708 2399 +a 1707 4106 1 +a 1708 1709 2398 +a 1708 4106 1 +a 1709 1710 2397 +a 1709 4106 1 +a 1710 1711 2396 +a 1710 4106 1 +a 1711 1712 2395 +a 1711 4106 1 +a 1712 1713 2394 +a 1712 4106 1 +a 1713 1714 2393 +a 1713 4106 1 +a 1714 1715 2392 +a 1714 4106 1 +a 1715 1716 2391 +a 1715 4106 1 +a 1716 1717 2390 +a 1716 4106 1 +a 1717 1718 2389 +a 1717 4106 1 +a 1718 1719 2388 +a 1718 4106 1 +a 1719 1720 2387 +a 1719 4106 1 +a 1720 1721 2386 +a 1720 4106 1 +a 1721 1722 2385 +a 1721 4106 1 +a 1722 1723 2384 +a 1722 4106 1 +a 1723 1724 2383 +a 1723 4106 1 +a 1724 1725 2382 +a 1724 4106 1 +a 1725 1726 2381 +a 1725 4106 1 +a 1726 1727 2380 +a 1726 4106 1 +a 1727 1728 2379 +a 1727 4106 1 +a 1728 1729 2378 +a 1728 4106 1 +a 1729 1730 2377 +a 1729 4106 1 +a 1730 1731 2376 +a 1730 4106 1 +a 1731 1732 2375 +a 1731 4106 1 +a 1732 1733 2374 +a 1732 4106 1 +a 1733 1734 2373 +a 1733 4106 1 +a 1734 1735 2372 +a 1734 4106 1 +a 1735 1736 2371 +a 1735 4106 1 +a 1736 1737 2370 +a 1736 4106 1 +a 1737 1738 2369 +a 1737 4106 1 +a 1738 1739 2368 +a 1738 4106 1 +a 1739 1740 2367 +a 1739 4106 1 +a 1740 1741 2366 +a 1740 4106 1 +a 1741 1742 2365 +a 1741 4106 1 +a 1742 1743 2364 +a 1742 4106 1 +a 1743 1744 2363 +a 1743 4106 1 +a 1744 1745 2362 +a 1744 4106 1 +a 1745 1746 2361 +a 1745 4106 1 +a 1746 1747 2360 +a 1746 4106 1 +a 1747 1748 2359 +a 1747 4106 1 +a 1748 1749 2358 +a 1748 4106 1 +a 1749 1750 2357 +a 1749 4106 1 +a 1750 1751 2356 +a 1750 4106 1 +a 1751 1752 2355 +a 1751 4106 1 +a 1752 1753 2354 +a 1752 4106 1 +a 1753 1754 2353 +a 1753 4106 1 +a 1754 1755 2352 +a 1754 4106 1 +a 1755 1756 2351 +a 1755 4106 1 +a 1756 1757 2350 +a 1756 4106 1 +a 1757 1758 2349 +a 1757 4106 1 +a 1758 1759 2348 +a 1758 4106 1 +a 1759 1760 2347 +a 1759 4106 1 +a 1760 1761 2346 +a 1760 4106 1 +a 1761 1762 2345 +a 1761 4106 1 +a 1762 1763 2344 +a 1762 4106 1 +a 1763 1764 2343 +a 1763 4106 1 +a 1764 1765 2342 +a 1764 4106 1 +a 1765 1766 2341 +a 1765 4106 1 +a 1766 1767 2340 +a 1766 4106 1 +a 1767 1768 2339 +a 1767 4106 1 +a 1768 1769 2338 +a 1768 4106 1 +a 1769 1770 2337 +a 1769 4106 1 +a 1770 1771 2336 +a 1770 4106 1 +a 1771 1772 2335 +a 1771 4106 1 +a 1772 1773 2334 +a 1772 4106 1 +a 1773 1774 2333 +a 1773 4106 1 +a 1774 1775 2332 +a 1774 4106 1 +a 1775 1776 2331 +a 1775 4106 1 +a 1776 1777 2330 +a 1776 4106 1 +a 1777 1778 2329 +a 1777 4106 1 +a 1778 1779 2328 +a 1778 4106 1 +a 1779 1780 2327 +a 1779 4106 1 +a 1780 1781 2326 +a 1780 4106 1 +a 1781 1782 2325 +a 1781 4106 1 +a 1782 1783 2324 +a 1782 4106 1 +a 1783 1784 2323 +a 1783 4106 1 +a 1784 1785 2322 +a 1784 4106 1 +a 1785 1786 2321 +a 1785 4106 1 +a 1786 1787 2320 +a 1786 4106 1 +a 1787 1788 2319 +a 1787 4106 1 +a 1788 1789 2318 +a 1788 4106 1 +a 1789 1790 2317 +a 1789 4106 1 +a 1790 1791 2316 +a 1790 4106 1 +a 1791 1792 2315 +a 1791 4106 1 +a 1792 1793 2314 +a 1792 4106 1 +a 1793 1794 2313 +a 1793 4106 1 +a 1794 1795 2312 +a 1794 4106 1 +a 1795 1796 2311 +a 1795 4106 1 +a 1796 1797 2310 +a 1796 4106 1 +a 1797 1798 2309 +a 1797 4106 1 +a 1798 1799 2308 +a 1798 4106 1 +a 1799 1800 2307 +a 1799 4106 1 +a 1800 1801 2306 +a 1800 4106 1 +a 1801 1802 2305 +a 1801 4106 1 +a 1802 1803 2304 +a 1802 4106 1 +a 1803 1804 2303 +a 1803 4106 1 +a 1804 1805 2302 +a 1804 4106 1 +a 1805 1806 2301 +a 1805 4106 1 +a 1806 1807 2300 +a 1806 4106 1 +a 1807 1808 2299 +a 1807 4106 1 +a 1808 1809 2298 +a 1808 4106 1 +a 1809 1810 2297 +a 1809 4106 1 +a 1810 1811 2296 +a 1810 4106 1 +a 1811 1812 2295 +a 1811 4106 1 +a 1812 1813 2294 +a 1812 4106 1 +a 1813 1814 2293 +a 1813 4106 1 +a 1814 1815 2292 +a 1814 4106 1 +a 1815 1816 2291 +a 1815 4106 1 +a 1816 1817 2290 +a 1816 4106 1 +a 1817 1818 2289 +a 1817 4106 1 +a 1818 1819 2288 +a 1818 4106 1 +a 1819 1820 2287 +a 1819 4106 1 +a 1820 1821 2286 +a 1820 4106 1 +a 1821 1822 2285 +a 1821 4106 1 +a 1822 1823 2284 +a 1822 4106 1 +a 1823 1824 2283 +a 1823 4106 1 +a 1824 1825 2282 +a 1824 4106 1 +a 1825 1826 2281 +a 1825 4106 1 +a 1826 1827 2280 +a 1826 4106 1 +a 1827 1828 2279 +a 1827 4106 1 +a 1828 1829 2278 +a 1828 4106 1 +a 1829 1830 2277 +a 1829 4106 1 +a 1830 1831 2276 +a 1830 4106 1 +a 1831 1832 2275 +a 1831 4106 1 +a 1832 1833 2274 +a 1832 4106 1 +a 1833 1834 2273 +a 1833 4106 1 +a 1834 1835 2272 +a 1834 4106 1 +a 1835 1836 2271 +a 1835 4106 1 +a 1836 1837 2270 +a 1836 4106 1 +a 1837 1838 2269 +a 1837 4106 1 +a 1838 1839 2268 +a 1838 4106 1 +a 1839 1840 2267 +a 1839 4106 1 +a 1840 1841 2266 +a 1840 4106 1 +a 1841 1842 2265 +a 1841 4106 1 +a 1842 1843 2264 +a 1842 4106 1 +a 1843 1844 2263 +a 1843 4106 1 +a 1844 1845 2262 +a 1844 4106 1 +a 1845 1846 2261 +a 1845 4106 1 +a 1846 1847 2260 +a 1846 4106 1 +a 1847 1848 2259 +a 1847 4106 1 +a 1848 1849 2258 +a 1848 4106 1 +a 1849 1850 2257 +a 1849 4106 1 +a 1850 1851 2256 +a 1850 4106 1 +a 1851 1852 2255 +a 1851 4106 1 +a 1852 1853 2254 +a 1852 4106 1 +a 1853 1854 2253 +a 1853 4106 1 +a 1854 1855 2252 +a 1854 4106 1 +a 1855 1856 2251 +a 1855 4106 1 +a 1856 1857 2250 +a 1856 4106 1 +a 1857 1858 2249 +a 1857 4106 1 +a 1858 1859 2248 +a 1858 4106 1 +a 1859 1860 2247 +a 1859 4106 1 +a 1860 1861 2246 +a 1860 4106 1 +a 1861 1862 2245 +a 1861 4106 1 +a 1862 1863 2244 +a 1862 4106 1 +a 1863 1864 2243 +a 1863 4106 1 +a 1864 1865 2242 +a 1864 4106 1 +a 1865 1866 2241 +a 1865 4106 1 +a 1866 1867 2240 +a 1866 4106 1 +a 1867 1868 2239 +a 1867 4106 1 +a 1868 1869 2238 +a 1868 4106 1 +a 1869 1870 2237 +a 1869 4106 1 +a 1870 1871 2236 +a 1870 4106 1 +a 1871 1872 2235 +a 1871 4106 1 +a 1872 1873 2234 +a 1872 4106 1 +a 1873 1874 2233 +a 1873 4106 1 +a 1874 1875 2232 +a 1874 4106 1 +a 1875 1876 2231 +a 1875 4106 1 +a 1876 1877 2230 +a 1876 4106 1 +a 1877 1878 2229 +a 1877 4106 1 +a 1878 1879 2228 +a 1878 4106 1 +a 1879 1880 2227 +a 1879 4106 1 +a 1880 1881 2226 +a 1880 4106 1 +a 1881 1882 2225 +a 1881 4106 1 +a 1882 1883 2224 +a 1882 4106 1 +a 1883 1884 2223 +a 1883 4106 1 +a 1884 1885 2222 +a 1884 4106 1 +a 1885 1886 2221 +a 1885 4106 1 +a 1886 1887 2220 +a 1886 4106 1 +a 1887 1888 2219 +a 1887 4106 1 +a 1888 1889 2218 +a 1888 4106 1 +a 1889 1890 2217 +a 1889 4106 1 +a 1890 1891 2216 +a 1890 4106 1 +a 1891 1892 2215 +a 1891 4106 1 +a 1892 1893 2214 +a 1892 4106 1 +a 1893 1894 2213 +a 1893 4106 1 +a 1894 1895 2212 +a 1894 4106 1 +a 1895 1896 2211 +a 1895 4106 1 +a 1896 1897 2210 +a 1896 4106 1 +a 1897 1898 2209 +a 1897 4106 1 +a 1898 1899 2208 +a 1898 4106 1 +a 1899 1900 2207 +a 1899 4106 1 +a 1900 1901 2206 +a 1900 4106 1 +a 1901 1902 2205 +a 1901 4106 1 +a 1902 1903 2204 +a 1902 4106 1 +a 1903 1904 2203 +a 1903 4106 1 +a 1904 1905 2202 +a 1904 4106 1 +a 1905 1906 2201 +a 1905 4106 1 +a 1906 1907 2200 +a 1906 4106 1 +a 1907 1908 2199 +a 1907 4106 1 +a 1908 1909 2198 +a 1908 4106 1 +a 1909 1910 2197 +a 1909 4106 1 +a 1910 1911 2196 +a 1910 4106 1 +a 1911 1912 2195 +a 1911 4106 1 +a 1912 1913 2194 +a 1912 4106 1 +a 1913 1914 2193 +a 1913 4106 1 +a 1914 1915 2192 +a 1914 4106 1 +a 1915 1916 2191 +a 1915 4106 1 +a 1916 1917 2190 +a 1916 4106 1 +a 1917 1918 2189 +a 1917 4106 1 +a 1918 1919 2188 +a 1918 4106 1 +a 1919 1920 2187 +a 1919 4106 1 +a 1920 1921 2186 +a 1920 4106 1 +a 1921 1922 2185 +a 1921 4106 1 +a 1922 1923 2184 +a 1922 4106 1 +a 1923 1924 2183 +a 1923 4106 1 +a 1924 1925 2182 +a 1924 4106 1 +a 1925 1926 2181 +a 1925 4106 1 +a 1926 1927 2180 +a 1926 4106 1 +a 1927 1928 2179 +a 1927 4106 1 +a 1928 1929 2178 +a 1928 4106 1 +a 1929 1930 2177 +a 1929 4106 1 +a 1930 1931 2176 +a 1930 4106 1 +a 1931 1932 2175 +a 1931 4106 1 +a 1932 1933 2174 +a 1932 4106 1 +a 1933 1934 2173 +a 1933 4106 1 +a 1934 1935 2172 +a 1934 4106 1 +a 1935 1936 2171 +a 1935 4106 1 +a 1936 1937 2170 +a 1936 4106 1 +a 1937 1938 2169 +a 1937 4106 1 +a 1938 1939 2168 +a 1938 4106 1 +a 1939 1940 2167 +a 1939 4106 1 +a 1940 1941 2166 +a 1940 4106 1 +a 1941 1942 2165 +a 1941 4106 1 +a 1942 1943 2164 +a 1942 4106 1 +a 1943 1944 2163 +a 1943 4106 1 +a 1944 1945 2162 +a 1944 4106 1 +a 1945 1946 2161 +a 1945 4106 1 +a 1946 1947 2160 +a 1946 4106 1 +a 1947 1948 2159 +a 1947 4106 1 +a 1948 1949 2158 +a 1948 4106 1 +a 1949 1950 2157 +a 1949 4106 1 +a 1950 1951 2156 +a 1950 4106 1 +a 1951 1952 2155 +a 1951 4106 1 +a 1952 1953 2154 +a 1952 4106 1 +a 1953 1954 2153 +a 1953 4106 1 +a 1954 1955 2152 +a 1954 4106 1 +a 1955 1956 2151 +a 1955 4106 1 +a 1956 1957 2150 +a 1956 4106 1 +a 1957 1958 2149 +a 1957 4106 1 +a 1958 1959 2148 +a 1958 4106 1 +a 1959 1960 2147 +a 1959 4106 1 +a 1960 1961 2146 +a 1960 4106 1 +a 1961 1962 2145 +a 1961 4106 1 +a 1962 1963 2144 +a 1962 4106 1 +a 1963 1964 2143 +a 1963 4106 1 +a 1964 1965 2142 +a 1964 4106 1 +a 1965 1966 2141 +a 1965 4106 1 +a 1966 1967 2140 +a 1966 4106 1 +a 1967 1968 2139 +a 1967 4106 1 +a 1968 1969 2138 +a 1968 4106 1 +a 1969 1970 2137 +a 1969 4106 1 +a 1970 1971 2136 +a 1970 4106 1 +a 1971 1972 2135 +a 1971 4106 1 +a 1972 1973 2134 +a 1972 4106 1 +a 1973 1974 2133 +a 1973 4106 1 +a 1974 1975 2132 +a 1974 4106 1 +a 1975 1976 2131 +a 1975 4106 1 +a 1976 1977 2130 +a 1976 4106 1 +a 1977 1978 2129 +a 1977 4106 1 +a 1978 1979 2128 +a 1978 4106 1 +a 1979 1980 2127 +a 1979 4106 1 +a 1980 1981 2126 +a 1980 4106 1 +a 1981 1982 2125 +a 1981 4106 1 +a 1982 1983 2124 +a 1982 4106 1 +a 1983 1984 2123 +a 1983 4106 1 +a 1984 1985 2122 +a 1984 4106 1 +a 1985 1986 2121 +a 1985 4106 1 +a 1986 1987 2120 +a 1986 4106 1 +a 1987 1988 2119 +a 1987 4106 1 +a 1988 1989 2118 +a 1988 4106 1 +a 1989 1990 2117 +a 1989 4106 1 +a 1990 1991 2116 +a 1990 4106 1 +a 1991 1992 2115 +a 1991 4106 1 +a 1992 1993 2114 +a 1992 4106 1 +a 1993 1994 2113 +a 1993 4106 1 +a 1994 1995 2112 +a 1994 4106 1 +a 1995 1996 2111 +a 1995 4106 1 +a 1996 1997 2110 +a 1996 4106 1 +a 1997 1998 2109 +a 1997 4106 1 +a 1998 1999 2108 +a 1998 4106 1 +a 1999 2000 2107 +a 1999 4106 1 +a 2000 2001 2106 +a 2000 4106 1 +a 2001 2002 2105 +a 2001 4106 1 +a 2002 2003 2104 +a 2002 4106 1 +a 2003 2004 2103 +a 2003 4106 1 +a 2004 2005 2102 +a 2004 4106 1 +a 2005 2006 2101 +a 2005 4106 1 +a 2006 2007 2100 +a 2006 4106 1 +a 2007 2008 2099 +a 2007 4106 1 +a 2008 2009 2098 +a 2008 4106 1 +a 2009 2010 2097 +a 2009 4106 1 +a 2010 2011 2096 +a 2010 4106 1 +a 2011 2012 2095 +a 2011 4106 1 +a 2012 2013 2094 +a 2012 4106 1 +a 2013 2014 2093 +a 2013 4106 1 +a 2014 2015 2092 +a 2014 4106 1 +a 2015 2016 2091 +a 2015 4106 1 +a 2016 2017 2090 +a 2016 4106 1 +a 2017 2018 2089 +a 2017 4106 1 +a 2018 2019 2088 +a 2018 4106 1 +a 2019 2020 2087 +a 2019 4106 1 +a 2020 2021 2086 +a 2020 4106 1 +a 2021 2022 2085 +a 2021 4106 1 +a 2022 2023 2084 +a 2022 4106 1 +a 2023 2024 2083 +a 2023 4106 1 +a 2024 2025 2082 +a 2024 4106 1 +a 2025 2026 2081 +a 2025 4106 1 +a 2026 2027 2080 +a 2026 4106 1 +a 2027 2028 2079 +a 2027 4106 1 +a 2028 2029 2078 +a 2028 4106 1 +a 2029 2030 2077 +a 2029 4106 1 +a 2030 2031 2076 +a 2030 4106 1 +a 2031 2032 2075 +a 2031 4106 1 +a 2032 2033 2074 +a 2032 4106 1 +a 2033 2034 2073 +a 2033 4106 1 +a 2034 2035 2072 +a 2034 4106 1 +a 2035 2036 2071 +a 2035 4106 1 +a 2036 2037 2070 +a 2036 4106 1 +a 2037 2038 2069 +a 2037 4106 1 +a 2038 2039 2068 +a 2038 4106 1 +a 2039 2040 2067 +a 2039 4106 1 +a 2040 2041 2066 +a 2040 4106 1 +a 2041 2042 2065 +a 2041 4106 1 +a 2042 2043 2064 +a 2042 4106 1 +a 2043 2044 2063 +a 2043 4106 1 +a 2044 2045 2062 +a 2044 4106 1 +a 2045 2046 2061 +a 2045 4106 1 +a 2046 2047 2060 +a 2046 4106 1 +a 2047 2048 2059 +a 2047 4106 1 +a 2048 2049 2058 +a 2048 4106 1 +a 2049 2050 2057 +a 2049 4106 1 +a 2050 2051 2056 +a 2050 4106 1 +a 2051 2052 2055 +a 2051 4106 1 +a 2052 2053 2054 +a 2052 4106 1 +a 2053 2054 2053 +a 2053 4106 1 +a 2054 2055 2052 +a 2054 4106 1 +a 2055 2056 2051 +a 2055 4106 1 +a 2056 2057 2050 +a 2056 4106 1 +a 2057 2058 2049 +a 2057 4106 1 +a 2058 2059 2048 +a 2058 4106 1 +a 2059 2060 2047 +a 2059 4106 1 +a 2060 2061 2046 +a 2060 4106 1 +a 2061 2062 2045 +a 2061 4106 1 +a 2062 2063 2044 +a 2062 4106 1 +a 2063 2064 2043 +a 2063 4106 1 +a 2064 2065 2042 +a 2064 4106 1 +a 2065 2066 2041 +a 2065 4106 1 +a 2066 2067 2040 +a 2066 4106 1 +a 2067 2068 2039 +a 2067 4106 1 +a 2068 2069 2038 +a 2068 4106 1 +a 2069 2070 2037 +a 2069 4106 1 +a 2070 2071 2036 +a 2070 4106 1 +a 2071 2072 2035 +a 2071 4106 1 +a 2072 2073 2034 +a 2072 4106 1 +a 2073 2074 2033 +a 2073 4106 1 +a 2074 2075 2032 +a 2074 4106 1 +a 2075 2076 2031 +a 2075 4106 1 +a 2076 2077 2030 +a 2076 4106 1 +a 2077 2078 2029 +a 2077 4106 1 +a 2078 2079 2028 +a 2078 4106 1 +a 2079 2080 2027 +a 2079 4106 1 +a 2080 2081 2026 +a 2080 4106 1 +a 2081 2082 2025 +a 2081 4106 1 +a 2082 2083 2024 +a 2082 4106 1 +a 2083 2084 2023 +a 2083 4106 1 +a 2084 2085 2022 +a 2084 4106 1 +a 2085 2086 2021 +a 2085 4106 1 +a 2086 2087 2020 +a 2086 4106 1 +a 2087 2088 2019 +a 2087 4106 1 +a 2088 2089 2018 +a 2088 4106 1 +a 2089 2090 2017 +a 2089 4106 1 +a 2090 2091 2016 +a 2090 4106 1 +a 2091 2092 2015 +a 2091 4106 1 +a 2092 2093 2014 +a 2092 4106 1 +a 2093 2094 2013 +a 2093 4106 1 +a 2094 2095 2012 +a 2094 4106 1 +a 2095 2096 2011 +a 2095 4106 1 +a 2096 2097 2010 +a 2096 4106 1 +a 2097 2098 2009 +a 2097 4106 1 +a 2098 2099 2008 +a 2098 4106 1 +a 2099 2100 2007 +a 2099 4106 1 +a 2100 2101 2006 +a 2100 4106 1 +a 2101 2102 2005 +a 2101 4106 1 +a 2102 2103 2004 +a 2102 4106 1 +a 2103 2104 2003 +a 2103 4106 1 +a 2104 2105 2002 +a 2104 4106 1 +a 2105 2106 2001 +a 2105 4106 1 +a 2106 2107 2000 +a 2106 4106 1 +a 2107 2108 1999 +a 2107 4106 1 +a 2108 2109 1998 +a 2108 4106 1 +a 2109 2110 1997 +a 2109 4106 1 +a 2110 2111 1996 +a 2110 4106 1 +a 2111 2112 1995 +a 2111 4106 1 +a 2112 2113 1994 +a 2112 4106 1 +a 2113 2114 1993 +a 2113 4106 1 +a 2114 2115 1992 +a 2114 4106 1 +a 2115 2116 1991 +a 2115 4106 1 +a 2116 2117 1990 +a 2116 4106 1 +a 2117 2118 1989 +a 2117 4106 1 +a 2118 2119 1988 +a 2118 4106 1 +a 2119 2120 1987 +a 2119 4106 1 +a 2120 2121 1986 +a 2120 4106 1 +a 2121 2122 1985 +a 2121 4106 1 +a 2122 2123 1984 +a 2122 4106 1 +a 2123 2124 1983 +a 2123 4106 1 +a 2124 2125 1982 +a 2124 4106 1 +a 2125 2126 1981 +a 2125 4106 1 +a 2126 2127 1980 +a 2126 4106 1 +a 2127 2128 1979 +a 2127 4106 1 +a 2128 2129 1978 +a 2128 4106 1 +a 2129 2130 1977 +a 2129 4106 1 +a 2130 2131 1976 +a 2130 4106 1 +a 2131 2132 1975 +a 2131 4106 1 +a 2132 2133 1974 +a 2132 4106 1 +a 2133 2134 1973 +a 2133 4106 1 +a 2134 2135 1972 +a 2134 4106 1 +a 2135 2136 1971 +a 2135 4106 1 +a 2136 2137 1970 +a 2136 4106 1 +a 2137 2138 1969 +a 2137 4106 1 +a 2138 2139 1968 +a 2138 4106 1 +a 2139 2140 1967 +a 2139 4106 1 +a 2140 2141 1966 +a 2140 4106 1 +a 2141 2142 1965 +a 2141 4106 1 +a 2142 2143 1964 +a 2142 4106 1 +a 2143 2144 1963 +a 2143 4106 1 +a 2144 2145 1962 +a 2144 4106 1 +a 2145 2146 1961 +a 2145 4106 1 +a 2146 2147 1960 +a 2146 4106 1 +a 2147 2148 1959 +a 2147 4106 1 +a 2148 2149 1958 +a 2148 4106 1 +a 2149 2150 1957 +a 2149 4106 1 +a 2150 2151 1956 +a 2150 4106 1 +a 2151 2152 1955 +a 2151 4106 1 +a 2152 2153 1954 +a 2152 4106 1 +a 2153 2154 1953 +a 2153 4106 1 +a 2154 2155 1952 +a 2154 4106 1 +a 2155 2156 1951 +a 2155 4106 1 +a 2156 2157 1950 +a 2156 4106 1 +a 2157 2158 1949 +a 2157 4106 1 +a 2158 2159 1948 +a 2158 4106 1 +a 2159 2160 1947 +a 2159 4106 1 +a 2160 2161 1946 +a 2160 4106 1 +a 2161 2162 1945 +a 2161 4106 1 +a 2162 2163 1944 +a 2162 4106 1 +a 2163 2164 1943 +a 2163 4106 1 +a 2164 2165 1942 +a 2164 4106 1 +a 2165 2166 1941 +a 2165 4106 1 +a 2166 2167 1940 +a 2166 4106 1 +a 2167 2168 1939 +a 2167 4106 1 +a 2168 2169 1938 +a 2168 4106 1 +a 2169 2170 1937 +a 2169 4106 1 +a 2170 2171 1936 +a 2170 4106 1 +a 2171 2172 1935 +a 2171 4106 1 +a 2172 2173 1934 +a 2172 4106 1 +a 2173 2174 1933 +a 2173 4106 1 +a 2174 2175 1932 +a 2174 4106 1 +a 2175 2176 1931 +a 2175 4106 1 +a 2176 2177 1930 +a 2176 4106 1 +a 2177 2178 1929 +a 2177 4106 1 +a 2178 2179 1928 +a 2178 4106 1 +a 2179 2180 1927 +a 2179 4106 1 +a 2180 2181 1926 +a 2180 4106 1 +a 2181 2182 1925 +a 2181 4106 1 +a 2182 2183 1924 +a 2182 4106 1 +a 2183 2184 1923 +a 2183 4106 1 +a 2184 2185 1922 +a 2184 4106 1 +a 2185 2186 1921 +a 2185 4106 1 +a 2186 2187 1920 +a 2186 4106 1 +a 2187 2188 1919 +a 2187 4106 1 +a 2188 2189 1918 +a 2188 4106 1 +a 2189 2190 1917 +a 2189 4106 1 +a 2190 2191 1916 +a 2190 4106 1 +a 2191 2192 1915 +a 2191 4106 1 +a 2192 2193 1914 +a 2192 4106 1 +a 2193 2194 1913 +a 2193 4106 1 +a 2194 2195 1912 +a 2194 4106 1 +a 2195 2196 1911 +a 2195 4106 1 +a 2196 2197 1910 +a 2196 4106 1 +a 2197 2198 1909 +a 2197 4106 1 +a 2198 2199 1908 +a 2198 4106 1 +a 2199 2200 1907 +a 2199 4106 1 +a 2200 2201 1906 +a 2200 4106 1 +a 2201 2202 1905 +a 2201 4106 1 +a 2202 2203 1904 +a 2202 4106 1 +a 2203 2204 1903 +a 2203 4106 1 +a 2204 2205 1902 +a 2204 4106 1 +a 2205 2206 1901 +a 2205 4106 1 +a 2206 2207 1900 +a 2206 4106 1 +a 2207 2208 1899 +a 2207 4106 1 +a 2208 2209 1898 +a 2208 4106 1 +a 2209 2210 1897 +a 2209 4106 1 +a 2210 2211 1896 +a 2210 4106 1 +a 2211 2212 1895 +a 2211 4106 1 +a 2212 2213 1894 +a 2212 4106 1 +a 2213 2214 1893 +a 2213 4106 1 +a 2214 2215 1892 +a 2214 4106 1 +a 2215 2216 1891 +a 2215 4106 1 +a 2216 2217 1890 +a 2216 4106 1 +a 2217 2218 1889 +a 2217 4106 1 +a 2218 2219 1888 +a 2218 4106 1 +a 2219 2220 1887 +a 2219 4106 1 +a 2220 2221 1886 +a 2220 4106 1 +a 2221 2222 1885 +a 2221 4106 1 +a 2222 2223 1884 +a 2222 4106 1 +a 2223 2224 1883 +a 2223 4106 1 +a 2224 2225 1882 +a 2224 4106 1 +a 2225 2226 1881 +a 2225 4106 1 +a 2226 2227 1880 +a 2226 4106 1 +a 2227 2228 1879 +a 2227 4106 1 +a 2228 2229 1878 +a 2228 4106 1 +a 2229 2230 1877 +a 2229 4106 1 +a 2230 2231 1876 +a 2230 4106 1 +a 2231 2232 1875 +a 2231 4106 1 +a 2232 2233 1874 +a 2232 4106 1 +a 2233 2234 1873 +a 2233 4106 1 +a 2234 2235 1872 +a 2234 4106 1 +a 2235 2236 1871 +a 2235 4106 1 +a 2236 2237 1870 +a 2236 4106 1 +a 2237 2238 1869 +a 2237 4106 1 +a 2238 2239 1868 +a 2238 4106 1 +a 2239 2240 1867 +a 2239 4106 1 +a 2240 2241 1866 +a 2240 4106 1 +a 2241 2242 1865 +a 2241 4106 1 +a 2242 2243 1864 +a 2242 4106 1 +a 2243 2244 1863 +a 2243 4106 1 +a 2244 2245 1862 +a 2244 4106 1 +a 2245 2246 1861 +a 2245 4106 1 +a 2246 2247 1860 +a 2246 4106 1 +a 2247 2248 1859 +a 2247 4106 1 +a 2248 2249 1858 +a 2248 4106 1 +a 2249 2250 1857 +a 2249 4106 1 +a 2250 2251 1856 +a 2250 4106 1 +a 2251 2252 1855 +a 2251 4106 1 +a 2252 2253 1854 +a 2252 4106 1 +a 2253 2254 1853 +a 2253 4106 1 +a 2254 2255 1852 +a 2254 4106 1 +a 2255 2256 1851 +a 2255 4106 1 +a 2256 2257 1850 +a 2256 4106 1 +a 2257 2258 1849 +a 2257 4106 1 +a 2258 2259 1848 +a 2258 4106 1 +a 2259 2260 1847 +a 2259 4106 1 +a 2260 2261 1846 +a 2260 4106 1 +a 2261 2262 1845 +a 2261 4106 1 +a 2262 2263 1844 +a 2262 4106 1 +a 2263 2264 1843 +a 2263 4106 1 +a 2264 2265 1842 +a 2264 4106 1 +a 2265 2266 1841 +a 2265 4106 1 +a 2266 2267 1840 +a 2266 4106 1 +a 2267 2268 1839 +a 2267 4106 1 +a 2268 2269 1838 +a 2268 4106 1 +a 2269 2270 1837 +a 2269 4106 1 +a 2270 2271 1836 +a 2270 4106 1 +a 2271 2272 1835 +a 2271 4106 1 +a 2272 2273 1834 +a 2272 4106 1 +a 2273 2274 1833 +a 2273 4106 1 +a 2274 2275 1832 +a 2274 4106 1 +a 2275 2276 1831 +a 2275 4106 1 +a 2276 2277 1830 +a 2276 4106 1 +a 2277 2278 1829 +a 2277 4106 1 +a 2278 2279 1828 +a 2278 4106 1 +a 2279 2280 1827 +a 2279 4106 1 +a 2280 2281 1826 +a 2280 4106 1 +a 2281 2282 1825 +a 2281 4106 1 +a 2282 2283 1824 +a 2282 4106 1 +a 2283 2284 1823 +a 2283 4106 1 +a 2284 2285 1822 +a 2284 4106 1 +a 2285 2286 1821 +a 2285 4106 1 +a 2286 2287 1820 +a 2286 4106 1 +a 2287 2288 1819 +a 2287 4106 1 +a 2288 2289 1818 +a 2288 4106 1 +a 2289 2290 1817 +a 2289 4106 1 +a 2290 2291 1816 +a 2290 4106 1 +a 2291 2292 1815 +a 2291 4106 1 +a 2292 2293 1814 +a 2292 4106 1 +a 2293 2294 1813 +a 2293 4106 1 +a 2294 2295 1812 +a 2294 4106 1 +a 2295 2296 1811 +a 2295 4106 1 +a 2296 2297 1810 +a 2296 4106 1 +a 2297 2298 1809 +a 2297 4106 1 +a 2298 2299 1808 +a 2298 4106 1 +a 2299 2300 1807 +a 2299 4106 1 +a 2300 2301 1806 +a 2300 4106 1 +a 2301 2302 1805 +a 2301 4106 1 +a 2302 2303 1804 +a 2302 4106 1 +a 2303 2304 1803 +a 2303 4106 1 +a 2304 2305 1802 +a 2304 4106 1 +a 2305 2306 1801 +a 2305 4106 1 +a 2306 2307 1800 +a 2306 4106 1 +a 2307 2308 1799 +a 2307 4106 1 +a 2308 2309 1798 +a 2308 4106 1 +a 2309 2310 1797 +a 2309 4106 1 +a 2310 2311 1796 +a 2310 4106 1 +a 2311 2312 1795 +a 2311 4106 1 +a 2312 2313 1794 +a 2312 4106 1 +a 2313 2314 1793 +a 2313 4106 1 +a 2314 2315 1792 +a 2314 4106 1 +a 2315 2316 1791 +a 2315 4106 1 +a 2316 2317 1790 +a 2316 4106 1 +a 2317 2318 1789 +a 2317 4106 1 +a 2318 2319 1788 +a 2318 4106 1 +a 2319 2320 1787 +a 2319 4106 1 +a 2320 2321 1786 +a 2320 4106 1 +a 2321 2322 1785 +a 2321 4106 1 +a 2322 2323 1784 +a 2322 4106 1 +a 2323 2324 1783 +a 2323 4106 1 +a 2324 2325 1782 +a 2324 4106 1 +a 2325 2326 1781 +a 2325 4106 1 +a 2326 2327 1780 +a 2326 4106 1 +a 2327 2328 1779 +a 2327 4106 1 +a 2328 2329 1778 +a 2328 4106 1 +a 2329 2330 1777 +a 2329 4106 1 +a 2330 2331 1776 +a 2330 4106 1 +a 2331 2332 1775 +a 2331 4106 1 +a 2332 2333 1774 +a 2332 4106 1 +a 2333 2334 1773 +a 2333 4106 1 +a 2334 2335 1772 +a 2334 4106 1 +a 2335 2336 1771 +a 2335 4106 1 +a 2336 2337 1770 +a 2336 4106 1 +a 2337 2338 1769 +a 2337 4106 1 +a 2338 2339 1768 +a 2338 4106 1 +a 2339 2340 1767 +a 2339 4106 1 +a 2340 2341 1766 +a 2340 4106 1 +a 2341 2342 1765 +a 2341 4106 1 +a 2342 2343 1764 +a 2342 4106 1 +a 2343 2344 1763 +a 2343 4106 1 +a 2344 2345 1762 +a 2344 4106 1 +a 2345 2346 1761 +a 2345 4106 1 +a 2346 2347 1760 +a 2346 4106 1 +a 2347 2348 1759 +a 2347 4106 1 +a 2348 2349 1758 +a 2348 4106 1 +a 2349 2350 1757 +a 2349 4106 1 +a 2350 2351 1756 +a 2350 4106 1 +a 2351 2352 1755 +a 2351 4106 1 +a 2352 2353 1754 +a 2352 4106 1 +a 2353 2354 1753 +a 2353 4106 1 +a 2354 2355 1752 +a 2354 4106 1 +a 2355 2356 1751 +a 2355 4106 1 +a 2356 2357 1750 +a 2356 4106 1 +a 2357 2358 1749 +a 2357 4106 1 +a 2358 2359 1748 +a 2358 4106 1 +a 2359 2360 1747 +a 2359 4106 1 +a 2360 2361 1746 +a 2360 4106 1 +a 2361 2362 1745 +a 2361 4106 1 +a 2362 2363 1744 +a 2362 4106 1 +a 2363 2364 1743 +a 2363 4106 1 +a 2364 2365 1742 +a 2364 4106 1 +a 2365 2366 1741 +a 2365 4106 1 +a 2366 2367 1740 +a 2366 4106 1 +a 2367 2368 1739 +a 2367 4106 1 +a 2368 2369 1738 +a 2368 4106 1 +a 2369 2370 1737 +a 2369 4106 1 +a 2370 2371 1736 +a 2370 4106 1 +a 2371 2372 1735 +a 2371 4106 1 +a 2372 2373 1734 +a 2372 4106 1 +a 2373 2374 1733 +a 2373 4106 1 +a 2374 2375 1732 +a 2374 4106 1 +a 2375 2376 1731 +a 2375 4106 1 +a 2376 2377 1730 +a 2376 4106 1 +a 2377 2378 1729 +a 2377 4106 1 +a 2378 2379 1728 +a 2378 4106 1 +a 2379 2380 1727 +a 2379 4106 1 +a 2380 2381 1726 +a 2380 4106 1 +a 2381 2382 1725 +a 2381 4106 1 +a 2382 2383 1724 +a 2382 4106 1 +a 2383 2384 1723 +a 2383 4106 1 +a 2384 2385 1722 +a 2384 4106 1 +a 2385 2386 1721 +a 2385 4106 1 +a 2386 2387 1720 +a 2386 4106 1 +a 2387 2388 1719 +a 2387 4106 1 +a 2388 2389 1718 +a 2388 4106 1 +a 2389 2390 1717 +a 2389 4106 1 +a 2390 2391 1716 +a 2390 4106 1 +a 2391 2392 1715 +a 2391 4106 1 +a 2392 2393 1714 +a 2392 4106 1 +a 2393 2394 1713 +a 2393 4106 1 +a 2394 2395 1712 +a 2394 4106 1 +a 2395 2396 1711 +a 2395 4106 1 +a 2396 2397 1710 +a 2396 4106 1 +a 2397 2398 1709 +a 2397 4106 1 +a 2398 2399 1708 +a 2398 4106 1 +a 2399 2400 1707 +a 2399 4106 1 +a 2400 2401 1706 +a 2400 4106 1 +a 2401 2402 1705 +a 2401 4106 1 +a 2402 2403 1704 +a 2402 4106 1 +a 2403 2404 1703 +a 2403 4106 1 +a 2404 2405 1702 +a 2404 4106 1 +a 2405 2406 1701 +a 2405 4106 1 +a 2406 2407 1700 +a 2406 4106 1 +a 2407 2408 1699 +a 2407 4106 1 +a 2408 2409 1698 +a 2408 4106 1 +a 2409 2410 1697 +a 2409 4106 1 +a 2410 2411 1696 +a 2410 4106 1 +a 2411 2412 1695 +a 2411 4106 1 +a 2412 2413 1694 +a 2412 4106 1 +a 2413 2414 1693 +a 2413 4106 1 +a 2414 2415 1692 +a 2414 4106 1 +a 2415 2416 1691 +a 2415 4106 1 +a 2416 2417 1690 +a 2416 4106 1 +a 2417 2418 1689 +a 2417 4106 1 +a 2418 2419 1688 +a 2418 4106 1 +a 2419 2420 1687 +a 2419 4106 1 +a 2420 2421 1686 +a 2420 4106 1 +a 2421 2422 1685 +a 2421 4106 1 +a 2422 2423 1684 +a 2422 4106 1 +a 2423 2424 1683 +a 2423 4106 1 +a 2424 2425 1682 +a 2424 4106 1 +a 2425 2426 1681 +a 2425 4106 1 +a 2426 2427 1680 +a 2426 4106 1 +a 2427 2428 1679 +a 2427 4106 1 +a 2428 2429 1678 +a 2428 4106 1 +a 2429 2430 1677 +a 2429 4106 1 +a 2430 2431 1676 +a 2430 4106 1 +a 2431 2432 1675 +a 2431 4106 1 +a 2432 2433 1674 +a 2432 4106 1 +a 2433 2434 1673 +a 2433 4106 1 +a 2434 2435 1672 +a 2434 4106 1 +a 2435 2436 1671 +a 2435 4106 1 +a 2436 2437 1670 +a 2436 4106 1 +a 2437 2438 1669 +a 2437 4106 1 +a 2438 2439 1668 +a 2438 4106 1 +a 2439 2440 1667 +a 2439 4106 1 +a 2440 2441 1666 +a 2440 4106 1 +a 2441 2442 1665 +a 2441 4106 1 +a 2442 2443 1664 +a 2442 4106 1 +a 2443 2444 1663 +a 2443 4106 1 +a 2444 2445 1662 +a 2444 4106 1 +a 2445 2446 1661 +a 2445 4106 1 +a 2446 2447 1660 +a 2446 4106 1 +a 2447 2448 1659 +a 2447 4106 1 +a 2448 2449 1658 +a 2448 4106 1 +a 2449 2450 1657 +a 2449 4106 1 +a 2450 2451 1656 +a 2450 4106 1 +a 2451 2452 1655 +a 2451 4106 1 +a 2452 2453 1654 +a 2452 4106 1 +a 2453 2454 1653 +a 2453 4106 1 +a 2454 2455 1652 +a 2454 4106 1 +a 2455 2456 1651 +a 2455 4106 1 +a 2456 2457 1650 +a 2456 4106 1 +a 2457 2458 1649 +a 2457 4106 1 +a 2458 2459 1648 +a 2458 4106 1 +a 2459 2460 1647 +a 2459 4106 1 +a 2460 2461 1646 +a 2460 4106 1 +a 2461 2462 1645 +a 2461 4106 1 +a 2462 2463 1644 +a 2462 4106 1 +a 2463 2464 1643 +a 2463 4106 1 +a 2464 2465 1642 +a 2464 4106 1 +a 2465 2466 1641 +a 2465 4106 1 +a 2466 2467 1640 +a 2466 4106 1 +a 2467 2468 1639 +a 2467 4106 1 +a 2468 2469 1638 +a 2468 4106 1 +a 2469 2470 1637 +a 2469 4106 1 +a 2470 2471 1636 +a 2470 4106 1 +a 2471 2472 1635 +a 2471 4106 1 +a 2472 2473 1634 +a 2472 4106 1 +a 2473 2474 1633 +a 2473 4106 1 +a 2474 2475 1632 +a 2474 4106 1 +a 2475 2476 1631 +a 2475 4106 1 +a 2476 2477 1630 +a 2476 4106 1 +a 2477 2478 1629 +a 2477 4106 1 +a 2478 2479 1628 +a 2478 4106 1 +a 2479 2480 1627 +a 2479 4106 1 +a 2480 2481 1626 +a 2480 4106 1 +a 2481 2482 1625 +a 2481 4106 1 +a 2482 2483 1624 +a 2482 4106 1 +a 2483 2484 1623 +a 2483 4106 1 +a 2484 2485 1622 +a 2484 4106 1 +a 2485 2486 1621 +a 2485 4106 1 +a 2486 2487 1620 +a 2486 4106 1 +a 2487 2488 1619 +a 2487 4106 1 +a 2488 2489 1618 +a 2488 4106 1 +a 2489 2490 1617 +a 2489 4106 1 +a 2490 2491 1616 +a 2490 4106 1 +a 2491 2492 1615 +a 2491 4106 1 +a 2492 2493 1614 +a 2492 4106 1 +a 2493 2494 1613 +a 2493 4106 1 +a 2494 2495 1612 +a 2494 4106 1 +a 2495 2496 1611 +a 2495 4106 1 +a 2496 2497 1610 +a 2496 4106 1 +a 2497 2498 1609 +a 2497 4106 1 +a 2498 2499 1608 +a 2498 4106 1 +a 2499 2500 1607 +a 2499 4106 1 +a 2500 2501 1606 +a 2500 4106 1 +a 2501 2502 1605 +a 2501 4106 1 +a 2502 2503 1604 +a 2502 4106 1 +a 2503 2504 1603 +a 2503 4106 1 +a 2504 2505 1602 +a 2504 4106 1 +a 2505 2506 1601 +a 2505 4106 1 +a 2506 2507 1600 +a 2506 4106 1 +a 2507 2508 1599 +a 2507 4106 1 +a 2508 2509 1598 +a 2508 4106 1 +a 2509 2510 1597 +a 2509 4106 1 +a 2510 2511 1596 +a 2510 4106 1 +a 2511 2512 1595 +a 2511 4106 1 +a 2512 2513 1594 +a 2512 4106 1 +a 2513 2514 1593 +a 2513 4106 1 +a 2514 2515 1592 +a 2514 4106 1 +a 2515 2516 1591 +a 2515 4106 1 +a 2516 2517 1590 +a 2516 4106 1 +a 2517 2518 1589 +a 2517 4106 1 +a 2518 2519 1588 +a 2518 4106 1 +a 2519 2520 1587 +a 2519 4106 1 +a 2520 2521 1586 +a 2520 4106 1 +a 2521 2522 1585 +a 2521 4106 1 +a 2522 2523 1584 +a 2522 4106 1 +a 2523 2524 1583 +a 2523 4106 1 +a 2524 2525 1582 +a 2524 4106 1 +a 2525 2526 1581 +a 2525 4106 1 +a 2526 2527 1580 +a 2526 4106 1 +a 2527 2528 1579 +a 2527 4106 1 +a 2528 2529 1578 +a 2528 4106 1 +a 2529 2530 1577 +a 2529 4106 1 +a 2530 2531 1576 +a 2530 4106 1 +a 2531 2532 1575 +a 2531 4106 1 +a 2532 2533 1574 +a 2532 4106 1 +a 2533 2534 1573 +a 2533 4106 1 +a 2534 2535 1572 +a 2534 4106 1 +a 2535 2536 1571 +a 2535 4106 1 +a 2536 2537 1570 +a 2536 4106 1 +a 2537 2538 1569 +a 2537 4106 1 +a 2538 2539 1568 +a 2538 4106 1 +a 2539 2540 1567 +a 2539 4106 1 +a 2540 2541 1566 +a 2540 4106 1 +a 2541 2542 1565 +a 2541 4106 1 +a 2542 2543 1564 +a 2542 4106 1 +a 2543 2544 1563 +a 2543 4106 1 +a 2544 2545 1562 +a 2544 4106 1 +a 2545 2546 1561 +a 2545 4106 1 +a 2546 2547 1560 +a 2546 4106 1 +a 2547 2548 1559 +a 2547 4106 1 +a 2548 2549 1558 +a 2548 4106 1 +a 2549 2550 1557 +a 2549 4106 1 +a 2550 2551 1556 +a 2550 4106 1 +a 2551 2552 1555 +a 2551 4106 1 +a 2552 2553 1554 +a 2552 4106 1 +a 2553 2554 1553 +a 2553 4106 1 +a 2554 2555 1552 +a 2554 4106 1 +a 2555 2556 1551 +a 2555 4106 1 +a 2556 2557 1550 +a 2556 4106 1 +a 2557 2558 1549 +a 2557 4106 1 +a 2558 2559 1548 +a 2558 4106 1 +a 2559 2560 1547 +a 2559 4106 1 +a 2560 2561 1546 +a 2560 4106 1 +a 2561 2562 1545 +a 2561 4106 1 +a 2562 2563 1544 +a 2562 4106 1 +a 2563 2564 1543 +a 2563 4106 1 +a 2564 2565 1542 +a 2564 4106 1 +a 2565 2566 1541 +a 2565 4106 1 +a 2566 2567 1540 +a 2566 4106 1 +a 2567 2568 1539 +a 2567 4106 1 +a 2568 2569 1538 +a 2568 4106 1 +a 2569 2570 1537 +a 2569 4106 1 +a 2570 2571 1536 +a 2570 4106 1 +a 2571 2572 1535 +a 2571 4106 1 +a 2572 2573 1534 +a 2572 4106 1 +a 2573 2574 1533 +a 2573 4106 1 +a 2574 2575 1532 +a 2574 4106 1 +a 2575 2576 1531 +a 2575 4106 1 +a 2576 2577 1530 +a 2576 4106 1 +a 2577 2578 1529 +a 2577 4106 1 +a 2578 2579 1528 +a 2578 4106 1 +a 2579 2580 1527 +a 2579 4106 1 +a 2580 2581 1526 +a 2580 4106 1 +a 2581 2582 1525 +a 2581 4106 1 +a 2582 2583 1524 +a 2582 4106 1 +a 2583 2584 1523 +a 2583 4106 1 +a 2584 2585 1522 +a 2584 4106 1 +a 2585 2586 1521 +a 2585 4106 1 +a 2586 2587 1520 +a 2586 4106 1 +a 2587 2588 1519 +a 2587 4106 1 +a 2588 2589 1518 +a 2588 4106 1 +a 2589 2590 1517 +a 2589 4106 1 +a 2590 2591 1516 +a 2590 4106 1 +a 2591 2592 1515 +a 2591 4106 1 +a 2592 2593 1514 +a 2592 4106 1 +a 2593 2594 1513 +a 2593 4106 1 +a 2594 2595 1512 +a 2594 4106 1 +a 2595 2596 1511 +a 2595 4106 1 +a 2596 2597 1510 +a 2596 4106 1 +a 2597 2598 1509 +a 2597 4106 1 +a 2598 2599 1508 +a 2598 4106 1 +a 2599 2600 1507 +a 2599 4106 1 +a 2600 2601 1506 +a 2600 4106 1 +a 2601 2602 1505 +a 2601 4106 1 +a 2602 2603 1504 +a 2602 4106 1 +a 2603 2604 1503 +a 2603 4106 1 +a 2604 2605 1502 +a 2604 4106 1 +a 2605 2606 1501 +a 2605 4106 1 +a 2606 2607 1500 +a 2606 4106 1 +a 2607 2608 1499 +a 2607 4106 1 +a 2608 2609 1498 +a 2608 4106 1 +a 2609 2610 1497 +a 2609 4106 1 +a 2610 2611 1496 +a 2610 4106 1 +a 2611 2612 1495 +a 2611 4106 1 +a 2612 2613 1494 +a 2612 4106 1 +a 2613 2614 1493 +a 2613 4106 1 +a 2614 2615 1492 +a 2614 4106 1 +a 2615 2616 1491 +a 2615 4106 1 +a 2616 2617 1490 +a 2616 4106 1 +a 2617 2618 1489 +a 2617 4106 1 +a 2618 2619 1488 +a 2618 4106 1 +a 2619 2620 1487 +a 2619 4106 1 +a 2620 2621 1486 +a 2620 4106 1 +a 2621 2622 1485 +a 2621 4106 1 +a 2622 2623 1484 +a 2622 4106 1 +a 2623 2624 1483 +a 2623 4106 1 +a 2624 2625 1482 +a 2624 4106 1 +a 2625 2626 1481 +a 2625 4106 1 +a 2626 2627 1480 +a 2626 4106 1 +a 2627 2628 1479 +a 2627 4106 1 +a 2628 2629 1478 +a 2628 4106 1 +a 2629 2630 1477 +a 2629 4106 1 +a 2630 2631 1476 +a 2630 4106 1 +a 2631 2632 1475 +a 2631 4106 1 +a 2632 2633 1474 +a 2632 4106 1 +a 2633 2634 1473 +a 2633 4106 1 +a 2634 2635 1472 +a 2634 4106 1 +a 2635 2636 1471 +a 2635 4106 1 +a 2636 2637 1470 +a 2636 4106 1 +a 2637 2638 1469 +a 2637 4106 1 +a 2638 2639 1468 +a 2638 4106 1 +a 2639 2640 1467 +a 2639 4106 1 +a 2640 2641 1466 +a 2640 4106 1 +a 2641 2642 1465 +a 2641 4106 1 +a 2642 2643 1464 +a 2642 4106 1 +a 2643 2644 1463 +a 2643 4106 1 +a 2644 2645 1462 +a 2644 4106 1 +a 2645 2646 1461 +a 2645 4106 1 +a 2646 2647 1460 +a 2646 4106 1 +a 2647 2648 1459 +a 2647 4106 1 +a 2648 2649 1458 +a 2648 4106 1 +a 2649 2650 1457 +a 2649 4106 1 +a 2650 2651 1456 +a 2650 4106 1 +a 2651 2652 1455 +a 2651 4106 1 +a 2652 2653 1454 +a 2652 4106 1 +a 2653 2654 1453 +a 2653 4106 1 +a 2654 2655 1452 +a 2654 4106 1 +a 2655 2656 1451 +a 2655 4106 1 +a 2656 2657 1450 +a 2656 4106 1 +a 2657 2658 1449 +a 2657 4106 1 +a 2658 2659 1448 +a 2658 4106 1 +a 2659 2660 1447 +a 2659 4106 1 +a 2660 2661 1446 +a 2660 4106 1 +a 2661 2662 1445 +a 2661 4106 1 +a 2662 2663 1444 +a 2662 4106 1 +a 2663 2664 1443 +a 2663 4106 1 +a 2664 2665 1442 +a 2664 4106 1 +a 2665 2666 1441 +a 2665 4106 1 +a 2666 2667 1440 +a 2666 4106 1 +a 2667 2668 1439 +a 2667 4106 1 +a 2668 2669 1438 +a 2668 4106 1 +a 2669 2670 1437 +a 2669 4106 1 +a 2670 2671 1436 +a 2670 4106 1 +a 2671 2672 1435 +a 2671 4106 1 +a 2672 2673 1434 +a 2672 4106 1 +a 2673 2674 1433 +a 2673 4106 1 +a 2674 2675 1432 +a 2674 4106 1 +a 2675 2676 1431 +a 2675 4106 1 +a 2676 2677 1430 +a 2676 4106 1 +a 2677 2678 1429 +a 2677 4106 1 +a 2678 2679 1428 +a 2678 4106 1 +a 2679 2680 1427 +a 2679 4106 1 +a 2680 2681 1426 +a 2680 4106 1 +a 2681 2682 1425 +a 2681 4106 1 +a 2682 2683 1424 +a 2682 4106 1 +a 2683 2684 1423 +a 2683 4106 1 +a 2684 2685 1422 +a 2684 4106 1 +a 2685 2686 1421 +a 2685 4106 1 +a 2686 2687 1420 +a 2686 4106 1 +a 2687 2688 1419 +a 2687 4106 1 +a 2688 2689 1418 +a 2688 4106 1 +a 2689 2690 1417 +a 2689 4106 1 +a 2690 2691 1416 +a 2690 4106 1 +a 2691 2692 1415 +a 2691 4106 1 +a 2692 2693 1414 +a 2692 4106 1 +a 2693 2694 1413 +a 2693 4106 1 +a 2694 2695 1412 +a 2694 4106 1 +a 2695 2696 1411 +a 2695 4106 1 +a 2696 2697 1410 +a 2696 4106 1 +a 2697 2698 1409 +a 2697 4106 1 +a 2698 2699 1408 +a 2698 4106 1 +a 2699 2700 1407 +a 2699 4106 1 +a 2700 2701 1406 +a 2700 4106 1 +a 2701 2702 1405 +a 2701 4106 1 +a 2702 2703 1404 +a 2702 4106 1 +a 2703 2704 1403 +a 2703 4106 1 +a 2704 2705 1402 +a 2704 4106 1 +a 2705 2706 1401 +a 2705 4106 1 +a 2706 2707 1400 +a 2706 4106 1 +a 2707 2708 1399 +a 2707 4106 1 +a 2708 2709 1398 +a 2708 4106 1 +a 2709 2710 1397 +a 2709 4106 1 +a 2710 2711 1396 +a 2710 4106 1 +a 2711 2712 1395 +a 2711 4106 1 +a 2712 2713 1394 +a 2712 4106 1 +a 2713 2714 1393 +a 2713 4106 1 +a 2714 2715 1392 +a 2714 4106 1 +a 2715 2716 1391 +a 2715 4106 1 +a 2716 2717 1390 +a 2716 4106 1 +a 2717 2718 1389 +a 2717 4106 1 +a 2718 2719 1388 +a 2718 4106 1 +a 2719 2720 1387 +a 2719 4106 1 +a 2720 2721 1386 +a 2720 4106 1 +a 2721 2722 1385 +a 2721 4106 1 +a 2722 2723 1384 +a 2722 4106 1 +a 2723 2724 1383 +a 2723 4106 1 +a 2724 2725 1382 +a 2724 4106 1 +a 2725 2726 1381 +a 2725 4106 1 +a 2726 2727 1380 +a 2726 4106 1 +a 2727 2728 1379 +a 2727 4106 1 +a 2728 2729 1378 +a 2728 4106 1 +a 2729 2730 1377 +a 2729 4106 1 +a 2730 2731 1376 +a 2730 4106 1 +a 2731 2732 1375 +a 2731 4106 1 +a 2732 2733 1374 +a 2732 4106 1 +a 2733 2734 1373 +a 2733 4106 1 +a 2734 2735 1372 +a 2734 4106 1 +a 2735 2736 1371 +a 2735 4106 1 +a 2736 2737 1370 +a 2736 4106 1 +a 2737 2738 1369 +a 2737 4106 1 +a 2738 2739 1368 +a 2738 4106 1 +a 2739 2740 1367 +a 2739 4106 1 +a 2740 2741 1366 +a 2740 4106 1 +a 2741 2742 1365 +a 2741 4106 1 +a 2742 2743 1364 +a 2742 4106 1 +a 2743 2744 1363 +a 2743 4106 1 +a 2744 2745 1362 +a 2744 4106 1 +a 2745 2746 1361 +a 2745 4106 1 +a 2746 2747 1360 +a 2746 4106 1 +a 2747 2748 1359 +a 2747 4106 1 +a 2748 2749 1358 +a 2748 4106 1 +a 2749 2750 1357 +a 2749 4106 1 +a 2750 2751 1356 +a 2750 4106 1 +a 2751 2752 1355 +a 2751 4106 1 +a 2752 2753 1354 +a 2752 4106 1 +a 2753 2754 1353 +a 2753 4106 1 +a 2754 2755 1352 +a 2754 4106 1 +a 2755 2756 1351 +a 2755 4106 1 +a 2756 2757 1350 +a 2756 4106 1 +a 2757 2758 1349 +a 2757 4106 1 +a 2758 2759 1348 +a 2758 4106 1 +a 2759 2760 1347 +a 2759 4106 1 +a 2760 2761 1346 +a 2760 4106 1 +a 2761 2762 1345 +a 2761 4106 1 +a 2762 2763 1344 +a 2762 4106 1 +a 2763 2764 1343 +a 2763 4106 1 +a 2764 2765 1342 +a 2764 4106 1 +a 2765 2766 1341 +a 2765 4106 1 +a 2766 2767 1340 +a 2766 4106 1 +a 2767 2768 1339 +a 2767 4106 1 +a 2768 2769 1338 +a 2768 4106 1 +a 2769 2770 1337 +a 2769 4106 1 +a 2770 2771 1336 +a 2770 4106 1 +a 2771 2772 1335 +a 2771 4106 1 +a 2772 2773 1334 +a 2772 4106 1 +a 2773 2774 1333 +a 2773 4106 1 +a 2774 2775 1332 +a 2774 4106 1 +a 2775 2776 1331 +a 2775 4106 1 +a 2776 2777 1330 +a 2776 4106 1 +a 2777 2778 1329 +a 2777 4106 1 +a 2778 2779 1328 +a 2778 4106 1 +a 2779 2780 1327 +a 2779 4106 1 +a 2780 2781 1326 +a 2780 4106 1 +a 2781 2782 1325 +a 2781 4106 1 +a 2782 2783 1324 +a 2782 4106 1 +a 2783 2784 1323 +a 2783 4106 1 +a 2784 2785 1322 +a 2784 4106 1 +a 2785 2786 1321 +a 2785 4106 1 +a 2786 2787 1320 +a 2786 4106 1 +a 2787 2788 1319 +a 2787 4106 1 +a 2788 2789 1318 +a 2788 4106 1 +a 2789 2790 1317 +a 2789 4106 1 +a 2790 2791 1316 +a 2790 4106 1 +a 2791 2792 1315 +a 2791 4106 1 +a 2792 2793 1314 +a 2792 4106 1 +a 2793 2794 1313 +a 2793 4106 1 +a 2794 2795 1312 +a 2794 4106 1 +a 2795 2796 1311 +a 2795 4106 1 +a 2796 2797 1310 +a 2796 4106 1 +a 2797 2798 1309 +a 2797 4106 1 +a 2798 2799 1308 +a 2798 4106 1 +a 2799 2800 1307 +a 2799 4106 1 +a 2800 2801 1306 +a 2800 4106 1 +a 2801 2802 1305 +a 2801 4106 1 +a 2802 2803 1304 +a 2802 4106 1 +a 2803 2804 1303 +a 2803 4106 1 +a 2804 2805 1302 +a 2804 4106 1 +a 2805 2806 1301 +a 2805 4106 1 +a 2806 2807 1300 +a 2806 4106 1 +a 2807 2808 1299 +a 2807 4106 1 +a 2808 2809 1298 +a 2808 4106 1 +a 2809 2810 1297 +a 2809 4106 1 +a 2810 2811 1296 +a 2810 4106 1 +a 2811 2812 1295 +a 2811 4106 1 +a 2812 2813 1294 +a 2812 4106 1 +a 2813 2814 1293 +a 2813 4106 1 +a 2814 2815 1292 +a 2814 4106 1 +a 2815 2816 1291 +a 2815 4106 1 +a 2816 2817 1290 +a 2816 4106 1 +a 2817 2818 1289 +a 2817 4106 1 +a 2818 2819 1288 +a 2818 4106 1 +a 2819 2820 1287 +a 2819 4106 1 +a 2820 2821 1286 +a 2820 4106 1 +a 2821 2822 1285 +a 2821 4106 1 +a 2822 2823 1284 +a 2822 4106 1 +a 2823 2824 1283 +a 2823 4106 1 +a 2824 2825 1282 +a 2824 4106 1 +a 2825 2826 1281 +a 2825 4106 1 +a 2826 2827 1280 +a 2826 4106 1 +a 2827 2828 1279 +a 2827 4106 1 +a 2828 2829 1278 +a 2828 4106 1 +a 2829 2830 1277 +a 2829 4106 1 +a 2830 2831 1276 +a 2830 4106 1 +a 2831 2832 1275 +a 2831 4106 1 +a 2832 2833 1274 +a 2832 4106 1 +a 2833 2834 1273 +a 2833 4106 1 +a 2834 2835 1272 +a 2834 4106 1 +a 2835 2836 1271 +a 2835 4106 1 +a 2836 2837 1270 +a 2836 4106 1 +a 2837 2838 1269 +a 2837 4106 1 +a 2838 2839 1268 +a 2838 4106 1 +a 2839 2840 1267 +a 2839 4106 1 +a 2840 2841 1266 +a 2840 4106 1 +a 2841 2842 1265 +a 2841 4106 1 +a 2842 2843 1264 +a 2842 4106 1 +a 2843 2844 1263 +a 2843 4106 1 +a 2844 2845 1262 +a 2844 4106 1 +a 2845 2846 1261 +a 2845 4106 1 +a 2846 2847 1260 +a 2846 4106 1 +a 2847 2848 1259 +a 2847 4106 1 +a 2848 2849 1258 +a 2848 4106 1 +a 2849 2850 1257 +a 2849 4106 1 +a 2850 2851 1256 +a 2850 4106 1 +a 2851 2852 1255 +a 2851 4106 1 +a 2852 2853 1254 +a 2852 4106 1 +a 2853 2854 1253 +a 2853 4106 1 +a 2854 2855 1252 +a 2854 4106 1 +a 2855 2856 1251 +a 2855 4106 1 +a 2856 2857 1250 +a 2856 4106 1 +a 2857 2858 1249 +a 2857 4106 1 +a 2858 2859 1248 +a 2858 4106 1 +a 2859 2860 1247 +a 2859 4106 1 +a 2860 2861 1246 +a 2860 4106 1 +a 2861 2862 1245 +a 2861 4106 1 +a 2862 2863 1244 +a 2862 4106 1 +a 2863 2864 1243 +a 2863 4106 1 +a 2864 2865 1242 +a 2864 4106 1 +a 2865 2866 1241 +a 2865 4106 1 +a 2866 2867 1240 +a 2866 4106 1 +a 2867 2868 1239 +a 2867 4106 1 +a 2868 2869 1238 +a 2868 4106 1 +a 2869 2870 1237 +a 2869 4106 1 +a 2870 2871 1236 +a 2870 4106 1 +a 2871 2872 1235 +a 2871 4106 1 +a 2872 2873 1234 +a 2872 4106 1 +a 2873 2874 1233 +a 2873 4106 1 +a 2874 2875 1232 +a 2874 4106 1 +a 2875 2876 1231 +a 2875 4106 1 +a 2876 2877 1230 +a 2876 4106 1 +a 2877 2878 1229 +a 2877 4106 1 +a 2878 2879 1228 +a 2878 4106 1 +a 2879 2880 1227 +a 2879 4106 1 +a 2880 2881 1226 +a 2880 4106 1 +a 2881 2882 1225 +a 2881 4106 1 +a 2882 2883 1224 +a 2882 4106 1 +a 2883 2884 1223 +a 2883 4106 1 +a 2884 2885 1222 +a 2884 4106 1 +a 2885 2886 1221 +a 2885 4106 1 +a 2886 2887 1220 +a 2886 4106 1 +a 2887 2888 1219 +a 2887 4106 1 +a 2888 2889 1218 +a 2888 4106 1 +a 2889 2890 1217 +a 2889 4106 1 +a 2890 2891 1216 +a 2890 4106 1 +a 2891 2892 1215 +a 2891 4106 1 +a 2892 2893 1214 +a 2892 4106 1 +a 2893 2894 1213 +a 2893 4106 1 +a 2894 2895 1212 +a 2894 4106 1 +a 2895 2896 1211 +a 2895 4106 1 +a 2896 2897 1210 +a 2896 4106 1 +a 2897 2898 1209 +a 2897 4106 1 +a 2898 2899 1208 +a 2898 4106 1 +a 2899 2900 1207 +a 2899 4106 1 +a 2900 2901 1206 +a 2900 4106 1 +a 2901 2902 1205 +a 2901 4106 1 +a 2902 2903 1204 +a 2902 4106 1 +a 2903 2904 1203 +a 2903 4106 1 +a 2904 2905 1202 +a 2904 4106 1 +a 2905 2906 1201 +a 2905 4106 1 +a 2906 2907 1200 +a 2906 4106 1 +a 2907 2908 1199 +a 2907 4106 1 +a 2908 2909 1198 +a 2908 4106 1 +a 2909 2910 1197 +a 2909 4106 1 +a 2910 2911 1196 +a 2910 4106 1 +a 2911 2912 1195 +a 2911 4106 1 +a 2912 2913 1194 +a 2912 4106 1 +a 2913 2914 1193 +a 2913 4106 1 +a 2914 2915 1192 +a 2914 4106 1 +a 2915 2916 1191 +a 2915 4106 1 +a 2916 2917 1190 +a 2916 4106 1 +a 2917 2918 1189 +a 2917 4106 1 +a 2918 2919 1188 +a 2918 4106 1 +a 2919 2920 1187 +a 2919 4106 1 +a 2920 2921 1186 +a 2920 4106 1 +a 2921 2922 1185 +a 2921 4106 1 +a 2922 2923 1184 +a 2922 4106 1 +a 2923 2924 1183 +a 2923 4106 1 +a 2924 2925 1182 +a 2924 4106 1 +a 2925 2926 1181 +a 2925 4106 1 +a 2926 2927 1180 +a 2926 4106 1 +a 2927 2928 1179 +a 2927 4106 1 +a 2928 2929 1178 +a 2928 4106 1 +a 2929 2930 1177 +a 2929 4106 1 +a 2930 2931 1176 +a 2930 4106 1 +a 2931 2932 1175 +a 2931 4106 1 +a 2932 2933 1174 +a 2932 4106 1 +a 2933 2934 1173 +a 2933 4106 1 +a 2934 2935 1172 +a 2934 4106 1 +a 2935 2936 1171 +a 2935 4106 1 +a 2936 2937 1170 +a 2936 4106 1 +a 2937 2938 1169 +a 2937 4106 1 +a 2938 2939 1168 +a 2938 4106 1 +a 2939 2940 1167 +a 2939 4106 1 +a 2940 2941 1166 +a 2940 4106 1 +a 2941 2942 1165 +a 2941 4106 1 +a 2942 2943 1164 +a 2942 4106 1 +a 2943 2944 1163 +a 2943 4106 1 +a 2944 2945 1162 +a 2944 4106 1 +a 2945 2946 1161 +a 2945 4106 1 +a 2946 2947 1160 +a 2946 4106 1 +a 2947 2948 1159 +a 2947 4106 1 +a 2948 2949 1158 +a 2948 4106 1 +a 2949 2950 1157 +a 2949 4106 1 +a 2950 2951 1156 +a 2950 4106 1 +a 2951 2952 1155 +a 2951 4106 1 +a 2952 2953 1154 +a 2952 4106 1 +a 2953 2954 1153 +a 2953 4106 1 +a 2954 2955 1152 +a 2954 4106 1 +a 2955 2956 1151 +a 2955 4106 1 +a 2956 2957 1150 +a 2956 4106 1 +a 2957 2958 1149 +a 2957 4106 1 +a 2958 2959 1148 +a 2958 4106 1 +a 2959 2960 1147 +a 2959 4106 1 +a 2960 2961 1146 +a 2960 4106 1 +a 2961 2962 1145 +a 2961 4106 1 +a 2962 2963 1144 +a 2962 4106 1 +a 2963 2964 1143 +a 2963 4106 1 +a 2964 2965 1142 +a 2964 4106 1 +a 2965 2966 1141 +a 2965 4106 1 +a 2966 2967 1140 +a 2966 4106 1 +a 2967 2968 1139 +a 2967 4106 1 +a 2968 2969 1138 +a 2968 4106 1 +a 2969 2970 1137 +a 2969 4106 1 +a 2970 2971 1136 +a 2970 4106 1 +a 2971 2972 1135 +a 2971 4106 1 +a 2972 2973 1134 +a 2972 4106 1 +a 2973 2974 1133 +a 2973 4106 1 +a 2974 2975 1132 +a 2974 4106 1 +a 2975 2976 1131 +a 2975 4106 1 +a 2976 2977 1130 +a 2976 4106 1 +a 2977 2978 1129 +a 2977 4106 1 +a 2978 2979 1128 +a 2978 4106 1 +a 2979 2980 1127 +a 2979 4106 1 +a 2980 2981 1126 +a 2980 4106 1 +a 2981 2982 1125 +a 2981 4106 1 +a 2982 2983 1124 +a 2982 4106 1 +a 2983 2984 1123 +a 2983 4106 1 +a 2984 2985 1122 +a 2984 4106 1 +a 2985 2986 1121 +a 2985 4106 1 +a 2986 2987 1120 +a 2986 4106 1 +a 2987 2988 1119 +a 2987 4106 1 +a 2988 2989 1118 +a 2988 4106 1 +a 2989 2990 1117 +a 2989 4106 1 +a 2990 2991 1116 +a 2990 4106 1 +a 2991 2992 1115 +a 2991 4106 1 +a 2992 2993 1114 +a 2992 4106 1 +a 2993 2994 1113 +a 2993 4106 1 +a 2994 2995 1112 +a 2994 4106 1 +a 2995 2996 1111 +a 2995 4106 1 +a 2996 2997 1110 +a 2996 4106 1 +a 2997 2998 1109 +a 2997 4106 1 +a 2998 2999 1108 +a 2998 4106 1 +a 2999 3000 1107 +a 2999 4106 1 +a 3000 3001 1106 +a 3000 4106 1 +a 3001 3002 1105 +a 3001 4106 1 +a 3002 3003 1104 +a 3002 4106 1 +a 3003 3004 1103 +a 3003 4106 1 +a 3004 3005 1102 +a 3004 4106 1 +a 3005 3006 1101 +a 3005 4106 1 +a 3006 3007 1100 +a 3006 4106 1 +a 3007 3008 1099 +a 3007 4106 1 +a 3008 3009 1098 +a 3008 4106 1 +a 3009 3010 1097 +a 3009 4106 1 +a 3010 3011 1096 +a 3010 4106 1 +a 3011 3012 1095 +a 3011 4106 1 +a 3012 3013 1094 +a 3012 4106 1 +a 3013 3014 1093 +a 3013 4106 1 +a 3014 3015 1092 +a 3014 4106 1 +a 3015 3016 1091 +a 3015 4106 1 +a 3016 3017 1090 +a 3016 4106 1 +a 3017 3018 1089 +a 3017 4106 1 +a 3018 3019 1088 +a 3018 4106 1 +a 3019 3020 1087 +a 3019 4106 1 +a 3020 3021 1086 +a 3020 4106 1 +a 3021 3022 1085 +a 3021 4106 1 +a 3022 3023 1084 +a 3022 4106 1 +a 3023 3024 1083 +a 3023 4106 1 +a 3024 3025 1082 +a 3024 4106 1 +a 3025 3026 1081 +a 3025 4106 1 +a 3026 3027 1080 +a 3026 4106 1 +a 3027 3028 1079 +a 3027 4106 1 +a 3028 3029 1078 +a 3028 4106 1 +a 3029 3030 1077 +a 3029 4106 1 +a 3030 3031 1076 +a 3030 4106 1 +a 3031 3032 1075 +a 3031 4106 1 +a 3032 3033 1074 +a 3032 4106 1 +a 3033 3034 1073 +a 3033 4106 1 +a 3034 3035 1072 +a 3034 4106 1 +a 3035 3036 1071 +a 3035 4106 1 +a 3036 3037 1070 +a 3036 4106 1 +a 3037 3038 1069 +a 3037 4106 1 +a 3038 3039 1068 +a 3038 4106 1 +a 3039 3040 1067 +a 3039 4106 1 +a 3040 3041 1066 +a 3040 4106 1 +a 3041 3042 1065 +a 3041 4106 1 +a 3042 3043 1064 +a 3042 4106 1 +a 3043 3044 1063 +a 3043 4106 1 +a 3044 3045 1062 +a 3044 4106 1 +a 3045 3046 1061 +a 3045 4106 1 +a 3046 3047 1060 +a 3046 4106 1 +a 3047 3048 1059 +a 3047 4106 1 +a 3048 3049 1058 +a 3048 4106 1 +a 3049 3050 1057 +a 3049 4106 1 +a 3050 3051 1056 +a 3050 4106 1 +a 3051 3052 1055 +a 3051 4106 1 +a 3052 3053 1054 +a 3052 4106 1 +a 3053 3054 1053 +a 3053 4106 1 +a 3054 3055 1052 +a 3054 4106 1 +a 3055 3056 1051 +a 3055 4106 1 +a 3056 3057 1050 +a 3056 4106 1 +a 3057 3058 1049 +a 3057 4106 1 +a 3058 3059 1048 +a 3058 4106 1 +a 3059 3060 1047 +a 3059 4106 1 +a 3060 3061 1046 +a 3060 4106 1 +a 3061 3062 1045 +a 3061 4106 1 +a 3062 3063 1044 +a 3062 4106 1 +a 3063 3064 1043 +a 3063 4106 1 +a 3064 3065 1042 +a 3064 4106 1 +a 3065 3066 1041 +a 3065 4106 1 +a 3066 3067 1040 +a 3066 4106 1 +a 3067 3068 1039 +a 3067 4106 1 +a 3068 3069 1038 +a 3068 4106 1 +a 3069 3070 1037 +a 3069 4106 1 +a 3070 3071 1036 +a 3070 4106 1 +a 3071 3072 1035 +a 3071 4106 1 +a 3072 3073 1034 +a 3072 4106 1 +a 3073 3074 1033 +a 3073 4106 1 +a 3074 3075 1032 +a 3074 4106 1 +a 3075 3076 1031 +a 3075 4106 1 +a 3076 3077 1030 +a 3076 4106 1 +a 3077 3078 1029 +a 3077 4106 1 +a 3078 3079 1028 +a 3078 4106 1 +a 3079 3080 1027 +a 3079 4106 1 +a 3080 3081 1026 +a 3080 4106 1 +a 3081 3082 1025 +a 3081 4106 1 +a 3082 3083 1024 +a 3082 4106 1 +a 3083 3084 1023 +a 3083 4106 1 +a 3084 3085 1022 +a 3084 4106 1 +a 3085 3086 1021 +a 3085 4106 1 +a 3086 3087 1020 +a 3086 4106 1 +a 3087 3088 1019 +a 3087 4106 1 +a 3088 3089 1018 +a 3088 4106 1 +a 3089 3090 1017 +a 3089 4106 1 +a 3090 3091 1016 +a 3090 4106 1 +a 3091 3092 1015 +a 3091 4106 1 +a 3092 3093 1014 +a 3092 4106 1 +a 3093 3094 1013 +a 3093 4106 1 +a 3094 3095 1012 +a 3094 4106 1 +a 3095 3096 1011 +a 3095 4106 1 +a 3096 3097 1010 +a 3096 4106 1 +a 3097 3098 1009 +a 3097 4106 1 +a 3098 3099 1008 +a 3098 4106 1 +a 3099 3100 1007 +a 3099 4106 1 +a 3100 3101 1006 +a 3100 4106 1 +a 3101 3102 1005 +a 3101 4106 1 +a 3102 3103 1004 +a 3102 4106 1 +a 3103 3104 1003 +a 3103 4106 1 +a 3104 3105 1002 +a 3104 4106 1 +a 3105 3106 1001 +a 3105 4106 1 +a 3106 3107 1000 +a 3106 4106 1 +a 3107 3108 999 +a 3107 4106 1 +a 3108 3109 998 +a 3108 4106 1 +a 3109 3110 997 +a 3109 4106 1 +a 3110 3111 996 +a 3110 4106 1 +a 3111 3112 995 +a 3111 4106 1 +a 3112 3113 994 +a 3112 4106 1 +a 3113 3114 993 +a 3113 4106 1 +a 3114 3115 992 +a 3114 4106 1 +a 3115 3116 991 +a 3115 4106 1 +a 3116 3117 990 +a 3116 4106 1 +a 3117 3118 989 +a 3117 4106 1 +a 3118 3119 988 +a 3118 4106 1 +a 3119 3120 987 +a 3119 4106 1 +a 3120 3121 986 +a 3120 4106 1 +a 3121 3122 985 +a 3121 4106 1 +a 3122 3123 984 +a 3122 4106 1 +a 3123 3124 983 +a 3123 4106 1 +a 3124 3125 982 +a 3124 4106 1 +a 3125 3126 981 +a 3125 4106 1 +a 3126 3127 980 +a 3126 4106 1 +a 3127 3128 979 +a 3127 4106 1 +a 3128 3129 978 +a 3128 4106 1 +a 3129 3130 977 +a 3129 4106 1 +a 3130 3131 976 +a 3130 4106 1 +a 3131 3132 975 +a 3131 4106 1 +a 3132 3133 974 +a 3132 4106 1 +a 3133 3134 973 +a 3133 4106 1 +a 3134 3135 972 +a 3134 4106 1 +a 3135 3136 971 +a 3135 4106 1 +a 3136 3137 970 +a 3136 4106 1 +a 3137 3138 969 +a 3137 4106 1 +a 3138 3139 968 +a 3138 4106 1 +a 3139 3140 967 +a 3139 4106 1 +a 3140 3141 966 +a 3140 4106 1 +a 3141 3142 965 +a 3141 4106 1 +a 3142 3143 964 +a 3142 4106 1 +a 3143 3144 963 +a 3143 4106 1 +a 3144 3145 962 +a 3144 4106 1 +a 3145 3146 961 +a 3145 4106 1 +a 3146 3147 960 +a 3146 4106 1 +a 3147 3148 959 +a 3147 4106 1 +a 3148 3149 958 +a 3148 4106 1 +a 3149 3150 957 +a 3149 4106 1 +a 3150 3151 956 +a 3150 4106 1 +a 3151 3152 955 +a 3151 4106 1 +a 3152 3153 954 +a 3152 4106 1 +a 3153 3154 953 +a 3153 4106 1 +a 3154 3155 952 +a 3154 4106 1 +a 3155 3156 951 +a 3155 4106 1 +a 3156 3157 950 +a 3156 4106 1 +a 3157 3158 949 +a 3157 4106 1 +a 3158 3159 948 +a 3158 4106 1 +a 3159 3160 947 +a 3159 4106 1 +a 3160 3161 946 +a 3160 4106 1 +a 3161 3162 945 +a 3161 4106 1 +a 3162 3163 944 +a 3162 4106 1 +a 3163 3164 943 +a 3163 4106 1 +a 3164 3165 942 +a 3164 4106 1 +a 3165 3166 941 +a 3165 4106 1 +a 3166 3167 940 +a 3166 4106 1 +a 3167 3168 939 +a 3167 4106 1 +a 3168 3169 938 +a 3168 4106 1 +a 3169 3170 937 +a 3169 4106 1 +a 3170 3171 936 +a 3170 4106 1 +a 3171 3172 935 +a 3171 4106 1 +a 3172 3173 934 +a 3172 4106 1 +a 3173 3174 933 +a 3173 4106 1 +a 3174 3175 932 +a 3174 4106 1 +a 3175 3176 931 +a 3175 4106 1 +a 3176 3177 930 +a 3176 4106 1 +a 3177 3178 929 +a 3177 4106 1 +a 3178 3179 928 +a 3178 4106 1 +a 3179 3180 927 +a 3179 4106 1 +a 3180 3181 926 +a 3180 4106 1 +a 3181 3182 925 +a 3181 4106 1 +a 3182 3183 924 +a 3182 4106 1 +a 3183 3184 923 +a 3183 4106 1 +a 3184 3185 922 +a 3184 4106 1 +a 3185 3186 921 +a 3185 4106 1 +a 3186 3187 920 +a 3186 4106 1 +a 3187 3188 919 +a 3187 4106 1 +a 3188 3189 918 +a 3188 4106 1 +a 3189 3190 917 +a 3189 4106 1 +a 3190 3191 916 +a 3190 4106 1 +a 3191 3192 915 +a 3191 4106 1 +a 3192 3193 914 +a 3192 4106 1 +a 3193 3194 913 +a 3193 4106 1 +a 3194 3195 912 +a 3194 4106 1 +a 3195 3196 911 +a 3195 4106 1 +a 3196 3197 910 +a 3196 4106 1 +a 3197 3198 909 +a 3197 4106 1 +a 3198 3199 908 +a 3198 4106 1 +a 3199 3200 907 +a 3199 4106 1 +a 3200 3201 906 +a 3200 4106 1 +a 3201 3202 905 +a 3201 4106 1 +a 3202 3203 904 +a 3202 4106 1 +a 3203 3204 903 +a 3203 4106 1 +a 3204 3205 902 +a 3204 4106 1 +a 3205 3206 901 +a 3205 4106 1 +a 3206 3207 900 +a 3206 4106 1 +a 3207 3208 899 +a 3207 4106 1 +a 3208 3209 898 +a 3208 4106 1 +a 3209 3210 897 +a 3209 4106 1 +a 3210 3211 896 +a 3210 4106 1 +a 3211 3212 895 +a 3211 4106 1 +a 3212 3213 894 +a 3212 4106 1 +a 3213 3214 893 +a 3213 4106 1 +a 3214 3215 892 +a 3214 4106 1 +a 3215 3216 891 +a 3215 4106 1 +a 3216 3217 890 +a 3216 4106 1 +a 3217 3218 889 +a 3217 4106 1 +a 3218 3219 888 +a 3218 4106 1 +a 3219 3220 887 +a 3219 4106 1 +a 3220 3221 886 +a 3220 4106 1 +a 3221 3222 885 +a 3221 4106 1 +a 3222 3223 884 +a 3222 4106 1 +a 3223 3224 883 +a 3223 4106 1 +a 3224 3225 882 +a 3224 4106 1 +a 3225 3226 881 +a 3225 4106 1 +a 3226 3227 880 +a 3226 4106 1 +a 3227 3228 879 +a 3227 4106 1 +a 3228 3229 878 +a 3228 4106 1 +a 3229 3230 877 +a 3229 4106 1 +a 3230 3231 876 +a 3230 4106 1 +a 3231 3232 875 +a 3231 4106 1 +a 3232 3233 874 +a 3232 4106 1 +a 3233 3234 873 +a 3233 4106 1 +a 3234 3235 872 +a 3234 4106 1 +a 3235 3236 871 +a 3235 4106 1 +a 3236 3237 870 +a 3236 4106 1 +a 3237 3238 869 +a 3237 4106 1 +a 3238 3239 868 +a 3238 4106 1 +a 3239 3240 867 +a 3239 4106 1 +a 3240 3241 866 +a 3240 4106 1 +a 3241 3242 865 +a 3241 4106 1 +a 3242 3243 864 +a 3242 4106 1 +a 3243 3244 863 +a 3243 4106 1 +a 3244 3245 862 +a 3244 4106 1 +a 3245 3246 861 +a 3245 4106 1 +a 3246 3247 860 +a 3246 4106 1 +a 3247 3248 859 +a 3247 4106 1 +a 3248 3249 858 +a 3248 4106 1 +a 3249 3250 857 +a 3249 4106 1 +a 3250 3251 856 +a 3250 4106 1 +a 3251 3252 855 +a 3251 4106 1 +a 3252 3253 854 +a 3252 4106 1 +a 3253 3254 853 +a 3253 4106 1 +a 3254 3255 852 +a 3254 4106 1 +a 3255 3256 851 +a 3255 4106 1 +a 3256 3257 850 +a 3256 4106 1 +a 3257 3258 849 +a 3257 4106 1 +a 3258 3259 848 +a 3258 4106 1 +a 3259 3260 847 +a 3259 4106 1 +a 3260 3261 846 +a 3260 4106 1 +a 3261 3262 845 +a 3261 4106 1 +a 3262 3263 844 +a 3262 4106 1 +a 3263 3264 843 +a 3263 4106 1 +a 3264 3265 842 +a 3264 4106 1 +a 3265 3266 841 +a 3265 4106 1 +a 3266 3267 840 +a 3266 4106 1 +a 3267 3268 839 +a 3267 4106 1 +a 3268 3269 838 +a 3268 4106 1 +a 3269 3270 837 +a 3269 4106 1 +a 3270 3271 836 +a 3270 4106 1 +a 3271 3272 835 +a 3271 4106 1 +a 3272 3273 834 +a 3272 4106 1 +a 3273 3274 833 +a 3273 4106 1 +a 3274 3275 832 +a 3274 4106 1 +a 3275 3276 831 +a 3275 4106 1 +a 3276 3277 830 +a 3276 4106 1 +a 3277 3278 829 +a 3277 4106 1 +a 3278 3279 828 +a 3278 4106 1 +a 3279 3280 827 +a 3279 4106 1 +a 3280 3281 826 +a 3280 4106 1 +a 3281 3282 825 +a 3281 4106 1 +a 3282 3283 824 +a 3282 4106 1 +a 3283 3284 823 +a 3283 4106 1 +a 3284 3285 822 +a 3284 4106 1 +a 3285 3286 821 +a 3285 4106 1 +a 3286 3287 820 +a 3286 4106 1 +a 3287 3288 819 +a 3287 4106 1 +a 3288 3289 818 +a 3288 4106 1 +a 3289 3290 817 +a 3289 4106 1 +a 3290 3291 816 +a 3290 4106 1 +a 3291 3292 815 +a 3291 4106 1 +a 3292 3293 814 +a 3292 4106 1 +a 3293 3294 813 +a 3293 4106 1 +a 3294 3295 812 +a 3294 4106 1 +a 3295 3296 811 +a 3295 4106 1 +a 3296 3297 810 +a 3296 4106 1 +a 3297 3298 809 +a 3297 4106 1 +a 3298 3299 808 +a 3298 4106 1 +a 3299 3300 807 +a 3299 4106 1 +a 3300 3301 806 +a 3300 4106 1 +a 3301 3302 805 +a 3301 4106 1 +a 3302 3303 804 +a 3302 4106 1 +a 3303 3304 803 +a 3303 4106 1 +a 3304 3305 802 +a 3304 4106 1 +a 3305 3306 801 +a 3305 4106 1 +a 3306 3307 800 +a 3306 4106 1 +a 3307 3308 799 +a 3307 4106 1 +a 3308 3309 798 +a 3308 4106 1 +a 3309 3310 797 +a 3309 4106 1 +a 3310 3311 796 +a 3310 4106 1 +a 3311 3312 795 +a 3311 4106 1 +a 3312 3313 794 +a 3312 4106 1 +a 3313 3314 793 +a 3313 4106 1 +a 3314 3315 792 +a 3314 4106 1 +a 3315 3316 791 +a 3315 4106 1 +a 3316 3317 790 +a 3316 4106 1 +a 3317 3318 789 +a 3317 4106 1 +a 3318 3319 788 +a 3318 4106 1 +a 3319 3320 787 +a 3319 4106 1 +a 3320 3321 786 +a 3320 4106 1 +a 3321 3322 785 +a 3321 4106 1 +a 3322 3323 784 +a 3322 4106 1 +a 3323 3324 783 +a 3323 4106 1 +a 3324 3325 782 +a 3324 4106 1 +a 3325 3326 781 +a 3325 4106 1 +a 3326 3327 780 +a 3326 4106 1 +a 3327 3328 779 +a 3327 4106 1 +a 3328 3329 778 +a 3328 4106 1 +a 3329 3330 777 +a 3329 4106 1 +a 3330 3331 776 +a 3330 4106 1 +a 3331 3332 775 +a 3331 4106 1 +a 3332 3333 774 +a 3332 4106 1 +a 3333 3334 773 +a 3333 4106 1 +a 3334 3335 772 +a 3334 4106 1 +a 3335 3336 771 +a 3335 4106 1 +a 3336 3337 770 +a 3336 4106 1 +a 3337 3338 769 +a 3337 4106 1 +a 3338 3339 768 +a 3338 4106 1 +a 3339 3340 767 +a 3339 4106 1 +a 3340 3341 766 +a 3340 4106 1 +a 3341 3342 765 +a 3341 4106 1 +a 3342 3343 764 +a 3342 4106 1 +a 3343 3344 763 +a 3343 4106 1 +a 3344 3345 762 +a 3344 4106 1 +a 3345 3346 761 +a 3345 4106 1 +a 3346 3347 760 +a 3346 4106 1 +a 3347 3348 759 +a 3347 4106 1 +a 3348 3349 758 +a 3348 4106 1 +a 3349 3350 757 +a 3349 4106 1 +a 3350 3351 756 +a 3350 4106 1 +a 3351 3352 755 +a 3351 4106 1 +a 3352 3353 754 +a 3352 4106 1 +a 3353 3354 753 +a 3353 4106 1 +a 3354 3355 752 +a 3354 4106 1 +a 3355 3356 751 +a 3355 4106 1 +a 3356 3357 750 +a 3356 4106 1 +a 3357 3358 749 +a 3357 4106 1 +a 3358 3359 748 +a 3358 4106 1 +a 3359 3360 747 +a 3359 4106 1 +a 3360 3361 746 +a 3360 4106 1 +a 3361 3362 745 +a 3361 4106 1 +a 3362 3363 744 +a 3362 4106 1 +a 3363 3364 743 +a 3363 4106 1 +a 3364 3365 742 +a 3364 4106 1 +a 3365 3366 741 +a 3365 4106 1 +a 3366 3367 740 +a 3366 4106 1 +a 3367 3368 739 +a 3367 4106 1 +a 3368 3369 738 +a 3368 4106 1 +a 3369 3370 737 +a 3369 4106 1 +a 3370 3371 736 +a 3370 4106 1 +a 3371 3372 735 +a 3371 4106 1 +a 3372 3373 734 +a 3372 4106 1 +a 3373 3374 733 +a 3373 4106 1 +a 3374 3375 732 +a 3374 4106 1 +a 3375 3376 731 +a 3375 4106 1 +a 3376 3377 730 +a 3376 4106 1 +a 3377 3378 729 +a 3377 4106 1 +a 3378 3379 728 +a 3378 4106 1 +a 3379 3380 727 +a 3379 4106 1 +a 3380 3381 726 +a 3380 4106 1 +a 3381 3382 725 +a 3381 4106 1 +a 3382 3383 724 +a 3382 4106 1 +a 3383 3384 723 +a 3383 4106 1 +a 3384 3385 722 +a 3384 4106 1 +a 3385 3386 721 +a 3385 4106 1 +a 3386 3387 720 +a 3386 4106 1 +a 3387 3388 719 +a 3387 4106 1 +a 3388 3389 718 +a 3388 4106 1 +a 3389 3390 717 +a 3389 4106 1 +a 3390 3391 716 +a 3390 4106 1 +a 3391 3392 715 +a 3391 4106 1 +a 3392 3393 714 +a 3392 4106 1 +a 3393 3394 713 +a 3393 4106 1 +a 3394 3395 712 +a 3394 4106 1 +a 3395 3396 711 +a 3395 4106 1 +a 3396 3397 710 +a 3396 4106 1 +a 3397 3398 709 +a 3397 4106 1 +a 3398 3399 708 +a 3398 4106 1 +a 3399 3400 707 +a 3399 4106 1 +a 3400 3401 706 +a 3400 4106 1 +a 3401 3402 705 +a 3401 4106 1 +a 3402 3403 704 +a 3402 4106 1 +a 3403 3404 703 +a 3403 4106 1 +a 3404 3405 702 +a 3404 4106 1 +a 3405 3406 701 +a 3405 4106 1 +a 3406 3407 700 +a 3406 4106 1 +a 3407 3408 699 +a 3407 4106 1 +a 3408 3409 698 +a 3408 4106 1 +a 3409 3410 697 +a 3409 4106 1 +a 3410 3411 696 +a 3410 4106 1 +a 3411 3412 695 +a 3411 4106 1 +a 3412 3413 694 +a 3412 4106 1 +a 3413 3414 693 +a 3413 4106 1 +a 3414 3415 692 +a 3414 4106 1 +a 3415 3416 691 +a 3415 4106 1 +a 3416 3417 690 +a 3416 4106 1 +a 3417 3418 689 +a 3417 4106 1 +a 3418 3419 688 +a 3418 4106 1 +a 3419 3420 687 +a 3419 4106 1 +a 3420 3421 686 +a 3420 4106 1 +a 3421 3422 685 +a 3421 4106 1 +a 3422 3423 684 +a 3422 4106 1 +a 3423 3424 683 +a 3423 4106 1 +a 3424 3425 682 +a 3424 4106 1 +a 3425 3426 681 +a 3425 4106 1 +a 3426 3427 680 +a 3426 4106 1 +a 3427 3428 679 +a 3427 4106 1 +a 3428 3429 678 +a 3428 4106 1 +a 3429 3430 677 +a 3429 4106 1 +a 3430 3431 676 +a 3430 4106 1 +a 3431 3432 675 +a 3431 4106 1 +a 3432 3433 674 +a 3432 4106 1 +a 3433 3434 673 +a 3433 4106 1 +a 3434 3435 672 +a 3434 4106 1 +a 3435 3436 671 +a 3435 4106 1 +a 3436 3437 670 +a 3436 4106 1 +a 3437 3438 669 +a 3437 4106 1 +a 3438 3439 668 +a 3438 4106 1 +a 3439 3440 667 +a 3439 4106 1 +a 3440 3441 666 +a 3440 4106 1 +a 3441 3442 665 +a 3441 4106 1 +a 3442 3443 664 +a 3442 4106 1 +a 3443 3444 663 +a 3443 4106 1 +a 3444 3445 662 +a 3444 4106 1 +a 3445 3446 661 +a 3445 4106 1 +a 3446 3447 660 +a 3446 4106 1 +a 3447 3448 659 +a 3447 4106 1 +a 3448 3449 658 +a 3448 4106 1 +a 3449 3450 657 +a 3449 4106 1 +a 3450 3451 656 +a 3450 4106 1 +a 3451 3452 655 +a 3451 4106 1 +a 3452 3453 654 +a 3452 4106 1 +a 3453 3454 653 +a 3453 4106 1 +a 3454 3455 652 +a 3454 4106 1 +a 3455 3456 651 +a 3455 4106 1 +a 3456 3457 650 +a 3456 4106 1 +a 3457 3458 649 +a 3457 4106 1 +a 3458 3459 648 +a 3458 4106 1 +a 3459 3460 647 +a 3459 4106 1 +a 3460 3461 646 +a 3460 4106 1 +a 3461 3462 645 +a 3461 4106 1 +a 3462 3463 644 +a 3462 4106 1 +a 3463 3464 643 +a 3463 4106 1 +a 3464 3465 642 +a 3464 4106 1 +a 3465 3466 641 +a 3465 4106 1 +a 3466 3467 640 +a 3466 4106 1 +a 3467 3468 639 +a 3467 4106 1 +a 3468 3469 638 +a 3468 4106 1 +a 3469 3470 637 +a 3469 4106 1 +a 3470 3471 636 +a 3470 4106 1 +a 3471 3472 635 +a 3471 4106 1 +a 3472 3473 634 +a 3472 4106 1 +a 3473 3474 633 +a 3473 4106 1 +a 3474 3475 632 +a 3474 4106 1 +a 3475 3476 631 +a 3475 4106 1 +a 3476 3477 630 +a 3476 4106 1 +a 3477 3478 629 +a 3477 4106 1 +a 3478 3479 628 +a 3478 4106 1 +a 3479 3480 627 +a 3479 4106 1 +a 3480 3481 626 +a 3480 4106 1 +a 3481 3482 625 +a 3481 4106 1 +a 3482 3483 624 +a 3482 4106 1 +a 3483 3484 623 +a 3483 4106 1 +a 3484 3485 622 +a 3484 4106 1 +a 3485 3486 621 +a 3485 4106 1 +a 3486 3487 620 +a 3486 4106 1 +a 3487 3488 619 +a 3487 4106 1 +a 3488 3489 618 +a 3488 4106 1 +a 3489 3490 617 +a 3489 4106 1 +a 3490 3491 616 +a 3490 4106 1 +a 3491 3492 615 +a 3491 4106 1 +a 3492 3493 614 +a 3492 4106 1 +a 3493 3494 613 +a 3493 4106 1 +a 3494 3495 612 +a 3494 4106 1 +a 3495 3496 611 +a 3495 4106 1 +a 3496 3497 610 +a 3496 4106 1 +a 3497 3498 609 +a 3497 4106 1 +a 3498 3499 608 +a 3498 4106 1 +a 3499 3500 607 +a 3499 4106 1 +a 3500 3501 606 +a 3500 4106 1 +a 3501 3502 605 +a 3501 4106 1 +a 3502 3503 604 +a 3502 4106 1 +a 3503 3504 603 +a 3503 4106 1 +a 3504 3505 602 +a 3504 4106 1 +a 3505 3506 601 +a 3505 4106 1 +a 3506 3507 600 +a 3506 4106 1 +a 3507 3508 599 +a 3507 4106 1 +a 3508 3509 598 +a 3508 4106 1 +a 3509 3510 597 +a 3509 4106 1 +a 3510 3511 596 +a 3510 4106 1 +a 3511 3512 595 +a 3511 4106 1 +a 3512 3513 594 +a 3512 4106 1 +a 3513 3514 593 +a 3513 4106 1 +a 3514 3515 592 +a 3514 4106 1 +a 3515 3516 591 +a 3515 4106 1 +a 3516 3517 590 +a 3516 4106 1 +a 3517 3518 589 +a 3517 4106 1 +a 3518 3519 588 +a 3518 4106 1 +a 3519 3520 587 +a 3519 4106 1 +a 3520 3521 586 +a 3520 4106 1 +a 3521 3522 585 +a 3521 4106 1 +a 3522 3523 584 +a 3522 4106 1 +a 3523 3524 583 +a 3523 4106 1 +a 3524 3525 582 +a 3524 4106 1 +a 3525 3526 581 +a 3525 4106 1 +a 3526 3527 580 +a 3526 4106 1 +a 3527 3528 579 +a 3527 4106 1 +a 3528 3529 578 +a 3528 4106 1 +a 3529 3530 577 +a 3529 4106 1 +a 3530 3531 576 +a 3530 4106 1 +a 3531 3532 575 +a 3531 4106 1 +a 3532 3533 574 +a 3532 4106 1 +a 3533 3534 573 +a 3533 4106 1 +a 3534 3535 572 +a 3534 4106 1 +a 3535 3536 571 +a 3535 4106 1 +a 3536 3537 570 +a 3536 4106 1 +a 3537 3538 569 +a 3537 4106 1 +a 3538 3539 568 +a 3538 4106 1 +a 3539 3540 567 +a 3539 4106 1 +a 3540 3541 566 +a 3540 4106 1 +a 3541 3542 565 +a 3541 4106 1 +a 3542 3543 564 +a 3542 4106 1 +a 3543 3544 563 +a 3543 4106 1 +a 3544 3545 562 +a 3544 4106 1 +a 3545 3546 561 +a 3545 4106 1 +a 3546 3547 560 +a 3546 4106 1 +a 3547 3548 559 +a 3547 4106 1 +a 3548 3549 558 +a 3548 4106 1 +a 3549 3550 557 +a 3549 4106 1 +a 3550 3551 556 +a 3550 4106 1 +a 3551 3552 555 +a 3551 4106 1 +a 3552 3553 554 +a 3552 4106 1 +a 3553 3554 553 +a 3553 4106 1 +a 3554 3555 552 +a 3554 4106 1 +a 3555 3556 551 +a 3555 4106 1 +a 3556 3557 550 +a 3556 4106 1 +a 3557 3558 549 +a 3557 4106 1 +a 3558 3559 548 +a 3558 4106 1 +a 3559 3560 547 +a 3559 4106 1 +a 3560 3561 546 +a 3560 4106 1 +a 3561 3562 545 +a 3561 4106 1 +a 3562 3563 544 +a 3562 4106 1 +a 3563 3564 543 +a 3563 4106 1 +a 3564 3565 542 +a 3564 4106 1 +a 3565 3566 541 +a 3565 4106 1 +a 3566 3567 540 +a 3566 4106 1 +a 3567 3568 539 +a 3567 4106 1 +a 3568 3569 538 +a 3568 4106 1 +a 3569 3570 537 +a 3569 4106 1 +a 3570 3571 536 +a 3570 4106 1 +a 3571 3572 535 +a 3571 4106 1 +a 3572 3573 534 +a 3572 4106 1 +a 3573 3574 533 +a 3573 4106 1 +a 3574 3575 532 +a 3574 4106 1 +a 3575 3576 531 +a 3575 4106 1 +a 3576 3577 530 +a 3576 4106 1 +a 3577 3578 529 +a 3577 4106 1 +a 3578 3579 528 +a 3578 4106 1 +a 3579 3580 527 +a 3579 4106 1 +a 3580 3581 526 +a 3580 4106 1 +a 3581 3582 525 +a 3581 4106 1 +a 3582 3583 524 +a 3582 4106 1 +a 3583 3584 523 +a 3583 4106 1 +a 3584 3585 522 +a 3584 4106 1 +a 3585 3586 521 +a 3585 4106 1 +a 3586 3587 520 +a 3586 4106 1 +a 3587 3588 519 +a 3587 4106 1 +a 3588 3589 518 +a 3588 4106 1 +a 3589 3590 517 +a 3589 4106 1 +a 3590 3591 516 +a 3590 4106 1 +a 3591 3592 515 +a 3591 4106 1 +a 3592 3593 514 +a 3592 4106 1 +a 3593 3594 513 +a 3593 4106 1 +a 3594 3595 512 +a 3594 4106 1 +a 3595 3596 511 +a 3595 4106 1 +a 3596 3597 510 +a 3596 4106 1 +a 3597 3598 509 +a 3597 4106 1 +a 3598 3599 508 +a 3598 4106 1 +a 3599 3600 507 +a 3599 4106 1 +a 3600 3601 506 +a 3600 4106 1 +a 3601 3602 505 +a 3601 4106 1 +a 3602 3603 504 +a 3602 4106 1 +a 3603 3604 503 +a 3603 4106 1 +a 3604 3605 502 +a 3604 4106 1 +a 3605 3606 501 +a 3605 4106 1 +a 3606 3607 500 +a 3606 4106 1 +a 3607 3608 499 +a 3607 4106 1 +a 3608 3609 498 +a 3608 4106 1 +a 3609 3610 497 +a 3609 4106 1 +a 3610 3611 496 +a 3610 4106 1 +a 3611 3612 495 +a 3611 4106 1 +a 3612 3613 494 +a 3612 4106 1 +a 3613 3614 493 +a 3613 4106 1 +a 3614 3615 492 +a 3614 4106 1 +a 3615 3616 491 +a 3615 4106 1 +a 3616 3617 490 +a 3616 4106 1 +a 3617 3618 489 +a 3617 4106 1 +a 3618 3619 488 +a 3618 4106 1 +a 3619 3620 487 +a 3619 4106 1 +a 3620 3621 486 +a 3620 4106 1 +a 3621 3622 485 +a 3621 4106 1 +a 3622 3623 484 +a 3622 4106 1 +a 3623 3624 483 +a 3623 4106 1 +a 3624 3625 482 +a 3624 4106 1 +a 3625 3626 481 +a 3625 4106 1 +a 3626 3627 480 +a 3626 4106 1 +a 3627 3628 479 +a 3627 4106 1 +a 3628 3629 478 +a 3628 4106 1 +a 3629 3630 477 +a 3629 4106 1 +a 3630 3631 476 +a 3630 4106 1 +a 3631 3632 475 +a 3631 4106 1 +a 3632 3633 474 +a 3632 4106 1 +a 3633 3634 473 +a 3633 4106 1 +a 3634 3635 472 +a 3634 4106 1 +a 3635 3636 471 +a 3635 4106 1 +a 3636 3637 470 +a 3636 4106 1 +a 3637 3638 469 +a 3637 4106 1 +a 3638 3639 468 +a 3638 4106 1 +a 3639 3640 467 +a 3639 4106 1 +a 3640 3641 466 +a 3640 4106 1 +a 3641 3642 465 +a 3641 4106 1 +a 3642 3643 464 +a 3642 4106 1 +a 3643 3644 463 +a 3643 4106 1 +a 3644 3645 462 +a 3644 4106 1 +a 3645 3646 461 +a 3645 4106 1 +a 3646 3647 460 +a 3646 4106 1 +a 3647 3648 459 +a 3647 4106 1 +a 3648 3649 458 +a 3648 4106 1 +a 3649 3650 457 +a 3649 4106 1 +a 3650 3651 456 +a 3650 4106 1 +a 3651 3652 455 +a 3651 4106 1 +a 3652 3653 454 +a 3652 4106 1 +a 3653 3654 453 +a 3653 4106 1 +a 3654 3655 452 +a 3654 4106 1 +a 3655 3656 451 +a 3655 4106 1 +a 3656 3657 450 +a 3656 4106 1 +a 3657 3658 449 +a 3657 4106 1 +a 3658 3659 448 +a 3658 4106 1 +a 3659 3660 447 +a 3659 4106 1 +a 3660 3661 446 +a 3660 4106 1 +a 3661 3662 445 +a 3661 4106 1 +a 3662 3663 444 +a 3662 4106 1 +a 3663 3664 443 +a 3663 4106 1 +a 3664 3665 442 +a 3664 4106 1 +a 3665 3666 441 +a 3665 4106 1 +a 3666 3667 440 +a 3666 4106 1 +a 3667 3668 439 +a 3667 4106 1 +a 3668 3669 438 +a 3668 4106 1 +a 3669 3670 437 +a 3669 4106 1 +a 3670 3671 436 +a 3670 4106 1 +a 3671 3672 435 +a 3671 4106 1 +a 3672 3673 434 +a 3672 4106 1 +a 3673 3674 433 +a 3673 4106 1 +a 3674 3675 432 +a 3674 4106 1 +a 3675 3676 431 +a 3675 4106 1 +a 3676 3677 430 +a 3676 4106 1 +a 3677 3678 429 +a 3677 4106 1 +a 3678 3679 428 +a 3678 4106 1 +a 3679 3680 427 +a 3679 4106 1 +a 3680 3681 426 +a 3680 4106 1 +a 3681 3682 425 +a 3681 4106 1 +a 3682 3683 424 +a 3682 4106 1 +a 3683 3684 423 +a 3683 4106 1 +a 3684 3685 422 +a 3684 4106 1 +a 3685 3686 421 +a 3685 4106 1 +a 3686 3687 420 +a 3686 4106 1 +a 3687 3688 419 +a 3687 4106 1 +a 3688 3689 418 +a 3688 4106 1 +a 3689 3690 417 +a 3689 4106 1 +a 3690 3691 416 +a 3690 4106 1 +a 3691 3692 415 +a 3691 4106 1 +a 3692 3693 414 +a 3692 4106 1 +a 3693 3694 413 +a 3693 4106 1 +a 3694 3695 412 +a 3694 4106 1 +a 3695 3696 411 +a 3695 4106 1 +a 3696 3697 410 +a 3696 4106 1 +a 3697 3698 409 +a 3697 4106 1 +a 3698 3699 408 +a 3698 4106 1 +a 3699 3700 407 +a 3699 4106 1 +a 3700 3701 406 +a 3700 4106 1 +a 3701 3702 405 +a 3701 4106 1 +a 3702 3703 404 +a 3702 4106 1 +a 3703 3704 403 +a 3703 4106 1 +a 3704 3705 402 +a 3704 4106 1 +a 3705 3706 401 +a 3705 4106 1 +a 3706 3707 400 +a 3706 4106 1 +a 3707 3708 399 +a 3707 4106 1 +a 3708 3709 398 +a 3708 4106 1 +a 3709 3710 397 +a 3709 4106 1 +a 3710 3711 396 +a 3710 4106 1 +a 3711 3712 395 +a 3711 4106 1 +a 3712 3713 394 +a 3712 4106 1 +a 3713 3714 393 +a 3713 4106 1 +a 3714 3715 392 +a 3714 4106 1 +a 3715 3716 391 +a 3715 4106 1 +a 3716 3717 390 +a 3716 4106 1 +a 3717 3718 389 +a 3717 4106 1 +a 3718 3719 388 +a 3718 4106 1 +a 3719 3720 387 +a 3719 4106 1 +a 3720 3721 386 +a 3720 4106 1 +a 3721 3722 385 +a 3721 4106 1 +a 3722 3723 384 +a 3722 4106 1 +a 3723 3724 383 +a 3723 4106 1 +a 3724 3725 382 +a 3724 4106 1 +a 3725 3726 381 +a 3725 4106 1 +a 3726 3727 380 +a 3726 4106 1 +a 3727 3728 379 +a 3727 4106 1 +a 3728 3729 378 +a 3728 4106 1 +a 3729 3730 377 +a 3729 4106 1 +a 3730 3731 376 +a 3730 4106 1 +a 3731 3732 375 +a 3731 4106 1 +a 3732 3733 374 +a 3732 4106 1 +a 3733 3734 373 +a 3733 4106 1 +a 3734 3735 372 +a 3734 4106 1 +a 3735 3736 371 +a 3735 4106 1 +a 3736 3737 370 +a 3736 4106 1 +a 3737 3738 369 +a 3737 4106 1 +a 3738 3739 368 +a 3738 4106 1 +a 3739 3740 367 +a 3739 4106 1 +a 3740 3741 366 +a 3740 4106 1 +a 3741 3742 365 +a 3741 4106 1 +a 3742 3743 364 +a 3742 4106 1 +a 3743 3744 363 +a 3743 4106 1 +a 3744 3745 362 +a 3744 4106 1 +a 3745 3746 361 +a 3745 4106 1 +a 3746 3747 360 +a 3746 4106 1 +a 3747 3748 359 +a 3747 4106 1 +a 3748 3749 358 +a 3748 4106 1 +a 3749 3750 357 +a 3749 4106 1 +a 3750 3751 356 +a 3750 4106 1 +a 3751 3752 355 +a 3751 4106 1 +a 3752 3753 354 +a 3752 4106 1 +a 3753 3754 353 +a 3753 4106 1 +a 3754 3755 352 +a 3754 4106 1 +a 3755 3756 351 +a 3755 4106 1 +a 3756 3757 350 +a 3756 4106 1 +a 3757 3758 349 +a 3757 4106 1 +a 3758 3759 348 +a 3758 4106 1 +a 3759 3760 347 +a 3759 4106 1 +a 3760 3761 346 +a 3760 4106 1 +a 3761 3762 345 +a 3761 4106 1 +a 3762 3763 344 +a 3762 4106 1 +a 3763 3764 343 +a 3763 4106 1 +a 3764 3765 342 +a 3764 4106 1 +a 3765 3766 341 +a 3765 4106 1 +a 3766 3767 340 +a 3766 4106 1 +a 3767 3768 339 +a 3767 4106 1 +a 3768 3769 338 +a 3768 4106 1 +a 3769 3770 337 +a 3769 4106 1 +a 3770 3771 336 +a 3770 4106 1 +a 3771 3772 335 +a 3771 4106 1 +a 3772 3773 334 +a 3772 4106 1 +a 3773 3774 333 +a 3773 4106 1 +a 3774 3775 332 +a 3774 4106 1 +a 3775 3776 331 +a 3775 4106 1 +a 3776 3777 330 +a 3776 4106 1 +a 3777 3778 329 +a 3777 4106 1 +a 3778 3779 328 +a 3778 4106 1 +a 3779 3780 327 +a 3779 4106 1 +a 3780 3781 326 +a 3780 4106 1 +a 3781 3782 325 +a 3781 4106 1 +a 3782 3783 324 +a 3782 4106 1 +a 3783 3784 323 +a 3783 4106 1 +a 3784 3785 322 +a 3784 4106 1 +a 3785 3786 321 +a 3785 4106 1 +a 3786 3787 320 +a 3786 4106 1 +a 3787 3788 319 +a 3787 4106 1 +a 3788 3789 318 +a 3788 4106 1 +a 3789 3790 317 +a 3789 4106 1 +a 3790 3791 316 +a 3790 4106 1 +a 3791 3792 315 +a 3791 4106 1 +a 3792 3793 314 +a 3792 4106 1 +a 3793 3794 313 +a 3793 4106 1 +a 3794 3795 312 +a 3794 4106 1 +a 3795 3796 311 +a 3795 4106 1 +a 3796 3797 310 +a 3796 4106 1 +a 3797 3798 309 +a 3797 4106 1 +a 3798 3799 308 +a 3798 4106 1 +a 3799 3800 307 +a 3799 4106 1 +a 3800 3801 306 +a 3800 4106 1 +a 3801 3802 305 +a 3801 4106 1 +a 3802 3803 304 +a 3802 4106 1 +a 3803 3804 303 +a 3803 4106 1 +a 3804 3805 302 +a 3804 4106 1 +a 3805 3806 301 +a 3805 4106 1 +a 3806 3807 300 +a 3806 4106 1 +a 3807 3808 299 +a 3807 4106 1 +a 3808 3809 298 +a 3808 4106 1 +a 3809 3810 297 +a 3809 4106 1 +a 3810 3811 296 +a 3810 4106 1 +a 3811 3812 295 +a 3811 4106 1 +a 3812 3813 294 +a 3812 4106 1 +a 3813 3814 293 +a 3813 4106 1 +a 3814 3815 292 +a 3814 4106 1 +a 3815 3816 291 +a 3815 4106 1 +a 3816 3817 290 +a 3816 4106 1 +a 3817 3818 289 +a 3817 4106 1 +a 3818 3819 288 +a 3818 4106 1 +a 3819 3820 287 +a 3819 4106 1 +a 3820 3821 286 +a 3820 4106 1 +a 3821 3822 285 +a 3821 4106 1 +a 3822 3823 284 +a 3822 4106 1 +a 3823 3824 283 +a 3823 4106 1 +a 3824 3825 282 +a 3824 4106 1 +a 3825 3826 281 +a 3825 4106 1 +a 3826 3827 280 +a 3826 4106 1 +a 3827 3828 279 +a 3827 4106 1 +a 3828 3829 278 +a 3828 4106 1 +a 3829 3830 277 +a 3829 4106 1 +a 3830 3831 276 +a 3830 4106 1 +a 3831 3832 275 +a 3831 4106 1 +a 3832 3833 274 +a 3832 4106 1 +a 3833 3834 273 +a 3833 4106 1 +a 3834 3835 272 +a 3834 4106 1 +a 3835 3836 271 +a 3835 4106 1 +a 3836 3837 270 +a 3836 4106 1 +a 3837 3838 269 +a 3837 4106 1 +a 3838 3839 268 +a 3838 4106 1 +a 3839 3840 267 +a 3839 4106 1 +a 3840 3841 266 +a 3840 4106 1 +a 3841 3842 265 +a 3841 4106 1 +a 3842 3843 264 +a 3842 4106 1 +a 3843 3844 263 +a 3843 4106 1 +a 3844 3845 262 +a 3844 4106 1 +a 3845 3846 261 +a 3845 4106 1 +a 3846 3847 260 +a 3846 4106 1 +a 3847 3848 259 +a 3847 4106 1 +a 3848 3849 258 +a 3848 4106 1 +a 3849 3850 257 +a 3849 4106 1 +a 3850 3851 256 +a 3850 4106 1 +a 3851 3852 255 +a 3851 4106 1 +a 3852 3853 254 +a 3852 4106 1 +a 3853 3854 253 +a 3853 4106 1 +a 3854 3855 252 +a 3854 4106 1 +a 3855 3856 251 +a 3855 4106 1 +a 3856 3857 250 +a 3856 4106 1 +a 3857 3858 249 +a 3857 4106 1 +a 3858 3859 248 +a 3858 4106 1 +a 3859 3860 247 +a 3859 4106 1 +a 3860 3861 246 +a 3860 4106 1 +a 3861 3862 245 +a 3861 4106 1 +a 3862 3863 244 +a 3862 4106 1 +a 3863 3864 243 +a 3863 4106 1 +a 3864 3865 242 +a 3864 4106 1 +a 3865 3866 241 +a 3865 4106 1 +a 3866 3867 240 +a 3866 4106 1 +a 3867 3868 239 +a 3867 4106 1 +a 3868 3869 238 +a 3868 4106 1 +a 3869 3870 237 +a 3869 4106 1 +a 3870 3871 236 +a 3870 4106 1 +a 3871 3872 235 +a 3871 4106 1 +a 3872 3873 234 +a 3872 4106 1 +a 3873 3874 233 +a 3873 4106 1 +a 3874 3875 232 +a 3874 4106 1 +a 3875 3876 231 +a 3875 4106 1 +a 3876 3877 230 +a 3876 4106 1 +a 3877 3878 229 +a 3877 4106 1 +a 3878 3879 228 +a 3878 4106 1 +a 3879 3880 227 +a 3879 4106 1 +a 3880 3881 226 +a 3880 4106 1 +a 3881 3882 225 +a 3881 4106 1 +a 3882 3883 224 +a 3882 4106 1 +a 3883 3884 223 +a 3883 4106 1 +a 3884 3885 222 +a 3884 4106 1 +a 3885 3886 221 +a 3885 4106 1 +a 3886 3887 220 +a 3886 4106 1 +a 3887 3888 219 +a 3887 4106 1 +a 3888 3889 218 +a 3888 4106 1 +a 3889 3890 217 +a 3889 4106 1 +a 3890 3891 216 +a 3890 4106 1 +a 3891 3892 215 +a 3891 4106 1 +a 3892 3893 214 +a 3892 4106 1 +a 3893 3894 213 +a 3893 4106 1 +a 3894 3895 212 +a 3894 4106 1 +a 3895 3896 211 +a 3895 4106 1 +a 3896 3897 210 +a 3896 4106 1 +a 3897 3898 209 +a 3897 4106 1 +a 3898 3899 208 +a 3898 4106 1 +a 3899 3900 207 +a 3899 4106 1 +a 3900 3901 206 +a 3900 4106 1 +a 3901 3902 205 +a 3901 4106 1 +a 3902 3903 204 +a 3902 4106 1 +a 3903 3904 203 +a 3903 4106 1 +a 3904 3905 202 +a 3904 4106 1 +a 3905 3906 201 +a 3905 4106 1 +a 3906 3907 200 +a 3906 4106 1 +a 3907 3908 199 +a 3907 4106 1 +a 3908 3909 198 +a 3908 4106 1 +a 3909 3910 197 +a 3909 4106 1 +a 3910 3911 196 +a 3910 4106 1 +a 3911 3912 195 +a 3911 4106 1 +a 3912 3913 194 +a 3912 4106 1 +a 3913 3914 193 +a 3913 4106 1 +a 3914 3915 192 +a 3914 4106 1 +a 3915 3916 191 +a 3915 4106 1 +a 3916 3917 190 +a 3916 4106 1 +a 3917 3918 189 +a 3917 4106 1 +a 3918 3919 188 +a 3918 4106 1 +a 3919 3920 187 +a 3919 4106 1 +a 3920 3921 186 +a 3920 4106 1 +a 3921 3922 185 +a 3921 4106 1 +a 3922 3923 184 +a 3922 4106 1 +a 3923 3924 183 +a 3923 4106 1 +a 3924 3925 182 +a 3924 4106 1 +a 3925 3926 181 +a 3925 4106 1 +a 3926 3927 180 +a 3926 4106 1 +a 3927 3928 179 +a 3927 4106 1 +a 3928 3929 178 +a 3928 4106 1 +a 3929 3930 177 +a 3929 4106 1 +a 3930 3931 176 +a 3930 4106 1 +a 3931 3932 175 +a 3931 4106 1 +a 3932 3933 174 +a 3932 4106 1 +a 3933 3934 173 +a 3933 4106 1 +a 3934 3935 172 +a 3934 4106 1 +a 3935 3936 171 +a 3935 4106 1 +a 3936 3937 170 +a 3936 4106 1 +a 3937 3938 169 +a 3937 4106 1 +a 3938 3939 168 +a 3938 4106 1 +a 3939 3940 167 +a 3939 4106 1 +a 3940 3941 166 +a 3940 4106 1 +a 3941 3942 165 +a 3941 4106 1 +a 3942 3943 164 +a 3942 4106 1 +a 3943 3944 163 +a 3943 4106 1 +a 3944 3945 162 +a 3944 4106 1 +a 3945 3946 161 +a 3945 4106 1 +a 3946 3947 160 +a 3946 4106 1 +a 3947 3948 159 +a 3947 4106 1 +a 3948 3949 158 +a 3948 4106 1 +a 3949 3950 157 +a 3949 4106 1 +a 3950 3951 156 +a 3950 4106 1 +a 3951 3952 155 +a 3951 4106 1 +a 3952 3953 154 +a 3952 4106 1 +a 3953 3954 153 +a 3953 4106 1 +a 3954 3955 152 +a 3954 4106 1 +a 3955 3956 151 +a 3955 4106 1 +a 3956 3957 150 +a 3956 4106 1 +a 3957 3958 149 +a 3957 4106 1 +a 3958 3959 148 +a 3958 4106 1 +a 3959 3960 147 +a 3959 4106 1 +a 3960 3961 146 +a 3960 4106 1 +a 3961 3962 145 +a 3961 4106 1 +a 3962 3963 144 +a 3962 4106 1 +a 3963 3964 143 +a 3963 4106 1 +a 3964 3965 142 +a 3964 4106 1 +a 3965 3966 141 +a 3965 4106 1 +a 3966 3967 140 +a 3966 4106 1 +a 3967 3968 139 +a 3967 4106 1 +a 3968 3969 138 +a 3968 4106 1 +a 3969 3970 137 +a 3969 4106 1 +a 3970 3971 136 +a 3970 4106 1 +a 3971 3972 135 +a 3971 4106 1 +a 3972 3973 134 +a 3972 4106 1 +a 3973 3974 133 +a 3973 4106 1 +a 3974 3975 132 +a 3974 4106 1 +a 3975 3976 131 +a 3975 4106 1 +a 3976 3977 130 +a 3976 4106 1 +a 3977 3978 129 +a 3977 4106 1 +a 3978 3979 128 +a 3978 4106 1 +a 3979 3980 127 +a 3979 4106 1 +a 3980 3981 126 +a 3980 4106 1 +a 3981 3982 125 +a 3981 4106 1 +a 3982 3983 124 +a 3982 4106 1 +a 3983 3984 123 +a 3983 4106 1 +a 3984 3985 122 +a 3984 4106 1 +a 3985 3986 121 +a 3985 4106 1 +a 3986 3987 120 +a 3986 4106 1 +a 3987 3988 119 +a 3987 4106 1 +a 3988 3989 118 +a 3988 4106 1 +a 3989 3990 117 +a 3989 4106 1 +a 3990 3991 116 +a 3990 4106 1 +a 3991 3992 115 +a 3991 4106 1 +a 3992 3993 114 +a 3992 4106 1 +a 3993 3994 113 +a 3993 4106 1 +a 3994 3995 112 +a 3994 4106 1 +a 3995 3996 111 +a 3995 4106 1 +a 3996 3997 110 +a 3996 4106 1 +a 3997 3998 109 +a 3997 4106 1 +a 3998 3999 108 +a 3998 4106 1 +a 3999 4000 107 +a 3999 4106 1 +a 4000 4001 106 +a 4000 4106 1 +a 4001 4002 105 +a 4001 4106 1 +a 4002 4003 104 +a 4002 4106 1 +a 4003 4004 103 +a 4003 4106 1 +a 4004 4005 102 +a 4004 4106 1 +a 4005 4006 101 +a 4005 4106 1 +a 4006 4007 100 +a 4006 4106 1 +a 4007 4008 99 +a 4007 4106 1 +a 4008 4009 98 +a 4008 4106 1 +a 4009 4010 97 +a 4009 4106 1 +a 4010 4011 96 +a 4010 4106 1 +a 4011 4012 95 +a 4011 4106 1 +a 4012 4013 94 +a 4012 4106 1 +a 4013 4014 93 +a 4013 4106 1 +a 4014 4015 92 +a 4014 4106 1 +a 4015 4016 91 +a 4015 4106 1 +a 4016 4017 90 +a 4016 4106 1 +a 4017 4018 89 +a 4017 4106 1 +a 4018 4019 88 +a 4018 4106 1 +a 4019 4020 87 +a 4019 4106 1 +a 4020 4021 86 +a 4020 4106 1 +a 4021 4022 85 +a 4021 4106 1 +a 4022 4023 84 +a 4022 4106 1 +a 4023 4024 83 +a 4023 4106 1 +a 4024 4025 82 +a 4024 4106 1 +a 4025 4026 81 +a 4025 4106 1 +a 4026 4027 80 +a 4026 4106 1 +a 4027 4028 79 +a 4027 4106 1 +a 4028 4029 78 +a 4028 4106 1 +a 4029 4030 77 +a 4029 4106 1 +a 4030 4031 76 +a 4030 4106 1 +a 4031 4032 75 +a 4031 4106 1 +a 4032 4033 74 +a 4032 4106 1 +a 4033 4034 73 +a 4033 4106 1 +a 4034 4035 72 +a 4034 4106 1 +a 4035 4036 71 +a 4035 4106 1 +a 4036 4037 70 +a 4036 4106 1 +a 4037 4038 69 +a 4037 4106 1 +a 4038 4039 68 +a 4038 4106 1 +a 4039 4040 67 +a 4039 4106 1 +a 4040 4041 66 +a 4040 4106 1 +a 4041 4042 65 +a 4041 4106 1 +a 4042 4043 64 +a 4042 4106 1 +a 4043 4044 63 +a 4043 4106 1 +a 4044 4045 62 +a 4044 4106 1 +a 4045 4046 61 +a 4045 4106 1 +a 4046 4047 60 +a 4046 4106 1 +a 4047 4048 59 +a 4047 4106 1 +a 4048 4049 58 +a 4048 4106 1 +a 4049 4050 57 +a 4049 4106 1 +a 4050 4051 56 +a 4050 4106 1 +a 4051 4052 55 +a 4051 4106 1 +a 4052 4053 54 +a 4052 4106 1 +a 4053 4054 53 +a 4053 4106 1 +a 4054 4055 52 +a 4054 4106 1 +a 4055 4056 51 +a 4055 4106 1 +a 4056 4057 50 +a 4056 4106 1 +a 4057 4058 49 +a 4057 4106 1 +a 4058 4059 48 +a 4058 4106 1 +a 4059 4060 47 +a 4059 4106 1 +a 4060 4061 46 +a 4060 4106 1 +a 4061 4062 45 +a 4061 4106 1 +a 4062 4063 44 +a 4062 4106 1 +a 4063 4064 43 +a 4063 4106 1 +a 4064 4065 42 +a 4064 4106 1 +a 4065 4066 41 +a 4065 4106 1 +a 4066 4067 40 +a 4066 4106 1 +a 4067 4068 39 +a 4067 4106 1 +a 4068 4069 38 +a 4068 4106 1 +a 4069 4070 37 +a 4069 4106 1 +a 4070 4071 36 +a 4070 4106 1 +a 4071 4072 35 +a 4071 4106 1 +a 4072 4073 34 +a 4072 4106 1 +a 4073 4074 33 +a 4073 4106 1 +a 4074 4075 32 +a 4074 4106 1 +a 4075 4076 31 +a 4075 4106 1 +a 4076 4077 30 +a 4076 4106 1 +a 4077 4078 29 +a 4077 4106 1 +a 4078 4079 28 +a 4078 4106 1 +a 4079 4080 27 +a 4079 4106 1 +a 4080 4081 26 +a 4080 4106 1 +a 4081 4082 25 +a 4081 4106 1 +a 4082 4083 24 +a 4082 4106 1 +a 4083 4084 23 +a 4083 4106 1 +a 4084 4085 22 +a 4084 4106 1 +a 4085 4086 21 +a 4085 4106 1 +a 4086 4087 20 +a 4086 4106 1 +a 4087 4088 19 +a 4087 4106 1 +a 4088 4089 18 +a 4088 4106 1 +a 4089 4090 17 +a 4089 4106 1 +a 4090 4091 16 +a 4090 4106 1 +a 4091 4092 15 +a 4091 4106 1 +a 4092 4093 14 +a 4092 4106 1 +a 4093 4094 13 +a 4093 4106 1 +a 4094 4095 12 +a 4094 4106 1 +a 4095 4096 11 +a 4095 4106 1 +a 4096 4097 10 +a 4096 4106 1 +a 4097 4098 9 +a 4097 4106 1 +a 4098 4099 8 +a 4098 4106 1 +a 4099 4100 7 +a 4099 4106 1 +a 4100 4101 6 +a 4100 4106 1 +a 4101 4102 5 +a 4101 4106 1 +a 4102 4103 4 +a 4102 4106 1 +a 4103 4104 3 +a 4103 4106 1 +a 4104 4105 2 +a 4104 4106 1 +a 4105 8208 1 +a 4105 4106 1 +a 4106 4107 4103 +a 4107 4108 4103 +a 4108 4109 4103 +a 4109 4110 4103 +a 4110 4111 4103 +a 4111 4112 4103 +a 4112 4113 4103 +a 4113 4114 4103 +a 4114 4115 4103 +a 4115 4116 4103 +a 4116 4117 4103 +a 4117 4118 4103 +a 4118 4119 4103 +a 4119 4120 4103 +a 4120 4121 4103 +a 4121 4122 4103 +a 4122 4123 4103 +a 4123 4124 4103 +a 4124 4125 4103 +a 4125 4126 4103 +a 4126 4127 4103 +a 4127 4128 4103 +a 4128 4129 4103 +a 4129 4130 4103 +a 4130 4131 4103 +a 4131 4132 4103 +a 4132 4133 4103 +a 4133 4134 4103 +a 4134 4135 4103 +a 4135 4136 4103 +a 4136 4137 4103 +a 4137 4138 4103 +a 4138 4139 4103 +a 4139 4140 4103 +a 4140 4141 4103 +a 4141 4142 4103 +a 4142 4143 4103 +a 4143 4144 4103 +a 4144 4145 4103 +a 4145 4146 4103 +a 4146 4147 4103 +a 4147 4148 4103 +a 4148 4149 4103 +a 4149 4150 4103 +a 4150 4151 4103 +a 4151 4152 4103 +a 4152 4153 4103 +a 4153 4154 4103 +a 4154 4155 4103 +a 4155 4156 4103 +a 4156 4157 4103 +a 4157 4158 4103 +a 4158 4159 4103 +a 4159 4160 4103 +a 4160 4161 4103 +a 4161 4162 4103 +a 4162 4163 4103 +a 4163 4164 4103 +a 4164 4165 4103 +a 4165 4166 4103 +a 4166 4167 4103 +a 4167 4168 4103 +a 4168 4169 4103 +a 4169 4170 4103 +a 4170 4171 4103 +a 4171 4172 4103 +a 4172 4173 4103 +a 4173 4174 4103 +a 4174 4175 4103 +a 4175 4176 4103 +a 4176 4177 4103 +a 4177 4178 4103 +a 4178 4179 4103 +a 4179 4180 4103 +a 4180 4181 4103 +a 4181 4182 4103 +a 4182 4183 4103 +a 4183 4184 4103 +a 4184 4185 4103 +a 4185 4186 4103 +a 4186 4187 4103 +a 4187 4188 4103 +a 4188 4189 4103 +a 4189 4190 4103 +a 4190 4191 4103 +a 4191 4192 4103 +a 4192 4193 4103 +a 4193 4194 4103 +a 4194 4195 4103 +a 4195 4196 4103 +a 4196 4197 4103 +a 4197 4198 4103 +a 4198 4199 4103 +a 4199 4200 4103 +a 4200 4201 4103 +a 4201 4202 4103 +a 4202 4203 4103 +a 4203 4204 4103 +a 4204 4205 4103 +a 4205 4206 4103 +a 4206 4207 4103 +a 4207 4208 4103 +a 4208 4209 4103 +a 4209 4210 4103 +a 4210 4211 4103 +a 4211 4212 4103 +a 4212 4213 4103 +a 4213 4214 4103 +a 4214 4215 4103 +a 4215 4216 4103 +a 4216 4217 4103 +a 4217 4218 4103 +a 4218 4219 4103 +a 4219 4220 4103 +a 4220 4221 4103 +a 4221 4222 4103 +a 4222 4223 4103 +a 4223 4224 4103 +a 4224 4225 4103 +a 4225 4226 4103 +a 4226 4227 4103 +a 4227 4228 4103 +a 4228 4229 4103 +a 4229 4230 4103 +a 4230 4231 4103 +a 4231 4232 4103 +a 4232 4233 4103 +a 4233 4234 4103 +a 4234 4235 4103 +a 4235 4236 4103 +a 4236 4237 4103 +a 4237 4238 4103 +a 4238 4239 4103 +a 4239 4240 4103 +a 4240 4241 4103 +a 4241 4242 4103 +a 4242 4243 4103 +a 4243 4244 4103 +a 4244 4245 4103 +a 4245 4246 4103 +a 4246 4247 4103 +a 4247 4248 4103 +a 4248 4249 4103 +a 4249 4250 4103 +a 4250 4251 4103 +a 4251 4252 4103 +a 4252 4253 4103 +a 4253 4254 4103 +a 4254 4255 4103 +a 4255 4256 4103 +a 4256 4257 4103 +a 4257 4258 4103 +a 4258 4259 4103 +a 4259 4260 4103 +a 4260 4261 4103 +a 4261 4262 4103 +a 4262 4263 4103 +a 4263 4264 4103 +a 4264 4265 4103 +a 4265 4266 4103 +a 4266 4267 4103 +a 4267 4268 4103 +a 4268 4269 4103 +a 4269 4270 4103 +a 4270 4271 4103 +a 4271 4272 4103 +a 4272 4273 4103 +a 4273 4274 4103 +a 4274 4275 4103 +a 4275 4276 4103 +a 4276 4277 4103 +a 4277 4278 4103 +a 4278 4279 4103 +a 4279 4280 4103 +a 4280 4281 4103 +a 4281 4282 4103 +a 4282 4283 4103 +a 4283 4284 4103 +a 4284 4285 4103 +a 4285 4286 4103 +a 4286 4287 4103 +a 4287 4288 4103 +a 4288 4289 4103 +a 4289 4290 4103 +a 4290 4291 4103 +a 4291 4292 4103 +a 4292 4293 4103 +a 4293 4294 4103 +a 4294 4295 4103 +a 4295 4296 4103 +a 4296 4297 4103 +a 4297 4298 4103 +a 4298 4299 4103 +a 4299 4300 4103 +a 4300 4301 4103 +a 4301 4302 4103 +a 4302 4303 4103 +a 4303 4304 4103 +a 4304 4305 4103 +a 4305 4306 4103 +a 4306 4307 4103 +a 4307 4308 4103 +a 4308 4309 4103 +a 4309 4310 4103 +a 4310 4311 4103 +a 4311 4312 4103 +a 4312 4313 4103 +a 4313 4314 4103 +a 4314 4315 4103 +a 4315 4316 4103 +a 4316 4317 4103 +a 4317 4318 4103 +a 4318 4319 4103 +a 4319 4320 4103 +a 4320 4321 4103 +a 4321 4322 4103 +a 4322 4323 4103 +a 4323 4324 4103 +a 4324 4325 4103 +a 4325 4326 4103 +a 4326 4327 4103 +a 4327 4328 4103 +a 4328 4329 4103 +a 4329 4330 4103 +a 4330 4331 4103 +a 4331 4332 4103 +a 4332 4333 4103 +a 4333 4334 4103 +a 4334 4335 4103 +a 4335 4336 4103 +a 4336 4337 4103 +a 4337 4338 4103 +a 4338 4339 4103 +a 4339 4340 4103 +a 4340 4341 4103 +a 4341 4342 4103 +a 4342 4343 4103 +a 4343 4344 4103 +a 4344 4345 4103 +a 4345 4346 4103 +a 4346 4347 4103 +a 4347 4348 4103 +a 4348 4349 4103 +a 4349 4350 4103 +a 4350 4351 4103 +a 4351 4352 4103 +a 4352 4353 4103 +a 4353 4354 4103 +a 4354 4355 4103 +a 4355 4356 4103 +a 4356 4357 4103 +a 4357 4358 4103 +a 4358 4359 4103 +a 4359 4360 4103 +a 4360 4361 4103 +a 4361 4362 4103 +a 4362 4363 4103 +a 4363 4364 4103 +a 4364 4365 4103 +a 4365 4366 4103 +a 4366 4367 4103 +a 4367 4368 4103 +a 4368 4369 4103 +a 4369 4370 4103 +a 4370 4371 4103 +a 4371 4372 4103 +a 4372 4373 4103 +a 4373 4374 4103 +a 4374 4375 4103 +a 4375 4376 4103 +a 4376 4377 4103 +a 4377 4378 4103 +a 4378 4379 4103 +a 4379 4380 4103 +a 4380 4381 4103 +a 4381 4382 4103 +a 4382 4383 4103 +a 4383 4384 4103 +a 4384 4385 4103 +a 4385 4386 4103 +a 4386 4387 4103 +a 4387 4388 4103 +a 4388 4389 4103 +a 4389 4390 4103 +a 4390 4391 4103 +a 4391 4392 4103 +a 4392 4393 4103 +a 4393 4394 4103 +a 4394 4395 4103 +a 4395 4396 4103 +a 4396 4397 4103 +a 4397 4398 4103 +a 4398 4399 4103 +a 4399 4400 4103 +a 4400 4401 4103 +a 4401 4402 4103 +a 4402 4403 4103 +a 4403 4404 4103 +a 4404 4405 4103 +a 4405 4406 4103 +a 4406 4407 4103 +a 4407 4408 4103 +a 4408 4409 4103 +a 4409 4410 4103 +a 4410 4411 4103 +a 4411 4412 4103 +a 4412 4413 4103 +a 4413 4414 4103 +a 4414 4415 4103 +a 4415 4416 4103 +a 4416 4417 4103 +a 4417 4418 4103 +a 4418 4419 4103 +a 4419 4420 4103 +a 4420 4421 4103 +a 4421 4422 4103 +a 4422 4423 4103 +a 4423 4424 4103 +a 4424 4425 4103 +a 4425 4426 4103 +a 4426 4427 4103 +a 4427 4428 4103 +a 4428 4429 4103 +a 4429 4430 4103 +a 4430 4431 4103 +a 4431 4432 4103 +a 4432 4433 4103 +a 4433 4434 4103 +a 4434 4435 4103 +a 4435 4436 4103 +a 4436 4437 4103 +a 4437 4438 4103 +a 4438 4439 4103 +a 4439 4440 4103 +a 4440 4441 4103 +a 4441 4442 4103 +a 4442 4443 4103 +a 4443 4444 4103 +a 4444 4445 4103 +a 4445 4446 4103 +a 4446 4447 4103 +a 4447 4448 4103 +a 4448 4449 4103 +a 4449 4450 4103 +a 4450 4451 4103 +a 4451 4452 4103 +a 4452 4453 4103 +a 4453 4454 4103 +a 4454 4455 4103 +a 4455 4456 4103 +a 4456 4457 4103 +a 4457 4458 4103 +a 4458 4459 4103 +a 4459 4460 4103 +a 4460 4461 4103 +a 4461 4462 4103 +a 4462 4463 4103 +a 4463 4464 4103 +a 4464 4465 4103 +a 4465 4466 4103 +a 4466 4467 4103 +a 4467 4468 4103 +a 4468 4469 4103 +a 4469 4470 4103 +a 4470 4471 4103 +a 4471 4472 4103 +a 4472 4473 4103 +a 4473 4474 4103 +a 4474 4475 4103 +a 4475 4476 4103 +a 4476 4477 4103 +a 4477 4478 4103 +a 4478 4479 4103 +a 4479 4480 4103 +a 4480 4481 4103 +a 4481 4482 4103 +a 4482 4483 4103 +a 4483 4484 4103 +a 4484 4485 4103 +a 4485 4486 4103 +a 4486 4487 4103 +a 4487 4488 4103 +a 4488 4489 4103 +a 4489 4490 4103 +a 4490 4491 4103 +a 4491 4492 4103 +a 4492 4493 4103 +a 4493 4494 4103 +a 4494 4495 4103 +a 4495 4496 4103 +a 4496 4497 4103 +a 4497 4498 4103 +a 4498 4499 4103 +a 4499 4500 4103 +a 4500 4501 4103 +a 4501 4502 4103 +a 4502 4503 4103 +a 4503 4504 4103 +a 4504 4505 4103 +a 4505 4506 4103 +a 4506 4507 4103 +a 4507 4508 4103 +a 4508 4509 4103 +a 4509 4510 4103 +a 4510 4511 4103 +a 4511 4512 4103 +a 4512 4513 4103 +a 4513 4514 4103 +a 4514 4515 4103 +a 4515 4516 4103 +a 4516 4517 4103 +a 4517 4518 4103 +a 4518 4519 4103 +a 4519 4520 4103 +a 4520 4521 4103 +a 4521 4522 4103 +a 4522 4523 4103 +a 4523 4524 4103 +a 4524 4525 4103 +a 4525 4526 4103 +a 4526 4527 4103 +a 4527 4528 4103 +a 4528 4529 4103 +a 4529 4530 4103 +a 4530 4531 4103 +a 4531 4532 4103 +a 4532 4533 4103 +a 4533 4534 4103 +a 4534 4535 4103 +a 4535 4536 4103 +a 4536 4537 4103 +a 4537 4538 4103 +a 4538 4539 4103 +a 4539 4540 4103 +a 4540 4541 4103 +a 4541 4542 4103 +a 4542 4543 4103 +a 4543 4544 4103 +a 4544 4545 4103 +a 4545 4546 4103 +a 4546 4547 4103 +a 4547 4548 4103 +a 4548 4549 4103 +a 4549 4550 4103 +a 4550 4551 4103 +a 4551 4552 4103 +a 4552 4553 4103 +a 4553 4554 4103 +a 4554 4555 4103 +a 4555 4556 4103 +a 4556 4557 4103 +a 4557 4558 4103 +a 4558 4559 4103 +a 4559 4560 4103 +a 4560 4561 4103 +a 4561 4562 4103 +a 4562 4563 4103 +a 4563 4564 4103 +a 4564 4565 4103 +a 4565 4566 4103 +a 4566 4567 4103 +a 4567 4568 4103 +a 4568 4569 4103 +a 4569 4570 4103 +a 4570 4571 4103 +a 4571 4572 4103 +a 4572 4573 4103 +a 4573 4574 4103 +a 4574 4575 4103 +a 4575 4576 4103 +a 4576 4577 4103 +a 4577 4578 4103 +a 4578 4579 4103 +a 4579 4580 4103 +a 4580 4581 4103 +a 4581 4582 4103 +a 4582 4583 4103 +a 4583 4584 4103 +a 4584 4585 4103 +a 4585 4586 4103 +a 4586 4587 4103 +a 4587 4588 4103 +a 4588 4589 4103 +a 4589 4590 4103 +a 4590 4591 4103 +a 4591 4592 4103 +a 4592 4593 4103 +a 4593 4594 4103 +a 4594 4595 4103 +a 4595 4596 4103 +a 4596 4597 4103 +a 4597 4598 4103 +a 4598 4599 4103 +a 4599 4600 4103 +a 4600 4601 4103 +a 4601 4602 4103 +a 4602 4603 4103 +a 4603 4604 4103 +a 4604 4605 4103 +a 4605 4606 4103 +a 4606 4607 4103 +a 4607 4608 4103 +a 4608 4609 4103 +a 4609 4610 4103 +a 4610 4611 4103 +a 4611 4612 4103 +a 4612 4613 4103 +a 4613 4614 4103 +a 4614 4615 4103 +a 4615 4616 4103 +a 4616 4617 4103 +a 4617 4618 4103 +a 4618 4619 4103 +a 4619 4620 4103 +a 4620 4621 4103 +a 4621 4622 4103 +a 4622 4623 4103 +a 4623 4624 4103 +a 4624 4625 4103 +a 4625 4626 4103 +a 4626 4627 4103 +a 4627 4628 4103 +a 4628 4629 4103 +a 4629 4630 4103 +a 4630 4631 4103 +a 4631 4632 4103 +a 4632 4633 4103 +a 4633 4634 4103 +a 4634 4635 4103 +a 4635 4636 4103 +a 4636 4637 4103 +a 4637 4638 4103 +a 4638 4639 4103 +a 4639 4640 4103 +a 4640 4641 4103 +a 4641 4642 4103 +a 4642 4643 4103 +a 4643 4644 4103 +a 4644 4645 4103 +a 4645 4646 4103 +a 4646 4647 4103 +a 4647 4648 4103 +a 4648 4649 4103 +a 4649 4650 4103 +a 4650 4651 4103 +a 4651 4652 4103 +a 4652 4653 4103 +a 4653 4654 4103 +a 4654 4655 4103 +a 4655 4656 4103 +a 4656 4657 4103 +a 4657 4658 4103 +a 4658 4659 4103 +a 4659 4660 4103 +a 4660 4661 4103 +a 4661 4662 4103 +a 4662 4663 4103 +a 4663 4664 4103 +a 4664 4665 4103 +a 4665 4666 4103 +a 4666 4667 4103 +a 4667 4668 4103 +a 4668 4669 4103 +a 4669 4670 4103 +a 4670 4671 4103 +a 4671 4672 4103 +a 4672 4673 4103 +a 4673 4674 4103 +a 4674 4675 4103 +a 4675 4676 4103 +a 4676 4677 4103 +a 4677 4678 4103 +a 4678 4679 4103 +a 4679 4680 4103 +a 4680 4681 4103 +a 4681 4682 4103 +a 4682 4683 4103 +a 4683 4684 4103 +a 4684 4685 4103 +a 4685 4686 4103 +a 4686 4687 4103 +a 4687 4688 4103 +a 4688 4689 4103 +a 4689 4690 4103 +a 4690 4691 4103 +a 4691 4692 4103 +a 4692 4693 4103 +a 4693 4694 4103 +a 4694 4695 4103 +a 4695 4696 4103 +a 4696 4697 4103 +a 4697 4698 4103 +a 4698 4699 4103 +a 4699 4700 4103 +a 4700 4701 4103 +a 4701 4702 4103 +a 4702 4703 4103 +a 4703 4704 4103 +a 4704 4705 4103 +a 4705 4706 4103 +a 4706 4707 4103 +a 4707 4708 4103 +a 4708 4709 4103 +a 4709 4710 4103 +a 4710 4711 4103 +a 4711 4712 4103 +a 4712 4713 4103 +a 4713 4714 4103 +a 4714 4715 4103 +a 4715 4716 4103 +a 4716 4717 4103 +a 4717 4718 4103 +a 4718 4719 4103 +a 4719 4720 4103 +a 4720 4721 4103 +a 4721 4722 4103 +a 4722 4723 4103 +a 4723 4724 4103 +a 4724 4725 4103 +a 4725 4726 4103 +a 4726 4727 4103 +a 4727 4728 4103 +a 4728 4729 4103 +a 4729 4730 4103 +a 4730 4731 4103 +a 4731 4732 4103 +a 4732 4733 4103 +a 4733 4734 4103 +a 4734 4735 4103 +a 4735 4736 4103 +a 4736 4737 4103 +a 4737 4738 4103 +a 4738 4739 4103 +a 4739 4740 4103 +a 4740 4741 4103 +a 4741 4742 4103 +a 4742 4743 4103 +a 4743 4744 4103 +a 4744 4745 4103 +a 4745 4746 4103 +a 4746 4747 4103 +a 4747 4748 4103 +a 4748 4749 4103 +a 4749 4750 4103 +a 4750 4751 4103 +a 4751 4752 4103 +a 4752 4753 4103 +a 4753 4754 4103 +a 4754 4755 4103 +a 4755 4756 4103 +a 4756 4757 4103 +a 4757 4758 4103 +a 4758 4759 4103 +a 4759 4760 4103 +a 4760 4761 4103 +a 4761 4762 4103 +a 4762 4763 4103 +a 4763 4764 4103 +a 4764 4765 4103 +a 4765 4766 4103 +a 4766 4767 4103 +a 4767 4768 4103 +a 4768 4769 4103 +a 4769 4770 4103 +a 4770 4771 4103 +a 4771 4772 4103 +a 4772 4773 4103 +a 4773 4774 4103 +a 4774 4775 4103 +a 4775 4776 4103 +a 4776 4777 4103 +a 4777 4778 4103 +a 4778 4779 4103 +a 4779 4780 4103 +a 4780 4781 4103 +a 4781 4782 4103 +a 4782 4783 4103 +a 4783 4784 4103 +a 4784 4785 4103 +a 4785 4786 4103 +a 4786 4787 4103 +a 4787 4788 4103 +a 4788 4789 4103 +a 4789 4790 4103 +a 4790 4791 4103 +a 4791 4792 4103 +a 4792 4793 4103 +a 4793 4794 4103 +a 4794 4795 4103 +a 4795 4796 4103 +a 4796 4797 4103 +a 4797 4798 4103 +a 4798 4799 4103 +a 4799 4800 4103 +a 4800 4801 4103 +a 4801 4802 4103 +a 4802 4803 4103 +a 4803 4804 4103 +a 4804 4805 4103 +a 4805 4806 4103 +a 4806 4807 4103 +a 4807 4808 4103 +a 4808 4809 4103 +a 4809 4810 4103 +a 4810 4811 4103 +a 4811 4812 4103 +a 4812 4813 4103 +a 4813 4814 4103 +a 4814 4815 4103 +a 4815 4816 4103 +a 4816 4817 4103 +a 4817 4818 4103 +a 4818 4819 4103 +a 4819 4820 4103 +a 4820 4821 4103 +a 4821 4822 4103 +a 4822 4823 4103 +a 4823 4824 4103 +a 4824 4825 4103 +a 4825 4826 4103 +a 4826 4827 4103 +a 4827 4828 4103 +a 4828 4829 4103 +a 4829 4830 4103 +a 4830 4831 4103 +a 4831 4832 4103 +a 4832 4833 4103 +a 4833 4834 4103 +a 4834 4835 4103 +a 4835 4836 4103 +a 4836 4837 4103 +a 4837 4838 4103 +a 4838 4839 4103 +a 4839 4840 4103 +a 4840 4841 4103 +a 4841 4842 4103 +a 4842 4843 4103 +a 4843 4844 4103 +a 4844 4845 4103 +a 4845 4846 4103 +a 4846 4847 4103 +a 4847 4848 4103 +a 4848 4849 4103 +a 4849 4850 4103 +a 4850 4851 4103 +a 4851 4852 4103 +a 4852 4853 4103 +a 4853 4854 4103 +a 4854 4855 4103 +a 4855 4856 4103 +a 4856 4857 4103 +a 4857 4858 4103 +a 4858 4859 4103 +a 4859 4860 4103 +a 4860 4861 4103 +a 4861 4862 4103 +a 4862 4863 4103 +a 4863 4864 4103 +a 4864 4865 4103 +a 4865 4866 4103 +a 4866 4867 4103 +a 4867 4868 4103 +a 4868 4869 4103 +a 4869 4870 4103 +a 4870 4871 4103 +a 4871 4872 4103 +a 4872 4873 4103 +a 4873 4874 4103 +a 4874 4875 4103 +a 4875 4876 4103 +a 4876 4877 4103 +a 4877 4878 4103 +a 4878 4879 4103 +a 4879 4880 4103 +a 4880 4881 4103 +a 4881 4882 4103 +a 4882 4883 4103 +a 4883 4884 4103 +a 4884 4885 4103 +a 4885 4886 4103 +a 4886 4887 4103 +a 4887 4888 4103 +a 4888 4889 4103 +a 4889 4890 4103 +a 4890 4891 4103 +a 4891 4892 4103 +a 4892 4893 4103 +a 4893 4894 4103 +a 4894 4895 4103 +a 4895 4896 4103 +a 4896 4897 4103 +a 4897 4898 4103 +a 4898 4899 4103 +a 4899 4900 4103 +a 4900 4901 4103 +a 4901 4902 4103 +a 4902 4903 4103 +a 4903 4904 4103 +a 4904 4905 4103 +a 4905 4906 4103 +a 4906 4907 4103 +a 4907 4908 4103 +a 4908 4909 4103 +a 4909 4910 4103 +a 4910 4911 4103 +a 4911 4912 4103 +a 4912 4913 4103 +a 4913 4914 4103 +a 4914 4915 4103 +a 4915 4916 4103 +a 4916 4917 4103 +a 4917 4918 4103 +a 4918 4919 4103 +a 4919 4920 4103 +a 4920 4921 4103 +a 4921 4922 4103 +a 4922 4923 4103 +a 4923 4924 4103 +a 4924 4925 4103 +a 4925 4926 4103 +a 4926 4927 4103 +a 4927 4928 4103 +a 4928 4929 4103 +a 4929 4930 4103 +a 4930 4931 4103 +a 4931 4932 4103 +a 4932 4933 4103 +a 4933 4934 4103 +a 4934 4935 4103 +a 4935 4936 4103 +a 4936 4937 4103 +a 4937 4938 4103 +a 4938 4939 4103 +a 4939 4940 4103 +a 4940 4941 4103 +a 4941 4942 4103 +a 4942 4943 4103 +a 4943 4944 4103 +a 4944 4945 4103 +a 4945 4946 4103 +a 4946 4947 4103 +a 4947 4948 4103 +a 4948 4949 4103 +a 4949 4950 4103 +a 4950 4951 4103 +a 4951 4952 4103 +a 4952 4953 4103 +a 4953 4954 4103 +a 4954 4955 4103 +a 4955 4956 4103 +a 4956 4957 4103 +a 4957 4958 4103 +a 4958 4959 4103 +a 4959 4960 4103 +a 4960 4961 4103 +a 4961 4962 4103 +a 4962 4963 4103 +a 4963 4964 4103 +a 4964 4965 4103 +a 4965 4966 4103 +a 4966 4967 4103 +a 4967 4968 4103 +a 4968 4969 4103 +a 4969 4970 4103 +a 4970 4971 4103 +a 4971 4972 4103 +a 4972 4973 4103 +a 4973 4974 4103 +a 4974 4975 4103 +a 4975 4976 4103 +a 4976 4977 4103 +a 4977 4978 4103 +a 4978 4979 4103 +a 4979 4980 4103 +a 4980 4981 4103 +a 4981 4982 4103 +a 4982 4983 4103 +a 4983 4984 4103 +a 4984 4985 4103 +a 4985 4986 4103 +a 4986 4987 4103 +a 4987 4988 4103 +a 4988 4989 4103 +a 4989 4990 4103 +a 4990 4991 4103 +a 4991 4992 4103 +a 4992 4993 4103 +a 4993 4994 4103 +a 4994 4995 4103 +a 4995 4996 4103 +a 4996 4997 4103 +a 4997 4998 4103 +a 4998 4999 4103 +a 4999 5000 4103 +a 5000 5001 4103 +a 5001 5002 4103 +a 5002 5003 4103 +a 5003 5004 4103 +a 5004 5005 4103 +a 5005 5006 4103 +a 5006 5007 4103 +a 5007 5008 4103 +a 5008 5009 4103 +a 5009 5010 4103 +a 5010 5011 4103 +a 5011 5012 4103 +a 5012 5013 4103 +a 5013 5014 4103 +a 5014 5015 4103 +a 5015 5016 4103 +a 5016 5017 4103 +a 5017 5018 4103 +a 5018 5019 4103 +a 5019 5020 4103 +a 5020 5021 4103 +a 5021 5022 4103 +a 5022 5023 4103 +a 5023 5024 4103 +a 5024 5025 4103 +a 5025 5026 4103 +a 5026 5027 4103 +a 5027 5028 4103 +a 5028 5029 4103 +a 5029 5030 4103 +a 5030 5031 4103 +a 5031 5032 4103 +a 5032 5033 4103 +a 5033 5034 4103 +a 5034 5035 4103 +a 5035 5036 4103 +a 5036 5037 4103 +a 5037 5038 4103 +a 5038 5039 4103 +a 5039 5040 4103 +a 5040 5041 4103 +a 5041 5042 4103 +a 5042 5043 4103 +a 5043 5044 4103 +a 5044 5045 4103 +a 5045 5046 4103 +a 5046 5047 4103 +a 5047 5048 4103 +a 5048 5049 4103 +a 5049 5050 4103 +a 5050 5051 4103 +a 5051 5052 4103 +a 5052 5053 4103 +a 5053 5054 4103 +a 5054 5055 4103 +a 5055 5056 4103 +a 5056 5057 4103 +a 5057 5058 4103 +a 5058 5059 4103 +a 5059 5060 4103 +a 5060 5061 4103 +a 5061 5062 4103 +a 5062 5063 4103 +a 5063 5064 4103 +a 5064 5065 4103 +a 5065 5066 4103 +a 5066 5067 4103 +a 5067 5068 4103 +a 5068 5069 4103 +a 5069 5070 4103 +a 5070 5071 4103 +a 5071 5072 4103 +a 5072 5073 4103 +a 5073 5074 4103 +a 5074 5075 4103 +a 5075 5076 4103 +a 5076 5077 4103 +a 5077 5078 4103 +a 5078 5079 4103 +a 5079 5080 4103 +a 5080 5081 4103 +a 5081 5082 4103 +a 5082 5083 4103 +a 5083 5084 4103 +a 5084 5085 4103 +a 5085 5086 4103 +a 5086 5087 4103 +a 5087 5088 4103 +a 5088 5089 4103 +a 5089 5090 4103 +a 5090 5091 4103 +a 5091 5092 4103 +a 5092 5093 4103 +a 5093 5094 4103 +a 5094 5095 4103 +a 5095 5096 4103 +a 5096 5097 4103 +a 5097 5098 4103 +a 5098 5099 4103 +a 5099 5100 4103 +a 5100 5101 4103 +a 5101 5102 4103 +a 5102 5103 4103 +a 5103 5104 4103 +a 5104 5105 4103 +a 5105 5106 4103 +a 5106 5107 4103 +a 5107 5108 4103 +a 5108 5109 4103 +a 5109 5110 4103 +a 5110 5111 4103 +a 5111 5112 4103 +a 5112 5113 4103 +a 5113 5114 4103 +a 5114 5115 4103 +a 5115 5116 4103 +a 5116 5117 4103 +a 5117 5118 4103 +a 5118 5119 4103 +a 5119 5120 4103 +a 5120 5121 4103 +a 5121 5122 4103 +a 5122 5123 4103 +a 5123 5124 4103 +a 5124 5125 4103 +a 5125 5126 4103 +a 5126 5127 4103 +a 5127 5128 4103 +a 5128 5129 4103 +a 5129 5130 4103 +a 5130 5131 4103 +a 5131 5132 4103 +a 5132 5133 4103 +a 5133 5134 4103 +a 5134 5135 4103 +a 5135 5136 4103 +a 5136 5137 4103 +a 5137 5138 4103 +a 5138 5139 4103 +a 5139 5140 4103 +a 5140 5141 4103 +a 5141 5142 4103 +a 5142 5143 4103 +a 5143 5144 4103 +a 5144 5145 4103 +a 5145 5146 4103 +a 5146 5147 4103 +a 5147 5148 4103 +a 5148 5149 4103 +a 5149 5150 4103 +a 5150 5151 4103 +a 5151 5152 4103 +a 5152 5153 4103 +a 5153 5154 4103 +a 5154 5155 4103 +a 5155 5156 4103 +a 5156 5157 4103 +a 5157 5158 4103 +a 5158 5159 4103 +a 5159 5160 4103 +a 5160 5161 4103 +a 5161 5162 4103 +a 5162 5163 4103 +a 5163 5164 4103 +a 5164 5165 4103 +a 5165 5166 4103 +a 5166 5167 4103 +a 5167 5168 4103 +a 5168 5169 4103 +a 5169 5170 4103 +a 5170 5171 4103 +a 5171 5172 4103 +a 5172 5173 4103 +a 5173 5174 4103 +a 5174 5175 4103 +a 5175 5176 4103 +a 5176 5177 4103 +a 5177 5178 4103 +a 5178 5179 4103 +a 5179 5180 4103 +a 5180 5181 4103 +a 5181 5182 4103 +a 5182 5183 4103 +a 5183 5184 4103 +a 5184 5185 4103 +a 5185 5186 4103 +a 5186 5187 4103 +a 5187 5188 4103 +a 5188 5189 4103 +a 5189 5190 4103 +a 5190 5191 4103 +a 5191 5192 4103 +a 5192 5193 4103 +a 5193 5194 4103 +a 5194 5195 4103 +a 5195 5196 4103 +a 5196 5197 4103 +a 5197 5198 4103 +a 5198 5199 4103 +a 5199 5200 4103 +a 5200 5201 4103 +a 5201 5202 4103 +a 5202 5203 4103 +a 5203 5204 4103 +a 5204 5205 4103 +a 5205 5206 4103 +a 5206 5207 4103 +a 5207 5208 4103 +a 5208 5209 4103 +a 5209 5210 4103 +a 5210 5211 4103 +a 5211 5212 4103 +a 5212 5213 4103 +a 5213 5214 4103 +a 5214 5215 4103 +a 5215 5216 4103 +a 5216 5217 4103 +a 5217 5218 4103 +a 5218 5219 4103 +a 5219 5220 4103 +a 5220 5221 4103 +a 5221 5222 4103 +a 5222 5223 4103 +a 5223 5224 4103 +a 5224 5225 4103 +a 5225 5226 4103 +a 5226 5227 4103 +a 5227 5228 4103 +a 5228 5229 4103 +a 5229 5230 4103 +a 5230 5231 4103 +a 5231 5232 4103 +a 5232 5233 4103 +a 5233 5234 4103 +a 5234 5235 4103 +a 5235 5236 4103 +a 5236 5237 4103 +a 5237 5238 4103 +a 5238 5239 4103 +a 5239 5240 4103 +a 5240 5241 4103 +a 5241 5242 4103 +a 5242 5243 4103 +a 5243 5244 4103 +a 5244 5245 4103 +a 5245 5246 4103 +a 5246 5247 4103 +a 5247 5248 4103 +a 5248 5249 4103 +a 5249 5250 4103 +a 5250 5251 4103 +a 5251 5252 4103 +a 5252 5253 4103 +a 5253 5254 4103 +a 5254 5255 4103 +a 5255 5256 4103 +a 5256 5257 4103 +a 5257 5258 4103 +a 5258 5259 4103 +a 5259 5260 4103 +a 5260 5261 4103 +a 5261 5262 4103 +a 5262 5263 4103 +a 5263 5264 4103 +a 5264 5265 4103 +a 5265 5266 4103 +a 5266 5267 4103 +a 5267 5268 4103 +a 5268 5269 4103 +a 5269 5270 4103 +a 5270 5271 4103 +a 5271 5272 4103 +a 5272 5273 4103 +a 5273 5274 4103 +a 5274 5275 4103 +a 5275 5276 4103 +a 5276 5277 4103 +a 5277 5278 4103 +a 5278 5279 4103 +a 5279 5280 4103 +a 5280 5281 4103 +a 5281 5282 4103 +a 5282 5283 4103 +a 5283 5284 4103 +a 5284 5285 4103 +a 5285 5286 4103 +a 5286 5287 4103 +a 5287 5288 4103 +a 5288 5289 4103 +a 5289 5290 4103 +a 5290 5291 4103 +a 5291 5292 4103 +a 5292 5293 4103 +a 5293 5294 4103 +a 5294 5295 4103 +a 5295 5296 4103 +a 5296 5297 4103 +a 5297 5298 4103 +a 5298 5299 4103 +a 5299 5300 4103 +a 5300 5301 4103 +a 5301 5302 4103 +a 5302 5303 4103 +a 5303 5304 4103 +a 5304 5305 4103 +a 5305 5306 4103 +a 5306 5307 4103 +a 5307 5308 4103 +a 5308 5309 4103 +a 5309 5310 4103 +a 5310 5311 4103 +a 5311 5312 4103 +a 5312 5313 4103 +a 5313 5314 4103 +a 5314 5315 4103 +a 5315 5316 4103 +a 5316 5317 4103 +a 5317 5318 4103 +a 5318 5319 4103 +a 5319 5320 4103 +a 5320 5321 4103 +a 5321 5322 4103 +a 5322 5323 4103 +a 5323 5324 4103 +a 5324 5325 4103 +a 5325 5326 4103 +a 5326 5327 4103 +a 5327 5328 4103 +a 5328 5329 4103 +a 5329 5330 4103 +a 5330 5331 4103 +a 5331 5332 4103 +a 5332 5333 4103 +a 5333 5334 4103 +a 5334 5335 4103 +a 5335 5336 4103 +a 5336 5337 4103 +a 5337 5338 4103 +a 5338 5339 4103 +a 5339 5340 4103 +a 5340 5341 4103 +a 5341 5342 4103 +a 5342 5343 4103 +a 5343 5344 4103 +a 5344 5345 4103 +a 5345 5346 4103 +a 5346 5347 4103 +a 5347 5348 4103 +a 5348 5349 4103 +a 5349 5350 4103 +a 5350 5351 4103 +a 5351 5352 4103 +a 5352 5353 4103 +a 5353 5354 4103 +a 5354 5355 4103 +a 5355 5356 4103 +a 5356 5357 4103 +a 5357 5358 4103 +a 5358 5359 4103 +a 5359 5360 4103 +a 5360 5361 4103 +a 5361 5362 4103 +a 5362 5363 4103 +a 5363 5364 4103 +a 5364 5365 4103 +a 5365 5366 4103 +a 5366 5367 4103 +a 5367 5368 4103 +a 5368 5369 4103 +a 5369 5370 4103 +a 5370 5371 4103 +a 5371 5372 4103 +a 5372 5373 4103 +a 5373 5374 4103 +a 5374 5375 4103 +a 5375 5376 4103 +a 5376 5377 4103 +a 5377 5378 4103 +a 5378 5379 4103 +a 5379 5380 4103 +a 5380 5381 4103 +a 5381 5382 4103 +a 5382 5383 4103 +a 5383 5384 4103 +a 5384 5385 4103 +a 5385 5386 4103 +a 5386 5387 4103 +a 5387 5388 4103 +a 5388 5389 4103 +a 5389 5390 4103 +a 5390 5391 4103 +a 5391 5392 4103 +a 5392 5393 4103 +a 5393 5394 4103 +a 5394 5395 4103 +a 5395 5396 4103 +a 5396 5397 4103 +a 5397 5398 4103 +a 5398 5399 4103 +a 5399 5400 4103 +a 5400 5401 4103 +a 5401 5402 4103 +a 5402 5403 4103 +a 5403 5404 4103 +a 5404 5405 4103 +a 5405 5406 4103 +a 5406 5407 4103 +a 5407 5408 4103 +a 5408 5409 4103 +a 5409 5410 4103 +a 5410 5411 4103 +a 5411 5412 4103 +a 5412 5413 4103 +a 5413 5414 4103 +a 5414 5415 4103 +a 5415 5416 4103 +a 5416 5417 4103 +a 5417 5418 4103 +a 5418 5419 4103 +a 5419 5420 4103 +a 5420 5421 4103 +a 5421 5422 4103 +a 5422 5423 4103 +a 5423 5424 4103 +a 5424 5425 4103 +a 5425 5426 4103 +a 5426 5427 4103 +a 5427 5428 4103 +a 5428 5429 4103 +a 5429 5430 4103 +a 5430 5431 4103 +a 5431 5432 4103 +a 5432 5433 4103 +a 5433 5434 4103 +a 5434 5435 4103 +a 5435 5436 4103 +a 5436 5437 4103 +a 5437 5438 4103 +a 5438 5439 4103 +a 5439 5440 4103 +a 5440 5441 4103 +a 5441 5442 4103 +a 5442 5443 4103 +a 5443 5444 4103 +a 5444 5445 4103 +a 5445 5446 4103 +a 5446 5447 4103 +a 5447 5448 4103 +a 5448 5449 4103 +a 5449 5450 4103 +a 5450 5451 4103 +a 5451 5452 4103 +a 5452 5453 4103 +a 5453 5454 4103 +a 5454 5455 4103 +a 5455 5456 4103 +a 5456 5457 4103 +a 5457 5458 4103 +a 5458 5459 4103 +a 5459 5460 4103 +a 5460 5461 4103 +a 5461 5462 4103 +a 5462 5463 4103 +a 5463 5464 4103 +a 5464 5465 4103 +a 5465 5466 4103 +a 5466 5467 4103 +a 5467 5468 4103 +a 5468 5469 4103 +a 5469 5470 4103 +a 5470 5471 4103 +a 5471 5472 4103 +a 5472 5473 4103 +a 5473 5474 4103 +a 5474 5475 4103 +a 5475 5476 4103 +a 5476 5477 4103 +a 5477 5478 4103 +a 5478 5479 4103 +a 5479 5480 4103 +a 5480 5481 4103 +a 5481 5482 4103 +a 5482 5483 4103 +a 5483 5484 4103 +a 5484 5485 4103 +a 5485 5486 4103 +a 5486 5487 4103 +a 5487 5488 4103 +a 5488 5489 4103 +a 5489 5490 4103 +a 5490 5491 4103 +a 5491 5492 4103 +a 5492 5493 4103 +a 5493 5494 4103 +a 5494 5495 4103 +a 5495 5496 4103 +a 5496 5497 4103 +a 5497 5498 4103 +a 5498 5499 4103 +a 5499 5500 4103 +a 5500 5501 4103 +a 5501 5502 4103 +a 5502 5503 4103 +a 5503 5504 4103 +a 5504 5505 4103 +a 5505 5506 4103 +a 5506 5507 4103 +a 5507 5508 4103 +a 5508 5509 4103 +a 5509 5510 4103 +a 5510 5511 4103 +a 5511 5512 4103 +a 5512 5513 4103 +a 5513 5514 4103 +a 5514 5515 4103 +a 5515 5516 4103 +a 5516 5517 4103 +a 5517 5518 4103 +a 5518 5519 4103 +a 5519 5520 4103 +a 5520 5521 4103 +a 5521 5522 4103 +a 5522 5523 4103 +a 5523 5524 4103 +a 5524 5525 4103 +a 5525 5526 4103 +a 5526 5527 4103 +a 5527 5528 4103 +a 5528 5529 4103 +a 5529 5530 4103 +a 5530 5531 4103 +a 5531 5532 4103 +a 5532 5533 4103 +a 5533 5534 4103 +a 5534 5535 4103 +a 5535 5536 4103 +a 5536 5537 4103 +a 5537 5538 4103 +a 5538 5539 4103 +a 5539 5540 4103 +a 5540 5541 4103 +a 5541 5542 4103 +a 5542 5543 4103 +a 5543 5544 4103 +a 5544 5545 4103 +a 5545 5546 4103 +a 5546 5547 4103 +a 5547 5548 4103 +a 5548 5549 4103 +a 5549 5550 4103 +a 5550 5551 4103 +a 5551 5552 4103 +a 5552 5553 4103 +a 5553 5554 4103 +a 5554 5555 4103 +a 5555 5556 4103 +a 5556 5557 4103 +a 5557 5558 4103 +a 5558 5559 4103 +a 5559 5560 4103 +a 5560 5561 4103 +a 5561 5562 4103 +a 5562 5563 4103 +a 5563 5564 4103 +a 5564 5565 4103 +a 5565 5566 4103 +a 5566 5567 4103 +a 5567 5568 4103 +a 5568 5569 4103 +a 5569 5570 4103 +a 5570 5571 4103 +a 5571 5572 4103 +a 5572 5573 4103 +a 5573 5574 4103 +a 5574 5575 4103 +a 5575 5576 4103 +a 5576 5577 4103 +a 5577 5578 4103 +a 5578 5579 4103 +a 5579 5580 4103 +a 5580 5581 4103 +a 5581 5582 4103 +a 5582 5583 4103 +a 5583 5584 4103 +a 5584 5585 4103 +a 5585 5586 4103 +a 5586 5587 4103 +a 5587 5588 4103 +a 5588 5589 4103 +a 5589 5590 4103 +a 5590 5591 4103 +a 5591 5592 4103 +a 5592 5593 4103 +a 5593 5594 4103 +a 5594 5595 4103 +a 5595 5596 4103 +a 5596 5597 4103 +a 5597 5598 4103 +a 5598 5599 4103 +a 5599 5600 4103 +a 5600 5601 4103 +a 5601 5602 4103 +a 5602 5603 4103 +a 5603 5604 4103 +a 5604 5605 4103 +a 5605 5606 4103 +a 5606 5607 4103 +a 5607 5608 4103 +a 5608 5609 4103 +a 5609 5610 4103 +a 5610 5611 4103 +a 5611 5612 4103 +a 5612 5613 4103 +a 5613 5614 4103 +a 5614 5615 4103 +a 5615 5616 4103 +a 5616 5617 4103 +a 5617 5618 4103 +a 5618 5619 4103 +a 5619 5620 4103 +a 5620 5621 4103 +a 5621 5622 4103 +a 5622 5623 4103 +a 5623 5624 4103 +a 5624 5625 4103 +a 5625 5626 4103 +a 5626 5627 4103 +a 5627 5628 4103 +a 5628 5629 4103 +a 5629 5630 4103 +a 5630 5631 4103 +a 5631 5632 4103 +a 5632 5633 4103 +a 5633 5634 4103 +a 5634 5635 4103 +a 5635 5636 4103 +a 5636 5637 4103 +a 5637 5638 4103 +a 5638 5639 4103 +a 5639 5640 4103 +a 5640 5641 4103 +a 5641 5642 4103 +a 5642 5643 4103 +a 5643 5644 4103 +a 5644 5645 4103 +a 5645 5646 4103 +a 5646 5647 4103 +a 5647 5648 4103 +a 5648 5649 4103 +a 5649 5650 4103 +a 5650 5651 4103 +a 5651 5652 4103 +a 5652 5653 4103 +a 5653 5654 4103 +a 5654 5655 4103 +a 5655 5656 4103 +a 5656 5657 4103 +a 5657 5658 4103 +a 5658 5659 4103 +a 5659 5660 4103 +a 5660 5661 4103 +a 5661 5662 4103 +a 5662 5663 4103 +a 5663 5664 4103 +a 5664 5665 4103 +a 5665 5666 4103 +a 5666 5667 4103 +a 5667 5668 4103 +a 5668 5669 4103 +a 5669 5670 4103 +a 5670 5671 4103 +a 5671 5672 4103 +a 5672 5673 4103 +a 5673 5674 4103 +a 5674 5675 4103 +a 5675 5676 4103 +a 5676 5677 4103 +a 5677 5678 4103 +a 5678 5679 4103 +a 5679 5680 4103 +a 5680 5681 4103 +a 5681 5682 4103 +a 5682 5683 4103 +a 5683 5684 4103 +a 5684 5685 4103 +a 5685 5686 4103 +a 5686 5687 4103 +a 5687 5688 4103 +a 5688 5689 4103 +a 5689 5690 4103 +a 5690 5691 4103 +a 5691 5692 4103 +a 5692 5693 4103 +a 5693 5694 4103 +a 5694 5695 4103 +a 5695 5696 4103 +a 5696 5697 4103 +a 5697 5698 4103 +a 5698 5699 4103 +a 5699 5700 4103 +a 5700 5701 4103 +a 5701 5702 4103 +a 5702 5703 4103 +a 5703 5704 4103 +a 5704 5705 4103 +a 5705 5706 4103 +a 5706 5707 4103 +a 5707 5708 4103 +a 5708 5709 4103 +a 5709 5710 4103 +a 5710 5711 4103 +a 5711 5712 4103 +a 5712 5713 4103 +a 5713 5714 4103 +a 5714 5715 4103 +a 5715 5716 4103 +a 5716 5717 4103 +a 5717 5718 4103 +a 5718 5719 4103 +a 5719 5720 4103 +a 5720 5721 4103 +a 5721 5722 4103 +a 5722 5723 4103 +a 5723 5724 4103 +a 5724 5725 4103 +a 5725 5726 4103 +a 5726 5727 4103 +a 5727 5728 4103 +a 5728 5729 4103 +a 5729 5730 4103 +a 5730 5731 4103 +a 5731 5732 4103 +a 5732 5733 4103 +a 5733 5734 4103 +a 5734 5735 4103 +a 5735 5736 4103 +a 5736 5737 4103 +a 5737 5738 4103 +a 5738 5739 4103 +a 5739 5740 4103 +a 5740 5741 4103 +a 5741 5742 4103 +a 5742 5743 4103 +a 5743 5744 4103 +a 5744 5745 4103 +a 5745 5746 4103 +a 5746 5747 4103 +a 5747 5748 4103 +a 5748 5749 4103 +a 5749 5750 4103 +a 5750 5751 4103 +a 5751 5752 4103 +a 5752 5753 4103 +a 5753 5754 4103 +a 5754 5755 4103 +a 5755 5756 4103 +a 5756 5757 4103 +a 5757 5758 4103 +a 5758 5759 4103 +a 5759 5760 4103 +a 5760 5761 4103 +a 5761 5762 4103 +a 5762 5763 4103 +a 5763 5764 4103 +a 5764 5765 4103 +a 5765 5766 4103 +a 5766 5767 4103 +a 5767 5768 4103 +a 5768 5769 4103 +a 5769 5770 4103 +a 5770 5771 4103 +a 5771 5772 4103 +a 5772 5773 4103 +a 5773 5774 4103 +a 5774 5775 4103 +a 5775 5776 4103 +a 5776 5777 4103 +a 5777 5778 4103 +a 5778 5779 4103 +a 5779 5780 4103 +a 5780 5781 4103 +a 5781 5782 4103 +a 5782 5783 4103 +a 5783 5784 4103 +a 5784 5785 4103 +a 5785 5786 4103 +a 5786 5787 4103 +a 5787 5788 4103 +a 5788 5789 4103 +a 5789 5790 4103 +a 5790 5791 4103 +a 5791 5792 4103 +a 5792 5793 4103 +a 5793 5794 4103 +a 5794 5795 4103 +a 5795 5796 4103 +a 5796 5797 4103 +a 5797 5798 4103 +a 5798 5799 4103 +a 5799 5800 4103 +a 5800 5801 4103 +a 5801 5802 4103 +a 5802 5803 4103 +a 5803 5804 4103 +a 5804 5805 4103 +a 5805 5806 4103 +a 5806 5807 4103 +a 5807 5808 4103 +a 5808 5809 4103 +a 5809 5810 4103 +a 5810 5811 4103 +a 5811 5812 4103 +a 5812 5813 4103 +a 5813 5814 4103 +a 5814 5815 4103 +a 5815 5816 4103 +a 5816 5817 4103 +a 5817 5818 4103 +a 5818 5819 4103 +a 5819 5820 4103 +a 5820 5821 4103 +a 5821 5822 4103 +a 5822 5823 4103 +a 5823 5824 4103 +a 5824 5825 4103 +a 5825 5826 4103 +a 5826 5827 4103 +a 5827 5828 4103 +a 5828 5829 4103 +a 5829 5830 4103 +a 5830 5831 4103 +a 5831 5832 4103 +a 5832 5833 4103 +a 5833 5834 4103 +a 5834 5835 4103 +a 5835 5836 4103 +a 5836 5837 4103 +a 5837 5838 4103 +a 5838 5839 4103 +a 5839 5840 4103 +a 5840 5841 4103 +a 5841 5842 4103 +a 5842 5843 4103 +a 5843 5844 4103 +a 5844 5845 4103 +a 5845 5846 4103 +a 5846 5847 4103 +a 5847 5848 4103 +a 5848 5849 4103 +a 5849 5850 4103 +a 5850 5851 4103 +a 5851 5852 4103 +a 5852 5853 4103 +a 5853 5854 4103 +a 5854 5855 4103 +a 5855 5856 4103 +a 5856 5857 4103 +a 5857 5858 4103 +a 5858 5859 4103 +a 5859 5860 4103 +a 5860 5861 4103 +a 5861 5862 4103 +a 5862 5863 4103 +a 5863 5864 4103 +a 5864 5865 4103 +a 5865 5866 4103 +a 5866 5867 4103 +a 5867 5868 4103 +a 5868 5869 4103 +a 5869 5870 4103 +a 5870 5871 4103 +a 5871 5872 4103 +a 5872 5873 4103 +a 5873 5874 4103 +a 5874 5875 4103 +a 5875 5876 4103 +a 5876 5877 4103 +a 5877 5878 4103 +a 5878 5879 4103 +a 5879 5880 4103 +a 5880 5881 4103 +a 5881 5882 4103 +a 5882 5883 4103 +a 5883 5884 4103 +a 5884 5885 4103 +a 5885 5886 4103 +a 5886 5887 4103 +a 5887 5888 4103 +a 5888 5889 4103 +a 5889 5890 4103 +a 5890 5891 4103 +a 5891 5892 4103 +a 5892 5893 4103 +a 5893 5894 4103 +a 5894 5895 4103 +a 5895 5896 4103 +a 5896 5897 4103 +a 5897 5898 4103 +a 5898 5899 4103 +a 5899 5900 4103 +a 5900 5901 4103 +a 5901 5902 4103 +a 5902 5903 4103 +a 5903 5904 4103 +a 5904 5905 4103 +a 5905 5906 4103 +a 5906 5907 4103 +a 5907 5908 4103 +a 5908 5909 4103 +a 5909 5910 4103 +a 5910 5911 4103 +a 5911 5912 4103 +a 5912 5913 4103 +a 5913 5914 4103 +a 5914 5915 4103 +a 5915 5916 4103 +a 5916 5917 4103 +a 5917 5918 4103 +a 5918 5919 4103 +a 5919 5920 4103 +a 5920 5921 4103 +a 5921 5922 4103 +a 5922 5923 4103 +a 5923 5924 4103 +a 5924 5925 4103 +a 5925 5926 4103 +a 5926 5927 4103 +a 5927 5928 4103 +a 5928 5929 4103 +a 5929 5930 4103 +a 5930 5931 4103 +a 5931 5932 4103 +a 5932 5933 4103 +a 5933 5934 4103 +a 5934 5935 4103 +a 5935 5936 4103 +a 5936 5937 4103 +a 5937 5938 4103 +a 5938 5939 4103 +a 5939 5940 4103 +a 5940 5941 4103 +a 5941 5942 4103 +a 5942 5943 4103 +a 5943 5944 4103 +a 5944 5945 4103 +a 5945 5946 4103 +a 5946 5947 4103 +a 5947 5948 4103 +a 5948 5949 4103 +a 5949 5950 4103 +a 5950 5951 4103 +a 5951 5952 4103 +a 5952 5953 4103 +a 5953 5954 4103 +a 5954 5955 4103 +a 5955 5956 4103 +a 5956 5957 4103 +a 5957 5958 4103 +a 5958 5959 4103 +a 5959 5960 4103 +a 5960 5961 4103 +a 5961 5962 4103 +a 5962 5963 4103 +a 5963 5964 4103 +a 5964 5965 4103 +a 5965 5966 4103 +a 5966 5967 4103 +a 5967 5968 4103 +a 5968 5969 4103 +a 5969 5970 4103 +a 5970 5971 4103 +a 5971 5972 4103 +a 5972 5973 4103 +a 5973 5974 4103 +a 5974 5975 4103 +a 5975 5976 4103 +a 5976 5977 4103 +a 5977 5978 4103 +a 5978 5979 4103 +a 5979 5980 4103 +a 5980 5981 4103 +a 5981 5982 4103 +a 5982 5983 4103 +a 5983 5984 4103 +a 5984 5985 4103 +a 5985 5986 4103 +a 5986 5987 4103 +a 5987 5988 4103 +a 5988 5989 4103 +a 5989 5990 4103 +a 5990 5991 4103 +a 5991 5992 4103 +a 5992 5993 4103 +a 5993 5994 4103 +a 5994 5995 4103 +a 5995 5996 4103 +a 5996 5997 4103 +a 5997 5998 4103 +a 5998 5999 4103 +a 5999 6000 4103 +a 6000 6001 4103 +a 6001 6002 4103 +a 6002 6003 4103 +a 6003 6004 4103 +a 6004 6005 4103 +a 6005 6006 4103 +a 6006 6007 4103 +a 6007 6008 4103 +a 6008 6009 4103 +a 6009 6010 4103 +a 6010 6011 4103 +a 6011 6012 4103 +a 6012 6013 4103 +a 6013 6014 4103 +a 6014 6015 4103 +a 6015 6016 4103 +a 6016 6017 4103 +a 6017 6018 4103 +a 6018 6019 4103 +a 6019 6020 4103 +a 6020 6021 4103 +a 6021 6022 4103 +a 6022 6023 4103 +a 6023 6024 4103 +a 6024 6025 4103 +a 6025 6026 4103 +a 6026 6027 4103 +a 6027 6028 4103 +a 6028 6029 4103 +a 6029 6030 4103 +a 6030 6031 4103 +a 6031 6032 4103 +a 6032 6033 4103 +a 6033 6034 4103 +a 6034 6035 4103 +a 6035 6036 4103 +a 6036 6037 4103 +a 6037 6038 4103 +a 6038 6039 4103 +a 6039 6040 4103 +a 6040 6041 4103 +a 6041 6042 4103 +a 6042 6043 4103 +a 6043 6044 4103 +a 6044 6045 4103 +a 6045 6046 4103 +a 6046 6047 4103 +a 6047 6048 4103 +a 6048 6049 4103 +a 6049 6050 4103 +a 6050 6051 4103 +a 6051 6052 4103 +a 6052 6053 4103 +a 6053 6054 4103 +a 6054 6055 4103 +a 6055 6056 4103 +a 6056 6057 4103 +a 6057 6058 4103 +a 6058 6059 4103 +a 6059 6060 4103 +a 6060 6061 4103 +a 6061 6062 4103 +a 6062 6063 4103 +a 6063 6064 4103 +a 6064 6065 4103 +a 6065 6066 4103 +a 6066 6067 4103 +a 6067 6068 4103 +a 6068 6069 4103 +a 6069 6070 4103 +a 6070 6071 4103 +a 6071 6072 4103 +a 6072 6073 4103 +a 6073 6074 4103 +a 6074 6075 4103 +a 6075 6076 4103 +a 6076 6077 4103 +a 6077 6078 4103 +a 6078 6079 4103 +a 6079 6080 4103 +a 6080 6081 4103 +a 6081 6082 4103 +a 6082 6083 4103 +a 6083 6084 4103 +a 6084 6085 4103 +a 6085 6086 4103 +a 6086 6087 4103 +a 6087 6088 4103 +a 6088 6089 4103 +a 6089 6090 4103 +a 6090 6091 4103 +a 6091 6092 4103 +a 6092 6093 4103 +a 6093 6094 4103 +a 6094 6095 4103 +a 6095 6096 4103 +a 6096 6097 4103 +a 6097 6098 4103 +a 6098 6099 4103 +a 6099 6100 4103 +a 6100 6101 4103 +a 6101 6102 4103 +a 6102 6103 4103 +a 6103 6104 4103 +a 6104 6105 4103 +a 6105 6106 4103 +a 6106 6107 4103 +a 6107 6108 4103 +a 6108 6109 4103 +a 6109 6110 4103 +a 6110 6111 4103 +a 6111 6112 4103 +a 6112 6113 4103 +a 6113 6114 4103 +a 6114 6115 4103 +a 6115 6116 4103 +a 6116 6117 4103 +a 6117 6118 4103 +a 6118 6119 4103 +a 6119 6120 4103 +a 6120 6121 4103 +a 6121 6122 4103 +a 6122 6123 4103 +a 6123 6124 4103 +a 6124 6125 4103 +a 6125 6126 4103 +a 6126 6127 4103 +a 6127 6128 4103 +a 6128 6129 4103 +a 6129 6130 4103 +a 6130 6131 4103 +a 6131 6132 4103 +a 6132 6133 4103 +a 6133 6134 4103 +a 6134 6135 4103 +a 6135 6136 4103 +a 6136 6137 4103 +a 6137 6138 4103 +a 6138 6139 4103 +a 6139 6140 4103 +a 6140 6141 4103 +a 6141 6142 4103 +a 6142 6143 4103 +a 6143 6144 4103 +a 6144 6145 4103 +a 6145 6146 4103 +a 6146 6147 4103 +a 6147 6148 4103 +a 6148 6149 4103 +a 6149 6150 4103 +a 6150 6151 4103 +a 6151 6152 4103 +a 6152 6153 4103 +a 6153 6154 4103 +a 6154 6155 4103 +a 6155 6156 4103 +a 6156 6157 4103 +a 6157 6158 4103 +a 6158 6159 4103 +a 6159 6160 4103 +a 6160 6161 4103 +a 6161 6162 4103 +a 6162 6163 4103 +a 6163 6164 4103 +a 6164 6165 4103 +a 6165 6166 4103 +a 6166 6167 4103 +a 6167 6168 4103 +a 6168 6169 4103 +a 6169 6170 4103 +a 6170 6171 4103 +a 6171 6172 4103 +a 6172 6173 4103 +a 6173 6174 4103 +a 6174 6175 4103 +a 6175 6176 4103 +a 6176 6177 4103 +a 6177 6178 4103 +a 6178 6179 4103 +a 6179 6180 4103 +a 6180 6181 4103 +a 6181 6182 4103 +a 6182 6183 4103 +a 6183 6184 4103 +a 6184 6185 4103 +a 6185 6186 4103 +a 6186 6187 4103 +a 6187 6188 4103 +a 6188 6189 4103 +a 6189 6190 4103 +a 6190 6191 4103 +a 6191 6192 4103 +a 6192 6193 4103 +a 6193 6194 4103 +a 6194 6195 4103 +a 6195 6196 4103 +a 6196 6197 4103 +a 6197 6198 4103 +a 6198 6199 4103 +a 6199 6200 4103 +a 6200 6201 4103 +a 6201 6202 4103 +a 6202 6203 4103 +a 6203 6204 4103 +a 6204 6205 4103 +a 6205 6206 4103 +a 6206 6207 4103 +a 6207 6208 4103 +a 6208 6209 4103 +a 6209 6210 4103 +a 6210 6211 4103 +a 6211 6212 4103 +a 6212 6213 4103 +a 6213 6214 4103 +a 6214 6215 4103 +a 6215 6216 4103 +a 6216 6217 4103 +a 6217 6218 4103 +a 6218 6219 4103 +a 6219 6220 4103 +a 6220 6221 4103 +a 6221 6222 4103 +a 6222 6223 4103 +a 6223 6224 4103 +a 6224 6225 4103 +a 6225 6226 4103 +a 6226 6227 4103 +a 6227 6228 4103 +a 6228 6229 4103 +a 6229 6230 4103 +a 6230 6231 4103 +a 6231 6232 4103 +a 6232 6233 4103 +a 6233 6234 4103 +a 6234 6235 4103 +a 6235 6236 4103 +a 6236 6237 4103 +a 6237 6238 4103 +a 6238 6239 4103 +a 6239 6240 4103 +a 6240 6241 4103 +a 6241 6242 4103 +a 6242 6243 4103 +a 6243 6244 4103 +a 6244 6245 4103 +a 6245 6246 4103 +a 6246 6247 4103 +a 6247 6248 4103 +a 6248 6249 4103 +a 6249 6250 4103 +a 6250 6251 4103 +a 6251 6252 4103 +a 6252 6253 4103 +a 6253 6254 4103 +a 6254 6255 4103 +a 6255 6256 4103 +a 6256 6257 4103 +a 6257 6258 4103 +a 6258 6259 4103 +a 6259 6260 4103 +a 6260 6261 4103 +a 6261 6262 4103 +a 6262 6263 4103 +a 6263 6264 4103 +a 6264 6265 4103 +a 6265 6266 4103 +a 6266 6267 4103 +a 6267 6268 4103 +a 6268 6269 4103 +a 6269 6270 4103 +a 6270 6271 4103 +a 6271 6272 4103 +a 6272 6273 4103 +a 6273 6274 4103 +a 6274 6275 4103 +a 6275 6276 4103 +a 6276 6277 4103 +a 6277 6278 4103 +a 6278 6279 4103 +a 6279 6280 4103 +a 6280 6281 4103 +a 6281 6282 4103 +a 6282 6283 4103 +a 6283 6284 4103 +a 6284 6285 4103 +a 6285 6286 4103 +a 6286 6287 4103 +a 6287 6288 4103 +a 6288 6289 4103 +a 6289 6290 4103 +a 6290 6291 4103 +a 6291 6292 4103 +a 6292 6293 4103 +a 6293 6294 4103 +a 6294 6295 4103 +a 6295 6296 4103 +a 6296 6297 4103 +a 6297 6298 4103 +a 6298 6299 4103 +a 6299 6300 4103 +a 6300 6301 4103 +a 6301 6302 4103 +a 6302 6303 4103 +a 6303 6304 4103 +a 6304 6305 4103 +a 6305 6306 4103 +a 6306 6307 4103 +a 6307 6308 4103 +a 6308 6309 4103 +a 6309 6310 4103 +a 6310 6311 4103 +a 6311 6312 4103 +a 6312 6313 4103 +a 6313 6314 4103 +a 6314 6315 4103 +a 6315 6316 4103 +a 6316 6317 4103 +a 6317 6318 4103 +a 6318 6319 4103 +a 6319 6320 4103 +a 6320 6321 4103 +a 6321 6322 4103 +a 6322 6323 4103 +a 6323 6324 4103 +a 6324 6325 4103 +a 6325 6326 4103 +a 6326 6327 4103 +a 6327 6328 4103 +a 6328 6329 4103 +a 6329 6330 4103 +a 6330 6331 4103 +a 6331 6332 4103 +a 6332 6333 4103 +a 6333 6334 4103 +a 6334 6335 4103 +a 6335 6336 4103 +a 6336 6337 4103 +a 6337 6338 4103 +a 6338 6339 4103 +a 6339 6340 4103 +a 6340 6341 4103 +a 6341 6342 4103 +a 6342 6343 4103 +a 6343 6344 4103 +a 6344 6345 4103 +a 6345 6346 4103 +a 6346 6347 4103 +a 6347 6348 4103 +a 6348 6349 4103 +a 6349 6350 4103 +a 6350 6351 4103 +a 6351 6352 4103 +a 6352 6353 4103 +a 6353 6354 4103 +a 6354 6355 4103 +a 6355 6356 4103 +a 6356 6357 4103 +a 6357 6358 4103 +a 6358 6359 4103 +a 6359 6360 4103 +a 6360 6361 4103 +a 6361 6362 4103 +a 6362 6363 4103 +a 6363 6364 4103 +a 6364 6365 4103 +a 6365 6366 4103 +a 6366 6367 4103 +a 6367 6368 4103 +a 6368 6369 4103 +a 6369 6370 4103 +a 6370 6371 4103 +a 6371 6372 4103 +a 6372 6373 4103 +a 6373 6374 4103 +a 6374 6375 4103 +a 6375 6376 4103 +a 6376 6377 4103 +a 6377 6378 4103 +a 6378 6379 4103 +a 6379 6380 4103 +a 6380 6381 4103 +a 6381 6382 4103 +a 6382 6383 4103 +a 6383 6384 4103 +a 6384 6385 4103 +a 6385 6386 4103 +a 6386 6387 4103 +a 6387 6388 4103 +a 6388 6389 4103 +a 6389 6390 4103 +a 6390 6391 4103 +a 6391 6392 4103 +a 6392 6393 4103 +a 6393 6394 4103 +a 6394 6395 4103 +a 6395 6396 4103 +a 6396 6397 4103 +a 6397 6398 4103 +a 6398 6399 4103 +a 6399 6400 4103 +a 6400 6401 4103 +a 6401 6402 4103 +a 6402 6403 4103 +a 6403 6404 4103 +a 6404 6405 4103 +a 6405 6406 4103 +a 6406 6407 4103 +a 6407 6408 4103 +a 6408 6409 4103 +a 6409 6410 4103 +a 6410 6411 4103 +a 6411 6412 4103 +a 6412 6413 4103 +a 6413 6414 4103 +a 6414 6415 4103 +a 6415 6416 4103 +a 6416 6417 4103 +a 6417 6418 4103 +a 6418 6419 4103 +a 6419 6420 4103 +a 6420 6421 4103 +a 6421 6422 4103 +a 6422 6423 4103 +a 6423 6424 4103 +a 6424 6425 4103 +a 6425 6426 4103 +a 6426 6427 4103 +a 6427 6428 4103 +a 6428 6429 4103 +a 6429 6430 4103 +a 6430 6431 4103 +a 6431 6432 4103 +a 6432 6433 4103 +a 6433 6434 4103 +a 6434 6435 4103 +a 6435 6436 4103 +a 6436 6437 4103 +a 6437 6438 4103 +a 6438 6439 4103 +a 6439 6440 4103 +a 6440 6441 4103 +a 6441 6442 4103 +a 6442 6443 4103 +a 6443 6444 4103 +a 6444 6445 4103 +a 6445 6446 4103 +a 6446 6447 4103 +a 6447 6448 4103 +a 6448 6449 4103 +a 6449 6450 4103 +a 6450 6451 4103 +a 6451 6452 4103 +a 6452 6453 4103 +a 6453 6454 4103 +a 6454 6455 4103 +a 6455 6456 4103 +a 6456 6457 4103 +a 6457 6458 4103 +a 6458 6459 4103 +a 6459 6460 4103 +a 6460 6461 4103 +a 6461 6462 4103 +a 6462 6463 4103 +a 6463 6464 4103 +a 6464 6465 4103 +a 6465 6466 4103 +a 6466 6467 4103 +a 6467 6468 4103 +a 6468 6469 4103 +a 6469 6470 4103 +a 6470 6471 4103 +a 6471 6472 4103 +a 6472 6473 4103 +a 6473 6474 4103 +a 6474 6475 4103 +a 6475 6476 4103 +a 6476 6477 4103 +a 6477 6478 4103 +a 6478 6479 4103 +a 6479 6480 4103 +a 6480 6481 4103 +a 6481 6482 4103 +a 6482 6483 4103 +a 6483 6484 4103 +a 6484 6485 4103 +a 6485 6486 4103 +a 6486 6487 4103 +a 6487 6488 4103 +a 6488 6489 4103 +a 6489 6490 4103 +a 6490 6491 4103 +a 6491 6492 4103 +a 6492 6493 4103 +a 6493 6494 4103 +a 6494 6495 4103 +a 6495 6496 4103 +a 6496 6497 4103 +a 6497 6498 4103 +a 6498 6499 4103 +a 6499 6500 4103 +a 6500 6501 4103 +a 6501 6502 4103 +a 6502 6503 4103 +a 6503 6504 4103 +a 6504 6505 4103 +a 6505 6506 4103 +a 6506 6507 4103 +a 6507 6508 4103 +a 6508 6509 4103 +a 6509 6510 4103 +a 6510 6511 4103 +a 6511 6512 4103 +a 6512 6513 4103 +a 6513 6514 4103 +a 6514 6515 4103 +a 6515 6516 4103 +a 6516 6517 4103 +a 6517 6518 4103 +a 6518 6519 4103 +a 6519 6520 4103 +a 6520 6521 4103 +a 6521 6522 4103 +a 6522 6523 4103 +a 6523 6524 4103 +a 6524 6525 4103 +a 6525 6526 4103 +a 6526 6527 4103 +a 6527 6528 4103 +a 6528 6529 4103 +a 6529 6530 4103 +a 6530 6531 4103 +a 6531 6532 4103 +a 6532 6533 4103 +a 6533 6534 4103 +a 6534 6535 4103 +a 6535 6536 4103 +a 6536 6537 4103 +a 6537 6538 4103 +a 6538 6539 4103 +a 6539 6540 4103 +a 6540 6541 4103 +a 6541 6542 4103 +a 6542 6543 4103 +a 6543 6544 4103 +a 6544 6545 4103 +a 6545 6546 4103 +a 6546 6547 4103 +a 6547 6548 4103 +a 6548 6549 4103 +a 6549 6550 4103 +a 6550 6551 4103 +a 6551 6552 4103 +a 6552 6553 4103 +a 6553 6554 4103 +a 6554 6555 4103 +a 6555 6556 4103 +a 6556 6557 4103 +a 6557 6558 4103 +a 6558 6559 4103 +a 6559 6560 4103 +a 6560 6561 4103 +a 6561 6562 4103 +a 6562 6563 4103 +a 6563 6564 4103 +a 6564 6565 4103 +a 6565 6566 4103 +a 6566 6567 4103 +a 6567 6568 4103 +a 6568 6569 4103 +a 6569 6570 4103 +a 6570 6571 4103 +a 6571 6572 4103 +a 6572 6573 4103 +a 6573 6574 4103 +a 6574 6575 4103 +a 6575 6576 4103 +a 6576 6577 4103 +a 6577 6578 4103 +a 6578 6579 4103 +a 6579 6580 4103 +a 6580 6581 4103 +a 6581 6582 4103 +a 6582 6583 4103 +a 6583 6584 4103 +a 6584 6585 4103 +a 6585 6586 4103 +a 6586 6587 4103 +a 6587 6588 4103 +a 6588 6589 4103 +a 6589 6590 4103 +a 6590 6591 4103 +a 6591 6592 4103 +a 6592 6593 4103 +a 6593 6594 4103 +a 6594 6595 4103 +a 6595 6596 4103 +a 6596 6597 4103 +a 6597 6598 4103 +a 6598 6599 4103 +a 6599 6600 4103 +a 6600 6601 4103 +a 6601 6602 4103 +a 6602 6603 4103 +a 6603 6604 4103 +a 6604 6605 4103 +a 6605 6606 4103 +a 6606 6607 4103 +a 6607 6608 4103 +a 6608 6609 4103 +a 6609 6610 4103 +a 6610 6611 4103 +a 6611 6612 4103 +a 6612 6613 4103 +a 6613 6614 4103 +a 6614 6615 4103 +a 6615 6616 4103 +a 6616 6617 4103 +a 6617 6618 4103 +a 6618 6619 4103 +a 6619 6620 4103 +a 6620 6621 4103 +a 6621 6622 4103 +a 6622 6623 4103 +a 6623 6624 4103 +a 6624 6625 4103 +a 6625 6626 4103 +a 6626 6627 4103 +a 6627 6628 4103 +a 6628 6629 4103 +a 6629 6630 4103 +a 6630 6631 4103 +a 6631 6632 4103 +a 6632 6633 4103 +a 6633 6634 4103 +a 6634 6635 4103 +a 6635 6636 4103 +a 6636 6637 4103 +a 6637 6638 4103 +a 6638 6639 4103 +a 6639 6640 4103 +a 6640 6641 4103 +a 6641 6642 4103 +a 6642 6643 4103 +a 6643 6644 4103 +a 6644 6645 4103 +a 6645 6646 4103 +a 6646 6647 4103 +a 6647 6648 4103 +a 6648 6649 4103 +a 6649 6650 4103 +a 6650 6651 4103 +a 6651 6652 4103 +a 6652 6653 4103 +a 6653 6654 4103 +a 6654 6655 4103 +a 6655 6656 4103 +a 6656 6657 4103 +a 6657 6658 4103 +a 6658 6659 4103 +a 6659 6660 4103 +a 6660 6661 4103 +a 6661 6662 4103 +a 6662 6663 4103 +a 6663 6664 4103 +a 6664 6665 4103 +a 6665 6666 4103 +a 6666 6667 4103 +a 6667 6668 4103 +a 6668 6669 4103 +a 6669 6670 4103 +a 6670 6671 4103 +a 6671 6672 4103 +a 6672 6673 4103 +a 6673 6674 4103 +a 6674 6675 4103 +a 6675 6676 4103 +a 6676 6677 4103 +a 6677 6678 4103 +a 6678 6679 4103 +a 6679 6680 4103 +a 6680 6681 4103 +a 6681 6682 4103 +a 6682 6683 4103 +a 6683 6684 4103 +a 6684 6685 4103 +a 6685 6686 4103 +a 6686 6687 4103 +a 6687 6688 4103 +a 6688 6689 4103 +a 6689 6690 4103 +a 6690 6691 4103 +a 6691 6692 4103 +a 6692 6693 4103 +a 6693 6694 4103 +a 6694 6695 4103 +a 6695 6696 4103 +a 6696 6697 4103 +a 6697 6698 4103 +a 6698 6699 4103 +a 6699 6700 4103 +a 6700 6701 4103 +a 6701 6702 4103 +a 6702 6703 4103 +a 6703 6704 4103 +a 6704 6705 4103 +a 6705 6706 4103 +a 6706 6707 4103 +a 6707 6708 4103 +a 6708 6709 4103 +a 6709 6710 4103 +a 6710 6711 4103 +a 6711 6712 4103 +a 6712 6713 4103 +a 6713 6714 4103 +a 6714 6715 4103 +a 6715 6716 4103 +a 6716 6717 4103 +a 6717 6718 4103 +a 6718 6719 4103 +a 6719 6720 4103 +a 6720 6721 4103 +a 6721 6722 4103 +a 6722 6723 4103 +a 6723 6724 4103 +a 6724 6725 4103 +a 6725 6726 4103 +a 6726 6727 4103 +a 6727 6728 4103 +a 6728 6729 4103 +a 6729 6730 4103 +a 6730 6731 4103 +a 6731 6732 4103 +a 6732 6733 4103 +a 6733 6734 4103 +a 6734 6735 4103 +a 6735 6736 4103 +a 6736 6737 4103 +a 6737 6738 4103 +a 6738 6739 4103 +a 6739 6740 4103 +a 6740 6741 4103 +a 6741 6742 4103 +a 6742 6743 4103 +a 6743 6744 4103 +a 6744 6745 4103 +a 6745 6746 4103 +a 6746 6747 4103 +a 6747 6748 4103 +a 6748 6749 4103 +a 6749 6750 4103 +a 6750 6751 4103 +a 6751 6752 4103 +a 6752 6753 4103 +a 6753 6754 4103 +a 6754 6755 4103 +a 6755 6756 4103 +a 6756 6757 4103 +a 6757 6758 4103 +a 6758 6759 4103 +a 6759 6760 4103 +a 6760 6761 4103 +a 6761 6762 4103 +a 6762 6763 4103 +a 6763 6764 4103 +a 6764 6765 4103 +a 6765 6766 4103 +a 6766 6767 4103 +a 6767 6768 4103 +a 6768 6769 4103 +a 6769 6770 4103 +a 6770 6771 4103 +a 6771 6772 4103 +a 6772 6773 4103 +a 6773 6774 4103 +a 6774 6775 4103 +a 6775 6776 4103 +a 6776 6777 4103 +a 6777 6778 4103 +a 6778 6779 4103 +a 6779 6780 4103 +a 6780 6781 4103 +a 6781 6782 4103 +a 6782 6783 4103 +a 6783 6784 4103 +a 6784 6785 4103 +a 6785 6786 4103 +a 6786 6787 4103 +a 6787 6788 4103 +a 6788 6789 4103 +a 6789 6790 4103 +a 6790 6791 4103 +a 6791 6792 4103 +a 6792 6793 4103 +a 6793 6794 4103 +a 6794 6795 4103 +a 6795 6796 4103 +a 6796 6797 4103 +a 6797 6798 4103 +a 6798 6799 4103 +a 6799 6800 4103 +a 6800 6801 4103 +a 6801 6802 4103 +a 6802 6803 4103 +a 6803 6804 4103 +a 6804 6805 4103 +a 6805 6806 4103 +a 6806 6807 4103 +a 6807 6808 4103 +a 6808 6809 4103 +a 6809 6810 4103 +a 6810 6811 4103 +a 6811 6812 4103 +a 6812 6813 4103 +a 6813 6814 4103 +a 6814 6815 4103 +a 6815 6816 4103 +a 6816 6817 4103 +a 6817 6818 4103 +a 6818 6819 4103 +a 6819 6820 4103 +a 6820 6821 4103 +a 6821 6822 4103 +a 6822 6823 4103 +a 6823 6824 4103 +a 6824 6825 4103 +a 6825 6826 4103 +a 6826 6827 4103 +a 6827 6828 4103 +a 6828 6829 4103 +a 6829 6830 4103 +a 6830 6831 4103 +a 6831 6832 4103 +a 6832 6833 4103 +a 6833 6834 4103 +a 6834 6835 4103 +a 6835 6836 4103 +a 6836 6837 4103 +a 6837 6838 4103 +a 6838 6839 4103 +a 6839 6840 4103 +a 6840 6841 4103 +a 6841 6842 4103 +a 6842 6843 4103 +a 6843 6844 4103 +a 6844 6845 4103 +a 6845 6846 4103 +a 6846 6847 4103 +a 6847 6848 4103 +a 6848 6849 4103 +a 6849 6850 4103 +a 6850 6851 4103 +a 6851 6852 4103 +a 6852 6853 4103 +a 6853 6854 4103 +a 6854 6855 4103 +a 6855 6856 4103 +a 6856 6857 4103 +a 6857 6858 4103 +a 6858 6859 4103 +a 6859 6860 4103 +a 6860 6861 4103 +a 6861 6862 4103 +a 6862 6863 4103 +a 6863 6864 4103 +a 6864 6865 4103 +a 6865 6866 4103 +a 6866 6867 4103 +a 6867 6868 4103 +a 6868 6869 4103 +a 6869 6870 4103 +a 6870 6871 4103 +a 6871 6872 4103 +a 6872 6873 4103 +a 6873 6874 4103 +a 6874 6875 4103 +a 6875 6876 4103 +a 6876 6877 4103 +a 6877 6878 4103 +a 6878 6879 4103 +a 6879 6880 4103 +a 6880 6881 4103 +a 6881 6882 4103 +a 6882 6883 4103 +a 6883 6884 4103 +a 6884 6885 4103 +a 6885 6886 4103 +a 6886 6887 4103 +a 6887 6888 4103 +a 6888 6889 4103 +a 6889 6890 4103 +a 6890 6891 4103 +a 6891 6892 4103 +a 6892 6893 4103 +a 6893 6894 4103 +a 6894 6895 4103 +a 6895 6896 4103 +a 6896 6897 4103 +a 6897 6898 4103 +a 6898 6899 4103 +a 6899 6900 4103 +a 6900 6901 4103 +a 6901 6902 4103 +a 6902 6903 4103 +a 6903 6904 4103 +a 6904 6905 4103 +a 6905 6906 4103 +a 6906 6907 4103 +a 6907 6908 4103 +a 6908 6909 4103 +a 6909 6910 4103 +a 6910 6911 4103 +a 6911 6912 4103 +a 6912 6913 4103 +a 6913 6914 4103 +a 6914 6915 4103 +a 6915 6916 4103 +a 6916 6917 4103 +a 6917 6918 4103 +a 6918 6919 4103 +a 6919 6920 4103 +a 6920 6921 4103 +a 6921 6922 4103 +a 6922 6923 4103 +a 6923 6924 4103 +a 6924 6925 4103 +a 6925 6926 4103 +a 6926 6927 4103 +a 6927 6928 4103 +a 6928 6929 4103 +a 6929 6930 4103 +a 6930 6931 4103 +a 6931 6932 4103 +a 6932 6933 4103 +a 6933 6934 4103 +a 6934 6935 4103 +a 6935 6936 4103 +a 6936 6937 4103 +a 6937 6938 4103 +a 6938 6939 4103 +a 6939 6940 4103 +a 6940 6941 4103 +a 6941 6942 4103 +a 6942 6943 4103 +a 6943 6944 4103 +a 6944 6945 4103 +a 6945 6946 4103 +a 6946 6947 4103 +a 6947 6948 4103 +a 6948 6949 4103 +a 6949 6950 4103 +a 6950 6951 4103 +a 6951 6952 4103 +a 6952 6953 4103 +a 6953 6954 4103 +a 6954 6955 4103 +a 6955 6956 4103 +a 6956 6957 4103 +a 6957 6958 4103 +a 6958 6959 4103 +a 6959 6960 4103 +a 6960 6961 4103 +a 6961 6962 4103 +a 6962 6963 4103 +a 6963 6964 4103 +a 6964 6965 4103 +a 6965 6966 4103 +a 6966 6967 4103 +a 6967 6968 4103 +a 6968 6969 4103 +a 6969 6970 4103 +a 6970 6971 4103 +a 6971 6972 4103 +a 6972 6973 4103 +a 6973 6974 4103 +a 6974 6975 4103 +a 6975 6976 4103 +a 6976 6977 4103 +a 6977 6978 4103 +a 6978 6979 4103 +a 6979 6980 4103 +a 6980 6981 4103 +a 6981 6982 4103 +a 6982 6983 4103 +a 6983 6984 4103 +a 6984 6985 4103 +a 6985 6986 4103 +a 6986 6987 4103 +a 6987 6988 4103 +a 6988 6989 4103 +a 6989 6990 4103 +a 6990 6991 4103 +a 6991 6992 4103 +a 6992 6993 4103 +a 6993 6994 4103 +a 6994 6995 4103 +a 6995 6996 4103 +a 6996 6997 4103 +a 6997 6998 4103 +a 6998 6999 4103 +a 6999 7000 4103 +a 7000 7001 4103 +a 7001 7002 4103 +a 7002 7003 4103 +a 7003 7004 4103 +a 7004 7005 4103 +a 7005 7006 4103 +a 7006 7007 4103 +a 7007 7008 4103 +a 7008 7009 4103 +a 7009 7010 4103 +a 7010 7011 4103 +a 7011 7012 4103 +a 7012 7013 4103 +a 7013 7014 4103 +a 7014 7015 4103 +a 7015 7016 4103 +a 7016 7017 4103 +a 7017 7018 4103 +a 7018 7019 4103 +a 7019 7020 4103 +a 7020 7021 4103 +a 7021 7022 4103 +a 7022 7023 4103 +a 7023 7024 4103 +a 7024 7025 4103 +a 7025 7026 4103 +a 7026 7027 4103 +a 7027 7028 4103 +a 7028 7029 4103 +a 7029 7030 4103 +a 7030 7031 4103 +a 7031 7032 4103 +a 7032 7033 4103 +a 7033 7034 4103 +a 7034 7035 4103 +a 7035 7036 4103 +a 7036 7037 4103 +a 7037 7038 4103 +a 7038 7039 4103 +a 7039 7040 4103 +a 7040 7041 4103 +a 7041 7042 4103 +a 7042 7043 4103 +a 7043 7044 4103 +a 7044 7045 4103 +a 7045 7046 4103 +a 7046 7047 4103 +a 7047 7048 4103 +a 7048 7049 4103 +a 7049 7050 4103 +a 7050 7051 4103 +a 7051 7052 4103 +a 7052 7053 4103 +a 7053 7054 4103 +a 7054 7055 4103 +a 7055 7056 4103 +a 7056 7057 4103 +a 7057 7058 4103 +a 7058 7059 4103 +a 7059 7060 4103 +a 7060 7061 4103 +a 7061 7062 4103 +a 7062 7063 4103 +a 7063 7064 4103 +a 7064 7065 4103 +a 7065 7066 4103 +a 7066 7067 4103 +a 7067 7068 4103 +a 7068 7069 4103 +a 7069 7070 4103 +a 7070 7071 4103 +a 7071 7072 4103 +a 7072 7073 4103 +a 7073 7074 4103 +a 7074 7075 4103 +a 7075 7076 4103 +a 7076 7077 4103 +a 7077 7078 4103 +a 7078 7079 4103 +a 7079 7080 4103 +a 7080 7081 4103 +a 7081 7082 4103 +a 7082 7083 4103 +a 7083 7084 4103 +a 7084 7085 4103 +a 7085 7086 4103 +a 7086 7087 4103 +a 7087 7088 4103 +a 7088 7089 4103 +a 7089 7090 4103 +a 7090 7091 4103 +a 7091 7092 4103 +a 7092 7093 4103 +a 7093 7094 4103 +a 7094 7095 4103 +a 7095 7096 4103 +a 7096 7097 4103 +a 7097 7098 4103 +a 7098 7099 4103 +a 7099 7100 4103 +a 7100 7101 4103 +a 7101 7102 4103 +a 7102 7103 4103 +a 7103 7104 4103 +a 7104 7105 4103 +a 7105 7106 4103 +a 7106 7107 4103 +a 7107 7108 4103 +a 7108 7109 4103 +a 7109 7110 4103 +a 7110 7111 4103 +a 7111 7112 4103 +a 7112 7113 4103 +a 7113 7114 4103 +a 7114 7115 4103 +a 7115 7116 4103 +a 7116 7117 4103 +a 7117 7118 4103 +a 7118 7119 4103 +a 7119 7120 4103 +a 7120 7121 4103 +a 7121 7122 4103 +a 7122 7123 4103 +a 7123 7124 4103 +a 7124 7125 4103 +a 7125 7126 4103 +a 7126 7127 4103 +a 7127 7128 4103 +a 7128 7129 4103 +a 7129 7130 4103 +a 7130 7131 4103 +a 7131 7132 4103 +a 7132 7133 4103 +a 7133 7134 4103 +a 7134 7135 4103 +a 7135 7136 4103 +a 7136 7137 4103 +a 7137 7138 4103 +a 7138 7139 4103 +a 7139 7140 4103 +a 7140 7141 4103 +a 7141 7142 4103 +a 7142 7143 4103 +a 7143 7144 4103 +a 7144 7145 4103 +a 7145 7146 4103 +a 7146 7147 4103 +a 7147 7148 4103 +a 7148 7149 4103 +a 7149 7150 4103 +a 7150 7151 4103 +a 7151 7152 4103 +a 7152 7153 4103 +a 7153 7154 4103 +a 7154 7155 4103 +a 7155 7156 4103 +a 7156 7157 4103 +a 7157 7158 4103 +a 7158 7159 4103 +a 7159 7160 4103 +a 7160 7161 4103 +a 7161 7162 4103 +a 7162 7163 4103 +a 7163 7164 4103 +a 7164 7165 4103 +a 7165 7166 4103 +a 7166 7167 4103 +a 7167 7168 4103 +a 7168 7169 4103 +a 7169 7170 4103 +a 7170 7171 4103 +a 7171 7172 4103 +a 7172 7173 4103 +a 7173 7174 4103 +a 7174 7175 4103 +a 7175 7176 4103 +a 7176 7177 4103 +a 7177 7178 4103 +a 7178 7179 4103 +a 7179 7180 4103 +a 7180 7181 4103 +a 7181 7182 4103 +a 7182 7183 4103 +a 7183 7184 4103 +a 7184 7185 4103 +a 7185 7186 4103 +a 7186 7187 4103 +a 7187 7188 4103 +a 7188 7189 4103 +a 7189 7190 4103 +a 7190 7191 4103 +a 7191 7192 4103 +a 7192 7193 4103 +a 7193 7194 4103 +a 7194 7195 4103 +a 7195 7196 4103 +a 7196 7197 4103 +a 7197 7198 4103 +a 7198 7199 4103 +a 7199 7200 4103 +a 7200 7201 4103 +a 7201 7202 4103 +a 7202 7203 4103 +a 7203 7204 4103 +a 7204 7205 4103 +a 7205 7206 4103 +a 7206 7207 4103 +a 7207 7208 4103 +a 7208 7209 4103 +a 7209 7210 4103 +a 7210 7211 4103 +a 7211 7212 4103 +a 7212 7213 4103 +a 7213 7214 4103 +a 7214 7215 4103 +a 7215 7216 4103 +a 7216 7217 4103 +a 7217 7218 4103 +a 7218 7219 4103 +a 7219 7220 4103 +a 7220 7221 4103 +a 7221 7222 4103 +a 7222 7223 4103 +a 7223 7224 4103 +a 7224 7225 4103 +a 7225 7226 4103 +a 7226 7227 4103 +a 7227 7228 4103 +a 7228 7229 4103 +a 7229 7230 4103 +a 7230 7231 4103 +a 7231 7232 4103 +a 7232 7233 4103 +a 7233 7234 4103 +a 7234 7235 4103 +a 7235 7236 4103 +a 7236 7237 4103 +a 7237 7238 4103 +a 7238 7239 4103 +a 7239 7240 4103 +a 7240 7241 4103 +a 7241 7242 4103 +a 7242 7243 4103 +a 7243 7244 4103 +a 7244 7245 4103 +a 7245 7246 4103 +a 7246 7247 4103 +a 7247 7248 4103 +a 7248 7249 4103 +a 7249 7250 4103 +a 7250 7251 4103 +a 7251 7252 4103 +a 7252 7253 4103 +a 7253 7254 4103 +a 7254 7255 4103 +a 7255 7256 4103 +a 7256 7257 4103 +a 7257 7258 4103 +a 7258 7259 4103 +a 7259 7260 4103 +a 7260 7261 4103 +a 7261 7262 4103 +a 7262 7263 4103 +a 7263 7264 4103 +a 7264 7265 4103 +a 7265 7266 4103 +a 7266 7267 4103 +a 7267 7268 4103 +a 7268 7269 4103 +a 7269 7270 4103 +a 7270 7271 4103 +a 7271 7272 4103 +a 7272 7273 4103 +a 7273 7274 4103 +a 7274 7275 4103 +a 7275 7276 4103 +a 7276 7277 4103 +a 7277 7278 4103 +a 7278 7279 4103 +a 7279 7280 4103 +a 7280 7281 4103 +a 7281 7282 4103 +a 7282 7283 4103 +a 7283 7284 4103 +a 7284 7285 4103 +a 7285 7286 4103 +a 7286 7287 4103 +a 7287 7288 4103 +a 7288 7289 4103 +a 7289 7290 4103 +a 7290 7291 4103 +a 7291 7292 4103 +a 7292 7293 4103 +a 7293 7294 4103 +a 7294 7295 4103 +a 7295 7296 4103 +a 7296 7297 4103 +a 7297 7298 4103 +a 7298 7299 4103 +a 7299 7300 4103 +a 7300 7301 4103 +a 7301 7302 4103 +a 7302 7303 4103 +a 7303 7304 4103 +a 7304 7305 4103 +a 7305 7306 4103 +a 7306 7307 4103 +a 7307 7308 4103 +a 7308 7309 4103 +a 7309 7310 4103 +a 7310 7311 4103 +a 7311 7312 4103 +a 7312 7313 4103 +a 7313 7314 4103 +a 7314 7315 4103 +a 7315 7316 4103 +a 7316 7317 4103 +a 7317 7318 4103 +a 7318 7319 4103 +a 7319 7320 4103 +a 7320 7321 4103 +a 7321 7322 4103 +a 7322 7323 4103 +a 7323 7324 4103 +a 7324 7325 4103 +a 7325 7326 4103 +a 7326 7327 4103 +a 7327 7328 4103 +a 7328 7329 4103 +a 7329 7330 4103 +a 7330 7331 4103 +a 7331 7332 4103 +a 7332 7333 4103 +a 7333 7334 4103 +a 7334 7335 4103 +a 7335 7336 4103 +a 7336 7337 4103 +a 7337 7338 4103 +a 7338 7339 4103 +a 7339 7340 4103 +a 7340 7341 4103 +a 7341 7342 4103 +a 7342 7343 4103 +a 7343 7344 4103 +a 7344 7345 4103 +a 7345 7346 4103 +a 7346 7347 4103 +a 7347 7348 4103 +a 7348 7349 4103 +a 7349 7350 4103 +a 7350 7351 4103 +a 7351 7352 4103 +a 7352 7353 4103 +a 7353 7354 4103 +a 7354 7355 4103 +a 7355 7356 4103 +a 7356 7357 4103 +a 7357 7358 4103 +a 7358 7359 4103 +a 7359 7360 4103 +a 7360 7361 4103 +a 7361 7362 4103 +a 7362 7363 4103 +a 7363 7364 4103 +a 7364 7365 4103 +a 7365 7366 4103 +a 7366 7367 4103 +a 7367 7368 4103 +a 7368 7369 4103 +a 7369 7370 4103 +a 7370 7371 4103 +a 7371 7372 4103 +a 7372 7373 4103 +a 7373 7374 4103 +a 7374 7375 4103 +a 7375 7376 4103 +a 7376 7377 4103 +a 7377 7378 4103 +a 7378 7379 4103 +a 7379 7380 4103 +a 7380 7381 4103 +a 7381 7382 4103 +a 7382 7383 4103 +a 7383 7384 4103 +a 7384 7385 4103 +a 7385 7386 4103 +a 7386 7387 4103 +a 7387 7388 4103 +a 7388 7389 4103 +a 7389 7390 4103 +a 7390 7391 4103 +a 7391 7392 4103 +a 7392 7393 4103 +a 7393 7394 4103 +a 7394 7395 4103 +a 7395 7396 4103 +a 7396 7397 4103 +a 7397 7398 4103 +a 7398 7399 4103 +a 7399 7400 4103 +a 7400 7401 4103 +a 7401 7402 4103 +a 7402 7403 4103 +a 7403 7404 4103 +a 7404 7405 4103 +a 7405 7406 4103 +a 7406 7407 4103 +a 7407 7408 4103 +a 7408 7409 4103 +a 7409 7410 4103 +a 7410 7411 4103 +a 7411 7412 4103 +a 7412 7413 4103 +a 7413 7414 4103 +a 7414 7415 4103 +a 7415 7416 4103 +a 7416 7417 4103 +a 7417 7418 4103 +a 7418 7419 4103 +a 7419 7420 4103 +a 7420 7421 4103 +a 7421 7422 4103 +a 7422 7423 4103 +a 7423 7424 4103 +a 7424 7425 4103 +a 7425 7426 4103 +a 7426 7427 4103 +a 7427 7428 4103 +a 7428 7429 4103 +a 7429 7430 4103 +a 7430 7431 4103 +a 7431 7432 4103 +a 7432 7433 4103 +a 7433 7434 4103 +a 7434 7435 4103 +a 7435 7436 4103 +a 7436 7437 4103 +a 7437 7438 4103 +a 7438 7439 4103 +a 7439 7440 4103 +a 7440 7441 4103 +a 7441 7442 4103 +a 7442 7443 4103 +a 7443 7444 4103 +a 7444 7445 4103 +a 7445 7446 4103 +a 7446 7447 4103 +a 7447 7448 4103 +a 7448 7449 4103 +a 7449 7450 4103 +a 7450 7451 4103 +a 7451 7452 4103 +a 7452 7453 4103 +a 7453 7454 4103 +a 7454 7455 4103 +a 7455 7456 4103 +a 7456 7457 4103 +a 7457 7458 4103 +a 7458 7459 4103 +a 7459 7460 4103 +a 7460 7461 4103 +a 7461 7462 4103 +a 7462 7463 4103 +a 7463 7464 4103 +a 7464 7465 4103 +a 7465 7466 4103 +a 7466 7467 4103 +a 7467 7468 4103 +a 7468 7469 4103 +a 7469 7470 4103 +a 7470 7471 4103 +a 7471 7472 4103 +a 7472 7473 4103 +a 7473 7474 4103 +a 7474 7475 4103 +a 7475 7476 4103 +a 7476 7477 4103 +a 7477 7478 4103 +a 7478 7479 4103 +a 7479 7480 4103 +a 7480 7481 4103 +a 7481 7482 4103 +a 7482 7483 4103 +a 7483 7484 4103 +a 7484 7485 4103 +a 7485 7486 4103 +a 7486 7487 4103 +a 7487 7488 4103 +a 7488 7489 4103 +a 7489 7490 4103 +a 7490 7491 4103 +a 7491 7492 4103 +a 7492 7493 4103 +a 7493 7494 4103 +a 7494 7495 4103 +a 7495 7496 4103 +a 7496 7497 4103 +a 7497 7498 4103 +a 7498 7499 4103 +a 7499 7500 4103 +a 7500 7501 4103 +a 7501 7502 4103 +a 7502 7503 4103 +a 7503 7504 4103 +a 7504 7505 4103 +a 7505 7506 4103 +a 7506 7507 4103 +a 7507 7508 4103 +a 7508 7509 4103 +a 7509 7510 4103 +a 7510 7511 4103 +a 7511 7512 4103 +a 7512 7513 4103 +a 7513 7514 4103 +a 7514 7515 4103 +a 7515 7516 4103 +a 7516 7517 4103 +a 7517 7518 4103 +a 7518 7519 4103 +a 7519 7520 4103 +a 7520 7521 4103 +a 7521 7522 4103 +a 7522 7523 4103 +a 7523 7524 4103 +a 7524 7525 4103 +a 7525 7526 4103 +a 7526 7527 4103 +a 7527 7528 4103 +a 7528 7529 4103 +a 7529 7530 4103 +a 7530 7531 4103 +a 7531 7532 4103 +a 7532 7533 4103 +a 7533 7534 4103 +a 7534 7535 4103 +a 7535 7536 4103 +a 7536 7537 4103 +a 7537 7538 4103 +a 7538 7539 4103 +a 7539 7540 4103 +a 7540 7541 4103 +a 7541 7542 4103 +a 7542 7543 4103 +a 7543 7544 4103 +a 7544 7545 4103 +a 7545 7546 4103 +a 7546 7547 4103 +a 7547 7548 4103 +a 7548 7549 4103 +a 7549 7550 4103 +a 7550 7551 4103 +a 7551 7552 4103 +a 7552 7553 4103 +a 7553 7554 4103 +a 7554 7555 4103 +a 7555 7556 4103 +a 7556 7557 4103 +a 7557 7558 4103 +a 7558 7559 4103 +a 7559 7560 4103 +a 7560 7561 4103 +a 7561 7562 4103 +a 7562 7563 4103 +a 7563 7564 4103 +a 7564 7565 4103 +a 7565 7566 4103 +a 7566 7567 4103 +a 7567 7568 4103 +a 7568 7569 4103 +a 7569 7570 4103 +a 7570 7571 4103 +a 7571 7572 4103 +a 7572 7573 4103 +a 7573 7574 4103 +a 7574 7575 4103 +a 7575 7576 4103 +a 7576 7577 4103 +a 7577 7578 4103 +a 7578 7579 4103 +a 7579 7580 4103 +a 7580 7581 4103 +a 7581 7582 4103 +a 7582 7583 4103 +a 7583 7584 4103 +a 7584 7585 4103 +a 7585 7586 4103 +a 7586 7587 4103 +a 7587 7588 4103 +a 7588 7589 4103 +a 7589 7590 4103 +a 7590 7591 4103 +a 7591 7592 4103 +a 7592 7593 4103 +a 7593 7594 4103 +a 7594 7595 4103 +a 7595 7596 4103 +a 7596 7597 4103 +a 7597 7598 4103 +a 7598 7599 4103 +a 7599 7600 4103 +a 7600 7601 4103 +a 7601 7602 4103 +a 7602 7603 4103 +a 7603 7604 4103 +a 7604 7605 4103 +a 7605 7606 4103 +a 7606 7607 4103 +a 7607 7608 4103 +a 7608 7609 4103 +a 7609 7610 4103 +a 7610 7611 4103 +a 7611 7612 4103 +a 7612 7613 4103 +a 7613 7614 4103 +a 7614 7615 4103 +a 7615 7616 4103 +a 7616 7617 4103 +a 7617 7618 4103 +a 7618 7619 4103 +a 7619 7620 4103 +a 7620 7621 4103 +a 7621 7622 4103 +a 7622 7623 4103 +a 7623 7624 4103 +a 7624 7625 4103 +a 7625 7626 4103 +a 7626 7627 4103 +a 7627 7628 4103 +a 7628 7629 4103 +a 7629 7630 4103 +a 7630 7631 4103 +a 7631 7632 4103 +a 7632 7633 4103 +a 7633 7634 4103 +a 7634 7635 4103 +a 7635 7636 4103 +a 7636 7637 4103 +a 7637 7638 4103 +a 7638 7639 4103 +a 7639 7640 4103 +a 7640 7641 4103 +a 7641 7642 4103 +a 7642 7643 4103 +a 7643 7644 4103 +a 7644 7645 4103 +a 7645 7646 4103 +a 7646 7647 4103 +a 7647 7648 4103 +a 7648 7649 4103 +a 7649 7650 4103 +a 7650 7651 4103 +a 7651 7652 4103 +a 7652 7653 4103 +a 7653 7654 4103 +a 7654 7655 4103 +a 7655 7656 4103 +a 7656 7657 4103 +a 7657 7658 4103 +a 7658 7659 4103 +a 7659 7660 4103 +a 7660 7661 4103 +a 7661 7662 4103 +a 7662 7663 4103 +a 7663 7664 4103 +a 7664 7665 4103 +a 7665 7666 4103 +a 7666 7667 4103 +a 7667 7668 4103 +a 7668 7669 4103 +a 7669 7670 4103 +a 7670 7671 4103 +a 7671 7672 4103 +a 7672 7673 4103 +a 7673 7674 4103 +a 7674 7675 4103 +a 7675 7676 4103 +a 7676 7677 4103 +a 7677 7678 4103 +a 7678 7679 4103 +a 7679 7680 4103 +a 7680 7681 4103 +a 7681 7682 4103 +a 7682 7683 4103 +a 7683 7684 4103 +a 7684 7685 4103 +a 7685 7686 4103 +a 7686 7687 4103 +a 7687 7688 4103 +a 7688 7689 4103 +a 7689 7690 4103 +a 7690 7691 4103 +a 7691 7692 4103 +a 7692 7693 4103 +a 7693 7694 4103 +a 7694 7695 4103 +a 7695 7696 4103 +a 7696 7697 4103 +a 7697 7698 4103 +a 7698 7699 4103 +a 7699 7700 4103 +a 7700 7701 4103 +a 7701 7702 4103 +a 7702 7703 4103 +a 7703 7704 4103 +a 7704 7705 4103 +a 7705 7706 4103 +a 7706 7707 4103 +a 7707 7708 4103 +a 7708 7709 4103 +a 7709 7710 4103 +a 7710 7711 4103 +a 7711 7712 4103 +a 7712 7713 4103 +a 7713 7714 4103 +a 7714 7715 4103 +a 7715 7716 4103 +a 7716 7717 4103 +a 7717 7718 4103 +a 7718 7719 4103 +a 7719 7720 4103 +a 7720 7721 4103 +a 7721 7722 4103 +a 7722 7723 4103 +a 7723 7724 4103 +a 7724 7725 4103 +a 7725 7726 4103 +a 7726 7727 4103 +a 7727 7728 4103 +a 7728 7729 4103 +a 7729 7730 4103 +a 7730 7731 4103 +a 7731 7732 4103 +a 7732 7733 4103 +a 7733 7734 4103 +a 7734 7735 4103 +a 7735 7736 4103 +a 7736 7737 4103 +a 7737 7738 4103 +a 7738 7739 4103 +a 7739 7740 4103 +a 7740 7741 4103 +a 7741 7742 4103 +a 7742 7743 4103 +a 7743 7744 4103 +a 7744 7745 4103 +a 7745 7746 4103 +a 7746 7747 4103 +a 7747 7748 4103 +a 7748 7749 4103 +a 7749 7750 4103 +a 7750 7751 4103 +a 7751 7752 4103 +a 7752 7753 4103 +a 7753 7754 4103 +a 7754 7755 4103 +a 7755 7756 4103 +a 7756 7757 4103 +a 7757 7758 4103 +a 7758 7759 4103 +a 7759 7760 4103 +a 7760 7761 4103 +a 7761 7762 4103 +a 7762 7763 4103 +a 7763 7764 4103 +a 7764 7765 4103 +a 7765 7766 4103 +a 7766 7767 4103 +a 7767 7768 4103 +a 7768 7769 4103 +a 7769 7770 4103 +a 7770 7771 4103 +a 7771 7772 4103 +a 7772 7773 4103 +a 7773 7774 4103 +a 7774 7775 4103 +a 7775 7776 4103 +a 7776 7777 4103 +a 7777 7778 4103 +a 7778 7779 4103 +a 7779 7780 4103 +a 7780 7781 4103 +a 7781 7782 4103 +a 7782 7783 4103 +a 7783 7784 4103 +a 7784 7785 4103 +a 7785 7786 4103 +a 7786 7787 4103 +a 7787 7788 4103 +a 7788 7789 4103 +a 7789 7790 4103 +a 7790 7791 4103 +a 7791 7792 4103 +a 7792 7793 4103 +a 7793 7794 4103 +a 7794 7795 4103 +a 7795 7796 4103 +a 7796 7797 4103 +a 7797 7798 4103 +a 7798 7799 4103 +a 7799 7800 4103 +a 7800 7801 4103 +a 7801 7802 4103 +a 7802 7803 4103 +a 7803 7804 4103 +a 7804 7805 4103 +a 7805 7806 4103 +a 7806 7807 4103 +a 7807 7808 4103 +a 7808 7809 4103 +a 7809 7810 4103 +a 7810 7811 4103 +a 7811 7812 4103 +a 7812 7813 4103 +a 7813 7814 4103 +a 7814 7815 4103 +a 7815 7816 4103 +a 7816 7817 4103 +a 7817 7818 4103 +a 7818 7819 4103 +a 7819 7820 4103 +a 7820 7821 4103 +a 7821 7822 4103 +a 7822 7823 4103 +a 7823 7824 4103 +a 7824 7825 4103 +a 7825 7826 4103 +a 7826 7827 4103 +a 7827 7828 4103 +a 7828 7829 4103 +a 7829 7830 4103 +a 7830 7831 4103 +a 7831 7832 4103 +a 7832 7833 4103 +a 7833 7834 4103 +a 7834 7835 4103 +a 7835 7836 4103 +a 7836 7837 4103 +a 7837 7838 4103 +a 7838 7839 4103 +a 7839 7840 4103 +a 7840 7841 4103 +a 7841 7842 4103 +a 7842 7843 4103 +a 7843 7844 4103 +a 7844 7845 4103 +a 7845 7846 4103 +a 7846 7847 4103 +a 7847 7848 4103 +a 7848 7849 4103 +a 7849 7850 4103 +a 7850 7851 4103 +a 7851 7852 4103 +a 7852 7853 4103 +a 7853 7854 4103 +a 7854 7855 4103 +a 7855 7856 4103 +a 7856 7857 4103 +a 7857 7858 4103 +a 7858 7859 4103 +a 7859 7860 4103 +a 7860 7861 4103 +a 7861 7862 4103 +a 7862 7863 4103 +a 7863 7864 4103 +a 7864 7865 4103 +a 7865 7866 4103 +a 7866 7867 4103 +a 7867 7868 4103 +a 7868 7869 4103 +a 7869 7870 4103 +a 7870 7871 4103 +a 7871 7872 4103 +a 7872 7873 4103 +a 7873 7874 4103 +a 7874 7875 4103 +a 7875 7876 4103 +a 7876 7877 4103 +a 7877 7878 4103 +a 7878 7879 4103 +a 7879 7880 4103 +a 7880 7881 4103 +a 7881 7882 4103 +a 7882 7883 4103 +a 7883 7884 4103 +a 7884 7885 4103 +a 7885 7886 4103 +a 7886 7887 4103 +a 7887 7888 4103 +a 7888 7889 4103 +a 7889 7890 4103 +a 7890 7891 4103 +a 7891 7892 4103 +a 7892 7893 4103 +a 7893 7894 4103 +a 7894 7895 4103 +a 7895 7896 4103 +a 7896 7897 4103 +a 7897 7898 4103 +a 7898 7899 4103 +a 7899 7900 4103 +a 7900 7901 4103 +a 7901 7902 4103 +a 7902 7903 4103 +a 7903 7904 4103 +a 7904 7905 4103 +a 7905 7906 4103 +a 7906 7907 4103 +a 7907 7908 4103 +a 7908 7909 4103 +a 7909 7910 4103 +a 7910 7911 4103 +a 7911 7912 4103 +a 7912 7913 4103 +a 7913 7914 4103 +a 7914 7915 4103 +a 7915 7916 4103 +a 7916 7917 4103 +a 7917 7918 4103 +a 7918 7919 4103 +a 7919 7920 4103 +a 7920 7921 4103 +a 7921 7922 4103 +a 7922 7923 4103 +a 7923 7924 4103 +a 7924 7925 4103 +a 7925 7926 4103 +a 7926 7927 4103 +a 7927 7928 4103 +a 7928 7929 4103 +a 7929 7930 4103 +a 7930 7931 4103 +a 7931 7932 4103 +a 7932 7933 4103 +a 7933 7934 4103 +a 7934 7935 4103 +a 7935 7936 4103 +a 7936 7937 4103 +a 7937 7938 4103 +a 7938 7939 4103 +a 7939 7940 4103 +a 7940 7941 4103 +a 7941 7942 4103 +a 7942 7943 4103 +a 7943 7944 4103 +a 7944 7945 4103 +a 7945 7946 4103 +a 7946 7947 4103 +a 7947 7948 4103 +a 7948 7949 4103 +a 7949 7950 4103 +a 7950 7951 4103 +a 7951 7952 4103 +a 7952 7953 4103 +a 7953 7954 4103 +a 7954 7955 4103 +a 7955 7956 4103 +a 7956 7957 4103 +a 7957 7958 4103 +a 7958 7959 4103 +a 7959 7960 4103 +a 7960 7961 4103 +a 7961 7962 4103 +a 7962 7963 4103 +a 7963 7964 4103 +a 7964 7965 4103 +a 7965 7966 4103 +a 7966 7967 4103 +a 7967 7968 4103 +a 7968 7969 4103 +a 7969 7970 4103 +a 7970 7971 4103 +a 7971 7972 4103 +a 7972 7973 4103 +a 7973 7974 4103 +a 7974 7975 4103 +a 7975 7976 4103 +a 7976 7977 4103 +a 7977 7978 4103 +a 7978 7979 4103 +a 7979 7980 4103 +a 7980 7981 4103 +a 7981 7982 4103 +a 7982 7983 4103 +a 7983 7984 4103 +a 7984 7985 4103 +a 7985 7986 4103 +a 7986 7987 4103 +a 7987 7988 4103 +a 7988 7989 4103 +a 7989 7990 4103 +a 7990 7991 4103 +a 7991 7992 4103 +a 7992 7993 4103 +a 7993 7994 4103 +a 7994 7995 4103 +a 7995 7996 4103 +a 7996 7997 4103 +a 7997 7998 4103 +a 7998 7999 4103 +a 7999 8000 4103 +a 8000 8001 4103 +a 8001 8002 4103 +a 8002 8003 4103 +a 8003 8004 4103 +a 8004 8005 4103 +a 8005 8006 4103 +a 8006 8007 4103 +a 8007 8008 4103 +a 8008 8009 4103 +a 8009 8010 4103 +a 8010 8011 4103 +a 8011 8012 4103 +a 8012 8013 4103 +a 8013 8014 4103 +a 8014 8015 4103 +a 8015 8016 4103 +a 8016 8017 4103 +a 8017 8018 4103 +a 8018 8019 4103 +a 8019 8020 4103 +a 8020 8021 4103 +a 8021 8022 4103 +a 8022 8023 4103 +a 8023 8024 4103 +a 8024 8025 4103 +a 8025 8026 4103 +a 8026 8027 4103 +a 8027 8028 4103 +a 8028 8029 4103 +a 8029 8030 4103 +a 8030 8031 4103 +a 8031 8032 4103 +a 8032 8033 4103 +a 8033 8034 4103 +a 8034 8035 4103 +a 8035 8036 4103 +a 8036 8037 4103 +a 8037 8038 4103 +a 8038 8039 4103 +a 8039 8040 4103 +a 8040 8041 4103 +a 8041 8042 4103 +a 8042 8043 4103 +a 8043 8044 4103 +a 8044 8045 4103 +a 8045 8046 4103 +a 8046 8047 4103 +a 8047 8048 4103 +a 8048 8049 4103 +a 8049 8050 4103 +a 8050 8051 4103 +a 8051 8052 4103 +a 8052 8053 4103 +a 8053 8054 4103 +a 8054 8055 4103 +a 8055 8056 4103 +a 8056 8057 4103 +a 8057 8058 4103 +a 8058 8059 4103 +a 8059 8060 4103 +a 8060 8061 4103 +a 8061 8062 4103 +a 8062 8063 4103 +a 8063 8064 4103 +a 8064 8065 4103 +a 8065 8066 4103 +a 8066 8067 4103 +a 8067 8068 4103 +a 8068 8069 4103 +a 8069 8070 4103 +a 8070 8071 4103 +a 8071 8072 4103 +a 8072 8073 4103 +a 8073 8074 4103 +a 8074 8075 4103 +a 8075 8076 4103 +a 8076 8077 4103 +a 8077 8078 4103 +a 8078 8079 4103 +a 8079 8080 4103 +a 8080 8081 4103 +a 8081 8082 4103 +a 8082 8083 4103 +a 8083 8084 4103 +a 8084 8085 4103 +a 8085 8086 4103 +a 8086 8087 4103 +a 8087 8088 4103 +a 8088 8089 4103 +a 8089 8090 4103 +a 8090 8091 4103 +a 8091 8092 4103 +a 8092 8093 4103 +a 8093 8094 4103 +a 8094 8095 4103 +a 8095 8096 4103 +a 8096 8097 4103 +a 8097 8098 4103 +a 8098 8099 4103 +a 8099 8100 4103 +a 8100 8101 4103 +a 8101 8102 4103 +a 8102 8103 4103 +a 8103 8104 4103 +a 8104 8105 4103 +a 8105 8106 4103 +a 8106 8107 4103 +a 8107 8108 4103 +a 8108 8109 4103 +a 8109 8110 4103 +a 8110 8111 4103 +a 8111 8112 4103 +a 8112 8113 4103 +a 8113 8114 4103 +a 8114 8115 4103 +a 8115 8116 4103 +a 8116 8117 4103 +a 8117 8118 4103 +a 8118 8119 4103 +a 8119 8120 4103 +a 8120 8121 4103 +a 8121 8122 4103 +a 8122 8123 4103 +a 8123 8124 4103 +a 8124 8125 4103 +a 8125 8126 4103 +a 8126 8127 4103 +a 8127 8128 4103 +a 8128 8129 4103 +a 8129 8130 4103 +a 8130 8131 4103 +a 8131 8132 4103 +a 8132 8133 4103 +a 8133 8134 4103 +a 8134 8135 4103 +a 8135 8136 4103 +a 8136 8137 4103 +a 8137 8138 4103 +a 8138 8139 4103 +a 8139 8140 4103 +a 8140 8141 4103 +a 8141 8142 4103 +a 8142 8143 4103 +a 8143 8144 4103 +a 8144 8145 4103 +a 8145 8146 4103 +a 8146 8147 4103 +a 8147 8148 4103 +a 8148 8149 4103 +a 8149 8150 4103 +a 8150 8151 4103 +a 8151 8152 4103 +a 8152 8153 4103 +a 8153 8154 4103 +a 8154 8155 4103 +a 8155 8156 4103 +a 8156 8157 4103 +a 8157 8158 4103 +a 8158 8159 4103 +a 8159 8160 4103 +a 8160 8161 4103 +a 8161 8162 4103 +a 8162 8163 4103 +a 8163 8164 4103 +a 8164 8165 4103 +a 8165 8166 4103 +a 8166 8167 4103 +a 8167 8168 4103 +a 8168 8169 4103 +a 8169 8170 4103 +a 8170 8171 4103 +a 8171 8172 4103 +a 8172 8173 4103 +a 8173 8174 4103 +a 8174 8175 4103 +a 8175 8176 4103 +a 8176 8177 4103 +a 8177 8178 4103 +a 8178 8179 4103 +a 8179 8180 4103 +a 8180 8181 4103 +a 8181 8182 4103 +a 8182 8183 4103 +a 8183 8184 4103 +a 8184 8185 4103 +a 8185 8186 4103 +a 8186 8187 4103 +a 8187 8188 4103 +a 8188 8189 4103 +a 8189 8190 4103 +a 8190 8191 4103 +a 8191 8192 4103 +a 8192 8193 4103 +a 8193 8194 4103 +a 8194 8195 4103 +a 8195 8196 4103 +a 8196 8197 4103 +a 8197 8198 4103 +a 8198 8199 4103 +a 8199 8200 4103 +a 8200 8201 4103 +a 8201 8202 4103 +a 8202 8203 4103 +a 8203 8204 4103 +a 8204 8205 4103 +a 8205 8206 4103 +a 8206 8207 4103 +a 8207 8208 4103 +a 8209 8210 4102 +a 8210 8211 4102 +a 8211 8212 4102 +a 8212 8213 4102 +a 8213 8214 4102 +a 8214 8215 4102 +a 8215 8216 4102 +a 8216 8217 4102 +a 8217 8218 4102 +a 8218 8219 4102 +a 8219 8220 4102 +a 8220 8221 4102 +a 8221 8222 4102 +a 8222 8223 4102 +a 8223 8224 4102 +a 8224 8225 4102 +a 8225 8226 4102 +a 8226 8227 4102 +a 8227 8228 4102 +a 8228 8229 4102 +a 8229 8230 4102 +a 8230 8231 4102 +a 8231 8232 4102 +a 8232 8233 4102 +a 8233 8234 4102 +a 8234 8235 4102 +a 8235 8236 4102 +a 8236 8237 4102 +a 8237 8238 4102 +a 8238 8239 4102 +a 8239 8240 4102 +a 8240 8241 4102 +a 8241 8242 4102 +a 8242 8243 4102 +a 8243 8244 4102 +a 8244 8245 4102 +a 8245 8246 4102 +a 8246 8247 4102 +a 8247 8248 4102 +a 8248 8249 4102 +a 8249 8250 4102 +a 8250 8251 4102 +a 8251 8252 4102 +a 8252 8253 4102 +a 8253 8254 4102 +a 8254 8255 4102 +a 8255 8256 4102 +a 8256 8257 4102 +a 8257 8258 4102 +a 8258 8259 4102 +a 8259 8260 4102 +a 8260 8261 4102 +a 8261 8262 4102 +a 8262 8263 4102 +a 8263 8264 4102 +a 8264 8265 4102 +a 8265 8266 4102 +a 8266 8267 4102 +a 8267 8268 4102 +a 8268 8269 4102 +a 8269 8270 4102 +a 8270 8271 4102 +a 8271 8272 4102 +a 8272 8273 4102 +a 8273 8274 4102 +a 8274 8275 4102 +a 8275 8276 4102 +a 8276 8277 4102 +a 8277 8278 4102 +a 8278 8279 4102 +a 8279 8280 4102 +a 8280 8281 4102 +a 8281 8282 4102 +a 8282 8283 4102 +a 8283 8284 4102 +a 8284 8285 4102 +a 8285 8286 4102 +a 8286 8287 4102 +a 8287 8288 4102 +a 8288 8289 4102 +a 8289 8290 4102 +a 8290 8291 4102 +a 8291 8292 4102 +a 8292 8293 4102 +a 8293 8294 4102 +a 8294 8295 4102 +a 8295 8296 4102 +a 8296 8297 4102 +a 8297 8298 4102 +a 8298 8299 4102 +a 8299 8300 4102 +a 8300 8301 4102 +a 8301 8302 4102 +a 8302 8303 4102 +a 8303 8304 4102 +a 8304 8305 4102 +a 8305 8306 4102 +a 8306 8307 4102 +a 8307 8308 4102 +a 8308 8309 4102 +a 8309 8310 4102 +a 8310 8311 4102 +a 8311 8312 4102 +a 8312 8313 4102 +a 8313 8314 4102 +a 8314 8315 4102 +a 8315 8316 4102 +a 8316 8317 4102 +a 8317 8318 4102 +a 8318 8319 4102 +a 8319 8320 4102 +a 8320 8321 4102 +a 8321 8322 4102 +a 8322 8323 4102 +a 8323 8324 4102 +a 8324 8325 4102 +a 8325 8326 4102 +a 8326 8327 4102 +a 8327 8328 4102 +a 8328 8329 4102 +a 8329 8330 4102 +a 8330 8331 4102 +a 8331 8332 4102 +a 8332 8333 4102 +a 8333 8334 4102 +a 8334 8335 4102 +a 8335 8336 4102 +a 8336 8337 4102 +a 8337 8338 4102 +a 8338 8339 4102 +a 8339 8340 4102 +a 8340 8341 4102 +a 8341 8342 4102 +a 8342 8343 4102 +a 8343 8344 4102 +a 8344 8345 4102 +a 8345 8346 4102 +a 8346 8347 4102 +a 8347 8348 4102 +a 8348 8349 4102 +a 8349 8350 4102 +a 8350 8351 4102 +a 8351 8352 4102 +a 8352 8353 4102 +a 8353 8354 4102 +a 8354 8355 4102 +a 8355 8356 4102 +a 8356 8357 4102 +a 8357 8358 4102 +a 8358 8359 4102 +a 8359 8360 4102 +a 8360 8361 4102 +a 8361 8362 4102 +a 8362 8363 4102 +a 8363 8364 4102 +a 8364 8365 4102 +a 8365 8366 4102 +a 8366 8367 4102 +a 8367 8368 4102 +a 8368 8369 4102 +a 8369 8370 4102 +a 8370 8371 4102 +a 8371 8372 4102 +a 8372 8373 4102 +a 8373 8374 4102 +a 8374 8375 4102 +a 8375 8376 4102 +a 8376 8377 4102 +a 8377 8378 4102 +a 8378 8379 4102 +a 8379 8380 4102 +a 8380 8381 4102 +a 8381 8382 4102 +a 8382 8383 4102 +a 8383 8384 4102 +a 8384 8385 4102 +a 8385 8386 4102 +a 8386 8387 4102 +a 8387 8388 4102 +a 8388 8389 4102 +a 8389 8390 4102 +a 8390 8391 4102 +a 8391 8392 4102 +a 8392 8393 4102 +a 8393 8394 4102 +a 8394 8395 4102 +a 8395 8396 4102 +a 8396 8397 4102 +a 8397 8398 4102 +a 8398 8399 4102 +a 8399 8400 4102 +a 8400 8401 4102 +a 8401 8402 4102 +a 8402 8403 4102 +a 8403 8404 4102 +a 8404 8405 4102 +a 8405 8406 4102 +a 8406 8407 4102 +a 8407 8408 4102 +a 8408 8409 4102 +a 8409 8410 4102 +a 8410 8411 4102 +a 8411 8412 4102 +a 8412 8413 4102 +a 8413 8414 4102 +a 8414 8415 4102 +a 8415 8416 4102 +a 8416 8417 4102 +a 8417 8418 4102 +a 8418 8419 4102 +a 8419 8420 4102 +a 8420 8421 4102 +a 8421 8422 4102 +a 8422 8423 4102 +a 8423 8424 4102 +a 8424 8425 4102 +a 8425 8426 4102 +a 8426 8427 4102 +a 8427 8428 4102 +a 8428 8429 4102 +a 8429 8430 4102 +a 8430 8431 4102 +a 8431 8432 4102 +a 8432 8433 4102 +a 8433 8434 4102 +a 8434 8435 4102 +a 8435 8436 4102 +a 8436 8437 4102 +a 8437 8438 4102 +a 8438 8439 4102 +a 8439 8440 4102 +a 8440 8441 4102 +a 8441 8442 4102 +a 8442 8443 4102 +a 8443 8444 4102 +a 8444 8445 4102 +a 8445 8446 4102 +a 8446 8447 4102 +a 8447 8448 4102 +a 8448 8449 4102 +a 8449 8450 4102 +a 8450 8451 4102 +a 8451 8452 4102 +a 8452 8453 4102 +a 8453 8454 4102 +a 8454 8455 4102 +a 8455 8456 4102 +a 8456 8457 4102 +a 8457 8458 4102 +a 8458 8459 4102 +a 8459 8460 4102 +a 8460 8461 4102 +a 8461 8462 4102 +a 8462 8463 4102 +a 8463 8464 4102 +a 8464 8465 4102 +a 8465 8466 4102 +a 8466 8467 4102 +a 8467 8468 4102 +a 8468 8469 4102 +a 8469 8470 4102 +a 8470 8471 4102 +a 8471 8472 4102 +a 8472 8473 4102 +a 8473 8474 4102 +a 8474 8475 4102 +a 8475 8476 4102 +a 8476 8477 4102 +a 8477 8478 4102 +a 8478 8479 4102 +a 8479 8480 4102 +a 8480 8481 4102 +a 8481 8482 4102 +a 8482 8483 4102 +a 8483 8484 4102 +a 8484 8485 4102 +a 8485 8486 4102 +a 8486 8487 4102 +a 8487 8488 4102 +a 8488 8489 4102 +a 8489 8490 4102 +a 8490 8491 4102 +a 8491 8492 4102 +a 8492 8493 4102 +a 8493 8494 4102 +a 8494 8495 4102 +a 8495 8496 4102 +a 8496 8497 4102 +a 8497 8498 4102 +a 8498 8499 4102 +a 8499 8500 4102 +a 8500 8501 4102 +a 8501 8502 4102 +a 8502 8503 4102 +a 8503 8504 4102 +a 8504 8505 4102 +a 8505 8506 4102 +a 8506 8507 4102 +a 8507 8508 4102 +a 8508 8509 4102 +a 8509 8510 4102 +a 8510 8511 4102 +a 8511 8512 4102 +a 8512 8513 4102 +a 8513 8514 4102 +a 8514 8515 4102 +a 8515 8516 4102 +a 8516 8517 4102 +a 8517 8518 4102 +a 8518 8519 4102 +a 8519 8520 4102 +a 8520 8521 4102 +a 8521 8522 4102 +a 8522 8523 4102 +a 8523 8524 4102 +a 8524 8525 4102 +a 8525 8526 4102 +a 8526 8527 4102 +a 8527 8528 4102 +a 8528 8529 4102 +a 8529 8530 4102 +a 8530 8531 4102 +a 8531 8532 4102 +a 8532 8533 4102 +a 8533 8534 4102 +a 8534 8535 4102 +a 8535 8536 4102 +a 8536 8537 4102 +a 8537 8538 4102 +a 8538 8539 4102 +a 8539 8540 4102 +a 8540 8541 4102 +a 8541 8542 4102 +a 8542 8543 4102 +a 8543 8544 4102 +a 8544 8545 4102 +a 8545 8546 4102 +a 8546 8547 4102 +a 8547 8548 4102 +a 8548 8549 4102 +a 8549 8550 4102 +a 8550 8551 4102 +a 8551 8552 4102 +a 8552 8553 4102 +a 8553 8554 4102 +a 8554 8555 4102 +a 8555 8556 4102 +a 8556 8557 4102 +a 8557 8558 4102 +a 8558 8559 4102 +a 8559 8560 4102 +a 8560 8561 4102 +a 8561 8562 4102 +a 8562 8563 4102 +a 8563 8564 4102 +a 8564 8565 4102 +a 8565 8566 4102 +a 8566 8567 4102 +a 8567 8568 4102 +a 8568 8569 4102 +a 8569 8570 4102 +a 8570 8571 4102 +a 8571 8572 4102 +a 8572 8573 4102 +a 8573 8574 4102 +a 8574 8575 4102 +a 8575 8576 4102 +a 8576 8577 4102 +a 8577 8578 4102 +a 8578 8579 4102 +a 8579 8580 4102 +a 8580 8581 4102 +a 8581 8582 4102 +a 8582 8583 4102 +a 8583 8584 4102 +a 8584 8585 4102 +a 8585 8586 4102 +a 8586 8587 4102 +a 8587 8588 4102 +a 8588 8589 4102 +a 8589 8590 4102 +a 8590 8591 4102 +a 8591 8592 4102 +a 8592 8593 4102 +a 8593 8594 4102 +a 8594 8595 4102 +a 8595 8596 4102 +a 8596 8597 4102 +a 8597 8598 4102 +a 8598 8599 4102 +a 8599 8600 4102 +a 8600 8601 4102 +a 8601 8602 4102 +a 8602 8603 4102 +a 8603 8604 4102 +a 8604 8605 4102 +a 8605 8606 4102 +a 8606 8607 4102 +a 8607 8608 4102 +a 8608 8609 4102 +a 8609 8610 4102 +a 8610 8611 4102 +a 8611 8612 4102 +a 8612 8613 4102 +a 8613 8614 4102 +a 8614 8615 4102 +a 8615 8616 4102 +a 8616 8617 4102 +a 8617 8618 4102 +a 8618 8619 4102 +a 8619 8620 4102 +a 8620 8621 4102 +a 8621 8622 4102 +a 8622 8623 4102 +a 8623 8624 4102 +a 8624 8625 4102 +a 8625 8626 4102 +a 8626 8627 4102 +a 8627 8628 4102 +a 8628 8629 4102 +a 8629 8630 4102 +a 8630 8631 4102 +a 8631 8632 4102 +a 8632 8633 4102 +a 8633 8634 4102 +a 8634 8635 4102 +a 8635 8636 4102 +a 8636 8637 4102 +a 8637 8638 4102 +a 8638 8639 4102 +a 8639 8640 4102 +a 8640 8641 4102 +a 8641 8642 4102 +a 8642 8643 4102 +a 8643 8644 4102 +a 8644 8645 4102 +a 8645 8646 4102 +a 8646 8647 4102 +a 8647 8648 4102 +a 8648 8649 4102 +a 8649 8650 4102 +a 8650 8651 4102 +a 8651 8652 4102 +a 8652 8653 4102 +a 8653 8654 4102 +a 8654 8655 4102 +a 8655 8656 4102 +a 8656 8657 4102 +a 8657 8658 4102 +a 8658 8659 4102 +a 8659 8660 4102 +a 8660 8661 4102 +a 8661 8662 4102 +a 8662 8663 4102 +a 8663 8664 4102 +a 8664 8665 4102 +a 8665 8666 4102 +a 8666 8667 4102 +a 8667 8668 4102 +a 8668 8669 4102 +a 8669 8670 4102 +a 8670 8671 4102 +a 8671 8672 4102 +a 8672 8673 4102 +a 8673 8674 4102 +a 8674 8675 4102 +a 8675 8676 4102 +a 8676 8677 4102 +a 8677 8678 4102 +a 8678 8679 4102 +a 8679 8680 4102 +a 8680 8681 4102 +a 8681 8682 4102 +a 8682 8683 4102 +a 8683 8684 4102 +a 8684 8685 4102 +a 8685 8686 4102 +a 8686 8687 4102 +a 8687 8688 4102 +a 8688 8689 4102 +a 8689 8690 4102 +a 8690 8691 4102 +a 8691 8692 4102 +a 8692 8693 4102 +a 8693 8694 4102 +a 8694 8695 4102 +a 8695 8696 4102 +a 8696 8697 4102 +a 8697 8698 4102 +a 8698 8699 4102 +a 8699 8700 4102 +a 8700 8701 4102 +a 8701 8702 4102 +a 8702 8703 4102 +a 8703 8704 4102 +a 8704 8705 4102 +a 8705 8706 4102 +a 8706 8707 4102 +a 8707 8708 4102 +a 8708 8709 4102 +a 8709 8710 4102 +a 8710 8711 4102 +a 8711 8712 4102 +a 8712 8713 4102 +a 8713 8714 4102 +a 8714 8715 4102 +a 8715 8716 4102 +a 8716 8717 4102 +a 8717 8718 4102 +a 8718 8719 4102 +a 8719 8720 4102 +a 8720 8721 4102 +a 8721 8722 4102 +a 8722 8723 4102 +a 8723 8724 4102 +a 8724 8725 4102 +a 8725 8726 4102 +a 8726 8727 4102 +a 8727 8728 4102 +a 8728 8729 4102 +a 8729 8730 4102 +a 8730 8731 4102 +a 8731 8732 4102 +a 8732 8733 4102 +a 8733 8734 4102 +a 8734 8735 4102 +a 8735 8736 4102 +a 8736 8737 4102 +a 8737 8738 4102 +a 8738 8739 4102 +a 8739 8740 4102 +a 8740 8741 4102 +a 8741 8742 4102 +a 8742 8743 4102 +a 8743 8744 4102 +a 8744 8745 4102 +a 8745 8746 4102 +a 8746 8747 4102 +a 8747 8748 4102 +a 8748 8749 4102 +a 8749 8750 4102 +a 8750 8751 4102 +a 8751 8752 4102 +a 8752 8753 4102 +a 8753 8754 4102 +a 8754 8755 4102 +a 8755 8756 4102 +a 8756 8757 4102 +a 8757 8758 4102 +a 8758 8759 4102 +a 8759 8760 4102 +a 8760 8761 4102 +a 8761 8762 4102 +a 8762 8763 4102 +a 8763 8764 4102 +a 8764 8765 4102 +a 8765 8766 4102 +a 8766 8767 4102 +a 8767 8768 4102 +a 8768 8769 4102 +a 8769 8770 4102 +a 8770 8771 4102 +a 8771 8772 4102 +a 8772 8773 4102 +a 8773 8774 4102 +a 8774 8775 4102 +a 8775 8776 4102 +a 8776 8777 4102 +a 8777 8778 4102 +a 8778 8779 4102 +a 8779 8780 4102 +a 8780 8781 4102 +a 8781 8782 4102 +a 8782 8783 4102 +a 8783 8784 4102 +a 8784 8785 4102 +a 8785 8786 4102 +a 8786 8787 4102 +a 8787 8788 4102 +a 8788 8789 4102 +a 8789 8790 4102 +a 8790 8791 4102 +a 8791 8792 4102 +a 8792 8793 4102 +a 8793 8794 4102 +a 8794 8795 4102 +a 8795 8796 4102 +a 8796 8797 4102 +a 8797 8798 4102 +a 8798 8799 4102 +a 8799 8800 4102 +a 8800 8801 4102 +a 8801 8802 4102 +a 8802 8803 4102 +a 8803 8804 4102 +a 8804 8805 4102 +a 8805 8806 4102 +a 8806 8807 4102 +a 8807 8808 4102 +a 8808 8809 4102 +a 8809 8810 4102 +a 8810 8811 4102 +a 8811 8812 4102 +a 8812 8813 4102 +a 8813 8814 4102 +a 8814 8815 4102 +a 8815 8816 4102 +a 8816 8817 4102 +a 8817 8818 4102 +a 8818 8819 4102 +a 8819 8820 4102 +a 8820 8821 4102 +a 8821 8822 4102 +a 8822 8823 4102 +a 8823 8824 4102 +a 8824 8825 4102 +a 8825 8826 4102 +a 8826 8827 4102 +a 8827 8828 4102 +a 8828 8829 4102 +a 8829 8830 4102 +a 8830 8831 4102 +a 8831 8832 4102 +a 8832 8833 4102 +a 8833 8834 4102 +a 8834 8835 4102 +a 8835 8836 4102 +a 8836 8837 4102 +a 8837 8838 4102 +a 8838 8839 4102 +a 8839 8840 4102 +a 8840 8841 4102 +a 8841 8842 4102 +a 8842 8843 4102 +a 8843 8844 4102 +a 8844 8845 4102 +a 8845 8846 4102 +a 8846 8847 4102 +a 8847 8848 4102 +a 8848 8849 4102 +a 8849 8850 4102 +a 8850 8851 4102 +a 8851 8852 4102 +a 8852 8853 4102 +a 8853 8854 4102 +a 8854 8855 4102 +a 8855 8856 4102 +a 8856 8857 4102 +a 8857 8858 4102 +a 8858 8859 4102 +a 8859 8860 4102 +a 8860 8861 4102 +a 8861 8862 4102 +a 8862 8863 4102 +a 8863 8864 4102 +a 8864 8865 4102 +a 8865 8866 4102 +a 8866 8867 4102 +a 8867 8868 4102 +a 8868 8869 4102 +a 8869 8870 4102 +a 8870 8871 4102 +a 8871 8872 4102 +a 8872 8873 4102 +a 8873 8874 4102 +a 8874 8875 4102 +a 8875 8876 4102 +a 8876 8877 4102 +a 8877 8878 4102 +a 8878 8879 4102 +a 8879 8880 4102 +a 8880 8881 4102 +a 8881 8882 4102 +a 8882 8883 4102 +a 8883 8884 4102 +a 8884 8885 4102 +a 8885 8886 4102 +a 8886 8887 4102 +a 8887 8888 4102 +a 8888 8889 4102 +a 8889 8890 4102 +a 8890 8891 4102 +a 8891 8892 4102 +a 8892 8893 4102 +a 8893 8894 4102 +a 8894 8895 4102 +a 8895 8896 4102 +a 8896 8897 4102 +a 8897 8898 4102 +a 8898 8899 4102 +a 8899 8900 4102 +a 8900 8901 4102 +a 8901 8902 4102 +a 8902 8903 4102 +a 8903 8904 4102 +a 8904 8905 4102 +a 8905 8906 4102 +a 8906 8907 4102 +a 8907 8908 4102 +a 8908 8909 4102 +a 8909 8910 4102 +a 8910 8911 4102 +a 8911 8912 4102 +a 8912 8913 4102 +a 8913 8914 4102 +a 8914 8915 4102 +a 8915 8916 4102 +a 8916 8917 4102 +a 8917 8918 4102 +a 8918 8919 4102 +a 8919 8920 4102 +a 8920 8921 4102 +a 8921 8922 4102 +a 8922 8923 4102 +a 8923 8924 4102 +a 8924 8925 4102 +a 8925 8926 4102 +a 8926 8927 4102 +a 8927 8928 4102 +a 8928 8929 4102 +a 8929 8930 4102 +a 8930 8931 4102 +a 8931 8932 4102 +a 8932 8933 4102 +a 8933 8934 4102 +a 8934 8935 4102 +a 8935 8936 4102 +a 8936 8937 4102 +a 8937 8938 4102 +a 8938 8939 4102 +a 8939 8940 4102 +a 8940 8941 4102 +a 8941 8942 4102 +a 8942 8943 4102 +a 8943 8944 4102 +a 8944 8945 4102 +a 8945 8946 4102 +a 8946 8947 4102 +a 8947 8948 4102 +a 8948 8949 4102 +a 8949 8950 4102 +a 8950 8951 4102 +a 8951 8952 4102 +a 8952 8953 4102 +a 8953 8954 4102 +a 8954 8955 4102 +a 8955 8956 4102 +a 8956 8957 4102 +a 8957 8958 4102 +a 8958 8959 4102 +a 8959 8960 4102 +a 8960 8961 4102 +a 8961 8962 4102 +a 8962 8963 4102 +a 8963 8964 4102 +a 8964 8965 4102 +a 8965 8966 4102 +a 8966 8967 4102 +a 8967 8968 4102 +a 8968 8969 4102 +a 8969 8970 4102 +a 8970 8971 4102 +a 8971 8972 4102 +a 8972 8973 4102 +a 8973 8974 4102 +a 8974 8975 4102 +a 8975 8976 4102 +a 8976 8977 4102 +a 8977 8978 4102 +a 8978 8979 4102 +a 8979 8980 4102 +a 8980 8981 4102 +a 8981 8982 4102 +a 8982 8983 4102 +a 8983 8984 4102 +a 8984 8985 4102 +a 8985 8986 4102 +a 8986 8987 4102 +a 8987 8988 4102 +a 8988 8989 4102 +a 8989 8990 4102 +a 8990 8991 4102 +a 8991 8992 4102 +a 8992 8993 4102 +a 8993 8994 4102 +a 8994 8995 4102 +a 8995 8996 4102 +a 8996 8997 4102 +a 8997 8998 4102 +a 8998 8999 4102 +a 8999 9000 4102 +a 9000 9001 4102 +a 9001 9002 4102 +a 9002 9003 4102 +a 9003 9004 4102 +a 9004 9005 4102 +a 9005 9006 4102 +a 9006 9007 4102 +a 9007 9008 4102 +a 9008 9009 4102 +a 9009 9010 4102 +a 9010 9011 4102 +a 9011 9012 4102 +a 9012 9013 4102 +a 9013 9014 4102 +a 9014 9015 4102 +a 9015 9016 4102 +a 9016 9017 4102 +a 9017 9018 4102 +a 9018 9019 4102 +a 9019 9020 4102 +a 9020 9021 4102 +a 9021 9022 4102 +a 9022 9023 4102 +a 9023 9024 4102 +a 9024 9025 4102 +a 9025 9026 4102 +a 9026 9027 4102 +a 9027 9028 4102 +a 9028 9029 4102 +a 9029 9030 4102 +a 9030 9031 4102 +a 9031 9032 4102 +a 9032 9033 4102 +a 9033 9034 4102 +a 9034 9035 4102 +a 9035 9036 4102 +a 9036 9037 4102 +a 9037 9038 4102 +a 9038 9039 4102 +a 9039 9040 4102 +a 9040 9041 4102 +a 9041 9042 4102 +a 9042 9043 4102 +a 9043 9044 4102 +a 9044 9045 4102 +a 9045 9046 4102 +a 9046 9047 4102 +a 9047 9048 4102 +a 9048 9049 4102 +a 9049 9050 4102 +a 9050 9051 4102 +a 9051 9052 4102 +a 9052 9053 4102 +a 9053 9054 4102 +a 9054 9055 4102 +a 9055 9056 4102 +a 9056 9057 4102 +a 9057 9058 4102 +a 9058 9059 4102 +a 9059 9060 4102 +a 9060 9061 4102 +a 9061 9062 4102 +a 9062 9063 4102 +a 9063 9064 4102 +a 9064 9065 4102 +a 9065 9066 4102 +a 9066 9067 4102 +a 9067 9068 4102 +a 9068 9069 4102 +a 9069 9070 4102 +a 9070 9071 4102 +a 9071 9072 4102 +a 9072 9073 4102 +a 9073 9074 4102 +a 9074 9075 4102 +a 9075 9076 4102 +a 9076 9077 4102 +a 9077 9078 4102 +a 9078 9079 4102 +a 9079 9080 4102 +a 9080 9081 4102 +a 9081 9082 4102 +a 9082 9083 4102 +a 9083 9084 4102 +a 9084 9085 4102 +a 9085 9086 4102 +a 9086 9087 4102 +a 9087 9088 4102 +a 9088 9089 4102 +a 9089 9090 4102 +a 9090 9091 4102 +a 9091 9092 4102 +a 9092 9093 4102 +a 9093 9094 4102 +a 9094 9095 4102 +a 9095 9096 4102 +a 9096 9097 4102 +a 9097 9098 4102 +a 9098 9099 4102 +a 9099 9100 4102 +a 9100 9101 4102 +a 9101 9102 4102 +a 9102 9103 4102 +a 9103 9104 4102 +a 9104 9105 4102 +a 9105 9106 4102 +a 9106 9107 4102 +a 9107 9108 4102 +a 9108 9109 4102 +a 9109 9110 4102 +a 9110 9111 4102 +a 9111 9112 4102 +a 9112 9113 4102 +a 9113 9114 4102 +a 9114 9115 4102 +a 9115 9116 4102 +a 9116 9117 4102 +a 9117 9118 4102 +a 9118 9119 4102 +a 9119 9120 4102 +a 9120 9121 4102 +a 9121 9122 4102 +a 9122 9123 4102 +a 9123 9124 4102 +a 9124 9125 4102 +a 9125 9126 4102 +a 9126 9127 4102 +a 9127 9128 4102 +a 9128 9129 4102 +a 9129 9130 4102 +a 9130 9131 4102 +a 9131 9132 4102 +a 9132 9133 4102 +a 9133 9134 4102 +a 9134 9135 4102 +a 9135 9136 4102 +a 9136 9137 4102 +a 9137 9138 4102 +a 9138 9139 4102 +a 9139 9140 4102 +a 9140 9141 4102 +a 9141 9142 4102 +a 9142 9143 4102 +a 9143 9144 4102 +a 9144 9145 4102 +a 9145 9146 4102 +a 9146 9147 4102 +a 9147 9148 4102 +a 9148 9149 4102 +a 9149 9150 4102 +a 9150 9151 4102 +a 9151 9152 4102 +a 9152 9153 4102 +a 9153 9154 4102 +a 9154 9155 4102 +a 9155 9156 4102 +a 9156 9157 4102 +a 9157 9158 4102 +a 9158 9159 4102 +a 9159 9160 4102 +a 9160 9161 4102 +a 9161 9162 4102 +a 9162 9163 4102 +a 9163 9164 4102 +a 9164 9165 4102 +a 9165 9166 4102 +a 9166 9167 4102 +a 9167 9168 4102 +a 9168 9169 4102 +a 9169 9170 4102 +a 9170 9171 4102 +a 9171 9172 4102 +a 9172 9173 4102 +a 9173 9174 4102 +a 9174 9175 4102 +a 9175 9176 4102 +a 9176 9177 4102 +a 9177 9178 4102 +a 9178 9179 4102 +a 9179 9180 4102 +a 9180 9181 4102 +a 9181 9182 4102 +a 9182 9183 4102 +a 9183 9184 4102 +a 9184 9185 4102 +a 9185 9186 4102 +a 9186 9187 4102 +a 9187 9188 4102 +a 9188 9189 4102 +a 9189 9190 4102 +a 9190 9191 4102 +a 9191 9192 4102 +a 9192 9193 4102 +a 9193 9194 4102 +a 9194 9195 4102 +a 9195 9196 4102 +a 9196 9197 4102 +a 9197 9198 4102 +a 9198 9199 4102 +a 9199 9200 4102 +a 9200 9201 4102 +a 9201 9202 4102 +a 9202 9203 4102 +a 9203 9204 4102 +a 9204 9205 4102 +a 9205 9206 4102 +a 9206 9207 4102 +a 9207 9208 4102 +a 9208 9209 4102 +a 9209 9210 4102 +a 9210 9211 4102 +a 9211 9212 4102 +a 9212 9213 4102 +a 9213 9214 4102 +a 9214 9215 4102 +a 9215 9216 4102 +a 9216 9217 4102 +a 9217 9218 4102 +a 9218 9219 4102 +a 9219 9220 4102 +a 9220 9221 4102 +a 9221 9222 4102 +a 9222 9223 4102 +a 9223 9224 4102 +a 9224 9225 4102 +a 9225 9226 4102 +a 9226 9227 4102 +a 9227 9228 4102 +a 9228 9229 4102 +a 9229 9230 4102 +a 9230 9231 4102 +a 9231 9232 4102 +a 9232 9233 4102 +a 9233 9234 4102 +a 9234 9235 4102 +a 9235 9236 4102 +a 9236 9237 4102 +a 9237 9238 4102 +a 9238 9239 4102 +a 9239 9240 4102 +a 9240 9241 4102 +a 9241 9242 4102 +a 9242 9243 4102 +a 9243 9244 4102 +a 9244 9245 4102 +a 9245 9246 4102 +a 9246 9247 4102 +a 9247 9248 4102 +a 9248 9249 4102 +a 9249 9250 4102 +a 9250 9251 4102 +a 9251 9252 4102 +a 9252 9253 4102 +a 9253 9254 4102 +a 9254 9255 4102 +a 9255 9256 4102 +a 9256 9257 4102 +a 9257 9258 4102 +a 9258 9259 4102 +a 9259 9260 4102 +a 9260 9261 4102 +a 9261 9262 4102 +a 9262 9263 4102 +a 9263 9264 4102 +a 9264 9265 4102 +a 9265 9266 4102 +a 9266 9267 4102 +a 9267 9268 4102 +a 9268 9269 4102 +a 9269 9270 4102 +a 9270 9271 4102 +a 9271 9272 4102 +a 9272 9273 4102 +a 9273 9274 4102 +a 9274 9275 4102 +a 9275 9276 4102 +a 9276 9277 4102 +a 9277 9278 4102 +a 9278 9279 4102 +a 9279 9280 4102 +a 9280 9281 4102 +a 9281 9282 4102 +a 9282 9283 4102 +a 9283 9284 4102 +a 9284 9285 4102 +a 9285 9286 4102 +a 9286 9287 4102 +a 9287 9288 4102 +a 9288 9289 4102 +a 9289 9290 4102 +a 9290 9291 4102 +a 9291 9292 4102 +a 9292 9293 4102 +a 9293 9294 4102 +a 9294 9295 4102 +a 9295 9296 4102 +a 9296 9297 4102 +a 9297 9298 4102 +a 9298 9299 4102 +a 9299 9300 4102 +a 9300 9301 4102 +a 9301 9302 4102 +a 9302 9303 4102 +a 9303 9304 4102 +a 9304 9305 4102 +a 9305 9306 4102 +a 9306 9307 4102 +a 9307 9308 4102 +a 9308 9309 4102 +a 9309 9310 4102 +a 9310 9311 4102 +a 9311 9312 4102 +a 9312 9313 4102 +a 9313 9314 4102 +a 9314 9315 4102 +a 9315 9316 4102 +a 9316 9317 4102 +a 9317 9318 4102 +a 9318 9319 4102 +a 9319 9320 4102 +a 9320 9321 4102 +a 9321 9322 4102 +a 9322 9323 4102 +a 9323 9324 4102 +a 9324 9325 4102 +a 9325 9326 4102 +a 9326 9327 4102 +a 9327 9328 4102 +a 9328 9329 4102 +a 9329 9330 4102 +a 9330 9331 4102 +a 9331 9332 4102 +a 9332 9333 4102 +a 9333 9334 4102 +a 9334 9335 4102 +a 9335 9336 4102 +a 9336 9337 4102 +a 9337 9338 4102 +a 9338 9339 4102 +a 9339 9340 4102 +a 9340 9341 4102 +a 9341 9342 4102 +a 9342 9343 4102 +a 9343 9344 4102 +a 9344 9345 4102 +a 9345 9346 4102 +a 9346 9347 4102 +a 9347 9348 4102 +a 9348 9349 4102 +a 9349 9350 4102 +a 9350 9351 4102 +a 9351 9352 4102 +a 9352 9353 4102 +a 9353 9354 4102 +a 9354 9355 4102 +a 9355 9356 4102 +a 9356 9357 4102 +a 9357 9358 4102 +a 9358 9359 4102 +a 9359 9360 4102 +a 9360 9361 4102 +a 9361 9362 4102 +a 9362 9363 4102 +a 9363 9364 4102 +a 9364 9365 4102 +a 9365 9366 4102 +a 9366 9367 4102 +a 9367 9368 4102 +a 9368 9369 4102 +a 9369 9370 4102 +a 9370 9371 4102 +a 9371 9372 4102 +a 9372 9373 4102 +a 9373 9374 4102 +a 9374 9375 4102 +a 9375 9376 4102 +a 9376 9377 4102 +a 9377 9378 4102 +a 9378 9379 4102 +a 9379 9380 4102 +a 9380 9381 4102 +a 9381 9382 4102 +a 9382 9383 4102 +a 9383 9384 4102 +a 9384 9385 4102 +a 9385 9386 4102 +a 9386 9387 4102 +a 9387 9388 4102 +a 9388 9389 4102 +a 9389 9390 4102 +a 9390 9391 4102 +a 9391 9392 4102 +a 9392 9393 4102 +a 9393 9394 4102 +a 9394 9395 4102 +a 9395 9396 4102 +a 9396 9397 4102 +a 9397 9398 4102 +a 9398 9399 4102 +a 9399 9400 4102 +a 9400 9401 4102 +a 9401 9402 4102 +a 9402 9403 4102 +a 9403 9404 4102 +a 9404 9405 4102 +a 9405 9406 4102 +a 9406 9407 4102 +a 9407 9408 4102 +a 9408 9409 4102 +a 9409 9410 4102 +a 9410 9411 4102 +a 9411 9412 4102 +a 9412 9413 4102 +a 9413 9414 4102 +a 9414 9415 4102 +a 9415 9416 4102 +a 9416 9417 4102 +a 9417 9418 4102 +a 9418 9419 4102 +a 9419 9420 4102 +a 9420 9421 4102 +a 9421 9422 4102 +a 9422 9423 4102 +a 9423 9424 4102 +a 9424 9425 4102 +a 9425 9426 4102 +a 9426 9427 4102 +a 9427 9428 4102 +a 9428 9429 4102 +a 9429 9430 4102 +a 9430 9431 4102 +a 9431 9432 4102 +a 9432 9433 4102 +a 9433 9434 4102 +a 9434 9435 4102 +a 9435 9436 4102 +a 9436 9437 4102 +a 9437 9438 4102 +a 9438 9439 4102 +a 9439 9440 4102 +a 9440 9441 4102 +a 9441 9442 4102 +a 9442 9443 4102 +a 9443 9444 4102 +a 9444 9445 4102 +a 9445 9446 4102 +a 9446 9447 4102 +a 9447 9448 4102 +a 9448 9449 4102 +a 9449 9450 4102 +a 9450 9451 4102 +a 9451 9452 4102 +a 9452 9453 4102 +a 9453 9454 4102 +a 9454 9455 4102 +a 9455 9456 4102 +a 9456 9457 4102 +a 9457 9458 4102 +a 9458 9459 4102 +a 9459 9460 4102 +a 9460 9461 4102 +a 9461 9462 4102 +a 9462 9463 4102 +a 9463 9464 4102 +a 9464 9465 4102 +a 9465 9466 4102 +a 9466 9467 4102 +a 9467 9468 4102 +a 9468 9469 4102 +a 9469 9470 4102 +a 9470 9471 4102 +a 9471 9472 4102 +a 9472 9473 4102 +a 9473 9474 4102 +a 9474 9475 4102 +a 9475 9476 4102 +a 9476 9477 4102 +a 9477 9478 4102 +a 9478 9479 4102 +a 9479 9480 4102 +a 9480 9481 4102 +a 9481 9482 4102 +a 9482 9483 4102 +a 9483 9484 4102 +a 9484 9485 4102 +a 9485 9486 4102 +a 9486 9487 4102 +a 9487 9488 4102 +a 9488 9489 4102 +a 9489 9490 4102 +a 9490 9491 4102 +a 9491 9492 4102 +a 9492 9493 4102 +a 9493 9494 4102 +a 9494 9495 4102 +a 9495 9496 4102 +a 9496 9497 4102 +a 9497 9498 4102 +a 9498 9499 4102 +a 9499 9500 4102 +a 9500 9501 4102 +a 9501 9502 4102 +a 9502 9503 4102 +a 9503 9504 4102 +a 9504 9505 4102 +a 9505 9506 4102 +a 9506 9507 4102 +a 9507 9508 4102 +a 9508 9509 4102 +a 9509 9510 4102 +a 9510 9511 4102 +a 9511 9512 4102 +a 9512 9513 4102 +a 9513 9514 4102 +a 9514 9515 4102 +a 9515 9516 4102 +a 9516 9517 4102 +a 9517 9518 4102 +a 9518 9519 4102 +a 9519 9520 4102 +a 9520 9521 4102 +a 9521 9522 4102 +a 9522 9523 4102 +a 9523 9524 4102 +a 9524 9525 4102 +a 9525 9526 4102 +a 9526 9527 4102 +a 9527 9528 4102 +a 9528 9529 4102 +a 9529 9530 4102 +a 9530 9531 4102 +a 9531 9532 4102 +a 9532 9533 4102 +a 9533 9534 4102 +a 9534 9535 4102 +a 9535 9536 4102 +a 9536 9537 4102 +a 9537 9538 4102 +a 9538 9539 4102 +a 9539 9540 4102 +a 9540 9541 4102 +a 9541 9542 4102 +a 9542 9543 4102 +a 9543 9544 4102 +a 9544 9545 4102 +a 9545 9546 4102 +a 9546 9547 4102 +a 9547 9548 4102 +a 9548 9549 4102 +a 9549 9550 4102 +a 9550 9551 4102 +a 9551 9552 4102 +a 9552 9553 4102 +a 9553 9554 4102 +a 9554 9555 4102 +a 9555 9556 4102 +a 9556 9557 4102 +a 9557 9558 4102 +a 9558 9559 4102 +a 9559 9560 4102 +a 9560 9561 4102 +a 9561 9562 4102 +a 9562 9563 4102 +a 9563 9564 4102 +a 9564 9565 4102 +a 9565 9566 4102 +a 9566 9567 4102 +a 9567 9568 4102 +a 9568 9569 4102 +a 9569 9570 4102 +a 9570 9571 4102 +a 9571 9572 4102 +a 9572 9573 4102 +a 9573 9574 4102 +a 9574 9575 4102 +a 9575 9576 4102 +a 9576 9577 4102 +a 9577 9578 4102 +a 9578 9579 4102 +a 9579 9580 4102 +a 9580 9581 4102 +a 9581 9582 4102 +a 9582 9583 4102 +a 9583 9584 4102 +a 9584 9585 4102 +a 9585 9586 4102 +a 9586 9587 4102 +a 9587 9588 4102 +a 9588 9589 4102 +a 9589 9590 4102 +a 9590 9591 4102 +a 9591 9592 4102 +a 9592 9593 4102 +a 9593 9594 4102 +a 9594 9595 4102 +a 9595 9596 4102 +a 9596 9597 4102 +a 9597 9598 4102 +a 9598 9599 4102 +a 9599 9600 4102 +a 9600 9601 4102 +a 9601 9602 4102 +a 9602 9603 4102 +a 9603 9604 4102 +a 9604 9605 4102 +a 9605 9606 4102 +a 9606 9607 4102 +a 9607 9608 4102 +a 9608 9609 4102 +a 9609 9610 4102 +a 9610 9611 4102 +a 9611 9612 4102 +a 9612 9613 4102 +a 9613 9614 4102 +a 9614 9615 4102 +a 9615 9616 4102 +a 9616 9617 4102 +a 9617 9618 4102 +a 9618 9619 4102 +a 9619 9620 4102 +a 9620 9621 4102 +a 9621 9622 4102 +a 9622 9623 4102 +a 9623 9624 4102 +a 9624 9625 4102 +a 9625 9626 4102 +a 9626 9627 4102 +a 9627 9628 4102 +a 9628 9629 4102 +a 9629 9630 4102 +a 9630 9631 4102 +a 9631 9632 4102 +a 9632 9633 4102 +a 9633 9634 4102 +a 9634 9635 4102 +a 9635 9636 4102 +a 9636 9637 4102 +a 9637 9638 4102 +a 9638 9639 4102 +a 9639 9640 4102 +a 9640 9641 4102 +a 9641 9642 4102 +a 9642 9643 4102 +a 9643 9644 4102 +a 9644 9645 4102 +a 9645 9646 4102 +a 9646 9647 4102 +a 9647 9648 4102 +a 9648 9649 4102 +a 9649 9650 4102 +a 9650 9651 4102 +a 9651 9652 4102 +a 9652 9653 4102 +a 9653 9654 4102 +a 9654 9655 4102 +a 9655 9656 4102 +a 9656 9657 4102 +a 9657 9658 4102 +a 9658 9659 4102 +a 9659 9660 4102 +a 9660 9661 4102 +a 9661 9662 4102 +a 9662 9663 4102 +a 9663 9664 4102 +a 9664 9665 4102 +a 9665 9666 4102 +a 9666 9667 4102 +a 9667 9668 4102 +a 9668 9669 4102 +a 9669 9670 4102 +a 9670 9671 4102 +a 9671 9672 4102 +a 9672 9673 4102 +a 9673 9674 4102 +a 9674 9675 4102 +a 9675 9676 4102 +a 9676 9677 4102 +a 9677 9678 4102 +a 9678 9679 4102 +a 9679 9680 4102 +a 9680 9681 4102 +a 9681 9682 4102 +a 9682 9683 4102 +a 9683 9684 4102 +a 9684 9685 4102 +a 9685 9686 4102 +a 9686 9687 4102 +a 9687 9688 4102 +a 9688 9689 4102 +a 9689 9690 4102 +a 9690 9691 4102 +a 9691 9692 4102 +a 9692 9693 4102 +a 9693 9694 4102 +a 9694 9695 4102 +a 9695 9696 4102 +a 9696 9697 4102 +a 9697 9698 4102 +a 9698 9699 4102 +a 9699 9700 4102 +a 9700 9701 4102 +a 9701 9702 4102 +a 9702 9703 4102 +a 9703 9704 4102 +a 9704 9705 4102 +a 9705 9706 4102 +a 9706 9707 4102 +a 9707 9708 4102 +a 9708 9709 4102 +a 9709 9710 4102 +a 9710 9711 4102 +a 9711 9712 4102 +a 9712 9713 4102 +a 9713 9714 4102 +a 9714 9715 4102 +a 9715 9716 4102 +a 9716 9717 4102 +a 9717 9718 4102 +a 9718 9719 4102 +a 9719 9720 4102 +a 9720 9721 4102 +a 9721 9722 4102 +a 9722 9723 4102 +a 9723 9724 4102 +a 9724 9725 4102 +a 9725 9726 4102 +a 9726 9727 4102 +a 9727 9728 4102 +a 9728 9729 4102 +a 9729 9730 4102 +a 9730 9731 4102 +a 9731 9732 4102 +a 9732 9733 4102 +a 9733 9734 4102 +a 9734 9735 4102 +a 9735 9736 4102 +a 9736 9737 4102 +a 9737 9738 4102 +a 9738 9739 4102 +a 9739 9740 4102 +a 9740 9741 4102 +a 9741 9742 4102 +a 9742 9743 4102 +a 9743 9744 4102 +a 9744 9745 4102 +a 9745 9746 4102 +a 9746 9747 4102 +a 9747 9748 4102 +a 9748 9749 4102 +a 9749 9750 4102 +a 9750 9751 4102 +a 9751 9752 4102 +a 9752 9753 4102 +a 9753 9754 4102 +a 9754 9755 4102 +a 9755 9756 4102 +a 9756 9757 4102 +a 9757 9758 4102 +a 9758 9759 4102 +a 9759 9760 4102 +a 9760 9761 4102 +a 9761 9762 4102 +a 9762 9763 4102 +a 9763 9764 4102 +a 9764 9765 4102 +a 9765 9766 4102 +a 9766 9767 4102 +a 9767 9768 4102 +a 9768 9769 4102 +a 9769 9770 4102 +a 9770 9771 4102 +a 9771 9772 4102 +a 9772 9773 4102 +a 9773 9774 4102 +a 9774 9775 4102 +a 9775 9776 4102 +a 9776 9777 4102 +a 9777 9778 4102 +a 9778 9779 4102 +a 9779 9780 4102 +a 9780 9781 4102 +a 9781 9782 4102 +a 9782 9783 4102 +a 9783 9784 4102 +a 9784 9785 4102 +a 9785 9786 4102 +a 9786 9787 4102 +a 9787 9788 4102 +a 9788 9789 4102 +a 9789 9790 4102 +a 9790 9791 4102 +a 9791 9792 4102 +a 9792 9793 4102 +a 9793 9794 4102 +a 9794 9795 4102 +a 9795 9796 4102 +a 9796 9797 4102 +a 9797 9798 4102 +a 9798 9799 4102 +a 9799 9800 4102 +a 9800 9801 4102 +a 9801 9802 4102 +a 9802 9803 4102 +a 9803 9804 4102 +a 9804 9805 4102 +a 9805 9806 4102 +a 9806 9807 4102 +a 9807 9808 4102 +a 9808 9809 4102 +a 9809 9810 4102 +a 9810 9811 4102 +a 9811 9812 4102 +a 9812 9813 4102 +a 9813 9814 4102 +a 9814 9815 4102 +a 9815 9816 4102 +a 9816 9817 4102 +a 9817 9818 4102 +a 9818 9819 4102 +a 9819 9820 4102 +a 9820 9821 4102 +a 9821 9822 4102 +a 9822 9823 4102 +a 9823 9824 4102 +a 9824 9825 4102 +a 9825 9826 4102 +a 9826 9827 4102 +a 9827 9828 4102 +a 9828 9829 4102 +a 9829 9830 4102 +a 9830 9831 4102 +a 9831 9832 4102 +a 9832 9833 4102 +a 9833 9834 4102 +a 9834 9835 4102 +a 9835 9836 4102 +a 9836 9837 4102 +a 9837 9838 4102 +a 9838 9839 4102 +a 9839 9840 4102 +a 9840 9841 4102 +a 9841 9842 4102 +a 9842 9843 4102 +a 9843 9844 4102 +a 9844 9845 4102 +a 9845 9846 4102 +a 9846 9847 4102 +a 9847 9848 4102 +a 9848 9849 4102 +a 9849 9850 4102 +a 9850 9851 4102 +a 9851 9852 4102 +a 9852 9853 4102 +a 9853 9854 4102 +a 9854 9855 4102 +a 9855 9856 4102 +a 9856 9857 4102 +a 9857 9858 4102 +a 9858 9859 4102 +a 9859 9860 4102 +a 9860 9861 4102 +a 9861 9862 4102 +a 9862 9863 4102 +a 9863 9864 4102 +a 9864 9865 4102 +a 9865 9866 4102 +a 9866 9867 4102 +a 9867 9868 4102 +a 9868 9869 4102 +a 9869 9870 4102 +a 9870 9871 4102 +a 9871 9872 4102 +a 9872 9873 4102 +a 9873 9874 4102 +a 9874 9875 4102 +a 9875 9876 4102 +a 9876 9877 4102 +a 9877 9878 4102 +a 9878 9879 4102 +a 9879 9880 4102 +a 9880 9881 4102 +a 9881 9882 4102 +a 9882 9883 4102 +a 9883 9884 4102 +a 9884 9885 4102 +a 9885 9886 4102 +a 9886 9887 4102 +a 9887 9888 4102 +a 9888 9889 4102 +a 9889 9890 4102 +a 9890 9891 4102 +a 9891 9892 4102 +a 9892 9893 4102 +a 9893 9894 4102 +a 9894 9895 4102 +a 9895 9896 4102 +a 9896 9897 4102 +a 9897 9898 4102 +a 9898 9899 4102 +a 9899 9900 4102 +a 9900 9901 4102 +a 9901 9902 4102 +a 9902 9903 4102 +a 9903 9904 4102 +a 9904 9905 4102 +a 9905 9906 4102 +a 9906 9907 4102 +a 9907 9908 4102 +a 9908 9909 4102 +a 9909 9910 4102 +a 9910 9911 4102 +a 9911 9912 4102 +a 9912 9913 4102 +a 9913 9914 4102 +a 9914 9915 4102 +a 9915 9916 4102 +a 9916 9917 4102 +a 9917 9918 4102 +a 9918 9919 4102 +a 9919 9920 4102 +a 9920 9921 4102 +a 9921 9922 4102 +a 9922 9923 4102 +a 9923 9924 4102 +a 9924 9925 4102 +a 9925 9926 4102 +a 9926 9927 4102 +a 9927 9928 4102 +a 9928 9929 4102 +a 9929 9930 4102 +a 9930 9931 4102 +a 9931 9932 4102 +a 9932 9933 4102 +a 9933 9934 4102 +a 9934 9935 4102 +a 9935 9936 4102 +a 9936 9937 4102 +a 9937 9938 4102 +a 9938 9939 4102 +a 9939 9940 4102 +a 9940 9941 4102 +a 9941 9942 4102 +a 9942 9943 4102 +a 9943 9944 4102 +a 9944 9945 4102 +a 9945 9946 4102 +a 9946 9947 4102 +a 9947 9948 4102 +a 9948 9949 4102 +a 9949 9950 4102 +a 9950 9951 4102 +a 9951 9952 4102 +a 9952 9953 4102 +a 9953 9954 4102 +a 9954 9955 4102 +a 9955 9956 4102 +a 9956 9957 4102 +a 9957 9958 4102 +a 9958 9959 4102 +a 9959 9960 4102 +a 9960 9961 4102 +a 9961 9962 4102 +a 9962 9963 4102 +a 9963 9964 4102 +a 9964 9965 4102 +a 9965 9966 4102 +a 9966 9967 4102 +a 9967 9968 4102 +a 9968 9969 4102 +a 9969 9970 4102 +a 9970 9971 4102 +a 9971 9972 4102 +a 9972 9973 4102 +a 9973 9974 4102 +a 9974 9975 4102 +a 9975 9976 4102 +a 9976 9977 4102 +a 9977 9978 4102 +a 9978 9979 4102 +a 9979 9980 4102 +a 9980 9981 4102 +a 9981 9982 4102 +a 9982 9983 4102 +a 9983 9984 4102 +a 9984 9985 4102 +a 9985 9986 4102 +a 9986 9987 4102 +a 9987 9988 4102 +a 9988 9989 4102 +a 9989 9990 4102 +a 9990 9991 4102 +a 9991 9992 4102 +a 9992 9993 4102 +a 9993 9994 4102 +a 9994 9995 4102 +a 9995 9996 4102 +a 9996 9997 4102 +a 9997 9998 4102 +a 9998 9999 4102 +a 9999 10000 4102 +a 10000 10001 4102 +a 10001 10002 4102 +a 10002 10003 4102 +a 10003 10004 4102 +a 10004 10005 4102 +a 10005 10006 4102 +a 10006 10007 4102 +a 10007 10008 4102 +a 10008 10009 4102 +a 10009 10010 4102 +a 10010 10011 4102 +a 10011 10012 4102 +a 10012 10013 4102 +a 10013 10014 4102 +a 10014 10015 4102 +a 10015 10016 4102 +a 10016 10017 4102 +a 10017 10018 4102 +a 10018 10019 4102 +a 10019 10020 4102 +a 10020 10021 4102 +a 10021 10022 4102 +a 10022 10023 4102 +a 10023 10024 4102 +a 10024 10025 4102 +a 10025 10026 4102 +a 10026 10027 4102 +a 10027 10028 4102 +a 10028 10029 4102 +a 10029 10030 4102 +a 10030 10031 4102 +a 10031 10032 4102 +a 10032 10033 4102 +a 10033 10034 4102 +a 10034 10035 4102 +a 10035 10036 4102 +a 10036 10037 4102 +a 10037 10038 4102 +a 10038 10039 4102 +a 10039 10040 4102 +a 10040 10041 4102 +a 10041 10042 4102 +a 10042 10043 4102 +a 10043 10044 4102 +a 10044 10045 4102 +a 10045 10046 4102 +a 10046 10047 4102 +a 10047 10048 4102 +a 10048 10049 4102 +a 10049 10050 4102 +a 10050 10051 4102 +a 10051 10052 4102 +a 10052 10053 4102 +a 10053 10054 4102 +a 10054 10055 4102 +a 10055 10056 4102 +a 10056 10057 4102 +a 10057 10058 4102 +a 10058 10059 4102 +a 10059 10060 4102 +a 10060 10061 4102 +a 10061 10062 4102 +a 10062 10063 4102 +a 10063 10064 4102 +a 10064 10065 4102 +a 10065 10066 4102 +a 10066 10067 4102 +a 10067 10068 4102 +a 10068 10069 4102 +a 10069 10070 4102 +a 10070 10071 4102 +a 10071 10072 4102 +a 10072 10073 4102 +a 10073 10074 4102 +a 10074 10075 4102 +a 10075 10076 4102 +a 10076 10077 4102 +a 10077 10078 4102 +a 10078 10079 4102 +a 10079 10080 4102 +a 10080 10081 4102 +a 10081 10082 4102 +a 10082 10083 4102 +a 10083 10084 4102 +a 10084 10085 4102 +a 10085 10086 4102 +a 10086 10087 4102 +a 10087 10088 4102 +a 10088 10089 4102 +a 10089 10090 4102 +a 10090 10091 4102 +a 10091 10092 4102 +a 10092 10093 4102 +a 10093 10094 4102 +a 10094 10095 4102 +a 10095 10096 4102 +a 10096 10097 4102 +a 10097 10098 4102 +a 10098 10099 4102 +a 10099 10100 4102 +a 10100 10101 4102 +a 10101 10102 4102 +a 10102 10103 4102 +a 10103 10104 4102 +a 10104 10105 4102 +a 10105 10106 4102 +a 10106 10107 4102 +a 10107 10108 4102 +a 10108 10109 4102 +a 10109 10110 4102 +a 10110 10111 4102 +a 10111 10112 4102 +a 10112 10113 4102 +a 10113 10114 4102 +a 10114 10115 4102 +a 10115 10116 4102 +a 10116 10117 4102 +a 10117 10118 4102 +a 10118 10119 4102 +a 10119 10120 4102 +a 10120 10121 4102 +a 10121 10122 4102 +a 10122 10123 4102 +a 10123 10124 4102 +a 10124 10125 4102 +a 10125 10126 4102 +a 10126 10127 4102 +a 10127 10128 4102 +a 10128 10129 4102 +a 10129 10130 4102 +a 10130 10131 4102 +a 10131 10132 4102 +a 10132 10133 4102 +a 10133 10134 4102 +a 10134 10135 4102 +a 10135 10136 4102 +a 10136 10137 4102 +a 10137 10138 4102 +a 10138 10139 4102 +a 10139 10140 4102 +a 10140 10141 4102 +a 10141 10142 4102 +a 10142 10143 4102 +a 10143 10144 4102 +a 10144 10145 4102 +a 10145 10146 4102 +a 10146 10147 4102 +a 10147 10148 4102 +a 10148 10149 4102 +a 10149 10150 4102 +a 10150 10151 4102 +a 10151 10152 4102 +a 10152 10153 4102 +a 10153 10154 4102 +a 10154 10155 4102 +a 10155 10156 4102 +a 10156 10157 4102 +a 10157 10158 4102 +a 10158 10159 4102 +a 10159 10160 4102 +a 10160 10161 4102 +a 10161 10162 4102 +a 10162 10163 4102 +a 10163 10164 4102 +a 10164 10165 4102 +a 10165 10166 4102 +a 10166 10167 4102 +a 10167 10168 4102 +a 10168 10169 4102 +a 10169 10170 4102 +a 10170 10171 4102 +a 10171 10172 4102 +a 10172 10173 4102 +a 10173 10174 4102 +a 10174 10175 4102 +a 10175 10176 4102 +a 10176 10177 4102 +a 10177 10178 4102 +a 10178 10179 4102 +a 10179 10180 4102 +a 10180 10181 4102 +a 10181 10182 4102 +a 10182 10183 4102 +a 10183 10184 4102 +a 10184 10185 4102 +a 10185 10186 4102 +a 10186 10187 4102 +a 10187 10188 4102 +a 10188 10189 4102 +a 10189 10190 4102 +a 10190 10191 4102 +a 10191 10192 4102 +a 10192 10193 4102 +a 10193 10194 4102 +a 10194 10195 4102 +a 10195 10196 4102 +a 10196 10197 4102 +a 10197 10198 4102 +a 10198 10199 4102 +a 10199 10200 4102 +a 10200 10201 4102 +a 10201 10202 4102 +a 10202 10203 4102 +a 10203 10204 4102 +a 10204 10205 4102 +a 10205 10206 4102 +a 10206 10207 4102 +a 10207 10208 4102 +a 10208 10209 4102 +a 10209 10210 4102 +a 10210 10211 4102 +a 10211 10212 4102 +a 10212 10213 4102 +a 10213 10214 4102 +a 10214 10215 4102 +a 10215 10216 4102 +a 10216 10217 4102 +a 10217 10218 4102 +a 10218 10219 4102 +a 10219 10220 4102 +a 10220 10221 4102 +a 10221 10222 4102 +a 10222 10223 4102 +a 10223 10224 4102 +a 10224 10225 4102 +a 10225 10226 4102 +a 10226 10227 4102 +a 10227 10228 4102 +a 10228 10229 4102 +a 10229 10230 4102 +a 10230 10231 4102 +a 10231 10232 4102 +a 10232 10233 4102 +a 10233 10234 4102 +a 10234 10235 4102 +a 10235 10236 4102 +a 10236 10237 4102 +a 10237 10238 4102 +a 10238 10239 4102 +a 10239 10240 4102 +a 10240 10241 4102 +a 10241 10242 4102 +a 10242 10243 4102 +a 10243 10244 4102 +a 10244 10245 4102 +a 10245 10246 4102 +a 10246 10247 4102 +a 10247 10248 4102 +a 10248 10249 4102 +a 10249 10250 4102 +a 10250 10251 4102 +a 10251 10252 4102 +a 10252 10253 4102 +a 10253 10254 4102 +a 10254 10255 4102 +a 10255 10256 4102 +a 10256 10257 4102 +a 10257 10258 4102 +a 10258 10259 4102 +a 10259 10260 4102 +a 10260 10261 4102 +a 10261 10262 4102 +a 10262 10263 4102 +a 10263 10264 4102 +a 10264 10265 4102 +a 10265 10266 4102 +a 10266 10267 4102 +a 10267 10268 4102 +a 10268 10269 4102 +a 10269 10270 4102 +a 10270 10271 4102 +a 10271 10272 4102 +a 10272 10273 4102 +a 10273 10274 4102 +a 10274 10275 4102 +a 10275 10276 4102 +a 10276 10277 4102 +a 10277 10278 4102 +a 10278 10279 4102 +a 10279 10280 4102 +a 10280 10281 4102 +a 10281 10282 4102 +a 10282 10283 4102 +a 10283 10284 4102 +a 10284 10285 4102 +a 10285 10286 4102 +a 10286 10287 4102 +a 10287 10288 4102 +a 10288 10289 4102 +a 10289 10290 4102 +a 10290 10291 4102 +a 10291 10292 4102 +a 10292 10293 4102 +a 10293 10294 4102 +a 10294 10295 4102 +a 10295 10296 4102 +a 10296 10297 4102 +a 10297 10298 4102 +a 10298 10299 4102 +a 10299 10300 4102 +a 10300 10301 4102 +a 10301 10302 4102 +a 10302 10303 4102 +a 10303 10304 4102 +a 10304 10305 4102 +a 10305 10306 4102 +a 10306 10307 4102 +a 10307 10308 4102 +a 10308 10309 4102 +a 10309 10310 4102 +a 10310 10311 4102 +a 10311 10312 4102 +a 10312 10313 4102 +a 10313 10314 4102 +a 10314 10315 4102 +a 10315 10316 4102 +a 10316 10317 4102 +a 10317 10318 4102 +a 10318 10319 4102 +a 10319 10320 4102 +a 10320 10321 4102 +a 10321 10322 4102 +a 10322 10323 4102 +a 10323 10324 4102 +a 10324 10325 4102 +a 10325 10326 4102 +a 10326 10327 4102 +a 10327 10328 4102 +a 10328 10329 4102 +a 10329 10330 4102 +a 10330 10331 4102 +a 10331 10332 4102 +a 10332 10333 4102 +a 10333 10334 4102 +a 10334 10335 4102 +a 10335 10336 4102 +a 10336 10337 4102 +a 10337 10338 4102 +a 10338 10339 4102 +a 10339 10340 4102 +a 10340 10341 4102 +a 10341 10342 4102 +a 10342 10343 4102 +a 10343 10344 4102 +a 10344 10345 4102 +a 10345 10346 4102 +a 10346 10347 4102 +a 10347 10348 4102 +a 10348 10349 4102 +a 10349 10350 4102 +a 10350 10351 4102 +a 10351 10352 4102 +a 10352 10353 4102 +a 10353 10354 4102 +a 10354 10355 4102 +a 10355 10356 4102 +a 10356 10357 4102 +a 10357 10358 4102 +a 10358 10359 4102 +a 10359 10360 4102 +a 10360 10361 4102 +a 10361 10362 4102 +a 10362 10363 4102 +a 10363 10364 4102 +a 10364 10365 4102 +a 10365 10366 4102 +a 10366 10367 4102 +a 10367 10368 4102 +a 10368 10369 4102 +a 10369 10370 4102 +a 10370 10371 4102 +a 10371 10372 4102 +a 10372 10373 4102 +a 10373 10374 4102 +a 10374 10375 4102 +a 10375 10376 4102 +a 10376 10377 4102 +a 10377 10378 4102 +a 10378 10379 4102 +a 10379 10380 4102 +a 10380 10381 4102 +a 10381 10382 4102 +a 10382 10383 4102 +a 10383 10384 4102 +a 10384 10385 4102 +a 10385 10386 4102 +a 10386 10387 4102 +a 10387 10388 4102 +a 10388 10389 4102 +a 10389 10390 4102 +a 10390 10391 4102 +a 10391 10392 4102 +a 10392 10393 4102 +a 10393 10394 4102 +a 10394 10395 4102 +a 10395 10396 4102 +a 10396 10397 4102 +a 10397 10398 4102 +a 10398 10399 4102 +a 10399 10400 4102 +a 10400 10401 4102 +a 10401 10402 4102 +a 10402 10403 4102 +a 10403 10404 4102 +a 10404 10405 4102 +a 10405 10406 4102 +a 10406 10407 4102 +a 10407 10408 4102 +a 10408 10409 4102 +a 10409 10410 4102 +a 10410 10411 4102 +a 10411 10412 4102 +a 10412 10413 4102 +a 10413 10414 4102 +a 10414 10415 4102 +a 10415 10416 4102 +a 10416 10417 4102 +a 10417 10418 4102 +a 10418 10419 4102 +a 10419 10420 4102 +a 10420 10421 4102 +a 10421 10422 4102 +a 10422 10423 4102 +a 10423 10424 4102 +a 10424 10425 4102 +a 10425 10426 4102 +a 10426 10427 4102 +a 10427 10428 4102 +a 10428 10429 4102 +a 10429 10430 4102 +a 10430 10431 4102 +a 10431 10432 4102 +a 10432 10433 4102 +a 10433 10434 4102 +a 10434 10435 4102 +a 10435 10436 4102 +a 10436 10437 4102 +a 10437 10438 4102 +a 10438 10439 4102 +a 10439 10440 4102 +a 10440 10441 4102 +a 10441 10442 4102 +a 10442 10443 4102 +a 10443 10444 4102 +a 10444 10445 4102 +a 10445 10446 4102 +a 10446 10447 4102 +a 10447 10448 4102 +a 10448 10449 4102 +a 10449 10450 4102 +a 10450 10451 4102 +a 10451 10452 4102 +a 10452 10453 4102 +a 10453 10454 4102 +a 10454 10455 4102 +a 10455 10456 4102 +a 10456 10457 4102 +a 10457 10458 4102 +a 10458 10459 4102 +a 10459 10460 4102 +a 10460 10461 4102 +a 10461 10462 4102 +a 10462 10463 4102 +a 10463 10464 4102 +a 10464 10465 4102 +a 10465 10466 4102 +a 10466 10467 4102 +a 10467 10468 4102 +a 10468 10469 4102 +a 10469 10470 4102 +a 10470 10471 4102 +a 10471 10472 4102 +a 10472 10473 4102 +a 10473 10474 4102 +a 10474 10475 4102 +a 10475 10476 4102 +a 10476 10477 4102 +a 10477 10478 4102 +a 10478 10479 4102 +a 10479 10480 4102 +a 10480 10481 4102 +a 10481 10482 4102 +a 10482 10483 4102 +a 10483 10484 4102 +a 10484 10485 4102 +a 10485 10486 4102 +a 10486 10487 4102 +a 10487 10488 4102 +a 10488 10489 4102 +a 10489 10490 4102 +a 10490 10491 4102 +a 10491 10492 4102 +a 10492 10493 4102 +a 10493 10494 4102 +a 10494 10495 4102 +a 10495 10496 4102 +a 10496 10497 4102 +a 10497 10498 4102 +a 10498 10499 4102 +a 10499 10500 4102 +a 10500 10501 4102 +a 10501 10502 4102 +a 10502 10503 4102 +a 10503 10504 4102 +a 10504 10505 4102 +a 10505 10506 4102 +a 10506 10507 4102 +a 10507 10508 4102 +a 10508 10509 4102 +a 10509 10510 4102 +a 10510 10511 4102 +a 10511 10512 4102 +a 10512 10513 4102 +a 10513 10514 4102 +a 10514 10515 4102 +a 10515 10516 4102 +a 10516 10517 4102 +a 10517 10518 4102 +a 10518 10519 4102 +a 10519 10520 4102 +a 10520 10521 4102 +a 10521 10522 4102 +a 10522 10523 4102 +a 10523 10524 4102 +a 10524 10525 4102 +a 10525 10526 4102 +a 10526 10527 4102 +a 10527 10528 4102 +a 10528 10529 4102 +a 10529 10530 4102 +a 10530 10531 4102 +a 10531 10532 4102 +a 10532 10533 4102 +a 10533 10534 4102 +a 10534 10535 4102 +a 10535 10536 4102 +a 10536 10537 4102 +a 10537 10538 4102 +a 10538 10539 4102 +a 10539 10540 4102 +a 10540 10541 4102 +a 10541 10542 4102 +a 10542 10543 4102 +a 10543 10544 4102 +a 10544 10545 4102 +a 10545 10546 4102 +a 10546 10547 4102 +a 10547 10548 4102 +a 10548 10549 4102 +a 10549 10550 4102 +a 10550 10551 4102 +a 10551 10552 4102 +a 10552 10553 4102 +a 10553 10554 4102 +a 10554 10555 4102 +a 10555 10556 4102 +a 10556 10557 4102 +a 10557 10558 4102 +a 10558 10559 4102 +a 10559 10560 4102 +a 10560 10561 4102 +a 10561 10562 4102 +a 10562 10563 4102 +a 10563 10564 4102 +a 10564 10565 4102 +a 10565 10566 4102 +a 10566 10567 4102 +a 10567 10568 4102 +a 10568 10569 4102 +a 10569 10570 4102 +a 10570 10571 4102 +a 10571 10572 4102 +a 10572 10573 4102 +a 10573 10574 4102 +a 10574 10575 4102 +a 10575 10576 4102 +a 10576 10577 4102 +a 10577 10578 4102 +a 10578 10579 4102 +a 10579 10580 4102 +a 10580 10581 4102 +a 10581 10582 4102 +a 10582 10583 4102 +a 10583 10584 4102 +a 10584 10585 4102 +a 10585 10586 4102 +a 10586 10587 4102 +a 10587 10588 4102 +a 10588 10589 4102 +a 10589 10590 4102 +a 10590 10591 4102 +a 10591 10592 4102 +a 10592 10593 4102 +a 10593 10594 4102 +a 10594 10595 4102 +a 10595 10596 4102 +a 10596 10597 4102 +a 10597 10598 4102 +a 10598 10599 4102 +a 10599 10600 4102 +a 10600 10601 4102 +a 10601 10602 4102 +a 10602 10603 4102 +a 10603 10604 4102 +a 10604 10605 4102 +a 10605 10606 4102 +a 10606 10607 4102 +a 10607 10608 4102 +a 10608 10609 4102 +a 10609 10610 4102 +a 10610 10611 4102 +a 10611 10612 4102 +a 10612 10613 4102 +a 10613 10614 4102 +a 10614 10615 4102 +a 10615 10616 4102 +a 10616 10617 4102 +a 10617 10618 4102 +a 10618 10619 4102 +a 10619 10620 4102 +a 10620 10621 4102 +a 10621 10622 4102 +a 10622 10623 4102 +a 10623 10624 4102 +a 10624 10625 4102 +a 10625 10626 4102 +a 10626 10627 4102 +a 10627 10628 4102 +a 10628 10629 4102 +a 10629 10630 4102 +a 10630 10631 4102 +a 10631 10632 4102 +a 10632 10633 4102 +a 10633 10634 4102 +a 10634 10635 4102 +a 10635 10636 4102 +a 10636 10637 4102 +a 10637 10638 4102 +a 10638 10639 4102 +a 10639 10640 4102 +a 10640 10641 4102 +a 10641 10642 4102 +a 10642 10643 4102 +a 10643 10644 4102 +a 10644 10645 4102 +a 10645 10646 4102 +a 10646 10647 4102 +a 10647 10648 4102 +a 10648 10649 4102 +a 10649 10650 4102 +a 10650 10651 4102 +a 10651 10652 4102 +a 10652 10653 4102 +a 10653 10654 4102 +a 10654 10655 4102 +a 10655 10656 4102 +a 10656 10657 4102 +a 10657 10658 4102 +a 10658 10659 4102 +a 10659 10660 4102 +a 10660 10661 4102 +a 10661 10662 4102 +a 10662 10663 4102 +a 10663 10664 4102 +a 10664 10665 4102 +a 10665 10666 4102 +a 10666 10667 4102 +a 10667 10668 4102 +a 10668 10669 4102 +a 10669 10670 4102 +a 10670 10671 4102 +a 10671 10672 4102 +a 10672 10673 4102 +a 10673 10674 4102 +a 10674 10675 4102 +a 10675 10676 4102 +a 10676 10677 4102 +a 10677 10678 4102 +a 10678 10679 4102 +a 10679 10680 4102 +a 10680 10681 4102 +a 10681 10682 4102 +a 10682 10683 4102 +a 10683 10684 4102 +a 10684 10685 4102 +a 10685 10686 4102 +a 10686 10687 4102 +a 10687 10688 4102 +a 10688 10689 4102 +a 10689 10690 4102 +a 10690 10691 4102 +a 10691 10692 4102 +a 10692 10693 4102 +a 10693 10694 4102 +a 10694 10695 4102 +a 10695 10696 4102 +a 10696 10697 4102 +a 10697 10698 4102 +a 10698 10699 4102 +a 10699 10700 4102 +a 10700 10701 4102 +a 10701 10702 4102 +a 10702 10703 4102 +a 10703 10704 4102 +a 10704 10705 4102 +a 10705 10706 4102 +a 10706 10707 4102 +a 10707 10708 4102 +a 10708 10709 4102 +a 10709 10710 4102 +a 10710 10711 4102 +a 10711 10712 4102 +a 10712 10713 4102 +a 10713 10714 4102 +a 10714 10715 4102 +a 10715 10716 4102 +a 10716 10717 4102 +a 10717 10718 4102 +a 10718 10719 4102 +a 10719 10720 4102 +a 10720 10721 4102 +a 10721 10722 4102 +a 10722 10723 4102 +a 10723 10724 4102 +a 10724 10725 4102 +a 10725 10726 4102 +a 10726 10727 4102 +a 10727 10728 4102 +a 10728 10729 4102 +a 10729 10730 4102 +a 10730 10731 4102 +a 10731 10732 4102 +a 10732 10733 4102 +a 10733 10734 4102 +a 10734 10735 4102 +a 10735 10736 4102 +a 10736 10737 4102 +a 10737 10738 4102 +a 10738 10739 4102 +a 10739 10740 4102 +a 10740 10741 4102 +a 10741 10742 4102 +a 10742 10743 4102 +a 10743 10744 4102 +a 10744 10745 4102 +a 10745 10746 4102 +a 10746 10747 4102 +a 10747 10748 4102 +a 10748 10749 4102 +a 10749 10750 4102 +a 10750 10751 4102 +a 10751 10752 4102 +a 10752 10753 4102 +a 10753 10754 4102 +a 10754 10755 4102 +a 10755 10756 4102 +a 10756 10757 4102 +a 10757 10758 4102 +a 10758 10759 4102 +a 10759 10760 4102 +a 10760 10761 4102 +a 10761 10762 4102 +a 10762 10763 4102 +a 10763 10764 4102 +a 10764 10765 4102 +a 10765 10766 4102 +a 10766 10767 4102 +a 10767 10768 4102 +a 10768 10769 4102 +a 10769 10770 4102 +a 10770 10771 4102 +a 10771 10772 4102 +a 10772 10773 4102 +a 10773 10774 4102 +a 10774 10775 4102 +a 10775 10776 4102 +a 10776 10777 4102 +a 10777 10778 4102 +a 10778 10779 4102 +a 10779 10780 4102 +a 10780 10781 4102 +a 10781 10782 4102 +a 10782 10783 4102 +a 10783 10784 4102 +a 10784 10785 4102 +a 10785 10786 4102 +a 10786 10787 4102 +a 10787 10788 4102 +a 10788 10789 4102 +a 10789 10790 4102 +a 10790 10791 4102 +a 10791 10792 4102 +a 10792 10793 4102 +a 10793 10794 4102 +a 10794 10795 4102 +a 10795 10796 4102 +a 10796 10797 4102 +a 10797 10798 4102 +a 10798 10799 4102 +a 10799 10800 4102 +a 10800 10801 4102 +a 10801 10802 4102 +a 10802 10803 4102 +a 10803 10804 4102 +a 10804 10805 4102 +a 10805 10806 4102 +a 10806 10807 4102 +a 10807 10808 4102 +a 10808 10809 4102 +a 10809 10810 4102 +a 10810 10811 4102 +a 10811 10812 4102 +a 10812 10813 4102 +a 10813 10814 4102 +a 10814 10815 4102 +a 10815 10816 4102 +a 10816 10817 4102 +a 10817 10818 4102 +a 10818 10819 4102 +a 10819 10820 4102 +a 10820 10821 4102 +a 10821 10822 4102 +a 10822 10823 4102 +a 10823 10824 4102 +a 10824 10825 4102 +a 10825 10826 4102 +a 10826 10827 4102 +a 10827 10828 4102 +a 10828 10829 4102 +a 10829 10830 4102 +a 10830 10831 4102 +a 10831 10832 4102 +a 10832 10833 4102 +a 10833 10834 4102 +a 10834 10835 4102 +a 10835 10836 4102 +a 10836 10837 4102 +a 10837 10838 4102 +a 10838 10839 4102 +a 10839 10840 4102 +a 10840 10841 4102 +a 10841 10842 4102 +a 10842 10843 4102 +a 10843 10844 4102 +a 10844 10845 4102 +a 10845 10846 4102 +a 10846 10847 4102 +a 10847 10848 4102 +a 10848 10849 4102 +a 10849 10850 4102 +a 10850 10851 4102 +a 10851 10852 4102 +a 10852 10853 4102 +a 10853 10854 4102 +a 10854 10855 4102 +a 10855 10856 4102 +a 10856 10857 4102 +a 10857 10858 4102 +a 10858 10859 4102 +a 10859 10860 4102 +a 10860 10861 4102 +a 10861 10862 4102 +a 10862 10863 4102 +a 10863 10864 4102 +a 10864 10865 4102 +a 10865 10866 4102 +a 10866 10867 4102 +a 10867 10868 4102 +a 10868 10869 4102 +a 10869 10870 4102 +a 10870 10871 4102 +a 10871 10872 4102 +a 10872 10873 4102 +a 10873 10874 4102 +a 10874 10875 4102 +a 10875 10876 4102 +a 10876 10877 4102 +a 10877 10878 4102 +a 10878 10879 4102 +a 10879 10880 4102 +a 10880 10881 4102 +a 10881 10882 4102 +a 10882 10883 4102 +a 10883 10884 4102 +a 10884 10885 4102 +a 10885 10886 4102 +a 10886 10887 4102 +a 10887 10888 4102 +a 10888 10889 4102 +a 10889 10890 4102 +a 10890 10891 4102 +a 10891 10892 4102 +a 10892 10893 4102 +a 10893 10894 4102 +a 10894 10895 4102 +a 10895 10896 4102 +a 10896 10897 4102 +a 10897 10898 4102 +a 10898 10899 4102 +a 10899 10900 4102 +a 10900 10901 4102 +a 10901 10902 4102 +a 10902 10903 4102 +a 10903 10904 4102 +a 10904 10905 4102 +a 10905 10906 4102 +a 10906 10907 4102 +a 10907 10908 4102 +a 10908 10909 4102 +a 10909 10910 4102 +a 10910 10911 4102 +a 10911 10912 4102 +a 10912 10913 4102 +a 10913 10914 4102 +a 10914 10915 4102 +a 10915 10916 4102 +a 10916 10917 4102 +a 10917 10918 4102 +a 10918 10919 4102 +a 10919 10920 4102 +a 10920 10921 4102 +a 10921 10922 4102 +a 10922 10923 4102 +a 10923 10924 4102 +a 10924 10925 4102 +a 10925 10926 4102 +a 10926 10927 4102 +a 10927 10928 4102 +a 10928 10929 4102 +a 10929 10930 4102 +a 10930 10931 4102 +a 10931 10932 4102 +a 10932 10933 4102 +a 10933 10934 4102 +a 10934 10935 4102 +a 10935 10936 4102 +a 10936 10937 4102 +a 10937 10938 4102 +a 10938 10939 4102 +a 10939 10940 4102 +a 10940 10941 4102 +a 10941 10942 4102 +a 10942 10943 4102 +a 10943 10944 4102 +a 10944 10945 4102 +a 10945 10946 4102 +a 10946 10947 4102 +a 10947 10948 4102 +a 10948 10949 4102 +a 10949 10950 4102 +a 10950 10951 4102 +a 10951 10952 4102 +a 10952 10953 4102 +a 10953 10954 4102 +a 10954 10955 4102 +a 10955 10956 4102 +a 10956 10957 4102 +a 10957 10958 4102 +a 10958 10959 4102 +a 10959 10960 4102 +a 10960 10961 4102 +a 10961 10962 4102 +a 10962 10963 4102 +a 10963 10964 4102 +a 10964 10965 4102 +a 10965 10966 4102 +a 10966 10967 4102 +a 10967 10968 4102 +a 10968 10969 4102 +a 10969 10970 4102 +a 10970 10971 4102 +a 10971 10972 4102 +a 10972 10973 4102 +a 10973 10974 4102 +a 10974 10975 4102 +a 10975 10976 4102 +a 10976 10977 4102 +a 10977 10978 4102 +a 10978 10979 4102 +a 10979 10980 4102 +a 10980 10981 4102 +a 10981 10982 4102 +a 10982 10983 4102 +a 10983 10984 4102 +a 10984 10985 4102 +a 10985 10986 4102 +a 10986 10987 4102 +a 10987 10988 4102 +a 10988 10989 4102 +a 10989 10990 4102 +a 10990 10991 4102 +a 10991 10992 4102 +a 10992 10993 4102 +a 10993 10994 4102 +a 10994 10995 4102 +a 10995 10996 4102 +a 10996 10997 4102 +a 10997 10998 4102 +a 10998 10999 4102 +a 10999 11000 4102 +a 11000 11001 4102 +a 11001 11002 4102 +a 11002 11003 4102 +a 11003 11004 4102 +a 11004 11005 4102 +a 11005 11006 4102 +a 11006 11007 4102 +a 11007 11008 4102 +a 11008 11009 4102 +a 11009 11010 4102 +a 11010 11011 4102 +a 11011 11012 4102 +a 11012 11013 4102 +a 11013 11014 4102 +a 11014 11015 4102 +a 11015 11016 4102 +a 11016 11017 4102 +a 11017 11018 4102 +a 11018 11019 4102 +a 11019 11020 4102 +a 11020 11021 4102 +a 11021 11022 4102 +a 11022 11023 4102 +a 11023 11024 4102 +a 11024 11025 4102 +a 11025 11026 4102 +a 11026 11027 4102 +a 11027 11028 4102 +a 11028 11029 4102 +a 11029 11030 4102 +a 11030 11031 4102 +a 11031 11032 4102 +a 11032 11033 4102 +a 11033 11034 4102 +a 11034 11035 4102 +a 11035 11036 4102 +a 11036 11037 4102 +a 11037 11038 4102 +a 11038 11039 4102 +a 11039 11040 4102 +a 11040 11041 4102 +a 11041 11042 4102 +a 11042 11043 4102 +a 11043 11044 4102 +a 11044 11045 4102 +a 11045 11046 4102 +a 11046 11047 4102 +a 11047 11048 4102 +a 11048 11049 4102 +a 11049 11050 4102 +a 11050 11051 4102 +a 11051 11052 4102 +a 11052 11053 4102 +a 11053 11054 4102 +a 11054 11055 4102 +a 11055 11056 4102 +a 11056 11057 4102 +a 11057 11058 4102 +a 11058 11059 4102 +a 11059 11060 4102 +a 11060 11061 4102 +a 11061 11062 4102 +a 11062 11063 4102 +a 11063 11064 4102 +a 11064 11065 4102 +a 11065 11066 4102 +a 11066 11067 4102 +a 11067 11068 4102 +a 11068 11069 4102 +a 11069 11070 4102 +a 11070 11071 4102 +a 11071 11072 4102 +a 11072 11073 4102 +a 11073 11074 4102 +a 11074 11075 4102 +a 11075 11076 4102 +a 11076 11077 4102 +a 11077 11078 4102 +a 11078 11079 4102 +a 11079 11080 4102 +a 11080 11081 4102 +a 11081 11082 4102 +a 11082 11083 4102 +a 11083 11084 4102 +a 11084 11085 4102 +a 11085 11086 4102 +a 11086 11087 4102 +a 11087 11088 4102 +a 11088 11089 4102 +a 11089 11090 4102 +a 11090 11091 4102 +a 11091 11092 4102 +a 11092 11093 4102 +a 11093 11094 4102 +a 11094 11095 4102 +a 11095 11096 4102 +a 11096 11097 4102 +a 11097 11098 4102 +a 11098 11099 4102 +a 11099 11100 4102 +a 11100 11101 4102 +a 11101 11102 4102 +a 11102 11103 4102 +a 11103 11104 4102 +a 11104 11105 4102 +a 11105 11106 4102 +a 11106 11107 4102 +a 11107 11108 4102 +a 11108 11109 4102 +a 11109 11110 4102 +a 11110 11111 4102 +a 11111 11112 4102 +a 11112 11113 4102 +a 11113 11114 4102 +a 11114 11115 4102 +a 11115 11116 4102 +a 11116 11117 4102 +a 11117 11118 4102 +a 11118 11119 4102 +a 11119 11120 4102 +a 11120 11121 4102 +a 11121 11122 4102 +a 11122 11123 4102 +a 11123 11124 4102 +a 11124 11125 4102 +a 11125 11126 4102 +a 11126 11127 4102 +a 11127 11128 4102 +a 11128 11129 4102 +a 11129 11130 4102 +a 11130 11131 4102 +a 11131 11132 4102 +a 11132 11133 4102 +a 11133 11134 4102 +a 11134 11135 4102 +a 11135 11136 4102 +a 11136 11137 4102 +a 11137 11138 4102 +a 11138 11139 4102 +a 11139 11140 4102 +a 11140 11141 4102 +a 11141 11142 4102 +a 11142 11143 4102 +a 11143 11144 4102 +a 11144 11145 4102 +a 11145 11146 4102 +a 11146 11147 4102 +a 11147 11148 4102 +a 11148 11149 4102 +a 11149 11150 4102 +a 11150 11151 4102 +a 11151 11152 4102 +a 11152 11153 4102 +a 11153 11154 4102 +a 11154 11155 4102 +a 11155 11156 4102 +a 11156 11157 4102 +a 11157 11158 4102 +a 11158 11159 4102 +a 11159 11160 4102 +a 11160 11161 4102 +a 11161 11162 4102 +a 11162 11163 4102 +a 11163 11164 4102 +a 11164 11165 4102 +a 11165 11166 4102 +a 11166 11167 4102 +a 11167 11168 4102 +a 11168 11169 4102 +a 11169 11170 4102 +a 11170 11171 4102 +a 11171 11172 4102 +a 11172 11173 4102 +a 11173 11174 4102 +a 11174 11175 4102 +a 11175 11176 4102 +a 11176 11177 4102 +a 11177 11178 4102 +a 11178 11179 4102 +a 11179 11180 4102 +a 11180 11181 4102 +a 11181 11182 4102 +a 11182 11183 4102 +a 11183 11184 4102 +a 11184 11185 4102 +a 11185 11186 4102 +a 11186 11187 4102 +a 11187 11188 4102 +a 11188 11189 4102 +a 11189 11190 4102 +a 11190 11191 4102 +a 11191 11192 4102 +a 11192 11193 4102 +a 11193 11194 4102 +a 11194 11195 4102 +a 11195 11196 4102 +a 11196 11197 4102 +a 11197 11198 4102 +a 11198 11199 4102 +a 11199 11200 4102 +a 11200 11201 4102 +a 11201 11202 4102 +a 11202 11203 4102 +a 11203 11204 4102 +a 11204 11205 4102 +a 11205 11206 4102 +a 11206 11207 4102 +a 11207 11208 4102 +a 11208 11209 4102 +a 11209 11210 4102 +a 11210 11211 4102 +a 11211 11212 4102 +a 11212 11213 4102 +a 11213 11214 4102 +a 11214 11215 4102 +a 11215 11216 4102 +a 11216 11217 4102 +a 11217 11218 4102 +a 11218 11219 4102 +a 11219 11220 4102 +a 11220 11221 4102 +a 11221 11222 4102 +a 11222 11223 4102 +a 11223 11224 4102 +a 11224 11225 4102 +a 11225 11226 4102 +a 11226 11227 4102 +a 11227 11228 4102 +a 11228 11229 4102 +a 11229 11230 4102 +a 11230 11231 4102 +a 11231 11232 4102 +a 11232 11233 4102 +a 11233 11234 4102 +a 11234 11235 4102 +a 11235 11236 4102 +a 11236 11237 4102 +a 11237 11238 4102 +a 11238 11239 4102 +a 11239 11240 4102 +a 11240 11241 4102 +a 11241 11242 4102 +a 11242 11243 4102 +a 11243 11244 4102 +a 11244 11245 4102 +a 11245 11246 4102 +a 11246 11247 4102 +a 11247 11248 4102 +a 11248 11249 4102 +a 11249 11250 4102 +a 11250 11251 4102 +a 11251 11252 4102 +a 11252 11253 4102 +a 11253 11254 4102 +a 11254 11255 4102 +a 11255 11256 4102 +a 11256 11257 4102 +a 11257 11258 4102 +a 11258 11259 4102 +a 11259 11260 4102 +a 11260 11261 4102 +a 11261 11262 4102 +a 11262 11263 4102 +a 11263 11264 4102 +a 11264 11265 4102 +a 11265 11266 4102 +a 11266 11267 4102 +a 11267 11268 4102 +a 11268 11269 4102 +a 11269 11270 4102 +a 11270 11271 4102 +a 11271 11272 4102 +a 11272 11273 4102 +a 11273 11274 4102 +a 11274 11275 4102 +a 11275 11276 4102 +a 11276 11277 4102 +a 11277 11278 4102 +a 11278 11279 4102 +a 11279 11280 4102 +a 11280 11281 4102 +a 11281 11282 4102 +a 11282 11283 4102 +a 11283 11284 4102 +a 11284 11285 4102 +a 11285 11286 4102 +a 11286 11287 4102 +a 11287 11288 4102 +a 11288 11289 4102 +a 11289 11290 4102 +a 11290 11291 4102 +a 11291 11292 4102 +a 11292 11293 4102 +a 11293 11294 4102 +a 11294 11295 4102 +a 11295 11296 4102 +a 11296 11297 4102 +a 11297 11298 4102 +a 11298 11299 4102 +a 11299 11300 4102 +a 11300 11301 4102 +a 11301 11302 4102 +a 11302 11303 4102 +a 11303 11304 4102 +a 11304 11305 4102 +a 11305 11306 4102 +a 11306 11307 4102 +a 11307 11308 4102 +a 11308 11309 4102 +a 11309 11310 4102 +a 11310 11311 4102 +a 11311 11312 4102 +a 11312 11313 4102 +a 11313 11314 4102 +a 11314 11315 4102 +a 11315 11316 4102 +a 11316 11317 4102 +a 11317 11318 4102 +a 11318 11319 4102 +a 11319 11320 4102 +a 11320 11321 4102 +a 11321 11322 4102 +a 11322 11323 4102 +a 11323 11324 4102 +a 11324 11325 4102 +a 11325 11326 4102 +a 11326 11327 4102 +a 11327 11328 4102 +a 11328 11329 4102 +a 11329 11330 4102 +a 11330 11331 4102 +a 11331 11332 4102 +a 11332 11333 4102 +a 11333 11334 4102 +a 11334 11335 4102 +a 11335 11336 4102 +a 11336 11337 4102 +a 11337 11338 4102 +a 11338 11339 4102 +a 11339 11340 4102 +a 11340 11341 4102 +a 11341 11342 4102 +a 11342 11343 4102 +a 11343 11344 4102 +a 11344 11345 4102 +a 11345 11346 4102 +a 11346 11347 4102 +a 11347 11348 4102 +a 11348 11349 4102 +a 11349 11350 4102 +a 11350 11351 4102 +a 11351 11352 4102 +a 11352 11353 4102 +a 11353 11354 4102 +a 11354 11355 4102 +a 11355 11356 4102 +a 11356 11357 4102 +a 11357 11358 4102 +a 11358 11359 4102 +a 11359 11360 4102 +a 11360 11361 4102 +a 11361 11362 4102 +a 11362 11363 4102 +a 11363 11364 4102 +a 11364 11365 4102 +a 11365 11366 4102 +a 11366 11367 4102 +a 11367 11368 4102 +a 11368 11369 4102 +a 11369 11370 4102 +a 11370 11371 4102 +a 11371 11372 4102 +a 11372 11373 4102 +a 11373 11374 4102 +a 11374 11375 4102 +a 11375 11376 4102 +a 11376 11377 4102 +a 11377 11378 4102 +a 11378 11379 4102 +a 11379 11380 4102 +a 11380 11381 4102 +a 11381 11382 4102 +a 11382 11383 4102 +a 11383 11384 4102 +a 11384 11385 4102 +a 11385 11386 4102 +a 11386 11387 4102 +a 11387 11388 4102 +a 11388 11389 4102 +a 11389 11390 4102 +a 11390 11391 4102 +a 11391 11392 4102 +a 11392 11393 4102 +a 11393 11394 4102 +a 11394 11395 4102 +a 11395 11396 4102 +a 11396 11397 4102 +a 11397 11398 4102 +a 11398 11399 4102 +a 11399 11400 4102 +a 11400 11401 4102 +a 11401 11402 4102 +a 11402 11403 4102 +a 11403 11404 4102 +a 11404 11405 4102 +a 11405 11406 4102 +a 11406 11407 4102 +a 11407 11408 4102 +a 11408 11409 4102 +a 11409 11410 4102 +a 11410 11411 4102 +a 11411 11412 4102 +a 11412 11413 4102 +a 11413 11414 4102 +a 11414 11415 4102 +a 11415 11416 4102 +a 11416 11417 4102 +a 11417 11418 4102 +a 11418 11419 4102 +a 11419 11420 4102 +a 11420 11421 4102 +a 11421 11422 4102 +a 11422 11423 4102 +a 11423 11424 4102 +a 11424 11425 4102 +a 11425 11426 4102 +a 11426 11427 4102 +a 11427 11428 4102 +a 11428 11429 4102 +a 11429 11430 4102 +a 11430 11431 4102 +a 11431 11432 4102 +a 11432 11433 4102 +a 11433 11434 4102 +a 11434 11435 4102 +a 11435 11436 4102 +a 11436 11437 4102 +a 11437 11438 4102 +a 11438 11439 4102 +a 11439 11440 4102 +a 11440 11441 4102 +a 11441 11442 4102 +a 11442 11443 4102 +a 11443 11444 4102 +a 11444 11445 4102 +a 11445 11446 4102 +a 11446 11447 4102 +a 11447 11448 4102 +a 11448 11449 4102 +a 11449 11450 4102 +a 11450 11451 4102 +a 11451 11452 4102 +a 11452 11453 4102 +a 11453 11454 4102 +a 11454 11455 4102 +a 11455 11456 4102 +a 11456 11457 4102 +a 11457 11458 4102 +a 11458 11459 4102 +a 11459 11460 4102 +a 11460 11461 4102 +a 11461 11462 4102 +a 11462 11463 4102 +a 11463 11464 4102 +a 11464 11465 4102 +a 11465 11466 4102 +a 11466 11467 4102 +a 11467 11468 4102 +a 11468 11469 4102 +a 11469 11470 4102 +a 11470 11471 4102 +a 11471 11472 4102 +a 11472 11473 4102 +a 11473 11474 4102 +a 11474 11475 4102 +a 11475 11476 4102 +a 11476 11477 4102 +a 11477 11478 4102 +a 11478 11479 4102 +a 11479 11480 4102 +a 11480 11481 4102 +a 11481 11482 4102 +a 11482 11483 4102 +a 11483 11484 4102 +a 11484 11485 4102 +a 11485 11486 4102 +a 11486 11487 4102 +a 11487 11488 4102 +a 11488 11489 4102 +a 11489 11490 4102 +a 11490 11491 4102 +a 11491 11492 4102 +a 11492 11493 4102 +a 11493 11494 4102 +a 11494 11495 4102 +a 11495 11496 4102 +a 11496 11497 4102 +a 11497 11498 4102 +a 11498 11499 4102 +a 11499 11500 4102 +a 11500 11501 4102 +a 11501 11502 4102 +a 11502 11503 4102 +a 11503 11504 4102 +a 11504 11505 4102 +a 11505 11506 4102 +a 11506 11507 4102 +a 11507 11508 4102 +a 11508 11509 4102 +a 11509 11510 4102 +a 11510 11511 4102 +a 11511 11512 4102 +a 11512 11513 4102 +a 11513 11514 4102 +a 11514 11515 4102 +a 11515 11516 4102 +a 11516 11517 4102 +a 11517 11518 4102 +a 11518 11519 4102 +a 11519 11520 4102 +a 11520 11521 4102 +a 11521 11522 4102 +a 11522 11523 4102 +a 11523 11524 4102 +a 11524 11525 4102 +a 11525 11526 4102 +a 11526 11527 4102 +a 11527 11528 4102 +a 11528 11529 4102 +a 11529 11530 4102 +a 11530 11531 4102 +a 11531 11532 4102 +a 11532 11533 4102 +a 11533 11534 4102 +a 11534 11535 4102 +a 11535 11536 4102 +a 11536 11537 4102 +a 11537 11538 4102 +a 11538 11539 4102 +a 11539 11540 4102 +a 11540 11541 4102 +a 11541 11542 4102 +a 11542 11543 4102 +a 11543 11544 4102 +a 11544 11545 4102 +a 11545 11546 4102 +a 11546 11547 4102 +a 11547 11548 4102 +a 11548 11549 4102 +a 11549 11550 4102 +a 11550 11551 4102 +a 11551 11552 4102 +a 11552 11553 4102 +a 11553 11554 4102 +a 11554 11555 4102 +a 11555 11556 4102 +a 11556 11557 4102 +a 11557 11558 4102 +a 11558 11559 4102 +a 11559 11560 4102 +a 11560 11561 4102 +a 11561 11562 4102 +a 11562 11563 4102 +a 11563 11564 4102 +a 11564 11565 4102 +a 11565 11566 4102 +a 11566 11567 4102 +a 11567 11568 4102 +a 11568 11569 4102 +a 11569 11570 4102 +a 11570 11571 4102 +a 11571 11572 4102 +a 11572 11573 4102 +a 11573 11574 4102 +a 11574 11575 4102 +a 11575 11576 4102 +a 11576 11577 4102 +a 11577 11578 4102 +a 11578 11579 4102 +a 11579 11580 4102 +a 11580 11581 4102 +a 11581 11582 4102 +a 11582 11583 4102 +a 11583 11584 4102 +a 11584 11585 4102 +a 11585 11586 4102 +a 11586 11587 4102 +a 11587 11588 4102 +a 11588 11589 4102 +a 11589 11590 4102 +a 11590 11591 4102 +a 11591 11592 4102 +a 11592 11593 4102 +a 11593 11594 4102 +a 11594 11595 4102 +a 11595 11596 4102 +a 11596 11597 4102 +a 11597 11598 4102 +a 11598 11599 4102 +a 11599 11600 4102 +a 11600 11601 4102 +a 11601 11602 4102 +a 11602 11603 4102 +a 11603 11604 4102 +a 11604 11605 4102 +a 11605 11606 4102 +a 11606 11607 4102 +a 11607 11608 4102 +a 11608 11609 4102 +a 11609 11610 4102 +a 11610 11611 4102 +a 11611 11612 4102 +a 11612 11613 4102 +a 11613 11614 4102 +a 11614 11615 4102 +a 11615 11616 4102 +a 11616 11617 4102 +a 11617 11618 4102 +a 11618 11619 4102 +a 11619 11620 4102 +a 11620 11621 4102 +a 11621 11622 4102 +a 11622 11623 4102 +a 11623 11624 4102 +a 11624 11625 4102 +a 11625 11626 4102 +a 11626 11627 4102 +a 11627 11628 4102 +a 11628 11629 4102 +a 11629 11630 4102 +a 11630 11631 4102 +a 11631 11632 4102 +a 11632 11633 4102 +a 11633 11634 4102 +a 11634 11635 4102 +a 11635 11636 4102 +a 11636 11637 4102 +a 11637 11638 4102 +a 11638 11639 4102 +a 11639 11640 4102 +a 11640 11641 4102 +a 11641 11642 4102 +a 11642 11643 4102 +a 11643 11644 4102 +a 11644 11645 4102 +a 11645 11646 4102 +a 11646 11647 4102 +a 11647 11648 4102 +a 11648 11649 4102 +a 11649 11650 4102 +a 11650 11651 4102 +a 11651 11652 4102 +a 11652 11653 4102 +a 11653 11654 4102 +a 11654 11655 4102 +a 11655 11656 4102 +a 11656 11657 4102 +a 11657 11658 4102 +a 11658 11659 4102 +a 11659 11660 4102 +a 11660 11661 4102 +a 11661 11662 4102 +a 11662 11663 4102 +a 11663 11664 4102 +a 11664 11665 4102 +a 11665 11666 4102 +a 11666 11667 4102 +a 11667 11668 4102 +a 11668 11669 4102 +a 11669 11670 4102 +a 11670 11671 4102 +a 11671 11672 4102 +a 11672 11673 4102 +a 11673 11674 4102 +a 11674 11675 4102 +a 11675 11676 4102 +a 11676 11677 4102 +a 11677 11678 4102 +a 11678 11679 4102 +a 11679 11680 4102 +a 11680 11681 4102 +a 11681 11682 4102 +a 11682 11683 4102 +a 11683 11684 4102 +a 11684 11685 4102 +a 11685 11686 4102 +a 11686 11687 4102 +a 11687 11688 4102 +a 11688 11689 4102 +a 11689 11690 4102 +a 11690 11691 4102 +a 11691 11692 4102 +a 11692 11693 4102 +a 11693 11694 4102 +a 11694 11695 4102 +a 11695 11696 4102 +a 11696 11697 4102 +a 11697 11698 4102 +a 11698 11699 4102 +a 11699 11700 4102 +a 11700 11701 4102 +a 11701 11702 4102 +a 11702 11703 4102 +a 11703 11704 4102 +a 11704 11705 4102 +a 11705 11706 4102 +a 11706 11707 4102 +a 11707 11708 4102 +a 11708 11709 4102 +a 11709 11710 4102 +a 11710 11711 4102 +a 11711 11712 4102 +a 11712 11713 4102 +a 11713 11714 4102 +a 11714 11715 4102 +a 11715 11716 4102 +a 11716 11717 4102 +a 11717 11718 4102 +a 11718 11719 4102 +a 11719 11720 4102 +a 11720 11721 4102 +a 11721 11722 4102 +a 11722 11723 4102 +a 11723 11724 4102 +a 11724 11725 4102 +a 11725 11726 4102 +a 11726 11727 4102 +a 11727 11728 4102 +a 11728 11729 4102 +a 11729 11730 4102 +a 11730 11731 4102 +a 11731 11732 4102 +a 11732 11733 4102 +a 11733 11734 4102 +a 11734 11735 4102 +a 11735 11736 4102 +a 11736 11737 4102 +a 11737 11738 4102 +a 11738 11739 4102 +a 11739 11740 4102 +a 11740 11741 4102 +a 11741 11742 4102 +a 11742 11743 4102 +a 11743 11744 4102 +a 11744 11745 4102 +a 11745 11746 4102 +a 11746 11747 4102 +a 11747 11748 4102 +a 11748 11749 4102 +a 11749 11750 4102 +a 11750 11751 4102 +a 11751 11752 4102 +a 11752 11753 4102 +a 11753 11754 4102 +a 11754 11755 4102 +a 11755 11756 4102 +a 11756 11757 4102 +a 11757 11758 4102 +a 11758 11759 4102 +a 11759 11760 4102 +a 11760 11761 4102 +a 11761 11762 4102 +a 11762 11763 4102 +a 11763 11764 4102 +a 11764 11765 4102 +a 11765 11766 4102 +a 11766 11767 4102 +a 11767 11768 4102 +a 11768 11769 4102 +a 11769 11770 4102 +a 11770 11771 4102 +a 11771 11772 4102 +a 11772 11773 4102 +a 11773 11774 4102 +a 11774 11775 4102 +a 11775 11776 4102 +a 11776 11777 4102 +a 11777 11778 4102 +a 11778 11779 4102 +a 11779 11780 4102 +a 11780 11781 4102 +a 11781 11782 4102 +a 11782 11783 4102 +a 11783 11784 4102 +a 11784 11785 4102 +a 11785 11786 4102 +a 11786 11787 4102 +a 11787 11788 4102 +a 11788 11789 4102 +a 11789 11790 4102 +a 11790 11791 4102 +a 11791 11792 4102 +a 11792 11793 4102 +a 11793 11794 4102 +a 11794 11795 4102 +a 11795 11796 4102 +a 11796 11797 4102 +a 11797 11798 4102 +a 11798 11799 4102 +a 11799 11800 4102 +a 11800 11801 4102 +a 11801 11802 4102 +a 11802 11803 4102 +a 11803 11804 4102 +a 11804 11805 4102 +a 11805 11806 4102 +a 11806 11807 4102 +a 11807 11808 4102 +a 11808 11809 4102 +a 11809 11810 4102 +a 11810 11811 4102 +a 11811 11812 4102 +a 11812 11813 4102 +a 11813 11814 4102 +a 11814 11815 4102 +a 11815 11816 4102 +a 11816 11817 4102 +a 11817 11818 4102 +a 11818 11819 4102 +a 11819 11820 4102 +a 11820 11821 4102 +a 11821 11822 4102 +a 11822 11823 4102 +a 11823 11824 4102 +a 11824 11825 4102 +a 11825 11826 4102 +a 11826 11827 4102 +a 11827 11828 4102 +a 11828 11829 4102 +a 11829 11830 4102 +a 11830 11831 4102 +a 11831 11832 4102 +a 11832 11833 4102 +a 11833 11834 4102 +a 11834 11835 4102 +a 11835 11836 4102 +a 11836 11837 4102 +a 11837 11838 4102 +a 11838 11839 4102 +a 11839 11840 4102 +a 11840 11841 4102 +a 11841 11842 4102 +a 11842 11843 4102 +a 11843 11844 4102 +a 11844 11845 4102 +a 11845 11846 4102 +a 11846 11847 4102 +a 11847 11848 4102 +a 11848 11849 4102 +a 11849 11850 4102 +a 11850 11851 4102 +a 11851 11852 4102 +a 11852 11853 4102 +a 11853 11854 4102 +a 11854 11855 4102 +a 11855 11856 4102 +a 11856 11857 4102 +a 11857 11858 4102 +a 11858 11859 4102 +a 11859 11860 4102 +a 11860 11861 4102 +a 11861 11862 4102 +a 11862 11863 4102 +a 11863 11864 4102 +a 11864 11865 4102 +a 11865 11866 4102 +a 11866 11867 4102 +a 11867 11868 4102 +a 11868 11869 4102 +a 11869 11870 4102 +a 11870 11871 4102 +a 11871 11872 4102 +a 11872 11873 4102 +a 11873 11874 4102 +a 11874 11875 4102 +a 11875 11876 4102 +a 11876 11877 4102 +a 11877 11878 4102 +a 11878 11879 4102 +a 11879 11880 4102 +a 11880 11881 4102 +a 11881 11882 4102 +a 11882 11883 4102 +a 11883 11884 4102 +a 11884 11885 4102 +a 11885 11886 4102 +a 11886 11887 4102 +a 11887 11888 4102 +a 11888 11889 4102 +a 11889 11890 4102 +a 11890 11891 4102 +a 11891 11892 4102 +a 11892 11893 4102 +a 11893 11894 4102 +a 11894 11895 4102 +a 11895 11896 4102 +a 11896 11897 4102 +a 11897 11898 4102 +a 11898 11899 4102 +a 11899 11900 4102 +a 11900 11901 4102 +a 11901 11902 4102 +a 11902 11903 4102 +a 11903 11904 4102 +a 11904 11905 4102 +a 11905 11906 4102 +a 11906 11907 4102 +a 11907 11908 4102 +a 11908 11909 4102 +a 11909 11910 4102 +a 11910 11911 4102 +a 11911 11912 4102 +a 11912 11913 4102 +a 11913 11914 4102 +a 11914 11915 4102 +a 11915 11916 4102 +a 11916 11917 4102 +a 11917 11918 4102 +a 11918 11919 4102 +a 11919 11920 4102 +a 11920 11921 4102 +a 11921 11922 4102 +a 11922 11923 4102 +a 11923 11924 4102 +a 11924 11925 4102 +a 11925 11926 4102 +a 11926 11927 4102 +a 11927 11928 4102 +a 11928 11929 4102 +a 11929 11930 4102 +a 11930 11931 4102 +a 11931 11932 4102 +a 11932 11933 4102 +a 11933 11934 4102 +a 11934 11935 4102 +a 11935 11936 4102 +a 11936 11937 4102 +a 11937 11938 4102 +a 11938 11939 4102 +a 11939 11940 4102 +a 11940 11941 4102 +a 11941 11942 4102 +a 11942 11943 4102 +a 11943 11944 4102 +a 11944 11945 4102 +a 11945 11946 4102 +a 11946 11947 4102 +a 11947 11948 4102 +a 11948 11949 4102 +a 11949 11950 4102 +a 11950 11951 4102 +a 11951 11952 4102 +a 11952 11953 4102 +a 11953 11954 4102 +a 11954 11955 4102 +a 11955 11956 4102 +a 11956 11957 4102 +a 11957 11958 4102 +a 11958 11959 4102 +a 11959 11960 4102 +a 11960 11961 4102 +a 11961 11962 4102 +a 11962 11963 4102 +a 11963 11964 4102 +a 11964 11965 4102 +a 11965 11966 4102 +a 11966 11967 4102 +a 11967 11968 4102 +a 11968 11969 4102 +a 11969 11970 4102 +a 11970 11971 4102 +a 11971 11972 4102 +a 11972 11973 4102 +a 11973 11974 4102 +a 11974 11975 4102 +a 11975 11976 4102 +a 11976 11977 4102 +a 11977 11978 4102 +a 11978 11979 4102 +a 11979 11980 4102 +a 11980 11981 4102 +a 11981 11982 4102 +a 11982 11983 4102 +a 11983 11984 4102 +a 11984 11985 4102 +a 11985 11986 4102 +a 11986 11987 4102 +a 11987 11988 4102 +a 11988 11989 4102 +a 11989 11990 4102 +a 11990 11991 4102 +a 11991 11992 4102 +a 11992 11993 4102 +a 11993 11994 4102 +a 11994 11995 4102 +a 11995 11996 4102 +a 11996 11997 4102 +a 11997 11998 4102 +a 11998 11999 4102 +a 11999 12000 4102 +a 12000 12001 4102 +a 12001 12002 4102 +a 12002 12003 4102 +a 12003 12004 4102 +a 12004 12005 4102 +a 12005 12006 4102 +a 12006 12007 4102 +a 12007 12008 4102 +a 12008 12009 4102 +a 12009 12010 4102 +a 12010 12011 4102 +a 12011 12012 4102 +a 12012 12013 4102 +a 12013 12014 4102 +a 12014 12015 4102 +a 12015 12016 4102 +a 12016 12017 4102 +a 12017 12018 4102 +a 12018 12019 4102 +a 12019 12020 4102 +a 12020 12021 4102 +a 12021 12022 4102 +a 12022 12023 4102 +a 12023 12024 4102 +a 12024 12025 4102 +a 12025 12026 4102 +a 12026 12027 4102 +a 12027 12028 4102 +a 12028 12029 4102 +a 12029 12030 4102 +a 12030 12031 4102 +a 12031 12032 4102 +a 12032 12033 4102 +a 12033 12034 4102 +a 12034 12035 4102 +a 12035 12036 4102 +a 12036 12037 4102 +a 12037 12038 4102 +a 12038 12039 4102 +a 12039 12040 4102 +a 12040 12041 4102 +a 12041 12042 4102 +a 12042 12043 4102 +a 12043 12044 4102 +a 12044 12045 4102 +a 12045 12046 4102 +a 12046 12047 4102 +a 12047 12048 4102 +a 12048 12049 4102 +a 12049 12050 4102 +a 12050 12051 4102 +a 12051 12052 4102 +a 12052 12053 4102 +a 12053 12054 4102 +a 12054 12055 4102 +a 12055 12056 4102 +a 12056 12057 4102 +a 12057 12058 4102 +a 12058 12059 4102 +a 12059 12060 4102 +a 12060 12061 4102 +a 12061 12062 4102 +a 12062 12063 4102 +a 12063 12064 4102 +a 12064 12065 4102 +a 12065 12066 4102 +a 12066 12067 4102 +a 12067 12068 4102 +a 12068 12069 4102 +a 12069 12070 4102 +a 12070 12071 4102 +a 12071 12072 4102 +a 12072 12073 4102 +a 12073 12074 4102 +a 12074 12075 4102 +a 12075 12076 4102 +a 12076 12077 4102 +a 12077 12078 4102 +a 12078 12079 4102 +a 12079 12080 4102 +a 12080 12081 4102 +a 12081 12082 4102 +a 12082 12083 4102 +a 12083 12084 4102 +a 12084 12085 4102 +a 12085 12086 4102 +a 12086 12087 4102 +a 12087 12088 4102 +a 12088 12089 4102 +a 12089 12090 4102 +a 12090 12091 4102 +a 12091 12092 4102 +a 12092 12093 4102 +a 12093 12094 4102 +a 12094 12095 4102 +a 12095 12096 4102 +a 12096 12097 4102 +a 12097 12098 4102 +a 12098 12099 4102 +a 12099 12100 4102 +a 12100 12101 4102 +a 12101 12102 4102 +a 12102 12103 4102 +a 12103 12104 4102 +a 12104 12105 4102 +a 12105 12106 4102 +a 12106 12107 4102 +a 12107 12108 4102 +a 12108 12109 4102 +a 12109 12110 4102 +a 12110 12111 4102 +a 12111 12112 4102 +a 12112 12113 4102 +a 12113 12114 4102 +a 12114 12115 4102 +a 12115 12116 4102 +a 12116 12117 4102 +a 12117 12118 4102 +a 12118 12119 4102 +a 12119 12120 4102 +a 12120 12121 4102 +a 12121 12122 4102 +a 12122 12123 4102 +a 12123 12124 4102 +a 12124 12125 4102 +a 12125 12126 4102 +a 12126 12127 4102 +a 12127 12128 4102 +a 12128 12129 4102 +a 12129 12130 4102 +a 12130 12131 4102 +a 12131 12132 4102 +a 12132 12133 4102 +a 12133 12134 4102 +a 12134 12135 4102 +a 12135 12136 4102 +a 12136 12137 4102 +a 12137 12138 4102 +a 12138 12139 4102 +a 12139 12140 4102 +a 12140 12141 4102 +a 12141 12142 4102 +a 12142 12143 4102 +a 12143 12144 4102 +a 12144 12145 4102 +a 12145 12146 4102 +a 12146 12147 4102 +a 12147 12148 4102 +a 12148 12149 4102 +a 12149 12150 4102 +a 12150 12151 4102 +a 12151 12152 4102 +a 12152 12153 4102 +a 12153 12154 4102 +a 12154 12155 4102 +a 12155 12156 4102 +a 12156 12157 4102 +a 12157 12158 4102 +a 12158 12159 4102 +a 12159 12160 4102 +a 12160 12161 4102 +a 12161 12162 4102 +a 12162 12163 4102 +a 12163 12164 4102 +a 12164 12165 4102 +a 12165 12166 4102 +a 12166 12167 4102 +a 12167 12168 4102 +a 12168 12169 4102 +a 12169 12170 4102 +a 12170 12171 4102 +a 12171 12172 4102 +a 12172 12173 4102 +a 12173 12174 4102 +a 12174 12175 4102 +a 12175 12176 4102 +a 12176 12177 4102 +a 12177 12178 4102 +a 12178 12179 4102 +a 12179 12180 4102 +a 12180 12181 4102 +a 12181 12182 4102 +a 12182 12183 4102 +a 12183 12184 4102 +a 12184 12185 4102 +a 12185 12186 4102 +a 12186 12187 4102 +a 12187 12188 4102 +a 12188 12189 4102 +a 12189 12190 4102 +a 12190 12191 4102 +a 12191 12192 4102 +a 12192 12193 4102 +a 12193 12194 4102 +a 12194 12195 4102 +a 12195 12196 4102 +a 12196 12197 4102 +a 12197 12198 4102 +a 12198 12199 4102 +a 12199 12200 4102 +a 12200 12201 4102 +a 12201 12202 4102 +a 12202 12203 4102 +a 12203 12204 4102 +a 12204 12205 4102 +a 12205 12206 4102 +a 12206 12207 4102 +a 12207 12208 4102 +a 12208 12209 4102 +a 12209 12210 4102 +a 12210 12211 4102 +a 12211 12212 4102 +a 12212 12213 4102 +a 12213 12214 4102 +a 12214 12215 4102 +a 12215 12216 4102 +a 12216 12217 4102 +a 12217 12218 4102 +a 12218 12219 4102 +a 12219 12220 4102 +a 12220 12221 4102 +a 12221 12222 4102 +a 12222 12223 4102 +a 12223 12224 4102 +a 12224 12225 4102 +a 12225 12226 4102 +a 12226 12227 4102 +a 12227 12228 4102 +a 12228 12229 4102 +a 12229 12230 4102 +a 12230 12231 4102 +a 12231 12232 4102 +a 12232 12233 4102 +a 12233 12234 4102 +a 12234 12235 4102 +a 12235 12236 4102 +a 12236 12237 4102 +a 12237 12238 4102 +a 12238 12239 4102 +a 12239 12240 4102 +a 12240 12241 4102 +a 12241 12242 4102 +a 12242 12243 4102 +a 12243 12244 4102 +a 12244 12245 4102 +a 12245 12246 4102 +a 12246 12247 4102 +a 12247 12248 4102 +a 12248 12249 4102 +a 12249 12250 4102 +a 12250 12251 4102 +a 12251 12252 4102 +a 12252 12253 4102 +a 12253 12254 4102 +a 12254 12255 4102 +a 12255 12256 4102 +a 12256 12257 4102 +a 12257 12258 4102 +a 12258 12259 4102 +a 12259 12260 4102 +a 12260 12261 4102 +a 12261 12262 4102 +a 12262 12263 4102 +a 12263 12264 4102 +a 12264 12265 4102 +a 12265 12266 4102 +a 12266 12267 4102 +a 12267 12268 4102 +a 12268 12269 4102 +a 12269 12270 4102 +a 12270 12271 4102 +a 12271 12272 4102 +a 12272 12273 4102 +a 12273 12274 4102 +a 12274 12275 4102 +a 12275 12276 4102 +a 12276 12277 4102 +a 12277 12278 4102 +a 12278 12279 4102 +a 12279 12280 4102 +a 12280 12281 4102 +a 12281 12282 4102 +a 12282 12283 4102 +a 12283 12284 4102 +a 12284 12285 4102 +a 12285 12286 4102 +a 12286 12287 4102 +a 12287 12288 4102 +a 12288 12289 4102 +a 12289 12290 4102 +a 12290 12291 4102 +a 12291 12292 4102 +a 12292 12293 4102 +a 12293 12294 4102 +a 12294 12295 4102 +a 12295 12296 4102 +a 12296 12297 4102 +a 12297 12298 4102 +a 12298 12299 4102 +a 12299 12300 4102 +a 12300 12301 4102 +a 12301 12302 4102 +a 12302 12303 4102 +a 12303 12304 4102 +a 12304 12305 4102 +a 12305 12306 4102 +a 12306 12307 4102 +a 12307 12308 4102 +a 12308 12309 4102 +a 12309 12310 4102 +a 12310 12311 4102 +a 12311 12312 4102 +a 12312 12313 4102 +a 12313 12314 4102 +a 12314 12315 4102 +a 12315 12316 4102 +a 12316 12317 4102 +a 12317 12318 4102 +a 12318 12319 4102 +a 12319 12320 4102 +a 12320 12321 4102 +a 12321 12322 4102 +a 12322 12323 4102 +a 12323 12324 4102 +a 12324 12325 4102 +a 12325 12326 4102 +a 12326 12327 4102 +a 12327 12328 4102 +a 12328 12329 4102 +a 12329 12330 4102 +a 12330 12331 4102 +a 12331 12332 4102 +a 12332 12333 4102 +a 12333 12334 4102 +a 12334 12335 4102 +a 12335 12336 4102 +a 12336 12337 4102 +a 12337 12338 4102 +a 12338 12339 4102 +a 12339 12340 4102 +a 12340 12341 4102 +a 12341 12342 4102 +a 12342 12343 4102 +a 12343 12344 4102 +a 12344 12345 4102 +a 12345 12346 4102 +a 12346 12347 4102 +a 12347 12348 4102 +a 12348 12349 4102 +a 12349 12350 4102 +a 12350 12351 4102 +a 12351 12352 4102 +a 12352 12353 4102 +a 12353 12354 4102 +a 12354 12355 4102 +a 12355 12356 4102 +a 12356 12357 4102 +a 12357 12358 4102 +a 12358 12359 4102 +a 12359 12360 4102 +a 12360 12361 4102 +a 12361 12362 4102 +a 12362 12363 4102 +a 12363 12364 4102 +a 12364 12365 4102 +a 12365 12366 4102 +a 12366 12367 4102 +a 12367 12368 4102 +a 12368 12369 4102 +a 12369 12370 4102 +a 12370 12371 4102 +a 12371 12372 4102 +a 12372 12373 4102 +a 12373 12374 4102 +a 12374 12375 4102 +a 12375 12376 4102 +a 12376 12377 4102 +a 12377 12378 4102 +a 12378 12379 4102 +a 12379 12380 4102 +a 12380 12381 4102 +a 12381 12382 4102 +a 12382 12383 4102 +a 12383 12384 4102 +a 12384 12385 4102 +a 12385 12386 4102 +a 12386 12387 4102 +a 12387 12388 4102 +a 12388 12389 4102 +a 12389 12390 4102 +a 12390 12391 4102 +a 12391 12392 4102 +a 12392 12393 4102 +a 12393 12394 4102 +a 12394 12395 4102 +a 12395 12396 4102 +a 12396 12397 4102 +a 12397 12398 4102 +a 12398 12399 4102 +a 12399 12400 4102 +a 12400 12401 4102 +a 12401 12402 4102 +a 12402 12403 4102 +a 12403 12404 4102 +a 12404 12405 4102 +a 12405 12406 4102 +a 12406 12407 4102 +a 12407 12408 4102 +a 12408 12409 4102 +a 12409 12410 4102 +a 12410 12411 4102 +a 12411 12412 4102 +a 12412 12413 4102 +a 12413 12414 4102 +a 12414 12415 4102 +a 12415 12416 4102 +a 12416 12417 4102 +a 12417 12418 4102 +a 12418 12419 4102 +a 12419 12420 4102 +a 12420 12421 4102 +a 12421 12422 4102 +a 12422 12423 4102 +a 12423 12424 4102 +a 12424 12425 4102 +a 12425 12426 4102 +a 12426 12427 4102 +a 12427 12428 4102 +a 12428 12429 4102 +a 12429 12430 4102 +a 12430 12431 4102 +a 12431 12432 4102 +a 12432 12433 4102 +a 12433 12434 4102 +a 12434 12435 4102 +a 12435 12436 4102 +a 12436 12437 4102 +a 12437 12438 4102 +a 12438 12439 4102 +a 12439 12440 4102 +a 12440 12441 4102 +a 12441 12442 4102 +a 12442 12443 4102 +a 12443 12444 4102 +a 12444 12445 4102 +a 12445 12446 4102 +a 12446 12447 4102 +a 12447 12448 4102 +a 12448 12449 4102 +a 12449 12450 4102 +a 12450 12451 4102 +a 12451 12452 4102 +a 12452 12453 4102 +a 12453 12454 4102 +a 12454 12455 4102 +a 12455 12456 4102 +a 12456 12457 4102 +a 12457 12458 4102 +a 12458 12459 4102 +a 12459 12460 4102 +a 12460 12461 4102 +a 12461 12462 4102 +a 12462 12463 4102 +a 12463 12464 4102 +a 12464 12465 4102 +a 12465 12466 4102 +a 12466 12467 4102 +a 12467 12468 4102 +a 12468 12469 4102 +a 12469 12470 4102 +a 12470 12471 4102 +a 12471 12472 4102 +a 12472 12473 4102 +a 12473 12474 4102 +a 12474 12475 4102 +a 12475 12476 4102 +a 12476 12477 4102 +a 12477 12478 4102 +a 12478 12479 4102 +a 12479 12480 4102 +a 12480 12481 4102 +a 12481 12482 4102 +a 12482 12483 4102 +a 12483 12484 4102 +a 12484 12485 4102 +a 12485 12486 4102 +a 12486 12487 4102 +a 12487 12488 4102 +a 12488 12489 4102 +a 12489 12490 4102 +a 12490 12491 4102 +a 12491 12492 4102 +a 12492 12493 4102 +a 12493 12494 4102 +a 12494 12495 4102 +a 12495 12496 4102 +a 12496 12497 4102 +a 12497 12498 4102 +a 12498 12499 4102 +a 12499 12500 4102 +a 12500 12501 4102 +a 12501 12502 4102 +a 12502 12503 4102 +a 12503 12504 4102 +a 12504 12505 4102 +a 12505 12506 4102 +a 12506 12507 4102 +a 12507 12508 4102 +a 12508 12509 4102 +a 12509 12510 4102 +a 12510 12511 4102 +a 12511 12512 4102 +a 12512 12513 4102 +a 12513 12514 4102 +a 12514 12515 4102 +a 12515 12516 4102 +a 12516 12517 4102 +a 12517 12518 4102 +a 12518 12519 4102 +a 12519 12520 4102 +a 12520 12521 4102 +a 12521 12522 4102 +a 12522 12523 4102 +a 12523 12524 4102 +a 12524 12525 4102 +a 12525 12526 4102 +a 12526 12527 4102 +a 12527 12528 4102 +a 12528 12529 4102 +a 12529 12530 4102 +a 12530 12531 4102 +a 12531 12532 4102 +a 12532 12533 4102 +a 12533 12534 4102 +a 12534 12535 4102 +a 12535 12536 4102 +a 12536 12537 4102 +a 12537 12538 4102 +a 12538 12539 4102 +a 12539 12540 4102 +a 12540 12541 4102 +a 12541 12542 4102 +a 12542 12543 4102 +a 12543 12544 4102 +a 12544 12545 4102 +a 12545 12546 4102 +a 12546 12547 4102 +a 12547 12548 4102 +a 12548 12549 4102 +a 12549 12550 4102 +a 12550 12551 4102 +a 12551 12552 4102 +a 12552 12553 4102 +a 12553 12554 4102 +a 12554 12555 4102 +a 12555 12556 4102 +a 12556 12557 4102 +a 12557 12558 4102 +a 12558 12559 4102 +a 12559 12560 4102 +a 12560 12561 4102 +a 12561 12562 4102 +a 12562 12563 4102 +a 12563 12564 4102 +a 12564 12565 4102 +a 12565 12566 4102 +a 12566 12567 4102 +a 12567 12568 4102 +a 12568 12569 4102 +a 12569 12570 4102 +a 12570 12571 4102 +a 12571 12572 4102 +a 12572 12573 4102 +a 12573 12574 4102 +a 12574 12575 4102 +a 12575 12576 4102 +a 12576 12577 4102 +a 12577 12578 4102 +a 12578 12579 4102 +a 12579 12580 4102 +a 12580 12581 4102 +a 12581 12582 4102 +a 12582 12583 4102 +a 12583 12584 4102 +a 12584 12585 4102 +a 12585 12586 4102 +a 12586 12587 4102 +a 12587 12588 4102 +a 12588 12589 4102 +a 12589 12590 4102 +a 12590 12591 4102 +a 12591 12592 4102 +a 12592 12593 4102 +a 12593 12594 4102 +a 12594 12595 4102 +a 12595 12596 4102 +a 12596 12597 4102 +a 12597 12598 4102 +a 12598 12599 4102 +a 12599 12600 4102 +a 12600 12601 4102 +a 12601 12602 4102 +a 12602 12603 4102 +a 12603 12604 4102 +a 12604 12605 4102 +a 12605 12606 4102 +a 12606 12607 4102 +a 12607 12608 4102 +a 12608 12609 4102 +a 12609 12610 4102 +a 12610 12611 4102 +a 12611 12612 4102 +a 12612 12613 4102 +a 12613 12614 4102 +a 12614 12615 4102 +a 12615 12616 4102 +a 12616 12617 4102 +a 12617 12618 4102 +a 12618 12619 4102 +a 12619 12620 4102 +a 12620 12621 4102 +a 12621 12622 4102 +a 12622 12623 4102 +a 12623 12624 4102 +a 12624 12625 4102 +a 12625 12626 4102 +a 12626 12627 4102 +a 12627 12628 4102 +a 12628 12629 4102 +a 12629 12630 4102 +a 12630 12631 4102 +a 12631 12632 4102 +a 12632 12633 4102 +a 12633 12634 4102 +a 12634 12635 4102 +a 12635 12636 4102 +a 12636 12637 4102 +a 12637 12638 4102 +a 12638 12639 4102 +a 12639 12640 4102 +a 12640 12641 4102 +a 12641 12642 4102 +a 12642 12643 4102 +a 12643 12644 4102 +a 12644 12645 4102 +a 12645 12646 4102 +a 12646 12647 4102 +a 12647 12648 4102 +a 12648 12649 4102 +a 12649 12650 4102 +a 12650 12651 4102 +a 12651 12652 4102 +a 12652 12653 4102 +a 12653 12654 4102 +a 12654 12655 4102 +a 12655 12656 4102 +a 12656 12657 4102 +a 12657 12658 4102 +a 12658 12659 4102 +a 12659 12660 4102 +a 12660 12661 4102 +a 12661 12662 4102 +a 12662 12663 4102 +a 12663 12664 4102 +a 12664 12665 4102 +a 12665 12666 4102 +a 12666 12667 4102 +a 12667 12668 4102 +a 12668 12669 4102 +a 12669 12670 4102 +a 12670 12671 4102 +a 12671 12672 4102 +a 12672 12673 4102 +a 12673 12674 4102 +a 12674 12675 4102 +a 12675 12676 4102 +a 12676 12677 4102 +a 12677 12678 4102 +a 12678 12679 4102 +a 12679 12680 4102 +a 12680 12681 4102 +a 12681 12682 4102 +a 12682 12683 4102 +a 12683 12684 4102 +a 12684 12685 4102 +a 12685 12686 4102 +a 12686 12687 4102 +a 12687 12688 4102 +a 12688 12689 4102 +a 12689 12690 4102 +a 12690 12691 4102 +a 12691 12692 4102 +a 12692 12693 4102 +a 12693 12694 4102 +a 12694 12695 4102 +a 12695 12696 4102 +a 12696 12697 4102 +a 12697 12698 4102 +a 12698 12699 4102 +a 12699 12700 4102 +a 12700 12701 4102 +a 12701 12702 4102 +a 12702 12703 4102 +a 12703 12704 4102 +a 12704 12705 4102 +a 12705 12706 4102 +a 12706 12707 4102 +a 12707 12708 4102 +a 12708 12709 4102 +a 12709 12710 4102 +a 12710 12711 4102 +a 12711 12712 4102 +a 12712 12713 4102 +a 12713 12714 4102 +a 12714 12715 4102 +a 12715 12716 4102 +a 12716 12717 4102 +a 12717 12718 4102 +a 12718 12719 4102 +a 12719 12720 4102 +a 12720 12721 4102 +a 12721 12722 4102 +a 12722 12723 4102 +a 12723 12724 4102 +a 12724 12725 4102 +a 12725 12726 4102 +a 12726 12727 4102 +a 12727 12728 4102 +a 12728 12729 4102 +a 12729 12730 4102 +a 12730 12731 4102 +a 12731 12732 4102 +a 12732 12733 4102 +a 12733 12734 4102 +a 12734 12735 4102 +a 12735 12736 4102 +a 12736 12737 4102 +a 12737 12738 4102 +a 12738 12739 4102 +a 12739 12740 4102 +a 12740 12741 4102 +a 12741 12742 4102 +a 12742 12743 4102 +a 12743 12744 4102 +a 12744 12745 4102 +a 12745 12746 4102 +a 12746 12747 4102 +a 12747 12748 4102 +a 12748 12749 4102 +a 12749 12750 4102 +a 12750 12751 4102 +a 12751 12752 4102 +a 12752 12753 4102 +a 12753 12754 4102 +a 12754 12755 4102 +a 12755 12756 4102 +a 12756 12757 4102 +a 12757 12758 4102 +a 12758 12759 4102 +a 12759 12760 4102 +a 12760 12761 4102 +a 12761 12762 4102 +a 12762 12763 4102 +a 12763 12764 4102 +a 12764 12765 4102 +a 12765 12766 4102 +a 12766 12767 4102 +a 12767 12768 4102 +a 12768 12769 4102 +a 12769 12770 4102 +a 12770 12771 4102 +a 12771 12772 4102 +a 12772 12773 4102 +a 12773 12774 4102 +a 12774 12775 4102 +a 12775 12776 4102 +a 12776 12777 4102 +a 12777 12778 4102 +a 12778 12779 4102 +a 12779 12780 4102 +a 12780 12781 4102 +a 12781 12782 4102 +a 12782 12783 4102 +a 12783 12784 4102 +a 12784 12785 4102 +a 12785 12786 4102 +a 12786 12787 4102 +a 12787 12788 4102 +a 12788 12789 4102 +a 12789 12790 4102 +a 12790 12791 4102 +a 12791 12792 4102 +a 12792 12793 4102 +a 12793 12794 4102 +a 12794 12795 4102 +a 12795 12796 4102 +a 12796 12797 4102 +a 12797 12798 4102 +a 12798 12799 4102 +a 12799 12800 4102 +a 12800 12801 4102 +a 12801 12802 4102 +a 12802 12803 4102 +a 12803 12804 4102 +a 12804 12805 4102 +a 12805 12806 4102 +a 12806 12807 4102 +a 12807 12808 4102 +a 12808 12809 4102 +a 12809 12810 4102 +a 12810 12811 4102 +a 12811 12812 4102 +a 12812 12813 4102 +a 12813 12814 4102 +a 12814 12815 4102 +a 12815 12816 4102 +a 12816 12817 4102 +a 12817 12818 4102 +a 12818 12819 4102 +a 12819 12820 4102 +a 12820 12821 4102 +a 12821 12822 4102 +a 12822 12823 4102 +a 12823 12824 4102 +a 12824 12825 4102 +a 12825 12826 4102 +a 12826 12827 4102 +a 12827 12828 4102 +a 12828 12829 4102 +a 12829 12830 4102 +a 12830 12831 4102 +a 12831 12832 4102 +a 12832 12833 4102 +a 12833 12834 4102 +a 12834 12835 4102 +a 12835 12836 4102 +a 12836 12837 4102 +a 12837 12838 4102 +a 12838 12839 4102 +a 12839 12840 4102 +a 12840 12841 4102 +a 12841 12842 4102 +a 12842 12843 4102 +a 12843 12844 4102 +a 12844 12845 4102 +a 12845 12846 4102 +a 12846 12847 4102 +a 12847 12848 4102 +a 12848 12849 4102 +a 12849 12850 4102 +a 12850 12851 4102 +a 12851 12852 4102 +a 12852 12853 4102 +a 12853 12854 4102 +a 12854 12855 4102 +a 12855 12856 4102 +a 12856 12857 4102 +a 12857 12858 4102 +a 12858 12859 4102 +a 12859 12860 4102 +a 12860 12861 4102 +a 12861 12862 4102 +a 12862 12863 4102 +a 12863 12864 4102 +a 12864 12865 4102 +a 12865 12866 4102 +a 12866 12867 4102 +a 12867 12868 4102 +a 12868 12869 4102 +a 12869 12870 4102 +a 12870 12871 4102 +a 12871 12872 4102 +a 12872 12873 4102 +a 12873 12874 4102 +a 12874 12875 4102 +a 12875 12876 4102 +a 12876 12877 4102 +a 12877 12878 4102 +a 12878 12879 4102 +a 12879 12880 4102 +a 12880 12881 4102 +a 12881 12882 4102 +a 12882 12883 4102 +a 12883 12884 4102 +a 12884 12885 4102 +a 12885 12886 4102 +a 12886 12887 4102 +a 12887 12888 4102 +a 12888 12889 4102 +a 12889 12890 4102 +a 12890 12891 4102 +a 12891 12892 4102 +a 12892 12893 4102 +a 12893 12894 4102 +a 12894 12895 4102 +a 12895 12896 4102 +a 12896 12897 4102 +a 12897 12898 4102 +a 12898 12899 4102 +a 12899 12900 4102 +a 12900 12901 4102 +a 12901 12902 4102 +a 12902 12903 4102 +a 12903 12904 4102 +a 12904 12905 4102 +a 12905 12906 4102 +a 12906 12907 4102 +a 12907 12908 4102 +a 12908 12909 4102 +a 12909 12910 4102 +a 12910 12911 4102 +a 12911 12912 4102 +a 12912 12913 4102 +a 12913 12914 4102 +a 12914 12915 4102 +a 12915 12916 4102 +a 12916 12917 4102 +a 12917 12918 4102 +a 12918 12919 4102 +a 12919 12920 4102 +a 12920 12921 4102 +a 12921 12922 4102 +a 12922 12923 4102 +a 12923 12924 4102 +a 12924 12925 4102 +a 12925 12926 4102 +a 12926 12927 4102 +a 12927 12928 4102 +a 12928 12929 4102 +a 12929 12930 4102 +a 12930 12931 4102 +a 12931 12932 4102 +a 12932 12933 4102 +a 12933 12934 4102 +a 12934 12935 4102 +a 12935 12936 4102 +a 12936 12937 4102 +a 12937 12938 4102 +a 12938 12939 4102 +a 12939 12940 4102 +a 12940 12941 4102 +a 12941 12942 4102 +a 12942 12943 4102 +a 12943 12944 4102 +a 12944 12945 4102 +a 12945 12946 4102 +a 12946 12947 4102 +a 12947 12948 4102 +a 12948 12949 4102 +a 12949 12950 4102 +a 12950 12951 4102 +a 12951 12952 4102 +a 12952 12953 4102 +a 12953 12954 4102 +a 12954 12955 4102 +a 12955 12956 4102 +a 12956 12957 4102 +a 12957 12958 4102 +a 12958 12959 4102 +a 12959 12960 4102 +a 12960 12961 4102 +a 12961 12962 4102 +a 12962 12963 4102 +a 12963 12964 4102 +a 12964 12965 4102 +a 12965 12966 4102 +a 12966 12967 4102 +a 12967 12968 4102 +a 12968 12969 4102 +a 12969 12970 4102 +a 12970 12971 4102 +a 12971 12972 4102 +a 12972 12973 4102 +a 12973 12974 4102 +a 12974 12975 4102 +a 12975 12976 4102 +a 12976 12977 4102 +a 12977 12978 4102 +a 12978 12979 4102 +a 12979 12980 4102 +a 12980 12981 4102 +a 12981 12982 4102 +a 12982 12983 4102 +a 12983 12984 4102 +a 12984 12985 4102 +a 12985 12986 4102 +a 12986 12987 4102 +a 12987 12988 4102 +a 12988 12989 4102 +a 12989 12990 4102 +a 12990 12991 4102 +a 12991 12992 4102 +a 12992 12993 4102 +a 12993 12994 4102 +a 12994 12995 4102 +a 12995 12996 4102 +a 12996 12997 4102 +a 12997 12998 4102 +a 12998 12999 4102 +a 12999 13000 4102 +a 13000 13001 4102 +a 13001 13002 4102 +a 13002 13003 4102 +a 13003 13004 4102 +a 13004 13005 4102 +a 13005 13006 4102 +a 13006 13007 4102 +a 13007 13008 4102 +a 13008 13009 4102 +a 13009 13010 4102 +a 13010 13011 4102 +a 13011 13012 4102 +a 13012 13013 4102 +a 13013 13014 4102 +a 13014 13015 4102 +a 13015 13016 4102 +a 13016 13017 4102 +a 13017 13018 4102 +a 13018 13019 4102 +a 13019 13020 4102 +a 13020 13021 4102 +a 13021 13022 4102 +a 13022 13023 4102 +a 13023 13024 4102 +a 13024 13025 4102 +a 13025 13026 4102 +a 13026 13027 4102 +a 13027 13028 4102 +a 13028 13029 4102 +a 13029 13030 4102 +a 13030 13031 4102 +a 13031 13032 4102 +a 13032 13033 4102 +a 13033 13034 4102 +a 13034 13035 4102 +a 13035 13036 4102 +a 13036 13037 4102 +a 13037 13038 4102 +a 13038 13039 4102 +a 13039 13040 4102 +a 13040 13041 4102 +a 13041 13042 4102 +a 13042 13043 4102 +a 13043 13044 4102 +a 13044 13045 4102 +a 13045 13046 4102 +a 13046 13047 4102 +a 13047 13048 4102 +a 13048 13049 4102 +a 13049 13050 4102 +a 13050 13051 4102 +a 13051 13052 4102 +a 13052 13053 4102 +a 13053 13054 4102 +a 13054 13055 4102 +a 13055 13056 4102 +a 13056 13057 4102 +a 13057 13058 4102 +a 13058 13059 4102 +a 13059 13060 4102 +a 13060 13061 4102 +a 13061 13062 4102 +a 13062 13063 4102 +a 13063 13064 4102 +a 13064 13065 4102 +a 13065 13066 4102 +a 13066 13067 4102 +a 13067 13068 4102 +a 13068 13069 4102 +a 13069 13070 4102 +a 13070 13071 4102 +a 13071 13072 4102 +a 13072 13073 4102 +a 13073 13074 4102 +a 13074 13075 4102 +a 13075 13076 4102 +a 13076 13077 4102 +a 13077 13078 4102 +a 13078 13079 4102 +a 13079 13080 4102 +a 13080 13081 4102 +a 13081 13082 4102 +a 13082 13083 4102 +a 13083 13084 4102 +a 13084 13085 4102 +a 13085 13086 4102 +a 13086 13087 4102 +a 13087 13088 4102 +a 13088 13089 4102 +a 13089 13090 4102 +a 13090 13091 4102 +a 13091 13092 4102 +a 13092 13093 4102 +a 13093 13094 4102 +a 13094 13095 4102 +a 13095 13096 4102 +a 13096 13097 4102 +a 13097 13098 4102 +a 13098 13099 4102 +a 13099 13100 4102 +a 13100 13101 4102 +a 13101 13102 4102 +a 13102 13103 4102 +a 13103 13104 4102 +a 13104 13105 4102 +a 13105 13106 4102 +a 13106 13107 4102 +a 13107 13108 4102 +a 13108 13109 4102 +a 13109 13110 4102 +a 13110 13111 4102 +a 13111 13112 4102 +a 13112 13113 4102 +a 13113 13114 4102 +a 13114 13115 4102 +a 13115 13116 4102 +a 13116 13117 4102 +a 13117 13118 4102 +a 13118 13119 4102 +a 13119 13120 4102 +a 13120 13121 4102 +a 13121 13122 4102 +a 13122 13123 4102 +a 13123 13124 4102 +a 13124 13125 4102 +a 13125 13126 4102 +a 13126 13127 4102 +a 13127 13128 4102 +a 13128 13129 4102 +a 13129 13130 4102 +a 13130 13131 4102 +a 13131 13132 4102 +a 13132 13133 4102 +a 13133 13134 4102 +a 13134 13135 4102 +a 13135 13136 4102 +a 13136 13137 4102 +a 13137 13138 4102 +a 13138 13139 4102 +a 13139 13140 4102 +a 13140 13141 4102 +a 13141 13142 4102 +a 13142 13143 4102 +a 13143 13144 4102 +a 13144 13145 4102 +a 13145 13146 4102 +a 13146 13147 4102 +a 13147 13148 4102 +a 13148 13149 4102 +a 13149 13150 4102 +a 13150 13151 4102 +a 13151 13152 4102 +a 13152 13153 4102 +a 13153 13154 4102 +a 13154 13155 4102 +a 13155 13156 4102 +a 13156 13157 4102 +a 13157 13158 4102 +a 13158 13159 4102 +a 13159 13160 4102 +a 13160 13161 4102 +a 13161 13162 4102 +a 13162 13163 4102 +a 13163 13164 4102 +a 13164 13165 4102 +a 13165 13166 4102 +a 13166 13167 4102 +a 13167 13168 4102 +a 13168 13169 4102 +a 13169 13170 4102 +a 13170 13171 4102 +a 13171 13172 4102 +a 13172 13173 4102 +a 13173 13174 4102 +a 13174 13175 4102 +a 13175 13176 4102 +a 13176 13177 4102 +a 13177 13178 4102 +a 13178 13179 4102 +a 13179 13180 4102 +a 13180 13181 4102 +a 13181 13182 4102 +a 13182 13183 4102 +a 13183 13184 4102 +a 13184 13185 4102 +a 13185 13186 4102 +a 13186 13187 4102 +a 13187 13188 4102 +a 13188 13189 4102 +a 13189 13190 4102 +a 13190 13191 4102 +a 13191 13192 4102 +a 13192 13193 4102 +a 13193 13194 4102 +a 13194 13195 4102 +a 13195 13196 4102 +a 13196 13197 4102 +a 13197 13198 4102 +a 13198 13199 4102 +a 13199 13200 4102 +a 13200 13201 4102 +a 13201 13202 4102 +a 13202 13203 4102 +a 13203 13204 4102 +a 13204 13205 4102 +a 13205 13206 4102 +a 13206 13207 4102 +a 13207 13208 4102 +a 13208 13209 4102 +a 13209 13210 4102 +a 13210 13211 4102 +a 13211 13212 4102 +a 13212 13213 4102 +a 13213 13214 4102 +a 13214 13215 4102 +a 13215 13216 4102 +a 13216 13217 4102 +a 13217 13218 4102 +a 13218 13219 4102 +a 13219 13220 4102 +a 13220 13221 4102 +a 13221 13222 4102 +a 13222 13223 4102 +a 13223 13224 4102 +a 13224 13225 4102 +a 13225 13226 4102 +a 13226 13227 4102 +a 13227 13228 4102 +a 13228 13229 4102 +a 13229 13230 4102 +a 13230 13231 4102 +a 13231 13232 4102 +a 13232 13233 4102 +a 13233 13234 4102 +a 13234 13235 4102 +a 13235 13236 4102 +a 13236 13237 4102 +a 13237 13238 4102 +a 13238 13239 4102 +a 13239 13240 4102 +a 13240 13241 4102 +a 13241 13242 4102 +a 13242 13243 4102 +a 13243 13244 4102 +a 13244 13245 4102 +a 13245 13246 4102 +a 13246 13247 4102 +a 13247 13248 4102 +a 13248 13249 4102 +a 13249 13250 4102 +a 13250 13251 4102 +a 13251 13252 4102 +a 13252 13253 4102 +a 13253 13254 4102 +a 13254 13255 4102 +a 13255 13256 4102 +a 13256 13257 4102 +a 13257 13258 4102 +a 13258 13259 4102 +a 13259 13260 4102 +a 13260 13261 4102 +a 13261 13262 4102 +a 13262 13263 4102 +a 13263 13264 4102 +a 13264 13265 4102 +a 13265 13266 4102 +a 13266 13267 4102 +a 13267 13268 4102 +a 13268 13269 4102 +a 13269 13270 4102 +a 13270 13271 4102 +a 13271 13272 4102 +a 13272 13273 4102 +a 13273 13274 4102 +a 13274 13275 4102 +a 13275 13276 4102 +a 13276 13277 4102 +a 13277 13278 4102 +a 13278 13279 4102 +a 13279 13280 4102 +a 13280 13281 4102 +a 13281 13282 4102 +a 13282 13283 4102 +a 13283 13284 4102 +a 13284 13285 4102 +a 13285 13286 4102 +a 13286 13287 4102 +a 13287 13288 4102 +a 13288 13289 4102 +a 13289 13290 4102 +a 13290 13291 4102 +a 13291 13292 4102 +a 13292 13293 4102 +a 13293 13294 4102 +a 13294 13295 4102 +a 13295 13296 4102 +a 13296 13297 4102 +a 13297 13298 4102 +a 13298 13299 4102 +a 13299 13300 4102 +a 13300 13301 4102 +a 13301 13302 4102 +a 13302 13303 4102 +a 13303 13304 4102 +a 13304 13305 4102 +a 13305 13306 4102 +a 13306 13307 4102 +a 13307 13308 4102 +a 13308 13309 4102 +a 13309 13310 4102 +a 13310 13311 4102 +a 13311 13312 4102 +a 13312 13313 4102 +a 13313 13314 4102 +a 13314 13315 4102 +a 13315 13316 4102 +a 13316 13317 4102 +a 13317 13318 4102 +a 13318 13319 4102 +a 13319 13320 4102 +a 13320 13321 4102 +a 13321 13322 4102 +a 13322 13323 4102 +a 13323 13324 4102 +a 13324 13325 4102 +a 13325 13326 4102 +a 13326 13327 4102 +a 13327 13328 4102 +a 13328 13329 4102 +a 13329 13330 4102 +a 13330 13331 4102 +a 13331 13332 4102 +a 13332 13333 4102 +a 13333 13334 4102 +a 13334 13335 4102 +a 13335 13336 4102 +a 13336 13337 4102 +a 13337 13338 4102 +a 13338 13339 4102 +a 13339 13340 4102 +a 13340 13341 4102 +a 13341 13342 4102 +a 13342 13343 4102 +a 13343 13344 4102 +a 13344 13345 4102 +a 13345 13346 4102 +a 13346 13347 4102 +a 13347 13348 4102 +a 13348 13349 4102 +a 13349 13350 4102 +a 13350 13351 4102 +a 13351 13352 4102 +a 13352 13353 4102 +a 13353 13354 4102 +a 13354 13355 4102 +a 13355 13356 4102 +a 13356 13357 4102 +a 13357 13358 4102 +a 13358 13359 4102 +a 13359 13360 4102 +a 13360 13361 4102 +a 13361 13362 4102 +a 13362 13363 4102 +a 13363 13364 4102 +a 13364 13365 4102 +a 13365 13366 4102 +a 13366 13367 4102 +a 13367 13368 4102 +a 13368 13369 4102 +a 13369 13370 4102 +a 13370 13371 4102 +a 13371 13372 4102 +a 13372 13373 4102 +a 13373 13374 4102 +a 13374 13375 4102 +a 13375 13376 4102 +a 13376 13377 4102 +a 13377 13378 4102 +a 13378 13379 4102 +a 13379 13380 4102 +a 13380 13381 4102 +a 13381 13382 4102 +a 13382 13383 4102 +a 13383 13384 4102 +a 13384 13385 4102 +a 13385 13386 4102 +a 13386 13387 4102 +a 13387 13388 4102 +a 13388 13389 4102 +a 13389 13390 4102 +a 13390 13391 4102 +a 13391 13392 4102 +a 13392 13393 4102 +a 13393 13394 4102 +a 13394 13395 4102 +a 13395 13396 4102 +a 13396 13397 4102 +a 13397 13398 4102 +a 13398 13399 4102 +a 13399 13400 4102 +a 13400 13401 4102 +a 13401 13402 4102 +a 13402 13403 4102 +a 13403 13404 4102 +a 13404 13405 4102 +a 13405 13406 4102 +a 13406 13407 4102 +a 13407 13408 4102 +a 13408 13409 4102 +a 13409 13410 4102 +a 13410 13411 4102 +a 13411 13412 4102 +a 13412 13413 4102 +a 13413 13414 4102 +a 13414 13415 4102 +a 13415 13416 4102 +a 13416 13417 4102 +a 13417 13418 4102 +a 13418 13419 4102 +a 13419 13420 4102 +a 13420 13421 4102 +a 13421 13422 4102 +a 13422 13423 4102 +a 13423 13424 4102 +a 13424 13425 4102 +a 13425 13426 4102 +a 13426 13427 4102 +a 13427 13428 4102 +a 13428 13429 4102 +a 13429 13430 4102 +a 13430 13431 4102 +a 13431 13432 4102 +a 13432 13433 4102 +a 13433 13434 4102 +a 13434 13435 4102 +a 13435 13436 4102 +a 13436 13437 4102 +a 13437 13438 4102 +a 13438 13439 4102 +a 13439 13440 4102 +a 13440 13441 4102 +a 13441 13442 4102 +a 13442 13443 4102 +a 13443 13444 4102 +a 13444 13445 4102 +a 13445 13446 4102 +a 13446 13447 4102 +a 13447 13448 4102 +a 13448 13449 4102 +a 13449 13450 4102 +a 13450 13451 4102 +a 13451 13452 4102 +a 13452 13453 4102 +a 13453 13454 4102 +a 13454 13455 4102 +a 13455 13456 4102 +a 13456 13457 4102 +a 13457 13458 4102 +a 13458 13459 4102 +a 13459 13460 4102 +a 13460 13461 4102 +a 13461 13462 4102 +a 13462 13463 4102 +a 13463 13464 4102 +a 13464 13465 4102 +a 13465 13466 4102 +a 13466 13467 4102 +a 13467 13468 4102 +a 13468 13469 4102 +a 13469 13470 4102 +a 13470 13471 4102 +a 13471 13472 4102 +a 13472 13473 4102 +a 13473 13474 4102 +a 13474 13475 4102 +a 13475 13476 4102 +a 13476 13477 4102 +a 13477 13478 4102 +a 13478 13479 4102 +a 13479 13480 4102 +a 13480 13481 4102 +a 13481 13482 4102 +a 13482 13483 4102 +a 13483 13484 4102 +a 13484 13485 4102 +a 13485 13486 4102 +a 13486 13487 4102 +a 13487 13488 4102 +a 13488 13489 4102 +a 13489 13490 4102 +a 13490 13491 4102 +a 13491 13492 4102 +a 13492 13493 4102 +a 13493 13494 4102 +a 13494 13495 4102 +a 13495 13496 4102 +a 13496 13497 4102 +a 13497 13498 4102 +a 13498 13499 4102 +a 13499 13500 4102 +a 13500 13501 4102 +a 13501 13502 4102 +a 13502 13503 4102 +a 13503 13504 4102 +a 13504 13505 4102 +a 13505 13506 4102 +a 13506 13507 4102 +a 13507 13508 4102 +a 13508 13509 4102 +a 13509 13510 4102 +a 13510 13511 4102 +a 13511 13512 4102 +a 13512 13513 4102 +a 13513 13514 4102 +a 13514 13515 4102 +a 13515 13516 4102 +a 13516 13517 4102 +a 13517 13518 4102 +a 13518 13519 4102 +a 13519 13520 4102 +a 13520 13521 4102 +a 13521 13522 4102 +a 13522 13523 4102 +a 13523 13524 4102 +a 13524 13525 4102 +a 13525 13526 4102 +a 13526 13527 4102 +a 13527 13528 4102 +a 13528 13529 4102 +a 13529 13530 4102 +a 13530 13531 4102 +a 13531 13532 4102 +a 13532 13533 4102 +a 13533 13534 4102 +a 13534 13535 4102 +a 13535 13536 4102 +a 13536 13537 4102 +a 13537 13538 4102 +a 13538 13539 4102 +a 13539 13540 4102 +a 13540 13541 4102 +a 13541 13542 4102 +a 13542 13543 4102 +a 13543 13544 4102 +a 13544 13545 4102 +a 13545 13546 4102 +a 13546 13547 4102 +a 13547 13548 4102 +a 13548 13549 4102 +a 13549 13550 4102 +a 13550 13551 4102 +a 13551 13552 4102 +a 13552 13553 4102 +a 13553 13554 4102 +a 13554 13555 4102 +a 13555 13556 4102 +a 13556 13557 4102 +a 13557 13558 4102 +a 13558 13559 4102 +a 13559 13560 4102 +a 13560 13561 4102 +a 13561 13562 4102 +a 13562 13563 4102 +a 13563 13564 4102 +a 13564 13565 4102 +a 13565 13566 4102 +a 13566 13567 4102 +a 13567 13568 4102 +a 13568 13569 4102 +a 13569 13570 4102 +a 13570 13571 4102 +a 13571 13572 4102 +a 13572 13573 4102 +a 13573 13574 4102 +a 13574 13575 4102 +a 13575 13576 4102 +a 13576 13577 4102 +a 13577 13578 4102 +a 13578 13579 4102 +a 13579 13580 4102 +a 13580 13581 4102 +a 13581 13582 4102 +a 13582 13583 4102 +a 13583 13584 4102 +a 13584 13585 4102 +a 13585 13586 4102 +a 13586 13587 4102 +a 13587 13588 4102 +a 13588 13589 4102 +a 13589 13590 4102 +a 13590 13591 4102 +a 13591 13592 4102 +a 13592 13593 4102 +a 13593 13594 4102 +a 13594 13595 4102 +a 13595 13596 4102 +a 13596 13597 4102 +a 13597 13598 4102 +a 13598 13599 4102 +a 13599 13600 4102 +a 13600 13601 4102 +a 13601 13602 4102 +a 13602 13603 4102 +a 13603 13604 4102 +a 13604 13605 4102 +a 13605 13606 4102 +a 13606 13607 4102 +a 13607 13608 4102 +a 13608 13609 4102 +a 13609 13610 4102 +a 13610 13611 4102 +a 13611 13612 4102 +a 13612 13613 4102 +a 13613 13614 4102 +a 13614 13615 4102 +a 13615 13616 4102 +a 13616 13617 4102 +a 13617 13618 4102 +a 13618 13619 4102 +a 13619 13620 4102 +a 13620 13621 4102 +a 13621 13622 4102 +a 13622 13623 4102 +a 13623 13624 4102 +a 13624 13625 4102 +a 13625 13626 4102 +a 13626 13627 4102 +a 13627 13628 4102 +a 13628 13629 4102 +a 13629 13630 4102 +a 13630 13631 4102 +a 13631 13632 4102 +a 13632 13633 4102 +a 13633 13634 4102 +a 13634 13635 4102 +a 13635 13636 4102 +a 13636 13637 4102 +a 13637 13638 4102 +a 13638 13639 4102 +a 13639 13640 4102 +a 13640 13641 4102 +a 13641 13642 4102 +a 13642 13643 4102 +a 13643 13644 4102 +a 13644 13645 4102 +a 13645 13646 4102 +a 13646 13647 4102 +a 13647 13648 4102 +a 13648 13649 4102 +a 13649 13650 4102 +a 13650 13651 4102 +a 13651 13652 4102 +a 13652 13653 4102 +a 13653 13654 4102 +a 13654 13655 4102 +a 13655 13656 4102 +a 13656 13657 4102 +a 13657 13658 4102 +a 13658 13659 4102 +a 13659 13660 4102 +a 13660 13661 4102 +a 13661 13662 4102 +a 13662 13663 4102 +a 13663 13664 4102 +a 13664 13665 4102 +a 13665 13666 4102 +a 13666 13667 4102 +a 13667 13668 4102 +a 13668 13669 4102 +a 13669 13670 4102 +a 13670 13671 4102 +a 13671 13672 4102 +a 13672 13673 4102 +a 13673 13674 4102 +a 13674 13675 4102 +a 13675 13676 4102 +a 13676 13677 4102 +a 13677 13678 4102 +a 13678 13679 4102 +a 13679 13680 4102 +a 13680 13681 4102 +a 13681 13682 4102 +a 13682 13683 4102 +a 13683 13684 4102 +a 13684 13685 4102 +a 13685 13686 4102 +a 13686 13687 4102 +a 13687 13688 4102 +a 13688 13689 4102 +a 13689 13690 4102 +a 13690 13691 4102 +a 13691 13692 4102 +a 13692 13693 4102 +a 13693 13694 4102 +a 13694 13695 4102 +a 13695 13696 4102 +a 13696 13697 4102 +a 13697 13698 4102 +a 13698 13699 4102 +a 13699 13700 4102 +a 13700 13701 4102 +a 13701 13702 4102 +a 13702 13703 4102 +a 13703 13704 4102 +a 13704 13705 4102 +a 13705 13706 4102 +a 13706 13707 4102 +a 13707 13708 4102 +a 13708 13709 4102 +a 13709 13710 4102 +a 13710 13711 4102 +a 13711 13712 4102 +a 13712 13713 4102 +a 13713 13714 4102 +a 13714 13715 4102 +a 13715 13716 4102 +a 13716 13717 4102 +a 13717 13718 4102 +a 13718 13719 4102 +a 13719 13720 4102 +a 13720 13721 4102 +a 13721 13722 4102 +a 13722 13723 4102 +a 13723 13724 4102 +a 13724 13725 4102 +a 13725 13726 4102 +a 13726 13727 4102 +a 13727 13728 4102 +a 13728 13729 4102 +a 13729 13730 4102 +a 13730 13731 4102 +a 13731 13732 4102 +a 13732 13733 4102 +a 13733 13734 4102 +a 13734 13735 4102 +a 13735 13736 4102 +a 13736 13737 4102 +a 13737 13738 4102 +a 13738 13739 4102 +a 13739 13740 4102 +a 13740 13741 4102 +a 13741 13742 4102 +a 13742 13743 4102 +a 13743 13744 4102 +a 13744 13745 4102 +a 13745 13746 4102 +a 13746 13747 4102 +a 13747 13748 4102 +a 13748 13749 4102 +a 13749 13750 4102 +a 13750 13751 4102 +a 13751 13752 4102 +a 13752 13753 4102 +a 13753 13754 4102 +a 13754 13755 4102 +a 13755 13756 4102 +a 13756 13757 4102 +a 13757 13758 4102 +a 13758 13759 4102 +a 13759 13760 4102 +a 13760 13761 4102 +a 13761 13762 4102 +a 13762 13763 4102 +a 13763 13764 4102 +a 13764 13765 4102 +a 13765 13766 4102 +a 13766 13767 4102 +a 13767 13768 4102 +a 13768 13769 4102 +a 13769 13770 4102 +a 13770 13771 4102 +a 13771 13772 4102 +a 13772 13773 4102 +a 13773 13774 4102 +a 13774 13775 4102 +a 13775 13776 4102 +a 13776 13777 4102 +a 13777 13778 4102 +a 13778 13779 4102 +a 13779 13780 4102 +a 13780 13781 4102 +a 13781 13782 4102 +a 13782 13783 4102 +a 13783 13784 4102 +a 13784 13785 4102 +a 13785 13786 4102 +a 13786 13787 4102 +a 13787 13788 4102 +a 13788 13789 4102 +a 13789 13790 4102 +a 13790 13791 4102 +a 13791 13792 4102 +a 13792 13793 4102 +a 13793 13794 4102 +a 13794 13795 4102 +a 13795 13796 4102 +a 13796 13797 4102 +a 13797 13798 4102 +a 13798 13799 4102 +a 13799 13800 4102 +a 13800 13801 4102 +a 13801 13802 4102 +a 13802 13803 4102 +a 13803 13804 4102 +a 13804 13805 4102 +a 13805 13806 4102 +a 13806 13807 4102 +a 13807 13808 4102 +a 13808 13809 4102 +a 13809 13810 4102 +a 13810 13811 4102 +a 13811 13812 4102 +a 13812 13813 4102 +a 13813 13814 4102 +a 13814 13815 4102 +a 13815 13816 4102 +a 13816 13817 4102 +a 13817 13818 4102 +a 13818 13819 4102 +a 13819 13820 4102 +a 13820 13821 4102 +a 13821 13822 4102 +a 13822 13823 4102 +a 13823 13824 4102 +a 13824 13825 4102 +a 13825 13826 4102 +a 13826 13827 4102 +a 13827 13828 4102 +a 13828 13829 4102 +a 13829 13830 4102 +a 13830 13831 4102 +a 13831 13832 4102 +a 13832 13833 4102 +a 13833 13834 4102 +a 13834 13835 4102 +a 13835 13836 4102 +a 13836 13837 4102 +a 13837 13838 4102 +a 13838 13839 4102 +a 13839 13840 4102 +a 13840 13841 4102 +a 13841 13842 4102 +a 13842 13843 4102 +a 13843 13844 4102 +a 13844 13845 4102 +a 13845 13846 4102 +a 13846 13847 4102 +a 13847 13848 4102 +a 13848 13849 4102 +a 13849 13850 4102 +a 13850 13851 4102 +a 13851 13852 4102 +a 13852 13853 4102 +a 13853 13854 4102 +a 13854 13855 4102 +a 13855 13856 4102 +a 13856 13857 4102 +a 13857 13858 4102 +a 13858 13859 4102 +a 13859 13860 4102 +a 13860 13861 4102 +a 13861 13862 4102 +a 13862 13863 4102 +a 13863 13864 4102 +a 13864 13865 4102 +a 13865 13866 4102 +a 13866 13867 4102 +a 13867 13868 4102 +a 13868 13869 4102 +a 13869 13870 4102 +a 13870 13871 4102 +a 13871 13872 4102 +a 13872 13873 4102 +a 13873 13874 4102 +a 13874 13875 4102 +a 13875 13876 4102 +a 13876 13877 4102 +a 13877 13878 4102 +a 13878 13879 4102 +a 13879 13880 4102 +a 13880 13881 4102 +a 13881 13882 4102 +a 13882 13883 4102 +a 13883 13884 4102 +a 13884 13885 4102 +a 13885 13886 4102 +a 13886 13887 4102 +a 13887 13888 4102 +a 13888 13889 4102 +a 13889 13890 4102 +a 13890 13891 4102 +a 13891 13892 4102 +a 13892 13893 4102 +a 13893 13894 4102 +a 13894 13895 4102 +a 13895 13896 4102 +a 13896 13897 4102 +a 13897 13898 4102 +a 13898 13899 4102 +a 13899 13900 4102 +a 13900 13901 4102 +a 13901 13902 4102 +a 13902 13903 4102 +a 13903 13904 4102 +a 13904 13905 4102 +a 13905 13906 4102 +a 13906 13907 4102 +a 13907 13908 4102 +a 13908 13909 4102 +a 13909 13910 4102 +a 13910 13911 4102 +a 13911 13912 4102 +a 13912 13913 4102 +a 13913 13914 4102 +a 13914 13915 4102 +a 13915 13916 4102 +a 13916 13917 4102 +a 13917 13918 4102 +a 13918 13919 4102 +a 13919 13920 4102 +a 13920 13921 4102 +a 13921 13922 4102 +a 13922 13923 4102 +a 13923 13924 4102 +a 13924 13925 4102 +a 13925 13926 4102 +a 13926 13927 4102 +a 13927 13928 4102 +a 13928 13929 4102 +a 13929 13930 4102 +a 13930 13931 4102 +a 13931 13932 4102 +a 13932 13933 4102 +a 13933 13934 4102 +a 13934 13935 4102 +a 13935 13936 4102 +a 13936 13937 4102 +a 13937 13938 4102 +a 13938 13939 4102 +a 13939 13940 4102 +a 13940 13941 4102 +a 13941 13942 4102 +a 13942 13943 4102 +a 13943 13944 4102 +a 13944 13945 4102 +a 13945 13946 4102 +a 13946 13947 4102 +a 13947 13948 4102 +a 13948 13949 4102 +a 13949 13950 4102 +a 13950 13951 4102 +a 13951 13952 4102 +a 13952 13953 4102 +a 13953 13954 4102 +a 13954 13955 4102 +a 13955 13956 4102 +a 13956 13957 4102 +a 13957 13958 4102 +a 13958 13959 4102 +a 13959 13960 4102 +a 13960 13961 4102 +a 13961 13962 4102 +a 13962 13963 4102 +a 13963 13964 4102 +a 13964 13965 4102 +a 13965 13966 4102 +a 13966 13967 4102 +a 13967 13968 4102 +a 13968 13969 4102 +a 13969 13970 4102 +a 13970 13971 4102 +a 13971 13972 4102 +a 13972 13973 4102 +a 13973 13974 4102 +a 13974 13975 4102 +a 13975 13976 4102 +a 13976 13977 4102 +a 13977 13978 4102 +a 13978 13979 4102 +a 13979 13980 4102 +a 13980 13981 4102 +a 13981 13982 4102 +a 13982 13983 4102 +a 13983 13984 4102 +a 13984 13985 4102 +a 13985 13986 4102 +a 13986 13987 4102 +a 13987 13988 4102 +a 13988 13989 4102 +a 13989 13990 4102 +a 13990 13991 4102 +a 13991 13992 4102 +a 13992 13993 4102 +a 13993 13994 4102 +a 13994 13995 4102 +a 13995 13996 4102 +a 13996 13997 4102 +a 13997 13998 4102 +a 13998 13999 4102 +a 13999 14000 4102 +a 14000 14001 4102 +a 14001 14002 4102 +a 14002 14003 4102 +a 14003 14004 4102 +a 14004 14005 4102 +a 14005 14006 4102 +a 14006 14007 4102 +a 14007 14008 4102 +a 14008 14009 4102 +a 14009 14010 4102 +a 14010 14011 4102 +a 14011 14012 4102 +a 14012 14013 4102 +a 14013 14014 4102 +a 14014 14015 4102 +a 14015 14016 4102 +a 14016 14017 4102 +a 14017 14018 4102 +a 14018 14019 4102 +a 14019 14020 4102 +a 14020 14021 4102 +a 14021 14022 4102 +a 14022 14023 4102 +a 14023 14024 4102 +a 14024 14025 4102 +a 14025 14026 4102 +a 14026 14027 4102 +a 14027 14028 4102 +a 14028 14029 4102 +a 14029 14030 4102 +a 14030 14031 4102 +a 14031 14032 4102 +a 14032 14033 4102 +a 14033 14034 4102 +a 14034 14035 4102 +a 14035 14036 4102 +a 14036 14037 4102 +a 14037 14038 4102 +a 14038 14039 4102 +a 14039 14040 4102 +a 14040 14041 4102 +a 14041 14042 4102 +a 14042 14043 4102 +a 14043 14044 4102 +a 14044 14045 4102 +a 14045 14046 4102 +a 14046 14047 4102 +a 14047 14048 4102 +a 14048 14049 4102 +a 14049 14050 4102 +a 14050 14051 4102 +a 14051 14052 4102 +a 14052 14053 4102 +a 14053 14054 4102 +a 14054 14055 4102 +a 14055 14056 4102 +a 14056 14057 4102 +a 14057 14058 4102 +a 14058 14059 4102 +a 14059 14060 4102 +a 14060 14061 4102 +a 14061 14062 4102 +a 14062 14063 4102 +a 14063 14064 4102 +a 14064 14065 4102 +a 14065 14066 4102 +a 14066 14067 4102 +a 14067 14068 4102 +a 14068 14069 4102 +a 14069 14070 4102 +a 14070 14071 4102 +a 14071 14072 4102 +a 14072 14073 4102 +a 14073 14074 4102 +a 14074 14075 4102 +a 14075 14076 4102 +a 14076 14077 4102 +a 14077 14078 4102 +a 14078 14079 4102 +a 14079 14080 4102 +a 14080 14081 4102 +a 14081 14082 4102 +a 14082 14083 4102 +a 14083 14084 4102 +a 14084 14085 4102 +a 14085 14086 4102 +a 14086 14087 4102 +a 14087 14088 4102 +a 14088 14089 4102 +a 14089 14090 4102 +a 14090 14091 4102 +a 14091 14092 4102 +a 14092 14093 4102 +a 14093 14094 4102 +a 14094 14095 4102 +a 14095 14096 4102 +a 14096 14097 4102 +a 14097 14098 4102 +a 14098 14099 4102 +a 14099 14100 4102 +a 14100 14101 4102 +a 14101 14102 4102 +a 14102 14103 4102 +a 14103 14104 4102 +a 14104 14105 4102 +a 14105 14106 4102 +a 14106 14107 4102 +a 14107 14108 4102 +a 14108 14109 4102 +a 14109 14110 4102 +a 14110 14111 4102 +a 14111 14112 4102 +a 14112 14113 4102 +a 14113 14114 4102 +a 14114 14115 4102 +a 14115 14116 4102 +a 14116 14117 4102 +a 14117 14118 4102 +a 14118 14119 4102 +a 14119 14120 4102 +a 14120 14121 4102 +a 14121 14122 4102 +a 14122 14123 4102 +a 14123 14124 4102 +a 14124 14125 4102 +a 14125 14126 4102 +a 14126 14127 4102 +a 14127 14128 4102 +a 14128 14129 4102 +a 14129 14130 4102 +a 14130 14131 4102 +a 14131 14132 4102 +a 14132 14133 4102 +a 14133 14134 4102 +a 14134 14135 4102 +a 14135 14136 4102 +a 14136 14137 4102 +a 14137 14138 4102 +a 14138 14139 4102 +a 14139 14140 4102 +a 14140 14141 4102 +a 14141 14142 4102 +a 14142 14143 4102 +a 14143 14144 4102 +a 14144 14145 4102 +a 14145 14146 4102 +a 14146 14147 4102 +a 14147 14148 4102 +a 14148 14149 4102 +a 14149 14150 4102 +a 14150 14151 4102 +a 14151 14152 4102 +a 14152 14153 4102 +a 14153 14154 4102 +a 14154 14155 4102 +a 14155 14156 4102 +a 14156 14157 4102 +a 14157 14158 4102 +a 14158 14159 4102 +a 14159 14160 4102 +a 14160 14161 4102 +a 14161 14162 4102 +a 14162 14163 4102 +a 14163 14164 4102 +a 14164 14165 4102 +a 14165 14166 4102 +a 14166 14167 4102 +a 14167 14168 4102 +a 14168 14169 4102 +a 14169 14170 4102 +a 14170 14171 4102 +a 14171 14172 4102 +a 14172 14173 4102 +a 14173 14174 4102 +a 14174 14175 4102 +a 14175 14176 4102 +a 14176 14177 4102 +a 14177 14178 4102 +a 14178 14179 4102 +a 14179 14180 4102 +a 14180 14181 4102 +a 14181 14182 4102 +a 14182 14183 4102 +a 14183 14184 4102 +a 14184 14185 4102 +a 14185 14186 4102 +a 14186 14187 4102 +a 14187 14188 4102 +a 14188 14189 4102 +a 14189 14190 4102 +a 14190 14191 4102 +a 14191 14192 4102 +a 14192 14193 4102 +a 14193 14194 4102 +a 14194 14195 4102 +a 14195 14196 4102 +a 14196 14197 4102 +a 14197 14198 4102 +a 14198 14199 4102 +a 14199 14200 4102 +a 14200 14201 4102 +a 14201 14202 4102 +a 14202 14203 4102 +a 14203 14204 4102 +a 14204 14205 4102 +a 14205 14206 4102 +a 14206 14207 4102 +a 14207 14208 4102 +a 14208 14209 4102 +a 14209 14210 4102 +a 14210 14211 4102 +a 14211 14212 4102 +a 14212 14213 4102 +a 14213 14214 4102 +a 14214 14215 4102 +a 14215 14216 4102 +a 14216 14217 4102 +a 14217 14218 4102 +a 14218 14219 4102 +a 14219 14220 4102 +a 14220 14221 4102 +a 14221 14222 4102 +a 14222 14223 4102 +a 14223 14224 4102 +a 14224 14225 4102 +a 14225 14226 4102 +a 14226 14227 4102 +a 14227 14228 4102 +a 14228 14229 4102 +a 14229 14230 4102 +a 14230 14231 4102 +a 14231 14232 4102 +a 14232 14233 4102 +a 14233 14234 4102 +a 14234 14235 4102 +a 14235 14236 4102 +a 14236 14237 4102 +a 14237 14238 4102 +a 14238 14239 4102 +a 14239 14240 4102 +a 14240 14241 4102 +a 14241 14242 4102 +a 14242 14243 4102 +a 14243 14244 4102 +a 14244 14245 4102 +a 14245 14246 4102 +a 14246 14247 4102 +a 14247 14248 4102 +a 14248 14249 4102 +a 14249 14250 4102 +a 14250 14251 4102 +a 14251 14252 4102 +a 14252 14253 4102 +a 14253 14254 4102 +a 14254 14255 4102 +a 14255 14256 4102 +a 14256 14257 4102 +a 14257 14258 4102 +a 14258 14259 4102 +a 14259 14260 4102 +a 14260 14261 4102 +a 14261 14262 4102 +a 14262 14263 4102 +a 14263 14264 4102 +a 14264 14265 4102 +a 14265 14266 4102 +a 14266 14267 4102 +a 14267 14268 4102 +a 14268 14269 4102 +a 14269 14270 4102 +a 14270 14271 4102 +a 14271 14272 4102 +a 14272 14273 4102 +a 14273 14274 4102 +a 14274 14275 4102 +a 14275 14276 4102 +a 14276 14277 4102 +a 14277 14278 4102 +a 14278 14279 4102 +a 14279 14280 4102 +a 14280 14281 4102 +a 14281 14282 4102 +a 14282 14283 4102 +a 14283 14284 4102 +a 14284 14285 4102 +a 14285 14286 4102 +a 14286 14287 4102 +a 14287 14288 4102 +a 14288 14289 4102 +a 14289 14290 4102 +a 14290 14291 4102 +a 14291 14292 4102 +a 14292 14293 4102 +a 14293 14294 4102 +a 14294 14295 4102 +a 14295 14296 4102 +a 14296 14297 4102 +a 14297 14298 4102 +a 14298 14299 4102 +a 14299 14300 4102 +a 14300 14301 4102 +a 14301 14302 4102 +a 14302 14303 4102 +a 14303 14304 4102 +a 14304 14305 4102 +a 14305 14306 4102 +a 14306 14307 4102 +a 14307 14308 4102 +a 14308 14309 4102 +a 14309 14310 4102 +a 14310 14311 4102 +a 14311 14312 4102 +a 14312 14313 4102 +a 14313 14314 4102 +a 14314 14315 4102 +a 14315 14316 4102 +a 14316 14317 4102 +a 14317 14318 4102 +a 14318 14319 4102 +a 14319 14320 4102 +a 14320 14321 4102 +a 14321 14322 4102 +a 14322 14323 4102 +a 14323 14324 4102 +a 14324 14325 4102 +a 14325 14326 4102 +a 14326 14327 4102 +a 14327 14328 4102 +a 14328 14329 4102 +a 14329 14330 4102 +a 14330 14331 4102 +a 14331 14332 4102 +a 14332 14333 4102 +a 14333 14334 4102 +a 14334 14335 4102 +a 14335 14336 4102 +a 14336 14337 4102 +a 14337 14338 4102 +a 14338 14339 4102 +a 14339 14340 4102 +a 14340 14341 4102 +a 14341 14342 4102 +a 14342 14343 4102 +a 14343 14344 4102 +a 14344 14345 4102 +a 14345 14346 4102 +a 14346 14347 4102 +a 14347 14348 4102 +a 14348 14349 4102 +a 14349 14350 4102 +a 14350 14351 4102 +a 14351 14352 4102 +a 14352 14353 4102 +a 14353 14354 4102 +a 14354 14355 4102 +a 14355 14356 4102 +a 14356 14357 4102 +a 14357 14358 4102 +a 14358 14359 4102 +a 14359 14360 4102 +a 14360 14361 4102 +a 14361 14362 4102 +a 14362 14363 4102 +a 14363 14364 4102 +a 14364 14365 4102 +a 14365 14366 4102 +a 14366 14367 4102 +a 14367 14368 4102 +a 14368 14369 4102 +a 14369 14370 4102 +a 14370 14371 4102 +a 14371 14372 4102 +a 14372 14373 4102 +a 14373 14374 4102 +a 14374 14375 4102 +a 14375 14376 4102 +a 14376 14377 4102 +a 14377 14378 4102 +a 14378 14379 4102 +a 14379 14380 4102 +a 14380 14381 4102 +a 14381 14382 4102 +a 14382 14383 4102 +a 14383 14384 4102 +a 14384 14385 4102 +a 14385 14386 4102 +a 14386 14387 4102 +a 14387 14388 4102 +a 14388 14389 4102 +a 14389 14390 4102 +a 14390 14391 4102 +a 14391 14392 4102 +a 14392 14393 4102 +a 14393 14394 4102 +a 14394 14395 4102 +a 14395 14396 4102 +a 14396 14397 4102 +a 14397 14398 4102 +a 14398 14399 4102 +a 14399 14400 4102 +a 14400 14401 4102 +a 14401 14402 4102 +a 14402 14403 4102 +a 14403 14404 4102 +a 14404 14405 4102 +a 14405 14406 4102 +a 14406 14407 4102 +a 14407 14408 4102 +a 14408 14409 4102 +a 14409 14410 4102 +a 14410 14411 4102 +a 14411 14412 4102 +a 14412 14413 4102 +a 14413 14414 4102 +a 14414 14415 4102 +a 14415 14416 4102 +a 14416 14417 4102 +a 14417 14418 4102 +a 14418 14419 4102 +a 14419 14420 4102 +a 14420 14421 4102 +a 14421 14422 4102 +a 14422 14423 4102 +a 14423 14424 4102 +a 14424 14425 4102 +a 14425 14426 4102 +a 14426 14427 4102 +a 14427 14428 4102 +a 14428 14429 4102 +a 14429 14430 4102 +a 14430 14431 4102 +a 14431 14432 4102 +a 14432 14433 4102 +a 14433 14434 4102 +a 14434 14435 4102 +a 14435 14436 4102 +a 14436 14437 4102 +a 14437 14438 4102 +a 14438 14439 4102 +a 14439 14440 4102 +a 14440 14441 4102 +a 14441 14442 4102 +a 14442 14443 4102 +a 14443 14444 4102 +a 14444 14445 4102 +a 14445 14446 4102 +a 14446 14447 4102 +a 14447 14448 4102 +a 14448 14449 4102 +a 14449 14450 4102 +a 14450 14451 4102 +a 14451 14452 4102 +a 14452 14453 4102 +a 14453 14454 4102 +a 14454 14455 4102 +a 14455 14456 4102 +a 14456 14457 4102 +a 14457 14458 4102 +a 14458 14459 4102 +a 14459 14460 4102 +a 14460 14461 4102 +a 14461 14462 4102 +a 14462 14463 4102 +a 14463 14464 4102 +a 14464 14465 4102 +a 14465 14466 4102 +a 14466 14467 4102 +a 14467 14468 4102 +a 14468 14469 4102 +a 14469 14470 4102 +a 14470 14471 4102 +a 14471 14472 4102 +a 14472 14473 4102 +a 14473 14474 4102 +a 14474 14475 4102 +a 14475 14476 4102 +a 14476 14477 4102 +a 14477 14478 4102 +a 14478 14479 4102 +a 14479 14480 4102 +a 14480 14481 4102 +a 14481 14482 4102 +a 14482 14483 4102 +a 14483 14484 4102 +a 14484 14485 4102 +a 14485 14486 4102 +a 14486 14487 4102 +a 14487 14488 4102 +a 14488 14489 4102 +a 14489 14490 4102 +a 14490 14491 4102 +a 14491 14492 4102 +a 14492 14493 4102 +a 14493 14494 4102 +a 14494 14495 4102 +a 14495 14496 4102 +a 14496 14497 4102 +a 14497 14498 4102 +a 14498 14499 4102 +a 14499 14500 4102 +a 14500 14501 4102 +a 14501 14502 4102 +a 14502 14503 4102 +a 14503 14504 4102 +a 14504 14505 4102 +a 14505 14506 4102 +a 14506 14507 4102 +a 14507 14508 4102 +a 14508 14509 4102 +a 14509 14510 4102 +a 14510 14511 4102 +a 14511 14512 4102 +a 14512 14513 4102 +a 14513 14514 4102 +a 14514 14515 4102 +a 14515 14516 4102 +a 14516 14517 4102 +a 14517 14518 4102 +a 14518 14519 4102 +a 14519 14520 4102 +a 14520 14521 4102 +a 14521 14522 4102 +a 14522 14523 4102 +a 14523 14524 4102 +a 14524 14525 4102 +a 14525 14526 4102 +a 14526 14527 4102 +a 14527 14528 4102 +a 14528 14529 4102 +a 14529 14530 4102 +a 14530 14531 4102 +a 14531 14532 4102 +a 14532 14533 4102 +a 14533 14534 4102 +a 14534 14535 4102 +a 14535 14536 4102 +a 14536 14537 4102 +a 14537 14538 4102 +a 14538 14539 4102 +a 14539 14540 4102 +a 14540 14541 4102 +a 14541 14542 4102 +a 14542 14543 4102 +a 14543 14544 4102 +a 14544 14545 4102 +a 14545 14546 4102 +a 14546 14547 4102 +a 14547 14548 4102 +a 14548 14549 4102 +a 14549 14550 4102 +a 14550 14551 4102 +a 14551 14552 4102 +a 14552 14553 4102 +a 14553 14554 4102 +a 14554 14555 4102 +a 14555 14556 4102 +a 14556 14557 4102 +a 14557 14558 4102 +a 14558 14559 4102 +a 14559 14560 4102 +a 14560 14561 4102 +a 14561 14562 4102 +a 14562 14563 4102 +a 14563 14564 4102 +a 14564 14565 4102 +a 14565 14566 4102 +a 14566 14567 4102 +a 14567 14568 4102 +a 14568 14569 4102 +a 14569 14570 4102 +a 14570 14571 4102 +a 14571 14572 4102 +a 14572 14573 4102 +a 14573 14574 4102 +a 14574 14575 4102 +a 14575 14576 4102 +a 14576 14577 4102 +a 14577 14578 4102 +a 14578 14579 4102 +a 14579 14580 4102 +a 14580 14581 4102 +a 14581 14582 4102 +a 14582 14583 4102 +a 14583 14584 4102 +a 14584 14585 4102 +a 14585 14586 4102 +a 14586 14587 4102 +a 14587 14588 4102 +a 14588 14589 4102 +a 14589 14590 4102 +a 14590 14591 4102 +a 14591 14592 4102 +a 14592 14593 4102 +a 14593 14594 4102 +a 14594 14595 4102 +a 14595 14596 4102 +a 14596 14597 4102 +a 14597 14598 4102 +a 14598 14599 4102 +a 14599 14600 4102 +a 14600 14601 4102 +a 14601 14602 4102 +a 14602 14603 4102 +a 14603 14604 4102 +a 14604 14605 4102 +a 14605 14606 4102 +a 14606 14607 4102 +a 14607 14608 4102 +a 14608 14609 4102 +a 14609 14610 4102 +a 14610 14611 4102 +a 14611 14612 4102 +a 14612 14613 4102 +a 14613 14614 4102 +a 14614 14615 4102 +a 14615 14616 4102 +a 14616 14617 4102 +a 14617 14618 4102 +a 14618 14619 4102 +a 14619 14620 4102 +a 14620 14621 4102 +a 14621 14622 4102 +a 14622 14623 4102 +a 14623 14624 4102 +a 14624 14625 4102 +a 14625 14626 4102 +a 14626 14627 4102 +a 14627 14628 4102 +a 14628 14629 4102 +a 14629 14630 4102 +a 14630 14631 4102 +a 14631 14632 4102 +a 14632 14633 4102 +a 14633 14634 4102 +a 14634 14635 4102 +a 14635 14636 4102 +a 14636 14637 4102 +a 14637 14638 4102 +a 14638 14639 4102 +a 14639 14640 4102 +a 14640 14641 4102 +a 14641 14642 4102 +a 14642 14643 4102 +a 14643 14644 4102 +a 14644 14645 4102 +a 14645 14646 4102 +a 14646 14647 4102 +a 14647 14648 4102 +a 14648 14649 4102 +a 14649 14650 4102 +a 14650 14651 4102 +a 14651 14652 4102 +a 14652 14653 4102 +a 14653 14654 4102 +a 14654 14655 4102 +a 14655 14656 4102 +a 14656 14657 4102 +a 14657 14658 4102 +a 14658 14659 4102 +a 14659 14660 4102 +a 14660 14661 4102 +a 14661 14662 4102 +a 14662 14663 4102 +a 14663 14664 4102 +a 14664 14665 4102 +a 14665 14666 4102 +a 14666 14667 4102 +a 14667 14668 4102 +a 14668 14669 4102 +a 14669 14670 4102 +a 14670 14671 4102 +a 14671 14672 4102 +a 14672 14673 4102 +a 14673 14674 4102 +a 14674 14675 4102 +a 14675 14676 4102 +a 14676 14677 4102 +a 14677 14678 4102 +a 14678 14679 4102 +a 14679 14680 4102 +a 14680 14681 4102 +a 14681 14682 4102 +a 14682 14683 4102 +a 14683 14684 4102 +a 14684 14685 4102 +a 14685 14686 4102 +a 14686 14687 4102 +a 14687 14688 4102 +a 14688 14689 4102 +a 14689 14690 4102 +a 14690 14691 4102 +a 14691 14692 4102 +a 14692 14693 4102 +a 14693 14694 4102 +a 14694 14695 4102 +a 14695 14696 4102 +a 14696 14697 4102 +a 14697 14698 4102 +a 14698 14699 4102 +a 14699 14700 4102 +a 14700 14701 4102 +a 14701 14702 4102 +a 14702 14703 4102 +a 14703 14704 4102 +a 14704 14705 4102 +a 14705 14706 4102 +a 14706 14707 4102 +a 14707 14708 4102 +a 14708 14709 4102 +a 14709 14710 4102 +a 14710 14711 4102 +a 14711 14712 4102 +a 14712 14713 4102 +a 14713 14714 4102 +a 14714 14715 4102 +a 14715 14716 4102 +a 14716 14717 4102 +a 14717 14718 4102 +a 14718 14719 4102 +a 14719 14720 4102 +a 14720 14721 4102 +a 14721 14722 4102 +a 14722 14723 4102 +a 14723 14724 4102 +a 14724 14725 4102 +a 14725 14726 4102 +a 14726 14727 4102 +a 14727 14728 4102 +a 14728 14729 4102 +a 14729 14730 4102 +a 14730 14731 4102 +a 14731 14732 4102 +a 14732 14733 4102 +a 14733 14734 4102 +a 14734 14735 4102 +a 14735 14736 4102 +a 14736 14737 4102 +a 14737 14738 4102 +a 14738 14739 4102 +a 14739 14740 4102 +a 14740 14741 4102 +a 14741 14742 4102 +a 14742 14743 4102 +a 14743 14744 4102 +a 14744 14745 4102 +a 14745 14746 4102 +a 14746 14747 4102 +a 14747 14748 4102 +a 14748 14749 4102 +a 14749 14750 4102 +a 14750 14751 4102 +a 14751 14752 4102 +a 14752 14753 4102 +a 14753 14754 4102 +a 14754 14755 4102 +a 14755 14756 4102 +a 14756 14757 4102 +a 14757 14758 4102 +a 14758 14759 4102 +a 14759 14760 4102 +a 14760 14761 4102 +a 14761 14762 4102 +a 14762 14763 4102 +a 14763 14764 4102 +a 14764 14765 4102 +a 14765 14766 4102 +a 14766 14767 4102 +a 14767 14768 4102 +a 14768 14769 4102 +a 14769 14770 4102 +a 14770 14771 4102 +a 14771 14772 4102 +a 14772 14773 4102 +a 14773 14774 4102 +a 14774 14775 4102 +a 14775 14776 4102 +a 14776 14777 4102 +a 14777 14778 4102 +a 14778 14779 4102 +a 14779 14780 4102 +a 14780 14781 4102 +a 14781 14782 4102 +a 14782 14783 4102 +a 14783 14784 4102 +a 14784 14785 4102 +a 14785 14786 4102 +a 14786 14787 4102 +a 14787 14788 4102 +a 14788 14789 4102 +a 14789 14790 4102 +a 14790 14791 4102 +a 14791 14792 4102 +a 14792 14793 4102 +a 14793 14794 4102 +a 14794 14795 4102 +a 14795 14796 4102 +a 14796 14797 4102 +a 14797 14798 4102 +a 14798 14799 4102 +a 14799 14800 4102 +a 14800 14801 4102 +a 14801 14802 4102 +a 14802 14803 4102 +a 14803 14804 4102 +a 14804 14805 4102 +a 14805 14806 4102 +a 14806 14807 4102 +a 14807 14808 4102 +a 14808 14809 4102 +a 14809 14810 4102 +a 14810 14811 4102 +a 14811 14812 4102 +a 14812 14813 4102 +a 14813 14814 4102 +a 14814 14815 4102 +a 14815 14816 4102 +a 14816 14817 4102 +a 14817 14818 4102 +a 14818 14819 4102 +a 14819 14820 4102 +a 14820 14821 4102 +a 14821 14822 4102 +a 14822 14823 4102 +a 14823 14824 4102 +a 14824 14825 4102 +a 14825 14826 4102 +a 14826 14827 4102 +a 14827 14828 4102 +a 14828 14829 4102 +a 14829 14830 4102 +a 14830 14831 4102 +a 14831 14832 4102 +a 14832 14833 4102 +a 14833 14834 4102 +a 14834 14835 4102 +a 14835 14836 4102 +a 14836 14837 4102 +a 14837 14838 4102 +a 14838 14839 4102 +a 14839 14840 4102 +a 14840 14841 4102 +a 14841 14842 4102 +a 14842 14843 4102 +a 14843 14844 4102 +a 14844 14845 4102 +a 14845 14846 4102 +a 14846 14847 4102 +a 14847 14848 4102 +a 14848 14849 4102 +a 14849 14850 4102 +a 14850 14851 4102 +a 14851 14852 4102 +a 14852 14853 4102 +a 14853 14854 4102 +a 14854 14855 4102 +a 14855 14856 4102 +a 14856 14857 4102 +a 14857 14858 4102 +a 14858 14859 4102 +a 14859 14860 4102 +a 14860 14861 4102 +a 14861 14862 4102 +a 14862 14863 4102 +a 14863 14864 4102 +a 14864 14865 4102 +a 14865 14866 4102 +a 14866 14867 4102 +a 14867 14868 4102 +a 14868 14869 4102 +a 14869 14870 4102 +a 14870 14871 4102 +a 14871 14872 4102 +a 14872 14873 4102 +a 14873 14874 4102 +a 14874 14875 4102 +a 14875 14876 4102 +a 14876 14877 4102 +a 14877 14878 4102 +a 14878 14879 4102 +a 14879 14880 4102 +a 14880 14881 4102 +a 14881 14882 4102 +a 14882 14883 4102 +a 14883 14884 4102 +a 14884 14885 4102 +a 14885 14886 4102 +a 14886 14887 4102 +a 14887 14888 4102 +a 14888 14889 4102 +a 14889 14890 4102 +a 14890 14891 4102 +a 14891 14892 4102 +a 14892 14893 4102 +a 14893 14894 4102 +a 14894 14895 4102 +a 14895 14896 4102 +a 14896 14897 4102 +a 14897 14898 4102 +a 14898 14899 4102 +a 14899 14900 4102 +a 14900 14901 4102 +a 14901 14902 4102 +a 14902 14903 4102 +a 14903 14904 4102 +a 14904 14905 4102 +a 14905 14906 4102 +a 14906 14907 4102 +a 14907 14908 4102 +a 14908 14909 4102 +a 14909 14910 4102 +a 14910 14911 4102 +a 14911 14912 4102 +a 14912 14913 4102 +a 14913 14914 4102 +a 14914 14915 4102 +a 14915 14916 4102 +a 14916 14917 4102 +a 14917 14918 4102 +a 14918 14919 4102 +a 14919 14920 4102 +a 14920 14921 4102 +a 14921 14922 4102 +a 14922 14923 4102 +a 14923 14924 4102 +a 14924 14925 4102 +a 14925 14926 4102 +a 14926 14927 4102 +a 14927 14928 4102 +a 14928 14929 4102 +a 14929 14930 4102 +a 14930 14931 4102 +a 14931 14932 4102 +a 14932 14933 4102 +a 14933 14934 4102 +a 14934 14935 4102 +a 14935 14936 4102 +a 14936 14937 4102 +a 14937 14938 4102 +a 14938 14939 4102 +a 14939 14940 4102 +a 14940 14941 4102 +a 14941 14942 4102 +a 14942 14943 4102 +a 14943 14944 4102 +a 14944 14945 4102 +a 14945 14946 4102 +a 14946 14947 4102 +a 14947 14948 4102 +a 14948 14949 4102 +a 14949 14950 4102 +a 14950 14951 4102 +a 14951 14952 4102 +a 14952 14953 4102 +a 14953 14954 4102 +a 14954 14955 4102 +a 14955 14956 4102 +a 14956 14957 4102 +a 14957 14958 4102 +a 14958 14959 4102 +a 14959 14960 4102 +a 14960 14961 4102 +a 14961 14962 4102 +a 14962 14963 4102 +a 14963 14964 4102 +a 14964 14965 4102 +a 14965 14966 4102 +a 14966 14967 4102 +a 14967 14968 4102 +a 14968 14969 4102 +a 14969 14970 4102 +a 14970 14971 4102 +a 14971 14972 4102 +a 14972 14973 4102 +a 14973 14974 4102 +a 14974 14975 4102 +a 14975 14976 4102 +a 14976 14977 4102 +a 14977 14978 4102 +a 14978 14979 4102 +a 14979 14980 4102 +a 14980 14981 4102 +a 14981 14982 4102 +a 14982 14983 4102 +a 14983 14984 4102 +a 14984 14985 4102 +a 14985 14986 4102 +a 14986 14987 4102 +a 14987 14988 4102 +a 14988 14989 4102 +a 14989 14990 4102 +a 14990 14991 4102 +a 14991 14992 4102 +a 14992 14993 4102 +a 14993 14994 4102 +a 14994 14995 4102 +a 14995 14996 4102 +a 14996 14997 4102 +a 14997 14998 4102 +a 14998 14999 4102 +a 14999 15000 4102 +a 15000 15001 4102 +a 15001 15002 4102 +a 15002 15003 4102 +a 15003 15004 4102 +a 15004 15005 4102 +a 15005 15006 4102 +a 15006 15007 4102 +a 15007 15008 4102 +a 15008 15009 4102 +a 15009 15010 4102 +a 15010 15011 4102 +a 15011 15012 4102 +a 15012 15013 4102 +a 15013 15014 4102 +a 15014 15015 4102 +a 15015 15016 4102 +a 15016 15017 4102 +a 15017 15018 4102 +a 15018 15019 4102 +a 15019 15020 4102 +a 15020 15021 4102 +a 15021 15022 4102 +a 15022 15023 4102 +a 15023 15024 4102 +a 15024 15025 4102 +a 15025 15026 4102 +a 15026 15027 4102 +a 15027 15028 4102 +a 15028 15029 4102 +a 15029 15030 4102 +a 15030 15031 4102 +a 15031 15032 4102 +a 15032 15033 4102 +a 15033 15034 4102 +a 15034 15035 4102 +a 15035 15036 4102 +a 15036 15037 4102 +a 15037 15038 4102 +a 15038 15039 4102 +a 15039 15040 4102 +a 15040 15041 4102 +a 15041 15042 4102 +a 15042 15043 4102 +a 15043 15044 4102 +a 15044 15045 4102 +a 15045 15046 4102 +a 15046 15047 4102 +a 15047 15048 4102 +a 15048 15049 4102 +a 15049 15050 4102 +a 15050 15051 4102 +a 15051 15052 4102 +a 15052 15053 4102 +a 15053 15054 4102 +a 15054 15055 4102 +a 15055 15056 4102 +a 15056 15057 4102 +a 15057 15058 4102 +a 15058 15059 4102 +a 15059 15060 4102 +a 15060 15061 4102 +a 15061 15062 4102 +a 15062 15063 4102 +a 15063 15064 4102 +a 15064 15065 4102 +a 15065 15066 4102 +a 15066 15067 4102 +a 15067 15068 4102 +a 15068 15069 4102 +a 15069 15070 4102 +a 15070 15071 4102 +a 15071 15072 4102 +a 15072 15073 4102 +a 15073 15074 4102 +a 15074 15075 4102 +a 15075 15076 4102 +a 15076 15077 4102 +a 15077 15078 4102 +a 15078 15079 4102 +a 15079 15080 4102 +a 15080 15081 4102 +a 15081 15082 4102 +a 15082 15083 4102 +a 15083 15084 4102 +a 15084 15085 4102 +a 15085 15086 4102 +a 15086 15087 4102 +a 15087 15088 4102 +a 15088 15089 4102 +a 15089 15090 4102 +a 15090 15091 4102 +a 15091 15092 4102 +a 15092 15093 4102 +a 15093 15094 4102 +a 15094 15095 4102 +a 15095 15096 4102 +a 15096 15097 4102 +a 15097 15098 4102 +a 15098 15099 4102 +a 15099 15100 4102 +a 15100 15101 4102 +a 15101 15102 4102 +a 15102 15103 4102 +a 15103 15104 4102 +a 15104 15105 4102 +a 15105 15106 4102 +a 15106 15107 4102 +a 15107 15108 4102 +a 15108 15109 4102 +a 15109 15110 4102 +a 15110 15111 4102 +a 15111 15112 4102 +a 15112 15113 4102 +a 15113 15114 4102 +a 15114 15115 4102 +a 15115 15116 4102 +a 15116 15117 4102 +a 15117 15118 4102 +a 15118 15119 4102 +a 15119 15120 4102 +a 15120 15121 4102 +a 15121 15122 4102 +a 15122 15123 4102 +a 15123 15124 4102 +a 15124 15125 4102 +a 15125 15126 4102 +a 15126 15127 4102 +a 15127 15128 4102 +a 15128 15129 4102 +a 15129 15130 4102 +a 15130 15131 4102 +a 15131 15132 4102 +a 15132 15133 4102 +a 15133 15134 4102 +a 15134 15135 4102 +a 15135 15136 4102 +a 15136 15137 4102 +a 15137 15138 4102 +a 15138 15139 4102 +a 15139 15140 4102 +a 15140 15141 4102 +a 15141 15142 4102 +a 15142 15143 4102 +a 15143 15144 4102 +a 15144 15145 4102 +a 15145 15146 4102 +a 15146 15147 4102 +a 15147 15148 4102 +a 15148 15149 4102 +a 15149 15150 4102 +a 15150 15151 4102 +a 15151 15152 4102 +a 15152 15153 4102 +a 15153 15154 4102 +a 15154 15155 4102 +a 15155 15156 4102 +a 15156 15157 4102 +a 15157 15158 4102 +a 15158 15159 4102 +a 15159 15160 4102 +a 15160 15161 4102 +a 15161 15162 4102 +a 15162 15163 4102 +a 15163 15164 4102 +a 15164 15165 4102 +a 15165 15166 4102 +a 15166 15167 4102 +a 15167 15168 4102 +a 15168 15169 4102 +a 15169 15170 4102 +a 15170 15171 4102 +a 15171 15172 4102 +a 15172 15173 4102 +a 15173 15174 4102 +a 15174 15175 4102 +a 15175 15176 4102 +a 15176 15177 4102 +a 15177 15178 4102 +a 15178 15179 4102 +a 15179 15180 4102 +a 15180 15181 4102 +a 15181 15182 4102 +a 15182 15183 4102 +a 15183 15184 4102 +a 15184 15185 4102 +a 15185 15186 4102 +a 15186 15187 4102 +a 15187 15188 4102 +a 15188 15189 4102 +a 15189 15190 4102 +a 15190 15191 4102 +a 15191 15192 4102 +a 15192 15193 4102 +a 15193 15194 4102 +a 15194 15195 4102 +a 15195 15196 4102 +a 15196 15197 4102 +a 15197 15198 4102 +a 15198 15199 4102 +a 15199 15200 4102 +a 15200 15201 4102 +a 15201 15202 4102 +a 15202 15203 4102 +a 15203 15204 4102 +a 15204 15205 4102 +a 15205 15206 4102 +a 15206 15207 4102 +a 15207 15208 4102 +a 15208 15209 4102 +a 15209 15210 4102 +a 15210 15211 4102 +a 15211 15212 4102 +a 15212 15213 4102 +a 15213 15214 4102 +a 15214 15215 4102 +a 15215 15216 4102 +a 15216 15217 4102 +a 15217 15218 4102 +a 15218 15219 4102 +a 15219 15220 4102 +a 15220 15221 4102 +a 15221 15222 4102 +a 15222 15223 4102 +a 15223 15224 4102 +a 15224 15225 4102 +a 15225 15226 4102 +a 15226 15227 4102 +a 15227 15228 4102 +a 15228 15229 4102 +a 15229 15230 4102 +a 15230 15231 4102 +a 15231 15232 4102 +a 15232 15233 4102 +a 15233 15234 4102 +a 15234 15235 4102 +a 15235 15236 4102 +a 15236 15237 4102 +a 15237 15238 4102 +a 15238 15239 4102 +a 15239 15240 4102 +a 15240 15241 4102 +a 15241 15242 4102 +a 15242 15243 4102 +a 15243 15244 4102 +a 15244 15245 4102 +a 15245 15246 4102 +a 15246 15247 4102 +a 15247 15248 4102 +a 15248 15249 4102 +a 15249 15250 4102 +a 15250 15251 4102 +a 15251 15252 4102 +a 15252 15253 4102 +a 15253 15254 4102 +a 15254 15255 4102 +a 15255 15256 4102 +a 15256 15257 4102 +a 15257 15258 4102 +a 15258 15259 4102 +a 15259 15260 4102 +a 15260 15261 4102 +a 15261 15262 4102 +a 15262 15263 4102 +a 15263 15264 4102 +a 15264 15265 4102 +a 15265 15266 4102 +a 15266 15267 4102 +a 15267 15268 4102 +a 15268 15269 4102 +a 15269 15270 4102 +a 15270 15271 4102 +a 15271 15272 4102 +a 15272 15273 4102 +a 15273 15274 4102 +a 15274 15275 4102 +a 15275 15276 4102 +a 15276 15277 4102 +a 15277 15278 4102 +a 15278 15279 4102 +a 15279 15280 4102 +a 15280 15281 4102 +a 15281 15282 4102 +a 15282 15283 4102 +a 15283 15284 4102 +a 15284 15285 4102 +a 15285 15286 4102 +a 15286 15287 4102 +a 15287 15288 4102 +a 15288 15289 4102 +a 15289 15290 4102 +a 15290 15291 4102 +a 15291 15292 4102 +a 15292 15293 4102 +a 15293 15294 4102 +a 15294 15295 4102 +a 15295 15296 4102 +a 15296 15297 4102 +a 15297 15298 4102 +a 15298 15299 4102 +a 15299 15300 4102 +a 15300 15301 4102 +a 15301 15302 4102 +a 15302 15303 4102 +a 15303 15304 4102 +a 15304 15305 4102 +a 15305 15306 4102 +a 15306 15307 4102 +a 15307 15308 4102 +a 15308 15309 4102 +a 15309 15310 4102 +a 15310 15311 4102 +a 15311 15312 4102 +a 15312 15313 4102 +a 15313 15314 4102 +a 15314 15315 4102 +a 15315 15316 4102 +a 15316 15317 4102 +a 15317 15318 4102 +a 15318 15319 4102 +a 15319 15320 4102 +a 15320 15321 4102 +a 15321 15322 4102 +a 15322 15323 4102 +a 15323 15324 4102 +a 15324 15325 4102 +a 15325 15326 4102 +a 15326 15327 4102 +a 15327 15328 4102 +a 15328 15329 4102 +a 15329 15330 4102 +a 15330 15331 4102 +a 15331 15332 4102 +a 15332 15333 4102 +a 15333 15334 4102 +a 15334 15335 4102 +a 15335 15336 4102 +a 15336 15337 4102 +a 15337 15338 4102 +a 15338 15339 4102 +a 15339 15340 4102 +a 15340 15341 4102 +a 15341 15342 4102 +a 15342 15343 4102 +a 15343 15344 4102 +a 15344 15345 4102 +a 15345 15346 4102 +a 15346 15347 4102 +a 15347 15348 4102 +a 15348 15349 4102 +a 15349 15350 4102 +a 15350 15351 4102 +a 15351 15352 4102 +a 15352 15353 4102 +a 15353 15354 4102 +a 15354 15355 4102 +a 15355 15356 4102 +a 15356 15357 4102 +a 15357 15358 4102 +a 15358 15359 4102 +a 15359 15360 4102 +a 15360 15361 4102 +a 15361 15362 4102 +a 15362 15363 4102 +a 15363 15364 4102 +a 15364 15365 4102 +a 15365 15366 4102 +a 15366 15367 4102 +a 15367 15368 4102 +a 15368 15369 4102 +a 15369 15370 4102 +a 15370 15371 4102 +a 15371 15372 4102 +a 15372 15373 4102 +a 15373 15374 4102 +a 15374 15375 4102 +a 15375 15376 4102 +a 15376 15377 4102 +a 15377 15378 4102 +a 15378 15379 4102 +a 15379 15380 4102 +a 15380 15381 4102 +a 15381 15382 4102 +a 15382 15383 4102 +a 15383 15384 4102 +a 15384 15385 4102 +a 15385 15386 4102 +a 15386 15387 4102 +a 15387 15388 4102 +a 15388 15389 4102 +a 15389 15390 4102 +a 15390 15391 4102 +a 15391 15392 4102 +a 15392 15393 4102 +a 15393 15394 4102 +a 15394 15395 4102 +a 15395 15396 4102 +a 15396 15397 4102 +a 15397 15398 4102 +a 15398 15399 4102 +a 15399 15400 4102 +a 15400 15401 4102 +a 15401 15402 4102 +a 15402 15403 4102 +a 15403 15404 4102 +a 15404 15405 4102 +a 15405 15406 4102 +a 15406 15407 4102 +a 15407 15408 4102 +a 15408 15409 4102 +a 15409 15410 4102 +a 15410 15411 4102 +a 15411 15412 4102 +a 15412 15413 4102 +a 15413 15414 4102 +a 15414 15415 4102 +a 15415 15416 4102 +a 15416 15417 4102 +a 15417 15418 4102 +a 15418 15419 4102 +a 15419 15420 4102 +a 15420 15421 4102 +a 15421 15422 4102 +a 15422 15423 4102 +a 15423 15424 4102 +a 15424 15425 4102 +a 15425 15426 4102 +a 15426 15427 4102 +a 15427 15428 4102 +a 15428 15429 4102 +a 15429 15430 4102 +a 15430 15431 4102 +a 15431 15432 4102 +a 15432 15433 4102 +a 15433 15434 4102 +a 15434 15435 4102 +a 15435 15436 4102 +a 15436 15437 4102 +a 15437 15438 4102 +a 15438 15439 4102 +a 15439 15440 4102 +a 15440 15441 4102 +a 15441 15442 4102 +a 15442 15443 4102 +a 15443 15444 4102 +a 15444 15445 4102 +a 15445 15446 4102 +a 15446 15447 4102 +a 15447 15448 4102 +a 15448 15449 4102 +a 15449 15450 4102 +a 15450 15451 4102 +a 15451 15452 4102 +a 15452 15453 4102 +a 15453 15454 4102 +a 15454 15455 4102 +a 15455 15456 4102 +a 15456 15457 4102 +a 15457 15458 4102 +a 15458 15459 4102 +a 15459 15460 4102 +a 15460 15461 4102 +a 15461 15462 4102 +a 15462 15463 4102 +a 15463 15464 4102 +a 15464 15465 4102 +a 15465 15466 4102 +a 15466 15467 4102 +a 15467 15468 4102 +a 15468 15469 4102 +a 15469 15470 4102 +a 15470 15471 4102 +a 15471 15472 4102 +a 15472 15473 4102 +a 15473 15474 4102 +a 15474 15475 4102 +a 15475 15476 4102 +a 15476 15477 4102 +a 15477 15478 4102 +a 15478 15479 4102 +a 15479 15480 4102 +a 15480 15481 4102 +a 15481 15482 4102 +a 15482 15483 4102 +a 15483 15484 4102 +a 15484 15485 4102 +a 15485 15486 4102 +a 15486 15487 4102 +a 15487 15488 4102 +a 15488 15489 4102 +a 15489 15490 4102 +a 15490 15491 4102 +a 15491 15492 4102 +a 15492 15493 4102 +a 15493 15494 4102 +a 15494 15495 4102 +a 15495 15496 4102 +a 15496 15497 4102 +a 15497 15498 4102 +a 15498 15499 4102 +a 15499 15500 4102 +a 15500 15501 4102 +a 15501 15502 4102 +a 15502 15503 4102 +a 15503 15504 4102 +a 15504 15505 4102 +a 15505 15506 4102 +a 15506 15507 4102 +a 15507 15508 4102 +a 15508 15509 4102 +a 15509 15510 4102 +a 15510 15511 4102 +a 15511 15512 4102 +a 15512 15513 4102 +a 15513 15514 4102 +a 15514 15515 4102 +a 15515 15516 4102 +a 15516 15517 4102 +a 15517 15518 4102 +a 15518 15519 4102 +a 15519 15520 4102 +a 15520 15521 4102 +a 15521 15522 4102 +a 15522 15523 4102 +a 15523 15524 4102 +a 15524 15525 4102 +a 15525 15526 4102 +a 15526 15527 4102 +a 15527 15528 4102 +a 15528 15529 4102 +a 15529 15530 4102 +a 15530 15531 4102 +a 15531 15532 4102 +a 15532 15533 4102 +a 15533 15534 4102 +a 15534 15535 4102 +a 15535 15536 4102 +a 15536 15537 4102 +a 15537 15538 4102 +a 15538 15539 4102 +a 15539 15540 4102 +a 15540 15541 4102 +a 15541 15542 4102 +a 15542 15543 4102 +a 15543 15544 4102 +a 15544 15545 4102 +a 15545 15546 4102 +a 15546 15547 4102 +a 15547 15548 4102 +a 15548 15549 4102 +a 15549 15550 4102 +a 15550 15551 4102 +a 15551 15552 4102 +a 15552 15553 4102 +a 15553 15554 4102 +a 15554 15555 4102 +a 15555 15556 4102 +a 15556 15557 4102 +a 15557 15558 4102 +a 15558 15559 4102 +a 15559 15560 4102 +a 15560 15561 4102 +a 15561 15562 4102 +a 15562 15563 4102 +a 15563 15564 4102 +a 15564 15565 4102 +a 15565 15566 4102 +a 15566 15567 4102 +a 15567 15568 4102 +a 15568 15569 4102 +a 15569 15570 4102 +a 15570 15571 4102 +a 15571 15572 4102 +a 15572 15573 4102 +a 15573 15574 4102 +a 15574 15575 4102 +a 15575 15576 4102 +a 15576 15577 4102 +a 15577 15578 4102 +a 15578 15579 4102 +a 15579 15580 4102 +a 15580 15581 4102 +a 15581 15582 4102 +a 15582 15583 4102 +a 15583 15584 4102 +a 15584 15585 4102 +a 15585 15586 4102 +a 15586 15587 4102 +a 15587 15588 4102 +a 15588 15589 4102 +a 15589 15590 4102 +a 15590 15591 4102 +a 15591 15592 4102 +a 15592 15593 4102 +a 15593 15594 4102 +a 15594 15595 4102 +a 15595 15596 4102 +a 15596 15597 4102 +a 15597 15598 4102 +a 15598 15599 4102 +a 15599 15600 4102 +a 15600 15601 4102 +a 15601 15602 4102 +a 15602 15603 4102 +a 15603 15604 4102 +a 15604 15605 4102 +a 15605 15606 4102 +a 15606 15607 4102 +a 15607 15608 4102 +a 15608 15609 4102 +a 15609 15610 4102 +a 15610 15611 4102 +a 15611 15612 4102 +a 15612 15613 4102 +a 15613 15614 4102 +a 15614 15615 4102 +a 15615 15616 4102 +a 15616 15617 4102 +a 15617 15618 4102 +a 15618 15619 4102 +a 15619 15620 4102 +a 15620 15621 4102 +a 15621 15622 4102 +a 15622 15623 4102 +a 15623 15624 4102 +a 15624 15625 4102 +a 15625 15626 4102 +a 15626 15627 4102 +a 15627 15628 4102 +a 15628 15629 4102 +a 15629 15630 4102 +a 15630 15631 4102 +a 15631 15632 4102 +a 15632 15633 4102 +a 15633 15634 4102 +a 15634 15635 4102 +a 15635 15636 4102 +a 15636 15637 4102 +a 15637 15638 4102 +a 15638 15639 4102 +a 15639 15640 4102 +a 15640 15641 4102 +a 15641 15642 4102 +a 15642 15643 4102 +a 15643 15644 4102 +a 15644 15645 4102 +a 15645 15646 4102 +a 15646 15647 4102 +a 15647 15648 4102 +a 15648 15649 4102 +a 15649 15650 4102 +a 15650 15651 4102 +a 15651 15652 4102 +a 15652 15653 4102 +a 15653 15654 4102 +a 15654 15655 4102 +a 15655 15656 4102 +a 15656 15657 4102 +a 15657 15658 4102 +a 15658 15659 4102 +a 15659 15660 4102 +a 15660 15661 4102 +a 15661 15662 4102 +a 15662 15663 4102 +a 15663 15664 4102 +a 15664 15665 4102 +a 15665 15666 4102 +a 15666 15667 4102 +a 15667 15668 4102 +a 15668 15669 4102 +a 15669 15670 4102 +a 15670 15671 4102 +a 15671 15672 4102 +a 15672 15673 4102 +a 15673 15674 4102 +a 15674 15675 4102 +a 15675 15676 4102 +a 15676 15677 4102 +a 15677 15678 4102 +a 15678 15679 4102 +a 15679 15680 4102 +a 15680 15681 4102 +a 15681 15682 4102 +a 15682 15683 4102 +a 15683 15684 4102 +a 15684 15685 4102 +a 15685 15686 4102 +a 15686 15687 4102 +a 15687 15688 4102 +a 15688 15689 4102 +a 15689 15690 4102 +a 15690 15691 4102 +a 15691 15692 4102 +a 15692 15693 4102 +a 15693 15694 4102 +a 15694 15695 4102 +a 15695 15696 4102 +a 15696 15697 4102 +a 15697 15698 4102 +a 15698 15699 4102 +a 15699 15700 4102 +a 15700 15701 4102 +a 15701 15702 4102 +a 15702 15703 4102 +a 15703 15704 4102 +a 15704 15705 4102 +a 15705 15706 4102 +a 15706 15707 4102 +a 15707 15708 4102 +a 15708 15709 4102 +a 15709 15710 4102 +a 15710 15711 4102 +a 15711 15712 4102 +a 15712 15713 4102 +a 15713 15714 4102 +a 15714 15715 4102 +a 15715 15716 4102 +a 15716 15717 4102 +a 15717 15718 4102 +a 15718 15719 4102 +a 15719 15720 4102 +a 15720 15721 4102 +a 15721 15722 4102 +a 15722 15723 4102 +a 15723 15724 4102 +a 15724 15725 4102 +a 15725 15726 4102 +a 15726 15727 4102 +a 15727 15728 4102 +a 15728 15729 4102 +a 15729 15730 4102 +a 15730 15731 4102 +a 15731 15732 4102 +a 15732 15733 4102 +a 15733 15734 4102 +a 15734 15735 4102 +a 15735 15736 4102 +a 15736 15737 4102 +a 15737 15738 4102 +a 15738 15739 4102 +a 15739 15740 4102 +a 15740 15741 4102 +a 15741 15742 4102 +a 15742 15743 4102 +a 15743 15744 4102 +a 15744 15745 4102 +a 15745 15746 4102 +a 15746 15747 4102 +a 15747 15748 4102 +a 15748 15749 4102 +a 15749 15750 4102 +a 15750 15751 4102 +a 15751 15752 4102 +a 15752 15753 4102 +a 15753 15754 4102 +a 15754 15755 4102 +a 15755 15756 4102 +a 15756 15757 4102 +a 15757 15758 4102 +a 15758 15759 4102 +a 15759 15760 4102 +a 15760 15761 4102 +a 15761 15762 4102 +a 15762 15763 4102 +a 15763 15764 4102 +a 15764 15765 4102 +a 15765 15766 4102 +a 15766 15767 4102 +a 15767 15768 4102 +a 15768 15769 4102 +a 15769 15770 4102 +a 15770 15771 4102 +a 15771 15772 4102 +a 15772 15773 4102 +a 15773 15774 4102 +a 15774 15775 4102 +a 15775 15776 4102 +a 15776 15777 4102 +a 15777 15778 4102 +a 15778 15779 4102 +a 15779 15780 4102 +a 15780 15781 4102 +a 15781 15782 4102 +a 15782 15783 4102 +a 15783 15784 4102 +a 15784 15785 4102 +a 15785 15786 4102 +a 15786 15787 4102 +a 15787 15788 4102 +a 15788 15789 4102 +a 15789 15790 4102 +a 15790 15791 4102 +a 15791 15792 4102 +a 15792 15793 4102 +a 15793 15794 4102 +a 15794 15795 4102 +a 15795 15796 4102 +a 15796 15797 4102 +a 15797 15798 4102 +a 15798 15799 4102 +a 15799 15800 4102 +a 15800 15801 4102 +a 15801 15802 4102 +a 15802 15803 4102 +a 15803 15804 4102 +a 15804 15805 4102 +a 15805 15806 4102 +a 15806 15807 4102 +a 15807 15808 4102 +a 15808 15809 4102 +a 15809 15810 4102 +a 15810 15811 4102 +a 15811 15812 4102 +a 15812 15813 4102 +a 15813 15814 4102 +a 15814 15815 4102 +a 15815 15816 4102 +a 15816 15817 4102 +a 15817 15818 4102 +a 15818 15819 4102 +a 15819 15820 4102 +a 15820 15821 4102 +a 15821 15822 4102 +a 15822 15823 4102 +a 15823 15824 4102 +a 15824 15825 4102 +a 15825 15826 4102 +a 15826 15827 4102 +a 15827 15828 4102 +a 15828 15829 4102 +a 15829 15830 4102 +a 15830 15831 4102 +a 15831 15832 4102 +a 15832 15833 4102 +a 15833 15834 4102 +a 15834 15835 4102 +a 15835 15836 4102 +a 15836 15837 4102 +a 15837 15838 4102 +a 15838 15839 4102 +a 15839 15840 4102 +a 15840 15841 4102 +a 15841 15842 4102 +a 15842 15843 4102 +a 15843 15844 4102 +a 15844 15845 4102 +a 15845 15846 4102 +a 15846 15847 4102 +a 15847 15848 4102 +a 15848 15849 4102 +a 15849 15850 4102 +a 15850 15851 4102 +a 15851 15852 4102 +a 15852 15853 4102 +a 15853 15854 4102 +a 15854 15855 4102 +a 15855 15856 4102 +a 15856 15857 4102 +a 15857 15858 4102 +a 15858 15859 4102 +a 15859 15860 4102 +a 15860 15861 4102 +a 15861 15862 4102 +a 15862 15863 4102 +a 15863 15864 4102 +a 15864 15865 4102 +a 15865 15866 4102 +a 15866 15867 4102 +a 15867 15868 4102 +a 15868 15869 4102 +a 15869 15870 4102 +a 15870 15871 4102 +a 15871 15872 4102 +a 15872 15873 4102 +a 15873 15874 4102 +a 15874 15875 4102 +a 15875 15876 4102 +a 15876 15877 4102 +a 15877 15878 4102 +a 15878 15879 4102 +a 15879 15880 4102 +a 15880 15881 4102 +a 15881 15882 4102 +a 15882 15883 4102 +a 15883 15884 4102 +a 15884 15885 4102 +a 15885 15886 4102 +a 15886 15887 4102 +a 15887 15888 4102 +a 15888 15889 4102 +a 15889 15890 4102 +a 15890 15891 4102 +a 15891 15892 4102 +a 15892 15893 4102 +a 15893 15894 4102 +a 15894 15895 4102 +a 15895 15896 4102 +a 15896 15897 4102 +a 15897 15898 4102 +a 15898 15899 4102 +a 15899 15900 4102 +a 15900 15901 4102 +a 15901 15902 4102 +a 15902 15903 4102 +a 15903 15904 4102 +a 15904 15905 4102 +a 15905 15906 4102 +a 15906 15907 4102 +a 15907 15908 4102 +a 15908 15909 4102 +a 15909 15910 4102 +a 15910 15911 4102 +a 15911 15912 4102 +a 15912 15913 4102 +a 15913 15914 4102 +a 15914 15915 4102 +a 15915 15916 4102 +a 15916 15917 4102 +a 15917 15918 4102 +a 15918 15919 4102 +a 15919 15920 4102 +a 15920 15921 4102 +a 15921 15922 4102 +a 15922 15923 4102 +a 15923 15924 4102 +a 15924 15925 4102 +a 15925 15926 4102 +a 15926 15927 4102 +a 15927 15928 4102 +a 15928 15929 4102 +a 15929 15930 4102 +a 15930 15931 4102 +a 15931 15932 4102 +a 15932 15933 4102 +a 15933 15934 4102 +a 15934 15935 4102 +a 15935 15936 4102 +a 15936 15937 4102 +a 15937 15938 4102 +a 15938 15939 4102 +a 15939 15940 4102 +a 15940 15941 4102 +a 15941 15942 4102 +a 15942 15943 4102 +a 15943 15944 4102 +a 15944 15945 4102 +a 15945 15946 4102 +a 15946 15947 4102 +a 15947 15948 4102 +a 15948 15949 4102 +a 15949 15950 4102 +a 15950 15951 4102 +a 15951 15952 4102 +a 15952 15953 4102 +a 15953 15954 4102 +a 15954 15955 4102 +a 15955 15956 4102 +a 15956 15957 4102 +a 15957 15958 4102 +a 15958 15959 4102 +a 15959 15960 4102 +a 15960 15961 4102 +a 15961 15962 4102 +a 15962 15963 4102 +a 15963 15964 4102 +a 15964 15965 4102 +a 15965 15966 4102 +a 15966 15967 4102 +a 15967 15968 4102 +a 15968 15969 4102 +a 15969 15970 4102 +a 15970 15971 4102 +a 15971 15972 4102 +a 15972 15973 4102 +a 15973 15974 4102 +a 15974 15975 4102 +a 15975 15976 4102 +a 15976 15977 4102 +a 15977 15978 4102 +a 15978 15979 4102 +a 15979 15980 4102 +a 15980 15981 4102 +a 15981 15982 4102 +a 15982 15983 4102 +a 15983 15984 4102 +a 15984 15985 4102 +a 15985 15986 4102 +a 15986 15987 4102 +a 15987 15988 4102 +a 15988 15989 4102 +a 15989 15990 4102 +a 15990 15991 4102 +a 15991 15992 4102 +a 15992 15993 4102 +a 15993 15994 4102 +a 15994 15995 4102 +a 15995 15996 4102 +a 15996 15997 4102 +a 15997 15998 4102 +a 15998 15999 4102 +a 15999 16000 4102 +a 16000 16001 4102 +a 16001 16002 4102 +a 16002 16003 4102 +a 16003 16004 4102 +a 16004 16005 4102 +a 16005 16006 4102 +a 16006 16007 4102 +a 16007 16008 4102 +a 16008 16009 4102 +a 16009 16010 4102 +a 16010 16011 4102 +a 16011 16012 4102 +a 16012 16013 4102 +a 16013 16014 4102 +a 16014 16015 4102 +a 16015 16016 4102 +a 16016 16017 4102 +a 16017 16018 4102 +a 16018 16019 4102 +a 16019 16020 4102 +a 16020 16021 4102 +a 16021 16022 4102 +a 16022 16023 4102 +a 16023 16024 4102 +a 16024 16025 4102 +a 16025 16026 4102 +a 16026 16027 4102 +a 16027 16028 4102 +a 16028 16029 4102 +a 16029 16030 4102 +a 16030 16031 4102 +a 16031 16032 4102 +a 16032 16033 4102 +a 16033 16034 4102 +a 16034 16035 4102 +a 16035 16036 4102 +a 16036 16037 4102 +a 16037 16038 4102 +a 16038 16039 4102 +a 16039 16040 4102 +a 16040 16041 4102 +a 16041 16042 4102 +a 16042 16043 4102 +a 16043 16044 4102 +a 16044 16045 4102 +a 16045 16046 4102 +a 16046 16047 4102 +a 16047 16048 4102 +a 16048 16049 4102 +a 16049 16050 4102 +a 16050 16051 4102 +a 16051 16052 4102 +a 16052 16053 4102 +a 16053 16054 4102 +a 16054 16055 4102 +a 16055 16056 4102 +a 16056 16057 4102 +a 16057 16058 4102 +a 16058 16059 4102 +a 16059 16060 4102 +a 16060 16061 4102 +a 16061 16062 4102 +a 16062 16063 4102 +a 16063 16064 4102 +a 16064 16065 4102 +a 16065 16066 4102 +a 16066 16067 4102 +a 16067 16068 4102 +a 16068 16069 4102 +a 16069 16070 4102 +a 16070 16071 4102 +a 16071 16072 4102 +a 16072 16073 4102 +a 16073 16074 4102 +a 16074 16075 4102 +a 16075 16076 4102 +a 16076 16077 4102 +a 16077 16078 4102 +a 16078 16079 4102 +a 16079 16080 4102 +a 16080 16081 4102 +a 16081 16082 4102 +a 16082 16083 4102 +a 16083 16084 4102 +a 16084 16085 4102 +a 16085 16086 4102 +a 16086 16087 4102 +a 16087 16088 4102 +a 16088 16089 4102 +a 16089 16090 4102 +a 16090 16091 4102 +a 16091 16092 4102 +a 16092 16093 4102 +a 16093 16094 4102 +a 16094 16095 4102 +a 16095 16096 4102 +a 16096 16097 4102 +a 16097 16098 4102 +a 16098 16099 4102 +a 16099 16100 4102 +a 16100 16101 4102 +a 16101 16102 4102 +a 16102 16103 4102 +a 16103 16104 4102 +a 16104 16105 4102 +a 16105 16106 4102 +a 16106 16107 4102 +a 16107 16108 4102 +a 16108 16109 4102 +a 16109 16110 4102 +a 16110 16111 4102 +a 16111 16112 4102 +a 16112 16113 4102 +a 16113 16114 4102 +a 16114 16115 4102 +a 16115 16116 4102 +a 16116 16117 4102 +a 16117 16118 4102 +a 16118 16119 4102 +a 16119 16120 4102 +a 16120 16121 4102 +a 16121 16122 4102 +a 16122 16123 4102 +a 16123 16124 4102 +a 16124 16125 4102 +a 16125 16126 4102 +a 16126 16127 4102 +a 16127 16128 4102 +a 16128 16129 4102 +a 16129 16130 4102 +a 16130 16131 4102 +a 16131 16132 4102 +a 16132 16133 4102 +a 16133 16134 4102 +a 16134 16135 4102 +a 16135 16136 4102 +a 16136 16137 4102 +a 16137 16138 4102 +a 16138 16139 4102 +a 16139 16140 4102 +a 16140 16141 4102 +a 16141 16142 4102 +a 16142 16143 4102 +a 16143 16144 4102 +a 16144 16145 4102 +a 16145 16146 4102 +a 16146 16147 4102 +a 16147 16148 4102 +a 16148 16149 4102 +a 16149 16150 4102 +a 16150 16151 4102 +a 16151 16152 4102 +a 16152 16153 4102 +a 16153 16154 4102 +a 16154 16155 4102 +a 16155 16156 4102 +a 16156 16157 4102 +a 16157 16158 4102 +a 16158 16159 4102 +a 16159 16160 4102 +a 16160 16161 4102 +a 16161 16162 4102 +a 16162 16163 4102 +a 16163 16164 4102 +a 16164 16165 4102 +a 16165 16166 4102 +a 16166 16167 4102 +a 16167 16168 4102 +a 16168 16169 4102 +a 16169 16170 4102 +a 16170 16171 4102 +a 16171 16172 4102 +a 16172 16173 4102 +a 16173 16174 4102 +a 16174 16175 4102 +a 16175 16176 4102 +a 16176 16177 4102 +a 16177 16178 4102 +a 16178 16179 4102 +a 16179 16180 4102 +a 16180 16181 4102 +a 16181 16182 4102 +a 16182 16183 4102 +a 16183 16184 4102 +a 16184 16185 4102 +a 16185 16186 4102 +a 16186 16187 4102 +a 16187 16188 4102 +a 16188 16189 4102 +a 16189 16190 4102 +a 16190 16191 4102 +a 16191 16192 4102 +a 16192 16193 4102 +a 16193 16194 4102 +a 16194 16195 4102 +a 16195 16196 4102 +a 16196 16197 4102 +a 16197 16198 4102 +a 16198 16199 4102 +a 16199 16200 4102 +a 16200 16201 4102 +a 16201 16202 4102 +a 16202 16203 4102 +a 16203 16204 4102 +a 16204 16205 4102 +a 16205 16206 4102 +a 16206 16207 4102 +a 16207 16208 4102 +a 16208 16209 4102 +a 16209 16210 4102 +a 16210 16211 4102 +a 16211 16212 4102 +a 16212 16213 4102 +a 16213 16214 4102 +a 16214 16215 4102 +a 16215 16216 4102 +a 16216 16217 4102 +a 16217 16218 4102 +a 16218 16219 4102 +a 16219 16220 4102 +a 16220 16221 4102 +a 16221 16222 4102 +a 16222 16223 4102 +a 16223 16224 4102 +a 16224 16225 4102 +a 16225 16226 4102 +a 16226 16227 4102 +a 16227 16228 4102 +a 16228 16229 4102 +a 16229 16230 4102 +a 16230 16231 4102 +a 16231 16232 4102 +a 16232 16233 4102 +a 16233 16234 4102 +a 16234 16235 4102 +a 16235 16236 4102 +a 16236 16237 4102 +a 16237 16238 4102 +a 16238 16239 4102 +a 16239 16240 4102 +a 16240 16241 4102 +a 16241 16242 4102 +a 16242 16243 4102 +a 16243 16244 4102 +a 16244 16245 4102 +a 16245 16246 4102 +a 16246 16247 4102 +a 16247 16248 4102 +a 16248 16249 4102 +a 16249 16250 4102 +a 16250 16251 4102 +a 16251 16252 4102 +a 16252 16253 4102 +a 16253 16254 4102 +a 16254 16255 4102 +a 16255 16256 4102 +a 16256 16257 4102 +a 16257 16258 4102 +a 16258 16259 4102 +a 16259 16260 4102 +a 16260 16261 4102 +a 16261 16262 4102 +a 16262 16263 4102 +a 16263 16264 4102 +a 16264 16265 4102 +a 16265 16266 4102 +a 16266 16267 4102 +a 16267 16268 4102 +a 16268 16269 4102 +a 16269 16270 4102 +a 16270 16271 4102 +a 16271 16272 4102 +a 16272 16273 4102 +a 16273 16274 4102 +a 16274 16275 4102 +a 16275 16276 4102 +a 16276 16277 4102 +a 16277 16278 4102 +a 16278 16279 4102 +a 16279 16280 4102 +a 16280 16281 4102 +a 16281 16282 4102 +a 16282 16283 4102 +a 16283 16284 4102 +a 16284 16285 4102 +a 16285 16286 4102 +a 16286 16287 4102 +a 16287 16288 4102 +a 16288 16289 4102 +a 16289 16290 4102 +a 16290 16291 4102 +a 16291 16292 4102 +a 16292 16293 4102 +a 16293 16294 4102 +a 16294 16295 4102 +a 16295 16296 4102 +a 16296 16297 4102 +a 16297 16298 4102 +a 16298 16299 4102 +a 16299 16300 4102 +a 16300 16301 4102 +a 16301 16302 4102 +a 16302 16303 4102 +a 16303 16304 4102 +a 16304 16305 4102 +a 16305 16306 4102 +a 16306 16307 4102 +a 16307 16308 4102 +a 16308 16309 4102 +a 16309 16310 4102 +a 16310 16311 4102 +a 16311 16312 4102 +a 16312 16313 4102 +a 16313 16314 4102 +a 16314 16315 4102 +a 16315 16316 4102 +a 16316 16317 4102 +a 16317 16318 4102 +a 16318 16319 4102 +a 16319 16320 4102 +a 16320 16321 4102 +a 16321 16322 4102 +a 16322 16323 4102 +a 16323 16324 4102 +a 16324 16325 4102 +a 16325 16326 4102 +a 16326 16327 4102 +a 16327 16328 4102 +a 16328 16329 4102 +a 16329 16330 4102 +a 16330 16331 4102 +a 16331 16332 4102 +a 16332 16333 4102 +a 16333 16334 4102 +a 16334 16335 4102 +a 16335 16336 4102 +a 16336 16337 4102 +a 16337 16338 4102 +a 16338 16339 4102 +a 16339 16340 4102 +a 16340 16341 4102 +a 16341 16342 4102 +a 16342 16343 4102 +a 16343 16344 4102 +a 16344 16345 4102 +a 16345 16346 4102 +a 16346 16347 4102 +a 16347 16348 4102 +a 16348 16349 4102 +a 16349 16350 4102 +a 16350 16351 4102 +a 16351 16352 4102 +a 16352 16353 4102 +a 16353 16354 4102 +a 16354 16355 4102 +a 16355 16356 4102 +a 16356 16357 4102 +a 16357 16358 4102 +a 16358 16359 4102 +a 16359 16360 4102 +a 16360 16361 4102 +a 16361 16362 4102 +a 16362 16363 4102 +a 16363 16364 4102 +a 16364 16365 4102 +a 16365 16366 4102 +a 16366 16367 4102 +a 16367 16368 4102 +a 16368 16369 4102 +a 16369 16370 4102 +a 16370 16371 4102 +a 16371 16372 4102 +a 16372 16373 4102 +a 16373 16374 4102 +a 16374 16375 4102 +a 16375 16376 4102 +a 16376 16377 4102 +a 16377 16378 4102 +a 16378 16379 4102 +a 16379 16380 4102 +a 16380 16381 4102 +a 16381 16382 4102 +a 16382 16383 4102 +a 16383 16384 4102 +a 16384 16385 4102 +a 16385 16386 4102 +a 16386 16387 4102 +a 16387 16388 4102 +a 16388 16389 4102 +a 16389 16390 4102 +a 16390 16391 4102 +a 16391 16392 4102 +a 16392 16393 4102 +a 16393 16394 4102 +a 16394 16395 4102 +a 16395 16396 4102 +a 16396 16397 4102 +a 16397 16398 4102 +a 16398 16399 4102 +a 16399 16400 4102 +a 16400 16401 4102 +a 16401 16402 4102 +a 16402 16403 4102 +a 16403 16404 4102 +a 16404 16405 4102 +a 16405 16406 4102 +a 16406 16407 4102 +a 16407 16408 4102 +a 16408 16409 4102 +a 16409 16410 4102 +a 16410 16411 4102 +a 16411 16412 4102 +a 16412 16413 4102 +a 16413 16414 4102 +a 8209 16414 1 +a 8210 16413 1 +a 8211 16412 1 +a 8212 16411 1 +a 8213 16410 1 +a 8214 16409 1 +a 8215 16408 1 +a 8216 16407 1 +a 8217 16406 1 +a 8218 16405 1 +a 8219 16404 1 +a 8220 16403 1 +a 8221 16402 1 +a 8222 16401 1 +a 8223 16400 1 +a 8224 16399 1 +a 8225 16398 1 +a 8226 16397 1 +a 8227 16396 1 +a 8228 16395 1 +a 8229 16394 1 +a 8230 16393 1 +a 8231 16392 1 +a 8232 16391 1 +a 8233 16390 1 +a 8234 16389 1 +a 8235 16388 1 +a 8236 16387 1 +a 8237 16386 1 +a 8238 16385 1 +a 8239 16384 1 +a 8240 16383 1 +a 8241 16382 1 +a 8242 16381 1 +a 8243 16380 1 +a 8244 16379 1 +a 8245 16378 1 +a 8246 16377 1 +a 8247 16376 1 +a 8248 16375 1 +a 8249 16374 1 +a 8250 16373 1 +a 8251 16372 1 +a 8252 16371 1 +a 8253 16370 1 +a 8254 16369 1 +a 8255 16368 1 +a 8256 16367 1 +a 8257 16366 1 +a 8258 16365 1 +a 8259 16364 1 +a 8260 16363 1 +a 8261 16362 1 +a 8262 16361 1 +a 8263 16360 1 +a 8264 16359 1 +a 8265 16358 1 +a 8266 16357 1 +a 8267 16356 1 +a 8268 16355 1 +a 8269 16354 1 +a 8270 16353 1 +a 8271 16352 1 +a 8272 16351 1 +a 8273 16350 1 +a 8274 16349 1 +a 8275 16348 1 +a 8276 16347 1 +a 8277 16346 1 +a 8278 16345 1 +a 8279 16344 1 +a 8280 16343 1 +a 8281 16342 1 +a 8282 16341 1 +a 8283 16340 1 +a 8284 16339 1 +a 8285 16338 1 +a 8286 16337 1 +a 8287 16336 1 +a 8288 16335 1 +a 8289 16334 1 +a 8290 16333 1 +a 8291 16332 1 +a 8292 16331 1 +a 8293 16330 1 +a 8294 16329 1 +a 8295 16328 1 +a 8296 16327 1 +a 8297 16326 1 +a 8298 16325 1 +a 8299 16324 1 +a 8300 16323 1 +a 8301 16322 1 +a 8302 16321 1 +a 8303 16320 1 +a 8304 16319 1 +a 8305 16318 1 +a 8306 16317 1 +a 8307 16316 1 +a 8308 16315 1 +a 8309 16314 1 +a 8310 16313 1 +a 8311 16312 1 +a 8312 16311 1 +a 8313 16310 1 +a 8314 16309 1 +a 8315 16308 1 +a 8316 16307 1 +a 8317 16306 1 +a 8318 16305 1 +a 8319 16304 1 +a 8320 16303 1 +a 8321 16302 1 +a 8322 16301 1 +a 8323 16300 1 +a 8324 16299 1 +a 8325 16298 1 +a 8326 16297 1 +a 8327 16296 1 +a 8328 16295 1 +a 8329 16294 1 +a 8330 16293 1 +a 8331 16292 1 +a 8332 16291 1 +a 8333 16290 1 +a 8334 16289 1 +a 8335 16288 1 +a 8336 16287 1 +a 8337 16286 1 +a 8338 16285 1 +a 8339 16284 1 +a 8340 16283 1 +a 8341 16282 1 +a 8342 16281 1 +a 8343 16280 1 +a 8344 16279 1 +a 8345 16278 1 +a 8346 16277 1 +a 8347 16276 1 +a 8348 16275 1 +a 8349 16274 1 +a 8350 16273 1 +a 8351 16272 1 +a 8352 16271 1 +a 8353 16270 1 +a 8354 16269 1 +a 8355 16268 1 +a 8356 16267 1 +a 8357 16266 1 +a 8358 16265 1 +a 8359 16264 1 +a 8360 16263 1 +a 8361 16262 1 +a 8362 16261 1 +a 8363 16260 1 +a 8364 16259 1 +a 8365 16258 1 +a 8366 16257 1 +a 8367 16256 1 +a 8368 16255 1 +a 8369 16254 1 +a 8370 16253 1 +a 8371 16252 1 +a 8372 16251 1 +a 8373 16250 1 +a 8374 16249 1 +a 8375 16248 1 +a 8376 16247 1 +a 8377 16246 1 +a 8378 16245 1 +a 8379 16244 1 +a 8380 16243 1 +a 8381 16242 1 +a 8382 16241 1 +a 8383 16240 1 +a 8384 16239 1 +a 8385 16238 1 +a 8386 16237 1 +a 8387 16236 1 +a 8388 16235 1 +a 8389 16234 1 +a 8390 16233 1 +a 8391 16232 1 +a 8392 16231 1 +a 8393 16230 1 +a 8394 16229 1 +a 8395 16228 1 +a 8396 16227 1 +a 8397 16226 1 +a 8398 16225 1 +a 8399 16224 1 +a 8400 16223 1 +a 8401 16222 1 +a 8402 16221 1 +a 8403 16220 1 +a 8404 16219 1 +a 8405 16218 1 +a 8406 16217 1 +a 8407 16216 1 +a 8408 16215 1 +a 8409 16214 1 +a 8410 16213 1 +a 8411 16212 1 +a 8412 16211 1 +a 8413 16210 1 +a 8414 16209 1 +a 8415 16208 1 +a 8416 16207 1 +a 8417 16206 1 +a 8418 16205 1 +a 8419 16204 1 +a 8420 16203 1 +a 8421 16202 1 +a 8422 16201 1 +a 8423 16200 1 +a 8424 16199 1 +a 8425 16198 1 +a 8426 16197 1 +a 8427 16196 1 +a 8428 16195 1 +a 8429 16194 1 +a 8430 16193 1 +a 8431 16192 1 +a 8432 16191 1 +a 8433 16190 1 +a 8434 16189 1 +a 8435 16188 1 +a 8436 16187 1 +a 8437 16186 1 +a 8438 16185 1 +a 8439 16184 1 +a 8440 16183 1 +a 8441 16182 1 +a 8442 16181 1 +a 8443 16180 1 +a 8444 16179 1 +a 8445 16178 1 +a 8446 16177 1 +a 8447 16176 1 +a 8448 16175 1 +a 8449 16174 1 +a 8450 16173 1 +a 8451 16172 1 +a 8452 16171 1 +a 8453 16170 1 +a 8454 16169 1 +a 8455 16168 1 +a 8456 16167 1 +a 8457 16166 1 +a 8458 16165 1 +a 8459 16164 1 +a 8460 16163 1 +a 8461 16162 1 +a 8462 16161 1 +a 8463 16160 1 +a 8464 16159 1 +a 8465 16158 1 +a 8466 16157 1 +a 8467 16156 1 +a 8468 16155 1 +a 8469 16154 1 +a 8470 16153 1 +a 8471 16152 1 +a 8472 16151 1 +a 8473 16150 1 +a 8474 16149 1 +a 8475 16148 1 +a 8476 16147 1 +a 8477 16146 1 +a 8478 16145 1 +a 8479 16144 1 +a 8480 16143 1 +a 8481 16142 1 +a 8482 16141 1 +a 8483 16140 1 +a 8484 16139 1 +a 8485 16138 1 +a 8486 16137 1 +a 8487 16136 1 +a 8488 16135 1 +a 8489 16134 1 +a 8490 16133 1 +a 8491 16132 1 +a 8492 16131 1 +a 8493 16130 1 +a 8494 16129 1 +a 8495 16128 1 +a 8496 16127 1 +a 8497 16126 1 +a 8498 16125 1 +a 8499 16124 1 +a 8500 16123 1 +a 8501 16122 1 +a 8502 16121 1 +a 8503 16120 1 +a 8504 16119 1 +a 8505 16118 1 +a 8506 16117 1 +a 8507 16116 1 +a 8508 16115 1 +a 8509 16114 1 +a 8510 16113 1 +a 8511 16112 1 +a 8512 16111 1 +a 8513 16110 1 +a 8514 16109 1 +a 8515 16108 1 +a 8516 16107 1 +a 8517 16106 1 +a 8518 16105 1 +a 8519 16104 1 +a 8520 16103 1 +a 8521 16102 1 +a 8522 16101 1 +a 8523 16100 1 +a 8524 16099 1 +a 8525 16098 1 +a 8526 16097 1 +a 8527 16096 1 +a 8528 16095 1 +a 8529 16094 1 +a 8530 16093 1 +a 8531 16092 1 +a 8532 16091 1 +a 8533 16090 1 +a 8534 16089 1 +a 8535 16088 1 +a 8536 16087 1 +a 8537 16086 1 +a 8538 16085 1 +a 8539 16084 1 +a 8540 16083 1 +a 8541 16082 1 +a 8542 16081 1 +a 8543 16080 1 +a 8544 16079 1 +a 8545 16078 1 +a 8546 16077 1 +a 8547 16076 1 +a 8548 16075 1 +a 8549 16074 1 +a 8550 16073 1 +a 8551 16072 1 +a 8552 16071 1 +a 8553 16070 1 +a 8554 16069 1 +a 8555 16068 1 +a 8556 16067 1 +a 8557 16066 1 +a 8558 16065 1 +a 8559 16064 1 +a 8560 16063 1 +a 8561 16062 1 +a 8562 16061 1 +a 8563 16060 1 +a 8564 16059 1 +a 8565 16058 1 +a 8566 16057 1 +a 8567 16056 1 +a 8568 16055 1 +a 8569 16054 1 +a 8570 16053 1 +a 8571 16052 1 +a 8572 16051 1 +a 8573 16050 1 +a 8574 16049 1 +a 8575 16048 1 +a 8576 16047 1 +a 8577 16046 1 +a 8578 16045 1 +a 8579 16044 1 +a 8580 16043 1 +a 8581 16042 1 +a 8582 16041 1 +a 8583 16040 1 +a 8584 16039 1 +a 8585 16038 1 +a 8586 16037 1 +a 8587 16036 1 +a 8588 16035 1 +a 8589 16034 1 +a 8590 16033 1 +a 8591 16032 1 +a 8592 16031 1 +a 8593 16030 1 +a 8594 16029 1 +a 8595 16028 1 +a 8596 16027 1 +a 8597 16026 1 +a 8598 16025 1 +a 8599 16024 1 +a 8600 16023 1 +a 8601 16022 1 +a 8602 16021 1 +a 8603 16020 1 +a 8604 16019 1 +a 8605 16018 1 +a 8606 16017 1 +a 8607 16016 1 +a 8608 16015 1 +a 8609 16014 1 +a 8610 16013 1 +a 8611 16012 1 +a 8612 16011 1 +a 8613 16010 1 +a 8614 16009 1 +a 8615 16008 1 +a 8616 16007 1 +a 8617 16006 1 +a 8618 16005 1 +a 8619 16004 1 +a 8620 16003 1 +a 8621 16002 1 +a 8622 16001 1 +a 8623 16000 1 +a 8624 15999 1 +a 8625 15998 1 +a 8626 15997 1 +a 8627 15996 1 +a 8628 15995 1 +a 8629 15994 1 +a 8630 15993 1 +a 8631 15992 1 +a 8632 15991 1 +a 8633 15990 1 +a 8634 15989 1 +a 8635 15988 1 +a 8636 15987 1 +a 8637 15986 1 +a 8638 15985 1 +a 8639 15984 1 +a 8640 15983 1 +a 8641 15982 1 +a 8642 15981 1 +a 8643 15980 1 +a 8644 15979 1 +a 8645 15978 1 +a 8646 15977 1 +a 8647 15976 1 +a 8648 15975 1 +a 8649 15974 1 +a 8650 15973 1 +a 8651 15972 1 +a 8652 15971 1 +a 8653 15970 1 +a 8654 15969 1 +a 8655 15968 1 +a 8656 15967 1 +a 8657 15966 1 +a 8658 15965 1 +a 8659 15964 1 +a 8660 15963 1 +a 8661 15962 1 +a 8662 15961 1 +a 8663 15960 1 +a 8664 15959 1 +a 8665 15958 1 +a 8666 15957 1 +a 8667 15956 1 +a 8668 15955 1 +a 8669 15954 1 +a 8670 15953 1 +a 8671 15952 1 +a 8672 15951 1 +a 8673 15950 1 +a 8674 15949 1 +a 8675 15948 1 +a 8676 15947 1 +a 8677 15946 1 +a 8678 15945 1 +a 8679 15944 1 +a 8680 15943 1 +a 8681 15942 1 +a 8682 15941 1 +a 8683 15940 1 +a 8684 15939 1 +a 8685 15938 1 +a 8686 15937 1 +a 8687 15936 1 +a 8688 15935 1 +a 8689 15934 1 +a 8690 15933 1 +a 8691 15932 1 +a 8692 15931 1 +a 8693 15930 1 +a 8694 15929 1 +a 8695 15928 1 +a 8696 15927 1 +a 8697 15926 1 +a 8698 15925 1 +a 8699 15924 1 +a 8700 15923 1 +a 8701 15922 1 +a 8702 15921 1 +a 8703 15920 1 +a 8704 15919 1 +a 8705 15918 1 +a 8706 15917 1 +a 8707 15916 1 +a 8708 15915 1 +a 8709 15914 1 +a 8710 15913 1 +a 8711 15912 1 +a 8712 15911 1 +a 8713 15910 1 +a 8714 15909 1 +a 8715 15908 1 +a 8716 15907 1 +a 8717 15906 1 +a 8718 15905 1 +a 8719 15904 1 +a 8720 15903 1 +a 8721 15902 1 +a 8722 15901 1 +a 8723 15900 1 +a 8724 15899 1 +a 8725 15898 1 +a 8726 15897 1 +a 8727 15896 1 +a 8728 15895 1 +a 8729 15894 1 +a 8730 15893 1 +a 8731 15892 1 +a 8732 15891 1 +a 8733 15890 1 +a 8734 15889 1 +a 8735 15888 1 +a 8736 15887 1 +a 8737 15886 1 +a 8738 15885 1 +a 8739 15884 1 +a 8740 15883 1 +a 8741 15882 1 +a 8742 15881 1 +a 8743 15880 1 +a 8744 15879 1 +a 8745 15878 1 +a 8746 15877 1 +a 8747 15876 1 +a 8748 15875 1 +a 8749 15874 1 +a 8750 15873 1 +a 8751 15872 1 +a 8752 15871 1 +a 8753 15870 1 +a 8754 15869 1 +a 8755 15868 1 +a 8756 15867 1 +a 8757 15866 1 +a 8758 15865 1 +a 8759 15864 1 +a 8760 15863 1 +a 8761 15862 1 +a 8762 15861 1 +a 8763 15860 1 +a 8764 15859 1 +a 8765 15858 1 +a 8766 15857 1 +a 8767 15856 1 +a 8768 15855 1 +a 8769 15854 1 +a 8770 15853 1 +a 8771 15852 1 +a 8772 15851 1 +a 8773 15850 1 +a 8774 15849 1 +a 8775 15848 1 +a 8776 15847 1 +a 8777 15846 1 +a 8778 15845 1 +a 8779 15844 1 +a 8780 15843 1 +a 8781 15842 1 +a 8782 15841 1 +a 8783 15840 1 +a 8784 15839 1 +a 8785 15838 1 +a 8786 15837 1 +a 8787 15836 1 +a 8788 15835 1 +a 8789 15834 1 +a 8790 15833 1 +a 8791 15832 1 +a 8792 15831 1 +a 8793 15830 1 +a 8794 15829 1 +a 8795 15828 1 +a 8796 15827 1 +a 8797 15826 1 +a 8798 15825 1 +a 8799 15824 1 +a 8800 15823 1 +a 8801 15822 1 +a 8802 15821 1 +a 8803 15820 1 +a 8804 15819 1 +a 8805 15818 1 +a 8806 15817 1 +a 8807 15816 1 +a 8808 15815 1 +a 8809 15814 1 +a 8810 15813 1 +a 8811 15812 1 +a 8812 15811 1 +a 8813 15810 1 +a 8814 15809 1 +a 8815 15808 1 +a 8816 15807 1 +a 8817 15806 1 +a 8818 15805 1 +a 8819 15804 1 +a 8820 15803 1 +a 8821 15802 1 +a 8822 15801 1 +a 8823 15800 1 +a 8824 15799 1 +a 8825 15798 1 +a 8826 15797 1 +a 8827 15796 1 +a 8828 15795 1 +a 8829 15794 1 +a 8830 15793 1 +a 8831 15792 1 +a 8832 15791 1 +a 8833 15790 1 +a 8834 15789 1 +a 8835 15788 1 +a 8836 15787 1 +a 8837 15786 1 +a 8838 15785 1 +a 8839 15784 1 +a 8840 15783 1 +a 8841 15782 1 +a 8842 15781 1 +a 8843 15780 1 +a 8844 15779 1 +a 8845 15778 1 +a 8846 15777 1 +a 8847 15776 1 +a 8848 15775 1 +a 8849 15774 1 +a 8850 15773 1 +a 8851 15772 1 +a 8852 15771 1 +a 8853 15770 1 +a 8854 15769 1 +a 8855 15768 1 +a 8856 15767 1 +a 8857 15766 1 +a 8858 15765 1 +a 8859 15764 1 +a 8860 15763 1 +a 8861 15762 1 +a 8862 15761 1 +a 8863 15760 1 +a 8864 15759 1 +a 8865 15758 1 +a 8866 15757 1 +a 8867 15756 1 +a 8868 15755 1 +a 8869 15754 1 +a 8870 15753 1 +a 8871 15752 1 +a 8872 15751 1 +a 8873 15750 1 +a 8874 15749 1 +a 8875 15748 1 +a 8876 15747 1 +a 8877 15746 1 +a 8878 15745 1 +a 8879 15744 1 +a 8880 15743 1 +a 8881 15742 1 +a 8882 15741 1 +a 8883 15740 1 +a 8884 15739 1 +a 8885 15738 1 +a 8886 15737 1 +a 8887 15736 1 +a 8888 15735 1 +a 8889 15734 1 +a 8890 15733 1 +a 8891 15732 1 +a 8892 15731 1 +a 8893 15730 1 +a 8894 15729 1 +a 8895 15728 1 +a 8896 15727 1 +a 8897 15726 1 +a 8898 15725 1 +a 8899 15724 1 +a 8900 15723 1 +a 8901 15722 1 +a 8902 15721 1 +a 8903 15720 1 +a 8904 15719 1 +a 8905 15718 1 +a 8906 15717 1 +a 8907 15716 1 +a 8908 15715 1 +a 8909 15714 1 +a 8910 15713 1 +a 8911 15712 1 +a 8912 15711 1 +a 8913 15710 1 +a 8914 15709 1 +a 8915 15708 1 +a 8916 15707 1 +a 8917 15706 1 +a 8918 15705 1 +a 8919 15704 1 +a 8920 15703 1 +a 8921 15702 1 +a 8922 15701 1 +a 8923 15700 1 +a 8924 15699 1 +a 8925 15698 1 +a 8926 15697 1 +a 8927 15696 1 +a 8928 15695 1 +a 8929 15694 1 +a 8930 15693 1 +a 8931 15692 1 +a 8932 15691 1 +a 8933 15690 1 +a 8934 15689 1 +a 8935 15688 1 +a 8936 15687 1 +a 8937 15686 1 +a 8938 15685 1 +a 8939 15684 1 +a 8940 15683 1 +a 8941 15682 1 +a 8942 15681 1 +a 8943 15680 1 +a 8944 15679 1 +a 8945 15678 1 +a 8946 15677 1 +a 8947 15676 1 +a 8948 15675 1 +a 8949 15674 1 +a 8950 15673 1 +a 8951 15672 1 +a 8952 15671 1 +a 8953 15670 1 +a 8954 15669 1 +a 8955 15668 1 +a 8956 15667 1 +a 8957 15666 1 +a 8958 15665 1 +a 8959 15664 1 +a 8960 15663 1 +a 8961 15662 1 +a 8962 15661 1 +a 8963 15660 1 +a 8964 15659 1 +a 8965 15658 1 +a 8966 15657 1 +a 8967 15656 1 +a 8968 15655 1 +a 8969 15654 1 +a 8970 15653 1 +a 8971 15652 1 +a 8972 15651 1 +a 8973 15650 1 +a 8974 15649 1 +a 8975 15648 1 +a 8976 15647 1 +a 8977 15646 1 +a 8978 15645 1 +a 8979 15644 1 +a 8980 15643 1 +a 8981 15642 1 +a 8982 15641 1 +a 8983 15640 1 +a 8984 15639 1 +a 8985 15638 1 +a 8986 15637 1 +a 8987 15636 1 +a 8988 15635 1 +a 8989 15634 1 +a 8990 15633 1 +a 8991 15632 1 +a 8992 15631 1 +a 8993 15630 1 +a 8994 15629 1 +a 8995 15628 1 +a 8996 15627 1 +a 8997 15626 1 +a 8998 15625 1 +a 8999 15624 1 +a 9000 15623 1 +a 9001 15622 1 +a 9002 15621 1 +a 9003 15620 1 +a 9004 15619 1 +a 9005 15618 1 +a 9006 15617 1 +a 9007 15616 1 +a 9008 15615 1 +a 9009 15614 1 +a 9010 15613 1 +a 9011 15612 1 +a 9012 15611 1 +a 9013 15610 1 +a 9014 15609 1 +a 9015 15608 1 +a 9016 15607 1 +a 9017 15606 1 +a 9018 15605 1 +a 9019 15604 1 +a 9020 15603 1 +a 9021 15602 1 +a 9022 15601 1 +a 9023 15600 1 +a 9024 15599 1 +a 9025 15598 1 +a 9026 15597 1 +a 9027 15596 1 +a 9028 15595 1 +a 9029 15594 1 +a 9030 15593 1 +a 9031 15592 1 +a 9032 15591 1 +a 9033 15590 1 +a 9034 15589 1 +a 9035 15588 1 +a 9036 15587 1 +a 9037 15586 1 +a 9038 15585 1 +a 9039 15584 1 +a 9040 15583 1 +a 9041 15582 1 +a 9042 15581 1 +a 9043 15580 1 +a 9044 15579 1 +a 9045 15578 1 +a 9046 15577 1 +a 9047 15576 1 +a 9048 15575 1 +a 9049 15574 1 +a 9050 15573 1 +a 9051 15572 1 +a 9052 15571 1 +a 9053 15570 1 +a 9054 15569 1 +a 9055 15568 1 +a 9056 15567 1 +a 9057 15566 1 +a 9058 15565 1 +a 9059 15564 1 +a 9060 15563 1 +a 9061 15562 1 +a 9062 15561 1 +a 9063 15560 1 +a 9064 15559 1 +a 9065 15558 1 +a 9066 15557 1 +a 9067 15556 1 +a 9068 15555 1 +a 9069 15554 1 +a 9070 15553 1 +a 9071 15552 1 +a 9072 15551 1 +a 9073 15550 1 +a 9074 15549 1 +a 9075 15548 1 +a 9076 15547 1 +a 9077 15546 1 +a 9078 15545 1 +a 9079 15544 1 +a 9080 15543 1 +a 9081 15542 1 +a 9082 15541 1 +a 9083 15540 1 +a 9084 15539 1 +a 9085 15538 1 +a 9086 15537 1 +a 9087 15536 1 +a 9088 15535 1 +a 9089 15534 1 +a 9090 15533 1 +a 9091 15532 1 +a 9092 15531 1 +a 9093 15530 1 +a 9094 15529 1 +a 9095 15528 1 +a 9096 15527 1 +a 9097 15526 1 +a 9098 15525 1 +a 9099 15524 1 +a 9100 15523 1 +a 9101 15522 1 +a 9102 15521 1 +a 9103 15520 1 +a 9104 15519 1 +a 9105 15518 1 +a 9106 15517 1 +a 9107 15516 1 +a 9108 15515 1 +a 9109 15514 1 +a 9110 15513 1 +a 9111 15512 1 +a 9112 15511 1 +a 9113 15510 1 +a 9114 15509 1 +a 9115 15508 1 +a 9116 15507 1 +a 9117 15506 1 +a 9118 15505 1 +a 9119 15504 1 +a 9120 15503 1 +a 9121 15502 1 +a 9122 15501 1 +a 9123 15500 1 +a 9124 15499 1 +a 9125 15498 1 +a 9126 15497 1 +a 9127 15496 1 +a 9128 15495 1 +a 9129 15494 1 +a 9130 15493 1 +a 9131 15492 1 +a 9132 15491 1 +a 9133 15490 1 +a 9134 15489 1 +a 9135 15488 1 +a 9136 15487 1 +a 9137 15486 1 +a 9138 15485 1 +a 9139 15484 1 +a 9140 15483 1 +a 9141 15482 1 +a 9142 15481 1 +a 9143 15480 1 +a 9144 15479 1 +a 9145 15478 1 +a 9146 15477 1 +a 9147 15476 1 +a 9148 15475 1 +a 9149 15474 1 +a 9150 15473 1 +a 9151 15472 1 +a 9152 15471 1 +a 9153 15470 1 +a 9154 15469 1 +a 9155 15468 1 +a 9156 15467 1 +a 9157 15466 1 +a 9158 15465 1 +a 9159 15464 1 +a 9160 15463 1 +a 9161 15462 1 +a 9162 15461 1 +a 9163 15460 1 +a 9164 15459 1 +a 9165 15458 1 +a 9166 15457 1 +a 9167 15456 1 +a 9168 15455 1 +a 9169 15454 1 +a 9170 15453 1 +a 9171 15452 1 +a 9172 15451 1 +a 9173 15450 1 +a 9174 15449 1 +a 9175 15448 1 +a 9176 15447 1 +a 9177 15446 1 +a 9178 15445 1 +a 9179 15444 1 +a 9180 15443 1 +a 9181 15442 1 +a 9182 15441 1 +a 9183 15440 1 +a 9184 15439 1 +a 9185 15438 1 +a 9186 15437 1 +a 9187 15436 1 +a 9188 15435 1 +a 9189 15434 1 +a 9190 15433 1 +a 9191 15432 1 +a 9192 15431 1 +a 9193 15430 1 +a 9194 15429 1 +a 9195 15428 1 +a 9196 15427 1 +a 9197 15426 1 +a 9198 15425 1 +a 9199 15424 1 +a 9200 15423 1 +a 9201 15422 1 +a 9202 15421 1 +a 9203 15420 1 +a 9204 15419 1 +a 9205 15418 1 +a 9206 15417 1 +a 9207 15416 1 +a 9208 15415 1 +a 9209 15414 1 +a 9210 15413 1 +a 9211 15412 1 +a 9212 15411 1 +a 9213 15410 1 +a 9214 15409 1 +a 9215 15408 1 +a 9216 15407 1 +a 9217 15406 1 +a 9218 15405 1 +a 9219 15404 1 +a 9220 15403 1 +a 9221 15402 1 +a 9222 15401 1 +a 9223 15400 1 +a 9224 15399 1 +a 9225 15398 1 +a 9226 15397 1 +a 9227 15396 1 +a 9228 15395 1 +a 9229 15394 1 +a 9230 15393 1 +a 9231 15392 1 +a 9232 15391 1 +a 9233 15390 1 +a 9234 15389 1 +a 9235 15388 1 +a 9236 15387 1 +a 9237 15386 1 +a 9238 15385 1 +a 9239 15384 1 +a 9240 15383 1 +a 9241 15382 1 +a 9242 15381 1 +a 9243 15380 1 +a 9244 15379 1 +a 9245 15378 1 +a 9246 15377 1 +a 9247 15376 1 +a 9248 15375 1 +a 9249 15374 1 +a 9250 15373 1 +a 9251 15372 1 +a 9252 15371 1 +a 9253 15370 1 +a 9254 15369 1 +a 9255 15368 1 +a 9256 15367 1 +a 9257 15366 1 +a 9258 15365 1 +a 9259 15364 1 +a 9260 15363 1 +a 9261 15362 1 +a 9262 15361 1 +a 9263 15360 1 +a 9264 15359 1 +a 9265 15358 1 +a 9266 15357 1 +a 9267 15356 1 +a 9268 15355 1 +a 9269 15354 1 +a 9270 15353 1 +a 9271 15352 1 +a 9272 15351 1 +a 9273 15350 1 +a 9274 15349 1 +a 9275 15348 1 +a 9276 15347 1 +a 9277 15346 1 +a 9278 15345 1 +a 9279 15344 1 +a 9280 15343 1 +a 9281 15342 1 +a 9282 15341 1 +a 9283 15340 1 +a 9284 15339 1 +a 9285 15338 1 +a 9286 15337 1 +a 9287 15336 1 +a 9288 15335 1 +a 9289 15334 1 +a 9290 15333 1 +a 9291 15332 1 +a 9292 15331 1 +a 9293 15330 1 +a 9294 15329 1 +a 9295 15328 1 +a 9296 15327 1 +a 9297 15326 1 +a 9298 15325 1 +a 9299 15324 1 +a 9300 15323 1 +a 9301 15322 1 +a 9302 15321 1 +a 9303 15320 1 +a 9304 15319 1 +a 9305 15318 1 +a 9306 15317 1 +a 9307 15316 1 +a 9308 15315 1 +a 9309 15314 1 +a 9310 15313 1 +a 9311 15312 1 +a 9312 15311 1 +a 9313 15310 1 +a 9314 15309 1 +a 9315 15308 1 +a 9316 15307 1 +a 9317 15306 1 +a 9318 15305 1 +a 9319 15304 1 +a 9320 15303 1 +a 9321 15302 1 +a 9322 15301 1 +a 9323 15300 1 +a 9324 15299 1 +a 9325 15298 1 +a 9326 15297 1 +a 9327 15296 1 +a 9328 15295 1 +a 9329 15294 1 +a 9330 15293 1 +a 9331 15292 1 +a 9332 15291 1 +a 9333 15290 1 +a 9334 15289 1 +a 9335 15288 1 +a 9336 15287 1 +a 9337 15286 1 +a 9338 15285 1 +a 9339 15284 1 +a 9340 15283 1 +a 9341 15282 1 +a 9342 15281 1 +a 9343 15280 1 +a 9344 15279 1 +a 9345 15278 1 +a 9346 15277 1 +a 9347 15276 1 +a 9348 15275 1 +a 9349 15274 1 +a 9350 15273 1 +a 9351 15272 1 +a 9352 15271 1 +a 9353 15270 1 +a 9354 15269 1 +a 9355 15268 1 +a 9356 15267 1 +a 9357 15266 1 +a 9358 15265 1 +a 9359 15264 1 +a 9360 15263 1 +a 9361 15262 1 +a 9362 15261 1 +a 9363 15260 1 +a 9364 15259 1 +a 9365 15258 1 +a 9366 15257 1 +a 9367 15256 1 +a 9368 15255 1 +a 9369 15254 1 +a 9370 15253 1 +a 9371 15252 1 +a 9372 15251 1 +a 9373 15250 1 +a 9374 15249 1 +a 9375 15248 1 +a 9376 15247 1 +a 9377 15246 1 +a 9378 15245 1 +a 9379 15244 1 +a 9380 15243 1 +a 9381 15242 1 +a 9382 15241 1 +a 9383 15240 1 +a 9384 15239 1 +a 9385 15238 1 +a 9386 15237 1 +a 9387 15236 1 +a 9388 15235 1 +a 9389 15234 1 +a 9390 15233 1 +a 9391 15232 1 +a 9392 15231 1 +a 9393 15230 1 +a 9394 15229 1 +a 9395 15228 1 +a 9396 15227 1 +a 9397 15226 1 +a 9398 15225 1 +a 9399 15224 1 +a 9400 15223 1 +a 9401 15222 1 +a 9402 15221 1 +a 9403 15220 1 +a 9404 15219 1 +a 9405 15218 1 +a 9406 15217 1 +a 9407 15216 1 +a 9408 15215 1 +a 9409 15214 1 +a 9410 15213 1 +a 9411 15212 1 +a 9412 15211 1 +a 9413 15210 1 +a 9414 15209 1 +a 9415 15208 1 +a 9416 15207 1 +a 9417 15206 1 +a 9418 15205 1 +a 9419 15204 1 +a 9420 15203 1 +a 9421 15202 1 +a 9422 15201 1 +a 9423 15200 1 +a 9424 15199 1 +a 9425 15198 1 +a 9426 15197 1 +a 9427 15196 1 +a 9428 15195 1 +a 9429 15194 1 +a 9430 15193 1 +a 9431 15192 1 +a 9432 15191 1 +a 9433 15190 1 +a 9434 15189 1 +a 9435 15188 1 +a 9436 15187 1 +a 9437 15186 1 +a 9438 15185 1 +a 9439 15184 1 +a 9440 15183 1 +a 9441 15182 1 +a 9442 15181 1 +a 9443 15180 1 +a 9444 15179 1 +a 9445 15178 1 +a 9446 15177 1 +a 9447 15176 1 +a 9448 15175 1 +a 9449 15174 1 +a 9450 15173 1 +a 9451 15172 1 +a 9452 15171 1 +a 9453 15170 1 +a 9454 15169 1 +a 9455 15168 1 +a 9456 15167 1 +a 9457 15166 1 +a 9458 15165 1 +a 9459 15164 1 +a 9460 15163 1 +a 9461 15162 1 +a 9462 15161 1 +a 9463 15160 1 +a 9464 15159 1 +a 9465 15158 1 +a 9466 15157 1 +a 9467 15156 1 +a 9468 15155 1 +a 9469 15154 1 +a 9470 15153 1 +a 9471 15152 1 +a 9472 15151 1 +a 9473 15150 1 +a 9474 15149 1 +a 9475 15148 1 +a 9476 15147 1 +a 9477 15146 1 +a 9478 15145 1 +a 9479 15144 1 +a 9480 15143 1 +a 9481 15142 1 +a 9482 15141 1 +a 9483 15140 1 +a 9484 15139 1 +a 9485 15138 1 +a 9486 15137 1 +a 9487 15136 1 +a 9488 15135 1 +a 9489 15134 1 +a 9490 15133 1 +a 9491 15132 1 +a 9492 15131 1 +a 9493 15130 1 +a 9494 15129 1 +a 9495 15128 1 +a 9496 15127 1 +a 9497 15126 1 +a 9498 15125 1 +a 9499 15124 1 +a 9500 15123 1 +a 9501 15122 1 +a 9502 15121 1 +a 9503 15120 1 +a 9504 15119 1 +a 9505 15118 1 +a 9506 15117 1 +a 9507 15116 1 +a 9508 15115 1 +a 9509 15114 1 +a 9510 15113 1 +a 9511 15112 1 +a 9512 15111 1 +a 9513 15110 1 +a 9514 15109 1 +a 9515 15108 1 +a 9516 15107 1 +a 9517 15106 1 +a 9518 15105 1 +a 9519 15104 1 +a 9520 15103 1 +a 9521 15102 1 +a 9522 15101 1 +a 9523 15100 1 +a 9524 15099 1 +a 9525 15098 1 +a 9526 15097 1 +a 9527 15096 1 +a 9528 15095 1 +a 9529 15094 1 +a 9530 15093 1 +a 9531 15092 1 +a 9532 15091 1 +a 9533 15090 1 +a 9534 15089 1 +a 9535 15088 1 +a 9536 15087 1 +a 9537 15086 1 +a 9538 15085 1 +a 9539 15084 1 +a 9540 15083 1 +a 9541 15082 1 +a 9542 15081 1 +a 9543 15080 1 +a 9544 15079 1 +a 9545 15078 1 +a 9546 15077 1 +a 9547 15076 1 +a 9548 15075 1 +a 9549 15074 1 +a 9550 15073 1 +a 9551 15072 1 +a 9552 15071 1 +a 9553 15070 1 +a 9554 15069 1 +a 9555 15068 1 +a 9556 15067 1 +a 9557 15066 1 +a 9558 15065 1 +a 9559 15064 1 +a 9560 15063 1 +a 9561 15062 1 +a 9562 15061 1 +a 9563 15060 1 +a 9564 15059 1 +a 9565 15058 1 +a 9566 15057 1 +a 9567 15056 1 +a 9568 15055 1 +a 9569 15054 1 +a 9570 15053 1 +a 9571 15052 1 +a 9572 15051 1 +a 9573 15050 1 +a 9574 15049 1 +a 9575 15048 1 +a 9576 15047 1 +a 9577 15046 1 +a 9578 15045 1 +a 9579 15044 1 +a 9580 15043 1 +a 9581 15042 1 +a 9582 15041 1 +a 9583 15040 1 +a 9584 15039 1 +a 9585 15038 1 +a 9586 15037 1 +a 9587 15036 1 +a 9588 15035 1 +a 9589 15034 1 +a 9590 15033 1 +a 9591 15032 1 +a 9592 15031 1 +a 9593 15030 1 +a 9594 15029 1 +a 9595 15028 1 +a 9596 15027 1 +a 9597 15026 1 +a 9598 15025 1 +a 9599 15024 1 +a 9600 15023 1 +a 9601 15022 1 +a 9602 15021 1 +a 9603 15020 1 +a 9604 15019 1 +a 9605 15018 1 +a 9606 15017 1 +a 9607 15016 1 +a 9608 15015 1 +a 9609 15014 1 +a 9610 15013 1 +a 9611 15012 1 +a 9612 15011 1 +a 9613 15010 1 +a 9614 15009 1 +a 9615 15008 1 +a 9616 15007 1 +a 9617 15006 1 +a 9618 15005 1 +a 9619 15004 1 +a 9620 15003 1 +a 9621 15002 1 +a 9622 15001 1 +a 9623 15000 1 +a 9624 14999 1 +a 9625 14998 1 +a 9626 14997 1 +a 9627 14996 1 +a 9628 14995 1 +a 9629 14994 1 +a 9630 14993 1 +a 9631 14992 1 +a 9632 14991 1 +a 9633 14990 1 +a 9634 14989 1 +a 9635 14988 1 +a 9636 14987 1 +a 9637 14986 1 +a 9638 14985 1 +a 9639 14984 1 +a 9640 14983 1 +a 9641 14982 1 +a 9642 14981 1 +a 9643 14980 1 +a 9644 14979 1 +a 9645 14978 1 +a 9646 14977 1 +a 9647 14976 1 +a 9648 14975 1 +a 9649 14974 1 +a 9650 14973 1 +a 9651 14972 1 +a 9652 14971 1 +a 9653 14970 1 +a 9654 14969 1 +a 9655 14968 1 +a 9656 14967 1 +a 9657 14966 1 +a 9658 14965 1 +a 9659 14964 1 +a 9660 14963 1 +a 9661 14962 1 +a 9662 14961 1 +a 9663 14960 1 +a 9664 14959 1 +a 9665 14958 1 +a 9666 14957 1 +a 9667 14956 1 +a 9668 14955 1 +a 9669 14954 1 +a 9670 14953 1 +a 9671 14952 1 +a 9672 14951 1 +a 9673 14950 1 +a 9674 14949 1 +a 9675 14948 1 +a 9676 14947 1 +a 9677 14946 1 +a 9678 14945 1 +a 9679 14944 1 +a 9680 14943 1 +a 9681 14942 1 +a 9682 14941 1 +a 9683 14940 1 +a 9684 14939 1 +a 9685 14938 1 +a 9686 14937 1 +a 9687 14936 1 +a 9688 14935 1 +a 9689 14934 1 +a 9690 14933 1 +a 9691 14932 1 +a 9692 14931 1 +a 9693 14930 1 +a 9694 14929 1 +a 9695 14928 1 +a 9696 14927 1 +a 9697 14926 1 +a 9698 14925 1 +a 9699 14924 1 +a 9700 14923 1 +a 9701 14922 1 +a 9702 14921 1 +a 9703 14920 1 +a 9704 14919 1 +a 9705 14918 1 +a 9706 14917 1 +a 9707 14916 1 +a 9708 14915 1 +a 9709 14914 1 +a 9710 14913 1 +a 9711 14912 1 +a 9712 14911 1 +a 9713 14910 1 +a 9714 14909 1 +a 9715 14908 1 +a 9716 14907 1 +a 9717 14906 1 +a 9718 14905 1 +a 9719 14904 1 +a 9720 14903 1 +a 9721 14902 1 +a 9722 14901 1 +a 9723 14900 1 +a 9724 14899 1 +a 9725 14898 1 +a 9726 14897 1 +a 9727 14896 1 +a 9728 14895 1 +a 9729 14894 1 +a 9730 14893 1 +a 9731 14892 1 +a 9732 14891 1 +a 9733 14890 1 +a 9734 14889 1 +a 9735 14888 1 +a 9736 14887 1 +a 9737 14886 1 +a 9738 14885 1 +a 9739 14884 1 +a 9740 14883 1 +a 9741 14882 1 +a 9742 14881 1 +a 9743 14880 1 +a 9744 14879 1 +a 9745 14878 1 +a 9746 14877 1 +a 9747 14876 1 +a 9748 14875 1 +a 9749 14874 1 +a 9750 14873 1 +a 9751 14872 1 +a 9752 14871 1 +a 9753 14870 1 +a 9754 14869 1 +a 9755 14868 1 +a 9756 14867 1 +a 9757 14866 1 +a 9758 14865 1 +a 9759 14864 1 +a 9760 14863 1 +a 9761 14862 1 +a 9762 14861 1 +a 9763 14860 1 +a 9764 14859 1 +a 9765 14858 1 +a 9766 14857 1 +a 9767 14856 1 +a 9768 14855 1 +a 9769 14854 1 +a 9770 14853 1 +a 9771 14852 1 +a 9772 14851 1 +a 9773 14850 1 +a 9774 14849 1 +a 9775 14848 1 +a 9776 14847 1 +a 9777 14846 1 +a 9778 14845 1 +a 9779 14844 1 +a 9780 14843 1 +a 9781 14842 1 +a 9782 14841 1 +a 9783 14840 1 +a 9784 14839 1 +a 9785 14838 1 +a 9786 14837 1 +a 9787 14836 1 +a 9788 14835 1 +a 9789 14834 1 +a 9790 14833 1 +a 9791 14832 1 +a 9792 14831 1 +a 9793 14830 1 +a 9794 14829 1 +a 9795 14828 1 +a 9796 14827 1 +a 9797 14826 1 +a 9798 14825 1 +a 9799 14824 1 +a 9800 14823 1 +a 9801 14822 1 +a 9802 14821 1 +a 9803 14820 1 +a 9804 14819 1 +a 9805 14818 1 +a 9806 14817 1 +a 9807 14816 1 +a 9808 14815 1 +a 9809 14814 1 +a 9810 14813 1 +a 9811 14812 1 +a 9812 14811 1 +a 9813 14810 1 +a 9814 14809 1 +a 9815 14808 1 +a 9816 14807 1 +a 9817 14806 1 +a 9818 14805 1 +a 9819 14804 1 +a 9820 14803 1 +a 9821 14802 1 +a 9822 14801 1 +a 9823 14800 1 +a 9824 14799 1 +a 9825 14798 1 +a 9826 14797 1 +a 9827 14796 1 +a 9828 14795 1 +a 9829 14794 1 +a 9830 14793 1 +a 9831 14792 1 +a 9832 14791 1 +a 9833 14790 1 +a 9834 14789 1 +a 9835 14788 1 +a 9836 14787 1 +a 9837 14786 1 +a 9838 14785 1 +a 9839 14784 1 +a 9840 14783 1 +a 9841 14782 1 +a 9842 14781 1 +a 9843 14780 1 +a 9844 14779 1 +a 9845 14778 1 +a 9846 14777 1 +a 9847 14776 1 +a 9848 14775 1 +a 9849 14774 1 +a 9850 14773 1 +a 9851 14772 1 +a 9852 14771 1 +a 9853 14770 1 +a 9854 14769 1 +a 9855 14768 1 +a 9856 14767 1 +a 9857 14766 1 +a 9858 14765 1 +a 9859 14764 1 +a 9860 14763 1 +a 9861 14762 1 +a 9862 14761 1 +a 9863 14760 1 +a 9864 14759 1 +a 9865 14758 1 +a 9866 14757 1 +a 9867 14756 1 +a 9868 14755 1 +a 9869 14754 1 +a 9870 14753 1 +a 9871 14752 1 +a 9872 14751 1 +a 9873 14750 1 +a 9874 14749 1 +a 9875 14748 1 +a 9876 14747 1 +a 9877 14746 1 +a 9878 14745 1 +a 9879 14744 1 +a 9880 14743 1 +a 9881 14742 1 +a 9882 14741 1 +a 9883 14740 1 +a 9884 14739 1 +a 9885 14738 1 +a 9886 14737 1 +a 9887 14736 1 +a 9888 14735 1 +a 9889 14734 1 +a 9890 14733 1 +a 9891 14732 1 +a 9892 14731 1 +a 9893 14730 1 +a 9894 14729 1 +a 9895 14728 1 +a 9896 14727 1 +a 9897 14726 1 +a 9898 14725 1 +a 9899 14724 1 +a 9900 14723 1 +a 9901 14722 1 +a 9902 14721 1 +a 9903 14720 1 +a 9904 14719 1 +a 9905 14718 1 +a 9906 14717 1 +a 9907 14716 1 +a 9908 14715 1 +a 9909 14714 1 +a 9910 14713 1 +a 9911 14712 1 +a 9912 14711 1 +a 9913 14710 1 +a 9914 14709 1 +a 9915 14708 1 +a 9916 14707 1 +a 9917 14706 1 +a 9918 14705 1 +a 9919 14704 1 +a 9920 14703 1 +a 9921 14702 1 +a 9922 14701 1 +a 9923 14700 1 +a 9924 14699 1 +a 9925 14698 1 +a 9926 14697 1 +a 9927 14696 1 +a 9928 14695 1 +a 9929 14694 1 +a 9930 14693 1 +a 9931 14692 1 +a 9932 14691 1 +a 9933 14690 1 +a 9934 14689 1 +a 9935 14688 1 +a 9936 14687 1 +a 9937 14686 1 +a 9938 14685 1 +a 9939 14684 1 +a 9940 14683 1 +a 9941 14682 1 +a 9942 14681 1 +a 9943 14680 1 +a 9944 14679 1 +a 9945 14678 1 +a 9946 14677 1 +a 9947 14676 1 +a 9948 14675 1 +a 9949 14674 1 +a 9950 14673 1 +a 9951 14672 1 +a 9952 14671 1 +a 9953 14670 1 +a 9954 14669 1 +a 9955 14668 1 +a 9956 14667 1 +a 9957 14666 1 +a 9958 14665 1 +a 9959 14664 1 +a 9960 14663 1 +a 9961 14662 1 +a 9962 14661 1 +a 9963 14660 1 +a 9964 14659 1 +a 9965 14658 1 +a 9966 14657 1 +a 9967 14656 1 +a 9968 14655 1 +a 9969 14654 1 +a 9970 14653 1 +a 9971 14652 1 +a 9972 14651 1 +a 9973 14650 1 +a 9974 14649 1 +a 9975 14648 1 +a 9976 14647 1 +a 9977 14646 1 +a 9978 14645 1 +a 9979 14644 1 +a 9980 14643 1 +a 9981 14642 1 +a 9982 14641 1 +a 9983 14640 1 +a 9984 14639 1 +a 9985 14638 1 +a 9986 14637 1 +a 9987 14636 1 +a 9988 14635 1 +a 9989 14634 1 +a 9990 14633 1 +a 9991 14632 1 +a 9992 14631 1 +a 9993 14630 1 +a 9994 14629 1 +a 9995 14628 1 +a 9996 14627 1 +a 9997 14626 1 +a 9998 14625 1 +a 9999 14624 1 +a 10000 14623 1 +a 10001 14622 1 +a 10002 14621 1 +a 10003 14620 1 +a 10004 14619 1 +a 10005 14618 1 +a 10006 14617 1 +a 10007 14616 1 +a 10008 14615 1 +a 10009 14614 1 +a 10010 14613 1 +a 10011 14612 1 +a 10012 14611 1 +a 10013 14610 1 +a 10014 14609 1 +a 10015 14608 1 +a 10016 14607 1 +a 10017 14606 1 +a 10018 14605 1 +a 10019 14604 1 +a 10020 14603 1 +a 10021 14602 1 +a 10022 14601 1 +a 10023 14600 1 +a 10024 14599 1 +a 10025 14598 1 +a 10026 14597 1 +a 10027 14596 1 +a 10028 14595 1 +a 10029 14594 1 +a 10030 14593 1 +a 10031 14592 1 +a 10032 14591 1 +a 10033 14590 1 +a 10034 14589 1 +a 10035 14588 1 +a 10036 14587 1 +a 10037 14586 1 +a 10038 14585 1 +a 10039 14584 1 +a 10040 14583 1 +a 10041 14582 1 +a 10042 14581 1 +a 10043 14580 1 +a 10044 14579 1 +a 10045 14578 1 +a 10046 14577 1 +a 10047 14576 1 +a 10048 14575 1 +a 10049 14574 1 +a 10050 14573 1 +a 10051 14572 1 +a 10052 14571 1 +a 10053 14570 1 +a 10054 14569 1 +a 10055 14568 1 +a 10056 14567 1 +a 10057 14566 1 +a 10058 14565 1 +a 10059 14564 1 +a 10060 14563 1 +a 10061 14562 1 +a 10062 14561 1 +a 10063 14560 1 +a 10064 14559 1 +a 10065 14558 1 +a 10066 14557 1 +a 10067 14556 1 +a 10068 14555 1 +a 10069 14554 1 +a 10070 14553 1 +a 10071 14552 1 +a 10072 14551 1 +a 10073 14550 1 +a 10074 14549 1 +a 10075 14548 1 +a 10076 14547 1 +a 10077 14546 1 +a 10078 14545 1 +a 10079 14544 1 +a 10080 14543 1 +a 10081 14542 1 +a 10082 14541 1 +a 10083 14540 1 +a 10084 14539 1 +a 10085 14538 1 +a 10086 14537 1 +a 10087 14536 1 +a 10088 14535 1 +a 10089 14534 1 +a 10090 14533 1 +a 10091 14532 1 +a 10092 14531 1 +a 10093 14530 1 +a 10094 14529 1 +a 10095 14528 1 +a 10096 14527 1 +a 10097 14526 1 +a 10098 14525 1 +a 10099 14524 1 +a 10100 14523 1 +a 10101 14522 1 +a 10102 14521 1 +a 10103 14520 1 +a 10104 14519 1 +a 10105 14518 1 +a 10106 14517 1 +a 10107 14516 1 +a 10108 14515 1 +a 10109 14514 1 +a 10110 14513 1 +a 10111 14512 1 +a 10112 14511 1 +a 10113 14510 1 +a 10114 14509 1 +a 10115 14508 1 +a 10116 14507 1 +a 10117 14506 1 +a 10118 14505 1 +a 10119 14504 1 +a 10120 14503 1 +a 10121 14502 1 +a 10122 14501 1 +a 10123 14500 1 +a 10124 14499 1 +a 10125 14498 1 +a 10126 14497 1 +a 10127 14496 1 +a 10128 14495 1 +a 10129 14494 1 +a 10130 14493 1 +a 10131 14492 1 +a 10132 14491 1 +a 10133 14490 1 +a 10134 14489 1 +a 10135 14488 1 +a 10136 14487 1 +a 10137 14486 1 +a 10138 14485 1 +a 10139 14484 1 +a 10140 14483 1 +a 10141 14482 1 +a 10142 14481 1 +a 10143 14480 1 +a 10144 14479 1 +a 10145 14478 1 +a 10146 14477 1 +a 10147 14476 1 +a 10148 14475 1 +a 10149 14474 1 +a 10150 14473 1 +a 10151 14472 1 +a 10152 14471 1 +a 10153 14470 1 +a 10154 14469 1 +a 10155 14468 1 +a 10156 14467 1 +a 10157 14466 1 +a 10158 14465 1 +a 10159 14464 1 +a 10160 14463 1 +a 10161 14462 1 +a 10162 14461 1 +a 10163 14460 1 +a 10164 14459 1 +a 10165 14458 1 +a 10166 14457 1 +a 10167 14456 1 +a 10168 14455 1 +a 10169 14454 1 +a 10170 14453 1 +a 10171 14452 1 +a 10172 14451 1 +a 10173 14450 1 +a 10174 14449 1 +a 10175 14448 1 +a 10176 14447 1 +a 10177 14446 1 +a 10178 14445 1 +a 10179 14444 1 +a 10180 14443 1 +a 10181 14442 1 +a 10182 14441 1 +a 10183 14440 1 +a 10184 14439 1 +a 10185 14438 1 +a 10186 14437 1 +a 10187 14436 1 +a 10188 14435 1 +a 10189 14434 1 +a 10190 14433 1 +a 10191 14432 1 +a 10192 14431 1 +a 10193 14430 1 +a 10194 14429 1 +a 10195 14428 1 +a 10196 14427 1 +a 10197 14426 1 +a 10198 14425 1 +a 10199 14424 1 +a 10200 14423 1 +a 10201 14422 1 +a 10202 14421 1 +a 10203 14420 1 +a 10204 14419 1 +a 10205 14418 1 +a 10206 14417 1 +a 10207 14416 1 +a 10208 14415 1 +a 10209 14414 1 +a 10210 14413 1 +a 10211 14412 1 +a 10212 14411 1 +a 10213 14410 1 +a 10214 14409 1 +a 10215 14408 1 +a 10216 14407 1 +a 10217 14406 1 +a 10218 14405 1 +a 10219 14404 1 +a 10220 14403 1 +a 10221 14402 1 +a 10222 14401 1 +a 10223 14400 1 +a 10224 14399 1 +a 10225 14398 1 +a 10226 14397 1 +a 10227 14396 1 +a 10228 14395 1 +a 10229 14394 1 +a 10230 14393 1 +a 10231 14392 1 +a 10232 14391 1 +a 10233 14390 1 +a 10234 14389 1 +a 10235 14388 1 +a 10236 14387 1 +a 10237 14386 1 +a 10238 14385 1 +a 10239 14384 1 +a 10240 14383 1 +a 10241 14382 1 +a 10242 14381 1 +a 10243 14380 1 +a 10244 14379 1 +a 10245 14378 1 +a 10246 14377 1 +a 10247 14376 1 +a 10248 14375 1 +a 10249 14374 1 +a 10250 14373 1 +a 10251 14372 1 +a 10252 14371 1 +a 10253 14370 1 +a 10254 14369 1 +a 10255 14368 1 +a 10256 14367 1 +a 10257 14366 1 +a 10258 14365 1 +a 10259 14364 1 +a 10260 14363 1 +a 10261 14362 1 +a 10262 14361 1 +a 10263 14360 1 +a 10264 14359 1 +a 10265 14358 1 +a 10266 14357 1 +a 10267 14356 1 +a 10268 14355 1 +a 10269 14354 1 +a 10270 14353 1 +a 10271 14352 1 +a 10272 14351 1 +a 10273 14350 1 +a 10274 14349 1 +a 10275 14348 1 +a 10276 14347 1 +a 10277 14346 1 +a 10278 14345 1 +a 10279 14344 1 +a 10280 14343 1 +a 10281 14342 1 +a 10282 14341 1 +a 10283 14340 1 +a 10284 14339 1 +a 10285 14338 1 +a 10286 14337 1 +a 10287 14336 1 +a 10288 14335 1 +a 10289 14334 1 +a 10290 14333 1 +a 10291 14332 1 +a 10292 14331 1 +a 10293 14330 1 +a 10294 14329 1 +a 10295 14328 1 +a 10296 14327 1 +a 10297 14326 1 +a 10298 14325 1 +a 10299 14324 1 +a 10300 14323 1 +a 10301 14322 1 +a 10302 14321 1 +a 10303 14320 1 +a 10304 14319 1 +a 10305 14318 1 +a 10306 14317 1 +a 10307 14316 1 +a 10308 14315 1 +a 10309 14314 1 +a 10310 14313 1 +a 10311 14312 1 +a 10312 14311 1 +a 10313 14310 1 +a 10314 14309 1 +a 10315 14308 1 +a 10316 14307 1 +a 10317 14306 1 +a 10318 14305 1 +a 10319 14304 1 +a 10320 14303 1 +a 10321 14302 1 +a 10322 14301 1 +a 10323 14300 1 +a 10324 14299 1 +a 10325 14298 1 +a 10326 14297 1 +a 10327 14296 1 +a 10328 14295 1 +a 10329 14294 1 +a 10330 14293 1 +a 10331 14292 1 +a 10332 14291 1 +a 10333 14290 1 +a 10334 14289 1 +a 10335 14288 1 +a 10336 14287 1 +a 10337 14286 1 +a 10338 14285 1 +a 10339 14284 1 +a 10340 14283 1 +a 10341 14282 1 +a 10342 14281 1 +a 10343 14280 1 +a 10344 14279 1 +a 10345 14278 1 +a 10346 14277 1 +a 10347 14276 1 +a 10348 14275 1 +a 10349 14274 1 +a 10350 14273 1 +a 10351 14272 1 +a 10352 14271 1 +a 10353 14270 1 +a 10354 14269 1 +a 10355 14268 1 +a 10356 14267 1 +a 10357 14266 1 +a 10358 14265 1 +a 10359 14264 1 +a 10360 14263 1 +a 10361 14262 1 +a 10362 14261 1 +a 10363 14260 1 +a 10364 14259 1 +a 10365 14258 1 +a 10366 14257 1 +a 10367 14256 1 +a 10368 14255 1 +a 10369 14254 1 +a 10370 14253 1 +a 10371 14252 1 +a 10372 14251 1 +a 10373 14250 1 +a 10374 14249 1 +a 10375 14248 1 +a 10376 14247 1 +a 10377 14246 1 +a 10378 14245 1 +a 10379 14244 1 +a 10380 14243 1 +a 10381 14242 1 +a 10382 14241 1 +a 10383 14240 1 +a 10384 14239 1 +a 10385 14238 1 +a 10386 14237 1 +a 10387 14236 1 +a 10388 14235 1 +a 10389 14234 1 +a 10390 14233 1 +a 10391 14232 1 +a 10392 14231 1 +a 10393 14230 1 +a 10394 14229 1 +a 10395 14228 1 +a 10396 14227 1 +a 10397 14226 1 +a 10398 14225 1 +a 10399 14224 1 +a 10400 14223 1 +a 10401 14222 1 +a 10402 14221 1 +a 10403 14220 1 +a 10404 14219 1 +a 10405 14218 1 +a 10406 14217 1 +a 10407 14216 1 +a 10408 14215 1 +a 10409 14214 1 +a 10410 14213 1 +a 10411 14212 1 +a 10412 14211 1 +a 10413 14210 1 +a 10414 14209 1 +a 10415 14208 1 +a 10416 14207 1 +a 10417 14206 1 +a 10418 14205 1 +a 10419 14204 1 +a 10420 14203 1 +a 10421 14202 1 +a 10422 14201 1 +a 10423 14200 1 +a 10424 14199 1 +a 10425 14198 1 +a 10426 14197 1 +a 10427 14196 1 +a 10428 14195 1 +a 10429 14194 1 +a 10430 14193 1 +a 10431 14192 1 +a 10432 14191 1 +a 10433 14190 1 +a 10434 14189 1 +a 10435 14188 1 +a 10436 14187 1 +a 10437 14186 1 +a 10438 14185 1 +a 10439 14184 1 +a 10440 14183 1 +a 10441 14182 1 +a 10442 14181 1 +a 10443 14180 1 +a 10444 14179 1 +a 10445 14178 1 +a 10446 14177 1 +a 10447 14176 1 +a 10448 14175 1 +a 10449 14174 1 +a 10450 14173 1 +a 10451 14172 1 +a 10452 14171 1 +a 10453 14170 1 +a 10454 14169 1 +a 10455 14168 1 +a 10456 14167 1 +a 10457 14166 1 +a 10458 14165 1 +a 10459 14164 1 +a 10460 14163 1 +a 10461 14162 1 +a 10462 14161 1 +a 10463 14160 1 +a 10464 14159 1 +a 10465 14158 1 +a 10466 14157 1 +a 10467 14156 1 +a 10468 14155 1 +a 10469 14154 1 +a 10470 14153 1 +a 10471 14152 1 +a 10472 14151 1 +a 10473 14150 1 +a 10474 14149 1 +a 10475 14148 1 +a 10476 14147 1 +a 10477 14146 1 +a 10478 14145 1 +a 10479 14144 1 +a 10480 14143 1 +a 10481 14142 1 +a 10482 14141 1 +a 10483 14140 1 +a 10484 14139 1 +a 10485 14138 1 +a 10486 14137 1 +a 10487 14136 1 +a 10488 14135 1 +a 10489 14134 1 +a 10490 14133 1 +a 10491 14132 1 +a 10492 14131 1 +a 10493 14130 1 +a 10494 14129 1 +a 10495 14128 1 +a 10496 14127 1 +a 10497 14126 1 +a 10498 14125 1 +a 10499 14124 1 +a 10500 14123 1 +a 10501 14122 1 +a 10502 14121 1 +a 10503 14120 1 +a 10504 14119 1 +a 10505 14118 1 +a 10506 14117 1 +a 10507 14116 1 +a 10508 14115 1 +a 10509 14114 1 +a 10510 14113 1 +a 10511 14112 1 +a 10512 14111 1 +a 10513 14110 1 +a 10514 14109 1 +a 10515 14108 1 +a 10516 14107 1 +a 10517 14106 1 +a 10518 14105 1 +a 10519 14104 1 +a 10520 14103 1 +a 10521 14102 1 +a 10522 14101 1 +a 10523 14100 1 +a 10524 14099 1 +a 10525 14098 1 +a 10526 14097 1 +a 10527 14096 1 +a 10528 14095 1 +a 10529 14094 1 +a 10530 14093 1 +a 10531 14092 1 +a 10532 14091 1 +a 10533 14090 1 +a 10534 14089 1 +a 10535 14088 1 +a 10536 14087 1 +a 10537 14086 1 +a 10538 14085 1 +a 10539 14084 1 +a 10540 14083 1 +a 10541 14082 1 +a 10542 14081 1 +a 10543 14080 1 +a 10544 14079 1 +a 10545 14078 1 +a 10546 14077 1 +a 10547 14076 1 +a 10548 14075 1 +a 10549 14074 1 +a 10550 14073 1 +a 10551 14072 1 +a 10552 14071 1 +a 10553 14070 1 +a 10554 14069 1 +a 10555 14068 1 +a 10556 14067 1 +a 10557 14066 1 +a 10558 14065 1 +a 10559 14064 1 +a 10560 14063 1 +a 10561 14062 1 +a 10562 14061 1 +a 10563 14060 1 +a 10564 14059 1 +a 10565 14058 1 +a 10566 14057 1 +a 10567 14056 1 +a 10568 14055 1 +a 10569 14054 1 +a 10570 14053 1 +a 10571 14052 1 +a 10572 14051 1 +a 10573 14050 1 +a 10574 14049 1 +a 10575 14048 1 +a 10576 14047 1 +a 10577 14046 1 +a 10578 14045 1 +a 10579 14044 1 +a 10580 14043 1 +a 10581 14042 1 +a 10582 14041 1 +a 10583 14040 1 +a 10584 14039 1 +a 10585 14038 1 +a 10586 14037 1 +a 10587 14036 1 +a 10588 14035 1 +a 10589 14034 1 +a 10590 14033 1 +a 10591 14032 1 +a 10592 14031 1 +a 10593 14030 1 +a 10594 14029 1 +a 10595 14028 1 +a 10596 14027 1 +a 10597 14026 1 +a 10598 14025 1 +a 10599 14024 1 +a 10600 14023 1 +a 10601 14022 1 +a 10602 14021 1 +a 10603 14020 1 +a 10604 14019 1 +a 10605 14018 1 +a 10606 14017 1 +a 10607 14016 1 +a 10608 14015 1 +a 10609 14014 1 +a 10610 14013 1 +a 10611 14012 1 +a 10612 14011 1 +a 10613 14010 1 +a 10614 14009 1 +a 10615 14008 1 +a 10616 14007 1 +a 10617 14006 1 +a 10618 14005 1 +a 10619 14004 1 +a 10620 14003 1 +a 10621 14002 1 +a 10622 14001 1 +a 10623 14000 1 +a 10624 13999 1 +a 10625 13998 1 +a 10626 13997 1 +a 10627 13996 1 +a 10628 13995 1 +a 10629 13994 1 +a 10630 13993 1 +a 10631 13992 1 +a 10632 13991 1 +a 10633 13990 1 +a 10634 13989 1 +a 10635 13988 1 +a 10636 13987 1 +a 10637 13986 1 +a 10638 13985 1 +a 10639 13984 1 +a 10640 13983 1 +a 10641 13982 1 +a 10642 13981 1 +a 10643 13980 1 +a 10644 13979 1 +a 10645 13978 1 +a 10646 13977 1 +a 10647 13976 1 +a 10648 13975 1 +a 10649 13974 1 +a 10650 13973 1 +a 10651 13972 1 +a 10652 13971 1 +a 10653 13970 1 +a 10654 13969 1 +a 10655 13968 1 +a 10656 13967 1 +a 10657 13966 1 +a 10658 13965 1 +a 10659 13964 1 +a 10660 13963 1 +a 10661 13962 1 +a 10662 13961 1 +a 10663 13960 1 +a 10664 13959 1 +a 10665 13958 1 +a 10666 13957 1 +a 10667 13956 1 +a 10668 13955 1 +a 10669 13954 1 +a 10670 13953 1 +a 10671 13952 1 +a 10672 13951 1 +a 10673 13950 1 +a 10674 13949 1 +a 10675 13948 1 +a 10676 13947 1 +a 10677 13946 1 +a 10678 13945 1 +a 10679 13944 1 +a 10680 13943 1 +a 10681 13942 1 +a 10682 13941 1 +a 10683 13940 1 +a 10684 13939 1 +a 10685 13938 1 +a 10686 13937 1 +a 10687 13936 1 +a 10688 13935 1 +a 10689 13934 1 +a 10690 13933 1 +a 10691 13932 1 +a 10692 13931 1 +a 10693 13930 1 +a 10694 13929 1 +a 10695 13928 1 +a 10696 13927 1 +a 10697 13926 1 +a 10698 13925 1 +a 10699 13924 1 +a 10700 13923 1 +a 10701 13922 1 +a 10702 13921 1 +a 10703 13920 1 +a 10704 13919 1 +a 10705 13918 1 +a 10706 13917 1 +a 10707 13916 1 +a 10708 13915 1 +a 10709 13914 1 +a 10710 13913 1 +a 10711 13912 1 +a 10712 13911 1 +a 10713 13910 1 +a 10714 13909 1 +a 10715 13908 1 +a 10716 13907 1 +a 10717 13906 1 +a 10718 13905 1 +a 10719 13904 1 +a 10720 13903 1 +a 10721 13902 1 +a 10722 13901 1 +a 10723 13900 1 +a 10724 13899 1 +a 10725 13898 1 +a 10726 13897 1 +a 10727 13896 1 +a 10728 13895 1 +a 10729 13894 1 +a 10730 13893 1 +a 10731 13892 1 +a 10732 13891 1 +a 10733 13890 1 +a 10734 13889 1 +a 10735 13888 1 +a 10736 13887 1 +a 10737 13886 1 +a 10738 13885 1 +a 10739 13884 1 +a 10740 13883 1 +a 10741 13882 1 +a 10742 13881 1 +a 10743 13880 1 +a 10744 13879 1 +a 10745 13878 1 +a 10746 13877 1 +a 10747 13876 1 +a 10748 13875 1 +a 10749 13874 1 +a 10750 13873 1 +a 10751 13872 1 +a 10752 13871 1 +a 10753 13870 1 +a 10754 13869 1 +a 10755 13868 1 +a 10756 13867 1 +a 10757 13866 1 +a 10758 13865 1 +a 10759 13864 1 +a 10760 13863 1 +a 10761 13862 1 +a 10762 13861 1 +a 10763 13860 1 +a 10764 13859 1 +a 10765 13858 1 +a 10766 13857 1 +a 10767 13856 1 +a 10768 13855 1 +a 10769 13854 1 +a 10770 13853 1 +a 10771 13852 1 +a 10772 13851 1 +a 10773 13850 1 +a 10774 13849 1 +a 10775 13848 1 +a 10776 13847 1 +a 10777 13846 1 +a 10778 13845 1 +a 10779 13844 1 +a 10780 13843 1 +a 10781 13842 1 +a 10782 13841 1 +a 10783 13840 1 +a 10784 13839 1 +a 10785 13838 1 +a 10786 13837 1 +a 10787 13836 1 +a 10788 13835 1 +a 10789 13834 1 +a 10790 13833 1 +a 10791 13832 1 +a 10792 13831 1 +a 10793 13830 1 +a 10794 13829 1 +a 10795 13828 1 +a 10796 13827 1 +a 10797 13826 1 +a 10798 13825 1 +a 10799 13824 1 +a 10800 13823 1 +a 10801 13822 1 +a 10802 13821 1 +a 10803 13820 1 +a 10804 13819 1 +a 10805 13818 1 +a 10806 13817 1 +a 10807 13816 1 +a 10808 13815 1 +a 10809 13814 1 +a 10810 13813 1 +a 10811 13812 1 +a 10812 13811 1 +a 10813 13810 1 +a 10814 13809 1 +a 10815 13808 1 +a 10816 13807 1 +a 10817 13806 1 +a 10818 13805 1 +a 10819 13804 1 +a 10820 13803 1 +a 10821 13802 1 +a 10822 13801 1 +a 10823 13800 1 +a 10824 13799 1 +a 10825 13798 1 +a 10826 13797 1 +a 10827 13796 1 +a 10828 13795 1 +a 10829 13794 1 +a 10830 13793 1 +a 10831 13792 1 +a 10832 13791 1 +a 10833 13790 1 +a 10834 13789 1 +a 10835 13788 1 +a 10836 13787 1 +a 10837 13786 1 +a 10838 13785 1 +a 10839 13784 1 +a 10840 13783 1 +a 10841 13782 1 +a 10842 13781 1 +a 10843 13780 1 +a 10844 13779 1 +a 10845 13778 1 +a 10846 13777 1 +a 10847 13776 1 +a 10848 13775 1 +a 10849 13774 1 +a 10850 13773 1 +a 10851 13772 1 +a 10852 13771 1 +a 10853 13770 1 +a 10854 13769 1 +a 10855 13768 1 +a 10856 13767 1 +a 10857 13766 1 +a 10858 13765 1 +a 10859 13764 1 +a 10860 13763 1 +a 10861 13762 1 +a 10862 13761 1 +a 10863 13760 1 +a 10864 13759 1 +a 10865 13758 1 +a 10866 13757 1 +a 10867 13756 1 +a 10868 13755 1 +a 10869 13754 1 +a 10870 13753 1 +a 10871 13752 1 +a 10872 13751 1 +a 10873 13750 1 +a 10874 13749 1 +a 10875 13748 1 +a 10876 13747 1 +a 10877 13746 1 +a 10878 13745 1 +a 10879 13744 1 +a 10880 13743 1 +a 10881 13742 1 +a 10882 13741 1 +a 10883 13740 1 +a 10884 13739 1 +a 10885 13738 1 +a 10886 13737 1 +a 10887 13736 1 +a 10888 13735 1 +a 10889 13734 1 +a 10890 13733 1 +a 10891 13732 1 +a 10892 13731 1 +a 10893 13730 1 +a 10894 13729 1 +a 10895 13728 1 +a 10896 13727 1 +a 10897 13726 1 +a 10898 13725 1 +a 10899 13724 1 +a 10900 13723 1 +a 10901 13722 1 +a 10902 13721 1 +a 10903 13720 1 +a 10904 13719 1 +a 10905 13718 1 +a 10906 13717 1 +a 10907 13716 1 +a 10908 13715 1 +a 10909 13714 1 +a 10910 13713 1 +a 10911 13712 1 +a 10912 13711 1 +a 10913 13710 1 +a 10914 13709 1 +a 10915 13708 1 +a 10916 13707 1 +a 10917 13706 1 +a 10918 13705 1 +a 10919 13704 1 +a 10920 13703 1 +a 10921 13702 1 +a 10922 13701 1 +a 10923 13700 1 +a 10924 13699 1 +a 10925 13698 1 +a 10926 13697 1 +a 10927 13696 1 +a 10928 13695 1 +a 10929 13694 1 +a 10930 13693 1 +a 10931 13692 1 +a 10932 13691 1 +a 10933 13690 1 +a 10934 13689 1 +a 10935 13688 1 +a 10936 13687 1 +a 10937 13686 1 +a 10938 13685 1 +a 10939 13684 1 +a 10940 13683 1 +a 10941 13682 1 +a 10942 13681 1 +a 10943 13680 1 +a 10944 13679 1 +a 10945 13678 1 +a 10946 13677 1 +a 10947 13676 1 +a 10948 13675 1 +a 10949 13674 1 +a 10950 13673 1 +a 10951 13672 1 +a 10952 13671 1 +a 10953 13670 1 +a 10954 13669 1 +a 10955 13668 1 +a 10956 13667 1 +a 10957 13666 1 +a 10958 13665 1 +a 10959 13664 1 +a 10960 13663 1 +a 10961 13662 1 +a 10962 13661 1 +a 10963 13660 1 +a 10964 13659 1 +a 10965 13658 1 +a 10966 13657 1 +a 10967 13656 1 +a 10968 13655 1 +a 10969 13654 1 +a 10970 13653 1 +a 10971 13652 1 +a 10972 13651 1 +a 10973 13650 1 +a 10974 13649 1 +a 10975 13648 1 +a 10976 13647 1 +a 10977 13646 1 +a 10978 13645 1 +a 10979 13644 1 +a 10980 13643 1 +a 10981 13642 1 +a 10982 13641 1 +a 10983 13640 1 +a 10984 13639 1 +a 10985 13638 1 +a 10986 13637 1 +a 10987 13636 1 +a 10988 13635 1 +a 10989 13634 1 +a 10990 13633 1 +a 10991 13632 1 +a 10992 13631 1 +a 10993 13630 1 +a 10994 13629 1 +a 10995 13628 1 +a 10996 13627 1 +a 10997 13626 1 +a 10998 13625 1 +a 10999 13624 1 +a 11000 13623 1 +a 11001 13622 1 +a 11002 13621 1 +a 11003 13620 1 +a 11004 13619 1 +a 11005 13618 1 +a 11006 13617 1 +a 11007 13616 1 +a 11008 13615 1 +a 11009 13614 1 +a 11010 13613 1 +a 11011 13612 1 +a 11012 13611 1 +a 11013 13610 1 +a 11014 13609 1 +a 11015 13608 1 +a 11016 13607 1 +a 11017 13606 1 +a 11018 13605 1 +a 11019 13604 1 +a 11020 13603 1 +a 11021 13602 1 +a 11022 13601 1 +a 11023 13600 1 +a 11024 13599 1 +a 11025 13598 1 +a 11026 13597 1 +a 11027 13596 1 +a 11028 13595 1 +a 11029 13594 1 +a 11030 13593 1 +a 11031 13592 1 +a 11032 13591 1 +a 11033 13590 1 +a 11034 13589 1 +a 11035 13588 1 +a 11036 13587 1 +a 11037 13586 1 +a 11038 13585 1 +a 11039 13584 1 +a 11040 13583 1 +a 11041 13582 1 +a 11042 13581 1 +a 11043 13580 1 +a 11044 13579 1 +a 11045 13578 1 +a 11046 13577 1 +a 11047 13576 1 +a 11048 13575 1 +a 11049 13574 1 +a 11050 13573 1 +a 11051 13572 1 +a 11052 13571 1 +a 11053 13570 1 +a 11054 13569 1 +a 11055 13568 1 +a 11056 13567 1 +a 11057 13566 1 +a 11058 13565 1 +a 11059 13564 1 +a 11060 13563 1 +a 11061 13562 1 +a 11062 13561 1 +a 11063 13560 1 +a 11064 13559 1 +a 11065 13558 1 +a 11066 13557 1 +a 11067 13556 1 +a 11068 13555 1 +a 11069 13554 1 +a 11070 13553 1 +a 11071 13552 1 +a 11072 13551 1 +a 11073 13550 1 +a 11074 13549 1 +a 11075 13548 1 +a 11076 13547 1 +a 11077 13546 1 +a 11078 13545 1 +a 11079 13544 1 +a 11080 13543 1 +a 11081 13542 1 +a 11082 13541 1 +a 11083 13540 1 +a 11084 13539 1 +a 11085 13538 1 +a 11086 13537 1 +a 11087 13536 1 +a 11088 13535 1 +a 11089 13534 1 +a 11090 13533 1 +a 11091 13532 1 +a 11092 13531 1 +a 11093 13530 1 +a 11094 13529 1 +a 11095 13528 1 +a 11096 13527 1 +a 11097 13526 1 +a 11098 13525 1 +a 11099 13524 1 +a 11100 13523 1 +a 11101 13522 1 +a 11102 13521 1 +a 11103 13520 1 +a 11104 13519 1 +a 11105 13518 1 +a 11106 13517 1 +a 11107 13516 1 +a 11108 13515 1 +a 11109 13514 1 +a 11110 13513 1 +a 11111 13512 1 +a 11112 13511 1 +a 11113 13510 1 +a 11114 13509 1 +a 11115 13508 1 +a 11116 13507 1 +a 11117 13506 1 +a 11118 13505 1 +a 11119 13504 1 +a 11120 13503 1 +a 11121 13502 1 +a 11122 13501 1 +a 11123 13500 1 +a 11124 13499 1 +a 11125 13498 1 +a 11126 13497 1 +a 11127 13496 1 +a 11128 13495 1 +a 11129 13494 1 +a 11130 13493 1 +a 11131 13492 1 +a 11132 13491 1 +a 11133 13490 1 +a 11134 13489 1 +a 11135 13488 1 +a 11136 13487 1 +a 11137 13486 1 +a 11138 13485 1 +a 11139 13484 1 +a 11140 13483 1 +a 11141 13482 1 +a 11142 13481 1 +a 11143 13480 1 +a 11144 13479 1 +a 11145 13478 1 +a 11146 13477 1 +a 11147 13476 1 +a 11148 13475 1 +a 11149 13474 1 +a 11150 13473 1 +a 11151 13472 1 +a 11152 13471 1 +a 11153 13470 1 +a 11154 13469 1 +a 11155 13468 1 +a 11156 13467 1 +a 11157 13466 1 +a 11158 13465 1 +a 11159 13464 1 +a 11160 13463 1 +a 11161 13462 1 +a 11162 13461 1 +a 11163 13460 1 +a 11164 13459 1 +a 11165 13458 1 +a 11166 13457 1 +a 11167 13456 1 +a 11168 13455 1 +a 11169 13454 1 +a 11170 13453 1 +a 11171 13452 1 +a 11172 13451 1 +a 11173 13450 1 +a 11174 13449 1 +a 11175 13448 1 +a 11176 13447 1 +a 11177 13446 1 +a 11178 13445 1 +a 11179 13444 1 +a 11180 13443 1 +a 11181 13442 1 +a 11182 13441 1 +a 11183 13440 1 +a 11184 13439 1 +a 11185 13438 1 +a 11186 13437 1 +a 11187 13436 1 +a 11188 13435 1 +a 11189 13434 1 +a 11190 13433 1 +a 11191 13432 1 +a 11192 13431 1 +a 11193 13430 1 +a 11194 13429 1 +a 11195 13428 1 +a 11196 13427 1 +a 11197 13426 1 +a 11198 13425 1 +a 11199 13424 1 +a 11200 13423 1 +a 11201 13422 1 +a 11202 13421 1 +a 11203 13420 1 +a 11204 13419 1 +a 11205 13418 1 +a 11206 13417 1 +a 11207 13416 1 +a 11208 13415 1 +a 11209 13414 1 +a 11210 13413 1 +a 11211 13412 1 +a 11212 13411 1 +a 11213 13410 1 +a 11214 13409 1 +a 11215 13408 1 +a 11216 13407 1 +a 11217 13406 1 +a 11218 13405 1 +a 11219 13404 1 +a 11220 13403 1 +a 11221 13402 1 +a 11222 13401 1 +a 11223 13400 1 +a 11224 13399 1 +a 11225 13398 1 +a 11226 13397 1 +a 11227 13396 1 +a 11228 13395 1 +a 11229 13394 1 +a 11230 13393 1 +a 11231 13392 1 +a 11232 13391 1 +a 11233 13390 1 +a 11234 13389 1 +a 11235 13388 1 +a 11236 13387 1 +a 11237 13386 1 +a 11238 13385 1 +a 11239 13384 1 +a 11240 13383 1 +a 11241 13382 1 +a 11242 13381 1 +a 11243 13380 1 +a 11244 13379 1 +a 11245 13378 1 +a 11246 13377 1 +a 11247 13376 1 +a 11248 13375 1 +a 11249 13374 1 +a 11250 13373 1 +a 11251 13372 1 +a 11252 13371 1 +a 11253 13370 1 +a 11254 13369 1 +a 11255 13368 1 +a 11256 13367 1 +a 11257 13366 1 +a 11258 13365 1 +a 11259 13364 1 +a 11260 13363 1 +a 11261 13362 1 +a 11262 13361 1 +a 11263 13360 1 +a 11264 13359 1 +a 11265 13358 1 +a 11266 13357 1 +a 11267 13356 1 +a 11268 13355 1 +a 11269 13354 1 +a 11270 13353 1 +a 11271 13352 1 +a 11272 13351 1 +a 11273 13350 1 +a 11274 13349 1 +a 11275 13348 1 +a 11276 13347 1 +a 11277 13346 1 +a 11278 13345 1 +a 11279 13344 1 +a 11280 13343 1 +a 11281 13342 1 +a 11282 13341 1 +a 11283 13340 1 +a 11284 13339 1 +a 11285 13338 1 +a 11286 13337 1 +a 11287 13336 1 +a 11288 13335 1 +a 11289 13334 1 +a 11290 13333 1 +a 11291 13332 1 +a 11292 13331 1 +a 11293 13330 1 +a 11294 13329 1 +a 11295 13328 1 +a 11296 13327 1 +a 11297 13326 1 +a 11298 13325 1 +a 11299 13324 1 +a 11300 13323 1 +a 11301 13322 1 +a 11302 13321 1 +a 11303 13320 1 +a 11304 13319 1 +a 11305 13318 1 +a 11306 13317 1 +a 11307 13316 1 +a 11308 13315 1 +a 11309 13314 1 +a 11310 13313 1 +a 11311 13312 1 +a 11312 13311 1 +a 11313 13310 1 +a 11314 13309 1 +a 11315 13308 1 +a 11316 13307 1 +a 11317 13306 1 +a 11318 13305 1 +a 11319 13304 1 +a 11320 13303 1 +a 11321 13302 1 +a 11322 13301 1 +a 11323 13300 1 +a 11324 13299 1 +a 11325 13298 1 +a 11326 13297 1 +a 11327 13296 1 +a 11328 13295 1 +a 11329 13294 1 +a 11330 13293 1 +a 11331 13292 1 +a 11332 13291 1 +a 11333 13290 1 +a 11334 13289 1 +a 11335 13288 1 +a 11336 13287 1 +a 11337 13286 1 +a 11338 13285 1 +a 11339 13284 1 +a 11340 13283 1 +a 11341 13282 1 +a 11342 13281 1 +a 11343 13280 1 +a 11344 13279 1 +a 11345 13278 1 +a 11346 13277 1 +a 11347 13276 1 +a 11348 13275 1 +a 11349 13274 1 +a 11350 13273 1 +a 11351 13272 1 +a 11352 13271 1 +a 11353 13270 1 +a 11354 13269 1 +a 11355 13268 1 +a 11356 13267 1 +a 11357 13266 1 +a 11358 13265 1 +a 11359 13264 1 +a 11360 13263 1 +a 11361 13262 1 +a 11362 13261 1 +a 11363 13260 1 +a 11364 13259 1 +a 11365 13258 1 +a 11366 13257 1 +a 11367 13256 1 +a 11368 13255 1 +a 11369 13254 1 +a 11370 13253 1 +a 11371 13252 1 +a 11372 13251 1 +a 11373 13250 1 +a 11374 13249 1 +a 11375 13248 1 +a 11376 13247 1 +a 11377 13246 1 +a 11378 13245 1 +a 11379 13244 1 +a 11380 13243 1 +a 11381 13242 1 +a 11382 13241 1 +a 11383 13240 1 +a 11384 13239 1 +a 11385 13238 1 +a 11386 13237 1 +a 11387 13236 1 +a 11388 13235 1 +a 11389 13234 1 +a 11390 13233 1 +a 11391 13232 1 +a 11392 13231 1 +a 11393 13230 1 +a 11394 13229 1 +a 11395 13228 1 +a 11396 13227 1 +a 11397 13226 1 +a 11398 13225 1 +a 11399 13224 1 +a 11400 13223 1 +a 11401 13222 1 +a 11402 13221 1 +a 11403 13220 1 +a 11404 13219 1 +a 11405 13218 1 +a 11406 13217 1 +a 11407 13216 1 +a 11408 13215 1 +a 11409 13214 1 +a 11410 13213 1 +a 11411 13212 1 +a 11412 13211 1 +a 11413 13210 1 +a 11414 13209 1 +a 11415 13208 1 +a 11416 13207 1 +a 11417 13206 1 +a 11418 13205 1 +a 11419 13204 1 +a 11420 13203 1 +a 11421 13202 1 +a 11422 13201 1 +a 11423 13200 1 +a 11424 13199 1 +a 11425 13198 1 +a 11426 13197 1 +a 11427 13196 1 +a 11428 13195 1 +a 11429 13194 1 +a 11430 13193 1 +a 11431 13192 1 +a 11432 13191 1 +a 11433 13190 1 +a 11434 13189 1 +a 11435 13188 1 +a 11436 13187 1 +a 11437 13186 1 +a 11438 13185 1 +a 11439 13184 1 +a 11440 13183 1 +a 11441 13182 1 +a 11442 13181 1 +a 11443 13180 1 +a 11444 13179 1 +a 11445 13178 1 +a 11446 13177 1 +a 11447 13176 1 +a 11448 13175 1 +a 11449 13174 1 +a 11450 13173 1 +a 11451 13172 1 +a 11452 13171 1 +a 11453 13170 1 +a 11454 13169 1 +a 11455 13168 1 +a 11456 13167 1 +a 11457 13166 1 +a 11458 13165 1 +a 11459 13164 1 +a 11460 13163 1 +a 11461 13162 1 +a 11462 13161 1 +a 11463 13160 1 +a 11464 13159 1 +a 11465 13158 1 +a 11466 13157 1 +a 11467 13156 1 +a 11468 13155 1 +a 11469 13154 1 +a 11470 13153 1 +a 11471 13152 1 +a 11472 13151 1 +a 11473 13150 1 +a 11474 13149 1 +a 11475 13148 1 +a 11476 13147 1 +a 11477 13146 1 +a 11478 13145 1 +a 11479 13144 1 +a 11480 13143 1 +a 11481 13142 1 +a 11482 13141 1 +a 11483 13140 1 +a 11484 13139 1 +a 11485 13138 1 +a 11486 13137 1 +a 11487 13136 1 +a 11488 13135 1 +a 11489 13134 1 +a 11490 13133 1 +a 11491 13132 1 +a 11492 13131 1 +a 11493 13130 1 +a 11494 13129 1 +a 11495 13128 1 +a 11496 13127 1 +a 11497 13126 1 +a 11498 13125 1 +a 11499 13124 1 +a 11500 13123 1 +a 11501 13122 1 +a 11502 13121 1 +a 11503 13120 1 +a 11504 13119 1 +a 11505 13118 1 +a 11506 13117 1 +a 11507 13116 1 +a 11508 13115 1 +a 11509 13114 1 +a 11510 13113 1 +a 11511 13112 1 +a 11512 13111 1 +a 11513 13110 1 +a 11514 13109 1 +a 11515 13108 1 +a 11516 13107 1 +a 11517 13106 1 +a 11518 13105 1 +a 11519 13104 1 +a 11520 13103 1 +a 11521 13102 1 +a 11522 13101 1 +a 11523 13100 1 +a 11524 13099 1 +a 11525 13098 1 +a 11526 13097 1 +a 11527 13096 1 +a 11528 13095 1 +a 11529 13094 1 +a 11530 13093 1 +a 11531 13092 1 +a 11532 13091 1 +a 11533 13090 1 +a 11534 13089 1 +a 11535 13088 1 +a 11536 13087 1 +a 11537 13086 1 +a 11538 13085 1 +a 11539 13084 1 +a 11540 13083 1 +a 11541 13082 1 +a 11542 13081 1 +a 11543 13080 1 +a 11544 13079 1 +a 11545 13078 1 +a 11546 13077 1 +a 11547 13076 1 +a 11548 13075 1 +a 11549 13074 1 +a 11550 13073 1 +a 11551 13072 1 +a 11552 13071 1 +a 11553 13070 1 +a 11554 13069 1 +a 11555 13068 1 +a 11556 13067 1 +a 11557 13066 1 +a 11558 13065 1 +a 11559 13064 1 +a 11560 13063 1 +a 11561 13062 1 +a 11562 13061 1 +a 11563 13060 1 +a 11564 13059 1 +a 11565 13058 1 +a 11566 13057 1 +a 11567 13056 1 +a 11568 13055 1 +a 11569 13054 1 +a 11570 13053 1 +a 11571 13052 1 +a 11572 13051 1 +a 11573 13050 1 +a 11574 13049 1 +a 11575 13048 1 +a 11576 13047 1 +a 11577 13046 1 +a 11578 13045 1 +a 11579 13044 1 +a 11580 13043 1 +a 11581 13042 1 +a 11582 13041 1 +a 11583 13040 1 +a 11584 13039 1 +a 11585 13038 1 +a 11586 13037 1 +a 11587 13036 1 +a 11588 13035 1 +a 11589 13034 1 +a 11590 13033 1 +a 11591 13032 1 +a 11592 13031 1 +a 11593 13030 1 +a 11594 13029 1 +a 11595 13028 1 +a 11596 13027 1 +a 11597 13026 1 +a 11598 13025 1 +a 11599 13024 1 +a 11600 13023 1 +a 11601 13022 1 +a 11602 13021 1 +a 11603 13020 1 +a 11604 13019 1 +a 11605 13018 1 +a 11606 13017 1 +a 11607 13016 1 +a 11608 13015 1 +a 11609 13014 1 +a 11610 13013 1 +a 11611 13012 1 +a 11612 13011 1 +a 11613 13010 1 +a 11614 13009 1 +a 11615 13008 1 +a 11616 13007 1 +a 11617 13006 1 +a 11618 13005 1 +a 11619 13004 1 +a 11620 13003 1 +a 11621 13002 1 +a 11622 13001 1 +a 11623 13000 1 +a 11624 12999 1 +a 11625 12998 1 +a 11626 12997 1 +a 11627 12996 1 +a 11628 12995 1 +a 11629 12994 1 +a 11630 12993 1 +a 11631 12992 1 +a 11632 12991 1 +a 11633 12990 1 +a 11634 12989 1 +a 11635 12988 1 +a 11636 12987 1 +a 11637 12986 1 +a 11638 12985 1 +a 11639 12984 1 +a 11640 12983 1 +a 11641 12982 1 +a 11642 12981 1 +a 11643 12980 1 +a 11644 12979 1 +a 11645 12978 1 +a 11646 12977 1 +a 11647 12976 1 +a 11648 12975 1 +a 11649 12974 1 +a 11650 12973 1 +a 11651 12972 1 +a 11652 12971 1 +a 11653 12970 1 +a 11654 12969 1 +a 11655 12968 1 +a 11656 12967 1 +a 11657 12966 1 +a 11658 12965 1 +a 11659 12964 1 +a 11660 12963 1 +a 11661 12962 1 +a 11662 12961 1 +a 11663 12960 1 +a 11664 12959 1 +a 11665 12958 1 +a 11666 12957 1 +a 11667 12956 1 +a 11668 12955 1 +a 11669 12954 1 +a 11670 12953 1 +a 11671 12952 1 +a 11672 12951 1 +a 11673 12950 1 +a 11674 12949 1 +a 11675 12948 1 +a 11676 12947 1 +a 11677 12946 1 +a 11678 12945 1 +a 11679 12944 1 +a 11680 12943 1 +a 11681 12942 1 +a 11682 12941 1 +a 11683 12940 1 +a 11684 12939 1 +a 11685 12938 1 +a 11686 12937 1 +a 11687 12936 1 +a 11688 12935 1 +a 11689 12934 1 +a 11690 12933 1 +a 11691 12932 1 +a 11692 12931 1 +a 11693 12930 1 +a 11694 12929 1 +a 11695 12928 1 +a 11696 12927 1 +a 11697 12926 1 +a 11698 12925 1 +a 11699 12924 1 +a 11700 12923 1 +a 11701 12922 1 +a 11702 12921 1 +a 11703 12920 1 +a 11704 12919 1 +a 11705 12918 1 +a 11706 12917 1 +a 11707 12916 1 +a 11708 12915 1 +a 11709 12914 1 +a 11710 12913 1 +a 11711 12912 1 +a 11712 12911 1 +a 11713 12910 1 +a 11714 12909 1 +a 11715 12908 1 +a 11716 12907 1 +a 11717 12906 1 +a 11718 12905 1 +a 11719 12904 1 +a 11720 12903 1 +a 11721 12902 1 +a 11722 12901 1 +a 11723 12900 1 +a 11724 12899 1 +a 11725 12898 1 +a 11726 12897 1 +a 11727 12896 1 +a 11728 12895 1 +a 11729 12894 1 +a 11730 12893 1 +a 11731 12892 1 +a 11732 12891 1 +a 11733 12890 1 +a 11734 12889 1 +a 11735 12888 1 +a 11736 12887 1 +a 11737 12886 1 +a 11738 12885 1 +a 11739 12884 1 +a 11740 12883 1 +a 11741 12882 1 +a 11742 12881 1 +a 11743 12880 1 +a 11744 12879 1 +a 11745 12878 1 +a 11746 12877 1 +a 11747 12876 1 +a 11748 12875 1 +a 11749 12874 1 +a 11750 12873 1 +a 11751 12872 1 +a 11752 12871 1 +a 11753 12870 1 +a 11754 12869 1 +a 11755 12868 1 +a 11756 12867 1 +a 11757 12866 1 +a 11758 12865 1 +a 11759 12864 1 +a 11760 12863 1 +a 11761 12862 1 +a 11762 12861 1 +a 11763 12860 1 +a 11764 12859 1 +a 11765 12858 1 +a 11766 12857 1 +a 11767 12856 1 +a 11768 12855 1 +a 11769 12854 1 +a 11770 12853 1 +a 11771 12852 1 +a 11772 12851 1 +a 11773 12850 1 +a 11774 12849 1 +a 11775 12848 1 +a 11776 12847 1 +a 11777 12846 1 +a 11778 12845 1 +a 11779 12844 1 +a 11780 12843 1 +a 11781 12842 1 +a 11782 12841 1 +a 11783 12840 1 +a 11784 12839 1 +a 11785 12838 1 +a 11786 12837 1 +a 11787 12836 1 +a 11788 12835 1 +a 11789 12834 1 +a 11790 12833 1 +a 11791 12832 1 +a 11792 12831 1 +a 11793 12830 1 +a 11794 12829 1 +a 11795 12828 1 +a 11796 12827 1 +a 11797 12826 1 +a 11798 12825 1 +a 11799 12824 1 +a 11800 12823 1 +a 11801 12822 1 +a 11802 12821 1 +a 11803 12820 1 +a 11804 12819 1 +a 11805 12818 1 +a 11806 12817 1 +a 11807 12816 1 +a 11808 12815 1 +a 11809 12814 1 +a 11810 12813 1 +a 11811 12812 1 +a 11812 12811 1 +a 11813 12810 1 +a 11814 12809 1 +a 11815 12808 1 +a 11816 12807 1 +a 11817 12806 1 +a 11818 12805 1 +a 11819 12804 1 +a 11820 12803 1 +a 11821 12802 1 +a 11822 12801 1 +a 11823 12800 1 +a 11824 12799 1 +a 11825 12798 1 +a 11826 12797 1 +a 11827 12796 1 +a 11828 12795 1 +a 11829 12794 1 +a 11830 12793 1 +a 11831 12792 1 +a 11832 12791 1 +a 11833 12790 1 +a 11834 12789 1 +a 11835 12788 1 +a 11836 12787 1 +a 11837 12786 1 +a 11838 12785 1 +a 11839 12784 1 +a 11840 12783 1 +a 11841 12782 1 +a 11842 12781 1 +a 11843 12780 1 +a 11844 12779 1 +a 11845 12778 1 +a 11846 12777 1 +a 11847 12776 1 +a 11848 12775 1 +a 11849 12774 1 +a 11850 12773 1 +a 11851 12772 1 +a 11852 12771 1 +a 11853 12770 1 +a 11854 12769 1 +a 11855 12768 1 +a 11856 12767 1 +a 11857 12766 1 +a 11858 12765 1 +a 11859 12764 1 +a 11860 12763 1 +a 11861 12762 1 +a 11862 12761 1 +a 11863 12760 1 +a 11864 12759 1 +a 11865 12758 1 +a 11866 12757 1 +a 11867 12756 1 +a 11868 12755 1 +a 11869 12754 1 +a 11870 12753 1 +a 11871 12752 1 +a 11872 12751 1 +a 11873 12750 1 +a 11874 12749 1 +a 11875 12748 1 +a 11876 12747 1 +a 11877 12746 1 +a 11878 12745 1 +a 11879 12744 1 +a 11880 12743 1 +a 11881 12742 1 +a 11882 12741 1 +a 11883 12740 1 +a 11884 12739 1 +a 11885 12738 1 +a 11886 12737 1 +a 11887 12736 1 +a 11888 12735 1 +a 11889 12734 1 +a 11890 12733 1 +a 11891 12732 1 +a 11892 12731 1 +a 11893 12730 1 +a 11894 12729 1 +a 11895 12728 1 +a 11896 12727 1 +a 11897 12726 1 +a 11898 12725 1 +a 11899 12724 1 +a 11900 12723 1 +a 11901 12722 1 +a 11902 12721 1 +a 11903 12720 1 +a 11904 12719 1 +a 11905 12718 1 +a 11906 12717 1 +a 11907 12716 1 +a 11908 12715 1 +a 11909 12714 1 +a 11910 12713 1 +a 11911 12712 1 +a 11912 12711 1 +a 11913 12710 1 +a 11914 12709 1 +a 11915 12708 1 +a 11916 12707 1 +a 11917 12706 1 +a 11918 12705 1 +a 11919 12704 1 +a 11920 12703 1 +a 11921 12702 1 +a 11922 12701 1 +a 11923 12700 1 +a 11924 12699 1 +a 11925 12698 1 +a 11926 12697 1 +a 11927 12696 1 +a 11928 12695 1 +a 11929 12694 1 +a 11930 12693 1 +a 11931 12692 1 +a 11932 12691 1 +a 11933 12690 1 +a 11934 12689 1 +a 11935 12688 1 +a 11936 12687 1 +a 11937 12686 1 +a 11938 12685 1 +a 11939 12684 1 +a 11940 12683 1 +a 11941 12682 1 +a 11942 12681 1 +a 11943 12680 1 +a 11944 12679 1 +a 11945 12678 1 +a 11946 12677 1 +a 11947 12676 1 +a 11948 12675 1 +a 11949 12674 1 +a 11950 12673 1 +a 11951 12672 1 +a 11952 12671 1 +a 11953 12670 1 +a 11954 12669 1 +a 11955 12668 1 +a 11956 12667 1 +a 11957 12666 1 +a 11958 12665 1 +a 11959 12664 1 +a 11960 12663 1 +a 11961 12662 1 +a 11962 12661 1 +a 11963 12660 1 +a 11964 12659 1 +a 11965 12658 1 +a 11966 12657 1 +a 11967 12656 1 +a 11968 12655 1 +a 11969 12654 1 +a 11970 12653 1 +a 11971 12652 1 +a 11972 12651 1 +a 11973 12650 1 +a 11974 12649 1 +a 11975 12648 1 +a 11976 12647 1 +a 11977 12646 1 +a 11978 12645 1 +a 11979 12644 1 +a 11980 12643 1 +a 11981 12642 1 +a 11982 12641 1 +a 11983 12640 1 +a 11984 12639 1 +a 11985 12638 1 +a 11986 12637 1 +a 11987 12636 1 +a 11988 12635 1 +a 11989 12634 1 +a 11990 12633 1 +a 11991 12632 1 +a 11992 12631 1 +a 11993 12630 1 +a 11994 12629 1 +a 11995 12628 1 +a 11996 12627 1 +a 11997 12626 1 +a 11998 12625 1 +a 11999 12624 1 +a 12000 12623 1 +a 12001 12622 1 +a 12002 12621 1 +a 12003 12620 1 +a 12004 12619 1 +a 12005 12618 1 +a 12006 12617 1 +a 12007 12616 1 +a 12008 12615 1 +a 12009 12614 1 +a 12010 12613 1 +a 12011 12612 1 +a 12012 12611 1 +a 12013 12610 1 +a 12014 12609 1 +a 12015 12608 1 +a 12016 12607 1 +a 12017 12606 1 +a 12018 12605 1 +a 12019 12604 1 +a 12020 12603 1 +a 12021 12602 1 +a 12022 12601 1 +a 12023 12600 1 +a 12024 12599 1 +a 12025 12598 1 +a 12026 12597 1 +a 12027 12596 1 +a 12028 12595 1 +a 12029 12594 1 +a 12030 12593 1 +a 12031 12592 1 +a 12032 12591 1 +a 12033 12590 1 +a 12034 12589 1 +a 12035 12588 1 +a 12036 12587 1 +a 12037 12586 1 +a 12038 12585 1 +a 12039 12584 1 +a 12040 12583 1 +a 12041 12582 1 +a 12042 12581 1 +a 12043 12580 1 +a 12044 12579 1 +a 12045 12578 1 +a 12046 12577 1 +a 12047 12576 1 +a 12048 12575 1 +a 12049 12574 1 +a 12050 12573 1 +a 12051 12572 1 +a 12052 12571 1 +a 12053 12570 1 +a 12054 12569 1 +a 12055 12568 1 +a 12056 12567 1 +a 12057 12566 1 +a 12058 12565 1 +a 12059 12564 1 +a 12060 12563 1 +a 12061 12562 1 +a 12062 12561 1 +a 12063 12560 1 +a 12064 12559 1 +a 12065 12558 1 +a 12066 12557 1 +a 12067 12556 1 +a 12068 12555 1 +a 12069 12554 1 +a 12070 12553 1 +a 12071 12552 1 +a 12072 12551 1 +a 12073 12550 1 +a 12074 12549 1 +a 12075 12548 1 +a 12076 12547 1 +a 12077 12546 1 +a 12078 12545 1 +a 12079 12544 1 +a 12080 12543 1 +a 12081 12542 1 +a 12082 12541 1 +a 12083 12540 1 +a 12084 12539 1 +a 12085 12538 1 +a 12086 12537 1 +a 12087 12536 1 +a 12088 12535 1 +a 12089 12534 1 +a 12090 12533 1 +a 12091 12532 1 +a 12092 12531 1 +a 12093 12530 1 +a 12094 12529 1 +a 12095 12528 1 +a 12096 12527 1 +a 12097 12526 1 +a 12098 12525 1 +a 12099 12524 1 +a 12100 12523 1 +a 12101 12522 1 +a 12102 12521 1 +a 12103 12520 1 +a 12104 12519 1 +a 12105 12518 1 +a 12106 12517 1 +a 12107 12516 1 +a 12108 12515 1 +a 12109 12514 1 +a 12110 12513 1 +a 12111 12512 1 +a 12112 12511 1 +a 12113 12510 1 +a 12114 12509 1 +a 12115 12508 1 +a 12116 12507 1 +a 12117 12506 1 +a 12118 12505 1 +a 12119 12504 1 +a 12120 12503 1 +a 12121 12502 1 +a 12122 12501 1 +a 12123 12500 1 +a 12124 12499 1 +a 12125 12498 1 +a 12126 12497 1 +a 12127 12496 1 +a 12128 12495 1 +a 12129 12494 1 +a 12130 12493 1 +a 12131 12492 1 +a 12132 12491 1 +a 12133 12490 1 +a 12134 12489 1 +a 12135 12488 1 +a 12136 12487 1 +a 12137 12486 1 +a 12138 12485 1 +a 12139 12484 1 +a 12140 12483 1 +a 12141 12482 1 +a 12142 12481 1 +a 12143 12480 1 +a 12144 12479 1 +a 12145 12478 1 +a 12146 12477 1 +a 12147 12476 1 +a 12148 12475 1 +a 12149 12474 1 +a 12150 12473 1 +a 12151 12472 1 +a 12152 12471 1 +a 12153 12470 1 +a 12154 12469 1 +a 12155 12468 1 +a 12156 12467 1 +a 12157 12466 1 +a 12158 12465 1 +a 12159 12464 1 +a 12160 12463 1 +a 12161 12462 1 +a 12162 12461 1 +a 12163 12460 1 +a 12164 12459 1 +a 12165 12458 1 +a 12166 12457 1 +a 12167 12456 1 +a 12168 12455 1 +a 12169 12454 1 +a 12170 12453 1 +a 12171 12452 1 +a 12172 12451 1 +a 12173 12450 1 +a 12174 12449 1 +a 12175 12448 1 +a 12176 12447 1 +a 12177 12446 1 +a 12178 12445 1 +a 12179 12444 1 +a 12180 12443 1 +a 12181 12442 1 +a 12182 12441 1 +a 12183 12440 1 +a 12184 12439 1 +a 12185 12438 1 +a 12186 12437 1 +a 12187 12436 1 +a 12188 12435 1 +a 12189 12434 1 +a 12190 12433 1 +a 12191 12432 1 +a 12192 12431 1 +a 12193 12430 1 +a 12194 12429 1 +a 12195 12428 1 +a 12196 12427 1 +a 12197 12426 1 +a 12198 12425 1 +a 12199 12424 1 +a 12200 12423 1 +a 12201 12422 1 +a 12202 12421 1 +a 12203 12420 1 +a 12204 12419 1 +a 12205 12418 1 +a 12206 12417 1 +a 12207 12416 1 +a 12208 12415 1 +a 12209 12414 1 +a 12210 12413 1 +a 12211 12412 1 +a 12212 12411 1 +a 12213 12410 1 +a 12214 12409 1 +a 12215 12408 1 +a 12216 12407 1 +a 12217 12406 1 +a 12218 12405 1 +a 12219 12404 1 +a 12220 12403 1 +a 12221 12402 1 +a 12222 12401 1 +a 12223 12400 1 +a 12224 12399 1 +a 12225 12398 1 +a 12226 12397 1 +a 12227 12396 1 +a 12228 12395 1 +a 12229 12394 1 +a 12230 12393 1 +a 12231 12392 1 +a 12232 12391 1 +a 12233 12390 1 +a 12234 12389 1 +a 12235 12388 1 +a 12236 12387 1 +a 12237 12386 1 +a 12238 12385 1 +a 12239 12384 1 +a 12240 12383 1 +a 12241 12382 1 +a 12242 12381 1 +a 12243 12380 1 +a 12244 12379 1 +a 12245 12378 1 +a 12246 12377 1 +a 12247 12376 1 +a 12248 12375 1 +a 12249 12374 1 +a 12250 12373 1 +a 12251 12372 1 +a 12252 12371 1 +a 12253 12370 1 +a 12254 12369 1 +a 12255 12368 1 +a 12256 12367 1 +a 12257 12366 1 +a 12258 12365 1 +a 12259 12364 1 +a 12260 12363 1 +a 12261 12362 1 +a 12262 12361 1 +a 12263 12360 1 +a 12264 12359 1 +a 12265 12358 1 +a 12266 12357 1 +a 12267 12356 1 +a 12268 12355 1 +a 12269 12354 1 +a 12270 12353 1 +a 12271 12352 1 +a 12272 12351 1 +a 12273 12350 1 +a 12274 12349 1 +a 12275 12348 1 +a 12276 12347 1 +a 12277 12346 1 +a 12278 12345 1 +a 12279 12344 1 +a 12280 12343 1 +a 12281 12342 1 +a 12282 12341 1 +a 12283 12340 1 +a 12284 12339 1 +a 12285 12338 1 +a 12286 12337 1 +a 12287 12336 1 +a 12288 12335 1 +a 12289 12334 1 +a 12290 12333 1 +a 12291 12332 1 +a 12292 12331 1 +a 12293 12330 1 +a 12294 12329 1 +a 12295 12328 1 +a 12296 12327 1 +a 12297 12326 1 +a 12298 12325 1 +a 12299 12324 1 +a 12300 12323 1 +a 12301 12322 1 +a 12302 12321 1 +a 12303 12320 1 +a 12304 12319 1 +a 12305 12318 1 +a 12306 12317 1 +a 12307 12316 1 +a 12308 12315 1 +a 12309 12314 1 +a 12310 12313 1 +a 1 3 1000000 +a 1 8209 1000000 +a 8208 2 1000000 +a 16414 2 1000000 diff --git a/tests/unit/all_almost_e.c b/tests/unit/all_almost_e.c new file mode 100644 index 0000000..f90bf97 --- /dev/null +++ b/tests/unit/all_almost_e.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_matrix_t rm1, rm2; + igraph_matrix_complex_t cm1, cm2; + const igraph_int_t nrow = 50, ncol = 60; + + igraph_rng_seed(igraph_rng_default(), 97); + + /* Real matrices */ + + igraph_matrix_init(&rm1, nrow, ncol); + for (igraph_int_t i=0; i < nrow; i++) { + for (igraph_int_t j=0; j < ncol; j++) { + MATRIX(rm1, i, j) = RNG_UNIF(0.0, 3.0); + } + } + + igraph_matrix_init_copy(&rm2, &rm1); + for (igraph_int_t i=0; i < nrow; i++) { + for (igraph_int_t j=0; j < ncol; j++) { + MATRIX(rm2, i, j) = pow(MATRIX(rm2, i, j), 1 / 7.0); + MATRIX(rm2, i, j) = pow(MATRIX(rm2, i, j), 7.0); + } + } + + IGRAPH_ASSERT(igraph_matrix_all_almost_e(&rm1, &rm2, 4*DBL_EPSILON)); + MATRIX(rm2, 0, 0) *= 2; + IGRAPH_ASSERT(! igraph_matrix_all_almost_e(&rm1, &rm2, 4*DBL_EPSILON)); + + igraph_matrix_destroy(&rm2); + igraph_matrix_destroy(&rm1); + + /* Complex matrices */ + + igraph_matrix_complex_init(&cm1, nrow, ncol); + + for (igraph_int_t i=0; i < nrow; i++) { + for (igraph_int_t j=0; j < ncol; j++) { + IGRAPH_REAL(MATRIX(cm1, i, j)) = RNG_NORMAL(0,1); + IGRAPH_IMAG(MATRIX(cm1, i, j)) = RNG_NORMAL(0,1); + } + } + + + igraph_matrix_complex_init_copy(&cm2, &cm1); + for (igraph_int_t i=0; i < nrow; i++) { + for (igraph_int_t j=0; j < ncol; j++) { + MATRIX(cm2, i, j) = igraph_complex_pow_real(MATRIX(cm2, i, j), 1 / 7.0); + MATRIX(cm2, i, j) = igraph_complex_pow_real(MATRIX(cm2, i, j), 7.0); + } + } + + IGRAPH_ASSERT(igraph_matrix_complex_all_almost_e(&cm1, &cm2, 8*DBL_EPSILON)); + MATRIX(cm2, 0, 0) = igraph_complex_mul(MATRIX(cm2, 0, 0), MATRIX(cm2, 1, 1)); + IGRAPH_ASSERT(! igraph_matrix_complex_all_almost_e(&cm1, &cm2, 8*DBL_EPSILON)); + + igraph_matrix_complex_destroy(&cm2); + igraph_matrix_complex_destroy(&cm1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/all_shortest_paths.c b/tests/unit/all_shortest_paths.c new file mode 100644 index 0000000..436419b --- /dev/null +++ b/tests/unit/all_shortest_paths.c @@ -0,0 +1,162 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t paths, paths_edge; + igraph_vector_int_t nrgeo; + igraph_vector_t weights; + igraph_int_t from, to; + + igraph_vector_int_list_init(&paths, 0); + igraph_vector_int_list_init(&paths_edge, 0); + igraph_vector_int_init(&nrgeo, 0); + + igraph_vector_init(&weights, 0); + + /* Note on the output: + * get_all_shortest_paths functions sort their output based on the + * last vertex only. Thus the ordering is not fully defined. + * + * This test does not currently canonicalize (i.e. sort) + * the result before printing it. + */ + + printf("Singleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + + from = 0; to = 0; + + igraph_get_all_shortest_paths(&graph, NULL, &paths, NULL, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + printf("\nSingleton graph, weighted\n"); + igraph_vector_resize(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1); + igraph_get_all_shortest_paths(&graph, &weights, &paths, NULL, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + igraph_destroy(&graph); + + printf("\nNo paths\n"); + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + + from = 0; to = 1; + + igraph_get_all_shortest_paths(&graph, NULL, &paths, &paths_edge, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + printf("Edge paths:\n"); + print_vector_int_list(&paths_edge); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + igraph_destroy(&graph); + + /* This graph has multi-edges (which induce multiple paths of the + * same length) as well as more paths of the same length between + * vertices 0 and 4. */ + igraph_small(&graph, 0, IGRAPH_ADJ_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 0, 1, 2, 5, 4, 5, 1, 6, 6, 7, 3, 7, + -1); + + from = 0; to = 4; + + printf("\nUnweighted\n"); + igraph_get_all_shortest_paths(&graph, NULL, &paths, &paths_edge, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + printf("Edge paths:\n"); + print_vector_int_list(&paths_edge); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + printf("\nWeighted, uniform weights\n"); + igraph_vector_resize(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.5); /* constant weights */ + + igraph_get_all_shortest_paths(&graph, &weights, &paths, &paths_edge, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + printf("Edge paths:\n"); + print_vector_int_list(&paths_edge); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + printf("\nWeighted, multiple weighted shortest paths\n"); + VECTOR(weights)[1] = 3.0; /* create path with one more hop, but equal weighted length */ + VECTOR(weights)[4] = 2.0; /* break symmetry on pair of parallel edges */ + + igraph_get_all_shortest_paths(&graph, &weights, &paths, &paths_edge, &nrgeo, from, igraph_vss_1(to), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + printf("Edge paths:\n"); + print_vector_int_list(&paths_edge); + IGRAPH_ASSERT(igraph_vector_int_list_size(&paths) == VECTOR(nrgeo)[to]); + + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Graph is from https://github.com/igraph/rigraph/issues/314 */ + printf("\nWeighted, multiple weighted shortest paths, testing tolerances\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,3, 1,2, 2,5, 2,3, 2,4, 3,5, 5,6, 6,7, 1,8, 8,9, 4,10, 6,11, 8,12, 8,13, 4,14, 7,15, + -1); + + igraph_real_t weights_raw[] = { 1.9617537, 0.9060834, 2.2165446, 1.6251956, + 2.4473929, 0.5913490, 8.7093236, 2.8387330, + 6.1225042, 20.7217776, 6.8027218, 16.3147479, + 5.2605598, 6.6816853, 4.9482123, 1.8989790 }; + + /* Choose carefully: If not using tolerances, the result would be incorrect + * for starting vertices 5 and 6, but not for all other starting vertices. */ + from = 6; + printf("From: %" IGRAPH_PRId ", to: all.\n", from); + + weights = igraph_vector_view(weights_raw, sizeof(weights_raw) / sizeof(igraph_real_t)); + igraph_get_all_shortest_paths(&graph, &weights, &paths, &paths_edge, &nrgeo, from, igraph_vss_all(), IGRAPH_ALL); + + printf("Vertex paths:\n"); + print_vector_int_list(&paths); + printf("Edge paths:\n"); + print_vector_int_list(&paths_edge); + + printf("nrgeo: "); + print_vector_int(&nrgeo); + + igraph_vector_int_list_destroy(&paths); + igraph_vector_int_list_destroy(&paths_edge); + + igraph_vector_int_destroy(&nrgeo); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/all_shortest_paths.out b/tests/unit/all_shortest_paths.out new file mode 100644 index 0000000..1ff23bc --- /dev/null +++ b/tests/unit/all_shortest_paths.out @@ -0,0 +1,125 @@ +Singleton graph +Vertex paths: +{ + 0: ( 0 ) +} + +Singleton graph, weighted +Vertex paths: +{ + 0: ( 0 ) +} + +No paths +Vertex paths: +{ +} +Edge paths: +{ +} + +Unweighted +Vertex paths: +{ + 0: ( 0 1 2 5 4 ) + 1: ( 0 1 2 5 4 ) + 2: ( 0 1 2 3 4 ) + 3: ( 0 1 2 3 4 ) +} +Edge paths: +{ + 0: ( 4 1 5 6 ) + 1: ( 0 1 5 6 ) + 2: ( 4 1 2 3 ) + 3: ( 0 1 2 3 ) +} + +Weighted, uniform weights +Vertex paths: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 1 2 3 4 ) + 2: ( 0 1 2 5 4 ) + 3: ( 0 1 2 5 4 ) +} +Edge paths: +{ + 0: ( 0 1 2 3 ) + 1: ( 4 1 2 3 ) + 2: ( 0 1 5 6 ) + 3: ( 4 1 5 6 ) +} + +Weighted, multiple weighted shortest paths +Vertex paths: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 1 6 7 3 4 ) + 2: ( 0 1 2 5 4 ) +} +Edge paths: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 7 8 9 3 ) + 2: ( 0 1 5 6 ) +} + +Weighted, multiple weighted shortest paths, testing tolerances +From: 6, to: all. +Vertex paths: +{ + 0: ( 6 5 3 0 ) + 1: ( 6 5 3 2 1 ) + 2: ( 6 5 2 1 ) + 3: ( 6 5 3 2 ) + 4: ( 6 5 2 ) + 5: ( 6 5 3 ) + 6: ( 6 5 3 2 4 ) + 7: ( 6 5 2 4 ) + 8: ( 6 5 ) + 9: ( 6 ) + 10: ( 6 7 ) + 11: ( 6 5 3 2 1 8 ) + 12: ( 6 5 2 1 8 ) + 13: ( 6 5 3 2 1 8 9 ) + 14: ( 6 5 2 1 8 9 ) + 15: ( 6 5 3 2 4 10 ) + 16: ( 6 5 2 4 10 ) + 17: ( 6 11 ) + 18: ( 6 5 3 2 1 8 12 ) + 19: ( 6 5 2 1 8 12 ) + 20: ( 6 5 3 2 1 8 13 ) + 21: ( 6 5 2 1 8 13 ) + 22: ( 6 5 3 2 4 14 ) + 23: ( 6 5 2 4 14 ) + 24: ( 6 7 15 ) +} +Edge paths: +{ + 0: ( 6 5 0 ) + 1: ( 6 5 3 1 ) + 2: ( 6 2 1 ) + 3: ( 6 5 3 ) + 4: ( 6 2 ) + 5: ( 6 5 ) + 6: ( 6 5 3 4 ) + 7: ( 6 2 4 ) + 8: ( 6 ) + 9: ( ) + 10: ( 7 ) + 11: ( 6 5 3 1 8 ) + 12: ( 6 2 1 8 ) + 13: ( 6 5 3 1 8 9 ) + 14: ( 6 2 1 8 9 ) + 15: ( 6 5 3 4 10 ) + 16: ( 6 2 4 10 ) + 17: ( 11 ) + 18: ( 6 5 3 1 8 12 ) + 19: ( 6 2 1 8 12 ) + 20: ( 6 5 3 1 8 13 ) + 21: ( 6 2 1 8 13 ) + 22: ( 6 5 3 4 14 ) + 23: ( 6 2 4 14 ) + 24: ( 7 15 ) +} +nrgeo: ( 1 2 2 1 2 1 1 1 2 2 2 1 2 2 2 1 ) diff --git a/tests/unit/assortativity.c b/tests/unit/assortativity.c new file mode 100644 index 0000000..5204ce5 --- /dev/null +++ b/tests/unit/assortativity.c @@ -0,0 +1,357 @@ +/* + igraph library. + Copyright (C) 2009-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void assortativity_unnormalized(const igraph_t *graph, igraph_real_t *res, igraph_bool_t directed) { + if (! igraph_is_directed(graph)) { + directed = 0; + } + + if (directed) { + igraph_vector_t outdeg, indeg; + igraph_vector_init(&outdeg, 0); + igraph_vector_init(&indeg, 0); + igraph_strength(graph, &outdeg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, NULL); + igraph_strength(graph, &indeg, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, NULL); + igraph_assortativity(graph, NULL, &outdeg, &indeg, res, directed, false); + igraph_vector_destroy(&outdeg); + igraph_vector_destroy(&indeg); + } else { + igraph_vector_t deg; + igraph_vector_init(°, 0); + igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, NULL); + igraph_assortativity(graph, NULL, °, NULL, res, directed, false); + igraph_vector_destroy(°); + } +} + +int main(void) { + + igraph_t g; + igraph_real_t assort, assort2, assort_unnorm, modularity; + igraph_vector_t values; + igraph_vector_int_t types; + + /* Assortativity based on vertex categories */ + + igraph_vector_int_init(&types, 0); + + igraph_vector_init(&values, 0); + + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Null graph nominal assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Null graph value assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_destroy(&g); + + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + + igraph_vector_int_resize(&types, 1); + VECTOR(types)[0] = 0; + + igraph_vector_resize(&values, 1); + VECTOR(values)[0] = 0; + + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Singleton graph nominal assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Singleton graph nominal assortativity, unnormalized: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Singleton graph value assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Singleton graph value assortativity, unnormalized: "); + igraph_real_printf(assort); + printf("\n"); + + igraph_add_edge(&g, 0, 0); + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Singleton graph with loop, nominal assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Singleton graph with loop, nominal assortativity, unnormalized: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Singleton graph with loop, value assortativity: "); + igraph_real_printf(assort); + printf("\n"); + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Singleton graph with loop, value assortativity, unnormalized: "); + igraph_real_printf(assort); + printf("\n"); + + igraph_destroy(&g); + igraph_vector_destroy(&values); + + printf("\n"); + + igraph_famous(&g, "zachary"); + + igraph_degree(&g, &types, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Karate club, normalized assortativity based on degree categories: %g\n", assort); + + igraph_assortativity_nominal(&g, NULL, &types, &assort_unnorm, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Karate club, non-normalized assortativity based on degree categories: %g\n", assort_unnorm); + + /* unnormalized assortativity based on categories is the same as modularity */ + igraph_modularity(&g, &types, NULL, 1, IGRAPH_UNDIRECTED, &modularity); + IGRAPH_ASSERT(igraph_almost_equals(assort_unnorm, modularity, 1e-15)); + + igraph_destroy(&g); + igraph_vector_int_destroy(&types); + + /*--------------------------------------*/ + /* Assortativity based on vertex values */ + + igraph_famous(&g, "zachary"); + + igraph_vector_init_range(&values, 0, igraph_vcount(&g)); + + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ true); + printf("Assortativity based on values: %g\n", assort); + + /* Assortativity is a Pearson correlation, thus it must be invariant to + * a constant shift in the values. */ + igraph_vector_add_constant(&values, -5); + igraph_assortativity(&g, NULL, &values, NULL, &assort2, IGRAPH_UNDIRECTED, /* normalized */ true); + IGRAPH_ASSERT(igraph_almost_equals(assort, assort2, 1e-15)); + + igraph_assortativity(&g, NULL, &values, NULL, &assort, IGRAPH_UNDIRECTED, /* normalized */ false); + printf("Assortativity based on values, unnormalized: %g\n", assort); + + /* Assortativity is a Pearson correlation, thus it must be invariant to + * a constant shift in the values. */ + igraph_vector_add_constant(&values, -5); + igraph_assortativity(&g, NULL, &values, NULL, &assort2, IGRAPH_UNDIRECTED, /* normalized */ false); + IGRAPH_ASSERT(igraph_almost_equals(assort, assort2, 1e-15)); + + igraph_vector_destroy(&values); + igraph_destroy(&g); + + /*--------------------------------*/ + /* Assortativity based on degrees */ + + /* Normalized case */ + printf("\nDegree assortativity, NORMALIZED\n"); + + igraph_famous(&g, "zachary"); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity: %g\n", assort); + + igraph_destroy(&g); + + /* Directed graph */ + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, -1); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed: %g\n", assort); + + igraph_destroy(&g); + + /* Verify handling of self-loops */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 0,3, 3,3, -1); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, undirected, with self-loop: %g\n", assort); + + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, 2,2, -1); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed, with self-loop: %g\n", assort); + + igraph_destroy(&g); + + /* Verify handling of multi-edges */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 0,3, 1,2, -1); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, undirected, with multi-edges: %g\n", assort); + + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, 0,2, -1); + + igraph_assortativity_degree(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed, with multi-edges: %g\n", assort); + + igraph_destroy(&g); + + /* Unnormalized case */ + printf("\nDegree assortativity, UNNORMALIZED\n"); + + igraph_famous(&g, "zachary"); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity: %g\n", assort); + + igraph_destroy(&g); + + /* Directed graph */ + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, -1); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed: %g\n", assort); + + igraph_destroy(&g); + + /* Verify handling of self-loops */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 0,3, 3,3, -1); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, undirected, with self-loop: %g\n", assort); + + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, 2,2, -1); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed, with self-loop: %g\n", assort); + + igraph_destroy(&g); + + /* Verify handling of multi-edges */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 0,3, 1,2, -1); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, undirected, with multi-edges: %g\n", assort); + + igraph_destroy(&g); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 0,3, 3,2, 0,2, -1); + + assortativity_unnormalized(&g, &assort, /*directed=*/ 1); + printf("Degree assortativity, directed, with multi-edges: %g\n", assort); + + igraph_destroy(&g); + + /*------------------*/ + /* Football network */ + + igraph_int_t football_types[] = { + 7, 0, 2, 3, 7, 3, 2, 8, 8, 7, 3, 10, 6, 2, 6, 2, 7, 9, 6, 1, 9, 8, 8, 7, 10, 0, 6, 9, + 11, 1, 1, 6, 2, 0, 6, 1, 5, 0, 6, 2, 3, 7, 5, 6, 4, 0, 11, 2, 4, 11, 10, 8, 3, 11, 6, + 1, 9, 4, 11, 10, 2, 6, 9, 10, 2, 9, 4, 11, 8, 10, 9, 6, 3, 11, 3, 4, 9, 8, 8, 1, 5, 3, + 5, 11, 3, 6, 4, 9, 11, 0, 5, 4, 4, 7, 1, 9, 9, 10, 3, 6, 2, 1, 3, 0, 7, 0, 2, 3, 8, 0, + 4, 8, 4, 9, 11 + }; + + printf("\nFootball network: "); + igraph_small(&g, sizeof(football_types) / sizeof(football_types[0]), + IGRAPH_UNDIRECTED, + 0, 1, 2, 3, 0, 4, 4, 5, 3, 5, 2, 6, 6, 7, 7, 8, 8, 9, 0, 9, 4, 9, 5, 10, 10, 11, 5, 11, + 3, 11, 12, 13, 2, 13, 2, 14, 12, 14, 14, 15, 13, 15, 2, 15, 4, 16, 9, 16, 0, 16, + 16, 17, 12, 17, 12, 18, 18, 19, 17, 20, 20, 21, 8, 21, 7, 21, 9, 22, 7, 22, 21, + 22, 8, 22, 22, 23, 9, 23, 4, 23, 16, 23, 0, 23, 11, 24, 24, 25, 1, 25, 3, 26, 12, + 26, 14, 26, 26, 27, 17, 27, 1, 27, 17, 27, 4, 28, 11, 28, 24, 28, 19, 29, 29, + 30, 19, 30, 18, 31, 31, 32, 21, 32, 15, 32, 13, 32, 6, 32, 0, 33, 1, 33, 25, 33, + 19, 33, 31, 34, 26, 34, 12, 34, 18, 34, 34, 35, 0, 35, 29, 35, 19, 35, 30, 35, + 18, 36, 12, 36, 20, 36, 19, 36, 36, 37, 1, 37, 25, 37, 33, 37, 18, 38, 16, 38, + 28, 38, 26, 38, 14, 38, 12, 38, 38, 39, 6, 39, 32, 39, 13, 39, 15, 39, 7, 40, 3, + 40, 40, 41, 8, 41, 4, 41, 23, 41, 9, 41, 0, 41, 16, 41, 34, 42, 29, 42, 18, 42, + 26, 42, 42, 43, 36, 43, 26, 43, 31, 43, 38, 43, 12, 43, 14, 43, 19, 44, 35, 44, + 30, 44, 44, 45, 13, 45, 33, 45, 1, 45, 37, 45, 25, 45, 21, 46, 46, 47, 22, 47, + 6, 47, 15, 47, 2, 47, 39, 47, 32, 47, 44, 48, 48, 49, 32, 49, 46, 49, 30, 50, + 24, 50, 11, 50, 28, 50, 50, 51, 40, 51, 8, 51, 22, 51, 21, 51, 3, 52, 40, 52, 5, + 52, 52, 53, 25, 53, 48, 53, 49, 53, 46, 53, 39, 54, 31, 54, 38, 54, 14, 54, 34, + 54, 18, 54, 54, 55, 31, 55, 6, 55, 35, 55, 29, 55, 19, 55, 30, 55, 27, 56, 56, + 57, 1, 57, 42, 57, 44, 57, 48, 57, 3, 58, 6, 58, 17, 58, 36, 58, 36, 59, 58, 59, + 59, 60, 10, 60, 39, 60, 6, 60, 47, 60, 13, 60, 15, 60, 2, 60, 43, 61, 47, 61, + 54, 61, 18, 61, 26, 61, 31, 61, 34, 61, 61, 62, 20, 62, 45, 62, 17, 62, 27, 62, + 56, 62, 27, 63, 58, 63, 59, 63, 42, 63, 63, 64, 9, 64, 32, 64, 60, 64, 2, 64, 6, + 64, 47, 64, 13, 64, 0, 65, 27, 65, 17, 65, 63, 65, 56, 65, 20, 65, 65, 66, 59, + 66, 24, 66, 44, 66, 48, 66, 16, 67, 41, 67, 46, 67, 53, 67, 49, 67, 67, 68, 15, + 68, 50, 68, 21, 68, 51, 68, 7, 68, 22, 68, 8, 68, 4, 69, 24, 69, 28, 69, 50, 69, + 11, 69, 69, 70, 43, 70, 65, 70, 20, 70, 56, 70, 62, 70, 27, 70, 60, 71, 18, 71, + 14, 71, 34, 71, 54, 71, 38, 71, 61, 71, 31, 71, 71, 72, 2, 72, 10, 72, 3, 72, + 40, 72, 52, 72, 7, 73, 49, 73, 53, 73, 67, 73, 46, 73, 73, 74, 2, 74, 72, 74, 5, + 74, 10, 74, 52, 74, 3, 74, 40, 74, 20, 75, 66, 75, 48, 75, 57, 75, 44, 75, 75, + 76, 27, 76, 59, 76, 20, 76, 70, 76, 66, 76, 56, 76, 62, 76, 73, 77, 22, 77, 7, + 77, 51, 77, 21, 77, 8, 77, 77, 78, 23, 78, 50, 78, 28, 78, 22, 78, 8, 78, 68, + 78, 7, 78, 51, 78, 31, 79, 43, 79, 30, 79, 19, 79, 29, 79, 35, 79, 55, 79, 79, + 80, 37, 80, 29, 80, 16, 81, 5, 81, 40, 81, 10, 81, 72, 81, 3, 81, 81, 82, 74, + 82, 39, 82, 77, 82, 80, 82, 30, 82, 29, 82, 7, 82, 53, 83, 81, 83, 69, 83, 73, + 83, 46, 83, 67, 83, 49, 83, 83, 84, 24, 84, 49, 84, 52, 84, 3, 84, 74, 84, 10, + 84, 81, 84, 5, 84, 3, 84, 6, 85, 14, 85, 38, 85, 43, 85, 80, 85, 12, 85, 26, 85, + 31, 85, 44, 86, 53, 86, 75, 86, 57, 86, 48, 86, 80, 86, 66, 86, 86, 87, 17, 87, + 62, 87, 56, 87, 24, 87, 20, 87, 65, 87, 49, 88, 58, 88, 83, 88, 69, 88, 46, 88, + 53, 88, 73, 88, 67, 88, 88, 89, 1, 89, 37, 89, 25, 89, 33, 89, 55, 89, 45, 89, + 5, 90, 8, 90, 23, 90, 0, 90, 11, 90, 50, 90, 24, 90, 69, 90, 28, 90, 29, 91, 48, + 91, 66, 91, 69, 91, 44, 91, 86, 91, 57, 91, 80, 91, 91, 92, 35, 92, 15, 92, 86, + 92, 48, 92, 57, 92, 61, 92, 66, 92, 75, 92, 0, 93, 23, 93, 80, 93, 16, 93, 4, + 93, 82, 93, 91, 93, 41, 93, 9, 93, 34, 94, 19, 94, 55, 94, 79, 94, 80, 94, 29, + 94, 30, 94, 82, 94, 35, 94, 70, 95, 69, 95, 76, 95, 62, 95, 56, 95, 27, 95, 17, + 95, 87, 95, 37, 95, 48, 96, 17, 96, 76, 96, 27, 96, 56, 96, 65, 96, 20, 96, 87, + 96, 5, 97, 86, 97, 58, 97, 11, 97, 59, 97, 63, 97, 97, 98, 77, 98, 48, 98, 84, + 98, 40, 98, 10, 98, 5, 98, 52, 98, 81, 98, 89, 99, 34, 99, 14, 99, 85, 99, 54, + 99, 18, 99, 31, 99, 61, 99, 71, 99, 14, 99, 99, 100, 82, 100, 13, 100, 2, 100, + 15, 100, 32, 100, 64, 100, 47, 100, 39, 100, 6, 100, 51, 101, 30, 101, 94, + 101, 1, 101, 79, 101, 58, 101, 19, 101, 55, 101, 35, 101, 29, 101, 100, 102, + 74, 102, 52, 102, 98, 102, 72, 102, 40, 102, 10, 102, 3, 102, 102, 103, 33, + 103, 45, 103, 25, 103, 89, 103, 37, 103, 1, 103, 70, 103, 72, 104, 11, 104, + 0, 104, 93, 104, 67, 104, 41, 104, 16, 104, 87, 104, 23, 104, 4, 104, 9, 104, + 89, 105, 103, 105, 33, 105, 62, 105, 37, 105, 45, 105, 1, 105, 80, 105, 25, + 105, 25, 106, 56, 106, 92, 106, 2, 106, 13, 106, 32, 106, 60, 106, 6, 106, + 64, 106, 15, 106, 39, 106, 88, 107, 75, 107, 98, 107, 102, 107, 72, 107, 40, + 107, 81, 107, 5, 107, 10, 107, 84, 107, 4, 108, 9, 108, 7, 108, 51, 108, 77, + 108, 21, 108, 78, 108, 22, 108, 68, 108, 79, 109, 30, 109, 63, 109, 1, 109, + 33, 109, 103, 109, 105, 109, 45, 109, 25, 109, 89, 109, 37, 109, 67, 110, + 13, 110, 24, 110, 80, 110, 88, 110, 49, 110, 73, 110, 46, 110, 83, 110, 53, + 110, 23, 111, 64, 111, 46, 111, 78, 111, 8, 111, 21, 111, 51, 111, 7, 111, + 108, 111, 68, 111, 77, 111, 52, 112, 96, 112, 97, 112, 57, 112, 66, 112, 63, + 112, 44, 112, 92, 112, 75, 112, 91, 112, 28, 113, 20, 113, 95, 113, 59, 113, + 70, 113, 17, 113, 87, 113, 76, 113, 65, 113, 96, 113, 83, 114, 88, 114, 110, + 114, 53, 114, 49, 114, 73, 114, 46, 114, 67, 114, 58, 114, 15, 114, 104, 114, + -1); + igraph_simplify(&g, /*remove_multiple=*/ true, /*remove_loops=*/ true, /*edge_comb=*/ NULL); + types = igraph_vector_int_view(football_types, sizeof(football_types) / sizeof(football_types[0])); + igraph_assortativity_nominal(&g, NULL, &types, &assort, IGRAPH_UNDIRECTED, /*normalized=*/ true); + printf("%g\n", assort); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/assortativity.out b/tests/unit/assortativity.out new file mode 100644 index 0000000..a48d382 --- /dev/null +++ b/tests/unit/assortativity.out @@ -0,0 +1,33 @@ +Null graph nominal assortativity: NaN +Null graph value assortativity: NaN +Singleton graph nominal assortativity: NaN +Singleton graph nominal assortativity, unnormalized: NaN +Singleton graph value assortativity: NaN +Singleton graph value assortativity, unnormalized: NaN +Singleton graph with loop, nominal assortativity: NaN +Singleton graph with loop, nominal assortativity, unnormalized: 0 +Singleton graph with loop, value assortativity: NaN +Singleton graph with loop, value assortativity, unnormalized: 0 + +Karate club, normalized assortativity based on degree categories: -0.077745 +Karate club, non-normalized assortativity based on degree categories: -0.0693623 +Assortativity based on values: 0.448049 +Assortativity based on values, unnormalized: 69.3478 + +Degree assortativity, NORMALIZED +Degree assortativity: -0.475613 +Degree assortativity, directed: -0.666667 +Degree assortativity, undirected, with self-loop: 0.166667 +Degree assortativity, directed, with self-loop: -0.707107 +Degree assortativity, undirected, with multi-edges: -0.111111 +Degree assortativity, directed, with multi-edges: -0.333333 + +Degree assortativity, UNNORMALIZED +Degree assortativity: -13.6943 +Degree assortativity, directed: -0.16 +Degree assortativity, undirected, with self-loop: 0.04 +Degree assortativity, directed, with self-loop: -0.333333 +Degree assortativity, undirected, with multi-edges: -0.04 +Degree assortativity, directed, with multi-edges: -0.333333 + +Football network: 0.607938 diff --git a/tests/unit/beta_skeletons.c b/tests/unit/beta_skeletons.c new file mode 100644 index 0000000..f1b44b9 --- /dev/null +++ b/tests/unit/beta_skeletons.c @@ -0,0 +1,182 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t graph, graph2; + igraph_bool_t is_same; + + igraph_real_t points[] = { + + 0.474217, 0.0314797, 0.208089, 0.439308, 0.967367, 0.530466, + 0.177005, 0.426713, 0.568462, 0.57507, 0.441834, 0.284514, 0.479224, + 0.817988, 0.720209, 0.225744, 0.204941, 0.44297, 0.285318, 0.912984, + 0.831097, 0.0176603, 0.827154, 0.472702, 0.173059, 0.561858, + 0.156276, 0.88019, 0.65935, 0.538207, 0.570379, 0.518081, 0.900553, + 0.656416, 0.726631, 0.863709, 0.380264, 0.287159, 0.31098, 0.230773, + 0.243089, 0.164584, 0.967974, 0.524992, 0.726605, 0.0724703, + 0.739752, 0.447069, 0.0443581, 0.444839 + }; + + igraph_real_t trig_lattice_points[] = {0.50000000000000000000000000000000000000000000000000, \ + 2.5980762113533159402911695122588085504142078807156, 0, \ + 1.7320508075688772935274463415058723669428052538104, \ + 1.0000000000000000000000000000000000000000000000000, \ + 1.7320508075688772935274463415058723669428052538104, \ + -0.50000000000000000000000000000000000000000000000000, \ + 0.86602540378443864676372317075293618347140262690519, \ + 0.50000000000000000000000000000000000000000000000000, \ + 0.86602540378443864676372317075293618347140262690519, \ + 1.5000000000000000000000000000000000000000000000000, \ + 0.86602540378443864676372317075293618347140262690519, \ + -1.0000000000000000000000000000000000000000000000000, 0, 0, 0, \ + 1.0000000000000000000000000000000000000000000000000, 0, \ + 2.0000000000000000000000000000000000000000000000000, 0 + }; + igraph_real_t rotated_square_lattice_points[] = { + -0.3594924531727418, 1.3677591805986329, -1.2231182700584293, + 1.8718925443115786, -2.0867440869441167, 2.3760259080245243, + -2.950369903829804, 2.8801592717374698, 0.14464091054020378, + 2.2313849974843203, -0.7189849063454836, 2.7355183611972658, + -1.582610723231171, 3.2396517249102117, -2.4462365401168586, + 3.743785088623157, 0.6487742742531495, 3.0950108143700077, + -0.21485154263253792, 3.5991441780829536, -1.0784773595182253, + 4.103277541795899, -1.9421031764039127, 4.607410905508845, + 1.152907637966095, 3.958636631255695, 0.28928182108040756, + 4.462769994968641, -0.5743439958052798, 4.966903358681586, + -1.4379698126909672, 5.4710367223945315 + }; + + igraph_matrix_t point_mat, point_small_mat, point_singleton_mat, point_null_mat, point_3d_mat, trig_lattice, rot_square_lattice; + + igraph_matrix_init_array(&point_mat, points, 25, 2, false); + igraph_matrix_init_array(&point_small_mat, points, 2, 2, false); + igraph_matrix_init_array(&point_singleton_mat, points, 1, 2, false); + igraph_matrix_init_array(&point_null_mat, points, 0, 2, false); + + igraph_matrix_init_array(&rot_square_lattice, rotated_square_lattice_points, 16, 2, false); + igraph_matrix_init_array(&trig_lattice, trig_lattice_points, 10, 2, false); + igraph_matrix_init_array(&point_3d_mat, points, 10, 3, false); + + printf("Lune beta skeleton beta = 2, 25 points\n"); + igraph_lune_beta_skeleton(&graph, &point_mat, 2); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Gabriel graph, 25 points\n"); + igraph_lune_beta_skeleton(&graph, &point_mat, 1); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Beta = 0.5, 25 points\n"); + igraph_lune_beta_skeleton(&graph, &point_mat, 0.5); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Beta = 1.1, Circle based 25 points\n"); + igraph_circle_beta_skeleton(&graph, &point_mat, 1.1); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Beta = 1.1, Circle based 2 points\n"); + igraph_circle_beta_skeleton(&graph, &point_small_mat, 1.1); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Beta = 1.1, Circle based 1 point\n"); + igraph_circle_beta_skeleton(&graph, &point_singleton_mat, 1.1); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Beta = 1.1, Circle based 0 points\n"); + igraph_circle_beta_skeleton(&graph, &point_null_mat, 1.1); + print_graph_canon(&graph); + igraph_destroy(&graph); + + + printf("Relative neighborhood graph, 10 points 3d\n"); + igraph_lune_beta_skeleton(&graph, &point_3d_mat, 2); + print_graph_canon(&graph); + igraph_destroy(&graph); + + igraph_vector_t weights; + + + printf("Beta weighted gabriel graph, 2d 25 points cutoff = Infinity\n"); + igraph_vector_init(&weights, 0); + igraph_beta_weighted_gabriel_graph(&graph, &weights, &point_mat, IGRAPH_INFINITY); + igraph_lune_beta_skeleton(&graph2, &point_mat, 1); + igraph_is_same_graph(&graph, &graph2, &is_same); + igraph_destroy(&graph2); + igraph_destroy(&graph); + print_vector(&weights); + IGRAPH_ASSERT(is_same); + igraph_vector_destroy(&weights); + + printf("Beta weighted gabriel graph, 2d 25 points cutoff = 5\n"); + igraph_vector_init(&weights, 0); + igraph_beta_weighted_gabriel_graph(&graph, &weights, &point_mat, 5); + igraph_destroy(&graph); + print_vector(&weights); + igraph_vector_destroy(&weights); + + printf("Beta weighted gabriel graph, 3d 10 points cutoff = Infinity\n"); + igraph_vector_init(&weights, 0); + igraph_beta_weighted_gabriel_graph(&graph, &weights, &point_3d_mat, IGRAPH_INFINITY); + igraph_lune_beta_skeleton(&graph2, &point_3d_mat, 1); + igraph_is_same_graph(&graph, &graph2, &is_same); + igraph_destroy(&graph2); + igraph_destroy(&graph); + print_vector(&weights); + IGRAPH_ASSERT(is_same); + igraph_vector_destroy(&weights); + + printf("Beta weighted gabriel graph, 3d 10 points cutoff = 5\n"); + igraph_vector_init(&weights, 0); + igraph_beta_weighted_gabriel_graph(&graph, &weights, &point_3d_mat, 5); + igraph_destroy(&graph); + print_vector(&weights); + igraph_vector_destroy(&weights); + + printf("Relative neighborhood graph, triangular lattice\n"); + igraph_relative_neighborhood_graph(&graph, &trig_lattice); + print_graph_canon(&graph); + igraph_destroy(&graph); + printf("Lune beta skeleton beta = 2, triangular_lattice\n"); + igraph_lune_beta_skeleton(&graph, &trig_lattice, 2); + print_graph_canon(&graph); + igraph_destroy(&graph); + + printf("Gabriel graph of rotated square lattice\n"); + igraph_gabriel_graph(&graph, &rot_square_lattice); + print_graph_canon(&graph); + igraph_destroy(&graph); + + igraph_matrix_destroy(&rot_square_lattice); + igraph_matrix_destroy(&trig_lattice); + igraph_matrix_destroy(&point_3d_mat); + igraph_matrix_destroy(&point_mat); + igraph_matrix_destroy(&point_small_mat); + igraph_matrix_destroy(&point_singleton_mat); + igraph_matrix_destroy(&point_null_mat); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/beta_skeletons.out b/tests/unit/beta_skeletons.out new file mode 100644 index 0000000..68d7580 --- /dev/null +++ b/tests/unit/beta_skeletons.out @@ -0,0 +1,338 @@ +Lune beta skeleton beta = 2, 25 points +directed: false +vcount: 25 +edges: { +0 5 +0 22 +1 8 +1 18 +2 16 +2 21 +3 8 +3 24 +4 6 +4 15 +5 7 +5 15 +5 18 +6 9 +6 17 +7 22 +7 23 +8 12 +9 13 +10 22 +11 21 +11 23 +12 13 +14 15 +14 23 +16 17 +18 19 +19 20 +} +Gabriel graph, 25 points +directed: false +vcount: 25 +edges: { +0 5 +0 19 +0 20 +0 22 +1 8 +1 15 +1 18 +1 19 +2 16 +2 21 +3 8 +3 24 +4 6 +4 12 +4 14 +4 15 +4 17 +5 7 +5 15 +5 18 +6 9 +6 12 +6 17 +7 22 +7 23 +8 12 +9 13 +10 22 +11 16 +11 21 +11 23 +12 13 +12 24 +14 15 +14 17 +14 23 +16 17 +18 19 +19 20 +} +Beta = 0.5, 25 points +directed: false +vcount: 25 +edges: { +0 5 +0 7 +0 10 +0 14 +0 15 +0 18 +0 19 +0 20 +0 22 +0 23 +1 3 +1 4 +1 5 +1 6 +1 8 +1 9 +1 15 +1 17 +1 18 +1 19 +1 20 +1 23 +2 6 +2 7 +2 11 +2 14 +2 16 +2 21 +2 22 +3 5 +3 8 +3 12 +3 18 +3 19 +3 20 +3 24 +4 5 +4 6 +4 8 +4 9 +4 12 +4 13 +4 14 +4 15 +4 16 +4 17 +4 18 +5 6 +5 7 +5 9 +5 12 +5 13 +5 14 +5 15 +5 18 +5 19 +5 22 +5 23 +6 8 +6 9 +6 11 +6 12 +6 13 +6 14 +6 16 +6 17 +6 18 +6 19 +6 21 +7 10 +7 11 +7 12 +7 14 +7 15 +7 19 +7 20 +7 21 +7 22 +7 23 +8 9 +8 12 +8 15 +8 17 +8 20 +8 23 +8 24 +9 12 +9 13 +9 15 +9 17 +9 18 +9 19 +10 11 +10 21 +10 22 +10 23 +11 14 +11 15 +11 16 +11 17 +11 21 +11 23 +12 13 +12 15 +12 18 +12 24 +13 15 +13 18 +13 24 +14 15 +14 16 +14 17 +14 18 +14 21 +14 23 +15 17 +15 18 +15 23 +16 17 +16 23 +17 23 +18 19 +18 22 +18 23 +19 20 +19 22 +19 24 +20 22 +20 24 +21 22 +} +Beta = 1.1, Circle based 25 points +directed: false +vcount: 25 +edges: { +0 22 +1 8 +2 16 +2 21 +3 24 +4 6 +4 15 +5 7 +5 15 +5 18 +6 9 +6 17 +7 22 +7 23 +8 12 +9 13 +10 22 +11 23 +12 13 +14 23 +16 17 +18 19 +19 20 +} +Beta = 1.1, Circle based 2 points +directed: false +vcount: 2 +edges: { +0 1 +} +Beta = 1.1, Circle based 1 point +directed: false +vcount: 1 +edges: { +} +Beta = 1.1, Circle based 0 points +directed: false +vcount: 0 +edges: { +} +Relative neighborhood graph, 10 points 3d +directed: false +vcount: 10 +edges: { +0 3 +0 5 +1 4 +1 7 +2 4 +2 5 +2 8 +3 8 +3 9 +4 6 +4 9 +7 8 +} +Beta weighted gabriel graph, 2d 25 points cutoff = Infinity +( 2.13771 1.87896 1.30643 10.5357 Inf 1.04766 2.38206 1.72356 10.9882 41555.3 115.458 6.14845 4.87967 1.32814 1.42918 8.52199 1.20967 2.72446 3.66988 Inf 11.4047 1.33013 3.50412 36.5966 10.6973 4.82793 48.4413 628.646 1.11477 8.32087 Inf 4.28868 1.19886 3.33 1.31892 11.3292 3.3514 15.7597 29.7302 ) +Beta weighted gabriel graph, 2d 25 points cutoff = 5 +( 2.13771 1.87896 1.30643 Inf Inf 1.04766 2.38206 1.72356 Inf Inf Inf Inf 4.87967 1.32814 1.42918 Inf 1.20967 2.72446 3.66988 Inf Inf 1.33013 3.50412 Inf Inf 4.82793 Inf Inf 1.11477 Inf Inf 4.28868 1.19886 3.33 1.31892 Inf 3.3514 Inf Inf ) +Beta weighted gabriel graph, 3d 10 points cutoff = Infinity +( 2.29424 2.87805 1.51364 15.0303 1.01534 2.12088 1.05443 1.30234 2.07368 8.72711 1.0848 1.99727 2.0717 1.25544 1.77266 2.21701 4.16352 58.0727 4.91485 1.47137 1.89216 2.00274 ) +Beta weighted gabriel graph, 3d 10 points cutoff = 5 +( 2.29424 2.87805 1.51364 Inf 1.01534 2.12088 1.05443 1.30234 2.07368 Inf 1.0848 1.99727 2.0717 1.25544 1.77266 2.21701 4.16352 Inf 4.91485 1.47137 1.89216 2.00274 ) +Relative neighborhood graph, triangular lattice +directed: false +vcount: 10 +edges: { +0 1 +0 2 +1 2 +1 3 +1 4 +2 4 +2 5 +3 4 +3 6 +3 7 +4 5 +4 7 +4 8 +5 8 +5 9 +6 7 +7 8 +8 9 +} +Lune beta skeleton beta = 2, triangular_lattice +directed: false +vcount: 10 +edges: { +} +Gabriel graph of rotated square lattice +directed: false +vcount: 16 +edges: { +0 1 +0 4 +1 2 +1 5 +2 3 +2 6 +3 7 +4 5 +4 8 +5 6 +5 9 +6 7 +6 10 +7 11 +8 9 +8 12 +9 10 +9 13 +10 11 +10 14 +11 15 +12 13 +13 14 +14 15 +} diff --git a/tests/unit/bfs.c b/tests/unit/bfs.c new file mode 100644 index 0000000..5cd50b8 --- /dev/null +++ b/tests/unit/bfs.c @@ -0,0 +1,149 @@ +/* + igraph library. + Copyright (C) 2009-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +igraph_error_t bfs_callback(const igraph_t *graph, + igraph_int_t vid, + igraph_int_t pred, + igraph_int_t succ, + igraph_int_t rank, + igraph_int_t dist, + void *extra) { + IGRAPH_UNUSED(graph); + IGRAPH_UNUSED(pred); + IGRAPH_UNUSED(succ); + IGRAPH_UNUSED(rank); + IGRAPH_UNUSED(dist); + IGRAPH_UNUSED(extra); + printf(" %" IGRAPH_PRId, vid); + return IGRAPH_SUCCESS; +} + +int main(void) { + + igraph_t graph, ring; + igraph_vector_int_t restricted, order, rank, father, pred, succ, dist, roots; + igraph_int_t i; + + igraph_ring(&ring, 10, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_disjoint_union(&graph, &ring, &ring); + igraph_destroy(&ring); + + igraph_vector_int_init(&order, 0); + igraph_vector_int_init(&rank, 0); + igraph_vector_int_init(&father, 0); + igraph_vector_int_init(&pred, 0); + igraph_vector_int_init(&succ, 0); + igraph_vector_int_init(&dist, 0); + + igraph_bfs(&graph, /*root=*/0, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, /*restricted=*/ 0, + &order, &rank, &father, &pred, &succ, &dist, + /*callback=*/ 0, /*extra=*/ 0); + + print_vector_int(&order); + print_vector_int(&rank); + print_vector_int(&father); + print_vector_int(&pred); + print_vector_int(&succ); + print_vector_int(&dist); + + igraph_vector_int_destroy(&order); + igraph_vector_int_destroy(&rank); + igraph_vector_int_destroy(&father); + igraph_vector_int_destroy(&pred); + igraph_vector_int_destroy(&succ); + igraph_vector_int_destroy(&dist); + + /* Test the callback */ + + printf("("); + igraph_bfs(&graph, /*root=*/ 0, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, /*restricted=*/ 0, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + /* Test different roots */ + + printf("("); + igraph_bfs(&graph, /*root=*/ 2, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, /*restricted=*/ 0, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + /* Test restricted */ + + igraph_vector_int_init(&restricted, 0); + for (i = 5; i < igraph_vcount(&graph); i++) { + igraph_vector_int_push_back(&restricted, i); + } + printf("("); + igraph_bfs(&graph, /*root=*/ 5, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, &restricted, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + /* Root not in restricted set */ + + printf("("); + igraph_bfs(&graph, /*root=*/ 4, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 1, &restricted, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + printf("("); + igraph_bfs(&graph, /*root=*/ 3, /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 0, &restricted, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + /* Multiple root vertices */ + + igraph_vector_int_init(&roots, 3); + VECTOR(roots)[0] = 3; + VECTOR(roots)[1] = 4; + VECTOR(roots)[2] = 6; + printf("("); + igraph_bfs(&graph, /*root=*/ -1, &roots, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 0, &restricted, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + /* Empty root vertex vector */ + + igraph_vector_int_clear(&roots); + printf("("); + igraph_bfs(&graph, /*root=*/ -1, &roots, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 0, &restricted, + 0, 0, 0, 0, 0, 0, &bfs_callback, 0); + printf(" )\n"); + + igraph_vector_int_destroy(&roots); + igraph_vector_int_destroy(&restricted); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/bfs.out b/tests/unit/bfs.out new file mode 100644 index 0000000..2681672 --- /dev/null +++ b/tests/unit/bfs.out @@ -0,0 +1,13 @@ +( 0 1 9 2 8 3 7 4 6 5 10 11 19 12 18 13 17 14 16 15 ) +( 0 1 3 5 7 9 8 6 4 2 10 11 13 15 17 19 18 16 14 12 ) +( -1 0 1 2 3 4 7 8 9 0 -1 10 11 12 13 14 17 18 19 10 ) +( -1 0 9 8 7 6 4 3 2 1 -1 10 19 18 17 16 14 13 12 11 ) +( 1 9 8 7 6 -1 5 4 3 2 11 19 18 17 16 -1 15 14 13 12 ) +( 0 1 2 3 4 5 4 3 2 1 0 1 2 3 4 5 4 3 2 1 ) +( 0 1 9 2 8 3 7 4 6 5 10 11 19 12 18 13 17 14 16 15 ) +( 2 1 3 0 4 9 5 8 6 7 10 11 19 12 18 13 17 14 16 15 ) +( 5 6 7 8 9 10 11 19 12 18 13 17 14 16 15 ) +( 5 6 7 8 9 10 11 19 12 18 13 17 14 16 15 ) +( ) +( 6 5 7 8 9 ) +( ) diff --git a/tests/unit/bfs_simple.c b/tests/unit/bfs_simple.c new file mode 100644 index 0000000..cb1a1f5 --- /dev/null +++ b/tests/unit/bfs_simple.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2009-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t vids, layers, parents; + + igraph_vector_int_init(&vids, 0); + igraph_vector_int_init(&layers, 0); + igraph_vector_int_init(&parents, 0); + + /* Test a ring graph */ + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 0); + igraph_bfs_simple(&g, 0, IGRAPH_ALL, &vids, &layers, &parents); + print_vector_int(&vids); + print_vector_int(&layers); + print_vector_int(&parents); + igraph_destroy(&g); + + /* Test a tree graph */ + igraph_kary_tree(&g, 20, 2, IGRAPH_TREE_UNDIRECTED); + igraph_bfs_simple(&g, 0, IGRAPH_ALL, &vids, &layers, &parents); + print_vector_int(&vids); + print_vector_int(&layers); + print_vector_int(&parents); + igraph_destroy(&g); + + /* Test th same graph with all arguments as nulls to see if we tolerate that */ + igraph_kary_tree(&g, 20, 2, IGRAPH_TREE_UNDIRECTED); + igraph_bfs_simple(&g, 0, IGRAPH_ALL, 0, 0, 0); + igraph_destroy(&g); + + /* Also test the case when 'layers' is not null and 'vids' is null to ensure + * that we don't need 'vids' in the internal implementation to populate + * 'layers' */ + igraph_kary_tree(&g, 20, 2, IGRAPH_TREE_UNDIRECTED); + igraph_bfs_simple(&g, 0, IGRAPH_ALL, 0, &layers, 0); + print_vector_int(&layers); + igraph_destroy(&g); + + /* Test directed graph where not all nodes are reachable */ + igraph_kary_tree(&g, 20, 2, IGRAPH_TREE_OUT); + igraph_bfs_simple(&g, 7, IGRAPH_OUT, 0, &layers, &parents); + print_vector_int(&layers); + print_vector_int(&parents); + igraph_destroy(&g); + + igraph_vector_int_destroy(&vids); + igraph_vector_int_destroy(&layers); + igraph_vector_int_destroy(&parents); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/bfs_simple.out b/tests/unit/bfs_simple.out new file mode 100644 index 0000000..25ed9dd --- /dev/null +++ b/tests/unit/bfs_simple.out @@ -0,0 +1,9 @@ +( 0 1 2 3 4 5 6 7 8 9 ) +( 0 1 2 3 4 5 6 7 8 9 10 ) +( -1 0 1 2 3 4 5 6 7 8 ) +( 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ) +( 0 1 3 7 15 20 ) +( -1 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 ) +( 0 1 3 7 15 20 ) +( 0 1 3 ) +( -2 -2 -2 -2 -2 -2 -2 -1 -2 -2 -2 -2 -2 -2 -2 7 7 -2 -2 -2 ) diff --git a/tests/unit/bipartite.net b/tests/unit/bipartite.net new file mode 100644 index 0000000..2d4f2c9 --- /dev/null +++ b/tests/unit/bipartite.net @@ -0,0 +1,26 @@ +*Vertices 13 8 +1 "A" +2 "B" +3 "C" +4 "D" +5 "E" +6 "F" +7 "G" +8 "H" +9 "x-1" +10 "x-2" +11 "x-3" +12 "x-4" +13 "x-5" +*Edges +1 10 +1 13 +2 12 +3 10 +3 11 +4 11 +5 12 +5 13 +6 12 +8 11 +8 13 diff --git a/tests/unit/bitset.c b/tests/unit/bitset.c new file mode 100644 index 0000000..1777508 --- /dev/null +++ b/tests/unit/bitset.c @@ -0,0 +1,403 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_bitset_t v1, v2, v3; + igraph_int_t n; + + printf("Initialise empty bitset\n"); + n = 0; + igraph_bitset_init(&v1, n); + IGRAPH_ASSERT(igraph_bitset_size(&v1) == n); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Initialise bitset of length 32\n"); + n = 32; + igraph_bitset_init(&v1, n); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Initialise bitset of length 64\n"); + n = 64; + igraph_bitset_init(&v1, n); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Initialise bitset of length 75\n"); + n = 75; + igraph_bitset_init(&v1, n); + print_bitset(&v1); + IGRAPH_ASSERT(igraph_bitset_size(&v1) == n); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Test IGRAPH_BIT_SET\n"); + n = 35; + igraph_bitset_init(&v1, n); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 24); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 13); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 17); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 34); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 13); + print_bitset(&v1); + printf("\n"); + + printf("Test IGRAPH_BIT_CLEAR\n"); + IGRAPH_BIT_CLEAR(v1, 33); + print_bitset(&v1); + IGRAPH_BIT_CLEAR(v1, 34); + print_bitset(&v1); + IGRAPH_BIT_CLEAR(v1, 17); + print_bitset(&v1); + printf("\n"); + + printf("Test printing\n"); + igraph_bitset_print(&v1); + printf("\n\n"); + + printf("Test bitset copy constructor\n"); + igraph_bitset_init_copy(&v2, &v1); + print_bitset(&v2); + igraph_bitset_destroy(&v1); + igraph_bitset_destroy(&v2); + printf("\n"); + + printf("Test bitset resize\n"); + igraph_bitset_init(&v1, 0); + print_bitset(&v1); + IGRAPH_ASSERT(igraph_bitset_size(&v1) == 0); + igraph_bitset_resize(&v1, 10); + print_bitset(&v1); + IGRAPH_ASSERT(igraph_bitset_size(&v1) == 10); + IGRAPH_BIT_SET(v1, 3); + print_bitset(&v1); + igraph_bitset_resize(&v1, 64); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 63); + print_bitset(&v1); + igraph_bitset_resize(&v1, 70); + print_bitset(&v1); + IGRAPH_BIT_SET(v1, 64); + print_bitset(&v1); + igraph_bitset_resize(&v1, 64); + print_bitset(&v1); + igraph_bitset_resize(&v1, 63); + print_bitset(&v1); + IGRAPH_ASSERT(igraph_bitset_size(&v1) == 63); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Test OR\n"); + n = 7; + igraph_bitset_init(&v1, n); + igraph_bitset_init(&v2, n); + igraph_bitset_init(&v3, n); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + IGRAPH_BIT_SET(v2, 1); + IGRAPH_BIT_SET(v2, 2); + IGRAPH_BIT_SET(v2, 5); + print_bitset(&v2); + IGRAPH_BIT_SET(v3, 2); + IGRAPH_BIT_SET(v3, 3); + IGRAPH_BIT_SET(v3, 6); + print_bitset(&v3); + igraph_bitset_or(&v1, &v2, &v3); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + igraph_bitset_destroy(&v2); + igraph_bitset_destroy(&v3); + printf("\n"); + + printf("Test AND\n"); + n = 7; + igraph_bitset_init(&v1, n); + igraph_bitset_init(&v2, n); + igraph_bitset_init(&v3, n); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + IGRAPH_BIT_SET(v2, 1); + IGRAPH_BIT_SET(v2, 2); + IGRAPH_BIT_SET(v2, 5); + print_bitset(&v2); + IGRAPH_BIT_SET(v3, 2); + IGRAPH_BIT_SET(v3, 3); + IGRAPH_BIT_SET(v3, 6); + print_bitset(&v3); + igraph_bitset_and(&v1, &v2, &v3); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + igraph_bitset_destroy(&v2); + igraph_bitset_destroy(&v3); + printf("\n"); + + printf("Test XOR\n"); + n = 7; + igraph_bitset_init(&v2, n); + igraph_bitset_init(&v1, n); + igraph_bitset_init(&v3, n); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + IGRAPH_BIT_SET(v2, 1); + IGRAPH_BIT_SET(v2, 2); + IGRAPH_BIT_SET(v2, 5); + print_bitset(&v2); + IGRAPH_BIT_SET(v3, 2); + IGRAPH_BIT_SET(v3, 3); + IGRAPH_BIT_SET(v3, 6); + print_bitset(&v3); + igraph_bitset_xor(&v1, &v2, &v3); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + igraph_bitset_destroy(&v2); + igraph_bitset_destroy(&v3); + printf("\n"); + + printf("Test NOT\n"); + n = 7; + igraph_bitset_init(&v1, n); + igraph_bitset_init(&v2, n); + IGRAPH_BIT_SET(v1, 1); + print_bitset(&v1); + IGRAPH_BIT_SET(v2, 1); + IGRAPH_BIT_SET(v2, 2); + IGRAPH_BIT_SET(v2, 5); + print_bitset(&v2); + igraph_bitset_not(&v1, &v2); + print_bitset(&v1); + igraph_bitset_destroy(&v1); + igraph_bitset_destroy(&v2); + printf("\n"); + + printf("Test popcount\n"); + n = 75; + igraph_bitset_init(&v1, n); + print_bitset(&v1); + + printf("Popcount: %" IGRAPH_PRId "\n", igraph_bitset_popcount(&v1)); + for (igraph_int_t i = 2; i < n; i++) { + if (i == 2 || i == 3 || i == 5 || i == 7 || i == 11 || !(i%2 == 0 || i%3 == 0 || i%5 == 0 || i%7 == 0 || i%11 == 0)) { + IGRAPH_BIT_SET(v1, i); + } + } + print_bitset(&v1); + printf("Popcount: %" IGRAPH_PRId "\n", igraph_bitset_popcount(&v1)); + IGRAPH_BIT_CLEAR(v1, 67); + print_bitset(&v1); + printf("Popcount: %" IGRAPH_PRId "\n", igraph_bitset_popcount(&v1)); + IGRAPH_BIT_SET(v1, 9); + print_bitset(&v1); + printf("Popcount: %" IGRAPH_PRId "\n", igraph_bitset_popcount(&v1)); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Popcount: %" IGRAPH_PRId "\n", igraph_bitset_popcount(&v1)); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Test count leading zeros\n"); + n = 67; + igraph_bitset_init(&v1, n); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + IGRAPH_BIT_SET(v1, 23); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + IGRAPH_BIT_CLEAR(v1, 0); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + IGRAPH_BIT_SET(v1, n - 3); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + IGRAPH_BIT_SET(v1, n - 1); + print_bitset(&v1); + printf("Leading zeros: %" IGRAPH_PRId "\n", igraph_bitset_countl_zero(&v1)); + igraph_bitset_destroy(&v1); + printf("\n"); + + + printf("Test count leading ones\n"); + n = 67; + igraph_bitset_init(&v1, n); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + IGRAPH_BIT_CLEAR(v1, 0); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + IGRAPH_BIT_CLEAR(v1, 23); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + IGRAPH_BIT_CLEAR(v1, n - 3); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + IGRAPH_BIT_CLEAR(v1, n - 1); + print_bitset(&v1); + printf("Leading ones: %" IGRAPH_PRId "\n", igraph_bitset_countl_one(&v1)); + igraph_bitset_destroy(&v1); + printf("\n"); + + + printf("Test count trailing zeros\n"); + n = 67; + igraph_bitset_init(&v1, n); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + IGRAPH_BIT_SET(v1, 23); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + IGRAPH_BIT_CLEAR(v1, 0); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + IGRAPH_BIT_CLEAR(v1, 23); + IGRAPH_BIT_SET(v1, n - 1); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + IGRAPH_BIT_SET(v1, 2); + print_bitset(&v1); + printf("Trailing zeros: %" IGRAPH_PRId "\n", igraph_bitset_countr_zero(&v1)); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Test count trailing ones\n"); + n = 67; + igraph_bitset_init(&v1, n); + igraph_bitset_not(&v1, &v1); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + IGRAPH_BIT_CLEAR(v1, 23); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + IGRAPH_BIT_CLEAR(v1, 0); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + IGRAPH_BIT_SET(v1, 0); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + IGRAPH_BIT_SET(v1, 23); + IGRAPH_BIT_CLEAR(v1, n - 1); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + IGRAPH_BIT_CLEAR(v1, 2); + print_bitset(&v1); + printf("Trailing ones: %" IGRAPH_PRId "\n", igraph_bitset_countr_one(&v1)); + igraph_bitset_destroy(&v1); + printf("\n"); + + printf("Test checking all elements\n"); + /* 0000 */ + igraph_bitset_init(&v1, 4); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* 1000 */ + IGRAPH_BIT_SET(v1, 3); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* 000 */ + /* This resize leaves a set bit in the unused part of the last word of the. + * bitset. This helps test that masking out the unused part is working. */ + igraph_bitset_resize(&v1, 3); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* 001 */ + IGRAPH_BIT_SET(v1, 0); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* 111 */ + IGRAPH_BIT_SET(v1, 1); IGRAPH_BIT_SET(v1, 2); + IGRAPH_ASSERT(igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_zero(&v1)); + igraph_bitset_destroy(&v1); + + igraph_bitset_init(&v1, 67); + /* all zeros */ + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* some ones */ + IGRAPH_BIT_SET(v1, 10); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + /* all ones */ + igraph_bitset_fill(&v1, true); + IGRAPH_ASSERT(igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_zero(&v1)); + /* some zeros */ + IGRAPH_BIT_CLEAR(v1, 20); + IGRAPH_ASSERT(! igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_any_zero(&v1)); + igraph_bitset_destroy(&v1); + + /* Empty bitset */ + igraph_bitset_init(&v1, 0); + IGRAPH_ASSERT(igraph_bitset_is_all_one(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_one(&v1)); + IGRAPH_ASSERT(igraph_bitset_is_all_zero(&v1)); + IGRAPH_ASSERT(! igraph_bitset_is_any_zero(&v1)); + igraph_bitset_destroy(&v1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/bitset.out b/tests/unit/bitset.out new file mode 100644 index 0000000..c7c5561 --- /dev/null +++ b/tests/unit/bitset.out @@ -0,0 +1,137 @@ +Initialise empty bitset + +Initialise bitset of length 32 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) + +Initialise bitset of length 64 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) + +Initialise bitset of length 75 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) + +Test IGRAPH_BIT_SET +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) + +Test IGRAPH_BIT_CLEAR +( 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) + +Test printing +00000000001000000000010000000000001 + +Test bitset copy constructor +( 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 ) + +Test bitset resize +( ) +( 0 0 0 0 0 0 0 0 0 0 ) +( 0 0 0 0 0 0 1 0 0 0 ) +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) +( 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) +( 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) +( 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) +( 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 ) + +Test OR +( 0 0 0 0 0 0 1 ) +( 0 1 0 0 1 1 0 ) +( 1 0 0 1 1 0 0 ) +( 1 1 0 1 1 1 0 ) + +Test AND +( 0 0 0 0 0 0 1 ) +( 0 1 0 0 1 1 0 ) +( 1 0 0 1 1 0 0 ) +( 0 0 0 0 1 0 0 ) + +Test XOR +( 0 0 0 0 0 0 1 ) +( 0 1 0 0 1 1 0 ) +( 1 0 0 1 1 0 0 ) +( 1 1 0 1 0 1 0 ) + +Test NOT +( 0 0 0 0 0 1 0 ) +( 0 1 0 0 1 1 0 ) +( 1 0 1 1 0 0 1 ) + +Test popcount +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Popcount: 0 +( 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 1 1 0 0 ) +Popcount: 21 +( 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 1 1 0 0 ) +Popcount: 20 +( 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 1 0 0 ) +Popcount: 21 +( 1 0 1 0 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 0 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 0 1 1 ) +Popcount: 54 + +Test count leading zeros +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Leading zeros: 0 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Leading zeros: 67 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +Leading zeros: 66 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +Leading zeros: 43 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Leading zeros: 43 +( 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Leading zeros: 2 +( 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Leading zeros: 0 + +Test count leading ones +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Leading ones: 67 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 ) +Leading ones: 66 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 ) +Leading ones: 43 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Leading ones: 43 +( 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Leading ones: 2 +( 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Leading ones: 0 + +Test count trailing zeros +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Trailing zeros: 0 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Trailing zeros: 67 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +Trailing zeros: 0 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 ) +Trailing zeros: 0 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Trailing zeros: 23 +( 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Trailing zeros: 66 +( 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 ) +Trailing zeros: 2 + +Test count trailing ones +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Trailing ones: 67 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Trailing ones: 23 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 ) +Trailing ones: 0 +( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Trailing ones: 23 +( 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) +Trailing ones: 66 +( 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 ) +Trailing ones: 2 + +Test checking all elements diff --git a/tests/unit/bliss_automorphisms.c b/tests/unit/bliss_automorphisms.c new file mode 100644 index 0000000..2b4b772 --- /dev/null +++ b/tests/unit/bliss_automorphisms.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2020-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#define TEST_GRAPH(name) \ + igraph_count_automorphisms_bliss(&graph, NULL, IGRAPH_BLISS_F, &info); \ + printf("%s: %s\n", name, info.group_size); \ + igraph_free(info.group_size); \ + igraph_destroy(&graph); + +#define TEST_FAMOUS(name) \ + igraph_famous(&graph, name); \ + TEST_GRAPH(name); + +int main(void) { + igraph_t graph; + igraph_bliss_info_t info; + + TEST_FAMOUS("Frucht"); + TEST_FAMOUS("Coxeter"); + TEST_FAMOUS("Petersen"); + TEST_FAMOUS("Meredith"); + + igraph_full(&graph, 23, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + TEST_GRAPH("Complete 23"); + + igraph_star(&graph, 17, IGRAPH_STAR_OUT, 0); + TEST_GRAPH("Directed star 17"); + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + TEST_GRAPH("Null graph"); + + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + TEST_GRAPH("Singleton graph"); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/bliss_automorphisms.out b/tests/unit/bliss_automorphisms.out new file mode 100644 index 0000000..eb69fdf --- /dev/null +++ b/tests/unit/bliss_automorphisms.out @@ -0,0 +1,8 @@ +Frucht: 1 +Coxeter: 336 +Petersen: 120 +Meredith: 38698352640 +Complete 23: 25852016738884976640000 +Directed star 17: 20922789888000 +Null graph: 1 +Singleton graph: 1 diff --git a/tests/unit/cattributes5.c b/tests/unit/cattributes5.c new file mode 100644 index 0000000..1b2ea73 --- /dev/null +++ b/tests/unit/cattributes5.c @@ -0,0 +1,97 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +static void simplify_write_destroy(igraph_t *g, igraph_attribute_combination_t *comb) { + igraph_simplify(g, /*remove_multiple=*/ true, /*remove_loops=*/ true, comb); + igraph_write_graph_graphml(g, stdout, /*prefixattr=*/ true); + igraph_attribute_combination_destroy(comb); + igraph_destroy(g); +} + +static void type_test(igraph_t *g, igraph_attribute_combination_type_t type_attr) { + igraph_t g2; + igraph_attribute_combination_t comb; + + igraph_copy(&g2, g); + igraph_attribute_combination(&comb, + "type", type_attr, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); +} + +int main(void) { + + igraph_t g, g2; + igraph_attribute_combination_t comb; + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0, 1, 0, 1, 0, 1, + 1, 2, 2, 3, + -1); + + SETEAB(&g, "type", 0, 1); + SETEAB(&g, "type", 1, 1); + SETEAB(&g, "type", 2, 0); + SETEAB(&g, "type", 3, 0); + SETEAB(&g, "type", 4, 1); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + "type", IGRAPH_ATTRIBUTE_COMBINE_FIRST, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + igraph_copy(&g2, &g); + igraph_attribute_combination(&comb, + "", IGRAPH_ATTRIBUTE_COMBINE_LAST, + IGRAPH_NO_MORE_ATTRIBUTES); + simplify_write_destroy(&g2, &comb); + + /* ****************************************************** */ + + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_LAST); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_SUM); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_PROD); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MIN); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MAX); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MEAN); + type_test(&g, IGRAPH_ATTRIBUTE_COMBINE_MEDIAN); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/cattributes5.out b/tests/unit/cattributes5.out new file mode 100644 index 0000000..79ccfab --- /dev/null +++ b/tests/unit/cattributes5.out @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + true + + + false + + + true + + + + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + diff --git a/tests/unit/cattributes6.c b/tests/unit/cattributes6.c new file mode 100644 index 0000000..f54c3fc --- /dev/null +++ b/tests/unit/cattributes6.c @@ -0,0 +1,322 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#include +#include + + +static void check_vector_queries(const igraph_t *g) { + igraph_vector_t vec; + igraph_strvector_t svec; + igraph_vector_bool_t bvec; + igraph_strvector_t vnames, enames; + igraph_vector_int_t vtypes, etypes; + igraph_int_t i, j; + + igraph_vector_int_init(&vtypes, 0); + igraph_vector_int_init(&etypes, 0); + igraph_strvector_init(&vnames, 0); + igraph_strvector_init(&enames, 0); + + + igraph_vector_init(&vec, 0); + igraph_strvector_init(&svec, 0); + igraph_vector_bool_init(&bvec, 0); + + igraph_cattribute_list(g, 0, 0, &vnames, &vtypes, + &enames, &etypes); + for (j = 0; j < igraph_strvector_size(&vnames); j++) { + if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_cattribute_VANV(g, igraph_strvector_get(&vnames, j), igraph_vss_all(), &vec); + for (i = 0; i < igraph_vcount(g); i++) { + igraph_real_t num = VAN(g, igraph_strvector_get(&vnames, j), i); + if (num != VECTOR(vec)[i] && + (!isnan(num) || !isnan(VECTOR(vec)[i]))) { + exit(51); + } + } + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + igraph_cattribute_VASV(g, igraph_strvector_get(&vnames, j), igraph_vss_all(), &svec); + for (i = 0; i < igraph_vcount(g); i++) { + const char *str = VAS(g, igraph_strvector_get(&vnames, j), i); + if (strcmp(str, igraph_strvector_get(&svec, i))) { + exit(52); + } + } + } else { + igraph_cattribute_VABV(g, igraph_strvector_get(&vnames, j), igraph_vss_all(), &bvec); + for (i = 0; i < igraph_vcount(g); i++) { + igraph_bool_t b = VAB(g, igraph_strvector_get(&vnames, j), i); + if (b != VECTOR(bvec)[i]) { + exit(53); + } + } + } + } + + for (j = 0; j < igraph_strvector_size(&enames); j++) { + if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_cattribute_EANV(g, igraph_strvector_get(&enames, j), + igraph_ess_all(IGRAPH_EDGEORDER_ID), &vec); + for (i = 0; i < igraph_ecount(g); i++) { + igraph_real_t num = EAN(g, igraph_strvector_get(&enames, j), i); + if (num != VECTOR(vec)[i] && + (!isnan(num) || !isnan(VECTOR(vec)[i]))) { + exit(54); + } + } + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + igraph_cattribute_EASV(g, igraph_strvector_get(&enames, j), + igraph_ess_all(IGRAPH_EDGEORDER_ID), &svec); + for (i = 0; i < igraph_ecount(g); i++) { + const char *str = EAS(g, igraph_strvector_get(&enames, j), i); + if (strcmp(str, igraph_strvector_get(&svec, i))) { + exit(55); + } + } + } else { + igraph_cattribute_EABV(g, igraph_strvector_get(&enames, j), + igraph_ess_all(IGRAPH_EDGEORDER_ID), &bvec); + for (i = 0; i < igraph_ecount(g); i++) { + igraph_bool_t b = EAB(g, igraph_strvector_get(&enames, j), i); + if (b != VECTOR(bvec)[i]) { + exit(56); + } + } + } + } + + igraph_strvector_destroy(&svec); + igraph_vector_destroy(&vec); + igraph_vector_bool_destroy(&bvec); + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_vector_int_destroy(&etypes); + igraph_vector_int_destroy(&vtypes); +} + +int main(void) { + + igraph_t g, g2; + FILE *ifile; + igraph_int_t i; + igraph_vector_t y; + igraph_strvector_t name; + igraph_vector_bool_t type; + char str[21]; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + ifile = fopen("links.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + SETGAB(&g, "bool_graph_attr", 1); + SETEAB(&g, "bool_edge_attr", 0, 1); + SETVAB(&g, "bool_vertex_attr", 0, 1); + + print_attributes(&g); + + /* Copying a graph */ + igraph_copy(&g2, &g); + print_attributes(&g2); + igraph_destroy(&g2); + + /* Adding vertices */ + igraph_add_vertices(&g, 3, 0); + print_attributes(&g); + + /* Adding edges */ + igraph_add_edge(&g, 1, 1); + igraph_add_edge(&g, 2, 5); + igraph_add_edge(&g, 3, 6); + print_attributes(&g); + + /* Deleting vertices */ + igraph_delete_vertices(&g, igraph_vss_1(1)); + igraph_delete_vertices(&g, igraph_vss_1(4)); + print_attributes(&g); + + /* Deleting edges */ + igraph_delete_edges(&g, igraph_ess_1(igraph_ecount(&g) - 1)); + igraph_delete_edges(&g, igraph_ess_1(0)); + print_attributes(&g); + + /* Set graph attributes */ + SETGAN(&g, "id", 10); + SETGAS(&g, "name", "toy"); + SETGAB(&g, "is_regular", 0); + + printf("Before deleting some attributes:\n"); + print_attributes(&g); + /* Delete graph attributes */ + DELGA(&g, "id"); + DELGA(&g, "name"); + DELGA(&g, "is_regular"); + + /* Delete vertex attributes */ + DELVA(&g, "x"); + DELVA(&g, "shape"); + DELVA(&g, "xfact"); + DELVA(&g, "yfact"); + + /* Delete edge attributes */ + DELEA(&g, "hook1"); + DELEA(&g, "hook2"); + DELEA(&g, "label"); + + printf("After deleting some attributes:\n"); + print_attributes(&g); + + /* Set vertex attributes */ + SETVAN(&g, "y", 0, -1); + SETVAN(&g, "y", 1, 2.1); + + SETVAS(&g, "name", 0, "foo"); + SETVAS(&g, "name", 1, "bar"); + + SETVAB(&g, "type", 0, 1); + SETVAB(&g, "type", 1, 0); + + /* Set edge attributes */ + SETEAN(&g, "weight", 2, 100.0); + SETEAN(&g, "weight", 0, -100.1); + + SETEAS(&g, "color", 2, "RED"); + SETEAS(&g, "color", 0, "Blue"); + + SETEAB(&g, "type", 0, 1); + SETEAB(&g, "type", 2, 0); + + printf("After setting vertex and edge attributes:\n"); + print_attributes(&g); + check_vector_queries(&g); + + /* Set vertex attributes as vector */ + igraph_vector_init(&y, igraph_vcount(&g)); + igraph_vector_fill(&y, 1.23); + SETVANV(&g, "y", &y); + igraph_vector_destroy(&y); + + igraph_vector_init_range(&y, 0, igraph_vcount(&g)); + SETVANV(&g, "foobar", &y); + igraph_vector_destroy(&y); + + igraph_vector_bool_init(&type, igraph_vcount(&g)); + for (i = 0; i < igraph_vcount(&g); i++) { + VECTOR(type)[i] = (i % 2 == 1); + } + SETVABV(&g, "type", &type); + igraph_vector_bool_destroy(&type); + + igraph_strvector_init(&name, igraph_vcount(&g)); + for (i = 0; i < igraph_vcount(&g); i++) { + snprintf(str, sizeof(str) - 1, "%" IGRAPH_PRId, i); + igraph_strvector_set(&name, i, str); + } + SETVASV(&g, "foo", &name); + igraph_strvector_destroy(&name); + + igraph_strvector_init(&name, igraph_vcount(&g)); + for (i = 0; i < igraph_vcount(&g); i++) { + snprintf(str, sizeof(str) - 1, "%" IGRAPH_PRId, i); + igraph_strvector_set(&name, i, str); + } + SETVASV(&g, "name", &name); + igraph_strvector_destroy(&name); + + /* Set edge attributes as vector */ + igraph_vector_init(&y, igraph_ecount(&g)); + igraph_vector_fill(&y, 12.3); + SETEANV(&g, "weight", &y); + igraph_vector_destroy(&y); + + igraph_vector_init_range(&y, 0, igraph_ecount(&g)); + SETEANV(&g, "foobar", &y); + igraph_vector_destroy(&y); + + igraph_vector_bool_init(&type, igraph_ecount(&g)); + for (i = 0; i < igraph_ecount(&g); i++) { + VECTOR(type)[i] = (i % 2 == 1); + } + SETEABV(&g, "type", &type); + igraph_vector_bool_destroy(&type); + + igraph_strvector_init(&name, igraph_ecount(&g)); + for (i = 0; i < igraph_ecount(&g); i++) { + snprintf(str, sizeof(str) - 1, "%" IGRAPH_PRId, i); + igraph_strvector_set(&name, i, str); + } + SETEASV(&g, "foo", &name); + igraph_strvector_destroy(&name); + + igraph_strvector_init(&name, igraph_ecount(&g)); + for (i = 0; i < igraph_ecount(&g); i++) { + snprintf(str, sizeof(str) - 1, "%" IGRAPH_PRId, i); + igraph_strvector_set(&name, i, str); + } + SETEASV(&g, "color", &name); + igraph_strvector_destroy(&name); + + printf("After setting vertex and edge attributes by vector:\n"); + print_attributes(&g); + /* Delete all remaining attributes */ + DELALL(&g); + + printf("After deleting all attributes:\n"); + print_attributes(&g); + + igraph_destroy(&g); + + printf("Setting attributes on vertex 0, permuting with 2\n"); + { + igraph_t g2; + igraph_vector_int_t per; + + igraph_vector_int_init_int(&per, 3, 2, 1, 0); + + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 1,2, -1); + SETVAN(&g, "foo", 0, 5); + SETVAB(&g, "bar", 0, 1); + SETVAS(&g, "baz", 0, "foobar"); + printf("Permuting to different graph:\n"); + igraph_permute_vertices(&g, &g2, &per); + print_attributes(&g2); + printf("Permuting to same graph:\n"); + igraph_cattribute_table.permute_vertices(&g, &g, &per); + print_attributes(&g); + igraph_destroy(&g); + igraph_destroy(&g2); + igraph_vector_int_destroy(&per); + } + + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/cattributes6.out b/tests/unit/cattributes6.out new file mode 100644 index 0000000..9f66de4 --- /dev/null +++ b/tests/unit/cattributes6.out @@ -0,0 +1,152 @@ +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="2" x=0.8188 y=0.2458 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Edge 0 (0-0): weight=1 hook2=0 edgewidth=3 color="Blue" arrowsize=3 angle1=-130 velocity1=0.6 angle2=-130 velocity2=0.6 arrowpos=0.5 label="Bezier loop" labelcolor="BlueViolet" fontsize=20 labelangle=58 labelpos=0.3 labeldegree=360 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=1 +Edge 1 (1-0): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=120 velocity1=1.3 angle2=-120 velocity2=0.3 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=19 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=0 bool_edge_attr=0 +Edge 2 (0-1): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=40 velocity1=2.8 angle2=30 velocity2=0.8 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=10 labelpos=0.65 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 3 (3-1): weight=-1 hook2=0 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=250 arrowpos=25 label="Circular arc" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 4 (2-3): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 5 (0-2): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 6 (2-2): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 + +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="2" x=0.8188 y=0.2458 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Edge 0 (0-0): weight=1 hook2=0 edgewidth=3 color="Blue" arrowsize=3 angle1=-130 velocity1=0.6 angle2=-130 velocity2=0.6 arrowpos=0.5 label="Bezier loop" labelcolor="BlueViolet" fontsize=20 labelangle=58 labelpos=0.3 labeldegree=360 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=1 +Edge 1 (1-0): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=120 velocity1=1.3 angle2=-120 velocity2=0.3 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=19 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=0 bool_edge_attr=0 +Edge 2 (0-1): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=40 velocity1=2.8 angle2=30 velocity2=0.8 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=10 labelpos=0.65 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 3 (3-1): weight=-1 hook2=0 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=250 arrowpos=25 label="Circular arc" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 4 (2-3): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 5 (0-2): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 6 (2-2): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 + +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="2" x=0.8188 y=0.2458 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 4: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 5: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 6: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Edge 0 (0-0): weight=1 hook2=0 edgewidth=3 color="Blue" arrowsize=3 angle1=-130 velocity1=0.6 angle2=-130 velocity2=0.6 arrowpos=0.5 label="Bezier loop" labelcolor="BlueViolet" fontsize=20 labelangle=58 labelpos=0.3 labeldegree=360 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=1 +Edge 1 (1-0): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=120 velocity1=1.3 angle2=-120 velocity2=0.3 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=19 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=0 bool_edge_attr=0 +Edge 2 (0-1): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=40 velocity1=2.8 angle2=30 velocity2=0.8 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=10 labelpos=0.65 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 3 (3-1): weight=-1 hook2=0 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=250 arrowpos=25 label="Circular arc" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 4 (2-3): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 5 (0-2): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 6 (2-2): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 + +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="2" x=0.8188 y=0.2458 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 4: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 5: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 6: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Edge 0 (0-0): weight=1 hook2=0 edgewidth=3 color="Blue" arrowsize=3 angle1=-130 velocity1=0.6 angle2=-130 velocity2=0.6 arrowpos=0.5 label="Bezier loop" labelcolor="BlueViolet" fontsize=20 labelangle=58 labelpos=0.3 labeldegree=360 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=1 +Edge 1 (1-0): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=120 velocity1=1.3 angle2=-120 velocity2=0.3 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=19 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=0 bool_edge_attr=0 +Edge 2 (0-1): weight=1 hook2=0 edgewidth=2 color="" arrowsize=1 angle1=40 velocity1=2.8 angle2=30 velocity2=0.8 arrowpos=25 label="Bezier arc" labelcolor="" fontsize=15 labelangle=10 labelpos=0.65 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 3 (3-1): weight=-1 hook2=0 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=250 arrowpos=25 label="Circular arc" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=0 +Edge 4 (2-3): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 5 (0-2): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 6 (2-2): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 +Edge 7 (1-1): weight=NaN hook2=NaN edgewidth=NaN color="" arrowsize=NaN angle1=NaN velocity1=NaN angle2=NaN velocity2=NaN arrowpos=NaN label="" labelcolor="" fontsize=NaN labelangle=NaN labelpos=NaN labeldegree=NaN labelangle2=NaN linepattern="" hook1=NaN bool_edge_attr=0 +Edge 8 (2-5): weight=NaN hook2=NaN edgewidth=NaN color="" arrowsize=NaN angle1=NaN velocity1=NaN angle2=NaN velocity2=NaN arrowpos=NaN label="" labelcolor="" fontsize=NaN labelangle=NaN labelpos=NaN labeldegree=NaN labelangle2=NaN linepattern="" hook1=NaN bool_edge_attr=0 +Edge 9 (3-6): weight=NaN hook2=NaN edgewidth=NaN color="" arrowsize=NaN angle1=NaN velocity1=NaN angle2=NaN velocity2=NaN arrowpos=NaN label="" labelcolor="" fontsize=NaN labelangle=NaN labelpos=NaN labeldegree=NaN labelangle2=NaN linepattern="" hook1=NaN bool_edge_attr=0 + +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 4: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Edge 0 (0-0): weight=1 hook2=0 edgewidth=3 color="Blue" arrowsize=3 angle1=-130 velocity1=0.6 angle2=-130 velocity2=0.6 arrowpos=0.5 label="Bezier loop" labelcolor="BlueViolet" fontsize=20 labelangle=58 labelpos=0.3 labeldegree=360 labelangle2=90 linepattern="" hook1=0 bool_edge_attr=1 +Edge 1 (1-2): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 2 (0-1): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 3 (1-1): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 +Edge 4 (2-4): weight=NaN hook2=NaN edgewidth=NaN color="" arrowsize=NaN angle1=NaN velocity1=NaN angle2=NaN velocity2=NaN arrowpos=NaN label="" labelcolor="" fontsize=NaN labelangle=NaN labelpos=NaN labeldegree=NaN labelangle2=NaN linepattern="" hook1=NaN bool_edge_attr=0 + +bool_graph_attr=1 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 4: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Edge 0 (1-2): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 1 (0-1): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 2 (1-1): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 + +Before deleting some attributes: +bool_graph_attr=1 id=10 name="toy" is_regular=0 +Vertex 0: name="1" x=0.0938 y=0.0896 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=1 +Vertex 1: name="3" x=0.3688 y=0.7792 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 2: name="4" x=0.9583 y=0.8563 shape="ellipse" xfact=1 yfact=1 bool_vertex_attr=0 +Vertex 3: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Vertex 4: name="" x=NaN y=NaN shape="" xfact=NaN yfact=NaN bool_vertex_attr=0 +Edge 0 (1-2): weight=1 hook2=0 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 label="Straight arc" labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 1 (0-1): weight=1 hook2=0 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 label="Oval arc" labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" hook1=0 bool_edge_attr=0 +Edge 2 (1-1): weight=-1 hook2=12 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 label="Circular loop" labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" hook1=6 bool_edge_attr=0 + +After deleting some attributes: +bool_graph_attr=1 +Vertex 0: name="1" y=0.0896 bool_vertex_attr=1 +Vertex 1: name="3" y=0.7792 bool_vertex_attr=0 +Vertex 2: name="4" y=0.8563 bool_vertex_attr=0 +Vertex 3: name="" y=NaN bool_vertex_attr=0 +Vertex 4: name="" y=NaN bool_vertex_attr=0 +Edge 0 (1-2): weight=1 edgewidth=2 color="OliveGreen" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 +Edge 1 (0-1): weight=1 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 +Edge 2 (1-1): weight=-1 edgewidth=1 color="Red" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" bool_edge_attr=0 + +After setting vertex and edge attributes: +bool_graph_attr=1 +Vertex 0: name="foo" y=-1 bool_vertex_attr=1 type=1 +Vertex 1: name="bar" y=2.1 bool_vertex_attr=0 type=0 +Vertex 2: name="4" y=0.8563 bool_vertex_attr=0 type=0 +Vertex 3: name="" y=NaN bool_vertex_attr=0 type=0 +Vertex 4: name="" y=NaN bool_vertex_attr=0 type=0 +Edge 0 (1-2): weight=-100.1 edgewidth=2 color="Blue" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 type=1 +Edge 1 (0-1): weight=1 edgewidth=5 color="Brown" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 type=0 +Edge 2 (1-1): weight=100 edgewidth=1 color="RED" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" bool_edge_attr=0 type=0 + +After setting vertex and edge attributes by vector: +bool_graph_attr=1 +Vertex 0: name="0" y=1.23 bool_vertex_attr=1 type=0 foobar=0 foo="0" +Vertex 1: name="1" y=1.23 bool_vertex_attr=0 type=1 foobar=1 foo="1" +Vertex 2: name="2" y=1.23 bool_vertex_attr=0 type=0 foobar=2 foo="2" +Vertex 3: name="3" y=1.23 bool_vertex_attr=0 type=1 foobar=3 foo="3" +Vertex 4: name="4" y=1.23 bool_vertex_attr=0 type=0 foobar=4 foo="4" +Edge 0 (1-2): weight=12.3 edgewidth=2 color="0" arrowsize=1 angle1=0 velocity1=1 angle2=0 velocity2=1 arrowpos=25 labelcolor="PineGreen" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 type=0 foobar=0 foo="0" +Edge 1 (0-1): weight=12.3 edgewidth=5 color="1" arrowsize=1 angle1=0 velocity1=-1 angle2=0 velocity2=-20 arrowpos=25 labelcolor="Black" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=0 labelangle2=90 linepattern="Dashed" bool_edge_attr=0 type=1 foobar=1 foo="1" +Edge 2 (1-1): weight=12.3 edgewidth=1 color="2" arrowsize=1 angle1=0 velocity1=-2 angle2=0 velocity2=-15 arrowpos=0.5 labelcolor="OrangeRed" fontsize=15 labelangle=10 labelpos=0.5 labeldegree=180 labelangle2=270 linepattern="" bool_edge_attr=0 type=0 foobar=2 foo="2" + +After deleting all attributes: +Vertex 0: +Vertex 1: +Vertex 2: +Vertex 3: +Vertex 4: +Edge 0 (1-2): +Edge 1 (0-1): +Edge 2 (1-1): + +Setting attributes on vertex 0, permuting with 2 +Permuting to different graph: +Vertex 0: foo=NaN bar=0 baz="" +Vertex 1: foo=NaN bar=0 baz="" +Vertex 2: foo=5 bar=1 baz="foobar" +Edge 0 (2-1): +Edge 1 (1-0): + +Permuting to same graph: +Vertex 0: foo=NaN bar=0 baz="" +Vertex 1: foo=NaN bar=0 baz="" +Vertex 2: foo=5 bar=1 baz="foobar" +Edge 0 (0-1): +Edge 1 (1-2): + diff --git a/tests/unit/centralization.c b/tests/unit/centralization.c new file mode 100644 index 0000000..9761b6f --- /dev/null +++ b/tests/unit/centralization.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_real_t cent; + igraph_arpack_options_t arpack_options; + + igraph_star(&g, 10, IGRAPH_STAR_IN, /*center=*/ 0); + igraph_centralization_degree(&g, + /*res=*/ NULL, + /*mode=*/ IGRAPH_IN, IGRAPH_NO_LOOPS, + ¢, /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("in-star, degree: %g\n", cent); + + igraph_centralization_betweenness(&g, /*res=*/ NULL, + IGRAPH_UNDIRECTED, ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("in-star, betweenness: %g\n", cent); + igraph_destroy(&g); + + + igraph_star(&g, 10, IGRAPH_STAR_OUT, /*center=*/ 0); + igraph_centralization_degree(&g, /*res=*/ NULL, + /*mode=*/ IGRAPH_OUT, IGRAPH_NO_LOOPS, + ¢, /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("out-star, degree: %g\n", cent); + + igraph_centralization_betweenness(&g, /*res=*/ NULL, + IGRAPH_UNDIRECTED, ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + printf("out-star, betweenness: %g\n", cent); + igraph_destroy(&g); + + igraph_small(&g, /*n=*/ 10, /*directed=*/ 0, 0, 1, -1); + igraph_arpack_options_init(&arpack_options); + igraph_centralization_eigenvector_centrality( + &g, + /*vector=*/ NULL, + /*value=*/ NULL, + /*mode=*/ IGRAPH_OUT, + &arpack_options, ¢, + /*theoretical_max=*/ NULL, + /*normalized=*/ true); + + printf("dyad, eigenvector centrality, not scaled: %g\n", cent); + + igraph_destroy(&g); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/centralization.out b/tests/unit/centralization.out new file mode 100644 index 0000000..5579acd --- /dev/null +++ b/tests/unit/centralization.out @@ -0,0 +1,5 @@ +in-star, degree: 1 +in-star, betweenness: 1 +out-star, degree: 1 +out-star, betweenness: 1 +dyad, eigenvector centrality, not scaled: 1 diff --git a/tests/unit/cmp_epsilon.c b/tests/unit/cmp_epsilon.c new file mode 100644 index 0000000..94edefc --- /dev/null +++ b/tests/unit/cmp_epsilon.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + /* Test "normal" cases */ + IGRAPH_ASSERT(igraph_cmp_epsilon(1, 2, 0.25) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(1, 2, 0.5) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(2, 1, 0.25) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(2, 1, 0.5) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(10, 11, 0.25) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(10, 11, 0.5) == 0); + + /* Test infinities. Infinities are not equal to finite numbers with any + * tolerance */ + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, 2, 0.5) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, 2, 20) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, 2, 200) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, 2, 0.5) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, 2, 20) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, 2, 200) < 0); + + /* Infinities may be equal, though */ + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, IGRAPH_INFINITY, 0.5) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, IGRAPH_INFINITY, 0.1) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, IGRAPH_INFINITY, 1e-7) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, -IGRAPH_INFINITY, 0.5) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, -IGRAPH_INFINITY, 0.1) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, -IGRAPH_INFINITY, 1e-7) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, IGRAPH_INFINITY, 0.5) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, IGRAPH_INFINITY, 0.1) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, IGRAPH_INFINITY, 1e-7) < 0); + + /* NaNs are not equal to anything, not even each other. Sign of the result + * is not guaranteed */ + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, IGRAPH_NAN, 0.1) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, IGRAPH_NAN, 0.1) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(0, IGRAPH_NAN, 1e-7) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, 0, 1e-7) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, IGRAPH_NAN, 1e-7) != 0); + + /* Comparison with zero tolerance should be a "strict" comparison */ + IGRAPH_ASSERT(igraph_cmp_epsilon(1, 2, 0) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(2, 1, 0) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(2, 2, 0) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(0, DBL_MIN, 0) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(DBL_MIN, 0, 0) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, 0, 0) < 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(-IGRAPH_INFINITY, -IGRAPH_INFINITY, 0) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, 0, 0) > 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_INFINITY, IGRAPH_INFINITY, 0) == 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, 0, 0) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, IGRAPH_NAN, 0) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, IGRAPH_INFINITY, 0) != 0); + IGRAPH_ASSERT(igraph_cmp_epsilon(IGRAPH_NAN, -IGRAPH_INFINITY, 0) != 0); + + return 0; +} diff --git a/tests/unit/coloring.c b/tests/unit/coloring.c new file mode 100644 index 0000000..5bdb00a --- /dev/null +++ b/tests/unit/coloring.c @@ -0,0 +1,229 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Verify that the colouring is valid, i.e. no two adjacent vertices have the same colour. */ +void verify_coloring(igraph_t *graph, const igraph_vector_int_t *colors) { + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_int_t no_of_nodes = igraph_vcount(graph); + + for (igraph_int_t i = 0; i < no_of_edges; ++i) { + if (IGRAPH_FROM(graph, i) == IGRAPH_TO(graph, i)) { + continue; + } + if ( VECTOR(*colors)[ IGRAPH_FROM(graph, i) ] == VECTOR(*colors)[ IGRAPH_TO(graph, i) ] ) { + IGRAPH_FATALF("Inconsistent coloring! Vertices %" IGRAPH_PRId " and %" IGRAPH_PRId " are adjacent but have the same color.\n", + IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i)); + } + } + + for (igraph_int_t i = 0; i < no_of_nodes; i++) { + igraph_int_t color = VECTOR(*colors)[i]; + if (color < 0 || color >= no_of_nodes) { + IGRAPH_FATALF("The vertex %" IGRAPH_PRId " has invalid color %" IGRAPH_PRId "\n", i, color); + } + } +} + +void print_result( + const char* result_name, const igraph_vector_int_t *colors, + igraph_bool_t print_color_vector +) { + printf("%s", result_name); + if (!igraph_vector_int_empty(colors)) { + printf("Number of colors used: %" IGRAPH_PRId "\n", igraph_vector_int_max(colors) + 1); + } + if (print_color_vector) { + print_vector_int(colors); + } +} + +void run_tests(igraph_t *graph, igraph_bool_t print_color_vector) { + igraph_vector_int_t colors; + igraph_vector_int_init(&colors, 0); + + igraph_vector_int_fill(&colors, -1); + igraph_vertex_coloring_greedy(graph, &colors, IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS); + verify_coloring(graph, &colors); + print_result("testing greedy coloring with COLORED_NEIGHBORS heuristic\n", &colors, print_color_vector); + + igraph_vector_int_fill(&colors, -1); + igraph_vertex_coloring_greedy(graph, &colors, IGRAPH_COLORING_GREEDY_DSATUR); + verify_coloring(graph, &colors); + print_result("testing greedy coloring with DSATUR heuristic\n", &colors, print_color_vector); + printf("\n"); + + igraph_vector_int_destroy(&colors); +} + +void test_null_graph(void) { + igraph_t graph; + + /* run it on the null graph */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + printf("Testing null graph\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +void test_graph_1_vertex(void) { + igraph_t graph; + + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + printf("Testing singleton graph\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +void test_graph_large(void) { + igraph_t graph; + + /* simple large undirected graph */ + igraph_erdos_renyi_game_gnm(&graph, 1000, 10000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + printf("Testing large simple graph\n"); + run_tests(&graph, false); + + /* graph with loops and parallel edges */ + igraph_rewire_edges(&graph, 1.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW); + printf("Testing large graph with loops and multi-edges\n"); + run_tests(&graph, false); + + igraph_destroy(&graph); +} + +// This graph has chromatic number of 3. +void test_graph_small(void) { + igraph_t graph; + + igraph_small(&graph, 8, IGRAPH_UNDIRECTED, + 0, 3, 0, 4, 1, 4, 2, 4, 1, 5, 2, 5, 3, 5, 0, 6, 1, 6, 2, 6, 3, 6, 0, 7, 1, 7, 2, 7, 4, 7, 5, 7, + -1); + printf("Testing small simple graph\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +// Same as the graph above with multi-edges and self-loops added +void test_graph_small_multi(void) { + igraph_t graph; + + igraph_small(&graph, 8, IGRAPH_UNDIRECTED, + 0, 3, 0, 4, 1, 4, 2, 4, 1, 5, 2, 5, 3, 5, 0, 6, 1, 6, 2, 6, 3, 6, 0, 7, 1, 7, 2, 7, 4, 7, 5, 7, + 0, 4, 0, 4, 3, 5, 3, 5, 3, 5, 0, 0, 0, 0, 1, 1, + -1); + printf("Testing small multigraph\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +// Note: This graph has chromatic number 4. +// Currently implemented greedy heuristics don't produce an exact result. +void test_graph_lcf(void) { + igraph_t graph; + + igraph_lcf_small(&graph, /* n= */ 8, /* shifts= */ 2, /* repeats= */ 8, 0); + printf("Testing small LCF graph\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +// Isolated vertices must be assigned color 0. +void test_isolated_vertices(void) { + igraph_t graph; + + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0, 1, -1); + printf("Testing graph with isolated vertices\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +// Isolated vertices must be assigned color 0. +void test_isolated_vertices_with_loops(void) { + igraph_t graph; + + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, + 0,0, 2,2, 2,2, + -1); + printf("Testing graph with isolated vertices and self-loops\n"); + run_tests(&graph, true); + + igraph_destroy(&graph); +} + +// Wheel graphs on odd/even number of vertices have chromatic number of 3/4. +// DSatur is expected to find an exact minimum coloring. +void test_wheel_graph(void) { + igraph_t graph_odd, graph_even; + + igraph_wheel(&graph_odd, 11, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("Testing wheel graph with odd number of vertices\n"); + run_tests(&graph_odd, true); + + igraph_wheel(&graph_even, 12, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("Testing wheel graph with even number of vertices\n"); + run_tests(&graph_even, true); + + igraph_destroy(&graph_odd); + igraph_destroy(&graph_even); +} + +// Bipartite graphs have a chromatic number of 2 (by definition). +// DSatur is expected to find an exact minimum coloring. +void test_bipartite_graph(void) { + igraph_t graph_1, graph_2; + + igraph_bipartite_game_gnm(&graph_1, 0, 100, 100, 10000, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + printf("Testing complete bipartite graph\n"); + run_tests(&graph_1, false); + + igraph_bipartite_game_gnm(&graph_2, 0, 100, 100, 10000 - 100, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false); + printf("Testing large bipartite graph\n"); + run_tests(&graph_2, false); + + igraph_destroy(&graph_1); + igraph_destroy(&graph_2); +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 42); + + test_null_graph(); + test_graph_1_vertex(); + test_graph_large(); + test_graph_small(); + test_graph_small_multi(); + test_graph_lcf(); + test_isolated_vertices(); + test_isolated_vertices_with_loops(); + test_wheel_graph(); + test_bipartite_graph(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_indexing.c b/tests/unit/community_indexing.c new file mode 100644 index 0000000..5646964 --- /dev/null +++ b/tests/unit/community_indexing.c @@ -0,0 +1,109 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* This test verifies that the membership vectors returns by community detection + * functions are properly indexed, i.e. there are no negative indices and there + * are no empty communities. */ + +void check(const igraph_vector_int_t *m) { + igraph_vector_int_t m2; + igraph_vector_int_init_copy(&m2, m); + igraph_reindex_membership(&m2, NULL, NULL); + IGRAPH_ASSERT(igraph_vector_int_min(m) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(m) == igraph_vector_int_max(&m2)); + igraph_vector_int_destroy(&m2); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_t membership; + igraph_real_t m; + igraph_error_handler_t *handler; + igraph_error_t ret; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&membership, 0); + + igraph_grg_game(&graph, 100, 0.2, false, NULL, NULL); + + igraph_community_fastgreedy(&graph, NULL, NULL, NULL, &membership); + check(&membership); + + igraph_community_label_propagation(&graph, &membership, IGRAPH_ALL, NULL, NULL, NULL, IGRAPH_LPA_DOMINANCE); + check(&membership); + + igraph_community_walktrap(&graph, NULL, 4, NULL, NULL, &membership); + check(&membership); + + igraph_community_edge_betweenness(&graph, NULL, NULL, NULL, NULL, NULL, &membership, IGRAPH_UNDIRECTED, NULL, NULL); + check(&membership); + + igraph_community_leading_eigenvector(&graph, NULL, NULL, &membership, igraph_vcount(&graph), NULL, NULL, false, NULL, NULL, NULL, NULL, NULL); + check(&membership); + + igraph_community_leiden(&graph, NULL, NULL, NULL, 1, 0.01, 1, false, &membership, NULL, NULL); + check(&membership); + + igraph_community_multilevel(&graph, NULL, 1, &membership, NULL, NULL); + check(&membership); + + igraph_community_fluid_communities(&graph, 10, &membership); + check(&membership); + + igraph_community_voronoi(&graph, &membership, NULL, NULL, NULL, NULL, IGRAPH_ALL, -1); + check(&membership); + + igraph_community_spinglass(&graph, NULL, &m, NULL, &membership, NULL, 5, false, 1.0, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_ORIG, 1); + check(&membership); + + igraph_community_spinglass(&graph, NULL, &m, NULL, &membership, NULL, 5, false, 1.0, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_NEG, 1); + check(&membership); + + handler = igraph_set_error_handler(&igraph_error_handler_ignore); + ret = igraph_community_infomap(&graph, NULL, NULL, 1, false, 0, &membership, NULL); + igraph_set_error_handler(handler); + if (ret != IGRAPH_UNIMPLEMENTED) { + IGRAPH_ASSERT(ret == IGRAPH_SUCCESS); + check(&membership); + } + + igraph_destroy(&graph); + + igraph_grg_game(&graph, 20, 0.5, false, NULL, NULL); + + handler = igraph_set_error_handler(&igraph_error_handler_ignore); + ret = igraph_community_optimal_modularity(&graph, NULL, 1, NULL, &membership); + igraph_set_error_handler(handler); + if (ret != IGRAPH_UNIMPLEMENTED) { /* Test only when GLPK is available */ + IGRAPH_ASSERT(ret == IGRAPH_SUCCESS); + check(&membership); + } + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_label_propagation.c b/tests/unit/community_label_propagation.c new file mode 100644 index 0000000..552b6c5 --- /dev/null +++ b/tests/unit/community_label_propagation.c @@ -0,0 +1,157 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t membership; + igraph_vector_t weights; + igraph_vector_int_t initial; + igraph_vector_bool_t fixed; + igraph_int_t i, j, k; + + /* Simple triangle graph, the output should be always one community */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 2, -1); + igraph_vector_int_init(&membership, 0); + + igraph_lpa_variant_t variants[3] = {IGRAPH_LPA_DOMINANCE, IGRAPH_LPA_RETENTION, IGRAPH_LPA_FAST}; + for (i = 0; i < 3; i++) { + for (j = 0; j < 100; j++) { + /* label propagation is a stochastic method */ + igraph_rng_seed(igraph_rng_default(), j); + + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, NULL, NULL, NULL, variants[i]); + + for (k = 0; k < 3; k++) { + IGRAPH_ASSERT(VECTOR(membership)[k] == VECTOR(membership)[0]); + } + } + } + + igraph_destroy(&g); + + /* label propagation is a stochastic method */ + igraph_rng_seed(igraph_rng_default(), 765); + + /* Zachary Karate club -- this is just a quick smoke test */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, NULL, NULL, NULL, IGRAPH_LPA_DOMINANCE); + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, NULL, NULL, NULL, IGRAPH_LPA_RETENTION); + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, NULL, NULL, NULL, IGRAPH_LPA_FAST); + + igraph_destroy(&g); + + /* Simple star graph to test weights */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 2, 3, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_init_int_end(&weights, -1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1); + igraph_vector_int_init_int_end(&initial, -1, 0, 0, 1, 1, 1, 1, -1); + igraph_vector_bool_init(&fixed, 6); + VECTOR(fixed)[3] = 1; + VECTOR(fixed)[4] = 1; + VECTOR(fixed)[5] = 1; + + for (i = 0; i < 3; i++) { + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, &weights, + &initial, &fixed, variants[i]); + for (j = 0; j < igraph_vcount(&g); j++) { + IGRAPH_ASSERT(VECTOR(membership)[j] == (j < 2 ? 0 : 1)); + } + + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, 0, + &initial, &fixed, variants[i]); + for (j = 0; j < igraph_vcount(&g); j++) { + IGRAPH_ASSERT(VECTOR(membership)[j] == 0); + } + + /* Check whether it works with no fixed vertices at all + * while an initial configuration is given -- see bug + * #570902 in Launchpad. This is a simple smoke test only. */ + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, &weights, + &initial, NULL, variants[i]); + } + + igraph_vector_bool_destroy(&fixed); + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&initial); + igraph_destroy(&g); + + /* Test line graph with fixed and initial memberships */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, -1); + + igraph_vector_init(&weights, 3); + VECTOR(weights)[0] = 2; + VECTOR(weights)[1] = 1; + VECTOR(weights)[2] = 2; + + igraph_vector_int_init(&initial, 4); + VECTOR(initial)[0] = 0; + VECTOR(initial)[1] = -1; + VECTOR(initial)[2] = -1; + VECTOR(initial)[3] = 1; + + igraph_vector_bool_init(&fixed, 4); + VECTOR(fixed)[0] = 1; + VECTOR(fixed)[1] = 0; + VECTOR(fixed)[2] = 0; + VECTOR(fixed)[3] = 1; + + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, &weights, + &initial, &fixed, IGRAPH_LPA_DOMINANCE); + + igraph_vector_int_print(&membership); + IGRAPH_ASSERT(VECTOR(membership)[0] == 0); + IGRAPH_ASSERT(VECTOR(membership)[3] == 1); + + igraph_vector_bool_destroy(&fixed); + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&initial); + igraph_destroy(&g); + + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_label_propagation2.c b/tests/unit/community_label_propagation2.c new file mode 100644 index 0000000..60c325c --- /dev/null +++ b/tests/unit/community_label_propagation2.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Test case for bug #1852 */ + +int main(void) { + igraph_t graph; + igraph_vector_int_t membership, initial_labels; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Undirected graph with unlabelled components */ + + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, + 1, 2, -1); + + igraph_vector_int_init(&membership, 0); + igraph_vector_int_init(&initial_labels, igraph_vcount(&graph)); + VECTOR(initial_labels)[0] = 1; + VECTOR(initial_labels)[1] = -1; + VECTOR(initial_labels)[2] = -1; + VECTOR(initial_labels)[3] = -1; + + igraph_community_label_propagation(&graph, &membership, IGRAPH_ALL, NULL, &initial_labels, NULL, IGRAPH_LPA_DOMINANCE); + print_vector_int(&membership); + + igraph_destroy(&graph); + + /* Directed graph with unlabelled nodes not reachable from any labelled ones. */ + + igraph_small(&graph, 8, IGRAPH_DIRECTED, + 0, 1, 1, 2, 3, 1, 2, 4, 4, 5, 5, 2, 4, 6, + -1); + + igraph_vector_int_resize(&initial_labels, igraph_vcount(&graph)); + igraph_vector_int_null(&initial_labels); + VECTOR(initial_labels)[0] = -1; + VECTOR(initial_labels)[1] = -1; + VECTOR(initial_labels)[2] = 1; + VECTOR(initial_labels)[3] = -1; + VECTOR(initial_labels)[4] = 2; + VECTOR(initial_labels)[6] = -1; + VECTOR(initial_labels)[7] = -1; + + igraph_community_label_propagation(&graph, &membership, IGRAPH_OUT, NULL, &initial_labels, NULL, IGRAPH_LPA_DOMINANCE); + print_vector_int(&membership); + + igraph_destroy(&graph); + + /* None of the nodes are labelled initially */ + + igraph_full(&graph, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_vector_int_resize(&initial_labels, igraph_vcount(&graph)); + igraph_vector_int_fill(&initial_labels, -1); + + igraph_community_label_propagation(&graph, &membership, IGRAPH_OUT, NULL, &initial_labels, NULL, IGRAPH_LPA_DOMINANCE); + print_vector_int(&membership); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&initial_labels); + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_label_propagation2.out b/tests/unit/community_label_propagation2.out new file mode 100644 index 0000000..3d6e3aa --- /dev/null +++ b/tests/unit/community_label_propagation2.out @@ -0,0 +1,3 @@ +( 0 2 2 1 ) +( 2 2 0 3 0 0 0 1 ) +( 0 0 0 0 0 ) diff --git a/tests/unit/community_label_propagation3.c b/tests/unit/community_label_propagation3.c new file mode 100644 index 0000000..dc02940 --- /dev/null +++ b/tests/unit/community_label_propagation3.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t membership, initial; + igraph_vector_bool_t fixed; + igraph_int_t i; + + /* label propagation is a stochastic method */ + igraph_rng_seed(igraph_rng_default(), 765); + + /* Zachary Karate club -- does not matter, we are simply interested in + * whether the system handles it correctly if a fixed node is unlabeled */ + igraph_famous(&g, "zachary"); + + + igraph_vector_int_init(&initial, igraph_vcount(&g)); + igraph_vector_int_fill(&initial, -1); + igraph_vector_bool_init(&fixed, igraph_vcount(&g)); + igraph_vector_bool_fill(&fixed, 0); + VECTOR(fixed)[7] = 1; + VECTOR(fixed)[13] = 1; + + igraph_vector_int_init(&membership, 0); + + igraph_community_label_propagation(&g, &membership, IGRAPH_OUT, NULL, &initial, &fixed, IGRAPH_LPA_DOMINANCE); + + for (i = 0; i < igraph_vcount(&g); i++) { + /* Check that the "fixed" vector has not been changed */ + if (i == 7 || i == 13) { + IGRAPH_ASSERT(VECTOR(fixed)[i]); + } else { + IGRAPH_ASSERT(!VECTOR(fixed)[i]); + } + + /* Check that no vertex remained unlabeled */ + IGRAPH_ASSERT(VECTOR(membership)[i] >= 0); + } + + igraph_vector_bool_destroy(&fixed); + igraph_vector_int_destroy(&initial); + igraph_vector_int_destroy(&membership); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_leiden.c b/tests/unit/community_leiden.c new file mode 100644 index 0000000..f9b1f2c --- /dev/null +++ b/tests/unit/community_leiden.c @@ -0,0 +1,313 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define TOL (1e-15) + +void run_leiden_CPM(const igraph_t *graph, const igraph_vector_t *edge_weights, const igraph_real_t resolution) { + + igraph_vector_int_t membership; + igraph_int_t nb_clusters = igraph_vcount(graph); + igraph_real_t quality, quality2; + + /* Initialize with singleton partition. */ + igraph_vector_int_init(&membership, igraph_vcount(graph)); + + /* Use same seed as for the simplified interface below, to ensure the same result. */ + igraph_rng_seed(igraph_rng_default(), 123); + igraph_community_leiden(graph, + edge_weights, NULL, NULL, + resolution, 0.01, false, 2, &membership, + &nb_clusters, + &quality); + + /* Handle negative zeros. */ + if (fabs(quality) < TOL) quality = 0.0; + + printf("Leiden found %" IGRAPH_PRId " clusters using CPM (resolution parameter=%.2f), quality is %.5f.\n", nb_clusters, resolution, quality); + + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + quality2 = quality; + + /* Use same seed as for the generic interface above, to ensure the same result. */ + igraph_rng_seed(igraph_rng_default(), 123); + igraph_community_leiden_simple(graph, + edge_weights, + IGRAPH_LEIDEN_OBJECTIVE_CPM, + resolution, 0.01, false, 2, &membership, + NULL, + &quality); + if (fabs(quality) < TOL) quality = 0.0; + IGRAPH_ASSERT((isnan(quality) && isnan(quality2)) || + igraph_almost_equals(quality, quality2, TOL)); + + igraph_vector_int_destroy(&membership); +} + +void run_leiden_modularity(igraph_t *graph, igraph_vector_t *edge_weights) { + + const igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_int_t membership; + igraph_vector_t out_strength, in_strength; + igraph_int_t nb_clusters = igraph_vcount(graph); + igraph_real_t quality, quality2; + const igraph_real_t directed_multiplier = directed ? 1.0 : 2.0; + igraph_real_t m; + + igraph_vector_init(&out_strength, igraph_vcount(graph)); + igraph_strength(graph, &out_strength, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, edge_weights); + + if (directed) { + igraph_vector_init(&in_strength, igraph_vcount(graph)); + igraph_strength(graph, &in_strength, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, edge_weights); + } + + m = edge_weights ? igraph_vector_sum(edge_weights) : igraph_ecount(graph); + + /* Initialize with singleton partition. */ + igraph_vector_int_init(&membership, igraph_vcount(graph)); + + /* Use same seed as for the simplified interface below, to ensure the same result. */ + igraph_rng_seed(igraph_rng_default(), 123); + igraph_community_leiden(graph, + edge_weights, &out_strength, directed ? &in_strength : NULL, + 1.0 / (directed_multiplier * m), 0.01, false, 2, &membership, &nb_clusters, + &quality); + + igraph_modularity(graph, &membership, edge_weights, 1.0, IGRAPH_DIRECTED, &quality2); + if (isnan(quality)) { + printf("Leiden found %" IGRAPH_PRId " clusters using modularity, %s, quality is nan.\n", nb_clusters, directed ? "directed" : "undirected"); + IGRAPH_ASSERT(isnan(quality2)); + } else { + /* It is necessary to not only use igraph_almost_equals(), but also check + * if the values are both very close to zero due to roundoff errors. */ + + if (fabs(quality) < TOL) quality = 0.0; + if (fabs(quality2) < TOL) quality2 = 0.0; + + printf("Leiden found %" IGRAPH_PRId " clusters using modularity, %s, quality is %.5f.\n", nb_clusters, directed ? "directed" : "undirected", quality); + IGRAPH_ASSERT(igraph_almost_equals(quality, quality2, TOL)); + } + + printf("Membership: "); + igraph_vector_int_print(&membership); + printf("\n"); + + /* Use same seed as for the generic interface above, to ensure the same result. */ + igraph_rng_seed(igraph_rng_default(), 123); + igraph_community_leiden_simple(graph, + edge_weights, + IGRAPH_LEIDEN_OBJECTIVE_MODULARITY, + 1.0, 0.01, false, 2, &membership, NULL, &quality); + if (fabs(quality) < TOL) quality = 0.0; + IGRAPH_ASSERT((isnan(quality) && isnan(quality2)) || + igraph_almost_equals(quality, quality2, TOL)); + + igraph_vector_int_destroy(&membership); + if (directed) igraph_vector_destroy(&in_strength); + igraph_vector_destroy(&out_strength); +} + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + + igraph_vector_init(&weights, 0); + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 0); + + /* Simple unweighted graph */ + igraph_small(&graph, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + run_leiden_modularity(&graph, NULL); + + /* Same simple graph, with uniform edge weights */ + igraph_vector_resize(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 2); + run_leiden_modularity(&graph, &weights); + + /* Same simple graph, but directed with reciprocal edges */ + igraph_to_directed(&graph, IGRAPH_TO_DIRECTED_MUTUAL); + run_leiden_modularity(&graph, NULL); + + igraph_destroy(&graph); + + /* Tiny directed graph; optimal community structure is different if + * ignoring edge directions. */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, 0, 2, 0, 3, 1, 2, 3, 1, 3, 2, -1); + run_leiden_modularity(&graph, NULL); + igraph_to_undirected(&graph, IGRAPH_TO_UNDIRECTED_EACH, NULL); + run_leiden_modularity(&graph, NULL); + igraph_destroy(&graph); + + /* Larger directed graph; optimal community structure is different if + * ignoring edge directions. */ + igraph_small( + &graph, 10, IGRAPH_DIRECTED, + 0, 3, 0, 4, 1, 0, 1, 4, 2, 1, 3, 0, 3, 2, 4, 0, 4, 3, 4, 7, 5, 0, 5, + 1, 5, 3, 5, 6, 5, 8, 5, 9, 7, 0, 8, 2, 8, 3, 9, 1, 9, 3, 9, 8, + -1); + run_leiden_modularity(&graph, NULL); + igraph_to_undirected(&graph, IGRAPH_TO_UNDIRECTED_EACH, NULL); + run_leiden_modularity(&graph, NULL); + igraph_destroy(&graph); + + /* Simple nonuniform weighted graph, with and without weights */ + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_resize(&weights, 8); + igraph_vector_fill(&weights, 1); + VECTOR(weights)[0] = 10; + VECTOR(weights)[1] = 10; + run_leiden_modularity(&graph, NULL); + run_leiden_modularity(&graph, &weights); + igraph_destroy(&graph); + + /* Zachary Karate club */ + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + run_leiden_modularity(&graph, NULL); + run_leiden_CPM(&graph, NULL, 0.06); + igraph_destroy(&graph); + + /* Simple disconnected graph with isolates */ + igraph_small(&graph, 9, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, + 4, 5, 4, 6, 4, 7, 5, 6, 5, 7, 6, 7, + -1); + run_leiden_modularity(&graph, NULL); + igraph_destroy(&graph); + + /* Disjoint union of two rings */ + igraph_small(&graph, 20, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 0, 9, + 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 10, 19, -1); + run_leiden_modularity(&graph, NULL); + run_leiden_CPM(&graph, NULL, 0.05); + igraph_destroy(&graph); + + /* Completely empty graph */ + igraph_small(&graph, 10, IGRAPH_UNDIRECTED, -1); + run_leiden_modularity(&graph, NULL); + igraph_destroy(&graph); + + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 0); + + /* Ring graph without loop edges */ + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 3,4, 4,5, 5,0, -1); + run_leiden_CPM(&graph, NULL, 0.4); + igraph_destroy(&graph); + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 0); + + /* Ring graph with loop edges */ + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 3,4, 4,5, 5,0, + 0,0, 1,1, 2,2, 3,3, 4,4, 5,5, + -1); + run_leiden_CPM(&graph, NULL, 0.4); + igraph_destroy(&graph); + + /* Regression test -- graph with two vertices and two edges */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0, 0, 1, 1, -1); + run_leiden_modularity(&graph, NULL); + igraph_destroy(&graph); + + /* The next two tests need an empty weight vector. */ + igraph_vector_clear(&weights); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + run_leiden_modularity(&graph, &weights); + igraph_destroy(&graph); + + /* Edgeless graph */ + igraph_empty(&graph, 5, IGRAPH_UNDIRECTED); + run_leiden_modularity(&graph, &weights); + igraph_destroy(&graph); + + /* Check that the input is validated properly. */ + + /* Small test graph. */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 0,3, 3,3, + -1); + igraph_vector_range(&weights, 1, igraph_ecount(&graph) + 1); + + /* Omitting membership should raise no error. */ + igraph_community_leiden_simple(&graph, &weights, IGRAPH_LEIDEN_OBJECTIVE_MODULARITY, + 1.0, 0.01, false, 1, NULL, NULL, NULL); + + /* Negative weight. */ + VECTOR(weights)[0] = -1; + CHECK_ERROR(igraph_community_leiden_simple(&graph, &weights, IGRAPH_LEIDEN_OBJECTIVE_MODULARITY, + 1.0, 0.01, false, 1, NULL, NULL, NULL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_community_leiden_simple(&graph, &weights, IGRAPH_LEIDEN_OBJECTIVE_ER, + 1.0, 0.01, false, 1, NULL, NULL, NULL), IGRAPH_EINVAL); + + /* NaN weight. */ + VECTOR(weights)[0] = IGRAPH_NAN; + CHECK_ERROR(igraph_community_leiden_simple(&graph, &weights, IGRAPH_LEIDEN_OBJECTIVE_CPM, + 1.0, 0.01, false, 1, NULL, NULL, NULL), IGRAPH_EINVAL); + + /* Invalid weight vector length. */ + igraph_vector_range(&weights, 1, igraph_ecount(&graph) + 2); + CHECK_ERROR(igraph_community_leiden_simple(&graph, &weights, IGRAPH_LEIDEN_OBJECTIVE_MODULARITY, + 1.0, 0.01, false, 1, NULL, NULL, NULL), IGRAPH_EINVAL); + + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_leiden.out b/tests/unit/community_leiden.out new file mode 100644 index 0000000..a88794b --- /dev/null +++ b/tests/unit/community_leiden.out @@ -0,0 +1,60 @@ +Leiden found 2 clusters using modularity, undirected, quality is 0.45238. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Leiden found 2 clusters using modularity, undirected, quality is 0.45238. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Leiden found 2 clusters using modularity, directed, quality is 0.45238. +Membership: 0 0 0 0 0 1 1 1 1 1 + +Leiden found 2 clusters using modularity, directed, quality is 0.08000. +Membership: 0 1 1 0 + +Leiden found 1 clusters using modularity, undirected, quality is 0.00000. +Membership: 0 0 0 0 + +Leiden found 3 clusters using modularity, directed, quality is 0.20868. +Membership: 0 1 1 0 0 2 2 0 2 2 + +Leiden found 2 clusters using modularity, undirected, quality is 0.18079. +Membership: 0 1 1 0 0 1 1 0 1 1 + +Leiden found 2 clusters using modularity, undirected, quality is 0.17969. +Membership: 0 0 1 1 1 1 + +Leiden found 2 clusters using modularity, undirected, quality is 0.17086. +Membership: 0 0 0 1 1 1 + +Leiden found 4 clusters using modularity, undirected, quality is 0.41979. +Membership: 0 0 0 0 1 1 1 0 2 2 1 0 0 0 2 2 1 0 2 0 2 0 2 3 3 3 2 3 3 2 2 3 2 2 + +Leiden found 2 clusters using CPM (resolution parameter=0.06), quality is 0.64949. +Membership: 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 + +Leiden found 3 clusters using modularity, undirected, quality is 0.50000. +Membership: 0 0 0 0 1 1 1 1 2 + +Leiden found 4 clusters using modularity, undirected, quality is 0.55000. +Membership: 0 0 0 1 1 1 1 1 0 0 2 2 2 2 2 3 3 3 3 3 + +Leiden found 2 clusters using CPM (resolution parameter=0.05), quality is 0.75000. +Membership: 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 + +Leiden found 10 clusters using modularity, undirected, quality is nan. +Membership: 0 1 2 3 4 5 6 7 8 9 + +Leiden found 2 clusters using CPM (resolution parameter=0.40), quality is 0.06667. +Membership: 0 0 1 1 1 0 + +Leiden found 2 clusters using CPM (resolution parameter=0.40), quality is 0.53333. +Membership: 0 0 1 1 1 0 + +Leiden found 2 clusters using modularity, undirected, quality is 0.50000. +Membership: 0 1 + +Leiden found 0 clusters using modularity, undirected, quality is nan. +Membership: + +Leiden found 5 clusters using modularity, undirected, quality is nan. +Membership: 0 1 2 3 4 + diff --git a/tests/unit/community_walktrap.c b/tests/unit/community_walktrap.c new file mode 100644 index 0000000..f365569 --- /dev/null +++ b/tests/unit/community_walktrap.c @@ -0,0 +1,187 @@ +/* + igraph library. + Copyright (C) 2006-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + + +int main(void) { + igraph_t graph; + + igraph_matrix_int_t merges; + igraph_vector_t modularity; + igraph_vector_int_t membership; + igraph_vector_t weights; + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Basic test\n\n"); + + /* Simple unweighted graph */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, -1); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + + igraph_community_walktrap(&graph, NULL, 4, &merges, &modularity, &membership); + printf("Merges:\n"); + print_matrix_int(&merges); + + /* We replace modularity values which are very close to zero + * by exact zeros, so that we can have consistent test outputs + * across platforms. */ + printf("Modularity: "); + igraph_vector_zapsmall(&modularity, 1e-15); + print_vector(&modularity); + + printf("Membership: "); + print_vector_int(&membership); + + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + igraph_matrix_int_destroy(&merges); + + /* Test the case when modularity=NULL and membership=NULL as this caused a crash in + * the R interface, see https://github.com/igraph/rigraph/issues/289 */ + + printf("\nWithout modularity and membership calculation\n\n"); + + igraph_matrix_int_init(&merges, 0, 0); + + igraph_community_walktrap(&graph, NULL, 4, &merges, NULL, NULL); + printf("Merges:\n"); + print_matrix_int(&merges); + igraph_matrix_int_destroy(&merges); + + /* Test the case when merges=NULL but modularity is requested, + * as the result was previously invalid due to not incrementing + * the "merge index" during computation. */ + + printf("\nWithout merges matrix calculation\n\n"); + + igraph_vector_init(&modularity, 0); + igraph_community_walktrap(&graph, NULL, 4, NULL, &modularity, NULL); + printf("Modularity:\n"); + igraph_vector_zapsmall(&modularity, 1e-15); + print_vector(&modularity); + igraph_vector_destroy(&modularity); + + igraph_destroy(&graph); + + /* Test for bug https://github.com/igraph/igraph/issues/2042 */ + + printf("\nBug 2042\n\n"); + + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, + 0,1, -1); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + igraph_vector_init_real(&weights, 1, 0.2); + + igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership); + printf("Merges:\n"); + print_matrix_int(&merges); + + printf("Modularity: "); + igraph_vector_zapsmall(&modularity, 1e-15); + print_vector(&modularity); + + printf("Membership: "); + print_vector_int(&membership); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + igraph_matrix_int_destroy(&merges); + + igraph_destroy(&graph); + + printf("\nSmall weighted graph\n\n"); + + igraph_ring(&graph, 6, IGRAPH_UNDIRECTED, 0, 1); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + igraph_vector_init_real(&weights, 6, + 1.0, 0.5, 0.25, 0.75, 1.25, 1.5); + + igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership); + printf("Merges:\n"); + print_matrix_int(&merges); + + printf("Modularity: "); + igraph_vector_zapsmall(&modularity, 1e-15); + print_vector(&modularity); + + printf("Membership: "); + print_vector_int(&membership); + + /* Negative weights are not allowed */ + VECTOR(weights)[0] = -0.1; + CHECK_ERROR(igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership), IGRAPH_EINVAL); + + /* Invalid weight vector size */ + VECTOR(weights)[0] = 0.1; + igraph_vector_pop_back(&weights); + CHECK_ERROR(igraph_community_walktrap(&graph, &weights, 4, &merges, &modularity, &membership), IGRAPH_EINVAL); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + igraph_matrix_int_destroy(&merges); + + igraph_destroy(&graph); + + printf("\nIsolated vertices\n\n"); + + igraph_empty(&graph, 5, IGRAPH_UNDIRECTED); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + + igraph_community_walktrap(&graph, NULL, 4, &merges, &modularity, &membership); + printf("Merges:\n"); + print_matrix_int(&merges); + + printf("Modularity: "); + igraph_vector_zapsmall(&modularity, 1e-15); + print_vector(&modularity); + + printf("Membership: "); + print_vector_int(&membership); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + igraph_matrix_int_destroy(&merges); + + igraph_destroy(&graph); + + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/community_walktrap.out b/tests/unit/community_walktrap.out new file mode 100644 index 0000000..8a7d283 --- /dev/null +++ b/tests/unit/community_walktrap.out @@ -0,0 +1,43 @@ +Basic test + +Merges: +[ 1 2 + 0 3 ] +Modularity: ( -0.333333 -0.222222 0 ) +Membership: ( 0 0 0 ) + +Without modularity and membership calculation + +Merges: +[ 1 2 + 0 3 ] + +Without merges matrix calculation + +Modularity: +( -0.333333 -0.222222 0 ) + +Bug 2042 + +Merges: +[ 0 1 ] +Modularity: ( -0.5 0 ) +Membership: ( 0 0 1 ) + +Small weighted graph + +Merges: +[ 3 4 + 1 2 + 0 5 + 7 8 + 6 9 ] +Modularity: ( -0.196145 -0.0895692 -0.0147392 0.146259 0.122449 0 ) +Membership: ( 0 1 1 2 2 0 ) + +Isolated vertices + +Merges: +[ 0-by-2 ] +Modularity: ( NaN ) +Membership: ( 0 1 2 3 4 ) diff --git a/tests/unit/components.c b/tests/unit/components.c new file mode 100644 index 0000000..e7e213c --- /dev/null +++ b/tests/unit/components.c @@ -0,0 +1,97 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void compute_and_print(const igraph_t *g, igraph_connectedness_t mode) { + igraph_vector_int_t membership, csize; + igraph_int_t no; + + igraph_vector_int_init(&membership, 1); + igraph_vector_int_init(&csize, 1); + + igraph_connected_components(g, &membership, &csize, &no, mode); + + /* Canonicalize membership representation */ + igraph_reindex_membership(&membership, NULL, NULL); + + printf("Mode: %s\n", mode == IGRAPH_STRONG ? "strong" : "weak"); + print_vector_int(&membership); + print_vector_int(&csize); + printf("No. of components: %" IGRAPH_PRId "\n", no); + + igraph_vector_int_destroy(&csize); + igraph_vector_int_destroy(&membership); +} + +int main(void) { + igraph_t g; + + /* Component calculations are run twice to exercise the cache */ + + printf("\nNull graph (not connected)\n"); + igraph_empty(&g, 0, IGRAPH_DIRECTED); + compute_and_print(&g, IGRAPH_STRONG); + compute_and_print(&g, IGRAPH_STRONG); + igraph_invalidate_cache(&g); + compute_and_print(&g, IGRAPH_WEAK); + compute_and_print(&g, IGRAPH_WEAK); + igraph_destroy(&g); + + printf("\nSingleton graph (connected)\n"); + igraph_empty(&g, 1, IGRAPH_DIRECTED); + compute_and_print(&g, IGRAPH_STRONG); + compute_and_print(&g, IGRAPH_STRONG); + igraph_invalidate_cache(&g); + compute_and_print(&g, IGRAPH_WEAK); + compute_and_print(&g, IGRAPH_WEAK); + igraph_destroy(&g); + + printf("\nKautz graph (connected)\n"); + igraph_kautz(&g, 2, 2); + compute_and_print(&g, IGRAPH_STRONG); + compute_and_print(&g, IGRAPH_STRONG); + igraph_invalidate_cache(&g); + compute_and_print(&g, IGRAPH_WEAK); + compute_and_print(&g, IGRAPH_WEAK); + igraph_destroy(&g); + + printf("\nDirected 2-path\n"); + igraph_small(&g, 2, IGRAPH_DIRECTED, 0,1, -1); + compute_and_print(&g, IGRAPH_STRONG); + compute_and_print(&g, IGRAPH_STRONG); + igraph_invalidate_cache(&g); + compute_and_print(&g, IGRAPH_WEAK); + compute_and_print(&g, IGRAPH_WEAK); + igraph_destroy(&g); + + printf("\nTwo disjoint 3-cycles\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, 3,4, 4,5, 5,3, -1); + compute_and_print(&g, IGRAPH_STRONG); + compute_and_print(&g, IGRAPH_STRONG); + igraph_invalidate_cache(&g); + compute_and_print(&g, IGRAPH_WEAK); + compute_and_print(&g, IGRAPH_WEAK); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/components.out b/tests/unit/components.out new file mode 100644 index 0000000..7f8a9d7 --- /dev/null +++ b/tests/unit/components.out @@ -0,0 +1,90 @@ + +Null graph (not connected) +Mode: strong +( ) +( ) +No. of components: 0 +Mode: strong +( ) +( ) +No. of components: 0 +Mode: weak +( ) +( ) +No. of components: 0 +Mode: weak +( ) +( ) +No. of components: 0 + +Singleton graph (connected) +Mode: strong +( 0 ) +( 1 ) +No. of components: 1 +Mode: strong +( 0 ) +( 1 ) +No. of components: 1 +Mode: weak +( 0 ) +( 1 ) +No. of components: 1 +Mode: weak +( 0 ) +( 1 ) +No. of components: 1 + +Kautz graph (connected) +Mode: strong +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 +Mode: strong +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 +Mode: weak +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 +Mode: weak +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 + +Directed 2-path +Mode: strong +( 0 1 ) +( 1 1 ) +No. of components: 2 +Mode: strong +( 0 1 ) +( 1 1 ) +No. of components: 2 +Mode: weak +( 0 0 ) +( 2 ) +No. of components: 1 +Mode: weak +( 0 0 ) +( 2 ) +No. of components: 1 + +Two disjoint 3-cycles +Mode: strong +( 0 0 0 1 1 1 ) +( 3 3 ) +No. of components: 2 +Mode: strong +( 0 0 0 1 1 1 ) +( 3 3 ) +No. of components: 2 +Mode: weak +( 0 0 0 1 1 1 ) +( 3 3 ) +No. of components: 2 +Mode: weak +( 0 0 0 1 1 1 ) +( 3 3 ) +No. of components: 2 diff --git a/tests/unit/constructor-failure.c b/tests/unit/constructor-failure.c new file mode 100644 index 0000000..6ab0cdd --- /dev/null +++ b/tests/unit/constructor-failure.c @@ -0,0 +1,202 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/* This test attempts to verify that when adding vertices or edges to a graph fails, + * the graph, as well as its attribute table, are left in a consistent state. + * This test operates with the C attribute handler. Since this attribute table is + * not directly accessible, only limited checks are done to verify its consistency. + * + * We use the printignore() error handler insted of ignore() as this eases debugging. + * Thus, this test has not corresponding expected output file. All checks are done + * directly in the code. + */ + +/* Check that the graph stays in a consistent state upon failure. + * This macro does not destroy the graph. */ +#define CHECK1(funcall) \ + do { \ + igraph_int_t vcount = igraph_vcount(&graph); \ + igraph_int_t ecount = igraph_ecount(&graph); \ + igraph_error_handler_t *handler; \ + handler = igraph_set_error_handler(igraph_error_handler_printignore); \ + IGRAPH_ASSERT(funcall != IGRAPH_SUCCESS); \ + igraph_set_error_handler(handler); \ + verify_graph(&graph, vcount, ecount); \ + } while (0) + +/* Check that there is no invalid memory access if the graph is on the finally + * stack when failure occurs. This macro unrolls the finally stack and destroys + * the graph. */ +#define CHECK2(funcall) \ + do { \ + igraph_error_handler_t *handler; \ + IGRAPH_FINALLY(igraph_destroy, &graph); \ + handler = igraph_set_error_handler(igraph_error_handler_printignore); \ + IGRAPH_ASSERT(funcall != IGRAPH_SUCCESS); \ + igraph_set_error_handler(handler); \ + } while (0) + +/* Do both checks, destroying the graph and resetting the finally stack in the process. */ +#define CHECK_DESTROY(funcall) \ + do { CHECK1(funcall); CHECK2(funcall); } while (0) + +void verify_graph(const igraph_t *graph, igraph_int_t vcount, igraph_int_t ecount) { + IGRAPH_ASSERT(igraph_vector_int_size(&graph->from) == ecount); + IGRAPH_ASSERT(igraph_vector_int_size(&graph->to) == ecount); + IGRAPH_ASSERT(igraph_vector_int_size(&graph->oi) == ecount); + IGRAPH_ASSERT(igraph_vector_int_size(&graph->ii) == ecount); + IGRAPH_ASSERT(igraph_vector_int_size(&graph->os) == vcount + 1); + IGRAPH_ASSERT(igraph_vector_int_size(&graph->is) == vcount + 1); +} + +int main(void) { + igraph_t graph; + + /* Enable attribute handler */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Helper vectors */ + + igraph_vector_t nvalues; + igraph_vector_init(&nvalues, 0); + + igraph_strvector_t svalues; + igraph_strvector_init(&svalues, 0); + + igraph_vector_bool_t bvalues; + igraph_vector_bool_init(&bvalues, 0); + + const igraph_vector_int_t edges = IGRAPH_VECTOR_NULL; /* 2 edges */ + igraph_vector_int_init_int((igraph_vector_int_t *) &edges, 4, 0,1, 1,2); + + /* attr1, numeric, 3 values */ + igraph_attribute_record_list_t attr1; + igraph_attribute_record_list_init(&attr1, 1); + + igraph_attribute_record_t* a1 = igraph_attribute_record_list_get_ptr(&attr1, 0); + igraph_attribute_record_set_name(a1, "foo"); + igraph_attribute_record_set_type(a1, IGRAPH_ATTRIBUTE_NUMERIC); + + igraph_vector_t vec_a1; + igraph_vector_init_int(&vec_a1, 3, 1, 2, 3); + igraph_vector_update(a1->value.as_vector, &vec_a1); + + igraph_vs_t vs1; + igraph_vs_vector_small(&vs1, 0, 1, 2, -1); + + /* attr2, string, 2 values */ + igraph_attribute_record_list_t attr2; + igraph_attribute_record_list_init(&attr2, 1); + + igraph_attribute_record_t* a2 = igraph_attribute_record_list_get_ptr(&attr2, 0); + igraph_attribute_record_set_name(a2, "bar"); + igraph_attribute_record_set_type(a2, IGRAPH_ATTRIBUTE_STRING); + + igraph_strvector_t vec_a2; + igraph_strvector_init(&vec_a2, 2); + igraph_strvector_set(&vec_a2, 0, "one"); + igraph_strvector_set(&vec_a2, 1, "two"); + igraph_strvector_update(a2->value.as_strvector, &vec_a2); + + /* attr3, boolean, 2 values */ + igraph_attribute_record_list_t attr3; + igraph_attribute_record_list_init(&attr3, 1); + + igraph_attribute_record_t* a3 = igraph_attribute_record_list_get_ptr(&attr3, 0); + igraph_attribute_record_set_name(a3, "baz"); + igraph_attribute_record_set_type(a3, IGRAPH_ATTRIBUTE_BOOLEAN); + + igraph_vector_bool_t vec_a3; + igraph_vector_bool_init_int(&vec_a3, 2, 1, 0); + igraph_vector_bool_update(a3->value.as_vector_bool, &vec_a3); + + igraph_vs_t vs3; + igraph_vs_vector_small(&vs3, 3, 4, -1); + + /* Perform tests */ + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + CHECK_DESTROY(igraph_add_vertices(&graph, 2, &attr1)); /* wrong number of vertices */ + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_add_vertices(&graph, 3, &attr1); + CHECK_DESTROY(igraph_add_edges(&graph, &edges, &attr1)); /* wrong number of edges */ + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_add_vertices(&graph, 3, &attr1); + igraph_add_edges(&graph, &edges, &attr2); + igraph_add_vertices(&graph, 2, &attr3); + + CHECK1(igraph_add_vertices(&graph, 2, &attr1)); /* wrong number of vertices */ + /* Attribute handling still works and graph has correct attributes after failure to add vertices? */ + IGRAPH_ASSERT(igraph_cattribute_VANV(&graph, a1->name, vs1, &nvalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_all_e(&vec_a1, &nvalues)); + IGRAPH_ASSERT(igraph_cattribute_EASV(&graph, a2->name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &svalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_strvector_size(&svalues) == igraph_strvector_size(&vec_a2)); + for (igraph_int_t i=0; i < igraph_strvector_size(&svalues); ++i) { + IGRAPH_ASSERT(strcmp(igraph_strvector_get(&vec_a2, i), igraph_strvector_get(&svalues, i)) == 0); + } + IGRAPH_ASSERT(igraph_cattribute_VABV(&graph, a3->name, vs3, &bvalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_bool_all_e(&vec_a3, &bvalues)); + + CHECK1(igraph_add_edges(&graph, &edges, &attr1)); /* wrong number of edges */ + /* Attribute handling still works and graph has correct attributes after failure to add vertices? */ + IGRAPH_ASSERT(igraph_cattribute_VANV(&graph, a1->name, vs1, &nvalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_all_e(&vec_a1, &nvalues)); + IGRAPH_ASSERT(igraph_cattribute_EASV(&graph, a2->name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &svalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_strvector_size(&svalues) == igraph_strvector_size(&vec_a2)); + for (igraph_int_t i=0; i < igraph_strvector_size(&svalues); ++i) { + IGRAPH_ASSERT(strcmp(igraph_strvector_get(&vec_a2, i), igraph_strvector_get(&svalues, i)) == 0); + } + IGRAPH_ASSERT(igraph_cattribute_VABV(&graph, a3->name, vs3, &bvalues) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_bool_all_e(&vec_a3, &bvalues)); + + igraph_destroy(&graph); + + /* Release attr1 */ + + igraph_attribute_record_list_destroy(&attr1); + igraph_vs_destroy(&vs1); + + /* Release attr2 */ + + igraph_attribute_record_list_destroy(&attr2); + + /* Release attr3 */ + + igraph_attribute_record_list_destroy(&attr3); + igraph_vs_destroy(&vs3); + + /* Release helpers */ + + igraph_vector_destroy(&vec_a1); + igraph_strvector_destroy(&vec_a2); + igraph_vector_bool_destroy(&vec_a3); + + igraph_vector_int_destroy((igraph_vector_int_t *) &edges); + igraph_strvector_destroy(&svalues); + igraph_vector_destroy(&nvalues); + igraph_vector_bool_destroy(&bvalues); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/coreness.c b/tests/unit/coreness.c new file mode 100644 index 0000000..be826b2 --- /dev/null +++ b/tests/unit/coreness.c @@ -0,0 +1,224 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void validate_coreness( + const igraph_t* graph, const igraph_vector_int_t* coreness, + igraph_neimode_t mode +) { + igraph_int_t i, j, min_coreness, max_coreness; + igraph_int_t nv = igraph_vcount(graph); + igraph_t subgraph; + igraph_vs_t vs; + igraph_vector_int_t vids, degree; + + IGRAPH_ASSERT(igraph_vector_int_size(coreness) == nv); + if (igraph_vcount(graph) < 1) { + return; + } + + min_coreness = igraph_vector_int_min(coreness); + max_coreness = igraph_vector_int_max(coreness); + + IGRAPH_ASSERT(min_coreness >= 0); + + igraph_vector_int_init(&vids, 0); + igraph_vector_int_init(°ree, 0); + + for (i = max_coreness; i >= 0; i--) { + igraph_vector_int_clear(&vids); + for (j = 0; j < nv; j++) { + if (VECTOR(*coreness)[j] >= i) { + igraph_vector_int_push_back(&vids, j); + } + } + + igraph_vs_vector(&vs, &vids); + igraph_induced_subgraph(graph, &subgraph, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_vs_destroy(&vs); + + igraph_degree(&subgraph, °ree, igraph_vss_all(), mode, IGRAPH_LOOPS_TWICE); + for (j = 0; j < igraph_vcount(&subgraph); j++) { + IGRAPH_ASSERT(VECTOR(degree)[j] >= i); + } + + igraph_destroy(&subgraph); + } + + igraph_vector_int_destroy(°ree); + igraph_vector_int_destroy(&vids); +} + +void test_graph(const igraph_t* graph, igraph_bool_t print) { + igraph_vector_int_t coreness; + + igraph_vector_int_init(&coreness, 0); + + if (igraph_is_directed(graph)) { + igraph_coreness(graph, &coreness, IGRAPH_ALL); + validate_coreness(graph, &coreness, IGRAPH_ALL); + if (print) { + printf("mode = ALL: "); + print_vector_int(&coreness); + } + + igraph_coreness(graph, &coreness, IGRAPH_OUT); + validate_coreness(graph, &coreness, IGRAPH_OUT); + if (print) { + printf("mode = OUT: "); + print_vector_int(&coreness); + } + + igraph_coreness(graph, &coreness, IGRAPH_IN); + validate_coreness(graph, &coreness, IGRAPH_IN); + if (print) { + printf("mode = IN: "); + print_vector_int(&coreness); + } + } else { + igraph_coreness(graph, &coreness, IGRAPH_ALL); + validate_coreness(graph, &coreness, IGRAPH_ALL); + if (print) { + print_vector_int(&coreness); + } + } + + igraph_vector_int_destroy(&coreness); +} + +void add_loop_and_multiple_edges(igraph_t* graph, igraph_real_t loop_prob, igraph_real_t multi_prob) { + igraph_int_t i, n, from, to; + igraph_vector_int_t extra_edges; + + igraph_vector_int_init(&extra_edges, 0); + + n = igraph_vcount(graph); + for (i = 0; i < n; i++) { + if (RNG_UNIF01() < loop_prob) { + igraph_vector_int_push_back(&extra_edges, i); + igraph_vector_int_push_back(&extra_edges, i); + } + } + + n = igraph_ecount(graph); + for (i = 0; i < n; i++) { + if (RNG_UNIF01() < multi_prob) { + igraph_edge(graph, i, &from, &to); + igraph_vector_int_push_back(&extra_edges, from); + igraph_vector_int_push_back(&extra_edges, to); + } + } + + igraph_add_edges(graph, &extra_edges, 0); + + igraph_vector_int_destroy(&extra_edges); +} + +void remove_some_edges(igraph_t* graph, igraph_real_t prob) { + igraph_int_t i, n; + igraph_vector_int_t to_remove; + + igraph_vector_int_init(&to_remove, 0); + + n = igraph_ecount(graph); + for (i = 0; i < n; i++) { + if (igraph_rng_get_unif01(igraph_rng_default()) < prob) { + igraph_vector_int_push_back(&to_remove, i); + } + } + + igraph_delete_edges(graph, igraph_ess_vector(&to_remove)); + + igraph_vector_int_destroy(&to_remove); +} + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 137); + + /* Empty and singleton graph */ + printf("Empty graph.\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + printf("Singleton graph.\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Simple full graph */ + printf("Full graph.\n"); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Full graph with loops */ + printf("Full graph with loops.\n"); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Full directed graph */ + printf("Full directed graph.\n"); + igraph_full(&g, 5, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Full directed graph */ + printf("Full directed graph with loops.\n"); + igraph_full(&g, 5, IGRAPH_DIRECTED, IGRAPH_LOOPS); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Zachary karate club */ + printf("Zachary karate club.\n"); + igraph_famous(&g, "zachary"); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Zachary karate club, randomly directed edges */ + printf("Zachary karate club, directed edges.\n"); + igraph_famous(&g, "zachary"); + igraph_to_directed(&g, IGRAPH_TO_DIRECTED_MUTUAL); + remove_some_edges(&g, 0.2); + test_graph(&g, /* print = */ 1); + igraph_destroy(&g); + + /* Zachary karate club with random loops and multi-edges */ + printf("Zachary karate club with loops and multi-edges.\n"); + for (int i = 0; i < 20; i++) { + igraph_famous(&g, "zachary"); + add_loop_and_multiple_edges(&g, 0.5, 0.2); + test_graph(&g, /* print = */ 0); + igraph_destroy(&g); + } + + /* Geometric random graph */ + printf("Geometric random graph.\n"); + igraph_grg_game(&g, 100, 0.2, /* torus = */ 0, /* x = */ 0, /* y = */ 0); + test_graph(&g, /* print = */ 0); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/coreness.out b/tests/unit/coreness.out new file mode 100644 index 0000000..ccf47c6 --- /dev/null +++ b/tests/unit/coreness.out @@ -0,0 +1,24 @@ +Empty graph. +( ) +Singleton graph. +( 0 ) +Full graph. +( 4 4 4 4 4 ) +Full graph with loops. +( 6 6 6 6 6 ) +Full directed graph. +mode = ALL: ( 8 8 8 8 8 ) +mode = OUT: ( 4 4 4 4 4 ) +mode = IN: ( 4 4 4 4 4 ) +Full directed graph with loops. +mode = ALL: ( 10 10 10 10 10 ) +mode = OUT: ( 5 5 5 5 5 ) +mode = IN: ( 5 5 5 5 5 ) +Zachary karate club. +( 4 4 4 4 3 3 3 4 4 2 3 1 2 4 2 2 2 2 2 3 2 2 2 3 3 3 2 3 3 3 4 3 4 4 ) +Zachary karate club, directed edges. +mode = ALL: ( 6 6 6 6 4 4 4 6 6 2 4 2 3 6 4 2 3 3 4 4 1 0 2 5 4 4 3 4 3 5 6 5 6 5 ) +mode = OUT: ( 3 3 3 3 2 2 2 3 3 1 2 1 1 3 2 2 1 2 2 2 1 0 1 2 2 2 1 2 2 2 3 2 3 2 ) +mode = IN: ( 3 3 3 3 2 2 2 3 3 1 2 1 2 3 2 0 2 1 2 2 0 0 1 3 2 1 2 2 1 3 3 2 3 3 ) +Zachary karate club with loops and multi-edges. +Geometric random graph. diff --git a/tests/unit/cutheap.c b/tests/unit/cutheap.c new file mode 100644 index 0000000..59636aa --- /dev/null +++ b/tests/unit/cutheap.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "core/cutheap.h" + +#include "test_utilities.h" + +int main(void) { + igraph_i_cutheap_t ch; + igraph_int_t i; + + igraph_i_cutheap_init(&ch, 10); + + for (i = 0; i < 10; i++) { + igraph_i_cutheap_update(&ch, i, i); + } + while (!igraph_i_cutheap_empty(&ch)) { + igraph_int_t idx = igraph_i_cutheap_popmax(&ch); + printf("%" IGRAPH_PRId " ", idx); + } + printf("\n"); + + igraph_i_cutheap_destroy(&ch); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/cutheap.out b/tests/unit/cutheap.out new file mode 100644 index 0000000..b53cb66 --- /dev/null +++ b/tests/unit/cutheap.out @@ -0,0 +1 @@ +9 8 7 6 5 4 3 2 1 0 diff --git a/tests/unit/cycle_bases.c b/tests/unit/cycle_bases.c new file mode 100644 index 0000000..39d12a6 --- /dev/null +++ b/tests/unit/cycle_bases.c @@ -0,0 +1,184 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void print_check_destroy(igraph_t *graph, igraph_vector_int_list_t *result) { + igraph_int_t i, rank = igraph_vector_int_list_size(result); + igraph_int_t ecount = igraph_ecount(graph), vcount = igraph_vcount(graph); + igraph_int_t ccount; + + for (i=0; i < rank; ++i) { + igraph_vector_int_t *cycle = igraph_vector_int_list_get_ptr(result, i); + IGRAPH_ASSERT(igraph_vector_int_size(cycle) > 0); + igraph_int_t first_edge = VECTOR(*cycle)[0]; + igraph_int_t last_edge = VECTOR(*cycle)[igraph_vector_int_size(cycle) - 1]; + /* Verify that first and last edges of the cycle share a vertex */ + IGRAPH_ASSERT( + (IGRAPH_FROM(graph, first_edge) == IGRAPH_FROM(graph, last_edge) || + IGRAPH_FROM(graph, first_edge) == IGRAPH_TO(graph, last_edge)) || + (IGRAPH_TO(graph, first_edge) == IGRAPH_FROM(graph, last_edge) || + IGRAPH_TO(graph, first_edge) == IGRAPH_TO(graph, last_edge)) + ); + print_vector_int(cycle); + } + igraph_connected_components(graph, NULL, NULL, &ccount, IGRAPH_WEAK); + IGRAPH_ASSERT(rank == ecount - vcount + ccount); + igraph_destroy(graph); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t result; + igraph_int_t rank; + + igraph_vector_int_list_init(&result, 0); + + printf("FUNDAMENTAL CYCLE BASIS\n"); + + printf("\nNull graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\nSingleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\nSingle vertex with loop\n"); + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, + 0,0, + -1); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\nTree\n"); + igraph_kary_tree(&graph, 3, 2, IGRAPH_TREE_UNDIRECTED); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\n2-cycle\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,1, 0,1, + -1); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\nDisconnected\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 1,2, 2,3, 3,1, + 4,5, 5,4, 4,5, + 6,7, 7,8, 8,9, 9,6, 6,8, + 10,10, 10,11, + 12,12, + -1); + igraph_fundamental_cycles(&graph, NULL, &result, -1, -1); + print_check_destroy(&graph, &result); + + printf("\nMINIMUM WEIGHT CYCLE BASIS\n"); + + printf("\nNull graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\nSingleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\nSingle vertex with loop\n"); + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, + 0,0, + -1); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\nTree\n"); + igraph_kary_tree(&graph, 3, 2, IGRAPH_TREE_UNDIRECTED); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\n2-cycle\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,1, 0,1, + -1); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\nDisconnected\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 1,2, 2,3, 3,1, + 4,5, 5,4, 4,5, + 6,7, 7,8, 8,9, 9,6, 6,8, + 10,10, 10,11, + 12,12, + -1); + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + print_check_destroy(&graph, &result); + + printf("\nPeriodic (5,6)-grid\n"); + + + { + igraph_vector_int_t dimvec; + igraph_vector_bool_t periodic; + + igraph_vector_int_init_int_end(&dimvec, -1, + 1, 5, 6, -1); + + igraph_vector_bool_init(&periodic, igraph_vector_int_size(&dimvec)); + igraph_vector_bool_fill(&periodic, 1); + + igraph_square_lattice(&graph, &dimvec, 1, IGRAPH_ADJ_UNDIRECTED, /* mutual */ false, &periodic); + + igraph_vector_bool_destroy(&periodic); + igraph_vector_int_destroy(&dimvec); + } + + igraph_minimum_cycle_basis(&graph, NULL, &result, /* cutoff */ -1, /* complete */ true, /* ordered */ true); + + rank = igraph_vector_int_list_size(&result); + + /* In a periodic grid graph, all elements in the minimum basis have size 4, + * except two, which have size equal to the grid dimensions. */ + + IGRAPH_ASSERT(igraph_vector_int_size(&VECTOR(result)[0]) == 4); + IGRAPH_ASSERT(igraph_vector_int_size(&VECTOR(result)[rank-1]) == 6); + IGRAPH_ASSERT(igraph_vector_int_size(&VECTOR(result)[rank-2]) == 5); + IGRAPH_ASSERT(igraph_vector_int_size(&VECTOR(result)[rank-3]) == 4); + + print_check_destroy(&graph, &result); + +#if 0 + /* This is for benchmarking */ + igraph_rng_seed(igraph_rng_default(), 42); + igraph_watts_strogatz_game(&graph, 3, 10, 2, 0.1, 0, 0); + igraph_minimum_cycle_basis(&graph, /* cutoff */ -1, /* complete */ true, * ordered */ true, &result); + print_check_destroy(&graph, &result); +#endif + + igraph_vector_int_list_destroy(&result); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/cycle_bases.out b/tests/unit/cycle_bases.out new file mode 100644 index 0000000..ec00893 --- /dev/null +++ b/tests/unit/cycle_bases.out @@ -0,0 +1,78 @@ +FUNDAMENTAL CYCLE BASIS + +Null graph + +Singleton graph + +Single vertex with loop +( 0 ) + +Tree + +2-cycle +( 0 1 ) + +Disconnected +( 1 0 2 ) +( 4 5 ) +( 3 5 ) +( 7 6 10 ) +( 8 10 9 ) +( 11 ) +( 13 ) + +MINIMUM WEIGHT CYCLE BASIS + +Null graph + +Singleton graph + +Single vertex with loop +( 0 ) + +Tree + +2-cycle +( 0 1 ) + +Disconnected +( 11 ) +( 13 ) +( 3 5 ) +( 4 5 ) +( 0 1 2 ) +( 6 7 10 ) +( 8 9 10 ) + +Periodic (5,6)-grid +( 0 1 10 3 ) +( 0 51 50 53 ) +( 1 8 9 18 ) +( 2 3 12 5 ) +( 2 53 52 55 ) +( 4 5 14 7 ) +( 4 55 54 57 ) +( 6 7 16 9 ) +( 6 57 56 59 ) +( 8 59 58 51 ) +( 10 11 20 13 ) +( 11 18 19 28 ) +( 12 13 22 15 ) +( 14 15 24 17 ) +( 16 17 26 19 ) +( 20 21 30 23 ) +( 21 28 29 38 ) +( 22 23 32 25 ) +( 24 25 34 27 ) +( 26 27 36 29 ) +( 30 31 40 33 ) +( 31 38 39 48 ) +( 32 33 42 35 ) +( 34 35 44 37 ) +( 36 37 46 39 ) +( 40 41 50 43 ) +( 41 48 49 58 ) +( 42 43 52 45 ) +( 44 45 54 47 ) +( 0 8 6 4 2 ) +( 1 51 41 31 21 11 ) diff --git a/tests/unit/d_indheap.c b/tests/unit/d_indheap.c new file mode 100644 index 0000000..6169da6 --- /dev/null +++ b/tests/unit/d_indheap.c @@ -0,0 +1,102 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "core/indheap.h" + +#include "test_utilities.h" + +int main(void) { + + igraph_d_indheap_t h; + igraph_int_t idx1, idx2; + + /* igraph_d_indheap_init, igraph_d_indheap_destroy */ + igraph_d_indheap_init(&h, 0); + igraph_d_indheap_destroy(&h); + igraph_d_indheap_init(&h, 100); + igraph_d_indheap_destroy(&h); + + /* igraph_d_indheap_empty, igraph_d_indheap_size */ + igraph_d_indheap_init(&h, 10); + if (!igraph_d_indheap_empty(&h)) { + return 1; + } + if (igraph_d_indheap_size(&h) != 0) { + return 2; + } + igraph_d_indheap_push(&h, 10, 0, 0); + if (igraph_d_indheap_empty(&h)) { + return 3; + } + if (igraph_d_indheap_size(&h) != 1) { + return 4; + } + + /* igraph_d_indheap_push */ + igraph_d_indheap_push(&h, 9, 9, 8); + igraph_d_indheap_push(&h, 3, 3, 2); + igraph_d_indheap_push(&h, 2, 2, 1); + igraph_d_indheap_push(&h, 1, 1, 0); + igraph_d_indheap_push(&h, 7, 7, 6); + igraph_d_indheap_push(&h, 4, 4, 3); + igraph_d_indheap_push(&h, 0, 0, 1); + igraph_d_indheap_push(&h, 6, 6, 5); + igraph_d_indheap_push(&h, 5, 5, 4); + igraph_d_indheap_push(&h, 8, 8, 7); + + /* igraph_d_indheap_max, igraph_d_indheap_delete_max */ + while (!igraph_d_indheap_empty(&h)) { + printf("% " IGRAPH_PRId , (igraph_int_t)igraph_d_indheap_max(&h)); + printf("% " IGRAPH_PRId "\n", (igraph_int_t)igraph_d_indheap_delete_max(&h)); + } + + /* igraph_d_indheap_reserve */ + igraph_d_indheap_reserve(&h, 5); + igraph_d_indheap_reserve(&h, 20); + igraph_d_indheap_reserve(&h, 0); + igraph_d_indheap_reserve(&h, 3); + + /* igraph_d_indheap_max_index */ + igraph_d_indheap_push(&h, 0, 0, 1); + igraph_d_indheap_push(&h, 8, 8, 7); + igraph_d_indheap_push(&h, 2, 2, 1); + igraph_d_indheap_push(&h, 7, 7, 6); + igraph_d_indheap_push(&h, 9, 9, 8); + igraph_d_indheap_push(&h, 4, 4, 3); + igraph_d_indheap_push(&h, 3, 3, 2); + igraph_d_indheap_push(&h, 5, 5, 4); + igraph_d_indheap_push(&h, 1, 1, 0); + igraph_d_indheap_push(&h, 6, 6, 5); + while (!igraph_d_indheap_empty(&h)) { + igraph_d_indheap_max_index(&h, &idx1, &idx2); + printf(" %" IGRAPH_PRId " %" IGRAPH_PRId, idx1, idx2); + igraph_d_indheap_delete_max(&h); + } + printf("\n"); + igraph_d_indheap_destroy(&h); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/d_indheap.out b/tests/unit/d_indheap.out new file mode 100644 index 0000000..7b58a46 --- /dev/null +++ b/tests/unit/d_indheap.out @@ -0,0 +1,12 @@ + 10 10 + 9 9 + 8 8 + 7 7 + 6 6 + 5 5 + 4 4 + 3 3 + 2 2 + 1 1 + 0 0 + 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 1 diff --git a/tests/unit/dgemv.c b/tests/unit/dgemv.c new file mode 100644 index 0000000..8bed03a --- /dev/null +++ b/tests/unit/dgemv.c @@ -0,0 +1,103 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Matrix-vector multiplication: y = A.x */ +void matmul(const igraph_matrix_t *A, const igraph_vector_t *x, igraph_vector_t *y, igraph_real_t beta) { + igraph_int_t i, j, nr = igraph_matrix_nrow(A), nc = igraph_matrix_ncol(A); + + IGRAPH_ASSERT(nc == igraph_vector_size(x)); + IGRAPH_ASSERT(nr == igraph_vector_size(y)); + + igraph_vector_scale(y, beta); + + for (i=0; i < nr; ++i) { + for (j=0; j < nc; ++j) { + VECTOR(*y)[i] += MATRIX(*A, i, j) * VECTOR(*x)[j]; + } + } +} + +int main(void) { + igraph_matrix_t A; + igraph_vector_t x, y1, y2; + igraph_int_t i, j; + const igraph_int_t nr = 5, nc = 8; + + igraph_rng_seed(igraph_rng_default(), 54632); + + igraph_matrix_init(&A, nr, nc); + igraph_vector_init(&x, nc); + + /* Fill with arbitrary values. Should be zeroes by beta. */ + igraph_vector_init_range(&y1, 1, nr + 1); + igraph_vector_init_copy(&y2, &y1); + + for (i=0; i < nr; ++i) { + for (j=0; j < nc; ++j) { + MATRIX(A, i, j) = (igraph_real_t) RNG_INTEGER(-10, 10); + } + } + + for (j=0; j < nc; ++j) { + VECTOR(x)[j] = (igraph_real_t) RNG_INTEGER(-10, 10); + } + + printf("Input matrix A:\n"); + print_matrix(&A); + + printf("\nInput vector x:\n"); + print_vector(&x); + + igraph_blas_dgemv(0, 1, &A, &x, 0, &y1); + matmul(&A, &x, &y2, 0); + + printf("\nResult vector DGEMV:\n"); + print_vector(&y1); + + printf("\nResult vector naive:\n"); + print_vector(&y2); + + /* Results should be exact since all values are integers */ + IGRAPH_ASSERT(igraph_vector_all_e(&y1, &y2)); + + printf("\nAdding to previous result with beta=2:\n"); + + igraph_blas_dgemv(0, 1, &A, &x, 2, &y1); + matmul(&A, &x, &y2, 2); + + printf("\nResult vector DGEMV:\n"); + print_vector(&y1); + + printf("\nResult vector naive:\n"); + print_vector(&y2); + + IGRAPH_ASSERT(igraph_vector_all_e(&y1, &y2)); + + igraph_vector_destroy(&y2); + igraph_vector_destroy(&y1); + igraph_vector_destroy(&x); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/dgemv.out b/tests/unit/dgemv.out new file mode 100644 index 0000000..51b4599 --- /dev/null +++ b/tests/unit/dgemv.out @@ -0,0 +1,23 @@ +Input matrix A: +[ -8 -3 6 -8 1 6 -10 -6 + 5 9 0 0 -10 10 7 -6 + -6 -9 5 7 -10 -7 -10 -10 + -7 -7 -5 0 6 10 5 2 + 10 2 -6 4 -2 -4 -8 -2 ] + +Input vector x: +( -1 -9 -5 -5 -6 -8 3 2 ) + +Result vector DGEMV: +( -51 -97 93 -2 -2 ) + +Result vector naive: +( -51 -97 93 -2 -2 ) + +Adding to previous result with beta=2: + +Result vector DGEMV: +( -153 -291 279 -6 -6 ) + +Result vector naive: +( -153 -291 279 -6 -6 ) diff --git a/tests/unit/edge_selectors.c b/tests/unit/edge_selectors.c new file mode 100644 index 0000000..1f30982 --- /dev/null +++ b/tests/unit/edge_selectors.c @@ -0,0 +1,143 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check(igraph_t *graph, igraph_es_t *es) { + igraph_eit_t eit; + igraph_int_t edge; + IGRAPH_ASSERT(igraph_eit_create(graph, *es, &eit) == IGRAPH_SUCCESS); + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + edge = IGRAPH_EIT_GET(eit); + printf("%" IGRAPH_PRId " %" IGRAPH_PRId "\n", IGRAPH_FROM(graph, edge), IGRAPH_TO(graph, edge)); + } + igraph_eit_destroy(&eit); +} + +int main(void) { + igraph_t g, g_no_vertices, g_no_edges; + igraph_es_t es, es_copy; + igraph_vector_int_t v, check_as_vector; + igraph_eit_t eit; + + igraph_small(&g, 5, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, -1); + igraph_small(&g_no_vertices, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_no_edges, 5, IGRAPH_UNDIRECTED, -1); + + printf("Checking es_vector:\n"); + igraph_vector_int_init_int(&v, 3, 2, 3, 4); + igraph_es_vector(&es, &v); + check(&g, &es); + CHECK_ERROR(igraph_eit_create(&g_no_edges, es, &eit), IGRAPH_EINVEID); + CHECK_ERROR(igraph_eit_create(&g_no_vertices, es, &eit), IGRAPH_EINVEID); + + printf("es_vector_copy\n"); + igraph_es_vector_copy(&es, &v); + check(&g, &es); + + printf("es_copy\n"); + igraph_es_copy(&es_copy, &es); + check(&g, &es); + igraph_es_destroy(&es_copy); + + printf("es_as_vector\n"); + igraph_vector_int_clear(&v); + igraph_es_as_vector(&g, es, &v); + igraph_vector_int_print(&v); + igraph_es_destroy(&es); + igraph_vector_int_destroy(&v); + + printf("es_vector with negative entry should fail.\n"); + igraph_vector_int_init_int(&v, 3, -2, 3, 4); + igraph_es_vector(&es, &v); + CHECK_ERROR(igraph_eit_create(&g, es, &eit), IGRAPH_EINVEID); + igraph_vector_int_destroy(&v); + + printf("Checking es_range:\n"); + igraph_es_range(&es, 2, 5); + check(&g, &es); + CHECK_ERROR(igraph_eit_create(&g_no_edges, es, &eit), IGRAPH_EINVEID); + CHECK_ERROR(igraph_eit_create(&g_no_vertices, es, &eit), IGRAPH_EINVEID); + + printf("Checking eit_as_vector using seq:\n"); + igraph_eit_create(&g, es, &eit); + igraph_vector_int_init_int(&check_as_vector, 0); + igraph_eit_as_vector(&eit, &check_as_vector); + igraph_vector_int_print(&check_as_vector); + igraph_vector_int_destroy(&check_as_vector); + + printf("Checking ess_range using es_range parameters:\n"); + es = igraph_ess_range(2, 5); + check(&g, &es); + CHECK_ERROR(igraph_eit_create(&g_no_edges, es, &eit), IGRAPH_EINVEID); + CHECK_ERROR(igraph_eit_create(&g_no_vertices, es, &eit), IGRAPH_EINVEID); + + printf("Checking whether ess_range accepts an empty range.\n"); + es = igraph_ess_range(2, 2); + check(&g, &es); + CHECK_ERROR(igraph_eit_create(&g_no_edges, es, &eit), IGRAPH_EINVEID); + CHECK_ERROR(igraph_eit_create(&g_no_vertices, es, &eit), IGRAPH_EINVEID); + + printf("Checking es_path:\n"); + igraph_vector_int_init_int(&v, 3, 4, 3, 2); + igraph_es_path(&es, &v, /*directed*/0); + check(&g, &es); + CHECK_ERROR(igraph_eit_create(&g_no_vertices, es, &eit), IGRAPH_EINVVID); + CHECK_ERROR(igraph_eit_create(&g_no_edges, es, &eit), IGRAPH_EINVAL); + igraph_es_destroy(&es); + + igraph_es_path(&es, &v, /*directed*/1); + CHECK_ERROR(igraph_eit_create(&g, es, &eit), IGRAPH_EINVAL); + igraph_vector_int_destroy(&v); + igraph_es_destroy(&es); + + printf("es_path with negative entry should fail.\n"); + igraph_vector_int_init_int(&v, 3, -4, 3, 2); + igraph_es_path(&es, &v, /*directed*/0); + CHECK_ERROR(igraph_eit_create(&g, es, &eit), IGRAPH_EINVVID); + + printf("Checking es_type.\n"); + IGRAPH_ASSERT(igraph_es_type(&es) == IGRAPH_ES_PATH); + igraph_es_destroy(&es); + + printf("Checking es_none.\n"); + igraph_es_none(&es); + check(&g, &es); + + printf("es_pairs:\n"); + igraph_vector_int_destroy(&v); + igraph_vector_int_init_int(&v, 4, 1,1, 2,0); + igraph_es_pairs(&es, &v, IGRAPH_DIRECTED); + check(&g, &es); + igraph_es_destroy(&es); + + printf("es_pairs for nonexistent pair should fail.\n"); + igraph_vector_int_destroy(&v); + igraph_vector_int_init_int(&v, 4, 6,1, 2,0); + igraph_es_pairs(&es, &v, IGRAPH_DIRECTED); + CHECK_ERROR(igraph_eit_create(&g, es, &eit), IGRAPH_EINVVID); + + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + igraph_destroy(&g_no_vertices); + igraph_destroy(&g_no_edges); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/edge_selectors.out b/tests/unit/edge_selectors.out new file mode 100644 index 0000000..463eeb7 --- /dev/null +++ b/tests/unit/edge_selectors.out @@ -0,0 +1,36 @@ +Checking es_vector: +1 1 +1 3 +2 0 +es_vector_copy +1 1 +1 3 +2 0 +es_copy +1 1 +1 3 +2 0 +es_as_vector +2 3 4 +es_vector with negative entry should fail. +Checking es_range: +1 1 +1 3 +2 0 +Checking eit_as_vector using seq: +2 3 4 +Checking ess_range using es_range parameters: +1 1 +1 3 +2 0 +Checking whether ess_range accepts an empty range. +Checking es_path: +3 4 +2 3 +es_path with negative entry should fail. +Checking es_type. +Checking es_none. +es_pairs: +1 1 +2 0 +es_pairs for nonexistent pair should fail. diff --git a/tests/unit/efficiency.c b/tests/unit/efficiency.c new file mode 100644 index 0000000..0771243 --- /dev/null +++ b/tests/unit/efficiency.c @@ -0,0 +1,152 @@ +/* + igraph library. + Copyright (C) 2020-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int test_graph(const char* name, const igraph_t* graph, const igraph_real_t* weights_array) { + igraph_real_t eff; + igraph_vector_t eff_vec; + + printf("###### Testing graph: %s ######\n\n", name); + + igraph_vector_init(&eff_vec, 0); + + if (weights_array) { + printf("UNWEIGHTED CASE:\n\n"); + } + + igraph_global_efficiency(graph, NULL, &eff, IGRAPH_UNDIRECTED); + printf("Global efficiency, undirected: %f\n", eff); + + igraph_global_efficiency(graph, NULL, &eff, IGRAPH_DIRECTED); + printf("Global efficiency, directed: %f\n", eff); + + igraph_average_local_efficiency(graph, NULL, &eff, IGRAPH_UNDIRECTED, IGRAPH_ALL); + printf("Average local efficiency, undirected: %f\n", eff); + + igraph_average_local_efficiency(graph, NULL, &eff, IGRAPH_DIRECTED, IGRAPH_ALL); + printf("Average local efficiency, directed, all neighbors: %f\n", eff); + + igraph_average_local_efficiency(graph, NULL, &eff, IGRAPH_DIRECTED, IGRAPH_IN); + printf("Average local efficiency, directed, in-neighbors: %f\n", eff); + + igraph_average_local_efficiency(graph, NULL, &eff, IGRAPH_DIRECTED, IGRAPH_OUT); + printf("Average local efficiency, directed, out-neighbors: %f\n", eff); + + printf("\nLocal efficiency, undirected:\n"); + igraph_local_efficiency(graph, NULL, &eff_vec, igraph_vss_all(), IGRAPH_UNDIRECTED, IGRAPH_ALL); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, all neighbors:\n"); + igraph_local_efficiency(graph, NULL, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_ALL); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, in-neighbors:\n"); + igraph_local_efficiency(graph, NULL, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_IN); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, out-neighbors:\n"); + igraph_local_efficiency(graph, NULL, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_OUT); + print_vector(&eff_vec); + + if (weights_array) { + const igraph_vector_t weights = igraph_vector_view(weights_array, igraph_ecount(graph)); + printf("\nWEIGHTED CASE:\n\n"); + + igraph_global_efficiency(graph, &weights, &eff, IGRAPH_UNDIRECTED); + printf("Global efficiency, undirected: %f\n", eff); + + igraph_global_efficiency(graph, &weights, &eff, IGRAPH_DIRECTED); + printf("Global efficiency, directed: %f\n", eff); + + igraph_average_local_efficiency(graph, &weights, &eff, IGRAPH_UNDIRECTED, IGRAPH_ALL); + printf("Average local efficiency, undirected: %f\n", eff); + + igraph_average_local_efficiency(graph, &weights, &eff, IGRAPH_DIRECTED, IGRAPH_ALL); + printf("Average local efficiency, directed, all neighbors: %f\n", eff); + + igraph_average_local_efficiency(graph, &weights, &eff, IGRAPH_DIRECTED, IGRAPH_IN); + printf("Average local efficiency, directed, in-neighbors: %f\n", eff); + + igraph_average_local_efficiency(graph, &weights, &eff, IGRAPH_DIRECTED, IGRAPH_OUT); + printf("Average local efficiency, directed, out-neighbors: %f\n", eff); + + printf("\nLocal efficiency, undirected:\n"); + igraph_local_efficiency(graph, &weights, &eff_vec, igraph_vss_all(), IGRAPH_UNDIRECTED, IGRAPH_ALL); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, all neighbors:\n"); + igraph_local_efficiency(graph, &weights, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_ALL); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, in-neighbors:\n"); + igraph_local_efficiency(graph, &weights, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_IN); + print_vector(&eff_vec); + + printf("\nLocal efficiency, directed, out-neighbors:\n"); + igraph_local_efficiency(graph, &weights, &eff_vec, igraph_vss_all(), IGRAPH_DIRECTED, IGRAPH_OUT); + print_vector(&eff_vec); + } + + printf("\n\n"); + + igraph_vector_destroy(&eff_vec); + + return 0; +} + +int test_ring(void) { + int result; + igraph_t graph; + const igraph_real_t weights_array[] = {1, 1, 1, 1}; + + igraph_ring(&graph, 4, IGRAPH_DIRECTED, /* mutual = */ false, /* circular = */ true); + result = test_graph("Ring graph", &graph, weights_array); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return result; +} + +int test_small_graph(void) { + int result; + igraph_t graph; + const igraph_real_t weights_array[] = {4, 4, 4, 3, 1, 5, 1, 2, 4, 5, 3, 5, 5, 4, 1, 1, 5, 4, 1, 1, 2, 1, 3, 5}; + + igraph_small(&graph, 13, IGRAPH_DIRECTED, + 0,8, 1,3, 1,4, 1,5, 1,8, 1,10, 2,0, 2,1, 2,4, 3,5, 4,2, 4,7, + 4,9, 5,3, 5,10, 6,7, 8,2, 8,3, 8,4, 8,9, 9,3, 9,4, 11,9, 11,3, + -1); + result = test_graph("Small test graph", &graph, weights_array); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return result; +} + +int main(void) { + + RUN_TEST(test_ring); + RUN_TEST(test_small_graph); + + return 0; +} diff --git a/tests/unit/efficiency.out b/tests/unit/efficiency.out new file mode 100644 index 0000000..c9a4b18 --- /dev/null +++ b/tests/unit/efficiency.out @@ -0,0 +1,90 @@ +###### Testing graph: Ring graph ###### + +UNWEIGHTED CASE: + +Global efficiency, undirected: 0.833333 +Global efficiency, directed: 0.611111 +Average local efficiency, undirected: 0.500000 +Average local efficiency, directed, all neighbors: 0.250000 +Average local efficiency, directed, in-neighbors: 0.000000 +Average local efficiency, directed, out-neighbors: 0.000000 + +Local efficiency, undirected: +( 0.5 0.5 0.5 0.5 ) + +Local efficiency, directed, all neighbors: +( 0.25 0.25 0.25 0.25 ) + +Local efficiency, directed, in-neighbors: +( 0 0 0 0 ) + +Local efficiency, directed, out-neighbors: +( 0 0 0 0 ) + +WEIGHTED CASE: + +Global efficiency, undirected: 0.833333 +Global efficiency, directed: 0.611111 +Average local efficiency, undirected: 0.500000 +Average local efficiency, directed, all neighbors: 0.250000 +Average local efficiency, directed, in-neighbors: 0.000000 +Average local efficiency, directed, out-neighbors: 0.000000 + +Local efficiency, undirected: +( 0.5 0.5 0.5 0.5 ) + +Local efficiency, directed, all neighbors: +( 0.25 0.25 0.25 0.25 ) + +Local efficiency, directed, in-neighbors: +( 0 0 0 0 ) + +Local efficiency, directed, out-neighbors: +( 0 0 0 0 ) + + +###### Testing graph: Small test graph ###### + +UNWEIGHTED CASE: + +Global efficiency, undirected: 0.509615 +Global efficiency, directed: 0.274786 +Average local efficiency, undirected: 0.605769 +Average local efficiency, directed, all neighbors: 0.329936 +Average local efficiency, directed, in-neighbors: 0.212115 +Average local efficiency, directed, out-neighbors: 0.149466 + +Local efficiency, undirected: +( 1 0.633333 0.833333 0.641667 0.5 0.833333 0 0 0.711111 0.722222 1 1 0 ) + +Local efficiency, directed, all neighbors: +( 0.75 0.401111 0.375 0.340833 0.316667 0.333333 0 0 0.466667 0.305556 0.5 0.5 0 ) + +Local efficiency, directed, in-neighbors: +( 0 0 0.5 0.340833 0.527778 0.5 0 0 0.166667 0.222222 0.5 0 0 ) + +Local efficiency, directed, out-neighbors: +( 0 0.3875 0.25 0 0.0555556 0 0 0 0.583333 0.166667 0 0.5 0 ) + +WEIGHTED CASE: + +Global efficiency, undirected: 0.247686 +Global efficiency, directed: 0.125356 +Average local efficiency, undirected: 0.281019 +Average local efficiency, directed, all neighbors: 0.150368 +Average local efficiency, directed, in-neighbors: 0.122897 +Average local efficiency, directed, out-neighbors: 0.068520 + +Local efficiency, undirected: +( 0.333333 0.306219 0.525 0.419167 0.358333 0.187037 0 0 0.380635 0.310185 0.333333 0.5 0 ) + +Local efficiency, directed, all neighbors: +( 0.291667 0.16403 0.245833 0.207976 0.204643 0.075 0 0 0.204987 0.143981 0.166667 0.25 0 ) + +Local efficiency, directed, in-neighbors: +( 0 0 0.5 0.207976 0.341071 0.125 0 0 0.0625 0.194444 0.166667 0 0 ) + +Local efficiency, directed, out-neighbors: +( 0 0.180711 0.116667 0 0.0416667 0 0 0 0.246164 0.0555556 0 0.25 0 ) + + diff --git a/tests/unit/eigen_stress.c b/tests/unit/eigen_stress.c new file mode 100644 index 0000000..f8854ef --- /dev/null +++ b/tests/unit/eigen_stress.c @@ -0,0 +1,106 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#define CHECK_PRINT(expr) \ + do { \ + igraph_error_t err = expr; \ + if (err != IGRAPH_SUCCESS) { \ + fprintf(stderr, "FAILED on the following graph at line %d:\n", __LINE__); \ + fprintf(stderr, "directed: %d, vcount: %d, ecount: %d\n", \ + (int) igraph_is_directed(&g), (int) igraph_vcount(&g), (int) igraph_ecount(&g)); \ + igraph_write_graph_edgelist(&g, stderr); \ + abort(); \ + } \ + } while (0) + +int main(void) { + igraph_vector_t vec; + igraph_real_t val; + + igraph_rng_seed(igraph_rng_default(), 987); + igraph_set_warning_handler(&igraph_warning_handler_ignore); + igraph_set_error_handler(&igraph_error_handler_printignore); + + igraph_vector_init(&vec, 0); + + for (igraph_int_t i=0; i < 1252; i++) { + igraph_t g; + igraph_atlas(&g, i); + + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_ALL, NULL, NULL)); + CHECK_PRINT(igraph_hub_and_authority_scores(&g, &vec, NULL, &val, NULL, NULL)); + + igraph_to_directed(&g, IGRAPH_TO_DIRECTED_RANDOM); + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_OUT, NULL, NULL)); + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_IN, NULL, NULL)); + CHECK_PRINT(igraph_hub_and_authority_scores(&g, &vec, NULL, &val, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.85, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.99, NULL, NULL)); + + igraph_destroy(&g); + } + + for (igraph_int_t i=3; i < 100; i++) { + igraph_t g; + igraph_ring(&g, i, true, false, true); + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, NULL, IGRAPH_OUT, NULL, NULL)); + igraph_destroy(&g); + } + + { + // A <--> C, D <--> E, B + igraph_t g; + igraph_small(&g, 5, true, + 0,2, 2,0, 3,4, 4,2, + -1); + + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_OUT, NULL, NULL)); + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_IN, NULL, NULL)); + CHECK_PRINT(igraph_hub_and_authority_scores(&g, &vec, NULL, &val, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.85, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.99, NULL, NULL)); + + igraph_destroy(&g); + } + + for (igraph_int_t n=4; n <= 7; n++) { + for (igraph_int_t i=0; i < 100; i++) { + igraph_t g; + + igraph_erdos_renyi_game_gnp(&g, n, 0.5, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_OUT, NULL, NULL)); + CHECK_PRINT(igraph_eigenvector_centrality(&g, &vec, &val, IGRAPH_IN, NULL, NULL)); + CHECK_PRINT(igraph_hub_and_authority_scores(&g, &vec, NULL, &val, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.85, NULL, NULL)); + CHECK_PRINT(igraph_pagerank(&g, IGRAPH_PAGERANK_ALGO_ARPACK, &vec, &val, igraph_vss_all(), true, 0.99, NULL, NULL)); + + igraph_destroy(&g); + } + } + + igraph_vector_destroy(&vec); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/empty b/tests/unit/empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/erdos_renyi_game_gnm.c b/tests/unit/erdos_renyi_game_gnm.c new file mode 100644 index 0000000..269a1b9 --- /dev/null +++ b/tests/unit/erdos_renyi_game_gnm.c @@ -0,0 +1,328 @@ +/* + igraph library. + Copyright (C) 2006-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int test_no_multiple(void) { + igraph_t g; + igraph_bool_t simple, has_multi; + + /* Ensure that the test is deterministic */ + igraph_rng_seed(igraph_rng_default(), 137); + + /* null graph */ + + igraph_erdos_renyi_game_gnm(&g, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_destroy(&g); + + /* singleton */ + + igraph_erdos_renyi_game_gnm(&g, 1, 0, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + IGRAPH_ASSERT(igraph_vcount(&g) == 1); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_destroy(&g); + + /* singleton with loop */ + + igraph_erdos_renyi_game_gnm(&g, 1, 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + IGRAPH_ASSERT(igraph_vcount(&g) == 1); + IGRAPH_ASSERT(igraph_ecount(&g) == 1); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 1, 1, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + + IGRAPH_ASSERT(igraph_vcount(&g) == 1); + IGRAPH_ASSERT(igraph_ecount(&g) == 1); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_destroy(&g); + + + /* directed with loops */ + igraph_erdos_renyi_game_gnm(&g, 10, 10 * 10 - 1, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 10 - 1); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_has_multiple(&g, &has_multi); IGRAPH_ASSERT(! has_multi); + + igraph_simplify(&g, /*remove_multiple=*/ false, /*remove_loops=*/ true, /*edge_comb=*/ NULL); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 9 || igraph_ecount(&g) == 10 * 9 - 1); + + igraph_destroy(&g); + + /* directed without loops */ + igraph_erdos_renyi_game_gnm(&g, 10, 10 * 9 - 1, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 9 - 1); + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + /* undirected with loops */ + igraph_erdos_renyi_game_gnm(&g, 10, 10 * 11 / 2 - 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 11 / 2 - 1); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_has_multiple(&g, &has_multi); IGRAPH_ASSERT(! has_multi); + + igraph_simplify(&g, /*remove_multiple=*/ false, /*remove_loops=*/ true, /*edge_comb=*/ NULL); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 9 / 2 || igraph_ecount(&g) == 10 * 9 / 2 - 1); + + igraph_destroy(&g); + + /* undirected without loops */ + igraph_erdos_renyi_game_gnm(&g, 10, 10 * 9 / 2 - 1, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 10 * 9 / 2 - 1); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + + /* Create a couple of large graphs too */ + igraph_erdos_renyi_game_gnm(&g, 100000, 200000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 100000); + IGRAPH_ASSERT(igraph_ecount(&g) == 200000); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 100000, 200000, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 100000); + IGRAPH_ASSERT(igraph_ecount(&g) == 200000); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 100000, 200000, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 100000); + IGRAPH_ASSERT(igraph_ecount(&g) == 200000); + igraph_has_multiple(&g, &has_multi); IGRAPH_ASSERT(! has_multi); + igraph_simplify(&g, false, true, /*edge_comb=*/ NULL); /* only remove loops */ + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 100000, 200000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&g) == 100000); + IGRAPH_ASSERT(igraph_ecount(&g) == 200000); + igraph_has_multiple(&g, &has_multi); IGRAPH_ASSERT(! has_multi); + igraph_destroy(&g); + + return 0; +} + +int test_multiple(void) { + igraph_t graph1, graph2; + igraph_bool_t same, has_loop; + + igraph_rng_seed(igraph_rng_default(), 137); /* Makes test deterministic */ + + /* null graph */ + + igraph_erdos_renyi_game_gnm(&graph1, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 0); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 0); + print_graph(&graph1); + igraph_destroy(&graph1); + + /* null graph with more than zero edges is invalid */ + + CHECK_ERROR( + igraph_erdos_renyi_game_gnm(&graph1, 0, 1, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED), + IGRAPH_EINVAL + ); + + /* singleton with no loops and more than zero edges is invalid */ + + CHECK_ERROR( + igraph_erdos_renyi_game_gnm(&graph1, 1, 1, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED), + IGRAPH_EINVAL + ); + + /* singleton with multiple loops */ + + igraph_erdos_renyi_game_gnm(&graph1, 1, 2, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); /* undirected with loops */ + IGRAPH_ASSERT(igraph_vcount(&graph1) == 1); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 2); + print_graph(&graph1); + igraph_destroy(&graph1); + + igraph_erdos_renyi_game_gnm(&graph1, 1, 3, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); /* undirected with loops */ + IGRAPH_ASSERT(igraph_vcount(&graph1) == 1); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 3); + print_graph(&graph1); + igraph_destroy(&graph1); + + /* directed with loops */ + + igraph_erdos_renyi_game_gnm(&graph1, 10, 560, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 10); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 560); + IGRAPH_ASSERT(igraph_is_directed(&graph1)); + igraph_simplify(&graph1, true, false, NULL); + IGRAPH_ASSERT(igraph_ecount(&graph1) <= 100); + igraph_destroy(&graph1); + + /* directed without loops */ + + igraph_erdos_renyi_game_gnm(&graph1, 10, 890, IGRAPH_DIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 10); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 890); + IGRAPH_ASSERT(igraph_is_directed(&graph1)); + igraph_has_loop(&graph1, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_simplify(&graph1, true, false, NULL); + IGRAPH_ASSERT(igraph_ecount(&graph1) <= 90); + igraph_destroy(&graph1); + + /* undirected with loops */ + + igraph_erdos_renyi_game_gnm(&graph1, 10, 540, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 10); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 540); + IGRAPH_ASSERT(!igraph_is_directed(&graph1)); + igraph_simplify(&graph1, true, false, NULL); + IGRAPH_ASSERT(igraph_ecount(&graph1) <= 55); + igraph_destroy(&graph1); + + /* undirected without loops */ + + igraph_erdos_renyi_game_gnm(&graph1, 10, 440, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 10); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 440); + IGRAPH_ASSERT(!igraph_is_directed(&graph1)); + igraph_has_loop(&graph1, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_copy(&graph2, &graph1); + igraph_simplify(&graph1, true, true, NULL); + igraph_simplify(&graph2, true, false, NULL); + igraph_isomorphic(&graph1, &graph2, &same); + IGRAPH_ASSERT(same); + igraph_destroy(&graph1); + igraph_destroy(&graph2); + + /* large graphs */ + + igraph_erdos_renyi_game_gnm(&graph1, 1000, 200000, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 1000); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 200000); + igraph_has_loop(&graph1, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_destroy(&graph1); + + igraph_erdos_renyi_game_gnm(&graph1, 100000, 200000, IGRAPH_DIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 100000); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 200000); + igraph_has_loop(&graph1, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_destroy(&graph1); + + igraph_erdos_renyi_game_gnm(&graph1, 100000, 200000, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 100000); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 200000); + igraph_destroy(&graph1); + + igraph_erdos_renyi_game_gnm(&graph1, 100000, 200000, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + IGRAPH_ASSERT(igraph_vcount(&graph1) == 100000); + IGRAPH_ASSERT(igraph_ecount(&graph1) == 200000); + igraph_destroy(&graph1); + + return 0; +} + +int test_iea(void) { + igraph_t g; + igraph_bool_t has_loop; + + /* null graph */ + igraph_iea_game(&g, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + /* null graph with more than zero edges is invalid */ + CHECK_ERROR( + igraph_iea_game(&g, 0, 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS), + IGRAPH_EINVAL + ); + + /* singleton with no loops and more than zero edges is invalid */ + CHECK_ERROR( + igraph_iea_game(&g, 1, 1, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS), + IGRAPH_EINVAL + ); + + /* singleton with loop */ + igraph_iea_game(&g, 1, 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 1); + IGRAPH_ASSERT(igraph_ecount(&g) == 1); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + /* singleton with three loops */ + igraph_iea_game(&g, 1, 3, IGRAPH_DIRECTED, IGRAPH_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 1); + IGRAPH_ASSERT(igraph_ecount(&g) == 3); + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_destroy(&g); + + /* larger undirected */ + igraph_iea_game(&g, 5, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 5); + IGRAPH_ASSERT(igraph_ecount(&g) == 10); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_has_loop(&g, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_destroy(&g); + + igraph_iea_game(&g, 5, 10, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 5); + IGRAPH_ASSERT(igraph_ecount(&g) == 10); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + /* larger directed */ + igraph_iea_game(&g, 4, 11, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 4); + IGRAPH_ASSERT(igraph_ecount(&g) == 11); + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_has_loop(&g, &has_loop); IGRAPH_ASSERT(! has_loop); + igraph_destroy(&g); + + igraph_iea_game(&g, 4, 11, IGRAPH_DIRECTED, IGRAPH_LOOPS); + IGRAPH_ASSERT(igraph_vcount(&g) == 4); + IGRAPH_ASSERT(igraph_ecount(&g) == 11); + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_destroy(&g); + + return 0; +} + +int main(void) { + RUN_TEST(test_no_multiple); + RUN_TEST(test_multiple); + RUN_TEST(test_iea); +} diff --git a/tests/unit/erdos_renyi_game_gnp.c b/tests/unit/erdos_renyi_game_gnp.c new file mode 100644 index 0000000..fb8820e --- /dev/null +++ b/tests/unit/erdos_renyi_game_gnp.c @@ -0,0 +1,260 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Helper functions for hypothesis testing for binomial and negative binomial distr. */ + +igraph_real_t log_binom_prob(igraph_int_t n, igraph_real_t p, igraph_int_t k) { + return lgamma(1+n) - lgamma(1+k) - lgamma(1 - k + n) + + k * log(p) + (n-k) * log(1-p); +} + +igraph_real_t log_neg_binom_prob(igraph_int_t n, igraph_real_t p, igraph_int_t k) { + return lgamma(k + n) - lgamma(1+k) - lgamma(n) + + k * log(1-p) + n * log(p); +} + +igraph_real_t binom_test(igraph_int_t n, igraph_real_t p, igraph_int_t k0) { + igraph_real_t res = 0; + igraph_real_t lP0 = log_binom_prob(n, p, k0); + igraph_real_t last_lP = -IGRAPH_INFINITY; + for (igraph_int_t k=0; k < n; k++) { + igraph_real_t lP = log_binom_prob(n, p, k); + if (lP <= lP0) { + res += exp(lP); + /* stop when relative change to 'res' is < 10^-12 */ + if (lP < last_lP && lP - log(res) < -12.0 * log(10)) break; + } + last_lP = lP; + } + return res; +} + +igraph_real_t neg_binom_test(igraph_int_t n, igraph_real_t p, igraph_int_t k0) { + igraph_real_t res = 0; + igraph_real_t lP0 = log_neg_binom_prob(n, p, k0); + igraph_real_t last_lP = -IGRAPH_INFINITY; + for (igraph_int_t k=0; ; k++) { + igraph_real_t lP = log_neg_binom_prob(n, p, k); + if (lP <= lP0) { + res += exp(lP); + /* stop when relative change to 'res' is < 10^-12 */ + if (lP < last_lP && lP - log(res) < -12.0 * log(10)) break; + } + last_lP = lP; + } + return res; +} + +void stress_test(void) { + igraph_rng_seed(igraph_rng_default(), 137); + + for (igraph_int_t size=2; size < 5; size++) { + for (igraph_int_t i=0; i < 100; i++) { + igraph_t g; + igraph_bool_t simple; + + igraph_erdos_renyi_game_gnp(&g, size, 0.5, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + if (! simple) { + printf("Erdos-Renyi GNP graph is not simple! size=%" IGRAPH_PRId ", i=%" IGRAPH_PRId ".\n", + size, i); + print_graph(&g); + } + IGRAPH_ASSERT(simple); + + igraph_destroy(&g); + } + } + + for (igraph_int_t size=2; size < 5; size++) { + for (igraph_int_t i=0; i < 100; i++) { + igraph_t g; + igraph_bool_t simple; + + igraph_erdos_renyi_game_gnp(&g, size, 0.5, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + if (! simple) { + printf("Erdos-Renyi GNP graph is not simple! size=%" IGRAPH_PRId ", i=%" IGRAPH_PRId ".\n", + size, i); + print_graph(&g); + } + IGRAPH_ASSERT(simple); + + igraph_destroy(&g); + } + } + + VERIFY_FINALLY_STACK(); +} + +void check_gnp( + igraph_int_t n, igraph_real_t p, + igraph_bool_t directed, + igraph_bool_t loops, igraph_bool_t multiple, + igraph_bool_t edge_labeled) { + + igraph_t graph; + igraph_bool_t has_loop, has_multi; + igraph_edge_type_sw_t allowed_edge_types; + + allowed_edge_types = IGRAPH_SIMPLE_SW; + if (loops) allowed_edge_types |= IGRAPH_LOOPS_SW; + if (multiple) allowed_edge_types |= IGRAPH_MULTI_SW; + + igraph_erdos_renyi_game_gnp(&graph, n, p, directed, allowed_edge_types, edge_labeled); + + IGRAPH_ASSERT(igraph_is_directed(&graph) == directed); + IGRAPH_ASSERT(igraph_vcount(&graph) == n); + + igraph_has_loop(&graph, &has_loop); + igraph_has_multiple(&graph, &has_multi); + + if (!multiple) IGRAPH_ASSERT(!has_multi); + if (!loops) IGRAPH_ASSERT(!has_loop); + + igraph_real_t complete_ecount; + + if (directed) { + complete_ecount = loops ? (igraph_real_t) n * n : (igraph_real_t) n * (n-1.0); + } else { + complete_ecount = loops ? (igraph_real_t) n * (n+1.0) / 2.0 : (igraph_real_t) n * (n-1) / 2.0; + } + + if (p == 0 || complete_ecount == 0) { + IGRAPH_ASSERT(igraph_ecount(&graph) == 0); + } else if (!multiple && p == 1) { + IGRAPH_ASSERT(igraph_ecount(&graph) == complete_ecount); + } else { + /* Edge count check using hypothesis testing. + * With simple graphs, the edge count follows a binomial distribution, + * with multigraphs, a negative binomial distribution. In both cases, + * we perform an exact two-tailed test unless the graph is too large. + */ + + igraph_real_t pval = 1; + igraph_real_t m = complete_ecount * p; + igraph_real_t sd = multiple ? sqrt(m * (1+p)) : sqrt(m * (1-p)); + igraph_real_t dev = (m - igraph_ecount(&graph)) / sd; + igraph_bool_t do_test = m < 10000 && ! edge_labeled; + + if (do_test) { + if (multiple) { + pval = neg_binom_test(complete_ecount, 1 / (1 + p), igraph_ecount(&graph)); + } else { + pval = binom_test(complete_ecount, p, igraph_ecount(&graph)); + } + printf("p-value=%.3f ", pval); + } else { + printf("p-value= ? "); + } + + /* Output stats, including the actual and expected edge counts, + * as well as their difference in standard deviations. */ + printf("for n=%6" IGRAPH_PRId ", p=%5g, %s, %s, %s; " + "ecount: %6" IGRAPH_PRId ", expected: %8.1f, % .2f SD\n", + n, p, + directed ? " directed" : "undirected", + loops ? " loops" : "no-loops", + multiple ? " multi" : "no-multi", + igraph_ecount(&graph), + m, dev); + + /* Failure is unlikely, but possible. If it happens, check manually. */ + if (do_test) { + IGRAPH_ASSERT(pval > 0.01); + } else if (!edge_labeled) { + /* If the graph is too large for the exact test to run efficiently, + * use a normal approximation, even though this is inaccurate with + * small p. */ + IGRAPH_ASSERT(fabs(dev) < 2.576); + } + } + + igraph_destroy(&graph); +} + +/* Check all parameter combinations */ +void check_all_gnp(igraph_int_t n, igraph_real_t p) { + + if (p <= 1) { + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_LOOPS, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_LOOPS, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED); + } + + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED); + + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED); + check_gnp(n, p, IGRAPH_UNDIRECTED, IGRAPH_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED); + check_gnp(n, p, IGRAPH_DIRECTED, IGRAPH_LOOPS, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED); +} + +void test_examples(void) { + + /* Ensure that the test is deterministic */ + igraph_rng_seed(igraph_rng_default(), 42); + + check_all_gnp(0, 0.0); + + /* Empty graph */ + + check_all_gnp(10, 0.0); + + /* Singleton, with loop if allowed */ + + check_all_gnp(1, 1.0); + check_all_gnp(1, 5.0); + + /* Complete graph */ + + check_all_gnp(10, 1.0); + + /* Random graph */ + + check_all_gnp(10, 0.5); + check_all_gnp(10, 12.0); + + /* Create a couple of larger graphs too */ + + check_all_gnp(100, 1.0 / 100); + check_all_gnp(100, 2.0 / 100); + check_all_gnp(100, 80.0 / 100); + check_all_gnp(1000, 2.0 / 1000); + check_all_gnp(100000, 2.0 / 100000); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + test_examples(); + stress_test(); + + return 0; +} diff --git a/tests/unit/error_macros.c b/tests/unit/error_macros.c new file mode 100644 index 0000000..2fd2202 --- /dev/null +++ b/tests/unit/error_macros.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include +#include + +int cause_error(void) { + IGRAPH_ERRORF("%d %f %ld %c", IGRAPH_EINVAL, 1, 1.0, 1L, 'a'); + return IGRAPH_SUCCESS; +} + +int cause_warning(void) { + IGRAPH_WARNINGF("%d %f %ld %c", 1, 1.0, 1L, 'a'); + return IGRAPH_SUCCESS; +} + +int cause_fatal(void) { + IGRAPH_FATALF("%d %f %ld %c", 1, 1.0, 1L, 'a'); +} + +void error_handler(const char *reason, const char *file, int line, igraph_error_t igraph_errno) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + printf("Error. Reason: %s\nErrno: %d\n", reason, igraph_errno); +} + +void warning_handler(const char *reason, const char *file, int line) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + printf("Warning. Reason: %s\n", reason); +} + +void fatal_handler(const char *reason, const char *file, int line) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + printf("Fatal. Reason: %s\n", reason); + exit(0); +} + +int main(void) { + igraph_set_error_handler(&error_handler); + igraph_set_warning_handler(&warning_handler); + igraph_set_fatal_handler(&fatal_handler); + + IGRAPH_ASSERT(cause_error() == IGRAPH_EINVAL); + IGRAPH_ASSERT(cause_warning() == IGRAPH_SUCCESS); + cause_fatal(); + + /* The igraph_fatal() call must not return, so the following lines should not run. */ + + printf("This should not be printed."); + + return 1; +} diff --git a/tests/unit/error_macros.out b/tests/unit/error_macros.out new file mode 100644 index 0000000..0142ead --- /dev/null +++ b/tests/unit/error_macros.out @@ -0,0 +1,4 @@ +Error. Reason: 1 1.000000 1 a +Errno: 4 +Warning. Reason: 1 1.000000 1 a +Fatal. Reason: 1 1.000000 1 a diff --git a/tests/unit/expand_path_to_pairs.c b/tests/unit/expand_path_to_pairs.c new file mode 100644 index 0000000..280a0b7 --- /dev/null +++ b/tests/unit/expand_path_to_pairs.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +igraph_error_t test_path_expansion(void) { + igraph_vector_int_t path; + + igraph_vector_int_init(&path, 0); + IGRAPH_CHECK(igraph_expand_path_to_pairs(&path)); + print_vector_int(&path); + igraph_vector_int_destroy(&path); + + igraph_vector_int_init(&path, 0); + igraph_vector_int_push_back(&path, 0); + IGRAPH_CHECK(igraph_expand_path_to_pairs(&path)); + print_vector_int(&path); + igraph_vector_int_destroy(&path); + + igraph_vector_int_init(&path, 0); + igraph_vector_int_push_back(&path, 0); + igraph_vector_int_push_back(&path, 1); + IGRAPH_CHECK(igraph_expand_path_to_pairs(&path)); + print_vector_int(&path); + igraph_vector_int_destroy(&path); + + igraph_vector_int_init(&path, 0); + igraph_vector_int_push_back(&path, 2); + igraph_vector_int_push_back(&path, 3); + igraph_vector_int_push_back(&path, 5); + igraph_vector_int_push_back(&path, 7); + IGRAPH_CHECK(igraph_expand_path_to_pairs(&path)); + print_vector_int(&path); + igraph_vector_int_destroy(&path); + + return IGRAPH_SUCCESS; +} + +int main(void) { + IGRAPH_ASSERT(test_path_expansion() == IGRAPH_SUCCESS); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/expand_path_to_pairs.out b/tests/unit/expand_path_to_pairs.out new file mode 100644 index 0000000..4b9fc06 --- /dev/null +++ b/tests/unit/expand_path_to_pairs.out @@ -0,0 +1,4 @@ +( ) +( ) +( 0 1 ) +( 2 3 3 5 5 7 ) diff --git a/tests/unit/fatal_handler.c b/tests/unit/fatal_handler.c new file mode 100644 index 0000000..9d3e63d --- /dev/null +++ b/tests/unit/fatal_handler.c @@ -0,0 +1,39 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +igraph_fatal_handler_t hanlder; + +void handler(const char *reason, const char *file, int line) { + printf("Reason: %s\nFile: %s\nLine: %d\n", reason, file, line); + exit(0); /* We use exit(0) instead of abort() to allow the test to succeed. */ +} + +int main(void) { + igraph_set_fatal_handler(&handler); + + igraph_fatal("REASON", "FILENAME", 123); + + /* The igraph_fatal() call must not return, so the following lines should not run. */ + + printf("This should not be printed."); + + return 0; +} diff --git a/tests/unit/fatal_handler.out b/tests/unit/fatal_handler.out new file mode 100644 index 0000000..1f7a3e6 --- /dev/null +++ b/tests/unit/fatal_handler.out @@ -0,0 +1,3 @@ +Reason: REASON +File: FILENAME +Line: 123 diff --git a/tests/unit/foreign_empty.c b/tests/unit/foreign_empty.c new file mode 100644 index 0000000..5380436 --- /dev/null +++ b/tests/unit/foreign_empty.c @@ -0,0 +1,91 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include "test_utilities.h" + +/* This test verifies that trying to read an empty file does not cause + * a crash or fatal error in any of the file format readers. */ + +int main(void) { + igraph_t graph; + FILE *file; + + file = fopen("empty", "r"); + IGRAPH_ASSERT(file != NULL); + + /* Formats for which an emtpy file is valid */ + + /* Edge list */ + IGRAPH_ASSERT(igraph_read_graph_edgelist(&graph, file, 0, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* NCOL */ + IGRAPH_ASSERT(igraph_read_graph_ncol(&graph, file, NULL, 1, IGRAPH_ADD_WEIGHTS_IF_PRESENT, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* LGL */ + IGRAPH_ASSERT(igraph_read_graph_lgl(&graph, file, 1, IGRAPH_ADD_WEIGHTS_IF_PRESENT, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* Formats for which an empty file is invalid */ + + igraph_set_error_handler(&igraph_error_handler_printignore); + + /* GraphML */ + IGRAPH_ASSERT(igraph_read_graph_graphml(&graph, file, 0) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* Pajek */ + IGRAPH_ASSERT(igraph_read_graph_pajek(&graph, file) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* DL */ + IGRAPH_ASSERT(igraph_read_graph_dl(&graph, file, IGRAPH_UNDIRECTED) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* GML */ + IGRAPH_ASSERT(igraph_read_graph_gml(&graph, file) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* graphdb */ + IGRAPH_ASSERT(igraph_read_graph_graphdb(&graph, file, IGRAPH_UNDIRECTED) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + /* DIMACS flow problem */ + IGRAPH_ASSERT(igraph_read_graph_dimacs_flow(&graph, file, NULL, NULL, NULL, NULL, NULL, IGRAPH_DIRECTED) != IGRAPH_SUCCESS); + rewind(file); + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/full.c b/tests/unit/full.c new file mode 100644 index 0000000..e1b881d --- /dev/null +++ b/tests/unit/full.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_int_t n_vertices = 10; + + printf("Null graph\n"); + igraph_full(&g, 0, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Singleton graph, no loops\n"); + igraph_full(&g, 1, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Singleton graph, loops\n"); + igraph_full(&g, 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Undirected, no loops\n"); + igraph_full(&g, n_vertices, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Directed, no loops\n"); + igraph_full(&g, n_vertices, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Undirected, with loops\n"); + igraph_full(&g, n_vertices, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Directed, with loops\n"); + igraph_full(&g, n_vertices, IGRAPH_DIRECTED, IGRAPH_LOOPS); + print_graph_canon(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/full.out b/tests/unit/full.out new file mode 100644 index 0000000..d033c22 --- /dev/null +++ b/tests/unit/full.out @@ -0,0 +1,326 @@ +Null graph +directed: false +vcount: 0 +edges: { +} +Singleton graph, no loops +directed: false +vcount: 1 +edges: { +} +Singleton graph, loops +directed: false +vcount: 1 +edges: { +0 0 +} +Undirected, no loops +directed: false +vcount: 10 +edges: { +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +4 5 +4 6 +4 7 +4 8 +4 9 +5 6 +5 7 +5 8 +5 9 +6 7 +6 8 +6 9 +7 8 +7 9 +8 9 +} +Directed, no loops +directed: true +vcount: 10 +edges: { +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 0 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 0 +2 1 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 0 +3 1 +3 2 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +4 0 +4 1 +4 2 +4 3 +4 5 +4 6 +4 7 +4 8 +4 9 +5 0 +5 1 +5 2 +5 3 +5 4 +5 6 +5 7 +5 8 +5 9 +6 0 +6 1 +6 2 +6 3 +6 4 +6 5 +6 7 +6 8 +6 9 +7 0 +7 1 +7 2 +7 3 +7 4 +7 5 +7 6 +7 8 +7 9 +8 0 +8 1 +8 2 +8 3 +8 4 +8 5 +8 6 +8 7 +8 9 +9 0 +9 1 +9 2 +9 3 +9 4 +9 5 +9 6 +9 7 +9 8 +} +Undirected, with loops +directed: false +vcount: 10 +edges: { +0 0 +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 1 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 2 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 3 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +4 4 +4 5 +4 6 +4 7 +4 8 +4 9 +5 5 +5 6 +5 7 +5 8 +5 9 +6 6 +6 7 +6 8 +6 9 +7 7 +7 8 +7 9 +8 8 +8 9 +9 9 +} +Directed, with loops +directed: true +vcount: 10 +edges: { +0 0 +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 0 +1 1 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 0 +2 1 +2 2 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 0 +3 1 +3 2 +3 3 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +4 0 +4 1 +4 2 +4 3 +4 4 +4 5 +4 6 +4 7 +4 8 +4 9 +5 0 +5 1 +5 2 +5 3 +5 4 +5 5 +5 6 +5 7 +5 8 +5 9 +6 0 +6 1 +6 2 +6 3 +6 4 +6 5 +6 6 +6 7 +6 8 +6 9 +7 0 +7 1 +7 2 +7 3 +7 4 +7 5 +7 6 +7 7 +7 8 +7 9 +8 0 +8 1 +8 2 +8 3 +8 4 +8 5 +8 6 +8 7 +8 8 +8 9 +9 0 +9 1 +9 2 +9 3 +9 4 +9 5 +9 6 +9 7 +9 8 +9 9 +} diff --git a/tests/unit/gen2wheap.c b/tests/unit/gen2wheap.c new file mode 100644 index 0000000..b756e05 --- /dev/null +++ b/tests/unit/gen2wheap.c @@ -0,0 +1,123 @@ + +#include + +#include "core/genheap.h" + +#include "test_utilities.h" + +typedef struct intpair_t { + int x, y; +} intpair_t; + +int cmp(const void *a, const void *b) { + const intpair_t *sa = a, *sb = b; + if (sa->x < sb->x) return -1; + if (sa->x > sb->x) return 1; + if (sa->y < sb->y) return -1; + if (sa->y > sb->y) return 1; + return 0; +} + +void intpair_print(const intpair_t *p) { + printf("(%d, %d)", p->x, p->y); +} + +int main(void) { + igraph_int_t n; + igraph_vector_int_t idx; + igraph_gen2wheap_t h; + igraph_int_t i; + intpair_t p; + + igraph_rng_seed(igraph_rng_default(), 42); + + n = 10; + + igraph_gen2wheap_init(&h, cmp, sizeof(intpair_t), n); + IGRAPH_ASSERT(igraph_gen2wheap_max_size(&h) == n); + + igraph_vector_int_init_range(&idx, 0, n); + igraph_vector_int_shuffle(&idx); + + for (i=0; i < n; i++) { + igraph_int_t j = VECTOR(idx)[i]; + p.x = RNG_INTEGER(1, 10); + p.y = RNG_INTEGER(1, 10); + printf("Adding %2" IGRAPH_PRId ": ", j); + intpair_print(&p); + printf("\n"); + IGRAPH_ASSERT(! igraph_gen2wheap_has_elem(&h, j)); + igraph_gen2wheap_push_with_index(&h, j, &p); + igraph_gen2wheap_check(&h); + IGRAPH_ASSERT(igraph_gen2wheap_has_elem(&h, j)); + IGRAPH_ASSERT(igraph_gen2wheap_size(&h) == i+1); + } + + printf("\n"); + + i = igraph_gen2wheap_size(&h); + while (!igraph_gen2wheap_empty(&h)) { + printf("Removing %2" IGRAPH_PRId ": ", igraph_gen2wheap_max_index(&h)); + intpair_print(igraph_gen2wheap_max(&h)); + printf("\n"); + igraph_gen2wheap_delete_max(&h); + igraph_gen2wheap_check(&h); + i--; + IGRAPH_ASSERT(igraph_gen2wheap_size(&h) == i); + } + + printf("\n"); + + n=5; + for (igraph_int_t i=0; i < n; i++) { + p.x = RNG_INTEGER(1, 10); + p.y = RNG_INTEGER(1, 10); + printf("Adding %2" IGRAPH_PRId ": ", i); + intpair_print(&p); + printf("\n"); + IGRAPH_ASSERT(! igraph_gen2wheap_has_elem(&h, i)); + igraph_gen2wheap_push_with_index(&h, i, &p); + igraph_gen2wheap_check(&h); + IGRAPH_ASSERT(igraph_gen2wheap_has_elem(&h, i)); + IGRAPH_ASSERT(igraph_gen2wheap_size(&h) == i+1); + } + + i = 1; + p.x = -1; p.y = -1; + printf("\nModifying %" IGRAPH_PRId " to ", i); + intpair_print(&p); + printf("\n"); + igraph_gen2wheap_modify(&h, i, &p); + igraph_gen2wheap_check(&h); + + i = igraph_gen2wheap_max_index(&h); + p = * (intpair_t *) igraph_gen2wheap_max(&h); + p.x -= 3; + printf("Modifying max to "); + intpair_print(&p); + printf("\n"); + igraph_gen2wheap_modify(&h, i, &p); + igraph_gen2wheap_check(&h); + + printf("\n"); + + i = igraph_gen2wheap_size(&h); + while (!igraph_gen2wheap_empty(&h)) { + printf("Removing %2" IGRAPH_PRId ": ", igraph_gen2wheap_max_index(&h)); + intpair_print(igraph_gen2wheap_max(&h)); + printf("\n"); + igraph_gen2wheap_delete_max(&h); + igraph_gen2wheap_check(&h); + i--; + IGRAPH_ASSERT(igraph_gen2wheap_size(&h) == i); + } + + printf("\n"); + + igraph_vector_int_destroy(&idx); + igraph_gen2wheap_destroy(&h); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/gen2wheap.out b/tests/unit/gen2wheap.out new file mode 100644 index 0000000..3dd5ab0 --- /dev/null +++ b/tests/unit/gen2wheap.out @@ -0,0 +1,37 @@ +Adding 9: (6, 9) +Adding 2: (10, 10) +Adding 0: (7, 4) +Adding 8: (9, 2) +Adding 3: (7, 2) +Adding 5: (7, 1) +Adding 7: (5, 1) +Adding 6: (5, 4) +Adding 4: (10, 10) +Adding 1: (6, 4) + +Removing 4: (10, 10) +Removing 2: (10, 10) +Removing 8: (9, 2) +Removing 0: (7, 4) +Removing 3: (7, 2) +Removing 5: (7, 1) +Removing 9: (6, 9) +Removing 1: (6, 4) +Removing 6: (5, 4) +Removing 7: (5, 1) + +Adding 0: (10, 7) +Adding 1: (6, 7) +Adding 2: (2, 5) +Adding 3: (5, 2) +Adding 4: (10, 2) + +Modifying 1 to (-1, -1) +Modifying max to (7, 7) + +Removing 4: (10, 2) +Removing 0: (7, 7) +Removing 3: (5, 2) +Removing 2: (2, 5) +Removing 1: (-1, -1) + diff --git a/tests/unit/global_transitivity.c b/tests/unit/global_transitivity.c new file mode 100644 index 0000000..a6937d7 --- /dev/null +++ b/tests/unit/global_transitivity.c @@ -0,0 +1,113 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_real_t global, global2, global3; + + /* Small graphs */ + + printf("Null graph: "); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nSingleton graph: "); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nTwo connected vertices: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nTriangle: "); + igraph_full(&g, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nTwo-star: "); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,2, 0,1, -1); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nZachary karate club: "); + igraph_famous(&g, "Zachary"); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%g"); + printf("\n"); + igraph_destroy(&g); + + printf("\nDirected and multigraphs:\n"); + + igraph_small(&g, 20, IGRAPH_DIRECTED, + 15, 12, 12, 10, 15, 0, 11, 10, 2, 8, 8, 6, 13, 17, 10, 10, 17, 2, 14, + 0, 16, 13, 14, 14, 0, 5, 6, 4, 0, 9, 0, 6, 10, 9, 16, 4, 14, 5, 17, + 15, 14, 9, 17, 17, 1, 4, 10, 16, 7, 0, 11, 12, 6, 13, 2, 17, 4, 0, 0, + 14, 4, 0, 6, 16, 16, 14, 13, 13, 12, 11, 3, 11, 11, 3, 6, 7, 4, 14, + 10, 8, 13, 7, 14, 2, 5, 2, 0, 14, 3, 15, 5, 5, 7, 2, 14, 15, 5, 10, + 10, 16, 7, 9, 14, 0, 15, 7, 13, 1, 15, 1, 4, 5, 4, 6, 16, 13, 6, 17, + 8, 6, 9, 3, 8, 6, 6, 14, 11, 14, 6, 10, 10, 5, 1, 0, 16, 17, 9, 1, 5, + 0, 5, 15, 8, 0, 0, 8, 5, 3, 9, 4, 13, 12, 11, 0, 11, 0, 10, 6, 4, 13, + 8, 9, 11, 11, 3, 16, 1, 2, 16, 0, 9, 8, 3, 8, 8, 7, 12, 10, 9, 3, 13, + 5, 3, 9, 6, 2, 11, 10, 1, 16, 0, 2, 10, 17, 16, 8, 11, 5, 13, 0, 19, 19, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + -1); + + printf("\nDirected multi: "); + igraph_transitivity_undirected(&g, &global, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global, "%.10g"); + printf("\n"); + + printf("Undirected multi: "); + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_COLLAPSE, NULL); + igraph_transitivity_undirected(&g, &global2, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global2, "%.10g"); + printf("\n"); + + printf("Simple: "); + igraph_simplify(&g, true, true, NULL); + igraph_transitivity_undirected(&g, &global3, IGRAPH_TRANSITIVITY_NAN); + print_real(stdout, global3, "%.10g"); + printf("\n"); + + IGRAPH_ASSERT(global == global2); + IGRAPH_ASSERT(global == global3); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/global_transitivity.out b/tests/unit/global_transitivity.out new file mode 100644 index 0000000..4121901 --- /dev/null +++ b/tests/unit/global_transitivity.out @@ -0,0 +1,17 @@ +Null graph: NaN + +Singleton graph: NaN + +Two connected vertices: NaN + +Triangle: 1 + +Two-star: 0 + +Zachary karate club: 0.255682 + +Directed and multigraphs: + +Directed multi: 0.4357541899 +Undirected multi: 0.4357541899 +Simple: 0.4357541899 diff --git a/tests/unit/glpk_error.c b/tests/unit/glpk_error.c new file mode 100644 index 0000000..1839443 --- /dev/null +++ b/tests/unit/glpk_error.c @@ -0,0 +1,72 @@ + +#include + +#include + +#include "test_utilities.h" + +static clock_t start; + +/* Wait for at least a second before attempting interruption */ +igraph_bool_t interruption_handler(void) { + if ( ((double) (clock() - start)) / CLOCKS_PER_SEC > 1.0 ) { + IGRAPH_FINALLY_FREE(); + return true; + } else { + return false; + } +} + +int main(void) { + igraph_t graph; + igraph_vector_int_t res; + igraph_error_handler_t *ehandler; + + igraph_vector_int_init(&res, 0); + + /* Skip test when igraph does not have GLPK support. */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, -1); + ehandler = igraph_set_error_handler(igraph_error_handler_ignore); + if (igraph_feedback_arc_set(&graph, &res, NULL, IGRAPH_FAS_EXACT_IP_TI) == IGRAPH_UNIMPLEMENTED) { + igraph_destroy(&graph); + igraph_vector_int_destroy(&res); + return 77; + } + igraph_set_error_handler(ehandler); + igraph_destroy(&graph); + + + /* Current versions of GLPK will error if more than 100 million rows (MAX_M) are added. + The graph size of 700 is chosen to just exceed this size. If future GLPK + versions relax this restriction, the test will need to be updated accordinly. */ + igraph_full(&graph, 700, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + + ehandler = igraph_set_error_handler(igraph_error_handler_printignore); + IGRAPH_ASSERT(igraph_feedback_arc_set(&graph, &res, NULL, IGRAPH_FAS_EXACT_IP_TI) == IGRAPH_FAILURE); + igraph_set_error_handler(ehandler); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&res); + + VERIFY_FINALLY_STACK(); + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&res, 0); + igraph_erdos_renyi_game_gnm(&graph, 100, 200, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_set_interruption_handler(interruption_handler); + ehandler = igraph_set_error_handler(igraph_error_handler_printignore); + start = clock(); + IGRAPH_ASSERT(igraph_feedback_arc_set(&graph, &res, NULL, IGRAPH_FAS_EXACT_IP_TI) == IGRAPH_INTERRUPTED); + igraph_set_error_handler(ehandler); + igraph_set_interruption_handler(NULL); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/gml.c b/tests/unit/gml.c new file mode 100644 index 0000000..54d5c61 --- /dev/null +++ b/tests/unit/gml.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include +#include + +#include "test_utilities.h" + +void test_input(const char *filename) { + igraph_t graph; + FILE *ifile; + + ifile = fopen(filename, "r"); + if (! ifile) { + IGRAPH_FATALF("Cannot open '%s'.\n", filename); + } + + printf("===== %s =====\n", filename); + + IGRAPH_ASSERT(igraph_read_graph_gml(&graph, ifile) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_write_graph_gml(&graph, stdout, IGRAPH_WRITE_GML_DEFAULT_SW, NULL, "igraph") == IGRAPH_SUCCESS); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + printf("======================\n\n"); +} + +int main(void) { + + /* Enable attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + test_input("graph1.gml"); + test_input("graph2.gml"); + test_input("graph3.gml"); + + return 0; +} diff --git a/tests/unit/gml.out b/tests/unit/gml.out new file mode 100644 index 0000000..9a63918 --- /dev/null +++ b/tests/unit/gml.out @@ -0,0 +1,79 @@ +===== graph1.gml ===== +Creator "igraph" +Version 1 +graph +[ + directed 1 + AttOne "x" + AttTwo 3.14 + node + [ + id 1 + b "2" + c 5.6 + nan "" + ] + node + [ + id 2 + a 2 + b "asd" + d -Inf + nan "foo" + ] + node + [ + id 5 + b "" + nan "" + ] + edge + [ + source 5 + target 1 + weight -0.45 + label "-0.45" + ] + edge + [ + source 2 + target 5 + label "Tom & Jerry's "friendship"" + num1 Inf + ] +] +====================== + +===== graph2.gml ===== +Creator "igraph" +Version 1 +graph +[ + directed 1 + node + [ + id 0 + Foo "" + ] + node + [ + id 1 + Foo "" + ] + node + [ + id 2 + Foo "bar & baz" + ] +] +====================== + +===== graph3.gml ===== +Creator "igraph" +Version 1 +graph +[ + directed 0 +] +====================== + diff --git a/tests/unit/graph1.gml b/tests/unit/graph1.gml new file mode 100644 index 0000000..eea87d0 --- /dev/null +++ b/tests/unit/graph1.gml @@ -0,0 +1,44 @@ +# comment lines are ignored +Version 1 +graph [ + directed 1 + node [ + id 1 + a [ b 1 ] + b +2 + c 5.6 + graphics [ x 0 y 0 ] + ] + node [ + a 1 + id 2 + a 2 + b "asd" + d -Inf + nan "foo" + ] + edge [ + source 5 target 1 + weight -0.45 + label -0.45 + num1 NAN + ] + node [ + id 5 + c [ str "bar" ] + ] + edge [ + source 2 target 5 + label "Tom & Jerry's "friendship"" +# The 'sou_rce' attribute will be ignored by the GML writer as it would conflict with +# 'source' after stripping the '_' character. + sou_rce 1.0 + num1 +inF + ] + AttOne 1 + AttOne "x" + AttTwo 3.14 +] + +# second graph is ignored +graph [ node [ ] ] diff --git a/tests/unit/graph2.gml b/tests/unit/graph2.gml new file mode 100644 index 0000000..31b089d --- /dev/null +++ b/tests/unit/graph2.gml @@ -0,0 +1,11 @@ + +# This graph contains nodes with no id fields, +# as well as invalid version and directedness. +# There is no newline at the end of this file. + +Version 3 +graph [ + directed 2 + node [ ] node [ ] node [ Foo "bar & baz" ] + composite [ a 1 ] +] diff --git a/tests/unit/graph3.gml b/tests/unit/graph3.gml new file mode 100644 index 0000000..5858ebf --- /dev/null +++ b/tests/unit/graph3.gml @@ -0,0 +1,4 @@ + +# Null graph, undirected + +graph [] diff --git a/tests/unit/graphlets.c b/tests/unit/graphlets.c new file mode 100644 index 0000000..e65d2fb --- /dev/null +++ b/tests/unit/graphlets.c @@ -0,0 +1,149 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void print_cliques_and_thresholds(igraph_vector_int_list_t* cliques, igraph_vector_t* thresholds) { + printf("Cliques:\n"); + print_vector_int_list(cliques); + printf("Thresholds:\n"); + print_vector(thresholds); +} + +void print_cliques_and_mu(igraph_vector_int_list_t* cliques, igraph_vector_t* mu) { + printf("Cliques:\n"); + print_vector_int_list(cliques); + printf("Mu:\n"); + print_vector_format(mu, stdout, "%.5f"); +} + +void test_graphlets_candidate_basis_simple(void) { + igraph_t g; + igraph_vector_int_list_t cliques; + igraph_vector_t thresholds; + igraph_vector_t weights; + igraph_int_t eid; + + igraph_full(&g, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_vector_int_list_init(&cliques, 0); + igraph_vector_init(&thresholds, 0); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1); + + igraph_graphlets_candidate_basis(&g, &weights, &cliques, &thresholds); + print_cliques_and_thresholds(&cliques, &thresholds); + + igraph_get_eid(&g, &eid, 0, 1, IGRAPH_UNDIRECTED, /* error = */ 0); + VECTOR(weights)[eid] = 2; + + igraph_graphlets_candidate_basis(&g, &weights, &cliques, &thresholds); + print_cliques_and_thresholds(&cliques, &thresholds); + + igraph_vector_destroy(&weights); + igraph_vector_int_list_destroy(&cliques); + igraph_vector_destroy(&thresholds); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); +} + +void test_graphlets_filtering(void) { + igraph_t g; + igraph_vector_int_list_t cliques; + igraph_vector_t thresholds; + igraph_vector_t weights_vec; + igraph_real_t weights[] = { 8, 8, 8, 5, 5, 5, 5, 5 }; + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, -1); + igraph_vector_int_list_init(&cliques, 0); + igraph_vector_init(&thresholds, 0); + weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + + igraph_graphlets_candidate_basis(&g, &weights_vec, &cliques, &thresholds); + print_cliques_and_thresholds(&cliques, &thresholds); + + igraph_vector_int_list_destroy(&cliques); + igraph_vector_destroy(&thresholds); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); +} + +void test_zachary_random_weights(void) { + igraph_t g; + igraph_vector_int_list_t cliques; + igraph_vector_t thresholds; + igraph_vector_t weights_vec; + igraph_real_t weights[] = { + 1, 5, 1, 1, 2, 4, 2, 2, 1, 4, 1, 5, 4, 2, 2, 3, 1, 1, 3, 4, 5, 5, 5, 4, + 2, 4, 3, 2, 1, 2, 3, 2, 4, 4, 2, 5, 4, 5, 4, 2, 2, 3, 1, 5, 2, 2, 2, 4, + 3, 5, 2, 2, 2, 5, 1, 1, 4, 5, 2, 1, 5, 4, 4, 1, 3, 3, 5, 5, 4, 5, 4, 2, + 2, 1, 2, 5, 5, 4 + }; + + igraph_famous(&g, "zachary"); + igraph_vector_int_list_init(&cliques, 0); + igraph_vector_init(&thresholds, 0); + weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + + igraph_graphlets_candidate_basis(&g, &weights_vec, &cliques, &thresholds); + print_cliques_and_thresholds(&cliques, &thresholds); + + igraph_vector_int_list_destroy(&cliques); + igraph_vector_destroy(&thresholds); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + +} + +void test_projection(void) { + igraph_t g; + igraph_vector_int_list_t cliques; + igraph_vector_t mu; + igraph_vector_t weights_vec; + igraph_real_t weights[] = { 2, 2, 3, 1, 1, 4, 4, 4 }; + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, -1); + igraph_vector_int_list_init(&cliques, 0); + igraph_vector_init(&mu, 0); + weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + + igraph_graphlets(&g, &weights_vec, &cliques, &mu, 1000); + print_cliques_and_mu(&cliques, &mu); + + igraph_vector_int_list_destroy(&cliques); + igraph_vector_destroy(&mu); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + test_graphlets_candidate_basis_simple(); + test_graphlets_filtering(); + test_zachary_random_weights(); + test_projection(); + + return 0; +} diff --git a/tests/unit/graphlets.out b/tests/unit/graphlets.out new file mode 100644 index 0000000..3138de0 --- /dev/null +++ b/tests/unit/graphlets.out @@ -0,0 +1,111 @@ +Cliques: +{ + 0: ( 0 1 2 3 4 ) +} +Thresholds: +( 1 ) +Cliques: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 1 ) +} +Thresholds: +( 1 2 ) +Cliques: +{ + 0: ( 0 1 2 ) + 1: ( 1 2 3 4 ) +} +Thresholds: +( 8 5 ) +Cliques: +{ + 0: ( 0 11 ) + 1: ( 9 33 ) + 2: ( 2 9 ) + 3: ( 14 32 33 ) + 4: ( 15 32 33 ) + 5: ( 5 6 16 ) + 6: ( 0 1 17 ) + 7: ( 0 3 12 ) + 8: ( 26 29 33 ) + 9: ( 20 32 33 ) + 10: ( 0 1 21 ) + 11: ( 22 32 33 ) + 12: ( 18 32 33 ) + 13: ( 23 27 33 ) + 14: ( 23 29 32 33 ) + 15: ( 23 25 ) + 16: ( 24 25 31 ) + 17: ( 24 27 ) + 18: ( 2 27 ) + 19: ( 28 31 33 ) + 20: ( 2 28 ) + 21: ( 0 4 6 ) + 22: ( 0 4 10 ) + 23: ( 31 32 33 ) + 24: ( 0 31 ) + 25: ( 0 5 6 ) + 26: ( 0 5 10 ) + 27: ( 19 33 ) + 28: ( 0 1 19 ) + 29: ( 13 33 ) + 30: ( 8 30 32 33 ) + 31: ( 1 30 ) + 32: ( 0 1 2 3 7 ) + 33: ( 0 1 2 3 13 ) + 34: ( 2 8 32 ) + 35: ( 0 2 8 ) + 36: ( 14 33 ) + 37: ( 15 33 ) + 38: ( 5 6 ) + 39: ( 0 17 ) + 40: ( 1 17 ) + 41: ( 3 12 ) + 42: ( 26 33 ) + 43: ( 26 29 ) + 44: ( 20 32 ) + 45: ( 0 21 ) + 46: ( 1 21 ) + 47: ( 22 33 ) + 48: ( 27 33 ) + 49: ( 23 29 33 ) + 50: ( 29 32 33 ) + 51: ( 23 29 ) + 52: ( 23 33 ) + 53: ( 24 25 ) + 54: ( 28 31 ) + 55: ( 4 6 ) + 56: ( 4 10 ) + 57: ( 31 33 ) + 58: ( 31 32 ) + 59: ( 0 6 ) + 60: ( 5 10 ) + 61: ( 0 19 ) + 62: ( 1 19 ) + 63: ( 8 30 33 ) + 64: ( 8 30 ) + 65: ( 8 33 ) + 66: ( 1 7 ) + 67: ( 0 2 7 ) + 68: ( 2 3 7 ) + 69: ( 2 7 ) + 70: ( 3 7 ) + 71: ( 1 13 ) + 72: ( 0 2 13 ) + 73: ( 2 3 13 ) + 74: ( 0 13 ) + 75: ( 0 2 ) + 76: ( 2 8 ) +} +Thresholds: +( 4 2 2 2 3 2 1 1 2 1 1 1 2 2 1 5 3 1 3 4 2 1 1 4 3 2 1 2 1 2 1 4 1 1 1 2 4 5 5 4 5 4 5 5 5 2 5 4 4 2 2 4 5 4 5 5 4 5 5 4 4 2 5 2 3 5 3 2 2 4 4 4 2 2 5 5 3 ) +Cliques: +{ + 0: ( 2 3 4 ) + 1: ( 0 1 2 ) + 2: ( 1 2 3 4 ) + 3: ( 1 2 ) +} +Mu: +( 1.13842 0.92554 0.86148 0.00000 ) diff --git a/tests/unit/graphml-default-attrs.xml b/tests/unit/graphml-default-attrs.xml new file mode 100644 index 0000000..5adca45 --- /dev/null +++ b/tests/unit/graphml-default-attrs.xml @@ -0,0 +1,25 @@ + + + + TRUE + + + male + + + 20 + + + FALSE + + + FALSE30 + female + + + + + diff --git a/tests/unit/graphml-hsa05010.xml b/tests/unit/graphml-hsa05010.xml new file mode 100644 index 0000000..507750e --- /dev/null +++ b/tests/unit/graphml-hsa05010.xml @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + hsa + 05010 + + compound + 1 + cpd:C00027 + C00027 + + + + + + compound + 2 + cpd:C00070 + C00070... + + + + + + compound + 2 + cpd:C14818 + C00070... + + + + + + gene + 4 + hsa:51107 + APH1A + Q96BI3 + APH1A + GO:0005515,GO:0016021,GO:0016485,GO:0043085 + + + gene + 6 + hsa:4311 + MME + P08473 + MME + GO:0004245,GO:0016021,GO:0016787,GO:0008237,GO:0007267,GO:0006508,GO:0005887,GO:0005886,GO:0016020 + + + gene + 7 + hsa:2932 + GSK3B + P49841 + GSK3B + GO:0016301,GO:0016740,GO:0005977,GO:0004696,GO:0005524,GO:0004674,GO:0006468,GO:0004672 + + + gene + 8 + hsa:4137 + MAPT + + + + + + gene + 9 + hsa:836 + CASP3 + P42574 + CASP3 + GO:0016787,GO:0008233,GO:0006917,GO:0030693,GO:0008234,GO:0006915,GO:0006508 + + + gene + 11 + hsa:840 + CASP7 + P55210 + CASP7 + GO:0008233,GO:0016787,GO:0008632,GO:0008234,GO:0006915,GO:0005737,GO:0006508 + + + gene + 12 + hsa:55851 + PSENEN + Q9NZ42 + PEN2 + + + + gene + 13 + hsa:6622 + SNCA + P37840 + SNCA + GO:0005737,GO:0007417,GO:0006916 + + + gene + 14 + hsa:5663 + PSEN1... + P49768 + + GO:0016021,GO:0007059,GO:0007001,GO:0006916,GO:0000776,GO:0000775,GO:0005639,GO:0005624,GO:0005783,GO:0007242 + + + gene + 14 + hsa:5664 + PSEN1... + P49810 + PSEN2 + GO:0016021,GO:0008632,GO:0007059,GO:0007001,GO:0000776,GO:0005639,GO:0005783,GO:0007242 + + + gene + 15 + hsa:836 + CASP3 + P42574 + CASP3 + GO:0016787,GO:0008233,GO:0006917,GO:0030693,GO:0008234,GO:0006915,GO:0006508 + + + gene + 16 + hsa:23385 + NCSTN + Q92542 + NCSTN + GO:0016021,GO:0016485,GO:0006508 + + + gene + 17 + hsa:2 + A2M + P01023 + A2M + GO:0017114,GO:0004866,GO:0051260,GO:0019899,GO:0008320,GO:0006886,GO:0004867 + + + gene + 18 + hsa:3416 + IDE + P14735 + IDE + GO:0004231,GO:0016787,GO:0008237,GO:0007548,GO:0007267,GO:0007165,GO:0006508,GO:0005777,GO:0005625,GO:0005615,GO:0004871,GO:0003824,GO:0004222 + + + gene + 19 + hsa:8883 + APPBP1 + Q13564 + APPBP1 + GO:0007165,GO:0005737,GO:0003824 + + + gene + 20 + hsa:23621 + BACE1... + P56817 + BACE1 + GO:0004190,GO:0008233,GO:0009049,GO:0016021,GO:0016787,GO:0050435,GO:0005768,GO:0006508,GO:0005794,GO:0006509,GO:0008798,GO:0005887,GO:0004194 + + + gene + 20 + hsa:25825 + BACE1... + Q9Y5Z0 + + GO:0004190,GO:0009049,GO:0016021,GO:0016787,GO:0009306,GO:0006464,GO:0005624,GO:0006508,GO:0004194 + + + gene + 22 + hsa:351 + APP, AD1 + P05067 + APP + GO:0008201,GO:0007155,GO:0006915,GO:0005905,GO:0006897,GO:0016021,GO:0005515,GO:0005887,GO:0005576,GO:0004867 + + + gene + 23 + hsa:2597 + GAPDH, GAPD + P04406 + + + + + gene + 24 + hsa:348 + APOE, AD2 + P02649 + APOE + GO:0001540,GO:0008015,GO:0007271,GO:0005737,GO:0005319,GO:0008201,GO:0006869,GO:0008289 + + + gene + 25 + hsa:322 + APBB1, RIR + O00213 + APBB1 + GO:0007165,GO:0001540,GO:0008134,GO:0045449,GO:0050821,GO:0005634,GO:0030308,GO:0045749,GO:0035035,GO:0007050,GO:0030048,GO:0045202,GO:0030027,GO:0030426,GO:0007409,GO:0050760 + + + gene + 26 + hsa:4023 + LPL + P06858 + LPL + GO:0005319,GO:0004465,GO:0016787,GO:0016042,GO:0008201,GO:0008015,GO:0006631,GO:0005576,GO:0006629,GO:0003824 + + + gene + 27 + hsa:4035 + LRP1, APR, A2MR + Q07954 + LRP1 + GO:0016021,GO:0004872,GO:0016020,GO:0008283,GO:0008034,GO:0006629,GO:0005887,GO:0005624,GO:0005509,GO:0005319,GO:0006897,GO:0005905 + + + 4.95265 + 0.693152 + 0.185704 + 0.670769 + 0.145403 + 0.05698 + 0.172033 + 0.00546283 + 2.93737 + 0.556617 + 0.176068 + 0.483953 + + + 0.50493 + 0.112413 + 0.111437 + 0.033196 + 0.145403 + 0.0605613 + 0.181454 + 0.00580618 + -0 + -0 + 0.171988 + -0 + + + 7.8977 + 1 + 1 + 0.995807 + 9.64739 + 1 + 1 + 0.998753 + 2.93737 + 0.400174 + 0.136512 + 0.347932 + + + 3.5307 + 0.498171 + 0.123255 + 0.455066 + 4.0529 + 0.366988 + 0.0822633 + 0.344877 + + + 5.41325 + 1 + 1 + 0.976533 + -0 + -0 + 0.151863 + -0 + 9.56439 + 1 + 1 + 0.998679 + + + 3.20433 + 0.383125 + 0.0883496 + 0.341558 + 0.145403 + 0.0605613 + 0.181454 + 0.00580618 + 10.0077 + 1 + 1 + 0.999029 + + + + + 0.50493 + 0.0888891 + 0.0880977 + 0.0262494 + 5.53428 + 1 + 1 + 0.978422 + -0 + -0 + 0.137908 + -0 + + + 3.17114 + 0.346041 + 0.0972198 + 0.307624 + 2.60224 + 1 + 1 + 0.835317 + -0 + -0 + 0.137908 + -0 + + + + + diff --git a/tests/unit/graphml-lenient.xml b/tests/unit/graphml-lenient.xml new file mode 100644 index 0000000..9130abe --- /dev/null +++ b/tests/unit/graphml-lenient.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/unit/graphml-namespace.xml b/tests/unit/graphml-namespace.xml new file mode 100644 index 0000000..83e5203 --- /dev/null +++ b/tests/unit/graphml-namespace.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/tests/unit/graphml-whitespace.xml b/tests/unit/graphml-whitespace.xml new file mode 100644 index 0000000..681ca80 --- /dev/null +++ b/tests/unit/graphml-whitespace.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + 1 + spam + true + + + + + + + + + + 3 + ham + true + + + + 4 + bacon + true + + + + 5 + eggs + true + + + + + + + + + + + diff --git a/tests/unit/graphml-yed.xml b/tests/unit/graphml-yed.xml new file mode 100644 index 0000000..bd66572 --- /dev/null +++ b/tests/unit/graphml-yed.xml @@ -0,0 +1,177 @@ + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/unit/harmonic_centrality.c b/tests/unit/harmonic_centrality.c new file mode 100644 index 0000000..b1ebfca --- /dev/null +++ b/tests/unit/harmonic_centrality.c @@ -0,0 +1,138 @@ +/* + igraph library. + Copyright (C) 2020-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_t res; + igraph_vector_t weights; + + igraph_vector_init(&res, 0); + + /* Path graph */ + igraph_ring(&graph, 7, IGRAPH_DIRECTED, 0, /* circular */ 0); + + printf("Unweighted undirected:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + printf("Unweighted directed:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_OUT, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + + printf("Unweighted undirected, cutoff=0:\n"); + igraph_harmonic_centrality_cutoff(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1, 0); + print_vector(&res); + printf("Unweighted undirected, cutoff=1:\n"); + igraph_harmonic_centrality_cutoff(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1, 1); + print_vector(&res); + + igraph_vector_init(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.0); + + printf("Unit-weighted undirected:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + printf("Unit-weighted directed:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_OUT, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + + printf("Unit-weighted undirected, cutoff=0:\n"); + igraph_harmonic_centrality_cutoff(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1, 0); + print_vector(&res); + printf("Unit-weighted undirected, cutoff=1:\n"); + igraph_harmonic_centrality_cutoff(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1, 1); + print_vector(&res); + + igraph_vector_destroy(&weights); + + igraph_vector_init_range(&weights, 1, igraph_ecount(&graph) + 1); + printf("Weighted undirected:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + printf("Weighted directed:\n"); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_OUT, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + igraph_vector_destroy(&weights); + + igraph_destroy(&graph); + + /* Graphs with no edges */ + + igraph_vector_init(&weights, 0); + + /* Null graph */ + + printf("Null graph:\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + igraph_destroy(&graph); + + /* Singleton graph */ + + printf("Singleton graph:\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + igraph_destroy(&graph); + + /* Empty graph with two vertices */ + + printf("Empty graph with two vertices:\n"); + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + + /* Graph with multiple connected components and isolated vertices */ + + printf("Multiple components, unweighted:\n"); + igraph_small(&graph, 20, IGRAPH_UNDIRECTED, + 1, 2, 2, 3, 1, 3, 4, 5, 5, 6, 6, 7, 5, 8, 8, 9, 6, 10, 10, 11, 7, 11, + 9, 13, 9, 14, 9, 15, 9, 16, 13, 14, 13, 15, 13, 16, 14, 15, 14, 16, + 15, 16, 17, 18, 4, 19, 4, 20, + -1); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ NULL, /* normalized= */ 1); + print_vector(&res); + + printf("Multiple components, constant weight vector:\n"); + igraph_vector_init(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.0 / 7); + igraph_harmonic_centrality(&graph, &res, igraph_vss_all(), IGRAPH_ALL, /* weights= */ &weights, /* normalized= */ 1); + print_vector(&res); + igraph_vector_destroy(&weights); + + igraph_destroy(&graph); + + igraph_vector_destroy(&res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/harmonic_centrality.out b/tests/unit/harmonic_centrality.out new file mode 100644 index 0000000..b4d218b --- /dev/null +++ b/tests/unit/harmonic_centrality.out @@ -0,0 +1,33 @@ +Unweighted undirected: +( 0.408333 0.547222 0.597222 0.611111 0.597222 0.547222 0.408333 ) +Unweighted directed: +( 0.408333 0.380556 0.347222 0.305556 0.25 0.166667 0 ) +Unweighted undirected, cutoff=0: +( 0 0 0 0 0 0 0 ) +Unweighted undirected, cutoff=1: +( 0.166667 0.333333 0.333333 0.333333 0.333333 0.333333 0.166667 ) +Unit-weighted undirected: +( 0.408333 0.547222 0.597222 0.611111 0.597222 0.547222 0.408333 ) +Unit-weighted directed: +( 0.408333 0.380556 0.347222 0.305556 0.25 0.166667 0 ) +Unit-weighted undirected, cutoff=0: +( 0 0 0 0 0 0 0 ) +Unit-weighted undirected, cutoff=1: +( 0.166667 0.333333 0.333333 0.333333 0.333333 0.333333 0.166667 ) +Weighted undirected: +( 0.285714 0.32209 0.241402 0.187963 0.149146 0.116534 0.0795695 ) +Weighted directed: +( 0.285714 0.155423 0.102513 0.0712963 0.0484848 0.0277778 0 ) +Null graph: +( ) +( ) +Singleton graph: +( 0 ) +( 0 ) +Empty graph with two vertices: +( 0 0 ) +( 0 0 ) +Multiple components, unweighted: +( 0 0.1 0.1 0.1 0.3125 0.358333 0.325 0.260833 0.329167 0.368333 0.260833 0.23 0 0.315 0.315 0.315 0.315 0.05 0.05 0.220833 0.220833 ) +Multiple components, constant weight vector: +( 0 0.7 0.7 0.7 2.1875 2.50833 2.275 1.82583 2.30417 2.57833 1.82583 1.61 0 2.205 2.205 2.205 2.205 0.35 0.35 1.54583 1.54583 ) diff --git a/tests/unit/heap.c b/tests/unit/heap.c new file mode 100644 index 0000000..666619d --- /dev/null +++ b/tests/unit/heap.c @@ -0,0 +1,145 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_heap_t h_max; + igraph_heap_min_t h_min; + igraph_int_t i; + igraph_real_t list[] = {-2, -9.999, 0, 6, 235, -2, -1000, -1, 4, 2000, 6, 0.5, 1, -9, 10}; + const igraph_int_t l_size = sizeof(list) / sizeof(list[0]); + + /* max heap init & destroy*/ + printf("Create empty max heap & destroy\n"); + igraph_heap_init(&h_max, 0); + IGRAPH_ASSERT(igraph_heap_empty(&h_max)); + igraph_heap_destroy(&h_max); + printf("Create empty max heap but allocate size for some elements\n"); + igraph_heap_init(&h_max, 10); + IGRAPH_ASSERT(igraph_heap_empty(&h_max)); + igraph_heap_destroy(&h_max); + + /* min heap init & destroy*/ + printf("Create empty min heap & destroy\n"); + igraph_heap_min_init(&h_min, 0); + IGRAPH_ASSERT(igraph_heap_min_empty(&h_min)); + igraph_heap_min_destroy(&h_min); + printf("Create empty min heap but allocate size for some elements\n"); + igraph_heap_min_init(&h_min, 10); + IGRAPH_ASSERT(igraph_heap_min_empty(&h_min)); + igraph_heap_min_destroy(&h_min); + + /* max heap_reserve, heap_size and heap_empty*/ + printf("Test max heap_reserve, heap_size and heap_empty\n"); + igraph_heap_init(&h_max, 5); + IGRAPH_ASSERT(igraph_heap_empty(&h_max)); + IGRAPH_ASSERT(igraph_heap_size(&h_max) == 0); + igraph_heap_reserve(&h_max, 10); + IGRAPH_ASSERT(igraph_heap_empty(&h_max)); + IGRAPH_ASSERT(igraph_heap_size(&h_max) == 0); + for (i=0; i < 15; i++){ + igraph_heap_push(&h_max,i); + } + IGRAPH_ASSERT(igraph_heap_size(&h_max) == 15); + IGRAPH_ASSERT(!igraph_heap_empty(&h_max)); + igraph_heap_reserve(&h_max, 5); + IGRAPH_ASSERT(igraph_heap_size(&h_max) == 15); + IGRAPH_ASSERT(!igraph_heap_empty(&h_max)); + igraph_heap_destroy(&h_max); + + /* min heap reserve, heap_size and heap_empty*/ + printf("Test min heap_reserve, heap_size and heap_empty\n"); + igraph_heap_min_init(&h_min, 5); + IGRAPH_ASSERT(igraph_heap_min_empty(&h_min)); + IGRAPH_ASSERT(igraph_heap_min_size(&h_min) == 0); + igraph_heap_min_reserve(&h_min, 10); + IGRAPH_ASSERT(igraph_heap_min_empty(&h_min)); + IGRAPH_ASSERT(igraph_heap_min_size(&h_min) == 0); + for (i=0; i < 15; i++){ + igraph_heap_min_push(&h_min, i); + } + IGRAPH_ASSERT(igraph_heap_min_size(&h_min) == 15); + IGRAPH_ASSERT(!igraph_heap_min_empty(&h_min)); + igraph_heap_min_reserve(&h_min, 5); + IGRAPH_ASSERT(igraph_heap_min_size(&h_min) == 15); + IGRAPH_ASSERT(!igraph_heap_min_empty(&h_min)); + igraph_heap_min_destroy(&h_min); + + /* max heap init_array and delete_top */ + printf("Test max heap_init array and delete_top\n"); + igraph_heap_init_array(&h_max,list,l_size); + while (igraph_heap_size(&h_max) > 0){ + printf("%g ", igraph_heap_delete_top(&h_max)); + } + printf("\n"); + igraph_heap_destroy(&h_max); + + /* min heap init_array and delete_top */ + printf("Test min heap init_array and delete_top\n"); + igraph_heap_min_init_array(&h_min, list, l_size); + while (igraph_heap_min_size(&h_min) > 0) { + printf("%g ", igraph_heap_min_delete_top(&h_min)); + } + printf("\n"); + igraph_heap_min_destroy(&h_min); + + /* max heap top and push */ + printf("Test max heap top and push\n"); + igraph_heap_init(&h_max, 0); + for (i=0; i < l_size; i++){ + igraph_heap_push(&h_max, list[i]); + printf("%g ", igraph_heap_top(&h_max)); + } + printf("\n"); + while (igraph_heap_size(&h_max)>0){ + printf("%g ", igraph_heap_delete_top(&h_max)); + } + printf("\n"); + + /* min heap top and push */ + printf("Test min heap top and push\n"); + igraph_heap_min_init(&h_min, 0); + for (i=0; i < l_size; i++){ + igraph_heap_min_push(&h_min, list[i]); + printf("%g ", igraph_heap_min_top(&h_min)); + } + printf("\n"); + while (igraph_heap_min_size(&h_min) > 0){ + printf("%g ", igraph_heap_min_delete_top(&h_min)); + } + printf("\n"); + + igraph_heap_destroy(&h_max); + igraph_heap_min_destroy(&h_min); + + /* Test initializing empty heap from array. */ + igraph_heap_init_array(&h_max, list, 0); + IGRAPH_ASSERT(igraph_heap_empty(&h_max)); + igraph_heap_push(&h_max, 3.0); + IGRAPH_ASSERT(! igraph_heap_empty(&h_max)); + IGRAPH_ASSERT(igraph_heap_top(&h_max) == 3.0); + igraph_heap_destroy(&h_max); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/heap.out b/tests/unit/heap.out new file mode 100644 index 0000000..28bab2f --- /dev/null +++ b/tests/unit/heap.out @@ -0,0 +1,16 @@ +Create empty max heap & destroy +Create empty max heap but allocate size for some elements +Create empty min heap & destroy +Create empty min heap but allocate size for some elements +Test max heap_reserve, heap_size and heap_empty +Test min heap_reserve, heap_size and heap_empty +Test max heap_init array and delete_top +2000 235 10 6 6 4 1 0.5 0 -1 -2 -2 -9 -9.999 -1000 +Test min heap init_array and delete_top +-1000 -9.999 -9 -2 -2 -1 0 0.5 1 4 6 6 10 235 2000 +Test max heap top and push +-2 -2 0 6 235 235 235 235 235 2000 2000 2000 2000 2000 2000 +2000 235 10 6 6 4 1 0.5 0 -1 -2 -2 -9 -9.999 -1000 +Test min heap top and push +-2 -9.999 -9.999 -9.999 -9.999 -9.999 -1000 -1000 -1000 -1000 -1000 -1000 -1000 -1000 -1000 +-1000 -9.999 -9 -2 -2 -1 0 0.5 1 4 6 6 10 235 2000 diff --git a/tests/unit/hub_and_authority.c b/tests/unit/hub_and_authority.c new file mode 100644 index 0000000..d3c2807 --- /dev/null +++ b/tests/unit/hub_and_authority.c @@ -0,0 +1,148 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +void print_hub_and_authority(igraph_t *g, igraph_vector_t *weights, igraph_bool_t use_options) { + igraph_arpack_options_t options; + igraph_vector_t hub_vector, authority_vector; + igraph_real_t value; + + igraph_arpack_options_init(&options); + igraph_vector_init(&hub_vector, 0); + igraph_vector_init(&authority_vector, 0); + + printf("--------------------------------------------------\n"); + + igraph_hub_and_authority_scores(g, &hub_vector, &authority_vector, &value, + weights, use_options ? &options : NULL); + + vector_chop(&hub_vector, 10e-10); + vector_chop(&authority_vector, 10e-10); + printf("hub:\n"); + print_vector(&hub_vector); + printf("authority:\n"); + print_vector(&authority_vector); + printf("value:\n"); + print_real(stdout, value, "%g"); + printf("\n"); + printf("--------------------------------------------------\n\n\n"); + igraph_vector_destroy(&hub_vector); + igraph_vector_destroy(&authority_vector); +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_arpack_options_t options; + igraph_real_t value; + + igraph_arpack_options_init(&options); + + printf("Null graph:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + print_hub_and_authority(&g, NULL, true); + igraph_destroy(&g); + + printf("Singleton graph with loop:\n"); + igraph_small(&g, 1, IGRAPH_DIRECTED, 0,0, -1); + print_hub_and_authority(&g, NULL, true); + igraph_destroy(&g); + + printf("Singleton graph with three loops:\n"); + igraph_small(&g, 1, IGRAPH_DIRECTED, 0,0, 0,0, 0,0, -1); + print_hub_and_authority(&g, NULL, true); + igraph_destroy(&g); + + printf("Three vertices, no links:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, -1); + print_hub_and_authority(&g, NULL, true); + igraph_destroy(&g); + + printf("Two hubs and one authority:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 2, + 1, 1); + print_hub_and_authority(&g, &weights, true); + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + /* https://nlp.stanford.edu/IR-book/html/htmledition/hubs-and-authorities-1.html */ + /* with different normalization */ + printf("Stanford example:\n"); + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0,2, 1,1, 1,2, 2,0, 2,2, 2,3, 3,3, 3,4, 4,6, 5,5, + 5,6, 6,3, 6,4, 6,6, -1); + igraph_vector_init_int(&weights, 14, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 2, 1, 1); + print_hub_and_authority(&g, &weights, false); + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + printf("Same example with scaling:\n"); + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0,2, 1,1, 1,2, 2,0, 2,2, 2,3, 3,3, 3,4, 4,6, 5,5, + 5,6, 6,3, 6,4, 6,6, -1); + igraph_vector_init_int(&weights, 14, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 2, 1, 1); + print_hub_and_authority(&g, &weights, false); + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + /* Verify that self-loops are counted twice in undirected graphs. */ + printf("Undirected graph with self-loops and multi-edges:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 2, 2, 3, 2, 3, + -1); + print_hub_and_authority(&g, NULL, false); + igraph_destroy(&g); + + printf("Degenerate example:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,0, 1,2, 2,1, 2,3, 3,0, -1); + igraph_hub_and_authority_scores(&g, NULL, NULL, &value, + NULL, &options); + printf("--------------------------------------------------\n"); + printf("value:\n"); + print_real(stdout, value, "%g"); + printf("\n"); + printf("--------------------------------------------------\n\n\n"); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking invalid weight vector.\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 3, + 1, 1, 1); + IGRAPH_ASSERT(igraph_hub_and_authority_scores(&g, NULL, NULL, NULL, + &weights, &options) == IGRAPH_EINVAL); + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/hub_and_authority.out b/tests/unit/hub_and_authority.out new file mode 100644 index 0000000..4906b73 --- /dev/null +++ b/tests/unit/hub_and_authority.out @@ -0,0 +1,96 @@ +Null graph: +-------------------------------------------------- +hub: +( ) +authority: +( ) +value: +0 +-------------------------------------------------- + + +Singleton graph with loop: +-------------------------------------------------- +hub: +( 1 ) +authority: +( 1 ) +value: +1 +-------------------------------------------------- + + +Singleton graph with three loops: +-------------------------------------------------- +hub: +( 1 ) +authority: +( 1 ) +value: +9 +-------------------------------------------------- + + +Three vertices, no links: +-------------------------------------------------- +hub: +( 1 1 1 ) +authority: +( 1 1 1 ) +value: +0 +-------------------------------------------------- + + +Two hubs and one authority: +-------------------------------------------------- +hub: +( 1 1 0 ) +authority: +( 0 0 1 ) +value: +2 +-------------------------------------------------- + + +Stanford example: +-------------------------------------------------- +hub: +( 0.100055 0.109548 0.944987 0.5126 0.10588 0.115926 1 ) +authority: +( 0.214644 0.0248828 0.262253 1 0.343572 0.0263314 0.277521 ) +value: +11.5396 +-------------------------------------------------- + + +Same example with scaling: +-------------------------------------------------- +hub: +( 0.100055 0.109548 0.944987 0.5126 0.10588 0.115926 1 ) +authority: +( 0.214644 0.0248828 0.262253 1 0.343572 0.0263314 0.277521 ) +value: +11.5396 +-------------------------------------------------- + + +Undirected graph with self-loops and multi-edges: +-------------------------------------------------- +hub: +( 0.0906902 0.314507 1 0.576713 ) +authority: +( 0.0906902 0.314507 1 0.576713 ) +value: +12.0266 +-------------------------------------------------- + + +Degenerate example: +-------------------------------------------------- +value: +2.61803 +-------------------------------------------------- + + +Checking invalid weight vector. diff --git a/tests/unit/igraph_add_edges.c b/tests/unit/igraph_add_edges.c new file mode 100644 index 0000000..96d6d55 --- /dev/null +++ b/tests/unit/igraph_add_edges.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_int_t v; + + /* Create graph */ + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 2, 2, + -1); + + /* Add edges */ + igraph_vector_int_init_int(&v, 4, 2, 1, 3, 3); + igraph_add_edges(&g, &v, 0); + + /* Check result */ + igraph_get_edgelist(&g, &v, 0); + print_vector_int(&v); + + /* Error, vector length */ + igraph_vector_int_resize(&v, 3); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 1; + VECTOR(v)[2] = 2; + CHECK_ERROR(igraph_add_edges(&g, &v, 0), IGRAPH_EINVAL); + + /* Check result */ + igraph_get_edgelist(&g, &v, 0); + print_vector_int(&v); + + /* Error, vector IDs */ + igraph_vector_int_resize(&v, 4); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 1; + VECTOR(v)[2] = 2; + VECTOR(v)[3] = 4; + CHECK_ERROR(igraph_add_edges(&g, &v, 0), IGRAPH_EINVVID); + + /* Check result */ + igraph_get_edgelist(&g, &v, 0); + print_vector_int(&v); + + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_add_edges.out b/tests/unit/igraph_add_edges.out new file mode 100644 index 0000000..7db1ba9 --- /dev/null +++ b/tests/unit/igraph_add_edges.out @@ -0,0 +1,3 @@ +( 0 1 1 2 2 3 2 2 2 1 3 3 ) +( 0 1 1 2 2 3 2 2 2 1 3 3 ) +( 0 1 1 2 2 3 2 2 2 1 3 3 ) diff --git a/tests/unit/igraph_add_vertices.c b/tests/unit/igraph_add_vertices.c new file mode 100644 index 0000000..ea87f89 --- /dev/null +++ b/tests/unit/igraph_add_vertices.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t g1; + igraph_vector_int_t v1; + + /* Create a graph */ + igraph_vector_int_init(&v1, 8); + VECTOR(v1)[0] = 0; + VECTOR(v1)[1] = 1; + VECTOR(v1)[2] = 1; + VECTOR(v1)[3] = 2; + VECTOR(v1)[4] = 2; + VECTOR(v1)[5] = 3; + VECTOR(v1)[6] = 2; + VECTOR(v1)[7] = 2; + igraph_create(&g1, &v1, 0, 0); + igraph_vector_int_destroy(&v1); + + /* Add more vertices */ + igraph_add_vertices(&g1, 10, 0); + IGRAPH_ASSERT(igraph_vcount(&g1) == 14); + + /* Add more vertices */ + igraph_add_vertices(&g1, 0, 0); + IGRAPH_ASSERT(igraph_vcount(&g1) == 14); + + /* Error */ + CHECK_ERROR(igraph_add_vertices(&g1, -1, 0), IGRAPH_EINVAL); + + igraph_destroy(&g1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_adhesion.c b/tests/unit/igraph_adhesion.c new file mode 100644 index 0000000..1616dc4 --- /dev/null +++ b/tests/unit/igraph_adhesion.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_int_t value; + igraph_bool_t checks = 1; + + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, 1, 6, 6, 3, -1); + + igraph_adhesion(&g, &value, checks); + + IGRAPH_ASSERT(value == 0); + + igraph_destroy(&g); + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, 1, 6, 6, 3, -1); + + igraph_adhesion(&g, &value, checks); + + IGRAPH_ASSERT(value == 2); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_adjacency.c b/tests/unit/igraph_adjacency.c new file mode 100644 index 0000000..5abd8db --- /dev/null +++ b/tests/unit/igraph_adjacency.c @@ -0,0 +1,257 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_destroy(igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, igraph_loops_t loops) { + igraph_t g; + igraph_t g_sparse; + igraph_sparsemat_t sparse_adjmatrix, sparse_adjmatrix_comp; + igraph_bool_t same; + + igraph_adjacency(&g, adjmatrix, mode, loops); + + igraph_matrix_as_sparsemat(&sparse_adjmatrix, adjmatrix, 0.0001); + igraph_sparsemat_compress(&sparse_adjmatrix, &sparse_adjmatrix_comp); + igraph_sparse_adjacency(&g_sparse, &sparse_adjmatrix_comp, mode, loops); + print_graph_canon(&g); + + igraph_is_same_graph(&g, &g_sparse, &same); + if (!same) { + printf("Sparse graph differs from non-sparse:\n"); + print_graph_canon(&g_sparse); + exit(1); + } + + igraph_matrix_destroy(adjmatrix); + igraph_sparsemat_destroy(&sparse_adjmatrix); + igraph_sparsemat_destroy(&sparse_adjmatrix_comp); + igraph_destroy(&g); + igraph_destroy(&g_sparse); +} + +void check_error(igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, igraph_loops_t loops, igraph_error_t error) { + igraph_t g; + igraph_sparsemat_t sparse_adjmatrix, sparse_adjmatrix_comp; + + igraph_matrix_as_sparsemat(&sparse_adjmatrix, adjmatrix, 0.0001); + igraph_sparsemat_compress(&sparse_adjmatrix, &sparse_adjmatrix_comp); + + CHECK_ERROR(igraph_adjacency(&g, adjmatrix, mode, loops), error); + CHECK_ERROR(igraph_sparse_adjacency(&g, &sparse_adjmatrix_comp, mode, loops), error); + + igraph_sparsemat_destroy(&sparse_adjmatrix); + igraph_sparsemat_destroy(&sparse_adjmatrix_comp); +} + +int main(void) { + igraph_matrix_t adjmatrix; + + printf("\n0x0 matrix:\n"); + matrix_init_int_row_major(&adjmatrix, 0, 0, NULL); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_ONCE); + + printf("\n1x1 matrix, no loops:\n"); + { + int e[] = {1}; + matrix_init_int_row_major(&adjmatrix, 1, 1, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_NO_LOOPS); + } + printf("\n1x1 matrix, loops once:\n"); + { + int e[] = {1}; + matrix_init_int_row_major(&adjmatrix, 1, 1, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_ONCE); + } + printf("\n1x1 matrix, loops twice (treated as loops once):\n"); + { + int e[] = {1}; + matrix_init_int_row_major(&adjmatrix, 1, 1, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_TWICE); + } + + printf("\n3x3 matrix, IGRAPH_ADJ_DIRECTED, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_DIRECTED, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_DIRECTED, loops twice (treated as loops once):\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UNDIRECTED, no loops:\n"); + { + int e[] = {4, 2, 0, 2, 0, 4, 0, 4, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UNDIRECTED, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UNDIRECTED, loops once:\n"); + { + int e[] = {4, 2, 0, 2, 0, 4, 0, 4, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UNDIRECTED, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UNDIRECTED, loops twice:\n"); + { + int e[] = {4, 2, 0, 2, 0, 4, 0, 4, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UNDIRECTED, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MAX, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MAX, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MAX, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MAX, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MAX, loops twice:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MAX, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MIN, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 5, 0, 4, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MIN, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MIN, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MIN, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_MIN, loops twice:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_MIN, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_PLUS, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_PLUS, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_PLUS, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_PLUS, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_PLUS, loops twice:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_PLUS, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UPPER, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UPPER, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UPPER, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UPPER, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_UPPER, loops twice (treated as loops once):\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_UPPER, IGRAPH_LOOPS_TWICE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_LOWER, no loops:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_LOWER, IGRAPH_NO_LOOPS); + } + printf("\n3x3 matrix, IGRAPH_ADJ_LOWER, loops once:\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_LOWER, IGRAPH_LOOPS_ONCE); + } + printf("\n3x3 matrix, IGRAPH_ADJ_LOWER, loops twice (treated as loops once):\n"); + { + int e[] = {4, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + print_destroy(&adjmatrix, IGRAPH_ADJ_LOWER, IGRAPH_LOOPS_TWICE); + } + + VERIFY_FINALLY_STACK(); + + printf("\nCheck handling of non-square matrix error.\n"); + { + int e[] = {1, 2, 0}; + matrix_init_int_row_major(&adjmatrix, 3, 1, e); + check_error(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_EINVAL); + igraph_matrix_destroy(&adjmatrix); + } + printf("\nCheck handling of negative number of edges error.\n"); + { + int e[] = {1, 2, 0, -3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + check_error(&adjmatrix, IGRAPH_ADJ_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_EINVAL); + igraph_matrix_destroy(&adjmatrix); + } + printf("\nCheck handling of odd number in diagonal.\n"); + { + int e[] = {1, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + check_error(&adjmatrix, IGRAPH_ADJ_UNDIRECTED, IGRAPH_LOOPS_TWICE, IGRAPH_EINVAL); + igraph_matrix_destroy(&adjmatrix); + } + printf("\nCheck handling of invalid adjacency mode.\n"); + { + int e[] = {0, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + check_error(&adjmatrix, (igraph_adjacency_t) 42, IGRAPH_LOOPS_TWICE, IGRAPH_EINVAL); + igraph_matrix_destroy(&adjmatrix); + } + printf("\nCheck handling of non-symmetric matrix for IGRAPH_ADJ_UNDIRECTED.\n"); + { + int e[] = {0, 2, 0, 3, 0, 4, 0, 5, 6}; + matrix_init_int_row_major(&adjmatrix, 3, 3, e); + check_error(&adjmatrix, IGRAPH_ADJ_UNDIRECTED, IGRAPH_LOOPS_ONCE, IGRAPH_EINVAL); + igraph_matrix_destroy(&adjmatrix); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_adjacency.out b/tests/unit/igraph_adjacency.out new file mode 100644 index 0000000..d5a93d0 --- /dev/null +++ b/tests/unit/igraph_adjacency.out @@ -0,0 +1,468 @@ + +0x0 matrix: +directed: true +vcount: 0 +edges: { +} + +1x1 matrix, no loops: +directed: true +vcount: 1 +edges: { +} + +1x1 matrix, loops once: +directed: true +vcount: 1 +edges: { +0 0 +} + +1x1 matrix, loops twice (treated as loops once): +directed: true +vcount: 1 +edges: { +0 0 +} + +3x3 matrix, IGRAPH_ADJ_DIRECTED, no loops: +directed: true +vcount: 3 +edges: { +0 1 +0 1 +1 0 +1 0 +1 0 +1 2 +1 2 +1 2 +1 2 +2 1 +2 1 +2 1 +2 1 +2 1 +} + +3x3 matrix, IGRAPH_ADJ_DIRECTED, loops once: +directed: true +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 0 +1 0 +1 0 +1 2 +1 2 +1 2 +1 2 +2 1 +2 1 +2 1 +2 1 +2 1 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_DIRECTED, loops twice (treated as loops once): +directed: true +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 0 +1 0 +1 0 +1 2 +1 2 +1 2 +1 2 +2 1 +2 1 +2 1 +2 1 +2 1 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_UNDIRECTED, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_UNDIRECTED, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_UNDIRECTED, loops twice: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_MAX, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_MAX, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_MAX, loops twice: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_MIN, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_MIN, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_MIN, loops twice: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_PLUS, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_PLUS, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_PLUS, loops twice: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 1 +0 1 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_UPPER, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_UPPER, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_UPPER, loops twice (treated as loops once): +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_LOWER, no loops: +directed: false +vcount: 3 +edges: { +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +} + +3x3 matrix, IGRAPH_ADJ_LOWER, loops once: +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +3x3 matrix, IGRAPH_ADJ_LOWER, loops twice (treated as loops once): +directed: false +vcount: 3 +edges: { +0 0 +0 0 +0 0 +0 0 +0 1 +0 1 +0 1 +1 2 +1 2 +1 2 +1 2 +1 2 +2 2 +2 2 +2 2 +2 2 +2 2 +2 2 +} + +Check handling of non-square matrix error. + +Check handling of negative number of edges error. + +Check handling of odd number in diagonal. + +Check handling of invalid adjacency mode. + +Check handling of non-symmetric matrix for IGRAPH_ADJ_UNDIRECTED. diff --git a/tests/unit/igraph_adjacency_spectral_embedding.c b/tests/unit/igraph_adjacency_spectral_embedding.c new file mode 100644 index 0000000..7e75777 --- /dev/null +++ b/tests/unit/igraph_adjacency_spectral_embedding.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +/* + + R + library(igraph) + g <- graph.tree(10, 3, mode="out") + A <- get.adjacency(g) + svd(A + .5 * degree(g) * diag(vcount(g))) + +*/ + +int main(void) { + + igraph_t graph; + igraph_matrix_t U, V; + igraph_vector_t cvec; + + igraph_kary_tree(&graph, /*n=*/ 14, /*children=*/ 4, IGRAPH_TREE_OUT); + + igraph_matrix_init(&U, 0, 0); + igraph_matrix_init(&V, 0, 0); + + igraph_vector_init(&cvec, 0); + igraph_strength(&graph, &cvec, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, 0); + igraph_vector_scale(&cvec, .5); + + igraph_adjacency_spectral_embedding(&graph, 4, /*weights=*/ 0, + IGRAPH_EIGEN_LA, + /*scaled=*/ 0, &U, &V, /*D=*/ 0, + &cvec, /*options=*/ 0); + + /* eigenvectors are in the columns of U and V; make sure that the + * first row contains positive values */ + print_matrix_first_row_positive(&U, "%8.4f"); + printf("--\n"); + print_matrix_first_row_positive(&V, "%8.4f"); + + igraph_vector_destroy(&cvec); + igraph_matrix_destroy(&V); + igraph_matrix_destroy(&U); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_adjacency_spectral_embedding.out b/tests/unit/igraph_adjacency_spectral_embedding.out new file mode 100644 index 0000000..6248d05 --- /dev/null +++ b/tests/unit/igraph_adjacency_spectral_embedding.out @@ -0,0 +1,29 @@ + 0.5897 0.0000 0.7766 0.1946 + 0.5678 0.7037 -0.4090 -0.0547 + 0.5678 -0.7037 -0.4090 -0.0547 + 0.0541 0.0000 0.2133 -0.9350 + 0.0233 0.0000 0.0714 0.0576 + 0.0224 0.0348 -0.0376 -0.0162 + 0.0224 0.0348 -0.0376 -0.0162 + 0.0224 0.0348 -0.0376 -0.0162 + 0.0224 0.0348 -0.0376 -0.0162 + 0.0224 -0.0348 -0.0376 -0.0162 + 0.0224 -0.0348 -0.0376 -0.0162 + 0.0224 -0.0348 -0.0376 -0.0162 + 0.0224 -0.0348 -0.0376 -0.0162 + 0.0021 0.0000 0.0196 -0.2767 +-- + 0.3281 0.0000 0.6513 0.2795 + 0.5588 0.5468 -0.1031 0.0416 + 0.5588 -0.5468 -0.1031 0.0416 + 0.1791 0.0000 0.4151 -0.5316 + 0.1673 0.0000 0.3406 0.1604 + 0.1610 0.2241 -0.1794 -0.0451 + 0.1610 0.2241 -0.1794 -0.0451 + 0.1610 0.2241 -0.1794 -0.0451 + 0.1610 0.2241 -0.1794 -0.0451 + 0.1610 -0.2241 -0.1794 -0.0451 + 0.1610 -0.2241 -0.1794 -0.0451 + 0.1610 -0.2241 -0.1794 -0.0451 + 0.1610 -0.2241 -0.1794 -0.0451 + 0.0153 0.0000 0.0935 -0.7706 diff --git a/tests/unit/igraph_adjlist_init_complementer.c b/tests/unit/igraph_adjlist_init_complementer.c new file mode 100644 index 0000000..672177d --- /dev/null +++ b/tests/unit/igraph_adjlist_init_complementer.c @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_adjlist_t adjlist; + + printf("Graph with loops and multiple edges, IGRAPH_IN, loops = IGRAPH_NO_LOOPS:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_IN, IGRAPH_NO_LOOPS); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_IN, loops = IGRAPH_LOOPS_ONCE:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_IN, loops = IGRAPH_LOOPS_TWICE (ignored because directed):\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_IN, IGRAPH_LOOPS_TWICE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_OUT, loops = IGRAPH_NO_LOOPS:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_OUT, IGRAPH_NO_LOOPS); + igraph_adjlist_fprint(&adjlist, stdout); /* to check fprint too */ + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_OUT, loops = IGRAPH_LOOPS_ONCE:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_OUT, loops = IGRAPH_LOOPS_TWICE (ignored because directed):\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_ALL, loops = IGRAPH_NO_LOOPS:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_ALL, loops = IGRAPH_LOOPS_ONCE:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + printf("Same graph, IGRAPH_ALL, loops = IGRAPH_LOOPS_TWICE:\n"); + igraph_adjlist_init_complementer(&g, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + igraph_adjlist_print(&adjlist); + igraph_adjlist_destroy(&adjlist); + + igraph_destroy(&g); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_adjlist_init_complementer.out b/tests/unit/igraph_adjlist_init_complementer.out new file mode 100644 index 0000000..8a7758e --- /dev/null +++ b/tests/unit/igraph_adjlist_init_complementer.out @@ -0,0 +1,63 @@ +Graph with loops and multiple edges, IGRAPH_IN, loops = IGRAPH_NO_LOOPS: +1 3 4 5 +2 3 4 5 +1 3 4 5 +0 4 5 +0 1 2 5 +0 1 2 3 4 +Same graph, IGRAPH_IN, loops = IGRAPH_LOOPS_ONCE: +0 1 3 4 5 +2 3 4 5 +1 2 3 4 5 +0 3 4 5 +0 1 2 4 5 +0 1 2 3 4 5 +Same graph, IGRAPH_IN, loops = IGRAPH_LOOPS_TWICE (ignored because directed): +0 1 3 4 5 +2 3 4 5 +1 2 3 4 5 +0 3 4 5 +0 1 2 4 5 +0 1 2 3 4 5 +Same graph, IGRAPH_OUT, loops = IGRAPH_NO_LOOPS: +3 4 5 +0 2 4 5 +1 4 5 +0 1 2 5 +0 1 2 3 5 +0 1 2 3 4 +Same graph, IGRAPH_OUT, loops = IGRAPH_LOOPS_ONCE: +0 3 4 5 +0 2 4 5 +1 2 4 5 +0 1 2 3 5 +0 1 2 3 4 5 +0 1 2 3 4 5 +Same graph, IGRAPH_OUT, loops = IGRAPH_LOOPS_TWICE (ignored because directed): +0 3 4 5 +0 2 4 5 +1 2 4 5 +0 1 2 3 5 +0 1 2 3 4 5 +0 1 2 3 4 5 +Same graph, IGRAPH_ALL, loops = IGRAPH_NO_LOOPS: +3 4 5 +2 4 5 +1 4 5 +0 5 +0 1 2 5 +0 1 2 3 4 +Same graph, IGRAPH_ALL, loops = IGRAPH_LOOPS_ONCE: +0 3 4 5 +2 4 5 +1 2 4 5 +0 3 5 +0 1 2 4 5 +0 1 2 3 4 5 +Same graph, IGRAPH_ALL, loops = IGRAPH_LOOPS_TWICE: +0 0 3 4 5 +2 4 5 +1 2 2 4 5 +0 3 3 5 +0 1 2 4 4 5 +0 1 2 3 4 5 5 diff --git a/tests/unit/igraph_adjlist_simplify.c b/tests/unit/igraph_adjlist_simplify.c new file mode 100644 index 0000000..38267ba --- /dev/null +++ b/tests/unit/igraph_adjlist_simplify.c @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g) { + igraph_adjlist_t al; + igraph_adjlist_init(g, &al, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + printf("Before:\n"); + igraph_adjlist_print(&al); + igraph_adjlist_simplify(&al); + printf("After:\n"); + igraph_adjlist_print(&al); + igraph_destroy(g); + igraph_adjlist_destroy(&al); +} + +int main(void) { + igraph_t g; + + printf("Graph with no vertices:\n"); + igraph_small(&g, 0, 1, -1); + print_and_destroy(&g); + + printf("\nGraph with loops and multiple edges:\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_adjlist_simplify.out b/tests/unit/igraph_adjlist_simplify.out new file mode 100644 index 0000000..5148a2a --- /dev/null +++ b/tests/unit/igraph_adjlist_simplify.out @@ -0,0 +1,19 @@ +Graph with no vertices: +Before: +After: + +Graph with loops and multiple edges: +Before: +1 2 2 +0 1 1 3 +0 0 3 +1 2 4 4 +3 3 + +After: +1 2 +0 3 +0 3 +1 2 4 +3 + diff --git a/tests/unit/igraph_all_st_cuts.c b/tests/unit/igraph_all_st_cuts.c new file mode 100644 index 0000000..95a437b --- /dev/null +++ b/tests/unit/igraph_all_st_cuts.c @@ -0,0 +1,403 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "core/marked_queue.h" +#include "core/estack.h" + +#include "flow/flow_internal.h" + +#include "test_utilities.h" + +int test_all_st_cuts(const igraph_t *graph, + igraph_int_t source, + igraph_int_t target) { + igraph_vector_int_list_t cuts, partition1s; + igraph_int_t n, i; + + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(graph, &cuts, &partition1s, + source, target); + + n = igraph_vector_int_list_size(&partition1s); + printf("Partitions and cuts:\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&partition1s, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("P: "); + igraph_vector_int_print(v); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + return 0; +} + +int main(void) { + igraph_t g; + igraph_vector_int_list_t cuts, partition1s; + igraph_int_t i, n; + + igraph_marked_queue_int_t S; + igraph_estack_t T; + igraph_int_t v; + igraph_vector_int_t Isv; + + /* ----------------------------------------------------------- */ + /* This is the example from the Provan-Shier paper, + for calculating the dominator tree and finding the right pivot + element */ + + igraph_small(&g, 12, IGRAPH_DIRECTED, + /* a->b */ 0, 1, + /* b->t */ 1, 11, + /* c->b */ 2, 1, /* c->d */ 2, 3, + /* d->e */ 3, 4, /* d->i */ 3, 8, + /* e->c */ 4, 2, + /* f->c */ 5, 2, /* f->e */ 5, 4, + /* g->d */ 6, 3, /* g->e */ 6, 4, /* g->f */ 6, 5, + /* g->j */ 6, 9, + /* h->g */ 7, 6, /* h->t */ 7, 11, + /* i->a */ 8, 0, + /* j->i */ 9, 8, + /* s->a */ 10, 0, /* s->c */ 10, 2, /* s->h */ 10, 7, + -1); + + /* S={s,a} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + igraph_marked_queue_int_start_batch(&S); + igraph_marked_queue_int_push(&S, 10); + igraph_marked_queue_int_push(&S, 0); + + /* T={t} */ + igraph_estack_init(&T, igraph_vcount(&g), 1); + igraph_estack_push(&T, 11); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 10, /*target=*/ 11, + &v, &Isv, NULL); + + /* Expected result: v=c, Isv={c,d,e,i} */ + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + /* S={}, T={} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + igraph_estack_init(&T, igraph_vcount(&g), 3); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 0, /*target=*/ 2, + &v, &Isv, NULL); + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + /* S={}, T={0} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + + igraph_estack_init(&T, igraph_vcount(&g), 3); + igraph_estack_push(&T, 0); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 0, /*target=*/ 2, + &v, &Isv, NULL); + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + /* S={0}, T={} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + igraph_marked_queue_int_push(&S, 0); + + igraph_estack_init(&T, igraph_vcount(&g), 3); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 0, /*target=*/ 2, + &v, &Isv, NULL); + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + /* S={0}, T={1} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + igraph_marked_queue_int_push(&S, 0); + + igraph_estack_init(&T, igraph_vcount(&g), 3); + igraph_estack_push(&T, 1); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 0, /*target=*/ 2, + &v, &Isv, NULL); + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + /* S={0,1}, T={} */ + igraph_marked_queue_int_init(&S, igraph_vcount(&g)); + igraph_marked_queue_int_push(&S, 0); + igraph_marked_queue_int_push(&S, 1); + + igraph_estack_init(&T, igraph_vcount(&g), 3); + + igraph_vector_int_init(&Isv, 0); + igraph_i_all_st_cuts_pivot(&g, &S, &T, + /*source=*/ 0, /*target=*/ 2, + &v, &Isv, NULL); + printf("%" IGRAPH_PRId "; ", v); + igraph_vector_int_print(&Isv); + + igraph_vector_int_destroy(&Isv); + igraph_estack_destroy(&T); + igraph_marked_queue_int_destroy(&S); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 1, 1, 2, + -1); + + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, /*cuts=*/ 0, &partition1s, + /*source=*/ 0, /*target=*/ 2); + print_vector_int_list(&partition1s); + igraph_vector_int_list_destroy(&partition1s); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 1, 3, 2, 4, 3, 4, + -1); + + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, /*cuts=*/ 0, &partition1s, + /*source=*/ 0, /*target=*/ 4); + print_vector_int_list(&partition1s); + igraph_vector_int_list_destroy(&partition1s); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 1, 2, 1, 3, 2, 4, 3, 4, 1, 5, 5, 4, + -1); + + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, &cuts, &partition1s, + /*source=*/ 0, /*target=*/ 4); + + n = igraph_vector_int_list_size(&partition1s); + printf("Partitions and cuts:\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&partition1s, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("P: "); + igraph_vector_int_print(v); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 3, IGRAPH_DIRECTED, + 0, 2, 1, 2, + -1); + + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, &cuts, &partition1s, + /*source=*/ 1, /*target=*/ 2); + + n = igraph_vector_int_list_size(&partition1s); + printf("Partitions and cuts:\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&partition1s, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("P: "); + igraph_vector_int_print(v); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 3, 1, + -1); + + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, &cuts, &partition1s, + /*source=*/ 0, /*target=*/ 4); + + n = igraph_vector_int_list_size(&partition1s); + printf("Partitions and cuts:\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&partition1s, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("P: "); + igraph_vector_int_print(v); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 3, 2, 3, + 1, 4, 1, 5, 1, 6, + 4, 2, 5, 2, 6, 2, + -1); + + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, &cuts, &partition1s, + /*source=*/ 0, /*target=*/ 3); + + n = igraph_vector_int_list_size(&partition1s); + printf("Partitions and cuts:\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(&partition1s, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("P: "); + igraph_vector_int_print(v); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + /* Check whether it also works if we don't provide partition1s */ + igraph_vector_int_list_init(&cuts, 0); + igraph_vector_int_list_init(&partition1s, 0); + igraph_all_st_cuts(&g, &cuts, /*partition1s=*/ 0, + /*source=*/ 0, /*target=*/ 3); + + n = igraph_vector_int_list_size(&cuts); + printf("Cuts only (no partitions):\n"); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&cuts, i); + printf("C: "); + igraph_vector_int_print(v2); + } + igraph_vector_int_list_destroy(&partition1s); + igraph_vector_int_list_destroy(&cuts); + + igraph_destroy(&g); + + /* ----------------------------------------------------------- + * Check problematic cases in issue #1102 + * ----------------------------------------------------------- */ + + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, + -1); + test_all_st_cuts(&g, 0, 2); + igraph_destroy(&g); + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, + -1); + test_all_st_cuts(&g, 0, 2); + test_all_st_cuts(&g, 1, 3); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_all_st_cuts.out b/tests/unit/igraph_all_st_cuts.out new file mode 100644 index 0000000..ec7ea93 --- /dev/null +++ b/tests/unit/igraph_all_st_cuts.out @@ -0,0 +1,98 @@ +2; 2 3 4 8 +0; 0 +0; +1; 1 +1; +1; +{ + 0: ( 0 ) + 1: ( 0 1 ) +} +{ + 0: ( 0 ) + 1: ( 0 1 ) + 2: ( 0 1 3 ) + 3: ( 0 1 2 ) + 4: ( 0 1 2 3 ) +} +Partitions and cuts: +P: 0 +C: 0 +P: 0 1 +C: 1 2 5 +P: 0 1 5 +C: 1 2 6 +P: 0 1 3 +C: 1 4 5 +P: 0 1 3 5 +C: 1 4 6 +P: 0 1 2 +C: 2 3 5 +P: 0 1 2 5 +C: 2 3 6 +P: 0 1 2 3 +C: 3 4 5 +P: 0 1 2 3 5 +C: 3 4 6 +Partitions and cuts: +P: 1 +C: 1 +Partitions and cuts: +P: 0 +C: 0 +P: 0 1 +C: 1 +P: 0 1 2 +C: 2 +P: 0 1 2 3 +C: 3 +Partitions and cuts: +P: 0 +C: 0 1 +P: 0 2 +C: 0 3 +P: 0 1 +C: 1 2 4 5 6 +P: 0 1 6 +C: 1 2 4 5 9 +P: 0 1 5 +C: 1 2 4 6 8 +P: 0 1 5 6 +C: 1 2 4 8 9 +P: 0 1 4 +C: 1 2 5 6 7 +P: 0 1 4 6 +C: 1 2 5 7 9 +P: 0 1 4 5 +C: 1 2 6 7 8 +P: 0 1 4 5 6 +C: 1 2 7 8 9 +P: 0 1 4 5 6 2 +C: 2 3 +Cuts only (no partitions): +C: 0 1 +C: 0 3 +C: 1 2 4 5 6 +C: 1 2 4 5 9 +C: 1 2 4 6 8 +C: 1 2 4 8 9 +C: 1 2 5 6 7 +C: 1 2 5 7 9 +C: 1 2 6 7 8 +C: 1 2 7 8 9 +C: 2 3 +Partitions and cuts: +P: 0 +C: 0 +P: 0 1 +C: 1 +Partitions and cuts: +P: 0 +C: 0 +P: 0 1 +C: 1 +Partitions and cuts: +P: 1 +C: 1 +P: 1 2 +C: 2 diff --git a/tests/unit/igraph_all_st_mincuts.c b/tests/unit/igraph_all_st_mincuts.c new file mode 100644 index 0000000..6bc781e --- /dev/null +++ b/tests/unit/igraph_all_st_mincuts.c @@ -0,0 +1,191 @@ +/* + igraph library. + Copyright (C) 2023-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, + igraph_real_t value, + igraph_vector_int_list_t *partitions, + igraph_vector_int_list_t *cuts) { + igraph_int_t i, e, m, n = igraph_vector_int_list_size(partitions); + printf("Found %" IGRAPH_PRId " cuts, value: %g\n", n, value); + for (i = 0; i < n; i++) { + igraph_vector_int_t *vec = igraph_vector_int_list_get_ptr(partitions, i); + igraph_vector_int_t *vec2 = cuts ? igraph_vector_int_list_get_ptr(cuts, i) : 0; + printf("Partition %" IGRAPH_PRId ": ", i); + igraph_vector_int_print(vec); + if (vec2) { + printf("Cut %" IGRAPH_PRId ":\n", i); + m = igraph_vector_int_size(vec2); + for (e = 0; e < m; e++) { + igraph_int_t from = IGRAPH_FROM(g, VECTOR(*vec2)[e]), to = IGRAPH_TO(g, VECTOR(*vec2)[e]); + if (igraph_is_directed(g)) { + printf(" %" IGRAPH_PRId " -> %" IGRAPH_PRId "\n", from, to); + } else { + printf(" %" IGRAPH_PRId " -- %" IGRAPH_PRId "\n", from, to); + } + } + } + } + + igraph_vector_int_list_destroy(partitions); + if (cuts) { + igraph_vector_int_list_destroy(cuts); + } + printf("\n"); +} + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t partitions; + igraph_vector_int_list_t cuts; + igraph_real_t value; + + printf("Graph 1:\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 4, + /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 2:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 1, 3, 2, 4, 3, 4, 4, 5, -1); + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 5, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 3:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 1, 3, 2, 4, 3, 4, 4, 5, -1); + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 4, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 4:\n"); + igraph_small(&g, 9, IGRAPH_DIRECTED, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 4, 4, 2, 1, 5, 5, 2, 1, 6, 6, 2, 1, 7, 7, 2, 1, 8, 8, 2, + -1); + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 3, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 5:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 1, 0, 2, 0, 2, 1, 3, 2, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 2, /*target=*/ 0, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 6:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 1, 0, 2, 0, 2, 1, 2, 3, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 2, /*target=*/ 0, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 7:\n"); + igraph_small(&g, 9, IGRAPH_DIRECTED, + 0, 4, 0, 7, 1, 6, 2, 1, 3, 8, 4, 0, 4, 2, + 4, 5, 5, 0, 5, 3, 6, 7, 7, 8, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 0, /*target=*/ 8, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 8:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 2, 3, 0, 1, 2, 5, 1, 2, 4, 3, 5, 5, 4, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 3, /*target=*/ 4, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + /* ---------------------------------------------------------------- */ + + printf("Graph 9:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 2, 0, 3, 0, 4, 0, 0, 5, 3, 1, 1, 4, 3, 2, 2, 5, 4, 3, 5, 3, 5, 4, + -1); + + igraph_vector_int_list_init(&partitions, 0); + igraph_vector_int_list_init(&cuts, 0); + igraph_all_st_mincuts(&g, &value, &cuts, &partitions, + /*source=*/ 3, /*target=*/ 0, /*capacity=*/ NULL); + + print_and_destroy(&g, value, &partitions, &cuts); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_all_st_mincuts.out b/tests/unit/igraph_all_st_mincuts.out new file mode 100644 index 0000000..f563591 --- /dev/null +++ b/tests/unit/igraph_all_st_mincuts.out @@ -0,0 +1,132 @@ +Graph 1: +Found 4 cuts, value: 1 +Partition 0: 0 +Cut 0: + 0 -> 1 +Partition 1: 0 1 +Cut 1: + 1 -> 2 +Partition 2: 0 1 2 +Cut 2: + 2 -> 3 +Partition 3: 0 1 2 3 +Cut 3: + 3 -> 4 + +Graph 2: +Found 2 cuts, value: 1 +Partition 0: 0 +Cut 0: + 0 -> 1 +Partition 1: 0 4 3 2 1 +Cut 1: + 4 -> 5 + +Graph 3: +Found 1 cuts, value: 1 +Partition 0: 0 +Cut 0: + 0 -> 1 + +Graph 4: +Found 3 cuts, value: 2 +Partition 0: 0 +Cut 0: + 0 -> 1 + 0 -> 2 +Partition 1: 0 2 +Cut 1: + 0 -> 1 + 2 -> 3 +Partition 2: 0 2 1 8 7 6 5 4 +Cut 2: + 1 -> 3 + 2 -> 3 + +Graph 5: +Found 2 cuts, value: 2 +Partition 0: 2 +Cut 0: + 2 -> 0 + 2 -> 1 +Partition 1: 2 1 +Cut 1: + 1 -> 0 + 2 -> 0 + +Graph 6: +Found 2 cuts, value: 2 +Partition 0: 2 3 +Cut 0: + 2 -> 0 + 2 -> 1 +Partition 1: 2 3 1 +Cut 1: + 1 -> 0 + 2 -> 0 + +Graph 7: +Found 5 cuts, value: 2 +Partition 0: 0 +Cut 0: + 0 -> 4 + 0 -> 7 +Partition 1: 0 7 +Cut 1: + 0 -> 4 + 7 -> 8 +Partition 2: 0 7 4 2 1 6 +Cut 2: + 4 -> 5 + 7 -> 8 +Partition 3: 0 7 4 2 1 6 5 +Cut 3: + 5 -> 3 + 7 -> 8 +Partition 4: 0 7 4 2 1 6 5 3 +Cut 4: + 3 -> 8 + 7 -> 8 + +Graph 8: +Found 4 cuts, value: 2 +Partition 0: 3 +Cut 0: + 3 -> 0 + 3 -> 5 +Partition 1: 3 0 +Cut 1: + 0 -> 2 + 3 -> 5 +Partition 2: 3 0 2 +Cut 2: + 2 -> 4 + 3 -> 5 +Partition 3: 3 0 2 5 1 +Cut 3: + 2 -> 4 + 5 -> 4 + +Graph 9: +Found 4 cuts, value: 3 +Partition 0: 3 +Cut 0: + 3 -> 0 + 3 -> 1 + 3 -> 2 +Partition 1: 3 1 +Cut 1: + 3 -> 0 + 1 -> 4 + 3 -> 2 +Partition 2: 3 1 4 +Cut 2: + 3 -> 0 + 4 -> 0 + 3 -> 2 +Partition 3: 3 1 4 2 5 +Cut 3: + 2 -> 0 + 3 -> 0 + 4 -> 0 + diff --git a/tests/unit/igraph_almost_equals.c b/tests/unit/igraph_almost_equals.c new file mode 100644 index 0000000..114489e --- /dev/null +++ b/tests/unit/igraph_almost_equals.c @@ -0,0 +1,191 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* This file is ported from Java; the original source is here: + https://floating-point-gui.de/errors/NearlyEqualsTest.java */ + +const double EPS = 0.00001; + +void assert_almost_equal_with_eps(double a, double b, double eps, int line) { + if (!igraph_almost_equals(a, b, eps)) { + igraph_fatalf("Assertion failed: %g == %g with eps = %g", IGRAPH_FILE_BASENAME, line, a, b, eps); + } +} + +void assert_not_equal_with_eps(double a, double b, double eps, int line) { + if (igraph_almost_equals(a, b, eps)) { + igraph_fatalf("Assertion failed: %g != %g with eps = %g", IGRAPH_FILE_BASENAME, line, a, b, eps); + } +} + +#define ASSERT_ALMOST_EQUAL_WITH_EPS(a, b, eps) assert_almost_equal_with_eps(a, b, eps, __LINE__) +#define ASSERT_ALMOST_EQUAL(a, b) assert_almost_equal_with_eps(a, b, EPS, __LINE__) +#define ASSERT_NOT_EQUAL_WITH_EPS(a, b, eps) assert_not_equal_with_eps(a, b, eps, __LINE__) +#define ASSERT_NOT_EQUAL(a, b) assert_not_equal_with_eps(a, b, EPS, __LINE__) + +void test_large_numbers(void) { + ASSERT_ALMOST_EQUAL(1000000, 1000001); + ASSERT_ALMOST_EQUAL(1000001, 1000000); + ASSERT_NOT_EQUAL(10000, 10001); + ASSERT_NOT_EQUAL(10001, 10000); +} + +void test_large_negative_numbers(void) { + ASSERT_ALMOST_EQUAL(-1000000, -1000001); + ASSERT_ALMOST_EQUAL(-1000001, -1000000); + ASSERT_NOT_EQUAL(-10000, -10001); + ASSERT_NOT_EQUAL(-10001, -10000); +} + +void test_numbers_around_one(void) { + ASSERT_ALMOST_EQUAL(1.0000001, 1.0000002); + ASSERT_ALMOST_EQUAL(1.0000002, 1.0000001); + ASSERT_NOT_EQUAL(1.0002, 1.0001); + ASSERT_NOT_EQUAL(1.0001, 1.0002); +} + +void test_numbers_around_minus_one(void) { + ASSERT_ALMOST_EQUAL(-1.0000001, -1.0000002); + ASSERT_ALMOST_EQUAL(-1.0000002, -1.0000001); + ASSERT_NOT_EQUAL(-1.0002, -1.0001); + ASSERT_NOT_EQUAL(-1.0001, -1.0002); +} + +void test_small_numbers(void) { + ASSERT_ALMOST_EQUAL(0.000000001000001, 0.000000001000002); + ASSERT_ALMOST_EQUAL(0.000000001000002, 0.000000001000001); + ASSERT_NOT_EQUAL(0.000000000001002, 0.000000000001001); + ASSERT_NOT_EQUAL(0.000000000001001, 0.000000000001002); +} + +void test_small_negative_numbers(void) { + ASSERT_ALMOST_EQUAL(-0.000000001000001, -0.000000001000002); + ASSERT_ALMOST_EQUAL(-0.000000001000002, -0.000000001000001); + ASSERT_NOT_EQUAL(-0.000000000001002, -0.000000000001001); + ASSERT_NOT_EQUAL(-0.000000000001001, -0.000000000001002); +} + +void test_small_differences_away_from_zero(void) { + ASSERT_ALMOST_EQUAL(0.3, 0.30000003); + ASSERT_ALMOST_EQUAL(-0.3, -0.30000003); +} + +void test_comparisons_involving_zero(void) { + ASSERT_ALMOST_EQUAL(0, 0); + ASSERT_ALMOST_EQUAL(0.0, -0.0); + ASSERT_ALMOST_EQUAL(-0.0, -0.0); + ASSERT_NOT_EQUAL(0.00000001, 0.0); + ASSERT_NOT_EQUAL(0.0, 0.00000001); + ASSERT_NOT_EQUAL(-0.00000001, 0.0); + ASSERT_NOT_EQUAL(0.0, -0.00000001); + + /* original test contained 1e-40 here, which is a denormalized number in + * single-precision float world. An equivalent value for doubles is ~1e-320, + * see : https://docs.oracle.com/javase/8/docs/api/constant-values.html#java.lang.Double.MIN_VALUE + * The value must be between Double.MIN_VALUE and Double.MIN_NORMAL */ + ASSERT_ALMOST_EQUAL_WITH_EPS(0.0, 1e-320, 0.01); + ASSERT_ALMOST_EQUAL_WITH_EPS(1e-320, 0.0, 0.01); +} + +void test_extreme_values(void) { + ASSERT_ALMOST_EQUAL(DBL_MAX, DBL_MAX); + ASSERT_NOT_EQUAL(DBL_MAX, -DBL_MAX); + ASSERT_NOT_EQUAL(-DBL_MAX, DBL_MAX); + ASSERT_NOT_EQUAL(DBL_MAX, DBL_MAX / 2); + ASSERT_NOT_EQUAL(DBL_MAX, -DBL_MAX / 2); + ASSERT_NOT_EQUAL(-DBL_MAX, DBL_MAX / 2); +} + +void test_infinities(void) { + ASSERT_ALMOST_EQUAL(IGRAPH_INFINITY, IGRAPH_INFINITY); + ASSERT_ALMOST_EQUAL(-IGRAPH_INFINITY, -IGRAPH_INFINITY); + ASSERT_NOT_EQUAL(-IGRAPH_INFINITY, IGRAPH_INFINITY); + ASSERT_NOT_EQUAL(IGRAPH_INFINITY, DBL_MAX); + ASSERT_NOT_EQUAL(-IGRAPH_INFINITY, -DBL_MAX); +} + +void test_nans(void) { + ASSERT_NOT_EQUAL(IGRAPH_NAN, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, 0); + ASSERT_NOT_EQUAL(-0.0, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, -0.0); + ASSERT_NOT_EQUAL(IGRAPH_NAN, IGRAPH_INFINITY); + ASSERT_NOT_EQUAL(IGRAPH_INFINITY, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, -IGRAPH_INFINITY); + ASSERT_NOT_EQUAL(-IGRAPH_INFINITY, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, DBL_MAX); + ASSERT_NOT_EQUAL(DBL_MAX, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, -DBL_MAX); + ASSERT_NOT_EQUAL(-DBL_MAX, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, DBL_MIN); + ASSERT_NOT_EQUAL(DBL_MIN, IGRAPH_NAN); + ASSERT_NOT_EQUAL(IGRAPH_NAN, -DBL_MIN); + ASSERT_NOT_EQUAL(-DBL_MIN, IGRAPH_NAN); +} + +void test_opposite_sides_of_zero(void) { + ASSERT_NOT_EQUAL(1.000000001, -1); + ASSERT_NOT_EQUAL(-1, 1.000000001); + ASSERT_NOT_EQUAL(-1.000000001, 1); + ASSERT_NOT_EQUAL(1, -1.000000001); + + /* These tests from NearlyEqualsTest.java involved denormalized numbers in + * Java world, and they were defined as floats. We use doubles, so I converted + * the values manually by looking up Double.MIN_VALUE */ + ASSERT_ALMOST_EQUAL(49e-324, -49e-324); +} + +void test_very_close_to_zero(void) { +#define DBL_DENORM_MIN 4.9e-324 + ASSERT_ALMOST_EQUAL(DBL_DENORM_MIN, DBL_DENORM_MIN); + ASSERT_ALMOST_EQUAL(DBL_DENORM_MIN, -DBL_DENORM_MIN); + ASSERT_ALMOST_EQUAL(-DBL_DENORM_MIN, DBL_DENORM_MIN); + ASSERT_ALMOST_EQUAL(DBL_DENORM_MIN, 0); + ASSERT_ALMOST_EQUAL(0, DBL_DENORM_MIN); + ASSERT_ALMOST_EQUAL(-DBL_DENORM_MIN, 0); + ASSERT_ALMOST_EQUAL(0, -DBL_DENORM_MIN); + + ASSERT_NOT_EQUAL(0.000000001, -DBL_DENORM_MIN); + ASSERT_NOT_EQUAL(0.000000001, DBL_DENORM_MIN); + ASSERT_NOT_EQUAL(DBL_DENORM_MIN, 0.000000001); + ASSERT_NOT_EQUAL(-DBL_DENORM_MIN, 0.000000001); +} + +int main(void) { + test_large_numbers(); + test_large_negative_numbers(); + test_numbers_around_one(); + test_numbers_around_minus_one(); + test_small_numbers(); + test_small_negative_numbers(); + test_small_differences_away_from_zero(); + test_comparisons_involving_zero(); + test_extreme_values(); + test_infinities(); + test_nans(); + test_opposite_sides_of_zero(); + test_very_close_to_zero(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_are_adjacent.c b/tests/unit/igraph_are_adjacent.c new file mode 100644 index 0000000..2049bf7 --- /dev/null +++ b/tests/unit/igraph_are_adjacent.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2011-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t connected; + + /* Complete graph. Any two distinct vertices are connected. */ + + igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + igraph_are_adjacent(&g, 2, 7, &connected); + IGRAPH_ASSERT(connected); + + igraph_are_adjacent(&g, 0, 0, &connected); + IGRAPH_ASSERT(! connected); + + igraph_destroy(&g); + + /* Complete graph with self-loops. */ + + igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + igraph_are_adjacent(&g, 1, 7, &connected); + IGRAPH_ASSERT(connected); + + igraph_are_adjacent(&g, 2, 2, &connected); + IGRAPH_ASSERT(connected); + + igraph_destroy(&g); + + /* Graph with no edges. Any two distinct vertices are disconnected. */ + + igraph_empty(&g, 10, IGRAPH_DIRECTED); + igraph_are_adjacent(&g, 3, 6, &connected); + IGRAPH_ASSERT(! connected); + igraph_destroy(&g); + + /* Invalid vertex ID, expecting an error */ + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 2,3, -1); + igraph_set_error_handler(igraph_error_handler_ignore); + IGRAPH_ASSERT(igraph_are_adjacent(&g, 0, igraph_vcount(&g) + 2 /* vertex ID out of range */, &connected) == IGRAPH_EINVVID); + igraph_set_error_handler(igraph_error_handler_abort); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_arpack_rnsolve.c b/tests/unit/igraph_arpack_rnsolve.c new file mode 100644 index 0000000..92cd5a4 --- /dev/null +++ b/tests/unit/igraph_arpack_rnsolve.c @@ -0,0 +1,232 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +typedef struct cb2_data_t { + igraph_matrix_t *A; +} cb2_data_t; + +igraph_error_t cb2(igraph_real_t *to, const igraph_real_t *from, int n, void *extra) { + IGRAPH_UNUSED(n); + cb2_data_t *data = (cb2_data_t*) extra; + igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, + data->A, from, /*beta=*/ 0.0, to); + return IGRAPH_SUCCESS; +} + +int check_eigenvector( + const char* test_name, + igraph_matrix_t* A, igraph_matrix_t* values, igraph_matrix_t* vectors, + int eval_idx, int evec_col_idx +) { + igraph_complex_t eval, prod; + igraph_complex_t *evec; + int i, j, n = igraph_matrix_nrow(A); + + eval = igraph_complex(MATRIX(*values, eval_idx, 0), MATRIX(*values, eval_idx, 1)); + evec = (igraph_complex_t*) calloc(n, sizeof(igraph_complex_t)); + if (IGRAPH_IMAG(eval) == 0) { + /* Real eigenvalue, so we have a real eigenvector */ + for (i = 0; i < n; i++) { + evec[i] = igraph_complex(MATRIX(*vectors, i, evec_col_idx), 0); + } + } else { + /* Complex eigenvalue pair, so we have a complex eigenvector pair */ + /* ARPACK always stores the eigenvector corresponding to the eigenvalue + * with a positive imaginary part. If the imaginary part is negative, we + * need to multiply the imaginary part of the eigenvector by -1 */ + for (i = 0; i < n; i++) { + evec[i] = igraph_complex( + MATRIX(*vectors, i, evec_col_idx), + MATRIX(*vectors, i, evec_col_idx + 1) * ( + IGRAPH_IMAG(eval) < 0 ? -1 : 1 + ) + ); + } + } + + /* Multiply matrix with eigenvector */ + for (i = 0; i < n; i++) { + prod = igraph_complex(0, 0); + for (j = 0; j < n; j++) { + prod = igraph_complex_add( + igraph_complex_mul_real(evec[j], MATRIX(*A, i, j)), + prod + ); + } + prod = igraph_complex_div(prod, eval); + if (!igraph_complex_almost_equals(prod, evec[i], 1e-12)) { + prod = igraph_complex_sub(prod, evec[i]); + printf("%s: vector corresponding to eigenvalue (%.4f + %.4f*i) is not an " + "eigenvector, coordinate %d differs by %.4f + %.4f*i\n", + test_name, IGRAPH_REAL(eval), IGRAPH_IMAG(eval), + i, IGRAPH_REAL(prod), IGRAPH_IMAG(prod)); + + free(evec); + return 1; + } + } + + /* Free stuff */ + free(evec); + + return 0; +} + +int check_eigenvectors( + const char* test_name, + igraph_matrix_t* A, igraph_matrix_t* values, igraph_matrix_t* vectors +) { + int i, j; + int nev = igraph_matrix_nrow(values); + int errors = 0; + igraph_bool_t conjugate_pair_will_come = 0; + + for (i = 0, j = 0; i < nev; i++) { + errors += check_eigenvector(test_name, A, values, vectors, i, j); + if (MATRIX(*values, i, 1) != 0) { + /* Complex eigenvalue */ + if (conjugate_pair_will_come) { + j += 2; + conjugate_pair_will_come = 0; + } else { + conjugate_pair_will_come = 1; + } + } else { + /* Real eigenvalue */ + j++; + } + } + return (errors > 0) ? 1 : 0; +} + +#define DIM 10 + +int main(void) { + igraph_matrix_t A; + igraph_matrix_t values, vectors; + igraph_arpack_options_t options; + cb2_data_t data = { &A }; + int i, j; + + /* Note: igraph_arpack_rnsolve() uses the RNG to generate a random + * starting vector for ARPACK. */ + igraph_rng_seed(igraph_rng_default(), 42 * 42); + + igraph_matrix_init(&A, DIM, DIM); + + for (i = 0; i < DIM; i++) { + for (j = 0; j < DIM; j++) { + MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), -12, 12); + } + } + + printf("Input matrix:\n"); + igraph_matrix_print(&A); + printf("\n"); + + printf("\n4 largest eigenvalues by magnitude:\n"); + + igraph_arpack_options_init(&options); + options.n = DIM; + options.start = 0; + options.nev = 4; + options.ncv = 9; + options.which[0] = 'L' ; + options.which[1] = 'M'; + + igraph_matrix_init(&values, 0, 0); + igraph_matrix_init(&vectors, options.n, 1); + + igraph_arpack_rnsolve(cb2, /*extra=*/ &data, &options, /*storage=*/ 0, + &values, &vectors); + printf("\nEigenvalues:\n"); + igraph_matrix_print(&values); + if (check_eigenvectors("LM #1", &A, &values, &vectors)) { + printf("\nEigenvectors:\n"); + igraph_matrix_print(&vectors); + } + + /* -------------- */ + + printf("\n3 largest eigenvalues by magnitude:\n"); + + options.nev = 3; + options.which[0] = 'L' ; + options.which[1] = 'M'; + + igraph_arpack_rnsolve(cb2, /*extra=*/ &data, &options, /*storage=*/ 0, + &values, &vectors); + printf("\nEigenvalues:\n"); + igraph_matrix_print(&values); + if (check_eigenvectors("LM #2", &A, &values, &vectors)) { + printf("\nEigenvectors:\n"); + igraph_matrix_print(&vectors); + } + + /* -------------- */ + + printf("\n3 smallest eigenvalues by real part:\n"); + + options.nev = 3; + options.which[0] = 'S' ; + options.which[1] = 'R'; + + igraph_arpack_rnsolve(cb2, /*extra=*/ &data, &options, /*storage=*/ 0, + &values, &vectors); + printf("\nEigenvalues:\n"); + igraph_matrix_print(&values); + if (check_eigenvectors("SR", &A, &values, &vectors)) { + printf("\nEigenvectors:\n"); + igraph_matrix_print(&vectors); + } + + /* -------------- */ + + printf("\n3 smallest eigenvalues by imaginary part:\n"); + + options.nev = 3; + options.which[0] = 'L' ; + options.which[1] = 'I'; + + igraph_arpack_rnsolve(cb2, /*extra=*/ &data, &options, /*storage=*/ 0, + &values, &vectors); + printf("\nEigenvalues:\n"); + igraph_matrix_print(&values); + if (check_eigenvectors("LI", &A, &values, &vectors)) { + printf("\nEigenvectors:\n"); + igraph_matrix_print(&vectors); + } + + /* -------------- */ + + igraph_matrix_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_arpack_rnsolve.out b/tests/unit/igraph_arpack_rnsolve.out new file mode 100644 index 0000000..b97de59 --- /dev/null +++ b/tests/unit/igraph_arpack_rnsolve.out @@ -0,0 +1,41 @@ +Input matrix: + 3 -6 -4 -3 5 -1 -5 1 -3 0 + 7 -12 11 5 3 -4 -10 -12 -9 1 + 0 -4 9 10 7 -7 -10 -10 10 3 +-1 4 6 -7 -2 -7 8 12 9 -3 +-7 -7 -9 12 11 -6 -7 -4 -8 5 +-6 9 -2 -5 1 7 -1 12 -10 -5 + 6 -2 7 3 -8 -10 3 -4 7 8 +12 -8 7 -4 -8 9 4 12 -3 9 + 2 -10 9 -7 -12 7 5 -11 4 8 +-7 1 -8 3 3 -4 -1 8 -1 -9 + + +4 largest eigenvalues by magnitude: + +Eigenvalues: + 24.0492 0 +-23.9674 0 + 13.8157 10.1107 + 13.8157 -10.1107 + +3 largest eigenvalues by magnitude: + +Eigenvalues: + 24.0492 0 +-23.9674 0 + 13.8157 10.1107 + +3 smallest eigenvalues by real part: + +Eigenvalues: +-23.9674 0 +-11.1679 0 + -7.1 3.73906 + +3 smallest eigenvalues by imaginary part: + +Eigenvalues: +4.04984 15.1474 +4.04984 -15.1474 +13.8157 10.1107 diff --git a/tests/unit/igraph_arpack_unpack_complex.c b/tests/unit/igraph_arpack_unpack_complex.c new file mode 100644 index 0000000..098b09e --- /dev/null +++ b/tests/unit/igraph_arpack_unpack_complex.c @@ -0,0 +1,103 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_matrix_t *vectors, igraph_matrix_t *values, igraph_int_t nev, igraph_error_t error) { + printf("vectors in:\n"); + print_matrix_format(vectors, stdout, "%6.2f"); + printf("values in:\n"); + print_matrix_format(values, stdout, "%6.2f"); + IGRAPH_ASSERT(igraph_arpack_unpack_complex(vectors, values, nev) == error); + printf("vectors out:\n"); + print_matrix_format(vectors, stdout, "%6.2f"); + printf("values out:\n"); + print_matrix_format(values, stdout, "%6.2f"); + igraph_matrix_destroy(vectors); + igraph_matrix_destroy(values); + printf("\n"); +} + +int main(void) { + igraph_matrix_t vectors; + igraph_matrix_t values; + + printf("Empty vectors and values:\n"); + matrix_init_int_row_major(&vectors, 0, 0, NULL); + matrix_init_int_row_major(&values, 0, 0, NULL); + print_and_destroy(&vectors, &values, 0, IGRAPH_SUCCESS); + + { + printf("Real vectors and values:\n"); + int vectors_elem[4] = {-1, 0, 9, 10}; + int values_elem[4] = {-6, 0, 3, 0}; + matrix_init_int_row_major(&vectors, 2, 2, vectors_elem); + matrix_init_int_row_major(&values, 2, 2, values_elem); + print_and_destroy(&vectors, &values, 2, IGRAPH_SUCCESS); + } + { + printf("Complex vectors and values:\n"); + igraph_real_t vectors_elem[36] = { + 0.123938, 0.3411, 0.114301, -0.134822, -0.421672, -0.484969, + -0.268889, 0.00766665, 0.413844, 0.200565, -0.0336028, -0.133362, + -0.192782, -0.140134, 0.579782, -0.0853149, -0.0684855, 0.117105, + 0.175547, 0.1833, 0.156218, 0.0623488, 0.422265, -0.257261, + -0.266691, 0.404647, -0.462498, -0.0885737, 0.203893, -0.135195, + 0.662813, -0.022972, -0.193704, 0.355354, -0.0405741, 0.493652}; + igraph_real_t values_elem[12] = { + -2.58338, 9.66092, -2.58338, -9.66092, 7.07998, 6.51033, + 7.07998, -6.51033, -7.9966, 2.74368, -7.9966, -2.74368}; + matrix_init_real_row_major(&vectors, 6, 6, vectors_elem); + matrix_init_real_row_major(&values, 6, 2, values_elem); + print_and_destroy(&vectors, &values, 6, IGRAPH_SUCCESS); + } + { + printf("Both complex and real vectors and values:\n"); + int vectors_elem[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + int values_elem[8] = {1, 0, 2, 1, 2, -1, 3, 0}; + matrix_init_int_row_major(&vectors, 4, 4, vectors_elem); + matrix_init_int_row_major(&values, 4, 2, values_elem); + print_and_destroy(&vectors, &values, 4, IGRAPH_SUCCESS); + } + { + printf("Both complex and real vectors and values, but nev = 2:\n"); + int vectors_elem[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + int values_elem[8] = {1, 0, 2, 1, 2, -1, 3, 0}; + matrix_init_int_row_major(&vectors, 4, 4, vectors_elem); + matrix_init_int_row_major(&values, 4, 2, values_elem); + print_and_destroy(&vectors, &values, 2, IGRAPH_SUCCESS); + } + { + printf("No vectors but there are values:\n"); + int values_elem[8] = {1, 0, 2, 1, 2, -1, 3, 0}; + matrix_init_int_row_major(&vectors, 0, 0, NULL); + matrix_init_int_row_major(&values, 4, 2, values_elem); + print_and_destroy(&vectors, &values, 4, IGRAPH_SUCCESS); + } + { + printf("No values, but there are vectors:\n"); + int vectors_elem[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + matrix_init_int_row_major(&vectors, 4, 4, vectors_elem); + matrix_init_int_row_major(&values, 0, 0, NULL); + print_and_destroy(&vectors, &values, 0, IGRAPH_SUCCESS); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_arpack_unpack_complex.out b/tests/unit/igraph_arpack_unpack_complex.out new file mode 100644 index 0000000..eff24c7 --- /dev/null +++ b/tests/unit/igraph_arpack_unpack_complex.out @@ -0,0 +1,125 @@ +Empty vectors and values: +vectors in: +[ 0-by-0 ] +values in: +[ 0-by-0 ] +vectors out: +[ 0-by-0 ] +values out: +[ 0-by-0 ] + +Real vectors and values: +vectors in: +[ -1.00 0.00 + 9.00 10.00 ] +values in: +[ -6.00 0.00 + 3.00 0.00 ] +vectors out: +[ -1.00 0.00 0.00 0.00 + 9.00 0.00 10.00 0.00 ] +values out: +[ -6.00 0.00 + 3.00 0.00 ] + +Complex vectors and values: +vectors in: +[ 0.12 0.34 0.11 -0.13 -0.42 -0.48 + -0.27 0.01 0.41 0.20 -0.03 -0.13 + -0.19 -0.14 0.58 -0.09 -0.07 0.12 + 0.18 0.18 0.16 0.06 0.42 -0.26 + -0.27 0.40 -0.46 -0.09 0.20 -0.14 + 0.66 -0.02 -0.19 0.36 -0.04 0.49 ] +values in: +[ -2.58 9.66 + -2.58 -9.66 + 7.08 6.51 + 7.08 -6.51 + -8.00 2.74 + -8.00 -2.74 ] +vectors out: +[ 0.12 0.34 0.12 -0.34 0.11 -0.13 0.11 0.13 -0.42 -0.48 -0.42 0.48 + -0.27 0.01 -0.27 -0.01 0.41 0.20 0.41 -0.20 -0.03 -0.13 -0.03 0.13 + -0.19 -0.14 -0.19 0.14 0.58 -0.09 0.58 0.09 -0.07 0.12 -0.07 -0.12 + 0.18 0.18 0.18 -0.18 0.16 0.06 0.16 -0.06 0.42 -0.26 0.42 0.26 + -0.27 0.40 -0.27 -0.40 -0.46 -0.09 -0.46 0.09 0.20 -0.14 0.20 0.14 + 0.66 -0.02 0.66 0.02 -0.19 0.36 -0.19 -0.36 -0.04 0.49 -0.04 -0.49 ] +values out: +[ -2.58 9.66 + -2.58 -9.66 + 7.08 6.51 + 7.08 -6.51 + -8.00 2.74 + -8.00 -2.74 ] + +Both complex and real vectors and values: +vectors in: +[ 1.00 2.00 3.00 4.00 + 5.00 6.00 7.00 8.00 + 9.00 10.00 11.00 12.00 + 13.00 14.00 15.00 16.00 ] +values in: +[ 1.00 0.00 + 2.00 1.00 + 2.00 -1.00 + 3.00 0.00 ] +vectors out: +[ 1.00 0.00 2.00 3.00 2.00 -3.00 4.00 0.00 + 5.00 0.00 6.00 7.00 6.00 -7.00 8.00 0.00 + 9.00 0.00 10.00 11.00 10.00 -11.00 12.00 0.00 + 13.00 0.00 14.00 15.00 14.00 -15.00 16.00 0.00 ] +values out: +[ 1.00 0.00 + 2.00 1.00 + 2.00 -1.00 + 3.00 0.00 ] + +Both complex and real vectors and values, but nev = 2: +vectors in: +[ 1.00 2.00 3.00 4.00 + 5.00 6.00 7.00 8.00 + 9.00 10.00 11.00 12.00 + 13.00 14.00 15.00 16.00 ] +values in: +[ 1.00 0.00 + 2.00 1.00 + 2.00 -1.00 + 3.00 0.00 ] +vectors out: +[ 1.00 0.00 2.00 3.00 + 5.00 0.00 6.00 7.00 + 9.00 0.00 10.00 11.00 + 13.00 0.00 14.00 15.00 ] +values out: +[ 1.00 0.00 + 2.00 1.00 ] + +No vectors but there are values: +vectors in: +[ 0-by-0 ] +values in: +[ 1.00 0.00 + 2.00 1.00 + 2.00 -1.00 + 3.00 0.00 ] +vectors out: +[ 0-by-8 ] +values out: +[ 1.00 0.00 + 2.00 1.00 + 2.00 -1.00 + 3.00 0.00 ] + +No values, but there are vectors: +vectors in: +[ 1.00 2.00 3.00 4.00 + 5.00 6.00 7.00 8.00 + 9.00 10.00 11.00 12.00 + 13.00 14.00 15.00 16.00 ] +values in: +[ 0-by-0 ] +vectors out: +[ 4-by-0 ] +values out: +[ 0-by-0 ] + diff --git a/tests/unit/igraph_atlas.c b/tests/unit/igraph_atlas.c new file mode 100644 index 0000000..6aebb96 --- /dev/null +++ b/tests/unit/igraph_atlas.c @@ -0,0 +1,30 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + CHECK_ERROR(igraph_atlas(&g, -1), IGRAPH_EINVAL); + CHECK_ERROR(igraph_atlas(&g, 1253), IGRAPH_EINVAL); + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_attribute_combination_remove.c b/tests/unit/igraph_attribute_combination_remove.c new file mode 100644 index 0000000..614688d --- /dev/null +++ b/tests/unit/igraph_attribute_combination_remove.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_comb(igraph_attribute_combination_t *comb) { + int i; + igraph_attribute_combination_record_t *r; + for (i = 0; i < igraph_vector_ptr_size(&comb->list); i++) { + r = VECTOR(comb->list)[i]; + if (r->name) { + printf("name: %s", r->name); + } else { + printf("name: NULL"); + } + printf(", type: %d\n", r->type); + } + printf("\n"); +} + +int main(void) { + igraph_attribute_combination_t comb; + + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + "type", IGRAPH_ATTRIBUTE_COMBINE_FIRST, + "", IGRAPH_ATTRIBUTE_COMBINE_IGNORE, + IGRAPH_NO_MORE_ATTRIBUTES); + + printf("starting combinations:\n"); + print_comb(&comb); + + printf("Removing nonexistent combination:\n"); + igraph_attribute_combination_remove(&comb, "nonexistent_name"); + print_comb(&comb); + + printf("Removing weight:\n"); + igraph_attribute_combination_remove(&comb, "weight"); + print_comb(&comb); + + printf("Removing type and NULL:\n"); + igraph_attribute_combination_remove(&comb, "type"); + igraph_attribute_combination_remove(&comb, NULL); + print_comb(&comb); + + printf("Removing nonexistent combination again:\n"); + igraph_attribute_combination_remove(&comb, "nonexistent_name"); + igraph_attribute_combination_remove(&comb, NULL); + print_comb(&comb); + + igraph_attribute_combination_destroy(&comb); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_attribute_combination_remove.out b/tests/unit/igraph_attribute_combination_remove.out new file mode 100644 index 0000000..1e8397c --- /dev/null +++ b/tests/unit/igraph_attribute_combination_remove.out @@ -0,0 +1,18 @@ +starting combinations: +name: weight, type: 3 +name: type, type: 8 +name: NULL, type: 0 + +Removing nonexistent combination: +name: weight, type: 3 +name: type, type: 8 +name: NULL, type: 0 + +Removing weight: +name: type, type: 8 +name: NULL, type: 0 + +Removing type and NULL: + +Removing nonexistent combination again: + diff --git a/tests/unit/igraph_average_path_length.c b/tests/unit/igraph_average_path_length.c new file mode 100644 index 0000000..e77dd43 --- /dev/null +++ b/tests/unit/igraph_average_path_length.c @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void print_and_destroy(igraph_t *graph, igraph_bool_t unconn) { + igraph_real_t res, unconn_pairs; + + igraph_average_path_length(graph, NULL, &res, &unconn_pairs, IGRAPH_DIRECTED, unconn); + printf("Average shortest path length: "); + print_real(stdout, res, "%g"); + printf("\nNo. of unconnected pairs: %g\n", unconn_pairs); + + igraph_destroy(graph); +} + +int main(void) { + igraph_t graph; + + printf("Null graph:\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + print_and_destroy(&graph, 1); + + printf("\nSingleton graph:\n"); + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + print_and_destroy(&graph, 1); + + printf("\n2-vertex empty graph, unconn=true:\n"); + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + print_and_destroy(&graph, 1); + + printf("\n2-vertex empty graph, unconn=false:\n"); + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + print_and_destroy(&graph, 0); + + printf("\nSmallest bifurcating directed tree, unconn=true:\n"); + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, 0,2, -1); + print_and_destroy(&graph, 1); + + printf("\nSmallest bifurcating directed tree, unconn=false:\n"); + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, 0,2, -1); + print_and_destroy(&graph, 1); + + printf("\nUndirected 11-cycle:\n"); + igraph_ring(&graph, 11, IGRAPH_UNDIRECTED, 0, 1); + print_and_destroy(&graph, 1); + + printf("\nDirected 6-cycle:\n"); + igraph_ring(&graph, 11, IGRAPH_DIRECTED, 0, 1); + print_and_destroy(&graph, 1); + + /* Result verified by szhorvat on 2021-03-04. */ + printf("\nDe Bruijn graph, n=3 and m=5:\n"); + igraph_de_bruijn(&graph, 3, 5); + print_and_destroy(&graph, 1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_average_path_length.out b/tests/unit/igraph_average_path_length.out new file mode 100644 index 0000000..892148b --- /dev/null +++ b/tests/unit/igraph_average_path_length.out @@ -0,0 +1,35 @@ +Null graph: +Average shortest path length: NaN +No. of unconnected pairs: 0 + +Singleton graph: +Average shortest path length: NaN +No. of unconnected pairs: 0 + +2-vertex empty graph, unconn=true: +Average shortest path length: NaN +No. of unconnected pairs: 2 + +2-vertex empty graph, unconn=false: +Average shortest path length: Inf +No. of unconnected pairs: 2 + +Smallest bifurcating directed tree, unconn=true: +Average shortest path length: 1 +No. of unconnected pairs: 4 + +Smallest bifurcating directed tree, unconn=false: +Average shortest path length: 1 +No. of unconnected pairs: 4 + +Undirected 11-cycle: +Average shortest path length: 3 +No. of unconnected pairs: 0 + +Directed 6-cycle: +Average shortest path length: 5.5 +No. of unconnected pairs: 0 + +De Bruijn graph, n=3 and m=5: +Average shortest path length: 4.34405 +No. of unconnected pairs: 0 diff --git a/tests/unit/igraph_average_path_length_dijkstra.c b/tests/unit/igraph_average_path_length_dijkstra.c new file mode 100644 index 0000000..e6b7b76 --- /dev/null +++ b/tests/unit/igraph_average_path_length_dijkstra.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void compute_and_print(igraph_t *graph, igraph_vector_t *weights, igraph_bool_t directed, igraph_bool_t unconn) { + igraph_real_t result; + igraph_real_t unconn_pairs; + + IGRAPH_ASSERT(igraph_average_path_length(graph, weights, &result, &unconn_pairs, + directed, unconn) == IGRAPH_SUCCESS); + + printf("Result: "); + print_real(stdout, result, "%8g"); + printf("\nUnconnected pairs: "); + print_real(stdout, unconn_pairs, "%8g"); + printf("\n\n"); +} + +int main(void) { + igraph_t g_0, g_1, g_2, g_3, g_lm; + igraph_vector_t weights_0, weights_3, weights_lm, weights_lm_neg; + igraph_real_t result; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_2, 2, 0, -1); + igraph_small(&g_3, 2, 1, 0,1, 0,2, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + igraph_vector_init(&weights_0, 0); + igraph_vector_init_int(&weights_3, 2, 1, 1); + igraph_vector_init_int(&weights_lm, 8, 0, 1, 2, 3, 4, 5, 6, 7); + igraph_vector_init_int(&weights_lm_neg, 8, -10, 1, 2, 3, 4, 5, 6, 7); + + printf("No vertices:\n"); + compute_and_print(&g_0, &weights_0, 1, 1); + + printf("One vertex:\n"); + compute_and_print(&g_1, &weights_0, 1, 1); + + printf("Two vertices:\n"); + compute_and_print(&g_2, &weights_0, 1, 1); + + printf("Two vertices, inf for unconnected pairs:\n"); + compute_and_print(&g_2, &weights_0, 1, 0); + + printf("Smallest bifurcating directed tree:\n"); + compute_and_print(&g_3, &weights_3, 1, 1); + + printf("Smallest bifurcating directed tree, inf for unconnected pairs:\n"); + compute_and_print(&g_3, &weights_3, 1, 0); + + printf("Graph with loops and multiple edges:\n"); + compute_and_print(&g_lm, &weights_lm, 1, 1); + + printf("Graph with loops and multiple edges, ignoring direction:\n"); + compute_and_print(&g_lm, &weights_lm, 0, 1); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking incorrect weight length error handling.\n"); + IGRAPH_ASSERT(igraph_average_path_length(&g_lm, &weights_0, &result, NULL, + 1, 1) == IGRAPH_EINVAL); + + printf("Checking negative weight error handling.\n"); + IGRAPH_ASSERT(igraph_average_path_length(&g_lm, &weights_lm_neg, &result, NULL, + 1, 1) == IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_2); + igraph_destroy(&g_3); + igraph_destroy(&g_lm); + igraph_vector_destroy(&weights_0); + igraph_vector_destroy(&weights_3); + igraph_vector_destroy(&weights_lm); + igraph_vector_destroy(&weights_lm_neg); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_average_path_length_dijkstra.out b/tests/unit/igraph_average_path_length_dijkstra.out new file mode 100644 index 0000000..53d5efd --- /dev/null +++ b/tests/unit/igraph_average_path_length_dijkstra.out @@ -0,0 +1,34 @@ +No vertices: +Result: NaN +Unconnected pairs: 0 + +One vertex: +Result: NaN +Unconnected pairs: 0 + +Two vertices: +Result: NaN +Unconnected pairs: 2 + +Two vertices, inf for unconnected pairs: +Result: Inf +Unconnected pairs: 2 + +Smallest bifurcating directed tree: +Result: 1 +Unconnected pairs: 4 + +Smallest bifurcating directed tree, inf for unconnected pairs: +Result: Inf +Unconnected pairs: 4 + +Graph with loops and multiple edges: +Result: 5 +Unconnected pairs: 19 + +Graph with loops and multiple edges, ignoring direction: +Result: 4.6 +Unconnected pairs: 10 + +Checking incorrect weight length error handling. +Checking negative weight error handling. diff --git a/tests/unit/igraph_barabasi_aging_game.c b/tests/unit/igraph_barabasi_aging_game.c new file mode 100644 index 0000000..f6643e6 --- /dev/null +++ b/tests/unit/igraph_barabasi_aging_game.c @@ -0,0 +1,146 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t outseq; + igraph_rng_seed(igraph_rng_default(), 42); + igraph_bool_t tree; + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 0, /*m: edges_per_step*/ 1, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1, /*aging_exp*/ 1, /*aging_bin*/ 1, + /*zero_deg_appeal*/ 0, /*zero_age_appeal*/ 0, /*deg_coef*/ 1.0, + /*age_coef */ 1.0, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph(&g); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + printf("No edges:\n"); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 0, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1, /*aging_exp*/ 1, /*aging_bin*/ 1, + /*zero_deg_appeal*/ 0, /*zero_age_appeal*/ 0, /*deg_coef*/ 1.0, + /*age_coef */ 1.0, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph(&g); + IGRAPH_ASSERT(igraph_vcount(&g) == 5); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + /*one edge per step makes a tree*/ + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 10, /*m: edges_per_step*/ 1, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 0.5, /*aging_exp*/ -0.5, /*aging_bin*/ 2, + /*zero_deg_appeal*/ 0.1, /*zero_age_appeal*/ 0, /*deg_coef*/ 0.1, + /*age_coef */ 0.1, /*directed*/ 1) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 9); + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_is_tree(&g, &tree, /* root*/ NULL, IGRAPH_IN); + IGRAPH_ASSERT(tree); + igraph_destroy(&g); + + printf("Prefer old vertices to make a star of triple edges:\n"); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 3, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 1, + /*pa_exp*/ 0.0, /*aging_exp*/ 10, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 1.0, /*zero_age_appeal*/ 0, /*deg_coef*/ 0.0, + /*age_coef */ 1, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Prefer new vertices to make a line of double edges:\n"); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 0.0, /*aging_exp*/ -10, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 0.1, /*zero_age_appeal*/ 0, /*deg_coef*/ 0.1, + /*age_coef */ 1, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Increasing thickness of the line using outseq:\n"); + igraph_vector_int_init_int(&outseq, 5, 1, 2, 3, 4, 5); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ &outseq, /*outpref*/ 0, + /*pa_exp*/ 0.1, /*aging_exp*/ -10, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 0.1, /*zero_age_appeal*/ 0, /*deg_coef*/ 0.1, + /*age_coef */ 10, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_int_destroy(&outseq); + + printf("Prefer more edges to make a star:\n"); + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 10.0, /*aging_exp*/ 0.0, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 0.0, /*zero_age_appeal*/ 1.0, /*deg_coef*/ 1.0, + /*age_coef */ 0.0, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /* Non-negative age coefficients are required*/ + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1.0, /*aging_exp*/ 0.0, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 1.0, /*zero_age_appeal*/ 1.0, /*deg_coef*/ 1.0, + /*age_coef */ -1.0, /*directed*/ 1) == IGRAPH_EINVAL); + + /* Non-negative degree coefficients are required*/ + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1.0, /*aging_exp*/ 0.0, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 1.0, /*zero_age_appeal*/ 1.0, /*deg_coef*/ -1.0, + /*age_coef */ 0.0, /*directed*/ 1) == IGRAPH_EINVAL); + + /* Non negative zero degree appeals are required*/ + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1.0, /*aging_exp*/ 0.0, /*aging_bin*/ 6, + /*zero_deg_appeal*/ -1.0, /*zero_age_appeal*/ 1.0, /*deg_coef*/ 1.0, + /*age_coef */ 0.0, /*directed*/ 1) == IGRAPH_EINVAL); + + /* Non negative zero age appeals are required*/ + IGRAPH_ASSERT(igraph_barabasi_aging_game( + &g, /*nodes*/ 5, /*m: edges_per_step*/ 2, + /*outseq: edges per step as vector*/ NULL, /*outpref*/ 0, + /*pa_exp*/ 1.0, /*aging_exp*/ 0.0, /*aging_bin*/ 6, + /*zero_deg_appeal*/ 1.0, /*zero_age_appeal*/ -1.0, /*deg_coef*/ 1.0, + /*age_coef */ 0.0, /*directed*/ 1) == IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_barabasi_aging_game.out b/tests/unit/igraph_barabasi_aging_game.out new file mode 100644 index 0000000..f4acdff --- /dev/null +++ b/tests/unit/igraph_barabasi_aging_game.out @@ -0,0 +1,72 @@ +No vertices: +directed: false +vcount: 0 +edges: { +} +No edges: +directed: false +vcount: 5 +edges: { +} +Prefer old vertices to make a star of triple edges: +directed: true +vcount: 5 +edges: { +1 0 +1 0 +1 0 +2 0 +2 0 +2 0 +3 0 +3 0 +3 0 +4 0 +4 0 +4 0 +} +Prefer new vertices to make a line of double edges: +directed: true +vcount: 5 +edges: { +1 0 +1 0 +2 1 +2 1 +3 2 +3 2 +4 3 +4 3 +} +Increasing thickness of the line using outseq: +directed: true +vcount: 5 +edges: { +1 0 +1 0 +2 1 +2 1 +2 1 +3 2 +3 2 +3 2 +3 2 +4 3 +4 3 +4 3 +4 3 +4 3 +} +Prefer more edges to make a star: +directed: true +vcount: 5 +edges: { +1 0 +1 0 +2 0 +2 0 +3 0 +3 0 +4 0 +4 0 +} diff --git a/tests/unit/igraph_barabasi_game.c b/tests/unit/igraph_barabasi_game.c new file mode 100644 index 0000000..4058e76 --- /dev/null +++ b/tests/unit/igraph_barabasi_game.c @@ -0,0 +1,70 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void run_test(igraph_int_t n, igraph_bool_t directed, const igraph_vector_int_t *outseq, const igraph_t *start) { + igraph_barabasi_algorithm_t algos[] = { IGRAPH_BARABASI_BAG, IGRAPH_BARABASI_PSUMTREE, IGRAPH_BARABASI_PSUMTREE_MULTIPLE }; + igraph_t g; + igraph_bool_t simple; + + for (size_t i=0; i < sizeof(algos) / sizeof(algos[0]); i++) { + igraph_barabasi_algorithm_t algo = algos[i]; + igraph_barabasi_game(&g, n, 1, 2, outseq, true, 1, directed, algo, start); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + IGRAPH_ASSERT(igraph_is_directed(&g) == directed); + if (algo == IGRAPH_BARABASI_PSUMTREE) { + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + } + igraph_destroy(&g); + } +} + +int main(void) { + igraph_vector_int_t v; + igraph_t g; + + run_test(10, true, NULL, NULL); + run_test(10, false, NULL, NULL); + + igraph_ring(&g, 5, IGRAPH_DIRECTED, false, true); + run_test(13, true, NULL, &g); + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_EACH, NULL); + run_test(13, false, NULL, &g); + igraph_destroy(&g); + + igraph_vector_int_init_int(&v, 4, + 1, 2, 1, 3); + run_test(4, true, &v, NULL); + run_test(4, false, &v, NULL); + igraph_vector_int_destroy(&v); + + CHECK_ERROR(igraph_barabasi_game(&g, -10, /*power=*/ 1, 1, NULL, false, /*A=*/ 1, IGRAPH_UNDIRECTED, + IGRAPH_BARABASI_BAG, /*start_from=*/ NULL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_barabasi_game(&g, 10, /*power=*/ 1, -2, NULL, false, /*A=*/ 1, IGRAPH_UNDIRECTED, + IGRAPH_BARABASI_BAG, /*start_from=*/ NULL), IGRAPH_EINVAL); + igraph_vector_int_init(&v, 9); + CHECK_ERROR(igraph_barabasi_game(&g, 10, /*power=*/ 1, 0, &v, false, /*A=*/ 1, IGRAPH_UNDIRECTED, + IGRAPH_BARABASI_BAG, /*start_from=*/ NULL), IGRAPH_EINVAL); + igraph_vector_int_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_betweenness.c b/tests/unit/igraph_betweenness.c new file mode 100644 index 0000000..c029666 --- /dev/null +++ b/tests/unit/igraph_betweenness.c @@ -0,0 +1,317 @@ +/* + igraph library. + Copyright (C) 2008-2021 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_t bet, bet2, weights; + igraph_vector_int_t edges; + igraph_real_t cutoff = 0.0; + + igraph_int_t nontriv[] = { 0, 19, 0, 16, 0, 20, 1, 19, 2, 5, 3, 7, 3, 8, + 4, 15, 4, 11, 5, 8, 5, 19, 6, 7, 6, 10, 6, 8, + 6, 9, 7, 20, 9, 10, 9, 20, 10, 19, + 11, 12, 11, 20, 12, 15, 13, 15, + 14, 18, 14, 16, 14, 17, 15, 16, 17, 18 + }; + + igraph_real_t nontriv_weights[] = { 0.5249, 1, 0.1934, 0.6274, 0.5249, + 0.0029, 0.3831, 0.05, 0.6274, 0.3831, + 0.5249, 0.0587, 0.0579, 0.0562, 0.0562, + 0.1934, 0.6274, 0.6274, 0.6274, 0.0418, + 0.6274, 0.3511, 0.3511, 0.1486, 1, 1, + 0.0711, 0.2409 + }; + + /*******************************************************/ + + printf("BA graph\n"); + printf("==========================================================\n"); + igraph_barabasi_game(/* graph= */ &g, + /* n= */ 1000, + /* power= */ 1, + /* m= */ 3, + /* outseq= */ 0, + /* outpref= */ 0, + /* A= */ 1, + /* directed= */ 0, + /* algo= */ IGRAPH_BARABASI_BAG, + /* start_from= */ 0); + + igraph_simplify(&g, /* remove_multiple= */ true, /* remove_loops= */ true, /*edge_comb=*/ NULL); + + igraph_vector_init(&bet, 0); + + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &bet, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ 2); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\nTree\n"); + printf("==========================================================\n"); + igraph_kary_tree(&g, 20000, 10, IGRAPH_TREE_UNDIRECTED); + + igraph_vector_init(&bet, 0); + + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &bet, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ 3); + + printf("Max betweenness: %f\n", igraph_vector_max(&bet)); + + igraph_vector_init(&bet2, 0); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1.0); + + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet2, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ 3); + + IGRAPH_ASSERT(igraph_vector_all_e(&bet, &bet2)); + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nSmall undirected graph with multiple and loop edges\n"); + printf("==========================================================\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 1, 2, 1, 1, 2, 3, 3, 0, 3, 3, -1); + igraph_vector_init(&bet, 0); + igraph_betweenness(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &bet, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false); + print_vector(&bet); + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\nNon-trivial weighted graph\n"); + printf("==========================================================\n"); + edges = igraph_vector_int_view(nontriv, sizeof(nontriv) / sizeof(nontriv[0])); + igraph_create(&g, &edges, 0, /* directed= */ 0); + weights = igraph_vector_view(nontriv_weights, + sizeof(nontriv_weights) / sizeof(nontriv_weights[0])); + igraph_vector_init(&bet, 0); + + igraph_betweenness(/*graph=*/ &g, /*weights=*/ &weights, /*res=*/ &bet, /*vids=*/ igraph_vss_all(), + /*directed=*/0, false); + + print_vector(&bet); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\nCorner case cutoff 0.0\n"); + printf("==========================================================\n"); + igraph_kary_tree(&g, 20, 3, IGRAPH_TREE_UNDIRECTED); + + /* unweighted */ + igraph_vector_init(&bet, 0); + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &bet, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ 0); + + igraph_vector_init(&bet2, 0); + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &bet2, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ -1); + + print_vector(&bet); + print_vector(&bet2); + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + + /* weighted */ + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 2.0); + + igraph_vector_init(&bet, 0); + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ 0); + + igraph_vector_init(&bet2, 0); + igraph_betweenness_cutoff(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet2, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false, + /* cutoff= */ -1); + + print_vector(&bet); + print_vector(&bet2); + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nSingle path graph cutoff\n"); + printf("==========================================================\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 1, + 1, 2, + 2, 3, + 3, 4, -1); + igraph_vector_init(&bet, igraph_vcount(&g)); + igraph_vector_init(&bet2, igraph_vcount(&g)); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1); + + for (cutoff = -1.0; cutoff < 5.0; cutoff += 1) + { + printf("Cutoff %.0f\n", cutoff); + printf("Unweighted\n"); + igraph_betweenness_cutoff(&g, + /* weights */ NULL, &bet, + igraph_vss_all(), IGRAPH_UNDIRECTED, false, + /* cutoff */ cutoff); + print_vector(&bet); + + printf("Weighted\n"); + igraph_betweenness_cutoff(&g, + /* weights */ &weights, &bet2, + igraph_vss_all(), IGRAPH_UNDIRECTED, false, + /* cutoff */ cutoff); + print_vector(&bet2); + printf("\n"); + + IGRAPH_ASSERT(igraph_vector_all_e(&bet, &bet2)); + } + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nCycle graph\n"); + printf("==========================================================\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0, 1, + 0, 2, + 1, 3, + 2, 3, -1); + igraph_vector_init(&bet, igraph_vcount(&g)); + igraph_vector_init(&bet2, igraph_vcount(&g)); + igraph_vector_init(&weights, igraph_ecount(&g)); + VECTOR(weights)[0] = 1.01; + VECTOR(weights)[1] = 2; + VECTOR(weights)[2] = 0.99; + VECTOR(weights)[3] = 2; + + for (cutoff = -1.0; cutoff < 5.0; cutoff += 1) + { + printf("Cutoff %.0f\n", cutoff); + printf("Unweighted\n"); + igraph_betweenness_cutoff(&g, + /* weights */ NULL, &bet, + igraph_vss_all(), IGRAPH_UNDIRECTED, false, + /* cutoff */ cutoff); + print_vector(&bet); + + printf("Weighted\n"); + igraph_betweenness_cutoff(&g, + /* weights */ &weights, &bet2, + igraph_vss_all(), IGRAPH_UNDIRECTED, false, + /* cutoff */ cutoff); + print_vector(&bet2); + printf("\n"); + } + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nNull graph\n"); + printf("==========================================================\n"); + + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_vector_init(&bet, 3); /* purposefully larger than zero, as igraph_betweenness must resize it */ + igraph_betweenness(&g, NULL, &bet, igraph_vss_all(), IGRAPH_UNDIRECTED, false); + print_vector(&bet); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\nEmpty graph\n"); + printf("==========================================================\n"); + + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + igraph_vector_init(&bet, 0); + igraph_betweenness(&g, NULL, &bet, igraph_vss_all(), IGRAPH_UNDIRECTED, false); + print_vector(&bet); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\n37x37 grid graph\n"); + printf("==========================================================\n"); + + { + igraph_vector_int_t dims; + + igraph_vector_int_init(&dims, 2); + VECTOR(dims)[0] = 37; + VECTOR(dims)[1] = 37; + + igraph_square_lattice(&g, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + + igraph_vector_init(&bet, 0); + igraph_betweenness(&g, NULL, &bet, igraph_vss_all(), IGRAPH_UNDIRECTED, false); + printf("Max betweenness: %f\n", igraph_vector_max(&bet)); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + igraph_vector_int_destroy(&dims); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_betweenness.out b/tests/unit/igraph_betweenness.out new file mode 100644 index 0000000..e65888c --- /dev/null +++ b/tests/unit/igraph_betweenness.out @@ -0,0 +1,111 @@ +BA graph +========================================================== + +Tree +========================================================== +Max betweenness: 1155.000000 + +Small undirected graph with multiple and loop edges +========================================================== +( 0.333333 0.666667 0.666667 0.333333 ) + +Non-trivial weighted graph +========================================================== +( 20 0 0 0 0 19 80 85 32 0 10 75 70 0 36 81 60 0 19 19 86 ) + +Corner case cutoff 0.0 +========================================================== +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 104 122 51 51 51 51 18 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 104 122 51 51 51 51 18 0 0 0 0 0 0 0 0 0 0 0 0 0 ) + +Single path graph cutoff +========================================================== +Cutoff -1 +Unweighted +( 0 3 4 3 0 ) +Weighted +( 0 3 4 3 0 ) + +Cutoff 0 +Unweighted +( 0 0 0 0 0 ) +Weighted +( 0 0 0 0 0 ) + +Cutoff 1 +Unweighted +( 0 0 0 0 0 ) +Weighted +( 0 0 0 0 0 ) + +Cutoff 2 +Unweighted +( 0 1 1 1 0 ) +Weighted +( 0 1 1 1 0 ) + +Cutoff 3 +Unweighted +( 0 2 3 2 0 ) +Weighted +( 0 2 3 2 0 ) + +Cutoff 4 +Unweighted +( 0 3 4 3 0 ) +Weighted +( 0 3 4 3 0 ) + + +Cycle graph +========================================================== +Cutoff -1 +Unweighted +( 0.5 0.5 0.5 0.5 ) +Weighted +( 0 1 0 1 ) + +Cutoff 0 +Unweighted +( 0 0 0 0 ) +Weighted +( 0 0 0 0 ) + +Cutoff 1 +Unweighted +( 0 0 0 0 ) +Weighted +( 0 0 0 0 ) + +Cutoff 2 +Unweighted +( 0.5 0.5 0.5 0.5 ) +Weighted +( 0 1 0 0 ) + +Cutoff 3 +Unweighted +( 0.5 0.5 0.5 0.5 ) +Weighted +( 0 1 0 1 ) + +Cutoff 4 +Unweighted +( 0.5 0.5 0.5 0.5 ) +Weighted +( 0 1 0 1 ) + + +Null graph +========================================================== +( ) + +Empty graph +========================================================== +( 0 0 ) + +37x37 grid graph +========================================================== +Max betweenness: 36094.891693 diff --git a/tests/unit/igraph_betweenness_subset.c b/tests/unit/igraph_betweenness_subset.c new file mode 100644 index 0000000..e67c661 --- /dev/null +++ b/tests/unit/igraph_betweenness_subset.c @@ -0,0 +1,361 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +int main(void) { + igraph_t g; + igraph_vector_int_t edges; + igraph_vector_t bet, bet2, weights; + igraph_vector_int_t node_vec, source_vec, target_vec; + igraph_vs_t vs, vs_source, vs_target; + igraph_int_t i, n; + + igraph_int_t nontriv[] = { 0, 19, 0, 16, 0, 20, 1, 19, 2, 5, 3, 7, 3, 8, + 4, 15, 4, 11, 5, 8, 5, 19, 6, 7, 6, 10, 6, 8, + 6, 9, 7, 20, 9, 10, 9, 20, 10, 19, + 11, 12, 11, 20, 12, 15, 13, 15, + 14, 18, 14, 16, 14, 17, 15, 16, 17, 18 + }; + + igraph_real_t nontriv_weights[] = { 0.5249, 1, 0.1934, 0.6274, 0.5249, + 0.0029, 0.3831, 0.05, 0.6274, 0.3831, + 0.5249, 0.0587, 0.0579, 0.0562, 0.0562, + 0.1934, 0.6274, 0.6274, 0.6274, 0.0418, + 0.6274, 0.3511, 0.3511, 0.1486, 1, 1, + 0.0711, 0.2409 + }; + + printf("BA graph (smoke test)\n"); + printf("==========================================================\n"); + igraph_barabasi_game(/* graph= */ &g, + /* n= */ 1000, + /* power= */ 1, + /* m= */ 3, + /* outseq= */ 0, + /* outpref= */ 0, + /* A= */ 1, + /* directed= */ IGRAPH_UNDIRECTED, + /* algo= */ IGRAPH_BARABASI_BAG, + /* start_from= */ 0); + + igraph_simplify(&g, /* remove_multiple= */ true, /* remove_loops= */ true, /*edge_comb=*/ NULL); + + igraph_vector_init(&bet, 0); + igraph_vs_range(&vs_source, 0, 501); + igraph_vs_range(&vs_target, 500, 1000); + + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false); + + igraph_vector_destroy(&bet); + igraph_vs_destroy(&vs_source); + igraph_vs_destroy(&vs_target); + igraph_destroy(&g); + + printf("\nTree\n"); + printf("==========================================================\n"); + igraph_kary_tree(&g, 11111, 10, IGRAPH_TREE_UNDIRECTED); + + /* We are including the rightmost 200 vertices from the lowermost layer + * (layer 5) of the tree. These have 20 parents in layer 4, 2 grandparents + * in layer 3, and a single grand-grandparent in layer 2. None of the + * shortest paths we consider should therefore pass through the root, the + * first 9 vertices of layer 2, the first 98 vertices of layer 3 or the + * first 980 vertices of layer 4; the betweenness of these vertices should + * all be zeros. + * + * Also, the betweenness of the common grand-grandparent in layer 2 is easy + * to calculate as any shortest path going between grand-grandchildren + * reachable via its left child and via its right child should pass through + * it. This gives us a betweenness of 100 * 100 = 10000 for this node. + * Similar calculations reveal that the betweenness of its two children + * will be 100 * 100 + 100 * 90 / 2 = 14500 (the same paths as above plus any + * path that goes from any of its subtree to any other subtree). These are + * also the maximal betweennesses. The betwennesses in the remaining layers + * are 1945 and 199 if the vertex being considered is a descendant of the + * common grand-grandparent in layer 2, and zero otherwise. */ + + igraph_vs_range(&vs_source, 10911, 11111); + igraph_vs_range(&vs_target, 10911, 11111); + igraph_vector_init(&bet, 0); + + igraph_betweenness_subset( + /* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false); + + printf("Max betweenness: %f\n", igraph_vector_max(&bet)); + + n = igraph_vcount(&g); + for (i = 0; i < n; i++) { + igraph_int_t expected; + + if (i >= 10911) { + /* layer 5, in the subset. There are 199 shortest paths that + * contain these nodes, but the nodes are the _endpoints_ of the + * paths so they don't count in the betweenness score */ + expected = 0; + } else if (i >= 1111) { + /* layer 5, not in the subset */ + expected = 0; + } else if (i >= 1091) { + /* layer 4, rightmost 20 nodes */ + expected = 1945; + } else if (i >= 111) { + /* layer 4, remaining nodes */ + expected = 0; + } else if (i >= 109) { + /* layer 3, rightmost 2 nodes */ + expected = 14500; + } else if (i >= 11) { + /* layer 3, remaining nodes */ + expected = 0; + } else if (i == 10) { + /* layer 2, rightmost node */ + expected = 10000; + } else { + expected = 0; + } + + if (VECTOR(bet)[i] != expected) { + printf( + "Invalid betweenness for vertex %" IGRAPH_PRId ", expected %" IGRAPH_PRId ", got %" IGRAPH_PRId "\n", + i, expected, (igraph_int_t) VECTOR(bet)[i] + ); + break; + } + } + + igraph_vector_init(&bet2, 0); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1.0); + + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet2, + /* sources = */ vs_source, + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false); + + IGRAPH_ASSERT(igraph_vector_all_e(&bet, &bet2)); + + igraph_vector_destroy(&weights); + igraph_vs_destroy(&vs_source); + igraph_vs_destroy(&vs_target); + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_destroy(&g); + + printf("\nNon-trivial weighted graph using subset algorithm\n"); + printf("==========================================================\n"); + edges = igraph_vector_int_view(nontriv, sizeof(nontriv) / sizeof(nontriv[0])); + igraph_create(&g, &edges, 0, /* directed= */ IGRAPH_UNDIRECTED); + weights = igraph_vector_view(nontriv_weights, + sizeof(nontriv_weights) / sizeof(nontriv_weights[0])); + + igraph_vector_init(&bet, 0); + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet, + /* sources = */ igraph_vss_all(), + /* target = */ igraph_vss_all(), + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false); + + print_vector(&bet); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\nSingle path graph of subset\n"); + printf("==========================================================\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 1, + 1, 2, + 2, 3, + 3, 4, -1); + igraph_vector_init(&bet, igraph_vcount(&g)); + igraph_vector_init(&bet2, igraph_vcount(&g)); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1); + + for (i = 0; i < 5; i++) + { + igraph_vector_int_init_range(&node_vec, 0, 5); + igraph_vector_int_remove(&node_vec, i); + igraph_vs_vector(&vs, &node_vec); + igraph_vector_int_init_range(&source_vec, 0, 5); + igraph_vector_int_remove(&source_vec, i); + igraph_vs_vector(&vs_source, &source_vec); + printf("subset without %" IGRAPH_PRId "\n", i); + printf("Unweighted\n"); + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ igraph_vss_all(), + /* vids= */ vs, + /* directed = */ IGRAPH_UNDIRECTED, false); + print_vector(&bet); + + printf("Weighted\n"); + igraph_betweenness_subset(/* graph= */ &g, + /* weights */ &weights, + /* res= */ &bet2, + /* sources = */ vs_source, + /* target = */ igraph_vss_all(), + /* vids= */ vs, + /* directed = */ IGRAPH_UNDIRECTED, false); + print_vector(&bet2); + printf("\n"); + + IGRAPH_ASSERT(igraph_vector_all_e(&bet, &bet2)); + igraph_vs_destroy(&vs); + igraph_vector_int_destroy(&node_vec); + igraph_vs_destroy(&vs_source); + igraph_vector_int_destroy(&source_vec); + } + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nCycle graph subset\n"); + printf("==========================================================\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0, 1, + 0, 2, + 1, 3, + 2, 3, -1); + igraph_vector_init(&bet, igraph_vcount(&g)); + igraph_vector_init(&bet2, igraph_vcount(&g)); + igraph_vector_init(&weights, igraph_ecount(&g)); + VECTOR(weights)[0] = 1.01; + VECTOR(weights)[1] = 2; + VECTOR(weights)[2] = 0.99; + VECTOR(weights)[3] = 2; + + for (i = 0; i < 3; i++) + { + igraph_vector_int_init_range(&target_vec, 0, 4); + igraph_vector_int_remove(&target_vec, i); + igraph_vs_vector(&vs_target, &target_vec); + printf("subset without %" IGRAPH_PRId "\n", i); + printf("Unweighted\n"); + igraph_betweenness_subset(/* graph= */ &g, + /* weights */ NULL, + /* res= */ &bet, + /* sources = */ igraph_vss_all(), + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false); + print_vector(&bet); + + printf("Weighted\n"); + igraph_betweenness_subset(/* graph= */ &g, + /* weights */ &weights, + /* res= */ &bet2, + /* sources = */ igraph_vss_all(), + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ 0, false); + print_vector(&bet2); + printf("\n"); + + igraph_vs_destroy(&vs_target); + igraph_vector_int_destroy(&target_vec); + } + + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + printf("\nEmpty graph\n"); + printf("==========================================================\n"); + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + igraph_vector_init(&bet, 0); + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ igraph_vss_all(), + /* target = */ igraph_vss_all(), + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false); + + print_vector(&bet); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + + printf("\n37x37 grid graph\n"); + printf("==========================================================\n"); + + { + igraph_vector_int_t dims; + + igraph_vector_int_init(&dims, 2); + VECTOR(dims)[0] = 37; + VECTOR(dims)[1] = 37; + + igraph_square_lattice(&g, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + + igraph_vector_init(&bet, 0); + igraph_vector_int_init_range(&target_vec, 0, igraph_vcount(&g)); + igraph_vector_int_remove(&target_vec, 0); + igraph_vs_vector(&vs_target, &target_vec); + igraph_vector_int_init_range(&source_vec, 0, igraph_vcount(&g)); + igraph_vector_int_remove(&source_vec, 0); + igraph_vs_vector(&vs_source, &source_vec); + + igraph_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ vs_target, + /* vids= */ igraph_vss_all(), + /* directed = */ IGRAPH_UNDIRECTED, false);; + printf("Max betweenness: %f\n", igraph_vector_max(&bet)); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + igraph_vector_int_destroy(&dims); + igraph_vs_destroy(&vs_target); + igraph_vector_int_destroy(&target_vec); + igraph_vs_destroy(&vs_source); + igraph_vector_int_destroy(&source_vec); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_betweenness_subset.out b/tests/unit/igraph_betweenness_subset.out new file mode 100644 index 0000000..2734ddd --- /dev/null +++ b/tests/unit/igraph_betweenness_subset.out @@ -0,0 +1,72 @@ +BA graph (smoke test) +========================================================== + +Tree +========================================================== +Max betweenness: 14500.000000 + +Non-trivial weighted graph using subset algorithm +========================================================== +( 20 0 0 0 0 19 80 85 32 0 10 75 70 0 36 81 60 0 19 19 86 ) + +Single path graph of subset +========================================================== +subset without 0 +Unweighted +( 1.5 3 2.5 0 ) +Weighted +( 1.5 3 2.5 0 ) + +subset without 1 +Unweighted +( 0 3 2.5 0 ) +Weighted +( 0 3 2.5 0 ) + +subset without 2 +Unweighted +( 0 2.5 2.5 0 ) +Weighted +( 0 2.5 2.5 0 ) + +subset without 3 +Unweighted +( 0 2.5 3 0 ) +Weighted +( 0 2.5 3 0 ) + +subset without 4 +Unweighted +( 0 2.5 3 1.5 ) +Weighted +( 0 2.5 3 1.5 ) + + +Cycle graph subset +========================================================== +subset without 0 +Unweighted +( 0.5 0.25 0.25 0.5 ) +Weighted +( 0 0.5 0 1 ) + +subset without 1 +Unweighted +( 0.25 0.5 0.5 0.25 ) +Weighted +( 0 1 0 0.5 ) + +subset without 2 +Unweighted +( 0.25 0.5 0.5 0.25 ) +Weighted +( 0 1 0 0.5 ) + + +Empty graph +========================================================== +( 0 0 ) + +37x37 grid graph +========================================================== +Max betweenness: 36050.526458 diff --git a/tests/unit/igraph_biadjacency.c b/tests/unit/igraph_biadjacency.c new file mode 100644 index 0000000..07d023f --- /dev/null +++ b/tests/unit/igraph_biadjacency.c @@ -0,0 +1,129 @@ +/* + igraph library. + Copyright (C) 2022-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_matrix_t *biadjmat, igraph_bool_t directed, igraph_neimode_t mode, igraph_bool_t multiple) { + igraph_t g; + igraph_vector_bool_t types; + igraph_vector_bool_init(&types, 0); + + igraph_biadjacency(&g, &types, biadjmat, directed, mode, multiple); + + print_graph_canon(&g); + printf("types: "); + + igraph_vector_bool_print(&types); + igraph_matrix_destroy(biadjmat); + igraph_destroy(&g); + igraph_vector_bool_destroy(&types); +} + +int main(void) { + igraph_t g; + igraph_vector_bool_t types; + igraph_vector_bool_init(&types, 0); + igraph_matrix_t biadjmat; + + { + printf("Bipartite adjacency matrix with no rows and no columns:\n"); + igraph_matrix_init(&biadjmat, 0, 0); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL, false); + } + + { + printf("Bipartite adjacency matrix no rows and some columns:\n"); + igraph_matrix_init(&biadjmat, 0, 5); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL, false); + } + + { + printf("\nBipartite adjacency matrix for two vertices:\n"); + int elem[] = {5}; + matrix_init_int_row_major(&biadjmat, 1, 1, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL, false); + } + + { + printf("\nBipartite adjacency matrix for two vertices, multiple = true:\n"); + int elem[] = {5}; + matrix_init_int_row_major(&biadjmat, 1, 1, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL, true); + } + + { + printf("\nBipartite adjacency matrix for five vertices:\n"); + int elem[] = {0, 1, 2, 3, 4, 5}; + matrix_init_int_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL, true); + } + + { + printf("\nSame graph, IGRAPH_OUT:\n"); + int elem[] = {0, 1, 2, 3, 4, 5}; + matrix_init_int_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_OUT, true); + } + + { + printf("\nSame graph, IGRAPH_IN:\n"); + int elem[] = {0, 1, 2, 3, 4, 5}; + matrix_init_int_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_IN, true); + } + + { + printf("\nSame graph, undirected:\n"); + int elem[] = {0, 1, 2, 3, 4, 5}; + matrix_init_int_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_UNDIRECTED, IGRAPH_OUT, true); + } + + { + printf("\nNon-integer elements, multiple=false:\n"); + igraph_real_t elem[] = {0.0, 1.2, -1.8, + 5.0, 0.0, 2.2}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_UNDIRECTED, IGRAPH_ALL, false); + } + + { + printf("\nNon-integer elements, multiple=true:\n"); + igraph_real_t elem[] = {0.0, 1.2, 1.8, + 5.0, 0.0, 2.2}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_UNDIRECTED, IGRAPH_ALL, true); + } + + VERIFY_FINALLY_STACK(); + + { + printf("\nCheck error for negative element.\n"); + int elem[] = {-5}; + matrix_init_int_row_major(&biadjmat, 1, 1, elem); + CHECK_ERROR(igraph_biadjacency(&g, &types, &biadjmat, IGRAPH_DIRECTED, + IGRAPH_ALL, true), IGRAPH_EINVAL); + igraph_matrix_destroy(&biadjmat); + } + + igraph_vector_bool_destroy(&types); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_biadjacency.out b/tests/unit/igraph_biadjacency.out new file mode 100644 index 0000000..7ee6660 --- /dev/null +++ b/tests/unit/igraph_biadjacency.out @@ -0,0 +1,170 @@ +Bipartite adjacency matrix with no rows and no columns: +directed: true +vcount: 0 +edges: { +} +types: +Bipartite adjacency matrix no rows and some columns: +directed: true +vcount: 5 +edges: { +} +types: 1 1 1 1 1 + +Bipartite adjacency matrix for two vertices: +directed: true +vcount: 2 +edges: { +0 1 +1 0 +} +types: 0 1 + +Bipartite adjacency matrix for two vertices, multiple = true: +directed: true +vcount: 2 +edges: { +0 1 +0 1 +0 1 +0 1 +0 1 +1 0 +1 0 +1 0 +1 0 +1 0 +} +types: 0 1 + +Bipartite adjacency matrix for five vertices: +directed: true +vcount: 5 +edges: { +0 3 +0 4 +0 4 +1 2 +1 2 +1 2 +1 3 +1 3 +1 3 +1 3 +1 4 +1 4 +1 4 +1 4 +1 4 +2 1 +2 1 +2 1 +3 0 +3 1 +3 1 +3 1 +3 1 +4 0 +4 0 +4 1 +4 1 +4 1 +4 1 +4 1 +} +types: 0 0 1 1 1 + +Same graph, IGRAPH_OUT: +directed: true +vcount: 5 +edges: { +0 3 +0 4 +0 4 +1 2 +1 2 +1 2 +1 3 +1 3 +1 3 +1 3 +1 4 +1 4 +1 4 +1 4 +1 4 +} +types: 0 0 1 1 1 + +Same graph, IGRAPH_IN: +directed: true +vcount: 5 +edges: { +2 1 +2 1 +2 1 +3 0 +3 1 +3 1 +3 1 +3 1 +4 0 +4 0 +4 1 +4 1 +4 1 +4 1 +4 1 +} +types: 0 0 1 1 1 + +Same graph, undirected: +directed: false +vcount: 5 +edges: { +0 3 +0 4 +0 4 +1 2 +1 2 +1 2 +1 3 +1 3 +1 3 +1 3 +1 4 +1 4 +1 4 +1 4 +1 4 +} +types: 0 0 1 1 1 + +Non-integer elements, multiple=false: +directed: false +vcount: 5 +edges: { +0 3 +0 4 +1 2 +1 4 +} +types: 0 0 1 1 1 + +Non-integer elements, multiple=true: +directed: false +vcount: 5 +edges: { +0 3 +0 4 +1 2 +1 2 +1 2 +1 2 +1 2 +1 4 +1 4 +} +types: 0 0 1 1 1 + +Check error for negative element. diff --git a/tests/unit/igraph_biconnected_components.c b/tests/unit/igraph_biconnected_components.c new file mode 100644 index 0000000..5c4e984 --- /dev/null +++ b/tests/unit/igraph_biconnected_components.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_list_t result; + igraph_vector_int_t ap; + igraph_int_t no; + + igraph_vector_int_list_init(&result, 0); + igraph_vector_int_init(&ap, 0); + igraph_small(&g, 10 /* extra isolated vertex */, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 3,0, + 2,4, 4,5, 5,2, + 5,6, + 7,8, + -1); + + printf("Vertices in biconnected components:\n"); + igraph_biconnected_components(&g, &no, NULL, NULL, /* components */ &result, NULL); + IGRAPH_ASSERT(no == igraph_vector_int_list_size(&result)); + print_vector_int_list(&result); + + printf("Edges in biconnected components:\n"); + igraph_biconnected_components(&g, &no, NULL, /* component_edges */ &result, NULL, NULL); + IGRAPH_ASSERT(no == igraph_vector_int_list_size(&result)); + print_vector_int_list(&result); + + printf("Edges in biconnected component spanning trees:\n"); + igraph_biconnected_components(&g, &no, /* tree_edges */ &result, NULL, NULL, &ap); + IGRAPH_ASSERT(no == igraph_vector_int_list_size(&result)); + print_vector_int_list(&result); + + printf("Articulation points:\n"); + print_vector_int(&ap); + + igraph_destroy(&g); + igraph_vector_int_destroy(&ap); + igraph_vector_int_list_destroy(&result); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_biconnected_components.out b/tests/unit/igraph_biconnected_components.out new file mode 100644 index 0000000..38ae9e9 --- /dev/null +++ b/tests/unit/igraph_biconnected_components.out @@ -0,0 +1,23 @@ +Vertices in biconnected components: +{ + 0: ( 6 5 ) + 1: ( 5 4 2 ) + 2: ( 3 2 1 0 ) + 3: ( 8 7 ) +} +Edges in biconnected components: +{ + 0: ( 7 ) + 1: ( 6 5 4 ) + 2: ( 3 2 1 0 ) + 3: ( 8 ) +} +Edges in biconnected component spanning trees: +{ + 0: ( 7 ) + 1: ( 5 4 ) + 2: ( 2 1 0 ) + 3: ( 8 ) +} +Articulation points: +( 5 2 ) diff --git a/tests/unit/igraph_bipartite_create.c b/tests/unit/igraph_bipartite_create.c new file mode 100644 index 0000000..167aebf --- /dev/null +++ b/tests/unit/igraph_bipartite_create.c @@ -0,0 +1,40 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_int_t edges_array[] = {0, 1, 1, 2, 3, 4, 5, 6, 6, 5, 2, 4, 1, 6, 0, 3 }; + const igraph_vector_int_t edges = + igraph_vector_int_view(edges_array, sizeof(edges_array) / sizeof(edges_array[0]));; + igraph_vector_bool_t types; + igraph_t g; + igraph_int_t i; + + igraph_vector_bool_init(&types, igraph_vector_int_max(&edges) + 1); + for (i = 0; i < igraph_vector_bool_size(&types); i++) { + VECTOR(types)[i] = i % 2; + } + /* Not a bipartite graph, vertices 2 and 4 are connected */ + CHECK_ERROR(igraph_create_bipartite(&g, &types, &edges, /*directed=*/ 1), IGRAPH_EINVAL); + + igraph_vector_bool_destroy(&types); + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_bipartite_game.c b/tests/unit/igraph_bipartite_game.c new file mode 100644 index 0000000..d12c1f0 --- /dev/null +++ b/tests/unit/igraph_bipartite_game.c @@ -0,0 +1,346 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_partitions( + const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_neimode_t mode +) { + igraph_int_t m = igraph_ecount(graph); + + IGRAPH_ASSERT(igraph_vector_bool_size(types) == igraph_vcount(graph)); + + for (igraph_int_t i=0; i < m; i++) { + switch (mode) { + case IGRAPH_OUT: + IGRAPH_ASSERT(VECTOR(*types)[IGRAPH_FROM(graph, i)] == false); + IGRAPH_ASSERT(VECTOR(*types)[IGRAPH_TO(graph, i)] == true); + break; + case IGRAPH_IN: + IGRAPH_ASSERT(VECTOR(*types)[IGRAPH_FROM(graph, i)] == true); + IGRAPH_ASSERT(VECTOR(*types)[IGRAPH_TO(graph, i)] == false); + break; + case IGRAPH_ALL: + IGRAPH_ASSERT(VECTOR(*types)[IGRAPH_FROM(graph, i)] != VECTOR(*types)[IGRAPH_TO(graph, i)]); + break; + } + } +} + +void check_gnm( + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode, igraph_bool_t multi +) { + igraph_t graph; + igraph_vector_bool_t types; + igraph_bool_t has_loop, has_multi; + igraph_bool_t bipartite; + + if (! directed) { + mode = IGRAPH_ALL; + } + + igraph_vector_bool_init(&types, 0); + igraph_bipartite_game_gnm(&graph, &types, n1, n2, m, directed, mode, multi ? IGRAPH_MULTI_SW : IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + /* check correct vertex and edge count, directedness */ + IGRAPH_ASSERT(igraph_is_directed(&graph) == directed); + IGRAPH_ASSERT(igraph_vcount(&graph) == n1 + n2); + IGRAPH_ASSERT(igraph_ecount(&graph) == m); + + /* bipartite graphs do not have self-loops */ + igraph_has_loop(&graph, &has_loop); + IGRAPH_ASSERT(! has_loop); + + /* no multi-edges unless explicitly allowed */ + igraph_has_multiple(&graph, &has_multi); + if (! multi) { + IGRAPH_ASSERT(! has_multi); + } + + /* redundant with the next check, but also tests is_bipartite() */ + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + + check_partitions(&graph, &types, mode); + + igraph_destroy(&graph); + igraph_vector_bool_destroy(&types); +} + +void check_iea( + igraph_int_t n1, igraph_int_t n2, igraph_int_t m, + igraph_bool_t directed, igraph_neimode_t mode +) { + igraph_t graph; + igraph_vector_bool_t types; + igraph_bool_t has_loop; + igraph_bool_t bipartite; + + if (! directed) { + mode = IGRAPH_ALL; + } + + igraph_vector_bool_init(&types, 0); + igraph_bipartite_iea_game(&graph, &types, n1, n2, m, directed, mode); + + /* check correct vertex and edge count, directedness */ + IGRAPH_ASSERT(igraph_is_directed(&graph) == directed); + IGRAPH_ASSERT(igraph_vcount(&graph) == n1 + n2); + IGRAPH_ASSERT(igraph_ecount(&graph) == m); + + /* bipartite graphs do not have self-loops */ + igraph_has_loop(&graph, &has_loop); + IGRAPH_ASSERT(! has_loop); + + /* redundant with the next check, but also tests is_bipartite() */ + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + + check_partitions(&graph, &types, mode); + + igraph_destroy(&graph); + igraph_vector_bool_destroy(&types); +} + +void check_gnp( + igraph_int_t n1, igraph_int_t n2, igraph_real_t p, + igraph_bool_t directed, igraph_neimode_t mode, igraph_bool_t multiple, + igraph_bool_t edge_labeled, + igraph_bool_t assume_edges +) { + igraph_t graph; + igraph_vector_bool_t types; + igraph_bool_t has_loop, has_multi; + igraph_bool_t bipartite; + + if (! directed) { + mode = IGRAPH_ALL; + } + + igraph_vector_bool_init(&types, 0); + igraph_bipartite_game_gnp(&graph, &types, n1, n2, p, directed, mode, multiple ? IGRAPH_MULTI_SW : IGRAPH_SIMPLE_SW, edge_labeled); + + /* check correct vertex and edge count, directedness */ + IGRAPH_ASSERT(igraph_is_directed(&graph) == directed); + IGRAPH_ASSERT(igraph_vcount(&graph) == n1 + n2); + if (assume_edges) { + /* with most parameter values, having no edges is exceedingly unlikely */ + IGRAPH_ASSERT(igraph_ecount(&graph) > 0); + } + if (p == 1) { + /* complete graph */ + IGRAPH_ASSERT(igraph_ecount(&graph) == (directed && mode == IGRAPH_ALL) ? 2*n1*n2 : n1*n2); + } else if (p == 0) { + /* empty graph */ + IGRAPH_ASSERT(igraph_ecount(&graph) == 0); + } + + /* bipartite graphs do not have self-loops */ + igraph_has_loop(&graph, &has_loop); + IGRAPH_ASSERT(! has_loop); + + /* no multi-edges unless explicitly allowed */ + igraph_has_multiple(&graph, &has_multi); + if (!multiple) IGRAPH_ASSERT(! has_multi); + + /* redundant with the next check, but also tests is_bipartite() */ + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + + check_partitions(&graph, &types, mode); + + igraph_destroy(&graph); + igraph_vector_bool_destroy(&types); +} + +int main(void) { + igraph_t graph; + igraph_neimode_t modes[] = { IGRAPH_OUT, IGRAPH_IN, IGRAPH_ALL }; + + igraph_rng_seed(igraph_rng_default(), 947); + + /* G(n,m) */ + + /* UNDIRECTED */ + + /* null graph */ + check_gnm(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + + /* empty partition */ + check_gnm(0, 2, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(3, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + + /* empty graph */ + check_gnm(5, 6, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(6, 5, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + + /* arbitrary graph */ + check_gnm(10, 20, 80, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(10, 20, 80, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + check_gnm(20, 10, 80, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + check_gnm(8, 12, 150, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + check_gnm(12, 8, 150, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + + /* complete graph */ + check_gnm(5, 6, 5*6, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(6, 5, 5*6, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + + /* DIRECTED */ + + for (size_t i=0; i < sizeof(modes) / sizeof(modes[0]); i++) { + /* null graph */ + check_gnm(0, 0, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE); + check_gnm(0, 0, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE); + + /* empty partition */ + check_gnm(3, 0, 0, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + check_gnm(0, 4, 0, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE); + + /* empty graph */ + check_gnm(2, 4, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE); + check_gnm(5, 3, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE); + + /* arbitrary graph */ + + check_gnm(4, 7, 15, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE); + check_gnm(4, 7, 15, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE); + + check_gnm(7, 4, 15, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE); + check_gnm(7, 4, 15, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE); + + /* many edges */ + check_gnm(4, 3, 25, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE); + } + + /* complete graph */ + check_gnm(3, 2, 6, IGRAPH_DIRECTED, IGRAPH_IN, IGRAPH_NO_MULTIPLE); + check_gnm(3, 2, 12, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE); + + VERIFY_FINALLY_STACK(); + + /* IEA */ + + /* UNDIRECTED */ + + /* null graph */ + check_iea(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL); + + /* empty partition */ + check_iea(2, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL); + + /* empty graph */ + check_iea(5, 6, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL); + + /* arbitrary graph */ + check_iea(20, 10, 80, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_iea(8, 12, 150, IGRAPH_UNDIRECTED, IGRAPH_ALL); + + /* DIRECTED */ + + for (size_t i=0; i < sizeof(modes) / sizeof(modes[0]); i++) { + /* null graph */ + check_iea(0, 0, 0, IGRAPH_DIRECTED, modes[i]); + + /* empty partition */ + check_iea(0, 2, 0, IGRAPH_DIRECTED, modes[i]); + + /* empty graph */ + check_iea(2, 4, 0, IGRAPH_DIRECTED, modes[i]); + + /* arbitrary graph */ + check_iea(4, 7, 15, IGRAPH_DIRECTED, modes[i]); + + /* many edges */ + check_iea(4, 3, 25, IGRAPH_DIRECTED, modes[i]); + } + + VERIFY_FINALLY_STACK(); + + /* G(n,p) */ + + /* UNDIRECTED */ + + check_gnp(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(2, 3, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(2, 3, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(0, 0, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, false); + check_gnp(2, 3, 0, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, false); + + check_gnp(8, 15, 0.8, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(6, 3, 1, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(8, 15, 0.8, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(6, 3, 1, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(8, 15, 0.8, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, true); + check_gnp(6, 3, 1, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, true); + + /* DIRECTED */ + + for (size_t i=0; i < sizeof(modes) / sizeof(modes[0]); i++) { + check_gnp(0, 0, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(2, 3, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(0, 0, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(2, 3, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, false); + check_gnp(0, 0, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, false); + check_gnp(2, 3, 0, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, false); + + check_gnp(8, 15, 0.8, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(6, 3, 1, IGRAPH_DIRECTED, modes[i], IGRAPH_NO_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(8, 15, 0.8, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(6, 3, 1, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_UNLABELED, true); + check_gnp(8, 15, 0.8, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, true); + check_gnp(6, 3, 1, IGRAPH_DIRECTED, modes[i], IGRAPH_MULTIPLE, IGRAPH_EDGE_LABELED, true); + } + + VERIFY_FINALLY_STACK(); + + CHECK_ERROR(igraph_bipartite_game_gnm(&graph, NULL, 0, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_game_gnm(&graph, NULL, 0, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED), IGRAPH_EINVAL); + + CHECK_ERROR(igraph_bipartite_game_gnm(&graph, NULL, 10, 10, 201, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + + CHECK_ERROR(igraph_bipartite_game_gnm(&graph, NULL, -1, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_game_gnm(&graph, NULL, 10, -1, 20, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + + CHECK_ERROR(igraph_bipartite_iea_game(&graph, NULL, 0, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_iea_game(&graph, NULL, 10, 0, 20, IGRAPH_UNDIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + + CHECK_ERROR(igraph_bipartite_iea_game(&graph, NULL, -1, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_iea_game(&graph, NULL, 10, -1, 20, IGRAPH_UNDIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + + CHECK_ERROR(igraph_bipartite_game_gnp(&graph, NULL, -1, 10, 0.1, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_game_gnp(&graph, NULL, 10, -1, 0.9, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + CHECK_ERROR(igraph_bipartite_game_gnp(&graph, NULL, 10, 10, 1.1, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, + false), IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_bipartite_projection.c b/tests/unit/igraph_bipartite_projection.c new file mode 100644 index 0000000..9b412cd --- /dev/null +++ b/tests/unit/igraph_bipartite_projection.c @@ -0,0 +1,182 @@ +/* + igraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include "test_utilities.h" + +int check_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + const igraph_t *proj1, + const igraph_t *proj2) { + igraph_int_t vcount1, ecount1, vcount2, ecount2; + igraph_bipartite_projection_size(graph, types, &vcount1, &ecount1, + &vcount2, &ecount2); + if (proj1 && igraph_vcount(proj1) != vcount1) { + exit(10); + } + if (proj1 && igraph_ecount(proj1) != ecount1) { + exit(11); + } + if (proj2 && igraph_vcount(proj2) != vcount2) { + exit(12); + } + if (proj2 && igraph_ecount(proj2) != ecount2) { + exit(13); + } + return 0; +} + +int main(void) { + + igraph_t g, p1, p2, full, ring; + igraph_vector_bool_t types; + igraph_bool_t iso; + igraph_int_t i, m2 = 0, w, f, t; + igraph_vector_int_t mult1, mult2; + + /*******************************************************/ + /* Full bipartite graph -> full graphs */ + /*******************************************************/ + + igraph_vector_bool_init(&types, 0); + igraph_full_bipartite(&g, &types, 5, 3, /*directed=*/ 0, + /*mode=*/ IGRAPH_ALL); + + /* Get both projections */ + igraph_bipartite_projection(&g, &types, &p1, &p2, 0, 0, /*probe1=*/ -1); + check_projection(&g, &types, &p1, &p2); + + /* Check first projection */ + igraph_full(&full, igraph_vcount(&p1), /*directed=*/0, /*loops=*/0); + igraph_isomorphic_bliss(&p1, &full, 0, 0, &iso, 0, 0, + IGRAPH_BLISS_FM, 0, 0); + if (!iso) { + return 1; + } + igraph_destroy(&full); + + /* Check second projection */ + igraph_full(&full, igraph_vcount(&p2), /*directed=*/0, /*loops=*/0); + igraph_isomorphic_bliss(&p2, &full, 0, 0, &iso, 0, 0, + IGRAPH_BLISS_FM, 0, 0); + if (!iso) { + return 2; + } + igraph_destroy(&full); + + igraph_destroy(&p1); + igraph_destroy(&p2); + igraph_destroy(&g); + igraph_vector_bool_destroy(&types); + + /*******************************************************/ + /* More sophisticated test */ + /*******************************************************/ + + igraph_ring(&g, 100, /*directed=*/ 1, /*mutual=*/ 1, + /*circular=*/ 1); + igraph_vector_bool_init(&types, igraph_vcount(&g)); + for (i = 0; i < igraph_vector_bool_size(&types); i++) { + VECTOR(types)[i] = i % 2 ? 0 : 1; + } + + /* Get both projections */ + igraph_bipartite_projection(&g, &types, &p1, &p2, 0, 0, /*probe1=*/ -1); + check_projection(&g, &types, &p1, &p2); + + /* Check first projection */ + igraph_ring(&ring, igraph_vcount(&g) / 2, /*directed=*/ 0, + /*mutual=*/ 0, /*circular=*/ 1); + igraph_isomorphic_bliss(&p1, &ring, 0, 0, &iso, 0, 0, + IGRAPH_BLISS_FM, 0, 0); + if (!iso) { + return 1; + } + + /* Check second projection */ + igraph_isomorphic_bliss(&p2, &ring, 0, 0, &iso, 0, 0, + IGRAPH_BLISS_FM, 0, 0); + if (!iso) { + return 2; + } + igraph_destroy(&ring); + + igraph_destroy(&p1); + igraph_destroy(&p2); + igraph_destroy(&g); + igraph_vector_bool_destroy(&types); + + /*******************************************************/ + /* Multiplicity test */ + /*******************************************************/ + + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 8, 1, 8, 2, 8, 3, 8, 4, 8, 4, 9, 5, 9, 6, 9, 7, 9, 0, 9, + -1); + igraph_vector_bool_init(&types, igraph_vcount(&g)); + igraph_vector_bool_fill(&types, true); + VECTOR(types)[8] = VECTOR(types)[9] = 0; + + igraph_vector_int_init(&mult1, 0); + igraph_vector_int_init(&mult2, 0); + igraph_bipartite_projection(&g, &types, &p1, &p2, &mult1, &mult2, + /*probe=*/ -1); + check_projection(&g, &types, &p1, &p2); + + if (igraph_vector_int_size(&mult1) != igraph_ecount(&p1)) { + return 21; + } + if (igraph_vector_int_size(&mult2) != igraph_ecount(&p2)) { + return 22; + } + if (VECTOR(mult1)[0] != 2) { + return 23; + } + for (i = 0; i < igraph_vector_int_size(&mult2); i++) { + if (VECTOR(mult2)[i] != 1 && VECTOR(mult2)[i] != 2) { + return 24; + } + if (VECTOR(mult2)[i] == 2) { + m2++; + w = i; + } + } + if (m2 != 1) { + return 25; + } + f = IGRAPH_FROM(&p2, w); + t = IGRAPH_TO(&p2, w); + if (fmin(f, t) != 0 || fmax(f, t) != 4) { + return 26; + } + + igraph_vector_int_destroy(&mult1); + igraph_vector_int_destroy(&mult2); + igraph_destroy(&p1); + igraph_destroy(&p2); + igraph_destroy(&g); + igraph_vector_bool_destroy(&types); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_blas_dgemm.c b/tests/unit/igraph_blas_dgemm.c new file mode 100644 index 0000000..885e69c --- /dev/null +++ b/tests/unit/igraph_blas_dgemm.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_matrix_t a, b, c, d, e; + + igraph_matrix_init(&c, 0, 0); + + igraph_real_t elemA[] = {1, 2, 3, 4}; + igraph_real_t elemB[] = {5, 6, 7, 8}; + igraph_real_t elemD[] = {5, 6, 7, 8, 9, 10}; + matrix_init_real_row_major(&a, 2, 2, elemA); + matrix_init_real_row_major(&b, 2, 2, elemB); + matrix_init_real_row_major(&d, 2, 3, elemD); + matrix_init_real_row_major(&e, 3, 2, elemD); + + printf("matrix multiplication, A={{1,2},{3,4}} B ={{5,6},{7,8}}\n"); + igraph_blas_dgemm(0, 0, 1, &a, &b, 0, &c); + igraph_matrix_print(&c); + + printf("transpose a first\n"); + igraph_blas_dgemm(1, 0, 1, &a, &b, 0, &c); + igraph_matrix_print(&c); + + printf("transpose b first\n"); + igraph_blas_dgemm(0, 1, 1, &a, &b, 0, &c); + igraph_matrix_print(&c); + + printf("transpose both matrices first\n"); + igraph_blas_dgemm(1, 1, 1, &a, &b, 0, &c); + igraph_matrix_print(&c); + + printf("multiply by 0.5\n"); + igraph_blas_dgemm(0, 0, 0.5, &a, &b, 0, &c); + igraph_matrix_print(&c); + + printf("multiply by 1.5 by using previous result\n"); + igraph_blas_dgemm(0, 0, 1, &a, &b, 1, &c); + igraph_matrix_print(&c); + + printf("matrix multiplication, A={{1,2},{3,4}} B={{5,6,7},{8,9,10}}\n"); + igraph_blas_dgemm(0, 0, 1, &a, &d, 0, &c); + igraph_matrix_print(&c); + + printf("matrix multiplication, A={{5,8},{6,9},{7,10}} B={{1,2},{3,4}}\n"); + igraph_blas_dgemm(1, 0, 1, &d, &a, 0, &c); + igraph_matrix_print(&c); + + printf("check error when matrix sizes don't match\n"); + CHECK_ERROR(igraph_blas_dgemm(0, 0, 1, &d, &a, 0, &c), IGRAPH_EINVAL); + CHECK_ERROR(igraph_blas_dgemm(0, 1, 1, &a, &d, 0, &c), IGRAPH_EINVAL); + CHECK_ERROR(igraph_blas_dgemm(0, 0, 1, &a, &b, 1, &d), IGRAPH_EINVAL); + CHECK_ERROR(igraph_blas_dgemm(0, 0, 1, &a, &b, 1, &e), IGRAPH_EINVAL); + + igraph_matrix_destroy(&a); + igraph_matrix_destroy(&b); + igraph_matrix_destroy(&c); + igraph_matrix_destroy(&d); + igraph_matrix_destroy(&e); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_blas_dgemm.out b/tests/unit/igraph_blas_dgemm.out new file mode 100644 index 0000000..51ab5b1 --- /dev/null +++ b/tests/unit/igraph_blas_dgemm.out @@ -0,0 +1,26 @@ +matrix multiplication, A={{1,2},{3,4}} B ={{5,6},{7,8}} +19 22 +43 50 +transpose a first +26 30 +38 44 +transpose b first +17 23 +39 53 +transpose both matrices first +23 31 +34 46 +multiply by 0.5 + 9.5 11 +21.5 25 +multiply by 1.5 by using previous result +28.5 33 +64.5 75 +matrix multiplication, A={{1,2},{3,4}} B={{5,6,7},{8,9,10}} +21 24 27 +47 54 61 +matrix multiplication, A={{5,8},{6,9},{7,10}} B={{1,2},{3,4}} +29 42 +33 48 +37 54 +check error when matrix sizes don't match diff --git a/tests/unit/igraph_bridges.c b/tests/unit/igraph_bridges.c new file mode 100644 index 0000000..c916912 --- /dev/null +++ b/tests/unit/igraph_bridges.c @@ -0,0 +1,59 @@ + +#include +#include + +#include "test_utilities.h" + +void sort_and_print_vector(igraph_vector_int_t *v) { + igraph_vector_int_sort(v); + print_vector_int(v); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_t bridges; + + igraph_vector_int_init(&bridges, 0); + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + igraph_small(&graph, /* num_nodes = */ 7, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 0, 2, 0, 3, 3, 4, 4, 5, 3, 5, 4, 6, -1); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + /* Test with disconnected graph. */ + igraph_small(&graph, /* num_nodes = */ 16, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 1, 3, 4, 5, 5, 6, 4, 6, 4, 7, 7, 8, 4, 8, 9, 10, 10, 11, + 11, 12, 9, 12, 9, 13, 13, 14, -1); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + /* Test with multi-edges and self-loops. */ + igraph_small(&graph, /* num_nodes = */ 3, IGRAPH_UNDIRECTED, + 0, 1, 0, 1, 1, 2, 2, 2, -1); + igraph_bridges(&graph, &bridges); + sort_and_print_vector(&bridges); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&bridges); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_bridges.out b/tests/unit/igraph_bridges.out new file mode 100644 index 0000000..4a7a3ce --- /dev/null +++ b/tests/unit/igraph_bridges.out @@ -0,0 +1,6 @@ +( ) +( ) +( ) +( 3 7 ) +( 0 1 2 13 14 ) +( 2 ) diff --git a/tests/unit/igraph_callaway_traits_game.c b/tests/unit/igraph_callaway_traits_game.c new file mode 100644 index 0000000..2765547 --- /dev/null +++ b/tests/unit/igraph_callaway_traits_game.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void init_vm(igraph_vector_t *type_dist, + int v0, int v1, + igraph_matrix_t *pref_matrix, + int m00, int m10, int m01, int m11) { + igraph_vector_init_int_end(type_dist, -1, v0, v1, -1); + igraph_matrix_init(pref_matrix, 2, 2); + MATRIX(*pref_matrix, 0, 0) = m00; + MATRIX(*pref_matrix, 1, 0) = m10; + MATRIX(*pref_matrix, 0, 1) = m01; + MATRIX(*pref_matrix, 1, 1) = m11; +} + +#define DESTROY_GVM() do { \ + igraph_destroy(&g); \ + igraph_vector_destroy(&type_dist); \ + igraph_matrix_destroy(&pref_matrix); \ + } while(0) + +int main(void) { + igraph_t g; + igraph_vector_t type_dist; + igraph_vector_int_t node_types; + igraph_matrix_t pref_matrix; + igraph_bool_t bipartite; + + igraph_rng_seed(igraph_rng_default(), 42); + + /*Zero matrix elements for only possible vertex type means no edges*/ + init_vm(&type_dist, 1, 0, &pref_matrix, 0, 0, 0, 1); + IGRAPH_ASSERT(igraph_callaway_traits_game(&g, /*nodes*/ 20, /*types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 0, NULL) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 20); + DESTROY_GVM(); + + /*No vertices*/ + init_vm(&type_dist, 1, 0, &pref_matrix, 0, 0, 0, 1); + IGRAPH_ASSERT(igraph_callaway_traits_game(&g, /*nodes*/ 0, /*types*/ 2, /*edges_per_step*/ 0, &type_dist, &pref_matrix, /*directed*/ 0, NULL) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + DESTROY_GVM(); + + /*Two types with only cross terms makes a bipartite graph*/ + init_vm(&type_dist, 2, 1, &pref_matrix, 0, 1, 1, 0); + igraph_vector_int_init(&node_types, 0); + IGRAPH_ASSERT(igraph_callaway_traits_game(&g, /*nodes*/ 20, /*types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 1, &node_types) == IGRAPH_SUCCESS); + igraph_is_bipartite(&g, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vector_int_size(&node_types) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&node_types) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&node_types) == 1); + igraph_vector_int_destroy(&node_types); + DESTROY_GVM(); + + /*Automatically determined type_dist*/ + init_vm(&type_dist, 0, 0, &pref_matrix, 0, 1, 1, 0); + igraph_vector_int_init(&node_types, 0); + IGRAPH_ASSERT(igraph_callaway_traits_game(&g, /*nodes*/ 20, /*types*/ 2, /*edges_per_step*/ 3, /*type_dist*/ NULL, &pref_matrix, /*directed*/ 0, &node_types) == IGRAPH_SUCCESS); + igraph_is_bipartite(&g, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vector_int_size(&node_types) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&node_types) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&node_types) == 1); + igraph_vector_int_destroy(&node_types); + DESTROY_GVM(); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /*Distribution of types should have at least one positive value*/ + init_vm(&type_dist, 0, 0, &pref_matrix, 0, 1, 1, 0); + IGRAPH_ASSERT(igraph_callaway_traits_game(&g, /*nodes*/ 20, /* types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 0, NULL) == IGRAPH_EINVAL); + DESTROY_GVM(); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_chung_lu_game.c b/tests/unit/igraph_chung_lu_game.c new file mode 100644 index 0000000..403c62f --- /dev/null +++ b/tests/unit/igraph_chung_lu_game.c @@ -0,0 +1,178 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_vector_t outdeg, indeg; + igraph_bool_t simple, multi; + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Zero-legth input */ + + igraph_vector_init(&outdeg, 0); + + igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &outdeg, true, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_destroy(&g); + + igraph_vector_destroy(&outdeg); + + /* ORIGINAL */ + + /* Must use floating point literals here! 2.0 instead of 2 */ + igraph_vector_init_real_end(&outdeg, -1.0, + 1.0, 0.0, 2.5, 2.0, 3.0, 2.0, 1.5, + -1.0); + igraph_vector_init_real_end(&indeg, -1.0, + 2.0, 2.0, 2.0, 2.0, 0.0, 2.0, 2.0, + -1.0); + + igraph_chung_lu_game(&g, &outdeg, NULL, false, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, false, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, true, IGRAPH_CHUNG_LU_ORIGINAL); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + igraph_vector_destroy(&indeg); + igraph_vector_destroy(&outdeg); + + /* GRG */ + + /* Must use floating point literals here! 2.0 instead of 2 */ + igraph_vector_init_real_end(&outdeg, -1.0, + 189.0, 0.0, 2.5, 12.0, 3.0, 2.0, 1.5, + -1.0); + igraph_vector_init_real_end(&indeg, -1.0, + 2.0, 2.0, 2.0, 2.0, 0.0, 200.0, 2.0, + -1.0); + + igraph_chung_lu_game(&g, &outdeg, NULL, false, IGRAPH_CHUNG_LU_MAXENT); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_MAXENT); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, false, IGRAPH_CHUNG_LU_MAXENT); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, true, IGRAPH_CHUNG_LU_MAXENT); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + /* NR */ + + igraph_chung_lu_game(&g, &outdeg, NULL, false, IGRAPH_CHUNG_LU_NR); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_NR); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, false, IGRAPH_CHUNG_LU_NR); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&g); + + igraph_chung_lu_game(&g, &outdeg, &indeg, true, IGRAPH_CHUNG_LU_NR); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == igraph_vector_size(&outdeg)); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(!multi); + igraph_destroy(&g); + + /* Invalid input */ + + /* Bad variant */ + CHECK_ERROR(igraph_chung_lu_game(&g, &outdeg, &indeg, true, (igraph_chung_lu_t) -1), IGRAPH_EINVAL); + + /* Inconsistent sum */ + VECTOR(outdeg)[0] = 0; + CHECK_ERROR(igraph_chung_lu_game(&g, &outdeg, &indeg, true, IGRAPH_CHUNG_LU_ORIGINAL), IGRAPH_EINVAL); + + /* Negative weight */ + VECTOR(outdeg)[0] = -1; + CHECK_ERROR(igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_ORIGINAL), IGRAPH_EINVAL); + + /* Non-finite weight */ + VECTOR(outdeg)[0] = IGRAPH_INFINITY; + CHECK_ERROR(igraph_chung_lu_game(&g, &outdeg, NULL, true, IGRAPH_CHUNG_LU_ORIGINAL), IGRAPH_EINVAL); + + igraph_vector_destroy(&indeg); + igraph_vector_destroy(&outdeg); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_circulant.c b/tests/unit/igraph_circulant.c new file mode 100644 index 0000000..b523a24 --- /dev/null +++ b/tests/unit/igraph_circulant.c @@ -0,0 +1,178 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t graph, graph_test; + igraph_vector_int_t shifts; + igraph_bool_t iso, same; + igraph_int_t i; + + /* Testing invalid values */ + + /* n = -3, shifts = [1], undirected */ + igraph_vector_int_init_int(&shifts, 1, 1); + CHECK_ERROR(igraph_circulant(&graph, -3, &shifts, IGRAPH_UNDIRECTED), IGRAPH_EINVAL); + igraph_vector_int_destroy(&shifts); + + /* Testing n = 0 case */ + /* n = 0, shifts = [1], undirected */ + igraph_vector_int_init_int(&shifts, 1, 1); + IGRAPH_ASSERT(igraph_circulant(&graph, 0, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + IGRAPH_ASSERT(igraph_ecount(&graph) == 0); + igraph_vector_int_destroy(&shifts); + igraph_destroy(&graph); + + /* Testing n = 1 case */ + /* n = 1, shifts = [1], undirected */ + igraph_vector_int_init_int(&shifts, 1, 1); + IGRAPH_ASSERT(igraph_circulant(&graph, 1, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 1, IGRAPH_UNDIRECTED, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&graph, &graph_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing n = 2 case */ + /* n = 2, shifts = [1], undirected */ + igraph_vector_int_init_int(&shifts, 1, 1); + IGRAPH_ASSERT(igraph_circulant(&graph, 2, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&graph, &graph_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing empty list case */ + /* n = 5, shifts = [], undirected */ + igraph_vector_int_init(&shifts, 0); + IGRAPH_ASSERT(igraph_circulant(&graph, 5, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 5, IGRAPH_UNDIRECTED, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&graph, &graph_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing typical use case */ + /* Test n = 5, shifts = [1, 2], undirected */ + igraph_vector_int_init_int(&shifts, 2, 1, 2); + IGRAPH_ASSERT(igraph_circulant(&graph, 5, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 2, + 2, 4, 4, 1, 1, 3, 3, 0, -1); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing simplification */ + /* n = 6, shifts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], undirected */ + igraph_vector_int_init(&shifts, 0); + for (i = 1; i <= 15; i++) { + igraph_vector_int_push_back(&shifts, i); + } + IGRAPH_ASSERT(igraph_circulant(&graph, 6, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_full(&graph_test, 6, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing simplification with negative values */ + /* n = 7, shifts = [-15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, + -4, -3, -2, -1, 0, 1, 2, 3, 4], undirected */ + igraph_vector_int_init(&shifts, 0); + for (i = -15; i <= 4; i++) { + igraph_vector_int_push_back(&shifts, i); + } + IGRAPH_ASSERT(igraph_circulant(&graph, 7, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_full(&graph_test, 7, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing simplification when n is even, the offset = n/2, and the graph is undirected */ + /* n = 6, shifts = [3], undirected */ + igraph_vector_int_init_int(&shifts, 1, 3); + IGRAPH_ASSERT(igraph_circulant(&graph, 6, &shifts, IGRAPH_UNDIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 6, IGRAPH_UNDIRECTED, 0, 3, 1, 4, 2, 5, -1); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing directed graph */ + /* n = 7, shifts = [1, -2], directed */ + igraph_vector_int_init_int(&shifts, 2, 1, -2); + IGRAPH_ASSERT(igraph_circulant(&graph, 7, &shifts, IGRAPH_DIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 7, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, + 0, 5, 1, 6, 2, 0, 3, 1, 4, 2, 5, 3, 6, 4, -1); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing simplification works for directed too */ + /* n = 5, shifts = [-4, 1, 6], directed */ + igraph_vector_int_init_int(&shifts, 3, -4, 1, 6); + IGRAPH_ASSERT(igraph_circulant(&graph, 5, &shifts, IGRAPH_DIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, -1); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* Testing that things that are normally simplified for undirected are not simplified for directed */ + /* n = 5, shifts = [1, -1], directed */ + igraph_vector_int_init_int(&shifts, 2, 1, -1); + IGRAPH_ASSERT(igraph_circulant(&graph, 5, &shifts, IGRAPH_DIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, + 1, 0, 2, 1, 3, 2, 4, 3, 0, 4, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&graph, &graph_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + /* n = 6, shifts = [3], directed */ + igraph_vector_int_init_int(&shifts, 1, 3); + IGRAPH_ASSERT(igraph_circulant(&graph, 6, &shifts, IGRAPH_DIRECTED) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 6, IGRAPH_DIRECTED, 0, 3, 1, 4, 2, 5, 3, 0, 4, 1, 5, 2, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&graph, &graph_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + igraph_vector_int_destroy(&shifts); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_cited_type_game.c b/tests/unit/igraph_cited_type_game.c new file mode 100644 index 0000000..9c37eba --- /dev/null +++ b/tests/unit/igraph_cited_type_game.c @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_t pref; + igraph_vector_int_t types; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No nodes:\n"); + igraph_vector_init_int(&pref, 2, 1, 1); + igraph_vector_int_init_int(&types, 0); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 0, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("No edges:\n"); + igraph_vector_init_int(&pref, 2, 1, 1); + igraph_vector_int_init_int(&types, 3, 1, 1, 1); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 3, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 0, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Make a star of double edges:\n"); + igraph_vector_init_real(&pref, 3, 1.0, 0.0, 0.0); + igraph_vector_int_init_int(&types, 5, 0, 1, 1, 1, 1); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 5, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 2, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Make a line:\n"); + igraph_vector_init_real(&pref, 7, 1.0e-30, 1.0e-20, 1.0e-10, 1.0, 1.0e+10, 1.0e+20, 0.0); + igraph_vector_int_init_int(&types, 7, 0, 1, 2, 3, 4, 5, 6); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 7, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 1, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking negative number of nodes error handling.\n"); + igraph_vector_init_real(&pref, 2, 1.0, 1.0); + igraph_vector_int_init_int(&types, 2, 0, 1); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ -5, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Checking too few types error handling.\n"); + igraph_vector_init_real(&pref, 1, 1.0); + igraph_vector_int_init_int(&types, 0); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 1, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Checking too many types error handling.\n"); + igraph_vector_init_real(&pref, 3, 1.0, 1.0, 1.0); + igraph_vector_int_init_int(&types, 2, 0, 1); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 1, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Checking negative type for error handling.\n"); + igraph_vector_init_real(&pref, 2, 1.0, 1.0); + igraph_vector_int_init_int(&types, 2, 0, -5); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 2, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Checking too big type for error handling.\n"); + igraph_vector_init_real(&pref, 2, 1.0, 1.0); + igraph_vector_int_init_int(&types, 2, 0, 5); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 2, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + printf("Checking negative preference error handling.\n"); + igraph_vector_init_real(&pref, 2, 1.0, -1.0); + igraph_vector_int_init_int(&types, 2, 0, 1); + IGRAPH_ASSERT(igraph_cited_type_game(&g, /*nodes*/ 2, /*types*/ &types, /*pref*/ &pref, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&pref); + igraph_vector_int_destroy(&types); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_cited_type_game.out b/tests/unit/igraph_cited_type_game.out new file mode 100644 index 0000000..4d9fc0c --- /dev/null +++ b/tests/unit/igraph_cited_type_game.out @@ -0,0 +1,40 @@ +No nodes: +directed: false +vcount: 0 +edges: { +} +No edges: +directed: false +vcount: 3 +edges: { +} +Make a star of double edges: +directed: true +vcount: 5 +edges: { +1 0 +1 0 +2 0 +2 0 +3 0 +3 0 +4 0 +4 0 +} +Make a line: +directed: true +vcount: 7 +edges: { +1 0 +2 1 +3 2 +4 3 +5 4 +6 5 +} +Checking negative number of nodes error handling. +Checking too few types error handling. +Checking too many types error handling. +Checking negative type for error handling. +Checking too big type for error handling. +Checking negative preference error handling. diff --git a/tests/unit/igraph_citing_cited_type_game.c b/tests/unit/igraph_citing_cited_type_game.c new file mode 100644 index 0000000..20af658 --- /dev/null +++ b/tests/unit/igraph_citing_cited_type_game.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t pref_empty, pref_bipartite, pref_line; + igraph_vector_int_t types_empty, types_bipartite, types_line; + igraph_bool_t bipartite; + int bipartite_elem[] = {0, 1, 1, 0}; + int line_elem[] = {0, 0, 1, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + }; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&pref_empty, 0, 0); + igraph_vector_int_init(&types_empty, 0); + + matrix_init_int_row_major(&pref_bipartite, 2, 2, bipartite_elem); + igraph_vector_int_init_int(&types_bipartite, 10, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0); + + matrix_init_int_row_major(&pref_line, 5, 5, line_elem); + igraph_vector_int_init_int(&types_line, 5, 0, 1, 2, 3, 4); + + printf("No nodes.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 0, &types_empty, &pref_empty, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + printf("Bipartite graph.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 10, &types_bipartite, &pref_bipartite, /*edges_per_step*/ 5, /*directed*/ 0) == IGRAPH_SUCCESS); + igraph_is_bipartite(&g, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 45); + igraph_destroy(&g); + + printf("No edges.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 10, &types_bipartite, &pref_bipartite, /*edges_per_step*/ 0, /*directed*/ 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + printf("A line.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 5, &types_line, &pref_line, /*edges_per_step*/ 1, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Too few types for nodes.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 5, &types_empty, &pref_empty, /*edges_per_step*/ 1, /*directed*/ 1) == IGRAPH_EINVAL); + + printf("Too few prefs.\n"); + IGRAPH_ASSERT(igraph_citing_cited_type_game(&g, /*nodes*/ 5, &types_line, &pref_empty, /*edges_per_step*/ 1, /*directed*/ 1) == IGRAPH_EINVAL); + + igraph_matrix_destroy(&pref_empty); + igraph_vector_int_destroy(&types_empty); + igraph_matrix_destroy(&pref_bipartite); + igraph_vector_int_destroy(&types_bipartite); + igraph_matrix_destroy(&pref_line); + igraph_vector_int_destroy(&types_line); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_citing_cited_type_game.out b/tests/unit/igraph_citing_cited_type_game.out new file mode 100644 index 0000000..712f939 --- /dev/null +++ b/tests/unit/igraph_citing_cited_type_game.out @@ -0,0 +1,14 @@ +No nodes. +Bipartite graph. +No edges. +A line. +directed: true +vcount: 5 +edges: { +1 0 +2 1 +3 2 +4 3 +} +Too few types for nodes. +Too few prefs. diff --git a/tests/unit/igraph_clique_size_hist.c b/tests/unit/igraph_clique_size_hist.c new file mode 100644 index 0000000..42a094d --- /dev/null +++ b/tests/unit/igraph_clique_size_hist.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, int min, int max) { + igraph_vector_t result; + igraph_vector_init(&result, 0); + IGRAPH_ASSERT(igraph_clique_size_hist(g, &result, min, max) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&result); +} + + +int main(void) { + igraph_t g_empty, g_lm; + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + print_and_destroy(&g_empty, 0, 0); + + printf("Graph with loops and multiple edges:\n"); + print_and_destroy(&g_lm, 0, 0); + + printf("Same graph, minimum clique size 2:\n"); + print_and_destroy(&g_lm, 2, 0); + + printf("Same graph, maximum clique size 2:\n"); + print_and_destroy(&g_lm, 0, 2); + + printf("Same graph, minimum and maximum clique size 10:\n"); + print_and_destroy(&g_lm, 10, 10); + + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_clique_size_hist.out b/tests/unit/igraph_clique_size_hist.out new file mode 100644 index 0000000..8c968ee --- /dev/null +++ b/tests/unit/igraph_clique_size_hist.out @@ -0,0 +1,10 @@ +No vertices: +( ) +Graph with loops and multiple edges: +( 6 6 2 ) +Same graph, minimum clique size 2: +( 0 6 2 ) +Same graph, maximum clique size 2: +( 6 6 ) +Same graph, minimum and maximum clique size 10: +( ) diff --git a/tests/unit/igraph_closeness.c b/tests/unit/igraph_closeness.c new file mode 100644 index 0000000..116b333 --- /dev/null +++ b/tests/unit/igraph_closeness.c @@ -0,0 +1,345 @@ +#include +#include +#include "test_utilities.h" + + +void simple_test_case_no_weights_undirected(void) { + + igraph_t g; + igraph_vector_t vector_actual_results; + + printf("Simple test case, no weights, undirected\n"); + + igraph_vector_init(&vector_actual_results, 0); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 0,1 , 1,2, -1); + + /* NOT NORMALISED TEST BELOW */ + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + NULL /*unweighted*/, /*not normalised*/ 0); + + printf("Non normalised results below\n"); + print_vector(&vector_actual_results); + + /* NORMALISED TEST BELOW */ + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + NULL, /*normalised*/ 1); + + printf("\nNormalised results below\n"); + print_vector(&vector_actual_results); + + igraph_vector_destroy(&vector_actual_results); + igraph_destroy(&g); +} + +void simple_test_case_with_weights_undirected(void) { + + igraph_t g; + igraph_vector_int_t vector_edges; + igraph_vector_t vector_weights, vector_actual_results; + + igraph_int_t real_edges[] = {0,1 , 1,2}; + igraph_real_t real_weights[] = {3, 5}; + + printf("\nSimple test case, with weights, undirected\n"); + + igraph_vector_init(&vector_actual_results, 0); + vector_edges = igraph_vector_int_view(real_edges, sizeof(real_edges)/sizeof(real_edges[0])); + igraph_create(&g, &vector_edges, /*number of vertices*/ 2, IGRAPH_DIRECTED); + + vector_weights = igraph_vector_view(real_weights, sizeof(real_weights)/sizeof(real_weights[0])); + + /* NOT NORMALISED TEST BELOW */ + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + &vector_weights, /*not normalised*/ 0); + + printf("Non normalised test below\n"); + + print_vector(&vector_actual_results); + + /* NORMALISED TEST BELOW */ + + printf("\nNormalised test below\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + &vector_weights, /*normalised*/ 1); + + print_vector(&vector_actual_results); + + igraph_vector_destroy(&vector_actual_results); + igraph_destroy(&g); +} + +void advanced_test_case_no_weights_undirected(void) { + + igraph_t g; + igraph_vector_t vector_actual_results; + + /* note, denominatory calculated as (shortest dist)*(n-1) for no normalisation + normalisation excludes n-1 as part of the denominator */ + + printf("\nAdvanced test case, no weights, undirected\n"); + + igraph_vector_init(&vector_actual_results, 0); + + igraph_small(&g, 0, IGRAPH_DIRECTED, 1,0 , 0,5 , 5,6 , 5,4, 4,1 , 1,2 , 2,4 , 4,6 , + 2,3 , 3,7 , 7,6 , 2,6 , -1); + + /* NOT NORMALISED TEST BELOW*/ + + printf("Non normalised test below\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + NULL, /*not normalised*/ 0); + + print_vector(&vector_actual_results); + + /* NORMALISED TEST BELOW*/ + + printf("\nNormalised test below\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + NULL, /*normalised*/ 1); + + print_vector(&vector_actual_results); + + igraph_vector_destroy(&vector_actual_results); + igraph_destroy(&g); +} + +void advanced_test_case_with_weights(void) { + + igraph_t g; + igraph_vector_int_t vector_edges; + igraph_vector_t vector_weights, vector_actual_results; + + igraph_int_t real_edges[] = {1,0 , 0,5 , 5,6 , 5,4, 4,1 , 1,2 , 2,4 , 4,6 , + 2,3 , 3,7 , 7,6 , 6,2}; + + igraph_real_t real_weights[] = {4, 9, 2, 2, 2, 3, 1, 1, 8, 7, 5, 5}; + + printf("\nAdvanced test case, with weights\n"); + + + igraph_vector_init(&vector_actual_results, 0); + vector_edges = igraph_vector_int_view(real_edges, sizeof(real_edges) / sizeof(real_edges[0])); + igraph_create(&g, &vector_edges, /*number of vertices*/ 2, IGRAPH_DIRECTED); + + vector_weights = igraph_vector_view(real_weights, sizeof(real_weights) / sizeof(real_weights[0])); + + /* TEST FOR UNDIRECTED GRAPH */ + + printf("Undirected graph test below\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_ALL /*graph is "undirected"*/, + &vector_weights, /*not normalised*/ 0); + + print_vector(&vector_actual_results); + + /* TEST FOR DIRECTED GRAPH + OUT means the min distance from the curr node to the other node */ + + printf("\nDirected graph test below for OUT\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_OUT /*graph is "out directed"*/, + &vector_weights, /*not normalised*/ 0); + + print_vector(&vector_actual_results); + + /* IN means the min distance from a node to the curr node */ + + printf("\nDirected graph test below for IN\n"); + + igraph_closeness(&g, &vector_actual_results /*store results here*/, + NULL, NULL, + /*calculating for all vectors in the graph*/ igraph_vss_all(), + IGRAPH_IN /*graph is "in directed"*/, + &vector_weights, /*not normalised*/ 0); + + print_vector(&vector_actual_results); + + igraph_vector_destroy(&vector_actual_results); + igraph_destroy(&g); +} + +void test_cutoff(void) { + + igraph_t g; + igraph_vector_t closeness; + igraph_vector_int_t reachable; + igraph_bool_t all_reachable; + size_t i; + igraph_real_t cutoff_vec[] = { -1.0, 0.0, 1.0, 2.9, 3.0, 3.1 }; + + printf("\n\nUnweighted undirected with cutoff\n"); + + igraph_ring(&g, 4, IGRAPH_UNDIRECTED, 0, 0); + + igraph_vector_init(&closeness, 0); + igraph_vector_int_init(&reachable, 0); + + for (i=0; i < sizeof(cutoff_vec) / sizeof(cutoff_vec[0]); ++i) { + printf("\nRange-limited closeness with cutoff %g\n", cutoff_vec[i]); + igraph_closeness_cutoff(&g, &closeness, &reachable, &all_reachable, + igraph_vss_all(), IGRAPH_ALL, NULL, /* normalized */ 1, + cutoff_vec[i]); + printf("Closeness: "); + print_vector(&closeness); + printf("Reachable: "); + print_vector_int(&reachable); + printf("All reachable: %s\n", all_reachable ? "true" : "false"); + } + + igraph_vector_int_destroy(&reachable); + igraph_vector_destroy(&closeness); + + igraph_destroy(&g); +} + +void test_cutoff_directed(void) { + + igraph_t g; + igraph_vector_t closeness; + igraph_vector_int_t reachable; + igraph_bool_t all_reachable; + size_t i; + igraph_real_t cutoff_vec[] = { -1.0, 0.0, 1.0, 2.9, 3.0, 3.1 }; + + printf("\n\nUnweighted directed with cutoff\n"); + + igraph_ring(&g, 4, IGRAPH_DIRECTED, 0, 0); + + igraph_vector_init(&closeness, 0); + igraph_vector_int_init(&reachable, 0); + + for (i=0; i < sizeof(cutoff_vec) / sizeof(cutoff_vec[0]); ++i) { + printf("\nRange-limited directed closeness with cutoff %g\n", cutoff_vec[i]); + igraph_closeness_cutoff(&g, &closeness, &reachable, &all_reachable, + igraph_vss_all(), IGRAPH_OUT, NULL, /* normalized */ 1, + cutoff_vec[i]); + printf("Closeness: "); + print_vector(&closeness); + printf("Reachable: "); + print_vector_int(&reachable); + printf("All reachable: %s\n", all_reachable ? "true" : "false"); + } + + igraph_vector_int_destroy(&reachable); + igraph_vector_destroy(&closeness); + + igraph_destroy(&g); +} + +void test_cutoff_weighted(void) { + + igraph_t g; + igraph_vector_t closeness; + igraph_vector_int_t reachable; + igraph_bool_t all_reachable; + size_t i; + igraph_real_t cutoff_vec[] = { -1.0, 0.0, 1.0, 2.9, 3.0, 5.0, 6.0 }; + igraph_vector_t weights; + + printf("\n\nWeighted undirected with cutoff\n"); + + igraph_ring(&g, 4, IGRAPH_UNDIRECTED, 0, 0); + + igraph_vector_init(&closeness, 0); + igraph_vector_int_init(&reachable, 0); + igraph_vector_init_range(&weights, 1, 4); + + for (i=0; i < sizeof(cutoff_vec) / sizeof(cutoff_vec[0]); ++i) { + printf("\nRange-limited weighted closeness with cutoff %g\n", cutoff_vec[i]); + igraph_closeness_cutoff(&g, &closeness, &reachable, &all_reachable, + igraph_vss_all(), IGRAPH_ALL, &weights, /* normalized */ 1, + cutoff_vec[i]); + printf("Closeness: "); + print_vector(&closeness); + printf("Reachable: "); + print_vector_int(&reachable); + printf("All reachable: %s\n", all_reachable ? "true" : "false"); + } + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&reachable); + igraph_vector_destroy(&closeness); + + igraph_destroy(&g); +} + +void test_edge_cases(void) { + + igraph_t g; + igraph_vector_t closeness; + igraph_vector_int_t reachable; + igraph_bool_t all_reachable; + int n; + + printf("\n\nEdgeless graphs\n"); + + igraph_vector_init(&closeness, 0); + igraph_vector_int_init(&reachable, 0); + + for (n=0; n <= 2; ++n) { + printf("\nEdgeless graph with %d vertices\n", n); + igraph_empty(&g, n, IGRAPH_UNDIRECTED); + + igraph_closeness(&g, &closeness, &reachable, &all_reachable, igraph_vss_all(), IGRAPH_ALL, NULL, 1); + printf("Closeness: "); + print_vector(&closeness); + printf("Reachable: "); + print_vector_int(&reachable); + printf("All reachable: %s\n", all_reachable ? "true" : "false"); + + igraph_destroy(&g); + } + + igraph_vector_int_destroy(&reachable); + igraph_vector_destroy(&closeness); + +} + +int main(void) { + + simple_test_case_no_weights_undirected(); + simple_test_case_with_weights_undirected(); + advanced_test_case_no_weights_undirected(); + advanced_test_case_with_weights(); + + test_cutoff(); + test_cutoff_directed(); + test_cutoff_weighted(); + + test_edge_cases(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_closeness.out b/tests/unit/igraph_closeness.out new file mode 100644 index 0000000..72b89a9 --- /dev/null +++ b/tests/unit/igraph_closeness.out @@ -0,0 +1,152 @@ +Simple test case, no weights, undirected +Non normalised results below +( 0.333333 0.5 0.333333 ) + +Normalised results below +( 0.666667 1 0.666667 ) + +Simple test case, with weights, undirected +Non normalised test below +( 0.0909091 0.125 0.0769231 ) + +Normalised test below +( 0.181818 0.25 0.153846 ) + +Advanced test case, no weights, undirected +Non normalised test below +( 0.0714286 0.0833333 0.1 0.0714286 0.1 0.0833333 0.1 0.0714286 ) + +Normalised test below +( 0.5 0.583333 0.7 0.5 0.7 0.583333 0.7 0.5 ) + +Advanced test case, with weights +Undirected graph test below +( 0.0169492 0.0285714 0.0322581 0.0140845 0.037037 0.027027 0.0333333 0.0192308 ) + +Directed graph test below for OUT +( 0.00869565 0.0172414 0.0192308 0.00763359 0.016129 0.0166667 0.0117647 0.01 ) + +Directed graph test below for IN +( 0.0128205 0.015873 0.015873 0.00980392 0.0188679 0.0075188 0.0263158 0.0075188 ) + + +Unweighted undirected with cutoff + +Range-limited closeness with cutoff -1 +Closeness: ( 0.5 0.75 0.75 0.5 ) +Reachable: ( 3 3 3 3 ) +All reachable: true + +Range-limited closeness with cutoff 0 +Closeness: ( NaN NaN NaN NaN ) +Reachable: ( 0 0 0 0 ) +All reachable: false + +Range-limited closeness with cutoff 1 +Closeness: ( 1 1 1 1 ) +Reachable: ( 1 2 2 1 ) +All reachable: false + +Range-limited closeness with cutoff 2.9 +Closeness: ( 0.666667 0.75 0.75 0.666667 ) +Reachable: ( 2 3 3 2 ) +All reachable: false + +Range-limited closeness with cutoff 3 +Closeness: ( 0.5 0.75 0.75 0.5 ) +Reachable: ( 3 3 3 3 ) +All reachable: true + +Range-limited closeness with cutoff 3.1 +Closeness: ( 0.5 0.75 0.75 0.5 ) +Reachable: ( 3 3 3 3 ) +All reachable: true + + +Unweighted directed with cutoff + +Range-limited directed closeness with cutoff -1 +Closeness: ( 0.5 0.666667 1 NaN ) +Reachable: ( 3 2 1 0 ) +All reachable: false + +Range-limited directed closeness with cutoff 0 +Closeness: ( NaN NaN NaN NaN ) +Reachable: ( 0 0 0 0 ) +All reachable: false + +Range-limited directed closeness with cutoff 1 +Closeness: ( 1 1 1 NaN ) +Reachable: ( 1 1 1 0 ) +All reachable: false + +Range-limited directed closeness with cutoff 2.9 +Closeness: ( 0.666667 0.666667 1 NaN ) +Reachable: ( 2 2 1 0 ) +All reachable: false + +Range-limited directed closeness with cutoff 3 +Closeness: ( 0.5 0.666667 1 NaN ) +Reachable: ( 3 2 1 0 ) +All reachable: false + +Range-limited directed closeness with cutoff 3.1 +Closeness: ( 0.5 0.666667 1 NaN ) +Reachable: ( 3 2 1 0 ) +All reachable: false + + +Weighted undirected with cutoff + +Range-limited weighted closeness with cutoff -1 +Closeness: ( 0.3 0.375 0.375 0.214286 ) +Reachable: ( 3 3 3 3 ) +All reachable: true + +Range-limited weighted closeness with cutoff 0 +Closeness: ( NaN NaN NaN NaN ) +Reachable: ( 0 0 0 0 ) +All reachable: false + +Range-limited weighted closeness with cutoff 1 +Closeness: ( 1 1 NaN NaN ) +Reachable: ( 1 1 0 0 ) +All reachable: false + +Range-limited weighted closeness with cutoff 2.9 +Closeness: ( 1 0.666667 0.5 NaN ) +Reachable: ( 1 2 1 0 ) +All reachable: false + +Range-limited weighted closeness with cutoff 3 +Closeness: ( 0.5 0.666667 0.375 0.333333 ) +Reachable: ( 2 2 3 1 ) +All reachable: false + +Range-limited weighted closeness with cutoff 5 +Closeness: ( 0.5 0.375 0.375 0.25 ) +Reachable: ( 2 3 3 2 ) +All reachable: false + +Range-limited weighted closeness with cutoff 6 +Closeness: ( 0.3 0.375 0.375 0.214286 ) +Reachable: ( 3 3 3 3 ) +All reachable: true + + +Edgeless graphs + +Edgeless graph with 0 vertices +Closeness: ( ) +Reachable: ( ) +All reachable: true + +Edgeless graph with 1 vertices +Closeness: ( NaN ) +Reachable: ( 0 ) +All reachable: true + +Edgeless graph with 2 vertices +Closeness: ( NaN NaN ) +Reachable: ( 0 0 ) +All reachable: false diff --git a/tests/unit/igraph_cohesion.c b/tests/unit/igraph_cohesion.c new file mode 100644 index 0000000..7a1d195 --- /dev/null +++ b/tests/unit/igraph_cohesion.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_int_t value; + igraph_bool_t checks = 1; + + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, 1, 6, 6, 3, 5, 0, -1); + + igraph_cohesion(&g, &value, checks); + + IGRAPH_ASSERT(value == 1); + + igraph_destroy(&g); + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, 1, 6, 6, 3, -1); + + igraph_cohesion(&g, &value, checks); + + IGRAPH_ASSERT(value == 2); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_cohesive_blocks.c b/tests/unit/igraph_cohesive_blocks.c new file mode 100644 index 0000000..e717a6a --- /dev/null +++ b/tests/unit/igraph_cohesive_blocks.c @@ -0,0 +1,125 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void doit(igraph_t *g) { + + igraph_vector_int_list_t blocks; + igraph_vector_int_t cohesion; + igraph_vector_int_t parent; + igraph_t block_tree; + igraph_int_t i; + + igraph_vector_int_list_init(&blocks, 0); + igraph_vector_int_init(&cohesion, 0); + igraph_vector_int_init(&parent, 0); + + igraph_cohesive_blocks(g, &blocks, &cohesion, &parent, + &block_tree); + + printf("Blocks:\n"); + for (i = 0; i < igraph_vector_int_list_size(&blocks); i++) { + igraph_vector_int_t *sg = igraph_vector_int_list_get_ptr(&blocks, i); + printf(" "); + igraph_vector_int_print(sg); + } + printf("Cohesion:\n "); + igraph_vector_int_print(&cohesion); + printf("Parents:\n "); + igraph_vector_int_print(&parent); + printf("Block graph:\n"); + igraph_write_graph_edgelist(&block_tree, stdout); + + igraph_vector_int_list_destroy(&blocks); + igraph_vector_int_destroy(&cohesion); + igraph_vector_int_destroy(&parent); + igraph_destroy(&block_tree); + +} + +int main(void) { + + igraph_t g; + + printf("The graph from the Moody-White paper:\n"); + igraph_small(&g, 23, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 1, 2, 1, 3, 1, 4, 1, 6, + 2, 3, 2, 5, 2, 6, + 3, 4, 3, 5, 3, 6, + 4, 5, 4, 6, 4, 20, + 5, 6, + 6, 7, 6, 10, 6, 13, 6, 18, + 7, 8, 7, 10, 7, 13, + 8, 9, + 9, 11, 9, 12, + 10, 11, 10, 13, + 11, 15, + 12, 15, + 13, 14, + 14, 15, + 16, 17, 16, 18, 16, 19, + 17, 19, 17, 20, + 18, 19, 18, 21, 18, 22, + 19, 20, + 20, 21, 20, 22, + 21, 22, + -1); + + doit(&g); + igraph_destroy(&g); + printf("--\n"); + + printf("Tricky graph, where the separators themselves form a block:\n"); + + igraph_small(&g, 8, IGRAPH_UNDIRECTED, + 0, 1, 0, 4, 0, 5, 1, 2, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 2, 7, + 3, 6, 3, 7, 4, 5, 5, 6, 6, 7, + -1); + + doit(&g); + igraph_destroy(&g); + printf("--\n"); + + printf("The science camp graph from http://intersci.ss.uci.edu/wiki/index.php/Cohesive_blocking\n"); + igraph_small(&g, 18, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, + 1, 2, 1, 3, 1, 16, 1, 17, + 2, 3, + 3, 17, + 4, 5, 4, 6, 4, 7, 4, 8, + 5, 6, 5, 7, + 6, 7, 6, 8, + 7, 8, 7, 16, + 8, 9, 8, 10, + 9, 11, 9, 12, 9, 13, 9, 14, + 10, 11, 10, 12, 10, 13, + 11, 14, + 12, 13, 12, 14, 12, 15, + 15, 16, 15, 17, + 16, 17, + -1); + + doit(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_cohesive_blocks.out b/tests/unit/igraph_cohesive_blocks.out new file mode 100644 index 0000000..300f34b --- /dev/null +++ b/tests/unit/igraph_cohesive_blocks.out @@ -0,0 +1,46 @@ +The graph from the Moody-White paper: +Blocks: + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + 0 1 2 3 4 5 6 16 17 18 19 20 21 22 + 6 7 8 9 10 11 12 13 14 15 + 0 1 2 3 4 5 6 + 6 7 10 13 +Cohesion: + 1 2 2 5 3 +Parents: + -1 0 0 1 2 +Block graph: +0 1 +0 2 +1 3 +2 4 +-- +Tricky graph, where the separators themselves form a block: +Blocks: + 0 1 2 3 4 5 6 7 + 0 1 4 5 + 2 3 6 7 + 1 2 5 6 +Cohesion: + 2 3 3 3 +Parents: + -1 0 0 0 +Block graph: +0 1 +0 2 +0 3 +-- +The science camp graph from http://intersci.ss.uci.edu/wiki/index.php/Cohesive_blocking +Blocks: + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + 0 1 2 3 + 4 5 6 7 8 + 9 10 11 12 13 14 +Cohesion: + 2 3 3 3 +Parents: + -1 0 0 0 +Block graph: +0 1 +0 2 +0 3 diff --git a/tests/unit/igraph_coloring.out b/tests/unit/igraph_coloring.out new file mode 100644 index 0000000..fb9f6e9 --- /dev/null +++ b/tests/unit/igraph_coloring.out @@ -0,0 +1,94 @@ +Testing null graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +( ) +testing greedy coloring with DSATUR heuristic +( ) + +Testing singleton graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 1 +( 0 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 1 +( 0 ) + +Testing large simple graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 11 +testing greedy coloring with DSATUR heuristic +Number of colors used: 8 + +Testing large graph with loops and multi-edges +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 11 +testing greedy coloring with DSATUR heuristic +Number of colors used: 8 + +Testing small simple graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 4 +( 2 2 2 3 1 1 0 0 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 3 +( 2 2 2 0 1 1 1 0 ) + +Testing small multigraph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 4 +( 3 1 1 1 0 0 0 2 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 3 +( 2 2 2 0 1 1 1 0 ) + +Testing small LCF graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 5 +( 0 4 3 2 1 0 2 1 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 5 +( 4 3 2 1 0 2 1 0 ) + +Testing graph with isolated vertices +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 2 +( 0 1 0 0 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 2 +( 1 0 0 0 ) + +Testing graph with isolated vertices and self-loops +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 1 +( 0 0 0 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 1 +( 0 0 0 ) + +Testing wheel graph with odd number of vertices +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 3 +( 0 2 1 2 1 2 1 2 1 2 1 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 3 +( 0 2 1 2 1 2 1 2 1 2 1 ) + +Testing wheel graph with even number of vertices +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 4 +( 0 3 2 1 2 1 2 1 2 1 2 1 ) +testing greedy coloring with DSATUR heuristic +Number of colors used: 4 +( 0 3 2 1 2 1 2 1 2 1 2 1 ) + +Testing complete bipartite graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 2 +testing greedy coloring with DSATUR heuristic +Number of colors used: 2 + +Testing large bipartite graph +testing greedy coloring with COLORED_NEIGHBORS heuristic +Number of colors used: 2 +testing greedy coloring with DSATUR heuristic +Number of colors used: 2 + diff --git a/tests/unit/igraph_community_eb_get_merges.c b/tests/unit/igraph_community_eb_get_merges.c new file mode 100644 index 0000000..5a2f307 --- /dev/null +++ b/tests/unit/igraph_community_eb_get_merges.c @@ -0,0 +1,127 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_bool_t directed, igraph_vector_int_t *edges, + igraph_vector_t *weights, igraph_matrix_int_t *res, igraph_vector_int_t *bridges, + igraph_vector_t *modularity, igraph_vector_int_t *membership) { + igraph_community_eb_get_merges(g, 1, edges, weights, res, bridges, modularity, membership); + if (bridges) { + printf("Bridges:"); + igraph_vector_int_print(bridges); + } + if (modularity) { + printf("Modularity:"); + print_vector(modularity); + } + if (membership) { + printf("Membership:"); + igraph_vector_int_print(membership); + } + if (res) { + printf("Merges:\n"); + igraph_matrix_int_print(res); + } + printf("\n"); + igraph_destroy(g); +} + +int main(void) { + igraph_t g; + igraph_vector_int_t edges; + igraph_vector_t weights; + igraph_matrix_int_t res; + igraph_vector_int_t bridges; + igraph_vector_t modularity; + igraph_vector_int_t membership; + + igraph_matrix_int_init(&res, 0, 0); + igraph_vector_init(&weights, 0); + igraph_vector_int_init(&bridges, 0); + igraph_vector_init(&modularity, 0); + igraph_vector_int_init(&membership, 0); + + { + printf("Graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + edges = igraph_vector_int_view(NULL, 0); + print_and_destroy(&g, 1, &edges, &weights, &res, &bridges, &modularity, &membership); + } + { + printf("Graph with one vertex:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + edges = igraph_vector_int_view(NULL, 0); + print_and_destroy(&g, 1, &edges, &weights, &res, &bridges, &modularity, &membership); + } + { + printf("Graph with two vertices, one edge:\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_int_t edge_array[] = {0}; + edges = igraph_vector_int_view(edge_array, 1); + print_and_destroy(&g, 1, &edges, NULL, &res, &bridges, &modularity, &membership); + } + { + printf("Triangle, remove three edges:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_int_t edge_array[] = {0, 1, 2}; + edges = igraph_vector_int_view(edge_array, 3); + print_and_destroy(&g, 1, &edges, NULL, &res, &bridges, &modularity, &membership); + } + { + printf("Two connected triangles, remove everything:\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 2,3, 3,4, 4,5, 5,3, -1); + igraph_int_t edge_array[] = {3, 0, 1, 2, 4, 5, 6}; + edges = igraph_vector_int_view(edge_array, 7); + print_and_destroy(&g, 1, &edges, NULL, &res, &bridges, NULL, &membership); + } + { + printf("Two connected triangles, remove everything, only check merges:\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 2,3, 3,4, 4,5, 5,3, -1); + igraph_int_t edge_array[] = {3, 0, 1, 2, 4, 5, 6}; + edges = igraph_vector_int_view(edge_array, 7); + print_and_destroy(&g, 1, &edges, NULL, &res, NULL, NULL, NULL); + } + + VERIFY_FINALLY_STACK(); + + { + printf("Check error when edge id is out of bounds.\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, -1); + igraph_int_t edge_array[] = {3, 0, 1, 2, 4, 5, 6}; + edges = igraph_vector_int_view(edge_array, 7); + CHECK_ERROR(igraph_community_eb_get_merges(&g, 1, &edges, NULL, NULL, NULL, NULL, NULL), IGRAPH_EINVEID); + igraph_destroy(&g); + } + { + printf("Check error when too few edges removed.\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_int_t edge_array[] = {0, 1}; + edges = igraph_vector_int_view(edge_array, 2); + CHECK_ERROR(igraph_community_eb_get_merges(&g, 1, &edges, NULL, NULL, NULL, NULL, NULL), IGRAPH_EINVAL); + igraph_destroy(&g); + } + + igraph_matrix_int_destroy(&res); + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&bridges); + igraph_vector_destroy(&modularity); + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); +} diff --git a/tests/unit/igraph_community_eb_get_merges.out b/tests/unit/igraph_community_eb_get_merges.out new file mode 100644 index 0000000..63320e3 --- /dev/null +++ b/tests/unit/igraph_community_eb_get_merges.out @@ -0,0 +1,47 @@ +Graph with no vertices: +Bridges: +Modularity:( NaN ) +Membership: +Merges: + +Graph with one vertex: +Bridges: +Modularity:( NaN ) +Membership: +Merges: + +Graph with two vertices, one edge: +Bridges:0 +Modularity:( -0.5 0 ) +Membership:0 0 +Merges: +1 0 + +Triangle, remove three edges: +Bridges:2 1 +Modularity:( -0.333333 -0.222222 0 ) +Membership:0 0 0 +Merges: +2 1 +3 0 + +Two connected triangles, remove everything: +Bridges:6 5 3 2 0 +Membership:0 1 2 3 4 5 +Merges: +5 3 +6 4 +2 0 +8 1 +7 9 + +Two connected triangles, remove everything, only check merges: +Merges: +3 5 +4 6 +0 2 +1 8 +9 7 + +Check error when edge id is out of bounds. +Check error when too few edges removed. diff --git a/tests/unit/igraph_community_edge_betweenness.c b/tests/unit/igraph_community_edge_betweenness.c new file mode 100644 index 0000000..0c20854 --- /dev/null +++ b/tests/unit/igraph_community_edge_betweenness.c @@ -0,0 +1,208 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +int igraph_vector_between(const igraph_vector_t* v, const igraph_vector_t* lo, + const igraph_vector_t* hi) { + return igraph_vector_all_le(lo, v) && igraph_vector_all_ge(hi, v); +} + +void test_unweighted(void) { + igraph_t g; + igraph_vector_int_t edges; + igraph_vector_t eb; + igraph_int_t i; + igraph_int_t no_of_edges; + + /* Zachary Karate club */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_vector_int_init(&edges, 0); + igraph_vector_init(&eb, 0); + igraph_community_edge_betweenness(&g, &edges, &eb, + /*merges=*/ NULL, + /*bridges=*/ NULL, + /*modularity=*/ NULL, + /*membership=*/ NULL, + IGRAPH_UNDIRECTED, + /*weights=*/ NULL, + /*lengths=*/ NULL); + + no_of_edges = igraph_ecount(&g); + for (i = 0; i < no_of_edges; i++) { + printf("%" IGRAPH_PRId " ", VECTOR(edges)[i]); + } + printf("\n"); + + for (i = 0; i < no_of_edges; i++) { + printf("%.2f ", VECTOR(eb)[i]); + } + printf("\n"); + + /* Try it once again without storage space for edges */ + igraph_community_edge_betweenness(&g, NULL, &eb, + /*merges=*/ NULL, + /*bridges=*/ NULL, + /*modularity=*/ NULL, + /*membership=*/ NULL, + IGRAPH_UNDIRECTED, + /*weights=*/ NULL, + /*lengths=*/ NULL); + for (i = 0; i < no_of_edges; i++) { + printf("%.2f ", VECTOR(eb)[i]); + } + printf("\n"); + + igraph_vector_destroy(&eb); + igraph_vector_int_destroy(&edges); + igraph_destroy(&g); +} + +#define EPS 1e-4 + +void test_weighted(void) { + igraph_t g; + igraph_vector_int_t edges; + igraph_vector_t eb, lengths; + igraph_real_t lengths_array[] = { 4, 1, 3, 2, 5, 8, 6, 7 }; + + igraph_int_t edges_array1[] = { 2, 3, 0, 1, 4, 7, 5, 6 }; + igraph_int_t edges_array2[] = { 2, 3, 6, 5, 0, 1, 4, 7 }; + igraph_real_t eb_array1_lo[] = { 4, 5, 3 + 1 / 3.0 - EPS, 4, 2.5, 4, 1, 1 }; + igraph_real_t eb_array1_hi[] = { 4, 5, 3 + 1 / 3.0 + EPS, 4, 2.5, 4, 1, 1 }; + igraph_real_t eb_array2_lo[] = { 4, 5, 3 + 1 / 3.0 - EPS, 6, 1.5, 2, 1, 1 }; + igraph_real_t eb_array2_hi[] = { 4, 5, 3 + 1 / 3.0 + EPS, 6, 1.5, 2, 1, 1 }; + + igraph_vector_int_t edges_sol1, edges_sol2; + igraph_vector_t eb_sol1_lo, eb_sol1_hi, eb_sol2_lo, eb_sol2_hi; + + edges_sol1 = igraph_vector_int_view(edges_array1, sizeof(edges_array1) / sizeof(edges_array1[0])); + edges_sol2 = igraph_vector_int_view(edges_array2, sizeof(edges_array2) / sizeof(edges_array2[0])); + eb_sol1_lo = igraph_vector_view(eb_array1_lo, sizeof(eb_array1_lo) / sizeof(eb_array1_lo[0])); + eb_sol2_lo = igraph_vector_view(eb_array2_lo, sizeof(eb_array2_lo) / sizeof(eb_array2_lo[0])); + eb_sol1_hi = igraph_vector_view(eb_array1_hi, sizeof(eb_array1_hi) / sizeof(eb_array1_hi[0])); + eb_sol2_hi = igraph_vector_view(eb_array2_hi, sizeof(eb_array2_hi) / sizeof(eb_array2_hi[0])); + + /* Small graph as follows: A--B--C--A, A--D--E--A, B--D, C--E */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 2, 4, 3, 4, -1); + lengths =igraph_vector_view(lengths_array, igraph_ecount(&g)); + + igraph_vector_int_init(&edges, 0); + igraph_vector_init(&eb, 0); + igraph_community_edge_betweenness(&g, &edges, &eb, + /*merges=*/ NULL, + /*bridges=*/ NULL, + /*modularity=*/ NULL, + /*membership=*/ NULL, + IGRAPH_UNDIRECTED, + NULL, + &lengths); + + if (!igraph_vector_int_all_e(&edges_sol1, &edges) && + !igraph_vector_int_all_e(&edges_sol2, &edges)) { + printf("Error, edges vector was: \n"); + igraph_vector_int_print(&edges); + exit(2); + } + if (!igraph_vector_between(&eb, &eb_sol1_lo, &eb_sol1_hi) && + !igraph_vector_between(&eb, &eb_sol2_lo, &eb_sol2_hi)) { + printf("Error, eb vector was: \n"); + igraph_vector_print(&eb); + exit(2); + } + + /* Try it once again without storage space for edges */ + igraph_community_edge_betweenness(&g, NULL, &eb, + /*merges=*/ NULL, + /*bridges=*/ NULL, + /*modularity=*/ NULL, + /*membership=*/ NULL, + IGRAPH_UNDIRECTED, + NULL, + &lengths); + + if (!igraph_vector_between(&eb, &eb_sol1_lo, &eb_sol1_hi) && + !igraph_vector_between(&eb, &eb_sol2_lo, &eb_sol2_hi)) { + printf("Error, eb vector was: \n"); + igraph_vector_print(&eb); + exit(2); + } + + igraph_vector_destroy(&eb); + igraph_vector_int_destroy(&edges); + igraph_destroy(&g); +} + +void test_zero_edge_graph(void) { + igraph_t g; + igraph_vector_t eb; + igraph_vector_int_t res; + + igraph_full(&g, 1, 0, 0); + igraph_vector_int_init(&res, igraph_ecount(&g)); + igraph_vector_init(&eb, igraph_ecount(&g)); + + igraph_community_edge_betweenness(&g, + &res, // result + &eb, // edge_betweenness result + NULL, // merges result + NULL, // bridges + NULL, // modularity + NULL, // membership + IGRAPH_UNDIRECTED, // directed + NULL, // weights + NULL // lengths + ); + + igraph_vector_destroy(&eb); + printf("No crash\n"); + igraph_vector_int_destroy(&res); + igraph_destroy(&g); +} + +int main(void) { + test_unweighted(); + test_weighted(); + test_zero_edge_graph(); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_community_edge_betweenness.out b/tests/unit/igraph_community_edge_betweenness.out new file mode 100644 index 0000000..f0f3bea --- /dev/null +++ b/tests/unit/igraph_community_edge_betweenness.out @@ -0,0 +1,4 @@ +15 1 7 45 52 31 23 16 24 25 28 44 68 27 4 5 3 8 76 75 70 57 58 26 9 67 66 10 33 46 47 48 49 63 69 50 51 12 20 53 54 13 21 35 38 55 56 14 22 29 42 43 41 73 74 6 18 32 0 2 11 17 19 30 34 36 37 39 40 59 60 61 62 64 65 71 72 77 +71.39 66.90 77.32 82.00 123.23 100.21 143.63 109.25 107.67 142.75 285.00 16.83 18.18 18.00 15.33 25.33 25.00 50.00 14.50 22.37 25.62 29.65 40.67 72.00 9.00 9.00 11.00 5.50 8.00 5.00 10.00 4.50 9.00 4.50 9.00 4.00 8.00 3.50 7.00 3.50 7.00 3.00 6.00 3.00 6.00 3.00 6.00 2.50 5.00 2.00 2.00 3.50 5.00 2.00 4.00 1.33 2.00 4.00 1.00 1.50 3.00 1.00 2.00 1.00 1.00 1.00 1.00 2.00 1.00 1.00 1.50 3.00 1.00 2.00 1.00 1.00 2.00 1.00 +71.39 66.90 77.32 82.00 123.23 100.21 143.63 109.25 107.67 142.75 285.00 16.83 18.18 18.00 15.33 25.33 25.00 50.00 14.50 22.37 25.62 29.65 40.67 72.00 9.00 9.00 11.00 5.50 8.00 5.00 10.00 4.50 9.00 4.50 9.00 4.00 8.00 3.50 7.00 3.50 7.00 3.00 6.00 3.00 6.00 3.00 6.00 2.50 5.00 2.00 2.00 3.50 5.00 2.00 4.00 1.33 2.00 4.00 1.00 1.50 3.00 1.00 2.00 1.00 1.00 1.00 1.00 2.00 1.00 1.00 1.50 3.00 1.00 2.00 1.00 1.00 2.00 1.00 +No crash diff --git a/tests/unit/igraph_community_fastgreedy.c b/tests/unit/igraph_community_fastgreedy.c new file mode 100644 index 0000000..0fb631f --- /dev/null +++ b/tests/unit/igraph_community_fastgreedy.c @@ -0,0 +1,218 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +void show_results(igraph_t *g, igraph_vector_t *mod, igraph_matrix_int_t *merges, + igraph_vector_int_t *membership, FILE* f) { + igraph_int_t i = 0; + igraph_vector_int_t our_membership; + + igraph_vector_int_init(&our_membership, 0); + + if (mod != 0) { + i = igraph_vector_which_max(mod); + fprintf(f, "Modularity: "); + igraph_real_fprintf(f, VECTOR(*mod)[i]); + fprintf(f, "\n"); + } else { + fprintf(f, "Modularity: ---\n"); + } + + if (membership != 0) { + igraph_vector_int_update(&our_membership, membership); + } else if (merges != 0) { + igraph_community_to_membership(merges, igraph_vcount(g), i, &our_membership, 0); + } + + printf("Membership: "); + for (i = 0; i < igraph_vector_int_size(&our_membership); i++) { + printf("%" IGRAPH_PRId " ", VECTOR(our_membership)[i]); + } + printf("\n"); + + igraph_vector_int_destroy(&our_membership); +} + +int main(void) { + igraph_t g; + igraph_vector_t modularity, weights; + igraph_vector_int_t membership; + igraph_matrix_int_t merges; + + igraph_vector_init(&modularity, 0); + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_init(&weights, 0); + igraph_vector_int_init(&membership, 0); + + /* Simple unweighted graph */ + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + + /* Same simple graph, with uniform edge weights */ + igraph_vector_resize(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 2); + igraph_community_fastgreedy(&g, &weights, &merges, &modularity, + /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Simple nonuniform weighted graph, with and without weights */ + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_resize(&weights, 8); + igraph_vector_fill(&weights, 1); + VECTOR(weights)[0] = 10; + VECTOR(weights)[1] = 10; + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_community_fastgreedy(&g, &weights, &merges, &modularity, + /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Zachary Karate club */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, + /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Simple disconnected graph with isolates */ + igraph_small(&g, 9, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, + 4, 5, 4, 6, 4, 7, 5, 6, 5, 7, 6, 7, + -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Disjoint union of two rings */ + igraph_small(&g, 20, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 0, 9, + 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 10, 19, -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Completely empty graph */ + igraph_small(&g, 10, IGRAPH_UNDIRECTED, -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Ring graph with loop edges */ + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 2, 2, -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Regression test -- graph with two vertices and two edges */ + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 0, 1, 1, -1); + igraph_community_fastgreedy(&g, 0, &merges, &modularity, /*membership=*/ 0); + show_results(&g, &modularity, &merges, 0, stdout); + igraph_destroy(&g); + + /* Regression test -- asking for optimal membership vector but not + * providing a modularity vector */ + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + igraph_community_fastgreedy(&g, 0, &merges, 0, &membership); + show_results(&g, 0, &merges, &membership, stdout); + igraph_destroy(&g); + + /* Regression test -- asking for optimal membership vector but not + * providing a merge matrix */ + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + igraph_community_fastgreedy(&g, 0, 0, &modularity, &membership); + show_results(&g, &modularity, 0, &membership, stdout); + + /* Regression test -- asking for optimal membership vector but not + * providing a merge matrix or a modularity vector */ + igraph_community_fastgreedy(&g, 0, 0, 0, &membership); + show_results(&g, 0, 0, &membership, stdout); + + /* Regression test -- asking for modularity but nothing else */ + igraph_community_fastgreedy(&g, 0, 0, &modularity, 0); + show_results(&g, &modularity, 0, 0, stdout); + + igraph_destroy(&g); + + printf("Testing a trivial disconnected graph\n"); + + /* Test a disconnected graph */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0,1, 2,3, + -1); + + /* Asking for all output to verify the result from the following + * two tests. */ + igraph_community_fastgreedy(&g, 0, &merges, &modularity, &membership); + show_results(&g, &modularity, &merges, &membership, stdout); + + /* Regression test -- asking for optimal membership vector but not + * providing a merge matrix or a modularity vector */ + igraph_community_fastgreedy(&g, 0, 0, 0, &membership); + show_results(&g, 0, 0, &membership, stdout); + + /* Regression test -- asking for modularity but nothing else */ + igraph_community_fastgreedy(&g, 0, 0, &modularity, 0); + show_results(&g, &modularity, 0, 0, stdout); + + igraph_destroy(&g); + + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + igraph_vector_destroy(&weights); + igraph_matrix_int_destroy(&merges); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_community_fastgreedy.out b/tests/unit/igraph_community_fastgreedy.out new file mode 100644 index 0000000..f342a14 --- /dev/null +++ b/tests/unit/igraph_community_fastgreedy.out @@ -0,0 +1,35 @@ +Modularity: 0.452381 +Membership: 1 1 1 1 1 0 0 0 0 0 +Modularity: 0.452381 +Membership: 1 1 1 1 1 0 0 0 0 0 +Modularity: 0.179688 +Membership: 1 1 0 0 0 0 +Modularity: 0.170858 +Membership: 1 1 1 0 0 0 +Modularity: 0.380671 +Membership: 0 2 2 2 0 0 0 2 1 2 0 0 2 2 1 1 0 2 1 0 1 2 1 1 1 1 1 1 1 1 1 1 1 1 +Modularity: 0.5 +Membership: 1 1 1 1 0 0 0 0 2 +Modularity: 0.54 +Membership: 1 1 1 1 3 3 3 3 1 1 0 0 0 0 0 0 2 2 2 2 +Modularity: NaN +Membership: 0 1 2 3 4 5 6 7 8 9 +Modularity: 0.28125 +Membership: 0 1 1 2 2 0 +Modularity: 0.5 +Membership: 0 1 +Modularity: --- +Membership: 1 1 1 1 1 0 0 0 0 0 +Modularity: 0.452381 +Membership: 1 1 1 1 1 0 0 0 0 0 +Modularity: --- +Membership: 1 1 1 1 1 0 0 0 0 0 +Modularity: 0.452381 +Membership: +Testing a trivial disconnected graph +Modularity: 0.5 +Membership: 1 1 0 0 +Modularity: --- +Membership: 1 1 0 0 +Modularity: 0.5 +Membership: diff --git a/tests/unit/igraph_community_fluid_communities.c b/tests/unit/igraph_community_fluid_communities.c new file mode 100644 index 0000000..ebf36a4 --- /dev/null +++ b/tests/unit/igraph_community_fluid_communities.c @@ -0,0 +1,90 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_int_t k; + igraph_vector_int_t membership; + + igraph_rng_seed(igraph_rng_default(), 247); + + /* Empty graph */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + igraph_vector_int_init(&membership, 0); + igraph_vector_int_push_back(&membership, 1); + igraph_community_fluid_communities(&g, 2, &membership); + if (igraph_vector_int_size(&membership) != 0) { + return 2; + } + igraph_vector_int_destroy(&membership); + igraph_destroy(&g); + + /* Graph with one vertex only */ + igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1); + igraph_vector_int_init(&membership, 0); + igraph_community_fluid_communities(&g, 2, &membership); + if (igraph_vector_int_size(&membership) != 1 || VECTOR(membership)[0] != 0) { + return 3; + } + igraph_vector_int_destroy(&membership); + igraph_destroy(&g); + + /* Zachary Karate club -- this is just a quick smoke test */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_vector_int_init(&membership, 0); + k = 2; + igraph_community_fluid_communities(&g, k, &membership); + if (!igraph_vector_int_contains(&membership, 0) || !igraph_vector_int_contains(&membership, 1)) { + printf("Resulting graph does not have exactly 2 communities as expected.\n"); + igraph_vector_int_print(&membership); + return 1; + } + + igraph_destroy(&g); + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_community_fluid_communities.out b/tests/unit/igraph_community_fluid_communities.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/igraph_community_infomap.c b/tests/unit/igraph_community_infomap.c new file mode 100644 index 0000000..fad4d07 --- /dev/null +++ b/tests/unit/igraph_community_infomap.c @@ -0,0 +1,313 @@ +/* + igraph library. + Copyright (C) 2011-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + + +void gsummary(const igraph_t *g) { + printf("|V|=%" IGRAPH_PRId ", |E|=%" IGRAPH_PRId ", directed=%d\n", + igraph_vcount(g), igraph_ecount(g), (int) igraph_is_directed(g)); +} + +void show_results(const igraph_vector_int_t *membership, igraph_real_t codelength) { + const igraph_int_t n = igraph_vector_int_size(membership); + printf("Codelength: %0.5f (in %" IGRAPH_PRId " modules)\n", + codelength, igraph_vector_int_max(membership) + 1 ); + printf("Membership: "); + for (igraph_int_t i = 0; i < n; i++) { + printf("%" IGRAPH_PRId " ", VECTOR(*membership)[i]); + } + printf("\n"); +} + +void show_results_lite(igraph_vector_int_t * membership, igraph_real_t codelength) { + const igraph_int_t n = igraph_vector_int_size(membership); + printf("Codelength: %0.5f (in %" IGRAPH_PRId " modules)\n", + codelength, igraph_vector_int_max(membership) + 1 ); + printf("Membership (every 100th vertex): "); + for (igraph_int_t i = 0; i < n; i += 100) { + printf("%" IGRAPH_PRId " ", VECTOR(*membership)[i] ); + } + printf("\n"); +} + +igraph_real_t infomap_weighted_test(const igraph_t *graph, const igraph_vector_t *weights, igraph_bool_t smoke_test, igraph_bool_t is_regularized) { + igraph_real_t codelength = -1; + igraph_vector_int_t membership; + + igraph_vector_int_init(&membership, 0); + + igraph_community_infomap(graph, /*edge_weights=*/ weights, /*vertex_weights=*/ NULL, + /*nb_trials=*/5, + is_regularized, /* regularization_strength */ 1, + &membership, &codelength); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == igraph_vcount(graph)); + + if (!smoke_test) { + if (igraph_vcount(graph) > 500) { + show_results_lite(&membership, codelength); + } else { + show_results(&membership, codelength); + } + } + + igraph_vector_int_destroy(&membership); + + return codelength; +} + + +igraph_real_t infomap_test(const igraph_t *graph, igraph_bool_t smoke_test, igraph_bool_t is_regularized) { + return infomap_weighted_test(graph, NULL, smoke_test, is_regularized); +} + + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_real_t codelength; + FILE *wikt; + igraph_error_handler_t *handler; + igraph_error_t errcode; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Two triangles connected by one edge */ + printf("# Two triangles connected by one edge\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 0, + 3, 4, 4, 5, 5, 3, + 0, 5, + -1); + + /* Test for Infomap availability before proceeding with tests. */ + handler = igraph_set_error_handler(&igraph_error_handler_printignore); + errcode = igraph_community_infomap(&g, NULL, NULL, 1, false, 0, NULL, NULL); + if (errcode == IGRAPH_UNIMPLEMENTED) { + igraph_destroy(&g); + return 77; /* skip test */ + } + igraph_set_error_handler(handler); + + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + /* Two 4-cliques (0123 and 4567) connected by two edges (0-4 and 1-5) */ + printf("\n# Two 4-cliques (0123 and 4567) connected by two edges (0-4 and 1-5)\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, /* 4-clique 0,1,2,3 */ + 7, 4, 7, 5, 7, 6, 4, 5, 4, 6, 5, 6, /* 4-clique 4,5,6,7 */ + 0, 4, 1, 5, /* 8, 0, 8, 4, */ + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + /* Zachary Karate club -- this is just a quick smoke test */ + printf("\n# Zachary Karate club\n"); + igraph_famous(&g, "Zachary"); + infomap_test(&g, /*smoke_test=*/ false, false); + + printf("\n# Zachary Karate club, but regularized\n"); + infomap_test(&g, /*smoke_test=*/ false, true); + igraph_destroy(&g); + + /* Flow.net that come in infomap_dir.tgz */ + printf("\n# Flow (from infomap_dir.tgz)\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 0, 1, 4, + 4, 5, 5, 6, 6, 7, 7, 4, 5, 8, + 8, 9, 9, 10, 10, 11, 11, 8, 9, 12, + 12, 13, 13, 14, 14, 15, 15, 12, 13, 0, + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + /* MultiphysChemBioEco40W_weighted_dir.net */ + printf("\n# MultiphysChemBioEco40W_weighted_dir.net (from infomap_dir.tgz)\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, + 8, 0, 9, 0, 16, 0, 18, 0, 0, 1, 2, 1, 3, 1, + 5, 1, 6, 1, 7, 1, 9, 1, 10, 1, 16, 1, 18, 1, + 0, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 0, 3, + 1, 3, 2, 3, 4, 3, 5, 3, 6, 3, 7, 3, 8, 3, + 9, 3, 10, 3, 11, 3, 13, 3, 14, 3, 16, 3, 17, 3, + 18, 3, 19, 3, 26, 3, 30, 3, 1, 4, 3, 4, 5, 4, + 6, 4, 13, 4, 18, 4, 0, 5, 1, 5, 2, 5, 3, 5, + 6, 5, 7, 5, 9, 5, 1, 6, 3, 6, 7, 6, 9, 6, + 16, 6, 0, 7, 1, 7, 2, 7, 3, 7, 5, 7, 6, 7, + 9, 7, 3, 8, 5, 8, 3, 9, 7, 9, 12, 10, 13, 10, + 14, 10, 15, 10, 16, 10, 17, 10, 18, 10, 19, 10, + 21, 10, 3, 11, 18, 11, 10, 12, 14, 12, 16, 12, + 17, 12, 18, 12, 3, 13, 10, 13, 14, 13, 16, 13, + 10, 14, 12, 14, 13, 14, 15, 14, 16, 14, 17, 14, + 18, 14, 10, 15, 14, 15, 18, 15, 0, 16, 2, 16, + 3, 16, 6, 16, 10, 16, 12, 16, 13, 16, 14, 16, + 17, 16, 18, 16, 10, 17, 12, 17, 14, 17, 18, 17, + 3, 18, 10, 18, 12, 18, 14, 18, 15, 18, 16, 18, + 17, 18, 19, 18, 21, 18, 11, 19, 16, 19, 17, 19, + 16, 20, 18, 20, 21, 20, 22, 20, 23, 20, 24, 20, + 25, 20, 26, 20, 27, 20, 28, 20, 29, 20, 3, 21, + 14, 21, 18, 21, 20, 21, 22, 21, 23, 21, 24, 21, + 25, 21, 26, 21, 27, 21, 28, 21, 29, 21, 35, 21, + 36, 21, 38, 21, 18, 22, 20, 22, 21, 22, 23, 22, + 24, 22, 25, 22, 26, 22, 27, 22, 29, 22, 3, 23, + 20, 23, 21, 23, 22, 23, 24, 23, 25, 23, 26, 23, + 27, 23, 28, 23, 29, 23, 35, 23, 38, 23, 39, 23, + 20, 24, 21, 24, 23, 24, 25, 24, 26, 24, 27, 24, + 28, 24, 29, 24, 9, 25, 20, 25, 21, 25, 22, 25, + 23, 25, 24, 25, 26, 25, 27, 25, 28, 25, 29, 25, + 18, 26, 20, 26, 21, 26, 22, 26, 23, 26, 25, 26, + 27, 26, 28, 26, 29, 26, 30, 26, 32, 26, 35, 26, + 36, 26, 38, 26, 39, 26, 3, 27, 14, 27, 20, 27, + 21, 27, 22, 27, 23, 27, 24, 27, 25, 27, 26, 27, + 28, 27, 29, 27, 38, 27, 3, 28, 18, 28, 20, 28, + 21, 28, 23, 28, 24, 28, 25, 28, 26, 28, 27, 28, + 29, 28, 35, 28, 14, 29, 16, 29, 18, 29, 20, 29, + 21, 29, 22, 29, 23, 29, 24, 29, 25, 29, 26, 29, + 27, 29, 28, 29, 31, 30, 32, 30, 33, 30, 34, 30, + 35, 30, 36, 30, 38, 30, 39, 30, 30, 31, 32, 31, + 34, 31, 36, 31, 30, 32, 34, 32, 35, 32, 36, 32, + 30, 33, 32, 33, 34, 33, 35, 33, 36, 33, 38, 33, + 30, 34, 31, 34, 32, 34, 33, 34, 35, 34, 36, 34, + 38, 34, 39, 34, 26, 35, 30, 35, 32, 35, 33, 35, + 34, 35, 36, 35, 38, 35, 39, 35, 30, 36, 34, 36, + 35, 36, 38, 36, 39, 36, 34, 37, 26, 38, 30, 38, + 32, 38, 33, 38, 34, 38, 35, 38, 36, 38, 39, 38, + 26, 39, 30, 39, 33, 39, 34, 39, 35, 39, 36, 39, + 38, 39, + -1); + igraph_vector_init_real(&weights, 306, + 5.0, 3.0, 130.0, 4.0, 15.0, 9.0, + 7.0, 1.0, 1.0, 3.0, 1.0, 1.0, + 1.0, 34.0, 38.0, 2.0, 23.0, 1.0, + 1.0, 3.0, 2.0, 2.0, 16.0, 1.0, + 3.0, 1.0, 3.0, 63.0, 92.0, 72.0, + 25.0, 447.0, 121.0, 65.0, 4.0, 16.0, + 35.0, 1.0, 19.0, 1.0, 78.0, 1.0, + 45.0, 1.0, 3.0, 1.0, 1.0, 25.0, + 1.0, 3.0, 1.0, 1.0, 3.0, 36.0, + 19.0, 136.0, 41.0, 96.0, 1.0, 7.0, + 26.0, 1.0, 2.0, 2.0, 3.0, 2.0, 2.0, + 23.0, 52.0, 4.0, 1.0, 2.0, 1.0, 3.0, + 1.0, 11.0, 2.0, 17.0, 1.0, 5.0, 18.0, + 86.0, 5.0, 1.0, 1.0, 1.0, 6.0, 1.0, + 2.0, 2.0, 20.0, 4.0, 5.0, 1.0, 5.0, + 12.0, 4.0, 1.0, 1.0, 4.0, 9.0, 40.0, + 2.0, 1.0, 4.0, 1.0, 1.0, 48.0, 2.0, + 18.0, 1.0, 7.0, 2.0, 2.0, 53.0, 25.0, + 9.0, 1.0, 23.0, 8.0, 62.0, 29.0, 35.0, + 4.0, 34.0, 35.0, 3.0, 1.0, 24.0, 1.0, + 6.0, 2.0, 2.0, 22.0, 7.0, 2.0, 5.0, + 14.0, 3.0, 28.0, 14.0, 20.0, 3.0, 1.0, + 5.0, 77.0, 20.0, 25.0, 35.0, 55.0, 35.0, + 115.0, 68.0, 105.0, 2.0, 2.0, 2.0, 4.0, + 2.0, 17.0, 12.0, 3.0, 3.0, 11.0, 10.0, + 7.0, 2.0, 12.0, 31.0, 11.0, 5.0, 11.0, + 65.0, 39.0, 17.0, 26.0, 3.0, 4.0, 2.0, + 3.0, 6.0, 4.0, 8.0, 1.0, 7.0, 7.0, + 6.0, 1.0, 39.0, 42.0, 9.0, 6.0, 9.0, + 5.0, 45.0, 43.0, 26.0, 1.0, 2.0, 6.0, + 2.0, 15.0, 3.0, 9.0, 2.0, 1.0, 1.0, + 1.0, 4.0, 2.0, 9.0, 2.0, 1.0, 2.0, + 28.0, 80.0, 10.0, 18.0, 13.0, 17.0, + 28.0, 40.0, 76.0, 1.0, 2.0, 1.0, 11.0, + 37.0, 5.0, 11.0, 14.0, 4.0, 14.0, 10.0, + 1.0, 1.0, 1.0, 1.0, 41.0, 121.0, 6.0, + 21.0, 12.0, 30.0, 6.0, 141.0, 43.0, 2.0, + 12.0, 6.0, 35.0, 10.0, 7.0, 2.0, 12.0, + 6.0, 2.0, 11.0, 1.0, 7.0, 6.0, 5.0, 3.0, + 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 67.0, 9.0, + 9.0, 11.0, 10.0, 21.0, 7.0, 12.0, 9.0, + 16.0, 7.0, 4.0, 11.0, 17.0, 37.0, 32.0, + 9.0, 2.0, 2.0, 5.0, 4.0, 2.0, 7.0, 3.0, + 3.0, 5.0, 8.0, 14.0, 3.0, 38.0, 3.0, 9.0, + 2.0, 8.0, 21.0, 18.0, 58.0); + infomap_weighted_test(&g, &weights, /*smoke_test=*/ false, false); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* Wiktionary English verbs -- this one is a bit flaky, igraph reports + * 1444 or 1445 modules, depending on the platform, so let's just run it as + * a quick smoke test but don't check the results too thoroughly as some + * changes are expected. We only check the codelength of the partition, + * this is more reliable. */ + printf("\n# Wiktionary english verbs (synonymy 2008)\n"); + wikt = fopen("wikti_en_V_syn.elist", "r"); + IGRAPH_ASSERT(wikt != NULL); + igraph_read_graph_edgelist(&g, wikt, 0, IGRAPH_UNDIRECTED); + fclose(wikt); + gsummary(&g); + codelength = infomap_test(&g, /*smoke_test=*/ true, false); + printf("Codelength: %0.2f\n", codelength); + igraph_destroy(&g); + + printf("\n# Singleton\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Two isolated vertices\n"); + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Small undirected disconnected graph\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 3,4, + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Undirected P_3 with loops at the ends\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, + 0,1, 1,2, 0,0, 2,2, + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Same as above, but directed, single loops\n"); + igraph_small(&g, 2, IGRAPH_DIRECTED, + 0,1, 1,0, 1,2, 2,1, 0,0, 2,2, + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Same as above, but double loops\n"); + igraph_small(&g, 2, IGRAPH_DIRECTED, + 0,1, 1,0, 1,2, 2,1, 0,0, 2,2, 0,0, 2,2, + -1); + infomap_test(&g, /*smoke_test=*/ false, false); + igraph_destroy(&g); + + printf("\n# Same as above, but instead of doubling loops, we double the loop weight\n"); + igraph_small(&g, 2, IGRAPH_DIRECTED, + 0,1, 1,0, 1,2, 2,1, 0,0, 2,2, + -1); + igraph_vector_init_real(&weights, 6, + 1.0, 1.0, 1.0, 1.0, 2.0, 2.0); + infomap_weighted_test(&g, &weights, /*smoke_test=*/ false, false); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_community_infomap.out b/tests/unit/igraph_community_infomap.out new file mode 100644 index 0000000..fde187d --- /dev/null +++ b/tests/unit/igraph_community_infomap.out @@ -0,0 +1,55 @@ +# Two triangles connected by one edge +Codelength: 2.32073 (in 2 modules) +Membership: 0 0 0 1 1 1 + +# Two 4-cliques (0123 and 4567) connected by two edges (0-4 and 1-5) +Codelength: 2.74930 (in 2 modules) +Membership: 0 0 0 0 1 1 1 1 + +# Zachary Karate club +Codelength: 4.31179 (in 3 modules) +Membership: 0 0 0 0 1 1 1 0 2 0 1 0 0 0 2 2 1 0 2 0 2 0 2 2 2 2 2 2 2 2 2 2 2 2 + +# Zachary Karate club, but regularized +Codelength: 4.95662 (in 1 modules) +Membership: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +# Flow (from infomap_dir.tgz) +Codelength: 3.15098 (in 4 modules) +Membership: 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 + +# MultiphysChemBioEco40W_weighted_dir.net (from infomap_dir.tgz) +Codelength: 3.20258 (in 5 modules) +Membership: 0 0 0 0 0 0 0 0 0 0 1 2 1 1 1 1 1 1 1 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 + +# Wiktionary english verbs (synonymy 2008) +|V|=7339, |E|=8293, directed=0 +Codelength: 4.55 + +# Singleton +Codelength: 0.00000 (in 1 modules) +Membership: 0 + +# Two isolated vertices +Codelength: 0.00000 (in 2 modules) +Membership: 0 1 + +# Small undirected disconnected graph +Codelength: 1.43872 (in 3 modules) +Membership: 0 0 0 1 1 2 + +# Undirected P_3 with loops at the ends +Codelength: 1.58496 (in 1 modules) +Membership: 0 0 0 + +# Same as above, but directed, single loops +Codelength: 1.57095 (in 1 modules) +Membership: 0 0 0 + +# Same as above, but double loops +Codelength: 1.58496 (in 1 modules) +Membership: 0 0 0 + +# Same as above, but instead of doubling loops, we double the loop weight +Codelength: 1.58496 (in 1 modules) +Membership: 0 0 0 diff --git a/tests/unit/igraph_community_leading_eigenvector2.c b/tests/unit/igraph_community_leading_eigenvector2.c new file mode 100644 index 0000000..011ef2c --- /dev/null +++ b/tests/unit/igraph_community_leading_eigenvector2.c @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + + +int main(void) { + + igraph_t g; + igraph_matrix_int_t merges; + igraph_vector_int_t membership; + igraph_vector_t x; + igraph_arpack_options_t options; + igraph_vector_t weights; + + /* Zachary Karate club */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, + 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, + 0, 31, 1, 2, 1, 3, 1, 7, 1, 13, + 1, 17, 1, 19, 1, 21, 1, 30, 2, 3, + 2, 7, 2, 8, 2, 9, 2, 13, 2, 27, + 2, 28, 2, 32, 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, + 13, 33, 14, 32, 14, 33, 15, 32, 15, 33, + 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 29, + 23, 32, 23, 33, 24, 25, 24, 27, 24, 31, + 25, 31, 26, 29, 26, 33, 27, 33, 28, 31, + 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, + 31, 32, 31, 33, 32, 33, + -1); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_int_init(&membership, 0); + igraph_vector_init(&x, 0); + igraph_arpack_options_init(&options); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1); + + igraph_community_leading_eigenvector(&g, &weights, &merges, + &membership, 1, + /* options = */ 0, /*modularity=*/ 0, + /*start=*/ 0, /*eigenvalues=*/ 0, + /*eigenvectors=*/ 0, /*history=*/ 0, + /*callback=*/ 0, + /*callback_extra=*/ 0); + + igraph_matrix_int_print(&merges); + print_vector_int(&membership); + + printf("\n"); + + /* Make all the steps, and use an explicit options object */ + igraph_community_leading_eigenvector(&g, &weights, &merges, + &membership, igraph_vcount(&g), + &options, /*modularity=*/ 0, + /*start=*/ 0, /*eigenvalues=*/ 0, + /*eigenvectors=*/ 0, /*history=*/ 0, + /*callback=*/ 0, + /*callback_extra=*/ 0); + + igraph_matrix_int_print(&merges); + print_vector_int(&membership); + + igraph_vector_destroy(&weights); + igraph_vector_destroy(&x); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_community_leading_eigenvector2.out b/tests/unit/igraph_community_leading_eigenvector2.out new file mode 100644 index 0000000..e931b79 --- /dev/null +++ b/tests/unit/igraph_community_leading_eigenvector2.out @@ -0,0 +1,7 @@ +0 1 +( 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 ) + +1 3 +0 2 +5 4 +( 0 2 2 2 0 0 0 2 1 1 0 0 2 2 1 1 0 2 1 2 1 2 1 3 3 3 1 3 3 1 1 3 1 1 ) diff --git a/tests/unit/igraph_community_optimal_modularity.c b/tests/unit/igraph_community_optimal_modularity.c new file mode 100644 index 0000000..7ef56c7 --- /dev/null +++ b/tests/unit/igraph_community_optimal_modularity.c @@ -0,0 +1,169 @@ +/* igraph library. + Copyright (C) 2010-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void prepare_weights_vector(igraph_vector_t *weights, const igraph_t *graph) { + const igraph_int_t m = igraph_ecount(graph); + igraph_edge_betweenness(graph, NULL, weights, igraph_ess_all(IGRAPH_EDGEORDER_ID), true, false); + for (igraph_int_t i=0; i < m; i++) { + VECTOR(*weights)[i] = 1 / VECTOR(*weights)[i]; + } +} + +void verify_with_leiden(const igraph_t *graph, const igraph_vector_t *weights, + igraph_real_t resolution, igraph_real_t modularity) { + + const igraph_bool_t directed = igraph_is_directed(graph); + const igraph_real_t directed_multiplier = directed ? 1.0 : 2.0; + igraph_vector_int_t leiden_membership; + igraph_vector_t vertex_out_weights, vertex_in_weights; + igraph_real_t Q, maxQ = 0.0; + igraph_real_t m = weights ? igraph_vector_sum(weights) : igraph_ecount(graph); + + igraph_vector_init(&vertex_out_weights, 0); + if (directed) igraph_vector_init(&vertex_in_weights, 0); + igraph_vector_int_init(&leiden_membership, 0); + + if (directed) { + igraph_strength(graph, &vertex_out_weights, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, weights); + igraph_strength(graph, &vertex_in_weights, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, weights); + } else { + igraph_strength(graph, &vertex_out_weights, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, weights); + } + + for (int i=0; i < 10; i++) { + igraph_community_leiden(graph, weights, &vertex_out_weights, directed ? &vertex_in_weights : NULL, + resolution / (directed_multiplier * m), + 0.01, false, 2, &leiden_membership, NULL, &Q); + if (Q > maxQ) { + maxQ = Q; + } + } + + if (igraph_cmp_epsilon(modularity, maxQ, 1e-15) < 0) { + printf("Partitioning doesn't achieve maximum modularity. " + "With Leiden: Q=%g; with optimal modularity: Q=%g\n", + maxQ, modularity); + printf("Leiden membership: "); + print_vector_int(&leiden_membership); + IGRAPH_ASSERT(igraph_cmp_epsilon(modularity, maxQ, 1e-15) >= 0); + } + + igraph_vector_int_destroy(&leiden_membership); + if (directed) igraph_vector_destroy(&vertex_in_weights); + igraph_vector_destroy(&vertex_out_weights); +} + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + igraph_vector_int_t membership; + igraph_real_t modularity; + igraph_error_handler_t *handler; + igraph_error_t errcode; + + igraph_rng_seed(igraph_rng_default(), 321); + + igraph_vector_init(&weights, 0); + igraph_vector_int_init(&membership, 0); + + igraph_famous(&graph, "zachary"); + + /* Zachary karate club, unweighted */ + handler = igraph_set_error_handler(&igraph_error_handler_printignore); + errcode = igraph_community_optimal_modularity(&graph, NULL, 1, &modularity, &membership); + if (errcode == IGRAPH_UNIMPLEMENTED) { + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&weights); + return 77; /* skip test */ + } else { + IGRAPH_ASSERT(errcode == IGRAPH_SUCCESS); + } + igraph_set_error_handler(handler); + + verify_with_leiden(&graph, NULL, 1, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.4197896120973046, 1e-15)); + + /* Zachary karate club, weighted */ + prepare_weights_vector(&weights, &graph); + igraph_community_optimal_modularity(&graph, &weights, 1, &modularity, &membership); + verify_with_leiden(&graph, &weights, 1, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.6138050900169181, 1e-15)); + + /* Zachary karate club, unweighted, resolution=2 */ + igraph_community_optimal_modularity(&graph, NULL, 2, &modularity, &membership); + verify_with_leiden(&graph, NULL, 2, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.16452991452991447, 1e-15)); + + /* Zachary karate club, weighted, resolution=2 */ + igraph_community_optimal_modularity(&graph, &weights, 2, &modularity, &membership); + verify_with_leiden(&graph, &weights, 2, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.4006998679817418, 1e-15)); + + /* Zachary karate club, unweighted, resolution=0.5 */ + igraph_community_optimal_modularity(&graph, NULL, 0.5, &modularity, &membership); + verify_with_leiden(&graph, NULL, 0.5, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.6217948717948718, 1e-15)); + + /* Zachary karate club, weighted, resolution=0.5 */ + igraph_community_optimal_modularity(&graph, &weights, 0.5, &modularity, &membership); + verify_with_leiden(&graph, &weights, 0.5, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.752255118181729, 1e-15)); + + igraph_destroy(&graph); + + /* simple graph with self-loops and multi-edges, unweighted */ + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0, 1, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 2, 2, 2, 2, -1); + + igraph_community_optimal_modularity(&graph, NULL, 1, &modularity, &membership); + verify_with_leiden(&graph, NULL, 1, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.36, 1e-15)); + + /* simple graph with self-loops and multi-edges, weighted */ + igraph_vector_resize(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.0); + VECTOR(weights)[2] = 5.0; + VECTOR(weights)[4] = 0.1; + + igraph_community_optimal_modularity(&graph, &weights, 1, &modularity, &membership); + verify_with_leiden(&graph, &weights, 1, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.266855078375386, 1e-15)); + + igraph_destroy(&graph); + + /* Directed graph, chosen so that: + * - the directed/undirected solutions differ + * - there is a single maximum on both cases */ + igraph_small(&graph, 5, IGRAPH_DIRECTED, + 1, 0, 1, 2, 1, 4, 2, 0, 2, 3, 3, 1, 3, 4, 4, 0, -1); + igraph_community_optimal_modularity(&graph, NULL, 1, &modularity, &membership); + verify_with_leiden(&graph, NULL, 1, modularity); + IGRAPH_ASSERT(igraph_almost_equals(modularity, 0.09375, 1e-15)); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_community_voronoi.c b/tests/unit/igraph_community_voronoi.c new file mode 100644 index 0000000..8422bd1 --- /dev/null +++ b/tests/unit/igraph_community_voronoi.c @@ -0,0 +1,120 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_vector_int_t membership, generators; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&membership, 0); + igraph_vector_int_init(&generators, 0); + + printf("Null graph:\n"); + + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_ALL, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + igraph_destroy(&graph); + + printf("\nSingleton graph:\n"); + + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_ALL, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + igraph_destroy(&graph); + + printf("\nTwo isolated nodes:\n"); + + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_ALL, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + igraph_destroy(&graph); + + printf("\nZachary:\n"); + + igraph_famous(&graph, "Zachary"); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_ALL, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + { + igraph_vector_t betw, ibetw; + + igraph_vector_init(&betw, 0); + + igraph_edge_betweenness(&graph, NULL, &betw, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + + igraph_int_t n = igraph_vector_size(&betw); + igraph_vector_init_copy(&ibetw, &betw); + for (igraph_int_t i=0; i < n; i++) { + VECTOR(ibetw)[i] = 1.0 / VECTOR(betw)[i]; + } + + printf("\nZachary, betweenness weighted:\n"); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, /*lengths=*/ &betw, /*weights=*/ &ibetw, IGRAPH_ALL, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + igraph_vector_destroy(&ibetw); + igraph_vector_destroy(&betw); + } + + igraph_destroy(&graph); + + { + igraph_t g1, g2; + + igraph_erdos_renyi_game_gnm(&g1, 10, 30, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_erdos_renyi_game_gnm(&g2, 10, 30, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_disjoint_union(&graph, &g1, &g2); + igraph_add_edge(&graph, 9, 10); + igraph_add_edge(&graph, 10, 9); + + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("\nDirected two-block, in:\n"); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_IN, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + printf("\nDirected two-block, out:\n"); + + igraph_community_voronoi(&graph, &membership, &generators, NULL, NULL, NULL, IGRAPH_OUT, -1); + + printf("Membership: "); print_vector_int(&membership); + printf("Generators: "); print_vector_int(&generators); + + igraph_destroy(&graph); + } + + igraph_vector_int_destroy(&generators); + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_community_voronoi.out b/tests/unit/igraph_community_voronoi.out new file mode 100644 index 0000000..1df8de4 --- /dev/null +++ b/tests/unit/igraph_community_voronoi.out @@ -0,0 +1,27 @@ +Null graph: +Membership: ( ) +Generators: ( ) + +Singleton graph: +Membership: ( 0 ) +Generators: ( 0 ) + +Two isolated nodes: +Membership: ( 0 1 ) +Generators: ( 0 1 ) + +Zachary: +Membership: ( 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 0 1 1 0 1 0 1 0 0 2 2 0 0 0 0 0 2 0 0 ) +Generators: ( 33 0 24 ) + +Zachary, betweenness weighted: +Membership: ( 1 1 1 1 2 2 2 1 1 0 2 1 1 1 0 0 2 1 0 1 0 1 0 0 3 3 0 3 0 0 0 0 0 0 ) +Generators: ( 33 1 6 24 ) + +Directed two-block, in: +Membership: ( 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 ) +Generators: ( 13 4 ) + +Directed two-block, out: +Membership: ( 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 ) +Generators: ( 13 0 ) diff --git a/tests/unit/igraph_compare_communities.c b/tests/unit/igraph_compare_communities.c new file mode 100644 index 0000000..53dfd64 --- /dev/null +++ b/tests/unit/igraph_compare_communities.c @@ -0,0 +1,101 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +char *names[] = { + [IGRAPH_COMMCMP_VI] = "VI", + [IGRAPH_COMMCMP_NMI] = "NMI", + [IGRAPH_COMMCMP_SPLIT_JOIN] = "Split-join", + [IGRAPH_COMMCMP_RAND] = "Rand", + [IGRAPH_COMMCMP_ADJUSTED_RAND] = "Adjusted Rand", +}; + + +void compare_and_print(igraph_vector_int_t *comm1, igraph_vector_int_t *comm2, igraph_community_comparison_t t, igraph_error_t e) { + igraph_real_t result; + printf("%s result: ", names[t]); + IGRAPH_ASSERT(igraph_compare_communities(comm1, comm2, &result, t) == e); + if (e == IGRAPH_EINVAL) { + printf("failed as expected\n"); + } else { + print_real(stdout, result, "%g"); + printf("\n"); + } +} + + +int main(void) { + igraph_vector_int_t comm1, comm2; + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Only one member, both partitions equal to whole set:\n"); + igraph_vector_int_init_int(&comm1, 1, 0); + igraph_vector_int_init_int(&comm2, 1, 0); + + + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_VI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_RAND, IGRAPH_EINVAL); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_ADJUSTED_RAND, IGRAPH_EINVAL); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_NMI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_SPLIT_JOIN, IGRAPH_SUCCESS); + + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("\nEmpty sets:\n"); + igraph_vector_int_init(&comm1, 0); + igraph_vector_int_init(&comm2, 0); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_VI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_RAND, IGRAPH_EINVAL); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_ADJUSTED_RAND, IGRAPH_EINVAL); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_NMI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_SPLIT_JOIN, IGRAPH_SUCCESS); + + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("\nTwo equal, differenly labeled partitions:\n"); + igraph_vector_int_init_int(&comm1, 2, 0, 1); + igraph_vector_int_init_int(&comm2, 2, 1, 0); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_VI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_RAND, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_ADJUSTED_RAND, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_NMI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_SPLIT_JOIN, IGRAPH_SUCCESS); + + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("\nTwo different partitions: ((5,1), (8,3,4), (0,6,7,2,9)) and ((5,8), (1,3,4,0), (6,7,2,9))\n"); + igraph_vector_int_init_int(&comm1, 10, 2, 0, 2, 1, 1, 0, 2, 2, 1, 2); + igraph_vector_int_init_int(&comm2, 10, 1, 1, 2, 1, 1, 0, 2, 2, 0, 2); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_VI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_RAND, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_ADJUSTED_RAND, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_NMI, IGRAPH_SUCCESS); + compare_and_print(&comm1, &comm2, IGRAPH_COMMCMP_SPLIT_JOIN, IGRAPH_SUCCESS); + + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_compare_communities.out b/tests/unit/igraph_compare_communities.out new file mode 100644 index 0000000..fc01149 --- /dev/null +++ b/tests/unit/igraph_compare_communities.out @@ -0,0 +1,27 @@ +Only one member, both partitions equal to whole set: +VI result: 0 +Rand result: failed as expected +Adjusted Rand result: failed as expected +NMI result: 1 +Split-join result: 0 + +Empty sets: +VI result: 0 +Rand result: failed as expected +Adjusted Rand result: failed as expected +NMI result: 1 +Split-join result: 0 + +Two equal, differenly labeled partitions: +VI result: 0 +Rand result: 1 +Adjusted Rand result: NaN +NMI result: 1 +Split-join result: 0 + +Two different partitions: ((5,1), (8,3,4), (0,6,7,2,9)) and ((5,8), (1,3,4,0), (6,7,2,9)) +VI result: 1.1343 +Rand result: 0.711111 +Adjusted Rand result: 0.312573 +NMI result: 0.455859 +Split-join result: 6 diff --git a/tests/unit/igraph_complex.c b/tests/unit/igraph_complex.c new file mode 100644 index 0000000..c0cc86e --- /dev/null +++ b/tests/unit/igraph_complex.c @@ -0,0 +1,183 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +/* Ensure that math constants are available. */ +#include "core/math.h" /* M_PI */ + +#include "test_utilities.h" + +#define ARE 4 +#define AIM 5 +#define BRE 6 +#define BIM 2 + +int main(void) { + + igraph_complex_t a = igraph_complex(ARE, AIM); + igraph_complex_t b = igraph_complex(BRE, BIM); + igraph_complex_t c, d, e; + + /* polar, mod, arg */ + c = igraph_complex_polar(igraph_complex_abs(a), igraph_complex_arg(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, c, 1e-14)); + + /* add */ + c = igraph_complex_add(a, b); + IGRAPH_ASSERT(IGRAPH_REAL(c) == ARE + BRE && IGRAPH_IMAG(c) == AIM + BIM); + + /* sub */ + c = igraph_complex_sub(a, b); + IGRAPH_ASSERT(IGRAPH_REAL(c) == ARE - BRE && IGRAPH_IMAG(c) == AIM - BIM); + + /* mul */ + c = igraph_complex_mul(a, b); + IGRAPH_ASSERT(IGRAPH_REAL(c) == ARE * BRE - AIM * BIM); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == ARE * BIM + AIM * BRE); + + /* div */ + c = igraph_complex_div(a, b); + c = igraph_complex_mul(c, b); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, c, 1e-14)); + + /* add_real */ + c = igraph_complex_add_real(a, IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_REAL(a) + IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_IMAG(a)); + + /* add_imag */ + c = igraph_complex_add_imag(a, IGRAPH_IMAG(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_REAL(a)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_IMAG(a) + IGRAPH_IMAG(b)); + + /* sub_real */ + c = igraph_complex_sub_real(a, IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_REAL(a) - IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_IMAG(a)); + + /* sub_imag */ + c = igraph_complex_sub_imag(a, IGRAPH_IMAG(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_REAL(a)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_IMAG(a) - IGRAPH_IMAG(b)); + + /* mul_real */ + c = igraph_complex_mul_real(a, IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_REAL(a) * IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_IMAG(a) * IGRAPH_REAL(b)); + + /* mul_imag */ + c = igraph_complex_mul_imag(a, IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == - IGRAPH_IMAG(a) * IGRAPH_REAL(b)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == IGRAPH_REAL(a) * IGRAPH_REAL(b)); + + /* div_real */ + c = igraph_complex_div_real(a, IGRAPH_REAL(b)); + IGRAPH_ASSERT(fabs(IGRAPH_REAL(c) - IGRAPH_REAL(a) / IGRAPH_REAL(b)) < 1e-15); + IGRAPH_ASSERT(fabs(IGRAPH_IMAG(c) - IGRAPH_IMAG(a) / IGRAPH_REAL(b)) < 1e-15); + + /* div_imag */ + c = igraph_complex_div_imag(a, IGRAPH_IMAG(b)); + IGRAPH_ASSERT(IGRAPH_REAL(c) == IGRAPH_IMAG(a) / IGRAPH_IMAG(b)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == - IGRAPH_REAL(a) / IGRAPH_IMAG(b)); + + /* conj */ + c = igraph_complex_conj(a); + IGRAPH_ASSERT(IGRAPH_REAL(c) == ARE && IGRAPH_IMAG(c) == -AIM); + + /* neg */ + c = igraph_complex_neg(a); + IGRAPH_ASSERT(IGRAPH_REAL(c) == - IGRAPH_REAL(a)); + IGRAPH_ASSERT(IGRAPH_IMAG(c) == - IGRAPH_IMAG(a)); + + /* inv */ + c = igraph_complex_inv(a); + d = igraph_complex(1.0, 0.0); + e = igraph_complex_div(d, a); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, e, 1e-14)); + + /* logabs */ + + /* sqrt */ + c = igraph_complex_sqrt(a); + d = igraph_complex_mul(c, c); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, d, 1e-14)); + + /* sqrt_real */ + c = igraph_complex_sqrt(igraph_complex(-1.0, 0.0)); + d = igraph_complex_sqrt_real(-1.0); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, d, 1e-14)); + + /* exp */ + c = igraph_complex_exp(igraph_complex(0.0, M_PI)); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, igraph_complex(-1.0, 0.0), 1e-14)); + + /* pow */ + c = igraph_complex_pow(igraph_complex(M_E, 0.0), igraph_complex(0.0, M_PI)); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, igraph_complex(-1.0, 0.0), 1e-14)); + + /* pow_real */ + c = igraph_complex_pow_real(a, 2.0); + d = igraph_complex_mul(a, a); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, d, 1e-14)); + + /* log */ + c = igraph_complex_exp(igraph_complex_log(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, c, 1e-14)); + + /* log10 */ + c = igraph_complex_pow(igraph_complex(10.0, 0), igraph_complex_log10(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, c, 1e-14)); + + /* log_b */ + c = igraph_complex_pow(b, igraph_complex_log_b(a, b)); + IGRAPH_ASSERT(igraph_complex_almost_equals(a, c, 1e-14)); + + /* sin, cos */ + c = igraph_complex_sin(a); + d = igraph_complex_cos(a); + e = igraph_complex_add(igraph_complex_mul(c, c), igraph_complex_mul(d, d)); + IGRAPH_ASSERT(igraph_complex_almost_equals(e, igraph_complex(1.0, 0.0), 1e-12)); + + /* tan */ + c = igraph_complex_tan(a); + d = igraph_complex_div(igraph_complex_sin(a), igraph_complex_cos(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, d, 1e-14)); + + /* sec */ + c = igraph_complex_sec(a); + d = igraph_complex_inv(igraph_complex_cos(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, d, 1e-14)); + + /* csc */ + c = igraph_complex_csc(a); + d = igraph_complex_inv(igraph_complex_sin(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(c, d, 1e-14)); + + /* cot */ + c = igraph_complex_tan(a); + d = igraph_complex_div(igraph_complex_sin(a), igraph_complex_cos(a)); + IGRAPH_ASSERT(igraph_complex_almost_equals(d, c, 1e-14)); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_connect_neighborhood.c b/tests/unit/igraph_connect_neighborhood.c new file mode 100644 index 0000000..096d664 --- /dev/null +++ b/tests/unit/igraph_connect_neighborhood.c @@ -0,0 +1,74 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_int_t order, igraph_neimode_t mode) { + igraph_connect_neighborhood(g, order, mode); + print_graph_canon(g); + igraph_destroy(g); +} + +int main(void) { + igraph_t g; + + printf("Graph with no vertices:\n"); + igraph_small(&g, IGRAPH_UNDIRECTED, 0, -1); + print_and_destroy(&g, 10, IGRAPH_ALL); + + printf("Directed graph with loops and multiple edges, order 0, IGRAPH_OUT:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 0, IGRAPH_OUT); + + printf("Same graph, order 1:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 1, IGRAPH_OUT); + + printf("Same graph, order 2:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 2, IGRAPH_OUT); + + printf("Same starting graph, order 2, IGRAPH_IN:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 2, IGRAPH_IN); + + printf("Same starting graph, order 2, IGRAPH_ALL:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 2, IGRAPH_ALL); + + printf("Same starting graph, order 3, IGRAPH_OUT:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 3, IGRAPH_OUT); + + printf("Same starting graph, order 12, IGRAPH_IN:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 12, IGRAPH_IN); + + printf("Same starting graph, but undirected, order 2, IGRAPH_OUT:\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, 2, IGRAPH_OUT); + + VERIFY_FINALLY_STACK(); + + printf("Check negative order error.\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + CHECK_ERROR(igraph_connect_neighborhood(&g, -1, IGRAPH_OUT), IGRAPH_EINVAL); + igraph_destroy(&g); + + return 0; +} diff --git a/tests/unit/igraph_connect_neighborhood.out b/tests/unit/igraph_connect_neighborhood.out new file mode 100644 index 0000000..9e62ba7 --- /dev/null +++ b/tests/unit/igraph_connect_neighborhood.out @@ -0,0 +1,124 @@ +Graph with no vertices: +directed: false +vcount: 0 +edges: { +} +Directed graph with loops and multiple edges, order 0, IGRAPH_OUT: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +1 1 +1 3 +2 3 +3 4 +3 4 +} +Same graph, order 1: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +1 1 +1 3 +2 3 +3 4 +3 4 +} +Same graph, order 2: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 1 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Same starting graph, order 2, IGRAPH_IN: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 1 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Same starting graph, order 2, IGRAPH_ALL: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 1 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Same starting graph, order 3, IGRAPH_OUT: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +0 4 +1 1 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Same starting graph, order 12, IGRAPH_IN: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +0 4 +1 1 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Same starting graph, but undirected, order 2, IGRAPH_OUT: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 1 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 +3 4 +} +Check negative order error. diff --git a/tests/unit/igraph_constraint.c b/tests/unit/igraph_constraint.c new file mode 100644 index 0000000..5ef3611 --- /dev/null +++ b/tests/unit/igraph_constraint.c @@ -0,0 +1,104 @@ +/* igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_vs_t vids, igraph_vector_t *weights) { + igraph_vector_t result; + igraph_vector_init(&result, 0); + IGRAPH_ASSERT(igraph_constraint(graph, &result, vids, weights) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_4_full, g_4_full_split, g_4_full_loop, g_4_line, g_hole; + igraph_vector_t weights_full, weights_full_split, weights_line, result; + + igraph_vector_init_real(&weights_line, 6, 1., 0., 0., 1., 0., 1.); + igraph_vector_init_real(&weights_full, 6, 1., 1., 1., 1., 1., 1.); + igraph_vector_init_real(&weights_full_split, 9, .5, .5, 1., 1., 1., 1./3., 1./3., 1./3., 1.); + igraph_vector_init(&result, 0); + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_4_full, 4, IGRAPH_UNDIRECTED, 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, -1); + igraph_small(&g_4_full_loop, 4, IGRAPH_UNDIRECTED, 0,0, 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, -1); + igraph_small(&g_4_full_split, 4, IGRAPH_UNDIRECTED, 0,1, 0,1, 0,2, 0,3, 1,2, + 1,3, 1,3, 1,3, 2,3, -1); + igraph_small(&g_4_line, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, -1); + igraph_small(&g_hole, 9, IGRAPH_UNDIRECTED, 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, 3,4, 4,5, + 5,6, 5,7, 5,8, 6,7, 6,8, 7,8, -1); + + printf("No vertices:\n"); + call_and_print(&g_0, igraph_vss_none(), NULL); + + printf("One vertex:\n"); + call_and_print(&g_1, igraph_vss_1(0), NULL); + + printf("Line graph, 4 vertices:\n"); + call_and_print(&g_4_line, igraph_vss_all(), NULL); + + printf("Full graph, 4 vertices, weights make it a line:\n"); + call_and_print(&g_4_full, igraph_vss_all(), &weights_line); + + /* + each p_ij and p_ji is equal to 1/3, because each node has 3 connections with + equal weight and p_ij is z_ij/sum_j(z_ij) where z are the edge weights. + so C_ij = (1/3 + (1/3)^2) for each j, which means C_i = (1/3 + 2/9)^2 *3 + = 0.925925925925926 + */ + printf("Full graph, 4 vertices, all same weights:\n"); + call_and_print(&g_4_full, igraph_vss_all(), &weights_full); + + printf("Full graph, 4 vertices, all same weights, but split over multiple edges:\n"); + call_and_print(&g_4_full_split, igraph_vss_all(), &weights_full_split); + + printf("Full graph, 4 vertices, no weights:\n"); + call_and_print(&g_4_full, igraph_vss_all(), NULL); + + printf("Full graph, 4 vertices, no weights, with loop:\n"); + call_and_print(&g_4_full_loop, igraph_vss_all(), NULL); + + /* + for node 0, each p_ij is again equal to 1/3, but p_ji equals 1/4 for j = 3, + because that's connected to 4 other nodes, so for example for C_01 the contribution + is + C_01 = (p_01 + p_02p_21 + p_03p_31)^2 = (1/3 + 1/9 + 1/12)^2 + and you end up with: + ((1/3 + 2/9)^2 ) + ((1/3 + 1/9 + 1/12)^2 * 2) = 0.8657407407407408 + */ + printf("Hole in middle of two clusters:\n"); + call_and_print(&g_hole, igraph_vss_all(), NULL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_4_full); + igraph_destroy(&g_4_full_loop); + igraph_destroy(&g_4_full_split); + igraph_destroy(&g_4_line); + igraph_destroy(&g_hole); + igraph_vector_destroy(&weights_line); + igraph_vector_destroy(&weights_full); + igraph_vector_destroy(&weights_full_split); + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_constraint.out b/tests/unit/igraph_constraint.out new file mode 100644 index 0000000..881eb51 --- /dev/null +++ b/tests/unit/igraph_constraint.out @@ -0,0 +1,27 @@ +No vertices: +( ) + +One vertex: +( NaN ) + +Line graph, 4 vertices: +( 1 0.5 0.5 1 ) + +Full graph, 4 vertices, weights make it a line: +( 1.25 0.5625 0.5625 1.25 ) + +Full graph, 4 vertices, all same weights: +( 0.925926 0.925926 0.925926 0.925926 ) + +Full graph, 4 vertices, all same weights, but split over multiple edges: +( 0.925926 0.925926 0.925926 0.925926 ) + +Full graph, 4 vertices, no weights: +( 0.925926 0.925926 0.925926 0.925926 ) + +Full graph, 4 vertices, no weights, with loop: +( 0.925926 0.925926 0.925926 0.925926 ) + +Hole in middle of two clusters: +( 0.865741 0.865741 0.865741 0.583333 0.5 0.583333 0.865741 0.865741 0.865741 ) + diff --git a/tests/unit/igraph_contract_vertices.c b/tests/unit/igraph_contract_vertices.c new file mode 100644 index 0000000..4e55c61 --- /dev/null +++ b/tests/unit/igraph_contract_vertices.c @@ -0,0 +1,117 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t mapping; + igraph_attribute_combination_t comb; + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_attribute_combination(&comb, + "an", IGRAPH_ATTRIBUTE_COMBINE_SUM, + "ab", IGRAPH_ATTRIBUTE_COMBINE_FIRST, + "as", IGRAPH_ATTRIBUTE_COMBINE_LAST, + "ean", IGRAPH_ATTRIBUTE_COMBINE_PROD, + IGRAPH_NO_MORE_ATTRIBUTES); + + printf("Graph with no vertices:\n"); + { + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + mapping = igraph_vector_int_view(NULL, 0); + igraph_contract_vertices(&g, &mapping, &comb); + igraph_destroy(&g); + } + + printf("Graph with loops and multiple edges:\n"); + { + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_bool_t booleans[6] = {0, 1, 0, 1, 0, 1}; + igraph_real_t real_numbers[6] = {0, 1, 2, 3, 4, 5}; + igraph_real_t real_numbers_edges[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + igraph_vector_t reals; + igraph_vector_t reals_edges; + igraph_vector_bool_t bools; + + reals = igraph_vector_view(real_numbers, 6); + reals_edges = igraph_vector_view(real_numbers_edges, 8); + bools = igraph_vector_bool_view(booleans, 6); + + SETVABV(&g, "ab", &bools); + SETVANV(&g, "an", &reals); + SETVAS(&g, "as", 0, "zero"); + SETVAS(&g, "as", 1, "one"); + SETVAS(&g, "as", 2, "two"); + SETVAS(&g, "as", 3, "three"); + SETVAS(&g, "as", 4, "four"); + SETVAS(&g, "as", 5, "five"); + SETEANV(&g, "ean", &reals_edges); + + { + printf("No contraction:\n"); + igraph_int_t mappings[6] = {0, 1, 2, 3, 4, 5}; + mapping = igraph_vector_int_view(mappings, 6); + igraph_contract_vertices(&g, &mapping, &comb); + print_attributes(&g); + } + { + printf("Contract 5 into 4:\n"); + igraph_int_t mappings[6] = {0, 1, 2, 3, 4, 4}; + mapping = igraph_vector_int_view(mappings, 6); + igraph_contract_vertices(&g, &mapping, &comb); + print_attributes(&g); + } + { + printf("Contract 4 into 3:\n"); + igraph_int_t mappings[5] = {0, 1, 2, 3, 3}; + mapping = igraph_vector_int_view(mappings, 5); + igraph_contract_vertices(&g, &mapping, &comb); + print_attributes(&g); + } + { + printf("Contract 3 into 2:\n"); + igraph_int_t mappings[4] = {0, 1, 2, 2}; + mapping = igraph_vector_int_view(mappings, 4); + igraph_contract_vertices(&g, &mapping, &comb); + print_attributes(&g); + } + { + printf("Contract 2 into 0:\n"); + igraph_int_t mappings[3] = {0, 1, 0}; + mapping = igraph_vector_int_view(mappings, 3); + igraph_contract_vertices(&g, &mapping, &comb); + print_attributes(&g); + } + } + + VERIFY_FINALLY_STACK(); + + { + printf("Check incorrect mapping size error.\n"); + igraph_int_t mappings[3] = {0, 1, 0}; + mapping = igraph_vector_int_view(mappings, 3); + CHECK_ERROR(igraph_contract_vertices(&g, &mapping, &comb), IGRAPH_EINVAL); + } + + igraph_attribute_combination_destroy(&comb); + igraph_destroy(&g); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_contract_vertices.out b/tests/unit/igraph_contract_vertices.out new file mode 100644 index 0000000..f2377a2 --- /dev/null +++ b/tests/unit/igraph_contract_vertices.out @@ -0,0 +1,73 @@ +Graph with no vertices: +Graph with loops and multiple edges: +No contraction: +Vertex 0: ab=0 an=0 as="zero" +Vertex 1: ab=1 an=1 as="one" +Vertex 2: ab=0 an=2 as="two" +Vertex 3: ab=1 an=3 as="three" +Vertex 4: ab=0 an=4 as="four" +Vertex 5: ab=1 an=5 as="five" +Edge 0 (0-1): ean=0 +Edge 1 (0-2): ean=1 +Edge 2 (1-1): ean=2 +Edge 3 (1-3): ean=3 +Edge 4 (2-0): ean=4 +Edge 5 (2-3): ean=5 +Edge 6 (3-4): ean=6 +Edge 7 (3-4): ean=7 + +Contract 5 into 4: +Vertex 0: ab=0 an=0 as="zero" +Vertex 1: ab=1 an=1 as="one" +Vertex 2: ab=0 an=2 as="two" +Vertex 3: ab=1 an=3 as="three" +Vertex 4: ab=0 an=9 as="five" +Edge 0 (0-1): ean=0 +Edge 1 (0-2): ean=1 +Edge 2 (1-1): ean=2 +Edge 3 (1-3): ean=3 +Edge 4 (2-0): ean=4 +Edge 5 (2-3): ean=5 +Edge 6 (3-4): ean=6 +Edge 7 (3-4): ean=7 + +Contract 4 into 3: +Vertex 0: ab=0 an=0 as="zero" +Vertex 1: ab=1 an=1 as="one" +Vertex 2: ab=0 an=2 as="two" +Vertex 3: ab=1 an=12 as="five" +Edge 0 (0-1): ean=0 +Edge 1 (0-2): ean=1 +Edge 2 (1-1): ean=2 +Edge 3 (1-3): ean=3 +Edge 4 (2-0): ean=4 +Edge 5 (2-3): ean=5 +Edge 6 (3-3): ean=6 +Edge 7 (3-3): ean=7 + +Contract 3 into 2: +Vertex 0: ab=0 an=0 as="zero" +Vertex 1: ab=1 an=1 as="one" +Vertex 2: ab=0 an=14 as="five" +Edge 0 (0-1): ean=0 +Edge 1 (0-2): ean=1 +Edge 2 (1-1): ean=2 +Edge 3 (1-2): ean=3 +Edge 4 (2-0): ean=4 +Edge 5 (2-2): ean=5 +Edge 6 (2-2): ean=6 +Edge 7 (2-2): ean=7 + +Contract 2 into 0: +Vertex 0: ab=0 an=14 as="five" +Vertex 1: ab=1 an=1 as="one" +Edge 0 (0-1): ean=0 +Edge 1 (0-0): ean=1 +Edge 2 (1-1): ean=2 +Edge 3 (1-0): ean=3 +Edge 4 (0-0): ean=4 +Edge 5 (0-0): ean=5 +Edge 6 (0-0): ean=6 +Edge 7 (0-0): ean=7 + +Check incorrect mapping size error. diff --git a/tests/unit/igraph_convergence_degree.c b/tests/unit/igraph_convergence_degree.c new file mode 100644 index 0000000..898ea10 --- /dev/null +++ b/tests/unit/igraph_convergence_degree.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_t result; + igraph_int_t i; + + igraph_vector_init(&result, 0); + + igraph_small(&g, 7, 0, 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 3, 4, 4, 5, 4, 6, 5, 6, -1); + igraph_convergence_degree(&g, &result, 0, 0); + for (i = 0; i < igraph_ecount(&g); i++) { + printf("%.4f ", igraph_vector_get(&result, i)); + } + printf("\n"); + igraph_destroy(&g); + + igraph_small(&g, 6, 1, 1, 0, 2, 0, 3, 0, 4, 0, 0, 5, -1); + igraph_convergence_degree(&g, &result, 0, 0); + for (i = 0; i < igraph_ecount(&g); i++) { + printf("%.4f ", igraph_vector_get(&result, i)); + } + printf("\n"); + igraph_destroy(&g); + + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_convergence_degree.out b/tests/unit/igraph_convergence_degree.out new file mode 100644 index 0000000..fa1eae6 --- /dev/null +++ b/tests/unit/igraph_convergence_degree.out @@ -0,0 +1,2 @@ +0.0000 0.0000 0.6000 0.0000 0.6000 0.6000 0.1429 0.6667 0.6667 0.0000 +-0.3333 -0.3333 -0.3333 -0.3333 0.6667 diff --git a/tests/unit/igraph_convex_hull_2d.c b/tests/unit/igraph_convex_hull_2d.c new file mode 100644 index 0000000..58b14bc --- /dev/null +++ b/tests/unit/igraph_convex_hull_2d.c @@ -0,0 +1,158 @@ +/* + igraph library. + Copyright (C) 2006-2022 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +void check_convex_hull(igraph_matrix_t *coords) { + igraph_vector_int_t result; + igraph_matrix_t resmat; + + /* Testing with index output mode */ + igraph_vector_int_init(&result, 1); + igraph_convex_hull_2d(coords, &result, 0); + + print_vector_int(&result); + igraph_vector_int_destroy(&result); + + /* Testing with coordinate output mode */ + igraph_matrix_init(&resmat, 0, 0); + igraph_convex_hull_2d(coords, 0, &resmat); + + print_matrix(&resmat); + igraph_matrix_destroy(&resmat); +} + +void test_simple(void) { + igraph_real_t coords_array[][2] = { + {3, 2}, {5, 1}, {4, 4}, {6, 4}, {4, 3}, + {2, 5}, {1, 3}, {2, 4}, {6, 3}, {9, 2} + }; + igraph_matrix_t coords; + + printf("test_simple\n"); + + igraph_matrix_init(&coords, 10, 2); + for (igraph_int_t i = 0; i < 20; i++) { + MATRIX(coords, i / 2, i % 2) = coords_array[i / 2][i % 2]; + } + check_convex_hull(&coords); + igraph_matrix_destroy(&coords); +} + +void test_collinear(void) { + igraph_real_t coords_array[][2] = + {{3, 2}, {5, 1}, {7, 0}, {9, -1}, {11, -2}}; + igraph_matrix_t coords; + + printf("test_collinear\n"); + + igraph_matrix_init(&coords, 5, 2); + for (igraph_int_t i = 0; i < 10; i++) { + MATRIX(coords, i / 2, i % 2) = coords_array[i / 2][i % 2]; + } + check_convex_hull(&coords); + igraph_matrix_destroy(&coords); +} + +void test_degenerate(void) { + igraph_matrix_t coords; + + printf("test_degenerate\n"); + + igraph_matrix_init(&coords, 2, 2); + MATRIX(coords, 0, 0) = 3; + MATRIX(coords, 0, 1) = 2; + MATRIX(coords, 1, 0) = 5; + MATRIX(coords, 1, 1) = 1; + check_convex_hull(&coords); + + igraph_matrix_resize(&coords, 1, 2); + MATRIX(coords, 0, 0) = 3; + MATRIX(coords, 0, 1) = 2; + check_convex_hull(&coords); + + igraph_matrix_resize(&coords, 0, 2); + check_convex_hull(&coords); + + igraph_matrix_destroy(&coords); +} + +void test_bug_805(void) { + igraph_real_t coords_array[][2] = { + {0, 0}, {1, 0}, {0.707, 0.707}, {0, 1}, {-0.707, 0.707}, {-1, 0}, + {-0.707, -0.707}, {0, -1}, {0.707, -0.707}, {2, 0}, {1.414, 1.414}, {0, 2}, + {-1.414, 1.414}, {-2, 0}, {-1.414, -1.414}, {0, -2}, {1.414, -1.414}, {3, 0}, + {2.121, 2.121}, {0, 3}, {-2.121, 2.121}, {-3, 0}, {-2.121, -2.121}, {0, -3}, + {2.121, -2.121}, {4, 0}, {2.828, 2.828}, {0, 4}, {-2.828, 2.828}, {-4, 0}, + {-2.828, -2.828}, {0, -4}, {2.828, -2.828} + }; + igraph_matrix_t coords; + + printf("test_bug_805\n"); + + igraph_matrix_init(&coords, 33, 2); + for (igraph_int_t i = 0; i < 66; i++) { + MATRIX(coords, i / 2, i % 2) = coords_array[i / 2][i % 2]; + } + check_convex_hull(&coords); + igraph_matrix_destroy(&coords); +} + +void test_bug_1115(void) { + igraph_real_t coords_array[][2] = { + {37, 52}, {49, 49}, {52, 64}, {20, 26}, {40, 30}, {21, 47}, {17, 63}, {31, 62}, + {52, 33}, {51, 21}, {42, 41}, {31, 32}, {5, 25}, {12, 42}, {36, 16}, {52, 41}, + {27, 23}, {17, 33}, {13, 13}, {57, 58}, {62, 42}, {42, 57}, {16, 57}, {8, 52}, + {7, 38}, {27, 68}, {30, 48}, {43, 67}, {58, 48}, {58, 27}, {37, 69}, {38, 46}, + {46, 10}, {61, 33}, {62, 63}, {63, 69}, {32, 22}, {45, 35}, {59, 15}, {5, 6}, + {10, 17}, {21, 10}, {5, 64}, {30, 15}, {39, 10}, {32, 39}, {25, 32}, {25, 55}, + {48, 28}, {56, 37}, {30, 40} + }; + igraph_matrix_t coords; + + printf("test_bug_1115\n"); + + igraph_matrix_init(&coords, 51, 2); + for (igraph_int_t i = 0; i < 102; i++) { + MATRIX(coords, i / 2, i % 2) = coords_array[i / 2][i % 2]; + } + check_convex_hull(&coords); + igraph_matrix_destroy(&coords); +} + +int main(void) { + + test_simple(); + + test_collinear(); + + test_degenerate(); + + test_bug_805(); + + test_bug_1115(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_convex_hull_2d.out b/tests/unit/igraph_convex_hull_2d.out new file mode 100644 index 0000000..ae96c1e --- /dev/null +++ b/tests/unit/igraph_convex_hull_2d.out @@ -0,0 +1,39 @@ +test_simple +( 1 6 5 3 9 ) +[ 5 1 + 1 3 + 2 5 + 6 4 + 9 2 ] +test_collinear +( 4 0 ) +[ 11 -2 + 3 2 ] +test_degenerate +( 1 0 ) +[ 5 1 + 3 2 ] +( 0 ) +[ 3 2 ] +( ) +[ 0-by-2 ] +test_bug_805 +( 31 30 29 28 27 26 25 32 ) +[ 0 -4 + -2.828 -2.828 + -4 0 + -2.828 2.828 + 0 4 + 2.828 2.828 + 4 0 + 2.828 -2.828 ] +test_bug_1115 +( 39 42 25 30 35 20 38 32 ) +[ 5 6 + 5 64 + 27 68 + 37 69 + 63 69 + 62 42 + 59 15 + 46 10 ] diff --git a/tests/unit/igraph_correlated_game.c b/tests/unit/igraph_correlated_game.c new file mode 100644 index 0000000..1fbee91 --- /dev/null +++ b/tests/unit/igraph_correlated_game.c @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2003-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g1, g2; + + igraph_rng_seed(igraph_rng_default(), 9275); + + igraph_erdos_renyi_game_gnp(&g1, 10, 0.3, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_correlated_game(&g2, &g1, 0.9, 0.3, /* permutation=*/ NULL); + IGRAPH_ASSERT(igraph_vcount(&g1) == igraph_vcount(&g2)); + igraph_destroy(&g2); + + igraph_correlated_game(&g2, &g1, 0.0, 0.3, /* permutation=*/ NULL); + IGRAPH_ASSERT(igraph_vcount(&g1) == igraph_vcount(&g2)); + igraph_destroy(&g2); + + igraph_correlated_game(&g2, &g1, 1.0, 0.3, /* permutation=*/ NULL); + IGRAPH_ASSERT(igraph_vcount(&g1) == igraph_vcount(&g2)); + igraph_destroy(&g2); + + igraph_destroy(&g1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_correlated_pair_game.c b/tests/unit/igraph_correlated_pair_game.c new file mode 100644 index 0000000..7b43fcc --- /dev/null +++ b/tests/unit/igraph_correlated_pair_game.c @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g1, g2; + igraph_bool_t same; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_correlated_pair_game(&g1, &g2, 0, /*corr*/ 1, /*p*/ 0.99, + /*directed*/ 1, /*permutation*/ NULL); + print_graph_canon(&g1); + print_graph_canon(&g2); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_correlated_pair_game(&g1, &g2, 10, /*corr*/ 1, /*p*/ 0.5, + /*directed*/ 1, /*permutation*/ NULL); + igraph_is_same_graph(&g1, &g2, &same); + IGRAPH_ASSERT(same); + + igraph_destroy(&g1); + igraph_destroy(&g2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_correlated_pair_game.out b/tests/unit/igraph_correlated_pair_game.out new file mode 100644 index 0000000..2f08890 --- /dev/null +++ b/tests/unit/igraph_correlated_pair_game.out @@ -0,0 +1,8 @@ +directed: true +vcount: 0 +edges: { +} +directed: true +vcount: 0 +edges: { +} diff --git a/tests/unit/igraph_count_adjacent_triangles.c b/tests/unit/igraph_count_adjacent_triangles.c new file mode 100644 index 0000000..5deb02e --- /dev/null +++ b/tests/unit/igraph_count_adjacent_triangles.c @@ -0,0 +1,85 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_t count_per_vertex; + igraph_real_t total_count; + igraph_vs_t vertices; + + igraph_vector_init(&count_per_vertex, 0); + + igraph_small(&graph, 20, IGRAPH_DIRECTED, + 15, 12, 12, 10, 15, 0, 11, 10, 2, 8, 8, 6, 13, 17, 10, 10, 17, 2, 14, + 0, 16, 13, 14, 14, 0, 5, 6, 4, 0, 9, 0, 6, 10, 9, 16, 4, 14, 5, 17, + 15, 14, 9, 17, 17, 1, 4, 10, 16, 7, 0, 11, 12, 6, 13, 2, 17, 4, 0, 0, + 14, 4, 0, 6, 16, 16, 14, 13, 13, 12, 11, 3, 11, 11, 3, 6, 7, 4, 14, + 10, 8, 13, 7, 14, 2, 5, 2, 0, 14, 3, 15, 5, 5, 7, 2, 14, 15, 5, 10, + 10, 16, 7, 9, 14, 0, 15, 7, 13, 1, 15, 1, 4, 5, 4, 6, 16, 13, 6, 17, + 8, 6, 9, 3, 8, 6, 6, 14, 11, 14, 6, 10, 10, 5, 1, 0, 16, 17, 9, 1, 5, + 0, 5, 15, 8, 0, 0, 8, 5, 3, 9, 4, 13, 12, 11, 0, 11, 0, 10, 6, 4, 13, + 8, 9, 11, 11, 3, 16, 1, 2, 16, 0, 9, 8, 3, 8, 8, 7, 12, 10, 9, 3, 13, + 5, 3, 9, 6, 2, 11, 10, 1, 16, 0, 2, 10, 17, 16, 8, 11, 5, 13, 0, 19, 19, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + -1); + + igraph_vs_range(&vertices, 0, igraph_vcount(&graph)); + + printf("\nDirected multi:\n"); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, igraph_vss_all()); + print_vector(&count_per_vertex); + igraph_count_triangles(&graph, &total_count); + printf("Total count: %g\n", total_count); + IGRAPH_ASSERT(igraph_vector_sum(&count_per_vertex) == 3*total_count); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, vertices); + print_vector(&count_per_vertex); + + printf("\nUndirected multi:\n"); + igraph_to_undirected(&graph, IGRAPH_TO_UNDIRECTED_COLLAPSE, NULL); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, igraph_vss_all()); + print_vector(&count_per_vertex); + igraph_count_triangles(&graph, &total_count); + printf("Total count: %g\n", total_count); + IGRAPH_ASSERT(igraph_vector_sum(&count_per_vertex) == 3*total_count); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, vertices); + print_vector(&count_per_vertex); + + printf("\nSimple:\n"); + igraph_simplify(&graph, true, true, NULL); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, igraph_vss_all()); + print_vector(&count_per_vertex); + igraph_count_triangles(&graph, &total_count); + printf("Total count: %g\n", total_count); + IGRAPH_ASSERT(igraph_vector_sum(&count_per_vertex) == 3*total_count); + igraph_count_adjacent_triangles(&graph, &count_per_vertex, vertices); + print_vector(&count_per_vertex); + + igraph_vs_destroy(&vertices); + + igraph_destroy(&graph); + + igraph_vector_destroy(&count_per_vertex); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_count_adjacent_triangles.out b/tests/unit/igraph_count_adjacent_triangles.out new file mode 100644 index 0000000..82cd89d --- /dev/null +++ b/tests/unit/igraph_count_adjacent_triangles.out @@ -0,0 +1,15 @@ + +Directed multi: +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) +Total count: 78 +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) + +Undirected multi: +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) +Total count: 78 +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) + +Simple: +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) +Total count: 78 +( 37 10 12 4 18 14 24 11 15 10 8 6 1 15 17 6 20 6 0 0 ) diff --git a/tests/unit/igraph_count_multiple.c b/tests/unit/igraph_count_multiple.c new file mode 100644 index 0000000..ea769e9 --- /dev/null +++ b/tests/unit/igraph_count_multiple.c @@ -0,0 +1,37 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t counts; + + igraph_vector_int_init(&counts, 0); + + /* undirected case */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, + 0,1, 0,1, 0,1, 0,0, 1,1, 1,1, + -1); + + igraph_count_multiple(&graph, &counts, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + print_vector_int(&counts); + + igraph_destroy(&graph); + + /* directed case */ + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, 0,1, 0,1, 0,0, 1,1, 1,1, + -1); + + igraph_count_multiple(&graph, &counts, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + print_vector_int(&counts); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&counts); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_count_multiple.out b/tests/unit/igraph_count_multiple.out new file mode 100644 index 0000000..e5e3345 --- /dev/null +++ b/tests/unit/igraph_count_multiple.out @@ -0,0 +1,2 @@ +( 3 3 3 1 2 2 ) +( 3 3 3 1 2 2 ) diff --git a/tests/unit/igraph_create.c b/tests/unit/igraph_create.c new file mode 100644 index 0000000..80395a6 --- /dev/null +++ b/tests/unit/igraph_create.c @@ -0,0 +1,40 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_int_t v; + igraph_t g; + + igraph_vector_int_init(&v, 9); + /* error: IGRAPH_EINVAL */ + VECTOR(v)[8] = 0; + CHECK_ERROR(igraph_create(&g, &v, 0, 0), IGRAPH_EINVAL); + + /* error: IGRAPH_EINVVID */ + igraph_vector_int_resize(&v, 8); + VECTOR(v)[7] = -1; + CHECK_ERROR(igraph_create(&g, &v, 10, 1), IGRAPH_EINVVID); + + igraph_vector_int_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_decompose_strong.c b/tests/unit/igraph_decompose_strong.c new file mode 100644 index 0000000..de425c4 --- /dev/null +++ b/tests/unit/igraph_decompose_strong.c @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t ring, g; + igraph_graph_list_t complist; + igraph_int_t i; + + igraph_graph_list_init(&complist, 0); + + /* A directed ring, a single strongly connected component */ + igraph_ring(&ring, 10, IGRAPH_DIRECTED, 0, 1); + igraph_decompose(&ring, &complist, IGRAPH_STRONG, -1, 0); + print_graph(igraph_graph_list_get_ptr(&complist, 0)); + igraph_destroy(&ring); + + /* a toy graph, three components maximum, with at least 2 vertices each */ + /* 0 >-> 1 >-> 3 >-> 4 + ^ v + \< 2 < / */ + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 0, + 1, 3, + 3, 4, + -1); + igraph_decompose(&g, &complist, IGRAPH_STRONG, 3, 2); + for (i = 0; i < igraph_graph_list_size(&complist); i++) { + print_graph(igraph_graph_list_get_ptr(&complist, i)); + } + igraph_destroy(&g); + + igraph_graph_list_destroy(&complist); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_decompose_strong.out b/tests/unit/igraph_decompose_strong.out new file mode 100644 index 0000000..3194273 --- /dev/null +++ b/tests/unit/igraph_decompose_strong.out @@ -0,0 +1,21 @@ +directed: true +vcount: 10 +edges: { +0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 0 +} +directed: true +vcount: 3 +edges: { +0 1 +1 2 +2 0 +} diff --git a/tests/unit/igraph_degree.c b/tests/unit/igraph_degree.c new file mode 100644 index 0000000..4507045 --- /dev/null +++ b/tests/unit/igraph_degree.c @@ -0,0 +1,130 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void all_degs(const igraph_t *g, igraph_vector_int_t *res, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_int_t n = igraph_vcount(g); + igraph_vector_int_resize(res, n); + for (igraph_int_t i = 0; i < n; i++) { + igraph_degree_1(g, &VECTOR(*res)[i], i, mode, loops); + } +} + +int main(void) { + igraph_vector_int_t v, v2, seq; + igraph_t g; + + igraph_vector_int_init(&seq, 3); + igraph_vector_int_init(&v, 0); + igraph_vector_int_init(&v2, 0); + VECTOR(seq)[0] = 2; + VECTOR(seq)[1] = 0; + VECTOR(seq)[2] = 2; + + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 2,2, 3,2, 2,3, -1); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("out, loops twice: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf(" in, loops twice: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS_TWICE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_IN, IGRAPH_LOOPS_TWICE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("all, loops twice: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("out, loops once: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf(" in, loops once: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS_ONCE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("all, loops once: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("out, no loops: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_OUT, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf(" in, no loops: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_IN, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + igraph_vector_int_clear(&v); + igraph_vector_int_clear(&v2); + printf("all, no loops: "); + igraph_degree(&g, &v, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_vector_int(&v); + all_degs(&g, &v2, IGRAPH_ALL, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(igraph_vector_int_all_e(&v, &v2)); + + /* Invalid mode */ + CHECK_ERROR(igraph_degree(&g, &v, igraph_vss_vector(&seq), (igraph_neimode_t)0, + IGRAPH_LOOPS), IGRAPH_EINVMODE); + + /* Vertex does not exist */ + VECTOR(seq)[0] = 4; + CHECK_ERROR(igraph_degree(&g, &v, igraph_vss_vector(&seq), IGRAPH_ALL, IGRAPH_LOOPS), IGRAPH_EINVVID); + + igraph_destroy(&g); + igraph_vector_int_destroy(&v2); + igraph_vector_int_destroy(&v); + igraph_vector_int_destroy(&seq); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_degree.out b/tests/unit/igraph_degree.out new file mode 100644 index 0000000..883a034 --- /dev/null +++ b/tests/unit/igraph_degree.out @@ -0,0 +1,9 @@ +out, loops twice: ( 1 1 3 1 ) + in, loops twice: ( 0 1 3 2 ) +all, loops twice: ( 1 2 6 3 ) +out, loops once: ( 1 1 3 1 ) + in, loops once: ( 0 1 3 2 ) +all, loops once: ( 1 2 5 3 ) +out, no loops: ( 1 1 2 1 ) + in, no loops: ( 0 1 2 2 ) +all, no loops: ( 1 2 4 3 ) diff --git a/tests/unit/igraph_degree_sequence_game.c b/tests/unit/igraph_degree_sequence_game.c new file mode 100644 index 0000000..2f498a0 --- /dev/null +++ b/tests/unit/igraph_degree_sequence_game.c @@ -0,0 +1,265 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +igraph_bool_t compare_degrees(const igraph_vector_int_t* expected, const igraph_vector_int_t *observed) { + const igraph_int_t n = igraph_vector_int_size(expected); + + if (igraph_vector_int_size(observed) != n) { + return false; + } + + for (igraph_int_t i = 0; i < n; i++) { + if (VECTOR(*expected)[i] != VECTOR(*observed)[i]) { + return false; + } + } + + return true; +} + +int main(void) { + igraph_t g, rg; + igraph_bool_t is_simple, is_connected; + + const igraph_int_t outarr[] = {2, 3, 2, 3, 3, 3, 3, 1, 4, 4}; + const igraph_int_t inarr[] = {3, 6, 2, 0, 2, 2, 4, 3, 3, 3}; + + const igraph_int_t n = sizeof(outarr) / sizeof(outarr[0]); + + const igraph_vector_int_t outdeg = igraph_vector_int_view(outarr, n); + const igraph_vector_int_t indeg = igraph_vector_int_view(inarr, n); + + igraph_vector_int_t empty; + igraph_vector_int_t degrees, rg_degrees; + + igraph_rng_seed(igraph_rng_default(), 333); + + igraph_vector_int_init(&empty, 0); + + /* This vector is used to check that the degrees of the result + * match the requested degrees. */ + igraph_vector_int_init(°rees, 0); + igraph_vector_int_init(&rg_degrees, 0); + + + /* Configuration model, undirected non-simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, NULL, IGRAPH_DEGSEQ_CONFIGURATION); + + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, NULL, IGRAPH_DEGSEQ_CONFIGURATION); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + + /* Configuration model, directed non-simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_CONFIGURATION); + + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&indeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, &empty, IGRAPH_DEGSEQ_CONFIGURATION); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + + /* Configuration model, undirected simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE); + + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&rg, 2000, 2000, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_degree(&rg, &rg_degrees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + + igraph_degree_sequence_game(&g, &rg_degrees, NULL, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&rg_degrees, °rees)); + + igraph_destroy(&g); + igraph_destroy(&rg); + + + /* Configuration model, directed simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE); + + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&indeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, &empty, IGRAPH_DEGSEQ_CONFIGURATION_SIMPLE); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + + /* Fast heuristic method, undirected simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, NULL, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, NULL, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + + /* Fast heuristic method, directed simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&indeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, &empty, IGRAPH_DEGSEQ_FAST_HEUR_SIMPLE); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + + /* Edge-swithching MCMC, undirected, simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, NULL, IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_destroy(&g); + + + /* Edge-swithching MCMC, directed, simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, &indeg, IGRAPH_DEGSEQ_EDGE_SWITCHING_SIMPLE); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&indeg, °rees)); + + igraph_destroy(&g); + + + /* Viger-Latapy method, undirected connected simple graphs */ + + igraph_degree_sequence_game(&g, &outdeg, NULL, IGRAPH_DEGSEQ_VL); + + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == n); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(is_simple); + + igraph_is_connected(&g, &is_connected, IGRAPH_WEAK); + IGRAPH_ASSERT(is_connected); + + igraph_degree(&g, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + IGRAPH_ASSERT(compare_degrees(&outdeg, °rees)); + + igraph_destroy(&g); + + igraph_degree_sequence_game(&g, &empty, NULL, IGRAPH_DEGSEQ_VL); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + /* This degree sequence contains a zero degree, so it cannot be realized by a connected graph. */ + CHECK_ERROR(igraph_degree_sequence_game(&g, &indeg, NULL, IGRAPH_DEGSEQ_VL), IGRAPH_EINVAL); + + igraph_vector_int_destroy(&rg_degrees); + igraph_vector_int_destroy(°rees); + igraph_vector_int_destroy(&empty); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_delaunay_graph.c b/tests/unit/igraph_delaunay_graph.c new file mode 100644 index 0000000..98f2ed1 --- /dev/null +++ b/tests/unit/igraph_delaunay_graph.c @@ -0,0 +1,217 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +igraph_error_t delaunay( + igraph_real_t *points, igraph_int_t numpoints, + igraph_int_t dimension, igraph_bool_t printing) { + + igraph_matrix_t points_mat; + igraph_t g; + + IGRAPH_CHECK(igraph_matrix_init_array(&points_mat, points, numpoints, dimension, IGRAPH_ROW_MAJOR)); + IGRAPH_FINALLY(igraph_matrix_destroy, &points_mat); + IGRAPH_CHECK(igraph_delaunay_graph(&g, &points_mat)); + IGRAPH_FINALLY(igraph_destroy, &g); + + if (printing) { + printf("%" IGRAPH_PRId "\n", igraph_ecount(&g)); + print_graph_canon(&g); + } + + igraph_matrix_destroy(&points_mat); + igraph_destroy(&g); + IGRAPH_FINALLY_CLEAN(2); + return IGRAPH_SUCCESS; +} + +#define PTCOUNT(points, dim) sizeof(points) / sizeof(points[0]) / dim + +int main(void) { + igraph_real_t points_raw[] = { + 0.311138, 0.59391, 0.67498, 0.1694, 0.822953, 0.904456, 0.129484, + 0.350282, 0.756002, 0.373154, 0.763904, 0.870096, 0.786728, + 0.00985041, 0.406027, 0.504641, 0.174089, 0.358568, 0.388813, + 0.744543, 0.0681765, 0.544161, 0.289917, 0.487095, 0.605402, + 0.270968, 0.978097, 0.955441, 0.495331, 0.688885, 0.693659, 0.579305, + 0.169171, 0.623525, 0.725786, 0.250287, 0.00648143, 0.743068, + 0.71552, 0.534675, 0.757967, 0.533673, 0.804339, 0.113337, 0.445796, + 0.410465, 0.38357, 0.918967, 0.906415, 0.893578, 0.501743, 0.914856, + 0.561324, 0.818718, 0.158764, 0.903373, 0.213128, 0.702836, 0.672937, + 0.291723, 0.855333, 0.0981787, 0.804335, 0.702915, 0.824296, + 0.294826, 0.702266, 0.346332, 0.223656, 0.640166, 0.404121, 0.151721, + 0.32827, 0.290019, 0.875388, 0.221269, 0.260946, 0.443243, 0.97165, + 0.11638, 0.246203, 0.447883, 0.987007, 0.0692745, 0.109563, 0.339652, + 0.0666818, 0.318156, 0.92505, 0.439913, 0.575609, 0.577994, 0.494698, + 0.335231, 0.5605, 0.701834, 0.863751, 0.129853, 0.641124, 0.277378, + 0.897763, 0.429955, 0.331589, 0.925289, 0.980783, 0.835259, 0.291818, + 0.693408, 0.631442, 0.53206, 0.159631, 0.350867, 0.699043, 0.993084, + 0.638868, 0.933556, 0.524775, 0.598018, 0.565078, 0.639017, 0.480785, + 0.0698051, 0.60897, 0.350966, 0.411467, 0.0688254, 0.430729, + 0.761323, 0.359096, 0.590563, 0.372144, 0.1754, 0.422717, 0.296634, + 0.874941, 0.32733, 0.616607, 0.547951, 0.902168, 0.369618, 0.226083, + 0.10907, 0.0655338, 0.456096, 0.633249, 0.676374, 0.9357, 0.945376, + 0.581192, 0.95893, 0.655664, 0.316474, 0.752296, 0.14192, 0.0125042, + 0.595269, 0.084296, 0.281774, 0.6996, 0.66766, 0.478567, 0.505426, + 0.716551, 0.232743, 0.191, 0.0114542, 0.609768, 0.903796, 0.772037, + 0.60114, 0.758929, 0.770639, 0.968881, 0.382973, 0.248955, 0.290848, + 0.752831, 0.0109218, 0.903305, 0.762493, 0.696122, 0.91684, + 0.0138796, 0.45168, 0.668332, 0.0793168, 0.499606, 0.321686, + 0.0934459, 0.499221, 0.92654, 0.917383, 0.479325, 0.147302, 0.9623, + 0.124589, 0.752746, 0.127419, 0.211051, 0.410824 + }; + + igraph_real_t point_cloud_3d[] = { + 0.125269, 0.195444, 0.285089, 0.992534, 0.250088, 0.551803, 0.104234, + 0.99641, 0.72404, 0.00427073, 0.721804, 0.00514948, 0.0685917, + 0.504841, 0.884932, 0.320637, 0.22425, 0.107845, 0.460224, 0.923877, + 0.480411, 0.139484, 0.687409, 0.871486, 0.0273725, 0.518508, + 0.472275, 0.57603, 0.848422, 0.79629, 0.844017, 0.51724, 0.486733, + 0.886948, 0.41991, 0.626941, 0.575853, 0.399617, 0.415894, 0.478654, + 0.586383, 0.661319, 0.413907, 0.836301, 0.893907, 0.887531, 0.451777, + 0.392684, 0.488796, 0.1865, 0.518422, 0.087241, 0.424709, 0.210399, + 0.930499, 0.503204, 0.44653, 0.0644197, 0.375475, 0.510661, 0.931541, + 0.742055, 0.739896, 0.320497, 0.949405, 0.00282601, 0.160896, + 0.712019, 0.966189, 0.172367, 0.216689, 0.132008, 0.955499, 0.276767, + 0.271158, 0.085173, 0.306008, 0.621809, 0.826937, 0.702707, 0.367152, + 0.0914104, 0.315055, 0.726888, 0.759314, 0.759621, 0.0954736, + 0.35047, 0.322236, 0.676948, 0.70161, 0.967861, 0.80878, 0.345871, + 0.617758, 0.0890548, 0.947943, 0.93674, 0.00637263, 0.798722, 0.1123, + 0.883446, 0.270618, 0.859522, 0.527832, 0.504072, 0.561741, 0.614159, + 0.611909, 0.779264, 0.0651863, 0.760387, 0.934354, 0.00447453, + 0.652217, 0.00252835, 0.13965, 0.281917, 0.408229, 0.140262, + 0.951417, 0.945453, 0.8125, 0.404407, 0.457046, 0.839765, 0.695236, + 0.345371, 0.372619, 0.33457, 0.484926, 0.329866, 0.813254, 0.0181675, + 0.294805, 0.616989, 0.403054, 0.123351, 0.137132, 0.641034, 0.804549, + 0.776808, 0.193548, 0.0441299, 0.823612, 0.919071, 0.559754, + 0.636797, 0.0478928, 0.36961 + }; + + igraph_real_t point_cloud_4d[] = { + 0.768464, 0.05359, 0.69102, 0.246049, 0.996004, 0.189829, 0.00302669, + 0.187796, 0.44531, 0.711051, 0.946157, 0.764919, 0.583837, 0.308961, + 0.219333, 0.885349, 0.480686, 0.620953, 0.0128075, 0.650813, + 0.717234, 0.157298, 0.775933, 0.816187, 0.189236, 0.194153, 0.347867, + 0.942655, 0.177974, 0.120843, 0.298, 0.779916, 0.00154216, 0.755666, + 0.537359, 0.00281348, 0.0687358, 0.298396, 0.19734, 0.940497 + }; + + igraph_real_t points_lattice[] = { + 0,0, + 0,1, + 0,2, + 1,0, + 1,1, + 1,2, + 2,0, + 2,1, + 2,2 + }; + + igraph_real_t points_collinear[] = { + 0,0, + 2,0, + 0,2, + 1,0 + }; + + igraph_real_t points_not_quite_collinear[] = { + 0,0, + 2,0, + 0,2, + 1,0.0000000001 + }; + + igraph_real_t points_all_collinear[] = { + 1,1, + 2,2, + 3,3, + 4,4, + }; + + igraph_real_t points_all_coplanar[] = { + 0., 0., 0., + -0.9852739637886722, 0.00098610486426615, 0.17098024411421095, + 0.14677925443983744, 0.5177782140612408, 0.842829503226861, + -0.8384947093488347, 0.518764318925507, 1.0138097473410719 + }; + + igraph_real_t points_duplicate[] ={ + 1,1, + 0,1, + 1,0, + 1,1 + }; + + printf("100 point cloud in 2d\n"); + delaunay(points_raw, PTCOUNT(points_raw, 2), 2, true); + + printf("50 point cloud in 3d\n"); + delaunay(point_cloud_3d, PTCOUNT(point_cloud_3d, 3), 3, true); + + printf("10 point cloud in 4d\n"); + delaunay(point_cloud_4d, PTCOUNT(point_cloud_4d, 4), 4, true); + + printf("3x3 square lattice\n"); + delaunay(points_lattice, PTCOUNT(points_lattice, 2), 2, true); + + printf("Triangle with one subdivided edge\n"); + delaunay(points_collinear, PTCOUNT(points_collinear, 2), 2, true); + + printf("Triangle with one subdivided edge, point then slightly perturbed\n"); + delaunay(points_not_quite_collinear, PTCOUNT(points_not_quite_collinear, 2), 2, true); + + /* TODO: Planned to be supported without error in the future. */ + printf("4 points on a line in 2d\n"); + CHECK_ERROR(delaunay(points_all_collinear, PTCOUNT(points_all_collinear, 2), 2, true), IGRAPH_EINVAL); + + /* TODO: Planned to be supported without error in the future. */ + printf("4 points on a plane in 3d\n"); + CHECK_ERROR(delaunay(points_all_coplanar, PTCOUNT(points_all_coplanar, 3), 3, true), IGRAPH_EINVAL); + + printf("Identical points\n"); + CHECK_ERROR(delaunay(points_duplicate, PTCOUNT(points_duplicate, 2), 2,false), IGRAPH_EINVAL); + + /* The below coordinate arrays are used only partially, hence no PTCOUNT */ + + printf("One point in 2d\n"); + delaunay(points_raw, 1, 2, true); + + printf("No points in 2d\n"); + delaunay(points_raw, 0, 2, true); + + printf("0x0 matrix\n"); + delaunay(points_raw, 0, 0, true); + + printf("2 points in 0d\n"); + CHECK_ERROR(delaunay(points_raw, 2, 0, true), IGRAPH_EINVAL); + + printf("4 points in 3d\n"); + delaunay(point_cloud_3d, 4, 3, true); + + /* TODO: Planned to be supported without error in the future. */ + printf("3 points in 3d\n"); + CHECK_ERROR(delaunay(point_cloud_3d, 3, 3, true), IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_delaunay_graph.out b/tests/unit/igraph_delaunay_graph.out new file mode 100644 index 0000000..4412a36 --- /dev/null +++ b/tests/unit/igraph_delaunay_graph.out @@ -0,0 +1,713 @@ +100 point cloud in 2d +287 +directed: false +vcount: 100 +edges: { +0 11 +0 34 +0 53 +0 64 +1 12 +1 49 +1 76 +1 81 +1 92 +1 96 +1 98 +2 5 +2 24 +2 56 +2 73 +2 89 +2 90 +2 95 +3 8 +3 42 +3 55 +3 71 +3 99 +4 17 +4 19 +4 20 +4 32 +4 33 +4 50 +4 67 +4 69 +5 85 +5 89 +5 90 +6 21 +6 30 +6 41 +6 82 +6 88 +7 11 +7 22 +7 64 +7 80 +8 55 +8 87 +8 99 +9 14 +9 23 +9 51 +9 53 +9 63 +9 64 +10 16 +10 71 +10 77 +10 91 +10 94 +11 16 +11 22 +11 34 +11 38 +11 40 +11 64 +12 49 +12 61 +12 75 +12 93 +12 96 +13 52 +13 56 +13 73 +13 95 +14 26 +14 47 +14 58 +14 59 +14 63 +14 64 +15 19 +15 54 +15 68 +15 72 +15 79 +15 84 +16 18 +16 28 +16 34 +16 40 +16 77 +16 94 +17 29 +17 32 +17 33 +17 37 +17 76 +17 81 +18 27 +18 28 +18 77 +18 91 +19 20 +19 33 +19 54 +19 84 +20 44 +20 50 +20 84 +21 30 +21 37 +21 48 +21 76 +21 88 +21 98 +22 36 +22 38 +22 46 +22 61 +22 66 +22 80 +23 25 +23 51 +23 63 +23 74 +24 52 +24 89 +24 95 +25 26 +25 63 +25 74 +25 83 +26 47 +26 63 +26 72 +26 83 +26 85 +27 28 +27 51 +27 56 +28 34 +28 51 +28 53 +29 33 +29 49 +29 75 +29 81 +30 41 +30 48 +30 97 +31 79 +31 84 +31 85 +31 89 +32 37 +32 67 +33 54 +33 61 +33 75 +34 53 +35 62 +35 65 +35 66 +35 96 +36 38 +36 65 +36 66 +36 87 +37 48 +37 67 +37 76 +37 86 +37 97 +38 40 +38 87 +38 99 +39 41 +39 86 +39 97 +40 94 +40 99 +41 52 +41 86 +41 97 +42 43 +42 55 +42 71 +42 78 +43 71 +43 78 +43 82 +43 91 +44 50 +44 52 +44 69 +44 84 +44 86 +44 89 +45 58 +45 59 +45 68 +45 72 +45 80 +46 61 +46 66 +46 93 +47 59 +47 72 +48 97 +49 75 +49 81 +50 69 +51 53 +51 56 +51 74 +52 86 +52 89 +52 95 +53 64 +54 61 +54 68 +54 80 +55 78 +55 87 +56 57 +56 73 +56 74 +56 90 +57 74 +57 83 +57 90 +58 59 +58 64 +58 80 +59 72 +60 62 +60 82 +60 88 +60 92 +60 96 +61 75 +61 80 +61 93 +62 65 +62 70 +62 82 +62 96 +64 80 +65 66 +65 70 +65 87 +66 93 +66 96 +67 69 +67 86 +68 72 +68 80 +69 86 +70 78 +70 82 +70 87 +71 91 +71 94 +71 99 +72 79 +72 85 +73 95 +74 83 +76 81 +76 98 +77 91 +78 82 +78 87 +79 84 +79 85 +82 88 +83 85 +83 90 +84 89 +85 89 +85 90 +86 97 +87 99 +88 92 +88 98 +92 96 +92 98 +93 96 +94 99 +} +50 point cloud in 3d +289 +directed: false +vcount: 50 +edges: { +0 3 +0 5 +0 8 +0 16 +0 17 +0 19 +0 23 +0 25 +0 27 +0 29 +0 33 +0 38 +0 39 +0 43 +0 49 +1 11 +1 15 +1 16 +1 18 +1 20 +1 24 +1 32 +1 33 +1 40 +1 42 +1 44 +1 49 +2 3 +2 4 +2 6 +2 7 +2 8 +2 9 +2 14 +2 21 +2 22 +2 30 +2 32 +2 34 +2 37 +2 46 +3 5 +3 8 +3 17 +3 21 +3 23 +3 31 +3 32 +3 34 +3 37 +3 39 +3 47 +4 7 +4 8 +4 22 +4 27 +4 33 +4 41 +4 46 +5 12 +5 16 +5 23 +5 31 +5 38 +5 39 +5 43 +5 45 +5 47 +5 49 +6 9 +6 13 +6 14 +6 21 +6 26 +6 28 +6 30 +6 31 +6 34 +6 35 +6 36 +6 37 +6 43 +6 48 +7 14 +7 22 +7 34 +7 41 +7 46 +8 13 +8 17 +8 19 +8 25 +8 27 +8 29 +8 31 +8 34 +8 43 +8 46 +9 13 +9 14 +9 20 +9 30 +9 34 +9 35 +9 40 +9 41 +9 48 +10 11 +10 12 +10 15 +10 18 +10 20 +10 26 +10 35 +10 42 +10 48 +11 12 +11 13 +11 15 +11 16 +11 18 +11 20 +11 29 +11 33 +11 35 +11 41 +11 42 +12 16 +12 26 +12 29 +12 35 +12 36 +12 42 +12 43 +12 45 +12 49 +13 14 +13 20 +13 29 +13 34 +13 35 +13 41 +13 43 +13 46 +14 20 +14 22 +14 30 +14 33 +14 34 +14 40 +14 41 +14 46 +15 18 +15 24 +15 26 +15 28 +15 32 +15 42 +15 45 +16 19 +16 25 +16 27 +16 29 +16 33 +16 35 +16 41 +16 42 +16 43 +16 49 +17 19 +17 23 +17 31 +17 39 +17 43 +18 20 +18 24 +18 26 +18 32 +18 40 +19 25 +19 27 +19 29 +19 43 +19 46 +20 26 +20 30 +20 32 +20 33 +20 35 +20 40 +20 41 +20 48 +21 30 +21 31 +21 32 +21 34 +21 36 +21 37 +21 47 +22 33 +22 40 +22 41 +23 38 +23 39 +23 47 +24 28 +24 32 +24 42 +24 44 +24 45 +24 47 +25 27 +25 29 +25 46 +26 28 +26 32 +26 35 +26 36 +26 37 +26 42 +26 43 +26 45 +26 48 +27 29 +27 33 +27 41 +27 46 +27 49 +28 32 +28 36 +28 37 +28 45 +28 47 +28 48 +29 33 +29 35 +29 41 +29 43 +29 46 +30 32 +30 37 +30 40 +30 48 +31 34 +31 36 +31 39 +31 43 +31 45 +31 47 +32 37 +32 40 +32 47 +32 48 +33 38 +33 40 +33 41 +33 44 +33 49 +34 35 +34 43 +34 46 +35 41 +35 43 +35 48 +36 37 +36 43 +36 45 +36 47 +37 47 +37 48 +38 42 +38 44 +38 45 +38 47 +38 49 +39 43 +39 45 +40 48 +41 46 +42 44 +42 45 +42 47 +42 49 +43 45 +44 47 +44 49 +45 47 +45 49 +47 49 +} +10 point cloud in 4d +39 +directed: false +vcount: 10 +edges: { +0 1 +0 2 +0 3 +0 4 +0 5 +0 7 +0 8 +1 2 +1 3 +1 4 +1 5 +1 7 +1 8 +1 9 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 4 +3 5 +3 6 +3 7 +3 9 +4 5 +4 6 +4 7 +4 8 +4 9 +5 6 +5 7 +6 7 +6 8 +6 9 +7 8 +7 9 +8 9 +} +3x3 square lattice +16 +directed: false +vcount: 9 +edges: { +0 1 +0 3 +1 2 +1 3 +1 4 +1 5 +2 5 +3 4 +3 6 +3 7 +4 5 +4 7 +5 7 +5 8 +6 7 +7 8 +} +Triangle with one subdivided edge +5 +directed: false +vcount: 4 +edges: { +0 2 +0 3 +1 2 +1 3 +2 3 +} +Triangle with one subdivided edge, point then slightly perturbed +6 +directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +} +4 points on a line in 2d +4 points on a plane in 3d +Identical points +One point in 2d +0 +directed: false +vcount: 1 +edges: { +} +No points in 2d +0 +directed: false +vcount: 0 +edges: { +} +0x0 matrix +0 +directed: false +vcount: 0 +edges: { +} +2 points in 0d +4 points in 3d +6 +directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +} +3 points in 3d diff --git a/tests/unit/igraph_delete_edges.c b/tests/unit/igraph_delete_edges.c new file mode 100644 index 0000000..ca6591d --- /dev/null +++ b/tests/unit/igraph_delete_edges.c @@ -0,0 +1,56 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_es_t es; + + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, -1); + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, 3, 2, -1); + + /* error test, no such edge to delete */ + CHECK_ERROR(igraph_delete_edges(&g, es), IGRAPH_EINVAL); + if (igraph_ecount(&g) != 3) { + return 3; + } + + /* error test, invalid vertex ID */ + igraph_es_destroy(&es); + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, 10, 2, -1); + CHECK_ERROR(igraph_delete_edges(&g, es), IGRAPH_EINVVID); + if (igraph_ecount(&g) != 3) { + return 5; + } + + /* error test, invalid (odd) length */ + igraph_es_destroy(&es); + igraph_es_pairs_small(&es, IGRAPH_DIRECTED, 0, 1, 2, -1); + CHECK_ERROR(igraph_delete_edges(&g, es), IGRAPH_EINVAL); + if (igraph_ecount(&g) != 3) { + return 7; + } + + igraph_es_destroy(&es); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_delete_vertices.c b/tests/unit/igraph_delete_vertices.c new file mode 100644 index 0000000..ed1ba9d --- /dev/null +++ b/tests/unit/igraph_delete_vertices.c @@ -0,0 +1,32 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,0, -1); + /* vertex does not exist */ + CHECK_ERROR(igraph_delete_vertices(&g, igraph_vss_1(3)), IGRAPH_EINVVID); + + igraph_destroy(&g); + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_density.c b/tests/unit/igraph_density.c new file mode 100644 index 0000000..0b21959 --- /dev/null +++ b/tests/unit/igraph_density.c @@ -0,0 +1,214 @@ +/* + igraph library. + Copyright (C) 2006-2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void test_density(const igraph_t *graph, igraph_bool_t loops) { + igraph_real_t density; + + if (igraph_density(graph, NULL, &density, loops)) { + printf("FAILED!\n"); + return; + } + + if (isnan(density)) { + printf("nan\n"); + } else { + printf("%.4f\n", density); + } +} + +/* Assumes an attribute handler. + * + * Takes a multigraph, simplifies edges and records edge multiplicites + * as "weights", then verifies that the density of the unweighted multigraph + * is the same as the density of the weighted simplified graph. + */ +void test_weighted_density(const igraph_t *graph, igraph_bool_t loops) { + igraph_t wg; + igraph_vector_t weights; + igraph_attribute_combination_t comb; + igraph_real_t dens1, dens2; + + igraph_copy(&wg, graph); + + igraph_vector_init(&weights, igraph_ecount(graph)); + igraph_vector_fill(&weights, 1); + SETEANV(&wg, "weight", &weights); + + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + IGRAPH_NO_MORE_ATTRIBUTES); + + igraph_simplify(&wg, /* remove_multiple */ true, /* remove_loops */ false, &comb); + + EANV(&wg, "weight", &weights); + + igraph_density(graph, NULL, &dens1, loops); + igraph_density(&wg, &weights, &dens2, loops); + + IGRAPH_ASSERT(dens1 == dens2); + + igraph_attribute_combination_destroy(&comb); + igraph_vector_destroy(&weights); + igraph_destroy(&wg); +} + +int main(void) { + + igraph_t g; + igraph_vector_int_t v; + + igraph_vector_int_init(&v, 0); + + /* Test graphs with no vertices and no edges */ + igraph_create(&g, &v, 0, IGRAPH_UNDIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 0, IGRAPH_DIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with one vertex and no edges */ + igraph_create(&g, &v, 1, IGRAPH_UNDIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with one vertex and a loop edge */ + igraph_vector_int_resize(&v, 2); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 0; + igraph_create(&g, &v, 1, IGRAPH_UNDIRECTED); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with one vertex and two loop edges */ + igraph_vector_int_resize(&v, 4); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 0; + VECTOR(v)[2] = 0; + VECTOR(v)[3] = 0; + igraph_create(&g, &v, 1, IGRAPH_UNDIRECTED); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with two vertices and one edge between them */ + igraph_vector_int_resize(&v, 2); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 1; + igraph_create(&g, &v, 2, IGRAPH_UNDIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with two vertices, one edge between them and a loop on one + * of them */ + igraph_vector_int_resize(&v, 4); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 1; + VECTOR(v)[2] = 1; + VECTOR(v)[3] = 1; + igraph_create(&g, &v, 2, IGRAPH_UNDIRECTED); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Test graphs with two vertices, one edge between them and a loop on both + * of them */ + igraph_vector_int_resize(&v, 6); + VECTOR(v)[0] = 0; + VECTOR(v)[1] = 1; + VECTOR(v)[2] = 1; + VECTOR(v)[3] = 1; + VECTOR(v)[4] = 0; + VECTOR(v)[5] = 0; + igraph_create(&g, &v, 2, IGRAPH_UNDIRECTED); + test_density(&g, true); + igraph_destroy(&g); + igraph_create(&g, &v, 1, IGRAPH_DIRECTED); + test_density(&g, true); + igraph_destroy(&g); + printf("======\n"); + + /* Zachary karate club graph */ + igraph_famous(&g, "zachary"); + test_density(&g, false); + test_density(&g, true); + igraph_destroy(&g); + + igraph_vector_int_destroy(&v); + + VERIFY_FINALLY_STACK(); + + /* Test weighted density. + * These tests require an attribute table and use randomness. */ + + igraph_set_attribute_table(&igraph_cattribute_table); + igraph_rng_seed(igraph_rng_default(), 1234); + + igraph_erdos_renyi_game_gnm(&g, 10, 30, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + test_weighted_density(&g, IGRAPH_LOOPS); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 9, 30, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + test_weighted_density(&g, IGRAPH_LOOPS); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 8, 30, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + test_weighted_density(&g, IGRAPH_LOOPS); + igraph_destroy(&g); + + igraph_erdos_renyi_game_gnm(&g, 7, 30, IGRAPH_DIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + test_weighted_density(&g, IGRAPH_LOOPS); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_density.out b/tests/unit/igraph_density.out new file mode 100644 index 0000000..cbd7583 --- /dev/null +++ b/tests/unit/igraph_density.out @@ -0,0 +1,29 @@ +nan +nan +nan +nan +====== +nan +0.0000 +nan +0.0000 +====== +1.0000 +1.0000 +====== +2.0000 +2.0000 +====== +1.0000 +0.3333 +0.5000 +0.2500 +====== +0.6667 +0.5000 +====== +1.0000 +0.7500 +====== +0.1390 +0.1311 diff --git a/tests/unit/igraph_diameter.c b/tests/unit/igraph_diameter.c new file mode 100644 index 0000000..cc077e2 --- /dev/null +++ b/tests/unit/igraph_diameter.c @@ -0,0 +1,90 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_real_t result; + igraph_int_t from, to; + igraph_vector_int_t path_edge, path_vertex, edge_vec; + igraph_int_t vec[] = {2,8}; + igraph_es_t edge_sele; + + printf("diameter of Barabasi graph:\n"); + + igraph_rng_seed(igraph_rng_default(), 1234); + igraph_barabasi_game(&g, 30, /*power=*/ 1, 30, 0, 0, /*A=*/ 1, + IGRAPH_DIRECTED, IGRAPH_BARABASI_BAG, + /*start_from=*/ 0); + igraph_diameter(&g, NULL, &result, NULL, NULL, NULL, NULL, IGRAPH_UNDIRECTED, 1); + + printf("Diameter: %" IGRAPH_PRId "\n", (igraph_int_t) result); + + igraph_destroy(&g); + + printf("diameter of ring and the path in terms of edges and vertices \n"); + + igraph_ring(&g, 10, IGRAPH_DIRECTED, 0, 0); + igraph_vector_int_init(&path_vertex, 0); + igraph_vector_int_init(&path_edge, 0); + igraph_diameter(&g, NULL, &result, &from, &to, &path_vertex, &path_edge, IGRAPH_DIRECTED, 1); + printf("diameter: %g, from %" IGRAPH_PRId " to %" IGRAPH_PRId "\n", result, + from, to); + print_vector_int(&path_vertex); + print_vector_int(&path_edge); + igraph_vector_int_destroy(&path_vertex); + igraph_vector_int_destroy(&path_edge); + + //disconnected graph + printf("disconnected ring graph\n"); + edge_vec = igraph_vector_int_view(vec, sizeof(vec) / sizeof(vec[0])); + igraph_es_vector(&edge_sele, &edge_vec); + igraph_delete_edges(&g, edge_sele); + printf("The largest path in one connected component\n"); + igraph_diameter(&g, NULL, &result, NULL, NULL, NULL, NULL, IGRAPH_DIRECTED, 1); + print_real(stdout, result, "%g"); + printf("\nuconn = False \n"); + igraph_diameter(&g, NULL, &result, NULL, NULL, NULL, NULL, IGRAPH_DIRECTED, 0); + print_real(stdout, result, "%g"); + + igraph_es_destroy(&edge_sele); + igraph_destroy(&g); + + // test graph with zero nodes + printf("\ngraph with zero nodes\n"); + igraph_empty(&g, 0, IGRAPH_DIRECTED); + igraph_diameter(&g, NULL, &result, &from, &to, NULL, NULL, IGRAPH_DIRECTED, 1); + print_real(stdout, result, "%g"); + printf("\nfrom = %" IGRAPH_PRId ", to = %" IGRAPH_PRId "\n", from, to); + igraph_destroy(&g); + + //test graph with one node + printf("graph with one node\n"); + igraph_empty(&g, 1, IGRAPH_DIRECTED); + igraph_diameter(&g, NULL, &result, &from, &to, NULL, NULL, IGRAPH_DIRECTED, 1); + print_real(stdout, result, "%g"); + printf("\nfrom = %" IGRAPH_PRId ", to = %" IGRAPH_PRId "\n", from, to); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_diameter.out b/tests/unit/igraph_diameter.out new file mode 100644 index 0000000..d0d4578 --- /dev/null +++ b/tests/unit/igraph_diameter.out @@ -0,0 +1,17 @@ +diameter of Barabasi graph: +Diameter: 2 +diameter of ring and the path in terms of edges and vertices +diameter: 9, from 0 to 9 +( 0 1 2 3 4 5 6 7 8 9 ) +( 0 1 2 3 4 5 6 7 8 ) +disconnected ring graph +The largest path in one connected component +5 +uconn = False +Inf +graph with zero nodes +NaN +from = -1, to = -1 +graph with one node +0 +from = 0, to = 0 diff --git a/tests/unit/igraph_diameter_dijkstra.c b/tests/unit/igraph_diameter_dijkstra.c new file mode 100644 index 0000000..bd18a47 --- /dev/null +++ b/tests/unit/igraph_diameter_dijkstra.c @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void call_and_print(igraph_t *g, igraph_vector_t *weights, igraph_bool_t unconn, igraph_bool_t directed) { + igraph_vector_int_t path_vertex, path_edge; + igraph_real_t result; + igraph_int_t from, to; + + igraph_vector_int_init(&path_edge, 0); + igraph_vector_int_init(&path_vertex, 0); + + igraph_diameter(g, weights, &result, &from, &to, &path_vertex, &path_edge, directed, unconn); + + printf("Diameter: "); + print_real(stdout, result, "%g"); + printf(", from %" IGRAPH_PRId " to %" IGRAPH_PRId "\n", from, to); + printf("Edges:\n"); + print_vector_int(&path_edge); + printf("Vertices:\n"); + print_vector_int(&path_vertex); + + igraph_vector_int_destroy(&path_edge); + igraph_vector_int_destroy(&path_vertex); + printf("\n"); +} + +int main(void) { + igraph_t g_ring, g_0, g_1, g_2, g_lm; + igraph_vector_t weights, weights_neg, weights_0; + + igraph_vector_init_int(&weights, 9, 1, 2, 3, 4, 5, 1, 1, 1, 1); + igraph_vector_init_int(&weights_neg, 9, -1, 2, 3, 4, 5, 1, 1, 1, 1); + igraph_vector_init(&weights_0, 0); + + igraph_empty(&g_0, 0, IGRAPH_DIRECTED); + igraph_empty(&g_1, 1, IGRAPH_DIRECTED); + igraph_empty(&g_2, 2, IGRAPH_DIRECTED); + igraph_ring(&g_ring, 10, IGRAPH_DIRECTED, 0, 0); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 4,3, 4,3, -1); + + printf("Graph with zero nodes:\n"); + call_and_print(&g_0, NULL, 1, 1); + + printf("Graph with zero nodes, weighted:\n"); + call_and_print(&g_0, &weights_0, 1, 1); + + printf("Graph with one node:\n"); + call_and_print(&g_1, NULL, 1, 1); + + printf("Graph with one node, weighted:\n"); + call_and_print(&g_1, &weights_0, 1, 1); + + printf("Graph with one node, returns inf for unconnected:\n"); + call_and_print(&g_1, NULL, 0, 1); + + printf("Graph with two nodes:\n"); + call_and_print(&g_2, NULL, 1, 1); + + printf("Graph with two nodes, returns inf for unconnected:\n"); + call_and_print(&g_2, NULL, 0, 1); + + printf("Ring without weights:\n"); + call_and_print(&g_ring, NULL, 1, 1); + + printf("Ring with weights:\n"); + call_and_print(&g_ring, &weights, 1, 1); + + printf("Graph with loops and multiple edges:\n"); + call_and_print(&g_lm, NULL, 1, 1); + + printf("Graph with loops and multiple edges, direction ignored:\n"); + call_and_print(&g_lm, NULL, 1, 0); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Ring with some negative weights:\n"); + igraph_diameter(&g_ring, &weights_neg, NULL, NULL, NULL, NULL, NULL, IGRAPH_DIRECTED, 1); + + printf("Ring with wrong weight vector size:\n"); + igraph_diameter(&g_ring, &weights_0, NULL, NULL, NULL, NULL, NULL, IGRAPH_DIRECTED, 1); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_2); + igraph_destroy(&g_ring); + igraph_destroy(&g_lm); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&weights_neg); + igraph_vector_destroy(&weights_0); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_diameter_dijkstra.out b/tests/unit/igraph_diameter_dijkstra.out new file mode 100644 index 0000000..aeeac59 --- /dev/null +++ b/tests/unit/igraph_diameter_dijkstra.out @@ -0,0 +1,79 @@ +Graph with zero nodes: +Diameter: NaN, from -1 to -1 +Edges: +( ) +Vertices: +( ) + +Graph with zero nodes, weighted: +Diameter: NaN, from -1 to -1 +Edges: +( ) +Vertices: +( ) + +Graph with one node: +Diameter: 0, from 0 to 0 +Edges: +( ) +Vertices: +( 0 ) + +Graph with one node, weighted: +Diameter: 0, from 0 to 0 +Edges: +( ) +Vertices: +( 0 ) + +Graph with one node, returns inf for unconnected: +Diameter: 0, from 0 to 0 +Edges: +( ) +Vertices: +( 0 ) + +Graph with two nodes: +Diameter: 0, from 0 to 0 +Edges: +( ) +Vertices: +( 0 ) + +Graph with two nodes, returns inf for unconnected: +Diameter: Inf, from -1 to -1 +Edges: +( ) +Vertices: +( ) + +Ring without weights: +Diameter: 9, from 0 to 9 +Edges: +( 0 1 2 3 4 5 6 7 8 ) +Vertices: +( 0 1 2 3 4 5 6 7 8 9 ) + +Ring with weights: +Diameter: 19, from 0 to 9 +Edges: +( 0 1 2 3 4 5 6 7 8 ) +Vertices: +( 0 1 2 3 4 5 6 7 8 9 ) + +Graph with loops and multiple edges: +Diameter: 2, from 0 to 3 +Edges: +( 0 3 ) +Vertices: +( 0 1 3 ) + +Graph with loops and multiple edges, direction ignored: +Diameter: 3, from 0 to 4 +Edges: +( 0 3 7 ) +Vertices: +( 0 1 3 4 ) + +Ring with some negative weights: +Ring with wrong weight vector size: diff --git a/tests/unit/igraph_disjoint_union.c b/tests/unit/igraph_disjoint_union.c new file mode 100644 index 0000000..cc5617c --- /dev/null +++ b/tests/unit/igraph_disjoint_union.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t left, right, uni; + igraph_vector_ptr_t glist; + igraph_int_t i, n; + + igraph_small(&left, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,3, -1); + igraph_small(&right, 5, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,4, -1); + + igraph_disjoint_union(&uni, &left, &right); + print_graph(&uni); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&uni); + + /* Empty graph list; the result is the directed null graph. */ + igraph_vector_ptr_init(&glist, 0); + igraph_disjoint_union_many(&uni, &glist); + print_graph(&uni); + igraph_vector_ptr_destroy(&glist); + igraph_destroy(&uni); + + /* Non-empty graph list. */ + igraph_vector_ptr_init(&glist, 10); + n = igraph_vector_ptr_size(&glist); + for (i = 0; i < n; i++) { + VECTOR(glist)[i] = IGRAPH_CALLOC(1, igraph_t); + igraph_small(VECTOR(glist)[i], 2, IGRAPH_DIRECTED, 0,1, 1,0, -1); + } + igraph_disjoint_union_many(&uni, &glist); + print_graph(&uni); + printf("\n"); + + /* Destroy and free the graph list. */ + n = igraph_vector_ptr_size(&glist); + for (i = 0; i < n; i++) { + igraph_destroy(VECTOR(glist)[i]); + free(VECTOR(glist)[i]); + } + igraph_vector_ptr_destroy(&glist); + igraph_destroy(&uni); + + return 0; +} diff --git a/tests/unit/igraph_disjoint_union.out b/tests/unit/igraph_disjoint_union.out new file mode 100644 index 0000000..ed670b1 --- /dev/null +++ b/tests/unit/igraph_disjoint_union.out @@ -0,0 +1,42 @@ +directed: false +vcount: 9 +edges: { +1 0 +2 1 +2 2 +3 2 +5 4 +6 5 +6 6 +8 6 +} + +directed: true +vcount: 0 +edges: { +} +directed: true +vcount: 20 +edges: { +0 1 +1 0 +2 3 +3 2 +4 5 +5 4 +6 7 +7 6 +8 9 +9 8 +10 11 +11 10 +12 13 +13 12 +14 15 +15 14 +16 17 +17 16 +18 19 +19 18 +} + diff --git a/tests/unit/igraph_distances_floyd_warshall.c b/tests/unit/igraph_distances_floyd_warshall.c new file mode 100644 index 0000000..6da5530 --- /dev/null +++ b/tests/unit/igraph_distances_floyd_warshall.c @@ -0,0 +1,119 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t d, d2; + igraph_vector_t weights; + + igraph_matrix_init(&d, 0, 0); + igraph_matrix_init(&d2, 0, 0); + + printf("Null graph\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + igraph_destroy(&g); + + printf("\nSingleton graph\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + igraph_destroy(&g); + + igraph_small(&g, 9, IGRAPH_DIRECTED, + 0,1, 1,2, 2,2, 2,3, 3,0, 0,4, 4,5, 3,0, 6,7, 5,4, 9,9, + -1); + + printf("\nUnweighted directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nUnweighted directed, 'in' mode\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_IN, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_IN); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nUnweighted undirected\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + igraph_vector_init_int(&weights, igraph_ecount(&g), + 2, 1, 5, 1, 2, 6, 8, 3, 3, 2, 3); + + printf("\nWeighted directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nWeighted undirected\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + VECTOR(weights)[1] = -2; + printf("\nNegative weight, directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + /* Check bad inputs */ + + /* Negative weight edge in undirected graph */ + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_ORIGINAL), IGRAPH_ENEGCYCLE); + + /* Negative cycle in directed graph */ + VECTOR(weights)[1] = -10; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), IGRAPH_ENEGCYCLE); + + /* Negative self-loop in directed graph */ + VECTOR(weights)[1] = 1; + VECTOR(weights)[10] = -1; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), IGRAPH_ENEGCYCLE); + + /* NaN weight */ + VECTOR(weights)[1] = IGRAPH_NAN; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_ORIGINAL), IGRAPH_EINVAL); + + igraph_vector_destroy(&weights); + + igraph_destroy(&g); + + igraph_matrix_destroy(&d2); + igraph_matrix_destroy(&d); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_distances_floyd_warshall.out b/tests/unit/igraph_distances_floyd_warshall.out new file mode 100644 index 0000000..18220ad --- /dev/null +++ b/tests/unit/igraph_distances_floyd_warshall.out @@ -0,0 +1,77 @@ +Null graph +[ 0-by-0 ] + +Singleton graph +[ 0 ] + +Unweighted directed +[ 0 1 2 3 1 2 Inf Inf Inf Inf + 3 0 1 2 4 5 Inf Inf Inf Inf + 2 3 0 1 3 4 Inf Inf Inf Inf + 1 2 3 0 2 3 Inf Inf Inf Inf + Inf Inf Inf Inf 0 1 Inf Inf Inf Inf + Inf Inf Inf Inf 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 1 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Unweighted directed, 'in' mode +[ 0 3 2 1 Inf Inf Inf Inf Inf Inf + 1 0 3 2 Inf Inf Inf Inf Inf Inf + 2 1 0 3 Inf Inf Inf Inf Inf Inf + 3 2 1 0 Inf Inf Inf Inf Inf Inf + 1 4 3 2 0 1 Inf Inf Inf Inf + 2 5 4 3 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf + Inf Inf Inf Inf Inf Inf 1 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Unweighted undirected +[ 0 1 2 1 1 2 Inf Inf Inf Inf + 1 0 1 2 2 3 Inf Inf Inf Inf + 2 1 0 1 3 4 Inf Inf Inf Inf + 1 2 1 0 2 3 Inf Inf Inf Inf + 1 2 3 2 0 1 Inf Inf Inf Inf + 2 3 4 3 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 1 Inf Inf + Inf Inf Inf Inf Inf Inf 1 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Weighted directed +[ 0 2 3 4 6 14 Inf Inf Inf Inf + 4 0 1 2 10 18 Inf Inf Inf Inf + 3 5 0 1 9 17 Inf Inf Inf Inf + 2 4 5 0 8 16 Inf Inf Inf Inf + Inf Inf Inf Inf 0 8 Inf Inf Inf Inf + Inf Inf Inf Inf 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Weighted undirected +[ 0 2 3 2 6 8 Inf Inf Inf Inf + 2 0 1 2 8 10 Inf Inf Inf Inf + 3 1 0 1 9 11 Inf Inf Inf Inf + 2 2 1 0 8 10 Inf Inf Inf Inf + 6 8 9 8 0 2 Inf Inf Inf Inf + 8 10 11 10 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf 3 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Negative weight, directed +[ 0 2 0 1 6 14 Inf Inf Inf Inf + 1 0 -2 -1 7 15 Inf Inf Inf Inf + 3 5 0 1 9 17 Inf Inf Inf Inf + 2 4 2 0 8 16 Inf Inf Inf Inf + Inf Inf Inf Inf 0 8 Inf Inf Inf Inf + Inf Inf Inf Inf 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] diff --git a/tests/unit/igraph_distances_floyd_warshall_speedup.c b/tests/unit/igraph_distances_floyd_warshall_speedup.c new file mode 100644 index 0000000..89f888b --- /dev/null +++ b/tests/unit/igraph_distances_floyd_warshall_speedup.c @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t d, d2; + igraph_vector_t weights; + + igraph_matrix_init(&d, 0, 0); + igraph_matrix_init(&d2, 0, 0); + + printf("Null graph\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + igraph_destroy(&g); + + printf("\nSingleton graph\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + igraph_destroy(&g); + + igraph_small(&g, 9, IGRAPH_DIRECTED, + 0,1, 1,2, 2,2, 2,3, 3,0, 0,4, 4,5, 3,0, 6,7, 5,4, 9,9, + -1); + + printf("\nUnweighted directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nUnweighted directed, 'in' mode\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_IN, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_IN); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nUnweighted undirected\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances(&g, NULL, &d2, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + igraph_vector_init_int(&weights, igraph_ecount(&g), + 2, 1, 5, 1, 2, 6, 8, 3, 3, 2, 3); + + printf("\nWeighted directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + printf("\nWeighted undirected\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + VECTOR(weights)[1] = -2; + printf("\nNegative weight, directed\n"); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + print_matrix(&d); + + igraph_distances_bellman_ford(&g, &d2, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + /* Check bad inputs */ + + /* Negative weight edge in undirected graph */ + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_ALL, IGRAPH_FLOYD_WARSHALL_TREE), IGRAPH_ENEGCYCLE); + + /* Negative cycle in directed graph */ + VECTOR(weights)[1] = -10; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), IGRAPH_ENEGCYCLE); + + /* Negative self-loop in directed graph */ + VECTOR(weights)[1] = 1; + VECTOR(weights)[10] = -1; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), IGRAPH_ENEGCYCLE); + + /* NaN weight */ + VECTOR(weights)[1] = IGRAPH_NAN; + CHECK_ERROR(igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), &weights, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE), IGRAPH_EINVAL); + igraph_destroy(&g); + + /* Unweighted directed - larger graph */ + igraph_erdos_renyi_game_gnp(&g, 100, 0.1, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_distances_floyd_warshall(&g, &d, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT, IGRAPH_FLOYD_WARSHALL_TREE); + igraph_distances_dijkstra(&g, &d2, igraph_vss_all(), igraph_vss_all(), NULL, IGRAPH_OUT); + IGRAPH_ASSERT(igraph_matrix_all_e(&d, &d2)); + + igraph_vector_destroy(&weights); + + igraph_destroy(&g); + + igraph_matrix_destroy(&d2); + igraph_matrix_destroy(&d); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_distances_floyd_warshall_speedup.out b/tests/unit/igraph_distances_floyd_warshall_speedup.out new file mode 100644 index 0000000..18220ad --- /dev/null +++ b/tests/unit/igraph_distances_floyd_warshall_speedup.out @@ -0,0 +1,77 @@ +Null graph +[ 0-by-0 ] + +Singleton graph +[ 0 ] + +Unweighted directed +[ 0 1 2 3 1 2 Inf Inf Inf Inf + 3 0 1 2 4 5 Inf Inf Inf Inf + 2 3 0 1 3 4 Inf Inf Inf Inf + 1 2 3 0 2 3 Inf Inf Inf Inf + Inf Inf Inf Inf 0 1 Inf Inf Inf Inf + Inf Inf Inf Inf 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 1 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Unweighted directed, 'in' mode +[ 0 3 2 1 Inf Inf Inf Inf Inf Inf + 1 0 3 2 Inf Inf Inf Inf Inf Inf + 2 1 0 3 Inf Inf Inf Inf Inf Inf + 3 2 1 0 Inf Inf Inf Inf Inf Inf + 1 4 3 2 0 1 Inf Inf Inf Inf + 2 5 4 3 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 Inf Inf Inf + Inf Inf Inf Inf Inf Inf 1 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Unweighted undirected +[ 0 1 2 1 1 2 Inf Inf Inf Inf + 1 0 1 2 2 3 Inf Inf Inf Inf + 2 1 0 1 3 4 Inf Inf Inf Inf + 1 2 1 0 2 3 Inf Inf Inf Inf + 1 2 3 2 0 1 Inf Inf Inf Inf + 2 3 4 3 1 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 1 Inf Inf + Inf Inf Inf Inf Inf Inf 1 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Weighted directed +[ 0 2 3 4 6 14 Inf Inf Inf Inf + 4 0 1 2 10 18 Inf Inf Inf Inf + 3 5 0 1 9 17 Inf Inf Inf Inf + 2 4 5 0 8 16 Inf Inf Inf Inf + Inf Inf Inf Inf 0 8 Inf Inf Inf Inf + Inf Inf Inf Inf 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Weighted undirected +[ 0 2 3 2 6 8 Inf Inf Inf Inf + 2 0 1 2 8 10 Inf Inf Inf Inf + 3 1 0 1 9 11 Inf Inf Inf Inf + 2 2 1 0 8 10 Inf Inf Inf Inf + 6 8 9 8 0 2 Inf Inf Inf Inf + 8 10 11 10 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf 3 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] + +Negative weight, directed +[ 0 2 0 1 6 14 Inf Inf Inf Inf + 1 0 -2 -1 7 15 Inf Inf Inf Inf + 3 5 0 1 9 17 Inf Inf Inf Inf + 2 4 2 0 8 16 Inf Inf Inf Inf + Inf Inf Inf Inf 0 8 Inf Inf Inf Inf + Inf Inf Inf Inf 2 0 Inf Inf Inf Inf + Inf Inf Inf Inf Inf Inf 0 3 Inf Inf + Inf Inf Inf Inf Inf Inf Inf 0 Inf Inf + Inf Inf Inf Inf Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf Inf Inf Inf Inf 0 ] diff --git a/tests/unit/igraph_distances_johnson.c b/tests/unit/igraph_distances_johnson.c new file mode 100644 index 0000000..940735e --- /dev/null +++ b/tests/unit/igraph_distances_johnson.c @@ -0,0 +1,104 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_empty, g_lm; + igraph_matrix_t result; + igraph_matrix_t bf_result; + igraph_vs_t vids; + igraph_vector_t weights_empty, weights_lm, weights_lm_neg_loop; + + igraph_matrix_init(&result, 0, 0); + igraph_matrix_init(&bf_result, 0, 0); + igraph_vs_all(&vids); + igraph_vector_init(&weights_empty, 0); + igraph_vector_init_int(&weights_lm_neg_loop, 9, -4, -3, -2, -1, 0, 1, 2, 3, 4); + igraph_vector_init_int(&weights_lm, 9, -1, 0, 1, -2, 2, 3, 4, 5, 6); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices, undirected:\n"); + igraph_empty(&g_empty, 0, IGRAPH_UNDIRECTED); + igraph_vector_resize(&weights_empty, igraph_ecount(&g_empty)); + igraph_distances_johnson(&g_empty, &result, vids, vids, &weights_empty, IGRAPH_OUT); + igraph_destroy(&g_empty); + print_matrix(&result); + + printf("No vertices, directed:\n"); + igraph_empty(&g_empty, 0, IGRAPH_DIRECTED); + igraph_vector_resize(&weights_empty, igraph_ecount(&g_empty)); + igraph_distances_johnson(&g_empty, &result, vids, vids, &weights_empty, IGRAPH_OUT); + igraph_destroy(&g_empty); + print_matrix(&result); + + printf("No edges, undirected:\n"); + igraph_empty(&g_empty, 3, IGRAPH_UNDIRECTED); + igraph_vector_resize(&weights_empty, igraph_ecount(&g_empty)); + igraph_distances_johnson(&g_empty, &result, vids, vids, &weights_empty, IGRAPH_OUT); + igraph_destroy(&g_empty); + print_matrix(&result); + + printf("No edges, directed:\n"); + igraph_empty(&g_empty, 4, IGRAPH_DIRECTED); + igraph_vector_resize(&weights_empty, igraph_ecount(&g_empty)); + igraph_distances_johnson(&g_empty, &result, vids, vids, &weights_empty, IGRAPH_OUT); + igraph_destroy(&g_empty); + print_matrix(&result); + + printf("Directed graph with loops and multi-edges:\n"); + igraph_distances_johnson(&g_lm, &result, vids, vids, &weights_lm, IGRAPH_OUT); + print_matrix(&result); + + printf("Directed graph with loops and multi-edges, select vertices 1 and 2:\n"); + igraph_distances_johnson(&g_lm, &result, igraph_vss_range(1, 3), igraph_vss_range(1, 3), &weights_lm, IGRAPH_OUT); + print_matrix(&result); + + printf("Directed graph with loops and multi-edges, select 0 -> 2:\n"); + igraph_distances_johnson(&g_lm, &result, igraph_vss_1(0), igraph_vss_1(2), &weights_lm, IGRAPH_OUT); + print_matrix(&result); + + printf("Directed graph with loops and multi-edges, select none:\n"); + igraph_distances_johnson(&g_lm, &result, igraph_vss_none(), igraph_vss_none(), &weights_lm, IGRAPH_OUT); + print_matrix(&result); + + printf("Directed graph with loops and multi-edges, IGRAPH_IN:\n"); + igraph_distances_johnson(&g_lm, &result, vids, vids, &weights_lm, IGRAPH_IN); + igraph_distances_bellman_ford(&g_lm, &bf_result, vids, vids, &weights_lm, IGRAPH_IN); + print_matrix(&result); + IGRAPH_ASSERT(igraph_matrix_all_e(&result, &bf_result)); + + VERIFY_FINALLY_STACK(); + + printf("Checking error for directed graph with loops and multi-edges with negative loop.\n"); + CHECK_ERROR(igraph_distances_johnson(&g_lm, &result, vids, vids, &weights_lm_neg_loop, IGRAPH_OUT), IGRAPH_ENEGCYCLE); + + printf("Directed graph with loops and multi-edges, IGRAPH_ALL:\n"); + CHECK_ERROR(igraph_distances_johnson(&g_lm, &result, vids, vids, &weights_lm, IGRAPH_ALL), IGRAPH_ENEGCYCLE); + + igraph_matrix_destroy(&result); + igraph_matrix_destroy(&bf_result); + igraph_destroy(&g_lm); + igraph_vector_destroy(&weights_empty); + igraph_vector_destroy(&weights_lm); + igraph_vector_destroy(&weights_lm_neg_loop); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_distances_johnson.out b/tests/unit/igraph_distances_johnson.out new file mode 100644 index 0000000..851824c --- /dev/null +++ b/tests/unit/igraph_distances_johnson.out @@ -0,0 +1,36 @@ +No vertices, undirected: +[ 0-by-0 ] +No vertices, directed: +[ 0-by-0 ] +No edges, undirected: +[ 0 Inf Inf + Inf 0 Inf + Inf Inf 0 ] +No edges, directed: +[ 0 Inf Inf Inf + Inf 0 Inf Inf + Inf Inf 0 Inf + Inf Inf Inf 0 ] +Directed graph with loops and multi-edges: +[ 0 -1 -3 1 6 Inf + 1 0 -2 2 7 Inf + 3 2 0 4 9 Inf + Inf Inf Inf 0 5 Inf + Inf Inf Inf Inf 0 Inf + Inf Inf Inf Inf Inf 0 ] +Directed graph with loops and multi-edges, select vertices 1 and 2: +[ 0 -2 + 2 0 ] +Directed graph with loops and multi-edges, select 0 -> 2: +[ -3 ] +Directed graph with loops and multi-edges, select none: +[ 0-by-0 ] +Directed graph with loops and multi-edges, IGRAPH_IN: +[ 0 1 3 Inf Inf Inf + -1 0 2 Inf Inf Inf + -3 -2 0 Inf Inf Inf + 1 2 4 0 Inf Inf + 6 7 9 5 0 Inf + Inf Inf Inf Inf Inf 0 ] +Checking error for directed graph with loops and multi-edges with negative loop. +Directed graph with loops and multi-edges, IGRAPH_ALL: diff --git a/tests/unit/igraph_diversity.c b/tests/unit/igraph_diversity.c new file mode 100644 index 0000000..7c1d1a2 --- /dev/null +++ b/tests/unit/igraph_diversity.c @@ -0,0 +1,86 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_t result; + igraph_vector_t weights; + + igraph_vector_init(&result, 0); + + /* null graph */ + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_vector_init(&weights, 0); + + printf("Null graph:\n"); + igraph_diversity(&g, &weights, &result, igraph_vss_all()); + print_vector(&result); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* graph with no edges */ + igraph_empty(&g, 5, IGRAPH_UNDIRECTED); + igraph_vector_init(&weights, 0); + + printf("Empty graph:\n"); + igraph_diversity(&g, &weights, &result, igraph_vss_all()); + print_vector(&result); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* real graph */ + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, 1,3, 2,3, -1); + igraph_vector_init_int_end(&weights, -1, 3, 2, 8, 1, 1, -1); + + printf("Graph with 4 nodes and 5 edges:\n"); + igraph_diversity(&g, &weights, &result, igraph_vss_all()); + print_vector(&result); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* degree-one vertices */ + igraph_kary_tree(&g, 10, 2, IGRAPH_TREE_UNDIRECTED); + igraph_vector_init_range(&weights, 1, igraph_ecount(&g) + 1); + + printf("Tree (having degree-one vertices):\n"); + igraph_diversity(&g, &weights, &result, igraph_vss_all()); + print_vector(&result); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* error conditions are tested from now on */ + VERIFY_FINALLY_STACK(); + + /* graph with multiple edges */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 2,0, -1); + igraph_vector_init_int_end(&weights, -1, 3, 2, 8, -1); + CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* negative weights */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 2,1, -1); + igraph_vector_init_int_end(&weights, -1, 3, -2, 8, -1); + CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* directed graph */ + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 0,2, -1); + igraph_vector_init_int_end(&weights, -1, 3, 2, -1); + CHECK_ERROR(igraph_diversity(&g, &weights, &result, igraph_vss_all()), IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_diversity.out b/tests/unit/igraph_diversity.out new file mode 100644 index 0000000..fffff67 --- /dev/null +++ b/tests/unit/igraph_diversity.out @@ -0,0 +1,8 @@ +Null graph: +( ) +Empty graph: +( NaN NaN NaN NaN NaN ) +Graph with 4 nodes and 5 edges: +( 0.970951 0.75 0.69137 1 ) +Tree (having degree-one vertices): +( 0.918296 0.88686 0.921463 0.934206 0.890492 0 0 0 0 0 ) diff --git a/tests/unit/igraph_dominator_tree.c b/tests/unit/igraph_dominator_tree.c new file mode 100644 index 0000000..8e755a5 --- /dev/null +++ b/tests/unit/igraph_dominator_tree.c @@ -0,0 +1,132 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g, domtree; + igraph_vector_int_t dom; + igraph_vector_int_t leftout; + + igraph_vector_int_init(&dom, 0); + igraph_small(&g, 13, IGRAPH_DIRECTED, + 0, 1, 0, 7, 0, 10, + 1, 2, 1, 5, + 2, 3, + 3, 4, + 4, 3, 4, 0, + 5, 3, 5, 6, + 6, 3, + 7, 8, 7, 10, 7, 11, + 8, 9, + 9, 4, 9, 8, + 10, 11, + 11, 12, + 12, 9, + -1); + + /* Check NULL vector arguments */ + igraph_dominator_tree(&g, /*root=*/ 0, /*dom=*/ 0, /*domtree=*/ 0, + /*leftout=*/ 0, /*mode=*/ IGRAPH_OUT); + + /* Proper calculation */ + igraph_dominator_tree(&g, /*root=*/ 0, &dom, /*domtree=*/ 0, + /*leftout=*/ 0, /*mode=*/ IGRAPH_OUT); + igraph_vector_int_print(&dom); + + /* Tree calculation */ + igraph_dominator_tree(&g, /*root=*/ 0, /*dom=*/ 0, /*domtree=*/ &domtree, + /*leftout=*/ 0, /*mode=*/ IGRAPH_OUT); + igraph_write_graph_edgelist(&domtree, stdout); + + igraph_vector_int_destroy(&dom); + igraph_destroy(&domtree); + igraph_destroy(&g); + + /* -------------------------------------------------------------------*/ + + igraph_vector_int_init(&dom, 0); + igraph_small(&g, 13, IGRAPH_DIRECTED, + 1, 0, 2, 0, 3, 0, + 4, 1, + 1, 2, 4, 2, 5, 2, + 6, 3, 7, 3, + 12, 4, + 8, 5, + 9, 6, + 9, 7, 10, 7, + 5, 8, 11, 8, + 11, 9, + 9, 10, + 9, 11, 0, 11, + 8, 12, + -1); + + /* Proper calculation */ + igraph_dominator_tree(&g, /*root=*/ 0, &dom, /*domtree=*/ 0, + /*leftout=*/ 0, /*mode=*/ IGRAPH_IN); + igraph_vector_int_print(&dom); + + /* Tree calculation */ + igraph_dominator_tree(&g, /*root=*/ 0, /*dom=*/ 0, /*domtree=*/ &domtree, + /*leftout=*/ 0, /*mode=*/ IGRAPH_IN); + igraph_write_graph_edgelist(&domtree, stdout); + + igraph_vector_int_destroy(&dom); + igraph_destroy(&domtree); + igraph_destroy(&g); + + /* -------------------------------------------------------------------*/ + + igraph_vector_int_init(&dom, 0); + igraph_vector_int_init(&leftout, 0); + + /* Check a graph with more components */ + igraph_small(&g, 20, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, + 1, 4, + 2, 1, 2, 4, 2, 8, + 3, 9, 3, 10, + 4, 15, + 8, 11, + 9, 12, + 10, 12, 10, 13, + 11, 8, 11, 14, + 12, 14, + 13, 12, + 14, 12, 14, 0, + 15, 11, + -1); + + igraph_dominator_tree(&g, /*root=*/ 0, &dom, &domtree, + &leftout, /*mode=*/ IGRAPH_OUT); + igraph_vector_int_print(&dom); + igraph_vector_int_print(&leftout); + igraph_write_graph_edgelist(&domtree, stdout); + + igraph_vector_int_destroy(&dom); + igraph_vector_int_destroy(&leftout); + igraph_destroy(&domtree); + igraph_destroy(&g); + + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_dominator_tree.out b/tests/unit/igraph_dominator_tree.out new file mode 100644 index 0000000..a4ba233 --- /dev/null +++ b/tests/unit/igraph_dominator_tree.out @@ -0,0 +1,40 @@ +-1 0 1 0 0 1 5 0 0 0 0 0 11 +0 1 +0 3 +0 4 +0 7 +0 8 +0 9 +0 10 +0 11 +1 2 +1 5 +5 6 +11 12 +-1 0 0 0 0 0 3 3 0 0 7 0 4 +1 0 +2 0 +3 0 +4 0 +5 0 +6 3 +7 3 +8 0 +9 0 +10 7 +11 0 +12 4 +-1 0 0 0 0 -2 -2 -2 0 3 3 0 0 10 0 4 -2 -2 -2 -2 +5 6 7 16 17 18 19 +0 1 +0 2 +0 3 +0 4 +0 8 +0 11 +0 12 +0 14 +3 9 +3 10 +4 15 +10 13 diff --git a/tests/unit/igraph_dot_product_game.c b/tests/unit/igraph_dot_product_game.c new file mode 100644 index 0000000..8468326 --- /dev/null +++ b/tests/unit/igraph_dot_product_game.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t vecs; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + { + matrix_init_real_row_major(&vecs, 0, 0, NULL); + igraph_dot_product_game(&g, &vecs, 1); + print_graph_canon(&g); + igraph_matrix_destroy(&vecs); + igraph_destroy(&g); + } + + printf("5 vertices, 0-1, 0-4, 1-4 are connected:\n"); + { + igraph_real_t elems[] = { + 1, 0, 0, -10, 100, + 0, 0.01, 0, -100, 100, + 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0 + }; + matrix_init_real_row_major(&vecs, 4, 5, elems); + igraph_dot_product_game(&g, &vecs, 1); + print_graph_canon(&g); + igraph_matrix_destroy(&vecs); + igraph_destroy(&g); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_dot_product_game.out b/tests/unit/igraph_dot_product_game.out new file mode 100644 index 0000000..f4e0f2e --- /dev/null +++ b/tests/unit/igraph_dot_product_game.out @@ -0,0 +1,16 @@ +No vertices: +directed: true +vcount: 0 +edges: { +} +5 vertices, 0-1, 0-4, 1-4 are connected: +directed: true +vcount: 5 +edges: { +0 1 +0 4 +1 0 +1 4 +4 0 +4 1 +} diff --git a/tests/unit/igraph_dyad_census.c b/tests/unit/igraph_dyad_census.c new file mode 100644 index 0000000..1474664 --- /dev/null +++ b/tests/unit/igraph_dyad_census.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph) { + igraph_real_t mut, asym, null; + IGRAPH_ASSERT(igraph_dyad_census(graph, &mut, &asym, &null) == IGRAPH_SUCCESS); + printf("Mutual: %.f ", mut); + printf("asymmetric: %.f ", asym); + printf("null: %.f\n\n", null); +} + + +int main(void) { + igraph_t g_0, g_1, g_2, g_lm, g_lmu; + + igraph_small(&g_0, 0, IGRAPH_DIRECTED, -1); + igraph_small(&g_1, 1, IGRAPH_DIRECTED, -1); + igraph_small(&g_2, 2, IGRAPH_DIRECTED, 0,1, -1); + igraph_small(&g_lm, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + call_and_print(&g_0); + + printf("One vertex:\n"); + call_and_print(&g_1); + + printf("Two vertices:\n"); + call_and_print(&g_2); + + printf("Graph with loops and multiple edges:\n"); + call_and_print(&g_lm); + + printf("Same graph, but undirected:\n"); + call_and_print(&g_lmu); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_2); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_dyad_census.out b/tests/unit/igraph_dyad_census.out new file mode 100644 index 0000000..f9a1a26 --- /dev/null +++ b/tests/unit/igraph_dyad_census.out @@ -0,0 +1,15 @@ +No vertices: +Mutual: 0 asymmetric: 0 null: 0 + +One vertex: +Mutual: 0 asymmetric: 0 null: 0 + +Two vertices: +Mutual: 0 asymmetric: 1 null: 0 + +Graph with loops and multiple edges: +Mutual: 1 asymmetric: 4 null: 10 + +Same graph, but undirected: +Mutual: 5 asymmetric: 0 null: 10 + diff --git a/tests/unit/igraph_ecc.c b/tests/unit/igraph_ecc.c new file mode 100644 index 0000000..671f763 --- /dev/null +++ b/tests/unit/igraph_ecc.c @@ -0,0 +1,219 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/* Compare vector for equality, allowing for NaN elements. + * Note that NaN != NaN, thus we cannot use vector_all_e(). */ +igraph_bool_t vec_equal(const igraph_vector_t *v1, const igraph_vector_t *v2) { + igraph_int_t n = igraph_vector_size(v1); + + if (igraph_vector_size(v2) != n) return false; + + for (igraph_int_t i=0; i < n; i++) { + igraph_real_t x1 = VECTOR(*v1)[i], x2 = VECTOR(*v2)[i]; + + if (isnan(x1)) { + if (! isnan(x2)) { + return false; + } + } else if (x1 != x2) { + return false; + } + } + return true; +} + +/* This is a trivial implementation of ECC for k=3 using igraph_list_triangles() */ +void get_ecc3(const igraph_t *g, igraph_vector_t *res, igraph_bool_t offset, igraph_bool_t normalize) { + igraph_vector_int_t triangles_vec, eids; + + + igraph_vector_resize(res, igraph_ecount(g)); + igraph_vector_null(res); + + igraph_vector_int_init(&triangles_vec, 0); + igraph_list_triangles(g, &triangles_vec); + + const igraph_matrix_int_t triangles = igraph_matrix_int_view_from_vector(&triangles_vec, 3); + + igraph_vector_int_init(&eids, 0); + + for (igraph_int_t i=0; i < igraph_matrix_int_ncol(&triangles); i++) { + igraph_int_t u, v, w; + igraph_int_t ec; + + u = MATRIX(triangles, 0, i); + v = MATRIX(triangles, 1, i); + w = MATRIX(triangles, 2, i); + + igraph_get_all_eids_between(g, &eids, u, v, IGRAPH_UNDIRECTED); + ec = igraph_vector_int_size(&eids); + for (igraph_int_t j=0; j < ec; j++) { + VECTOR(*res)[ VECTOR(eids)[j] ] += 1; + } + + igraph_get_all_eids_between(g, &eids, v, w, IGRAPH_UNDIRECTED); + ec = igraph_vector_int_size(&eids); + for (igraph_int_t j=0; j < ec; j++) { + VECTOR(*res)[ VECTOR(eids)[j] ] += 1; + } + + igraph_get_all_eids_between(g, &eids, w, u, IGRAPH_UNDIRECTED); + ec = igraph_vector_int_size(&eids); + for (igraph_int_t j=0; j < ec; j++) { + VECTOR(*res)[ VECTOR(eids)[j] ] += 1; + } + } + + igraph_vector_int_t degree; + igraph_vector_int_init(°ree, 0); + igraph_degree(g, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + + for (igraph_int_t e=0; e < igraph_ecount(g); e++) { + igraph_int_t v1 = IGRAPH_FROM(g, e), v2 = IGRAPH_TO(g, e); + igraph_real_t s; + if (v1 == v2) { + s = 0.0; + } else { + igraph_int_t d1 = VECTOR(degree)[v1], d2 = VECTOR(degree)[v2]; + s = (d1 < d2 ? d1 : d2) - 1.0; + } + if (offset) VECTOR(*res)[e] += 1; + if (normalize) VECTOR(*res)[e] /= s; + } + + igraph_vector_int_destroy(°ree); + + igraph_vector_int_destroy(&eids); + igraph_vector_int_destroy(&triangles_vec); +} + +/* These are globals, as they're used both from test_ecc() and main() */ +igraph_vector_t ecc1, ecc2, ecc3; + +void test_ecc(const igraph_t *g) { + printf("k=3\n"); + igraph_ecc(g, &ecc1, igraph_ess_all(IGRAPH_EDGEORDER_ID), 3, false, true); + print_vector(&ecc1); + + igraph_ecc(g, &ecc2, igraph_ess_range(0, igraph_ecount(g)), 3, false, true); + print_vector(&ecc2); + IGRAPH_ASSERT(vec_equal(&ecc1, &ecc2)); + + get_ecc3(g, &ecc3, false, true); + print_vector(&ecc3); + + IGRAPH_ASSERT(vec_equal(&ecc1, &ecc3)); + + printf("\nk=4\n"); + igraph_ecc(g, &ecc1, igraph_ess_all(IGRAPH_EDGEORDER_ID), 4, false, true); + print_vector(&ecc1); + + igraph_ecc(g, &ecc2, igraph_ess_range(0, igraph_ecount(g)), 4, false, true); + print_vector(&ecc2); + IGRAPH_ASSERT(vec_equal(&ecc1, &ecc2)); +} + +int main(void) { + igraph_t g; + + igraph_vector_init(&ecc1, 0); + igraph_vector_init(&ecc2, 0); + igraph_vector_init(&ecc3, 0); + + /* Null graph */ + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + test_ecc(&g); + igraph_destroy(&g); + + /* Singleton */ + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + test_ecc(&g); + igraph_destroy(&g); + + /* P_2 */ + + printf("\nP_2 graph\n"); + igraph_full(&g, 2, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + test_ecc(&g); + igraph_destroy(&g); + + /* K_5 */ + + printf("\nK_5 graph:\n"); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + test_ecc(&g); + igraph_destroy(&g); + + printf("\nK_5 graph with self-loops:\n"); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + test_ecc(&g); + igraph_destroy(&g); + + /* Multigraph */ + + printf("\nMultigraph:\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 0,1, 1,3, 3,4, 4,0, 0,5, 5,5, 5,5, 1,4, + -1); + test_ecc(&g); + igraph_destroy(&g); + + /* Large degrees */ + { + igraph_vector_int_t deg; + printf("\nGraph with large degrees:\n"); + igraph_vector_int_init_range(°, 0, 30); + for (igraph_int_t i=0; i < igraph_vector_int_size(°); i++) { + VECTOR(deg)[i] /= 2; + VECTOR(deg)[i] += 1; + } + igraph_realize_degree_sequence(&g, °, NULL, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_INDEX); + test_ecc(&g); + igraph_destroy(&g); + igraph_vector_int_destroy(°); + } + + /* Karate club */ + + printf("\nZachary karate club:\n"); + igraph_famous(&g, "Zachary"); + + test_ecc(&g); + + /* Check invalid input */ + + CHECK_ERROR(igraph_ecc(&g, &ecc1, igraph_ess_all(IGRAPH_EDGEORDER_ID), 2, false, true), IGRAPH_EINVAL); + CHECK_ERROR(igraph_ecc(&g, &ecc1, igraph_ess_all(IGRAPH_EDGEORDER_ID), 5, false, true), IGRAPH_UNIMPLEMENTED); + + igraph_destroy(&g); + + igraph_vector_destroy(&ecc3); + igraph_vector_destroy(&ecc2); + igraph_vector_destroy(&ecc1); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_ecc.out b/tests/unit/igraph_ecc.out new file mode 100644 index 0000000..ace598d --- /dev/null +++ b/tests/unit/igraph_ecc.out @@ -0,0 +1,79 @@ +Null graph: +k=3 +( ) +( ) +( ) + +k=4 +( ) +( ) + +Singleton graph: +k=3 +( ) +( ) +( ) + +k=4 +( ) +( ) + +P_2 graph +k=3 +( NaN ) +( NaN ) +( NaN ) + +k=4 +( NaN ) +( NaN ) + +K_5 graph: +k=3 +( 1 1 1 1 1 1 1 1 1 1 ) +( 1 1 1 1 1 1 1 1 1 1 ) +( 1 1 1 1 1 1 1 1 1 1 ) + +k=4 +( 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 ) +( 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 0.666667 ) + +K_5 graph with self-loops: +k=3 +( NaN 0.6 0.6 0.6 0.6 NaN 0.6 0.6 0.6 NaN 0.6 0.6 NaN 0.6 NaN ) +( NaN 0.6 0.6 0.6 0.6 NaN 0.6 0.6 0.6 NaN 0.6 0.6 NaN 0.6 NaN ) +( NaN 0.6 0.6 0.6 0.6 NaN 0.6 0.6 0.6 NaN 0.6 0.6 NaN 0.6 NaN ) + +k=4 +( NaN 0.24 0.24 0.24 0.24 NaN 0.24 0.24 0.24 NaN 0.24 0.24 NaN 0.24 NaN ) +( NaN 0.24 0.24 0.24 0.24 NaN 0.24 0.24 0.24 NaN 0.24 0.24 NaN 0.24 NaN ) + +Multigraph: +k=3 +( 0.5 1 1 0.5 1 1 0.5 0 NaN NaN 1 ) +( 0.5 1 1 0.5 1 1 0.5 0 NaN NaN 1 ) +( 0.5 1 1 0.5 1 1 0.5 0 NaN NaN 1 ) + +k=4 +( 0.0625 0.25 0.25 0.0625 0.25 0.5 0.25 0 NaN NaN 0.125 ) +( 0.0625 0.25 0.25 0.0625 0.25 0.5 0.25 0 NaN NaN 0.125 ) + +Graph with large degrees: +k=3 +( NaN NaN 1 1 1 1 0.5 0.5 0 0 0 0 0.333333 0.333333 0.333333 1 0.333333 0.333333 0.333333 0.333333 0.5 0.25 0.25 0.25 0.25 0 0.5 0 0.25 0.25 0.4 0.2 0.2 0.6 0.2 0.8 0.2 0.2 0.2 0.6 0.4 0.4 0.333333 0.5 0.666667 0.166667 0.166667 0.166667 0.333333 0 0.666667 0.166667 0.333333 0.333333 0.666667 0.166667 0.142857 0.571429 0.428571 0.285714 0.285714 0.714286 0.142857 0.571429 0.428571 0.857143 0.285714 0.142857 0.285714 0.714286 0.571429 0.428571 0.25 0.25 0.375 0.125 0.25 0.25 0.5 0.75 0.5 0.625 0.375 0.5 0.75 0.75 0 0.111111 0.222222 0.777778 0.888889 0.333333 0.444444 0.888889 0.333333 0.111111 0.555556 0.3 0.1 0.3 0.3 0.8 0.7 0.3 0.3 0.1 0.727273 0.545455 0.363636 0.454545 0.181818 0.181818 0.416667 0.0833333 0.416667 0.333333 0.416667 0.923077 0.0769231 0.5 ) +( NaN NaN 1 1 1 1 0.5 0.5 0 0 0 0 0.333333 0.333333 0.333333 1 0.333333 0.333333 0.333333 0.333333 0.5 0.25 0.25 0.25 0.25 0 0.5 0 0.25 0.25 0.4 0.2 0.2 0.6 0.2 0.8 0.2 0.2 0.2 0.6 0.4 0.4 0.333333 0.5 0.666667 0.166667 0.166667 0.166667 0.333333 0 0.666667 0.166667 0.333333 0.333333 0.666667 0.166667 0.142857 0.571429 0.428571 0.285714 0.285714 0.714286 0.142857 0.571429 0.428571 0.857143 0.285714 0.142857 0.285714 0.714286 0.571429 0.428571 0.25 0.25 0.375 0.125 0.25 0.25 0.5 0.75 0.5 0.625 0.375 0.5 0.75 0.75 0 0.111111 0.222222 0.777778 0.888889 0.333333 0.444444 0.888889 0.333333 0.111111 0.555556 0.3 0.1 0.3 0.3 0.8 0.7 0.3 0.3 0.1 0.727273 0.545455 0.363636 0.454545 0.181818 0.181818 0.416667 0.0833333 0.416667 0.333333 0.416667 0.923077 0.0769231 0.5 ) +( NaN NaN 1 1 1 1 0.5 0.5 0 0 0 0 0.333333 0.333333 0.333333 1 0.333333 0.333333 0.333333 0.333333 0.5 0.25 0.25 0.25 0.25 0 0.5 0 0.25 0.25 0.4 0.2 0.2 0.6 0.2 0.8 0.2 0.2 0.2 0.6 0.4 0.4 0.333333 0.5 0.666667 0.166667 0.166667 0.166667 0.333333 0 0.666667 0.166667 0.333333 0.333333 0.666667 0.166667 0.142857 0.571429 0.428571 0.285714 0.285714 0.714286 0.142857 0.571429 0.428571 0.857143 0.285714 0.142857 0.285714 0.714286 0.571429 0.428571 0.25 0.25 0.375 0.125 0.25 0.25 0.5 0.75 0.5 0.625 0.375 0.5 0.75 0.75 0 0.111111 0.222222 0.777778 0.888889 0.333333 0.444444 0.888889 0.333333 0.111111 0.555556 0.3 0.1 0.3 0.3 0.8 0.7 0.3 0.3 0.1 0.727273 0.545455 0.363636 0.454545 0.181818 0.181818 0.416667 0.0833333 0.416667 0.333333 0.416667 0.923077 0.0769231 0.5 ) + +k=4 +( NaN NaN 0.428571 0.428571 0.846154 0.846154 0.653846 0.653846 0.428571 0.607143 0.625 0.75 0.611111 0.5 0.547619 0.309524 0.666667 0.666667 0.757576 0.575758 0.545455 0.704545 0.615385 0.615385 0.410714 0.553571 0.583333 0.708333 0.75 0.775 0.66 0.66 0.633333 0.516667 0.528571 0.257143 0.569231 0.569231 0.727273 0.581818 0.688889 0.733333 0.722222 0.648148 0.560606 0.69697 0.551282 0.551282 0.392857 0.47619 0.5 0.597222 0.7 0.7 0.541667 0.6875 0.642857 0.517857 0.628571 0.642857 0.559524 0.464286 0.469388 0.244898 0.397959 0.173469 0.472527 0.483516 0.61039 0.532468 0.603175 0.650794 0.597222 0.555556 0.431818 0.602273 0.490385 0.490385 0.239583 0.40625 0.375 0.365385 0.363636 0.28125 0.3625 0.3875 0.488889 0.5 0.472222 0.292929 0.246914 0.404762 0.388889 0.313131 0.425926 0.433333 0.246032 0.3 0.481818 0.430769 0.423077 0.258333 0.25 0.415385 0.423077 0.490909 0.198347 0.214286 0.325758 0.305195 0.363636 0.431818 0.27381 0.346154 0.282051 0.232143 0.275641 0.0828402 0.28022 0.229592 ) +( NaN NaN 0.428571 0.428571 0.846154 0.846154 0.653846 0.653846 0.428571 0.607143 0.625 0.75 0.611111 0.5 0.547619 0.309524 0.666667 0.666667 0.757576 0.575758 0.545455 0.704545 0.615385 0.615385 0.410714 0.553571 0.583333 0.708333 0.75 0.775 0.66 0.66 0.633333 0.516667 0.528571 0.257143 0.569231 0.569231 0.727273 0.581818 0.688889 0.733333 0.722222 0.648148 0.560606 0.69697 0.551282 0.551282 0.392857 0.47619 0.5 0.597222 0.7 0.7 0.541667 0.6875 0.642857 0.517857 0.628571 0.642857 0.559524 0.464286 0.469388 0.244898 0.397959 0.173469 0.472527 0.483516 0.61039 0.532468 0.603175 0.650794 0.597222 0.555556 0.431818 0.602273 0.490385 0.490385 0.239583 0.40625 0.375 0.365385 0.363636 0.28125 0.3625 0.3875 0.488889 0.5 0.472222 0.292929 0.246914 0.404762 0.388889 0.313131 0.425926 0.433333 0.246032 0.3 0.481818 0.430769 0.423077 0.258333 0.25 0.415385 0.423077 0.490909 0.198347 0.214286 0.325758 0.305195 0.363636 0.431818 0.27381 0.346154 0.282051 0.232143 0.275641 0.0828402 0.28022 0.229592 ) + +Zachary karate club: +k=3 +( 0.875 0.555556 1 1 0.666667 0.666667 1 0.25 1 NaN 1 0.75 1 0.5 1 0 0.5 0.8 1 0.75 1 0.5 1 0 0.8 1 0 0 0.111111 0 0.5 0.75 1 1 0.75 0.5 0.5 0.666667 0.5 1 1 0.666667 0.75 0.5 0 0 1 1 1 1 1 1 0 1 1 1 1 0 0.333333 0.5 0.75 0.666667 0.5 0 0.5 0.5 1 1 0.333333 0.5 0.5 0.666667 1 0.666667 0.666667 0.2 0.4 0.909091 ) +( 0.875 0.555556 1 1 0.666667 0.666667 1 0.25 1 NaN 1 0.75 1 0.5 1 0 0.5 0.8 1 0.75 1 0.5 1 0 0.8 1 0 0 0.111111 0 0.5 0.75 1 1 0.75 0.5 0.5 0.666667 0.5 1 1 0.666667 0.75 0.5 0 0 1 1 1 1 1 1 0 1 1 1 1 0 0.333333 0.5 0.75 0.666667 0.5 0 0.5 0.5 1 1 0.333333 0.5 0.5 0.666667 1 0.666667 0.666667 0.2 0.4 0.909091 ) +( 0.875 0.555556 1 1 0.666667 0.666667 1 0.25 1 NaN 1 0.75 1 0.5 1 0 0.5 0.8 1 0.75 1 0.5 1 0 0.8 1 0 0 0.111111 0 0.5 0.75 1 1 0.75 0.5 0.5 0.666667 0.5 1 1 0.666667 0.75 0.5 0 0 1 1 1 1 1 1 0 1 1 1 1 0 0.333333 0.5 0.75 0.666667 0.5 0 0.5 0.5 1 1 0.333333 0.5 0.5 0.666667 1 0.666667 0.666667 0.2 0.4 0.909091 ) + +k=4 +( 0.108333 0.125926 0.186667 0.0666667 0.0666667 0.0666667 0.311111 0.166667 0.0666667 NaN 0.266667 0.283333 0.4 0.3 0.4 0.08 0.222222 0.35 0.5 0.4375 0.75 0.5 0.75 0.208333 0.266667 0.37037 0.222222 0.388889 0.111111 0.555556 0.305556 0.416667 0.666667 0.8 0.5 0.5 0.75 0.333333 0.5 0.333333 0.333333 0.416667 0.272727 0.28125 0.3125 0.15625 0.818182 0.5625 0.818182 0.5625 0.818182 0.5625 0.15625 0.818182 0.5625 0.818182 0.5625 0.375 0.333333 0.295455 0.1875 0.333333 0.25 0.333333 0.1 0.2 0.666667 0.125 0.166667 0.3 0.1875 0.333333 0.229167 0.363636 0.25 0.254545 0.175 0.0681818 ) +( 0.108333 0.125926 0.186667 0.0666667 0.0666667 0.0666667 0.311111 0.166667 0.0666667 NaN 0.266667 0.283333 0.4 0.3 0.4 0.08 0.222222 0.35 0.5 0.4375 0.75 0.5 0.75 0.208333 0.266667 0.37037 0.222222 0.388889 0.111111 0.555556 0.305556 0.416667 0.666667 0.8 0.5 0.5 0.75 0.333333 0.5 0.333333 0.333333 0.416667 0.272727 0.28125 0.3125 0.15625 0.818182 0.5625 0.818182 0.5625 0.818182 0.5625 0.15625 0.818182 0.5625 0.818182 0.5625 0.375 0.333333 0.295455 0.1875 0.333333 0.25 0.333333 0.1 0.2 0.666667 0.125 0.166667 0.3 0.1875 0.333333 0.229167 0.363636 0.25 0.254545 0.175 0.0681818 ) diff --git a/tests/unit/igraph_eccentricity.c b/tests/unit/igraph_eccentricity.c new file mode 100644 index 0000000..fee5f64 --- /dev/null +++ b/tests/unit/igraph_eccentricity.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_t ecc; + + igraph_vector_init(&ecc, 0); + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nPath with isolated vertex:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,2, + -1); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nUndirected path graph:\n"); + igraph_path_graph(&g, 5, IGRAPH_UNDIRECTED, /* mutual */ false); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nDirected path graph:\n"); + igraph_path_graph(&g, 5, IGRAPH_DIRECTED, /* mutual */ false); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nUndirected star:\n"); + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nOut-star:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_ALL); + print_vector(&ecc); + igraph_destroy(&g); + + printf("\nOut-star, IGRAPH_OUT:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_eccentricity(&g, NULL, &ecc, igraph_vss_all(), IGRAPH_OUT); + print_vector(&ecc); + igraph_destroy(&g); + + igraph_vector_destroy(&ecc); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eccentricity.out b/tests/unit/igraph_eccentricity.out new file mode 100644 index 0000000..6b37c3d --- /dev/null +++ b/tests/unit/igraph_eccentricity.out @@ -0,0 +1,23 @@ +Null graph: +( ) + +Singleton graph: +( 0 ) + +Path with isolated vertex: +( 1 0 1 ) + +Undirected path graph: +( 4 3 2 3 4 ) + +Directed path graph: +( 4 3 2 1 0 ) + +Undirected star: +( 1 2 2 2 2 2 2 2 2 2 ) + +Out-star: +( 1 2 2 2 2 2 2 2 2 2 ) + +Out-star, IGRAPH_OUT: +( 1 0 0 0 0 0 0 0 0 0 ) diff --git a/tests/unit/igraph_eccentricity_dijkstra.c b/tests/unit/igraph_eccentricity_dijkstra.c new file mode 100644 index 0000000..6acc9d7 --- /dev/null +++ b/tests/unit/igraph_eccentricity_dijkstra.c @@ -0,0 +1,117 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include + +#include "test_utilities.h" + +void print_and_destroy_weighted(igraph_t *graph, igraph_neimode_t mode, igraph_vector_t *weights) { + igraph_vector_t ecc; + igraph_vector_init(&ecc, 0); + igraph_eccentricity(graph, weights, &ecc, igraph_vss_all(), mode); + print_vector(&ecc); + igraph_destroy(graph); + igraph_vector_destroy(&ecc); + if (weights) { + igraph_vector_destroy(weights); + } +} + +void print_and_destroy(igraph_t *graph, igraph_neimode_t mode) { + igraph_vector_t weights; + igraph_vector_init(&weights, igraph_ecount(graph)); + for (int i = 0; i < igraph_ecount(graph); i++) { + VECTOR(weights)[i] = 1; + } + print_and_destroy_weighted(graph, mode, &weights); +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_vector_t ecc; + igraph_vector_init(&ecc, 0); + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nPath with isolated vertex:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,2, + -1); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nUndirected path graph:\n"); + igraph_ring(&g, 5, IGRAPH_UNDIRECTED, /* mutual */ 0, /* circular */ 0); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nDirected path graph:\n"); + igraph_ring(&g, 5, IGRAPH_DIRECTED, /* mutual */ 0, /* circular */ 0); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nUndirected star:\n"); + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nOut-star:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + print_and_destroy(&g, IGRAPH_ALL); + + printf("\nOut-star, IGRAPH_OUT:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + print_and_destroy(&g, IGRAPH_OUT); + + printf("\nOut-star with NULL weights:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + print_and_destroy_weighted(&g, IGRAPH_ALL, NULL); + + printf("\nOut-star with weights:\n"); + igraph_vector_init_real(&weights, 9, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9); + igraph_small(&g, 10, IGRAPH_DIRECTED, 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, 0,9, -1); + print_and_destroy_weighted(&g, IGRAPH_ALL, &weights); + + VERIFY_FINALLY_STACK(); + + printf("\nCheck wrong number of weights error.\n"); + igraph_vector_init(&weights, 1); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + CHECK_ERROR(igraph_eccentricity(&g, &weights, &ecc, igraph_vss_all(), IGRAPH_OUT), IGRAPH_EINVAL); + + printf("Check NaN weight error.\n"); + igraph_destroy(&g); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + VECTOR(weights)[0] = IGRAPH_NAN; + CHECK_ERROR(igraph_eccentricity(&g, &weights, &ecc, igraph_vss_all(), IGRAPH_OUT), IGRAPH_EINVAL); + + printf("Check negative weight error.\n"); + VECTOR(weights)[0] = -1; + CHECK_ERROR(igraph_eccentricity(&g, &weights, &ecc, igraph_vss_all(), IGRAPH_OUT), IGRAPH_EINVAL); + + igraph_vector_destroy(&ecc); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eccentricity_dijkstra.out b/tests/unit/igraph_eccentricity_dijkstra.out new file mode 100644 index 0000000..d1f550c --- /dev/null +++ b/tests/unit/igraph_eccentricity_dijkstra.out @@ -0,0 +1,33 @@ +Null graph: +( ) + +Singleton graph: +( 0 ) + +Path with isolated vertex: +( 1 0 1 ) + +Undirected path graph: +( 4 3 2 3 4 ) + +Directed path graph: +( 4 3 2 1 0 ) + +Undirected star: +( 1 2 2 2 2 2 2 2 2 2 ) + +Out-star: +( 1 2 2 2 2 2 2 2 2 2 ) + +Out-star, IGRAPH_OUT: +( 1 0 0 0 0 0 0 0 0 0 ) + +Out-star with NULL weights: +( 1 2 2 2 2 2 2 2 2 2 ) + +Out-star with weights: +( 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.7 ) + +Check wrong number of weights error. +Check NaN weight error. +Check negative weight error. diff --git a/tests/unit/igraph_edge_betweenness.c b/tests/unit/igraph_edge_betweenness.c new file mode 100644 index 0000000..c9735c0 --- /dev/null +++ b/tests/unit/igraph_edge_betweenness.c @@ -0,0 +1,278 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +/* https://github.com/igraph/igraph/issues/950 */ +void test_bug950(void) { + /* Testing the case of weighted graphs with multiple alternate + * paths to the same node with slightly different weights due to + * floating point inaccuracies. */ + igraph_t g; + igraph_vector_t eb; + igraph_vector_t weights; + igraph_int_t from, to; + igraph_int_t no_of_edges, i; + + igraph_full(&g, 6, 0, 0); + no_of_edges = igraph_ecount(&g); + + igraph_vector_init(&weights, no_of_edges); + + for (i = 0; i < no_of_edges; i++) { + igraph_edge(&g, i, &from, &to); + if((from < 3 && to < 3) || (from >= 3 && to >= 3)) + VECTOR(weights)[i] = 1; + else + VECTOR(weights)[i] = 0.1; + } + + igraph_vector_init(&eb, 0); + + igraph_edge_betweenness(&g, &weights, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + print_vector(&eb); + + igraph_vector_destroy(&eb); + igraph_vector_destroy(&weights); + igraph_destroy(&g); +} + + +/* https://github.com/igraph/igraph/issues/1050 */ +void test_bug1050(void) { + /* compare cutoff = -1 with cutoff = 0 */ + igraph_t g; + igraph_vector_t eb, eb2; + igraph_vector_t weights; + + igraph_full(&g, 6, 0, 0); + + /* unweighted */ + igraph_vector_init(&eb, igraph_ecount(&g)); + igraph_vector_init(&eb2, igraph_ecount(&g)); + + igraph_edge_betweenness_cutoff(&g, /* weights */ 0, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /* cutoff */ -1); + igraph_edge_betweenness_cutoff(&g, /* weights */ 0, &eb2, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /* cutoff */ 0); + + /* results must differ */ + IGRAPH_ASSERT(! igraph_vector_all_e(&eb, &eb2)); + + igraph_vector_destroy(&eb); + igraph_vector_destroy(&eb2); + + /* weighted */ + igraph_vector_init(&eb, igraph_ecount(&g)); + igraph_vector_init(&eb2, igraph_ecount(&g)); + + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1); + VECTOR(weights)[0] = 2; + + igraph_edge_betweenness_cutoff(&g, &weights, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /* cutoff */ -1); + igraph_edge_betweenness_cutoff(&g, &weights, &eb2, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /* cutoff */ 0); + + /* results must differ */ + IGRAPH_ASSERT(! igraph_vector_all_e(&eb, &eb2)); + + igraph_vector_destroy(&eb); + igraph_vector_destroy(&eb2); + igraph_vector_destroy(&weights); + igraph_destroy(&g); +} + + +/* Helper function to test edge subset selection for edge betweenness functions */ +static void test_edge_subset(const igraph_t *g, igraph_es_t eids, const char *description) { + igraph_vector_t eb_full, eb_subset, eb_expected; + igraph_vector_int_t idx_vec; + + printf("%s: ", description); + + /* Calculate full edge betweenness for reference */ + igraph_vector_init(&eb_full, 0); + igraph_edge_betweenness(g, NULL, &eb_full, igraph_ess_all(IGRAPH_EDGEORDER_ID), + IGRAPH_UNDIRECTED, false); + + /* Calculate edge betweenness for the subset */ + igraph_vector_init(&eb_subset, 0); + igraph_edge_betweenness(g, NULL, &eb_subset, eids, IGRAPH_UNDIRECTED, false); + print_vector(&eb_subset); + + /* Create expected result by indexing the full results */ + igraph_vector_int_init(&idx_vec, 0); + igraph_es_as_vector(g, eids, &idx_vec); + + igraph_vector_init(&eb_expected, 0); + igraph_vector_index(&eb_full, &eb_expected, &idx_vec); + + /* Verify that subset matches expected result */ + IGRAPH_ASSERT(igraph_vector_is_equal(&eb_subset, &eb_expected)); + + /* Clean up */ + igraph_vector_destroy(&eb_full); + igraph_vector_destroy(&eb_subset); + igraph_vector_destroy(&eb_expected); + igraph_vector_int_destroy(&idx_vec); +} + + + +void test_eids_parameter(void) { + /* Test the eids parameter for edge betweenness functions to ensure + * that subsetting works correctly and repeated edges are handled properly */ + igraph_t g; + igraph_vector_t eb_full; + igraph_vector_int_t edge_vec; + + printf("\nTesting eids parameter edge selection\n"); + + /* Use a path graph which has different betweenness values for edges */ + /* Path graph: 0-1-2-3-4 (4 edges) - betweenness values will be different */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, -1); + + /* Show full edge betweenness for reference */ + igraph_vector_init(&eb_full, 0); + igraph_edge_betweenness(&g, NULL, &eb_full, igraph_ess_all(IGRAPH_EDGEORDER_ID), + IGRAPH_UNDIRECTED, false); + printf("Full edge betweenness: "); + print_vector(&eb_full); + igraph_vector_destroy(&eb_full); + + /* Test various edge selection scenarios */ + test_edge_subset(&g, igraph_ess_range(1, 4), "Range selection (edges 1-3)"); + test_edge_subset(&g, igraph_ess_1(2), "Single edge selection (edge 2)"); + + igraph_vector_int_init_int(&edge_vec, 3, 0, 2, 3); + test_edge_subset(&g, igraph_ess_vector(&edge_vec), "Vector selection (edges 0, 2, 3)"); + igraph_vector_int_destroy(&edge_vec); + + igraph_vector_int_init_int(&edge_vec, 4, 0, 2, 0, 2); + test_edge_subset(&g, igraph_ess_vector(&edge_vec), "Vector selection with duplicates (edges 0, 2, 0, 2)"); + igraph_vector_int_destroy(&edge_vec); + + test_edge_subset(&g, igraph_ess_range(0, 0), "Empty edge selection"); + + igraph_destroy(&g); +} + + +int main(void) { + igraph_t g; + igraph_vector_t eb, eb2; + igraph_vector_t weights; + + igraph_vector_init(&eb, 0); + + printf("Null graph\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_edge_betweenness(&g, NULL, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + print_vector(&eb); + igraph_destroy(&g); + + printf("\nEdgeless graph on 3 vertices\n"); + igraph_empty(&g, 3, IGRAPH_DIRECTED); + igraph_edge_betweenness(&g, NULL, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_DIRECTED, false); + print_vector(&eb); + igraph_destroy(&g); + + igraph_vector_destroy(&eb); + + printf("\nNo cutoff, undirected, unweighted\n"); + igraph_famous(&g, "zachary"); + igraph_vector_init(&eb, 0); + igraph_edge_betweenness(&g, /*weights=*/ 0, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + print_vector(&eb); + + printf("\nNo cutoff, undirected, unit weighted\n"); + igraph_vector_init(&eb2, 0); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1.0); + igraph_edge_betweenness(&g, &weights, &eb2, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + print_vector(&eb2); + + /* check that weighted and unweighted calculations give the same result */ + igraph_vector_scale(&eb2, -1); + igraph_vector_add(&eb, &eb2); + igraph_vector_abs(&eb); + IGRAPH_ASSERT(igraph_vector_max(&eb) < 1e-13); + + igraph_vector_destroy(&weights); + igraph_vector_destroy(&eb2); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + + printf("\nSmall directed graph, unweighted\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 1,0, 2,0, 0,3, 3,4, 4,5, 5,0, 5,6, + -1); + igraph_vector_init(&eb, 0); + igraph_edge_betweenness(&g, /* weights */ NULL, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_DIRECTED, false); + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + + printf("\nSmall undirected graph 1, unweighted, cutoff=2\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 4, -1); + igraph_vector_init(&eb, 0); + igraph_edge_betweenness_cutoff(&g, /*weights=*/ 0, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /*cutoff=*/2); + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + + printf("\nSmall undirected graph 2, unweighted, cutoff=2\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 3, 1, 2, 1, 4, 2, 5, 3, 4, 3, 6, 4, 5, 4, 7, 5, 8, + 6, 7, 7, 8, -1); + igraph_vector_init(&eb, 0); + igraph_edge_betweenness_cutoff(&g, /*weights=*/ 0, &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, + false, /*cutoff=*/2); + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + + printf("\nSmall undirected graph 3, unweighted, with multiple and loop edges\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 1, 2, 1, 1, 2, 3, 3, 0, 3, 3, -1); + igraph_vector_init(&eb, 0); + igraph_edge_betweenness(/* graph= */ &g, + /* weights= */ 0, + /* res= */ &eb, igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + + printf("\nTesting bug 950, tolerances\n"); + test_bug950(); + + printf("\nTesting bug 1050, cutoff values\n"); + test_bug1050(); + + test_eids_parameter(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_edge_betweenness.out b/tests/unit/igraph_edge_betweenness.out new file mode 100644 index 0000000..2148a31 --- /dev/null +++ b/tests/unit/igraph_edge_betweenness.out @@ -0,0 +1,36 @@ +Null graph +( ) + +Edgeless graph on 3 vertices +( ) + +No cutoff, undirected, unweighted +( 14.1667 43.6389 11.5 29.3333 43.8333 43.8333 12.8024 41.6484 29.3333 33 26.1 23.7706 22.5095 25.7706 22.5095 71.3929 13.0333 4.33333 4.16429 6.95952 10.4905 8.20952 10.4905 18.1095 12.5833 14.1452 23.1087 12.781 38.7016 17.281 5.14762 4.28095 1.8881 6.9 8.37143 2.66667 1.66667 1.66667 2.66667 16.5 16.5 5.5 17.0778 22.6849 16.6143 38.0492 13.5111 19.4889 13.5111 19.4889 13.5111 19.4889 33.3135 13.5111 19.4889 13.5111 19.4889 11.0944 5.91111 12.5333 18.3278 3.73333 2.36667 10.4667 22.5 23.5944 2.54286 30.4571 17.0976 8.33333 13.781 13.0873 16.7222 9.56667 15.0429 23.2444 29.954 4.61429 ) + +No cutoff, undirected, unit weighted +( 14.1667 43.6389 11.5 29.3333 43.8333 43.8333 12.8024 41.6484 29.3333 33 26.1 23.7706 22.5095 25.7706 22.5095 71.3929 13.0333 4.33333 4.16429 6.95952 10.4905 8.20952 10.4905 18.1095 12.5833 14.1452 23.1087 12.781 38.7016 17.281 5.14762 4.28095 1.8881 6.9 8.37143 2.66667 1.66667 1.66667 2.66667 16.5 16.5 5.5 17.0778 22.6849 16.6143 38.0492 13.5111 19.4889 13.5111 19.4889 13.5111 19.4889 33.3135 13.5111 19.4889 13.5111 19.4889 11.0944 5.91111 12.5333 18.3278 3.73333 2.36667 10.4667 22.5 23.5944 2.54286 30.4571 17.0976 8.33333 13.781 13.0873 16.7222 9.56667 15.0429 23.2444 29.954 4.61429 ) + +Small directed graph, unweighted +( 5 5 15 14 13 6 6 ) + +Small undirected graph 1, unweighted, cutoff=2 +( 4 3 3 2 ) + +Small undirected graph 2, unweighted, cutoff=2 +( 3 3 3 4 3 4 3 4 4 3 3 3 ) + +Small undirected graph 3, unweighted, with multiple and loop edges +( 2 1.16667 1.16667 0 2 1.66667 0 ) + +Testing bug 950, tolerances +( 0 0 2.33333 2.33333 2.33333 0 2.33333 2.33333 2.33333 2.33333 2.33333 2.33333 0 0 0 ) + +Testing bug 1050, cutoff values + +Testing eids parameter edge selection +Full edge betweenness: ( 4 6 6 4 ) +Range selection (edges 1-3): ( 6 6 4 ) +Single edge selection (edge 2): ( 6 ) +Vector selection (edges 0, 2, 3): ( 4 6 4 ) +Vector selection with duplicates (edges 0, 2, 0, 2): ( 4 6 4 6 ) +Empty edge selection: ( ) diff --git a/tests/unit/igraph_edge_betweenness_subset.c b/tests/unit/igraph_edge_betweenness_subset.c new file mode 100644 index 0000000..5499141 --- /dev/null +++ b/tests/unit/igraph_edge_betweenness_subset.c @@ -0,0 +1,313 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +/* https://github.com/igraph/igraph/issues/950 */ +void test_bug950_edge(void) { + /* Testing the case of weighted graphs with multiple alternate + * paths to the same node with slightly different weights due to + * floating point inaccuracies. */ + igraph_t g; + igraph_vector_t eb; + igraph_vector_t weights; + igraph_int_t from, to; + igraph_int_t no_of_edges, i; + + igraph_full(&g, 6, 0, 0); + no_of_edges = igraph_ecount(&g); + + igraph_vector_init(&weights, no_of_edges); + + for (i = 0; i < no_of_edges; i++) { + igraph_edge(&g, i, &from, &to); + if((from < 3 && to < 3) || (from >= 3 && to >= 3)) + VECTOR(weights)[i] = 1; + else + VECTOR(weights)[i] = 0.1; + } + + printf("\nTesting bug 950 source and targets = vss_all()\n"); + printf("==========================================================\n"); + igraph_vector_init(&eb, 0); + + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ &weights, + /* res= */ &eb, + /* sources = */ igraph_vss_all(), + /* target = */ igraph_vss_all(), + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_vector_destroy(&weights); + igraph_destroy(&g); +} + +int main(void) { + igraph_t g; + igraph_es_t es; + igraph_vector_t eb, bet, bet2, weights; + igraph_vector_int_t node_vec, source_vec, target_vec; + igraph_vs_t vs_source, vs_target; + igraph_int_t i, n; + + /* edge betweenness test */ + + printf("Tree\n"); + printf("==========================================================\n"); + igraph_kary_tree(&g, 11111, 10, IGRAPH_TREE_UNDIRECTED); + + /* We are including the rightmost 200 vertices from the lowermost layer + * (layer 5) of the tree. These have 20 parents in layer 4, 2 grandparents + * in layer 3, and a single grand-grandparent in layer 2. None of the + * shortest paths we consider should therefore pass through the root, the + * first 9 vertices of layer 2, the first 98 vertices of layer 3 or the + * first 980 vertices of layer 4; the edge betweenness of the "upward" + * pointing edges from these vertices should all be zeros. + * + * (Note that due to how igraph constructs tree graphs, edge i is always the + * edge that leads from vertex i+1 towards the root). + * + * Also, the edge betweenness of the edges leading to children of the common + * grand-grandparent in layer 2 is easy to calculate as any shortest path + * going between grand-grandchildren reachable via its left child and via + * its right child should pass through them. This gives us a betweenness of + * 100 * 100 = 10000 for both of these edges. + * + * Similar calculations reveal that the edge betwennesses in subsequent + * layers are 1900 (10 * 190) and 199 (1 * 199) if the edge being considered + * leads to a descendant of the common grand-grandparent in layer 2, and + * zero otherwise. */ + + igraph_vs_range(&vs_source, 10911, 11111); + igraph_vs_range(&vs_target, 10911, 11111); + igraph_vector_init(&bet, 0); + + igraph_edge_betweenness_subset( + /* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ vs_target, + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + + printf("Max edge betweenness: %f\n", igraph_vector_max(&bet)); + + n = igraph_ecount(&g); + for (i = 0; i < n; i++) { + igraph_int_t expected; + igraph_int_t vid = i + 1; + + if (vid >= 10911) { + /* edge leading to layer 5, in the subset. There are 199 shortest + * paths that pass through this edge, one path to any _other_ + * node in the selected subset of 200 nodes */ + expected = 199; + } else if (vid >= 1111) { + /* edge leading to layer 5, not in the subset */ + expected = 0; + } else if (vid >= 1091) { + /* edge leading to layer 4, rightmost 20 nodes */ + expected = 1900; + } else if (vid >= 111) { + /* edge leading to layer 4, remaining nodes */ + expected = 0; + } else if (vid >= 109) { + /* edge leading to layer 3, rightmost 2 nodes */ + expected = 10000; + } else if (vid >= 11) { + /* edge leading to layer 3, remaining nodes */ + expected = 0; + } else if (vid == 10) { + /* edge leading to layer 2, rightmost node */ + expected = 0; + } else { + expected = 0; + } + + if (VECTOR(bet)[i] != expected) { + printf( + "Invalid betweenness for edge %" IGRAPH_PRId " (from vertex %" IGRAPH_PRId " towards the " + "root), expected %" IGRAPH_PRId ", got %" IGRAPH_PRId "\n", + i, vid, expected, (igraph_int_t) VECTOR(bet)[i] + ); + break; + } + } + + igraph_vector_init(&bet2, 0); + igraph_vector_init(&weights, igraph_ecount(&g)); + igraph_vector_fill(&weights, 1.0); + + igraph_edge_betweenness_subset( + /* graph= */ &g, + /* weights= */ &weights, + /* res= */ &bet2, + /* sources = */ vs_source, + /* target = */ vs_target, + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + + IGRAPH_ASSERT(igraph_vector_all_e(&bet, &bet2)); + + igraph_vector_destroy(&weights); + igraph_vs_destroy(&vs_source); + igraph_vs_destroy(&vs_target); + igraph_vector_destroy(&bet); + igraph_vector_destroy(&bet2); + igraph_destroy(&g); + + printf("\nZachary karate club, unweighted graph, edge betweenness\n"); + printf("==========================================================\n"); + igraph_famous(&g, "zachary"); + igraph_vector_int_init_range(&source_vec, 0, 33); + igraph_vs_vector(&vs_source, &source_vec); + igraph_vector_int_init_range(&target_vec, 1, 34); + igraph_vs_vector(&vs_target, &target_vec); + igraph_vector_init(&eb, 0); + + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &eb, + /* sources = */ vs_source, + /* target = */ vs_target, + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_destroy(&g); + igraph_vs_destroy(&vs_source); + igraph_vector_int_destroy(&source_vec); + igraph_vs_destroy(&vs_target); + igraph_vector_int_destroy(&target_vec); + + printf("\nSmall unweighted graph, edge betweenness\n"); + printf("==========================================================\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 4, -1); + + igraph_vector_init(&eb, 0); + igraph_vector_int_init_range(&node_vec, 0, 4); + igraph_es_vector(&es, &node_vec); + igraph_vector_int_init_range(&target_vec, 1, 5); + igraph_vs_vector(&vs_target, &target_vec); + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &eb, + /* sources = */ igraph_vss_all(), + /* target = */ vs_target, + /* eids= */ es, + /* directed = */ IGRAPH_UNDIRECTED, false); + + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_es_destroy(&es); + igraph_vector_int_destroy(&node_vec); + igraph_vs_destroy(&vs_target); + igraph_vector_int_destroy(&target_vec); + igraph_destroy(&g); + + printf("\nSmall unweighted graph, edge betweenness with subset of sources\n"); + printf("==========================================================\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0, 1, 0, 3, 1, 2, 1, 4, 2, 5, 3, 4, 3, 6, 4, 5, 4, 7, 5, 8, + 6, 7, 7, 8, -1); + igraph_vector_init(&eb, 0); + igraph_vector_int_init_range(&source_vec, 1, 9); + igraph_vs_vector(&vs_source, &source_vec); + + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &eb, + /* sources = */ vs_source, + /* target = */ igraph_vss_all(), + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + print_vector(&eb); + igraph_vector_destroy(&eb); + igraph_vs_destroy(&vs_source); + igraph_vector_int_destroy(&source_vec); + igraph_destroy(&g); + + test_bug950_edge(); + + printf("\nEmpty graph\n"); + printf("==========================================================\n"); + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + + igraph_vector_init(&bet, 0); + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ igraph_vss_all(), + /* target = */ igraph_vss_all(), + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + print_vector(&bet); + igraph_vector_destroy(&bet); + + igraph_destroy(&g); + + printf("\n37x37 grid graph\n"); + printf("==========================================================\n"); + + { + igraph_vector_int_t dims; + + igraph_vector_int_init(&dims, 2); + VECTOR(dims)[0] = 37; + VECTOR(dims)[1] = 37; + + igraph_square_lattice(&g, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + + igraph_vector_init(&bet, 0); + igraph_vector_int_init_range(&target_vec, 0, igraph_vcount(&g)); + igraph_vector_int_remove(&target_vec, 0); + igraph_vs_vector(&vs_target, &target_vec); + igraph_vector_int_init_range(&source_vec, 0, igraph_vcount(&g)); + igraph_vector_int_remove(&source_vec, 0); + igraph_vs_vector(&vs_source, &source_vec); + + igraph_edge_betweenness_subset(/* graph= */ &g, + /* weights= */ NULL, + /* res= */ &bet, + /* sources = */ vs_source, + /* target = */ vs_target, + /* eids= */ igraph_ess_all(IGRAPH_EDGEORDER_ID), + /* directed = */ IGRAPH_UNDIRECTED, false); + printf("Max edge betweenness: %f\n", igraph_vector_max(&bet)); + + igraph_vector_destroy(&bet); + igraph_destroy(&g); + igraph_vector_int_destroy(&dims); + igraph_vs_destroy(&vs_target); + igraph_vector_int_destroy(&target_vec); + igraph_vs_destroy(&vs_source); + igraph_vector_int_destroy(&source_vec); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_edge_betweenness_subset.out b/tests/unit/igraph_edge_betweenness_subset.out new file mode 100644 index 0000000..b6071e3 --- /dev/null +++ b/tests/unit/igraph_edge_betweenness_subset.out @@ -0,0 +1,27 @@ +Tree +========================================================== +Max edge betweenness: 10000.000000 + +Zachary karate club, unweighted graph, edge betweenness +========================================================== +( 13.4167 41.1825 11 28.3333 42.3333 42.3333 12.1595 38.4849 28.3333 32 25.2 21.5079 21.7238 23.5079 21.7238 67.1738 13.0333 4.33333 4.05714 6.61429 10.2762 7.86429 10.2762 17.5143 12.5833 13.931 22.4341 12.4119 37.9317 16.6619 5.02857 4.1619 1.85238 6.8 7.73571 2.66667 1.66667 1.66667 2.66667 16 16 5.25 16.427 20.3032 15.9952 34.6865 13.2968 18.7032 13.2968 18.7032 13.2968 18.7032 30.7056 13.2968 18.7032 13.2968 18.7032 10.7889 5.85556 12.3667 17.3556 3.73333 2.36667 10.2167 21.75 22.7889 2.54286 29.4571 16.2286 8.08333 13.1619 12.873 15.9365 9.56667 14.1976 22.5937 27.1913 3.99524 ) + +Small unweighted graph, edge betweenness +========================================================== +( 5 3.5 3.5 3.5 ) + +Small unweighted graph, edge betweenness with subset of sources +========================================================== +( 3.33333 3.33333 4.58333 6.58333 5.08333 6.58333 4.58333 6.83333 6.83333 5.08333 5.08333 5.08333 ) + +Testing bug 950 source and targets = vss_all() +========================================================== +( 0 0 2.33333 2.33333 2.33333 0 2.33333 2.33333 2.33333 2.33333 2.33333 2.33333 0 0 0 ) + +Empty graph +========================================================== +( ) + +37x37 grid graph +========================================================== +Max edge betweenness: 18367.263229 diff --git a/tests/unit/igraph_edge_disjoint_paths.c b/tests/unit/igraph_edge_disjoint_paths.c new file mode 100644 index 0000000..2b302f4 --- /dev/null +++ b/tests/unit/igraph_edge_disjoint_paths.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_int_t value; + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0,1, 0,2, 1,2, 1,3, 2,4, 3,4, 3,5, 4,5, 3,3, -1); + + igraph_edge_disjoint_paths(&g, &value, 0, 5); + IGRAPH_ASSERT(value == 2); + + igraph_edge_disjoint_paths(&g, &value, 0, 3); + IGRAPH_ASSERT(value == 1); + + igraph_edge_disjoint_paths(&g, &value, 3, 0); + IGRAPH_ASSERT(value == 0); + + igraph_edge_disjoint_paths(&g, &value, 3, 5); + IGRAPH_ASSERT(value == 2); + + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_EACH, NULL); + + igraph_edge_disjoint_paths(&g, &value, 4, 3); + IGRAPH_ASSERT(value == 3); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_edges.c b/tests/unit/igraph_edges.c new file mode 100644 index 0000000..1207570 --- /dev/null +++ b/tests/unit/igraph_edges.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g) { + igraph_es_t eids; + igraph_vector_int_t edges; + + igraph_es_all(&eids, IGRAPH_EDGEORDER_ID); + igraph_vector_int_init(&edges, 0); + + printf(" row-wise order: "); + igraph_edges(g, eids, &edges, 0); + print_vector_int(&edges); + + printf(" column-wise order: "); + igraph_edges(g, eids, &edges, 1); + print_vector_int(&edges); + + igraph_destroy(g); + igraph_vector_int_destroy(&edges); +} + +int main(void) { + igraph_t g; + + printf("No edges:\n"); + igraph_small(&g, 0, 1, -1); + print_and_destroy(&g); + + printf("Directed graph with loops and multiple edges:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 4,3, 4,3, -1); + print_and_destroy(&g); + + printf("Undirected graph with loops and multiple edges:\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 4,3, 4,3, -1); + print_and_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_edges.out b/tests/unit/igraph_edges.out new file mode 100644 index 0000000..0cfe21b --- /dev/null +++ b/tests/unit/igraph_edges.out @@ -0,0 +1,9 @@ +No edges: + row-wise order: ( ) + column-wise order: ( ) +Directed graph with loops and multiple edges: + row-wise order: ( 0 1 0 2 1 1 1 3 2 0 2 3 4 3 4 3 ) + column-wise order: ( 0 0 1 1 2 2 4 4 1 2 1 3 0 3 3 3 ) +Undirected graph with loops and multiple edges: + row-wise order: ( 0 1 0 2 1 1 1 3 0 2 2 3 3 4 3 4 ) + column-wise order: ( 0 0 1 1 0 2 3 3 1 2 1 3 2 3 4 4 ) diff --git a/tests/unit/igraph_eigen_matrix.c b/tests/unit/igraph_eigen_matrix.c new file mode 100644 index 0000000..42d5917 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix.c @@ -0,0 +1,133 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_int_t nodes = 10; + igraph_real_t triplets[] = { 1, 0, 1 / 4.0, 0, 1, 1 / 3.0, + 2, 0, 1 / 4.0, 0, 2, 1 / 3.0, + 3, 0, 1.0, 0, 3, 1 / 3.0, + 4, 1, 1.0, 1, 4, 1 / 4.0, + 5, 1, 1.0, 1, 5, 1 / 4.0, + 6, 1, 1.0, 1, 6, 1 / 4.0, + 7, 2, 1.0, 2, 7, 1 / 4.0, + 8, 2, 1.0, 2, 8, 1 / 4.0, + 9, 2, 1.0, 2, 9, 1 / 4.0 + }; + + igraph_sparsemat_t mat; + igraph_int_t i, j, n = sizeof(triplets) / sizeof(triplets[0]); + igraph_eigen_which_t which; + igraph_vector_complex_t values, values2; + igraph_matrix_complex_t vectors, vectors2; + igraph_matrix_t mat2; + + igraph_sparsemat_init(&mat, nodes, nodes, n / 3); + for (i = 0; i < n; i += 3) { + igraph_sparsemat_entry(&mat, triplets[i], triplets[i + 1], triplets[i + 2]); + } + + which.pos = IGRAPH_EIGEN_LM; + which.howmany = 1; + + igraph_vector_complex_init(&values, 0); + igraph_matrix_complex_init(&vectors, 0, 0); + + igraph_eigen_matrix(/*matrix=*/ 0, /*sparsemat=*/ &mat, /*fun=*/ 0, + nodes, /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + if (IGRAPH_REAL(MATRIX(vectors, 0, 0)) < 0) { + igraph_matrix_complex_scale(&vectors, igraph_complex(-1.0, -0.0 )); + } + + igraph_vector_complex_print(&values); + igraph_matrix_complex_print(&vectors); + + igraph_sparsemat_destroy(&mat); + + /* Calcualate all eigenvalues, using SM and LM and then check that they + are the same, in opposite order. We use a random matrix this time. */ + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&mat2, nodes, nodes); + for (i = 0; i < nodes; i++) { + for (j = 0; j < nodes; j++) { + MATRIX(mat2, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + which.pos = IGRAPH_EIGEN_LM; + which.howmany = nodes; + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + which.pos = IGRAPH_EIGEN_SM; + which.howmany = nodes; + igraph_vector_complex_init(&values2, 0); + igraph_matrix_complex_init(&vectors2, 0, 0); + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values2, &vectors2); + +#define DUMP() do { \ + printf("Element-wise difference of the following two vectors is too large:\n"); \ + igraph_vector_complex_print(&values); \ + igraph_vector_complex_print(&values2); \ + } while(0) + + for (i = 0; i < nodes; i++) { + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(VECTOR(values)[i], + VECTOR(values2)[nodes - i - 1])); + if (d > 1e-15) { + DUMP(); + return 2; + } + for (j = 0; j < nodes; j++) { + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(MATRIX(vectors, j, i), + MATRIX(vectors2, j, + nodes - i - 1))); + if (d > 1e-15) { + DUMP(); + return 3; + } + } + } + + igraph_vector_complex_destroy(&values); + igraph_matrix_complex_destroy(&vectors); + igraph_vector_complex_destroy(&values2); + igraph_matrix_complex_destroy(&vectors2); + + igraph_matrix_destroy(&mat2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix.out b/tests/unit/igraph_eigen_matrix.out new file mode 100644 index 0000000..c24fe02 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix.out @@ -0,0 +1,11 @@ +1+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i +0.316228+0i diff --git a/tests/unit/igraph_eigen_matrix2.c b/tests/unit/igraph_eigen_matrix2.c new file mode 100644 index 0000000..f3c9170 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix2.c @@ -0,0 +1,116 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DUMP() do { \ + igraph_vector_complex_print(&values); \ + igraph_vector_complex_print(&values2); \ + } while(0) + +int main(void) { + + const int nodes = 10; + igraph_matrix_t mat2; + igraph_vector_complex_t values, values2; + igraph_matrix_complex_t vectors, vectors2; + igraph_eigen_which_t which; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&mat2, nodes, nodes); + for (i = 0; i < nodes; i++) { + int j; + for (j = 0; j < nodes; j++) { + MATRIX(mat2, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + /* Test LR, a single eigenvalue first */ + + igraph_vector_complex_init(&values, 0); + igraph_matrix_complex_init(&vectors, 0, 0); + which.pos = IGRAPH_EIGEN_LR; + which.howmany = 1; + + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + igraph_vector_complex_print(&values); + igraph_matrix_complex_print(&vectors); + + igraph_vector_complex_destroy(&values); + igraph_matrix_complex_destroy(&vectors); + + /* LR, and SR, all eigenvalues */ + + igraph_vector_complex_init(&values, 0); + igraph_matrix_complex_init(&vectors, 0, 0); + which.pos = IGRAPH_EIGEN_LR; + which.howmany = nodes; + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + igraph_vector_complex_init(&values2, 0); + igraph_matrix_complex_init(&vectors2, 0, 0); + which.pos = IGRAPH_EIGEN_SR; + which.howmany = nodes; + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values2, &vectors2); + + for (i = 0; i < nodes; i++) { + int j; + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(VECTOR(values)[i], + VECTOR(values2)[nodes - i - 1])); + if (d > 1e-15) { + DUMP(); + return 2; + } + for (j = 0; j < nodes; j++) { + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(MATRIX(vectors, j, i), + MATRIX(vectors2, j, + nodes - i - 1))); + if (d > 1e-15) { + DUMP(); + return 3; + } + } + } + + igraph_vector_complex_destroy(&values); + igraph_matrix_complex_destroy(&vectors); + igraph_vector_complex_destroy(&values2); + igraph_matrix_complex_destroy(&vectors2); + + igraph_matrix_destroy(&mat2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix2.out b/tests/unit/igraph_eigen_matrix2.out new file mode 100644 index 0000000..ef0de5a --- /dev/null +++ b/tests/unit/igraph_eigen_matrix2.out @@ -0,0 +1,11 @@ +59.7723+0i +0.326586+0i +0.361886+0i +0.298836+0i +0.288278+0i +0.239871+0i + 0.35571+0i +0.336723+0i +0.407276+0i +0.224727+0i +0.275946+0i diff --git a/tests/unit/igraph_eigen_matrix3.c b/tests/unit/igraph_eigen_matrix3.c new file mode 100644 index 0000000..cc19326 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix3.c @@ -0,0 +1,97 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DUMP() do { \ + igraph_vector_complex_print(&values); \ + igraph_vector_complex_print(&values2); \ + } while(0) + +int main(void) { + + const int nodes = 10, skip = 3; + igraph_matrix_t mat2; + igraph_vector_complex_t values, values2; + igraph_matrix_complex_t vectors, vectors2; + igraph_eigen_which_t which; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&mat2, nodes, nodes); + for (i = 0; i < nodes; i++) { + int j; + for (j = 0; j < nodes; j++) { + MATRIX(mat2, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + which.pos = IGRAPH_EIGEN_SELECT; + which.il = skip; + which.iu = nodes - skip; + + igraph_vector_complex_init(&values, 0); + igraph_matrix_complex_init(&vectors, 0, 0); + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + which.pos = IGRAPH_EIGEN_ALL; + + igraph_vector_complex_init(&values2, 0); + igraph_matrix_complex_init(&vectors2, 0, 0); + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values2, &vectors2); + + for (i = 0; i < nodes - skip * 2 + 1; i++) { + int j; + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(VECTOR(values)[i], + VECTOR(values2)[i + skip - 1])); + if (d > 1e-15) { + DUMP(); + return 2; + } + for (j = 0; j < nodes; j++) { + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(MATRIX(vectors, j, i), + MATRIX(vectors2, j, i + skip - 1))); + if (d > 1e-15) { + DUMP(); + return 3; + } + } + } + + igraph_vector_complex_destroy(&values); + igraph_matrix_complex_destroy(&vectors); + igraph_vector_complex_destroy(&values2); + igraph_matrix_complex_destroy(&vectors2); + igraph_matrix_destroy(&mat2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix3.out b/tests/unit/igraph_eigen_matrix3.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/igraph_eigen_matrix4.c b/tests/unit/igraph_eigen_matrix4.c new file mode 100644 index 0000000..c068886 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix4.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DUMP() do { \ + igraph_vector_complex_print(&values); \ + igraph_vector_complex_print(&values2); \ + } while(0) + +int main(void) { + + const int nodes = 10; + igraph_matrix_t mat2; + igraph_vector_complex_t values, values2; + igraph_matrix_complex_t vectors, vectors2; + igraph_eigen_which_t which; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&mat2, nodes, nodes); + for (i = 0; i < nodes; i++) { + int j; + for (j = 0; j < nodes; j++) { + MATRIX(mat2, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + igraph_vector_complex_init(&values, 0); + igraph_matrix_complex_init(&vectors, 0, 0); + which.pos = IGRAPH_EIGEN_LI; + which.howmany = nodes; + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values, &vectors); + + igraph_vector_complex_init(&values2, 0); + igraph_matrix_complex_init(&vectors2, 0, 0); + which.pos = IGRAPH_EIGEN_SI; + which.howmany = nodes; + igraph_eigen_matrix(&mat2, /*sparsemat=*/ 0, /*fun=*/ 0, nodes, + /*extra=*/ 0, IGRAPH_EIGEN_LAPACK, &which, + /*options=*/ 0, /*storage=*/ 0, &values2, &vectors2); + + igraph_vector_complex_print(&values); + igraph_vector_complex_print(&values2); + + for (i = 0; i < nodes; i++) { + int j; + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(VECTOR(values)[i], + VECTOR(values2)[nodes - i - 1])); + if (d > 1e-15) { + DUMP(); + return 2; + } + for (j = 0; j < nodes; j++) { + igraph_real_t d = + igraph_complex_abs(igraph_complex_sub(MATRIX(vectors, j, i), + MATRIX(vectors2, j, + nodes - i - 1))); + if (d > 1e-15) { + DUMP(); + return 3; + } + } + } + + igraph_vector_complex_destroy(&values); + igraph_matrix_complex_destroy(&vectors); + igraph_vector_complex_destroy(&values2); + igraph_matrix_complex_destroy(&vectors2); + igraph_matrix_destroy(&mat2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix4.out b/tests/unit/igraph_eigen_matrix4.out new file mode 100644 index 0000000..35bb1db --- /dev/null +++ b/tests/unit/igraph_eigen_matrix4.out @@ -0,0 +1,2 @@ +-6.09451+6.92801i 1.3402+5.63701i 4.37096+2.48743i 59.7723+0i 1.62036+0i -1.50609+0i -5.11985+0i 4.37096-2.48743i 1.3402-5.63701i -6.09451-6.92801i +-6.09451-6.92801i 1.3402-5.63701i 4.37096-2.48743i -5.11985+0i -1.50609+0i 1.62036+0i 59.7723+0i 4.37096+2.48743i 1.3402+5.63701i -6.09451+6.92801i diff --git a/tests/unit/igraph_eigen_matrix_symmetric.c b/tests/unit/igraph_eigen_matrix_symmetric.c new file mode 100644 index 0000000..219f9d2 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix_symmetric.c @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DIM 10 + +int check_ev(const igraph_matrix_t *A, const igraph_vector_t *values, + const igraph_matrix_t *vectors) { + + int i, n = igraph_matrix_nrow(A); + int ne = igraph_matrix_ncol(vectors); + igraph_vector_t lhs, rhs; + + if (ne != igraph_vector_size(values)) { + printf("'values' and 'vectors' sizes do not match\n"); + exit(1); + } + + igraph_vector_init(&lhs, n); + igraph_vector_init(&rhs, n); + + for (i = 0; i < ne; i++) { + const igraph_vector_t v = igraph_vector_view(&MATRIX(*vectors, 0, i), n); + igraph_blas_dgemv(/*transpose=*/ 0, /*alpha=*/ 1, A, &v, + /*beta=*/ 0, &lhs); + igraph_vector_update(&rhs, &v); + igraph_vector_scale(&rhs, VECTOR(*values)[i]); + if (igraph_vector_maxdifference(&lhs, &rhs) > 1e-10) { + printf("LHS: "); + igraph_vector_print(&lhs); + printf("RHS: "); + igraph_vector_print(&rhs); + exit(2); + } + } + + igraph_vector_destroy(&rhs); + igraph_vector_destroy(&lhs); + + return 0; +} + +int main(void) { + + igraph_matrix_t A; + igraph_vector_t values; + igraph_matrix_t vectors; + int i, j; + igraph_eigen_which_t which; + + igraph_rng_seed(igraph_rng_default(), 42 * 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&vectors, 0, 0); + igraph_vector_init(&values, 0); + + /* All eigenvalues and eigenvectors */ + + for (i = 0; i < DIM; i++) { + for (j = i; j < DIM; j++) { + MATRIX(A, i, j) = MATRIX(A, j, i) = + igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + which.pos = IGRAPH_EIGEN_LM; + which.howmany = 5; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_LAPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors); + + which.howmany = 8; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_LAPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors); + + which.pos = IGRAPH_EIGEN_BE; + which.howmany = 5; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_LAPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors); + + which.pos = IGRAPH_EIGEN_SM; + which.howmany = 5; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_LAPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix_symmetric.out b/tests/unit/igraph_eigen_matrix_symmetric.out new file mode 100644 index 0000000..7e1297d --- /dev/null +++ b/tests/unit/igraph_eigen_matrix_symmetric.out @@ -0,0 +1,4 @@ +54.2836 -14.0379 11.141 -10.6996 -8.73724 +54.2836 -14.0379 11.141 -10.6996 -8.73724 8.62656 7.57286 4.18242 +54.2836 -14.0379 11.141 -10.6996 8.62656 +-1.76375 2.43199 4.18242 7.57286 8.62656 diff --git a/tests/unit/igraph_eigen_matrix_symmetric_arpack.c b/tests/unit/igraph_eigen_matrix_symmetric_arpack.c new file mode 100644 index 0000000..506e5d8 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix_symmetric_arpack.c @@ -0,0 +1,127 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DIM 10 + +int check_ev(const igraph_matrix_t *A, const igraph_vector_t *values, + const igraph_matrix_t *vectors, int err_off) { + + int i, n = igraph_matrix_nrow(A); + int ne = igraph_matrix_ncol(vectors); + igraph_vector_t lhs, rhs; + + if (ne != igraph_vector_size(values)) { + printf("'values' and 'vectors' sizes do not match\n"); + exit(err_off + 1); + } + + igraph_vector_init(&lhs, n); + igraph_vector_init(&rhs, n); + + for (i = 0; i < ne; i++) { + const igraph_vector_t v = igraph_vector_view(&MATRIX(*vectors, 0, i), n); + igraph_blas_dgemv(/*transpose=*/ 0, /*alpha=*/ 1, A, &v, + /*beta=*/ 0, &lhs); + igraph_vector_update(&rhs, &v); + igraph_vector_scale(&rhs, VECTOR(*values)[i]); + if (igraph_vector_maxdifference(&lhs, &rhs) > 1e-10) { + printf("LHS %i: ", i); + igraph_vector_print(&lhs); + printf("RHS %i: ", i); + igraph_vector_print(&rhs); + exit(err_off + 2); + } + } + + igraph_vector_destroy(&rhs); + igraph_vector_destroy(&lhs); + + return 0; +} + +int main(void) { + + igraph_matrix_t A; + igraph_vector_t values; + igraph_matrix_t vectors; + int i, j; + igraph_eigen_which_t which; + igraph_arpack_options_t options; + + igraph_rng_seed(igraph_rng_default(), 42 * 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&vectors, 0, 0); + igraph_vector_init(&values, 0); + + igraph_arpack_options_init(&options); + + for (i = 0; i < DIM; i++) { + for (j = i; j < DIM; j++) { + MATRIX(A, i, j) = MATRIX(A, j, i) = + igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + which.pos = IGRAPH_EIGEN_LM; + which.howmany = 2; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_ARPACK, &which, &options, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors, 0); + + which.howmany = 8; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_ARPACK, &which, &options, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors, 10); + + which.pos = IGRAPH_EIGEN_BE; + which.howmany = 5; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_ARPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors, 20); + + which.pos = IGRAPH_EIGEN_SM; + which.howmany = 5; + igraph_eigen_matrix_symmetric(&A, /*sA=*/ 0, /*fun=*/ 0, DIM, /*extra=*/ 0, + IGRAPH_EIGEN_ARPACK, &which, /*options=*/ 0, + /*storage=*/ 0, &values, &vectors); + igraph_vector_print(&values); + check_ev(&A, &values, &vectors, 30); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigen_matrix_symmetric_arpack.out b/tests/unit/igraph_eigen_matrix_symmetric_arpack.out new file mode 100644 index 0000000..82a9ce4 --- /dev/null +++ b/tests/unit/igraph_eigen_matrix_symmetric_arpack.out @@ -0,0 +1,4 @@ +54.2836 -14.0379 +54.2836 -14.0379 11.141 -10.6996 -8.73724 8.62656 7.57286 4.18242 +54.2836 -14.0379 11.141 -10.6996 8.62656 +-1.76375 2.43199 4.18242 7.57286 8.62656 diff --git a/tests/unit/igraph_eigenvector_centrality.c b/tests/unit/igraph_eigenvector_centrality.c new file mode 100644 index 0000000..b78abb0 --- /dev/null +++ b/tests/unit/igraph_eigenvector_centrality.c @@ -0,0 +1,165 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_neimode_t mode, igraph_vector_t *weights) { + igraph_vector_t v; + igraph_real_t value; + + igraph_vector_init(&v, 0); + igraph_eigenvector_centrality(g, &v, &value, + mode, + weights, + /* options */ NULL); + + printf("Eigenvalue: %g\n", value); + printf("Eigenvector:\n"); + igraph_vector_print(&v); + printf("\n"); + + igraph_destroy(g); + igraph_vector_destroy(&v); +} + +int main(void) { + + igraph_t g; + igraph_vector_t weights; + + printf("Undirected graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, IGRAPH_ALL, NULL); + + printf("Directed graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + print_and_destroy(&g, IGRAPH_OUT, NULL); + + printf("Undirected graph with no edges:\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, IGRAPH_ALL, NULL); + + printf("Directed graph with no edges:\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, -1); + print_and_destroy(&g, IGRAPH_IN, NULL); + + printf("Undirected full graph:\n"); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, /* loops */ false); + print_and_destroy(&g, IGRAPH_ALL, NULL); + + printf("Directed full graph:\n"); + igraph_full(&g, 5, IGRAPH_DIRECTED, /* loops */ false); + print_and_destroy(&g, IGRAPH_OUT, NULL); + + printf("Undirected full graph with weights:\n"); + igraph_vector_init(&weights, 10); + igraph_vector_fill(&weights, 1); + igraph_full(&g, 5, IGRAPH_UNDIRECTED, /* loops */ false); + print_and_destroy(&g, IGRAPH_ALL, &weights); + igraph_vector_destroy(&weights); + + printf("Directed full graph with weights:\n"); + igraph_full(&g, 5, IGRAPH_DIRECTED, /* loops */ false); + igraph_vector_init(&weights, 20); + igraph_vector_fill(&weights, 1); + print_and_destroy(&g, IGRAPH_OUT, &weights); + igraph_vector_destroy(&weights); + + printf("Undirected star graph with weights:\n"); + igraph_vector_init(&weights, 4); + igraph_vector_fill(&weights, 1); + igraph_star(&g, 5, IGRAPH_STAR_UNDIRECTED, 0); + print_and_destroy(&g, IGRAPH_ALL, &weights); + igraph_vector_destroy(&weights); + + printf("Directed out-star graph with weights:\n"); + igraph_star(&g, 5, IGRAPH_STAR_OUT, 0); + igraph_vector_init(&weights, 4); + igraph_vector_fill(&weights, 1); + print_and_destroy(&g, IGRAPH_OUT, &weights); + igraph_vector_destroy(&weights); + + printf("Directed in-star graph with weights:\n"); + igraph_star(&g, 5, IGRAPH_STAR_IN, 0); + igraph_vector_init(&weights, 4); + igraph_vector_fill(&weights, 2); + print_and_destroy(&g, IGRAPH_OUT, &weights); + igraph_vector_destroy(&weights); + + printf("Small directed graph, OUT:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + print_and_destroy(&g, IGRAPH_OUT, NULL); + + printf("Small directed graph, IN:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + print_and_destroy(&g, IGRAPH_IN, NULL); + + printf("Small directed graph, ALL:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + print_and_destroy(&g, IGRAPH_ALL, NULL); + + printf("Small directed graph with weights, OUT:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + igraph_vector_init_range(&weights, 1, igraph_ecount(&g)+1); + print_and_destroy(&g, IGRAPH_OUT, &weights); + igraph_vector_destroy(&weights); + + printf("Small directed graph with weights, IN:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + igraph_vector_init_range(&weights, 1, igraph_ecount(&g)+1); + print_and_destroy(&g, IGRAPH_IN, &weights); + igraph_vector_destroy(&weights); + + printf("Small directed graph with weights, ALL:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 1,3, + -1); + igraph_vector_init_range(&weights, 1, igraph_ecount(&g)+1); + print_and_destroy(&g, IGRAPH_ALL, &weights); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + + printf("Check handling of wrong number of weights.\n"); + igraph_vector_init(&weights, 2); + igraph_vector_fill(&weights, 1); + igraph_full(&g, 5, IGRAPH_DIRECTED, /* loops */ false); + CHECK_ERROR(igraph_eigenvector_centrality(&g, NULL, NULL, /* mode */ IGRAPH_OUT, + &weights, + /* options */ NULL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_eigenvector_centrality(&g, NULL, NULL, /* mode */ IGRAPH_ALL, + &weights, + /* options */ NULL), IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eigenvector_centrality.out b/tests/unit/igraph_eigenvector_centrality.out new file mode 100644 index 0000000..f270539 --- /dev/null +++ b/tests/unit/igraph_eigenvector_centrality.out @@ -0,0 +1,86 @@ +Undirected graph with no vertices: +Eigenvalue: 0 +Eigenvector: + + +Directed graph with no vertices: +Eigenvalue: 0 +Eigenvector: + + +Undirected graph with no edges: +Eigenvalue: 0 +Eigenvector: +1 1 1 1 1 + +Directed graph with no edges: +Eigenvalue: 0 +Eigenvector: +1 1 1 1 1 + +Undirected full graph: +Eigenvalue: 4 +Eigenvector: +1 1 1 1 1 + +Directed full graph: +Eigenvalue: 4 +Eigenvector: +1 1 1 1 1 + +Undirected full graph with weights: +Eigenvalue: 4 +Eigenvector: +1 1 1 1 1 + +Directed full graph with weights: +Eigenvalue: 4 +Eigenvector: +1 1 1 1 1 + +Undirected star graph with weights: +Eigenvalue: 2 +Eigenvector: +1 0.5 0.5 0.5 0.5 + +Directed out-star graph with weights: +Eigenvalue: 0 +Eigenvector: +0 1 1 1 1 + +Directed in-star graph with weights: +Eigenvalue: 0 +Eigenvector: +1 0 0 0 0 + +Small directed graph, OUT: +Eigenvalue: 1.22074 +Eigenvector: +0.819173 0.671044 0.5497 1 + +Small directed graph, IN: +Eigenvalue: 1.22074 +Eigenvector: +0.819173 1 0.5497 0.671044 + +Small directed graph, ALL: +Eigenvalue: 2.56155 +Eigenvector: +0.780776 1 0.780776 1 + +Small directed graph with weights, OUT: +Eigenvalue: 3.0334 +Eigenvector: +1 0.329663 0.217355 0.75835 + +Small directed graph with weights, IN: +Eigenvalue: 3.0334 +Eigenvector: +0.329663 1 0.429924 0.434711 + +Small directed graph with weights, ALL: +Eigenvalue: 8.17656 +Eigenvector: +0.589808 0.822599 0.568111 1 + +Check handling of wrong number of weights. diff --git a/tests/unit/igraph_empty.c b/tests/unit/igraph_empty.c new file mode 100644 index 0000000..6578f07 --- /dev/null +++ b/tests/unit/igraph_empty.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + + /* empty directed graph, zero vertices */ + igraph_empty(&g, 0, 1); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* empty undirected graph, zero vertices */ + igraph_empty(&g, 0, 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* empty directed graph, 20 vertices */ + igraph_empty(&g, 20, 1); + IGRAPH_ASSERT(igraph_vcount(&g) == 20); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* empty undirected graph, 30 vertices */ + igraph_empty(&g, 30, 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 30); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* error: negative number of vertices */ + CHECK_ERROR(igraph_empty(&g, -1, 0), IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_es_all_between.c b/tests/unit/igraph_es_all_between.c new file mode 100644 index 0000000..df97a76 --- /dev/null +++ b/tests/unit/igraph_es_all_between.c @@ -0,0 +1,201 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +igraph_error_t test_directed(void) { + igraph_t g; + igraph_es_t es; + igraph_eit_t eit; + igraph_int_t size; + + IGRAPH_CHECK(igraph_small(&g, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 1, 2, 3, 4, 1, 2, 4, 0, -1)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 3, 4)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + + /* single edge between 0 and 1 */ + igraph_es_all_between(&es, 0, 1, IGRAPH_DIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 1); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 0); + IGRAPH_ASSERT(to == 1); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* no edge between 3 and 0 */ + igraph_es_all_between(&es, 3, 0, IGRAPH_DIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 0); + IGRAPH_ASSERT(IGRAPH_EIT_END(eit)); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* many edges between 1 and 2 */ + igraph_es_all_between(&es, 1, 2, IGRAPH_DIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 8); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 1); + IGRAPH_ASSERT(to == 2); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* two edges between 3 and 4, using IGRAPH_UNDIRECTED */ + igraph_es_all_between(&es, 4, 3, IGRAPH_UNDIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 2); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 3); + IGRAPH_ASSERT(to == 4); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return IGRAPH_SUCCESS; +} + +igraph_error_t test_undirected(void) { + igraph_t g; + igraph_es_t es; + igraph_eit_t eit; + igraph_int_t size; + + IGRAPH_CHECK(igraph_small(&g, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 1, 2, 3, 4, 1, 2, 4, 0, -1)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 2, 1)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + IGRAPH_CHECK(igraph_add_edge(&g, 3, 4)); + IGRAPH_CHECK(igraph_add_edge(&g, 2, 1)); + IGRAPH_CHECK(igraph_add_edge(&g, 1, 2)); + + /* single edge between 0 and 1 */ + igraph_es_all_between(&es, 0, 1, IGRAPH_UNDIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 1); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 0); + IGRAPH_ASSERT(to == 1); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* no edge between 3 and 0 */ + igraph_es_all_between(&es, 3, 0, IGRAPH_UNDIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 0); + IGRAPH_ASSERT(IGRAPH_EIT_END(eit)); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* many edges between 1 and 2 */ + igraph_es_all_between(&es, 1, 2, IGRAPH_UNDIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 8); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 1); + IGRAPH_ASSERT(to == 2); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + /* two edges between 3 and 4 */ + igraph_es_all_between(&es, 4, 3, IGRAPH_UNDIRECTED); + IGRAPH_CHECK(igraph_eit_create(&g, es, &eit)); + IGRAPH_CHECK(igraph_es_size(&g, &es, &size)); + IGRAPH_ASSERT(size == 2); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + IGRAPH_CHECK(igraph_edge(&g, edge, &from, &to)); + IGRAPH_ASSERT(from == 3); + IGRAPH_ASSERT(to == 4); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return IGRAPH_SUCCESS; +} + +int main(void) { + IGRAPH_ASSERT(test_undirected() == IGRAPH_SUCCESS); + IGRAPH_ASSERT(test_directed() == IGRAPH_SUCCESS); + + return 0; +} diff --git a/tests/unit/igraph_es_path.c b/tests/unit/igraph_es_path.c new file mode 100644 index 0000000..ab47707 --- /dev/null +++ b/tests/unit/igraph_es_path.c @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_es_t es; + igraph_eit_t eit; + igraph_int_t size; + + /* DIRECTED */ + + igraph_ring(&g, 10, IGRAPH_DIRECTED, 0, 1); + igraph_es_path_small(&es, IGRAPH_DIRECTED, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, -1); + igraph_eit_create(&g, es, &eit); + igraph_es_size(&g, &es, &size); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + igraph_edge(&g, edge, &from, &to); + IGRAPH_EIT_NEXT(eit); + size--; + } + IGRAPH_ASSERT(size == 0); + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + igraph_destroy(&g); + + /* UNDIRECTED */ + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + igraph_es_path_small(&es, IGRAPH_DIRECTED, + 0, 1, 2, 3, 4, 3, 2, 3, 4, 5, 6, 5, 4, 5, 6, 7, 8, 9, 0, 1, 0, 9, -1); + igraph_eit_create(&g, es, &eit); + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t edge = IGRAPH_EIT_GET(eit); + igraph_int_t from, to; + igraph_edge(&g, edge, &from, &to); + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_establishment_game.c b/tests/unit/igraph_establishment_game.c new file mode 100644 index 0000000..5525032 --- /dev/null +++ b/tests/unit/igraph_establishment_game.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void init_vm(igraph_vector_t *type_dist, + int v0, int v1, + igraph_matrix_t *pref_matrix, + int m00, int m10, int m01, int m11) { + igraph_vector_init_int_end(type_dist, -1, v0, v1, -1); + igraph_matrix_init(pref_matrix, 2, 2); + MATRIX(*pref_matrix, 0, 0) = m00; + MATRIX(*pref_matrix, 1, 0) = m10; + MATRIX(*pref_matrix, 0, 1) = m01; + MATRIX(*pref_matrix, 1, 1) = m11; +} + +#define DESTROY_GVM() do { \ + igraph_destroy(&g); \ + igraph_vector_destroy(&type_dist); \ + igraph_matrix_destroy(&pref_matrix); \ + } while(0) + +int main(void) { + igraph_t g; + igraph_vector_t type_dist; + igraph_vector_int_t node_type_vec; + igraph_matrix_t pref_matrix; + igraph_bool_t bipartite; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&node_type_vec, 0); + /*No vertices*/ + init_vm(&type_dist, 1, 0, &pref_matrix, 0, 0, 0, 1); + IGRAPH_ASSERT(igraph_establishment_game(&g, /*nodes*/ 0, /*types*/ 2, /*edges_per_step*/ 0, &type_dist, &pref_matrix, /*directed*/ 0, /*node_type_vec*/ NULL) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + DESTROY_GVM(); + + /*Zero matrix elements for only possible vertex type means no edges*/ + init_vm(&type_dist, 1, 0, &pref_matrix, 0, 0, 0, 1); + IGRAPH_ASSERT(igraph_establishment_game(&g, /*nodes*/ 20, /*types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 0, /*node_type_vec*/ NULL) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 20); + DESTROY_GVM(); + + /*Two types with only cross terms makes a bipartite graph*/ + init_vm(&type_dist, 1, 1, &pref_matrix, 0, 1, 1, 0); + IGRAPH_ASSERT(igraph_establishment_game(&g, /*nodes*/ 20, /*types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 1, &node_type_vec) == IGRAPH_SUCCESS); + igraph_is_bipartite(&g, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_is_directed(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&node_type_vec) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&node_type_vec) == 1); + IGRAPH_ASSERT(igraph_vector_int_size(&node_type_vec) == 20); + DESTROY_GVM(); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /*Distribution of types should have at least one possible value*/ + igraph_vector_init(&type_dist, 0); + igraph_matrix_init(&pref_matrix, 0, 0); + IGRAPH_ASSERT(igraph_establishment_game(&g, /*nodes*/ 20, /* types*/ 2, /*edges_per_step*/ 5, &type_dist, &pref_matrix, /*directed*/ 0, /*node_type_vec*/ NULL) == IGRAPH_EINVAL); + DESTROY_GVM(); + + igraph_vector_int_destroy(&node_type_vec); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_eulerian_cycle.c b/tests/unit/igraph_eulerian_cycle.c new file mode 100644 index 0000000..813965f --- /dev/null +++ b/tests/unit/igraph_eulerian_cycle.c @@ -0,0 +1,183 @@ + +#include +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_vector_int_t edge_res, vertex_res; + igraph_es_t es; + igraph_vs_t vs; + + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_init(&vertex_res, 0); +/* + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); +*/ + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_vs_1(&vs, 0); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&edge_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 3,4, 4,5, 5,2, + 2,6, 6,4, 4,8, 2,8, 2,7, 0,7, -1); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,0, 0,0, 0,0, -1); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_vs_1(&vs, 0); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, -1); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,3, 3,4, 4,0, 0,2, 2,1, 1,0, -1); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,6, 6,4, 4,5, 5,0, 0,1, 1,2, + 2,3, 3,4, 4,2, 2,0, -1); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vertex_res); + igraph_vector_int_init(&vertex_res, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_cycle(&graph, &edge_res, &vertex_res); + print_vector_int(&edge_res); + print_vector_int(&vertex_res); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_destroy(&vertex_res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eulerian_cycle.out b/tests/unit/igraph_eulerian_cycle.out new file mode 100644 index 0000000..337c125 --- /dev/null +++ b/tests/unit/igraph_eulerian_cycle.out @@ -0,0 +1,32 @@ +( ) +( ) + +( 0 1 2 3 4 5 6 7 8 9 10 11 ) +( 0 1 2 3 4 5 2 6 4 8 2 7 0 ) + +( 2 1 0 ) +( 0 0 0 0 ) + +( ) +( ) + +( ) +( ) + +( ) +( ) + +( 0 1 2 ) +( 0 1 2 0 ) + +( 3 4 5 0 1 2 ) +( 0 2 1 0 3 4 0 ) + +( 4 5 9 0 1 8 6 7 2 3 ) +( 0 1 2 0 6 4 2 3 4 5 0 ) + +( ) +( ) + +( ) +( ) diff --git a/tests/unit/igraph_eulerian_path.c b/tests/unit/igraph_eulerian_path.c new file mode 100644 index 0000000..9862bc4 --- /dev/null +++ b/tests/unit/igraph_eulerian_path.c @@ -0,0 +1,443 @@ + +#include +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_vector_int_t edge_res, vector_res; + igraph_es_t es; + igraph_vs_t vs; + + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_init(&vector_res, 0); + /* + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + printf("\n"); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + */ + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_vs_1(&vs, 0); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0 , -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0 , + 1,5, 5,4, 4,1, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,1, 1,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,0 , 1,2, 2,3, 3,4, 4,1 ,-1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 3,4, 4,5, 5,2, + 2,6, 6,4, 4,8, 2,8, 2,7, 0,7, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, 2,3, 3,4 , 2,4 , 1,5, + 0,5 , -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,4, 3,4, 1,3, 2,5, 4,5, 2,6, 1,6, 0,4, 6,5, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 7,8, 8,9, 9,7, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,2, 2,3, 3,4, 4,5, 5,6, 6,3, + 3,7, 7,5, 5,9, 3,9, 3,8, 1,8, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2 , 2,0 , 0,3 , 3,2 , + 2,5 , 5,3 , 3,4 , 4,5 , 1,4 , 0,4 ,-1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 2,3 , 3,4 , 4,2 , 2,5 , 5,4 , + 4,7 , 7,5 , 5,6 , 6,7 , 3,6 , 2,6 ,-1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, 2,5 , 5,4 , 5,6 , 6,2 , + 2,3 , 3,4 , 4,8, 8,2 , 2,7, 7,0 , -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,4 , 4,0, 1,2 , 2,3 , 3,5, 5,2, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_vs_1(&vs, 0); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1 , 1,2, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,0, 0,0, 0,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1 , 1,3, 3,2, 2,0 , 2,1, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,3, 3,4, 4,0, 0,2, 2,1, 1,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,6, 6,4, 4,5, 5,0, 0,1, 1,2, + 2,3, 3,4, 4,2, 2,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 3,4, 1,3, 2,1, 1,2, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 3,4, 1,3, 2,1, 1,2, 0,0, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,2 , 2,4 , 4,5 , 5,2 , 2,0 , 0,1 , + 1,1 , 1,3 , 1,3 , 3,2 , 2,1 , 3,5 , -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 1,3 , 3,5 , 5,6 , 6,3 , 3,1 , 1,2 , + 2,2 , 2,4 , 2,4 , 4,3 , 3,2 , 4,6 , -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 7,8, 8,9, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 7,8, 8,9, 9,7, -1); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_init(&edge_res, 0); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + printf("\n"); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_res); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_init(&vector_res, 0); + igraph_vector_int_init(&edge_res, 0); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0, 1, -1); + igraph_es_1(&es, 0); + igraph_delete_edges(&graph, es); + igraph_vs_1(&vs, 1); + igraph_delete_vertices(&graph, vs); + igraph_eulerian_path(&graph, &edge_res, &vector_res); + print_vector_int(&edge_res); + print_vector_int(&vector_res); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&vector_res); + igraph_vector_int_destroy(&edge_res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_eulerian_path.out b/tests/unit/igraph_eulerian_path.out new file mode 100644 index 0000000..e64ed15 --- /dev/null +++ b/tests/unit/igraph_eulerian_path.out @@ -0,0 +1,95 @@ +( ) +( ) + +( 0 1 ) +( 0 1 2 ) + +( 0 1 2 ) +( 0 1 2 0 ) + +( 0 5 4 3 1 2 ) +( 0 1 4 5 1 2 0 ) + +( 3 2 1 0 ) +( 0 1 2 1 0 ) + +( 1 2 3 4 5 0 ) +( 0 1 2 3 4 1 0 ) + +( 0 1 2 3 4 5 6 7 8 9 10 11 ) +( 0 1 2 3 4 5 2 6 4 8 2 7 0 ) + +( 0 6 5 1 2 3 4 ) +( 1 0 5 1 2 3 4 2 ) + +( 5 1 0 9 2 7 8 4 3 6 10 ) +( 5 2 1 0 4 2 6 1 3 4 5 6 ) + +( 0 1 2 ) +( 7 8 9 7 ) + +( 0 1 2 3 4 5 6 7 8 9 10 11 ) +( 1 2 3 4 5 6 3 7 5 9 3 8 1 ) + +( 0 2 1 9 10 3 4 5 6 7 8 ) +( 1 0 2 1 4 0 3 2 5 3 4 5 ) + +( 0 2 1 9 10 3 4 5 6 7 8 ) +( 3 2 4 3 6 2 5 4 7 5 6 7 ) + +( 7 6 1 0 11 10 2 3 8 9 5 4 ) +( 4 3 2 1 0 7 2 5 4 8 2 6 5 ) + +( 0 2 1 3 4 5 6 ) +( 1 0 4 1 2 3 5 2 ) + +( ) +( ) + +( ) +( ) + +( ) +( ) + +( 0 1 ) +( 0 1 2 ) + +( 2 1 0 ) +( 0 0 0 0 ) + +( 0 1 2 ) +( 0 1 2 0 ) + +( 3 0 1 2 4 ) +( 2 0 1 3 2 1 ) + +( 3 4 5 0 1 2 ) +( 0 2 1 0 3 4 0 ) + +( 4 5 9 0 1 8 6 7 2 3 ) +( 0 1 2 0 6 4 2 3 4 5 0 ) + +( 0 4 3 2 1 ) +( 0 1 2 1 3 4 ) + +( 5 0 4 3 2 1 ) +( 0 0 1 2 1 3 4 ) + +( 5 6 8 9 4 0 10 7 11 3 1 2 ) +( 0 1 1 3 2 0 2 1 3 5 2 4 5 ) + +( 5 6 8 9 4 0 10 7 11 3 1 2 ) +( 1 2 2 4 3 1 3 2 4 6 3 5 6 ) + +( 0 1 ) +( 7 8 9 ) + +( 0 1 2 ) +( 7 8 9 7 ) + +( ) +( ) + +( ) +( ) diff --git a/tests/unit/igraph_extended_chordal_ring.c b/tests/unit/igraph_extended_chordal_ring.c new file mode 100644 index 0000000..a1327b8 --- /dev/null +++ b/tests/unit/igraph_extended_chordal_ring.c @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g, g_rev, g_test; + igraph_bool_t same; + igraph_matrix_int_t W; + int i, j; + + /* Directed, pentagram with ring, both clockwise */ + igraph_matrix_int_init(&W, 1, 1); + igraph_matrix_int_set(&W, 0, 0, 2); + IGRAPH_ASSERT(igraph_extended_chordal_ring(&g, /* nodes */ 5, &W, 1 /*directed*/) == IGRAPH_SUCCESS); + igraph_small(&g_test, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 2, 1, 3, 2, 4, 3, 0, 4, 1, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + + /* Use negative matrix value for same specification */ + igraph_matrix_int_set(&W, 0, 0, -3); + IGRAPH_ASSERT(igraph_extended_chordal_ring(&g_rev, /* nodes */ 5, &W, 1 /*directed*/) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_same_graph(&g_rev, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_rev); + igraph_destroy(&g_test); + igraph_matrix_int_destroy(&W); + + + /* From article, should give double edges for chords in igraph */ + igraph_matrix_int_init(&W, 2, 2); + int m[2][2] = {{4, 2}, + {8, 10}}; + for (i=0; i < 2; i++) { + for (j=0; j < 2; j++) { + MATRIX(W, i, j) = m[i][j]; + } + } + IGRAPH_ASSERT(igraph_extended_chordal_ring(&g, /* nodes */ 12, &W, 0 /*undirected*/) == IGRAPH_SUCCESS); + igraph_small(&g_test, 12, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, + 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 0, + 0, 4, 2, 6, 4, 8, 6, 10, 8, 0, 10, 2, + 0, 4, 2, 6, 4, 8, 6, 10, 8, 0, 10, 2, + 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 1, + 1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 1, + -1); + + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + igraph_matrix_int_destroy(&W); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_feedback_arc_set.c b/tests/unit/igraph_feedback_arc_set.c new file mode 100644 index 0000000..da68a15 --- /dev/null +++ b/tests/unit/igraph_feedback_arc_set.c @@ -0,0 +1,255 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_fas(const igraph_t *graph, const igraph_vector_int_t *fas) { + igraph_t g; + igraph_bool_t is_acyclic = false; + + igraph_copy(&g, graph); + igraph_delete_edges(&g, igraph_ess_vector(fas)); + + igraph_is_acyclic(&g, &is_acyclic); + IGRAPH_ASSERT(is_acyclic); + + if (! igraph_is_directed(graph)) { + igraph_int_t comp_no; + igraph_connected_components(graph, NULL, NULL, &comp_no, IGRAPH_WEAK); + IGRAPH_ASSERT(igraph_ecount(graph) - igraph_vector_int_size(fas) == igraph_vcount(graph) - comp_no); + } + + igraph_destroy(&g); +} + +igraph_real_t weight(const igraph_vector_t *weights, const igraph_vector_int_t *fas) { + const igraph_int_t size = igraph_vector_int_size(fas); + if (!weights) { + return (igraph_real_t) size; + } else { + igraph_real_t total = 0; + for (igraph_int_t i=0; i < size; i++) { + total += VECTOR(*weights)[ VECTOR(*fas)[i] ]; + } + return total; + } +} + +void compare_methods(const igraph_t *graph, const igraph_vector_t *weights) { + igraph_vector_int_t fas1, fas2; + + igraph_vector_int_init(&fas1, 0); + igraph_vector_int_init(&fas2, 0); + + igraph_feedback_arc_set(graph, &fas1, weights, IGRAPH_FAS_EXACT_IP_TI); + check_fas(graph, &fas1); + + igraph_feedback_arc_set(graph, &fas2, weights, IGRAPH_FAS_EXACT_IP_CG); + check_fas(graph, &fas2); + + /* Check that the two IP methods return solutions of the same size/weight. */ + IGRAPH_ASSERT(igraph_almost_equals(weight(weights, &fas1), weight(weights, &fas2), 1e-10)); + + igraph_feedback_arc_set(graph, &fas1, weights, IGRAPH_FAS_APPROX_EADES); + check_fas(graph, &fas1); + + /* Check that the exact IP method returns a solution that is at most as large + * as the solution from the heuristic. */ + IGRAPH_ASSERT(igraph_cmp_epsilon(weight(weights, &fas1), weight(weights, &fas2), 1e-10) >= 0); + + igraph_vector_int_destroy(&fas2); + igraph_vector_int_destroy(&fas1); +} + +void rand_weights(const igraph_t *graph, igraph_vector_t *weights) { + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_resize(weights, ecount); + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(*weights)[i] = RNG_UNIF01(); + } +} + +void test_undirected(void) { + igraph_t graph; + igraph_vector_int_t result; + + igraph_vector_int_init(&result, 0); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Two isolated vertices */ + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Single vertex with loop */ + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, + 0,0, + -1); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + check_fas(&graph, &result); + igraph_destroy(&graph); + + /* Random graph */ + igraph_erdos_renyi_game_gnm(&graph, 20, 40, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + check_fas(&graph, &result); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&result); + + VERIFY_FINALLY_STACK(); +} + +void test_directed(void) { + igraph_t graph; + igraph_vector_int_t result; + igraph_vector_t weights; + + igraph_vector_int_init(&result, 0); + igraph_vector_init(&weights, 0); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_CG); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_TI); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_CG); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_TI); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Two isolated vertices */ + igraph_empty(&graph, 2, IGRAPH_DIRECTED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_CG); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_TI); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Single vertex with loop */ + igraph_small(&graph, 1, IGRAPH_DIRECTED, + 0,0, + -1); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_APPROX_EADES); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_CG); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_TI); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + igraph_destroy(&graph); + + /* Kautz graph */ + igraph_kautz(&graph, 2, 2); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + + /* De Bruijn graph */ + igraph_de_bruijn(&graph, 3, 2); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + + /* Multigraph with loops and isolated vertices */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 1,2, 1,2, 2,3, 3,4, 3,4, 4,1, 5,4, 1,5, 5,6, 6,5, 6,7, 3,3, 3,3, 8,8, + -1); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + + /* Large cycle graph */ + igraph_ring(&graph, 30, IGRAPH_DIRECTED, false, true); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + + /* Acyclic graph */ + igraph_erdos_renyi_game_gnm(&graph, 20, 30, IGRAPH_DIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_to_directed(&graph, IGRAPH_TO_DIRECTED_ACYCLIC); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + + /* Several random graphs */ + for (igraph_int_t m=10; m <= 60; m += 10) { + igraph_erdos_renyi_game_gnm(&graph, 20, m, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + compare_methods(&graph, NULL); + rand_weights(&graph, &weights); + compare_methods(&graph, &weights); + igraph_destroy(&graph); + } + + /* Stress test CG method. + * This should run in a reasonable amount of time. */ + + igraph_erdos_renyi_game_gnm(&graph, 200, 400, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_feedback_arc_set(&graph, &result, NULL, IGRAPH_FAS_EXACT_IP_CG); + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&result); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + + test_undirected(); + test_directed(); + + return 0; +} diff --git a/tests/unit/igraph_feedback_vertex_set.c b/tests/unit/igraph_feedback_vertex_set.c new file mode 100644 index 0000000..a3ea326 --- /dev/null +++ b/tests/unit/igraph_feedback_vertex_set.c @@ -0,0 +1,210 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_fvs(const igraph_t *graph, const igraph_vector_int_t *fvs) { + igraph_t g; + igraph_bool_t is_acyclic = false; + + igraph_copy(&g, graph); + igraph_delete_vertices(&g, igraph_vss_vector(fvs)); + + igraph_is_acyclic(&g, &is_acyclic); + IGRAPH_ASSERT(is_acyclic); + + igraph_destroy(&g); +} + +igraph_real_t weight(const igraph_vector_t *weights, const igraph_vector_int_t *fvs) { + const igraph_int_t size = igraph_vector_int_size(fvs); + if (!weights) { + return (igraph_real_t) size; + } else { + igraph_real_t total = 0; + for (igraph_int_t i=0; i < size; i++) { + total += VECTOR(*weights)[ VECTOR(*fvs)[i] ]; + } + return total; + } +} + +void compare_methods(const igraph_t *graph, const igraph_vector_t *weights) { + igraph_vector_int_t fvs; + + igraph_vector_int_init(&fvs, 0); + + igraph_feedback_vertex_set(graph, &fvs, weights, IGRAPH_FVS_EXACT_IP); + check_fvs(graph, &fvs); + + igraph_vector_int_destroy(&fvs); + igraph_vector_int_destroy(&fvs); +} + +void rand_weights(const igraph_t *graph, igraph_vector_t *weights) { + const igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_resize(weights, ecount); + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(*weights)[i] = RNG_UNIF01(); + } +} + +void test_undirected(void) { + igraph_t graph; + igraph_vector_int_t result; + igraph_vector_t weights; + + igraph_vector_int_init(&result, 0); + igraph_vector_init(&weights, 0); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Two isolated vertices */ + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Single vertex with loop */ + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, + 0,0, + -1); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + /* Small graph */ + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 0, 2, 2, 3, 1, 3, 0, 4, 2, 4, -1); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 2); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 0, 2, 3, 3, 1, 0, 4, 4, 2, 4, 3, -1); + igraph_vector_resize(&weights, 5); + VECTOR(weights)[0] = 3; + VECTOR(weights)[1] = 3; + VECTOR(weights)[2] = 2; + VECTOR(weights)[3] = 2; + VECTOR(weights)[4] = 1; + igraph_feedback_vertex_set(&graph, &result, &weights, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 2); + igraph_vector_int_sort(&result); + IGRAPH_ASSERT(VECTOR(result)[0] == 2); + IGRAPH_ASSERT(VECTOR(result)[1] == 4); + igraph_destroy(&graph); + + /* Random graph */ + igraph_erdos_renyi_game_gnm(&graph, 10, 20, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&result); + + VERIFY_FINALLY_STACK(); +} + +void test_directed(void) { + igraph_t graph; + igraph_vector_int_t result; + + igraph_vector_int_init(&result, 0); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Two isolated vertices */ + igraph_empty(&graph, 2, IGRAPH_DIRECTED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 0); + igraph_destroy(&graph); + + /* Single vertex with loop */ + igraph_small(&graph, 1, IGRAPH_DIRECTED, + 0,0, + -1); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 0); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + /* Small graph */ + igraph_small(&graph, 6, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 0, 2, 3, 3, 1, 0, 4, 4, 2, 4, 3, -1); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 1); + IGRAPH_ASSERT(VECTOR(result)[0] == 2); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + /* Kautz graph, see https://doi.org/10.1016/j.disc.2006.09.010 Fig. 1 */ + igraph_kautz(&graph, 2, 2); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + IGRAPH_ASSERT(igraph_vector_int_size(&result) == 5); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + /* Random graph */ + igraph_erdos_renyi_game_gnm(&graph, 10, 20, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + igraph_feedback_vertex_set(&graph, &result, NULL, IGRAPH_FVS_EXACT_IP); + check_fvs(&graph, &result); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&result); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 137); + + test_undirected(); + test_directed(); + + return 0; +} diff --git a/tests/unit/igraph_find_cycle.c b/tests/unit/igraph_find_cycle.c new file mode 100644 index 0000000..51949c1 --- /dev/null +++ b/tests/unit/igraph_find_cycle.c @@ -0,0 +1,83 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t vertices, edges; + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_OUT); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + printf("Several isolated vertices:\n"); + igraph_empty(&g, 3, IGRAPH_UNDIRECTED); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_OUT); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + printf("Isolated vertices with self-loops:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 1,1, 1,1, 2,2, + -1); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_OUT); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + printf("Small directed graph:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,4, 2,0, + -1); + printf("OUT\n"); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_OUT); + print_vector_int(&vertices); + print_vector_int(&edges); + printf("IN\n"); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_IN); + print_vector_int(&vertices); + print_vector_int(&edges); + printf("ALL\n"); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_IN); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + printf("Small undirected multigraph:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 1,2, 3,4, 3,4, 3,4, + -1); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_ALL); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + printf("Directed acyclic graph:\n"); + igraph_small(&g, 8, IGRAPH_DIRECTED, + 0,1, 1,2, 0,3, 3,2, 2,4, 5,6, + -1); + printf("OUT\n"); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_OUT); + print_vector_int(&vertices); + print_vector_int(&edges); + printf("IN\n"); + igraph_find_cycle(&g, &vertices, &edges, IGRAPH_IN); + print_vector_int(&vertices); + print_vector_int(&edges); + igraph_destroy(&g); + + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&vertices); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_find_cycle.out b/tests/unit/igraph_find_cycle.out new file mode 100644 index 0000000..eaace50 --- /dev/null +++ b/tests/unit/igraph_find_cycle.out @@ -0,0 +1,29 @@ +Null graph: +( ) +( ) +Several isolated vertices: +( ) +( ) +Isolated vertices with self-loops: +( 1 ) +( 0 ) +Small directed graph: +OUT +( 1 2 0 ) +( 0 1 4 ) +IN +( 2 1 0 ) +( 4 1 0 ) +ALL +( 2 1 0 ) +( 4 1 0 ) +Small undirected multigraph: +( 4 3 ) +( 1 2 ) +Directed acyclic graph: +OUT +( ) +( ) +IN +( ) +( ) diff --git a/tests/unit/igraph_forest_fire_game.c b/tests/unit/igraph_forest_fire_game.c new file mode 100644 index 0000000..35da0d8 --- /dev/null +++ b/tests/unit/igraph_forest_fire_game.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/0, /*fw_prob*/ 0.0, + /*bw_factor*/ 0.0, /*pambs*/1, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("No ambassadors:\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/10, /*fw_prob*/ 0.0, + /*bw_factor*/ 0.0, /*pambs*/0, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("More ambassadors than nodes:\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/5, /*fw_prob*/ 0.0, + /*bw_factor*/ 0.0, /*pambs*/100, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Some normal inputs, just to check for memory problems, no output checking.\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/50, /*fw_prob*/ 0.5, + /*bw_factor*/ 0.5, /*pambs*/3, /*directed*/ 1) == IGRAPH_SUCCESS); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Negative fw_prob.\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/5, /*fw_prob*/ -0.5, + /*bw_factor*/ 0.0, /*pambs*/100, /*directed*/ 1) == IGRAPH_EINVAL); + + printf("Negative bw_factor.\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/5, /*fw_prob*/ 0.5, + /*bw_factor*/ -0.5, /*pambs*/100, /*directed*/ 0) == IGRAPH_EINVAL); + + printf("Negative number of ambassadors.\n"); + IGRAPH_ASSERT(igraph_forest_fire_game(&g, /*number of vertices*/5, /*fw_prob*/ 0.5, + /*bw_factor*/ 0.5, /*pambs*/-100, /*directed*/ 1) == IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_forest_fire_game.out b/tests/unit/igraph_forest_fire_game.out new file mode 100644 index 0000000..d08a97e --- /dev/null +++ b/tests/unit/igraph_forest_fire_game.out @@ -0,0 +1,29 @@ +No vertices: +directed: false +vcount: 0 +edges: { +} +No ambassadors: +directed: false +vcount: 10 +edges: { +} +More ambassadors than nodes: +directed: true +vcount: 5 +edges: { +1 0 +2 0 +2 1 +3 0 +3 1 +3 2 +4 0 +4 1 +4 2 +4 3 +} +Some normal inputs, just to check for memory problems, no output checking. +Negative fw_prob. +Negative bw_factor. +Negative number of ambassadors. diff --git a/tests/unit/igraph_from_prufer.c b/tests/unit/igraph_from_prufer.c new file mode 100644 index 0000000..378be68 --- /dev/null +++ b/tests/unit/igraph_from_prufer.c @@ -0,0 +1,42 @@ + +#include +#include + +#include "test_utilities.h" + + +int main(void) { + igraph_t graph; + igraph_int_t prufer1[] = {2, 3, 2, 3}; + igraph_int_t prufer2[] = {0, 2, 4, 1, 1, 0}; + igraph_vector_int_t prufer; + igraph_bool_t tree; + + prufer = igraph_vector_int_view(prufer1, sizeof(prufer1) / sizeof(prufer1[0])); + igraph_from_prufer(&graph, &prufer); + igraph_is_tree(&graph, &tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(tree); + print_graph(&graph); + igraph_destroy(&graph); + + prufer = igraph_vector_int_view(prufer2, sizeof(prufer2) / sizeof(prufer2[0])); + igraph_from_prufer(&graph, &prufer); + igraph_is_tree(&graph, &tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(tree); + print_graph(&graph); + igraph_destroy(&graph); + + /* For a zero-length array, we cannot use the same pattern as above because + standard C does not allow raw zero-length arrays. */ + igraph_vector_int_init(&prufer, 0); + igraph_from_prufer(&graph, &prufer); + igraph_is_tree(&graph, &tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(tree); + print_graph(&graph); + igraph_destroy(&graph); + igraph_vector_int_destroy(&prufer); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_from_prufer.out b/tests/unit/igraph_from_prufer.out new file mode 100644 index 0000000..41b5f4c --- /dev/null +++ b/tests/unit/igraph_from_prufer.out @@ -0,0 +1,25 @@ +directed: false +vcount: 6 +edges: { +2 0 +3 1 +4 2 +3 2 +5 3 +} +directed: false +vcount: 8 +edges: { +3 0 +5 2 +4 2 +4 1 +6 1 +1 0 +7 0 +} +directed: false +vcount: 2 +edges: { +1 0 +} diff --git a/tests/unit/igraph_full_bipartite.c b/tests/unit/igraph_full_bipartite.c new file mode 100644 index 0000000..6c017d8 --- /dev/null +++ b/tests/unit/igraph_full_bipartite.c @@ -0,0 +1,175 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_full_bipartite(const igraph_t *g, const igraph_vector_bool_t *types, + igraph_int_t n1, igraph_int_t n2, + igraph_bool_t directed, igraph_neimode_t mode) { + igraph_int_t vcount = igraph_vcount(g); + igraph_int_t ecount = igraph_ecount(g); + igraph_int_t expected_ecount; + igraph_bool_t bipartite; + + /* Check vertex count */ + IGRAPH_ASSERT(vcount == n1 + n2); + + /* Check that the graph is bipartite */ + igraph_is_bipartite(g, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + + /* Check directedness */ + IGRAPH_ASSERT(igraph_is_directed(g) == directed); + + /* Check types vector if provided */ + if (types) { + IGRAPH_ASSERT(igraph_vector_bool_size(types) == vcount); + /* First n1 vertices should be false (type 0), rest should be true (type 1) */ + for (igraph_int_t i = 0; i < n1; i++) { + IGRAPH_ASSERT(VECTOR(*types)[i] == false); + } + for (igraph_int_t i = n1; i < vcount; i++) { + IGRAPH_ASSERT(VECTOR(*types)[i] == true); + } + } + + /* Check edge count */ + if (!directed) { + expected_ecount = n1 * n2; + } else if (mode == IGRAPH_OUT || mode == IGRAPH_IN) { + expected_ecount = n1 * n2; + } else { /* mode == IGRAPH_ALL */ + expected_ecount = 2 * n1 * n2; + } + IGRAPH_ASSERT(ecount == expected_ecount); + + /* Check that edges are only between different partitions */ + for (igraph_int_t i = 0; i < ecount; i++) { + igraph_int_t from = IGRAPH_FROM(g, i); + igraph_int_t to = IGRAPH_TO(g, i); + + if (types) { + IGRAPH_ASSERT(VECTOR(*types)[from] != VECTOR(*types)[to]); + } + + /* For directed graphs with specific modes, check edge directions */ + if (directed) { + if (mode == IGRAPH_OUT) { + IGRAPH_ASSERT(from < n1 && to >= n1); + } else if (mode == IGRAPH_IN) { + IGRAPH_ASSERT(from >= n1 && to < n1); + } + /* For IGRAPH_ALL, both directions are allowed */ + } + } +} + +int main(void) { + igraph_t graph; + igraph_vector_bool_t types; + igraph_int_t n1, n2; + + igraph_vector_bool_init(&types, 0); + + /* Test 1: Small undirected complete bipartite graph */ + printf("Testing small undirected complete bipartite graph...\n"); + n1 = 3; n2 = 4; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 2: Small directed complete bipartite graph with IGRAPH_OUT */ + printf("Testing small directed complete bipartite graph (OUT mode)...\n"); + n1 = 2; n2 = 3; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_OUT); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_OUT); + igraph_destroy(&graph); + + /* Test 3: Small directed complete bipartite graph with IGRAPH_IN */ + printf("Testing small directed complete bipartite graph (IN mode)...\n"); + n1 = 2; n2 = 3; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_IN); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_IN); + igraph_destroy(&graph); + + /* Test 4: Small directed complete bipartite graph with IGRAPH_ALL */ + printf("Testing small directed complete bipartite graph (ALL mode)...\n"); + n1 = 2; n2 = 2; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_DIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 5: Empty first partition */ + printf("Testing empty first partition...\n"); + n1 = 0; n2 = 3; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 6: Empty second partition */ + printf("Testing empty second partition...\n"); + n1 = 3; n2 = 0; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 7: Both partitions empty */ + printf("Testing both partitions empty...\n"); + n1 = 0; n2 = 0; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 8: Singleton partitions */ + printf("Testing singleton partitions...\n"); + n1 = 1; n2 = 1; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 9: Test without types vector */ + printf("Testing without types vector...\n"); + n1 = 3; n2 = 2; + igraph_full_bipartite(&graph, NULL, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, NULL, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + /* Test 10: Larger graph */ + printf("Testing larger graph...\n"); + n1 = 5; n2 = 6; + igraph_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + check_full_bipartite(&graph, &types, n1, n2, IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_destroy(&graph); + + igraph_vector_bool_destroy(&types); + + /* Test error conditions */ + printf("Testing error conditions...\n"); + /* Test negative n1 parameter */ + CHECK_ERROR(igraph_full_bipartite(&graph, NULL, -1, 5, IGRAPH_UNDIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + /* Test negative n2 parameter */ + CHECK_ERROR(igraph_full_bipartite(&graph, NULL, 5, -1, IGRAPH_UNDIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + /* Test both negative parameters */ + CHECK_ERROR(igraph_full_bipartite(&graph, NULL, -1, -1, IGRAPH_UNDIRECTED, IGRAPH_ALL), IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_full_bipartite.out b/tests/unit/igraph_full_bipartite.out new file mode 100644 index 0000000..e98496b --- /dev/null +++ b/tests/unit/igraph_full_bipartite.out @@ -0,0 +1,11 @@ +Testing small undirected complete bipartite graph... +Testing small directed complete bipartite graph (OUT mode)... +Testing small directed complete bipartite graph (IN mode)... +Testing small directed complete bipartite graph (ALL mode)... +Testing empty first partition... +Testing empty second partition... +Testing both partitions empty... +Testing singleton partitions... +Testing without types vector... +Testing larger graph... +Testing error conditions... \ No newline at end of file diff --git a/tests/unit/igraph_full_citation.c b/tests/unit/igraph_full_citation.c new file mode 100644 index 0000000..540b25a --- /dev/null +++ b/tests/unit/igraph_full_citation.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g, g_test; + igraph_bool_t same; + igraph_int_t n_vertices = 4; + + /* Undirected, should be a full graph */ + IGRAPH_ASSERT(igraph_full_citation(&g, n_vertices, 0 /*undirected*/) == IGRAPH_SUCCESS); + igraph_small(&g_test, 4, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + + /* Directed, only edges from i->j if i > j */ + IGRAPH_ASSERT(igraph_full_citation(&g, n_vertices, 1 /*directed*/) == IGRAPH_SUCCESS); + igraph_small(&g_test, 4, IGRAPH_DIRECTED, 1, 0, 2, 0, 3, 0, 2, 1, 3, 1, 3, 2, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + + /* Directed, 1 vertex, should be edgeless */ + IGRAPH_ASSERT(igraph_full_citation(&g, 1 /*n_vertices*/, 1 /*directed*/) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* Directed, 0 vertices, empty graph */ + IGRAPH_ASSERT(igraph_full_citation(&g, 0 /*n_vertices*/, 1 /*directed*/) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; + +} diff --git a/tests/unit/igraph_full_multipartite.c b/tests/unit/igraph_full_multipartite.c new file mode 100644 index 0000000..f68c9e3 --- /dev/null +++ b/tests/unit/igraph_full_multipartite.c @@ -0,0 +1,158 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t partitions; + igraph_vector_int_t types; + + printf("Empty directed graph, zero vertices:"); + igraph_vector_int_init(&partitions, 0); + igraph_vector_int_init(&types, 0); + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nDirected graph with one partition, 4 vertices:"); + igraph_vector_int_init(&partitions, 1); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 4; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nDirected graph with 3 partitions:"); + igraph_vector_int_init(&partitions, 3); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 2; + VECTOR(partitions)[1] = 3; + VECTOR(partitions)[2] = 3; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nDirected graph, 4 partitions, mode=IN:"); + igraph_vector_int_init(&partitions, 4); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 2; + VECTOR(partitions)[1] = 3; + VECTOR(partitions)[2] = 4; + VECTOR(partitions)[3] = 2; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_IN); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nUndirected graph, 4 partitions:"); + igraph_vector_int_init(&partitions, 4); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 2; + VECTOR(partitions)[1] = 3; + VECTOR(partitions)[2] = 4; + VECTOR(partitions)[3] = 2; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_UNDIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nDirected graph with 3 partitions, all partitions with size zero:"); + igraph_vector_int_init(&partitions, 3); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 0; + VECTOR(partitions)[1] = 0; + VECTOR(partitions)[2] = 0; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + printf("\nDirected graph with 3 partitions, one parition with size zero:"); + igraph_vector_int_init(&partitions, 3); + igraph_vector_int_init(&types, 0); + + VECTOR(partitions)[0] = 2; + VECTOR(partitions)[1] = 0; + VECTOR(partitions)[2] = 3; + + igraph_full_multipartite(&g, &types, &partitions, IGRAPH_DIRECTED, IGRAPH_ALL); + + print_graph_canon(&g); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types); + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return IGRAPH_SUCCESS; +} diff --git a/tests/unit/igraph_full_multipartite.out b/tests/unit/igraph_full_multipartite.out new file mode 100644 index 0000000..4b38d33 --- /dev/null +++ b/tests/unit/igraph_full_multipartite.out @@ -0,0 +1,197 @@ +Empty directed graph, zero vertices:directed: true +vcount: 0 +edges: { +} + +Partition type: + + +Directed graph with one partition, 4 vertices:directed: true +vcount: 4 +edges: { +} + +Partition type: +0 0 0 0 + +Directed graph with 3 partitions:directed: true +vcount: 8 +edges: { +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +2 0 +2 1 +2 5 +2 6 +2 7 +3 0 +3 1 +3 5 +3 6 +3 7 +4 0 +4 1 +4 5 +4 6 +4 7 +5 0 +5 1 +5 2 +5 3 +5 4 +6 0 +6 1 +6 2 +6 3 +6 4 +7 0 +7 1 +7 2 +7 3 +7 4 +} + +Partition type: +0 0 1 1 1 2 2 2 + +Directed graph, 4 partitions, mode=IN:directed: true +vcount: 11 +edges: { +2 0 +2 1 +3 0 +3 1 +4 0 +4 1 +5 0 +5 1 +5 2 +5 3 +5 4 +6 0 +6 1 +6 2 +6 3 +6 4 +7 0 +7 1 +7 2 +7 3 +7 4 +8 0 +8 1 +8 2 +8 3 +8 4 +9 0 +9 1 +9 2 +9 3 +9 4 +9 5 +9 6 +9 7 +9 8 +10 0 +10 1 +10 2 +10 3 +10 4 +10 5 +10 6 +10 7 +10 8 +} + +Partition type: +0 0 1 1 1 2 2 2 2 3 3 + +Undirected graph, 4 partitions:directed: false +vcount: 11 +edges: { +0 2 +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +0 10 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +1 10 +2 5 +2 6 +2 7 +2 8 +2 9 +2 10 +3 5 +3 6 +3 7 +3 8 +3 9 +3 10 +4 5 +4 6 +4 7 +4 8 +4 9 +4 10 +5 9 +5 10 +6 9 +6 10 +7 9 +7 10 +8 9 +8 10 +} + +Partition type: +0 0 1 1 1 2 2 2 2 3 3 + +Directed graph with 3 partitions, all partitions with size zero:directed: true +vcount: 0 +edges: { +} + +Partition type: + + +Directed graph with 3 partitions, one parition with size zero:directed: true +vcount: 5 +edges: { +0 2 +0 3 +0 4 +1 2 +1 3 +1 4 +2 0 +2 1 +3 0 +3 1 +4 0 +4 1 +} + +Partition type: +0 0 1 1 1 diff --git a/tests/unit/igraph_generalized_petersen.c b/tests/unit/igraph_generalized_petersen.c new file mode 100644 index 0000000..f1e0a95 --- /dev/null +++ b/tests/unit/igraph_generalized_petersen.c @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph, graph_test; + igraph_bool_t iso; + + /* Compares G(5,2) with Petersen Graph in igraph_famous */ + IGRAPH_ASSERT(igraph_generalized_petersen(&graph, /* n */ 5, /* k */ 2) == IGRAPH_SUCCESS); + igraph_famous(&graph_test, "Petersen"); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + + /* Compares G(9,3) with expected small graph */ + IGRAPH_ASSERT(igraph_generalized_petersen(&graph, /* n */ 9, /* k */ 3) == IGRAPH_SUCCESS); + igraph_small(&graph_test, 18, IGRAPH_UNDIRECTED, 0, 3, 0, 6, 0, 9, 1, 4, 1, 7, 1, 10, 2, 5, 2, + 8, 2, 11, 3, 6, 3, 12, 4, 7, 4, 13, 5, 8, 5, 14, 6, 15, 7, 16, 8, 17, 9, 10, 9, 17, 10, + 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, -1); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + + /* Compares G(10,2) with Dodecahedral Graph in igraph_famous */ + IGRAPH_ASSERT(igraph_generalized_petersen(&graph, /* n */ 10, /* k */ 2) == IGRAPH_SUCCESS); + igraph_famous(&graph_test, "Dodecahedral"); + IGRAPH_ASSERT(igraph_isomorphic(&graph, &graph_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&graph); + igraph_destroy(&graph_test); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_adjacency.c b/tests/unit/igraph_get_adjacency.c new file mode 100644 index 0000000..624537a --- /dev/null +++ b/tests/unit/igraph_get_adjacency.c @@ -0,0 +1,200 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void test_undirected(void) { + igraph_t graph; + igraph_real_t weights_array[] = { 5, 4, 3, 2, 1, 6, 3, 2 }; + const igraph_vector_t weights = + igraph_vector_view(weights_array, sizeof(weights_array) / sizeof(weights_array[0])); + igraph_matrix_t m; + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + + igraph_matrix_init(&m, 2, 2); + + printf("Undirected, unweighted, upper, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, upper, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, upper, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, both, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, both, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Undirected, weighted, both, loops twice:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + igraph_matrix_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +void test_directed(void) { + igraph_t graph; + igraph_real_t weights_array[] = { 5, 4, 3, 2, 1, 6, 3, 2 }; + const igraph_vector_t weights = + igraph_vector_view(weights_array, sizeof(weights_array) / sizeof(weights_array[0])); + igraph_matrix_t m; + + igraph_small(&graph, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + + igraph_matrix_init(&m, 2, 2); + + printf("Directed, unweighted, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Directed, unweighted, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Directed, unweighted, loops twice (same as once):\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Directed, weighted, no loops:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_NO_LOOPS); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Directed, weighted, loops once:\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_ONCE); + igraph_matrix_print(&m); + printf("========\n"); + + printf("Directed, weighted, loops twice (same as once):\n"); + igraph_get_adjacency(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_TWICE); + igraph_matrix_print(&m); + printf("========\n"); + + igraph_matrix_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +void test_errors(void) { + igraph_t graph; + igraph_matrix_t m; + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + igraph_matrix_init(&m, 2, 2); + + CHECK_ERROR(igraph_get_adjacency(&graph, &m, (igraph_get_adjacency_t) 42, NULL, IGRAPH_LOOPS_ONCE), IGRAPH_EINVAL); + + igraph_matrix_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + test_undirected(); + test_directed(); + test_errors(); + + return 0; +} diff --git a/tests/unit/igraph_get_adjacency.out b/tests/unit/igraph_get_adjacency.out new file mode 100644 index 0000000..39b1bb2 --- /dev/null +++ b/tests/unit/igraph_get_adjacency.out @@ -0,0 +1,168 @@ +Undirected, unweighted, upper, no loops: +0 2 0 1 1 +0 0 1 0 0 +0 0 0 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, upper, loops once: +0 2 0 1 1 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, upper, loops twice: +0 2 0 1 1 +0 0 1 0 0 +0 0 2 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, lower, no loops: +0 0 0 0 0 +2 0 0 0 0 +0 1 0 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, lower, loops once: +0 0 0 0 0 +2 0 0 0 0 +0 1 1 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, lower, loops twice: +0 0 0 0 0 +2 0 0 0 0 +0 1 2 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, both, no loops: +0 2 0 1 1 +2 0 1 0 0 +0 1 0 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, unweighted, both, loops once: +0 2 0 1 1 +2 0 1 0 0 +0 1 1 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, unweighted, both, loops twice: +0 2 0 1 1 +2 0 1 0 0 +0 1 2 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, weighted, upper, no loops: +0 7 0 6 1 +0 0 4 0 0 +0 0 0 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, upper, loops once: +0 7 0 6 1 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, upper, loops twice: +0 7 0 6 1 +0 0 4 0 0 +0 0 6 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, lower, no loops: +0 0 0 0 0 +7 0 0 0 0 +0 4 0 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, lower, loops once: +0 0 0 0 0 +7 0 0 0 0 +0 4 3 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, lower, loops twice: +0 0 0 0 0 +7 0 0 0 0 +0 4 6 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, both, no loops: +0 7 0 6 1 +7 0 4 0 0 +0 4 0 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Undirected, weighted, both, loops once: +0 7 0 6 1 +7 0 4 0 0 +0 4 3 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Undirected, weighted, both, loops twice: +0 7 0 6 1 +7 0 4 0 0 +0 4 6 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Directed, unweighted, no loops: +0 2 0 1 0 +0 0 1 0 0 +0 0 0 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, unweighted, loops once: +0 2 0 1 0 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, unweighted, loops twice (same as once): +0 2 0 1 0 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, weighted, no loops: +0 7 0 6 0 +0 0 4 0 0 +0 0 0 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== +Directed, weighted, loops once: +0 7 0 6 0 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== +Directed, weighted, loops twice (same as once): +0 7 0 6 0 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== diff --git a/tests/unit/igraph_get_adjacency_sparse.c b/tests/unit/igraph_get_adjacency_sparse.c new file mode 100644 index 0000000..76f6291 --- /dev/null +++ b/tests/unit/igraph_get_adjacency_sparse.c @@ -0,0 +1,211 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void print_sparse_matrix(const igraph_sparsemat_t* sm) { + igraph_matrix_t m; + + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_as_matrix(&m, sm); + igraph_matrix_print(&m); + igraph_matrix_destroy(&m); +} + + +void test_undirected(void) { + igraph_t graph; + igraph_real_t weights_array[] = { 5, 4, 3, 2, 1, 6, 3, 2 }; + const igraph_vector_t weights = + igraph_vector_view(weights_array, sizeof(weights_array) / sizeof(weights_array[0])); + igraph_sparsemat_t m; + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + + + igraph_sparsemat_init(&m, 2, 2, 0); + + printf("Undirected, unweighted, upper, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, upper, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, upper, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, NULL, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, lower, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, NULL, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, unweighted, both, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, upper, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_UPPER, &weights, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, lower, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_LOWER, &weights, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, both, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, both, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Undirected, weighted, both, loops twice:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + igraph_sparsemat_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +void test_directed(void) { + igraph_t graph; + igraph_real_t weights_array[] = { 5, 4, 3, 2, 1, 6, 3, 2 }; + const igraph_vector_t weights = + igraph_vector_view(weights_array, sizeof(weights_array) / sizeof(weights_array[0])); + igraph_sparsemat_t m; + + igraph_small(&graph, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + + igraph_sparsemat_init(&m, 2, 2, 0); + + printf("Directed, unweighted, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Directed, unweighted, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Directed, unweighted, loops twice (same as once):\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Directed, weighted, no loops:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_NO_LOOPS); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Directed, weighted, loops once:\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_ONCE); + print_sparse_matrix(&m); + printf("========\n"); + + printf("Directed, weighted, loops twice (same as once):\n"); + igraph_get_adjacency_sparse(&graph, &m, IGRAPH_GET_ADJACENCY_BOTH, &weights, IGRAPH_LOOPS_TWICE); + print_sparse_matrix(&m); + printf("========\n"); + + igraph_sparsemat_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +void test_errors(void) { + igraph_t graph; + igraph_sparsemat_t m; + + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1); + igraph_sparsemat_init(&m, 2, 2, 0); + + CHECK_ERROR(igraph_get_adjacency_sparse(&graph, &m, (igraph_get_adjacency_t) 42, NULL, IGRAPH_LOOPS_ONCE), IGRAPH_EINVAL); + + igraph_sparsemat_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + test_undirected(); + test_directed(); + test_errors(); + + return 0; +} diff --git a/tests/unit/igraph_get_adjacency_sparse.out b/tests/unit/igraph_get_adjacency_sparse.out new file mode 100644 index 0000000..39b1bb2 --- /dev/null +++ b/tests/unit/igraph_get_adjacency_sparse.out @@ -0,0 +1,168 @@ +Undirected, unweighted, upper, no loops: +0 2 0 1 1 +0 0 1 0 0 +0 0 0 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, upper, loops once: +0 2 0 1 1 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, upper, loops twice: +0 2 0 1 1 +0 0 1 0 0 +0 0 2 1 0 +0 0 0 0 1 +0 0 0 0 0 +======== +Undirected, unweighted, lower, no loops: +0 0 0 0 0 +2 0 0 0 0 +0 1 0 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, lower, loops once: +0 0 0 0 0 +2 0 0 0 0 +0 1 1 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, lower, loops twice: +0 0 0 0 0 +2 0 0 0 0 +0 1 2 0 0 +1 0 1 0 0 +1 0 0 1 0 +======== +Undirected, unweighted, both, no loops: +0 2 0 1 1 +2 0 1 0 0 +0 1 0 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, unweighted, both, loops once: +0 2 0 1 1 +2 0 1 0 0 +0 1 1 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, unweighted, both, loops twice: +0 2 0 1 1 +2 0 1 0 0 +0 1 2 1 0 +1 0 1 0 1 +1 0 0 1 0 +======== +Undirected, weighted, upper, no loops: +0 7 0 6 1 +0 0 4 0 0 +0 0 0 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, upper, loops once: +0 7 0 6 1 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, upper, loops twice: +0 7 0 6 1 +0 0 4 0 0 +0 0 6 3 0 +0 0 0 0 2 +0 0 0 0 0 +======== +Undirected, weighted, lower, no loops: +0 0 0 0 0 +7 0 0 0 0 +0 4 0 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, lower, loops once: +0 0 0 0 0 +7 0 0 0 0 +0 4 3 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, lower, loops twice: +0 0 0 0 0 +7 0 0 0 0 +0 4 6 0 0 +6 0 3 0 0 +1 0 0 2 0 +======== +Undirected, weighted, both, no loops: +0 7 0 6 1 +7 0 4 0 0 +0 4 0 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Undirected, weighted, both, loops once: +0 7 0 6 1 +7 0 4 0 0 +0 4 3 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Undirected, weighted, both, loops twice: +0 7 0 6 1 +7 0 4 0 0 +0 4 6 3 0 +6 0 3 0 2 +1 0 0 2 0 +======== +Directed, unweighted, no loops: +0 2 0 1 0 +0 0 1 0 0 +0 0 0 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, unweighted, loops once: +0 2 0 1 0 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, unweighted, loops twice (same as once): +0 2 0 1 0 +0 0 1 0 0 +0 0 1 1 0 +0 0 0 0 1 +1 0 0 0 0 +======== +Directed, weighted, no loops: +0 7 0 6 0 +0 0 4 0 0 +0 0 0 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== +Directed, weighted, loops once: +0 7 0 6 0 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== +Directed, weighted, loops twice (same as once): +0 7 0 6 0 +0 0 4 0 0 +0 0 3 3 0 +0 0 0 0 2 +1 0 0 0 0 +======== diff --git a/tests/unit/igraph_get_all_shortest_paths_dijkstra.c b/tests/unit/igraph_get_all_shortest_paths_dijkstra.c new file mode 100644 index 0000000..2049e5a --- /dev/null +++ b/tests/unit/igraph_get_all_shortest_paths_dijkstra.c @@ -0,0 +1,184 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_nrgeo(const igraph_t *graph, igraph_vs_t vs, + const igraph_vector_int_list_t *paths, + const igraph_vector_int_t *nrgeo) { + igraph_int_t i, n; + igraph_vector_int_t nrgeo2, *path; + igraph_vit_t vit; + + n = igraph_vcount(graph); + igraph_vector_int_init(&nrgeo2, n); + if (igraph_vector_int_size(nrgeo) != n) { + printf("nrgeo vector length must be %" IGRAPH_PRId ", was %" IGRAPH_PRId, n, igraph_vector_int_size(nrgeo)); + return; + } + + n = igraph_vector_int_list_size(paths); + for (i = 0; i < n; i++) { + path = igraph_vector_int_list_get_ptr(paths, i); + if (path == 0) { + printf("Null path found in result vector at index %" IGRAPH_PRId "\n", i); + return; + } + if (igraph_vector_int_size(path) == 0) { + printf("Empty path found in result vector at index %" IGRAPH_PRId "\n", i); + return; + } + VECTOR(nrgeo2)[igraph_vector_int_tail(path)] += 1; + } + + igraph_vit_create(graph, vs, &vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + igraph_int_t node = IGRAPH_VIT_GET(vit); + if (VECTOR(*nrgeo)[node] - VECTOR(nrgeo2)[node]) { + printf("nrgeo[%" IGRAPH_PRId "] invalid, observed = %" IGRAPH_PRId ", expected = %" IGRAPH_PRId "\n", + node, VECTOR(*nrgeo)[node], VECTOR(nrgeo2)[node]); + } + } + igraph_vit_destroy(&vit); + + igraph_vector_int_destroy(&nrgeo2); +} + +void print_and_destroy_items(igraph_vector_int_list_t* vec) { + igraph_int_t i; + + for (i = 0; i < igraph_vector_int_list_size(vec); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(vec, i)); + } + + igraph_vector_int_list_clear(vec); +} + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t vertices, edges; + + igraph_real_t weights[] = { 1, 2, 3, 4, 5, 1, 1, 1, 1, 1 }; + igraph_real_t weights2[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + igraph_int_t dim[] = { 4, 4 }; + + igraph_vector_t weights_vec; + igraph_vector_int_t nrgeo; + igraph_vector_int_t dim_vec; + igraph_vs_t vs; + + igraph_vector_int_init(&nrgeo, 0); + + /* Simple ring graph without weights */ + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + + igraph_vector_int_list_init(&vertices, 0); + igraph_vector_int_list_init(&edges, 0); + igraph_vs_vector_small(&vs, 1, 3, 4, 5, 2, 1, -1); + + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ &vertices, /*edges=*/ &edges, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ NULL, /*mode=*/ IGRAPH_OUT); + check_nrgeo(&g, vs, &vertices, &nrgeo); + print_and_destroy_items(&vertices); + print_and_destroy_items(&edges); + + /* Same ring, but with weights */ + + weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ &vertices, /*edges=*/ NULL, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ &weights_vec, /*mode=*/ IGRAPH_OUT); + check_nrgeo(&g, vs, &vertices, &nrgeo); + print_and_destroy_items(&vertices); + + /* we are now testing the combination of vertices == NULL and edges != NUL */ + + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ NULL, /*edges=*/ &edges, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ &weights_vec, /*mode=*/ IGRAPH_OUT); + print_and_destroy_items(&edges); + + igraph_destroy(&g); + + /* More complicated example */ + + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + weights_vec = igraph_vector_view(weights2, sizeof(weights2) / sizeof(weights2[0])); + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ &vertices, /*edges=*/ &edges, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ &weights_vec, /*mode=*/ IGRAPH_OUT); + + check_nrgeo(&g, vs, &vertices, &nrgeo); + + /* Sort the paths in a deterministic manner to avoid problems with + * different qsort() implementations on different platforms */ + igraph_vector_int_list_sort(&vertices, igraph_vector_int_colex_cmp); + igraph_vector_int_list_sort(&edges, igraph_vector_int_colex_cmp); + print_and_destroy_items(&vertices); + print_and_destroy_items(&edges); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + /* Regular lattice with some heavyweight edges */ + dim_vec = igraph_vector_int_view(dim, sizeof(dim) / sizeof(dim[0])); + igraph_square_lattice(&g, &dim_vec, 1, 0, 0, 0); + igraph_vs_vector_small(&vs, 3, 12, 15, -1); + igraph_vector_init(&weights_vec, 24); + igraph_vector_fill(&weights_vec, 1); + VECTOR(weights_vec)[2] = 100; + VECTOR(weights_vec)[8] = 100; /* 1-->2, 4-->8 */ + igraph_get_all_shortest_paths_dijkstra( + &g, + /*vertices=*/ 0, /*edges=*/ 0, /*nrgeo=*/ &nrgeo, + /*from=*/ 0, /*to=*/ vs, + /*weights=*/ &weights_vec, /*mode=*/ IGRAPH_OUT); + igraph_vector_destroy(&weights_vec); + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + printf("%" IGRAPH_PRId " ", VECTOR(nrgeo)[3]); + printf("%" IGRAPH_PRId " ", VECTOR(nrgeo)[12]); + printf("%" IGRAPH_PRId "\n", VECTOR(nrgeo)[15]); + + igraph_vector_int_list_destroy(&vertices); + igraph_vector_int_list_destroy(&edges); + igraph_vector_int_destroy(&nrgeo); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_all_shortest_paths_dijkstra.out b/tests/unit/igraph_get_all_shortest_paths_dijkstra.out new file mode 100644 index 0000000..6140ede --- /dev/null +++ b/tests/unit/igraph_get_all_shortest_paths_dijkstra.out @@ -0,0 +1,37 @@ +0 1 +0 1 2 +0 1 2 3 +0 1 2 3 4 +0 9 8 7 6 5 +0 1 2 3 4 5 +0 +0 1 +0 1 2 +0 1 2 3 +9 8 7 6 5 +0 1 2 3 4 +0 1 +0 1 2 +0 1 2 3 +0 1 2 3 4 +0 9 8 7 6 5 4 +0 9 8 7 6 5 +0 +0 1 +0 1 2 +0 1 2 3 +9 8 7 6 5 4 +9 8 7 6 5 +0 1 +0 1 2 +0 3 +0 1 2 3 +0 1 4 +0 1 5 +0 +2 +0 3 +0 4 +0 5 +0 3 6 +4 4 12 diff --git a/tests/unit/igraph_get_all_simple_paths.c b/tests/unit/igraph_get_all_simple_paths.c new file mode 100644 index 0000000..077cace --- /dev/null +++ b/tests/unit/igraph_get_all_simple_paths.c @@ -0,0 +1,70 @@ +/* igraph library. + Copyright (C) 2010-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_list_t res, res_all; + igraph_int_t n; + + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 5, + 0, 3, 3, 4, 4, 5, + 3, 2, 3, 5, + -1); + + igraph_vector_int_list_init(&res, 0); + + printf("TEST MINLEN\n\n"); + for (igraph_int_t i = 0; i <= 5; i++) { + igraph_get_all_simple_paths(&g, &res, 0, igraph_vss_1(5), IGRAPH_ALL, i, -1, IGRAPH_UNLIMITED); + + printf("Paths for minlen=%" IGRAPH_PRId ":\n", i); + print_vector_int_list(&res); + } + + printf("\nTEST MAXLEN\n\n"); + for (igraph_int_t i = 0; i <= 5; i++) { + igraph_get_all_simple_paths(&g, &res, 0, igraph_vss_1(5), IGRAPH_ALL, -1, i, IGRAPH_UNLIMITED); + + printf("Paths for maxlen=%" IGRAPH_PRId ":\n", i); + print_vector_int_list(&res); + } + + igraph_vector_int_list_init(&res_all, 0); + + igraph_get_all_simple_paths(&g, &res_all, 0, igraph_vss_1(5), IGRAPH_ALL, -1, -1, IGRAPH_UNLIMITED); + + n = igraph_vector_int_list_size(&res); + IGRAPH_ASSERT(igraph_vector_int_list_size(&res_all) == n); + for (igraph_int_t i = 0; i < n; i++) { + IGRAPH_ASSERT(igraph_vector_int_all_e( + igraph_vector_int_list_get_ptr(&res, i), + igraph_vector_int_list_get_ptr(&res_all, i))); + } + + igraph_vector_int_list_destroy(&res_all); + igraph_vector_int_list_destroy(&res); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_get_all_simple_paths.out b/tests/unit/igraph_get_all_simple_paths.out new file mode 100644 index 0000000..d33ccc6 --- /dev/null +++ b/tests/unit/igraph_get_all_simple_paths.out @@ -0,0 +1,83 @@ +TEST MINLEN + +Paths for minlen=0: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) + 2: ( 0 1 2 5 ) + 3: ( 0 3 2 5 ) + 4: ( 0 3 4 5 ) + 5: ( 0 3 5 ) +} +Paths for minlen=1: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) + 2: ( 0 1 2 5 ) + 3: ( 0 3 2 5 ) + 4: ( 0 3 4 5 ) + 5: ( 0 3 5 ) +} +Paths for minlen=2: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) + 2: ( 0 1 2 5 ) + 3: ( 0 3 2 5 ) + 4: ( 0 3 4 5 ) + 5: ( 0 3 5 ) +} +Paths for minlen=3: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) + 2: ( 0 1 2 5 ) + 3: ( 0 3 2 5 ) + 4: ( 0 3 4 5 ) +} +Paths for minlen=4: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) +} +Paths for minlen=5: +{ + 0: ( 0 1 2 3 4 5 ) +} + +TEST MAXLEN + +Paths for maxlen=0: +{ +} +Paths for maxlen=1: +{ +} +Paths for maxlen=2: +{ + 0: ( 0 3 5 ) +} +Paths for maxlen=3: +{ + 0: ( 0 1 2 5 ) + 1: ( 0 3 2 5 ) + 2: ( 0 3 4 5 ) + 3: ( 0 3 5 ) +} +Paths for maxlen=4: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 0 1 2 5 ) + 2: ( 0 3 2 5 ) + 3: ( 0 3 4 5 ) + 4: ( 0 3 5 ) +} +Paths for maxlen=5: +{ + 0: ( 0 1 2 3 4 5 ) + 1: ( 0 1 2 3 5 ) + 2: ( 0 1 2 5 ) + 3: ( 0 3 2 5 ) + 4: ( 0 3 4 5 ) + 5: ( 0 3 5 ) +} diff --git a/tests/unit/igraph_get_biadjacency.c b/tests/unit/igraph_get_biadjacency.c new file mode 100644 index 0000000..5eb2754 --- /dev/null +++ b/tests/unit/igraph_get_biadjacency.c @@ -0,0 +1,89 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_vector_bool_t *types) { + igraph_matrix_t result; + igraph_vector_int_t row_ids; + igraph_vector_int_t col_ids; + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init(&row_ids, 0); + igraph_vector_int_init(&col_ids, 0); + IGRAPH_ASSERT(igraph_get_biadjacency(graph, types, NULL, &result, &row_ids, &col_ids) == IGRAPH_SUCCESS); + printf("Bipartite adjacency matrix:\n"); + print_matrix(&result); + printf("Row ids:\n"); + print_vector_int(&row_ids); + printf("Col ids:\n"); + print_vector_int(&col_ids); + printf("\n"); + igraph_vector_int_destroy(&row_ids); + igraph_vector_int_destroy(&col_ids); + igraph_matrix_destroy(&result); +} + + +int main(void) { + igraph_t g_0, g_1, g_mu, g_mun; + igraph_vector_bool_t t_0, t_1, t_mu; + igraph_matrix_t result; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_mu, 6, 0, 0,1, 0,2, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_mun, 6, 0, 0,1, 0,2, 0,3, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + igraph_vector_bool_init(&t_0, 0); + igraph_vector_bool_init_int(&t_1, 1, 1); + igraph_vector_bool_init_int(&t_mu, 6, 0, 1, 1, 0, 1, 0); + + igraph_matrix_init(&result, 0, 0); + + printf("No vertices:\n"); + call_and_print(&g_0, &t_0); + + printf("One vertex:\n"); + call_and_print(&g_1, &t_1); + + printf("Disconnected graph with multiple edges:\n"); + call_and_print(&g_mu, &t_mu); + + printf("Checking non-bipartite graph.\n"); + call_and_print(&g_mun, &t_mu); + + VERIFY_FINALLY_STACK(); + + printf("Checking wrong type vector size error handling.\n"); + CHECK_ERROR(igraph_get_biadjacency(&g_mu, &t_0, NULL, &result, NULL, NULL), IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_mu); + igraph_destroy(&g_mun); + + igraph_vector_bool_destroy(&t_0); + igraph_vector_bool_destroy(&t_1); + igraph_vector_bool_destroy(&t_mu); + + igraph_matrix_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_biadjacency.out b/tests/unit/igraph_get_biadjacency.out new file mode 100644 index 0000000..f294bf8 --- /dev/null +++ b/tests/unit/igraph_get_biadjacency.out @@ -0,0 +1,37 @@ +No vertices: +Bipartite adjacency matrix: +[ 0-by-0 ] +Row ids: +( ) +Col ids: +( ) + +One vertex: +Bipartite adjacency matrix: +[ 0-by-1 ] +Row ids: +( ) +Col ids: +( 0 ) + +Disconnected graph with multiple edges: +Bipartite adjacency matrix: +[ 1 3 0 + 1 1 2 + 0 0 0 ] +Row ids: +( 0 3 5 ) +Col ids: +( 1 2 4 ) + +Checking non-bipartite graph. +Bipartite adjacency matrix: +[ 1 3 0 + 1 1 2 + 0 0 0 ] +Row ids: +( 0 3 5 ) +Col ids: +( 1 2 4 ) + +Checking wrong type vector size error handling. diff --git a/tests/unit/igraph_get_eid.c b/tests/unit/igraph_get_eid.c new file mode 100644 index 0000000..4d8eb69 --- /dev/null +++ b/tests/unit/igraph_get_eid.c @@ -0,0 +1,41 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_int_t eid; + + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + + /* NON-EXISTENT EDGE */ + CHECK_ERROR(igraph_get_eid(&g, &eid, 5, 6, IGRAPH_UNDIRECTED, /*error=*/ 1), IGRAPH_EINVAL); + + /* INVALID VERTEX ID */ + CHECK_ERROR(igraph_get_eid(&g, &eid, 171, 6, IGRAPH_UNDIRECTED, /*error=*/ 1), IGRAPH_EINVVID); + + /* INVALID VERTEX ID even if error == 0 */ + CHECK_ERROR(igraph_get_eid(&g, &eid, 171, 6, IGRAPH_UNDIRECTED, /*error=*/ 0), IGRAPH_EINVVID); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_get_isomorphisms_vf2.c b/tests/unit/igraph_get_isomorphisms_vf2.c new file mode 100644 index 0000000..39b5534 --- /dev/null +++ b/tests/unit/igraph_get_isomorphisms_vf2.c @@ -0,0 +1,137 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/* Vertices/edges with the same parity match */ +igraph_bool_t compat_parity(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + return (g1_num % 2) == (g2_num % 2); +} + +igraph_bool_t compat_not_arg(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + return g1_num != *(int*)arg + g2_num; +} + +void print_and_destroy_maps(igraph_vector_int_list_t *vp) { + print_vector_int_list(vp); + igraph_vector_int_list_destroy(vp); +} + +void check_print_destroy(igraph_t *g1, + igraph_t *g2, + igraph_vector_int_t *vertex_color1, + igraph_vector_int_t *vertex_color2, + igraph_vector_int_t *edge_color1, + igraph_vector_int_t *edge_color2, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg, + int error) { + igraph_vector_int_list_t maps; + igraph_vector_int_list_init(&maps, 0); + IGRAPH_ASSERT(igraph_get_isomorphisms_vf2(g1, g2, vertex_color1, vertex_color2, edge_color1, edge_color2, &maps, node_compat_fn, edge_compat_fn, arg) == error); + print_and_destroy_maps(&maps); + printf("\n"); + +} + +void check_print_destroy_simple(igraph_t *g1, igraph_t *g2) { + check_print_destroy(g1, g2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); +} + +int main(void) { + igraph_t cycle, cycle_dir, cycle_loop; + igraph_t g_0, g_1; + igraph_vector_int_t coloring; + int three = 3; + + igraph_vector_int_init_int(&coloring, 5, 0, 1, 0, 1, 0); + igraph_small(&g_0, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_1, 1, IGRAPH_UNDIRECTED, -1); + igraph_cycle_graph(&cycle, 5, /*directed*/ false, /*mutual*/ false); + igraph_cycle_graph(&cycle_dir, 5, /*directed*/ true, /*mutual*/ false); + igraph_cycle_graph(&cycle_loop, 5, /*directed*/ false, /*mutual*/ false); + igraph_add_edge(&cycle_loop, 2, 2); + + printf("Two empty graphs:\n"); + check_print_destroy_simple(&g_0, &g_0); + + printf("Two singleton graphs:\n"); + check_print_destroy_simple(&g_1, &g_1); + + printf("Empty and singleton graphs:\n"); + check_print_destroy_simple(&g_0, &g_1); + + printf("Two cycles:\n"); + check_print_destroy_simple(&cycle, &cycle); + + printf("Two directed cycles:\n"); + check_print_destroy_simple(&cycle_dir, &cycle_dir); + + printf("Two cycles where node parity should be equal:\n"); + check_print_destroy(&cycle, &cycle, NULL, NULL, NULL, NULL, &compat_parity, NULL, NULL, IGRAPH_SUCCESS); + + printf("Two cycles where edge parity should be equal:\n"); + check_print_destroy(&cycle, &cycle, NULL, NULL, NULL, NULL, NULL, &compat_parity, NULL, IGRAPH_SUCCESS); + + printf("Two cycles with only one vertex coloring:\n"); + check_print_destroy(&cycle, &cycle, &coloring, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Two cycles with vertex coloring:\n"); + check_print_destroy(&cycle, &cycle, &coloring, &coloring, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Two cycles with edge coloring:\n"); + check_print_destroy(&cycle, &cycle, NULL, NULL, &coloring, &coloring, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Two cycles where node of graph 1 should not be 3 higher than node of graph 2:\n"); + check_print_destroy(&cycle, &cycle, NULL, NULL, NULL, NULL, &compat_not_arg, NULL, &three, IGRAPH_SUCCESS); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Two cycles with different directedness:\n"); + check_print_destroy(&cycle, &cycle_dir, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_EINVAL); + + printf("Graph with loop edges:\n"); + check_print_destroy(&cycle, &cycle_loop, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&cycle); + igraph_destroy(&cycle_dir); + igraph_destroy(&cycle_loop); + igraph_vector_int_destroy(&coloring); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_isomorphisms_vf2.out b/tests/unit/igraph_get_isomorphisms_vf2.out new file mode 100644 index 0000000..a3f4bb7 --- /dev/null +++ b/tests/unit/igraph_get_isomorphisms_vf2.out @@ -0,0 +1,94 @@ +Two empty graphs: +{ + 0: ( ) +} + +Two singleton graphs: +{ + 0: ( 0 ) +} + +Empty and singleton graphs: +{ +} + +Two cycles: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 4 3 2 1 ) + 2: ( 1 0 4 3 2 ) + 3: ( 1 2 3 4 0 ) + 4: ( 2 1 0 4 3 ) + 5: ( 2 3 4 0 1 ) + 6: ( 3 2 1 0 4 ) + 7: ( 3 4 0 1 2 ) + 8: ( 4 0 1 2 3 ) + 9: ( 4 3 2 1 0 ) +} + +Two directed cycles: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 1 2 3 4 0 ) + 2: ( 2 3 4 0 1 ) + 3: ( 3 4 0 1 2 ) + 4: ( 4 0 1 2 3 ) +} + +Two cycles where node parity should be equal: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 4 3 2 1 0 ) +} + +Two cycles where edge parity should be equal: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 4 3 2 1 ) +} + +Two cycles with only one vertex coloring: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 4 3 2 1 ) + 2: ( 1 0 4 3 2 ) + 3: ( 1 2 3 4 0 ) + 4: ( 2 1 0 4 3 ) + 5: ( 2 3 4 0 1 ) + 6: ( 3 2 1 0 4 ) + 7: ( 3 4 0 1 2 ) + 8: ( 4 0 1 2 3 ) + 9: ( 4 3 2 1 0 ) +} + +Two cycles with vertex coloring: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 4 3 2 1 0 ) +} + +Two cycles with edge coloring: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 4 3 2 1 ) +} + +Two cycles where node of graph 1 should not be 3 higher than node of graph 2: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 1 0 4 3 2 ) + 2: ( 1 2 3 4 0 ) + 3: ( 2 1 0 4 3 ) + 4: ( 2 3 4 0 1 ) + 5: ( 4 0 1 2 3 ) + 6: ( 4 3 2 1 0 ) +} + +Two cycles with different directedness: +{ +} + +Graph with loop edges: +{ +} + diff --git a/tests/unit/igraph_get_k_shortest_paths.c b/tests/unit/igraph_get_k_shortest_paths.c new file mode 100644 index 0000000..114a685 --- /dev/null +++ b/tests/unit/igraph_get_k_shortest_paths.c @@ -0,0 +1,159 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print( + igraph_t *graph, igraph_vector_t *weights, igraph_int_t k, + igraph_int_t from, igraph_int_t to, igraph_neimode_t mode +) { + igraph_vector_int_list_t vertex_paths; + igraph_vector_int_list_t edge_paths; + igraph_vector_int_list_t paths_for_verification; + igraph_int_t i, n; + + igraph_vector_int_list_init(&vertex_paths, 0); + igraph_vector_int_list_init(&edge_paths, 0); + igraph_vector_int_list_init(&paths_for_verification, 0); + + IGRAPH_ASSERT(igraph_get_k_shortest_paths(graph, weights, &vertex_paths, &edge_paths, k, from, to, mode) == IGRAPH_SUCCESS); + + printf("result (vertex IDs): \n"); + print_vector_int_list(&vertex_paths); + + printf("result (edge IDs): \n"); + print_vector_int_list(&edge_paths); + + /* now we execute the same test but with only one of vertex_paths or edge_paths, + * and we check that we get the same result */ + IGRAPH_ASSERT(igraph_get_k_shortest_paths(graph, weights, NULL, &paths_for_verification, k, from, to, mode) == IGRAPH_SUCCESS); + n = igraph_vector_int_list_size(&paths_for_verification); + IGRAPH_ASSERT(n == igraph_vector_int_list_size(&edge_paths)); + for (i = 0; i < n; i++) { + IGRAPH_ASSERT(igraph_vector_int_is_equal( + igraph_vector_int_list_get_ptr(&edge_paths, i), + igraph_vector_int_list_get_ptr(&paths_for_verification, i) + )); + } + + IGRAPH_ASSERT(igraph_get_k_shortest_paths(graph, weights, &paths_for_verification, NULL, k, from, to, mode) == IGRAPH_SUCCESS); + n = igraph_vector_int_list_size(&paths_for_verification); + IGRAPH_ASSERT(n == igraph_vector_int_list_size(&vertex_paths)); + for (i = 0; i < n; i++) { + IGRAPH_ASSERT(igraph_vector_int_is_equal( + igraph_vector_int_list_get_ptr(&vertex_paths, i), + igraph_vector_int_list_get_ptr(&paths_for_verification, i) + )); + } + + igraph_vector_int_list_destroy(&paths_for_verification); + igraph_vector_int_list_destroy(&vertex_paths); + igraph_vector_int_list_destroy(&edge_paths); + printf("\n"); + + VERIFY_FINALLY_STACK(); +} + + +int main(void) { + /* Wiki example taken from https://en.wikipedia.org/wiki/Yen's_algorithm */ + igraph_t g_0, g_1, g_2, g_2c, g_wiki, g_wiki_u, g_sz, g_lattice; + igraph_vector_t weights, weights_wiki, weights_inf; + igraph_vector_int_t dims; + igraph_vector_int_list_t paths; + + igraph_vector_int_list_init(&paths, 0); + + igraph_small(&g_0, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_1, 1, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_2, 2, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_2c, 2, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_small(&g_wiki, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,3, 2,1, 2,3, 2,4, 3,4, 3,5, 4,5, -1); + igraph_small(&g_wiki_u, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,3, 2,1, 2,3, 2,4, 3,4, 3,5, 4,5, -1); + igraph_small(&g_sz, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 1, 2, 2, 0, 0, 3, 3, 4, 4, 2, 2, 2, 2, 4, 0, 4, -1); + + igraph_vector_int_init_int(&dims, 2, 3, 3); + igraph_square_lattice(&g_lattice, &dims, 1, IGRAPH_UNDIRECTED, /* mutual = */ 0, /* periodic = */ NULL); + igraph_vector_int_destroy(&dims); + + igraph_vector_init(&weights, 0); + igraph_vector_init_int(&weights_wiki, 9, 3, 2, 4, 1, 2, 3, 2, 1, 2); + igraph_vector_init_real(&weights_inf, 1, IGRAPH_INFINITY); + + printf("One vertex:\n"); + call_and_print(&g_1, &weights, 2, 0, 0, IGRAPH_ALL); + + printf("Two disconnected vertices:\n"); + call_and_print(&g_2, &weights, 2, 0, 1, IGRAPH_ALL); + + printf("Two vertices with infinite weight edge in between:\n"); + call_and_print(&g_2c, &weights_inf, 2, 0, 1, IGRAPH_ALL); + + printf("Wiki example:\n"); + call_and_print(&g_wiki, &weights_wiki, 10, 0, 5, IGRAPH_OUT); + + printf("Wiki example, 0 shortest paths:\n"); + call_and_print(&g_wiki, &weights_wiki, 0, 0, 5, IGRAPH_OUT); + + printf("Wiki example, 2 shortest paths:\n"); + call_and_print(&g_wiki, &weights_wiki, 2, 0, 5, IGRAPH_OUT); + + printf("Wiki example, other direction:\n"); + call_and_print(&g_wiki, &weights_wiki, 10, 5, 0, IGRAPH_IN); + + printf("Wiki example, direction ignored:\n"); + call_and_print(&g_wiki, &weights_wiki, 20, 5, 0, IGRAPH_ALL); + + printf("Wiki example, undirected:\n"); + call_and_print(&g_wiki_u, &weights_wiki, 20, 5, 0, IGRAPH_ALL); + + printf("Wiki example, no weights:\n"); + call_and_print(&g_wiki, NULL, 10, 0, 5, IGRAPH_OUT); + + printf("Directed unweighted graph:\n"); + call_and_print(&g_sz, NULL, 4, 0, 4, IGRAPH_OUT); + + printf("3x3 square lattice:\n"); + call_and_print(&g_lattice, NULL, 6, 0, 8, IGRAPH_OUT); + + printf("Zero vertices, from and to don't exist:\n"); + CHECK_ERROR(igraph_get_k_shortest_paths(&g_0, &weights, NULL, &paths, 4, 0, 0, IGRAPH_ALL), IGRAPH_EINVVID); + + printf("Wrong weights length:\n"); + CHECK_ERROR(igraph_get_k_shortest_paths(&g_wiki, &weights, NULL, &paths, 4, 0, 5, IGRAPH_ALL), IGRAPH_EINVAL); + + printf("Non-existent mode:\n"); + CHECK_ERROR(igraph_get_k_shortest_paths(&g_1, &weights, NULL, &paths, 4, 0, 0, (igraph_neimode_t) 100), IGRAPH_EINVMODE); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_2); + igraph_destroy(&g_2c); + igraph_destroy(&g_wiki); + igraph_destroy(&g_wiki_u); + igraph_destroy(&g_sz); + igraph_destroy(&g_lattice); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&weights_wiki); + igraph_vector_destroy(&weights_inf); + igraph_vector_int_list_destroy(&paths); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_k_shortest_paths.out b/tests/unit/igraph_get_k_shortest_paths.out new file mode 100644 index 0000000..3b6ab42 --- /dev/null +++ b/tests/unit/igraph_get_k_shortest_paths.out @@ -0,0 +1,219 @@ +One vertex: +result (vertex IDs): +{ + 0: ( 0 ) +} +result (edge IDs): +{ + 0: ( ) +} + +Two disconnected vertices: +result (vertex IDs): +{ +} +result (edge IDs): +{ +} + +Two vertices with infinite weight edge in between: +result (vertex IDs): +{ +} +result (edge IDs): +{ +} + +Wiki example: +result (vertex IDs): +{ + 0: ( 0 2 3 5 ) + 1: ( 0 2 4 5 ) + 2: ( 0 1 3 5 ) + 3: ( 0 2 1 3 5 ) + 4: ( 0 2 3 4 5 ) + 5: ( 0 1 3 4 5 ) + 6: ( 0 2 1 3 4 5 ) +} +result (edge IDs): +{ + 0: ( 1 4 7 ) + 1: ( 1 5 8 ) + 2: ( 0 2 7 ) + 3: ( 1 3 2 7 ) + 4: ( 1 4 6 8 ) + 5: ( 0 2 6 8 ) + 6: ( 1 3 2 6 8 ) +} + +Wiki example, 0 shortest paths: +result (vertex IDs): +{ +} +result (edge IDs): +{ +} + +Wiki example, 2 shortest paths: +result (vertex IDs): +{ + 0: ( 0 2 3 5 ) + 1: ( 0 2 4 5 ) +} +result (edge IDs): +{ + 0: ( 1 4 7 ) + 1: ( 1 5 8 ) +} + +Wiki example, other direction: +result (vertex IDs): +{ + 0: ( 5 3 2 0 ) + 1: ( 5 4 2 0 ) + 2: ( 5 3 1 0 ) + 3: ( 5 4 3 2 0 ) + 4: ( 5 3 1 2 0 ) + 5: ( 5 4 3 1 0 ) + 6: ( 5 4 3 1 2 0 ) +} +result (edge IDs): +{ + 0: ( 7 4 1 ) + 1: ( 8 5 1 ) + 2: ( 7 2 0 ) + 3: ( 8 6 4 1 ) + 4: ( 7 2 3 1 ) + 5: ( 8 6 2 0 ) + 6: ( 8 6 2 3 1 ) +} + +Wiki example, direction ignored: +result (vertex IDs): +{ + 0: ( 5 3 2 0 ) + 1: ( 5 4 2 0 ) + 2: ( 5 3 2 1 0 ) + 3: ( 5 3 1 0 ) + 4: ( 5 4 3 2 0 ) + 5: ( 5 3 1 2 0 ) + 6: ( 5 3 4 2 0 ) + 7: ( 5 4 2 1 0 ) + 8: ( 5 3 4 2 1 0 ) + 9: ( 5 4 3 2 1 0 ) + 10: ( 5 4 3 1 0 ) + 11: ( 5 4 3 1 2 0 ) + 12: ( 5 4 2 3 1 0 ) +} +result (edge IDs): +{ + 0: ( 7 4 1 ) + 1: ( 8 5 1 ) + 2: ( 7 4 3 0 ) + 3: ( 7 2 0 ) + 4: ( 8 6 4 1 ) + 5: ( 7 2 3 1 ) + 6: ( 7 6 5 1 ) + 7: ( 8 5 3 0 ) + 8: ( 7 6 5 3 0 ) + 9: ( 8 6 4 3 0 ) + 10: ( 8 6 2 0 ) + 11: ( 8 6 2 3 1 ) + 12: ( 8 5 4 2 0 ) +} + +Wiki example, undirected: +result (vertex IDs): +{ + 0: ( 5 3 2 0 ) + 1: ( 5 4 2 0 ) + 2: ( 5 3 2 1 0 ) + 3: ( 5 3 1 0 ) + 4: ( 5 4 3 2 0 ) + 5: ( 5 3 1 2 0 ) + 6: ( 5 3 4 2 0 ) + 7: ( 5 4 2 1 0 ) + 8: ( 5 3 4 2 1 0 ) + 9: ( 5 4 3 2 1 0 ) + 10: ( 5 4 3 1 0 ) + 11: ( 5 4 3 1 2 0 ) + 12: ( 5 4 2 3 1 0 ) +} +result (edge IDs): +{ + 0: ( 7 4 1 ) + 1: ( 8 5 1 ) + 2: ( 7 4 3 0 ) + 3: ( 7 2 0 ) + 4: ( 8 6 4 1 ) + 5: ( 7 2 3 1 ) + 6: ( 7 6 5 1 ) + 7: ( 8 5 3 0 ) + 8: ( 7 6 5 3 0 ) + 9: ( 8 6 4 3 0 ) + 10: ( 8 6 2 0 ) + 11: ( 8 6 2 3 1 ) + 12: ( 8 5 4 2 0 ) +} + +Wiki example, no weights: +result (vertex IDs): +{ + 0: ( 0 1 3 5 ) + 1: ( 0 2 4 5 ) + 2: ( 0 2 3 5 ) + 3: ( 0 1 3 4 5 ) + 4: ( 0 2 3 4 5 ) + 5: ( 0 2 1 3 5 ) + 6: ( 0 2 1 3 4 5 ) +} +result (edge IDs): +{ + 0: ( 0 2 7 ) + 1: ( 1 5 8 ) + 2: ( 1 4 7 ) + 3: ( 0 2 6 8 ) + 4: ( 1 4 6 8 ) + 5: ( 1 3 2 7 ) + 6: ( 1 3 2 6 8 ) +} + +Directed unweighted graph: +result (vertex IDs): +{ + 0: ( 0 4 ) + 1: ( 0 3 4 ) + 2: ( 0 1 2 4 ) + 3: ( 0 1 2 4 ) +} +result (edge IDs): +{ + 0: ( 9 ) + 1: ( 4 5 ) + 2: ( 0 2 8 ) + 3: ( 0 1 8 ) +} + +3x3 square lattice: +result (vertex IDs): +{ + 0: ( 0 1 2 5 8 ) + 1: ( 0 3 4 5 8 ) + 2: ( 0 1 4 7 8 ) + 3: ( 0 3 4 7 8 ) + 4: ( 0 1 4 5 8 ) + 5: ( 0 3 6 7 8 ) +} +result (edge IDs): +{ + 0: ( 0 2 4 9 ) + 1: ( 1 5 7 9 ) + 2: ( 0 3 8 11 ) + 3: ( 1 5 8 11 ) + 4: ( 0 3 7 9 ) + 5: ( 1 6 10 11 ) +} + +Zero vertices, from and to don't exist: +Wrong weights length: +Non-existent mode: diff --git a/tests/unit/igraph_get_laplacian.c b/tests/unit/igraph_get_laplacian.c new file mode 100644 index 0000000..06730bb --- /dev/null +++ b/tests/unit/igraph_get_laplacian.c @@ -0,0 +1,115 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void test_laplacian(igraph_t *g, const igraph_vector_t *w, igraph_bool_t dir, igraph_laplacian_normalization_t normalization) { + igraph_matrix_t m, m_converted_sparse; + igraph_sparsemat_t m_sparse; + + igraph_matrix_init(&m, 0, 0); + igraph_matrix_init(&m_converted_sparse, 0, 0); + igraph_sparsemat_init(&m_sparse, 0, 0, 0); + + igraph_get_laplacian(g, &m, IGRAPH_OUT, normalization, w); + igraph_get_laplacian_sparse(g, &m_sparse, IGRAPH_OUT, normalization, w); + igraph_matrix_print(&m); + igraph_sparsemat_as_matrix(&m_converted_sparse, &m_sparse); + IGRAPH_ASSERT(igraph_matrix_all_almost_e(&m, &m_converted_sparse, 1e-15)); + + igraph_matrix_destroy(&m); + igraph_matrix_destroy(&m_converted_sparse); + igraph_sparsemat_destroy(&m_sparse); +} + +int main(void) { + igraph_t g_e_un, g_e_dir, g_un, g_dir; + igraph_vector_t weights, weights_e; + char *n[] = {"none", "symmetric", "left", "right"}; + + igraph_vector_init_int(&weights, 9, 1, 2, 3, 4, 5, 2, 2, 3, 3); + igraph_vector_init_int(&weights_e, 0); + igraph_small(&g_e_un, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_e_dir, 0, IGRAPH_DIRECTED, -1); + igraph_small(&g_un, 6, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 3,4, 4,0, 1,1, 2,2, 1,2, 3,4, -1); + igraph_small(&g_dir, 6, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 3,4, 4,0, 1,1, 2,2, 1,2, 3,4, -1); + + for (int normalization = 0; normalization < 4; normalization++) { + for (int weighted = 0; weighted < 2; weighted++) { + for (int directed = 0; directed < 2; directed++) { + printf("=== normalization: %s, %sweighted, %sdirected\n", + n[normalization], + (weighted ? "" : "un"), + (directed ? "" : "un") + ); + + test_laplacian(directed ? &g_e_dir : &g_e_un, weighted ? &weights_e : NULL, directed, (igraph_laplacian_normalization_t) normalization); + test_laplacian(directed ? &g_dir : &g_un, weighted ? &weights : NULL, directed, (igraph_laplacian_normalization_t) normalization); + } + } + } + + igraph_vector_destroy(&weights_e); + igraph_destroy(&g_e_un); + igraph_destroy(&g_e_dir); + igraph_vector_destroy(&weights); + igraph_destroy(&g_un); + igraph_destroy(&g_dir); + + VERIFY_FINALLY_STACK(); + + { + igraph_t g; + igraph_matrix_t m; + igraph_sparsemat_t m_sparse; + + igraph_matrix_init(&m, 0, 0); + igraph_sparsemat_init(&m_sparse, 0, 0, 0); + + printf("Check errors for zero out/in and non-zero in/out degree.\n"); + /* For IGRAPH_OUT, SYMMETRIC and RIGHT should fail, + for IGRAPH_IN, SYMMETRIC and LEFT should fail. */ + + igraph_small(&g, 2, IGRAPH_DIRECTED, 0,1, -1); + for (int mode_n = 0; mode_n < 2; mode_n++) { + for (int lsr = mode_n; lsr < 2 + mode_n; lsr++) { + igraph_neimode_t mode = mode_n ? IGRAPH_OUT : IGRAPH_IN; + igraph_laplacian_normalization_t normalization; + if (lsr == 0) { + normalization = IGRAPH_LAPLACIAN_LEFT; + } else if (lsr == 1) { + normalization = IGRAPH_LAPLACIAN_SYMMETRIC; + } else { + normalization = IGRAPH_LAPLACIAN_RIGHT; + } + CHECK_ERROR(igraph_get_laplacian(&g, &m, mode, + normalization, NULL), IGRAPH_EINVAL); + CHECK_ERROR(igraph_get_laplacian_sparse(&g, &m_sparse, mode, + normalization, NULL), IGRAPH_EINVAL);; + } + } + igraph_destroy(&g); + igraph_matrix_destroy(&m); + igraph_sparsemat_destroy(&m_sparse); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_get_laplacian.out b/tests/unit/igraph_get_laplacian.out new file mode 100644 index 0000000..51f3b23 --- /dev/null +++ b/tests/unit/igraph_get_laplacian.out @@ -0,0 +1,113 @@ +=== normalization: none, unweighted, undirected + 2 -1 0 0 -1 0 +-1 3 -2 0 0 0 + 0 -2 3 -1 0 0 + 0 0 -1 3 -2 0 +-1 0 0 -2 3 0 + 0 0 0 0 0 0 +=== normalization: none, unweighted, directed + 1 -1 0 0 0 0 + 0 2 -2 0 0 0 + 0 0 1 -1 0 0 + 0 0 0 2 -2 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: none, weighted, undirected + 6 -1 0 0 -5 0 +-1 6 -5 0 0 0 + 0 -5 8 -3 0 0 + 0 0 -3 10 -7 0 +-5 0 0 -7 12 0 + 0 0 0 0 0 0 +=== normalization: none, weighted, directed + 1 -1 0 0 0 0 + 0 5 -5 0 0 0 + 0 0 3 -3 0 0 + 0 0 0 7 -7 0 +-5 0 0 0 5 0 + 0 0 0 0 0 0 +=== normalization: symmetric, unweighted, undirected + 1 -0.316228 0 0 -0.408248 0 +-0.316228 0.6 -0.4 0 0 0 + 0 -0.4 0.6 -0.258199 0 0 + 0 0 -0.258199 1 -0.666667 0 +-0.408248 0 0 -0.666667 1 0 + 0 0 0 0 0 0 +=== normalization: symmetric, unweighted, directed + 1 -0.57735 0 0 0 0 + 0 0.666667 -0.816497 0 0 0 + 0 0 0.5 -0.5 0 0 + 0 0 0 1 -1.41421 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: symmetric, weighted, undirected + 1 -0.129099 0 0 -0.589256 0 +-0.129099 0.6 -0.456435 0 0 0 + 0 -0.456435 0.666667 -0.273861 0 0 + 0 0 -0.273861 1 -0.63901 0 +-0.589256 0 0 -0.63901 1 0 + 0 0 0 0 0 0 +=== normalization: symmetric, weighted, directed + 1 -0.377964 0 0 0 0 + 0 0.714286 -0.845154 0 0 0 + 0 0 0.6 -0.507093 0 0 + 0 0 0 1 -1.18322 0 +-2.23607 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: left, unweighted, undirected + 1 -0.5 0 0 -0.5 0 + -0.2 0.6 -0.4 0 0 0 + 0 -0.4 0.6 -0.2 0 0 + 0 0 -0.333333 1 -0.666667 0 +-0.333333 0 0 -0.666667 1 0 + 0 0 0 0 0 0 +=== normalization: left, unweighted, directed + 1 -1 0 0 0 0 + 0 0.666667 -0.666667 0 0 0 + 0 0 0.5 -0.5 0 0 + 0 0 0 1 -1 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: left, weighted, undirected + 1 -0.166667 0 0 -0.833333 0 + -0.1 0.6 -0.5 0 0 0 + 0 -0.416667 0.666667 -0.25 0 0 + 0 0 -0.3 1 -0.7 0 +-0.416667 0 0 -0.583333 1 0 + 0 0 0 0 0 0 +=== normalization: left, weighted, directed + 1 -1 0 0 0 0 + 0 0.714286 -0.714286 0 0 0 + 0 0 0.6 -0.6 0 0 + 0 0 0 1 -1 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: right, unweighted, undirected + 1 -0.2 0 0 -0.333333 0 +-0.5 0.6 -0.4 0 0 0 + 0 -0.4 0.6 -0.333333 0 0 + 0 0 -0.2 1 -0.666667 0 +-0.5 0 0 -0.666667 1 0 + 0 0 0 0 0 0 +=== normalization: right, unweighted, directed + 1 -0.333333 0 0 0 0 + 0 0.666667 -1 0 0 0 + 0 0 0.5 -0.5 0 0 + 0 0 0 1 -2 0 +-1 0 0 0 1 0 + 0 0 0 0 0 0 +=== normalization: right, weighted, undirected + 1 -0.1 0 0 -0.416667 0 +-0.166667 0.6 -0.416667 0 0 0 + 0 -0.5 0.666667 -0.3 0 0 + 0 0 -0.25 1 -0.583333 0 +-0.833333 0 0 -0.7 1 0 + 0 0 0 0 0 0 +=== normalization: right, weighted, directed + 1 -0.142857 0 0 0 0 + 0 0.714286 -1 0 0 0 + 0 0 0.6 -0.428571 0 0 + 0 0 0 1 -1.4 0 +-5 0 0 0 1 0 + 0 0 0 0 0 0 +Check errors for zero out/in and non-zero in/out degree. diff --git a/tests/unit/igraph_get_shortest_path_astar.c b/tests/unit/igraph_get_shortest_path_astar.c new file mode 100644 index 0000000..b4f6aa3 --- /dev/null +++ b/tests/unit/igraph_get_shortest_path_astar.c @@ -0,0 +1,233 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +#define LENGTH 10 + +int check_edges(const igraph_t *graph, const igraph_vector_int_t *vertices, + const igraph_vector_int_t *edges, int error_code) { + + igraph_bool_t directed = igraph_is_directed(graph); + + igraph_int_t j, n2 = igraph_vector_int_size(edges); + if (igraph_vector_int_size(vertices) == 0 && n2 == 0) { + return 0; + } + if (igraph_vector_int_size(vertices) != n2 + 1) { + exit(error_code + 2); + } + for (j = 0; j < n2; j++) { + igraph_int_t edge = VECTOR(*edges)[j]; + igraph_int_t from = VECTOR(*vertices)[j]; + igraph_int_t to = VECTOR(*vertices)[j + 1]; + if (directed) { + if (from != IGRAPH_FROM(graph, edge) || + to != IGRAPH_TO (graph, edge)) { + exit(error_code); + } + } else { + igraph_int_t from2 = IGRAPH_FROM(graph, edge); + igraph_int_t to2 = IGRAPH_TO(graph, edge); + igraph_int_t min1 = from < to ? from : to; + igraph_int_t max1 = from < to ? to : from; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + if (min1 != min2 || max1 != max2) { + exit(error_code + 3); + } + } + } + + return 0; +} + +igraph_error_t lattice_heuristic(igraph_real_t *result, igraph_int_t source_id, igraph_int_t target_id, void *extra) { + IGRAPH_UNUSED(target_id); + IGRAPH_UNUSED(extra); + int x[4]; + for (int i = 0; i < 4; i++) { + x[i] = source_id % LENGTH; + source_id /= LENGTH; + } + *result = abs(LENGTH - 1 - x[0]) + x[1] + x[2] + x[3]; + return IGRAPH_SUCCESS; +} + +struct xyt { + igraph_vector_t x; + igraph_vector_t y; +}; + +igraph_error_t euclidean_heuristic(igraph_real_t *result, igraph_int_t source_id, igraph_int_t target_id, void *extra) { + struct xyt *xyp = extra; + igraph_real_t xt, xf, yt, yf; + xt = VECTOR(xyp->x)[target_id]; + yt = VECTOR(xyp->y)[target_id]; + xf = VECTOR(xyp->x)[source_id]; + yf = VECTOR(xyp->y)[source_id]; + *result = sqrt((xt-xf)*(xt-xf) + (yt-yf)*(yt-yf)); + return IGRAPH_SUCCESS; +} + +int main(void) { + + igraph_t g; + igraph_vector_int_t vertices, edges; + igraph_real_t weights[] = { 1, 2, 3, 4, 5, 1, 1, 1, 1, 1 }; + igraph_vector_t weights_vec; + struct xyt xy; + igraph_vector_int_t dimvector; + igraph_int_t dims[] = {LENGTH, LENGTH, LENGTH, LENGTH}; + + //set seed for grg random graph generation + igraph_rng_seed(igraph_rng_default(), 42); + + /* Simple ring graph without weights */ + + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + + + printf("Astar on singleton, unweighted, no heuristic:\n"); + igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1); + igraph_get_shortest_path_astar(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ 0, + /*weights=*/ NULL, /*mode=*/ IGRAPH_OUT, + /*heuristic=*/ NULL, NULL); + + check_edges(&g, &vertices, &edges, /*error code*/10); + + igraph_vector_int_print(&vertices); + + igraph_destroy(&g); + + printf("Astar on ring, unweighted, no heuristic:\n"); + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + igraph_get_shortest_path_astar(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ 5, + /*weights=*/ NULL, /*mode=*/ IGRAPH_OUT, + /*heuristic=*/ NULL, NULL); + + check_edges(&g, &vertices, &edges, /*error code*/10); + + igraph_vector_int_print(&vertices); + + /* Same ring, but with weights */ + + printf("Astar, weighted, no heuristic:\n"); + weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + igraph_get_shortest_path_astar(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ 5, + &weights_vec, IGRAPH_OUT, + /*heuristic=*/ NULL, NULL); + + check_edges(&g, &vertices, &edges, 20); + + igraph_vector_int_print(&vertices); + + igraph_destroy(&g); + + printf("Astar, unweighted, lattice with manhattan distance heuristic:\n"); + dimvector = igraph_vector_int_view(dims, sizeof(dims)/sizeof(dims[0])); + + igraph_square_lattice(&g, &dimvector, /*nei*/ 1, IGRAPH_UNDIRECTED, /*mutual*/ false, /*periodic*/NULL); + + //print_graph_canon(&g); + igraph_get_shortest_path_astar(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ LENGTH-1, + /*weights*/NULL, IGRAPH_OUT, + lattice_heuristic, NULL); + igraph_vector_int_print(&vertices); + check_edges(&g, &vertices, &edges, /*error code*/60); + + igraph_destroy(&g); + + printf("Astar, unweighted, grg with euclidean distance heuristic:\n"); + igraph_vector_init(&xy.x, 0); + igraph_vector_init(&xy.y, 0); + + igraph_grg_game(&g, /*nodes*/100, /*radius*/0.2, /*torus*/ false, &xy.x, &xy.y); + igraph_vector_init(&weights_vec, igraph_ecount(&g)); + + for (int i = 0; i < igraph_ecount(&g); i++) { + igraph_real_t xt, xf, yt, yf; + xt = VECTOR(xy.x)[IGRAPH_TO(&g, i)]; + xf = VECTOR(xy.x)[IGRAPH_FROM(&g, i)]; + yt = VECTOR(xy.y)[IGRAPH_TO(&g, i)]; + yf = VECTOR(xy.y)[IGRAPH_FROM(&g, i)]; + VECTOR(weights_vec)[i] = sqrt((xt-xf)*(xt-xf) + (yt-yf)*(yt-yf)); + } + + igraph_get_shortest_path_astar(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ LENGTH-1, + /*weights*/&weights_vec, IGRAPH_OUT, + euclidean_heuristic, &xy); + + igraph_vector_int_print(&vertices); + check_edges(&g, &vertices, &edges, /*error code*/80); + + printf("Check with dijkstra:\n"); + igraph_get_shortest_path_dijkstra(&g, /*vertices=*/ &vertices, + /*edges=*/ &edges, /*from=*/ 0, /*to=*/ LENGTH-1, + /*weights*/&weights_vec, IGRAPH_OUT); + + igraph_vector_int_print(&vertices); + + printf("Same situation but through shortest_path_astar interface:\n"); + igraph_get_shortest_path_astar(&g, &vertices, + &edges, /*from=*/ 0, /*to=*/ LENGTH - 1, + /*weights*/&weights_vec, IGRAPH_OUT, + euclidean_heuristic, &xy); + + igraph_vector_int_print(&vertices); + + printf("Checking from out of range.\n"); + igraph_destroy(&g); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + CHECK_ERROR(igraph_get_shortest_path_astar(&g, NULL, NULL, 10, 1, NULL, IGRAPH_ALL, NULL, NULL), IGRAPH_EINVVID); + + printf("Checking to out of range.\n"); + igraph_destroy(&g); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + CHECK_ERROR(igraph_get_shortest_path_astar(&g, NULL, NULL, 0, 10, NULL, IGRAPH_ALL, NULL, NULL), IGRAPH_EINVVID); + + printf("Checking wrong weight length error.\n"); + igraph_vector_destroy(&weights_vec); + igraph_vector_init_int(&weights_vec, 0); + CHECK_ERROR(igraph_get_shortest_path_astar(&g, NULL, NULL, 0, 1, &weights_vec, IGRAPH_ALL, NULL, NULL), IGRAPH_EINVAL); + + printf("Checking negative weight error.\n"); + igraph_vector_destroy(&weights_vec); + igraph_vector_init_int(&weights_vec, 1, -1); + CHECK_ERROR(igraph_get_shortest_path_astar(&g, NULL, NULL, 0, 1, &weights_vec, IGRAPH_ALL, NULL, NULL), IGRAPH_EINVAL); + + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&weights_vec); + igraph_vector_destroy(&xy.x); + igraph_vector_destroy(&xy.y); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_shortest_path_astar.out b/tests/unit/igraph_get_shortest_path_astar.out new file mode 100644 index 0000000..fda6a83 --- /dev/null +++ b/tests/unit/igraph_get_shortest_path_astar.out @@ -0,0 +1,18 @@ +Astar on singleton, unweighted, no heuristic: +0 +Astar on ring, unweighted, no heuristic: +0 1 2 3 4 5 +Astar, weighted, no heuristic: +0 9 8 7 6 5 +Astar, unweighted, lattice with manhattan distance heuristic: +0 1 2 3 4 5 6 7 8 9 +Astar, unweighted, grg with euclidean distance heuristic: +0 13 17 24 20 9 +Check with dijkstra: +0 13 17 24 20 9 +Same situation but through shortest_path_astar interface: +0 13 17 24 20 9 +Checking from out of range. +Checking to out of range. +Checking wrong weight length error. +Checking negative weight error. diff --git a/tests/unit/igraph_get_shortest_path_bellman_ford.c b/tests/unit/igraph_get_shortest_path_bellman_ford.c new file mode 100644 index 0000000..b99d2a5 --- /dev/null +++ b/tests/unit/igraph_get_shortest_path_bellman_ford.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +int main(void) { + /*most functionality is currently tested in + igraph_get_shortest_paths_bellman_ford(), which is called by + igraph_get_shortest_path_bellman_ford()*/ + igraph_t g; + igraph_vector_int_t vertices, edges; + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + + printf("Basic example, don't ask for vertices and edges.\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_get_shortest_path_bellman_ford(&g, NULL, NULL, + 0, 1, NULL, IGRAPH_OUT); + + printf("Basic example, ask for vertices and edges:\n"); + igraph_get_shortest_path_bellman_ford(&g, &vertices, &edges, + 0, 1, NULL, IGRAPH_OUT); + printf("vertices: "); + print_vector_int(&vertices); + printf("edges: "); + print_vector_int(&edges); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + printf("Check error when passing null graph.\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + CHECK_ERROR( + igraph_get_shortest_path_bellman_ford(&g, NULL, NULL, 0, 0, NULL, IGRAPH_OUT), + IGRAPH_EINVVID); + + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_shortest_path_bellman_ford.out b/tests/unit/igraph_get_shortest_path_bellman_ford.out new file mode 100644 index 0000000..f063046 --- /dev/null +++ b/tests/unit/igraph_get_shortest_path_bellman_ford.out @@ -0,0 +1,5 @@ +Basic example, don't ask for vertices and edges. +Basic example, ask for vertices and edges: +vertices: ( 0 1 ) +edges: ( 0 ) +Check error when passing null graph. diff --git a/tests/unit/igraph_get_shortest_paths2.c b/tests/unit/igraph_get_shortest_paths2.c new file mode 100644 index 0000000..eea03c6 --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths2.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + const igraph_int_t edges[] = { 0, 1, 0, 2, 1, 6, 2, 6, 1, 3, 1, 4, 1, 5, + 3, 2, 4, 2, 5, 2 + }; + const igraph_vector_int_t edgev = igraph_vector_int_view(edges, sizeof(edges) / sizeof(edges[0])); + igraph_vector_int_list_t resvertices, resedges; + igraph_vector_int_t parents, inbound_edges; + igraph_int_t vcount, i; + + + vcount = igraph_vector_int_max(&edgev) + 1; + igraph_create(&g, &edgev, vcount, IGRAPH_DIRECTED); + + igraph_vector_int_list_init(&resvertices, 0); + igraph_vector_int_list_init(&resedges, 0); + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound_edges, 0); + + igraph_get_shortest_paths(&g, NULL, &resvertices, &resedges, /*from=*/ 0, + /*to=*/ igraph_vss_all(), /*mode=*/ IGRAPH_OUT, + &parents, &inbound_edges); + + for (i = 0; i < vcount; i++) { + igraph_vector_int_t *v1 = igraph_vector_int_list_get_ptr(&resvertices, i); + igraph_vector_int_t *v2 = igraph_vector_int_list_get_ptr(&resedges, i); + printf("%" IGRAPH_PRId " V: ", i); + igraph_vector_int_print(v1); + printf("%" IGRAPH_PRId " E: ", i); + igraph_vector_int_print(v2); + } + printf("pred: "); + igraph_vector_int_print(&parents); + printf("inbe: "); + igraph_vector_int_print(&inbound_edges); + + igraph_vector_int_destroy(&inbound_edges); + igraph_vector_int_destroy(&parents); + igraph_vector_int_list_destroy(&resedges); + igraph_vector_int_list_destroy(&resvertices); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_get_shortest_paths2.out b/tests/unit/igraph_get_shortest_paths2.out new file mode 100644 index 0000000..9a7213f --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths2.out @@ -0,0 +1,16 @@ +0 V: 0 +0 E: +1 V: 0 1 +1 E: 0 +2 V: 0 2 +2 E: 1 +3 V: 0 1 3 +3 E: 0 4 +4 V: 0 1 4 +4 E: 0 5 +5 V: 0 1 5 +5 E: 0 6 +6 V: 0 1 6 +6 E: 0 2 +pred: -1 0 0 1 1 1 1 +inbe: -1 0 1 4 5 6 2 diff --git a/tests/unit/igraph_get_shortest_paths_bellman_ford.c b/tests/unit/igraph_get_shortest_paths_bellman_ford.c new file mode 100644 index 0000000..2c0fcac --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths_bellman_ford.c @@ -0,0 +1,214 @@ +/* + igraph library. + (C) 2006-2021 The igraph development team Gabor Csardi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" +#include + +void check_evecs(const igraph_t *graph, const igraph_vector_int_list_t *vecs, + const igraph_vector_int_list_t *evecs) { + + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t i, n = igraph_vector_int_list_size(vecs); + + IGRAPH_ASSERT(igraph_vector_int_list_size(evecs) == n); + + for (i = 0; i < n; i++) { + igraph_vector_int_t *vvec = igraph_vector_int_list_get_ptr(vecs, i); + igraph_vector_int_t *evec = igraph_vector_int_list_get_ptr(evecs, i); + igraph_int_t j, n2 = igraph_vector_int_size(evec); + if (igraph_vector_int_size(vvec) == 0 && n2 == 0) { + continue; + } + IGRAPH_ASSERT(igraph_vector_int_size(vvec) == n2 + 1); + + for (j = 0; j < n2; j++) { + igraph_int_t edge = VECTOR(*evec)[j]; + igraph_int_t from = VECTOR(*vvec)[j]; + igraph_int_t to = VECTOR(*vvec)[j + 1]; + if (directed) { + IGRAPH_ASSERT(from == IGRAPH_FROM(graph, edge) && + to == IGRAPH_TO(graph, edge)); + } else { + igraph_int_t from2 = IGRAPH_FROM(graph, edge); + igraph_int_t to2 = IGRAPH_TO(graph, edge); + igraph_int_t min1 = from < to ? from : to; + igraph_int_t max1 = from < to ? to : from; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + IGRAPH_ASSERT(min1 == min2 && max1 == max2); + } + } + } +} + +void check_parents_inbound(const igraph_t* graph, const igraph_vector_int_t* parents, + const igraph_vector_int_t* inbound, int start) { + + igraph_int_t i, n = igraph_vcount(graph); + + IGRAPH_ASSERT(igraph_vector_int_size(parents) == n); + IGRAPH_ASSERT(igraph_vector_int_size(inbound) == n); + + IGRAPH_ASSERT(VECTOR(*parents)[start] == -1 && VECTOR(*inbound)[start] == -1); + + for (i = 0; i < n; i++) { + if (VECTOR(*parents)[i] == -2) { + IGRAPH_ASSERT(VECTOR(*inbound)[i] == -1); + + } else if (VECTOR(*parents)[i] == -1) { + IGRAPH_ASSERT(i == start); + + IGRAPH_ASSERT(VECTOR(*inbound)[i] == -1); + + } else { + igraph_int_t eid = VECTOR(*inbound)[i]; + igraph_int_t u = IGRAPH_FROM(graph, eid), v = IGRAPH_TO(graph, eid); + if (v != i && !igraph_is_directed(graph)) { + igraph_int_t dummy = u; + u = v; + v = dummy; + } + IGRAPH_ASSERT(v == i); + IGRAPH_ASSERT(u == VECTOR(*parents)[i]); + } + } +} + +int main(void) { + igraph_t g; + igraph_vector_int_list_t vecs, evecs; + igraph_vector_int_t parents, inbound; + igraph_real_t weights_data_0[] = { 0, 2, 1, 0, 5, 2, 1, 1, 0, 2, 2, 8, 1, 1, 3, 1, 1, 4, 2, 1 }; + igraph_real_t weights_data_1[] = { 6, 7, 8, -4, -2, -3, 9, 2, 7 }; + igraph_real_t weights_data_2[] = { 6, 7, 2, -4, -2, -3, 9, 2, 7 }; + igraph_vector_t weights_vec; + igraph_vs_t vs; + igraph_int_t vs_size; + + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 4, 1, 5, + 2, 3, 2, 6, 3, 2, 3, 6, + 4, 5, 4, 7, 5, 6, 5, 8, 5, 9, + 7, 5, 7, 8, 8, 9, + 5, 2, + 2, 1, + -1); + + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound, 0); + + printf("Paths to only some vertices\n"); + + igraph_vs_vector_small(&vs, 0, 1, 3, 5, 2, 1, -1); + igraph_vs_size(&g, &vs, &vs_size); + + igraph_vector_int_list_init(&vecs, 0); + igraph_vector_int_list_init(&evecs, 0); + + weights_vec = igraph_vector_view(weights_data_0, sizeof(weights_data_0) / sizeof(weights_data_0[0])); + igraph_get_shortest_paths_bellman_ford(&g, /*vertices=*/ &vecs, /*edges=*/ &evecs, + /*from=*/ 0, /*to=*/ vs, + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + + check_evecs(&g, &vecs, &evecs); + check_parents_inbound(&g, &parents, &inbound, /* from= */ 0); + + print_vector_int_list(&vecs); + + printf("\nPaths to all vertices\n"); + + vs_size = igraph_vcount(&g); + + igraph_get_shortest_paths_bellman_ford(&g, /*vertices=*/ &vecs, /*edges=*/ &evecs, + /*from=*/ 0, /*to=*/ igraph_vss_all(), + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + + check_evecs(&g, &vecs, &evecs); + check_parents_inbound(&g, &parents, &inbound, /* from= */ 0); + + print_vector_int_list(&vecs); + + igraph_vector_int_list_destroy(&vecs); + igraph_vector_int_list_destroy(&evecs); + + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&inbound); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + + printf("\nGraph with negative weights\n"); + + /***************************************/ + + /* Graph with negative weights */ + + igraph_vector_int_list_init(&vecs, 0); + igraph_vector_int_list_init(&evecs, 0); + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound, 0); + + igraph_vs_vector_small(&vs, 0, 1, 3, 2, 1, -1); + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 0, 3, 1, 3, 1, 4, 2, 1, 3, 2, 3, 4, 4, 0, 4, 2, + -1); + + weights_vec = igraph_vector_view(weights_data_1, sizeof(weights_data_1) / sizeof(weights_data_1[0])); + igraph_get_shortest_paths_bellman_ford(&g, /*vertices=*/ &vecs, /*edges=*/ &evecs, + /*from=*/ 0, /*to=*/ vs, + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + + check_evecs(&g, &vecs, &evecs); + check_parents_inbound(&g, &parents, &inbound, /* from= */ 0); + + print_vector_int_list(&vecs); + + /***************************************/ + + /* Same graph with negative loop */ + igraph_set_error_handler(igraph_error_handler_ignore); + weights_vec = igraph_vector_view(weights_data_2, + sizeof(weights_data_2) / sizeof(weights_data_2[0])); + IGRAPH_ASSERT(igraph_get_shortest_paths_bellman_ford(&g, /*vertices=*/ &vecs, /*edges=*/ &evecs, + /*from=*/ 0, /*to=*/ vs, + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound) == IGRAPH_ENEGCYCLE); + + igraph_vector_int_list_destroy(&vecs); + igraph_vector_int_list_destroy(&evecs); + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&inbound); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_shortest_paths_bellman_ford.out b/tests/unit/igraph_get_shortest_paths_bellman_ford.out new file mode 100644 index 0000000..19457f6 --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths_bellman_ford.out @@ -0,0 +1,32 @@ +Paths to only some vertices +{ + 0: ( 0 ) + 1: ( 0 1 ) + 2: ( 0 3 ) + 3: ( 0 1 5 ) + 4: ( 0 1 2 ) + 5: ( 0 1 ) +} + +Paths to all vertices +{ + 0: ( 0 ) + 1: ( 0 1 ) + 2: ( 0 1 2 ) + 3: ( 0 3 ) + 4: ( 0 1 4 ) + 5: ( 0 1 5 ) + 6: ( 0 1 2 6 ) + 7: ( 0 1 4 7 ) + 8: ( 0 1 5 8 ) + 9: ( 0 1 5 9 ) +} + +Graph with negative weights +{ + 0: ( 0 ) + 1: ( 0 3 2 1 ) + 2: ( 0 3 ) + 3: ( 0 3 2 ) + 4: ( 0 3 2 1 ) +} diff --git a/tests/unit/igraph_get_shortest_paths_dijkstra.c b/tests/unit/igraph_get_shortest_paths_dijkstra.c new file mode 100644 index 0000000..154d9ae --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths_dijkstra.c @@ -0,0 +1,171 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int check_evecs(const igraph_t *graph, const igraph_vector_int_list_t *vecs, + const igraph_vector_int_list_t *evecs, int error_code) { + + igraph_bool_t directed = igraph_is_directed(graph); + igraph_int_t i, n = igraph_vector_int_list_size(vecs); + if (igraph_vector_int_list_size(evecs) != n) { + exit(error_code + 1); + } + + for (i = 0; i < n; i++) { + igraph_vector_int_t *vvec = igraph_vector_int_list_get_ptr(vecs, i); + igraph_vector_int_t *evec = igraph_vector_int_list_get_ptr(evecs, i); + igraph_int_t j, n2 = igraph_vector_int_size(evec); + if (igraph_vector_int_size(vvec) == 0 && n2 == 0) { + continue; + } + if (igraph_vector_int_size(vvec) != n2 + 1) { + exit(error_code + 2); + } + for (j = 0; j < n2; j++) { + igraph_int_t edge = VECTOR(*evec)[j]; + igraph_int_t from = VECTOR(*vvec)[j]; + igraph_int_t to = VECTOR(*vvec)[j + 1]; + if (directed) { + if (from != IGRAPH_FROM(graph, edge) || + to != IGRAPH_TO (graph, edge)) { + exit(error_code); + } + } else { + igraph_int_t from2 = IGRAPH_FROM(graph, edge); + igraph_int_t to2 = IGRAPH_TO(graph, edge); + igraph_int_t min1 = from < to ? from : to; + igraph_int_t max1 = from < to ? to : from; + igraph_int_t min2 = from2 < to2 ? from2 : to2; + igraph_int_t max2 = from2 < to2 ? to2 : from2; + if (min1 != min2 || max1 != max2) { + exit(error_code + 3); + } + } + } + } + + return 0; +} + +int check_parents_inbound(const igraph_t* graph, const igraph_vector_int_t* parents, + const igraph_vector_int_t* inbound, int start, int error_code) { + igraph_int_t i, n = igraph_vcount(graph); + + if (igraph_vector_int_size(parents) != n || + igraph_vector_int_size(inbound) != n) { + exit(error_code); + } + + if (VECTOR(*parents)[start] != -1 || VECTOR(*inbound)[start] != -1) { + printf("%" IGRAPH_PRId "\n", VECTOR(*parents)[start]); + printf("%" IGRAPH_PRId "\n", VECTOR(*inbound)[start]); + exit(error_code + 1); + } + + for (i = 0; i < n; i++) { + if (VECTOR(*parents)[i] == -2) { + if (VECTOR(*inbound)[i] != -1) { + exit(error_code + 2); + } + } else if (VECTOR(*parents)[i] == -1) { + if (i != start) { + exit(error_code + 3); + } + if (VECTOR(*inbound)[i] != -1) { + exit(error_code + 4); + } + } else { + igraph_int_t eid = VECTOR(*inbound)[i]; + igraph_int_t u = IGRAPH_FROM(graph, eid), v = IGRAPH_TO(graph, eid); + if (v != i && !igraph_is_directed(graph)) { + igraph_int_t dummy = u; + u = v; + v = dummy; + } + if (v != i) { + exit(error_code + 5); + } else if (u != VECTOR(*parents)[i]) { + exit(error_code + 6); + } + } + } + + return 0; +} + +int main(void) { + + igraph_t g; + igraph_vector_int_list_t vecs, evecs; + igraph_vector_int_t parents, inbound; + igraph_int_t i; + igraph_real_t weights[] = { 1, 2, 3, 4, 5, 1, 1, 1, 1, 1 }; + const igraph_vector_t weights_vec = igraph_vector_view(weights, sizeof(weights) / sizeof(weights[0])); + igraph_vs_t vs; + + /* Simple ring graph without weights */ + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + + igraph_vector_int_list_init(&vecs, 0); + igraph_vector_int_list_init(&evecs, 0); + igraph_vector_int_init(&parents, 0); + igraph_vector_int_init(&inbound, 0); + + igraph_vs_vector_small(&vs, 0, 1, 3, 5, 2, 1, -1); + + igraph_get_shortest_paths_dijkstra(&g, /*vertices=*/ &vecs, + /*edges=*/ &evecs, /*from=*/ 0, /*to=*/ vs, + /*weights=*/ 0, /*mode=*/ IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + + check_evecs(&g, &vecs, &evecs, 10); + check_parents_inbound(&g, &parents, &inbound, /* from= */ 0, 40); + + for (i = 0; i < igraph_vector_int_list_size(&vecs); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&vecs, i)); + } + + /* Same ring, but with weights */ + + igraph_get_shortest_paths_dijkstra(&g, /*vertices=*/ &vecs, + /*edges=*/ &evecs, /*from=*/ 0, /*to=*/ vs, + &weights_vec, IGRAPH_OUT, + &parents, + /*inbound_edges=*/ &inbound); + + check_evecs(&g, &vecs, &evecs, 20); + check_parents_inbound(&g, &parents, &inbound, /* from= */ 0, 50); + + for (i = 0; i < igraph_vector_int_list_size(&vecs); i++) { + igraph_vector_int_print(igraph_vector_int_list_get_ptr(&vecs, i)); + } + + igraph_vector_int_list_destroy(&vecs); + igraph_vector_int_list_destroy(&evecs); + igraph_vector_int_destroy(&parents); + igraph_vector_int_destroy(&inbound); + + igraph_vs_destroy(&vs); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_shortest_paths_dijkstra.out b/tests/unit/igraph_get_shortest_paths_dijkstra.out new file mode 100644 index 0000000..e9ce12e --- /dev/null +++ b/tests/unit/igraph_get_shortest_paths_dijkstra.out @@ -0,0 +1,12 @@ +0 +0 1 +0 1 2 3 +0 1 2 3 4 5 +0 1 2 +0 1 +0 +0 1 +0 1 2 3 +0 9 8 7 6 5 +0 1 2 +0 1 diff --git a/tests/unit/igraph_get_stochastic.c b/tests/unit/igraph_get_stochastic.c new file mode 100644 index 0000000..24d84e5 --- /dev/null +++ b/tests/unit/igraph_get_stochastic.c @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void validate_sums(igraph_matrix_t* m, igraph_bool_t column_wise) { + igraph_vector_t v; + igraph_vector_t expected; + + igraph_vector_init(&v, 0); + igraph_vector_init(&expected, 0); + + if (column_wise) { + igraph_matrix_colsum(m, &v); + } else { + igraph_matrix_rowsum(m, &v); + } + + igraph_vector_resize(&expected, igraph_matrix_nrow(m)); + igraph_vector_fill(&expected, 1); + VECTOR(expected)[igraph_vector_size(&expected) - 1] = 0; + + IGRAPH_ASSERT(igraph_vector_all_almost_e(&v, &expected, 1e-7)); + + igraph_vector_destroy(&expected); + igraph_vector_destroy(&v); +} + +void test_graph(igraph_bool_t directed) { + igraph_t graph; + igraph_real_t weights_array[] = { 5, 4, 3, 2, 1, 6, 3, 2 }; + const igraph_vector_t weights = + igraph_vector_view(weights_array, sizeof(weights_array) / sizeof(weights_array[0])); + igraph_matrix_t m; + const char* prefix = directed ? "Directed" : "Undirected"; + + igraph_small( + &graph, 6, directed ? IGRAPH_DIRECTED : IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 3, 2, 2, 0, 1, -1 + ); + + igraph_matrix_init(&m, 2, 2); + + printf("%s, unweighted, row-wise:\n", prefix); + igraph_get_stochastic(&graph, &m, /* column_wise = */ 0, NULL); + igraph_matrix_print(&m); + validate_sums(&m, /* column_wise = */ 0); + printf("========\n"); + + printf("%s, unweighted, column-wise:\n", prefix); + igraph_get_stochastic(&graph, &m, /* column_wise = */ 1, NULL); + igraph_matrix_print(&m); + validate_sums(&m, /* column_wise = */ 1); + printf("========\n"); + + printf("%s, weighted, row-wise:\n", prefix); + igraph_get_stochastic(&graph, &m, /* column_wise = */ 0, &weights); + igraph_matrix_print(&m); + validate_sums(&m, /* column_wise = */ 0); + printf("========\n"); + + printf("%s, weighted, column-wise:\n", prefix); + igraph_get_stochastic(&graph, &m, /* column_wise = */ 1, &weights); + igraph_matrix_print(&m); + validate_sums(&m, /* column_wise = */ 1); + printf("========\n"); + + igraph_matrix_destroy(&m); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + + test_graph(/* directed = */ 0); + test_graph(/* directed = */ 1); + + return 0; +} diff --git a/tests/unit/igraph_get_stochastic.out b/tests/unit/igraph_get_stochastic.out new file mode 100644 index 0000000..7e7ccb2 --- /dev/null +++ b/tests/unit/igraph_get_stochastic.out @@ -0,0 +1,64 @@ +Undirected, unweighted, row-wise: + 0 0.5 0 0.25 0.25 0 +0.666667 0 0.333333 0 0 0 + 0 0.25 0.5 0.25 0 0 +0.333333 0 0.333333 0 0.333333 0 + 0.5 0 0 0.5 0 0 + 0 0 0 0 0 0 +======== +Undirected, unweighted, column-wise: + 0 0.666667 0 0.333333 0.5 0 + 0.5 0 0.25 0 0 0 + 0 0.333333 0.5 0.333333 0 0 +0.25 0 0.25 0 0.5 0 +0.25 0 0 0.333333 0 0 + 0 0 0 0 0 0 +======== +Undirected, weighted, row-wise: + 0 0.5 0 0.428571 0.0714286 0 +0.636364 0 0.363636 0 0 0 + 0 0.307692 0.461538 0.230769 0 0 +0.545455 0 0.272727 0 0.181818 0 +0.333333 0 0 0.666667 0 0 + 0 0 0 0 0 0 +======== +Undirected, weighted, column-wise: + 0 0.636364 0 0.545455 0.333333 0 + 0.5 0 0.307692 0 0 0 + 0 0.363636 0.461538 0.272727 0 0 + 0.428571 0 0.230769 0 0.666667 0 +0.0714286 0 0 0.181818 0 0 + 0 0 0 0 0 0 +======== +Directed, unweighted, row-wise: +0 0.666667 0 0.333333 0 0 +0 0 1 0 0 0 +0 0 0.5 0.5 0 0 +0 0 0 0 1 0 +1 0 0 0 0 0 +0 0 0 0 0 0 +======== +Directed, unweighted, column-wise: +0 1 0 0.5 0 0 +0 0 0.5 0 0 0 +0 0 0.5 0.5 0 0 +0 0 0 0 1 0 +1 0 0 0 0 0 +0 0 0 0 0 0 +======== +Directed, weighted, row-wise: +0 0.538462 0 0.461538 0 0 +0 0 1 0 0 0 +0 0 0.5 0.5 0 0 +0 0 0 0 1 0 +1 0 0 0 0 0 +0 0 0 0 0 0 +======== +Directed, weighted, column-wise: +0 1 0 0.666667 0 0 +0 0 0.571429 0 0 0 +0 0 0.428571 0.333333 0 0 +0 0 0 0 1 0 +1 0 0 0 0 0 +0 0 0 0 0 0 +======== diff --git a/tests/unit/igraph_get_stochastic_sparse.c b/tests/unit/igraph_get_stochastic_sparse.c new file mode 100644 index 0000000..db5db6f --- /dev/null +++ b/tests/unit/igraph_get_stochastic_sparse.c @@ -0,0 +1,50 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_sparsemat_t res; + + igraph_sparsemat_init(&res, 0, 0, 0); + + printf("Graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + igraph_get_stochastic_sparse(&g, &res, /* column_wise = */ 0, /* weights = */ NULL); + igraph_sparsemat_print(&res, stdout); + igraph_destroy(&g); + + printf("\nGraph with 4 vertices, rowwise:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,3, 1,3, 2,2, 2,2, 2,3, 3,0, -1); + igraph_get_stochastic_sparse(&g, &res, /* column_wise = */ 0, /* weights = */ NULL); + igraph_sparsemat_print(&res, stdout); + igraph_destroy(&g); + + printf("\nColumnwise:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 1,3, 1,3, 2,2, 2,2, 2,3, 3,0, -1); + igraph_get_stochastic_sparse(&g, &res, /* column_wise = */ 1, /* weights = */ NULL); + igraph_sparsemat_print(&res, stdout); + igraph_destroy(&g); + + igraph_sparsemat_destroy(&res); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_stochastic_sparse.out b/tests/unit/igraph_get_stochastic_sparse.out new file mode 100644 index 0000000..cd43595 --- /dev/null +++ b/tests/unit/igraph_get_stochastic_sparse.out @@ -0,0 +1,19 @@ +Graph with no vertices: + +Graph with 4 vertices, rowwise: +0 1 : 1 +1 3 : 0.5 +1 3 : 0.5 +2 2 : 0.333333 +2 2 : 0.333333 +2 3 : 0.333333 +3 0 : 1 + +Columnwise: +0 1 : 1 +1 3 : 0.333333 +1 3 : 0.333333 +2 2 : 0.5 +2 2 : 0.5 +2 3 : 0.333333 +3 0 : 1 diff --git a/tests/unit/igraph_get_subisomorphisms_vf2.c b/tests/unit/igraph_get_subisomorphisms_vf2.c new file mode 100644 index 0000000..7c8953c --- /dev/null +++ b/tests/unit/igraph_get_subisomorphisms_vf2.c @@ -0,0 +1,151 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/* Vertices/edges with the same parity match */ +igraph_bool_t compat_parity(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + return (g1_num % 2) == (g2_num % 2); +} + +igraph_bool_t compat_not_arg(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_int_t g1_num, + const igraph_int_t g2_num, + void *arg) { + IGRAPH_UNUSED(graph1); + IGRAPH_UNUSED(graph2); + IGRAPH_UNUSED(arg); + return g1_num != *(int*)arg + g2_num; +} + +void print_and_destroy_maps(igraph_vector_int_list_t *vp) { + print_vector_int_list(vp); + igraph_vector_int_list_destroy(vp); +} + +void check_print_destroy(igraph_t *g1, + igraph_t *g2, + igraph_vector_int_t *vertex_color1, + igraph_vector_int_t *vertex_color2, + igraph_vector_int_t *edge_color1, + igraph_vector_int_t *edge_color2, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg, + int error) { + igraph_vector_int_list_t maps; + igraph_vector_int_list_init(&maps, 0); + IGRAPH_ASSERT(igraph_get_subisomorphisms_vf2(g1, g2, vertex_color1, vertex_color2, edge_color1, edge_color2, &maps, node_compat_fn, edge_compat_fn, arg) == error); + print_and_destroy_maps(&maps); + printf("\n"); +} + +void check_print_destroy_simple(igraph_t *g1, igraph_t *g2) { + check_print_destroy(g1, g2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); +} + +int main(void) { + igraph_t ring, ring_dir; + igraph_t ring_plus, ring_plus_dir; + igraph_t ring_loop; + igraph_t g_0, g_1; + igraph_vector_int_t coloring; + igraph_vector_int_t plus_edge_coloring; + igraph_vector_int_t plus_vertex_coloring; + int three = 3; + + igraph_vector_int_init_int(&plus_vertex_coloring, 6, 0, 1, 0, 1, 1, 0); + igraph_vector_int_init_int(&coloring, 5, 0, 1, 0, 1, 0); + igraph_vector_int_init_int(&plus_edge_coloring, 6, 0, 0, 1, 0, 1, 0); + igraph_small(&ring_plus, 6, 0, 0,1, 0,3, 2,1, 2,3, 3,5, 5,0, -1); + igraph_small(&ring_plus_dir, 6, 1, 0,1, 0,3, 1,2, 2,3, 3,5, 5,0, -1); + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_ring(&ring, 5, /*directed*/ 0, /*mutual*/ 0, /*circular*/ 1); + igraph_ring(&ring_dir, 5, /*directed*/ 1, /*mutual*/ 0, /*circular*/ 1); + igraph_ring(&ring_loop, 5, /*directed*/ 0, /*mutual*/ 0, /*circular*/ 1); + igraph_add_edge(&ring_loop, 2, 2); + + printf("Two empty graphs:\n"); + check_print_destroy_simple(&g_0, &g_0); + + printf("Two singleton graphs:\n"); + check_print_destroy_simple(&g_1, &g_1); + + printf("Empty and singleton graphs:\n"); + check_print_destroy_simple(&g_0, &g_1); + + printf("Singleton and empty graphs:\n"); + check_print_destroy_simple(&g_1, &g_0); + + printf("Ring with add vertex and edge (ring+) and ring:\n"); + check_print_destroy_simple(&ring_plus, &ring); + + printf("Ring+ and ring, directed:\n"); + check_print_destroy_simple(&ring_plus_dir, &ring_dir); + + printf("Ring+ and ring where node parity should be equal:\n"); + check_print_destroy(&ring_plus, &ring, NULL, NULL, NULL, NULL, &compat_parity, NULL, NULL, IGRAPH_SUCCESS); + + printf("Ring+ and ring where edge parity should be equal:\n"); + check_print_destroy(&ring_plus, &ring, NULL, NULL, NULL, NULL, NULL, &compat_parity, NULL, IGRAPH_SUCCESS); + + printf("Ring+ and ring with only one vertex coloring:\n"); + check_print_destroy(&ring_plus, &ring, &coloring, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Ring+ and ring with vertex coloring:\n"); + check_print_destroy(&ring_plus, &ring, &plus_vertex_coloring, &coloring, NULL, NULL, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Ring+ and ring with edge coloring:\n"); + check_print_destroy(&ring_plus, &ring, NULL, NULL, &plus_edge_coloring, &coloring, NULL, NULL, NULL, IGRAPH_SUCCESS); + + printf("Ring+ and ring where node of graph 1 should not be 3 higher than node of graph 2:\n"); + check_print_destroy(&ring_plus, &ring, NULL, NULL, NULL, NULL, &compat_not_arg, NULL, &three, IGRAPH_SUCCESS); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Ring+ and ring with different directedness.\n"); + check_print_destroy(&ring_plus_dir, &ring, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_EINVAL); + + printf("Graph with loop edges.\n"); + check_print_destroy(&ring, &ring_loop, NULL, NULL, NULL, NULL, NULL, NULL, NULL, IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&ring); + igraph_destroy(&ring_dir); + igraph_destroy(&ring_plus); + igraph_destroy(&ring_plus_dir); + igraph_destroy(&ring_loop); + igraph_vector_int_destroy(&coloring); + igraph_vector_int_destroy(&plus_edge_coloring); + igraph_vector_int_destroy(&plus_vertex_coloring); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_get_subisomorphisms_vf2.out b/tests/unit/igraph_get_subisomorphisms_vf2.out new file mode 100644 index 0000000..f3f6276 --- /dev/null +++ b/tests/unit/igraph_get_subisomorphisms_vf2.out @@ -0,0 +1,96 @@ +Two empty graphs: +{ + 0: ( ) +} + +Two singleton graphs: +{ + 0: ( 0 ) +} + +Empty and singleton graphs: +{ +} + +Singleton and empty graphs: +{ + 0: ( ) +} + +Ring with add vertex and edge (ring+) and ring: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 0 5 3 2 1 ) + 2: ( 1 0 5 3 2 ) + 3: ( 1 2 3 5 0 ) + 4: ( 2 1 0 5 3 ) + 5: ( 2 3 5 0 1 ) + 6: ( 3 2 1 0 5 ) + 7: ( 3 5 0 1 2 ) + 8: ( 5 0 1 2 3 ) + 9: ( 5 3 2 1 0 ) +} + +Ring+ and ring, directed: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 1 2 3 5 0 ) + 2: ( 2 3 5 0 1 ) + 3: ( 3 5 0 1 2 ) + 4: ( 5 0 1 2 3 ) +} + +Ring+ and ring where node parity should be equal: +{ +} + +Ring+ and ring where edge parity should be equal: +{ + 0: ( 1 0 5 3 2 ) + 1: ( 1 2 3 5 0 ) +} + +Ring+ and ring with only one vertex coloring: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 0 5 3 2 1 ) + 2: ( 1 0 5 3 2 ) + 3: ( 1 2 3 5 0 ) + 4: ( 2 1 0 5 3 ) + 5: ( 2 3 5 0 1 ) + 6: ( 3 2 1 0 5 ) + 7: ( 3 5 0 1 2 ) + 8: ( 5 0 1 2 3 ) + 9: ( 5 3 2 1 0 ) +} + +Ring+ and ring with vertex coloring: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 5 3 2 1 0 ) +} + +Ring+ and ring with edge coloring: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 0 5 3 2 1 ) +} + +Ring+ and ring where node of graph 1 should not be 3 higher than node of graph 2: +{ + 0: ( 0 1 2 3 5 ) + 1: ( 0 5 3 2 1 ) + 2: ( 1 2 3 5 0 ) + 3: ( 2 1 0 5 3 ) + 4: ( 5 0 1 2 3 ) + 5: ( 5 3 2 1 0 ) +} + +Ring+ and ring with different directedness. +{ +} + +Graph with loop edges. +{ +} + diff --git a/tests/unit/igraph_gomory_hu_tree.c b/tests/unit/igraph_gomory_hu_tree.c new file mode 100644 index 0000000..c3b3d25 --- /dev/null +++ b/tests/unit/igraph_gomory_hu_tree.c @@ -0,0 +1,221 @@ +/* + igraph library. + Copyright (C) 2006-2013 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +igraph_error_t validate_tree(const igraph_t *graph, const igraph_t *tree, + const igraph_vector_t *flow, const igraph_vector_t *capacity) { + igraph_int_t n = igraph_vcount(graph); + igraph_int_t no_of_clusters, min_weight_edge_index; + igraph_vector_int_t edges; + igraph_vector_int_t membership; + igraph_real_t min_weight, flow_value; + igraph_t copy; + igraph_int_t i, j, k, m; + + if (igraph_vcount(tree) != n) { + printf("Gomory-Hu tree should have %" IGRAPH_PRId " vertices\n", n); + return IGRAPH_EINVAL; + } + + if (igraph_ecount(tree) != n - 1) { + printf("Gomory-Hu tree should have %" IGRAPH_PRId " edges\n", n - 1); + return IGRAPH_EINVAL; + } + + if (igraph_is_directed(tree)) { + printf("Gomory-Hu tree should be undirected\n"); + return IGRAPH_EINVAL; + } + + if (n < 2) { + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); + + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + IGRAPH_CHECK(igraph_get_shortest_path(tree, NULL, 0, &edges, i, j, IGRAPH_ALL)); + m = igraph_vector_int_size(&edges); + if (m == 0) { + continue; + } + + /* first, check whether the minimum weight along the shortest path + * from i to j is the same as the maximum flow between i and j in + * the original graph */ + min_weight = VECTOR(*flow)[VECTOR(edges)[0]]; + min_weight_edge_index = VECTOR(edges)[0]; + for (k = 1; k < m; k++) { + if (VECTOR(*flow)[VECTOR(edges)[k]] < min_weight) { + min_weight = VECTOR(*flow)[VECTOR(edges)[k]]; + min_weight_edge_index = VECTOR(edges)[k]; + } + } + + IGRAPH_CHECK(igraph_maxflow(graph, &flow_value, 0, 0, 0, 0, i, j, capacity, 0)); + if (flow_value != min_weight) { + printf("Min weight of path %" IGRAPH_PRId " -- %" IGRAPH_PRId " in Gomory-Hu tree is %.4f, " + "expected %.4f from flow calculation\n", i, j, min_weight, flow_value); + return IGRAPH_EINVAL; + } + + /* next, check whether removing an edge s-t from the Gomory-Hu tree would + * partition it exactly the same way as a minimum cut between s and t in + * the original graph */ + IGRAPH_CHECK(igraph_copy(©, tree)); + IGRAPH_FINALLY(igraph_destroy, ©); + + IGRAPH_CHECK(igraph_delete_edges(©, igraph_ess_1(min_weight_edge_index))); + IGRAPH_CHECK(igraph_connected_components(©, &membership, 0, &no_of_clusters, IGRAPH_WEAK)); + + if (no_of_clusters != 2) { + printf( + "Removing edge %" IGRAPH_PRId " -- %" IGRAPH_PRId + " (index %" IGRAPH_PRId ") from the Gomory-Hu tree cuts it " + "in %" IGRAPH_PRId " clusters, expected 2\n", + IGRAPH_FROM(tree, min_weight_edge_index), + IGRAPH_TO(tree, min_weight_edge_index), + min_weight_edge_index, + no_of_clusters + ); + return IGRAPH_EINVAL; + } + + /* finally, check the total capacity of the edges that go between the + * partitions in the original graph; it should be the same as the + * weight of the edge in the Gomory-Hu tree that corresponds to the + * minimum weight along the path we found above */ + m = igraph_ecount(graph); + flow_value = 0.0; + for (j = 0; j < m; j++) { + if (VECTOR(membership)[IGRAPH_FROM(graph, j)] != VECTOR(membership)[IGRAPH_TO(graph, j)]) { + flow_value += capacity ? VECTOR(*capacity)[j] : 1; + } + } + + if (flow_value != VECTOR(*flow)[min_weight_edge_index]) { + printf( + "Edge %" IGRAPH_PRId " -- %" IGRAPH_PRId + " (index %" IGRAPH_PRId ") in the Gomory-Hu tree has weight = %.2f, but " + "the corresponding flow in the original graph has value = %.2f\n", + IGRAPH_FROM(tree, min_weight_edge_index), + IGRAPH_TO(tree, min_weight_edge_index), + min_weight_edge_index, + VECTOR(*flow)[min_weight_edge_index], flow_value + ); + printf("Edge list of original graph:\n"); + print_graph(graph); + if (capacity) { + printf("Capacities of original graph: "); + print_vector(capacity); + } else { + printf("All edges have capacity = 1\n"); + } + printf("Edge list of Gomory-Hu tree:\n"); + print_graph(tree); + printf("Weights of the Gomory-Hu tree: "); + print_vector(flow); + printf("Partition of original graph corresponding to this cut:\n"); + print_vector_int(&membership); + return IGRAPH_EINVAL; + } + + igraph_destroy(©); + IGRAPH_FINALLY_CLEAN(1); + } + } + + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&membership); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +int main(void) { + + igraph_t g; + igraph_t tree; + igraph_vector_t flow; + igraph_vector_t capacity; + + /* initialize flow and capacity vectors */ + igraph_vector_init(&capacity, 0); + igraph_vector_init(&flow, 0); + + /* empty undirected graph */ + igraph_empty(&g, 0, 0); + IGRAPH_ASSERT(igraph_gomory_hu_tree(&g, &tree, &flow, &capacity) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&tree) == 0); + IGRAPH_ASSERT(igraph_vector_size(&flow) == 0); + igraph_destroy(&tree); + igraph_destroy(&g); + + /* simple undirected graph */ + igraph_small(&g, 6, 0, 0, 1, 0, 2, 1, 2, 1, 3, 1, 4, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_resize(&capacity, 9); + VECTOR(capacity)[0] = 1; + VECTOR(capacity)[1] = 7; + VECTOR(capacity)[2] = 1; + VECTOR(capacity)[3] = 3; + VECTOR(capacity)[4] = 2; + VECTOR(capacity)[5] = 4; + VECTOR(capacity)[6] = 1; + VECTOR(capacity)[7] = 6; + VECTOR(capacity)[8] = 2; + IGRAPH_ASSERT(igraph_gomory_hu_tree(&g, &tree, &flow, &capacity) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(validate_tree(&g, &tree, &flow, &capacity) == IGRAPH_SUCCESS); + igraph_destroy(&tree); + + /* Make sure we don't blow up without an outgoing flow vector */ + IGRAPH_ASSERT(igraph_gomory_hu_tree(&g, &tree, 0, &capacity) == IGRAPH_SUCCESS); + igraph_destroy(&tree); + igraph_destroy(&g); + + /* example from Github issue #1810 */ + igraph_full(&g, 4, /* directed = */ 0, /* loops = */ 0); + IGRAPH_ASSERT(igraph_gomory_hu_tree(&g, &tree, &flow, 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(validate_tree(&g, &tree, &flow, 0) == IGRAPH_SUCCESS); + igraph_destroy(&tree); + igraph_destroy(&g); + + /* simple directed graph - should throw an error */ + igraph_small(&g, 6, 1, 0, 1, 0, 2, 1, 2, 1, 3, 1, 4, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_set_error_handler(igraph_error_handler_ignore); + VERIFY_FINALLY_STACK(); + IGRAPH_ASSERT(igraph_gomory_hu_tree(&g, &tree, &flow, &capacity) == IGRAPH_EINVAL); + igraph_set_error_handler(igraph_error_handler_abort); + igraph_destroy(&g); + + /* destroy flow and capacity vectors */ + igraph_vector_destroy(&flow); + igraph_vector_destroy(&capacity); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_graph_center.c b/tests/unit/igraph_graph_center.c new file mode 100644 index 0000000..b513b92 --- /dev/null +++ b/tests/unit/igraph_graph_center.c @@ -0,0 +1,277 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include +#include + +#include "test_utilities.h" + +void check_radius(const igraph_t *graph, const igraph_vector_int_t *center, igraph_neimode_t mode) { + igraph_vector_t ecc; + igraph_real_t radius; + igraph_int_t n = igraph_vector_int_size(center); + + igraph_radius(graph, NULL, &radius, mode); + printf("Radius: %g\n", radius); + + if (n == 0) { + /* Null graph has radius NaN */ + IGRAPH_ASSERT(isnan(radius)); + } else { + igraph_vector_init(&ecc, 0); + igraph_eccentricity(graph, NULL, &ecc, igraph_vss_vector(center), mode); + for (igraph_int_t i=0; i < n; i++) { + IGRAPH_ASSERT(VECTOR(ecc)[i] == radius); + } + igraph_vector_destroy(&ecc); + } +} + +void check_radius_dijkstra(const igraph_t *graph, const igraph_vector_t *weights, + const igraph_vector_int_t *center, igraph_neimode_t mode) { + igraph_vector_t ecc; + igraph_real_t radius; + igraph_int_t n = igraph_vector_int_size(center); + const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON; + + igraph_radius(graph, weights, &radius, mode); + printf("Radius: %g\n", radius); + + if (n == 0) { + /* Null graph has radius NaN */ + IGRAPH_ASSERT(isnan(radius)); + } else { + igraph_vector_init(&ecc, 0); + igraph_eccentricity(graph, weights, &ecc, igraph_vss_vector(center), mode); + for (igraph_int_t i=0; i < n; i++) { + IGRAPH_ASSERT(igraph_cmp_epsilon(VECTOR(ecc)[i], radius, eps) == 0); + } + igraph_vector_destroy(&ecc); + } +} + +int main(void) { + + igraph_t g; + igraph_vector_int_t center; + igraph_vector_t weights, w; + + /* Make sure that this vector has at least as many entries as the + * largest edge count within the test graphs below. */ + igraph_vector_init_range(&weights, 2, 13); + + igraph_vector_int_init(¢er, 0); + + /* Unweighted calculations */ + printf("UNWEIGHTED\n\n"); + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nPath with isolated vertex:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,2, + -1); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nFour isolated vertices:\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, -1); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected path graph P_5:\n"); + igraph_ring(&g, 5, IGRAPH_UNDIRECTED, /* mutual */ false, /* circular */ false); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected graph\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 0, 2, 3, 0, 4, 1, 2, 5, -1); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nDirected path graph P_5:\n"); + igraph_ring(&g, 5, IGRAPH_DIRECTED, /* mutual */ false, /* circular */ false); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nOut-star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nOut-star S_10, undirected mode:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_ALL); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nIn-star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_IN, 0); + igraph_graph_center(&g, NULL, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius(&g, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + /* Weighted calculations */ + printf("\n\nWEIGHTED\n\n"); + + printf("Null graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + igraph_destroy(&g); + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nPath with isolated vertex:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,2, + -1); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nFour isolated vertices:\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, -1); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected path graph P_5:\n"); + igraph_ring(&g, 5, IGRAPH_UNDIRECTED, /* mutual */ false, /* circular */ false); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected graph\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 0, 2, 3, 0, 4, 1, 2, 5, -1); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nDirected path graph P_5:\n"); + igraph_ring(&g, 5, IGRAPH_DIRECTED, /* mutual */ false, /* circular */ false); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nUndirected star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_UNDIRECTED, 0); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nOut-star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nOut-star S_10, undirected mode:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_ALL); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nIn-star S_10:\n"); + igraph_star(&g, 10, IGRAPH_STAR_OUT, 0); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + igraph_destroy(&g); + + printf("\nDirected cycle C_5\n"); + igraph_ring(&g, 5, IGRAPH_DIRECTED, /* mutual */ false, /* circular */ true); + w = igraph_vector_view(VECTOR(weights), igraph_ecount(&g)); + igraph_graph_center(&g, &w, ¢er, IGRAPH_OUT); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_OUT); + + printf("\nDirected cycle C_5, mode=IN\n"); + igraph_graph_center(&g, &w, ¢er, IGRAPH_IN); + print_vector_int(¢er); + check_radius_dijkstra(&g, &w, ¢er, IGRAPH_IN); + igraph_destroy(&g); + + igraph_vector_int_destroy(¢er); + + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_graph_center.out b/tests/unit/igraph_graph_center.out new file mode 100644 index 0000000..35228da --- /dev/null +++ b/tests/unit/igraph_graph_center.out @@ -0,0 +1,99 @@ +UNWEIGHTED + +Null graph: +( ) +Radius: nan + +Singleton graph: +( 0 ) +Radius: 0 + +Path with isolated vertex: +( 1 ) +Radius: 0 + +Four isolated vertices: +( 0 1 2 3 ) +Radius: 0 + +Undirected path graph P_5: +( 2 ) +Radius: 2 + +Undirected graph +( 0 1 2 ) +Radius: 2 + +Directed path graph P_5: +( 4 ) +Radius: 0 + +Undirected star S_10: +( 0 ) +Radius: 1 + +Out-star S_10: +( 1 2 3 4 5 6 7 8 9 ) +Radius: 0 + +Out-star S_10, undirected mode: +( 0 ) +Radius: 1 + +In-star S_10: +( 0 ) +Radius: 0 + + +WEIGHTED + +Null graph: +( ) + +Singleton graph: +( 0 ) +Radius: 0 + +Path with isolated vertex: +( 1 ) +Radius: 0 + +Four isolated vertices: +( 0 1 2 3 ) +Radius: 0 + +Undirected path graph P_5: +( 2 3 ) +Radius: 9 + +Undirected graph +( 2 ) +Radius: 9 + +Directed path graph P_5: +( 4 ) +Radius: 0 + +Undirected star S_10: +( 0 ) +Radius: 10 + +Out-star S_10: +( 1 2 3 4 5 6 7 8 9 ) +Radius: 0 + +Out-star S_10, undirected mode: +( 0 ) +Radius: 10 + +In-star S_10: +( 1 2 3 4 5 6 7 8 9 ) +Radius: 0 + +Directed cycle C_5 +( 0 ) +Radius: 14 + +Directed cycle C_5, mode=IN +( 4 ) +Radius: 14 diff --git a/tests/unit/igraph_graph_power.c b/tests/unit/igraph_graph_power.c new file mode 100644 index 0000000..6368295 --- /dev/null +++ b/tests/unit/igraph_graph_power.c @@ -0,0 +1,120 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_result(igraph_t *g, igraph_int_t order, igraph_bool_t directed) { + igraph_t res; + igraph_graph_power(g, &res, order, directed); + print_graph_canon(&res); + if ((order == 0 || order == 1) && igraph_has_attribute_table()) { + print_attributes(&res); + } + igraph_destroy(&res); +} + +int main(void) { + igraph_t g; + + + printf("Directed graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + print_result(&g, 10, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 10, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A->B->C, directed, order 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 1,2, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 2, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A->B->C, undirected, order 2:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 1,2, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A->B<-C, directed, order 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 2,1, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 2, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A<-B<-C, directed, order 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 1,0, 2,1, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 2, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A->B->C->A, directed, order 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 2, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nBasic example, A->B<->C->A, directed, order 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 1,2, 2,1, 2,0, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + printf("Ignore edge directions:\n"); + print_result(&g, 2, IGRAPH_UNDIRECTED); + igraph_destroy(&g); + + printf("\nDirected graph with loops and multiple edges, order 0:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_result(&g, 0, IGRAPH_DIRECTED); + igraph_destroy(&g); + + printf("\nDirected graph with loops and multiple edges, order 0, with attributes set:\n"); + igraph_set_attribute_table(&igraph_cattribute_table); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + SETGAB(&g, "bool_graph_attr", 1); + SETEAB(&g, "bool_edge_attr", 0, 1); + SETVAB(&g, "bool_vertex_attr", 0, 1); + + print_result(&g, 0, IGRAPH_DIRECTED); + + printf("Same graph, order 1:\n"); + print_result(&g, 1, IGRAPH_DIRECTED); + + printf("Same starting graph, order 2:\n"); + print_result(&g, 2, IGRAPH_DIRECTED); + + printf("Same starting graph, order 3:\n"); + print_result(&g, 3, IGRAPH_DIRECTED); + + printf("Same starting graph, order 12:\n"); + print_result(&g, 12, IGRAPH_DIRECTED); + + printf("Same starting graph, but undirected, order 2:\n"); + igraph_destroy(&g); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,1, 1,3, 2,3, 3,4, 3,4, -1); + print_result(&g, 2, IGRAPH_DIRECTED); + + VERIFY_FINALLY_STACK(); + + printf("\nCheck negative order error.\n"); + CHECK_ERROR(igraph_graph_power(&g, NULL, -1, IGRAPH_DIRECTED), IGRAPH_EINVAL); + igraph_destroy(&g); + + return 0; +} diff --git a/tests/unit/igraph_graph_power.out b/tests/unit/igraph_graph_power.out new file mode 100644 index 0000000..3115084 --- /dev/null +++ b/tests/unit/igraph_graph_power.out @@ -0,0 +1,210 @@ +Directed graph with no vertices: +directed: true +vcount: 0 +edges: { +} +Ignore edge directions: +directed: false +vcount: 0 +edges: { +} + +Basic example, A->B->C, directed, order 2: +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} +Ignore edge directions: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +Basic example, A->B->C, undirected, order 2: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +Basic example, A->B<-C, directed, order 2: +directed: true +vcount: 3 +edges: { +0 1 +2 1 +} +Ignore edge directions: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +Basic example, A<-B<-C, directed, order 2: +directed: true +vcount: 3 +edges: { +1 0 +2 0 +2 1 +} +Ignore edge directions: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +Basic example, A->B->C->A, directed, order 2: +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 0 +1 2 +2 0 +2 1 +} +Ignore edge directions: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +Basic example, A->B<->C->A, directed, order 2: +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 0 +1 2 +2 0 +2 1 +} +Ignore edge directions: +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +1 2 +} + +Directed graph with loops and multiple edges, order 0: +directed: true +vcount: 6 +edges: { +} + +Directed graph with loops and multiple edges, order 0, with attributes set: +directed: true +vcount: 6 +edges: { +} +bool_graph_attr=1 +Vertex 0: bool_vertex_attr=1 +Vertex 1: bool_vertex_attr=0 +Vertex 2: bool_vertex_attr=0 +Vertex 3: bool_vertex_attr=0 +Vertex 4: bool_vertex_attr=0 +Vertex 5: bool_vertex_attr=0 + +Same graph, order 1: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +1 3 +2 3 +3 4 +} +bool_graph_attr=1 +Vertex 0: bool_vertex_attr=1 +Vertex 1: bool_vertex_attr=0 +Vertex 2: bool_vertex_attr=0 +Vertex 3: bool_vertex_attr=0 +Vertex 4: bool_vertex_attr=0 +Vertex 5: bool_vertex_attr=0 +Edge 0 (0-1): +Edge 1 (0-2): +Edge 2 (1-3): +Edge 3 (2-3): +Edge 4 (3-4): + +Same starting graph, order 2: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 3 +1 4 +2 3 +2 4 +3 4 +} +Same starting graph, order 3: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +0 4 +1 3 +1 4 +2 3 +2 4 +3 4 +} +Same starting graph, order 12: +directed: true +vcount: 6 +edges: { +0 1 +0 2 +0 3 +0 4 +1 3 +1 4 +2 3 +2 4 +3 4 +} +Same starting graph, but undirected, order 2: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +1 4 +2 3 +2 4 +3 4 +} + +Check negative order error. diff --git a/tests/unit/igraph_grg_game.c b/tests/unit/igraph_grg_game.c new file mode 100644 index 0000000..c2df03b --- /dev/null +++ b/tests/unit/igraph_grg_game.c @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 137); + + /* Empty graph */ + igraph_grg_game(&g, 100, 0, 0, 0, 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + + /* Full graph */ + igraph_grg_game(&g, 10, sqrt(2.0) / 2, 1, 0, 0); + IGRAPH_ASSERT(igraph_ecount(&g) == igraph_vcount(&g) * (igraph_vcount(&g) - 1) / 2); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_growing_random_game.c b/tests/unit/igraph_growing_random_game.c new file mode 100644 index 0000000..c2f0e81 --- /dev/null +++ b/tests/unit/igraph_growing_random_game.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + + +int main(void) { + igraph_t g; + igraph_vector_int_t degree; + igraph_bool_t tree; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* no vertices */ + + igraph_growing_random_game(&g, /* n: vertices */ 0, /* m: edges_per_vertex */ 3, /* directed */ 0, /* citation */ 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + igraph_destroy(&g); + + /* 1 edge per vertex with citation makes a tree */ + + igraph_growing_random_game(&g, /* n: vertices */ 20, /* m: edges_per_vertex */ 1, /* directed */ 0, /* citation */ 1); + igraph_is_tree(&g, &tree, /* root */ NULL, /* unused mode */ IGRAPH_ALL); + IGRAPH_ASSERT(tree); + igraph_destroy(&g); + + /* out degree of citation equals edges per vertex */ + + igraph_growing_random_game(&g, /* n: vertices */ 10, /* m: edges_per_vertex */ 7, /* directed */ 1, /* citation */ 1); + igraph_vector_int_init(°ree, 0); + igraph_degree(&g, °ree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + for (i = 1; i < 10; i++) { + IGRAPH_ASSERT(VECTOR(degree)[i] == 7); + } + IGRAPH_ASSERT(igraph_is_directed(&g)); + igraph_vector_int_destroy(°ree); + igraph_destroy(&g); + + /* total number of edges is (vertices - 1) * edges */ + + igraph_growing_random_game(&g, /* n: vertices */ 10, /* m: edges_per_vertex */ 7, /* directed */ 1, /* citation */ 0); + IGRAPH_ASSERT(igraph_ecount(&g) == 63); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_has_mutual.c b/tests/unit/igraph_has_mutual.c new file mode 100644 index 0000000..da56b19 --- /dev/null +++ b/tests/unit/igraph_has_mutual.c @@ -0,0 +1,82 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_bool_t has_mutual; + + /* undirected null graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(! has_mutual); + igraph_destroy(&graph); + + /* undirected edgeless graph */ + igraph_empty(&graph, 3, IGRAPH_UNDIRECTED); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(! has_mutual); + igraph_destroy(&graph); + + /* directed null graph */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(! has_mutual); + igraph_destroy(&graph); + + /* directed edgeless graph */ + igraph_empty(&graph, 3, IGRAPH_DIRECTED); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(! has_mutual); + igraph_destroy(&graph); + + /* undirected with edges */ + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(has_mutual); + igraph_destroy(&graph); + + /* directed with no mutual */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 1,2, -1); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(! has_mutual); + igraph_destroy(&graph); + + /* directed with mutual */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 1,0, -1); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(has_mutual); + igraph_destroy(&graph); + + /* directed with loops, loops considered mutual */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 0,1, 1,1, 1,2, -1); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_LOOPS); + IGRAPH_ASSERT(has_mutual); + igraph_destroy(&graph); + + /* directed with loops, loops not considered mutual */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 0,1, 1,1, 1,2, -1); + igraph_has_mutual(&graph, &has_mutual, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(!has_mutual); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_hexagonal_lattice.c b/tests/unit/igraph_hexagonal_lattice.c new file mode 100644 index 0000000..3e6bcee --- /dev/null +++ b/tests/unit/igraph_hexagonal_lattice.c @@ -0,0 +1,102 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t dimvector; + + /* empty graph */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 2)); + VECTOR(dimvector)[0] = 3; + + IGRAPH_CHECK(igraph_hexagonal_lattice(&graph, &dimvector, true, false)); + printf("Empty graph:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* triangular triangular lattice with a single vertex and no edges*/ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 1)); + VECTOR(dimvector)[0] = 1; + + IGRAPH_CHECK(igraph_hexagonal_lattice(&graph, &dimvector, true, false)); + printf("Triangular hexagonal lattice, single hexagon:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* triangular hexagonal lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 1)); + VECTOR(dimvector)[0] = 5; + + IGRAPH_CHECK(igraph_hexagonal_lattice(&graph, &dimvector, true, false)); + printf("Triangular hexagonal lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* rectangular hexagonal lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 2)); + VECTOR(dimvector)[0] = 4; + VECTOR(dimvector)[1] = 5; + + IGRAPH_CHECK(igraph_hexagonal_lattice(&graph, &dimvector, true, true)); + printf("Rectangular hexagonal lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* hexagonal hexagonal lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 3)); + VECTOR(dimvector)[0] = 3; + VECTOR(dimvector)[1] = 4; + VECTOR(dimvector)[2] = 5; + + IGRAPH_CHECK(igraph_hexagonal_lattice(&graph, &dimvector, false, true)); + printf("Hexagonal hexagonal lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + + /* Erroneous calls */ + VECTOR(dimvector)[0] = -3; + CHECK_ERROR(igraph_hexagonal_lattice(&graph, &dimvector, true, true), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 4)); + VECTOR(dimvector)[0] = 3; + VECTOR(dimvector)[1] = 4; + VECTOR(dimvector)[2] = 5; + VECTOR(dimvector)[3] = 5; + CHECK_ERROR(igraph_hexagonal_lattice(&graph, &dimvector, true, true), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_hexagonal_lattice.out b/tests/unit/igraph_hexagonal_lattice.out new file mode 100644 index 0000000..f7da9c2 --- /dev/null +++ b/tests/unit/igraph_hexagonal_lattice.out @@ -0,0 +1,374 @@ +Empty graph: +directed: true +vcount: 0 +edges: { +} +Triangular hexagonal lattice, single hexagon: +directed: true +vcount: 6 +edges: { +0 1 +0 3 +1 2 +2 5 +3 4 +4 5 +} +Triangular hexagonal lattice: +directed: true +vcount: 46 +edges: { +0 1 +0 11 +1 2 +2 3 +2 13 +3 4 +4 5 +4 15 +5 6 +6 7 +6 17 +7 8 +8 9 +8 19 +9 10 +10 21 +11 12 +12 13 +12 22 +13 14 +14 15 +14 24 +15 16 +16 17 +16 26 +17 18 +18 19 +18 28 +19 20 +20 21 +20 30 +22 23 +23 24 +23 31 +24 25 +25 26 +25 33 +26 27 +27 28 +27 35 +28 29 +29 30 +29 37 +31 32 +32 33 +32 38 +33 34 +34 35 +34 40 +35 36 +36 37 +36 42 +38 39 +39 40 +39 43 +40 41 +41 42 +41 45 +43 44 +44 45 +} +Rectangular hexagonal lattice: +directed: true +vcount: 58 +edges: { +0 1 +0 12 +1 0 +1 2 +2 1 +2 3 +2 14 +3 2 +3 4 +4 3 +4 5 +4 16 +5 4 +5 6 +6 5 +6 7 +6 18 +7 6 +7 8 +8 7 +8 9 +8 20 +9 8 +9 10 +10 9 +10 22 +11 12 +11 23 +12 0 +12 11 +12 13 +13 12 +13 14 +13 25 +14 2 +14 13 +14 15 +15 14 +15 16 +15 27 +16 4 +16 15 +16 17 +17 16 +17 18 +17 29 +18 6 +18 17 +18 19 +19 18 +19 20 +19 31 +20 8 +20 19 +20 21 +21 20 +21 22 +21 33 +22 10 +22 21 +23 11 +23 24 +24 23 +24 25 +24 36 +25 13 +25 24 +25 26 +26 25 +26 27 +26 38 +27 15 +27 26 +27 28 +28 27 +28 29 +28 40 +29 17 +29 28 +29 30 +30 29 +30 31 +30 42 +31 19 +31 30 +31 32 +32 31 +32 33 +32 44 +33 21 +33 32 +33 34 +34 33 +34 46 +35 36 +35 47 +36 24 +36 35 +36 37 +37 36 +37 38 +37 49 +38 26 +38 37 +38 39 +39 38 +39 40 +39 51 +40 28 +40 39 +40 41 +41 40 +41 42 +41 53 +42 30 +42 41 +42 43 +43 42 +43 44 +43 55 +44 32 +44 43 +44 45 +45 44 +45 46 +45 57 +46 34 +46 45 +47 35 +47 48 +48 47 +48 49 +49 37 +49 48 +49 50 +50 49 +50 51 +51 39 +51 50 +51 52 +52 51 +52 53 +53 41 +53 52 +53 54 +54 53 +54 55 +55 43 +55 54 +55 56 +56 55 +56 57 +57 45 +57 56 +} +Hexagonal hexagonal lattice: +directed: false +vcount: 94 +edges: { +0 1 +0 8 +1 2 +2 3 +2 10 +3 4 +4 5 +4 12 +5 6 +6 14 +7 8 +7 17 +8 9 +9 10 +9 19 +10 11 +11 12 +11 21 +12 13 +13 14 +13 23 +14 15 +15 25 +16 17 +16 28 +17 18 +18 19 +18 30 +19 20 +20 21 +20 32 +21 22 +22 23 +22 34 +23 24 +24 25 +24 36 +25 26 +26 38 +27 28 +27 40 +28 29 +29 30 +29 42 +30 31 +31 32 +31 44 +32 33 +33 34 +33 46 +34 35 +35 36 +35 48 +36 37 +37 38 +37 50 +38 39 +39 52 +40 41 +41 42 +41 54 +42 43 +43 44 +43 56 +44 45 +45 46 +45 58 +46 47 +47 48 +47 60 +48 49 +49 50 +49 62 +50 51 +51 52 +51 64 +52 53 +53 66 +54 55 +55 56 +55 67 +56 57 +57 58 +57 69 +58 59 +59 60 +59 71 +60 61 +61 62 +61 73 +62 63 +63 64 +63 75 +64 65 +65 66 +65 77 +67 68 +68 69 +68 78 +69 70 +70 71 +70 80 +71 72 +72 73 +72 82 +73 74 +74 75 +74 84 +75 76 +76 77 +76 86 +78 79 +79 80 +79 87 +80 81 +81 82 +81 89 +82 83 +83 84 +83 91 +84 85 +85 86 +85 93 +87 88 +88 89 +89 90 +90 91 +91 92 +92 93 +} diff --git a/tests/unit/igraph_hrg.c b/tests/unit/igraph_hrg.c new file mode 100644 index 0000000..b236efe --- /dev/null +++ b/tests/unit/igraph_hrg.c @@ -0,0 +1,103 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_t full, tree; + igraph_hrg_t hrg; + igraph_t dendrogram; + igraph_vector_t prob; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_full(&full, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_kary_tree(&tree, 15, /*children=*/ 2, /*type=*/ IGRAPH_TREE_UNDIRECTED); + igraph_disjoint_union(&graph, &full, &tree); + igraph_add_edge(&graph, 0, 10); + + igraph_destroy(&full); + igraph_destroy(&tree); + + // Fit + igraph_hrg_init(&hrg, igraph_vcount(&graph)); + igraph_hrg_fit(&graph, &hrg, /*start=*/ false, /*steps=*/ 0); + + // Create a graph from it + igraph_vector_init(&prob, 0); + igraph_from_hrg_dendrogram(&dendrogram, &hrg, &prob); + + // Print the tree, with labels + igraph_vector_int_t neis; + igraph_vector_int_init(&neis, 0); + for (igraph_int_t i=0; i < igraph_vcount(&graph)-1; i++) { + printf("Vertex # %2" IGRAPH_PRId ", ", (i+igraph_vcount(&graph))); + igraph_neighbors( + &dendrogram, &neis, i+igraph_vcount(&graph), IGRAPH_OUT, + IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE + ); + printf("left: # %2" IGRAPH_PRId ", right: # %2" IGRAPH_PRId ", ", VECTOR(neis)[0], VECTOR(neis)[1]); + printf("prob: %6.2g\n", VECTOR(prob)[i+igraph_vcount(&graph)]); + } + igraph_vector_int_destroy(&neis); + + igraph_vector_destroy(&prob); + igraph_destroy(&dendrogram); + igraph_hrg_destroy(&hrg); + igraph_destroy(&graph); + + // test small graph, omit probabilities + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, + 0,1, + -1); + + igraph_hrg_init(&hrg, igraph_vcount(&graph)); + igraph_hrg_fit(&graph, &hrg, /*start=*/ false, /*steps=*/ 0); + igraph_from_hrg_dendrogram(&dendrogram, &hrg, NULL); + igraph_destroy(&dendrogram); + igraph_hrg_destroy(&hrg); + + // test with a specific number of steps + igraph_hrg_init(&hrg, igraph_vcount(&graph)); + igraph_hrg_fit(&graph, &hrg, /*start=*/ false, /*steps=*/ 10); + igraph_from_hrg_dendrogram(&dendrogram, &hrg, NULL); + igraph_destroy(&dendrogram); + igraph_hrg_destroy(&hrg); + + igraph_destroy(&graph); + + // graph must have at least 3 vertices at the moment + igraph_full(&graph, 2, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_hrg_init(&hrg, igraph_vcount(&graph)); + CHECK_ERROR(igraph_hrg_fit(&graph, &hrg, /*start=*/ false, /*steps=*/ 0), IGRAPH_EINVAL); + igraph_hrg_destroy(&hrg); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_hrg2.c b/tests/unit/igraph_hrg2.c new file mode 100644 index 0000000..572f869 --- /dev/null +++ b/tests/unit/igraph_hrg2.c @@ -0,0 +1,98 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t karate; + igraph_vector_int_t parents; + igraph_vector_t weights; + igraph_int_t i, n; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&karate, 34, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, 0, 12, 0, 13, + 0, 17, 0, 19, 0, 21, 0, 31, + 1, 2, 1, 3, 1, 7, 1, 13, 1, 17, 1, 19, 1, 21, 1, 30, + 2, 3, 2, 7, 2, 27, 2, 28, 2, 32, 2, 9, 2, 8, 2, 13, + 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, + 5, 6, 5, 10, 5, 16, + 6, 16, + 8, 30, 8, 32, 8, 33, + 9, 33, + 13, 33, + 14, 32, 14, 33, + 15, 32, 15, 33, + 18, 32, 18, 33, + 19, 33, + 20, 32, 20, 33, + 22, 32, 22, 33, + 23, 25, 23, 27, 23, 32, 23, 33, 23, 29, + 24, 25, 24, 27, 24, 31, + 25, 31, + 26, 29, 26, 33, + 27, 33, + 28, 31, 28, 33, + 29, 32, 29, 33, + 30, 32, 30, 33, + 31, 32, 31, 33, + 32, 33, + -1); + + igraph_vector_int_init(&parents, 0); + igraph_vector_init(&weights, 0); + igraph_hrg_consensus(&karate, &parents, &weights, /* hrg= */ NULL, + /* start= */ false, /* num_samples= */ 100); + + /* We do some simple validity tests on the results only; the exact results + * are different on i386 vs other platforms due to numerical inaccuracies */ + if (igraph_vector_size(&weights) + igraph_vcount(&karate) != igraph_vector_int_size(&parents)) { + fprintf(stderr, "Vector length mismatch: %" IGRAPH_PRId " + %" IGRAPH_PRId " != %" IGRAPH_PRId "\n", + igraph_vector_size(&weights), igraph_vcount(&karate), + igraph_vector_int_size(&parents) + ); + IGRAPH_FATAL("Vector length mismatch."); + } + + n = igraph_vector_int_size(&parents); + for (i = 0; i < n; i++) { + if (VECTOR(parents)[i] < -1 || VECTOR(parents)[i] >= igraph_vcount(&karate) + igraph_vector_size(&weights)) { + fprintf(stderr, "Invalid parents vector:\n"); + igraph_vector_int_fprint(&parents, stderr); + IGRAPH_FATAL("Invalid parents vector."); + } + } + + igraph_vector_int_destroy(&parents); + igraph_vector_destroy(&weights); + igraph_destroy(&karate); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_hrg3.c b/tests/unit/igraph_hrg3.c new file mode 100644 index 0000000..ad26ef8 --- /dev/null +++ b/tests/unit/igraph_hrg3.c @@ -0,0 +1,99 @@ +/* -*- mode: C++ -*- */ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t karate; + igraph_vector_int_t edges; + igraph_vector_t prob; + igraph_int_t i, n; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&karate, 34, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 10, 0, 11, 0, 12, 0, 13, + 0, 17, 0, 19, 0, 21, 0, 31, + 1, 2, 1, 3, 1, 7, 1, 13, 1, 17, 1, 19, 1, 21, 1, 30, + 2, 3, 2, 7, 2, 27, 2, 28, 2, 32, 2, 9, 2, 8, 2, 13, + 3, 7, 3, 12, 3, 13, + 4, 6, 4, 10, + 5, 6, 5, 10, 5, 16, + 6, 16, + 8, 30, 8, 32, 8, 33, + 9, 33, + 13, 33, + 14, 32, 14, 33, + 15, 32, 15, 33, + 18, 32, 18, 33, + 19, 33, + 20, 32, 20, 33, + 22, 32, 22, 33, + 23, 25, 23, 27, 23, 32, 23, 33, 23, 29, + 24, 25, 24, 27, 24, 31, + 25, 31, + 26, 29, 26, 33, + 27, 33, + 28, 31, 28, 33, + 29, 32, 29, 33, + 30, 32, 30, 33, + 31, 32, 31, 33, + 32, 33, + -1); + + igraph_vector_int_init(&edges, 0); + igraph_vector_init(&prob, 0); + igraph_hrg_predict(&karate, &edges, &prob, /* hrg= */ NULL, /* start= */ false, + /* num_samples= */ 100, /* num_bins= */ 25); + + /* We do some simple validity tests on the results only; the exact results + * are different on i386 vs other platforms due to numerical inaccuracies */ + n = igraph_vector_int_size(&edges); + for (i = 0; i < n; i++) { + if (VECTOR(edges)[i] < 0 || VECTOR(edges)[i] >= igraph_vcount(&karate)) { + printf("Invalid edges vector:\n"); + igraph_vector_int_print(&edges); + return 1; + } + } + n = igraph_vector_size(&prob); + for (i = 0; i < n; i++) { + if (VECTOR(prob)[i] < 0 || VECTOR(prob)[i] > 1) { + printf("Invalid prob vector:\n"); + igraph_vector_print(&prob); + return 2; + } + } + + igraph_vector_destroy(&prob); + igraph_vector_int_destroy(&edges); + + igraph_destroy(&karate); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_hrg_create.c b/tests/unit/igraph_hrg_create.c new file mode 100644 index 0000000..5bc00bf --- /dev/null +++ b/tests/unit/igraph_hrg_create.c @@ -0,0 +1,79 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_hrg_t hrg; + igraph_t graph, new_graph; + igraph_vector_t prob; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Two leaf nodes which are always connected:\n"); + igraph_vector_init_real(&prob, 1, 1.0); + igraph_hrg_init(&hrg, 0); + igraph_small(&graph, 3, IGRAPH_DIRECTED, 0,1, 0,2, -1); + /* the graph and the prob form the hrg dendrogram with probabilities. So you're not just using some graph */ + /* igraph_hrg_fit is used if you just have some graph that you'd want to do HRG analysis on */ + igraph_hrg_create(&hrg, &graph, &prob); + igraph_hrg_game(&new_graph, &hrg); + print_graph_canon(&new_graph); + igraph_destroy(&graph); + igraph_destroy(&new_graph); + igraph_vector_destroy(&prob); + + printf("Four leaf nodes, one node connected to all others:\n"); + igraph_vector_init_real(&prob, 3, 1.0, 0.0, 0.0); + igraph_small(&graph, 7, IGRAPH_DIRECTED, 0,3, 0,1, 1,4, 1,2, 2,5, 2,6, -1); + igraph_hrg_create(&hrg, &graph, &prob); + igraph_hrg_game(&new_graph, &hrg); + print_graph_canon(&new_graph); + + igraph_destroy(&graph); + igraph_destroy(&new_graph); + igraph_vector_destroy(&prob); + + VERIFY_FINALLY_STACK(); + + printf("Check error handling for wrong number of probabilities.\n"); + igraph_vector_init_real(&prob, 3, 1.0, 0.0, 0.0); + igraph_small(&graph, 3, IGRAPH_DIRECTED, 0,1, 0,2, -1); + CHECK_ERROR(igraph_hrg_create(&hrg, &graph, &prob), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_vector_destroy(&prob); + + printf("Check error handling for graph with loop.\n"); + igraph_vector_init_real(&prob, 1, 1.0); + igraph_small(&graph, 3, IGRAPH_DIRECTED, 0,0, 0,2, -1); + CHECK_ERROR(igraph_hrg_create(&hrg, &graph, &prob), IGRAPH_EINVAL); + igraph_destroy(&graph); + + printf("Check error handling for graph with no edges.\n"); + igraph_small(&graph, 3, IGRAPH_DIRECTED, -1); + CHECK_ERROR(igraph_hrg_create(&hrg, &graph, &prob), IGRAPH_EINVAL); + igraph_destroy(&graph); + igraph_vector_destroy(&prob); + igraph_hrg_destroy(&hrg); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_hrg_create.out b/tests/unit/igraph_hrg_create.out new file mode 100644 index 0000000..bdeafb7 --- /dev/null +++ b/tests/unit/igraph_hrg_create.out @@ -0,0 +1,17 @@ +Two leaf nodes which are always connected: +directed: false +vcount: 2 +edges: { +0 1 +} +Four leaf nodes, one node connected to all others: +directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +} +Check error handling for wrong number of probabilities. +Check error handling for graph with loop. +Check error handling for graph with no edges. diff --git a/tests/unit/igraph_hsbm_game.c b/tests/unit/igraph_hsbm_game.c new file mode 100644 index 0000000..8a452f2 --- /dev/null +++ b/tests/unit/igraph_hsbm_game.c @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_int_t n, igraph_int_t m, igraph_vector_t *rho, igraph_matrix_t *pref_matrix, igraph_real_t p) { + igraph_t result; + igraph_hsbm_game(&result, n, m, rho, pref_matrix, p); + print_graph_canon(&result); + printf("\n"); + igraph_destroy(&result); +} + + +int main(void) { + igraph_matrix_t pref_matrix; + igraph_vector_t rho; + + igraph_vector_init_int(&rho, 1, 1); + + igraph_matrix_init(&pref_matrix, 1, 1); + MATRIX(pref_matrix, 0, 0) = 1; + + printf("One block, one vertex.\n"); + call_and_print(1, 1, &rho, &pref_matrix, 0); + + igraph_vector_destroy(&rho); + igraph_matrix_destroy(&pref_matrix); + + { + igraph_vector_init_real(&rho, 3, 0.6, 0.4, 0.0); + + igraph_real_t elems[] = {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + matrix_init_real_row_major(&pref_matrix, 3, 3, elems); + + printf("One block, two clusters, 6 and 4 vertices in cluster, complete bipartite.\n"); + call_and_print(10, 10, &rho, &pref_matrix, 0); + + igraph_vector_destroy(&rho); + igraph_matrix_destroy(&pref_matrix); + } + + { + igraph_vector_init_real(&rho, 3, 0.6, 0.4, 0.0); + + igraph_real_t elems[] = {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + matrix_init_real_row_major(&pref_matrix, 3, 3, elems); + + printf("Two blocks, two clusters each, 3 and 2 vertices in cluster, each vertex connected to every other, except those in the same cluster.\n"); + call_and_print(10, 5, &rho, &pref_matrix, 1); + + igraph_vector_destroy(&rho); + igraph_matrix_destroy(&pref_matrix); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_hsbm_game.out b/tests/unit/igraph_hsbm_game.out new file mode 100644 index 0000000..c326939 --- /dev/null +++ b/tests/unit/igraph_hsbm_game.out @@ -0,0 +1,79 @@ +One block, one vertex. +directed: false +vcount: 1 +edges: { +} + +One block, two clusters, 6 and 4 vertices in cluster, complete bipartite. +directed: false +vcount: 10 +edges: { +0 6 +0 7 +0 8 +0 9 +1 6 +1 7 +1 8 +1 9 +2 6 +2 7 +2 8 +2 9 +3 6 +3 7 +3 8 +3 9 +4 6 +4 7 +4 8 +4 9 +5 6 +5 7 +5 8 +5 9 +} + +Two blocks, two clusters each, 3 and 2 vertices in cluster, each vertex connected to every other, except those in the same cluster. +directed: false +vcount: 10 +edges: { +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 5 +3 6 +3 7 +3 8 +3 9 +4 5 +4 6 +4 7 +4 8 +4 9 +5 8 +5 9 +6 8 +6 9 +7 8 +7 9 +} + diff --git a/tests/unit/igraph_hsbm_list_game.c b/tests/unit/igraph_hsbm_list_game.c new file mode 100644 index 0000000..414b99c --- /dev/null +++ b/tests/unit/igraph_hsbm_list_game.c @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_int_t n, igraph_vector_int_t *mlist, igraph_vector_list_t *rholist, igraph_matrix_list_t *pref_matrix_list, igraph_real_t p) { + igraph_t result; + igraph_hsbm_list_game(&result, n, mlist, rholist, pref_matrix_list, p); + print_graph_canon(&result); + printf("\n"); + igraph_destroy(&result); +} + + +int main(void) { + igraph_matrix_t pref_matrix; + igraph_matrix_list_t pref_matrix_list; + igraph_vector_t rho; + igraph_vector_list_t rholist; + igraph_vector_int_t mlist; + + igraph_vector_list_init(&rholist, 0); + igraph_vector_init_int(&rho, 1, 1); + igraph_vector_list_push_back(&rholist, &rho); + + igraph_matrix_list_init(&pref_matrix_list, 0); + igraph_matrix_init(&pref_matrix, 1, 1); + MATRIX(pref_matrix, 0, 0) = 1; + igraph_matrix_list_push_back(&pref_matrix_list, &pref_matrix); + + igraph_vector_int_init_int(&mlist, 1, 1); + printf("One block, one vertex.\n"); + call_and_print(1, &mlist, &rholist, &pref_matrix_list, 0); + + igraph_vector_list_clear(&rholist); + igraph_matrix_list_clear(&pref_matrix_list); + igraph_vector_int_destroy(&mlist); + + { + igraph_vector_init_real(&rho, 3, 0.6, 0.4, 0.0); + igraph_vector_list_push_back(&rholist, &rho); + + igraph_real_t elems[] = {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + matrix_init_real_row_major(&pref_matrix, 3, 3, elems); + igraph_matrix_list_push_back(&pref_matrix_list, &pref_matrix); + + igraph_vector_int_init_int(&mlist, 1, 10); + printf("One block, two clusters, 6 and 4 vertices in cluster, complete bipartite.\n"); + call_and_print(10, &mlist, &rholist, &pref_matrix_list, 0); + + igraph_vector_list_clear(&rholist); + igraph_matrix_list_clear(&pref_matrix_list); + igraph_vector_int_destroy(&mlist); + } + + { + igraph_vector_init_real(&rho, 3, 0.6, 0.4, 0.0); + igraph_vector_list_push_back(&rholist, &rho); + igraph_vector_init_real(&rho, 3, 0.6, 0.4, 0.0); + igraph_vector_list_push_back(&rholist, &rho); + + igraph_real_t elems[] = {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + matrix_init_real_row_major(&pref_matrix, 3, 3, elems); + igraph_matrix_list_push_back(&pref_matrix_list, &pref_matrix); + matrix_init_real_row_major(&pref_matrix, 3, 3, elems); + igraph_matrix_list_push_back(&pref_matrix_list, &pref_matrix); + + igraph_vector_int_init_int(&mlist, 2, 5, 5); + printf("Two blocks, two clusters each, 3 and 2 vertices in cluster, each vertex connected to every other, except those in the same cluster.\n"); + call_and_print(10, &mlist, &rholist, &pref_matrix_list, 1); + + igraph_vector_list_destroy(&rholist); + igraph_matrix_list_destroy(&pref_matrix_list); + igraph_vector_int_destroy(&mlist); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_hsbm_list_game.out b/tests/unit/igraph_hsbm_list_game.out new file mode 100644 index 0000000..c326939 --- /dev/null +++ b/tests/unit/igraph_hsbm_list_game.out @@ -0,0 +1,79 @@ +One block, one vertex. +directed: false +vcount: 1 +edges: { +} + +One block, two clusters, 6 and 4 vertices in cluster, complete bipartite. +directed: false +vcount: 10 +edges: { +0 6 +0 7 +0 8 +0 9 +1 6 +1 7 +1 8 +1 9 +2 6 +2 7 +2 8 +2 9 +3 6 +3 7 +3 8 +3 9 +4 6 +4 7 +4 8 +4 9 +5 6 +5 7 +5 8 +5 9 +} + +Two blocks, two clusters each, 3 and 2 vertices in cluster, each vertex connected to every other, except those in the same cluster. +directed: false +vcount: 10 +edges: { +0 3 +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +1 3 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +2 3 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +3 5 +3 6 +3 7 +3 8 +3 9 +4 5 +4 6 +4 7 +4 8 +4 9 +5 8 +5 9 +6 8 +6 9 +7 8 +7 9 +} + diff --git a/tests/unit/igraph_i_layout_sphere.c b/tests/unit/igraph_i_layout_sphere.c new file mode 100644 index 0000000..818958b --- /dev/null +++ b/tests/unit/igraph_i_layout_sphere.c @@ -0,0 +1,82 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "layout/layout_internal.h" + +#include "test_utilities.h" + +int main(void) { + igraph_int_t i; + igraph_matrix_t m; + igraph_real_t x, y, z, r; + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + /* 2D */ + igraph_matrix_init(&m, 1000, 2); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + MATRIX(m, i, 0) = RNG_UNIF01(); + MATRIX(m, i, 1) = RNG_UNIF01(); + } + igraph_i_layout_sphere_2d(&m, &x, &y, &r); + + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + igraph_real_t dist = hypot(MATRIX(m, i, 0) - x, MATRIX(m, i, 1) - y); + if (dist > r) { + printf("x: %f y: %f r: %f\n", x, y, r); + printf("x: %f y: %f dist: %f (%" IGRAPH_PRId ")\n", + MATRIX(m, i, 0), MATRIX(m, i, 1), dist, i); + return 1; + } + } + igraph_matrix_destroy(&m); + + /* 3D */ + igraph_matrix_init(&m, 1000, 3); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + MATRIX(m, i, 0) = RNG_UNIF01(); + MATRIX(m, i, 1) = RNG_UNIF01(); + MATRIX(m, i, 2) = RNG_UNIF01(); + } + igraph_i_layout_sphere_3d(&m, &x, &y, &z, &r); + + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + igraph_real_t dist = sqrt((MATRIX(m, i, 0) - x) * (MATRIX(m, i, 0) - x) + + (MATRIX(m, i, 1) - y) * (MATRIX(m, i, 1) - y) + + (MATRIX(m, i, 2) - z) * (MATRIX(m, i, 2) - z)); + if (dist > r) { + printf("x: %f y: %f z: %f r: %f\n", x, y, z, r); + printf("x: %f y: %f z: %f dist: %f (%" IGRAPH_PRId ")\n", + MATRIX(m, i, 0), MATRIX(m, i, 1), MATRIX(m, i, 2), dist, i); + return 1; + } + } + igraph_matrix_destroy(&m); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_i_umap_fit_ab.c b/tests/unit/igraph_i_umap_fit_ab.c new file mode 100644 index 0000000..7196563 --- /dev/null +++ b/tests/unit/igraph_i_umap_fit_ab.c @@ -0,0 +1,48 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "layout/layout_internal.h" + +#include "test_utilities.h" + +int main(void) { + size_t i; + igraph_real_t a, b; + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + igraph_real_t min_dists[8] = {0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0}; + + /* test with various typical min_dist values. Originally there is a scaling sigma + * factor, but it's 1.0 in all default cases so we fix it for now */ + for (i = 0; i < sizeof(min_dists) / sizeof(min_dists[0]); i++) { + igraph_i_umap_fit_ab(min_dists[i], &a, &b); + printf("%g, %.1g, %.1g\n", min_dists[i], a, b); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_i_umap_fit_ab.out b/tests/unit/igraph_i_umap_fit_ab.out new file mode 100644 index 0000000..b8066eb --- /dev/null +++ b/tests/unit/igraph_i_umap_fit_ab.out @@ -0,0 +1,8 @@ +0, 2, 0.8 +0.001, 2, 0.8 +0.003, 2, 0.8 +0.01, 2, 0.8 +0.03, 2, 0.8 +0.1, 2, 0.9 +0.3, 1, 1 +1, 0.1, 2 diff --git a/tests/unit/igraph_incident.c b/tests/unit/igraph_incident.c new file mode 100644 index 0000000..a54db0c --- /dev/null +++ b/tests/unit/igraph_incident.c @@ -0,0 +1,108 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_int_t pnode, igraph_neimode_t mode, igraph_loops_t loops) { + igraph_vector_int_t eids; + igraph_vector_int_init(&eids, 0); + IGRAPH_ASSERT(igraph_incident(graph, &eids, pnode, mode, loops) == IGRAPH_SUCCESS); + print_vector_int(&eids); + igraph_vector_int_destroy(&eids); +} + + +int main(void) { + igraph_t g_1, g_lm, g_lmu, g_s1, g_s2; + + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_s1, 2, 1, 0,1, 0,1, 1,0, 1,0, -1); + igraph_small(&g_s2, 2, 1, 0,1, 1,0, 1,0, -1); + + igraph_vector_int_t eids; + igraph_vector_int_init(&eids, 0); + + printf("One vertex:\n"); + call_and_print(&g_1, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + + printf("Vertex with multiple edges, IGRAPH_IN:\n"); + call_and_print(&g_lm, 0, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + + printf("Vertex with multiple edges, IGRAPH_OUT:\n"); + call_and_print(&g_lm, 0, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + printf("Vertex with multiple edges, IGRAPH_ALL:\n"); + call_and_print(&g_lm, 0, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("Vertex with multiple edges, undirected:\n"); + call_and_print(&g_lmu, 0, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_NO_LOOPS); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_NO_LOOPS); + + printf("Vertex 1 with loop, undirected, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_NO_LOOPS); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + + printf("Vertex 1 with loop, undirected, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("Vertex 1 with loop, undirected, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_LOOPS_TWICE); + + printf("Graph with 2 edges from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL:\n"); + call_and_print(&g_s1, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + + printf("Graph with 1 edge from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL:\n"); + call_and_print(&g_s2, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + + VERIFY_FINALLY_STACK(); + + printf("Vertex not in graph:\n"); + CHECK_ERROR(igraph_neighbors(&g_lm, &eids, 100, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE), IGRAPH_EINVVID); + + printf("Non-existent mode:\n"); + CHECK_ERROR(igraph_neighbors(&g_lm, &eids, 0, (igraph_neimode_t) 100, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE), IGRAPH_EINVMODE); + + igraph_destroy(&g_1); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + igraph_destroy(&g_s1); + igraph_destroy(&g_s2); + igraph_vector_int_destroy(&eids); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_incident.out b/tests/unit/igraph_incident.out new file mode 100644 index 0000000..d8701b0 --- /dev/null +++ b/tests/unit/igraph_incident.out @@ -0,0 +1,34 @@ +One vertex: +( ) +Vertex with multiple edges, IGRAPH_IN: +( 5 4 ) +Vertex with multiple edges, IGRAPH_OUT: +( 0 1 ) +Vertex with multiple edges, IGRAPH_ALL: +( 0 1 5 4 ) +Vertex with multiple edges, undirected: +( 0 5 4 1 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_NO_LOOPS: +( 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_NO_LOOPS: +( 0 3 ) +Vertex 1 with loop, undirected, IGRAPH_NO_LOOPS: +( 0 3 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_ONCE: +( 2 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_ONCE: +( 0 2 3 ) +Vertex 1 with loop, undirected, IGRAPH_LOOPS_ONCE: +( 0 2 3 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_TWICE: +( 2 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_TWICE: +( 0 2 2 3 ) +Vertex 1 with loop, undirected, IGRAPH_LOOPS_TWICE: +( 0 2 2 3 ) +Graph with 2 edges from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL: +( 1 3 0 2 ) +Graph with 1 edge from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL: +( 0 2 1 ) +Vertex not in graph: +Non-existent mode: diff --git a/tests/unit/igraph_induced_subgraph.c b/tests/unit/igraph_induced_subgraph.c new file mode 100644 index 0000000..22b9d35 --- /dev/null +++ b/tests/unit/igraph_induced_subgraph.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2020 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g, sub; + igraph_vector_int_t keep; + + /* test with a simple directed graph, copy-and-delete implementation */ + igraph_small(&g, 9, IGRAPH_DIRECTED, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 4, 4, 2, 1, 5, 5, 2, 1, 6, 6, 2, 1, 7, 7, 2, 1, 8, 8, 2, + -1); + igraph_vector_int_init_int_end(&keep, -1, 0, 1, 2, 4, -1); + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_COPY_AND_DELETE); + print_graph(&sub); + igraph_vector_int_destroy(&keep); + igraph_destroy(&sub); + igraph_destroy(&g); + + printf("==============\n"); + + /* test with a simple directed graph, create-from-scratch implementation */ + igraph_small(&g, 9, IGRAPH_DIRECTED, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 4, 4, 2, 1, 5, 5, 2, 1, 6, 6, 2, 1, 7, 7, 2, 1, 8, 8, 2, + -1); + igraph_vector_int_init_int_end(&keep, -1, 0, 1, 2, 4, -1); + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH); + print_graph(&sub); + igraph_vector_int_destroy(&keep); + igraph_destroy(&sub); + igraph_destroy(&g); + + printf("==============\n"); + + /* test with a graph that has loop edges, copy-and-delete implementation */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 1, -1); + igraph_vector_int_init_int_end(&keep, -1, 0, 1, -1); + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_COPY_AND_DELETE); + print_graph(&sub); + igraph_vector_int_destroy(&keep); + igraph_destroy(&sub); + igraph_destroy(&g); + + printf("==============\n"); + + /* test with a graph that has loop edges, create-from-scratch implementation */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 1, -1); + igraph_vector_int_init_int_end(&keep, -1, 0, 1, -1); + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH); + print_graph(&sub); + igraph_vector_int_destroy(&keep); + igraph_destroy(&sub); + igraph_destroy(&g); + + printf("==============\n"); + + /* regression test for issue #2398 on GitHub */ + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, -1); + igraph_vector_int_init_int_end(&keep, -1, 0, 1, 0, 1, 0, 1, -1); + + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH); + print_graph(&sub); + igraph_destroy(&sub); + + igraph_induced_subgraph(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_COPY_AND_DELETE); + print_graph(&sub); + igraph_destroy(&sub); + + igraph_vector_int_destroy(&keep); + igraph_destroy(&g); + + printf("==============\n"); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_induced_subgraph.out b/tests/unit/igraph_induced_subgraph.out new file mode 100644 index 0000000..ff57267 --- /dev/null +++ b/tests/unit/igraph_induced_subgraph.out @@ -0,0 +1,43 @@ +directed: true +vcount: 4 +edges: { +0 1 +0 2 +1 3 +3 2 +} +============== +directed: true +vcount: 4 +edges: { +0 1 +0 2 +1 3 +3 2 +} +============== +directed: false +vcount: 2 +edges: { +1 0 +1 1 +} +============== +directed: false +vcount: 2 +edges: { +1 0 +1 1 +} +============== +directed: false +vcount: 2 +edges: { +1 0 +} +directed: false +vcount: 2 +edges: { +1 0 +} +============== \ No newline at end of file diff --git a/tests/unit/igraph_induced_subgraph_edges.c b/tests/unit/igraph_induced_subgraph_edges.c new file mode 100644 index 0000000..19feed4 --- /dev/null +++ b/tests/unit/igraph_induced_subgraph_edges.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t edges; + + igraph_vector_int_init(&edges, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + // 0 1 2 3 4 5 6 7 8 9 10 + 4,4, 0,3, 0,3, 2,3, 1,4, 2,4, 3,4, 2,3, 4,4, 2,3, 1,1, + -1); + + igraph_induced_subgraph_edges(&graph, igraph_vss_range(2,5), &edges); + igraph_vector_int_sort(&edges); /* canonicalize */ + print_vector_int(&edges); + + igraph_induced_subgraph_edges(&graph, igraph_vss_1(0), &edges); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + + igraph_induced_subgraph_edges(&graph, igraph_vss_1(1), &edges); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + + igraph_induced_subgraph_edges(&graph, igraph_vss_all(), &edges); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edges); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_induced_subgraph_edges.out b/tests/unit/igraph_induced_subgraph_edges.out new file mode 100644 index 0000000..4856679 --- /dev/null +++ b/tests/unit/igraph_induced_subgraph_edges.out @@ -0,0 +1,4 @@ +( 0 3 5 6 7 8 9 ) +( ) +( 10 ) +( 0 1 2 3 4 5 6 7 8 9 10 ) diff --git a/tests/unit/igraph_induced_subgraph_map.c b/tests/unit/igraph_induced_subgraph_map.c new file mode 100644 index 0000000..564ca5a --- /dev/null +++ b/tests/unit/igraph_induced_subgraph_map.c @@ -0,0 +1,65 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g, sub; + igraph_vector_int_t map, invmap; + igraph_vector_int_t keep; + igraph_int_t i; + + igraph_small(&g, 9, IGRAPH_DIRECTED, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 4, 4, 2, 1, 5, 5, 2, 1, 6, 6, 2, 1, 7, 7, 2, 1, 8, 8, 2, + -1); + igraph_vector_int_init(&map, 0); + igraph_vector_int_init(&invmap, 0); + igraph_vector_int_init(&keep, igraph_vcount(&g)); + for (i = 0; i < igraph_vector_int_size(&keep); i++) { + VECTOR(keep)[i] = i; + } + + igraph_induced_subgraph_map(&g, &sub, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_COPY_AND_DELETE, + &map, &invmap); + + printf("Map: "); + igraph_vector_int_print(&map); + printf("Inverse map: "); + igraph_vector_int_print(&invmap); + print_graph(&sub); + + igraph_vector_int_destroy(&keep); + igraph_vector_int_destroy(&map); + igraph_vector_int_destroy(&invmap); + + igraph_destroy(&sub); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_induced_subgraph_map.out b/tests/unit/igraph_induced_subgraph_map.out new file mode 100644 index 0000000..f6a6df3 --- /dev/null +++ b/tests/unit/igraph_induced_subgraph_map.out @@ -0,0 +1,20 @@ +Map: 0 1 2 3 4 5 6 7 8 +Inverse map: 0 1 2 3 4 5 6 7 8 +directed: true +vcount: 9 +edges: { +0 1 +0 2 +1 3 +2 3 +1 4 +4 2 +1 5 +5 2 +1 6 +6 2 +1 7 +7 2 +1 8 +8 2 +} diff --git a/tests/unit/igraph_intersection.c b/tests/unit/igraph_intersection.c new file mode 100644 index 0000000..abb60ee --- /dev/null +++ b/tests/unit/igraph_intersection.c @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t star, ring, uni, result; + igraph_vector_ptr_t glist; + + igraph_star(&star, 11, IGRAPH_STAR_UNDIRECTED, /*center=*/ 10); + igraph_ring(&ring, 10, IGRAPH_UNDIRECTED, /*mutual=*/ false, /*circular=*/ true); + + igraph_union(&uni, &star, &ring, /*edge_map1=*/ NULL, /*edge_map2=*/ NULL); + + igraph_intersection(&result, &uni, &star, /*edge_map1*/ NULL, /*edge_map2=*/ NULL); + print_graph_canon(&result); + + igraph_destroy(&result); + + /* ---------------------------- */ + + igraph_vector_ptr_init(&glist, 2); + VECTOR(glist)[0] = &uni; + VECTOR(glist)[1] = ☆ + + igraph_intersection_many(&result, &glist, /*edgemaps=*/ NULL); + printf("--\n"); + print_graph_canon(&result); + + igraph_vector_ptr_destroy(&glist); + igraph_destroy(&result); + igraph_destroy(&uni); + igraph_destroy(&ring); + igraph_destroy(&star); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_intersection.out b/tests/unit/igraph_intersection.out new file mode 100644 index 0000000..5946252 --- /dev/null +++ b/tests/unit/igraph_intersection.out @@ -0,0 +1,29 @@ +directed: false +vcount: 11 +edges: { +0 10 +1 10 +2 10 +3 10 +4 10 +5 10 +6 10 +7 10 +8 10 +9 10 +} +-- +directed: false +vcount: 11 +edges: { +0 10 +1 10 +2 10 +3 10 +4 10 +5 10 +6 10 +7 10 +8 10 +9 10 +} diff --git a/tests/unit/igraph_is_acyclic.c b/tests/unit/igraph_is_acyclic.c new file mode 100644 index 0000000..51baec1 --- /dev/null +++ b/tests/unit/igraph_is_acyclic.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_bool_t acyclic; + + /* Null graph directed */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Null graph undirected */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Singleton graph directed */ + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Singleton graph undirected */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Directed cyclic */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, + 0,1, 2,0, 1,3, 3,2, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(!acyclic); + igraph_destroy(&graph); + + /* Directed acyclic */ + igraph_small(&graph, 3, IGRAPH_DIRECTED, + 0,1, 2,0, 1,3, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Undirected cyclic */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, + 0,1, 2,0, 1,3, 3,2, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(! acyclic); + igraph_destroy(&graph); + + /* Undirected acyclic */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, + 0,1, 2,0, 1,3, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + /* Self loop directed */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, + 0,1, 1,3, 3,2, 2,2, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(! acyclic); + igraph_destroy(&graph); + + /* Self loop undirected */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, + 0,1, 2,0, 1,3, 3,3, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(! acyclic); + igraph_destroy(&graph); + + /* Directed acyclic graph which would be cyclic if undirected */ + igraph_small(&graph, 3, IGRAPH_DIRECTED, + 0,1, 1,2, 0,2, + -1); + igraph_is_acyclic(&graph, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; + +} diff --git a/tests/unit/igraph_is_biconnected.c b/tests/unit/igraph_is_biconnected.c new file mode 100644 index 0000000..fb8aa69 --- /dev/null +++ b/tests/unit/igraph_is_biconnected.c @@ -0,0 +1,135 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t result; + + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + igraph_small(&g, 2, IGRAPH_UNDIRECTED, + 0,1, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(result); + igraph_destroy(&g); + + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 3,0, + 2,4, 4,5, 5,2, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + igraph_ring(&g, 10, IGRAPH_UNDIRECTED, 0, 1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(result); + igraph_destroy(&g); + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 1,3, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 1,3, 3,4, 4,2, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(result); + igraph_destroy(&g); + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 1,3, 3,4, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + /* Two disjoint cycles */ + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 3,4, 4,5, 5,3, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + /* Cycle + isolated vertex */ + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + /* Special case: the root is an articulation point */ + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 0,3, 3,4, 4,0, + -1); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(!result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + /* Cache concistency checks */ + + /* K_2 is a special graph: it is biconnected yet it has no cycles. + * Make sure we don't accidentally set or interpret the cache + * incorrectly. */ + + igraph_bool_t acyclic; + + igraph_full(&g, 2, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(result); + igraph_is_acyclic(&g, &acyclic); + IGRAPH_ASSERT(acyclic); + + igraph_invalidate_cache(&g); + + igraph_is_acyclic(&g, &acyclic); + IGRAPH_ASSERT(acyclic); + igraph_is_biconnected(&g, &result); + IGRAPH_ASSERT(result); + + igraph_destroy(&g); + + + return 0; +} diff --git a/tests/unit/igraph_is_bigraphical.c b/tests/unit/igraph_is_bigraphical.c new file mode 100644 index 0000000..95ee5a5 --- /dev/null +++ b/tests/unit/igraph_is_bigraphical.c @@ -0,0 +1,54 @@ + +#include + +#include "test_utilities.h" + +#define BIGRAPHICAL_PRINT_DESTROY(deg1, deg2) \ + igraph_is_bigraphical(&(deg1), &(deg2), IGRAPH_SIMPLE_SW, &simple); \ + igraph_is_bigraphical(&(deg1), &(deg2), IGRAPH_MULTI_SW, &multi); \ + print_vector_int(&(deg1)); \ + print_vector_int(&(deg2)); \ + printf("simple: %s, multi: %s\n\n", simple ? "true" : "false", multi ? "true" : "false"); \ + igraph_vector_int_destroy(&(deg1)); \ + igraph_vector_int_destroy(&(deg2)); + +int main(void) { + igraph_vector_int_t deg1, deg2; + igraph_bool_t simple, multi; + + igraph_vector_int_init(°1, 0); + igraph_vector_int_init(°2, 0); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 3, 3, -1); + igraph_vector_int_init_int_end(°2, -1, 1, 2, 3, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 3, 2, 1, -1); + igraph_vector_int_init_int_end(°2, -1, 1, 2, 3, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 1, 1, 1, 1, -1); + igraph_vector_int_init_int_end(°2, -1, 2, 3, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 1, 1, 1, 1, -1); + igraph_vector_int_init_int_end(°2, -1, 2, 2, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 1, 2, 0, 3, 0, -1); + igraph_vector_int_init_int_end(°2, -1, 2, 3, 1, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, 5, 2, -1); + igraph_vector_int_init_int_end(°2, -1, 1, 2, 2, 2, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + igraph_vector_int_init_int_end(°1, -1, -2, 2, 6, -1); + igraph_vector_int_init_int_end(°2, -1, 0, 2, 2, 2, -1); + BIGRAPHICAL_PRINT_DESTROY(deg1, deg2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_bigraphical.out b/tests/unit/igraph_is_bigraphical.out new file mode 100644 index 0000000..1533667 --- /dev/null +++ b/tests/unit/igraph_is_bigraphical.out @@ -0,0 +1,32 @@ +( ) +( ) +simple: true, multi: true + +( 3 3 ) +( 1 2 3 ) +simple: false, multi: true + +( 3 2 1 ) +( 1 2 3 ) +simple: true, multi: true + +( 1 1 1 1 ) +( 2 3 ) +simple: false, multi: false + +( 1 1 1 1 ) +( 2 2 ) +simple: true, multi: true + +( 1 2 0 3 0 ) +( 2 3 1 ) +simple: true, multi: true + +( 5 2 ) +( 1 2 2 2 ) +simple: false, multi: true + +( -2 2 6 ) +( 0 2 2 2 ) +simple: false, multi: false + diff --git a/tests/unit/igraph_is_bipartite.c b/tests/unit/igraph_is_bipartite.c new file mode 100644 index 0000000..f608098 --- /dev/null +++ b/tests/unit/igraph_is_bipartite.c @@ -0,0 +1,79 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_bool_t bipartite, acyclic, has_loop; + igraph_vector_bool_t types; + + igraph_vector_bool_init(&types, 5); + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_is_bipartite(&graph, &bipartite, &types); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_vector_bool_size(&types) == igraph_vcount(&graph)); + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_is_bipartite(&graph, &bipartite, &types); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_vector_bool_size(&types) == igraph_vcount(&graph)); + igraph_destroy(&graph); + + /* Singleton with self-loop */ + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, + 0, 0, + -1); + igraph_is_bipartite(&graph, &bipartite, &types); + IGRAPH_ASSERT(! bipartite); + IGRAPH_ASSERT(igraph_vector_bool_size(&types) == igraph_vcount(&graph)); + + /* Test cache usage */ + igraph_has_loop(&graph, &has_loop); + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(! bipartite); + + igraph_destroy(&graph); + + /* Directed path */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 0,1, 1,2, + -1); + + igraph_is_bipartite(&graph, &bipartite, &types); + IGRAPH_ASSERT(bipartite); + IGRAPH_ASSERT(igraph_vector_bool_size(&types) == igraph_vcount(&graph)); + + /* Test cache usage */ + bipartite = false; + igraph_is_forest(&graph, &acyclic, NULL, IGRAPH_ALL); + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(bipartite); + + /* Odd directed cycle */ + igraph_add_edge(&graph, 2, 0); + igraph_invalidate_cache(&graph); + + igraph_is_bipartite(&graph, &bipartite, &types); + IGRAPH_ASSERT(! bipartite); + IGRAPH_ASSERT(igraph_vector_bool_size(&types) == igraph_vcount(&graph)); + + /* Test cache usage */ + bipartite = true; + igraph_is_forest(&graph, &acyclic, NULL, IGRAPH_ALL); + igraph_is_bipartite(&graph, &bipartite, NULL); + IGRAPH_ASSERT(! bipartite); + + igraph_destroy(&graph); + + igraph_vector_bool_destroy(&types); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_chordal.c b/tests/unit/igraph_is_chordal.c new file mode 100644 index 0000000..0e6a628 --- /dev/null +++ b/tests/unit/igraph_is_chordal.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_vector_int_t *alpha, igraph_vector_int_t *alpham1, + igraph_bool_t fill, igraph_bool_t ng) { + igraph_bool_t chordal; + igraph_vector_int_t fill_in; + igraph_t newgraph; + igraph_vector_int_init(&fill_in, 0); + IGRAPH_ASSERT(igraph_is_chordal(graph, alpha, alpham1, &chordal, + fill ? &fill_in : NULL, ng? &newgraph : NULL) == IGRAPH_SUCCESS); + printf("Is chordal: %d\nFill in:\n", chordal); + print_vector_int(&fill_in); + if (ng) { + printf("New graph:\n"); + print_graph_canon(&newgraph); + igraph_destroy(&newgraph); + } + printf("\n"); + igraph_vector_int_destroy(&fill_in); +} + + +int main(void) { + igraph_t g_0, g_1, g_lmu; + igraph_bool_t chordal; + igraph_vector_int_t alpha, alpham1; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + call_and_print(&g_0, NULL, NULL, 1, 1); + + printf("One vertex:\n"); + call_and_print(&g_1, NULL, NULL, 1, 1); + + printf("One vertex, don't calculate anything.\n\n"); + IGRAPH_ASSERT(igraph_is_chordal(&g_1, NULL, NULL, NULL, NULL, NULL) == IGRAPH_SUCCESS); + + printf("Disconnected graph with loops and multiple edges:\n"); + call_and_print(&g_lmu, NULL, NULL, 1, 1); + + printf("Same graph, don't ask for fill_in vector:\n"); + call_and_print(&g_lmu, NULL, NULL, 0, 1); + + printf("Same graph, don't ask for fill_in vector or newgraph:\n"); + call_and_print(&g_lmu, NULL, NULL, 0, 0); + + printf("Same graph, own calculation of alpha and its inverse:\n"); + igraph_vector_int_init(&alpha, 0); + igraph_vector_int_init(&alpham1, 0); + igraph_maximum_cardinality_search(&g_lmu, &alpha, &alpham1); + call_and_print(&g_lmu, &alpha, &alpham1, 1, 1); + + printf("Same graph, own calculation of alpha:\n"); + call_and_print(&g_lmu, &alpha, NULL, 1, 1); + + printf("Same graph, own calculation of inverse alpha:\n"); + call_and_print(&g_lmu, NULL, &alpham1, 1, 1); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Wrong size alpha.\n"); + igraph_vector_int_clear(&alpha); + IGRAPH_ASSERT(igraph_is_chordal(&g_lmu, &alpha, NULL, &chordal, NULL, NULL) == IGRAPH_EINVAL); + + printf("Wrong size alpham1.\n"); + IGRAPH_ASSERT(igraph_is_chordal(&g_lmu, NULL, &alpha, &chordal, NULL, NULL) == IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_lmu); + igraph_vector_int_destroy(&alpha); + igraph_vector_int_destroy(&alpham1); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_is_chordal.out b/tests/unit/igraph_is_chordal.out new file mode 100644 index 0000000..c4e3370 --- /dev/null +++ b/tests/unit/igraph_is_chordal.out @@ -0,0 +1,129 @@ +No vertices: +Is chordal: 1 +Fill in: +( ) +New graph: +directed: false +vcount: 0 +edges: { +} + +One vertex: +Is chordal: 1 +Fill in: +( ) +New graph: +directed: false +vcount: 1 +edges: { +} + +One vertex, don't calculate anything. + +Disconnected graph with loops and multiple edges: +Is chordal: 0 +Fill in: +( 3 0 ) +New graph: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 2 +0 2 +0 3 +1 1 +1 3 +2 3 +3 4 +3 4 +} + +Same graph, don't ask for fill_in vector: +Is chordal: 0 +Fill in: +( ) +New graph: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 2 +0 2 +0 3 +1 1 +1 3 +2 3 +3 4 +3 4 +} + +Same graph, don't ask for fill_in vector or newgraph: +Is chordal: 0 +Fill in: +( ) + +Same graph, own calculation of alpha and its inverse: +Is chordal: 0 +Fill in: +( 3 0 ) +New graph: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 2 +0 2 +0 3 +1 1 +1 3 +2 3 +3 4 +3 4 +} + +Same graph, own calculation of alpha: +Is chordal: 0 +Fill in: +( 3 0 ) +New graph: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 2 +0 2 +0 3 +1 1 +1 3 +2 3 +3 4 +3 4 +} + +Same graph, own calculation of inverse alpha: +Is chordal: 0 +Fill in: +( 3 0 ) +New graph: +directed: false +vcount: 6 +edges: { +0 1 +0 2 +0 2 +0 2 +0 3 +1 1 +1 3 +2 3 +3 4 +3 4 +} + +Wrong size alpha. +Wrong size alpham1. diff --git a/tests/unit/igraph_is_clique.c b/tests/unit/igraph_is_clique.c new file mode 100644 index 0000000..8244758 --- /dev/null +++ b/tests/unit/igraph_is_clique.c @@ -0,0 +1,145 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t is_clique, is_indep_set; + igraph_vector_int_t vids; + + igraph_small(&g, 10, IGRAPH_DIRECTED, + 0, 2, 0, 3, 0, 4, 0, 6, 0, 7, 0, 8, 0, 9, 1, 2, 1, 4, 2, 1, 2, 3, 2, \ + 7, 2, 8, 2, 9, 3, 0, 3, 1, 3, 2, 3, 4, 3, 5, 3, 6, 3, 7, 3, 9, 4, 0, \ + 4, 2, 4, 3, 4, 5, 4, 6, 5, 1, 5, 2, 5, 4, 5, 7, 5, 9, 6, 0, 6, 1, 6, \ + 4, 6, 8, 7, 2, 7, 3, 7, 6, 7, 9, 8, 2, 8, 4, 8, 6, 8, 7, 9, 0, 9, 2, \ + 9, 3, 9, 4, 9, 7, 9, 8, + -1); + + /* Empty set */ + igraph_vector_int_init(&vids, 0); + is_clique = false; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(is_clique); + igraph_vector_int_destroy(&vids); + + /* Singleton set */ + is_clique = false; + igraph_is_clique(&g, igraph_vss_1(0), true, &is_clique); + IGRAPH_ASSERT(is_clique); + + igraph_vector_int_init_int_end(&vids, -1, + 1, 2, -1); + is_clique = false; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(is_clique); + igraph_vector_int_destroy(&vids); + + /* It is a clique: Duplicates handled? */ + igraph_vector_int_init_int_end(&vids, -1, + 1, 2, 2, 2, 1, -1); + is_clique = false; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(is_clique); + igraph_vector_int_destroy(&vids); + + igraph_vector_int_init_int_end(&vids, -1, + 9, 2, 7, 3, -1); + is_clique = false; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(is_clique); + igraph_vector_int_destroy(&vids); + + /* Not a clique, in either a directed or undirected sense. */ + igraph_vector_int_init_int_end(&vids, -1, + 3, 8, 5, -1); + is_clique = true; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(! is_clique); + is_clique = true; + igraph_is_clique(&g, igraph_vss_vector(&vids), false, &is_clique); + IGRAPH_ASSERT(! is_clique); + igraph_vector_int_destroy(&vids); + + /* Not a clique: Duplicates handled? */ + igraph_vector_int_init_int_end(&vids, -1, + 5, 3, 4, 3, 4, 5, 5, 5, -1); + is_clique = true; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(! is_clique); + igraph_vector_int_destroy(&vids); + + /* This is only an undirected clique, but not a directed one. */ + igraph_vector_int_init_int_end(&vids, -1, + 4, 0, 8, 9, 2, -1); + is_clique = true; + igraph_is_clique(&g, igraph_vss_vector(&vids), true, &is_clique); + IGRAPH_ASSERT(! is_clique); + igraph_is_clique(&g, igraph_vss_vector(&vids), false, &is_clique); + IGRAPH_ASSERT(is_clique); + igraph_vector_int_destroy(&vids); + + /* Complete vertex set */ + + is_clique = true; + igraph_is_clique(&g, igraph_vss_all(), true, &is_clique); + IGRAPH_ASSERT(! is_clique); + + is_clique = true; + igraph_is_clique(&g, igraph_vss_all(), false, &is_clique); + IGRAPH_ASSERT(! is_clique); + + /* Independent sets */ + + /* Empty set */ + igraph_vector_int_init(&vids, 0); + is_indep_set = false; + igraph_is_independent_vertex_set(&g, igraph_vss_vector(&vids), &is_indep_set); + IGRAPH_ASSERT(is_indep_set); + igraph_vector_int_destroy(&vids); + + /* Singleton set */ + is_indep_set = false; + igraph_is_independent_vertex_set(&g, igraph_vss_1(5), &is_indep_set); + IGRAPH_ASSERT(is_indep_set); + + igraph_vector_int_init_int_end(&vids, -1, + 6, 9, -1); + is_indep_set = false; + igraph_is_independent_vertex_set(&g, igraph_vss_vector(&vids), &is_indep_set); + IGRAPH_ASSERT(is_indep_set); + igraph_vector_int_destroy(&vids); + + igraph_vector_int_init_int_end(&vids, -1, + 5, 6, 9, -1); + is_indep_set = true; + igraph_is_independent_vertex_set(&g, igraph_vss_vector(&vids), &is_indep_set); + IGRAPH_ASSERT(! is_indep_set); + igraph_vector_int_destroy(&vids); + + is_indep_set = true; + igraph_is_independent_vertex_set(&g, igraph_vss_all(), &is_indep_set); + IGRAPH_ASSERT(! is_indep_set); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_complete.c b/tests/unit/igraph_is_complete.c new file mode 100644 index 0000000..c9a225e --- /dev/null +++ b/tests/unit/igraph_is_complete.c @@ -0,0 +1,207 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Check that the null graph is complete */ +static igraph_error_t check_null(void) +{ + igraph_t null; + igraph_bool_t complete; + + igraph_full(&null, 0, false, false); + IGRAPH_ASSERT(!igraph_vcount(&null)); + IGRAPH_ASSERT(!igraph_ecount(&null)); + igraph_is_complete(&null, &complete); + IGRAPH_ASSERT(complete); + + igraph_destroy(&null); + + return IGRAPH_SUCCESS; +} + +/* Check that the singleton graph is complete */ +static igraph_error_t check_singleton(void) +{ + igraph_t singleton; + igraph_bool_t complete; + + igraph_full(&singleton, 1, false, false); + IGRAPH_ASSERT(igraph_vcount(&singleton) == 1); + IGRAPH_ASSERT(!igraph_ecount(&singleton)); + igraph_is_complete(&singleton, &complete); + IGRAPH_ASSERT(complete); + + igraph_destroy(&singleton); + + return IGRAPH_SUCCESS; +} + +/* Run checks on other non-trivial graphs */ +/* - Generating a full graph without any loops, adding the mutuals if needed */ +/* - Checking that it is complete */ +/* - Removing an edge */ +/* - Checking that it is not complete anymore */ +/* - Then doing the same for multiple graphs */ +static igraph_error_t check(const igraph_bool_t directed, const igraph_int_t vertices) +{ + igraph_t simple, multiple; + igraph_bool_t complete = false; + igraph_int_t eid = -1; + igraph_int_t size = -1; + igraph_es_t es; + igraph_vector_bool_t mutuals; + igraph_vector_int_t toadd; + igraph_int_t i; + + /* generic, simple graph */ + igraph_full(&simple, vertices, directed, false); + + /* igraph_full does not produce complete directed graphs, merely + * **tournaments**. So the mutuals must be added manually in order to get a + * complete graph. */ + + /* So we must find all edges for which the mutual is missing, and add it + ourselves */ + + if (directed) { + + igraph_vector_bool_init(&mutuals, vertices * vertices); + igraph_vector_bool_clear(&mutuals); + + igraph_is_mutual(&simple, &mutuals, igraph_ess_all(IGRAPH_EDGEORDER_ID), + false); + + igraph_vector_int_init(&toadd, vertices * vertices); + igraph_vector_int_clear(&toadd); + + for (i = 0 ; i < igraph_vector_bool_size(&mutuals); ++i) { + /* mutual is missing, adding it */ + if (VECTOR(mutuals)[i] == false) { + igraph_vector_int_push_back(&toadd, IGRAPH_TO(&simple, i)); + igraph_vector_int_push_back(&toadd, IGRAPH_FROM(&simple, i)); + } + } + + igraph_add_edges(&simple, &toadd, NULL); + } + igraph_is_complete(&simple, &complete); + IGRAPH_ASSERT(complete); + + /* Remove a specific edge */ + igraph_delete_edges(&simple, igraph_ess_1(42)); + igraph_is_complete(&simple, &complete); + IGRAPH_ASSERT(!complete); + + /* generic, multiple graph */ + igraph_full(&multiple, vertices, directed, false); + + /* igraph_full does not produce complete directed graphs, merely + * **tournaments**. So the mutuals must be added manually in order to get a + * complete graph. */ + + /* So we must find all edges for which the mutual is missing, and add it + ourselves */ + if (directed) { + + igraph_vector_bool_clear(&mutuals); + + igraph_is_mutual(&multiple, &mutuals, + igraph_ess_all(IGRAPH_EDGEORDER_ID), false); + + igraph_vector_int_clear(&toadd); + + for (i = 0 ; i < igraph_vector_bool_size(&mutuals); ++i) { + /* mutual is missing, adding it */ + if (VECTOR(mutuals)[i] == false) { + igraph_vector_int_push_back(&toadd, IGRAPH_TO(&multiple, i)); + igraph_vector_int_push_back(&toadd, IGRAPH_FROM(&multiple, i)); + } + } + + igraph_add_edges(&multiple, &toadd, NULL); + } + + /* Add two loops */ + igraph_add_edge(&multiple, 41, 41); + igraph_add_edge(&multiple, 42, 42); + + igraph_is_complete(&multiple, &complete); + IGRAPH_ASSERT(complete); + + igraph_invalidate_cache(&multiple); + + /* Add two multiple edges */ + igraph_add_edge(&multiple, IGRAPH_FROM(&multiple, 42), + IGRAPH_TO(&multiple, 42)); + igraph_add_edge(&multiple, IGRAPH_FROM(&multiple, 42), + IGRAPH_TO(&multiple, 42)); + + igraph_is_complete(&multiple, &complete); + IGRAPH_ASSERT(complete); + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Here, we cannot just simply remove any edge anymore */ + /* We must make sure that it is not a loop or an edge that has parallel + edges */ + do { + eid = igraph_rng_get_integer(igraph_rng_default(), 0, + igraph_ecount(&multiple) - 1); + igraph_es_pairs_small(&es, false, IGRAPH_FROM(&multiple, eid), + IGRAPH_TO(&multiple, eid), -1); + + igraph_es_size(&multiple, &es, &size); + + } while (size != 1 || + IGRAPH_FROM(&multiple, eid) == IGRAPH_TO(&multiple, eid)); + + /* Remove the edge we found. There is one necessarily, provided vertices is + high enough */ + /* We only added two loops, and two parallels */ + igraph_delete_edges(&multiple, es); + + igraph_is_complete(&multiple, &complete); + IGRAPH_ASSERT(!complete); + + igraph_destroy(&simple); + igraph_destroy(&multiple); + igraph_es_destroy(&es); + + if (directed) { + igraph_vector_bool_destroy(&mutuals); + igraph_vector_int_destroy(&toadd); + } + + return IGRAPH_SUCCESS; +} + +int main(void) +{ + check_null(); + check_singleton(); + check(false, 50); + check(false, 51); + check(true, 50); + check(true, 51); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_connected.c b/tests/unit/igraph_is_connected.c new file mode 100644 index 0000000..dd6e2cf --- /dev/null +++ b/tests/unit/igraph_is_connected.c @@ -0,0 +1,102 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_bool_t conn; + + /* Null graph */ + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(! conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(! conn); + + igraph_destroy(&graph); + + /* Singleton graph */ + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(conn); + + igraph_destroy(&graph); + + /* Two isolated vertices, one with a self-loop */ + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,0, -1); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(! conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(! conn); + + igraph_destroy(&graph); + + /* Two isolated vertices, three self-loops */ + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,0, 0,0, 1,1, -1); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(! conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(! conn); + + igraph_destroy(&graph); + + /* Weakly connected directed */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, + 0,1, 2,0, 1,2, 3,2, + -1); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(! conn); + + igraph_destroy(&graph); + + /* Directed cycle */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, + 0,1, 2,0, 1,3, 3,2, + -1); + + igraph_is_connected(&graph, &conn, IGRAPH_WEAK); + IGRAPH_ASSERT(conn); + + igraph_is_connected(&graph, &conn, IGRAPH_STRONG); + IGRAPH_ASSERT(conn); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_dag.c b/tests/unit/igraph_is_dag.c new file mode 100644 index 0000000..ffdcac7 --- /dev/null +++ b/tests/unit/igraph_is_dag.c @@ -0,0 +1,126 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_bool_t is_dag; + + // directed empty graphs + for (igraph_int_t n=0; n < 3; n++ ){ + igraph_empty(&graph, n, IGRAPH_DIRECTED); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(is_dag); + igraph_destroy(&graph); + } + + // undirected graphs + for (igraph_int_t n=0; n < 3; n++ ){ + igraph_empty(&graph, n, IGRAPH_UNDIRECTED); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + } + + // 1-path + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(is_dag); + igraph_destroy(&graph); + + // reciprocal edges -- not a DAG + igraph_small(&graph, 2, IGRAPH_DIRECTED, + 0,1, 1,0, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(! is_dag); + igraph_destroy(&graph); + + // 4-cycle -- not a DAG + igraph_small(&graph, 4, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + // 4-cycle with outgoing edge -- not a DAG + igraph_small(&graph, 5, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 0,4, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + // 4-cycle with incoming edge -- not a DAG + igraph_small(&graph, 5, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,0, 4,0, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + // X shape, DAG + igraph_small(&graph, 5, IGRAPH_DIRECTED, + 1,0, 2,0, 0,3, 0,4, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(is_dag); + igraph_destroy(&graph); + + // self-loop present, not a DAG + igraph_small(&graph, 5, IGRAPH_DIRECTED, + 1,0, 2,0, 0,3, 0,4, 0,0, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + // singleton with self-loop + igraph_small(&graph, 1, IGRAPH_DIRECTED, + 0,0, + -1); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + // out-tree + igraph_kary_tree(&graph, 6, 2, IGRAPH_TREE_OUT); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(is_dag); + igraph_destroy(&graph); + + // in-tree + igraph_kary_tree(&graph, 6, 2, IGRAPH_TREE_IN); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(is_dag); + igraph_destroy(&graph); + + // undirected -- not a DAG + igraph_kary_tree(&graph, 6, 2, IGRAPH_TREE_UNDIRECTED); + igraph_is_dag(&graph, &is_dag); + IGRAPH_ASSERT(!is_dag); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_is_eulerian.c b/tests/unit/igraph_is_eulerian.c new file mode 100644 index 0000000..0d23b7f --- /dev/null +++ b/tests/unit/igraph_is_eulerian.c @@ -0,0 +1,207 @@ + +#include +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t graph; + igraph_bool_t has_path, has_cycle; + + /* undirected cases */ + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,2 , 2,3, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,2 , 2,3, 3,1, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 0,1,0,1,-1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,0, 0,0,0,0,-1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 3,4, 4,5, 5,2, + 2,6, 6,4, 4,8, 2,8, 2,7, 0,7, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, 2,3, 2,4 , 3,5 , 4,5, + 4,6, 0,6, 6,7, 1,7, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1 , 1,2, 2,3, 3,4 , 2,4 , 1,5, + 0,5 , -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,4, 3,4, 1,3, 2,5, 4,5, 2,6, 1,6, 0,4, 6,5, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,3 , 3,5 , 5,6 , 6,3 , 3,1 , 1,2 , + 2,2 , 2,4 , 2,4 , 4,3 , 3,2 , 4,6 , -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 7,8, 8,9, 9,7, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 2,3, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,1, 2,3, 3,1, 4,5, 5,6, 6,4, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* two disconnected self loops */ + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,1, 2,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* one self loop and one disconnected multiedge selfloop */ + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 1,1, 1,1, 2,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* multiple self-loop singletons */ + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, 0,0 , 1,1 , 1,1 , -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* no edges, multiple vertices */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* no edges except one self loop, multiple vertices */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0,0, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* directed cases*/ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1 , 1,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 1,2, 2,0, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1 , 1,2, 1,3, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1 , 1,3, 3,2, 2,0 , 2,1, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,3, 3,4, 4,0, 0,2, 2,1, 1,0, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,6, 6,4, 4,5, 5,0, 0,1, 1,2, + 2,3, 3,4, 4,2, 2,0, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* multiedges */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 0,1, 1,2, 2,1, 1,3, 3,4, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* one self loop */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 0,0, 1,2, 2,1, 1,3, 3,4, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* multiedges and one self loop */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,2 , 2,4 , 4,5 , 5,2 , 2,0 , 0,1 , + 1,1 , 1,3 , 1,3 , 3,2 , 2,1 , 3,5 , -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 1,3 , 3,5 , 5,6 , 6,3 , 3,1 , 1,2 , + 2,2 , 2,4 , 2,4 , 4,3 , 3,2 , 4,6 , -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, 0,1, 0,1 , 0,1, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* disconnected graphs and self loops, both undirected and directed */ + /* disconnected with singleton vertices */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 8,9, 9,10, 10,8, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* two disconnected self loops, directed */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 1,1, 2,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* one self loop and one disconnected multiedge selfloop, directed */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, 1,1, 1,1, 2,2, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* no edges, multiple vertices, directed */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + /* no edges except one self loop, multiple vertices, directed */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, 0,0, -1); + igraph_is_eulerian(&graph, &has_path, &has_cycle); + printf("%d %d\n", has_path, has_cycle); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_eulerian.out b/tests/unit/igraph_is_eulerian.out new file mode 100644 index 0000000..86bd6b7 --- /dev/null +++ b/tests/unit/igraph_is_eulerian.out @@ -0,0 +1,34 @@ +1 0 +1 0 +1 1 +1 0 +1 1 +1 1 +0 0 +1 0 +1 0 +1 0 +1 1 +0 0 +0 0 +0 0 +0 0 +0 0 +1 1 +1 1 +1 0 +1 1 +0 0 +1 0 +1 1 +1 1 +0 0 +1 0 +1 0 +1 0 +0 0 +1 1 +0 0 +0 0 +1 1 +1 1 diff --git a/tests/unit/igraph_is_forest.c b/tests/unit/igraph_is_forest.c new file mode 100644 index 0000000..32aa3f9 --- /dev/null +++ b/tests/unit/igraph_is_forest.c @@ -0,0 +1,145 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_output(const igraph_t *graph, igraph_bool_t *res, igraph_neimode_t mode){ + igraph_bool_t result; + igraph_vector_int_t roots; + + igraph_vector_int_init(&roots, 0); + igraph_is_forest(graph, &result, &roots, mode); + if (result) { + printf("Root nodes: "); + igraph_vector_int_print(&roots); + } + else { + printf("Not a forest.\n"); + } + IGRAPH_ASSERT(*res == result); + igraph_vector_int_destroy(&roots); + printf("\n"); +} + +int main(void) { + igraph_t graph; + igraph_bool_t res; + igraph_neimode_t mode; + + printf("Empty Graph\n"); + mode=IGRAPH_ALL; + res=1; + igraph_empty(&graph, 0, 0); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Graph with 0 edges\n"); + mode=IGRAPH_ALL; + res=1; + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Graph with 1 vertex\n"); + mode=IGRAPH_ALL; + res=1; + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Undirected Graph\n"); + mode=IGRAPH_ALL; + res=1; + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 3, 4, 3, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Directed Graph out trees\n"); + mode=IGRAPH_OUT; + res=1; + igraph_small(&graph, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 3, 4, 3, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Directed Graph in trees\n"); + mode=IGRAPH_IN; + res=0; + igraph_small(&graph, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 3, 4, 3, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Undirected Graph with cycle\n"); + mode=IGRAPH_ALL; + res=0; + igraph_small(&graph, 7, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 3, 4, 3, 5, 4, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Undirected Graph with ecount>vcount-1\n"); + mode=IGRAPH_ALL; + res=0; + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 3, 5, 4, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Undirected Graph with self-loop\n"); + mode=IGRAPH_ALL; + res=0; + igraph_small(&graph, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 4, 3, 3, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Directed Multigraph\n"); + mode=IGRAPH_OUT; + res=0; + igraph_small(&graph, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 3, 4, 3, 5, 3, 5, -1); + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + printf("Disjoint union of a cycle with two trees, directed\n"); + mode=IGRAPH_OUT; + res=0; + igraph_small(&graph, 10, IGRAPH_DIRECTED, + 0, 1, 1, 2, 1, 3, 4, 5, 5, 6, 6, 7, 7, 4, 8, 9, + -1); + check_output(&graph, &res, mode); + + printf("Disjoint union of a cycle with two trees, undirected\n"); + mode=IGRAPH_ALL; + res=0; + check_output(&graph, &res, mode); + igraph_destroy(&graph); + + /* Cache testing */ + /* 1 <- 0 -> 2 <- 3 */ + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 0,1, 0,2, 3,2, -1); + /* This must not cache that the graph is not a forest, + * as we are only checking the directed case: */ + igraph_is_forest(&graph, &res, NULL, IGRAPH_OUT); + IGRAPH_ASSERT(!res); + igraph_is_forest(&graph, &res, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(res); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_forest.out b/tests/unit/igraph_is_forest.out new file mode 100644 index 0000000..b2a4132 --- /dev/null +++ b/tests/unit/igraph_is_forest.out @@ -0,0 +1,36 @@ +Empty Graph +Root nodes: + +Graph with 0 edges +Root nodes: 0 1 2 3 4 + +Graph with 1 vertex +Root nodes: 0 + +Undirected Graph +Root nodes: 0 3 + +Directed Graph out trees +Root nodes: 0 3 + +Directed Graph in trees +Not a forest. + +Undirected Graph with cycle +Not a forest. + +Undirected Graph with ecount>vcount-1 +Not a forest. + +Undirected Graph with self-loop +Not a forest. + +Directed Multigraph +Not a forest. + +Disjoint union of a cycle with two trees, directed +Not a forest. + +Disjoint union of a cycle with two trees, undirected +Not a forest. + diff --git a/tests/unit/igraph_is_forest2.c b/tests/unit/igraph_is_forest2.c new file mode 100644 index 0000000..46e0cd9 --- /dev/null +++ b/tests/unit/igraph_is_forest2.c @@ -0,0 +1,105 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Direct test for undirected forests: + * Decompose into connected components and check that each one is a tree */ +igraph_bool_t is_forest(const igraph_t *graph) { + igraph_bool_t res = 1; + igraph_graph_list_t components; + + igraph_graph_list_init(&components, 0); + + igraph_decompose(graph, &components, IGRAPH_WEAK, -1, 0); + + igraph_int_t n = igraph_graph_list_size(&components); + for (igraph_int_t i=0; i < n; ++i) { + igraph_is_tree(igraph_graph_list_get_ptr(&components, i), &res, NULL, IGRAPH_ALL); + + if (! res) { + break; + } + } + + igraph_graph_list_destroy(&components); + + return res; +} + +/* This test generates 'trials' random graphs, allowing self-loops and + * multi-edges, and exercises the forest detection function on each. */ +int main(void) { + const igraph_int_t n = 100; /* vertex count */ + const igraph_int_t m = n / 2; /* edge count */ + const igraph_int_t trials = 300; + igraph_int_t true_count; + igraph_bool_t res1, res2; + igraph_vector_int_t edges; + + igraph_rng_seed(igraph_rng_default(), 847532); + + igraph_vector_int_init(&edges, 2 * m); + + true_count = 0; + for (igraph_int_t k = 0; k < trials; ++k) { + igraph_t graph; + + for (igraph_int_t i = 0; i < 2 * m; ++i) { + VECTOR(edges)[i] = RNG_INTEGER(0, n - 1); + } + + igraph_create(&graph, &edges, n, IGRAPH_UNDIRECTED); + + igraph_is_forest(&graph, &res1, NULL, IGRAPH_ALL); + res2 = is_forest(&graph); + + if (res1 != res2) { + printf("Invalid result for the following graph.\nExpected result: %s\nActual result: %s\n\n", + res2 ? "true" : "false", + res1 ? "true" : "false"); + print_graph(&graph); + } + + igraph_destroy(&graph); + + if (res1 != res2) { + break; + } + + if (res1) { + true_count += 1; + } + } + + igraph_vector_int_destroy(&edges); + + /* ensure that test fails if we got an unexpected result */ + IGRAPH_ASSERT(res1 == res2); + + /* Check that the test is covering all cases: what fraction of random graphs was a tree? + * With random multigraphs, this should be around 20-30% when the edge count is half + * that of the vertex count/ */ + printf("Fraction of random multigraphs which were a forest: %g\n", ((double) true_count) / trials); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_graphical.c b/tests/unit/igraph_is_graphical.c new file mode 100644 index 0000000..ef1b5e8 --- /dev/null +++ b/tests/unit/igraph_is_graphical.c @@ -0,0 +1,284 @@ + +#include + +#include "test_utilities.h" + +/* Undirected case */ +void graphical_print_destroy(igraph_vector_int_t *ds) { + int err; + igraph_bool_t simple, loops, multi, multiloops; + + print_vector_int(ds); + + err = igraph_is_graphical(ds, NULL, IGRAPH_SIMPLE_SW, &simple); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ds, NULL, IGRAPH_LOOPS_SW, &loops); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ds, NULL, IGRAPH_MULTI_SW, &multi); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ds, NULL, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, &multiloops); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + + printf("simple: %s, loops: %s, multi: %s, multiloops: %s\n\n", + simple ? " true" : "false", + loops ? " true" : "false", + multi ? " true" : "false", + multiloops ? " true" : "false"); + +cleanup: + igraph_vector_int_destroy(ds); +} + + +/* Directed case */ +void digraphical_print_destroy(igraph_vector_int_t *ods, igraph_vector_int_t *ids) { + int err; + igraph_bool_t simple, loops, multi, multiloops; + + print_vector_int(ods); + print_vector_int(ids); + + err = igraph_is_graphical(ods, ids, IGRAPH_SIMPLE_SW, &simple); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ods, ids, IGRAPH_LOOPS_SW, &loops); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ods, ids, IGRAPH_MULTI_SW, &multi); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + err = igraph_is_graphical(ods, ids, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, &multiloops); + if (err != IGRAPH_SUCCESS) { + printf("error!\n\n"); goto cleanup; + } + + printf("simple: %s, loops: %s, multi: %s, multiloops: %s\n\n", + simple ? " true" : "false", + loops ? " true" : "false", + multi ? " true" : "false", + multiloops ? " true" : "false"); + +cleanup: + igraph_vector_int_destroy(ods); + igraph_vector_int_destroy(ids); +} + + +int main(void) { + igraph_vector_int_t ds, ods, ids; + + igraph_set_error_handler(&igraph_error_handler_ignore); + + /* Undirected case: */ + + /* Empty */ + igraph_vector_int_init(&ds, 0); + graphical_print_destroy(&ds); + + /* All zeros */ + igraph_vector_int_init_int_end(&ds, -1, 0, 0, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 3, 3, 3, 3, 3, 3, 3, 3, -1); + graphical_print_destroy(&ds); + + /* Undirected degree sequence with negative degree */ + igraph_vector_int_init_int_end(&ds, -1, 3, -2, 3, 3, 3, 3, 3, 3, -1); + graphical_print_destroy(&ds); + + /* Undirected degree sequence with uneven sum */ + igraph_vector_int_init_int_end(&ds, -1, 3, 3, 3, 3, 3, 3, 3, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 5, 3, 6, 2, 2, 8, 1, 1, 10, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 3, 3, 2, 4, 1, 5, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 7, 4, 7, 7, 8, 9, 9, 4, 6, 5, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 4, 4, 4, 1, 1, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 4, 4, 4, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 4, 4, 4, 4, 1, 1, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 3, 3, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 4, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 2, 3, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 3, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 5, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 1, 4, -1); + graphical_print_destroy(&ds); + + /* Extra cases for undirected simple with single loops */ + igraph_vector_int_init_int_end(&ds, -1, 7, 7, 3, 2, 2, 1, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 7, 7, 3, 3, 2, 2, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 6, 6, 6, 4, 2, 0, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 6, 6, 6, 4, 4, 0, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 6, 6, 6, 4, 3, 1, -1); + graphical_print_destroy(&ds); + + /* The following two sequences are realizable as simple graphs. + * The algorithm that checks this exits the last loop with these + * two sequences. An earlier buggy version of the function failed + * to set the result in this case. */ + igraph_vector_int_init_int_end(&ds, -1, 2, 2, 2, 2, 4, -1); + graphical_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 3, 0, 5, 3, 5, 3, 3, -1); + graphical_print_destroy(&ds); + + + /* Directed case: */ + + /* Empty */ + igraph_vector_int_init(&ods, 0); + igraph_vector_int_init(&ids, 0); + digraphical_print_destroy(&ods, &ids); + + /* All zeros */ + igraph_vector_int_init_int_end(&ods, -1, 0, -1); + igraph_vector_int_init_int_end(&ids, -1, 0, -1); + digraphical_print_destroy(&ods, &ids); + + /* Different length, must throw an error */ + igraph_vector_int_init_int_end(&ods, -1, 1, 1, -1); + igraph_vector_int_init_int_end(&ids, -1, 2, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ods, -1, 0, 2, 3, 0, 4, 3, 1, 3, 4, 2, -1); + igraph_vector_int_init_int_end(&ids, -1, 0, 3, 1, 3, 2, 4, 4, 1, 3, 1, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ods, -1, 0, 2, 3, 0, 4, 3, 1, 3, 4, 2, -1); + igraph_vector_int_init_int_end(&ids, -1, 0, 3, 1, -7, 2, 4, 4, 1, 3, 1, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ods, -1, 0, 2, 3, 0, 4, 3, 1, 3, 4, 2, -1); + igraph_vector_int_init_int_end(&ids, -1, 0, 3, 1, 2, 2, 4, 4, 1, 3, 1, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 3, 3, 3, 3, 3, 3, 3, 3, 3, -1); + igraph_vector_int_init_int_end(&ods, -1, 3, 3, 3, 3, 3, 3, 3, 3, 3, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 1, 3, 2, 1, 3, 4, 3, 3, 1, 3, -1); + igraph_vector_int_init_int_end(&ods, -1, 4, 1, 2, 3, 2, 3, 2, 3, 2, 2, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 7, 4, 6, 4, 7, 8, 8, 8, 7, 4, -1); + igraph_vector_int_init_int_end(&ods, -1, 8, 5, 6, 8, 6, 6, 5, 7, 5, 7, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 3, 3, 1, 0, 2, 3, 0, 7, -1); + igraph_vector_int_init_int_end(&ods, -1, 2, 2, 4, 3, 4, 3, 1, 0, -1); + digraphical_print_destroy(&ods, &ids); + + /* Only one vertex with a non-zero out-degree. Regression test for bug #851 */ + igraph_vector_int_init_int_end(&ids, -1, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 1, -1); + digraphical_print_destroy(&ods, &ids); + + /* Another degree sequence when there is only + * one vertex with a non-zero out-degree. Regression test for bug #851 */ + igraph_vector_int_init_int_end(&ids, -1, 2, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 2, -1); + digraphical_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 2, 2, -1); + igraph_vector_int_init_int_end(&ods, -1, 2, 2, -1); + digraphical_print_destroy(&ods, &ids); + + /* Valid directed graphical degree sequence. Regression test for bug #1092 */ + igraph_vector_int_init_int_end(&ids, -1, 1, 0, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 2, 0, -1); + digraphical_print_destroy(&ods, &ids); + + /* Same as above, ids & ods exchanged. */ + igraph_vector_int_init_int_end(&ids, -1, 1, 0, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 2, 0, -1); + digraphical_print_destroy(&ids, &ods); + + /* single loops: graphical, but multi-eges only: non-graphical */ + igraph_vector_int_init_int_end(&ids, -1, 1, 0, 2, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 1, 2, -1); + digraphical_print_destroy(&ids, &ods); + + /* Degree sequences of simple threshold digraphs: + * These sequences make good test cases as they have a unique realization. + * These sequnces were constructed based on point 3 of Theorem 1 in https://arxiv.org/abs/1212.1149 + * by computing a "closure" of a random digraphs. + */ + igraph_vector_int_init_int_end(&ids, -1, 4, 3, 0, 3, 4, -1); + igraph_vector_int_init_int_end(&ods, -1, 3, 3, 4, 3, 1, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 4, 4, 3, 3, 4, -1); + igraph_vector_int_init_int_end(&ods, -1, 4, 4, 4, 4, 2, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 2, 4, 0, 3, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 3, 2, 3, 1, 1, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 11, 0, 0, 0, 7, 0, 9, 0, 13, 0, 0, 0, 0, 0, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 3, 4, 4, 4, 3, 4, 3, 4, 2, 3, 2, 2, 1, 1, 0, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 8, 4, 0, 0, 5, 0, 8, 9, 7, 0, 0, 11, 14, 13, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 8, 8, 9, 9, 8, 8, 6, 5, 6, 4, 3, 2, 1, 1, 1, -1); + digraphical_print_destroy(&ids, &ods); + + /* Some degree sequences with no simple realizations, + * based on incrementing an in- and out-degree of the above. */ + igraph_vector_int_init_int_end(&ids, -1, 8, 4, 1, 0, 5, 0, 8, 9, 7, 0, 0, 11, 14, 13, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 8, 8, 10, 9, 8, 8, 6, 5, 6, 4, 3, 2, 1, 1, 1, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 9, 4, 0, 0, 5, 0, 8, 9, 7, 0, 0, 11, 14, 13, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 8, 9, 9, 9, 8, 8, 6, 5, 6, 4, 3, 2, 1, 1, 1, -1); + digraphical_print_destroy(&ids, &ods); + + igraph_vector_int_init_int_end(&ids, -1, 8, 4, 0, 0, 5, 0, 8, 9, 7, 0, 0, 11, 14, 14, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 8, 8, 9, 9, 8, 8, 6, 5, 6, 4, 3, 3, 1, 1, 1, -1); + digraphical_print_destroy(&ids, &ods); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_graphical.out b/tests/unit/igraph_is_graphical.out new file mode 100644 index 0000000..a255da2 --- /dev/null +++ b/tests/unit/igraph_is_graphical.out @@ -0,0 +1,168 @@ +( ) +simple: true, loops: true, multi: true, multiloops: true + +( 0 0 ) +simple: true, loops: true, multi: true, multiloops: true + +( 3 3 3 3 3 3 3 3 ) +simple: true, loops: true, multi: true, multiloops: true + +( 3 -2 3 3 3 3 3 3 ) +simple: false, loops: false, multi: false, multiloops: false + +( 3 3 3 3 3 3 3 ) +simple: false, loops: false, multi: false, multiloops: false + +( 4 4 5 3 6 2 2 8 1 1 10 ) +simple: true, loops: true, multi: true, multiloops: true + +( 3 3 2 4 1 5 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 7 4 7 7 8 9 9 4 6 5 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 4 4 4 4 1 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 4 4 4 4 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 4 4 4 4 4 1 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 3 3 ) +simple: false, loops: true, multi: true, multiloops: true + +( 4 4 4 ) +simple: false, loops: true, multi: true, multiloops: true + +( 1 2 2 3 ) +simple: true, loops: true, multi: true, multiloops: true + +( 1 2 3 ) +simple: false, loops: true, multi: true, multiloops: true + +( 1 2 5 ) +simple: false, loops: false, multi: false, multiloops: true + +( 1 1 4 ) +simple: false, loops: true, multi: false, multiloops: true + +( 7 7 3 2 2 1 ) +simple: false, loops: false, multi: true, multiloops: true + +( 7 7 3 3 2 2 ) +simple: false, loops: true, multi: true, multiloops: true + +( 6 6 6 4 2 0 ) +simple: false, loops: false, multi: true, multiloops: true + +( 6 6 6 4 4 0 ) +simple: false, loops: true, multi: true, multiloops: true + +( 6 6 6 4 3 1 ) +simple: false, loops: true, multi: true, multiloops: true + +( 2 2 2 2 4 ) +simple: true, loops: true, multi: true, multiloops: true + +( 3 0 5 3 5 3 3 ) +simple: true, loops: true, multi: true, multiloops: true + +( ) +( ) +simple: true, loops: true, multi: true, multiloops: true + +( 0 ) +( 0 ) +simple: true, loops: true, multi: true, multiloops: true + +( 1 1 ) +( 2 ) +error! + +( 0 2 3 0 4 3 1 3 4 2 ) +( 0 3 1 3 2 4 4 1 3 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 0 2 3 0 4 3 1 3 4 2 ) +( 0 3 1 -7 2 4 4 1 3 1 ) +simple: false, loops: false, multi: false, multiloops: false + +( 0 2 3 0 4 3 1 3 4 2 ) +( 0 3 1 2 2 4 4 1 3 1 ) +simple: false, loops: false, multi: false, multiloops: false + +( 3 3 3 3 3 3 3 3 3 ) +( 3 3 3 3 3 3 3 3 3 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 1 2 3 2 3 2 3 2 2 ) +( 1 3 2 1 3 4 3 3 1 3 ) +simple: true, loops: true, multi: true, multiloops: true + +( 8 5 6 8 6 6 5 7 5 7 ) +( 7 4 6 4 7 8 8 8 7 4 ) +simple: true, loops: true, multi: true, multiloops: true + +( 2 2 4 3 4 3 1 0 ) +( 3 3 1 0 2 3 0 7 ) +simple: true, loops: true, multi: true, multiloops: true + +( 1 ) +( 1 ) +simple: false, loops: true, multi: false, multiloops: true + +( 0 2 ) +( 2 0 ) +simple: false, loops: false, multi: true, multiloops: true + +( 2 2 ) +( 2 2 ) +simple: false, loops: true, multi: true, multiloops: true + +( 0 2 0 ) +( 1 0 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 1 0 1 ) +( 0 2 0 ) +simple: true, loops: true, multi: true, multiloops: true + +( 1 0 2 ) +( 0 1 2 ) +simple: false, loops: true, multi: false, multiloops: true + +( 4 3 0 3 4 ) +( 3 3 4 3 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 4 4 3 3 4 ) +( 4 4 4 4 2 ) +simple: true, loops: true, multi: true, multiloops: true + +( 2 4 0 3 1 ) +( 3 2 3 1 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 11 0 0 0 7 0 9 0 13 0 0 0 0 0 0 ) +( 3 4 4 4 3 4 3 4 2 3 2 2 1 1 0 ) +simple: true, loops: true, multi: true, multiloops: true + +( 8 4 0 0 5 0 8 9 7 0 0 11 14 13 0 ) +( 8 8 9 9 8 8 6 5 6 4 3 2 1 1 1 ) +simple: true, loops: true, multi: true, multiloops: true + +( 8 4 1 0 5 0 8 9 7 0 0 11 14 13 0 ) +( 8 8 10 9 8 8 6 5 6 4 3 2 1 1 1 ) +simple: false, loops: true, multi: true, multiloops: true + +( 9 4 0 0 5 0 8 9 7 0 0 11 14 13 0 ) +( 8 9 9 9 8 8 6 5 6 4 3 2 1 1 1 ) +simple: false, loops: true, multi: true, multiloops: true + +( 8 4 0 0 5 0 8 9 7 0 0 11 14 14 0 ) +( 8 8 9 9 8 8 6 5 6 4 3 3 1 1 1 ) +simple: false, loops: false, multi: true, multiloops: true + diff --git a/tests/unit/igraph_is_mutual.c b/tests/unit/igraph_is_mutual.c new file mode 100644 index 0000000..7275f68 --- /dev/null +++ b/tests/unit/igraph_is_mutual.c @@ -0,0 +1,69 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_es_t es, igraph_bool_t loops) { + igraph_vector_bool_t result; + igraph_vector_bool_init(&result, 0); + igraph_is_mutual(graph, &result, es, loops); + igraph_vector_bool_print(&result); + printf("\n"); + igraph_vector_bool_destroy(&result); +} + + +int main(void) { + igraph_t g_0, g_lm, g_lmu; + igraph_vector_bool_t result; + + igraph_vector_bool_init(&result, 0); + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + call_and_print(&g_0, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_LOOPS); + + printf("Graph with loops and multiple edges, loops considered:\n"); + call_and_print(&g_lm, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_LOOPS); + + printf("Graph with loops and multiple edges, loops not considered:\n"); + call_and_print(&g_lm, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_NO_LOOPS); + + printf("Same graph, selecting edge 4:\n"); + call_and_print(&g_lm, igraph_ess_1(4), IGRAPH_LOOPS); + + printf("Same graph, but undirected:\n"); + call_and_print(&g_lmu, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_LOOPS); + + VERIFY_FINALLY_STACK(); + + printf("Edge out of range.\n"); + CHECK_ERROR(igraph_is_mutual(&g_lm, &result, igraph_ess_1(100), true), IGRAPH_EINVEID); + + igraph_destroy(&g_0); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + igraph_vector_bool_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_is_mutual.out b/tests/unit/igraph_is_mutual.out new file mode 100644 index 0000000..685bb7a --- /dev/null +++ b/tests/unit/igraph_is_mutual.out @@ -0,0 +1,16 @@ +No vertices: + + +Graph with loops and multiple edges, loops considered: +0 1 1 0 1 1 0 0 0 + +Graph with loops and multiple edges, loops not considered: +0 1 0 0 1 1 0 0 0 + +Same graph, selecting edge 4: +1 + +Same graph, but undirected: +1 1 1 1 1 1 1 1 1 + +Edge out of range. diff --git a/tests/unit/igraph_is_same_graph.c b/tests/unit/igraph_is_same_graph.c new file mode 100644 index 0000000..6ca1909 --- /dev/null +++ b/tests/unit/igraph_is_same_graph.c @@ -0,0 +1,84 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g1, g2; + igraph_bool_t res; + + /* undirected graphs */ + igraph_small(&g1, 4, 0, + 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_small(&g2, 4, 0, + 1, 0, 1, 2, 2, 3, 3, 0, -1); + + /* a graph is always same as itself */ + igraph_is_same_graph(&g1, &g1, &res); + IGRAPH_ASSERT(res); + + /* undirected graphs should be the same no matter + * the direction of the edges (one is swapped in g2 */ + igraph_is_same_graph(&g1, &g2, &res); + IGRAPH_ASSERT(res); + + /* end of undirected */ + igraph_destroy(&g1); + igraph_destroy(&g2); + + /* directed graphs */ + igraph_small(&g1, 4, 1, + 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_small(&g2, 4, 1, + 1, 0, 1, 2, 2, 3, 3, 0, -1); + + /* directed graphs should not be the same if an + * edge has the opposite direction */ + igraph_is_same_graph(&g1, &g2, &res); + IGRAPH_ASSERT(!res); + + igraph_destroy(&g2); + + /* change order of edges, they should be reordered by graph->ii */ + igraph_small(&g2, 4, 1, + 1, 2, 0, 1, 2, 3, 3, 0, -1); + igraph_is_same_graph(&g1, &g2, &res); + IGRAPH_ASSERT(res); + + /* end of directed */ + igraph_destroy(&g1); + igraph_destroy(&g2); + + /* undirected vs directed */ + igraph_small(&g1, 4, 0, + 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_small(&g2, 4, 1, + 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_is_same_graph(&g1, &g2, &res); + IGRAPH_ASSERT(!res); + + /* end of undirected vs directed */ + igraph_destroy(&g1); + igraph_destroy(&g2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_separator.c b/tests/unit/igraph_is_separator.c new file mode 100644 index 0000000..7062d32 --- /dev/null +++ b/tests/unit/igraph_is_separator.c @@ -0,0 +1,293 @@ +/* + igraph library. + Copyright (C) 2010-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Brute force test of minimality. */ +igraph_bool_t is_minimal_sep(const igraph_t *graph, const igraph_vector_int_t *S) { + igraph_bool_t res; + igraph_is_separator(graph, igraph_vss_vector(S), &res); + + if (res) { + for (igraph_int_t i=0; i < igraph_vector_int_size(S); i++) { + igraph_bool_t sep; + igraph_vector_int_t S2; + igraph_vector_int_init_copy(&S2, S); + igraph_vector_int_remove_fast(&S2, i); + igraph_is_separator(graph, igraph_vss_vector(&S2), &sep); + igraph_vector_int_destroy(&S2); + if (sep) { + res = false; + break; + } + } + } + + return res; +} + +int main(void) { + igraph_t graph; + igraph_vector_int_t S; + igraph_bool_t is_sep, is_min; + + /* Simple star graph, remove the center */ + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_is_separator(&graph, igraph_vss_1(0), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_1(0), &is_min); + IGRAPH_ASSERT(is_min); + + /* Same graph, but another vertex */ + igraph_is_separator(&graph, igraph_vss_1(6), &is_sep); + IGRAPH_ASSERT(! is_sep); + + /* Same graph, all vertices but the center */ + igraph_is_separator(&graph, igraph_vss_range(1, 10), &is_sep); + IGRAPH_ASSERT(! is_sep); + + /* Same graph, all vertices */ + igraph_is_separator(&graph, igraph_vss_range(0, 10), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_destroy(&graph); + + /* Same graph, no vertices */ + igraph_is_separator(&graph, igraph_vss_range(0, 0), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_destroy(&graph); + + /* Test graph 2 */ + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,0, 0,3, 4,5, 5,6, 1,1, 5,6, + -1); + + igraph_vector_int_init_int_end(&S, -1, + 0, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 5, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 1, 0, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + /* Pass in vertices multiple times */ + igraph_vector_int_init_int_end(&S, -1, + 1, 3, 0, 1, 3, 3, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(!is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 4, 1, 0, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 4, 1, 0, 2, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 4, 1, 0, 5, 2, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 1, 0, 5, 2, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 5, 6, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 5, 0, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_is_separator(&graph, igraph_vss_range(0, 0), &is_sep); + IGRAPH_ASSERT(!is_sep); + + igraph_destroy(&graph); + + /* Test graph 3 */ + + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0,1, 1,3, 0,2, 2,3, 2,4, + -1); + + igraph_vector_int_init_int_end(&S, -1, + 5, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(! is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 1, 2, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + + igraph_add_edge(&graph, 1, 4); + + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + /* Pass in vertices multiple times */ + igraph_vector_int_init_int_end(&S, -1, + 1, 2, 1, 2, 2, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_destroy(&graph); + + /* Test graph 4 */ + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, + 4,5, 4,6, 4,7, 5,6, 5,7, 6,7, + 2,5, 3,4, + -1); + + igraph_vector_int_init_int_end(&S, -1, + 2, 3, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 2, 4, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 3, 5, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 2, 3, 5, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(! is_min); + igraph_vector_int_destroy(&S); + + igraph_destroy(&graph); + + /* Karate club network */ + + igraph_famous(&graph, "Zachary"); + + igraph_vector_int_init_int_end(&S, -1, 32, 33, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(is_min); + igraph_vector_int_destroy(&S); + + igraph_vector_int_init_int_end(&S, -1, + 8, 9, 19, 30, 31, -1); + igraph_is_separator(&graph, igraph_vss_vector(&S), &is_min); + IGRAPH_ASSERT(!is_min); + igraph_vector_int_destroy(&S); + + /* Caution: + * igraph_all_minimal_st_separators() returns minimal (s,t) separators, + * i.e. separators that are minimal with respect to separating a specific + * (s,t) pair, but not necessarily minimal separators. See the documentation + * for an example. + */ + + igraph_vector_int_list_t separators; + igraph_vector_int_list_init(&separators, 0); + igraph_all_minimal_st_separators(&graph, &separators); + for (igraph_int_t i=0; i < igraph_vector_int_list_size(&separators); i++) { + const igraph_vector_int_t *sep = igraph_vector_int_list_get_ptr(&separators, i); + igraph_is_separator(&graph, igraph_vss_vector(sep), &is_sep); + IGRAPH_ASSERT(is_sep); + igraph_is_minimal_separator( &graph, igraph_vss_vector(sep), &is_min); + + igraph_bool_t is_min2 = is_minimal_sep(&graph, sep); + IGRAPH_ASSERT((!is_min) == (! is_min2)); + } + igraph_vector_int_list_destroy(&separators); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_is_tree.c b/tests/unit/igraph_is_tree.c new file mode 100644 index 0000000..56cddbd --- /dev/null +++ b/tests/unit/igraph_is_tree.c @@ -0,0 +1,140 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t res; + igraph_int_t root; + + /* the null graph is not a tree */ + igraph_empty(&g, 0, 0); + + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + /* the single-vertex graph is a tree */ + igraph_empty(&g, 1, 0); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_destroy(&g); + + /* undirected 4-cycle, not a tree */ + igraph_small(&g, 4, 0, + 0, 1, 1, 2, 2, 3, 3, 0, -1); + + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + /* disconnected graph with the same number of edges a tree would have */ + igraph_small(&g, 4, 0, + 0, 1, 1, 2, 0, 2, 3, 4, -1); + + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + /* disjoint union of an out-tree and two cycles, with the same number + * of edges as a tree would have, and the same in-degrees as an out-tree + * would have */ + igraph_small(&g, 11, IGRAPH_DIRECTED, + 10, 0, 0, 2, 0, 6, 9, 1, 1, 8, 8, 4, 4, 9, 3, 7, 7, 5, 5, 3, + -1); + + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + /* 3-star, tree */ + igraph_small(&g, 4, 0, + 0, 1, 0, 2, 0, 3, -1); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_destroy(&g); + + /* out-tree */ + igraph_small(&g, 4, 1, + 0, 1, 1, 2, 1, 3, -1); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_OUT); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_is_tree(&g, &res, &root, IGRAPH_IN); + IGRAPH_ASSERT(! res); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_destroy(&g); + + /* in-tree */ + igraph_small(&g, 4, 1, + 0, 1, 2, 1, 1, 3, -1); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_IN); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 3); + + igraph_is_tree(&g, &res, &root, IGRAPH_OUT); + IGRAPH_ASSERT(! res); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_destroy(&g); + + /* neither an in-tree, nor an out-ree, but still a tree when ignoring edge-directions */ + igraph_small(&g, 6, 1, + 0, 1, 1, 2, 2, 3, 4, 1, 2, 5, -1); + + root = -1; + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(root == 0); + + igraph_is_tree(&g, &res, &root, IGRAPH_IN); + IGRAPH_ASSERT(! res); + + igraph_is_tree(&g, &res, &root, IGRAPH_OUT); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + /* Regression test, see: + * https://github.com/szhorvat/IGraphM/issues/90 + * https://github.com/igraph/igraph/pull/1160 + */ + igraph_small(&g, 5, 0, + 0, 3, 0, 4, 1, 3, 1, 4, -1); + + igraph_is_tree(&g, &res, &root, IGRAPH_ALL); + IGRAPH_ASSERT(! res); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_isomorphic.c b/tests/unit/igraph_isomorphic.c new file mode 100644 index 0000000..a4d1e3b --- /dev/null +++ b/tests/unit/igraph_isomorphic.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g1, igraph_t *g2) { + igraph_bool_t iso; + igraph_isomorphic(g1, g2, &iso); + printf("%d\n", iso); + igraph_destroy(g1); + igraph_destroy(g2); +} + +int main(void) { + igraph_t g1, g2; + igraph_bool_t iso; + printf("Two multigraphs: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,1, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,2, 1,2, -1); + print_and_destroy(&g1, &g2); + + printf("Only second graph is multigraph: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,2, 1,2, -1); + print_and_destroy(&g1, &g2); + + printf("Only first graph is multigraph: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,1, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,2, -1); + print_and_destroy(&g1, &g2); + + printf("Two multigraphs, first has loop: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,0, 0,1, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,2, 1,2, -1); + print_and_destroy(&g1, &g2); + + printf("Two multigraphs, both have loops: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,0, 0,1, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,1, 1,2, 1,2, -1); + print_and_destroy(&g1, &g2); + + printf("Two multigraphs, only loops: "); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,0, 0,0, 1,1, -1); + igraph_small(&g2, 3, IGRAPH_UNDIRECTED, 1,1, 2,2, 2,2, -1); + print_and_destroy(&g1, &g2); + + printf("Check error for two multigraphs, different directedness\n"); + igraph_small(&g1, 3, IGRAPH_UNDIRECTED, 0,1, 0,1, -1); + igraph_small(&g2, 3, IGRAPH_DIRECTED, 1,2, 1,2, -1); + CHECK_ERROR(igraph_isomorphic(&g1, &g2, &iso), IGRAPH_EINVAL); + igraph_destroy(&g1); + igraph_destroy(&g2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_isomorphic.out b/tests/unit/igraph_isomorphic.out new file mode 100644 index 0000000..d666c62 --- /dev/null +++ b/tests/unit/igraph_isomorphic.out @@ -0,0 +1,7 @@ +Two multigraphs: 1 +Only second graph is multigraph: 0 +Only first graph is multigraph: 0 +Two multigraphs, first has loop: 0 +Two multigraphs, both have loops: 1 +Two multigraphs, only loops: 1 +Check error for two multigraphs, different directedness diff --git a/tests/unit/igraph_isomorphic_bliss.c b/tests/unit/igraph_isomorphic_bliss.c new file mode 100644 index 0000000..a3e4132 --- /dev/null +++ b/tests/unit/igraph_isomorphic_bliss.c @@ -0,0 +1,149 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g1, g2; + igraph_t ring1, ring2; + igraph_vector_int_t color1, color2; + igraph_vector_int_t perm; + igraph_bool_t iso; + + igraph_bliss_sh_t sh_values[] = { + IGRAPH_BLISS_F, IGRAPH_BLISS_FL, + IGRAPH_BLISS_FS, IGRAPH_BLISS_FM, + IGRAPH_BLISS_FLM, IGRAPH_BLISS_FSM + }; + + const char *sh_names[] = { + "F", "FL", "FS", "FM", "FLM", "FSM" + }; + + size_t i; + + /* necessary because of igraph_vector_shuffle() below */ + igraph_rng_seed(igraph_rng_default(), 137); + + for (i=0; i < sizeof(sh_values) / sizeof(igraph_bliss_sh_t); ++i) { + + igraph_bliss_sh_t sh = sh_values[i]; + printf("Splitting heuristic: %s\n", sh_names[i]); + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/1); + igraph_vector_int_init_range(&perm, 0, igraph_vcount(&ring1)); + igraph_vector_int_shuffle(&perm); + igraph_permute_vertices(&ring1, &ring2, &perm); + + /* Without colors */ + printf("Without vertex colors: "); + iso = 0; + igraph_isomorphic_bliss(&ring1, &ring2, 0, 0, &iso, 0, 0, sh, 0, 0); + printf("%s\n", iso ? "YES" : "NO"); + + /* Everything has the same colors */ + printf("All vertices having the same color: "); + igraph_vector_int_init(&color1, igraph_vcount(&ring1)); + igraph_vector_int_init(&color2, igraph_vcount(&ring2)); + igraph_vector_int_fill(&color1, 1); + igraph_vector_int_fill(&color2, 1); + + iso = 0; + igraph_isomorphic_bliss(&ring1, &ring2, &color1, &color2, &iso, 0, 0, sh, 0, 0); + printf("%s\n", iso ? "YES" : "NO"); + + /* Try a negative result */ + printf("Non-matching colors 1: "); + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + + iso = 1; + igraph_isomorphic_bliss(&ring1, &ring2, &color1, &color2, &iso, 0, 0, sh, 0, 0); + printf("%s\n", iso ? "YES" : "NO"); + + /* Another negative, same color distribution, different topology */ + printf("Non-matching colors 2: "); + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + VECTOR(color1)[1] = 1; + VECTOR(color2)[0] = 1; + VECTOR(color2)[3] = 1; + + iso = 1; + igraph_isomorphic_bliss(&ring1, &ring2, &color1, &color2, &iso, 0, 0, sh, 0, 0); + printf("%s\n", iso ? "YES" : "NO"); + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_vector_int_destroy(&perm); + igraph_destroy(&ring2); + igraph_destroy(&ring1); + + /* More complicated test with colors */ + printf("Isomorphic colored graphs: "); + + igraph_small(&g1, 8, IGRAPH_DIRECTED, + 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 7, 2, 4, 2, 6, 2, 7, 3, 5, 3, 6, 3, 7, -1 + ); + igraph_small(&g2, 8, IGRAPH_DIRECTED, + 0, 1, 0, 3, 0, 4, 2, 3, 2, 1, 2, 6, 5, 1, 5, 4, 5, 6, 7, 3, 7, 6, 7, 4, -1 + ); + + igraph_vector_int_init(&color1, 8); + igraph_vector_int_init(&color2, 8); + + VECTOR(color1)[1] = 1; + VECTOR(color1)[3] = 1; + VECTOR(color1)[5] = 1; + VECTOR(color1)[7] = 1; + + VECTOR(color2)[2] = 1; + VECTOR(color2)[3] = 1; + VECTOR(color2)[6] = 1; + VECTOR(color2)[7] = 1; + + iso = 0; + igraph_isomorphic_bliss(&g1, &g2, &color1, &color2, &iso, 0, 0, sh, 0, 0); + printf("%s\n", iso ? "YES" : "NO"); + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_destroy(&g2); + igraph_destroy(&g1); + + printf("\n"); + + VERIFY_FINALLY_STACK(); + + } + + return 0; +} diff --git a/tests/unit/igraph_isomorphic_bliss.out b/tests/unit/igraph_isomorphic_bliss.out new file mode 100644 index 0000000..1424d0f --- /dev/null +++ b/tests/unit/igraph_isomorphic_bliss.out @@ -0,0 +1,42 @@ +Splitting heuristic: F +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + +Splitting heuristic: FL +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + +Splitting heuristic: FS +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + +Splitting heuristic: FM +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + +Splitting heuristic: FLM +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + +Splitting heuristic: FSM +Without vertex colors: YES +All vertices having the same color: YES +Non-matching colors 1: NO +Non-matching colors 2: NO +Isomorphic colored graphs: YES + diff --git a/tests/unit/igraph_isomorphic_vf2.c b/tests/unit/igraph_isomorphic_vf2.c new file mode 100644 index 0000000..6ec7936 --- /dev/null +++ b/tests/unit/igraph_isomorphic_vf2.c @@ -0,0 +1,229 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t ring1, ring2; + igraph_vector_int_t color1, color2; + igraph_vector_int_t perm; + igraph_bool_t iso; + igraph_int_t count; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 12345); + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/1); + igraph_vector_int_init_range(&perm, 0, igraph_vcount(&ring1)); + igraph_vector_int_shuffle(&perm); + igraph_permute_vertices(&ring1, &ring2, &perm); + + /* Without colors */ + igraph_isomorphic(&ring1, &ring2, &iso); + if (!iso) { + fprintf(stderr, "Without color failed.\n"); + return 1; + } + + /* Without colors, number of isomorphisms */ + igraph_count_isomorphisms_vf2(&ring1, &ring2, 0, 0, 0, 0, &count, 0, 0, 0); + if (count != 200) { + fprintf(stderr, "Count without colors failed, expected 200, got %" IGRAPH_PRId ".\n", count); + return 2; + } + + /* Separate colors for each vertex */ + igraph_vector_int_init(&color1, igraph_vcount(&ring1)); + igraph_vector_int_init(&color2, igraph_vcount(&ring2)); + for (i = 0; i < igraph_vector_int_size(&color1); i++) { + VECTOR(color1)[i] = i; + } + for (i = 0; i < igraph_vector_int_size(&color2); i++) { + VECTOR(color2)[i] = VECTOR(color1)[VECTOR(perm)[i]]; + } + igraph_count_isomorphisms_vf2(&ring1, &ring2, &color1, &color2, 0, 0, &count, 0, 0, 0); + if (count != 1) { + fprintf(stderr, "Count with separate colors failed, expected 1, got %" IGRAPH_PRId ".\n", count); + return 5; + } + + /* Try a negative result */ + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + igraph_isomorphic_vf2(&ring1, &ring2, &color1, &color2, 0, 0, &iso, 0, 0, 0, 0, 0); + if (iso) { + fprintf(stderr, "Negative test failed.\n"); + return 6; + } + + /* Another negative, same color distribution, different topology */ + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + VECTOR(color1)[1] = 1; + VECTOR(color2)[0] = 1; + VECTOR(color2)[(VECTOR(perm)[1] + 1) % igraph_vcount(&ring2)] = 1; + igraph_isomorphic_vf2(&ring1, &ring2, &color1, &color2, 0, 0, &iso, 0, 0, 0, 0, 0); + if (iso) { + fprintf(stderr, "Second negative test failed.\n"); + return 7; + } + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_vector_int_destroy(&perm); + igraph_destroy(&ring2); + igraph_destroy(&ring1); + + /* ---------------------------------------------------------------- */ + /* SUBGRAPH ISOMORPHISM */ + /* ---------------------------------------------------------------- */ + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + igraph_ring(&ring2, 80, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + + /* One color */ + igraph_vector_int_init(&color1, igraph_vcount(&ring1)); + igraph_vector_int_init(&color2, igraph_vcount(&ring2)); + igraph_count_subisomorphisms_vf2(&ring1, &ring2, &color1, &color2, 0, 0, + &count, 0, 0, 0); + if (count != 42) { + fprintf(stderr, "Count with one color failed, expected 42, got %" IGRAPH_PRId ".\n", count); + return 31; + } + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + + /* ---------------------------------------------------------------- */ + /* EDGE COLORING, GRAPH ISOMORPHISM */ + /* ---------------------------------------------------------------- */ + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/ 1); + igraph_vector_int_init_range(&perm, 0, igraph_ecount(&ring1)); + igraph_vector_int_shuffle(&perm); + igraph_permute_vertices(&ring1, &ring2, &perm); + igraph_vector_int_destroy(&perm); + + /* Everything has the same color */ + igraph_vector_int_init(&color1, igraph_ecount(&ring1)); + igraph_vector_int_init(&color2, igraph_ecount(&ring2)); + igraph_isomorphic_vf2(&ring1, &ring2, 0, 0, &color1, &color2, &iso, 0, 0, 0, 0, 0); + if (!iso) { + fprintf(stderr, "Single edge-color failed.\n"); + return 41; + } + + /* Two colors, just counting */ + for (i = 0; i < igraph_vector_int_size(&color1); i += 2) { + VECTOR(color1)[i] = VECTOR(color2)[i] = 0; + VECTOR(color1)[i + 1] = VECTOR(color2)[i] = 1; + } + igraph_count_isomorphisms_vf2(&ring1, &ring2, 0, 0, &color1, &color2, &count, 0, 0, 0); + if (count != 100) { + fprintf(stderr, "Count with two edge colors failed, expected 100, got %" IGRAPH_PRId ".\n", count); + return 42; + } + + /* Separate colors for each edge */ + for (i = 0; i < igraph_vector_int_size(&color1); i++) { + VECTOR(color1)[i] = VECTOR(color2)[i] = i; + } + igraph_count_isomorphisms_vf2(&ring1, &ring2, 0, 0, &color1, &color2, &count, 0, 0, 0); + if (count != 1) { + fprintf(stderr, "Count with separate edge colors failed, expected 1, got %" IGRAPH_PRId ".\n", count); + return 43; + } + + /* Try a negative result */ + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + igraph_isomorphic_vf2(&ring1, &ring2, 0, 0, &color1, &color2, &iso, 0, 0, 0, 0, 0); + if (iso) { + fprintf(stderr, "Negative edge test failed.\n"); + return 44; + } + + /* Another negative, same color distribution, different topology */ + igraph_vector_int_fill(&color1, 0); + igraph_vector_int_fill(&color2, 0); + VECTOR(color1)[0] = 1; + VECTOR(color1)[1] = 1; + VECTOR(color2)[0] = 1; + VECTOR(color2)[2] = 1; + igraph_isomorphic_vf2(&ring1, &ring2, 0, 0, &color1, &color2, &iso, 0, 0, 0, 0, 0); + if (iso) { + fprintf(stderr, "Second negative edge test failed.\n"); + return 45; + } + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + + /* ---------------------------------------------------------------- */ + /* EDGE COLORED SUBGRAPH ISOMORPHISM */ + /* ---------------------------------------------------------------- */ + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + igraph_ring(&ring2, 80, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/0); + + /* One color */ + igraph_vector_int_init(&color1, igraph_ecount(&ring1)); + igraph_vector_int_init(&color2, igraph_ecount(&ring2)); + igraph_count_subisomorphisms_vf2(&ring1, &ring2, 0, 0, &color1, &color2, + &count, 0, 0, 0); + if (count != 42) { + fprintf(stderr, "Count with one edge color failed, expected 42, got %" IGRAPH_PRId ".\n", count); + return 51; + } + + /* Two colors */ + for (i = 0; i < igraph_vector_int_size(&color1) - 1; i += 2) { + VECTOR(color1)[i] = 0; + VECTOR(color1)[i + 1] = 1; + } + for (i = 0; i < igraph_vector_int_size(&color2) - 1; i += 2) { + VECTOR(color2)[i] = 0; + VECTOR(color2)[i + 1] = 1; + } + igraph_count_subisomorphisms_vf2(&ring1, &ring2, 0, 0, &color1, &color2, + &count, 0, 0, 0); + if (count != 22) { + fprintf(stderr, "Count with two edge colors failed, expected 22, got %" IGRAPH_PRId ".\n", count); + return 52; + } + + igraph_vector_int_destroy(&color1); + igraph_vector_int_destroy(&color2); + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_join.c b/tests/unit/igraph_join.c new file mode 100644 index 0000000..b74f2e7 --- /dev/null +++ b/tests/unit/igraph_join.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t left, right, joined; + + /* Standard join. */ + printf("Standard join.\n"); + igraph_small(&left, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, -1); + igraph_small(&right, 5, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, 2,4, -1); + + igraph_join(&joined, &left, &right); + print_graph(&joined); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + /* Standard directed join */ + printf("Standard directed join.\n"); + igraph_small(&left, 2, IGRAPH_DIRECTED, 0,1, -1); + igraph_small(&right, 3, IGRAPH_DIRECTED, 0,1, 2,1, -1); + + igraph_join(&joined, &left, &right); + print_graph(&joined); + printf("\n"); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + /* Empty graphs; the result is the null graph. */ + igraph_small(&left, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&right, 0, IGRAPH_UNDIRECTED, -1); + igraph_join(&joined, &left, &right); + IGRAPH_ASSERT(igraph_ecount(&joined) != 0 || igraph_vcount(&joined) != 0); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + /* Empty graph joined with non-empty; the result is the non-empty. */ + igraph_small(&left, 4, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,2, -1); + igraph_small(&right, 0, IGRAPH_UNDIRECTED, -1); + igraph_join(&joined, &left, &right); + IGRAPH_ASSERT(igraph_ecount(&joined) != igraph_ecount(&left) + || igraph_vcount(&joined) != igraph_vcount(&left)); + + igraph_destroy(&left); + igraph_destroy(&right); + igraph_destroy(&joined); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_join.out b/tests/unit/igraph_join.out new file mode 100644 index 0000000..8e83993 --- /dev/null +++ b/tests/unit/igraph_join.out @@ -0,0 +1,54 @@ +Standard join. +directed: false +vcount: 9 +edges: { +0 1 +0 4 +0 5 +0 6 +0 7 +0 8 +1 2 +1 4 +1 5 +1 6 +1 7 +1 8 +2 2 +2 4 +2 5 +2 6 +2 7 +2 8 +3 4 +3 5 +3 6 +3 7 +3 8 +4 5 +5 6 +6 6 +6 8 +} + +Standard directed join. +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +3 0 +3 1 +4 0 +4 1 +4 3 +} + diff --git a/tests/unit/igraph_joint_degree_distribution.c b/tests/unit/igraph_joint_degree_distribution.c new file mode 100644 index 0000000..b536525 --- /dev/null +++ b/tests/unit/igraph_joint_degree_distribution.c @@ -0,0 +1,315 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Check vector equality with tolerances. Consider NaN values equal. */ +igraph_bool_t vector_eq(const igraph_vector_t *a, const igraph_vector_t *b) { + igraph_int_t na = igraph_vector_size(a); + igraph_int_t nb = igraph_vector_size(b); + if (na != nb) { + return false; + } + for (igraph_int_t i=0; i < na; i++) { + if (isnan(VECTOR(*a)[i]) && isnan(VECTOR(*b)[i])) { + continue; + } + if (! igraph_almost_equals(VECTOR(*a)[i], VECTOR(*b)[i], 1e-12)) { + return false; + } + } + return true; +} + +/* Compare to igraph_joint_degree_matrix() */ +void check_jdm(const igraph_t *g, const igraph_vector_t *weights) { + igraph_matrix_t jdm, p; + igraph_int_t vcount = igraph_vcount(g); + igraph_int_t nrow, ncol, n; + + igraph_matrix_init(&jdm, 0, 0); + igraph_matrix_init(&p, 0, 0); + + igraph_joint_degree_matrix(g, weights, &jdm, -1, -1); + igraph_joint_degree_distribution(g, weights, &p, IGRAPH_OUT, IGRAPH_IN, true, /*normalized*/ false, -1, -1); + + nrow = igraph_matrix_nrow(&p); + ncol = igraph_matrix_ncol(&p); + + if (vcount > 0) { + igraph_vector_t v; + + IGRAPH_ASSERT(nrow > 0); + IGRAPH_ASSERT(ncol > 0); + nrow--; + ncol--; + + IGRAPH_ASSERT(igraph_matrix_nrow(&jdm) == nrow); + IGRAPH_ASSERT(igraph_matrix_ncol(&jdm) == ncol); + + igraph_vector_init(&v, 0); + + igraph_matrix_get_col(&p, &v, 0); + IGRAPH_ASSERT(igraph_vector_isnull(&v)); + + igraph_matrix_get_row(&p, &v, 0); + IGRAPH_ASSERT(igraph_vector_isnull(&v)); + + igraph_vector_destroy(&v); + + igraph_matrix_remove_row(&p, 0); + igraph_matrix_remove_col(&p, 0); + } else { + // vcount == 0 + IGRAPH_ASSERT(nrow == 0); + IGRAPH_ASSERT(ncol == 0); + } + + n = nrow < ncol ? nrow : ncol; + + if (! igraph_is_directed(g)) { + for (igraph_int_t i=0; i < n; i++) { + MATRIX(jdm, i, i) *= 2; + } + } + + igraph_real_t total; + if (weights) { + total = igraph_vector_sum(weights); + } else { + total = igraph_ecount(g); + } + if (! igraph_is_directed(g)) { + total *= 2; + } + + IGRAPH_ASSERT(igraph_matrix_sum(&p) == total); + + IGRAPH_ASSERT(igraph_matrix_all_e(&jdm, &p)); + + igraph_matrix_destroy(&p); + igraph_matrix_destroy(&jdm); +} + +void check_assort_i(const igraph_t *g, const igraph_vector_t *weights, + igraph_neimode_t from_mode, igraph_neimode_t to_mode) { + igraph_matrix_t p; + igraph_real_t r1, r2; + igraph_int_t nrow, ncol; + igraph_vector_t dfrom, dto; + igraph_vector_t a, b; + igraph_bool_t directed = igraph_is_directed(g); + + igraph_vector_init(&dfrom, 0); + igraph_vector_init(&dto, 0); + igraph_vector_init(&a, 0); + igraph_vector_init(&b, 0); + + igraph_strength(g, &dfrom, igraph_vss_all(), from_mode, IGRAPH_LOOPS, NULL); + igraph_strength(g, &dto, igraph_vss_all(), to_mode, IGRAPH_LOOPS, NULL); + + igraph_matrix_init(&p, 0, 0); + igraph_joint_degree_distribution(g, weights, &p, from_mode, to_mode, true, /*normalized*/ true, -1, -1); + + nrow = igraph_matrix_nrow(&p); + ncol = igraph_matrix_ncol(&p); + + igraph_assortativity(g, weights, &dfrom, directed ? &dto : NULL, &r1, IGRAPH_DIRECTED, /*normalized*/ false); + + igraph_matrix_rowsum(&p, &a); + igraph_matrix_colsum(&p, &b); + + r2 = 0; + for (igraph_int_t i=0; i < nrow; i++) { + for (igraph_int_t j=0; j < ncol; j++) { + r2 += (MATRIX(p, i, j) - VECTOR(a)[i] * VECTOR(b)[j]) * (igraph_real_t) i * (igraph_real_t) j; + } + } + // printf("Assortativity: %g == %g\n", r1, r2); + IGRAPH_ASSERT(igraph_almost_equals(r1, r2, 1e-13)); + + igraph_matrix_destroy(&p); + + igraph_vector_destroy(&b); + igraph_vector_destroy(&a); + igraph_vector_destroy(&dto); + igraph_vector_destroy(&dfrom); +} + +/* Compare to igraph_assortativity() */ +void check_assort(const igraph_t *g, const igraph_vector_t *weights) { + if (igraph_is_directed(g)) { + check_assort_i(g, weights, IGRAPH_OUT, IGRAPH_IN); + check_assort_i(g, weights, IGRAPH_IN, IGRAPH_OUT); + check_assort_i(g, weights, IGRAPH_OUT, IGRAPH_OUT); + check_assort_i(g, weights, IGRAPH_IN, IGRAPH_IN); + check_assort_i(g, weights, IGRAPH_ALL, IGRAPH_IN); + check_assort_i(g, weights, IGRAPH_ALL, IGRAPH_OUT); + check_assort_i(g, weights, IGRAPH_OUT, IGRAPH_ALL); + check_assort_i(g, weights, IGRAPH_IN, IGRAPH_ALL); + } else { + check_assort_i(g, weights, IGRAPH_ALL, IGRAPH_ALL); + } +} + +void check_knnk_i(const igraph_t *g, const igraph_vector_t *weights, igraph_neimode_t from_mode, igraph_neimode_t to_mode) { + igraph_matrix_t p; + igraph_int_t nrow, ncol; + igraph_vector_t knnk, knnk2; + igraph_vector_t q; + + igraph_vector_init(&knnk, 0); + igraph_vector_init(&q, 0); + + igraph_matrix_init(&p, 0, 0); + igraph_joint_degree_distribution(g, weights, &p, from_mode, to_mode, true, /*normalized*/ true, -1, -1); + + nrow = igraph_matrix_nrow(&p); + ncol = igraph_matrix_ncol(&p); + + igraph_degree_correlation_vector(g, weights, &knnk, from_mode, to_mode, /*directed_neighbors*/ true); + IGRAPH_ASSERT(igraph_vector_size(&knnk) == nrow); + + igraph_vector_init(&knnk2, nrow); + igraph_matrix_rowsum(&p, &q); + for (igraph_int_t k=0; k < nrow; k++) { + for (igraph_int_t j=0; j < ncol; j++) { + VECTOR(knnk2)[k] += j * MATRIX(p, k, j); + } + } + + igraph_vector_div(&knnk2, &q); + + /* + printf("%d - %d\n", from_mode, to_mode); + print_vector(&knnk); + print_vector(&knnk2); + */ + IGRAPH_ASSERT(vector_eq(&knnk, &knnk2)); + + igraph_vector_destroy(&knnk2); + + igraph_matrix_destroy(&p); + + igraph_vector_destroy(&q); + igraph_vector_destroy(&knnk); +} + +/* Compare to igraph_degree_correlation_vector() */ +void check_knnk(const igraph_t *g, const igraph_vector_t *weights) { + if (igraph_is_directed(g)) { + check_knnk_i(g, weights, IGRAPH_OUT, IGRAPH_IN); + check_knnk_i(g, weights, IGRAPH_IN, IGRAPH_OUT); + check_knnk_i(g, weights, IGRAPH_OUT, IGRAPH_OUT); + check_knnk_i(g, weights, IGRAPH_IN, IGRAPH_IN); + check_knnk_i(g, weights, IGRAPH_ALL, IGRAPH_IN); + check_knnk_i(g, weights, IGRAPH_ALL, IGRAPH_OUT); + check_knnk_i(g, weights, IGRAPH_OUT, IGRAPH_ALL); + check_knnk_i(g, weights, IGRAPH_IN, IGRAPH_ALL); + } else { + check_knnk_i(g, weights, IGRAPH_ALL, IGRAPH_ALL); + } +} + +int main(void) { + igraph_t dg, ug; + igraph_vector_t weights; + igraph_matrix_t p; + + igraph_rng_seed(igraph_rng_default(), 137); + + igraph_matrix_init(&p, 0, 0); + + /* Directed */ + + igraph_empty(&dg, 0, IGRAPH_DIRECTED); + check_jdm(&dg, NULL); + igraph_destroy(&dg); + + igraph_empty(&dg, 1, IGRAPH_DIRECTED); + check_jdm(&dg, NULL); + igraph_destroy(&dg); + + igraph_small(&dg, 2, IGRAPH_DIRECTED, 0,0, 0,1, 0,1, 1,0, -1); + check_jdm(&dg, NULL); + check_assort(&dg, NULL); + check_knnk(&dg, NULL); + igraph_destroy(&dg); + + igraph_erdos_renyi_game_gnm(&dg, 10, 30, IGRAPH_DIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + check_jdm(&dg, NULL); + check_assort(&dg, NULL); + check_knnk(&dg, NULL); + + igraph_vector_init_range(&weights, 0, igraph_ecount(&dg)); + check_jdm(&dg, &weights); + check_assort(&dg, &weights); + check_knnk(&dg, &weights); + igraph_vector_destroy(&weights); + + igraph_joint_degree_distribution(&dg, NULL, &p, IGRAPH_ALL, IGRAPH_ALL, false, false, -1, -1); + print_matrix(&p); + + igraph_joint_degree_distribution(&dg, NULL, &p, IGRAPH_ALL, IGRAPH_ALL, false, false, 3, 10); + print_matrix(&p); + + igraph_destroy(&dg); + + /* Undirected */ + + igraph_empty(&ug, 0, IGRAPH_UNDIRECTED); + check_jdm(&ug, NULL); + igraph_destroy(&ug); + + igraph_empty(&ug, 1, IGRAPH_UNDIRECTED); + check_jdm(&ug, NULL); + igraph_destroy(&ug); + + igraph_small(&ug, 2, IGRAPH_UNDIRECTED, 0,1, 0,1, 1,1, -1); + check_jdm(&ug, NULL); + check_assort(&ug, NULL); + check_knnk(&ug, NULL); + igraph_destroy(&ug); + + igraph_erdos_renyi_game_gnm(&ug, 10, 30, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW, IGRAPH_EDGE_UNLABELED); + check_jdm(&ug, NULL); + check_assort(&ug, NULL); + check_knnk(&ug, NULL); + + igraph_vector_init_range(&weights, 0, igraph_ecount(&ug)); + check_jdm(&ug, &weights); + check_assort(&ug, &weights); + check_knnk(&ug, &weights); + igraph_vector_destroy(&weights); + + igraph_joint_degree_distribution(&ug, NULL, &p, IGRAPH_ALL, IGRAPH_ALL, false, false, -1, -1); + print_matrix(&p); + + igraph_joint_degree_distribution(&ug, NULL, &p, IGRAPH_ALL, IGRAPH_ALL, false, false, 3, 10); + print_matrix(&p); + + igraph_destroy(&ug); + + igraph_matrix_destroy(&p); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_joint_degree_distribution.out b/tests/unit/igraph_joint_degree_distribution.out new file mode 100644 index 0000000..e180dff --- /dev/null +++ b/tests/unit/igraph_joint_degree_distribution.out @@ -0,0 +1,26 @@ +[ 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 4 0 2 3 4 3 + 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 2 0 2 2 4 2 + 0 0 0 0 3 0 2 0 1 1 + 0 0 0 0 4 0 4 1 4 3 + 0 0 0 0 3 0 2 1 3 0 ] +[ 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 ] +[ 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 6 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 5 + 0 0 0 0 0 0 0 0 + 0 0 0 6 0 5 0 38 ] +[ 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 3 0 0 3 0 0 0 ] diff --git a/tests/unit/igraph_joint_type_distribution.c b/tests/unit/igraph_joint_type_distribution.c new file mode 100644 index 0000000..bf7fe98 --- /dev/null +++ b/tests/unit/igraph_joint_type_distribution.c @@ -0,0 +1,163 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Compare with igraph_modularity() and igraph_assortativity_nominal() */ +void check_assort(const igraph_t *g, const igraph_vector_t *weights, const igraph_vector_int_t *types) { + igraph_vector_t a, b; + igraph_matrix_t p; + igraph_int_t n; + igraph_real_t c1, c2, q1, q2; + + igraph_vector_init(&a, 0); + igraph_vector_init(&b, 0); + + igraph_matrix_init(&p, 0, 0); + + igraph_joint_type_distribution(g, weights, &p, types, NULL, /*directed*/ true, /*normalized*/ true); + + igraph_matrix_rowsum(&p, &a); + igraph_matrix_colsum(&p, &b); + + n = igraph_matrix_nrow(&p); + IGRAPH_ASSERT(igraph_matrix_ncol(&p) == n); + + c1 = c2 = 0; + for (igraph_int_t i=0; i < n; i++) { + c1 += MATRIX(p, i, i); + c2 += VECTOR(a)[i] * VECTOR(b)[i]; + } + q2 = c1 - c2; + + igraph_modularity(g, types, weights, 1, true, &q1); + // printf("Modularity: %g == %g\n", q1, q2); + IGRAPH_ASSERT(igraph_almost_equals(q1, q2, 1e-14)); + + if (! weights) { + q1 /= 1 - c2; + igraph_assortativity_nominal(g, NULL, types, &q2, /*directed*/ true, /*normalized*/ true); + // printf("Normalized nominal assortativity: %g == %g\n", q1, q2); + IGRAPH_ASSERT(igraph_almost_equals(q1, q2, 1e-14)); + } + + igraph_matrix_destroy(&p); + + igraph_vector_destroy(&b); + igraph_vector_destroy(&a); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_t t1, t2; + igraph_vector_t weights; + igraph_matrix_t p; + + igraph_matrix_init(&p, 0, 0); + + printf("Null graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&t1, 0); + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, false, false); + print_matrix(&p); + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + printf("\nSingleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_vector_int_init_int(&t1, 1, 0); + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, false, false); + print_matrix(&p); + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + printf("\nUndirected singleton with loop\n"); + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, 0,0, -1); + igraph_vector_int_init_int(&t1, 1, 0); + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, false, false); + print_matrix(&p); + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + printf("\nDirected singleton with loop\n"); + igraph_small(&graph, 1, IGRAPH_DIRECTED, 0,0, -1); + igraph_vector_int_init_int(&t1, 1, 0); + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, false, false); + print_matrix(&p); + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + printf("\nSmall undirected graph\n"); + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 3, 0, 0, 3, 0, 2, 3, 1, 5, 5, 4, 2, 1, 1, 1, 1, 0, 1, 5, 1, + -1); + + igraph_vector_int_init_int(&t1, 6, + 0, 0, 1, 1, 2, 2); + + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, false, false); + print_matrix(&p); + + IGRAPH_ASSERT(igraph_matrix_sum(&p) == 2*igraph_ecount(&graph)); + check_assort(&graph, NULL, &t1); + + igraph_vector_init_range(&weights, 10, 10 + igraph_ecount(&graph)); + check_assort(&graph, &weights, &t1); + igraph_vector_destroy(&weights); + + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + printf("\nSmall directed graph\n"); + igraph_small(&graph, 6, IGRAPH_DIRECTED, + 1, 1, 2, 4, 0, 2, 2, 2, 3, 2, 3, 0, 2, 1, 2, 3, 4, 1, 2, 4, + -1); + + igraph_vector_int_init_int(&t1, 6, + 0, 0, 1, 1, 2, 2); + + igraph_joint_type_distribution(&graph, NULL, &p, &t1, NULL, true, false); + + printf("From and to types are the same:\n"); + print_matrix(&p); + IGRAPH_ASSERT(igraph_matrix_sum(&p) == igraph_ecount(&graph)); + + check_assort(&graph, NULL, &t1); + + igraph_vector_init_range(&weights, 10, 10 + igraph_ecount(&graph)); + check_assort(&graph, &weights, &t1); + igraph_vector_destroy(&weights); + + igraph_vector_int_init_int(&t2, 6, + 0, 1, 1, 1, 0, 1); + printf("From and to types are different:\n"); + igraph_joint_type_distribution(&graph, NULL, &p, &t1, &t2, true, false); + print_matrix(&p); + IGRAPH_ASSERT(igraph_matrix_sum(&p) == igraph_ecount(&graph)); + + igraph_vector_int_destroy(&t2); + igraph_vector_int_destroy(&t1); + igraph_destroy(&graph); + + igraph_matrix_destroy(&p); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_joint_type_distribution.out b/tests/unit/igraph_joint_type_distribution.out new file mode 100644 index 0000000..743f562 --- /dev/null +++ b/tests/unit/igraph_joint_type_distribution.out @@ -0,0 +1,26 @@ +Null graph +[ 0-by-0 ] + +Singleton graph +[ 0 ] + +Undirected singleton with loop +[ 2 ] + +Directed singleton with loop +[ 2 ] + +Small undirected graph +[ 6 4 1 + 4 0 1 + 1 1 2 ] + +Small directed graph +From and to types are the same: +[ 1 1 0 + 2 3 2 + 1 0 0 ] +From and to types are different: +[ 0 2 + 3 4 + 0 1 ] diff --git a/tests/unit/igraph_k_regular_game.c b/tests/unit/igraph_k_regular_game.c new file mode 100644 index 0000000..d449b84 --- /dev/null +++ b/tests/unit/igraph_k_regular_game.c @@ -0,0 +1,199 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t deg; + igraph_bool_t is_simple; + + igraph_set_error_handler(&igraph_error_handler_ignore); + + igraph_vector_int_init(°, 0); + + /* k-regular undirected graph, even degrees, no multiple edges */ + igraph_k_regular_game(&g, 10, 4, 0, 0); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + if (!is_simple) { + return 1; + } + if (igraph_is_directed(&g)) { + return 1; + } + igraph_destroy(&g); + + /* k-regular undirected graph, odd degrees, even number of vertices, no multiple edges */ + igraph_k_regular_game(&g, 10, 3, 0, 0); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + if (!is_simple) { + return 2; + } + if (igraph_is_directed(&g)) { + return 2; + } + igraph_destroy(&g); + + /* k-regular undirected graph, odd degrees, odd number of vertices, no multiple edges */ + if (!igraph_k_regular_game(&g, 9, 3, 0, 0)) { + return 3; + } + + /* k-regular undirected graph, even degrees, multiple edges */ + igraph_k_regular_game(&g, 10, 4, 0, 1); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(°); + if (igraph_is_directed(&g)) { + return 14; + } + igraph_destroy(&g); + + /* k-regular undirected graph, odd degrees, even number of vertices, multiple edges */ + igraph_k_regular_game(&g, 10, 3, 0, 1); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + igraph_vector_int_print(°); + if (igraph_is_directed(&g)) { + return 15; + } + igraph_destroy(&g); + + /* k-regular undirected graph, odd degrees, odd number of vertices, multiple edges */ + if (!igraph_k_regular_game(&g, 9, 3, 0, 1)) { + return 4; + } + + /* k-regular directed graph, even degrees, no multiple edges */ + igraph_k_regular_game(&g, 10, 4, 1, 0); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + if (!is_simple) { + return 5; + } + if (!igraph_is_directed(&g)) { + return 5; + } + igraph_destroy(&g); + + /* k-regular directed graph, odd degrees, even number of vertices, no multiple edges */ + igraph_k_regular_game(&g, 10, 3, 1, 0); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + if (!is_simple) { + return 6; + } + if (!igraph_is_directed(&g)) { + return 6; + } + igraph_destroy(&g); + + /* k-regular directed graph, odd degrees, odd number of vertices, no multiple edges */ + igraph_k_regular_game(&g, 9, 3, 1, 0); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + if (!is_simple) { + return 7; + } + if (!igraph_is_directed(&g)) { + return 7; + } + igraph_destroy(&g); + + /* k-regular directed graph, even degrees, multiple edges */ + igraph_k_regular_game(&g, 10, 4, 1, 1); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + if (!igraph_is_directed(&g)) { + return 16; + } + igraph_destroy(&g); + + /* k-regular directed graph, odd degrees, even number of vertices, multiple edges */ + igraph_k_regular_game(&g, 10, 3, 1, 1); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + if (!igraph_is_directed(&g)) { + return 17; + } + igraph_destroy(&g); + + /* k-regular directed graph, odd degrees, odd number of vertices, multiple edges */ + igraph_k_regular_game(&g, 9, 3, 1, 1); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_vector_int_print(°); + igraph_degree(&g, °, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + igraph_vector_int_print(°); + if (!igraph_is_directed(&g)) { + return 18; + } + igraph_destroy(&g); + + /* k-regular undirected graph, too large degree, no multiple edges */ + if (!igraph_k_regular_game(&g, 10, 10, 0, 0)) { + return 8; + } + + /* k-regular directed graph, too large degree, no multiple edges */ + if (!igraph_k_regular_game(&g, 10, 10, 1, 0)) { + return 9; + } + + /* empty graph */ + if (igraph_k_regular_game(&g, 0, 0, 0, 0)) { + return 10; + } + if (igraph_vcount(&g) != 0 || igraph_ecount(&g) != 0 || igraph_is_directed(&g)) { + return 11; + } + igraph_destroy(&g); + if (igraph_k_regular_game(&g, 0, 0, 1, 0)) { + return 12; + } + if (igraph_vcount(&g) != 0 || igraph_ecount(&g) != 0 || !igraph_is_directed(&g)) { + return 13; + } + igraph_destroy(&g); + + igraph_vector_int_destroy(°); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_k_regular_game.out b/tests/unit/igraph_k_regular_game.out new file mode 100644 index 0000000..8b82ce0 --- /dev/null +++ b/tests/unit/igraph_k_regular_game.out @@ -0,0 +1,16 @@ +4 4 4 4 4 4 4 4 4 4 +3 3 3 3 3 3 3 3 3 3 +4 4 4 4 4 4 4 4 4 4 +3 3 3 3 3 3 3 3 3 3 +4 4 4 4 4 4 4 4 4 4 +4 4 4 4 4 4 4 4 4 4 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 +4 4 4 4 4 4 4 4 4 4 +4 4 4 4 4 4 4 4 4 4 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 diff --git a/tests/unit/igraph_k_shortest_paths.out b/tests/unit/igraph_k_shortest_paths.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/igraph_kautz.c b/tests/unit/igraph_kautz.c new file mode 100644 index 0000000..0aa6f4f --- /dev/null +++ b/tests/unit/igraph_kautz.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g, g_test; + igraph_bool_t iso, same; + + /* BA, AB, CB, etc. */ + IGRAPH_ASSERT(igraph_kautz(&g, /* m */ 2, /* n */ 1) == IGRAPH_SUCCESS); + igraph_small(&g_test, 6, IGRAPH_DIRECTED, 0, 1, 0, 5, 1, 0, 1, 4, 2, 0, 2, 4, 3, 1, 3, 5, 4, 2, 4, 3, 5, 3, 5, 2, -1); + IGRAPH_ASSERT(igraph_isomorphic(&g, &g_test, &iso) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(iso); + igraph_destroy(&g); + igraph_destroy(&g_test); + + /* 1 symbol, string length 11, should be empty graph */ + IGRAPH_ASSERT(igraph_kautz(&g, /* m */ 0, /* n */ 10) == IGRAPH_SUCCESS); + igraph_small(&g_test, 0, IGRAPH_DIRECTED, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + + /* 1 symbol, string length 1 should be single vertex */ + IGRAPH_ASSERT(igraph_kautz(&g, /* m */ 0, /* n */ 0) == IGRAPH_SUCCESS); + igraph_small(&g_test, 1, IGRAPH_DIRECTED, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + + /* String length 1 should be full graph */ + IGRAPH_ASSERT(igraph_kautz(&g, /* m */ 5, /* n */ 0) == IGRAPH_SUCCESS); + igraph_full(&g_test, 6, IGRAPH_DIRECTED, /*loops*/ 0); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g); + igraph_destroy(&g_test); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_lapack_dgeev.c b/tests/unit/igraph_lapack_dgeev.c new file mode 100644 index 0000000..b620c40 --- /dev/null +++ b/tests/unit/igraph_lapack_dgeev.c @@ -0,0 +1,224 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include "test_utilities.h" + +#define DIM 10 + +int real_cplx_mult(const igraph_matrix_t *A, + const igraph_vector_t *v_real, + const igraph_vector_t *v_imag, + igraph_vector_t *res_real, + igraph_vector_t *res_imag) { + + igraph_int_t n = igraph_vector_size(v_real); + igraph_int_t r, c; + + if (igraph_matrix_nrow(A) != n || + igraph_matrix_ncol(A) != n || + igraph_vector_size(v_imag) != n) { + printf("Wrong matrix or vector size"); + return 1; + } + + igraph_vector_resize(res_real, n); + igraph_vector_resize(res_imag, n); + + for (r = 0; r < n; r++) { + igraph_real_t s_real = 0.0; + igraph_real_t s_imag = 0.0; + for (c = 0; c < n; c++) { + s_real += MATRIX(*A, r, c) * VECTOR(*v_real)[c]; + s_imag += MATRIX(*A, r, c) * VECTOR(*v_imag)[c]; + } + VECTOR(*res_real)[r] = s_real; + VECTOR(*res_imag)[r] = s_imag; + } + + return 0; +} + +int sc_cplx_cplx_mult(igraph_real_t lambda_real, + igraph_real_t lambda_imag, + const igraph_vector_t *v_real, + const igraph_vector_t *v_imag, + igraph_vector_t *res_real, + igraph_vector_t *res_imag) { + + igraph_int_t r; + igraph_int_t n = igraph_vector_size(v_real); + + if (igraph_vector_size(v_imag) != n) { + printf("Wrong vector sizes"); + return 1; + } + + igraph_vector_resize(res_real, n); + igraph_vector_resize(res_imag, n); + + for (r = 0; r < n; r++) { + VECTOR(*res_real)[r] = (lambda_real * VECTOR(*v_real)[r] - + lambda_imag * VECTOR(*v_imag)[r]); + VECTOR(*res_imag)[r] = (lambda_imag * VECTOR(*v_real)[r] + + lambda_real * VECTOR(*v_imag)[r]); + } + + return 0; +} + +igraph_bool_t check_ev(const igraph_matrix_t *A, + const igraph_vector_t *values_real, + const igraph_vector_t *values_imag, + const igraph_matrix_t *vectors_left, + const igraph_matrix_t *vectors_right, + igraph_real_t tol) { + + int i, n = igraph_matrix_nrow(A); + igraph_vector_t v_real, v_imag; + igraph_vector_t AV_real, AV_imag, lv_real, lv_imag; + igraph_vector_t null; + + if (igraph_matrix_ncol(A) != n) { + return 1; + } + if (igraph_vector_size(values_real) != n) { + return 1; + } + if (igraph_vector_size(values_imag) != n) { + return 1; + } + if (igraph_matrix_nrow(vectors_left) != n) { + return 1; + } + if (igraph_matrix_ncol(vectors_left) != n) { + return 1; + } + if (igraph_matrix_nrow(vectors_right) != n) { + return 1; + } + if (igraph_matrix_ncol(vectors_right) != n) { + return 1; + } + + igraph_vector_init(&AV_real, n); + igraph_vector_init(&AV_imag, n); + igraph_vector_init(&lv_real, n); + igraph_vector_init(&lv_imag, n); + igraph_vector_init(&null, n); + igraph_vector_null(&null); + + for (i = 0; i < n; i++) { + if (VECTOR(*values_imag)[i] == 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + v_imag = igraph_vector_view(VECTOR(null), n); + } else if (VECTOR(*values_imag)[i] > 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + v_imag = igraph_vector_view(&MATRIX(*vectors_right, 0, i + 1), n); + } else if (VECTOR(*values_imag)[i] < 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i - 1), n); + v_imag = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + igraph_vector_scale(&v_imag, -1.0); + } + real_cplx_mult(A, &v_real, &v_imag, &AV_real, &AV_imag); + sc_cplx_cplx_mult(VECTOR(*values_real)[i], VECTOR(*values_imag)[i], + &v_real, &v_imag, &lv_real, &lv_imag); + + if (igraph_vector_maxdifference(&AV_real, &lv_real) > tol || + igraph_vector_maxdifference(&AV_imag, &lv_imag) > tol) { + printf("ERROR:\n"); + igraph_vector_print(&AV_real); + igraph_vector_print(&AV_imag); + igraph_vector_print(&lv_real); + igraph_vector_print(&lv_imag); + return 1; + } + } + + igraph_vector_destroy(&null); + igraph_vector_destroy(&AV_imag); + igraph_vector_destroy(&AV_real); + igraph_vector_destroy(&lv_imag); + igraph_vector_destroy(&lv_real); + + return 0; +} + +int main(void) { + + igraph_matrix_t A; + igraph_matrix_t vectors_left, vectors_right; + igraph_vector_t values_real, values_imag; + int i, j; + int info = 1; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&vectors_left, 0, 0); + igraph_matrix_init(&vectors_right, 0, 0); + igraph_vector_init(&values_real, 0); + igraph_vector_init(&values_imag, 0); + + for (i = 0; i < DIM; i++) { + for (j = 0; j < DIM; j++) { + MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + igraph_lapack_dgeev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, &info); + + if (check_ev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, /*tol=*/ 1e-8)) { + return 1; + } + + igraph_matrix_resize(&A, 10, 10); + igraph_matrix_null(&A); + MATRIX(A, 0, 1) = MATRIX(A, 0, 2) = MATRIX(A, 0, 3) = 1 / 3.0; + MATRIX(A, 1, 0) = MATRIX(A, 1, 4) = MATRIX(A, 1, 5) = MATRIX(A, 1, 6) = 1 / 4.0; + MATRIX(A, 2, 0) = MATRIX(A, 2, 7) = MATRIX(A, 2, 8) = MATRIX(A, 2, 9) = 1 / 4.0; + MATRIX(A, 3, 0) = 1.0; + MATRIX(A, 4, 1) = 1.0; + MATRIX(A, 5, 1) = 1.0; + MATRIX(A, 6, 1) = 1.0; + MATRIX(A, 7, 2) = 1.0; + MATRIX(A, 8, 2) = 1.0; + MATRIX(A, 9, 2) = 1.0; + + info = 0; + igraph_lapack_dgeev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, &info); + + if (check_ev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, /*tol=*/ 1e-8)) { + return 3; + } + + igraph_vector_destroy(&values_imag); + igraph_vector_destroy(&values_real); + igraph_matrix_destroy(&vectors_right); + igraph_matrix_destroy(&vectors_left); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_lapack_dgeevx.c b/tests/unit/igraph_lapack_dgeevx.c new file mode 100644 index 0000000..1370efd --- /dev/null +++ b/tests/unit/igraph_lapack_dgeevx.c @@ -0,0 +1,206 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include "test_utilities.h" + +#define DIM 10 + +int real_cplx_mult(const igraph_matrix_t *A, + const igraph_vector_t *v_real, + const igraph_vector_t *v_imag, + igraph_vector_t *res_real, + igraph_vector_t *res_imag) { + + igraph_int_t n = igraph_vector_size(v_real); + igraph_int_t r, c; + + if (igraph_matrix_nrow(A) != n || + igraph_matrix_ncol(A) != n || + igraph_vector_size(v_imag) != n) { + printf("Wrong matrix or vector size"); + return 1; + } + + igraph_vector_resize(res_real, n); + igraph_vector_resize(res_imag, n); + + for (r = 0; r < n; r++) { + igraph_real_t s_real = 0.0; + igraph_real_t s_imag = 0.0; + for (c = 0; c < n; c++) { + s_real += MATRIX(*A, r, c) * VECTOR(*v_real)[c]; + s_imag += MATRIX(*A, r, c) * VECTOR(*v_imag)[c]; + } + VECTOR(*res_real)[r] = s_real; + VECTOR(*res_imag)[r] = s_imag; + } + + return 0; +} + +int sc_cplx_cplx_mult(igraph_real_t lambda_real, + igraph_real_t lambda_imag, + const igraph_vector_t *v_real, + const igraph_vector_t *v_imag, + igraph_vector_t *res_real, + igraph_vector_t *res_imag) { + + igraph_int_t r; + igraph_int_t n = igraph_vector_size(v_real); + + if (igraph_vector_size(v_imag) != n) { + printf("Wrong vector sizes"); + return 1; + } + + igraph_vector_resize(res_real, n); + igraph_vector_resize(res_imag, n); + + for (r = 0; r < n; r++) { + VECTOR(*res_real)[r] = (lambda_real * VECTOR(*v_real)[r] - + lambda_imag * VECTOR(*v_imag)[r]); + VECTOR(*res_imag)[r] = (lambda_imag * VECTOR(*v_real)[r] + + lambda_real * VECTOR(*v_imag)[r]); + } + + return 0; +} + +igraph_bool_t check_ev(const igraph_matrix_t *A, + const igraph_vector_t *values_real, + const igraph_vector_t *values_imag, + const igraph_matrix_t *vectors_left, + const igraph_matrix_t *vectors_right, + igraph_real_t tol) { + + igraph_int_t n = igraph_matrix_nrow(A); + igraph_vector_t v_real, v_imag; + igraph_vector_t AV_real, AV_imag, lv_real, lv_imag; + igraph_vector_t null; + igraph_int_t i; + + if (igraph_matrix_ncol(A) != n) { + return 1; + } + if (igraph_vector_size(values_real) != n) { + return 1; + } + if (igraph_vector_size(values_imag) != n) { + return 1; + } + if (igraph_matrix_nrow(vectors_left) != n) { + return 1; + } + if (igraph_matrix_ncol(vectors_left) != n) { + return 1; + } + if (igraph_matrix_nrow(vectors_right) != n) { + return 1; + } + if (igraph_matrix_ncol(vectors_right) != n) { + return 1; + } + + igraph_vector_init(&AV_real, n); + igraph_vector_init(&AV_imag, n); + igraph_vector_init(&lv_real, n); + igraph_vector_init(&lv_imag, n); + igraph_vector_init(&null, n); + igraph_vector_null(&null); + + for (i = 0; i < n; i++) { + if (VECTOR(*values_imag)[i] == 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + v_imag = igraph_vector_view(VECTOR(null), n); + } else if (VECTOR(*values_imag)[i] > 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + v_imag = igraph_vector_view(&MATRIX(*vectors_right, 0, i + 1), n); + } else if (VECTOR(*values_imag)[i] < 0.0) { + v_real = igraph_vector_view(&MATRIX(*vectors_right, 0, i - 1), n); + v_imag = igraph_vector_view(&MATRIX(*vectors_right, 0, i), n); + igraph_vector_scale(&v_imag, -1.0); + } + real_cplx_mult(A, &v_real, &v_imag, &AV_real, &AV_imag); + sc_cplx_cplx_mult(VECTOR(*values_real)[i], VECTOR(*values_imag)[i], + &v_real, &v_imag, &lv_real, &lv_imag); + + if (igraph_vector_maxdifference(&AV_real, &lv_real) > tol || + igraph_vector_maxdifference(&AV_imag, &lv_imag) > tol) { + igraph_vector_print(&AV_real); + igraph_vector_print(&AV_imag); + igraph_vector_print(&lv_real); + igraph_vector_print(&lv_imag); + return 1; + } + } + + igraph_vector_destroy(&null); + igraph_vector_destroy(&AV_imag); + igraph_vector_destroy(&AV_real); + igraph_vector_destroy(&lv_imag); + igraph_vector_destroy(&lv_real); + + return 0; +} + +int main(void) { + + igraph_matrix_t A; + igraph_matrix_t vectors_left, vectors_right; + igraph_vector_t values_real, values_imag; + igraph_int_t i, j; + int info = 1; + int ilo, ihi; + igraph_real_t abnrm; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&vectors_left, 0, 0); + igraph_matrix_init(&vectors_right, 0, 0); + igraph_vector_init(&values_real, 0); + igraph_vector_init(&values_imag, 0); + + for (i = 0; i < DIM; i++) { + for (j = 0; j < DIM; j++) { + MATRIX(A, i, j) = igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + igraph_lapack_dgeevx(IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH, + &A, &values_real, &values_imag, + &vectors_left, &vectors_right, &ilo, &ihi, + /*scale=*/ 0, &abnrm, /*rconde=*/ 0, + /*rcondv=*/ 0, &info); + + if (check_ev(&A, &values_real, &values_imag, + &vectors_left, &vectors_right, /*tol=*/ 1e-8)) { + return 1; + } + + igraph_vector_destroy(&values_imag); + igraph_vector_destroy(&values_real); + igraph_matrix_destroy(&vectors_right); + igraph_matrix_destroy(&vectors_left); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_lapack_dgehrd.c b/tests/unit/igraph_lapack_dgehrd.c new file mode 100644 index 0000000..058e111 --- /dev/null +++ b/tests/unit/igraph_lapack_dgehrd.c @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + int nodes = 10; + igraph_t tree; + igraph_matrix_t sto; + igraph_matrix_t hess; + igraph_matrix_complex_t evec1, evec2; + igraph_vector_complex_t eval1, eval2; + igraph_eigen_which_t which; + int i; + + igraph_kary_tree(&tree, nodes, /* children= */ 3, IGRAPH_TREE_UNDIRECTED); + + igraph_matrix_init(&sto, nodes, nodes); + igraph_get_stochastic(&tree, &sto, /*column_wise=*/ 0, /* weights = */ NULL); + igraph_matrix_transpose(&sto); + + igraph_matrix_init(&hess, nodes, nodes); + igraph_lapack_dgehrd(&sto, 1, nodes, &hess); + + igraph_matrix_complex_init(&evec1, 0, 0); + igraph_vector_complex_init(&eval1, 0); + which.pos = IGRAPH_EIGEN_ALL; + igraph_eigen_matrix(&sto, 0, 0, nodes, 0, IGRAPH_EIGEN_LAPACK, &which, 0, 0, + &eval1, &evec1); + + igraph_matrix_complex_init(&evec2, 0, 0); + igraph_vector_complex_init(&eval2, 0); + igraph_eigen_matrix(&hess, 0, 0, nodes, 0, IGRAPH_EIGEN_LAPACK, &which, 0, + 0, &eval2, &evec2); + + for (i = 0; i < nodes; i++) { + igraph_real_t d = igraph_complex_abs(igraph_complex_sub(VECTOR(eval1)[i], + VECTOR(eval2)[i])); + if (d > 1e-14) { + printf("Difference: %g\n", d); + return 1; + } + } + + igraph_matrix_complex_destroy(&evec2); + igraph_vector_complex_destroy(&eval2); + + igraph_matrix_complex_destroy(&evec1); + igraph_vector_complex_destroy(&eval1); + + igraph_matrix_destroy(&hess); + igraph_matrix_destroy(&sto); + igraph_destroy(&tree); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_lapack_dgehrd.out b/tests/unit/igraph_lapack_dgehrd.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/igraph_lapack_dgetrf.c b/tests/unit/igraph_lapack_dgetrf.c new file mode 100644 index 0000000..ed89f52 --- /dev/null +++ b/tests/unit/igraph_lapack_dgetrf.c @@ -0,0 +1,74 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_and_destroy(igraph_matrix_t *matrix, igraph_bool_t pivot) { + igraph_vector_int_t pivot_indices; + int info; + igraph_vector_int_init(&pivot_indices, 0); + printf("Starting matrix:\n"); + igraph_matrix_print(matrix); + if (pivot) { + IGRAPH_ASSERT(igraph_lapack_dgetrf(matrix, &pivot_indices, &info) == IGRAPH_SUCCESS); + } else { + IGRAPH_ASSERT(igraph_lapack_dgetrf(matrix, NULL, &info) == IGRAPH_SUCCESS); + } + printf("Returned matrix:\n"); + igraph_matrix_print(matrix); + if (pivot) { + printf("Returned pivot indices:\n"); + igraph_vector_int_print(&pivot_indices); + } + printf("info: %d\n", info); + igraph_vector_int_destroy(&pivot_indices); + igraph_matrix_destroy(matrix); + printf("\n"); +} + +int main(void) { + igraph_matrix_t matrix; + + printf("Empty matrix:\n"); + igraph_matrix_init(&matrix, 0, 0); + check_and_destroy(&matrix, 1); + + int elements_1[9] = {7, 8, 9, 2, 2, 3, 1, 1, 1}; + matrix_init_int_row_major(&matrix, 3, 3, elements_1); + check_and_destroy(&matrix, 1); + + int elements_2[9] = {1, 1, 1, 2, 2, 3, 7, 8, 9}; + matrix_init_int_row_major(&matrix, 3, 3, elements_2); + check_and_destroy(&matrix, 1); + + int elements_3[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + matrix_init_int_row_major(&matrix, 3, 3, elements_3); + check_and_destroy(&matrix, 0); + + int elements_4[6] = {1, 2, 3, 4, 5, 6}; + matrix_init_int_row_major(&matrix, 2, 3, elements_4); + check_and_destroy(&matrix, 1); + + int elements_5[6] = {1, 2, 3, 4, 5, 6}; + matrix_init_int_row_major(&matrix, 3, 2, elements_5); + check_and_destroy(&matrix, 1); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_lapack_dgetrf.out b/tests/unit/igraph_lapack_dgetrf.out new file mode 100644 index 0000000..e8f319c --- /dev/null +++ b/tests/unit/igraph_lapack_dgetrf.out @@ -0,0 +1,63 @@ +Empty matrix: +Starting matrix: +Returned matrix: +Returned pivot indices: + +info: 0 + +Starting matrix: +7 8 9 +2 2 3 +1 1 1 +Returned matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Returned pivot indices: +1 2 3 +info: 0 + +Starting matrix: +1 1 1 +2 2 3 +7 8 9 +Returned matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Returned pivot indices: +3 2 3 +info: 0 + +Starting matrix: +0 1 2 +3 4 5 +6 7 8 +Returned matrix: + 6 7 8 + 0 1 2 +0.5 0.5 0 +info: 3 + +Starting matrix: +1 2 3 +4 5 6 +Returned matrix: + 4 5 6 +0.25 0.75 1.5 +Returned pivot indices: +2 2 +info: 0 + +Starting matrix: +1 2 +3 4 +5 6 +Returned matrix: + 5 6 +0.2 0.8 +0.6 0.5 +Returned pivot indices: +3 3 +info: 0 + diff --git a/tests/unit/igraph_lapack_dgetrs.c b/tests/unit/igraph_lapack_dgetrs.c new file mode 100644 index 0000000..1f72d96 --- /dev/null +++ b/tests/unit/igraph_lapack_dgetrs.c @@ -0,0 +1,122 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_and_destroy(igraph_matrix_t *a, igraph_matrix_t *b, igraph_vector_int_t *ipiv, + igraph_bool_t transpose, igraph_error_t error) { + printf("LU matrix:\n"); + igraph_matrix_print(a); + printf("Pivot vector:\n"); + igraph_vector_int_print(ipiv); + printf("B matrix:\n"); + igraph_matrix_print(b); + IGRAPH_ASSERT(igraph_lapack_dgetrs(transpose, a, ipiv, b) == error); + if (error == IGRAPH_SUCCESS) { + printf("Returned matrix:\n"); + igraph_matrix_print(b); + } + igraph_vector_int_destroy(ipiv); + igraph_matrix_destroy(a); + igraph_matrix_destroy(b); + printf("\n"); +} + +int main(void) { + igraph_matrix_t a, b; + igraph_vector_int_t ipiv; + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking empty matrices:\n"); + igraph_matrix_init(&a, 0, 0); + igraph_matrix_init(&b, 0, 0); + igraph_vector_int_init_int(&ipiv, 0); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_SUCCESS); + + { + printf("Checking 3x3 matrix:\n"); + double a_elements[] = {7, 8, 9, 2./7., -2./7., 3./7., 1./7., 1./2., -1./2.}; + int b_elements[] = {1, 1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 3, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 3, 1, 2, 3); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_SUCCESS); + } + { + printf("Checking transpose and pivot:\n"); + double a_elements[] = {9, 3, 1, 8./9., -2./3., 1./9., 7./9., 1./2., 1./6.}; + int b_elements[] = {1, 1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 3, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 3, 3, 2, 3); + check_and_destroy(&a, &b, &ipiv, 1, IGRAPH_SUCCESS); + } + { + printf("Checking 2x3 matrix, expected to fail:\n"); + double a_elements[] = {4, 5, 6, 1./4., 3./4., 3./2.}; + int b_elements[] = {1, 1}; + matrix_init_real_row_major(&a, 2, 3, a_elements); + matrix_init_int_row_major(&b, 2, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 2, 2, 2); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_EINVAL); + } + { + printf("Checking singular matrix, this gives random output, so we just check for memory problems.\n\n"); + double a_elements[] = {6, 8, 7, 0, 1, 2, 0.5, 0.5, 0}; + int b_elements[] = {1, 1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 3, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 3, 1, 2, 3); + IGRAPH_ASSERT(igraph_lapack_dgetrs(0, &a, &ipiv, &b) == IGRAPH_SUCCESS); + igraph_vector_int_destroy(&ipiv); + igraph_matrix_destroy(&a); + igraph_matrix_destroy(&b); + } + { + printf("Checking wrong size of B matrix, should fail:\n"); + double a_elements[] = {7, 8, 9, 2./7., -2./7., 3./7., 1./7., 1./2., -1./2.}; + int b_elements[] = {1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 2, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 3, 1, 2, 3); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_EINVAL); + } + { + printf("Checking nonexisting pivots, should fail:\n"); + double a_elements[] = {7, 8, 9, 2./7., -2./7., 3./7., 1./7., 1./2., -1./2.}; + int b_elements[] = {1, 1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 3, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 3, 5, 6, 7); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_EINVAL); + } + { + printf("Checking too few pivots, should fail:\n"); + double a_elements[] = {7, 8, 9, 2./7., -2./7., 3./7., 1./7., 1./2., -1./2.}; + int b_elements[] = {1, 1, 1}; + matrix_init_real_row_major(&a, 3, 3, a_elements); + matrix_init_int_row_major(&b, 3, 1, b_elements); + igraph_vector_int_init_int(&ipiv, 0); + check_and_destroy(&a, &b, &ipiv, 0, IGRAPH_EINVAL); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_lapack_dgetrs.out b/tests/unit/igraph_lapack_dgetrs.out new file mode 100644 index 0000000..04f19a6 --- /dev/null +++ b/tests/unit/igraph_lapack_dgetrs.out @@ -0,0 +1,86 @@ +Checking empty matrices: +LU matrix: +Pivot vector: + +B matrix: +Returned matrix: + +Checking 3x3 matrix: +LU matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Pivot vector: +1 2 3 +B matrix: +1 +1 +1 +Returned matrix: + 6 +-4 +-1 + +Checking transpose and pivot: +LU matrix: + 9 3 1 +0.888889 -0.666667 0.111111 +0.777778 0.5 0.166667 +Pivot vector: +3 2 3 +B matrix: +1 +1 +1 +Returned matrix: + 6 +-4 +-1 + +Checking 2x3 matrix, expected to fail: +LU matrix: + 4 5 6 +0.25 0.75 1.5 +Pivot vector: +2 2 +B matrix: +1 +1 + +Checking singular matrix, this gives random output, so we just check for memory problems. + +Checking wrong size of B matrix, should fail: +LU matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Pivot vector: +1 2 3 +B matrix: +1 +1 + +Checking nonexisting pivots, should fail: +LU matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Pivot vector: +5 6 7 +B matrix: +1 +1 +1 + +Checking too few pivots, should fail: +LU matrix: + 7 8 9 +0.285714 -0.285714 0.428571 +0.142857 0.5 -0.5 +Pivot vector: + +B matrix: +1 +1 +1 + diff --git a/tests/unit/igraph_lapack_dsyevr.c b/tests/unit/igraph_lapack_dsyevr.c new file mode 100644 index 0000000..ed1a790 --- /dev/null +++ b/tests/unit/igraph_lapack_dsyevr.c @@ -0,0 +1,215 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +#define DIM 10 + +igraph_bool_t check_ev(const igraph_matrix_t *A, + const igraph_vector_t *values, + const igraph_matrix_t *vectors, igraph_real_t tol) { + igraph_vector_t y; + igraph_int_t m = igraph_matrix_ncol(vectors); + igraph_int_t n = igraph_matrix_nrow(A); + + if (igraph_matrix_ncol(A) != n) { + return 1; + } + if (igraph_vector_size(values) != m) { + return 1; + } + if (igraph_matrix_nrow(vectors) != n) { + return 1; + } + + igraph_vector_init(&y, n); + + for (igraph_int_t i = 0; i < m; i++) { + const igraph_vector_t v = igraph_vector_view(&MATRIX(*vectors, 0, i), n); + igraph_vector_update(&y, &v); + igraph_blas_dgemv(/*transpose=*/ 0, /*alpha=*/ 1.0, A, &v, + /*beta=*/ -VECTOR(*values)[i], &y); + for (igraph_int_t j = 0; j < n; j++) { + if (fabs(VECTOR(y)[j]) > tol) { + printf("Matrix:\n"); + igraph_matrix_print(A); + printf("lambda= %g\n", VECTOR(*values)[i]); + printf("v= "); + igraph_vector_print(&v); + printf("residual: "); + igraph_vector_print(&y); + return 1; + } + } + } + + igraph_vector_destroy(&y); + return 0; +} + +int main(void) { + + igraph_matrix_t A; + igraph_matrix_t vectors, vectors2; + igraph_vector_t values, values2; + int i, j; + int il, iu; + igraph_real_t vl, vu; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&A, DIM, DIM); + igraph_matrix_init(&vectors, 0, 0); + igraph_vector_init(&values, 0); + + /* All eigenvalues and eigenvectors */ + + for (i = 0; i < DIM; i++) { + for (j = i; j < DIM; j++) { + MATRIX(A, i, j) = MATRIX(A, j, i) = + igraph_rng_get_integer(igraph_rng_default(), 1, 10); + } + } + + igraph_lapack_dsyevr(&A, IGRAPH_LAPACK_DSYEV_ALL, /*vl=*/ 0, /*vu=*/ 0, + /*vestimate=*/ 0, /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-10, &values, &vectors, /*support=*/ 0); + + if (igraph_vector_size(&values) != DIM) { + return 1; + } + if (igraph_matrix_nrow(&vectors) != DIM || + igraph_matrix_ncol(&vectors) != DIM) { + return 2; + } + if (check_ev(&A, &values, &vectors, /*tol=*/ 1e-8)) { + return 3; + } + + /* Only a subset */ + + igraph_matrix_init(&vectors2, 0, 0); + igraph_vector_init(&values2, 0); + + il = 2; + iu = 5; + igraph_lapack_dsyevr(&A, IGRAPH_LAPACK_DSYEV_SELECT, /*vl=*/ 0, /*vu=*/ 0, + /*vestimate=*/ 0, /*il=*/ il, /*iu=*/ iu, + /*abstol=*/ 1e-10, &values2, &vectors2, + /*support=*/ 0); + + if (igraph_vector_size(&values2) != iu - il + 1) { + return 4; + } + if (igraph_matrix_nrow(&vectors2) != DIM || + igraph_matrix_ncol(&vectors2) != iu - il + 1) { + return 5; + } + for (i = 0; i < iu - il + 1; i++) { + igraph_real_t m1 = 1.0; + + if (fabs(VECTOR(values)[il + i - 1] - VECTOR(values2)[i]) > 1e-8) { + printf("Full: "); + igraph_vector_print(&values); + printf("Subset: "); + igraph_vector_print(&values2); + return 6; + } + + if (MATRIX(vectors, 0, il + i - 1) * MATRIX(vectors2, 0, i) < 0) { + m1 = -1.0; + } else { + m1 = 1.0; + } + + for (j = 0; j < DIM; j++) { + if (fabs(MATRIX(vectors, j, il + i - 1) - + m1 * MATRIX(vectors2, j, i)) > 1e-8) { + printf("Full:\n"); + igraph_matrix_print(&vectors); + printf("Subset:\n"); + igraph_matrix_print(&vectors2); + return 7; + } + } + } + + igraph_vector_destroy(&values2); + igraph_matrix_destroy(&vectors2); + + /* Subset based on an interval */ + + igraph_matrix_init(&vectors2, 0, 0); + igraph_vector_init(&values2, 0); + + il = 2; + iu = 5; + vl = (VECTOR(values)[il - 1] + VECTOR(values)[il - 2]) / 2.0; + vu = (VECTOR(values)[iu] + VECTOR(values)[iu - 1]) / 2.0; + + igraph_lapack_dsyevr(&A, IGRAPH_LAPACK_DSYEV_INTERVAL, vl, vu, + /*vestimate=*/ iu - il + 1, /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-10, &values2, &vectors2, + /*support=*/ 0); + + if (igraph_vector_size(&values2) != iu - il + 1) { + return 4; + } + if (igraph_matrix_nrow(&vectors2) != DIM || + igraph_matrix_ncol(&vectors2) != iu - il + 1) { + return 5; + } + for (i = 0; i < iu - il + 1; i++) { + igraph_real_t m1 = 1.0; + + if (fabs(VECTOR(values)[il + i - 1] - VECTOR(values2)[i]) > 1e-8) { + printf("Full: "); + igraph_vector_print(&values); + printf("Subset: "); + igraph_vector_print(&values2); + return 6; + } + + if (MATRIX(vectors, 0, il + i - 1) * MATRIX(vectors2, 0, i) < 0) { + m1 = -1.0; + } else { + m1 = 1.0; + } + + for (j = 0; j < DIM; j++) { + if (fabs(MATRIX(vectors, j, il + i - 1) - + m1 * MATRIX(vectors2, j, i)) > 1e-8) { + printf("Full:\n"); + igraph_matrix_print(&vectors); + printf("Subset:\n"); + igraph_matrix_print(&vectors2); + return 7; + } + } + } + + igraph_vector_destroy(&values2); + igraph_matrix_destroy(&vectors2); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&A); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_lastcit_game.c b/tests/unit/igraph_lastcit_game.c new file mode 100644 index 0000000..659eef5 --- /dev/null +++ b/tests/unit/igraph_lastcit_game.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_t preference; + + igraph_rng_seed(igraph_rng_default(), 42); + + /*No nodes*/ + igraph_vector_init_int_end(&preference, -1, 1, 1, -1); + IGRAPH_ASSERT(igraph_lastcit_game(&g, /*nodes*/ 0, /*edges_per_node*/ 5, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 0); + igraph_destroy(&g); + igraph_vector_destroy(&preference); + + /*No edges*/ + igraph_vector_init_int_end(&preference, -1, 1, 1, -1); + IGRAPH_ASSERT(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 0, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 9); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + igraph_destroy(&g); + igraph_vector_destroy(&preference); + + /*Only cite un-cited to make a line*/ + igraph_vector_init_int_end(&preference, -1, 0, 1, -1); + IGRAPH_ASSERT(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&preference); + + /*Hugely prefer cited to make a star*/ + igraph_vector_init_real(&preference, 2, 1e30, 1e-30); + IGRAPH_ASSERT(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + igraph_vector_destroy(&preference); + + VERIFY_FINALLY_STACK(); + + /*Negative number of nodes*/ + igraph_vector_init_int_end(&preference, -1, 1, 1, -1); + CHECK_ERROR(igraph_lastcit_game(&g, /*nodes*/ -9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0), IGRAPH_EINVAL); + igraph_vector_destroy(&preference); + + /*Too few agebins*/ + igraph_vector_init_int_end(&preference, -1, 1, -1); + CHECK_ERROR(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 0, /*preference*/ &preference, /*directed*/ 0), IGRAPH_EINVAL); + igraph_vector_destroy(&preference); + + /*Wrong vector size*/ + igraph_vector_init_int_end(&preference, -1, 1, -1); + CHECK_ERROR(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0), IGRAPH_EINVAL); + igraph_vector_destroy(&preference); + + /*No uncited preference*/ + igraph_vector_init_int_end(&preference, -1, 1, 0, -1); + CHECK_ERROR(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0), IGRAPH_EINVAL); + igraph_vector_destroy(&preference); + + /*Negative preference*/ + igraph_vector_init_int_end(&preference, -1, -2, 1, -1); + CHECK_ERROR(igraph_lastcit_game(&g, /*nodes*/ 9, /*edges_per_node*/ 1, /*agebins*/ 1, /*preference*/ &preference, /*directed*/ 0), IGRAPH_EINVAL); + igraph_vector_destroy(&preference); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_lastcit_game.out b/tests/unit/igraph_lastcit_game.out new file mode 100644 index 0000000..d0617e2 --- /dev/null +++ b/tests/unit/igraph_lastcit_game.out @@ -0,0 +1,24 @@ +directed: false +vcount: 9 +edges: { +0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +} +directed: true +vcount: 9 +edges: { +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +} diff --git a/tests/unit/igraph_layout_align.c b/tests/unit/igraph_layout_align.c new file mode 100644 index 0000000..65d3dee --- /dev/null +++ b/tests/unit/igraph_layout_align.c @@ -0,0 +1,194 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#include + +void check_align(const igraph_t *graph) { + const igraph_int_t vcount = igraph_vcount(graph); + igraph_matrix_t layout; + igraph_bool_t connected; + + igraph_matrix_init(&layout, 0, 0); + + /* Test with a 2D layout. */ + + igraph_layout_fruchterman_reingold( + graph, &layout, + /* use_seed */ false, /* niter */ 1000, + /* start_temp */ sqrt(vcount), + IGRAPH_LAYOUT_NOGRID, + /* weights */ NULL, + NULL, NULL, NULL, NULL); + + igraph_layout_align(graph, &layout); + + IGRAPH_ASSERT(igraph_matrix_nrow(&layout) == vcount); + IGRAPH_ASSERT(igraph_matrix_ncol(&layout) == 2); + IGRAPH_ASSERT(igraph_vector_is_all_finite(&layout.data)); + + /* Test with a 3D layout. */ + + igraph_layout_fruchterman_reingold_3d( + graph, &layout, + /* use_seed */ false, /* niter */ 1000, + /* start_temp */ sqrt(vcount), + /* weights */ NULL, + NULL, NULL, NULL, NULL, NULL, NULL); + + igraph_layout_align(graph, &layout); + + IGRAPH_ASSERT(igraph_matrix_nrow(&layout) == vcount); + IGRAPH_ASSERT(igraph_matrix_ncol(&layout) == 3); + IGRAPH_ASSERT(igraph_vector_is_all_finite(&layout.data)); + + /* Test connected graphs having at least 4 vertices with a 4D layout. + * igraph_layout_mds() only supports 2D layouts with disconnected graphs, + * and requires at least as many vertices as the dimension. */ + + igraph_is_connected(graph, &connected, IGRAPH_WEAK); + + if (connected && vcount >= 4) { + igraph_layout_mds(graph, &layout, NULL, 4); + + igraph_layout_align(graph, &layout); + + IGRAPH_ASSERT(igraph_matrix_nrow(&layout) == vcount); + IGRAPH_ASSERT(igraph_matrix_ncol(&layout) == 4); + IGRAPH_ASSERT(igraph_vector_is_all_finite(&layout.data)); + } + + /* Test with a 1D layout. */ + + igraph_matrix_resize(&layout, vcount, 1); + igraph_layout_align(graph, &layout); + + igraph_matrix_destroy(&layout); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + igraph_t graph; + + printf("Empty\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + check_align(&graph); + igraph_destroy(&graph); + + printf("Singleton\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + check_align(&graph); + igraph_destroy(&graph); + + printf("Singleton with loop\n"); + igraph_full(&graph, 1, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("Two isolated vertices\n"); + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + check_align(&graph); + igraph_destroy(&graph); + + printf("P_2\n"); + igraph_full(&graph, 2, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("K_2 with loops\n"); + igraph_full(&graph, 2, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("C_3\n"); + igraph_full(&graph, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("K_4\n"); + igraph_full(&graph, 4, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("K_5\n"); + igraph_full(&graph, 5, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + check_align(&graph); + igraph_destroy(&graph); + + printf("C_4\n"); + igraph_ring(&graph, 4, IGRAPH_UNDIRECTED, false, /* circular */ true); + check_align(&graph); + igraph_destroy(&graph); + + printf("P_4\n"); + igraph_ring(&graph, 4, IGRAPH_UNDIRECTED, false, /* circular */ false); + check_align(&graph); + igraph_destroy(&graph); + + printf("C_7\n"); + igraph_ring(&graph, 7, IGRAPH_UNDIRECTED, false, /* circular */ true); + check_align(&graph); + igraph_destroy(&graph); + + printf("Frucht\n"); + igraph_famous(&graph, "Frucht"); + check_align(&graph); + igraph_destroy(&graph); + + printf("Petersen\n"); + igraph_famous(&graph, "Petersen"); + check_align(&graph); + igraph_destroy(&graph); + + printf("Groetzsch\n"); + igraph_famous(&graph, "Groetzsch"); + check_align(&graph); + igraph_destroy(&graph); + + printf("Kautz(3,2)\n"); + igraph_kautz(&graph, 3, 2); + check_align(&graph); + igraph_destroy(&graph); + + printf("Karate club\n"); + igraph_famous(&graph, "Zachary"); + check_align(&graph); + igraph_destroy(&graph); + + { + igraph_matrix_t layout; + + /* Wrong row count */ + igraph_matrix_init(&layout, 5, 2); + igraph_full(&graph, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + CHECK_ERROR(igraph_layout_align(&graph, &layout), IGRAPH_EINVAL); + + /* Zero-dimensional layout */ + igraph_matrix_resize(&layout, igraph_vcount(&graph), 0); + CHECK_ERROR(igraph_layout_align(&graph, &layout), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_matrix_destroy(&layout); + } + + return 0; +} diff --git a/tests/unit/igraph_layout_bipartite.c b/tests/unit/igraph_layout_bipartite.c new file mode 100644 index 0000000..4df0a55 --- /dev/null +++ b/tests/unit/igraph_layout_bipartite.c @@ -0,0 +1,110 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_vector_bool_t types; + + printf("No vertices:\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init(&types, 0); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/ 100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("1 vertex:\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 1, 0); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/ 100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("4 vertices, disconnected, not actually bipartite, with loops and multiple edges:\n"); + igraph_small(&g, 4, 0, 0,0, 0,0, 0,0, 1,2, 1,2, 1,3, 1,3, 2,3, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 4, 0, 1, 0, 1); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/ 100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("10 vertices bipartite graph:\n"); + igraph_small(&g, 10, 0, 0,5, 0,7, 1,6, 1,7, 1,8, 2,5, 3,8, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 10, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("10 vertices bipartite graph, no iterations:\n"); + igraph_small(&g, 10, 0, 0,5, 0,7, 1,6, 1,7, 1,8, 2,5, 3,8, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 10, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/0) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("4 vertices with -10 true values for types:\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, 3,0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 4, 0, -10, 0, -10); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ 1.0, /*maxiter*/ 100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("4 vertices, negative vgaps:\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, 3,0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 4, 0, 1, 0, 1); + IGRAPH_ASSERT(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ 1.0, /*vgap*/ -1.0, /*maxiter*/ 100) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + printf("4 vertices, negative hgaps, emits error.\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, 3,0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_bool_init_int(&types, 4, 0, 1, 0, 1); + CHECK_ERROR(igraph_layout_bipartite(&g, &types, &result, /*hgap*/ -1.0, /*vgap*/ 1.0, /*maxiter*/ 100), IGRAPH_EINVAL); + igraph_vector_bool_destroy(&types); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_bipartite.out b/tests/unit/igraph_layout_bipartite.out new file mode 100644 index 0000000..26642a9 --- /dev/null +++ b/tests/unit/igraph_layout_bipartite.out @@ -0,0 +1,42 @@ +No vertices: +[ 0-by-2 ] +1 vertex: +[ 0 1 ] +4 vertices, disconnected, not actually bipartite, with loops and multiple edges: +[ 0 1 + 1 0 + 1 1 + 2 0 ] +10 vertices bipartite graph: +[ 1 1 + 2 1 + 0 1 + 3 1 + 4 1 + 0 0 + 2 0 + 1 0 + 3 0 + 5 0 ] +10 vertices bipartite graph, no iterations: +[ -0.5 1 + 1 1 + 2 1 + 3 1 + 4 1 + 0 0 + 1 0 + 2 0 + 3 0 + 5 0 ] +4 vertices with -10 true values for types: +[ 0 1 + 0 0 + 1 1 + 1 0 ] +4 vertices, negative vgaps: +[ 0 -1 + 0 0 + 1 -1 + 1 0 ] +4 vertices, negative hgaps, emits error. diff --git a/tests/unit/igraph_layout_davidson_harel.c b/tests/unit/igraph_layout_davidson_harel.c new file mode 100644 index 0000000..59ded5a --- /dev/null +++ b/tests/unit/igraph_layout_davidson_harel.c @@ -0,0 +1,180 @@ +/* + igraph library. + Copyright (C) 2014-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "layout/layout_internal.h" + +#include "test_utilities.h" + +int intersect(void) { + + igraph_real_t negative[][8] = { + { 1, 2, 2, 2, 1, 1, 2, 1 }, /* 1 */ + { 1, 2, 1, 1, 2, 2, 2, 1 }, /* 2 */ + { 1, 0, 0, 1, 2, 0, 3, 1 }, /* 3 */ + { 1, 0, 1, 1, 0, 2, 2, 2 }, /* 4 */ + { 1, 0, 1, 2, 3, 1, 3, 3 }, /* 5 */ + { 0, 0, 0, 2, 1, 1, 1, 2 }, /* 6 */ + { 0, 1, 1, 1, 2, 0, 2, 3 }, /* 7 */ + { 0, 0, 5, 0, 2, 1, 4, 3 }, /* 8 */ + { 0, 0, 5, 5, 3, 2, 3, 2 } /* 9 */ + }; + + igraph_real_t positive[][8] = { + { 0, 1, 2, 1, 1, 0, 1, 2 }, /* 10 */ + { 0, 2, 5, 2, 1, 1, 4, 3 }, /* 11 */ + { 0, 0, 0, 3, 0, 1, 5, 1 }, /* 12 */ + { 0, 4, 2, 6, 0, 4, 2, 2 } /* 13 */ + }; + /* { 1,1,1,1, 1,1,0,0 }, /\* 14 *\/ */ + /* { 0,0,1,1, 1,1,1,1 }, /\* 15 *\/ */ + /* { 0,0,2,2, 1,1,1,1 }}; /\* 16 *\/ */ + + int no_neg = sizeof(negative) / sizeof(igraph_real_t) / 8; + int no_pos = sizeof(positive) / sizeof(igraph_real_t) / 8; + + for (int i = 0; i < no_neg; i++) { + igraph_real_t *co = negative[i]; + if (igraph_i_layout_segments_intersect( + co[0], co[1], co[2], co[3], + co[4], co[5], co[6], co[7])) { + return i + 1; + } + } + + for (int i = 0; i < no_pos; i++) { + igraph_real_t *co = positive[i]; + if (!igraph_i_layout_segments_intersect( + co[0], co[1], co[2], co[3], + co[4], co[5], co[6], co[7])) { + return no_neg + i + 1; + } + } + + return 0; +} + +int distance(void) { + + igraph_real_t configs[][7] = { + { 1, 1, 2, 0, 2, 3, 1.0 }, /* 1 */ + { 1, 1, 1, 0, 1, 3, 0.0 }, /* 2 */ + { 1, 1, 0, 1, 1, 0, 0.5 }, /* 3 */ + { 1, 2, 0, 0, 0, 1, 2.0 }, /* 4 */ + { 1, 0, 0, 1, 0, 2, 2.0 }, /* 5 */ + { 0, 0, 1, 1, 1, 2, 2.0 }, /* 6 */ + { 0, 3, 1, 1, 1, 2, 2.0 } /* 7 */ + }; + + int no = sizeof(configs) / sizeof(igraph_real_t) / 8; + + for (int i = 0; i < no; i++) { + igraph_real_t *co = configs[i]; + igraph_real_t res = igraph_i_layout_point_segment_dist2( + co[0], co[1], co[2], co[3], co[4], co[5]); + if (fabs(res - co[6]) > 1e-12) { + printf("%g\n", (double) res); + return i + 1; + } + } + + return 0; +} + +void check_layout_davidson_harel(void) { + igraph_t g; + igraph_matrix_t res; + igraph_bool_t use_seed; + igraph_int_t maxiter, fineiter; + igraph_real_t cool_fact, weight_node_dist, weight_border; + igraph_real_t weight_edge_lengths, weight_edge_crossings, weight_node_edge_dist; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&res, 0, 0); + + use_seed = 0; + maxiter = 5; + fineiter = 5; + cool_fact = 0.75; + weight_node_dist = 1.0; + weight_border = 0.1; + weight_edge_lengths = 0.5; + weight_edge_crossings = 0.8; + weight_node_edge_dist = 0.2; + + printf("Checking graph with no vertices\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + igraph_layout_davidson_harel(&g, &res, use_seed, maxiter, fineiter, cool_fact, + weight_node_dist, weight_border, + weight_edge_lengths, + weight_edge_crossings, + weight_node_edge_dist); + + IGRAPH_ASSERT(igraph_matrix_nrow(&res) == 0); + igraph_destroy(&g); + + printf("Checking graph with 10 vertices, no edges\n"); + igraph_small(&g, 10, IGRAPH_DIRECTED, -1); + igraph_layout_davidson_harel(&g, &res, use_seed, maxiter, fineiter, cool_fact, + weight_node_dist, weight_border, + weight_edge_lengths, + weight_edge_crossings, + weight_node_edge_dist); + IGRAPH_ASSERT(igraph_matrix_nrow(&res) == 10); + IGRAPH_ASSERT(igraph_matrix_ncol(&res) == 2); + IGRAPH_ASSERT(igraph_matrix_max(&res) < 20); + IGRAPH_ASSERT(igraph_matrix_min(&res) > -20); + igraph_destroy(&g); + + printf("Checking full graph with 10 vertices\n"); + igraph_full(&g, 10, IGRAPH_DIRECTED, 1); + igraph_layout_davidson_harel(&g, &res, use_seed, maxiter, fineiter, cool_fact, + weight_node_dist, weight_border, + weight_edge_lengths, + weight_edge_crossings, + weight_node_edge_dist); + IGRAPH_ASSERT(igraph_matrix_nrow(&res) == 10); + IGRAPH_ASSERT(igraph_matrix_ncol(&res) == 2); + IGRAPH_ASSERT(igraph_matrix_max(&res) < 20); + IGRAPH_ASSERT(igraph_matrix_min(&res) > -20); + igraph_destroy(&g); + igraph_matrix_destroy(&res); +} + +int main(void) { + int res1, res2; + + res1 = intersect(); + if (res1 != 0) { + printf("Unexpected result from intersect(), config %d.\n", res1); + return res1; + } + res2 = distance() ; + if (res2 != 0) { + printf("Unexpected result from distance(), config %d.\n", res2); + return res2; + } + + check_layout_davidson_harel(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_davidson_harel.out b/tests/unit/igraph_layout_davidson_harel.out new file mode 100644 index 0000000..a918258 --- /dev/null +++ b/tests/unit/igraph_layout_davidson_harel.out @@ -0,0 +1,3 @@ +Checking graph with no vertices +Checking graph with 10 vertices, no edges +Checking full graph with 10 vertices diff --git a/tests/unit/igraph_layout_drl.c b/tests/unit/igraph_layout_drl.c new file mode 100644 index 0000000..3ccca78 --- /dev/null +++ b/tests/unit/igraph_layout_drl.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void set_options_fast(igraph_layout_drl_options_t *options) { + options->edge_cut = 4.0/5.0; + + options->init_iterations = 10; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 10; + options->liquid_temperature = 2000; + options->liquid_attraction = 10; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 10; + options->expansion_temperature = 2000; + options->expansion_attraction = 2; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 10; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 10; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 10; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 1; +} + +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_layout_drl_options_t options; + int i; + igraph_real_t *damping_muls[6] = {&options.init_damping_mult, &options.liquid_damping_mult, &options.expansion_damping_mult, &options.cooldown_damping_mult, &options.crunch_damping_mult, &options.simmer_damping_mult}; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_layout_drl_options_init(&options, IGRAPH_LAYOUT_DRL_DEFAULT); + + printf("The Zachary karate club.\n"); + igraph_famous(&g, "zachary"); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_drl(&g, &result, /*use_seed*/ 0, &options, + /*weights*/ NULL) == IGRAPH_SUCCESS); + check_and_destroy(&result, 50); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + set_options_fast(&options); + printf("Negative damping.\n"); + igraph_matrix_init(&result, 0, 0); + for (i = 0; i < 6; i++) { + *damping_muls[i] *= -1.0; + IGRAPH_ASSERT(igraph_layout_drl(&g, &result, /*use_seed*/ 0, &options, + /*weights*/ NULL) == IGRAPH_EINVAL); + *damping_muls[i] *= -1.0; + } + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_drl_3d.c b/tests/unit/igraph_layout_drl_3d.c new file mode 100644 index 0000000..2079c57 --- /dev/null +++ b/tests/unit/igraph_layout_drl_3d.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_layout_drl_options_t options; + int i; + igraph_real_t *damping_muls[6] = {&options.init_damping_mult, &options.liquid_damping_mult, &options.expansion_damping_mult, &options.cooldown_damping_mult, &options.crunch_damping_mult, &options.simmer_damping_mult}; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_layout_drl_options_init(&options, IGRAPH_LAYOUT_DRL_REFINE); + + printf("The Zachary karate club.\n"); + igraph_famous(&g, "zachary"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_drl_3d(&g, &result, /*use_seed*/ 0, &options, + /*weights*/ NULL); + check_and_destroy(&result, 50); + + VERIFY_FINALLY_STACK(); + + igraph_layout_drl_options_init(&options, IGRAPH_LAYOUT_DRL_FINAL); + printf("Negative damping.\n"); + igraph_matrix_init(&result, 0, 0); + for (i = 0; i < 6; i++) { + *damping_muls[i] = -1.0; + CHECK_ERROR(igraph_layout_drl_3d(&g, &result, /*use_seed*/ 0, &options, + /*weights*/ NULL), IGRAPH_EINVAL); + *damping_muls[i] = 1.0; + } + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_drl_3d.out b/tests/unit/igraph_layout_drl_3d.out new file mode 100644 index 0000000..8d40472 --- /dev/null +++ b/tests/unit/igraph_layout_drl_3d.out @@ -0,0 +1,2 @@ +The Zachary karate club. +Negative damping. diff --git a/tests/unit/igraph_layout_fruchterman_reingold.c b/tests/unit/igraph_layout_fruchterman_reingold.c new file mode 100644 index 0000000..ce9104d --- /dev/null +++ b/tests/unit/igraph_layout_fruchterman_reingold.c @@ -0,0 +1,128 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void make_box(int vertices, float half_size, igraph_vector_t *bounds) { + for (int i = 0; i < 4; i++) { + igraph_vector_init(&bounds[i], vertices); + } + igraph_vector_fill(&bounds[0], -half_size); + igraph_vector_fill(&bounds[1], half_size); + igraph_vector_fill(&bounds[2], -half_size); + igraph_vector_fill(&bounds[3], half_size); +} + +void destroy_bounds(igraph_vector_t *bounds) { + for (int i = 0; i < 4; i++) { + igraph_vector_destroy(&bounds[i]); + } +} + +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_vector_t bounds[4]; + igraph_vector_t weights; + igraph_real_t seed[20] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1.0}; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Empty graph.\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, IGRAPH_LAYOUT_NOGRID, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("Singleton graph in a box.\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + make_box(1, 1.0, bounds); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, IGRAPH_LAYOUT_NOGRID, + /*weights*/ NULL, &bounds[0], &bounds[1], &bounds[2], &bounds[3]) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + destroy_bounds(bounds); + + printf("A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1.\n"); + igraph_small(&g, 10, 0, 0,1, 1,2, 2,0, 5,6, 6,7, 7,6, 7,7, 8,8, -1); + igraph_vector_init(&weights, 8); + igraph_vector_fill(&weights, 100); + make_box(10, 1.0, bounds); + printf("Without weights, grid or bounds.\n"); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 10.0, IGRAPH_LAYOUT_NOGRID, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL) == IGRAPH_SUCCESS); + check_and_destroy(&result, 50.0); + + printf("With weights and no grid.\n"); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, IGRAPH_LAYOUT_NOGRID, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL) == IGRAPH_SUCCESS); + check_and_destroy(&result, 50.0); + + printf("With weights and grid and high temperature.\n"); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 0, + /*niter*/ 10, /*start_temp*/ 1e10, IGRAPH_LAYOUT_GRID, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3]) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + + printf("With weights and grid and high temperature and seed.\n"); + matrix_init_real_row_major(&result, 10, 2, seed); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 1, + /*niter*/ 10, /*start_temp*/ 1e10, IGRAPH_LAYOUT_GRID, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3]) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + printf("Full graph of 5 vertices, seed and no iterations:\n"); + igraph_full(&g, 5, 0, 0); + matrix_init_real_row_major(&result, 5, 2, seed); + IGRAPH_ASSERT(igraph_layout_fruchterman_reingold(&g, &result, /*use_seed*/ 1, + /*niter*/ 0, /*start_temp*/ 100, IGRAPH_LAYOUT_GRID, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + destroy_bounds(bounds); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_fruchterman_reingold.out b/tests/unit/igraph_layout_fruchterman_reingold.out new file mode 100644 index 0000000..fcca374 --- /dev/null +++ b/tests/unit/igraph_layout_fruchterman_reingold.out @@ -0,0 +1,14 @@ +Empty graph. +[ 0-by-2 ] +Singleton graph in a box. +A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1. +Without weights, grid or bounds. +With weights and no grid. +With weights and grid and high temperature. +With weights and grid and high temperature and seed. +Full graph of 5 vertices, seed and no iterations: +[ 0.1 0.2 + 0.3 0.4 + 0.5 0.6 + 0.7 0.8 + 0.9 1 ] diff --git a/tests/unit/igraph_layout_fruchterman_reingold_3d.c b/tests/unit/igraph_layout_fruchterman_reingold_3d.c new file mode 100644 index 0000000..3d3ca05 --- /dev/null +++ b/tests/unit/igraph_layout_fruchterman_reingold_3d.c @@ -0,0 +1,130 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void make_box(int vertices, float half_size, igraph_vector_t *bounds) { + for (int i = 0; i < 6; i++) { + igraph_vector_init(&bounds[i], vertices); + } + igraph_vector_fill(&bounds[0], -half_size); + igraph_vector_fill(&bounds[1], half_size); + igraph_vector_fill(&bounds[2], -half_size); + igraph_vector_fill(&bounds[3], half_size); + igraph_vector_fill(&bounds[4], -half_size); + igraph_vector_fill(&bounds[5], half_size); +} + +void destroy_bounds(igraph_vector_t *bounds) { + for (int i = 0; i < 6; i++) { + igraph_vector_destroy(&bounds[i]); + } +} + +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_vector_t bounds[6]; + igraph_vector_t weights; + igraph_real_t seed[30] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2, -1.3, -1.4, -1.5, -1.6, -1.7, -1.8, -1.9, -2.0}; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Empty graph.\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("Singleton graph in a box.\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + make_box(1, 1.0, bounds); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, + /*weights*/ NULL, &bounds[0], &bounds[1], &bounds[2], &bounds[3], &bounds[4], &bounds[5]); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + destroy_bounds(bounds); + + printf("A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1.\n"); + igraph_small(&g, 10, 0, 0,1, 1,2, 2,0, 5,6, 6,7, 7,6, 7,7, 8,8, -1); + igraph_vector_init(&weights, 8); + igraph_vector_fill(&weights, 100); + make_box(10, 1.0, bounds); + printf("Without weights, grid or bounds.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 10.0, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + check_and_destroy(&result, 50.0); + + printf("With weights.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 0, + /*niter*/ 100, /*start_temp*/ 1.0, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + check_and_destroy(&result, 50.0); + + printf("With weights and high temperature.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 0, + /*niter*/ 10, /*start_temp*/ 1e10, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3], &bounds[4], &bounds[5]); + check_and_destroy(&result, 1.0); + + printf("With weights and high temperature and seed.\n"); + matrix_init_real_row_major(&result, 10, 3, seed); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 1, + /*niter*/ 10, /*start_temp*/ 1e10, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3], &bounds[4], &bounds[5]); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + printf("Full graph of 5 vertices, seed and no iterations:\n"); + igraph_full(&g, 5, 0, 0); + matrix_init_real_row_major(&result, 5, 3, seed); + igraph_layout_fruchterman_reingold_3d(&g, &result, /*use_seed*/ 1, + /*niter*/ 0, /*start_temp*/ 100, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + destroy_bounds(bounds); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_fruchterman_reingold_3d.out b/tests/unit/igraph_layout_fruchterman_reingold_3d.out new file mode 100644 index 0000000..dc6e7b9 --- /dev/null +++ b/tests/unit/igraph_layout_fruchterman_reingold_3d.out @@ -0,0 +1,14 @@ +Empty graph. +[ 0-by-3 ] +Singleton graph in a box. +A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1. +Without weights, grid or bounds. +With weights. +With weights and high temperature. +With weights and high temperature and seed. +Full graph of 5 vertices, seed and no iterations: +[ 0.1 0.2 0.3 + 0.4 0.5 0.6 + 0.7 0.8 0.9 + 1 -0.1 -0.2 + -0.3 -0.4 -0.5 ] diff --git a/tests/unit/igraph_layout_gem.c b/tests/unit/igraph_layout_gem.c new file mode 100644 index 0000000..16dc4a0 --- /dev/null +++ b/tests/unit/igraph_layout_gem.c @@ -0,0 +1,120 @@ +/* igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_matrix_t res; + igraph_bool_t use_seed; + igraph_bool_t do_not_use_seed; + igraph_int_t maxiter; + igraph_real_t temp_max; + igraph_real_t temp_min; + igraph_real_t temp_init; + + igraph_rng_seed(igraph_rng_default(), 42); + use_seed = 1; + do_not_use_seed = 0; + maxiter = 10; + temp_max = 1; + temp_min = 0.1; + temp_init = 1; + + printf("Check if 0 vertex graph crashes.\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, -1); + igraph_matrix_init(&res, 0, 2); + + igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, temp_init); + igraph_matrix_print(&res); + igraph_destroy(&graph); + igraph_matrix_destroy(&res); + + printf("Check seeded 1-vertex graph, no iterations.\n"); + maxiter = 0; + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, -1); + { + int elem[] = {3,4}; + matrix_init_int_row_major(&res, 1, 2, elem); + } + + igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, temp_init); + igraph_matrix_print(&res); + igraph_destroy(&graph); + igraph_matrix_destroy(&res); + maxiter = 40; + + printf("Check if 0 vertex graph crashes without a seed.\n"); + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, -1); + igraph_matrix_init(&res, 0, 2); + + igraph_layout_gem(&graph, &res, do_not_use_seed, maxiter, temp_max, temp_min, temp_init); + igraph_matrix_print(&res); + igraph_destroy(&graph); + igraph_matrix_destroy(&res); + + printf("Check unseeded 1-vertex graph.\n"); + maxiter = 40; + igraph_small(&graph, 1, IGRAPH_UNDIRECTED, -1); + igraph_matrix_init(&res, 0, 2); + + igraph_layout_gem(&graph, &res, do_not_use_seed, maxiter, temp_max, temp_min, temp_init); + igraph_destroy(&graph); + igraph_matrix_destroy(&res); + + printf("Check 2-vertex graph.\n"); + maxiter = 100000; + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0,1, -1); + { + int elem[] = {3, 4, 5, 6}; + matrix_init_int_row_major(&res, 2, 2, elem); + } + + igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, temp_init); + IGRAPH_ASSERT(igraph_matrix_min(&res) > -1000); + IGRAPH_ASSERT(igraph_matrix_max(&res) < 1000); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + maxiter = 3; + printf("Check negative maxiter.\n"); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, -1, temp_max, temp_min, temp_init), IGRAPH_EINVAL); + printf("Check negative temp_max.\n"); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, -1, temp_min, temp_init), IGRAPH_EINVAL); + printf("Check negative temp_min.\n"); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, -1, temp_init), IGRAPH_EINVAL); + printf("Check negative temp_init.\n"); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, -1), IGRAPH_EINVAL); + printf("Check temp_init not between min and max.\n"); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, 1, 2, 1.5), IGRAPH_EINVAL); + printf("Check too many rows in seed.\n"); + igraph_matrix_destroy(&res); + igraph_matrix_init(&res, 10, 2); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, temp_init), IGRAPH_EINVAL); + printf("Check too many columns in seed.\n"); + igraph_matrix_destroy(&res); + igraph_matrix_init(&res, 2, 5); + CHECK_ERROR(igraph_layout_gem(&graph, &res, use_seed, maxiter, temp_max, temp_min, temp_init), IGRAPH_EINVAL); + + igraph_matrix_destroy(&res); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_gem.out b/tests/unit/igraph_layout_gem.out new file mode 100644 index 0000000..e70c584 --- /dev/null +++ b/tests/unit/igraph_layout_gem.out @@ -0,0 +1,13 @@ +Check if 0 vertex graph crashes. +Check seeded 1-vertex graph, no iterations. +3 4 +Check if 0 vertex graph crashes without a seed. +Check unseeded 1-vertex graph. +Check 2-vertex graph. +Check negative maxiter. +Check negative temp_max. +Check negative temp_min. +Check negative temp_init. +Check temp_init not between min and max. +Check too many rows in seed. +Check too many columns in seed. diff --git a/tests/unit/igraph_layout_graphopt.c b/tests/unit/igraph_layout_graphopt.c new file mode 100644 index 0000000..1712d54 --- /dev/null +++ b/tests/unit/igraph_layout_graphopt.c @@ -0,0 +1,98 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.001, /*node_mass*/ 30, /*spring_length*/ 0, /*spring constant*/ 1, /*max_sa_movement*/ 5, /*use seed*/ 0) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("One vertex.\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.001, /*node_mass*/ 30, /*spring_length*/ 0, /*spring constant*/ 1, /*max_sa_movement*/ 5, /*use seed*/ 0) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + printf("Full graph of 4 vertices, no loops.\n"); + igraph_full(&g, 4, 0, 0); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.001, /*node_mass*/ 30, /*spring_length*/ 0, /*spring constant*/ 1, /*max_sa_movement*/ 5, /*use seed*/ 0) == IGRAPH_SUCCESS); + check_and_destroy(&result, 20.0); + igraph_destroy(&g); + + printf("4 vertices, disconnected, with loops and multi-edges.\n"); + igraph_small(&g, 4, 0, 0,0, 0,0, 0,0, 1,2, 1,2, 1,2, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.001, /*node_mass*/ 30, /*spring_length*/ 0, /*spring constant*/ 1, /*max_sa_movement*/ 5, /*use seed*/ 0) == IGRAPH_SUCCESS); + check_and_destroy(&result, 100.0); + igraph_destroy(&g); + + printf("Full graph of 4 vertices, no loops with no repulsion.\n"); + igraph_full(&g, 4, 0, 0); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.0, /*node_mass*/ 30, /*spring_length*/ 0, /*spring constant*/ 1, /*max_sa_movement*/ 5, /*use seed*/ 0) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + printf("4 vertices in a line, with no repulsion, spring length 1 and a seed:\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, -1); + igraph_real_t seed[] = {0.15, -0.15, 0.05, -0.05, -0.05, 0.05, -0.15, 0.15}; + matrix_init_real_row_major(&result, 4, 2, seed); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.0, /*node_mass*/ 30, /*spring_length*/ 1, /*spring constant*/ 10, /*max_sa_movement*/ 5, /*use seed*/ 1) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("4 vertices in a line, with no repulsion, spring length 1 and a seed, no sa_movement:\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, -1); + matrix_init_real_row_major(&result, 4, 2, seed); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.0, /*node_mass*/ 30, /*spring_length*/ 1, /*spring constant*/ 10, /*max_sa_movement*/ 0, /*use seed*/ 1) == IGRAPH_SUCCESS); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("Wrong size seed.\n"); + igraph_small(&g, 4, 0, 0,1, 1,2, 2,3, -1); + matrix_init_real_row_major(&result, 3, 2, seed); + IGRAPH_ASSERT(igraph_layout_graphopt(&g, &result, /*niter*/ 500, /*node_charge*/ 0.0, /*node_mass*/ 30, /*spring_length*/ 1, /*spring constant*/ 10, /*max_sa_movement*/ 0, /*use seed*/ 1) == IGRAPH_SUCCESS); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_graphopt.out b/tests/unit/igraph_layout_graphopt.out new file mode 100644 index 0000000..8dfd438 --- /dev/null +++ b/tests/unit/igraph_layout_graphopt.out @@ -0,0 +1,17 @@ +No vertices: +[ 0-by-2 ] +One vertex. +Full graph of 4 vertices, no loops. +4 vertices, disconnected, with loops and multi-edges. +Full graph of 4 vertices, no loops with no repulsion. +4 vertices in a line, with no repulsion, spring length 1 and a seed: +[ 1.06066 -1.06066 + 0.353553 -0.353553 + -0.353553 0.353553 + -1.06066 1.06066 ] +4 vertices in a line, with no repulsion, spring length 1 and a seed, no sa_movement: +[ 0.15 -0.15 + 0.05 -0.05 + -0.05 0.05 + -0.15 0.15 ] +Wrong size seed. diff --git a/tests/unit/igraph_layout_grid.c b/tests/unit/igraph_layout_grid.c new file mode 100644 index 0000000..744eb37 --- /dev/null +++ b/tests/unit/igraph_layout_grid.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t coords; + + igraph_empty(&g, 15, 0); + igraph_matrix_init(&coords, 0, 0); + + /* Predefined width, 2D */ + igraph_layout_grid(&g, &coords, 5); + igraph_matrix_print(&coords); + printf("===\n"); + + /* Automatic width, 2D */ + igraph_layout_grid(&g, &coords, -1); + igraph_matrix_print(&coords); + printf("===\n"); + + /* Predefined width and height, 3D */ + igraph_layout_grid_3d(&g, &coords, 4, 2); + igraph_matrix_print(&coords); + printf("=====\n"); + + /* Predefined width, 3D */ + igraph_layout_grid_3d(&g, &coords, 4, -1); + igraph_matrix_print(&coords); + printf("=====\n"); + + /* Predefined height, 3D */ + igraph_layout_grid_3d(&g, &coords, -1, 3); + igraph_matrix_print(&coords); + printf("=====\n"); + + /* Automatic width and height, 3D */ + igraph_layout_grid_3d(&g, &coords, -1, -1); + igraph_matrix_print(&coords); + + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_grid.out b/tests/unit/igraph_layout_grid.out new file mode 100644 index 0000000..1b8a947 --- /dev/null +++ b/tests/unit/igraph_layout_grid.out @@ -0,0 +1,95 @@ +0 0 +1 0 +2 0 +3 0 +4 0 +0 1 +1 1 +2 1 +3 1 +4 1 +0 2 +1 2 +2 2 +3 2 +4 2 +=== +0 0 +1 0 +2 0 +3 0 +0 1 +1 1 +2 1 +3 1 +0 2 +1 2 +2 2 +3 2 +0 3 +1 3 +2 3 +=== +0 0 0 +1 0 0 +2 0 0 +3 0 0 +0 1 0 +1 1 0 +2 1 0 +3 1 0 +0 0 1 +1 0 1 +2 0 1 +3 0 1 +0 1 1 +1 1 1 +2 1 1 +===== +0 0 0 +1 0 0 +2 0 0 +3 0 0 +0 1 0 +1 1 0 +2 1 0 +3 1 0 +0 0 1 +1 0 1 +2 0 1 +3 0 1 +0 1 1 +1 1 1 +2 1 1 +===== +0 0 0 +1 0 0 +2 0 0 +0 1 0 +1 1 0 +2 1 0 +0 2 0 +1 2 0 +2 2 0 +0 0 1 +1 0 1 +2 0 1 +0 1 1 +1 1 1 +2 1 1 +===== +0 0 0 +1 0 0 +2 0 0 +0 1 0 +1 1 0 +2 1 0 +0 2 0 +1 2 0 +2 2 0 +0 0 1 +1 0 1 +2 0 1 +0 1 1 +1 1 1 +2 1 1 diff --git a/tests/unit/igraph_layout_kamada_kawai.c b/tests/unit/igraph_layout_kamada_kawai.c new file mode 100644 index 0000000..0e3b8eb --- /dev/null +++ b/tests/unit/igraph_layout_kamada_kawai.c @@ -0,0 +1,249 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void make_box(int vertices, float half_size, igraph_vector_t bounds[]) { + for (int i = 0; i < 4; i++) { + igraph_vector_init(&bounds[i], vertices); + } + igraph_vector_fill(&bounds[0], -half_size); + igraph_vector_fill(&bounds[1], half_size); + igraph_vector_fill(&bounds[2], -half_size); + igraph_vector_fill(&bounds[3], half_size); +} + +void destroy_bounds(igraph_vector_t bounds[]) { + for (int i = 0; i < 4; i++) { + igraph_vector_destroy(&bounds[i]); + } +} + +void make_box_3d(int vertices, float half_size, igraph_vector_t bounds3d[]) { + for (int i = 0; i < 6; i++) { + igraph_vector_init(&bounds3d[i], vertices); + } + igraph_vector_fill(&bounds3d[0], -half_size); + igraph_vector_fill(&bounds3d[1], half_size); + igraph_vector_fill(&bounds3d[2], -half_size); + igraph_vector_fill(&bounds3d[3], half_size); + igraph_vector_fill(&bounds3d[4], -half_size); + igraph_vector_fill(&bounds3d[5], half_size); +} + +void destroy_bounds_3d(igraph_vector_t bounds3d[]) { + for (int i = 0; i < 6; i++) { + igraph_vector_destroy(&bounds3d[i]); + } +} + +/* Works for both 2D and 3D */ +void check_and_destroy(igraph_matrix_t *result, igraph_real_t half_size) { + igraph_real_t min, max; + igraph_matrix_minmax(result, &min, &max); + IGRAPH_ASSERT(min >= -half_size); + IGRAPH_ASSERT(max <= half_size); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_vector_t bounds[4], bounds3d[6]; + igraph_vector_t weights; + igraph_real_t seed[20] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1.0}; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Empty graph.\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_matrix_init(&result, 0, 0); + + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL); + print_matrix(&result); + + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + print_matrix(&result); + + igraph_matrix_destroy(&result); + igraph_destroy(&g); + + printf("Singleton graph in a box.\n"); + igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1); + + igraph_matrix_init(&result, 0, 0); + make_box(igraph_vcount(&g), 1.0, bounds); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weights*/ NULL, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + check_and_destroy(&result, 1.0); + destroy_bounds(bounds); + + igraph_matrix_init(&result, 0, 0); + make_box_3d(igraph_vcount(&g), 1.0, bounds3d); + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weights*/ NULL, + &bounds3d[0], &bounds3d[1], &bounds3d[2], &bounds3d[3], &bounds3d[4], &bounds3d[5]); + check_and_destroy(&result, 1.0); + destroy_bounds_3d(bounds3d); + + igraph_destroy(&g); + + printf("Two connected vertices.\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 2, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL); + check_and_destroy(&result, 1.0); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 2, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + check_and_destroy(&result, 1.0); + + printf("Two connected vertices in a box.\n"); + + igraph_matrix_init(&result, 0, 0); + make_box(igraph_vcount(&g), 1.0, bounds); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 2, + /*weights*/ NULL, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + check_and_destroy(&result, 1.0); + destroy_bounds(bounds); + + igraph_matrix_init(&result, 0, 0); + make_box_3d(igraph_vcount(&g), 1.0, bounds3d); + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weights*/ NULL, + &bounds3d[0], &bounds3d[1], &bounds3d[2], &bounds3d[3], &bounds3d[4], &bounds3d[5]); + check_and_destroy(&result, 1.0); + destroy_bounds_3d(bounds3d); + + igraph_destroy(&g); + + printf("P_3 graph.\n"); + igraph_ring(&g, 3, IGRAPH_UNDIRECTED, /*mutual*/ false, /*cirular*/ false); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 3, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL); + check_and_destroy(&result, 2.0); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 3, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + check_and_destroy(&result, 2.0); + + igraph_destroy(&g); + + printf("P_4 graph.\n"); + igraph_ring(&g, 4, IGRAPH_UNDIRECTED, /*mutual*/ false, /*cirular*/ false); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 4, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL); + check_and_destroy(&result, 2.0); + + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai_3d(&g, &result, /*use_seed*/ false, /*maxiter*/ 1000, + /*epsilon*/ 0, /*kkconst */ 4, + /*weight*/ NULL, + /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, /*maxy*/ NULL, /*minz*/ NULL, /*maxz*/ NULL); + check_and_destroy(&result, 2.0); + + igraph_destroy(&g); + + printf("A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1.\n"); + igraph_small(&g, 10, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,0, 5,6, 6,7, 7,6, 7,7, 8,8, -1); + igraph_vector_init(&weights, 8); + igraph_vector_fill(&weights, 100); + make_box(10, 1.0, bounds); + printf("Without weights or bounds.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL); + check_and_destroy(&result, 50.0); + + printf("With weights.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 10, + &weights, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL); + check_and_destroy(&result, 50.0); + + printf("With weights, bounds, and high kkconst.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 1000, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + check_and_destroy(&result, 1.0); + + printf("With weights, bounds, and low kkconst.\n"); + igraph_matrix_init(&result, 0, 0); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ false, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 0.0001, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + check_and_destroy(&result, 1.0); + + printf("With weights, bounds, and high kkconst and seed.\n"); + matrix_init_real_row_major(&result, 10, 2, seed); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ true, /*maxiter*/ 100, + /*epsilon*/ 0.0001, /*kkconst */ 1000, + &weights, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + check_and_destroy(&result, 1.0); + igraph_destroy(&g); + + printf("Full graph of 5 vertices, seed and no iterations:\n"); + igraph_full(&g, 5, 0, 0); + matrix_init_real_row_major(&result, 5, 2, seed); + igraph_layout_kamada_kawai(&g, &result, /*use_seed*/ true, /*maxiter*/ 0, + /*epsilon*/ 0.0001, /*kkconst */ 10, + /*weight*/ NULL, /*minx*/ NULL, /*maxx*/ NULL, /*miny*/ NULL, + /*maxy*/ NULL); + print_matrix(&result); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + destroy_bounds(bounds); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_kamada_kawai.out b/tests/unit/igraph_layout_kamada_kawai.out new file mode 100644 index 0000000..7f7bcb1 --- /dev/null +++ b/tests/unit/igraph_layout_kamada_kawai.out @@ -0,0 +1,20 @@ +Empty graph. +[ 0-by-2 ] +[ 0-by-3 ] +Singleton graph in a box. +Two connected vertices. +Two connected vertices in a box. +P_3 graph. +P_4 graph. +A few tests with a disconnected graph of 10 vertices with loops in a box from -1 to 1. +Without weights or bounds. +With weights. +With weights, bounds, and high kkconst. +With weights, bounds, and low kkconst. +With weights, bounds, and high kkconst and seed. +Full graph of 5 vertices, seed and no iterations: +[ 0.1 0.2 + 0.3 0.4 + 0.5 0.6 + 0.7 0.8 + 0.9 1 ] diff --git a/tests/unit/igraph_layout_lgl.c b/tests/unit/igraph_layout_lgl.c new file mode 100644 index 0000000..6cd37f7 --- /dev/null +++ b/tests/unit/igraph_layout_lgl.c @@ -0,0 +1,108 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_matrix_t coords; + igraph_real_t vc; + + igraph_rng_seed(igraph_rng_default(), 33); + + printf("Testing graph with no vertices\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&coords, 0, 0); + vc = igraph_vcount(&g); + igraph_layout_lgl(&g, &coords, + /* maxiter */ 150, + /* maxdelta */ vc, + /* area */ vc * vc, + /* coolexp */ 1.5, + /* repulserad */ vc * vc * vc, + /* cellsize */ sqrt(sqrt(vc)), + /* root */ 0); + + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + printf("Testing k-ary tree\n"); + igraph_kary_tree(&g, 100, 3, IGRAPH_TREE_UNDIRECTED); + igraph_matrix_init(&coords, 0, 0); + vc = igraph_vcount(&g); + igraph_layout_lgl(&g, &coords, + /* maxiter */ 150, + /* maxdelta */ vc, + /* area */ vc * vc, + /* coolexp */ 1.5, + /* repulserad */ vc * vc * vc, + /* cellsize */ sqrt(sqrt(vc)), + /* root */ 0); + + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + printf("Testing k-ary tree (many more times to stress-test igraph_2dgrid_t)\n"); + igraph_kary_tree(&g, 100, 3, IGRAPH_TREE_UNDIRECTED); + for (igraph_int_t i = 0; i < 100; i++) { + igraph_matrix_init(&coords, 0, 0); + vc = igraph_vcount(&g); + igraph_layout_lgl(&g, &coords, + /* maxiter */ 150, + /* maxdelta */ vc, + /* area */ vc * vc, + /* coolexp */ 1.5, + /* repulserad */ vc * vc * vc, + /* cellsize */ sqrt(sqrt(vc)), + /* root */ 0); + + igraph_matrix_destroy(&coords); + } + igraph_destroy(&g); + + /* Test that a warning is printed for disconnected graphs */ + printf("Testing disconnected graph\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, 3, 4, -1); + igraph_matrix_init(&coords, 0, 0); + vc = igraph_vcount(&g); + EXPECT_WARNING( + igraph_layout_lgl(&g, &coords, + /* maxiter */ 150, + /* maxdelta */ vc, + /* area */ vc * vc, + /* coolexp */ 1.5, + /* repulserad */ vc * vc * vc, + /* cellsize */ sqrt(sqrt(vc)), + /* root */ 0), + "LGL layout does not support disconnected graphs yet." + ); + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_lgl.out b/tests/unit/igraph_layout_lgl.out new file mode 100644 index 0000000..418aaf0 --- /dev/null +++ b/tests/unit/igraph_layout_lgl.out @@ -0,0 +1,4 @@ +Testing graph with no vertices +Testing k-ary tree +Testing k-ary tree (many more times to stress-test igraph_2dgrid_t) +Testing disconnected graph diff --git a/tests/unit/igraph_layout_mds.c b/tests/unit/igraph_layout_mds.c new file mode 100644 index 0000000..e7f69c7 --- /dev/null +++ b/tests/unit/igraph_layout_mds.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "test_utilities.h" + +#define sqr(x) ((x)*(x)) + +int main(void) { + igraph_t g; + igraph_matrix_t coords, dist_mat; + igraph_int_t i, j; + + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&coords, 0, 0); + igraph_layout_mds(&g, &coords, 0, 2); + print_matrix(&coords); + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + igraph_kary_tree(&g, 10, 2, IGRAPH_TREE_UNDIRECTED); + igraph_matrix_init(&coords, 0, 0); + igraph_layout_mds(&g, &coords, 0, 2); + if (MATRIX(coords, 0, 0) > 0) { + for (i = 0; i < igraph_matrix_nrow(&coords); i++) { + MATRIX(coords, i, 0) *= -1; + } + } + if (MATRIX(coords, 0, 1) < 0) { + for (i = 0; i < igraph_matrix_nrow(&coords); i++) { + MATRIX(coords, i, 1) *= -1; + } + } + print_matrix(&coords); + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + igraph_full(&g, 8, IGRAPH_UNDIRECTED, 0); + igraph_matrix_init(&coords, 8, 2); + igraph_matrix_init(&dist_mat, 8, 8); + for (i = 0; i < 8; i++) + for (j = 0; j < 2; j++) { + MATRIX(coords, i, j) = RNG_INTEGER(0, 1000); + } + for (i = 0; i < 8; i++) + for (j = i + 1; j < 8; j++) { + double dist_sq = 0.0; + dist_sq += sqr(MATRIX(coords, i, 0) - MATRIX(coords, j, 0)); + dist_sq += sqr(MATRIX(coords, i, 1) - MATRIX(coords, j, 1)); + MATRIX(dist_mat, i, j) = sqrt(dist_sq); + MATRIX(dist_mat, j, i) = sqrt(dist_sq); + } + igraph_layout_mds(&g, &coords, &dist_mat, 2); + for (i = 0; i < 8; i++) + for (j = i + 1; j < 8; j++) { + double dist_sq = 0.0; + dist_sq += sqr(MATRIX(coords, i, 0) - MATRIX(coords, j, 0)); + dist_sq += sqr(MATRIX(coords, i, 1) - MATRIX(coords, j, 1)); + if (fabs(sqrt(dist_sq) - MATRIX(dist_mat, i, j)) > 1e-2) { + printf("dist(%" IGRAPH_PRId ", %" IGRAPH_PRId ") should be %.4f, but it is %.4f\n", + i, j, MATRIX(dist_mat, i, j), sqrt(dist_sq)); + return 1; + } + } + igraph_matrix_destroy(&dist_mat); + igraph_matrix_destroy(&coords); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_mds.out b/tests/unit/igraph_layout_mds.out new file mode 100644 index 0000000..3595b61 --- /dev/null +++ b/tests/unit/igraph_layout_mds.out @@ -0,0 +1,11 @@ +[ 0-by-2 ] +[ -0.692039 0.0247583 + 0.399957 0.178289 + -1.78403 -0.128772 + 1.25186 -0.697105 + 0.891182 1.32979 + -2.6988 -0.242629 + -2.6988 -0.242629 + 1.97413 -1.3515 + 1.97413 -1.3515 + 1.38241 2.4813 ] diff --git a/tests/unit/igraph_layout_merge.c b/tests/unit/igraph_layout_merge.c new file mode 100644 index 0000000..7a15968 --- /dev/null +++ b/tests/unit/igraph_layout_merge.c @@ -0,0 +1,82 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "layout/layout_internal.h" + +#include "test_utilities.h" + +int main(void) { + + /*******************/ + /* Testing the DLA */ + /*******************/ + igraph_int_t nodes = 10; + igraph_i_layout_mergegrid_t grid; + igraph_vector_t x, y, r; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&x, nodes); + igraph_vector_init(&y, nodes); + igraph_vector_init(&r, nodes); + igraph_i_layout_mergegrid_init(&grid, -5, 5, 100, -5, 5, 100); + + /* radius */ + for (i = 0; i < nodes; i++) { + VECTOR(r)[i] = rand() / (double)RAND_MAX; + } + igraph_vector_sort(&r); + + /* place */ + VECTOR(x)[0] = 0; + VECTOR(y)[0] = 0; + igraph_i_layout_merge_place_sphere(&grid, 0, 0, VECTOR(r)[nodes - 1], 0); + + for (i = 1; i < nodes; i++) { + /* fprintf(stderr, "%" IGRAPH_PRId " ", i); */ + igraph_i_layout_merge_dla(&grid, i, + igraph_vector_get_ptr(&x, i), + igraph_vector_get_ptr(&y, i), + VECTOR(r)[nodes - i - 1], 0, 0, 4, 7); + igraph_i_layout_merge_place_sphere(&grid, VECTOR(x)[i], VECTOR(y)[i], + VECTOR(r)[nodes - i - 1], i); + } + + /* for (i=0; i + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t small, big; + igraph_matrix_t small_coords, big_coords, merged_coords; + igraph_vector_ptr_t graph_ptr; + igraph_matrix_list_t coords_list; + igraph_arpack_options_t arpack_opts; + igraph_int_t i, j, nrow, ncol; + + /* To make things reproducible */ + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&big, 10, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, + -1); + + igraph_small(&small, 3, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 0, + -1); + + igraph_arpack_options_init(&arpack_opts); + + igraph_matrix_init(&big_coords, 0, 0); + igraph_layout_circle(&big, &big_coords, igraph_vss_all()); + + igraph_matrix_init(&small_coords, 0, 0); + igraph_layout_circle(&small, &small_coords, igraph_vss_all()); + + igraph_vector_ptr_init(&graph_ptr, 2); + igraph_matrix_list_init(&coords_list, 0); + igraph_matrix_init(&merged_coords, 0, 0); + VECTOR(graph_ptr)[0] = &big; + VECTOR(graph_ptr)[1] = &small; + igraph_matrix_list_push_back(&coords_list, &big_coords); + igraph_matrix_list_push_back(&coords_list, &small_coords); + /* ownership of big_coords and small_coords now belongs to coords_list */ + + igraph_layout_merge_dla(&graph_ptr, &coords_list, &merged_coords); + + nrow = igraph_matrix_nrow(&merged_coords); + ncol = igraph_matrix_ncol(&merged_coords); + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + if (fabs(MATRIX(merged_coords, i, j)) < 1e-8) { + MATRIX(merged_coords, i, j) = 0; + } + } + } + + igraph_matrix_printf(&merged_coords, "%.4f"); + + igraph_matrix_destroy(&merged_coords); + igraph_vector_ptr_destroy(&graph_ptr); + igraph_matrix_list_destroy(&coords_list); + igraph_destroy(&small); + igraph_destroy(&big); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_merge2.out b/tests/unit/igraph_layout_merge2.out new file mode 100644 index 0000000..954b96b --- /dev/null +++ b/tests/unit/igraph_layout_merge2.out @@ -0,0 +1,13 @@ +4.0748 0.0000 +3.2966 2.3951 +1.2592 3.8754 +-1.2592 3.8754 +-3.2966 2.3951 +-4.0748 0.0000 +-3.2966 -2.3951 +-1.2592 -3.8754 +1.2592 -3.8754 +3.2966 -2.3951 +-6.5576 -0.7416 +-9.5422 0.9815 +-9.5422 -2.4648 diff --git a/tests/unit/igraph_layout_merge3.c b/tests/unit/igraph_layout_merge3.c new file mode 100644 index 0000000..d597ac0 --- /dev/null +++ b/tests/unit/igraph_layout_merge3.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_matrix_t coords; + int i; + + igraph_matrix_init(&coords, 0, 0); + + for (i = 0; i < 10; i++) { + igraph_erdos_renyi_game_gnp(&graph, 100, 2.0 / 100, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_layout_mds(&graph, &coords, /*dist=*/ 0, /*dim=*/ 2); + igraph_destroy(&graph); + } + + igraph_matrix_destroy(&coords); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_random_3d.c b/tests/unit/igraph_layout_random_3d.c new file mode 100644 index 0000000..0169cce --- /dev/null +++ b/tests/unit/igraph_layout_random_3d.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_real_t min, max; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_random_3d(&g, &result) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_matrix_nrow(&result) == 0); + IGRAPH_ASSERT(igraph_matrix_ncol(&result) == 3); + igraph_destroy(&g); + igraph_matrix_destroy(&result); + + printf("One vertex:\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_random_3d(&g, &result) == IGRAPH_SUCCESS); + igraph_matrix_minmax(&result, &min, &max); + IGRAPH_ASSERT(-1 <= min); + IGRAPH_ASSERT(max <= 1); + IGRAPH_ASSERT(igraph_matrix_nrow(&result) == 1); + IGRAPH_ASSERT(igraph_matrix_ncol(&result) == 3); + igraph_destroy(&g); + igraph_matrix_destroy(&result); + + printf("10 vertices:\n"); + igraph_small(&g, 10, 0, 0,1, 0,1, 0,1, 2,2, 2,2, 2,2, 3,4, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_random_3d(&g, &result) == IGRAPH_SUCCESS); + igraph_matrix_minmax(&result, &min, &max); + IGRAPH_ASSERT(-1 <= min); + IGRAPH_ASSERT(max <= 1); + IGRAPH_ASSERT(igraph_matrix_nrow(&result) == 10); + IGRAPH_ASSERT(igraph_matrix_ncol(&result) == 3); + igraph_destroy(&g); + igraph_matrix_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_reingold_tilford_circular.c b/tests/unit/igraph_layout_reingold_tilford_circular.c new file mode 100644 index 0000000..d02d4d0 --- /dev/null +++ b/tests/unit/igraph_layout_reingold_tilford_circular.c @@ -0,0 +1,116 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void chop_print_destroy(igraph_matrix_t *result) { + matrix_chop(result, 1e-10); + print_matrix(result); + igraph_matrix_destroy(result); +} + +int main(void) { + igraph_t g; + igraph_matrix_t result; + igraph_vector_int_t roots, rootlevel; + + printf("Empty graph check:\n"); + igraph_small(&g, 0, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_ALL, /*roots*/ NULL, /*rootlevel*/ NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + + printf("Singleton graph check:\n"); + igraph_small(&g, 1, 0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_ALL, /*roots*/ NULL, /*rootlevel*/ NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + + printf("Star graph check with given root:\n"); + igraph_small(&g, 5, 1, 0,1, 0,2, 0,3, 0,4, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init_int(&roots, 1, 1); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_OUT, &roots, /*rootlevel*/ NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + + printf("Star graph check with root found by topological sort:\n"); + igraph_small(&g, 5, 1, 1,0, 2,0, 3,0, 4,0, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_IN, NULL, /*rootlevel*/ NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + + printf("Two minitrees without rootlevel:\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 3,4, 3,5, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init_int(&roots, 2, 0, 3); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_OUT, &roots, NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + + printf("Two minitrees with rootlevel 10 and 20:\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 3,4, 3,5, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init_int(&roots, 2, 0, 3); + igraph_vector_int_init_int(&rootlevel, 2, 10, 20); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_OUT, &roots, &rootlevel) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + igraph_vector_int_destroy(&rootlevel); + + printf("Graph with just loops, triple edges and disconnected vertices:\n"); + igraph_small(&g, 5, 1, 0,0, 0,0, 0,0, 1,2, 1,2, 1,2, -1); + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_ALL, NULL, /*rootlevel*/ NULL) == IGRAPH_SUCCESS); + chop_print_destroy(&result); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking proper error handling:\n"); + printf("Giving negative root.\n"); + igraph_small(&g, 5, 1, 0,1, 0,2, 0,3, 0,4, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init_int(&roots, 1, -1); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_OUT, &roots, /*rootlevel*/ NULL) == IGRAPH_EINVVID); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + + printf("Giving negative rootlevel.\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 3,4, 3,5, -1); + igraph_matrix_init(&result, 0, 0); + igraph_vector_int_init_int(&roots, 2, 0, 3); + igraph_vector_int_init_int(&rootlevel, 2, -10, -20); + IGRAPH_ASSERT(igraph_layout_reingold_tilford_circular(&g, &result, IGRAPH_OUT, &roots, &rootlevel) == IGRAPH_EINVAL); + igraph_matrix_destroy(&result); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + igraph_vector_int_destroy(&rootlevel); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_reingold_tilford_circular.out b/tests/unit/igraph_layout_reingold_tilford_circular.out new file mode 100644 index 0000000..1c2957a --- /dev/null +++ b/tests/unit/igraph_layout_reingold_tilford_circular.out @@ -0,0 +1,39 @@ +Empty graph check: +[ 0-by-2 ] +Singleton graph check: +[ 0 0 ] +Star graph check with given root: +[ 1 0 + 0 0 + -0.104528 0.994522 + -0.978148 -0.207912 + 0.309017 -0.951057 ] +Star graph check with root found by topological sort: +[ 0 0 + 1 0 + -0.104528 0.994522 + -0.978148 -0.207912 + 0.309017 -0.951057 ] +Two minitrees without rootlevel: +[ 0.642788 0.766044 + 2 0 + -0.347296 1.96962 + -0.34202 -0.939693 + -1.87939 -0.68404 + 1 -1.73205 ] +Two minitrees with rootlevel 10 and 20: +[ 5.5 9.52628 + 12 0 + -6 10.3923 + -10.5 -18.1865 + -22 0 + 11 -19.0526 ] +Graph with just loops, triple edges and disconnected vertices: +[ 1 0 + -0.104528 0.994522 + -0.209057 1.98904 + -0.978148 -0.207912 + 0.309017 -0.951057 ] +Checking proper error handling: +Giving negative root. +Giving negative rootlevel. diff --git a/tests/unit/igraph_layout_reingold_tilford_extended.c b/tests/unit/igraph_layout_reingold_tilford_extended.c new file mode 100644 index 0000000..d418348 --- /dev/null +++ b/tests/unit/igraph_layout_reingold_tilford_extended.c @@ -0,0 +1,55 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + FILE *f; + igraph_matrix_t coords; + /* igraph_int_t i, n; */ + + f = fopen("igraph_layout_reingold_tilford_extended.in", "r"); + IGRAPH_ASSERT(f != NULL); + igraph_read_graph_edgelist(&g, f, 0, IGRAPH_DIRECTED); + igraph_matrix_init(&coords, 0, 0); + + igraph_layout_reingold_tilford(&g, &coords, IGRAPH_IN, 0, 0); + + /* + n=igraph_vcount(&g); + for (i=0; i + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void run_test(int n) { + igraph_t graph; + igraph_matrix_t layout; + + printf("\n%d-vertex graph:\n", n); + igraph_empty(&graph, n, IGRAPH_UNDIRECTED); + igraph_matrix_init(&layout, 0, 0); + igraph_layout_sphere(&graph, &layout); + IGRAPH_ASSERT(igraph_matrix_nrow(&layout) == igraph_vcount(&graph)); + IGRAPH_ASSERT(igraph_matrix_ncol(&layout) == 3); + igraph_matrix_zapsmall(&layout, 0 /* automatic */); + igraph_matrix_print(&layout); + igraph_matrix_destroy(&layout); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + run_test(0); + run_test(1); + run_test(2); + run_test(11); + + return 0; +} diff --git a/tests/unit/igraph_layout_sphere.out b/tests/unit/igraph_layout_sphere.out new file mode 100644 index 0000000..8d59ed9 --- /dev/null +++ b/tests/unit/igraph_layout_sphere.out @@ -0,0 +1,22 @@ + +0-vertex graph: + +1-vertex graph: +0 0 -1 + +2-vertex graph: +0 0 -1 +0 0 1 + +11-vertex graph: + 0 0 -1 +-0.141614 0.583048 -0.8 +-0.799764 -0.0194193 -0.6 +-0.324757 -0.857049 -0.4 + 0.664718 -0.719826 -0.2 + 0.966323 0.257333 0 + 0.197259 0.959734 0.2 + -0.76198 0.5093 0.4 + -0.57566 -0.555532 0.6 + 0.506779 -0.321208 0.8 + 0 0 1 diff --git a/tests/unit/igraph_layout_star.c b/tests/unit/igraph_layout_star.c new file mode 100644 index 0000000..2028560 --- /dev/null +++ b/tests/unit/igraph_layout_star.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_int_t center, igraph_vector_int_t *order, igraph_error_t error) { + igraph_matrix_t result; + igraph_matrix_init(&result, 0, 0); + IGRAPH_ASSERT(igraph_layout_star(g, &result, center, order) == error); + if (error == IGRAPH_SUCCESS) { + matrix_chop(&result, 1e-13); + print_matrix(&result); + } + + igraph_matrix_destroy(&result); + igraph_destroy(g); + if(order) { + igraph_vector_int_destroy(order); + } + +} + +int main(void) { + igraph_t g; + igraph_vector_int_t order; + + printf("No vertices.\n"); + igraph_small(&g, 0, 0, -1); + print_and_destroy(&g, 0, NULL, IGRAPH_SUCCESS); + + printf("Star of 8 points and a center:\n"); + igraph_small(&g, 9, 0, -1); + print_and_destroy(&g, 0, NULL, IGRAPH_SUCCESS); + + printf("Star of 8 points and a center in reverse:\n"); + igraph_small(&g, 9, 0, -1); + igraph_vector_int_init_int(&order, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + print_and_destroy(&g, 0, &order, IGRAPH_SUCCESS); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking if negative center fails nicely.\n"); + igraph_small(&g, 9, 0, -1); + print_and_destroy(&g, -10, NULL, IGRAPH_EINVAL); + + printf("Checking if order out of range fails nicely.\n"); + igraph_small(&g, 9, 0, -1); + igraph_vector_int_init_int(&order, 9, -1, -1, -1, 10, 10, 10, 2, 1, 0); + print_and_destroy(&g, 0, &order, IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_layout_star.out b/tests/unit/igraph_layout_star.out new file mode 100644 index 0000000..ee75993 --- /dev/null +++ b/tests/unit/igraph_layout_star.out @@ -0,0 +1,24 @@ +No vertices. +[ 0-by-2 ] +Star of 8 points and a center: +[ 0 0 + 1 0 + 0.707107 0.707107 + 0 1 + -0.707107 0.707107 + -1 0 + -0.707107 -0.707107 + 0 -1 + 0.707107 -0.707107 ] +Star of 8 points and a center in reverse: +[ 0 0 + 0.707107 -0.707107 + 0 -1 + -0.707107 -0.707107 + -1 0 + -0.707107 0.707107 + 0 1 + 0.707107 0.707107 + 1 0 ] +Checking if negative center fails nicely. +Checking if order out of range fails nicely. diff --git a/tests/unit/igraph_layout_sugiyama.c b/tests/unit/igraph_layout_sugiyama.c new file mode 100644 index 0000000..af75d29 --- /dev/null +++ b/tests/unit/igraph_layout_sugiyama.c @@ -0,0 +1,153 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_matrix_t coords; + igraph_vector_int_t edgelist, edgelist2; + igraph_matrix_list_t routing; + igraph_vector_int_t layers; + + igraph_matrix_init(&coords, 0, 0); + igraph_matrix_list_init(&routing, 0); + + /* Layout on simple graph with predefined layers */ + printf("Simple graph with predefined layers\n"); + igraph_vector_int_init_int_end(&layers, -1, 0, 1, 1, 2, 3, 3, 4, 4, 5, -1); + igraph_vector_int_init_int_end(&edgelist, -1, + 0, 1, 0, 2, 0, 3, 1, 2, 2, 2, 1, 4, 2, 5, 4, 6, 5, 7, 6, 8, 7, 8, + 3, 8, 8, 1, 8, 2, -1); + igraph_create(&g, &edgelist, 0, IGRAPH_DIRECTED); + + igraph_layout_sugiyama(&g, &coords, 0, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + printf("\n"); + + /* Same, but this time also return the routing information */ + printf("Simple graph with routing information\n"); + igraph_layout_sugiyama(&g, &coords, &routing, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + printf("\n"); + + igraph_vector_int_destroy(&layers); + + /* Same, but with automatic layering */ + printf("Simple graph with automatic layers\n"); + igraph_layout_sugiyama(&g, &coords, &routing, 0, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + printf("\n"); + + /* Layering with gaps in it */ + printf("Simple graph with gaps in layers\n"); + igraph_vector_int_init_int_end(&layers, -1, 0, 2, 2, 4, 6, 6, 12, 12, 15, -1); + igraph_layout_sugiyama(&g, &coords, &routing, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + printf("\n"); + + igraph_destroy(&g); + igraph_vector_int_destroy(&edgelist); + igraph_vector_int_destroy(&layers); + + /* Check edge directions and the order of control points within edges */ + igraph_vector_int_init_int_end(&edgelist, -1, 0, 1, 1, 2, 2, 3, 0, 3, 3, 0, -1); + igraph_vector_int_init_int_end(&layers, -1, 0, 1, 2, 3, -1); + + printf("Path graph with two shortcuts, directed\n"); + igraph_create(&g, &edgelist, 0, IGRAPH_DIRECTED); + igraph_vector_int_init(&edgelist2, 0); + igraph_get_edgelist(&g, &edgelist2, 0); + print_vector_int(&edgelist2); + igraph_vector_int_destroy(&edgelist2); + igraph_layout_sugiyama(&g, &coords, &routing, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + igraph_destroy(&g); + printf("\n"); + + printf("Path graph with two shortcuts, undirected\n"); + igraph_create(&g, &edgelist, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&edgelist2, 0); + igraph_get_edgelist(&g, &edgelist2, 0); + print_vector_int(&edgelist2); + igraph_vector_int_destroy(&edgelist2); + igraph_layout_sugiyama(&g, &coords, &routing, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + igraph_destroy(&g); + printf("\n"); + + printf("Path graph with two shortcuts, undirected, opposite layer order\n"); + igraph_vector_int_reverse(&layers); + igraph_create(&g, &edgelist, 0, IGRAPH_UNDIRECTED); + igraph_layout_sugiyama(&g, &coords, &routing, &layers, + /* hgap = */ 1, + /* vgap = */ 1, + /* maxiter = */ 100, + /* weights = */ 0); + print_matrix(&coords); + print_matrix_list(&routing); + igraph_destroy(&g); + printf("\n"); + + igraph_vector_int_destroy(&layers); + igraph_vector_int_destroy(&edgelist); + + igraph_matrix_list_destroy(&routing); + igraph_matrix_destroy(&coords); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_sugiyama.out b/tests/unit/igraph_layout_sugiyama.out new file mode 100644 index 0000000..193e744 --- /dev/null +++ b/tests/unit/igraph_layout_sugiyama.out @@ -0,0 +1,169 @@ +Simple graph with predefined layers +[ 2.5 0 + 0.5 1 + 2.5 1 + 4 2 + 0 3 + 2 3 + 0 4 + 2 4 + 2 5 ] + +Simple graph with routing information +[ 2.5 0 + 0.5 1 + 2.5 1 + 4 2 + 0 3 + 2 3 + 0 4 + 2 4 + 2 5 ] +{ + 0: [ 0-by-2 ] + 1: [ 0-by-2 ] + 2: [ 4 1 ] + 3: [ 0-by-2 ] + 4: [ 0-by-2 ] + 5: [ 0 2 ] + 6: [ 2 2 ] + 7: [ 0-by-2 ] + 8: [ 0-by-2 ] + 9: [ 0-by-2 ] + 10: [ 0-by-2 ] + 11: + [ 4 3 + 4 4 ] + 12: + [ 1 4 + 1 3 + 1 2 ] + 13: + [ 3 4 + 3 3 + 3 2 ] +} + +Simple graph with automatic layers +[ 2.5 0 + 1 1 + 2.5 2 + 4 1 + 0 2 + 2 3 + 0 3 + 2 4 + 2 5 ] +{ + 0: [ 0-by-2 ] + 1: [ 2.5 1 ] + 2: [ 0-by-2 ] + 3: [ 0-by-2 ] + 4: [ 0-by-2 ] + 5: [ 0-by-2 ] + 6: [ 0-by-2 ] + 7: [ 0-by-2 ] + 8: [ 0-by-2 ] + 9: [ 0 4 ] + 10: [ 0-by-2 ] + 11: + [ 4 2 + 4 3 + 4 4 ] + 12: + [ 1 4 + 1 3 + 1 2 ] + 13: + [ 3 4 + 3 3 ] +} + +Simple graph with gaps in layers +[ 2.5 0 + 0.5 2 + 2.5 2 + 4 4 + 0 6 + 2 6 + 0 12 + 2 12 + 2 15 ] +{ + 0: [ 0-by-2 ] + 1: [ 0-by-2 ] + 2: [ 4 2 ] + 3: [ 0-by-2 ] + 4: [ 0-by-2 ] + 5: [ 0 4 ] + 6: [ 2 4 ] + 7: [ 0-by-2 ] + 8: [ 0-by-2 ] + 9: [ 0-by-2 ] + 10: [ 0-by-2 ] + 11: + [ 4 6 + 4 12 ] + 12: + [ 1 12 + 1 6 + 1 4 ] + 13: + [ 3 12 + 3 6 + 3 4 ] +} + +Path graph with two shortcuts, directed +( 0 1 1 2 2 3 0 3 3 0 ) +[ 0 0 + 0 1 + 0 2 + 1 3 ] +{ + 0: [ 0-by-2 ] + 1: [ 0-by-2 ] + 2: [ 0-by-2 ] + 3: + [ 1 1 + 1 2 ] + 4: + [ 2 2 + 2 1 ] +} + +Path graph with two shortcuts, undirected +( 0 1 1 2 2 3 0 3 0 3 ) +[ 0 0 + 0 1 + 0 2 + 1 3 ] +{ + 0: [ 0-by-2 ] + 1: [ 0-by-2 ] + 2: [ 0-by-2 ] + 3: + [ 2 1 + 2 2 ] + 4: + [ 1 1 + 1 2 ] +} + +Path graph with two shortcuts, undirected, opposite layer order +[ 1 3 + 0 2 + 0 1 + 0 0 ] +{ + 0: [ 0-by-2 ] + 1: [ 0-by-2 ] + 2: [ 0-by-2 ] + 3: + [ 2 2 + 2 1 ] + 4: + [ 1 2 + 1 1 ] +} + diff --git a/tests/unit/igraph_layout_umap.c b/tests/unit/igraph_layout_umap.c new file mode 100644 index 0000000..adec204 --- /dev/null +++ b/tests/unit/igraph_layout_umap.c @@ -0,0 +1,337 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + + +/* Helper function, computes layout span of a subset of vertices */ +igraph_real_t get_layout_span(const igraph_matrix_t *layout, int i_start, int i_end) { + igraph_real_t dx, dy, dz, xmin, xmax, ymin, ymax, zmin, zmax, spanmax; + igraph_int_t ndim = igraph_matrix_ncol(layout); + + if (i_start >= i_end) + return -1; + + xmin = xmax = MATRIX(*layout, i_start, 0); + ymin = ymax = MATRIX(*layout, i_start, 1); + if (ndim == 3) { + zmin = zmax = MATRIX(*layout, i_start, 2); + } + for (int i = i_start + 1; i < i_end; i++) { + xmin = fmin(xmin, MATRIX(*layout, i, 0)); + xmax = fmax(xmax, MATRIX(*layout, i, 0)); + ymin = fmin(ymin, MATRIX(*layout, i, 1)); + ymax = fmax(ymax, MATRIX(*layout, i, 1)); + if (ndim == 3) { + zmin = fmin(zmin, MATRIX(*layout, i, 2)); + zmax = fmax(zmax, MATRIX(*layout, i, 2)); + } + } + /* total span of the layout */ + dx = xmax - xmin; + dy = ymax - ymin; + spanmax = dx > dy ? dx : dy; + if (ndim == 3) { + dz = zmax - zmin; + spanmax = dz > spanmax ? dz : spanmax; + } + return spanmax; +} + + +void check_graph_largeunion( + const igraph_matrix_t *layout, + const igraph_vector_int_t *subgraph_sizes) { + + /* sizes of the full subgraphs are 50, 70, 90 */ + igraph_real_t distlim; + igraph_int_t nrow = igraph_matrix_nrow(layout); + igraph_error_t err; + int nerr = 0; + igraph_int_t nvertices = 0; + + /* each cluster should occupy no more than say 50% of the layout */ + distlim = 0.2 * get_layout_span(layout, 0, nrow); + for (int i = 0; i < igraph_vector_int_size(subgraph_sizes); i++) { + err = get_layout_span( + layout, nvertices, nvertices + VECTOR(*subgraph_sizes)[i]) > distlim; + nvertices += VECTOR(*subgraph_sizes)[i]; + if (err) { + nerr++; + printf("layout of subgraph #%d too wide\n", i); + } + } + if (nerr == 0) { + printf("UMAP layout of large graph seems fine.\n"); + } +} + + +void check_graph_twoclusters_weights( + const igraph_vector_t *weights, + const igraph_vector_t *distances) { + int nerr = 0; + igraph_int_t i; + igraph_real_t weight; + igraph_int_t nc = igraph_vector_size(weights); + igraph_int_t nd; + + if (distances == NULL) { + for (i = 0; i < nc; i++) { + if (VECTOR(*weights)[i] != 1.0) { + printf("Connectivities with NULL distances should all be 1 or -1.\n"); + return; + } + } + + printf("Connectivities from NULL distances seem fine.\n"); + } else { + nd = igraph_vector_size(distances); + if (nd != nc) { + nerr++; + printf("Length of distances and weights must be equal.\n"); + return; + } + + for (i = 0; i < nc; i++) { + weight = VECTOR(*weights)[i]; + if (weight > 1.0) { + printf("Weights cannot be >1.0, found %f", weight); + return; + } else if (weight < 0.0) { + printf("Weights cannot be negative, found %f", weight); + return; + } + } + printf("Connectivities from distances seem fine.\n"); + } + return; +} + + +void check_graph_twoclusters(const igraph_matrix_t *layout) { + /* 4 vertices (0-3), 2 articulation points (4-5), 4 vertices (6-9) */ + igraph_real_t xm, ym, zm, dx, dy, dz, dist, distmax; + igraph_int_t ndim = igraph_matrix_ncol(layout); + + int nerr = 0; + + distmax = get_layout_span(layout, 0, 12); + + for (int iclu = 0; iclu < 8; iclu+= 7) { + xm = 0; + ym = 0; + zm = 0; + for (int i = iclu; i < iclu + 4; i++) { + xm += MATRIX(*layout, i, 0); + ym += MATRIX(*layout, i, 1); + if (ndim == 3) { + zm += MATRIX(*layout, i, 2); + } + } + xm /= 4; + ym /= 4; + zm /= 4; + for (int i = iclu; i < iclu + 4; i++) { + dx = MATRIX(*layout, i, 0) - xm; + dy = MATRIX(*layout, i, 1) - ym; + if (ndim == 3) { + dz = MATRIX(*layout, i, 2) - zm; + } + dist = (dx * dx) + (dy * dy); + if (ndim == 3) { + dist += (dz * dz); + } + dist = sqrt(dist); + + if (dist > 0.2 * distmax) { + printf("ERROR: UMAP cluster not compact!\n"); + printf("Cluster %d of 2, vertex %d, dx: %g, dy: %g, dist: %g, distmax: %g, d/dmax: %e\n", 1 + iclu / 7, i, dx, dy, dist, distmax, dist / distmax); + nerr++; + } + } + } + + if (nerr == 0) { + printf("UMAP layout seems fine.\n"); + } else { + printf("UMAP layout inconsistent:\nx\ty\n"); + igraph_matrix_print(layout); + } +} + + +void check_graph_singleton(const igraph_matrix_t *layout) { + igraph_int_t nrows = igraph_matrix_nrow(layout); + igraph_int_t ncols = igraph_matrix_ncol(layout); + igraph_int_t nerr = 0; + + if (nrows != 1) { + printf("Singleton graph layout has %d rows instead of 1.\n", (int)nrows); + nerr++; + } + if (ncols != 2) { + printf("Singleton graph layout has %d cols instead of 2.\n", (int)ncols); + nerr++; + } + if ((fabs(MATRIX(*layout, 0, 0)) > 0.001) || (fabs(MATRIX(*layout, 0, 1)) > 0.001)) { + printf("Singleton graph layout is not (0,0): (%g,%g)\n", MATRIX(*layout, 0, 0), MATRIX(*layout, 0, 1)); + nerr++; + } + if (nerr == 0) { + printf("UMAP layout seems fine.\n"); + } +} + + +int main(void) { + igraph_t graph, empty_graph, singleton_graph; + igraph_vector_t distances, weights; + igraph_matrix_t layout; + igraph_t graph1, graph2, graph3; + igraph_vector_ptr_t graph_ptr; + igraph_vector_int_t subgraph_sizes; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_matrix_init(&layout, 0, 0); + + /* Check passing NULL as distances */ + igraph_ring(&graph, 10, IGRAPH_UNDIRECTED, false, true); + igraph_layout_umap(&graph, &layout, false, NULL, 0.01, 100, true); + igraph_destroy(&graph); + + /* Check simple graphs (empty, singleton) */ + igraph_small(&empty_graph, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&singleton_graph, 1, IGRAPH_UNDIRECTED, -1); + + printf("Check error for negative min_dist.\n"); + CHECK_ERROR(igraph_layout_umap(&empty_graph, &layout, 0, NULL, -0.01, 500, 0), IGRAPH_EINVAL); + + printf("Check error for negative epochs.\n"); + CHECK_ERROR(igraph_layout_umap(&empty_graph, &layout, 0, NULL, 0.01, -1, 0), IGRAPH_EINVAL); + + printf("Check error for seed layout with wrong dimensions.\n"); + CHECK_ERROR(igraph_layout_umap(&empty_graph, &layout, 1, NULL, 0.01, 500, 0), IGRAPH_EINVAL); + + printf("Empty graph:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&empty_graph, &layout, 0, NULL, 0.01, 500, 0) == IGRAPH_SUCCESS); + igraph_matrix_print(&layout); + + printf("Singleton graph:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&singleton_graph, &layout, 0, NULL, 0.01, 500, 0) == IGRAPH_SUCCESS); + check_graph_singleton(&layout); + + printf("Singleton graph, use seed:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&singleton_graph, &layout, 1, NULL, 0.01, 500, 0) == IGRAPH_SUCCESS); + check_graph_singleton(&layout); + + igraph_destroy(&singleton_graph); + igraph_destroy(&empty_graph); + + /* Check a small graph with two main groups of vertices */ + igraph_small(&graph, 12, IGRAPH_UNDIRECTED, + 0,1, 0,2, 0,3, 1,2, 1,3, 2,3, + 3,4, 4,5, 5,6, + 6,7, 7,8, 6,8, 7,9, 6,9, 8,9, 7,10, 8,10, 9,10, 10,11, 9,11, 8,11, 7,11, + -1); + igraph_vector_init_real(&distances, + igraph_ecount(&graph), + 0.1, 0.09, 0.12, 0.09, 0.1, 0.1, + 0.9, 0.9, 0.9, + 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.08, 0.05, 0.1, 0.08, 0.12, 0.09, 0.11 + ); + + printf("layout of two clusters of vertices with 2 articulation points:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&graph, &layout, 0, &distances, 0.01, 500, 0) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + + printf("same graph, different epochs:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&graph, &layout, 0, &distances, 0.0, 5000, 0) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + + printf("Same graph, no distances:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&graph, &layout, 0, NULL, 0.0, 500, 0) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + igraph_matrix_resize(&layout, 0, 0); + + printf("Same graph, 3D layout:\n"); + IGRAPH_ASSERT(igraph_layout_umap_3d(&graph, &layout, 0, NULL, 0.0, 500, 0) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + + printf("Same graph, custom initial layout:\n"); + igraph_layout_random(&graph, &layout); + IGRAPH_ASSERT(igraph_layout_umap(&graph, &layout, 1, NULL, 0.01, 500, 0) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + + printf("Same graph, just compute weights from NULL distances:\n"); + igraph_vector_init(&weights, 0); + igraph_layout_umap_compute_weights(&graph, NULL, &weights); + check_graph_twoclusters_weights(&weights, NULL); + + printf("Same graph, just compute weights from distances:\n"); + igraph_layout_umap_compute_weights(&graph, &distances, &weights); + check_graph_twoclusters_weights(&weights, &distances); + + printf("Same graph, precomputed weights:\n"); + IGRAPH_ASSERT(igraph_layout_umap(&graph, &layout, 0, &weights, 0.01, 500, 1) == IGRAPH_SUCCESS); + check_graph_twoclusters(&layout); + + igraph_matrix_destroy(&layout); + igraph_vector_destroy(&distances); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Check a larger graph with around 150 vertices, split in 3 main clusters */ + printf("Large disjoint union of 3 full graphs:\n"); + igraph_matrix_init(&layout, 0, 0); + igraph_vector_int_init(&subgraph_sizes, 3); + VECTOR(subgraph_sizes)[0] = 10; + VECTOR(subgraph_sizes)[1] = 50; + VECTOR(subgraph_sizes)[2] = 30; + // Make 3 full graphs + igraph_vector_ptr_init(&graph_ptr, 3); + igraph_full(&graph1, VECTOR(subgraph_sizes)[0], 0, 0); + VECTOR(graph_ptr)[0] = &graph1; + igraph_full(&graph2, VECTOR(subgraph_sizes)[1], 0, 0); + VECTOR(graph_ptr)[1] = &graph2; + igraph_full(&graph3, VECTOR(subgraph_sizes)[2], 0, 0); + VECTOR(graph_ptr)[2] = &graph3; + // Get the disjoint union + igraph_disjoint_union_many(&graph, &graph_ptr); + // Call UMAP + IGRAPH_ASSERT(igraph_layout_umap( + &graph, &layout, 0, NULL, 0.0, 50, 0) == IGRAPH_SUCCESS); + // Check the layout, it should have three balls + check_graph_largeunion(&layout, &subgraph_sizes); + + // Destroy data structures + igraph_matrix_destroy(&layout); + igraph_destroy(&graph); + igraph_destroy(&graph3); + igraph_destroy(&graph2); + igraph_destroy(&graph1); + igraph_vector_int_destroy(&subgraph_sizes); + igraph_vector_ptr_destroy(&graph_ptr); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_layout_umap.out b/tests/unit/igraph_layout_umap.out new file mode 100644 index 0000000..9ed2821 --- /dev/null +++ b/tests/unit/igraph_layout_umap.out @@ -0,0 +1,26 @@ +Check error for negative min_dist. +Check error for negative epochs. +Check error for seed layout with wrong dimensions. +Empty graph: +Singleton graph: +UMAP layout seems fine. +Singleton graph, use seed: +UMAP layout seems fine. +layout of two clusters of vertices with 2 articulation points: +UMAP layout seems fine. +same graph, different epochs: +UMAP layout seems fine. +Same graph, no distances: +UMAP layout seems fine. +Same graph, 3D layout: +UMAP layout seems fine. +Same graph, custom initial layout: +UMAP layout seems fine. +Same graph, just compute weights from NULL distances: +Connectivities from NULL distances seem fine. +Same graph, just compute weights from distances: +Connectivities from distances seem fine. +Same graph, precomputed weights: +UMAP layout seems fine. +Large disjoint union of 3 full graphs: +UMAP layout of large graph seems fine. diff --git a/tests/unit/igraph_lcf.c b/tests/unit/igraph_lcf.c new file mode 100644 index 0000000..7bcb3da --- /dev/null +++ b/tests/unit/igraph_lcf.c @@ -0,0 +1,69 @@ +/* + igraph library. + Copyright (C) 2007-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g, g2; + igraph_bool_t iso; + + // Franklin graph: [5, -5]^6x + igraph_lcf_small(&g, 12, 5, -5, 6, 0); + igraph_famous(&g2, "franklin"); + + igraph_isomorphic(&g, &g2, &iso); + IGRAPH_ASSERT(iso); + + igraph_destroy(&g); + igraph_destroy(&g2); + + // [3, -2]^4, n=8 + igraph_lcf_small(&g, 8, 3, -2, 4, 0); + + IGRAPH_ASSERT(igraph_ecount(&g) == 16); + + igraph_destroy(&g); + + // [2, -2]^2, n=2 + igraph_lcf_small(&g, 2, 2, -2, 2, 0); + + IGRAPH_ASSERT(igraph_ecount(&g) == 1); + + igraph_destroy(&g); + + // [2]^2, n=2 + igraph_lcf_small(&g, 2, 2, 2, 0); + + IGRAPH_ASSERT(igraph_ecount(&g) == 1); + + igraph_destroy(&g); + + // Regression test for bug #996 + igraph_lcf_small(&g, 0, 0); + + IGRAPH_ASSERT(igraph_ecount(&g) == 0 && igraph_vcount(&g) == 0); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_le_community_to_membership.c b/tests/unit/igraph_le_community_to_membership.c new file mode 100644 index 0000000..83e0759 --- /dev/null +++ b/tests/unit/igraph_le_community_to_membership.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_vector_int_t *membership, igraph_vector_int_t *csize, igraph_matrix_int_t *mat) { + printf("Membership: "); + igraph_vector_int_print(membership); + printf("Csize: "); + igraph_vector_int_print(csize); + printf("\n"); + igraph_vector_int_destroy(membership); + igraph_matrix_int_destroy(mat); +} + +int main(void) { + igraph_matrix_int_t merges; + igraph_vector_int_t membership; + igraph_vector_int_t csize; + + igraph_vector_int_init_int(&csize, 0); + + printf("One member:\n"); + igraph_vector_int_init_int(&membership, 1, 0); + igraph_matrix_int_init(&merges, 0, 2); + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 0, &membership, &csize) == IGRAPH_SUCCESS); + print_and_destroy(&membership, &csize, &merges); + + printf("Five singleton clusters, one merge:\n"); + igraph_vector_int_init_int(&membership, 5, 0, 1, 2, 3, 4); + { + int elem[] = {1, 3}; + matrix_int_init_int_row_major(&merges, 1, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 1, &membership, &csize) == IGRAPH_SUCCESS); + print_and_destroy(&membership, &csize, &merges); + + printf("Six clusters, two merges:\n"); + igraph_vector_int_init_int(&membership, 12, 0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5); + { + int elem[] = {0,3, 2,5}; + matrix_int_init_int_row_major(&merges, 2, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 2, &membership, &csize) == IGRAPH_SUCCESS); + print_and_destroy(&membership, &csize, &merges); + + + VERIFY_FINALLY_STACK(); + printf("These should fail nicely:\n"); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Five singleton clusters, two merges, but second merge refers to singleton which is already merged.\n"); + igraph_vector_int_init_int(&membership, 5, 0, 1, 2, 3, 4); + { + int elem[] = {1,3, 1,4,}; + matrix_int_init_int_row_major(&merges, 2, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 2, &membership, &csize) == IGRAPH_EINVAL); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + + printf("Negative cluster index.\n"); + igraph_vector_int_init_int(&membership, 5, -1, 0, 1, 2, 3); + { + int elem[] = {1,2, 3,4,}; + matrix_int_init_int_row_major(&merges, 2, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 2, &membership, &csize) == IGRAPH_EINVAL); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + + printf("Skip a cluster index.\n"); + igraph_vector_int_init_int(&membership, 5, 0, 0, 2, 3, 4); + { + int elem[] = {1,2, 3,4,}; + matrix_int_init_int_row_major(&merges, 2, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 2, &membership, &csize) == IGRAPH_EINVAL); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + + printf("Too many steps.\n"); + igraph_vector_int_init_int(&membership, 5, 0, 1, 2, 3, 4); + { + int elem[] = {1,2, 3,4,}; + matrix_int_init_int_row_major(&merges, 2, 2, elem); + } + IGRAPH_ASSERT(igraph_le_community_to_membership(&merges, /*steps*/ 20, &membership, &csize) == IGRAPH_EINVAL); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + + igraph_vector_int_destroy(&csize); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_le_community_to_membership.out b/tests/unit/igraph_le_community_to_membership.out new file mode 100644 index 0000000..90f6849 --- /dev/null +++ b/tests/unit/igraph_le_community_to_membership.out @@ -0,0 +1,17 @@ +One member: +Membership: 0 +Csize: 1 + +Five singleton clusters, one merge: +Membership: 1 0 2 0 3 +Csize: 2 1 1 1 + +Six clusters, two merges: +Membership: 1 1 2 0 0 1 1 3 3 0 0 0 +Csize: 5 4 1 2 + +These should fail nicely: +Five singleton clusters, two merges, but second merge refers to singleton which is already merged. +Negative cluster index. +Skip a cluster index. +Too many steps. diff --git a/tests/unit/igraph_linegraph.c b/tests/unit/igraph_linegraph.c new file mode 100644 index 0000000..662be9b --- /dev/null +++ b/tests/unit/igraph_linegraph.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g_start, g_line, g_test; + igraph_bool_t same; + + /* Undirected */ + igraph_small(&g_start, 7, IGRAPH_UNDIRECTED, + 0, 1, // 0 + 1, 2, // 1 + 1, 3, // 2 + 1, 3, // 3 + 2, 2, // 4 + 2, 4, // 5 + 3, 4, // 6 + 4, 5, // 7 + -1); + IGRAPH_ASSERT(igraph_linegraph(&g_start, &g_line) == IGRAPH_SUCCESS); + igraph_small(&g_test, 8, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 1, 4, 1, 4, 1, 5, 2, 3, 2, 3, + 2, 6, 3, 6, 4, 4, 4, 5, 4, 5, 5, 6, 5, 7, 6, 7, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g_line, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g_start); + igraph_destroy(&g_line); + igraph_destroy(&g_test); + + /* Directed */ + igraph_small(&g_start, 7, IGRAPH_DIRECTED, + 0, 1, // 0 + 1, 2, // 1 + 1, 3, // 2 + 3, 1, // 3 + 2, 2, // 4 + 2, 4, // 5 + 3, 4, // 6 + 4, 5, // 7 + -1); + IGRAPH_ASSERT(igraph_linegraph(&g_start, &g_line) == IGRAPH_SUCCESS); + igraph_small(&g_test, 8, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 4, 1, 5, 2, 3, 2, 6, 3, 1, 3, 2, 4, 4, 4, 5, + 5, 7, 6, 7, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g_line, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g_start); + igraph_destroy(&g_line); + igraph_destroy(&g_test); + + /* No edges */ + igraph_small(&g_start, 7, IGRAPH_DIRECTED, -1); + IGRAPH_ASSERT(igraph_linegraph(&g_start, &g_line) == IGRAPH_SUCCESS); + igraph_small(&g_test, 0, IGRAPH_DIRECTED, -1); + IGRAPH_ASSERT(igraph_is_same_graph(&g_line, &g_test, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + igraph_destroy(&g_start); + igraph_destroy(&g_line); + igraph_destroy(&g_test); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_list_triangles.c b/tests/unit/igraph_list_triangles.c new file mode 100644 index 0000000..1ff325a --- /dev/null +++ b/tests/unit/igraph_list_triangles.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph) { + igraph_vector_int_t result; + igraph_vector_int_init(&result, 0); + IGRAPH_ASSERT(igraph_list_triangles(graph, &result) == IGRAPH_SUCCESS); + print_vector_int(&result); + IGRAPH_ASSERT(igraph_vector_int_size(&result) % 3 == 0); + igraph_vector_int_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_5_full, g_lm; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_full(&g_5_full, 5, 0, IGRAPH_NO_LOOPS); + igraph_small(&g_lm, 6, 1, 0,1, 0,1, 0,2, 0,2, 1,1, 1,2, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + + printf("No vertices:\n"); + call_and_print(&g_0); + + printf("One vertex:\n"); + call_and_print(&g_1); + + printf("Full graph of 5 vertices:\n"); + call_and_print(&g_5_full); + + printf("Graph with loops and multiple edges:\n"); + call_and_print(&g_lm); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_5_full); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_list_triangles.out b/tests/unit/igraph_list_triangles.out new file mode 100644 index 0000000..359c7ab --- /dev/null +++ b/tests/unit/igraph_list_triangles.out @@ -0,0 +1,12 @@ +No vertices: +( ) + +One vertex: +( ) + +Full graph of 5 vertices: +( 0 1 4 0 1 2 0 1 3 0 2 4 0 2 3 0 3 4 1 2 4 1 2 3 1 3 4 2 3 4 ) + +Graph with loops and multiple edges: +( 1 2 0 1 2 3 ) + diff --git a/tests/unit/igraph_local_scan_k_ecount.c b/tests/unit/igraph_local_scan_k_ecount.c new file mode 100644 index 0000000..ed8f69f --- /dev/null +++ b/tests/unit/igraph_local_scan_k_ecount.c @@ -0,0 +1,107 @@ +/* igraph library. Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, int k, igraph_vector_t *weights, igraph_neimode_t mode) { + igraph_vector_t result; + igraph_vector_init(&result, 0); + IGRAPH_ASSERT(igraph_local_scan_k_ecount(graph, k, &result, weights, mode) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_lmu, g_lm, g_lm_nl; + igraph_vector_t weights, result; + + igraph_vector_init_real(&weights, 8, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6); + igraph_vector_init(&result, 0); + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); //undirected + igraph_small(&g_lm_nl, 6, 1, 0,1, 0,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); // no loop + + printf("No vertices:\n"); + call_and_print(&g_0, 2, NULL, IGRAPH_ALL); + + printf("One vertex:\n"); + call_and_print(&g_1, 2, NULL, IGRAPH_ALL); + + printf("Directed disconnected graph with loops and multiple edges, no weights, k = 0, IGRAPH_IN:\n"); + call_and_print(&g_lm, 0, NULL, IGRAPH_IN); + + printf("Same graph, k=1:\n"); + call_and_print(&g_lm, 1, NULL, IGRAPH_IN); + + printf("Same graph, k=1, IGRAPH_ALL:\n"); + call_and_print(&g_lm, 1, NULL, IGRAPH_ALL); + + printf("Same graph, without loops, k=1:\n"); + call_and_print(&g_lm_nl, 1, NULL, IGRAPH_IN); + + printf("Same graph with loop, k=1, undirected:\n"); + call_and_print(&g_lmu, 1, NULL, IGRAPH_IN); + + printf("Same graph, weighted:\n"); + call_and_print(&g_lmu, 1, &weights, IGRAPH_IN); + + printf("Checking if calling igraph_local_scan_1_ecount properly redirects:\n"); + igraph_vector_clear(&result); + printf("Directed, IN: "); + IGRAPH_ASSERT(igraph_local_scan_1_ecount(&g_lm, &result, NULL, IGRAPH_IN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("Directed, ALL: "); + IGRAPH_ASSERT(igraph_local_scan_1_ecount(&g_lm, &result, NULL, IGRAPH_ALL) == IGRAPH_SUCCESS); + print_vector(&result); + printf("Undirected, with weights: "); + IGRAPH_ASSERT(igraph_local_scan_1_ecount(&g_lmu, &result, &weights, IGRAPH_IN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("Same graph, directed, k=2:\n"); + call_and_print(&g_lm, 2, NULL, IGRAPH_IN); + + printf("Same graph, undirected, k=2:\n"); + call_and_print(&g_lmu, 2, NULL, IGRAPH_IN); + + printf("Same graph, weighted:\n"); + call_and_print(&g_lmu, 2, &weights, IGRAPH_IN); + + VERIFY_FINALLY_STACK(); + + printf("Wrong size weights.\n"); + igraph_vector_clear(&weights); + CHECK_ERROR(igraph_local_scan_k_ecount(&g_lmu, 3, &result, &weights, IGRAPH_ALL), IGRAPH_EINVAL); + + printf("Negative k.\n"); + CHECK_ERROR(igraph_local_scan_k_ecount(&g_lmu, -3, &result, NULL, IGRAPH_ALL), IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_lmu); + igraph_destroy(&g_lm); + igraph_destroy(&g_lm_nl); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_local_scan_k_ecount.out b/tests/unit/igraph_local_scan_k_ecount.out new file mode 100644 index 0000000..5cc8fce --- /dev/null +++ b/tests/unit/igraph_local_scan_k_ecount.out @@ -0,0 +1,40 @@ +No vertices: +( ) + +One vertex: +( 0 ) + +Directed disconnected graph with loops and multiple edges, no weights, k = 0, IGRAPH_IN: +( 1 2 1 2 2 0 ) + +Same graph, k=1: +( 2 2 2 3 2 0 ) + +Same graph, k=1, IGRAPH_ALL: +( 4 3 3 5 2 0 ) + +Same graph, without loops, k=1: +( 2 1 2 2 2 0 ) + +Same graph with loop, k=1, undirected: +( 4 3 3 5 2 0 ) + +Same graph, weighted: +( 0.3 0.2 0.7 1.8 1.1 0 ) + +Checking if calling igraph_local_scan_1_ecount properly redirects: +Directed, IN: ( 2 2 2 3 2 0 ) +Directed, ALL: ( 4 3 3 5 2 0 ) +Undirected, with weights: ( 0.3 0.2 0.7 1.8 1.1 0 ) + +Same graph, directed, k=2: +( 2 4 2 6 5 0 ) + +Same graph, undirected, k=2: +( 6 8 8 8 5 0 ) + +Same graph, weighted: +( 0.9 2 2 2 1.8 0 ) + +Wrong size weights. +Negative k. diff --git a/tests/unit/igraph_local_scan_k_ecount_them.c b/tests/unit/igraph_local_scan_k_ecount_them.c new file mode 100644 index 0000000..a89ae11 --- /dev/null +++ b/tests/unit/igraph_local_scan_k_ecount_them.c @@ -0,0 +1,135 @@ +/* igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *us, igraph_t *them, int k, igraph_vector_t *weights, igraph_neimode_t mode) { + igraph_vector_t result; + igraph_vector_init(&result, 0); + IGRAPH_ASSERT(igraph_local_scan_k_ecount_them(us, them, k, &result, weights, mode) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_lmu, g_lm, g_lm_nl, g_6, g_6_1, g_6_full; + igraph_vector_t weights, result; + + igraph_vector_init_real(&weights, 8, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6); + igraph_vector_init(&result, 0); + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_6, 6, 1, -1); + igraph_small(&g_6_1, 6, 1, 0,1, -1); + igraph_full(&g_6_full, 6, 1, 0); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); //undirected + igraph_small(&g_lm_nl, 6, 1, 0,1, 0,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); // no loop + + printf("First some tests where us and them are equal:\n"); + printf("No vertices:\n"); + call_and_print(&g_0, &g_0, 2, NULL, IGRAPH_ALL); + + printf("One vertex:\n"); + call_and_print(&g_1, &g_1, 2, NULL, IGRAPH_ALL); + + printf("Directed disconnected graph with loops and multiple edges, no weights, k = 0, IGRAPH_IN:\n"); + call_and_print(&g_lm, &g_lm, 0, NULL, IGRAPH_IN); + + printf("Same graph, with weights:\n"); + call_and_print(&g_lm, &g_lm, 0, &weights, IGRAPH_IN); + + printf("Same graph, k=1:\n"); + call_and_print(&g_lm, &g_lm, 1, NULL, IGRAPH_IN); + + printf("Same graph, k=1, IGRAPH_ALL:\n"); + call_and_print(&g_lm, &g_lm, 1, NULL, IGRAPH_ALL); + + printf("Same graph, without loops, k=1:\n"); + call_and_print(&g_lm_nl, &g_lm_nl, 1, NULL, IGRAPH_IN); + + printf("Same graph with loop, k=1, undirected:\n"); + call_and_print(&g_lmu, &g_lmu, 1, NULL, IGRAPH_IN); + + printf("Same graph, directed, k=2:\n"); + call_and_print(&g_lm, &g_lm, 2, NULL, IGRAPH_IN); + + printf("Same graph, undirected, k=2:\n"); + call_and_print(&g_lmu, &g_lmu, 2, NULL, IGRAPH_IN); + + printf("Same graph, weighted:\n"); + call_and_print(&g_lmu, &g_lmu, 2, &weights, IGRAPH_IN); + + printf("Now some tests where us and them are not equal:\n"); + printf("Us = same graph, them = edgless, directed, k=1, " + "should show 0, because there are no edges:\n"); + call_and_print(&g_lm, &g_6, 1, NULL, IGRAPH_IN); + + printf("Switched us and them, " + "should show only 1 at the loop:\n"); + call_and_print(&g_6, &g_lm, 1, NULL, IGRAPH_IN); + + printf("Us = same graph, them = only edge from 0 to 1, " + "directed, k=1, IGRAPH_IN, " + "should show edge from 0 to 1:\n"); + call_and_print(&g_lm, &g_6_1, 1, NULL, IGRAPH_IN); + + printf("Switched us and them, " + "should show edge from 0 to 1 and loop:\n"); + call_and_print(&g_6_1, &g_lm, 1, NULL, IGRAPH_IN); + + printf("Us = same graph, them = full graph, " + "directed, k=3, IGRAPH_ALL, " + "should show 4*5=20 edges for the connected part:\n"); + call_and_print(&g_lm, &g_6_full, 3, NULL, IGRAPH_ALL); + + printf("Switched us and them, " + "should show 8 edges for the whole graph:\n"); + call_and_print(&g_6_full, &g_lm, 3, NULL, IGRAPH_ALL); + VERIFY_FINALLY_STACK(); + + printf("Check error handling:\n"); + printf("Wrong size weights.\n"); + igraph_vector_clear(&weights); + CHECK_ERROR(igraph_local_scan_k_ecount_them(&g_lmu, &g_lmu, 3, &result, &weights, IGRAPH_ALL), IGRAPH_EINVAL); + + printf("Negative k.\n"); + CHECK_ERROR(igraph_local_scan_k_ecount_them(&g_lmu, &g_lmu, -3, &result, NULL, IGRAPH_ALL), IGRAPH_EINVAL); + + printf("Number of vertices in us and them not equal.\n"); + CHECK_ERROR(igraph_local_scan_k_ecount_them(&g_lmu, &g_1, 3, &result, NULL, IGRAPH_ALL), IGRAPH_EINVAL); + + printf("Directedness in us and them not equal.\n"); + CHECK_ERROR(igraph_local_scan_k_ecount_them(&g_lmu, &g_lm, 3, &result, NULL, IGRAPH_ALL), IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_lmu); + igraph_destroy(&g_lm); + igraph_destroy(&g_lm_nl); + igraph_destroy(&g_6); + igraph_destroy(&g_6_1); + igraph_destroy(&g_6_full); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_local_scan_k_ecount_them.out b/tests/unit/igraph_local_scan_k_ecount_them.out new file mode 100644 index 0000000..3a9af81 --- /dev/null +++ b/tests/unit/igraph_local_scan_k_ecount_them.out @@ -0,0 +1,58 @@ +First some tests where us and them are equal: +No vertices: +( ) + +One vertex: +( 0 ) + +Directed disconnected graph with loops and multiple edges, no weights, k = 0, IGRAPH_IN: +( 1 2 1 2 2 0 ) + +Same graph, with weights: +( 0.3 0 0 0.6 1.1 0 ) + +Same graph, k=1: +( 2 2 2 3 2 0 ) + +Same graph, k=1, IGRAPH_ALL: +( 4 3 3 5 2 0 ) + +Same graph, without loops, k=1: +( 2 1 2 2 2 0 ) + +Same graph with loop, k=1, undirected: +( 4 3 3 5 2 0 ) + +Same graph, directed, k=2: +( 2 4 2 6 5 0 ) + +Same graph, undirected, k=2: +( 6 8 8 8 5 0 ) + +Same graph, weighted: +( 0.9 2 2 2 1.8 0 ) + +Now some tests where us and them are not equal: +Us = same graph, them = edgless, directed, k=1, should show 0, because there are no edges: +( 0 0 0 0 0 0 ) + +Switched us and them, should show only 1 at the loop: +( 0 1 0 0 0 0 ) + +Us = same graph, them = only edge from 0 to 1, directed, k=1, IGRAPH_IN, should show edge from 0 to 1: +( 0 1 0 0 0 0 ) + +Switched us and them, should show edge from 0 to 1 and loop: +( 0 2 0 0 0 0 ) + +Us = same graph, them = full graph, directed, k=3, IGRAPH_ALL, should show 4*5=20 edges for the connected part: +( 20 20 20 20 20 0 ) + +Switched us and them, should show 8 edges for the whole graph: +( 8 8 8 8 8 8 ) + +Check error handling: +Wrong size weights. +Negative k. +Number of vertices in us and them not equal. +Directedness in us and them not equal. diff --git a/tests/unit/igraph_local_scan_subset_ecount.c b/tests/unit/igraph_local_scan_subset_ecount.c new file mode 100644 index 0000000..df7f26a --- /dev/null +++ b/tests/unit/igraph_local_scan_subset_ecount.c @@ -0,0 +1,101 @@ +/* igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_vector_t *weights, igraph_vector_int_list_t *subsets) { + igraph_vector_t result; + igraph_vector_init(&result, 0); + IGRAPH_ASSERT(igraph_local_scan_subset_ecount(graph, &result, weights, subsets) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_lmu, g_lm; + igraph_vector_t weights, result; + igraph_vector_int_list_t subsets; + igraph_vector_int_t n1; + + igraph_vector_init_real(&weights, 8, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6); + igraph_vector_init(&result, 0); + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); //undirected + + igraph_vector_int_list_init(&subsets, 0); + + + printf("No vertices:\n"); + call_and_print(&g_0, NULL, &subsets); + + printf("one vertex, empty subset:\n"); + igraph_vector_int_init(&n1, 0); + igraph_vector_int_list_push_back(&subsets, &n1); + call_and_print(&g_1, NULL, &subsets); + + printf("Graph with unconnected vertices, loops and multiple edges, empty subsets:\n"); + for (int i = 0; i < 5; i ++) { + igraph_vector_int_init_int(&n1, 0); + igraph_vector_int_list_push_back(&subsets, &n1); + } + call_and_print(&g_lm, NULL, &subsets); + igraph_vector_int_list_clear(&subsets); + + printf("Same graph, every vertex as a subset:\n"); + for (int i = 0; i < 6; i ++) { + igraph_vector_int_init_int(&n1, 1, i); + igraph_vector_int_list_push_back(&subsets, &n1); + } + call_and_print(&g_lm, NULL, &subsets); + igraph_vector_int_list_clear(&subsets); + + printf("Same graph, every vertex and next one as a subset:\n"); + for (int i = 0; i < 6; i ++) { + igraph_vector_int_init_int(&n1, 2, i, (i + 1) % 6); + igraph_vector_int_list_push_back(&subsets, &n1); + } + call_and_print(&g_lm, NULL, &subsets); + + printf("Same situation, but with weights:\n"); + call_and_print(&g_lm, &weights, &subsets); + + printf("Same situation, no weights, but undirected graph:\n"); + call_and_print(&g_lmu, NULL, &subsets); + igraph_vector_int_list_clear(&subsets); + + printf("Same graph, whole graph as subset:\n"); + igraph_vector_int_init_int(&n1, 6, 0, 1, 2, 3, 4, 5); + igraph_vector_int_list_push_back(&subsets, &n1); + call_and_print(&g_lmu, NULL, &subsets); + igraph_vector_int_list_destroy(&subsets); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + igraph_vector_destroy(&weights); + igraph_vector_destroy(&result); + igraph_vector_int_list_destroy(&subsets); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_local_scan_subset_ecount.out b/tests/unit/igraph_local_scan_subset_ecount.out new file mode 100644 index 0000000..6d3efef --- /dev/null +++ b/tests/unit/igraph_local_scan_subset_ecount.out @@ -0,0 +1,24 @@ +No vertices: +( ) + +one vertex, empty subset: +( 0 ) + +Graph with unconnected vertices, loops and multiple edges, empty subsets: +( 0 0 0 0 0 0 ) + +Same graph, every vertex as a subset: +( 0 1 0 0 0 0 ) + +Same graph, every vertex and next one as a subset: +( 2 1 1 2 0 0 ) + +Same situation, but with weights: +( 0 0.1 0.4 1.1 0 0 ) + +Same situation, no weights, but undirected graph: +( 2 1 1 2 0 0 ) + +Same graph, whole graph as subset: +( 8 ) + diff --git a/tests/unit/igraph_local_transitivity.c b/tests/unit/igraph_local_transitivity.c new file mode 100644 index 0000000..b393983 --- /dev/null +++ b/tests/unit/igraph_local_transitivity.c @@ -0,0 +1,245 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +/* Compare the elements of two vectors for equality, handling NaN values. */ +igraph_bool_t vector_equal(const igraph_vector_t *v1, const igraph_vector_t *v2) { + igraph_int_t n1 = igraph_vector_size(v1), n2 = igraph_vector_size(v2); + igraph_int_t i; + + if (n1 != n2) { + return 0; + } + + for (i=0; i < n1; ++i) { + /* Since NaN == NaN compares false, we must handle NaN values early. */ + if (isnan(VECTOR(*v1)[i]) && isnan(VECTOR(*v2)[i])) { + continue; + } + if (VECTOR(*v1)[i] != VECTOR(*v2)[i]) { + return 0; + } + } + + return 1; +} + +/* Compute the average of a vector, ignoring NaN values. */ +igraph_real_t vector_avg(const igraph_vector_t *v) { + igraph_int_t n = igraph_vector_size(v); + igraph_int_t i; + igraph_real_t sum = 0.0, count; + + count = 0; + for (i=0; i < n; ++i) { + if (isnan(VECTOR(*v)[i])) { + continue; + } + sum += VECTOR(*v)[i]; + count += 1; + } + return sum / count; +} + +int main(void) { + + igraph_t g; + igraph_vector_t result1, result2, result3; + igraph_vs_t vertices; + igraph_real_t avg_local; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_init(&result1, 0); + igraph_vector_init(&result2, 0); + + /* igraph_transitivity_local_undirected() uses different code paths for: + * - all vertices + * - some vertices of graphs with >= 100 vertices + * - some vertices of graphs with < 100 vertices + * + * We test that these are consistent. + */ + + /* 100 vertices */ + + igraph_erdos_renyi_game_gnp(&g, 100, 0.1, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_vs_range(&vertices, 0, igraph_vcount(&g)); + + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN); + igraph_transitivity_local_undirected(&g, &result2, vertices, + IGRAPH_TRANSITIVITY_NAN); + + IGRAPH_ASSERT(vector_equal(&result1, &result2)); + + igraph_vs_destroy(&vertices); + igraph_destroy(&g); + + /* 50 vertices */ + + igraph_erdos_renyi_game_gnp(&g, 50, 0.3, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_vs_range(&vertices, 0, igraph_vcount(&g)); + + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), + IGRAPH_TRANSITIVITY_NAN); + igraph_transitivity_local_undirected(&g, &result2, vertices, + IGRAPH_TRANSITIVITY_NAN); + + IGRAPH_ASSERT(vector_equal(&result1, &result2)); + + igraph_vs_destroy(&vertices); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + /* Zachary karate club */ + + printf("Zachary karate club network:\n"); + igraph_famous(&g, "Zachary"); + + printf("IGRAPH_TRANSITIVITY_ZERO:\n"); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_ZERO); + print_vector(&result1); + + printf("IGRAPH_TRANSITIVITY_NAN:\n"); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + + igraph_destroy(&g); + + /* Small graphs */ + + printf("\nNull graph:\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_NAN); + IGRAPH_ASSERT(isnan(avg_local)); + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_ZERO); + IGRAPH_ASSERT(avg_local == 0); + igraph_destroy(&g); + + printf("\nSingleton graph:\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_NAN); + IGRAPH_ASSERT(isnan(avg_local)); + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_ZERO); + IGRAPH_ASSERT(avg_local == 0); + igraph_destroy(&g); + + printf("\nTwo connected vertices:\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + igraph_destroy(&g); + + printf("\nTriangle:\n"); + igraph_full(&g, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + igraph_destroy(&g); + + printf("\nTwo-star:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,2, 0,1, -1); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + igraph_destroy(&g); + + /* Multigraph */ + + printf("\nDirected and multigraphs:\n"); + + igraph_small(&g, 20, IGRAPH_DIRECTED, + 15, 12, 12, 10, 15, 0, 11, 10, 2, 8, 8, 6, 13, 17, 10, 10, 17, 2, 14, + 0, 16, 13, 14, 14, 0, 5, 6, 4, 0, 9, 0, 6, 10, 9, 16, 4, 14, 5, 17, + 15, 14, 9, 17, 17, 1, 4, 10, 16, 7, 0, 11, 12, 6, 13, 2, 17, 4, 0, 0, + 14, 4, 0, 6, 16, 16, 14, 13, 13, 12, 11, 3, 11, 11, 3, 6, 7, 4, 14, + 10, 8, 13, 7, 14, 2, 5, 2, 0, 14, 3, 15, 5, 5, 7, 2, 14, 15, 5, 10, + 10, 16, 7, 9, 14, 0, 15, 7, 13, 1, 15, 1, 4, 5, 4, 6, 16, 13, 6, 17, + 8, 6, 9, 3, 8, 6, 6, 14, 11, 14, 6, 10, 10, 5, 1, 0, 16, 17, 9, 1, 5, + 0, 5, 15, 8, 0, 0, 8, 5, 3, 9, 4, 13, 12, 11, 0, 11, 0, 10, 6, 4, 13, + 8, 9, 11, 11, 3, 16, 1, 2, 16, 0, 9, 8, 3, 8, 8, 7, 12, 10, 9, 3, 13, + 5, 3, 9, 6, 2, 11, 10, 1, 16, 0, 2, 10, 17, 16, 8, 11, 5, 13, 0, 19, 19, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + -1); + + igraph_vs_range(&vertices, 0, igraph_vcount(&g)); + + printf("\nDirected multi:\n"); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + + igraph_vector_init_copy(&result3, &result1); + + igraph_transitivity_local_undirected(&g, &result2, vertices, IGRAPH_TRANSITIVITY_NAN); + print_vector(&result2); + IGRAPH_ASSERT(vector_equal(&result2, &result3)); + + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_NAN); + printf("Average: %.10g == %.10g == %.10g\n", avg_local, vector_avg(&result1), vector_avg(&result2)); + IGRAPH_ASSERT(fabs(avg_local - vector_avg(&result1)) < 1e-14); + + printf("\nUndirected multi:\n"); + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_COLLAPSE, NULL); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + IGRAPH_ASSERT(vector_equal(&result1, &result3)); + igraph_transitivity_local_undirected(&g, &result2, vertices, IGRAPH_TRANSITIVITY_NAN); + print_vector(&result2); + IGRAPH_ASSERT(vector_equal(&result2, &result3)); + + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_NAN); + printf("Average: %.10g == %.10g == %.10g\n", avg_local, vector_avg(&result1), vector_avg(&result2)); + IGRAPH_ASSERT(fabs(avg_local - vector_avg(&result1)) < 1e-14); + + printf("\nUndirected simple:\n"); + igraph_simplify(&g, true, true, NULL); + igraph_transitivity_local_undirected(&g, &result1, igraph_vss_all(), IGRAPH_TRANSITIVITY_NAN); + print_vector(&result1); + IGRAPH_ASSERT(vector_equal(&result1, &result3)); + igraph_transitivity_local_undirected(&g, &result2, vertices, IGRAPH_TRANSITIVITY_NAN); + print_vector(&result2); + IGRAPH_ASSERT(vector_equal(&result2, &result3)); + + igraph_transitivity_avglocal_undirected(&g, &avg_local, IGRAPH_TRANSITIVITY_NAN); + printf("Average: %.10g == %.10g == %.10g\n", avg_local, vector_avg(&result1), vector_avg(&result2)); + IGRAPH_ASSERT(fabs(avg_local - vector_avg(&result1)) < 1e-14); + + igraph_vector_destroy(&result3); + igraph_vector_destroy(&result2); + igraph_vector_destroy(&result1); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_local_transitivity.out b/tests/unit/igraph_local_transitivity.out new file mode 100644 index 0000000..b484061 --- /dev/null +++ b/tests/unit/igraph_local_transitivity.out @@ -0,0 +1,37 @@ +Zachary karate club network: +IGRAPH_TRANSITIVITY_ZERO: +( 0.15 0.333333 0.244444 0.666667 0.666667 0.5 0.5 1 0.5 0 0.666667 0 1 0.6 1 1 1 1 1 0.333333 1 1 1 0.4 0.333333 0.333333 1 0.166667 0.333333 0.666667 0.5 0.2 0.19697 0.110294 ) +IGRAPH_TRANSITIVITY_NAN: +( 0.15 0.333333 0.244444 0.666667 0.666667 0.5 0.5 1 0.5 0 0.666667 NaN 1 0.6 1 1 1 1 1 0.333333 1 1 1 0.4 0.333333 0.333333 1 0.166667 0.333333 0.666667 0.5 0.2 0.19697 0.110294 ) + +Null graph: +( ) + +Singleton graph: +( NaN ) + +Two connected vertices: +( NaN NaN ) + +Triangle: +( 1 1 1 ) + +Two-star: +( 0 NaN NaN ) + +Directed and multigraphs: + +Directed multi: +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +Average: 0.4126407543 == 0.4126407543 == 0.4126407543 + +Undirected multi: +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +Average: 0.4126407543 == 0.4126407543 == 0.4126407543 + +Undirected simple: +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +( 0.474359 0.47619 0.428571 0.266667 0.642857 0.388889 0.533333 0.52381 0.535714 0.357143 0.285714 0.4 0.166667 0.416667 0.472222 0.214286 0.444444 0.4 NaN NaN ) +Average: 0.4126407543 == 0.4126407543 == 0.4126407543 diff --git a/tests/unit/igraph_maxflow.c b/tests/unit/igraph_maxflow.c new file mode 100644 index 0000000..58de276 --- /dev/null +++ b/tests/unit/igraph_maxflow.c @@ -0,0 +1,240 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int check_flow(int errorinc, + const igraph_t *graph, igraph_real_t flow_value, + const igraph_vector_t *flow, const igraph_vector_int_t *cut, + const igraph_vector_int_t *partition, + const igraph_vector_int_t *partition2, + igraph_int_t source, igraph_int_t target, + const igraph_vector_t *capacity, + igraph_bool_t print) { + + igraph_int_t i, n = igraph_vcount(graph), m = igraph_ecount(graph); + igraph_int_t nc = igraph_vector_int_size(cut); + igraph_vector_int_t inedges, outedges; + igraph_bool_t directed = igraph_is_directed(graph); + igraph_real_t cutsize; + igraph_t graph_copy; + igraph_matrix_t sp; + igraph_vector_int_t cut_int; + + igraph_vector_int_init(&cut_int, 0); + + if (print) { + printf("flow value: %g\n", (double) flow_value); + printf("flow: "); + igraph_vector_print(flow); + printf("first partition: "); + igraph_vector_int_print(partition); + printf("second partition: "); + igraph_vector_int_print(partition2); + printf("edges in the cut: "); + for (i = 0; i < nc; i++) { + igraph_int_t edge = VECTOR(*cut)[i]; + igraph_int_t from = IGRAPH_FROM(graph, edge); + igraph_int_t to = IGRAPH_TO (graph, edge); + if (!directed && from > to) { + igraph_int_t tmp = from; + from = to; + to = tmp; + } + printf("%" IGRAPH_PRId "-%" IGRAPH_PRId " (%g), ", from, to, VECTOR(*capacity)[edge]); + } + printf("\n"); + } + fflush(stdout); + + /* Always less than the capacity */ + for (i = 0; i < m; i++) { + if (VECTOR(*flow)[i] > VECTOR(*capacity)[i]) { + return errorinc + 3; + } + } + + /* What comes in goes out, but only in directed graphs, + there is no in and out in undirected ones... + */ + if (igraph_is_directed(graph)) { + igraph_vector_int_init(&inedges, 0); + igraph_vector_int_init(&outedges, 0); + + for (i = 0; i < n; i++) { + igraph_int_t n1, n2, j; + igraph_real_t in_flow = 0.0, out_flow = 0.0; + igraph_incident(graph, &inedges, i, IGRAPH_IN, IGRAPH_LOOPS_ONCE); + igraph_incident(graph, &outedges, i, IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + n1 = igraph_vector_int_size(&inedges); + n2 = igraph_vector_int_size(&outedges); + for (j = 0; j < n1; j++) { + igraph_int_t e = VECTOR(inedges)[j]; + in_flow += VECTOR(*flow)[e]; + } + for (j = 0; j < n2; j++) { + igraph_int_t e = VECTOR(outedges)[j]; + out_flow += VECTOR(*flow)[e]; + } + if (i == source) { + if (in_flow > 0) { + return errorinc + 4; + } + if (out_flow != flow_value) { + return errorinc + 5; + } + } else if (i == target) { + if (out_flow > 0) { + return errorinc + 6; + } + if (in_flow != flow_value) { + return errorinc + 7; + } + + } else { + if (in_flow != out_flow) { + return errorinc + 8; + } + } + } + + igraph_vector_int_destroy(&inedges); + igraph_vector_int_destroy(&outedges); + } + + /* Check the minimum cut size*/ + for (i = 0, cutsize = 0.0; i < nc; i++) { + igraph_int_t edge = VECTOR(*cut)[i]; + cutsize += VECTOR(*capacity)[edge]; + } + if (fabs(cutsize - flow_value) > 1e-14) { + return errorinc + 9; + } + + /* Check that the cut indeed cuts */ + igraph_copy(&graph_copy, graph); + + n = igraph_vector_int_size(cut); + igraph_vector_int_resize(&cut_int, n); + for (i = 0; i < n; i++) { + VECTOR(cut_int)[i] = VECTOR(*cut)[i]; + } + igraph_delete_edges(&graph_copy, igraph_ess_vector(&cut_int)); + igraph_matrix_init(&sp, 1, 1); + igraph_distances(&graph_copy, NULL, &sp, /*from=*/ igraph_vss_1(source), + /*to=*/ igraph_vss_1(target), IGRAPH_OUT); + if (MATRIX(sp, 0, 0) != IGRAPH_INFINITY) { + return errorinc + 10; + } + igraph_matrix_destroy(&sp); + igraph_destroy(&graph_copy); + + igraph_vector_int_destroy(&cut_int); + + return 0; +} + +int main(void) { + + igraph_t g; + igraph_real_t flow_value; + igraph_vector_int_t cut; + igraph_vector_t capacity; + igraph_vector_int_t partition, partition2; + igraph_vector_t flow; + igraph_int_t i, n; + igraph_int_t source, target; + FILE *infile; + igraph_real_t flow_value2 = 0.0; + int check; + igraph_maxflow_stats_t stats; + + igraph_vector_init(&capacity, 0); + + /***************/ + infile = fopen("ak-4102.max", "r"); + igraph_read_graph_dimacs_flow( + &g, infile, 0, 0, &source, &target, &capacity, IGRAPH_DIRECTED + ); + fclose(infile); + + igraph_vector_int_init(&cut, 0); + igraph_vector_int_init(&partition, 0); + igraph_vector_int_init(&partition2, 0); + igraph_vector_init(&flow, 0); + + igraph_maxflow(&g, &flow_value, &flow, &cut, &partition, + &partition2, source, target, &capacity, &stats); + + if (flow_value != 8207) { + return 1; + } + + n = igraph_vector_int_size(&cut); + for (i = 0; i < n; i++) { + igraph_int_t e = VECTOR(cut)[i]; + flow_value2 += VECTOR(capacity)[e]; + } + if (flow_value != flow_value2) { + return 2; + } + + /* Check the flow */ + if ( (check = check_flow(0, &g, flow_value, &flow, &cut, &partition, + &partition2, source, target, &capacity, + /*print=*/ 0))) { + return check; + } + + igraph_destroy(&g); + igraph_vector_destroy(&capacity); + igraph_vector_int_destroy(&cut); + igraph_vector_int_destroy(&partition); + igraph_vector_int_destroy(&partition2); + igraph_vector_destroy(&flow); + + /* ------------------------------------- */ + + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 3, -1); + igraph_vector_init_int_end(&capacity, -1, 4, 2, 10, 2, 2, -1); + igraph_vector_int_init(&cut, 0); + igraph_vector_int_init(&partition, 0); + igraph_vector_int_init(&partition2, 0); + igraph_vector_init(&flow, 0); + + igraph_maxflow(&g, &flow_value, &flow, &cut, &partition, &partition2, + /*source=*/ 0, /*target=*/ 3, &capacity, &stats); + + if ( (check = check_flow(20, &g, flow_value, &flow, &cut, &partition, + &partition2, 0, 3, &capacity, + /*print=*/ 1))) { + return check; + } + + igraph_vector_int_destroy(&cut); + igraph_vector_int_destroy(&partition2); + igraph_vector_int_destroy(&partition); + igraph_vector_destroy(&capacity); + igraph_vector_destroy(&flow); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_maxflow.out b/tests/unit/igraph_maxflow.out new file mode 100644 index 0000000..06da92d --- /dev/null +++ b/tests/unit/igraph_maxflow.out @@ -0,0 +1,5 @@ +flow value: 4 +flow: 4 0 2 2 2 +first partition: 0 1 2 +second partition: 3 +edges in the cut: 1-3 (2), 2-3 (2), diff --git a/tests/unit/igraph_maximal_cliques.c b/tests/unit/igraph_maximal_cliques.c new file mode 100644 index 0000000..ac746a4 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques.c @@ -0,0 +1,159 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +#define NODES 1000 +#define CLIQUE_SIZE 10 +#define NO_CLIQUES 10 + +void permutation(igraph_vector_int_t *vec) { + igraph_int_t i, r, tmp; + for (i = 0; i < CLIQUE_SIZE; i++) { + r = RNG_INTEGER(0, NODES - 1); + tmp = VECTOR(*vec)[i]; + VECTOR(*vec)[i] = VECTOR(*vec)[r]; + VECTOR(*vec)[r] = tmp; + } +} + +int sort_cmp(const igraph_vector_int_t *a, const igraph_vector_int_t *b) { + igraph_int_t i, alen = igraph_vector_int_size(a), blen = igraph_vector_int_size(b); + if (alen != blen) { + return (alen < blen) - (alen > blen); + } + for (i = 0; i < alen; i++) { + igraph_int_t ea = VECTOR(*a)[i], eb = VECTOR(*b)[i]; + if (ea != eb) { + return (ea > eb) - (ea < eb); + } + } + return 0; +} + +void sort_cliques(igraph_vector_int_list_t *cliques) { + igraph_int_t i, n = igraph_vector_int_list_size(cliques); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(cliques, i); + igraph_vector_int_sort(v); + } + igraph_vector_int_list_sort(cliques, sort_cmp); +} + +void print_cliques(igraph_vector_int_list_t *cliques) { + igraph_int_t i; + sort_cliques(cliques); + for (i = 0; i < igraph_vector_int_list_size(cliques); i++) { + igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(cliques, i); + igraph_vector_int_print(v); + } +} + +int main(void) { + + igraph_t g, g2, cli; + igraph_vector_int_t perm, inv_perm; + igraph_vector_int_list_t cliques; + igraph_int_t no; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Create a graph that has a random component, plus a number of + relatively small cliques */ + + igraph_vector_int_init_range(&perm, 0, NODES); + igraph_vector_int_init(&inv_perm, NODES); + igraph_erdos_renyi_game_gnm(&g, NODES, NODES, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_full(&cli, CLIQUE_SIZE, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + for (i = 0; i < NO_CLIQUES; i++) { + /* Generate a permutation and then invert it for sake of compatibility + * with earlier tests when igraph_permute_vertices() took the inverse + * permutation */ + permutation(&perm); + igraph_invert_permutation(&perm, &inv_perm); + + /* Permute vertices of g */ + igraph_permute_vertices(&g, &g2, &inv_perm); + igraph_destroy(&g); + g = g2; + + /* Add a clique */ + igraph_union(&g2, &g, &cli, /*edge_map1=*/ NULL, /*edge_map2=*/ NULL); + igraph_destroy(&g); + g = g2; + } + igraph_simplify(&g, /*remove_multiple=*/ true, /*remove_loop=*/ false, /*edge_comb=*/ NULL); + + igraph_vector_int_destroy(&inv_perm); + igraph_vector_int_destroy(&perm); + igraph_destroy(&cli); + + /* Find the maximal cliques */ + + igraph_vector_int_list_init(&cliques, 0); + igraph_maximal_cliques(&g, &cliques, /*min_size=*/ 3, + /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + igraph_maximal_cliques_count(&g, &no, /*min_size=*/ 3, + /*max_size=*/ 0 /*no limit*/); + + if (no != igraph_vector_int_list_size(&cliques)) { + return 1; + } + + /* Print them */ + + print_cliques(&cliques); + + /* Clean up */ + + igraph_vector_int_list_destroy(&cliques); + igraph_destroy(&g); + + /* Build a triangle with a loop (thanks to Emmanuel Navarro) */ + + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, 0, 0, -1); + + /* Find the maximal cliques */ + + igraph_vector_int_list_init(&cliques, 0); + igraph_maximal_cliques(&g, &cliques, /*min_size=*/ 3, + /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + igraph_maximal_cliques_count(&g, &no, /*min_size=*/ 3, + /*max_size=*/ 0 /*no limit*/); + + if (no != igraph_vector_int_list_size(&cliques)) { + return 2; + } + + /* Print them */ + + print_cliques(&cliques); + + /* Clean up */ + + igraph_vector_int_list_destroy(&cliques); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_maximal_cliques.out b/tests/unit/igraph_maximal_cliques.out new file mode 100644 index 0000000..53d6187 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques.out @@ -0,0 +1,15 @@ +0 1 2 3 4 5 6 7 8 9 +0 1 2 5 6 7 9 64 209 582 +8 19 76 89 253 488 560 830 841 857 +12 65 127 209 250 398 485 587 815 901 +19 76 89 253 488 560 830 841 857 892 +33 152 181 296 343 801 896 903 967 988 +33 152 296 343 387 477 801 896 903 967 +62 65 146 469 585 630 849 901 979 995 +178 488 523 560 768 771 821 882 890 953 +488 560 841 890 +229 586 948 +279 547 863 +291 408 580 +466 552 925 +0 1 2 diff --git a/tests/unit/igraph_maximal_cliques2.c b/tests/unit/igraph_maximal_cliques2.c new file mode 100644 index 0000000..23e255f --- /dev/null +++ b/tests/unit/igraph_maximal_cliques2.c @@ -0,0 +1,82 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void sort_cliques(igraph_vector_int_list_t *cliques) { + igraph_int_t i, n = igraph_vector_int_list_size(cliques); + for (i = 0; i < n; i++) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(cliques, i)); + } + igraph_vector_int_list_sort(cliques, igraph_vector_int_lex_cmp); +} + +void print_and_destroy(igraph_vector_int_list_t *cliques) { + sort_cliques(cliques); + print_vector_int_list(cliques); + igraph_vector_int_list_destroy(cliques); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t cliques; + igraph_int_t no; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_ring(&graph, /*n=*/ 10, /*directed=*/ 0, + /*mutual=*/ 0, /*circular=*/ 1); + igraph_vector_int_list_init(&cliques, 0); + + igraph_maximal_cliques(&graph, &cliques, /*min_size=*/ IGRAPH_UNLIMITED, + /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + igraph_maximal_cliques_count(&graph, &no, /*min_size=*/ 0, + /*max_size=*/ 0 /*no limit*/); + IGRAPH_ASSERT(no == igraph_vector_int_list_size(&cliques)); + + print_and_destroy(&cliques); + igraph_destroy(&graph); + + printf("---\n"); + /* ----------------------------------------------------------- */ + + igraph_erdos_renyi_game_gnp(&graph, 50, 0.5, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_vector_int_list_init(&cliques, 0); + + igraph_maximal_cliques(&graph, &cliques, /*min_size=*/ 8, + /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + igraph_maximal_cliques_count(&graph, &no, /*min_size=*/ 8, + /*max_size=*/ 0 /*no limit*/); + IGRAPH_ASSERT(no == igraph_vector_int_list_size(&cliques)); + + print_and_destroy(&cliques); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_maximal_cliques2.out b/tests/unit/igraph_maximal_cliques2.out new file mode 100644 index 0000000..8be32cd --- /dev/null +++ b/tests/unit/igraph_maximal_cliques2.out @@ -0,0 +1,20 @@ +{ + 0: ( 0 1 ) + 1: ( 0 9 ) + 2: ( 1 2 ) + 3: ( 2 3 ) + 4: ( 3 4 ) + 5: ( 4 5 ) + 6: ( 5 6 ) + 7: ( 6 7 ) + 8: ( 7 8 ) + 9: ( 8 9 ) +} +--- +{ + 0: ( 6 15 22 23 27 33 40 44 ) + 1: ( 6 22 23 27 33 40 44 48 ) + 2: ( 13 15 22 24 27 33 40 44 ) + 3: ( 13 15 22 24 33 36 38 42 ) + 4: ( 15 22 24 29 33 36 38 42 ) +} diff --git a/tests/unit/igraph_maximal_cliques3.c b/tests/unit/igraph_maximal_cliques3.c new file mode 100644 index 0000000..7570e92 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques3.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void sort_cliques(igraph_vector_int_list_t *cliques) { + igraph_int_t i, n = igraph_vector_int_list_size(cliques); + for (i = 0; i < n; i++) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(cliques, i)); + } + igraph_vector_int_list_sort(cliques, igraph_vector_int_lex_cmp); +} + +void print_and_destroy(igraph_vector_int_list_t *cliques) { + sort_cliques(cliques); + print_vector_int_list(cliques); + igraph_vector_int_list_destroy(cliques); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t cliques; + + igraph_rng_seed(igraph_rng_default(), 41); + igraph_erdos_renyi_game_gnp(&graph, 100, 0.7, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_vector_int_list_init(&cliques, 0); + + igraph_maximal_cliques(&graph, &cliques, /*min_size=*/ 15, + /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + + print_and_destroy(&cliques); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_maximal_cliques3.out b/tests/unit/igraph_maximal_cliques3.out new file mode 100644 index 0000000..d4fd9b0 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques3.out @@ -0,0 +1,6 @@ +{ + 0: ( 1 6 28 29 35 45 51 58 65 69 78 79 81 92 98 ) + 1: ( 1 6 28 29 35 49 51 58 65 69 78 79 81 92 98 ) + 2: ( 1 9 19 27 30 38 39 41 61 68 83 86 94 96 98 ) + 3: ( 7 12 15 19 23 47 64 75 80 85 87 92 94 95 98 ) +} diff --git a/tests/unit/igraph_maximal_cliques4.c b/tests/unit/igraph_maximal_cliques4.c new file mode 100644 index 0000000..7895cff --- /dev/null +++ b/tests/unit/igraph_maximal_cliques4.c @@ -0,0 +1,92 @@ +/* + igraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void sort_cliques(igraph_vector_int_list_t *cliques) { + igraph_int_t i, n = igraph_vector_int_list_size(cliques); + for (i = 0; i < n; i++) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(cliques, i)); + } + igraph_vector_int_list_sort(cliques, igraph_vector_int_lex_cmp); +} + +void print_and_destroy(igraph_vector_int_list_t *cliques) { + sort_cliques(cliques); + print_vector_int_list(cliques); + igraph_vector_int_list_destroy(cliques); +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t cliques, cl1, cl2; + igraph_vector_int_t v1, v2; + igraph_int_t n, n1, n2; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_erdos_renyi_game_gnp(&graph, /*n=*/ 100, /*p=*/ 0.5, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_vector_int_list_init(&cliques, 0); + igraph_vector_int_list_init(&cl1, 0); + igraph_vector_int_list_init(&cl2, 0); + + igraph_maximal_cliques_subset(&graph, /*subset=*/ 0, + &cliques, &n, /*outfile=*/ 0, + /*min_size=*/ 9, /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + + igraph_vector_int_init_range(&v1, 0, 13); + igraph_vector_int_init_range(&v2, 13, 100); + igraph_maximal_cliques_subset(&graph, &v1, &cl1, &n1, /*outfile=*/ 0, + /*min_size=*/ 9, /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + igraph_maximal_cliques_subset(&graph, &v2, &cl2, &n2, /*outfile=*/ 0, + /*min_size=*/ 9, /*max_size=*/ IGRAPH_UNLIMITED, + IGRAPH_UNLIMITED); + + igraph_vector_int_destroy(&v1); + igraph_vector_int_destroy(&v2); + + if (n1 + n2 != n) { + return 1; + } + if (n1 != igraph_vector_int_list_size(&cl1)) { + return 2; + } + if (n2 != igraph_vector_int_list_size(&cl2)) { + return 3; + } + + print_and_destroy(&cliques); + printf("---\n"); + print_and_destroy(&cl1); + printf("+\n"); + print_and_destroy(&cl2); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_maximal_cliques4.out b/tests/unit/igraph_maximal_cliques4.out new file mode 100644 index 0000000..9780dd9 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques4.out @@ -0,0 +1,108 @@ +{ + 0: ( 1 22 27 29 51 57 58 59 95 ) + 1: ( 1 22 29 51 52 57 58 59 95 ) + 2: ( 6 11 14 15 27 49 67 87 88 ) + 3: ( 6 11 14 15 49 67 87 88 98 ) + 4: ( 6 11 14 27 48 49 73 87 88 ) + 5: ( 6 11 14 27 49 67 73 87 88 ) + 6: ( 6 14 22 23 27 44 48 51 80 ) + 7: ( 6 14 22 23 27 48 51 80 95 ) + 8: ( 6 14 22 23 44 48 51 80 84 ) + 9: ( 6 14 22 23 48 51 80 84 95 ) + 10: ( 6 14 23 27 48 49 51 80 95 ) + 11: ( 11 14 27 48 49 73 82 87 88 ) + 12: ( 11 14 27 49 67 73 82 87 88 ) + 13: ( 11 25 27 48 49 73 82 87 88 ) + 14: ( 11 25 27 49 67 73 82 87 88 ) + 15: ( 14 27 48 49 51 57 80 92 95 ) + 16: ( 14 27 48 49 57 62 80 92 95 ) + 17: ( 18 22 24 27 29 51 58 77 80 ) + 18: ( 18 22 24 27 51 58 77 80 99 ) + 19: ( 18 22 24 29 51 58 63 77 80 ) + 20: ( 18 22 24 45 51 56 63 77 80 ) + 21: ( 18 22 24 45 51 56 77 80 99 ) + 22: ( 18 22 24 45 51 58 63 77 80 ) + 23: ( 18 22 24 45 51 58 77 80 99 ) + 24: ( 18 22 24 45 56 77 80 85 99 ) + 25: ( 20 31 52 56 57 59 62 79 80 ) + 26: ( 22 23 33 40 43 44 48 62 84 ) + 27: ( 22 24 27 29 44 51 58 77 80 ) + 28: ( 22 24 27 29 51 58 59 80 95 ) + 29: ( 22 24 27 29 51 58 77 80 95 ) + 30: ( 22 24 27 44 51 58 77 80 99 ) + 31: ( 22 24 29 44 51 58 63 77 80 ) + 32: ( 22 24 29 51 52 53 58 59 80 95 ) + 33: ( 22 24 29 51 52 58 63 80 95 ) + 34: ( 22 24 29 51 58 63 77 80 95 ) + 35: ( 22 24 29 52 53 59 80 95 96 ) + 36: ( 22 24 44 51 56 63 68 77 80 ) + 37: ( 22 24 45 51 56 63 68 77 80 ) + 38: ( 22 24 45 51 58 63 77 80 95 ) + 39: ( 22 24 45 51 63 68 77 80 95 ) + 40: ( 22 27 29 51 57 58 59 80 95 ) + 41: ( 22 27 29 57 59 62 80 95 96 ) + 42: ( 22 27 29 57 62 76 80 95 96 ) + 43: ( 22 29 51 52 57 58 59 80 95 ) + 44: ( 22 29 52 57 59 62 80 95 96 ) + 45: ( 24 29 52 53 59 79 80 95 96 ) + 46: ( 25 27 48 49 61 73 82 88 92 ) + 47: ( 25 27 49 61 73 76 82 89 92 ) + 48: ( 25 27 49 67 73 76 82 89 92 ) + 49: ( 29 52 57 59 62 79 80 95 96 ) +} +--- +{ + 0: ( 6 11 14 15 27 49 67 87 88 ) + 1: ( 6 11 14 15 49 67 87 88 98 ) + 2: ( 6 11 14 27 48 49 73 87 88 ) + 3: ( 6 11 14 27 49 67 73 87 88 ) + 4: ( 6 14 22 23 27 44 48 51 80 ) + 5: ( 6 14 22 23 27 48 51 80 95 ) + 6: ( 6 14 22 23 44 48 51 80 84 ) + 7: ( 6 14 22 23 48 51 80 84 95 ) + 8: ( 6 14 23 27 48 49 51 80 95 ) + 9: ( 11 14 27 48 49 73 82 87 88 ) + 10: ( 11 14 27 49 67 73 82 87 88 ) + 11: ( 11 25 27 48 49 73 82 87 88 ) + 12: ( 11 25 27 49 67 73 82 87 88 ) + 13: ( 14 27 48 49 51 57 80 92 95 ) + 14: ( 14 27 48 49 57 62 80 92 95 ) +} ++ +{ + 0: ( 1 22 27 29 51 57 58 59 95 ) + 1: ( 1 22 29 51 52 57 58 59 95 ) + 2: ( 18 22 24 27 29 51 58 77 80 ) + 3: ( 18 22 24 27 51 58 77 80 99 ) + 4: ( 18 22 24 29 51 58 63 77 80 ) + 5: ( 18 22 24 45 51 56 63 77 80 ) + 6: ( 18 22 24 45 51 56 77 80 99 ) + 7: ( 18 22 24 45 51 58 63 77 80 ) + 8: ( 18 22 24 45 51 58 77 80 99 ) + 9: ( 18 22 24 45 56 77 80 85 99 ) + 10: ( 20 31 52 56 57 59 62 79 80 ) + 11: ( 22 23 33 40 43 44 48 62 84 ) + 12: ( 22 24 27 29 44 51 58 77 80 ) + 13: ( 22 24 27 29 51 58 59 80 95 ) + 14: ( 22 24 27 29 51 58 77 80 95 ) + 15: ( 22 24 27 44 51 58 77 80 99 ) + 16: ( 22 24 29 44 51 58 63 77 80 ) + 17: ( 22 24 29 51 52 53 58 59 80 95 ) + 18: ( 22 24 29 51 52 58 63 80 95 ) + 19: ( 22 24 29 51 58 63 77 80 95 ) + 20: ( 22 24 29 52 53 59 80 95 96 ) + 21: ( 22 24 44 51 56 63 68 77 80 ) + 22: ( 22 24 45 51 56 63 68 77 80 ) + 23: ( 22 24 45 51 58 63 77 80 95 ) + 24: ( 22 24 45 51 63 68 77 80 95 ) + 25: ( 22 27 29 51 57 58 59 80 95 ) + 26: ( 22 27 29 57 59 62 80 95 96 ) + 27: ( 22 27 29 57 62 76 80 95 96 ) + 28: ( 22 29 51 52 57 58 59 80 95 ) + 29: ( 22 29 52 57 59 62 80 95 96 ) + 30: ( 24 29 52 53 59 79 80 95 96 ) + 31: ( 25 27 48 49 61 73 82 88 92 ) + 32: ( 25 27 49 61 73 76 82 89 92 ) + 33: ( 25 27 49 67 73 76 82 89 92 ) + 34: ( 29 52 57 59 62 79 80 95 96 ) +} diff --git a/tests/unit/igraph_maximal_cliques_file.c b/tests/unit/igraph_maximal_cliques_file.c new file mode 100644 index 0000000..1d8d481 --- /dev/null +++ b/tests/unit/igraph_maximal_cliques_file.c @@ -0,0 +1,45 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_empty, g_lm; + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_maximal_cliques_file(&g_empty, stdout, /*min*/ 0, /*max*/ 0, IGRAPH_UNLIMITED) == IGRAPH_SUCCESS); + + printf("\nGraph with loops and multiple edges:\n"); + IGRAPH_ASSERT(igraph_maximal_cliques_file(&g_lm, stdout, /*min*/ 0, /*max*/ 0, IGRAPH_UNLIMITED) == IGRAPH_SUCCESS); + + printf("\nGraph with loops and multiple edges, minimum clique size 10:\n"); + IGRAPH_ASSERT(igraph_maximal_cliques_file(&g_lm, stdout, /*min*/ 10, /*max*/ 0, IGRAPH_UNLIMITED) == IGRAPH_SUCCESS); + + printf("\nGraph with loops and multiple edges, maximum clique size 2:\n"); + IGRAPH_ASSERT(igraph_maximal_cliques_file(&g_lm, stdout, /*min*/ 0, /*max*/ 2, IGRAPH_UNLIMITED) == IGRAPH_SUCCESS); + + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_maximal_cliques_file.out b/tests/unit/igraph_maximal_cliques_file.out new file mode 100644 index 0000000..2d216fa --- /dev/null +++ b/tests/unit/igraph_maximal_cliques_file.out @@ -0,0 +1,13 @@ +No vertices: + +Graph with loops and multiple edges: +5 +3 4 +3 1 2 +0 1 2 + +Graph with loops and multiple edges, minimum clique size 10: + +Graph with loops and multiple edges, maximum clique size 2: +5 +3 4 diff --git a/tests/unit/igraph_maximum_bipartite_matching.c b/tests/unit/igraph_maximum_bipartite_matching.c new file mode 100644 index 0000000..c95ea83 --- /dev/null +++ b/tests/unit/igraph_maximum_bipartite_matching.c @@ -0,0 +1,180 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int test_weighted_graph_from_mit_notes(void) { + /* Test graph from the following lecture notes: + * http://math.mit.edu/~goemans/18433S07/matching-notes.pdf + */ + igraph_t graph; + igraph_vector_bool_t types; + igraph_vector_int_t matching; + igraph_vector_t weights; + igraph_int_t matching_size; + igraph_real_t matching_weight; + igraph_bool_t is_matching; + igraph_real_t weight_array[] = { 2, 7, 2, 3, + 1, 3, 9, 3, 3, + 1, 3, 3, 1, 2, + 4, 1, 2, + 3 + }; + int i; + + igraph_small(&graph, 0, 0, + 0, 6, 0, 7, 0, 8, 0, 9, + 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, + 2, 5, 2, 6, 2, 7, 2, 8, 2, 9, + 3, 5, 3, 7, 3, 9, + 4, 7, -1); + igraph_vector_bool_init(&types, 10); + for (i = 0; i < 10; i++) { + VECTOR(types)[i] = (i >= 5); + } + igraph_vector_int_init(&matching, 0); + igraph_vector_init_array(&weights, weight_array, + sizeof(weight_array) / sizeof(weight_array[0])); + + igraph_maximum_bipartite_matching(&graph, &types, &matching_size, + &matching_weight, &matching, &weights, 0); + if (matching_size != 4) { + printf("matching_size is %" IGRAPH_PRId ", expected: 4\n", matching_size); + return 1; + } + if (matching_weight != 19) { + printf("matching_weight is %" IGRAPH_PRId ", expected: 19\n", (igraph_int_t) matching_weight); + return 2; + } + igraph_is_maximal_matching(&graph, &types, &matching, &is_matching); + if (!is_matching) { + printf("not a matching: "); + igraph_vector_int_print(&matching); + return 3; + } + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&matching); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + return 0; +} + +int test_weighted_graph_generated(void) { + /* Several randomly generated small test graphs */ + igraph_t graph; + igraph_vector_bool_t types; + igraph_vector_int_t matching; + igraph_vector_t weights; + igraph_int_t matching_size; + igraph_real_t matching_weight; + igraph_real_t weight_array_1[] = { 8, 5, 9, 18, 20, 13 }; + igraph_real_t weight_array_2[] = { 20, 4, 20, 3, 13, 1 }; + int i; + + igraph_vector_bool_init(&types, 10); + for (i = 0; i < 10; i++) { + VECTOR(types)[i] = (i >= 5); + } + igraph_vector_int_init(&matching, 0); + + /* Case 1 */ + + igraph_small(&graph, 0, 0, 0, 8, 2, 7, 3, 7, 3, 8, 4, 5, 4, 9, -1); + igraph_vector_init_array(&weights, weight_array_1, + sizeof(weight_array_1) / sizeof(weight_array_1[0])); + igraph_maximum_bipartite_matching(&graph, &types, &matching_size, + &matching_weight, &matching, &weights, 0); + if (matching_weight != 43) { + printf("matching_weight is %" IGRAPH_PRId ", expected: 43\n", (igraph_int_t)matching_weight); + return 2; + } + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Case 2 */ + + igraph_small(&graph, 0, 0, 0, 5, 0, 6, 1, 7, 2, 5, 3, 5, 3, 9, -1); + igraph_vector_init_array(&weights, weight_array_2, + sizeof(weight_array_2) / sizeof(weight_array_2[0])); + igraph_maximum_bipartite_matching(&graph, &types, &matching_size, + &matching_weight, &matching, &weights, 0); + if (matching_weight != 41) { + printf("matching_weight is %" IGRAPH_PRId ", expected: 41\n", (igraph_int_t)matching_weight); + return 2; + } + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&matching); + igraph_vector_bool_destroy(&types); + + return 0; +} + +int main(void) { + igraph_t g; + igraph_vector_bool_t types; + igraph_vector_t weights; + + igraph_int_t matching_size; + igraph_real_t weighted_size; + + igraph_vector_int_t matching; + + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, + -1); + + igraph_vector_bool_init(&types, 4); + VECTOR(types)[0] = 0; + VECTOR(types)[1] = 1; + VECTOR(types)[2] = 0; + VECTOR(types)[3] = 1; + + igraph_vector_int_init(&matching, 0); + + igraph_vector_init(&weights, igraph_vcount(&g)); + igraph_vector_fill(&weights, 1.0); + + // Test incorrect types + CHECK_ERROR(igraph_maximum_bipartite_matching(&g, &types, &matching_size, NULL, &matching, NULL, 0), IGRAPH_EINVAL); + + // Test incorrect types for weighted graph + VECTOR(types)[2] = 0; + CHECK_ERROR(igraph_maximum_bipartite_matching(&g, &types, &matching_size, &weighted_size, &matching, &weights, 0), IGRAPH_EINVAL); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&matching); + + igraph_vector_bool_destroy(&types); + igraph_destroy(&g); + + if (test_weighted_graph_generated()) { + return 1; + } + + if (test_weighted_graph_from_mit_notes()) { + return 2; + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_mean_degree.c b/tests/unit/igraph_mean_degree.c new file mode 100644 index 0000000..d33168c --- /dev/null +++ b/tests/unit/igraph_mean_degree.c @@ -0,0 +1,59 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_real_t k; + + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + igraph_mean_degree(&graph, &k, true); + IGRAPH_ASSERT(isnan(k)); + igraph_destroy(&graph); + + igraph_empty(&graph, 10, IGRAPH_UNDIRECTED); + igraph_mean_degree(&graph, &k, true); + IGRAPH_ASSERT(k == 0); + igraph_destroy(&graph); + + igraph_ring(&graph, 5, IGRAPH_DIRECTED, false, true); + igraph_mean_degree(&graph, &k, true); + IGRAPH_ASSERT(k == 1); + igraph_destroy(&graph); + + igraph_ring(&graph, 5, IGRAPH_UNDIRECTED, false, true); + igraph_mean_degree(&graph, &k, true); + IGRAPH_ASSERT(k == 2); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, + 0,1, 1,1, -1); + igraph_mean_degree(&graph, &k, true); + IGRAPH_ASSERT(k == 2); + igraph_mean_degree(&graph, &k, false); + IGRAPH_ASSERT(k == 1); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_minimum_size_separators.c b/tests/unit/igraph_minimum_size_separators.c new file mode 100644 index 0000000..0692cf3 --- /dev/null +++ b/tests/unit/igraph_minimum_size_separators.c @@ -0,0 +1,173 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(const igraph_t *graph, igraph_vector_int_list_t *list) { + const igraph_int_t n = igraph_vector_int_list_size(list); + for (igraph_int_t i=0; i < n; i++) { + igraph_bool_t sep; + igraph_is_minimal_separator( + graph, + igraph_vss_vector(igraph_vector_int_list_get_ptr(list, i)), + &sep); + IGRAPH_ASSERT(sep); + } + print_vector_int_list(list); + printf("----\n"); + + igraph_vector_int_list_destroy(list); +} + +int main(void) { + igraph_t g, g2; + igraph_vector_int_list_t sep; + igraph_vs_t vs; + + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, + -1); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + print_and_destroy(&g, &sep); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 3, 1, 3, 2, 3, + 0, 4, 1, 4, 2, 4, + -1); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + print_and_destroy(&g, &sep); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 2, 0, 3, 0, 4, 0, + 2, 1, 3, 1, 4, 1, + -1); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + print_and_destroy(&g, &sep); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 2, 0, 3, 1, 2, 1, 3, 5, 2, 5, 3, 6, 2, 6, 3, + 7, 2, 7, 3, 8, 2, 8, 3, 9, 2, 9, 3, + 2, 4, 4, 3, + -1); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + print_and_destroy(&g, &sep); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_full(&g, 4, IGRAPH_UNDIRECTED, /*loops=*/ false); + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + print_and_destroy(&g, &sep); + igraph_destroy(&g); + + /* ----------------------------------------------------------- */ + + igraph_small(&g, 23, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, + 1, 2, 1, 3, 1, 4, 1, 6, + 2, 3, 2, 5, 2, 6, + 3, 4, 3, 5, 3, 6, + 4, 5, 4, 6, 4, 20, + 5, 6, + 6, 7, 6, 10, 6, 13, 6, 18, + 7, 8, 7, 10, 7, 13, + 8, 9, + 9, 11, 9, 12, + 10, 11, 10, 13, + 11, 15, + 12, 15, + 13, 14, + 14, 15, + 16, 17, 16, 18, 16, 19, + 17, 19, 17, 20, + 18, 19, 18, 21, 18, 22, + 19, 20, + 20, 21, 20, 22, + 21, 22, + -1); + + igraph_vector_int_list_init(&sep, 0); + igraph_minimum_size_separators(&g, &sep); + printf("Orig:\n"); + print_and_destroy(&g, &sep); + + igraph_vector_int_list_init(&sep, 0); + igraph_vs_vector_small(&vs, 0, 1, 2, 3, 4, 5, 6, 16, 17, 18, 19, 20, 21, 22, -1); + igraph_induced_subgraph(&g, &g2, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_minimum_size_separators(&g2, &sep); + printf("1-7,17-23:\n"); + print_and_destroy(&g2, &sep); + igraph_vs_destroy(&vs); + igraph_destroy(&g2); + + igraph_vector_int_list_init(&sep, 0); + igraph_vs_vector_small(&vs, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1); + igraph_induced_subgraph(&g, &g2, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_minimum_size_separators(&g2, &sep); + printf("7-16:\n"); + print_and_destroy(&g2, &sep); + igraph_vs_destroy(&vs); + igraph_destroy(&g2); + + igraph_vector_int_list_init(&sep, 0); + igraph_vs_vector_small(&vs, 16, 17, 18, 19, 20, 21, 22, -1); + igraph_induced_subgraph(&g, &g2, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_minimum_size_separators(&g2, &sep); + printf("17-23:\n"); + print_and_destroy(&g2, &sep); + igraph_vs_destroy(&vs); + igraph_destroy(&g2); + + igraph_vector_int_list_init(&sep, 0); + igraph_vs_vector_small(&vs, 6, 7, 10, 13, -1); + igraph_induced_subgraph(&g, &g2, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_minimum_size_separators(&g2, &sep); + printf("7,8,11,14:\n"); + print_and_destroy(&g2, &sep); + igraph_vs_destroy(&vs); + igraph_destroy(&g2); + + igraph_vector_int_list_init(&sep, 0); + igraph_vs_vector_small(&vs, 0, 1, 2, 3, 4, 5, 6, -1); + igraph_induced_subgraph(&g, &g2, vs, IGRAPH_SUBGRAPH_AUTO); + igraph_minimum_size_separators(&g2, &sep); + printf("1-7:\n"); + print_and_destroy(&g2, &sep); + igraph_vs_destroy(&vs); + igraph_destroy(&g2); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_minimum_size_separators.out b/tests/unit/igraph_minimum_size_separators.out new file mode 100644 index 0000000..ce6be5e --- /dev/null +++ b/tests/unit/igraph_minimum_size_separators.out @@ -0,0 +1,55 @@ +{ + 0: ( 0 ) +} +---- +{ + 0: ( 3 4 ) +} +---- +{ + 0: ( 0 1 ) +} +---- +{ + 0: ( 2 3 ) +} +---- +{ +} +---- +Orig: +{ + 0: ( 6 ) +} +---- +1-7,17-23: +{ + 0: ( 9 11 ) + 1: ( 4 9 ) + 2: ( 6 11 ) + 3: ( 4 6 ) +} +---- +7-16: +{ + 0: ( 3 9 ) + 1: ( 7 9 ) + 2: ( 1 3 ) +} +---- +17-23: +{ + 0: ( 2 4 ) +} +---- +7,8,11,14: +{ +} +---- +1-7: +{ + 0: ( 1 2 3 4 5 ) + 1: ( 0 2 3 4 6 ) + 2: ( 0 1 3 5 6 ) +} +---- diff --git a/tests/unit/igraph_modularity.c b/tests/unit/igraph_modularity.c new file mode 100644 index 0000000..aa55e8a --- /dev/null +++ b/tests/unit/igraph_modularity.c @@ -0,0 +1,128 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + igraph_vector_int_t membership; + igraph_real_t modularity, resolution; + igraph_attribute_combination_t comb; + + /* Turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_attribute_combination(&comb, + "weight", IGRAPH_ATTRIBUTE_COMBINE_SUM, + IGRAPH_NO_MORE_ATTRIBUTES); + + /* Set default seed to get reproducible results */ + igraph_rng_seed(igraph_rng_default(), 0); + + /* Null graph */ + igraph_vector_int_init(&membership, 0); + + igraph_small(&graph, 0, IGRAPH_UNDIRECTED, -1); + igraph_modularity(&graph, &membership, 0, /* resolution */ 1, IGRAPH_UNDIRECTED, &modularity); + IGRAPH_ASSERT(isnan(modularity)); + igraph_destroy(&graph); + + igraph_small(&graph, 0, IGRAPH_DIRECTED, -1); + igraph_modularity(&graph, &membership, 0, /* resolution */ 1, IGRAPH_UNDIRECTED, &modularity); + IGRAPH_ASSERT(isnan(modularity)); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&membership); + + /* Simple unweighted graph */ + igraph_small(&graph, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, + 0, 5, -1); + + /* Set weights */ + igraph_vector_init(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.0); + SETEANV(&graph, "weight", &weights); + + /* Set membership */ + igraph_vector_int_init_int(&membership, igraph_vcount(&graph), + 0, 0, 0, 0, 0, 1, 1, 1, 1, 1); + + /* Calculate modularity */ + for (resolution = 0.5; resolution <= 1.5; resolution += 0.5) { + igraph_modularity(&graph, &membership, &weights, + /* resolution */ resolution, + IGRAPH_DIRECTED, &modularity); + printf("Modularity (resolution %.2f) is %g.\n", resolution, modularity); + } + + igraph_to_directed(&graph, IGRAPH_TO_DIRECTED_MUTUAL); + igraph_vector_resize(&weights, igraph_ecount(&graph)); + igraph_vector_fill(&weights, 1.0); + for (resolution = 0.5; resolution <= 1.5; resolution += 0.5) { + igraph_modularity(&graph, &membership, &weights, + /* resolution */ resolution, + IGRAPH_DIRECTED, &modularity); + printf("Modularity (resolution %.2f) is %g on directed graph.\n", resolution, modularity); + } + + /* Recalculate modularity on contracted graph */ + igraph_contract_vertices(&graph, &membership, NULL); + igraph_vector_int_destroy(&membership); + + igraph_simplify(&graph, /* remove_multiple */ true, /* remove_loops */ false, &comb); + + igraph_vector_int_init_range(&membership, 0, igraph_vcount(&graph)); + EANV(&graph, "weight", &weights); + for (resolution = 0.5; resolution <= 1.5; resolution += 0.5) { + igraph_modularity(&graph, &membership, &weights, + /* resolution */ resolution, + IGRAPH_DIRECTED, &modularity); + printf("Modularity (resolution %.2f) is %g after aggregation.\n", resolution, modularity); + } + + { + igraph_real_t modularity2; + igraph_modularity(&graph, &membership, &weights, 1.0, IGRAPH_DIRECTED, &modularity); + + /* All entries are distinct in the current membership vector. + * We replace one with an element that will trigger automatic + * reindexing within igraph_modularity(). + * The modularity should not change. */ + VECTOR(membership)[1] = -5; + igraph_modularity(&graph, &membership, &weights, 1.0, IGRAPH_DIRECTED, &modularity2); + IGRAPH_ASSERT(modularity == modularity2); + } + + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + igraph_attribute_combination_destroy(&comb); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_modularity.out b/tests/unit/igraph_modularity.out new file mode 100644 index 0000000..5e062b0 --- /dev/null +++ b/tests/unit/igraph_modularity.out @@ -0,0 +1,9 @@ +Modularity (resolution 0.50) is 0.702381. +Modularity (resolution 1.00) is 0.452381. +Modularity (resolution 1.50) is 0.202381. +Modularity (resolution 0.50) is 0.702381 on directed graph. +Modularity (resolution 1.00) is 0.452381 on directed graph. +Modularity (resolution 1.50) is 0.202381 on directed graph. +Modularity (resolution 0.50) is 0.702381 after aggregation. +Modularity (resolution 1.00) is 0.452381 after aggregation. +Modularity (resolution 1.50) is 0.202381 after aggregation. diff --git a/tests/unit/igraph_modularity_matrix.c b/tests/unit/igraph_modularity_matrix.c new file mode 100644 index 0000000..8dfeae1 --- /dev/null +++ b/tests/unit/igraph_modularity_matrix.c @@ -0,0 +1,133 @@ +/* + igraph library. + Copyright (C) 2021-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void test_print_destroy(igraph_t *g, igraph_vector_t *weights, igraph_real_t resolution, igraph_matrix_t *modmat, igraph_bool_t directed) { + + igraph_modularity_matrix(g, weights, resolution, modmat, directed); + + /* Prevent test failures due to roundoff errors. + * See https://github.com/igraph/igraph/issues/2473 */ + igraph_matrix_zapsmall(modmat, 0); + + print_matrix(modmat); + igraph_destroy(g); + igraph_matrix_destroy(modmat); + if (weights) { + igraph_vector_destroy(weights); + } +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_vector_int_t membership; + igraph_matrix_t modmat; + igraph_real_t modularity, test_modularity; + igraph_int_t i, j; + + printf("No vertices:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("No edges:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Triangle with no resolution should give the adjacency matrix:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 0.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Triangle and point with self-loop, undirected :\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, 3,3, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Triangle and point with self-loop, directed, but direction ignored:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 0,2, 1,2, 3,3, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Triangle and point with self-loop, directed:\n"); + igraph_small(&g, 4, IGRAPH_DIRECTED, 0,1, 0,2, 1,2, 3,3, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_DIRECTED); + + printf("Triangle with weights 0, 1, 2:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 3, 0, 1, 2); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, &weights, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Triangle with weights 0, -1, -2:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 3, 0, -1, -2); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, &weights, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Directed triangle with weights 0, 1, 2:\n"); + igraph_small(&g, 3, IGRAPH_DIRECTED, 0,1, 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 3, 0, 1, 2); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, &weights, 1.0, &modmat, IGRAPH_DIRECTED); + + printf("Triangle with weights -1, 0, 1 will cause divisions by zero:\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,2, -1); + igraph_vector_init_int(&weights, 3, -1, 0, 1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, &weights, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Cancellation (matrix will have zeros):\n"); + igraph_small(&g, 4, IGRAPH_UNDIRECTED, + 0,1, 1,2, 2,3, 0,3, 0,1, 2,2, -1); + igraph_matrix_init(&modmat, 0, 0); + test_print_destroy(&g, NULL, 1.0, &modmat, IGRAPH_UNDIRECTED); + + printf("Comparison with modularity:\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, 0,1, 0,2, 1,2, 3,4, 4,0, -1); + igraph_vector_init_int(&weights, 5, 1, 2, 3, 4, 5); + igraph_vector_int_init_int(&membership, 5, 0, 0, 0, 1, 1); + igraph_matrix_init(&modmat, 0, 0); + IGRAPH_ASSERT(igraph_modularity_matrix(&g, &weights, 0.7, &modmat, IGRAPH_DIRECTED) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_modularity(&g, &membership, &weights, 0.7, IGRAPH_DIRECTED, &modularity) == IGRAPH_SUCCESS); + print_matrix(&modmat); + test_modularity = 0; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + test_modularity += MATRIX(modmat, i, j); + } + } + for (i = 3; i < 5; i++) { + for (j = 3; j < 5; j++) { + test_modularity += MATRIX(modmat, i, j); + } + } + printf("Modularity: %g, modularity via matrix: %g\n", modularity, test_modularity / igraph_vector_sum(&weights)); + igraph_destroy(&g); + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&weights); + igraph_matrix_destroy(&modmat); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_modularity_matrix.out b/tests/unit/igraph_modularity_matrix.out new file mode 100644 index 0000000..062c470 --- /dev/null +++ b/tests/unit/igraph_modularity_matrix.out @@ -0,0 +1,53 @@ +No vertices: +[ 0-by-0 ] +No edges: +[ NaN NaN NaN + NaN NaN NaN + NaN NaN NaN ] +Triangle with no resolution should give the adjacency matrix: +[ 0 1 1 + 1 0 1 + 1 1 0 ] +Triangle and point with self-loop, undirected : +[ -0.5 0.5 0.5 -0.5 + 0.5 -0.5 0.5 -0.5 + 0.5 0.5 -0.5 -0.5 + -0.5 -0.5 -0.5 1.5 ] +Triangle and point with self-loop, directed, but direction ignored: +[ -0.5 0.5 0.5 -0.5 + 0.5 -0.5 0.5 -0.5 + 0.5 0.5 -0.5 -0.5 + -0.5 -0.5 -0.5 1.5 ] +Triangle and point with self-loop, directed: +[ 0 0.5 0 -0.5 + 0 -0.25 0.5 -0.25 + 0 0 0 0 + 0 -0.25 -0.5 0.75 ] +Triangle with weights 0, 1, 2: +[ -0.166667 -0.333333 0.5 + -0.333333 -0.666667 1 + 0.5 1 -1.5 ] +Triangle with weights 0, -1, -2: +[ 0.166667 0.333333 -0.5 + 0.333333 0.666667 -1 + -0.5 -1 1.5 ] +Directed triangle with weights 0, 1, 2: +[ 0 0 0 + 0 0 0 + 0 0 0 ] +Triangle with weights -1, 0, 1 will cause divisions by zero: +[ -Inf NaN Inf + NaN NaN NaN + Inf NaN -Inf ] +Cancellation (matrix will have zeros): +[ -0.75 1.25 -1 0.5 + 1.25 -0.75 0 -0.5 + -1 0 0.666667 0.333333 + 0.5 -0.5 0.333333 -0.333333 ] +Comparison with modularity: +[ -0.7 0.86 1.3 0 -0.56 + -0.7 -0.14 2.3 0 -0.56 + 0 0 0 0 0 + -0.933333 -0.186667 -0.933333 0 3.25333 + 3.83333 -0.233333 -1.16667 0 -0.933333 ] +Modularity: 0.349333, modularity via matrix: 0.349333 diff --git a/tests/unit/igraph_motifs_randesu.c b/tests/unit/igraph_motifs_randesu.c new file mode 100644 index 0000000..3e013b9 --- /dev/null +++ b/tests/unit/igraph_motifs_randesu.c @@ -0,0 +1,68 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +igraph_error_t print_motif(const igraph_t *graph, const igraph_vector_int_t *vids, + igraph_int_t isoclass, void* extra) { + IGRAPH_UNUSED(graph); + IGRAPH_UNUSED(extra); + printf("Class %" IGRAPH_PRId ": ", isoclass); + print_vector_int(vids); + return IGRAPH_SUCCESS; +} + +int main(void) { + + igraph_t g; + igraph_vector_t hist; + igraph_int_t size; + + igraph_ring(&g, 1000, IGRAPH_DIRECTED, 1, 1); + igraph_vector_init(&hist, 0); + igraph_motifs_randesu(&g, &hist, 3, NULL); + print_vector(&hist); + igraph_destroy(&g); + igraph_vector_destroy(&hist); + + igraph_famous(&g, "Octahedral"); + size = 3; + printf("Motif size: %" IGRAPH_PRId "\n", size); + igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL); + size = 4; + printf("Motif size: %" IGRAPH_PRId "\n", size); + igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL); + size = 5; + printf("Motif size: %" IGRAPH_PRId "\n", size); + igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL); + size = 6; + printf("Motif size: %" IGRAPH_PRId "\n", size); + igraph_motifs_randesu_callback(&g, size, NULL, &print_motif, NULL); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_motifs_randesu.out b/tests/unit/igraph_motifs_randesu.out new file mode 100644 index 0000000..fbe0095 --- /dev/null +++ b/tests/unit/igraph_motifs_randesu.out @@ -0,0 +1,47 @@ +( NaN NaN 0 NaN 0 0 0 0 0 0 1000 0 0 0 0 0 ) +Motif size: 3 +Class 2: ( 0 5 1 ) +Class 3: ( 0 5 2 ) +Class 3: ( 0 5 3 ) +Class 2: ( 0 5 4 ) +Class 3: ( 0 3 1 ) +Class 2: ( 0 3 2 ) +Class 2: ( 0 3 4 ) +Class 3: ( 0 2 1 ) +Class 2: ( 0 2 4 ) +Class 2: ( 0 1 4 ) +Class 3: ( 1 4 2 ) +Class 3: ( 1 4 3 ) +Class 2: ( 1 4 5 ) +Class 2: ( 1 3 2 ) +Class 2: ( 1 3 5 ) +Class 2: ( 1 2 5 ) +Class 3: ( 2 5 4 ) +Class 2: ( 2 5 3 ) +Class 2: ( 2 4 3 ) +Class 3: ( 3 5 4 ) +Motif size: 4 +Class 8: ( 0 5 4 1 ) +Class 9: ( 0 5 4 2 ) +Class 9: ( 0 5 4 3 ) +Class 9: ( 0 5 3 1 ) +Class 9: ( 0 5 3 2 ) +Class 9: ( 0 5 2 1 ) +Class 9: ( 0 3 4 1 ) +Class 8: ( 0 3 4 2 ) +Class 9: ( 0 3 2 1 ) +Class 9: ( 0 2 4 1 ) +Class 9: ( 1 4 5 2 ) +Class 9: ( 1 4 5 3 ) +Class 9: ( 1 4 3 2 ) +Class 8: ( 1 3 5 2 ) +Class 9: ( 2 5 3 4 ) +Motif size: 5 +Class 31: ( 0 5 4 3 1 ) +Class 31: ( 0 5 4 3 2 ) +Class 31: ( 0 5 4 2 1 ) +Class 31: ( 0 5 3 2 1 ) +Class 31: ( 0 3 4 2 1 ) +Class 31: ( 1 4 5 3 2 ) +Motif size: 6 +Class 152: ( 0 5 4 3 2 1 ) diff --git a/tests/unit/igraph_motifs_randesu_estimate.c b/tests/unit/igraph_motifs_randesu_estimate.c new file mode 100644 index 0000000..515155a --- /dev/null +++ b/tests/unit/igraph_motifs_randesu_estimate.c @@ -0,0 +1,91 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, int size, igraph_vector_t *cut_prob, + igraph_int_t sample_size, igraph_vector_int_t *parsample) { + igraph_real_t estimate; + IGRAPH_ASSERT(igraph_motifs_randesu_estimate(graph, &estimate, size, cut_prob, sample_size, parsample) == IGRAPH_SUCCESS); + printf("Estimate: %g\n\n", estimate); +} + + +int main(void) { + igraph_t g_0, g_1, g_50_full, g_4_3_1; + igraph_vector_t cut_prob_01; + igraph_vector_int_t parsample; + igraph_real_t estimate; + + igraph_vector_init_real(&cut_prob_01, 3, 0.1, 0.1, 0.1); + igraph_vector_int_init_range(&parsample, 0, 41); + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_full(&g_50_full, 50, 0, IGRAPH_NO_LOOPS); + igraph_small(&g_4_3_1, 4, 0, 0,1, 1,2, 2,0, -1); + + printf("No vertices:\n"); + call_and_print(&g_0, /*size*/ 3, NULL, /*sample_size*/ 1, /*parsample*/ NULL); + + printf("One vertex:\n"); + call_and_print(&g_1, /*size*/ 3, NULL, /*sample_size*/ 1, /*parsample*/ NULL); + + printf("Full graph of 50 vertices, motif size 3, sample all, (50 choose 3 = 19600):\n"); + call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 50, /*parsample*/ NULL); + + printf("Full graph of 50 vertices, motif size 3, sample all, cut_prob 0.1 at each level:\n"); + call_and_print(&g_50_full, /*size*/ 3, &cut_prob_01, /*sample_size*/ 50, /*parsample*/ NULL); + + printf("Full graph of 50 vertices, motif size 3, sample 20:\n"); + call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 20, /*parsample*/ NULL); + + printf("Full graph of 50 vertices, motif size 3, sample first 40:\n"); + call_and_print(&g_50_full, /*size*/ 3, NULL, /*sample_size*/ 0, &parsample); + + printf("Full graph of 50 vertices, motif size 4, sample 20 (50 choose 4 = 230300):\n"); + call_and_print(&g_50_full, /*size*/ 4, NULL, /*sample_size*/ 20, /*parsample*/ NULL); + + printf("Triangle and a vertex, motif size 4, sample all:\n"); + call_and_print(&g_4_3_1, /*size*/ 4, NULL, /*sample_size*/ 4, /*parsample*/ NULL); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Cut prob too short.\n"); + IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 14, &cut_prob_01, /*sample_size*/ 4, /*parsample*/ NULL) == IGRAPH_EINVAL); + + printf("Too many samples.\n"); + IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, NULL, /*sample_size*/ 40, /*parsample*/ NULL) == IGRAPH_EINVAL); + + printf("Too many parsamples.\n"); + IGRAPH_ASSERT(igraph_motifs_randesu_estimate(&g_4_3_1, &estimate, /*size*/ 4, NULL, /*sample_size*/ 4, /*parsample*/ &parsample) == IGRAPH_EINVVID); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_50_full); + igraph_destroy(&g_4_3_1); + igraph_vector_destroy(&cut_prob_01); + igraph_vector_int_destroy(&parsample); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_motifs_randesu_estimate.out b/tests/unit/igraph_motifs_randesu_estimate.out new file mode 100644 index 0000000..ef3902c --- /dev/null +++ b/tests/unit/igraph_motifs_randesu_estimate.out @@ -0,0 +1,27 @@ +No vertices: +Estimate: 0 + +One vertex: +Estimate: 0 + +Full graph of 50 vertices, motif size 3, sample all, (50 choose 3 = 19600): +Estimate: 19600 + +Full graph of 50 vertices, motif size 3, sample all, cut_prob 0.1 at each level: +Estimate: 16034 + +Full graph of 50 vertices, motif size 3, sample 20: +Estimate: 16040 + +Full graph of 50 vertices, motif size 3, sample first 40: +Estimate: 23800 + +Full graph of 50 vertices, motif size 4, sample 20 (50 choose 4 = 230300): +Estimate: 241430 + +Triangle and a vertex, motif size 4, sample all: +Estimate: 0 + +Cut prob too short. +Too many samples. +Too many parsamples. diff --git a/tests/unit/igraph_motifs_randesu_no.c b/tests/unit/igraph_motifs_randesu_no.c new file mode 100644 index 0000000..e3f2a1a --- /dev/null +++ b/tests/unit/igraph_motifs_randesu_no.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, int size, igraph_vector_t *cut_prob) { + igraph_real_t result; + IGRAPH_ASSERT(igraph_motifs_randesu_no(graph, &result, size, cut_prob) == IGRAPH_SUCCESS); + printf("Result: %g\n\n", result); +} + +int main(void) { + igraph_t g_0, g_1, g_50_full, g_4_3_1; + igraph_vector_t cut_prob_01; + igraph_real_t result; + + igraph_vector_init_real(&cut_prob_01, 3, 0.1, 0.1, 0.1); + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_full(&g_50_full, 50, 0, IGRAPH_NO_LOOPS); + igraph_small(&g_4_3_1, 4, 0, 0,1, 1,2, 2,0, -1); + + printf("No vertices:\n"); + call_and_print(&g_0, /*size*/ 3, NULL); + + printf("One vertex:\n"); + call_and_print(&g_1, /*size*/ 3, NULL); + + printf("Full graph of 50 vertices, motif size 3 (50 choose 3 = 19600):\n"); + call_and_print(&g_50_full, /*size*/ 3, NULL); + + printf("Full graph of 50 vertices, motif size 3, cut_prob 0.1 at each level:\n"); + call_and_print(&g_50_full, /*size*/ 3, &cut_prob_01); + + printf("Full graph of 50 vertices, motif size 4 (50 choose 4 = 230300:\n"); + call_and_print(&g_50_full, /*size*/ 4, NULL); + + printf("Triangle and a vertex, motif size 4:\n"); + call_and_print(&g_4_3_1, /*size*/ 4, NULL); + + VERIFY_FINALLY_STACK(); + + printf("Cut prob too short.\n"); + CHECK_ERROR(igraph_motifs_randesu_no(&g_4_3_1, &result, /*size*/ 14, &cut_prob_01), IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_50_full); + igraph_destroy(&g_4_3_1); + igraph_vector_destroy(&cut_prob_01); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_motifs_randesu_no.out b/tests/unit/igraph_motifs_randesu_no.out new file mode 100644 index 0000000..41b6613 --- /dev/null +++ b/tests/unit/igraph_motifs_randesu_no.out @@ -0,0 +1,19 @@ +No vertices: +Result: 0 + +One vertex: +Result: 0 + +Full graph of 50 vertices, motif size 3 (50 choose 3 = 19600): +Result: 19600 + +Full graph of 50 vertices, motif size 3, cut_prob 0.1 at each level: +Result: 16034 + +Full graph of 50 vertices, motif size 4 (50 choose 4 = 230300: +Result: 230300 + +Triangle and a vertex, motif size 4: +Result: 0 + +Cut prob too short. diff --git a/tests/unit/igraph_nearest_neighbor_graph.c b/tests/unit/igraph_nearest_neighbor_graph.c new file mode 100644 index 0000000..61d93e9 --- /dev/null +++ b/tests/unit/igraph_nearest_neighbor_graph.c @@ -0,0 +1,278 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#include /* fabs() */ + +double sqr(double x) { return x*x; } + +igraph_error_t RKNN_neighbors( + const igraph_real_t *arr, + igraph_int_t num_points, + igraph_int_t dims, + igraph_int_t neighbors, + igraph_real_t cutoff, + igraph_metric_t metric) { + + igraph_t graph; + igraph_matrix_t points; + igraph_matrix_t adj_mat; + igraph_matrix_t dist_mat; + igraph_vector_int_t degrees; + + // The cutoff value is not only passed to igraph_nearest_neighbor_graph() + // but also used directly by this test function. + cutoff = cutoff >= 0 ? cutoff : INFINITY; + + IGRAPH_MATRIX_INIT_FINALLY(&dist_mat, num_points, num_points); + + IGRAPH_CHECK(igraph_matrix_init_array(&points, &arr[0], num_points, dims, IGRAPH_ROW_MAJOR)); + IGRAPH_FINALLY(igraph_matrix_destroy, &points); + + for (igraph_int_t start = 0; start < num_points - 1; start++) { + for (igraph_int_t end = start + 1; end < num_points; end++) { + igraph_real_t distance = 0; + switch (metric) { + case IGRAPH_METRIC_L2: + for (igraph_int_t i = 0; i < dims; i++) { + distance += sqr(MATRIX(points, start, i) - MATRIX(points, end, i)); + } + break; + case IGRAPH_METRIC_L1: + for (igraph_int_t i = 0; i < dims; i++) { + distance += fabs(MATRIX(points, start, i) - MATRIX(points, end, i)); + } + break; + } + MATRIX(dist_mat, start, end) = distance; + MATRIX(dist_mat, end, start) = distance; + } + } + + IGRAPH_CHECK(igraph_nearest_neighbor_graph(&graph, &points, metric, neighbors, cutoff, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &graph); + + print_matrix(&points); + print_graph_canon(&graph); + + IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, 0); + IGRAPH_CHECK(igraph_degree(&graph, °rees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); + if (neighbors >= 0) { + IGRAPH_ASSERT(igraph_vector_int_max(°rees) <= neighbors); + } + + IGRAPH_MATRIX_INIT_FINALLY(&adj_mat, 0, 0); + IGRAPH_CHECK(igraph_get_adjacency(&graph, &adj_mat, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_NO_LOOPS)); + + for (igraph_int_t start = 0; start < num_points; start++) { + igraph_real_t min_missing = IGRAPH_INFINITY; + igraph_real_t max_present = 0; + + for (igraph_int_t end = 0; end < num_points; end++) { + if (start == end) { + continue; + } + if (MATRIX(adj_mat, start, end) == 1) { + if (max_present < MATRIX(dist_mat, start, end)) { + max_present = MATRIX(dist_mat, start, end); + } + } else { + if (min_missing > MATRIX(dist_mat, start, end)) { + min_missing = MATRIX(dist_mat, start, end); + } + } + } + IGRAPH_ASSERT(min_missing >= max_present); + if (metric == IGRAPH_METRIC_L2) { + IGRAPH_ASSERT(max_present <= cutoff * cutoff); + } else if (metric == IGRAPH_METRIC_L1) { + IGRAPH_ASSERT(max_present <= cutoff); + } + } + + igraph_matrix_destroy(&adj_mat); + igraph_vector_int_destroy(°rees); + igraph_destroy(&graph); + igraph_matrix_destroy(&points); + igraph_matrix_destroy(&dist_mat); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +int main(void) { + igraph_real_t points1d[] = { + 12, + 8, + 5, + 10, + 12 + }; + + igraph_real_t points2d[] = { + 12, 8, + 8, 6, + 5, 12, + 10, 1, + 12, 2 + }; + + // Use coordinates that do not lead to roundoff errors + igraph_real_t points2d_4star[] = { + 0, 0, + 1, 0, + -1, 0, + 0, 1, + 0, -1 + }; + + igraph_real_t points3d[] = { + 1, 6, 4, + 6, 2, 3, + 3, 6, 6, + 3, 2, 2, + 2, 3, 3 + }; + + igraph_real_t points4d[] = { + 1, 6, 4, 4, + 6, 2, 3, 3, + 3, 6, 6, 5, + 3, 2, 2, 3, + 2, 3, 3, 4 + }; + +#define PTCOUNT(points, dim) sizeof(points) / sizeof(points[0]) / dim + + printf("# L2 Metric test suite:\n"); + + printf("\n1d 2 neighbors, cutoff 3\n"); + RKNN_neighbors(points1d, PTCOUNT(points1d, 1), 1, 2, 3, IGRAPH_METRIC_L2); + printf("\n1d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points1d, PTCOUNT(points1d, 1), 1, 1, -1, IGRAPH_METRIC_L2); + printf("\n1d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points1d, PTCOUNT(points1d, 1), 1, -1, -1, IGRAPH_METRIC_L2); + printf("\n1d unlimited neighbors, cutoff 7\n"); + RKNN_neighbors(points1d, PTCOUNT(points1d, 1), 1, -1, 3, IGRAPH_METRIC_L2); + + printf("\n2d 2 neighbors, cutoff 5\n"); + RKNN_neighbors(points2d, PTCOUNT(points2d, 2), 2, 2, 5, IGRAPH_METRIC_L2); + printf("\n2d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points2d, PTCOUNT(points2d, 2), 2, 1, -1, IGRAPH_METRIC_L2); + printf("\n2d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points2d, PTCOUNT(points2d, 2), 2, -1, -1, IGRAPH_METRIC_L2); + printf("\n2d unlimited neighbors, cutoff 7\n"); + RKNN_neighbors(&points2d[0], PTCOUNT(points2d, 2), 2, -1, 7, IGRAPH_METRIC_L2); + + printf("\n2d 2 neighbors, cutoff 1.2, degenerate case\n"); + RKNN_neighbors(points2d_4star, PTCOUNT(points2d_4star, 2), 2, 2, 1.2, IGRAPH_METRIC_L2); + + printf("\n3d, 2 neighbors, cutoff 4\n"); + RKNN_neighbors(&points3d[0], PTCOUNT(points3d, 3), 3, 2, 4, IGRAPH_METRIC_L2); + printf("\n3d, unlimited neighbors, cutoff 4\n"); + RKNN_neighbors(&points3d[0], PTCOUNT(points3d, 3), 3, -1, 4, IGRAPH_METRIC_L2); + printf("\n3d, 2 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points3d[0], PTCOUNT(points3d, 3), 3, 2, -1, IGRAPH_METRIC_L2); + printf("\n3d, unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points3d[0], PTCOUNT(points3d, 3), 3, -1, -1, IGRAPH_METRIC_L2); + + printf("\n4d 2 neighbors, cutoff 5\n"); + RKNN_neighbors(points4d, PTCOUNT(points4d, 4), 4, 2, 5, IGRAPH_METRIC_L2); + printf("\n4d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points4d, PTCOUNT(points4d, 4), 4, 1, -1, IGRAPH_METRIC_L2); + printf("\n4d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(points4d, PTCOUNT(points4d, 4), 4, -1, -1, IGRAPH_METRIC_L2); + printf("\n4d unlimited neighbors, cutoff 4\n"); + RKNN_neighbors(points4d, PTCOUNT(points4d, 4), 4, -1, 4, IGRAPH_METRIC_L2); + + printf("\n3d, no points\n"); + RKNN_neighbors(points3d, 0, 3, -1, 100, IGRAPH_METRIC_L2); + printf("\n0d, no points\n"); + RKNN_neighbors(points3d, 0, 0, -1, 100, IGRAPH_METRIC_L2); + printf("\n3d, 1 point\n"); + RKNN_neighbors(points3d, 1, 3, -1, 100, IGRAPH_METRIC_L2); + + printf("\n2d, zero neighbors\n"); + RKNN_neighbors(points2d, PTCOUNT(points2d, 2), 2, 0, 5, IGRAPH_METRIC_L2); + + printf("\n2d, zero cutoff\n"); + RKNN_neighbors(points2d, PTCOUNT(points2d, 2), 2, -1, 0, IGRAPH_METRIC_L2); + + printf("\n0d, 1 point should error\n"); + CHECK_ERROR(RKNN_neighbors(points3d, 1, 0, 10, INFINITY, IGRAPH_METRIC_L2), IGRAPH_EINVAL); + + + printf("\nFibonacci spiral with 25 points, 1 neighbor unlimited cutoff.\n"); + const igraph_int_t point_count = 25; + igraph_real_t fib[25 * 2]; + + for (igraph_int_t k = 0; k < point_count; k++) { + igraph_real_t r = sqrt(k); // pi phi + fib[2 * k ] = r * cos(2 * k * 3.14159265359 / 1.618033988749); + fib[2 * k + 1] = r * sin(2 * k * 3.14159265359 / 1.618033988749); + } + + RKNN_neighbors(&fib[0], 25, 2, 1, -1, IGRAPH_METRIC_L2); + + printf("# L1 Metric test suite:\n"); + + printf("\n1d 2 neighbors, cutoff 3\n"); + RKNN_neighbors(&points1d[0], 5, 1, 2, 3, IGRAPH_METRIC_L1); + printf("\n1d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points1d[0], 5, 1, 1, -1, IGRAPH_METRIC_L1); + printf("\n1d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points1d[0], 5, 1, -1, -1, IGRAPH_METRIC_L1); + printf("\n1d unlimited neighbors, cutoff 7\n"); + RKNN_neighbors(&points1d[0], 5, 1, -1, 3, IGRAPH_METRIC_L1); + + printf("\n2d 2 neighbors, cutoff 5\n"); + RKNN_neighbors(&points2d[0], 5, 2, 2, 5, IGRAPH_METRIC_L1); + printf("\n2d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points2d[0], 5, 2, 1, -1, IGRAPH_METRIC_L1); + printf("\n2d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points2d[0], 5, 2, -1, -1, IGRAPH_METRIC_L1); + printf("\n2d unlimited neighbors, cutoff 7\n"); + RKNN_neighbors(&points2d[0], 5, 2, -1, 7, IGRAPH_METRIC_L1); + + printf("\n2d 2 neighbors, cutoff INFINITY, degenerate case\n"); + RKNN_neighbors(points2d_4star, PTCOUNT(points2d_4star, 2), 2, 2, -1, IGRAPH_METRIC_L1); + + printf("\n3d, 2 neighbors, cutoff 4\n"); + RKNN_neighbors(&points3d[0], 5, 3, 2, 4, IGRAPH_METRIC_L1); + printf("\n3d, unlimited neighbors, cutoff 4\n"); + RKNN_neighbors(&points3d[0], 5, 3, -1, 4, IGRAPH_METRIC_L1); + printf("\n3d, 2 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points3d[0], 5, 3, 2, -1, IGRAPH_METRIC_L1); + printf("\n3d, unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points3d[0], 5, 3, -1, -1, IGRAPH_METRIC_L1); + + printf("\n4d 2 neighbors, cutoff 5\n"); + RKNN_neighbors(&points4d[0], 5, 4, 2, 5, IGRAPH_METRIC_L1); + printf("\n4d 1 neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points4d[0], 5, 4, 1, -1, IGRAPH_METRIC_L1); + printf("\n4d unlimited neighbors, cutoff INFINITY\n"); + RKNN_neighbors(&points4d[0], 5, 4, -1, -1, IGRAPH_METRIC_L1); + printf("\n4d unlimited neighbors, cutoff 8\n"); + RKNN_neighbors(&points4d[0], 5, 4, -1, 8, IGRAPH_METRIC_L1); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_nearest_neighbor_graph.out b/tests/unit/igraph_nearest_neighbor_graph.out new file mode 100644 index 0000000..ab9d04e --- /dev/null +++ b/tests/unit/igraph_nearest_neighbor_graph.out @@ -0,0 +1,809 @@ +# L2 Metric test suite: + +1d 2 neighbors, cutoff 3 +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 3 +0 4 +1 3 +3 0 +3 1 +4 0 +4 3 +} + +1d 1 neighbors, cutoff INFINITY +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 4 +1 3 +2 1 +3 0 +4 0 +} + +1d unlimited neighbors, cutoff INFINITY +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +1d unlimited neighbors, cutoff 7 +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 3 +0 4 +1 3 +3 0 +3 1 +3 4 +4 0 +4 3 +} + +2d 2 neighbors, cutoff 5 +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +1 0 +3 4 +4 3 +} + +2d 1 neighbors, cutoff INFINITY +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +1 0 +2 1 +3 4 +4 3 +} + +2d unlimited neighbors, cutoff INFINITY +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +2d unlimited neighbors, cutoff 7 +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +0 4 +1 0 +1 2 +1 3 +1 4 +2 1 +3 1 +3 4 +4 0 +4 1 +4 3 +} + +2d 2 neighbors, cutoff 1.2, degenerate case +[ 0 0 + 1 0 + -1 0 + 0 1 + 0 -1 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 0 +2 0 +3 0 +4 0 +} + +3d, 2 neighbors, cutoff 4 +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +2 0 +3 1 +3 4 +4 0 +4 3 +} + +3d, unlimited neighbors, cutoff 4 +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +2 0 +3 1 +3 4 +4 0 +4 3 +} + +3d, 2 neighbors, cutoff INFINITY +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +1 4 +2 0 +2 4 +3 1 +3 4 +4 0 +4 3 +} + +3d, unlimited neighbors, cutoff INFINITY +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +4d 2 neighbors, cutoff 5 +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +1 4 +2 0 +2 4 +3 1 +3 4 +4 0 +4 3 +} + +4d 1 neighbors, cutoff INFINITY +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 2 +1 3 +2 0 +3 4 +4 3 +} + +4d unlimited neighbors, cutoff INFINITY +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +4d unlimited neighbors, cutoff 4 +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +2 0 +3 1 +3 4 +4 0 +4 3 +} + +3d, no points +[ 0-by-3 ] +directed: true +vcount: 0 +edges: { +} + +0d, no points +[ 0-by-0 ] +directed: true +vcount: 0 +edges: { +} + +3d, 1 point +[ 1 6 4 ] +directed: true +vcount: 1 +edges: { +} + +2d, zero neighbors +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +} + +2d, zero cutoff +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +} + +0d, 1 point should error + +Fibonacci spiral with 25 points, 1 neighbor unlimited cutoff. +[ 0 0 + -0.737369 -0.67549 + 0.123639 1.4088 + 1.05385 -1.37456 + -1.96943 0.348364 + 1.88669 1.20016 + -0.635898 -2.36551 + -1.21945 2.34797 + 2.6568 -0.97026 + -2.77304 -1.14467 + 1.34032 2.86418 + 0.992612 -3.1646 + -2.99718 1.73693 + 3.52145 0.774182 + -2.15194 -3.06091 + -0.49772 3.84087 + 3.0586 -2.57779 + -4.11958 -0.170358 + 3.00731 2.99267 + -0.201344 -4.35425 + -2.86534 3.43363 + 4.54165 -0.611073 + -3.85017 -2.67885 + 1.0526 4.67889 + 2.43568 -4.25058 ] +directed: true +vcount: 25 +edges: { +0 1 +1 0 +2 0 +3 8 +4 1 +5 13 +6 14 +7 2 +8 3 +9 17 +10 18 +11 19 +12 20 +13 5 +14 6 +15 7 +16 8 +17 9 +18 10 +19 11 +20 12 +21 13 +22 14 +23 15 +24 16 +} +# L1 Metric test suite: + +1d 2 neighbors, cutoff 3 +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 3 +0 4 +1 3 +3 0 +3 1 +4 0 +4 3 +} + +1d 1 neighbors, cutoff INFINITY +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 4 +1 3 +2 1 +3 0 +4 0 +} + +1d unlimited neighbors, cutoff INFINITY +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +1d unlimited neighbors, cutoff 7 +[ 12 + 8 + 5 + 10 + 12 ] +directed: true +vcount: 5 +edges: { +0 3 +0 4 +1 3 +3 0 +3 1 +3 4 +4 0 +4 3 +} + +2d 2 neighbors, cutoff 5 +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +3 4 +4 3 +} + +2d 1 neighbors, cutoff INFINITY +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +1 0 +2 1 +3 4 +4 3 +} + +2d unlimited neighbors, cutoff INFINITY +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +2d unlimited neighbors, cutoff 7 +[ 12 8 + 8 6 + 5 12 + 10 1 + 12 2 ] +directed: true +vcount: 5 +edges: { +0 1 +0 4 +1 0 +3 4 +4 0 +4 3 +} + +2d 2 neighbors, cutoff INFINITY, degenerate case +[ 0 0 + 1 0 + -1 0 + 0 1 + 0 -1 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 0 +1 2 +2 0 +2 1 +3 0 +3 1 +4 0 +4 1 +} + +3d, 2 neighbors, cutoff 4 +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +3 4 +4 3 +} + +3d, unlimited neighbors, cutoff 4 +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +3 4 +4 3 +} + +3d, 2 neighbors, cutoff INFINITY +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +1 4 +2 0 +2 4 +3 1 +3 4 +4 0 +4 3 +} + +3d, unlimited neighbors, cutoff INFINITY +[ 1 6 4 + 6 2 3 + 3 6 6 + 3 2 2 + 2 3 3 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +4d 2 neighbors, cutoff 5 +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +1 3 +3 1 +3 4 +4 3 +} + +4d 1 neighbors, cutoff INFINITY +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 2 +1 3 +2 0 +3 1 +4 3 +} + +4d unlimited neighbors, cutoff INFINITY +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 1 +0 2 +0 3 +0 4 +1 0 +1 2 +1 3 +1 4 +2 0 +2 1 +2 3 +2 4 +3 0 +3 1 +3 2 +3 4 +4 0 +4 1 +4 2 +4 3 +} + +4d unlimited neighbors, cutoff 8 +[ 1 6 4 4 + 6 2 3 3 + 3 6 6 5 + 3 2 2 3 + 2 3 3 4 ] +directed: true +vcount: 5 +edges: { +0 2 +0 4 +1 3 +1 4 +2 0 +3 1 +3 4 +4 0 +4 1 +4 3 +} diff --git a/tests/unit/igraph_neighborhood.c b/tests/unit/igraph_neighborhood.c new file mode 100644 index 0000000..73a651d --- /dev/null +++ b/tests/unit/igraph_neighborhood.c @@ -0,0 +1,95 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_empty, g_lm; + igraph_vector_int_list_t result; + igraph_vs_t vids; + + igraph_vector_int_list_init(&result, 0); + igraph_vs_all(&vids); + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_empty, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 0:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 0, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 1, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_IN, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 10, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 10, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 2, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL:\n"); + IGRAPH_ASSERT(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ 4) == IGRAPH_SUCCESS); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT:\n"); + igraph_neighborhood(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 0); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT:\n"); + igraph_neighborhood(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2); + print_vector_int_list(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN:\n"); + igraph_neighborhood(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_IN, /*mindist*/ 2); + print_vector_int_list(&result); + + VERIFY_FINALLY_STACK(); + + printf("Negative mindist.\n"); + CHECK_ERROR(igraph_neighborhood(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ -4), IGRAPH_EINVAL); + + igraph_vector_int_list_destroy(&result); + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_neighborhood.out b/tests/unit/igraph_neighborhood.out new file mode 100644 index 0000000..c9d6fe6 --- /dev/null +++ b/tests/unit/igraph_neighborhood.out @@ -0,0 +1,85 @@ +No vertices: +{ +} +Directed graph with loops and multi-edges, order 0: +{ + 0: ( 0 ) + 1: ( 1 ) + 2: ( 2 ) + 3: ( 3 ) + 4: ( 4 ) + 5: ( 5 ) +} +Directed graph with loops and multi-edges, order 1, ignoring direction: +{ + 0: ( 0 1 2 ) + 1: ( 1 0 3 ) + 2: ( 2 0 3 ) + 3: ( 3 1 2 4 ) + 4: ( 4 3 ) + 5: ( 5 ) +} +Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN: +{ + 0: ( 0 2 ) + 1: ( 1 0 ) + 2: ( 2 0 ) + 3: ( 3 1 2 ) + 4: ( 4 3 ) + 5: ( 5 ) +} +Directed graph with loops and multi-edges, order 10, ignoring direction: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 1 0 3 2 4 ) + 2: ( 2 0 3 1 4 ) + 3: ( 3 1 2 4 0 ) + 4: ( 4 3 1 2 0 ) + 5: ( 5 ) +} +Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT: +{ + 0: ( 3 ) + 1: ( 4 ) + 2: ( 1 4 ) + 3: ( ) + 4: ( ) + 5: ( ) +} +Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL: +{ + 0: ( ) + 1: ( ) + 2: ( ) + 3: ( ) + 4: ( ) + 5: ( ) +} +Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 1 3 4 ) + 2: ( 2 0 3 1 4 ) + 3: ( 3 4 ) + 4: ( 4 ) + 5: ( 5 ) +} +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT: +{ + 0: ( 3 4 ) + 1: ( 4 ) + 2: ( 1 4 ) + 3: ( ) + 4: ( ) + 5: ( ) +} +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN: +{ + 0: ( ) + 1: ( 2 ) + 2: ( ) + 3: ( 0 ) + 4: ( 1 2 0 ) + 5: ( ) +} +Negative mindist. diff --git a/tests/unit/igraph_neighborhood_graphs.c b/tests/unit/igraph_neighborhood_graphs.c new file mode 100644 index 0000000..1f568e8 --- /dev/null +++ b/tests/unit/igraph_neighborhood_graphs.c @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_clear(igraph_graph_list_t *result) { + igraph_int_t i; + igraph_t *g; + for (i = 0; i < igraph_graph_list_size(result); i++) { + g = igraph_graph_list_get_ptr(result, i); + if (igraph_vcount(g) == 0) { + printf("null graph\n"); + } else if (igraph_ecount(g) == 0 && igraph_vcount(g) == 1) { + printf("One vertex, no edges\n"); + } else { + print_graph_canon(g); + } + } + printf("\n"); + igraph_graph_list_clear(result); +} + +int main(void) { + igraph_t g_empty, g_lm; + igraph_graph_list_t result; + igraph_vs_t vids; + + igraph_graph_list_init(&result, 0); + igraph_vs_all(&vids); + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_empty, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 0:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 0, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 1, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_IN, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 10, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 10, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 2, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL:\n"); + IGRAPH_ASSERT(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ 4) == IGRAPH_SUCCESS); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT:\n"); + igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 0); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT:\n"); + igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2); + print_and_clear(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN:\n"); + igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_IN, /*mindist*/ 2); + print_and_clear(&result); + + VERIFY_FINALLY_STACK(); + + printf("Negative mindist.\n"); + CHECK_ERROR(igraph_neighborhood_graphs(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ -4), IGRAPH_EINVAL); + + igraph_graph_list_destroy(&result); + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_neighborhood_graphs.out b/tests/unit/igraph_neighborhood_graphs.out new file mode 100644 index 0000000..e586388 --- /dev/null +++ b/tests/unit/igraph_neighborhood_graphs.out @@ -0,0 +1,246 @@ +No vertices: + +Directed graph with loops and multi-edges, order 0: +One vertex, no edges +directed: true +vcount: 1 +edges: { +0 0 +} +One vertex, no edges +One vertex, no edges +One vertex, no edges +One vertex, no edges + +Directed graph with loops and multi-edges, order 1, ignoring direction: +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 1 +2 0 +} +directed: true +vcount: 3 +edges: { +0 1 +1 1 +1 2 +} +directed: true +vcount: 3 +edges: { +0 1 +1 0 +1 2 +} +directed: true +vcount: 4 +edges: { +0 0 +0 2 +1 2 +2 3 +2 3 +} +directed: true +vcount: 2 +edges: { +0 1 +0 1 +} +One vertex, no edges + +Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN: +directed: true +vcount: 2 +edges: { +0 1 +1 0 +} +directed: true +vcount: 2 +edges: { +0 1 +1 1 +} +directed: true +vcount: 2 +edges: { +0 1 +1 0 +} +directed: true +vcount: 3 +edges: { +0 0 +0 2 +1 2 +} +directed: true +vcount: 2 +edges: { +0 1 +0 1 +} +One vertex, no edges + +Directed graph with loops and multi-edges, order 10, ignoring direction: +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +One vertex, no edges + +Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT: +One vertex, no edges +One vertex, no edges +directed: true +vcount: 2 +edges: { +0 0 +} +null graph +null graph +null graph + +Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL: +null graph +null graph +null graph +null graph +null graph +null graph + +Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT: +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 3 +edges: { +0 0 +0 1 +1 2 +1 2 +} +directed: true +vcount: 5 +edges: { +0 1 +0 2 +1 1 +1 3 +2 0 +2 3 +3 4 +3 4 +} +directed: true +vcount: 2 +edges: { +0 1 +0 1 +} +One vertex, no edges +One vertex, no edges + +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT: +directed: true +vcount: 2 +edges: { +0 1 +0 1 +} +One vertex, no edges +directed: true +vcount: 2 +edges: { +0 0 +} +null graph +null graph +null graph + +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN: +null graph +One vertex, no edges +null graph +One vertex, no edges +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 1 +2 0 +} +null graph + +Negative mindist. diff --git a/tests/unit/igraph_neighborhood_size.c b/tests/unit/igraph_neighborhood_size.c new file mode 100644 index 0000000..9aef41b --- /dev/null +++ b/tests/unit/igraph_neighborhood_size.c @@ -0,0 +1,97 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_empty, g_lm; + igraph_vector_int_t result; + igraph_vs_t vids; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&result, 0); + igraph_vs_all(&vids); + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_empty, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 0:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 0, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 1, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 1, + /*mode*/ IGRAPH_IN, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 10, ignoring direction:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 10, + /*mode*/ IGRAPH_ALL, /*mindist*/ 0) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 2, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL:\n"); + IGRAPH_ASSERT(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ 4) == IGRAPH_SUCCESS); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT:\n"); + igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 0); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT:\n"); + igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_OUT, /*mindist*/ 2); + print_vector_int(&result); + + printf("Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN:\n"); + igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ -1, + /*mode*/ IGRAPH_IN, /*mindist*/ 2); + print_vector_int(&result); + + VERIFY_FINALLY_STACK(); + + printf("Negative mindist.\n"); + CHECK_ERROR(igraph_neighborhood_size(&g_lm, &result, vids, /*order*/ 4, + /*mode*/ IGRAPH_ALL, /*mindist*/ -4), IGRAPH_EINVAL); + + igraph_vector_int_destroy(&result); + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_neighborhood_size.out b/tests/unit/igraph_neighborhood_size.out new file mode 100644 index 0000000..7489624 --- /dev/null +++ b/tests/unit/igraph_neighborhood_size.out @@ -0,0 +1,21 @@ +No vertices: +( ) +Directed graph with loops and multi-edges, order 0: +( 1 1 1 1 1 1 ) +Directed graph with loops and multi-edges, order 1, ignoring direction: +( 3 3 3 4 2 1 ) +Directed graph with loops and multi-edges, order 1, only checking IGRAPH_IN: +( 2 2 2 3 2 1 ) +Directed graph with loops and multi-edges, order 10, ignoring direction: +( 5 5 5 5 5 1 ) +Directed graph with loops and multi-edges, order 2, mindist 2, IGRAPH_OUT: +( 1 1 2 0 0 0 ) +Directed graph with loops and multi-edges, order 4, mindist 4, IGRAPH_ALL: +( 0 0 0 0 0 0 ) +Directed graph with loops and multi-edges, infinite order, IGRAPH_OUT: +( 5 3 5 2 1 1 ) +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_OUT: +( 2 1 2 0 0 0 ) +Directed graph with loops and multi-edges, infinite order, mindist 2, IGRAPH_IN: +( 0 1 0 1 3 0 ) +Negative mindist. diff --git a/tests/unit/igraph_neighbors.c b/tests/unit/igraph_neighbors.c new file mode 100644 index 0000000..cd84369 --- /dev/null +++ b/tests/unit/igraph_neighbors.c @@ -0,0 +1,182 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_int_t pnode, igraph_neimode_t mode, igraph_loops_t loops, igraph_bool_t multiple) { + igraph_vector_int_t neis; + igraph_vector_int_init(&neis, 0); + IGRAPH_ASSERT(igraph_neighbors(graph, &neis, pnode, mode, loops, multiple) == IGRAPH_SUCCESS); + print_vector_int(&neis); + igraph_vector_int_destroy(&neis); +} + + +int main(void) { + igraph_t g_1, g_lm, g_lmu, g_lmmlu, g_s1, g_s2; + + /* g_1: one vertex */ + igraph_small(&g_1, 1, 0, -1); + + /* g_lm: graph with loop and multiple edges, directed */ + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + /* g_lmu: graph with loop and multiple edges, undirected */ + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + /* g_lmmlu: graph with loop, multiple and multiple loop edges, undirected */ + igraph_small(&g_lmmlu, 5, 0, + 0, 1, 0, 3, 0, 8, + 1, 2, + 2, 2, 2, 3, + 3, 0, 3, 4, + 4, 4, 4, 4, 4, 5, 4, 5, 4, 5, + 5, 6, + 6, 7, 6, 8, + 8, 0, + -1 + ); + + igraph_small(&g_s1, 2, 1, 0,1, 0,1, 1,0, 1,0, -1); + igraph_small(&g_s2, 2, 1, 0,1, 1,0, 1,0, -1); + + igraph_vector_int_t neis; + igraph_vector_int_init(&neis, 0); + + printf("One vertex:\n"); + call_and_print(&g_1, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_IN, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_OUT, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_ALL, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Vertex with multiple edges, undirected, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmu, 0, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_IN, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_OUT, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + + printf("Vertex with multiple edges, IGRAPH_ALL, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lm, 0, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("Vertex with multiple edges, undirected, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmu, 0, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, undirected, IGRAPH_NO_LOOPS:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, undirected, IGRAPH_LOOPS_ONCE:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lm, 1, IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lm, 1, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Vertex 1 with loop, undirected, IGRAPH_LOOPS_TWICE:\n"); + call_and_print(&g_lmu, 1, IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 2, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_lmmlu, 4, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); + + printf("Graph with 2 edges from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_s1, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + printf("Graph with 1 edge from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL, IGRAPH_MULTIPLE:\n"); + call_and_print(&g_s2, 0, IGRAPH_ALL, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /* + printf("Trying IGRAPH_LOOPS_TWICE with IGRAPH_OUT:\n"); + IGRAPH_ASSERT(igraph_neighbors(&g_lm, &neis, 0, IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE) == IGRAPH_EINVAL); + */ + + printf("Trying invalid vertex ID:\n"); + IGRAPH_ASSERT(igraph_neighbors(&g_lm, &neis, 42, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE) == IGRAPH_EINVVID); + + printf("Trying invalid mode:\n"); + IGRAPH_ASSERT(igraph_neighbors(&g_lm, &neis, 0, (igraph_neimode_t) 42, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE) == IGRAPH_EINVMODE); + + igraph_destroy(&g_1); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + igraph_destroy(&g_lmmlu); + igraph_destroy(&g_s1); + igraph_destroy(&g_s2); + igraph_vector_int_destroy(&neis); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_neighbors.out b/tests/unit/igraph_neighbors.out new file mode 100644 index 0000000..bf261ed --- /dev/null +++ b/tests/unit/igraph_neighbors.out @@ -0,0 +1,66 @@ +One vertex: +( ) +Vertex with multiple edges, IGRAPH_IN, IGRAPH_MULTIPLE: +( 2 2 ) +Vertex with multiple edges, IGRAPH_OUT, IGRAPH_MULTIPLE: +( 1 2 ) +Vertex with multiple edges, IGRAPH_ALL, IGRAPH_MULTIPLE: +( 1 2 2 2 ) +Vertex with multiple edges, undirected, IGRAPH_MULTIPLE: +( 1 2 2 2 ) +Vertex with multiple edges, IGRAPH_IN, IGRAPH_NO_MULTIPLE: +( 2 ) +Vertex with multiple edges, IGRAPH_OUT, IGRAPH_NO_MULTIPLE: +( 1 2 ) +Vertex with multiple edges, IGRAPH_ALL, IGRAPH_NO_MULTIPLE: +( 1 2 ) +Vertex with multiple edges, undirected, IGRAPH_NO_MULTIPLE: +( 1 2 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_NO_LOOPS: +( 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_NO_LOOPS: +( 0 3 ) +Vertex 1 with loop, undirected, IGRAPH_NO_LOOPS: +( 0 3 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_ONCE: +( 1 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_ONCE: +( 0 1 3 ) +Vertex 1 with loop, undirected, IGRAPH_LOOPS_ONCE: +( 0 1 3 ) +Vertex 1 with loop, IGRAPH_OUT, IGRAPH_LOOPS_TWICE: +( 1 3 ) +Vertex 1 with loop, IGRAPH_ALL, IGRAPH_LOOPS_TWICE: +( 0 1 1 3 ) +Vertex 1 with loop, undirected, IGRAPH_LOOPS_TWICE: +( 0 1 1 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE: +( 1 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE: +( 1 2 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE: +( 1 2 2 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE: +( 1 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE: +( 1 2 3 ) +Vertex 2 with single loop edge, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE: +( 1 2 2 3 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE: +( 3 5 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_NO_MULTIPLE: +( 3 4 5 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_NO_MULTIPLE: +( 3 4 4 5 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE: +( 3 5 5 5 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE: +( 3 4 4 5 5 5 ) +Vertex 4 with multiple loop edges, undirected, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE: +( 3 4 4 4 4 5 5 5 ) +Graph with 2 edges from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL, IGRAPH_MULTIPLE: +( 1 1 1 1 ) +Graph with 1 edge from 0 to 1, and 2 from 1 to 0, IGRAPH_ALL, IGRAPH_MULTIPLE: +( 1 1 1 ) +Trying invalid vertex ID: +Trying invalid mode: diff --git a/tests/unit/igraph_pagerank.c b/tests/unit/igraph_pagerank.c new file mode 100644 index 0000000..89a79c3 --- /dev/null +++ b/tests/unit/igraph_pagerank.c @@ -0,0 +1,400 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "test_utilities.h" + +int is_almost_one(igraph_real_t x) { + /* 2^5 = 32 is 5 binary digits of tolerance */ + if (fabs(x - 1) > 32*DBL_EPSILON) { + printf("Expected value to be 1, but actually got %15g.", x); + return 0; + } + return 1; +} + +int main(void) { + + igraph_t g; + igraph_vector_t res, reset, weights; + igraph_arpack_options_t arpack_options; + igraph_real_t value; + igraph_error_t err; + + /* The ARPACK method uses a random perturbation to the in-degrees + to set the starting vector for ARPACK. */ + igraph_rng_seed(igraph_rng_default(), 137); + + igraph_arpack_options_init(&arpack_options); + + /* Test graphs taken from http://www.iprcom.com/papers/pagerank/ */ + + printf("\nTest graph 1\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0,1, 1,2, 2,0, 3,2, 0,2, + -1); + + igraph_vector_init(&res, 0); + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&res); + + igraph_destroy(&g); + + printf("\nTest graph 2\n"); + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0,1, 0,2, 0,3, 1,0, 2,0, 3,0, + 3,4, 3,5, 3,6, 3,7, 4,0, 5,0, + 6,0, 7,0, + -1); + + igraph_vector_init(&res, 0); + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&res); + igraph_destroy(&g); + + /* Undirected star graph */ + + printf("\nUndirected star\n"); + + igraph_star(&g, 11, IGRAPH_STAR_UNDIRECTED, 0); + igraph_vector_init(&res, 0); + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + /* Check twice more for consistency, this time without explicitly + * supplied ARPACK options */ + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, 0); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, 0); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + /* Check personalized PageRank */ + + printf("\nPersonalized PageRank\n"); + + igraph_personalized_pagerank_vs(&g, 0, &res, &value, + igraph_vss_1(1), 0.5, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_personalized_pagerank_vs(&g, 0, &res, &value, + igraph_vss_1(1), 0.5, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + + /* Errors */ + + igraph_set_error_handler(igraph_error_handler_ignore); + igraph_vector_init(&reset, 2); + err = igraph_personalized_pagerank(&g, 0, &res, 0, &reset, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, + &arpack_options); + IGRAPH_ASSERT(err == IGRAPH_EINVAL); + + err = igraph_personalized_pagerank(&g, 0, &res, 0, &reset, 0.85, 0, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + IGRAPH_ASSERT(err == IGRAPH_EINVAL); + + igraph_vector_resize(&reset, 10); + igraph_vector_fill(&reset, 0); + err = igraph_personalized_pagerank(&g, 0, + &res, 0, + &reset, 0.85, 0, igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + IGRAPH_ASSERT(err == IGRAPH_EINVAL); + + err = igraph_personalized_pagerank(&g, 0, + &res, 0, + &reset, 0.85, 0, igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + IGRAPH_ASSERT(err == IGRAPH_EINVAL); + + igraph_vector_destroy(&reset); + igraph_destroy(&g); + igraph_set_error_handler(igraph_error_handler_abort); + + + /* Special cases: check for empty graph */ + /* The ARPACK method has a special code path for this case */ + + printf("\nEdgeless graph\n"); + + igraph_empty(&g, 10, IGRAPH_UNDIRECTED); + igraph_pagerank(&g, 0, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, 0, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_destroy(&g); + + /* Special cases: check for empty graph, personalized */ + /* The ARPACK method has a special code path for this case */ + + printf("\nEdgeless graph, personalized PageRank\n"); + + igraph_empty(&g, 4, IGRAPH_UNDIRECTED); + igraph_vector_init_range(&reset, 1, 5); + + igraph_personalized_pagerank(&g, 0, &res, &value, &reset, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_personalized_pagerank(&g, 0, &res, &value, &reset, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + /* We also test the personalized case on a non-empty disconnected case, + * which does not use the special code path for the ARPACK version. + * The result must be the same for ARPACK and PRPACK. */ + + printf("\nOne edge, two isolated vertices, personalized\n"); + igraph_add_edge(&g, 0, 1); + + igraph_personalized_pagerank(&g, 0, &res, &value, &reset, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_personalized_pagerank(&g, 0, &res, &value, &reset, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&reset); + igraph_destroy(&g); + + /* Special cases: check for full graph, zero weights */ + /* The ARPACK method has a special code path for this case */ + + printf("\nComplete graph, zero weights\n"); + + igraph_full(&g, 10, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_vector_init(&weights, 45); + igraph_vector_fill(&weights, 0); + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* Another test case for PageRank (bug #792352) */ + + printf("\nTest graph 3\n"); + + igraph_small(&g, 9, IGRAPH_DIRECTED, 0, 5, 1, 5, 2, 0, 3, 1, 5, 4, 5, 7, 6, 0, 8, 0, 8, 1, -1); + igraph_vector_init(&weights, 9); + VECTOR(weights)[0] = 4; + VECTOR(weights)[1] = 5; + VECTOR(weights)[2] = 5; + VECTOR(weights)[3] = 4; + VECTOR(weights)[4] = 4; + VECTOR(weights)[5] = 4; + VECTOR(weights)[6] = 3; + VECTOR(weights)[7] = 4; + VECTOR(weights)[8] = 4; + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + /* Multigraph */ + + printf("\nSmall undirected multigraph (unweighted)\n"); + + igraph_small(&g, 0, IGRAPH_UNDIRECTED, + 0,1, 1,2, 1,2, + -1); + + igraph_pagerank(&g, NULL, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, NULL, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + printf("\nSmall undirected multigraph (unit weights)\n"); + + igraph_vector_init(&weights, 3); + igraph_vector_fill(&weights, 1.0); + + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + printf("ARPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, &weights, &res, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, 0); + printf("PRPACK: "); print_vector(&res); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + igraph_vector_destroy(&res); + + /* Graph with more than 127 vertices. PRPACK uses a different method above this size. */ + + { + igraph_vector_int_t edges_to_delete; + igraph_vector_t res_arpack, res_prpack; + igraph_vector_t weights; + igraph_int_t i, n; + + printf("\nLarge test graph, unweighted\n"); + + /* 243 vertices, 729 edges */ + igraph_de_bruijn(&g, 3, 5); + + /* We delete some edges to break the symmetry of the graph. + * Otherwise all vertices would have the same PageRank. */ + igraph_vector_int_init_range(&edges_to_delete, 0, 38); + igraph_delete_edges(&g, igraph_ess_vector(&edges_to_delete)); + igraph_vector_int_destroy(&edges_to_delete); + + /* Note: This test graph is not connected and has self-loops. */ + + igraph_vector_init(&res_arpack, 0); + igraph_vector_init(&res_prpack, 0); + + igraph_pagerank(&g, NULL, &res_arpack, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, NULL, &res_prpack, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, NULL); + IGRAPH_ASSERT(is_almost_one(value)); + + n = igraph_vector_size(&res_arpack); + for (i=0; i < n; ++i) { + igraph_real_t ar = VECTOR(res_arpack)[i]; + igraph_real_t pr = VECTOR(res_prpack)[i]; + if (fabs(ar - pr) > 1e-12) { + printf("Unexpected difference between ARPACK and PRPACK results for vertex %" IGRAPH_PRId ":\n" + "ARPACK: %g\n" + "PRPACK: %g\n" + "Difference: %g\n", + i, ar, pr, fabs(ar - pr)); + } + } + + printf("\nLarge test graph, weighted\n"); + + igraph_vector_init_range(&weights, igraph_ecount(&g) + 1, 2*igraph_ecount(&g) + 1); + + igraph_pagerank(&g, &weights, &res_arpack, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_ARPACK, &arpack_options); + IGRAPH_ASSERT(is_almost_one(value)); + + igraph_pagerank(&g, &weights, &res_prpack, &value, 0.85, 1, + igraph_vss_all(), IGRAPH_PAGERANK_ALGO_PRPACK, NULL); + IGRAPH_ASSERT(is_almost_one(value)); + + n = igraph_vector_size(&res_arpack); + for (i=0; i < n; ++i) { + igraph_real_t ar = VECTOR(res_arpack)[i]; + igraph_real_t pr = VECTOR(res_prpack)[i]; + if (fabs(ar - pr) > 1e-12) { + printf("Unexpected difference between ARPACK and PRPACK results for vertex %" IGRAPH_PRId ":\n" + "ARPACK: %g\n" + "PRPACK: %g\n" + "Difference: %g\n", + i, ar, pr, fabs(ar - pr)); + } + } + + igraph_vector_destroy(&weights); + + igraph_vector_destroy(&res_arpack); + igraph_vector_destroy(&res_prpack); + + igraph_destroy(&g); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_pagerank.out b/tests/unit/igraph_pagerank.out new file mode 100644 index 0000000..38ae365 --- /dev/null +++ b/tests/unit/igraph_pagerank.out @@ -0,0 +1,52 @@ + +Test graph 1 +ARPACK: ( 0.288717 0.201989 0.389109 0.120186 ) +PRPACK: ( 0.288717 0.201989 0.389109 0.120186 ) + +Test graph 2 +ARPACK: ( 0.336204 0.0759047 0.0759047 0.205964 0.0765056 0.0765056 0.0765056 0.0765056 ) +PRPACK: ( 0.336204 0.0759047 0.0759047 0.205964 0.0765056 0.0765056 0.0765056 0.0765056 ) + +Undirected star +ARPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) +PRPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) +ARPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) +PRPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) +ARPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) +PRPACK: ( 0.46683 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 0.053317 ) + +Personalized PageRank +ARPACK: ( 0.333333 0.516667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 ) +PRPACK: ( 0.333333 0.516667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 0.0166667 ) + +Edgeless graph +ARPACK: ( 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 ) +PRPACK: ( 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 ) + +Edgeless graph, personalized PageRank +ARPACK: ( 0.1 0.2 0.3 0.4 ) +PRPACK: ( 0.1 0.2 0.3 0.4 ) + +One edge, two isolated vertices, personalized +ARPACK: ( 0.36036 0.38038 0.111111 0.148148 ) +PRPACK: ( 0.36036 0.38038 0.111111 0.148148 ) + +Complete graph, zero weights +ARPACK: ( 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 ) +PRPACK: ( 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 ) + +Test graph 3 +ARPACK: ( 0.143734 0.104639 0.045995 0.045995 0.155268 0.257112 0.045995 0.155268 0.045995 ) +PRPACK: ( 0.143734 0.104639 0.045995 0.045995 0.155268 0.257112 0.045995 0.155268 0.045995 ) + +Small undirected multigraph (unweighted) +ARPACK: ( 0.187838 0.486486 0.325676 ) +PRPACK: ( 0.187838 0.486486 0.325676 ) + +Small undirected multigraph (unit weights) +ARPACK: ( 0.187838 0.486486 0.325676 ) +PRPACK: ( 0.187838 0.486486 0.325676 ) + +Large test graph, unweighted + +Large test graph, weighted diff --git a/tests/unit/igraph_path_length_hist.c b/tests/unit/igraph_path_length_hist.c new file mode 100644 index 0000000..2d529b8 --- /dev/null +++ b/tests/unit/igraph_path_length_hist.c @@ -0,0 +1,68 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_vector_t *res, igraph_bool_t directed) { + igraph_real_t unconnected; + igraph_path_length_hist(g, res, &unconnected, directed); + + printf("result: "); + igraph_vector_print(res); + printf("unconnected: %g\n\n", unconnected); + + igraph_destroy(g); + +} + +int main(void) { + igraph_t g; + igraph_vector_t res; + + igraph_vector_init(&res, 0); + + printf("Graph with no vertices:\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, &res, 1); + + printf("Graph with one vertex:\n"); + igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, &res, 1); + + printf("Graph with two vertices:\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, &res, 1); + + printf("Graph with two connected vertices:\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + print_and_destroy(&g, &res, 1); + + printf("Graph with loops and multiple edges, undirected:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, &res, 0); + + printf("Graph with loops and multiple edges, directed:\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,2, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, &res, 1); + + igraph_vector_destroy(&res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_path_length_hist.out b/tests/unit/igraph_path_length_hist.out new file mode 100644 index 0000000..f636d3a --- /dev/null +++ b/tests/unit/igraph_path_length_hist.out @@ -0,0 +1,24 @@ +Graph with no vertices: +result: +unconnected: 0 + +Graph with one vertex: +result: +unconnected: 0 + +Graph with two vertices: +result: +unconnected: 1 + +Graph with two connected vertices: +result: 1 +unconnected: 0 + +Graph with loops and multiple edges, undirected: +result: 6 3 1 +unconnected: 5 + +Graph with loops and multiple edges, directed: +result: 7 5 1 +unconnected: 17 + diff --git a/tests/unit/igraph_perfect.c b/tests/unit/igraph_perfect.c new file mode 100644 index 0000000..ab5474e --- /dev/null +++ b/tests/unit/igraph_perfect.c @@ -0,0 +1,134 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph, comp_graph; + igraph_bool_t is_perfect; + igraph_error_handler_t *ehandler; + + igraph_rng_seed(igraph_rng_default(), 42); + + // Bipartite + //========================================================== + igraph_bipartite_game_gnm(&graph, NULL, 10, 10, 20, IGRAPH_UNDIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // Complement to star graph size 10 - chordal + //========================================================== + igraph_star(&graph, 10, IGRAPH_STAR_UNDIRECTED, 0); + igraph_complementer(&comp_graph, &graph, 0); + igraph_is_perfect(&comp_graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + igraph_destroy(&comp_graph); + + // A cycle of size 5 + //========================================================== + igraph_ring(&graph, 5, IGRAPH_UNDIRECTED, 0, 1); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(!is_perfect); + igraph_destroy(&graph); + + // A complement cycle of size 7 + //========================================================== + igraph_ring(&graph, 7, IGRAPH_UNDIRECTED, 0, 1); + igraph_complementer(&comp_graph, &graph, 0); + igraph_is_perfect(&comp_graph, &is_perfect); + IGRAPH_ASSERT(!is_perfect); + igraph_destroy(&graph); + igraph_destroy(&comp_graph); + + // A random tree + //========================================================== + igraph_tree_game(&graph, 10, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // 4X4 grid + //========================================================== + igraph_vector_int_t dims; + igraph_vector_int_init(&dims, 2); + VECTOR(dims)[0] = 4; + VECTOR(dims)[1] = 4; + igraph_square_lattice(&graph, &dims, 1, IGRAPH_UNDIRECTED, /* mutual */ 0, /* periodic */ 0); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + igraph_vector_int_destroy(&dims); + + // Paley graph of order 9 + //========================================================== + igraph_small(&graph, 9, IGRAPH_UNDIRECTED, + 0, 1, 0, 3, 0, 6, 0, 2, 1, 2, 1, 4, 1, 7, 2, 5, 2, 8, + 3, 4, 3, 5, 3, 6, 4, 5, 4, 7, 5, 8, 6, 7, 7, 8, 6, 8, -1); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // famous graph - chvatal + //========================================================== + igraph_famous(&graph, "Chvatal"); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(!is_perfect); + igraph_destroy(&graph); + + // famous graph - house + //========================================================== + igraph_famous(&graph, "House"); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // Null graph + //========================================================== + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // Singleton graph + //========================================================== + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // Empty graph + //========================================================== + igraph_empty(&graph, 2, IGRAPH_UNDIRECTED); + igraph_is_perfect(&graph, &is_perfect); + IGRAPH_ASSERT(is_perfect); + igraph_destroy(&graph); + + // Test directed paths + igraph_bipartite_game_gnm(&graph, NULL, 10, 10, 20, IGRAPH_DIRECTED, IGRAPH_ALL, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + ehandler = igraph_set_error_handler(igraph_error_handler_printignore); + IGRAPH_ASSERT(igraph_is_perfect(&graph, &is_perfect) == IGRAPH_EINVAL); + igraph_set_error_handler(ehandler); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_permute_vertices.c b/tests/unit/igraph_permute_vertices.c new file mode 100644 index 0000000..646c358 --- /dev/null +++ b/tests/unit/igraph_permute_vertices.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "test_utilities.h" + +int main(void) { + igraph_t graph, pgraph; + igraph_vector_int_t perm; + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&graph, 6, IGRAPH_DIRECTED, + 0,1, 1,0, 2,1, 2,2, 5,4, 5,4, + -1); + + printf("Graph before permuting vertices:\n"); + print_graph(&graph); + + igraph_vector_int_init_int(&perm, 6, + 0, 1, 5, 3, 4, 2); + + igraph_permute_vertices(&graph, &pgraph, &perm); + + printf("\nGraph after permuting vertices:\n"); + print_graph(&pgraph); + + igraph_destroy(&pgraph); + + /* Now we test invalid input. */ + + /* Out-of-range value in permutation */ + VECTOR(perm)[0] = 7; + CHECK_ERROR(igraph_permute_vertices(&graph, &pgraph, &perm), IGRAPH_EINVAL); + + /* Duplicate value in permutation */ + VECTOR(perm)[0] = 1; + CHECK_ERROR(igraph_permute_vertices(&graph, &pgraph, &perm), IGRAPH_EINVAL); + + /* Invalid permutation vector length */ + VECTOR(perm)[0] = 0; + VECTOR(perm)[2] = 2; + igraph_vector_int_resize(&perm, 5); + CHECK_ERROR(igraph_permute_vertices(&graph, &pgraph, &perm), IGRAPH_EINVAL); + + igraph_vector_int_destroy(&perm); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_permute_vertices.out b/tests/unit/igraph_permute_vertices.out new file mode 100644 index 0000000..7aa06aa --- /dev/null +++ b/tests/unit/igraph_permute_vertices.out @@ -0,0 +1,23 @@ +Graph before permuting vertices: +directed: true +vcount: 6 +edges: { +0 1 +1 0 +2 1 +2 2 +5 4 +5 4 +} + +Graph after permuting vertices: +directed: true +vcount: 6 +edges: { +0 1 +1 0 +5 1 +5 5 +2 4 +2 4 +} diff --git a/tests/unit/igraph_power_law_fit.c b/tests/unit/igraph_power_law_fit.c new file mode 100644 index 0000000..8d27c35 --- /dev/null +++ b/tests/unit/igraph_power_law_fit.c @@ -0,0 +1,330 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "test_utilities.h" + +void print_result(const igraph_plfit_result_t* result) { + printf("continuous = %s\n", result->continuous ? "true" : "false"); + printf("alpha = %.5f\n", result->alpha); + printf("xmin = %.5f\n", result->xmin); + printf("L = %.5f\n", result->L); + printf("D = %.5f\n", result->D); + printf("====================\n"); +} + +int test_continuous(void) { + igraph_plfit_result_t result; + igraph_vector_t vector; + double data[] = { 1.52219974, 6.80675663, 1.02798042, 1.31180733, 3.97473174, + 1.17209342, 1.64889191, 2.47764721, 1.32939375, 3.03762554, 1.62638327, + 6.08405495, 1.70890382, 1.05294973, 1.17408407, 4.48945532, 1.16777371, + 2.52502391, 1.09755984, 1.63838051, 1.03811206, 1.47224168, 1.57161431, + 1.60163451, 2.08280263, 1.04678340, 1.33317526, 1.58588741, 1.26484666, + 1.02367503, 1.57045702, 3.42374138, 1.23190611, 1.09378228, 1.04959505, + 1.05818408, 1.43879491, 2.22750459, 1.41027204, 1.81964745, 2.80239939, + 1.25399323, 1.07479219, 3.94616077, 1.26367914, 1.87367507, 1.35741026, + 1.14867526, 7.33024762, 1.87957274, 2.79258534, 1.21682159, 1.61194300, + 2.81885973, 1.21514746, 1.12850917, 51.85245035, 1.21883209, 1.04861029, + 1.69215609, 2.18429429, 1.59752172, 1.41909984, 3.14393355, 1.18298455, + 1.67063821, 1.88568524, 1.07445906, 1.45007973, 1.12568920, 1.56806310, + 1.36996101, 1.19440982, 6.57296980, 1.35860725, 1.06552137, 1.16950701, + 1.34750790, 1.66977492, 1.22658722, 1.62247444, 1.23458784, 8.55843760, + 1.70020162, 4.76368831, 1.04846170, 1.13689661, 1.94449567, 1.10584812, + 1.32525767, 1.26640912, 1.91372972, 1.56185373, 2.37829675, 1.04616674, + 2.43549177, 1.14961092, 1.82106455, 1.25818298, 1.64763037, 1.43019402, + 1.50439978, 1.90281251, 1.34827040, 1.57935671, 1.77260751, 1.06976614, + 1.12236012, 2.19770254, 1.51825533, 1.19027804, 1.08307524, 1.57912902, + 3.33313888, 2.14005088, 1.38341873, 1.20088138, 1.25870539, 1.03811620, + 1.86622820, 2.99310953, 1.55615055, 2.12364873, 4.49081000, 1.01274439, + 1.22373389, 3.79059729, 3.10099275, 2.70218546, 1.03609624, 2.20776919, + 1.00651347, 1.87344592, 1.04903307, 1.24899747, 1.20377911, 1.12706494, + 1.01706713, 7.01069306, 1.05363146, 2.50105512, 1.11168552, 1.71133998, + 1.17714528, 1.37986755, 2.20981534, 1.18179277, 2.07982010, 4.04967099, + 1.00680257, 1.62850069, 2.58816230, 1.35079027, 1.03382890, 4.54326500, + 1.62489905, 1.36102570, 1.52349738, 1.06606346, 7.80558026, 1.02602538, + 1.43330925, 1.36040920, 9.29692547, 15.27015690, 1.75966437, 1.02635409, + 1.40421505, 2.87296958, 1.46232202, 1.87065204, 3.37278803, 1.82589564, + 1.06488044, 1.72568108, 1.21062115, 4.39311214, 1.12636227, 2.20820528, + 1.09826903, 2.58989998, 1.34944949, 1.08654244, 2.38021951, 3.96308780, + 1.37494639, 1.18245279, 3.72506217, 3.79775023, 1.19018356, 2.86924476, + 3.40015888, 1.92317855, 1.55203754, 1.34985008, 1.31480190, 1.65899877, + 4.77446435, 1.41073246, 1.35555456, 2.40543613, 2.72162935, 1.34475982, + 1.41342115, 5.15278473, 1.69654436, 3.21081899, 1.18822397, 1.40394863, + 1.06793574, 1.67085563, 1.08125975, 1.11765459, 1.17245045, 1.15711479, + 1.18656910, 1.61296203, 1.71427634, 1.24017302, 2.05291524, 2.52658791, + 2.04645295, 34.07541626, 1.32670899, 1.03893757, 1.08957199, 5.55332328, + 1.17276097, 1.60389480, 2.02098430, 2.92934928, 1.00558653, 1.05830070, + 1.81440889, 3.85044779, 1.12317456, 1.39547640, 2.93105179, 1.95048788, + 1.05602445, 1.96855429, 1.60432293, 3.28820202, 1.50117325, 1.19775674, + 1.28280841, 1.08318646, 1.02098264, 1.24861938, 1.06511473, 1.07549717, + 3.57739126, 1.07265409, 1.06312441, 1.16296512, 3.83654484, 2.02366951, + 1.73168875, 1.60443228, 2.30779766, 1.50531775, 1.31925607, 1.87926179, + 1.86249354, 2.14768716, 2.31583955, 2.15651148, 1.29677318, 1.10110071, + 1.03383916, 1.50665009, 1.16502917, 1.40055008, 2.80847193, 1.29824634, + 2.76239920, 1.73123621, 1.15286577, 1.89493526, 1.63112634, 1.17828846, + 1.01293513, 1.84834048, 4.19026736, 1.82684815, 3.51812301, 1.33499862, + 2.03087497, 1.32419883, 1.34126954, 1.98250684, 1.00025697, 1.59416883, + 6.38249787, 2.79055559, 1.57750678, 1.36953983, 1.37513919, 3.63573178, + 1.15637432, 9.28386344, 1.16947695, 1.54995742, 1.44018755, 1.29332881, + 1.81274872, 1.14900153, 1.07117403, 1.17035915, 1.39229249, 1.96645872, + 1.09147706, 1.25211993, 1.07092474, 1.85394206, 1.29807741, 3.41499510, + 1.22444449, 1.00913782, 3.87431854, 1.01072376, 1.01186727, 3.00175639, + 2.52183377, 1.23992099, 1.69819010, 1.36850400, 1.14577814, 1.06035078, + 1.08414298, 1.55920217, 5.07059630, 1.15434572, 1.41873305, 1.24712256, + 1.10478618, 1.30707247, 1.85719110, 1.89873207, 1.72629431, 1.65171651, + 7.10864875, 2.31945709, 1.06722361, 1.26696259, 2.23845503, 1.38674196, + 1.91015397, 1.29590323, 1.10448028, 4.52757499, 2.00258408, 1.38299092, + 1.01431427, 1.54039270, 1.34880396, 1.08784083, 1.35553378, 1.37307373, + 1.32320467, 1.50261683, 6.91050685, 1.06083157, 1.20841351, 2.92719840, + 2.82178183, 2.05765813, 1.84621661, 1.04677388, 2.13801850, 1.39654855, + 1.13037727, 1.37887598, 1.03221650, 1.15981176, 1.09896163, 1.88624084, + 1.43459062, 1.54587662, 1.48604380, 2.06197392, 1.97079675, 4.31388672, + 2.94376994, 3.48708489, 1.09674551, 2.46926816, 1.23705940, 1.57512843, + 1.15595205, 1.18432818, 1.54298936, 1.60600489, 1.07361787, 1.38666771, + 1.45533003, 1.78940830, 1.33799752, 1.12955889, 4.59400278, 1.15170228, + 1.39346636, 1.61408789, 2.21293753, 5.33166143, 1.18147947, 1.54426891, + 1.32496426, 1.25037632, 3.31244261, 1.36211171, 1.82239599, 1.75235087, + 1.67044831, 1.24802350, 1.34776327, 1.34740665, 1.30664120, 1.06852680, + 1.22513631, 1.25310923, 1.36394926, 1.07796356, 3.10823551, 1.46770227, + 1.40264883, 1.08787681, 1.26460358, 1.10348946, 2.03168839, 1.09435135, + 1.66991715, 1.19738540, 1.28922229, 2.85704149, 1.33952521, 1.73497688, + 2.90052876, 5.34596348, 1.36399078, 3.38399264, 1.06089658, 1.09370142, + 1.37523679, 3.01964907, 1.40684792, 1.11312672, 2.44666372, 1.73953904, + 1.65569280, 1.05813000, 2.02893022, 1.72877601, 1.55758690, 1.83904301, + 1.14316984, 1.17792251, 1.44106281, 9.67126482, 1.93207441, 1.08242887, + 2.87271135, 2.19095115, 2.13195479, 1.02355472, 1.18218470, 1.30907724, + 1.13291587, 2.85659336, 12.62726889, 1.18818589, 1.02852443, 1.12838670, + 1.36349361, 1.34817100, 1.30535737, 3.22225028, 1.28680350, 1.83979657, + 1.11088952, 1.43866586, 8.52587567, 3.73988696, 2.65816056, 1.17373111, + 2.61567111, 3.24024082, 2.96798864, 1.05335616, 1.31159271, 1.36485918, + 1.24988767, 7.80609746, 1.54892174, 1.10682809, 1.21728827, 1.20429971, + 1.72719055, 1.78534831, 1.04414979, 1.25646988, 1.19788383, 1.08854812, + 1.04859628, 1.04676064, 5.07295341, 3.83595341, 1.61079632, 1.10528426, + 1.15050241, 2.78129736, 1.25494119, 1.28692155, 1.06812292, 3.29393761, + 1.37542463, 1.67241953, 1.21698665, 10.57727604, 8.63598976, 1.18886984, + 1.30609583, 9.47777457, 1.69612900, 2.23002585, 1.58461615, 1.04110023, + 3.08140806, 1.39599251, 1.06575789, 1.29741002, 1.75253864, 1.82594258, + 1.15111702, 1.17370053, 1.15254396, 1.94401179, 5.36344596, 4.66322185, + 1.15073993, 3.21478159, 1.39843306, 1.03961906, 5.72845289, 1.72454161, + 1.04610704, 1.38975310, 1.77732797, 1.10139931, 2.23656355, 1.89952669, + 1.72136921, 1.15798212, 1.59545971, 1.08789161, 1.93272206, 2.57480708, + 1.04977784, 2.00874078, 3.40065861, 1.00978603, 3.97804652, 1.54762586, + 1.01015493, 1.15148220, 1.15246483, 19.67426012, 1.33290993, 2.33137522, + 1.12841749, 1.73407057, 2.00469493, 1.27418995, 1.49814918, 1.10398785, + 1.20063760, 1.05536150, 1.87616599, 1.49305736, 1.60241346, 1.16666060, + 1.05013736, 1.77929210, 1.00206028, 3.41096863, 1.47499925, 1.14071240, + 1.65361002, 1.76466424, 8.49298111, 1.41069285, 2.11681605, 4.90260842, + 1.13029658, 1.20802818, 1.42525579, 1.00310774, 1.08082363, 9.95194247, + 2.82773946, 2.77420002, 1.82543685, 1.28557906, 1.97711769, 1.19001264, + 1.95712650, 1.54230291, 1.31625757, 2.36364128, 1.11523099, 1.00343756, + 1.71299382, 1.44667100, 2.38154868, 1.41174217, 1.80660493, 1.51020853, + 1.16761479, 1.25898190, 1.18150781, 1.58465451, 2.03560597, 3.48531184, + 1.21187672, 1.35111036, 1.02954922, 1.90892663, 3.99078548, 5.67385199, + 4.38055264, 1.17446048, 13.41617858, 1.60241740, 1.14811206, 4.68120263, + 3.83763710, 2.66095263, 1.83338503, 4.75973082, 1.08982301, 4.04104276, + 1.34220189, 1.06135891, 2.71185882, 1.46085873, 1.09915614, 10.35178646, + 2.54402271, 2.65696704, 1.31388649, 1.02942408, 1.57780748, 1.01552697, + 2.24860361, 2.22011778, 1.13595134, 1.11492512, 2.11966788, 1.20420149, + 1.11112428, 3.09324603, 2.87240762, 1.50486558, 1.92227231, 4.12480449, + 1.58244751, 1.69922308, 6.28134904, 2.91944178, 1.85386792, 1.41799519, + 1.64636127, 2.05837832, 1.07153521, 2.05376943, 2.60053549, 1.09773382, + 1.54671309, 1.68007415, 3.43941489, 1.41601033, 2.00237256, 1.20830978, + 1.25582363, 1.10830461, 1.24850906, 1.88035202, 1.70557719, 1.04191110, + 1.33501003, 1.33554804, 1.36935735, 4.79153510, 1.06566392, 1.14495966, + 1.90020028, 1.08266994, 1.20588153, 1.40730214, 4.34320304, 1.71762330, + 1.06620797, 1.39695239, 1.03024563, 3.94971225, 5.02945862, 1.06145571, + 1.42511911, 2.13889169, 1.04986044, 1.91400616, 5.50708156, 1.52870464, + 1.11303137, 1.05282759, 1.83793940, 3.05244089, 2.64499634, 1.51688076, + 2.63350152, 1.31014486, 1.69462474, 1.67792130, 1.34236945, 1.02358460, + 1.04593509, 1.04007620, 1.87990081, 1.28585413, 1.01636283, 3.55338495, + 1.19542700, 1.23630628, 1.32321942, 4.03762786, 1.25379147, 1.12330233, + 1.24966418, 1.26323243, 1.14779989, 1.20378343, 1.01531796, 1.44500318, + 1.72723672, 15.68799957, 1.37641063, 7.00788166, 3.89674130, 1.68303382, + 1.10089816, 1.72831362, 2.70479861, 1.75821836, 2.32404215, 2.64165162, + 1.42441301, 1.83256456, 1.12548819, 4.81273800, 2.52840227, 2.68430190, + 1.00928919, 1.02438446, 1.33909276, 2.32261242, 1.01299124, 1.07614975, + 1.66823898, 1.97172786, 1.01707292, 1.68325092, 1.76834032, 1.08952069, + 1.02265517, 1.96843176, 1.83351706, 1.92704772, 18.44811035, 1.00178046, + 2.70555953, 1.35839004, 1.04834633, 1.26649072, 2.87152600, 4.12536409, + 1.25200853, 1.71199647, 1.61175739, 1.26313274, 1.75224120, 2.70412800, + 1.33998630, 1.61271556, 2.65784769, 10.38771107, 1.33121364, 1.01207979, + 2.00238212, 2.50195600, 1.96917548, 1.71618169, 1.37050585, 10.11861690, + 1.18339112, 1.80083386, 2.88582103, 1.21935761, 2.37900131, 1.49449487, + 4.75106319, 2.33977804, 2.87963540, 1.01807103, 3.74847411, 1.71981276, + 1.50726964, 1.20723219, 1.37904840, 1.04565533, 1.59877004, 1.11481349, + 2.17320556, 2.07108468, 1.23274077, 1.75180110, 1.27558910, 1.63240839, + 1.58760550, 1.01266256, 1.30395323, 1.14618521, 1.02385023, 2.24198100, + 1.26765471, 1.15855534, 1.83936251, 1.32970987, 1.25844192, 1.31133485, + 4.74300303, 6.19325623, 1.31832913, 3.97645560, 1.00545340, 1.24431862, + 1.25855820, 1.15514241, 1.35986865, 1.72446070, 1.13069572, 2.45890932, + 1.00394684, 1.03533631, 1.87698184, 2.34576160, 1.03997887, 1.02694456, + 2.52227100, 2.66278467, 1.17002905, 3.42239624, 2.46753038, 1.17103623, + 1.07832850, 1.42782632, 1.29110546, 1.03435772, 1.33512109, 1.14337058, + 1.34103634, 1.15155161, 2.59805360, 2.09650343, 1.53399143, 1.02319185, + 1.32210667, 1.05720671, 1.20882651, 2.34881662, 1.05163662, 3.26219380, + 10.58124156, 1.07283644, 1.02105339, 1.23268679, 1.81469813, 1.49393533, + 1.29760853, 5.37676625, 1.02529938, 1.86815537, 1.57961476, 3.77408176, + 2.79405589, 3.25246617, 1.63913824, 3.12133428, 1.03787574, 4.17232960, + 1.33406468, 1.57119541, 1.13675102, 3.42874720, 1.13066210, 1.33896458, + 1.23883935, 1.35272696, 1.15172654, 2.18633755, 1.23251881, 1.59742606, + 1.08718410, 1.06168544, 1.19926517, 1.00214807, 1.29121086, 3.44575916, + 1.26524744, 1.16718301, 4.11789988, 1.25375574, 1.35753968, 1.69247751, + 1.28473150, 2.20669768, 1.53213883, 2.30598771, 1.68420243, 1.37320685, + 2.08619411, 1.26990265, 1.82215898, 1.10656122, 1.40229835, 1.11896817, + 1.00127366, 2.88218857, 2.79105702, 1.28699225, 1.15929737, 1.07928363, + 10.54130128, 8.79261793, 1.15699405, 1.69050500, 2.76586152, 1.22802809, + 1.38014655, 2.19208585, 1.64409370, 1.46918371, 2.99582898, 1.37759923, + 1.29776632, 1.82884215, 2.67317357, 1.37063041, 1.26884340, 1.07874723, + 1.48172681, 1.01771849, 2.40642202, 1.37115433, 1.05954574, 2.12998246, + 2.34178079, 1.54515623, 1.00179963, 2.12228030, 1.46007334, 1.20664530, + 1.31417158, 1.03322353, 1.95420119, 1.30541569, 1.15016102, 2.17036908, + 2.81707947, 1.16173181, 2.01742565, 1.02478594, 1.57428560, 1.21209176, + 2.20735202, 1.12935761, 2.08850147, 1.05353378, 1.02324910, 1.49636415, + 1.48061026, 2.25651770, 3.04296168, 1.24380806, 1.07707360, 2.00284318, + 10.02810932, 3.38695326, 6.82841534, 2.13556915, 1.19152238 + }; + + vector = igraph_vector_view(data, sizeof(data) / sizeof(data[0])); + + /* determining xmin and alpha */ + if (igraph_power_law_fit(&vector, &result, -1, 0)) { + return 1; + } + print_result(&result); + + /* determining alpha only */ + if (igraph_power_law_fit(&vector, &result, 2, 0)) { + return 2; + } + print_result(&result); + + return 0; +} + +int test_discrete(void) { + igraph_plfit_result_t result; + igraph_vector_t vector; + igraph_real_t p; + + double data[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 6, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 4, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 2, 1, 4, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 4, 1, 1, 14, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 4, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 5, 1, 1, 5, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 1, + 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, + 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, + 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, + 1, 1, 1, 1, 2, 11, 1, 33, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, + 2, 1, 1, 1, 2, 2, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 2, 1, 2, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 1, 1, 1, 4, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 3, 2, 1, 1, 1, 2 + }; + + vector = igraph_vector_view(data, sizeof(data) / sizeof(data[0])); + + /* determining xmin and alpha */ + if (igraph_power_law_fit(&vector, &result, -1, 0)) { + return 3; + } + IGRAPH_ASSERT(result.data == &vector); + print_result(&result); + + /* determining alpha only */ + if (igraph_power_law_fit(&vector, &result, 2, 0)) { + return 4; + } + IGRAPH_ASSERT(result.data == &vector); + print_result(&result); + + /* forcing continuous fitting */ + if (igraph_power_law_fit(&vector, &result, -1, 1)) { + return 5; + } + IGRAPH_ASSERT(result.data == &vector); + print_result(&result); + + /* forcing continuous fitting, xmin given */ + if (igraph_power_law_fit(&vector, &result, 2, 1)) { + return 6; + } + IGRAPH_ASSERT(result.data == &vector); + print_result(&result); + + /* Calculating p-value for the fit */ + if (igraph_plfit_result_calculate_p_value(&result, &p, 0.1)) { + return 7; + } + printf("p = %.2f\n", p); + + return 0; +} + +int main(void) { + int retval; + + /* Seed random number generator to ensure reproducibility. */ + igraph_rng_seed(igraph_rng_default(), 42); + + retval = test_continuous(); + if (retval) { + return retval; + } + + retval = test_discrete(); + if (retval) { + return retval; + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_power_law_fit.out b/tests/unit/igraph_power_law_fit.out new file mode 100644 index 0000000..c1ac4e6 --- /dev/null +++ b/tests/unit/igraph_power_law_fit.out @@ -0,0 +1,37 @@ +continuous = true +alpha = 2.81976 +xmin = 1.00979 +L = -946.14703 +D = 0.01454 +==================== +continuous = true +alpha = 2.81157 +xmin = 2.00000 +L = -463.92064 +D = 0.05091 +==================== +continuous = false +alpha = 3.11405 +xmin = 1.00000 +L = -622.60933 +D = 0.00941 +==================== +continuous = false +alpha = 3.27157 +xmin = 2.00000 +L = -185.83215 +D = 0.04504 +==================== +continuous = true +alpha = 3.77550 +xmin = 11.00000 +L = -13.68681 +D = 0.15260 +==================== +continuous = true +alpha = 5.26868 +xmin = 2.00000 +L = -75.22503 +D = 0.70253 +==================== +p = 0.00 diff --git a/tests/unit/igraph_preference_game.c b/tests/unit/igraph_preference_game.c new file mode 100644 index 0000000..e4b60fe --- /dev/null +++ b/tests/unit/igraph_preference_game.c @@ -0,0 +1,250 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_t type_dist; + igraph_matrix_t pref_mat, type_dist_mat; + igraph_vector_int_t types, out_types, in_types; + igraph_bool_t connected, has_loop, has_multi; + igraph_int_t i, j, count; + + igraph_vector_int_init(&types, 0); + + /* Symmetric preference game */ + igraph_vector_init_real(&type_dist, 3, 1.0, 1.0, 1.0); + + igraph_matrix_init(&pref_mat, 3, 3); + for (i = 0; i < 3; i++) { + MATRIX(pref_mat, i, i) = 0.2; + } + + /* undirected, no loops */ + igraph_preference_game(&g, 1000, 3, &type_dist, /*fixed_sizes=*/ 0, + &pref_mat, &types, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 1000); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_is_connected(&g, &connected, IGRAPH_STRONG); + IGRAPH_ASSERT(! connected); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(! has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + IGRAPH_ASSERT(igraph_vector_int_size(&types) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&types) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&types) == 2); + + igraph_destroy(&g); + + /* Note: preference matrix must be symmetric in the undirected case. */ + for (i = 0; i < 2; i++) { + MATRIX(pref_mat, i, i + 1) = 0.1; + MATRIX(pref_mat, i + 1, i) = 0.1; + } + + /* directed, no loops */ + igraph_preference_game(&g, 1000, 3, &type_dist, /*fixed_sizes=*/0, + &pref_mat, &types, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 1000); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(! has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + IGRAPH_ASSERT(igraph_vector_int_size(&types) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&types) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&types) == 2); + + igraph_destroy(&g); + + /* undirected, loops */ + for (i = 0; i < 3; i++) { + MATRIX(pref_mat, i, i) = 1.0; + } + + igraph_preference_game(&g, 100, 3, &type_dist, /*fixed_sizes=*/ 0, + &pref_mat, &types, IGRAPH_UNDIRECTED, IGRAPH_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) >= 1395); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + IGRAPH_ASSERT(igraph_vector_int_size(&types) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_min(&types) == 0); + IGRAPH_ASSERT(igraph_vector_int_max(&types) == 2); + + igraph_destroy(&g); + + /* directed, loops */ + igraph_preference_game(&g, 100, 3, &type_dist, /*fixed_sizes=*/ 0, + &pref_mat, NULL, IGRAPH_DIRECTED, IGRAPH_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) >= 2700); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + igraph_destroy(&g); + + /* fixed sizes, divide evenly */ + igraph_matrix_resize(&pref_mat, 9, 9); + for (i = 0; i < 9; i++) { + for (j = 0; j < 9; j++) { + MATRIX(pref_mat, i, j) = (j == i + 1 || j == i - 1) ? 0.1 : 0; + } + } + igraph_preference_game(&g, 50, 9, /*type_dist=*/ 0, /*fixed_sizes=*/ 1, + &pref_mat, &types, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 50); + IGRAPH_ASSERT(!igraph_is_directed(&g)); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(! has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + for (i = 0; i < 9; i++) { + count = 0; + for (j = 0; j < 50; j++) { + if (VECTOR(types)[j] == i) { + count++; + } + } + IGRAPH_ASSERT(count == 5 || count == 6); + } + + igraph_destroy(&g); + + igraph_vector_int_destroy(&types); + + /* Asymmetric preference game */ + + igraph_vector_int_init(&out_types, 0); + igraph_vector_int_init(&in_types, 0); + + /* directed, no loops */ + igraph_matrix_resize(&pref_mat, 2, 3); + MATRIX(pref_mat, 0, 0) = 1; + MATRIX(pref_mat, 0, 1) = 1; + MATRIX(pref_mat, 0, 2) = 1; + MATRIX(pref_mat, 1, 0) = 1; + MATRIX(pref_mat, 1, 1) = 1; + MATRIX(pref_mat, 1, 2) = 1; + + igraph_asymmetric_preference_game(&g, 100, 2, 3, NULL, &pref_mat, &out_types, &in_types, IGRAPH_NO_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) == 9900); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_has_loop(&g, &has_loop); + IGRAPH_ASSERT(! has_loop); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + igraph_destroy(&g); + + /* directed, loops */ + igraph_matrix_resize(&pref_mat, 2, 2); + MATRIX(pref_mat, 0, 0) = 1; + MATRIX(pref_mat, 0, 1) = 1; + MATRIX(pref_mat, 1, 0) = 1; + MATRIX(pref_mat, 1, 1) = 1; + + igraph_asymmetric_preference_game(&g, 100, 2, 2, NULL, &pref_mat, NULL, NULL, IGRAPH_LOOPS); + + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) == 10000); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_count_loops(&g, &count); + IGRAPH_ASSERT(count == 100); + + igraph_has_multiple(&g, &has_multi); + IGRAPH_ASSERT(! has_multi); + + igraph_destroy(&g); + + /* check that vertex out-/in-types are generated correctly; here pref_mat does not matter */ + + igraph_matrix_resize(&pref_mat, 3, 2); + igraph_matrix_null(&pref_mat); + + igraph_matrix_init(&type_dist_mat, 3, 2); + igraph_matrix_null(&type_dist_mat); + MATRIX(type_dist_mat, 2, 0) = 1; /* all out-types are 2, all in-types are 0 */ + + igraph_asymmetric_preference_game(&g, 10, 3, 2, &type_dist_mat, &pref_mat, &out_types, &in_types, IGRAPH_LOOPS); + { + /* Check that all out-types are 2 and all in-types are 0 */ + igraph_vector_int_t v; + igraph_vector_int_init(&v, igraph_vcount(&g)); + + igraph_vector_int_fill(&v, 2); + IGRAPH_ASSERT(igraph_vector_int_all_e(&out_types, &v)); + + igraph_vector_int_fill(&v, 0); + IGRAPH_ASSERT(igraph_vector_int_all_e(&in_types, &v)); + + igraph_vector_int_destroy(&v); + } + + igraph_destroy(&g); + + igraph_matrix_destroy(&type_dist_mat); + igraph_vector_destroy(&type_dist); + igraph_matrix_destroy(&pref_mat); + + igraph_vector_int_destroy(&out_types); + igraph_vector_int_destroy(&in_types); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_product.c b/tests/unit/igraph_product.c new file mode 100644 index 0000000..6d4d2e3 --- /dev/null +++ b/tests/unit/igraph_product.c @@ -0,0 +1,336 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/********************** Cartesian Product ************************/ +void test_grid_vs_square_lattice(void) { + // P4 X P3 should be a 4x3 grid (non-periodic) + igraph_t p4, p3, product, lattice; + igraph_bool_t is_iso; + igraph_ring(&p4, 4, IGRAPH_UNDIRECTED, false, false); // P4 + igraph_ring(&p3, 3, IGRAPH_UNDIRECTED, false, false); // P3 + igraph_product(&product, &p4, &p3, IGRAPH_PRODUCT_CARTESIAN); + + igraph_vector_int_t dims; + igraph_vector_int_init(&dims, 2); + VECTOR(dims)[0] = 4; + VECTOR(dims)[1] = 3; + + igraph_square_lattice(&lattice, &dims, 1, IGRAPH_UNDIRECTED, false, NULL); + + igraph_isomorphic(&product, &lattice, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&p4); + igraph_destroy(&p3); + igraph_destroy(&product); + igraph_destroy(&lattice); + igraph_vector_int_destroy(&dims); +} + +void test_cylinder_vs_cartesian(void) { + // C4 X P3 should be a cylindrical lattice (periodic in one dimension) + igraph_t c4, p3, product, cylinder; + igraph_bool_t is_iso; + igraph_ring(&c4, 4, IGRAPH_UNDIRECTED, false, true); // C4 + igraph_ring(&p3, 3, IGRAPH_UNDIRECTED, false, false); // P3 + igraph_product(&product, &c4, &p3, IGRAPH_PRODUCT_CARTESIAN); + + igraph_vector_int_t dims; + igraph_vector_bool_t periodic; + igraph_vector_int_init(&dims, 2); + igraph_vector_bool_init(&periodic, 2); + VECTOR(dims)[0] = 4; + VECTOR(dims)[1] = 3; + VECTOR(periodic)[0] = true; // periodic in first dim (C4) + VECTOR(periodic)[1] = false; // non-periodic in second dim (P3) + + igraph_square_lattice(&cylinder, &dims, 1, IGRAPH_UNDIRECTED, false, &periodic); + igraph_isomorphic(&product, &cylinder, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&c4); + igraph_destroy(&p3); + igraph_destroy(&product); + igraph_destroy(&cylinder); + igraph_vector_int_destroy(&dims); + igraph_vector_bool_destroy(&periodic); +} + + +void test_cube_vs_cartesian(void) { + // K2 X K2 X K2 = Q3 (3-dimensional hypercube) + igraph_t k2, temp, q3, cube; + igraph_bool_t is_iso; + + igraph_full(&k2, 2, IGRAPH_UNDIRECTED, false); // K2 + igraph_product(&temp, &k2, &k2, IGRAPH_PRODUCT_CARTESIAN); + igraph_product(&q3, &temp, &k2, IGRAPH_PRODUCT_CARTESIAN); + + igraph_hypercube(&cube, 3, IGRAPH_UNDIRECTED); + igraph_isomorphic(&q3, &cube, &is_iso); + + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&k2); + igraph_destroy(&temp); + igraph_destroy(&q3); + igraph_destroy(&cube); +} + +void test_torus_vs_cartesian(void) { + // C4 X C4 = Torus + igraph_t c4a, c4b, product, torus; + igraph_bool_t is_iso; + + igraph_ring(&c4a, 4, IGRAPH_UNDIRECTED, false, true); + igraph_ring(&c4b, 4, IGRAPH_UNDIRECTED, false, true); + igraph_product(&product, &c4a, &c4b, IGRAPH_PRODUCT_CARTESIAN); + + igraph_vector_int_t dims; + igraph_vector_bool_t periodic; + igraph_vector_int_init(&dims, 2); + igraph_vector_bool_init(&periodic, 2); + VECTOR(dims)[0] = 4; + VECTOR(dims)[1] = 4; + VECTOR(periodic)[0] = true; + VECTOR(periodic)[1] = true; + + igraph_square_lattice(&torus, &dims, 1, IGRAPH_UNDIRECTED, false, &periodic); + igraph_isomorphic(&product, &torus, &is_iso); + + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&c4a); + igraph_destroy(&c4b); + igraph_destroy(&product); + igraph_destroy(&torus); + igraph_vector_int_destroy(&dims); + igraph_vector_bool_destroy(&periodic); +} + +void test_self_loop_cartesian(void) { + // 1-vertex loop X K4 (circular=false) = K4(circular=true) + igraph_t v1loop, k4a, k4b, product; + igraph_bool_t is_iso; + + igraph_full(&v1loop, 1, IGRAPH_UNDIRECTED, true); + igraph_full(&k4a, 4, IGRAPH_UNDIRECTED, false); + igraph_product(&product, &v1loop, &k4a, IGRAPH_PRODUCT_CARTESIAN); + + igraph_full(&k4b, 4, IGRAPH_UNDIRECTED, true); + igraph_isomorphic(&product, &k4b, &is_iso); + + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&v1loop); + igraph_destroy(&k4a); + igraph_destroy(&k4b); + igraph_destroy(&product); +} + +void test_multigraph_cartesian(void) { + igraph_t g1, g2, product; + + // g1: a multigraph with 2 vertices and parallel edges + self-loop + igraph_vector_int_t edges1; + igraph_vector_int_init(&edges1, 6); + VECTOR(edges1)[0] = 0; VECTOR(edges1)[1] = 1; + VECTOR(edges1)[2] = 0; VECTOR(edges1)[3] = 1; // Parallel edge + VECTOR(edges1)[4] = 1; VECTOR(edges1)[5] = 1; // Self-loop + igraph_create(&g1, &edges1, 2, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&edges1); + + // g2: 3 vertices with some parallel edges + self-loop + igraph_vector_int_t edges2; + igraph_vector_int_init(&edges2, 8); + VECTOR(edges2)[0] = 0; VECTOR(edges2)[1] = 1; + VECTOR(edges2)[2] = 0; VECTOR(edges2)[3] = 1; // Parallel edge + VECTOR(edges2)[4] = 1; VECTOR(edges2)[5] = 2; + VECTOR(edges2)[6] = 2; VECTOR(edges2)[7] = 2; // Self-loop + igraph_create(&g2, &edges2, 3, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&edges2); + + igraph_product(&product, &g1, &g2, IGRAPH_PRODUCT_CARTESIAN); + + // Check expected number of vertices: |V1| * |V2| = 2 * 3 = 6 + IGRAPH_ASSERT(igraph_vcount(&product) == 6); + + // verified by v1*e2 + v2*e1, see: https://en.wikipedia.org/wiki/Graph_product + IGRAPH_ASSERT(igraph_ecount(&product) == 17); + + igraph_destroy(&g1); + igraph_destroy(&g2); + igraph_destroy(&product); +} + +/********************** Tensor Product ************************/ +// K2 X petersen = G(10,3) +void test_petersen_tensor(void) { + igraph_t k2, petersen, g_10_3, product; + igraph_bool_t is_iso; + + igraph_full(&k2, 2, IGRAPH_UNDIRECTED, false); + igraph_famous(&petersen, "petersen"); + igraph_generalized_petersen(&g_10_3, 10, 3); + + igraph_product(&product, &k2, &petersen, IGRAPH_PRODUCT_TENSOR); + + igraph_isomorphic(&product, &g_10_3, &is_iso); + + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&k2); + igraph_destroy(&petersen); + igraph_destroy(&g_10_3); + igraph_destroy(&product); +} + +// C2 X C3 = C6 +void test_dir_cycle_tensor(void) { + igraph_t c2, c3, c6, product; + igraph_bool_t is_iso; + + igraph_ring(&c2, 2, IGRAPH_DIRECTED, false, true); + igraph_ring(&c3, 3, IGRAPH_DIRECTED, false, true); + igraph_ring(&c6, 6, IGRAPH_DIRECTED, false, true); + + igraph_product(&product, &c2, &c3, IGRAPH_PRODUCT_TENSOR); + + igraph_isomorphic(&product, &c6, &is_iso); + + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&c2); + igraph_destroy(&c3); + igraph_destroy(&c6); + igraph_destroy(&product); +} + +/********************** Lexicographic Product ************************/ +// K2 X K4 = K8 +void test_k2_k4_lex(void) { + igraph_t k2, k4, k8, product; + igraph_bool_t is_iso; + + igraph_full(&k2, 2, IGRAPH_UNDIRECTED, false); + igraph_full(&k4, 4, IGRAPH_UNDIRECTED, false); + igraph_full(&k8, 8, IGRAPH_UNDIRECTED, false); + + igraph_product(&product, &k2, &k4, IGRAPH_PRODUCT_LEXICOGRAPHIC); + igraph_isomorphic(&product, &k8, &is_iso); + IGRAPH_ASSERT(is_iso); + igraph_destroy(&k2); + igraph_destroy(&k4); + igraph_destroy(&k8); + igraph_destroy(&product); +} + +/********************** Strong Product ************************/ +// P8 X P8 = King Graph +void test_p8_p8_strong(void) { + igraph_t p8, king_graph, product; + igraph_bool_t is_iso; + + igraph_vector_int_t edges; + igraph_vector_int_init(&edges, 0); + + igraph_int_t n = 8; // n x n chessboard + + for (igraph_int_t i = 0; i < n; i++) { + for (igraph_int_t j = 0; j < n; j++) { + igraph_int_t u = i * n + j; + for (igraph_int_t dx = -1; dx <= 1; dx++) { + for (igraph_int_t dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + igraph_int_t ni = i + dx, nj = j + dy; + if (ni >= 0 && ni < n && nj >= 0 && nj < n) { + igraph_int_t v = ni * n + nj; + // To avoid duplicate edges, only add u < v + if (u < v) { + igraph_vector_int_push_back(&edges, u); + igraph_vector_int_push_back(&edges, v); + } + } + } + } + } + } + igraph_create(&king_graph, &edges, n * n, IGRAPH_UNDIRECTED); + igraph_ring(&p8, 8, IGRAPH_UNDIRECTED, false, false); + + igraph_product(&product, &p8, &p8, IGRAPH_PRODUCT_STRONG); + igraph_isomorphic(&product, &king_graph, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&king_graph); + igraph_destroy(&product); + igraph_destroy(&p8); + igraph_vector_int_destroy(&edges); +} + +/********************** Modular Product ************************/ +// C4 X P2 +void test_c4_p2_modular(void) { + igraph_t c4, p2, res, prod; + igraph_bool_t is_iso; + + igraph_ring(&c4, 4, IGRAPH_UNDIRECTED, false, true); + igraph_ring(&p2, 2, IGRAPH_UNDIRECTED, false, false); + + igraph_product(&res, &c4, &p2, IGRAPH_PRODUCT_MODULAR); + + igraph_small(&prod, 8, IGRAPH_UNDIRECTED, 0,3, 0,7, 1,2, 1,6, 2,5, 3,4, 4,7, 5,6, -1); + + igraph_isomorphic(&res, &prod, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&c4); + igraph_destroy(&p2); + igraph_destroy(&res); + igraph_destroy(&prod); +} + +int main(void) { + // CARTESIAN PRODUCT TEST + test_grid_vs_square_lattice(); + test_cylinder_vs_cartesian(); + test_cube_vs_cartesian(); + test_torus_vs_cartesian(); + test_self_loop_cartesian(); + test_multigraph_cartesian(); + + // TENSOR PRODUCT TEST + test_petersen_tensor(); + test_dir_cycle_tensor(); + + // LEXICOGRAPHIC PRODUCT TEST + test_k2_k4_lex(); + + // STRONG PRODUCT TEST + test_p8_p8_strong(); + + // MODULAR PRODUCT TEST + test_c4_p2_modular(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_progress_handler_stderr.c b/tests/unit/igraph_progress_handler_stderr.c new file mode 100644 index 0000000..4f67182 --- /dev/null +++ b/tests/unit/igraph_progress_handler_stderr.c @@ -0,0 +1,27 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_progress_handler_stderr("This is a message ", 10, NULL); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_pseudo_diameter.c b/tests/unit/igraph_pseudo_diameter.c new file mode 100644 index 0000000..f59ddd4 --- /dev/null +++ b/tests/unit/igraph_pseudo_diameter.c @@ -0,0 +1,131 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_result(igraph_t *g, igraph_int_t start_vid, igraph_bool_t directed, igraph_bool_t unconn) { + igraph_real_t result; + igraph_int_t from, to; + IGRAPH_ASSERT(igraph_pseudo_diameter(g, NULL, &result, start_vid, &from, &to, directed, unconn) == IGRAPH_SUCCESS); + printf("result: "); + print_real(stdout, result, "%g"); + printf(", from %" IGRAPH_PRId " to %" IGRAPH_PRId "\n\n", from, to); +} + +int main(void) { + igraph_t g; + igraph_real_t result; + igraph_error_handler_t *ehandler; + igraph_int_t i; + + igraph_rng_seed(igraph_rng_default(), 42); + + + printf("No vertices, no allowed starting vertex ID.\n\n"); + igraph_small(&g, 0, 0, -1); + ehandler = igraph_set_error_handler(igraph_error_handler_ignore); + IGRAPH_ASSERT(igraph_pseudo_diameter(&g, NULL, &result, 0, NULL, NULL, 1, 1) == IGRAPH_EINVAL); + igraph_set_error_handler(ehandler); + + printf("Null graph without explicit start vertex:\n"); + print_result(&g, -1, 1, 1); + igraph_destroy(&g); + + printf("1 vertex:\n"); + igraph_small(&g, 1, 0, -1); + print_result(&g, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices unconn = true:\n"); + igraph_small(&g, 2, 0, -1); + print_result(&g, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices unconn = false:\n"); + igraph_small(&g, 2, 0, -1); + print_result(&g, 0, 1, 0); + igraph_destroy(&g); + + printf("2 vertices, directed, unconn = true:\n"); + igraph_small(&g, 2, 1, -1); + print_result(&g, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices, directed, unconn = false:\n"); + igraph_small(&g, 2, 1, -1); + print_result(&g, 0, 1, 0); + igraph_destroy(&g); + + printf("Undirected disconnected graph with loops and multiple edges.\n"); + igraph_small(&g, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + for (i = 0; i < 6; i ++) { + print_result(&g, i, 1, 1); + } + igraph_destroy(&g); + + printf("Directed disconnected graph with loops and multiple edges, direction ignored.\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + for (i = 0; i < 6; i ++) { + print_result(&g, i, 0, 1); + } + igraph_destroy(&g); + + printf("Directed disconnected graph with loops and multiple edges.\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + for (i = 0; i < 6; i ++) { + print_result(&g, i, 1, 1); + } + igraph_destroy(&g); + + printf("Directed graph, pointing outward from 2.\n"); + igraph_small(&g, 5, 1, 1,0, 2,1, 2,3, 3,4, -1); + for (i = 0; i < 5; i ++) { + print_result(&g, i, 1, 1); + } + igraph_destroy(&g); + + printf("Same graph, direction ignored.\n"); + igraph_small(&g, 5, 1, 1,0, 2,1, 2,3, 3,4, -1); + for (i = 0; i < 5; i ++) { + print_result(&g, i, 0, 1); + } + igraph_destroy(&g); + + printf("Same graph, unconn = false.\n"); + igraph_small(&g, 5, 1, 1,0, 2,1, 2,3, 3,4, -1); + print_result(&g, 0, 1, 0); + igraph_destroy(&g); + + printf("Same graph, undirected.\n"); + igraph_small(&g, 5, 0, 1,0, 2,1, 2,3, 3,4, -1); + for (i = 0; i < 5; i ++) { + print_result(&g, i, 1, 1); + } + igraph_destroy(&g); + + printf("Petersen graph with random starts.\n"); + igraph_famous(&g, "petersen"); + for (i = 0; i < 6; i ++) { + print_result(&g, -1, 1, 1); + } + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_pseudo_diameter.out b/tests/unit/igraph_pseudo_diameter.out new file mode 100644 index 0000000..93b7650 --- /dev/null +++ b/tests/unit/igraph_pseudo_diameter.out @@ -0,0 +1,108 @@ +No vertices, no allowed starting vertex ID. + +Null graph without explicit start vertex: +result: NaN, from -1 to -1 + +1 vertex: +result: 0, from 0 to 0 + +2 vertices unconn = true: +result: 0, from 0 to 0 + +2 vertices unconn = false: +result: Inf, from -1 to -1 + +2 vertices, directed, unconn = true: +result: 0, from 0 to 0 + +2 vertices, directed, unconn = false: +result: Inf, from -1 to -1 + +Undirected disconnected graph with loops and multiple edges. +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 3, from 4 to 0 + +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 0, from 5 to 5 + +Directed disconnected graph with loops and multiple edges, direction ignored. +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 3, from 4 to 0 + +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 0, from 5 to 5 + +Directed disconnected graph with loops and multiple edges. +result: 3, from 0 to 4 + +result: 2, from 2 to 1 + +result: 3, from 0 to 4 + +result: 3, from 0 to 4 + +result: 3, from 0 to 4 + +result: 0, from 5 to 5 + +Directed graph, pointing outward from 2. +result: 2, from 2 to 0 + +result: 2, from 2 to 0 + +result: 2, from 2 to 0 + +result: 2, from 2 to 0 + +result: 2, from 2 to 4 + +Same graph, direction ignored. +result: 4, from 0 to 4 + +result: 4, from 4 to 0 + +result: 4, from 0 to 4 + +result: 4, from 0 to 4 + +result: 4, from 4 to 0 + +Same graph, unconn = false. +result: Inf, from -1 to -1 + +Same graph, undirected. +result: 4, from 0 to 4 + +result: 4, from 4 to 0 + +result: 4, from 0 to 4 + +result: 4, from 0 to 4 + +result: 4, from 4 to 0 + +Petersen graph with random starts. +result: 2, from 1 to 4 + +result: 2, from 5 to 1 + +result: 2, from 8 to 2 + +result: 2, from 9 to 0 + +result: 2, from 9 to 0 + +result: 2, from 7 to 1 + diff --git a/tests/unit/igraph_pseudo_diameter_dijkstra.c b/tests/unit/igraph_pseudo_diameter_dijkstra.c new file mode 100644 index 0000000..e8624fb --- /dev/null +++ b/tests/unit/igraph_pseudo_diameter_dijkstra.c @@ -0,0 +1,153 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_result(igraph_t *g, igraph_vector_t *weights, igraph_int_t start_vid, igraph_bool_t directed, igraph_bool_t unconn) { + igraph_real_t result; + igraph_int_t from, to; + IGRAPH_ASSERT(igraph_pseudo_diameter(g, weights, &result, start_vid, &from, &to, directed, unconn) == IGRAPH_SUCCESS); + printf("result: "); + print_real(stdout, result, "%g"); + printf(", from %" IGRAPH_PRId " to %" IGRAPH_PRId "\n\n", from, to); +} + +int main(void) { + igraph_t g; + igraph_real_t result; + int i; + igraph_vector_t weights; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("Null graph without explicit start vertex:\n"); + igraph_vector_init_int(&weights, 0); + igraph_small(&g, 0, 0, -1); + print_result(&g, &weights, -1, 1, 1); + igraph_destroy(&g); + + printf("1 vertex:\n"); + igraph_small(&g, 1, 0, -1); + print_result(&g, &weights, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices unconn = true:\n"); + igraph_small(&g, 2, 0, -1); + print_result(&g, &weights, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices unconn = false:\n"); + igraph_small(&g, 2, 0, -1); + print_result(&g, &weights, 0, 1, 0); + igraph_destroy(&g); + + printf("2 vertices, directed, unconn = true:\n"); + igraph_small(&g, 2, 1, -1); + print_result(&g, &weights, 0, 1, 1); + igraph_destroy(&g); + + printf("2 vertices, directed, unconn = false:\n"); + igraph_small(&g, 2, 1, -1); + print_result(&g, &weights, 0, 1, 0); + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + printf("Undirected disconnected graph with loops and multiple edges, all weights equal.\n"); + igraph_vector_init_int(&weights, 8, 1, 1, 1, 1, 1, 1, 1, 1); + igraph_small(&g, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + for (i = 0; i < 6; i ++) { + print_result(&g, &weights, i, 1, 1); + } + + printf("Undirected disconnected graph with loops and multiple edges, NULL weights.\n"); + for (i = 0; i < 6; i ++) { + print_result(&g, NULL, i, 1, 1); + } + igraph_destroy(&g); + + printf("Same graph, directed, direction ignored.\n"); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + for (i = 0; i < 6; i ++) { + print_result(&g, &weights, i, 0, 1); + } + + printf("Same graph, direction not ignored.\n"); + for (i = 0; i < 6; i ++) { + print_result(&g, &weights, i, 1, 1); + } + igraph_vector_destroy(&weights); + + printf("Same graph, weighted.\n"); + igraph_vector_init_int(&weights, 8, 0, 1, 2, 3, 4, 5, 6, 7); + for (i = 0; i < 6; i ++) { + print_result(&g, &weights, i, 1, 1); + } + + printf("Same graph, weighted, directions ignored.\n"); + for (i = 0; i < 6; i ++) { + print_result(&g, &weights, i, 0, 1); + } + igraph_destroy(&g); + igraph_vector_destroy(&weights); + + printf("Directed graph, weighted, pointing outward from 2.\n"); + igraph_vector_init_int(&weights, 4, 0, 1, 2, 3); + igraph_small(&g, 5, 1, 1,0, 2,1, 2,3, 3,4, -1); + for (i = 0; i < 5; i ++) { + print_result(&g, &weights, i, 1, 1); + } + + printf("Same graph, random starting vertex.\n"); + print_result(&g, &weights, -1, 1, 1); + + printf("Same graph, unconn = false.\n"); + print_result(&g, &weights, 0, 1, 0); + + printf("Same graph, direction ignored.\n"); + for (i = 0; i < 5; i ++) { + print_result(&g, &weights, i, 0, 1); + } + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Weight vector size too small.\n"); + igraph_vector_init_int(&weights, 3, 0, 1, 2); + igraph_small(&g, 5, 1, 1,0, 2,1, 2,3, 3,4, -1); + IGRAPH_ASSERT(igraph_pseudo_diameter(&g, &weights, &result, 0, NULL, NULL, 0, 1) == IGRAPH_EINVAL); + + printf("Starting vertex ID too large.\n"); + IGRAPH_ASSERT(igraph_pseudo_diameter(&g, &weights, &result, 10, NULL, NULL, 0, 1) == IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + + printf("Negative weight.\n"); + igraph_vector_init_int(&weights, 4, 0, 1, 2, -1); + IGRAPH_ASSERT(igraph_pseudo_diameter(&g, &weights, &result, 0, NULL, NULL, 0, 1) == IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + + printf("NaN weight.\n"); + igraph_vector_init_real(&weights, 4, 0., 1., 2., IGRAPH_NAN); + IGRAPH_ASSERT(igraph_pseudo_diameter(&g, &weights, &result, 0, NULL, NULL, 0, 1) == IGRAPH_EINVAL); + igraph_vector_destroy(&weights); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_pseudo_diameter_dijkstra.out b/tests/unit/igraph_pseudo_diameter_dijkstra.out new file mode 100644 index 0000000..529c17a --- /dev/null +++ b/tests/unit/igraph_pseudo_diameter_dijkstra.out @@ -0,0 +1,128 @@ +Null graph without explicit start vertex: +result: NaN, from -1 to -1 + +1 vertex: +result: 0, from 0 to 0 + +2 vertices unconn = true: +result: 0, from 0 to 0 + +2 vertices unconn = false: +result: Inf, from -1 to -1 + +2 vertices, directed, unconn = true: +result: 0, from 0 to 0 + +2 vertices, directed, unconn = false: +result: Inf, from -1 to -1 + +Undirected disconnected graph with loops and multiple edges, all weights equal. +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 2, from 2 to 1 + +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 0, from 5 to 5 + +Undirected disconnected graph with loops and multiple edges, NULL weights. +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 3, from 4 to 0 + +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 0, from 5 to 5 + +Same graph, directed, direction ignored. +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 2, from 2 to 1 + +result: 3, from 0 to 4 + +result: 3, from 4 to 0 + +result: 0, from 5 to 5 + +Same graph, direction not ignored. +result: 3, from 0 to 4 + +result: 2, from 2 to 1 + +result: 3, from 0 to 4 + +result: 3, from 0 to 4 + +result: 3, from 0 to 4 + +result: 0, from 5 to 5 + +Same graph, weighted. +result: 11, from 2 to 4 + +result: 11, from 2 to 4 + +result: 11, from 2 to 4 + +result: 11, from 2 to 4 + +result: 11, from 2 to 4 + +result: 0, from 5 to 5 + +Same graph, weighted, directions ignored. +result: 10, from 4 to 2 + +result: 10, from 4 to 2 + +result: 10, from 2 to 4 + +result: 10, from 4 to 2 + +result: 10, from 4 to 2 + +result: 0, from 5 to 5 + +Directed graph, weighted, pointing outward from 2. +result: 5, from 2 to 4 + +result: 5, from 2 to 4 + +result: 5, from 2 to 4 + +result: 5, from 2 to 4 + +result: 5, from 2 to 4 + +Same graph, random starting vertex. +result: 5, from 2 to 4 + +Same graph, unconn = false. +result: Inf, from -1 to -1 + +Same graph, direction ignored. +result: 6, from 0 to 4 + +result: 6, from 1 to 4 + +result: 6, from 4 to 0 + +result: 6, from 0 to 4 + +result: 6, from 4 to 0 + +Weight vector size too small. +Starting vertex ID too large. +Negative weight. +NaN weight. diff --git a/tests/unit/igraph_psumtree.c b/tests/unit/igraph_psumtree.c new file mode 100644 index 0000000..eb18eba --- /dev/null +++ b/tests/unit/igraph_psumtree.c @@ -0,0 +1,213 @@ + +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_psumtree_t tree; + igraph_vector_t vec; + igraph_int_t i, idx; + igraph_real_t sum; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Uniform random numbers */ + igraph_vector_init(&vec, 16); + igraph_psumtree_init(&tree, 16); + sum = igraph_psumtree_sum(&tree); + if (sum != 0) { + printf("Sum: %f instead of 0.\n", sum); + return 1; + } + + for (i = 0; i < 16; i++) { + igraph_psumtree_update(&tree, i, 1); + } + if ((sum = igraph_psumtree_sum(&tree)) != 16) { + printf("Sum: %f instead of 16.\n", sum); + return 2; + } + + for (i = 0; i < 16000; i++) { + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + for (i = 0; i < 16; i++) { + if (VECTOR(vec)[i] < 800 || VECTOR(vec)[i] > 1200) { + return 3; + } + } + + /* Nonuniform, even indices have twice as much chance */ + for (i = 0; i < 16; i += 2) { + igraph_psumtree_update(&tree, i, 2); + } + if ((sum = igraph_psumtree_sum(&tree)) != 24) { + printf("Sum: %f instead of 24.\n", sum); + return 4; + } + + igraph_vector_null(&vec); + for (i = 0; i < 24000; i++) { + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + for (i = 0; i < 16; i++) { + if (i % 2 == 0 && (VECTOR(vec)[i] < 1800 || VECTOR(vec)[i] > 2200)) { + return 5; + } + if (i % 2 != 0 && (VECTOR(vec)[i] < 800 || VECTOR(vec)[i] > 1200)) { + return 6; + } + } + + /* Test zero probabilities */ + igraph_psumtree_update(&tree, 0, 0); + igraph_psumtree_update(&tree, 5, 0); + igraph_psumtree_update(&tree, 15, 0); + sum = igraph_psumtree_sum(&tree); + + igraph_vector_null(&vec); + for (i = 0; i < 20000; i++) { + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + if (VECTOR(vec)[0] != 0 || VECTOR(vec)[5] != 0 || VECTOR(vec)[15] != 0) { + return 7; + } + + igraph_vector_destroy(&vec); + igraph_psumtree_destroy(&tree); + + /* Check that search considers intervals closed on the left, + * see https://github.com/igraph/igraph/issues/2080 */ + + igraph_psumtree_init(&tree, 6); + igraph_psumtree_update(&tree, 2, 1.0); + igraph_psumtree_update(&tree, 4, 1.0); + igraph_psumtree_search(&tree, &idx, 0.0); + IGRAPH_ASSERT(idx == 2); + igraph_psumtree_search(&tree, &idx, 0.5); + IGRAPH_ASSERT(idx == 2); + igraph_psumtree_search(&tree, &idx, 1.0); + IGRAPH_ASSERT(idx == 4); + igraph_psumtree_search(&tree, &idx, 1.2); + IGRAPH_ASSERT(idx == 4); + igraph_psumtree_destroy(&tree); + + /****************************************************/ + /* Non power-of-two vector size */ + /****************************************************/ + + igraph_vector_init(&vec, 9); + igraph_psumtree_init(&tree, 9); + + for (i = 0; i < 9; i++) { + igraph_psumtree_update(&tree, i, 1); + } + sum = igraph_psumtree_sum(&tree); + + for (i = 0; i < 9000; i++) { + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + for (i = 0; i < 9; i++) { + if (VECTOR(vec)[i] < 800 || VECTOR(vec)[i] > 1200) { + return 8; + } + } + + /* Nonuniform, even indices have twice as much chance */ + for (i = 0; i < 9; i += 2) { + igraph_psumtree_update(&tree, i, 2); + } + sum = igraph_psumtree_sum(&tree); + + igraph_vector_null(&vec); + for (i = 0; i < 14000; i++) { + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + for (i = 0; i < 9; i++) { + if (i % 2 == 0 && (VECTOR(vec)[i] < 1800 || VECTOR(vec)[i] > 2200)) { + return 9; + } + if (i % 2 != 0 && (VECTOR(vec)[i] < 800 || VECTOR(vec)[i] > 1200)) { + return 10; + } + } + + /* Test query */ + for (i = 0; i < igraph_psumtree_size(&tree); i++) { + if (i % 2 == 0 && igraph_psumtree_get(&tree, i) != 2) { + return 11; + } + if (i % 2 != 0 && igraph_psumtree_get(&tree, i) != 1) { + return 12; + } + } + + /* Test zero probabilities */ + igraph_psumtree_update(&tree, 0, 0); + igraph_psumtree_update(&tree, 5, 0); + igraph_psumtree_update(&tree, 8, 0); + sum = igraph_psumtree_sum(&tree); + + igraph_vector_null(&vec); + for (i = 0; i < 9000; i++) {; + igraph_psumtree_search(&tree, &idx, RNG_UNIF(0, sum)); + VECTOR(vec)[idx] += 1; + } + if (VECTOR(vec)[0] != 0 || VECTOR(vec)[5] != 0 || VECTOR(vec)[8] != 0) { + return 11; + } + + igraph_vector_destroy(&vec); + igraph_psumtree_destroy(&tree); + + /****************************************************/ + /* Error handling */ + /****************************************************/ + + igraph_psumtree_init(&tree, 9); + + igraph_error_handler_t *oldhandler = igraph_set_error_handler(&igraph_error_handler_ignore); + if (igraph_psumtree_update(&tree, 2, -2) == IGRAPH_SUCCESS) { + return 12; + } + if (igraph_psumtree_update(&tree, 2, -INFINITY) == IGRAPH_SUCCESS) { + return 13; + } + if (igraph_psumtree_update(&tree, 2, IGRAPH_NAN) == IGRAPH_SUCCESS) { + return 14; + } + igraph_set_error_handler(oldhandler); + + igraph_psumtree_destroy(&tree); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_qsort.c b/tests/unit/igraph_qsort.c new file mode 100644 index 0000000..c7e00de --- /dev/null +++ b/tests/unit/igraph_qsort.c @@ -0,0 +1,61 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA 02139, USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int comp(const void *a, const void *b) { + igraph_real_t *aa = (igraph_real_t *) a; + igraph_real_t *bb = (igraph_real_t *) b; + + if (*aa < *bb) { + return -1; + } else if (*aa > *bb) { + return 1; + } + + return 0; +} + +int main(void) { + const int len = 100; + igraph_vector_t v; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_init(&v, len); + for (i = 0; i < len; i++) { + VECTOR(v)[i] = i; + } + igraph_vector_shuffle(&v); + + igraph_qsort(VECTOR(v), igraph_vector_size(&v), sizeof(VECTOR(v)[0]), comp); + + igraph_vector_print(&v); + + igraph_vector_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_qsort.out b/tests/unit/igraph_qsort.out new file mode 100644 index 0000000..580e05a --- /dev/null +++ b/tests/unit/igraph_qsort.out @@ -0,0 +1 @@ +0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 diff --git a/tests/unit/igraph_qsort_r.c b/tests/unit/igraph_qsort_r.c new file mode 100644 index 0000000..f8e5922 --- /dev/null +++ b/tests/unit/igraph_qsort_r.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA 02139, USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int comp(void *extra, const void *a, const void *b) { + igraph_vector_t *v = (igraph_vector_t*) extra; + igraph_int_t *aa = (igraph_int_t*) a; + igraph_int_t *bb = (igraph_int_t*) b; + igraph_real_t aaa = VECTOR(*v)[*aa]; + igraph_real_t bbb = VECTOR(*v)[*bb]; + + if (aaa < bbb) { + return -1; + } else if (aaa > bbb) { + return 1; + } + + return 0; +} + +int main(void) { + const int len = 100; + igraph_vector_t v; + igraph_vector_int_t idx; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_vector_init(&v, len); + igraph_vector_int_init(&idx, len); + for (i = 0; i < len; i++) { + VECTOR(v)[i] = i; + VECTOR(idx)[i] = i; + } + igraph_vector_shuffle(&v); + + igraph_qsort_r(VECTOR(idx), len, sizeof(VECTOR(idx)[0]), (void*) &v, comp); + + for (i = 0; i < len; i++) { + printf("%g ", VECTOR(v)[ VECTOR(idx)[i] ]); + } + printf("\n"); + + igraph_vector_int_destroy(&idx); + igraph_vector_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_qsort_r.out b/tests/unit/igraph_qsort_r.out new file mode 100644 index 0000000..246042d --- /dev/null +++ b/tests/unit/igraph_qsort_r.out @@ -0,0 +1 @@ +0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 diff --git a/tests/unit/igraph_random_sample.c b/tests/unit/igraph_random_sample.c new file mode 100644 index 0000000..1a1d665 --- /dev/null +++ b/tests/unit/igraph_random_sample.c @@ -0,0 +1,65 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check(igraph_int_t l, igraph_int_t h, igraph_int_t s) { + igraph_vector_int_t v; + + igraph_vector_int_init(&v, 0); + igraph_random_sample(&v, l, h, s); + + IGRAPH_ASSERT(igraph_vector_int_size(&v) == s); + IGRAPH_ASSERT(VECTOR(v)[0] >= l); + IGRAPH_ASSERT(VECTOR(v)[igraph_vector_int_size(&v) - 1] <= h); + + for (igraph_int_t i = 0; i < s - 1; i++) { + IGRAPH_ASSERT(VECTOR(v)[i] < VECTOR(v)[i + 1]); + } + + igraph_vector_int_destroy(&v); +} + +int main(void) { + igraph_vector_int_t v; + + igraph_rng_seed(igraph_rng_default(), 42); + + check(-100, 100, 10); + check(-10000, 10000, 100); + check(0, 0, 1); + check(100, 110, 11); + + igraph_vector_int_init(&v, 0); + + igraph_random_sample(&v, 100, 1000, 0); + IGRAPH_ASSERT(igraph_vector_int_size(&v) == 0); + + /* test parameters */ + /*----------low----high----length*/ + /* lower limit is greater than upper limit */ + CHECK_ERROR(igraph_random_sample(&v, 300, 200, 10), IGRAPH_EINVAL); + /* sample size is greater than size of candidate pool */ + CHECK_ERROR(igraph_random_sample(&v, 200, 300, 500), IGRAPH_EINVAL); + + igraph_vector_int_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_random_walk.c b/tests/unit/igraph_random_walk.c new file mode 100644 index 0000000..ef76eea --- /dev/null +++ b/tests/unit/igraph_random_walk.c @@ -0,0 +1,235 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void random_vertex_walk(igraph_t *graph, igraph_vector_t *weights, + igraph_int_t start, igraph_neimode_t mode, + igraph_int_t steps, igraph_random_walk_stuck_t stuck) { + igraph_vector_int_t vertices; + + igraph_vector_int_init(&vertices, 0); + igraph_random_walk(graph, weights, &vertices, NULL, start, mode, steps, stuck); + print_vector_int(&vertices); + igraph_vector_int_destroy(&vertices); +} + +void random_walk(igraph_t *graph, igraph_vector_t *weights, + igraph_int_t start, igraph_neimode_t mode, + igraph_int_t steps, igraph_random_walk_stuck_t stuck) { + igraph_vector_int_t vertices; + igraph_vector_int_t edges; + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + igraph_random_walk(graph, weights, &vertices, &edges, start, mode, steps, stuck); + print_vector_int(&vertices); + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); +} + +int main(void) { + igraph_t g_1, g_line, g_full, g_loop, g_de_bruijn; + igraph_vector_int_t vertices, edges; + igraph_vector_t g_line_weights, g_de_bruijn_weights, error_weights; + igraph_int_t ec, i; + + igraph_vector_int_init(&vertices, 0); + igraph_vector_int_init(&edges, 0); + igraph_vector_init(&g_line_weights, 0); + igraph_vector_init(&g_de_bruijn_weights, 0); + igraph_vector_init(&error_weights, 0); + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&g_1, 1, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_line, 5, IGRAPH_DIRECTED, 0,1, 1,2, 2,3, 3,4, -1); + igraph_full(&g_full, 20, IGRAPH_UNDIRECTED, 0); + igraph_small(&g_loop, 1, IGRAPH_DIRECTED, 0,0, -1); + + /* This directed graph has loop edges. + It also has multi-edges when considered as undirected. */ + igraph_de_bruijn(&g_de_bruijn, 3, 2); + + /* Initialize de bruijn graph weights */ + igraph_rng_seed(igraph_rng_default(), 42); + ec = igraph_ecount(&g_de_bruijn); + igraph_vector_resize(&g_de_bruijn_weights, ec); + for (i = 0; i < ec; ++i) { + VECTOR(g_de_bruijn_weights)[i] = igraph_rng_get_unif01(igraph_rng_default()); + } + + /* Initialize g_line graph weights */ + igraph_rng_seed(igraph_rng_default(), 42); + ec = igraph_ecount(&g_line); + igraph_vector_resize(&g_line_weights, ec); + for (i = 0; i < ec; ++i) { + VECTOR(g_line_weights)[i] = igraph_rng_get_unif01(igraph_rng_default()); + } + + + /* 1. only vertices required, edges = NULL + unweighted -> igraph_i_random_walk_adjlist + weighted -> igraph_i_random_walk_inclist */ + printf("Only vertices required, edges = NULL:\n"); + + printf("Singleton graph with zero steps:\n"); + random_vertex_walk(&g_1, NULL, 0, IGRAPH_OUT, 0, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Singleton graph with one step:\n"); + random_vertex_walk(&g_line, NULL, 0, IGRAPH_OUT, 1, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph:\n"); + random_vertex_walk(&g_line, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph, 10 steps, returns on stuck:\n"); + random_vertex_walk(&g_line, NULL, 0, IGRAPH_OUT, 10, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph backward:\n"); + random_vertex_walk(&g_line, NULL, 4, IGRAPH_IN, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Loop at vertex 0:\n"); + random_vertex_walk(&g_loop, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + igraph_rng_seed(igraph_rng_default(), 42); + printf("Checking an actual random walk with seed 42:\n"); + random_vertex_walk(&g_full, NULL, 4, IGRAPH_IN, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + /* weighted, directed */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, &vertices, NULL, 0, IGRAPH_OUT, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 1001); + + /* weighted, undirecetd */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, &vertices, NULL, 0, IGRAPH_ALL, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 1001); + + /* weighted, directed line graph, 4 edges, 10 steps, returns on stuck */ + igraph_random_walk(&g_line, &g_line_weights, &vertices, NULL, 4, IGRAPH_IN, 10, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 5); + + + /* 2. both vertices and edges required -> igraph_i_random_walk_inclist */ + printf("\nBoth vertices and edges required:\n"); + + printf("Singleton graph with zero steps:\n"); + random_walk(&g_1, NULL, 0, IGRAPH_OUT, 0, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Singleton graph with one step:\n"); + random_walk(&g_line, NULL, 0, IGRAPH_OUT, 1, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph:\n"); + random_walk(&g_line, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph, 10 steps, returns on stuck:\n"); + random_walk(&g_line, NULL, 0, IGRAPH_OUT, 10, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Line graph backward:\n"); + random_walk(&g_line, NULL, 4, IGRAPH_IN, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + printf("Loop at vertex 0:\n"); + random_walk(&g_loop, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + igraph_rng_seed(igraph_rng_default(), 42); + printf("Checking an actual random walk with seed 42:\n"); + random_walk(&g_full, NULL, 4, IGRAPH_IN, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN); + + /* weighted, directed */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, &vertices, &edges, 0, IGRAPH_OUT, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 1001); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == 1000); + + /* weighted, undirecetd */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, &vertices, &edges, 0, IGRAPH_ALL, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 1001); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == 1000); + + /* weighted, directed line graph, 4 edges, 10 steps, returns on stuck */ + igraph_random_walk(&g_line, &g_line_weights, &vertices, &edges, 4, IGRAPH_IN, 10, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&vertices) == 5); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == 4); + + + /* 3. only edges required, vertices = NULL -> igraph_i_random_walk_inclist */ + printf("\nOnly edges required, vertices = NULL:\n"); + + /* weighted, directed */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, NULL, &edges, 0, IGRAPH_OUT, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == 1000); + + /* weighted, undirecetd */ + igraph_random_walk(&g_de_bruijn, &g_de_bruijn_weights, NULL, &edges, 0, IGRAPH_ALL, 1000, IGRAPH_RANDOM_WALK_STUCK_RETURN); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == 1000); + + + igraph_set_error_handler(igraph_error_handler_ignore); + printf("Checking error handling:\n"); + + /* 1. only vertices required, edges = NULL + unweighted -> igraph_i_random_walk_adjlist + weighted -> igraph_i_random_walk_inclist */ + printf("\nOnly vertices required, edges = NULL:\n"); + + printf("unweighted directed line graph, 10 steps, errors on stuck.\n"); + IGRAPH_ASSERT(igraph_random_walk(&g_line, NULL, &vertices, NULL, 0, IGRAPH_OUT, 10, IGRAPH_RANDOM_WALK_STUCK_ERROR) == IGRAPH_ERWSTUCK); + + printf("Vertex out of range.\n"); + IGRAPH_ASSERT(igraph_random_walk(&g_1, NULL, &vertices, NULL, 10, IGRAPH_OUT, 0, IGRAPH_RANDOM_WALK_STUCK_RETURN) == IGRAPH_EINVAL); + + printf("Negative number of steps.\n"); + IGRAPH_ASSERT(igraph_random_walk(&g_1, NULL, &vertices, NULL, 0, IGRAPH_OUT, -10, IGRAPH_RANDOM_WALK_STUCK_RETURN) == IGRAPH_EINVAL); + + /* weighted, directed line graph, 4 edges, 10 steps, errors on stuck */ + IGRAPH_ASSERT(igraph_random_walk(&g_line, &g_line_weights, &vertices, NULL, 4, IGRAPH_IN, 10, IGRAPH_RANDOM_WALK_STUCK_ERROR) == IGRAPH_ERWSTUCK); + + /* weighted, directed line graph, negative weight value for edge-0 */ + ec = igraph_ecount(&g_line); + igraph_vector_resize(&error_weights, ec); + VECTOR(error_weights)[0] = -10; + IGRAPH_ASSERT(igraph_random_walk(&g_line, &error_weights, &vertices, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN) == IGRAPH_EINVAL); + + /* weighted, directed line graph, invalid weight vector length (!= ec) */ + igraph_vector_resize(&error_weights, 2 * ec); + IGRAPH_ASSERT(igraph_random_walk(&g_line, &error_weights, &vertices, NULL, 0, IGRAPH_OUT, 4, IGRAPH_RANDOM_WALK_STUCK_RETURN) == IGRAPH_EINVAL); + + + /* 2. both vertices and edges required -> igraph_i_random_walk_inclist */ + printf("\nBoth vertices and edges required:\n"); + + printf("unweighted directed line graph, 10 steps, errors on stuck.\n"); + IGRAPH_ASSERT(igraph_random_walk(&g_line, NULL, &vertices, &edges, 0, IGRAPH_OUT, 10, IGRAPH_RANDOM_WALK_STUCK_ERROR) == IGRAPH_ERWSTUCK); + + /* weighted, directed line graph, 4 edges, 10 steps, errors on stuck */ + IGRAPH_ASSERT(igraph_random_walk(&g_line, &g_line_weights, &vertices, &edges, 4, IGRAPH_IN, 10, IGRAPH_RANDOM_WALK_STUCK_ERROR) == IGRAPH_ERWSTUCK); + + + igraph_destroy(&g_1); + igraph_destroy(&g_line); + igraph_destroy(&g_loop); + igraph_destroy(&g_full); + igraph_destroy(&g_de_bruijn); + + igraph_vector_destroy(&g_de_bruijn_weights); + igraph_vector_destroy(&g_line_weights); + igraph_vector_destroy(&error_weights); + igraph_vector_int_destroy(&vertices); + igraph_vector_int_destroy(&edges); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_random_walk.out b/tests/unit/igraph_random_walk.out new file mode 100644 index 0000000..6a9b2f8 --- /dev/null +++ b/tests/unit/igraph_random_walk.out @@ -0,0 +1,42 @@ +Only vertices required, edges = NULL: +Singleton graph with zero steps: +( 0 ) +Singleton graph with one step: +( 0 1 ) +Line graph: +( 0 1 2 3 4 ) +Line graph, 10 steps, returns on stuck: +( 0 1 2 3 4 ) +Line graph backward: +( 4 3 2 1 0 ) +Loop at vertex 0: +( 0 0 0 0 0 ) +Checking an actual random walk with seed 42: +( 4 2 11 16 19 ) + +Both vertices and edges required: +Singleton graph with zero steps: +( 0 ) +Singleton graph with one step: +( 0 1 ) +Line graph: +( 0 1 2 3 4 ) +Line graph, 10 steps, returns on stuck: +( 0 1 2 3 4 ) +Line graph backward: +( 4 3 2 1 0 ) +Loop at vertex 0: +( 0 0 0 0 0 ) +Checking an actual random walk with seed 42: +( 4 2 11 16 19 ) + +Only edges required, vertices = NULL: +Checking error handling: + +Only vertices required, edges = NULL: +unweighted directed line graph, 10 steps, errors on stuck. +Vertex out of range. +Negative number of steps. + +Both vertices and edges required: +unweighted directed line graph, 10 steps, errors on stuck. diff --git a/tests/unit/igraph_read_graph_graphdb.c b/tests/unit/igraph_read_graph_graphdb.c new file mode 100644 index 0000000..348cff0 --- /dev/null +++ b/tests/unit/igraph_read_graph_graphdb.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ +#include +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + FILE *ifile; + + ifile = fopen("si2_b06m_s20.A98", "rb"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_graphdb(&g, ifile, IGRAPH_DIRECTED); + fclose(ifile); + IGRAPH_ASSERT(igraph_is_directed(&g)); + print_graph(&g); + igraph_destroy(&g); + + igraph_error_handler_t *handler = igraph_set_error_handler(&igraph_error_handler_printignore); + + /* Node count too low, extra bytes at end. */ + ifile = fopen("si2_b06m_s20-bad1.A98", "rb"); + IGRAPH_ASSERT(ifile != NULL); + IGRAPH_ASSERT(igraph_read_graph_graphdb(&g, ifile, IGRAPH_DIRECTED) == IGRAPH_PARSEERROR); + fclose(ifile); + + /* Truncated file. */ + ifile = fopen("si2_b06m_s20-bad2.A98", "rb"); + IGRAPH_ASSERT(ifile != NULL); + IGRAPH_ASSERT(igraph_read_graph_graphdb(&g, ifile, IGRAPH_DIRECTED) == IGRAPH_PARSEERROR); + fclose(ifile); + + igraph_set_error_handler(handler); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_read_graph_graphdb.out b/tests/unit/igraph_read_graph_graphdb.out new file mode 100644 index 0000000..3f8c91b --- /dev/null +++ b/tests/unit/igraph_read_graph_graphdb.out @@ -0,0 +1,8 @@ +directed: true +vcount: 4 +edges: { +0 2 +1 0 +3 0 +3 2 +} diff --git a/tests/unit/igraph_read_graph_graphml.c b/tests/unit/igraph_read_graph_graphml.c new file mode 100644 index 0000000..1393b23 --- /dev/null +++ b/tests/unit/igraph_read_graph_graphml.c @@ -0,0 +1,212 @@ +/* + igraph library. + Copyright (C) 2006-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ +#include +#include +#include /* unlink */ + +#include "test_utilities.h" + +void custom_warning_handler (const char *reason, const char *file, + int line) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + printf("Warning: %s\n", reason); +} + +void dump_graph(const char* header, const igraph_t* g) { + fputs(header, stdout); + printf("Vertices: %" IGRAPH_PRId "\n", igraph_vcount(g)); + printf("Edges: %" IGRAPH_PRId "\n", igraph_ecount(g)); + printf("Directed: %i\n", igraph_is_directed(g) ? 1 : 0); + igraph_write_graph_edgelist(g, stdout); +} + +void dump_vertex_attribute_bool(const char* name, const igraph_t* g) { + igraph_int_t i, n = igraph_vcount(g); + + printf("Vertex attribute '%s':", name); + for (i = 0; i < n; i++) { + printf(" %s", VAB(g, name, i) ? "true" : "false"); + } + printf("\n"); +} + +void dump_vertex_attribute_numeric(const char* name, const igraph_t* g) { + igraph_int_t i, n = igraph_vcount(g); + + printf("Vertex attribute '%s':", name); + for (i = 0; i < n; i++) { + printf(" "); + igraph_real_printf_precise(VAN(g, name, i)); + } + printf("\n"); +} + +void dump_vertex_attribute_string(const char* name, const igraph_t* g) { + igraph_int_t i, n = igraph_vcount(g); + + printf("Vertex attribute '%s':", name); + for (i = 0; i < n; i++) { + printf(" '%s'", VAS(g, name, i)); + } + printf("\n"); +} + +int main(void) { + igraph_t g; + igraph_error_handler_t* oldhandler; + igraph_warning_handler_t* oldwarnhandler; + igraph_error_t result; + FILE *ifile, *ofile; + + igraph_set_attribute_table(&igraph_cattribute_table); + + /* GraphML */ + ifile = fopen("test.graphml", "r"); + IGRAPH_ASSERT(ifile != NULL); + + oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + oldwarnhandler = igraph_set_warning_handler(custom_warning_handler); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + /* maybe it is simply disabled at compile-time */ + if (result == IGRAPH_UNIMPLEMENTED) { + return 77; + } + return 1; + } + igraph_set_error_handler(oldhandler); + + fclose(ifile); + + /* Write it back */ + ofile = fopen("test2.graphml", "w"); + /* If we can't create the test file, just skip the test */ + if (ofile) { + if ((result = igraph_write_graph_graphml(&g, ofile, /*prefixattr=*/ true))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ofile); + unlink("test2.graphml"); + } + dump_graph("Directed graph:\n", &g); + dump_vertex_attribute_bool("gender", &g); + dump_vertex_attribute_string("color", &g); + igraph_destroy(&g); + + /* The same with undirected graph */ + ifile = fopen("test.graphml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Undirected graph:\n", &g); + dump_vertex_attribute_bool("gender", &g); + dump_vertex_attribute_string("color", &g); + igraph_destroy(&g); + + /* Test a GraphML file with default attributes */ + ifile = fopen("graphml-default-attrs.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Graph with default attributes:\n", &g); + dump_vertex_attribute_bool("type", &g); + dump_vertex_attribute_string("gender", &g); + dump_vertex_attribute_numeric("age", &g); + dump_vertex_attribute_bool("retired", &g); + igraph_destroy(&g); + + /* Test a GraphML file with namespaces */ + ifile = fopen("graphml-namespace.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Graph with namespace:\n", &g); + igraph_destroy(&g); + + /* Test a not-really-valid GraphML file as it has no namespace information */ + ifile = fopen("graphml-lenient.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Graph without namespace information:\n", &g); + igraph_destroy(&g); + + /* Test a GraphML file with excess whitespace around attribute values + * (which we attempt to handle gracefully) */ + ifile = fopen("graphml-whitespace.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Graph with whitespace in attributes:\n", &g); + dump_vertex_attribute_bool("type", &g); + dump_vertex_attribute_string("name", &g); + dump_vertex_attribute_numeric("weight", &g); + igraph_destroy(&g); + + /* Test a GraphML file from yEd -- we should be able to parse the nodes and + * edges */ + igraph_set_warning_handler(igraph_warning_handler_ignore); + ifile = fopen("graphml-yed.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + if ((result = igraph_read_graph_graphml(&g, ifile, 0))) { + printf("Received unexpected return code: %d\n", result); + return 1; + } + fclose(ifile); + dump_graph("Graph from yEd:\n", &g); + igraph_destroy(&g); + + /* Restore the old error handler */ + igraph_set_error_handler(igraph_error_handler_abort); + + /* Restore the old warning handler */ + igraph_set_warning_handler(oldwarnhandler); + + /* There were sometimes problems with this file */ + /* Only if called from R though, and only on random occasions, once in every + ten reads. Do testing here doesn't make much sense, but if we have the file + then let's do it anyway. */ + ifile = fopen("graphml-hsa05010.xml", "r"); + IGRAPH_ASSERT(ifile != NULL); + igraph_read_graph_graphml(&g, ifile, 0); + fclose(ifile); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_read_graph_graphml.out b/tests/unit/igraph_read_graph_graphml.out new file mode 100644 index 0000000..a38b9af --- /dev/null +++ b/tests/unit/igraph_read_graph_graphml.out @@ -0,0 +1,67 @@ +Warning: Unknown attribute key 'd3' in a tag, ignoring attribute. +Directed graph: +Vertices: 6 +Edges: 7 +Directed: 0 +0 1 +0 2 +1 3 +2 3 +2 4 +3 5 +4 5 +Vertex attribute 'gender': true true false true false false +Vertex attribute 'color': 'green' 'yellow' 'blue' 'red "with entities"' 'yellow' 'turquoise' +Warning: Unknown attribute key 'd3' in a tag, ignoring attribute. +Undirected graph: +Vertices: 6 +Edges: 7 +Directed: 0 +0 1 +0 2 +1 3 +2 3 +2 4 +3 5 +4 5 +Vertex attribute 'gender': true true false true false false +Vertex attribute 'color': 'green' 'yellow' 'blue' 'red "with entities"' 'yellow' 'turquoise' +Graph with default attributes: +Vertices: 3 +Edges: 2 +Directed: 1 +0 1 +0 2 +Vertex attribute 'type': false true true +Vertex attribute 'gender': 'male' 'female' 'male' +Vertex attribute 'age': 30 20 20 +Vertex attribute 'retired': false false false +Graph with namespace: +Vertices: 3 +Edges: 2 +Directed: 0 +0 1 +1 2 +Graph without namespace information: +Vertices: 3 +Edges: 2 +Directed: 0 +0 1 +1 2 +Graph with whitespace in attributes: +Vertices: 5 +Edges: 4 +Directed: 0 +0 1 +1 2 +1 3 +2 3 +Vertex attribute 'type': true false true true true +Vertex attribute 'name': 'spam' ' ' ' ham' 'bacon ' ' eggs ' +Vertex attribute 'weight': 1 NaN 3 4 5 +Graph from yEd: +Vertices: 3 +Edges: 2 +Directed: 1 +1 0 +2 1 diff --git a/tests/unit/igraph_realize_bipartite_degree_sequence.c b/tests/unit/igraph_realize_bipartite_degree_sequence.c new file mode 100644 index 0000000..6e5539a --- /dev/null +++ b/tests/unit/igraph_realize_bipartite_degree_sequence.c @@ -0,0 +1,323 @@ +/* + igraph library. + Constructing realizations of degree sequences and bi-degree sequences. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +void check_degrees( + const igraph_t *graph, + const igraph_vector_int_t *ds1, const igraph_vector_int_t *ds2 +) { + const igraph_int_t vcount = igraph_vcount(graph); + const igraph_int_t n1 = igraph_vector_int_size(ds1); + const igraph_int_t n2 = igraph_vector_int_size(ds2); + igraph_vector_int_t degrees, expected_degrees; + + if (vcount != n1+n2) { + printf("Bad graph size %" IGRAPH_PRId + " for bidegree sequence size (%" IGRAPH_PRId + ", %" IGRAPH_PRId ")", + vcount, n1, n2); + return; + } + + igraph_vector_int_init(°rees, vcount); + igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + + igraph_vector_int_init_copy(&expected_degrees, ds1); + igraph_vector_int_append(&expected_degrees, ds2); + if (! igraph_vector_int_all_e(°rees, &expected_degrees)) { + printf("Degrees not as expected!"); + printf("Expected: "); print_vector_int(&expected_degrees); + printf("Actual: "); print_vector_int(°rees); + } + + igraph_vector_int_destroy(&expected_degrees); + igraph_vector_int_destroy(°rees); +} + +int main(void) { + igraph_vector_int_t ds1, ds2; + igraph_t g; + igraph_bool_t is_connected; + igraph_bool_t is_bipartite; + igraph_bool_t is_simple; + + igraph_realize_degseq_t methods[] = { + IGRAPH_REALIZE_DEGSEQ_SMALLEST, + IGRAPH_REALIZE_DEGSEQ_LARGEST, + IGRAPH_REALIZE_DEGSEQ_INDEX + }; + char *method_names[] = { + "smallest", "largest", "index" + }; + + // Edge cases + printf("===EDGE CASES===\n"); + + // Tests that should fail with IGRAPH_EINVAL + printf("--ds1 empty--\n"); + igraph_vector_int_init(&ds1, 0); + igraph_vector_int_init_int(&ds2, 2, + 1, 1); + CHECK_ERROR( + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST), + IGRAPH_EINVAL + ); + + printf("--ds2 empty--\n"); + CHECK_ERROR( + igraph_realize_bipartite_degree_sequence(&g, &ds2, &ds1, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST), + IGRAPH_EINVAL + ); + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + + printf("\n===Empty degree sequences===\n"); + + igraph_vector_int_init(&ds1, 0); + igraph_vector_int_init(&ds2, 0); + for (size_t i=0; i < sizeof(methods) / sizeof(methods[0]); i++) { + printf("Method: %s\n", method_names[i]); + igraph_realize_bipartite_degree_sequence( + &g, &ds1, &ds2, IGRAPH_SIMPLE_SW, methods[i]); + print_graph(&g); + printf("\n"); + igraph_destroy(&g); + } + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + + printf("\n===All 0 degree sequences===\n"); + + igraph_vector_int_init(&ds1, 3); + igraph_vector_int_init(&ds2, 4); + for (size_t i=0; i < sizeof(methods) / sizeof(methods[0]); i++) { + printf("Method: %s\n", method_names[i]); + igraph_realize_bipartite_degree_sequence( + &g, &ds1, &ds2, IGRAPH_SIMPLE_SW, methods[i]); + print_graph(&g); + printf("\n"); + igraph_destroy(&g); + } + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + + printf("\n===TESTING SMALLEST METHOD===\n"); + // Simple undirected bipartite graph. + printf("\n===Simple, connected graph===\n"); + igraph_int_t deg1[] = {2, 3, 2, 1}; + igraph_int_t deg2[] = {3, 1, 2, 1, 1}; + + igraph_vector_int_init_array(&ds1, deg1, 4); + igraph_vector_int_init_array(&ds2, deg2, 5); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + igraph_is_connected(&g, &is_connected, IGRAPH_STRONG); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + printf("Simple: %d\n", is_simple); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + // undirected bipartite multigraph. + igraph_bool_t has_multi; + printf("\n===Connected multigraph===\n"); + + igraph_int_t deg1m[] = {2, 3, 1}; + igraph_int_t deg2m[] = {4, 2}; + + igraph_vector_int_init_array(&ds1, deg1m, 3); + igraph_vector_int_init_array(&ds2, deg2m, 2); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_MULTI_SW, IGRAPH_REALIZE_DEGSEQ_SMALLEST); + + igraph_has_multiple(&g, &has_multi); + igraph_is_connected(&g, &is_connected, IGRAPH_STRONG); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + printf("vcount: %" IGRAPH_PRId "\n", igraph_vcount(&g)); + printf("Has multiedges: %d\n", has_multi); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + printf("\n===TESTING LARGEST METHOD===\n"); + // Simple undirected bipartite graph. + printf("\n===Simple graph===\n"); + igraph_int_t deg1l[] = {2, 3, 2, 1}; + igraph_int_t deg2l[] = {3, 1, 2, 1, 1}; + + igraph_vector_int_init_array(&ds1, deg1l, 4); + igraph_vector_int_init_array(&ds2, deg2l, 5); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_LARGEST); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + // For this method, it does not have to be connected + igraph_is_connected(&g, &is_connected, IGRAPH_WEAK); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + printf("Simple: %d\n", is_simple); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + // undirected bipartite multigraph. + printf("\n===Multigraph===\n"); + + igraph_int_t deg1l_m[] = {2, 3, 1}; + igraph_int_t deg2l_m[]= {4, 2}; + + igraph_vector_int_init_array(&ds1, deg1l_m, 3); + igraph_vector_int_init_array(&ds2, deg2l_m, 2); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_MULTI_SW, IGRAPH_REALIZE_DEGSEQ_LARGEST); + + igraph_has_multiple(&g, &has_multi); + // For this method, it does not need to be connected + igraph_is_connected(&g, &is_connected, IGRAPH_WEAK); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + printf("Has multiedges: %d\n", has_multi); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + printf("\n===TESTING INDEX METHOD===\n"); + // Simple, undirected bipartite. + printf("\n===Simple graph===\n"); + igraph_int_t deg1i[] = {1, 4, 3, 2}; + igraph_int_t deg2i[] = {2, 1, 1, 4, 2}; + + igraph_vector_int_init_array(&ds1, deg1i, 4); + igraph_vector_int_init_array(&ds2, deg2i, 5); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_SIMPLE_SW, IGRAPH_REALIZE_DEGSEQ_INDEX); + + igraph_is_simple(&g, &is_simple, IGRAPH_DIRECTED); + // For this method, it does not have to be connected + igraph_is_connected(&g, &is_connected, IGRAPH_STRONG); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + printf("Simple: %d\n", is_simple); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + // undirected bipartite multigraph. + printf("\n===Multigraph===\n"); + + igraph_int_t deg1i_m[] = {2, 3, 1}; + igraph_int_t deg2i_m[]= {4, 2}; + + igraph_vector_int_init_array(&ds1, deg1i_m, 3); + igraph_vector_int_init_array(&ds2, deg2i_m, 2); + + igraph_realize_bipartite_degree_sequence(&g, &ds1, &ds2, IGRAPH_MULTI_SW, IGRAPH_REALIZE_DEGSEQ_INDEX); + + print_graph(&g); + check_degrees(&g, &ds1, &ds2); + + igraph_has_multiple(&g, &has_multi); + // For this method, it does not need to be connected + igraph_is_connected(&g, &is_connected, IGRAPH_STRONG); + igraph_is_bipartite(&g, &is_bipartite, NULL); + + printf("Has multiedges: %d\n", has_multi); + printf("Connected: %d\n", is_connected); + printf("Bipartite: %d\n", is_bipartite); + + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + igraph_destroy(&g); + + // A bidegree sequence that is not potentially connected + igraph_vector_int_init_int(&ds1, 3, + 1, 1, 1); + for (size_t i=0; i < sizeof(methods) / sizeof(methods[0]); i++) { + igraph_realize_bipartite_degree_sequence( + &g, &ds1, &ds1, IGRAPH_SIMPLE_SW, methods[i]); + igraph_is_bipartite(&g, &is_bipartite, NULL); + IGRAPH_ASSERT(is_bipartite); + igraph_destroy(&g); + } + igraph_vector_int_destroy(&ds1); + + // A bidegree sequence that can only be realized as a multigraph, + // but not a simple graph + igraph_vector_int_init_int(&ds1, 3, + 1, 3, 1); + igraph_vector_int_init_int(&ds2, 2, + 2, 3); + for (size_t i=0; i < sizeof(methods) / sizeof(methods[0]); i++) { + CHECK_ERROR( + igraph_realize_bipartite_degree_sequence( + &g, &ds1, &ds2, IGRAPH_SIMPLE_SW, methods[i]), + IGRAPH_EINVAL + ); + + igraph_realize_bipartite_degree_sequence( + &g, &ds1, &ds2, IGRAPH_MULTI_SW, methods[i]); + + igraph_is_bipartite(&g, &is_bipartite, NULL); + IGRAPH_ASSERT(is_bipartite); + igraph_destroy(&g); + } + igraph_vector_int_destroy(&ds1); + igraph_vector_int_destroy(&ds2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_realize_bipartite_degree_sequence.out b/tests/unit/igraph_realize_bipartite_degree_sequence.out new file mode 100644 index 0000000..638007d --- /dev/null +++ b/tests/unit/igraph_realize_bipartite_degree_sequence.out @@ -0,0 +1,148 @@ +===EDGE CASES=== +--ds1 empty-- +--ds2 empty-- + +===Empty degree sequences=== +Method: smallest +directed: false +vcount: 0 +edges: { +} + +Method: largest +directed: false +vcount: 0 +edges: { +} + +Method: index +directed: false +vcount: 0 +edges: { +} + + +===All 0 degree sequences=== +Method: smallest +directed: false +vcount: 7 +edges: { +} + +Method: largest +directed: false +vcount: 7 +edges: { +} + +Method: index +directed: false +vcount: 7 +edges: { +} + + +===TESTING SMALLEST METHOD=== + +===Simple, connected graph=== +directed: false +vcount: 9 +edges: { +4 3 +8 1 +7 1 +4 1 +5 0 +6 0 +4 2 +6 2 +} +Simple: 1 +Connected: 1 +Bipartite: 1 + +===Connected multigraph=== +directed: false +vcount: 5 +edges: { +3 2 +3 0 +3 0 +3 1 +4 1 +4 1 +} +vcount: 5 +Has multiedges: 1 +Connected: 1 +Bipartite: 1 + +===TESTING LARGEST METHOD=== + +===Simple graph=== +directed: false +vcount: 9 +edges: { +4 1 +6 1 +5 1 +4 0 +6 0 +4 2 +7 2 +8 3 +} +Simple: 1 +Connected: 0 +Bipartite: 1 + +===Multigraph=== +directed: false +vcount: 5 +edges: { +3 1 +3 1 +3 0 +4 0 +4 1 +3 2 +} +Has multiedges: 1 +Connected: 1 +Bipartite: 1 + +===TESTING INDEX METHOD=== + +===Simple graph=== +directed: false +vcount: 9 +edges: { +7 0 +7 1 +4 1 +8 1 +5 1 +7 2 +4 2 +8 2 +7 3 +6 3 +} +Simple: 1 +Connected: 1 +Bipartite: 1 + +===Multigraph=== +directed: false +vcount: 5 +edges: { +3 0 +3 0 +3 1 +4 1 +4 1 +3 2 +} +Has multiedges: 1 +Connected: 1 +Bipartite: 1 diff --git a/tests/unit/igraph_realize_degree_sequence.c b/tests/unit/igraph_realize_degree_sequence.c new file mode 100644 index 0000000..0d4efcc --- /dev/null +++ b/tests/unit/igraph_realize_degree_sequence.c @@ -0,0 +1,138 @@ + +#include +#include + +#include "test_utilities.h" + +void realize1(igraph_vector_int_t *ods, igraph_vector_int_t *ids, igraph_edge_type_sw_t et, igraph_realize_degseq_t method) { + igraph_t graph; + int err; + err = igraph_realize_degree_sequence(&graph, ods, ids, et, method); + if (err == IGRAPH_SUCCESS) { + printf("\n"); + print_graph(&graph); + igraph_destroy(&graph); + } else if (err == IGRAPH_UNIMPLEMENTED) { + printf(" not implemented\n"); + } else { + printf(" not graphical\n"); + } +} + +void realize2(igraph_vector_int_t *ods, igraph_vector_int_t *ids, igraph_edge_type_sw_t et) { + printf("Largest:"); + realize1(ods, ids, et, IGRAPH_REALIZE_DEGSEQ_LARGEST); + printf("Smallest:"); + realize1(ods, ids, et, IGRAPH_REALIZE_DEGSEQ_SMALLEST); + printf("Index:"); + realize1(ods, ids, et, IGRAPH_REALIZE_DEGSEQ_INDEX); +} + +void undirected_print_destroy(igraph_vector_int_t *ds) { + print_vector_int(ds); + printf("\nSIMPLE GRAPH:\n"); + realize2(ds, NULL, IGRAPH_SIMPLE_SW); + printf("\nLOOPLESS MULTIGRAPH:\n"); + realize2(ds, NULL, IGRAPH_MULTI_SW); + printf("\nLOOPY MULTIGRAPH:\n"); + realize2(ds, NULL, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW); + printf("\n\n"); + igraph_vector_int_destroy(ds); +} + +void directed_print_destroy(igraph_vector_int_t *ods, igraph_vector_int_t *ids) { + print_vector_int(ods); + print_vector_int(ids); + printf("\nSIMPLE GRAPH:\n"); + realize2(ods, ids, IGRAPH_SIMPLE_SW); + printf("\nLOOPLESS MULTIGRAPH:\n"); + realize2(ods, ids, IGRAPH_MULTI_SW); + printf("\nLOOPY MULTIGRAPH:\n"); + realize2(ods, ids, IGRAPH_MULTI_SW | IGRAPH_LOOPS_SW); + printf("\n\n"); + igraph_vector_int_destroy(ods); + igraph_vector_int_destroy(ids); +} + + +int main(void) { + igraph_vector_int_t ds, ods, ids; + + igraph_set_error_handler(&igraph_error_handler_ignore); + + /* Undirected */ + + igraph_vector_int_init(&ds, 0); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 2, 3, -1); + undirected_print_destroy(&ds); + + /* contains negative degree */ + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 2, -3, -1); + undirected_print_destroy(&ds); + + /* odd sum */ + igraph_vector_int_init_int_end(&ds, -1, 1, 1, 2, 3, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 2, 3, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 4, 4, 4, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 3, 5, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 5, 3, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 1, 3, 3, 4, 1, 2, 1, 1, 1, 3, -1); + undirected_print_destroy(&ds); + + igraph_vector_int_init_int_end(&ds, -1, 2, 0, 3, 2, 2, 2, 2, 3, -1); + undirected_print_destroy(&ds); + + /* Directed */ + + igraph_vector_int_init(&ods, 0); + igraph_vector_int_init(&ids, 0); + directed_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ods, -1, 3, 0, 1, 1, 1, 1, 0, 1, -1); + igraph_vector_int_init_int_end(&ids, -1, 2, 1, 0, 2, 2, 1, 0, 0, -1); + directed_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ods, -1, 3, 1, 2, 3, 1, 2, 2, -1); + igraph_vector_int_init_int_end(&ids, -1, 2, 2, 1, 2, 3, 2, 2, -1); + directed_print_destroy(&ods, &ids); + + /* single loops: graphical, but multi-eges only: non-graphical */ + igraph_vector_int_init_int_end(&ids, -1, 1, 0, 2, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 1, 2, -1); + directed_print_destroy(&ods, &ids); + + /* same as before, different ordering, to test the "index" method */ + igraph_vector_int_init_int_end(&ids, -1, 2, 0, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 2, 1, 0, -1); + directed_print_destroy(&ods, &ids); + + /* same as before, different ordering, to test the "index" method */ + igraph_vector_int_init_int_end(&ids, -1, 0, 2, 1, -1); + igraph_vector_int_init_int_end(&ods, -1, 1, 2, 0, -1); + directed_print_destroy(&ods, &ids); + + igraph_vector_int_init_int_end(&ids, -1, 2, 0, -1); + igraph_vector_int_init_int_end(&ods, -1, 0, 2, -1); + directed_print_destroy(&ods, &ids); + + /* simple complete graph on 4 vertices */ + igraph_vector_int_init_int_end(&ids, -1, 3, 3, 3, 3, -1); + igraph_vector_int_init_int_end(&ods, -1, 3, 3, 3, 3, -1); + directed_print_destroy(&ods, &ids); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_realize_degree_sequence.out b/tests/unit/igraph_realize_degree_sequence.out new file mode 100644 index 0000000..62ccb1f --- /dev/null +++ b/tests/unit/igraph_realize_degree_sequence.out @@ -0,0 +1,974 @@ +( ) + +SIMPLE GRAPH: +Largest: +directed: false +vcount: 0 +edges: { +} +Smallest: +directed: false +vcount: 0 +edges: { +} +Index: +directed: false +vcount: 0 +edges: { +} + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 0 +edges: { +} +Smallest: +directed: false +vcount: 0 +edges: { +} +Index: +directed: false +vcount: 0 +edges: { +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 0 +edges: { +} +Smallest: +directed: false +vcount: 0 +edges: { +} +Index: +directed: false +vcount: 0 +edges: { +} + + +( 1 2 2 3 ) + +SIMPLE GRAPH: +Largest: +directed: false +vcount: 4 +edges: { +3 0 +3 2 +3 1 +2 1 +} +Smallest: +directed: false +vcount: 4 +edges: { +3 0 +3 2 +3 1 +2 1 +} +Index: +directed: false +vcount: 4 +edges: { +3 0 +3 1 +2 1 +3 2 +} + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 4 +edges: { +3 1 +3 2 +1 0 +3 2 +} +Smallest: +directed: false +vcount: 4 +edges: { +3 0 +3 1 +2 1 +3 2 +} +Index: +directed: false +vcount: 4 +edges: { +3 0 +3 1 +2 1 +3 2 +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 4 +edges: { +3 1 +3 2 +1 0 +3 2 +} +Smallest: +directed: false +vcount: 4 +edges: { +3 0 +3 1 +2 1 +3 2 +} +Index: +directed: false +vcount: 4 +edges: { +3 0 +3 1 +2 1 +3 2 +} + + +( 1 2 2 -3 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPY MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + + +( 1 1 2 3 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPY MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + + +( 1 2 3 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 3 +edges: { +2 1 +2 0 +2 1 +} +Smallest: +directed: false +vcount: 3 +edges: { +2 0 +2 1 +2 1 +} +Index: +directed: false +vcount: 3 +edges: { +2 0 +2 1 +2 1 +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 3 +edges: { +2 1 +2 0 +2 1 +} +Smallest: +directed: false +vcount: 3 +edges: { +2 0 +2 1 +2 1 +} +Index: +directed: false +vcount: 3 +edges: { +2 0 +2 1 +2 1 +} + + +( 4 4 4 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 3 +edges: { +1 0 +2 1 +2 0 +2 1 +2 0 +1 0 +} +Smallest: +directed: false +vcount: 3 +edges: { +2 0 +1 0 +2 0 +1 0 +2 1 +2 1 +} +Index: +directed: false +vcount: 3 +edges: { +1 0 +2 0 +2 0 +1 0 +2 1 +2 1 +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 3 +edges: { +1 0 +2 1 +2 0 +2 1 +2 0 +1 0 +} +Smallest: +directed: false +vcount: 3 +edges: { +2 0 +1 0 +2 0 +1 0 +2 1 +2 1 +} +Index: +directed: false +vcount: 3 +edges: { +1 0 +2 0 +2 0 +1 0 +2 1 +2 1 +} + + +( 3 5 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +1 1 +} +Smallest: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +1 1 +} +Index: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +1 1 +} + + +( 5 3 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +0 0 +} +Smallest: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +0 0 +} +Index: +directed: false +vcount: 2 +edges: { +1 0 +1 0 +1 0 +0 0 +} + + +( 1 3 3 4 1 2 1 1 1 3 ) + +SIMPLE GRAPH: +Largest: +directed: false +vcount: 10 +edges: { +5 3 +9 3 +3 2 +3 1 +2 1 +9 1 +9 2 +8 5 +7 6 +4 0 +} +Smallest: +directed: false +vcount: 10 +edges: { +8 3 +7 3 +9 6 +4 2 +1 0 +2 1 +9 1 +9 3 +5 3 +5 2 +} +Index: +directed: false +vcount: 10 +edges: { +3 0 +3 1 +9 1 +2 1 +9 2 +3 2 +5 3 +5 4 +9 6 +8 7 +} + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 10 +edges: { +3 1 +9 2 +5 3 +9 1 +3 2 +4 0 +7 6 +8 5 +9 1 +3 2 +} +Smallest: +directed: false +vcount: 10 +edges: { +8 3 +7 1 +6 2 +9 4 +3 0 +5 3 +5 1 +2 1 +9 2 +9 3 +} +Index: +directed: false +vcount: 10 +edges: { +3 0 +3 1 +2 1 +9 1 +9 2 +3 2 +5 3 +5 4 +9 6 +8 7 +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 10 +edges: { +3 1 +9 2 +5 3 +9 1 +3 2 +4 0 +7 6 +8 5 +9 1 +3 2 +} +Smallest: +directed: false +vcount: 10 +edges: { +8 3 +7 1 +6 2 +9 4 +3 0 +5 3 +5 1 +2 1 +9 2 +9 3 +} +Index: +directed: false +vcount: 10 +edges: { +3 0 +3 1 +2 1 +9 1 +9 2 +3 2 +5 3 +5 4 +9 6 +8 7 +} + + +( 2 0 3 2 2 2 2 3 ) + +SIMPLE GRAPH: +Largest: +directed: false +vcount: 8 +edges: { +7 6 +7 5 +7 2 +4 2 +3 2 +3 0 +4 0 +6 5 +} +Smallest: +directed: false +vcount: 8 +edges: { +7 6 +6 2 +7 2 +5 2 +5 4 +4 3 +3 0 +7 0 +} +Index: +directed: false +vcount: 8 +edges: { +7 0 +2 0 +7 2 +6 2 +5 3 +4 3 +5 4 +7 6 +} + +LOOPLESS MULTIGRAPH: +Largest: +directed: false +vcount: 8 +edges: { +7 2 +3 0 +5 4 +7 6 +3 2 +5 0 +7 4 +6 2 +} +Smallest: +directed: false +vcount: 8 +edges: { +6 2 +7 6 +7 0 +3 0 +4 3 +5 4 +5 2 +7 2 +} +Index: +directed: false +vcount: 8 +edges: { +2 0 +7 0 +7 2 +3 2 +4 3 +5 4 +6 5 +7 6 +} + +LOOPY MULTIGRAPH: +Largest: +directed: false +vcount: 8 +edges: { +7 2 +3 0 +5 4 +7 6 +3 2 +5 0 +7 4 +6 2 +} +Smallest: +directed: false +vcount: 8 +edges: { +6 2 +7 6 +7 0 +3 0 +4 3 +5 4 +5 2 +7 2 +} +Index: +directed: false +vcount: 8 +edges: { +2 0 +7 0 +7 2 +3 2 +4 3 +5 4 +6 5 +7 6 +} + + +( ) +( ) + +SIMPLE GRAPH: +Largest: +directed: true +vcount: 0 +edges: { +} +Smallest: +directed: true +vcount: 0 +edges: { +} +Index: +directed: true +vcount: 0 +edges: { +} + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 3 0 1 1 1 1 0 1 ) +( 2 1 0 2 2 1 0 0 ) + +SIMPLE GRAPH: +Largest: +directed: true +vcount: 8 +edges: { +0 3 +0 4 +0 5 +3 0 +4 0 +5 4 +2 3 +7 1 +} +Smallest: +directed: true +vcount: 8 +edges: { +7 0 +2 3 +5 4 +3 0 +0 4 +0 3 +0 5 +4 1 +} +Index: +directed: true +vcount: 8 +edges: { +0 3 +0 4 +0 5 +2 0 +3 4 +4 3 +5 0 +7 1 +} + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 3 1 2 3 1 2 2 ) +( 2 2 1 2 3 2 2 ) + +SIMPLE GRAPH: +Largest: +directed: true +vcount: 7 +edges: { +4 0 +3 4 +3 5 +3 6 +1 4 +0 1 +0 3 +0 5 +6 2 +6 1 +2 6 +2 3 +5 0 +5 4 +} +Smallest: +directed: true +vcount: 7 +edges: { +2 4 +2 0 +0 3 +0 5 +0 6 +6 4 +6 1 +1 3 +3 5 +3 4 +3 1 +4 6 +5 0 +5 2 +} +Index: +directed: true +vcount: 7 +edges: { +0 4 +0 3 +0 5 +1 6 +2 4 +2 1 +3 0 +3 6 +3 5 +4 0 +5 4 +5 3 +6 1 +6 2 +} + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 0 1 2 ) +( 1 0 2 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 2 1 0 ) +( 2 0 1 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 1 2 0 ) +( 0 2 1 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 0 2 ) +( 2 0 ) + +SIMPLE GRAPH: +Largest: not graphical +Smallest: not graphical +Index: not graphical + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + +( 3 3 3 3 ) +( 3 3 3 3 ) + +SIMPLE GRAPH: +Largest: +directed: true +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 0 +1 2 +1 3 +2 0 +2 1 +2 3 +3 0 +3 1 +3 2 +} +Smallest: +directed: true +vcount: 4 +edges: { +3 0 +3 1 +3 2 +2 3 +2 0 +2 1 +1 3 +1 2 +1 0 +0 3 +0 2 +0 1 +} +Index: +directed: true +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 0 +1 2 +1 3 +2 0 +2 1 +2 3 +3 0 +3 1 +3 2 +} + +LOOPLESS MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + +LOOPY MULTIGRAPH: +Largest: not implemented +Smallest: not implemented +Index: not implemented + + diff --git a/tests/unit/igraph_recent_degree_aging_game.c b/tests/unit/igraph_recent_degree_aging_game.c new file mode 100644 index 0000000..0877ef3 --- /dev/null +++ b/tests/unit/igraph_recent_degree_aging_game.c @@ -0,0 +1,93 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t tree; + igraph_vector_int_t outseq; + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/0, /*edges per step(m)*/ 1, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 1, /*aging_exp*/ 1, /*aging_bins*/ 1, /*time_window*/ 1, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("No edges:\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/5, /*edges per step(m)*/ 0, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 1, /*aging_exp*/ 1, /*aging_bins*/ 6, /*time_window*/ 1, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Prefer more edges to make a star of double edges:\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/5, /*edges per step(m)*/ 2, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 20, /*aging_exp*/ 0, /*aging_bins*/ 1, /*time_window*/ 100, /*zero appeal*/ 0.001, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Prefer older edges to make a star:\n"); + igraph_vector_int_init_int(&outseq, 7, 1, 2, 1, 2, 1, 2, 1); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/7, /*edges per step(m)*/ 0, &outseq, + /*outpref?*/ 0, /*pa_exp*/ 0, /*aging_exp*/ 20, /*aging_bins*/ 8, /*time_window*/ 100, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Checking if adding one edge per step makes a tree.\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/10, /*edges per step(m)*/ 1, /*outseq*/ NULL, + /*outpref?*/ 1, /*pa_exp*/ 2, /*aging_exp*/ 2, /*aging_bins*/ 5, /*time_window*/ 4, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + igraph_is_tree(&g, &tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(tree); + igraph_destroy(&g); + igraph_vector_int_destroy(&outseq); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking if these errors are properly handled:\n"); + printf("Negative number of vertices.\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/-20, /*edges per step(m)*/ 1, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 2, /*aging_exp*/ 2, /*aging_bins*/ 3, /*time_window*/ 4, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + printf("Negative number of edges.\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/20, /*edges per step(m)*/ -1, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 2, /*aging_exp*/ 2, /*aging_bins*/ 10, /*time_window*/ 4, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + printf("Negative aging bin.\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/20, /*edges per step(m)*/ 1, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 2, /*aging_exp*/ 2, /*aging_bins*/ -3, /*time_window*/ 4, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + printf("Negative zero appeal.\n"); + IGRAPH_ASSERT(igraph_recent_degree_aging_game(&g, /*nodes*/20, /*edges per step(m)*/ 1, /*outseq*/ NULL, + /*outpref?*/ 0, /*pa_exp*/ 2, /*aging_exp*/ 2, /*aging_bins*/ 10, /*time_window*/ 4, /*zero appeal*/ -1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_recent_degree_aging_game.out b/tests/unit/igraph_recent_degree_aging_game.out new file mode 100644 index 0000000..6aeec62 --- /dev/null +++ b/tests/unit/igraph_recent_degree_aging_game.out @@ -0,0 +1,43 @@ +No vertices: +directed: false +vcount: 0 +edges: { +} +No edges: +directed: false +vcount: 5 +edges: { +} +Prefer more edges to make a star of double edges: +directed: false +vcount: 5 +edges: { +0 1 +0 1 +0 2 +0 2 +0 3 +0 3 +0 4 +0 4 +} +Prefer older edges to make a star: +directed: false +vcount: 7 +edges: { +0 1 +0 1 +0 2 +0 3 +0 3 +0 4 +0 5 +0 5 +0 6 +} +Checking if adding one edge per step makes a tree. +Checking if these errors are properly handled: +Negative number of vertices. +Negative number of edges. +Negative aging bin. +Negative zero appeal. diff --git a/tests/unit/igraph_recent_degree_game.c b/tests/unit/igraph_recent_degree_game.c new file mode 100644 index 0000000..997f734 --- /dev/null +++ b/tests/unit/igraph_recent_degree_game.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/0, /*power*/ 0.0, + /*window*/ 1, /*edges per step(m)*/ 1, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("No edges:\n"); + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ 5, /*power*/ 0.0, + /*window*/ 1, /*edges per step(m)*/ 0, /*outseq*/ NULL, /*outpref?*/ 1, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("A star with double edges.\n"); + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ 10, /*power*/ 30.0, + /*window*/ 100, /*edges per step(m)*/ 2, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ 0.001, + /*directed?*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /*Negative number of vertices*/ + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ -1, /*power*/ 10.0, + /*window*/ 100, /*edges per step(m)*/ 1, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + /*Negative number of edges*/ + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ 1, /*power*/ 10.0, + /*window*/ 100, /*edges per step(m)*/ -1, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + /*Negative window*/ + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ 1, /*power*/ 10.0, + /*window*/ -100, /*edges per step(m)*/ 1, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ 1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + /*Negative zero appeal*/ + IGRAPH_ASSERT(igraph_recent_degree_game(&g, /*number of vertices (n)*/ 1, /*power*/ 10.0, + /*window*/ 100, /*edges per step(m)*/ 1, /*outseq*/ NULL, /*outpref?*/ 0, /*zero appeal*/ -1, + /*directed?*/ 0) == IGRAPH_EINVAL); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_recent_degree_game.out b/tests/unit/igraph_recent_degree_game.out new file mode 100644 index 0000000..f8000d8 --- /dev/null +++ b/tests/unit/igraph_recent_degree_game.out @@ -0,0 +1,33 @@ +No vertices: +directed: false +vcount: 0 +edges: { +} +No edges: +directed: false +vcount: 5 +edges: { +} +A star with double edges. +directed: true +vcount: 10 +edges: { +1 0 +1 0 +2 0 +2 0 +3 0 +3 0 +4 0 +4 0 +5 0 +5 0 +6 0 +6 0 +7 0 +7 0 +8 0 +8 0 +9 0 +9 0 +} diff --git a/tests/unit/igraph_reindex_membership.c b/tests/unit/igraph_reindex_membership.c new file mode 100644 index 0000000..8a4fee8 --- /dev/null +++ b/tests/unit/igraph_reindex_membership.c @@ -0,0 +1,86 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check(igraph_int_t n, igraph_int_t type_index_count, igraph_int_t type_count) { + igraph_vector_int_t membership; + igraph_vector_int_t mapping; + igraph_vector_int_t new_to_old; + igraph_int_t nb_clusters; + + igraph_vector_int_init(&new_to_old, 0); + + igraph_vector_int_init(&mapping, type_count); + for (igraph_int_t i=0; i < type_count; i++) { + VECTOR(mapping)[i] = RNG_INTEGER(0, type_index_count-1); + } + + igraph_vector_int_init(&membership, n); + for (igraph_int_t i=0; i < n; i++) { + VECTOR(membership)[i] = VECTOR(mapping)[ RNG_INTEGER(0, type_count-1) ]; + } + + igraph_int_t min_original_type = n > 0 ? igraph_vector_int_min(&membership) : 0; + igraph_int_t max_original_type = n > 0 ? igraph_vector_int_max(&membership) : 0; + + printf("Old: "); print_vector_int(&membership); + igraph_reindex_membership(&membership, &new_to_old, &nb_clusters); + printf("New: "); print_vector_int(&membership); + printf("New to old: "); print_vector_int(&new_to_old); + printf("nb_clusters: %" IGRAPH_PRId "\n\n", nb_clusters); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == n); + if (n == 0) { + IGRAPH_ASSERT(nb_clusters == 0); + IGRAPH_ASSERT(igraph_vector_int_size(&new_to_old) == 0); + } else { + IGRAPH_ASSERT(nb_clusters == igraph_vector_int_max(&membership) + 1); + IGRAPH_ASSERT(nb_clusters <= type_count); + IGRAPH_ASSERT(igraph_vector_int_min(&membership) == 0); + + IGRAPH_ASSERT(igraph_vector_int_size(&new_to_old) == nb_clusters); + IGRAPH_ASSERT(igraph_vector_int_min(&new_to_old) == min_original_type); + IGRAPH_ASSERT(igraph_vector_int_max(&new_to_old) == max_original_type); + } + + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&mapping); + igraph_vector_int_destroy(&new_to_old); +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 42); + + check(0, 10, 7); + + check(1, 1, 1); + check(1, 10, 2); + + check(10, 10, 7); + check(10, 100, 7); + + check(7, 7, 7); + check(7, 100, 7); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_residual_graph.c b/tests/unit/igraph_residual_graph.c new file mode 100644 index 0000000..1f9af78 --- /dev/null +++ b/tests/unit/igraph_residual_graph.c @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g, residual, expected_residual; + igraph_vector_t capacity, residual_capacity, flow, expected_residual_capacity; + igraph_bool_t iso; + + igraph_vector_init(&residual_capacity, 0); + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_small(&expected_residual, 6, IGRAPH_DIRECTED, + 0, 1, 1, 2, 1, 3, 2, 4, 3, 5, 4, 5, -1); + igraph_vector_init_int_end(&capacity, -1, 4, 2, 2, 3, 4, 1, 2, 5, -1); + igraph_vector_init_int_end(&flow, -1, 3, 2, 1, 2, 3, 1, 1, 4, -1); + igraph_vector_init_int_end(&expected_residual_capacity, -1, 1, 1, 1, 1, 1, 1, -1); + + igraph_residual_graph(&g, &capacity, &residual, &residual_capacity, &flow); + + /* tests */ + + IGRAPH_ASSERT(!igraph_isomorphic(&residual, &expected_residual, &iso)); + + IGRAPH_ASSERT(iso); + + IGRAPH_ASSERT(igraph_vector_all_e(&expected_residual_capacity, &residual_capacity)); + + /* cleanup */ + + igraph_vector_destroy(&capacity); + igraph_vector_destroy(&residual_capacity); + igraph_vector_destroy(&flow); + igraph_vector_destroy(&expected_residual_capacity); + + igraph_destroy(&g); + igraph_destroy(&residual); + igraph_destroy(&expected_residual); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_reverse_edges.c b/tests/unit/igraph_reverse_edges.c new file mode 100644 index 0000000..1d21491 --- /dev/null +++ b/tests/unit/igraph_reverse_edges.c @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + + igraph_small(&graph, 0, IGRAPH_DIRECTED, + 0,1, 1,2, 2,3, 3,1, 1,4, + -1); + + printf("Original graph:\n"); + print_graph(&graph); + + printf("Reverse one edge:\n"); + igraph_reverse_edges(&graph, igraph_ess_1(2)); + print_graph(&graph); + + printf("Reverse all edges:\n"); + igraph_reverse_edges(&graph, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + print_graph(&graph); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_rewire.c b/tests/unit/igraph_rewire.c new file mode 100644 index 0000000..25bba19 --- /dev/null +++ b/tests/unit/igraph_rewire.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "operators/rewire_internal.h" + +#include "test_utilities.h" + +static void check_rewiring(igraph_tree_mode_t tree_mode, igraph_bool_t use_adjlist, igraph_bool_t allow_loops, const char* description) { + + igraph_t g; + igraph_vector_int_t indegree_before, outdegree_before, indegree_after, outdegree_after; + + igraph_kary_tree(&g, 10, 3, tree_mode); + + igraph_vector_int_init(&indegree_before, 0); + igraph_vector_int_init(&outdegree_before, 0); + igraph_degree(&g, &indegree_before, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_degree(&g, &outdegree_before, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + + igraph_i_rewire(&g, 1000, allow_loops ? IGRAPH_LOOPS_SW : IGRAPH_SIMPLE_SW, use_adjlist, NULL); + + igraph_vector_int_init(&indegree_after, 0); + igraph_vector_int_init(&outdegree_after, 0); + igraph_degree(&g, &indegree_after, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS); + igraph_degree(&g, &outdegree_after, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS); + + if ((!igraph_vector_int_all_e(&indegree_before, &indegree_after)) || + (!igraph_vector_int_all_e(&outdegree_before, &outdegree_after))) { + + print_graph(&g); + IGRAPH_FATALF("%s: graph degrees changed. Rewired graph is above.\n", description); + } + + igraph_destroy(&g); + igraph_vector_int_destroy(&indegree_before); + igraph_vector_int_destroy(&outdegree_before); + igraph_vector_int_destroy(&indegree_after); + igraph_vector_int_destroy(&outdegree_after); + +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 3925); + + /* Short test for the top-level igraph_rewire() functions (instead of igraph_i_rewire()). */ + { + igraph_t graph; + igraph_cycle_graph(&graph, 12, IGRAPH_UNDIRECTED, /* mutual= */ false); + igraph_rewire(&graph, 50, IGRAPH_SIMPLE_SW, NULL); + igraph_destroy(&graph); + } + + check_rewiring(IGRAPH_TREE_OUT, 0, 0, "Directed, no loops, standard-method"); + check_rewiring(IGRAPH_TREE_OUT, 1, 0, "Directed, no loops, adjlist-method"); + check_rewiring(IGRAPH_TREE_OUT, 0, 1, "Directed, loops, standard-method"); + check_rewiring(IGRAPH_TREE_OUT, 1, 1, "Directed, loops, adjlist-method"); + check_rewiring(IGRAPH_TREE_UNDIRECTED, 0, 0, "Undirected, no loops, standard-method"); + check_rewiring(IGRAPH_TREE_UNDIRECTED, 1, 0, "Undirected, no loops, adjlist-method"); + check_rewiring(IGRAPH_TREE_UNDIRECTED, 0, 1, "Undirected, loops, standard-method"); + check_rewiring(IGRAPH_TREE_UNDIRECTED, 1, 1, "Undirected, loops, adjlist-method"); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_rewire_directed_edges.c b/tests/unit/igraph_rewire_directed_edges.c new file mode 100644 index 0000000..d9fc72a --- /dev/null +++ b/tests/unit/igraph_rewire_directed_edges.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g, g_copy; + igraph_bool_t same; + igraph_vector_int_t degrees; + igraph_vs_t vertices; + igraph_rng_seed(igraph_rng_default(), 42); + + /*No edges, should just return the same graph*/ + igraph_small(&g, 5, IGRAPH_DIRECTED, -1); + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 0.1, + /*loops*/ false, /*mode*/ IGRAPH_ALL) + == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_ecount(&g) == 0); + IGRAPH_ASSERT(igraph_vcount(&g) == 5); + igraph_destroy(&g); + + /*No rewire*/ + igraph_small(&g, 10, IGRAPH_DIRECTED, 0,1, 0,3, 5,4, 4,8, 9,2, 9,3, 9,7, 7,7, 7,8, -1); + igraph_copy(&g_copy, &g); + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 0.0, + /*loops*/ false, /*mode*/ IGRAPH_ALL) + == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_copy, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(same); + + /*Rewire*/ + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 0.5, + /*loops*/ true, /*mode*/ IGRAPH_ALL) + == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_same_graph(&g, &g_copy, &same) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(!same); /*guaranteed for this seed*/ + IGRAPH_ASSERT(igraph_ecount(&g) == 9); + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + igraph_destroy(&g); + igraph_destroy(&g_copy); + + /*Out-star remains out-star if outs are moved*/ + igraph_small(&g, 10, IGRAPH_DIRECTED, 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8, 0,9, -1); + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 1.0, + /*loops*/ false, /*mode*/ IGRAPH_OUT) + == IGRAPH_SUCCESS); + igraph_vector_int_init(°rees, 0); + igraph_vs_1(&vertices, 0); + igraph_degree(&g, °rees, vertices, IGRAPH_ALL, IGRAPH_NO_LOOPS); + IGRAPH_ASSERT(VECTOR(degrees)[0] == 9); + igraph_vector_int_destroy(°rees); + igraph_vs_destroy(&vertices); + igraph_destroy(&g); + + /*Check if multiple edges are created when using mode == IGRAPH_ALL*/ + igraph_small(&g, 5, IGRAPH_DIRECTED, 0,1, 0,2, 0,3, 0,4, 1,2, 1,3, 1,4, 2,3, 2,4, 3,4, -1); + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 1.0, + /*loops*/ false, /*mode*/ IGRAPH_ALL) + == IGRAPH_SUCCESS); + print_graph_canon(&g); + + /*A few erroneous calls*/ + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ -0.1, /*loops*/ false, /*mode*/ IGRAPH_ALL) == IGRAPH_EINVAL); + IGRAPH_ASSERT(igraph_rewire_directed_edges(&g, /*probability*/ 1.1, /*loops*/ false, /*mode*/ IGRAPH_ALL) == IGRAPH_EINVAL); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_rewire_directed_edges.out b/tests/unit/igraph_rewire_directed_edges.out new file mode 100644 index 0000000..4802f6b --- /dev/null +++ b/tests/unit/igraph_rewire_directed_edges.out @@ -0,0 +1,14 @@ +directed: true +vcount: 5 +edges: { +0 4 +0 4 +1 0 +1 2 +1 2 +1 2 +2 0 +2 1 +3 1 +3 1 +} diff --git a/tests/unit/igraph_rng_get_integer.c b/tests/unit/igraph_rng_get_integer.c new file mode 100644 index 0000000..932bacf --- /dev/null +++ b/tests/unit/igraph_rng_get_integer.c @@ -0,0 +1,208 @@ +/* + igraph library. + Copyright (C) 2011-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +void simple_tests(void) { + int i; + + /* Seed the RNG, generate 10 random integers */ + igraph_rng_seed(igraph_rng_default(), 42); + for (i = 0; i < 10; i++) { + printf("%" IGRAPH_PRId "\n", igraph_rng_get_integer(igraph_rng_default(), 10, 100)); + } + + printf("========\n"); + + /* Seed the RNG again with the same seed, verify that we get the same + * numbers */ + igraph_rng_seed(igraph_rng_default(), 42); + for (i = 0; i < 10; i++) { + printf("%" IGRAPH_PRId "\n", igraph_rng_get_integer(igraph_rng_default(), 10, 100)); + } + + printf("========\n"); + + /* Seed the RNG again with a different seed, verify that we get different + * numbers */ + igraph_rng_seed(igraph_rng_default(), 84); + for (i = 0; i < 10; i++) { + printf("%" IGRAPH_PRId "\n", igraph_rng_get_integer(igraph_rng_default(), 10, 100)); + } + + printf("========\n"); +} + +void generate_random_vector( + igraph_rng_t* rng, igraph_vector_int_t* numbers, igraph_int_t lo, igraph_int_t hi +) { + igraph_int_t i, n = igraph_vector_int_size(numbers); + + for (i = 0; i < n; i++) { + VECTOR(*numbers)[i] = igraph_rng_get_integer(rng, lo, hi); + } + IGRAPH_ASSERT(igraph_vector_int_min(numbers) >= lo); + IGRAPH_ASSERT(igraph_vector_int_max(numbers) <= hi); +} + +/* Checks whether a given vector of numbers contains all numbers between `lo' + * and `hi' */ +void check_occurrences(const igraph_vector_int_t* numbers, igraph_int_t lo, igraph_int_t hi) { + igraph_int_t i; + + for (i = lo; i <= hi; i++) { + IGRAPH_ASSERT(igraph_vector_int_contains(numbers, i)); + } +} + +/* Checks whether each X consecutive bits of the given numbers contain all + * possible combinations of (0, 1) as a quick proxy for randomness, for X = 1..4 */ +void check_consecutive_bits(const igraph_vector_int_t* numbers, uint8_t num_bits) { + igraph_uint_t i, j, k, masked, n, mask, still_needed; + igraph_vector_bool_t seen; + + n = igraph_vector_int_size(numbers); + + for (j = 0; j < 4; j++) { + mask = ((igraph_int_t) 1 << (j + 1)) - 1; + igraph_vector_bool_init(&seen, mask + 1); + for (i = 0; i < num_bits - j; i++, mask <<= 1) { + still_needed = ((igraph_uint_t) 1 << (j + 1)); + igraph_vector_bool_fill(&seen, 0); + for (k = 0; k < n; k++) { + masked = (VECTOR(*numbers)[k] & mask) >> i; + if (!VECTOR(seen)[masked]) { + VECTOR(seen)[masked] = 1; + still_needed--; + if (!still_needed) { + break; + } + } + } + + if (still_needed) { + if (j > 0) { + printf( + "Expected %" IGRAPH_PRIu " consecutive bit(s) of random integers with " + "%d bits to contain all bit combinations.\n", + (j + 1), num_bits + ); + printf( + "Missing %" IGRAPH_PRIu " combination(s) in bits %" IGRAPH_PRIu "-%" IGRAPH_PRIu "\n", + still_needed, (i + j), i + ); + } else { + printf( + "Expected every bit of random integers with " + "%d bits to contain at least one 0 and at least one 1.\n", + num_bits + ); + printf( + "This does not hold for bit %" IGRAPH_PRIu " in this vector:\n", + i + ); + } + printf("\n"); + print_vector_int(numbers); + IGRAPH_ASSERT(!still_needed); + } + } + igraph_vector_bool_destroy(&seen); + } +} + +void stress_tests(void) { + igraph_rng_t rng; + const igraph_rng_type_t* rng_types[] = { + &igraph_rngtype_mt19937, + &igraph_rngtype_glibc2, + &igraph_rngtype_pcg32, + &igraph_rngtype_pcg64, + }; + igraph_vector_int_t numbers; + const igraph_int_t N = 1000; + + igraph_vector_int_init(&numbers, N); + + for (size_t i = 0; i < sizeof(rng_types) / sizeof(rng_types[0]); i++) { + igraph_error_handler_t *oldhandler = igraph_set_error_handler(&igraph_error_handler_printignore); + igraph_error_t err = igraph_rng_init(&rng, rng_types[i]); + switch (err) { + case IGRAPH_SUCCESS: + break; + case IGRAPH_UNIMPLEMENTED: + continue; + default: + IGRAPH_FATAL("Error while initializing RNG."); + } + igraph_set_error_handler(oldhandler); + + igraph_rng_seed(&rng, 42); + + /* We are going to test multiple ranges. In each range, we generate + * 1000 random numbers and test whether all the values are in the + * specified range. For the small ranges, we also test whether each + * value occurred at least once as the chances of this not happening is + * astronomically small */ + + /* Test integer generation in a small 0-based range */ + generate_random_vector(&rng, &numbers, 0, 5); + check_occurrences(&numbers, 0, 5); + + /* Test integer generation in a small non-0-based range */ + generate_random_vector(&rng, &numbers, 8, 13); + check_occurrences(&numbers, 8, 13); + + /* Test integer generation in a larger non-0-based range */ + generate_random_vector(&rng, &numbers, -2048, 2047); + check_consecutive_bits(&numbers, 12); + + /* Test integer generation in [0; IGRAPH_INTEGER_MAX] */ + generate_random_vector(&rng, &numbers, 0, IGRAPH_INTEGER_MAX); + check_consecutive_bits(&numbers, sizeof(igraph_int_t) * 8 - 1); + + /* Test integer generation in [-5; IGRAPH_INTEGER_MAX-5] */ + generate_random_vector(&rng, &numbers, -5, IGRAPH_INTEGER_MAX-5); + + /* Test integer generation in [-5; IGRAPH_INTEGER_MAX] */ + generate_random_vector(&rng, &numbers, -5, IGRAPH_INTEGER_MAX); + + /* Test integer generation in [IGRAPH_INTEGER_MIN; -5] */ + generate_random_vector(&rng, &numbers, IGRAPH_INTEGER_MIN, -5); + + /* Test integer generation in [IGRAPH_INTEGER_MIN; IGRAPH_INTEGER_MAX] */ + generate_random_vector(&rng, &numbers, IGRAPH_INTEGER_MIN, IGRAPH_INTEGER_MAX); + check_consecutive_bits(&numbers, sizeof(igraph_int_t) * 8); + + igraph_rng_destroy(&rng); + } + + igraph_vector_int_destroy(&numbers); +} + +int main(void) { + simple_tests(); + stress_tests(); + + return 0; +} diff --git a/tests/unit/igraph_rng_get_integer.out b/tests/unit/igraph_rng_get_integer.out new file mode 100644 index 0000000..1568516 --- /dev/null +++ b/tests/unit/igraph_rng_get_integer.out @@ -0,0 +1,33 @@ +22 +59 +86 +99 +99 +77 +93 +12 +20 +62 +======== +22 +59 +86 +99 +99 +77 +93 +12 +20 +62 +======== +33 +48 +87 +63 +80 +14 +15 +95 +79 +43 +======== diff --git a/tests/unit/igraph_rng_sample_dirichlet.c b/tests/unit/igraph_rng_sample_dirichlet.c new file mode 100644 index 0000000..8799b0f --- /dev/null +++ b/tests/unit/igraph_rng_sample_dirichlet.c @@ -0,0 +1,83 @@ +/* igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check_result(igraph_matrix_t *res, igraph_int_t n) +{ + igraph_vector_t colsum; + igraph_vector_init(&colsum, 0); + + IGRAPH_ASSERT(igraph_matrix_min(res) >= 0); + IGRAPH_ASSERT(igraph_matrix_max(res) <= 1.0); + igraph_matrix_colsum(res, &colsum); + IGRAPH_ASSERT(igraph_vector_size(&colsum) == n); + for (igraph_int_t i = 0; i < igraph_vector_size(&colsum); i++) { + IGRAPH_ASSERT(igraph_almost_equals(VECTOR(colsum)[i], 1, 0.0000001)); + } + igraph_vector_destroy(&colsum); +} + + +int main(void) { + igraph_vector_t alpha; + igraph_matrix_t res; + + igraph_rng_seed(igraph_rng_default(), 42); + igraph_matrix_init(&res, 0, 0); + + printf("Zero vectors to sample should return empty matrix:\n"); + igraph_vector_init_int(&alpha, 2, 1, 1); + igraph_rng_sample_dirichlet(igraph_rng_default(), 0, &alpha, &res); + igraph_matrix_print(&res); + igraph_vector_destroy(&alpha); + + printf("Check if result vectors add up to one.\n"); + igraph_vector_init_int(&alpha, 5, 1, 2, 3, 4, 5); + igraph_rng_sample_dirichlet(igraph_rng_default(), 100, &alpha, &res); + check_result(&res, 100); + igraph_vector_destroy(&alpha); + + printf("Distribution localized at 0.5, 0.5:\n"); + igraph_vector_init_real(&alpha, 2, 1e30, 1e30); + igraph_rng_sample_dirichlet(igraph_rng_default(), 2, &alpha, &res); + igraph_matrix_print(&res); + igraph_vector_destroy(&alpha); + + VERIFY_FINALLY_STACK(); + + printf("Check if too short parameter vector is handled correctly.\n"); + igraph_vector_init(&alpha, 0); + CHECK_ERROR(igraph_rng_sample_dirichlet(igraph_rng_default(), 0, &alpha, &res), IGRAPH_EINVAL); + igraph_vector_destroy(&alpha); + + printf("Check if negative number of samples is handled correctly.\n"); + igraph_vector_init_int(&alpha, 2, 1, 1); + CHECK_ERROR(igraph_rng_sample_dirichlet(igraph_rng_default(), -1, &alpha, &res), IGRAPH_EINVAL); + igraph_vector_destroy(&alpha); + + printf("Check if negative alpha is handled correctly.\n"); + igraph_vector_init_int(&alpha, 2, -1, 1); + CHECK_ERROR(igraph_rng_sample_dirichlet(igraph_rng_default(), 0, &alpha, &res), IGRAPH_EINVAL); + igraph_vector_destroy(&alpha); + + igraph_matrix_destroy(&res); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_rng_sample_dirichlet.out b/tests/unit/igraph_rng_sample_dirichlet.out new file mode 100644 index 0000000..7a22df1 --- /dev/null +++ b/tests/unit/igraph_rng_sample_dirichlet.out @@ -0,0 +1,10 @@ +Zero vectors to sample should return empty matrix: + + +Check if result vectors add up to one. +Distribution localized at 0.5, 0.5: +0.5 0.5 +0.5 0.5 +Check if too short parameter vector is handled correctly. +Check if negative number of samples is handled correctly. +Check if negative alpha is handled correctly. diff --git a/tests/unit/igraph_rng_sample_sphere.c b/tests/unit/igraph_rng_sample_sphere.c new file mode 100644 index 0000000..b4186ea --- /dev/null +++ b/tests/unit/igraph_rng_sample_sphere.c @@ -0,0 +1,66 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void check(igraph_bool_t volume, igraph_int_t dim, igraph_int_t n, igraph_real_t radius, igraph_bool_t positive) { + igraph_matrix_t samples; + + igraph_matrix_init(&samples, 0, 0); + if (volume) { + igraph_rng_sample_sphere_volume(igraph_rng_default(), dim, n, radius, positive, &samples); + } else { + igraph_rng_sample_sphere_surface(igraph_rng_default(), dim, n, radius, positive, &samples); + } + IGRAPH_ASSERT(igraph_matrix_ncol(&samples) == n); + IGRAPH_ASSERT(igraph_matrix_nrow(&samples) == dim); + for (igraph_int_t col = 0; col < n; col++) { + igraph_real_t sum = 0; + for (igraph_int_t row = 0; row < dim; row++) { + if (positive) { + IGRAPH_ASSERT(MATRIX(samples, row, col) >= 0); + } + sum += MATRIX(samples, row, col) * MATRIX(samples, row, col); + } + if (volume) { + IGRAPH_ASSERT(sum <= radius * radius); + } else { + IGRAPH_ASSERT(igraph_almost_equals(sum, radius * radius, 0.00001)); + } + } + igraph_matrix_destroy(&samples); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 42); + + //No samples + check(0, 2, 0, 1, 0); + check(1, 2, 0, 1, 0); + //Five samples, four-dimensions, radius 2 + check(0, 4, 5, 2, 0); + check(1, 4, 5, 2, 0); + //Same, positive orthant + check(0, 4, 5, 2, 1); + check(1, 4, 5, 2, 1); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_rooted_product.c b/tests/unit/igraph_rooted_product.c new file mode 100644 index 0000000..943ed5e --- /dev/null +++ b/tests/unit/igraph_rooted_product.c @@ -0,0 +1,115 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +// P2 X P4 with root 0 is P8 +void test_p2_p4(void) { + igraph_t p2, p4, p8, product; + igraph_bool_t is_iso; + + igraph_ring(&p2, 2, IGRAPH_UNDIRECTED, false, false); + igraph_ring(&p4, 4, IGRAPH_UNDIRECTED, false, false); + igraph_ring(&p8, 8, IGRAPH_UNDIRECTED, false, false); + + igraph_rooted_product(&product, &p2, &p4, 0); + + igraph_isomorphic(&product, &p8, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&product); + igraph_destroy(&p2); + igraph_destroy(&p4); + igraph_destroy(&p8); +} + +void test_multigraph(void) { + igraph_t g1, g2, exp_prod, product; + igraph_bool_t is_iso; + + igraph_small(&g1, 2, IGRAPH_UNDIRECTED, 0, 1, 0, 1, -1); + igraph_small(&g2, 2, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 0, 1, -1); + igraph_small(&exp_prod, 4, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 0,1, + 2, 3, 2, 3, 2,3, + 0,2, 0, 2, -1, IGRAPH_UNDIRECTED); + + igraph_rooted_product(&product, &g1, &g2, 0); + igraph_isomorphic(&product, &exp_prod, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&product); + igraph_destroy(&g1); + igraph_destroy(&g2); + igraph_destroy(&exp_prod); +} + +void test_directed(void) { + igraph_t p3, c3, exp_prod, product; + igraph_bool_t is_iso; + + igraph_ring(&p3, 3, IGRAPH_DIRECTED, false, false); + igraph_ring(&c3, 3, IGRAPH_DIRECTED, false, true); + + // calculated by hand + igraph_small(&exp_prod, 9, IGRAPH_DIRECTED, 0,1, 1,2, + 3,0, 0,4, 4,3, + 5,1, 1,6, 6,5, + 7,2, 2,8, 8,7, -1); + + igraph_rooted_product(&product, &p3, &c3, 0); + igraph_isomorphic(&exp_prod, &product, &is_iso); + IGRAPH_ASSERT(is_iso); + + igraph_destroy(&product); + igraph_destroy(&p3); + igraph_destroy(&c3); + igraph_destroy(&exp_prod); +} + +void test_null_and_singleton(void) { + igraph_t null_g, singleton_g, product; + + igraph_empty(&null_g, 0, IGRAPH_UNDIRECTED); + igraph_empty(&singleton_g, 1, IGRAPH_UNDIRECTED); + + igraph_rooted_product(&product, &null_g, &singleton_g, 0); + IGRAPH_ASSERT(igraph_vcount(&product) == 0); + igraph_destroy(&product); + + igraph_rooted_product(&product, &singleton_g, &singleton_g, 0); + IGRAPH_ASSERT(igraph_vcount(&product) == 1); + IGRAPH_ASSERT(igraph_ecount(&product) == 0); + igraph_destroy(&product); + + CHECK_ERROR(igraph_rooted_product(&product, &singleton_g, &null_g, 0), IGRAPH_EINVVID); + + igraph_destroy(&singleton_g); + igraph_destroy(&null_g); +} + +int main(void) { + test_p2_p4(); + test_multigraph(); + test_directed(); + test_null_and_singleton(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_running_mean.c b/tests/unit/igraph_running_mean.c new file mode 100644 index 0000000..878c98c --- /dev/null +++ b/tests/unit/igraph_running_mean.c @@ -0,0 +1,60 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_t data, result; + + igraph_set_error_handler(igraph_error_handler_ignore); + igraph_vector_init_int(&result, 0); + + printf("No values, binwidth 0 should fail.\n"); + igraph_vector_init_int(&data, 0); + IGRAPH_ASSERT(igraph_running_mean(&data, &result, /*binwidth*/ 0) == IGRAPH_EINVAL); + igraph_vector_destroy(&data); + + printf("No values, binwidth 1 should fail.\n"); + igraph_vector_init_int(&data, 0); + IGRAPH_ASSERT(igraph_running_mean(&data, &result, /*binwidth*/ 1) == IGRAPH_EINVAL); + igraph_vector_destroy(&data); + + printf("One value, binwidth 1:\n"); + igraph_vector_init_int(&data, 1, 1); + IGRAPH_ASSERT(igraph_running_mean(&data, &result, /*binwidth*/ 1) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&data); + + printf("1, 2, 3, 4, 5, binwidth 1:\n"); + igraph_vector_init_int(&data, 5, 1, 2, 3, 4, 5); + IGRAPH_ASSERT(igraph_running_mean(&data, &result, /*binwidth*/ 1) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&data); + + printf("1, 2, 3, 4, 5, binwidth 2:\n"); + igraph_vector_init_int(&data, 5, 1, 2, 3, 4, 5); + IGRAPH_ASSERT(igraph_running_mean(&data, &result, /*binwidth*/ 2) == IGRAPH_SUCCESS); + print_vector(&result); + igraph_vector_destroy(&data); + + igraph_vector_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_running_mean.out b/tests/unit/igraph_running_mean.out new file mode 100644 index 0000000..8ce5528 --- /dev/null +++ b/tests/unit/igraph_running_mean.out @@ -0,0 +1,8 @@ +No values, binwidth 0 should fail. +No values, binwidth 1 should fail. +One value, binwidth 1: +( 1 ) +1, 2, 3, 4, 5, binwidth 1: +( 1 2 3 4 5 ) +1, 2, 3, 4, 5, binwidth 2: +( 1.5 2.5 3.5 4.5 ) diff --git a/tests/unit/igraph_sbm_game.c b/tests/unit/igraph_sbm_game.c new file mode 100644 index 0000000..f5fb6b6 --- /dev/null +++ b/tests/unit/igraph_sbm_game.c @@ -0,0 +1,169 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print( + igraph_matrix_t *pref_matrix, + igraph_vector_int_t *block_sizes, + igraph_bool_t directed, + igraph_bool_t loops, igraph_bool_t multiple) { + + igraph_t result; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; + if (loops) allowed_edge_types |= IGRAPH_LOOPS_SW; + if (multiple) allowed_edge_types |= IGRAPH_MULTI_SW; + + IGRAPH_ASSERT(igraph_sbm_game(&result, pref_matrix, block_sizes, directed, allowed_edge_types) == IGRAPH_SUCCESS); + print_graph_canon(&result); + printf("\n"); + + if (!loops) { + igraph_bool_t has_loops; + igraph_has_loop(&result, &has_loops); + IGRAPH_ASSERT(! has_loops); + } + + if (!multiple) { + igraph_bool_t has_multi; + igraph_has_multiple(&result, &has_multi); + IGRAPH_ASSERT(! has_multi); + } + + igraph_destroy(&result); +} + + +int main(void) { + igraph_t result; + igraph_matrix_t pref_matrix_0, pref_matrix_1, pref_matrix_2, pref_matrix_3, pref_matrix_3u, pref_matrix_nonsq, pref_matrix_oor, pref_matrix_nsym; + igraph_vector_int_t block_sizes_0, block_sizes_1, block_sizes_2, block_sizes_3, block_sizes_neg; + + igraph_matrix_init(&pref_matrix_0, 0, 0); + + igraph_matrix_init(&pref_matrix_1, 1, 1); + MATRIX(pref_matrix_1, 0, 0) = 1; + + igraph_matrix_init(&pref_matrix_2, 2, 2); + + igraph_matrix_init(&pref_matrix_3, 3, 3); + igraph_matrix_null(&pref_matrix_3); + MATRIX(pref_matrix_3, 0, 1) = 1; + MATRIX(pref_matrix_3, 2, 2) = 1; + + igraph_matrix_init(&pref_matrix_3u, 3, 3); + igraph_matrix_null(&pref_matrix_3u); + MATRIX(pref_matrix_3u, 0, 1) = 1; + MATRIX(pref_matrix_3u, 1, 0) = 1; + MATRIX(pref_matrix_3u, 2, 2) = 1; + + igraph_matrix_init(&pref_matrix_nonsq, 3, 2); + + igraph_matrix_init(&pref_matrix_oor, 3, 3); + igraph_matrix_null(&pref_matrix_oor); + MATRIX(pref_matrix_oor, 0, 1) = 10; + + igraph_matrix_init(&pref_matrix_nsym, 3, 3); + igraph_matrix_null(&pref_matrix_nsym); + MATRIX(pref_matrix_nsym, 0, 1) = 1; + + igraph_vector_int_init_int(&block_sizes_0, 0); + igraph_vector_int_init_int(&block_sizes_1, 1, 1); + igraph_vector_int_init_int(&block_sizes_2, 2, 1, 1); + igraph_vector_int_init_int(&block_sizes_3, 3, 2, 2, 2); + igraph_vector_int_init_int(&block_sizes_neg, 3, 2, 2, -2); + + /* Tests without multi-edges */ + + printf("No vertices.\n"); + call_and_print(&pref_matrix_0, &block_sizes_0, false, false, false); + + printf("One vertex, directed, with loops.\n"); + call_and_print(&pref_matrix_1, &block_sizes_1, true, true, false); + + printf("Six vertices, directed, only edges from block 0 to 1 and 2 to 2.\n"); + call_and_print(&pref_matrix_3, &block_sizes_3, true, true, false); + + printf("Six vertices, directed, only edges from block 0 to 1 and 2 to 2, no loops.\n"); + call_and_print(&pref_matrix_3, &block_sizes_3, true, false, false); + + printf("Six vertices, undirected, only edges between block 0 and 1, and inside block 2.\n"); + call_and_print(&pref_matrix_3u, &block_sizes_3, false, true, false); + + printf("Six vertices, undirected, only edges between block 0 and 1, and inside block 2, no loops.\n"); + call_and_print(&pref_matrix_3u, &block_sizes_3, false, false, false); + + /* Smoke test for multi-edges */ + + { + const int trials = 100; + igraph_real_t mean_ecount = 0; + + igraph_matrix_fill(&pref_matrix_2, 100); + + for (int i=0; i < trials; i++) { + igraph_sbm_game(&result, &pref_matrix_2, &block_sizes_2, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW); + mean_ecount += igraph_ecount(&result); + igraph_destroy(&result); + } + mean_ecount /= trials; + + /* Since we disallowed loops, edges are present only between the two + * distinct vertices of this graph. For 100 trials and a rate of p=100, + * the probability that the edge count is below 80 is ~1.8% + * and that it's above 120 is ~2.8%. */ + IGRAPH_ASSERT(80 < mean_ecount && mean_ecount < 120); + } + + VERIFY_FINALLY_STACK(); + + /* Verify validation */ + + printf("Check for nonsquare matrix error handling.\n"); + CHECK_ERROR(igraph_sbm_game(&result, &pref_matrix_nonsq, &block_sizes_3, false, IGRAPH_SIMPLE_SW), IGRAPH_EINVAL); + + printf("Check for preference matrix probability out of range error handling.\n"); + CHECK_ERROR(igraph_sbm_game(&result, &pref_matrix_oor, &block_sizes_3, false, IGRAPH_SIMPLE_SW), IGRAPH_EINVAL); + + printf("Check for nonsymmetric preference matrix for undirected graph error handling.\n"); + CHECK_ERROR(igraph_sbm_game(&result, &pref_matrix_nsym, &block_sizes_3, false, IGRAPH_SIMPLE_SW), IGRAPH_EINVAL); + + printf("Check for incorrect block size vector error handling.\n"); + CHECK_ERROR(igraph_sbm_game(&result, &pref_matrix_3, &block_sizes_1, true, IGRAPH_SIMPLE_SW), IGRAPH_EINVAL); + + printf("Check for negative block size error handling.\n"); + CHECK_ERROR(igraph_sbm_game(&result, &pref_matrix_3, &block_sizes_neg, true, IGRAPH_SIMPLE_SW), IGRAPH_EINVAL); + + igraph_matrix_destroy(&pref_matrix_0); + igraph_matrix_destroy(&pref_matrix_1); + igraph_matrix_destroy(&pref_matrix_2); + igraph_matrix_destroy(&pref_matrix_3); + igraph_matrix_destroy(&pref_matrix_3u); + igraph_matrix_destroy(&pref_matrix_oor); + igraph_matrix_destroy(&pref_matrix_nsym); + igraph_matrix_destroy(&pref_matrix_nonsq); + igraph_vector_int_destroy(&block_sizes_0); + igraph_vector_int_destroy(&block_sizes_1); + igraph_vector_int_destroy(&block_sizes_2); + igraph_vector_int_destroy(&block_sizes_3); + igraph_vector_int_destroy(&block_sizes_neg); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sbm_game.out b/tests/unit/igraph_sbm_game.out new file mode 100644 index 0000000..66feedc --- /dev/null +++ b/tests/unit/igraph_sbm_game.out @@ -0,0 +1,68 @@ +No vertices. +directed: false +vcount: 0 +edges: { +} + +One vertex, directed, with loops. +directed: true +vcount: 1 +edges: { +0 0 +} + +Six vertices, directed, only edges from block 0 to 1 and 2 to 2. +directed: true +vcount: 6 +edges: { +0 2 +0 3 +1 2 +1 3 +4 4 +4 5 +5 4 +5 5 +} + +Six vertices, directed, only edges from block 0 to 1 and 2 to 2, no loops. +directed: true +vcount: 6 +edges: { +0 2 +0 3 +1 2 +1 3 +4 5 +5 4 +} + +Six vertices, undirected, only edges between block 0 and 1, and inside block 2. +directed: false +vcount: 6 +edges: { +0 2 +0 3 +1 2 +1 3 +4 4 +4 5 +5 5 +} + +Six vertices, undirected, only edges between block 0 and 1, and inside block 2, no loops. +directed: false +vcount: 6 +edges: { +0 2 +0 3 +1 2 +1 3 +4 5 +} + +Check for nonsquare matrix error handling. +Check for preference matrix probability out of range error handling. +Check for nonsymmetric preference matrix for undirected graph error handling. +Check for incorrect block size vector error handling. +Check for negative block size error handling. diff --git a/tests/unit/igraph_set_progress_handler.c b/tests/unit/igraph_set_progress_handler.c new file mode 100644 index 0000000..724cc49 --- /dev/null +++ b/tests/unit/igraph_set_progress_handler.c @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +igraph_error_t handler(const char* message, igraph_real_t percent, void*data) { + printf("handler, %s, %f, %d\n", message, percent, *(int*)data); + return IGRAPH_SUCCESS; +} + +int main(void) { + igraph_set_progress_handler(handler); + int data = 10; + + printf("progress with set progress handler:\n"); + IGRAPH_PROGRESS("message", 100.0, &data); + + igraph_progress_handler_t *previous = igraph_set_progress_handler(NULL); + + printf("\nprogress with no handler:\n"); + IGRAPH_PROGRESS("message", 100.0, &data); + + igraph_set_progress_handler(previous); + + printf("\nprogress with previous handler:\n"); + IGRAPH_PROGRESS("message", 100.0, &data); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_set_progress_handler.out b/tests/unit/igraph_set_progress_handler.out new file mode 100644 index 0000000..fa289a4 --- /dev/null +++ b/tests/unit/igraph_set_progress_handler.out @@ -0,0 +1,7 @@ +progress with set progress handler: +handler, message, 100.000000, 10 + +progress with no handler: + +progress with previous handler: +handler, message, 100.000000, 10 diff --git a/tests/unit/igraph_similarity.c b/tests/unit/igraph_similarity.c new file mode 100644 index 0000000..f23a46c --- /dev/null +++ b/tests/unit/igraph_similarity.c @@ -0,0 +1,208 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int check_jaccard_all(const igraph_t* g, igraph_matrix_t* m, + igraph_neimode_t mode, igraph_bool_t loops) { + igraph_vector_int_t pairs; + igraph_vector_t res; + igraph_int_t i, j, k, n; + igraph_eit_t eit; + + igraph_vector_init(&res, 0); + + /* First, query the similarities for all the vertices to a matrix */ + igraph_similarity_jaccard(g, m, igraph_vss_all(), igraph_vss_all(), mode, loops); + + /* Second, query the similarities for all pairs using a pair vector */ + n = igraph_vcount(g); + igraph_vector_int_init(&pairs, 0); + for (i = 0; i < n; i++) { + for (j = n - 1; j >= 0; j--) { + igraph_vector_int_push_back(&pairs, i); + igraph_vector_int_push_back(&pairs, j); + } + } + igraph_similarity_jaccard_pairs(g, &res, &pairs, mode, loops); + for (i = 0, k = 0; i < n; i++) { + for (j = n - 1; j >= 0; j--, k++) { + if (fabs(VECTOR(res)[k] - MATRIX(*m, i, j)) > 1e-6) { + fprintf(stderr, "Jaccard similarity calculation for vertex pair %" IGRAPH_PRId "-%" IGRAPH_PRId " " + "does not match the value in the full matrix (%.6f vs %.6f)\n", + i, j, VECTOR(res)[k], MATRIX(*m, i, j)); + return 1; + } + } + } + igraph_vector_int_destroy(&pairs); + + /* Third, query the similarities for all edges */ + igraph_similarity_jaccard_es(g, &res, igraph_ess_all(IGRAPH_EDGEORDER_FROM), mode, loops); + igraph_eit_create(g, igraph_ess_all(IGRAPH_EDGEORDER_FROM), &eit); + k = 0; + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + i = IGRAPH_FROM(g, eid); + j = IGRAPH_TO(g, eid); + if (fabs(VECTOR(res)[k] - MATRIX(*m, i, j)) > 1e-6) { + fprintf(stderr, "Jaccard similarity calculation for edge %" IGRAPH_PRId "-%" IGRAPH_PRId " (ID=%" IGRAPH_PRId ") " + "does not match the value in the full matrix (%.6f vs %.6f)\n", + i, j, eid, VECTOR(res)[k], MATRIX(*m, i, j)); + return 1; + } + IGRAPH_EIT_NEXT(eit); + k++; + } + + igraph_eit_destroy(&eit); + + igraph_vector_destroy(&res); + + return 0; +} + +int check_dice_all(const igraph_t* g, igraph_matrix_t* m, + igraph_neimode_t mode, igraph_bool_t loops) { + igraph_vector_int_t pairs; + igraph_vector_t res; + igraph_int_t i, j, k, n; + igraph_eit_t eit; + + igraph_vector_init(&res, 0); + + /* First, query the similarities for all the vertices to a matrix */ + igraph_similarity_dice(g, m, igraph_vss_all(), igraph_vss_all(), mode, loops); + + /* Second, query the similarities for all pairs using a pair vector */ + n = igraph_vcount(g); + igraph_vector_int_init(&pairs, 0); + for (i = 0; i < n; i++) { + for (j = n - 1; j >= 0; j--) { + igraph_vector_int_push_back(&pairs, i); + igraph_vector_int_push_back(&pairs, j); + } + } + igraph_similarity_dice_pairs(g, &res, &pairs, mode, loops); + for (i = 0, k = 0; i < n; i++) { + for (j = n - 1; j >= 0; j--, k++) { + if (fabs(VECTOR(res)[k] - MATRIX(*m, i, j)) > 1e-6) { + fprintf(stderr, "Dice similarity calculation for vertex pair %" IGRAPH_PRId "-%" IGRAPH_PRId " " + "does not match the value in the full matrix (%.6f vs %.6f)\n", + i, j, VECTOR(res)[k], MATRIX(*m, i, j)); + return 1; + } + } + } + igraph_vector_int_destroy(&pairs); + + /* Third, query the similarities for all edges */ + igraph_similarity_dice_es(g, &res, igraph_ess_all(IGRAPH_EDGEORDER_FROM), mode, loops); + igraph_eit_create(g, igraph_ess_all(IGRAPH_EDGEORDER_FROM), &eit); + k = 0; + while (!IGRAPH_EIT_END(eit)) { + igraph_int_t eid = IGRAPH_EIT_GET(eit); + i = IGRAPH_FROM(g, eid); + j = IGRAPH_TO(g, eid); + if (fabs(VECTOR(res)[k] - MATRIX(*m, i, j)) > 1e-6) { + fprintf(stderr, "Dice similarity calculation for edge %" IGRAPH_PRId "-%" IGRAPH_PRId " (ID=%" IGRAPH_PRId ") " + "does not match the value in the full matrix (%.6f vs %.6f)\n", + i, j, eid, VECTOR(res)[k], MATRIX(*m, i, j)); + return 1; + } + IGRAPH_EIT_NEXT(eit); + k++; + } + + igraph_eit_destroy(&eit); + + igraph_vector_destroy(&res); + + return 0; +} + +int main(void) { + + igraph_t g; + igraph_matrix_t m; + int ret; + + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0, 1, 2, 1, 2, 0, 3, 0, + -1); + + igraph_matrix_init(&m, 0, 0); + + ret = check_jaccard_all(&g, &m, IGRAPH_ALL, 1); + print_matrix(&m); + if (ret) { + return 1; + } + + igraph_similarity_jaccard(&g, &m, igraph_vss_range(1, 3), igraph_vss_range(1, 3), IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_matrix(&m); + + igraph_similarity_jaccard(&g, &m, igraph_vss_range(1, 3), igraph_vss_1(1), IGRAPH_ALL, IGRAPH_NO_LOOPS); + print_matrix(&m); + + ret = check_jaccard_all(&g, &m, IGRAPH_OUT, 1); + print_matrix(&m); + if (ret) { + return 3; + } + + ret = check_jaccard_all(&g, &m, IGRAPH_IN, 0); + print_matrix(&m); + if (ret) { + return 4; + } + + ret = check_dice_all(&g, &m, IGRAPH_ALL, 1); + print_matrix(&m); + if (ret) { + return 5; + } + + ret = check_dice_all(&g, &m, IGRAPH_OUT, 1); + print_matrix(&m); + if (ret) { + return 6; + } + + ret = check_dice_all(&g, &m, IGRAPH_IN, 0); + print_matrix(&m); + if (ret) { + return 7; + } + + igraph_similarity_inverse_log_weighted(&g, &m, igraph_vss_all(), IGRAPH_ALL); + print_matrix(&m); + + igraph_similarity_inverse_log_weighted(&g, &m, igraph_vss_all(), IGRAPH_OUT); + print_matrix(&m); + + igraph_similarity_inverse_log_weighted(&g, &m, igraph_vss_all(), IGRAPH_IN); + print_matrix(&m); + + igraph_matrix_destroy(&m); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_similarity.out b/tests/unit/igraph_similarity.out new file mode 100644 index 0000000..0636746 --- /dev/null +++ b/tests/unit/igraph_similarity.out @@ -0,0 +1,40 @@ +[ 1 0.75 0.75 0.5 + 0.75 1 1 0.25 + 0.75 1 1 0.25 + 0.5 0.25 0.25 1 ] +[ 1 0.333333 + 0.333333 1 ] +[ 1 + 0.333333 ] +[ 1 0.5 0.666667 0.333333 + 0.5 1 0.333333 0 + 0.666667 0.333333 1 0.25 + 0.333333 0 0.25 1 ] +[ 1 0.333333 0 0 + 0.333333 1 0 0 + 0 0 1 0 + 0 0 0 1 ] +[ 1 0.857143 0.857143 0.666667 + 0.857143 1 1 0.4 + 0.857143 1 1 0.4 + 0.666667 0.4 0.4 1 ] +[ 1 0.666667 0.8 0.5 + 0.666667 1 0.5 0 + 0.8 0.5 1 0.4 + 0.5 0 0.4 1 ] +[ 1 0.5 0 0 + 0.5 1 0 0 + 0 0 1 0 + 0 0 0 1 ] +[ 0 1.4427 1.4427 0 + 1.4427 0 0.910239 0.910239 + 1.4427 0.910239 0 0.910239 + 0 0.910239 0.910239 0 ] +[ 0 0 1.4427 0 + 0 0 0 0 + 1.4427 0 0 1.4427 + 0 0 1.4427 0 ] +[ 0 1.4427 0 0 + 1.4427 0 0 0 + 0 0 0 0 + 0 0 0 0 ] diff --git a/tests/unit/igraph_simple_cycles.c b/tests/unit/igraph_simple_cycles.c new file mode 100644 index 0000000..1567cc2 --- /dev/null +++ b/tests/unit/igraph_simple_cycles.c @@ -0,0 +1,472 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void check_cycles_max(const igraph_t *graph, igraph_neimode_t mode, igraph_int_t expected, igraph_int_t max_cycle_length) { + igraph_vector_int_list_t results_v; + igraph_vector_int_list_t results_e; + + igraph_vector_int_list_init(&results_v, 0); + igraph_vector_int_list_init(&results_e, 0); + + igraph_simple_cycles(graph, &results_v, &results_e, mode, 0, max_cycle_length, IGRAPH_UNLIMITED); + + printf("Finished search, found %" IGRAPH_PRId + " cycles, expected %" IGRAPH_PRId " cycles." + " Max cycle length was %" IGRAPH_PRId " vertices.\n\n", + igraph_vector_int_list_size(&results_v), expected, max_cycle_length); + + if (igraph_vcount(graph) < 100) { + printf("Vertex IDs in cycles:\n"); + print_vector_int_list(&results_v); + + printf("Edge IDs in cycles:\n"); + print_vector_int_list(&results_e); + } + + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_v) == expected); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_e) == expected); + + for (igraph_int_t i = 0; i < expected; i++) { + igraph_vector_int_t *vertices, *edges; + + vertices = igraph_vector_int_list_get_ptr(&results_v, i); + edges = igraph_vector_int_list_get_ptr(&results_e, i); + IGRAPH_ASSERT(igraph_vector_int_size(vertices) == igraph_vector_int_size(edges)); + if (max_cycle_length >= 0) { + IGRAPH_ASSERT(igraph_vector_int_size(vertices) <= max_cycle_length); + } + } + + igraph_vector_int_list_destroy(&results_v); + igraph_vector_int_list_destroy(&results_e); +} + +void check_cycles(const igraph_t *graph, igraph_neimode_t mode, igraph_int_t expected) { + check_cycles_max(graph, mode, expected, -1); +} + +int main(void) { + igraph_t g; + igraph_t g_ring_undirected, g_star_undirected; + + printf("Testing null graph\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + check_cycles(&g, IGRAPH_OUT, 0); + igraph_destroy(&g); + + printf("\nTesting edgeless graph\n"); + igraph_empty(&g, 5, IGRAPH_UNDIRECTED); + check_cycles(&g, IGRAPH_OUT, 0); + igraph_destroy(&g); + + printf("\nTesting directed cycle graph\n"); + igraph_ring(&g, 10, IGRAPH_DIRECTED, /*mutual=*/ false, /*circular=*/ true); + check_cycles(&g, IGRAPH_OUT, 1); + igraph_destroy(&g); + + // printf("\nTesting large directed cycle graph\n"); + // igraph_ring(&g, 10000, IGRAPH_DIRECTED, /*mutual=*/ false, /*circular=*/ true); + // check_cycles(&g, 1); + // igraph_destroy(&g); + + printf("\nTesting directed star\n"); + igraph_star(&g, 7, IGRAPH_STAR_OUT, 1); + check_cycles(&g, IGRAPH_OUT, 0); + igraph_destroy(&g); + + printf("\nTesting directed wheel\n"); + igraph_wheel(&g, 8, IGRAPH_WHEEL_OUT, 0); + check_cycles(&g, IGRAPH_OUT, 1); + check_cycles(&g, IGRAPH_IN, 1); + check_cycles(&g, IGRAPH_ALL, 43); + igraph_destroy(&g); + + printf("\nTesting undirected ring\n"); + igraph_ring(&g_ring_undirected, 10, IGRAPH_UNDIRECTED, /*mutual=*/ false, /*circular=*/ true); + check_cycles(&g_ring_undirected, IGRAPH_OUT, 1); + + igraph_star(&g_star_undirected, 7, IGRAPH_STAR_UNDIRECTED, 1); + printf("\nTesting undirected star\n"); + check_cycles(&g_star_undirected, IGRAPH_OUT, 0); + + igraph_disjoint_union(&g, &g_ring_undirected, &g_star_undirected); + printf("\nTesting union of undirected wheel and star\n"); + check_cycles(&g, IGRAPH_OUT, 1); + + printf("\nTesting union of undirected wheel, star and a single edge\n"); + igraph_add_edge(&g, 7, 13); // add a random edge between the two structures to make them connected + check_cycles(&g, IGRAPH_OUT, 1); + igraph_destroy(&g); + igraph_destroy(&g_star_undirected); + igraph_destroy(&g_ring_undirected); + + printf("\nTesting a tree\n"); + igraph_kary_tree(&g, 20, 3, IGRAPH_TREE_OUT); + check_cycles(&g, IGRAPH_OUT, 0); + check_cycles(&g, IGRAPH_ALL, 0); + igraph_destroy(&g); + + printf("\nTesting a complete DAG\n"); + igraph_full_citation(&g, 5, IGRAPH_DIRECTED); + check_cycles(&g, IGRAPH_OUT, 0); + check_cycles(&g, IGRAPH_ALL, 37); + igraph_destroy(&g); + + igraph_t g_wheel_undirected_2; + igraph_wheel(&g_wheel_undirected_2, 10, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("\nCreated undirected wheel\n"); + // call cycles finder, expect 73 cycles to be found ( + // 9 cycle of 10 nodes, + // 10 cycles of 9 nodes, + // 9 cycles of 8 nodes, + // 9 cycles of 7 nodes, + // 9 cycles of 6 nodes, + // 9 cycles of 5 nodes, + // 9 cycles of 4 nodes + // 9 cycles of 3 nodes, + // ) + check_cycles(&g_wheel_undirected_2, IGRAPH_OUT, 73); + // test the max_cycle_length parameter + check_cycles_max(&g_wheel_undirected_2, IGRAPH_OUT, 9, 3); + check_cycles_max(&g_wheel_undirected_2, IGRAPH_OUT, 18, 4); + // clean up + igraph_destroy(&g_wheel_undirected_2); + + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-1326064152 + /* + * This graph looks like: + * + * 1--2\ + * | | | + * 4--3/ + * + */ + printf("\nTesting directed graph with a cycle of length 4 and a multi-edge\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, + 1, 2, + 2, 3, + 2, 3, + 3, 4, + 4, 1, + -1); + check_cycles(&g, IGRAPH_OUT, 2); + check_cycles_max(&g, IGRAPH_OUT, 2, 4); + igraph_destroy(&g); + + // same, but undirected + printf("\nTesting undirected graph with a cycle of length 4 and a multi-edge\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 1, 2, + 2, 3, + 2, 3, + 3, 4, + 4, 1, + -1); + check_cycles(&g, IGRAPH_OUT, 3); + check_cycles_max(&g, IGRAPH_OUT, 3, 4); + igraph_destroy(&g); + + // check that self-loops are handled + printf("\nTesting directed graph with single self-loop\n"); + igraph_small(&g, 1, IGRAPH_DIRECTED, 0, 0, -1); + check_cycles(&g, IGRAPH_OUT, 1); + check_cycles(&g, IGRAPH_ALL, 1); + igraph_destroy(&g); + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-2243053770 + printf("\nTesting undirected graph with single length-2-loop\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, 0, 1, -1); + check_cycles(&g, IGRAPH_OUT, 1); + igraph_destroy(&g); + + printf("\nTesting undirected graph with 3 length-2-loops\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 0, 1, -1); + check_cycles(&g, IGRAPH_OUT, 3); + igraph_destroy(&g); + + // and in https://github.com/igraph/igraph/pull/2181#issuecomment-2243060608 + printf("\nTesting undirected graph with single length-2-loop and a self-loop\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 0, 0, -1); + check_cycles(&g, IGRAPH_OUT, 2); + igraph_destroy(&g); + + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-2243942240 + printf("\nTesting undirected graph of type 'envelope'\n"); + // expect: + // 4 of length 3 vertices + // 4 of length 5 vertices + // 5 of length 4 vertices + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 1, + 0, 3, + 0, 4, + 1, 2, + 1, 3, + 2, 3, + 2, 4, + 3, 4, + -1); + check_cycles(&g, IGRAPH_OUT, 13); + igraph_destroy(&g); + + printf("\nTesting undirected graph of type 'boat'\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 2, + 0, 4, + 1, 2, + 1, 3, + 1, 4, + 2, 3, + 2, 4, + -1); + check_cycles(&g, IGRAPH_OUT, 6); + igraph_destroy(&g); + + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-2249751754 + printf("\nTesting undirected graph of type 'house'\n"); + igraph_small(&g, 5, IGRAPH_UNDIRECTED, + 0, 3, + 0, 4, + 1, 2, + 1, 3, + 1, 4, + 2, 3, + -1); + check_cycles(&g, IGRAPH_OUT, 3); + igraph_destroy(&g); + + printf("\nTesting undirected graph of type 'prism'\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0, 1, + 0, 3, + 0, 5, + 1, 4, + 1, 5, + 2, 3, + 2, 4, + 2, 5, + 3, 4, + -1); + check_cycles(&g, IGRAPH_OUT, 14); + igraph_destroy(&g); + + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-2251350410 + printf("\nTesting undirected graph of type '7 vertices'\n"); + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0, 1, + 0, 4, + 0, 5, + 0, 6, + 1, 2, + 1, 3, + 1, 5, + 1, 6, + 2, 3, + 2, 6, + 3, 4, + 3, 5, + 3, 6, + 4, 5, + -1); + check_cycles(&g, IGRAPH_OUT, 89); + igraph_destroy(&g); + + printf("\nTesting undirected graph of type 'shooting star'\n"); + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0, 1, + 0, 2, + 0, 3, + 0, 4, + 0, 6, + 1, 2, + 1, 3, + 1, 4, + 1, 5, + 1, 6, + 2, 3, + 2, 4, + 2, 6, + 3, 4, + 3, 5, + 4, 6, + 5, 6, + -1); + check_cycles(&g, IGRAPH_OUT, 18 + 43 + 78 + 96 + 60); + igraph_destroy(&g); + + + // Tests as requested in https://github.com/igraph/igraph/pull/2181#issuecomment-2428987492 + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, + 1, 2, + 2, 3, + 3, 0, + 1, 4, + 4, 2, + -1); + check_cycles(&g, IGRAPH_OUT, 2); + check_cycles_max(&g, IGRAPH_OUT, 0, 3); + check_cycles_max(&g, IGRAPH_OUT, 1, 4); + check_cycles_max(&g, IGRAPH_OUT, 2, 5); + igraph_destroy(&g); + + + printf("\nTesting directed graph of type 'stable boat'\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 2, + 0, 3, + 0, 4, + 1, 0, + 2, 3, + 3, 4, + 4, 1, + 4, 3, + -1); + check_cycles(&g, IGRAPH_OUT, 4); + check_cycles_max(&g, IGRAPH_OUT, 1, 2); + check_cycles_max(&g, IGRAPH_OUT, 2, 3); + check_cycles_max(&g, IGRAPH_OUT, 3, 4); + igraph_destroy(&g); + + printf("\nTesting directed graph of type 'stable letter'\n"); + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 2, 0, 3, 1, 2, 1, 4, 2, 3, 2, 4, 3, 1, 4, 0, 4, 2, + -1); + check_cycles(&g, IGRAPH_OUT, 7); + check_cycles_max(&g, IGRAPH_OUT, 0, 1); + check_cycles_max(&g, IGRAPH_OUT, 1, 2); + check_cycles_max(&g, IGRAPH_OUT, 3, 3); + check_cycles_max(&g, IGRAPH_OUT, 5, 4); + check_cycles_max(&g, IGRAPH_OUT, 7, 5); + igraph_destroy(&g); + + printf("\nTesting directed graph of type 'double square'\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 2, 0, 4, 1, 3, 2, 5, 3, 0, 4, 1, 5, 4, + -1); + check_cycles(&g, IGRAPH_OUT, 2); + check_cycles_max(&g, IGRAPH_OUT, 0, 1); + check_cycles_max(&g, IGRAPH_OUT, 0, 2); + check_cycles_max(&g, IGRAPH_OUT, 0, 3); + check_cycles_max(&g, IGRAPH_OUT, 1, 4); + check_cycles_max(&g, IGRAPH_OUT, 1, 5); + check_cycles_max(&g, IGRAPH_OUT, 2, 6); + igraph_destroy(&g); + + printf("\nTesting undirected graph of type 'Mickey'\n"); + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0,1, + 1,2, + 2,0, + 0,0, + 0,3, + 3,4, + 4,5, + 5,0, + -1); + check_cycles(&g, IGRAPH_ALL, 3); + igraph_destroy(&g); + + printf("\nTesting undirected graph of type 'Mickey2'\n"); + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0,1, + 1,2, + 2,0, + 1,1, + 0,3, + 3,4, + 4,5, + 5,0, + -1); + check_cycles(&g, IGRAPH_ALL, 3); + igraph_destroy(&g); + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0,1, + 1,2, + 2,0, + 1,1, + 0,3, + 3,4, + 4,5, + 5,0, + -1); + check_cycles(&g, IGRAPH_ALL, 3); + igraph_destroy(&g); + + // as requested in https://github.com/igraph/igraph/issues/2692#issuecomment-2457627378 + printf("\nTesting undirected graph of type 'Mickey3'\n"); + igraph_small(&g, 7, IGRAPH_UNDIRECTED, + 0, 1, + 1, 2, + 2, 0, + 0, 3, + 3, 4, + 4, 5, + 5, 0, + 1, 1, + 5, 6, + 6, 5, + 5, 5, + 5, 5, + -1); + check_cycles(&g, IGRAPH_ALL, 6); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + { + igraph_vector_int_list_t v; + + igraph_vector_int_list_init(&v, 0); + + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0,1, 1,2, 2,0, + 0,0, + 0,3, 3,4, 4,5, 5,0, + -1); + + // Check that passing NULL vector lists doesn't crash. + igraph_simple_cycles(&g, NULL, NULL, IGRAPH_ALL, -1, -1, IGRAPH_UNLIMITED); + + // Test limit on minimum cycle size. + igraph_simple_cycles(&g, &v, NULL, IGRAPH_ALL, -1, -1, IGRAPH_UNLIMITED); + IGRAPH_ASSERT(igraph_vector_int_list_size(&v) == 3); + + igraph_simple_cycles(&g, NULL, &v, IGRAPH_ALL, 1, -1, IGRAPH_UNLIMITED); + IGRAPH_ASSERT(igraph_vector_int_list_size(&v) == 3); + + igraph_simple_cycles(&g, &v, NULL, IGRAPH_ALL, 2, -1, IGRAPH_UNLIMITED); + IGRAPH_ASSERT(igraph_vector_int_list_size(&v) == 2); + + igraph_simple_cycles(&g, NULL, &v, IGRAPH_ALL, 2, 3, IGRAPH_UNLIMITED); + IGRAPH_ASSERT(igraph_vector_int_list_size(&v) == 1); + + igraph_destroy(&g); + igraph_vector_int_list_destroy(&v); + } + + return 0; +} diff --git a/tests/unit/igraph_simple_cycles.out b/tests/unit/igraph_simple_cycles.out new file mode 100644 index 0000000..38e01f9 --- /dev/null +++ b/tests/unit/igraph_simple_cycles.out @@ -0,0 +1,1889 @@ +Testing null graph +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} + +Testing edgeless graph +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} + +Testing directed cycle graph +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} + +Testing directed star +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} + +Testing directed wheel +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 2 3 4 5 6 7 ) +} +Edge IDs in cycles: +{ + 0: ( 7 8 9 10 11 12 13 ) +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 7 6 5 4 3 2 ) +} +Edge IDs in cycles: +{ + 0: ( 13 12 11 10 9 8 7 ) +} +Finished search, found 43 cycles, expected 43 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 2 3 ) + 2: ( 0 1 2 3 4 ) + 3: ( 0 1 2 3 4 5 ) + 4: ( 0 1 2 3 4 5 6 ) + 5: ( 0 1 2 3 4 5 6 7 ) + 6: ( 0 1 7 ) + 7: ( 0 1 7 6 ) + 8: ( 0 1 7 6 5 ) + 9: ( 0 1 7 6 5 4 ) + 10: ( 0 1 7 6 5 4 3 ) + 11: ( 0 1 7 6 5 4 3 2 ) + 12: ( 0 2 1 7 ) + 13: ( 0 2 1 7 6 ) + 14: ( 0 2 1 7 6 5 ) + 15: ( 0 2 1 7 6 5 4 ) + 16: ( 0 2 1 7 6 5 4 3 ) + 17: ( 0 2 3 ) + 18: ( 0 2 3 4 ) + 19: ( 0 2 3 4 5 ) + 20: ( 0 2 3 4 5 6 ) + 21: ( 0 2 3 4 5 6 7 ) + 22: ( 0 3 2 1 7 ) + 23: ( 0 3 2 1 7 6 ) + 24: ( 0 3 2 1 7 6 5 ) + 25: ( 0 3 2 1 7 6 5 4 ) + 26: ( 0 3 4 ) + 27: ( 0 3 4 5 ) + 28: ( 0 3 4 5 6 ) + 29: ( 0 3 4 5 6 7 ) + 30: ( 0 4 3 2 1 7 ) + 31: ( 0 4 3 2 1 7 6 ) + 32: ( 0 4 3 2 1 7 6 5 ) + 33: ( 0 4 5 ) + 34: ( 0 4 5 6 ) + 35: ( 0 4 5 6 7 ) + 36: ( 0 5 4 3 2 1 7 ) + 37: ( 0 5 4 3 2 1 7 6 ) + 38: ( 0 5 6 ) + 39: ( 0 5 6 7 ) + 40: ( 0 6 5 4 3 2 1 7 ) + 41: ( 0 6 7 ) + 42: ( 1 2 3 4 5 6 7 ) +} +Edge IDs in cycles: +{ + 0: ( 0 7 1 ) + 1: ( 0 7 8 2 ) + 2: ( 0 7 8 9 3 ) + 3: ( 0 7 8 9 10 4 ) + 4: ( 0 7 8 9 10 11 5 ) + 5: ( 0 7 8 9 10 11 12 6 ) + 6: ( 0 13 6 ) + 7: ( 0 13 12 5 ) + 8: ( 0 13 12 11 4 ) + 9: ( 0 13 12 11 10 3 ) + 10: ( 0 13 12 11 10 9 2 ) + 11: ( 0 13 12 11 10 9 8 1 ) + 12: ( 1 7 13 6 ) + 13: ( 1 7 13 12 5 ) + 14: ( 1 7 13 12 11 4 ) + 15: ( 1 7 13 12 11 10 3 ) + 16: ( 1 7 13 12 11 10 9 2 ) + 17: ( 1 8 2 ) + 18: ( 1 8 9 3 ) + 19: ( 1 8 9 10 4 ) + 20: ( 1 8 9 10 11 5 ) + 21: ( 1 8 9 10 11 12 6 ) + 22: ( 2 8 7 13 6 ) + 23: ( 2 8 7 13 12 5 ) + 24: ( 2 8 7 13 12 11 4 ) + 25: ( 2 8 7 13 12 11 10 3 ) + 26: ( 2 9 3 ) + 27: ( 2 9 10 4 ) + 28: ( 2 9 10 11 5 ) + 29: ( 2 9 10 11 12 6 ) + 30: ( 3 9 8 7 13 6 ) + 31: ( 3 9 8 7 13 12 5 ) + 32: ( 3 9 8 7 13 12 11 4 ) + 33: ( 3 10 4 ) + 34: ( 3 10 11 5 ) + 35: ( 3 10 11 12 6 ) + 36: ( 4 10 9 8 7 13 6 ) + 37: ( 4 10 9 8 7 13 12 5 ) + 38: ( 4 11 5 ) + 39: ( 4 11 12 6 ) + 40: ( 5 11 10 9 8 7 13 6 ) + 41: ( 5 12 6 ) + 42: ( 7 8 9 10 11 12 13 ) +} + +Testing undirected ring +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} + +Testing undirected star +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} + +Testing union of undirected wheel and star +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} + +Testing union of undirected wheel, star and a single edge +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 4 5 6 7 8 9 ) +} + +Testing a tree +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} + +Testing a complete DAG +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 37 cycles, expected 37 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 2 3 ) + 2: ( 0 1 2 3 4 ) + 3: ( 0 1 2 4 ) + 4: ( 0 1 2 4 3 ) + 5: ( 0 1 3 ) + 6: ( 0 1 3 2 ) + 7: ( 0 1 3 2 4 ) + 8: ( 0 1 3 4 ) + 9: ( 0 1 3 4 2 ) + 10: ( 0 1 4 ) + 11: ( 0 1 4 2 ) + 12: ( 0 1 4 2 3 ) + 13: ( 0 1 4 3 ) + 14: ( 0 1 4 3 2 ) + 15: ( 0 2 1 3 ) + 16: ( 0 2 1 3 4 ) + 17: ( 0 2 1 4 ) + 18: ( 0 2 1 4 3 ) + 19: ( 0 2 3 ) + 20: ( 0 2 3 1 4 ) + 21: ( 0 2 3 4 ) + 22: ( 0 2 4 ) + 23: ( 0 2 4 1 3 ) + 24: ( 0 2 4 3 ) + 25: ( 0 3 1 2 4 ) + 26: ( 0 3 1 4 ) + 27: ( 0 3 2 1 4 ) + 28: ( 0 3 2 4 ) + 29: ( 0 3 4 ) + 30: ( 1 2 3 ) + 31: ( 1 2 3 4 ) + 32: ( 1 2 4 ) + 33: ( 1 2 4 3 ) + 34: ( 1 3 2 4 ) + 35: ( 1 3 4 ) + 36: ( 2 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 1 ) + 1: ( 0 2 5 3 ) + 2: ( 0 2 5 9 6 ) + 3: ( 0 2 8 6 ) + 4: ( 0 2 8 9 3 ) + 5: ( 0 4 3 ) + 6: ( 0 4 5 1 ) + 7: ( 0 4 5 8 6 ) + 8: ( 0 4 9 6 ) + 9: ( 0 4 9 8 1 ) + 10: ( 0 7 6 ) + 11: ( 0 7 8 1 ) + 12: ( 0 7 8 5 3 ) + 13: ( 0 7 9 3 ) + 14: ( 0 7 9 5 1 ) + 15: ( 1 2 4 3 ) + 16: ( 1 2 4 9 6 ) + 17: ( 1 2 7 6 ) + 18: ( 1 2 7 9 3 ) + 19: ( 1 5 3 ) + 20: ( 1 5 4 7 6 ) + 21: ( 1 5 9 6 ) + 22: ( 1 8 6 ) + 23: ( 1 8 7 4 3 ) + 24: ( 1 8 9 3 ) + 25: ( 3 4 2 8 6 ) + 26: ( 3 4 7 6 ) + 27: ( 3 5 2 7 6 ) + 28: ( 3 5 8 6 ) + 29: ( 3 9 6 ) + 30: ( 2 5 4 ) + 31: ( 2 5 9 7 ) + 32: ( 2 8 7 ) + 33: ( 2 8 9 4 ) + 34: ( 4 5 8 7 ) + 35: ( 4 9 7 ) + 36: ( 5 9 8 ) +} + +Created undirected wheel +Finished search, found 73 cycles, expected 73 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 2 3 ) + 2: ( 0 1 2 3 4 ) + 3: ( 0 1 2 3 4 5 ) + 4: ( 0 1 2 3 4 5 6 ) + 5: ( 0 1 2 3 4 5 6 7 ) + 6: ( 0 1 2 3 4 5 6 7 8 ) + 7: ( 0 1 2 3 4 5 6 7 8 9 ) + 8: ( 0 1 9 ) + 9: ( 0 1 9 8 ) + 10: ( 0 1 9 8 7 ) + 11: ( 0 1 9 8 7 6 ) + 12: ( 0 1 9 8 7 6 5 ) + 13: ( 0 1 9 8 7 6 5 4 ) + 14: ( 0 1 9 8 7 6 5 4 3 ) + 15: ( 0 1 9 8 7 6 5 4 3 2 ) + 16: ( 0 2 1 9 ) + 17: ( 0 2 1 9 8 ) + 18: ( 0 2 1 9 8 7 ) + 19: ( 0 2 1 9 8 7 6 ) + 20: ( 0 2 1 9 8 7 6 5 ) + 21: ( 0 2 1 9 8 7 6 5 4 ) + 22: ( 0 2 1 9 8 7 6 5 4 3 ) + 23: ( 0 2 3 ) + 24: ( 0 2 3 4 ) + 25: ( 0 2 3 4 5 ) + 26: ( 0 2 3 4 5 6 ) + 27: ( 0 2 3 4 5 6 7 ) + 28: ( 0 2 3 4 5 6 7 8 ) + 29: ( 0 2 3 4 5 6 7 8 9 ) + 30: ( 0 3 2 1 9 ) + 31: ( 0 3 2 1 9 8 ) + 32: ( 0 3 2 1 9 8 7 ) + 33: ( 0 3 2 1 9 8 7 6 ) + 34: ( 0 3 2 1 9 8 7 6 5 ) + 35: ( 0 3 2 1 9 8 7 6 5 4 ) + 36: ( 0 3 4 ) + 37: ( 0 3 4 5 ) + 38: ( 0 3 4 5 6 ) + 39: ( 0 3 4 5 6 7 ) + 40: ( 0 3 4 5 6 7 8 ) + 41: ( 0 3 4 5 6 7 8 9 ) + 42: ( 0 4 3 2 1 9 ) + 43: ( 0 4 3 2 1 9 8 ) + 44: ( 0 4 3 2 1 9 8 7 ) + 45: ( 0 4 3 2 1 9 8 7 6 ) + 46: ( 0 4 3 2 1 9 8 7 6 5 ) + 47: ( 0 4 5 ) + 48: ( 0 4 5 6 ) + 49: ( 0 4 5 6 7 ) + 50: ( 0 4 5 6 7 8 ) + 51: ( 0 4 5 6 7 8 9 ) + 52: ( 0 5 4 3 2 1 9 ) + 53: ( 0 5 4 3 2 1 9 8 ) + 54: ( 0 5 4 3 2 1 9 8 7 ) + 55: ( 0 5 4 3 2 1 9 8 7 6 ) + 56: ( 0 5 6 ) + 57: ( 0 5 6 7 ) + 58: ( 0 5 6 7 8 ) + 59: ( 0 5 6 7 8 9 ) + 60: ( 0 6 5 4 3 2 1 9 ) + 61: ( 0 6 5 4 3 2 1 9 8 ) + 62: ( 0 6 5 4 3 2 1 9 8 7 ) + 63: ( 0 6 7 ) + 64: ( 0 6 7 8 ) + 65: ( 0 6 7 8 9 ) + 66: ( 0 7 6 5 4 3 2 1 9 ) + 67: ( 0 7 6 5 4 3 2 1 9 8 ) + 68: ( 0 7 8 ) + 69: ( 0 7 8 9 ) + 70: ( 0 8 7 6 5 4 3 2 1 9 ) + 71: ( 0 8 9 ) + 72: ( 1 2 3 4 5 6 7 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 9 1 ) + 1: ( 0 9 10 2 ) + 2: ( 0 9 10 11 3 ) + 3: ( 0 9 10 11 12 4 ) + 4: ( 0 9 10 11 12 13 5 ) + 5: ( 0 9 10 11 12 13 14 6 ) + 6: ( 0 9 10 11 12 13 14 15 7 ) + 7: ( 0 9 10 11 12 13 14 15 16 8 ) + 8: ( 0 17 8 ) + 9: ( 0 17 16 7 ) + 10: ( 0 17 16 15 6 ) + 11: ( 0 17 16 15 14 5 ) + 12: ( 0 17 16 15 14 13 4 ) + 13: ( 0 17 16 15 14 13 12 3 ) + 14: ( 0 17 16 15 14 13 12 11 2 ) + 15: ( 0 17 16 15 14 13 12 11 10 1 ) + 16: ( 1 9 17 8 ) + 17: ( 1 9 17 16 7 ) + 18: ( 1 9 17 16 15 6 ) + 19: ( 1 9 17 16 15 14 5 ) + 20: ( 1 9 17 16 15 14 13 4 ) + 21: ( 1 9 17 16 15 14 13 12 3 ) + 22: ( 1 9 17 16 15 14 13 12 11 2 ) + 23: ( 1 10 2 ) + 24: ( 1 10 11 3 ) + 25: ( 1 10 11 12 4 ) + 26: ( 1 10 11 12 13 5 ) + 27: ( 1 10 11 12 13 14 6 ) + 28: ( 1 10 11 12 13 14 15 7 ) + 29: ( 1 10 11 12 13 14 15 16 8 ) + 30: ( 2 10 9 17 8 ) + 31: ( 2 10 9 17 16 7 ) + 32: ( 2 10 9 17 16 15 6 ) + 33: ( 2 10 9 17 16 15 14 5 ) + 34: ( 2 10 9 17 16 15 14 13 4 ) + 35: ( 2 10 9 17 16 15 14 13 12 3 ) + 36: ( 2 11 3 ) + 37: ( 2 11 12 4 ) + 38: ( 2 11 12 13 5 ) + 39: ( 2 11 12 13 14 6 ) + 40: ( 2 11 12 13 14 15 7 ) + 41: ( 2 11 12 13 14 15 16 8 ) + 42: ( 3 11 10 9 17 8 ) + 43: ( 3 11 10 9 17 16 7 ) + 44: ( 3 11 10 9 17 16 15 6 ) + 45: ( 3 11 10 9 17 16 15 14 5 ) + 46: ( 3 11 10 9 17 16 15 14 13 4 ) + 47: ( 3 12 4 ) + 48: ( 3 12 13 5 ) + 49: ( 3 12 13 14 6 ) + 50: ( 3 12 13 14 15 7 ) + 51: ( 3 12 13 14 15 16 8 ) + 52: ( 4 12 11 10 9 17 8 ) + 53: ( 4 12 11 10 9 17 16 7 ) + 54: ( 4 12 11 10 9 17 16 15 6 ) + 55: ( 4 12 11 10 9 17 16 15 14 5 ) + 56: ( 4 13 5 ) + 57: ( 4 13 14 6 ) + 58: ( 4 13 14 15 7 ) + 59: ( 4 13 14 15 16 8 ) + 60: ( 5 13 12 11 10 9 17 8 ) + 61: ( 5 13 12 11 10 9 17 16 7 ) + 62: ( 5 13 12 11 10 9 17 16 15 6 ) + 63: ( 5 14 6 ) + 64: ( 5 14 15 7 ) + 65: ( 5 14 15 16 8 ) + 66: ( 6 14 13 12 11 10 9 17 8 ) + 67: ( 6 14 13 12 11 10 9 17 16 7 ) + 68: ( 6 15 7 ) + 69: ( 6 15 16 8 ) + 70: ( 7 15 14 13 12 11 10 9 17 8 ) + 71: ( 7 16 8 ) + 72: ( 9 10 11 12 13 14 15 16 17 ) +} +Finished search, found 9 cycles, expected 9 cycles. Max cycle length was 3 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 9 ) + 2: ( 0 2 3 ) + 3: ( 0 3 4 ) + 4: ( 0 4 5 ) + 5: ( 0 5 6 ) + 6: ( 0 6 7 ) + 7: ( 0 7 8 ) + 8: ( 0 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 9 1 ) + 1: ( 0 17 8 ) + 2: ( 1 10 2 ) + 3: ( 2 11 3 ) + 4: ( 3 12 4 ) + 5: ( 4 13 5 ) + 6: ( 5 14 6 ) + 7: ( 6 15 7 ) + 8: ( 7 16 8 ) +} +Finished search, found 18 cycles, expected 18 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 2 3 ) + 2: ( 0 1 9 ) + 3: ( 0 1 9 8 ) + 4: ( 0 2 1 9 ) + 5: ( 0 2 3 ) + 6: ( 0 2 3 4 ) + 7: ( 0 3 4 ) + 8: ( 0 3 4 5 ) + 9: ( 0 4 5 ) + 10: ( 0 4 5 6 ) + 11: ( 0 5 6 ) + 12: ( 0 5 6 7 ) + 13: ( 0 6 7 ) + 14: ( 0 6 7 8 ) + 15: ( 0 7 8 ) + 16: ( 0 7 8 9 ) + 17: ( 0 8 9 ) +} +Edge IDs in cycles: +{ + 0: ( 0 9 1 ) + 1: ( 0 9 10 2 ) + 2: ( 0 17 8 ) + 3: ( 0 17 16 7 ) + 4: ( 1 9 17 8 ) + 5: ( 1 10 2 ) + 6: ( 1 10 11 3 ) + 7: ( 2 11 3 ) + 8: ( 2 11 12 4 ) + 9: ( 3 12 4 ) + 10: ( 3 12 13 5 ) + 11: ( 4 13 5 ) + 12: ( 4 13 14 6 ) + 13: ( 5 14 6 ) + 14: ( 5 14 15 7 ) + 15: ( 6 15 7 ) + 16: ( 6 15 16 8 ) + 17: ( 7 16 8 ) +} + +Testing directed graph with a cycle of length 4 and a multi-edge +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 2 3 4 ) + 1: ( 1 2 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 3 4 ) + 1: ( 0 1 3 4 ) +} +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 2 3 4 ) + 1: ( 1 2 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 3 4 ) + 1: ( 0 1 3 4 ) +} + +Testing undirected graph with a cycle of length 4 and a multi-edge +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 2 3 4 ) + 1: ( 1 2 3 4 ) + 2: ( 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 3 4 ) + 1: ( 0 1 3 4 ) + 2: ( 1 2 ) +} +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 1 2 3 4 ) + 1: ( 1 2 3 4 ) + 2: ( 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 3 4 ) + 1: ( 0 1 3 4 ) + 2: ( 1 2 ) +} + +Testing directed graph with single self-loop +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 ) +} +Edge IDs in cycles: +{ + 0: ( 0 ) +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 ) +} +Edge IDs in cycles: +{ + 0: ( 0 ) +} + +Testing undirected graph with single length-2-loop +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 ) +} + +Testing undirected graph with 3 length-2-loops +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 ) + 1: ( 0 1 ) + 2: ( 0 1 ) +} +Edge IDs in cycles: +{ + 0: ( 1 2 ) + 1: ( 0 2 ) + 2: ( 0 1 ) +} + +Testing undirected graph with single length-2-loop and a self-loop +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 ) + 1: ( 0 1 ) +} +Edge IDs in cycles: +{ + 0: ( 2 ) + 1: ( 0 1 ) +} + +Testing undirected graph of type 'envelope' +Finished search, found 13 cycles, expected 13 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 1 2 3 4 ) + 2: ( 0 1 2 4 ) + 3: ( 0 1 2 4 3 ) + 4: ( 0 1 3 ) + 5: ( 0 1 3 2 4 ) + 6: ( 0 1 3 4 ) + 7: ( 0 3 1 2 4 ) + 8: ( 0 3 2 4 ) + 9: ( 0 3 4 ) + 10: ( 1 2 3 ) + 11: ( 1 2 4 3 ) + 12: ( 2 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 3 5 1 ) + 1: ( 0 3 5 7 2 ) + 2: ( 0 3 6 2 ) + 3: ( 0 3 6 7 1 ) + 4: ( 0 4 1 ) + 5: ( 0 4 5 6 2 ) + 6: ( 0 4 7 2 ) + 7: ( 1 4 3 6 2 ) + 8: ( 1 5 6 2 ) + 9: ( 1 7 2 ) + 10: ( 3 5 4 ) + 11: ( 3 6 7 4 ) + 12: ( 5 7 6 ) +} + +Testing undirected graph of type 'boat' +Finished search, found 6 cycles, expected 6 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 1 4 ) + 1: ( 0 2 3 1 4 ) + 2: ( 0 2 4 ) + 3: ( 1 2 3 ) + 4: ( 1 2 4 ) + 5: ( 1 3 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 2 4 1 ) + 1: ( 0 5 3 4 1 ) + 2: ( 0 6 1 ) + 3: ( 2 5 3 ) + 4: ( 2 6 4 ) + 5: ( 3 5 6 4 ) +} + +Testing undirected graph of type 'house' +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 3 1 4 ) + 1: ( 0 3 2 1 4 ) + 2: ( 1 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 3 4 1 ) + 1: ( 0 5 2 4 1 ) + 2: ( 2 5 3 ) +} + +Testing undirected graph of type 'prism' +Finished search, found 14 cycles, expected 14 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 4 2 3 ) + 1: ( 0 1 4 2 5 ) + 2: ( 0 1 4 3 ) + 3: ( 0 1 4 3 2 5 ) + 4: ( 0 1 5 ) + 5: ( 0 1 5 2 3 ) + 6: ( 0 1 5 2 4 3 ) + 7: ( 0 3 2 4 1 5 ) + 8: ( 0 3 2 5 ) + 9: ( 0 3 4 1 5 ) + 10: ( 0 3 4 2 5 ) + 11: ( 1 4 2 5 ) + 12: ( 1 4 3 2 5 ) + 13: ( 2 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 3 6 5 1 ) + 1: ( 0 3 6 7 2 ) + 2: ( 0 3 8 1 ) + 3: ( 0 3 8 5 7 2 ) + 4: ( 0 4 2 ) + 5: ( 0 4 7 5 1 ) + 6: ( 0 4 7 6 8 1 ) + 7: ( 1 5 6 3 4 2 ) + 8: ( 1 5 7 2 ) + 9: ( 1 8 3 4 2 ) + 10: ( 1 8 6 7 2 ) + 11: ( 3 6 7 4 ) + 12: ( 3 8 5 7 4 ) + 13: ( 5 8 6 ) +} + +Testing undirected graph of type '7 vertices' +Finished search, found 89 cycles, expected 89 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 4 ) + 1: ( 0 1 2 3 4 5 ) + 2: ( 0 1 2 3 5 ) + 3: ( 0 1 2 3 5 4 ) + 4: ( 0 1 2 3 6 ) + 5: ( 0 1 2 6 ) + 6: ( 0 1 2 6 3 4 ) + 7: ( 0 1 2 6 3 4 5 ) + 8: ( 0 1 2 6 3 5 ) + 9: ( 0 1 2 6 3 5 4 ) + 10: ( 0 1 3 2 6 ) + 11: ( 0 1 3 4 ) + 12: ( 0 1 3 4 5 ) + 13: ( 0 1 3 5 ) + 14: ( 0 1 3 5 4 ) + 15: ( 0 1 3 6 ) + 16: ( 0 1 5 ) + 17: ( 0 1 5 3 2 6 ) + 18: ( 0 1 5 3 4 ) + 19: ( 0 1 5 3 6 ) + 20: ( 0 1 5 4 ) + 21: ( 0 1 5 4 3 2 6 ) + 22: ( 0 1 5 4 3 6 ) + 23: ( 0 1 6 ) + 24: ( 0 1 6 2 3 4 ) + 25: ( 0 1 6 2 3 4 5 ) + 26: ( 0 1 6 2 3 5 ) + 27: ( 0 1 6 2 3 5 4 ) + 28: ( 0 1 6 3 4 ) + 29: ( 0 1 6 3 4 5 ) + 30: ( 0 1 6 3 5 ) + 31: ( 0 1 6 3 5 4 ) + 32: ( 0 4 3 1 2 6 ) + 33: ( 0 4 3 1 5 ) + 34: ( 0 4 3 1 6 ) + 35: ( 0 4 3 2 1 5 ) + 36: ( 0 4 3 2 1 6 ) + 37: ( 0 4 3 2 6 ) + 38: ( 0 4 3 2 6 1 5 ) + 39: ( 0 4 3 5 ) + 40: ( 0 4 3 5 1 2 6 ) + 41: ( 0 4 3 5 1 6 ) + 42: ( 0 4 3 6 ) + 43: ( 0 4 3 6 1 5 ) + 44: ( 0 4 3 6 2 1 5 ) + 45: ( 0 4 5 ) + 46: ( 0 4 5 1 2 3 6 ) + 47: ( 0 4 5 1 2 6 ) + 48: ( 0 4 5 1 3 2 6 ) + 49: ( 0 4 5 1 3 6 ) + 50: ( 0 4 5 1 6 ) + 51: ( 0 4 5 3 1 2 6 ) + 52: ( 0 4 5 3 1 6 ) + 53: ( 0 4 5 3 2 1 6 ) + 54: ( 0 4 5 3 2 6 ) + 55: ( 0 4 5 3 6 ) + 56: ( 0 5 1 2 3 6 ) + 57: ( 0 5 1 2 6 ) + 58: ( 0 5 1 3 2 6 ) + 59: ( 0 5 1 3 6 ) + 60: ( 0 5 1 6 ) + 61: ( 0 5 3 1 2 6 ) + 62: ( 0 5 3 1 6 ) + 63: ( 0 5 3 2 1 6 ) + 64: ( 0 5 3 2 6 ) + 65: ( 0 5 3 6 ) + 66: ( 0 5 4 3 1 2 6 ) + 67: ( 0 5 4 3 1 6 ) + 68: ( 0 5 4 3 2 1 6 ) + 69: ( 0 5 4 3 2 6 ) + 70: ( 0 5 4 3 6 ) + 71: ( 1 2 3 ) + 72: ( 1 2 3 4 5 ) + 73: ( 1 2 3 5 ) + 74: ( 1 2 3 6 ) + 75: ( 1 2 6 ) + 76: ( 1 2 6 3 ) + 77: ( 1 2 6 3 4 5 ) + 78: ( 1 2 6 3 5 ) + 79: ( 1 3 2 6 ) + 80: ( 1 3 4 5 ) + 81: ( 1 3 5 ) + 82: ( 1 3 6 ) + 83: ( 1 5 3 2 6 ) + 84: ( 1 5 3 6 ) + 85: ( 1 5 4 3 2 6 ) + 86: ( 1 5 4 3 6 ) + 87: ( 2 3 6 ) + 88: ( 3 4 5 ) +} +Edge IDs in cycles: +{ + 0: ( 0 4 8 10 1 ) + 1: ( 0 4 8 10 13 2 ) + 2: ( 0 4 8 11 2 ) + 3: ( 0 4 8 11 13 1 ) + 4: ( 0 4 8 12 3 ) + 5: ( 0 4 9 3 ) + 6: ( 0 4 9 12 10 1 ) + 7: ( 0 4 9 12 10 13 2 ) + 8: ( 0 4 9 12 11 2 ) + 9: ( 0 4 9 12 11 13 1 ) + 10: ( 0 5 8 9 3 ) + 11: ( 0 5 10 1 ) + 12: ( 0 5 10 13 2 ) + 13: ( 0 5 11 2 ) + 14: ( 0 5 11 13 1 ) + 15: ( 0 5 12 3 ) + 16: ( 0 6 2 ) + 17: ( 0 6 11 8 9 3 ) + 18: ( 0 6 11 10 1 ) + 19: ( 0 6 11 12 3 ) + 20: ( 0 6 13 1 ) + 21: ( 0 6 13 10 8 9 3 ) + 22: ( 0 6 13 10 12 3 ) + 23: ( 0 7 3 ) + 24: ( 0 7 9 8 10 1 ) + 25: ( 0 7 9 8 10 13 2 ) + 26: ( 0 7 9 8 11 2 ) + 27: ( 0 7 9 8 11 13 1 ) + 28: ( 0 7 12 10 1 ) + 29: ( 0 7 12 10 13 2 ) + 30: ( 0 7 12 11 2 ) + 31: ( 0 7 12 11 13 1 ) + 32: ( 1 10 5 4 9 3 ) + 33: ( 1 10 5 6 2 ) + 34: ( 1 10 5 7 3 ) + 35: ( 1 10 8 4 6 2 ) + 36: ( 1 10 8 4 7 3 ) + 37: ( 1 10 8 9 3 ) + 38: ( 1 10 8 9 7 6 2 ) + 39: ( 1 10 11 2 ) + 40: ( 1 10 11 6 4 9 3 ) + 41: ( 1 10 11 6 7 3 ) + 42: ( 1 10 12 3 ) + 43: ( 1 10 12 7 6 2 ) + 44: ( 1 10 12 9 4 6 2 ) + 45: ( 1 13 2 ) + 46: ( 1 13 6 4 8 12 3 ) + 47: ( 1 13 6 4 9 3 ) + 48: ( 1 13 6 5 8 9 3 ) + 49: ( 1 13 6 5 12 3 ) + 50: ( 1 13 6 7 3 ) + 51: ( 1 13 11 5 4 9 3 ) + 52: ( 1 13 11 5 7 3 ) + 53: ( 1 13 11 8 4 7 3 ) + 54: ( 1 13 11 8 9 3 ) + 55: ( 1 13 11 12 3 ) + 56: ( 2 6 4 8 12 3 ) + 57: ( 2 6 4 9 3 ) + 58: ( 2 6 5 8 9 3 ) + 59: ( 2 6 5 12 3 ) + 60: ( 2 6 7 3 ) + 61: ( 2 11 5 4 9 3 ) + 62: ( 2 11 5 7 3 ) + 63: ( 2 11 8 4 7 3 ) + 64: ( 2 11 8 9 3 ) + 65: ( 2 11 12 3 ) + 66: ( 2 13 10 5 4 9 3 ) + 67: ( 2 13 10 5 7 3 ) + 68: ( 2 13 10 8 4 7 3 ) + 69: ( 2 13 10 8 9 3 ) + 70: ( 2 13 10 12 3 ) + 71: ( 4 8 5 ) + 72: ( 4 8 10 13 6 ) + 73: ( 4 8 11 6 ) + 74: ( 4 8 12 7 ) + 75: ( 4 9 7 ) + 76: ( 4 9 12 5 ) + 77: ( 4 9 12 10 13 6 ) + 78: ( 4 9 12 11 6 ) + 79: ( 5 8 9 7 ) + 80: ( 5 10 13 6 ) + 81: ( 5 11 6 ) + 82: ( 5 12 7 ) + 83: ( 6 11 8 9 7 ) + 84: ( 6 11 12 7 ) + 85: ( 6 13 10 8 9 7 ) + 86: ( 6 13 10 12 7 ) + 87: ( 8 12 9 ) + 88: ( 10 13 11 ) +} + +Testing undirected graph of type 'shooting star' +Finished search, found 295 cycles, expected 295 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 1 2 3 ) + 2: ( 0 1 2 3 4 ) + 3: ( 0 1 2 3 4 6 ) + 4: ( 0 1 2 3 5 6 ) + 5: ( 0 1 2 3 5 6 4 ) + 6: ( 0 1 2 4 ) + 7: ( 0 1 2 4 3 ) + 8: ( 0 1 2 4 3 5 6 ) + 9: ( 0 1 2 4 6 ) + 10: ( 0 1 2 4 6 5 3 ) + 11: ( 0 1 2 6 ) + 12: ( 0 1 2 6 4 ) + 13: ( 0 1 2 6 4 3 ) + 14: ( 0 1 2 6 5 3 ) + 15: ( 0 1 2 6 5 3 4 ) + 16: ( 0 1 3 ) + 17: ( 0 1 3 2 ) + 18: ( 0 1 3 2 4 ) + 19: ( 0 1 3 2 4 6 ) + 20: ( 0 1 3 2 6 ) + 21: ( 0 1 3 2 6 4 ) + 22: ( 0 1 3 4 ) + 23: ( 0 1 3 4 2 ) + 24: ( 0 1 3 4 2 6 ) + 25: ( 0 1 3 4 6 ) + 26: ( 0 1 3 4 6 2 ) + 27: ( 0 1 3 5 6 ) + 28: ( 0 1 3 5 6 2 ) + 29: ( 0 1 3 5 6 2 4 ) + 30: ( 0 1 3 5 6 4 ) + 31: ( 0 1 3 5 6 4 2 ) + 32: ( 0 1 4 ) + 33: ( 0 1 4 2 ) + 34: ( 0 1 4 2 3 ) + 35: ( 0 1 4 2 3 5 6 ) + 36: ( 0 1 4 2 6 ) + 37: ( 0 1 4 2 6 5 3 ) + 38: ( 0 1 4 3 ) + 39: ( 0 1 4 3 2 ) + 40: ( 0 1 4 3 2 6 ) + 41: ( 0 1 4 3 5 6 ) + 42: ( 0 1 4 3 5 6 2 ) + 43: ( 0 1 4 6 ) + 44: ( 0 1 4 6 2 ) + 45: ( 0 1 4 6 2 3 ) + 46: ( 0 1 4 6 5 3 ) + 47: ( 0 1 4 6 5 3 2 ) + 48: ( 0 1 5 3 ) + 49: ( 0 1 5 3 2 ) + 50: ( 0 1 5 3 2 4 ) + 51: ( 0 1 5 3 2 4 6 ) + 52: ( 0 1 5 3 2 6 ) + 53: ( 0 1 5 3 2 6 4 ) + 54: ( 0 1 5 3 4 ) + 55: ( 0 1 5 3 4 2 ) + 56: ( 0 1 5 3 4 2 6 ) + 57: ( 0 1 5 3 4 6 ) + 58: ( 0 1 5 3 4 6 2 ) + 59: ( 0 1 5 6 ) + 60: ( 0 1 5 6 2 ) + 61: ( 0 1 5 6 2 3 ) + 62: ( 0 1 5 6 2 3 4 ) + 63: ( 0 1 5 6 2 4 ) + 64: ( 0 1 5 6 2 4 3 ) + 65: ( 0 1 5 6 4 ) + 66: ( 0 1 5 6 4 2 ) + 67: ( 0 1 5 6 4 2 3 ) + 68: ( 0 1 5 6 4 3 ) + 69: ( 0 1 5 6 4 3 2 ) + 70: ( 0 1 6 ) + 71: ( 0 1 6 2 ) + 72: ( 0 1 6 2 3 ) + 73: ( 0 1 6 2 3 4 ) + 74: ( 0 1 6 2 4 ) + 75: ( 0 1 6 2 4 3 ) + 76: ( 0 1 6 4 ) + 77: ( 0 1 6 4 2 ) + 78: ( 0 1 6 4 2 3 ) + 79: ( 0 1 6 4 3 ) + 80: ( 0 1 6 4 3 2 ) + 81: ( 0 1 6 5 3 ) + 82: ( 0 1 6 5 3 2 ) + 83: ( 0 1 6 5 3 2 4 ) + 84: ( 0 1 6 5 3 4 ) + 85: ( 0 1 6 5 3 4 2 ) + 86: ( 0 2 1 3 ) + 87: ( 0 2 1 3 4 ) + 88: ( 0 2 1 3 4 6 ) + 89: ( 0 2 1 3 5 6 ) + 90: ( 0 2 1 3 5 6 4 ) + 91: ( 0 2 1 4 ) + 92: ( 0 2 1 4 3 ) + 93: ( 0 2 1 4 3 5 6 ) + 94: ( 0 2 1 4 6 ) + 95: ( 0 2 1 4 6 5 3 ) + 96: ( 0 2 1 5 3 ) + 97: ( 0 2 1 5 3 4 ) + 98: ( 0 2 1 5 3 4 6 ) + 99: ( 0 2 1 5 6 ) + 100: ( 0 2 1 5 6 4 ) + 101: ( 0 2 1 5 6 4 3 ) + 102: ( 0 2 1 6 ) + 103: ( 0 2 1 6 4 ) + 104: ( 0 2 1 6 4 3 ) + 105: ( 0 2 1 6 5 3 ) + 106: ( 0 2 1 6 5 3 4 ) + 107: ( 0 2 3 ) + 108: ( 0 2 3 1 4 ) + 109: ( 0 2 3 1 4 6 ) + 110: ( 0 2 3 1 5 6 ) + 111: ( 0 2 3 1 5 6 4 ) + 112: ( 0 2 3 1 6 ) + 113: ( 0 2 3 1 6 4 ) + 114: ( 0 2 3 4 ) + 115: ( 0 2 3 4 1 5 6 ) + 116: ( 0 2 3 4 1 6 ) + 117: ( 0 2 3 4 6 ) + 118: ( 0 2 3 5 1 4 ) + 119: ( 0 2 3 5 1 4 6 ) + 120: ( 0 2 3 5 1 6 ) + 121: ( 0 2 3 5 1 6 4 ) + 122: ( 0 2 3 5 6 ) + 123: ( 0 2 3 5 6 1 4 ) + 124: ( 0 2 3 5 6 4 ) + 125: ( 0 2 4 ) + 126: ( 0 2 4 1 3 ) + 127: ( 0 2 4 1 3 5 6 ) + 128: ( 0 2 4 1 5 3 ) + 129: ( 0 2 4 1 5 6 ) + 130: ( 0 2 4 1 6 ) + 131: ( 0 2 4 1 6 5 3 ) + 132: ( 0 2 4 3 ) + 133: ( 0 2 4 3 1 5 6 ) + 134: ( 0 2 4 3 1 6 ) + 135: ( 0 2 4 3 5 1 6 ) + 136: ( 0 2 4 3 5 6 ) + 137: ( 0 2 4 6 ) + 138: ( 0 2 4 6 1 3 ) + 139: ( 0 2 4 6 1 5 3 ) + 140: ( 0 2 4 6 5 1 3 ) + 141: ( 0 2 4 6 5 3 ) + 142: ( 0 2 6 ) + 143: ( 0 2 6 1 3 ) + 144: ( 0 2 6 1 3 4 ) + 145: ( 0 2 6 1 4 ) + 146: ( 0 2 6 1 4 3 ) + 147: ( 0 2 6 1 5 3 ) + 148: ( 0 2 6 1 5 3 4 ) + 149: ( 0 2 6 4 ) + 150: ( 0 2 6 4 1 3 ) + 151: ( 0 2 6 4 1 5 3 ) + 152: ( 0 2 6 4 3 ) + 153: ( 0 2 6 5 1 3 ) + 154: ( 0 2 6 5 1 3 4 ) + 155: ( 0 2 6 5 1 4 ) + 156: ( 0 2 6 5 1 4 3 ) + 157: ( 0 2 6 5 3 ) + 158: ( 0 2 6 5 3 1 4 ) + 159: ( 0 2 6 5 3 4 ) + 160: ( 0 3 1 2 4 ) + 161: ( 0 3 1 2 4 6 ) + 162: ( 0 3 1 2 6 ) + 163: ( 0 3 1 2 6 4 ) + 164: ( 0 3 1 4 ) + 165: ( 0 3 1 4 2 6 ) + 166: ( 0 3 1 4 6 ) + 167: ( 0 3 1 5 6 ) + 168: ( 0 3 1 5 6 2 4 ) + 169: ( 0 3 1 5 6 4 ) + 170: ( 0 3 1 6 ) + 171: ( 0 3 1 6 2 4 ) + 172: ( 0 3 1 6 4 ) + 173: ( 0 3 2 1 4 ) + 174: ( 0 3 2 1 4 6 ) + 175: ( 0 3 2 1 5 6 ) + 176: ( 0 3 2 1 5 6 4 ) + 177: ( 0 3 2 1 6 ) + 178: ( 0 3 2 1 6 4 ) + 179: ( 0 3 2 4 ) + 180: ( 0 3 2 4 1 5 6 ) + 181: ( 0 3 2 4 1 6 ) + 182: ( 0 3 2 4 6 ) + 183: ( 0 3 2 6 ) + 184: ( 0 3 2 6 1 4 ) + 185: ( 0 3 2 6 4 ) + 186: ( 0 3 2 6 5 1 4 ) + 187: ( 0 3 4 ) + 188: ( 0 3 4 1 2 6 ) + 189: ( 0 3 4 1 5 6 ) + 190: ( 0 3 4 1 6 ) + 191: ( 0 3 4 2 1 5 6 ) + 192: ( 0 3 4 2 1 6 ) + 193: ( 0 3 4 2 6 ) + 194: ( 0 3 4 6 ) + 195: ( 0 3 5 1 2 4 ) + 196: ( 0 3 5 1 2 4 6 ) + 197: ( 0 3 5 1 2 6 ) + 198: ( 0 3 5 1 2 6 4 ) + 199: ( 0 3 5 1 4 ) + 200: ( 0 3 5 1 4 2 6 ) + 201: ( 0 3 5 1 4 6 ) + 202: ( 0 3 5 1 6 ) + 203: ( 0 3 5 1 6 2 4 ) + 204: ( 0 3 5 1 6 4 ) + 205: ( 0 3 5 6 ) + 206: ( 0 3 5 6 1 2 4 ) + 207: ( 0 3 5 6 1 4 ) + 208: ( 0 3 5 6 2 1 4 ) + 209: ( 0 3 5 6 2 4 ) + 210: ( 0 3 5 6 4 ) + 211: ( 0 4 1 2 3 5 6 ) + 212: ( 0 4 1 2 6 ) + 213: ( 0 4 1 3 2 6 ) + 214: ( 0 4 1 3 5 6 ) + 215: ( 0 4 1 5 3 2 6 ) + 216: ( 0 4 1 5 6 ) + 217: ( 0 4 1 6 ) + 218: ( 0 4 2 1 3 5 6 ) + 219: ( 0 4 2 1 5 6 ) + 220: ( 0 4 2 1 6 ) + 221: ( 0 4 2 3 1 5 6 ) + 222: ( 0 4 2 3 1 6 ) + 223: ( 0 4 2 3 5 1 6 ) + 224: ( 0 4 2 3 5 6 ) + 225: ( 0 4 2 6 ) + 226: ( 0 4 3 1 2 6 ) + 227: ( 0 4 3 1 5 6 ) + 228: ( 0 4 3 1 6 ) + 229: ( 0 4 3 2 1 5 6 ) + 230: ( 0 4 3 2 1 6 ) + 231: ( 0 4 3 2 6 ) + 232: ( 0 4 3 5 1 2 6 ) + 233: ( 0 4 3 5 1 6 ) + 234: ( 0 4 3 5 6 ) + 235: ( 0 4 6 ) + 236: ( 1 2 3 ) + 237: ( 1 2 3 4 ) + 238: ( 1 2 3 4 6 ) + 239: ( 1 2 3 4 6 5 ) + 240: ( 1 2 3 5 ) + 241: ( 1 2 3 5 6 ) + 242: ( 1 2 3 5 6 4 ) + 243: ( 1 2 4 ) + 244: ( 1 2 4 3 ) + 245: ( 1 2 4 3 5 ) + 246: ( 1 2 4 3 5 6 ) + 247: ( 1 2 4 6 ) + 248: ( 1 2 4 6 5 ) + 249: ( 1 2 4 6 5 3 ) + 250: ( 1 2 6 ) + 251: ( 1 2 6 4 ) + 252: ( 1 2 6 4 3 ) + 253: ( 1 2 6 4 3 5 ) + 254: ( 1 2 6 5 ) + 255: ( 1 2 6 5 3 ) + 256: ( 1 2 6 5 3 4 ) + 257: ( 1 3 2 4 ) + 258: ( 1 3 2 4 6 ) + 259: ( 1 3 2 4 6 5 ) + 260: ( 1 3 2 6 ) + 261: ( 1 3 2 6 4 ) + 262: ( 1 3 2 6 5 ) + 263: ( 1 3 4 ) + 264: ( 1 3 4 2 6 ) + 265: ( 1 3 4 2 6 5 ) + 266: ( 1 3 4 6 ) + 267: ( 1 3 4 6 5 ) + 268: ( 1 3 5 ) + 269: ( 1 3 5 6 ) + 270: ( 1 3 5 6 2 4 ) + 271: ( 1 3 5 6 4 ) + 272: ( 1 4 2 3 5 ) + 273: ( 1 4 2 3 5 6 ) + 274: ( 1 4 2 6 ) + 275: ( 1 4 2 6 5 ) + 276: ( 1 4 3 2 6 ) + 277: ( 1 4 3 2 6 5 ) + 278: ( 1 4 3 5 ) + 279: ( 1 4 3 5 6 ) + 280: ( 1 4 6 ) + 281: ( 1 4 6 2 3 5 ) + 282: ( 1 4 6 5 ) + 283: ( 1 5 3 2 4 6 ) + 284: ( 1 5 3 2 6 ) + 285: ( 1 5 3 4 2 6 ) + 286: ( 1 5 3 4 6 ) + 287: ( 1 5 6 ) + 288: ( 2 3 4 ) + 289: ( 2 3 4 6 ) + 290: ( 2 3 5 6 ) + 291: ( 2 3 5 6 4 ) + 292: ( 2 4 3 5 6 ) + 293: ( 2 4 6 ) + 294: ( 3 4 6 5 ) +} +Edge IDs in cycles: +{ + 0: ( 0 5 1 ) + 1: ( 0 5 10 2 ) + 2: ( 0 5 10 13 3 ) + 3: ( 0 5 10 13 15 4 ) + 4: ( 0 5 10 14 16 4 ) + 5: ( 0 5 10 14 16 15 3 ) + 6: ( 0 5 11 3 ) + 7: ( 0 5 11 13 2 ) + 8: ( 0 5 11 13 14 16 4 ) + 9: ( 0 5 11 15 4 ) + 10: ( 0 5 11 15 16 14 2 ) + 11: ( 0 5 12 4 ) + 12: ( 0 5 12 15 3 ) + 13: ( 0 5 12 15 13 2 ) + 14: ( 0 5 12 16 14 2 ) + 15: ( 0 5 12 16 14 13 3 ) + 16: ( 0 6 2 ) + 17: ( 0 6 10 1 ) + 18: ( 0 6 10 11 3 ) + 19: ( 0 6 10 11 15 4 ) + 20: ( 0 6 10 12 4 ) + 21: ( 0 6 10 12 15 3 ) + 22: ( 0 6 13 3 ) + 23: ( 0 6 13 11 1 ) + 24: ( 0 6 13 11 12 4 ) + 25: ( 0 6 13 15 4 ) + 26: ( 0 6 13 15 12 1 ) + 27: ( 0 6 14 16 4 ) + 28: ( 0 6 14 16 12 1 ) + 29: ( 0 6 14 16 12 11 3 ) + 30: ( 0 6 14 16 15 3 ) + 31: ( 0 6 14 16 15 11 1 ) + 32: ( 0 7 3 ) + 33: ( 0 7 11 1 ) + 34: ( 0 7 11 10 2 ) + 35: ( 0 7 11 10 14 16 4 ) + 36: ( 0 7 11 12 4 ) + 37: ( 0 7 11 12 16 14 2 ) + 38: ( 0 7 13 2 ) + 39: ( 0 7 13 10 1 ) + 40: ( 0 7 13 10 12 4 ) + 41: ( 0 7 13 14 16 4 ) + 42: ( 0 7 13 14 16 12 1 ) + 43: ( 0 7 15 4 ) + 44: ( 0 7 15 12 1 ) + 45: ( 0 7 15 12 10 2 ) + 46: ( 0 7 15 16 14 2 ) + 47: ( 0 7 15 16 14 10 1 ) + 48: ( 0 8 14 2 ) + 49: ( 0 8 14 10 1 ) + 50: ( 0 8 14 10 11 3 ) + 51: ( 0 8 14 10 11 15 4 ) + 52: ( 0 8 14 10 12 4 ) + 53: ( 0 8 14 10 12 15 3 ) + 54: ( 0 8 14 13 3 ) + 55: ( 0 8 14 13 11 1 ) + 56: ( 0 8 14 13 11 12 4 ) + 57: ( 0 8 14 13 15 4 ) + 58: ( 0 8 14 13 15 12 1 ) + 59: ( 0 8 16 4 ) + 60: ( 0 8 16 12 1 ) + 61: ( 0 8 16 12 10 2 ) + 62: ( 0 8 16 12 10 13 3 ) + 63: ( 0 8 16 12 11 3 ) + 64: ( 0 8 16 12 11 13 2 ) + 65: ( 0 8 16 15 3 ) + 66: ( 0 8 16 15 11 1 ) + 67: ( 0 8 16 15 11 10 2 ) + 68: ( 0 8 16 15 13 2 ) + 69: ( 0 8 16 15 13 10 1 ) + 70: ( 0 9 4 ) + 71: ( 0 9 12 1 ) + 72: ( 0 9 12 10 2 ) + 73: ( 0 9 12 10 13 3 ) + 74: ( 0 9 12 11 3 ) + 75: ( 0 9 12 11 13 2 ) + 76: ( 0 9 15 3 ) + 77: ( 0 9 15 11 1 ) + 78: ( 0 9 15 11 10 2 ) + 79: ( 0 9 15 13 2 ) + 80: ( 0 9 15 13 10 1 ) + 81: ( 0 9 16 14 2 ) + 82: ( 0 9 16 14 10 1 ) + 83: ( 0 9 16 14 10 11 3 ) + 84: ( 0 9 16 14 13 3 ) + 85: ( 0 9 16 14 13 11 1 ) + 86: ( 1 5 6 2 ) + 87: ( 1 5 6 13 3 ) + 88: ( 1 5 6 13 15 4 ) + 89: ( 1 5 6 14 16 4 ) + 90: ( 1 5 6 14 16 15 3 ) + 91: ( 1 5 7 3 ) + 92: ( 1 5 7 13 2 ) + 93: ( 1 5 7 13 14 16 4 ) + 94: ( 1 5 7 15 4 ) + 95: ( 1 5 7 15 16 14 2 ) + 96: ( 1 5 8 14 2 ) + 97: ( 1 5 8 14 13 3 ) + 98: ( 1 5 8 14 13 15 4 ) + 99: ( 1 5 8 16 4 ) + 100: ( 1 5 8 16 15 3 ) + 101: ( 1 5 8 16 15 13 2 ) + 102: ( 1 5 9 4 ) + 103: ( 1 5 9 15 3 ) + 104: ( 1 5 9 15 13 2 ) + 105: ( 1 5 9 16 14 2 ) + 106: ( 1 5 9 16 14 13 3 ) + 107: ( 1 10 2 ) + 108: ( 1 10 6 7 3 ) + 109: ( 1 10 6 7 15 4 ) + 110: ( 1 10 6 8 16 4 ) + 111: ( 1 10 6 8 16 15 3 ) + 112: ( 1 10 6 9 4 ) + 113: ( 1 10 6 9 15 3 ) + 114: ( 1 10 13 3 ) + 115: ( 1 10 13 7 8 16 4 ) + 116: ( 1 10 13 7 9 4 ) + 117: ( 1 10 13 15 4 ) + 118: ( 1 10 14 8 7 3 ) + 119: ( 1 10 14 8 7 15 4 ) + 120: ( 1 10 14 8 9 4 ) + 121: ( 1 10 14 8 9 15 3 ) + 122: ( 1 10 14 16 4 ) + 123: ( 1 10 14 16 9 7 3 ) + 124: ( 1 10 14 16 15 3 ) + 125: ( 1 11 3 ) + 126: ( 1 11 7 6 2 ) + 127: ( 1 11 7 6 14 16 4 ) + 128: ( 1 11 7 8 14 2 ) + 129: ( 1 11 7 8 16 4 ) + 130: ( 1 11 7 9 4 ) + 131: ( 1 11 7 9 16 14 2 ) + 132: ( 1 11 13 2 ) + 133: ( 1 11 13 6 8 16 4 ) + 134: ( 1 11 13 6 9 4 ) + 135: ( 1 11 13 14 8 9 4 ) + 136: ( 1 11 13 14 16 4 ) + 137: ( 1 11 15 4 ) + 138: ( 1 11 15 9 6 2 ) + 139: ( 1 11 15 9 8 14 2 ) + 140: ( 1 11 15 16 8 6 2 ) + 141: ( 1 11 15 16 14 2 ) + 142: ( 1 12 4 ) + 143: ( 1 12 9 6 2 ) + 144: ( 1 12 9 6 13 3 ) + 145: ( 1 12 9 7 3 ) + 146: ( 1 12 9 7 13 2 ) + 147: ( 1 12 9 8 14 2 ) + 148: ( 1 12 9 8 14 13 3 ) + 149: ( 1 12 15 3 ) + 150: ( 1 12 15 7 6 2 ) + 151: ( 1 12 15 7 8 14 2 ) + 152: ( 1 12 15 13 2 ) + 153: ( 1 12 16 8 6 2 ) + 154: ( 1 12 16 8 6 13 3 ) + 155: ( 1 12 16 8 7 3 ) + 156: ( 1 12 16 8 7 13 2 ) + 157: ( 1 12 16 14 2 ) + 158: ( 1 12 16 14 6 7 3 ) + 159: ( 1 12 16 14 13 3 ) + 160: ( 2 6 5 11 3 ) + 161: ( 2 6 5 11 15 4 ) + 162: ( 2 6 5 12 4 ) + 163: ( 2 6 5 12 15 3 ) + 164: ( 2 6 7 3 ) + 165: ( 2 6 7 11 12 4 ) + 166: ( 2 6 7 15 4 ) + 167: ( 2 6 8 16 4 ) + 168: ( 2 6 8 16 12 11 3 ) + 169: ( 2 6 8 16 15 3 ) + 170: ( 2 6 9 4 ) + 171: ( 2 6 9 12 11 3 ) + 172: ( 2 6 9 15 3 ) + 173: ( 2 10 5 7 3 ) + 174: ( 2 10 5 7 15 4 ) + 175: ( 2 10 5 8 16 4 ) + 176: ( 2 10 5 8 16 15 3 ) + 177: ( 2 10 5 9 4 ) + 178: ( 2 10 5 9 15 3 ) + 179: ( 2 10 11 3 ) + 180: ( 2 10 11 7 8 16 4 ) + 181: ( 2 10 11 7 9 4 ) + 182: ( 2 10 11 15 4 ) + 183: ( 2 10 12 4 ) + 184: ( 2 10 12 9 7 3 ) + 185: ( 2 10 12 15 3 ) + 186: ( 2 10 12 16 8 7 3 ) + 187: ( 2 13 3 ) + 188: ( 2 13 7 5 12 4 ) + 189: ( 2 13 7 8 16 4 ) + 190: ( 2 13 7 9 4 ) + 191: ( 2 13 11 5 8 16 4 ) + 192: ( 2 13 11 5 9 4 ) + 193: ( 2 13 11 12 4 ) + 194: ( 2 13 15 4 ) + 195: ( 2 14 8 5 11 3 ) + 196: ( 2 14 8 5 11 15 4 ) + 197: ( 2 14 8 5 12 4 ) + 198: ( 2 14 8 5 12 15 3 ) + 199: ( 2 14 8 7 3 ) + 200: ( 2 14 8 7 11 12 4 ) + 201: ( 2 14 8 7 15 4 ) + 202: ( 2 14 8 9 4 ) + 203: ( 2 14 8 9 12 11 3 ) + 204: ( 2 14 8 9 15 3 ) + 205: ( 2 14 16 4 ) + 206: ( 2 14 16 9 5 11 3 ) + 207: ( 2 14 16 9 7 3 ) + 208: ( 2 14 16 12 5 7 3 ) + 209: ( 2 14 16 12 11 3 ) + 210: ( 2 14 16 15 3 ) + 211: ( 3 7 5 10 14 16 4 ) + 212: ( 3 7 5 12 4 ) + 213: ( 3 7 6 10 12 4 ) + 214: ( 3 7 6 14 16 4 ) + 215: ( 3 7 8 14 10 12 4 ) + 216: ( 3 7 8 16 4 ) + 217: ( 3 7 9 4 ) + 218: ( 3 11 5 6 14 16 4 ) + 219: ( 3 11 5 8 16 4 ) + 220: ( 3 11 5 9 4 ) + 221: ( 3 11 10 6 8 16 4 ) + 222: ( 3 11 10 6 9 4 ) + 223: ( 3 11 10 14 8 9 4 ) + 224: ( 3 11 10 14 16 4 ) + 225: ( 3 11 12 4 ) + 226: ( 3 13 6 5 12 4 ) + 227: ( 3 13 6 8 16 4 ) + 228: ( 3 13 6 9 4 ) + 229: ( 3 13 10 5 8 16 4 ) + 230: ( 3 13 10 5 9 4 ) + 231: ( 3 13 10 12 4 ) + 232: ( 3 13 14 8 5 12 4 ) + 233: ( 3 13 14 8 9 4 ) + 234: ( 3 13 14 16 4 ) + 235: ( 3 15 4 ) + 236: ( 5 10 6 ) + 237: ( 5 10 13 7 ) + 238: ( 5 10 13 15 9 ) + 239: ( 5 10 13 15 16 8 ) + 240: ( 5 10 14 8 ) + 241: ( 5 10 14 16 9 ) + 242: ( 5 10 14 16 15 7 ) + 243: ( 5 11 7 ) + 244: ( 5 11 13 6 ) + 245: ( 5 11 13 14 8 ) + 246: ( 5 11 13 14 16 9 ) + 247: ( 5 11 15 9 ) + 248: ( 5 11 15 16 8 ) + 249: ( 5 11 15 16 14 6 ) + 250: ( 5 12 9 ) + 251: ( 5 12 15 7 ) + 252: ( 5 12 15 13 6 ) + 253: ( 5 12 15 13 14 8 ) + 254: ( 5 12 16 8 ) + 255: ( 5 12 16 14 6 ) + 256: ( 5 12 16 14 13 7 ) + 257: ( 6 10 11 7 ) + 258: ( 6 10 11 15 9 ) + 259: ( 6 10 11 15 16 8 ) + 260: ( 6 10 12 9 ) + 261: ( 6 10 12 15 7 ) + 262: ( 6 10 12 16 8 ) + 263: ( 6 13 7 ) + 264: ( 6 13 11 12 9 ) + 265: ( 6 13 11 12 16 8 ) + 266: ( 6 13 15 9 ) + 267: ( 6 13 15 16 8 ) + 268: ( 6 14 8 ) + 269: ( 6 14 16 9 ) + 270: ( 6 14 16 12 11 7 ) + 271: ( 6 14 16 15 7 ) + 272: ( 7 11 10 14 8 ) + 273: ( 7 11 10 14 16 9 ) + 274: ( 7 11 12 9 ) + 275: ( 7 11 12 16 8 ) + 276: ( 7 13 10 12 9 ) + 277: ( 7 13 10 12 16 8 ) + 278: ( 7 13 14 8 ) + 279: ( 7 13 14 16 9 ) + 280: ( 7 15 9 ) + 281: ( 7 15 12 10 14 8 ) + 282: ( 7 15 16 8 ) + 283: ( 8 14 10 11 15 9 ) + 284: ( 8 14 10 12 9 ) + 285: ( 8 14 13 11 12 9 ) + 286: ( 8 14 13 15 9 ) + 287: ( 8 16 9 ) + 288: ( 10 13 11 ) + 289: ( 10 13 15 12 ) + 290: ( 10 14 16 12 ) + 291: ( 10 14 16 15 11 ) + 292: ( 11 13 14 16 12 ) + 293: ( 11 15 12 ) + 294: ( 13 15 16 14 ) +} +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 1 4 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 4 5 2 3 ) +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was 3 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 ) +} +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was 5 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 1 4 2 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 3 ) + 1: ( 0 4 5 2 3 ) +} + +Testing directed graph of type 'stable boat' +Finished search, found 4 cycles, expected 4 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 3 4 1 ) + 1: ( 0 3 4 1 ) + 2: ( 0 4 1 ) + 3: ( 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 4 5 6 3 ) + 1: ( 1 5 6 3 ) + 2: ( 2 6 3 ) + 3: ( 5 7 ) +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was 2 vertices. + +Vertex IDs in cycles: +{ + 0: ( 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 5 7 ) +} +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was 3 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 4 1 ) + 1: ( 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 2 6 3 ) + 1: ( 5 7 ) +} +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 3 4 1 ) + 1: ( 0 4 1 ) + 2: ( 3 4 ) +} +Edge IDs in cycles: +{ + 0: ( 1 5 6 3 ) + 1: ( 2 6 3 ) + 2: ( 5 7 ) +} + +Testing directed graph of type 'stable letter' +Finished search, found 7 cycles, expected 7 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 3 1 4 ) + 1: ( 0 2 4 ) + 2: ( 0 3 1 2 4 ) + 3: ( 0 3 1 4 ) + 4: ( 1 2 3 ) + 5: ( 1 4 2 3 ) + 6: ( 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 4 6 3 7 ) + 1: ( 0 5 7 ) + 2: ( 1 6 2 5 7 ) + 3: ( 1 6 3 7 ) + 4: ( 2 4 6 ) + 5: ( 3 8 4 6 ) + 6: ( 5 8 ) +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was 1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was 2 vertices. + +Vertex IDs in cycles: +{ + 0: ( 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 5 8 ) +} +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was 3 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 4 ) + 1: ( 1 2 3 ) + 2: ( 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 5 7 ) + 1: ( 2 4 6 ) + 2: ( 5 8 ) +} +Finished search, found 5 cycles, expected 5 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 4 ) + 1: ( 0 3 1 4 ) + 2: ( 1 2 3 ) + 3: ( 1 4 2 3 ) + 4: ( 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 5 7 ) + 1: ( 1 6 3 7 ) + 2: ( 2 4 6 ) + 3: ( 3 8 4 6 ) + 4: ( 5 8 ) +} +Finished search, found 7 cycles, expected 7 cycles. Max cycle length was 5 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 3 1 4 ) + 1: ( 0 2 4 ) + 2: ( 0 3 1 2 4 ) + 3: ( 0 3 1 4 ) + 4: ( 1 2 3 ) + 5: ( 1 4 2 3 ) + 6: ( 2 4 ) +} +Edge IDs in cycles: +{ + 0: ( 0 4 6 3 7 ) + 1: ( 0 5 7 ) + 2: ( 1 6 2 5 7 ) + 3: ( 1 6 3 7 ) + 4: ( 2 4 6 ) + 5: ( 3 8 4 6 ) + 6: ( 5 8 ) +} + +Testing directed graph of type 'double square' +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 5 4 1 3 ) + 1: ( 0 4 1 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 3 6 5 2 4 ) + 1: ( 1 5 2 4 ) +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was 1 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was 2 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 0 cycles, expected 0 cycles. Max cycle length was 3 vertices. + +Vertex IDs in cycles: +{ +} +Edge IDs in cycles: +{ +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was 4 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 4 1 3 ) +} +Edge IDs in cycles: +{ + 0: ( 1 5 2 4 ) +} +Finished search, found 1 cycles, expected 1 cycles. Max cycle length was 5 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 4 1 3 ) +} +Edge IDs in cycles: +{ + 0: ( 1 5 2 4 ) +} +Finished search, found 2 cycles, expected 2 cycles. Max cycle length was 6 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 2 5 4 1 3 ) + 1: ( 0 4 1 3 ) +} +Edge IDs in cycles: +{ + 0: ( 0 3 6 5 2 4 ) + 1: ( 1 5 2 4 ) +} + +Testing undirected graph of type 'Mickey' +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 ) + 1: ( 0 1 2 ) + 2: ( 0 3 4 5 ) +} +Edge IDs in cycles: +{ + 0: ( 3 ) + 1: ( 0 1 2 ) + 2: ( 4 5 6 7 ) +} + +Testing undirected graph of type 'Mickey2' +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 3 4 5 ) + 2: ( 1 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 4 5 6 7 ) + 2: ( 3 ) +} +Finished search, found 3 cycles, expected 3 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 3 4 5 ) + 2: ( 1 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 4 5 6 7 ) + 2: ( 3 ) +} + +Testing undirected graph of type 'Mickey3' +Finished search, found 6 cycles, expected 6 cycles. Max cycle length was -1 vertices. + +Vertex IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 0 3 4 5 ) + 2: ( 1 ) + 3: ( 5 ) + 4: ( 5 ) + 5: ( 5 6 ) +} +Edge IDs in cycles: +{ + 0: ( 0 1 2 ) + 1: ( 3 4 5 6 ) + 2: ( 7 ) + 3: ( 11 ) + 4: ( 10 ) + 5: ( 8 9 ) +} diff --git a/tests/unit/igraph_simple_interconnected_islands_game.c b/tests/unit/igraph_simple_interconnected_islands_game.c new file mode 100644 index 0000000..995f5d4 --- /dev/null +++ b/tests/unit/igraph_simple_interconnected_islands_game.c @@ -0,0 +1,93 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No islands:\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/0, /*size of islands*/ 1, + /*islands_pin*/ 1, /*number of edges between two islands*/ 1) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("One island, no edges:\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/1, /*size of islands*/ 4, + /*islands_pin*/ 0, /*number of edges between two islands*/ 2) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("One island, full graph:\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/1, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 2) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Two islands, full graphs, no connections between islands:\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/2, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 0) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Three islands, full graphs, 16 connections between islands.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/3, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 16) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_ecount(&g) == 18 + 48); + igraph_destroy(&g); + + printf("Three islands, random graphs, 3 connections between islands.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/3, /*size of islands*/ 4, + /*islands_pin*/ 0.5, /*number of edges between two islands*/ 3) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Negative number of islands.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/-2, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 0) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Negative island size.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/2, /*size of islands*/ -4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 0) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Probability out of range.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/2, /*size of islands*/ 4, + /*islands_pin*/ 2, /*number of edges between two islands*/ 0) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Negative number of edges between islands.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/2, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ -3) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Too many edges between islands.\n"); + IGRAPH_ASSERT(igraph_simple_interconnected_islands_game(&g, /*number of islands*/3, /*size of islands*/ 4, + /*islands_pin*/ 1, /*number of edges between two islands*/ 20) == IGRAPH_EINVAL); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_simple_interconnected_islands_game.out b/tests/unit/igraph_simple_interconnected_islands_game.out new file mode 100644 index 0000000..53a61b3 --- /dev/null +++ b/tests/unit/igraph_simple_interconnected_islands_game.out @@ -0,0 +1,63 @@ +No islands: +directed: false +vcount: 0 +edges: { +} +One island, no edges: +directed: false +vcount: 4 +edges: { +} +One island, full graph: +directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +} +Two islands, full graphs, no connections between islands: +directed: false +vcount: 8 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +4 5 +4 6 +4 7 +5 6 +5 7 +6 7 +} +Three islands, full graphs, 16 connections between islands. +Three islands, random graphs, 3 connections between islands. +directed: false +vcount: 12 +edges: { +0 4 +0 11 +1 10 +2 4 +3 4 +3 10 +4 5 +4 6 +5 7 +5 9 +6 8 +7 11 +9 10 +10 11 +} +Negative number of islands. +Negative island size. +Probability out of range. +Negative number of edges between islands. +Too many edges between islands. diff --git a/tests/unit/igraph_sir.c b/tests/unit/igraph_sir.c new file mode 100644 index 0000000..9c7381c --- /dev/null +++ b/tests/unit/igraph_sir.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_sir(igraph_sir_t *sir) { + igraph_int_t i, n; + + n = igraph_vector_size(&sir->times); + IGRAPH_ASSERT(n >= 2); + + IGRAPH_ASSERT(VECTOR(sir->times)[0] == 0); + for (i = 1; i < n; i++) { + IGRAPH_ASSERT(VECTOR(sir->times)[i] > VECTOR(sir->times)[i-1]); + } + + printf("susceptibles: "); + print_vector_int(&sir->no_s); + printf("infected: "); + print_vector_int(&sir->no_i); + printf("recovered: "); + print_vector_int(&sir->no_r); +} + + +void print_result(igraph_t *g, igraph_real_t beta, igraph_real_t gamma, igraph_int_t no_sim) { + igraph_vector_ptr_t result; + igraph_vector_ptr_init(&result, 0); + IGRAPH_ASSERT(igraph_sir(g, beta, gamma, no_sim, &result) == IGRAPH_SUCCESS); + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&result); i++) { + print_sir(VECTOR(result)[i]); + igraph_sir_destroy(VECTOR(result)[i]); + } + igraph_vector_ptr_destroy_all(&result); + printf("\n"); +} + +int main(void) { + igraph_t g_empty, g_lm, g_line, g_1, g_2, g_full; + + igraph_rng_seed(igraph_rng_default(), 43); + + igraph_small(&g_empty, 0, 0, -1); + igraph_small(&g_lm, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_2, 2, 0, -1); + igraph_small(&g_line, 5, 0, 0,1, 1,2, 2,3, 3,4, -1); + igraph_full(&g_full, 5, 0, IGRAPH_NO_LOOPS); + + printf("Only one person, low recovery rate:\n"); + print_result(&g_1, 0.1, 0.0001, 2); + + printf("Two people, not connected, only one infection expected:\n"); + print_result(&g_2, 1, 1, 2); + + printf("Line:\n"); + print_result(&g_line, 1, 1, 2); + + printf("Line, low infection rate, few infections expected:\n"); + print_result(&g_line, 0.0001, 1, 2); + + printf("Full graph, more infections expected than line with same rates:\n"); + print_result(&g_full, 1, 1, 2); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + IGRAPH_ASSERT(igraph_sir(&g_lm, 1, 1, 1, NULL) == IGRAPH_EINVAL); + IGRAPH_ASSERT(igraph_sir(&g_empty, 1, 1, 1, NULL) == IGRAPH_EINVAL); + + igraph_destroy(&g_empty); + igraph_destroy(&g_lm); + igraph_destroy(&g_line); + igraph_destroy(&g_1); + igraph_destroy(&g_2); + igraph_destroy(&g_full); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sir.out b/tests/unit/igraph_sir.out new file mode 100644 index 0000000..4f48449 --- /dev/null +++ b/tests/unit/igraph_sir.out @@ -0,0 +1,40 @@ +Only one person, low recovery rate: +susceptibles: ( 0 0 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) +susceptibles: ( 0 0 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) + +Two people, not connected, only one infection expected: +susceptibles: ( 1 1 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) +susceptibles: ( 1 1 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) + +Line: +susceptibles: ( 4 3 3 3 ) +infected: ( 1 2 1 0 ) +recovered: ( 0 0 1 2 ) +susceptibles: ( 4 3 2 2 2 2 ) +infected: ( 1 2 3 2 1 0 ) +recovered: ( 0 0 0 1 2 3 ) + +Line, low infection rate, few infections expected: +susceptibles: ( 4 4 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) +susceptibles: ( 4 4 ) +infected: ( 1 0 ) +recovered: ( 0 1 ) + +Full graph, more infections expected than line with same rates: +susceptibles: ( 4 3 2 1 0 0 0 0 0 0 ) +infected: ( 1 2 3 4 5 4 3 2 1 0 ) +recovered: ( 0 0 0 0 0 1 2 3 4 5 ) +susceptibles: ( 4 3 3 2 1 0 0 0 0 0 ) +infected: ( 1 2 1 2 3 4 3 2 1 0 ) +recovered: ( 0 0 1 1 1 1 2 3 4 5 ) + diff --git a/tests/unit/igraph_solve_lsap.c b/tests/unit/igraph_solve_lsap.c new file mode 100644 index 0000000..c4bc1f3 --- /dev/null +++ b/tests/unit/igraph_solve_lsap.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_int_t result; + igraph_matrix_t m_pdc, m_0, m_m34, m_m43; + int pdc[] = {9, 2, 7, 8, + 6, 4, 3, 7, + 5, 8, 1, 8, + 7, 6, 9, 4}; + int m34[] = {3, 3, 2, 3, + 2, 3, 3, 3, + 3, 2, 3, 3}; + int m43[] = {3, 3, 2, + 2, 3, 3, + 3, 2, 3, + 2, 3, 3}; + igraph_vector_int_init(&result, 0); + matrix_init_int_row_major(&m_pdc, 4, 4, pdc); + matrix_init_int_row_major(&m_m34, 3, 4, m34); + matrix_init_int_row_major(&m_m43, 4, 3, m43); + igraph_matrix_init(&m_0, 0, 0); + + printf("4 tasks, 4 agents:\n"); + igraph_solve_lsap(&m_pdc, 4, &result); + print_vector_int(&result); + + printf("\n0 tasks, 0 agents:\n"); + igraph_solve_lsap(&m_0, 0, &result); + print_vector_int(&result); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("\n4 tasks, 3 agents, n = 4.\n"); + IGRAPH_ASSERT(igraph_solve_lsap(&m_m34, 4, &result) == IGRAPH_EINVAL); + + printf("\n3 tasks, 4 agents, n = 4.\n"); + IGRAPH_ASSERT(igraph_solve_lsap(&m_m43, 4, &result) == IGRAPH_EINVAL); + + igraph_vector_int_destroy(&result); + igraph_matrix_destroy(&m_pdc); + igraph_matrix_destroy(&m_0); + igraph_matrix_destroy(&m_m34); + igraph_matrix_destroy(&m_m43); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_solve_lsap.out b/tests/unit/igraph_solve_lsap.out new file mode 100644 index 0000000..d4e1a48 --- /dev/null +++ b/tests/unit/igraph_solve_lsap.out @@ -0,0 +1,9 @@ +4 tasks, 4 agents: +( 1 0 2 3 ) + +0 tasks, 0 agents: +( ) + +4 tasks, 3 agents, n = 4. + +3 tasks, 4 agents, n = 4. diff --git a/tests/unit/igraph_spanner.c b/tests/unit/igraph_spanner.c new file mode 100644 index 0000000..70a165e --- /dev/null +++ b/tests/unit/igraph_spanner.c @@ -0,0 +1,203 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include +#include "test_utilities.h" +#include + +void test_spanner(igraph_t *graph, igraph_vector_int_t *spanner, double stretch, igraph_vector_t *weights) { + igraph_int_t no_of_nodes = igraph_vcount(graph); + igraph_int_t no_of_edges = igraph_ecount(graph); + igraph_t spanner_graph; + igraph_vector_t spanner_weights; + igraph_matrix_t res_spanner, res_graph; + + /* create the spanner graph with igraph_subgraph_from_edges() as recommended in the docs, + then compare it with the original graph and validate the stretch factor */ + if (weights) { + igraph_cattribute_EAN_setv(graph, "weight", weights); + } + igraph_subgraph_from_edges(graph, &spanner_graph, igraph_ess_vector(spanner), 0); + igraph_vector_init(&spanner_weights, igraph_vector_int_size(spanner)); + if (weights){ + igraph_cattribute_EANV(&spanner_graph, "weight", igraph_ess_all(IGRAPH_EDGEORDER_ID), &spanner_weights); + } else { + igraph_vector_fill(&spanner_weights, 1); + } + + // compare number of nodes + IGRAPH_ASSERT(igraph_vcount(&spanner_graph) == no_of_nodes); + + // compare number of edges. We expect the number of edges to decrease in + // all cases but the trivial ones + if (stretch > 1 && no_of_edges > 1) { + IGRAPH_ASSERT(igraph_ecount(&spanner_graph) < no_of_edges); + } + + // print the number of nodes, the number of original edges and the new edge + // count. This is not validated (there is no expected output) but it helps + // to gauge whether the algorithm is not simply keeping most of the edges + printf( + "%" IGRAPH_PRId ", %" IGRAPH_PRId " --> %" IGRAPH_PRId "\n", + no_of_nodes, no_of_edges, igraph_ecount(&spanner_graph) + ); + + // Validate the stretch factor + igraph_matrix_init(&res_spanner, 0, 0); + igraph_matrix_init(&res_graph, 0, 0); + igraph_distances(graph, weights, &res_graph, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + igraph_distances(&spanner_graph, &spanner_weights, &res_spanner, igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL); + for (igraph_int_t x = 0; x < no_of_nodes; x++) { + for (igraph_int_t y = 0; y < no_of_nodes; y++) { + if (x == y) { + continue; + } + IGRAPH_ASSERT(MATRIX(res_spanner, x, y) <= MATRIX(res_graph, x, y) * stretch); + } + } + igraph_matrix_destroy(&res_graph); + igraph_matrix_destroy(&res_spanner); + + // Clean up + igraph_vector_destroy(&spanner_weights); + igraph_destroy(&spanner_graph); +} + +int main(void) { + igraph_t graph; + igraph_vector_t weights; + igraph_vector_int_t spanner; + igraph_int_t no_of_edges; + + /* Initialize attribute handler; we will use edge attributes in test_spanner() */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Seed the RNG to make the test output predictable */ + igraph_rng_seed(igraph_rng_default(), 42); + + /* Create the output vector -- this will be re-used several times */ + igraph_vector_int_init(&spanner, 0); + + /* Trivial spanner with stretch of one */ + printf("Complete graph with stretch of one\n"); + igraph_full(&graph, 20, IGRAPH_UNDIRECTED, 0); + igraph_spanner(&graph, &spanner, 1, NULL); + test_spanner(&graph, &spanner, 1, NULL); + igraph_destroy(&graph); + + /* Test spanner for random weighted complete graph */ + printf("Weighted complete graph with random weights and stretch = 10\n"); + igraph_full(&graph, 20, IGRAPH_UNDIRECTED, 0); + no_of_edges = igraph_ecount(&graph); + igraph_vector_init(&weights, no_of_edges); + for (int i = 0; i < no_of_edges; i++) { + double generated_number = igraph_rng_get_unif(igraph_rng_default(), 1, 100); + VECTOR(weights)[i] = generated_number; + } + igraph_spanner(&graph, &spanner, 10, &weights); + test_spanner(&graph, &spanner, 10, &weights); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Test spanner for unweighted complete graph */ + printf("Unweighted complete graph with stretch = 5\n"); + igraph_full(&graph, 20, IGRAPH_UNDIRECTED, 0); + igraph_spanner(&graph, &spanner, 5, NULL); + test_spanner(&graph, &spanner, 5, NULL); + igraph_destroy(&graph); + + /* Random Erdos-Renyi graph */ + printf("Random Erdos-Renyi graph\n"); + igraph_erdos_renyi_game_gnp(&graph, 200, 0.25, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + no_of_edges = igraph_ecount(&graph); + igraph_vector_init(&weights, no_of_edges); + for (igraph_int_t i = 0; i < no_of_edges; i++) { + double generated_number = igraph_rng_get_unif(igraph_rng_default(), 1, 100); + VECTOR(weights)[i] = generated_number; + } + igraph_spanner(&graph, &spanner, 7, &weights); + test_spanner(&graph, &spanner, 7, &weights); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Geometric random graph */ + printf("Geometric random graph, unweighted\n"); + igraph_grg_game(&graph, 100, 0.2, /* torus = */ 0, 0, 0); + igraph_spanner(&graph, &spanner, 7, 0); + test_spanner(&graph, &spanner, 7, 0); + igraph_destroy(&graph); + + /* Singleton graph */ + printf("Singleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_spanner(&graph, &spanner, 2, 0); + test_spanner(&graph, &spanner, 2, 0); + igraph_destroy(&graph); + + /* Null graph */ + printf("Null graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_spanner(&graph, &spanner, 2, 0); + test_spanner(&graph, &spanner, 2, 0); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + /* Error conditions */ + igraph_set_error_handler(igraph_error_handler_ignore); + + igraph_erdos_renyi_game_gnp(&graph, 200, 0.9, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + no_of_edges = igraph_ecount(&graph); + igraph_vector_init(&weights, no_of_edges); + for (igraph_int_t i = 0; i < no_of_edges; i++) { + double generated_number = igraph_rng_get_unif(igraph_rng_default(), 1, 100); + VECTOR(weights)[i] = generated_number; + } + + printf("Negative weight\n"); + VECTOR(weights)[10] = -42; + IGRAPH_ASSERT(igraph_spanner(&graph, &spanner, 7, &weights) == IGRAPH_EINVAL); + VECTOR(weights)[10] = 42; + + printf("NaN weight\n"); + VECTOR(weights)[10] = IGRAPH_NAN; + IGRAPH_ASSERT(igraph_spanner(&graph, &spanner, 7, &weights) == IGRAPH_EINVAL); + VECTOR(weights)[10] = 42; + + printf("Invalid spanning factor\n"); + IGRAPH_ASSERT(igraph_spanner(&graph, &spanner, 0.5, &weights) == IGRAPH_EINVAL); + + printf("Invalid weight vector length\n"); + igraph_vector_resize(&weights, no_of_edges - 1); + IGRAPH_ASSERT(igraph_spanner(&graph, &spanner, 7, &weights) == IGRAPH_EINVAL); + + igraph_vector_destroy(&weights); + igraph_destroy(&graph); + + /* Regression test for https://github.com/igraph/igraph/issues/2487 + * Edge directions should be ignored in directed graphs. */ + igraph_rng_seed(igraph_rng_default(), 42); + igraph_small(&graph, 2, IGRAPH_DIRECTED, 0, 1, -1); + igraph_spanner(&graph, &spanner, 1.72, NULL); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&spanner); + + return IGRAPH_SUCCESS; +} diff --git a/tests/unit/igraph_sparsemat2.c b/tests/unit/igraph_sparsemat2.c new file mode 100644 index 0000000..a686717 --- /dev/null +++ b/tests/unit/igraph_sparsemat2.c @@ -0,0 +1,147 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#define NCOMPLEX /* to make it compile with MSVC on Windows */ + +#include + +#include "test_utilities.h" + +igraph_error_t my_gaxpy(const igraph_matrix_t *m, + const igraph_vector_t *v, + igraph_vector_t *res) { + return igraph_blas_dgemv(false, 1.0, m, v, 0.0, res); +} + +igraph_bool_t check_same(const igraph_sparsemat_t *A, + const igraph_matrix_t *M) { + igraph_matrix_t A_dense; + igraph_bool_t result; + + igraph_matrix_init(&A_dense, 1, 1); + igraph_sparsemat_as_matrix(&A_dense, A); + result = igraph_matrix_all_e(&A_dense, M); + igraph_matrix_destroy(&A_dense); + + return result; +} + +int main(void) { + + igraph_sparsemat_t A, B, C, D; + igraph_vector_t v, w, x, y; + igraph_matrix_t M, N, O; + igraph_int_t i; + + /* Matrix-vector product */ +#define NROW 10 +#define NCOL 5 +#define EDGES NROW*NCOL/3 + igraph_matrix_init(&M, NROW, NCOL); + igraph_sparsemat_init(&A, NROW, NCOL, EDGES); + for (i = 0; i < EDGES; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(M, r, c) = MATRIX(M, r, c) + value; + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_init(&v, NCOL); + igraph_vector_init(&w, NCOL); + for (i = 0; i < NCOL; i++) { + VECTOR(v)[i] = VECTOR(w)[i] = RNG_INTEGER(1, 5); + } + + igraph_vector_init(&x, NROW); + igraph_vector_init(&y, NROW); + my_gaxpy(&M, &v, &x); + igraph_vector_null(&y); + igraph_sparsemat_gaxpy(&B, &w, &y); + + if (!igraph_vector_all_e(&x, &y)) { + return 1; + } + + igraph_vector_destroy(&x); + igraph_vector_destroy(&y); + igraph_vector_destroy(&v); + igraph_vector_destroy(&w); + igraph_sparsemat_destroy(&B); + igraph_matrix_destroy(&M); + +#undef NROW +#undef NCOL +#undef EDGES + + /* Matrix-matrix product */ +#define NROW_A 10 +#define NCOL_A 7 +#define EDGES_A NROW_A*NCOL_A/3 +#define NROW_B 7 +#define NCOL_B 9 +#define EDGES_B NROW_B*NCOL_B/3 + igraph_matrix_init(&M, NROW_A, NCOL_A); + igraph_sparsemat_init(&A, NROW_A, NCOL_A, EDGES_A); + for (i = 0; i < EDGES_A; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW_A - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL_A - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(M, r, c) = MATRIX(M, r, c) + value; + igraph_sparsemat_entry(&A, r, c, value); + } + igraph_sparsemat_compress(&A, &C); + igraph_sparsemat_destroy(&A); + + igraph_matrix_init(&N, NROW_B, NCOL_B); + igraph_sparsemat_init(&B, NROW_B, NCOL_B, EDGES_B); + for (i = 0; i < EDGES_B; i++) { + igraph_int_t r = RNG_INTEGER(0, NROW_B - 1); + igraph_int_t c = RNG_INTEGER(0, NCOL_B - 1); + igraph_real_t value = RNG_INTEGER(1, 5); + MATRIX(N, r, c) = MATRIX(N, r, c) + value; + igraph_sparsemat_entry(&B, r, c, value); + } + igraph_sparsemat_compress(&B, &D); + igraph_sparsemat_destroy(&B); + + igraph_matrix_init(&O, 0, 0); + igraph_blas_dgemm(false, false, 1.0, &M, &N, 0.0, &O); + igraph_sparsemat_multiply(&C, &D, &A); + + if (! check_same(&A, &O)) { + return 2; + } + + igraph_sparsemat_destroy(&C); + igraph_sparsemat_destroy(&D); + igraph_sparsemat_destroy(&A); + igraph_matrix_destroy(&M); + igraph_matrix_destroy(&N); + igraph_matrix_destroy(&O); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat2.out b/tests/unit/igraph_sparsemat2.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/igraph_sparsemat5.c b/tests/unit/igraph_sparsemat5.c new file mode 100644 index 0000000..f25917a --- /dev/null +++ b/tests/unit/igraph_sparsemat5.c @@ -0,0 +1,400 @@ +/* + igraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define EPS 1e-13 + + +/* Generic test for 1x1 matrices */ +void test_1x1(igraph_real_t value) { + igraph_sparsemat_t A, B; + igraph_matrix_t values, vectors; + igraph_vector_t values2; + igraph_arpack_options_t options; + + igraph_arpack_options_init(&options); + + igraph_sparsemat_init(&A, 1, 1, 1); + igraph_sparsemat_entry(&A, 0, 0, value); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_matrix_init(&values, 0, 0); + igraph_matrix_init(&vectors, 0, 0); + options.mode = 1; + igraph_sparsemat_arpack_rnsolve(&B, /*options=*/ 0, /*storage=*/ 0, + &values, &vectors); + printf("rnsolve:\n - eigenvalues:\n"); + print_matrix(&values); + printf(" - eigenvectors:\n"); + print_matrix(&vectors); + igraph_matrix_destroy(&values); + igraph_matrix_destroy(&vectors); + + igraph_vector_init(&values2, 0); + igraph_matrix_init(&vectors, 0, 0); + options.mode = 1; + igraph_sparsemat_arpack_rssolve(&B, /*options=*/ 0, /*storage=*/ 0, + &values2, &vectors, IGRAPH_SPARSEMAT_SOLVE_LU); + printf("rssolve:\n - eigenvalues:\n"); + print_vector(&values2); + printf(" - eigenvectors:\n"); + print_matrix(&vectors); + igraph_vector_destroy(&values2); + igraph_matrix_destroy(&vectors); + + igraph_sparsemat_destroy(&B); +} + +/* Generic test for 2x2 matrices */ +void test_2x2(igraph_real_t a, igraph_real_t b, igraph_real_t c, igraph_real_t d) { + igraph_sparsemat_t A, B; + igraph_matrix_t values, vectors; + igraph_vector_t values2; + igraph_arpack_options_t options; + + igraph_arpack_options_init(&options); + options.mode = 1; + options.nev = 2; + + igraph_sparsemat_init(&A, 2, 2, 4); + igraph_sparsemat_entry(&A, 0, 0, a); + igraph_sparsemat_entry(&A, 0, 1, b); + igraph_sparsemat_entry(&A, 1, 0, c); + igraph_sparsemat_entry(&A, 1, 1, d); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_matrix_init(&values, 0, 0); + igraph_matrix_init(&vectors, 0, 0); + igraph_sparsemat_arpack_rnsolve(&B, &options, /*storage=*/ 0, + &values, &vectors); + printf("rnsolve:\n - eigenvalues:\n"); + print_matrix(&values); + printf(" - eigenvectors:\n"); + print_matrix(&vectors); + igraph_matrix_destroy(&values); + igraph_matrix_destroy(&vectors); + + if (b == c) { + igraph_vector_init(&values2, 0); + igraph_matrix_init(&vectors, 0, 0); + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values2, &vectors, IGRAPH_SPARSEMAT_SOLVE_QR); + printf("rssolve:\n - eigenvalues:\n"); + print_vector(&values2); + printf(" - eigenvectors:\n"); + print_matrix(&vectors); + igraph_vector_destroy(&values2); + igraph_matrix_destroy(&vectors); + } + + igraph_sparsemat_destroy(&B); +} + +int main(void) { + + igraph_sparsemat_t A, B; + igraph_matrix_t vectors, values2; + igraph_vector_t values; + igraph_int_t i; + igraph_arpack_options_t options; + igraph_real_t min, max; + igraph_t g1, g2, g3; + + /* igraph_arpack_rssolve()/rnsolve() use the RNG to generate + * a random starting vector for ARPACK. */ + igraph_rng_seed(igraph_rng_default(), 123); + + /***********************************************************************/ + + /* Identity matrix */ + printf("== Identity matrix ==\n"); +#define DIM 10 + igraph_sparsemat_init(&A, DIM, DIM, DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, 1.0); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_init(&values, 0); + igraph_arpack_options_init(&options); + + options.mode = 1; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ 0, /*solvemethod=*/0); + IGRAPH_ASSERT(VECTOR(values)[0] == 1.0); + + options.mode = 3; + options.sigma = 2; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ 0, + IGRAPH_SPARSEMAT_SOLVE_LU); + IGRAPH_ASSERT(VECTOR(values)[0] == 1.0); + + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ 0, + IGRAPH_SPARSEMAT_SOLVE_QR); + IGRAPH_ASSERT(VECTOR(values)[0] == 1.0); + + igraph_vector_destroy(&values); + igraph_sparsemat_destroy(&B); + +#undef DIM + + /***********************************************************************/ + + /* Diagonal matrix */ + printf("\n== Diagonal matrix ==\n"); +#define DIM 10 + igraph_sparsemat_init(&A, DIM, DIM, DIM); + for (i = 0; i < DIM; i++) { + igraph_sparsemat_entry(&A, i, i, i + 1.0); + } + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_init(&values, 0); + igraph_matrix_init(&vectors, 0, 0); + + /* Regular mode */ + options.mode = 1; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ &vectors, + /*solvemethod=*/ 0); + if ( fabs(VECTOR(values)[0] - DIM) > EPS ) { + printf("Regular: VECTOR(values)[0] numerical precision is only %g, should be %g", + fabs((double)VECTOR(values)[0] - DIM), EPS); + IGRAPH_FATAL("Precision lower than expected."); + } + + IGRAPH_ASSERT( fabs(fabs(MATRIX(vectors, DIM - 1, 0)) - 1.0) < EPS); + + MATRIX(vectors, DIM - 1, 0) = 0.0; + igraph_matrix_minmax(&vectors, &min, &max); + IGRAPH_ASSERT(fabs(min) < EPS); + IGRAPH_ASSERT(fabs(max) < EPS); + + /* Shift and invert mode */ + options.mode = 3; + options.sigma = 11; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ &vectors, + IGRAPH_SPARSEMAT_SOLVE_LU); + if ( fabs(VECTOR(values)[0] - DIM) > EPS ) { + printf("Shift and invert, LU: VECTOR(values)[0] numerical precision is only %g, should be %g", + fabs((double)VECTOR(values)[0] - DIM), EPS); + IGRAPH_FATAL("Precision lower than expected."); + } + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, /*vectors=*/ &vectors, + IGRAPH_SPARSEMAT_SOLVE_QR); + if ( fabs(VECTOR(values)[0] - DIM) > EPS ) { + printf("Shift and invert, QR: VECTOR(values)[0] numerical precision is only %g, should be %g", + fabs((double)VECTOR(values)[0] - DIM), EPS); + IGRAPH_FATAL("Precision lower than expected."); + } + + IGRAPH_ASSERT( fabs(fabs(MATRIX(vectors, DIM - 1, 0)) - 1.0) < EPS); + + MATRIX(vectors, DIM - 1, 0) = 0.0; + igraph_matrix_minmax(&vectors, &min, &max); + IGRAPH_ASSERT(fabs(min) < EPS); + IGRAPH_ASSERT(fabs(max) < EPS); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_sparsemat_destroy(&B); +#undef DIM + + /***********************************************************************/ + + /* A tree, plus a ring */ + printf("\n== A tree, plus a ring ==\n"); +#define DIM 10 + igraph_kary_tree(&g1, DIM, /*children=*/ 2, IGRAPH_TREE_UNDIRECTED); + igraph_ring(&g2, DIM, IGRAPH_UNDIRECTED, /*mutual=*/ 0, /*circular=*/ 1); + igraph_union(&g3, &g1, &g2, /*edge_map1=*/ 0, /*edge_map1=*/ 0); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_get_adjacency_sparse(&g3, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&g3); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_vector_init(&values, 0); + igraph_matrix_init(&vectors, 0, 0); + + /* Regular mode */ + options.mode = 1; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, &vectors, /*solvemethod=*/ 0); + + if (MATRIX(vectors, 0, 0) < 0.0) { + igraph_matrix_scale(&vectors, -1.0); + } + + printf("\nRegular:\n"); + printf("Eigenvalues:\n"); + print_vector(&values); + printf("Eigenvectors:\n"); + print_matrix(&vectors); + + /* Shift and invert mode */ + options.mode = 3; + options.sigma = VECTOR(values)[0] * 1.1; + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, &vectors, + IGRAPH_SPARSEMAT_SOLVE_LU); + + if (MATRIX(vectors, 0, 0) < 0.0) { + igraph_matrix_scale(&vectors, -1.0); + } + printf("\nShift and invert, LU:\n"); + printf("Eigenvalues:\n"); + print_vector(&values); + printf("Eigenvectors:\n"); + print_matrix(&vectors); + + igraph_sparsemat_arpack_rssolve(&B, &options, /*storage=*/ 0, + &values, &vectors, + IGRAPH_SPARSEMAT_SOLVE_QR); + if (MATRIX(vectors, 0, 0) < 0.0) { + igraph_matrix_scale(&vectors, -1.0); + } + printf("\nShift and invert, QR:\n"); + printf("Eigenvalues:\n"); + print_vector(&values); + printf("Eigenvectors:\n"); + print_matrix(&vectors); + + igraph_vector_destroy(&values); + igraph_matrix_destroy(&vectors); + igraph_sparsemat_destroy(&B); +#undef DIM + + + /***********************************************************************/ + + /* A directed tree and a directed, mutual ring, no ARPACK options */ + printf("\n== A directed tree and a directed, mutual ring ==\n"); +#define DIM 10 + igraph_kary_tree(&g1, DIM, /*children=*/ 2, IGRAPH_TREE_OUT); + igraph_ring(&g2, DIM, IGRAPH_DIRECTED, /*mutual=*/ 1, /*circular=*/ 1); + igraph_union(&g3, &g1, &g2, /*edge_map1=*/ 0, /*edge_map2=*/ 0); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_get_adjacency_sparse(&g3, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&g3); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_matrix_init(&values2, 0, 0); + igraph_matrix_init(&vectors, 0, 0); + + /* Regular mode */ + options.mode = 1; + igraph_sparsemat_arpack_rnsolve(&B, /*options=*/ 0, /*storage=*/ 0, + &values2, &vectors); + + if (MATRIX(vectors, 0, 0) < 0.0) { + igraph_matrix_scale(&vectors, -1.0); + } + + printf("\nRegular:\n"); + printf("Eigenvalues:\n"); + print_matrix(&values2); + printf("Eigenvectors:\n"); + print_matrix(&vectors); + + igraph_matrix_destroy(&values2); + igraph_matrix_destroy(&vectors); + igraph_sparsemat_destroy(&B); +#undef DIM + + /***********************************************************************/ + + /* A small test graph */ + printf("\n== A small test graph ==\n"); + + igraph_small(&g1, 11, IGRAPH_DIRECTED, + 0, 1, 1, 3, 1, 8, 2, 10, 3, 6, 3, 10, 4, 2, 5, 4, + 6, 1, 6, 4, 7, 9, 8, 5, 8, 7, 9, 8, 10, 0, + -1); + + igraph_sparsemat_init(&A, 1, 1, 0); + igraph_get_adjacency_sparse(&g1, &A, IGRAPH_GET_ADJACENCY_BOTH, NULL, IGRAPH_LOOPS_ONCE); + igraph_destroy(&g1); + igraph_sparsemat_compress(&A, &B); + igraph_sparsemat_destroy(&A); + + igraph_matrix_init(&values2, 0, 0); + igraph_matrix_init(&vectors, 0, 0); + + /* Regular mode */ + options.mode = 1; + igraph_sparsemat_arpack_rnsolve(&B, &options, /*storage=*/ 0, + &values2, &vectors); + + if (MATRIX(vectors, 0, 0) < 0.0) { + igraph_matrix_scale(&vectors, -1.0); + } + + printf("\nRegular:\n"); + printf("Eigenvalues:\n"); + print_matrix(&values2); + printf("Eigenvectors:\n"); + print_matrix(&vectors); + + igraph_matrix_destroy(&values2); + igraph_matrix_destroy(&vectors); + igraph_sparsemat_destroy(&B); + + /***********************************************************************/ + + /* Testing the special case solver for 1x1 matrices */ + printf("\n== Testing the special case solver for 1x1 matrices ==\n"); + test_1x1(2); + test_1x1(0); + test_1x1(-3); + + /***********************************************************************/ + + /* Testing the special case solver for 2x2 matrices */ + printf("\n== Testing the special case solver for 2x2 matrices ==\n"); + test_2x2(1, 2, 2, 4); /* symmetric */ + test_2x2(1, 2, 3, 4); /* non-symmetric, real eigenvalues */ + test_2x2(1, -5, 10, 4); /* non-symmetric, complex eigenvalues */ + test_2x2(0, 0, 0, 0); /* symmetric, pathological */ + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat5.out b/tests/unit/igraph_sparsemat5.out new file mode 100644 index 0000000..725b4b6 --- /dev/null +++ b/tests/unit/igraph_sparsemat5.out @@ -0,0 +1,159 @@ +== Identity matrix == + +== Diagonal matrix == + +== A tree, plus a ring == + +Regular: +Eigenvalues: +( 3.79369 ) +Eigenvectors: +[ 0.272513 + 0.387127 + 0.421856 + 0.429479 + 0.344793 + 0.266584 + 0.24469 + 0.239837 + 0.235697 + 0.224848 ] + +Shift and invert, LU: +Eigenvalues: +( 3.79369 ) +Eigenvectors: +[ 0.272513 + 0.387127 + 0.421856 + 0.429479 + 0.344793 + 0.266584 + 0.24469 + 0.239837 + 0.235697 + 0.224848 ] + +Shift and invert, QR: +Eigenvalues: +( 3.79369 ) +Eigenvectors: +[ 0.272513 + 0.387127 + 0.421856 + 0.429479 + 0.344793 + 0.266584 + 0.24469 + 0.239837 + 0.235697 + 0.224848 ] + +== A directed tree and a directed, mutual ring == + +Regular: +Eigenvalues: +[ 2.61264 0 ] +Eigenvectors: +[ 0.467245 + 0.571573 + 0.427081 + 0.335532 + 0.263456 + 0.130696 + 0.0780048 + 0.0731022 + 0.112985 + 0.222086 ] + +== A small test graph == + +Regular: +Eigenvalues: +[ 1.35971 0 ] +Eigenvectors: +[ 0.352334 + 0.479071 + 0.190574 + 0.525509 + 0.140158 + 0.10308 + 0.455414 + 0.0680917 + 0.125888 + 0.0925848 + 0.259125 ] + +== Testing the special case solver for 1x1 matrices == +rnsolve: + - eigenvalues: +[ 2 0 ] + - eigenvectors: +[ 1 ] +rssolve: + - eigenvalues: +( 2 ) + - eigenvectors: +[ 1 ] +rnsolve: + - eigenvalues: +[ 0 0 ] + - eigenvectors: +[ 1 ] +rssolve: + - eigenvalues: +( 0 ) + - eigenvectors: +[ 1 ] +rnsolve: + - eigenvalues: +[ -3 0 ] + - eigenvectors: +[ 1 ] +rssolve: + - eigenvalues: +( -3 ) + - eigenvectors: +[ 1 ] + +== Testing the special case solver for 2x2 matrices == +rnsolve: + - eigenvalues: +[ 5 0 + 0 0 ] + - eigenvectors: +[ 1 -4 + 2 2 ] +rssolve: + - eigenvalues: +( 5 0 ) + - eigenvectors: +[ 1 -4 + 2 2 ] +rnsolve: + - eigenvalues: +[ 5.37228 0 + -0.372281 0 ] + - eigenvectors: +[ 1.37228 -4.37228 + 3 3 ] +rnsolve: + - eigenvalues: +[ 2.5 6.91014 + 2.5 -6.91014 ] + - eigenvectors: +[ -1.5 6.91014 + 10 0 ] +rnsolve: + - eigenvalues: +[ 0 0 + 0 0 ] + - eigenvectors: +[ 1 0 + 0 1 ] +rssolve: + - eigenvalues: +( 0 0 ) + - eigenvectors: +[ 1 0 + 0 1 ] diff --git a/tests/unit/igraph_sparsemat9.c b/tests/unit/igraph_sparsemat9.c new file mode 100644 index 0000000..d31c828 --- /dev/null +++ b/tests/unit/igraph_sparsemat9.c @@ -0,0 +1,87 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DIM1 10 +#define DIM2 5 +#define DIM3 6 + +#define INT(a) (igraph_rng_get_integer(igraph_rng_default(), 0, (a))) +#define REAL() (igraph_rng_get_normal(igraph_rng_default(), 0, 1)) + +int main(void) { + igraph_sparsemat_t sA, sB, sC; + igraph_matrix_t A1, A2, A3, B, C; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_sparsemat_init(&sA, DIM1, DIM2, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&sA, INT(DIM1 - 1), INT(DIM2 - 1), REAL()); + } + igraph_sparsemat_compress(&sA, &sB); + igraph_sparsemat_destroy(&sA); + + igraph_sparsemat_init(&sA, DIM2, DIM3, 20); + for (i = 0; i < 10; i++) { + igraph_sparsemat_entry(&sA, INT(DIM2 - 1), INT(DIM3 - 1), REAL()); + } + igraph_sparsemat_compress(&sA, &sC); + igraph_sparsemat_destroy(&sA); + + igraph_matrix_init(&B, 0, 0); + igraph_sparsemat_as_matrix(&B, &sB); + igraph_matrix_init(&C, 0, 0); + igraph_sparsemat_as_matrix(&C, &sC); + + /* All possible products */ + igraph_sparsemat_multiply(&sB, &sC, &sA); + igraph_matrix_init(&A1, 0, 0); + igraph_sparsemat_as_matrix(&A1, &sA); + igraph_matrix_init(&A2, 0, 0); + igraph_sparsemat_dense_multiply(&B, &sC, &A2); + igraph_matrix_init(&A3, 0, 0); + igraph_sparsemat_multiply_by_dense(&sB, &C, &A3); + + if (igraph_matrix_maxdifference(&A1, &A2) > 1e-10 || + igraph_matrix_maxdifference(&A2, &A3) > 1e-10) { + return 1; + } + + igraph_sparsemat_destroy(&sA); + igraph_sparsemat_destroy(&sB); + igraph_sparsemat_destroy(&sC); + + igraph_matrix_destroy(&A1); + igraph_matrix_destroy(&A2); + igraph_matrix_destroy(&A3); + igraph_matrix_destroy(&B); + igraph_matrix_destroy(&C); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat_droptol.c b/tests/unit/igraph_sparsemat_droptol.c new file mode 100644 index 0000000..e750519 --- /dev/null +++ b/tests/unit/igraph_sparsemat_droptol.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + + printf("0x0 matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_droptol(&spmat_comp, 5) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + printf("3x3 matrix.\n"); + igraph_sparsemat_init(&spmat, 3, 3, /*nzmax*/7); + igraph_sparsemat_entry(&spmat, 0, 0, 5); + igraph_sparsemat_entry(&spmat, 1, 1, 6); + igraph_sparsemat_entry(&spmat, 2, 2, 7); + igraph_sparsemat_entry(&spmat, 3, 0, 1); + igraph_sparsemat_entry(&spmat, 0, 3, 2); + igraph_sparsemat_entry(&spmat, 2, 1, 3); + igraph_sparsemat_entry(&spmat, 1, 2, -14); + igraph_sparsemat_compress(&spmat, &spmat_comp); + printf("Remove values within distance 5 from zero:\n"); + IGRAPH_ASSERT(igraph_sparsemat_droptol(&spmat_comp, 5) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + printf("Remove values within distance 20 from zero:\n"); + IGRAPH_ASSERT(igraph_sparsemat_droptol(&spmat_comp, 20) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("uncompressed matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + IGRAPH_ASSERT(igraph_sparsemat_droptol(&spmat, 10) == IGRAPH_EINVAL); + igraph_sparsemat_destroy(&spmat); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sparsemat_droptol.out b/tests/unit/igraph_sparsemat_droptol.out new file mode 100644 index 0000000..40fae2f --- /dev/null +++ b/tests/unit/igraph_sparsemat_droptol.out @@ -0,0 +1,16 @@ +0x0 matrix. +3x3 matrix. +Remove values within distance 5 from zero: +col 0: locations 0 to -1 +col 1: locations 0 to 0 +1 : 6 +col 2: locations 1 to 2 +2 : 7 +1 : -14 +col 3: locations 3 to 2 +Remove values within distance 20 from zero: +col 0: locations 0 to -1 +col 1: locations 0 to -1 +col 2: locations 0 to -1 +col 3: locations 0 to -1 +uncompressed matrix. diff --git a/tests/unit/igraph_sparsemat_fkeep.c b/tests/unit/igraph_sparsemat_fkeep.c new file mode 100644 index 0000000..41d8d92 --- /dev/null +++ b/tests/unit/igraph_sparsemat_fkeep.c @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +igraph_int_t fkeep_none(igraph_int_t row, igraph_int_t col, igraph_real_t value, void *other) { + IGRAPH_UNUSED(row); + IGRAPH_UNUSED(col); + IGRAPH_UNUSED(value); + IGRAPH_UNUSED(other); + return 0; +} + +igraph_int_t fkeep(igraph_int_t row, igraph_int_t col, igraph_real_t value, void *other) { + if (row == 0 || col == 1 || value > *(int*)other) { + return 0; + } + return 1; +} + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + int a = 0; + + printf("0x0 matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_fkeep(&spmat_comp, &fkeep, &a) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + printf("3x3 matrix.\n"); + igraph_sparsemat_init(&spmat, 3, 3, /*nzmax*/7); + igraph_sparsemat_entry(&spmat, 0, 0, 5); + igraph_sparsemat_entry(&spmat, 1, 1, 6); + igraph_sparsemat_entry(&spmat, 2, 2, 7); + igraph_sparsemat_entry(&spmat, 3, 0, 1); + igraph_sparsemat_entry(&spmat, 0, 3, 2); + igraph_sparsemat_entry(&spmat, 2, 1, 3); + igraph_sparsemat_entry(&spmat, 1, 2, 4); + igraph_sparsemat_compress(&spmat, &spmat_comp); + a = 6; + printf("Remove row 0, column 1, and values above 6:\n"); + IGRAPH_ASSERT(igraph_sparsemat_fkeep(&spmat_comp, &fkeep, &a) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + printf("Remove everything:\n"); + IGRAPH_ASSERT(igraph_sparsemat_fkeep(&spmat_comp, &fkeep_none, &a) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("uncompressed matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + IGRAPH_ASSERT(igraph_sparsemat_fkeep(&spmat, &fkeep, &a) == IGRAPH_EINVAL); + igraph_sparsemat_destroy(&spmat); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sparsemat_fkeep.out b/tests/unit/igraph_sparsemat_fkeep.out new file mode 100644 index 0000000..c055d2a --- /dev/null +++ b/tests/unit/igraph_sparsemat_fkeep.out @@ -0,0 +1,15 @@ +0x0 matrix. +3x3 matrix. +Remove row 0, column 1, and values above 6: +col 0: locations 0 to 0 +3 : 1 +col 1: locations 1 to 0 +col 2: locations 1 to 1 +1 : 4 +col 3: locations 2 to 1 +Remove everything: +col 0: locations 0 to -1 +col 1: locations 0 to -1 +col 2: locations 0 to -1 +col 3: locations 0 to -1 +uncompressed matrix. diff --git a/tests/unit/igraph_sparsemat_getelements_sorted.c b/tests/unit/igraph_sparsemat_getelements_sorted.c new file mode 100644 index 0000000..a07dc92 --- /dev/null +++ b/tests/unit/igraph_sparsemat_getelements_sorted.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_res_i_j(igraph_vector_t *result, igraph_vector_int_t *i, igraph_vector_int_t *j) { + print_vector(result); + print_vector_int(i); + print_vector_int(j); +} + +void destroy_all(igraph_vector_t *result, igraph_vector_int_t *i, igraph_vector_int_t *j, igraph_sparsemat_t *spmat, igraph_sparsemat_t *spmat_comp) { + igraph_vector_destroy(result); + igraph_vector_int_destroy(i); + igraph_vector_int_destroy(j); + igraph_sparsemat_destroy(spmat); + igraph_sparsemat_destroy(spmat_comp); +} + +void init_all(igraph_vector_t *result, igraph_vector_int_t *i, igraph_vector_int_t *j, igraph_sparsemat_t *spmat) { + igraph_vector_init(result, 0); + igraph_vector_int_init(i, 0); + igraph_vector_int_init(j, 0); + igraph_sparsemat_init(spmat, 0, 0, 0); +} + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + igraph_vector_t result; + igraph_vector_int_t i, j; + int k, l; + int size = 3; + + printf("0x0 matrix\n"); + init_all(&result, &i, &j, &spmat); + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_getelements_sorted(&spmat, &i, &j, &result) == IGRAPH_SUCCESS); + printf("triplet:\n"); + print_res_i_j(&result, &i, &j); + IGRAPH_ASSERT(igraph_sparsemat_getelements_sorted(&spmat_comp, &i, &j, &result) == IGRAPH_SUCCESS); + printf("compressed:\n"); + print_res_i_j(&result, &i, &j); + destroy_all(&result, &i, &j, &spmat, &spmat_comp); + + printf("\n3x3 matrix\n"); + init_all(&result, &i, &j, &spmat); + /* make sure to fill spmat in an order that is not _already_ sorted by + * row indices first */ + for (k = 0; k < size; k += 2) { + for (l = 0; l < size; l ++) { + igraph_sparsemat_entry(&spmat, k, l, 100); + } + } + for (k = 0; k < size; k += 2) { + for (l = 0; l < size; l ++) { + igraph_sparsemat_entry(&spmat, k, l, k * size + l); + } + } + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_getelements_sorted(&spmat, &i, &j, &result) == IGRAPH_SUCCESS); + printf("triplet:\n"); + print_res_i_j(&result, &i, &j); + IGRAPH_ASSERT(igraph_sparsemat_getelements_sorted(&spmat_comp, &i, &j, &result) == IGRAPH_SUCCESS); + printf("compressed:\n"); + print_res_i_j(&result, &i, &j); + + destroy_all(&result, &i, &j, &spmat, &spmat_comp); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sparsemat_getelements_sorted.out b/tests/unit/igraph_sparsemat_getelements_sorted.out new file mode 100644 index 0000000..ce3cacc --- /dev/null +++ b/tests/unit/igraph_sparsemat_getelements_sorted.out @@ -0,0 +1,19 @@ +0x0 matrix +triplet: +( ) +( ) +( ) +compressed: +( ) +( ) +( 0 ) + +3x3 matrix +triplet: +( 100 0 100 6 100 1 100 7 100 2 100 8 ) +( 0 0 2 2 0 0 2 2 0 0 2 2 ) +( 0 0 0 0 1 1 1 1 2 2 2 2 ) +compressed: +( 100 0 100 6 100 1 100 7 100 2 100 8 ) +( 0 0 2 2 0 0 2 2 0 0 2 2 ) +( 0 4 8 12 ) diff --git a/tests/unit/igraph_sparsemat_is_symmetric.c b/tests/unit/igraph_sparsemat_is_symmetric.c new file mode 100644 index 0000000..1dccd05 --- /dev/null +++ b/tests/unit/igraph_sparsemat_is_symmetric.c @@ -0,0 +1,71 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +#define DIM 10 + +#define INT(a) (igraph_rng_get_integer(igraph_rng_default(), 0, (a))) + +int main(void) { + int runs = 100; + const int noelements = 20; + igraph_sparsemat_t A; + igraph_bool_t result; + int i; + + igraph_rng_seed(igraph_rng_default(), 42); + + for (; runs > 0; runs--) { + + igraph_sparsemat_init(&A, DIM, DIM, noelements * 2); + for (i = 0; i < noelements; i++) { + int row = INT(DIM - 1); + int col = INT(DIM - 1); + int val = INT(100); + igraph_sparsemat_entry(&A, row, col, val); + igraph_sparsemat_entry(&A, col, row, val); + } + igraph_sparsemat_is_symmetric(&A, &result); + if (!result) { + return 1; + } + igraph_sparsemat_destroy(&A); + + igraph_sparsemat_init(&A, DIM, DIM, noelements); + for (i = 0; i < noelements; i++) { + igraph_sparsemat_entry(&A, INT(DIM - 1), INT(DIM - 1), INT(100)); + } + igraph_sparsemat_is_symmetric(&A, &result); + if (result) { + return 2; + } + igraph_sparsemat_destroy(&A); + + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat_iterator_idx.c b/tests/unit/igraph_sparsemat_iterator_idx.c new file mode 100644 index 0000000..7cbd241 --- /dev/null +++ b/tests/unit/igraph_sparsemat_iterator_idx.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + igraph_sparsemat_iterator_t it; + igraph_sparsemat_iterator_t it_comp; + + printf("0x0 matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + igraph_sparsemat_compress(&spmat, &spmat_comp); + igraph_sparsemat_iterator_init(&it, &spmat); + igraph_sparsemat_iterator_init(&it_comp, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_iterator_idx(&it) == 0); + IGRAPH_ASSERT(igraph_sparsemat_iterator_idx(&it_comp) == 0); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + printf("3x3 matrix.\n"); + igraph_sparsemat_init(&spmat, 3, 3, /*nzmax*/0); + igraph_sparsemat_entry(&spmat, 0, 0, 5); + igraph_sparsemat_entry(&spmat, 3, 3, 6); + igraph_sparsemat_entry(&spmat, 3, 3, 6); + igraph_sparsemat_compress(&spmat, &spmat_comp); + igraph_sparsemat_iterator_init(&it, &spmat); + igraph_sparsemat_iterator_init(&it_comp, &spmat_comp); + igraph_sparsemat_iterator_next(&it); + igraph_sparsemat_iterator_next(&it); + igraph_sparsemat_iterator_next(&it_comp); + IGRAPH_ASSERT(igraph_sparsemat_iterator_idx(&it) == 2); + IGRAPH_ASSERT(igraph_sparsemat_iterator_idx(&it_comp) == 1); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sparsemat_minmax.c b/tests/unit/igraph_sparsemat_minmax.c new file mode 100644 index 0000000..22ff370 --- /dev/null +++ b/tests/unit/igraph_sparsemat_minmax.c @@ -0,0 +1,247 @@ +/* + igraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +#define N 10 +#define M 20 +#define NZ 50 + +#define MIN 0 +#define MAX 10 + +typedef igraph_error_t fun(igraph_sparsemat_t *A, igraph_vector_t *res); + +int doit(int which) { + + igraph_int_t i; + igraph_sparsemat_t A, A2; + igraph_vector_t vec; + fun *colfun, *rowfun; + + if (which == MIN) { + colfun = igraph_sparsemat_colmins; + rowfun = igraph_sparsemat_rowmins; + } else { + colfun = igraph_sparsemat_colmaxs; + rowfun = igraph_sparsemat_rowmaxs; + } + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Triplet diagonal matrix */ + + printf("Triplet diagonal matrix\n"); + igraph_vector_init(&vec, N); + for (i = 0; i < N; i++) { + VECTOR(vec)[i] = i; + } + igraph_sparsemat_init_diag(&A, /*nzmax=*/ N, /*values=*/ &vec, /*compress=*/ 0); + + igraph_vector_null(&vec); + rowfun(&A, &vec); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 1; + } + } + + igraph_vector_null(&vec); + colfun(&A, &vec); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 2; + } + } + + igraph_vector_destroy(&vec); + igraph_sparsemat_destroy(&A); + + /* Compressed diagonal matrix */ + + printf("Compressed diagonal matrix\n"); + igraph_vector_init(&vec, N); + for (i = 0; i < N; i++) { + VECTOR(vec)[i] = i; + } + igraph_sparsemat_init_diag(&A, /*nzmax=*/ N, /*values=*/ &vec, /*compress=*/ 1); + + igraph_vector_null(&vec); + rowfun(&A, &vec); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 3; + } + } + + igraph_vector_null(&vec); + colfun(&A, &vec); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 4; + } + } + + igraph_vector_destroy(&vec); + igraph_sparsemat_destroy(&A); + + + /* Random triplet matrix */ + + printf("Random triplet matrix\n"); + igraph_sparsemat_init(&A, /*rows=*/ N, /*cols=*/ M, /*nzmax=*/ NZ + 5); + for (i = 0; i < NZ; i++) { + int r = igraph_rng_get_integer(igraph_rng_default(), 0, N - 1); + int c = igraph_rng_get_integer(igraph_rng_default(), 0, M - 1); + igraph_real_t x = igraph_rng_get_integer(igraph_rng_default(), + -10, 10); + IGRAPH_ASSERT(x >= -10 && x <= 10); + igraph_sparsemat_entry(&A, r, c, x); + } + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 0); + colfun(&A, &vec); + igraph_vector_print(&vec); + + igraph_vector_null(&vec); + rowfun(&A, &vec); + igraph_vector_print(&vec); + + /* Random compresssed matrix */ + + printf("Random compressed matrix\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + colfun(&A2, &vec); + igraph_vector_print(&vec); + + igraph_vector_null(&vec); + rowfun(&A2, &vec); + igraph_vector_print(&vec); + + igraph_vector_destroy(&vec); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + /* Matrix with zero rows, triplet */ + + printf("Matrix with zero rows, triplet\n"); + igraph_sparsemat_init(&A, /*rows=*/ 0, /*cols=*/ M, /*nzmax=*/ NZ); + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 5); + rowfun(&A, &vec); + if (igraph_vector_size(&vec) != 0) { + return which + 5; + } + + igraph_vector_null(&vec); + colfun(&A, &vec); + igraph_vector_print(&vec); + + /* Matrix with zero rows, compressed */ + + printf("Matrix with zero rows, compressed\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + rowfun(&A, &vec); + if (igraph_vector_size(&vec) != 0) { + return which + 6; + } + + igraph_vector_null(&vec); + colfun(&A, &vec); + igraph_vector_print(&vec); + + igraph_vector_destroy(&vec); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + /* Matrix with zero columns, triplet */ + + printf("Matrix with zero columns, triplet\n"); + igraph_sparsemat_init(&A, /*rows=*/ N, /*cols=*/ 0, /*nzmax=*/ NZ); + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 5); + colfun(&A, &vec); + if (igraph_vector_size(&vec) != 0) { + return which + 7; + } + + igraph_vector_null(&vec); + rowfun(&A, &vec); + igraph_vector_print(&vec); + + /* Matrix with zero columns, compressed */ + + printf("Matrix with zero columns, compressed\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + colfun(&A, &vec); + if (igraph_vector_size(&vec) != 0) { + return which + 8; + } + + igraph_vector_null(&vec); + rowfun(&A, &vec); + igraph_vector_print(&vec); + + igraph_vector_destroy(&vec); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + return 0; +} + +int main(void) { + int res; + + res = doit(/*which=*/ MIN); + if (res) { + return res; + } + + VERIFY_FINALLY_STACK(); + + res = doit(/*which=*/ MAX); + if (res) { + return res; + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat_minmax.out b/tests/unit/igraph_sparsemat_minmax.out new file mode 100644 index 0000000..7942811 --- /dev/null +++ b/tests/unit/igraph_sparsemat_minmax.out @@ -0,0 +1,32 @@ +Triplet diagonal matrix +Compressed diagonal matrix +Random triplet matrix +-8 -5 3 -5 -3 7 -10 -4 -2 1 -10 -6 -10 -5 -4 -5 -6 -9 10 -8 +10 -10 -6 -4 -5 9 -10 -10 -9 -8 +Random compressed matrix +-8 -5 3 -5 -3 7 -10 -4 -1 1 -7 -6 -10 -5 -4 -5 -6 -11 10 -8 +10 -10 -6 -4 -5 9 -7 -10 -11 -8 +Matrix with zero rows, triplet +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +Matrix with zero rows, compressed +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +Matrix with zero columns, triplet +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +Matrix with zero columns, compressed +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +Triplet diagonal matrix +Compressed diagonal matrix +Random triplet matrix +8 5 -3 5 3 -7 10 4 2 -1 10 6 10 5 4 5 6 9 -10 8 +-10 10 6 4 5 -9 10 10 9 8 +Random compressed matrix +8 5 -3 5 3 -7 10 4 1 -1 7 6 10 5 4 5 6 11 -10 8 +-10 10 6 4 5 -9 7 10 11 8 +Matrix with zero rows, triplet +-Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf +Matrix with zero rows, compressed +-Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf +Matrix with zero columns, triplet +-Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf +Matrix with zero columns, compressed +-Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf diff --git a/tests/unit/igraph_sparsemat_nonzero_storage.c b/tests/unit/igraph_sparsemat_nonzero_storage.c new file mode 100644 index 0000000..4066a8d --- /dev/null +++ b/tests/unit/igraph_sparsemat_nonzero_storage.c @@ -0,0 +1,69 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + int i, j; + int size = 3; + + printf("0x0 matrix\n"); + igraph_sparsemat_init(&spmat, 0, 0, 0); + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat) == 0); + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat_comp) == 0); + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + printf("3x3 compressed matrix with duplicate values that add up to zero.\n"); + igraph_sparsemat_init(&spmat, size, size, 7); + for (i = 0; i < size; i++) { + for (j = 0; j < size; j++) { + igraph_sparsemat_entry(&spmat, i, j, 5); + igraph_sparsemat_entry(&spmat, i, j, -5); + + /* This checks if there's two entries for every loop. */ + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat) == (i * size + j + 1) * 2); + igraph_sparsemat_compress(&spmat, &spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat_comp) == (i * size + j + 1) * 2); + + igraph_sparsemat_destroy(&spmat_comp); + } + } + printf("Adding one entry to work around some broken error handling.\n"); + igraph_sparsemat_entry(&spmat, 0, 0, 5); + igraph_sparsemat_compress(&spmat, &spmat_comp); + + printf("Removing duplicates should leave us with one entry in each position.\n"); + igraph_sparsemat_dupl(&spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat_comp) == (size * size)); + + printf("Removing all zeros should leave us with only one entry.\n"); + igraph_sparsemat_dropzeros(&spmat_comp); + IGRAPH_ASSERT(igraph_sparsemat_nonzero_storage(&spmat_comp) == 1); + + igraph_sparsemat_destroy(&spmat); + igraph_sparsemat_destroy(&spmat_comp); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_sparsemat_normalize.c b/tests/unit/igraph_sparsemat_normalize.c new file mode 100644 index 0000000..ed11b2b --- /dev/null +++ b/tests/unit/igraph_sparsemat_normalize.c @@ -0,0 +1,106 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void create_test_matrix( + igraph_sparsemat_t* spmat, + igraph_sparsemat_t* spmat_comp, + igraph_bool_t use_zero_col_row +) { + igraph_sparsemat_init(spmat, 3, 3, /*nzmax*/7); + igraph_sparsemat_entry(spmat, 0, 0, 6); + igraph_sparsemat_entry(spmat, 2, 2, 7); + + if (!use_zero_col_row) { + igraph_sparsemat_entry(spmat, 0, 1, 2); + igraph_sparsemat_entry(spmat, 1, 0, 4); + igraph_sparsemat_entry(spmat, 1, 1, 6); + igraph_sparsemat_entry(spmat, 2, 1, 2); + igraph_sparsemat_entry(spmat, 1, 2, -14); + } + + igraph_sparsemat_compress(spmat, spmat_comp); +} + +int main(void) { + igraph_sparsemat_t spmat; + igraph_sparsemat_t spmat_comp; + + printf("0x0 matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + igraph_sparsemat_compress(&spmat, &spmat_comp); + + IGRAPH_ASSERT(igraph_sparsemat_normalize_rows(&spmat_comp, /* allow_zero = */ 1) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_sparsemat_normalize_cols(&spmat_comp, /* allow_zero = */ 1) == IGRAPH_SUCCESS); + + /* Normalization should succeed for an empty matrix even if allow_zero = 0 + * because technically there is no 0/0 division anywhere */ + IGRAPH_ASSERT(igraph_sparsemat_normalize_rows(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_sparsemat_normalize_cols(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_SUCCESS); + + igraph_sparsemat_destroy(&spmat_comp); + igraph_sparsemat_destroy(&spmat); + + printf("\n"); + printf("3x3 matrix without zero rows and columns, row-wise normalization.\n"); + create_test_matrix(&spmat, &spmat_comp, /* use_zero_col_row = */ 0); + IGRAPH_ASSERT(igraph_sparsemat_normalize_rows(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat_comp); + igraph_sparsemat_destroy(&spmat); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("\n"); + printf("3x3 matrix without zero rows and columns, column-wise normalization.\n"); + create_test_matrix(&spmat, &spmat_comp, /* use_zero_col_row = */ 0); + IGRAPH_ASSERT(igraph_sparsemat_normalize_cols(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat_comp); + igraph_sparsemat_destroy(&spmat); + + printf("\n"); + printf("3x3 matrix with zero rows and columns, row-wise normalization.\n"); + create_test_matrix(&spmat, &spmat_comp, /* use_zero_col_row = */ 1); + IGRAPH_ASSERT(igraph_sparsemat_normalize_rows(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_EINVAL); + IGRAPH_ASSERT(igraph_sparsemat_normalize_rows(&spmat_comp, /* allow_zero = */ 1) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat_comp); + igraph_sparsemat_destroy(&spmat); + + printf("\n"); + printf("3x3 matrix with zero rows and columns, column-wise normalization.\n"); + create_test_matrix(&spmat, &spmat_comp, /* use_zero_col_row = */ 1); + IGRAPH_ASSERT(igraph_sparsemat_normalize_cols(&spmat_comp, /* allow_zero = */ 0) == IGRAPH_EINVAL); + IGRAPH_ASSERT(igraph_sparsemat_normalize_cols(&spmat_comp, /* allow_zero = */ 1) == IGRAPH_SUCCESS); + igraph_sparsemat_print(&spmat_comp, stdout); + igraph_sparsemat_destroy(&spmat_comp); + igraph_sparsemat_destroy(&spmat); + + printf("\n"); + printf("uncompressed matrix.\n"); + igraph_sparsemat_init(&spmat, 0, 0, /*nzmax*/0); + IGRAPH_ASSERT(igraph_sparsemat_droptol(&spmat, 10) == IGRAPH_EINVAL); + igraph_sparsemat_destroy(&spmat); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat_normalize.out b/tests/unit/igraph_sparsemat_normalize.out new file mode 100644 index 0000000..05b3195 --- /dev/null +++ b/tests/unit/igraph_sparsemat_normalize.out @@ -0,0 +1,41 @@ +0x0 matrix. + +3x3 matrix without zero rows and columns, row-wise normalization. +col 0: locations 0 to 1 +0 : 0.75 +1 : -1 +col 1: locations 2 to 4 +0 : 0.25 +1 : -1.5 +2 : 0.222222 +col 2: locations 5 to 6 +2 : 0.777778 +1 : 3.5 + +3x3 matrix without zero rows and columns, column-wise normalization. +col 0: locations 0 to 1 +0 : 0.6 +1 : 0.4 +col 1: locations 2 to 4 +0 : 0.2 +1 : 0.6 +2 : 0.2 +col 2: locations 5 to 6 +2 : -1 +1 : 2 + +3x3 matrix with zero rows and columns, row-wise normalization. +col 0: locations 0 to 0 +0 : 1 +col 1: locations 1 to 0 +col 2: locations 1 to 1 +2 : 1 + +3x3 matrix with zero rows and columns, column-wise normalization. +col 0: locations 0 to 0 +0 : 1 +col 1: locations 1 to 0 +col 2: locations 1 to 1 +2 : 1 + +uncompressed matrix. diff --git a/tests/unit/igraph_sparsemat_view.out b/tests/unit/igraph_sparsemat_view.out new file mode 100644 index 0000000..edff804 --- /dev/null +++ b/tests/unit/igraph_sparsemat_view.out @@ -0,0 +1,6 @@ +Empty sparsemat. +[ 0-by-0 ] +3x2 sparsemat: +[ 0 5 + 1 0 + 0 2 ] diff --git a/tests/unit/igraph_sparsemat_which_minmax.c b/tests/unit/igraph_sparsemat_which_minmax.c new file mode 100644 index 0000000..03ac6d6 --- /dev/null +++ b/tests/unit/igraph_sparsemat_which_minmax.c @@ -0,0 +1,282 @@ +/* + igraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +#define N 10 +#define M 20 +#define NZ 50 + +#define MIN 0 +#define MAX 10 + +typedef igraph_error_t fun(igraph_sparsemat_t *A, igraph_vector_t *res, + igraph_vector_int_t *pos); + +int doit(int which) { + + int i; + igraph_sparsemat_t A, A2; + igraph_vector_t vec; + igraph_vector_int_t pos; + fun *colfun, *rowfun; + + if (which == MIN) { + colfun = igraph_sparsemat_which_min_cols; + rowfun = igraph_sparsemat_which_min_rows; + } else { + /* colfun = */ /* TODO */ + /* rowfun = */ /* TODO */ + } + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Triplet diagonal matrix */ + + printf("Triplet diagonal matrix\n"); + igraph_vector_init(&vec, N); + igraph_vector_int_init(&pos, N); + for (i = 0; i < N; i++) { + VECTOR(vec)[i] = i; + } + igraph_sparsemat_init_diag(&A, /*nzmax=*/ N, /*values=*/ &vec, /*compress=*/ 0); + + igraph_vector_null(&vec); + igraph_vector_int_null(&pos); + rowfun(&A, &vec, &pos); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 1; + } + } + for (i = 0; i < N; i++) { + if (VECTOR(pos)[i] != i) { + return which + 2; + } + } + + igraph_vector_null(&vec); + colfun(&A, &vec, &pos); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 3; + } + } + for (i = 0; i < N; i++) { + if (VECTOR(pos)[i] != i) { + return which + 4; + } + } + + igraph_vector_destroy(&vec); + igraph_vector_int_destroy(&pos); + igraph_sparsemat_destroy(&A); + + /* Compressed diagonal matrix */ + + igraph_vector_init(&vec, N); + igraph_vector_int_init(&pos, N); + for (i = 0; i < N; i++) { + VECTOR(vec)[i] = i; + } + igraph_sparsemat_init_diag(&A, /*nzmax=*/ N, /*values=*/ &vec, /*compress=*/ 1); + + igraph_vector_null(&vec); + rowfun(&A, &vec, &pos); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 5; + } + } + for (i = 0; i < N; i++) { + if (VECTOR(pos)[i] != i) { + return which + 6; + } + } + + igraph_vector_null(&vec); + colfun(&A, &vec, &pos); + for (i = 0; i < N; i++) { + if (VECTOR(vec)[i] != i) { + return which + 7; + } + } + for (i = 0; i < N; i++) { + if (VECTOR(pos)[i] != i) { + return which + 8; + } + } + + igraph_vector_destroy(&vec); + igraph_vector_int_destroy(&pos); + igraph_sparsemat_destroy(&A); + + + /* Random triplet matrix */ + + printf("Random triplet matrix\n"); + igraph_sparsemat_init(&A, /*rows=*/ N, /*cols=*/ M, /*nzmax=*/ NZ + 5); + for (i = 0; i < NZ; i++) { + igraph_int_t r = RNG_INTEGER(0, N-1); + igraph_int_t c = RNG_INTEGER(0, M-1); + igraph_real_t x = RNG_INTEGER(-10, 10); + IGRAPH_ASSERT(x >= -10 && x <= 10); + igraph_sparsemat_entry(&A, r, c, x); + } + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 0); + igraph_vector_int_init(&pos, 0); + colfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + igraph_vector_null(&vec); + rowfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + /* Random compresssed matrix */ + + printf("Random compressed matrix\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + colfun(&A2, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + igraph_vector_null(&vec); + rowfun(&A2, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + igraph_vector_destroy(&vec); + igraph_vector_int_destroy(&pos); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + /* Matrix with zero rows, triplet */ + + printf("Matrix with zero rows, triplet\n"); + igraph_sparsemat_init(&A, /*rows=*/ 0, /*cols=*/ M, /*nzmax=*/ NZ); + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 5); + igraph_vector_int_init(&pos, 5); + rowfun(&A, &vec, &pos); + if (igraph_vector_size(&vec) != 0) { + return which + 5; + } + + igraph_vector_null(&vec); + colfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + /* Matrix with zero rows, compressed */ + + printf("Matrix with zero rows, compressed\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + rowfun(&A, &vec, &pos); + if (igraph_vector_size(&vec) != 0) { + return which + 6; + } + + igraph_vector_null(&vec); + colfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + igraph_vector_destroy(&vec); + igraph_vector_int_destroy(&pos); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + /* Matrix with zero columns, triplet */ + + printf("Matrix with zero columns, triplet\n"); + igraph_sparsemat_init(&A, /*rows=*/ N, /*cols=*/ 0, /*nzmax=*/ NZ); + if (which == MAX) { + igraph_sparsemat_scale(&A, -1.0); + } + + igraph_vector_init(&vec, 5); + igraph_vector_int_init(&pos, 5); + colfun(&A, &vec, &pos); + if (igraph_vector_size(&vec) != 0) { + return which + 7; + } + + igraph_vector_null(&vec); + rowfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + /* Matrix with zero columns, compressed */ + + printf("Matrix with zero columns, compressed\n"); + igraph_sparsemat_compress(&A, &A2); + + igraph_vector_null(&vec); + colfun(&A, &vec, &pos); + if (igraph_vector_size(&vec) != 0) { + return which + 8; + } + + igraph_vector_null(&vec); + rowfun(&A, &vec, &pos); + igraph_vector_print(&vec); + igraph_vector_int_print(&pos); + + igraph_vector_destroy(&vec); + igraph_vector_int_destroy(&pos); + igraph_sparsemat_destroy(&A); + igraph_sparsemat_destroy(&A2); + + return 0; +} + +int main(void) { + int res; + + res = doit(/*which=*/ MIN); + if (res) { + return res; + } + + /* res = doit(/\*which=*\/ MAX); */ + /* if (res) { return res; } */ + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_sparsemat_which_minmax.out b/tests/unit/igraph_sparsemat_which_minmax.out new file mode 100644 index 0000000..e5567be --- /dev/null +++ b/tests/unit/igraph_sparsemat_which_minmax.out @@ -0,0 +1,23 @@ +Triplet diagonal matrix +Random triplet matrix +-8 -5 3 -5 -3 7 -10 -4 -2 1 -10 -6 -10 -5 -4 -5 -6 -9 10 -8 +9 2 8 9 6 8 7 6 1 3 6 2 1 4 3 6 8 8 3 8 +10 -10 -6 -4 -5 9 -10 -10 -9 -8 +14 12 11 14 13 17 10 6 17 0 +Random compressed matrix +-8 -5 3 -5 -3 7 -10 -4 -1 1 -7 -6 -10 -5 -4 -5 -6 -11 10 -8 +9 2 8 9 6 8 7 6 1 3 6 2 1 4 3 6 8 8 3 8 +10 -10 -6 -4 -5 9 -7 -10 -11 -8 +14 12 11 14 13 17 10 6 17 0 +Matrix with zero rows, triplet +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Matrix with zero rows, compressed +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Matrix with zero columns, triplet +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +0 0 0 0 0 0 0 0 0 0 +Matrix with zero columns, compressed +Inf Inf Inf Inf Inf Inf Inf Inf Inf Inf +0 0 0 0 0 0 0 0 0 0 diff --git a/tests/unit/igraph_spatial_edge_lengths.c b/tests/unit/igraph_spatial_edge_lengths.c new file mode 100644 index 0000000..83d4fbd --- /dev/null +++ b/tests/unit/igraph_spatial_edge_lengths.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_matrix_t points; + igraph_vector_t lengths; + + igraph_matrix_init(&points, 0, 0); + igraph_vector_init(&lengths, 0); + + printf("K_4, 2D square\n"); + igraph_full(&graph, 4, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + igraph_layout_grid(&graph, &points, 2); + printf("L2: "); + igraph_spatial_edge_lengths(&graph, &lengths, &points, IGRAPH_METRIC_L2); + print_vector(&lengths); + printf("L1: "); + igraph_spatial_edge_lengths(&graph, &lengths, &points, IGRAPH_METRIC_L1); + print_vector(&lengths); + + igraph_matrix_resize(&points, 3, 2); + CHECK_ERROR(igraph_spatial_edge_lengths(&graph, &lengths, &points, IGRAPH_METRIC_L2), IGRAPH_EINVAL); + + igraph_destroy(&graph); + + /* 0-by-0 matrix must be accepted for the null graph only. */ + printf("\nNull graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_matrix_resize(&points, 0, 0); + printf("L2: "); + igraph_spatial_edge_lengths(&graph, &lengths, &points, IGRAPH_METRIC_L2); + print_vector(&lengths); + printf("L1: "); + igraph_spatial_edge_lengths(&graph, &lengths, &points, IGRAPH_METRIC_L1); + print_vector(&lengths); + igraph_destroy(&graph); + + igraph_vector_destroy(&lengths); + igraph_matrix_destroy(&points); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_spatial_edge_lengths.out b/tests/unit/igraph_spatial_edge_lengths.out new file mode 100644 index 0000000..c2afd2d --- /dev/null +++ b/tests/unit/igraph_spatial_edge_lengths.out @@ -0,0 +1,7 @@ +K_4, 2D square +L2: ( 1 1 1.41421 1.41421 1 1 ) +L1: ( 1 1 2 2 1 1 ) + +Null graph +L2: ( ) +L1: ( ) diff --git a/tests/unit/igraph_split_join_distance.c b/tests/unit/igraph_split_join_distance.c new file mode 100644 index 0000000..5c60986 --- /dev/null +++ b/tests/unit/igraph_split_join_distance.c @@ -0,0 +1,72 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_int_t comm1, comm2; + igraph_int_t distance12, distance21; + + printf("No vertices:\n"); + igraph_vector_int_init_int(&comm1, 0); + igraph_vector_int_init_int(&comm2, 0); + IGRAPH_ASSERT(igraph_split_join_distance(&comm1, &comm2, &distance12, &distance21) == IGRAPH_SUCCESS); + printf("%" IGRAPH_PRId ", %" IGRAPH_PRId "\n", distance12, distance21); + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("Comparing 5 separate vertices and one 5-element cluster:\n"); + igraph_vector_int_init_int(&comm1, 5, 0, 1, 2, 3, 4); + igraph_vector_int_init_int(&comm2, 5, 0, 0, 0, 0, 0); + IGRAPH_ASSERT(igraph_split_join_distance(&comm1, &comm2, &distance12, &distance21) == IGRAPH_SUCCESS); + printf("%" IGRAPH_PRId ", %" IGRAPH_PRId "\n", distance12, distance21); + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("Comparing ((6, 1), (2,4), (3,5,0)) with ((2), (6,0,3), (4,5), (1)):\n"); + igraph_vector_int_init_int(&comm1, 7, 2, 0, 1, 2, 1, 2, 0); + igraph_vector_int_init_int(&comm2, 7, 1, 3, 0, 1, 2, 2, 1); + IGRAPH_ASSERT(igraph_split_join_distance(&comm1, &comm2, &distance12, &distance21) == IGRAPH_SUCCESS); + printf("%" IGRAPH_PRId ", %" IGRAPH_PRId "\n", distance12, distance21); + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + printf("Comparing ((0,1), (), 2) with ((0), (), (1,2))\n"); + igraph_vector_int_init_int(&comm1, 3, 0, 0, 2); + igraph_vector_int_init_int(&comm2, 3, 0, 2, 2); + IGRAPH_ASSERT(igraph_split_join_distance(&comm1, &comm2, &distance12, &distance21) == IGRAPH_SUCCESS); + printf("%" IGRAPH_PRId ", %" IGRAPH_PRId "\n", distance12, distance21); + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + VERIFY_FINALLY_STACK(); + + printf("\nExpected to fail nicely:\n\n"); + + printf("Differently sized clusterings\n"); + igraph_vector_int_init_int(&comm1, 3, 0, 1, 2); + igraph_vector_int_init_int(&comm2, 5, 0, 0, 0, 0, 0); + CHECK_ERROR(igraph_split_join_distance(&comm1, &comm2, &distance12, &distance21), IGRAPH_EINVAL); + igraph_vector_int_destroy(&comm1); + igraph_vector_int_destroy(&comm2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_split_join_distance.out b/tests/unit/igraph_split_join_distance.out new file mode 100644 index 0000000..ee1327e --- /dev/null +++ b/tests/unit/igraph_split_join_distance.out @@ -0,0 +1,12 @@ +No vertices: +0, 0 +Comparing 5 separate vertices and one 5-element cluster: +0, 4 +Comparing ((6, 1), (2,4), (3,5,0)) with ((2), (6,0,3), (4,5), (1)): +3, 2 +Comparing ((0,1), (), 2) with ((0), (), (1,2)) +1, 1 + +Expected to fail nicely: + +Differently sized clusterings diff --git a/tests/unit/igraph_square_lattice.c b/tests/unit/igraph_square_lattice.c new file mode 100644 index 0000000..d1dc4a5 --- /dev/null +++ b/tests/unit/igraph_square_lattice.c @@ -0,0 +1,231 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +typedef struct { + int dim; + int m; + int nei; + igraph_bool_t directed, mutual, periodic; + igraph_int_t *dimedges; +} lat_test_t; + +#define LAT_TEST(id, d, m, ne, di, mu, ci, ...) \ + igraph_int_t lat_ ## id ## _edges[] = { __VA_ARGS__ } ; \ + lat_test_t lat_ ## id = { d, m, ne, di, mu, ci, lat_ ## id ## _edges } + +/*----------------d--m--ne-di-mu-ci-dimedges------------------------*/ +LAT_TEST(u_0, 0, 0, 1, 0, 0, 0, -1 ); +LAT_TEST(u_01, 1, 0, 1, 0, 0, 0, 0 ); +LAT_TEST(u_02, 2, 0, 1, 0, 0, 0, 0, 1 ); +LAT_TEST(u_03, 2, 0, 1, 0, 0, 0, 1, 0 ); + +LAT_TEST(d_0, 0, 0, 1, 1, 0, 0, -1 ); +LAT_TEST(d_01, 1, 0, 1, 1, 0, 0, 0 ); +LAT_TEST(d_02, 2, 0, 1, 1, 0, 0, 0, 1 ); +LAT_TEST(d_03, 2, 0, 1, 1, 0, 0, 1, 0 ); + +LAT_TEST(u_1, 1, 0, 1, 0, 0, 0, 1 ); +LAT_TEST(u_1x1, 2, 0, 1, 0, 0, 0, 1, 1 ); +LAT_TEST(u_2, 1, 1, 1, 0, 0, 0, 2, 0, 1 ); +LAT_TEST(u_2x1, 2, 1, 1, 0, 0, 0, 2, 1, 0, 1 ); +LAT_TEST(u_2x2, 2, 4, 1, 0, 0, 0, 2, 2, 0, 1, 0, 2, 1, 3, 2, 3 ); + +LAT_TEST(uc_1, 1, 0, 1, 0, 0, 1, 1 ); +LAT_TEST(uc_1x1, 2, 0, 1, 0, 0, 1, 1, 1 ); +LAT_TEST(uc_2, 1, 1, 1, 0, 0, 1, 2, 0, 1 ); +LAT_TEST(uc_2x1, 2, 1, 1, 0, 0, 1, 2, 1, 0, 1 ); +LAT_TEST(uc_2x2, 2, 4, 1, 0, 0, 1, 2, 2, 0, 1, 0, 2, 1, 3, 2, 3 ); + +LAT_TEST(dc_1, 1, 0, 1, 1, 0, 1, 1 ); +LAT_TEST(dc_1x1, 2, 0, 1, 1, 0, 1, 1, 1 ); +LAT_TEST(dc_2, 1, 2, 1, 1, 0, 1, 2, 0, 1, 1, 0 ); +LAT_TEST(dc_2x1, 2, 2, 1, 1, 0, 1, 2, 1, 0, 1, 1, 0 ); +LAT_TEST(dc_2x2, 2, 8, 1, 1, 0, 1, 2, 2, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 0, 2, 0, 3, 1, 3, 2, ); + +LAT_TEST(d_1, 1, 0, 1, 1, 0, 0, 1 ); +LAT_TEST(d_1x1, 2, 0, 1, 1, 0, 0, 1, 1 ); +LAT_TEST(d_2, 1, 1, 1, 1, 0, 0, 2, 0, 1 ); +LAT_TEST(d_2x1, 2, 1, 1, 1, 0, 0, 2, 1, 0, 1 ); +LAT_TEST(d_2x2, 2, 4, 1, 1, 0, 0, 2, 2, 0, 1, 0, 2, 1, 3, 2, 3 ); + +LAT_TEST(dmc_1, 1, 0, 1, 1, 0, 1, 1 ); +LAT_TEST(dmc_1x1, 2, 0, 1, 1, 0, 1, 1, 1 ); +LAT_TEST(dmc_2, 1, 2, 1, 1, 0, 1, 2, 0, 1, 1, 0 ); +LAT_TEST(dmc_2x1, 2, 2, 1, 1, 0, 1, 2, 1, 0, 1, 1, 0 ); +LAT_TEST(dmc_2x2, 2, 4, 1, 1, 0, 1, 2, 2, 0, 1, 0, 2, 1, 3, 2, 3, + 1, 0, 3, 2, ); +/*----------------d--m--ne-di-mu-ci-dimedges------------------------*/ + +/* TODO: add more */ + +lat_test_t *all_checks[] = { /* 1 */ &lat_u_0, /* 2 */ &lat_u_01, + /* 3 */ &lat_u_02, /* 4 */ &lat_u_03, + /* 5 */ &lat_d_0, /* 6 */ &lat_d_01, + /* 7 */ &lat_d_02, /* 8 */ &lat_d_03, + /* 9 */ &lat_u_1, /* 10 */ &lat_u_1x1, + /* 11 */ &lat_u_2, /* 12 */ &lat_u_2x1, + /* 13 */ &lat_u_2x2, /* 14 */ &lat_u_1, + /* 15 */ &lat_u_1x1, /* 16 */ &lat_u_2, + /* 17 */ &lat_u_2x1, /* 18 */ &lat_uc_2x2, + /* 19 */ &lat_dc_1, /* 20 */ &lat_dc_1x1, + /* 21 */ &lat_dc_2, /* 22 */ &lat_dc_2x1, + /* 23 */ &lat_dc_2x2,/* 24 */ &lat_d_1, + /* 25 */ &lat_d_1x1, /* 26 */ &lat_d_2, + /* 27 */ &lat_d_2x1, /* 28 */ &lat_d_2x2, + /* 29 */ &lat_dc_2x2,/* 30 */ &lat_d_1, + /* 31 */ &lat_d_1x1, /* 32 */ &lat_d_2, + /* 33 */ &lat_d_2x1, /* 34 */ &lat_d_2x2, + 0 + }; + +int check_lattice_properties(const igraph_t *lattice) { + igraph_bool_t res; + + /* Connected */ + igraph_is_connected(lattice, &res, IGRAPH_WEAK); + if (!res && igraph_vcount(lattice) > 0) { + printf("Not connected\n"); + return 1; + } + + /* Simple */ + igraph_is_simple(lattice, &res, IGRAPH_DIRECTED); + if (!res) { + printf("Not simple\n"); + return 2; + } + + return 0; +} + +int check_lattice(const lat_test_t *test) { + igraph_t graph, othergraph; + igraph_vector_int_t otheredges; + igraph_vector_int_t dimvector; + igraph_vector_bool_t periodic; + igraph_bool_t iso; + int ret; + + /* Create lattice */ + dimvector = igraph_vector_int_view(test->dimedges, test->dim); + igraph_vector_bool_init(&periodic, test->dim); + igraph_vector_bool_fill(&periodic, test->periodic); + igraph_square_lattice( + &graph, &dimvector, test->nei, test->directed, test->mutual, &periodic + ); + igraph_vector_bool_destroy(&periodic); + + /* Check its properties */ + if ((ret = check_lattice_properties(&graph))) { + igraph_destroy(&graph); + printf("Lattice properties are not satisfied\n"); + return ret; + } + + /* Check that it is isomorphic to the stored graph */ + otheredges = igraph_vector_int_view(test->dimedges + test->dim, test->m * 2); + igraph_create(&othergraph, &otheredges, igraph_vector_int_prod(&dimvector), + test->directed); + igraph_isomorphic(&graph, &othergraph, &iso); + if (!iso) { + printf("--\n"); + print_graph(&graph); + printf("--\n"); + print_graph(&othergraph); + igraph_destroy(&graph); + igraph_destroy(&othergraph); + return 50; + } + + igraph_destroy(&graph); + igraph_destroy(&othergraph); + return 0; +} + +int main(void) { + int i, ret; + + i = 0; + while (all_checks[i]) { + if ((ret = check_lattice(all_checks[i]))) { + printf("Check no #%d failed.\n", (int) (i + 1)); + return ret; + } + i++; + } + + VERIFY_FINALLY_STACK(); + + /* Compare to the hypercube graph. */ + { + igraph_t g1, g2; + igraph_vector_int_t dims; + igraph_bool_t iso; + + /* Q_1 is the singleton graph */ + + igraph_hypercube(&g1, 0, IGRAPH_UNDIRECTED); + IGRAPH_ASSERT(igraph_vcount(&g1) == 1); + IGRAPH_ASSERT(igraph_ecount(&g1) == 0); + IGRAPH_ASSERT(!igraph_is_directed(&g1)); + igraph_destroy(&g1); + + /* Q_4 undirected */ + + igraph_vector_int_init(&dims, 4); + igraph_vector_int_fill(&dims, 2); + + igraph_square_lattice(&g2, &dims, 1, IGRAPH_UNDIRECTED, false, NULL); + igraph_hypercube(&g1, 4, IGRAPH_UNDIRECTED); + igraph_isomorphic(&g1, &g2, &iso); + IGRAPH_ASSERT(iso); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_vector_int_destroy(&dims); + + /* Q_5 directed */ + + igraph_vector_int_init(&dims, 5); + igraph_vector_int_fill(&dims, 2); + + igraph_square_lattice(&g2, &dims, 1, IGRAPH_DIRECTED, false, NULL); + igraph_hypercube(&g1, 5, IGRAPH_DIRECTED); + igraph_isomorphic(&g1, &g2, &iso); + IGRAPH_ASSERT(iso); + igraph_destroy(&g1); + igraph_destroy(&g2); + + igraph_vector_int_destroy(&dims); + + CHECK_ERROR(igraph_hypercube(&g1, 128, false), IGRAPH_EINVAL); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_st_edge_connectivity.c b/tests/unit/igraph_st_edge_connectivity.c new file mode 100644 index 0000000..7b1dbe0 --- /dev/null +++ b/tests/unit/igraph_st_edge_connectivity.c @@ -0,0 +1,39 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_int_t value; + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, -1); + + igraph_st_edge_connectivity(&g, &value, 0, 5); + + igraph_destroy(&g); + + IGRAPH_ASSERT(value == 2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_st_mincut.c b/tests/unit/igraph_st_mincut.c new file mode 100644 index 0000000..8bf52f6 --- /dev/null +++ b/tests/unit/igraph_st_mincut.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void sort_and_print(igraph_vector_int_t *vec) { + igraph_vector_int_sort(vec); + print_vector_int(vec); +} + +int main(void) { + + igraph_t g; + igraph_vector_int_t cut, partition, partition2; + igraph_vector_t capacity; + igraph_real_t value; + int source = 0; + int target = 4; + + igraph_vector_int_init(&partition, 0); + igraph_vector_int_init(&partition2, 0); + igraph_vector_int_init(&cut, 0); + + igraph_small(&g, 5, IGRAPH_DIRECTED, 0, 1, 1, 2, 1, 3, 2, 4, 3, 4, -1); + igraph_vector_init_int_end(&capacity, -1, 8, 2, 3, 3, 2, -1); + + /* test without capacity */ + + igraph_st_mincut(&g, &value, &cut, &partition, &partition2, source, target, /*capacity*/ NULL); + + /* cut and partition should have only one element */ + print_vector_int(&cut); + print_vector_int(&partition); + sort_and_print(&partition2); + + /* test with capacity */ + + igraph_st_mincut(&g, &value, &cut, &partition, &partition2, source, target, &capacity); + + sort_and_print(&cut); + sort_and_print(&partition); + sort_and_print(&partition2); + + /* cleanup */ + + igraph_vector_int_destroy(&cut); + igraph_vector_int_destroy(&partition); + igraph_vector_int_destroy(&partition2); + igraph_vector_destroy(&capacity); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_st_mincut.out b/tests/unit/igraph_st_mincut.out new file mode 100644 index 0000000..5fbe789 --- /dev/null +++ b/tests/unit/igraph_st_mincut.out @@ -0,0 +1,6 @@ +( 0 ) +( 0 ) +( 1 2 3 4 ) +( 1 4 ) +( 0 1 3 ) +( 2 4 ) diff --git a/tests/unit/igraph_st_mincut_value.c b/tests/unit/igraph_st_mincut_value.c new file mode 100644 index 0000000..9ef4bd0 --- /dev/null +++ b/tests/unit/igraph_st_mincut_value.c @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_t capacity; + igraph_real_t value; + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_init_int_end(&capacity, -1, 5, 2, 2, 3, 4, 1, 2, 5, -1); + + igraph_st_mincut_value(&g, &value, 0, 5, &capacity); + + igraph_vector_destroy(&capacity); + igraph_destroy(&g); + + IGRAPH_ASSERT(value == 7); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_st_vertex_connectivity.c b/tests/unit/igraph_st_vertex_connectivity.c new file mode 100644 index 0000000..b0d643a --- /dev/null +++ b/tests/unit/igraph_st_vertex_connectivity.c @@ -0,0 +1,86 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_int_t source, igraph_int_t target, igraph_vconn_nei_t neighbors) { + igraph_int_t res; + igraph_st_vertex_connectivity(g, &res, source, target, neighbors); + printf("%" IGRAPH_PRId "\n", res); + igraph_destroy(g); +} + +int main(void) { + igraph_t g; + igraph_int_t res; + + printf("graph with two unconnected vertices: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_ERROR); + + printf("graph with two connected vertices, IGRAPH_VCONN_NEI_NEGATIVE: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_NEGATIVE); + + printf("graph with two connected vertices, IGRAPH_VCONN_NEI_NUMBER_OF_NODES: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_NUMBER_OF_NODES); + + printf("graph with two connected vertices, IGRAPH_VCONN_NEI_IGNORE: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, 0,1, 0,1, -1); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_IGNORE); + + printf("directed graph with two connected vertices, IGRAPH_VCONN_NEI_IGNORE: "); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, 0,1, 0,1, 1,0, 1,0, -1); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_IGNORE); + + printf("line graph with 6 vertices: "); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 1,2, 2,3, 3,4, 4,5, -1); + print_and_destroy(&g, 0, 5, IGRAPH_VCONN_NEI_ERROR); + + printf("full graph with 6 vertices IGRAPH_VCONN_NEI_IGNORE: "); + igraph_full(&g, 6, IGRAPH_UNDIRECTED, 0); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_IGNORE); + + printf("full graph with 6 vertices IGRAPH_VCONN_NEI_IGNORE, directed: "); + igraph_full(&g, 6, IGRAPH_DIRECTED, 0); + print_and_destroy(&g, 0, 1, IGRAPH_VCONN_NEI_IGNORE); + + printf("line graph with 3 vertices, 6 edges: "); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, 0,1, 0,1, 1,2, 1,2, 1,2, 1,2, -1); + print_and_destroy(&g, 0, 2, IGRAPH_VCONN_NEI_ERROR); + + VERIFY_FINALLY_STACK(); + + printf("check error on graph with two connected vertices.\n"); + igraph_small(&g, 2, IGRAPH_UNDIRECTED, 0,1, -1); + CHECK_ERROR(igraph_st_vertex_connectivity(&g, &res, 0, 0, IGRAPH_VCONN_NEI_ERROR), IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("check error on graph with no vertices.\n"); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + CHECK_ERROR(igraph_st_vertex_connectivity(&g, &res, 0, 0, IGRAPH_VCONN_NEI_ERROR), IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("check error on graph with one vertex.\n"); + igraph_small(&g, 1, IGRAPH_UNDIRECTED, -1); + CHECK_ERROR(igraph_st_vertex_connectivity(&g, &res, 0, 0, IGRAPH_VCONN_NEI_ERROR), IGRAPH_EINVAL); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); +} diff --git a/tests/unit/igraph_st_vertex_connectivity.out b/tests/unit/igraph_st_vertex_connectivity.out new file mode 100644 index 0000000..d2b1ae3 --- /dev/null +++ b/tests/unit/igraph_st_vertex_connectivity.out @@ -0,0 +1,12 @@ +graph with two unconnected vertices: 0 +graph with two connected vertices, IGRAPH_VCONN_NEI_NEGATIVE: -1 +graph with two connected vertices, IGRAPH_VCONN_NEI_NUMBER_OF_NODES: 2 +graph with two connected vertices, IGRAPH_VCONN_NEI_IGNORE: 0 +directed graph with two connected vertices, IGRAPH_VCONN_NEI_IGNORE: 0 +line graph with 6 vertices: 1 +full graph with 6 vertices IGRAPH_VCONN_NEI_IGNORE: 4 +full graph with 6 vertices IGRAPH_VCONN_NEI_IGNORE, directed: 4 +line graph with 3 vertices, 6 edges: 1 +check error on graph with two connected vertices. +check error on graph with no vertices. +check error on graph with one vertex. diff --git a/tests/unit/igraph_static_power_law_game.c b/tests/unit/igraph_static_power_law_game.c new file mode 100644 index 0000000..5b8260f --- /dev/null +++ b/tests/unit/igraph_static_power_law_game.c @@ -0,0 +1,113 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_rng_seed(igraph_rng_default(), 42); + + printf("No vertices:\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 0, /*number of edges*/ 0, + /*exponent_out*/ 2.0, /*exponent in*/ 2.0, IGRAPH_SIMPLE_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("No edges, undirected:\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 10, /*number of edges*/ 0, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_SIMPLE_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + print_graph_canon(&g); + igraph_destroy(&g); + + printf("Checking some basic outputs.\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 100, /*number of edges*/ 30, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) == 30); + igraph_destroy(&g); + + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 90, /*number of edges*/ 40, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_LOOPS_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 90); + IGRAPH_ASSERT(igraph_ecount(&g) == 40); + igraph_destroy(&g); + + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 110, /*number of edges*/ 50, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 110); + IGRAPH_ASSERT(igraph_ecount(&g) == 50); + igraph_destroy(&g); + + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 100, /*number of edges*/ 30, + /*exponent_out*/ 2.0, /*exponent in*/ 2.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 100); + IGRAPH_ASSERT(igraph_ecount(&g) == 30); + igraph_destroy(&g); + + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 90, /*number of edges*/ 40, + /*exponent_out*/ 2.0, /*exponent in*/ 2.2, IGRAPH_LOOPS_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 90); + IGRAPH_ASSERT(igraph_ecount(&g) == 40); + igraph_destroy(&g); + + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 110, /*number of edges*/ 50, + /*exponent_out*/ 2.0, /*exponent in*/ 2.5, IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&g) == 110); + IGRAPH_ASSERT(igraph_ecount(&g) == 50); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Negative number of vertices.\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ -100, /*number of edges*/ 30, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Negative number of edges.\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 100, /*number of edges*/ -30, + /*exponent_out*/ 2.0, /*exponent in*/ -2.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Exponent out too low.\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 100, /*number of edges*/ 30, + /*exponent_out*/ 1.0, /*exponent in*/ -2.0, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_EINVAL); + igraph_destroy(&g); + + printf("Exponent in too low but not negative.\n"); + IGRAPH_ASSERT(igraph_static_power_law_game(&g, /*number of vertices*/ 100, /*number of edges*/ 30, + /*exponent_out*/ 2.0, /*exponent in*/ 0.5, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, + /*finite_size_correction*/ true) == IGRAPH_EINVAL); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_static_power_law_game.out b/tests/unit/igraph_static_power_law_game.out new file mode 100644 index 0000000..db040f4 --- /dev/null +++ b/tests/unit/igraph_static_power_law_game.out @@ -0,0 +1,15 @@ +No vertices: +directed: true +vcount: 0 +edges: { +} +No edges, undirected: +directed: false +vcount: 10 +edges: { +} +Checking some basic outputs. +Negative number of vertices. +Negative number of edges. +Exponent out too low. +Exponent in too low but not negative. diff --git a/tests/unit/igraph_strvector.c b/tests/unit/igraph_strvector.c new file mode 100644 index 0000000..36bccec --- /dev/null +++ b/tests/unit/igraph_strvector.c @@ -0,0 +1,193 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void strvector_print(const igraph_strvector_t *sv) { + igraph_int_t i, s = igraph_strvector_size(sv); + for (i = 0; i < s; i++) { + printf("\"%s\"\n", igraph_strvector_get(sv, i)); + } + printf("\n"); +} + +int main(void) { + + igraph_strvector_t sv1, sv2, sv3, sv4; + + printf("igraph_strvector_init, igraph_strvector_destroy\n"); + igraph_strvector_init(&sv1, 10); + igraph_strvector_destroy(&sv1); + igraph_strvector_init(&sv1, 0); + igraph_strvector_destroy(&sv1); + + printf("igraph_strvector_get, igraph_strvector_set\n"); + igraph_strvector_init(&sv1, 5); + strvector_print(&sv1); + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + igraph_strvector_set(&sv1, 3, "three"); + igraph_strvector_set(&sv1, 4, "four"); + strvector_print(&sv1); + + printf("igraph_strvector_remove_section\n"); + igraph_strvector_remove_section(&sv1, 0, 5); + if (igraph_strvector_size(&sv1) != 0) { + return 1; + } + printf("resize to 10 and then back to 5\n"); + igraph_strvector_resize(&sv1, 10); + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + igraph_strvector_set(&sv1, 3, "three"); + igraph_strvector_set(&sv1, 4, "four"); + igraph_strvector_resize(&sv1, 5); + strvector_print(&sv1); + printf("resize to 0\n"); + igraph_strvector_resize(&sv1, 0); + if (igraph_strvector_size(&sv1) != 0) { + return 1; + } + printf("resize to 10 and then back to 5 again\n"); + igraph_strvector_resize(&sv1, 10); + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + igraph_strvector_set(&sv1, 3, "three"); + igraph_strvector_set(&sv1, 4, "four"); + igraph_strvector_resize(&sv1, 5); + strvector_print(&sv1); + + printf("igraph_strvector_copy\n"); + igraph_strvector_init_copy(&sv2, &sv1); + strvector_print(&sv1); + + igraph_strvector_resize(&sv1, 0); + igraph_strvector_destroy(&sv2); + igraph_strvector_init_copy(&sv2, &sv1); + if (igraph_strvector_size(&sv2) != 0) { + return 2; + } + igraph_strvector_destroy(&sv2); + + printf("igraph_strvector_push_back\n"); + igraph_strvector_push_back(&sv1, "zeroth"); + igraph_strvector_push_back(&sv1, "first"); + igraph_strvector_push_back(&sv1, "second"); + igraph_strvector_push_back(&sv1, "third"); + igraph_strvector_push_back(&sv1, "fourth"); + strvector_print(&sv1); + + printf("igraph_strvector_push_back_len\n"); + igraph_strvector_push_back_len(&sv1, "extra zeroth", 5); + igraph_strvector_push_back_len(&sv1, "extra first", 100); + igraph_strvector_push_back_len(&sv1, "extra second", 0); + strvector_print(&sv1); + igraph_strvector_destroy(&sv1); + + printf("igraph_strvector_append\n"); + printf("===\n"); + igraph_strvector_init(&sv1, 0); + igraph_strvector_init(&sv2, 0); + igraph_strvector_append(&sv1, &sv2); + strvector_print(&sv1); + printf("===\n"); + + igraph_strvector_resize(&sv1, 3); + igraph_strvector_append(&sv1, &sv2); + strvector_print(&sv1); + printf("===\n"); + + igraph_strvector_append(&sv2, &sv1); + strvector_print(&sv2); + printf("===\n"); + + igraph_strvector_set(&sv1, 0, "0"); + igraph_strvector_set(&sv1, 1, "1"); + igraph_strvector_set(&sv1, 2, "2"); + igraph_strvector_set(&sv2, 0, "3"); + igraph_strvector_set(&sv2, 1, "4"); + igraph_strvector_set(&sv2, 2, "5"); + igraph_strvector_append(&sv1, &sv2); + strvector_print(&sv1); + + printf("igraph_strvector_swap\n"); + igraph_strvector_swap(&sv1, &sv2); + printf("===\n"); + strvector_print(&sv1); + printf("===\n"); + strvector_print(&sv2); + + igraph_strvector_destroy(&sv1); + igraph_strvector_destroy(&sv2); + + printf("igraph_strvector_merge\n"); + igraph_strvector_init(&sv1, 3); + igraph_strvector_set(&sv1, 0, "zero"); + igraph_strvector_set(&sv1, 1, "one"); + igraph_strvector_set(&sv1, 2, "two"); + + igraph_strvector_init(&sv2, 2); + igraph_strvector_set(&sv2, 0, "a"); + igraph_strvector_set(&sv2, 1, "b"); + + igraph_strvector_init_copy(&sv3, &sv1); + igraph_strvector_init_copy(&sv4, &sv2); + + igraph_strvector_merge(&sv1, &sv2); + IGRAPH_ASSERT(igraph_strvector_size(&sv2) == 0); + + igraph_strvector_append(&sv3, &sv4); + IGRAPH_ASSERT(igraph_strvector_size(&sv1) == igraph_strvector_size(&sv3)); + + for (igraph_int_t i=0; i < igraph_strvector_size(&sv1); ++i) { + IGRAPH_ASSERT(strcmp(igraph_strvector_get(&sv1, i), igraph_strvector_get(&sv3, i)) == 0); + } + + igraph_strvector_destroy(&sv1); + igraph_strvector_destroy(&sv2); + igraph_strvector_destroy(&sv3); + igraph_strvector_destroy(&sv4); + + printf("clear\n"); + igraph_strvector_init(&sv1, 3); + igraph_strvector_set(&sv1, 0, "0"); + igraph_strvector_set(&sv1, 1, "1"); + igraph_strvector_set(&sv1, 2, "2"); + igraph_strvector_clear(&sv1); + if (igraph_strvector_size(&sv1) != 0) { + return 3; + } + igraph_strvector_resize(&sv1, 4); + strvector_print(&sv1); + igraph_strvector_set(&sv1, 0, "one"); + igraph_strvector_set(&sv1, 2, "two"); + strvector_print(&sv1); + + igraph_strvector_swap_elements(&sv1, 1, 0); + strvector_print(&sv1); + + igraph_strvector_destroy(&sv1); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_strvector.out b/tests/unit/igraph_strvector.out new file mode 100644 index 0000000..cb202e5 --- /dev/null +++ b/tests/unit/igraph_strvector.out @@ -0,0 +1,106 @@ +igraph_strvector_init, igraph_strvector_destroy +igraph_strvector_get, igraph_strvector_set +"" +"" +"" +"" +"" + +"zero" +"one" +"two" +"three" +"four" + +igraph_strvector_remove_section +resize to 10 and then back to 5 +"zero" +"one" +"two" +"three" +"four" + +resize to 0 +resize to 10 and then back to 5 again +"zero" +"one" +"two" +"three" +"four" + +igraph_strvector_copy +"zero" +"one" +"two" +"three" +"four" + +igraph_strvector_push_back +"zeroth" +"first" +"second" +"third" +"fourth" + +igraph_strvector_push_back_len +"zeroth" +"first" +"second" +"third" +"fourth" +"extra" +"extra first" +"" + +igraph_strvector_append +=== + +=== +"" +"" +"" + +=== +"" +"" +"" + +=== +"0" +"1" +"2" +"3" +"4" +"5" + +igraph_strvector_swap +=== +"3" +"4" +"5" + +=== +"0" +"1" +"2" +"3" +"4" +"5" + +igraph_strvector_merge +clear +"" +"" +"" +"" + +"one" +"" +"two" +"" + +"" +"one" +"two" +"" + diff --git a/tests/unit/igraph_subcomponent.c b/tests/unit/igraph_subcomponent.c new file mode 100644 index 0000000..9f6edff --- /dev/null +++ b/tests/unit/igraph_subcomponent.c @@ -0,0 +1,81 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_int_t vertex, igraph_neimode_t mode) { + igraph_vector_int_t result; + igraph_vector_int_init(&result, 0); + IGRAPH_ASSERT(igraph_subcomponent(graph, &result, vertex, mode) == IGRAPH_SUCCESS); + igraph_vector_int_sort(&result); + igraph_vector_int_print(&result); + igraph_vector_int_destroy(&result); + printf("\n"); +} + + +int main(void) { + igraph_t g_0, g_1, g_lm, g_lmu; + igraph_vector_int_t result; + igraph_vector_int_init(&result, 0); + igraph_int_t i; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_lm, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + igraph_small(&g_lmu, 6, 0, 0,1, 0,2, 1,1, 1,3, 2,0, 2,0, 2,3, 3,4, 3,4, -1); + + printf("No vertices, should give error for impossible starting vertex.\n"); + CHECK_ERROR(igraph_subcomponent(&g_0, &result, 0, IGRAPH_ALL), IGRAPH_EINVVID); + + printf("One vertex.\n"); + call_and_print(&g_1, 0, IGRAPH_ALL); + + printf("All vertices of a graph, IGRAPH_OUT:\n"); + for (i = 0; i < 6; i++) { + call_and_print(&g_lm, i, IGRAPH_OUT); + } + + printf("All vertices of a graph, IGRAPH_IN:\n"); + for (i = 0; i < 6; i++) { + call_and_print(&g_lm, i, IGRAPH_IN); + } + + printf("All vertices of a graph, IGRAPH_ALL:\n"); + for (i = 0; i < 6; i++) { + call_and_print(&g_lm, i, IGRAPH_ALL); + } + + printf("All vertices of a graph, undirected:\n"); + for (i = 0; i < 6; i++) { + call_and_print(&g_lmu, i, IGRAPH_OUT); + } + + printf("Check for invalid mode error handling.\n"); + CHECK_ERROR(igraph_subcomponent(&g_1, &result, 0, (igraph_neimode_t) 100), IGRAPH_EINVMODE); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_lm); + igraph_destroy(&g_lmu); + igraph_vector_int_destroy(&result); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_subcomponent.out b/tests/unit/igraph_subcomponent.out new file mode 100644 index 0000000..a97a075 --- /dev/null +++ b/tests/unit/igraph_subcomponent.out @@ -0,0 +1,57 @@ +No vertices, should give error for impossible starting vertex. +One vertex. +0 + +All vertices of a graph, IGRAPH_OUT: +0 1 2 3 4 + +1 3 4 + +0 1 2 3 4 + +3 4 + +4 + +5 + +All vertices of a graph, IGRAPH_IN: +0 2 + +0 1 2 + +0 2 + +0 1 2 3 + +0 1 2 3 4 + +5 + +All vertices of a graph, IGRAPH_ALL: +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +5 + +All vertices of a graph, undirected: +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +0 1 2 3 4 + +5 + +Check for invalid mode error handling. diff --git a/tests/unit/igraph_subisomorphic.c b/tests/unit/igraph_subisomorphic.c new file mode 100644 index 0000000..8c207ce --- /dev/null +++ b/tests/unit/igraph_subisomorphic.c @@ -0,0 +1,64 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + /*igraph_subisomorphic now calls igraph_subisomorphic_vf2, most of + the testing is done through calling that directly*/ + igraph_t g1, g2; + igraph_bool_t result; + + printf("No vertices.\n"); + igraph_small(&g1, 0, 0, -1); + igraph_small(&g2, 0, 0, -1); + IGRAPH_ASSERT(igraph_subisomorphic(&g1, &g2, &result) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(result); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("Basic positive example, undirected.\n"); + igraph_small(&g1, 4, 0, 0,1, 1,2, 3,2, 3,1, -1); + igraph_small(&g2, 3, 0, 0,1, 1,2, 2,0, -1); + IGRAPH_ASSERT(igraph_subisomorphic(&g1, &g2, &result) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(result); + igraph_destroy(&g1); + igraph_destroy(&g2); + + printf("Basic negative example, directed.\n"); + igraph_small(&g1, 4, 1, 0,1, 1,2, 3,2, 3,1, -1); + igraph_small(&g2, 3, 1, 0,1, 1,2, 2,0, -1); + IGRAPH_ASSERT(igraph_subisomorphic(&g1, &g2, &result) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(!result); + igraph_destroy(&g1); + igraph_destroy(&g2); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Mismatching directedness.\n"); + igraph_small(&g1, 4, 1, 0,1, 1,2, 3,2, 3,1, -1); + igraph_small(&g2, 3, 0, 0,1, 1,2, 2,0, -1); + IGRAPH_ASSERT(igraph_subisomorphic(&g1, &g2, &result) == IGRAPH_EINVAL); + igraph_destroy(&g1); + igraph_destroy(&g2); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_subisomorphic_lad.c b/tests/unit/igraph_subisomorphic_lad.c new file mode 100644 index 0000000..476b80a --- /dev/null +++ b/tests/unit/igraph_subisomorphic_lad.c @@ -0,0 +1,195 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +/* This test counts motifs using LAD and compares the results with + * the RANDESU motif finder */ +void test_k_motifs(const igraph_t *graph, const int k, const int class_count, igraph_bool_t directed) { + igraph_vector_t randesu_counts, lad_counts; + igraph_bool_t equal; + igraph_int_t i, n; + igraph_int_t vcount; + igraph_real_t expected_count; + + vcount = igraph_vcount(graph); + + n = class_count; + + igraph_vector_init(&lad_counts, n); + + for (i = 0; i < n; i++) { + igraph_t pattern; + igraph_vector_int_list_t maps; + igraph_int_t nAutomorphisms; + + igraph_isoclass_create(&pattern, k, i, directed); + igraph_vector_int_list_init(&maps, 0); + + igraph_subisomorphic_lad(&pattern, graph, NULL, NULL, NULL, &maps, /* induced = */ true); + + igraph_count_subisomorphisms_vf2(&pattern, &pattern, NULL, NULL, NULL, NULL, &nAutomorphisms, NULL, NULL, NULL); + + VECTOR(lad_counts)[i] = igraph_vector_int_list_size(&maps) / nAutomorphisms; + + igraph_vector_int_list_destroy(&maps); + + igraph_destroy(&pattern); + } + + igraph_vector_init(&randesu_counts, 0); + igraph_motifs_randesu(graph, &randesu_counts, k, NULL); + + equal = 1 /* true */; + for (i = 0; i < n; i++) { + if (isnan(VECTOR(randesu_counts)[i])) { + continue; + } + if (VECTOR(randesu_counts)[i] != VECTOR(lad_counts)[i]) { + equal = 0; + break; + } + } + + if (! equal) { + printf("LAD %s %d-motif count does not agree with RANDESU.\n", directed ? "directed" : "undirected", k); + } + + expected_count = 1; + for (i = 0; i < k; i++) { + expected_count *= (vcount - i); + } + for (i = 0; i < k; i++) { + expected_count /= (i + 1); + } + if (igraph_vector_sum(&lad_counts) != expected_count) { + printf("Total %d-vertex %s subgraph count is incorrect.\n", k, directed ? "directed" : "undirected"); + } + + igraph_vector_destroy(&randesu_counts); + igraph_vector_destroy(&lad_counts); +} + +void test_motifs(void) { + igraph_t graph; + igraph_int_t count; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* The graph is chosen to have approximately 50% density + * so that most motifs have a high chance of appearing. */ + igraph_erdos_renyi_game_gnm(&graph, 30, 400, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_graph_count(3, IGRAPH_DIRECTED, &count); + test_k_motifs(&graph, 3, count, IGRAPH_DIRECTED); + + igraph_graph_count(4, IGRAPH_DIRECTED, &count); + test_k_motifs(&graph, 4, count, IGRAPH_DIRECTED); + + igraph_destroy(&graph); +} + +void test_motifs_undirected(void) { + igraph_t graph; + igraph_int_t count; + + igraph_rng_seed(igraph_rng_default(), 137); + + /* The graph is chosen to have slightly higher than 50% density + * so that most connected motifs have a high chance of appearing. */ + igraph_erdos_renyi_game_gnm(&graph, 18, 80, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_graph_count(3, IGRAPH_UNDIRECTED, &count); + test_k_motifs(&graph, 3, count, IGRAPH_UNDIRECTED); + + igraph_graph_count(4, IGRAPH_UNDIRECTED, &count); + test_k_motifs(&graph, 4, count, IGRAPH_UNDIRECTED); + + igraph_destroy(&graph); + + /* Use a smaller graph so that the test would not take too long. + * The graph is chosen to have slightly higher than 50% density + * so that most connected motifs have a high chance of appearing. */ + igraph_erdos_renyi_game_gnm(&graph, 12, 36, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_graph_count(5, IGRAPH_UNDIRECTED, &count); + test_k_motifs(&graph, 5, count, IGRAPH_UNDIRECTED); + + igraph_graph_count(6, IGRAPH_UNDIRECTED, &count); + test_k_motifs(&graph, 6, count, IGRAPH_UNDIRECTED); + + igraph_destroy(&graph); +} + + +int main(void) { + igraph_t pattern, target; + igraph_bool_t iso; + igraph_vector_int_t map; + igraph_vector_int_list_t maps; + + igraph_vector_int_init(&map, 0); + igraph_vector_int_list_init(&maps, 0); + + igraph_small(&target, 9, IGRAPH_UNDIRECTED, + 0, 1, 0, 4, 0, 6, + 1, 4, 1, 2, + 2, 3, + 3, 4, 3, 5, 3, 7, 3, 8, + 4, 5, 4, 6, + 5, 6, 5, 8, + 7, 8, + -1); + + igraph_small(&pattern, 0, IGRAPH_UNDIRECTED, -1); + igraph_subisomorphic_lad(&pattern, &target, /*domains=*/ NULL, &iso, &map, &maps, + /*induced=*/ false); + + IGRAPH_ASSERT(iso); + IGRAPH_ASSERT(igraph_vector_int_size(&map) == 0); + IGRAPH_ASSERT(igraph_vector_int_list_size(&maps) == 1); + IGRAPH_ASSERT(igraph_vector_int_size(igraph_vector_int_list_get_ptr(&maps, 0)) == 0); + + igraph_destroy(&pattern); + igraph_destroy(&target); + + igraph_vector_int_destroy(&map); + igraph_vector_int_list_destroy(&maps); + + + /* Check error: pattern and target differ in directedness */ + igraph_vector_int_init(&map, 0); + igraph_vector_int_list_init(&maps, 0); + igraph_small(&pattern, 0, IGRAPH_DIRECTED, -1); + CHECK_ERROR( + igraph_subisomorphic_lad(&pattern, &target, /*domains=*/ 0, + &iso, &map, &maps, /*induced=*/ 0), + IGRAPH_EINVAL + ); + igraph_vector_int_destroy(&map); + igraph_vector_int_list_destroy(&maps); + igraph_destroy(&pattern); + igraph_destroy(&target); + + test_motifs(); + test_motifs_undirected(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_to_directed.c b/tests/unit/igraph_to_directed.c new file mode 100644 index 0000000..04f227f --- /dev/null +++ b/tests/unit/igraph_to_directed.c @@ -0,0 +1,43 @@ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t ug; + igraph_t dg; + + igraph_small(&ug, 6, /* directed= */ 0, + 2,0, 1,4, 3,2, 2,3, 0,4, 5,0, 2,3, 5,3, 2,5, 0,1, + -1); + + igraph_copy(&dg, &ug); + printf("\nARBITRARY:\n"); + igraph_to_directed(&dg, IGRAPH_TO_DIRECTED_ARBITRARY); + print_graph(&dg); + igraph_destroy(&dg); + + igraph_copy(&dg, &ug); + printf("\nMUTUAL:\n"); + igraph_to_directed(&dg, IGRAPH_TO_DIRECTED_MUTUAL); + print_graph(&dg); + igraph_destroy(&dg); + + igraph_copy(&dg, &ug); + printf("\nACYCLIC:\n"); + igraph_to_directed(&dg, IGRAPH_TO_DIRECTED_ACYCLIC); + print_graph(&dg); + igraph_destroy(&dg); + + igraph_copy(&dg, &ug); + printf("\nRANDOM (edge count only):\n"); + igraph_to_directed(&dg, IGRAPH_TO_DIRECTED_RANDOM); + printf("%" IGRAPH_PRId "\n", igraph_ecount(&dg)); + igraph_destroy(&dg); + + igraph_destroy(&ug); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_to_directed.out b/tests/unit/igraph_to_directed.out new file mode 100644 index 0000000..6c0d17e --- /dev/null +++ b/tests/unit/igraph_to_directed.out @@ -0,0 +1,61 @@ + +ARBITRARY: +directed: true +vcount: 6 +edges: { +0 2 +1 4 +2 3 +2 3 +0 4 +0 5 +2 3 +3 5 +2 5 +0 1 +} + +MUTUAL: +directed: true +vcount: 6 +edges: { +0 2 +1 4 +2 3 +2 3 +0 4 +0 5 +2 3 +3 5 +2 5 +0 1 +2 0 +4 1 +3 2 +3 2 +4 0 +5 0 +3 2 +5 3 +5 2 +1 0 +} + +ACYCLIC: +directed: true +vcount: 6 +edges: { +0 2 +1 4 +2 3 +2 3 +0 4 +0 5 +2 3 +3 5 +2 5 +0 1 +} + +RANDOM (edge count only): +10 diff --git a/tests/unit/igraph_to_prufer.c b/tests/unit/igraph_to_prufer.c new file mode 100644 index 0000000..228321b --- /dev/null +++ b/tests/unit/igraph_to_prufer.c @@ -0,0 +1,166 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +igraph_bool_t test_from_prufer_back_to_prufer(void) { + igraph_t graph; + igraph_int_t prufer[] = {2, 3, 2, 3}; + + igraph_vector_int_t expected_prufer, output_prufer; + + igraph_bool_t success = 0; + + expected_prufer = igraph_vector_int_view(prufer, 4); + igraph_from_prufer(&graph, &expected_prufer); + + igraph_vector_int_init(&output_prufer, 4); + igraph_to_prufer(&graph, &output_prufer); + + success = igraph_vector_int_all_e(&expected_prufer, &output_prufer); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&output_prufer); + + return success; +} + +igraph_bool_t test_from_prufer_back_to_prufer_with_resize(void) { + igraph_t graph; + igraph_int_t prufer[] = {0, 2, 4, 1, 1, 0}; + + igraph_vector_int_t expected_prufer, output_prufer; + + igraph_bool_t success; + + expected_prufer = igraph_vector_int_view(prufer, 6); + igraph_from_prufer(&graph, &expected_prufer); + + igraph_vector_int_init(&output_prufer, 0); + igraph_to_prufer(&graph, &output_prufer); + + success = igraph_vector_int_all_e(&expected_prufer, &output_prufer); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&output_prufer); + + return success; +} + +igraph_bool_t test_from_prufer_back_to_prufer_with_resize2(void) { + igraph_t graph; + igraph_int_t prufer[] = {2, 4, 5, 1, 3}; + + igraph_vector_int_t expected_prufer, output_prufer; + + igraph_bool_t success; + + expected_prufer = igraph_vector_int_view(prufer, 5); + igraph_from_prufer(&graph, &expected_prufer); + + igraph_vector_int_init(&output_prufer, 0); + igraph_to_prufer(&graph, &output_prufer); + + + success = igraph_vector_int_all_e(&output_prufer, &expected_prufer); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&output_prufer); + + return success; +} + +igraph_error_t random_tree(igraph_int_t size, igraph_t* tree, igraph_vector_int_t* prufer) { + igraph_int_t i, j; + igraph_int_t prufer_length; + + if (size < 0) { + IGRAPH_ERROR("Invalid size.", IGRAPH_EINVAL); + } + + if (size < 2) { + return igraph_empty(tree, size, IGRAPH_UNDIRECTED); + } + + prufer_length = size - 2; + IGRAPH_CHECK(igraph_vector_int_resize(prufer, prufer_length)); + + for (i = 0; i < prufer_length; ++i) { + j = RNG_INTEGER(0, size - 1); + VECTOR(*prufer)[i] = j; + } + + IGRAPH_CHECK(igraph_from_prufer(tree, prufer)); + + return IGRAPH_SUCCESS; +} + +igraph_bool_t test_from_random_prufer_back_to_prufer(int tree_size) { + igraph_t graph; + igraph_vector_int_t expected_prufer, output_prufer; + + igraph_bool_t success = 0; + igraph_int_t random_seed = 4096; + + igraph_vector_int_init(&output_prufer, 0); + igraph_vector_int_init(&expected_prufer, 0); + + igraph_rng_seed(igraph_rng_default(), random_seed); + + random_tree(tree_size, &graph, &expected_prufer); + + igraph_to_prufer(&graph, &output_prufer); + + success = igraph_vector_int_all_e(&output_prufer, &expected_prufer); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&expected_prufer); + igraph_vector_int_destroy(&output_prufer); + + return success; +} + +#undef RUN_TEST /* from test_utilities.h */ + +int test_num = 0; +#define RUN_TEST(TEST) \ + test_num++; \ + if (!(TEST)) { \ + return test_num; \ + } + +int main(void) { + RUN_TEST(test_from_prufer_back_to_prufer()); + RUN_TEST(test_from_prufer_back_to_prufer_with_resize()); + RUN_TEST(test_from_prufer_back_to_prufer_with_resize2()); + RUN_TEST(test_from_random_prufer_back_to_prufer(10)); + RUN_TEST(test_from_random_prufer_back_to_prufer(100)); + RUN_TEST(test_from_random_prufer_back_to_prufer(1000)); + RUN_TEST(test_from_random_prufer_back_to_prufer(10000)); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_transitive_closure.c b/tests/unit/igraph_transitive_closure.c new file mode 100644 index 0000000..69dff4a --- /dev/null +++ b/tests/unit/igraph_transitive_closure.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void print_closure(const igraph_t *graph) { + igraph_t closure; + igraph_bool_t simple; + + igraph_transitive_closure(graph, &closure); + print_graph_canon(&closure); + IGRAPH_ASSERT(igraph_vcount(graph) == igraph_vcount(&closure)); + IGRAPH_ASSERT(igraph_is_directed(graph) == igraph_is_directed(&closure)); + igraph_is_simple(&closure, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + igraph_destroy(&closure); +} + +int main(void) { + igraph_t graph; + + printf("Directed null graph\n"); + igraph_empty(&graph, 0, IGRAPH_DIRECTED); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nUndirected null graph\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nDirected singleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_DIRECTED); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nUndirected singleton graph\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nEdgeless graph\n"); + igraph_empty(&graph, 3, IGRAPH_DIRECTED); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nSmall DAG\n"); + igraph_small(&graph, 9, IGRAPH_DIRECTED, + 8, 7, 7, 6, 6, 3, 6, 0, 3, 2, 3, 1, 5, 0, 4, 1, + -1); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nSmall directed multigraph with cycles \n"); + igraph_small(&graph, 10, IGRAPH_DIRECTED, + 0,1, 1,2, 2,0, 2,0, 0,3, 3,4, 4,3, 3,3, 0,5, 5,6, 6,6, 8,7, + -1); + print_closure(&graph); + igraph_destroy(&graph); + + printf("\nSmall undirected graph\n"); + igraph_small(&graph, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 3,4, + -1); + print_closure(&graph); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_transitive_closure.out b/tests/unit/igraph_transitive_closure.out new file mode 100644 index 0000000..087b94a --- /dev/null +++ b/tests/unit/igraph_transitive_closure.out @@ -0,0 +1,92 @@ +Directed null graph +directed: true +vcount: 0 +edges: { +} + +Undirected null graph +directed: false +vcount: 0 +edges: { +} + +Directed singleton graph +directed: true +vcount: 1 +edges: { +} + +Undirected singleton graph +directed: false +vcount: 1 +edges: { +} + +Edgeless graph +directed: true +vcount: 3 +edges: { +} + +Small DAG +directed: true +vcount: 9 +edges: { +3 1 +3 2 +4 1 +5 0 +6 0 +6 1 +6 2 +6 3 +7 0 +7 1 +7 2 +7 3 +7 6 +8 0 +8 1 +8 2 +8 3 +8 6 +8 7 +} + +Small directed multigraph with cycles +directed: true +vcount: 10 +edges: { +0 1 +0 2 +0 3 +0 4 +0 5 +0 6 +1 0 +1 2 +1 3 +1 4 +1 5 +1 6 +2 0 +2 1 +2 3 +2 4 +2 5 +2 6 +3 4 +4 3 +5 6 +8 7 +} + +Small undirected graph +directed: false +vcount: 6 +edges: { +0 1 +0 2 +1 2 +3 4 +} diff --git a/tests/unit/igraph_transitivity_avglocal_undirected.c b/tests/unit/igraph_transitivity_avglocal_undirected.c new file mode 100644 index 0000000..19ed3a5 --- /dev/null +++ b/tests/unit/igraph_transitivity_avglocal_undirected.c @@ -0,0 +1,77 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_0, g_1, g_simple, g_ml; + igraph_real_t result; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_simple, 6, 1, 0,1, 0,2, 1,2, 1,3, 2,3, 3,4, -1); + igraph_small(&g_ml, 6, 0, 0,1, 0,2, 1,1, 1,2, 1,3, 2,1, 2,3, 3,4, 3,4, -1); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("No vertices, transitivity zero:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_0, &result, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("No vertices, transitivity NaN:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_0, &result, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("One vertex, transitivity zero:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_1, &result, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("One vertex, transitivity NaN:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_1, &result, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("Simple graph:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_simple, &result, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("Multigraph:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_ml, &result, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + printf("Multigraph, TRANSITIVITY_NAN:\n"); + IGRAPH_ASSERT(igraph_transitivity_avglocal_undirected(&g_ml, &result, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_real(stdout, result, "%g"); + printf("\n"); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_simple); + igraph_destroy(&g_ml); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_transitivity_avglocal_undirected.out b/tests/unit/igraph_transitivity_avglocal_undirected.out new file mode 100644 index 0000000..bbf4123 --- /dev/null +++ b/tests/unit/igraph_transitivity_avglocal_undirected.out @@ -0,0 +1,14 @@ +No vertices, transitivity zero: +0 +No vertices, transitivity NaN: +NaN +One vertex, transitivity zero: +0 +One vertex, transitivity NaN: +NaN +Simple graph: +0.444444 +Multigraph: +0.444444 +Multigraph, TRANSITIVITY_NAN: +0.666667 diff --git a/tests/unit/igraph_transitivity_barrat.c b/tests/unit/igraph_transitivity_barrat.c new file mode 100644 index 0000000..1c3fa77 --- /dev/null +++ b/tests/unit/igraph_transitivity_barrat.c @@ -0,0 +1,132 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void warning_handler_print_stdout(const char *reason, const char *file, + int line) { + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + fprintf(stdout, "Warning: %s\n", reason); +} + +int main(void) { + igraph_t g_0, g_1, g_simple; + igraph_vector_t result, weights_none, weights_simple; + + igraph_small(&g_0, 0, 0, -1); + igraph_small(&g_1, 1, 0, -1); + igraph_small(&g_simple, 6, 0, 0,1, 0,2, 1,2, 1,3, 2,3, 3,4, -1); + + igraph_vector_init(&result, 0); + igraph_vector_init(&weights_none, 0); + igraph_vector_init_int(&weights_simple, 6, -1, 0, 1, 2, 3, 4); + + igraph_set_warning_handler(warning_handler_print_stdout); + + printf("No vertices, transitivity zero, NULL weights:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_all(), /*weights*/ NULL, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("No vertices, transitivity zero, NULL weights, no vs:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_none(), /*weights*/ NULL, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("No vertices, transitivity zero, no weights:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_all(), &weights_none, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("No vertices, transitivity zero, no weights, no vs:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_none(), &weights_none, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("No vertices, transitivity NAN:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_all(), NULL, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("No vertices, transitivity NAN, no vs:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_0, &result, igraph_vss_none(), NULL, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("One vertex, transitivity zero:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_1, &result, igraph_vss_all(), NULL, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("One vertex, transitivity NaN:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_1, &result, igraph_vss_all(), NULL, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("One vertex, transitivity zero, vs one:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_1, &result, igraph_vss_1(0), NULL, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("One vertex, transitivity NaN, vs one:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_1, &result, igraph_vss_1(0), NULL, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("Simple graph, NULL weights, transitivity NAN:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_all(), NULL, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("Simple graph, with weights, transitivity NAN:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_all(), &weights_simple, IGRAPH_TRANSITIVITY_NAN) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("Simple graph, with weights, transitivity zero:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_all(), &weights_simple, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + printf("Simple graph, with weights, transitivity zero, vss none:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_none(), &weights_simple, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_SUCCESS); + print_vector(&result); + printf("\n"); + + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Wrong weight length, vss all:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_all(), &weights_none, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_EINVAL); + + printf("Wrong weight length, vss none:\n"); + IGRAPH_ASSERT(igraph_transitivity_barrat(&g_simple, &result, igraph_vss_none(), &weights_none, IGRAPH_TRANSITIVITY_ZERO) == IGRAPH_EINVAL); + + igraph_destroy(&g_0); + igraph_destroy(&g_1); + igraph_destroy(&g_simple); + + igraph_vector_destroy(&result); + igraph_vector_destroy(&weights_none); + igraph_vector_destroy(&weights_simple); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_transitivity_barrat.out b/tests/unit/igraph_transitivity_barrat.out new file mode 100644 index 0000000..08ecba8 --- /dev/null +++ b/tests/unit/igraph_transitivity_barrat.out @@ -0,0 +1,45 @@ +No vertices, transitivity zero, NULL weights: +( ) + +No vertices, transitivity zero, NULL weights, no vs: +( ) + +No vertices, transitivity zero, no weights: +( ) + +No vertices, transitivity zero, no weights, no vs: +( ) + +No vertices, transitivity NAN: +( ) + +No vertices, transitivity NAN, no vs: +( ) + +One vertex, transitivity zero: +( 0 ) + +One vertex, transitivity NaN: +( NaN ) + +One vertex, transitivity zero, vs one: +( 0 ) + +One vertex, transitivity NaN, vs one: +( NaN ) + +Simple graph, NULL weights, transitivity NAN: +Warning: No weights given for Barrat's transitivity, unweighted version is used. +( 1 0.666667 0.666667 0.333333 NaN NaN ) + +Simple graph, with weights, transitivity NAN: +( 1 0.75 0.625 0.277778 NaN NaN ) + +Simple graph, with weights, transitivity zero: +( 1 0.75 0.625 0.277778 0 0 ) + +Simple graph, with weights, transitivity zero, vss none: +( ) + +Wrong weight length, vss all: +Wrong weight length, vss none: diff --git a/tests/unit/igraph_tree_from_parent_vector.c b/tests/unit/igraph_tree_from_parent_vector.c new file mode 100644 index 0000000..d481e21 --- /dev/null +++ b/tests/unit/igraph_tree_from_parent_vector.c @@ -0,0 +1,77 @@ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t parents; + igraph_bool_t is_tree, is_forest; + + igraph_vector_int_init_int(&parents, 5, + 4, 4, 1, -2, 3); + + printf("Out-tree:\n"); + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT); + print_graph(&graph); + igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_OUT); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + printf("\nIn-tree:\n"); + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_IN); + print_graph(&graph); + igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_IN); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + printf("\nUndirected tree:\n"); + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_UNDIRECTED); + print_graph(&graph); + igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + printf("\nForest:\n"); + VECTOR(parents)[0] = -1; + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT); + print_graph(&graph); + igraph_is_forest(&graph, &is_forest, NULL, IGRAPH_OUT); + IGRAPH_ASSERT(is_forest); + igraph_destroy(&graph); + + /* Invalid tree mode */ + CHECK_ERROR(igraph_tree_from_parent_vector(&graph, &parents, -1), IGRAPH_EINVAL); + + /* Invalid vertex */ + VECTOR(parents)[0] = 5; + CHECK_ERROR(igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT), IGRAPH_EINVVID); + + /* Self-loop */ + VECTOR(parents)[0] = 0; + CHECK_ERROR(igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT), IGRAPH_EINVAL); + + /* Longer cycle */ + VECTOR(parents)[0] = 4; + VECTOR(parents)[3] = 0; + CHECK_ERROR(igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT), IGRAPH_EINVAL); + + printf("\nEdgeless graph:\n"); + igraph_vector_int_fill(&parents, -1); + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT); + print_graph(&graph); + IGRAPH_ASSERT(igraph_ecount(&graph) == 0); + igraph_destroy(&graph); + + printf("\nNull graph:\n"); + igraph_vector_int_clear(&parents); + igraph_tree_from_parent_vector(&graph, &parents, IGRAPH_TREE_OUT); + print_graph(&graph); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + + igraph_vector_int_destroy(&parents); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_tree_from_parent_vector.out b/tests/unit/igraph_tree_from_parent_vector.out new file mode 100644 index 0000000..fad0396 --- /dev/null +++ b/tests/unit/igraph_tree_from_parent_vector.out @@ -0,0 +1,50 @@ +Out-tree: +directed: true +vcount: 5 +edges: { +4 0 +3 4 +4 1 +1 2 +} + +In-tree: +directed: true +vcount: 5 +edges: { +0 4 +4 3 +1 4 +2 1 +} + +Undirected tree: +directed: false +vcount: 5 +edges: { +4 0 +4 3 +4 1 +2 1 +} + +Forest: +directed: true +vcount: 5 +edges: { +4 1 +3 4 +1 2 +} + +Edgeless graph: +directed: true +vcount: 5 +edges: { +} + +Null graph: +directed: true +vcount: 0 +edges: { +} diff --git a/tests/unit/igraph_triangular_lattice.c b/tests/unit/igraph_triangular_lattice.c new file mode 100644 index 0000000..0aedbfd --- /dev/null +++ b/tests/unit/igraph_triangular_lattice.c @@ -0,0 +1,102 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t dimvector; + + /* empty graph */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 2)); + VECTOR(dimvector)[0] = 3; + + IGRAPH_CHECK(igraph_triangular_lattice(&graph, &dimvector, true, false)); + printf("Empty graph:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* triangular triangular lattice with a single vertex and no edges*/ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 1)); + VECTOR(dimvector)[0] = 1; + + IGRAPH_CHECK(igraph_triangular_lattice(&graph, &dimvector, true, false)); + printf("Triangular triangular lattice, single vertex:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* triangular triangular lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 1)); + VECTOR(dimvector)[0] = 5; + + IGRAPH_CHECK(igraph_triangular_lattice(&graph, &dimvector, true, false)); + printf("Triangular triangular lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* rectangular triangular lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 2)); + VECTOR(dimvector)[0] = 4; + VECTOR(dimvector)[1] = 5; + + IGRAPH_CHECK(igraph_triangular_lattice(&graph, &dimvector, true, true)); + printf("Rectangular triangular lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + /* hexagonal triangular lattice */ + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 3)); + VECTOR(dimvector)[0] = 3; + VECTOR(dimvector)[1] = 4; + VECTOR(dimvector)[2] = 5; + + IGRAPH_CHECK(igraph_triangular_lattice(&graph, &dimvector, false, true)); + printf("Hexagonal triangular lattice:\n"); + print_graph_canon(&graph); + + igraph_destroy(&graph); + + /* Erroneous calls */ + VECTOR(dimvector)[0] = -3; + CHECK_ERROR(igraph_triangular_lattice(&graph, &dimvector, true, true), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + IGRAPH_CHECK(igraph_vector_int_init(&dimvector, 4)); + VECTOR(dimvector)[0] = 3; + VECTOR(dimvector)[1] = 4; + VECTOR(dimvector)[2] = 5; + VECTOR(dimvector)[3] = 5; + CHECK_ERROR(igraph_triangular_lattice(&graph, &dimvector, true, true), IGRAPH_EINVAL); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&dimvector); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_triangular_lattice.out b/tests/unit/igraph_triangular_lattice.out new file mode 100644 index 0000000..4141012 --- /dev/null +++ b/tests/unit/igraph_triangular_lattice.out @@ -0,0 +1,228 @@ +Empty graph: +directed: true +vcount: 0 +edges: { +} +Triangular triangular lattice, single vertex: +directed: true +vcount: 1 +edges: { +} +Triangular triangular lattice: +directed: true +vcount: 15 +edges: { +0 1 +0 5 +1 2 +1 5 +1 6 +2 3 +2 6 +2 7 +3 4 +3 7 +3 8 +4 8 +5 6 +5 9 +6 7 +6 9 +6 10 +7 8 +7 10 +7 11 +8 11 +9 10 +9 12 +10 11 +10 12 +10 13 +11 13 +12 13 +12 14 +13 14 +} +Rectangular triangular lattice: +directed: true +vcount: 20 +edges: { +0 1 +0 5 +0 6 +1 0 +1 2 +1 6 +1 7 +2 1 +2 3 +2 7 +2 8 +3 2 +3 4 +3 8 +3 9 +4 3 +4 9 +5 0 +5 6 +5 10 +6 0 +6 1 +6 5 +6 7 +6 10 +6 11 +7 1 +7 2 +7 6 +7 8 +7 11 +7 12 +8 2 +8 3 +8 7 +8 9 +8 12 +8 13 +9 3 +9 4 +9 8 +9 13 +9 14 +10 5 +10 6 +10 11 +10 15 +10 16 +11 6 +11 7 +11 10 +11 12 +11 16 +11 17 +12 7 +12 8 +12 11 +12 13 +12 17 +12 18 +13 8 +13 9 +13 12 +13 14 +13 18 +13 19 +14 9 +14 13 +14 19 +15 10 +15 16 +16 10 +16 11 +16 15 +16 17 +17 11 +17 12 +17 16 +17 18 +18 12 +18 13 +18 17 +18 19 +19 13 +19 14 +19 18 +} +Hexagonal triangular lattice: +directed: false +vcount: 36 +edges: { +0 1 +0 3 +0 4 +1 2 +1 4 +1 5 +2 5 +2 6 +3 4 +3 7 +3 8 +4 5 +4 8 +4 9 +5 6 +5 9 +5 10 +6 10 +6 11 +7 8 +7 12 +7 13 +8 9 +8 13 +8 14 +9 10 +9 14 +9 15 +10 11 +10 15 +10 16 +11 16 +11 17 +12 13 +12 18 +13 14 +13 18 +13 19 +14 15 +14 19 +14 20 +15 16 +15 20 +15 21 +16 17 +16 21 +16 22 +17 22 +17 23 +18 19 +18 24 +19 20 +19 24 +19 25 +20 21 +20 25 +20 26 +21 22 +21 26 +21 27 +22 23 +22 27 +22 28 +23 28 +24 25 +24 29 +25 26 +25 29 +25 30 +26 27 +26 30 +26 31 +27 28 +27 31 +27 32 +28 32 +29 30 +29 33 +30 31 +30 33 +30 34 +31 32 +31 34 +31 35 +32 35 +33 34 +34 35 +} diff --git a/tests/unit/igraph_trussness.c b/tests/unit/igraph_trussness.c new file mode 100644 index 0000000..c707063 --- /dev/null +++ b/tests/unit/igraph_trussness.c @@ -0,0 +1,112 @@ +/* + + Copyright 2017 The Johns Hopkins University Applied Physics Laboratory LLC. All Rights Reserved. + Copyright 2021 The igraph team + + Truss algorithm for cohesive subgroups. + + Author: Alex Perrone + Date: 2017-08-03 + Minor edits: The igraph team, 2021 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +void print_and_destroy(igraph_t *graph, igraph_vector_int_t *trussness) { + igraph_int_t i, n = igraph_vector_int_size(trussness); + + printf("fromNode, toNode, trussness\n"); + for (i=0; i < n; i++) { + igraph_int_t from, to; + igraph_edge(graph, i, &from, &to); + printf("%" IGRAPH_PRId ", %" IGRAPH_PRId ", %" IGRAPH_PRId "\n", from, to, VECTOR(*trussness)[i]); + } + + igraph_vector_int_destroy(trussness); + igraph_destroy(graph); +} + +int main(void) { + + igraph_t graph; + igraph_vector_int_t trussness; + + /* Create actual graph */ + igraph_int_t edges[] = { 0,1, 0,2, 0,3, 0,4, + 1,2, 1,3, 1,4, 2,3, 2,4, 3,4, 3,6, 3,11, + 4,5, 4,6, 5,6, 5,7, 5,8, 5,9, 6,7, 6,10, 6,11, + 7,8, 7,9, 8,9, 8,10 }; + const igraph_vector_int_t v = igraph_vector_int_view(edges, sizeof(edges) / sizeof(edges[0])); + + igraph_create(&graph, &v, 0, IGRAPH_UNDIRECTED); + + /* Compute the trussness of the edges. */ + printf("Simple graph:\n"); + igraph_vector_int_init(&trussness, 0); + igraph_trussness(&graph, &trussness); + print_and_destroy(&graph, &trussness); + + /* Add some loop edges -- they should have trussness = 2 */ + printf("\nGraph with loops:\n"); + igraph_create(&graph, &v, 0, IGRAPH_UNDIRECTED); + igraph_add_edge(&graph, 0, 0); + igraph_add_edge(&graph, 7, 7); + igraph_add_edge(&graph, 5, 5); + igraph_vector_int_init(&trussness, 0); + igraph_trussness(&graph, &trussness); + print_and_destroy(&graph, &trussness); + + /* Null graph trivial case */ + printf("\nNull graph:\n"); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&trussness, 0); + igraph_trussness(&graph, &trussness); + print_and_destroy(&graph, &trussness); + + /* Singleton graph trivial case */ + printf("\nSingleton graph:\n"); + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&trussness, 0); + igraph_trussness(&graph, &trussness); + print_and_destroy(&graph, &trussness); + + /* Graph with no edges trivial case */ + printf("\nGraph with no edges:\n"); + igraph_empty(&graph, 10, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&trussness, 0); + igraph_trussness(&graph, &trussness); + print_and_destroy(&graph, &trussness); + + VERIFY_FINALLY_STACK(); + + /* Multigraph */ + printf("\nTrying multigraph:\n"); + igraph_create(&graph, &v, 0, IGRAPH_UNDIRECTED); + igraph_to_directed(&graph, IGRAPH_TO_DIRECTED_MUTUAL); + igraph_to_undirected(&graph, IGRAPH_TO_UNDIRECTED_EACH, 0); + igraph_vector_int_init(&trussness, 0); + CHECK_ERROR(igraph_trussness(&graph, &trussness), IGRAPH_UNIMPLEMENTED); + igraph_vector_int_destroy(&trussness); + igraph_destroy(&graph); + + return 0; +} diff --git a/tests/unit/igraph_trussness.out b/tests/unit/igraph_trussness.out new file mode 100644 index 0000000..d18d431 --- /dev/null +++ b/tests/unit/igraph_trussness.out @@ -0,0 +1,69 @@ +Simple graph: +fromNode, toNode, trussness +0, 1, 5 +0, 2, 5 +0, 3, 5 +0, 4, 5 +1, 2, 5 +1, 3, 5 +1, 4, 5 +2, 3, 5 +2, 4, 5 +3, 4, 5 +3, 6, 3 +3, 11, 3 +4, 5, 3 +4, 6, 3 +5, 6, 3 +5, 7, 4 +5, 8, 4 +5, 9, 4 +6, 7, 3 +6, 10, 2 +6, 11, 3 +7, 8, 4 +7, 9, 4 +8, 9, 4 +8, 10, 2 + +Graph with loops: +fromNode, toNode, trussness +0, 1, 5 +0, 2, 5 +0, 3, 5 +0, 4, 5 +1, 2, 5 +1, 3, 5 +1, 4, 5 +2, 3, 5 +2, 4, 5 +3, 4, 5 +3, 6, 3 +3, 11, 3 +4, 5, 3 +4, 6, 3 +5, 6, 3 +5, 7, 4 +5, 8, 4 +5, 9, 4 +6, 7, 3 +6, 10, 2 +6, 11, 3 +7, 8, 4 +7, 9, 4 +8, 9, 4 +8, 10, 2 +0, 0, 2 +7, 7, 2 +5, 5, 2 + +Null graph: +fromNode, toNode, trussness + +Singleton graph: +fromNode, toNode, trussness + +Graph with no edges: +fromNode, toNode, trussness + +Trying multigraph: diff --git a/tests/unit/igraph_turan.c b/tests/unit/igraph_turan.c new file mode 100644 index 0000000..1fe756e --- /dev/null +++ b/tests/unit/igraph_turan.c @@ -0,0 +1,167 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g_turan; + igraph_t g_multipartite; + igraph_vector_int_t partitions; + igraph_vector_int_t types_turan; + igraph_vector_int_t types_multipartite; + igraph_bool_t res; + + printf("Empty graph with zero vertices:"); + igraph_vector_int_init(&types_turan, 0); + igraph_turan(&g_turan, &types_turan, 0, 10); + + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + + + printf("\nTuran graph with 10 vertices and 1 partition:"); + igraph_vector_int_init(&types_turan, 0); + igraph_turan(&g_turan, &types_turan, 10, 1); + + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + + + printf("\nTuran graph with 4 vertices and 6 partitions\n"); + printf("gives 4 vertices and 1 partition:"); + igraph_vector_int_init(&types_turan, 0); + igraph_turan(&g_turan, &types_turan, 4, 6); + + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + + igraph_vector_int_init(&partitions, 4); + igraph_vector_int_init(&types_multipartite, 0); + igraph_vector_int_init(&types_turan, 0); + + VECTOR(partitions)[0] = 3; + VECTOR(partitions)[1] = 3; + VECTOR(partitions)[2] = 3; + VECTOR(partitions)[3] = 4; + + igraph_full_multipartite(&g_multipartite, &types_multipartite, &partitions, + IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_turan(&g_turan, &types_turan, 13, 4); + + printf("\nTuran graph with 13 vertices and 4 partitions:"); + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_isomorphic(&g_multipartite, &g_turan, &res); + if (res) { + printf("\nTuran(13,4) is isomorphic to full multipartite(4,3,3,3)\n"); + } + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types_multipartite); + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + igraph_destroy(&g_multipartite); + + + igraph_vector_int_init(&partitions, 3); + igraph_vector_int_init(&types_multipartite, 0); + igraph_vector_int_init(&types_turan, 0); + + VECTOR(partitions)[0] = 3; + VECTOR(partitions)[1] = 3; + VECTOR(partitions)[2] = 2; + + igraph_full_multipartite(&g_multipartite, &types_multipartite, &partitions, + IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_turan(&g_turan, &types_turan, 8, 3); + + printf("\nTuran graph with 8 vertices and 3 partitions:"); + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_isomorphic(&g_multipartite, &g_turan, &res); + if (res) { + printf("\nTuran(8,3) is isomorphic to full multipartite(3,3,2)\n"); + } + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types_multipartite); + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + igraph_destroy(&g_multipartite); + + + igraph_vector_int_init(&partitions, 3); + igraph_vector_int_init(&types_multipartite, 0); + igraph_vector_int_init(&types_turan, 0); + + VECTOR(partitions)[0] = 2; + VECTOR(partitions)[1] = 2; + VECTOR(partitions)[2] = 2; + + igraph_full_multipartite(&g_multipartite, &types_multipartite, &partitions, + IGRAPH_UNDIRECTED, IGRAPH_ALL); + igraph_turan(&g_turan, &types_turan, 6, 3); + + printf("\nTuran graph with 6 vertices and 3 partitions:"); + print_graph_canon(&g_turan); + + printf("\nPartition type:\n"); + igraph_vector_int_print(&types_turan); + + igraph_isomorphic(&g_multipartite, &g_turan, &res); + if (res) { + printf("\nTuran(6,3) is isomorphic to full multipartite(2,2,2)\n"); + } + + igraph_vector_int_destroy(&partitions); + igraph_vector_int_destroy(&types_multipartite); + igraph_vector_int_destroy(&types_turan); + igraph_destroy(&g_turan); + igraph_destroy(&g_multipartite); + + VERIFY_FINALLY_STACK(); + + return IGRAPH_SUCCESS; +} diff --git a/tests/unit/igraph_turan.out b/tests/unit/igraph_turan.out new file mode 100644 index 0000000..06d8c1f --- /dev/null +++ b/tests/unit/igraph_turan.out @@ -0,0 +1,156 @@ +Empty graph with zero vertices:directed: false +vcount: 0 +edges: { +} + +Partition type: + + +Turan graph with 10 vertices and 1 partition:directed: false +vcount: 10 +edges: { +} + +Partition type: +0 0 0 0 0 0 0 0 0 0 + +Turan graph with 4 vertices and 6 partitions +gives 4 vertices and 1 partition:directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +} + +Partition type: +0 1 2 3 + +Turan graph with 13 vertices and 4 partitions:directed: false +vcount: 13 +edges: { +0 4 +0 5 +0 6 +0 7 +0 8 +0 9 +0 10 +0 11 +0 12 +1 4 +1 5 +1 6 +1 7 +1 8 +1 9 +1 10 +1 11 +1 12 +2 4 +2 5 +2 6 +2 7 +2 8 +2 9 +2 10 +2 11 +2 12 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +3 10 +3 11 +3 12 +4 7 +4 8 +4 9 +4 10 +4 11 +4 12 +5 7 +5 8 +5 9 +5 10 +5 11 +5 12 +6 7 +6 8 +6 9 +6 10 +6 11 +6 12 +7 10 +7 11 +7 12 +8 10 +8 11 +8 12 +9 10 +9 11 +9 12 +} + +Partition type: +0 0 0 0 1 1 1 2 2 2 3 3 3 + +Turan(13,4) is isomorphic to full multipartite(4,3,3,3) + +Turan graph with 8 vertices and 3 partitions:directed: false +vcount: 8 +edges: { +0 3 +0 4 +0 5 +0 6 +0 7 +1 3 +1 4 +1 5 +1 6 +1 7 +2 3 +2 4 +2 5 +2 6 +2 7 +3 6 +3 7 +4 6 +4 7 +5 6 +5 7 +} + +Partition type: +0 0 0 1 1 1 2 2 + +Turan(8,3) is isomorphic to full multipartite(3,3,2) + +Turan graph with 6 vertices and 3 partitions:directed: false +vcount: 6 +edges: { +0 2 +0 3 +0 4 +0 5 +1 2 +1 3 +1 4 +1 5 +2 4 +2 5 +3 4 +3 5 +} + +Partition type: +0 0 1 1 2 2 + +Turan(6,3) is isomorphic to full multipartite(2,2,2) diff --git a/tests/unit/igraph_unfold_tree.c b/tests/unit/igraph_unfold_tree.c new file mode 100644 index 0000000..edbb05d --- /dev/null +++ b/tests/unit/igraph_unfold_tree.c @@ -0,0 +1,82 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t *g, igraph_vector_int_t *roots) { + igraph_t tree; + igraph_vector_int_t vertex_index; + + igraph_vector_int_init(&vertex_index, 0); + + igraph_unfold_tree(g, &tree, IGRAPH_OUT, roots, &vertex_index); + + printf("Tree:\n"); + print_graph_canon(&tree); + printf("Vertex indices:\n"); + igraph_vector_int_print(&vertex_index); + printf("\n"); + + igraph_destroy(&tree); + igraph_destroy(g); + igraph_vector_int_destroy(roots); + igraph_vector_int_destroy(&vertex_index); +} + +int main(void) { + igraph_t g, tree; + igraph_vector_int_t roots; + + printf("Graph with no vertices\n"); + igraph_vector_int_init(&roots, 0); + igraph_small(&g, 0, IGRAPH_UNDIRECTED, -1); + print_and_destroy(&g, &roots); + + printf("Graph with loops, multiple edges and isolated vertex:\n"); + igraph_vector_int_init_int(&roots, 1, 0); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, &roots); + + printf("Same graph, undirected:\n"); + igraph_vector_int_init_int(&roots, 1, 0); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, -1); + print_and_destroy(&g, &roots); + + printf("Almost same graph, two roots:\n"); + igraph_vector_int_init_int(&roots, 2, 0, 5); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, 5,5, 5,5, -1); + print_and_destroy(&g, &roots); + + printf("Same graph, multiple roots in same tree:\n"); + igraph_vector_int_init_int(&roots, 5, 0, 0, 1, 2, 3); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, 5,5, 5,5, -1); + print_and_destroy(&g, &roots); + + VERIFY_FINALLY_STACK(); + + printf("Check error for root out of bounds.\n"); + igraph_vector_int_init_int(&roots, 5, -1, 0, 1, 2, 3); + igraph_small(&g, 6, 1, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, 3,4, 5,5, 5,5, -1); + CHECK_ERROR(igraph_unfold_tree(&g, &tree, IGRAPH_OUT, &roots, NULL), IGRAPH_EINVVID); + igraph_destroy(&g); + igraph_vector_int_destroy(&roots); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_unfold_tree.out b/tests/unit/igraph_unfold_tree.out new file mode 100644 index 0000000..b32a1cc --- /dev/null +++ b/tests/unit/igraph_unfold_tree.out @@ -0,0 +1,80 @@ +Graph with no vertices +Tree: +directed: false +vcount: 0 +edges: { +} +Vertex indices: + + +Graph with loops, multiple edges and isolated vertex: +Tree: +directed: true +vcount: 10 +edges: { +0 1 +0 2 +1 3 +2 7 +2 8 +3 4 +3 9 +6 1 +} +Vertex indices: +0 1 2 3 4 5 1 0 3 4 + +Same graph, undirected: +Tree: +directed: false +vcount: 10 +edges: { +0 1 +0 2 +0 6 +1 3 +1 7 +2 8 +3 4 +3 9 +} +Vertex indices: +0 1 2 3 4 5 2 1 3 4 + +Almost same graph, two roots: +Tree: +directed: true +vcount: 12 +edges: { +0 1 +0 2 +1 3 +2 7 +2 8 +3 4 +3 9 +6 1 +10 5 +11 5 +} +Vertex indices: +0 1 2 3 4 5 1 0 3 4 5 5 + +Same graph, multiple roots in same tree: +Tree: +directed: true +vcount: 10 +edges: { +0 1 +0 2 +1 3 +2 7 +2 8 +3 4 +3 9 +6 1 +} +Vertex indices: +0 1 2 3 4 5 1 0 3 4 + +Check error for root out of bounds. diff --git a/tests/unit/igraph_union.c b/tests/unit/igraph_union.c new file mode 100644 index 0000000..671222f --- /dev/null +++ b/tests/unit/igraph_union.c @@ -0,0 +1,155 @@ +/* + igraph library. + Copyright (C) 2006-2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +#include "test_utilities.h" + +void print_and_destroy_graph_and_maps(igraph_t *uni, igraph_vector_int_list_t *list) { + printf("Union graph:\n"); + print_graph(uni); + igraph_destroy(uni); + + printf("Edge maps:\n"); + print_vector_int_list(list); + igraph_vector_int_list_clear(list); +} + +int main(void) { + igraph_t left, right, uni; + igraph_vector_int_t edges; + igraph_vector_ptr_t glist; + igraph_vector_int_t edge_map1, edge_map2; + igraph_vector_int_list_t edgemaps; + + printf("BINARY VERSION\n"); + + igraph_vector_int_init(&edge_map1, 0); + igraph_vector_int_init(&edge_map2, 0); + + igraph_small(&left, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 2, 2, 3, + -1); + + igraph_small(&right, 0, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 2, 2, 4, + -1); + + igraph_union(&uni, &left, &right, &edge_map1, &edge_map2); + printf("Union graph:\n"); + print_graph(&uni); + printf("Edge maps:\n"); + print_vector_int(&edge_map1); + print_vector_int(&edge_map2); + + igraph_destroy(&uni); + igraph_destroy(&left); + igraph_destroy(&right); + + igraph_vector_int_destroy(&edge_map1); + igraph_vector_int_destroy(&edge_map2); + + printf("\n\nN-ARY VERSION\n"); + + /* Empty graph list */ + + printf("\nEmpty graph list:\n"); + igraph_vector_ptr_init(&glist, 0); + igraph_vector_int_list_init(&edgemaps, 0); + igraph_union_many(&uni, &glist, &edgemaps); + print_and_destroy_graph_and_maps(&uni, &edgemaps); + igraph_vector_ptr_destroy(&glist); + + /* Non-empty graph list */ + + printf("\nNon-empty directed graph list 1:\n"); + igraph_vector_ptr_init(&glist, 10); + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + VECTOR(glist)[i] = IGRAPH_CALLOC(1, igraph_t); + igraph_small(VECTOR(glist)[i], 0, IGRAPH_DIRECTED, + 0, 1, 1, 0, -1); + } + + igraph_union_many(&uni, &glist, &edgemaps); + + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + igraph_destroy(VECTOR(glist)[i]); + IGRAPH_FREE(VECTOR(glist)[i]); + } + + print_and_destroy_graph_and_maps(&uni, &edgemaps); + igraph_vector_ptr_destroy(&glist); + + /* Another non-empty graph list */ + + printf("\nNon-empty directed graph list 2:\n"); + + igraph_vector_ptr_init(&glist, 10); + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + VECTOR(glist)[i] = IGRAPH_CALLOC(1, igraph_t); + igraph_vector_int_init(&edges, 4); + VECTOR(edges)[0] = i; VECTOR(edges)[1] = i+1; + VECTOR(edges)[2] = 1; VECTOR(edges)[3] = 0; + igraph_create(VECTOR(glist)[i], &edges, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&edges); + } + + igraph_union_many(&uni, &glist, &edgemaps); + igraph_write_graph_edgelist(&uni, stdout); + + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + igraph_destroy(VECTOR(glist)[i]); + IGRAPH_FREE(VECTOR(glist)[i]); + } + + print_and_destroy_graph_and_maps(&uni, &edgemaps); + igraph_vector_ptr_destroy(&glist); + + /* Undirected graph list*/ + + printf("\nUndirected graph list:\n"); + + igraph_vector_ptr_init(&glist, 10); + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + VECTOR(glist)[i] = IGRAPH_CALLOC(1, igraph_t); + igraph_vector_int_init(&edges, 4); + VECTOR(edges)[0] = i; VECTOR(edges)[1] = i+1; + VECTOR(edges)[2] = 1; VECTOR(edges)[3] = 0; + igraph_create(VECTOR(glist)[i], &edges, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_destroy(&edges); + } + + igraph_union_many(&uni, &glist, &edgemaps); + igraph_write_graph_edgelist(&uni, stdout); + + for (igraph_int_t i = 0; i < igraph_vector_ptr_size(&glist); i++) { + igraph_destroy(VECTOR(glist)[i]); + IGRAPH_FREE(VECTOR(glist)[i]); + } + + print_and_destroy_graph_and_maps(&uni, &edgemaps); + igraph_vector_ptr_destroy(&glist); + + igraph_vector_int_list_destroy(&edgemaps); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_union.out b/tests/unit/igraph_union.out new file mode 100644 index 0000000..e97d17c --- /dev/null +++ b/tests/unit/igraph_union.out @@ -0,0 +1,133 @@ +BINARY VERSION +Union graph: +directed: true +vcount: 5 +edges: { +0 1 +1 2 +2 2 +2 3 +2 4 +} +Edge maps: +( 0 1 2 3 ) +( 0 1 2 4 ) + + +N-ARY VERSION + +Empty graph list: +Union graph: +directed: true +vcount: 0 +edges: { +} +Edge maps: +{ +} + +Non-empty directed graph list 1: +Union graph: +directed: true +vcount: 2 +edges: { +1 0 +0 1 +} +Edge maps: +{ + 0: ( 1 0 ) + 1: ( 1 0 ) + 2: ( 1 0 ) + 3: ( 1 0 ) + 4: ( 1 0 ) + 5: ( 1 0 ) + 6: ( 1 0 ) + 7: ( 1 0 ) + 8: ( 1 0 ) + 9: ( 1 0 ) +} + +Non-empty directed graph list 2: +0 1 +1 0 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +Union graph: +directed: true +vcount: 11 +edges: { +9 10 +8 9 +7 8 +6 7 +5 6 +4 5 +3 4 +2 3 +1 2 +1 0 +0 1 +} +Edge maps: +{ + 0: ( 10 9 ) + 1: ( 8 9 ) + 2: ( 7 9 ) + 3: ( 6 9 ) + 4: ( 5 9 ) + 5: ( 4 9 ) + 6: ( 3 9 ) + 7: ( 2 9 ) + 8: ( 1 9 ) + 9: ( 0 9 ) +} + +Undirected graph list: +0 1 +0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +Union graph: +directed: false +vcount: 11 +edges: { +10 9 +9 8 +8 7 +7 6 +6 5 +5 4 +4 3 +3 2 +2 1 +1 0 +1 0 +} +Edge maps: +{ + 0: ( 10 9 ) + 1: ( 8 9 ) + 2: ( 7 9 ) + 3: ( 6 9 ) + 4: ( 5 9 ) + 5: ( 4 9 ) + 6: ( 3 9 ) + 7: ( 2 9 ) + 8: ( 1 9 ) + 9: ( 0 9 ) +} diff --git a/tests/unit/igraph_vector_floor.c b/tests/unit/igraph_vector_floor.c new file mode 100644 index 0000000..64684cf --- /dev/null +++ b/tests/unit/igraph_vector_floor.c @@ -0,0 +1,41 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +int main(void) { + igraph_vector_t from; + igraph_vector_int_t to; + + igraph_vector_init_real(&from, 9, -0.6, -0.5, -0.4, -0.0, 0.0, 0.4, 0.5, 0.6, 1.1); + igraph_vector_int_init(&to, 0); + + printf("From:\n"); + igraph_vector_print(&from); + IGRAPH_ASSERT(igraph_vector_floor(&from, &to) == IGRAPH_SUCCESS); + + printf("To:\n"); + igraph_vector_int_print(&to); + + igraph_vector_int_destroy(&to); + igraph_vector_destroy(&from); + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_vector_floor.out b/tests/unit/igraph_vector_floor.out new file mode 100644 index 0000000..88c44aa --- /dev/null +++ b/tests/unit/igraph_vector_floor.out @@ -0,0 +1,4 @@ +From: +-0.6 -0.5 -0.4 -0 0 0.4 0.5 0.6 1.1 +To: +-1 -1 -1 0 0 0 0 0 1 diff --git a/tests/unit/igraph_vector_lex_cmp.c b/tests/unit/igraph_vector_lex_cmp.c new file mode 100644 index 0000000..4739f21 --- /dev/null +++ b/tests/unit/igraph_vector_lex_cmp.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_t v1, v2, v3, v4, v5, v6, v7, v8; + + igraph_vector_init_real(&v1, 3, 1e30, 2e30, 9e30); + igraph_vector_init_real(&v2, 3, 1e30, 2e30, 3e30); + igraph_vector_init_real(&v3, 2, 1e30, 2e30); + igraph_vector_init_real(&v4, 0); + igraph_vector_init_real(&v5, 3, 1e30, 2e30, 9e30); + igraph_vector_init_real(&v6, 0); + igraph_vector_init_real(&v7, 3, 9e30, 2e30, 1e30); + igraph_vector_init_real(&v8, 2, 3e30, 3e30); + + igraph_vector_t *vectors[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8}; + size_t n = sizeof(vectors) / sizeof(igraph_vector_t *); + + printf("Lexicographical ordering:\n"); + igraph_qsort(vectors, n, sizeof(igraph_vector_t *), igraph_vector_lex_cmp_untyped); + + for (size_t i = 0; i < n; i++) { + print_vector(vectors[i]); + } + + printf("\nColexicographical ordering:\n"); + igraph_qsort(vectors, n, sizeof(igraph_vector_t *), igraph_vector_colex_cmp_untyped); + + for (size_t i = 0; i < n; i++) { + print_vector(vectors[i]); + igraph_vector_destroy(vectors[i]); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_vector_lex_cmp.out b/tests/unit/igraph_vector_lex_cmp.out new file mode 100644 index 0000000..b65d3a8 --- /dev/null +++ b/tests/unit/igraph_vector_lex_cmp.out @@ -0,0 +1,19 @@ +Lexicographical ordering: +( ) +( ) +( 1e+30 2e+30 ) +( 1e+30 2e+30 3e+30 ) +( 1e+30 2e+30 9e+30 ) +( 1e+30 2e+30 9e+30 ) +( 3e+30 3e+30 ) +( 9e+30 2e+30 1e+30 ) + +Colexicographical ordering: +( ) +( ) +( 9e+30 2e+30 1e+30 ) +( 1e+30 2e+30 ) +( 1e+30 2e+30 3e+30 ) +( 3e+30 3e+30 ) +( 1e+30 2e+30 9e+30 ) +( 1e+30 2e+30 9e+30 ) diff --git a/tests/unit/igraph_vertex_disjoint_paths.c b/tests/unit/igraph_vertex_disjoint_paths.c new file mode 100644 index 0000000..04941fe --- /dev/null +++ b/tests/unit/igraph_vertex_disjoint_paths.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_int_t value; + + igraph_small(&g, 7, IGRAPH_DIRECTED, + 0,1, 0,2, 1,2, 1,3, 2,4, 3,4, 3,5, 4,5, 0,5, 3,3, 5,2, 1,3, 3,1, + -1); + + igraph_vertex_disjoint_paths(&g, &value, 0, 5); + IGRAPH_ASSERT(value == 3); + + igraph_vertex_disjoint_paths(&g, &value, 1, 3); + IGRAPH_ASSERT(value == 2); + + igraph_vertex_disjoint_paths(&g, &value, 4, 0); + IGRAPH_ASSERT(value == 0); + + igraph_to_undirected(&g, IGRAPH_TO_UNDIRECTED_EACH, NULL); + + igraph_vertex_disjoint_paths(&g, &value, 4, 0); + IGRAPH_ASSERT(value == 3); + + igraph_vertex_disjoint_paths(&g, &value, 1, 3); + IGRAPH_ASSERT(value == 5); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_voronoi.c b/tests/unit/igraph_voronoi.c new file mode 100644 index 0000000..a69d346 --- /dev/null +++ b/tests/unit/igraph_voronoi.c @@ -0,0 +1,134 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t generators, membership; + igraph_vector_t distances, weights; + + igraph_rng_seed(igraph_rng_default(), 42); + + /* Init result vars */ + + igraph_vector_int_init(&membership, 0); + igraph_vector_init(&distances, 0); + + /* Edge cases */ + + printf("Singleton graph.\n"); + + igraph_empty(&g, 1, IGRAPH_DIRECTED); + igraph_vector_int_init_int(&generators, 1, + 0); + + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_ALL, IGRAPH_VORONOI_RANDOM); + print_vector_int(&membership); + print_vector(&distances); + + igraph_vector_int_destroy(&generators); + igraph_destroy(&g); + + /* Disconnected directed multigraph */ + + printf("\n\nDisconnected directed multigraph.\n"); + + igraph_small(&g, 0, IGRAPH_DIRECTED, + 0,2, 1,2, 2,3, 3,4, 5,4, 6,4, 2,3, 1,1, + -1); + igraph_vector_int_init_int(&generators, 2, + 0, 1); + + printf("\nTiebreaking: 'first'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_FIRST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'last'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_LAST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'random'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_OUT, IGRAPH_VORONOI_RANDOM); + print_vector_int(&membership); + print_vector(&distances); + + igraph_vector_int_destroy(&generators); + igraph_destroy(&g); + + /* Karate club network */ + + printf("\n\nKarate club, unweighted.\n"); + + igraph_famous(&g, "Zachary"); + + igraph_vector_int_init_int(&generators, 3, + 0, 32, 24); + + printf("\nTiebreaking: 'first'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_ALL, IGRAPH_VORONOI_FIRST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'last'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_ALL, IGRAPH_VORONOI_LAST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'random'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, NULL, IGRAPH_ALL, IGRAPH_VORONOI_RANDOM); + print_vector_int(&membership); + print_vector(&distances); + + printf("\n\nKarate club, betweenness weighted.\n"); + + igraph_vector_init(&weights, 0); + igraph_edge_betweenness(&g, NULL, &weights, igraph_ess_all(IGRAPH_EDGEORDER_ID), IGRAPH_UNDIRECTED, false); + + printf("\nTiebreaking: 'first'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_ALL, IGRAPH_VORONOI_FIRST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'last'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_ALL, IGRAPH_VORONOI_LAST); + print_vector_int(&membership); + print_vector(&distances); + + printf("\nTiebreaking: 'random'\n"); + igraph_voronoi(&g, &membership, &distances, &generators, &weights, IGRAPH_ALL, IGRAPH_VORONOI_RANDOM); + print_vector_int(&membership); + print_vector(&distances); + + igraph_vector_destroy(&weights); + + igraph_vector_int_destroy(&generators); + igraph_destroy(&g); + + /* Destroy result vars */ + + igraph_vector_destroy(&distances); + igraph_vector_int_destroy(&membership); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_voronoi.out b/tests/unit/igraph_voronoi.out new file mode 100644 index 0000000..fdbf0e9 --- /dev/null +++ b/tests/unit/igraph_voronoi.out @@ -0,0 +1,48 @@ +Singleton graph. +( 0 ) +( 0 ) + + +Disconnected directed multigraph. + +Tiebreaking: 'first' +( 0 1 0 0 0 -1 -1 ) +( 0 0 1 2 3 Inf Inf ) + +Tiebreaking: 'last' +( 0 1 1 1 1 -1 -1 ) +( 0 0 1 2 3 Inf Inf ) + +Tiebreaking: 'random' +( 0 1 1 0 0 -1 -1 ) +( 0 0 1 2 3 Inf Inf ) + + +Karate club, unweighted. + +Tiebreaking: 'first' +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 0 1 1 0 1 1 ) +( 0 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 0 1 2 1 2 1 1 1 0 1 ) + +Tiebreaking: 'last' +( 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 2 1 1 2 1 1 ) +( 0 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 0 1 2 1 2 1 1 1 0 1 ) + +Tiebreaking: 'random' +( 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 1 1 1 2 1 1 ) +( 0 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 0 1 2 1 2 1 1 1 0 1 ) + + +Karate club, betweenness weighted. + +Tiebreaking: 'first' +( 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 1 1 1 2 1 1 ) +( 0 14.1667 20.2143 11.5 29.3333 32 32 12.8024 15.0667 21.2286 29.3333 33 18.4 19.8714 13.5111 13.5111 48.5 22.5095 13.5111 22.3762 13.5111 22.5095 13.5111 12.5333 0 2.36667 15.6302 10.4667 18.3952 13.0873 9.56667 22.5 0 4.61429 ) + +Tiebreaking: 'last' +( 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 1 1 1 2 1 1 ) +( 0 14.1667 20.2143 11.5 29.3333 32 32 12.8024 15.0667 21.2286 29.3333 33 18.4 19.8714 13.5111 13.5111 48.5 22.5095 13.5111 22.3762 13.5111 22.5095 13.5111 12.5333 0 2.36667 15.6302 10.4667 18.3952 13.0873 9.56667 22.5 0 4.61429 ) + +Tiebreaking: 'random' +( 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 2 2 1 2 1 1 1 2 1 1 ) +( 0 14.1667 20.2143 11.5 29.3333 32 32 12.8024 15.0667 21.2286 29.3333 33 18.4 19.8714 13.5111 13.5111 48.5 22.5095 13.5111 22.3762 13.5111 22.5095 13.5111 12.5333 0 2.36667 15.6302 10.4667 18.3952 13.0873 9.56667 22.5 0 4.61429 ) diff --git a/tests/unit/igraph_weighted_adjacency.c b/tests/unit/igraph_weighted_adjacency.c new file mode 100644 index 0000000..9d5dbd2 --- /dev/null +++ b/tests/unit/igraph_weighted_adjacency.c @@ -0,0 +1,331 @@ +/* + igraph library. + Copyright (C) 2006-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +//TODO make public? +igraph_error_t igraph_is_same_graph_weighted(const igraph_t *graph1, const igraph_t *graph2, igraph_bool_t *res, igraph_vector_t *weights1, igraph_vector_t *weights2) { + igraph_int_t nv1 = igraph_vcount(graph1); + igraph_int_t nv2 = igraph_vcount(graph2); + igraph_int_t ne1 = igraph_ecount(graph1); + igraph_int_t ne2 = igraph_ecount(graph2); + igraph_int_t i, eid1, eid2; + + *res = 0; /* Assume that the graphs differ */ + + /* Check for same number of vertices/edges */ + if ((nv1 != nv2) || (ne1 != ne2)) { + return IGRAPH_SUCCESS; + } + + /* Check for same directedness */ + if (igraph_is_directed(graph1) != igraph_is_directed(graph2)) { + return IGRAPH_SUCCESS; + } + + for (i = 0; i < ne1; i++) { + eid1 = VECTOR(graph1->ii)[i]; + eid2 = VECTOR(graph2->ii)[i]; + + /* Check they have the same source */ + if (IGRAPH_FROM(graph1, eid1) != IGRAPH_FROM(graph2, eid2)) { + return IGRAPH_SUCCESS; + } + + /* Check they have the same target */ + if (IGRAPH_TO(graph1, eid1) != IGRAPH_TO(graph2, eid2)) { + return IGRAPH_SUCCESS; + } + + /* Check they have the same weight */ + if (VECTOR(*weights1)[eid1] != VECTOR(*weights2)[eid2]) { + return IGRAPH_SUCCESS; + } + } + + *res = 1; /* No difference was found, graphs are the same */ + return IGRAPH_SUCCESS; +} + +void check_sparsemat(igraph_t *g, igraph_adjacency_t mode, igraph_loops_t loops, igraph_matrix_t *adjmatrix, igraph_vector_t *other_weights) { + igraph_t g_sparse; + igraph_sparsemat_t sparse_adjmatrix, sparse_adjmatrix_comp; + igraph_bool_t same; + igraph_vector_t weights; + + igraph_vector_init(&weights, 0); + + igraph_matrix_as_sparsemat(&sparse_adjmatrix, adjmatrix, 0.0001); + igraph_sparsemat_compress(&sparse_adjmatrix, &sparse_adjmatrix_comp); + igraph_sparse_weighted_adjacency(&g_sparse, &sparse_adjmatrix_comp, mode, &weights, loops); + igraph_is_same_graph_weighted(g, &g_sparse, &same, other_weights, &weights); + if (!same) { + printf("Sparse graph differs from non-sparse:\n"); + print_weighted_graph_canon(&g_sparse, &weights); + exit(1); + } + igraph_sparsemat_destroy(&sparse_adjmatrix); + igraph_sparsemat_destroy(&sparse_adjmatrix_comp); + igraph_destroy(&g_sparse); + igraph_vector_destroy(&weights); +} + +void test_single_matrix(igraph_matrix_t* mat, igraph_adjacency_t mode) { + igraph_t g; + igraph_vector_t weights; + + igraph_vector_init(&weights, 0); + + printf("No loops\n--------\n\n"); + igraph_weighted_adjacency(&g, mat, mode, &weights, IGRAPH_NO_LOOPS); + + print_weighted_graph_canon(&g, &weights); + check_sparsemat(&g, mode, IGRAPH_NO_LOOPS, mat, &weights); + + igraph_destroy(&g); + printf("\n"); + + printf("Loops once\n----------\n\n"); + igraph_weighted_adjacency(&g, mat, mode, &weights, IGRAPH_LOOPS_ONCE); + print_weighted_graph_canon(&g, &weights); + check_sparsemat(&g, mode, IGRAPH_LOOPS_ONCE, mat, &weights); + igraph_destroy(&g); + printf("\n"); + + printf("Loops twice\n-----------\n\n"); + igraph_weighted_adjacency(&g, mat, mode, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&g, &weights); + check_sparsemat(&g, mode, IGRAPH_LOOPS_TWICE, mat, &weights); + igraph_destroy(&g); + printf("\n"); + + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); +} + +void check_error(const igraph_matrix_t *adjmatrix, igraph_adjacency_t mode, igraph_loops_t loops, igraph_error_t error) { + igraph_t g; + igraph_sparsemat_t sparse_adjmatrix, sparse_adjmatrix_comp; + igraph_vector_t weights; + igraph_vector_init(&weights, 0); + + igraph_matrix_as_sparsemat(&sparse_adjmatrix, adjmatrix, 0.0001); + igraph_sparsemat_compress(&sparse_adjmatrix, &sparse_adjmatrix_comp); + + CHECK_ERROR(igraph_weighted_adjacency(&g, adjmatrix, mode, &weights, loops), error); + CHECK_ERROR(igraph_sparse_adjacency(&g, &sparse_adjmatrix_comp, mode, loops), error); + + igraph_sparsemat_destroy(&sparse_adjmatrix); + igraph_sparsemat_destroy(&sparse_adjmatrix_comp); + igraph_vector_destroy(&weights); +} + +int main(void) { + igraph_matrix_t mat; + igraph_matrix_t mat_sym; + int m[4][4] = { { 0, 1, 2, 0 }, { 2, 0, 0, 1 }, { 0, 0, 4, 0 }, { 0, 1, 0, 0 } }; + int m_sym[4][4] = { { 0, 2, 2, 0 }, { 2, 0, 0, 1 }, { 2, 0, 4, 0 }, { 0, 1, 0, 0 } }; + igraph_int_t i, j; + + printf("0x0 matrix\n"); + printf("==========\n\n"); + igraph_matrix_init(&mat, 0, 0); + test_single_matrix(&mat, IGRAPH_ADJ_DIRECTED); + igraph_matrix_destroy(&mat); + + igraph_matrix_init(&mat, 4, 4); + for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { + MATRIX(mat, i, j) = m[i][j]; + } + igraph_matrix_init(&mat_sym, 4, 4); + for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { + MATRIX(mat_sym, i, j) = m_sym[i][j]; + } + + /* [ 0 1 2 0 ] + [ 2 0 0 1 ] + [ 0 0 4 0 ] + [ 0 1 0 0 ] */ + printf("IGRAPH_ADJ_DIRECTED\n"); + printf("===================\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_DIRECTED); + + /* [ 0 1 2 0 ] + [ - 0 0 1 ] + [ - - 4 0 ] + [ - - - 0 ] */ + printf("IGRAPH_ADJ_UPPER\n"); + printf("================\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_UPPER); + + /* [ 0 - - - ] + [ 2 0 - - ] + [ 0 0 4 - ] + [ 0 1 0 0 ] */ + printf("IGRAPH_ADJ_LOWER\n"); + printf("================\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_LOWER); + + /* [ 0 1 0 0 ] + [ 1 0 0 1 ] + [ 0 0 4 0 ] + [ 0 1 0 0 ] */ + printf("IGRAPH_ADJ_MIN\n"); + printf("==============\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_MIN); + + /* [ 0 2 2 0 ] + [ 2 0 0 1 ] + [ 2 0 4 0 ] + [ 0 1 0 0 ] */ + printf("IGRAPH_ADJ_MAX\n"); + printf("==============\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_MAX); + + /* [ 0 2 2 0 ] + [ 2 0 0 1 ] + [ 2 0 4 0 ] + [ 0 1 0 0 ] */ + printf("IGRAPH_ADJ_UNDIRECTED\n"); + printf("==============\n\n"); + test_single_matrix(&mat_sym, IGRAPH_ADJ_UNDIRECTED); + + /* [ 0 3 2 0 ] + [ 3 0 0 2 ] + [ 2 0 4 0 ] + [ 0 2 0 0 ] */ + printf("IGRAPH_ADJ_PLUS\n"); + printf("===============\n\n"); + test_single_matrix(&mat, IGRAPH_ADJ_PLUS); + + igraph_matrix_destroy(&mat); + igraph_matrix_destroy(&mat_sym); + + VERIFY_FINALLY_STACK(); + + { + igraph_real_t elem[] = { 0, 1.5, IGRAPH_INFINITY, + IGRAPH_NAN, -IGRAPH_INFINITY, -5.2, + IGRAPH_NAN, 0, IGRAPH_NAN }; + + igraph_real_t elem_sym[] = { 0, IGRAPH_NAN, IGRAPH_INFINITY, + IGRAPH_NAN, -IGRAPH_INFINITY, 0, + IGRAPH_INFINITY, 0, IGRAPH_NAN }; + + igraph_matrix_t am; + igraph_vector_t weights; + igraph_t graph; + + printf("\nTesting NaN and Inf passthrough\n"); + + igraph_vector_init(&weights, 0); + + matrix_init_real_row_major(&am, 3, 3, elem); + + printf("\nIGRAPH_ADJ_DIRECTED\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_DIRECTED, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + printf("\nIGRAPH_ADJ_MAX\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_MAX, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + printf("\nIGRAPH_ADJ_MIN\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_MIN, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + printf("\nIGRAPH_ADJ_LOWER\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_LOWER, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + printf("\nIGRAPH_ADJ_UPPER\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_UPPER, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + printf("\nIGRAPH_ADJ_PLUS\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_PLUS, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + /* Must detect that the matrix is not symmetric and throw an error. */ + CHECK_ERROR( + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_UNDIRECTED, &weights, IGRAPH_LOOPS_TWICE), + IGRAPH_EINVAL); + + igraph_matrix_destroy(&am); + + matrix_init_real_row_major(&am, 3, 3, elem_sym); + + /* Must detect that the matrix is symmetric (desite the NaNs) and succeed. */ + printf("\nIGRAPH_ADJ_UNDIRECTED (symmetric)\n"); + igraph_weighted_adjacency(&graph, &am, IGRAPH_ADJ_UNDIRECTED, &weights, IGRAPH_LOOPS_TWICE); + print_weighted_graph_canon(&graph, &weights); + igraph_destroy(&graph); + + igraph_matrix_destroy(&am); + + igraph_vector_destroy(&weights); + + printf("\n"); + } + + VERIFY_FINALLY_STACK(); + + { + printf("Check handling of non-square matrix error.\n"); + igraph_real_t e[] = {1, 2, 0}; + const igraph_matrix_t mat = igraph_matrix_view(e, 1, 3); + check_error(&mat, IGRAPH_ADJ_DIRECTED, IGRAPH_NO_LOOPS, IGRAPH_EINVAL); + } + + { + printf("Check handling of invalid adjacency mode.\n"); + igraph_real_t e[] = {0, 2, 0, 3, 0, 4, 0, 5, 6}; + const igraph_matrix_t mat = igraph_matrix_view(e, 3, 3); + check_error(&mat, (igraph_adjacency_t) 42, IGRAPH_LOOPS_TWICE, IGRAPH_EINVAL); + } + + { + printf("Check error for 0x1 matrix.\n"); + igraph_matrix_init(&mat, 0, 1); + check_error(&mat, IGRAPH_ADJ_DIRECTED, IGRAPH_LOOPS_TWICE, IGRAPH_EINVAL); + igraph_matrix_destroy(&mat); + } + + { + printf("Check error for non-symmetric matrix and IGRAPH_ADJ_UNDIRECTED.\n"); + igraph_real_t e[] = {0, 2, 0, 3, 0, 4, 0, 5, 6}; + const igraph_matrix_t mat = igraph_matrix_view(e, 3, 3); + check_error(&mat, IGRAPH_ADJ_UNDIRECTED, IGRAPH_LOOPS_TWICE, IGRAPH_EINVAL); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_weighted_adjacency.out b/tests/unit/igraph_weighted_adjacency.out new file mode 100644 index 0000000..1147840 --- /dev/null +++ b/tests/unit/igraph_weighted_adjacency.out @@ -0,0 +1,376 @@ +0x0 matrix +========== + +No loops +-------- + +directed: true +vcount: 0 +edges: { +} + +Loops once +---------- + +directed: true +vcount: 0 +edges: { +} + +Loops twice +----------- + +directed: true +vcount: 0 +edges: { +} + +IGRAPH_ADJ_DIRECTED +=================== + +No loops +-------- + +directed: true +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 0: 2 +1 3: 1 +3 1: 1 +} + +Loops once +---------- + +directed: true +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 0: 2 +1 3: 1 +2 2: 4 +3 1: 1 +} + +Loops twice +----------- + +directed: true +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 0: 2 +1 3: 1 +2 2: 4 +3 1: 1 +} + +IGRAPH_ADJ_UPPER +================ + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 3: 1 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 3: 1 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +0 2: 2 +1 3: 1 +2 2: 4 +} + +IGRAPH_ADJ_LOWER +================ + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +1 3: 1 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +1 3: 1 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +1 3: 1 +2 2: 4 +} + +IGRAPH_ADJ_MIN +============== + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +1 3: 1 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +1 3: 1 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 1 +1 3: 1 +2 2: 2 +} + +IGRAPH_ADJ_MAX +============== + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +2 2: 2 +} + +IGRAPH_ADJ_UNDIRECTED +============== + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 2 +0 2: 2 +1 3: 1 +2 2: 2 +} + +IGRAPH_ADJ_PLUS +=============== + +No loops +-------- + +directed: false +vcount: 4 +edges: { +0 1: 3 +0 2: 2 +1 3: 2 +} + +Loops once +---------- + +directed: false +vcount: 4 +edges: { +0 1: 3 +0 2: 2 +1 3: 2 +2 2: 4 +} + +Loops twice +----------- + +directed: false +vcount: 4 +edges: { +0 1: 3 +0 2: 2 +1 3: 2 +2 2: 2 +} + + +Testing NaN and Inf passthrough + +IGRAPH_ADJ_DIRECTED +directed: true +vcount: 3 +edges: { +0 1: 1.5 +0 2: Inf +1 0: NaN +1 1: -Inf +1 2: -5.2 +2 0: NaN +2 2: NaN +} + +IGRAPH_ADJ_MAX +directed: false +vcount: 3 +edges: { +0 1: NaN +0 2: NaN +1 1: -Inf +2 2: NaN +} + +IGRAPH_ADJ_MIN +directed: false +vcount: 3 +edges: { +0 1: NaN +0 2: NaN +1 1: -Inf +1 2: -5.2 +2 2: NaN +} + +IGRAPH_ADJ_LOWER +directed: false +vcount: 3 +edges: { +0 1: NaN +0 2: NaN +1 1: -Inf +2 2: NaN +} + +IGRAPH_ADJ_UPPER +directed: false +vcount: 3 +edges: { +0 1: 1.5 +0 2: Inf +1 1: -Inf +1 2: -5.2 +2 2: NaN +} + +IGRAPH_ADJ_PLUS +directed: false +vcount: 3 +edges: { +0 1: NaN +0 2: NaN +1 1: -Inf +1 2: -5.2 +2 2: NaN +} + +IGRAPH_ADJ_UNDIRECTED (symmetric) +directed: false +vcount: 3 +edges: { +0 1: NaN +0 2: Inf +1 1: -Inf +2 2: NaN +} + +Check handling of non-square matrix error. +Check handling of invalid adjacency mode. +Check error for 0x1 matrix. +Check error for non-symmetric matrix and IGRAPH_ADJ_UNDIRECTED. diff --git a/tests/unit/igraph_weighted_biadjacency.c b/tests/unit/igraph_weighted_biadjacency.c new file mode 100644 index 0000000..8c664cb --- /dev/null +++ b/tests/unit/igraph_weighted_biadjacency.c @@ -0,0 +1,108 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_matrix_t *biadjmat, igraph_bool_t directed, igraph_neimode_t mode) { + igraph_t g; + igraph_vector_bool_t types; + igraph_vector_t weights; + + igraph_vector_bool_init(&types, 0); + igraph_vector_init(&weights, 0); + + igraph_weighted_biadjacency(&g, &types, &weights, biadjmat, directed, mode); + + print_weighted_graph_canon(&g, &weights); + printf("types: "); + igraph_vector_bool_print(&types); + + igraph_destroy(&g); + igraph_vector_destroy(&weights); + igraph_vector_bool_destroy(&types); + + igraph_matrix_destroy(biadjmat); +} + +int main(void) { + igraph_matrix_t biadjmat; + + { + printf("Bipartite adjacency matrix with no rows and no columns:\n"); + igraph_matrix_init(&biadjmat, 0, 0); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL); + } + + { + printf("Bipartite adjacency matrix with some rows and no columns:\n"); + igraph_matrix_init(&biadjmat, 3, 0); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL); + } + + { + printf("\nBipartite adjacency matrix for two vertices:\n"); + igraph_real_t elem[] = {1.25}; + matrix_init_real_row_major(&biadjmat, 1, 1, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL); + } + + { + printf("\nBipartite adjacency matrix for five vertices:\n"); + igraph_real_t elem[] = {0.0, -4.5, 2.3, + -0.1, 0.0, 0.0}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_ALL); + } + + { + printf("\nSame graph, IGRAPH_OUT:\n"); + igraph_real_t elem[] = {0.0, -4.5, 2.3, + -0.1, 0.0, 0.0}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_OUT); + } + + { + printf("\nSame graph, IGRAPH_IN:\n"); + igraph_real_t elem[] = {0.0, -4.5, 2.3, + -0.1, 0.0, 0.0}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_DIRECTED, IGRAPH_IN); + } + + { + printf("\nSame graph, undirected:\n"); + igraph_real_t elem[] = {0.0, -4.5, 2.3, + -0.1, 0.0, 0.0}; + matrix_init_real_row_major(&biadjmat, 2, 3, elem); + print_and_destroy(&biadjmat, IGRAPH_UNDIRECTED, IGRAPH_ALL); + } + + { + printf("\nInfinity and NaN:\n"); + igraph_real_t elem[] = {0.0, -4.5, + IGRAPH_INFINITY, -IGRAPH_INFINITY, + IGRAPH_NAN, 0.0}; + matrix_init_real_row_major(&biadjmat, 3, 2, elem); + print_and_destroy(&biadjmat, IGRAPH_UNDIRECTED, IGRAPH_ALL); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_weighted_biadjacency.out b/tests/unit/igraph_weighted_biadjacency.out new file mode 100644 index 0000000..478c300 --- /dev/null +++ b/tests/unit/igraph_weighted_biadjacency.out @@ -0,0 +1,75 @@ +Bipartite adjacency matrix with no rows and no columns: +directed: true +vcount: 0 +edges: { +} +types: +Bipartite adjacency matrix with some rows and no columns: +directed: true +vcount: 3 +edges: { +} +types: 0 0 0 + +Bipartite adjacency matrix for two vertices: +directed: true +vcount: 2 +edges: { +0 1: 1.25 +1 0: 1.25 +} +types: 0 1 + +Bipartite adjacency matrix for five vertices: +directed: true +vcount: 5 +edges: { +0 3: -4.5 +0 4: 2.3 +1 2: -0.1 +2 1: -0.1 +3 0: -4.5 +4 0: 2.3 +} +types: 0 0 1 1 1 + +Same graph, IGRAPH_OUT: +directed: true +vcount: 5 +edges: { +0 3: -4.5 +0 4: 2.3 +1 2: -0.1 +} +types: 0 0 1 1 1 + +Same graph, IGRAPH_IN: +directed: true +vcount: 5 +edges: { +2 1: -0.1 +3 0: -4.5 +4 0: 2.3 +} +types: 0 0 1 1 1 + +Same graph, undirected: +directed: false +vcount: 5 +edges: { +0 3: -4.5 +0 4: 2.3 +1 2: -0.1 +} +types: 0 0 1 1 1 + +Infinity and NaN: +directed: false +vcount: 5 +edges: { +0 4: -4.5 +1 3: Inf +1 4: -Inf +2 3: NaN +} +types: 0 0 0 1 1 diff --git a/tests/unit/igraph_weighted_cliques.c b/tests/unit/igraph_weighted_cliques.c new file mode 100644 index 0000000..3f566a7 --- /dev/null +++ b/tests/unit/igraph_weighted_cliques.c @@ -0,0 +1,249 @@ + +#include +#include + +#include "test_utilities.h" + +int compare_vectors(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2) { + igraph_int_t s1 = igraph_vector_int_size(v1); + igraph_int_t s2 = igraph_vector_int_size(v2); + if (s1 < s2) { + return -1; + } + if (s1 > s2) { + return 1; + } + for (igraph_int_t i = 0; i < s1; ++i) { + if (VECTOR(*v1)[i] < VECTOR(*v2)[i]) { + return -1; + } + if (VECTOR(*v1)[i] > VECTOR(*v2)[i]) { + return 1; + } + } + return 0; +} + +/* Takes a pointer vector of vectors. Sorts each vector, then sorts the pointer vector */ +void canonicalize_list(igraph_vector_int_list_t *list) { + igraph_int_t len = igraph_vector_int_list_size(list); + for (igraph_int_t i = 0; i < len; ++i) { + igraph_vector_int_sort(igraph_vector_int_list_get_ptr(list, i)); + } + igraph_vector_int_list_sort(list, &compare_vectors); +} + +/* Prints a clique vector along with its weight */ +void print_weighted_clique(const igraph_vector_int_t *clique, const igraph_vector_t *vertex_weights) { + igraph_int_t i, n = igraph_vector_int_size(clique); + igraph_real_t clique_weight = 0.0; + for (i = 0; i < n; i++) { + int v = VECTOR(*clique)[i]; + clique_weight += vertex_weights ? igraph_vector_get(vertex_weights, v) : 1; + printf(" %d", v); + } + printf(" w=%g\n", clique_weight); +} + +/* Prints a clique list and clears it */ +void print_and_clear_weighted_clique_list(igraph_vector_int_list_t *cliques, const igraph_vector_t *vertex_weights) { + igraph_int_t i, count; + + canonicalize_list(cliques); + + count = igraph_vector_int_list_size(cliques); + for (i = 0; i < count; i++) { + igraph_vector_int_t* v = igraph_vector_int_list_get_ptr(cliques, i); + print_weighted_clique(v, vertex_weights); + } + + igraph_vector_int_list_clear(cliques); +} + +int main(void) { + igraph_t graph; + + const igraph_int_t n = 10; /* number of vertices in test graph */ + + /* edges of the test graph */ + igraph_vector_int_t edges; + igraph_int_t edge_data[] = {0, 1, 0, 6, 0, 7, 0, 8, 0, 9, 1, 2, 1, 3, 1, 4, 1, + 6, 1, 7, 1, 8, 1, 9, 2, 3, 2, 5, 2, 6, 2, 7, 2, 9, + 3, 5, 3, 6, 3, 7, 3, 9, 4, 5, 4, 6, 4, 7, 4, 9, 5, + 8, 6, 7, 6, 8, 7, 8, 8, 9 + }; + + /* vertex weights in test graph, + note that current implementation only supports integer weights */ + igraph_vector_t vertex_weights; + igraph_real_t vertex_weight_data[] = {3., 2., 3., 5., 2., 3., 1., 3., 5., 5.}; + + igraph_vector_int_list_t result; /* result clique list */ + igraph_int_t count; /* number of cliques found */ + + igraph_real_t weighted_clique_no; + + + /* create graph */ + igraph_vector_int_init_array(&edges, edge_data, (sizeof edge_data) / sizeof(edge_data[0])); + igraph_create(&graph, &edges, n, IGRAPH_UNDIRECTED); + + /* set up vertex weight vector */ + igraph_vector_init_array(&vertex_weights, vertex_weight_data, (sizeof vertex_weight_data) / sizeof(vertex_weight_data[0])); + + /* initialize result vector_ptr */ + igraph_vector_int_list_init(&result, 0); + + + /* all weighted cliques above weight 6 */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ false, 6, 0, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " weighted cliques found above weight 6\n", count); + print_and_clear_weighted_clique_list(&result, &vertex_weights); + + + /* all weighted cliques between weights 5 and 10 */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ false, 5, 10, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " weighted cliques found between weights 5 and 10\n", count); + print_and_clear_weighted_clique_list(&result, &vertex_weights); + + + /* maximal weighted cliques above weight 7 */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ true, 7, 0, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " maximal weighted cliques found above weight 7\n", count); + print_and_clear_weighted_clique_list(&result, &vertex_weights); + + + /* maximal weighed cliques beteen weights 5 and 10 */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ true, 5, 10, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " maximal weighted cliques found between weights 5 and 10\n", count); + print_and_clear_weighted_clique_list(&result, &vertex_weights); + + + /* largest weight cliques */ + igraph_largest_weighted_cliques(&graph, &vertex_weights, &result); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " largest weight cliques found\n", count); + print_and_clear_weighted_clique_list(&result, &vertex_weights); + + igraph_weighted_clique_number(&graph, &vertex_weights, &weighted_clique_no); + printf("weighted clique number: %g\n", weighted_clique_no); + + + /* Test unweighted fallback: */ + printf("\nUnweighted case:\n"); + + /* test fallback to unweighted variants: all cliques */ + igraph_weighted_cliques(&graph, NULL, &result, /* maximal= */ false, 4, 5, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " unweighted cliques found between sizes 4 and 5\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test fallback to unweighted variants: maximal cliques */ + igraph_weighted_cliques(&graph, NULL, &result, /* maximal= */ true, 4, 5, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " unweighted maximal cliques found between sizes 4 and 5\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test fallback to unweighted variants: largest cliques */ + igraph_largest_weighted_cliques(&graph, NULL, &result); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " largest unweighted cliques found\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test fallback to unweighted variants: clique number */ + igraph_weighted_clique_number(&graph, NULL, &weighted_clique_no); + printf("unweighted clique number: %g\n", weighted_clique_no); + + /* Here we test unit weights, which should give identical results to the unweighted case: */ + printf("\nUnit weights:\n"); + + igraph_vector_fill(&vertex_weights, 1); + + /* test unit weights: all cliques */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ false, 4, 5, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " cliques with unit weights found between sizes 4 and 5\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test unit weights: maximal cliques */ + igraph_weighted_cliques(&graph, &vertex_weights, &result, /* maximal= */ true, 4, 5, IGRAPH_UNLIMITED); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " maximal cliques with unit weights between sizes 4 and 5\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test unit weights: largest cliques */ + igraph_largest_weighted_cliques(&graph, &vertex_weights, &result); + + count = igraph_vector_int_list_size(&result); + printf("%" IGRAPH_PRId " largest cliques with unit weights found\n", count); + print_and_clear_weighted_clique_list(&result, 0); + + /* test unit weights: clique number */ + igraph_weighted_clique_number(&graph, NULL, &weighted_clique_no); + printf("clique number with unit weights: %g\n", weighted_clique_no); + + + /* free data structures */ + igraph_vector_int_list_destroy(&result); + igraph_vector_destroy(&vertex_weights); + igraph_destroy(&graph); + igraph_vector_int_destroy(&edges); + + /* additional small examples */ + + printf("\nP_2 graph with weights (5, 5):\n"); + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, + 0,1, + -1); + + igraph_vector_init_int(&vertex_weights, 2, + 5, 5); + + igraph_weighted_clique_number(&graph, &vertex_weights, &weighted_clique_no); + printf("weighted clique number: %g\n", weighted_clique_no); + + printf("\nP_2 graph with weights (5, 4):\n"); + VECTOR(vertex_weights)[1] = 4; + + igraph_weighted_clique_number(&graph, &vertex_weights, &weighted_clique_no); + printf("weighted clique number: %g\n", weighted_clique_no); + + igraph_vector_destroy(&vertex_weights); + igraph_destroy(&graph); + + printf("\nK_3 graph with weights (3, 3, 3):\n"); + igraph_full(&graph, 3, IGRAPH_UNDIRECTED, IGRAPH_NO_LOOPS); + + igraph_vector_init_int(&vertex_weights, 3, + 3, 3, 3); + + igraph_weighted_clique_number(&graph, &vertex_weights, &weighted_clique_no); + printf("weighted clique number: %g\n", weighted_clique_no); + + printf("\nK_3 graph with weights (3, 4, 3):\n"); + VECTOR(vertex_weights)[1] = 4; + + igraph_weighted_clique_number(&graph, &vertex_weights, &weighted_clique_no); + printf("weighted clique number: %g\n", weighted_clique_no); + + igraph_vector_destroy(&vertex_weights); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_weighted_cliques.out b/tests/unit/igraph_weighted_cliques.out new file mode 100644 index 0000000..5219e23 --- /dev/null +++ b/tests/unit/igraph_weighted_cliques.out @@ -0,0 +1,204 @@ +63 weighted cliques found above weight 6 + 0 7 w=6 + 0 8 w=8 + 0 9 w=8 + 1 3 w=7 + 1 8 w=7 + 1 9 w=7 + 2 3 w=8 + 2 5 w=6 + 2 7 w=6 + 2 9 w=8 + 3 5 w=8 + 3 6 w=6 + 3 7 w=8 + 3 9 w=10 + 4 9 w=7 + 5 8 w=8 + 6 8 w=6 + 7 8 w=8 + 8 9 w=10 + 0 1 6 w=6 + 0 1 7 w=8 + 0 1 8 w=10 + 0 1 9 w=10 + 0 6 7 w=7 + 0 6 8 w=9 + 0 7 8 w=11 + 0 8 9 w=13 + 1 2 3 w=10 + 1 2 6 w=6 + 1 2 7 w=8 + 1 2 9 w=10 + 1 3 6 w=8 + 1 3 7 w=10 + 1 3 9 w=12 + 1 4 7 w=7 + 1 4 9 w=9 + 1 6 7 w=6 + 1 6 8 w=8 + 1 7 8 w=10 + 1 8 9 w=12 + 2 3 5 w=11 + 2 3 6 w=9 + 2 3 7 w=11 + 2 3 9 w=13 + 2 6 7 w=7 + 3 6 7 w=9 + 4 6 7 w=6 + 6 7 8 w=9 + 0 1 6 7 w=9 + 0 1 6 8 w=11 + 0 1 7 8 w=13 + 0 1 8 9 w=15 + 0 6 7 8 w=12 + 1 2 3 6 w=11 + 1 2 3 7 w=13 + 1 2 3 9 w=15 + 1 2 6 7 w=9 + 1 3 6 7 w=11 + 1 4 6 7 w=8 + 1 6 7 8 w=11 + 2 3 6 7 w=12 + 0 1 6 7 8 w=14 + 1 2 3 6 7 w=14 +53 weighted cliques found between weights 5 and 10 + 3 w=5 + 8 w=5 + 9 w=5 + 0 1 w=5 + 0 7 w=6 + 0 8 w=8 + 0 9 w=8 + 1 2 w=5 + 1 3 w=7 + 1 7 w=5 + 1 8 w=7 + 1 9 w=7 + 2 3 w=8 + 2 5 w=6 + 2 7 w=6 + 2 9 w=8 + 3 5 w=8 + 3 6 w=6 + 3 7 w=8 + 3 9 w=10 + 4 5 w=5 + 4 7 w=5 + 4 9 w=7 + 5 8 w=8 + 6 8 w=6 + 7 8 w=8 + 8 9 w=10 + 0 1 6 w=6 + 0 1 7 w=8 + 0 1 8 w=10 + 0 1 9 w=10 + 0 6 7 w=7 + 0 6 8 w=9 + 1 2 3 w=10 + 1 2 6 w=6 + 1 2 7 w=8 + 1 2 9 w=10 + 1 3 6 w=8 + 1 3 7 w=10 + 1 4 6 w=5 + 1 4 7 w=7 + 1 4 9 w=9 + 1 6 7 w=6 + 1 6 8 w=8 + 1 7 8 w=10 + 2 3 6 w=9 + 2 6 7 w=7 + 3 6 7 w=9 + 4 6 7 w=6 + 6 7 8 w=9 + 0 1 6 7 w=9 + 1 2 6 7 w=9 + 1 4 6 7 w=8 +8 maximal weighted cliques found above weight 7 + 5 8 w=8 + 1 4 9 w=9 + 2 3 5 w=11 + 0 1 8 9 w=15 + 1 2 3 9 w=15 + 1 4 6 7 w=8 + 0 1 6 7 8 w=14 + 1 2 3 6 7 w=14 +4 maximal weighted cliques found between weights 5 and 10 + 4 5 w=5 + 5 8 w=8 + 1 4 9 w=9 + 1 4 6 7 w=8 +2 largest weight cliques found + 0 1 8 9 w=15 + 1 2 3 9 w=15 +weighted clique number: 15 + +Unweighted case: +15 unweighted cliques found between sizes 4 and 5 + 0 1 6 7 w=4 + 0 1 6 8 w=4 + 0 1 7 8 w=4 + 0 1 8 9 w=4 + 0 6 7 8 w=4 + 1 2 3 6 w=4 + 1 2 3 7 w=4 + 1 2 3 9 w=4 + 1 2 6 7 w=4 + 1 3 6 7 w=4 + 1 4 6 7 w=4 + 1 6 7 8 w=4 + 2 3 6 7 w=4 + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +5 unweighted maximal cliques found between sizes 4 and 5 + 0 1 8 9 w=4 + 1 2 3 9 w=4 + 1 4 6 7 w=4 + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +2 largest unweighted cliques found + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +unweighted clique number: 5 + +Unit weights: +15 cliques with unit weights found between sizes 4 and 5 + 0 1 6 7 w=4 + 0 1 6 8 w=4 + 0 1 7 8 w=4 + 0 1 8 9 w=4 + 0 6 7 8 w=4 + 1 2 3 6 w=4 + 1 2 3 7 w=4 + 1 2 3 9 w=4 + 1 2 6 7 w=4 + 1 3 6 7 w=4 + 1 4 6 7 w=4 + 1 6 7 8 w=4 + 2 3 6 7 w=4 + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +5 maximal cliques with unit weights between sizes 4 and 5 + 0 1 8 9 w=4 + 1 2 3 9 w=4 + 1 4 6 7 w=4 + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +2 largest cliques with unit weights found + 0 1 6 7 8 w=5 + 1 2 3 6 7 w=5 +clique number with unit weights: 5 + +P_2 graph with weights (5, 5): +weighted clique number: 10 + +P_2 graph with weights (5, 4): +weighted clique number: 9 + +K_3 graph with weights (3, 3, 3): +weighted clique number: 9 + +K_3 graph with weights (3, 4, 3): +weighted clique number: 10 diff --git a/tests/unit/igraph_wheel.c b/tests/unit/igraph_wheel.c new file mode 100644 index 0000000..16391db --- /dev/null +++ b/tests/unit/igraph_wheel.c @@ -0,0 +1,56 @@ +/* igraph library. Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void call_and_print(igraph_t *graph, igraph_int_t n, igraph_wheel_mode_t mode, + igraph_int_t center) { + + IGRAPH_ASSERT(igraph_wheel(graph, n, mode, center) == IGRAPH_SUCCESS); + print_graph_canon(graph); + igraph_destroy(graph); + printf("\n"); +} + +int main(void) { + igraph_t graph; + + printf("-- Test graph with 1 vertex --\n"); + call_and_print(&graph, 1, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("-- Test graph with 2 vertices --\n"); + call_and_print(&graph, 2, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("-- Test graph with OUT mode --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_OUT, 0); + printf("-- Test graph with IN mode --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_IN, 0); + printf("-- Test graph with MUTUAL mode --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_MUTUAL, 0); + printf("-- Test graph with UNDIRECTED mode \n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_UNDIRECTED, 0); + printf("-- Test graph with center equal to n/2 --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_OUT, 2); + printf("-- Test graph with center equal to n - 1: --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_OUT, 3); + printf("-- Test graph with center equal to 1: --\n"); + call_and_print(&graph, 4, IGRAPH_WHEEL_OUT, 1); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_wheel.out b/tests/unit/igraph_wheel.out new file mode 100644 index 0000000..ef75dda --- /dev/null +++ b/tests/unit/igraph_wheel.out @@ -0,0 +1,104 @@ +-- Test graph with 1 vertex -- +directed: false +vcount: 0 +edges: { +} + +-- Test graph with 2 vertices -- +directed: false +vcount: 2 +edges: { +0 1 +1 1 +} + +-- Test graph with OUT mode -- +directed: true +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +2 3 +3 1 +} + +-- Test graph with IN mode -- +directed: true +vcount: 4 +edges: { +1 0 +1 2 +2 0 +2 3 +3 0 +3 1 +} + +-- Test graph with MUTUAL mode -- +directed: true +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 0 +1 2 +1 3 +2 0 +2 1 +2 3 +3 0 +3 1 +3 2 +} + +-- Test graph with UNDIRECTED mode +directed: false +vcount: 4 +edges: { +0 1 +0 2 +0 3 +1 2 +1 3 +2 3 +} + +-- Test graph with center equal to n/2 -- +directed: true +vcount: 4 +edges: { +0 1 +1 3 +2 0 +2 1 +2 3 +3 0 +} + +-- Test graph with center equal to n - 1: -- +directed: true +vcount: 4 +edges: { +0 1 +1 2 +2 0 +3 0 +3 1 +3 2 +} + +-- Test graph with center equal to 1: -- +directed: true +vcount: 4 +edges: { +0 2 +1 0 +1 2 +1 3 +2 3 +3 0 +} + diff --git a/tests/unit/igraph_write_graph_dimacs_flow.c b/tests/unit/igraph_write_graph_dimacs_flow.c new file mode 100644 index 0000000..40da465 --- /dev/null +++ b/tests/unit/igraph_write_graph_dimacs_flow.c @@ -0,0 +1,69 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_t capacity; + igraph_int_t source = 0; + igraph_int_t target = 5; + + /* + Expected output: + + ``` + c created by igraph + p problem n_vertices n_edges + n source s + n target t + a arc_node_1 arc_node2 capacity + ``` + + We always outout max as the problem. + + */ + + igraph_small(&g, 6, IGRAPH_DIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 4, 5, -1); + igraph_vector_init_int_end(&capacity, -1, 5, 2, 2, 3, 4, 1, 2, 5, -1); + + printf("DIMACS graph output:\n"); + igraph_write_graph_dimacs_flow(&g, stdout, source, target, &capacity); + + igraph_destroy(&g); + igraph_vector_destroy(&capacity); + + igraph_small(&g, 0, IGRAPH_DIRECTED, -1); + igraph_vector_init(&capacity, 0); + + /* Check that the function does not crash/misbehave on a null graph. + Note that currently igraph outputs DIMACS files for the max-flow + problem, which only makes sense if there are at least two vertices, + a source and the target. Here we use dummy values for them. */ + printf("\nDIMACS graph output for null graph:\n"); + igraph_write_graph_dimacs_flow(&g, stdout, source, target, &capacity); + + igraph_vector_destroy(&capacity); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_write_graph_dimacs_flow.out b/tests/unit/igraph_write_graph_dimacs_flow.out new file mode 100644 index 0000000..0d90169 --- /dev/null +++ b/tests/unit/igraph_write_graph_dimacs_flow.out @@ -0,0 +1,19 @@ +DIMACS graph output: +c created by igraph +p max 6 8 +n 1 s +n 6 t +a 1 2 5 +a 1 3 2 +a 2 3 2 +a 2 4 3 +a 3 5 4 +a 4 5 1 +a 4 6 2 +a 5 6 5 + +DIMACS graph output for null graph: +c created by igraph +p max 0 0 +n 1 s +n 6 t diff --git a/tests/unit/igraph_write_graph_dot.c b/tests/unit/igraph_write_graph_dot.c new file mode 100644 index 0000000..8efbb9e --- /dev/null +++ b/tests/unit/igraph_write_graph_dot.c @@ -0,0 +1,56 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g; + + igraph_set_warning_handler(igraph_warning_handler_ignore); + igraph_set_attribute_table(&igraph_cattribute_table); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 4,3, 4,3, -1); + /* Set vertex attributes */ + SETVAN(&g, "VAN", 0, -1); + SETVAN(&g, "VAN", 1, 2.1); + SETVAN(&g, "VAN", 2, 1.23e-6); + SETVAN(&g, "VAN", 3, 1e7); + + SETVAS(&g, "VAS", 0, "foo"); + SETVAS(&g, "VAS", 1, "bar"); + + SETVAB(&g, "VAB", 0, 1); + SETVAB(&g, "VAB", 1, 0); + + /* Set edge attributes */ + SETEAN(&g, "EAN", 0, -100.1); + SETEAN(&g, "EAN", 2, 100.0); + + SETEAS(&g, "EAS", 0, "Blue"); + SETEAS(&g, "EAS", 2, "RED"); + + SETEAB(&g, "EAB", 0, 1); + SETEAB(&g, "EAB", 2, 0); + + igraph_write_graph_dot(&g, stdout); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/igraph_write_graph_dot.out b/tests/unit/igraph_write_graph_dot.out new file mode 100644 index 0000000..13e54e8 --- /dev/null +++ b/tests/unit/igraph_write_graph_dot.out @@ -0,0 +1,74 @@ +/* Created by igraph @VERSION@ */ +graph { + 0 [ + VAN=-1 + VAS=foo + VAB=1 + ]; + 1 [ + VAN=2.1 + VAS=bar + VAB=0 + ]; + 2 [ + VAN="1.23e-06" + VAS="" + VAB=0 + ]; + 3 [ + VAN=10000000 + VAS="" + VAB=0 + ]; + 4 [ + VAN=NaN + VAS="" + VAB=0 + ]; + 5 [ + VAN=NaN + VAS="" + VAB=0 + ]; + + 1 -- 0 [ + EAN=-100.1 + EAS=Blue + EAB=1 + ]; + 2 -- 0 [ + EAN=NaN + EAS="" + EAB=0 + ]; + 1 -- 1 [ + EAN=100 + EAS=RED + EAB=0 + ]; + 3 -- 1 [ + EAN=NaN + EAS="" + EAB=0 + ]; + 2 -- 0 [ + EAN=NaN + EAS="" + EAB=0 + ]; + 3 -- 2 [ + EAN=NaN + EAS="" + EAB=0 + ]; + 4 -- 3 [ + EAN=NaN + EAS="" + EAB=0 + ]; + 4 -- 3 [ + EAN=NaN + EAS="" + EAB=0 + ]; +} diff --git a/tests/unit/igraph_write_graph_leda.c b/tests/unit/igraph_write_graph_leda.c new file mode 100644 index 0000000..26e3f82 --- /dev/null +++ b/tests/unit/igraph_write_graph_leda.c @@ -0,0 +1,112 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ +#include +#include + +#include "test_utilities.h" + +int main(void) { + int i; + igraph_t g; + igraph_vector_t values; + igraph_strvector_t strvalues; + igraph_vector_bool_t boolvalues; + const char *strings[] = {"foo", "bar", "baz", "spam", "eggs", "bacon"}; + + /* Setting up attribute handler */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* Saving directed graph, no attributes */ + igraph_ring(&g, 5, /* directed = */ 1, + /* mutual = */ 0, + /* circular = */ 1); + igraph_write_graph_leda(&g, stdout, 0, 0); + printf("===\n"); + igraph_destroy(&g); + + /* Saving undirected graph, no attributes */ + igraph_ring(&g, 5, /* directed = */ 0, + /* mutual = */ 0, + /* circular = */ 1); + igraph_write_graph_leda(&g, stdout, 0, 0); + printf("===\n"); + igraph_destroy(&g); + + /* Saving directed graph with vertex attributes */ + igraph_ring(&g, 5, /* directed = */ 1, + /* mutual = */ 0, + /* circular = */ 1); + igraph_vector_init_range(&values, 5, 10); + SETVANV(&g, "name", &values); + igraph_write_graph_leda(&g, stdout, "name", 0); + igraph_vector_destroy(&values); + printf("===\n"); + DELVAS(&g); + igraph_strvector_init(&strvalues, 5); + for (i = 0; i < 5; i++) { + igraph_strvector_set(&strvalues, i, strings[i]); + } + SETVASV(&g, "name", &strvalues); + igraph_write_graph_leda(&g, stdout, "name", 0); + igraph_strvector_destroy(&strvalues); + printf("===\n"); + igraph_destroy(&g); + + /* Saving undirected graph with edge attributes */ + igraph_ring(&g, 5, /* directed = */ 0, + /* mutual = */ 0, + /* circular = */ 1); + igraph_vector_init_range(&values, 5, 10); + SETEANV(&g, "weight", &values); + igraph_write_graph_leda(&g, stdout, 0, "weight"); + igraph_vector_destroy(&values); + printf("===\n"); + DELEAS(&g); + igraph_strvector_init(&strvalues, 5); + for (i = 0; i < 5; i++) { + igraph_strvector_set(&strvalues, i, strings[i]); + } + SETEASV(&g, "weight", &strvalues); + igraph_write_graph_leda(&g, stdout, 0, "weight"); + igraph_strvector_destroy(&strvalues); + printf("===\n"); + igraph_destroy(&g); + + /* Saving undirected graph with numerical edge attributes and boolean vertex attributes */ + igraph_ring(&g, 5, /* directed = */ 0, + /* mutual = */ 0, + /* circular = */ 1); + igraph_vector_init_range(&values, 123456789, 123456794); + SETEANV(&g, "weight", &values); + igraph_vector_bool_init(&boolvalues, igraph_vcount(&g)); + VECTOR(boolvalues)[1] = true; VECTOR(boolvalues)[3] = true; + SETVABV(&g, "binary", &boolvalues); + igraph_write_graph_leda(&g, stdout, "binary", "weight"); + igraph_vector_bool_destroy(&boolvalues); + igraph_vector_destroy(&values); + printf("===\n"); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/igraph_write_graph_leda.out b/tests/unit/igraph_write_graph_leda.out new file mode 100644 index 0000000..4adfcec --- /dev/null +++ b/tests/unit/igraph_write_graph_leda.out @@ -0,0 +1,133 @@ +LEDA.GRAPH +void +void +-1 +# Vertices +5 +|{}| +|{}| +|{}| +|{}| +|{}| +# Edges +5 +1 2 0 |{}| +2 3 0 |{}| +3 4 0 |{}| +4 5 0 |{}| +5 1 0 |{}| +=== +LEDA.GRAPH +void +void +-2 +# Vertices +5 +|{}| +|{}| +|{}| +|{}| +|{}| +# Edges +5 +1 2 0 |{}| +1 5 0 |{}| +2 3 0 |{}| +3 4 0 |{}| +4 5 0 |{}| +=== +LEDA.GRAPH +double +void +-1 +# Vertices +5 +|{5}| +|{6}| +|{7}| +|{8}| +|{9}| +# Edges +5 +1 2 0 |{}| +2 3 0 |{}| +3 4 0 |{}| +4 5 0 |{}| +5 1 0 |{}| +=== +LEDA.GRAPH +string +void +-1 +# Vertices +5 +|{foo}| +|{bar}| +|{baz}| +|{spam}| +|{eggs}| +# Edges +5 +1 2 0 |{}| +2 3 0 |{}| +3 4 0 |{}| +4 5 0 |{}| +5 1 0 |{}| +=== +LEDA.GRAPH +void +double +-2 +# Vertices +5 +|{}| +|{}| +|{}| +|{}| +|{}| +# Edges +5 +1 2 0 |{5}| +1 5 0 |{9}| +2 3 0 |{6}| +3 4 0 |{7}| +4 5 0 |{8}| +=== +LEDA.GRAPH +void +string +-2 +# Vertices +5 +|{}| +|{}| +|{}| +|{}| +|{}| +# Edges +5 +1 2 0 |{foo}| +1 5 0 |{eggs}| +2 3 0 |{bar}| +3 4 0 |{baz}| +4 5 0 |{spam}| +=== +LEDA.GRAPH +bool +double +-2 +# Vertices +5 +|{false|} +|{true|} +|{false|} +|{true|} +|{false|} +# Edges +5 +1 2 0 |{123456789}| +1 5 0 |{123456793}| +2 3 0 |{123456790}| +3 4 0 |{123456791}| +4 5 0 |{123456792}| +=== diff --git a/tests/unit/inclist.c b/tests/unit/inclist.c new file mode 100644 index 0000000..ed8a0cb --- /dev/null +++ b/tests/unit/inclist.c @@ -0,0 +1,208 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#define TEST_INCLIST(label, mode, loops) { \ + igraph_inclist_init(&g, &inclist, mode, loops); \ + printf(label ": "); \ + print_inclist(&inclist); \ + printf("\n"); \ + igraph_inclist_destroy(&inclist); \ +} + +#define TEST_LAZY_INCLIST(label, mode, loops) { \ + igraph_lazy_inclist_init(&g, &lazy_inclist, mode, loops); \ + printf(label ": "); \ + print_lazy_inclist(&lazy_inclist); \ + printf("\n"); \ + igraph_lazy_inclist_destroy(&lazy_inclist); \ +} + +int test_loop_elimination_for_undirected_graph(void) { + igraph_t g; + igraph_inclist_t inclist; + igraph_lazy_inclist_t lazy_inclist; + + igraph_small( + &g, 5, /* directed = */ 0, + /* edge 0 */ 0, 1, + /* edge 1 */ 0, 3, + /* edge 2 */ 1, 2, + /* edge 3 */ 2, 2, + /* edge 4 */ 2, 3, + /* edge 5 */ 3, 0, + /* edge 6 */ 3, 4, + /* edge 7 */ 4, 0, + /* edge 8 */ 4, 4, + /* edge 9 */ 4, 5, + /* edge 10 */ 4, 6, + /* edge 11 */ 4, 4, + /* edge 12 */ 6, 5, + -1 + ); + + printf("Testing loop edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_INCLIST("Loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS); + TEST_INCLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE); + TEST_INCLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + + printf("============================================================\n\n"); + + printf("Testing lazy loop edge elimination in undirected graph\n\n"); + + /* We are testing IGRAPH_ALL, IGRAPH_IN and IGRAPH_OUT below; it should + * make no difference */ + TEST_LAZY_INCLIST("Loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS); + TEST_LAZY_INCLIST("Loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE); + TEST_LAZY_INCLIST("Loops listed twice", IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_loop_elimination_for_directed_graph(void) { + igraph_t g; + igraph_inclist_t inclist; + igraph_lazy_inclist_t lazy_inclist; + + igraph_small( + &g, 5, /* directed = */ 1, + /* edge 0 */ 0, 1, + /* edge 1 */ 0, 3, + /* edge 2 */ 1, 2, + /* edge 3 */ 2, 2, + /* edge 4 */ 2, 3, + /* edge 5 */ 3, 0, + /* edge 6 */ 3, 4, + /* edge 7 */ 4, 0, + /* edge 8 */ 4, 4, + /* edge 9 */ 4, 5, + /* edge 10 */ 4, 6, + /* edge 11 */ 4, 4, + /* edge 12 */ 6, 5, + -1 + ); + + printf("Testing loop edge elimination in directed graph\n\n"); + + TEST_INCLIST("In-edges, loops eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS); + TEST_INCLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE); + TEST_INCLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE); + + TEST_INCLIST("Out-edges, loops eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS); + TEST_INCLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + TEST_INCLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + + TEST_INCLIST("In- and out-edges, loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS); + TEST_INCLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + TEST_INCLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("============================================================\n\n"); + + printf("Testing lazy loop edge elimination in directed graph\n\n"); + + TEST_LAZY_INCLIST("In-edges, loops eliminated", IGRAPH_IN, IGRAPH_NO_LOOPS); + TEST_LAZY_INCLIST("In-edges, loops listed once", IGRAPH_IN, IGRAPH_LOOPS_ONCE); + TEST_LAZY_INCLIST("In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_IN, IGRAPH_LOOPS_TWICE); + + TEST_LAZY_INCLIST("Out-edges, loops eliminated", IGRAPH_OUT, IGRAPH_NO_LOOPS); + TEST_LAZY_INCLIST("Out-edges, loops listed once", IGRAPH_OUT, IGRAPH_LOOPS_ONCE); + TEST_LAZY_INCLIST("Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given", + IGRAPH_OUT, IGRAPH_LOOPS_TWICE); + + TEST_LAZY_INCLIST("In- and out-edges, loops eliminated", IGRAPH_ALL, IGRAPH_NO_LOOPS); + TEST_LAZY_INCLIST("In- and out-edges, loops listed once", IGRAPH_ALL, IGRAPH_LOOPS_ONCE); + TEST_LAZY_INCLIST("In- and out-edges, loops listed twice", IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int test_adjlist_from_inclist(void) { + igraph_t g; + igraph_inclist_t inclist; + igraph_adjlist_t adjlist; + + igraph_small( + &g, 5, /* directed = */ 1, + /* edge 0 */ 0, 1, + /* edge 1 */ 0, 3, + /* edge 2 */ 1, 2, + /* edge 3 */ 2, 2, + /* edge 4 */ 2, 3, + /* edge 5 */ 3, 0, + /* edge 6 */ 3, 4, + /* edge 7 */ 4, 0, + /* edge 8 */ 4, 4, + /* edge 9 */ 4, 5, + /* edge 10 */ 4, 6, + /* edge 11 */ 4, 4, + /* edge 12 */ 6, 5, + -1 + ); + + printf("Testing incidence to adjacency list conversion\n\n"); + + igraph_inclist_init(&g, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); + igraph_adjlist_init_from_inclist(&g, &adjlist, &inclist); + + printf("Incidence list (printed with igraph_inclist_fprint):\n"); + igraph_inclist_fprint(&inclist, stdout); + printf("\nCorresponding adjacency list: "); + print_adjlist(&adjlist); + printf("\n"); + printf("Cleared incidence list (printed with igraph_inclist_print):\n"); + igraph_inclist_clear(&inclist); + igraph_inclist_print(&inclist); + + igraph_adjlist_destroy(&adjlist); + igraph_inclist_destroy(&inclist); + + printf("============================================================\n\n"); + + igraph_destroy(&g); + + return 0; +} + +int main(void) { + + RUN_TEST(test_loop_elimination_for_undirected_graph); + RUN_TEST(test_loop_elimination_for_directed_graph); + + RUN_TEST(test_adjlist_from_inclist); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/inclist.out b/tests/unit/inclist.out new file mode 100644 index 0000000..e537bdc --- /dev/null +++ b/tests/unit/inclist.out @@ -0,0 +1,287 @@ +Testing loop edge elimination in undirected graph + +Loops eliminated: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +Loops listed once: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +Loops listed twice: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 3 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +============================================================ + +Testing lazy loop edge elimination in undirected graph + +Loops eliminated: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +Loops listed once: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +Loops listed twice: { + 0: ( 0 5 1 7 ) + 1: ( 0 2 ) + 2: ( 2 3 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +============================================================ + +Testing loop edge elimination in directed graph + +In-edges, loops eliminated: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 ) + 3: ( 1 4 ) + 4: ( 6 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +In-edges, loops listed once: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 3 ) + 3: ( 1 4 ) + 4: ( 6 11 8 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 3 ) + 3: ( 1 4 ) + 4: ( 6 11 8 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +Out-edges, loops eliminated: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 4 ) + 3: ( 5 6 ) + 4: ( 7 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +Out-edges, loops listed once: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 3 4 ) + 3: ( 5 6 ) + 4: ( 7 11 8 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 3 4 ) + 3: ( 5 6 ) + 4: ( 7 11 8 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +In- and out-edges, loops eliminated: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +In- and out-edges, loops listed once: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 3 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 11 8 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +============================================================ + +Testing lazy loop edge elimination in directed graph + +In-edges, loops eliminated: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 ) + 3: ( 1 4 ) + 4: ( 6 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +In-edges, loops listed once: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 3 ) + 3: ( 1 4 ) + 4: ( 6 11 8 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +In-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 5 7 ) + 1: ( 0 ) + 2: ( 2 3 ) + 3: ( 1 4 ) + 4: ( 6 11 8 ) + 5: ( 9 12 ) + 6: ( 10 ) +} + +Out-edges, loops eliminated: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 4 ) + 3: ( 5 6 ) + 4: ( 7 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +Out-edges, loops listed once: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 3 4 ) + 3: ( 5 6 ) + 4: ( 7 11 8 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +Out-edges, loops listed once even if IGRAPH_LOOPS_TWICE is given: { + 0: ( 0 1 ) + 1: ( 2 ) + 2: ( 3 4 ) + 3: ( 5 6 ) + 4: ( 7 11 8 9 10 ) + 5: ( ) + 6: ( 12 ) +} + +In- and out-edges, loops eliminated: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +In- and out-edges, loops listed once: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +In- and out-edges, loops listed twice: { + 0: ( 0 1 5 7 ) + 1: ( 0 2 ) + 2: ( 2 3 3 4 ) + 3: ( 5 1 4 6 ) + 4: ( 7 6 11 11 8 8 9 10 ) + 5: ( 9 12 ) + 6: ( 10 12 ) +} + +============================================================ + +Testing incidence to adjacency list conversion + +Incidence list (printed with igraph_inclist_fprint): +0 1 5 7 +0 2 +2 3 3 4 +5 1 4 6 +7 6 11 11 8 8 9 10 +9 12 +10 12 + +Corresponding adjacency list: { + 0: ( 1 3 3 4 ) + 1: ( 0 2 ) + 2: ( 1 2 2 3 ) + 3: ( 0 0 2 4 ) + 4: ( 0 3 4 4 4 4 5 6 ) + 5: ( 4 6 ) + 6: ( 4 5 ) +} + +Cleared incidence list (printed with igraph_inclist_print): + + + + + + + +============================================================ + diff --git a/tests/unit/input.dl b/tests/unit/input.dl new file mode 100644 index 0000000..50d75dd --- /dev/null +++ b/tests/unit/input.dl @@ -0,0 +1,104 @@ +DL n=66 +format = edgelist1 +labels embedded: +data: +R1 C1 +R1 C5 +R1 C7 +R1 C9 +R1 C11 +R1 C12 +R1 C13 +R1 C16 +R1 C17 +R1 C23 +R1 C24 +R1 C25 +R1 C28 +R2 C8 +R2 C11 +R2 C12 +R2 C17 +R2 C20 +R2 C24 +R2 C26 +R2 C27 +R2 C28 +R3 C2 +R3 C3 +R4 C17 +R4 C23 +R5 C6 +R5 C13 +R5 C19 +R5 C22 +R5 C24 +R6 C14 +R7 C17 +R7 C22 +R7 C26 +R8 C1 +R8 C17 +R8 C19 +R8 C22 +R9 C19 +R9 C22 +R9 C23 +R10 C6 +R10 C18 +R10 C28 +R11 C25 +R12 C25 +R13 C13 +R13 C19 +R14 C1 +R14 C4 +R14 C21 +R15 C15 +R15 C17 +R16 C17 +R16 C23 +R17 C4 +R18 C28 +R19 C6 +R20 C17 +R21 C28 +R22 C4 +R23 C6 +R23 C17 +R24 C11 +R25 C4 +R26 C16 +R26 C20 +R27 C1 +R27 C2 +R27 C5 +R27 C17 +R28 C13 +R28 C20 +R28 C21 +R29 C12 +R30 C1 +R30 C2 +R30 C22 +R31 C10 +R31 C13 +R31 C15 +R32 C6 +R32 C22 +R32 C28 +R33 C14 +R33 C23 +R34 C3 +R34 C28 +R35 C28 +R36 C13 +R36 C20 +R36 C27 +R36 C28 +R37 C28 +R38 C8 +R38 C10 +R38 C13 +R38 C14 +R38 C23 diff --git a/tests/unit/is_coloring.c b/tests/unit/is_coloring.c new file mode 100644 index 0000000..bdeab40 --- /dev/null +++ b/tests/unit/is_coloring.c @@ -0,0 +1,294 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void test_vertex_coloring(void) { + igraph_t graph; + igraph_vector_int_t types; + igraph_bool_t res; + + printf("Testing igraph_is_vertex_coloring()\n"); + + /* Empty graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&types, 0); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Single vertex */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_vector_int_init_int(&types, 1, 0); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Two connected vertices with different colors */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_vector_int_init_int(&types, 2, 0, 1); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + + /* Two connected vertices with same colors (invalid) */ + igraph_vector_int_init_int(&types, 2, 0, 0); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(!res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Triangle with valid 3-coloring */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, -1); + igraph_vector_int_init_int(&types, 3, 0, 1, 2); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + + /* Triangle with invalid coloring (two adjacent vertices same color) */ + igraph_vector_int_init_int(&types, 3, 0, 0, 1); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(!res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Self-loop should not affect coloring validity */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0, 0, 0, 1, -1); + igraph_vector_int_init_int(&types, 2, 0, 1); + igraph_is_vertex_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + printf("igraph_is_vertex_coloring() tests passed\n"); +} + +void test_bipartite_coloring(void) { + igraph_t graph; + igraph_vector_bool_t types; + igraph_bool_t res; + igraph_neimode_t mode; + + printf("Testing igraph_is_bipartite_coloring()\n"); + + /* Empty graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_vector_bool_init(&types, 0); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_ALL); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Single vertex */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_vector_bool_init_int(&types, 1, 0); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_ALL); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Valid bipartite undirected graph */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0, 2, 0, 3, 1, 2, 1, 3, -1); + igraph_vector_bool_init_int(&types, 4, 0, 0, 1, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_ALL); /* Undirected graph */ + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Invalid bipartite coloring (two adjacent vertices same type) */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 1, 3, 2, 3, -1); + igraph_vector_bool_init_int(&types, 4, 0, 0, 1, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(!res); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Directed bipartite graph - all edges from false to true */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, 0, 2, 0, 3, 1, 2, 1, 3, -1); + igraph_vector_bool_init_int(&types, 4, 0, 0, 1, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_OUT); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Directed bipartite graph - all edges from true to false */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, 2, 0, 3, 0, 2, 1, 3, 1, -1); + igraph_vector_bool_init_int(&types, 4, 0, 0, 1, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_IN); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Directed bipartite graph - edges in both directions */ + igraph_small(&graph, 4, IGRAPH_DIRECTED, 0, 2, 2, 1, 1, 3, 3, 0, -1); + igraph_vector_bool_init_int(&types, 4, 0, 0, 1, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, &mode); + IGRAPH_ASSERT(res); + IGRAPH_ASSERT(mode == IGRAPH_ALL); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + /* Self-loop should not affect bipartite coloring validity */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, 0, 0, 0, 2, 1, 2, -1); + igraph_vector_bool_init_int(&types, 3, 0, 0, 1); + igraph_is_bipartite_coloring(&graph, &types, &res, NULL); + IGRAPH_ASSERT(res); + igraph_vector_bool_destroy(&types); + igraph_destroy(&graph); + + printf("igraph_is_bipartite_coloring() tests passed\n"); +} + +void test_edge_coloring(void) { + igraph_t graph; + igraph_vector_int_t types; + igraph_bool_t res; + + printf("Testing igraph_is_edge_coloring()\n"); + + /* Empty graph */ + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&types, 0); + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Single vertex, no edges */ + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); + igraph_vector_int_init(&types, 0); + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Two vertices, one edge */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0, 1, -1); + igraph_vector_int_init_int(&types, 1, 0); + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Triangle with valid edge coloring */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, -1); + igraph_vector_int_init_int(&types, 3, 0, 1, 2); /* Three different colors */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + + /* Triangle with invalid edge coloring (two incident edges same color) */ + igraph_vector_int_init_int(&types, 3, 0, 1, 0); /* Edges 0 and 2 both color 0, both incident to vertex 0 */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(!res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Star graph with valid edge coloring */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0, 1, 0, 2, 0, 3, -1); + igraph_vector_int_init_int(&types, 3, 0, 1, 2); /* All edges incident to vertex 0 have different colors */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + + /* Star graph with invalid edge coloring */ + igraph_vector_int_init_int(&types, 3, 0, 0, 1); /* Two edges incident to vertex 0 have same color */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(!res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Path graph with valid edge coloring */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, -1); + igraph_vector_int_init_int(&types, 3, 0, 1, 0); /* Alternating colors */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + /* Graph with self-loop: 1 - 1 - 2 (self-loop and regular edge) */ + /* Self-loops are not considered adjacent to themselves */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 1, 1, 1, 0, -1); /* vertex 1 self-loop, edge 1-0 */ + igraph_vector_int_init_int(&types, 2, 0, 1); /* Different colors - should be valid */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(res); + igraph_vector_int_destroy(&types); + + /* Same colors for self-loop and adjacent edge - should be invalid */ + igraph_vector_int_init_int(&types, 2, 0, 0); /* Same colors - should be invalid */ + igraph_is_edge_coloring(&graph, &types, &res); + IGRAPH_ASSERT(!res); + igraph_vector_int_destroy(&types); + igraph_destroy(&graph); + + printf("igraph_is_edge_coloring() tests passed\n"); +} + +void test_error_conditions(void) { + igraph_t graph; + igraph_vector_int_t int_types; + igraph_vector_bool_t bool_types; + igraph_bool_t res; + igraph_neimode_t mode; + + printf("Testing error conditions\n"); + + /* Create a simple graph */ + igraph_small(&graph, 3, IGRAPH_UNDIRECTED, 0, 1, 1, 2, -1); + + /* Wrong size vector for vertex coloring */ + igraph_vector_int_init_int(&int_types, 2, 0, 1); /* Size 2, but graph has 3 vertices */ + CHECK_ERROR(igraph_is_vertex_coloring(&graph, &int_types, &res), IGRAPH_EINVAL); + igraph_vector_int_destroy(&int_types); + + /* Wrong size vector for bipartite coloring */ + igraph_vector_bool_init_int(&bool_types, 2, 0, 1); /* Size 2, but graph has 3 vertices */ + CHECK_ERROR(igraph_is_bipartite_coloring(&graph, &bool_types, &res, &mode), IGRAPH_EINVAL); + igraph_vector_bool_destroy(&bool_types); + + igraph_destroy(&graph); + + /* Wrong size vector for edge coloring */ + igraph_small(&graph, 2, IGRAPH_UNDIRECTED, 0, 1, -1); /* Graph with 1 edge */ + igraph_vector_int_init_int(&int_types, 2, 0, 1); /* Size 2, but graph has 1 edge */ + CHECK_ERROR(igraph_is_edge_coloring(&graph, &int_types, &res), IGRAPH_EINVAL); + igraph_vector_int_destroy(&int_types); + igraph_destroy(&graph); + + printf("Error condition tests passed\n"); +} + +int main(void) { + test_vertex_coloring(); + test_bipartite_coloring(); + test_edge_coloring(); + test_error_conditions(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/isoclasses.c b/tests/unit/isoclasses.c new file mode 100644 index 0000000..1741394 --- /dev/null +++ b/tests/unit/isoclasses.c @@ -0,0 +1,67 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_int_t edges; + igraph_vs_t vids; + igraph_int_t class; + + igraph_vector_int_init_int_end(&edges, -1, + 0, 1, 1, 3, 1, 4, 1, 6, 3, 1, + 4, 1, 4, 2, 6, 4, 6, 5, 7, 8, + 8, 7, 7, 9, 9, 7, 8, 9, 9, 8, + -1); + igraph_create(&g, &edges, 0, IGRAPH_DIRECTED); + igraph_vector_int_destroy(&edges); + + igraph_vs_vector_small(&vids, 1, 4, 6, -1); + igraph_isoclass_subgraph(&g, vids, &class); + printf("class: %d\n", (int) class); + igraph_vs_destroy(&vids); + + igraph_vs_vector_small(&vids, 0, 1, 3, -1); + igraph_isoclass_subgraph(&g, vids, &class); + printf("class: %d\n", (int) class); + igraph_vs_destroy(&vids); + + igraph_vs_vector_small(&vids, 7, 8, 9, -1); + igraph_isoclass_subgraph(&g, vids, &class); + printf("class: %d\n", (int) class); + igraph_vs_destroy(&vids); + + igraph_vs_vector_small(&vids, 0, 2, 5, -1); + igraph_isoclass_subgraph(&g, vids, &class); + printf("class: %d\n", (int) class); + igraph_vs_destroy(&vids); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/isoclasses.out b/tests/unit/isoclasses.out new file mode 100644 index 0000000..c69226c --- /dev/null +++ b/tests/unit/isoclasses.out @@ -0,0 +1,4 @@ +class: 12 +class: 5 +class: 15 +class: 0 diff --git a/tests/unit/isoclasses2.c b/tests/unit/isoclasses2.c new file mode 100644 index 0000000..f737794 --- /dev/null +++ b/tests/unit/isoclasses2.c @@ -0,0 +1,180 @@ +/* + igraph library. + Copyright (C) 2021-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Check that isoclass() and isoclass_create() are consistent with each other. */ +void verify_classes(void) { + igraph_int_t class; + igraph_int_t size; + + igraph_int_t classcountD[] = { 1, 1, 3, 16, 218 }; /* no. of unlabelled directed graphs */ + igraph_int_t classcountU[] = { 1, 1, 2, 4, 11, 34, 156 }; /* no. of unlabelled undirected graphs */ + + /* Directed */ + for (size=3; size <= 4; size++) { + for (class=0; class < classcountD[size]; class++) { + igraph_t g; + igraph_int_t class2; + + igraph_isoclass_create(&g, size, class, IGRAPH_DIRECTED); + igraph_isoclass(&g, &class2); + igraph_destroy(&g); + + IGRAPH_ASSERT(class == class2); + } + } + + /* Undirected */ + for (size=3; size <= 6; size++) { + for (class=0; class < classcountU[size]; class++) { + igraph_t g; + igraph_int_t class2; + + igraph_isoclass_create(&g, size, class, IGRAPH_UNDIRECTED); + igraph_isoclass(&g, &class2); + igraph_destroy(&g); + + IGRAPH_ASSERT(class == class2); + } + } +} + +/* Generate small random graphs and check that their isoclasses are identified correctly. */ +void random_test(void) { + igraph_int_t size, i; + + igraph_rng_seed(igraph_rng_default(), 137); + + /* Directed */ + for (size=3; size <= 4; size++) { + for (i=0; i < 200; ++i) { + igraph_t g1, g2; + igraph_int_t class; + igraph_bool_t iso; + + igraph_erdos_renyi_game_gnp(&g1, size, 0.5, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_isoclass(&g1, &class); + igraph_isoclass_create(&g2, size, class, IGRAPH_DIRECTED); + + igraph_isomorphic_bliss(&g1, &g2, NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL); + IGRAPH_ASSERT(iso); + + igraph_destroy(&g2); + igraph_destroy(&g1); + } + } + + /* Undirected */ + for (size=3; size <= 6; size++) { + for (i=0; i < 200; ++i) { + igraph_t g1, g2; + igraph_int_t class; + igraph_bool_t iso; + + igraph_erdos_renyi_game_gnp(&g1, size, 0.5, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + igraph_isoclass(&g1, &class); + igraph_isoclass_create(&g2, size, class, IGRAPH_UNDIRECTED); + + igraph_isomorphic_bliss(&g1, &g2, NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL); + IGRAPH_ASSERT(iso); + + igraph_destroy(&g2); + igraph_destroy(&g1); + } + } +} + +/* Generate a random graph, select random subgraphs, and check that their + * isoclasses are identified correctly. */ +void random_subgraph_test(void) { + igraph_t graph; + igraph_int_t size, i; + igraph_vector_int_t vids; + + igraph_rng_seed(igraph_rng_default(), 42); + + igraph_vector_int_init(&vids, 0); + + /* Directed */ + + igraph_erdos_renyi_game_gnp(&graph, 40, 0.5, IGRAPH_DIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + for (size=3; size <= 4; size++) { + for (i=0; i < 100; ++i) { + igraph_t sg1, sg2; + igraph_int_t class; + igraph_bool_t iso; + + igraph_random_sample(&vids, 0, igraph_vcount(&graph) - 1, size); + igraph_isoclass_subgraph(&graph, igraph_vss_vector(&vids), &class); + igraph_isoclass_create(&sg1, size, class, igraph_is_directed(&graph)); + igraph_induced_subgraph(&graph, &sg2, igraph_vss_vector(&vids), IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH); + + igraph_isomorphic_bliss(&sg1, &sg2, NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL); + IGRAPH_ASSERT(iso); + + igraph_destroy(&sg1); + igraph_destroy(&sg2); + } + } + + igraph_destroy(&graph); + + /* Undirected */ + + igraph_erdos_renyi_game_gnp(&graph, 60, 0.5, IGRAPH_UNDIRECTED, IGRAPH_SIMPLE_SW, IGRAPH_EDGE_UNLABELED); + + for (size=3; size <= 6; size++) { + for (i=0; i < 100; ++i) { + igraph_t sg1, sg2; + igraph_int_t class; + igraph_bool_t iso; + + igraph_random_sample(&vids, 0, igraph_vcount(&graph) - 1, size); + igraph_isoclass_subgraph(&graph, igraph_vss_vector(&vids), &class); + igraph_isoclass_create(&sg1, size, class, igraph_is_directed(&graph)); + igraph_induced_subgraph(&graph, &sg2, igraph_vss_vector(&vids), IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH); + + igraph_isomorphic_bliss(&sg1, &sg2, NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL); + IGRAPH_ASSERT(iso); + + igraph_destroy(&sg1); + igraph_destroy(&sg2); + } + } + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&vids); +} + +int main(void) { + + verify_classes(); + random_test(); + random_subgraph_test(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/isomorphism_test.c b/tests/unit/isomorphism_test.c new file mode 100644 index 0000000..2fe3e91 --- /dev/null +++ b/tests/unit/isomorphism_test.c @@ -0,0 +1,237 @@ +/* + igraph library. + Copyright (C) 2015-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "test_utilities.h" + +void random_permutation(igraph_vector_int_t *vec) { + /* We just do size(vec) * 2 swaps */ + igraph_int_t one, two, tmp, i, n = igraph_vector_int_size(vec); + for (i = 0; i < 2 * n; i++) { + one = RNG_INTEGER(0, n - 1); + two = RNG_INTEGER(0, n - 1); + tmp = VECTOR(*vec)[one]; + VECTOR(*vec)[one] = VECTOR(*vec)[two]; + VECTOR(*vec)[two] = tmp; + } +} + + +void test3(void) { + igraph_int_t i, j; + igraph_graph_list_t graphs3; + igraph_t g; + + // Verify that no two 3-vertex graphs of distinct isoclasses are considered isomorphic by Bliss or VF2. + + igraph_graph_list_init(&graphs3, 0); + + for (i = 0; i < 16; i++) { + igraph_isoclass_create(&g, 3, i, /* directed = */ 1); + igraph_graph_list_push_back(&graphs3, &g); + } + + for (i = 0; i < 16; i++) + for (j = i + 1; j < 16; j++) { + igraph_bool_t iso; + igraph_isomorphic_bliss( + igraph_graph_list_get_ptr(&graphs3, i), + igraph_graph_list_get_ptr(&graphs3, j), + NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL + ); + if (iso) { + printf("Bliss failure, 3 vertex directed graphs of isoclass %" IGRAPH_PRId " and %" IGRAPH_PRId " are not isomorphic. Bliss reports otherwise.\n", i, j); + } + } + + for (i = 0; i < 16; i++) + for (j = i + 1; j < 16; j++) { + igraph_bool_t iso; + igraph_isomorphic_vf2( + igraph_graph_list_get_ptr(&graphs3, i), + igraph_graph_list_get_ptr(&graphs3, j), + NULL, NULL, NULL, NULL, &iso, NULL, NULL, NULL, NULL, NULL + ); + if (iso) { + printf("VF2 failure, 3 vertex directed graphs of isoclass %" IGRAPH_PRId " and %" IGRAPH_PRId " are not isomorphic. VF2 reports otherwise.\n", i, j); + } + } + + igraph_graph_list_destroy(&graphs3); +} + + +void test4(void) { + igraph_int_t i, j; + igraph_graph_list_t graphs4; + igraph_t g; + + // Verify that no two 4-vertex graphs of distinct isoclasses are considered isomorphic by Bliss or VF2. + + igraph_graph_list_init(&graphs4, 0); + + for (i = 0; i < 218; i++) { + igraph_isoclass_create(&g, 4, i, /* directed = */ 1); + igraph_graph_list_push_back(&graphs4, &g); + } + + for (i = 0; i < 218; i++) + for (j = i + 1; j < 218; j++) { + igraph_bool_t iso; + igraph_isomorphic_bliss( + igraph_graph_list_get_ptr(&graphs4, i), + igraph_graph_list_get_ptr(&graphs4, j), + NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL + ); + if (iso) { + printf("Bliss failure, 4 vertex directed graphs of isoclass %" IGRAPH_PRId " and %" IGRAPH_PRId " are not isomorphic. Bliss reports otherwise.\n", i, j); + } + } + + for (i = 0; i < 218; i++) + for (j = i + 1; j < 218; j++) { + igraph_bool_t iso; + igraph_isomorphic_vf2( + igraph_graph_list_get_ptr(&graphs4, i), + igraph_graph_list_get_ptr(&graphs4, j), + NULL, NULL, NULL, NULL, &iso, NULL, NULL, NULL, NULL, NULL + ); + if (iso) { + printf("VF2 failure, 4 vertex directed graphs of isoclass %" IGRAPH_PRId " and %" IGRAPH_PRId " are not isomorphic. VF2 reports otherwise.\n", i, j); + } + } + + igraph_graph_list_destroy(&graphs4); +} + + +void test_bliss(void) { + igraph_t ring1, ring2, directed_ring; + igraph_vector_int_t perm; + igraph_bool_t iso; + igraph_bliss_info_t info; + igraph_vector_int_t color; + igraph_vector_int_list_t generators; + igraph_real_t num_automorphisms; + + igraph_ring(&ring1, 100, /*directed=*/ 0, /*mutual=*/ 0, /*circular=*/1); + igraph_vector_int_init_range(&perm, 0, igraph_vcount(&ring1)); + random_permutation(&perm); + igraph_permute_vertices(&ring1, &ring2, &perm); + + igraph_ring(&directed_ring, 100, /* directed= */ 1, /* mutual = */0, /* circular = */1); + + igraph_vector_int_list_init(&generators, 0); + + igraph_isomorphic_bliss(&ring1, &ring2, NULL, NULL, &iso, NULL, NULL, IGRAPH_BLISS_F, NULL, NULL); + if (! iso) { + printf("Bliss failed on ring isomorphism.\n"); + } + + igraph_count_automorphisms(&ring1, NULL, &num_automorphisms); + if (num_automorphisms != 200) { + printf("Biss automorphism count failed: ring1.\n"); + } + + igraph_count_automorphisms(&ring2, NULL, &num_automorphisms); + if (num_automorphisms != 200) { + printf("Biss automorphism count failed: ring2.\n"); + } + + igraph_count_automorphisms(&directed_ring, NULL, &num_automorphisms); + if (num_automorphisms != 100) { + printf("Biss automorphism count failed: directed_ring.\n"); + } + + // The following test is included so there is at least one call to igraph_automorphism_group_bliss + // in the test suite. However, the generator set returned may depend on the splitting + // heursitics as well as on the Bliss version. If the test fails, please verify manually + // that the generating set is valid. For a undirected cycle graph like ring2, there should + // be two generators: a cyclic permutation and a reversal of the vertex order. + igraph_automorphism_group_bliss(&ring2, NULL, &generators, IGRAPH_BLISS_F, NULL); + if (igraph_vector_int_list_size(&generators) != 2) + printf("Bliss automorphism generators may have failed with ring2. " + "Please verify the generators manually. " + "Note that the generator set is not guaranteed to be minimal.\n"); + igraph_vector_int_list_clear(&generators); + + // For a directed ring, the only generator should be a cyclic permutation. + igraph_automorphism_group_bliss(&directed_ring, NULL, &generators, IGRAPH_BLISS_F, NULL); + if (igraph_vector_int_list_size(&generators) != 1) + printf("Bliss automorphism generators may have failed with directed_ring. " + "Please verify the generators manually. " + "Note that the generator set is not guaranteed to be minimal.\n"); + igraph_vector_int_list_clear(&generators); + + igraph_vector_int_init_range(&color, 0, igraph_vcount(&ring1)); + + igraph_count_automorphisms_bliss(&ring1, &color, IGRAPH_BLISS_F, &info); + if (strcmp(info.group_size, "1") != 0) { + printf("Bliss automorphism count with color failed: ring1.\n"); + } + igraph_free(info.group_size); + + // There's only one automorphism for this coloured graph, so the generating set is empty. + igraph_automorphism_group_bliss(&ring1, &color, &generators, IGRAPH_BLISS_F, NULL); + if (igraph_vector_int_list_size(&generators) != 0) { + printf("Bliss automorphism generators failed with colored graph.\n"); + } + + igraph_vector_int_list_destroy(&generators); + + igraph_vector_int_destroy(&color); + + igraph_vector_int_destroy(&perm); + + igraph_destroy(&ring1); + igraph_destroy(&ring2); + igraph_destroy(&directed_ring); +} + +void test_bug_995(void) { + igraph_t g1, g2; + igraph_bool_t result; + + igraph_small(&g1, 3, 0, 0, 1, 1, 2, 2, 2, -1); + igraph_small(&g2, 3, 0, 0, 1, 1, 2, 1, 1, -1); + + igraph_isomorphic(&g1, &g2, &result); + if (result) { + printf("igraph_isomorphic() failed with loop edges, see bug #995\n"); + } + + igraph_destroy(&g1); + igraph_destroy(&g2); +} + +int main(void) { + + igraph_rng_seed(igraph_rng_default(), 293847); /* make tests deterministic */ + + test3(); + test4(); + test_bliss(); + test_bug_995(); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/isomorphism_test.out b/tests/unit/isomorphism_test.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/jdm.c b/tests/unit/jdm.c new file mode 100644 index 0000000..e58ca95 --- /dev/null +++ b/tests/unit/jdm.c @@ -0,0 +1,322 @@ +/* + igraph library. + Copyright (C) 2023 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void print_and_destroy(igraph_t* g, igraph_matrix_t* jdm, igraph_vector_t* weights) { + print_matrix(jdm); + + igraph_destroy(g); + if (weights) { + igraph_vector_destroy(weights); + } +} + +int main (void) { + igraph_t g; + igraph_matrix_t jdm; + igraph_vector_t weights; + + // This matrix will be re-used throughout the test + igraph_matrix_init(&jdm, 0, 0); + + printf("Graph with no vertices\n"); + igraph_small(&g, 0, false, -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("3-cycle\n"); + igraph_ring(&g, 3, IGRAPH_UNDIRECTED, false, true); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Three self-loops\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,0, 1,1, 2,2, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("One self-loop and two parallel edges\n"); + igraph_small(&g, 3, IGRAPH_UNDIRECTED, + 0,0, 1,2, 1,2, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Simple, undirected graph\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Simple, directed graph\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected, self-loops, no multiedges\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed, self-loops, no multiedges\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected multigraph, no self-loops\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed multigraph, no self-loops\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected multigraph with self-loops\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 4, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed multigraph with self-loops\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + // Weight tests + printf("Weighted, simple, undirected graph\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_vector_init_range(&weights, -1,6); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, simple, directed graph\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_vector_init_range(&weights, -2,8); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, undirected, self-loops, no multiedges\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_vector_init_range(&weights, 1,9); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, directed, self-loops, no multiedges\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_vector_init_range(&weights, 1,12); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, undirected multigraph, no self-loops\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_vector_init_range(&weights, 1,9); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, directed multigraph, no self-loops\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, 4, 3, + -1); + igraph_vector_init_range(&weights, 1,12); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, undirected multigraph with self-loops\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 4, 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_vector_init_range(&weights, 1,10); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + printf("Weighted, directed multigraph with self-loops\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 1, 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, 4, 3, + -1); + igraph_vector_init_range(&weights, 1,13); + igraph_joint_degree_matrix(&g, &weights, &jdm, -1, -1); + print_and_destroy(&g, &jdm, &weights); + + // dout din tests + + // Directed + printf("Directed: dout is small, cropped JDM (3x3)\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 3, 3); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed: din is small, cropped JDM (4, 2)\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 4, 2); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed: Automatic resize, dout < 0 (4x2)\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, 2); + print_and_destroy(&g, &jdm, NULL); + + printf("Directed: Valid dout and din (5x5)\n"); + igraph_small(&g, 5, true, + 0, 1, 0, 2, 0, 4, + 1, 0, + 3, 2, 3, 4, + 4, 0, 4, 1, 4, 2, 4, 3, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 5, 5); + print_and_destroy(&g, &jdm, NULL); + + // Undirected + printf("Undirected: dout is small, cropped JDM (3x4)\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 3, 4); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected: din is small, cropped JDM (4x3)\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 4, 3); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected: Automatic resize, dout or din < 0 (4x4)\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, -1, -1); + print_and_destroy(&g, &jdm, NULL); + + printf("Undirected: Valid dout and din (5x5)\n"); + igraph_small(&g, 5, false, + 0, 1, 0, 2, 0, 4, + 1, 4, + 2, 3, 2, 4, + 3, 4, + -1); + igraph_joint_degree_matrix(&g, NULL, &jdm, 5, 5); + print_and_destroy(&g, &jdm, NULL); + + // Clean up + igraph_matrix_destroy(&jdm); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/jdm.out b/tests/unit/jdm.out new file mode 100644 index 0000000..11974f3 --- /dev/null +++ b/tests/unit/jdm.out @@ -0,0 +1,139 @@ +Graph with no vertices +[ 0-by-0 ] +3-cycle +[ 0 0 + 0 3 ] +Three self-loops +[ 0 0 + 0 3 ] +One self-loop and two parallel edges +[ 0 0 + 0 3 ] +Simple, undirected graph +[ 0 0 0 0 + 0 0 2 2 + 0 2 1 2 + 0 2 2 0 ] +Simple, directed graph +[ 0 1 0 + 0 1 1 + 0 2 1 + 1 2 1 ] +Undirected, self-loops, no multiedges +[ 0 0 0 0 + 0 0 1 1 + 0 1 1 3 + 0 1 3 2 ] +Directed, self-loops, no multiedges +[ 0 0 0 + 0 2 2 + 0 1 2 + 1 1 2 ] +Undirected multigraph, no self-loops +[ 0 0 0 0 0 + 0 0 1 0 1 + 0 1 2 0 4 + 0 0 0 0 0 + 0 1 4 0 0 ] +Directed multigraph, no self-loops +[ 0 1 0 + 0 1 1 + 0 2 1 + 0 0 0 + 0 4 1 ] +Undirected multigraph with self-loops +[ 0 0 0 0 0 + 0 0 1 0 1 + 0 1 1 0 3 + 0 0 0 0 0 + 0 1 3 0 3 ] +Directed multigraph with self-loops +[ 0 0 0 + 0 2 2 + 0 1 2 + 0 0 0 + 0 3 2 ] +Weighted, simple, undirected graph +[ 0 0 0 0 + 0 0 2 7 + 0 2 0 5 + 0 7 5 0 ] +Weighted, simple, directed graph +[ 0 1 0 + 0 3 2 + 0 -2 -1 + 7 9 6 ] +Weighted, undirected, self-loops, no multiedges +[ 0 0 0 0 + 0 0 6 8 + 0 6 2 11 + 0 8 11 9 ] +Weighted, directed, self-loops, no multiedges +[ 0 0 0 + 0 12 10 + 0 3 3 + 11 8 19 ] +Weighted, undirected multigraph, no self-loops +[ 0 0 0 0 0 + 0 0 6 0 8 + 0 6 3 0 19 + 0 0 0 0 0 + 0 8 19 0 0 ] +Weighted, directed multigraph, no self-loops +[ 0 4 0 + 0 6 5 + 0 4 2 + 0 0 0 + 0 36 9 ] +Weighted, undirected multigraph with self-loops +[ 0 0 0 0 0 + 0 0 7 0 9 + 0 7 2 0 12 + 0 0 0 0 0 + 0 9 12 0 15 ] +Weighted, directed multigraph with self-loops +[ 0 0 0 + 0 12 10 + 0 3 3 + 0 0 0 + 0 31 19 ] +Directed: dout is small, cropped JDM (3x3) +[ 0 1 0 + 0 1 1 + 0 2 1 ] +Directed: din is small, cropped JDM (4, 2) +[ 0 1 + 0 1 + 0 2 + 1 2 ] +Directed: Automatic resize, dout < 0 (4x2) +[ 0 1 + 0 1 + 0 2 + 1 2 ] +Directed: Valid dout and din (5x5) +[ 0 1 0 0 0 + 0 1 1 0 0 + 0 2 1 0 0 + 1 2 1 0 0 + 0 0 0 0 0 ] +Undirected: dout is small, cropped JDM (3x4) +[ 0 0 0 0 + 0 0 2 2 + 0 2 1 2 ] +Undirected: din is small, cropped JDM (4x3) +[ 0 0 0 + 0 0 2 + 0 2 1 + 0 2 2 ] +Undirected: Automatic resize, dout or din < 0 (4x4) +[ 0 0 0 0 + 0 0 2 2 + 0 2 1 2 + 0 2 2 0 ] +Undirected: Valid dout and din (5x5) +[ 0 0 0 0 0 + 0 0 2 2 0 + 0 2 1 2 0 + 0 2 2 0 0 + 0 0 0 0 0 ] diff --git a/tests/unit/kary_tree.c b/tests/unit/kary_tree.c new file mode 100644 index 0000000..7c8d26b --- /dev/null +++ b/tests/unit/kary_tree.c @@ -0,0 +1,57 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + + +#define PRINT_DESTROY(name) \ + printf(name "\n"); \ + print_graph_canon(&graph); \ + igraph_destroy(&graph); \ + printf("\n"); + + +int main(void) { + igraph_t graph; + + igraph_kary_tree(&graph, 0, 1, IGRAPH_TREE_UNDIRECTED); + PRINT_DESTROY("Null graph"); + + igraph_kary_tree(&graph, 0, 1, IGRAPH_TREE_OUT); + PRINT_DESTROY("Directed null graph"); + + igraph_kary_tree(&graph, 1, 1, IGRAPH_TREE_UNDIRECTED); + PRINT_DESTROY("Singleton graph"); + + igraph_kary_tree(&graph, 3, 1, IGRAPH_TREE_OUT); + PRINT_DESTROY("Path graph"); + + igraph_kary_tree(&graph, 3, 2, IGRAPH_TREE_OUT); + PRINT_DESTROY("Binary out-tree, n=3"); + + igraph_kary_tree(&graph, 3, 2, IGRAPH_TREE_IN); + PRINT_DESTROY("Binary in-tree, n=3"); + + igraph_kary_tree(&graph, 14, 3, IGRAPH_TREE_OUT); + PRINT_DESTROY("Ternary out-tree, n=14"); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/kary_tree.out b/tests/unit/kary_tree.out new file mode 100644 index 0000000..e296d53 --- /dev/null +++ b/tests/unit/kary_tree.out @@ -0,0 +1,61 @@ +Null graph +directed: false +vcount: 0 +edges: { +} + +Directed null graph +directed: true +vcount: 0 +edges: { +} + +Singleton graph +directed: false +vcount: 1 +edges: { +} + +Path graph +directed: true +vcount: 3 +edges: { +0 1 +1 2 +} + +Binary out-tree, n=3 +directed: true +vcount: 3 +edges: { +0 1 +0 2 +} + +Binary in-tree, n=3 +directed: true +vcount: 3 +edges: { +1 0 +2 0 +} + +Ternary out-tree, n=14 +directed: true +vcount: 14 +edges: { +0 1 +0 2 +0 3 +1 4 +1 5 +1 6 +2 7 +2 8 +2 9 +3 10 +3 11 +3 12 +4 13 +} + diff --git a/tests/unit/knn.c b/tests/unit/knn.c new file mode 100644 index 0000000..b71a47c --- /dev/null +++ b/tests/unit/knn.c @@ -0,0 +1,198 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +/* Check vector equality with tolerances. Consider NaN values equal. */ +igraph_bool_t vector_eq(const igraph_vector_t *a, const igraph_vector_t *b) { + igraph_int_t na = igraph_vector_size(a); + igraph_int_t nb = igraph_vector_size(b); + if (na != nb) { + return false; + } + for (igraph_int_t i=0; i < na; i++) { + if (isnan(VECTOR(*a)[i]) && isnan(VECTOR(*b)[i])) { + continue; + } + if (! igraph_almost_equals(VECTOR(*a)[i], VECTOR(*b)[i], 1e-12)) { + return false; + } + } + return true; +} + +/* Compare results from igraph_avg_nearest_neighbor_degree() and igraph_degree_correlation_vector() */ +void compare_implementations(const igraph_t *g, igraph_neimode_t mode1, igraph_neimode_t mode2, const igraph_vector_t *weights) { + igraph_vector_t knn, knnk1, knnk2; + + igraph_vector_init(&knn, 0); + igraph_vector_init(&knnk1, 0); + igraph_vector_init(&knnk2, 0); + + igraph_avg_nearest_neighbor_degree(g, igraph_vss_all(), + mode1, mode2, + &knn, &knnk1, weights); + + printf("k_nn_i: "); + print_vector(&knn); + + igraph_degree_correlation_vector(g, weights, &knnk2, mode1, mode2, mode1 == IGRAPH_ALL ? false : true); + + printf("k_nn(k): "); + print_vector(&knnk2); + /* print_vector(&knnk1); */ + + IGRAPH_ASSERT(isnan(VECTOR(knnk2)[0])); + + igraph_vector_remove(&knnk2, 0); + IGRAPH_ASSERT(vector_eq(&knnk1, &knnk2)); + + igraph_vector_destroy(&knnk2); + igraph_vector_destroy(&knnk1); + igraph_vector_destroy(&knn); + + printf("\n"); +} + +/* Check that undirected graphs are treated as a directed ones with all-reciprocal edges. + * This helps verify non-simple graphs, especially those with self-loops. */ +void check_dir_undir_equiv(const igraph_t *ug) { + igraph_t rg; + igraph_vector_t knn1, knn2, knnk1, knnk2; + + igraph_vector_init(&knn1, 0); + igraph_vector_init(&knn2, 0); + igraph_vector_init(&knnk1, 0); + igraph_vector_init(&knnk2, 0); + + igraph_copy(&rg, ug); + igraph_to_directed(&rg, IGRAPH_TO_DIRECTED_MUTUAL); + + igraph_avg_nearest_neighbor_degree(ug, igraph_vss_all(), IGRAPH_ALL, IGRAPH_ALL, &knn1, &knnk1, NULL); + igraph_avg_nearest_neighbor_degree(&rg, igraph_vss_all(), IGRAPH_OUT, IGRAPH_OUT, &knn2, &knnk2, NULL); + + IGRAPH_ASSERT(vector_eq(&knn1, &knn2)); + IGRAPH_ASSERT(vector_eq(&knnk1, &knnk2)); + + igraph_avg_nearest_neighbor_degree(&rg, igraph_vss_all(), IGRAPH_IN, IGRAPH_IN, &knn2, &knnk2, NULL); + + IGRAPH_ASSERT(vector_eq(&knn1, &knn2)); + IGRAPH_ASSERT(vector_eq(&knnk1, &knnk2)); + + igraph_vector_null(&knnk1); + igraph_vector_null(&knnk2); + + igraph_degree_correlation_vector(ug, NULL, &knnk1, IGRAPH_ALL, IGRAPH_ALL, false); + igraph_degree_correlation_vector(&rg, NULL, &knnk2, IGRAPH_OUT, IGRAPH_OUT, false); + + IGRAPH_ASSERT(vector_eq(&knnk1, &knnk2)); + + igraph_destroy(&rg); + + igraph_vector_destroy(&knnk2); + igraph_vector_destroy(&knnk1); + igraph_vector_destroy(&knn2); + igraph_vector_destroy(&knn1); +} + +int main(void) { + igraph_t ug, dg, g; + igraph_vector_t uweights, dweights; + + igraph_small(&ug, 10, IGRAPH_UNDIRECTED, + 0, 4, 1, 1, 1, 2, 1, 2, 1, 8, 2, 3, 2, 6, 3, + 5, 3, 6, 3, 7, 4, 7, 7, 8, 7, 8, 7, 8, 6, 6, + -1); + + igraph_vector_init_range(&uweights, 0, igraph_ecount(&ug)); + igraph_vector_scale(&uweights, 1.0/8); + + igraph_small(&dg, 6, IGRAPH_DIRECTED, + 0, 0, 0, 1, 0, 2, 1, 0, 1, 3, 1, 4, 2, 0, 2, + 3, 3, 2, 3, 3, 4, 2, 4, 3, 0, 1, 1, 3, 1, 3, + -1); + + igraph_vector_init_range(&dweights, 0, igraph_ecount(&dg)); + igraph_vector_scale(&dweights, 1.0/8); + + printf("UNWEIGHTED\n\n"); + printf("Undirected\n"); + compare_implementations(&ug, IGRAPH_ALL, IGRAPH_ALL, NULL); + + printf("Directed treated as undirected\n"); + compare_implementations(&dg, IGRAPH_ALL, IGRAPH_ALL, NULL); + + printf("Directed: out, all\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_ALL, NULL); + + printf("Directed: out, in\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_IN, NULL); + + printf("Directed: out, out\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_OUT, NULL); + + printf("\nWEIGHTED\n\n"); + printf("Undirected\n"); + compare_implementations(&ug, IGRAPH_ALL, IGRAPH_ALL, &uweights); + + printf("Directed treated as undirected\n"); + compare_implementations(&dg, IGRAPH_ALL, IGRAPH_ALL, &dweights); + + printf("Directed: out, all\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_ALL, &dweights); + + printf("Directed: out, in\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_IN, &dweights); + + printf("Directed: out, out\n"); + compare_implementations(&dg, IGRAPH_OUT, IGRAPH_OUT, &dweights); + + printf("\nSPECIAL CASES\n\n"); + printf("Null graph\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + compare_implementations(&g, IGRAPH_ALL, IGRAPH_ALL, NULL); + igraph_destroy(&g); + + printf("Singleton with loop\n"); + igraph_small(&g, 1, IGRAPH_UNDIRECTED, 0,0, -1); + compare_implementations(&g, IGRAPH_ALL, IGRAPH_ALL, NULL); + igraph_destroy(&g); + + printf("Two isolated vertices\n"); + igraph_empty(&g, 2, IGRAPH_UNDIRECTED); + compare_implementations(&g, IGRAPH_ALL, IGRAPH_ALL, NULL); + igraph_destroy(&g); + + printf("Check directed/undirected equivalence principle\n"); + check_dir_undir_equiv(&ug); + + igraph_vector_destroy(&dweights); + igraph_destroy(&dg); + igraph_vector_destroy(&uweights); + igraph_destroy(&ug); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/knn.out b/tests/unit/knn.out new file mode 100644 index 0000000..11387ee --- /dev/null +++ b/tests/unit/knn.out @@ -0,0 +1,61 @@ +UNWEIGHTED + +Undirected +k_nn_i: ( 2 4.4 4.5 3.5 3 4 4 3.6 5 NaN ) +k_nn(k): ( NaN 3 3 NaN 4.25 4 ) + +Directed treated as undirected +k_nn_i: ( 6.42857 6.85714 6.6 6.25 6.66667 NaN ) +k_nn(k): ( NaN NaN NaN 6.66667 NaN 6.6 NaN 6.64286 6.25 ) + +Directed: out, all +k_nn_i: ( 6.5 6.8 7.5 6.5 6.5 NaN ) +k_nn(k): ( NaN NaN 6.83333 NaN 6.5 6.8 ) + +Directed: out, in +k_nn_i: ( 2.5 4.4 4.5 4.5 4.5 NaN ) +k_nn(k): ( NaN NaN 4.5 NaN 2.5 4.4 ) + +Directed: out, out +k_nn_i: ( 4 2.4 3 2 2 NaN ) +k_nn(k): ( NaN NaN 2.33333 NaN 4 2.4 ) + + +WEIGHTED + +Undirected +k_nn_i: ( NaN 4.18182 4.3125 3.58621 5 4 4 3.63636 5 NaN ) +k_nn(k): ( NaN 4 5 NaN 4.25984 3.72727 ) + +Directed treated as undirected +k_nn_i: ( 6.33333 7.21154 6.24242 6.25333 6.65385 NaN ) +k_nn(k): ( NaN NaN NaN 6.65385 NaN 6.24242 NaN 6.93421 6.25333 ) + +Directed: out, all +k_nn_i: ( 6.73333 7.28205 7.53846 6.58824 6.57143 NaN ) +k_nn(k): ( NaN NaN 6.82353 NaN 6.73333 7.28205 ) + +Directed: out, in +k_nn_i: ( 2.13333 5.12821 4.61538 4.58824 4.57143 NaN ) +k_nn(k): ( NaN NaN 4.58824 NaN 2.13333 5.12821 ) + +Directed: out, out +k_nn_i: ( 4.6 2.15385 2.92308 2 2 NaN ) +k_nn(k): ( NaN NaN 2.23529 NaN 4.6 2.15385 ) + + +SPECIAL CASES + +Null graph +k_nn_i: ( ) +k_nn(k): ( NaN ) + +Singleton with loop +k_nn_i: ( 2 ) +k_nn(k): ( NaN NaN 2 ) + +Two isolated vertices +k_nn_i: ( NaN NaN ) +k_nn(k): ( NaN ) + +Check directed/undirected equivalence principle diff --git a/tests/unit/levc-stress.c b/tests/unit/levc-stress.c new file mode 100644 index 0000000..312a187 --- /dev/null +++ b/tests/unit/levc-stress.c @@ -0,0 +1,70 @@ +/* vim:set sw=4 ts=4 sts=4 et: */ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* This is a test for bug #1002140, reported by Luiz Fernando + Bittencourt: https://bugs.launchpad.net/igraph/+bug/1002140 */ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 42); /* make tests deterministic */ + + for (int k = 0; k < 20; k++) { + igraph_t g; + igraph_matrix_int_t merges; + igraph_vector_int_t membership; + igraph_arpack_options_t options; + double modularity; + igraph_vector_int_t history; + FILE *DLFile = fopen("input.dl", "r"); + + IGRAPH_ASSERT(DLFile != NULL); + + igraph_read_graph_dl(&g, DLFile, IGRAPH_UNDIRECTED); + fclose(DLFile); + + igraph_matrix_int_init(&merges, 0, 0); + igraph_vector_int_init(&membership, 0); + igraph_vector_int_init(&history, 0); + igraph_arpack_options_init(&options); + + igraph_community_leading_eigenvector(&g, /*weights=*/ NULL, &merges, + &membership, igraph_vcount(&g), + &options, &modularity, + /*start=*/ 0, /*eigenvalues=*/ NULL, + /*eigenvectors=*/ NULL, &history, + /*callback=*/ NULL, + /*callback_extra=*/ NULL); + + igraph_vector_int_destroy(&history); + igraph_vector_int_destroy(&membership); + igraph_matrix_int_destroy(&merges); + igraph_destroy(&g); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/lineendings.c b/tests/unit/lineendings.c new file mode 100644 index 0000000..4d9d837 --- /dev/null +++ b/tests/unit/lineendings.c @@ -0,0 +1,70 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + FILE *ifile; + + /* turn on attribute handling */ + /* igraph_set_attribute_table(&igraph_cattribute_table); */ + + ifile = fopen("pajek1.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + igraph_write_graph_pajek(&g, stdout); + igraph_destroy(&g); + + ifile = fopen("pajek2.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + igraph_write_graph_pajek(&g, stdout); + igraph_destroy(&g); + + ifile = fopen("pajek3.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + igraph_write_graph_pajek(&g, stdout); + igraph_destroy(&g); + + ifile = fopen("pajek4.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + igraph_write_graph_pajek(&g, stdout); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/lineendings.out b/tests/unit/lineendings.out new file mode 100644 index 0000000..e313473 --- /dev/null +++ b/tests/unit/lineendings.out @@ -0,0 +1,44 @@ +*Vertices 10 +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +*Vertices 10 +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +*Vertices 10 +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 +*Vertices 10 +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 diff --git a/tests/unit/links.net b/tests/unit/links.net new file mode 100644 index 0000000..175b90b --- /dev/null +++ b/tests/unit/links.net @@ -0,0 +1,16 @@ +% Example Pajek file + +*Network TRALALA +*vertices 4 + 1 "1" 0.0938 0.0896 ellipse x_fact 1 y_fact 1 + 2 "2" 0.8188 0.2458 ellipse x_fact 1 y_fact 1 + 3 "3" 0.3688 0.7792 ellipse x_fact 1 + 4 "4" 0.9583 0.8563 ellipse x_fact 1 +*arcs +1 1 1 h2 0 w 3 c Blue s 3 a1 -130 k1 0.6 a2 -130 k2 0.6 ap 0.5 l "Bezier loop" lc BlueViolet fos 20 lr 58 lp 0.3 la 360 +2 1 1 h2 0 a1 120 k1 1.3 a2 -120 k2 0.3 ap 25 l "Bezier arc" lphi 270 la 180 lr 19 lp 0.5 +1 2 1 h2 0 a1 40 k1 2.8 a2 30 k2 0.8 ap 25 l "Bezier arc" lphi 90 la 0 lp 0.65 +4 2 -1 h2 0 w 1 k1 -2 k2 250 ap 25 l "Circular arc" c Red lc OrangeRed +3 4 1 p Dashed h2 0 w 2 c OliveGreen ap 25 l "Straight arc" lc PineGreen +1 3 1 p Dashed h2 0 w 5 k1 -1 k2 -20 ap 25 l "Oval arc" c Brown lc Black +3 3 -1 h1 6 w 1 h2 12 k1 -2 k2 -15 ap 0.5 l "Circular loop" c Red lc OrangeRed lphi 270 la 180 diff --git a/tests/unit/marked_queue.c b/tests/unit/marked_queue.c new file mode 100644 index 0000000..9f213de --- /dev/null +++ b/tests/unit/marked_queue.c @@ -0,0 +1,65 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "core/marked_queue.h" + +#include "test_utilities.h" + +int main(void) { + igraph_marked_queue_int_t Q; + igraph_int_t i; + + igraph_marked_queue_int_init(&Q, 100); + for (i = 0; i < 50; i++) { + igraph_marked_queue_int_push(&Q, i); + if (!igraph_marked_queue_int_iselement(&Q, i)) { + return 4; + } + if (! ((i + 1) % 5)) { + igraph_marked_queue_int_start_batch(&Q); + } + } + + for (i = 1; i < 50; i++) { + if (!igraph_marked_queue_int_iselement(&Q, i)) { + printf("Problem with %" IGRAPH_PRId ".\n", i); + return 3; + } + } + + for (i = 0; i <= 50 / 5; i++) { + if (igraph_marked_queue_int_empty(&Q)) { + return 1; + } + igraph_marked_queue_int_pop_back_batch(&Q); + } + if (!igraph_marked_queue_int_empty(&Q)) { + return 2; + } + + igraph_marked_queue_int_destroy(&Q); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/matrix.c b/tests/unit/matrix.c new file mode 100644 index 0000000..692c2a0 --- /dev/null +++ b/tests/unit/matrix.c @@ -0,0 +1,177 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_matrix_t m, m1; + igraph_int_t i, j, k; + igraph_real_t arr[] = { 1, 2, 11, 12, 21, 22 }; + + /* igraph_matrix_init, igraph_matrix_destroy */ + igraph_matrix_init(&m, 10, 10); + igraph_matrix_destroy(&m); + + igraph_matrix_init(&m, 0, 0); + igraph_matrix_destroy(&m); + + /* igraph_matrix_ncol, igraph_matrix_nrow */ + igraph_matrix_init(&m, 10, 5); + if (igraph_matrix_nrow(&m) != 10) { + return 1; + } + if (igraph_matrix_ncol(&m) != 5) { + return 2; + } + + /* igraph_matrix_size, igraph_matrix_resize */ + igraph_matrix_resize(&m, 6, 5); + if (igraph_matrix_size(&m) != 30) { + return 3; + } + if (igraph_matrix_nrow(&m) != 6) { + return 4; + } + if (igraph_matrix_ncol(&m) != 5) { + return 5; + } + igraph_matrix_resize(&m, 2, 4); + if (igraph_matrix_nrow(&m) != 2) { + return 6; + } + if (igraph_matrix_ncol(&m) != 4) { + return 7; + } + igraph_matrix_destroy(&m); + + /* MATRIX, igraph_matrix_null */ + igraph_matrix_init(&m, 3, 4); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + for (j = 0; j < igraph_matrix_ncol(&m); j++) { + MATRIX(m, i, j) = i + 1; + } + } + print_matrix(&m); + igraph_matrix_null(&m); + print_matrix(&m); + igraph_matrix_destroy(&m); + + /* igraph_matrix_add_cols, igraph_matrix_add_rows */ + igraph_matrix_init(&m, 4, 3); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + for (j = 0; j < igraph_matrix_ncol(&m); j++) { + MATRIX(m, i, j) = (i + 1) * (j + 1); + } + } + igraph_matrix_add_cols(&m, 2); + igraph_matrix_add_rows(&m, 2); + if (igraph_matrix_ncol(&m) != 5) { + return 8; + } + if (igraph_matrix_nrow(&m) != 6) { + return 9; + } + igraph_matrix_destroy(&m); + + /* igraph_matrix_remove_col */ + igraph_matrix_init(&m, 5, 3); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + for (j = 0; j < igraph_matrix_ncol(&m); j++) { + MATRIX(m, i, j) = (i + 1) * (j + 1); + } + } + igraph_matrix_remove_col(&m, 0); + print_matrix(&m); + igraph_matrix_remove_col(&m, 1); + print_matrix(&m); + igraph_matrix_destroy(&m); + + /* TODO: igraph_matrix_permdelete_rows */ + + /* igraph_matrix_copy */ + igraph_matrix_init(&m, 2, 3); + for (i = 0; i < igraph_matrix_nrow(&m); i++) { + for (j = 0; j < igraph_matrix_ncol(&m); j++) { + MATRIX(m, i, j) = (i + 1) * (j + 1); + } + } + igraph_matrix_init_copy(&m1, &m); + print_matrix(&m1); + igraph_matrix_destroy(&m); + igraph_matrix_destroy(&m1); + + /* igraph_matrix_init_array */ + igraph_matrix_init_array(&m, arr, 2, 3, IGRAPH_COLUMN_MAJOR); + print_matrix(&m); + igraph_matrix_destroy(&m); + + igraph_matrix_init_array(&m, arr, 2, 3, IGRAPH_ROW_MAJOR); + print_matrix(&m); + igraph_matrix_destroy(&m); + + igraph_matrix_init_array(&m, arr, 3, 2, IGRAPH_ROW_MAJOR); + print_matrix(&m); + igraph_matrix_destroy(&m); + + /* in-place transpose */ + igraph_matrix_init(&m, 5, 2); + k = 0; + for (i = 0; i < igraph_matrix_ncol(&m); i++) { + for (j = 0; j < igraph_matrix_nrow(&m); j++) { + MATRIX(m, j, i) = k++; + } + } + print_matrix(&m); + igraph_matrix_transpose(&m); + print_matrix(&m); + igraph_matrix_destroy(&m); + + igraph_matrix_init(&m, 5, 1); + k = 0; + for (i = 0; i < igraph_matrix_ncol(&m); i++) { + for (j = 0; j < igraph_matrix_nrow(&m); j++) { + MATRIX(m, j, i) = k++; + } + } + print_matrix(&m); + igraph_matrix_transpose(&m); + print_matrix(&m); + igraph_matrix_destroy(&m); + + igraph_matrix_init(&m, 1, 5); + k = 0; + for (i = 0; i < igraph_matrix_ncol(&m); i++) { + for (j = 0; j < igraph_matrix_nrow(&m); j++) { + MATRIX(m, j, i) = k++; + } + } + print_matrix(&m); + igraph_matrix_transpose(&m); + print_matrix(&m); + igraph_matrix_destroy(&m); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/matrix.out b/tests/unit/matrix.out new file mode 100644 index 0000000..4c20a62 --- /dev/null +++ b/tests/unit/matrix.out @@ -0,0 +1,44 @@ +[ 1 1 1 1 + 2 2 2 2 + 3 3 3 3 ] +[ 0 0 0 0 + 0 0 0 0 + 0 0 0 0 ] +[ 2 3 + 4 6 + 6 9 + 8 12 + 10 15 ] +[ 2 + 4 + 6 + 8 + 10 ] +[ 1 2 3 + 2 4 6 ] +[ 1 11 21 + 2 12 22 ] +[ 1 2 11 + 12 21 22 ] +[ 1 2 + 11 12 + 21 22 ] +[ 0 5 + 1 6 + 2 7 + 3 8 + 4 9 ] +[ 0 1 2 3 4 + 5 6 7 8 9 ] +[ 0 + 1 + 2 + 3 + 4 ] +[ 0 1 2 3 4 ] +[ 0 1 2 3 4 ] +[ 0 + 1 + 2 + 3 + 4 ] diff --git a/tests/unit/matrix2.c b/tests/unit/matrix2.c new file mode 100644 index 0000000..187234a --- /dev/null +++ b/tests/unit/matrix2.c @@ -0,0 +1,344 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +void byrow(igraph_matrix_t *m) { + igraph_int_t r = igraph_matrix_nrow(m), c = igraph_matrix_ncol(m); + igraph_int_t n = 0, i, j; + for (i = 0; i < r; i++) { + for (j = 0; j < c; j++) { + MATRIX(*m, i, j) = n++; + } + } +} + +#define apply(m,a,b) \ + for (i=0; i + 334 Harvard st, Cambridge MA, USA 02139 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_matrix_t m; + + igraph_matrix_init(&m, 10, 10); + if (igraph_matrix_capacity(&m) != 100) { + return 1; + } + + igraph_matrix_add_cols(&m, 5); + igraph_matrix_resize(&m, 5, 5); + igraph_matrix_resize_min(&m); + if (igraph_matrix_capacity(&m) != igraph_matrix_size(&m)) { + return 2; + } + + igraph_matrix_destroy(&m); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/matrix_complex.c b/tests/unit/matrix_complex.c new file mode 100644 index 0000000..1ce27c4 --- /dev/null +++ b/tests/unit/matrix_complex.c @@ -0,0 +1,93 @@ +/* igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "core/math.h" /* M_PI */ +#include "test_utilities.h" + +int main(void) { + igraph_matrix_complex_t c; + igraph_matrix_t real; + igraph_matrix_t imag; + int e[] = {1, 2, 3, 4}; + int e2[] = {5, 6, 7, 8}; + + matrix_init_int_row_major(&real, 2, 2, e); + matrix_init_int_row_major(&imag, 2, 2, e2); + + printf("Complex matrix:\n"); + igraph_matrix_complex_create(&c, &real, &imag); + igraph_matrix_complex_fprint(&c, stdout); + + printf("Real part:\n"); + igraph_matrix_resize(&real, 0, 0); + igraph_matrix_complex_real(&c, &real); + igraph_matrix_print(&real); + + printf("Imaginary part:\n"); + igraph_matrix_resize(&imag, 0, 0); + igraph_matrix_complex_imag(&c, &imag); + igraph_matrix_print(&imag); + + printf("Real and imaginary part:\n"); + igraph_matrix_resize(&real, 0, 0); + igraph_matrix_resize(&imag, 0, 0); + igraph_matrix_complex_realimag(&c, &real, &imag); + igraph_matrix_print(&real); + igraph_matrix_print(&imag); + igraph_matrix_complex_destroy(&c); + + igraph_matrix_destroy(&real); + igraph_matrix_destroy(&imag); + + { + printf("Complex matrix from polar:\n"); + igraph_matrix_complex_t p; + igraph_real_t r_e[] = {1, 2, 3, 4}; + igraph_real_t theta_e[] = {0, .5 * M_PI, M_PI, 1.5 * M_PI}; + const igraph_matrix_t r = igraph_matrix_view(r_e, 2, 2); + const igraph_matrix_t theta = igraph_matrix_view(theta_e, 2, 2); + + igraph_matrix_complex_create_polar(&p, &r, &theta); + print_matrix_complex_round(&p); + igraph_matrix_complex_destroy(&p); + } + + VERIFY_FINALLY_STACK(); + + { + igraph_real_t e3[] = {1, 2, 3}; + igraph_real_t e4[] = {5, 6, 7, 8}; + printf("Check if unequal number of imaginary and real rows is handled correctly.\n"); + const igraph_matrix_t real = igraph_matrix_view(e3, 1, 2); + const igraph_matrix_t imag = igraph_matrix_view(e4, 2, 2); + CHECK_ERROR(igraph_matrix_complex_create(&c, &real, &imag), IGRAPH_EINVAL); + CHECK_ERROR(igraph_matrix_complex_create_polar(&c, &real, &imag), IGRAPH_EINVAL); + } + { + igraph_real_t e3[] = {1, 2, 3}; + igraph_real_t e4[] = {5, 6, 7, 8}; + printf("Check if unequal number of imaginary and real columns is handled correctly.\n"); + const igraph_matrix_t real = igraph_matrix_view(e3, 2, 1); + const igraph_matrix_t imag = igraph_matrix_view(e4, 2, 2); + CHECK_ERROR(igraph_matrix_complex_create(&c, &real, &imag), IGRAPH_EINVAL); + CHECK_ERROR(igraph_matrix_complex_create_polar(&c, &real, &imag), IGRAPH_EINVAL); + } + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/matrix_complex.out b/tests/unit/matrix_complex.out new file mode 100644 index 0000000..58e61fa --- /dev/null +++ b/tests/unit/matrix_complex.out @@ -0,0 +1,19 @@ +Complex matrix: +1+5i 2+6i +3+7i 4+8i +Real part: +1 2 +3 4 +Imaginary part: +5 6 +7 8 +Real and imaginary part: +1 2 +3 4 +5 6 +7 8 +Complex matrix from polar: +1+0i -3+0i +0+2i -0-4i +Check if unequal number of imaginary and real rows is handled correctly. +Check if unequal number of imaginary and real columns is handled correctly. diff --git a/tests/unit/max_results.c b/tests/unit/max_results.c new file mode 100644 index 0000000..0e3a84c --- /dev/null +++ b/tests/unit/max_results.c @@ -0,0 +1,240 @@ +/* igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +igraph_bool_t veclist_is_equal(const igraph_vector_int_list_t *a, const igraph_vector_int_list_t *b) { + igraph_int_t n = igraph_vector_int_list_size(a); + if (igraph_vector_int_list_size(b) != n) { + return false; + } + for (igraph_int_t i=0; i < n; i++) { + if (!igraph_vector_int_is_equal(igraph_vector_int_list_get_ptr(a, i), + igraph_vector_int_list_get_ptr(b, i))) { + return false; + } + } + return true; +} + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t results_full, results_limited; + igraph_vector_t vertex_weights; + igraph_int_t max_results; + + igraph_vector_int_list_init(&results_full, 0); + igraph_vector_int_list_init(&results_limited, 0); + igraph_famous(&graph, "Zachary"); + igraph_vector_init_range(&vertex_weights, 1, igraph_vcount(&graph) + 1); + + /* Cliques */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_cliques(&graph, &results_full, 2, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_cliques(&graph, &results_limited, 2, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 3; + igraph_cliques(&graph, &results_full, 2, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_cliques(&graph, &results_limited, 2, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Weighted cliques */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_weighted_cliques(&graph, &vertex_weights, &results_full, false, 5, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_weighted_cliques(&graph, &vertex_weights, &results_limited, false, 5, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 5; + igraph_weighted_cliques(&graph, &vertex_weights, &results_full, false, 4, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_weighted_cliques(&graph, &vertex_weights, &results_limited, false, 4, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Maximal cliques */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_maximal_cliques(&graph, &results_full, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_maximal_cliques(&graph, &results_limited, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 1; + igraph_maximal_cliques(&graph, &results_full, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_maximal_cliques(&graph, &results_limited, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Independent vertex sets */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_independent_vertex_sets(&graph, &results_full, IGRAPH_UNLIMITED, 3, IGRAPH_UNLIMITED); + igraph_independent_vertex_sets(&graph, &results_limited, IGRAPH_UNLIMITED, 3, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 10; + igraph_independent_vertex_sets(&graph, &results_full, IGRAPH_UNLIMITED, 4, IGRAPH_UNLIMITED); + igraph_independent_vertex_sets(&graph, &results_limited, IGRAPH_UNLIMITED, 4, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 7; + igraph_independent_vertex_sets(&graph, &results_full, 2, 4, IGRAPH_UNLIMITED); + igraph_independent_vertex_sets(&graph, &results_limited, 2, 4, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Maximal independent vertex sets */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_maximal_independent_vertex_sets(&graph, &results_full, IGRAPH_UNLIMITED, 8, IGRAPH_UNLIMITED); + igraph_maximal_independent_vertex_sets(&graph, &results_limited, IGRAPH_UNLIMITED, 8, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 5; + igraph_maximal_independent_vertex_sets(&graph, &results_full, IGRAPH_UNLIMITED, 9, IGRAPH_UNLIMITED); + igraph_maximal_independent_vertex_sets(&graph, &results_limited, IGRAPH_UNLIMITED, 9, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 4; + igraph_maximal_independent_vertex_sets(&graph, &results_full, 7, 10, IGRAPH_UNLIMITED); + igraph_maximal_independent_vertex_sets(&graph, &results_limited, 7, 10, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Simple cycles */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_simple_cycles(&graph, NULL, &results_full, IGRAPH_ALL, IGRAPH_UNLIMITED, 3, IGRAPH_UNLIMITED); + igraph_simple_cycles(&graph, NULL, &results_limited, IGRAPH_ALL, IGRAPH_UNLIMITED, 3, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 3; + igraph_simple_cycles(&graph, NULL, &results_full, IGRAPH_ALL, IGRAPH_UNLIMITED, 4, IGRAPH_UNLIMITED); + igraph_simple_cycles(&graph, NULL, &results_limited, IGRAPH_ALL, IGRAPH_UNLIMITED, 4, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 8; + igraph_simple_cycles(&graph, NULL, &results_full, IGRAPH_ALL, 5, 5, IGRAPH_UNLIMITED); + igraph_simple_cycles(&graph, NULL, &results_limited, IGRAPH_ALL, 5, 5, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + /* Simple paths */ + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 0; + igraph_get_all_simple_paths(&graph, &results_full, 0, igraph_vss_1(4), IGRAPH_ALL, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_get_all_simple_paths(&graph, &results_limited, 0, igraph_vss_1(4), IGRAPH_ALL, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 5; + igraph_get_all_simple_paths(&graph, &results_full, 0, igraph_vss_1(4), IGRAPH_ALL, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_get_all_simple_paths(&graph, &results_limited, 0, igraph_vss_1(4), IGRAPH_ALL, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 4; + igraph_get_all_simple_paths(&graph, &results_full, 0, igraph_vss_1(4), IGRAPH_ALL, 3, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + igraph_get_all_simple_paths(&graph, &results_limited, 0, igraph_vss_1(4), IGRAPH_ALL, 3, IGRAPH_UNLIMITED, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_int_list_clear(&results_full); + igraph_vector_int_list_clear(&results_limited); + max_results = 3; + igraph_get_all_simple_paths(&graph, &results_full, 0, igraph_vss_1(4), IGRAPH_ALL, 3, 4, IGRAPH_UNLIMITED); + igraph_get_all_simple_paths(&graph, &results_limited, 0, igraph_vss_1(4), IGRAPH_ALL, 3, 4, max_results); + IGRAPH_ASSERT(igraph_vector_int_list_size(&results_limited) == max_results); + igraph_vector_int_list_resize(&results_full, max_results); + IGRAPH_ASSERT(veclist_is_equal(&results_limited, &results_full)); + + igraph_vector_destroy(&vertex_weights); + igraph_destroy(&graph); + igraph_vector_int_list_destroy(&results_limited); + igraph_vector_int_list_destroy(&results_full); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/maximal_cliques_callback.c b/tests/unit/maximal_cliques_callback.c new file mode 100644 index 0000000..b770f89 --- /dev/null +++ b/tests/unit/maximal_cliques_callback.c @@ -0,0 +1,106 @@ +/* + igraph library. + Copyright (C) 2020-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +struct userdata { + int i; + igraph_vector_int_list_t *list; +}; + +int compare_vectors(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2) { + igraph_int_t s1, s2, i; + + s1 = igraph_vector_int_size(v1); + s2 = igraph_vector_int_size(v2); + if (s1 < s2) { + return -1; + } + if (s1 > s2) { + return 1; + } + for (i = 0; i < s1; ++i) { + if (VECTOR(*v1)[i] < VECTOR(*v2)[i]) { + return -1; + } + if (VECTOR(*v1)[i] > VECTOR(*v2)[i]) { + return 1; + } + } + return 0; +} + + +igraph_error_t handler(const igraph_vector_int_t *clique, void *arg) { + struct userdata *ud; + igraph_bool_t cont; + + ud = (struct userdata *) arg; + cont = 1; /* true */ + + if (compare_vectors(clique, igraph_vector_int_list_get_ptr(ud->list, ud->i)) != 0) { + printf("igraph_maximal_cliques() and igraph_maximal_cliques_callback() give different results.\n"); + cont = 0; /* false */ + } + + ud->i += 1; + + return cont ? IGRAPH_SUCCESS : IGRAPH_STOP; +} + + +igraph_error_t handler_stop(const igraph_vector_int_t *clique, void *arg) { + /* Stop search as soon as a 3-clique is found. */ + /* Since there are two 3-cliques in the test graph, this will stop the search before it is complete. */ + IGRAPH_UNUSED(arg); + return (igraph_vector_int_size(clique) == 3) ? IGRAPH_STOP : IGRAPH_SUCCESS; +} + + +int main(void) { + igraph_t graph; + igraph_vector_int_list_t list; + struct userdata ud; + + igraph_small(&graph, 6, 0, + 1, 2, 2, 3, 3, 4, 4, 5, 5, 2, 2, 4, + -1); + + igraph_vector_int_list_init(&list, 0); + igraph_maximal_cliques(&graph, &list, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED, IGRAPH_UNLIMITED); + + ud.i = 0; + ud.list = &list; + + /* Check that the callback function finds the same cliques as igraph_maximal_cliques() */ + IGRAPH_ASSERT(igraph_maximal_cliques_callback(&graph, 0, 0, &handler, (void *) &ud) == IGRAPH_SUCCESS); + + /* Check that the search can be stopped correctly */ + IGRAPH_ASSERT(igraph_maximal_cliques_callback(&graph, 0, 0, &handler_stop, NULL) == IGRAPH_SUCCESS); + + igraph_vector_int_list_destroy(&list); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/maximal_cliques_hist.c b/tests/unit/maximal_cliques_hist.c new file mode 100644 index 0000000..5b73d8e --- /dev/null +++ b/tests/unit/maximal_cliques_hist.c @@ -0,0 +1,42 @@ +/* + igraph library. + Copyright (C) 2020-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_t hist; + + igraph_small(&graph, 6, 0, + 1, 2, 2, 3, 3, 4, 4, 5, 5, 2, 2, 4, + -1); + + igraph_vector_init(&hist, 0); + + igraph_maximal_cliques_hist(&graph, &hist, 0, 0); + igraph_vector_print(&hist); + + igraph_vector_destroy(&hist); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/maximal_cliques_hist.out b/tests/unit/maximal_cliques_hist.out new file mode 100644 index 0000000..0586d9f --- /dev/null +++ b/tests/unit/maximal_cliques_hist.out @@ -0,0 +1 @@ +1 1 2 diff --git a/tests/unit/minimum_spanning_tree.c b/tests/unit/minimum_spanning_tree.c new file mode 100644 index 0000000..c61473d --- /dev/null +++ b/tests/unit/minimum_spanning_tree.c @@ -0,0 +1,88 @@ + +#include + +#include "test_utilities.h" + +void rand_weights(const igraph_t *graph, igraph_vector_t *weights) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_vector_resize(weights, ecount); + for (igraph_int_t i=0; i < ecount; i++) { + VECTOR(*weights)[i] = RNG_UNIF01(); + } +} + +igraph_real_t total_weight(const igraph_vector_int_t *edges, const igraph_vector_t *weights) { + const igraph_int_t n = igraph_vector_int_size(edges); + igraph_real_t sum = 0; + for (igraph_int_t i=0; i < n; i++) { + sum += VECTOR(*weights)[ VECTOR(*edges)[i] ]; + } + return sum; +} + +void check_forest(const igraph_t *g, const igraph_vector_int_t *edges) { + igraph_t sg; + igraph_bool_t is_forest; + igraph_subgraph_from_edges(g, &sg, igraph_ess_vector(edges), false); + igraph_is_forest(&sg, &is_forest, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(is_forest); + igraph_destroy(&sg); +} + +int main(void) { + igraph_t g; + igraph_vector_t weights; + igraph_vector_int_t edges; + igraph_int_t no_comps, no_nodes; + + igraph_rng_seed(igraph_rng_default(), 77685); + + igraph_vector_init(&weights, 0); + igraph_vector_int_init(&edges, 0); + + igraph_erdos_renyi_game_gnm(&g, 50, 100, IGRAPH_UNDIRECTED, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + rand_weights(&g, &weights); + + igraph_connected_components(&g, NULL, NULL, &no_comps, IGRAPH_WEAK); + no_nodes = igraph_vcount(&g); + + printf("Automatic\n"); + igraph_minimum_spanning_tree(&g, &edges, &weights, IGRAPH_MST_AUTOMATIC); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + printf("Total weight: %g\n", total_weight(&edges, &weights)); + check_forest(&g, &edges); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == no_nodes - no_comps); + + printf("\nPrim\n"); + igraph_minimum_spanning_tree(&g, &edges, &weights, IGRAPH_MST_PRIM); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + printf("Total weight: %g\n", total_weight(&edges, &weights)); + check_forest(&g, &edges); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == no_nodes - no_comps); + + printf("\nKruskal\n"); + igraph_minimum_spanning_tree(&g, &edges, &weights, IGRAPH_MST_KRUSKAL); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + printf("Total weight: %g\n", total_weight(&edges, &weights)); + check_forest(&g, &edges); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == no_nodes - no_comps); + + printf("\nUnweighted\n"); + igraph_minimum_spanning_tree(&g, &edges, &weights, IGRAPH_MST_UNWEIGHTED); + igraph_vector_int_sort(&edges); + print_vector_int(&edges); + check_forest(&g, &edges); + IGRAPH_ASSERT(igraph_vector_int_size(&edges) == no_nodes - no_comps); + + igraph_destroy(&g); + + igraph_vector_int_destroy(&edges); + igraph_vector_destroy(&weights); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/minimum_spanning_tree.out b/tests/unit/minimum_spanning_tree.out new file mode 100644 index 0000000..a5e2282 --- /dev/null +++ b/tests/unit/minimum_spanning_tree.out @@ -0,0 +1,14 @@ +Automatic +( 0 1 5 6 7 8 13 16 17 23 25 26 28 31 32 34 36 38 39 42 44 46 50 52 58 59 60 62 63 64 67 69 70 71 73 75 78 83 84 85 87 91 92 93 94 97 98 ) +Total weight: 14.6873 + +Prim +( 0 1 5 6 7 8 13 16 17 23 25 26 28 31 32 34 36 38 39 42 44 46 50 52 58 59 60 62 63 64 67 69 70 71 73 75 78 83 84 85 87 91 92 93 94 97 98 ) +Total weight: 14.6873 + +Kruskal +( 0 1 5 6 7 8 13 16 17 23 25 26 28 31 32 34 36 38 39 42 44 46 50 52 58 59 60 62 63 64 67 69 70 71 73 75 78 83 84 85 87 91 92 93 94 97 98 ) +Total weight: 14.6873 + +Unweighted +( 0 1 2 6 8 12 13 14 19 25 28 29 30 31 33 36 39 40 41 42 43 46 47 51 54 56 59 60 61 62 63 65 66 70 71 72 75 77 83 84 90 91 93 94 97 98 99 ) diff --git a/tests/unit/mycielskian.c b/tests/unit/mycielskian.c new file mode 100644 index 0000000..9ba1670 --- /dev/null +++ b/tests/unit/mycielskian.c @@ -0,0 +1,179 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void test_mycielskian(void) { + igraph_t g, res; + igraph_t groetzsch; + igraph_bool_t isomorphic; + + igraph_famous(&groetzsch, "Groetzsch"); + + // k == 0 testing + printf("small graph, k=0\n"); + igraph_small( + &g, 5, /* directed = */ false, + /* edge 0 */ 0, 1, + /* edge 1 */ 0, 3, + /* edge 2 */ 1, 2, + /* edge 3 */ 2, 4, + /* edge 4 */ 2, 3, + /* edge 5 */ 3, 4, + -1 + ); + igraph_mycielskian(&g, &res, 0); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // vcount == 0, k==0 testing + printf("null graph, k=0\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_mycielskian(&g, &res, 0); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // vcount == 0, k==1 testing + printf("null graph, k=1\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_mycielskian(&g, &res, 1); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // vcount == 0, k==3 testing + printf("null graph, k=3\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_mycielskian(&g, &res, 3); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // vcount == 1, k==0 testing + printf("singleton graph, k=0\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_mycielskian(&g, &res, 0); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // checking for directed graphs for directed attribute + printf("small directed graph, k=0\n"); + igraph_small( + &g, 5, /* directed = */ true, + /* edge 0 */ 0, 1, + /* edge 1 */ 0, 3, + /* edge 2 */ 1, 2, + /* edge 3 */ 2, 4, + /* edge 4 */ 2, 3, + /* edge 5 */ 3, 4, + -1 + ); + igraph_mycielskian(&g, &res, 0); + print_graph(&res); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // vcount == 1, k==3 testing --> should output a Grötzsch graph + printf("singleton graph, k=3\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_mycielskian(&g, &res, 3); + print_graph(&res); + igraph_isomorphic(&groetzsch, &res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&g); + printf("\n\n"); + + // P_2 graph with k=2 gives the Grötzsch graph + printf("P_2, k=2\n"); + igraph_ring(&g, 2, IGRAPH_UNDIRECTED, false, false); + igraph_mycielskian(&g, &res, 2); + print_graph(&res); + igraph_isomorphic(&groetzsch, &res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&g); + + igraph_destroy(&groetzsch); + + VERIFY_FINALLY_STACK(); +} + +void test_mycielski_graph(void) { + igraph_t res; + igraph_t expected_res; + igraph_bool_t isomorphic; + + // k == 0 testing --> should be a null graph + igraph_mycielski_graph(&res, 0); + igraph_empty(&expected_res, 0, IGRAPH_UNDIRECTED); + igraph_isomorphic(&res, &expected_res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&expected_res); + + //should be a vertex + igraph_mycielski_graph(&res, 1); + igraph_empty(&expected_res, 1, IGRAPH_UNDIRECTED); + igraph_isomorphic(&res, &expected_res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&expected_res); + + // should be a path + igraph_mycielski_graph(&res, 2); + igraph_ring(&expected_res, 2, IGRAPH_UNDIRECTED, false, false); + igraph_isomorphic(&res, &expected_res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&expected_res); + + // should be a 5-cycle + igraph_mycielski_graph(&res, 3); + igraph_ring(&expected_res, 5, IGRAPH_UNDIRECTED, false, true); + igraph_isomorphic(&res, &expected_res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&expected_res); + + // should be the Groetzsch graph + igraph_mycielski_graph(&res, 4); + igraph_famous(&expected_res, "Groetzsch"); + igraph_isomorphic(&res, &expected_res, &isomorphic); + IGRAPH_ASSERT(isomorphic); + igraph_destroy(&res); + igraph_destroy(&expected_res); + + VERIFY_FINALLY_STACK(); +} + +int main(void) { + test_mycielskian(); + test_mycielski_graph(); +} diff --git a/tests/unit/mycielskian.out b/tests/unit/mycielskian.out new file mode 100644 index 0000000..dff77ce --- /dev/null +++ b/tests/unit/mycielskian.out @@ -0,0 +1,111 @@ +small graph, k=0 +directed: false +vcount: 5 +edges: { +1 0 +3 0 +2 1 +4 2 +3 2 +4 3 +} + + +null graph, k=0 +directed: false +vcount: 0 +edges: { +} + + +null graph, k=1 +directed: false +vcount: 1 +edges: { +} + + +null graph, k=3 +directed: false +vcount: 5 +edges: { +1 0 +3 0 +2 1 +4 2 +4 3 +} + + +singleton graph, k=0 +directed: false +vcount: 1 +edges: { +} + + +small directed graph, k=0 +directed: true +vcount: 5 +edges: { +0 1 +0 3 +1 2 +2 4 +2 3 +3 4 +} + + +singleton graph, k=3 +directed: false +vcount: 11 +edges: { +1 0 +3 0 +2 1 +4 2 +4 3 +6 0 +5 1 +8 0 +5 3 +7 1 +6 2 +9 2 +7 4 +9 3 +8 4 +10 5 +10 6 +10 7 +10 8 +10 9 +} + + +P_2, k=2 +directed: false +vcount: 11 +edges: { +1 0 +3 0 +2 1 +4 2 +4 3 +6 0 +5 1 +8 0 +5 3 +7 1 +6 2 +9 2 +7 4 +9 3 +8 4 +10 5 +10 6 +10 7 +10 8 +10 9 +} diff --git a/tests/unit/ncol.c b/tests/unit/ncol.c new file mode 100644 index 0000000..7b1803a --- /dev/null +++ b/tests/unit/ncol.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include +#include "test_utilities.h" + +int main(void) { + igraph_t g_in; + igraph_t g_out; + FILE *file; + igraph_bool_t same; + + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_small(&g_in, 4, IGRAPH_DIRECTED, + 0, 1, 0, 1, 0, 1, + 1, 2, 2, 3, 3, 0, + -1); + + SETEAN(&g_in, "edge_attr", 0, 12.3); /* edge 0-1 */ + SETEAN(&g_in, "edge_attr", 4, -IGRAPH_INFINITY); /* edge 2-3 */ + SETVAS(&g_in, "vertex_attr", 0, "vertex_name0"); + SETVAS(&g_in, "vertex_attr", 1, "vertex_name1"); + SETVAS(&g_in, "vertex_attr", 2, "vertex_name2"); + SETVAS(&g_in, "vertex_attr", 3, "vertex_name3"); + + char filename[] = "ncol.tmp"; + file = fopen(filename, "w"); + IGRAPH_ASSERT(file != NULL); /* make sure that the file was created successfully */ + + igraph_write_graph_ncol(&g_in, file, "vertex_attr", "edge_attr"); + fclose(file); + + + file = fopen(filename, "r"); + IGRAPH_ASSERT(file); + igraph_read_graph_ncol(&g_out, file, NULL, 1, IGRAPH_ADD_WEIGHTS_YES, + IGRAPH_DIRECTED); + + /* is_same_graph() checks that vertex and edge lists are the same, + * but does not verify attributes. */ + igraph_is_same_graph(&g_in, &g_out, &same); + if (!same) { + IGRAPH_FATAL("Written and read graph are not the same."); + } + + print_attributes(&g_out); + + fclose(file); + unlink(filename); + igraph_destroy(&g_in); + igraph_destroy(&g_out); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/ncol.out b/tests/unit/ncol.out new file mode 100644 index 0000000..6c79624 --- /dev/null +++ b/tests/unit/ncol.out @@ -0,0 +1,11 @@ +Vertex 0: name="vertex_name0" +Vertex 1: name="vertex_name1" +Vertex 2: name="vertex_name2" +Vertex 3: name="vertex_name3" +Edge 0 (0-1): weight=12.3 +Edge 1 (0-1): weight=NaN +Edge 2 (0-1): weight=NaN +Edge 3 (1-2): weight=NaN +Edge 4 (2-3): weight=-Inf +Edge 5 (3-0): weight=NaN + diff --git a/tests/unit/null_communities.c b/tests/unit/null_communities.c new file mode 100644 index 0000000..4db9cd6 --- /dev/null +++ b/tests/unit/null_communities.c @@ -0,0 +1,204 @@ +/* + igraph library. + Copyright (C) 2022-2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Testing community detection on the null graph: + * + * - The modularity should be NaN. + * - In hierarchical methods, the modularity vector should have size 1. + */ + +int main(void) { + igraph_t g; + igraph_vector_t modularity; + igraph_vector_int_t membership; + igraph_matrix_int_t merges; + igraph_real_t m; + igraph_arpack_options_t ao; + + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + + igraph_vector_init(&modularity, 2); + igraph_vector_int_init(&membership, 1); + igraph_matrix_int_init(&merges, 1, 1); + + /* Edge betweenness */ + + igraph_community_edge_betweenness(&g, NULL, NULL, &merges, NULL, &modularity, &membership, IGRAPH_UNDIRECTED, NULL, NULL); + + IGRAPH_ASSERT(igraph_matrix_int_nrow(&merges) == 0); + IGRAPH_ASSERT(igraph_matrix_int_ncol(&merges) == 2); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1); + IGRAPH_ASSERT(isnan(VECTOR(modularity)[0])); + + /* Fast greedy */ + + igraph_vector_resize(&modularity, 2); + igraph_vector_int_resize(&membership, 1); + igraph_matrix_int_resize(&merges, 1, 1); + + igraph_community_fastgreedy(&g, NULL, &merges, &modularity, &membership); + + IGRAPH_ASSERT(igraph_matrix_int_nrow(&merges) == 0); + IGRAPH_ASSERT(igraph_matrix_int_ncol(&merges) == 2); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1); + IGRAPH_ASSERT(isnan(VECTOR(modularity)[0])); + + /* Fluid communities */ + + igraph_vector_int_resize(&membership, 1); + + igraph_community_fluid_communities(&g, 0, &membership); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + + /* InfoMAP */ + + { + igraph_error_t ret; + igraph_error_handler_t *handler; + + m = 2; + igraph_vector_int_resize(&membership, 1); + + handler = igraph_set_error_handler(igraph_error_handler_ignore); + ret = igraph_community_infomap(&g, NULL, NULL, 3, false, 0, &membership, &m); + igraph_set_error_handler(handler); + + if (ret != IGRAPH_UNIMPLEMENTED) { + IGRAPH_ASSERT(ret == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + } + } + + /* Label propagation */ + + igraph_vector_int_resize(&membership, 1); + + igraph_lpa_variant_t variants[3] = {IGRAPH_LPA_DOMINANCE, IGRAPH_LPA_RETENTION, IGRAPH_LPA_FAST}; + for (igraph_int_t i = 0; i < 3; i++) { + igraph_community_label_propagation(&g, &membership, IGRAPH_ALL, NULL, NULL, NULL, variants[i]); + } + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + + /* Leading eigenvector */ + + m = 2; + igraph_vector_int_resize(&membership, 1); + igraph_matrix_int_resize(&merges, 1, 1); + + igraph_arpack_options_init(&ao); + igraph_community_leading_eigenvector(&g, NULL, &merges, &membership, 1, &ao, &m, 0, NULL, NULL, NULL, NULL, NULL); + + IGRAPH_ASSERT(igraph_matrix_int_nrow(&merges) == 0); + IGRAPH_ASSERT(igraph_matrix_int_ncol(&merges) == 2); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(isnan(m)); + + /* Leiden */ + + m = 2; + igraph_vector_int_resize(&membership, 1); + + igraph_community_leiden(&g, NULL, NULL, NULL, 1, 0.01, 0, 1, &membership, NULL, &m); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(isnan(m)); + + /* Multilevel */ + + igraph_vector_int_resize(&membership, 1); + igraph_vector_resize(&modularity, 2); + + igraph_community_multilevel(&g, NULL, 1, &membership, NULL, &modularity); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1); + IGRAPH_ASSERT(isnan(VECTOR(modularity)[0])); + + /* Optimal modularity */ + /* Test only when GLPK is available */ + + { + igraph_error_t ret; + igraph_error_handler_t *handler; + + m = 2; + igraph_vector_int_resize(&membership, 1); + + handler = igraph_set_error_handler(igraph_error_handler_ignore); + ret = igraph_community_optimal_modularity(&g, NULL, 1, &m, &membership); + igraph_set_error_handler(handler); + + if (ret != IGRAPH_UNIMPLEMENTED) { + IGRAPH_ASSERT(ret == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(isnan(m)); + } + } + + /* Spinglass */ + + m = 2; + igraph_vector_int_resize(&membership, 1); + + igraph_community_spinglass(&g, NULL, &m, NULL, &membership, NULL, 5, 0, 1, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_ORIG, 1); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(isnan(m)); + + m = 2; + igraph_vector_int_resize(&membership, 1); + + igraph_community_spinglass(&g, NULL, &m, NULL, &membership, NULL, 5, false, 1.0, 0.01, 0.99, IGRAPH_SPINCOMM_UPDATE_SIMPLE, 1, IGRAPH_SPINCOMM_IMP_NEG, 1); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(isnan(m)); + + /* Walktrap */ + + igraph_vector_resize(&modularity, 2); + igraph_vector_int_resize(&membership, 1); + igraph_matrix_int_resize(&merges, 1, 1); + + igraph_community_walktrap(&g, NULL, 4, &merges, &modularity, &membership); + + IGRAPH_ASSERT(igraph_matrix_int_nrow(&merges) == 0); + IGRAPH_ASSERT(igraph_matrix_int_ncol(&merges) == 2); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 0); + IGRAPH_ASSERT(igraph_vector_size(&modularity) == 1); + IGRAPH_ASSERT(isnan(VECTOR(modularity)[0])); + + /* Cleanup */ + + igraph_matrix_int_destroy(&merges); + igraph_vector_int_destroy(&membership); + igraph_vector_destroy(&modularity); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/overflow.c b/tests/unit/overflow.c new file mode 100644 index 0000000..53e8cec --- /dev/null +++ b/tests/unit/overflow.c @@ -0,0 +1,73 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "math/safe_intop.h" + +#define EXPECT_SUCC(expr) IGRAPH_ASSERT((expr) == IGRAPH_SUCCESS) +#define EXPECT_FAIL(expr) IGRAPH_ASSERT((expr) != IGRAPH_SUCCESS) + +int main(void) { + igraph_int_t res; + + igraph_set_error_handler(igraph_error_handler_printignore); + + /* Addition */ + + EXPECT_SUCC(igraph_i_safe_add(1, 2, &res)); + EXPECT_SUCC(igraph_i_safe_add(-123, 42, &res)); + EXPECT_SUCC(igraph_i_safe_add(-5, -137, &res)); + EXPECT_SUCC(igraph_i_safe_add(IGRAPH_INTEGER_MAX-1, 1, &res)); + EXPECT_SUCC(igraph_i_safe_add(IGRAPH_INTEGER_MIN+1, -1, &res)); + EXPECT_SUCC(igraph_i_safe_add(IGRAPH_INTEGER_MIN, IGRAPH_INTEGER_MAX, &res)); + EXPECT_SUCC(igraph_i_safe_add(IGRAPH_INTEGER_MAX/2, IGRAPH_INTEGER_MAX/2, &res)); + + EXPECT_FAIL(igraph_i_safe_add(IGRAPH_INTEGER_MAX, IGRAPH_INTEGER_MAX, &res)); + EXPECT_FAIL(igraph_i_safe_add(IGRAPH_INTEGER_MAX, 1, &res)); + EXPECT_FAIL(igraph_i_safe_add(IGRAPH_INTEGER_MIN, -1, &res)); + EXPECT_FAIL(igraph_i_safe_add(IGRAPH_INTEGER_MAX/2 + 2, IGRAPH_INTEGER_MAX/2, &res)); + + /* Multiplication */ + + EXPECT_SUCC(igraph_i_safe_mult(3, 4, &res)); + EXPECT_SUCC(igraph_i_safe_mult(-35, 14, &res)); + EXPECT_SUCC(igraph_i_safe_mult(-5, -9, &res)); + EXPECT_SUCC(igraph_i_safe_mult(IGRAPH_INTEGER_MAX, 1, &res)); + EXPECT_SUCC(igraph_i_safe_mult(1, IGRAPH_INTEGER_MAX, &res)); + EXPECT_SUCC(igraph_i_safe_mult(2, IGRAPH_INTEGER_MAX/2, &res)); + EXPECT_SUCC(igraph_i_safe_mult(2, IGRAPH_INTEGER_MIN/2, &res)); + + EXPECT_FAIL(igraph_i_safe_mult(2, IGRAPH_INTEGER_MAX, &res)); + EXPECT_FAIL(igraph_i_safe_mult(2, IGRAPH_INTEGER_MIN, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MAX, IGRAPH_INTEGER_MAX, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MAX, IGRAPH_INTEGER_MIN, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MAX/2, 3, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MIN/2, 3, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MAX/2, -3, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MIN/2, -3, &res)); + + /* The following tests assume that, mathematically, INTEGER_MIN = -INTEGER_MAX - 1 */ + + EXPECT_SUCC(igraph_i_safe_mult(IGRAPH_INTEGER_MAX, -1, &res)); + EXPECT_SUCC(igraph_i_safe_mult(-1, IGRAPH_INTEGER_MAX, &res)); + + EXPECT_FAIL(igraph_i_safe_mult(-1, IGRAPH_INTEGER_MIN, &res)); + EXPECT_FAIL(igraph_i_safe_mult(IGRAPH_INTEGER_MIN, -1, &res)); + + return 0; +} diff --git a/tests/unit/pajek.c b/tests/unit/pajek.c new file mode 100644 index 0000000..6b37886 --- /dev/null +++ b/tests/unit/pajek.c @@ -0,0 +1,105 @@ +/* + igraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_bool_t simple; + FILE *ifile; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + ifile = fopen("pajek5.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 9); + + igraph_destroy(&g); + + ifile = fopen("pajek6.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 9); + + igraph_destroy(&g); + + /* This file starts with a UTF-8 BOM, which Pajek itself supports. + * It also contains some custom attributes. */ + ifile = fopen("utf8_with_bom.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 10); + IGRAPH_ASSERT(igraph_ecount(&g) == 6); + + igraph_destroy(&g); + + /* File in Arcslist format */ + ifile = fopen("pajek_arcslist.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 18); + IGRAPH_ASSERT(igraph_ecount(&g) == 55); + IGRAPH_ASSERT(igraph_is_directed(&g)); + + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + + igraph_destroy(&g); + + /* File in Edgeslist format */ + ifile = fopen("pajek_edgeslist.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 3); + IGRAPH_ASSERT(igraph_ecount(&g) == 3); + IGRAPH_ASSERT(! igraph_is_directed(&g)); + + igraph_is_simple(&g, &simple, IGRAPH_DIRECTED); + IGRAPH_ASSERT(simple); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/pajek1.net b/tests/unit/pajek1.net new file mode 100644 index 0000000..8d8c1fb --- /dev/null +++ b/tests/unit/pajek1.net @@ -0,0 +1,21 @@ +*Vertices 10 +1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green +2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green +3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green +4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green +5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green +6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue +7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red +8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green +9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green +10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 diff --git a/tests/unit/pajek2.c b/tests/unit/pajek2.c new file mode 100644 index 0000000..d44b2c6 --- /dev/null +++ b/tests/unit/pajek2.c @@ -0,0 +1,54 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + FILE *ifile; + int i, n; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + ifile = fopen("bipartite.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&g, ifile); + fclose(ifile); + + IGRAPH_ASSERT(igraph_vcount(&g) == 13); + IGRAPH_ASSERT(igraph_ecount(&g) == 11); + + for (i = 0, n = igraph_vcount(&g); i < n; i++) { + printf("%d ", (int) VAB(&g, "type", i)); + } + printf("\n"); + + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/pajek2.net b/tests/unit/pajek2.net new file mode 100644 index 0000000..6fb83b8 --- /dev/null +++ b/tests/unit/pajek2.net @@ -0,0 +1 @@ +*Vertices 10 1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green 2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green 3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green 4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green 5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green 6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue 7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red 8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green 9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green 10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green *Edges 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 diff --git a/tests/unit/pajek2.out b/tests/unit/pajek2.out new file mode 100644 index 0000000..6816138 --- /dev/null +++ b/tests/unit/pajek2.out @@ -0,0 +1 @@ +0 0 0 0 0 0 0 0 1 1 1 1 1 diff --git a/tests/unit/pajek3.net b/tests/unit/pajek3.net new file mode 100644 index 0000000..4017af3 --- /dev/null +++ b/tests/unit/pajek3.net @@ -0,0 +1,21 @@ +*Vertices 10 +1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green +2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green +3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green +4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green +5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green +6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue +7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red +8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green +9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green +10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green +*Edges +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 diff --git a/tests/unit/pajek4.net b/tests/unit/pajek4.net new file mode 100644 index 0000000..a5f0db4 --- /dev/null +++ b/tests/unit/pajek4.net @@ -0,0 +1,21 @@ +*Vertices 10 + 1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green + 2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green + 3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green + 4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green + 5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green + 6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue + 7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red + 8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green + 9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green + 10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green + *Edges + 1 2 + 2 3 + 3 4 + 4 5 + 5 6 + 6 7 + 7 8 + 8 9 + 9 10 diff --git a/tests/unit/pajek5.net b/tests/unit/pajek5.net new file mode 100644 index 0000000..239e842 --- /dev/null +++ b/tests/unit/pajek5.net @@ -0,0 +1,21 @@ +*Vertices 10 +1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green +2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green +3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green +4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green +5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green +6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue +7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red +8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green +9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green +10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green +*Edges 9 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 diff --git a/tests/unit/pajek6.net b/tests/unit/pajek6.net new file mode 100644 index 0000000..9badd2d --- /dev/null +++ b/tests/unit/pajek6.net @@ -0,0 +1,21 @@ +*Vertices 10 +1 "Vert 1" 0 0 box x_fact 1 y_fact 1 ic Green +2 "Vert 2" 0 0 box x_fact 1 y_fact 1 ic Green +3 "Vert 3" 0 0 box x_fact 1 y_fact 1 ic Green +4 "Vert 4" 0 0 box x_fact 1 y_fact 1 ic Green +5 "Vert 5" 0 0 box x_fact 1 y_fact 1 ic Green +6 "Vert 6" 0 0 box x_fact 1 y_fact 1 ic Blue +7 "Vert 7" 0 0 box x_fact 1 y_fact 1 ic Red +8 "Vert 8" 0 0 box x_fact 1 y_fact 1 ic Green +9 "Vert 9" 0 0 box x_fact 1 y_fact 1 ic Green +10 "Vert 10" 0 0 box x_fact 1 y_fact 1 ic Green +*Arcs 9 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +9 10 diff --git a/tests/unit/pajek_arcslist.net b/tests/unit/pajek_arcslist.net new file mode 100644 index 0000000..366de7e --- /dev/null +++ b/tests/unit/pajek_arcslist.net @@ -0,0 +1,40 @@ +This is a simplified version of http://mrvar.fdv.uni-lj.si/pajek/data/sampsonl.net + +*Vertices 18 +1 "ROMUL_10" +2 "BONAVEN_5" +3 "AMBROSE_9" +4 "BERTH_6" +5 "PETER_4" +6 "LOUIS_11" +7 "VICTOR_8" +8 "WINF_12" +9 "JOHN_1" +10 "GREG_2" +11 "HUGH_14" +12 "BONI_15" +13 "MARK_7" +14 "ALBERT_16" +15 "AMAND_13" +16 "BASIL_3" +17 "ELIAS_17" +18 "SIMP_18" +*Arcslist +1 3 5 14 +2 1 7 14 +3 1 2 17 +4 5 6 10 +5 4 11 13 +6 1 4 9 +7 2 8 16 +8 1 2 9 +9 5 8 16 +10 4 8 14 +11 5 8 14 +12 1 2 14 +13 5 7 18 +14 1 11 12 15 +15 1 2 14 +16 1 2 7 +17 3 13 18 +18 1 2 7 diff --git a/tests/unit/pajek_bip.net b/tests/unit/pajek_bip.net new file mode 100644 index 0000000..4040647 --- /dev/null +++ b/tests/unit/pajek_bip.net @@ -0,0 +1,27 @@ +*vertices 15 10 + 1 "A" + 2 "B" + 3 "C" + 4 "D" + 5 "E" + 6 "F" + 7 "G" + 8 "H" + 9 "I" +10 "J" +11 "1" +12 "2" +13 "3" +14 "4" +15 "5" +*matrix +1 0 0 0 0 +1 1 0 0 0 +1 1 1 0 0 +1 1 1 1 0 +1 1 1 1 1 +0 0 0 0 1 +1 0 0 0 1 +1 1 0 1 1 +0 0 0 0 0 +1 0 1 0 1 diff --git a/tests/unit/pajek_bip2.net b/tests/unit/pajek_bip2.net new file mode 100644 index 0000000..bb4fc6a --- /dev/null +++ b/tests/unit/pajek_bip2.net @@ -0,0 +1,27 @@ +*vertices 15 10 + 1 "A" + 2 "B" + 3 "C" + 4 "D" + 5 "E" + 6 "F" + 7 "G" + 8 "H" + 9 "I" +10 "J" +11 "1" +12 "2" +13 "3" +14 "4" +15 "5" +*matrix +1 0 0 0 0 1 +1 1 0 0 0 2 +1 1 1 0 0 3 +1 1 1 1 0 4 +1 1 1 1 1 5 +0 0 0 0 1 1 +1 0 0 0 1 2 +1 1 0 1 1 4 +0 0 0 0 0 0 +1 0 1 0 1 3 diff --git a/tests/unit/pajek_bipartite.c b/tests/unit/pajek_bipartite.c new file mode 100644 index 0000000..843116a --- /dev/null +++ b/tests/unit/pajek_bipartite.c @@ -0,0 +1,46 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_bool_t type; + igraph_bool_t typev[] = { false, true, false, true, false, true, false, true, false, true }; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + igraph_ring(&graph, 10, IGRAPH_UNDIRECTED, /*mutual=*/ false, /*circular=*/ true); + type = igraph_vector_bool_view(typev, sizeof(typev) / sizeof(typev[0])); + SETVABV(&graph, "type", &type); + + igraph_write_graph_pajek(&graph, stdout); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/pajek_bipartite.out b/tests/unit/pajek_bipartite.out new file mode 100644 index 0000000..8b72b17 --- /dev/null +++ b/tests/unit/pajek_bipartite.out @@ -0,0 +1,22 @@ +*Vertices 10 5 +1 "1" +2 "3" +3 "5" +4 "7" +5 "9" +6 "2" +7 "4" +8 "6" +9 "8" +10 "10" +*Edges +1 6 +6 2 +2 7 +7 3 +3 8 +8 4 +4 9 +9 5 +5 10 +1 10 diff --git a/tests/unit/pajek_bipartite2.c b/tests/unit/pajek_bipartite2.c new file mode 100644 index 0000000..306314a --- /dev/null +++ b/tests/unit/pajek_bipartite2.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + FILE *ifile; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + /* first file, without marginals */ + + ifile = fopen("pajek_bip.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&graph, ifile); + fclose(ifile); + + print_attributes(&graph); + + igraph_destroy(&graph); + + /* second file, with marginals */ + + printf("---\n"); + + ifile = fopen("pajek_bip2.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&graph, ifile); + fclose(ifile); + + print_attributes(&graph); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/pajek_bipartite2.out b/tests/unit/pajek_bipartite2.out new file mode 100644 index 0000000..38efa97 --- /dev/null +++ b/tests/unit/pajek_bipartite2.out @@ -0,0 +1,83 @@ +Vertex 0: type=0 name="A" +Vertex 1: type=0 name="B" +Vertex 2: type=0 name="C" +Vertex 3: type=0 name="D" +Vertex 4: type=0 name="E" +Vertex 5: type=0 name="F" +Vertex 6: type=0 name="G" +Vertex 7: type=0 name="H" +Vertex 8: type=0 name="I" +Vertex 9: type=0 name="J" +Vertex 10: type=1 name="1" +Vertex 11: type=1 name="2" +Vertex 12: type=1 name="3" +Vertex 13: type=1 name="4" +Vertex 14: type=1 name="5" +Edge 0 (10-0): weight=1 +Edge 1 (10-1): weight=1 +Edge 2 (11-1): weight=1 +Edge 3 (10-2): weight=1 +Edge 4 (11-2): weight=1 +Edge 5 (12-2): weight=1 +Edge 6 (10-3): weight=1 +Edge 7 (11-3): weight=1 +Edge 8 (12-3): weight=1 +Edge 9 (13-3): weight=1 +Edge 10 (10-4): weight=1 +Edge 11 (11-4): weight=1 +Edge 12 (12-4): weight=1 +Edge 13 (13-4): weight=1 +Edge 14 (14-4): weight=1 +Edge 15 (14-5): weight=1 +Edge 16 (10-6): weight=1 +Edge 17 (14-6): weight=1 +Edge 18 (10-7): weight=1 +Edge 19 (11-7): weight=1 +Edge 20 (13-7): weight=1 +Edge 21 (14-7): weight=1 +Edge 22 (10-9): weight=1 +Edge 23 (12-9): weight=1 +Edge 24 (14-9): weight=1 + +--- +Vertex 0: type=0 name="A" +Vertex 1: type=0 name="B" +Vertex 2: type=0 name="C" +Vertex 3: type=0 name="D" +Vertex 4: type=0 name="E" +Vertex 5: type=0 name="F" +Vertex 6: type=0 name="G" +Vertex 7: type=0 name="H" +Vertex 8: type=0 name="I" +Vertex 9: type=0 name="J" +Vertex 10: type=1 name="1" +Vertex 11: type=1 name="2" +Vertex 12: type=1 name="3" +Vertex 13: type=1 name="4" +Vertex 14: type=1 name="5" +Edge 0 (10-0): weight=1 +Edge 1 (10-1): weight=1 +Edge 2 (11-1): weight=1 +Edge 3 (10-2): weight=1 +Edge 4 (11-2): weight=1 +Edge 5 (12-2): weight=1 +Edge 6 (10-3): weight=1 +Edge 7 (11-3): weight=1 +Edge 8 (12-3): weight=1 +Edge 9 (13-3): weight=1 +Edge 10 (10-4): weight=1 +Edge 11 (11-4): weight=1 +Edge 12 (12-4): weight=1 +Edge 13 (13-4): weight=1 +Edge 14 (14-4): weight=1 +Edge 15 (14-5): weight=1 +Edge 16 (10-6): weight=1 +Edge 17 (14-6): weight=1 +Edge 18 (10-7): weight=1 +Edge 19 (11-7): weight=1 +Edge 20 (13-7): weight=1 +Edge 21 (14-7): weight=1 +Edge 22 (10-9): weight=1 +Edge 23 (12-9): weight=1 +Edge 24 (14-9): weight=1 + diff --git a/tests/unit/pajek_edgeslist.net b/tests/unit/pajek_edgeslist.net new file mode 100644 index 0000000..4ccf5c1 --- /dev/null +++ b/tests/unit/pajek_edgeslist.net @@ -0,0 +1,8 @@ +*Network "C_3 cycle graph" +*Vertices 3 +1 "Alice" +2 "Bob" +3 "Cedric" +*Edgeslist +1 2 3 +2 3 diff --git a/tests/unit/pajek_signed.c b/tests/unit/pajek_signed.c new file mode 100644 index 0000000..e06f1c6 --- /dev/null +++ b/tests/unit/pajek_signed.c @@ -0,0 +1,47 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + FILE *ifile; + + /* turn on attribute handling */ + igraph_set_attribute_table(&igraph_cattribute_table); + + ifile = fopen("pajek_signed.net", "r"); + IGRAPH_ASSERT(ifile != NULL); + + igraph_read_graph_pajek(&graph, ifile); + fclose(ifile); + + print_attributes(&graph); + + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/pajek_signed.net b/tests/unit/pajek_signed.net new file mode 100644 index 0000000..9279c7b --- /dev/null +++ b/tests/unit/pajek_signed.net @@ -0,0 +1,23 @@ +*NETWORK First.net; 14.04.2009 / 09:46:56 +*Vertices 10 +1 "S65" +2 "S29" +3 "S04" +4 "S75" +5 "S24" +6 "S81" +7 "S51" +8 "S78" +9 "S86" +10 "S39" +*Matrix + 0 0 0 0 0 1 0 0 0 -1 + 0 0 1 1 0 1 1 0 1 0 + -1 0 0 1 0 0 1 0 1 0 + -1 1 0 0 1 1 1 0 1 0 + 0 1 0 1 0 0 0 0 -1 -1 + 1 -1 0 0 0 0 0 1 0 0 + 0 -1 1 1 0 -1 0 0 1 0 + 0 0 1 1 0 1 -1 0 1 1 + 0 0 0 0 0 0 0 0 0 -1 + 1 1 1 1 1 1 1 1 1 0 diff --git a/tests/unit/pajek_signed.out b/tests/unit/pajek_signed.out new file mode 100644 index 0000000..0726615 --- /dev/null +++ b/tests/unit/pajek_signed.out @@ -0,0 +1,56 @@ +Vertex 0: name="S65" +Vertex 1: name="S29" +Vertex 2: name="S04" +Vertex 3: name="S75" +Vertex 4: name="S24" +Vertex 5: name="S81" +Vertex 6: name="S51" +Vertex 7: name="S78" +Vertex 8: name="S86" +Vertex 9: name="S39" +Edge 0 (0-5): weight=1 +Edge 1 (0-9): weight=-1 +Edge 2 (1-2): weight=1 +Edge 3 (1-3): weight=1 +Edge 4 (1-5): weight=1 +Edge 5 (1-6): weight=1 +Edge 6 (1-8): weight=1 +Edge 7 (2-0): weight=-1 +Edge 8 (2-3): weight=1 +Edge 9 (2-6): weight=1 +Edge 10 (2-8): weight=1 +Edge 11 (3-0): weight=-1 +Edge 12 (3-1): weight=1 +Edge 13 (3-4): weight=1 +Edge 14 (3-5): weight=1 +Edge 15 (3-6): weight=1 +Edge 16 (3-8): weight=1 +Edge 17 (4-1): weight=1 +Edge 18 (4-3): weight=1 +Edge 19 (4-8): weight=-1 +Edge 20 (4-9): weight=-1 +Edge 21 (5-0): weight=1 +Edge 22 (5-1): weight=-1 +Edge 23 (5-7): weight=1 +Edge 24 (6-1): weight=-1 +Edge 25 (6-2): weight=1 +Edge 26 (6-3): weight=1 +Edge 27 (6-5): weight=-1 +Edge 28 (6-8): weight=1 +Edge 29 (7-2): weight=1 +Edge 30 (7-3): weight=1 +Edge 31 (7-5): weight=1 +Edge 32 (7-6): weight=-1 +Edge 33 (7-8): weight=1 +Edge 34 (7-9): weight=1 +Edge 35 (8-9): weight=-1 +Edge 36 (9-0): weight=1 +Edge 37 (9-1): weight=1 +Edge 38 (9-2): weight=1 +Edge 39 (9-3): weight=1 +Edge 40 (9-4): weight=1 +Edge 41 (9-5): weight=1 +Edge 42 (9-6): weight=1 +Edge 43 (9-7): weight=1 +Edge 44 (9-8): weight=1 + diff --git a/tests/unit/paths.c b/tests/unit/paths.c new file mode 100644 index 0000000..ed334c1 --- /dev/null +++ b/tests/unit/paths.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_vector_int_t edge_walk, vertex_walk; + + igraph_vector_int_init(&vertex_walk, 0); + igraph_vector_int_init(&edge_walk, 0); + + igraph_ring(&graph, 4, IGRAPH_DIRECTED, false, true); + igraph_vector_int_range(&edge_walk, 0, igraph_ecount(&graph)); + + printf("Cycle, detect start:\n"); + igraph_vertex_path_from_edge_path(&graph, -1, &edge_walk, &vertex_walk, IGRAPH_ALL); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + printf("\nCycle plus one vertex, start given:\n"); + igraph_vector_int_push_back(&edge_walk, 0); + igraph_vertex_path_from_edge_path(&graph, 0, &edge_walk, &vertex_walk, IGRAPH_ALL); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + /* Inconsistent start vertex */ + CHECK_ERROR( + igraph_vertex_path_from_edge_path(&graph, 1, &edge_walk, &vertex_walk, IGRAPH_ALL), + IGRAPH_EINVAL + ); + + /* Invalid start vertex */ + CHECK_ERROR( + igraph_vertex_path_from_edge_path(&graph, 10, &edge_walk, &vertex_walk, IGRAPH_ALL), + IGRAPH_EINVVID + ); + + printf("\nSingle edge, detect start:\n"); + igraph_vector_int_clear(&edge_walk); + igraph_vector_int_push_back(&edge_walk, 2); + igraph_vertex_path_from_edge_path(&graph, -1, &edge_walk, &vertex_walk, IGRAPH_ALL); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + printf("\nSingle edge, directed OUT, detect start:\n"); + igraph_vector_int_clear(&edge_walk); + igraph_vector_int_push_back(&edge_walk, 2); + igraph_vertex_path_from_edge_path(&graph, -1, &edge_walk, &vertex_walk, IGRAPH_OUT); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + printf("\nSingle edge, directed IN, detect start:\n"); + igraph_vector_int_clear(&edge_walk); + igraph_vector_int_push_back(&edge_walk, 2); + igraph_vertex_path_from_edge_path(&graph, -1, &edge_walk, &vertex_walk, IGRAPH_IN); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + printf("\nZero edges, start given:\n"); + igraph_vector_int_clear(&edge_walk); + igraph_vertex_path_from_edge_path(&graph, 2, &edge_walk, &vertex_walk, IGRAPH_ALL); + print_vector_int(&edge_walk); + print_vector_int(&vertex_walk); + + /* Zero edges, start not given: */ + CHECK_ERROR( + igraph_vertex_path_from_edge_path(&graph, -1, &edge_walk, &vertex_walk, IGRAPH_ALL), + IGRAPH_EINVAL + ); + + igraph_destroy(&graph); + igraph_vector_int_destroy(&edge_walk); + igraph_vector_int_destroy(&vertex_walk); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/paths.out b/tests/unit/paths.out new file mode 100644 index 0000000..4cf7485 --- /dev/null +++ b/tests/unit/paths.out @@ -0,0 +1,23 @@ +Cycle, detect start: +( 0 1 2 3 ) +( 0 1 2 3 0 ) + +Cycle plus one vertex, start given: +( 0 1 2 3 0 ) +( 0 1 2 3 0 1 ) + +Single edge, detect start: +( 2 ) +( 2 3 ) + +Single edge, directed OUT, detect start: +( 2 ) +( 2 3 ) + +Single edge, directed IN, detect start: +( 2 ) +( 3 2 ) + +Zero edges, start given: +( ) +( 2 ) diff --git a/tests/unit/percolation.c b/tests/unit/percolation.c new file mode 100644 index 0000000..2fe08c4 --- /dev/null +++ b/tests/unit/percolation.c @@ -0,0 +1,364 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +igraph_error_t percolate_bond(igraph_t *graph, igraph_vector_int_t *edge_indices, igraph_bool_t printing) { + igraph_vector_int_t giant_size, vertex_count; + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t number_percolated; + + if (edge_indices != NULL) { + number_percolated = igraph_vector_int_size(edge_indices); + } else { + number_percolated = ecount; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&giant_size, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_count, 0); + + IGRAPH_CHECK(igraph_bond_percolation(graph, &giant_size, &vertex_count, edge_indices)); + + if (printing) { + print_vector_int(&giant_size); + print_vector_int(&vertex_count); + } + if (number_percolated == ecount) { + // It's only guaranteed to have the same component ecount if all edges are included. + igraph_vector_int_t component_sizes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&component_sizes, 0); + + IGRAPH_CHECK(igraph_connected_components(graph, NULL, &component_sizes, NULL, IGRAPH_WEAK)); + + IGRAPH_ASSERT(igraph_vector_int_size(&giant_size) == ecount); + IGRAPH_ASSERT(igraph_vector_int_size(&vertex_count) == ecount); + if (ecount > 1) { + IGRAPH_ASSERT(igraph_vector_int_max(&giant_size) == igraph_vector_int_max(&component_sizes)); + } + igraph_vector_int_destroy(&component_sizes); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_int_t prev = 0; + for (igraph_int_t i = 0; i < number_percolated; i++) { + IGRAPH_ASSERT(VECTOR(giant_size)[i] > 0); // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(giant_size)[i] >= prev); // Size of largest component must be nondecreasing. + IGRAPH_ASSERT(VECTOR(giant_size)[i] <= i + 2); // Largest component cannot be bigger than a tree with the same number of edges. + prev = VECTOR(giant_size)[i]; + } + prev = 0; + for (igraph_int_t i = 0; i < number_percolated; i++) { + IGRAPH_ASSERT(VECTOR(vertex_count)[i] > 0); // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(vertex_count)[i] >= prev); // Size of largest component must be nondecreasing. + IGRAPH_ASSERT(VECTOR(vertex_count)[i] <= 2*i + 2); // Largest component cannot be bigger than a tree with the same number of edges. + prev = VECTOR(vertex_count)[i]; + } + + igraph_vector_int_destroy(&giant_size); + igraph_vector_int_destroy(&vertex_count); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +void test_bond(void) { + // Test with normal graph and provided edge list + igraph_t k_3, c_4, karate, random, null_graph, singleton; + + igraph_empty(&null_graph, 0, false); + igraph_empty(&singleton, 1, false); + igraph_full(&k_3, 3, false, false); + igraph_small(&c_4, 4, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_famous(&karate, "Zachary"); + igraph_erdos_renyi_game_gnp(&random, 100, 0.01, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + printf("# Bond percolation test suite\n"); + printf("Null graph, no provided edge order.\n"); + percolate_bond(&null_graph, NULL, true); + + printf("Singleton graph, no provided edge order.\n"); + percolate_bond(&singleton, NULL, true); + + printf("K_3 graph percolation curve, no provided edge sequence.\n"); + percolate_bond(&k_3, NULL, true); // sequence is random, but since it should be consistent it doesn't matter + + igraph_vector_int_t edge_ids; + igraph_vector_int_init_int(&edge_ids, 4, 0, 2, 1, 3); + + printf("C_4 graph with edge sequence ( 0 2 1 3 ).\n"); + percolate_bond(&c_4, &edge_ids, true); + + igraph_vector_int_destroy(&edge_ids); + printf("Zachary karate graph, no edge list given.\n"); + // Karate graph, no edge list given + percolate_bond(&karate, NULL, false); + // Generated disconnected graph, 100 vertices, p=0.01 + + igraph_vector_int_t storage_order; + igraph_vector_int_init_range(&storage_order, 0, igraph_ecount(&karate)); + printf("Zachary karate graph, edges in storage order.\n"); + percolate_bond(&karate, &storage_order, true); + igraph_vector_int_destroy(&storage_order); + + percolate_bond(&random, NULL, false); // sanity check + + printf("Error on duplicates\n"); + igraph_vector_int_t duplicates; + igraph_vector_int_init_int(&duplicates, 5, 0,1,2,3,3); + CHECK_ERROR(percolate_bond(&c_4, &duplicates, false), IGRAPH_EINVAL); + igraph_vector_int_destroy(&duplicates); + printf("Null outputs\n"); + igraph_bond_percolation(&karate, NULL, NULL, NULL); + + igraph_destroy(&singleton); + igraph_destroy(&null_graph); + igraph_destroy(&k_3); + igraph_destroy(&c_4); + igraph_destroy(&karate); + igraph_destroy(&random); + + VERIFY_FINALLY_STACK(); +} + +igraph_error_t percolate_site(igraph_t *graph, igraph_vector_int_t *vert_indices, igraph_bool_t printing) { + igraph_vector_int_t outputs, edge_counts; + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t number_percolated; + + if (vert_indices != NULL) { + number_percolated = igraph_vector_int_size(vert_indices); + } + else { + number_percolated = vcount; + } + + IGRAPH_VECTOR_INT_INIT_FINALLY(&outputs, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&edge_counts, 0); + + IGRAPH_CHECK(igraph_site_percolation(graph, &outputs, &edge_counts, vert_indices)); + + if (printing) { + print_vector_int(&outputs); + print_vector_int(&edge_counts); + } + + igraph_vector_int_t component_sizes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&component_sizes, 0); + + IGRAPH_CHECK(igraph_connected_components(graph, NULL, &component_sizes, NULL, IGRAPH_WEAK)); + + IGRAPH_ASSERT(igraph_vector_int_size(&outputs) == number_percolated); + if (number_percolated > 1 && number_percolated == vcount) { + // it is only guaranteed to have the same component vcount if all vertices are included + IGRAPH_ASSERT(igraph_vector_int_max(&outputs) == igraph_vector_int_max(&component_sizes)); + } + igraph_vector_int_destroy(&component_sizes); + IGRAPH_FINALLY_CLEAN(1); + igraph_int_t prev = 0; + for (igraph_int_t i = 0; i < number_percolated; i++) { + IGRAPH_ASSERT(VECTOR(outputs)[i] <= vcount); //vcount cannot be greater than number of vertices + IGRAPH_ASSERT(VECTOR(outputs)[i] > 0); // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(outputs)[i] >= prev); // Size of largest component must be nondecreasing. + IGRAPH_ASSERT(VECTOR(outputs)[i] <= i + 1); // Largest component cannot be bigger than a tree with the same number of vertices. + prev = VECTOR(outputs)[i]; + } + prev = 0; + for (igraph_int_t i = 0; i < number_percolated; i++) { + IGRAPH_ASSERT(VECTOR(edge_counts)[i] >= 0); // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(edge_counts)[i] >= prev); // Size of largest component must be nondecreasing. + prev = VECTOR(edge_counts)[i]; + } + + igraph_vector_int_destroy(&edge_counts); + IGRAPH_FINALLY_CLEAN(1); + igraph_vector_int_destroy(&outputs); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + + +void test_site(void) { + + // Test with normal graph and provided edge list + igraph_t k_5, c_4, karate, random, null_graph, singleton; + + igraph_empty(&null_graph, 0, false); + igraph_empty(&singleton, 1, false); + igraph_full(&k_5, 5, false, false); + igraph_small(&c_4, 4, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 3, 3, 0, -1); + igraph_famous(&karate, "Zachary"); + igraph_erdos_renyi_game_gnp(&random, 100, 0.01, IGRAPH_UNDIRECTED, IGRAPH_MULTI_SW, IGRAPH_EDGE_UNLABELED); + printf("# Site percolation test suite\n"); + printf("Null graph, no provided vertex order.\n"); + percolate_site(&null_graph, NULL, true); + + printf("Singleton graph, no provided vertex order.\n"); + percolate_site(&singleton, NULL, true); + + printf("K_5 graph percolation curve, no provided vertex sequence.\n"); + percolate_site(&k_5, NULL, true); // sequence is random, but since it should be consistent it doesn't matter + + igraph_vector_int_t vertex_ids; + igraph_vector_int_init_int(&vertex_ids, 4, 0, 2, 1, 3); + + printf("C_4 graph with vertex sequence ( 0 2 1 3 ).\n"); + percolate_site(&c_4, &vertex_ids, true); + + igraph_vector_int_destroy(&vertex_ids); + printf("Zachary karate graph, no vertex list given.\n"); + // Karate graph, no vertex list given + percolate_site(&karate, NULL, false); + // Generated disconnected graph, 100 vertices, p=0.01 + + igraph_vector_int_t storage_order; + igraph_vector_int_init_range(&storage_order, 0, igraph_vcount(&karate)); + printf("Zachary karate graph, vertices in storage order.\n"); + percolate_site(&karate, &storage_order, true); + igraph_vector_int_destroy(&storage_order); + + percolate_site(&random, NULL, false); + + igraph_vector_int_t bad_vert_list_repeat, bad_vert_list_too_big, vert_list_missing_vertex; + igraph_vector_int_init_int(&bad_vert_list_too_big, 6, 0, 1, 2, 3, 4, 5); + igraph_vector_int_init_int(&vert_list_missing_vertex, 3, 0, 1, 2); + igraph_vector_int_init_int(&bad_vert_list_repeat, 5, 0, 0, 0, 0, 0); + + // should error due to vertex being too big + printf("K_5 with too big vertex index\n"); + CHECK_ERROR(percolate_site(&k_5, &bad_vert_list_too_big, false), IGRAPH_EINVVID); + + // missing vertices are allowed + printf("K_5 with missing vertices\n"); + percolate_site(&k_5, &vert_list_missing_vertex, true); + + // should error due to repeated vertices + printf("K_5 with repeated vertices\n"); + CHECK_ERROR(percolate_site(&k_5, &bad_vert_list_repeat, false), IGRAPH_EINVAL); + + printf("Null outputs\n"); + igraph_bond_percolation(&karate, NULL, NULL, NULL); + + igraph_destroy(&singleton); + igraph_destroy(&null_graph); + igraph_destroy(&k_5); + igraph_destroy(&c_4); + igraph_destroy(&karate); + igraph_destroy(&random); + + igraph_vector_int_destroy(&vert_list_missing_vertex); + igraph_vector_int_destroy(&bad_vert_list_repeat); + igraph_vector_int_destroy(&bad_vert_list_too_big); + + VERIFY_FINALLY_STACK(); +} + +igraph_error_t percolate_edgelist(igraph_vector_int_t *edges, igraph_bool_t printing) { + igraph_vector_int_t giant_size, vertex_count; + igraph_t graph; + igraph_int_t ecount; + + IGRAPH_VECTOR_INT_INIT_FINALLY(&giant_size, 0); + IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_count, 0); + + IGRAPH_CHECK(igraph_edgelist_percolation(edges, &giant_size, &vertex_count)); + + if (printing) { + print_vector_int(&giant_size); + print_vector_int(&vertex_count); + } + + igraph_vector_int_t component_sizes; + IGRAPH_VECTOR_INT_INIT_FINALLY(&component_sizes, 0); + + IGRAPH_CHECK(igraph_create(&graph, edges, 0, false)); + ecount = igraph_ecount(&graph); + IGRAPH_CHECK(igraph_connected_components(&graph,NULL, &component_sizes,NULL,IGRAPH_WEAK)); + igraph_destroy(&graph); + + IGRAPH_ASSERT(igraph_vector_int_size(&giant_size) == ecount); + if (ecount > 1) { + IGRAPH_ASSERT(igraph_vector_int_max(&giant_size) == igraph_vector_int_max(&component_sizes)); + } + + igraph_int_t prev = 0; + for (igraph_int_t i = 0; i < ecount; i++) { + // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(giant_size)[i] > 0); + // Size of largest component must be nondecreasing. + IGRAPH_ASSERT(VECTOR(giant_size)[i] >= prev); + // Largest component cannot be bigger than a tree with the same number of edges. + IGRAPH_ASSERT(VECTOR(giant_size)[i] <= i + 2); + prev = VECTOR(giant_size)[i]; + } + + prev = 0; + for (igraph_int_t i = 0; i < ecount; i++) { + // Sizes cannot be negative. + IGRAPH_ASSERT(VECTOR(vertex_count)[i] > 0); + // Size of largest component must be nondecreasing. + IGRAPH_ASSERT(VECTOR(vertex_count)[i] >= prev); + // Largest component cannot be bigger than a tree with the same number of edges. + IGRAPH_ASSERT(VECTOR(vertex_count)[i] <= 2*i + 2); + prev = VECTOR(vertex_count)[i]; + } + + igraph_vector_int_destroy(&giant_size); + igraph_vector_int_destroy(&component_sizes); + igraph_vector_int_destroy(&vertex_count); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +void test_edgelist_percolation(void) { + // Edge list percolation is already called from bond percolation, + // so this mostly tests for expected errors that cannot occur from generated edge lists. + igraph_vector_int_t odd, negative, loopy; + igraph_vector_int_init_int(&odd, 5, 0, 1, 1, 2, 1); + igraph_vector_int_init_int(&negative, 6, -1, 1, 0, 0, 1, -1); + igraph_vector_int_init_int(&loopy, 8, 0, 0, 0, 1, 1, 2, 2, 0); + + printf("# Edge list percolation\n"); + printf("Percolation with ( 0 1 1 2 1 ), odd number of entries\n"); + CHECK_ERROR(percolate_edgelist(&odd, false), IGRAPH_EINVAL); + printf("Percolation with ( -1 1 0 0 1 -1 ), negative numbers\n"); + CHECK_ERROR(percolate_edgelist(&negative, false), IGRAPH_EINVVID); + + printf("Percolation with ( 0 0 0 1 1 2 2 0 ), k_3 with loop\n"); + percolate_edgelist(&loopy, true); + + printf("Null outputs\n"); + igraph_edgelist_percolation(&loopy, NULL, NULL); + + igraph_vector_int_destroy(&odd); + igraph_vector_int_destroy(&negative); + igraph_vector_int_destroy(&loopy); + VERIFY_FINALLY_STACK(); +} + +int main(void) { + igraph_rng_seed(igraph_rng_default(), 30062025); + + test_bond(); + test_site(); + test_edgelist_percolation(); + + return 0; +} diff --git a/tests/unit/percolation.out b/tests/unit/percolation.out new file mode 100644 index 0000000..a69908e --- /dev/null +++ b/tests/unit/percolation.out @@ -0,0 +1,49 @@ +# Bond percolation test suite +Null graph, no provided edge order. +( ) +( ) +Singleton graph, no provided edge order. +( ) +( ) +K_3 graph percolation curve, no provided edge sequence. +( 2 3 3 ) +( 2 3 3 ) +C_4 graph with edge sequence ( 0 2 1 3 ). +( 2 2 4 4 ) +( 2 4 4 4 ) +Zachary karate graph, no edge list given. +Zachary karate graph, edges in storage order. +( 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 17 17 17 17 17 17 17 18 18 18 19 20 21 22 22 22 22 22 22 22 22 22 22 23 23 23 23 24 24 24 25 25 26 26 27 27 27 28 28 29 29 29 31 31 31 32 33 33 33 33 34 34 34 34 34 34 34 34 34 34 34 34 ) +( 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 17 17 17 17 17 17 17 18 18 18 19 20 21 22 22 22 22 22 22 22 22 22 22 23 23 23 23 24 24 24 25 25 26 26 27 27 27 28 28 29 29 31 31 31 31 32 33 33 33 33 34 34 34 34 34 34 34 34 34 34 34 34 ) +Error on duplicates +Null outputs +# Site percolation test suite +Null graph, no provided vertex order. +( ) +( ) +Singleton graph, no provided vertex order. +( 1 ) +( 0 ) +K_5 graph percolation curve, no provided vertex sequence. +( 1 2 3 4 5 ) +( 0 1 3 6 10 ) +C_4 graph with vertex sequence ( 0 2 1 3 ). +( 1 1 3 4 ) +( 0 0 2 4 ) +Zachary karate graph, no vertex list given. +Zachary karate graph, vertices in storage order. +( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 14 15 16 16 17 17 18 18 18 18 18 18 22 23 25 26 27 33 34 ) +( 0 1 3 6 7 8 11 15 17 18 21 22 24 28 28 28 30 32 32 34 34 36 36 36 36 38 38 41 42 44 46 50 61 78 ) +K_5 with too big vertex index +K_5 with missing vertices +( 1 2 3 ) +( 0 1 3 ) +K_5 with repeated vertices +Null outputs +# Edge list percolation +Percolation with ( 0 1 1 2 1 ), odd number of entries +Percolation with ( -1 1 0 0 1 -1 ), negative numbers +Percolation with ( 0 0 0 1 1 2 2 0 ), k_3 with loop +( 1 2 3 3 ) +( 1 2 3 3 ) +Null outputs diff --git a/tests/unit/prop_caching.c b/tests/unit/prop_caching.c new file mode 100644 index 0000000..78f2241 --- /dev/null +++ b/tests/unit/prop_caching.c @@ -0,0 +1,168 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* + * This test tries to ensure that property caching works correctly in the most + * "dangerous" scenarios (going from disconnected to connected graphs and + * back). Feel free to extend the file later on with regression tests for any + * issues that we might find related to caching. + * + * There are no direct APIs to reach into the cache internals (because the + * presence of the cache is an implementation detail). Therefore, we simply do + * the following: + * + * - perform some operation + * - query the value of certain properties that we know that are typically + * cached (e.g., connectedness, presence of multi- and loop edges) etc + * - invalidate the cache + * - perform the query again, check whether the results match + * + * The assumption is that functions like igraph_is_simple(), igraph_has_loops() + * etc work correctly if the cache is empty; correctness of these functions + * without a cache should be tested in other unit tests, not here. + */ + +igraph_error_t has_mutual_nonloop_edge(const igraph_t* graph, igraph_bool_t* result) { + return igraph_has_mutual(graph, result, /* loops = */ false); +} + +igraph_error_t has_mutual_edge(const igraph_t* graph, igraph_bool_t* result) { + return igraph_has_mutual(graph, result, /* loops = */ true); +} + +igraph_error_t is_weakly_connected(const igraph_t* graph, igraph_bool_t* result) { + return igraph_is_connected(graph, result, IGRAPH_WEAK); +} + +igraph_error_t is_strongly_connected(const igraph_t* graph, igraph_bool_t* result) { + return igraph_is_connected(graph, result, IGRAPH_STRONG); +} + +igraph_error_t is_forest(const igraph_t* graph, igraph_bool_t* result) { + return igraph_is_forest(graph, result, /* roots = */ NULL, IGRAPH_ALL); +} + +igraph_error_t is_simple(const igraph_t* graph, igraph_bool_t* result) { + return igraph_is_simple(graph, result, IGRAPH_DIRECTED); +} + +igraph_error_t is_simple_ignoring_directions(const igraph_t* graph, igraph_bool_t* result) { + return igraph_is_simple(graph, result, IGRAPH_UNDIRECTED); +} + +void validate_properties(const igraph_t* graph) { + igraph_bool_t result, cached_result, recalculated_result; + +#define CHECK(func) { \ + igraph_invalidate_cache(graph); \ + func(graph, &result); \ + func(graph, &cached_result); \ + igraph_invalidate_cache(graph); \ + func(graph, &recalculated_result); \ + IGRAPH_ASSERT(result == cached_result); \ + IGRAPH_ASSERT(recalculated_result == cached_result); \ +} + + CHECK(igraph_has_loop); + CHECK(igraph_has_multiple); + CHECK(igraph_is_dag); + CHECK(is_forest); + CHECK(is_simple); + CHECK(is_simple_ignoring_directions); + CHECK(is_weakly_connected); + CHECK(is_strongly_connected); + CHECK(has_mutual_edge); + CHECK(has_mutual_nonloop_edge); +} + +void test_basic_operations(igraph_t* graph) { + validate_properties(graph); + igraph_add_vertices(graph, 1, /* attr = */ NULL); + validate_properties(graph); + igraph_add_vertices(graph, 2, /* attr = */ NULL); + validate_properties(graph); + igraph_add_edge(graph, 0, 1); + validate_properties(graph); + igraph_add_edge(graph, 0, 2); + validate_properties(graph); + igraph_add_edge(graph, 1, 2); + validate_properties(graph); + igraph_add_edge(graph, 2, 0); + validate_properties(graph); + igraph_delete_edges(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID)); + validate_properties(graph); + igraph_add_edge(graph, 0, 2); + validate_properties(graph); + igraph_delete_vertices(graph, igraph_vss_1(1)); + validate_properties(graph); + igraph_delete_vertices(graph, igraph_vss_all()); + validate_properties(graph); +} + +int test_basic_operations_directed(void) { + igraph_t g; + + igraph_empty(&g, 0, IGRAPH_DIRECTED); + test_basic_operations(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} + +int test_basic_operations_undirected(void) { + igraph_t g; + + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + test_basic_operations(&g); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} + +int test_multi_loops_adjlist_init(void) { + igraph_t g; + igraph_adjlist_t al; + igraph_bool_t multi; + + igraph_small(&g, 1, IGRAPH_UNDIRECTED, + 0,0, 0,0, -1); + igraph_adjlist_init(&g, &al, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE); + igraph_has_multiple(&g, &multi); + IGRAPH_ASSERT(multi); + igraph_adjlist_destroy(&al); + igraph_destroy(&g); + + return 0; +} + +int main(void) { + + RUN_TEST(test_basic_operations_directed); + RUN_TEST(test_basic_operations_undirected); + RUN_TEST(test_multi_loops_adjlist_init); + + return 0; +} diff --git a/tests/unit/random_sampling.c b/tests/unit/random_sampling.c new file mode 100644 index 0000000..571fa73 --- /dev/null +++ b/tests/unit/random_sampling.c @@ -0,0 +1,306 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +/* Basic check: Are means within tol*sigma from the expected value? + * This is meant to catch gross bugs while changing RNG/sampler code. */ +void stats(void) { + igraph_int_t k; + const igraph_int_t n = 100000; + igraph_real_t m, tm, tsd; + igraph_real_t tol = 3; + + printf("\n"); + + /* Binary trials with success of probability p. Counting successes. */ + { + igraph_real_t p = 0.1; + m = 0; + for (k = 0; k < n; k++) { + if (RNG_UNIF01() < p) { + m += 1; + } + } + tm = n*p; + tsd = sqrt(n*p*(1-p)); + printf("binary trials: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of Poisson distributed values. */ + { + tm = 2; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_POIS(tm); + } + m /= n; + tsd = sqrt(tm) / sqrt(n); + printf("pois: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of geometrically distributed values. */ + { + igraph_real_t p = 0.25; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_GEOM(p); + } + m /= n; + tm = (1-p) / p; + tsd = sqrt(1 - p) / p / sqrt(n); + printf("geom: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of binomially distributed values. */ + { + igraph_real_t p = 0.33; + igraph_int_t nn = 77; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_BINOM(nn, p); + } + m /= n; + tm = nn * p; + tsd = sqrt(nn*p*(1-p)) / sqrt(n); + printf("binom: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of exponentially distributed values. */ + { + tm = 3; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_EXP(1 / tm); + } + m /= n; + tsd = tm / sqrt(n); + printf("exp: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of gamma distributed values. */ + { + igraph_real_t shape = 1.5, scale = 3.5; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_GAMMA(shape, scale); + } + m /= n; + tm = shape*scale; + tsd = sqrt(shape) * scale / sqrt(n); + printf("gamma: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + /* Mean of normally distributed values. */ + { + tm = 3.0; tsd = 2.0; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_NORMAL(tm, tsd); + } + m /= n; + tsd /= sqrt(n); + printf("norm: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } + + { + tm = 0.5; tsd = 0.5; + m = 0; + for (k = 0; k < n; k++) { + m += RNG_BOOL(); + } + m /= n; + printf("binary: %g; expected: %g; std. dev.: %g\n", m, tm, tsd); + IGRAPH_ASSERT(tm - tol*tsd < m && m < tm + tol*tsd); + } +} + +/* These is merely a smoke test for various random samplers. + * It does not verify the correctness of the result, except + * for some special edge cases. */ + +void sample(void) { + igraph_int_t i; + igraph_real_t x; + igraph_bool_t b; + + i = RNG_INTEGER(-100, 100); + printf("integer: %" IGRAPH_PRId "\n", i); + IGRAPH_ASSERT(-100 <= i && i <= 100); + + i = RNG_INTEGER(2, 2); + printf("integer: %" IGRAPH_PRId "\n", i); + IGRAPH_ASSERT(i == 2); + + i = RNG_INTEGER(-5, -5); + printf("integer: %" IGRAPH_PRId "\n", i); + IGRAPH_ASSERT(i == -5); + + x = RNG_UNIF(-100, 100); + printf("unif: %g\n", x); + IGRAPH_ASSERT(-100 <= x && x < 100); + + x = RNG_UNIF(3, 3); + printf("unif: %g\n", x); + IGRAPH_ASSERT(x == 3); + + x = RNG_UNIF(-4.5, -4.5); + printf("unif: %g\n", x); + IGRAPH_ASSERT(x == -4.5); + + x = RNG_UNIF01(); + printf("unif01: %g\n", x); + IGRAPH_ASSERT(0 <= x); + IGRAPH_ASSERT(x < 1); + + x = RNG_BINOM(10, 0.3); + printf("binom: %g\n", x); + IGRAPH_ASSERT(0 <= x && x <= 10); + + x = RNG_BINOM(5, 0); + IGRAPH_ASSERT(x == 0); + + x = RNG_BINOM(5, 1); + IGRAPH_ASSERT(x == 5); + + x = RNG_BINOM((1LL << 31) - 1, 0.5); + IGRAPH_ASSERT(!isnan(x)); + IGRAPH_ASSERT(0 <= x && x <= (1LL << 31) - 1); + +#if IGRAPH_INTEGER_SIZE > 32 + x = RNG_BINOM((1LL << 31), 0.5); + IGRAPH_ASSERT(!isnan(x)); + IGRAPH_ASSERT(0 <= x && x <= (1LL << 31) - 1); +#endif + + x = RNG_GEOM(0.2); + printf("geom: %g\n", x); + IGRAPH_ASSERT(0 <= x); + + x = RNG_GEOM(1); + IGRAPH_ASSERT(x == 0); + + x = RNG_GEOM(0); + IGRAPH_ASSERT(isnan(x)); + + x = RNG_NORMAL(0, 1); + printf("normal: %g\n", x); + IGRAPH_ASSERT(isfinite(x)); + + x = RNG_EXP(1); + printf("exp: %g\n", x); + IGRAPH_ASSERT(0 <= x); + + x = RNG_EXP(0); + IGRAPH_ASSERT(isnan(x)); + + x = RNG_GAMMA(1, 1); + printf("gamma: %g\n", x); + IGRAPH_ASSERT(0 <= x); + + /* Note: Some systems would reject 0 as a parameter value instead of returning 0 */ + x = RNG_GAMMA(0, 1); + IGRAPH_ASSERT(x == 0); + + x = RNG_GAMMA(1, 0); + IGRAPH_ASSERT(x == 0); + + x = RNG_POIS(10); + printf("poisson: %g\n", x); + IGRAPH_ASSERT(0 <= x && isfinite(x)); + + x = RNG_POIS(0); + IGRAPH_ASSERT(x == 0); + + x = RNG_POIS(-1); + IGRAPH_ASSERT(isnan(x)); + + x = RNG_POIS((1LL << 31) - 1); + IGRAPH_ASSERT(0 <= x && isfinite(x)); + + x = RNG_POIS((1LL << 31)); + IGRAPH_ASSERT(0 <= x && isfinite(x)); + +#if IGRAPH_INTEGER_SIZE > 32 + x = RNG_POIS((1LL << 32)); + IGRAPH_ASSERT(0 <= x && isfinite(x)); +#endif + + b = RNG_BOOL(); + IGRAPH_ASSERT(b == true || b == false); +} + +void test_and_destroy(igraph_rng_type_t *rng_type) { + igraph_rng_t *def = igraph_rng_default(); + igraph_rng_t rng; + + igraph_error_handler_t *oldhandler = igraph_set_error_handler(&igraph_error_handler_printignore); + igraph_error_t err = igraph_rng_init(&rng, rng_type); + switch (err) { + case IGRAPH_SUCCESS: + break; + case IGRAPH_UNIMPLEMENTED: + return; + default: + IGRAPH_FATAL("Error while initializing RNG."); + } + igraph_set_error_handler(oldhandler); + + printf("\n%s\n\n", igraph_rng_name(&rng)); + + igraph_rng_set_default(&rng); + igraph_rng_seed(igraph_rng_default(), 137); + + sample(); + stats(); + + igraph_rng_set_default(def); + igraph_rng_destroy(&rng); +} + +int main(void) { + igraph_rng_type_t rng_types[] = { + igraph_rngtype_glibc2, + igraph_rngtype_mt19937, + igraph_rngtype_pcg32, + igraph_rngtype_pcg64 + }; + + printf("Default\n\n"); + igraph_rng_seed(igraph_rng_default(), 709); + + sample(); + stats(); + + for (size_t i = 0; i < sizeof(rng_types) / sizeof(rng_types[0]); i++) { + test_and_destroy(&rng_types[i]); + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/random_spanning_tree.c b/tests/unit/random_spanning_tree.c new file mode 100644 index 0000000..29d2b85 --- /dev/null +++ b/tests/unit/random_spanning_tree.c @@ -0,0 +1,75 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph, spanning_tree; + igraph_vector_int_t tree_edges; + igraph_bool_t is_tree; + int err; + + igraph_rng_seed(igraph_rng_default(), 987); + + igraph_vector_int_init(&tree_edges, 0); + + /* This is guaranteed to create a connected graph. */ + igraph_barabasi_game(&graph, 100, 2, 2, NULL, 0, 1, IGRAPH_UNDIRECTED, IGRAPH_BARABASI_PSUMTREE, NULL); + + err = igraph_random_spanning_tree(&graph, &tree_edges, 0); + IGRAPH_ASSERT(!err); + + IGRAPH_ASSERT(igraph_vector_int_size(&tree_edges) == igraph_vcount(&graph) - 1); + + err = igraph_subgraph_from_edges(&graph, &spanning_tree, igraph_ess_vector(&tree_edges), /* delete_vertices= */ 0); + IGRAPH_ASSERT(!err); + + IGRAPH_ASSERT(igraph_vcount(&spanning_tree) == igraph_vcount(&graph)); + + igraph_is_tree(&spanning_tree, &is_tree, NULL, IGRAPH_ALL); + IGRAPH_ASSERT(is_tree); + + igraph_destroy(&spanning_tree); + igraph_destroy(&graph); + + /* Non-connected forest graph. There is only one solution. */ + igraph_small(&graph, 4, IGRAPH_UNDIRECTED, 0,1, 2,3, -1); + + /* Find a spanning tree of the component containing vertex 0 */ + err = igraph_random_spanning_tree(&graph, &tree_edges, 0); + IGRAPH_ASSERT(!err); + + IGRAPH_ASSERT(igraph_vector_int_size(&tree_edges) == 1); + IGRAPH_ASSERT(VECTOR(tree_edges)[0] == 0); + + /* Find a spanning forest */ + err = igraph_random_spanning_tree(&graph, &tree_edges, -1); + IGRAPH_ASSERT(!err); + + IGRAPH_ASSERT(igraph_vector_int_size(&tree_edges) == 2); + IGRAPH_ASSERT(VECTOR(tree_edges)[0] == 0 && VECTOR(tree_edges)[1] == 1); + + igraph_destroy(&graph); + + igraph_vector_int_destroy(&tree_edges); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/reachability.c b/tests/unit/reachability.c new file mode 100644 index 0000000..c78cf9b --- /dev/null +++ b/tests/unit/reachability.c @@ -0,0 +1,129 @@ +/* + igraph library. + Copyright (C) 2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +void compute_and_print(const igraph_t *g, igraph_neimode_t mode) { + igraph_vector_int_t membership, csize, reach_counts; + igraph_bitset_list_t reach; + igraph_int_t no_of_nodes, no_of_components; + + no_of_nodes = igraph_vcount(g); + + igraph_vector_int_init(&membership, 0); + igraph_vector_int_init(&csize, 0); + igraph_bitset_list_init(&reach, 0); + + igraph_reachability(g, &membership, &csize, &no_of_components, &reach, mode); + + igraph_vector_int_init(&reach_counts, no_of_nodes); + + igraph_count_reachable(g, &reach_counts, mode); + + printf("Mode: "); + switch (mode) { + case IGRAPH_OUT: + printf("OUT\n"); break; + case IGRAPH_IN: + printf("IN\n"); break; + case IGRAPH_ALL: + printf("ALL\n"); break; + } + print_vector_int(&membership); + print_vector_int(&csize); + printf("No. of components: %" IGRAPH_PRId "\n", no_of_components); + print_bitset_list(&reach); + print_vector_int(&reach_counts); + + igraph_bitset_list_destroy(&reach); + igraph_vector_int_destroy(&csize); + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&reach_counts); +} + +int main(void) { + igraph_t g; + + /* Component calculations are run twice to exercise the cache */ + + printf("\nNull graph (not connected)\n"); + igraph_empty(&g, 0, IGRAPH_DIRECTED); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nSingleton graph (connected)\n"); + igraph_empty(&g, 1, IGRAPH_DIRECTED); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nKautz graph (connected)\n"); + igraph_kautz(&g, 2, 2); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nDirected 2-path\n"); + igraph_small(&g, 2, IGRAPH_DIRECTED, 0, 1, -1); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nTwo disjoint 3-cycles\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 3, 4, 4, 5, 5, 3, -1); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_IN); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nPath graph with 6 vertices ascending\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, -1); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_IN); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nPath graph with 6 vertices descending\n"); + igraph_small(&g, 6, IGRAPH_DIRECTED, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, -1); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_IN); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nSmall directed graph\n"); + igraph_small(&g, 13, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 0, 1, 3, 3, 4, 4, 5, 5, 4, 7, 4, 7, 8, 9, 8, 10, 9, 8, 10, 11, 6, 12, 6, -1); + compute_and_print(&g, IGRAPH_OUT); + compute_and_print(&g, IGRAPH_IN); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + printf("\nSmall undirected graph\n"); + igraph_small(&g, 6, IGRAPH_UNDIRECTED, + 0,1, 1,2, 0,2, + 3,4, + -1); + compute_and_print(&g, IGRAPH_ALL); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/reachability.out b/tests/unit/reachability.out new file mode 100644 index 0000000..dd27b85 --- /dev/null +++ b/tests/unit/reachability.out @@ -0,0 +1,225 @@ + +Null graph (not connected) +Mode: OUT +( ) +( ) +No. of components: 0 +{ +} +( ) +Mode: ALL +( ) +( ) +No. of components: 0 +{ +} +( ) + +Singleton graph (connected) +Mode: OUT +( 0 ) +( 1 ) +No. of components: 1 +{ + 0: ( 1 ) +} +( 1 ) +Mode: ALL +( 0 ) +( 1 ) +No. of components: 1 +{ + 0: ( 1 ) +} +( 1 ) + +Kautz graph (connected) +Mode: OUT +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 +{ + 0: ( 1 1 1 1 1 1 1 1 1 1 1 1 ) +} +( 12 12 12 12 12 12 12 12 12 12 12 12 ) +Mode: ALL +( 0 0 0 0 0 0 0 0 0 0 0 0 ) +( 12 ) +No. of components: 1 +{ + 0: ( 1 1 1 1 1 1 1 1 1 1 1 1 ) +} +( 12 12 12 12 12 12 12 12 12 12 12 12 ) + +Directed 2-path +Mode: OUT +( 0 1 ) +( 1 1 ) +No. of components: 2 +{ + 0: ( 1 1 ) + 1: ( 1 0 ) +} +( 2 1 ) +Mode: ALL +( 0 0 ) +( 2 ) +No. of components: 1 +{ + 0: ( 1 1 ) +} +( 2 2 ) + +Two disjoint 3-cycles +Mode: OUT +( 1 1 1 0 0 0 ) +( 3 3 ) +No. of components: 2 +{ + 0: ( 1 1 1 0 0 0 ) + 1: ( 0 0 0 1 1 1 ) +} +( 3 3 3 3 3 3 ) +Mode: IN +( 1 1 1 0 0 0 ) +( 3 3 ) +No. of components: 2 +{ + 0: ( 1 1 1 0 0 0 ) + 1: ( 0 0 0 1 1 1 ) +} +( 3 3 3 3 3 3 ) +Mode: ALL +( 0 0 0 1 1 1 ) +( 3 3 ) +No. of components: 2 +{ + 0: ( 0 0 0 1 1 1 ) + 1: ( 1 1 1 0 0 0 ) +} +( 3 3 3 3 3 3 ) + +Path graph with 6 vertices ascending +Mode: OUT +( 0 1 2 3 4 5 ) +( 1 1 1 1 1 1 ) +No. of components: 6 +{ + 0: ( 1 1 1 1 1 1 ) + 1: ( 1 1 1 1 1 0 ) + 2: ( 1 1 1 1 0 0 ) + 3: ( 1 1 1 0 0 0 ) + 4: ( 1 1 0 0 0 0 ) + 5: ( 1 0 0 0 0 0 ) +} +( 6 5 4 3 2 1 ) +Mode: IN +( 0 1 2 3 4 5 ) +( 1 1 1 1 1 1 ) +No. of components: 6 +{ + 0: ( 0 0 0 0 0 1 ) + 1: ( 0 0 0 0 1 1 ) + 2: ( 0 0 0 1 1 1 ) + 3: ( 0 0 1 1 1 1 ) + 4: ( 0 1 1 1 1 1 ) + 5: ( 1 1 1 1 1 1 ) +} +( 1 2 3 4 5 6 ) +Mode: ALL +( 0 0 0 0 0 0 ) +( 6 ) +No. of components: 1 +{ + 0: ( 1 1 1 1 1 1 ) +} +( 6 6 6 6 6 6 ) + +Path graph with 6 vertices descending +Mode: OUT +( 5 4 3 2 1 0 ) +( 1 1 1 1 1 1 ) +No. of components: 6 +{ + 0: ( 1 1 1 1 1 1 ) + 1: ( 0 1 1 1 1 1 ) + 2: ( 0 0 1 1 1 1 ) + 3: ( 0 0 0 1 1 1 ) + 4: ( 0 0 0 0 1 1 ) + 5: ( 0 0 0 0 0 1 ) +} +( 1 2 3 4 5 6 ) +Mode: IN +( 5 4 3 2 1 0 ) +( 1 1 1 1 1 1 ) +No. of components: 6 +{ + 0: ( 1 0 0 0 0 0 ) + 1: ( 1 1 0 0 0 0 ) + 2: ( 1 1 1 0 0 0 ) + 3: ( 1 1 1 1 0 0 ) + 4: ( 1 1 1 1 1 0 ) + 5: ( 1 1 1 1 1 1 ) +} +( 6 5 4 3 2 1 ) +Mode: ALL +( 0 0 0 0 0 0 ) +( 6 ) +No. of components: 1 +{ + 0: ( 1 1 1 1 1 1 ) +} +( 6 6 6 6 6 6 ) + +Small directed graph +Mode: OUT +( 5 5 5 6 7 7 4 2 3 3 3 1 0 ) +( 1 1 1 3 1 3 1 2 ) +No. of components: 8 +{ + 0: ( 1 0 0 0 0 0 1 0 0 0 0 0 0 ) + 1: ( 0 1 0 0 0 0 1 0 0 0 0 0 0 ) + 2: ( 0 0 1 1 1 1 0 1 1 0 0 0 0 ) + 3: ( 0 0 1 1 1 0 0 0 0 0 0 0 0 ) + 4: ( 0 0 0 0 0 0 1 0 0 0 0 0 0 ) + 5: ( 0 0 0 0 0 0 0 1 1 1 1 1 1 ) + 6: ( 0 0 0 0 0 0 0 1 1 1 0 0 0 ) + 7: ( 0 0 0 0 0 0 0 1 1 0 0 0 0 ) +} +( 6 6 6 3 2 2 1 6 3 3 3 2 2 ) +Mode: IN +( 5 5 5 6 7 7 4 2 3 3 3 1 0 ) +( 1 1 1 3 1 3 1 2 ) +No. of components: 8 +{ + 0: ( 1 0 0 0 0 0 0 0 0 0 0 0 0 ) + 1: ( 0 1 0 0 0 0 0 0 0 0 0 0 0 ) + 2: ( 0 0 0 0 0 1 0 0 0 0 0 0 0 ) + 3: ( 0 0 1 1 1 1 0 0 0 0 0 0 0 ) + 4: ( 1 1 0 0 0 0 1 0 0 0 0 0 0 ) + 5: ( 0 0 0 0 0 0 0 0 0 0 1 1 1 ) + 6: ( 0 0 0 0 0 0 0 0 0 1 1 1 1 ) + 7: ( 0 0 0 0 0 1 0 1 1 1 1 1 1 ) +} +( 3 3 3 4 7 7 3 1 4 4 4 1 1 ) +Mode: ALL +( 0 0 0 0 0 0 1 0 0 0 0 1 1 ) +( 10 3 ) +No. of components: 2 +{ + 0: ( 0 0 1 1 1 1 0 1 1 1 1 1 1 ) + 1: ( 1 1 0 0 0 0 1 0 0 0 0 0 0 ) +} +( 10 10 10 10 10 10 3 10 10 10 10 3 3 ) + +Small undirected graph +Mode: ALL +( 0 0 0 1 1 2 ) +( 3 2 1 ) +No. of components: 3 +{ + 0: ( 0 0 0 1 1 1 ) + 1: ( 0 1 1 0 0 0 ) + 2: ( 1 0 0 0 0 0 ) +} +( 3 3 3 2 2 1 ) diff --git a/tests/unit/rich_club.c b/tests/unit/rich_club.c new file mode 100644 index 0000000..50816fd --- /dev/null +++ b/tests/unit/rich_club.c @@ -0,0 +1,288 @@ +/* + igraph library. + Copyright (C) 2025 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include "test_utilities.h" + +void null_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + + igraph_vector_init(&result, 0); + igraph_vector_int_init(&vertex_order, 0); + igraph_empty(&graph, 0, IGRAPH_UNDIRECTED); // null graph + + /* output */ + printf("Test 1: null graph\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, false); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void singleton_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + + igraph_vector_init(&result, 1); + igraph_vector_int_init(&vertex_order, 1); // vertex_order: [0] + VECTOR(vertex_order)[0] = 0; + igraph_empty(&graph, 1, IGRAPH_UNDIRECTED); // singleton + + /* output */ + printf("Test 2: singleton graph\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, false); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void undirected_no_loop_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_vector_init(&result, vcount); + igraph_vector_int_init(&vertex_order, vcount); + for (igraph_int_t i = 0; i < vcount; i++){ + VECTOR(vertex_order)[i] = i; + } + igraph_small(&graph, vcount, IGRAPH_UNDIRECTED, + 0,3, 1,3, 2,3, 4,3, 5,3, 5,6, 1,2, 2,5, -1); + + /* output */ + printf("Test 3a: undirected, no-loop graph (in-order vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, false); + print_vector(&result); + printf("\n"); + + igraph_vector_int_reverse(&vertex_order); + printf("Test 3b: undirected, no-loop graph (reverse vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, false); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void directed_no_loop_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_vector_init(&result, vcount); + igraph_vector_int_init(&vertex_order, vcount); + for (igraph_int_t i = 0; i < vcount; i++){ + VECTOR(vertex_order)[i] = i; + } + igraph_small(&graph, vcount, IGRAPH_DIRECTED, + 0,2, 1,2, 2,3, 1,3, 3,5, 3,4, 5,6, 6,5, -1); + + /* output */ + printf("Test 4a: directed, no-loop graph (in-order vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, true); + print_vector(&result); + printf("\n"); + + igraph_vector_int_reverse(&vertex_order); + printf("Test 4b: directed, no-loop graph (reverse vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, false, true); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void undirected_loop_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_vector_init(&result, vcount); + igraph_vector_int_init(&vertex_order, vcount); + for (igraph_int_t i = 0; i < vcount; i++){ + VECTOR(vertex_order)[i] = i; + } + igraph_small(&graph, vcount, IGRAPH_UNDIRECTED, + 0,3, 1,3, 2,3, 4,4, 5,3, 5,6, 1,2, 2,5, 6,4, -1); + + /* output */ + printf("Test 5a: undirected, loop graph (in-order vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, true, false); + print_vector(&result); + printf("\n"); + + igraph_vector_int_reverse(&vertex_order); + printf("Test 5b: undirected, loop graph (reverse vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, true, false); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void directed_loop_graph(void) { + igraph_t graph; + igraph_vector_t result; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_vector_init(&result, vcount); + igraph_vector_int_init(&vertex_order, vcount); + for (igraph_int_t i = 0; i < vcount; i++){ + VECTOR(vertex_order)[i] = i; + } + igraph_small(&graph, vcount, IGRAPH_DIRECTED, + 0,2, 1,2, 2,3, 1,3, 3,5, 3,4, 5,6, 6,5, 4,4, -1); + + /* output */ + printf("Test 6a: directed, loop graph (in-order vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, true, true); + print_vector(&result); + printf("\n"); + + igraph_vector_int_reverse(&vertex_order); + printf("Test 6b: directed, loop graph (reverse vertex removal)\n"); + igraph_rich_club_sequence(&graph, NULL, &result, &vertex_order, true, true, true); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +void weighted_graph(void) { + // same undirected, no-loop graph as Test 3 + igraph_t graph; + igraph_vector_t result, weights; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_small(&graph, vcount, IGRAPH_UNDIRECTED, + 0,3, 1,3, 2,3, 4,3, 5,3, 5,6, 1,2, 2,5, -1); + + igraph_vector_init(&result, vcount); + igraph_vector_int_init(&vertex_order, vcount); + for (igraph_int_t i = 0; i < vcount; i++){ + VECTOR(vertex_order)[i] = i; + } + + igraph_vector_init(&weights, igraph_ecount(&graph)); + for (igraph_int_t i = 0; i < igraph_ecount(&graph); i++) { + // all "edges" have weight 2, output should be double of Test 3a + VECTOR(weights)[i] = 2; + } + + /* output */ + printf("Test 7a: weighted graph\n"); + igraph_rich_club_sequence(&graph, &weights, &result, + &vertex_order, true, false, false); + print_vector(&result); + printf("\n"); + + // change weights to include some non-integer weights + // for this specific graph, weights should now be the same as that of Test 3a + VECTOR(weights)[0] = 1; // (0,3) + VECTOR(weights)[1] = 0.5; // (1,3) + VECTOR(weights)[2] = 0.5; // (2,3) + VECTOR(weights)[3] = 1; // (4,3) + VECTOR(weights)[4] = 1; // (5,3) + VECTOR(weights)[5] = 1; // (5,6) + VECTOR(weights)[6] = 1.5; // (1,2) + VECTOR(weights)[7] = 1.5; // (2,5) + + printf("Test 7b: weighted graph (non-integer weights)\n"); + igraph_rich_club_sequence(&graph, &weights, &result, + &vertex_order, true, false, false); + print_vector(&result); + + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_vector_destroy(&weights); + igraph_destroy(&graph); +} + +void error_checks(void) { + igraph_t graph; + igraph_vector_t result, weights; + igraph_vector_int_t vertex_order; + const igraph_int_t vcount = 7; + + igraph_small(&graph, vcount, IGRAPH_UNDIRECTED, + 0,3, 1,3, 2,3, 4,3, 5,3, 5,6, 1,2, 2,5, -1); // 8 edges + + igraph_vector_init(&result, vcount); + + // wrong vertex order size (!= number of vertices) + igraph_vector_int_init(&vertex_order, 1); + CHECK_ERROR(igraph_rich_club_sequence(&graph, NULL, &result, + &vertex_order, true, false, false), + IGRAPH_EINVAL); + igraph_vector_int_resize(&vertex_order, vcount); + + // wrong weights vector size (!= number of edges) + igraph_vector_init(&weights, 1); + CHECK_ERROR(igraph_rich_club_sequence(&graph, &weights, &result, + &vertex_order, true, false, false), + IGRAPH_EINVAL); + + igraph_vector_destroy(&weights); + igraph_vector_int_destroy(&vertex_order); + igraph_vector_destroy(&result); + igraph_destroy(&graph); +} + +int main(void) { + null_graph(); + printf("\n"); + + singleton_graph(); + printf("\n"); + + undirected_no_loop_graph(); + printf("\n"); + + directed_no_loop_graph(); + printf("\n"); + + undirected_loop_graph(); + printf("\n"); + + directed_loop_graph(); + printf("\n"); + + weighted_graph(); + + error_checks(); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/rich_club.out b/tests/unit/rich_club.out new file mode 100644 index 0000000..786a2fa --- /dev/null +++ b/tests/unit/rich_club.out @@ -0,0 +1,35 @@ +Test 1: null graph +( ) + +Test 2: singleton graph +( NaN ) + +Test 3a: undirected, no-loop graph (in-order vertex removal) +( 0.380952 0.466667 0.5 0.5 0.333333 1 NaN ) + +Test 3b: undirected, no-loop graph (reverse vertex removal) +( 0.380952 0.466667 0.5 0.666667 0.333333 0 NaN ) + +Test 4a: directed, no-loop graph (in-order vertex removal) +( 0.190476 0.233333 0.25 0.333333 0.333333 1 NaN ) + +Test 4b: directed, no-loop graph (reverse vertex removal) +( 0.190476 0.2 0.25 0.333333 0.333333 0 NaN ) + +Test 5a: undirected, loop graph (in-order vertex removal) +( 0.321429 0.380952 0.4 0.4 0.5 0.333333 0 ) + +Test 5b: undirected, loop graph (reverse vertex removal) +( 0.321429 0.333333 0.333333 0.4 0.166667 0 0 ) + +Test 6a: directed, loop graph (in-order vertex removal) +( 0.183673 0.222222 0.24 0.3125 0.333333 0.5 0 ) + +Test 6b: directed, loop graph (reverse vertex removal) +( 0.183673 0.194444 0.24 0.25 0.222222 0 0 ) + +Test 7a: weighted graph +( 0.761905 0.933333 1 1 0.666667 2 NaN ) + +Test 7b: weighted graph (non-integer weights) +( 0.380952 0.466667 0.5 0.5 0.333333 1 NaN ) \ No newline at end of file diff --git a/tests/unit/ring.c b/tests/unit/ring.c new file mode 100644 index 0000000..b723847 --- /dev/null +++ b/tests/unit/ring.c @@ -0,0 +1,51 @@ +/* + igraph library. + Copyright (C) 2009-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + int directed, circular, mutual; + int n; + + for (n=0; n < 4; ++n) { + for (directed=0; directed < 2; ++directed) { + for (circular=0; circular < 2; ++circular) { + for (mutual=0; mutual < 2; ++mutual) { + igraph_t graph; + igraph_ring(&graph, n, directed, mutual, circular); + printf("n=%d, %s, %s, %s\n", n, + directed ? "directed" : "undirected", + circular ? "cycle" : "path", + mutual ? "bidirectional" : "unidirectional" + ); + print_graph_canon(&graph); + printf("\n"); + igraph_destroy(&graph); + } + } + } + } + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/ring.out b/tests/unit/ring.out new file mode 100644 index 0000000..9f7848e --- /dev/null +++ b/tests/unit/ring.out @@ -0,0 +1,237 @@ +n=0, undirected, path, unidirectional +directed: false +vcount: 0 +edges: { +} + +n=0, undirected, path, bidirectional +directed: false +vcount: 0 +edges: { +} + +n=0, undirected, cycle, unidirectional +directed: false +vcount: 0 +edges: { +} + +n=0, undirected, cycle, bidirectional +directed: false +vcount: 0 +edges: { +} + +n=0, directed, path, unidirectional +directed: true +vcount: 0 +edges: { +} + +n=0, directed, path, bidirectional +directed: true +vcount: 0 +edges: { +} + +n=0, directed, cycle, unidirectional +directed: true +vcount: 0 +edges: { +} + +n=0, directed, cycle, bidirectional +directed: true +vcount: 0 +edges: { +} + +n=1, undirected, path, unidirectional +directed: false +vcount: 1 +edges: { +} + +n=1, undirected, path, bidirectional +directed: false +vcount: 1 +edges: { +} + +n=1, undirected, cycle, unidirectional +directed: false +vcount: 1 +edges: { +0 0 +} + +n=1, undirected, cycle, bidirectional +directed: false +vcount: 1 +edges: { +0 0 +} + +n=1, directed, path, unidirectional +directed: true +vcount: 1 +edges: { +} + +n=1, directed, path, bidirectional +directed: true +vcount: 1 +edges: { +} + +n=1, directed, cycle, unidirectional +directed: true +vcount: 1 +edges: { +0 0 +} + +n=1, directed, cycle, bidirectional +directed: true +vcount: 1 +edges: { +0 0 +0 0 +} + +n=2, undirected, path, unidirectional +directed: false +vcount: 2 +edges: { +0 1 +} + +n=2, undirected, path, bidirectional +directed: false +vcount: 2 +edges: { +0 1 +} + +n=2, undirected, cycle, unidirectional +directed: false +vcount: 2 +edges: { +0 1 +0 1 +} + +n=2, undirected, cycle, bidirectional +directed: false +vcount: 2 +edges: { +0 1 +0 1 +} + +n=2, directed, path, unidirectional +directed: true +vcount: 2 +edges: { +0 1 +} + +n=2, directed, path, bidirectional +directed: true +vcount: 2 +edges: { +0 1 +1 0 +} + +n=2, directed, cycle, unidirectional +directed: true +vcount: 2 +edges: { +0 1 +1 0 +} + +n=2, directed, cycle, bidirectional +directed: true +vcount: 2 +edges: { +0 1 +0 1 +1 0 +1 0 +} + +n=3, undirected, path, unidirectional +directed: false +vcount: 3 +edges: { +0 1 +1 2 +} + +n=3, undirected, path, bidirectional +directed: false +vcount: 3 +edges: { +0 1 +1 2 +} + +n=3, undirected, cycle, unidirectional +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +n=3, undirected, cycle, bidirectional +directed: false +vcount: 3 +edges: { +0 1 +0 2 +1 2 +} + +n=3, directed, path, unidirectional +directed: true +vcount: 3 +edges: { +0 1 +1 2 +} + +n=3, directed, path, bidirectional +directed: true +vcount: 3 +edges: { +0 1 +1 0 +1 2 +2 1 +} + +n=3, directed, cycle, unidirectional +directed: true +vcount: 3 +edges: { +0 1 +1 2 +2 0 +} + +n=3, directed, cycle, bidirectional +directed: true +vcount: 3 +edges: { +0 1 +0 2 +1 0 +1 2 +2 0 +2 1 +} + diff --git a/tests/unit/rng_init_destroy_max_bits_name_set_default.c b/tests/unit/rng_init_destroy_max_bits_name_set_default.c new file mode 100644 index 0000000..80f5946 --- /dev/null +++ b/tests/unit/rng_init_destroy_max_bits_name_set_default.c @@ -0,0 +1,132 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void test_and_destroy(igraph_rng_type_t *rng_type, igraph_rng_t *rng_def) { + int i; + igraph_rng_t rng; + + igraph_error_handler_t *oldhandler = igraph_set_error_handler(&igraph_error_handler_printignore); + igraph_error_t err = igraph_rng_init(&rng, rng_type); + switch (err) { + case IGRAPH_SUCCESS: + break; + case IGRAPH_UNIMPLEMENTED: + return; + default: + IGRAPH_FATAL("Error while initializing RNG."); + } + igraph_set_error_handler(oldhandler); + + printf("rng name: %s\n", igraph_rng_name(&rng)); + + igraph_rng_seed(&rng, 42); + for (i = 0; i < 5; i++) { + printf("%" IGRAPH_PRId "\n", igraph_rng_get_integer(&rng, 0, 100)); + } + printf("\n"); + + igraph_rng_set_default(&rng); + igraph_rng_seed(igraph_rng_default(), 42); + for (i = 0; i < 5; i++) { + printf("%" IGRAPH_PRId "\n", igraph_rng_get_integer(igraph_rng_default(), 0, 100)); + } + printf("\n"); + + IGRAPH_ASSERT(igraph_rng_max(&rng) >= 0x7fffffff); + IGRAPH_ASSERT(igraph_rng_bits(&rng) >= 31); + + igraph_rng_set_default(rng_def); + igraph_rng_destroy(&rng); +} + +void test_and_destroy_with_expected_values( + igraph_rng_type_t *rng_type, const igraph_vector_int_t *expected +) { + int i; + igraph_error_t retval; + igraph_rng_t rng; + + igraph_set_error_handler(igraph_error_handler_ignore); + retval = igraph_rng_init(&rng, rng_type); + igraph_set_error_handler(igraph_error_handler_abort); + + if (retval == IGRAPH_UNIMPLEMENTED) { + /* not supported in the current setup, this is OK */ + return; + } + + igraph_rng_seed(&rng, 42); + for (i = 0; i < 5; i++) { + IGRAPH_ASSERT(VECTOR(*expected)[i] == igraph_rng_get_integer(&rng, 0, 100)); + } + + igraph_rng_seed(&rng, 42); + for (i = 0; i < 5; i++) { + IGRAPH_ASSERT(VECTOR(*expected)[i] == igraph_rng_get_integer(&rng, 0, 100)); + } + + IGRAPH_ASSERT(igraph_rng_max(&rng) >= 0x7fffffff); + IGRAPH_ASSERT(igraph_rng_bits(&rng) >= 31); + + igraph_rng_destroy(&rng); +} + +void test_mandatory_rngtypes(void) { + int i; + igraph_rng_type_t rng_types[] = { + igraph_rngtype_glibc2, + igraph_rngtype_mt19937, + igraph_rngtype_pcg32, + }; + const int NUM_RNG_TYPES = sizeof(rng_types) / sizeof(rng_types[0]); + igraph_rng_t rng_def; + + IGRAPH_ASSERT(igraph_rng_init(&rng_def, &igraph_rngtype_glibc2) == IGRAPH_SUCCESS); + + for (i = 0; i < NUM_RNG_TYPES; i++) { + test_and_destroy(&rng_types[i], &rng_def); + } + + igraph_rng_destroy(&rng_def); + + VERIFY_FINALLY_STACK(); +} + +void test_optional_rngtypes(void) { + igraph_rng_type_t rng_types[] = { + igraph_rngtype_pcg64 + }; + igraph_int_t expected_values[1][5] = { + { 61, 38, 73, 84, 67 } + }; + int i; + + for (i = 0; i < 1; i++) { + const igraph_vector_int_t expected = igraph_vector_int_view(expected_values[i], 5); + test_and_destroy_with_expected_values(&rng_types[i], &expected); + } +} + +int main(void) { + test_mandatory_rngtypes(); + test_optional_rngtypes(); + return 0; +} diff --git a/tests/unit/rng_init_destroy_max_bits_name_set_default.out b/tests/unit/rng_init_destroy_max_bits_name_set_default.out new file mode 100644 index 0000000..b09e6df --- /dev/null +++ b/tests/unit/rng_init_destroy_max_bits_name_set_default.out @@ -0,0 +1,39 @@ +rng name: LIBC +3 +69 +20 +64 +30 + +3 +69 +20 +64 +30 + +rng name: MT19937 +37 +80 +96 +18 +73 + +37 +80 +96 +18 +73 + +rng name: PCG32 +13 +54 +84 +99 +99 + +13 +54 +84 +99 +99 + diff --git a/tests/unit/rng_reproducibility.c b/tests/unit/rng_reproducibility.c new file mode 100644 index 0000000..716c158 --- /dev/null +++ b/tests/unit/rng_reproducibility.c @@ -0,0 +1,38 @@ +/* + igraph library. + Copyright (C) 2019-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +/* + * This test serves to ensure that the same sequence of random numbers are generated for the + * same seed on all platforms (different operating systems and 32- or 64-bit systems). + */ + +int main(void) { + int i; + igraph_rng_seed(igraph_rng_default(), 137); + + for (i = 0; i < 32; ++i) { + printf("%" IGRAPH_PRId "\n", RNG_INTEGER(0, 100)); + } + + for (i = 0; i < 32; ++i) { + printf("%g\n", RNG_UNIF(0, 1e-6)); + } + return 0; +} diff --git a/tests/unit/rng_reproducibility.out b/tests/unit/rng_reproducibility.out new file mode 100644 index 0000000..6f30475 --- /dev/null +++ b/tests/unit/rng_reproducibility.out @@ -0,0 +1,64 @@ +70 +74 +60 +48 +5 +57 +56 +42 +14 +56 +41 +20 +7 +78 +38 +41 +97 +28 +30 +9 +68 +73 +30 +66 +11 +41 +22 +41 +4 +99 +35 +71 +4.87906e-07 +8.04065e-07 +8.5431e-07 +3.76015e-07 +4.00421e-07 +9.8439e-07 +3.55795e-08 +5.80515e-07 +6.83342e-07 +3.29069e-07 +3.99956e-07 +1.48455e-07 +5.61698e-07 +6.7363e-07 +4.30492e-07 +1.96356e-07 +3.13386e-07 +7.64698e-07 +5.05553e-07 +7.90849e-07 +2.74013e-07 +8.73595e-08 +8.06211e-07 +7.2246e-07 +2.00342e-07 +4.3115e-07 +3.00813e-07 +4.68466e-07 +6.67159e-07 +1.52378e-07 +7.37829e-07 +4.32757e-07 diff --git a/tests/unit/set.c b/tests/unit/set.c new file mode 100644 index 0000000..456e043 --- /dev/null +++ b/tests/unit/set.c @@ -0,0 +1,85 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "core/set.h" + +#include "test_utilities.h" + +void print_set(igraph_set_t *set, FILE *f) { + igraph_int_t state = 0; + igraph_int_t element; + while (igraph_set_iterate(set, &state, &element)) { + fprintf(f, " %" IGRAPH_PRId , element); + } + fprintf(f, "\n"); +} + +int main(void) { + + igraph_set_t set; + igraph_int_t i; + + /* simple init */ + igraph_set_init(&set, 0); + igraph_set_destroy(&set); + + /* addition, igraph_set_size */ + igraph_set_init(&set, 10); + i = 10; + while (igraph_set_size(&set) < 10) { + igraph_set_add(&set, 2 * i); + i--; + } + while (igraph_set_size(&set) < 21) { + igraph_set_add(&set, 2 * i + 1); + i++; + } + print_set(&set, stdout); + + /* adding existing element */ + igraph_set_add(&set, 8); + if (igraph_set_size(&set) != 21) { + return 4; + } + + /* igraph_set_contains */ + if (igraph_set_contains(&set, 42) || !igraph_set_contains(&set, 7)) { + return 3; + } + + /* igraph_set_empty, igraph_set_clear */ + if (igraph_set_empty(&set)) { + return 1; + } + igraph_set_clear(&set); + if (!igraph_set_empty(&set)) { + return 2; + } + igraph_set_destroy(&set); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/set.out b/tests/unit/set.out new file mode 100644 index 0000000..08c0614 --- /dev/null +++ b/tests/unit/set.out @@ -0,0 +1 @@ + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 diff --git a/tests/unit/si2_b06m_s20-bad1.A98 b/tests/unit/si2_b06m_s20-bad1.A98 new file mode 100644 index 0000000000000000000000000000000000000000..5c422aa607cb81028cc43b783aa2f78cbeeed5fb GIT binary patch literal 18 RcmZQ(U}RtdVh~^gV*mh<01N;C literal 0 HcmV?d00001 diff --git a/tests/unit/si2_b06m_s20-bad2.A98 b/tests/unit/si2_b06m_s20-bad2.A98 new file mode 100644 index 0000000000000000000000000000000000000000..1934c4e60ff50f7f4a717721f0320c515dc78d13 GIT binary patch literal 17 RcmZQ!U}RtdVh~^gVE_P?01W^D literal 0 HcmV?d00001 diff --git a/tests/unit/si2_b06m_s20.A98 b/tests/unit/si2_b06m_s20.A98 new file mode 100644 index 0000000000000000000000000000000000000000..8603c7d254530083b084d663e16092e7841ff4f7 GIT binary patch literal 18 RcmZQ!U}RtdVh~^gV*mi601W^D literal 0 HcmV?d00001 diff --git a/tests/unit/simplify_and_colorize.c b/tests/unit/simplify_and_colorize.c new file mode 100644 index 0000000..89213ed --- /dev/null +++ b/tests/unit/simplify_and_colorize.c @@ -0,0 +1,78 @@ +/* + igraph library. + Copyright (C) 2019-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +#define SIMPLIFY_PRINT_DESTROY(name) \ + printf(name "\n"); \ + igraph_simplify_and_colorize(&graph, &res, &vcol, &ecol); \ + print_graph(&res); \ + print_vector_int(&vcol); \ + print_vector_int(&ecol); \ + printf("\n"); \ + igraph_destroy(&res); \ + igraph_destroy(&graph); + +int main(void) { + igraph_t graph, res; + igraph_vector_int_t vcol, ecol; + + igraph_vector_int_init(&vcol, 0); + igraph_vector_int_init(&ecol, 0); + + /* null graph */ + igraph_empty(&graph, 0, 0); + SIMPLIFY_PRINT_DESTROY("K0"); + + /* singleton graph */ + igraph_empty(&graph, 1, 0); + SIMPLIFY_PRINT_DESTROY("K1"); + + /* 4-cycle-graph */ + igraph_ring(&graph, 4, 0, 0, 1); + SIMPLIFY_PRINT_DESTROY("C4"); + + /* both multi-edges and self loops */ + igraph_small(&graph, 2, 0, + 0, 1, 0, 1, 1, 1, -1); + SIMPLIFY_PRINT_DESTROY("Undirected graph 1"); + + /* parallel edges specified with different vertex orderings */ + igraph_small(&graph, 3, 0, + 0, 1, 1, 2, 2, 0, 2, 2, 2, 2, 2, 1, -1); + SIMPLIFY_PRINT_DESTROY("Undirected graph 2"); + + /* directed version of the same as above */ + igraph_small(&graph, 3, 1, + 0, 1, 1, 2, 2, 0, 2, 2, 2, 2, 2, 1, -1); + SIMPLIFY_PRINT_DESTROY("Directed graph 1"); + + /* isolated vertices */ + igraph_small(&graph, 4, 1, + 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, -1); + SIMPLIFY_PRINT_DESTROY("Directed graph 2"); + + igraph_vector_int_destroy(&vcol); + igraph_vector_int_destroy(&ecol); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/simplify_and_colorize.out b/tests/unit/simplify_and_colorize.out new file mode 100644 index 0000000..c6ce220 --- /dev/null +++ b/tests/unit/simplify_and_colorize.out @@ -0,0 +1,70 @@ +K0 +directed: false +vcount: 0 +edges: { +} +( ) +( ) + +K1 +directed: false +vcount: 1 +edges: { +} +( 0 ) +( ) + +C4 +directed: false +vcount: 4 +edges: { +1 0 +3 0 +2 1 +3 2 +} +( 0 0 0 0 ) +( 1 1 1 1 ) + +Undirected graph 1 +directed: false +vcount: 2 +edges: { +1 0 +} +( 0 1 ) +( 2 ) + +Undirected graph 2 +directed: false +vcount: 3 +edges: { +1 0 +2 0 +2 1 +} +( 0 0 2 ) +( 1 1 2 ) + +Directed graph 1 +directed: true +vcount: 3 +edges: { +0 1 +1 2 +2 0 +2 1 +} +( 0 0 2 ) +( 1 1 1 1 ) + +Directed graph 2 +directed: true +vcount: 4 +edges: { +0 1 +1 0 +} +( 0 2 0 0 ) +( 2 3 ) + diff --git a/tests/unit/single_target_shortest_path.c b/tests/unit/single_target_shortest_path.c new file mode 100644 index 0000000..fa94908 --- /dev/null +++ b/tests/unit/single_target_shortest_path.c @@ -0,0 +1,78 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_vector_int_t vpath, epath; + igraph_vector_t w; + + /* Unweighted */ + + igraph_small(&g, 5, IGRAPH_DIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 0, 3, + -1); + igraph_vector_int_init(&vpath, 0); + igraph_vector_int_init(&epath, 0); + igraph_get_shortest_path(&g, NULL, &vpath, &epath, 0, 4, IGRAPH_OUT); + igraph_vector_int_print(&vpath); + igraph_vector_int_print(&epath); + + igraph_get_shortest_path(&g, NULL, &vpath, &epath, 0, 0, IGRAPH_OUT); + igraph_vector_int_print(&vpath); + igraph_vector_int_print(&epath); + + igraph_set_warning_handler(igraph_warning_handler_ignore); + igraph_get_shortest_path(&g, NULL, &vpath, &epath, 4, 0, IGRAPH_OUT); + igraph_vector_int_print(&vpath); + igraph_vector_int_print(&epath); + igraph_set_warning_handler(igraph_warning_handler_print); + + igraph_get_shortest_path(&g, NULL, &vpath, &epath, 4, 0, IGRAPH_ALL); + igraph_vector_int_print(&vpath); + igraph_vector_int_print(&epath); + + /* Weighted */ + + igraph_vector_init(&w, 5); + VECTOR(w)[0] = 1; + VECTOR(w)[1] = 1; + VECTOR(w)[2] = 1; + VECTOR(w)[3] = 1; + VECTOR(w)[4] = 3.1; + + igraph_get_shortest_path_dijkstra(&g, &vpath, &epath, 0, 4, &w, IGRAPH_OUT); + igraph_vector_int_print(&vpath); + igraph_vector_int_print(&epath); + + igraph_vector_destroy(&w); + igraph_vector_int_destroy(&epath); + igraph_vector_int_destroy(&vpath); + igraph_destroy(&g); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/single_target_shortest_path.out b/tests/unit/single_target_shortest_path.out new file mode 100644 index 0000000..972ab28 --- /dev/null +++ b/tests/unit/single_target_shortest_path.out @@ -0,0 +1,10 @@ +0 3 4 +4 3 +0 + + + +4 3 0 +3 4 +0 1 2 3 4 +0 1 2 3 diff --git a/tests/unit/spinglass.c b/tests/unit/spinglass.c new file mode 100644 index 0000000..b368ab7 --- /dev/null +++ b/tests/unit/spinglass.c @@ -0,0 +1,344 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t g; + igraph_real_t modularity, temperature; + igraph_vector_int_t membership, csize; + igraph_real_t cohesion, adhesion; + igraph_real_t inner_links, outer_links; + + igraph_rng_seed(igraph_rng_default(), 137); + + /* Two 5-cliques connected by a single edge */ + igraph_small(&g, 10, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9, 0, 5, -1); + igraph_vector_int_init(&membership, 0); + igraph_vector_int_init(&csize, 0); + + printf("\nOriginal implementation.\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + 0, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_size(&csize) == igraph_vector_int_max(&membership) + 1); + + /* The following depend on the random seed, however, for this graph, + the result is almost always the same (i.e. two clusters). */ + printf("Modularity: %g\n", modularity); + print_vector_int(&membership); + + printf("\nOriginal implementation, parallel updating.\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + 1, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_size(&csize) == igraph_vector_int_max(&membership) + 1); + + /* The following depend on the random seed, however, for this graph, + the result is almost always the same (i.e. two clusters). */ + printf("Modularity: %g\n", modularity); + print_vector_int(&membership); + + printf("\nNegative implementation.\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + 0, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_NEG, + /*gamma_minus =*/ 0); + + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == igraph_vcount(&g)); + IGRAPH_ASSERT(igraph_vector_int_size(&csize) == igraph_vector_int_max(&membership) + 1); + + /* The following depend on the random seed, however, for this graph, + the result is almost always the same (i.e. two clusters). */ + printf("Modularity: %g\n", modularity); + print_vector_int(&membership); + + /* Try to call this as well, we don't check the results currently.... */ + + igraph_community_spinglass_single(&g, + /*weights= */ 0, + /*vertex= */ 0, + /*community=*/ &membership, + /*cohesion= */ &cohesion, + /*adhesion= */ &adhesion, + /*inner_links= */ &inner_links, + /*outer_links= */ &outer_links, + /*spins= */ 2, + /*update_rule= */ IGRAPH_SPINCOMM_UPDATE_CONFIG, + /*gamma= */ 1.0); + + igraph_destroy(&g); + + printf("\nTrivial case: null graph.\n"); + igraph_empty(&g, 0, IGRAPH_UNDIRECTED); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + 0, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + IGRAPH_ASSERT(igraph_vector_int_empty(&membership)); + IGRAPH_ASSERT(igraph_vector_int_empty(&csize)); + IGRAPH_ASSERT(temperature == 0.01); + IGRAPH_ASSERT(isnan(modularity)); + igraph_destroy(&g); + + printf("\nTrivial case: singleton graph.\n"); + igraph_empty(&g, 1, IGRAPH_UNDIRECTED); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + 0, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + IGRAPH_ASSERT(igraph_vector_int_size(&membership) == 1 && VECTOR(membership)[0] == 0); + IGRAPH_ASSERT(igraph_vector_int_size(&csize) == 1 && VECTOR(csize)[0] == 1); + IGRAPH_ASSERT(temperature == 0.01); + IGRAPH_ASSERT(isnan(modularity)); + igraph_destroy(&g); + + printf("\nTest on a random graph to verify consistency of output.\n"); + igraph_barabasi_game(&g, 20, 1, 3, NULL, true, 0, false, IGRAPH_BARABASI_PSUMTREE, NULL); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + false, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("Zero temp:\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + false, /* parallel update */ + 0.0, /* start temperature */ + 0.0, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("Parallel update:\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + true, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("Parallel update, zero temp:\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + true, /* parallel update */ + 0.0, /* start temperature */ + 0.0, /* stop temperature */ + 0.0, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("Negative implementation:\n"); + igraph_community_spinglass(&g, + NULL, /* no weights */ + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + false, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_NEG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("With weights:\n"); + igraph_vector_t weights; + igraph_vector_init_range(&weights, 1, igraph_ecount(&g)+1); + + igraph_community_spinglass(&g, + &weights, + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + false, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + printf("Parallel update:\n"); + igraph_community_spinglass(&g, + &weights, + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + true, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_ORIG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + const igraph_int_t half_ec = igraph_ecount(&g) / 2; + igraph_vector_range(&weights, -half_ec, igraph_ecount(&g) - half_ec); + printf("Negative implementation:\n"); + igraph_community_spinglass(&g, + &weights, + &modularity, + &temperature, + &membership, + &csize, + 10, /* no of spins */ + false, /* parallel update */ + 1.0, /* start temperature */ + 0.01, /* stop temperature */ + 0.99, /* cooling factor */ + IGRAPH_SPINCOMM_UPDATE_CONFIG, + 1.0, /* gamma */ + IGRAPH_SPINCOMM_IMP_NEG, + /*gamma_minus =*/ 0); + printf("Modularity: %g\nTemperature: %g\n", modularity, temperature); + print_vector_int(&membership); + + igraph_vector_destroy(&weights); + + igraph_destroy(&g); + + + igraph_vector_int_destroy(&membership); + igraph_vector_int_destroy(&csize); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/spinglass.out b/tests/unit/spinglass.out new file mode 100644 index 0000000..14e64d9 --- /dev/null +++ b/tests/unit/spinglass.out @@ -0,0 +1,49 @@ + +Original implementation. +Modularity: 0.452381 +( 1 1 1 1 1 0 0 0 0 0 ) + +Original implementation, parallel updating. +Modularity: 0.452381 +( 1 1 1 1 1 0 0 0 0 0 ) + +Negative implementation. +Modularity: 0.452381 +( 0 0 0 0 0 1 1 1 1 1 ) + +Trivial case: null graph. + +Trivial case: singleton graph. + +Test on a random graph to verify consistency of output. +Modularity: 0.209191 +Temperature: 0.0878155 +( 3 0 0 3 2 3 3 0 0 0 3 3 0 2 2 0 2 1 1 0 ) +Zero temp: +Modularity: 0.188443 +Temperature: 0 +( 2 3 2 2 0 1 1 2 2 0 1 1 1 0 0 3 0 3 3 3 ) +Parallel update: +Modularity: 0 +Temperature: 0.470429 +( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) +Parallel update, zero temp: +Modularity: -0.13323 +Temperature: 0 +( 1 2 2 1 2 2 2 1 1 1 0 2 2 2 2 1 2 2 1 1 ) +Negative implementation: +Modularity: 0.206619 +Temperature: 0.0570013 +( 0 1 1 0 2 2 2 1 1 1 2 2 1 0 2 1 2 3 3 1 ) +With weights: +Modularity: 0.0164729 +Temperature: 1.17406 +( 1 3 2 2 0 0 0 1 3 2 0 0 3 2 0 2 0 3 3 3 ) +Parallel update: +Modularity: 0.00242651 +Temperature: 1.1979 +( 4 2 2 0 0 3 0 1 1 1 4 3 1 3 0 1 3 3 4 1 ) +Negative implementation: +Modularity: 0.247814 +Temperature: 0.00992249 +( 0 1 2 3 4 1 4 4 5 6 5 0 5 2 4 2 4 1 1 1 ) diff --git a/tests/unit/stack.c b/tests/unit/stack.c new file mode 100644 index 0000000..c87ac27 --- /dev/null +++ b/tests/unit/stack.c @@ -0,0 +1,93 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_stack_t st; + int i; + + /* igraph_stack_init, igraph_stack_destroy */ + igraph_stack_init(&st, 0); + igraph_stack_destroy(&st); + igraph_stack_init(&st, 10); + igraph_stack_destroy(&st); + + /* igraph_stack_reserve */ + igraph_stack_init(&st, 0); + igraph_stack_reserve(&st, 10); + igraph_stack_reserve(&st, 5); + + /* igraph_stack_empty */ + if (!igraph_stack_empty(&st)) { + return 1; + } + igraph_stack_push(&st, 1); + if (igraph_stack_empty(&st)) { + return 2; + } + + /* igraph_stack_size */ + if (igraph_stack_size(&st) != 1) { + return 3; + } + for (i = 0; i < 10; i++) { + igraph_stack_push(&st, i); + } + if (igraph_stack_size(&st) != 11) { + return 4; + } + + /* igraph_stack_clear */ + igraph_stack_clear(&st); + if (!igraph_stack_empty(&st)) { + return 5; + } + igraph_stack_push(&st, 100); + if (igraph_stack_pop(&st) != 100) { + return 6; + } + igraph_stack_clear(&st); + igraph_stack_clear(&st); + + /* igraph_stack_push, igraph_stack_pop */ + for (i = 0; i < 100; i++) { + igraph_stack_push(&st, 100 - i); + } + for (i = 0; i < 100; i++) { + if (igraph_stack_pop(&st) != i + 1) { + return 7; + } + } + if (!igraph_stack_empty(&st)) { + return 8; + } + + igraph_stack_destroy(&st); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/strvector_set_len_remove_print.c b/tests/unit/strvector_set_len_remove_print.c new file mode 100644 index 0000000..2485d9e --- /dev/null +++ b/tests/unit/strvector_set_len_remove_print.c @@ -0,0 +1,53 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_strvector_t sv; + char *test_string = "This is a string."; + char *test_string2 = "A completely different one."; + + printf("Two strings in a vector.\n"); + igraph_strvector_init(&sv, 5); + IGRAPH_ASSERT(igraph_strvector_set_len(&sv, 0, test_string, strlen(test_string)) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_strvector_set_len(&sv, 4, test_string2, strlen(test_string2)) == IGRAPH_SUCCESS); + igraph_strvector_print(&sv, " | "); + + printf("\nRemove a nonexistent one.\n"); + igraph_strvector_remove(&sv, 1); + igraph_strvector_print(&sv, " | "); + + printf("\nRemove one.\n"); + igraph_strvector_remove(&sv, 0); + igraph_strvector_print(&sv, " | "); + igraph_strvector_destroy(&sv); + + printf("\nOverwriting a string.\n"); + igraph_strvector_init(&sv, 5); + IGRAPH_ASSERT(igraph_strvector_set_len(&sv, 2, test_string2, strlen(test_string2)) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_strvector_set_len(&sv, 2, test_string, strlen(test_string)) == IGRAPH_SUCCESS); + igraph_strvector_print(&sv, " | "); + igraph_strvector_destroy(&sv); + + printf("\n"); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/strvector_set_len_remove_print.out b/tests/unit/strvector_set_len_remove_print.out new file mode 100644 index 0000000..e08244f --- /dev/null +++ b/tests/unit/strvector_set_len_remove_print.out @@ -0,0 +1,8 @@ +Two strings in a vector. +This is a string. | | | | A completely different one. +Remove a nonexistent one. +This is a string. | | | A completely different one. +Remove one. + | | A completely different one. +Overwriting a string. + | | This is a string. | | diff --git a/tests/unit/symmetric_tree.c b/tests/unit/symmetric_tree.c new file mode 100644 index 0000000..4f494d8 --- /dev/null +++ b/tests/unit/symmetric_tree.c @@ -0,0 +1,111 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +#define PRINT_DESTROY(name) \ + printf(name "\n"); \ + print_graph_canon(&g); \ + igraph_destroy(&g); \ + igraph_vector_int_destroy(&v); \ + printf("\n"); + +int main(void) { + + igraph_t g; + igraph_vector_int_t v; + + /**** Test with empty vector ****/ + // initialize variables for test + igraph_vector_int_init_int(&v, 0); + // test + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + // root vertex always gets created, cannot create null graph with this function + PRINT_DESTROY("Singleton graph"); + + igraph_vector_int_init_int(&v, 0); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_OUT) == IGRAPH_SUCCESS); + PRINT_DESTROY("Directed singleton graph"); + + /**** Test with -1 value ****/ + // invalid number of children + igraph_vector_int_init_int(&v, 1, -1); + CHECK_ERROR(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED), IGRAPH_EINVAL); + igraph_destroy(&g); + igraph_vector_int_destroy(&v); + + /**** Test undirected symmetric graph with 1 child ****/ + // 1 edge, 2 vertices (root and child) + igraph_vector_int_init_int(&v, 1, 1); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Undirected graph with 2 vertices and 1 edge"); + + /**** Test directed symmetric graph with 1 child ****/ + // 1 edge, 2 vertices (root and child) + igraph_vector_int_init_int(&v, 1, 1); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_IN) == IGRAPH_SUCCESS); + PRINT_DESTROY("Directed graph with 2 vertices and 1 edge"); + + /**** Test directed symmetric path graph with 1 child in each level ****/ + igraph_vector_int_init_int(&v, 3, 1, 1, 1); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_OUT) == IGRAPH_SUCCESS); + PRINT_DESTROY("Directed path graph with 4 level and 1 child in each"); + + /**** Test undirected symmetric path graph with 1 child in each level ****/ + igraph_vector_int_init_int(&v, 3, 1, 1, 1); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Undirected path graph with 4 level and 1 child in each"); + + /**** Test directed symmetric graph as binary tree with 1 level ****/ + igraph_vector_int_init_int(&v, 1, 2); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_OUT) == IGRAPH_SUCCESS); + PRINT_DESTROY("Binary out-tree with 3 vertices"); + + /**** Test undirected symmetric graph as binary tree with 1 level ****/ + igraph_vector_int_init_int(&v, 1, 2); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Undirected binery tree with 3 vertices"); + + + /**** Test directed symmetric graph with 2 level, each 3 children ****/ + igraph_vector_int_init_int(&v, 2, 3, 3); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_OUT) == IGRAPH_SUCCESS); + PRINT_DESTROY("Symmetric out-tree with 2 level, 3 children in each"); + + /**** Test undirected symmetric graph with 2 level, each 3 children ****/ + igraph_vector_int_init_int(&v, 2, 3, 3); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Symmetric undirected tree with 2 level, 3 children in each"); + + /**** Test directed symmetric graph with 2 level, first with 3 and second with 4 children ****/ + igraph_vector_int_init_int(&v, 2, 3, 4); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_OUT) == IGRAPH_SUCCESS); + PRINT_DESTROY("Symmetric out-tree with 2 level, 3 children in first, 4 in second"); + + /**** Test undirected symmetric graph with 2 level, first with 4 and second with 3 children ****/ + igraph_vector_int_init_int(&v, 2, 4, 3); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Undirected symmetric tree with 2 level, 4 children in first, 3 in second"); + + /**** Test undirected symmetric graph with 3 level, first with 3, second with 4, third with 5 children ****/ + igraph_vector_int_init_int(&v, 3, 3, 4, 5); + IGRAPH_ASSERT(igraph_symmetric_tree(&g, &v, IGRAPH_TREE_UNDIRECTED) == IGRAPH_SUCCESS); + PRINT_DESTROY("Undirected symmetric tree with 3 level, 3 children in first, 4 in second, 5 in third"); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/symmetric_tree.out b/tests/unit/symmetric_tree.out new file mode 100644 index 0000000..4c7fb88 --- /dev/null +++ b/tests/unit/symmetric_tree.out @@ -0,0 +1,220 @@ +Singleton graph +directed: false +vcount: 1 +edges: { +} + +Directed singleton graph +directed: true +vcount: 1 +edges: { +} + +Undirected graph with 2 vertices and 1 edge +directed: false +vcount: 2 +edges: { +0 1 +} + +Directed graph with 2 vertices and 1 edge +directed: true +vcount: 2 +edges: { +1 0 +} + +Directed path graph with 4 level and 1 child in each +directed: true +vcount: 4 +edges: { +0 1 +1 2 +2 3 +} + +Undirected path graph with 4 level and 1 child in each +directed: false +vcount: 4 +edges: { +0 1 +1 2 +2 3 +} + +Binary out-tree with 3 vertices +directed: true +vcount: 3 +edges: { +0 1 +0 2 +} + +Undirected binery tree with 3 vertices +directed: false +vcount: 3 +edges: { +0 1 +0 2 +} + +Symmetric out-tree with 2 level, 3 children in each +directed: true +vcount: 13 +edges: { +0 1 +0 2 +0 3 +1 4 +1 5 +1 6 +2 7 +2 8 +2 9 +3 10 +3 11 +3 12 +} + +Symmetric undirected tree with 2 level, 3 children in each +directed: false +vcount: 13 +edges: { +0 1 +0 2 +0 3 +1 4 +1 5 +1 6 +2 7 +2 8 +2 9 +3 10 +3 11 +3 12 +} + +Symmetric out-tree with 2 level, 3 children in first, 4 in second +directed: true +vcount: 16 +edges: { +0 1 +0 2 +0 3 +1 4 +1 5 +1 6 +1 7 +2 8 +2 9 +2 10 +2 11 +3 12 +3 13 +3 14 +3 15 +} + +Undirected symmetric tree with 2 level, 4 children in first, 3 in second +directed: false +vcount: 17 +edges: { +0 1 +0 2 +0 3 +0 4 +1 5 +1 6 +1 7 +2 8 +2 9 +2 10 +3 11 +3 12 +3 13 +4 14 +4 15 +4 16 +} + +Undirected symmetric tree with 3 level, 3 children in first, 4 in second, 5 in third +directed: false +vcount: 76 +edges: { +0 1 +0 2 +0 3 +1 4 +1 5 +1 6 +1 7 +2 8 +2 9 +2 10 +2 11 +3 12 +3 13 +3 14 +3 15 +4 16 +4 17 +4 18 +4 19 +4 20 +5 21 +5 22 +5 23 +5 24 +5 25 +6 26 +6 27 +6 28 +6 29 +6 30 +7 31 +7 32 +7 33 +7 34 +7 35 +8 36 +8 37 +8 38 +8 39 +8 40 +9 41 +9 42 +9 43 +9 44 +9 45 +10 46 +10 47 +10 48 +10 49 +10 50 +11 51 +11 52 +11 53 +11 54 +11 55 +12 56 +12 57 +12 58 +12 59 +12 60 +13 61 +13 62 +13 63 +13 64 +13 65 +14 66 +14 67 +14 68 +14 69 +14 70 +15 71 +15 72 +15 73 +15 74 +15 75 +} + diff --git a/tests/unit/test.graphml b/tests/unit/test.graphml new file mode 100644 index 0000000..a6ab576 --- /dev/null +++ b/tests/unit/test.graphml @@ -0,0 +1,55 @@ + + + + + yellow + + + + + + 1 + + + 2006-11-12 + + + + green + incorrect + true + + + + blue + 0 + + + red "with entities" + + + + false + + + turquoise + fAlSe + + + 1.0 + + + 1.0 + + + 2.0 + + + + + + 1.1 + + + diff --git a/tests/unit/test_utilities.c b/tests/unit/test_utilities.c new file mode 100644 index 0000000..97974df --- /dev/null +++ b/tests/unit/test_utilities.c @@ -0,0 +1,646 @@ + +/* + * This file contains functions that are useful when writing tests. + * Add #include "test_utilities.h" to the test program to use them. + */ + +#include "test_utilities.h" +#include +#include + +/* Print an igraph_real_t value. Be consistent in printing NaN/Inf across platforms. */ +void print_real(FILE *f, igraph_real_t x, const char *format) { + igraph_bool_t g8 = !strcmp(format, "%8g"); + if (isfinite(x)) { + if (x == 0 && signbit(x)) { + /* print negative zeros as positive zeros for sake of consistency */ + x = +0.0; + } + fprintf(f, format, x); + } else if (isnan(x)) { + fprintf(f, g8 ? " NaN" : "NaN"); + } else if (isinf(x) && x > 0) { + fprintf(f, g8 ? " Inf" : "Inf"); + } else if (isinf(x) && x < 0) { + fprintf(f, g8 ? " -Inf" : "-Inf"); + } +} + +void print_vector_format(const igraph_vector_t *v, FILE *f, const char *format) { + igraph_int_t i, n = igraph_vector_size(v); + fprintf(f, "("); + for (i=0; i < n; i++) { + fprintf(f, " "); + print_real(f, VECTOR(*v)[i], format); + } + fprintf(f, " )\n"); +} + +/* Print elements of a vector. Use parentheses to make it clear when a vector has size zero. */ +void print_vector(const igraph_vector_t *v) { + print_vector_format(v, stdout, "%g"); +} + +/* Round elements of a vector to integers and print them. */ +/* This is meant to be used when the elements of a vector are integer values. */ +void print_vector_round(const igraph_vector_t *v) { + print_vector_format(v, stdout, "%.f"); +} + + +/* Print elements of an integer vector */ +void print_vector_int(const igraph_vector_int_t *v) { + igraph_int_t i, n = igraph_vector_int_size(v); + printf("("); + for (i=0; i < n; i++) { + printf(" %" IGRAPH_PRId, VECTOR(*v)[i]); + } + printf(" )\n"); +} + +/* Print all vectors in an integer vector list. Use brackets around each vector + * and also use brackets around the entire list to make it clear when the list + * is empty */ +void print_vector_int_list(const igraph_vector_int_list_t *v) { + igraph_int_t i, n = igraph_vector_int_list_size(v); + printf("{\n"); + for (i = 0; i < n; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_vector_int(igraph_vector_int_list_get_ptr(v, i)); + } + printf("}\n"); +} + +void print_matrix_format(const igraph_matrix_t *m, FILE *f, const char *format) { + print_matrix_format_indent(m, f, format, ""); +} + +/* Print elements of a matrix. Use brackets to make it clear when a vector has size zero. */ +void print_matrix_format_indent(const igraph_matrix_t *m, FILE *f, const char *format, const char *indent) { + igraph_int_t i, j, nrow = igraph_matrix_nrow(m), ncol = igraph_matrix_ncol(m); + if (nrow == 0 || ncol == 0) { + /* When the matrix is empty, output the dimensions */ + fprintf(f, "%s[ %" IGRAPH_PRId "-by-%" IGRAPH_PRId" ]\n", indent, nrow, ncol); + return; + } + for (i = 0; i < nrow; i++) { + fprintf(f, i == 0 ? "%s[" : "%s ", indent); + for (j = 0; j < ncol; j++) { + fprintf(f, " "); + print_real(f, MATRIX(*m, i, j), format); + } + fprintf(f, i == nrow-1 ? " ]\n" : "\n"); + } +} + +void print_matrix(const igraph_matrix_t *m) { + print_matrix_indent(m, ""); +} + +void print_matrix_indent(const igraph_matrix_t *m, const char *indent) { + print_matrix_format_indent(m, stdout, "%8g", indent); +} + +void print_matrix_int(const igraph_matrix_int_t *m) { + igraph_int_t i, j, nrow = igraph_matrix_int_nrow(m), ncol = igraph_matrix_int_ncol(m); + if (nrow == 0 || ncol == 0) { + /* When the matrix is empty, output the dimensions */ + printf("[ %" IGRAPH_PRId "-by-%" IGRAPH_PRId" ]\n", nrow, ncol); + return; + } + for (i = 0; i < nrow; i++) { + printf(i == 0 ? "[" : " "); + for (j = 0; j < ncol; j++) { + printf(" "); + printf("%8" IGRAPH_PRId, MATRIX(*m, i, j)); + } + printf(i == nrow-1 ? " ]\n" : "\n"); + } +} + +/* Round elements of a matrix to integers and print them. */ +/* This is meant to be used when the elements of a matrix are integer values. */ +void print_matrix_round(const igraph_matrix_t *m) { + print_matrix_format(m, stdout, "%4.f"); +} + +void print_matrix_complex_round(const igraph_matrix_complex_t *m) { + + igraph_int_t nr = igraph_matrix_complex_nrow(m); + igraph_int_t nc = igraph_matrix_complex_ncol(m); + igraph_int_t i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + igraph_complex_t z = MATRIX(*m, i, j); + if (j != 0) { + putchar(' '); + } + printf("%.f%+.fi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + printf("\n"); + } +} + +void print_matrix_list(const igraph_matrix_list_t *m) { + igraph_int_t i, n = igraph_matrix_list_size(m); + printf("{\n"); + for (i = 0; i < n; ++i) { + igraph_matrix_t *mat = igraph_matrix_list_get_ptr(m, i); + if (igraph_matrix_nrow(mat) < 2) { + printf(" %2" IGRAPH_PRId ": ", i); + print_matrix(mat); + } else { + printf(" %2" IGRAPH_PRId ":\n", i); + print_matrix_indent(mat, " "); + } + } + printf("}\n"); +} + +/* Print an adjacency list. Use brackets around each vector and also use + * brackets around the entire adjacency list to make it clear when the list + * is empty. + */ +void print_adjlist(const igraph_adjlist_t *adjlist) { + igraph_int_t vcount = igraph_adjlist_size(adjlist); + igraph_int_t i; + + printf("{\n"); + for (i = 0; i < vcount; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_vector_int(igraph_adjlist_get(adjlist, i)); + } + printf("}\n"); +} + +/* Print a graph. Use brackets to make it obvious when the edge list is empty. */ +void print_graph(const igraph_t *graph) { + print_weighted_graph(graph, NULL); +} + +/* Print a graph with edge weights from a vector. Use brackets to make it + * obvious when the edge list is empty. */ +void print_weighted_graph(const igraph_t *graph, const igraph_vector_t* weights) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t i; + + printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false"); + printf("vcount: %" IGRAPH_PRId "\n", vcount); + printf("edges: {\n"); + for (i=0; i < ecount; ++i) { + printf( + "%" IGRAPH_PRId " %" IGRAPH_PRId, + IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i) + ); + if (weights) { + printf(": "); + print_real(stdout, VECTOR(*weights)[i], "%g"); + } + printf("\n"); + } + printf("}\n"); +} + +/* Print a graph with edge weights from an edge attribute. Use brackets to make + * it obvious when the edge list is empty. */ +void print_weighted_graph_attr(const igraph_t *graph, const char* attr) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t vcount = igraph_vcount(graph); + igraph_int_t i; + + printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false"); + printf("vcount: %" IGRAPH_PRId "\n", vcount); + printf("edges: {\n"); + for (i=0; i < ecount; ++i) + printf + ("%" IGRAPH_PRId " %" IGRAPH_PRId ": %g\n", + IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i), + EAN(graph, attr, i) + ); + printf("}\n"); +} + +/* Print an incidence list. Use brackets around each vector and also use + * brackets around the entire incidence list to make it clear when the list + * is empty. + */ +void print_inclist(const igraph_inclist_t *inclist) { + igraph_int_t vcount = igraph_inclist_size(inclist); + igraph_int_t i; + + printf("{\n"); + for (i = 0; i < vcount; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_vector_int(igraph_inclist_get(inclist, i)); + } + printf("}\n"); +} + +/* Print a lazy adjacency list. Use brackets around each vector and also use + * brackets around the entire lazy adjacency list to make it clear when the list + * is empty. + */ +void print_lazy_adjlist(igraph_lazy_adjlist_t *adjlist) { + igraph_int_t vcount = igraph_lazy_adjlist_size(adjlist); + igraph_int_t i; + + printf("{\n"); + for (i = 0; i < vcount; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_vector_int(igraph_lazy_adjlist_get(adjlist, i)); + } + printf("}\n"); +} + +/* Print a lazy incidence list. Use brackets around each vector and also use + * brackets around the entire incidence list to make it clear when the list + * is empty. + */ +void print_lazy_inclist(igraph_lazy_inclist_t *inclist) { + igraph_int_t vcount = igraph_lazy_inclist_size(inclist); + igraph_int_t i; + + printf("{\n"); + for (i = 0; i < vcount; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_vector_int(igraph_lazy_inclist_get(inclist, i)); + } + printf("}\n"); +} + +/* Edge comparison function used for sorting in print_graph_canon(). */ +int edge_compare(void *pedges, const void *pi1, const void *pi2) { + const igraph_int_t i1 = * (const igraph_int_t *) pi1; + const igraph_int_t i2 = * (const igraph_int_t *) pi2; + const igraph_vector_int_t *edges = (const igraph_vector_int_t *) pedges; + if (VECTOR(*edges)[2*i1] < VECTOR(*edges)[2*i2]) { + return -1; + } else if (VECTOR(*edges)[2*i1] > VECTOR(*edges)[2*i2]) { + return 1; + } else if (VECTOR(*edges)[2*i1+1] < VECTOR(*edges)[2*i2+1]) { + return -1; + } else if (VECTOR(*edges)[2*i1+1] > VECTOR(*edges)[2*i2+1]) { + return 1; + } else { + return 0; + } +} + +/* Print a weighted graph using a sorted edge list. Other than sorting (i.e. canonicalizing) + * the edge list, this function is identical to print_graph(). */ +void print_weighted_graph_canon(const igraph_t *graph, const igraph_vector_t *weights) { + igraph_int_t ecount = igraph_ecount(graph); + igraph_int_t vcount = igraph_vcount(graph); + igraph_vector_int_t edges, idx; + + printf("directed: %s\n", igraph_is_directed(graph) ? "true" : "false"); + printf("vcount: %" IGRAPH_PRId "\n", vcount); + printf("edges: {\n"); + + igraph_vector_int_init(&edges, 0); + igraph_get_edgelist(graph, &edges, false); + + /* If the graph is undirected, we make sure that the first vertex of undirected edges + * is always the one with the lower ID. */ + if (! igraph_is_directed(graph)) { + for (igraph_int_t i=0; i < ecount; i++) { + if (VECTOR(edges)[2*i] > VECTOR(edges)[2*i+1]) { + igraph_int_t tmp = VECTOR(edges)[2*i]; + VECTOR(edges)[2*i] = VECTOR(edges)[2*i+1]; + VECTOR(edges)[2*i+1] = tmp; + } + } + } + + igraph_vector_int_init_range(&idx, 0, igraph_ecount(graph)); + + /* Sort the edge list */ + igraph_qsort_r(&VECTOR(idx)[0], ecount, sizeof(igraph_int_t), &edges, &edge_compare); + + for (igraph_int_t i=0; i < ecount; i++) { + const igraph_int_t k = VECTOR(idx)[i]; + printf("%" IGRAPH_PRId " %" IGRAPH_PRId, VECTOR(edges)[2*k], VECTOR(edges)[2*k+1]); + if (weights) { + printf(": "); + print_real(stdout, VECTOR(*weights)[k], "%g"); + } + printf("\n"); + } + + printf("}\n"); + + igraph_vector_int_destroy(&idx); + igraph_vector_int_destroy(&edges); +} + +/* Print a graph using a sorted edge list. Other than sorting (i.e. canonicalizing) + * the edge list, this function is identical to print_graph(). */ +void print_graph_canon(const igraph_t *graph) { + print_weighted_graph_canon(graph, NULL); +} + +/* Print a vector, ensuring that the first nonzero element is positive. */ +void print_vector_first_nonzero_element_positive(const igraph_vector_t *vector, const char* format) { + igraph_vector_t copy; + igraph_int_t i, n; + + igraph_vector_init_copy(©, vector); + + n = igraph_vector_size(©); + + for (i = 0; i < n; i++) { + if (VECTOR(copy)[i] < 0) { + for (; i < n; i++) { + if (VECTOR(copy)[i] != 0) { + VECTOR(copy)[i] *= -1; + } + } + break; + } else if (VECTOR(copy)[i] > 0) { + break; + } + } + + igraph_vector_printf(©, format); + igraph_vector_destroy(©); +} + +/* Print a complex vector, ensuring that the first element with nonzero real + * part has a positive real part. */ +void print_vector_complex_first_nonzero_real_part_positive(const igraph_vector_complex_t *vector) { + igraph_vector_complex_t copy; + igraph_int_t i, n; + + igraph_vector_complex_init_copy(©, vector); + + n = igraph_vector_complex_size(©); + + for (i = 0; i < n; i++) { + if (IGRAPH_REAL(VECTOR(copy)[i]) < 0) { + for (; i < n; i++) { + if (IGRAPH_REAL(VECTOR(copy)[i]) != 0) { + IGRAPH_REAL(VECTOR(copy)[i]) *= -1; + } + if (IGRAPH_IMAG(VECTOR(copy)[i]) != 0) { + IGRAPH_IMAG(VECTOR(copy)[i]) *= -1; + } + } + break; + } else if (IGRAPH_REAL(VECTOR(copy)[i]) > 0) { + break; + } + } + + igraph_vector_complex_print(©); + igraph_vector_complex_destroy(©); +} + +/* Print a matrix, ensuring that the first nonzero element in each column is + * positive. */ +void print_matrix_first_row_positive(const igraph_matrix_t *matrix, const char* format) { + igraph_matrix_t copy; + igraph_int_t i, j, nrow, ncol; + + igraph_matrix_init_copy(©, matrix); + + nrow = igraph_matrix_nrow(©); + ncol = igraph_matrix_ncol(©); + + for (i = 0; i < ncol; i++) { + for (j = 0; j < nrow; j++) { + if (MATRIX(copy, j, i) < 0) { + for (; j < nrow; j++) { + if (MATRIX(copy, j, i) != 0) { + MATRIX(copy, j, i) *= -1; + } + } + break; + } else if (MATRIX(copy, j, i) > 0) { + break; + } + } + } + + igraph_matrix_printf(©, format); + igraph_matrix_destroy(©); +} + +/* Print a complex matrix, ensuring that the first element with nonzero real + * part in each column has a positive real part. */ +void print_matrix_complex_first_row_positive(const igraph_matrix_complex_t *matrix) { + igraph_matrix_complex_t copy; + igraph_int_t i, j, nrow, ncol; + igraph_complex_t z; + char buf[256]; + size_t len; + + igraph_matrix_complex_init_copy(©, matrix); + + nrow = igraph_matrix_complex_nrow(©); + ncol = igraph_matrix_complex_ncol(©); + + for (i = 0; i < ncol; i++) { + for (j = 0; j < nrow; j++) { + if (IGRAPH_REAL(MATRIX(copy, j, i)) < 0) { + for (; j < nrow; j++) { + if (IGRAPH_REAL(MATRIX(copy, j, i)) != 0) { + IGRAPH_REAL(MATRIX(copy, j, i)) *= -1; + } + if (IGRAPH_IMAG(MATRIX(copy, j, i)) != 0) { + IGRAPH_IMAG(MATRIX(copy, j, i)) *= -1; + } + } + break; + } else if (IGRAPH_REAL(MATRIX(copy, j, i)) > 0) { + break; + } + } + } + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + z = MATRIX(copy, i, j); + if (j != 0) { + putchar(' '); + } + + snprintf(buf, sizeof(buf), "%g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + len = strlen(buf); + + /* ensure that we don't print -0 in the imaginary part */ + if (len > 3 && buf[len-3] == '-' && buf[len-2] == '0' && buf[len-1] == 'i') { + buf[len-3] = '+'; + } + + /* ensure that we don't print -0 in the real part either */ + if (buf[0] == '-' && buf[1] == '0' && (buf[2] == '+' || buf[2] == '-')) { + printf("%s", buf + 1); + } else { + printf("%s", buf); + } + } + printf("\n"); + } + + igraph_matrix_complex_destroy(©); +} + +void matrix_init_int_row_major(igraph_matrix_t *mat, igraph_int_t nrow, igraph_int_t ncol, const int *elem) { + igraph_int_t c, r; + size_t i_elem = 0; + igraph_matrix_init(mat, nrow, ncol); + for (r = 0; r < nrow; r++) { + for (c = 0; c < ncol; c++) { + MATRIX(*mat, r, c) = elem[i_elem]; + i_elem++; + } + } +} + +void matrix_int_init_int_row_major(igraph_matrix_int_t *mat, igraph_int_t nrow, igraph_int_t ncol, const int *elem) { + igraph_int_t c, r; + size_t i_elem = 0; + igraph_matrix_int_init(mat, nrow, ncol); + for (r = 0; r < nrow; r++) { + for (c = 0; c < ncol; c++) { + MATRIX(*mat, r, c) = elem[i_elem]; + i_elem++; + } + } +} + +void matrix_init_real_row_major(igraph_matrix_t *mat, igraph_int_t nrow, igraph_int_t ncol, const igraph_real_t *elem) { + igraph_int_t c, r; + size_t i_elem = 0; + igraph_matrix_init(mat, nrow, ncol); + for (r = 0; r < nrow; r++) { + for (c = 0; c < ncol; c++) { + MATRIX(*mat, r, c) = elem[i_elem]; + i_elem++; + } + } +} + +void matrix_chop(igraph_matrix_t *mat, igraph_real_t cutoff) { + igraph_int_t nelems = igraph_matrix_nrow(mat) * igraph_matrix_ncol(mat); + for (igraph_int_t i = 0; i < nelems; i++) { + if (fabs(VECTOR(mat->data)[i]) < cutoff) { + VECTOR(mat->data)[i] = 0; + } + } +} + +void vector_chop(igraph_vector_t *vec, igraph_real_t cutoff) { + igraph_int_t nelems = igraph_vector_size(vec); + for (igraph_int_t i = 0; i < nelems; i++) { + if (fabs(VECTOR(*vec)[i]) < cutoff) { + VECTOR(*vec)[i] = 0; + } + } +} + +/* print all graph, edge and vertex attributes of a graph */ +void print_attributes(const igraph_t *g) { + igraph_vector_int_t gtypes, vtypes, etypes; + igraph_strvector_t gnames, vnames, enames; + igraph_int_t i; + + igraph_int_t j; + + igraph_vector_int_init(>ypes, 0); + igraph_vector_int_init(&vtypes, 0); + igraph_vector_int_init(&etypes, 0); + igraph_strvector_init(&gnames, 0); + igraph_strvector_init(&vnames, 0); + igraph_strvector_init(&enames, 0); + + igraph_cattribute_list(g, &gnames, >ypes, &vnames, &vtypes, + &enames, &etypes); + + /* Graph attributes */ + for (i = 0; i < igraph_strvector_size(&gnames); i++) { + if (i != 0) + putchar(' '); + printf("%s=", igraph_strvector_get(&gnames, i)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(GAN(g, igraph_strvector_get(&gnames, i))); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + printf("%d", GAB(g, igraph_strvector_get(&gnames, i))); + } else { + printf("\"%s\"", GAS(g, igraph_strvector_get(&gnames, i))); + } + } + if (igraph_strvector_size(&gnames)) + printf("\n"); + + for (i = 0; i < igraph_vcount(g); i++) { + printf("Vertex %" IGRAPH_PRId ":", i); + for (j = 0; j < igraph_strvector_size(&vnames); j++) { + putchar(' '); + printf("%s=", igraph_strvector_get(&vnames, j)); + if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(VAN(g, igraph_strvector_get(&vnames, j), i)); + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + printf("%d", VAB(g, igraph_strvector_get(&vnames, j), i)); + } else { + printf("\"%s\"", VAS(g, igraph_strvector_get(&vnames, j), i)); + } + } + printf("\n"); + } + + for (i = 0; i < igraph_ecount(g); i++) { + printf("Edge %" IGRAPH_PRId " (%" IGRAPH_PRId "-%" IGRAPH_PRId "):", i, IGRAPH_FROM(g, i), IGRAPH_TO(g, i)); + for (j = 0; j < igraph_strvector_size(&enames); j++) { + putchar(' '); + printf("%s=", igraph_strvector_get(&enames, j)); + if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_real_printf(EAN(g, igraph_strvector_get(&enames, j), i)); + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + printf("%d", EAB(g, igraph_strvector_get(&enames, j), i)); + } else { + printf("\"%s\"", EAS(g, igraph_strvector_get(&enames, j), i)); + } + } + printf("\n"); + } + printf("\n"); + + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + igraph_vector_int_destroy(&etypes); + igraph_vector_int_destroy(&vtypes); + igraph_vector_int_destroy(>ypes); +} + +expect_warning_context_t expect_warning_ctx; + +void record_last_warning(const char *reason, const char *file, int line) { + IGRAPH_UNUSED(file); IGRAPH_UNUSED(line); + + if (expect_warning_ctx.observed) { + igraph_free(expect_warning_ctx.observed); + } + + expect_warning_ctx.observed = strdup(reason); +} + +void print_bitset(const igraph_bitset_t* bitset) { + printf("("); + for (igraph_int_t i = bitset->size - 1; i >= 0; --i) { + printf(" %d", !!IGRAPH_BIT_TEST(*bitset, i)); + } + printf(" )\n"); +} + +void print_bitset_list(const igraph_bitset_list_t *v) { + igraph_int_t i, n = igraph_bitset_list_size(v); + printf("{\n"); + for (i = 0; i < n; ++i) { + printf(" %" IGRAPH_PRId ": ", i); + print_bitset(igraph_bitset_list_get_ptr(v, i)); + } + printf("}\n"); +} diff --git a/tests/unit/test_utilities.h b/tests/unit/test_utilities.h new file mode 100644 index 0000000..9a2b46d --- /dev/null +++ b/tests/unit/test_utilities.h @@ -0,0 +1,184 @@ +#ifndef TEST_UTILITIES_H +#define TEST_UTILITIES_H + +/* + * This file declares functions that are useful when writing tests. + */ + +#include +#include +#include + +/* Structure used by the EXPECT_WARNING macro to test whether a warning was + * triggered in a section of the code in unit tests */ +typedef struct { + const char* expected; + char* observed; +} expect_warning_context_t; + +extern expect_warning_context_t expect_warning_ctx; + +/* Print an igraph_real_t value. Be consistent in printing NaN/Inf across platforms. */ +void print_real(FILE *f, igraph_real_t x, const char *format); + +void print_vector_format(const igraph_vector_t *v, FILE *f, const char *format); + +/* Print elements of a vector. Use parentheses to make it clear when a vector has size zero. */ +void print_vector(const igraph_vector_t *v); + +/* Round elements of a vector to integers and print them. */ +/* This is meant to be used when the elements of a vector are integer values. */ +void print_vector_round(const igraph_vector_t *v); + +/* Print elements of an integer vector */ +void print_vector_int(const igraph_vector_int_t *v); + +/* Print all vectors in an integer vector list. Use brackets around each vector + * and also use brackets around the entire list to make it clear when the list + * is empty */ +void print_vector_int_list(const igraph_vector_int_list_t *v); + +/* Print elements of a matrix. Use brackets to make it clear when a vector has size zero. */ +void print_matrix_format(const igraph_matrix_t *m, FILE *f, const char *format); + +/* Print elements of a matrix, with indentation. Use brackets to make it clear when a vector has size zero. */ +void print_matrix_format_indent(const igraph_matrix_t *m, FILE *f, const char *format, const char *indent); + +void print_matrix(const igraph_matrix_t *m); +void print_matrix_indent(const igraph_matrix_t *m, const char *indent); + +void print_matrix_int(const igraph_matrix_int_t *m); + +/* Round elements of a matrix to integers and print them. */ +/* This is meant to be used when the elements of a matrix are integer values. */ +void print_matrix_round(const igraph_matrix_t *m); + +/* Round elements of a complex matrix to integers and print them. */ +/* This is meant to be used when the elements of a matrix are integer values. */ +void print_matrix_complex_round(const igraph_matrix_complex_t *m); + +/* Print all matrices in a matrix list. Use brackets around each matrix + * and also use brackets around the entire list to make it clear when the list + * is empty */ +void print_matrix_list(const igraph_matrix_list_t *m); + +/* Print an adjacency list. Use brackets around each vector and also use + * brackets around the entire adjacency list to make it clear when the list + * is empty. + */ +void print_adjlist(const igraph_adjlist_t *adjlist); + +/* Print a graph. Use brackets to make it obvious when the edge list is empty. */ +void print_graph(const igraph_t *graph); + +/* Print a graph with edge weights from a vector. Use brackets to make it + * obvious when the edge list is empty. */ +void print_weighted_graph(const igraph_t *graph, const igraph_vector_t* weights); + +/* Print a graph with edge weights from an edge attribute. Use brackets to make + * it obvious when the edge list is empty. */ +void print_weighted_graph_attr(const igraph_t *graph, const char* attr); + +/* Print an incidence list. Use brackets around each vector and also use + * brackets around the entire incidence list to make it clear when the list + * is empty. + */ +void print_inclist(const igraph_inclist_t *inclist); + +/* Print a lazy adjacency list. Use brackets around each vector and also use + * brackets around the entire lazy adjacency list to make it clear when the list + * is empty. + */ +void print_lazy_adjlist(igraph_lazy_adjlist_t *adjlist); + +/* Print a lazy incidence list. Use brackets around each vector and also use + * brackets around the entire incidence list to make it clear when the list + * is empty. + */ +void print_lazy_inclist(igraph_lazy_inclist_t *inclist); + +/* Print a graph using a sorted edge list. Other than sorting (i.e. canonicalizing) the edge list, + * this function is identical to print_graph(). */ +void print_graph_canon(const igraph_t *graph); + +/* Print a weighted graph using a sorted edge list. Other than sorting (i.e. canonicalizing) + * the edge list, this function is identical to print_graph(). */ +void print_weighted_graph_canon(const igraph_t *graph, const igraph_vector_t *weights); + +/* Print a vector, ensuring that the first nonzero element is positive. */ +void print_vector_first_nonzero_element_positive(const igraph_vector_t *vector, const char* format); + +/* Print a complex vector, ensuring that the first element with nonzero real + * part has a positive real part. */ +void print_vector_complex_first_nonzero_real_part_positive(const igraph_vector_complex_t *vector); + +/* Print a matrix, ensuring that the first nonzero element in each column is + * positive. */ +void print_matrix_first_row_positive(const igraph_matrix_t *matrix, const char* format); + +/* Print a complex matrix, ensuring that the first element with nonzero real + * part in each column has a positive real part. */ +void print_matrix_complex_first_row_positive(const igraph_matrix_complex_t *matrix); + +/* print all graph, edge and vertex attributes of a graph */ +void print_attributes(const igraph_t *g); + +void matrix_init_int_row_major(igraph_matrix_t *mat, igraph_int_t nrow, igraph_int_t ncol, const int *elem); +void matrix_int_init_int_row_major(igraph_matrix_int_t *mat, igraph_int_t nrow, igraph_int_t ncol, const int *elem); +void matrix_init_real_row_major(igraph_matrix_t *mat, igraph_int_t nrow, igraph_int_t ncol, const igraph_real_t *elem); + +void matrix_chop(igraph_matrix_t *mat, igraph_real_t cutoff); +void vector_chop(igraph_vector_t *vec, igraph_real_t cutoff); + +#define VERIFY_FINALLY_STACK() \ + do { if (!IGRAPH_FINALLY_STACK_EMPTY) { \ + IGRAPH_FATALF( \ + "Finally stack is not empty (stack size is %d). " \ + "Check that the number in IGRAPH_FINALLY_CLEAN matches the IGRAPH_FINALLY count.\n", \ + IGRAPH_FINALLY_STACK_SIZE()); \ + } } while (0) + +/* Run a test in a separate function; return the return value of the function + * if it is nonzero. Also verify the FINALLY stack and bail out if it is not + * empty. Needs an integer variable named 'retval' in the local context. */ +#define RUN_TEST(func) \ + do { \ + int retval = func(); \ + if (retval) { \ + return retval; \ + } \ + VERIFY_FINALLY_STACK(); \ + } while (0) + +#define CHECK_ERROR(funcall, expected_err) \ + do { \ + igraph_error_handler_t *handler; \ + handler = igraph_set_error_handler(igraph_error_handler_ignore); \ + IGRAPH_ASSERT(funcall == expected_err); \ + igraph_set_error_handler(handler); \ + } while (0) + +void record_last_warning(const char *reason, const char *file, int line); + +void print_bitset(const igraph_bitset_t* bitset); + +void print_bitset_list(const igraph_bitset_list_t* bitset); + +#define EXPECT_WARNING(funcall, expected_warning) \ + do { \ + igraph_warning_handler_t *handler; \ + expect_warning_ctx.expected = expected_warning; \ + expect_warning_ctx.observed = NULL; \ + handler = igraph_set_warning_handler(record_last_warning); \ + IGRAPH_ASSERT(IGRAPH_SUCCESS == funcall); \ + igraph_set_warning_handler(handler); \ + if (expect_warning_ctx.observed == NULL) { \ + IGRAPH_FATALF("Expected this warning but none was raised:\n %s\n", expected_warning); \ + } else if (strcmp(expect_warning_ctx.observed, expect_warning_ctx.expected)) { \ + IGRAPH_FATALF("Expected warning:\n %s\ngot:\n %s\n", expected_warning, expect_warning_ctx.observed); \ + /* no need to free(expect_warning_ctx.observed); as previous line aborts immediately */ \ + } else { \ + free(expect_warning_ctx.observed); \ + } \ + } while (0) +#endif /* TEST_UTILITIES_H */ diff --git a/tests/unit/tls1.c b/tests/unit/tls1.c new file mode 100644 index 0000000..2367524 --- /dev/null +++ b/tests/unit/tls1.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +void *thread_function(void *arg) { + IGRAPH_UNUSED(arg); + IGRAPH_FINALLY(igraph_free, NULL); + return 0; +} + +int main(void) { + pthread_t thread_id; + void *exit_status; + + /* Skip if igraph is not thread-safe */ + if (!IGRAPH_THREAD_SAFE) { + return 77; + } + + /* Run a thread that leaves some junk in the error stack */ + pthread_create(&thread_id, NULL, thread_function, 0); + pthread_join(thread_id, &exit_status); + + /* Check that the error stack is not common */ + if (!IGRAPH_FINALLY_STACK_EMPTY) { + printf("Foobar\n"); + return 1; + } + + return 0; +} diff --git a/tests/unit/tls2.c b/tests/unit/tls2.c new file mode 100644 index 0000000..ba30a43 --- /dev/null +++ b/tests/unit/tls2.c @@ -0,0 +1,243 @@ +/* + igraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "linalg/arpack_internal.h" + +#include "test_utilities.h" + +/* Test whether ARPACK is thread-safe. We will create two threads, + each calling a different ARPACK eigensolver. We will make sure that + the ARPACK calls from the two threads overlap */ + +typedef struct thread_data_t { + igraph_matrix_t *m; + igraph_vector_t *result; + pthread_cond_t *cond; + pthread_mutex_t *mutex; + int *steps, *othersteps; +} thread_data_t; + +int arpack_mult(igraph_real_t *to, igraph_real_t *from, + igraph_matrix_t *matrix) { + /* TODO */ + igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, matrix, + from, /*beta=*/ 0.0, to); + + return 0; +} + +/* This is the function performed by each thread. It calls the + low-level ARPACK symmetric eigensolver, step by step. After each + step, it synchronizes with the other thread. + + The synchronization ensures that the two threads are using the + thread-local variables at the same time. If they are really + thread-local, then ARPACK still delivers the correct solution for + the two matrices. Otherwise the result is undefined: maybe results + will be incorrect, or the program will crash. + + This function is basically a simplified copy of igraph_arpack_rssolve. +*/ + +void *thread_function(void *arg) { + thread_data_t *data = (thread_data_t*) arg; + igraph_matrix_t *M = data->m; + igraph_vector_t *result = data->result; + pthread_cond_t *cond = data->cond; + pthread_mutex_t *mutex = data->mutex; + igraph_arpack_options_t options; + igraph_real_t *v, *workl, *workd, *d, *resid, *ax; + int *select; + int ido = 0; +#if IGRAPH_THREAD_SAFE + int rvec = 1; + char *all = "All"; +#endif + int i; + + igraph_arpack_options_init(&options); + options.n = igraph_matrix_nrow(M); + options.ldv = options.n; + options.nev = 1; + options.ncv = 3; + options.lworkl = options.ncv * (options.ncv + 8); + options.which[0] = 'L'; + options.which[1] = 'M'; + + options.iparam[0] = options.ishift; + options.iparam[2] = options.mxiter; + options.iparam[3] = options.nb; + options.iparam[4] = 0; + options.iparam[6] = options.mode; + options.info = options.start; + + v = IGRAPH_CALLOC(options.ldv * options.ncv, igraph_real_t); + workl = IGRAPH_CALLOC(options.lworkl, igraph_real_t); + workd = IGRAPH_CALLOC(3 * options.n, igraph_real_t); + d = IGRAPH_CALLOC(2 * options.ncv, igraph_real_t); + resid = IGRAPH_CALLOC(options.n, igraph_real_t); + ax = IGRAPH_CALLOC(options.n, igraph_real_t); + select = IGRAPH_CALLOC(options.ncv, int); + + if (!v || !workl || !workd || !d || !resid || !ax || !select) { + printf("Out of memory\n"); + return 0; + } + + while (1) { +#if IGRAPH_THREAD_SAFE + igraphdsaupd_(&ido, options.bmat, &options.n, options.which, + &options.nev, &options.tol, resid, &options.ncv, v, + &options.ldv, options.iparam, options.ipntr, workd, + workl, &options.lworkl, &options.info); +#endif + + if (ido == -1 || ido == 1) { + + igraph_real_t *from = workd + options.ipntr[0] - 1; + igraph_real_t *to = workd + options.ipntr[1] - 1; + arpack_mult(to, from, M); + + } else { + break; + } + + pthread_mutex_lock(mutex); + *(data->steps) += 1; + if ( *(data->othersteps) == *(data->steps) ) { + pthread_cond_signal(cond); + } + + while ( *(data->othersteps) < * (data->steps) && *(data->othersteps) != -1 ) { + pthread_cond_wait(cond, mutex); + } + pthread_mutex_unlock(mutex); + } + + pthread_mutex_lock(mutex); + *data->steps = -1; + pthread_cond_signal(cond); + pthread_mutex_unlock(mutex); + + if (options.info != 0) { + printf("ARPACK error\n"); + return 0; + } + +#if IGRAPH_THREAD_SAFE + igraphdseupd_(&rvec, all, select, d, v, &options.ldv, + &options.sigma, options.bmat, &options.n, + options.which, &options.nev, &options.tol, + resid, &options.ncv, v, &options.ldv, options.iparam, + options.ipntr, workd, workl, &options.lworkl, + &options.ierr); +#endif + + if (options.ierr != 0) { + printf("ARPACK error\n"); + return 0; + } + + igraph_vector_resize(result, options.n); + for (i = 0; i < options.n; i++) { + VECTOR(*result)[i] = v[i]; + } + + if (VECTOR(*result)[0] < 0) { + igraph_vector_scale(result, -1.0); + } + + free(v); + free(workl); + free(workd); + free(d); + free(resid); + free(ax); + free(select); + + return 0; +} + +int main(void) { + pthread_t thread_id1, thread_id2; + void *exit_status1, *exit_status2; + igraph_matrix_t m1, m2; + igraph_vector_t result1, result2; + pthread_cond_t steps_cond = PTHREAD_COND_INITIALIZER; + pthread_mutex_t steps_mutex = PTHREAD_MUTEX_INITIALIZER; + int steps1 = 0, steps2 = 0; + thread_data_t + data1 = { &m1, &result1, &steps_cond, &steps_mutex, &steps1, &steps2 }, + data2 = { &m2, &result2, &steps_cond, &steps_mutex, &steps2, &steps1 }; + int i, j; + + /* Skip if igraph is not thread safe */ + if (!IGRAPH_THREAD_SAFE) { + return 77; + } + + igraph_matrix_init(&m1, 10, 10); + igraph_matrix_init(&m2, 10, 10); + igraph_vector_init(&result1, igraph_matrix_nrow(&m1)); + igraph_vector_init(&result2, igraph_matrix_nrow(&m2)); + + igraph_rng_seed(igraph_rng_default(), 42); + + for (i = 0; i < igraph_matrix_nrow(&m1); i++) { + for (j = 0; j <= i; j++) { + MATRIX(m1, i, j) = MATRIX(m1, j, i) = + igraph_rng_get_integer(igraph_rng_default(), 0, 10); + } + } + + for (i = 0; i < igraph_matrix_nrow(&m2); i++) { + for (j = 0; j <= i; j++) { + MATRIX(m2, i, j) = MATRIX(m2, j, i) = + igraph_rng_get_integer(igraph_rng_default(), 0, 10); + } + } + + pthread_create(&thread_id1, NULL, thread_function, (void *) &data1); + pthread_create(&thread_id2, NULL, thread_function, (void *) &data2); + + pthread_join(thread_id1, &exit_status1); + pthread_join(thread_id2, &exit_status2); + + igraph_matrix_print(&m1); + igraph_vector_print(&result1); + printf("---\n"); + igraph_matrix_print(&m2); + igraph_vector_print(&result2); + + igraph_vector_destroy(&result1); + igraph_vector_destroy(&result2); + igraph_matrix_destroy(&m1); + igraph_matrix_destroy(&m2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/tls2.out b/tests/unit/tls2.out new file mode 100644 index 0000000..fd28f95 --- /dev/null +++ b/tests/unit/tls2.out @@ -0,0 +1,23 @@ + 1 5 10 10 9 8 4 3 1 7 + 5 9 10 0 10 1 0 10 10 8 +10 10 8 1 10 6 4 7 2 2 +10 0 1 6 7 1 4 5 7 3 + 9 10 10 7 3 7 10 7 2 6 + 8 1 6 1 7 0 10 1 3 10 + 4 0 4 4 10 10 5 4 1 7 + 3 10 7 5 7 1 4 4 4 1 + 1 10 2 7 2 3 1 4 4 8 + 7 8 2 3 6 10 7 1 8 7 +0.335098 0.373089 0.362013 0.241464 0.403622 0.277745 0.281592 0.271598 0.235929 0.332236 +--- +3 7 7 7 4 7 9 0 2 6 +7 7 6 3 9 6 10 0 2 7 +7 6 3 8 9 8 9 7 5 9 +7 3 8 9 2 4 10 10 5 2 +4 9 9 2 3 7 1 3 6 9 +7 6 8 4 7 6 8 0 1 7 +9 10 9 10 1 8 3 2 4 4 +0 0 7 10 3 0 2 10 5 3 +2 2 5 5 6 1 4 5 10 10 +6 7 9 2 9 7 4 3 10 9 +0.300487 0.32522 0.384798 0.32095 0.299319 0.311637 0.33754 0.209222 0.272569 0.366266 diff --git a/tests/unit/topological_sorting.c b/tests/unit/topological_sorting.c new file mode 100644 index 0000000..13477d9 --- /dev/null +++ b/tests/unit/topological_sorting.c @@ -0,0 +1,99 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_t g; + igraph_vector_int_t v; + igraph_vector_int_t res; + igraph_bool_t is_dag; + int ret; + + /* Test graph taken from http://en.wikipedia.org/wiki/Topological_sorting + * @ 05.03.2006 */ + igraph_small(&g, 8, IGRAPH_DIRECTED, + 0, 3, 0, 4, 1, 3, 2, 4, 2, 7, 3, 5, 3, 6, 3, 7, 4, 6, + -1); + + igraph_vector_int_init(&res, 0); + + igraph_is_dag(&g, &is_dag); + if (!is_dag) { + return 2; + } + + igraph_topological_sorting(&g, &res, IGRAPH_OUT); + print_vector_int(&res); + igraph_topological_sorting(&g, &res, IGRAPH_IN); + print_vector_int(&res); + + /* Error handling */ + VERIFY_FINALLY_STACK(); + igraph_set_error_handler(igraph_error_handler_ignore); + + /* Add a cycle: 5 -> 0 */ + igraph_vector_int_init_int(&v, 2, 5, 0); + igraph_add_edges(&g, &v, 0); + igraph_is_dag(&g, &is_dag); + if (is_dag) { + return 3; + } + ret = igraph_topological_sorting(&g, &res, IGRAPH_OUT); + if (ret != IGRAPH_EINVAL) { + return 1; + } + + igraph_vector_int_destroy(&v); + igraph_destroy(&g); + + /* This graph is the same but undirected */ + igraph_small(&g, 8, IGRAPH_UNDIRECTED, + 0, 3, 0, 4, 1, 3, 2, 4, 2, 7, 3, 5, 3, 6, 3, 7, 4, 6, + -1); + + igraph_is_dag(&g, &is_dag); + if (is_dag) { + return 4; + } + + ret = igraph_topological_sorting(&g, &res, IGRAPH_ALL); + if (ret != IGRAPH_EINVAL) { + return 1; + } + + ret = igraph_topological_sorting(&g, &res, IGRAPH_OUT); + if (ret != IGRAPH_EINVAL) { + return 1; + } + + igraph_destroy(&g); + + igraph_vector_int_destroy(&res); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/topological_sorting.out b/tests/unit/topological_sorting.out new file mode 100644 index 0000000..d6422ff --- /dev/null +++ b/tests/unit/topological_sorting.out @@ -0,0 +1,2 @@ +( 0 1 2 3 4 5 7 6 ) +( 5 6 7 4 3 2 0 1 ) diff --git a/tests/unit/tree_game.c b/tests/unit/tree_game.c new file mode 100644 index 0000000..72ec907 --- /dev/null +++ b/tests/unit/tree_game.c @@ -0,0 +1,88 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_t graph; + igraph_bool_t is_tree = false, are_adjacent = false; + + igraph_rng_seed(igraph_rng_default(), 74088); + + /* Undirected */ + + IGRAPH_ASSERT(igraph_tree_game(&graph, 123, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_OUT) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + IGRAPH_ASSERT(igraph_tree_game(&graph, 123, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_OUT) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + /* Directed out-tree */ + + IGRAPH_ASSERT(igraph_tree_game(&graph, 123, IGRAPH_DIRECTED, IGRAPH_RANDOM_TREE_LERW) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_is_tree(&graph, &is_tree, NULL, IGRAPH_OUT) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(is_tree); + igraph_destroy(&graph); + + /* IGRAPH_RANDOM_TREE_PRUFER does not currently support directed graphs */ + + /* Null graph */ + + IGRAPH_ASSERT(igraph_tree_game(&graph, 0, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + + IGRAPH_ASSERT(igraph_tree_game(&graph, 0, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 0); + igraph_destroy(&graph); + + /* Singleton graph */ + + IGRAPH_ASSERT(igraph_tree_game(&graph, 1, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 1); + igraph_destroy(&graph); + + IGRAPH_ASSERT(igraph_tree_game(&graph, 1, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 1); + igraph_destroy(&graph); + + /* P_2 */ + + IGRAPH_ASSERT(igraph_tree_game(&graph, 2, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_LERW) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 2); + IGRAPH_ASSERT(igraph_ecount(&graph) == 1); + IGRAPH_ASSERT(igraph_are_adjacent(&graph, 0, 1, &are_adjacent) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(are_adjacent); + igraph_destroy(&graph); + + IGRAPH_ASSERT(igraph_tree_game(&graph, 2, IGRAPH_UNDIRECTED, IGRAPH_RANDOM_TREE_PRUFER) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vcount(&graph) == 2); + IGRAPH_ASSERT(igraph_ecount(&graph) == 1); + IGRAPH_ASSERT(igraph_are_adjacent(&graph, 0, 1, &are_adjacent) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(are_adjacent); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/triad_census.c b/tests/unit/triad_census.c new file mode 100644 index 0000000..ba0b6f6 --- /dev/null +++ b/tests/unit/triad_census.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2015-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_vector_t tri; + igraph_t graph; + + igraph_set_warning_handler(igraph_warning_handler_ignore); + + igraph_small(&graph, 10, IGRAPH_DIRECTED, + 0, 2, 1, 4, 2, 5, 2, 7, 3, 7, 3, 8, 4, 2, 5, 8, 6, 0, 6, 1, 6, 2, 7, + 0, 8, 0, 8, 2, 8, 3, 8, 5, 9, 2, 9, 3, 9, 4, 9, 5, + -1); + + igraph_vector_init(&tri, 0); + + igraph_triad_census(&graph, &tri); + print_vector_round(&tri); + + igraph_to_undirected(&graph, IGRAPH_TO_UNDIRECTED_COLLAPSE, NULL); /* convert to undirected */ + igraph_triad_census(&graph, &tri); + print_vector_round(&tri); + + igraph_vector_destroy(&tri); + igraph_destroy(&graph); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/triad_census.out b/tests/unit/triad_census.out new file mode 100644 index 0000000..ee099b7 --- /dev/null +++ b/tests/unit/triad_census.out @@ -0,0 +1,2 @@ +( 25 45 7 7 12 11 2 4 4 1 1 0 0 1 0 0 ) +( 25 0 52 0 0 0 0 0 0 0 37 0 0 0 0 6 ) diff --git a/tests/unit/trie.c b/tests/unit/trie.c new file mode 100644 index 0000000..110d4a6 --- /dev/null +++ b/tests/unit/trie.c @@ -0,0 +1,137 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "core/trie.h" + +#include "test_utilities.h" + +int main(void) { + + igraph_trie_t trie; + igraph_int_t id; + igraph_int_t i; + const char *str; + + /* init */ + igraph_trie_init(&trie, 0); + + /* add and get values */ + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "a", &id); + printf("a: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "axon", &id); + printf("axon: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + /* check for existence */ + igraph_trie_check(&trie, "head", &id); + printf("head: %" IGRAPH_PRId "\n", id); + igraph_trie_check(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + + /* destroy */ + igraph_trie_destroy(&trie); + + /* the same with index */ + igraph_trie_init(&trie, 1); + + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "a", &id); + printf("a: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "axon", &id); + printf("axon: %" IGRAPH_PRId "\n", id); + + igraph_trie_get(&trie, "hello", &id); + printf("hello: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "hepp", &id); + printf("hepp: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + igraph_trie_get(&trie, "also", &id); + printf("also: %" IGRAPH_PRId "\n", id); + + /* check for existence */ + igraph_trie_check(&trie, "head", &id); + printf("head: %" IGRAPH_PRId "\n", id); + igraph_trie_check(&trie, "alma", &id); + printf("alma: %" IGRAPH_PRId "\n", id); + + for (i = 0; i < igraph_trie_size(&trie); i++) { + str = igraph_trie_idx(&trie, i); + printf("%" IGRAPH_PRId ": %s\n", i, str); + } + + /* prevent insertion of empty key */ + igraph_set_error_handler(igraph_error_handler_ignore); + IGRAPH_ASSERT(igraph_trie_get(&trie, "", &id) == IGRAPH_EINVAL); + + igraph_trie_destroy(&trie); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/trie.out b/tests/unit/trie.out new file mode 100644 index 0000000..7367a56 --- /dev/null +++ b/tests/unit/trie.out @@ -0,0 +1,38 @@ +hello: 0 +hepp: 1 +alma: 2 +also: 3 +hello: 0 +hepp: 1 +alma: 2 +also: 3 +a: 4 +axon: 5 +hello: 0 +hepp: 1 +alma: 2 +also: 3 +head: -1 +alma: 2 +hello: 0 +hepp: 1 +alma: 2 +also: 3 +hello: 0 +hepp: 1 +alma: 2 +also: 3 +a: 4 +axon: 5 +hello: 0 +hepp: 1 +alma: 2 +also: 3 +head: -1 +alma: 2 +0: hello +1: hepp +2: alma +3: also +4: a +5: axon diff --git a/tests/unit/utf8_with_bom.net b/tests/unit/utf8_with_bom.net new file mode 100644 index 0000000..57abfdf --- /dev/null +++ b/tests/unit/utf8_with_bom.net @@ -0,0 +1,18 @@ +*Vertices 10 + 1 "Vlado" 0.4682 0.4841 0.5000 ellipse foo bar + 2 "Андреј" 0.7125 0.8376 0.5000 ellipse foo baz + 3 "ŽnidarÅ¡ič" 0.3346 0.7739 0.5000 ellipse foo 123 + 4 "Đurđić" 0.2455 0.8725 0.5000 ellipse foo 电脑 + 5 "Jürgen Müller" 0.6361 0.6373 0.5000 ellipse foo "a b c" + 6 "∰∈∀≋∞⊚" 0.5153 0.6935 0.5000 ellipse foo "þæð" + 7 "⑤⑨④②⑦" 0.8053 0.4917 0.5000 ellipse foo 123.456 + 8 "♔♕♖♗♘♙" 0.4173 0.6161 0.5000 ellipse foo xxx + 9 "焹焓燝牕犫獨" 0.5623 0.5539 0.5000 ellipse x -1 + 10 "ανδρει" 0.3690 0.3202 0.5000 ellipse y 1 +*Edges + 2 3 1 l "焹焓燝牕犫獨" + 3 4 1 + 2 6 1 + 3 6 1 + 5 6 1 + 2 7 1 diff --git a/tests/unit/vector.c b/tests/unit/vector.c new file mode 100644 index 0000000..4a2702e --- /dev/null +++ b/tests/unit/vector.c @@ -0,0 +1,374 @@ +/* + igraph library. + Copyright (C) 2006-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_vector_t v; + igraph_vector_int_t v2, v4, v5, v6; + igraph_int_t i; + igraph_real_t *ptr; + igraph_int_t pos; + igraph_real_t min, max, min2, max2; + igraph_int_t which_min, which_max, which_min2, which_max2; + + printf("Initialise empty vector\n"); + igraph_vector_init(&v, 0); + igraph_vector_destroy(&v); + + printf("Initialise vector of length 10\n"); + igraph_vector_init(&v, 10); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test VECTOR() and igraph_vector_size\n"); + igraph_vector_init(&v, 10); + for (i = 0; i < igraph_vector_size(&v); i++) { + VECTOR(v)[i] = 10 - i; + } + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_reserve and igraph_vector_push_back\n"); + igraph_vector_init(&v, 0); + igraph_vector_reserve(&v, 10); + for (i = 0; i < 10; i++) { + igraph_vector_push_back(&v, i); + } + + printf("Test igraph_vector_empty and igraph_vector_clear\n"); + IGRAPH_ASSERT(!igraph_vector_empty(&v)); + igraph_vector_clear(&v); + IGRAPH_ASSERT(igraph_vector_empty(&v)); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_get and igraph_vector_get_ptr\n"); + igraph_vector_init(&v, 5); + for (i = 0; i < igraph_vector_size(&v); i++) { + *igraph_vector_get_ptr(&v, i) = 100 * i; + } + for (i = 0; i < igraph_vector_size(&v); i++) { + printf(" %" IGRAPH_PRId "", (igraph_int_t)igraph_vector_get(&v, i)); + } + printf("\n"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_set\n"); + igraph_vector_init(&v, 5); + for (i = 0; i < igraph_vector_size(&v); i++) { + igraph_vector_set(&v, i, 20 * i); + } + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_null\n"); + igraph_vector_init(&v, 0); + igraph_vector_null(&v); + igraph_vector_destroy(&v); + igraph_vector_init(&v, 10); + for (i = 0; i < igraph_vector_size(&v); i++) { + VECTOR(v)[i] = i + 1; + } + igraph_vector_null(&v); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_tail, igraph_vector_pop_back\n"); + igraph_vector_init(&v, 10); + for (i = 0; i < igraph_vector_size(&v); i++) { + VECTOR(v)[i] = i + 1; + } + while (!igraph_vector_empty(&v)) { + printf(" %" IGRAPH_PRId "", (igraph_int_t)igraph_vector_tail(&v)); + printf(" %" IGRAPH_PRId "", (igraph_int_t)igraph_vector_pop_back(&v)); + } + printf("\n"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_resize, igraph_vector_sort\n"); + igraph_vector_init(&v, 20); + for (i = 0; i < 10; i++) { + VECTOR(v)[i] = 10 - i; + } + igraph_vector_resize(&v, 10); + igraph_vector_sort(&v); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_{which}_{min, max}\n"); + igraph_vector_init(&v, 10); + for (i = 0; i < igraph_vector_size(&v); i++) { + VECTOR(v)[i] = 100 - i; + } + for (i = 0; i < 10; i++) { + printf(" %" IGRAPH_PRId "", (igraph_int_t)VECTOR(v)[i]); + } + printf("\n"); + + min = igraph_vector_min(&v); + which_min = igraph_vector_which_min(&v); + + IGRAPH_ASSERT(min == 91); + IGRAPH_ASSERT(which_min == 9); + IGRAPH_ASSERT(min == VECTOR(v)[which_min]); + + max = igraph_vector_max(&v); + which_max = igraph_vector_which_max(&v); + + IGRAPH_ASSERT(max == 100); + IGRAPH_ASSERT(which_max == 0); + IGRAPH_ASSERT(max == VECTOR(v)[which_max]); + + igraph_vector_minmax(&v, &min2, &max2); + igraph_vector_which_minmax(&v, &which_min2, &which_max2); + + IGRAPH_ASSERT(min == min2); + IGRAPH_ASSERT(max == max2); + IGRAPH_ASSERT(which_min == which_min2); + IGRAPH_ASSERT(which_max == which_max2); + IGRAPH_ASSERT(min2 == VECTOR(v)[which_min2]); + IGRAPH_ASSERT(max2 == VECTOR(v)[which_max2]); + + printf("Test NaN values\n"); + igraph_vector_push_back(&v, IGRAPH_NAN); + igraph_vector_push_back(&v, IGRAPH_NAN); + igraph_vector_push_back(&v, 1); + + IGRAPH_ASSERT(igraph_vector_is_any_nan(&v)); + + min = igraph_vector_min(&v); + which_min = igraph_vector_which_min(&v); + + IGRAPH_ASSERT(isnan(min)); + /* Index should be to first NaN value */ + IGRAPH_ASSERT(which_min == 10); + IGRAPH_ASSERT(isnan(VECTOR(v)[which_min])); + + max = igraph_vector_max(&v); + which_max = igraph_vector_which_max(&v); + + IGRAPH_ASSERT(isnan(max)); + /* Index should be to first NaN value */ + IGRAPH_ASSERT(which_max == 10); + /* In case of NaN it should hold that which_max == which_min */ + IGRAPH_ASSERT(which_max == which_min); + + igraph_vector_minmax(&v, &min2, &max2); + igraph_vector_which_minmax(&v, &which_min2, &which_max2); + + IGRAPH_ASSERT(isnan(min2)); + IGRAPH_ASSERT(isnan(max2)); + IGRAPH_ASSERT(which_min == which_min2); + IGRAPH_ASSERT(which_max == which_max2); + /* In case of NaN it should hold that which_max == which_min */ + IGRAPH_ASSERT(which_min2 == which_max2); + IGRAPH_ASSERT(isnan(VECTOR(v)[which_min2])); + IGRAPH_ASSERT(isnan(VECTOR(v)[which_max2])); + + printf("Test igraph_vector_init_array\n"); + igraph_vector_destroy(&v); + ptr = (igraph_real_t*) malloc(10 * sizeof(igraph_real_t)); + igraph_vector_init_array(&v, ptr, 10); + free(ptr); + for (i = 0; i < 10; i++) { + VECTOR(v)[i] = 100 - i; + } + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_copy_to\n"); + ptr = (igraph_real_t*) malloc(10 * sizeof(igraph_real_t)); + igraph_vector_init_range(&v, 11, 21); + igraph_vector_copy_to(&v, ptr); + for (i = 0; i < 10; i++) { + printf(" %" IGRAPH_PRId "", (igraph_int_t)ptr[i]); + } + printf("\n"); + free(ptr); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_init_range, igraph_vector_sum, igraph_vector_prod\n"); + igraph_vector_init_range(&v, 1, 6); + printf(" %" IGRAPH_PRId "", (igraph_int_t)igraph_vector_sum(&v)); + printf(" %" IGRAPH_PRId "\n", (igraph_int_t)igraph_vector_prod(&v)); + + printf("Test igraph_vector_remove_section\n"); + igraph_vector_remove_section(&v, 2, 4); + print_vector_format(&v, stdout, "%g"); + + printf("Test igraph_vector_remove_section with invalid limits\n"); + igraph_vector_remove_section(&v, -3, -1); + igraph_vector_remove_section(&v, 100, 120); + igraph_vector_remove_section(&v, 2, 0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_remove_section(&v, 1, 20); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_remove\n"); + igraph_vector_init_range(&v, 1, 11); + igraph_vector_remove(&v, 9); + igraph_vector_remove(&v, 0); + igraph_vector_remove(&v, 4); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_remove_fast\n"); + igraph_vector_init_range(&v, 1, 11); + igraph_vector_remove_fast(&v, 9); + igraph_vector_remove_fast(&v, 0); + igraph_vector_remove_fast(&v, 4); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_move_interval\n"); + igraph_vector_init_range(&v, 0, 10); + igraph_vector_move_interval(&v, 5, 10, 0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_isininterval\n"); + igraph_vector_init_range(&v, 1, 11); + IGRAPH_ASSERT(igraph_vector_isininterval(&v, 1, 10)); + IGRAPH_ASSERT(!igraph_vector_isininterval(&v, 2, 10)); + IGRAPH_ASSERT(!igraph_vector_isininterval(&v, 1, 9)); + + printf("Test igraph_vector_any_smaller\n"); + IGRAPH_ASSERT(!igraph_vector_any_smaller(&v, 1)); + IGRAPH_ASSERT(igraph_vector_any_smaller(&v, 2)); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_all_e\n"); + + printf("Test igraph_vector_binsearch\n"); + igraph_vector_init_range(&v, 0, 10); + for (i = 0; i < igraph_vector_size(&v); i++) { + IGRAPH_ASSERT(igraph_vector_binsearch(&v, 0, 0)); + } + IGRAPH_ASSERT(!igraph_vector_binsearch(&v, 10, 0)); + IGRAPH_ASSERT(!igraph_vector_binsearch(&v, -1, 0)); + + for (i = 0; i < igraph_vector_size(&v); i++) { + VECTOR(v)[i] = 2 * i; + } + for (i = 0; i < igraph_vector_size(&v); i++) { + IGRAPH_ASSERT(igraph_vector_binsearch(&v, VECTOR(v)[i], &pos)); + IGRAPH_ASSERT(pos == i); + IGRAPH_ASSERT(!igraph_vector_binsearch(&v, VECTOR(v)[i] + 1, &pos)); + } + igraph_vector_destroy(&v); + + printf("Test Binsearch in empty vector\n"); + igraph_vector_init(&v, 0); + IGRAPH_ASSERT(!igraph_vector_contains_sorted(&v, 0)); + IGRAPH_ASSERT(!igraph_vector_binsearch(&v, 1, &pos)); + IGRAPH_ASSERT(pos == 0); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_init_real\n"); + igraph_vector_init_real(&v, 10, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_init_int\n"); + igraph_vector_init_int(&v, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_init_real\n"); + igraph_vector_init_real_end(&v, -1, 1.0, 2.0, 3.0, 4.0, 5.0, + 6.0, 7.0, 8.0, 9.0, 10.0, -1.0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_vector_init_int\n"); + igraph_vector_init_int_end(&v, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test filter_smaller, quite special....\n"); + igraph_vector_init_int_end(&v, -1, 0, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, -1); + igraph_vector_filter_smaller(&v, 4); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + igraph_vector_init_int_end(&v, -1, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, -1); + igraph_vector_filter_smaller(&v, 0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + igraph_vector_init_int_end(&v, -1, 0, 0, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, -1); + igraph_vector_filter_smaller(&v, 0); + print_vector_format(&v, stdout, "%g"); + igraph_vector_destroy(&v); + + printf("Test igraph_i_vector_int_rank and igraph_i_vector_int_order\n"); + igraph_vector_int_init_int_end(&v2, -1, 0, 1, 2, 6, 5, 2, 1, 0, -1); + igraph_vector_int_init(&v4, 0); + igraph_i_vector_int_rank(&v2, &v4, 7); + print_vector_int(&v2); + print_vector_int(&v4); + igraph_i_vector_int_order(&v2, &v4, 7); + print_vector_int(&v4); + igraph_vector_int_destroy(&v2); + igraph_vector_int_destroy(&v4); + + printf("Test pair order\n"); + igraph_vector_int_init_int_end(&v5, -1, 1, 1, 2, 2, -1); + igraph_vector_int_init_int_end(&v6, -1, 2, 3, 1, 3, -1); + igraph_vector_int_init(&v4, 0); + igraph_vector_int_pair_order(&v5, &v6, &v4, 3); + print_vector_int(&v4); + igraph_vector_int_destroy(&v5); + igraph_vector_int_destroy(&v6); + igraph_vector_int_destroy(&v4); + + printf("Test fill\n"); + + igraph_vector_init(&v, 100); + igraph_vector_fill(&v, 1.234567); + for (i = 0; i < igraph_vector_size(&v); i++) { + IGRAPH_ASSERT(VECTOR(v)[i] == 1.234567); + } + igraph_vector_destroy(&v); + + printf("Test range\n"); + + igraph_vector_init(&v, 100); + igraph_vector_range(&v, 20, 50); + IGRAPH_ASSERT(igraph_vector_size(&v) == 30); + for (i = 0; i < igraph_vector_size(&v); i++) { + IGRAPH_ASSERT(VECTOR(v)[i] == 20 + i); + } + igraph_vector_destroy(&v); + + printf("Test igraph_vector_int_init_range, igraph_i_vector_int_order\n"); + igraph_vector_int_init_range(&v4, 1, 11); + igraph_vector_int_reverse(&v4); + igraph_vector_int_scale(&v4, 3); + igraph_vector_int_init(&v5, 0); + igraph_i_vector_int_order(&v4, &v5, /* maxval */ igraph_vector_int_max(&v4)); + print_vector_int(&v5); + igraph_vector_int_destroy(&v4); + igraph_vector_int_destroy(&v5); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector.out b/tests/unit/vector.out new file mode 100644 index 0000000..84c1430 --- /dev/null +++ b/tests/unit/vector.out @@ -0,0 +1,64 @@ +Initialise empty vector +Initialise vector of length 10 +( 0 0 0 0 0 0 0 0 0 0 ) +Test VECTOR() and igraph_vector_size +( 10 9 8 7 6 5 4 3 2 1 ) +Test igraph_vector_reserve and igraph_vector_push_back +Test igraph_vector_empty and igraph_vector_clear +Test igraph_vector_get and igraph_vector_get_ptr + 0 100 200 300 400 +Test igraph_vector_set +( 0 20 40 60 80 ) +Test igraph_vector_null +( 0 0 0 0 0 0 0 0 0 0 ) +Test igraph_vector_tail, igraph_vector_pop_back + 10 10 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 +Test igraph_vector_resize, igraph_vector_sort +( 1 2 3 4 5 6 7 8 9 10 ) +Test igraph_vector_{which}_{min, max} + 100 99 98 97 96 95 94 93 92 91 +Test NaN values +Test igraph_vector_init_array +( 100 99 98 97 96 95 94 93 92 91 ) +Test igraph_vector_copy_to + 11 12 13 14 15 16 17 18 19 20 +Test igraph_vector_init_range, igraph_vector_sum, igraph_vector_prod + 15 120 +Test igraph_vector_remove_section +( 1 2 5 ) +Test igraph_vector_remove_section with invalid limits +( 1 2 5 ) +( 1 ) +Test igraph_vector_remove +( 2 3 4 5 7 8 9 ) +Test igraph_vector_remove_fast +( 9 2 3 4 8 6 7 ) +Test igraph_vector_move_interval +( 5 6 7 8 9 5 6 7 8 9 ) +Test igraph_vector_isininterval +Test igraph_vector_any_smaller +Test igraph_vector_all_e +Test igraph_vector_binsearch +Test Binsearch in empty vector +Test igraph_vector_init_real +( 1 2 3 4 5 6 7 8 9 10 ) +Test igraph_vector_init_int +( 1 2 3 4 5 6 7 8 9 10 ) +Test igraph_vector_init_real +( 1 2 3 4 5 6 7 8 9 10 ) +Test igraph_vector_init_int +( 1 2 3 4 5 6 7 8 9 10 ) +Test filter_smaller, quite special.... +( 4 4 5 6 7 8 ) +( 1 2 3 4 4 4 4 5 6 7 8 ) +( 0 1 2 3 4 4 4 4 5 6 7 8 ) +Test igraph_i_vector_int_rank and igraph_i_vector_int_order +( 0 1 2 6 5 2 1 0 ) +( 1 3 5 7 6 4 2 0 ) +( 7 0 6 1 5 2 4 3 ) +Test pair order +( 0 1 2 3 ) +Test fill +Test range +Test igraph_vector_int_init_range, igraph_i_vector_int_order +( 9 8 7 6 5 4 3 2 1 0 ) diff --git a/tests/unit/vector2.c b/tests/unit/vector2.c new file mode 100644 index 0000000..d6c8310 --- /dev/null +++ b/tests/unit/vector2.c @@ -0,0 +1,161 @@ +/* + igraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + + igraph_vector_t v1, v2, v3, v4, v5; + igraph_real_t min, max; + igraph_int_t imin, imax; + int i; + + igraph_vector_init_range(&v1, 1, 11); + igraph_vector_init_range(&v2, 0, 10); + + igraph_vector_swap(&v1, &v2); + print_vector_format(&v1, stdout, "%g"); + print_vector_format(&v2, stdout, "%g"); + + igraph_vector_swap_elements(&v1, 0, 9); + igraph_vector_swap_elements(&v1, 3, 6); + print_vector_format(&v1, stdout, "%g"); + + igraph_vector_reverse(&v2); + print_vector_format(&v2, stdout, "%g"); + igraph_vector_reverse(&v2); + print_vector_format(&v2, stdout, "%g"); + + /* reverse an odd number of elements */ + igraph_vector_reverse_section(&v2, 3, 6); + print_vector_format(&v2, stdout, "%g"); + igraph_vector_reverse_section(&v2, 3, 6); + print_vector_format(&v2, stdout, "%g"); + + /* reverse an even number of elements */ + igraph_vector_reverse_section(&v2, 3, 7); + print_vector_format(&v2, stdout, "%g"); + igraph_vector_reverse_section(&v2, 3, 7); + print_vector_format(&v2, stdout, "%g"); + + igraph_vector_rotate_left(&v2, 3); + print_vector_format(&v2, stdout, "%g"); + igraph_vector_rotate_left(&v2, -3); + print_vector_format(&v2, stdout, "%g"); + + igraph_vector_destroy(&v1); + igraph_vector_destroy(&v2); + + igraph_vector_init(&v1, 10); + igraph_vector_init(&v2, 10); + igraph_vector_fill(&v1, 4); + igraph_vector_fill(&v2, 2); + + igraph_vector_add(&v1, &v2); + print_vector_format(&v1, stdout, "%g"); + igraph_vector_sub(&v1, &v2); + print_vector_format(&v1, stdout, "%g"); + igraph_vector_div(&v1, &v2); + print_vector_format(&v1, stdout, "%g"); + igraph_vector_mul(&v1, &v2); + print_vector_format(&v1, stdout, "%g"); + + igraph_vector_minmax(&v1, &min, &max); + igraph_vector_which_minmax(&v1, &imin, &imax); + printf("%g %g %" IGRAPH_PRId " %" IGRAPH_PRId "\n", min, max, imin, imax); + + igraph_vector_destroy(&v1); + igraph_vector_destroy(&v2); + + igraph_vector_init_range(&v1, 1, 11); + igraph_vector_init(&v2, 10); + for (i = 0; i < 10; i++) { + VECTOR(v2)[i] = 10 - i; + } + + igraph_vector_minmax(&v1, &min, &max); + igraph_vector_which_minmax(&v1, &imin, &imax); + printf("%g %g %" IGRAPH_PRId " %" IGRAPH_PRId "\n", min, max, imin, imax); + igraph_vector_minmax(&v2, &min, &max); + igraph_vector_which_minmax(&v2, &imin, &imax); + printf("%g %g %" IGRAPH_PRId " %" IGRAPH_PRId "\n", min, max, imin, imax); + + if (igraph_vector_isnull(&v1)) { + return 1; + } + igraph_vector_null(&v1); + if (!igraph_vector_isnull(&v1)) { + return 2; + } + + igraph_vector_destroy(&v1); + igraph_vector_destroy(&v2); + + igraph_vector_init_int(&v1, 10, 3, 5, 6, 6, 6, 7, 8, 8, 9, 10); + igraph_vector_init_int(&v2, 10, 1, 3, 3, 6, 6, 9, 12, 15, 17, 20); + igraph_vector_init(&v3, 0); + igraph_vector_init(&v4, 0); + igraph_vector_init(&v5, 0); + + igraph_vector_intersect_sorted(&v1, &v2, &v3); + print_vector_format(&v3, stdout, "%g"); + + igraph_vector_difference_sorted(&v1, &v2, &v3); + print_vector_format(&v3, stdout, "%g"); + igraph_vector_difference_sorted(&v2, &v1, &v3); + print_vector_format(&v3, stdout, "%g"); + igraph_vector_difference_sorted(&v2, &v2, &v3); + print_vector_format(&v3, stdout, "%g"); + + igraph_vector_difference_and_intersection_sorted(&v1, &v2, &v3, &v4, &v5); + print_vector_format(&v3, stdout, "%g"); + print_vector_format(&v4, stdout, "%g"); + print_vector_format(&v5, stdout, "%g"); + + igraph_vector_destroy(&v1); + igraph_vector_destroy(&v2); + igraph_vector_destroy(&v3); + igraph_vector_destroy(&v4); + igraph_vector_destroy(&v5); + + igraph_vector_init_range(&v1, 0, 50); + igraph_vector_init_range(&v2, 20, 23); + igraph_vector_init(&v3, 0); + + igraph_vector_intersect_sorted(&v1, &v2, &v3); + print_vector_format(&v3, stdout, "%g"); + igraph_vector_difference_sorted(&v1, &v2, &v3); + print_vector_format(&v3, stdout, "%g"); + igraph_vector_difference_sorted(&v2, &v1, &v3); + print_vector_format(&v3, stdout, "%g"); + + igraph_vector_destroy(&v1); + igraph_vector_destroy(&v2); + igraph_vector_destroy(&v3); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector2.out b/tests/unit/vector2.out new file mode 100644 index 0000000..2b2bb47 --- /dev/null +++ b/tests/unit/vector2.out @@ -0,0 +1,28 @@ +( 0 1 2 3 4 5 6 7 8 9 ) +( 1 2 3 4 5 6 7 8 9 10 ) +( 9 1 2 6 4 5 3 7 8 0 ) +( 10 9 8 7 6 5 4 3 2 1 ) +( 1 2 3 4 5 6 7 8 9 10 ) +( 1 2 3 6 5 4 7 8 9 10 ) +( 1 2 3 4 5 6 7 8 9 10 ) +( 1 2 3 7 6 5 4 8 9 10 ) +( 1 2 3 4 5 6 7 8 9 10 ) +( 4 5 6 7 8 9 10 1 2 3 ) +( 1 2 3 4 5 6 7 8 9 10 ) +( 6 6 6 6 6 6 6 6 6 6 ) +( 4 4 4 4 4 4 4 4 4 4 ) +( 2 2 2 2 2 2 2 2 2 2 ) +( 4 4 4 4 4 4 4 4 4 4 ) +4 4 0 0 +1 10 0 9 +1 10 9 0 +( 3 6 6 9 ) +( 5 6 7 8 8 10 ) +( 1 3 12 15 17 20 ) +( ) +( 5 6 7 8 8 10 ) +( 1 3 12 15 17 20 ) +( 3 6 6 9 ) +( 20 21 22 ) +( 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ) +( ) diff --git a/tests/unit/vector3.c b/tests/unit/vector3.c new file mode 100644 index 0000000..7482615 --- /dev/null +++ b/tests/unit/vector3.c @@ -0,0 +1,52 @@ +/* + igraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard st, Cambridge MA, USA 02139 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +#include "test_utilities.h" + +int main(void) { + igraph_vector_t v; + + igraph_vector_init_range(&v, 1, 1001); + IGRAPH_ASSERT(igraph_vector_capacity(&v) == 1000); + + igraph_vector_push_back(&v, 1001); + IGRAPH_ASSERT(igraph_vector_capacity(&v) == 2000); + + igraph_vector_resize_min(&v); + IGRAPH_ASSERT(igraph_vector_capacity(&v) == igraph_vector_size(&v)); + + igraph_vector_destroy(&v); + + /* regression test for #1479 -- calling resize_min() on an empty vector */ + igraph_vector_init_range(&v, 1, 1001); + igraph_vector_clear(&v); + igraph_vector_resize_min(&v); + IGRAPH_ASSERT(igraph_vector_capacity(&v) == 0); + IGRAPH_ASSERT(igraph_vector_size(&v) == 0); + igraph_vector_destroy(&v); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector4.c b/tests/unit/vector4.c new file mode 100644 index 0000000..efde896 --- /dev/null +++ b/tests/unit/vector4.c @@ -0,0 +1,96 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_vector_t v; + igraph_vector_t v2; + igraph_bool_t result_bool; + igraph_real_t nan[3] = {1, IGRAPH_NAN, 2}; + igraph_real_t basic[3] = {1, 5, 2}; + igraph_real_t basic_small[3] = {0, -5, -2}; + + printf("Checking if vector is equal to itself:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(basic, 3); + result_bool = igraph_vector_is_equal(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if vector is equal to other vector:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(nan, 3); + result_bool = igraph_vector_is_equal(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are less than its own elements:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(basic, 3); + result_bool = igraph_vector_all_l(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are less than vector with nan:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(nan, 3); + result_bool = igraph_vector_all_l(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are less than vector with smaller elements:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(basic_small, 3); + result_bool = igraph_vector_all_l(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are less than vector with larger elements:\n"); + v = igraph_vector_view(basic_small, 3); + v2 = igraph_vector_view(basic, 3); + result_bool = igraph_vector_all_l(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are greater than its own elements:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(basic, 3); + result_bool = igraph_vector_all_g(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are greater than vector with nan:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(nan, 3); + result_bool = igraph_vector_all_g(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are greater than vector with smaller elements:\n"); + v = igraph_vector_view(basic, 3); + v2 = igraph_vector_view(basic_small, 3); + result_bool = igraph_vector_all_g(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking if all elements of vector are greater than vector with larger elements:\n"); + v = igraph_vector_view(basic_small, 3); + v2 = igraph_vector_view(basic, 3); + result_bool = igraph_vector_all_g(&v, &v2); + printf("%d\n", result_bool); + + printf("Checking vector_printf without nans:\n"); + v = igraph_vector_view(basic, 3); + igraph_vector_printf(&v, "--%4g--"); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/vector4.out b/tests/unit/vector4.out new file mode 100644 index 0000000..ab97536 --- /dev/null +++ b/tests/unit/vector4.out @@ -0,0 +1,22 @@ +Checking if vector is equal to itself: +1 +Checking if vector is equal to other vector: +0 +Checking if all elements of vector are less than its own elements: +0 +Checking if all elements of vector are less than vector with nan: +0 +Checking if all elements of vector are less than vector with smaller elements: +0 +Checking if all elements of vector are less than vector with larger elements: +1 +Checking if all elements of vector are greater than its own elements: +0 +Checking if all elements of vector are greater than vector with nan: +0 +Checking if all elements of vector are greater than vector with smaller elements: +1 +Checking if all elements of vector are greater than vector with larger elements: +0 +Checking vector_printf without nans: +-- 1-- -- 5-- -- 2-- diff --git a/tests/unit/vector_list.c b/tests/unit/vector_list.c new file mode 100644 index 0000000..b99569b --- /dev/null +++ b/tests/unit/vector_list.c @@ -0,0 +1,227 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_vector_int_list_t list, list2; + igraph_vector_int_t v; + igraph_vector_int_t* v_ptr; + igraph_int_t i; + + printf("Initialise empty vector list\n"); + igraph_vector_int_list_init(&list, 0); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Initialise vector list of length 10\n"); + igraph_vector_int_list_init(&list, 10); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_get_ptr and igraph_vector_int_list_size\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < igraph_vector_int_list_size(&list); i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), 10 - i); + } + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_reserve and igraph_vector_int_list_push_back_new\n"); + igraph_vector_int_list_init(&list, 0); + igraph_vector_int_list_reserve(&list, 10); + for (i = 0; i < 10; i++) { + igraph_vector_int_list_push_back_new(&list, /* item = */ 0); + } + + printf("Test igraph_vector_int_list_empty and igraph_vector_int_list_clear\n"); + IGRAPH_ASSERT(!igraph_vector_int_list_empty(&list)); + igraph_vector_int_list_clear(&list); + IGRAPH_ASSERT(igraph_vector_int_list_empty(&list)); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_list_set\n"); + igraph_vector_int_list_init(&list, 5); + for (i = 0; i < igraph_vector_int_list_size(&list); i++) { + igraph_vector_int_init(&v, 1); + VECTOR(v)[0] = 20 * i; + igraph_vector_int_list_set(&list, i, &v); /* ownership taken */ + } + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_tail_ptr, igraph_vector_int_list_pop_back\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < igraph_vector_int_list_size(&list); i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + while (!igraph_vector_int_list_empty(&list)) { + print_vector_int(igraph_vector_int_list_tail_ptr(&list)); + v = igraph_vector_int_list_pop_back(&list); + /* v is now owned by us, not the vector_int_list */ + print_vector_int(&v); + igraph_vector_int_destroy(&v); + } + printf("\n"); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_resize, igraph_vector_int_list_sort\n"); + igraph_vector_int_list_init(&list, 20); + for (i = 0; i < 10; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), 10 - i); + } + igraph_vector_int_list_resize(&list, 10); + igraph_vector_int_list_sort(&list, igraph_vector_int_lex_cmp); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_remove\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < 10; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + igraph_vector_int_list_remove(&list, 9, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + igraph_vector_int_list_remove(&list, 0, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + igraph_vector_int_list_remove(&list, 4, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_remove_fast\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < 10; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + igraph_vector_int_list_remove_fast(&list, 9, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + igraph_vector_int_list_remove_fast(&list, 0, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + igraph_vector_int_list_remove_fast(&list, 4, /* item = */ &v); + print_vector_int(&v); /* v is now owned by us */ + igraph_vector_int_destroy(&v); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_discard\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < 10; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + igraph_vector_int_list_discard(&list, 9); + igraph_vector_int_list_discard(&list, 0); + igraph_vector_int_list_discard(&list, 4); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_discard_fast\n"); + igraph_vector_int_list_init(&list, 10); + for (i = 0; i < 10; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + igraph_vector_int_list_discard_fast(&list, 9); + igraph_vector_int_list_discard_fast(&list, 0); + igraph_vector_int_list_discard_fast(&list, 4); + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_swap and igraph_vector_int_list_swap_elements\n"); + igraph_vector_int_list_init(&list, 5); + igraph_vector_int_list_init(&list2, 10); + for (i = 0; i < igraph_vector_int_list_size(&list); i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), 20 * i); + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list2, i * 2), i); + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list2, i * 2 + 1), 2 * i); + } + igraph_vector_int_list_swap(&list, &list2); + igraph_vector_int_list_swap_elements(&list, 9, 8); + igraph_vector_int_list_swap_elements(&list, 3, 6); + print_vector_int_list(&list); + print_vector_int_list(&list2); + igraph_vector_int_list_destroy(&list); + igraph_vector_int_list_destroy(&list2); + + printf("Test igraph_vector_int_list_replace\n"); + igraph_vector_int_list_init(&list, 3); + for (i = 0; i < 3; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + igraph_vector_int_init(&v, 3); + VECTOR(v)[0] = 77; VECTOR(v)[1] = 42; VECTOR(v)[2] = 317; + igraph_vector_int_list_replace(&list, 1, &v); + print_vector_int_list(&list); + print_vector_int(&v); + igraph_vector_int_destroy(&v); + igraph_vector_int_list_destroy(&list); + + printf("Test igraph_vector_int_list_insert, igraph_vector_int_list_insert_new and igraph_vector_int_list_insert_copy\n"); + igraph_vector_int_list_init(&list, 3); + for (i = 0; i < 3; i++) { + igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&list, i), i + 1); + } + + igraph_vector_int_list_insert_new(&list, 2, &v_ptr); + igraph_vector_int_push_back(v_ptr, 13); + igraph_vector_int_push_back(v_ptr, 61); + + igraph_vector_int_init(&v, 3); + VECTOR(v)[0] = 77; VECTOR(v)[1] = 42; VECTOR(v)[2] = 317; + igraph_vector_int_list_insert(&list, 1, &v); + + igraph_vector_int_init(&v, 2); + VECTOR(v)[0] = 11; VECTOR(v)[1] = -3; + igraph_vector_int_list_insert_copy(&list, 1, &v); + igraph_vector_int_push_back(&v, -51); + igraph_vector_int_destroy(&v); + + print_vector_int_list(&list); + igraph_vector_int_list_destroy(&list); + + printf("Test errors\n"); + igraph_set_error_handler(igraph_error_handler_ignore); + igraph_vector_int_list_init(&list, 10); + IGRAPH_ASSERT( + igraph_vector_int_list_remove(&list, 17, /* item = */ &v) == IGRAPH_EINVAL + ); + IGRAPH_ASSERT( + igraph_vector_int_list_remove(&list, -2, /* item = */ &v) == IGRAPH_EINVAL + ); + IGRAPH_ASSERT( + igraph_vector_int_list_remove_fast(&list, 17, /* item = */ &v) == IGRAPH_EINVAL + ); + IGRAPH_ASSERT( + igraph_vector_int_list_remove_fast(&list, -2, /* item = */ &v) == IGRAPH_EINVAL + ); + igraph_vector_int_list_destroy(&list); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector_list.out b/tests/unit/vector_list.out new file mode 100644 index 0000000..c08eb00 --- /dev/null +++ b/tests/unit/vector_list.out @@ -0,0 +1,157 @@ +Initialise empty vector list +{ +} +Initialise vector list of length 10 +{ + 0: ( ) + 1: ( ) + 2: ( ) + 3: ( ) + 4: ( ) + 5: ( ) + 6: ( ) + 7: ( ) + 8: ( ) + 9: ( ) +} +Test igraph_vector_int_list_get_ptr and igraph_vector_int_list_size +{ + 0: ( 10 ) + 1: ( 9 ) + 2: ( 8 ) + 3: ( 7 ) + 4: ( 6 ) + 5: ( 5 ) + 6: ( 4 ) + 7: ( 3 ) + 8: ( 2 ) + 9: ( 1 ) +} +Test igraph_vector_int_list_reserve and igraph_vector_int_list_push_back_new +Test igraph_vector_int_list_empty and igraph_vector_int_list_clear +Test igraph_vector_list_set +{ + 0: ( 0 ) + 1: ( 20 ) + 2: ( 40 ) + 3: ( 60 ) + 4: ( 80 ) +} +Test igraph_vector_int_list_tail_ptr, igraph_vector_int_list_pop_back +( 10 ) +( 10 ) +( 9 ) +( 9 ) +( 8 ) +( 8 ) +( 7 ) +( 7 ) +( 6 ) +( 6 ) +( 5 ) +( 5 ) +( 4 ) +( 4 ) +( 3 ) +( 3 ) +( 2 ) +( 2 ) +( 1 ) +( 1 ) + +Test igraph_vector_int_list_resize, igraph_vector_int_list_sort +{ + 0: ( 1 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 4 ) + 4: ( 5 ) + 5: ( 6 ) + 6: ( 7 ) + 7: ( 8 ) + 8: ( 9 ) + 9: ( 10 ) +} +Test igraph_vector_int_list_remove +( 10 ) +( 1 ) +( 6 ) +{ + 0: ( 2 ) + 1: ( 3 ) + 2: ( 4 ) + 3: ( 5 ) + 4: ( 7 ) + 5: ( 8 ) + 6: ( 9 ) +} +Test igraph_vector_int_list_remove_fast +( 10 ) +( 1 ) +( 5 ) +{ + 0: ( 9 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 4 ) + 4: ( 8 ) + 5: ( 6 ) + 6: ( 7 ) +} +Test igraph_vector_int_list_discard +{ + 0: ( 2 ) + 1: ( 3 ) + 2: ( 4 ) + 3: ( 5 ) + 4: ( 7 ) + 5: ( 8 ) + 6: ( 9 ) +} +Test igraph_vector_int_list_discard_fast +{ + 0: ( 9 ) + 1: ( 2 ) + 2: ( 3 ) + 3: ( 4 ) + 4: ( 8 ) + 5: ( 6 ) + 6: ( 7 ) +} +Test igraph_vector_int_list_swap and igraph_vector_int_list_swap_elements +{ + 0: ( 0 ) + 1: ( 0 ) + 2: ( 1 ) + 3: ( 3 ) + 4: ( 2 ) + 5: ( 4 ) + 6: ( 2 ) + 7: ( 6 ) + 8: ( 8 ) + 9: ( 4 ) +} +{ + 0: ( 0 ) + 1: ( 20 ) + 2: ( 40 ) + 3: ( 60 ) + 4: ( 80 ) +} +Test igraph_vector_int_list_replace +{ + 0: ( 1 ) + 1: ( 77 42 317 ) + 2: ( 3 ) +} +( 2 ) +Test igraph_vector_int_list_insert, igraph_vector_int_list_insert_new and igraph_vector_int_list_insert_copy +{ + 0: ( 1 ) + 1: ( 11 -3 ) + 2: ( 77 42 317 ) + 3: ( 2 ) + 4: ( 13 61 ) + 5: ( 3 ) +} +Test errors diff --git a/tests/unit/vector_ptr.c b/tests/unit/vector_ptr.c new file mode 100644 index 0000000..0d954d3 --- /dev/null +++ b/tests/unit/vector_ptr.c @@ -0,0 +1,284 @@ +/* + igraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +igraph_vector_ptr_t custom_destructor_stack; + +void custom_destructor(void* ptr) { + igraph_vector_ptr_push_back(&custom_destructor_stack, ptr); +} + +int main(void) { + + igraph_vector_ptr_t v1, v2; + igraph_vector_ptr_t v3 = IGRAPH_VECTOR_PTR_NULL; + int i; + void ** ptr; + int d1 = 1, d2 = 2, d3 = 3, d4 = 4, d5 = 5; + char *block1 = 0, *block2 = 0; + + /* igraph_vector_ptr_init, igraph_vector_ptr_destroy */ + igraph_vector_ptr_init(&v1, 10); + igraph_vector_ptr_destroy(&v1); + igraph_vector_ptr_init(&v1, 0); + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_free_all, igraph_vector_ptr_destroy_all */ + igraph_vector_ptr_init(&v1, 5); + for (i = 0; i < igraph_vector_ptr_size(&v1); i++) { + VECTOR(v1)[i] = (void*)malloc(i * 10); + } + igraph_vector_ptr_free_all(&v1); + for (i = 0; i < igraph_vector_ptr_size(&v1); i++) { + VECTOR(v1)[i] = (void*)malloc(i * 10); + } + igraph_vector_ptr_destroy_all(&v1); + + /* igraph_vector_ptr_reserve */ + igraph_vector_ptr_init(&v1, 0); + igraph_vector_ptr_reserve(&v1, 5); + igraph_vector_ptr_reserve(&v1, 15); + igraph_vector_ptr_reserve(&v1, 1); + igraph_vector_ptr_reserve(&v1, 0); + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_empty, igraph_vector_ptr_clear */ + igraph_vector_ptr_init(&v1, 10); + if (igraph_vector_ptr_empty(&v1)) { + return 1; + } + igraph_vector_ptr_clear(&v1); + if (!igraph_vector_ptr_empty(&v1)) { + return 2; + } + + /* igraph_vector_ptr_size */ + if (igraph_vector_ptr_size(&v1) != 0) { + return 3; + } + igraph_vector_ptr_resize(&v1, 10); + if (igraph_vector_ptr_size(&v1) != 10) { + return 4; + } + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_push_back */ + igraph_vector_ptr_init(&v1, 0); + for (i = 0; i < 10; i++) { + igraph_vector_ptr_push_back(&v1, (void*)malloc(i * 10)); + } + igraph_vector_ptr_destroy_all(&v1); + + /* igraph_vector_ptr_get */ + igraph_vector_ptr_init(&v1, 5); + VECTOR(v1)[0] = &d1; + VECTOR(v1)[1] = &d2; + VECTOR(v1)[2] = &d3; + VECTOR(v1)[3] = &d4; + VECTOR(v1)[4] = &d5; + if (igraph_vector_ptr_get(&v1, 0) != &d1) { + return 5; + } + if (igraph_vector_ptr_get(&v1, 1) != &d2) { + return 6; + } + if (igraph_vector_ptr_get(&v1, 2) != &d3) { + return 7; + } + if (igraph_vector_ptr_get(&v1, 3) != &d4) { + return 8; + } + if (igraph_vector_ptr_get(&v1, 4) != &d5) { + return 9; + } + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_set */ + igraph_vector_ptr_init(&v1, 5); + igraph_vector_ptr_set(&v1, 0, &d1); + igraph_vector_ptr_set(&v1, 1, &d2); + igraph_vector_ptr_set(&v1, 2, &d3); + igraph_vector_ptr_set(&v1, 3, &d4); + igraph_vector_ptr_set(&v1, 4, &d5); + if (igraph_vector_ptr_get(&v1, 0) != &d1) { + return 5; + } + if (igraph_vector_ptr_get(&v1, 1) != &d2) { + return 6; + } + if (igraph_vector_ptr_get(&v1, 2) != &d3) { + return 7; + } + if (igraph_vector_ptr_get(&v1, 3) != &d4) { + return 8; + } + if (igraph_vector_ptr_get(&v1, 4) != &d5) { + return 9; + } + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_null */ + igraph_vector_ptr_init(&v1, 5); + igraph_vector_ptr_set(&v1, 0, &d1); + igraph_vector_ptr_set(&v1, 1, &d2); + igraph_vector_ptr_set(&v1, 2, &d3); + igraph_vector_ptr_set(&v1, 3, &d4); + igraph_vector_ptr_set(&v1, 4, &d5); + igraph_vector_ptr_null(&v1); + for (i = 0; i < igraph_vector_ptr_size(&v1); i++) { + if (VECTOR(v1)[i] != 0) { + return 10; + } + } + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_resize */ + igraph_vector_ptr_init(&v1, 10); + igraph_vector_ptr_set(&v1, 0, &d1); + igraph_vector_ptr_set(&v1, 1, &d2); + igraph_vector_ptr_set(&v1, 2, &d3); + igraph_vector_ptr_set(&v1, 3, &d4); + igraph_vector_ptr_set(&v1, 4, &d5); + igraph_vector_ptr_resize(&v1, 10); + igraph_vector_ptr_resize(&v1, 15); + igraph_vector_ptr_resize(&v1, 5); + if (igraph_vector_ptr_size(&v1) != 5) { + return 11; + } + if (igraph_vector_ptr_get(&v1, 0) != &d1) { + return 12; + } + if (igraph_vector_ptr_get(&v1, 1) != &d2) { + return 13; + } + if (igraph_vector_ptr_get(&v1, 2) != &d3) { + return 14; + } + if (igraph_vector_ptr_get(&v1, 3) != &d4) { + return 15; + } + if (igraph_vector_ptr_get(&v1, 4) != &d5) { + return 16; + } + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_view */ + ptr = (void**) malloc(5 * sizeof(void*)); + v3 = igraph_vector_ptr_view(ptr, 5); + ptr[0] = &d1; + ptr[1] = &d2; + ptr[2] = &d3; + ptr[3] = &d4; + ptr[4] = &d5; + for (i = 0; i < igraph_vector_ptr_size(&v3); i++) { + if ( *((int*)VECTOR(v3)[i]) != i + 1) { + return 17; + } + } + + /* igraph_vector_ptr_init_array */ + igraph_vector_ptr_init_array(&v1, ptr, 5); + for (i = 0; i < igraph_vector_ptr_size(&v1); i++) { + if ( *((int*)VECTOR(v1)[i]) != i + 1) { + return 18; + } + } + + /* igraph_vector_ptr_copy_to */ + igraph_vector_ptr_copy_to(&v1, ptr); + for (i = 0; i < igraph_vector_ptr_size(&v1); i++) { + if ( *((int*)ptr[i]) != i + 1) { + return 19; + } + } + free(ptr); + igraph_vector_ptr_destroy(&v1); + + /* igraph_vector_ptr_copy */ + igraph_vector_ptr_init(&v1, 5); + igraph_vector_ptr_set(&v1, 0, &d1); + igraph_vector_ptr_set(&v1, 1, &d2); + igraph_vector_ptr_set(&v1, 2, &d3); + igraph_vector_ptr_set(&v1, 3, &d4); + igraph_vector_ptr_set(&v1, 4, &d5); + igraph_vector_ptr_init_copy(&v2, &v1); + igraph_vector_ptr_destroy(&v1); + for (i = 0; i < igraph_vector_ptr_size(&v2); i++) { + if ( *((int*)VECTOR(v2)[i]) != i + 1) { + return 20; + } + } + + /* igraph_vector_ptr_remove */ + igraph_vector_ptr_remove(&v2, 0); + igraph_vector_ptr_remove(&v2, 3); + if ( *((int*)VECTOR(v2)[0]) != 2) { + return 21; + } + if ( *((int*)VECTOR(v2)[1]) != 3) { + return 22; + } + if ( *((int*)VECTOR(v2)[2]) != 4) { + return 23; + } + + igraph_vector_ptr_destroy(&v2); + + /* Testing destructor */ + igraph_vector_ptr_init(&custom_destructor_stack, 0); + igraph_vector_ptr_init(&v1, 2); + block1 = IGRAPH_CALLOC(32, char); + block2 = IGRAPH_CALLOC(64, char); + VECTOR(v1)[0] = block1; + VECTOR(v1)[1] = block2; + if (igraph_vector_ptr_get_item_destructor(&v1) != 0) { + return 24; + } + if (igraph_vector_ptr_set_item_destructor(&v1, &custom_destructor) != 0) { + return 25; + } + /* Okay, let's clear the vector. This should push the blocks in the + * custom destructor stack */ + igraph_vector_ptr_clear(&v1); + /* Put the blocks back and destroy the vector */ + igraph_vector_ptr_push_back(&v1, block1); + igraph_vector_ptr_push_back(&v1, block2); + igraph_vector_ptr_destroy_all(&v1); + + if (VECTOR(custom_destructor_stack)[0] != block1 || + VECTOR(custom_destructor_stack)[1] != block2 || + VECTOR(custom_destructor_stack)[2] != block1 || + VECTOR(custom_destructor_stack)[3] != block2 + ) { + return 26; + } + + igraph_vector_ptr_destroy(&custom_destructor_stack); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector_ptr_qsort_ind.out b/tests/unit/vector_ptr_qsort_ind.out new file mode 100644 index 0000000..013a691 --- /dev/null +++ b/tests/unit/vector_ptr_qsort_ind.out @@ -0,0 +1,12 @@ +( ) +( 2 8 6 0 9 1 5 3 7 4 ) +( 0 ) +( 1 ) +( 2 ) +( 3 ) +( 4 ) +( 5 ) +( 6 ) +( 7 ) +( 8 ) +( 9 ) diff --git a/tests/unit/vector_ptr_sort_ind.c b/tests/unit/vector_ptr_sort_ind.c new file mode 100644 index 0000000..3018c22 --- /dev/null +++ b/tests/unit/vector_ptr_sort_ind.c @@ -0,0 +1,80 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +static int compare_first_items(const void* a, const void* b) { + igraph_vector_t *vec1 = (igraph_vector_t*) a; + igraph_vector_t *vec2 = (igraph_vector_t*) b; + + return VECTOR(*vec1)[0] - VECTOR(*vec2)[0]; +} + +int main(void) { + igraph_vector_ptr_t vectors; + igraph_vector_t* vec; + igraph_vector_int_t indices; + int values[] = { 3, 5, 0, 7, 9, 6, 2, 8, 1, 4, -1 }; + int i, *ptr; + + /* Special case: empty vector */ + igraph_vector_ptr_init(&vectors, 0); + igraph_vector_int_init(&indices, 0); + igraph_vector_ptr_sort_ind(&vectors, &indices, compare_first_items); + print_vector_int(&indices); + igraph_vector_int_destroy(&indices); + igraph_vector_ptr_destroy_all(&vectors); + + /* Create vectors of length 1, each containing a value from 'values', and + * put them in a vector of pointers */ + igraph_vector_ptr_init(&vectors, 0); + for (ptr = values; *ptr >= 0; ptr++) { + vec = IGRAPH_CALLOC(1, igraph_vector_t); + igraph_vector_init(vec, 1); + VECTOR(*vec)[0] = *ptr; + igraph_vector_ptr_push_back(&vectors, vec); + } + + /* Sort the vector of vectors by the first item of each vector, and get + * the index vector */ + igraph_vector_int_init(&indices, 0); + igraph_vector_ptr_sort_ind(&vectors, &indices, compare_first_items); + print_vector_int(&indices); + + /* Permute the vector of vectors by the index vector */ + igraph_vector_ptr_permute(&vectors, &indices); + + /* Print and clean up */ + for (i = 0; i < igraph_vector_ptr_size(&vectors); i++) { + print_vector(VECTOR(vectors)[i]); + igraph_vector_destroy(VECTOR(vectors)[i]); + } + igraph_vector_ptr_destroy_all(&vectors); + igraph_vector_int_destroy(&indices); + + /* Check finalizer stack */ + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector_sort_ind.c b/tests/unit/vector_sort_ind.c new file mode 100644 index 0000000..bb2bb2a --- /dev/null +++ b/tests/unit/vector_sort_ind.c @@ -0,0 +1,63 @@ +/* + igraph library. + Copyright (C) 2006-2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include + +#include "test_utilities.h" + +int main(void) { + igraph_vector_t vector; + igraph_vector_int_t indices; + igraph_real_t values[] = { 87, 23, 8, 82, 94, 56, 36, 33, 76, 66 }; + igraph_real_t values2[] = { 87, 23, 8, 82, 94, 56, 36, 33, 76, 66 }; + + /* Special case: empty vector */ + igraph_vector_init(&vector, 0); + igraph_vector_int_init(&indices, 0); + igraph_vector_sort_ind(&vector, &indices, IGRAPH_ASCENDING); + print_vector_int(&indices); + igraph_vector_int_destroy(&indices); + igraph_vector_destroy(&vector); + + /* Non-empty vector, descending */ + vector = igraph_vector_view(values, sizeof(values) / sizeof(values[0])); + igraph_vector_int_init(&indices, 0); + igraph_vector_sort_ind(&vector, &indices, IGRAPH_DESCENDING); + print_vector_int(&indices); + + /* Non-empty vector, ascending */ + vector = igraph_vector_view(values2, sizeof(values2) / sizeof(values2[0])); + igraph_vector_sort_ind(&vector, &indices, IGRAPH_ASCENDING); + print_vector_int(&indices); + + /* Permute the vector by the index vector */ + igraph_vector_permute(&vector, &indices); + print_vector(&vector); + + /* Print and clean up */ + igraph_vector_int_destroy(&indices); + + /* Check finalizer stack */ + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/vector_sort_ind.out b/tests/unit/vector_sort_ind.out new file mode 100644 index 0000000..9ca5dc0 --- /dev/null +++ b/tests/unit/vector_sort_ind.out @@ -0,0 +1,4 @@ +( ) +( 4 0 3 8 9 5 6 7 1 2 ) +( 2 1 7 6 5 9 8 3 0 4 ) +( 8 23 33 36 56 66 76 82 87 94 ) diff --git a/tests/unit/vertex_selectors.c b/tests/unit/vertex_selectors.c new file mode 100644 index 0000000..f9ffd12 --- /dev/null +++ b/tests/unit/vertex_selectors.c @@ -0,0 +1,116 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +void check(igraph_t *graph, igraph_vs_t *vs) { + igraph_vit_t vit; + + IGRAPH_ASSERT(igraph_vit_create(graph, *vs, &vit) == IGRAPH_SUCCESS); + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + printf("%" IGRAPH_PRId "\n", IGRAPH_VIT_GET(vit)); + } +} + +int main(void) { + igraph_t g, g_no_vertices, g_no_edges; + igraph_vs_t vs, vs_copy; + igraph_vector_int_t v; + igraph_vit_t vit; + + igraph_small(&g, 5, IGRAPH_DIRECTED, 0,1, 0,2, 1,1, 1,3, 2,0, 2,3, 3,4, -1); + igraph_small(&g_no_vertices, 0, IGRAPH_UNDIRECTED, -1); + igraph_small(&g_no_edges, 5, IGRAPH_UNDIRECTED, -1); + + printf("Checking vs_none vertex selector:\n"); + IGRAPH_ASSERT(igraph_vs_none(&vs) == IGRAPH_SUCCESS); + check(&g, &vs); + check(&g_no_edges, &vs); + check(&g_no_vertices, &vs); + + igraph_set_error_handler(igraph_error_handler_ignore); + + printf("Checking vector selector:\n"); + igraph_vector_int_init_int(&v, 3, 2, 3, 4); + IGRAPH_ASSERT(igraph_vs_vector(&vs, &v) == IGRAPH_SUCCESS); + printf("Some graph:\n"); + check(&g, &vs); + printf("Edgeless graph:\n"); + check(&g_no_edges, &vs); + printf("Graph without vertices should fail.\n"); + IGRAPH_ASSERT(igraph_vit_create(&g_no_vertices, vs, &vit) == IGRAPH_EINVVID); + igraph_vector_int_destroy(&v); + + printf("Vertex selector with negative index should fail\n"); + igraph_vector_int_init_int(&v, 3, -2, 3, 4); + IGRAPH_ASSERT(igraph_vs_vector(&vs, &v) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_vit_create(&g, vs, &vit) == IGRAPH_EINVVID); + igraph_vector_int_destroy(&v); + + printf("Checking copy vector selector:\n"); + igraph_vector_int_init_int(&v, 3, 2, 3, 4); + IGRAPH_ASSERT(igraph_vs_vector_copy(&vs, &v) == IGRAPH_SUCCESS); + printf("Some graph:\n"); + check(&g, &vs); + printf("Edgeless graph:\n"); + check(&g_no_edges, &vs); + printf("Copy of vs:\n"); + igraph_vs_copy(&vs_copy, &vs); + check(&g_no_edges, &vs_copy); + igraph_vs_destroy(&vs_copy); + + printf("Graph without vertices should fail.\n"); + IGRAPH_ASSERT(igraph_vit_create(&g_no_vertices, vs, &vit) == IGRAPH_EINVVID); + IGRAPH_ASSERT(igraph_vs_type(&vs) == IGRAPH_VS_VECTOR); + igraph_vs_destroy(&vs); + + printf("As vector should give all 5 vertices:\n"); + igraph_vs_all(&vs); + igraph_vs_as_vector(&g, vs, &v); + igraph_vector_int_print(&v); + + printf("As vector should give 0 vertices:\n"); + igraph_vs_as_vector(&g_no_vertices, vs, &v); + igraph_vector_int_print(&v); + + printf("Checking vs_range:\n"); + igraph_vs_range(&vs, 2, 5); + check(&g, &vs); + CHECK_ERROR(igraph_vit_create(&g_no_vertices, vs, &vit), IGRAPH_EINVAL); + + printf("Checking vss_range using vs_range parameters:\n"); + vs = igraph_vss_range(2, 5); + check(&g, &vs); + CHECK_ERROR(igraph_vit_create(&g_no_vertices, vs, &vit), IGRAPH_EINVAL); + + printf("Checking whether vss_range accepts an empty range.\n"); + vs = igraph_vss_range(2, 2); + check(&g, &vs); + CHECK_ERROR(igraph_vit_create(&g_no_vertices, vs, &vit), IGRAPH_EINVAL); + + igraph_destroy(&g); + igraph_destroy(&g_no_vertices); + igraph_destroy(&g_no_edges); + + igraph_vector_int_destroy(&v); + igraph_vs_destroy(&vs); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tests/unit/vertex_selectors.out b/tests/unit/vertex_selectors.out new file mode 100644 index 0000000..e78c825 --- /dev/null +++ b/tests/unit/vertex_selectors.out @@ -0,0 +1,39 @@ +Checking vs_none vertex selector: +Checking vector selector: +Some graph: +2 +3 +4 +Edgeless graph: +2 +3 +4 +Graph without vertices should fail. +Vertex selector with negative index should fail +Checking copy vector selector: +Some graph: +2 +3 +4 +Edgeless graph: +2 +3 +4 +Copy of vs: +2 +3 +4 +Graph without vertices should fail. +As vector should give all 5 vertices: +0 1 2 3 4 +As vector should give 0 vertices: + +Checking vs_range: +2 +3 +4 +Checking vss_range using vs_range parameters: +2 +3 +4 +Checking whether vss_range accepts an empty range. diff --git a/tests/unit/watts_strogatz_game.c b/tests/unit/watts_strogatz_game.c new file mode 100644 index 0000000..2149047 --- /dev/null +++ b/tests/unit/watts_strogatz_game.c @@ -0,0 +1,125 @@ +/* + igraph library. + Copyright (C) 2011-2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "test_utilities.h" + +#define N 1000 + +igraph_bool_t has_loops(const igraph_t *graph) { + igraph_bool_t res; + igraph_has_loop(graph, &res); + return res; +} + +igraph_bool_t has_multiple(const igraph_t *graph) { + igraph_bool_t res; + igraph_has_multiple(graph, &res); + return res; +} + +#define ERR() do { \ + printf("Seed: %" IGRAPH_PRIu "\n", seed); \ + print_graph(&ws); \ + } while (0) + +#define SEED() do { \ + seed=igraph_rng_get_integer(igraph_rng_default(), 1, 10000); \ + igraph_rng_seed(igraph_rng_default(), seed); \ + } while (0) + +int main(void) { + + igraph_t ws; + igraph_bool_t sim, seen_loops, seen_multiple; + igraph_int_t i; + igraph_uint_t seed = 1305473657; + + igraph_rng_seed(igraph_rng_default(), seed); + + /* No loops, no multiple edges */ + for (i = 0; i < N; i++) { + SEED(); + igraph_watts_strogatz_game(&ws, /*dim=*/ 1, /*size=*/ 5, /*nei=*/ 1, + /*p=*/ 0.5, IGRAPH_SIMPLE_SW); + igraph_is_simple(&ws, &sim, IGRAPH_DIRECTED); + if (!sim) { + ERR(); + return 1; + } + if (has_loops(&ws)) { + ERR(); + return 1; + } + if (has_multiple(&ws)) { + ERR(); + return 2; + } + igraph_destroy(&ws); + } + + /* No loops, multiple edges possible */ + seen_multiple = 0; + for (i = 0; i < N; i++) { + SEED(); + igraph_watts_strogatz_game(&ws, /*dim=*/ 1, /*size=*/ 5, /*nei=*/ 1, + /*p=*/ 0.5, IGRAPH_MULTI_SW); + if (has_loops(&ws)) { + ERR(); + return 3; + } + seen_multiple = seen_multiple || has_multiple(&ws); + igraph_destroy(&ws); + } + /* This might actually happen */ + /* if (!seen_multiple) { return 4; } */ + + /* Loops possible, no multiple edges */ + seen_loops = 0; + for (i = 0; i < N; i++) { + SEED(); + igraph_watts_strogatz_game(&ws, /*dim=*/ 1, /*size=*/ 5, /*nei=*/ 1, + /*p=*/ 0.5, IGRAPH_LOOPS_SW); + if (has_multiple(&ws)) { + return 5; + } + seen_loops = seen_loops || has_loops(&ws); + igraph_destroy(&ws); + } + /* This might actually happen */ + /* if (!seen_loops) { return 6; } */ + + /* Both loops and multiple edges are possible */ + for (i = 0; i < N; i++) { + SEED(); + igraph_watts_strogatz_game(&ws, /*dim=*/ 1, /*size=*/ 5, /*nei=*/ 1, + /*p=*/ 0.5, IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW); + seen_loops = seen_loops || has_loops(&ws); + seen_multiple = seen_multiple || has_multiple(&ws); + igraph_destroy(&ws); + } + /* This might actually happen */ + /* if (!seen_loops) { return 7; } */ + /* if (!seen_multiple) { return 8; } */ + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/widest_paths.c b/tests/unit/widest_paths.c new file mode 100644 index 0000000..69b081d --- /dev/null +++ b/tests/unit/widest_paths.c @@ -0,0 +1,579 @@ +/* + igraph library. + Copyright (C) 2021 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include + +#include "test_utilities.h" + +/* initialise structures used in "get_widest_path(s)" algorithms */ +void init_vertices_and_edges(igraph_int_t n, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + igraph_vector_int_list_init(vertices, n); + igraph_vector_int_list_init(edges, n); + + igraph_vector_int_init(parents, 0); + igraph_vector_int_init(inbound_edges, 0); + + igraph_vector_int_init(vertices2, 0); + igraph_vector_int_init(edges2, 0); +} + +/* destroy structures used in "get_widest_path(s)" algorithms, ignores if NULL*/ +void destroy_vertices_and_edges(igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + if (edges2) igraph_vector_int_destroy(edges2); + if (vertices2) igraph_vector_int_destroy(vertices2); + + if (inbound_edges) igraph_vector_int_destroy(inbound_edges); + if (parents) igraph_vector_int_destroy(parents); + + if (edges) igraph_vector_int_list_destroy(edges); + if (vertices) igraph_vector_int_list_destroy(vertices); +} + +/* destroy all structures, ignores if NULL */ +void destroy_all(igraph_t *g, igraph_vector_t *w, igraph_matrix_t *res1, igraph_matrix_t *res2, + igraph_vs_t *from, igraph_vs_t *to, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + + destroy_vertices_and_edges(vertices, edges, parents, inbound_edges, vertices2, edges2); + + if (to) igraph_vs_destroy(to); + if (from) igraph_vs_destroy(from); + if (res2) igraph_matrix_destroy(res2); + if (res1) igraph_matrix_destroy(res1); + + if (w) igraph_vector_destroy(w); + if (g) igraph_destroy(g); +} + +/* confirm that all widest paths algorithms fail */ +void check_invalid_input(igraph_t *g, igraph_vector_t *w, igraph_matrix_t *res, + igraph_int_t source, igraph_int_t destination, + igraph_vs_t *from, igraph_vs_t *to, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + CHECK_ERROR(igraph_widest_path_widths_dijkstra(g, res, *from, *to, w, IGRAPH_OUT), IGRAPH_EINVAL); + CHECK_ERROR(igraph_widest_path_widths_floyd_warshall(g, res, *from, *to, w, IGRAPH_OUT), IGRAPH_EINVAL); + CHECK_ERROR(igraph_get_widest_paths(g, vertices, edges, source, *to, w, IGRAPH_OUT, + parents, inbound_edges), IGRAPH_EINVAL); + CHECK_ERROR(igraph_get_widest_path(g, vertices2, edges2, source, destination, w, IGRAPH_OUT), IGRAPH_EINVAL); +} + +/* runs the width finding algorithms and assert they succeed */ +void run_widest_paths(igraph_t *g, igraph_vector_t *w, igraph_matrix_t *res1, igraph_matrix_t *res2, + igraph_vs_t *from, igraph_vs_t *to, igraph_neimode_t mode) { + IGRAPH_ASSERT(igraph_widest_path_widths_dijkstra(g, res1, *from, *to, w, mode) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_widest_path_widths_floyd_warshall(g, res2, *from, *to, w, mode) == IGRAPH_SUCCESS); +} + +/* runs the path finding algorithms and asserts they succeed */ +void run_get_widest_paths(igraph_t *g, igraph_vector_t *w, + igraph_int_t source, igraph_int_t destination, + igraph_vs_t *to, igraph_neimode_t mode, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + IGRAPH_ASSERT(igraph_get_widest_paths(g, vertices, edges, source, *to, w, mode, + parents, inbound_edges) == IGRAPH_SUCCESS); + IGRAPH_ASSERT(igraph_get_widest_path(g, vertices2, edges2, source, destination, + w, mode) == IGRAPH_SUCCESS); +} + +/* print results of just the matrices */ +void check_and_print_matrices(igraph_matrix_t *res1, igraph_matrix_t *res2) { + IGRAPH_ASSERT(igraph_matrix_all_e(res1, res2)); + print_matrix_format(res1, stdout, "%f"); +} + +/* prints results of all widest path algorithms */ +void print_results(igraph_int_t n, igraph_matrix_t *res1, igraph_matrix_t *res2, + igraph_vector_int_list_t *vertices, igraph_vector_int_list_t *edges, + igraph_vector_int_t *parents, igraph_vector_int_t *inbound_edges, + igraph_vector_int_t *vertices2, igraph_vector_int_t *edges2) { + igraph_int_t i; + + check_and_print_matrices(res1, res2); + printf("\n"); + + for (i = 0; i < n; i++) { + printf("path to node %" IGRAPH_PRId ":\n", i); + igraph_vector_int_t *vertex_path = igraph_vector_int_list_get_ptr(vertices, i); + igraph_vector_int_t *edge_path = igraph_vector_int_list_get_ptr(edges, i); + printf(" vertices: "); + print_vector_int(vertex_path); + printf(" edges: "); + print_vector_int(edge_path); + } + printf("\n"); + + printf("parents: "); + print_vector_int(parents); + printf("inbound_edges: "); + print_vector_int(inbound_edges); + printf("\n"); + + printf("vertex path: "); + print_vector_int(vertices2); + printf("edge path: "); + print_vector_int(edges2); + printf("\n"); +} + + +int main(void) { + + igraph_int_t n, m; + igraph_t g; + igraph_matrix_t res1, res2; + igraph_vs_t from, to; + igraph_vector_t w; + + igraph_vector_int_list_t vertices; + igraph_vector_int_list_t edges; + igraph_vector_int_t parents; + igraph_vector_int_t inbound_edges; + + igraph_vector_int_t vertices2; + igraph_vector_int_t edges2; + + + /* ==================================================================== */ + /* 1. null weight vector */ + + n = 3; + m = 3; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, -1); + + igraph_matrix_init(&res1, n, n); + igraph_vs_range(&from, 0, n); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + check_invalid_input(&g, NULL, &res1, 0, 2, &from, &to, &vertices, &edges, + &parents, &inbound_edges, &vertices2, &edges2); + + destroy_vertices_and_edges(&vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, NULL, &res1, NULL, &from, &to, NULL, NULL, NULL, NULL, NULL, NULL); + + /* ==================================================================== */ + /* 2. Number of weights don't match number of edges */ + + n = 3; + m = 3; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, -1); + igraph_vector_init_real(&w, m+3, -1.0, 2.0, 3.0, 2.0, 5.0, 1.0); + + igraph_matrix_init(&res1, n, n); + igraph_vs_range(&from, 0, n); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + check_invalid_input(&g, &w, &res1, 0, 2, &from, &to, &vertices, &edges, + &parents, &inbound_edges, &vertices2, &edges2); + + destroy_vertices_and_edges(&vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, NULL, &from, &to, NULL, NULL, NULL, NULL, NULL, NULL); + + /* ==================================================================== */ + /* 3. NaN values in weights */ + + n = 3; + m = 3; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 2, 0, -1); + igraph_vector_init_real(&w, m, -1.0, IGRAPH_NAN, 3.0); + + igraph_matrix_init(&res1, n, n); + igraph_vs_range(&from, 0, n); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + check_invalid_input(&g, &w, &res1, 0, 2, &from, &to, &vertices, &edges, + &parents, &inbound_edges, &vertices2, &edges2); + + destroy_vertices_and_edges(&vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, NULL, &from, &to, NULL, NULL, NULL, NULL, NULL, NULL); + + /* ==================================================================== */ + /* 4. Empty graph */ + + n = 0; + m = 0; + igraph_small(&g, n, IGRAPH_UNDIRECTED, -1); + igraph_vector_init(&w, m); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_none(&from); + igraph_vs_none(&to); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + /* Should successfully run with nothing occuring */ + + destroy_all(&g, &w, &res1, &res2, &from, &to, NULL, NULL, NULL, NULL, NULL, NULL); + + /* ==================================================================== */ + /* 5. 1 node graph */ + printf("=== 5. Testing 1 Node Graph ===\n"); + + n = 1; + m = 0; + igraph_small(&g, n, IGRAPH_UNDIRECTED, -1); + igraph_vector_init(&w, m); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_all(&from); + igraph_vs_all(&to); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 0, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 6. Unreachable Nodes */ + printf("\n=== 6. Testing Unreachable Nodes ===\n"); + + n = 4; + m = 4; + igraph_small(&g, n, IGRAPH_DIRECTED, 0, 2, 0, 1, 1, 2, 3, 2, -1); + igraph_vector_init_real(&w, m, 1.0, 2.0, 3.0, 5.0); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 3, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 7. Self Loops */ + printf("\n=== 7. Testing Self Loops ===\n"); + + n = 3; + m = 6; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 2, 0, 1, 1, 2, 0, 0, 1, 1, 2, 2, -1); + igraph_vector_init_real(&w, m, 1.0, 2.0, 3.0, 5.0, 5.0, 1.0); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_all(&from); + igraph_vs_all(&to); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 2, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + /* Self loops should be effectively ignored, since the widest path to + yourself is always infinity. */ + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 8. Multiple Edges */ + printf("\n=== 8. Testing Multiple Edges ===\n"); + + n = 2; + m = 8; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, -1); + igraph_vector_init_real(&w, m, 2.0, 2.0, 2.0, 10.0, 2.0, 2.0, 2.0, 2.0); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 1, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 9. Directed Graphs */ + printf("\n=== 9. Testing Directed Graphs ===\n"); + + n = 2; + m = 6; + igraph_small(&g, n, IGRAPH_DIRECTED, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, -1); + igraph_vector_init_real(&w, m, 100.0, 200.0, 100.0, 200.0, 1.0, 200.0 ); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 1, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 10. Mode */ + printf("\n=== 10. Testing Mode ===\n"); + /* This is same as test 9, just with the mode reversed. */ + + n = 2; + m = 6; + igraph_small(&g, n, IGRAPH_DIRECTED, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, -1); + igraph_vector_init_real(&w, m, 100.0, 200.0, 100.0, 200.0, 1.0, 200.0 ); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_IN); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 1, &to, IGRAPH_IN, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 11. Multiple Widest Paths */ + printf("\n=== 11. Testing Multiple Widest Paths ===\n"); + + n = 8; + m = 9; + igraph_small(&g, n, IGRAPH_DIRECTED, 0, 1, 1, 2, 2, 7, 0, 3, 3, 4, 4, 7, + 0, 5, 5, 6, 6, 7, -1); + igraph_vector_init_real(&w, m, 100.0, 10.0, 200.0, 10.0, 15.0, 35.0, 3300.0, 10.0, 10.0 ); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 7, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + check_and_print_matrices(&res1, &res2); + + printf("\npath to node 7:\n"); + igraph_vector_int_t *vertex_path = igraph_vector_int_list_get_ptr(&vertices, 7); + igraph_vector_int_t *edge_path = igraph_vector_int_list_get_ptr(&edges, 7); + + printf(" vertices: "); + print_vector_int(vertex_path); + printf(" edges: "); + print_vector_int(edge_path); + printf("parents: "); + print_vector_int(&parents); + printf("inbound_edges: "); + print_vector_int(&inbound_edges); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + /* ==================================================================== */ + /* 12. 5 Node Simple Graph */ + printf("\n=== 12. Testing 5 Node Simple Graph ===\n"); + + n = 5; + m = 5; + igraph_small(&g, n, IGRAPH_UNDIRECTED, + 0, 1, 1, 2, 2, 3, 3, 4, 0, 3, + -1); + igraph_vector_init_real(&w, m, 8.0, 6.0, 10.0, 7.0, 5.0); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_all(&from); + igraph_vs_all(&to); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 4, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + /* ==================================================================== */ + /* 13. 7 Node Wikipedia Graph */ + printf("\n=== 13. Testing 7 Node Wikipedia Graph ===\n"); + + n = 7; + m = 11; + igraph_small(&g, n, IGRAPH_UNDIRECTED, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4, 2, 5, + 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + -1); + igraph_vector_init_real(&w, m, 15.0, 53.0, 40.0, 46.0, 31.0, + 17.0, 3.0, 11.0, 29.0, 8.0, 40.0); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 3); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 3, /* destination */ 6, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 14. Negative Widths */ + printf("\n=== 14. Testing Negative Weights ===\n"); + + n = 4; + m = 4; + igraph_small(&g, n, IGRAPH_DIRECTED, 0, 1, 1, 2, 0, 3, 3, 2, -1); + igraph_vector_init_real(&w, m, 2.0, -3.0, -5.0, 6.0); + + igraph_matrix_init(&res1, 1, n); + igraph_matrix_init(&res2, 1, n); + igraph_vs_1(&from, 0); + igraph_vs_range(&to, 0, n); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 2, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + + /* ==================================================================== */ + /* 15. Disconnected Graph */ + printf("\n=== 15. Testing Disconnected Graphs ===\n"); + + n = 5; + m = 4; + igraph_small(&g, n, IGRAPH_UNDIRECTED, 0, 1, 1, 2, 0, 2, 3, 4, -1); + igraph_vector_init_real(&w, m, 2.0, 5.0, 5.0, 7.0); + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_all(&from); + igraph_vs_all(&to); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_OUT); + run_get_widest_paths(&g, &w, /* source */ 0, /* destination */ 4, &to, IGRAPH_OUT, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + /* ==================================================================== */ + /* 16. Infinite weights */ + + n = 10; + m = 6; + igraph_small(&g, n, IGRAPH_UNDIRECTED, + 0,1, 1,2, 3,4, 4,5, 6,7, 8,9, + -1); + igraph_vector_init_real(&w, m, + 0.5, IGRAPH_INFINITY, /* 0,1, 1,2 */ + 1.5, -IGRAPH_INFINITY, /* 3,4, 4,5 */ + IGRAPH_INFINITY, /* 6,7 */ + -IGRAPH_INFINITY); /* 8,9 */ + + igraph_matrix_init(&res1, n, n); + igraph_matrix_init(&res2, n, n); + igraph_vs_all(&from); + igraph_vs_all(&to); + + init_vertices_and_edges(n, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + /* Note: Since edges with negative infinite weight are ignored, there is + * no path from 3 to 5. */ + run_widest_paths(&g, &w, &res1, &res2, &from, &to, IGRAPH_ALL); + run_get_widest_paths(&g, &w, /* source */ 3, /* destination */ 5, &to, IGRAPH_ALL, + &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + print_results(n, &res1, &res2, &vertices, &edges, &parents, &inbound_edges, &vertices2, &edges2); + + destroy_all(&g, &w, &res1, &res2, &from, &to, &vertices, &edges, &parents, &inbound_edges, + &vertices2, &edges2); + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/widest_paths.out b/tests/unit/widest_paths.out new file mode 100644 index 0000000..3cb0c6d --- /dev/null +++ b/tests/unit/widest_paths.out @@ -0,0 +1,281 @@ +=== 5. Testing 1 Node Graph === +[ Inf ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) + +parents: ( -1 ) +inbound_edges: ( -1 ) + +vertex path: ( 0 ) +edge path: ( ) + + +=== 6. Testing Unreachable Nodes === +[ Inf 2.000000 2.000000 -Inf ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 1 ) +path to node 2: + vertices: ( 0 1 2 ) + edges: ( 1 2 ) +path to node 3: + vertices: ( ) + edges: ( ) + +parents: ( -1 0 1 -2 ) +inbound_edges: ( -1 1 2 -1 ) + +vertex path: ( ) +edge path: ( ) + + +=== 7. Testing Self Loops === +[ Inf 2.000000 2.000000 + 2.000000 Inf 3.000000 + 2.000000 3.000000 Inf ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 1 ) +path to node 2: + vertices: ( 0 1 2 ) + edges: ( 1 2 ) + +parents: ( -1 0 1 ) +inbound_edges: ( -1 1 2 ) + +vertex path: ( 0 1 2 ) +edge path: ( 1 2 ) + + +=== 8. Testing Multiple Edges === +[ Inf 10.000000 ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 3 ) + +parents: ( -1 0 ) +inbound_edges: ( -1 3 ) + +vertex path: ( 0 1 ) +edge path: ( 3 ) + + +=== 9. Testing Directed Graphs === +[ Inf 1.000000 ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 4 ) + +parents: ( -1 0 ) +inbound_edges: ( -1 4 ) + +vertex path: ( 0 1 ) +edge path: ( 4 ) + + +=== 10. Testing Mode === +[ Inf 200.000000 ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 5 ) + +parents: ( -1 0 ) +inbound_edges: ( -1 5 ) + +vertex path: ( 0 1 ) +edge path: ( 5 ) + + +=== 11. Testing Multiple Widest Paths === +[ Inf 100.000000 10.000000 10.000000 10.000000 3300.000000 10.000000 10.000000 ] + +path to node 7: + vertices: ( 0 1 2 7 ) + edges: ( 0 1 2 ) +parents: ( -1 0 1 0 3 0 5 2 ) +inbound_edges: ( -1 0 1 3 4 6 7 2 ) + +=== 12. Testing 5 Node Simple Graph === +[ Inf 8.000000 6.000000 6.000000 6.000000 + 8.000000 Inf 6.000000 6.000000 6.000000 + 6.000000 6.000000 Inf 10.000000 7.000000 + 6.000000 6.000000 10.000000 Inf 7.000000 + 6.000000 6.000000 7.000000 7.000000 Inf ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 0 ) +path to node 2: + vertices: ( 0 1 2 ) + edges: ( 0 1 ) +path to node 3: + vertices: ( 0 1 2 3 ) + edges: ( 0 1 2 ) +path to node 4: + vertices: ( 0 1 2 3 4 ) + edges: ( 0 1 2 3 ) + +parents: ( -1 0 1 2 3 ) +inbound_edges: ( -1 0 1 2 3 ) + +vertex path: ( 0 1 2 3 4 ) +edge path: ( 0 1 2 3 ) + + +=== 13. Testing 7 Node Wikipedia Graph === +[ 40.000000 46.000000 40.000000 Inf 31.000000 29.000000 29.000000 ] + +path to node 0: + vertices: ( 3 1 2 0 ) + edges: ( 3 2 1 ) +path to node 1: + vertices: ( 3 1 ) + edges: ( 3 ) +path to node 2: + vertices: ( 3 1 2 ) + edges: ( 3 2 ) +path to node 3: + vertices: ( 3 ) + edges: ( ) +path to node 4: + vertices: ( 3 1 2 4 ) + edges: ( 3 2 4 ) +path to node 5: + vertices: ( 3 1 2 4 5 ) + edges: ( 3 2 4 8 ) +path to node 6: + vertices: ( 3 1 2 4 5 6 ) + edges: ( 3 2 4 8 10 ) + +parents: ( 2 3 1 -1 2 4 5 ) +inbound_edges: ( 1 3 2 -1 4 8 10 ) + +vertex path: ( 3 1 2 4 5 6 ) +edge path: ( 3 2 4 8 10 ) + + +=== 14. Testing Negative Weights === +[ Inf 2.000000 -3.000000 -5.000000 ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 1 ) + edges: ( 0 ) +path to node 2: + vertices: ( 0 3 2 ) + edges: ( 2 3 ) +path to node 3: + vertices: ( 0 3 ) + edges: ( 2 ) + +parents: ( -1 0 3 0 ) +inbound_edges: ( -1 0 3 2 ) + +vertex path: ( 0 1 2 ) +edge path: ( 0 1 ) + + +=== 15. Testing Disconnected Graphs === +[ Inf 5.000000 5.000000 -Inf -Inf + 5.000000 Inf 5.000000 -Inf -Inf + 5.000000 5.000000 Inf -Inf -Inf + -Inf -Inf -Inf Inf 7.000000 + -Inf -Inf -Inf 7.000000 Inf ] + +path to node 0: + vertices: ( 0 ) + edges: ( ) +path to node 1: + vertices: ( 0 2 1 ) + edges: ( 2 1 ) +path to node 2: + vertices: ( 0 2 ) + edges: ( 2 ) +path to node 3: + vertices: ( ) + edges: ( ) +path to node 4: + vertices: ( ) + edges: ( ) + +parents: ( -1 2 0 -2 -2 ) +inbound_edges: ( -1 1 2 -1 -1 ) + +vertex path: ( ) +edge path: ( ) + +[ Inf 0.500000 0.500000 -Inf -Inf -Inf -Inf -Inf -Inf -Inf + 0.500000 Inf Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf + 0.500000 Inf Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf + -Inf -Inf -Inf Inf 1.500000 -Inf -Inf -Inf -Inf -Inf + -Inf -Inf -Inf 1.500000 Inf -Inf -Inf -Inf -Inf -Inf + -Inf -Inf -Inf -Inf -Inf Inf -Inf -Inf -Inf -Inf + -Inf -Inf -Inf -Inf -Inf -Inf Inf Inf -Inf -Inf + -Inf -Inf -Inf -Inf -Inf -Inf Inf Inf -Inf -Inf + -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf Inf -Inf + -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf Inf ] + +path to node 0: + vertices: ( ) + edges: ( ) +path to node 1: + vertices: ( ) + edges: ( ) +path to node 2: + vertices: ( ) + edges: ( ) +path to node 3: + vertices: ( 3 ) + edges: ( ) +path to node 4: + vertices: ( 3 4 ) + edges: ( 2 ) +path to node 5: + vertices: ( ) + edges: ( ) +path to node 6: + vertices: ( ) + edges: ( ) +path to node 7: + vertices: ( ) + edges: ( ) +path to node 8: + vertices: ( ) + edges: ( ) +path to node 9: + vertices: ( ) + edges: ( ) + +parents: ( -2 -2 -2 -1 3 -2 -2 -2 -2 -2 ) +inbound_edges: ( -1 -1 -1 -1 2 -1 -1 -1 -1 -1 ) + +vertex path: ( ) +edge path: ( ) + diff --git a/tests/unit/wikti_en_V_syn.elist b/tests/unit/wikti_en_V_syn.elist new file mode 100644 index 0000000..2b0e794 --- /dev/null +++ b/tests/unit/wikti_en_V_syn.elist @@ -0,0 +1,8293 @@ +0 1 +0 2 +0 220 +0 229 +0 1010 +1 2 +1 12 +1 3849 +3 4 +3 5 +3 6 +3 7 +3 8 +3 9 +3 10 +3 11 +4 7 +7 671 +8 2285 +9 671 +10 671 +10 939 +10 1355 +10 1844 +10 2546 +10 3054 +11 671 +12 3849 +13 14 +13 15 +13 16 +13 17 +13 2270 +14 1795 +14 2270 +14 2531 +15 2270 +17 1466 +18 19 +18 20 +18 21 +18 22 +18 23 +18 24 +18 25 +18 26 +18 2041 +21 1970 +22 838 +24 5101 +25 2531 +25 5101 +27 1079 +27 2041 +27 2126 +27 3081 +28 29 +28 30 +28 31 +28 32 +28 33 +28 34 +28 35 +28 36 +28 37 +28 38 +29 31 +29 35 +29 881 +29 882 +29 883 +29 884 +29 885 +38 3930 +39 40 +39 41 +39 1251 +42 6340 +43 44 +43 45 +43 46 +44 318 +47 48 +47 49 +47 50 +47 5319 +48 50 +48 51 +48 3380 +50 51 +50 52 +50 53 +50 54 +50 55 +50 79 +51 3380 +52 58 +52 59 +52 60 +52 61 +52 62 +52 63 +52 64 +52 65 +52 66 +52 67 +52 68 +52 69 +52 70 +52 71 +52 72 +52 73 +52 74 +52 75 +52 76 +52 77 +52 78 +52 1697 +53 3380 +56 7312 +57 1376 +58 61 +58 64 +58 67 +58 73 +58 2730 +60 3371 +61 3371 +66 68 +66 70 +66 1949 +66 3641 +66 4869 +66 4870 +66 4871 +66 4872 +74 2543 +74 4679 +75 76 +76 389 +76 665 +76 827 +76 853 +76 883 +76 1017 +76 1152 +76 1416 +76 1468 +76 1469 +76 1470 +76 1471 +76 1472 +76 1473 +76 1474 +76 1475 +76 1476 +76 1477 +76 1478 +76 1479 +76 1480 +76 1481 +76 1482 +76 1483 +76 1484 +76 1485 +76 1486 +77 725 +78 5319 +80 2539 +80 3798 +81 2467 +82 83 +82 84 +82 85 +82 86 +82 87 +82 88 +82 89 +82 90 +82 91 +82 92 +82 93 +82 94 +82 95 +82 96 +82 97 +82 1006 +82 2140 +83 85 +83 87 +83 88 +83 90 +83 91 +83 92 +83 93 +83 95 +83 114 +83 115 +84 612 +85 2140 +85 4577 +88 90 +88 91 +88 95 +88 241 +88 4572 +88 5016 +89 1003 +89 1164 +89 1180 +89 1201 +89 1909 +89 2390 +89 4415 +89 5125 +90 93 +90 1201 +90 1341 +90 2390 +91 241 +91 5016 +92 130 +92 3619 +95 4055 +97 612 +97 2531 +97 2937 +97 3516 +97 4023 +97 4024 +97 4025 +97 4026 +97 4027 +97 4028 +97 4029 +97 4030 +97 4031 +97 5016 +98 99 +98 100 +99 100 +99 106 +99 3481 +99 3616 +99 5874 +100 3616 +100 4755 +101 102 +101 103 +101 104 +101 105 +101 106 +101 107 +101 1095 +101 4777 +102 103 +102 104 +102 312 +102 400 +102 1189 +102 1567 +102 2699 +102 2760 +102 3393 +102 3448 +102 4243 +102 5088 +103 2760 +103 3393 +104 107 +104 1498 +104 1567 +104 1999 +104 3892 +104 4777 +105 106 +105 107 +106 107 +106 3622 +106 3952 +106 4515 +107 3997 +107 4777 +108 6079 +109 110 +109 111 +109 112 +109 113 +109 1679 +109 2263 +116 117 +116 118 +117 355 +118 251 +118 2571 +119 5857 +120 4825 +121 122 +123 124 +123 125 +123 126 +123 127 +124 125 +124 126 +124 127 +124 1398 +125 126 +125 127 +126 127 +128 129 +130 138 +130 2975 +130 3618 +131 132 +132 380 +132 384 +132 385 +132 386 +132 742 +132 1394 +132 1704 +132 2084 +132 2085 +132 2086 +132 3018 +132 3139 +132 3201 +132 3648 +132 3653 +132 3654 +132 3655 +132 4414 +132 4987 +132 5041 +133 2975 +134 135 +134 136 +135 5623 +136 6204 +137 138 +137 139 +137 140 +137 141 +137 142 +137 143 +137 144 +137 145 +137 146 +137 147 +137 148 +137 149 +137 150 +137 151 +137 152 +137 153 +137 154 +137 155 +137 156 +137 157 +137 158 +137 159 +137 160 +137 161 +137 162 +137 163 +137 164 +137 165 +137 166 +137 167 +137 168 +137 169 +137 170 +137 171 +137 172 +137 173 +137 174 +137 175 +137 176 +138 139 +138 140 +138 141 +138 142 +138 143 +138 185 +138 186 +138 187 +138 188 +138 189 +138 190 +138 191 +138 192 +138 193 +138 194 +138 195 +138 196 +138 197 +138 198 +138 199 +138 200 +138 201 +138 202 +138 203 +138 204 +138 205 +138 206 +138 207 +138 208 +138 209 +138 210 +138 211 +139 208 +140 142 +140 205 +140 208 +140 1704 +140 3960 +141 204 +141 3960 +142 143 +142 206 +159 297 +166 422 +166 423 +166 3804 +177 3930 +178 2725 +178 2728 +178 3816 +179 2120 +179 2122 +180 181 +181 4287 +181 6173 +181 6401 +181 6444 +181 7287 +181 7292 +182 445 +182 2260 +183 184 +185 1138 +185 1425 +185 1797 +185 2373 +185 2984 +185 3914 +187 1210 +187 1616 +187 4154 +188 597 +189 567 +189 1904 +189 2390 +189 4906 +190 1882 +190 2027 +190 4158 +191 1138 +191 1425 +191 3960 +194 1498 +195 1884 +198 211 +198 4920 +199 205 +202 621 +202 622 +202 623 +203 385 +203 1223 +203 3270 +204 567 +204 587 +204 1022 +204 1704 +204 1904 +204 2960 +205 1010 +205 1013 +205 1014 +205 1494 +205 1495 +205 1496 +206 2974 +207 354 +208 297 +208 1748 +208 3960 +210 597 +210 1138 +210 1425 +211 445 +211 1653 +211 3421 +212 6259 +213 1590 +214 498 +215 239 +215 240 +215 1153 +215 2672 +216 217 +216 218 +216 219 +216 220 +216 4766 +217 220 +217 2058 +219 220 +219 818 +219 819 +219 820 +219 821 +219 822 +219 823 +219 824 +219 825 +219 1722 +219 4766 +220 232 +220 283 +220 284 +220 285 +220 286 +220 287 +220 6453 +221 222 +221 223 +221 224 +221 225 +221 226 +221 227 +221 784 +221 3940 +222 3940 +223 1558 +224 1653 +224 1925 +224 3098 +225 226 +225 564 +225 1026 +225 1558 +225 2037 +225 2363 +225 3169 +225 6678 +226 227 +226 564 +226 1026 +226 1558 +226 1946 +226 1947 +226 2037 +227 1558 +227 2037 +228 1589 +228 3093 +228 6782 +229 230 +229 231 +229 232 +229 233 +229 234 +229 235 +229 236 +229 237 +229 238 +230 236 +230 1167 +230 3423 +230 3424 +232 233 +232 1010 +233 234 +233 235 +233 1563 +233 2064 +233 3091 +234 236 +234 4496 +236 4833 +239 240 +241 242 +241 243 +241 244 +241 245 +244 2390 +245 1254 +246 3094 +247 248 +249 250 +249 251 +249 252 +249 604 +250 251 +250 400 +250 965 +251 354 +251 393 +251 394 +251 395 +251 396 +251 397 +251 398 +251 399 +251 400 +251 401 +251 402 +251 985 +251 1105 +251 1126 +251 1918 +251 1980 +252 297 +253 254 +253 255 +253 256 +253 257 +253 258 +253 259 +253 260 +253 261 +253 262 +253 263 +253 264 +255 454 +255 1619 +255 1971 +257 523 +262 3571 +265 266 +265 267 +265 268 +265 269 +265 270 +265 271 +265 272 +265 273 +265 274 +265 275 +265 276 +265 277 +265 278 +265 279 +265 1821 +280 281 +280 282 +281 5678 +282 787 +282 1636 +282 2445 +282 6200 +283 6453 +285 6453 +286 2053 +288 839 +288 5033 +289 290 +289 291 +289 292 +289 293 +289 294 +289 295 +289 296 +289 297 +289 298 +289 299 +289 300 +289 301 +289 302 +289 303 +289 304 +289 305 +289 306 +289 307 +289 308 +289 706 +289 1201 +289 1722 +289 2059 +289 3615 +289 3935 +289 4539 +289 4554 +289 5254 +289 6307 +289 7197 +289 7300 +290 306 +290 7300 +291 306 +291 4554 +291 4555 +291 7300 +292 325 +294 3279 +296 4539 +296 4540 +296 7197 +297 298 +297 389 +297 404 +297 408 +297 409 +297 478 +297 581 +297 612 +297 614 +297 617 +297 700 +297 972 +297 1180 +297 1305 +297 1318 +297 1353 +297 1371 +297 1419 +297 1431 +297 1487 +297 1721 +297 1722 +297 1723 +297 1724 +297 1725 +297 1726 +297 1727 +297 1728 +297 1729 +297 1730 +297 1731 +297 1732 +297 1733 +297 1734 +297 1735 +297 1736 +297 1737 +297 1738 +297 1739 +297 1740 +297 1741 +297 1742 +297 1743 +297 1744 +297 1745 +297 1746 +297 1747 +297 1748 +297 1749 +297 1750 +297 1751 +297 1752 +297 1753 +297 1754 +297 1755 +297 1756 +297 1757 +297 2733 +298 1722 +298 2733 +298 3935 +299 498 +299 1722 +306 4554 +306 7300 +309 310 +309 311 +312 313 +312 314 +312 315 +312 316 +312 317 +312 368 +313 625 +313 626 +316 3390 +317 1674 +318 400 +319 320 +319 321 +319 6552 +322 935 +322 6054 +323 324 +325 326 +325 327 +325 328 +325 329 +325 330 +325 331 +325 332 +326 438 +328 2041 +328 2833 +329 1079 +330 587 +330 2200 +333 2495 +333 2531 +334 400 +334 1402 +334 2650 +334 3156 +334 3453 +335 336 +337 338 +337 339 +337 340 +337 341 +337 342 +337 343 +337 344 +337 345 +337 346 +337 347 +337 348 +337 349 +337 350 +337 351 +337 352 +337 353 +338 339 +338 3852 +339 341 +339 342 +339 343 +339 346 +339 347 +339 351 +339 4119 +341 342 +341 346 +341 347 +342 343 +342 347 +342 349 +342 1597 +346 347 +351 3169 +354 355 +354 356 +354 357 +354 358 +356 600 +359 3508 +359 3510 +360 1180 +361 362 +362 2732 +363 672 +364 2488 +365 366 +366 4491 +367 2213 +368 369 +368 370 +369 370 +369 1047 +369 1239 +369 1240 +369 1241 +369 1242 +369 1243 +371 6782 +372 373 +372 374 +372 375 +372 376 +372 377 +372 378 +372 379 +373 374 +373 375 +373 376 +373 377 +373 378 +373 379 +373 4791 +374 375 +374 378 +374 379 +375 1223 +376 1223 +381 3117 +381 5859 +382 3117 +382 5215 +382 5217 +383 1400 +384 385 +384 386 +384 387 +384 388 +384 5641 +385 610 +385 1889 +385 3269 +385 3270 +385 3271 +386 387 +386 1889 +386 3270 +386 5640 +386 7109 +386 7110 +387 5359 +387 5641 +388 1010 +388 2958 +389 390 +389 391 +389 392 +389 496 +389 1126 +391 763 +392 2079 +393 1105 +393 1106 +394 672 +396 453 +396 623 +396 2344 +396 2571 +396 2792 +396 4217 +397 1105 +398 596 +398 1413 +399 400 +399 1188 +399 1925 +399 1980 +399 2213 +400 470 +400 581 +400 617 +400 666 +400 671 +400 672 +400 689 +400 706 +400 722 +400 767 +400 857 +400 905 +400 914 +400 956 +400 968 +400 1090 +400 1105 +400 1114 +400 1118 +400 1171 +400 1172 +400 1173 +400 1174 +400 1175 +400 1176 +400 1177 +400 1178 +400 1179 +400 1180 +400 1181 +400 1182 +400 1183 +400 1184 +400 1185 +400 1186 +400 1187 +400 1188 +400 1189 +400 1190 +400 1191 +400 1192 +400 1193 +400 1194 +400 1195 +400 1219 +400 1653 +400 2263 +400 3963 +401 454 +402 604 +402 1137 +402 1138 +403 861 +405 406 +405 407 +405 408 +405 409 +405 410 +405 4500 +406 407 +406 1037 +406 1038 +406 1040 +406 3643 +407 1038 +407 1039 +407 1040 +407 2515 +407 3877 +407 5069 +407 5070 +407 5071 +408 1253 +408 1755 +408 3687 +408 3978 +408 3979 +408 3980 +408 3981 +408 4590 +409 454 +409 1724 +410 3755 +411 412 +411 413 +411 414 +411 415 +411 416 +411 417 +411 418 +411 832 +411 1096 +411 3062 +411 3488 +411 4018 +412 414 +412 4676 +412 5769 +413 800 +413 2753 +413 4876 +414 1153 +414 1349 +414 1350 +414 1351 +414 1352 +414 2924 +415 1153 +417 1688 +417 4016 +417 4018 +418 2159 +418 4018 +419 420 +421 827 +422 423 +422 701 +422 702 +422 703 +422 704 +422 705 +422 2999 +424 425 +424 426 +424 427 +424 428 +424 429 +424 430 +424 1932 +424 2293 +425 3533 +426 427 +426 428 +426 1766 +426 1767 +426 1768 +427 428 +427 1609 +427 3488 +427 4014 +427 5101 +428 1768 +428 3488 +428 3533 +429 3798 +430 915 +430 5101 +430 5561 +431 432 +431 4226 +432 3762 +432 4226 +433 985 +433 1180 +433 1212 +434 597 +434 3645 +434 3646 +435 436 +435 437 +435 1936 +436 1070 +436 1936 +436 1939 +436 3488 +436 4059 +437 1936 +437 5016 +439 1234 +439 1679 +439 2263 +439 3412 +440 441 +440 6135 +442 3553 +443 535 +444 4580 +444 4583 +445 446 +445 447 +445 448 +445 449 +445 450 +445 451 +445 452 +445 453 +446 954 +446 3436 +447 3371 +447 3428 +448 3624 +448 3625 +448 4036 +448 4908 +449 2296 +452 2607 +453 591 +453 679 +453 1106 +453 1628 +453 1688 +453 1927 +453 2344 +453 2345 +453 2607 +453 3026 +453 3224 +453 3806 +453 5490 +453 5794 +454 455 +454 456 +454 457 +454 458 +454 459 +454 460 +454 461 +454 462 +454 463 +454 464 +454 465 +454 466 +454 467 +454 468 +454 469 +454 470 +454 471 +454 472 +454 473 +454 474 +454 475 +454 476 +454 477 +454 478 +454 479 +454 480 +454 481 +454 482 +457 2054 +459 3997 +460 2531 +462 542 +465 1434 +465 1649 +465 1650 +465 1651 +465 1652 +465 6577 +469 4893 +470 541 +470 604 +470 686 +470 687 +470 1173 +470 1987 +470 2440 +470 2441 +471 835 +471 965 +471 2291 +471 4741 +471 4949 +472 1138 +473 985 +473 1955 +475 3907 +477 1138 +478 1724 +478 2213 +479 1877 +479 2483 +480 4289 +481 936 +481 1248 +483 484 +483 485 +483 486 +483 487 +483 488 +483 489 +483 490 +483 491 +483 492 +483 493 +483 4035 +485 644 +485 645 +485 1424 +485 2060 +485 2061 +485 2062 +485 2063 +486 487 +486 488 +486 489 +486 2597 +487 892 +487 894 +487 2452 +487 2599 +487 3622 +487 4900 +488 2105 +488 2106 +488 2107 +488 2108 +488 2110 +488 2111 +488 2112 +488 2113 +488 2114 +488 5916 +489 1036 +489 2106 +489 2597 +490 2114 +491 2112 +491 2113 +491 2114 +492 2114 +493 644 +493 3546 +493 3729 +493 4001 +493 4035 +494 495 +494 496 +494 497 +494 498 +494 499 +494 500 +494 501 +494 502 +495 3798 +495 4791 +495 5579 +496 587 +496 1425 +496 3211 +496 7179 +498 1331 +498 3152 +498 3205 +498 3206 +498 3207 +498 3208 +498 3209 +500 1356 +500 1489 +503 504 +503 681 +503 826 +503 4652 +505 506 +505 507 +505 508 +505 4015 +506 915 +506 1949 +507 4015 +507 5146 +508 5690 +509 3533 +510 4984 +511 512 +511 513 +511 514 +511 515 +511 516 +511 517 +511 518 +511 519 +512 1848 +513 2516 +513 5501 +514 634 +514 1619 +515 1848 +515 2030 +515 2516 +515 3112 +516 1848 +516 3112 +520 521 +520 522 +520 4085 +520 6529 +524 2405 +525 526 +527 528 +527 529 +527 530 +527 531 +528 2555 +529 1219 +530 3572 +531 1139 +531 1299 +531 1447 +531 2520 +531 2549 +531 2555 +531 2846 +531 3572 +531 3963 +531 6647 +531 7022 +532 1611 +533 534 +533 681 +533 3056 +535 536 +535 537 +535 538 +537 626 +537 900 +538 3075 +539 2827 +540 2228 +540 4160 +542 543 +542 544 +542 545 +542 546 +543 2041 +543 2425 +544 2425 +545 980 +547 1092 +548 549 +548 550 +548 551 +548 552 +548 553 +548 554 +548 555 +548 556 +548 557 +548 558 +548 559 +548 560 +548 561 +548 562 +548 745 +550 551 +550 745 +551 556 +551 558 +551 745 +551 906 +556 745 +558 745 +563 6075 +564 695 +564 696 +564 697 +564 5962 +564 6849 +565 6458 +566 782 +566 1096 +566 3434 +567 568 +567 569 +567 570 +567 571 +567 572 +567 573 +567 574 +567 575 +567 576 +567 577 +567 578 +567 3228 +568 1904 +568 3228 +569 575 +570 681 +570 985 +570 1722 +570 4135 +570 5317 +571 732 +573 826 +573 2816 +574 1904 +574 4906 +575 1904 +575 5507 +578 1180 +578 5125 +579 672 +580 581 +580 582 +580 583 +580 584 +580 585 +580 586 +580 1096 +580 1376 +581 1097 +581 1126 +581 1376 +581 2178 +581 3056 +582 1376 +582 2431 +583 1376 +584 1376 +584 2413 +584 2431 +587 588 +587 589 +587 590 +587 591 +587 592 +587 593 +587 594 +587 595 +587 596 +587 597 +587 598 +587 599 +587 600 +587 601 +587 602 +587 603 +587 1425 +587 3225 +588 593 +588 596 +589 783 +589 2200 +589 5340 +591 783 +591 5340 +593 595 +593 596 +593 597 +593 1633 +593 1843 +593 2373 +593 2374 +593 2375 +593 2376 +595 597 +595 1079 +596 597 +596 1310 +596 1413 +596 1414 +597 651 +597 905 +597 1305 +597 1371 +597 1729 +597 1730 +597 1731 +597 1986 +597 2324 +597 2325 +597 2326 +597 2327 +600 735 +600 813 +600 1075 +600 1076 +600 1077 +600 1078 +600 1079 +600 1080 +600 1081 +600 1082 +600 1083 +600 1084 +600 1085 +600 1086 +600 1087 +600 1088 +600 1089 +600 5016 +600 6387 +603 5772 +604 605 +604 606 +604 607 +604 608 +604 2441 +607 672 +608 985 +608 2213 +609 4962 +610 3270 +610 4204 +611 2362 +611 5380 +612 613 +612 614 +612 615 +612 616 +612 617 +612 618 +612 619 +612 620 +612 1931 +612 2258 +612 2531 +613 1931 +614 617 +614 794 +614 1918 +614 3378 +614 3379 +614 3380 +615 2937 +617 875 +617 876 +617 1035 +617 1126 +617 1130 +617 1180 +617 1446 +617 1769 +617 1918 +617 1999 +617 2213 +617 2214 +617 2234 +617 2587 +617 2588 +617 2589 +617 2590 +617 2591 +617 2592 +617 2593 +617 2594 +617 6980 +618 619 +618 935 +618 2333 +618 3460 +619 928 +619 2241 +619 2242 +620 6692 +621 622 +621 3454 +622 2862 +622 3748 +622 5404 +622 5405 +622 5406 +623 4217 +624 6148 +624 6149 +625 626 +625 627 +625 628 +626 897 +626 900 +626 1395 +626 1587 +626 2114 +626 3608 +626 4084 +627 4121 +628 4121 +629 630 +629 4876 +631 3440 +632 4570 +633 863 +633 864 +633 866 +633 1037 +633 1325 +633 4304 +634 635 +634 636 +634 637 +634 638 +634 639 +634 640 +634 641 +634 642 +634 643 +634 7002 +639 642 +639 1236 +640 642 +640 2228 +640 2495 +640 2531 +640 2653 +640 5561 +641 5561 +642 1223 +642 1236 +642 1339 +642 1399 +642 1400 +642 1401 +642 2329 +642 2531 +644 645 +646 647 +646 648 +646 649 +646 650 +649 650 +649 3788 +649 3789 +650 1385 +650 1832 +650 1909 +652 653 +652 654 +652 655 +652 656 +652 657 +652 658 +652 659 +652 660 +654 655 +656 660 +657 3763 +661 662 +661 663 +661 664 +661 1268 +661 1367 +661 2555 +662 1268 +662 2553 +662 3321 +663 1444 +663 2555 +663 6674 +664 3277 +664 3527 +665 827 +665 1648 +666 667 +666 668 +666 669 +666 670 +666 671 +666 672 +666 1180 +666 3443 +667 1071 +668 3443 +669 937 +671 2606 +671 3706 +672 965 +672 1111 +672 1491 +672 1492 +672 1493 +672 2333 +673 4580 +674 2267 +675 676 +676 4144 +677 678 +680 2531 +681 682 +681 683 +681 684 +681 685 +681 686 +681 687 +681 688 +681 689 +681 690 +681 691 +681 692 +681 5736 +684 2041 +685 1722 +685 5317 +689 1487 +689 3056 +690 4472 +691 692 +691 1923 +691 2604 +691 4472 +692 2604 +692 4472 +693 694 +695 1946 +696 2037 +696 2126 +696 5157 +698 699 +698 2487 +698 2488 +698 5456 +703 5691 +703 6782 +706 707 +706 708 +706 709 +706 710 +706 711 +706 712 +706 713 +706 714 +706 715 +706 716 +706 717 +706 718 +706 719 +706 720 +706 721 +706 722 +706 723 +706 724 +706 725 +706 726 +706 727 +706 1234 +706 1679 +706 6569 +707 708 +707 1002 +707 1008 +707 1223 +707 1234 +707 1679 +707 6569 +708 1119 +708 1679 +711 1681 +711 3516 +721 806 +721 1681 +722 783 +722 1069 +722 1070 +722 1180 +722 1219 +722 2178 +722 4736 +725 726 +725 1425 +725 1680 +725 2420 +725 4862 +728 2531 +728 3867 +729 3581 +730 915 +731 1353 +732 733 +734 1546 +736 737 +736 738 +736 739 +736 740 +736 741 +737 740 +737 741 +737 2858 +737 3346 +737 3347 +737 3348 +738 740 +739 740 +739 6039 +740 741 +740 1687 +740 1712 +740 6405 +741 2858 +741 3346 +741 3347 +742 743 +742 744 +745 746 +745 747 +745 748 +745 749 +745 750 +745 751 +747 4091 +752 753 +752 754 +753 754 +753 1951 +753 4065 +754 1283 +754 1951 +754 2755 +754 3631 +754 4065 +754 5154 +754 6147 +755 756 +757 758 +757 759 +757 760 +757 761 +761 1947 +761 2706 +762 4195 +764 765 +764 766 +764 2725 +764 2728 +764 3816 +764 4597 +765 2725 +765 2728 +765 3816 +765 4597 +767 1070 +767 1690 +767 2347 +767 5529 +768 769 +768 770 +768 771 +768 772 +768 773 +770 6933 +771 6940 +772 5696 +774 775 +776 3553 +777 778 +779 780 +779 781 +780 781 +780 799 +780 800 +780 2753 +780 3855 +781 800 +781 2779 +783 784 +783 5340 +784 986 +784 988 +784 1363 +784 1372 +784 3012 +784 4109 +784 5329 +785 786 +785 4833 +787 788 +787 789 +787 790 +788 1636 +788 2445 +789 2320 +789 2321 +791 1153 +792 4197 +793 794 +793 795 +794 1387 +794 1918 +796 906 +797 1697 +798 799 +798 800 +799 800 +799 2753 +799 2779 +799 4580 +800 2753 +800 2777 +800 2778 +800 2779 +800 4580 +800 4876 +801 802 +801 803 +801 804 +801 805 +802 6270 +806 807 +806 808 +808 1681 +809 810 +809 811 +809 3255 +810 811 +810 3247 +810 3254 +810 3255 +810 3256 +810 3257 +810 4172 +810 5949 +810 5950 +811 3244 +811 3245 +811 3246 +811 3247 +811 3248 +811 3249 +811 3250 +811 3251 +811 3252 +811 3253 +811 3254 +811 3255 +811 3256 +811 3257 +812 813 +812 814 +812 815 +812 5010 +813 3098 +815 7233 +816 3030 +817 906 +817 5926 +817 5981 +821 5548 +822 1657 +826 1219 +826 1653 +826 2816 +826 4782 +827 828 +827 829 +827 830 +827 831 +827 832 +827 833 +827 834 +827 2531 +832 1100 +832 1213 +832 1353 +832 2655 +832 4079 +832 4080 +832 4204 +832 5024 +833 922 +834 5097 +834 6692 +835 965 +836 837 +836 2092 +837 2092 +839 840 +839 841 +839 842 +839 843 +839 870 +839 2581 +840 870 +840 2581 +842 870 +844 4215 +845 846 +846 7056 +847 6895 +847 7117 +848 849 +848 850 +848 851 +849 4830 +852 4623 +854 855 +854 856 +857 1180 +857 1353 +857 2213 +857 5317 +858 3740 +859 7122 +860 6567 +861 862 +861 863 +861 864 +861 865 +861 866 +861 867 +861 868 +861 4304 +863 864 +863 865 +863 866 +863 868 +863 1325 +863 6279 +864 865 +864 866 +864 868 +864 1325 +865 1320 +866 1320 +866 1321 +866 1322 +866 1323 +866 1324 +866 1325 +867 5861 +868 1325 +869 5448 +870 871 +870 872 +870 873 +870 874 +870 875 +870 876 +870 877 +870 5033 +871 2581 +875 876 +875 926 +875 1304 +875 1386 +875 1542 +875 1543 +875 2145 +875 2146 +875 2147 +876 6581 +877 949 +877 1896 +877 2016 +877 2367 +877 3221 +877 3588 +877 5689 +877 7251 +878 1400 +878 2412 +879 3679 +879 5121 +880 3761 +883 1010 +883 1017 +884 1353 +884 2041 +885 1846 +885 4431 +886 887 +887 1877 +888 889 +888 890 +888 891 +889 890 +889 2065 +889 2066 +889 2067 +889 2068 +889 2069 +889 2070 +889 2071 +889 2072 +889 2073 +889 2074 +889 2075 +890 891 +890 2371 +890 2822 +890 3222 +890 3483 +890 3484 +890 3485 +890 3486 +890 3487 +890 3581 +890 4635 +890 4864 +890 5141 +890 5563 +890 6529 +891 2370 +891 3581 +891 4431 +892 893 +892 894 +892 895 +892 896 +895 2452 +896 1995 +896 2105 +896 2108 +896 2120 +896 3756 +896 5916 +897 898 +897 899 +897 900 +897 4134 +900 1583 +900 1584 +900 1585 +900 1586 +900 1587 +901 902 +901 903 +901 904 +901 2084 +902 903 +902 904 +903 904 +903 2084 +905 1980 +905 3836 +906 907 +906 908 +906 909 +906 910 +906 911 +906 912 +906 1314 +906 1821 +906 1822 +907 908 +907 2443 +907 2445 +907 3311 +907 3313 +908 3311 +908 6957 +911 1821 +912 2872 +913 4802 +914 915 +914 965 +915 916 +915 917 +915 918 +915 919 +915 920 +915 921 +915 922 +915 923 +915 924 +915 925 +915 5274 +919 5101 +921 923 +921 924 +921 3051 +922 923 +922 2566 +922 2567 +922 6830 +922 6831 +923 1358 +923 3213 +926 1697 +926 4700 +927 2662 +928 929 +928 930 +928 931 +928 932 +928 933 +928 934 +928 935 +928 936 +928 937 +928 938 +928 939 +928 940 +928 941 +928 4085 +929 930 +930 931 +930 932 +930 2003 +930 2662 +930 2668 +930 3422 +930 3924 +930 3925 +930 5287 +930 5630 +931 1363 +932 2662 +934 2212 +934 4085 +935 1162 +935 1941 +935 2015 +935 2332 +935 2604 +935 2605 +935 3404 +935 4113 +936 4085 +937 938 +937 941 +937 985 +937 2410 +937 2675 +937 3399 +937 4113 +937 5092 +939 1268 +939 2413 +942 972 +942 1598 +942 2602 +942 4683 +942 4690 +943 944 +943 945 +943 6554 +944 1653 +944 1657 +946 4690 +947 948 +947 949 +947 3221 +948 4430 +948 5302 +948 6559 +948 6560 +949 3221 +949 3222 +949 4012 +949 4174 +949 4924 +949 6605 +950 951 +950 952 +950 953 +950 954 +950 955 +950 2662 +951 3712 +952 2662 +954 1151 +954 2423 +954 3397 +956 4590 +957 6773 +958 959 +958 960 +958 961 +958 3169 +959 1387 +959 4465 +962 963 +962 964 +962 3007 +962 5633 +963 4433 +964 4433 +965 966 +965 967 +965 968 +965 969 +965 970 +965 971 +965 972 +965 2531 +965 3098 +966 2531 +966 3098 +966 4765 +967 2401 +972 1296 +972 1297 +973 2661 +974 5215 +974 5217 +975 5380 +976 977 +976 978 +976 979 +976 980 +976 981 +977 1827 +980 3928 +980 5371 +982 4443 +983 984 +983 985 +985 1690 +985 1691 +985 1697 +985 1698 +985 1699 +985 3919 +985 4820 +985 5203 +985 5331 +985 5332 +985 5333 +985 5334 +985 5335 +985 5336 +985 5337 +985 5338 +985 5339 +985 5340 +985 5341 +986 987 +986 988 +986 989 +986 990 +987 989 +987 990 +988 990 +989 990 +989 1729 +989 3291 +989 4284 +989 4373 +989 4736 +989 6378 +991 6458 +992 993 +992 994 +992 995 +995 1434 +995 1435 +996 997 +996 998 +996 999 +1000 2531 +1000 3098 +1001 3539 +1002 1003 +1002 1004 +1002 1005 +1002 1006 +1002 1007 +1002 1008 +1002 1009 +1005 1006 +1005 1008 +1006 1007 +1006 1009 +1006 1820 +1006 2526 +1006 3292 +1007 1009 +1007 1820 +1007 2488 +1007 3292 +1008 1820 +1008 2526 +1008 2527 +1008 2529 +1009 1472 +1009 1481 +1009 1820 +1009 2526 +1009 3223 +1010 1011 +1010 1012 +1010 1013 +1010 1014 +1010 1015 +1010 1016 +1010 1017 +1011 2958 +1013 1014 +1013 1495 +1013 1496 +1015 2956 +1015 2958 +1015 3156 +1015 3234 +1015 3567 +1015 3897 +1016 1022 +1016 5174 +1017 1021 +1017 1022 +1017 1120 +1017 1121 +1017 1122 +1017 1123 +1017 1124 +1017 2555 +1017 3321 +1017 4855 +1017 6654 +1018 1512 +1019 2041 +1020 3440 +1021 1022 +1021 1023 +1022 1023 +1022 2527 +1022 3187 +1022 4849 +1022 4850 +1022 4851 +1022 4852 +1022 4853 +1022 4854 +1022 4855 +1022 4856 +1024 1025 +1024 1026 +1024 1027 +1024 1028 +1024 1029 +1024 1030 +1024 1031 +1024 1032 +1024 3540 +1025 3540 +1026 1947 +1026 3540 +1028 3540 +1029 1423 +1029 1427 +1029 2141 +1029 2142 +1029 3750 +1031 3607 +1031 3702 +1031 3750 +1031 4182 +1031 5252 +1032 3540 +1032 3546 +1033 1126 +1033 1487 +1034 1035 +1037 1038 +1037 1039 +1037 1040 +1038 1039 +1039 1040 +1039 3771 +1040 3771 +1041 3143 +1042 1043 +1044 1054 +1045 2610 +1046 4561 +1047 1048 +1047 1049 +1049 3679 +1050 3546 +1051 5340 +1052 1053 +1053 7023 +1055 1353 +1055 4015 +1056 3706 +1056 6453 +1057 1058 +1057 1059 +1058 1769 +1060 1061 +1061 2449 +1061 2499 +1061 2591 +1061 3024 +1061 3695 +1061 3843 +1061 4273 +1062 1063 +1062 1064 +1065 5091 +1066 6286 +1067 6286 +1068 6887 +1069 3759 +1070 1806 +1072 6774 +1073 1791 +1073 2289 +1074 1434 +1074 4057 +1075 3930 +1075 4556 +1077 1079 +1078 3371 +1079 1473 +1079 2049 +1079 2375 +1080 3371 +1080 3712 +1083 2131 +1084 1679 +1084 1681 +1086 3156 +1090 1932 +1091 6288 +1092 1093 +1092 1094 +1092 1095 +1093 1095 +1093 3652 +1093 4545 +1093 4993 +1093 4994 +1095 3529 +1096 1097 +1096 1098 +1096 1099 +1096 1100 +1096 1101 +1096 1102 +1096 4016 +1097 1379 +1097 1520 +1097 2397 +1098 4016 +1100 2655 +1100 3062 +1101 4115 +1101 4741 +1103 1138 +1103 2553 +1104 6777 +1105 1106 +1105 1107 +1105 1108 +1105 1109 +1105 1110 +1105 1111 +1105 1112 +1105 2017 +1105 2263 +1108 1223 +1113 1716 +1113 3020 +1114 1180 +1114 3510 +1115 1116 +1116 3930 +1117 1234 +1117 1679 +1118 3030 +1118 4706 +1125 2212 +1126 1127 +1126 1128 +1126 1129 +1126 1130 +1126 1131 +1126 1132 +1126 1133 +1126 1134 +1126 1135 +1126 1136 +1126 1479 +1128 1133 +1128 1479 +1128 1565 +1128 1635 +1128 1666 +1128 1667 +1128 1668 +1128 1669 +1128 1670 +1129 1212 +1130 1133 +1130 1479 +1130 2589 +1132 3672 +1133 1444 +1133 1445 +1133 1448 +1133 2587 +1133 2589 +1133 3798 +1134 1487 +1134 4545 +1136 1831 +1137 1138 +1137 1139 +1137 1140 +1137 1141 +1137 1142 +1137 1143 +1137 1144 +1137 1145 +1137 1146 +1138 1310 +1138 1425 +1138 1758 +1138 1955 +1138 2010 +1138 2011 +1138 2012 +1138 2013 +1138 2014 +1139 1140 +1139 1299 +1139 1888 +1139 2520 +1139 2846 +1139 3853 +1139 6647 +1140 1498 +1140 3853 +1141 1964 +1141 3853 +1141 4446 +1143 1838 +1144 4459 +1144 6263 +1147 1697 +1148 1781 +1148 2284 +1148 3490 +1149 1180 +1149 3800 +1149 4082 +1150 5238 +1151 1153 +1151 2774 +1151 3895 +1153 1154 +1153 1155 +1153 1156 +1153 1157 +1153 1158 +1153 1159 +1153 1160 +1154 1223 +1155 6270 +1156 1415 +1156 4116 +1158 1223 +1159 1960 +1161 3089 +1163 1487 +1164 1165 +1164 1166 +1168 5309 +1169 1170 +1169 1223 +1169 6930 +1172 1653 +1173 1653 +1173 2441 +1173 7200 +1180 1199 +1180 1200 +1180 1201 +1180 1202 +1180 1203 +1180 1204 +1180 1205 +1180 1206 +1180 1207 +1180 1208 +1180 1209 +1180 1210 +1180 1211 +1180 1212 +1180 1213 +1180 1214 +1180 1215 +1180 1216 +1180 1217 +1180 1218 +1180 1219 +1180 1220 +1180 1221 +1180 1353 +1180 1846 +1180 4415 +1181 4590 +1182 1201 +1182 1660 +1182 3733 +1182 4590 +1184 1188 +1184 1919 +1184 1925 +1184 4227 +1185 1188 +1185 1280 +1185 1281 +1185 2402 +1185 3148 +1186 1188 +1186 1925 +1186 3148 +1187 1188 +1188 1595 +1188 2402 +1188 3148 +1188 3396 +1188 3397 +1188 3398 +1188 6861 +1193 6820 +1194 2210 +1194 4191 +1194 5329 +1194 5330 +1196 1197 +1198 4961 +1198 5834 +1199 4503 +1199 5168 +1200 1722 +1200 2095 +1200 2096 +1201 1202 +1201 2547 +1201 2747 +1201 2748 +1201 2749 +1201 2750 +1201 2751 +1201 2752 +1201 2753 +1201 2754 +1201 2755 +1201 2756 +1201 2757 +1201 2758 +1201 2759 +1201 4415 +1201 5125 +1202 2771 +1202 2773 +1202 4415 +1202 4416 +1202 5922 +1203 5125 +1204 1205 +1204 3155 +1209 6569 +1210 3507 +1210 4155 +1210 4156 +1210 6387 +1211 2937 +1211 5016 +1212 1353 +1212 1948 +1212 1949 +1213 3574 +1213 5024 +1214 4379 +1214 5785 +1219 1420 +1219 1423 +1219 1475 +1219 1911 +1219 2161 +1219 2162 +1219 2163 +1219 2164 +1219 2165 +1219 2166 +1219 2167 +1219 2168 +1219 2169 +1221 1845 +1221 1846 +1221 2415 +1221 2416 +1221 2417 +1221 2418 +1221 2419 +1221 5163 +1221 6057 +1222 2662 +1223 1224 +1223 1225 +1223 1226 +1223 1227 +1223 1228 +1223 1229 +1223 1230 +1223 1231 +1223 1232 +1223 1233 +1223 1234 +1223 1235 +1223 1236 +1223 1237 +1223 1238 +1223 2306 +1223 2307 +1226 1227 +1229 1997 +1230 1231 +1230 2306 +1230 2307 +1230 3897 +1230 5189 +1231 2306 +1231 2307 +1231 5189 +1231 5198 +1232 5189 +1232 5198 +1233 1904 +1233 2103 +1234 1391 +1234 1679 +1234 1680 +1234 2170 +1234 3318 +1234 3616 +1234 4005 +1234 4006 +1234 4007 +1234 4008 +1234 4009 +1234 4010 +1236 1401 +1236 2160 +1236 2531 +1237 1619 +1237 1932 +1243 6700 +1244 2126 +1244 2200 +1244 3732 +1244 3836 +1245 4144 +1246 1247 +1246 2495 +1246 2499 +1246 2650 +1246 2653 +1246 4113 +1246 4443 +1247 2495 +1247 4113 +1248 1249 +1248 1250 +1249 1250 +1249 4906 +1251 4042 +1252 6373 +1253 1254 +1253 1255 +1253 1256 +1253 2213 +1256 3433 +1257 1258 +1259 1791 +1260 1558 +1260 3056 +1260 3156 +1261 3156 +1262 4963 +1263 1932 +1264 1265 +1264 1266 +1264 3527 +1265 1724 +1265 1980 +1265 3527 +1265 6429 +1266 3527 +1266 6429 +1267 3156 +1268 1269 +1268 1270 +1268 1271 +1268 1272 +1268 1273 +1268 1274 +1268 1275 +1268 1276 +1268 1277 +1270 2457 +1270 2808 +1270 4527 +1273 6573 +1273 6574 +1273 6575 +1278 3440 +1279 3581 +1279 5154 +1280 1281 +1280 1282 +1280 1283 +1280 1284 +1283 1925 +1283 6861 +1285 2196 +1286 1287 +1286 1288 +1286 1289 +1287 1359 +1289 1359 +1290 1828 +1291 1292 +1291 1293 +1291 1294 +1291 2650 +1291 6706 +1293 1877 +1293 2483 +1293 3156 +1293 3317 +1294 2054 +1294 2845 +1295 2265 +1297 4040 +1298 1299 +1298 2798 +1298 5000 +1298 6647 +1299 2477 +1299 2521 +1299 2846 +1299 2848 +1299 3963 +1299 6647 +1299 6674 +1300 6127 +1300 6129 +1300 6130 +1301 1302 +1303 2356 +1304 1487 +1304 2108 +1304 3732 +1304 4766 +1305 1306 +1305 1307 +1308 2919 +1309 5194 +1310 1980 +1310 3689 +1311 1312 +1311 6748 +1312 4716 +1313 2041 +1314 1315 +1314 1316 +1314 1317 +1314 1821 +1319 4431 +1321 1322 +1321 2568 +1321 3419 +1322 2569 +1322 3419 +1322 3698 +1325 1701 +1325 3600 +1325 4304 +1326 4804 +1327 6289 +1328 5181 +1329 1330 +1329 1331 +1330 6129 +1331 1862 +1332 3498 +1333 4135 +1334 2127 +1334 5274 +1335 3054 +1335 4015 +1336 1413 +1336 1980 +1336 4777 +1336 5404 +1337 6366 +1338 4831 +1339 2531 +1340 3156 +1342 1464 +1342 2108 +1343 3156 +1343 5189 +1344 3344 +1345 1346 +1345 7117 +1346 1544 +1346 5329 +1346 7117 +1347 3379 +1348 1896 +1348 2016 +1348 5689 +1348 7251 +1353 1354 +1353 1355 +1353 1356 +1353 1660 +1353 3588 +1353 4015 +1355 4862 +1356 1489 +1357 1358 +1357 1359 +1358 1359 +1360 1361 +1361 3192 +1361 3193 +1361 3194 +1362 1997 +1362 4195 +1362 4197 +1362 7186 +1363 1364 +1363 1365 +1363 1366 +1363 1367 +1363 1368 +1363 1369 +1363 1370 +1363 1371 +1363 1372 +1363 1373 +1363 4109 +1363 4476 +1365 1367 +1366 4063 +1366 4476 +1367 1368 +1367 1720 +1367 3649 +1367 3650 +1367 4476 +1368 1720 +1369 4476 +1370 4476 +1374 3798 +1375 3490 +1376 1377 +1376 1378 +1376 1379 +1376 1447 +1379 3488 +1380 1381 +1380 1382 +1380 1383 +1380 1384 +1380 1385 +1380 3287 +1380 3600 +1380 3601 +1381 1383 +1381 1384 +1381 1842 +1381 2956 +1381 2957 +1381 2958 +1381 2959 +1381 3600 +1381 3963 +1382 4188 +1383 4144 +1384 3287 +1384 3600 +1385 3458 +1385 3540 +1385 3601 +1386 1387 +1387 1574 +1387 2753 +1387 2917 +1387 3669 +1388 1389 +1388 1390 +1388 1391 +1388 1392 +1388 1393 +1388 1394 +1391 2260 +1391 2263 +1391 4051 +1392 2054 +1393 2058 +1393 2818 +1394 2054 +1394 2058 +1394 4493 +1395 4464 +1396 1397 +1396 1398 +1397 1398 +1397 1569 +1397 3459 +1398 3459 +1400 2053 +1400 2258 +1400 2329 +1400 2331 +1400 2411 +1400 2412 +1400 2413 +1401 2531 +1402 1403 +1402 1404 +1402 1405 +1402 1406 +1402 1407 +1402 1408 +1402 1409 +1402 1410 +1402 1411 +1402 1412 +1402 7243 +1404 7243 +1413 1763 +1413 1764 +1413 1980 +1413 5595 +1417 1418 +1419 1758 +1421 1422 +1423 1424 +1423 1425 +1423 1426 +1423 1427 +1423 1428 +1423 3018 +1425 1426 +1425 1797 +1425 2013 +1425 2014 +1425 2235 +1425 2373 +1425 2420 +1425 2984 +1425 3225 +1425 3748 +1425 3914 +1426 1883 +1426 3471 +1426 4656 +1426 4755 +1427 2141 +1427 2142 +1427 3813 +1429 1430 +1429 1431 +1429 1432 +1429 1433 +1429 2655 +1429 4135 +1430 1431 +1430 1432 +1430 1433 +1431 1433 +1434 1435 +1434 1436 +1437 1438 +1437 1439 +1438 2362 +1440 2414 +1440 3490 +1441 1442 +1442 1443 +1444 1445 +1444 1446 +1444 1447 +1444 1448 +1444 1449 +1444 1450 +1444 1451 +1444 1452 +1444 2589 +1445 2589 +1445 4160 +1445 4274 +1445 4741 +1446 1447 +1446 2589 +1446 2591 +1446 3585 +1446 4703 +1447 2322 +1447 2555 +1447 2587 +1447 2589 +1447 2593 +1447 2751 +1447 3585 +1447 3586 +1447 3587 +1447 3588 +1447 3589 +1447 3590 +1447 3854 +1447 4160 +1447 6674 +1448 2589 +1451 2555 +1453 2126 +1453 2960 +1453 4766 +1453 7154 +1454 1455 +1454 1456 +1457 1994 +1458 1781 +1459 3930 +1460 1461 +1460 1462 +1460 4741 +1461 1462 +1462 2524 +1462 4741 +1463 1464 +1463 1465 +1463 1466 +1463 2258 +1465 1466 +1466 2051 +1466 2052 +1466 2053 +1467 5549 +1470 2367 +1470 3929 +1470 4431 +1472 3292 +1474 2460 +1474 4015 +1474 4032 +1475 1481 +1475 1507 +1477 3948 +1478 1660 +1478 1694 +1481 2714 +1481 3292 +1481 7156 +1483 1900 +1483 2064 +1483 2519 +1483 2527 +1483 2837 +1483 2859 +1483 2860 +1483 2861 +1483 2862 +1483 2863 +1483 2864 +1483 2865 +1483 2866 +1483 2867 +1483 2868 +1483 2869 +1483 3510 +1485 4178 +1486 2214 +1486 6980 +1487 1488 +1487 1489 +1487 1490 +1488 1956 +1488 2108 +1488 4113 +1489 3211 +1491 4145 +1497 2956 +1497 3234 +1497 3599 +1497 6165 +1498 1499 +1498 1500 +1498 1501 +1498 1502 +1498 1503 +1498 1504 +1498 1505 +1498 1506 +1498 1507 +1498 1997 +1508 3761 +1509 1510 +1509 2807 +1509 4642 +1510 2807 +1511 3112 +1512 1513 +1512 1514 +1512 1515 +1512 1516 +1512 1517 +1512 1518 +1512 2238 +1512 4487 +1513 3750 +1514 1897 +1516 2238 +1516 4487 +1519 1520 +1519 1521 +1520 5106 +1522 2653 +1523 1524 +1523 1525 +1523 1526 +1523 1527 +1523 1528 +1523 1529 +1523 1530 +1523 1531 +1523 1532 +1523 1533 +1523 1534 +1523 1535 +1523 1536 +1523 1537 +1523 1538 +1523 1539 +1523 1540 +1524 4446 +1526 3299 +1526 3300 +1526 3301 +1526 3302 +1526 3303 +1526 3304 +1526 4446 +1527 2431 +1530 2431 +1530 5106 +1537 2671 +1541 5016 +1542 1543 +1542 1544 +1542 1545 +1543 1544 +1543 1545 +1543 1872 +1544 2447 +1544 3569 +1544 4260 +1544 4284 +1545 1736 +1545 3002 +1545 3026 +1546 1547 +1546 1548 +1546 1549 +1546 1550 +1546 1551 +1546 1552 +1546 1553 +1550 2260 +1550 3074 +1554 1674 +1554 3856 +1555 2454 +1556 4015 +1557 3608 +1559 1704 +1560 1561 +1560 1562 +1563 2808 +1563 5954 +1564 1565 +1565 1795 +1565 2260 +1566 1932 +1566 2258 +1568 1569 +1568 1570 +1568 1571 +1568 1572 +1568 1573 +1568 1574 +1568 1575 +1568 4801 +1569 1570 +1569 1571 +1571 1575 +1571 5586 +1574 2089 +1574 5586 +1576 2974 +1576 2975 +1577 3585 +1577 3706 +1578 6440 +1579 2674 +1579 4085 +1580 1581 +1580 1582 +1588 1869 +1589 1590 +1589 1591 +1589 1592 +1589 1593 +1589 1594 +1590 2645 +1590 3770 +1591 5796 +1593 2513 +1593 3094 +1593 4244 +1593 5056 +1593 5273 +1593 5480 +1593 5865 +1594 3093 +1596 6500 +1597 2798 +1598 1599 +1598 1600 +1598 1601 +1600 2388 +1600 3930 +1600 3976 +1602 1603 +1604 2424 +1605 1606 +1605 3988 +1607 1608 +1608 3972 +1608 3973 +1608 3974 +1608 3975 +1609 3533 +1610 1917 +1611 1612 +1611 1613 +1611 1614 +1611 1615 +1611 3081 +1616 1617 +1616 4797 +1618 2260 +1618 2261 +1618 4797 +1619 1620 +1619 1621 +1619 1622 +1622 2054 +1622 3228 +1623 1624 +1623 1625 +1626 1627 +1626 1628 +1626 1629 +1626 1630 +1626 1631 +1626 1632 +1626 1633 +1626 1634 +1626 2041 +1626 3590 +1626 6340 +1627 1633 +1633 1634 +1633 3836 +1633 4447 +1635 2725 +1635 2728 +1635 3816 +1636 1637 +1638 3258 +1639 1640 +1639 1641 +1639 1642 +1639 1643 +1639 1644 +1639 1645 +1639 1646 +1647 1648 +1647 4556 +1648 2313 +1648 2921 +1648 3111 +1648 3214 +1648 3470 +1648 3707 +1648 4257 +1648 5351 +1648 5841 +1648 6635 +1652 3955 +1652 4057 +1652 4058 +1653 1654 +1653 1655 +1653 1656 +1653 1657 +1653 1658 +1653 1659 +1653 1660 +1653 1661 +1653 1662 +1653 1663 +1653 1664 +1653 1665 +1654 1781 +1656 1657 +1660 3588 +1660 7143 +1662 2531 +1668 1877 +1668 4018 +1671 6054 +1672 1673 +1674 1675 +1674 1676 +1674 1677 +1677 3856 +1677 4134 +1678 2388 +1678 2394 +1679 1680 +1679 1681 +1679 1682 +1679 2263 +1679 3071 +1680 2263 +1680 2958 +1680 4755 +1680 6458 +1681 3907 +1683 1684 +1683 1685 +1683 5805 +1686 1687 +1686 1970 +1686 4284 +1686 4373 +1686 4460 +1687 3088 +1687 3668 +1687 4260 +1687 4284 +1687 4360 +1687 4373 +1687 4374 +1687 4460 +1687 4964 +1687 5585 +1687 6405 +1688 4016 +1688 4018 +1689 3488 +1689 4741 +1690 1691 +1690 1692 +1691 3317 +1693 3127 +1694 1695 +1694 1696 +1697 1698 +1697 1699 +1697 4289 +1698 4015 +1698 4032 +1698 4289 +1700 3555 +1700 5032 +1700 7315 +1700 7325 +1702 1703 +1704 1705 +1704 1706 +1704 1707 +1704 1708 +1704 2390 +1704 5189 +1705 2390 +1709 4000 +1710 4828 +1711 2332 +1712 1713 +1712 1714 +1712 1715 +1712 1716 +1712 1717 +1712 1718 +1712 1719 +1712 3488 +1712 5371 +1712 5477 +1713 7233 +1714 3617 +1715 5106 +1716 1717 +1716 4142 +1716 5477 +1717 4949 +1718 2531 +1718 3992 +1718 4016 +1718 4949 +1718 5106 +1722 1727 +1722 2733 +1722 2821 +1722 3403 +1722 4220 +1722 5293 +1722 5294 +1724 1885 +1724 4802 +1729 4373 +1730 1731 +1735 2531 +1737 4015 +1742 1744 +1742 4421 +1742 4802 +1742 6429 +1743 5106 +1752 3259 +1758 1759 +1758 1760 +1758 1761 +1759 1761 +1760 1761 +1761 2199 +1761 2941 +1761 4396 +1761 5614 +1762 4706 +1763 6340 +1764 1980 +1764 4777 +1765 3690 +1766 1768 +1766 3390 +1767 1768 +1767 3390 +1767 3488 +1768 3901 +1768 4181 +1769 1770 +1771 1772 +1771 2563 +1771 4195 +1772 2563 +1772 6409 +1773 6567 +1774 1775 +1774 3798 +1774 4750 +1775 3798 +1775 7173 +1776 2285 +1777 3056 +1778 2460 +1779 2690 +1780 2260 +1781 1782 +1781 1783 +1781 1784 +1781 1785 +1781 1786 +1781 1787 +1781 1788 +1781 1789 +1781 1790 +1781 1791 +1781 1792 +1781 1793 +1781 1794 +1785 3503 +1787 2284 +1787 2292 +1789 3490 +1789 3621 +1790 3503 +1790 3997 +1791 2144 +1794 2017 +1795 1796 +1795 1797 +1795 1798 +1795 1799 +1797 1888 +1797 3630 +1797 3917 +1800 1801 +1801 4208 +1802 1803 +1802 1804 +1803 4583 +1805 2674 +1805 3837 +1805 4716 +1806 2038 +1807 1808 +1807 1809 +1807 1810 +1811 1812 +1811 1813 +1811 1814 +1811 1815 +1811 1816 +1811 1817 +1811 1818 +1811 1819 +1813 1817 +1813 3191 +1816 5696 +1817 1819 +1817 5533 +1818 5061 +1819 3426 +1819 4811 +1820 1972 +1820 2526 +1820 2557 +1820 3292 +1820 3293 +1821 1822 +1821 1823 +1821 1824 +1821 1825 +1821 1826 +1822 1823 +1822 6796 +1822 6797 +1822 6798 +1822 6799 +1822 6800 +1828 1829 +1828 1830 +1831 1832 +1831 1833 +1831 1834 +1831 1835 +1831 1836 +1831 1837 +1832 3081 +1839 3844 +1839 4690 +1840 1841 +1840 1842 +1840 3600 +1842 1904 +1845 1846 +1845 5163 +1845 6057 +1846 1996 +1846 2417 +1846 2419 +1846 2603 +1846 2612 +1846 2613 +1846 2614 +1846 2615 +1846 2616 +1846 2617 +1846 2618 +1846 2619 +1846 2620 +1846 2621 +1846 2622 +1846 2623 +1846 2624 +1846 2625 +1846 2626 +1846 2627 +1846 2628 +1846 2629 +1846 2630 +1846 2631 +1846 2632 +1846 2633 +1846 2634 +1846 2635 +1846 2636 +1846 2637 +1846 2638 +1846 4513 +1846 6057 +1847 1997 +1848 1849 +1848 1850 +1848 1851 +1848 1852 +1848 1853 +1848 1891 +1848 3911 +1849 1891 +1850 1891 +1850 2312 +1853 3112 +1854 1855 +1854 1856 +1854 1857 +1855 5044 +1855 5377 +1855 5522 +1856 7225 +1856 7331 +1858 1862 +1858 1863 +1858 1864 +1858 1865 +1858 3053 +1858 3818 +1858 4939 +1858 5052 +1858 5094 +1859 3673 +1859 7193 +1860 1861 +1862 1863 +1862 1864 +1862 1865 +1862 1866 +1862 1867 +1862 1868 +1862 1869 +1862 3353 +1863 1864 +1865 3052 +1866 3353 +1866 5285 +1867 3353 +1870 4500 +1871 2523 +1872 1873 +1872 1874 +1875 2531 +1876 1877 +1876 1878 +1876 1879 +1876 1880 +1876 1881 +1876 2483 +1876 4460 +1877 1878 +1877 2482 +1877 2483 +1877 2484 +1878 1932 +1878 2483 +1886 3056 +1886 3879 +1886 4862 +1887 4364 +1887 4707 +1887 4708 +1887 4710 +1889 3270 +1890 1891 +1890 1892 +1891 1892 +1891 1928 +1891 3617 +1893 1894 +1895 2103 +1896 2016 +1896 2367 +1897 1898 +1897 1899 +1900 7179 +1901 1902 +1901 1903 +1901 1904 +1901 1905 +1901 1906 +1901 1907 +1901 3964 +1901 3967 +1901 3969 +1902 3964 +1902 3967 +1902 3969 +1903 3964 +1903 3967 +1903 3969 +1904 2130 +1904 2882 +1904 2883 +1904 2884 +1904 2885 +1904 2886 +1904 2887 +1904 2888 +1904 2889 +1904 2890 +1904 2891 +1904 2892 +1904 2893 +1904 2894 +1904 2895 +1904 2896 +1904 2897 +1904 2898 +1904 2899 +1904 2900 +1904 2901 +1904 2902 +1904 2903 +1904 2904 +1904 2905 +1904 2906 +1904 2907 +1904 3964 +1904 3967 +1904 3969 +1904 5115 +1904 5575 +1908 6503 +1909 1989 +1910 1932 +1911 1912 +1911 1913 +1914 6422 +1915 4061 +1916 2126 +1919 1926 +1920 2696 +1921 5598 +1922 4741 +1923 1924 +1925 1926 +1926 3559 +1926 4809 +1929 2332 +1930 5000 +1931 1932 +1932 2257 +1932 2258 +1932 2259 +1933 1934 +1935 3795 +1936 1937 +1936 1938 +1936 1939 +1936 1940 +1939 4059 +1940 2937 +1941 2332 +1941 2333 +1942 2571 +1942 2574 +1943 3224 +1944 1945 +1946 1947 +1946 3476 +1946 3902 +1946 5447 +1946 5496 +1946 5592 +1946 6366 +1947 2377 +1947 2378 +1947 2379 +1947 2380 +1947 5380 +1949 2881 +1950 1951 +1951 2981 +1952 3219 +1953 3353 +1954 1955 +1955 2193 +1955 2600 +1955 2601 +1955 3176 +1955 3177 +1955 4569 +1956 1957 +1958 1959 +1961 2053 +1961 2329 +1961 2413 +1962 1963 +1962 5404 +1963 2558 +1964 1965 +1964 1966 +1964 1967 +1964 1968 +1964 1969 +1964 3259 +1964 4446 +1965 3259 +1967 2388 +1969 4446 +1969 5156 +1970 2450 +1970 2452 +1970 2599 +1970 3709 +1970 3888 +1972 2301 +1972 3292 +1973 6162 +1973 6814 +1974 1975 +1974 1976 +1977 1978 +1977 1979 +1980 1981 +1980 1982 +1980 1983 +1980 1984 +1980 1985 +1980 1986 +1984 5414 +1986 2361 +1987 2289 +1988 3510 +1989 2258 +1990 1991 +1992 3800 +1992 5357 +1993 6776 +1995 2108 +1995 3447 +1995 5916 +1997 1998 +1997 1999 +1997 2000 +1997 2001 +1997 4195 +1997 5629 +1999 2480 +1999 2520 +1999 3586 +1999 7186 +2001 5629 +2002 5786 +2003 2662 +2004 2041 +2005 2372 +2006 4002 +2007 2008 +2007 2009 +2010 3469 +2010 4200 +2011 2213 +2013 2526 +2013 2529 +2013 2988 +2013 2989 +2013 2990 +2013 2991 +2013 2992 +2013 5093 +2015 6054 +2016 5689 +2016 7251 +2017 2018 +2017 2019 +2017 2020 +2021 2022 +2023 6127 +2023 6129 +2023 6130 +2023 7077 +2024 2214 +2024 7179 +2025 2026 +2025 2027 +2027 4977 +2027 6444 +2027 7287 +2028 2029 +2028 2030 +2031 2032 +2031 2033 +2033 2089 +2034 2035 +2034 2036 +2035 6440 +2038 2039 +2038 2040 +2041 2042 +2041 2043 +2041 2044 +2041 2045 +2041 2046 +2041 2047 +2041 2048 +2041 2049 +2041 2050 +2043 6289 +2044 2144 +2048 6340 +2050 5163 +2050 6057 +2052 2531 +2052 2539 +2053 2258 +2053 2331 +2053 2412 +2053 2413 +2053 3114 +2053 3115 +2053 3116 +2054 2055 +2054 2056 +2054 2057 +2054 2058 +2054 2059 +2055 2916 +2056 2058 +2057 2058 +2058 4545 +2058 4643 +2059 4366 +2061 3960 +2062 3334 +2062 3960 +2064 2115 +2076 4113 +2076 4114 +2077 2078 +2077 3761 +2080 2081 +2080 2082 +2083 6512 +2084 2085 +2084 2086 +2084 2087 +2085 3015 +2085 3228 +2085 3540 +2086 3228 +2088 7179 +2089 2090 +2089 2091 +2093 2094 +2097 2098 +2097 2099 +2097 2100 +2097 2101 +2097 3510 +2097 3632 +2099 2101 +2102 2103 +2102 2104 +2103 2104 +2103 3335 +2103 4179 +2103 4180 +2104 5272 +2105 2106 +2105 2107 +2105 2108 +2105 2109 +2105 2115 +2106 2452 +2106 2922 +2106 3829 +2106 3836 +2106 4862 +2107 2108 +2107 6820 +2108 2111 +2108 2143 +2108 4158 +2108 5170 +2110 6820 +2111 4482 +2111 4921 +2112 2113 +2112 2114 +2113 2114 +2113 4164 +2114 4164 +2114 4259 +2114 4755 +2114 4921 +2114 5318 +2115 2116 +2117 2118 +2117 2119 +2117 2120 +2117 2121 +2117 2122 +2117 2123 +2117 2124 +2117 3682 +2118 2381 +2119 2120 +2119 2122 +2120 2123 +2120 3623 +2120 4494 +2120 4495 +2120 5916 +2120 6488 +2120 6820 +2121 2122 +2122 2269 +2122 4771 +2125 2412 +2125 2558 +2126 2127 +2126 2128 +2126 2129 +2127 5274 +2129 2189 +2131 5817 +2132 2133 +2132 2440 +2133 2440 +2134 2135 +2134 2136 +2134 2137 +2134 2138 +2135 2136 +2135 2137 +2135 5755 +2136 2137 +2136 2138 +2137 4514 +2138 6000 +2139 2140 +2139 4115 +2141 3813 +2142 3813 +2144 2289 +2144 6289 +2148 2149 +2148 2150 +2149 4621 +2150 4621 +2151 2152 +2153 4446 +2154 5353 +2155 4431 +2156 2157 +2158 2159 +2158 2531 +2159 4018 +2160 6379 +2161 4748 +2161 4749 +2163 3075 +2163 4015 +2164 2755 +2166 3546 +2170 2974 +2170 2975 +2171 2172 +2171 2173 +2171 2174 +2171 2175 +2173 3098 +2175 2604 +2175 4949 +2176 3400 +2177 2798 +2177 4801 +2178 2179 +2178 4736 +2179 7252 +2180 2531 +2181 4906 +2182 2183 +2183 5933 +2184 2185 +2186 2924 +2187 3287 +2188 4655 +2189 3440 +2190 2191 +2192 7288 +2193 2194 +2194 2213 +2195 2196 +2195 2197 +2196 4636 +2196 4637 +2196 4638 +2196 4639 +2197 2442 +2198 4984 +2200 2201 +2202 6572 +2203 2206 +2203 2753 +2203 4465 +2204 2960 +2204 6153 +2205 2604 +2205 3027 +2206 2207 +2208 2209 +2210 4976 +2211 2212 +2212 2531 +2212 2537 +2212 3846 +2213 2214 +2213 2215 +2213 2216 +2213 2217 +2213 4015 +2213 4474 +2214 2218 +2214 2219 +2214 2220 +2214 2221 +2214 2222 +2214 2223 +2214 2224 +2214 2225 +2214 2226 +2214 2227 +2215 2531 +2215 4518 +2218 2653 +2218 4015 +2220 5314 +2221 2223 +2221 2862 +2228 2229 +2228 2230 +2228 2231 +2228 2232 +2228 4160 +2228 4162 +2228 4831 +2233 3454 +2233 3457 +2234 2364 +2235 3761 +2236 2251 +2237 2238 +2237 2239 +2238 4288 +2238 4356 +2238 4958 +2238 4959 +2238 4960 +2240 4651 +2243 3829 +2244 2958 +2245 2246 +2247 2248 +2247 2249 +2250 3760 +2250 5156 +2251 2252 +2253 3997 +2253 4145 +2254 2255 +2254 2256 +2258 2412 +2259 2531 +2260 2261 +2260 2262 +2260 2263 +2260 2264 +2263 3318 +2263 3661 +2264 4672 +2266 5248 +2267 2268 +2270 2271 +2270 2272 +2270 2273 +2274 3283 +2274 5020 +2274 5198 +2275 2276 +2275 2277 +2275 2278 +2279 2280 +2281 2282 +2281 2283 +2283 3605 +2283 4306 +2284 5668 +2285 2286 +2285 2287 +2285 2288 +2289 2290 +2292 4291 +2292 4490 +2292 5011 +2292 5668 +2293 2294 +2293 2295 +2293 2296 +2293 2297 +2293 2298 +2293 2299 +2293 2300 +2293 2301 +2293 2302 +2293 2303 +2295 2301 +2296 4915 +2296 4922 +2301 2420 +2301 3442 +2304 3540 +2305 2602 +2305 3490 +2306 2307 +2306 2308 +2306 2309 +2307 2308 +2307 2309 +2308 2309 +2310 2311 +2310 2312 +2314 2315 +2314 2316 +2314 2317 +2314 2318 +2314 2319 +2314 6120 +2321 7092 +2323 6910 +2328 6075 +2329 2330 +2329 2331 +2331 2413 +2331 3625 +2331 3629 +2332 2333 +2332 2334 +2332 2335 +2332 2336 +2333 3702 +2337 2338 +2337 2339 +2337 2340 +2338 2339 +2338 2340 +2339 2340 +2341 2796 +2342 2343 +2342 5438 +2344 2345 +2345 5794 +2346 4704 +2347 2348 +2347 2349 +2347 2350 +2347 2351 +2347 2352 +2347 2353 +2347 2354 +2350 5831 +2354 2531 +2355 3776 +2356 2357 +2356 2358 +2356 2359 +2356 2360 +2359 2360 +2360 6822 +2365 2366 +2365 7018 +2367 5689 +2367 6038 +2368 3498 +2369 2370 +2370 2371 +2373 2984 +2373 3748 +2373 3914 +2373 4347 +2375 3829 +2375 5376 +2379 2704 +2379 2731 +2382 2383 +2382 2384 +2382 2385 +2386 2387 +2388 2389 +2388 2390 +2388 2391 +2388 2392 +2388 2393 +2388 2394 +2388 5317 +2390 3037 +2390 3038 +2390 3039 +2390 3040 +2390 3041 +2390 3042 +2390 3043 +2390 3044 +2390 3045 +2395 2396 +2396 2500 +2397 3488 +2398 3490 +2398 3493 +2398 3494 +2399 2975 +2399 2996 +2399 3618 +2399 3621 +2400 2401 +2403 2404 +2406 2407 +2408 2531 +2409 4344 +2411 3843 +2413 2432 +2413 4949 +2418 6057 +2419 2613 +2419 2776 +2419 5163 +2419 6057 +2421 2422 +2421 2423 +2423 5954 +2424 6333 +2425 2426 +2426 2945 +2427 4876 +2427 6453 +2428 2429 +2430 6133 +2432 6573 +2432 6574 +2432 6575 +2433 2434 +2433 2435 +2433 2436 +2433 2437 +2433 2438 +2434 2437 +2434 2644 +2434 2645 +2434 2646 +2434 2647 +2434 2648 +2435 4763 +2435 4764 +2439 3081 +2441 3693 +2443 2444 +2443 2445 +2443 2446 +2443 6957 +2445 3096 +2446 4815 +2447 3800 +2447 6895 +2448 3443 +2449 2450 +2449 2451 +2449 2452 +2449 2922 +2450 2452 +2450 3829 +2450 4583 +2450 6820 +2451 2452 +2451 3829 +2452 2597 +2452 2598 +2452 2599 +2452 2923 +2452 3839 +2452 3888 +2452 4275 +2452 4530 +2452 5912 +2453 2555 +2454 2455 +2454 2456 +2457 2458 +2457 2808 +2459 2460 +2459 2495 +2460 2495 +2461 2462 +2463 4431 +2464 3477 +2465 3352 +2466 3960 +2467 2468 +2469 2470 +2471 2472 +2471 2473 +2471 2474 +2471 2475 +2471 2476 +2472 6096 +2473 5943 +2473 6096 +2473 6124 +2473 6301 +2473 6960 +2474 6096 +2475 6096 +2476 6096 +2477 2478 +2477 3963 +2479 5752 +2481 2531 +2481 5101 +2482 2494 +2482 3210 +2483 2484 +2484 2704 +2484 4115 +2484 4802 +2484 5911 +2485 3027 +2486 3733 +2487 2488 +2488 3070 +2488 3324 +2488 4060 +2488 4253 +2488 4254 +2488 4255 +2488 4256 +2489 4261 +2490 2491 +2492 3317 +2493 2494 +2495 2496 +2495 2497 +2495 2498 +2495 2499 +2496 2499 +2498 2653 +2499 2592 +2501 4203 +2502 2503 +2502 2504 +2502 2505 +2502 2506 +2502 2507 +2502 2508 +2502 2509 +2502 2510 +2502 2511 +2512 4594 +2513 2514 +2513 2515 +2513 5480 +2513 5677 +2515 3249 +2517 5981 +2518 2862 +2520 2549 +2520 4152 +2520 4153 +2521 3854 +2521 6647 +2522 3094 +2524 4015 +2525 4458 +2526 2527 +2526 2528 +2526 2529 +2526 2530 +2526 3292 +2527 2862 +2527 7179 +2528 2529 +2528 3292 +2528 3294 +2529 2530 +2530 2588 +2531 2532 +2531 2533 +2531 2534 +2531 2535 +2531 2536 +2531 2537 +2531 2538 +2531 2539 +2531 2540 +2531 2541 +2531 2542 +2531 2543 +2531 2544 +2531 2545 +2533 3798 +2535 7174 +2536 2910 +2536 3174 +2539 3341 +2539 4344 +2539 4398 +2539 4399 +2541 3858 +2541 5398 +2543 4564 +2543 4678 +2543 4679 +2548 3540 +2549 2550 +2549 2551 +2549 2552 +2549 2555 +2549 3963 +2549 5422 +2549 6674 +2550 2555 +2550 3267 +2553 2554 +2553 2555 +2553 2556 +2555 3321 +2558 2559 +2558 2560 +2558 2561 +2558 2562 +2558 5827 +2561 3310 +2563 2564 +2563 4195 +2564 7288 +2565 6659 +2566 5093 +2570 5181 +2571 2572 +2571 2573 +2571 2574 +2571 2575 +2571 2576 +2571 2577 +2574 2948 +2574 3298 +2574 6340 +2577 3721 +2578 2579 +2578 2580 +2579 2580 +2581 2582 +2581 2583 +2581 2584 +2581 2585 +2581 2586 +2587 2589 +2587 3585 +2588 4970 +2589 3268 +2589 4942 +2590 3458 +2590 4306 +2590 6581 +2592 2750 +2594 2846 +2594 3853 +2595 2872 +2596 4801 +2597 2598 +2597 2599 +2597 3829 +2597 3836 +2597 3839 +2597 4583 +2599 3829 +2599 6820 +2601 3176 +2604 2605 +2607 2608 +2607 2609 +2610 2611 +2613 6576 +2620 6057 +2639 2640 +2641 2642 +2641 2643 +2643 3198 +2643 3199 +2643 3733 +2643 4988 +2643 5939 +2643 6016 +2644 2646 +2644 2647 +2644 5015 +2648 6907 +2649 4831 +2650 2651 +2650 2652 +2650 2653 +2650 2654 +2651 4443 +2652 2653 +2652 3585 +2653 2654 +2653 3027 +2653 3028 +2653 3585 +2653 6638 +2653 6639 +2654 3585 +2655 2656 +2655 2657 +2655 2658 +2655 2659 +2655 2660 +2655 3062 +2655 4876 +2658 3098 +2659 3103 +2662 2663 +2662 2664 +2662 2665 +2662 2666 +2662 2667 +2662 2668 +2662 2669 +2670 4673 +2672 2673 +2674 2675 +2674 2676 +2674 2677 +2674 2678 +2674 2679 +2676 4195 +2678 2683 +2679 4195 +2680 2681 +2680 2682 +2683 3673 +2684 3344 +2685 2686 +2685 2687 +2685 2688 +2685 2689 +2685 2802 +2685 4126 +2686 7175 +2687 2688 +2687 2801 +2687 2802 +2687 2803 +2687 2804 +2687 2805 +2687 2806 +2687 4126 +2688 2799 +2688 2800 +2688 2801 +2688 2802 +2688 4126 +2690 2691 +2690 2692 +2690 2693 +2690 2694 +2692 2693 +2692 3154 +2695 2696 +2695 2697 +2696 2697 +2696 3142 +2696 3143 +2698 5699 +2700 4689 +2701 4160 +2702 2703 +2704 2705 +2706 2707 +2708 2709 +2708 2710 +2708 2711 +2708 2712 +2708 5874 +2710 2958 +2710 6722 +2713 5880 +2714 2715 +2714 2716 +2714 2717 +2714 2718 +2714 2719 +2715 2716 +2715 2717 +2715 2719 +2715 2925 +2715 2926 +2715 2927 +2715 2928 +2715 2929 +2715 2930 +2715 2931 +2715 2932 +2715 2933 +2715 2934 +2715 2935 +2715 2936 +2715 5631 +2715 5877 +2715 5962 +2715 6817 +2715 7194 +2720 2721 +2720 2722 +2720 2723 +2724 5862 +2725 2726 +2725 2727 +2725 2728 +2725 2729 +2726 2728 +2726 3816 +2727 2728 +2727 3816 +2728 3816 +2729 3816 +2732 3605 +2733 2734 +2733 2735 +2733 2736 +2733 2737 +2734 2737 +2738 2739 +2738 2740 +2741 2742 +2741 2743 +2741 2744 +2742 2774 +2742 7310 +2745 2746 +2750 2752 +2750 5139 +2752 3027 +2753 2774 +2753 2779 +2753 2915 +2753 2916 +2753 2917 +2753 2918 +2753 4415 +2753 6667 +2753 7179 +2755 3077 +2755 3736 +2761 5629 +2762 2763 +2762 2764 +2762 2765 +2762 2766 +2762 2767 +2762 2768 +2762 2769 +2763 2764 +2763 2765 +2763 2766 +2763 2767 +2763 2768 +2763 2769 +2763 5120 +2763 6645 +2763 7036 +2764 2765 +2764 2766 +2764 2767 +2764 2768 +2764 2769 +2765 2766 +2765 2767 +2765 2768 +2765 2769 +2765 4645 +2765 4945 +2766 2767 +2766 2768 +2766 2769 +2767 2768 +2767 2769 +2768 2769 +2768 5421 +2768 6385 +2768 6571 +2770 2771 +2770 2772 +2770 2773 +2770 2774 +2770 2775 +2771 2775 +2772 2775 +2773 2775 +2774 2775 +2774 3575 +2774 3895 +2774 5400 +2775 3807 +2779 4580 +2780 3829 +2781 2782 +2781 2783 +2781 2784 +2781 2785 +2785 5803 +2786 2787 +2786 2788 +2786 2789 +2789 4866 +2790 3493 +2791 6437 +2791 6438 +2792 4730 +2793 4716 +2794 2795 +2796 2797 +2798 2846 +2800 2802 +2800 7106 +2801 2802 +2802 4126 +2807 4642 +2808 2809 +2808 2810 +2808 2811 +2808 2812 +2808 2813 +2812 3617 +2814 2815 +2816 2817 +2819 2820 +2823 2824 +2823 6055 +2825 4991 +2826 2827 +2828 2829 +2828 2830 +2828 5541 +2831 5878 +2832 5879 +2833 2919 +2834 2835 +2836 2837 +2837 5125 +2838 2839 +2838 2840 +2841 2842 +2842 2843 +2842 2844 +2846 2847 +2846 2848 +2846 2849 +2850 3855 +2851 6532 +2852 2853 +2852 3510 +2853 3510 +2853 4146 +2853 4147 +2853 4148 +2854 2855 +2856 3936 +2857 3863 +2862 2975 +2862 3339 +2862 7179 +2863 3292 +2863 3294 +2863 3510 +2870 7100 +2871 4683 +2872 2873 +2872 2874 +2872 2875 +2872 2876 +2872 2877 +2874 5500 +2875 5926 +2876 5691 +2876 5926 +2878 2958 +2878 3317 +2879 2880 +2908 2909 +2910 2911 +2910 2912 +2910 3702 +2911 6036 +2913 4716 +2914 6667 +2915 3624 +2916 5590 +2919 2920 +2919 5477 +2922 2923 +2922 4803 +2923 3829 +2926 7193 +2932 5080 +2937 2938 +2937 2939 +2937 2940 +2937 5416 +2940 5016 +2941 3540 +2942 2943 +2944 3702 +2946 4708 +2947 4364 +2948 3334 +2949 2950 +2950 6913 +2951 2952 +2951 2953 +2951 2954 +2951 2955 +2953 5285 +2953 6962 +2956 3234 +2956 3235 +2956 3236 +2956 4923 +2956 6428 +2956 7054 +2957 6428 +2958 3287 +2958 3316 +2958 3317 +2958 3318 +2958 3319 +2958 4306 +2961 2962 +2963 2964 +2963 2965 +2963 2966 +2967 2968 +2967 2969 +2968 2969 +2970 2971 +2970 2972 +2973 3994 +2973 4145 +2974 2975 +2974 2976 +2974 2977 +2975 2996 +2975 2998 +2975 3618 +2975 3620 +2975 5093 +2975 7018 +2978 4580 +2979 4716 +2979 7154 +2980 2981 +2982 5189 +2983 3612 +2984 3748 +2984 3913 +2984 3914 +2985 3502 +2986 4195 +2987 3390 +2987 3488 +2993 6340 +2994 2995 +2996 2997 +2996 2998 +2997 3618 +2997 4158 +2998 3618 +2998 3619 +2999 3000 +3001 3844 +3003 3004 +3003 3005 +3003 3006 +3003 3311 +3003 3312 +3003 3315 +3003 4130 +3004 3005 +3004 3006 +3004 3315 +3005 3006 +3005 3315 +3005 4130 +3005 5285 +3006 3315 +3006 4130 +3007 3008 +3007 3009 +3007 3010 +3007 3011 +3007 3012 +3007 3013 +3008 5633 +3013 5357 +3014 3498 +3014 5003 +3015 3016 +3015 3017 +3015 3018 +3015 3019 +3016 3017 +3016 3018 +3016 3204 +3016 4777 +3018 3653 +3018 3654 +3018 4777 +3020 3021 +3022 6020 +3023 6020 +3025 4421 +3026 3458 +3027 3028 +3027 3029 +3031 3311 +3032 6900 +3033 3034 +3033 3035 +3033 3036 +3041 6333 +3041 6456 +3046 3047 +3046 3048 +3046 3049 +3046 4415 +3046 6227 +3046 6452 +3046 7022 +3050 5615 +3050 5618 +3052 3053 +3054 3055 +3055 4015 +3056 3057 +3056 3156 +3058 3059 +3060 3061 +3062 3063 +3062 3064 +3062 3065 +3066 3067 +3066 3068 +3069 3661 +3071 3072 +3071 3073 +3075 3076 +3075 3077 +3075 3078 +3075 3079 +3075 3080 +3081 3082 +3081 3083 +3081 3084 +3081 3085 +3081 3086 +3081 3087 +3086 4197 +3089 3090 +3092 3600 +3093 3094 +3093 3095 +3093 3096 +3093 3548 +3094 4171 +3094 4244 +3094 4245 +3094 4246 +3094 4247 +3094 4248 +3094 4249 +3094 4250 +3095 5865 +3096 5480 +3096 5865 +3097 3134 +3098 3099 +3098 3100 +3098 3101 +3098 3102 +3098 3103 +3098 3104 +3098 3105 +3098 3106 +3098 3107 +3098 3108 +3102 3106 +3102 6840 +3103 3467 +3105 3106 +3106 4169 +3106 4170 +3108 6840 +3109 3110 +3112 3113 +3117 3118 +3117 3119 +3120 3121 +3120 3122 +3123 3472 +3124 3472 +3125 3126 +3128 6918 +3129 3130 +3129 3131 +3129 3132 +3133 5198 +3134 3135 +3134 3136 +3134 3137 +3134 6673 +3135 3136 +3135 3137 +3138 3602 +3140 3141 +3143 4760 +3144 3145 +3144 3146 +3144 3147 +3146 5503 +3146 6511 +3149 3150 +3149 3151 +3153 3904 +3153 4461 +3156 3157 +3156 3158 +3156 3159 +3156 3160 +3156 3161 +3156 3162 +3158 6467 +3161 4500 +3163 3164 +3163 3165 +3163 3166 +3164 3165 +3164 3166 +3164 3579 +3164 4692 +3165 3859 +3166 4323 +3167 3168 +3169 3170 +3169 3171 +3169 3172 +3169 3173 +3175 3448 +3176 3177 +3176 4569 +3177 3178 +3179 5909 +3180 3181 +3180 3182 +3183 3184 +3183 3185 +3186 3447 +3187 3188 +3187 3189 +3187 3190 +3187 4855 +3188 5479 +3195 3540 +3196 3197 +3196 3219 +3198 3199 +3198 3733 +3199 4993 +3199 4994 +3200 3201 +3202 6145 +3203 3416 +3207 3208 +3212 7233 +3215 3216 +3215 3605 +3216 3605 +3217 4804 +3218 4804 +3219 3220 +3221 3222 +3223 3292 +3226 3408 +3227 3733 +3228 3229 +3228 3230 +3228 3231 +3232 3233 +3234 3235 +3234 3236 +3234 3237 +3234 3238 +3234 3239 +3235 6165 +3235 6428 +3236 4565 +3236 6428 +3239 4015 +3239 7222 +3240 3241 +3240 3242 +3240 3243 +3241 4460 +3246 3255 +3247 5061 +3248 3252 +3248 3993 +3249 3252 +3249 5480 +3254 3255 +3255 3256 +3255 3426 +3259 3260 +3259 3261 +3259 3262 +3259 3263 +3259 3264 +3265 4970 +3266 4711 +3266 4878 +3269 3270 +3270 3271 +3270 4204 +3272 4489 +3273 4893 +3274 4804 +3275 3276 +3276 5633 +3277 3278 +3279 3280 +3281 5916 +3282 3960 +3283 3284 +3283 3285 +3283 5198 +3284 3285 +3284 5020 +3285 5020 +3286 5477 +3287 3288 +3287 3289 +3287 3290 +3287 3317 +3291 3317 +3291 4284 +3292 3293 +3292 3294 +3295 3296 +3297 4876 +3299 7047 +3305 3306 +3305 3307 +3305 3308 +3305 3309 +3307 5230 +3311 3312 +3311 3313 +3311 3314 +3311 3315 +3311 7235 +3312 3315 +3312 3857 +3312 6468 +3313 6393 +3313 7235 +3315 4130 +3315 5285 +3320 5317 +3321 3322 +3321 3323 +3325 3326 +3325 3327 +3325 3328 +3325 3329 +3325 3330 +3325 3331 +3325 3332 +3325 3333 +3329 3332 +3330 3332 +3331 3332 +3332 3333 +3332 4067 +3332 4068 +3332 4069 +3332 4070 +3332 4071 +3332 4072 +3333 5074 +3336 3337 +3338 5483 +3340 5444 +3342 3343 +3344 3345 +3344 4766 +3346 5884 +3349 3350 +3351 6995 +3353 3354 +3355 3356 +3355 3357 +3355 3358 +3355 3359 +3355 3360 +3355 3361 +3355 3362 +3355 3363 +3358 3363 +3362 3363 +3363 3364 +3363 3365 +3363 3366 +3363 3367 +3363 3368 +3363 3369 +3363 3370 +3371 3372 +3371 3373 +3371 3374 +3371 3375 +3371 3376 +3372 5165 +3373 4155 +3374 3428 +3377 4135 +3380 4149 +3380 4150 +3380 4151 +3381 3382 +3381 3383 +3384 3385 +3384 3386 +3384 3387 +3384 3388 +3384 3389 +3386 3389 +3386 4076 +3386 4077 +3386 4277 +3386 4278 +3390 3391 +3390 3392 +3390 3393 +3390 3394 +3390 3488 +3393 3503 +3395 4741 +3398 3569 +3400 3401 +3400 4262 +3402 5449 +3402 6846 +3405 3406 +3406 7039 +3407 4212 +3409 3410 +3411 3922 +3411 4599 +3412 3413 +3414 3415 +3416 3417 +3416 3418 +3417 4054 +3417 4551 +3420 6250 +3421 6567 +3425 3426 +3427 3701 +3427 4741 +3427 6845 +3428 3429 +3428 3430 +3428 3431 +3428 3432 +3434 3435 +3437 3438 +3439 3440 +3439 4690 +3440 3441 +3440 4562 +3440 5306 +3440 5591 +3443 3444 +3443 3445 +3443 3446 +3449 3450 +3449 3451 +3449 3452 +3454 3455 +3454 3456 +3454 3457 +3458 3482 +3458 4470 +3461 3462 +3461 3463 +3464 3465 +3464 3466 +3468 4655 +3469 3540 +3473 3930 +3474 3475 +3474 3577 +3475 3577 +3475 4592 +3477 3478 +3479 3560 +3480 3761 +3483 3581 +3483 3673 +3488 3489 +3490 3491 +3490 3492 +3490 3493 +3490 3494 +3490 3495 +3493 3578 +3494 3578 +3494 3591 +3494 3592 +3494 3621 +3496 3497 +3498 3499 +3498 3500 +3501 3502 +3502 3906 +3502 3944 +3502 4075 +3502 5265 +3502 5266 +3502 5267 +3502 5268 +3503 3504 +3503 3505 +3503 3997 +3503 6580 +3506 7112 +3507 4061 +3508 3509 +3510 3511 +3510 3512 +3510 3513 +3510 3514 +3510 3515 +3510 4401 +3510 7296 +3514 3576 +3516 3517 +3516 3518 +3516 3519 +3518 3519 +3520 3521 +3520 3522 +3520 3523 +3520 3524 +3525 3526 +3527 4344 +3528 3529 +3528 3530 +3528 3531 +3529 4497 +3532 6952 +3534 4065 +3535 3536 +3535 3537 +3537 3600 +3538 3929 +3539 4108 +3540 3541 +3540 3542 +3540 3543 +3540 3544 +3540 3545 +3540 3546 +3540 3547 +3546 3941 +3546 4294 +3546 4295 +3548 3549 +3548 3550 +3548 6918 +3549 4192 +3551 3552 +3554 4018 +3556 3557 +3556 3558 +3559 3560 +3560 3638 +3560 4975 +3561 3562 +3563 3564 +3563 3565 +3566 4704 +3567 3568 +3568 3850 +3568 6447 +3569 3893 +3570 3621 +3572 3573 +3573 4061 +3580 4840 +3581 3582 +3582 3673 +3582 4690 +3582 5154 +3582 5306 +3583 4740 +3583 5248 +3584 5494 +3586 5116 +3586 5812 +3588 4995 +3590 3848 +3593 3594 +3593 3595 +3593 3596 +3593 3597 +3593 6925 +3597 7178 +3598 3901 +3600 3601 +3602 3603 +3602 3604 +3605 3666 +3606 5249 +3608 3609 +3608 3610 +3608 3611 +3609 3611 +3613 3614 +3615 3935 +3616 4158 +3616 4862 +3616 7330 +3618 3619 +3618 3620 +3618 3621 +3619 3620 +3621 3847 +3621 3860 +3621 4003 +3621 4004 +3622 4158 +3624 3625 +3624 3626 +3624 3627 +3624 3628 +3624 3629 +3624 5404 +3624 6566 +3625 3629 +3625 3682 +3625 4036 +3628 3629 +3629 3682 +3629 4120 +3629 6667 +3632 4366 +3633 3634 +3635 4121 +3636 5197 +3637 4160 +3639 4716 +3640 3641 +3640 3642 +3641 3642 +3641 4824 +3642 4824 +3642 5403 +3644 5911 +3645 3646 +3645 3647 +3646 3647 +3649 3731 +3649 4476 +3650 6053 +3651 3673 +3652 4993 +3652 4994 +3656 3657 +3656 3658 +3656 3659 +3657 3658 +3660 4489 +3661 3662 +3663 5835 +3664 3665 +3667 3764 +3668 4373 +3670 3671 +3670 4426 +3671 4426 +3673 3674 +3673 3675 +3673 3676 +3673 3677 +3676 5154 +3676 6147 +3676 7154 +3678 4336 +3680 3681 +3682 4232 +3683 3684 +3683 3685 +3686 5040 +3687 3688 +3687 4590 +3690 3691 +3690 3692 +3694 3829 +3696 4460 +3697 3991 +3698 6795 +3699 3700 +3701 6845 +3702 3703 +3703 6765 +3704 3705 +3708 4755 +3709 5374 +3710 6916 +3711 5252 +3712 3713 +3714 6270 +3715 5284 +3716 3717 +3718 7002 +3719 3720 +3722 4476 +3723 3747 +3723 4232 +3724 4778 +3725 4207 +3725 4783 +3726 3727 +3728 4447 +3729 4035 +3730 5260 +3733 3734 +3735 5016 +3737 4065 +3738 5101 +3739 4580 +3740 3741 +3740 3742 +3742 5477 +3743 5252 +3743 6379 +3743 7114 +3744 3745 +3744 3746 +3748 3913 +3748 3914 +3749 4489 +3750 3751 +3750 3752 +3750 3753 +3754 4491 +3756 3757 +3757 3758 +3762 4226 +3764 3765 +3764 3766 +3764 3767 +3764 3768 +3765 5732 +3766 4455 +3769 7248 +3769 7249 +3771 3871 +3771 3872 +3771 3873 +3771 3874 +3771 3875 +3771 3876 +3771 3877 +3771 3878 +3772 3773 +3774 4553 +3775 4791 +3777 3778 +3777 3779 +3780 3781 +3780 3782 +3781 3782 +3781 3885 +3782 3885 +3783 3784 +3783 3785 +3783 3786 +3783 3787 +3784 3786 +3786 3787 +3786 5346 +3788 6212 +3790 3791 +3790 3792 +3790 3793 +3790 3794 +3790 3795 +3790 3796 +3790 3797 +3791 3795 +3793 3795 +3794 3795 +3795 4299 +3796 5649 +3798 3799 +3798 7173 +3799 4113 +3800 3801 +3800 3802 +3800 3803 +3802 4730 +3804 3805 +3805 4426 +3805 5018 +3808 3809 +3810 3811 +3811 3812 +3814 4217 +3815 4927 +3817 3818 +3817 3819 +3817 3820 +3817 3821 +3817 3822 +3818 5215 +3818 5216 +3818 5217 +3820 5215 +3820 5217 +3823 3824 +3825 3826 +3825 3827 +3825 3828 +3829 3830 +3829 3831 +3829 3832 +3829 3833 +3829 3834 +3829 3835 +3829 3836 +3829 3837 +3829 3838 +3829 3839 +3830 4167 +3834 6820 +3836 3839 +3837 4716 +3839 4528 +3839 4529 +3839 4530 +3840 3841 +3842 4785 +3843 3844 +3843 3845 +3844 3845 +3844 4081 +3844 4471 +3845 4081 +3845 4790 +3847 4163 +3850 3851 +3850 3852 +3850 5198 +3851 5855 +3853 3854 +3854 3927 +3854 4045 +3854 4175 +3854 4176 +3854 4177 +3854 4803 +3860 4716 +3861 4723 +3861 4724 +3862 6135 +3864 3865 +3864 3866 +3865 3866 +3866 4770 +3867 3868 +3867 3869 +3867 3870 +3872 4021 +3873 4021 +3873 6353 +3873 6655 +3879 3880 +3879 3881 +3879 3883 +3879 5210 +3879 6371 +3880 3882 +3880 3883 +3884 3905 +3884 3906 +3884 6130 +3884 6463 +3886 6353 +3886 6655 +3887 4903 +3889 6818 +3890 3891 +3894 4727 +3895 3896 +3897 4059 +3898 3899 +3900 3901 +3902 4580 +3903 3904 +3905 3906 +3905 6463 +3908 3909 +3910 4479 +3911 3912 +3913 4506 +3914 4504 +3914 4505 +3915 3916 +3918 3919 +3918 3920 +3921 6092 +3921 6342 +3922 3923 +3924 3925 +3926 5434 +3928 5371 +3929 3930 +3929 3931 +3929 3932 +3929 3933 +3929 4431 +3930 3931 +3930 3976 +3930 4117 +3930 4432 +3931 6662 +3934 4197 +3937 3938 +3937 3939 +3942 6757 +3943 4195 +3943 4197 +3945 3946 +3945 3947 +3947 4074 +3947 5253 +3949 3950 +3949 3951 +3953 3954 +3953 4228 +3953 4229 +3953 4230 +3953 4599 +3954 4228 +3954 4229 +3954 4230 +3955 4058 +3956 4330 +3957 3958 +3957 3959 +3960 3961 +3961 5219 +3962 4421 +3964 3965 +3964 3966 +3964 3967 +3964 3968 +3964 3969 +3964 3970 +3964 3971 +3965 3967 +3965 3969 +3966 3967 +3966 3969 +3967 3968 +3967 3969 +3967 3970 +3967 3971 +3968 3969 +3969 3970 +3969 3971 +3973 6932 +3977 7235 +3978 4015 +3981 6384 +3982 3983 +3983 5308 +3984 3985 +3986 5231 +3987 7288 +3989 3990 +3994 3995 +3994 3996 +3997 3998 +3997 3999 +4001 4035 +4005 5404 +4005 6413 +4005 6414 +4005 6647 +4011 5319 +4011 6034 +4013 4791 +4014 4059 +4015 4032 +4016 4017 +4016 4018 +4017 5249 +4018 4791 +4018 4792 +4018 5076 +4018 5077 +4019 4020 +4022 7142 +4033 4034 +4036 4656 +4037 4038 +4037 4039 +4040 4041 +4042 4043 +4044 5550 +4046 4047 +4048 5524 +4049 4050 +4052 4741 +4052 6554 +4053 4804 +4055 4056 +4057 4058 +4062 4518 +4064 4967 +4065 4066 +4070 4071 +4071 4072 +4071 6886 +4073 5085 +4074 4076 +4076 4077 +4076 4078 +4080 6884 +4082 4083 +4085 4086 +4085 4087 +4088 4089 +4088 4090 +4089 4090 +4092 4093 +4092 4094 +4092 4095 +4092 4096 +4092 4097 +4092 4098 +4094 6457 +4098 4230 +4099 4100 +4099 4101 +4099 4102 +4099 4103 +4104 5957 +4105 4106 +4105 4107 +4109 4110 +4109 4111 +4109 4112 +4112 4163 +4113 4114 +4115 4116 +4118 5249 +4120 6566 +4121 4122 +4123 4446 +4124 4217 +4125 6277 +4126 4127 +4126 4128 +4126 4129 +4126 4130 +4126 4131 +4126 4132 +4126 4133 +4128 6282 +4134 4135 +4134 4136 +4134 4137 +4138 4139 +4138 4140 +4141 5300 +4143 5565 +4157 4364 +4158 4159 +4160 4161 +4160 4162 +4163 4164 +4163 4165 +4166 6659 +4167 5100 +4168 7038 +4172 5776 +4173 5513 +4182 5252 +4183 4184 +4185 4186 +4187 4458 +4188 4189 +4190 4191 +4192 4193 +4192 4194 +4195 4196 +4195 4197 +4195 4198 +4195 4199 +4197 4199 +4197 5264 +4201 5442 +4202 4232 +4202 6704 +4204 4205 +4204 4206 +4205 4206 +4207 4783 +4209 4210 +4209 4211 +4213 4214 +4213 4215 +4215 4690 +4215 4830 +4216 7133 +4218 4219 +4218 6087 +4218 6088 +4218 6089 +4219 6087 +4219 6088 +4219 6089 +4220 4221 +4222 4223 +4224 4225 +4228 4229 +4228 4230 +4228 4231 +4229 4230 +4230 4231 +4230 4359 +4232 4233 +4234 4235 +4234 4236 +4234 4237 +4234 4238 +4234 4239 +4237 4238 +4240 4241 +4242 5156 +4244 6782 +4251 4252 +4257 5352 +4258 5319 +4262 4263 +4262 4741 +4264 4265 +4264 4266 +4264 4267 +4264 4268 +4266 4267 +4266 4943 +4266 4944 +4267 4943 +4267 4944 +4267 5852 +4269 4270 +4269 6289 +4270 6289 +4271 4767 +4272 4366 +4273 4274 +4276 4425 +4278 4973 +4278 6281 +4279 4280 +4279 4281 +4280 4281 +4282 4283 +4284 4373 +4285 4286 +4288 4487 +4290 6127 +4290 6129 +4290 6130 +4292 4293 +4296 4297 +4298 4791 +4300 4301 +4302 4303 +4303 4304 +4303 6130 +4304 4305 +4304 6130 +4306 4307 +4308 6508 +4309 4310 +4309 4311 +4309 4312 +4309 4313 +4310 4808 +4310 5731 +4314 4315 +4314 4316 +4314 4317 +4318 4319 +4318 4320 +4319 4320 +4321 6135 +4322 4545 +4324 7149 +4325 4326 +4325 4327 +4325 4328 +4325 4329 +4326 4327 +4326 4328 +4326 4329 +4327 4328 +4328 4329 +4330 4331 +4330 4332 +4330 4333 +4330 4334 +4334 5787 +4335 5194 +4337 4338 +4339 4340 +4339 4341 +4340 4341 +4342 6681 +4342 6751 +4342 6953 +4343 4590 +4344 4345 +4344 4346 +4348 4433 +4348 6200 +4349 6518 +4350 5478 +4351 4352 +4351 4353 +4351 4354 +4351 4355 +4356 4487 +4357 6342 +4358 5207 +4358 5929 +4360 4373 +4360 6405 +4361 4362 +4363 4708 +4364 4365 +4364 5380 +4364 5835 +4366 4367 +4368 5251 +4369 4370 +4371 4372 +4372 4519 +4372 4521 +4373 4374 +4373 4375 +4374 6014 +4376 4377 +4378 4893 +4380 5189 +4380 5198 +4381 4382 +4381 4383 +4381 4384 +4385 4386 +4387 4388 +4387 4389 +4387 4390 +4388 6601 +4391 6653 +4392 5885 +4393 4741 +4394 5056 +4394 6353 +4394 6655 +4395 5357 +4397 5098 +4397 5827 +4400 7011 +4402 5118 +4403 6419 +4404 4405 +4404 4406 +4405 4406 +4405 6015 +4406 6015 +4407 7133 +4408 4409 +4410 4411 +4410 4412 +4411 4412 +4413 4755 +4415 4416 +4415 4417 +4415 4418 +4415 4419 +4415 4420 +4416 7179 +4418 6178 +4421 4422 +4421 4423 +4424 7245 +4427 4428 +4429 4468 +4429 4469 +4433 4434 +4433 4435 +4433 4436 +4433 4437 +4433 4438 +4433 4439 +4433 4440 +4433 4441 +4442 6899 +4444 4445 +4445 6551 +4447 4448 +4449 4450 +4449 4451 +4449 4452 +4449 4453 +4449 5568 +4449 5569 +4450 4451 +4450 5568 +4451 5462 +4451 5568 +4451 5569 +4454 6129 +4455 4456 +4456 6124 +4457 6476 +4458 4459 +4460 6014 +4461 4462 +4461 4463 +4466 6114 +4467 6353 +4467 6655 +4468 4469 +4469 5624 +4472 4473 +4474 4475 +4476 4477 +4476 4478 +4476 4479 +4477 4479 +4477 5390 +4477 5391 +4478 4479 +4480 4481 +4480 6859 +4483 5173 +4484 4485 +4484 4486 +4484 4487 +4487 4958 +4487 4959 +4487 4960 +4488 4655 +4490 5011 +4490 6034 +4491 4492 +4498 4499 +4499 5248 +4501 4502 +4507 6092 +4508 4509 +4510 6123 +4511 4512 +4511 7154 +4515 5326 +4516 4517 +4519 4520 +4519 4521 +4522 7034 +4523 4524 +4525 4526 +4530 6484 +4530 6488 +4531 4532 +4533 4534 +4535 4536 +4537 4550 +4538 7154 +4541 5909 +4542 4543 +4544 4654 +4544 4655 +4546 4547 +4546 4548 +4548 5522 +4548 6296 +4549 4640 +4549 6090 +4552 6853 +4557 6634 +4558 4559 +4560 4626 +4563 6387 +4564 4678 +4565 4566 +4565 4567 +4568 6527 +4571 4572 +4571 4573 +4574 4575 +4574 4576 +4575 4576 +4577 4578 +4578 4655 +4579 4631 +4580 4581 +4580 4582 +4580 4583 +4580 4584 +4580 4585 +4580 4586 +4580 4587 +4580 4588 +4580 4589 +4580 4590 +4583 4893 +4588 5502 +4590 4591 +4592 4593 +4594 4595 +4596 5284 +4597 4598 +4599 4600 +4599 4601 +4599 4602 +4599 4603 +4599 4604 +4599 4605 +4599 4606 +4599 4607 +4599 4608 +4599 4609 +4599 4610 +4599 4611 +4599 4612 +4602 4603 +4613 4614 +4613 4615 +4613 4616 +4613 4617 +4613 4618 +4613 4619 +4613 4620 +4613 4693 +4614 4693 +4615 4673 +4615 4674 +4615 4693 +4622 6427 +4624 4625 +4627 4628 +4628 7329 +4629 4630 +4629 4631 +4629 4632 +4629 4633 +4629 6125 +4631 4632 +4631 4633 +4631 5415 +4632 6208 +4634 5502 +4641 4642 +4644 4791 +4644 4880 +4645 4646 +4646 5657 +4647 4893 +4648 4649 +4650 6211 +4652 4653 +4654 4655 +4657 6127 +4657 6129 +4657 6130 +4658 6232 +4659 4660 +4661 7321 +4662 4663 +4664 4665 +4664 4666 +4664 5578 +4664 6045 +4665 4667 +4665 5578 +4666 6786 +4668 6963 +4669 4670 +4671 5260 +4673 4674 +4673 4675 +4673 4676 +4673 4693 +4674 4676 +4674 5815 +4674 5817 +4677 6838 +4678 4679 +4678 4680 +4678 4681 +4678 4682 +4684 4685 +4684 4686 +4684 4687 +4684 7265 +4685 4686 +4685 4687 +4685 4719 +4685 7265 +4686 4687 +4687 7265 +4688 5118 +4690 4691 +4691 5414 +4694 4695 +4696 4697 +4696 4865 +4698 4699 +4701 4702 +4702 6792 +4705 5561 +4705 5827 +4705 6692 +4707 4708 +4707 4709 +4707 4710 +4708 4710 +4712 4713 +4714 4715 +4716 4717 +4718 5992 +4720 4721 +4720 4722 +4723 4724 +4725 4726 +4725 5716 +4728 4729 +4729 4961 +4729 5768 +4731 4732 +4731 4733 +4731 4734 +4735 4804 +4736 4737 +4736 4738 +4736 4739 +4736 4917 +4736 5875 +4738 5255 +4738 5256 +4738 5257 +4741 4742 +4741 4743 +4744 6906 +4745 4746 +4745 4747 +4750 4751 +4752 7017 +4753 4754 +4756 6075 +4757 4758 +4759 4828 +4761 4762 +4763 4933 +4763 6927 +4768 5443 +4768 5527 +4769 5954 +4772 4773 +4772 4774 +4772 5577 +4772 6505 +4773 4774 +4773 5577 +4773 6505 +4774 5577 +4775 4776 +4775 5961 +4776 5960 +4776 5961 +4778 4779 +4780 7256 +4781 4782 +4784 4785 +4784 4786 +4785 4787 +4785 4788 +4785 4789 +4785 5300 +4789 5589 +4791 4792 +4793 5396 +4794 5004 +4795 5238 +4796 5775 +4798 5305 +4799 4800 +4800 5741 +4804 4805 +4804 4806 +4807 5793 +4810 4811 +4810 4812 +4810 5061 +4813 7291 +4814 5805 +4815 4816 +4815 4817 +4815 4818 +4815 4819 +4817 5896 +4821 6289 +4822 4823 +4826 7116 +4827 7154 +4829 6499 +4831 4832 +4833 4834 +4833 4835 +4833 4836 +4833 4837 +4838 4839 +4839 5582 +4841 4842 +4841 4843 +4841 4844 +4841 4845 +4841 4846 +4841 4847 +4841 4848 +4844 4956 +4847 4848 +4857 4858 +4858 4877 +4858 5279 +4858 5316 +4859 4860 +4861 6801 +4862 4863 +4867 4868 +4873 6289 +4874 6751 +4875 6662 +4878 4879 +4880 4881 +4880 4882 +4880 6186 +4883 4884 +4883 4885 +4883 4886 +4883 4887 +4883 4888 +4889 4890 +4891 5445 +4892 5107 +4893 4894 +4893 4895 +4893 4896 +4893 4897 +4893 4898 +4895 7099 +4896 7099 +4899 5524 +4901 4902 +4904 4905 +4906 4907 +4909 4910 +4911 4912 +4913 4914 +4915 4916 +4915 4922 +4918 4919 +4925 6531 +4926 5397 +4927 4928 +4927 4929 +4927 4930 +4931 4932 +4934 5699 +4935 5914 +4935 6232 +4936 6151 +4937 5691 +4937 6782 +4938 7183 +4939 5300 +4940 5437 +4941 7095 +4946 4947 +4946 4948 +4949 4950 +4951 4952 +4953 7233 +4954 4955 +4956 4957 +4965 4966 +4968 4969 +4970 4971 +4972 4973 +4973 6280 +4974 5736 +4978 4979 +4980 5914 +4981 5012 +4982 4983 +4985 4986 +4988 4993 +4988 4994 +4988 6888 +4989 5887 +4990 5875 +4991 4992 +4991 5372 +4992 5087 +4992 5372 +4996 4997 +4998 4999 +5000 5001 +5000 5002 +5005 5006 +5005 5007 +5005 5008 +5009 5487 +5009 5488 +5011 5668 +5012 5013 +5012 5014 +5016 5017 +5019 5033 +5021 5022 +5021 5023 +5024 5025 +5024 5026 +5024 5027 +5024 5028 +5024 5029 +5024 5030 +5024 5031 +5029 6364 +5033 5034 +5033 5035 +5033 5036 +5033 5037 +5038 5923 +5038 6124 +5039 5040 +5042 6232 +5043 5804 +5044 5045 +5044 6296 +5045 5522 +5045 5648 +5045 5989 +5045 6276 +5045 7260 +5046 5891 +5047 6933 +5047 6934 +5048 5049 +5050 6974 +5051 5513 +5053 5755 +5054 6204 +5055 7299 +5057 6676 +5058 5623 +5059 5228 +5059 5300 +5059 5360 +5059 5943 +5059 6124 +5060 5930 +5061 5062 +5061 5063 +5064 5216 +5065 5066 +5067 5471 +5068 5752 +5072 6010 +5073 5930 +5073 6208 +5074 5075 +5075 5867 +5077 5864 +5078 5079 +5081 5082 +5083 5084 +5085 5086 +5089 5215 +5089 5217 +5090 6145 +5094 5095 +5095 6150 +5096 5280 +5097 5098 +5097 5099 +5098 5561 +5098 5827 +5101 5102 +5101 5103 +5101 5104 +5105 5550 +5108 5109 +5110 5111 +5112 5483 +5113 6554 +5114 6775 +5117 5445 +5118 5119 +5118 5120 +5118 6645 +5120 6645 +5121 5122 +5123 5124 +5126 5127 +5126 7272 +5128 5129 +5128 5130 +5128 5131 +5128 5132 +5128 5133 +5128 5134 +5128 5135 +5128 5136 +5128 5137 +5128 5138 +5129 5136 +5129 5137 +5130 5134 +5131 5136 +5131 5137 +5134 5136 +5134 5137 +5135 5136 +5135 5137 +5136 5137 +5136 5429 +5136 5430 +5136 5431 +5136 7283 +5136 7284 +5137 5429 +5137 5430 +5137 5431 +5140 6062 +5142 5943 +5143 6271 +5144 6791 +5145 5280 +5147 5529 +5148 5149 +5150 5151 +5152 5823 +5153 5235 +5155 6492 +5157 5158 +5159 6387 +5160 6606 +5161 6579 +5162 6669 +5164 6227 +5165 5455 +5166 5595 +5167 5691 +5167 5926 +5169 6379 +5171 5479 +5172 6973 +5175 5176 +5177 5178 +5177 5179 +5177 5180 +5178 5179 +5178 5180 +5179 5180 +5182 5183 +5184 5185 +5184 5186 +5187 7033 +5188 6340 +5189 5190 +5189 5191 +5189 5198 +5189 5855 +5192 6790 +5193 6557 +5194 5195 +5196 6112 +5197 6695 +5198 5199 +5198 5200 +5198 5201 +5198 5202 +5200 6602 +5200 6603 +5200 7248 +5200 7249 +5204 5205 +5204 5206 +5207 5208 +5207 5929 +5208 5929 +5208 6325 +5209 6371 +5211 6039 +5212 5213 +5212 5214 +5215 5216 +5215 5217 +5215 5218 +5216 5217 +5217 5218 +5220 5677 +5221 5222 +5223 5224 +5223 5225 +5223 5226 +5223 5227 +5228 5229 +5230 5231 +5231 5814 +5232 5233 +5232 5234 +5235 5236 +5235 5237 +5239 5240 +5241 5248 +5242 5243 +5242 5244 +5245 5246 +5245 5247 +5249 5250 +5258 5259 +5260 5261 +5260 5262 +5260 5263 +5269 5270 +5269 5271 +5274 5275 +5276 5277 +5278 6829 +5280 5281 +5280 5282 +5283 5579 +5285 5286 +5285 6962 +5285 7203 +5288 5289 +5290 6703 +5291 6208 +5292 6208 +5295 5845 +5295 6562 +5296 5775 +5297 5298 +5299 6000 +5301 6058 +5303 7201 +5304 7273 +5307 6621 +5307 7139 +5309 5310 +5311 5312 +5311 5313 +5315 6383 +5316 6896 +5319 6034 +5320 5321 +5322 5787 +5322 5796 +5322 6929 +5322 6952 +5323 5778 +5324 7098 +5325 6958 +5327 5328 +5339 5340 +5340 5873 +5342 7055 +5343 6769 +5344 5345 +5347 5348 +5349 5350 +5354 5355 +5354 5356 +5357 5358 +5357 6210 +5360 5361 +5360 5362 +5360 6248 +5361 6248 +5363 5471 +5364 5365 +5365 6021 +5366 6578 +5367 6448 +5368 6197 +5369 5370 +5373 7309 +5375 5376 +5378 5662 +5379 7098 +5380 5381 +5380 5382 +5380 5383 +5380 5384 +5385 5386 +5385 5387 +5385 5388 +5385 5389 +5392 5393 +5392 5394 +5392 5399 +5394 5736 +5395 5699 +5401 5402 +5404 7198 +5407 5408 +5409 5987 +5410 5411 +5410 5412 +5413 6208 +5415 5437 +5416 5417 +5416 5418 +5416 5419 +5416 5420 +5421 5802 +5421 5862 +5421 6385 +5423 6340 +5424 5425 +5424 5697 +5424 5698 +5425 5697 +5425 5698 +5426 5427 +5426 5428 +5432 5433 +5435 5988 +5436 6733 +5439 5835 +5440 5441 +5443 5897 +5446 5583 +5450 5451 +5452 5453 +5452 5454 +5456 5457 +5456 5458 +5456 5459 +5456 5460 +5456 5461 +5462 5568 +5462 5569 +5463 6670 +5464 6670 +5465 5466 +5466 6845 +5467 5533 +5468 5469 +5468 5470 +5469 5470 +5471 5472 +5471 5473 +5474 5475 +5476 6768 +5481 5482 +5483 5484 +5483 5485 +5483 5486 +5487 5488 +5487 5489 +5491 5563 +5492 5493 +5495 5751 +5495 6508 +5496 5497 +5496 5498 +5496 5499 +5503 5504 +5505 5506 +5508 5509 +5508 6812 +5509 6812 +5509 6813 +5510 6250 +5511 5512 +5514 5515 +5514 5516 +5517 6324 +5518 6124 +5519 5520 +5521 6296 +5523 6670 +5525 5526 +5527 5528 +5527 5897 +5529 5530 +5529 5531 +5529 5532 +5533 5534 +5535 5536 +5535 5537 +5535 5538 +5535 5539 +5535 5540 +5535 5541 +5536 5541 +5537 5541 +5538 5541 +5539 5541 +5540 5541 +5541 5542 +5543 6087 +5543 6088 +5543 6089 +5543 6447 +5544 5545 +5546 5868 +5547 5548 +5550 5551 +5550 5552 +5550 5553 +5553 5622 +5554 5555 +5554 6907 +5556 5823 +5557 6296 +5558 6301 +5558 6960 +5559 5560 +5562 6910 +5564 6279 +5566 5567 +5568 5569 +5569 5574 +5570 5571 +5570 5572 +5570 5573 +5575 5576 +5580 5581 +5584 5892 +5586 5587 +5586 5588 +5586 6012 +5586 6274 +5593 5752 +5594 6296 +5596 5597 +5599 6303 +5600 5601 +5602 6067 +5603 6535 +5604 5849 +5605 5606 +5605 5607 +5606 5607 +5608 5609 +5608 5610 +5608 5611 +5608 5612 +5611 6502 +5613 5626 +5615 5616 +5615 5617 +5615 5618 +5615 5619 +5616 5618 +5617 5618 +5618 5619 +5620 5621 +5622 5623 +5625 6223 +5627 6645 +5628 7248 +5628 7249 +5631 5632 +5633 5634 +5633 5635 +5633 5636 +5637 5638 +5637 5639 +5640 5641 +5642 5643 +5644 5645 +5644 5646 +5644 5647 +5648 6296 +5649 5650 +5649 5651 +5652 6204 +5653 5654 +5655 5656 +5657 5658 +5659 5660 +5659 6184 +5660 6184 +5661 6770 +5663 5664 +5663 5665 +5663 5666 +5663 5667 +5668 5669 +5670 5699 +5671 6199 +5672 6958 +5673 6127 +5673 6129 +5673 6130 +5674 6771 +5675 5676 +5678 5679 +5678 5680 +5681 5682 +5683 5684 +5683 5685 +5683 5686 +5683 5687 +5683 5688 +5689 5871 +5691 5692 +5691 5693 +5691 5926 +5694 5695 +5697 5698 +5699 5700 +5699 5701 +5699 5702 +5699 5703 +5699 5704 +5705 5706 +5707 6415 +5708 6652 +5709 5710 +5711 5712 +5713 5714 +5713 5715 +5714 5715 +5717 5718 +5719 5720 +5721 5722 +5723 7090 +5724 5725 +5726 5727 +5726 5728 +5726 5729 +5726 5730 +5727 5728 +5727 5729 +5730 7335 +5733 6877 +5734 5735 +5736 5737 +5736 5738 +5739 5740 +5742 5743 +5744 6561 +5745 5987 +5746 5787 +5747 7040 +5747 7041 +5748 6963 +5749 7000 +5750 7301 +5752 5753 +5752 5754 +5752 5755 +5754 5755 +5755 5913 +5756 6206 +5757 6651 +5758 5759 +5760 5936 +5761 5762 +5762 5763 +5764 5765 +5764 5766 +5764 5767 +5770 5771 +5773 5774 +5777 6248 +5779 5780 +5779 5781 +5780 5781 +5782 5783 +5782 5784 +5783 5784 +5787 5788 +5787 5789 +5787 5790 +5787 5791 +5787 5792 +5794 5795 +5796 5797 +5796 5798 +5796 5799 +5798 5904 +5798 6929 +5800 6009 +5801 6091 +5802 5862 +5806 5807 +5808 5809 +5810 5811 +5813 7097 +5815 5816 +5815 5817 +5818 5819 +5818 5820 +5818 5821 +5819 5820 +5819 5821 +5820 5821 +5822 6405 +5823 5824 +5823 5825 +5823 5826 +5828 5829 +5828 5933 +5829 5933 +5830 6232 +5832 5833 +5836 5837 +5838 5880 +5839 5840 +5842 5843 +5842 5844 +5845 7193 +5846 5858 +5847 5848 +5850 5851 +5853 5854 +5855 5856 +5859 5860 +5862 5863 +5865 5866 +5867 5868 +5868 7250 +5869 5870 +5872 6829 +5875 5876 +5876 6426 +5876 6519 +5876 6740 +5876 6741 +5876 6742 +5876 7096 +5880 5881 +5882 5883 +5886 5887 +5886 5888 +5887 6402 +5889 5890 +5889 6066 +5891 5892 +5893 6227 +5894 5895 +5898 6216 +5899 6944 +5900 5901 +5900 5902 +5900 6554 +5903 7186 +5904 5905 +5904 5906 +5907 5908 +5910 7148 +5914 5915 +5917 5918 +5919 6938 +5920 6199 +5921 6427 +5923 5924 +5923 5925 +5927 5928 +5930 5931 +5930 5932 +5934 6420 +5935 6711 +5936 5937 +5936 5938 +5937 7301 +5940 5941 +5942 6991 +5943 5944 +5945 5946 +5947 5948 +5951 7117 +5952 6493 +5953 5954 +5955 6553 +5956 6383 +5957 5958 +5957 5959 +5963 5964 +5965 5966 +5967 5968 +5969 7037 +5970 6910 +5971 5972 +5973 5974 +5975 5976 +5977 5978 +5979 6897 +5980 6068 +5981 5982 +5983 5984 +5985 5986 +5989 6296 +5990 6212 +5991 6296 +5993 6087 +5993 6088 +5993 6089 +5994 5995 +5994 5996 +5997 5998 +5998 6032 +5998 6033 +5999 6897 +6000 6001 +6002 6003 +6004 6839 +6005 6006 +6005 6007 +6008 6031 +6009 6010 +6011 6928 +6012 6013 +6017 6018 +6017 6019 +6022 6153 +6023 6751 +6024 6025 +6026 6027 +6028 6079 +6029 6030 +6034 6035 +6036 6037 +6038 7251 +6040 6041 +6040 6042 +6043 6044 +6043 6045 +6045 6786 +6046 6047 +6048 6049 +6050 6051 +6052 6087 +6052 6088 +6052 6089 +6055 6056 +6058 6059 +6060 6061 +6063 6064 +6065 6621 +6069 6070 +6071 6116 +6072 6073 +6072 6074 +6075 6076 +6075 6077 +6078 7154 +6079 6080 +6081 6082 +6083 6203 +6084 7151 +6085 7273 +6086 6802 +6087 6088 +6087 6089 +6088 6089 +6089 7193 +6092 6093 +6094 6095 +6096 6097 +6098 7093 +6099 6964 +6100 6101 +6100 6102 +6100 6103 +6104 6105 +6104 6106 +6104 6107 +6104 6108 +6105 6106 +6105 6107 +6106 6107 +6106 6108 +6107 6108 +6109 6207 +6110 7201 +6111 6375 +6113 6296 +6115 7150 +6117 6118 +6119 7105 +6120 6121 +6122 6341 +6125 6126 +6127 6128 +6127 6129 +6127 6130 +6127 6131 +6128 6436 +6129 6130 +6129 6131 +6129 6132 +6130 6131 +6133 6134 +6135 6136 +6135 6137 +6135 6138 +6135 6139 +6140 6141 +6140 6142 +6140 6143 +6140 6144 +6145 6146 +6152 6458 +6153 6154 +6155 6156 +6157 7085 +6158 7164 +6159 6669 +6160 6161 +6163 7185 +6164 6705 +6164 6914 +6165 6166 +6167 6168 +6169 6170 +6171 6172 +6173 6174 +6174 6444 +6174 7287 +6175 6176 +6177 6941 +6179 6180 +6179 6181 +6179 6182 +6179 6183 +6185 6224 +6187 6188 +6188 6189 +6190 6191 +6192 6977 +6193 6296 +6194 6396 +6195 6918 +6196 6227 +6198 6940 +6201 6202 +6205 6206 +6208 6209 +6213 6214 +6215 7149 +6216 6217 +6218 6219 +6220 7003 +6221 6959 +6222 6312 +6224 6225 +6226 6428 +6227 7022 +6228 6229 +6230 6231 +6233 6234 +6233 6235 +6234 6781 +6236 6237 +6237 6885 +6238 6948 +6238 6949 +6238 6950 +6239 6985 +6240 6947 +6241 6242 +6241 6243 +6244 6245 +6246 6247 +6249 7040 +6249 7041 +6251 6252 +6251 6253 +6251 6254 +6255 6256 +6255 6257 +6255 6258 +6259 6260 +6259 6261 +6259 6262 +6264 6383 +6265 6266 +6267 6995 +6268 6269 +6272 6273 +6273 7320 +6275 6822 +6278 7301 +6283 6284 +6283 6285 +6287 6289 +6290 6789 +6291 7163 +6292 7037 +6293 6960 +6294 6789 +6295 6942 +6296 6297 +6296 6298 +6299 6300 +6302 6346 +6304 6305 +6304 6306 +6308 7175 +6309 6310 +6309 6311 +6309 6933 +6309 6934 +6310 6311 +6310 7048 +6313 6962 +6314 6315 +6316 6902 +6317 6318 +6319 6320 +6319 6321 +6319 6322 +6319 6323 +6319 6395 +6319 6396 +6320 6321 +6320 6322 +6320 6323 +6320 6395 +6320 6396 +6321 6322 +6321 6323 +6321 6395 +6321 6396 +6321 6441 +6321 6442 +6321 6443 +6323 6396 +6326 7155 +6327 7068 +6328 6329 +6328 6330 +6331 6332 +6332 6903 +6334 7094 +6335 6959 +6336 6946 +6337 6735 +6338 6339 +6343 6344 +6345 6901 +6347 6348 +6349 6350 +6351 6352 +6353 6354 +6353 6355 +6354 6655 +6355 6655 +6356 6357 +6357 7065 +6358 6359 +6360 6361 +6362 6363 +6365 6901 +6367 6368 +6369 6370 +6371 6372 +6374 7038 +6376 6978 +6377 6944 +6379 6380 +6381 6789 +6382 6937 +6385 6386 +6387 6388 +6387 6389 +6387 6390 +6387 6391 +6387 6392 +6393 6394 +6395 6396 +6395 6397 +6396 6441 +6396 7134 +6396 7135 +6396 7136 +6396 7137 +6396 7138 +6398 7233 +6399 6400 +6403 6404 +6406 6407 +6406 6408 +6406 7018 +6409 6410 +6411 6412 +6416 6417 +6418 6852 +6420 6421 +6422 6423 +6424 6425 +6429 6430 +6429 6431 +6432 6433 +6434 6435 +6437 6438 +6439 6772 +6444 6602 +6444 6603 +6444 7248 +6444 7249 +6445 6446 +6449 6450 +6449 6451 +6450 6451 +6453 6454 +6455 6646 +6458 6459 +6458 6460 +6461 6462 +6463 6464 +6465 6466 +6468 6469 +6470 6471 +6472 6473 +6472 6474 +6472 6475 +6473 6474 +6474 6475 +6476 6477 +6476 6478 +6476 6479 +6476 6480 +6476 6481 +6476 6990 +6477 6478 +6478 6990 +6482 6483 +6484 6485 +6484 6486 +6484 6487 +6484 6488 +6484 6489 +6484 6490 +6485 6488 +6486 6488 +6487 6488 +6491 6896 +6494 6495 +6494 6496 +6497 6498 +6501 7267 +6501 7268 +6503 6504 +6506 6507 +6509 6510 +6512 6513 +6514 6519 +6515 6516 +6516 6754 +6517 6518 +6518 6723 +6519 6520 +6519 6521 +6519 6522 +6523 6524 +6525 6526 +6525 6527 +6527 6528 +6529 6530 +6533 6534 +6536 6537 +6536 6538 +6536 6539 +6540 6541 +6542 6543 +6544 6545 +6544 6546 +6544 6547 +6545 6547 +6546 6547 +6548 6787 +6548 6788 +6548 7036 +6549 6550 +6554 6555 +6554 6556 +6558 6942 +6559 6560 +6563 6564 +6565 7193 +6567 6568 +6570 7095 +6573 6574 +6573 6575 +6574 6575 +6582 6583 +6584 6585 +6584 6586 +6587 6588 +6589 6590 +6591 6592 +6593 6594 +6593 6595 +6596 6597 +6598 6599 +6598 6600 +6602 6603 +6603 6604 +6607 6608 +6609 6954 +6610 6611 +6610 6612 +6610 6613 +6610 6614 +6610 6615 +6610 6616 +6612 6613 +6612 6614 +6613 6614 +6613 6734 +6617 6618 +6617 6619 +6620 6812 +6620 6813 +6621 6622 +6621 6623 +6624 7255 +6625 6626 +6625 6627 +6625 6628 +6625 6629 +6625 6630 +6625 6631 +6632 6633 +6636 6637 +6640 6641 +6640 6642 +6643 6644 +6647 6648 +6649 6650 +6656 6657 +6656 6658 +6659 6660 +6659 6661 +6662 6663 +6664 6665 +6664 6666 +6664 6667 +6664 6668 +6665 6666 +6665 6667 +6665 6668 +6666 6667 +6666 6668 +6667 6668 +6671 6951 +6672 6951 +6675 6676 +6676 6677 +6679 6680 +6682 6683 +6684 7114 +6685 6686 +6687 6688 +6687 6689 +6690 6691 +6693 6930 +6694 6821 +6695 6696 +6695 6697 +6698 7244 +6699 7019 +6699 7020 +6699 7021 +6701 6702 +6705 6914 +6707 6708 +6709 6710 +6712 6713 +6712 6714 +6714 6752 +6715 6716 +6715 6717 +6715 6718 +6715 6719 +6715 6720 +6715 6721 +6724 6725 +6724 6726 +6727 6728 +6729 6730 +6731 6732 +6735 6736 +6735 6737 +6738 6739 +6741 7096 +6743 6744 +6745 6746 +6747 6748 +6749 6750 +6752 6753 +6755 6756 +6758 6759 +6758 6760 +6761 6762 +6763 7000 +6763 7001 +6764 7099 +6766 6767 +6778 6779 +6780 6781 +6782 6783 +6784 6785 +6787 6788 +6787 7036 +6793 6794 +6803 6804 +6805 6806 +6806 6807 +6808 6809 +6810 6811 +6812 6813 +6815 6816 +6819 7180 +6823 6824 +6825 6826 +6827 6828 +6830 6831 +6832 6834 +6833 6835 +6836 6837 +6841 6842 +6843 6844 +6847 6848 +6850 7028 +6851 7090 +6854 6855 +6854 6856 +6857 6858 +6859 6860 +6862 6863 +6862 6864 +6862 6865 +6862 6866 +6862 6867 +6862 6868 +6862 6869 +6862 6870 +6862 6871 +6862 6872 +6862 6873 +6862 6874 +6862 6875 +6862 6876 +6878 6879 +6878 6880 +6878 6881 +6878 6882 +6878 6883 +6879 6883 +6880 6882 +6880 6883 +6881 6882 +6889 6890 +6889 6891 +6892 6893 +6892 6894 +6897 6898 +6903 6904 +6903 6905 +6908 6909 +6911 6912 +6915 7033 +6917 6918 +6918 7199 +6919 7033 +6920 6921 +6922 6923 +6924 7192 +6926 6979 +6930 6931 +6933 6934 +6935 6936 +6939 6968 +6942 6943 +6945 7192 +6948 6949 +6948 6950 +6948 6976 +6949 6950 +6949 6976 +6955 6956 +6960 6961 +6965 6966 +6965 6967 +6969 6970 +6971 7201 +6972 7030 +6975 7078 +6981 6982 +6983 6984 +6983 6985 +6986 6987 +6986 6988 +6986 6989 +6991 6992 +6993 6994 +6996 6997 +6998 6999 +7000 7001 +7004 7005 +7006 7007 +7008 7009 +7008 7010 +7012 7013 +7014 7015 +7016 7081 +7019 7020 +7024 7025 +7026 7027 +7028 7029 +7031 7032 +7034 7035 +7040 7041 +7042 7043 +7044 7045 +7046 7111 +7049 7050 +7051 7052 +7051 7053 +7052 7053 +7057 7058 +7059 7060 +7059 7061 +7060 7061 +7062 7063 +7062 7064 +7066 7067 +7069 7070 +7069 7071 +7072 7073 +7074 7144 +7075 7093 +7076 7082 +7079 7080 +7083 7084 +7086 7087 +7088 7089 +7091 7092 +7101 7102 +7103 7104 +7107 7108 +7112 7113 +7114 7115 +7118 7119 +7118 7120 +7121 7257 +7122 7123 +7124 7125 +7126 7127 +7128 7129 +7130 7131 +7130 7132 +7131 7132 +7140 7141 +7145 7273 +7146 7147 +7152 7153 +7157 7158 +7159 7160 +7161 7162 +7165 7166 +7167 7168 +7169 7170 +7171 7172 +7176 7177 +7181 7318 +7182 7203 +7183 7184 +7187 7188 +7187 7189 +7188 7190 +7188 7191 +7189 7190 +7195 7196 +7201 7202 +7204 7205 +7206 7207 +7208 7209 +7210 7211 +7212 7213 +7214 7215 +7216 7217 +7218 7219 +7220 7221 +7223 7224 +7224 7269 +7224 7293 +7224 7319 +7226 7227 +7228 7229 +7230 7231 +7230 7232 +7233 7234 +7235 7236 +7237 7238 +7239 7240 +7240 7328 +7241 7242 +7246 7247 +7248 7249 +7253 7254 +7258 7259 +7261 7262 +7263 7264 +7266 7267 +7266 7268 +7267 7268 +7270 7271 +7273 7274 +7275 7276 +7275 7277 +7276 7277 +7278 7279 +7279 7280 +7281 7282 +7283 7284 +7285 7286 +7289 7290 +7294 7295 +7297 7298 +7302 7303 +7304 7305 +7304 7306 +7307 7308 +7310 7311 +7313 7314 +7316 7317 +7321 7322 +7323 7324 +7326 7327 +7332 7333 +7332 7334 +7335 7336 +7335 7337 +7335 7338 diff --git a/tests/unit/zapsmall.c b/tests/unit/zapsmall.c new file mode 100644 index 0000000..8e13175 --- /dev/null +++ b/tests/unit/zapsmall.c @@ -0,0 +1,49 @@ +/* + igraph library. + Copyright (C) 2022 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + igraph_matrix_t mat; + + igraph_matrix_init(&mat, 3, 3); + MATRIX(mat, 0, 0) = 1e-11; + MATRIX(mat, 0, 1) = -1e-11; + MATRIX(mat, 0, 2) = 1e-10; + MATRIX(mat, 1, 0) = -1e-10; + MATRIX(mat, 1, 1) = IGRAPH_INFINITY; + MATRIX(mat, 1, 2) = -IGRAPH_INFINITY; + MATRIX(mat, 2, 0) = IGRAPH_NAN; + MATRIX(mat, 2, 1) = 0.0; + MATRIX(mat, 2, 2) = 1.0; + + print_matrix(&mat); + igraph_matrix_zapsmall(&mat, 0); + print_matrix(&mat); + + CHECK_ERROR(igraph_matrix_zapsmall(&mat, -1), IGRAPH_EINVAL); + + igraph_matrix_destroy(&mat); + + /* TODO complex case */ + + VERIFY_FINALLY_STACK(); + + return 0; +} diff --git a/tests/unit/zapsmall.out b/tests/unit/zapsmall.out new file mode 100644 index 0000000..ced5d12 --- /dev/null +++ b/tests/unit/zapsmall.out @@ -0,0 +1,6 @@ +[ 1e-11 -1e-11 1e-10 + -1e-10 Inf -Inf + NaN 0 1 ] +[ 0 0 1e-10 + -1e-10 Inf -Inf + NaN 0 1 ] diff --git a/tests/unit/zero_allocs.c b/tests/unit/zero_allocs.c new file mode 100644 index 0000000..5f56313 --- /dev/null +++ b/tests/unit/zero_allocs.c @@ -0,0 +1,62 @@ +/* + igraph library. + Copyright (C) 2021-2024 The igraph development team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include "test_utilities.h" + +int main(void) { + int *a; + char *b; + + /* Macros */ + + a = IGRAPH_CALLOC(0, int); + IGRAPH_ASSERT(a); + + a = IGRAPH_REALLOC(a, 0, int); + IGRAPH_ASSERT(a); + + IGRAPH_FREE(a); + IGRAPH_ASSERT(!a); /* IGRAPH_FREE(a) sets 'a' to NULL */ + + /* We use type 'char' to work around warnings from some moderns compilers such as Clang 22: + * error: allocation of insufficient size '1' for type 'int' with size '4' [-Werror,-Walloc-size] */ + b = IGRAPH_MALLOC(0); + IGRAPH_ASSERT(b); + + IGRAPH_FREE(b); + IGRAPH_ASSERT(!b); + + /* Functions */ + + a = igraph_calloc(0, sizeof(*a)); + IGRAPH_ASSERT(a); + + a = igraph_realloc(a, 0); + IGRAPH_ASSERT(a); + + igraph_free(a); + + a = igraph_malloc(0); + IGRAPH_ASSERT(a); + + igraph_free(a); + + VERIFY_FINALLY_STACK(); + return 0; +} diff --git a/tools/removeexamples.py b/tools/removeexamples.py new file mode 100644 index 0000000..aae2b33 --- /dev/null +++ b/tools/removeexamples.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Helper script used to remove the bundled examples from the DocBook files +that are used to generate the PDF documentation. + +This file is part of the documentation build process. You do not need to call +it manually. +""" + +import sys +from xml.etree.ElementTree import ElementTree + + +def usage(): + print(sys.argv[0], " ") + + +def main(): + if len(sys.argv) != 3: + usage() + sys.exit(2) + + # Read in + tree = ElementTree() + tree.parse(sys.argv[1]) + + # Remove examples + examples = tree.findall(".//example") + for ex in examples: + prog = ex.find("programlisting") + ex.remove(prog) + + # Write result + tree.write(sys.argv[2]) + + +if __name__ == "__main__": + main() diff --git a/tools/strip_licenses_from_examples.py b/tools/strip_licenses_from_examples.py new file mode 100644 index 0000000..b516d56 --- /dev/null +++ b/tools/strip_licenses_from_examples.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Strips the license notices from the bundled igraph examples so they are +not visible in the documentation. +""" + +import argparse +import sys + +from pathlib import Path +from typing import IO + + +def strip_license_notice(infp: IO[str], outfp: IO[str]) -> None: + seen_code = False + + for line in infp: + if not seen_code: + # This is an approximation; we consider the first line that starts + # with a non-whitespace character, * or / as "real code", and we strip + # everything before that + if line and line[0] not in ' \n\t\r/*': + seen_code = True + outfp.write(line) + else: + outfp.write(line) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", "--out-dir", help="output directory to put the stripped files in", + ) + parser.add_argument('files', help="input files to process", nargs="*") + options = parser.parse_args() + + if not options.out_dir: + # We must have one or two args; one arg means that we are printing to + # stdout + if len(options.files) == 0: + return 0 + elif len(options.files) > 2: + parser.error( + "At most two files (one input, one output) must be given " + "when -o is not used" + ) + return 1 + + with Path(options.files[0]).open("r") as infp: + if len(options.files) > 1: + with Path(options.files[1]).open("w") as outfp: + strip_license_notice(infp, outfp) + else: + strip_license_notice(infp, sys.stdout) + + else: + # We have an output dir so we can handle an arbitrary number of input + # files + for filename in options.files: + path = Path(filename) + with path.open("r") as infp: + with (Path(options.out_dir) / path.name).open("w") as outfp: + strip_license_notice(infp, outfp) + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt new file mode 100644 index 0000000..3aa40ea --- /dev/null +++ b/vendor/CMakeLists.txt @@ -0,0 +1,9 @@ +add_subdirectory(cs) +add_subdirectory(f2c) +add_subdirectory(glpk) +add_subdirectory(infomap) +add_subdirectory(lapack) +add_subdirectory(mini-gmp) +add_subdirectory(pcg) +add_subdirectory(plfit) +add_subdirectory(qhull) diff --git a/vendor/cs/CMakeLists.txt b/vendor/cs/CMakeLists.txt new file mode 100644 index 0000000..bef000d --- /dev/null +++ b/vendor/cs/CMakeLists.txt @@ -0,0 +1,93 @@ +# Declare the files needed to compile our vendored CXSparse copy +add_library( + cxsparse_vendored + OBJECT + EXCLUDE_FROM_ALL + cs_add.c + cs_amd.c + cs_chol.c + cs_cholsol.c + cs_compress.c + cs_counts.c + cs_cumsum.c + cs_dfs.c + cs_dmperm.c + cs_droptol.c + cs_dropzeros.c + cs_dupl.c + cs_entry.c + cs_ereach.c + cs_etree.c + cs_fkeep.c + cs_gaxpy.c + cs_happly.c + cs_house.c + cs_ipvec.c + cs_leaf.c + cs_load.c + cs_lsolve.c + cs_ltsolve.c + cs_lu.c + cs_lusol.c + cs_malloc.c + cs_maxtrans.c + cs_multiply.c + cs_norm.c + cs_permute.c + cs_pinv.c + cs_post.c + cs_pvec.c + cs_qr.c + cs_qrsol.c + cs_randperm.c + cs_reach.c + cs_scatter.c + cs_scc.c + cs_schol.c + cs_spsolve.c + cs_sqr.c + cs_symperm.c + cs_tdfs.c + cs_transpose.c + cs_updown.c + cs_usolve.c + cs_util.c + cs_utsolve.c + # the following files are not needed - they contain no symbols + # cs_print.c +) + +target_include_directories( + cxsparse_vendored + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET cxsparse_vendored PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Disable complex number support for CXSparse because: +# - It is necessary to compile with MSVC +# - igraph does not need complex number support from CXSparse on any platform +target_compile_definitions(cxsparse_vendored PUBLIC NCOMPLEX) + +# Since these are included as object files, they should call the +# function as is (without a visibility specification) +target_compile_definitions(cxsparse_vendored PRIVATE IGRAPH_STATIC) + +use_all_warnings(cxsparse_vendored) + +if (MSVC) + target_compile_options( + cxsparse_vendored PRIVATE + /wd4100 + ) # disable unreferenced parameter warning +else() + target_compile_options( + cxsparse_vendored PRIVATE + $<$:-Wno-unused-variable> + ) +endif() + diff --git a/vendor/cs/License.txt b/vendor/cs/License.txt new file mode 100644 index 0000000..df6f425 --- /dev/null +++ b/vendor/cs/License.txt @@ -0,0 +1,19 @@ +CXSparse: a Concise Sparse matrix package - Extended. +Copyright (c) 2006, Timothy A. Davis. +http://www.suitesparse.com + +-------------------------------------------------------------------------------- + +CXSparse is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +CXSparse is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/vendor/cs/cs.h b/vendor/cs/cs.h new file mode 100644 index 0000000..f6ce945 --- /dev/null +++ b/vendor/cs/cs.h @@ -0,0 +1,315 @@ +/* This is a MODIFIED version of the original CXSparse/Include/cs.h file from + * SuiteSparse 5.12.0 (CXSparse version 3.2.0). The modifications are outlined + * here: + * + * - Dependency on SuiteSparse_long was removed + * - CXSparse is configured to use igraph_int_t as cs_long_t + * - CXSparse function prefix is set to cs_igraph instead of cs_igraph + * - Unneeded CXSparse function variants are removed + * + * The remaining comments below are from the original cs.h header */ + +/* ========================================================================== */ +/* CXSparse/Include/cs.h file */ +/* ========================================================================== */ + +/* This is the CXSparse/Include/cs.h file. It has the same name (cs.h) as + the CSparse/Include/cs.h file. The 'make install' for SuiteSparse installs + CXSparse, and this file, instead of CSparse. The two packages have the same + cs.h include filename, because CXSparse is a superset of CSparse. Any user + program that uses CSparse can rely on CXSparse instead, with no change to the + user code. The #include "cs.h" line will work for both versions, in user + code, and the function names and user-visible typedefs from CSparse all + appear in CXSparse. For experimenting and changing the package itself, I + recommend using CSparse since it's simpler and easier to modify. For + using the package in production codes, I recommend CXSparse since it has + more features (support for complex matrices, and both int and long + versions). + */ + +/* ========================================================================== */ + +#ifndef _CXS_H +#define _CXS_H +#include +#include +#include +#include +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#endif + +#include "igraph_types.h" + +#ifdef __cplusplus +#ifndef NCOMPLEX +#include +typedef std::complex cs_complex_t ; +#endif +extern "C" { +#else +#ifndef NCOMPLEX +#include +#define cs_complex_t double _Complex +#endif +#endif + +#define CS_VER 3 /* CXSparse Version */ +#define CS_SUBVER 2 +#define CS_SUBSUB 0 +#define CS_DATE "Sept 12, 2017" /* CSparse release date */ +#define CS_COPYRIGHT "Copyright (c) Timothy A. Davis, 2006-2016" +#define CXSPARSE + +#define cs_long_t igraph_int_t +#define cs_long_t_id "%" IGRAPH_PRId +#define cs_long_t_max IGRAPH_INTEGER_MAX + +/* -------------------------------------------------------------------------- */ +/* double/cs_long_t version of CXSparse */ +/* -------------------------------------------------------------------------- */ + +/* --- primary CSparse routines and data structures ------------------------- */ + +typedef struct cs_igraph_sparse /* matrix in compressed-column or triplet form */ +{ + cs_long_t nzmax ; /* maximum number of entries */ + cs_long_t m ; /* number of rows */ + cs_long_t n ; /* number of columns */ + cs_long_t *p ; /* column pointers (size n+1) or col indlces (size nzmax) */ + cs_long_t *i ; /* row indices, size nzmax */ + double *x ; /* numerical values, size nzmax */ + cs_long_t nz ; /* # of entries in triplet matrix, -1 for compressed-col */ +} cs_igraph ; + +cs_igraph *cs_igraph_add (const cs_igraph *A, const cs_igraph *B, double alpha, double beta) ; +cs_long_t cs_igraph_cholsol (cs_long_t order, const cs_igraph *A, double *b) ; +cs_long_t cs_igraph_dupl (cs_igraph *A) ; +cs_long_t cs_igraph_entry (cs_igraph *T, cs_long_t i, cs_long_t j, double x) ; +cs_long_t cs_igraph_lusol (cs_long_t order, const cs_igraph *A, double *b, double tol) ; +cs_long_t cs_igraph_gaxpy (const cs_igraph *A, const double *x, double *y) ; +cs_igraph *cs_igraph_multiply (const cs_igraph *A, const cs_igraph *B) ; +cs_long_t cs_igraph_qrsol (cs_long_t order, const cs_igraph *A, double *b) ; +cs_igraph *cs_igraph_transpose (const cs_igraph *A, cs_long_t values) ; +cs_igraph *cs_igraph_compress (const cs_igraph *T) ; +double cs_igraph_norm (const cs_igraph *A) ; +/*cs_long_t cs_igraph_print (const cs_igraph *A, cs_long_t brief) ;*/ +cs_igraph *cs_igraph_load (FILE *f) ; + +/* utilities */ +void *cs_igraph_calloc (cs_long_t n, size_t size) ; +void *cs_igraph_free (void *p) ; +void *cs_igraph_realloc (void *p, cs_long_t n, size_t size, cs_long_t *ok) ; +cs_igraph *cs_igraph_spalloc (cs_long_t m, cs_long_t n, cs_long_t nzmax, cs_long_t values, + cs_long_t t) ; +cs_igraph *cs_igraph_spfree (cs_igraph *A) ; +cs_long_t cs_igraph_sprealloc (cs_igraph *A, cs_long_t nzmax) ; +void *cs_igraph_malloc (cs_long_t n, size_t size) ; + +/* --- secondary CSparse routines and data structures ----------------------- */ + +typedef struct cs_igraph_symbolic /* symbolic Cholesky, LU, or QR analysis */ +{ + cs_long_t *pinv ; /* inverse row perm. for QR, fill red. perm for Chol */ + cs_long_t *q ; /* fill-reducing column permutation for LU and QR */ + cs_long_t *parent ; /* elimination tree for Cholesky and QR */ + cs_long_t *cp ; /* column pointers for Cholesky, row counts for QR */ + cs_long_t *leftmost ; /* leftmost[i] = min(find(A(i,:))), for QR */ + cs_long_t m2 ; /* # of rows for QR, after adding fictitious rows */ + double lnz ; /* # entries in L for LU or Cholesky; in V for QR */ + double unz ; /* # entries in U for LU; in R for QR */ +} cs_igraphs ; + +typedef struct cs_igraph_numeric /* numeric Cholesky, LU, or QR factorization */ +{ + cs_igraph *L ; /* L for LU and Cholesky, V for QR */ + cs_igraph *U ; /* U for LU, r for QR, not used for Cholesky */ + cs_long_t *pinv ; /* partial pivoting for LU */ + double *B ; /* beta [0..n-1] for QR */ +} cs_igraphn ; + +typedef struct cs_igraph_dmperm_results /* cs_igraph_dmperm or cs_igraph_scc output */ +{ + cs_long_t *p ; /* size m, row permutation */ + cs_long_t *q ; /* size n, column permutation */ + cs_long_t *r ; /* size nb+1, block k is rows r[k] to r[k+1]-1 in A(p,q) */ + cs_long_t *s ; /* size nb+1, block k is cols s[k] to s[k+1]-1 in A(p,q) */ + cs_long_t nb ; /* # of blocks in fine dmperm decomposition */ + cs_long_t rr [5] ; /* coarse row decomposition */ + cs_long_t cc [5] ; /* coarse column decomposition */ +} cs_igraphd ; + +cs_long_t *cs_igraph_amd (cs_long_t order, const cs_igraph *A) ; +cs_igraphn *cs_igraph_chol (const cs_igraph *A, const cs_igraphs *S) ; +cs_igraphd *cs_igraph_dmperm (const cs_igraph *A, cs_long_t seed) ; +cs_long_t cs_igraph_droptol (cs_igraph *A, double tol) ; +cs_long_t cs_igraph_dropzeros (cs_igraph *A) ; +cs_long_t cs_igraph_happly (const cs_igraph *V, cs_long_t i, double beta, double *x) ; +cs_long_t cs_igraph_ipvec (const cs_long_t *p, const double *b, double *x, cs_long_t n) ; +cs_long_t cs_igraph_lsolve (const cs_igraph *L, double *x) ; +cs_long_t cs_igraph_ltsolve (const cs_igraph *L, double *x) ; +cs_igraphn *cs_igraph_lu (const cs_igraph *A, const cs_igraphs *S, double tol) ; +cs_igraph *cs_igraph_permute (const cs_igraph *A, const cs_long_t *pinv, const cs_long_t *q, + cs_long_t values) ; +cs_long_t *cs_igraph_pinv (const cs_long_t *p, cs_long_t n) ; +cs_long_t cs_igraph_pvec (const cs_long_t *p, const double *b, double *x, cs_long_t n) ; +cs_igraphn *cs_igraph_qr (const cs_igraph *A, const cs_igraphs *S) ; +cs_igraphs *cs_igraph_schol (cs_long_t order, const cs_igraph *A) ; +cs_igraphs *cs_igraph_sqr (cs_long_t order, const cs_igraph *A, cs_long_t qr) ; +cs_igraph *cs_igraph_symperm (const cs_igraph *A, const cs_long_t *pinv, cs_long_t values) ; +cs_long_t cs_igraph_usolve (const cs_igraph *U, double *x) ; +cs_long_t cs_igraph_utsolve (const cs_igraph *U, double *x) ; +cs_long_t cs_igraph_updown (cs_igraph *L, cs_long_t sigma, const cs_igraph *C, + const cs_long_t *parent) ; + +/* utilities */ +cs_igraphs *cs_igraph_sfree (cs_igraphs *S) ; +cs_igraphn *cs_igraph_nfree (cs_igraphn *N) ; +cs_igraphd *cs_igraph_dfree (cs_igraphd *D) ; + +/* --- tertiary CSparse routines -------------------------------------------- */ + +cs_long_t *cs_igraph_counts (const cs_igraph *A, const cs_long_t *parent, + const cs_long_t *post, cs_long_t ata) ; +double cs_igraph_cumsum (cs_long_t *p, cs_long_t *c, cs_long_t n) ; +cs_long_t cs_igraph_dfs (cs_long_t j, cs_igraph *G, cs_long_t top, cs_long_t *xi, + cs_long_t *pstack, const cs_long_t *pinv) ; +cs_long_t *cs_igraph_etree (const cs_igraph *A, cs_long_t ata) ; +cs_long_t cs_igraph_fkeep (cs_igraph *A, + cs_long_t (*fkeep) (cs_long_t, cs_long_t, double, void *), void *other) ; +double cs_igraph_house (double *x, double *beta, cs_long_t n) ; +cs_long_t *cs_igraph_maxtrans (const cs_igraph *A, cs_long_t seed) ; +cs_long_t *cs_igraph_post (const cs_long_t *parent, cs_long_t n) ; +cs_igraphd *cs_igraph_scc (cs_igraph *A) ; +cs_long_t cs_igraph_scatter (const cs_igraph *A, cs_long_t j, double beta, cs_long_t *w, + double *x, cs_long_t mark,cs_igraph *C, cs_long_t nz) ; +cs_long_t cs_igraph_tdfs (cs_long_t j, cs_long_t k, cs_long_t *head, const cs_long_t *next, + cs_long_t *post, cs_long_t *stack) ; +cs_long_t cs_igraph_leaf (cs_long_t i, cs_long_t j, const cs_long_t *first, + cs_long_t *maxfirst, cs_long_t *prevleaf, cs_long_t *ancestor, cs_long_t *jleaf) ; +cs_long_t cs_igraph_reach (cs_igraph *G, const cs_igraph *B, cs_long_t k, cs_long_t *xi, + const cs_long_t *pinv) ; +cs_long_t cs_igraph_spsolve (cs_igraph *L, const cs_igraph *B, cs_long_t k, cs_long_t *xi, + double *x, const cs_long_t *pinv, cs_long_t lo) ; +cs_long_t cs_igraph_ereach (const cs_igraph *A, cs_long_t k, const cs_long_t *parent, + cs_long_t *s, cs_long_t *w) ; +cs_long_t *cs_igraph_randperm (cs_long_t n, cs_long_t seed) ; + +/* utilities */ +cs_igraphd *cs_igraph_dalloc (cs_long_t m, cs_long_t n) ; +cs_igraph *cs_igraph_done (cs_igraph *C, void *w, void *x, cs_long_t ok) ; +cs_long_t *cs_igraph_idone (cs_long_t *p, cs_igraph *C, void *w, cs_long_t ok) ; +cs_igraphn *cs_igraph_ndone (cs_igraphn *N, cs_igraph *C, void *w, void *x, cs_long_t ok) ; +cs_igraphd *cs_igraph_ddone (cs_igraphd *D, cs_igraph *C, void *w, cs_long_t ok) ; + +/* -------------------------------------------------------------------------- */ +/* Macros for constructing each version of CSparse */ +/* -------------------------------------------------------------------------- */ + +#define CS_INT cs_long_t +#define CS_INT_MAX cs_long_t_max +#define CS_ID cs_long_t_id +#define CS_ENTRY double +#define CS_NAME(nm) cs_igraph ## nm +#define cs cs_igraph + +#define CS_REAL(x) (x) +#define CS_IMAG(x) (0.) +#define CS_CONJ(x) (x) +#define CS_ABS(x) fabs(x) + +#define CS_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define CS_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define CS_FLIP(i) (-(i)-2) +#define CS_UNFLIP(i) (((i) < 0) ? CS_FLIP(i) : (i)) +#define CS_MARKED(w,j) (w [j] < 0) +#define CS_MARK(w,j) { w [j] = CS_FLIP (w [j]) ; } +#define CS_CSC(A) (A && (A->nz == -1)) +#define CS_TRIPLET(A) (A && (A->nz >= 0)) + +/* --- primary CSparse routines and data structures ------------------------- */ + +#define cs_add CS_NAME (_add) +#define cs_cholsol CS_NAME (_cholsol) +#define cs_dupl CS_NAME (_dupl) +#define cs_entry CS_NAME (_entry) +#define cs_lusol CS_NAME (_lusol) +#define cs_gaxpy CS_NAME (_gaxpy) +#define cs_multiply CS_NAME (_multiply) +#define cs_qrsol CS_NAME (_qrsol) +#define cs_transpose CS_NAME (_transpose) +#define cs_compress CS_NAME (_compress) +#define cs_norm CS_NAME (_norm) +/*#define cs_print CS_NAME (_print)*/ +#define cs_load CS_NAME (_load) + +/* utilities */ +#define cs_calloc CS_NAME (_calloc) +#define cs_free CS_NAME (_free) +#define cs_realloc CS_NAME (_realloc) +#define cs_spalloc CS_NAME (_spalloc) +#define cs_spfree CS_NAME (_spfree) +#define cs_sprealloc CS_NAME (_sprealloc) +#define cs_malloc CS_NAME (_malloc) + +/* --- secondary CSparse routines and data structures ----------------------- */ +#define css CS_NAME (s) +#define csn CS_NAME (n) +#define csd CS_NAME (d) + +#define cs_amd CS_NAME (_amd) +#define cs_chol CS_NAME (_chol) +#define cs_dmperm CS_NAME (_dmperm) +#define cs_droptol CS_NAME (_droptol) +#define cs_dropzeros CS_NAME (_dropzeros) +#define cs_happly CS_NAME (_happly) +#define cs_ipvec CS_NAME (_ipvec) +#define cs_lsolve CS_NAME (_lsolve) +#define cs_ltsolve CS_NAME (_ltsolve) +#define cs_lu CS_NAME (_lu) +#define cs_permute CS_NAME (_permute) +#define cs_pinv CS_NAME (_pinv) +#define cs_pvec CS_NAME (_pvec) +#define cs_qr CS_NAME (_qr) +#define cs_schol CS_NAME (_schol) +#define cs_sqr CS_NAME (_sqr) +#define cs_symperm CS_NAME (_symperm) +#define cs_usolve CS_NAME (_usolve) +#define cs_utsolve CS_NAME (_utsolve) +#define cs_updown CS_NAME (_updown) + +/* utilities */ +#define cs_sfree CS_NAME (_sfree) +#define cs_nfree CS_NAME (_nfree) +#define cs_dfree CS_NAME (_dfree) + +/* --- tertiary CSparse routines -------------------------------------------- */ +#define cs_counts CS_NAME (_counts) +#define cs_cumsum CS_NAME (_cumsum) +#define cs_dfs CS_NAME (_dfs) +#define cs_etree CS_NAME (_etree) +#define cs_fkeep CS_NAME (_fkeep) +#define cs_house CS_NAME (_house) +#define cs_invmatch CS_NAME (_invmatch) +#define cs_maxtrans CS_NAME (_maxtrans) +#define cs_post CS_NAME (_post) +#define cs_scc CS_NAME (_scc) +#define cs_scatter CS_NAME (_scatter) +#define cs_tdfs CS_NAME (_tdfs) +#define cs_reach CS_NAME (_reach) +#define cs_spsolve CS_NAME (_spsolve) +#define cs_ereach CS_NAME (_ereach) +#define cs_randperm CS_NAME (_randperm) +#define cs_leaf CS_NAME (_leaf) + +/* utilities */ +#define cs_dalloc CS_NAME (_dalloc) +#define cs_done CS_NAME (_done) +#define cs_idone CS_NAME (_idone) +#define cs_ndone CS_NAME (_ndone) +#define cs_ddone CS_NAME (_ddone) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/vendor/cs/cs_add.c b/vendor/cs/cs_add.c new file mode 100644 index 0000000..44b0d3f --- /dev/null +++ b/vendor/cs/cs_add.c @@ -0,0 +1,28 @@ +#include "cs.h" +/* C = alpha*A + beta*B */ +cs *cs_add (const cs *A, const cs *B, CS_ENTRY alpha, CS_ENTRY beta) +{ + CS_INT p, j, nz = 0, anz, *Cp, *Ci, *Bp, m, n, bnz, *w, values ; + CS_ENTRY *x, *Bx, *Cx ; + cs *C ; + if (!CS_CSC (A) || !CS_CSC (B)) return (NULL) ; /* check inputs */ + if (A->m != B->m || A->n != B->n) return (NULL) ; + m = A->m ; anz = A->p [A->n] ; + n = B->n ; Bp = B->p ; Bx = B->x ; bnz = Bp [n] ; + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + values = (A->x != NULL) && (Bx != NULL) ; + x = values ? cs_malloc (m, sizeof (CS_ENTRY)) : NULL ; /* get workspace */ + C = cs_spalloc (m, n, anz + bnz, values, 0) ; /* allocate result*/ + if (!C || !w || (values && !x)) return (cs_done (C, w, x, 0)) ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (j = 0 ; j < n ; j++) + { + Cp [j] = nz ; /* column j of C starts here */ + nz = cs_scatter (A, j, alpha, w, x, j+1, C, nz) ; /* alpha*A(:,j)*/ + nz = cs_scatter (B, j, beta, w, x, j+1, C, nz) ; /* beta*B(:,j) */ + if (values) for (p = Cp [j] ; p < nz ; p++) Cx [p] = x [Ci [p]] ; + } + Cp [n] = nz ; /* finalize the last column of C */ + cs_sprealloc (C, 0) ; /* remove extra space from C */ + return (cs_done (C, w, x, 1)) ; /* success; free workspace, return C */ +} diff --git a/vendor/cs/cs_amd.c b/vendor/cs/cs_amd.c new file mode 100644 index 0000000..3f5c702 --- /dev/null +++ b/vendor/cs/cs_amd.c @@ -0,0 +1,364 @@ +#include "cs.h" +/* clear w */ +static CS_INT cs_wclear (CS_INT mark, CS_INT lemax, CS_INT *w, CS_INT n) +{ + CS_INT k ; + if (mark < 2 || (mark + lemax < 0)) + { + for (k = 0 ; k < n ; k++) if (w [k] != 0) w [k] = 1 ; + mark = 2 ; + } + return (mark) ; /* at this point, w [0..n-1] < mark holds */ +} + +/* keep off-diagonal entries; drop diagonal entries */ +static CS_INT cs_diag (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) { return (i != j) ; } + +/* p = amd(A+A') if symmetric is true, or amd(A'A) otherwise */ +CS_INT *cs_amd (CS_INT order, const cs *A) /* order 0:natural, 1:Chol, 2:LU, 3:QR */ +{ + cs *C, *A2, *AT ; + CS_INT *Cp, *Ci, *last, *W, *len, *nv, *next, *P, *head, *elen, *degree, *w, + *hhead, *ATp, *ATi, d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1, + k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi, nvj, nvk, mark, wnvi, + ok, cnz, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, n, m, t ; + CS_INT h ; + /* --- Construct matrix C ----------------------------------------------- */ + if (!CS_CSC (A) || order <= 0 || order > 3) return (NULL) ; /* check */ + AT = cs_transpose (A, 0) ; /* compute A' */ + if (!AT) return (NULL) ; + m = A->m ; n = A->n ; + dense = CS_MAX (16, 10 * sqrt ((double) n)) ; /* find dense threshold */ + dense = CS_MIN (n-2, dense) ; + if (order == 1 && n == m) + { + C = cs_add (A, AT, 0, 0) ; /* C = A+A' */ + } + else if (order == 2) + { + ATp = AT->p ; /* drop dense columns from AT */ + ATi = AT->i ; + for (p2 = 0, j = 0 ; j < m ; j++) + { + p = ATp [j] ; /* column j of AT starts here */ + ATp [j] = p2 ; /* new column j starts here */ + if (ATp [j+1] - p > dense) continue ; /* skip dense col j */ + for ( ; p < ATp [j+1] ; p++) ATi [p2++] = ATi [p] ; + } + ATp [m] = p2 ; /* finalize AT */ + A2 = cs_transpose (AT, 0) ; /* A2 = AT' */ + C = A2 ? cs_multiply (AT, A2) : NULL ; /* C=A'*A with no dense rows */ + cs_spfree (A2) ; + } + else + { + C = cs_multiply (AT, A) ; /* C=A'*A */ + } + cs_spfree (AT) ; + if (!C) return (NULL) ; + cs_fkeep (C, &cs_diag, NULL) ; /* drop diagonal entries */ + Cp = C->p ; + cnz = Cp [n] ; + P = cs_malloc (n+1, sizeof (CS_INT)) ; /* allocate result */ + W = cs_malloc (8*(n+1), sizeof (CS_INT)) ; /* get workspace */ + t = cnz + cnz/5 + 2*n ; /* add elbow room to C */ + if (!P || !W || !cs_sprealloc (C, t)) return (cs_idone (P, C, W, 0)) ; + len = W ; nv = W + (n+1) ; next = W + 2*(n+1) ; + head = W + 3*(n+1) ; elen = W + 4*(n+1) ; degree = W + 5*(n+1) ; + w = W + 6*(n+1) ; hhead = W + 7*(n+1) ; + last = P ; /* use P as workspace for last */ + /* --- Initialize quotient graph ---------------------------------------- */ + for (k = 0 ; k < n ; k++) len [k] = Cp [k+1] - Cp [k] ; + len [n] = 0 ; + nzmax = C->nzmax ; + Ci = C->i ; + for (i = 0 ; i <= n ; i++) + { + head [i] = -1 ; /* degree list i is empty */ + last [i] = -1 ; + next [i] = -1 ; + hhead [i] = -1 ; /* hash list i is empty */ + nv [i] = 1 ; /* node i is just one node */ + w [i] = 1 ; /* node i is alive */ + elen [i] = 0 ; /* Ek of node i is empty */ + degree [i] = len [i] ; /* degree of node i */ + } + mark = cs_wclear (0, 0, w, n) ; /* clear w */ + elen [n] = -2 ; /* n is a dead element */ + Cp [n] = -1 ; /* n is a root of assembly tree */ + w [n] = 0 ; /* n is a dead element */ + /* --- Initialize degree lists ------------------------------------------ */ + for (i = 0 ; i < n ; i++) + { + d = degree [i] ; + if (d == 0) /* node i is empty */ + { + elen [i] = -2 ; /* element i is dead */ + nel++ ; + Cp [i] = -1 ; /* i is a root of assembly tree */ + w [i] = 0 ; + } + else if (d > dense) /* node i is dense */ + { + nv [i] = 0 ; /* absorb i into element n */ + elen [i] = -1 ; /* node i is dead */ + nel++ ; + Cp [i] = CS_FLIP (n) ; + nv [n]++ ; + } + else + { + if (head [d] != -1) last [head [d]] = i ; + next [i] = head [d] ; /* put node i in degree list d */ + head [d] = i ; + } + } + while (nel < n) /* while (selecting pivots) do */ + { + /* --- Select node of minimum approximate degree -------------------- */ + for (k = -1 ; mindeg < n && (k = head [mindeg]) == -1 ; mindeg++) ; + if (next [k] != -1) last [next [k]] = -1 ; + head [mindeg] = next [k] ; /* remove k from degree list */ + elenk = elen [k] ; /* elenk = |Ek| */ + nvk = nv [k] ; /* # of nodes k represents */ + nel += nvk ; /* nv[k] nodes of A eliminated */ + /* --- Garbage collection ------------------------------------------- */ + if (elenk > 0 && cnz + mindeg >= nzmax) + { + for (j = 0 ; j < n ; j++) + { + if ((p = Cp [j]) >= 0) /* j is a live node or element */ + { + Cp [j] = Ci [p] ; /* save first entry of object */ + Ci [p] = CS_FLIP (j) ; /* first entry is now CS_FLIP(j) */ + } + } + for (q = 0, p = 0 ; p < cnz ; ) /* scan all of memory */ + { + if ((j = CS_FLIP (Ci [p++])) >= 0) /* found object j */ + { + Ci [q] = Cp [j] ; /* restore first entry of object */ + Cp [j] = q++ ; /* new pointer to object j */ + for (k3 = 0 ; k3 < len [j]-1 ; k3++) Ci [q++] = Ci [p++] ; + } + } + cnz = q ; /* Ci [cnz...nzmax-1] now free */ + } + /* --- Construct new element ---------------------------------------- */ + dk = 0 ; + nv [k] = -nvk ; /* flag k as in Lk */ + p = Cp [k] ; + pk1 = (elenk == 0) ? p : cnz ; /* do in place if elen[k] == 0 */ + pk2 = pk1 ; + for (k1 = 1 ; k1 <= elenk + 1 ; k1++) + { + if (k1 > elenk) + { + e = k ; /* search the nodes in k */ + pj = p ; /* list of nodes starts at Ci[pj]*/ + ln = len [k] - elenk ; /* length of list of nodes in k */ + } + else + { + e = Ci [p++] ; /* search the nodes in e */ + pj = Cp [e] ; + ln = len [e] ; /* length of list of nodes in e */ + } + for (k2 = 1 ; k2 <= ln ; k2++) + { + i = Ci [pj++] ; + if ((nvi = nv [i]) <= 0) continue ; /* node i dead, or seen */ + dk += nvi ; /* degree[Lk] += size of node i */ + nv [i] = -nvi ; /* negate nv[i] to denote i in Lk*/ + Ci [pk2++] = i ; /* place i in Lk */ + if (next [i] != -1) last [next [i]] = last [i] ; + if (last [i] != -1) /* remove i from degree list */ + { + next [last [i]] = next [i] ; + } + else + { + head [degree [i]] = next [i] ; + } + } + if (e != k) + { + Cp [e] = CS_FLIP (k) ; /* absorb e into k */ + w [e] = 0 ; /* e is now a dead element */ + } + } + if (elenk != 0) cnz = pk2 ; /* Ci [cnz...nzmax] is free */ + degree [k] = dk ; /* external degree of k - |Lk\i| */ + Cp [k] = pk1 ; /* element k is in Ci[pk1..pk2-1] */ + len [k] = pk2 - pk1 ; + elen [k] = -2 ; /* k is now an element */ + /* --- Find set differences ----------------------------------------- */ + mark = cs_wclear (mark, lemax, w, n) ; /* clear w if necessary */ + for (pk = pk1 ; pk < pk2 ; pk++) /* scan 1: find |Le\Lk| */ + { + i = Ci [pk] ; + if ((eln = elen [i]) <= 0) continue ;/* skip if elen[i] empty */ + nvi = -nv [i] ; /* nv [i] was negated */ + wnvi = mark - nvi ; + for (p = Cp [i] ; p <= Cp [i] + eln - 1 ; p++) /* scan Ei */ + { + e = Ci [p] ; + if (w [e] >= mark) + { + w [e] -= nvi ; /* decrement |Le\Lk| */ + } + else if (w [e] != 0) /* ensure e is a live element */ + { + w [e] = degree [e] + wnvi ; /* 1st time e seen in scan 1 */ + } + } + } + /* --- Degree update ------------------------------------------------ */ + for (pk = pk1 ; pk < pk2 ; pk++) /* scan2: degree update */ + { + i = Ci [pk] ; /* consider node i in Lk */ + p1 = Cp [i] ; + p2 = p1 + elen [i] - 1 ; + pn = p1 ; + for (h = 0, d = 0, p = p1 ; p <= p2 ; p++) /* scan Ei */ + { + e = Ci [p] ; + if (w [e] != 0) /* e is an unabsorbed element */ + { + dext = w [e] - mark ; /* dext = |Le\Lk| */ + if (dext > 0) + { + d += dext ; /* sum up the set differences */ + Ci [pn++] = e ; /* keep e in Ei */ + h += e ; /* compute the hash of node i */ + } + else + { + Cp [e] = CS_FLIP (k) ; /* aggressive absorb. e->k */ + w [e] = 0 ; /* e is a dead element */ + } + } + } + elen [i] = pn - p1 + 1 ; /* elen[i] = |Ei| */ + p3 = pn ; + p4 = p1 + len [i] ; + for (p = p2 + 1 ; p < p4 ; p++) /* prune edges in Ai */ + { + j = Ci [p] ; + if ((nvj = nv [j]) <= 0) continue ; /* node j dead or in Lk */ + d += nvj ; /* degree(i) += |j| */ + Ci [pn++] = j ; /* place j in node list of i */ + h += j ; /* compute hash for node i */ + } + if (d == 0) /* check for mass elimination */ + { + Cp [i] = CS_FLIP (k) ; /* absorb i into k */ + nvi = -nv [i] ; + dk -= nvi ; /* |Lk| -= |i| */ + nvk += nvi ; /* |k| += nv[i] */ + nel += nvi ; + nv [i] = 0 ; + elen [i] = -1 ; /* node i is dead */ + } + else + { + degree [i] = CS_MIN (degree [i], d) ; /* update degree(i) */ + Ci [pn] = Ci [p3] ; /* move first node to end */ + Ci [p3] = Ci [p1] ; /* move 1st el. to end of Ei */ + Ci [p1] = k ; /* add k as 1st element in of Ei */ + len [i] = pn - p1 + 1 ; /* new len of adj. list of node i */ + h = ((h<0) ? (-h):h) % n ; /* finalize hash of i */ + next [i] = hhead [h] ; /* place i in hash bucket */ + hhead [h] = i ; + last [i] = h ; /* save hash of i in last[i] */ + } + } /* scan2 is done */ + degree [k] = dk ; /* finalize |Lk| */ + lemax = CS_MAX (lemax, dk) ; + mark = cs_wclear (mark+lemax, lemax, w, n) ; /* clear w */ + /* --- Supernode detection ------------------------------------------ */ + for (pk = pk1 ; pk < pk2 ; pk++) + { + i = Ci [pk] ; + if (nv [i] >= 0) continue ; /* skip if i is dead */ + h = last [i] ; /* scan hash bucket of node i */ + i = hhead [h] ; + hhead [h] = -1 ; /* hash bucket will be empty */ + for ( ; i != -1 && next [i] != -1 ; i = next [i], mark++) + { + ln = len [i] ; + eln = elen [i] ; + for (p = Cp [i]+1 ; p <= Cp [i] + ln-1 ; p++) w [Ci [p]] = mark; + jlast = i ; + for (j = next [i] ; j != -1 ; ) /* compare i with all j */ + { + ok = (len [j] == ln) && (elen [j] == eln) ; + for (p = Cp [j] + 1 ; ok && p <= Cp [j] + ln - 1 ; p++) + { + if (w [Ci [p]] != mark) ok = 0 ; /* compare i and j*/ + } + if (ok) /* i and j are identical */ + { + Cp [j] = CS_FLIP (i) ; /* absorb j into i */ + nv [i] += nv [j] ; + nv [j] = 0 ; + elen [j] = -1 ; /* node j is dead */ + j = next [j] ; /* delete j from hash bucket */ + next [jlast] = j ; + } + else + { + jlast = j ; /* j and i are different */ + j = next [j] ; + } + } + } + } + /* --- Finalize new element------------------------------------------ */ + for (p = pk1, pk = pk1 ; pk < pk2 ; pk++) /* finalize Lk */ + { + i = Ci [pk] ; + if ((nvi = -nv [i]) <= 0) continue ;/* skip if i is dead */ + nv [i] = nvi ; /* restore nv[i] */ + d = degree [i] + dk - nvi ; /* compute external degree(i) */ + d = CS_MIN (d, n - nel - nvi) ; + if (head [d] != -1) last [head [d]] = i ; + next [i] = head [d] ; /* put i back in degree list */ + last [i] = -1 ; + head [d] = i ; + mindeg = CS_MIN (mindeg, d) ; /* find new minimum degree */ + degree [i] = d ; + Ci [p++] = i ; /* place i in Lk */ + } + nv [k] = nvk ; /* # nodes absorbed into k */ + if ((len [k] = p-pk1) == 0) /* length of adj list of element k*/ + { + Cp [k] = -1 ; /* k is a root of the tree */ + w [k] = 0 ; /* k is now a dead element */ + } + if (elenk != 0) cnz = p ; /* free unused space in Lk */ + } + /* --- Postordering ----------------------------------------------------- */ + for (i = 0 ; i < n ; i++) Cp [i] = CS_FLIP (Cp [i]) ;/* fix assembly tree */ + for (j = 0 ; j <= n ; j++) head [j] = -1 ; + for (j = n ; j >= 0 ; j--) /* place unordered nodes in lists */ + { + if (nv [j] > 0) continue ; /* skip if j is an element */ + next [j] = head [Cp [j]] ; /* place j in list of its parent */ + head [Cp [j]] = j ; + } + for (e = n ; e >= 0 ; e--) /* place elements in lists */ + { + if (nv [e] <= 0) continue ; /* skip unless e is an element */ + if (Cp [e] != -1) + { + next [e] = head [Cp [e]] ; /* place e in list of its parent */ + head [Cp [e]] = e ; + } + } + for (k = 0, i = 0 ; i <= n ; i++) /* postorder the assembly tree */ + { + if (Cp [i] == -1) k = cs_tdfs (i, k, head, next, P, w) ; + } + return (cs_idone (P, C, W, 1)) ; +} diff --git a/vendor/cs/cs_chol.c b/vendor/cs/cs_chol.c new file mode 100644 index 0000000..535809a --- /dev/null +++ b/vendor/cs/cs_chol.c @@ -0,0 +1,59 @@ +#include "cs.h" +/* L = chol (A, [pinv parent cp]), pinv is optional */ +csn *cs_chol (const cs *A, const css *S) +{ + CS_ENTRY d, lki, *Lx, *x, *Cx ; + CS_INT top, i, p, k, n, *Li, *Lp, *cp, *pinv, *s, *c, *parent, *Cp, *Ci ; + cs *L, *C, *E ; + csn *N ; + if (!CS_CSC (A) || !S || !S->cp || !S->parent) return (NULL) ; + n = A->n ; + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + c = cs_malloc (2*n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + cp = S->cp ; pinv = S->pinv ; parent = S->parent ; + C = pinv ? cs_symperm (A, pinv, 1) : ((cs *) A) ; + E = pinv ? C : NULL ; /* E is alias for A, or a copy E=A(p,p) */ + if (!N || !c || !x || !C) return (cs_ndone (N, E, c, x, 0)) ; + s = c + n ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + N->L = L = cs_spalloc (n, n, cp [n], 1, 0) ; /* allocate result */ + if (!L) return (cs_ndone (N, E, c, x, 0)) ; + Lp = L->p ; Li = L->i ; Lx = L->x ; + for (k = 0 ; k < n ; k++) Lp [k] = c [k] = cp [k] ; + for (k = 0 ; k < n ; k++) /* compute L(k,:) for L*L' = C */ + { + /* --- Nonzero pattern of L(k,:) ------------------------------------ */ + top = cs_ereach (C, k, parent, s, c) ; /* find pattern of L(k,:) */ + x [k] = 0 ; /* x (0:k) is now zero */ + for (p = Cp [k] ; p < Cp [k+1] ; p++) /* x = full(triu(C(:,k))) */ + { + if (Ci [p] <= k) x [Ci [p]] = Cx [p] ; + } + d = x [k] ; /* d = C(k,k) */ + x [k] = 0 ; /* clear x for k+1st iteration */ + /* --- Triangular solve --------------------------------------------- */ + for ( ; top < n ; top++) /* solve L(0:k-1,0:k-1) * x = C(:,k) */ + { + i = s [top] ; /* s [top..n-1] is pattern of L(k,:) */ + lki = x [i] / Lx [Lp [i]] ; /* L(k,i) = x (i) / L(i,i) */ + x [i] = 0 ; /* clear x for k+1st iteration */ + for (p = Lp [i] + 1 ; p < c [i] ; p++) + { + x [Li [p]] -= Lx [p] * lki ; + } + d -= lki * CS_CONJ (lki) ; /* d = d - L(k,i)*L(k,i) */ + p = c [i]++ ; + Li [p] = k ; /* store L(k,i) in column i */ + Lx [p] = CS_CONJ (lki) ; + } + /* --- Compute L(k,k) ----------------------------------------------- */ + if (CS_REAL (d) <= 0 || CS_IMAG (d) != 0) + return (cs_ndone (N, E, c, x, 0)) ; /* not pos def */ + p = c [k]++ ; + Li [p] = k ; /* store L(k,k) = sqrt (d) in column k */ + Lx [p] = sqrt (d) ; + } + Lp [n] = cp [n] ; /* finalize L */ + return (cs_ndone (N, E, c, x, 1)) ; /* success: free E,s,x; return N */ +} diff --git a/vendor/cs/cs_cholsol.c b/vendor/cs/cs_cholsol.c new file mode 100644 index 0000000..6e7dc4f --- /dev/null +++ b/vendor/cs/cs_cholsol.c @@ -0,0 +1,26 @@ +#include "cs.h" +/* x=A\b where A is symmetric positive definite; b overwritten with solution */ +CS_INT cs_cholsol (CS_INT order, const cs *A, CS_ENTRY *b) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + CS_INT n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + S = cs_schol (order, A) ; /* ordering and symbolic analysis */ + N = cs_chol (A, S) ; /* numeric Cholesky factorization */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (S->pinv, b, x, n) ; /* x = P*b */ + cs_lsolve (N->L, x) ; /* x = L\x */ + cs_ltsolve (N->L, x) ; /* x = L'\x */ + cs_pvec (S->pinv, x, b, n) ; /* b = P'*x */ + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + return (ok) ; +} diff --git a/vendor/cs/cs_compress.c b/vendor/cs/cs_compress.c new file mode 100644 index 0000000..dc62eba --- /dev/null +++ b/vendor/cs/cs_compress.c @@ -0,0 +1,22 @@ +#include "cs.h" +/* C = compressed-column form of a triplet matrix T */ +cs *cs_compress (const cs *T) +{ + CS_INT m, n, nz, p, k, *Cp, *Ci, *w, *Ti, *Tj ; + CS_ENTRY *Cx, *Tx ; + cs *C ; + if (!CS_TRIPLET (T)) return (NULL) ; /* check inputs */ + m = T->m ; n = T->n ; Ti = T->i ; Tj = T->p ; Tx = T->x ; nz = T->nz ; + C = cs_spalloc (m, n, nz, Tx != NULL, 0) ; /* allocate result */ + w = cs_calloc (n, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (k = 0 ; k < nz ; k++) w [Tj [k]]++ ; /* column counts */ + cs_cumsum (Cp, w, n) ; /* column pointers */ + for (k = 0 ; k < nz ; k++) + { + Ci [p = w [Tj [k]]++] = Ti [k] ; /* A(i,j) is the pth entry in C */ + if (Cx) Cx [p] = Tx [k] ; + } + return (cs_done (C, w, NULL, 1)) ; /* success; free w and return C */ +} diff --git a/vendor/cs/cs_counts.c b/vendor/cs/cs_counts.c new file mode 100644 index 0000000..a1b3de0 --- /dev/null +++ b/vendor/cs/cs_counts.c @@ -0,0 +1,61 @@ +#include "cs.h" +/* column counts of LL'=A or LL'=A'A, given parent & post ordering */ +#define HEAD(k,j) (ata ? head [k] : j) +#define NEXT(J) (ata ? next [J] : -1) +static void init_ata (cs *AT, const CS_INT *post, CS_INT *w, CS_INT **head, CS_INT **next) +{ + CS_INT i, k, p, m = AT->n, n = AT->m, *ATp = AT->p, *ATi = AT->i ; + *head = w+4*n, *next = w+5*n+1 ; + for (k = 0 ; k < n ; k++) w [post [k]] = k ; /* invert post */ + for (i = 0 ; i < m ; i++) + { + for (k = n, p = ATp[i] ; p < ATp[i+1] ; p++) k = CS_MIN (k, w [ATi[p]]); + (*next) [i] = (*head) [k] ; /* place row i in linked list k */ + (*head) [k] = i ; + } +} +CS_INT *cs_counts (const cs *A, const CS_INT *parent, const CS_INT *post, CS_INT ata) +{ + CS_INT i, j, k, n, m, J, s, p, q, jleaf, *ATp, *ATi, *maxfirst, *prevleaf, + *ancestor, *head = NULL, *next = NULL, *colcount, *w, *first, *delta ; + cs *AT ; + if (!CS_CSC (A) || !parent || !post) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; + s = 4*n + (ata ? (n+m+1) : 0) ; + delta = colcount = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (s, sizeof (CS_INT)) ; /* get workspace */ + AT = cs_transpose (A, 0) ; /* AT = A' */ + if (!AT || !colcount || !w) return (cs_idone (colcount, AT, w, 0)) ; + ancestor = w ; maxfirst = w+n ; prevleaf = w+2*n ; first = w+3*n ; + for (k = 0 ; k < s ; k++) w [k] = -1 ; /* clear workspace w [0..s-1] */ + for (k = 0 ; k < n ; k++) /* find first [j] */ + { + j = post [k] ; + delta [j] = (first [j] == -1) ? 1 : 0 ; /* delta[j]=1 if j is a leaf */ + for ( ; j != -1 && first [j] == -1 ; j = parent [j]) first [j] = k ; + } + ATp = AT->p ; ATi = AT->i ; + if (ata) init_ata (AT, post, w, &head, &next) ; + for (i = 0 ; i < n ; i++) ancestor [i] = i ; /* each node in its own set */ + for (k = 0 ; k < n ; k++) + { + j = post [k] ; /* j is the kth node in postordered etree */ + if (parent [j] != -1) delta [parent [j]]-- ; /* j is not a root */ + for (J = HEAD (k,j) ; J != -1 ; J = NEXT (J)) /* J=j for LL'=A case */ + { + for (p = ATp [J] ; p < ATp [J+1] ; p++) + { + i = ATi [p] ; + q = cs_leaf (i, j, first, maxfirst, prevleaf, ancestor, &jleaf); + if (jleaf >= 1) delta [j]++ ; /* A(i,j) is in skeleton */ + if (jleaf == 2) delta [q]-- ; /* account for overlap in q */ + } + } + if (parent [j] != -1) ancestor [j] = parent [j] ; + } + for (j = 0 ; j < n ; j++) /* sum up delta's of each child */ + { + if (parent [j] != -1) colcount [parent [j]] += colcount [j] ; + } + return (cs_idone (colcount, AT, w, 1)) ; /* success: free workspace */ +} diff --git a/vendor/cs/cs_cumsum.c b/vendor/cs/cs_cumsum.c new file mode 100644 index 0000000..e839497 --- /dev/null +++ b/vendor/cs/cs_cumsum.c @@ -0,0 +1,17 @@ +#include "cs.h" +/* p [0..n] = cumulative sum of c [0..n-1], and then copy p [0..n-1] into c */ +double cs_cumsum (CS_INT *p, CS_INT *c, CS_INT n) +{ + CS_INT i, nz = 0 ; + double nz2 = 0 ; + if (!p || !c) return (-1) ; /* check inputs */ + for (i = 0 ; i < n ; i++) + { + p [i] = nz ; + nz += c [i] ; + nz2 += c [i] ; /* also in double to avoid CS_INT overflow */ + c [i] = p [i] ; /* also copy p[0..n-1] back into c[0..n-1]*/ + } + p [n] = nz ; + return (nz2) ; /* return sum (c [0..n-1]) */ +} diff --git a/vendor/cs/cs_dfs.c b/vendor/cs/cs_dfs.c new file mode 100644 index 0000000..6c7115d --- /dev/null +++ b/vendor/cs/cs_dfs.c @@ -0,0 +1,36 @@ +#include "cs.h" +/* depth-first-search of the graph of a matrix, starting at node j */ +CS_INT cs_dfs (CS_INT j, cs *G, CS_INT top, CS_INT *xi, CS_INT *pstack, const CS_INT *pinv) +{ + CS_INT i, p, p2, done, jnew, head = 0, *Gp, *Gi ; + if (!CS_CSC (G) || !xi || !pstack) return (-1) ; /* check inputs */ + Gp = G->p ; Gi = G->i ; + xi [0] = j ; /* initialize the recursion stack */ + while (head >= 0) + { + j = xi [head] ; /* get j from the top of the recursion stack */ + jnew = pinv ? (pinv [j]) : j ; + if (!CS_MARKED (Gp, j)) + { + CS_MARK (Gp, j) ; /* mark node j as visited */ + pstack [head] = (jnew < 0) ? 0 : CS_UNFLIP (Gp [jnew]) ; + } + done = 1 ; /* node j done if no unvisited neighbors */ + p2 = (jnew < 0) ? 0 : CS_UNFLIP (Gp [jnew+1]) ; + for (p = pstack [head] ; p < p2 ; p++) /* examine all neighbors of j */ + { + i = Gi [p] ; /* consider neighbor node i */ + if (CS_MARKED (Gp, i)) continue ; /* skip visited node i */ + pstack [head] = p ; /* pause depth-first search of node j */ + xi [++head] = i ; /* start dfs at node i */ + done = 0 ; /* node j is not done */ + break ; /* break, to start dfs (i) */ + } + if (done) /* depth-first search at node j is done */ + { + head-- ; /* remove j from the recursion stack */ + xi [--top] = j ; /* and place in the output stack */ + } + } + return (top) ; +} diff --git a/vendor/cs/cs_dmperm.c b/vendor/cs/cs_dmperm.c new file mode 100644 index 0000000..e213845 --- /dev/null +++ b/vendor/cs/cs_dmperm.c @@ -0,0 +1,144 @@ +#include "cs.h" +/* breadth-first search for coarse decomposition (C0,C1,R1 or R0,R3,C3) */ +static CS_INT cs_bfs (const cs *A, CS_INT n, CS_INT *wi, CS_INT *wj, CS_INT *queue, + const CS_INT *imatch, const CS_INT *jmatch, CS_INT mark) +{ + CS_INT *Ap, *Ai, head = 0, tail = 0, j, i, p, j2 ; + cs *C ; + for (j = 0 ; j < n ; j++) /* place all unmatched nodes in queue */ + { + if (imatch [j] >= 0) continue ; /* skip j if matched */ + wj [j] = 0 ; /* j in set C0 (R0 if transpose) */ + queue [tail++] = j ; /* place unmatched col j in queue */ + } + if (tail == 0) return (1) ; /* quick return if no unmatched nodes */ + C = (mark == 1) ? ((cs *) A) : cs_transpose (A, 0) ; + if (!C) return (0) ; /* bfs of C=A' to find R3,C3 from R0 */ + Ap = C->p ; Ai = C->i ; + while (head < tail) /* while queue is not empty */ + { + j = queue [head++] ; /* get the head of the queue */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (wi [i] >= 0) continue ; /* skip if i is marked */ + wi [i] = mark ; /* i in set R1 (C3 if transpose) */ + j2 = jmatch [i] ; /* traverse alternating path to j2 */ + if (wj [j2] >= 0) continue ;/* skip j2 if it is marked */ + wj [j2] = mark ; /* j2 in set C1 (R3 if transpose) */ + queue [tail++] = j2 ; /* add j2 to queue */ + } + } + if (mark != 1) cs_spfree (C) ; /* free A' if it was created */ + return (1) ; +} + +/* collect matched rows and columns into p and q */ +static void cs_matched (CS_INT n, const CS_INT *wj, const CS_INT *imatch, CS_INT *p, CS_INT *q, + CS_INT *cc, CS_INT *rr, CS_INT set, CS_INT mark) +{ + CS_INT kc = cc [set], j ; + CS_INT kr = rr [set-1] ; + for (j = 0 ; j < n ; j++) + { + if (wj [j] != mark) continue ; /* skip if j is not in C set */ + p [kr++] = imatch [j] ; + q [kc++] = j ; + } + cc [set+1] = kc ; + rr [set] = kr ; +} + +/* collect unmatched rows into the permutation vector p */ +static void cs_unmatched (CS_INT m, const CS_INT *wi, CS_INT *p, CS_INT *rr, CS_INT set) +{ + CS_INT i, kr = rr [set] ; + for (i = 0 ; i < m ; i++) if (wi [i] == 0) p [kr++] = i ; + rr [set+1] = kr ; +} + +/* return 1 if row i is in R2 */ +static CS_INT cs_rprune (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) +{ + CS_INT *rr = (CS_INT *) other ; + return (i >= rr [1] && i < rr [2]) ; +} + +/* Given A, compute coarse and then fine dmperm */ +csd *cs_dmperm (const cs *A, CS_INT seed) +{ + CS_INT m, n, i, j, k, cnz, nc, *jmatch, *imatch, *wi, *wj, *pinv, *Cp, *Ci, + *ps, *rs, nb1, nb2, *p, *q, *cc, *rr, *r, *s, ok ; + cs *C ; + csd *D, *scc ; + /* --- Maximum matching ------------------------------------------------- */ + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; + D = cs_dalloc (m, n) ; /* allocate result */ + if (!D) return (NULL) ; + p = D->p ; q = D->q ; r = D->r ; s = D->s ; cc = D->cc ; rr = D->rr ; + jmatch = cs_maxtrans (A, seed) ; /* max transversal */ + imatch = jmatch + m ; /* imatch = inverse of jmatch */ + if (!jmatch) return (cs_ddone (D, NULL, jmatch, 0)) ; + /* --- Coarse decomposition --------------------------------------------- */ + wi = r ; wj = s ; /* use r and s as workspace */ + for (j = 0 ; j < n ; j++) wj [j] = -1 ; /* unmark all cols for bfs */ + for (i = 0 ; i < m ; i++) wi [i] = -1 ; /* unmark all rows for bfs */ + cs_bfs (A, n, wi, wj, q, imatch, jmatch, 1) ; /* find C1, R1 from C0*/ + ok = cs_bfs (A, m, wj, wi, p, jmatch, imatch, 3) ; /* find R3, C3 from R0*/ + if (!ok) return (cs_ddone (D, NULL, jmatch, 0)) ; + cs_unmatched (n, wj, q, cc, 0) ; /* unmatched set C0 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 1, 1) ; /* set R1 and C1 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 2, -1) ; /* set R2 and C2 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 3, 3) ; /* set R3 and C3 */ + cs_unmatched (m, wi, p, rr, 3) ; /* unmatched set R0 */ + cs_free (jmatch) ; + /* --- Fine decomposition ----------------------------------------------- */ + pinv = cs_pinv (p, m) ; /* pinv=p' */ + if (!pinv) return (cs_ddone (D, NULL, NULL, 0)) ; + C = cs_permute (A, pinv, q, 0) ;/* C=A(p,q) (it will hold A(R2,C2)) */ + cs_free (pinv) ; + if (!C) return (cs_ddone (D, NULL, NULL, 0)) ; + Cp = C->p ; + nc = cc [3] - cc [2] ; /* delete cols C0, C1, and C3 from C */ + if (cc [2] > 0) for (j = cc [2] ; j <= cc [3] ; j++) Cp [j-cc[2]] = Cp [j] ; + C->n = nc ; + if (rr [2] - rr [1] < m) /* delete rows R0, R1, and R3 from C */ + { + cs_fkeep (C, cs_rprune, rr) ; + cnz = Cp [nc] ; + Ci = C->i ; + if (rr [1] > 0) for (k = 0 ; k < cnz ; k++) Ci [k] -= rr [1] ; + } + C->m = nc ; + scc = cs_scc (C) ; /* find strongly connected components of C*/ + if (!scc) return (cs_ddone (D, C, NULL, 0)) ; + /* --- Combine coarse and fine decompositions --------------------------- */ + ps = scc->p ; /* C(ps,ps) is the permuted matrix */ + rs = scc->r ; /* kth block is rs[k]..rs[k+1]-1 */ + nb1 = scc->nb ; /* # of blocks of A(R2,C2) */ + for (k = 0 ; k < nc ; k++) wj [k] = q [ps [k] + cc [2]] ; + for (k = 0 ; k < nc ; k++) q [k + cc [2]] = wj [k] ; + for (k = 0 ; k < nc ; k++) wi [k] = p [ps [k] + rr [1]] ; + for (k = 0 ; k < nc ; k++) p [k + rr [1]] = wi [k] ; + nb2 = 0 ; /* create the fine block partitions */ + r [0] = s [0] = 0 ; + if (cc [2] > 0) nb2++ ; /* leading coarse block A (R1, [C0 C1]) */ + for (k = 0 ; k < nb1 ; k++) /* coarse block A (R2,C2) */ + { + r [nb2] = rs [k] + rr [1] ; /* A (R2,C2) splits into nb1 fine blocks */ + s [nb2] = rs [k] + cc [2] ; + nb2++ ; + } + if (rr [2] < m) + { + r [nb2] = rr [2] ; /* trailing coarse block A ([R3 R0], C3) */ + s [nb2] = cc [3] ; + nb2++ ; + } + r [nb2] = m ; + s [nb2] = n ; + D->nb = nb2 ; + cs_dfree (scc) ; + return (cs_ddone (D, C, NULL, 1)) ; +} diff --git a/vendor/cs/cs_droptol.c b/vendor/cs/cs_droptol.c new file mode 100644 index 0000000..59b8df2 --- /dev/null +++ b/vendor/cs/cs_droptol.c @@ -0,0 +1,9 @@ +#include "cs.h" +static CS_INT cs_tol (CS_INT i, CS_INT j, CS_ENTRY aij, void *tol) +{ + return (CS_ABS (aij) > *((double *) tol)) ; +} +CS_INT cs_droptol (cs *A, double tol) +{ + return (cs_fkeep (A, &cs_tol, &tol)) ; /* keep all large entries */ +} diff --git a/vendor/cs/cs_dropzeros.c b/vendor/cs/cs_dropzeros.c new file mode 100644 index 0000000..d93f605 --- /dev/null +++ b/vendor/cs/cs_dropzeros.c @@ -0,0 +1,9 @@ +#include "cs.h" +static CS_INT cs_nonzero (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) +{ + return (aij != 0) ; +} +CS_INT cs_dropzeros (cs *A) +{ + return (cs_fkeep (A, &cs_nonzero, NULL)) ; /* keep all nonzero entries */ +} diff --git a/vendor/cs/cs_dupl.c b/vendor/cs/cs_dupl.c new file mode 100644 index 0000000..fdf2e1e --- /dev/null +++ b/vendor/cs/cs_dupl.c @@ -0,0 +1,34 @@ +#include "cs.h" +/* remove duplicate entries from A */ +CS_INT cs_dupl (cs *A) +{ + CS_INT i, j, p, q, nz = 0, n, m, *Ap, *Ai, *w ; + CS_ENTRY *Ax ; + if (!CS_CSC (A)) return (0) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + w = cs_malloc (m, sizeof (CS_INT)) ; /* get workspace */ + if (!w) return (0) ; /* out of memory */ + for (i = 0 ; i < m ; i++) w [i] = -1 ; /* row i not yet seen */ + for (j = 0 ; j < n ; j++) + { + q = nz ; /* column j will start at q */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* A(i,j) is nonzero */ + if (w [i] >= q) + { + Ax [w [i]] += Ax [p] ; /* A(i,j) is a duplicate */ + } + else + { + w [i] = nz ; /* record where row i occurs */ + Ai [nz] = i ; /* keep A(i,j) */ + Ax [nz++] = Ax [p] ; + } + } + Ap [j] = q ; /* record start of column j */ + } + Ap [n] = nz ; /* finalize A */ + cs_free (w) ; /* free workspace */ + return (cs_sprealloc (A, 0)) ; /* remove extra space from A */ +} diff --git a/vendor/cs/cs_entry.c b/vendor/cs/cs_entry.c new file mode 100644 index 0000000..f712ba7 --- /dev/null +++ b/vendor/cs/cs_entry.c @@ -0,0 +1,13 @@ +#include "cs.h" +/* add an entry to a triplet matrix; return 1 if ok, 0 otherwise */ +CS_INT cs_entry (cs *T, CS_INT i, CS_INT j, CS_ENTRY x) +{ + if (!CS_TRIPLET (T) || i < 0 || j < 0) return (0) ; /* check inputs */ + if (T->nz >= T->nzmax && !cs_sprealloc (T,2*(T->nzmax))) return (0) ; + if (T->x) T->x [T->nz] = x ; + T->i [T->nz] = i ; + T->p [T->nz++] = j ; + T->m = CS_MAX (T->m, i+1) ; + T->n = CS_MAX (T->n, j+1) ; + return (1) ; +} diff --git a/vendor/cs/cs_ereach.c b/vendor/cs/cs_ereach.c new file mode 100644 index 0000000..9edad52 --- /dev/null +++ b/vendor/cs/cs_ereach.c @@ -0,0 +1,23 @@ +#include "cs.h" +/* find nonzero pattern of Cholesky L(k,1:k-1) using etree and triu(A(:,k)) */ +CS_INT cs_ereach (const cs *A, CS_INT k, const CS_INT *parent, CS_INT *s, CS_INT *w) +{ + CS_INT i, p, n, len, top, *Ap, *Ai ; + if (!CS_CSC (A) || !parent || !s || !w) return (-1) ; /* check inputs */ + top = n = A->n ; Ap = A->p ; Ai = A->i ; + CS_MARK (w, k) ; /* mark node k as visited */ + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + i = Ai [p] ; /* A(i,k) is nonzero */ + if (i > k) continue ; /* only use upper triangular part of A */ + for (len = 0 ; !CS_MARKED (w,i) ; i = parent [i]) /* traverse up etree*/ + { + s [len++] = i ; /* L(k,i) is nonzero */ + CS_MARK (w, i) ; /* mark i as visited */ + } + while (len > 0) s [--top] = s [--len] ; /* push path onto stack */ + } + for (p = top ; p < n ; p++) CS_MARK (w, s [p]) ; /* unmark all nodes */ + CS_MARK (w, k) ; /* unmark node k */ + return (top) ; /* s [top..n-1] contains pattern of L(k,:)*/ +} diff --git a/vendor/cs/cs_etree.c b/vendor/cs/cs_etree.c new file mode 100644 index 0000000..5620928 --- /dev/null +++ b/vendor/cs/cs_etree.c @@ -0,0 +1,30 @@ +#include "cs.h" +/* compute the etree of A (using triu(A), or A'A without forming A'A */ +CS_INT *cs_etree (const cs *A, CS_INT ata) +{ + CS_INT i, k, p, m, n, inext, *Ap, *Ai, *w, *parent, *ancestor, *prev ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; + parent = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (n + (ata ? m : 0), sizeof (CS_INT)) ; /* get workspace */ + if (!w || !parent) return (cs_idone (parent, NULL, w, 0)) ; + ancestor = w ; prev = w + n ; + if (ata) for (i = 0 ; i < m ; i++) prev [i] = -1 ; + for (k = 0 ; k < n ; k++) + { + parent [k] = -1 ; /* node k has no parent yet */ + ancestor [k] = -1 ; /* nor does k have an ancestor */ + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + i = ata ? (prev [Ai [p]]) : (Ai [p]) ; + for ( ; i != -1 && i < k ; i = inext) /* traverse from i to k */ + { + inext = ancestor [i] ; /* inext = ancestor of i */ + ancestor [i] = k ; /* path compression */ + if (inext == -1) parent [i] = k ; /* no anc., parent is k */ + } + if (ata) prev [Ai [p]] = k ; + } + } + return (cs_idone (parent, NULL, w, 1)) ; +} diff --git a/vendor/cs/cs_fkeep.c b/vendor/cs/cs_fkeep.c new file mode 100644 index 0000000..09219e8 --- /dev/null +++ b/vendor/cs/cs_fkeep.c @@ -0,0 +1,25 @@ +#include "cs.h" +/* drop entries for which fkeep(A(i,j)) is false; return nz if OK, else -1 */ +CS_INT cs_fkeep (cs *A, CS_INT (*fkeep) (CS_INT, CS_INT, CS_ENTRY, void *), void *other) +{ + CS_INT j, p, nz = 0, n, *Ap, *Ai ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !fkeep) return (-1) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; /* get current location of col j */ + Ap [j] = nz ; /* record new location of col j */ + for ( ; p < Ap [j+1] ; p++) + { + if (fkeep (Ai [p], j, Ax ? Ax [p] : 1, other)) + { + if (Ax) Ax [nz] = Ax [p] ; /* keep A(i,j) */ + Ai [nz++] = Ai [p] ; + } + } + } + Ap [n] = nz ; /* finalize A */ + cs_sprealloc (A, 0) ; /* remove extra space from A */ + return (nz) ; +} diff --git a/vendor/cs/cs_gaxpy.c b/vendor/cs/cs_gaxpy.c new file mode 100644 index 0000000..db93cbc --- /dev/null +++ b/vendor/cs/cs_gaxpy.c @@ -0,0 +1,17 @@ +#include "cs.h" +/* y = A*x+y */ +CS_INT cs_gaxpy (const cs *A, const CS_ENTRY *x, CS_ENTRY *y) +{ + CS_INT p, j, n, *Ap, *Ai ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !x || !y) return (0) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + y [Ai [p]] += Ax [p] * x [j] ; + } + } + return (1) ; +} diff --git a/vendor/cs/cs_happly.c b/vendor/cs/cs_happly.c new file mode 100644 index 0000000..98a306c --- /dev/null +++ b/vendor/cs/cs_happly.c @@ -0,0 +1,19 @@ +#include "cs.h" +/* apply the ith Householder vector to x */ +CS_INT cs_happly (const cs *V, CS_INT i, double beta, CS_ENTRY *x) +{ + CS_INT p, *Vp, *Vi ; + CS_ENTRY *Vx, tau = 0 ; + if (!CS_CSC (V) || !x) return (0) ; /* check inputs */ + Vp = V->p ; Vi = V->i ; Vx = V->x ; + for (p = Vp [i] ; p < Vp [i+1] ; p++) /* tau = v'*x */ + { + tau += CS_CONJ (Vx [p]) * x [Vi [p]] ; + } + tau *= beta ; /* tau = beta*(v'*x) */ + for (p = Vp [i] ; p < Vp [i+1] ; p++) /* x = x - v*tau */ + { + x [Vi [p]] -= Vx [p] * tau ; + } + return (1) ; +} diff --git a/vendor/cs/cs_house.c b/vendor/cs/cs_house.c new file mode 100644 index 0000000..e825c89 --- /dev/null +++ b/vendor/cs/cs_house.c @@ -0,0 +1,30 @@ +#include "cs.h" +/* create a Householder reflection [v,beta,s]=house(x), overwrite x with v, + * where (I-beta*v*v')*x = s*e1 and e1 = [1 0 ... 0]'. + * Note that this CXSparse version is different than CSparse. See Higham, + * Accuracy & Stability of Num Algorithms, 2nd ed, 2002, page 357. */ +CS_ENTRY cs_house (CS_ENTRY *x, double *beta, CS_INT n) +{ + CS_ENTRY s = 0 ; + CS_INT i ; + if (!x || !beta) return (-1) ; /* check inputs */ + /* s = norm(x) */ + for (i = 0 ; i < n ; i++) s += x [i] * CS_CONJ (x [i]) ; + s = sqrt (s) ; + if (s == 0) + { + (*beta) = 0 ; + x [0] = 1 ; + } + else + { + /* s = sign(x[0]) * norm (x) ; */ + if (x [0] != 0) + { + s *= x [0] / CS_ABS (x [0]) ; + } + x [0] += s ; + (*beta) = 1. / CS_REAL (CS_CONJ (s) * x [0]) ; + } + return (-s) ; +} diff --git a/vendor/cs/cs_ipvec.c b/vendor/cs/cs_ipvec.c new file mode 100644 index 0000000..4935ace --- /dev/null +++ b/vendor/cs/cs_ipvec.c @@ -0,0 +1,9 @@ +#include "cs.h" +/* x(p) = b, for dense vectors x and b; p=NULL denotes identity */ +CS_INT cs_ipvec (const CS_INT *p, const CS_ENTRY *b, CS_ENTRY *x, CS_INT n) +{ + CS_INT k ; + if (!x || !b) return (0) ; /* check inputs */ + for (k = 0 ; k < n ; k++) x [p ? p [k] : k] = b [k] ; + return (1) ; +} diff --git a/vendor/cs/cs_leaf.c b/vendor/cs/cs_leaf.c new file mode 100644 index 0000000..bd93bda --- /dev/null +++ b/vendor/cs/cs_leaf.c @@ -0,0 +1,22 @@ +#include "cs.h" +/* consider A(i,j), node j in ith row subtree and return lca(jprev,j) */ +CS_INT cs_leaf (CS_INT i, CS_INT j, const CS_INT *first, CS_INT *maxfirst, CS_INT *prevleaf, + CS_INT *ancestor, CS_INT *jleaf) +{ + CS_INT q, s, sparent, jprev ; + if (!first || !maxfirst || !prevleaf || !ancestor || !jleaf) return (-1) ; + *jleaf = 0 ; + if (i <= j || first [j] <= maxfirst [i]) return (-1) ; /* j not a leaf */ + maxfirst [i] = first [j] ; /* update max first[j] seen so far */ + jprev = prevleaf [i] ; /* jprev = previous leaf of ith subtree */ + prevleaf [i] = j ; + *jleaf = (jprev == -1) ? 1: 2 ; /* j is first or subsequent leaf */ + if (*jleaf == 1) return (i) ; /* if 1st leaf, q = root of ith subtree */ + for (q = jprev ; q != ancestor [q] ; q = ancestor [q]) ; + for (s = jprev ; s != q ; s = sparent) + { + sparent = ancestor [s] ; /* path compression */ + ancestor [s] = q ; + } + return (q) ; /* q = least common ancester (jprev,j) */ +} diff --git a/vendor/cs/cs_load.c b/vendor/cs/cs_load.c new file mode 100644 index 0000000..91e1f37 --- /dev/null +++ b/vendor/cs/cs_load.c @@ -0,0 +1,26 @@ +#include "cs.h" +/* load a triplet matrix from a file */ +cs *cs_load (FILE *f) +{ + double i, j ; /* use double for integers to avoid csi conflicts */ + double x ; +#ifdef CS_COMPLEX + double xi ; +#endif + cs *T ; + if (!f) return (NULL) ; /* check inputs */ + T = cs_spalloc (0, 0, 1, 1, 1) ; /* allocate result */ +#ifdef CS_COMPLEX + while (fscanf (f, "%lg %lg %lg %lg\n", &i, &j, &x, &xi) == 4) +#else + while (fscanf (f, "%lg %lg %lg\n", &i, &j, &x) == 3) +#endif + { +#ifdef CS_COMPLEX + if (!cs_entry (T, (CS_INT) i, (CS_INT) j, x + xi*I)) return (cs_spfree (T)) ; +#else + if (!cs_entry (T, (CS_INT) i, (CS_INT) j, x)) return (cs_spfree (T)) ; +#endif + } + return (T) ; +} diff --git a/vendor/cs/cs_lsolve.c b/vendor/cs/cs_lsolve.c new file mode 100644 index 0000000..099b0c5 --- /dev/null +++ b/vendor/cs/cs_lsolve.c @@ -0,0 +1,18 @@ +#include "cs.h" +/* solve Lx=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_lsolve (const cs *L, CS_ENTRY *x) +{ + CS_INT p, j, n, *Lp, *Li ; + CS_ENTRY *Lx ; + if (!CS_CSC (L) || !x) return (0) ; /* check inputs */ + n = L->n ; Lp = L->p ; Li = L->i ; Lx = L->x ; + for (j = 0 ; j < n ; j++) + { + x [j] /= Lx [Lp [j]] ; + for (p = Lp [j]+1 ; p < Lp [j+1] ; p++) + { + x [Li [p]] -= Lx [p] * x [j] ; + } + } + return (1) ; +} diff --git a/vendor/cs/cs_ltsolve.c b/vendor/cs/cs_ltsolve.c new file mode 100644 index 0000000..29b1ca2 --- /dev/null +++ b/vendor/cs/cs_ltsolve.c @@ -0,0 +1,18 @@ +#include "cs.h" +/* solve L'x=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_ltsolve (const cs *L, CS_ENTRY *x) +{ + CS_INT p, j, n, *Lp, *Li ; + CS_ENTRY *Lx ; + if (!CS_CSC (L) || !x) return (0) ; /* check inputs */ + n = L->n ; Lp = L->p ; Li = L->i ; Lx = L->x ; + for (j = n-1 ; j >= 0 ; j--) + { + for (p = Lp [j]+1 ; p < Lp [j+1] ; p++) + { + x [j] -= CS_CONJ (Lx [p]) * x [Li [p]] ; + } + x [j] /= CS_CONJ (Lx [Lp [j]]) ; + } + return (1) ; +} diff --git a/vendor/cs/cs_lu.c b/vendor/cs/cs_lu.c new file mode 100644 index 0000000..270172c --- /dev/null +++ b/vendor/cs/cs_lu.c @@ -0,0 +1,88 @@ +#include "cs.h" +/* [L,U,pinv]=lu(A, [q lnz unz]). lnz and unz can be guess */ +csn *cs_lu (const cs *A, const css *S, double tol) +{ + cs *L, *U ; + csn *N ; + CS_ENTRY pivot, *Lx, *Ux, *x ; + double a, t ; + CS_INT *Lp, *Li, *Up, *Ui, *pinv, *xi, *q, n, ipiv, k, top, p, i, col, lnz,unz; + if (!CS_CSC (A) || !S) return (NULL) ; /* check inputs */ + n = A->n ; + q = S->q ; lnz = S->lnz ; unz = S->unz ; + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + xi = cs_malloc (2*n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + if (!x || !xi || !N) return (cs_ndone (N, NULL, xi, x, 0)) ; + N->L = L = cs_spalloc (n, n, lnz, 1, 0) ; /* allocate result L */ + N->U = U = cs_spalloc (n, n, unz, 1, 0) ; /* allocate result U */ + N->pinv = pinv = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result pinv */ + if (!L || !U || !pinv) return (cs_ndone (N, NULL, xi, x, 0)) ; + Lp = L->p ; Up = U->p ; + for (i = 0 ; i < n ; i++) x [i] = 0 ; /* clear workspace */ + for (i = 0 ; i < n ; i++) pinv [i] = -1 ; /* no rows pivotal yet */ + for (k = 0 ; k <= n ; k++) Lp [k] = 0 ; /* no cols of L yet */ + lnz = unz = 0 ; + for (k = 0 ; k < n ; k++) /* compute L(:,k) and U(:,k) */ + { + /* --- Triangular solve --------------------------------------------- */ + Lp [k] = lnz ; /* L(:,k) starts here */ + Up [k] = unz ; /* U(:,k) starts here */ + if ((lnz + n > L->nzmax && !cs_sprealloc (L, 2*L->nzmax + n)) || + (unz + n > U->nzmax && !cs_sprealloc (U, 2*U->nzmax + n))) + { + return (cs_ndone (N, NULL, xi, x, 0)) ; + } + Li = L->i ; Lx = L->x ; Ui = U->i ; Ux = U->x ; + col = q ? (q [k]) : k ; + top = cs_spsolve (L, A, col, xi, x, pinv, 1) ; /* x = L\A(:,col) */ + /* --- Find pivot --------------------------------------------------- */ + ipiv = -1 ; + a = -1 ; + for (p = top ; p < n ; p++) + { + i = xi [p] ; /* x(i) is nonzero */ + if (pinv [i] < 0) /* row i is not yet pivotal */ + { + if ((t = CS_ABS (x [i])) > a) + { + a = t ; /* largest pivot candidate so far */ + ipiv = i ; + } + } + else /* x(i) is the entry U(pinv[i],k) */ + { + Ui [unz] = pinv [i] ; + Ux [unz++] = x [i] ; + } + } + if (ipiv == -1 || a <= 0) return (cs_ndone (N, NULL, xi, x, 0)) ; + /* tol=1 for partial pivoting; tol<1 gives preference to diagonal */ + if (pinv [col] < 0 && CS_ABS (x [col]) >= a*tol) ipiv = col ; + /* --- Divide by pivot ---------------------------------------------- */ + pivot = x [ipiv] ; /* the chosen pivot */ + Ui [unz] = k ; /* last entry in U(:,k) is U(k,k) */ + Ux [unz++] = pivot ; + pinv [ipiv] = k ; /* ipiv is the kth pivot row */ + Li [lnz] = ipiv ; /* first entry in L(:,k) is L(k,k) = 1 */ + Lx [lnz++] = 1 ; + for (p = top ; p < n ; p++) /* L(k+1:n,k) = x / pivot */ + { + i = xi [p] ; + if (pinv [i] < 0) /* x(i) is an entry in L(:,k) */ + { + Li [lnz] = i ; /* save unpermuted row in L */ + Lx [lnz++] = x [i] / pivot ; /* scale pivot column */ + } + x [i] = 0 ; /* x [0..n-1] = 0 for next k */ + } + } + /* --- Finalize L and U ------------------------------------------------- */ + Lp [n] = lnz ; + Up [n] = unz ; + Li = L->i ; /* fix row indices of L for final pinv */ + for (p = 0 ; p < lnz ; p++) Li [p] = pinv [Li [p]] ; + cs_sprealloc (L, 0) ; /* remove extra space from L and U */ + cs_sprealloc (U, 0) ; + return (cs_ndone (N, NULL, xi, x, 1)) ; /* success */ +} diff --git a/vendor/cs/cs_lusol.c b/vendor/cs/cs_lusol.c new file mode 100644 index 0000000..e0727e2 --- /dev/null +++ b/vendor/cs/cs_lusol.c @@ -0,0 +1,26 @@ +#include "cs.h" +/* x=A\b where A is unsymmetric; b overwritten with solution */ +CS_INT cs_lusol (CS_INT order, const cs *A, CS_ENTRY *b, double tol) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + CS_INT n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + S = cs_sqr (order, A, 0) ; /* ordering and symbolic analysis */ + N = cs_lu (A, S, tol) ; /* numeric LU factorization */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (N->pinv, b, x, n) ; /* x = b(p) */ + cs_lsolve (N->L, x) ; /* x = L\x */ + cs_usolve (N->U, x) ; /* x = U\x */ + cs_ipvec (S->q, x, b, n) ; /* b(q) = x */ + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + return (ok) ; +} diff --git a/vendor/cs/cs_malloc.c b/vendor/cs/cs_malloc.c new file mode 100644 index 0000000..2a3f6da --- /dev/null +++ b/vendor/cs/cs_malloc.c @@ -0,0 +1,35 @@ +#include "cs.h" +#ifdef MATLAB_MEX_FILE +#define malloc mxMalloc +#define free mxFree +#define realloc mxRealloc +#define calloc mxCalloc +#endif + +/* wrapper for malloc */ +void *cs_malloc (CS_INT n, size_t size) +{ + return (malloc (CS_MAX (n,1) * size)) ; +} + +/* wrapper for calloc */ +void *cs_calloc (CS_INT n, size_t size) +{ + return (calloc (CS_MAX (n,1), size)) ; +} + +/* wrapper for free */ +void *cs_free (void *p) +{ + if (p) free (p) ; /* free p if it is not already NULL */ + return (NULL) ; /* return NULL to simplify the use of cs_free */ +} + +/* wrapper for realloc */ +void *cs_realloc (void *p, CS_INT n, size_t size, CS_INT *ok) +{ + void *pnew ; + pnew = realloc (p, CS_MAX (n,1) * size) ; /* realloc the block */ + *ok = (pnew != NULL) ; /* realloc fails if pnew is NULL */ + return ((*ok) ? pnew : p) ; /* return original p if failure */ +} diff --git a/vendor/cs/cs_maxtrans.c b/vendor/cs/cs_maxtrans.c new file mode 100644 index 0000000..4947cee --- /dev/null +++ b/vendor/cs/cs_maxtrans.c @@ -0,0 +1,92 @@ +#include "cs.h" +/* find an augmenting path starting at column k and extend the match if found */ +static void cs_augment (CS_INT k, const cs *A, CS_INT *jmatch, CS_INT *cheap, CS_INT *w, + CS_INT *js, CS_INT *is, CS_INT *ps) +{ + CS_INT found = 0, p, i = -1, *Ap = A->p, *Ai = A->i, head = 0, j ; + js [0] = k ; /* start with just node k in jstack */ + while (head >= 0) + { + /* --- Start (or continue) depth-first-search at node j ------------- */ + j = js [head] ; /* get j from top of jstack */ + if (w [j] != k) /* 1st time j visited for kth path */ + { + w [j] = k ; /* mark j as visited for kth path */ + for (p = cheap [j] ; p < Ap [j+1] && !found ; p++) + { + i = Ai [p] ; /* try a cheap assignment (i,j) */ + found = (jmatch [i] == -1) ; + } + cheap [j] = p ; /* start here next time j is traversed*/ + if (found) + { + is [head] = i ; /* column j matched with row i */ + break ; /* end of augmenting path */ + } + ps [head] = Ap [j] ; /* no cheap match: start dfs for j */ + } + /* --- Depth-first-search of neighbors of j ------------------------- */ + for (p = ps [head] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* consider row i */ + if (w [jmatch [i]] == k) continue ; /* skip jmatch [i] if marked */ + ps [head] = p + 1 ; /* pause dfs of node j */ + is [head] = i ; /* i will be matched with j if found */ + js [++head] = jmatch [i] ; /* start dfs at column jmatch [i] */ + break ; + } + if (p == Ap [j+1]) head-- ; /* node j is done; pop from stack */ + } /* augment the match if path found: */ + if (found) for (p = head ; p >= 0 ; p--) jmatch [is [p]] = js [p] ; +} + +/* find a maximum transveral */ +CS_INT *cs_maxtrans (const cs *A, CS_INT seed) /*[jmatch [0..m-1]; imatch [0..n-1]]*/ +{ + CS_INT i, j, k, n, m, p, n2 = 0, m2 = 0, *Ap, *jimatch, *w, *cheap, *js, *is, + *ps, *Ai, *Cp, *jmatch, *imatch, *q ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; m = A->m ; Ap = A->p ; Ai = A->i ; + w = jimatch = cs_calloc (m+n, sizeof (CS_INT)) ; /* allocate result */ + if (!jimatch) return (NULL) ; + for (k = 0, j = 0 ; j < n ; j++) /* count nonempty rows and columns */ + { + n2 += (Ap [j] < Ap [j+1]) ; + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + w [Ai [p]] = 1 ; + k += (j == Ai [p]) ; /* count entries already on diagonal */ + } + } + if (k == CS_MIN (m,n)) /* quick return if diagonal zero-free */ + { + jmatch = jimatch ; imatch = jimatch + m ; + for (i = 0 ; i < k ; i++) jmatch [i] = i ; + for ( ; i < m ; i++) jmatch [i] = -1 ; + for (j = 0 ; j < k ; j++) imatch [j] = j ; + for ( ; j < n ; j++) imatch [j] = -1 ; + return (cs_idone (jimatch, NULL, NULL, 1)) ; + } + for (i = 0 ; i < m ; i++) m2 += w [i] ; + C = (m2 < n2) ? cs_transpose (A,0) : ((cs *) A) ; /* transpose if needed */ + if (!C) return (cs_idone (jimatch, (m2 < n2) ? C : NULL, NULL, 0)) ; + n = C->n ; m = C->m ; Cp = C->p ; + jmatch = (m2 < n2) ? jimatch + n : jimatch ; + imatch = (m2 < n2) ? jimatch : jimatch + m ; + w = cs_malloc (5*n, sizeof (CS_INT)) ; /* get workspace */ + if (!w) return (cs_idone (jimatch, (m2 < n2) ? C : NULL, w, 0)) ; + cheap = w + n ; js = w + 2*n ; is = w + 3*n ; ps = w + 4*n ; + for (j = 0 ; j < n ; j++) cheap [j] = Cp [j] ; /* for cheap assignment */ + for (j = 0 ; j < n ; j++) w [j] = -1 ; /* all columns unflagged */ + for (i = 0 ; i < m ; i++) jmatch [i] = -1 ; /* nothing matched yet */ + q = cs_randperm (n, seed) ; /* q = random permutation */ + for (k = 0 ; k < n ; k++) /* augment, starting at column q[k] */ + { + cs_augment (q ? q [k]: k, C, jmatch, cheap, w, js, is, ps) ; + } + cs_free (q) ; + for (j = 0 ; j < n ; j++) imatch [j] = -1 ; /* find row match */ + for (i = 0 ; i < m ; i++) if (jmatch [i] >= 0) imatch [jmatch [i]] = i ; + return (cs_idone (jimatch, (m2 < n2) ? C : NULL, w, 1)) ; +} diff --git a/vendor/cs/cs_multiply.c b/vendor/cs/cs_multiply.c new file mode 100644 index 0000000..34e3a36 --- /dev/null +++ b/vendor/cs/cs_multiply.c @@ -0,0 +1,35 @@ +#include "cs.h" +/* C = A*B */ +cs *cs_multiply (const cs *A, const cs *B) +{ + CS_INT p, j, nz = 0, anz, *Cp, *Ci, *Bp, m, n, bnz, *w, values, *Bi ; + CS_ENTRY *x, *Bx, *Cx ; + cs *C ; + if (!CS_CSC (A) || !CS_CSC (B)) return (NULL) ; /* check inputs */ + if (A->n != B->m) return (NULL) ; + m = A->m ; anz = A->p [A->n] ; + n = B->n ; Bp = B->p ; Bi = B->i ; Bx = B->x ; bnz = Bp [n] ; + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + values = (A->x != NULL) && (Bx != NULL) ; + x = values ? cs_malloc (m, sizeof (CS_ENTRY)) : NULL ; /* get workspace */ + C = cs_spalloc (m, n, anz + bnz, values, 0) ; /* allocate result */ + if (!C || !w || (values && !x)) return (cs_done (C, w, x, 0)) ; + Cp = C->p ; + for (j = 0 ; j < n ; j++) + { + if (nz + m > C->nzmax && !cs_sprealloc (C, 2*(C->nzmax)+m)) + { + return (cs_done (C, w, x, 0)) ; /* out of memory */ + } + Ci = C->i ; Cx = C->x ; /* C->i and C->x may be reallocated */ + Cp [j] = nz ; /* column j of C starts here */ + for (p = Bp [j] ; p < Bp [j+1] ; p++) + { + nz = cs_scatter (A, Bi [p], Bx ? Bx [p] : 1, w, x, j+1, C, nz) ; + } + if (values) for (p = Cp [j] ; p < nz ; p++) Cx [p] = x [Ci [p]] ; + } + Cp [n] = nz ; /* finalize the last column of C */ + cs_sprealloc (C, 0) ; /* remove extra space from C */ + return (cs_done (C, w, x, 1)) ; /* success; free workspace, return C */ +} diff --git a/vendor/cs/cs_norm.c b/vendor/cs/cs_norm.c new file mode 100644 index 0000000..0e7b3c6 --- /dev/null +++ b/vendor/cs/cs_norm.c @@ -0,0 +1,16 @@ +#include "cs.h" +/* 1-norm of a sparse matrix = max (sum (abs (A))), largest column sum */ +double cs_norm (const cs *A) +{ + CS_INT p, j, n, *Ap ; + CS_ENTRY *Ax ; + double norm = 0, s ; + if (!CS_CSC (A) || !A->x) return (-1) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + for (s = 0, p = Ap [j] ; p < Ap [j+1] ; p++) s += CS_ABS (Ax [p]) ; + norm = CS_MAX (norm, s) ; + } + return (norm) ; +} diff --git a/vendor/cs/cs_permute.c b/vendor/cs/cs_permute.c new file mode 100644 index 0000000..9adae45 --- /dev/null +++ b/vendor/cs/cs_permute.c @@ -0,0 +1,25 @@ +#include "cs.h" +/* C = A(p,q) where p and q are permutations of 0..m-1 and 0..n-1. */ +cs *cs_permute (const cs *A, const CS_INT *pinv, const CS_INT *q, CS_INT values) +{ + CS_INT t, j, k, nz = 0, m, n, *Ap, *Ai, *Cp, *Ci ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (m, n, Ap [n], values && Ax != NULL, 0) ; /* alloc result */ + if (!C) return (cs_done (C, NULL, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (k = 0 ; k < n ; k++) + { + Cp [k] = nz ; /* column k of C is column q[k] of A */ + j = q ? (q [k]) : k ; + for (t = Ap [j] ; t < Ap [j+1] ; t++) + { + if (Cx) Cx [nz] = Ax [t] ; /* row i of A is row pinv[i] of C */ + Ci [nz++] = pinv ? (pinv [Ai [t]]) : Ai [t] ; + } + } + Cp [n] = nz ; /* finalize the last column of C */ + return (cs_done (C, NULL, NULL, 1)) ; +} diff --git a/vendor/cs/cs_pinv.c b/vendor/cs/cs_pinv.c new file mode 100644 index 0000000..de0660e --- /dev/null +++ b/vendor/cs/cs_pinv.c @@ -0,0 +1,11 @@ +#include "cs.h" +/* pinv = p', or p = pinv' */ +CS_INT *cs_pinv (CS_INT const *p, CS_INT n) +{ + CS_INT k, *pinv ; + if (!p) return (NULL) ; /* p = NULL denotes identity */ + pinv = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + if (!pinv) return (NULL) ; /* out of memory */ + for (k = 0 ; k < n ; k++) pinv [p [k]] = k ;/* invert the permutation */ + return (pinv) ; /* return result */ +} diff --git a/vendor/cs/cs_post.c b/vendor/cs/cs_post.c new file mode 100644 index 0000000..0f61203 --- /dev/null +++ b/vendor/cs/cs_post.c @@ -0,0 +1,24 @@ +#include "cs.h" +/* post order a forest */ +CS_INT *cs_post (const CS_INT *parent, CS_INT n) +{ + CS_INT j, k = 0, *post, *w, *head, *next, *stack ; + if (!parent) return (NULL) ; /* check inputs */ + post = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (3*n, sizeof (CS_INT)) ; /* get workspace */ + if (!w || !post) return (cs_idone (post, NULL, w, 0)) ; + head = w ; next = w + n ; stack = w + 2*n ; + for (j = 0 ; j < n ; j++) head [j] = -1 ; /* empty linked lists */ + for (j = n-1 ; j >= 0 ; j--) /* traverse nodes in reverse order*/ + { + if (parent [j] == -1) continue ; /* j is a root */ + next [j] = head [parent [j]] ; /* add j to list of its parent */ + head [parent [j]] = j ; + } + for (j = 0 ; j < n ; j++) + { + if (parent [j] != -1) continue ; /* skip j if it is not a root */ + k = cs_tdfs (j, k, head, next, post, stack) ; + } + return (cs_idone (post, NULL, w, 1)) ; /* success; free w, return post */ +} diff --git a/vendor/cs/cs_print.c b/vendor/cs/cs_print.c new file mode 100644 index 0000000..7e7b16d --- /dev/null +++ b/vendor/cs/cs_print.c @@ -0,0 +1,55 @@ +#include "cs.h" +/* print a sparse matrix; use %g for integers to avoid differences with CS_INT */ + +/* Disabled for igraph as it prints to stdio */ +#if 0 +CS_INT cs_print (const cs *A, CS_INT brief) +{ + CS_INT p, j, m, n, nzmax, nz, *Ap, *Ai ; + CS_ENTRY *Ax ; + if (!A) { printf ("(null)\n") ; return (0) ; } + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + nzmax = A->nzmax ; nz = A->nz ; + printf ("CXSparse Version %d.%d.%d, %s. %s\n", CS_VER, CS_SUBVER, + CS_SUBSUB, CS_DATE, CS_COPYRIGHT) ; + if (nz < 0) + { + printf ("%g-by-%g, nzmax: %g nnz: %g, 1-norm: %g\n", (double) m, + (double) n, (double) nzmax, (double) (Ap [n]), cs_norm (A)) ; + for (j = 0 ; j < n ; j++) + { + printf (" col %g : locations %g to %g\n", (double) j, + (double) (Ap [j]), (double) (Ap [j+1]-1)) ; + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + printf (" %g : ", (double) (Ai [p])) ; +#ifdef CS_COMPLEX + printf ("(%g, %g)\n", + Ax ? CS_REAL (Ax [p]) : 1, Ax ? CS_IMAG (Ax [p]) : 0) ; +#else + printf ("%g\n", Ax ? Ax [p] : 1) ; +#endif + if (brief && p > 20) { printf (" ...\n") ; return (1) ; } + } + } + } + else + { + printf ("triplet: %g-by-%g, nzmax: %g nnz: %g\n", (double) m, + (double) n, (double) nzmax, (double) nz) ; + for (p = 0 ; p < nz ; p++) + { + + printf (" %g %g : ", (double) (Ai [p]), (double) (Ap [p])) ; +#ifdef CS_COMPLEX + printf ("(%g, %g)\n", + Ax ? CS_REAL (Ax [p]) : 1, Ax ? CS_IMAG (Ax [p]) : 0) ; +#else + printf ("%g\n", Ax ? Ax [p] : 1) ; +#endif + if (brief && p > 20) { printf (" ...\n") ; return (1) ; } + } + } + return (1) ; +} +#endif diff --git a/vendor/cs/cs_pvec.c b/vendor/cs/cs_pvec.c new file mode 100644 index 0000000..1254c2a --- /dev/null +++ b/vendor/cs/cs_pvec.c @@ -0,0 +1,9 @@ +#include "cs.h" +/* x = b(p), for dense vectors x and b; p=NULL denotes identity */ +CS_INT cs_pvec (const CS_INT *p, const CS_ENTRY *b, CS_ENTRY *x, CS_INT n) +{ + CS_INT k ; + if (!x || !b) return (0) ; /* check inputs */ + for (k = 0 ; k < n ; k++) x [k] = b [p ? p [k] : k] ; + return (1) ; +} diff --git a/vendor/cs/cs_qr.c b/vendor/cs/cs_qr.c new file mode 100644 index 0000000..8bce32e --- /dev/null +++ b/vendor/cs/cs_qr.c @@ -0,0 +1,74 @@ +#include "cs.h" +/* sparse QR factorization [V,beta,pinv,R] = qr (A) */ +csn *cs_qr (const cs *A, const css *S) +{ + CS_ENTRY *Rx, *Vx, *Ax, *x ; + double *Beta ; + CS_INT i, k, p, n, vnz, p1, top, m2, len, col, rnz, *s, *leftmost, *Ap, *Ai, + *parent, *Rp, *Ri, *Vp, *Vi, *w, *pinv, *q ; + cs *R, *V ; + csn *N ; + if (!CS_CSC (A) || !S) return (NULL) ; + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + q = S->q ; parent = S->parent ; pinv = S->pinv ; m2 = S->m2 ; + vnz = S->lnz ; rnz = S->unz ; leftmost = S->leftmost ; + w = cs_malloc (m2+n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + x = cs_malloc (m2, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + if (!w || !x || !N) return (cs_ndone (N, NULL, w, x, 0)) ; + s = w + m2 ; /* s is size n */ + for (k = 0 ; k < m2 ; k++) x [k] = 0 ; /* clear workspace x */ + N->L = V = cs_spalloc (m2, n, vnz, 1, 0) ; /* allocate result V */ + N->U = R = cs_spalloc (m2, n, rnz, 1, 0) ; /* allocate result R */ + N->B = Beta = cs_malloc (n, sizeof (double)) ; /* allocate result Beta */ + if (!R || !V || !Beta) return (cs_ndone (N, NULL, w, x, 0)) ; + Rp = R->p ; Ri = R->i ; Rx = R->x ; + Vp = V->p ; Vi = V->i ; Vx = V->x ; + for (i = 0 ; i < m2 ; i++) w [i] = -1 ; /* clear w, to mark nodes */ + rnz = 0 ; vnz = 0 ; + for (k = 0 ; k < n ; k++) /* compute V and R */ + { + Rp [k] = rnz ; /* R(:,k) starts here */ + Vp [k] = p1 = vnz ; /* V(:,k) starts here */ + w [k] = k ; /* add V(k,k) to pattern of V */ + Vi [vnz++] = k ; + top = n ; + col = q ? q [k] : k ; + for (p = Ap [col] ; p < Ap [col+1] ; p++) /* find R(:,k) pattern */ + { + i = leftmost [Ai [p]] ; /* i = min(find(A(i,q))) */ + for (len = 0 ; w [i] != k ; i = parent [i]) /* traverse up to k */ + { + s [len++] = i ; + w [i] = k ; + } + while (len > 0) s [--top] = s [--len] ; /* push path on stack */ + i = pinv [Ai [p]] ; /* i = permuted row of A(:,col) */ + x [i] = Ax [p] ; /* x (i) = A(:,col) */ + if (i > k && w [i] < k) /* pattern of V(:,k) = x (k+1:m) */ + { + Vi [vnz++] = i ; /* add i to pattern of V(:,k) */ + w [i] = k ; + } + } + for (p = top ; p < n ; p++) /* for each i in pattern of R(:,k) */ + { + i = s [p] ; /* R(i,k) is nonzero */ + cs_happly (V, i, Beta [i], x) ; /* apply (V(i),Beta(i)) to x */ + Ri [rnz] = i ; /* R(i,k) = x(i) */ + Rx [rnz++] = x [i] ; + x [i] = 0 ; + if (parent [i] == k) vnz = cs_scatter (V, i, 0, w, NULL, k, V, vnz); + } + for (p = p1 ; p < vnz ; p++) /* gather V(:,k) = x */ + { + Vx [p] = x [Vi [p]] ; + x [Vi [p]] = 0 ; + } + Ri [rnz] = k ; /* R(k,k) = norm (x) */ + Rx [rnz++] = cs_house (Vx+p1, Beta+k, vnz-p1) ; /* [v,beta]=house(x) */ + } + Rp [n] = rnz ; /* finalize R */ + Vp [n] = vnz ; /* finalize V */ + return (cs_ndone (N, NULL, w, x, 1)) ; /* success */ +} diff --git a/vendor/cs/cs_qrsol.c b/vendor/cs/cs_qrsol.c new file mode 100644 index 0000000..d817ef2 --- /dev/null +++ b/vendor/cs/cs_qrsol.c @@ -0,0 +1,53 @@ +#include "cs.h" +/* x=A\b where A can be rectangular; b overwritten with solution */ +CS_INT cs_qrsol (CS_INT order, const cs *A, CS_ENTRY *b) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + cs *AT = NULL ; + CS_INT k, m, n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + m = A->m ; + if (m >= n) + { + S = cs_sqr (order, A, 1) ; /* ordering and symbolic analysis */ + N = cs_qr (A, S) ; /* numeric QR factorization */ + x = cs_calloc (S ? S->m2 : 1, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (S->pinv, b, x, m) ; /* x(0:m-1) = b(p(0:m-1) */ + for (k = 0 ; k < n ; k++) /* apply Householder refl. to x */ + { + cs_happly (N->L, k, N->B [k], x) ; + } + cs_usolve (N->U, x) ; /* x = R\x */ + cs_ipvec (S->q, x, b, n) ; /* b(q(0:n-1)) = x(0:n-1) */ + } + } + else + { + AT = cs_transpose (A, 1) ; /* Ax=b is underdetermined */ + S = cs_sqr (order, AT, 1) ; /* ordering and symbolic analysis */ + N = cs_qr (AT, S) ; /* numeric QR factorization of A' */ + x = cs_calloc (S ? S->m2 : 1, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (AT && S && N && x) ; + if (ok) + { + cs_pvec (S->q, b, x, m) ; /* x(q(0:m-1)) = b(0:m-1) */ + cs_utsolve (N->U, x) ; /* x = R'\x */ + for (k = m-1 ; k >= 0 ; k--) /* apply Householder refl. to x */ + { + cs_happly (N->L, k, N->B [k], x) ; + } + cs_pvec (S->pinv, x, b, n) ; /* b(0:n-1) = x(p(0:n-1)) */ + } + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + cs_spfree (AT) ; + return (ok) ; +} diff --git a/vendor/cs/cs_randperm.c b/vendor/cs/cs_randperm.c new file mode 100644 index 0000000..937187e --- /dev/null +++ b/vendor/cs/cs_randperm.c @@ -0,0 +1,26 @@ +#include "cs.h" + +#include "igraph_random.h" + +/* return a random permutation vector, the identity perm, or p = n-1:-1:0. + * seed = -1 means p = n-1:-1:0. seed = 0 means p = identity. otherwise + * p = random permutation. */ +CS_INT *cs_randperm (CS_INT n, CS_INT seed) +{ + CS_INT *p, k, j, t ; + if (seed == 0) return (NULL) ; /* return p = NULL (identity) */ + p = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + if (!p) return (NULL) ; /* out of memory */ + for (k = 0 ; k < n ; k++) p [k] = n-k-1 ; + if (seed == -1) return (p) ; /* return reverse permutation */ + /* srand (seed) ; /\* get new random number seed *\/ */ + for (k = 0 ; k < n ; k++) + { + /* j = k + (rand ( ) % (n-k)) ; /\* j = rand CS_INT in range k to n-1 *\/ */ + j = RNG_INTEGER(k, n-1) ; + t = p [j] ; /* swap p[k] and p[j] */ + p [j] = p [k] ; + p [k] = t ; + } + return (p) ; +} diff --git a/vendor/cs/cs_reach.c b/vendor/cs/cs_reach.c new file mode 100644 index 0000000..0efb342 --- /dev/null +++ b/vendor/cs/cs_reach.c @@ -0,0 +1,19 @@ +#include "cs.h" +/* xi [top...n-1] = nodes reachable from graph of G*P' via nodes in B(:,k). + * xi [n...2n-1] used as workspace */ +CS_INT cs_reach (cs *G, const cs *B, CS_INT k, CS_INT *xi, const CS_INT *pinv) +{ + CS_INT p, n, top, *Bp, *Bi, *Gp ; + if (!CS_CSC (G) || !CS_CSC (B) || !xi) return (-1) ; /* check inputs */ + n = G->n ; Bp = B->p ; Bi = B->i ; Gp = G->p ; + top = n ; + for (p = Bp [k] ; p < Bp [k+1] ; p++) + { + if (!CS_MARKED (Gp, Bi [p])) /* start a dfs at unmarked node i */ + { + top = cs_dfs (Bi [p], G, top, xi, xi+n, pinv) ; + } + } + for (p = top ; p < n ; p++) CS_MARK (Gp, xi [p]) ; /* restore G */ + return (top) ; +} diff --git a/vendor/cs/cs_scatter.c b/vendor/cs/cs_scatter.c new file mode 100644 index 0000000..734fdb2 --- /dev/null +++ b/vendor/cs/cs_scatter.c @@ -0,0 +1,22 @@ +#include "cs.h" +/* x = x + beta * A(:,j), where x is a dense vector and A(:,j) is sparse */ +CS_INT cs_scatter (const cs *A, CS_INT j, CS_ENTRY beta, CS_INT *w, CS_ENTRY *x, CS_INT mark, + cs *C, CS_INT nz) +{ + CS_INT i, p, *Ap, *Ai, *Ci ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !w || !CS_CSC (C)) return (-1) ; /* check inputs */ + Ap = A->p ; Ai = A->i ; Ax = A->x ; Ci = C->i ; + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* A(i,j) is nonzero */ + if (w [i] < mark) + { + w [i] = mark ; /* i is new entry in column j */ + Ci [nz++] = i ; /* add i to pattern of C(:,j) */ + if (x) x [i] = beta * Ax [p] ; /* x(i) = beta*A(i,j) */ + } + else if (x) x [i] += beta * Ax [p] ; /* i exists in C(:,j) already */ + } + return (nz) ; +} diff --git a/vendor/cs/cs_scc.c b/vendor/cs/cs_scc.c new file mode 100644 index 0000000..cc6d805 --- /dev/null +++ b/vendor/cs/cs_scc.c @@ -0,0 +1,41 @@ +#include "cs.h" +/* find the strongly connected components of a square matrix */ +csd *cs_scc (cs *A) /* matrix A temporarily modified, then restored */ +{ + CS_INT n, i, k, b, nb = 0, top, *xi, *pstack, *p, *r, *Ap, *ATp, *rcopy, *Blk ; + cs *AT ; + csd *D ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; Ap = A->p ; + D = cs_dalloc (n, 0) ; /* allocate result */ + AT = cs_transpose (A, 0) ; /* AT = A' */ + xi = cs_malloc (2*n+1, sizeof (CS_INT)) ; /* get workspace */ + if (!D || !AT || !xi) return (cs_ddone (D, AT, xi, 0)) ; + Blk = xi ; rcopy = pstack = xi + n ; + p = D->p ; r = D->r ; ATp = AT->p ; + top = n ; + for (i = 0 ; i < n ; i++) /* first dfs(A) to find finish times (xi) */ + { + if (!CS_MARKED (Ap, i)) top = cs_dfs (i, A, top, xi, pstack, NULL) ; + } + for (i = 0 ; i < n ; i++) CS_MARK (Ap, i) ; /* restore A; unmark all nodes*/ + top = n ; + nb = n ; + for (k = 0 ; k < n ; k++) /* dfs(A') to find strongly connnected comp */ + { + i = xi [k] ; /* get i in reverse order of finish times */ + if (CS_MARKED (ATp, i)) continue ; /* skip node i if already ordered */ + r [nb--] = top ; /* node i is the start of a component in p */ + top = cs_dfs (i, AT, top, p, pstack, NULL) ; + } + r [nb] = 0 ; /* first block starts at zero; shift r up */ + for (k = nb ; k <= n ; k++) r [k-nb] = r [k] ; + D->nb = nb = n-nb ; /* nb = # of strongly connected components */ + for (b = 0 ; b < nb ; b++) /* sort each block in natural order */ + { + for (k = r [b] ; k < r [b+1] ; k++) Blk [p [k]] = b ; + } + for (b = 0 ; b <= nb ; b++) rcopy [b] = r [b] ; + for (i = 0 ; i < n ; i++) p [rcopy [Blk [i]]++] = i ; + return (cs_ddone (D, AT, xi, 1)) ; +} diff --git a/vendor/cs/cs_schol.c b/vendor/cs/cs_schol.c new file mode 100644 index 0000000..7da2a57 --- /dev/null +++ b/vendor/cs/cs_schol.c @@ -0,0 +1,26 @@ +#include "cs.h" +/* ordering and symbolic analysis for a Cholesky factorization */ +css *cs_schol (CS_INT order, const cs *A) +{ + CS_INT n, *c, *post, *P ; + cs *C ; + css *S ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; + S = cs_calloc (1, sizeof (css)) ; /* allocate result S */ + if (!S) return (NULL) ; /* out of memory */ + P = cs_amd (order, A) ; /* P = amd(A+A'), or natural */ + S->pinv = cs_pinv (P, n) ; /* find inverse permutation */ + cs_free (P) ; + if (order && !S->pinv) return (cs_sfree (S)) ; + C = cs_symperm (A, S->pinv, 0) ; /* C = spones(triu(A(P,P))) */ + S->parent = cs_etree (C, 0) ; /* find etree of C */ + post = cs_post (S->parent, n) ; /* postorder the etree */ + c = cs_counts (C, S->parent, post, 0) ; /* find column counts of chol(C) */ + cs_free (post) ; + cs_spfree (C) ; + S->cp = cs_malloc (n+1, sizeof (CS_INT)) ; /* allocate result S->cp */ + S->unz = S->lnz = cs_cumsum (S->cp, c, n) ; /* find column pointers for L */ + cs_free (c) ; + return ((S->lnz >= 0) ? S : cs_sfree (S)) ; +} diff --git a/vendor/cs/cs_spsolve.c b/vendor/cs/cs_spsolve.c new file mode 100644 index 0000000..8c6ecce --- /dev/null +++ b/vendor/cs/cs_spsolve.c @@ -0,0 +1,28 @@ +#include "cs.h" +/* solve Gx=b(:,k), where G is either upper (lo=0) or lower (lo=1) triangular */ +CS_INT cs_spsolve (cs *G, const cs *B, CS_INT k, CS_INT *xi, CS_ENTRY *x, const CS_INT *pinv, + CS_INT lo) +{ + CS_INT j, J, p, q, px, top, n, *Gp, *Gi, *Bp, *Bi ; + CS_ENTRY *Gx, *Bx ; + if (!CS_CSC (G) || !CS_CSC (B) || !xi || !x) return (-1) ; + Gp = G->p ; Gi = G->i ; Gx = G->x ; n = G->n ; + Bp = B->p ; Bi = B->i ; Bx = B->x ; + top = cs_reach (G, B, k, xi, pinv) ; /* xi[top..n-1]=Reach(B(:,k)) */ + for (p = top ; p < n ; p++) x [xi [p]] = 0 ; /* clear x */ + for (p = Bp [k] ; p < Bp [k+1] ; p++) x [Bi [p]] = Bx [p] ; /* scatter B */ + for (px = top ; px < n ; px++) + { + j = xi [px] ; /* x(j) is nonzero */ + J = pinv ? (pinv [j]) : j ; /* j maps to col J of G */ + if (J < 0) continue ; /* column J is empty */ + x [j] /= Gx [lo ? (Gp [J]) : (Gp [J+1]-1)] ;/* x(j) /= G(j,j) */ + p = lo ? (Gp [J]+1) : (Gp [J]) ; /* lo: L(j,j) 1st entry */ + q = lo ? (Gp [J+1]) : (Gp [J+1]-1) ; /* up: U(j,j) last entry */ + for ( ; p < q ; p++) + { + x [Gi [p]] -= Gx [p] * x [j] ; /* x(i) -= G(i,j) * x(j) */ + } + } + return (top) ; /* return top of stack */ +} diff --git a/vendor/cs/cs_sqr.c b/vendor/cs/cs_sqr.c new file mode 100644 index 0000000..1b14ca4 --- /dev/null +++ b/vendor/cs/cs_sqr.c @@ -0,0 +1,87 @@ +#include "cs.h" +/* compute nnz(V) = S->lnz, S->pinv, S->leftmost, S->m2 from A and S->parent */ +static CS_INT cs_vcount (const cs *A, css *S) +{ + CS_INT i, k, p, pa, n = A->n, m = A->m, *Ap = A->p, *Ai = A->i, *next, *head, + *tail, *nque, *pinv, *leftmost, *w, *parent = S->parent ; + S->pinv = pinv = cs_malloc (m+n, sizeof (CS_INT)) ; /* allocate pinv, */ + S->leftmost = leftmost = cs_malloc (m, sizeof (CS_INT)) ; /* and leftmost */ + w = cs_malloc (m+3*n, sizeof (CS_INT)) ; /* get workspace */ + if (!pinv || !w || !leftmost) + { + cs_free (w) ; /* pinv and leftmost freed later */ + return (0) ; /* out of memory */ + } + next = w ; head = w + m ; tail = w + m + n ; nque = w + m + 2*n ; + for (k = 0 ; k < n ; k++) head [k] = -1 ; /* queue k is empty */ + for (k = 0 ; k < n ; k++) tail [k] = -1 ; + for (k = 0 ; k < n ; k++) nque [k] = 0 ; + for (i = 0 ; i < m ; i++) leftmost [i] = -1 ; + for (k = n-1 ; k >= 0 ; k--) + { + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + leftmost [Ai [p]] = k ; /* leftmost[i] = min(find(A(i,:)))*/ + } + } + for (i = m-1 ; i >= 0 ; i--) /* scan rows in reverse order */ + { + pinv [i] = -1 ; /* row i is not yet ordered */ + k = leftmost [i] ; + if (k == -1) continue ; /* row i is empty */ + if (nque [k]++ == 0) tail [k] = i ; /* first row in queue k */ + next [i] = head [k] ; /* put i at head of queue k */ + head [k] = i ; + } + S->lnz = 0 ; + S->m2 = m ; + for (k = 0 ; k < n ; k++) /* find row permutation and nnz(V)*/ + { + i = head [k] ; /* remove row i from queue k */ + S->lnz++ ; /* count V(k,k) as nonzero */ + if (i < 0) i = S->m2++ ; /* add a fictitious row */ + pinv [i] = k ; /* associate row i with V(:,k) */ + if (--nque [k] <= 0) continue ; /* skip if V(k+1:m,k) is empty */ + S->lnz += nque [k] ; /* nque [k] is nnz (V(k+1:m,k)) */ + if ((pa = parent [k]) != -1) /* move all rows to parent of k */ + { + if (nque [pa] == 0) tail [pa] = tail [k] ; + next [tail [k]] = head [pa] ; + head [pa] = next [i] ; + nque [pa] += nque [k] ; + } + } + for (i = 0 ; i < m ; i++) if (pinv [i] < 0) pinv [i] = k++ ; + cs_free (w) ; + return (1) ; +} + +/* symbolic ordering and analysis for QR or LU */ +css *cs_sqr (CS_INT order, const cs *A, CS_INT qr) +{ + CS_INT n, k, ok = 1, *post ; + css *S ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; + S = cs_calloc (1, sizeof (css)) ; /* allocate result S */ + if (!S) return (NULL) ; /* out of memory */ + S->q = cs_amd (order, A) ; /* fill-reducing ordering */ + if (order && !S->q) return (cs_sfree (S)) ; + if (qr) /* QR symbolic analysis */ + { + cs *C = order ? cs_permute (A, NULL, S->q, 0) : ((cs *) A) ; + S->parent = cs_etree (C, 1) ; /* etree of C'*C, where C=A(:,q) */ + post = cs_post (S->parent, n) ; + S->cp = cs_counts (C, S->parent, post, 1) ; /* col counts chol(C'*C) */ + cs_free (post) ; + ok = C && S->parent && S->cp && cs_vcount (C, S) ; + if (ok) for (S->unz = 0, k = 0 ; k < n ; k++) S->unz += S->cp [k] ; + if (order) cs_spfree (C) ; + } + else + { + S->unz = 4*(A->p [n]) + n ; /* for LU factorization only, */ + S->lnz = S->unz ; /* guess nnz(L) and nnz(U) */ + } + return (ok ? S : cs_sfree (S)) ; /* return result S */ +} diff --git a/vendor/cs/cs_symperm.c b/vendor/cs/cs_symperm.c new file mode 100644 index 0000000..7bbd6fe --- /dev/null +++ b/vendor/cs/cs_symperm.c @@ -0,0 +1,39 @@ +#include "cs.h" +/* C = A(p,p) where A and C are symmetric the upper part stored; pinv not p */ +cs *cs_symperm (const cs *A, const CS_INT *pinv, CS_INT values) +{ + CS_INT i, j, p, q, i2, j2, n, *Ap, *Ai, *Cp, *Ci, *w ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (n, n, Ap [n], values && (Ax != NULL), 0) ; /* alloc result*/ + w = cs_calloc (n, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (j = 0 ; j < n ; j++) /* count entries in each column of C */ + { + j2 = pinv ? pinv [j] : j ; /* column j of A is column j2 of C */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (i > j) continue ; /* skip lower triangular part of A */ + i2 = pinv ? pinv [i] : i ; /* row i of A is row i2 of C */ + w [CS_MAX (i2, j2)]++ ; /* column count of C */ + } + } + cs_cumsum (Cp, w, n) ; /* compute column pointers of C */ + for (j = 0 ; j < n ; j++) + { + j2 = pinv ? pinv [j] : j ; /* column j of A is column j2 of C */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (i > j) continue ; /* skip lower triangular part of A*/ + i2 = pinv ? pinv [i] : i ; /* row i of A is row i2 of C */ + Ci [q = w [CS_MAX (i2, j2)]++] = CS_MIN (i2, j2) ; + if (Cx) Cx [q] = (i2 <= j2) ? Ax [p] : CS_CONJ (Ax [p]) ; + } + } + return (cs_done (C, w, NULL, 1)) ; /* success; free workspace, return C */ +} diff --git a/vendor/cs/cs_tdfs.c b/vendor/cs/cs_tdfs.c new file mode 100644 index 0000000..f32077b --- /dev/null +++ b/vendor/cs/cs_tdfs.c @@ -0,0 +1,24 @@ +#include "cs.h" +/* depth-first search and postorder of a tree rooted at node j */ +CS_INT cs_tdfs (CS_INT j, CS_INT k, CS_INT *head, const CS_INT *next, CS_INT *post, CS_INT *stack) +{ + CS_INT i, p, top = 0 ; + if (!head || !next || !post || !stack) return (-1) ; /* check inputs */ + stack [0] = j ; /* place j on the stack */ + while (top >= 0) /* while (stack is not empty) */ + { + p = stack [top] ; /* p = top of stack */ + i = head [p] ; /* i = youngest child of p */ + if (i == -1) + { + top-- ; /* p has no unordered children left */ + post [k++] = p ; /* node p is the kth postordered node */ + } + else + { + head [p] = next [i] ; /* remove i from children of p */ + stack [++top] = i ; /* start dfs on child node i */ + } + } + return (k) ; +} diff --git a/vendor/cs/cs_transpose.c b/vendor/cs/cs_transpose.c new file mode 100644 index 0000000..15a6c00 --- /dev/null +++ b/vendor/cs/cs_transpose.c @@ -0,0 +1,25 @@ +#include "cs.h" +/* C = A' */ +cs *cs_transpose (const cs *A, CS_INT values) +{ + CS_INT p, q, j, *Cp, *Ci, n, m, *Ap, *Ai, *w ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (n, m, Ap [n], values && Ax, 0) ; /* allocate result */ + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (p = 0 ; p < Ap [n] ; p++) w [Ai [p]]++ ; /* row counts */ + cs_cumsum (Cp, w, m) ; /* row pointers */ + for (j = 0 ; j < n ; j++) + { + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + Ci [q = w [Ai [p]]++] = j ; /* place A(i,j) as entry C(j,i) */ + if (Cx) Cx [q] = (values > 0) ? CS_CONJ (Ax [p]) : Ax [p] ; + } + } + return (cs_done (C, w, NULL, 1)) ; /* success; free w and return C */ +} diff --git a/vendor/cs/cs_updown.c b/vendor/cs/cs_updown.c new file mode 100644 index 0000000..e49af83 --- /dev/null +++ b/vendor/cs/cs_updown.c @@ -0,0 +1,48 @@ +#include "cs.h" +/* sparse Cholesky update/downdate, L*L' + sigma*w*w' (sigma = +1 or -1) */ +CS_INT cs_updown (cs *L, CS_INT sigma, const cs *C, const CS_INT *parent) +{ + CS_INT n, p, f, j, *Lp, *Li, *Cp, *Ci ; + CS_ENTRY *Lx, *Cx, alpha, gamma, w1, w2, *w ; + double beta = 1, beta2 = 1, delta ; +#ifdef CS_COMPLEX + cs_complex_t phase ; +#endif + if (!CS_CSC (L) || !CS_CSC (C) || !parent) return (0) ; /* check inputs */ + Lp = L->p ; Li = L->i ; Lx = L->x ; n = L->n ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + if ((p = Cp [0]) >= Cp [1]) return (1) ; /* return if C empty */ + w = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + if (!w) return (0) ; /* out of memory */ + f = Ci [p] ; + for ( ; p < Cp [1] ; p++) f = CS_MIN (f, Ci [p]) ; /* f = min (find (C)) */ + for (j = f ; j != -1 ; j = parent [j]) w [j] = 0 ; /* clear workspace w */ + for (p = Cp [0] ; p < Cp [1] ; p++) w [Ci [p]] = Cx [p] ; /* w = C */ + for (j = f ; j != -1 ; j = parent [j]) /* walk path f up to root */ + { + p = Lp [j] ; + alpha = w [j] / Lx [p] ; /* alpha = w(j) / L(j,j) */ + beta2 = beta*beta + sigma*alpha*CS_CONJ(alpha) ; + if (beta2 <= 0) break ; /* not positive definite */ + beta2 = sqrt (beta2) ; + delta = (sigma > 0) ? (beta / beta2) : (beta2 / beta) ; + gamma = sigma * CS_CONJ(alpha) / (beta2 * beta) ; + Lx [p] = delta * Lx [p] + ((sigma > 0) ? (gamma * w [j]) : 0) ; + beta = beta2 ; +#ifdef CS_COMPLEX + phase = CS_ABS (Lx [p]) / Lx [p] ; /* phase = abs(L(j,j))/L(j,j)*/ + Lx [p] *= phase ; /* L(j,j) = L(j,j) * phase */ +#endif + for (p++ ; p < Lp [j+1] ; p++) + { + w1 = w [Li [p]] ; + w [Li [p]] = w2 = w1 - alpha * Lx [p] ; + Lx [p] = delta * Lx [p] + gamma * ((sigma > 0) ? w1 : w2) ; +#ifdef CS_COMPLEX + Lx [p] *= phase ; /* L(i,j) = L(i,j) * phase */ +#endif + } + } + cs_free (w) ; + return (beta2 > 0) ; +} diff --git a/vendor/cs/cs_usolve.c b/vendor/cs/cs_usolve.c new file mode 100644 index 0000000..6e89c83 --- /dev/null +++ b/vendor/cs/cs_usolve.c @@ -0,0 +1,18 @@ +#include "cs.h" +/* solve Ux=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_usolve (const cs *U, CS_ENTRY *x) +{ + CS_INT p, j, n, *Up, *Ui ; + CS_ENTRY *Ux ; + if (!CS_CSC (U) || !x) return (0) ; /* check inputs */ + n = U->n ; Up = U->p ; Ui = U->i ; Ux = U->x ; + for (j = n-1 ; j >= 0 ; j--) + { + x [j] /= Ux [Up [j+1]-1] ; + for (p = Up [j] ; p < Up [j+1]-1 ; p++) + { + x [Ui [p]] -= Ux [p] * x [j] ; + } + } + return (1) ; +} diff --git a/vendor/cs/cs_util.c b/vendor/cs/cs_util.c new file mode 100644 index 0000000..bb887ea --- /dev/null +++ b/vendor/cs/cs_util.c @@ -0,0 +1,120 @@ +#include "cs.h" +/* allocate a sparse matrix (triplet form or compressed-column form) */ +cs *cs_spalloc (CS_INT m, CS_INT n, CS_INT nzmax, CS_INT values, CS_INT triplet) +{ + cs *A = cs_calloc (1, sizeof (cs)) ; /* allocate the cs struct */ + if (!A) return (NULL) ; /* out of memory */ + A->m = m ; /* define dimensions and nzmax */ + A->n = n ; + A->nzmax = nzmax = CS_MAX (nzmax, 1) ; + A->nz = triplet ? 0 : -1 ; /* allocate triplet or comp.col */ + A->p = cs_malloc (triplet ? nzmax : n+1, sizeof (CS_INT)) ; + A->i = cs_malloc (nzmax, sizeof (CS_INT)) ; + A->x = values ? cs_malloc (nzmax, sizeof (CS_ENTRY)) : NULL ; + return ((!A->p || !A->i || (values && !A->x)) ? cs_spfree (A) : A) ; +} + +/* change the max # of entries sparse matrix */ +CS_INT cs_sprealloc (cs *A, CS_INT nzmax) +{ + CS_INT ok, oki, okj = 1, okx = 1 ; + if (!A) return (0) ; + if (nzmax <= 0) nzmax = (CS_CSC (A)) ? (A->p [A->n]) : A->nz ; + nzmax = CS_MAX (nzmax, 1) ; + A->i = cs_realloc (A->i, nzmax, sizeof (CS_INT), &oki) ; + if (CS_TRIPLET (A)) A->p = cs_realloc (A->p, nzmax, sizeof (CS_INT), &okj) ; + if (A->x) A->x = cs_realloc (A->x, nzmax, sizeof (CS_ENTRY), &okx) ; + ok = (oki && okj && okx) ; + if (ok) A->nzmax = nzmax ; + return (ok) ; +} + +/* free a sparse matrix */ +cs *cs_spfree (cs *A) +{ + if (!A) return (NULL) ; /* do nothing if A already NULL */ + cs_free (A->p) ; + cs_free (A->i) ; + cs_free (A->x) ; + return ((cs *) cs_free (A)) ; /* free the cs struct and return NULL */ +} + +/* free a numeric factorization */ +csn *cs_nfree (csn *N) +{ + if (!N) return (NULL) ; /* do nothing if N already NULL */ + cs_spfree (N->L) ; + cs_spfree (N->U) ; + cs_free (N->pinv) ; + cs_free (N->B) ; + return ((csn *) cs_free (N)) ; /* free the csn struct and return NULL */ +} + +/* free a symbolic factorization */ +css *cs_sfree (css *S) +{ + if (!S) return (NULL) ; /* do nothing if S already NULL */ + cs_free (S->pinv) ; + cs_free (S->q) ; + cs_free (S->parent) ; + cs_free (S->cp) ; + cs_free (S->leftmost) ; + return ((css *) cs_free (S)) ; /* free the css struct and return NULL */ +} + +/* allocate a cs_dmperm or cs_scc result */ +csd *cs_dalloc (CS_INT m, CS_INT n) +{ + csd *D ; + D = cs_calloc (1, sizeof (csd)) ; + if (!D) return (NULL) ; + D->p = cs_malloc (m, sizeof (CS_INT)) ; + D->r = cs_malloc (m+6, sizeof (CS_INT)) ; + D->q = cs_malloc (n, sizeof (CS_INT)) ; + D->s = cs_malloc (n+6, sizeof (CS_INT)) ; + return ((!D->p || !D->r || !D->q || !D->s) ? cs_dfree (D) : D) ; +} + +/* free a cs_dmperm or cs_scc result */ +csd *cs_dfree (csd *D) +{ + if (!D) return (NULL) ; /* do nothing if D already NULL */ + cs_free (D->p) ; + cs_free (D->q) ; + cs_free (D->r) ; + cs_free (D->s) ; + return ((csd *) cs_free (D)) ; /* free the csd struct and return NULL */ +} + +/* free workspace and return a sparse matrix result */ +cs *cs_done (cs *C, void *w, void *x, CS_INT ok) +{ + cs_free (w) ; /* free workspace */ + cs_free (x) ; + return (ok ? C : cs_spfree (C)) ; /* return result if OK, else free it */ +} + +/* free workspace and return CS_INT array result */ +CS_INT *cs_idone (CS_INT *p, cs *C, void *w, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + return (ok ? p : (CS_INT *) cs_free (p)) ; /* return result, or free it */ +} + +/* free workspace and return a numeric factorization (Cholesky, LU, or QR) */ +csn *cs_ndone (csn *N, cs *C, void *w, void *x, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + cs_free (x) ; + return (ok ? N : cs_nfree (N)) ; /* return result if OK, else free it */ +} + +/* free workspace and return a csd result */ +csd *cs_ddone (csd *D, cs *C, void *w, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + return (ok ? D : cs_dfree (D)) ; /* return result if OK, else free it */ +} diff --git a/vendor/cs/cs_utsolve.c b/vendor/cs/cs_utsolve.c new file mode 100644 index 0000000..d879fae --- /dev/null +++ b/vendor/cs/cs_utsolve.c @@ -0,0 +1,18 @@ +#include "cs.h" +/* solve U'x=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_utsolve (const cs *U, CS_ENTRY *x) +{ + CS_INT p, j, n, *Up, *Ui ; + CS_ENTRY *Ux ; + if (!CS_CSC (U) || !x) return (0) ; /* check inputs */ + n = U->n ; Up = U->p ; Ui = U->i ; Ux = U->x ; + for (j = 0 ; j < n ; j++) + { + for (p = Up [j] ; p < Up [j+1]-1 ; p++) + { + x [j] -= CS_CONJ (Ux [p]) * x [Ui [p]] ; + } + x [j] /= CS_CONJ (Ux [Up [j+1]-1]) ; + } + return (1) ; +} diff --git a/vendor/infomap/CITATION.cff b/vendor/infomap/CITATION.cff new file mode 100644 index 0000000..4acf57c --- /dev/null +++ b/vendor/infomap/CITATION.cff @@ -0,0 +1,16 @@ +cff-version: 2.8.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Edler" + given-names: "Daniel" + orcid: "https://orcid.org/0000-0001-5420-0591" +- family-names: "Holmgren" + given-names: "Anton" + orcid: "https://orcid.org/0000-0001-5859-4073" +- family-names: "Rosvall" + given-names: "Martin" + orcid: "https://orcid.org/0000-0002-7181-9940" +title: "The MapEquation software package" +version: 2.8.0 +date-released: 2024-06-20 +url: "https://mapequation.org" diff --git a/vendor/infomap/CMakeLists.txt b/vendor/infomap/CMakeLists.txt new file mode 100644 index 0000000..111c9e2 --- /dev/null +++ b/vendor/infomap/CMakeLists.txt @@ -0,0 +1,48 @@ +# Declare the files needed to compile our vendored Infomap copy +add_library(infomap_vendored + OBJECT + EXCLUDE_FROM_ALL + src/core/MetaMapEquation.cpp + src/core/MemMapEquation.cpp + src/core/InfoEdge.cpp + src/core/BiasedMapEquation.cpp + src/core/StateNetwork.cpp + src/core/iterators/InfomapIterator.cpp + src/core/InfomapBase.cpp + src/core/InfoNode.cpp + src/io/Network.cpp + src/io/ClusterMap.cpp + src/io/ProgramInterface.cpp + src/io/Output.cpp + src/io/Config.cpp + src/utils/Log.cpp + src/utils/FileURI.cpp + src/utils/FlowCalculator.cpp +) + +target_include_directories( + infomap_vendored + + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/ + + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${PROJECT_BINARY_DIR}/src # config.h +) + +if(BUILD_SHARED_LIBS) + set_property(TARGET infomap_vendored PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Since these are included as object files, they should call the +# function as is (without visibility specification) +target_compile_definitions(infomap_vendored PRIVATE IGRAPH_STATIC) + +# Infomap uses unsigned indices in OpenMP loops, which requires +# at least OpenMP 3.0. As of 2025, MSVC only supports OpenMP 2.0, +# thus this check is necessary. +if(IGRAPH_OPENMP_SUPPORT AND OpenMP_CXX_VERSION VERSION_GREATER_EQUAL 3) + target_link_libraries(infomap_vendored PRIVATE OpenMP::OpenMP_CXX) +endif() diff --git a/vendor/infomap/LICENSE_GPLv3.txt b/vendor/infomap/LICENSE_GPLv3.txt new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/vendor/infomap/LICENSE_GPLv3.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/vendor/infomap/README.rst b/vendor/infomap/README.rst new file mode 100644 index 0000000..b5819b0 --- /dev/null +++ b/vendor/infomap/README.rst @@ -0,0 +1,162 @@ +.. image:: https://github.com/mapequation/infomap/actions/workflows/build.yml/badge.svg + +Infomap +======= + +Infomap is a network clustering algorithm based on the `Map equation`_. + +For detailed documentation, see `mapequation.org/infomap`_. + +For a list of recent changes, see `CHANGELOG.md`_ in the source directory. + +.. _Map equation: https://www.mapequation.org/publications.html#Rosvall-Axelsson-Bergstrom-2009-Map-equation +.. _`mapequation.org/infomap`: https://www.mapequation.org/infomap +.. _`CHANGELOG.md`: https://github.com/mapequation/infomap/blob/master/CHANGELOG.md + +Getting started +--------------- + +Infomap can be installed either from `PyPI`_ using ``pip`` or by +compiling from source. + +An experimental Javascript version for browsers is available on `NPM`_. + +.. _PyPI: https://pypi.org/project/infomap/ + +Using pip +--------- + +A pre-compiled version is available for macOS users. + +Installing on other operating systems requires a +working ``gcc`` or ``clang`` compiler. + +To install, run:: + + pip install infomap + + +To upgrade, run:: + + pip install --upgrade infomap + + +When the Python package is installed, an executable called +``infomap`` (with lowercase i) is available from any directory. + +To get started, read `Infomap Python API`_. + +.. _`Infomap Python API`: https://mapequation.github.io/infomap/python/ + +Using Docker +------------ + +There are currently two Docker images available on `Docker Hub`_. + +- ``mapequation/infomap`` +- ``mapequation/infomap:notebook`` based on ``jupyter/scipy-notebook`` + +The image ``mapequation/infomap`` can be started with + +.. code-block:: bash + + docker run -it --rm \ + -v `pwd`:/data \ + mapequation/infomap + [infomap arguments] + +You can also use the supplied `docker-compose.yml`_: + +.. code-block:: bash + + docker-compose run --rm infomap + +The image ``mapequation/infomap:notebook`` can be started with + +.. code-block:: bash + + docker run \ + -v `pwd`:/home/jovyan/work \ + -p 8888:8888 \ + mapequation/infomap:notebook \ + start.sh jupyter lab + +Or similarly, using docker-compose: + +.. code-block:: bash + + docker-compose up notebook + +.. _`Docker Hub`: https://hub.docker.com/r/mapequation/infomap +.. _`docker-compose.yml`: https://github.com/mapequation/infomap/blob/master/docker-compose.yml + +Compiling from source +--------------------- + +Installing Infomap from source requires a working ``gcc`` or ``clang`` compiler. + +To download and compile the newest version from `Github`_, clone the repository +by running + +.. code-block:: shell + + git clone git@github.com:mapequation/infomap.git + cd infomap + make + +This creates the binary ``Infomap``, run it using:: + + ./Infomap [options] network_data destination + +For a list of options, run:: + + ./Infomap --help + +Read `the documentation`_ to learn more about the different options. + +.. _Github: https://www.github.com/mapequation/infomap +.. _the documentation: https://www.mapequation.org/infomap + +Npm package +----------- + +An experimental Javascript web worker is available on `NPM`_. + +To install it, run + +.. code-block:: shell + + npm install @mapequation/infomap + +.. _NPM: https://www.npmjs.com/package/@mapequation/infomap + +Feedback +-------- + +If you have any questions, suggestions or issues regarding the software, +please add them to `GitHub issues`_. + +.. _Github issues: http://www.github.com/mapequation/infomap/issues + +Authors +------- + +Daniel Edler, Anton Holmgren, Martin Rosvall + +For contact information, see `mapequation.org/about.html`_. + +.. _`mapequation.org/about.html`: https://www.mapequation.org/about.html + +Terms of use +------------ + +Infomap is released under a dual licence. + +To give everyone maximum freedom to make use of Infomap +and derivative works, we make the code open source under +the GNU General Public License version 3 or any +later version (see `LICENSE_GPLv3.txt`_). + +For a non-copyleft license, please contact us. + +.. _LICENSE_GPLv3.txt: https://github.com/mapequation/infomap/blob/master/LICENSE_GPLv3.txt diff --git a/vendor/infomap/src/Infomap.h b/vendor/infomap/src/Infomap.h new file mode 100644 index 0000000..9ebcd6f --- /dev/null +++ b/vendor/infomap/src/Infomap.h @@ -0,0 +1,103 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_H_ +#define INFOMAP_H_ + +#include "core/InfomapBase.h" +#include "io/Config.h" + +#include +#include +#include + +namespace infomap { + +// Wrapper class for the Python API +struct InfomapWrapper : public InfomapBase { +public: + InfomapWrapper() : InfomapBase() { } + InfomapWrapper(const std::string& flags) : InfomapBase(flags) { } + InfomapWrapper(const Config& conf) : InfomapBase(conf) { } + virtual ~InfomapWrapper() = default; + + // =================================================== + // Wrapper methods + // =================================================== + + void readInputData(std::string filename = "", bool accumulate = true) { m_network.readInputData(std::move(filename), accumulate); } + + void addNode(unsigned int id) { m_network.addNode(id); } + void addNode(unsigned int id, std::string name) { m_network.addNode(id, std::move(name)); } + void addNode(unsigned int id, double weight) { m_network.addNode(id, weight); } + void addNode(unsigned int id, std::string name, double weight) { m_network.addNode(id, std::move(name), weight); } + + void addName(unsigned int id, const std::string& name) { m_network.addName(id, name); } + std::string getName(unsigned int id) const + { + auto& names = m_network.names(); + auto it = names.find(id); + return it != names.end() ? it->second : ""; + } + + const std::map& getNames() const { return m_network.names(); } + + void addPhysicalNode(unsigned int id, const std::string& name = "") { m_network.addPhysicalNode(id, name); } + void addStateNode(unsigned int id, unsigned int physId) { m_network.addStateNode(id, physId); } + + void addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0) { m_network.addLink(sourceId, targetId, weight); } + void addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight) { m_network.addLink(sourceId, targetId, weight); } + void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight = 1.0) { m_network.addMultilayerLink(layer1, n1, layer2, n2, weight); } + void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight) { m_network.addMultilayerIntraLink(layer, n1, n2, weight); } + void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight) { m_network.addMultilayerInterLink(layer1, n, layer2, interWeight); } + + void setBipartiteStartId(unsigned int startId) { m_network.setBipartiteStartId(startId); } + + std::map, double> getLinks(bool flow) const + { + std::map, double> links; + + for (const auto& node : m_network.nodeLinkMap()) { + const auto sourceId = node.first.id; + + for (const auto& link : node.second) { + const auto targetId = link.first.id; + links[{ sourceId, targetId }] = flow ? link.second.flow : link.second.weight; + } + } + + return links; + } + + std::map getModules(int level = 1, bool states = false) + { + if (haveMemory() && !states) { + throw std::runtime_error("Cannot get modules on higher-order network without states."); + } + std::map modules; + for (auto it = iterTree(level); !it.isEnd(); ++it) { + auto& node = *it; + if (node.isLeaf()) { + modules[states ? node.stateId : node.physicalId] = it.moduleId(); + } + } + return modules; + } + + using InfomapBase::codelength; + using InfomapBase::getEntropyRate; + using InfomapBase::getMultilevelModules; + using InfomapBase::iterLeafNodes; + using InfomapBase::iterTree; + using InfomapBase::run; +}; + +} // namespace infomap + +#endif // INFOMAP_H_ diff --git a/vendor/infomap/src/core/BiasedMapEquation.cpp b/vendor/infomap/src/core/BiasedMapEquation.cpp new file mode 100644 index 0000000..8ae9ad6 --- /dev/null +++ b/vendor/infomap/src/core/BiasedMapEquation.cpp @@ -0,0 +1,240 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "BiasedMapEquation.h" +#include "FlowData.h" +#include "InfoNode.h" + +#include +#include +#include +#include "StateNetwork.h" + +namespace infomap { + +double BiasedMapEquation::s_totalDegree = 1; +unsigned int BiasedMapEquation::s_numNodes = 0; + +void BiasedMapEquation::setNetworkProperties(const StateNetwork& network) +{ + s_totalDegree = network.sumWeightedDegree(); + // Negative entropy bias is based on discrete counts, if average weight is below 1, use unweighted total degree + if (s_totalDegree < network.sumDegree()) { + s_totalDegree = network.sumDegree(); + } + s_numNodes = network.numNodes(); +} + +double BiasedMapEquation::getIndexCodelength() const +{ + return indexCodelength + indexEntropyBiasCorrection; +} + +double BiasedMapEquation::getModuleCodelength() const +{ + return moduleCodelength + biasedCost + moduleEntropyBiasCorrection; +} + +double BiasedMapEquation::getCodelength() const +{ + return codelength + biasedCost + getEntropyBiasCorrection(); +} + +double BiasedMapEquation::getEntropyBiasCorrection() const +{ + return indexEntropyBiasCorrection + moduleEntropyBiasCorrection; +} + +// =================================================== +// IO +// =================================================== + +std::ostream& BiasedMapEquation::print(std::ostream& out) const +{ + out << indexCodelength << " + " << moduleCodelength; + if (preferredNumModules != 0) { + out << " + " << biasedCost; + } + if (useEntropyBiasCorrection) { + out << " + " << getEntropyBiasCorrection(); + } + out << " = " << io::toPrecision(getCodelength()); + return out; +} + +std::ostream& operator<<(std::ostream& out, const BiasedMapEquation& mapEq) +{ + return mapEq.print(out); +} + +// =================================================== +// Init +// =================================================== + +void BiasedMapEquation::init(const Config& config) +{ + Log(3) << "BiasedMapEquation::init()...\n"; + preferredNumModules = config.preferredNumberOfModules; + useEntropyBiasCorrection = config.entropyBiasCorrection; + entropyBiasCorrectionMultiplier = config.entropyBiasCorrectionMultiplier; +} + +void BiasedMapEquation::initNetwork(InfoNode& root) +{ + Log(3) << "BiasedMapEquation::initNetwork()...\n"; + Base::initNetwork(root); +} + +void BiasedMapEquation::initPartition(std::vector& nodes) +{ + calculateCodelength(nodes); +} + +// =================================================== +// Codelength +// =================================================== + +double BiasedMapEquation::calcNumModuleCost(unsigned int numModules) const +{ + if (preferredNumModules == 0) return 0; + int deltaNumModules = numModules - preferredNumModules; + return 1 * std::abs(deltaNumModules); +} + +double BiasedMapEquation::calcIndexEntropyBiasCorrection(unsigned int numModules) const +{ + return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1) / (2 * s_totalDegree) : 0; +} + +double BiasedMapEquation::calcModuleEntropyBiasCorrection() const +{ + return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * s_numNodes / (2 * s_totalDegree) : 0; +} + +double BiasedMapEquation::calcEntropyBiasCorrection(unsigned int numModules) const +{ + return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1 + s_numNodes) / (2 * s_totalDegree) : 0; +} + +void BiasedMapEquation::calculateCodelength(std::vector& nodes) +{ + calculateCodelengthTerms(nodes); + + calculateCodelengthFromCodelengthTerms(); + + currentNumModules = nodes.size(); + + biasedCost = calcNumModuleCost(currentNumModules); + + indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules); + moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection(); +} + +double BiasedMapEquation::calcCodelength(const InfoNode& parent) const +{ + return parent.isLeafModule() + ? calcCodelengthOnModuleOfLeafNodes(parent) + : calcCodelengthOnModuleOfModules(parent); +} + +double BiasedMapEquation::calcCodelengthOnModuleOfModules(const InfoNode& parent) const +{ + double L = Base::calcCodelengthOnModuleOfModules(parent); + if (!useEntropyBiasCorrection) + return L; + + return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree); +} + +double BiasedMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const +{ + double L = Base::calcCodelength(parent); + if (!useEntropyBiasCorrection) + return L; + + return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree); +} + +int BiasedMapEquation::getDeltaNumModulesIfMoving(unsigned int oldModule, + unsigned int newModule, + std::vector& moduleMembers) +{ + bool removeOld = moduleMembers[oldModule] == 1; + bool createNew = moduleMembers[newModule] == 0; + int deltaNumModules = removeOld && !createNew ? -1 : (!removeOld && createNew ? 1 : 0); + return deltaNumModules; +} + +double BiasedMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + + if (preferredNumModules == 0) + return deltaL; + + int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers); + + double deltaBiasedCost = calcNumModuleCost(currentNumModules + deltaNumModules) - biasedCost; + + double deltaEntropyBiasCorrection = calcEntropyBiasCorrection(currentNumModules + deltaNumModules) - getEntropyBiasCorrection(); + + return deltaL + deltaBiasedCost + deltaEntropyBiasCorrection; +} + +// =================================================== +// Consolidation +// =================================================== + +void BiasedMapEquation::updateCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + + if (preferredNumModules == 0) + return; + + int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers); + + currentNumModules += deltaNumModules; + biasedCost = calcNumModuleCost(currentNumModules); + indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules); + moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection(); +} + +void BiasedMapEquation::consolidateModules(std::vector& modules) +{ + unsigned int numModules = 0; + for (auto& module : modules) { + if (module == nullptr) + continue; + ++numModules; + } + currentNumModules = numModules; +} + +#if 0 +// =================================================== +// Debug +// =================================================== + +void BiasedMapEquation::printDebug() const +{ + std::cout << "BiasedMapEquation\n"; + Base::printDebug(); +} +#endif + +} // namespace infomap diff --git a/vendor/infomap/src/core/BiasedMapEquation.h b/vendor/infomap/src/core/BiasedMapEquation.h new file mode 100644 index 0000000..6f043e8 --- /dev/null +++ b/vendor/infomap/src/core/BiasedMapEquation.h @@ -0,0 +1,180 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef BIASED_MAPEQUATION_H_ +#define BIASED_MAPEQUATION_H_ + +#include "MapEquation.h" +#include "FlowData.h" +#include "../utils/Log.h" +#include +#include +#include +#include + +namespace infomap { + +class InfoNode; +class StateNetwork; + +class BiasedMapEquation : private MapEquation<> { + using Base = MapEquation<>; + +public: + using FlowDataType = FlowData; + using DeltaFlowDataType = DeltaFlow; + + // =================================================== + // Getters + // =================================================== + + double getIndexCodelength() const override; + + double getModuleCodelength() const override; + + double getCodelength() const override; + + double getEntropyBiasCorrection() const; + + // =================================================== + // IO + // =================================================== + + std::ostream& print(std::ostream& out) const override; + + friend std::ostream& operator<<(std::ostream&, const BiasedMapEquation&); + + // =================================================== + // Init + // =================================================== + + void init(const Config& config) override; + + void initTree(InfoNode& /*root*/) override { } + + void initNetwork(InfoNode& root) override; + + using Base::initSuperNetwork; + + using Base::initSubNetwork; + + void initPartition(std::vector& nodes) override; + + // =================================================== + // Codelength + // =================================================== + + double calcCodelength(const InfoNode& parent) const override; + + using Base::addMemoryContributions; + + double getDeltaCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + // =================================================== + // Consolidation + // =================================================== + + void updateCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + void consolidateModules(std::vector& modules) override; + +#if 0 + // =================================================== + // Debug + // =================================================== + + void printDebug() const override; +#endif + +private: + // =================================================== + // Private member functions + // =================================================== + double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override; + double calcCodelengthOnModuleOfModules(const InfoNode& parent) const override; + + static int getDeltaNumModulesIfMoving(unsigned int oldModule, unsigned int newModule, std::vector& moduleMembers); + + // =================================================== + // Init + // =================================================== + + // =================================================== + // Codelength + // =================================================== + + void calculateCodelength(std::vector& nodes) override; + + using Base::calculateCodelengthTerms; + + using Base::calculateCodelengthFromCodelengthTerms; + + double calcNumModuleCost(unsigned int numModules) const; + + double calcIndexEntropyBiasCorrection(unsigned int numModules) const; + double calcModuleEntropyBiasCorrection() const; + double calcEntropyBiasCorrection(unsigned int numModules) const; + + // =================================================== + // Consolidation + // =================================================== + +public: + // =================================================== + // Public member variables + // =================================================== + + using Base::codelength; + using Base::indexCodelength; + using Base::moduleCodelength; + +private: + // =================================================== + // Private member variables + // =================================================== + + using Base::enter_log_enter; + using Base::enterFlow; + using Base::enterFlow_log_enterFlow; + using Base::exit_log_exit; + using Base::flow_log_flow; // node.(flow + exitFlow) + using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same + + // For hierarchical + using Base::exitNetworkFlow; + using Base::exitNetworkFlow_log_exitNetworkFlow; + + // For biased + unsigned int preferredNumModules = 0; + unsigned int currentNumModules = 0; + double biasedCost = 0.0; + + // For entropy bias correction + bool useEntropyBiasCorrection = false; + double entropyBiasCorrectionMultiplier = 1; + double indexEntropyBiasCorrection = 0; + double moduleEntropyBiasCorrection = 0; + static double s_totalDegree; + static unsigned int s_numNodes; + +public: + static void setNetworkProperties(const StateNetwork& network); +}; + +} // namespace infomap + +#endif // BIASED_MAPEQUATION_H_ diff --git a/vendor/infomap/src/core/FlowData.h b/vendor/infomap/src/core/FlowData.h new file mode 100644 index 0000000..8b70d9b --- /dev/null +++ b/vendor/infomap/src/core/FlowData.h @@ -0,0 +1,165 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef FLOWDATA_H_ +#define FLOWDATA_H_ + +#include +#include + +namespace infomap { + +struct FlowData { + double flow = 0.0; + double enterFlow = 0.0; + double exitFlow = 0.0; + double teleportFlow = 0.0; + double teleportSourceFlow = 0.0; + double teleportWeight = 0.0; + double danglingFlow = 0.0; + + FlowData() = default; + FlowData(double flow) : flow(flow) { } + + FlowData& operator+=(const FlowData& other) + { + flow += other.flow; + enterFlow += other.enterFlow; + exitFlow += other.exitFlow; + teleportFlow += other.teleportFlow; + teleportSourceFlow += other.teleportSourceFlow; + teleportWeight += other.teleportWeight; + danglingFlow += other.danglingFlow; + return *this; + } + + FlowData& operator-=(const FlowData& other) + { + flow -= other.flow; + enterFlow -= other.enterFlow; + exitFlow -= other.exitFlow; + teleportFlow -= other.teleportFlow; + teleportSourceFlow -= other.teleportSourceFlow; + teleportWeight -= other.teleportWeight; + danglingFlow -= other.danglingFlow; + return *this; + } + + friend std::ostream& operator<<(std::ostream& out, const FlowData& data) + { + return out << "flow: " << data.flow << ", enter: " << data.enterFlow << ", exit: " << data.exitFlow + << ", teleWeight: " << data.teleportWeight << ", danglingFlow: " << data.danglingFlow + << ", teleFlow: " << data.teleportFlow; + } +}; + +struct DeltaFlow { + unsigned int module = 0; + double deltaExit = 0.0; + double deltaEnter = 0.0; + unsigned int count = 0; + + explicit DeltaFlow(unsigned int module, double deltaExit, double deltaEnter) + : module(module), + deltaExit(deltaExit), + deltaEnter(deltaEnter) { } + + DeltaFlow() = default; + DeltaFlow(const DeltaFlow&) = default; + DeltaFlow(DeltaFlow&&) = default; + DeltaFlow& operator=(const DeltaFlow&) = default; + DeltaFlow& operator=(DeltaFlow&&) = default; + virtual ~DeltaFlow() = default; + + DeltaFlow& operator+=(const DeltaFlow& other) + { + module = other.module; + deltaExit += other.deltaExit; + deltaEnter += other.deltaEnter; + ++count; + return *this; + } + + virtual void reset() + { + module = 0; + deltaExit = 0.0; + deltaEnter = 0.0; + count = 0; + } + + friend void swap(DeltaFlow& first, DeltaFlow& second) noexcept + { + std::swap(first.module, second.module); + std::swap(first.deltaExit, second.deltaExit); + std::swap(first.deltaEnter, second.deltaEnter); + std::swap(first.count, second.count); + } + + friend std::ostream& operator<<(std::ostream& out, const DeltaFlow& data) + { + return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count; + } +}; + +struct MemDeltaFlow : DeltaFlow { + double sumDeltaPlogpPhysFlow = 0.0; + double sumPlogpPhysFlow = 0.0; + + MemDeltaFlow() = default; + + explicit MemDeltaFlow(unsigned int module, double deltaExit, double deltaEnter, double sumDeltaPlogpPhysFlow = 0.0, double sumPlogpPhysFlow = 0.0) + : DeltaFlow(module, deltaExit, deltaEnter), + sumDeltaPlogpPhysFlow(sumDeltaPlogpPhysFlow), + sumPlogpPhysFlow(sumPlogpPhysFlow) { } + + MemDeltaFlow& operator+=(const MemDeltaFlow& other) + { + DeltaFlow::operator+=(other); + sumDeltaPlogpPhysFlow += other.sumDeltaPlogpPhysFlow; + sumPlogpPhysFlow += other.sumPlogpPhysFlow; + return *this; + } + + void reset() override + { + DeltaFlow::reset(); + sumDeltaPlogpPhysFlow = 0.0; + sumPlogpPhysFlow = 0.0; + } + + friend void swap(MemDeltaFlow& first, MemDeltaFlow& second) noexcept + { + swap(static_cast(first), static_cast(second)); + std::swap(first.sumDeltaPlogpPhysFlow, second.sumDeltaPlogpPhysFlow); + std::swap(first.sumPlogpPhysFlow, second.sumPlogpPhysFlow); + } + + friend std::ostream& operator<<(std::ostream& out, const MemDeltaFlow& data) + { + return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count << ", sumDeltaPlogpPhysFlow: " << data.sumDeltaPlogpPhysFlow << ", sumPlogpPhysFlow: " << data.sumPlogpPhysFlow; + } +}; + +struct PhysData { + unsigned int physNodeIndex; + double sumFlowFromM2Node; // The amount of flow from the memory node in this physical node + + explicit PhysData(unsigned int physNodeIndex, double sumFlowFromM2Node = 0.0) + : physNodeIndex(physNodeIndex), sumFlowFromM2Node(sumFlowFromM2Node) { } + + friend std::ostream& operator<<(std::ostream& out, const PhysData& data) + { + return out << "physNodeIndex: " << data.physNodeIndex << ", sumFlowFromM2Node: " << data.sumFlowFromM2Node; + } +}; + +} // namespace infomap + +#endif // FLOWDATA_H_ diff --git a/vendor/infomap/src/core/InfoEdge.cpp b/vendor/infomap/src/core/InfoEdge.cpp new file mode 100644 index 0000000..6bcb703 --- /dev/null +++ b/vendor/infomap/src/core/InfoEdge.cpp @@ -0,0 +1,26 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "InfoEdge.h" +#include "InfoNode.h" + +namespace infomap { + +InfoNode& infomap::InfoEdge::other(InfoNode& node) const +{ + return (node == *source) ? *target : *source; +} + +std::ostream& operator<<(std::ostream& out, const InfoEdge& edge) +{ + return out << "(" << *edge.source << ") -> (" << *edge.target << "), flow: " + << edge.data.flow; +} + +} // namespace infomap diff --git a/vendor/infomap/src/core/InfoEdge.h b/vendor/infomap/src/core/InfoEdge.h new file mode 100644 index 0000000..e2d245f --- /dev/null +++ b/vendor/infomap/src/core/InfoEdge.h @@ -0,0 +1,47 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOEDGE_H_ +#define INFOEDGE_H_ + +#include + +namespace infomap { + +struct EdgeData { +public: + EdgeData() = default; + + EdgeData(double weight, double flow) : weight(weight), flow(flow) { } + + double weight; + double flow; +}; + +class InfoNode; + +class InfoEdge { +public: + InfoEdge(InfoNode& source, InfoNode& target, double weight, double flow) + : data(weight, flow), + source(&source), + target(&target) { } + + InfoNode& other(InfoNode& node) const; + + friend std::ostream& operator<<(std::ostream& out, const InfoEdge& edge); + + EdgeData data; + InfoNode* source; + InfoNode* target; +}; + +} // namespace infomap + +#endif // INFOEDGE_H_ diff --git a/vendor/infomap/src/core/InfoNode.cpp b/vendor/infomap/src/core/InfoNode.cpp new file mode 100644 index 0000000..b7a7b0a --- /dev/null +++ b/vendor/infomap/src/core/InfoNode.cpp @@ -0,0 +1,405 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "InfoNode.h" +#include "InfomapBase.h" +#include + +namespace infomap { + +InfoNode::~InfoNode() noexcept +{ + if (m_infomap != nullptr) { + delete m_infomap; + } + + deleteChildren(); + + if (next != nullptr) + next->previous = previous; + if (previous != nullptr) + previous->next = next; + if (parent != nullptr) { + if (parent->firstChild == this) + parent->firstChild = next; + if (parent->lastChild == this) + parent->lastChild = previous; + } + + // Delete outgoing edges. + // TODO: Renders ingoing edges invalid. Assume or assert that all nodes on the same level are deleted? + for (edge_iterator outEdgeIt(begin_outEdge()); + outEdgeIt != end_outEdge(); + ++outEdgeIt) { + delete *outEdgeIt; + } +} + +InfomapBase& InfoNode::setInfomap(InfomapBase* infomap) +{ + disposeInfomap(); + m_infomap = infomap; + if (infomap == nullptr) + throw std::logic_error("InfoNode::setInfomap(...) called with null infomap"); + return *m_infomap; +} + +InfomapBase& InfoNode::getInfomap() +{ + if (m_infomap == nullptr) + throw std::logic_error("InfoNode::getInfomap() called but infomap is null"); + return *m_infomap; +} + +const InfomapBase& InfoNode::getInfomap() const +{ + if (m_infomap == nullptr) + throw std::logic_error("InfoNode::getInfomap() called but infomap is null"); + return *m_infomap; +} + +InfoNode* InfoNode::getInfomapRoot() noexcept +{ + return m_infomap != nullptr ? &m_infomap->root() : nullptr; +} + +InfoNode const* InfoNode::getInfomapRoot() const noexcept +{ + return m_infomap != nullptr ? &m_infomap->root() : nullptr; +} + +bool InfoNode::disposeInfomap() noexcept +{ + if (m_infomap != nullptr) { + delete m_infomap; + m_infomap = nullptr; + return true; + } + return false; +} + +unsigned int InfoNode::depth() const noexcept +{ + unsigned int depth = 0; + InfoNode* n = parent; + while (n != nullptr) { + ++depth; + n = n->parent; + } + return depth; +} + +unsigned int InfoNode::firstDepthBelow() const noexcept +{ + unsigned int depthBelow = 0; + InfoNode* child = firstChild; + while (child != nullptr) { + ++depthBelow; + child = child->firstChild; + } + return depthBelow; +} + +unsigned int InfoNode::childIndex() const noexcept +{ + unsigned int childIndex = 0; + const InfoNode* n(this); + while (n->previous) { + n = n->previous; + ++childIndex; + } + return childIndex; +} + +std::vector InfoNode::calculatePath() const noexcept +{ + const InfoNode* current = this; + std::vector path; + while (current->parent != nullptr) { + path.push_back(current->childIndex() + 1); + current = current->parent; + if (current->owner != nullptr) { + current = current->owner; + } + } + std::reverse(path.begin(), path.end()); + return path; +} + +unsigned int InfoNode::infomapChildDegree() const noexcept +{ + return m_infomap == nullptr ? childDegree() : m_infomap->root().childDegree(); +} + +void InfoNode::addChild(InfoNode* child) noexcept +{ + if (firstChild == nullptr) { + child->previous = nullptr; + firstChild = child; + } else { + child->previous = lastChild; + lastChild->next = child; + } + lastChild = child; + child->next = nullptr; + child->parent = this; + ++m_childDegree; +} + +void InfoNode::releaseChildren() noexcept +{ + firstChild = nullptr; + lastChild = nullptr; + m_childDegree = 0; +} + +InfoNode& InfoNode::replaceChildrenWithOneNode() +{ + if (childDegree() == 1) + return *firstChild; + if (firstChild == nullptr) + throw std::logic_error("replaceChildrenWithOneNode called on a node without any children."); + if (firstChild->firstChild == nullptr) + throw std::logic_error("replaceChildrenWithOneNode called on a node without any grandchildren."); + auto* middleNode = new InfoNode(); + InfoNode::child_iterator nodeIt = begin_child(); + unsigned int numOriginalChildrenLeft = m_childDegree; + auto d0 = m_childDegree; + do { + InfoNode* n = nodeIt.current(); + ++nodeIt; + middleNode->addChild(n); + } while (--numOriginalChildrenLeft != 0); + + releaseChildren(); + addChild(middleNode); + auto d1 = middleNode->replaceChildrenWithGrandChildren(); + if (d1 != d0) + throw std::logic_error("replaceChildrenWithOneNode replaced different number of children as having before"); + return *middleNode; +} + +unsigned int InfoNode::replaceChildrenWithGrandChildren() noexcept +{ + if (firstChild == nullptr) + return 0; + InfoNode::child_iterator nodeIt = begin_child(); + unsigned int numOriginalChildrenLeft = m_childDegree; + unsigned int numChildrenReplaced = 0; + do { + InfoNode* n = nodeIt.current(); + ++nodeIt; + numChildrenReplaced += n->replaceWithChildren(); + } while (--numOriginalChildrenLeft != 0); + return numChildrenReplaced; +} + +unsigned int InfoNode::replaceWithChildren() noexcept +{ + if (isLeaf() || isRoot()) + return 0; + + // Re-parent children + unsigned int deltaChildDegree = 0; + InfoNode* child = firstChild; + do { + child->parent = parent; + child = child->next; + ++deltaChildDegree; + } while (child != nullptr); + parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted + + firstChild->previous = previous; + lastChild->next = next; + + if (parent->firstChild == this) { + parent->firstChild = firstChild; + } else { + previous->next = firstChild; + } + + if (parent->lastChild == this) { + parent->lastChild = lastChild; + } else { + next->previous = lastChild; + } + + // Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected. + firstChild = nullptr; + lastChild = nullptr; + next = nullptr; + previous = nullptr; + parent = nullptr; + delete this; + return 1; +} + +void InfoNode::replaceChildrenWithGrandChildrenDebug() noexcept +{ + if (firstChild == nullptr) + return; + InfoNode::child_iterator nodeIt = begin_child(); + unsigned int numOriginalChildrenLeft = m_childDegree; + do { + InfoNode* n = nodeIt.current(); + ++nodeIt; + n->replaceWithChildrenDebug(); + } while (--numOriginalChildrenLeft != 0); +} + +void InfoNode::replaceWithChildrenDebug() noexcept +{ + if (isLeaf() || isRoot()) + return; + + // Re-parent children + unsigned int deltaChildDegree = 0; + InfoNode* child = firstChild; + do { + child->parent = parent; + child = child->next; + ++deltaChildDegree; + } while (child != nullptr); + parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted + + if (parent->firstChild == this) { + parent->firstChild = firstChild; + } else { + previous->next = firstChild; + firstChild->previous = previous; + } + + if (parent->lastChild == this) { + parent->lastChild = lastChild; + } else { + next->previous = lastChild; + lastChild->next = next; + } + + // Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected. + firstChild = nullptr; + lastChild = nullptr; + next = nullptr; + previous = nullptr; + parent = nullptr; + + delete this; +} + +void InfoNode::remove(bool removeChildren) noexcept +{ + firstChild = removeChildren ? nullptr : firstChild; + delete this; +} + +void InfoNode::deleteChildren() noexcept +{ + if (firstChild == nullptr) + return; + + child_iterator nodeIt = begin_child(); + do { + InfoNode* n = nodeIt.current(); + ++nodeIt; + delete n; + } while (nodeIt.current() != nullptr); + + firstChild = nullptr; + lastChild = nullptr; + m_childDegree = 0; +} + +void InfoNode::calcChildDegree() noexcept +{ + m_childrenChanged = false; + if (firstChild == nullptr) + m_childDegree = 0; + else if (firstChild == lastChild) + m_childDegree = 1; + else { + m_childDegree = 0; + for (auto& child : children()) { + (void)child; + ++m_childDegree; + } + } +} + +void InfoNode::setChildDegree(unsigned int value) noexcept +{ + m_childDegree = value; + m_childrenChanged = false; +} + +void InfoNode::initClean() noexcept +{ + releaseChildren(); + previous = next = parent = nullptr; + + physicalNodes.clear(); +} + +void InfoNode::sortChildrenOnFlow(bool recurse) noexcept +{ + if (childDegree() == 0) + return; + std::vector nodes(childDegree()); + double lastFlow = 1.0; + bool isSorted = true; + unsigned int i = 0; + for (InfoNode& child : children()) { + if (child.data.flow > lastFlow) { + isSorted = false; + } + nodes[i] = &child; + lastFlow = child.data.flow; + ++i; + } + if (!isSorted) { + std::sort(nodes.begin(), nodes.end(), [](const InfoNode* a, const InfoNode* b) { + return b->data.flow < a->data.flow; + }); + releaseChildren(); + for (auto node : nodes) { + addChild(node); + } + } + if (recurse) { + for (InfoNode& child : children()) { + auto newRoot = child.getInfomapRoot(); + InfoNode& node = newRoot ? *newRoot : child; + node.sortChildrenOnFlow(recurse); + } + } +} + +unsigned int InfoNode::collapseChildren() noexcept +{ + std::swap(collapsedFirstChild, firstChild); + std::swap(collapsedLastChild, lastChild); + unsigned int numCollapsedChildren = childDegree(); + releaseChildren(); + return numCollapsedChildren; +} + +unsigned int InfoNode::expandChildren() +{ + bool haveCollapsedChildren = collapsedFirstChild != nullptr; + if (haveCollapsedChildren) { + if (firstChild != nullptr || lastChild != nullptr) + throw std::logic_error("Expand collapsed children called on a node that already has children."); + std::swap(collapsedFirstChild, firstChild); + std::swap(collapsedLastChild, lastChild); + calcChildDegree(); + return childDegree(); + } + return 0; +} + +} // namespace infomap diff --git a/vendor/infomap/src/core/InfoNode.h b/vendor/infomap/src/core/InfoNode.h new file mode 100644 index 0000000..5d2c5e8 --- /dev/null +++ b/vendor/infomap/src/core/InfoNode.h @@ -0,0 +1,374 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFONODE_H_ +#define INFONODE_H_ + +#include "FlowData.h" +#include "InfoEdge.h" +#include "iterators/infomapIterators.h" +#include "iterators/IterWrapper.h" +#include "../utils/MetaCollection.h" + +#include +#include +#include +#include +#include + +namespace infomap { + +class InfomapBase; + +class InfoNode { +public: + using child_iterator = ChildIterator; + using const_child_iterator = ChildIterator; + using infomap_child_iterator = InfomapChildIterator; + using const_infomap_child_iterator = InfomapChildIterator; + + using tree_iterator = TreeIterator; + using const_tree_iterator = TreeIterator; + + using leaf_node_iterator = LeafNodeIterator; + using const_leaf_node_iterator = LeafNodeIterator; + using leaf_module_iterator = LeafModuleIterator; + using const_leaf_module_iterator = LeafModuleIterator; + + using post_depth_first_iterator = DepthFirstIterator; + using const_post_depth_first_iterator = DepthFirstIterator; + + using edge_iterator = std::vector::iterator; + using const_edge_iterator = std::vector::const_iterator; + + using edge_iterator_wrapper = IterWrapper; + using const_edge_iterator_wrapper = IterWrapper; + + using infomap_iterator_wrapper = IterWrapper; + using const_infomap_iterator_wrapper = IterWrapper; + + using child_iterator_wrapper = IterWrapper; + using const_child_iterator_wrapper = IterWrapper; + + using infomap_child_iterator_wrapper = IterWrapper; + using const_infomap_child_iterator_wrapper = IterWrapper; + +public: + FlowData data; + unsigned int index = 0; // Temporary index used in finding best module + unsigned int stateId = 0; // Unique state node id for the leaf nodes + unsigned int physicalId = 0; // Physical id equals stateId for first order networks, otherwise can be non-unique + unsigned int layerId = 0; // Layer id for multilayer networks + std::vector metaData; // Categorical value for each meta data dimension + + InfoNode* owner = nullptr; // Infomap owner (if this is an Infomap root) + InfoNode* parent = nullptr; + InfoNode* previous = nullptr; // sibling + InfoNode* next = nullptr; // sibling + InfoNode* firstChild = nullptr; + InfoNode* lastChild = nullptr; + InfoNode* collapsedFirstChild = nullptr; + InfoNode* collapsedLastChild = nullptr; + double codelength = 0.0; // TODO: Better design for hierarchical stuff!? + bool dirty = false; + + std::vector physicalNodes; + MetaCollection metaCollection; // For modules + std::vector stateNodes; // For physically aggregated nodes + +private: + unsigned int m_childDegree = 0; + bool m_childrenChanged = false; + unsigned int m_numLeafMembers = 0; + + std::vector m_outEdges; + std::vector m_inEdges; + + InfomapBase* m_infomap = nullptr; + +public: + InfoNode(const FlowData& flowData) + : data(flowData) {}; + + // For first order nodes, physicalId equals stateId + InfoNode(const FlowData& flowData, unsigned int stateId) + : data(flowData), stateId(stateId), physicalId(stateId) {}; + + InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId) + : data(flowData), stateId(stateId), physicalId(physicalId) {}; + + InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId, unsigned int layerId) + : data(flowData), stateId(stateId), physicalId(physicalId), layerId(layerId) {}; + + InfoNode() = default; + + InfoNode(const InfoNode& other) + : data(other.data), + index(other.index), + stateId(other.stateId), + physicalId(other.physicalId), + layerId(other.layerId), + metaData(other.metaData), + parent(other.parent), + previous(other.previous), + next(other.next), + firstChild(other.firstChild), + lastChild(other.lastChild), + collapsedFirstChild(other.collapsedFirstChild), + collapsedLastChild(other.collapsedLastChild), + codelength(other.codelength), + dirty(other.dirty), + metaCollection(other.metaCollection), + m_childDegree(other.m_childDegree), + m_childrenChanged(other.m_childrenChanged), + m_numLeafMembers(other.m_numLeafMembers) { } + + ~InfoNode() noexcept; + + InfoNode& operator=(const InfoNode& other) + { + data = other.data; + index = other.index; + stateId = other.stateId; + physicalId = other.physicalId; + layerId = other.layerId; + metaData = other.metaData; + parent = other.parent; + previous = other.previous; + next = other.next; + firstChild = other.firstChild; + lastChild = other.lastChild; + collapsedFirstChild = other.collapsedFirstChild; + collapsedLastChild = other.collapsedLastChild; + codelength = other.codelength; + dirty = other.dirty; + metaCollection = other.metaCollection; + m_childDegree = other.m_childDegree; + m_childrenChanged = other.m_childrenChanged; + m_numLeafMembers = other.m_numLeafMembers; + return *this; + } + + // ---------------------------- Getters ---------------------------- + + unsigned int getMetaData(unsigned int dimension = 0) noexcept + { + if (dimension >= metaData.size()) { + return 0; + } + auto meta = metaData[dimension]; + return meta < 0 ? 0 : static_cast(meta); + } + + // ---------------------------- Infomap ---------------------------- + InfomapBase& getInfomap(); + + const InfomapBase& getInfomap() const; + + InfomapBase& setInfomap(InfomapBase*); + + InfoNode* getInfomapRoot() noexcept; + + InfoNode const* getInfomapRoot() const noexcept; + + /** + * Dispose the Infomap instance if it exists + * @return true if an existing Infomap instance was deleted + */ + bool disposeInfomap() noexcept; + + /** + * Number of physical nodes in memory nodes + */ + unsigned int numPhysicalNodes() const noexcept { return physicalNodes.size(); } + + // ---------------------------- Tree iterators ---------------------------- + + // Default iteration on children + child_iterator begin() noexcept { return { this }; } + + child_iterator end() noexcept { return { nullptr }; } + + const_child_iterator begin() const noexcept { return { this }; } + + const_child_iterator end() const noexcept { return { nullptr }; } + + child_iterator begin_child() noexcept { return { this }; } + + child_iterator end_child() noexcept { return { nullptr }; } + + const_child_iterator begin_child() const noexcept { return { this }; } + + const_child_iterator end_child() const noexcept { return { nullptr }; } + + child_iterator_wrapper children() noexcept { return { { this }, { nullptr } }; } + + const_child_iterator_wrapper children() const noexcept { return { { this }, { nullptr } }; } + + infomap_child_iterator_wrapper infomap_children() noexcept { return { { this }, { nullptr } }; } + + const_infomap_child_iterator_wrapper infomap_children() const noexcept { return { { this }, { nullptr } }; } + + post_depth_first_iterator begin_post_depth_first() noexcept { return { this }; } + + leaf_node_iterator begin_leaf_nodes() noexcept { return { this }; } + + leaf_module_iterator begin_leaf_modules() noexcept { return { this }; } + + tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits::max()) noexcept { return { this, static_cast(maxClusterLevel) }; } + + tree_iterator end_tree() noexcept { return { nullptr }; } + + const_tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits::max()) const noexcept { return { this, static_cast(maxClusterLevel) }; } + + const_tree_iterator end_tree() const noexcept { return { nullptr }; } + + infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits::max()) noexcept { return { { this, static_cast(maxClusterLevel) }, { nullptr } }; } + + const_infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits::max()) const noexcept { return { { this, static_cast(maxClusterLevel) }, { nullptr } }; } + + // ---------------------------- Graph iterators ---------------------------- + + edge_iterator begin_outEdge() noexcept { return m_outEdges.begin(); } + + edge_iterator end_outEdge() noexcept { return m_outEdges.end(); } + + edge_iterator begin_inEdge() noexcept { return m_inEdges.begin(); } + + edge_iterator end_inEdge() noexcept { return m_inEdges.end(); } + + edge_iterator_wrapper outEdges() noexcept { return { m_outEdges }; } + + edge_iterator_wrapper inEdges() noexcept { return { m_inEdges }; } + + // ---------------------------- Capacity ---------------------------- + + unsigned int childDegree() const noexcept { return m_childDegree; } + + bool isLeaf() const noexcept { return firstChild == nullptr; } + + // TODO: Safe to assume all children are leaves if first child is leaf? + bool isLeafModule() const noexcept { return m_infomap == nullptr && firstChild != nullptr && firstChild->firstChild == nullptr; } + + bool isRoot() const noexcept { return parent == nullptr; } + + unsigned int depth() const noexcept; + + unsigned int firstDepthBelow() const noexcept; + + unsigned int numLeafMembers() const noexcept { return m_numLeafMembers; } + + bool isDangling() const noexcept { return m_outEdges.empty(); } + + unsigned int outDegree() const noexcept { return m_outEdges.size(); } + + unsigned int inDegree() const noexcept { return m_inEdges.size(); } + + unsigned int degree() const noexcept { return outDegree() + inDegree(); } + + // ---------------------------- Order ---------------------------- + bool isFirst() const noexcept { return !parent || parent->firstChild == this; } + + bool isLast() const noexcept { return !parent || parent->lastChild == this; } + + unsigned int childIndex() const noexcept; + + // Generate 1-based tree path + std::vector calculatePath() const noexcept; + + unsigned int infomapChildDegree() const noexcept; + + unsigned int id() const noexcept { return stateId; } + + // ---------------------------- Operators ---------------------------- + + bool operator==(const InfoNode& rhs) const noexcept { return this == &rhs; } + + bool operator!=(const InfoNode& rhs) const noexcept { return this != &rhs; } + + friend std::ostream& operator<<(std::ostream& out, const InfoNode& node) noexcept + { + if (node.isLeaf()) + out << "[" << node.physicalId << "]"; + else + out << "[module]"; + return out; + } + + // ---------------------------- Mutators ---------------------------- + + /** + * Clear a cloned node to initial state + */ + void initClean() noexcept; + + void sortChildrenOnFlow(bool recurse = true) noexcept; + + /** + * Release the children and store the child pointers for later expansion + * @return the number of children collapsed + */ + unsigned int collapseChildren() noexcept; + + /** + * Expand collapsed children + * @return the number of collapsed children expanded + */ + unsigned int expandChildren(); + + // ------ OLD ----- + + // After change, set the child degree if known instead of lazily computing it by traversing the linked list + void setChildDegree(unsigned int value) noexcept; + + void setNumLeafNodes(unsigned int value) noexcept { m_numLeafMembers = value; } + + void addChild(InfoNode* child) noexcept; + + void releaseChildren() noexcept; + + /** + * If not already having a single child, replace children + * with a single new node, assuming grandchildren. + * @return the single child + */ + InfoNode& replaceChildrenWithOneNode(); + + /** + * @return 1 if the node is removed, otherwise 0 + */ + unsigned int replaceWithChildren() noexcept; + + void replaceWithChildrenDebug() noexcept; + + /** + * @return The number of children removed + */ + unsigned int replaceChildrenWithGrandChildren() noexcept; + + void replaceChildrenWithGrandChildrenDebug() noexcept; + + void remove(bool removeChildren) noexcept; + + void deleteChildren() noexcept; + + void addOutEdge(InfoNode& target, double weight, double flow = 0.0) noexcept + { + auto* edge = new InfoEdge(*this, target, weight, flow); + m_outEdges.push_back(edge); + target.m_inEdges.push_back(edge); + } + +private: + void calcChildDegree() noexcept; +}; + +} // namespace infomap + +#endif // INFONODE_H_ diff --git a/vendor/infomap/src/core/InfomapBase.cpp b/vendor/infomap/src/core/InfomapBase.cpp new file mode 100644 index 0000000..1eada52 --- /dev/null +++ b/vendor/infomap/src/core/InfomapBase.cpp @@ -0,0 +1,2182 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "InfomapBase.h" +#include "InfomapConfig.h" +#include "InfoNode.h" +#include "FlowData.h" +#include "BiasedMapEquation.h" +#include "MemMapEquation.h" +#include "MetaMapEquation.h" +#include "InfomapOptimizer.h" +#include "../io/SafeFile.h" +#include "../utils/FileURI.h" +#include "../utils/FlowCalculator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _OPENMP +#include +#endif + +namespace infomap { + +std::map> InfomapBase::getMultilevelModules(bool states) +{ + if (haveMemory() && !states) { + throw std::runtime_error("Cannot get multilevel modules on higher-order network without states."); + } + unsigned int maxDepth = maxTreeDepth(); + unsigned int numModuleLevels = maxDepth - 1; + std::map> modules; + if (maxDepth < 2) return modules; + for (unsigned int level = 1; level <= numModuleLevels; ++level) { + for (auto it(iterLeafNodes(static_cast(level))); !it.isEnd(); ++it) { + auto& node = *it; + auto nodeId = states ? node.stateId : node.physicalId; + modules[nodeId].push_back(it.moduleId()); + } + } + return modules; +} + +// =================================================== +// IO +// =================================================== + +std::ostream& operator<<(std::ostream& out, const InfomapBase& infomap) +{ + return infomap.toString(out); +} + +// =================================================== +// Getters +// =================================================== + +unsigned int InfomapBase::numLevels() const +{ + // TODO: Make sure this is not called unless tree is guaranteed to have even depth! + unsigned int depth = 0; + InfoNode* n = m_root.firstChild; + while (n != nullptr) { + ++depth; + n = n->firstChild; + } + return depth; +} + +unsigned int InfomapBase::maxTreeDepth() const +{ + unsigned int maxDepth = 0; + for (auto it = root().begin_tree(); !it.isEnd(); ++it) { + const InfoNode& node = *it; + if (node.isLeaf()) { + if (it.depth() > maxDepth) { + maxDepth = it.depth(); + } + } + } + return maxDepth; +} + +// =================================================== +// Run +// =================================================== + +void InfomapBase::run(const std::string& parameters) +{ + if (!isMainInfomap()) { + runPartition(); + return; + } + + m_elapsedTime = Stopwatch(true); + m_startDate = Date(); + + std::string currentParameters = io::Str() << m_initialParameters << (parameters.empty() ? "" : " ") << parameters; + if (currentParameters != m_currentParameters) { + m_currentParameters = currentParameters; + setConfig(Config(m_currentParameters, isCLI)); + m_network.setConfig(*this); + } + + Log::init(verbosity, silent, verboseNumberPrecision); + + Log() << "=======================================================\n"; + Log() << " Infomap v" << INFOMAP_VERSION << " starts at " << m_startDate << "\n"; + Log() << " -> Input network: " << networkFile << "\n"; + if (noFileOutput) + Log() << " -> No file output!\n"; + else + Log() << " -> Output path: " << outDirectory << "\n"; + if (!parsedOptions.empty()) { + for (unsigned int i = 0; i < parsedOptions.size(); ++i) + Log() << (i == 0 ? " -> Configuration: " : " ") << parsedOptions[i] << "\n"; + } + if (!m_initialPartition.empty()) { + Log() << " -> " << m_initialPartition.size() << " initial module ids provided\n"; + } + Log() << "=======================================================\n"; +#ifdef _OPENMP +#pragma omp parallel +#pragma omp master + { + Log() << " OpenMP " << _OPENMP << " detected with " << omp_get_num_threads() << " threads...\n"; + } +#endif + + m_network.postProcessInputData(); + if (m_network.numNodes() == 0) { + m_network.readInputData(networkFile); + } + + if (!metaDataFile.empty()) { + initMetaData(metaDataFile); + } + + run(m_network); + + Log() << "===================================================\n"; + Log() << " Infomap ends at " << m_endDate << "\n"; + Log() << " (Elapsed time: " << m_elapsedTime << ")\n"; + Log() << "===================================================\n"; +} + +void InfomapBase::run(Network& network) +{ + if (!isMainInfomap()) + throw std::logic_error("Can't run a non-main Infomap with an input network"); + + if (network.numNodes() == 0) { + network.postProcessInputData(); + if (network.numNodes() == 0) { + throw std::domain_error("Network is empty"); + } + } + + if (printStateNetwork) { + std::string filename = outDirectory + outName + "_states.net"; + Log() << "Writing state network to '" << filename << "'... "; + network.writeStateNetwork(filename); + Log() << "done!\n"; + } + + if (printPajekNetwork) { + std::string filename; + if (printStates()) { + filename = outDirectory + outName + "_states_as_physical.net"; + Log() << "Writing state network as first order Pajek network to '" << filename << "'... "; + } else { + // Non-memory input + filename = outDirectory + outName + ".net"; + Log() << "Writing Pajek network to '" << filename << "'... "; + } + network.writePajekNetwork(filename); + Log() << "done!\n"; + } + + if (network.haveMemoryInput()) { + Log() << " -> Found higher order network input, using the Map Equation for higher order network flows\n"; + setStateInput(); + setStateOutput(); + + if (network.isMultilayerNetwork() && !isMultilayerNetwork()) { + setMultilayerInput(); + } + } else { + if (haveMemory() || network.higherOrderInputMethodCalled()) { + Log() << " -> Warning: Higher order network specified but no higher order input found.\n"; + // Use state output anyway for consistency even in the special case when input is first order + setStateOutput(); + } + Log() << " -> Ordinary network input, using the Map Equation for first order network flows\n"; + } + + if (network.haveDirectedInput() && isUndirectedFlow()) { + Log() << " -> Notice: Directed input found, changing flow model from '" << flowModel << "' to '" << FlowModel(FlowModel::directed) << "'\n"; + flowModel = FlowModel::directed; + } + network.setConfig(*this); + + calculateFlow(network, *this); + + if (network.isBipartite()) { + bipartite = true; + } + + initNetwork(network); + + if (numLeafNodes() == 0) + throw std::domain_error("No nodes to partition"); + + if (printFlowNetwork) { + std::string filename; + if (printStates()) { + filename = outDirectory + outName + "_states_as_physical_flow.net"; + Log() << "Writing flow state network as first order Pajek network to '" << filename << "'... "; + } else { + // Non-memory input + filename = outDirectory + outName + "_flow.net"; + Log() << "Writing flow network to '" << filename << "'... "; + } + network.writePajekNetwork(filename, true); + Log() << "done!\n"; + } + + // If used as a library, we may want to reuse the network instance, else clear to use less memory + // TODO: May have to use some meta data for output? + if (isCLI) { + network.clearLinks(); + } + + if (haveMemory()) + Log(2) << "Run Infomap with memory...\n"; + else + Log(2) << "Run Infomap...\n"; + + std::ostringstream bestSolutionStatistics; + unsigned int bestNumLevels = 0; + double bestHierarchicalCodelength = std::numeric_limits::max(); + m_codelengths.clear(); + NodePaths bestTree(numLeafNodes()); + unsigned int bestTrialIndex = 0; + + unsigned int numTrials = this->numTrials; + + for (unsigned int i = 0; i < numTrials; ++i) { + removeModules(); + auto startDate = Date(); + Stopwatch timer(true); + + if (isMainInfomap()) { + Log() << "\n"; + Log() << "================================================\n"; + Log() << "Trial " << (i + 1) << "/" << numTrials << " starting at " << startDate << "\n"; + Log() << "================================================\n"; + + if (!clusterDataFile.empty()) + initPartition(clusterDataFile, clusterDataIsHard, &network); + else if (!m_initialPartition.empty()) + initPartition(m_initialPartition, clusterDataIsHard); + } + + if (!noInfomap) + runPartition(); + else + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + + if (haveHardPartition()) + restoreHardPartition(); + + if (isMainInfomap()) { + Log() << "\n=> Trial " << (i + 1) << "/" << numTrials << " finished in " << timer.getElapsedTimeInSec() << "s with codelength " << m_hierarchicalCodelength << "\n"; + m_codelengths.push_back(m_hierarchicalCodelength); + + if (printAllTrials && numTrials > 1) { + writeResult(static_cast(i + 1)); + } + + if (m_hierarchicalCodelength < bestHierarchicalCodelength - 1e-10) { + bestSolutionStatistics.clear(); + bestSolutionStatistics.str(""); + bestNumLevels = printPerLevelCodelength(root(), bestSolutionStatistics); + bestHierarchicalCodelength = m_hierarchicalCodelength; + bestTrialIndex = i; + root().sortChildrenOnFlow(); + writeResult(); + if (numTrials > 1) { + bestTree.clear(); + for (auto it(iterLeafNodes()); !it.isEnd(); ++it) { + bestTree.emplace_back(it->stateId, it.path()); + } + } + } + } + } + if (isMainInfomap()) { + m_elapsedTime.stop(); + m_endDate = Date(); + Log() << "\n\n"; + Log() << "================================================\n"; + Log() << "Summary after " << numTrials << (numTrials > 1 ? " trials\n" : " trial\n"); + Log() << "================================================\n"; + if (numTrials > 1) { + if (bestTrialIndex < numTrials - 1) { + // Restore Infomap tree to best solution + initTree(bestTree); + writeResult(); // Overwrite result to get total elapsed time in output file header + } + + Log() << std::fixed << std::setprecision(9); + double averageCodelength = 0.0; + double minCodelength = m_codelengths[0]; + double maxCodelength = m_codelengths[0]; + Log() << "Codelengths: ["; + for (auto codelength : m_codelengths) { + Log() << codelength << ", "; + averageCodelength += codelength; + minCodelength = std::min(minCodelength, codelength); + maxCodelength = std::max(maxCodelength, codelength); + } + averageCodelength /= numTrials; + Log() << "\b\b]\n"; + Log() << "[min, average, max] codelength: [" << minCodelength << ", " << averageCodelength << ", " << maxCodelength << "]\n\n"; + } + + Log() << "Best end modular solution in " << bestNumLevels << " levels"; + if (bestHierarchicalCodelength > m_oneLevelCodelength) + Log() << " (warning: worse than one-level solution)"; + Log() << ":\n"; + Log() << bestSolutionStatistics.str() << '\n'; + } +} + +// =================================================== +// Run: Init: * +// =================================================== + +InfomapBase& InfomapBase::initMetaData(const std::string& metaDataFile) +{ + m_network.readMetaData(metaDataFile); + return *this; +} + +InfomapBase& InfomapBase::initNetwork(Network& network) +{ + if (network.numNodes() == 0) + throw std::domain_error("No nodes in network"); + if (m_root.childDegree() > 0) { + m_root.deleteChildren(); + m_leafNodes.clear(); + } + generateSubNetwork(network); + + initOptimizer(); + + init(); + return *this; +} + +InfomapBase& InfomapBase::initNetwork(InfoNode& parent, bool asSuperNetwork) +{ + generateSubNetwork(parent); + + if (asSuperNetwork) + transformNodeFlowToEnterFlow(m_root); + + init(); + return *this; +} + +InfomapBase& InfomapBase::initPartition(const std::string& clusterDataFile, bool hard, const Network* network) +{ + FileURI file(clusterDataFile); + ClusterMap clusterMap; + if (this->isMultilayerNetwork() && network != nullptr) { + auto map = network->layerNodeToStateId(); + clusterMap.readClusterData(clusterDataFile, false, &map); + } else { + clusterMap.readClusterData(clusterDataFile); + } + + Log() << "Init partition from file '" << clusterDataFile << "'... "; + + const auto& ext = clusterMap.extension(); + + if (ext == "tree" || ext == "ftree") { + initTree(clusterMap.nodePaths()); + } else if (ext == "clu") { + initPartition(clusterMap.clusterIds(), hard); + } + + Log() << "done! Generated " << numLevels() << " levels with codelength " << getIndexCodelength() << " + " << (m_hierarchicalCodelength - getIndexCodelength()) << " = " << io::toPrecision(m_hierarchicalCodelength) << '\n'; + + return *this; +} + +InfomapBase& InfomapBase::initTree(const NodePaths& tree) +{ + Log(4) << "Init tree... " << std::setprecision(9); + auto maxDepth = 2; + std::map nodeIdToIndex; + auto leafIndex = 0; + for (auto& leafNode : m_leafNodes) { + // Also detach leaf nodes to delete all modules, safe to call multiple times + leafNode->parent->releaseChildren(); + // Set parent to nullptr to be able to collect any orphaned nodes in the end + leafNode->parent = nullptr; + nodeIdToIndex[leafNode->stateId] = leafIndex; + ++leafIndex; + } + m_root.deleteChildren(); + + auto numNodesFound = 0; + auto numNodesNotInNetwork = 0; + for (const auto& nodePath : tree) { + ++numNodesFound; + InfoNode* node = &root(); + auto depth = 0; + const auto& path = nodePath.second; + const auto nodeId = nodePath.first; + InfoNode* leafNode = nullptr; + + try { + auto nodeIndex = nodeIdToIndex.at(nodeId); + leafNode = m_leafNodes[nodeIndex]; + } catch (std::exception& e) { + ++numNodesNotInNetwork; + continue; + } + + for (size_t i = 0; i < path.size(); ++i) { + auto childNumber = path[i]; // 1-based indexing + + // Create new node if path doesn't exist + // TODO: Check correct tree indexing? + if (node->childDegree() < childNumber) { + InfoNode* child = leafNode; + if (i + 1 < path.size()) { + child = new InfoNode(); + } + node->addChild(child); + } + + node = node->lastChild; + ++depth; + } + maxDepth = std::max(maxDepth, depth); + } + + if (numNodesNotInNetwork > 0) { + Log(1) << "\n -> " << numNodesNotInNetwork << "/" << numNodesFound << " nodes in tree not found in network."; + } + + auto numNodesAddedToNeighbouringModules = 0; + auto numNodesWithoutClusterInfo = 0; + + // Set orphaned nodes to their own or neighbouring module + for (auto& leafNode : m_leafNodes) { + if (leafNode->parent != nullptr) { + continue; + } + + ++numNodesWithoutClusterInfo; + if (assignToNeighbouringModule) { + // Take first neighbour that has a module assigned + for (auto& edge : leafNode->outEdges()) { + if (edge->target->parent != nullptr) { + edge->target->parent->addChild(leafNode); + ++numNodesAddedToNeighbouringModules; + break; + } + } + // Check incoming links if still orphan + if (leafNode->parent == nullptr) { + for (auto& edge : leafNode->inEdges()) { + if (edge->source->parent != nullptr) { + edge->source->parent->addChild(leafNode); + ++numNodesAddedToNeighbouringModules; + break; + } + } + } + // Set to own module if no neighbour module available + if (leafNode->parent == nullptr) { + auto module = new InfoNode(); + root().addChild(module); + module->addChild(leafNode); + } + } else { + // Set to own module if no neighbour module available + auto module = new InfoNode(); + root().addChild(module); + module->addChild(leafNode); + } + } + if (numNodesWithoutClusterInfo > 0) { + if (assignToNeighbouringModule) { + Log() << "\n -> " << numNodesWithoutClusterInfo << " nodes not found in tree, " << numNodesAddedToNeighbouringModules << " are put into neighbouring modules and " << (numNodesWithoutClusterInfo - numNodesAddedToNeighbouringModules) << " in separate."; + } else { + Log() << "\n -> " << numNodesWithoutClusterInfo << " nodes not found in tree are put into separate modules."; + } + } + + aggregateFlowValuesFromLeafToRoot(); + initTree(); + initNetwork(); + m_numNonTrivialTopModules = calculateNumNonTrivialTopModules(); + // Init partition to calculate indexCodelength and moduleCodelength + if (haveModules()) + setActiveNetworkFromChildrenOfRoot(); + else + setActiveNetworkFromLeafs(); + initPartition(); + + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + Log() << "\n -> Initiated to codelength " << m_hierarchicalCodelength << " in " << maxDepth << " levels with " << numTopModules() << " top modules.\n"; + Log() << std::setprecision(6); + return *this; +} + +/** + * clusterIds is a map from nodeId -> clusterId + */ +InfomapBase& InfomapBase::initPartition(const std::map& clusterIds, bool hard) +{ + // Generate map from node id to index in leaf node vector + std::map nodeIdToIndex; + unsigned int leafIndex = 0; + for (auto& nodePtr : m_leafNodes) { + auto& leafNode = *nodePtr; + nodeIdToIndex[leafNode.stateId] = leafIndex; + ++leafIndex; + } + + // Remap clusterIds to vector from leaf node index to module index instead of nodeId -> clusterId + std::map> clusterIdToNodeIds; + for (auto it : clusterIds) { + clusterIdToNodeIds[it.second].insert(it.first); + } + unsigned int numNodes = numLeafNodes(); + std::vector modules(numNodes); + std::vector selectedNodes(numNodes, 0); + unsigned int moduleIndex = 0; + for (const auto& it : clusterIdToNodeIds) { + auto& nodes = it.second; + for (auto& nodeId : nodes) { + auto nodeIndex = nodeIdToIndex[nodeId]; + modules[nodeIndex] = moduleIndex; + ++selectedNodes[nodeIndex]; + } + ++moduleIndex; + } + + unsigned int numNodesWithoutClusterInfo = 0; + for (auto& count : selectedNodes) { + if (count == 0) { + ++numNodesWithoutClusterInfo; + } + } + + if (numNodesWithoutClusterInfo == 0) { + return initPartition(modules, hard); + } + + if (assignToNeighbouringModule) { + Log() << "\n -> " << numNodesWithoutClusterInfo << " nodes not found in cluster file are put into neighbouring modules if possible. "; + for (unsigned int i = 0; i < numNodes; ++i) { + if (selectedNodes[i] == 0) { + // Check out edges greedily for connected modules + auto& node = *m_leafNodes[i]; + for (auto& e : node.outEdges()) { + auto& edge = *e; + auto targetNodeIndex = nodeIdToIndex[edge.target->stateId]; + if (selectedNodes[targetNodeIndex] != 0) { + selectedNodes[i] = 1; + modules[i] = modules[targetNodeIndex]; + break; + } + } + if (selectedNodes[i] == 0) { + // Check in edges greedily for connected modules + for (auto& e : node.inEdges()) { + auto& edge = *e; + auto sourceNodeIndex = nodeIdToIndex[edge.source->stateId]; + if (selectedNodes[sourceNodeIndex] != 0) { + selectedNodes[i] = 1; + modules[i] = modules[sourceNodeIndex]; + break; + } + } + } + if (selectedNodes[i] == 0) { + // No connected node with a module index, fall back to create new module + selectedNodes[i] = 1; + modules[i] = moduleIndex; + ++moduleIndex; + } + } + } + } else { + Log() << "\n -> " << numNodesWithoutClusterInfo << " nodes not found in cluster file are put into separate modules. "; + // Put non-selected nodes in its own module + for (unsigned int i = 0; i < numNodes; ++i) { + if (selectedNodes[i] == 0) { + modules[i] = moduleIndex; + ++moduleIndex; + } + } + } + + return initPartition(modules); +} + +InfomapBase& InfomapBase::initPartition(std::vector>& clusters, bool hard) +{ + Log(3) << "\n -> Init " << (hard ? "hard" : "soft") << " partition... " << std::flush; + unsigned int numNodes = numLeafNodes(); + // Get module indices from the merge structure + std::vector modules(numNodes); + std::vector selectedNodes(numNodes, 0); + unsigned int moduleIndex = 0; + + for (auto& cluster : clusters) { + if (cluster.empty()) + continue; + for (auto id : cluster) { + ++selectedNodes[id]; + modules[id] = moduleIndex; + } + ++moduleIndex; + } + + // Put non-selected nodes in its own module + unsigned int numNodesWithoutClusterInfo = 0; + for (unsigned int i = 0; i < numNodes; ++i) { + if (selectedNodes[i] == 0) { + modules[i] = moduleIndex; + ++moduleIndex; + ++numNodesWithoutClusterInfo; + } + } + if (numNodesWithoutClusterInfo > 0) + Log() << "\n -> " << numNodesWithoutClusterInfo << " nodes not found in cluster file are put into separate modules. "; + + return initPartition(modules, hard); +} + +InfomapBase& InfomapBase::initPartition(std::vector& modules, bool hard) +{ + removeModules(); + setActiveNetworkFromLeafs(); + initPartition(); // TODO: confusing same name, should be able to init default without arguments here too + moveActiveNodesToPredefinedModules(modules); + consolidateModules(false); + + if (hard) { + // Save the original network + m_originalLeafNodes.swap(m_leafNodes); + + // Use the pre-partitioned network as the new leaf network + unsigned int numNodesInNewNetwork = numTopModules(); + m_leafNodes.resize(numNodesInNewNetwork); + unsigned int nodeIndex = 0; + unsigned int numLinksInNewNetwork = 0; + for (InfoNode& node : m_root) { + m_leafNodes[nodeIndex] = &node; + // Collapse children + node.collapseChildren(); + numLinksInNewNetwork += node.outDegree(); + ++nodeIndex; + } + + Log(1) << "\n -> Hard-partitioned the network to " << numNodesInNewNetwork << " nodes and " << numLinksInNewNetwork << " links with codelength " << *this << '\n'; + } else { + Log(1) << "\n -> Initiated to codelength " << *this << " in " << numTopModules() << " top modules.\n"; + } + m_hierarchicalCodelength = getCodelength(); + + return *this; +} + +void InfomapBase::generateSubNetwork(Network& network) +{ + unsigned int numNodes = network.numNodes(); + auto& metaData = network.metaData(); + numMetaDataDimensions = network.numMetaDataColumns(); + + Log() << "Build internal network with " << numNodes << " nodes and " << network.numLinks() << " links...\n"; + if (!metaData.empty()) { + Log() << "and " << metaData.size() << " meta-data records in " << numMetaDataDimensions << " dimensions\n"; + } + + m_leafNodes.reserve(numNodes); + double sumNodeFlow = 0.0; + double sumTeleFlow = 0.0; + std::map nodeIndexMap; + for (auto& nodeIt : network.nodes()) { + auto& networkNode = nodeIt.second; + auto* node = new InfoNode(networkNode.flow, networkNode.id, networkNode.physicalId, networkNode.layerId); + node->data.teleportWeight = networkNode.weight; + node->data.teleportFlow = networkNode.teleFlow; + node->data.exitFlow = networkNode.exitFlow; + node->data.enterFlow = networkNode.enterFlow; + if (haveMetaData()) { + auto meta = metaData.find(networkNode.id); + if (meta != metaData.end()) { + node->metaData = meta->second; + } else { + node->metaData = std::vector(numMetaDataDimensions, -1); + } + } + sumNodeFlow += networkNode.flow; + sumTeleFlow += networkNode.teleFlow; + m_root.addChild(node); + nodeIndexMap[networkNode.id] = m_leafNodes.size(); + m_leafNodes.push_back(node); + } + m_root.data.flow = sumNodeFlow; + m_root.data.teleportFlow = sumTeleFlow; + // m_calculateEnterExitFlow = true; //TODO: Implement always in flow calculation + if (!this->regularized) { + m_calculateEnterExitFlow = true; + } + BiasedMapEquation::setNetworkProperties(network); + + if (std::abs(sumNodeFlow - 1.0) > 1e-10) + Log() << "Warning, total flow on nodes differs from 1.0 by " << sumNodeFlow - 1.0 << ".\n"; + + bool changeMarkovTime = std::abs(markovTime - 1) > 1e-3; + if (changeMarkovTime) { + Log() << " -> Rescale link flow with global Markov time " << markovTime << "\n"; + } + + for (auto& linkIt : network.nodeLinkMap()) { + unsigned int linkSourceId = linkIt.first.id; + unsigned int sourceIndex = nodeIndexMap[linkSourceId]; + const auto& subLinks = linkIt.second; + for (auto& subIt : subLinks) { + unsigned int linkTargetId = subIt.first.id; + unsigned int targetIndex = nodeIndexMap[linkTargetId]; + // Ignore self-links in optimization as it doesn't change enter/exit flow on modular level + if (sourceIndex != targetIndex) { + auto& linkData = subIt.second; + m_leafNodes[sourceIndex]->addOutEdge(*m_leafNodes[targetIndex], linkData.weight, linkData.flow * markovTime); + } + } + } + + if (variableMarkovTime) { + Log() << " -> Rescale link flow with variable Markov time\n"; + if (std::abs(variableMarkovTimeDamping - 1) > 1e-9) { + Log() << " -> Use variable Markov time strength " << variableMarkovTimeDamping << "\n"; + } + } + + double maxEntropy = 0.0; + double maxFlow = 0.0; + double entropyRate = 0.0; + unsigned int maxDegree = 0; + unsigned int maxOutDegree = 0; + unsigned int maxInDegree = 0; + double totDegree = network.sumDegree(); + std::vector entropies(numNodes, 0); + + for (unsigned i = 0; i < numNodes; ++i) { + InfoNode& node = *m_leafNodes[i]; + maxDegree = std::max(maxDegree, node.degree()); + maxOutDegree = std::max(maxOutDegree, node.outDegree()); + maxInDegree = std::max(maxInDegree, node.inDegree()); + double entropy = 0; + double sumOut = 0; + for (InfoEdge* e : node.outEdges()) { + entropy -= infomath::plogp(e->data.weight); + sumOut += e->data.weight; + } + if (isUndirectedFlow()) { + for (InfoEdge* e : node.inEdges()) { + entropy -= infomath::plogp(e->data.weight); + sumOut += e->data.weight; + } + } + entropy = sumOut > 1e-9 ? (entropy + infomath::plogp(sumOut)) / sumOut : 0; + maxEntropy = std::max(maxEntropy, entropy); + entropyRate += m_leafNodes[i]->data.flow * entropy; + maxFlow = std::max(maxFlow, node.data.flow); + entropies[i] = entropy; // Store for undirected networks + } + + m_entropyRate = entropyRate; + m_maxEntropy = maxEntropy; + m_maxFlow = maxFlow; + + double minLocalScale = variableMarkovTimeMinLocalScale; + double damping = variableMarkovTimeDamping; + + double maxScale = infomath::linlog(maxFlow * totDegree, damping); + + if (variableMarkovTime) { + if (damping < 0) { + maxScale = infomath::linlog(pow(2.0, maxEntropy), -damping); + } + for (unsigned i = 0; i < numNodes; ++i) { + InfoNode& node = *m_leafNodes[i]; + double localScale = damping < 0 ? infomath::linlog(pow(2.0, entropies[i]), -damping) : infomath::linlog(std::max(minLocalScale, node.data.flow * totDegree), damping); + for (InfoEdge* e : node.outEdges()) { + if (isUndirectedFlow()) { + double oppositeLocalScale = damping < 0 ? infomath::linlog(pow(2.0, entropies[nodeIndexMap[e->target->stateId]]), -damping) : infomath::linlog(std::max(minLocalScale, e->target->data.flow * totDegree), damping); + localScale = std::max(localScale, oppositeLocalScale); + } + double localMarkovTimeScale = maxScale / std::max(minLocalScale, localScale); + e->data.flow *= localMarkovTimeScale; + network.nodeLinkMap()[e->source->stateId][e->target->stateId].flow = e->data.flow; + } + } + } +} + +void InfomapBase::generateSubNetwork(InfoNode& parent) +{ + // Store parent module + m_root.owner = &parent; + m_root.data = parent.data; + + unsigned int numNodes = parent.childDegree(); + m_leafNodes.resize(numNodes); + + Log(1) << "Generate sub network with " << numNodes << " nodes...\n"; + + unsigned int childIndex = 0; + for (InfoNode& node : parent) { + auto* clonedNode = new InfoNode(node); + clonedNode->initClean(); + m_root.addChild(clonedNode); + node.index = childIndex; // Set index to its place in this subnetwork to be able to find edge target below + m_leafNodes[childIndex] = clonedNode; + ++childIndex; + } + + InfoNode* parentPtr = &parent; + // Clone edges + for (InfoNode& node : parent) { + for (InfoEdge* e : node.outEdges()) { + InfoEdge& edge = *e; + // If neighbour node is within the same module, add the link to this subnetwork. + if (edge.target->parent == parentPtr) { + m_leafNodes[edge.source->index]->addOutEdge(*m_leafNodes[edge.target->index], edge.data.weight, edge.data.flow); + } + } + } +} + +void InfomapBase::init() +{ + Log(3) << "InfomapBase::init()...\n"; + + if (m_calculateEnterExitFlow && isMainInfomap()) + initEnterExitFlow(); + + initNetwork(); + + m_oneLevelCodelength = calcCodelength(m_root); + Log() << " -> One-level codelength: " << m_oneLevelCodelength << '\n'; +} + +// =================================================== +// Run: * +// =================================================== + +void InfomapBase::hierarchicalPartition() +{ + Log(1) << "Hierarchical partition...\n"; + + const auto depth = maxTreeDepth(); + if (depth > 2) { + Log(1) << "Continuing from a tree with " << depth << " levels...\n"; + + if (fastHierarchicalSolution == 0) { + Log(1) << "Removing sub modules...\n"; + removeSubModules(true); + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + } + + else if (fastHierarchicalSolution == 1) { + Log(1) << "Fine-tune bottom modules... "; + bool isSilent = isMainInfomap() && Log::isSilent(); + + double codelengthBefore = 0.0; + double codelengthAfter = 0.0; + + for (InfoNode& module : m_root.infomapTree()) { + if (!module.isLeaf() && module.firstChild->isLeafModule()) { + codelengthBefore += module.codelength; + auto numLeafs = 0; + for (auto& leafModule : module) { + numLeafs += leafModule.childDegree(); + } + + std::vector modules(numLeafs); + std::vector leafs(numLeafs); + + auto i = 0; + for (auto it = module.begin_tree(); !it.isEnd(); ++it) { + if (it->isLeaf()) { + modules[i] = it.moduleIndex(); + leafs[i] = it.current(); + ++i; + } + } + + module.replaceChildrenWithGrandChildren(); + + auto& subInfomap = getSubInfomap(module).initNetwork(module); + + subInfomap.initPartition(modules); + + // Run two-level partition + find hierarchically super modules (skip recursion) + subInfomap.setOnlySuperModules(true).run(); + + // Collect sub Infomap modules + i = 0; + for (auto& subLeafPtr : subInfomap.leafNodes()) { + modules[i] = subLeafPtr->index; + ++i; + } + + // Create new sub modules + std::vector subModules(numLeafs, nullptr); + module.releaseChildren(); + + for (auto j = 0; j < numLeafs; ++j) { + InfoNode* leaf = leafs[j]; + unsigned int moduleIndex = modules[j]; + if (subModules[moduleIndex] == nullptr) { + subModules[moduleIndex] = new InfoNode(subInfomap.leafNodes()[j]->parent->data); + subModules[moduleIndex]->index = moduleIndex; + module.addChild(subModules[moduleIndex]); + } + subModules[moduleIndex]->addChild(leaf); + } + module.disposeInfomap(); + module.codelength = calcCodelength(module); + codelengthAfter += module.codelength; + } + } + + if (isMainInfomap()) + Log::setSilent(isSilent); + + const double diffCodelength = codelengthBefore - codelengthAfter; + Log() << "done! Codelength improvement " << (diffCodelength / codelengthBefore) * 100 << "% to codelength " << codelengthAfter << "\n"; + } + + recursivePartition(); + return; + } + + partition(); + + if (numTopModules() == 1 || numTopModules() == numLeafNodes()) { + Log(1) << "Trivial partition, skip search for hierarchical solution.\n"; + return; + } + + if (numTopModules() > preferredNumberOfModules) { + findHierarchicalSuperModules(); + } + + if (onlySuperModules) { + removeSubModules(true); + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + return; + } + + if (fastHierarchicalSolution >= 2) { + // FIXME Calculate individual module codelengths and store on the modules? + return; + } + + if (fastHierarchicalSolution == 0) { + removeSubModules(true); + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + } + + recursivePartition(); +} + +void InfomapBase::partition() +{ + auto oldPrecision = Log::precision(); + Log(0, 0) << "Two-level compression: " << std::setprecision(2) << std::flush; + Log(1) << "Trying to find modular structure... \n"; + double initialCodelength = m_oneLevelCodelength; + double oldCodelength = initialCodelength; + + m_tuneIterationIndex = 0; + findTopModulesRepeatedly(levelAggregationLimit); + + double newCodelength = getCodelength(); + double compression = oldCodelength < 1e-16 ? 0.0 : (oldCodelength - newCodelength) / oldCodelength; + Log(0, 0) << (compression * 100) << "% " << std::flush; + oldCodelength = newCodelength; + + bool doFineTune = true; + bool coarseTuned = false; + while (numTopModules() > 1 && (m_tuneIterationIndex + 1) != tuneIterationLimit) { + ++m_tuneIterationIndex; + if (doFineTune) { + Log(3) << "\n"; + Log(1, 3) << "Fine tune... " << std::flush; + unsigned int numEffectiveLoops = fineTune(); + if (numEffectiveLoops > 0) { + Log(1, 3) << " -> done in " << numEffectiveLoops << " effective loops to codelength " << *this << " in " << numTopModules() << " modules.\n"; + // Continue to merge fine-tuned modules + findTopModulesRepeatedly(levelAggregationLimit); + } else { + Log(1, 3) << "no improvement.\n"; + } + } else { + coarseTuned = true; + if (!noCoarseTune) { + Log(3) << "\n"; + Log(1, 3) << "Coarse tune... " << std::flush; + unsigned int numEffectiveLoops = coarseTune(); + if (numEffectiveLoops > 0) { + Log(1, 3) << "done in " << numEffectiveLoops << " effective loops to codelength " << *this << " in " << numTopModules() << " modules.\n"; + // Continue to merge fine-tuned modules + findTopModulesRepeatedly(levelAggregationLimit); + } else { + Log(1, 3) << "no improvement.\n"; + } + } + } + newCodelength = getCodelength(); + compression = oldCodelength < 1e-16 ? 0.0 : (oldCodelength - newCodelength) / oldCodelength; + bool isImprovement = newCodelength <= oldCodelength - minimumCodelengthImprovement && newCodelength < oldCodelength - initialCodelength * minimumRelativeTuneIterationImprovement; + if (!isImprovement) { + // Make sure coarse-tuning have been tried + if (coarseTuned) + break; + } else { + oldCodelength = newCodelength; + } + Log(0, 0) << (compression * 100) << "% " << std::flush; + doFineTune = !doFineTune; + } + + Log(0, 0) << std::setprecision(oldPrecision) << '\n'; + Log() << "Partitioned to codelength " << *this << " in " << numTopModules(); + if (m_numNonTrivialTopModules != numTopModules()) + Log() << " (" << m_numNonTrivialTopModules << " non-trivial)"; + Log() << " modules.\n"; + + if (!preferModularSolution && preferredNumberOfModules == 0 && haveNonTrivialModules() && getCodelength() > getOneLevelCodelength()) { + Log() << "Worse codelength than one-level codelength, putting all nodes in one module... "; + + // Create new single module between modules and root + auto& module = root().replaceChildrenWithOneNode(); + module.data = m_root.data; + module.index = 0; + for (auto& node : module) { + node.index = 0; + } + module.codelength = getOneLevelCodelength(); + m_hierarchicalCodelength = getOneLevelCodelength(); + + } else { + // Set consolidated cluster index on nodes and modules + for (auto clusterIt = m_root.begin_tree(); !clusterIt.isEnd(); ++clusterIt) { + clusterIt->index = clusterIt.moduleIndex(); + } + + // Calculate individual module codelengths and store on the modules + calcCodelengthOnTree(root(), false); + m_root.codelength = getIndexCodelength(); + m_hierarchicalCodelength = getCodelength(); + } + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); +} + +void InfomapBase::restoreHardPartition() +{ + // Collect all leaf nodes in a separate sequence to be able to iterate independent of tree structure + std::vector leafNodes(numLeafNodes()); + unsigned int leafIndex = 0; + for (InfoNode& node : m_root.infomapTree()) { + if (node.isLeaf()) { + leafNodes[leafIndex] = &node; + ++leafIndex; + } + } + // Expand all children + unsigned int numExpandedChildren = 0; + unsigned int numExpandedNodes = 0; + for (InfoNode* node : leafNodes) { + ++numExpandedNodes; + numExpandedChildren += node->expandChildren(); + node->replaceWithChildren(); + } + + // Swap back original leaf nodes + m_originalLeafNodes.swap(m_leafNodes); + + Log(1) << "Expanded " << numExpandedNodes << " hard modules to " << numExpandedChildren << " original nodes.\n"; +} + +// =================================================== +// Run: Init: * +// =================================================== + +void InfomapBase::initEnterExitFlow() +{ + // Not done in Bayesian + // TODO: Skip this, always add enter/exit/tele flow from flow calculator + // Calculate enter/exit + double alpha = teleportationProbability; + double beta = 1.0 - alpha; + + for (auto* n : m_leafNodes) { + n->data.enterFlow = n->data.exitFlow = 0.0; + } + if (!isUndirectedClustering()) { + for (auto* n : m_leafNodes) { + auto& node = *n; + node.data.teleportFlow = alpha * node.data.flow; + node.data.teleportSourceFlow = node.data.flow; + if (node.isDangling()) { + node.data.teleportFlow = node.data.flow; + node.data.danglingFlow = node.data.flow; + m_sumDanglingFlow += node.data.flow; + } + } + for (auto* n : m_leafNodes) { + auto& node = *n; + for (InfoEdge* e : node.outEdges()) { + InfoEdge& edge = *e; + // Self-links not included here, should not add to enter and exit flow in its enclosing module + edge.source->data.exitFlow += edge.data.flow; + edge.target->data.enterFlow += edge.data.flow; + } + if (recordedTeleportation) { + // Don't let self-teleportation add to the enter/exit flow (i.e. multiply with (1.0 - node.data.teleportWeight)) + node.data.exitFlow += (alpha * node.data.flow + beta * node.data.danglingFlow) * (1.0 - node.data.teleportWeight); + node.data.enterFlow += (alpha * (1.0 - node.data.flow) + beta * (m_sumDanglingFlow - node.data.danglingFlow)) * node.data.teleportWeight; + } + } + } else { + for (auto* n : m_leafNodes) { + auto& node = *n; + node.data.teleportFlow = alpha * node.data.flow; + node.data.teleportSourceFlow = node.data.flow; + if (node.isDangling()) { + node.data.teleportFlow = node.data.flow; + node.data.danglingFlow = node.data.flow; + } + for (InfoEdge* e : node.outEdges()) { + InfoEdge& edge = *e; + // Self-links not included here, should not add to enter and exit flow in its enclosing module + double halfFlow = edge.data.flow / 2; + edge.source->data.exitFlow += halfFlow; + edge.target->data.exitFlow += halfFlow; + edge.source->data.enterFlow += halfFlow; + edge.target->data.enterFlow += halfFlow; + } + } + } +} + +// Aggregate node and enter/exit flow to all tree nodes +void InfomapBase::aggregateFlowValuesFromLeafToRoot() +{ + // Aggregate flow from leaf nodes to root node + unsigned int numLevels = 0; + root().data.flow = 0.0; + for (auto it = root().begin_post_depth_first(); !it.isEnd(); ++it) { + auto& node = *it; + if (!node.isRoot()) + node.parent->data += node.data; + // Don't aggregate enter and exit flow + if (!node.isLeaf()) { + node.index = it.depth(); // Use index to store the depth on modules + node.data.enterFlow = 0.0; + node.data.exitFlow = 0.0; + } else + numLevels = std::max(numLevels, it.depth()); + } + + if (std::abs(root().data.flow - 1.0) > 1e-10) { + Log() << "Warning, aggregated flow is not exactly 1.0, but " << std::setprecision(10) << root().data.flow << std::setprecision(9) << ".\n"; + } + + // Aggregate enter and exit flow between modules + for (auto& leafNode : m_leafNodes) { + auto& leafNodeSource = *leafNode; + for (InfoEdge* e : leafNodeSource.outEdges()) { + auto& edge = *e; + auto& leafNodeTarget = edge.target; + double linkFlow = edge.data.flow; + double halfFlow = linkFlow / 2; + + InfoNode* node1 = leafNodeSource.parent; + InfoNode* node2 = leafNodeTarget->parent; + + if (node1 == node2) + continue; + + // First aggregate link flow until equal depth + while (node1->index > node2->index) { + if (isUndirectedClustering()) { + node1->data.exitFlow += halfFlow; + node1->data.enterFlow += halfFlow; + } else { + node1->data.exitFlow += linkFlow; + } + node1 = node1->parent; + } + while (node2->index > node1->index) { + if (isUndirectedClustering()) { + node2->data.enterFlow += halfFlow; + node2->data.exitFlow += halfFlow; + } else { + node2->data.enterFlow += linkFlow; + } + node2 = node2->parent; + } + + // Then aggregate link flow until equal parent + while (node1 != node2) { + if (isUndirectedClustering()) { + node1->data.exitFlow += halfFlow; + node1->data.enterFlow += halfFlow; + node2->data.enterFlow += halfFlow; + node2->data.exitFlow += halfFlow; + } else { + node1->data.exitFlow += linkFlow; + node2->data.enterFlow += linkFlow; + } + node1 = node1->parent; + node2 = node2->parent; + } + } + } + if (recordedTeleportation) { + Log() << "\n\nAggregating enter/exit flow for recorded teleportation, sum teleFlow: " << m_root.data.teleportFlow << "\n"; + + for (auto& node : m_root.infomapTree()) { + if (!node.isLeaf()) { + // Don't code self-teleportation + + node.data.enterFlow += (m_root.data.teleportFlow - node.data.teleportFlow) * node.data.teleportWeight; + node.data.exitFlow += node.data.teleportFlow * (1.0 - node.data.teleportWeight); + } + } + } +} + +double InfomapBase::calcCodelengthOnTree(InfoNode& root, bool includeRoot) const +{ + double totalCodelength = 0.0; + // Calculate enter/exit flow (assuming 0 by default) + for (auto& node : root.infomapTree()) { + if (node.isLeaf() || (!includeRoot && node.isRoot())) + continue; + node.codelength = calcCodelength(node); + totalCodelength += node.codelength; + } + return totalCodelength; +} + +// =================================================== +// Run: Partition: * +// =================================================== + +void InfomapBase::setActiveNetworkFromChildrenOfRoot() +{ + m_moduleNodes.resize(m_root.childDegree()); + unsigned int i = 0; + for (auto& node : m_root) + m_moduleNodes[i++] = &node; + m_activeNetwork = &m_moduleNodes; +} + +void InfomapBase::findTopModulesRepeatedly(unsigned int maxLevels) +{ + Log(1, 2) << "Iteration " << (m_tuneIterationIndex + 1) << ", moving "; + Log(3) << "\nIteration " << (m_tuneIterationIndex + 1) << ":\n"; + m_aggregationLevel = 0; + unsigned int numLevelsConsolidated = numLevels() - 1; + if (maxLevels == 0) + maxLevels = std::numeric_limits::max(); + + std::string initialCodelength; + + // Reapply core algorithm on modular network, replacing modules with super modules + while (numTopModules() > 1 && numLevelsConsolidated != maxLevels) { + if (haveModules()) + setActiveNetworkFromChildrenOfRoot(); + else + setActiveNetworkFromLeafs(); + initPartition(); + if (m_aggregationLevel == 0) { + initialCodelength = io::Str() << "" << *this; + } + + Log(1, 2) << activeNetwork().size() << "*" << std::flush; + Log(3) << "Level " << (numLevelsConsolidated + 1) << " (codelength: " << *this << "): Moving " << activeNetwork().size() << " nodes... " << std::flush; + // Core loop, merging modules + unsigned int numOptimizationLoops = optimizeActiveNetwork(); + + Log(1, 2) << numOptimizationLoops << ", " << std::flush; + Log(3) << "done! -> codelength " << *this << " in " << numActiveModules() << " modules.\n"; + + // If no improvement, revert codelength terms to the last consolidated state + if (haveModules() && restoreConsolidatedOptimizationPointIfNoImprovement()) { + Log(3) << "-> Restored to codelength " << *this << " in " << numTopModules() << " modules.\n"; + break; + } + + // Consolidate modules + bool replaceExistingModules = haveModules(); + consolidateModules(replaceExistingModules); + ++numLevelsConsolidated; + ++m_aggregationLevel; + } + m_aggregationLevel = 0; + + m_numNonTrivialTopModules = calculateNumNonTrivialTopModules(); + + Log(1, 2) << (m_isCoarseTune ? "modules" : "nodes") << "*loops from codelength " << initialCodelength << " to codelength " << *this << " in " << numTopModules() << " modules. (" << m_numNonTrivialTopModules << " non-trivial modules)\n"; +} + +unsigned int InfomapBase::fineTune() +{ + if (numLevels() != 2) + throw std::logic_error("InfomapBase::fineTune() called but numLevels != 2"); + + setActiveNetworkFromLeafs(); + initPartition(); + + // Make sure module nodes have correct index //TODO: Assume always correct here? + unsigned int moduleIndex = 0; + for (auto& module : m_root) { + module.index = moduleIndex++; + } + + // Put leaf modules in existing modules + std::vector modules(numLeafNodes()); + for (unsigned int i = 0; i < numLeafNodes(); ++i) { + modules[i] = m_leafNodes[i]->parent->index; + } + moveActiveNodesToPredefinedModules(modules); + + Log(3) << " -> moved to codelength " << *this << " in " << numActiveModules() << " existing modules. Try tuning...\n"; + + // Continue to optimize from there to tune leaf nodes + unsigned int numEffectiveLoops = optimizeActiveNetwork(); + if (numEffectiveLoops == 0) { + restoreConsolidatedOptimizationPointIfNoImprovement(); + Log(4) << "Fine-tune didn't improve solution, restoring last.\n"; + } else { + // Delete existing modules and consolidate fine-tuned modules + root().replaceChildrenWithGrandChildren(); + consolidateModules(false); + Log(4) << "Fine-tune done in " << numEffectiveLoops << " effective loops to codelength " << *this << " in " << numTopModules() << " modules.\n"; + } + + return numEffectiveLoops; +} + +unsigned int InfomapBase::coarseTune() +{ + if (numLevels() != 2) + throw std::logic_error("InfomapBase::coarseTune() called but numLevels != 2"); + + Log(4) << "Coarse-tune...\nPartition each module in sub-modules for coarse tune...\n"; + + bool isSilent = false; + if (isMainInfomap()) { + isSilent = Log::isSilent(); + Log::setSilent(true); + } + + unsigned int moduleIndexOffset = 0; + for (auto& node : m_root) { + // Don't search for sub-modules in too small modules + if (node.childDegree() < 2) { + for (auto& child : node) { + child.index = moduleIndexOffset; + } + moduleIndexOffset += 1; + continue; + } else { + InfomapBase& subInfomap = getSubInfomap(node) + .setTwoLevel(true) + .setTuneIterationLimit(1); + subInfomap.initNetwork(node).run(); + + auto originalLeafIt = node.begin_child(); + for (auto& subLeafPtr : subInfomap.leafNodes()) { + InfoNode& subLeaf = *subLeafPtr; + originalLeafIt->index = subLeaf.index + moduleIndexOffset; + ++originalLeafIt; + } + moduleIndexOffset += subInfomap.numTopModules(); + + node.disposeInfomap(); + } + } + + if (isMainInfomap()) + Log::setSilent(isSilent); + + Log(4) << "Move leaf nodes to " << moduleIndexOffset << " sub-modules... \n"; + // Put leaf modules in the calculated sub-modules + std::vector subModules(numLeafNodes()); + for (unsigned int i = 0; i < numLeafNodes(); ++i) { + subModules[i] = m_leafNodes[i]->index; + } + + setActiveNetworkFromLeafs(); + initPartition(); + moveActiveNodesToPredefinedModules(subModules); + + Log(4) << "Moved to " << numActiveModules() << " modules...\n"; + + // Replace existing modules but store that structure on the sub-modules + consolidateModules(true); + + Log(4) << "Consolidated " << numTopModules() << " sub-modules to codelength " << *this << '\n'; + + Log(4) << "Move sub-modules to former modular structure... \n"; + // Put sub-modules in the former modular structure + std::vector modules(numTopModules()); + unsigned int iModule = 0; + for (auto& subModule : m_root) { + modules[iModule++] = subModule.index; + } + + setActiveNetworkFromChildrenOfRoot(); + initPartition(); + // Move sub-modules to former modular structure + moveActiveNodesToPredefinedModules(modules); + + Log(4) << "Tune sub-modules from codelength " << *this << " in " << numActiveModules() << " modules... \n"; + // Continue to optimize from there to tune sub-modules + unsigned int numEffectiveLoops = optimizeActiveNetwork(); + Log(4) << "Tuned sub-modules in " << numEffectiveLoops << " effective loops to codelength " << *this << " in " << numActiveModules() << " modules.\n"; + consolidateModules(true); + Log(4) << "Consolidated tuned sub-modules to codelength " << *this << " in " << numTopModules() << " modules.\n"; + return numEffectiveLoops; +} + +unsigned int InfomapBase::calculateNumNonTrivialTopModules() const +{ + if (root().childDegree() == 1) + return 0; + unsigned int numNonTrivialTopModules = 0; + for (auto& module : m_root) { + if (module.childDegree() > 1) + ++numNonTrivialTopModules; + } + return numNonTrivialTopModules; +} + +unsigned int InfomapBase::calculateMaxDepth() const +{ + unsigned int maxDepth = 0; + for (auto it(m_root.begin_tree()); !it.isEnd(); ++it) { + if (it->isLeaf()) { + maxDepth = std::max(maxDepth, it.depth()); + } + } + return maxDepth; +} + +unsigned int InfomapBase::findHierarchicalSuperModulesFast(unsigned int superLevelLimit) +{ + if (superLevelLimit == 0) + return 0; + + unsigned int numLevelsCreated = 0; + double oldIndexLength = getIndexCodelength(); + double hierarchicalCodelength = getCodelength(); + double workingHierarchicalCodelength = hierarchicalCodelength; + + Log(1) << "\nFind hierarchical super modules iteratively...\n"; + Log(0, 0) << "Fast super-level compression: " << std::setprecision(2) << std::flush; + + // Add index codebooks as long as the code gets shorter + do { + Log(1) << "Iteration " << numLevelsCreated + 1 << ", finding super modules fast to " << numTopModules() << (haveModules() ? " modules" : " nodes") << "... \n"; + + if (haveModules()) { + setActiveNetworkFromChildrenOfRoot(); + transformNodeFlowToEnterFlow(m_root); + initSuperNetwork(); + } else { + setActiveNetworkFromLeafs(); + } + + initPartition(); + + unsigned int numEffectiveLoops = optimizeActiveNetwork(); + + double codelength = getCodelength(); + double indexCodelength = getIndexCodelength(); + unsigned int numSuperModules = numActiveModules(); + bool trivialSolution = numSuperModules == 1 || numSuperModules == activeNetwork().size(); + + bool acceptSolution = !trivialSolution && codelength < oldIndexLength - minimumCodelengthImprovement; + // Force at least one modular level! + bool acceptByForce = !acceptSolution && !haveModules(); + if (acceptByForce) + acceptSolution = true; + + workingHierarchicalCodelength += codelength - oldIndexLength; + + Log(0, 0) << hideIf(!acceptSolution) << ((hierarchicalCodelength - workingHierarchicalCodelength) / hierarchicalCodelength * 100) << "% " << std::flush; + Log(1) << " -> Found " << numActiveModules() << " super modules in " << numEffectiveLoops << " effective loops with hierarchical codelength " << indexCodelength << " + " << (workingHierarchicalCodelength - indexCodelength) << " = " << workingHierarchicalCodelength << (acceptSolution ? "\n" : ", discarding the solution.\n") << std::flush; + Log(1) << oldIndexLength << " -> " << *this << "\n"; + + if (!acceptSolution) { + restoreConsolidatedOptimizationPointIfNoImprovement(true); + break; + } + + // Consolidate the dynamic modules without replacing any existing ones. + consolidateModules(false); + + hierarchicalCodelength = workingHierarchicalCodelength; + oldIndexLength = indexCodelength; + + ++numLevelsCreated; + + m_numNonTrivialTopModules = calculateNumNonTrivialTopModules(); + + } while (m_numNonTrivialTopModules > 1 && numLevelsCreated != superLevelLimit); + + // Restore the temporary transformation of flow to enter flow on super modules + resetFlowOnModules(); + + Log(0, 0) << "to codelength " << io::toPrecision(hierarchicalCodelength) << " in " << numTopModules() << " top modules.\n"; + Log(1) << "Finding super modules done! Added " << numLevelsCreated << " levels with hierarchical codelength " << io::toPrecision(hierarchicalCodelength) << " in " << numTopModules() << " top modules.\n"; + + // Calculate and store hierarchical codelengths + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + + return numLevelsCreated; +} + +unsigned int InfomapBase::findHierarchicalSuperModules(unsigned int superLevelLimit) +{ + if (superLevelLimit == 0) + return 0; + + unsigned int numLevelsCreated = 0; + double oldIndexLength = getIndexCodelength(); + double hierarchicalCodelength = getCodelength(); + double workingHierarchicalCodelength = hierarchicalCodelength; + + if (!haveModules()) + throw std::logic_error("Trying to find hierarchical super modules without any modules"); + + Log(1) << "\nFind hierarchical super modules iteratively...\n"; + Log(0, 0) << "Super-level compression: " << std::setprecision(2) << std::flush; + + // Add index codebooks as long as the code gets shorter + do { + Log(1) << "Iteration " << numLevelsCreated + 1 << ", finding super modules to " << numTopModules() << " modules... \n"; + bool isSilent = false; + if (isMainInfomap()) { + isSilent = Log::isSilent(); + Log::setSilent(true); + } + InfoNode tmp(m_root.data); // Temporary owner for the super Infomap instance + auto& superInfomap = getSuperInfomap(tmp) + .setTwoLevel(true); + superInfomap.initNetwork(m_root, true); //.initSuperNetwork(); + superInfomap.run(); + if (isMainInfomap()) { + Log::setSilent(isSilent); + } + double superCodelength = superInfomap.getCodelength(); + double superIndexCodelength = superInfomap.getIndexCodelength(); + + unsigned int numSuperModules = superInfomap.numTopModules(); + bool trivialSolution = numSuperModules == 1 || numSuperModules == numTopModules(); + + if (trivialSolution) { + Log(1) << "failed to find non-trivial super modules.\n"; + break; + } + + bool improvedCodelength = superCodelength < oldIndexLength - minimumCodelengthImprovement; + if (!improvedCodelength) { + Log(1) << "two-level index codebook not improved over one-level.\n"; + break; + } + + workingHierarchicalCodelength += superCodelength - oldIndexLength; + + Log(0, 0) << ((hierarchicalCodelength - workingHierarchicalCodelength) + / hierarchicalCodelength * 100) + << "% " << std::flush; + Log(1) << " -> Found " << numSuperModules << " super modules in with hierarchical codelength " << superIndexCodelength << " + " << (superCodelength - superIndexCodelength) << " + " << (workingHierarchicalCodelength - superCodelength) << " = " << workingHierarchicalCodelength << '\n'; + + // Merge current top modules to two-level solution found in the super Infomap instance. + std::vector modules(numTopModules()); + unsigned int iModule = 0; + for (InfoNode* n : superInfomap.leafNodes()) { + modules[iModule++] = n->index; + } + + setActiveNetworkFromChildrenOfRoot(); + initPartition(); + // Move top modules to super modules + moveActiveNodesToPredefinedModules(modules); + + // Consolidate the dynamic modules without replacing any existing ones. + consolidateModules(false); + + Log(1) << "Consolidated " << numTopModules() << " super modules. Index codelength: " << oldIndexLength << " -> " << *this << "\n"; + + hierarchicalCodelength = workingHierarchicalCodelength; + oldIndexLength = superIndexCodelength; + + ++numLevelsCreated; + + m_numNonTrivialTopModules = calculateNumNonTrivialTopModules(); + + } while (m_numNonTrivialTopModules > 1 && numLevelsCreated != superLevelLimit); + + // Restore the temporary transformation of flow to enter flow on super modules + resetFlowOnModules(); + + Log(0, 0) << "to codelength " << io::toPrecision(hierarchicalCodelength) << " in " << numTopModules() << " top modules.\n"; + Log(1) << "Finding super modules done! Added " << numLevelsCreated << " levels with hierarchical codelength " << io::toPrecision(hierarchicalCodelength) << " in " << numTopModules() << " top modules.\n"; + + // Calculate and store hierarchical codelengths + m_hierarchicalCodelength = calcCodelengthOnTree(root(), true); + + return numLevelsCreated; +} + +void InfomapBase::transformNodeFlowToEnterFlow(InfoNode& parent) +{ + double sumFlow = 0.0; + for (auto& module : m_root) { + module.data.flow = module.data.enterFlow; + sumFlow += module.data.flow; + } + parent.data.flow = sumFlow; +} + +void InfomapBase::resetFlowOnModules() +{ + // Reset flow on all modules + for (auto& module : m_root.infomapTree()) { + if (!module.isLeaf()) + module.data.flow = 0.0; + } + // Aggregate flow from leaf nodes up in the tree + for (InfoNode* n : m_leafNodes) { + double leafNodeFlow = n->data.flow; + InfoNode* module = n->parent; + do { + module->data.flow += leafNodeFlow; + module = module->parent; + } while (module != nullptr); + } +} + +unsigned int InfomapBase::removeModules() +{ + unsigned int numLevelsDeleted = 0; + while (numLevels() > 1) { + m_root.replaceChildrenWithGrandChildren(); + ++numLevelsDeleted; + } + if (numLevelsDeleted > 0) { + Log(2) << "Removed " << numLevelsDeleted << " levels of modules.\n"; + } + return numLevelsDeleted; +} + +unsigned int InfomapBase::removeSubModules(bool recalculateCodelengthOnTree) +{ + unsigned int numLevelsDeleted = 0; + while (numLevels() > 2) { + for (InfoNode& module : m_root) + module.replaceChildrenWithGrandChildren(); + ++numLevelsDeleted; + } + if (numLevelsDeleted > 0) { + Log(2) << "Removed " << numLevelsDeleted << " levels of sub-modules.\n"; + if (recalculateCodelengthOnTree) + calcCodelengthOnTree(root(), false); + } + return numLevelsDeleted; +} + +unsigned int InfomapBase::recursivePartition() +{ + double indexCodelength = getIndexCodelength(); + double hierarchicalCodelength = m_hierarchicalCodelength; + + Log(0, 0) << "\nRecursive sub-structure compression: " << std::flush; + + PartitionQueue partitionQueue; + if (fastHierarchicalSolution > 0) { + queueLeafModules(partitionQueue); + Log(1) << "\nFind sub modules recursively from " << partitionQueue.size() << " sub modules on level " << numLevels() - 2; + } else { + queueTopModules(partitionQueue); + Log(1) << "\nFind sub modules recursively from " << partitionQueue.size() << " top modules"; + double moduleCodelength = 0.0; + for (auto& module : m_root) + moduleCodelength += module.codelength; + hierarchicalCodelength = indexCodelength + moduleCodelength; + } + Log(1) << " with codelength: " << indexCodelength << " + " << (hierarchicalCodelength - indexCodelength) << " = " << io::toPrecision(hierarchicalCodelength) << '\n'; + + double sumConsolidatedCodelength = hierarchicalCodelength - partitionQueue.moduleCodelength; + + bool isSilent = false; + if (isMainInfomap()) { + isSilent = Log::isSilent(); + } + + while (partitionQueue.size() > 0) { + Log(1) << "Level " << partitionQueue.level << ": " << (partitionQueue.flow * 100) << "% of the flow in " << partitionQueue.size() << " modules. Partitioning... " << std::setprecision(6) << std::flush; + + if (isMainInfomap()) + Log::setSilent(true); + + // Partition all modules in the queue and fill up the next level queue + PartitionQueue nextLevelQueue; + processPartitionQueue(partitionQueue, nextLevelQueue); + + if (isMainInfomap()) + Log::setSilent(isSilent); + + double leftToImprove = partitionQueue.moduleCodelength; + sumConsolidatedCodelength += partitionQueue.indexCodelength + partitionQueue.leafCodelength; + double limitCodelength = sumConsolidatedCodelength + leftToImprove; + + Log(0, 0) << ((hierarchicalCodelength - limitCodelength) / hierarchicalCodelength) * 100 << "% " << std::flush; + Log(1) << "done! Codelength: " << partitionQueue.indexCodelength << " + " << partitionQueue.leafCodelength << " (+ " << leftToImprove << " left to improve)" + << " -> limit: " << io::toPrecision(limitCodelength) << " bits.\n"; + + hierarchicalCodelength = limitCodelength; + + partitionQueue.swap(nextLevelQueue); + } + + // Store resulting hierarchical codelength + m_hierarchicalCodelength = hierarchicalCodelength; + + Log(0, 0) << ". Found " << partitionQueue.level << " levels with codelength " << io::toPrecision(hierarchicalCodelength) << "\n"; + Log(1) << " -> Found " << partitionQueue.level << " levels with codelength " << io::toPrecision(hierarchicalCodelength) << "\n"; + + return partitionQueue.level; +} + +void InfomapBase::queueTopModules(PartitionQueue& partitionQueue) +{ + // Add modules to partition queue + unsigned int numNonTrivialModules = 0; + partitionQueue.resize(numTopModules()); + double sumFlow = 0.0; + double sumNonTrivialFlow = 0.0; + double sumModuleCodelength = 0.0; + unsigned int moduleIndex = 0; + for (auto& module : m_root) { + partitionQueue[moduleIndex] = &module; + sumFlow += module.data.flow; + sumModuleCodelength += module.codelength; + if (module.childDegree() > 1) { + ++numNonTrivialModules; + sumNonTrivialFlow += module.data.flow; + } + ++moduleIndex; + } + partitionQueue.flow = sumFlow; + partitionQueue.numNonTrivialModules = numNonTrivialModules; + partitionQueue.nonTrivialFlow = sumNonTrivialFlow; + partitionQueue.indexCodelength = getIndexCodelength(); + partitionQueue.moduleCodelength = sumModuleCodelength; + partitionQueue.level = 1; +} + +void InfomapBase::queueLeafModules(PartitionQueue& partitionQueue) +{ + unsigned int numLeafModules = 0; + for (auto& node : m_root.infomapTree()) { + if (node.isLeafModule()) + ++numLeafModules; + } + + // Add modules to partition queue + partitionQueue.resize(numLeafModules); + unsigned int numNonTrivialModules = 0; + double sumFlow = 0.0; + double sumNonTrivialFlow = 0.0; + double sumModuleCodelength = 0.0; + unsigned int moduleIndex = 0; + unsigned int maxDepth = 0; + for (auto it = m_root.begin_tree(); !it.isEnd(); ++it) { + if (it->isLeafModule()) { + auto& module = *it; + partitionQueue[moduleIndex] = it.current(); + sumFlow += module.data.flow; + sumModuleCodelength += module.codelength; + if (module.childDegree() > 1) { + ++numNonTrivialModules; + sumNonTrivialFlow += module.data.flow; + } + maxDepth = std::max(maxDepth, it.depth()); + ++moduleIndex; + } + } + partitionQueue.flow = sumFlow; + partitionQueue.numNonTrivialModules = numNonTrivialModules; + partitionQueue.nonTrivialFlow = sumNonTrivialFlow; + partitionQueue.indexCodelength = getIndexCodelength(); + partitionQueue.moduleCodelength = sumModuleCodelength; + partitionQueue.level = maxDepth; +} + +bool InfomapBase::processPartitionQueue(PartitionQueue& queue, PartitionQueue& nextLevelQueue) const +{ + PartitionQueue::size_t numModules = queue.size(); + std::vector indexCodelengths(numModules, 0.0); + std::vector moduleCodelengths(numModules, 0.0); + std::vector leafCodelengths(numModules, 0.0); + std::vector subQueues(numModules); + +#pragma omp parallel for schedule(dynamic) + for (PartitionQueue::size_t moduleIndex = 0; moduleIndex < numModules; ++moduleIndex) { + InfoNode& module = *queue[moduleIndex]; + + module.codelength = calcCodelength(module); + // Delete former sub-structure if exists + if (module.disposeInfomap()) + module.codelength = calcCodelength(module); + + // If only trivial substructure is to be found, no need to create infomap instance to find sub-module structures. + if (module.childDegree() <= 2) { + module.codelength = calcCodelength(module); + leafCodelengths[moduleIndex] = module.codelength; + continue; + } + + double oldModuleCodelength = module.codelength; + PartitionQueue& subQueue = subQueues[moduleIndex]; + subQueue.level = queue.level + 1; + + auto& subInfomap = getSubInfomap(module) + .initNetwork(module); + // Run two-level partition + find hierarchically super modules (skip recursion) + subInfomap.setOnlySuperModules(true).run(); + + double subCodelength = subInfomap.getHierarchicalCodelength(); + double subIndexCodelength = subInfomap.root().codelength; + double subModuleCodelength = subCodelength - subIndexCodelength; + InfoNode& subRoot = *module.getInfomapRoot(); + unsigned int numSubModules = subRoot.childDegree(); + bool trivialSubPartition = numSubModules == 1 || numSubModules == module.childDegree(); + bool improvedCodelength = subCodelength < oldModuleCodelength - minimumCodelengthImprovement; + + if (trivialSubPartition || !improvedCodelength) { + Log(1) << "Disposing unaccepted sub Infomap instance.\n"; + module.disposeInfomap(); + module.codelength = oldModuleCodelength; + subQueue.skip = true; + leafCodelengths[moduleIndex] = module.codelength; + } else { + // Improvement + subInfomap.queueTopModules(subQueue); + indexCodelengths[moduleIndex] = subIndexCodelength; + moduleCodelengths[moduleIndex] = subModuleCodelength; + } + } + + double sumLeafCodelength = 0.0; + double sumIndexCodelength = 0.0; + double sumModuleCodelengths = 0.0; + PartitionQueue::size_t nextLevelSize = 0; + for (PartitionQueue::size_t moduleIndex = 0; moduleIndex < numModules; ++moduleIndex) { + nextLevelSize += subQueues[moduleIndex].skip ? 0 : subQueues[moduleIndex].size(); + sumLeafCodelength += leafCodelengths[moduleIndex]; + sumIndexCodelength += indexCodelengths[moduleIndex]; + sumModuleCodelengths += moduleCodelengths[moduleIndex]; + } + + queue.indexCodelength = sumIndexCodelength; + queue.leafCodelength = sumLeafCodelength; + queue.moduleCodelength = sumModuleCodelengths; + + // Collect the sub-queues and build the next-level queue + nextLevelQueue.level = queue.level + 1; + nextLevelQueue.resize(nextLevelSize); + PartitionQueue::size_t nextLevelIndex = 0; + for (PartitionQueue::size_t moduleIndex = 0; moduleIndex < numModules; ++moduleIndex) { + PartitionQueue& subQueue = subQueues[moduleIndex]; + if (!subQueue.skip) { + for (PartitionQueue::size_t subIndex = 0; subIndex < subQueue.size(); ++subIndex) { + nextLevelQueue[nextLevelIndex++] = subQueue[subIndex]; + } + nextLevelQueue.flow += subQueue.flow; + nextLevelQueue.nonTrivialFlow += subQueue.nonTrivialFlow; + nextLevelQueue.numNonTrivialModules += subQueue.numNonTrivialModules; + } + } + + return nextLevelSize > 0; +} + +// =================================================== +// Write output +// =================================================== + +void InfomapBase::writeResult(int trial) +{ + if (noFileOutput) + return; + + io::Str s; + s << outDirectory + outName; + + if (printAllTrials && trial != -1 && numTrials > 1) { + s << "_trial_" << trial; + } + + std::string basename = s; + + if (printTree) { + std::string filename = basename + ".tree"; + + if (!printStates()) { + Log() << "Write tree to " << filename << "... "; + writeTree(filename); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical tree to " << filename << "... "; + writeTree(filename); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.tree"; + Log() << "Write state tree to " << filenameStates << "... "; + writeTree(filenameStates, true); + Log() << "done!\n"; + } + } + + if (printFlowTree) { + std::string filename = basename + ".ftree"; + + if (!printStates()) { + Log() << "Write flow tree to " << filename << "... "; + writeFlowTree(filename); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical flow tree to " << filename << "... "; + writeFlowTree(filename, false); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.ftree"; + Log() << "Write state flow tree to " << filenameStates << "... "; + writeFlowTree(filenameStates, true); + Log() << "done!\n"; + } + } + + if (printNewick) { + std::string filename = basename + ".nwk"; + + if (!printStates()) { + Log() << "Write Newick tree to " << filename << "... "; + writeNewickTree(filename); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical Newick tree to " << filename << "... "; + writeNewickTree(filename, false); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.nwk"; + Log() << "Write state Newick tree to " << filenameStates << "... "; + writeNewickTree(filenameStates, true); + Log() << "done!\n"; + } + } + + if (printJson) { + std::string filename = basename + ".json"; + const bool writeLinks = false; + + if (!printStates()) { + Log() << "Write JSON tree to " << filename << "... "; + writeJsonTree(filename, false, writeLinks); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical JSON tree to " << filename << "... "; + writeJsonTree(filename, false, writeLinks); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.json"; + Log() << "Write state JSON tree to " << filenameStates << "... "; + writeJsonTree(filenameStates, true, writeLinks); + Log() << "done!\n"; + } + } + + if (printCsv) { + std::string filename = basename + ".csv"; + + if (!printStates()) { + Log() << "Write CSV tree to " << filename << "... "; + writeCsvTree(filename); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical CSV tree to " << filename << "... "; + writeCsvTree(filename, false); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.csv"; + Log() << "Write state CSV tree to " << filenameStates << "... "; + writeCsvTree(filenameStates, true); + Log() << "done!\n"; + } + } + + if (printClu) { + std::string filename = basename + ".clu"; + if (!printStates()) { + Log() << "Write node modules to " << filename << "... "; + writeClu(filename, false, cluLevel); + Log() << "done!\n"; + } else { + // Write both physical and state level + Log() << "Write physical node modules to " << filename << "... "; + writeClu(filename, false, cluLevel); + Log() << "done!\n"; + std::string filenameStates = basename + "_states.clu"; + Log() << "Write state node modules to " << filenameStates << "... "; + writeClu(filenameStates, true, cluLevel); + Log() << "done!\n"; + } + } +} + +unsigned int printPerLevelCodelength(const InfoNode& parent, std::ostream& out) +{ + std::vector perLevelStats; + aggregatePerLevelCodelength(parent, perLevelStats); + + unsigned int numLevels = perLevelStats.size(); + + out << "Per level number of modules: ["; + for (unsigned int i = 0; i < numLevels - 1; ++i) { + out << io::padValue(perLevelStats[i].numModules, 11) << ", "; + } + out << io::padValue(perLevelStats[numLevels - 1].numModules, 11) << "]"; + unsigned int sumNumModules = 0; + for (unsigned int i = 0; i < numLevels; ++i) + sumNumModules += perLevelStats[i].numModules; + out << " (sum: " << sumNumModules << ")\n"; + + out << "Per level number of leaf nodes: ["; + for (unsigned int i = 0; i < numLevels - 1; ++i) { + out << io::padValue(perLevelStats[i].numLeafNodes, 11) << ", "; + } + out << io::padValue(perLevelStats[numLevels - 1].numLeafNodes, 11) << "]"; + unsigned int sumNumLeafNodes = 0; + for (unsigned int i = 0; i < numLevels; ++i) + sumNumLeafNodes += perLevelStats[i].numLeafNodes; + out << " (sum: " << sumNumLeafNodes << ")\n"; + + out << "Per level average child degree: ["; + double childDegree = perLevelStats[0].numNodes(); + double sumAverageChildDegree = childDegree * childDegree; + if (numLevels > 1) { + out << io::padValue(perLevelStats[0].numModules, 11) << ", "; + } + for (unsigned int i = 1; i < numLevels - 1; ++i) { + childDegree = perLevelStats[i].numNodes() * 1.0 / perLevelStats[i - 1].numModules; + sumAverageChildDegree += childDegree * perLevelStats[i].numNodes(); + out << io::padValue(childDegree, 11) << ", "; + } + if (numLevels > 1) { + childDegree = perLevelStats[numLevels - 1].numNodes() * 1.0 / perLevelStats[numLevels - 2].numModules; + sumAverageChildDegree += childDegree * perLevelStats[numLevels - 1].numNodes(); + } + out << io::padValue(childDegree, 11) << "]"; + out << " (average: " << sumAverageChildDegree / (sumNumModules + sumNumLeafNodes) << ")\n"; + + out << std::fixed << std::setprecision(9); + out << "Per level codelength for modules: ["; + for (unsigned int i = 0; i < numLevels - 1; ++i) { + out << perLevelStats[i].indexLength << ", "; + } + out << perLevelStats[numLevels - 1].indexLength << "]"; + double sumIndexLengths = 0.0; + for (unsigned int i = 0; i < numLevels; ++i) + sumIndexLengths += perLevelStats[i].indexLength; + out << " (sum: " << sumIndexLengths << ")\n"; + + out << "Per level codelength for leaf nodes: ["; + for (unsigned int i = 0; i < numLevels - 1; ++i) { + out << perLevelStats[i].leafLength << ", "; + } + out << perLevelStats[numLevels - 1].leafLength << "]"; + + double sumLeafLengths = 0.0; + for (unsigned int i = 0; i < numLevels; ++i) + sumLeafLengths += perLevelStats[i].leafLength; + out << " (sum: " << sumLeafLengths << ")\n"; + + out << "Per level codelength total: ["; + for (unsigned int i = 0; i < numLevels - 1; ++i) { + out << perLevelStats[i].codelength() << ", "; + } + out << perLevelStats[numLevels - 1].codelength() << "]"; + + double sumCodelengths = 0.0; + for (unsigned int i = 0; i < numLevels; ++i) + sumCodelengths += perLevelStats[i].codelength(); + out << " (sum: " << sumCodelengths << ")\n"; + + return numLevels; +} + +void aggregatePerLevelCodelength(const InfoNode& parent, std::vector& perLevelStat, unsigned int level) +{ + if (perLevelStat.size() < level + 1) + perLevelStat.resize(level + 1); + + if (parent.firstChild->isLeaf()) { + perLevelStat[level].numLeafNodes += parent.childDegree(); + perLevelStat[level].leafLength += parent.codelength; + return; + } + + perLevelStat[level].numModules += parent.childDegree(); + perLevelStat[level].indexLength += parent.codelength; + + for (auto& module : parent) { + if (module.getInfomapRoot() != nullptr) + aggregatePerLevelCodelength(*module.getInfomapRoot(), perLevelStat, level + 1); + else + aggregatePerLevelCodelength(module, perLevelStat, level + 1); + } +} + +void InfomapBase::initOptimizer(bool forceNoMemory) +{ + if (haveMetaData()) { + m_optimizer = std::make_unique>(); + } else if (haveMemory() && !forceNoMemory) { + m_optimizer = std::make_unique>(); + } else { + m_optimizer = std::make_unique>(); + } + m_optimizer->init(this); +} + +} // namespace infomap diff --git a/vendor/infomap/src/core/InfomapBase.h b/vendor/infomap/src/core/InfomapBase.h new file mode 100644 index 0000000..4c8a132 --- /dev/null +++ b/vendor/infomap/src/core/InfomapBase.h @@ -0,0 +1,586 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_BASE_H_ +#define INFOMAP_BASE_H_ + +#include "InfomapConfig.h" +#include "InfoEdge.h" +#include "InfoNode.h" +#include "InfomapOptimizerBase.h" +#include "iterators/InfomapIterator.h" +#include "../io/ClusterMap.h" +#include "../io/Network.h" +#include "../io/Output.h" +#include "../utils/Log.h" +#include "../utils/Date.h" +#include "../utils/Stopwatch.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace infomap { + +namespace detail { + class PartitionQueue; + struct PerLevelStat; +} // namespace detail + +class InfomapBase : public InfomapConfig { + template + friend class InfomapOptimizer; + + void initOptimizer(bool forceNoMemory = false); + +public: + using PartitionQueue = detail::PartitionQueue; + + InfomapBase() : InfomapConfig() { initOptimizer(); } + + explicit InfomapBase(const Config& conf) : InfomapConfig(conf), m_network(conf) { initOptimizer(); } + + explicit InfomapBase(const std::string& flags, bool isCli = false) : InfomapConfig(flags, isCli) + { + initOptimizer(); + m_network.setConfig(*this); + m_initialParameters = m_currentParameters = flags; + } + + virtual ~InfomapBase() = default; + + // =================================================== + // Iterators + // =================================================== + + InfomapIterator iterTree(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapIteratorPhysical iterTreePhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapModuleIterator iterModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapLeafModuleIterator iterLeafModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapLeafIterator iterLeafNodes(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapLeafIteratorPhysical iterLeafNodesPhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapIterator begin(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; } + + InfomapIterator end() const { return InfomapIterator(nullptr); } + + // =================================================== + // Getters + // =================================================== + + Network& network() { return m_network; } + const Network& network() const { return m_network; } + + InfoNode& root() { return m_root; } + const InfoNode& root() const { return m_root; } + + unsigned int numLeafNodes() const { return m_leafNodes.size(); } + + const std::vector& leafNodes() const { return m_leafNodes; } + + unsigned int numTopModules() const { return m_root.childDegree(); } + + unsigned int numActiveModules() const { return m_optimizer->numActiveModules(); } + + unsigned int numNonTrivialTopModules() const { return m_numNonTrivialTopModules; } + + bool haveModules() const { return !m_root.isLeaf() && !m_root.firstChild->isLeaf(); } + + bool haveNonTrivialModules() const { return numNonTrivialTopModules() > 0; } + + /** + * Number of node levels below the root in current Infomap instance, 1 if no modules + */ + unsigned int numLevels() const; + + /** + * Get maximum depth of any child in the tree, following possible sub Infomap instances + */ + unsigned int maxTreeDepth() const; + + double getCodelength() const { return m_optimizer->getCodelength(); } + + double getMetaCodelength(bool unweighted = false) const { return m_optimizer->getMetaCodelength(unweighted); } + + double codelength() const { return m_hierarchicalCodelength; } + + const std::vector& codelengths() const { return m_codelengths; } + + double getIndexCodelength() const { return m_optimizer->getIndexCodelength(); } + + double getModuleCodelength() const { return m_hierarchicalCodelength - m_optimizer->getIndexCodelength(); } + + double getHierarchicalCodelength() const { return m_hierarchicalCodelength; } + + double getOneLevelCodelength() const { return m_oneLevelCodelength; } + + double getRelativeCodelengthSavings() const + { + auto oneLevelCodelength = getOneLevelCodelength(); + return oneLevelCodelength < 1e-16 ? 0 : 1.0 - codelength() / oneLevelCodelength; + } + + double getEntropyRate() { return m_entropyRate; } + double getMaxEntropy() { return m_maxEntropy; } + double getMaxFlow() { return m_maxFlow; } + + const Date& getStartDate() const { return m_startDate; } + const Stopwatch& getElapsedTime() const { return m_elapsedTime; } + + std::vector& activeNetwork() const { return *m_activeNetwork; } + + std::map> getMultilevelModules(bool states = false); + + // =================================================== + // IO + // =================================================== + + std::ostream& toString(std::ostream& out) const { return m_optimizer->toString(out); } + + // =================================================== + // Run + // =================================================== + + using InitialPartition = std::map; + + const InitialPartition& getInitialPartition() const { return m_initialPartition; } + + InfomapBase& setInitialPartition(const InitialPartition& moduleIds) + { + m_initialPartition = moduleIds; + return *this; + } + + void run(const std::string& parameters = ""); + + void run(Network& network); + +private: + bool isFullNetwork() const { return m_isMain && m_aggregationLevel == 0; } + bool isFirstLoop() const { return m_tuneIterationIndex == 0 && isFullNetwork(); } + + InfomapBase* getNewInfomapInstance() const { return new InfomapBase(getConfig()); } + InfomapBase* getNewInfomapInstanceWithoutMemory() const + { + auto im = new InfomapBase(); + im->initOptimizer(true); + return im; + } + + InfomapBase& getSubInfomap(InfoNode& node) const + { + return node.setInfomap(getNewInfomapInstance()) + .setIsMain(false) + .setSubLevel(m_subLevel + 1) + .setNonMainConfig(*this); + } + + InfomapBase& getSuperInfomap(InfoNode& node) const + { + return node.setInfomap(getNewInfomapInstanceWithoutMemory()) + .setIsMain(false) + .setSubLevel(m_subLevel + SUPER_LEVEL_ADDITION) + .setNonMainConfig(*this); + } + + /** + * Only the main infomap reads an external cluster file if exist + */ + InfomapBase& setIsMain(bool isMain) + { + m_isMain = isMain; + return *this; + } + + InfomapBase& setSubLevel(unsigned int level) + { + m_subLevel = level; + return *this; + } + + bool isTopLevel() const { return (m_subLevel & (SUPER_LEVEL_ADDITION - 1)) == 0; } + + bool isSuperLevelOnTopLevel() const { return m_subLevel == SUPER_LEVEL_ADDITION; } + + bool isMainInfomap() const { return m_isMain; } + + bool haveHardPartition() const { return !m_originalLeafNodes.empty(); } + + // =================================================== + // Run: * + // =================================================== + + InfomapBase& initNetwork(Network& network); + InfomapBase& initNetwork(InfoNode& parent, bool asSuperNetwork = false); + + void generateSubNetwork(Network& network); + void generateSubNetwork(InfoNode& parent); + + /** + * Init categorical meta data on all nodes from a file with the following format: + * # nodeId metaData + * 1 1 + * 2 1 + * 3 2 + * 4 2 + * 5 3 + * + */ + InfomapBase& initMetaData(const std::string& metaDataFile); + + /** + * Provide an initial partition of the network. + * + * @param clusterDataFile A .clu file containing cluster data. + * @param hard If true, the provided clusters will not be splitted. This reduces the + * effective network size during the optimization phase but the hard partitions are + * after that replaced by the original nodes. + */ + InfomapBase& initPartition(const std::string& clusterDataFile, bool hard = false, const Network* network = nullptr); + + /** + * Provide an initial partition of the network. + * + * @param clusterIds map from nodeId to clusterId, doesn't have to be complete + * @param hard If true, the provided clusters will not be splitted. This reduces the + * effective network size during the optimization phase but the hard partitions are + * after that replaced by the original nodes. + */ + InfomapBase& initPartition(const std::map& clusterIds, bool hard = false); + + /** + * Provide an initial partition of the network. + * + * @param clusters Each sub-vector contain node IDs for all nodes that should be merged. + * @param hard If true, the provided clusters will not be splitted. This reduces the + * effective network size during the optimization phase but the hard partitions are + * after that replaced by the original nodes. + */ + InfomapBase& initPartition(std::vector>& clusters, bool hard = false); + + /** + * Provide an initial partition of the network. + * + * @param modules Module indices for each node + */ + InfomapBase& initPartition(std::vector& modules, bool hard = false); + + /** + * Provide an initial hierarchical partition of the network + * + * @param tree A tree path for each node + */ + InfomapBase& initTree(const NodePaths& tree); + + void init(); + + void runPartition() + { + if (twoLevel) + partition(); + else + hierarchicalPartition(); + } + + void restoreHardPartition(); + + void writeResult(int trial = -1); + + // =================================================== + // runPartition: * + // =================================================== + + void hierarchicalPartition(); + + void partition(); + + // =================================================== + // runPartition: init: * + // =================================================== + + /** + * Done in network? + */ + void initEnterExitFlow(); + + void aggregateFlowValuesFromLeafToRoot(); + + // Init terms that is constant for the whole network + void initTree() { return m_optimizer->initTree(); } + + void initNetwork() { return m_optimizer->initNetwork(); } + + void initSuperNetwork() { return m_optimizer->initSuperNetwork(); } + + double calcCodelength(const InfoNode& parent) const { return m_optimizer->calcCodelength(parent); } + + /** + * Calculate and store codelength on all modules in the tree + * @param includeRoot Also calculate the codelength on the root node + * @return the hierarchical codelength + */ + double calcCodelengthOnTree(InfoNode& root, bool includeRoot = true) const; + + // =================================================== + // Run: Partition: * + // =================================================== + + void setActiveNetworkFromLeafs() { m_activeNetwork = &m_leafNodes; } + + void setActiveNetworkFromChildrenOfRoot(); + + void initPartition() { return m_optimizer->initPartition(); } + + void findTopModulesRepeatedly(unsigned int maxLevels); + + unsigned int fineTune(); + + unsigned int coarseTune(); + + /** + * Return the number of effective core loops, i.e. not the last if not at coreLoopLimit + */ + unsigned int optimizeActiveNetwork() { return m_optimizer->optimizeActiveNetwork(); } + + void moveActiveNodesToPredefinedModules(std::vector& modules) + { + return m_optimizer->moveActiveNodesToPredefinedModules(modules); + } + + void consolidateModules(bool replaceExistingModules = true) + { + return m_optimizer->consolidateModules(replaceExistingModules); + } + + unsigned int calculateNumNonTrivialTopModules() const; + + unsigned int calculateMaxDepth() const; + + // =================================================== + // Partition: findTopModulesRepeatedly: * + // =================================================== + + /** + * Return true if restored to consolidated optimization state + */ + bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) + { + return m_optimizer->restoreConsolidatedOptimizationPointIfNoImprovement(forceRestore); + } + + // =================================================== + // Run: Hierarchical Partition: * + // =================================================== + + /** + * Find super modules applying the whole two-level algorithm on the + * top modules iteratively + * @param levelLimit The maximum number of super module levels allowed + * @return number of levels created + */ + unsigned int findHierarchicalSuperModules(unsigned int superLevelLimit = std::numeric_limits::max()); + + /** + * Find super modules fast by merge and consolidate top modules iteratively + * @param levelLimit The maximum number of super module levels allowed + * @return number of levels created + */ + unsigned int findHierarchicalSuperModulesFast(unsigned int superLevelLimit = std::numeric_limits::max()); + + void transformNodeFlowToEnterFlow(InfoNode& parent); + + void resetFlowOnModules(); + + unsigned int removeModules(); + + unsigned int removeSubModules(bool recalculateCodelengthOnTree); + + unsigned int recursivePartition(); + + void queueTopModules(PartitionQueue& partitionQueue); + + void queueLeafModules(PartitionQueue& partitionQueue); + + bool processPartitionQueue(PartitionQueue& queue, PartitionQueue& nextLevel) const; + +public: + // =================================================== + // Output: * + // =================================================== + + /** + * Write tree to a .tree file. + * @param filename the filename for the output file. If empty, use default + * based on output directory and input file name + * @param states if memory network, print the state-level network without merging physical nodes within modules + * @return the filename written to + */ + std::string writeTree(const std::string& filename = "", bool states = false) { return infomap::writeTree(*this, m_network, filename, states); } + + /** + * Write flow tree to a .ftree file. + * This is the same as a .tree file but appended with links aggregated + * within modules on all levels in the tree + * @param filename the filename for the output file. If empty, use default + * based on output directory and input file name + * @param states if memory network, print the state-level network without merging physical nodes within modules + * @return the filename written to + */ + std::string writeFlowTree(const std::string& filename = "", bool states = false) { return infomap::writeFlowTree(*this, m_network, filename, states); } + + /** + * Write Newick tree to a .tre file. + * @param filename the filename for the output file. If empty, use default + * based on output directory and input file name + * @param states if memory network, print the state-level network without merging physical nodes within modules + * @return the filename written to + */ + std::string writeNewickTree(const std::string& filename = "", bool states = false) { return infomap::writeNewickTree(*this, filename, states); } + + std::string writeJsonTree(const std::string& filename = "", bool states = false, bool writeLinks = false) { return infomap::writeJsonTree(*this, m_network, filename, states, writeLinks); } + + std::string writeCsvTree(const std::string& filename = "", bool states = false) { return infomap::writeCsvTree(*this, m_network, filename, states); } + + /** + * Write tree to a .clu file. + * @param filename the filename for the output file. If empty, use default + * based on output directory and input file name + * @param states if memory network, print the state-level network without merging physical nodes within modules + * @param moduleIndexLevel the depth from the root on which to advance module index. + * Value 1 (default) will give the module index on the coarsest level, 2 the level below and so on. + * Value -1 will give the module index for the lowest level, i.e. the finest modular structure. + * @return the filename written to + */ + std::string writeClu(const std::string& filename = "", bool states = false, int moduleIndexLevel = 1) { return infomap::writeClu(*this, m_network, filename, states, moduleIndexLevel); } + +private: + +#if 0 + // =================================================== + // Debug: * + // =================================================== + + void printDebug() const { return m_optimizer->printDebug(); } +#endif + + // =================================================== + // Members + // =================================================== + +protected: + InfoNode m_root; + std::vector m_leafNodes; + std::vector m_moduleNodes; + std::vector* m_activeNetwork = nullptr; + + std::vector m_originalLeafNodes; + + Network m_network; + InitialPartition m_initialPartition = {}; // nodeId -> moduleId + + const unsigned int SUPER_LEVEL_ADDITION = 1 << 20; + bool m_isMain = true; + unsigned int m_subLevel = 0; + + bool m_calculateEnterExitFlow = false; + + double m_oneLevelCodelength = 0.0; + unsigned int m_numNonTrivialTopModules = 0; + unsigned int m_tuneIterationIndex = 0; + bool m_isCoarseTune = false; + unsigned int m_aggregationLevel = 0; + + double m_hierarchicalCodelength = 0.0; + std::vector m_codelengths; + double m_entropyRate = 0.0; + double m_maxEntropy = 0.0; + double m_maxFlow = 0.0; + + double m_sumDanglingFlow = 0.0; + + Date m_startDate; + Date m_endDate; + Stopwatch m_elapsedTime = Stopwatch(false); + std::string m_initialParameters; + std::string m_currentParameters; + + std::unique_ptr m_optimizer; +}; + +/** + * Print per level statistics + */ +unsigned int printPerLevelCodelength(const InfoNode& parent, std::ostream& out); + +void aggregatePerLevelCodelength(const InfoNode& parent, std::vector& perLevelStat, unsigned int level = 0); + +namespace detail { + + struct PerLevelStat { + double codelength() const { return indexLength + leafLength; } + + unsigned int numNodes() const { return numModules + numLeafNodes; } + + unsigned int numModules = 0; + unsigned int numLeafNodes = 0; + double indexLength = 0.0; + double leafLength = 0.0; + }; + + class PartitionQueue { + using PendingModule = InfoNode*; + + std::deque m_queue; + + public: + unsigned int level = 1; + unsigned int numNonTrivialModules = 0; + double flow = 0.0; + double nonTrivialFlow = 0.0; + bool skip = false; + double indexCodelength = 0.0; // Consolidated + double leafCodelength = 0.0; // Consolidated + double moduleCodelength = 0.0; // Left to improve on next level + + using size_t = std::deque::size_type; + + void swap(PartitionQueue& other) noexcept + { + std::swap(level, other.level); + std::swap(numNonTrivialModules, other.numNonTrivialModules); + std::swap(flow, other.flow); + std::swap(nonTrivialFlow, other.nonTrivialFlow); + std::swap(skip, other.skip); + std::swap(indexCodelength, other.indexCodelength); + std::swap(leafCodelength, other.leafCodelength); + std::swap(moduleCodelength, other.moduleCodelength); + m_queue.swap(other.m_queue); + } + + size_t size() const { return m_queue.size(); } + + void resize(size_t size) { m_queue.resize(size); } + + PendingModule& operator[](size_t i) { return m_queue[i]; } + }; + +} // namespace detail + +} // namespace infomap + +#endif // INFOMAP_BASE_H_ diff --git a/vendor/infomap/src/core/InfomapConfig.h b/vendor/infomap/src/core/InfomapConfig.h new file mode 100644 index 0000000..e6ff0b8 --- /dev/null +++ b/vendor/infomap/src/core/InfomapConfig.h @@ -0,0 +1,137 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_CONFIG_H_ +#define INFOMAP_CONFIG_H_ + +#include "../io/Config.h" +#include "../utils/Random.h" +#include "../utils/Log.h" +#include + +namespace infomap { + +template +class InfomapConfig : public Config { +public: + InfomapConfig() = default; + + InfomapConfig(const std::string& flags, bool isCli = false) : InfomapConfig(Config(flags, isCli)) { } + + InfomapConfig(const Config& conf) : Config(conf), m_rand(/* conf.seedToRandomNumberGenerator */) + { + Log::precision(conf.verboseNumberPrecision); + } + + virtual ~InfomapConfig() = default; + + InfomapConfig(const InfomapConfig&) = default; + InfomapConfig& operator=(const InfomapConfig&) = default; + InfomapConfig(InfomapConfig&&) noexcept = default; + InfomapConfig& operator=(InfomapConfig&&) noexcept = default; + +private: + Infomap& get() + { + return static_cast(*this); + } + +protected: + Random m_rand; + +public: + Config& getConfig() + { + return *this; + } + + const Config& getConfig() const + { + return *this; + } + + Infomap& setConfig(const Config& conf) + { + *this = conf; + /* m_rand.seed(conf.seedToRandomNumberGenerator); */ + Log::precision(conf.verboseNumberPrecision); + return get(); + } + + Infomap& setNonMainConfig(const Config& conf) + { + cloneAsNonMain(conf); + return get(); + } + + Infomap& setNumTrials(unsigned int N) + { + numTrials = N; + return get(); + } + + Infomap& setVerbosity(unsigned int level) + { + verbosity = level; + return get(); + } + + Infomap& setTwoLevel(bool value) + { + twoLevel = value; + return get(); + } + + Infomap& setTuneIterationLimit(unsigned int value) + { + tuneIterationLimit = value; + return get(); + } + + Infomap& setFastHierarchicalSolution(unsigned int level) + { + fastHierarchicalSolution = level; + return get(); + } + + Infomap& setOnlySuperModules(bool value) + { + onlySuperModules = value; + return get(); + } + + Infomap& setNoCoarseTune(bool value) + { + noCoarseTune = value; + return get(); + } + + Infomap& setNoInfomap(bool value = true) + { + noInfomap = value; + return get(); + } + + Infomap& setMarkovTime(double codeRate) + { + markovTime = codeRate; + return get(); + } + + Infomap& setDirected(bool value) + { + directed = value; + flowModel = directed ? FlowModel::directed : FlowModel::undirected; + return get(); + } +}; + +} // namespace infomap + +#endif // INFOMAP_CONFIG_H_ diff --git a/vendor/infomap/src/core/InfomapOptimizer.h b/vendor/infomap/src/core/InfomapOptimizer.h new file mode 100644 index 0000000..29c683f --- /dev/null +++ b/vendor/infomap/src/core/InfomapOptimizer.h @@ -0,0 +1,766 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_OPTIMIZER_H_ +#define INFOMAP_OPTIMIZER_H_ + +#include "InfomapOptimizerBase.h" +#include "InfomapBase.h" +#include "../utils/VectorMap.h" +#include "../utils/infomath.h" +#include "InfoNode.h" +#include "FlowData.h" + +#include +#include + +namespace infomap { + +template +class InfomapOptimizer : public InfomapOptimizerBase { + using FlowDataType = FlowData; + using DeltaFlowDataType = typename Objective::DeltaFlowDataType; + +public: + void init(InfomapBase* infomap) override + { + m_infomap = infomap; + m_objective.init(infomap->getConfig()); + this->setInterruptionHandler(infomap->getConfig().interruptionHandler); + } + + // =================================================== + // IO + // =================================================== + + std::ostream& toString(std::ostream& out) const override { return out << m_objective; } + + // =================================================== + // Getters + // =================================================== + + double getCodelength() const override { return m_objective.getCodelength(); } + + double getIndexCodelength() const override { return m_objective.getIndexCodelength(); } + + double getModuleCodelength() const override { return m_objective.getModuleCodelength(); } + + double getMetaCodelength(bool unweighted = false) const override; + +protected: + unsigned int numActiveModules() const override { return m_infomap->activeNetwork().size() - m_emptyModules.size(); } + + // =================================================== + // Run: Init: * + // =================================================== + + // Init terms that is constant for the whole network + void initTree() override; + + void initNetwork() override; + + void initSuperNetwork() override; + + double calcCodelength(const InfoNode& parent) const override { return m_objective.calcCodelength(parent); } + + // =================================================== + // Run: Partition: * + // =================================================== + + void initPartition() override; + + void moveActiveNodesToPredefinedModules(std::vector& modules) override; + + bool moveNodeToPredefinedModule(InfoNode& current, unsigned int module); + + unsigned int optimizeActiveNetwork() override; + + unsigned int tryMoveEachNodeIntoBestModule() override; + + unsigned int tryMoveEachNodeIntoBestModuleInParallel() override; + + void consolidateModules(bool replaceExistingModules = true) override; + + bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) override; + +#if 0 + // =================================================== + // Debug: * + // =================================================== + + void printDebug() override { m_objective.printDebug(); } +#endif + + // =================================================== + // Protected members + // =================================================== + + InfomapBase* m_infomap = nullptr; + Objective m_objective; + Objective m_consolidatedObjective; + std::vector m_moduleFlowData; + std::vector m_moduleMembers; + std::vector m_emptyModules; +}; + +// =================================================== +// Getters +// =================================================== + +template <> +inline double InfomapOptimizer::getMetaCodelength(bool unweighted) const +{ + return m_objective.getMetaCodelength(unweighted); +} + +template +inline double InfomapOptimizer::getMetaCodelength(bool /*unweighted*/) const +{ + return 0.0; +} + +// =================================================== +// Run: Init: * +// =================================================== + +template +inline void InfomapOptimizer::initTree() +{ + Log(4) << "InfomapOptimizer::initTree()...\n"; + m_objective.initTree(m_infomap->root()); +} + +template +inline void InfomapOptimizer::initNetwork() +{ + Log(4) << "InfomapOptimizer::initNetwork()...\n"; + m_objective.initNetwork(m_infomap->root()); + + if (!m_infomap->isMainInfomap()) + m_objective.initSubNetwork(m_infomap->root()); // TODO: Already called in initNetwork? +} + +template +inline void InfomapOptimizer::initSuperNetwork() +{ + Log(4) << "InfomapOptimizer::initSuperNetwork()...\n"; + m_objective.initSuperNetwork(m_infomap->root()); +} + +// =================================================== +// Run: Partition: * +// =================================================== + +template +void InfomapOptimizer::initPartition() +{ + auto& network = m_infomap->activeNetwork(); + Log(4) << "InfomapOptimizer::initPartition() with " << network.size() << " nodes...\n"; + + // Init one module for each node + auto numNodes = network.size(); + m_moduleFlowData.resize(numNodes); + m_moduleMembers.assign(numNodes, 1); + m_emptyModules.clear(); + m_emptyModules.reserve(numNodes); + + unsigned int i = 0; + for (auto& nodePtr : network) { + InfoNode& node = *nodePtr; + node.index = i; // Unique module index for each node + m_moduleFlowData[i] = node.data; + node.dirty = true; + ++i; + } + + m_objective.initPartition(network); +} + +template +void InfomapOptimizer::moveActiveNodesToPredefinedModules(std::vector& modules) +{ + auto& network = m_infomap->activeNetwork(); + auto numNodes = network.size(); + if (modules.size() != numNodes) + throw std::length_error("Size of predefined modules differ from size of active network."); + + for (unsigned int i = 0; i < numNodes; ++i) { + moveNodeToPredefinedModule(*network[i], modules[i]); + } +} + +template +bool InfomapOptimizer::moveNodeToPredefinedModule(InfoNode& current, unsigned int newModule) +{ + unsigned int oldM = current.index; + unsigned int newM = newModule; + + if (newM == oldM) { + return false; + } + + DeltaFlowDataType oldModuleDelta(oldM, 0.0, 0.0); + DeltaFlowDataType newModuleDelta(newM, 0.0, 0.0); + + // For all outlinks + for (auto& e : current.outEdges()) { + auto& edge = *e; + unsigned int otherModule = edge.target->index; + if (otherModule == oldM) { + oldModuleDelta.deltaExit += edge.data.flow; + } else if (otherModule == newM) { + newModuleDelta.deltaExit += edge.data.flow; + } + } + // For all inlinks + for (auto& e : current.inEdges()) { + auto& edge = *e; + unsigned int otherModule = edge.source->index; + if (otherModule == oldM) { + oldModuleDelta.deltaEnter += edge.data.flow; + } else if (otherModule == newM) { + newModuleDelta.deltaEnter += edge.data.flow; + } + } + + // For recorded teleportation + if (m_infomap->recordedTeleportation) { + auto& oldModuleFlowData = m_moduleFlowData[oldM]; + double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight; + double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight); + oldModuleDelta.deltaEnter += deltaEnterOld; + oldModuleDelta.deltaExit += deltaExitOld; + + auto& newModuleFlowData = m_moduleFlowData[newM]; + double deltaEnterNew = current.data.teleportFlow * newModuleFlowData.teleportWeight; + double deltaExitNew = newModuleFlowData.teleportFlow * current.data.teleportWeight; + newModuleDelta.deltaEnter += deltaEnterNew; + newModuleDelta.deltaExit += deltaExitNew; + } + // Update empty module vector + if (m_moduleMembers[newM] == 0) { + m_emptyModules.pop_back(); + } + if (m_moduleMembers[current.index] == 1) { + m_emptyModules.push_back(oldM); + } + + m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, m_moduleFlowData, m_moduleMembers); + + m_moduleMembers[oldM] -= 1; + m_moduleMembers[newM] += 1; + + current.index = newM; + return true; +} + +template +inline unsigned int InfomapOptimizer::optimizeActiveNetwork() +{ + unsigned int coreLoopCount = 0; + unsigned int numEffectiveLoops = 0; + double oldCodelength = m_objective.getCodelength(); + unsigned int loopLimit = m_infomap->coreLoopLimit; + unsigned int minRandLoop = 2; + if (loopLimit >= minRandLoop && m_infomap->randomizeCoreLoopLimit) + loopLimit = m_infomap->m_rand.randInt(minRandLoop, loopLimit); + if (m_infomap->m_aggregationLevel > 0 || m_infomap->m_isCoarseTune) { + loopLimit = 20; + } + + do { + ++coreLoopCount; + unsigned int numNodesMoved = m_infomap->innerParallelization + ? tryMoveEachNodeIntoBestModuleInParallel() + : tryMoveEachNodeIntoBestModule(); + // Break if not enough improvement + if (numNodesMoved == 0 || m_objective.getCodelength() >= oldCodelength - m_infomap->minimumCodelengthImprovement) + break; + ++numEffectiveLoops; + oldCodelength = m_objective.getCodelength(); + } while (coreLoopCount != loopLimit); + + return numEffectiveLoops; +} + +template +unsigned int InfomapOptimizer::tryMoveEachNodeIntoBestModule() +{ + // Get random enumeration of nodes + auto& network = m_infomap->activeNetwork(); + std::vector nodeEnumeration(network.size()); + m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration); + + auto numNodes = nodeEnumeration.size(); + unsigned int numMoved = 0; + + // Create map with module links + VectorMap deltaFlow(numNodes); + + for (unsigned int i = 0; i < numNodes; ++i) { + InfoNode& current = *network[nodeEnumeration[i]]; + + this->checkInterruption(); + + if (!current.dirty) + continue; + + // If other nodes have moved here, don't move away on first loop + if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1) + continue; + + // If no links connecting this node with other nodes, it won't move into others, + // and others won't move into this. TODO: Always best leave it alone? + // For memory networks, don't skip try move to same physical node! + + deltaFlow.startRound(); + + // For all outlinks + for (auto& e : current.outEdges()) { + auto& edge = *e; + InfoNode* neighbour = edge.target; + deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0)); + } + // For all inlinks + for (auto& e : current.inEdges()) { + auto& edge = *e; + InfoNode* neighbour = edge.source; + deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow)); + } + + // For not moving + deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0)); + DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index]; + oldModuleDelta.module = current.index; // Make sure index is correct if created new + + // Option to move to empty module (if node not already alone) + if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) { + deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0)); + } + + // For memory networks + m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow); + + auto& moduleDeltaEnterExit = deltaFlow.values(); + unsigned int numModuleLinks = deltaFlow.size(); + + // For recorded teleportation + if (m_infomap->recordedTeleportation) { + for (unsigned int j = 0; j < numModuleLinks; ++j) { + auto& deltaEnterExit = moduleDeltaEnterExit[j]; + auto moduleIndex = deltaEnterExit.module; + if (moduleIndex == current.index) { + auto& oldModuleFlowData = m_moduleFlowData[moduleIndex]; + double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight; + double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight); + deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitOld, deltaEnterOld)); + } else { + auto& newModuleFlowData = m_moduleFlowData[moduleIndex]; + double deltaEnterNew = newModuleFlowData.teleportFlow * current.data.teleportWeight; + double deltaExitNew = current.data.teleportFlow * newModuleFlowData.teleportWeight; + deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitNew, deltaEnterNew)); + } + } + } + + // Randomize link order for optimized search + std::vector moduleEnumeration(numModuleLinks); + m_infomap->m_rand.getRandomizedIndexVector(moduleEnumeration); + + DeltaFlowDataType bestDeltaModule(oldModuleDelta); + double bestDeltaCodelength = 0.0; + DeltaFlowDataType strongestConnectedModule(oldModuleDelta); + double deltaCodelengthOnStrongestConnectedModule = 0.0; + + // Find the move that minimizes the description length + for (unsigned int k = 0; k < numModuleLinks; ++k) { + auto j = moduleEnumeration[k]; + unsigned int otherModule = moduleDeltaEnterExit[j].module; + if (otherModule != current.index) { + double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current, + oldModuleDelta, + moduleDeltaEnterExit[j], + m_moduleFlowData, + m_moduleMembers); + + if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) { + bestDeltaModule = moduleDeltaEnterExit[j]; + bestDeltaCodelength = deltaCodelength; + } + + // Save strongest connected module to prefer if codelength improvement equal + if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) { + strongestConnectedModule = moduleDeltaEnterExit[j]; + deltaCodelengthOnStrongestConnectedModule = deltaCodelength; + } + } + } + + // Prefer strongest connected module if equal delta codelength + if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) { + bestDeltaModule = strongestConnectedModule; + } + + // Make best possible move + if (bestDeltaModule.module != current.index) { + unsigned int bestModuleIndex = bestDeltaModule.module; + // Update empty module vector + if (m_moduleMembers[bestModuleIndex] == 0) { + m_emptyModules.pop_back(); + } + if (m_moduleMembers[current.index] == 1) { + m_emptyModules.push_back(current.index); + } + + m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers); + + m_moduleMembers[current.index] -= 1; + m_moduleMembers[bestModuleIndex] += 1; + + unsigned int oldModuleIndex = current.index; + current.index = bestModuleIndex; + + ++numMoved; + + InfoNode* nodeInOldModule = ¤t; + unsigned int numLinkedNodesInOldModule = 0; + // Mark neighbours as dirty + for (auto& e : current.outEdges()) { + e->target->dirty = true; + if (e->target->index == oldModuleIndex) { + nodeInOldModule = e->target; + ++numLinkedNodesInOldModule; + } + } + for (auto& e : current.inEdges()) { + e->source->dirty = true; + if (e->source->index == oldModuleIndex) { + nodeInOldModule = e->source; + ++numLinkedNodesInOldModule; + } + } + + // Move single connected nodes to same module + if (numLinkedNodesInOldModule == 1 && m_moduleMembers[oldModuleIndex] == 1) { + moveNodeToPredefinedModule(*nodeInOldModule, bestModuleIndex); + ++numMoved; + // Mark neighbours as dirty + if (nodeInOldModule->degree() > 1) { + for (auto& e : nodeInOldModule->outEdges()) + e->target->dirty = true; + for (auto& e : nodeInOldModule->inEdges()) + e->source->dirty = true; + } + } + } else { + current.dirty = false; + } + } + + return numMoved; +} + +/** + * Minimize the codelength by trying to move each node into best module, in parallel. + * + * For each node: + * 1. Calculate the change in codelength for a move to each of its neighbouring modules or to an empty module + * 2. Move to the one that reduces the codelength the most, if any. + * + * @return The number of nodes moved. + */ +template +unsigned int InfomapOptimizer::tryMoveEachNodeIntoBestModuleInParallel() +{ + // Get random enumeration of nodes + auto& network = m_infomap->activeNetwork(); + std::vector nodeEnumeration(network.size()); + m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration); + + auto numNodes = nodeEnumeration.size(); + unsigned int numMoved = 0; + unsigned int numInvalidMoves = 0; + +#pragma omp parallel for schedule(dynamic) // Use dynamic scheduling as some threads could end early + for (unsigned int i = 0; i < numNodes; ++i) { + // Pick nodes in random order + InfoNode& current = *network[nodeEnumeration[i]]; + + if (!current.dirty) + continue; + + // If other nodes have moved here, don't move away on first loop + if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1) + continue; + + // If no links connecting this node with other nodes, it won't move into others, + // and others won't move into this. TODO: Always best leave it alone? + // For memory networks, don't skip try move to same physical node! + + // Create map with module links + VectorMap deltaFlow(numNodes); + + // For all outlinks + for (auto& e : current.outEdges()) { + auto& edge = *e; + InfoNode* neighbour = edge.target; + deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0)); + } + // For all inlinks + for (auto& e : current.inEdges()) { + auto& edge = *e; + InfoNode* neighbour = edge.source; + deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow)); + } + + // For not moving + deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0)); + DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index]; + oldModuleDelta.module = current.index; // Make sure index is correct if created new + + // Option to move to empty module (if node not already alone) + if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) { + // deltaFlow[m_emptyModules.back()] += DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0); + deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0)); + } + + // For memory networks + m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow); + + auto& moduleDeltaEnterExit = deltaFlow.values(); + unsigned int numModuleLinks = deltaFlow.size(); + + // Randomize link order for optimized search + if (numModuleLinks > 2) { + for (unsigned int j = 0; j < numModuleLinks - 2; ++j) { + unsigned int randPos = m_infomap->m_rand.randInt(j + 1, numModuleLinks - 1); + swap(moduleDeltaEnterExit[j], moduleDeltaEnterExit[randPos]); + } + } + + DeltaFlowDataType bestDeltaModule(oldModuleDelta); + double bestDeltaCodelength = 0.0; + DeltaFlowDataType strongestConnectedModule(oldModuleDelta); + double deltaCodelengthOnStrongestConnectedModule = 0.0; + + // Find the move that minimizes the description length + for (unsigned int j = 0; j < deltaFlow.size(); ++j) { + unsigned int otherModule = moduleDeltaEnterExit[j].module; + if (otherModule != current.index) { + double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current, + oldModuleDelta, + moduleDeltaEnterExit[j], + m_moduleFlowData, + m_moduleMembers); + + if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) { + bestDeltaModule = moduleDeltaEnterExit[j]; + bestDeltaCodelength = deltaCodelength; + } + + // Save strongest connected module to prefer if codelength improvement equal + if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) { + strongestConnectedModule = moduleDeltaEnterExit[j]; + deltaCodelengthOnStrongestConnectedModule = deltaCodelength; + } + } + } + + // Prefer strongest connected module if equal delta codelength + if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) { + bestDeltaModule = strongestConnectedModule; + } + + // Make best possible move + if (bestDeltaModule.module == current.index) { + current.dirty = false; + continue; + } else { +#pragma omp critical(moveUpdate) + { + unsigned int bestModuleIndex = bestDeltaModule.module; + unsigned int oldModuleIndex = current.index; + + bool validMove = bestModuleIndex == m_emptyModules.back() + // Check validity of move to empty target + ? m_moduleMembers[oldModuleIndex] > 1 && !m_emptyModules.empty() + // Not valid if the best module is empty now but not when decided + : m_moduleMembers[bestModuleIndex] > 0; + + if (validMove) { + // Recalculate delta codelength for proposed move to see if still an improvement + oldModuleDelta = DeltaFlowDataType(oldModuleIndex, 0.0, 0.0); + DeltaFlowDataType newModuleDelta(bestModuleIndex, 0.0, 0.0); + + // For all outlinks + for (auto& e : current.outEdges()) { + auto& edge = *e; + unsigned int otherModule = edge.target->index; + if (otherModule == oldModuleIndex) + oldModuleDelta.deltaExit += edge.data.flow; + else if (otherModule == bestModuleIndex) + newModuleDelta.deltaExit += edge.data.flow; + } + // For all inlinks + for (auto& e : current.inEdges()) { + auto& edge = *e; + unsigned int otherModule = edge.source->index; + if (otherModule == oldModuleIndex) + oldModuleDelta.deltaEnter += edge.data.flow; + else if (otherModule == bestModuleIndex) + newModuleDelta.deltaEnter += edge.data.flow; + } + + // For memory networks + m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow); + + double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current, + oldModuleDelta, + newModuleDelta, + m_moduleFlowData, + m_moduleMembers); + + if (deltaCodelength < 0.0 - m_infomap->minimumSingleNodeCodelengthImprovement) { + // Update empty module vector + if (m_moduleMembers[bestModuleIndex] == 0) { + m_emptyModules.pop_back(); + } + if (m_moduleMembers[oldModuleIndex] == 1) { + m_emptyModules.push_back(oldModuleIndex); + } + + m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers); + + m_moduleMembers[oldModuleIndex] -= 1; + m_moduleMembers[bestModuleIndex] += 1; + + current.index = bestModuleIndex; + + ++numMoved; + + // Mark neighbours as dirty + for (auto& e : current.outEdges()) + e->target->dirty = true; + for (auto& e : current.inEdges()) + e->source->dirty = true; + } else { + ++numInvalidMoves; + } + } else { + ++numInvalidMoves; + } + } + } + } + + return numMoved + numInvalidMoves; +} + +template +inline void InfomapOptimizer::consolidateModules(bool replaceExistingModules) +{ + auto& network = m_infomap->activeNetwork(); + auto numNodes = network.size(); + std::vector modules(numNodes, nullptr); + + InfoNode& firstActiveNode = *network[0]; + auto level = firstActiveNode.depth(); + auto leafLevel = m_infomap->numLevels(); + + if (leafLevel == 1) + replaceExistingModules = false; + + // Release children pointers on current parent(s) to put new modules between + for (auto& n : network) { + n->parent->releaseChildren(); // Safe to call multiple times + } + + // Create the new module nodes and re-parent the active network from its common parent to the new module level + for (unsigned int i = 0; i < numNodes; ++i) { + InfoNode* node = network[i]; + unsigned int moduleIndex = node->index; + if (modules[moduleIndex] == nullptr) { + modules[moduleIndex] = new InfoNode(m_moduleFlowData[moduleIndex]); + modules[moduleIndex]->index = moduleIndex; + node->parent->addChild(modules[moduleIndex]); + } + modules[moduleIndex]->addChild(node); + } + + using NodePair = std::pair; + using EdgeMap = std::map; + EdgeMap moduleLinks; + + for (auto& node : network) { + unsigned int module1 = node->index; + for (auto& e : node->outEdges()) { + InfoEdge& edge = *e; + unsigned int module2 = edge.target->index; + if (module1 != module2) { + // Use new variables to not swap module1 + unsigned int m1 = module1, m2 = module2; + // If undirected, the order may be swapped to aggregate the edge on an opposite one + if (m_infomap->isUndirectedClustering() && m1 > m2) + std::swap(m1, m2); + auto ret = moduleLinks.insert(std::make_pair(NodePair(m1, m2), edge.data.flow)); + if (!ret.second) { + ret.first->second += edge.data.flow; + } + } + } + } + + // Add the aggregated edge flow structure to the new modules + for (auto& e : moduleLinks) { + const auto& nodePair = e.first; + modules[nodePair.first]->addOutEdge(*modules[nodePair.second], 0.0, e.second); + } + + if (replaceExistingModules) { + if (level == 1) { + Log(4) << "Consolidated super modules, removing old modules...\n"; + for (auto& node : network) + node->replaceWithChildren(); + } else if (level == 2) { + Log(4) << "Consolidated sub-modules, removing modules...\n"; + unsigned int moduleIndex = 0; + for (InfoNode& module : m_infomap->root()) { + // Store current modular structure on the sub-modules + for (auto& subModule : module) + subModule.index = moduleIndex; + ++moduleIndex; + } + m_infomap->root().replaceChildrenWithGrandChildren(); + } + } + + // Calculate the number of non-trivial modules + m_infomap->m_numNonTrivialTopModules = 0; + for (auto& module : m_infomap->root()) { + if (module.childDegree() != 1) + ++m_infomap->m_numNonTrivialTopModules; + } + + m_objective.consolidateModules(modules); + m_consolidatedObjective = m_objective; +} + +template +inline bool InfomapOptimizer::restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore) +{ + if (forceRestore || m_objective.getCodelength() >= m_consolidatedObjective.getCodelength() - m_infomap->minimumSingleNodeCodelengthImprovement) { + m_objective = m_consolidatedObjective; + return true; + } + return false; +} + +} /* namespace infomap */ + +#endif // INFOMAP_OPTIMIZER_H_ diff --git a/vendor/infomap/src/core/InfomapOptimizerBase.h b/vendor/infomap/src/core/InfomapOptimizerBase.h new file mode 100644 index 0000000..75ba8ff --- /dev/null +++ b/vendor/infomap/src/core/InfomapOptimizerBase.h @@ -0,0 +1,113 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_OPTIMIZER_BASE_H_ +#define INFOMAP_OPTIMIZER_BASE_H_ + +#include "InfomapBase.h" +#include "InfoNode.h" +#include "FlowData.h" +#include + +namespace infomap { + +class InfomapOptimizerBase { + friend class InfomapBase; + using FlowDataType = FlowData; + +public: + InfomapOptimizerBase() = default; + + virtual ~InfomapOptimizerBase() = default; + + virtual void init(InfomapBase* infomap) = 0; + + // =================================================== + // IO + // =================================================== + + virtual std::ostream& toString(std::ostream& out) const = 0; + + // =================================================== + // Getters + // =================================================== + + virtual double getCodelength() const = 0; + + virtual double getIndexCodelength() const = 0; + + virtual double getModuleCodelength() const = 0; + + virtual double getMetaCodelength(bool /*unweighted*/ = false) const { return 0.0; } + + void setInterruptionHandler(interruptionHandlerFn *interruptionHandler) { + this->interruptionHandler = interruptionHandler; + } + +protected: + virtual unsigned int numActiveModules() const = 0; + + void checkInterruption() { + if (interruptionHandler) { + if (interruptionHandler()) { + throw infomap::InterruptException(); + } + } + } + + // =================================================== + // Run: Init: * + // =================================================== + + // Init terms that is constant for the whole network + virtual void initTree() = 0; + + virtual void initNetwork() = 0; + + virtual void initSuperNetwork() = 0; + + virtual double calcCodelength(const InfoNode& parent) const = 0; + + // =================================================== + // Run: Partition: * + // =================================================== + + virtual void initPartition() = 0; + + virtual void moveActiveNodesToPredefinedModules(std::vector& modules) = 0; + + virtual unsigned int optimizeActiveNetwork() = 0; + + virtual unsigned int tryMoveEachNodeIntoBestModule() = 0; + + // virtual unsigned int tryMoveEachNodeIntoBestModuleLocal() = 0; + + virtual unsigned int tryMoveEachNodeIntoBestModuleInParallel() = 0; + + virtual void consolidateModules(bool replaceExistingModules = true) = 0; + + virtual bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) = 0; + +#if 0 + // =================================================== + // Debug: * + // =================================================== + + virtual void printDebug() = 0; +#endif + +private: + + interruptionHandlerFn *interruptionHandler = NULL; + +}; + +} /* namespace infomap */ + +#endif // INFOMAP_OPTIMIZER_BASE_H_ diff --git a/vendor/infomap/src/core/MapEquation.h b/vendor/infomap/src/core/MapEquation.h new file mode 100644 index 0000000..b9913f8 --- /dev/null +++ b/vendor/infomap/src/core/MapEquation.h @@ -0,0 +1,339 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef MAPEQUATION_H_ +#define MAPEQUATION_H_ + +#include "../utils/infomath.h" +#include "../utils/convert.h" +#include "../io/Config.h" +#include "../utils/Log.h" +#include "../utils/VectorMap.h" +#include "InfoNode.h" +#include "FlowData.h" +#include +#include +#include + +namespace infomap { + +class InfoNode; + +template +class MapEquation { + using ME = MapEquation; + +public: + MapEquation() = default; + + MapEquation(const MapEquation& other) = default; + + MapEquation& operator=(const MapEquation& other) = default; + + MapEquation(MapEquation&& other) noexcept = default; + + MapEquation& operator=(MapEquation&& other) noexcept = default; + + virtual ~MapEquation() = default; + + // =================================================== + // Getters + // =================================================== + + virtual double getIndexCodelength() const { return indexCodelength; } + + virtual double getModuleCodelength() const { return moduleCodelength; } + + virtual double getCodelength() const { return codelength; } + + // =================================================== + // IO + // =================================================== + + virtual std::ostream& print(std::ostream& out) const + { + return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength); + } + + // =================================================== + // Init + // =================================================== + + virtual void init(const Config&) + { + Log(3) << "MapEquation::init()...\n"; + } + + virtual void initTree(InfoNode& /*root*/) = 0; + + virtual void initNetwork(InfoNode& root) + { + Log(3) << "MapEquation::initNetwork()...\n"; + + nodeFlow_log_nodeFlow = 0.0; + for (InfoNode& node : root) { + nodeFlow_log_nodeFlow += infomath::plogp(node.data.flow); + } + ME::initSubNetwork(root); + } + + virtual void initSuperNetwork(InfoNode& root) + { + Log(3) << "MapEquation::initSuperNetwork()...\n"; + + nodeFlow_log_nodeFlow = 0.0; + for (InfoNode& node : root) { + nodeFlow_log_nodeFlow += infomath::plogp(node.data.enterFlow); + } + } + + virtual void initSubNetwork(InfoNode& root) + { + exitNetworkFlow = root.data.exitFlow; + exitNetworkFlow_log_exitNetworkFlow = infomath::plogp(exitNetworkFlow); + } + + virtual void initPartition(std::vector& nodes) { ME::calculateCodelength(nodes); } + + // =================================================== + // Codelength + // =================================================== + + virtual double calcCodelength(const InfoNode& parent) const + { + return parent.isLeafModule() ? ME::calcCodelengthOnModuleOfLeafNodes(parent) : ME::calcCodelengthOnModuleOfModules(parent); + } + + virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, DeltaFlowDataType& /*newModuleDelta*/) { } + + virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, VectorMap& /*moduleDeltaFlow*/) { } + + virtual double getDeltaCodelengthOnMovingNode(InfoNode& current, + DeltaFlowDataType& oldModuleDelta, + DeltaFlowDataType& newModuleDelta, + std::vector& moduleFlowData, + std::vector& /*moduleMembers*/); + + // =================================================== + // Consolidation + // =================================================== + + virtual void updateCodelengthOnMovingNode(InfoNode& current, + DeltaFlowDataType& oldModuleDelta, + DeltaFlowDataType& newModuleDelta, + std::vector& moduleFlowData, + std::vector& /*moduleMembers*/); + + virtual void consolidateModules(std::vector& /*modules*/) = 0; + + // =================================================== + // Debug + // =================================================== + +#if 0 + virtual void printDebug() const + { + std::cout << "(enterFlow_log_enterFlow: " << enterFlow_log_enterFlow << ", " + << "enter_log_enter: " << enter_log_enter << ", " + << "exitNetworkFlow_log_exitNetworkFlow: " << exitNetworkFlow_log_exitNetworkFlow << ") "; + } +#endif + +protected: + // =================================================== + // Protected member functions + // =================================================== + + virtual double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const; + + virtual double calcCodelengthOnModuleOfModules(const InfoNode& parent) const; + + virtual void calculateCodelength(std::vector& nodes) + { + ME::calculateCodelengthTerms(nodes); + ME::calculateCodelengthFromCodelengthTerms(); + } + + virtual void calculateCodelengthTerms(std::vector& nodes); + + virtual void calculateCodelengthFromCodelengthTerms() + { + indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow; + moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow; + codelength = indexCodelength + moduleCodelength; + } + +public: + // =================================================== + // Public member variables + // =================================================== + + double codelength = 0.0; + double indexCodelength = 0.0; + double moduleCodelength = 0.0; + +protected: + // =================================================== + // Protected member variables + // =================================================== + + double nodeFlow_log_nodeFlow = 0.0; // constant while the leaf network is the same + double flow_log_flow = 0.0; // node.(flow + exitFlow) + double exit_log_exit = 0.0; + double enter_log_enter = 0.0; + double enterFlow = 0.0; + double enterFlow_log_enterFlow = 0.0; + + // For hierarchical + double exitNetworkFlow = 0.0; + double exitNetworkFlow_log_exitNetworkFlow = 0.0; +}; + +template +double MapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector& moduleFlowData, std::vector&) +{ + using infomath::plogp; + unsigned int oldModule = oldModuleDelta.module; + unsigned int newModule = newModuleDelta.module; + double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit; + double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit; + + double delta_enter = plogp(enterFlow + deltaEnterExitOldModule - deltaEnterExitNewModule) - enterFlow_log_enterFlow; + + double delta_enter_log_enter = -plogp(moduleFlowData[oldModule].enterFlow) + - plogp(moduleFlowData[newModule].enterFlow) + + plogp(moduleFlowData[oldModule].enterFlow - current.data.enterFlow + deltaEnterExitOldModule) + + plogp(moduleFlowData[newModule].enterFlow + current.data.enterFlow - deltaEnterExitNewModule); + + double delta_exit_log_exit = -plogp(moduleFlowData[oldModule].exitFlow) + - plogp(moduleFlowData[newModule].exitFlow) + + plogp(moduleFlowData[oldModule].exitFlow - current.data.exitFlow + deltaEnterExitOldModule) + + plogp(moduleFlowData[newModule].exitFlow + current.data.exitFlow - deltaEnterExitNewModule); + + double delta_flow_log_flow = -plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + - plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow) + + plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow + - current.data.exitFlow - current.data.flow + deltaEnterExitOldModule) + + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow + + current.data.exitFlow + current.data.flow - deltaEnterExitNewModule); + + double deltaL = delta_enter - delta_enter_log_enter - delta_exit_log_exit + delta_flow_log_flow; + return deltaL; +} + +template +void MapEquation::updateCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector& moduleFlowData, std::vector&) +{ + using infomath::plogp; + unsigned int oldModule = oldModuleDelta.module; + unsigned int newModule = newModuleDelta.module; + double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit; + double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit; + + enterFlow -= moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow; + enter_log_enter -= plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow); + exit_log_exit -= plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow); + flow_log_flow -= plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow); + + moduleFlowData[oldModule] -= current.data; + moduleFlowData[newModule] += current.data; + + moduleFlowData[oldModule].enterFlow += deltaEnterExitOldModule; + moduleFlowData[oldModule].exitFlow += deltaEnterExitOldModule; + moduleFlowData[newModule].enterFlow -= deltaEnterExitNewModule; + moduleFlowData[newModule].exitFlow -= deltaEnterExitNewModule; + + enterFlow += moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow; + enter_log_enter += plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow); + exit_log_exit += plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow); + flow_log_flow += plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow); + + enterFlow_log_enterFlow = plogp(enterFlow); + + indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow; + moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow; + codelength = indexCodelength + moduleCodelength; +} + +template +double MapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const +{ + double parentFlow = parent.data.flow; + double parentExit = parent.data.exitFlow; + double totalParentFlow = parentFlow + parentExit; + if (totalParentFlow < 1e-16) + return 0.0; + + double indexLength = 0.0; + for (const auto& node : parent) { + indexLength -= infomath::plogp(node.data.flow / totalParentFlow); + } + indexLength -= infomath::plogp(parentExit / totalParentFlow); + + indexLength *= totalParentFlow; + + return indexLength; +} + +template +double MapEquation::calcCodelengthOnModuleOfModules(const InfoNode& parent) const +{ + double parentFlow = parent.data.flow; + double parentExit = parent.data.exitFlow; + if (parentFlow < 1e-16) + return 0.0; + + // H(x) = -xlog(x), T = q + SUM(p), q = exitFlow, p = enterFlow + // Normal format + // L = q * -log(q/T) + SUM(p * -log(p/T)) + // Compact format + // L = T * ( H(q/T) + SUM( H(p/T) ) ) + // Expanded format + // L = q * -log(q) - q * -log(T) + SUM( p * -log(p) - p * -log(T) ) + // = T * log(T) - q*log(q) - SUM( p*log(p) ) + // = -H(T) + H(q) + SUM(H(p)) + // As T is not known, use expanded format to avoid two loops + double sumEnter = 0.0; + double sumEnterLogEnter = 0.0; + for (const auto& node : parent) { + sumEnter += node.data.enterFlow; // rate of enter to finer level + sumEnterLogEnter += infomath::plogp(node.data.enterFlow); + } + // The possibilities from this module: Either exit to coarser level or enter one of its children + double totalCodewordUse = parentExit + sumEnter; + + return infomath::plogp(totalCodewordUse) - sumEnterLogEnter - infomath::plogp(parentExit); +} + +template +void MapEquation::calculateCodelengthTerms(std::vector& nodes) +{ + enter_log_enter = 0.0; + flow_log_flow = 0.0; + exit_log_exit = 0.0; + enterFlow = 0.0; + + // For each module + for (InfoNode* n : nodes) { + InfoNode& node = *n; + // own node/module codebook + flow_log_flow += infomath::plogp(node.data.flow + node.data.exitFlow); + + // use of index codebook + enter_log_enter += infomath::plogp(node.data.enterFlow); + exit_log_exit += infomath::plogp(node.data.exitFlow); + enterFlow += node.data.enterFlow; + } + enterFlow += exitNetworkFlow; + enterFlow_log_enterFlow = infomath::plogp(enterFlow); +} + +} // namespace infomap + +#endif // MAPEQUATION_H_ diff --git a/vendor/infomap/src/core/MemMapEquation.cpp b/vendor/infomap/src/core/MemMapEquation.cpp new file mode 100644 index 0000000..1c25d4b --- /dev/null +++ b/vendor/infomap/src/core/MemMapEquation.cpp @@ -0,0 +1,451 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "MemMapEquation.h" +#include "FlowData.h" +#include "InfoNode.h" + +#include +#include +#include +#include +#include + +namespace infomap { + +// =================================================== +// IO +// =================================================== + +std::ostream& MemMapEquation::print(std::ostream& out) const +{ + return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength); +} + +std::ostream& operator<<(std::ostream& out, const MemMapEquation& mapEq) +{ + return mapEq.print(out); +} + +// =================================================== +// Init +// =================================================== + +void MemMapEquation::init(const Config& /*config*/) +{ + Log(3) << "MemMapEquation::init()...\n"; +} + +void MemMapEquation::initNetwork(InfoNode& root) +{ + initPhysicalNodes(root); +} + +void MemMapEquation::initSuperNetwork(InfoNode& /*root*/) +{ + // TODO: How use enterFlow instead of flow +} + +void MemMapEquation::initSubNetwork(InfoNode& /*root*/) +{ + // Base::initSubNetwork(root); +} + +void MemMapEquation::initPartition(std::vector& nodes) +{ + initPartitionOfPhysicalNodes(nodes); + + calculateCodelength(nodes); +} + +void MemMapEquation::initPhysicalNodes(InfoNode& root) +{ + bool notInitiatedOnRoot = root.physicalNodes.empty(); + if (notInitiatedOnRoot) { + // Assume leaf nodes directly under the root node + std::unordered_map physicalNodes; + unsigned int maxPhysicalId = 0; + unsigned int minPhysicalId = std::numeric_limits::max(); + for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) { + InfoNode& node = *it; + physicalNodes[node.physicalId] += node.data.flow; + minPhysicalId = std::min(minPhysicalId, node.physicalId); + maxPhysicalId = std::max(maxPhysicalId, node.physicalId); + } + + // Re-index physical nodes if necessary + std::map toZeroBasedIndex; + if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) { + unsigned int zeroBasedPhysicalId = 0; + for (const auto& physNode : physicalNodes) { + toZeroBasedIndex.insert(std::make_pair(physNode.first, zeroBasedPhysicalId++)); + } + } + + for (const auto& physNode : physicalNodes) { + unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physNode.first] : (physNode.first - minPhysicalId); + root.physicalNodes.emplace_back(zeroBasedIndex, physNode.second); + } + } + auto firstLeafIt = root.begin_leaf_nodes(); + auto depth = firstLeafIt.depth(); + bool notInitiatedOnLeafNodes = firstLeafIt->physicalNodes.empty(); + if (notInitiatedOnLeafNodes) { + Log(3) << "MemMapEquation::initPhysicalNodesOnOriginalNetwork()...\n"; + std::set setOfPhysicalNodes; + unsigned int maxPhysicalId = 0; + unsigned int minPhysicalId = std::numeric_limits::max(); + for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) { + InfoNode& node = *it; + setOfPhysicalNodes.insert(node.physicalId); + maxPhysicalId = std::max(maxPhysicalId, node.physicalId); + minPhysicalId = std::min(minPhysicalId, node.physicalId); + } + + m_numPhysicalNodes = setOfPhysicalNodes.size(); + + // Re-index physical nodes if necessary + std::map toZeroBasedIndex; + if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) { + unsigned int zeroBasedPhysicalId = 0; + for (unsigned int physIndex : setOfPhysicalNodes) { + toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++)); + } + } + + for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) { + InfoNode& node = *it; + unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[node.physicalId] : (node.physicalId - minPhysicalId); + node.physicalNodes.emplace_back(zeroBasedIndex, node.data.flow); + } + + // If leaf nodes was not directly under root, make sure leaf modules have + // physical nodes defined also + if (depth > 1) { + for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) { + InfoNode& module = *it; + std::map physToFlow; + for (auto& node : module) { + for (PhysData& physData : node.physicalNodes) { + physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node; + } + } + for (auto& physFlow : physToFlow) { + module.physicalNodes.emplace_back(physFlow.first, physFlow.second); + } + } + } + } else { + // Either a sub-network (without modules) or the whole network with reconstructed tree + if (depth == 1) { + // new sub-network + Log(3) << "MemMapEquation::initPhysicalNodesOnSubNetwork()...\n"; + std::set setOfPhysicalNodes; + unsigned int maxPhysNodeIndex = 0; + unsigned int minPhysNodeIndex = std::numeric_limits::max(); + + // Collect all physical nodes in this sub network + for (InfoNode& node : root) { + for (PhysData& physData : node.physicalNodes) { + setOfPhysicalNodes.insert(physData.physNodeIndex); + maxPhysNodeIndex = std::max(maxPhysNodeIndex, physData.physNodeIndex); + minPhysNodeIndex = std::min(minPhysNodeIndex, physData.physNodeIndex); + } + } + + m_numPhysicalNodes = setOfPhysicalNodes.size(); + + // Re-index physical nodes if needed (not required when reconstructing tree) + if (maxPhysNodeIndex >= m_numPhysicalNodes) { + std::map toZeroBasedIndex; + if (maxPhysNodeIndex - minPhysNodeIndex + 1 > m_numPhysicalNodes) { + unsigned int zeroBasedPhysicalId = 0; + for (unsigned int physIndex : setOfPhysicalNodes) { + toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++)); + } + } + + for (InfoNode& node : root) { + for (PhysData& physData : node.physicalNodes) { + unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physData.physNodeIndex] : (physData.physNodeIndex - minPhysNodeIndex); + physData.physNodeIndex = zeroBasedIndex; + } + } + } + } else { + // whole network with reconstructed tree + for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) { + InfoNode& module = *it; + std::map physToFlow; + for (auto& node : module) { + for (PhysData& physData : node.physicalNodes) { + physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node; + } + } + for (auto& physFlow : physToFlow) { + module.physicalNodes.emplace_back(physFlow.first, physFlow.second); + } + } + } + } +} + +void MemMapEquation::initPartitionOfPhysicalNodes(std::vector& nodes) +{ + Log(4) << "MemMapEquation::initPartitionOfPhysicalNodes()...\n"; + m_physToModuleToMemNodes.clear(); + m_physToModuleToMemNodes.resize(m_numPhysicalNodes); + + for (auto& n : nodes) { + InfoNode& node = *n; + unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase + + for (PhysData& physData : node.physicalNodes) { + m_physToModuleToMemNodes[physData.physNodeIndex].insert(m_physToModuleToMemNodes[physData.physNodeIndex].end(), + std::make_pair(moduleIndex, MemNodeSet(1, physData.sumFlowFromM2Node))); + } + } + + m_memoryContributionsAdded = false; +} + +// =================================================== +// Codelength +// =================================================== + +void MemMapEquation::calculateCodelength(std::vector& nodes) +{ + calculateCodelengthTerms(nodes); + + calculateNodeFlow_log_nodeFlow(); + + calculateCodelengthFromCodelengthTerms(); +} + +void MemMapEquation::calculateNodeFlow_log_nodeFlow() +{ + nodeFlow_log_nodeFlow = 0.0; + for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) { + const ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[i]; + for (const auto& moduleToMemNode : moduleToMemNodes) + nodeFlow_log_nodeFlow += infomath::plogp(moduleToMemNode.second.sumFlow); + } +} + +double MemMapEquation::calcCodelength(const InfoNode& parent) const +{ + if (parent.isLeafModule()) { + return calcCodelengthOnModuleOfLeafNodes(parent); + } + // Use first-order model on index codebook + return Base::calcCodelengthOnModuleOfModules(parent); +} + +double MemMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const +{ + if (parent.numPhysicalNodes() == 0) { + return Base::calcCodelength(parent); // Infomap root node + } + + // TODO: For ordinary networks, flow should be used instead of enter flow + // for leaf nodes, what about memory networks? sumFlowFromM2Node vs sumEnterFlowFromM2Node? + double parentFlow = parent.data.flow; + double parentExit = parent.data.exitFlow; + double totalParentFlow = parentFlow + parentExit; + if (totalParentFlow < 1e-16) + return 0.0; + + double indexLength = 0.0; + + for (const PhysData& physData : parent.physicalNodes) { + indexLength -= infomath::plogp(physData.sumFlowFromM2Node / totalParentFlow); + } + indexLength -= infomath::plogp(parentExit / totalParentFlow); + + indexLength *= totalParentFlow; + + return indexLength; +} + +void MemMapEquation::addMemoryContributions(InfoNode& current, + MemDeltaFlow& oldModuleDelta, + VectorMap& moduleDeltaFlow) +{ + // Overlapping modules + /* + * delta = old.first + new.first + old.second - new.second. + * Two cases: (p(x) = plogp(x)) + * Moving to a module that already have that physical node: (old: p1, p2, new p3, moving p2 -> old:p1, new p2,p3) + * Then old.second = new.second = plogp(physicalNodeSize) -> cancelation -> delta = p(p1) - p(p1+p2) + p(p2+p3) - p(p3) + * Moving to a module that not have that physical node: (old: p1, p2, new -, moving p2 -> old: p1, new: p2) + * Then new.first = new.second = 0 -> delta = p(p1) - p(p1+p2) + p(p2). + */ + auto& physicalNodes = current.physicalNodes; + unsigned int numPhysicalNodes = physicalNodes.size(); + for (unsigned int i = 0; i < numPhysicalNodes; ++i) { + PhysData& physData = physicalNodes[i]; + ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex]; + for (const auto& moduleToMemNode : moduleToMemNodes) { + unsigned int moduleIndex = moduleToMemNode.first; + auto& memNodeSet = moduleToMemNode.second; + if (moduleIndex == current.index) // From where the multiple assigned node is moved + { + double oldPhysFlow = memNodeSet.sumFlow; + double newPhysFlow = memNodeSet.sumFlow - physData.sumFlowFromM2Node; + oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow); + oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node); + } else // To where the multiple assigned node is moved + { + double oldPhysFlow = memNodeSet.sumFlow; + double newPhysFlow = memNodeSet.sumFlow + physData.sumFlowFromM2Node; + + double sumDeltaPlogpPhysFlow = infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow); + double sumPlogpPhysFlow = infomath::plogp(physData.sumFlowFromM2Node); + moduleDeltaFlow.add(moduleIndex, MemDeltaFlow(moduleIndex, 0.0, 0.0, sumDeltaPlogpPhysFlow, sumPlogpPhysFlow)); + } + } + } + m_memoryContributionsAdded = true; +} + +double MemMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current, + MemDeltaFlow& oldModuleDelta, + MemDeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + + double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow; + + return deltaL - delta_nodeFlow_log_nodeFlow; +} + +// =================================================== +// Consolidation +// =================================================== + +void MemMapEquation::updateCodelengthOnMovingNode(InfoNode& current, + MemDeltaFlow& oldModuleDelta, + MemDeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + if (m_memoryContributionsAdded) + updatePhysicalNodes(current, oldModuleDelta.module, newModuleDelta.module); + else + addMemoryContributionsAndUpdatePhysicalNodes(current, oldModuleDelta, newModuleDelta); + + double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow; + + nodeFlow_log_nodeFlow += delta_nodeFlow_log_nodeFlow; + moduleCodelength -= delta_nodeFlow_log_nodeFlow; + codelength -= delta_nodeFlow_log_nodeFlow; +} + +void MemMapEquation::updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex) +{ + // For all multiple assigned nodes + for (const auto& physData : current.physicalNodes) { + ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex]; + + // Remove contribution to old module + auto overlapIt = moduleToMemNodes.find(oldModuleIndex); + if (overlapIt == moduleToMemNodes.end()) + throw std::length_error(io::Str() << "Couldn't find old module " << oldModuleIndex << " in physical node " << physData.physNodeIndex); + + MemNodeSet& oldMemNodeSet = overlapIt->second; + oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node; + if (--oldMemNodeSet.numMemNodes == 0) + moduleToMemNodes.erase(overlapIt); + + // Add contribution to new module + overlapIt = moduleToMemNodes.find(bestModuleIndex); + if (overlapIt == moduleToMemNodes.end()) { + moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node))); + } else { + MemNodeSet& newMemNodeSet = overlapIt->second; + ++newMemNodeSet.numMemNodes; + newMemNodeSet.sumFlow += physData.sumFlowFromM2Node; + } + } +} + +void MemMapEquation::addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta) +{ + unsigned int oldModuleIndex = oldModuleDelta.module; + unsigned int bestModuleIndex = newModuleDelta.module; + + // For all multiple assigned nodes + for (const auto& physData : current.physicalNodes) { + ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex]; + + // Remove contribution to old module + auto overlapIt = moduleToMemNodes.find(oldModuleIndex); + if (overlapIt == moduleToMemNodes.end()) + throw std::length_error("Couldn't find old module among physical node assignments."); + + MemNodeSet& oldMemNodeSet = overlapIt->second; + double oldPhysFlow = oldMemNodeSet.sumFlow; + double newPhysFlow = oldMemNodeSet.sumFlow - physData.sumFlowFromM2Node; + oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow); + oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node); + oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node; + if (--oldMemNodeSet.numMemNodes == 0) + moduleToMemNodes.erase(overlapIt); + + // Add contribution to new module + overlapIt = moduleToMemNodes.find(bestModuleIndex); + if (overlapIt == moduleToMemNodes.end()) { + moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node))); + oldPhysFlow = 0.0; + newPhysFlow = physData.sumFlowFromM2Node; + newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow); + newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node); + } else { + MemNodeSet& newMemNodeSet = overlapIt->second; + oldPhysFlow = newMemNodeSet.sumFlow; + newPhysFlow = newMemNodeSet.sumFlow + physData.sumFlowFromM2Node; + newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow); + newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node); + ++newMemNodeSet.numMemNodes; + newMemNodeSet.sumFlow += physData.sumFlowFromM2Node; + } + } +} + +void MemMapEquation::consolidateModules(std::vector& modules) +{ + std::map> validate; + + for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) { + ModuleToMemNodes& modToMemNodes = m_physToModuleToMemNodes[i]; + for (const auto& modToMemNode : modToMemNodes) { + if (++validate[modToMemNode.first][i] > 1) + throw std::domain_error("[InfomapGreedy::consolidateModules] Error updating physical nodes: duplication error"); + + modules[modToMemNode.first]->physicalNodes.emplace_back(i, modToMemNode.second.sumFlow); + } + } +} + +#if 0 +// =================================================== +// Debug +// =================================================== + +void MemMapEquation::printDebug() const +{ + std::cout << "MemMapEquation::m_numPhysicalNodes: " << m_numPhysicalNodes << "\n"; + Base::printDebug(); +} +#endif + +} // namespace infomap diff --git a/vendor/infomap/src/core/MemMapEquation.h b/vendor/infomap/src/core/MemMapEquation.h new file mode 100644 index 0000000..e4d7410 --- /dev/null +++ b/vendor/infomap/src/core/MemMapEquation.h @@ -0,0 +1,174 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef MEM_MAPEQUATION_H_ +#define MEM_MAPEQUATION_H_ + +#include "MapEquation.h" +#include "FlowData.h" +#include "../utils/Log.h" +#include +#include +#include +#include + +namespace infomap { + +class InfoNode; +struct MemNodeSet; + +class MemMapEquation : private MapEquation { + using Base = MapEquation; + +public: + using FlowDataType = FlowData; + using DeltaFlowDataType = MemDeltaFlow; + + // =================================================== + // Getters + // =================================================== + + using Base::getIndexCodelength; + + using Base::getModuleCodelength; + + using Base::getCodelength; + + // =================================================== + // IO + // =================================================== + + std::ostream& print(std::ostream& out) const override; + friend std::ostream& operator<<(std::ostream&, const MemMapEquation&); + + // =================================================== + // Init + // =================================================== + + void init(const Config& config) override; + + void initTree(InfoNode& /*root*/) override { } + + void initNetwork(InfoNode& root) override; + + void initSuperNetwork(InfoNode& root) override; + + void initSubNetwork(InfoNode& root) override; + + void initPartition(std::vector& nodes) override; + + // =================================================== + // Codelength + // =================================================== + + double calcCodelength(const InfoNode& parent) const override; + + void addMemoryContributions(InfoNode& current, MemDeltaFlow& oldModuleDelta, VectorMap& moduleDeltaFlow) override; + + double getDeltaCodelengthOnMovingNode(InfoNode& current, + MemDeltaFlow& oldModuleDelta, + MemDeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + // =================================================== + // Consolidation + // =================================================== + + void updateCodelengthOnMovingNode(InfoNode& current, + MemDeltaFlow& oldModuleDelta, + MemDeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + void consolidateModules(std::vector& modules) override; + +#if 0 + // =================================================== + // Debug + // =================================================== + + void printDebug() const override; +#endif + +private: + // =================================================== + // Private member functions + // =================================================== + double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override; + + // =================================================== + // Init + // =================================================== + + void initPhysicalNodes(InfoNode& root); + + void initPartitionOfPhysicalNodes(std::vector& nodes); + + // =================================================== + // Codelength + // =================================================== + + void calculateCodelength(std::vector& nodes) override; + + using Base::calculateCodelengthTerms; + + using Base::calculateCodelengthFromCodelengthTerms; + + void calculateNodeFlow_log_nodeFlow(); + + // =================================================== + // Consolidation + // =================================================== + + void updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex); + + void addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta); + +public: + // =================================================== + // Public member variables + // =================================================== + + using Base::codelength; + using Base::indexCodelength; + using Base::moduleCodelength; + +private: + // =================================================== + // Private member variables + // =================================================== + + using Base::enter_log_enter; + using Base::enterFlow; + using Base::enterFlow_log_enterFlow; + using Base::exit_log_exit; + using Base::flow_log_flow; // node.(flow + exitFlow) + using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same + + // For hierarchical + using Base::exitNetworkFlow; + using Base::exitNetworkFlow_log_exitNetworkFlow; + + using ModuleToMemNodes = std::map; + + std::vector m_physToModuleToMemNodes; // vector[physicalNodeID] map + unsigned int m_numPhysicalNodes = 0; + bool m_memoryContributionsAdded = false; +}; + +struct MemNodeSet { + MemNodeSet(unsigned int numMemNodes, double sumFlow) : numMemNodes(numMemNodes), sumFlow(sumFlow) { } + unsigned int numMemNodes; // use counter to check for zero to avoid round-off errors in sumFlow + double sumFlow; +}; + +} // namespace infomap + +#endif // MEM_MAPEQUATION_H_ diff --git a/vendor/infomap/src/core/MetaMapEquation.cpp b/vendor/infomap/src/core/MetaMapEquation.cpp new file mode 100644 index 0000000..48685fb --- /dev/null +++ b/vendor/infomap/src/core/MetaMapEquation.cpp @@ -0,0 +1,271 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "MetaMapEquation.h" +#include "FlowData.h" +#include "InfoNode.h" + +#include +#include + +namespace infomap { + +double MetaMapEquation::getModuleCodelength() const +{ + return moduleCodelength + metaCodelength * metaDataRate; +} + +double MetaMapEquation::getCodelength() const +{ + return codelength + metaCodelength * metaDataRate; +} + +// =================================================== +// IO +// =================================================== + +std::ostream& MetaMapEquation::print(std::ostream& out) const +{ + return out << indexCodelength << " + " << moduleCodelength << " + " << metaCodelength << " = " << io::toPrecision(getCodelength()); +} + +std::ostream& operator<<(std::ostream& out, const MetaMapEquation& mapEq) +{ + return mapEq.print(out); +} + +// =================================================== +// Init +// =================================================== + +void MetaMapEquation::init(const Config& config) +{ + Log(3) << "MetaMapEquation::init()...\n"; + numMetaDataDimensions = config.numMetaDataDimensions; + metaDataRate = config.metaDataRate; + weightByFlow = !config.unweightedMetaData; +} + +void MetaMapEquation::initTree(InfoNode& root) +{ + Log(3) << "MetaMapEquation::initTree()...\n"; + initMetaNodes(root); +} + +void MetaMapEquation::initNetwork(InfoNode& root) +{ + Log(3) << "MetaMapEquation::initNetwork()...\n"; + Base::initNetwork(root); + m_unweightedNodeFlow = 1.0 / root.childDegree(); +} + +void MetaMapEquation::initPartition(std::vector& nodes) +{ + initPartitionOfMetaNodes(nodes); + calculateCodelength(nodes); +} + +void MetaMapEquation::initMetaNodes(InfoNode& root) +{ + bool notInitiated = root.firstChild->metaCollection.empty(); + if (notInitiated) { + Log(3) << "MetaMapEquation::initMetaNodes()...\n"; + + for (auto it = root.begin_post_depth_first(); !it.isEnd(); ++it) { + auto& node = *it; + if (node.isRoot()) { + break; + } + if (node.isLeaf()) { + if (!node.metaData.empty()) { + // TODO: Use flow here and move weightByFlow choice to metaCollection, using flowCount? + double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow; + node.parent->metaCollection.add(node.metaData[0], flow); + } else { + throw std::length_error("A node is missing meta data using MetaMapEquation"); + } + } else { + node.parent->metaCollection.add(node.metaCollection); + } + } + } +} + +void MetaMapEquation::initPartitionOfMetaNodes(std::vector& nodes) +{ + Log(4) << "MetaMapEquation::initPartitionOfMetaNodes()...\n"; + m_moduleToMetaCollection.clear(); + + for (auto& n : nodes) { + InfoNode& node = *n; + unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase + if (node.metaCollection.empty()) { + if (!node.metaData.empty()) { + double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow; + node.metaCollection.add(node.metaData[0], flow); + } else + throw std::length_error("A node is missing meta data using MetaMapEquation"); + } + m_moduleToMetaCollection[moduleIndex] = node.metaCollection; + } +} + +// =================================================== +// Codelength +// =================================================== + +void MetaMapEquation::calculateCodelength(std::vector& nodes) +{ + calculateCodelengthTerms(nodes); + + calculateCodelengthFromCodelengthTerms(); + + metaCodelength = 0.0; + + // Treat each node as a single module + for (InfoNode* n : nodes) { + InfoNode& node = *n; + metaCodelength += node.metaCollection.calculateEntropy(); + } +} + +double MetaMapEquation::calcCodelength(const InfoNode& parent) const +{ + return parent.isLeafModule() ? calcCodelengthOnModuleOfLeafNodes(parent) : Base::calcCodelengthOnModuleOfModules(parent); +} + +double MetaMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const +{ + double indexLength = Base::calcCodelength(parent); + + // Meta addition + MetaCollection metaCollection; + for (const InfoNode& node : parent) { + if (!node.metaCollection.empty()) + metaCollection.add(node.metaCollection); + else + metaCollection.add(node.metaData[0], weightByFlow ? node.data.flow : m_unweightedNodeFlow); // TODO: Initiate to collection and use all dimensions + } + + double _metaCodelength = metaCollection.calculateEntropy(); + + return indexLength + metaDataRate * _metaCodelength; +} + +double MetaMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + + double deltaMetaL = 0.0; + + unsigned int oldModuleIndex = oldModuleDelta.module; + unsigned int newModuleIndex = newModuleDelta.module; + + // Remove codelength of old and new module before changes + deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0); + deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0); + // Add codelength of old module with current node removed + deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, -1); + // Add codelength of old module with current node added + deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 1); + + return deltaL + deltaMetaL * metaDataRate; +} + +double MetaMapEquation::getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing) +{ + auto& currentMetaCollection = m_moduleToMetaCollection[module]; + + double moduleMetaCodelength = 0.0; + + if (addRemoveOrNothing == 0) { + moduleMetaCodelength = currentMetaCollection.calculateEntropy(); + } + // If add or remove, do the change, calculate new codelength and then undo the change + else if (addRemoveOrNothing == 1) { + currentMetaCollection.add(current.metaCollection); + moduleMetaCodelength = currentMetaCollection.calculateEntropy(); + currentMetaCollection.remove(current.metaCollection); + } else { + currentMetaCollection.remove(current.metaCollection); + moduleMetaCodelength = currentMetaCollection.calculateEntropy(); + currentMetaCollection.add(current.metaCollection); + } + + return moduleMetaCodelength; +} + +// =================================================== +// Consolidation +// =================================================== + +void MetaMapEquation::updateCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) +{ + Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers); + + double deltaMetaL = 0.0; + + unsigned int oldModuleIndex = oldModuleDelta.module; + unsigned int newModuleIndex = newModuleDelta.module; + + // Remove codelength of old and new module before changes + deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0); + deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0); + + // Update meta data from moving node + updateMetaData(current, oldModuleIndex, newModuleIndex); + + // Add codelength of old and new module after changes + deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, 0); + deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 0); + + metaCodelength += deltaMetaL; +} + +void MetaMapEquation::updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex) +{ + // Remove meta id from old module (can be a set of meta ids when moving submodules in coarse tune) + auto& oldMetaCollection = m_moduleToMetaCollection[oldModuleIndex]; + oldMetaCollection.remove(current.metaCollection); + + // Add meta id to new module + auto& newMetaCollection = m_moduleToMetaCollection[bestModuleIndex]; + newMetaCollection.add(current.metaCollection); +} + +void MetaMapEquation::consolidateModules(std::vector& modules) +{ + for (auto& module : modules) { + if (module == nullptr) + continue; + module->metaCollection = m_moduleToMetaCollection[module->index]; + } +} + +#if 0 +// =================================================== +// Debug +// =================================================== + +void MetaMapEquation::printDebug() const +{ + std::cout << "MetaMapEquation\n"; + Base::printDebug(); +} +#endif + +} // namespace infomap diff --git a/vendor/infomap/src/core/MetaMapEquation.h b/vendor/infomap/src/core/MetaMapEquation.h new file mode 100644 index 0000000..29b780c --- /dev/null +++ b/vendor/infomap/src/core/MetaMapEquation.h @@ -0,0 +1,185 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef META_MAPEQUATION_H_ +#define META_MAPEQUATION_H_ + +#include "MapEquation.h" +#include "FlowData.h" +#include "../utils/Log.h" +#include "../utils/MetaCollection.h" +#include +#include +#include +#include + +namespace infomap { + +class InfoNode; + +class MetaMapEquation : private MapEquation<> { + using Base = MapEquation<>; + +public: + using FlowDataType = FlowData; + using DeltaFlowDataType = DeltaFlow; + + // =================================================== + // Getters + // =================================================== + + using Base::getIndexCodelength; + + double getModuleCodelength() const override; + + double getCodelength() const override; + + double getMetaCodelength(bool unweighted = false) const + { + return unweighted ? metaCodelength : metaDataRate * metaCodelength; + }; + + // =================================================== + // IO + // =================================================== + + std::ostream& print(std::ostream& out) const override; + friend std::ostream& operator<<(std::ostream&, const MetaMapEquation&); + + // =================================================== + // Init + // =================================================== + + void init(const Config& config) override; + + void initTree(InfoNode& root) override; + + void initNetwork(InfoNode& root) override; + + using Base::initSuperNetwork; + + using Base::initSubNetwork; + + void initPartition(std::vector& nodes) override; + + // =================================================== + // Codelength + // =================================================== + + double calcCodelength(const InfoNode& parent) const override; + + using Base::addMemoryContributions; + + double getDeltaCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + // =================================================== + // Consolidation + // =================================================== + + void updateCodelengthOnMovingNode(InfoNode& current, + DeltaFlow& oldModuleDelta, + DeltaFlow& newModuleDelta, + std::vector& moduleFlowData, + std::vector& moduleMembers) override; + + void consolidateModules(std::vector& modules) override; + +#if 0 + // =================================================== + // Debug + // =================================================== + + void printDebug() const override; +#endif + +private: + // =================================================== + // Private member functions + // =================================================== + double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override; + + // =================================================== + // Init + // =================================================== + + void initMetaNodes(InfoNode& root); + + void initPartitionOfMetaNodes(std::vector& nodes); + + // =================================================== + // Codelength + // =================================================== + + void calculateCodelength(std::vector& nodes) override; + + using Base::calculateCodelengthTerms; + + using Base::calculateCodelengthFromCodelengthTerms; + + // =================================================== + // Consolidation + // =================================================== + + void updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex); + +public: + // =================================================== + // Public member variables + // =================================================== + + using Base::codelength; + using Base::indexCodelength; + using Base::moduleCodelength; + +private: + // =================================================== + // Private member functions + // =================================================== + + /** + * Get meta codelength of module of current node + * @param addRemoveOrNothing +1, -1 or 0 to calculate codelength + * as if current node was added, removed or untouched in current module + */ + double getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing); + + // =================================================== + // Private member variables + // =================================================== + + using Base::enter_log_enter; + using Base::enterFlow; + using Base::enterFlow_log_enterFlow; + using Base::exit_log_exit; + using Base::flow_log_flow; // node.(flow + exitFlow) + using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same + + // For hierarchical + using Base::exitNetworkFlow; + using Base::exitNetworkFlow_log_exitNetworkFlow; + + // For meta data + using ModuleMetaMap = std::map; // moduleId -> (metaId -> count) + + ModuleMetaMap m_moduleToMetaCollection; + + unsigned int numMetaDataDimensions = 0; + double metaDataRate = 1.0; + bool weightByFlow = true; + double metaCodelength = 0.0; + double m_unweightedNodeFlow = 0.0; +}; + +} // namespace infomap + +#endif // META_MAPEQUATION_H_ diff --git a/vendor/infomap/src/core/StateNetwork.cpp b/vendor/infomap/src/core/StateNetwork.cpp new file mode 100644 index 0000000..464428c --- /dev/null +++ b/vendor/infomap/src/core/StateNetwork.cpp @@ -0,0 +1,329 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "StateNetwork.h" +#include "../utils/FlowCalculator.h" +#include "../utils/Log.h" +#include "../io/SafeFile.h" +#include +#include + +namespace infomap { + +std::pair StateNetwork::addStateNode(const StateNode& node) +{ + auto ret = m_nodes.insert(StateNetwork::NodeMap::value_type(node.id, node)); + if (ret.second) { + // If state node didn't exist, also create the associated physical node + addPhysicalNode(node.physicalId); + if (node.id != node.physicalId) { + m_haveMemoryInput = true; + } + } + return ret; +} + +std::pair StateNetwork::addStateNode(unsigned int id, unsigned int physId) +{ + m_higherOrderInputMethodCalled = true; + return addStateNode(StateNode(id, physId)); +} + +std::pair StateNetwork::addNode(unsigned int id) +{ + return addStateNode(StateNode(id)); +} + +std::pair StateNetwork::addNode(unsigned int id, double weight) +{ + auto ret = addNode(id); + auto& node = ret.first->second; + node.weight = weight; + return ret; +} + +std::pair StateNetwork::addNode(unsigned int id, std::string name) +{ + m_names[id] = std::move(name); + return addNode(id); +} + +std::pair StateNetwork::addNode(unsigned int id, std::string name, double weight) +{ + m_names[id] = std::move(name); + return addNode(id, weight); +} + +StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId) +{ + auto& physNode = m_physNodes[physId]; + physNode.physId = physId; + m_sumNodeWeight += 1.0; + return physNode; +} + +StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight) +{ + auto& physNode = addPhysicalNode(physId); + physNode.weight = weight; + m_sumNodeWeight += weight; + return physNode; +} + +StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, const std::string& name) +{ + auto& physNode = addPhysicalNode(physId); + m_names[physId] = name; + m_sumNodeWeight += 1.0; + return physNode; +} + +StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight, const std::string& name) +{ + auto& physNode = addPhysicalNode(physId); + physNode.weight = weight; + m_sumNodeWeight += weight; + m_names[physId] = name; + return physNode; +} + +std::pair::iterator, bool> StateNetwork::addName(unsigned int id, const std::string& name) +{ + return m_names.insert(std::make_pair(id, name)); +} + +bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, double weight) +{ + if (weight < m_config.weightThreshold || weight <= 0) { + ++m_numLinksIgnoredByWeightThreshold; + m_totalLinkWeightIgnored += weight; + return false; + } + m_totalLinkWeightAdded += weight; + + if (sourceId == targetId) { + ++m_numSelfLinksFound; + if (m_config.noSelfLinks) { + return false; + } + ++m_numSelfLinks; + m_sumSelfLinkWeight += weight; + } + + addNode(sourceId); + addNode(targetId); + + ++m_numLinks; + m_sumLinkWeight += weight; + + bool addedNewLink = true; + + // Aggregate link weights if they are defined more than once + auto& outLinks = m_nodeLinkMap[sourceId]; + if (outLinks.empty()) { + outLinks[targetId] = weight; + } else { + auto ret = outLinks.insert(std::make_pair(StateNode(targetId), LinkData(weight))); + auto& linkData = ret.first->second; + if (!ret.second) { + linkData.weight += weight; + ++m_numAggregatedLinks; + --m_numLinks; + addedNewLink = false; + } else { + linkData.weight = weight; + } + } + m_outWeights[sourceId] += weight; + return addedNewLink; +} + +bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight) +{ + return addLink(sourceId, targetId, static_cast(weight)); +} + +bool StateNetwork::removeLink(unsigned int sourceId, unsigned int targetId) +{ + auto itSource = m_nodeLinkMap.find(sourceId); + if (itSource == m_nodeLinkMap.end()) { + return false; + } + + auto& targetMap = itSource->second; + auto itTarget = targetMap.find(targetId); + + if (itTarget == targetMap.end()) { + return false; + } + + double weight = itTarget->second.weight; + + targetMap.erase(itTarget); + if (targetMap.empty()) { + m_nodeLinkMap.erase(itSource); + } else { + --m_numAggregatedLinks; + } + + --m_numLinks; + m_sumLinkWeight -= weight; + m_totalLinkWeightAdded -= weight; + + if (sourceId == targetId) { + --m_numSelfLinksFound; + --m_numSelfLinks; + m_sumSelfLinkWeight -= weight; + } + + m_outWeights[sourceId] -= weight; + + return true; +} + +bool StateNetwork::undirectedToDirected() +{ + // Collect links in separate data structure to not risk iterating newly added links + if (!m_config.isUndirectedFlow()) { + return false; + } + std::deque oppositeLinks; + for (auto& linkIt : m_nodeLinkMap) { + unsigned int sourceId = linkIt.first.id; + const auto& subLinks = linkIt.second; + for (auto& subIt : subLinks) { + unsigned int targetId = subIt.first.id; + if (targetId == sourceId) { + continue; // Self-links are treated as directed on undirected networks + } + double weight = subIt.second.weight; + oppositeLinks.emplace_back(targetId, sourceId, weight); + } + } + for (auto& link : oppositeLinks) { + addLink(link.source, link.target, link.weight); + } + return true; +} + +void StateNetwork::clearLinks() +{ + m_nodeLinkMap.clear(); +} + +void StateNetwork::clear() +{ + m_nodes.clear(); + m_nodeLinkMap.clear(); + m_physNodes.clear(); + m_outWeights.clear(); + m_names.clear(); + + m_haveDirectedInput = false; + m_haveMemoryInput = false; + m_numStateNodesFound = 0; + m_numLinks = 0; + m_numSelfLinksFound = 0; + m_sumLinkWeight = 0.0; + m_numSelfLinks = 0; + m_sumSelfLinkWeight = 0.0; + m_numAggregatedLinks = 0; + m_totalLinkWeightAdded = 0.0; + m_numLinksIgnoredByWeightThreshold = 0; + m_totalLinkWeightIgnored = 0.0; +} + +void StateNetwork::writeStateNetwork(const std::string& filename) const +{ + if (filename.empty()) + throw std::runtime_error("writeStateNetwork called with empty filename"); + + SafeOutFile outFile(filename); + + outFile << "# v" << INFOMAP_VERSION << "\n" + << "# ./Infomap " << m_config.parsedString << "\n"; + + if (!m_names.empty()) { + outFile << "*Vertices\n"; + for (auto& nameIt : m_names) { + auto& physId = nameIt.first; + auto& name = nameIt.second; + outFile << physId << " \"" << name << "\"\n"; + } + } + + outFile << "*States\n"; + outFile << "# stateId physicalId\n"; + for (const auto& nodeIt : nodes()) { + const auto& node = nodeIt.second; + outFile << node.id << " " << node.physicalId; + // Optional name + if (!node.name.empty()) + outFile << " \"" << node.name << "\""; + outFile << "\n"; + } + + outFile << "*Links\n"; + for (auto& linkIt : m_nodeLinkMap) { + for (auto& subIt : linkIt.second) { + outFile << linkIt.first.id << " " << subIt.first.id << " " << subIt.second.weight << "\n"; + } + } +} + +void StateNetwork::writePajekNetwork(const std::string& filename, bool printFlow) const +{ + if (filename.empty()) + throw std::runtime_error("writePajekNetwork called with empty filename"); + + SafeOutFile outFile(filename); + + outFile << "# v" << INFOMAP_VERSION << "\n" + << "# ./Infomap " << m_config.parsedString << "\n"; + if (haveMemoryInput()) + outFile << "# State network as physical network\n"; + + outFile << "*Vertices\n"; + outFile << "#id name " << (printFlow ? "flow" : "weight") << "\n"; + for (const auto& nodeIt : nodes()) { + const auto& node = nodeIt.second; + outFile << node.id << " \""; + // Name, default to id + const auto& nameIt = haveMemoryInput() ? m_names.end() : m_names.find(node.id); + if (nameIt != m_names.end()) + outFile << nameIt->second; + else + outFile << node.id; + outFile << "\" " << (printFlow ? node.flow : node.weight) << "\n"; + } + + outFile << (m_config.printAsUndirected() ? "*Edges" : "*Arcs") << "\n"; + outFile << "#source target " << (printFlow ? "flow" : "weight") << "\n"; + for (auto& linkIt : m_nodeLinkMap) { + for (auto& subIt : linkIt.second) { + auto& linkData = subIt.second; + outFile << linkIt.first.id << " " << subIt.first.id << " " << (printFlow ? linkData.flow : linkData.weight) << "\n"; + } + } +} + +std::pair StateNetwork::addStateNodeWithAutogeneratedId(unsigned int physId) +{ + // Keys sorted with std::less comparator, so last key is the largest + unsigned int stateId = m_nodes.empty() ? 0 : m_nodes.crbegin()->first + 1; + return addStateNode(stateId, physId); +} + +std::pair StateNetwork::addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2) +{ + unsigned int stateId = physId << (numLayersLog2 + 1) | layerId; + return addStateNode(stateId, physId); +} + +} // namespace infomap diff --git a/vendor/infomap/src/core/StateNetwork.h b/vendor/infomap/src/core/StateNetwork.h new file mode 100644 index 0000000..97b4308 --- /dev/null +++ b/vendor/infomap/src/core/StateNetwork.h @@ -0,0 +1,216 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef STATE_NETWORK_H_ +#define STATE_NETWORK_H_ + +#include "../io/Config.h" +#include +#include +#include +#include +#include + +namespace infomap { + +class StateNetwork { +public: + struct StateNode { + unsigned int id = 0; + unsigned int physicalId = 0; + std::string name; + unsigned int layerId = 0; + double weight = 1.0; + double flow = 0.0; + double enterFlow = 0.0; + double exitFlow = 0.0; + double teleFlow = 0.0; + + StateNode(unsigned int id = 0) : id(id), physicalId(id) { } + + StateNode(unsigned int id, unsigned int physicalId) : id(id), physicalId(physicalId) { } + + StateNode(unsigned int id, unsigned int physicalId, std::string name) : id(id), physicalId(physicalId), name(std::move(name)) { } + + bool operator==(const StateNode& rhs) const { return id == rhs.id; } + bool operator!=(const StateNode& rhs) const { return id != rhs.id; } + bool operator<(const StateNode& rhs) const { return id < rhs.id; } + }; + + struct PhysNode { + unsigned int physId = 0; + double weight = 1.0; + PhysNode(unsigned int physId) : physId(physId) { } + PhysNode(unsigned int physId, double weight) : physId(physId), weight(weight) { } + PhysNode(double weight = 1.0) : weight(weight) { } + }; + + struct LinkData { + double weight = 1.0; + double flow = 0.0; + + LinkData(double weight = 1.0) : weight(weight) { } + + LinkData& operator+=(double w) + { + weight += w; + return *this; + } + }; + + struct StateLink { + StateLink(unsigned int sourceIndex = 0, unsigned int targetIndex = 0, double weight = 0.0) + : source(sourceIndex), + target(targetIndex), + weight(weight), + flow(weight) { } + + unsigned int source; + unsigned int target; + double weight; + double flow; + }; + + // Unique state id to state node + using NodeMap = std::map; + using OutLinkMap = std::map; + using NodeLinkMap = std::map; + +protected: + friend class FlowCalculator; + // Config + Config m_config; + // Network + bool m_haveDirectedInput = false; + bool m_haveMemoryInput = false; + bool m_higherOrderInputMethodCalled = false; + NodeMap m_nodes; // Nodes indexed by state id (equal physical id for first-order networks) + NodeLinkMap m_nodeLinkMap; + unsigned int m_numStateNodesFound = 0; + double m_sumNodeWeight = 0.0; + unsigned int m_numLinks = 0; + double m_sumLinkWeight = 0.0; + unsigned int m_numSelfLinksFound = 0; + unsigned int m_numSelfLinks = 0; + double m_sumSelfLinkWeight = 0.0; + unsigned int m_numAggregatedLinks = 0; + double m_totalLinkWeightAdded = 0.0; + unsigned int m_numLinksIgnoredByWeightThreshold = 0; + double m_totalLinkWeightIgnored = 0.0; + std::map m_outWeights; + bool m_haveNodeWeights = false; + bool m_haveStateNodeWeights = false; + bool m_haveFileInput = false; + // Attributes + std::map m_names; + std::map m_physNodes; + + // Bipartite + unsigned int m_bipartiteStartId = 0; + +public: + StateNetwork() : m_config(Config()) { } + StateNetwork(Config config) : m_config(std::move(config)) { } + virtual ~StateNetwork() = default; + + StateNetwork(const StateNetwork&) = delete; + StateNetwork& operator=(const StateNetwork&) = delete; + StateNetwork(StateNetwork&&) = delete; + StateNetwork& operator=(StateNetwork&&) = delete; + + // Config + void setConfig(const Config& config) { m_config = config; } + + // Mutators + std::pair addStateNode(const StateNode& node); + std::pair addStateNode(unsigned int id, unsigned int physId); + std::pair addNode(unsigned int id); + std::pair addNode(unsigned int id, std::string name); + std::pair addNode(unsigned int id, double weight); + std::pair addNode(unsigned int id, std::string, double weight); + PhysNode& addPhysicalNode(unsigned int physId); + PhysNode& addPhysicalNode(unsigned int physId, double weight); + PhysNode& addPhysicalNode(unsigned int physId, const std::string& name); + PhysNode& addPhysicalNode(unsigned int physId, double weight, const std::string& name); + std::pair::iterator, bool> addName(unsigned int id, const std::string&); + bool addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0); + bool addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight); + + /** + * Remove link + * Note: It will not remove nodes if they become dangling + */ + bool removeLink(unsigned int sourceId, unsigned int targetId); + + // Expand each undirected link to two opposite directed links + bool undirectedToDirected(); + + /** + * Clear all network data and reset to default state. + */ + virtual void clear(); + + /** + * Clear link data but keep node data. + */ + virtual void clearLinks(); + + // Getters + const NodeMap& nodes() const { return m_nodes; } + unsigned int numNodes() const { return m_nodes.size(); } + unsigned int numPhysicalNodes() const { return m_physNodes.size(); } + double sumNodeWeight() const { return m_sumNodeWeight; } + const NodeLinkMap& nodeLinkMap() const { return m_nodeLinkMap; } + NodeLinkMap& nodeLinkMap() { return m_nodeLinkMap; } + unsigned int numLinks() const { return m_numLinks; } + double sumLinkWeight() const { return m_sumLinkWeight; } + unsigned int numSelfLinks() const { return m_numSelfLinks; } + double sumSelfLinkWeight() const { return m_sumSelfLinkWeight; } + // Use convention of counting self-links only once, treating them as directed + double sumWeightedDegree() const { return 2 * sumLinkWeight() - (m_config.isUndirectedFlow() ? sumSelfLinkWeight() : 0); } + unsigned int sumDegree() const { return 2 * numLinks() - (m_config.isUndirectedFlow() ? numSelfLinks() : 0); } + std::map& outWeights() { return m_outWeights; } + std::map& names() { return m_names; } + const std::map& names() const { return m_names; } + bool haveNodeWeights() const { return m_haveNodeWeights; } + bool haveStateNodeWeights() const { return m_haveStateNodeWeights; } + bool haveFileInput() const { return m_haveFileInput; } + + virtual const std::map>& metaData() const = 0; + + bool haveDirectedInput() const { return m_haveDirectedInput; } + bool haveMemoryInput() const { return m_haveMemoryInput; } + bool higherOrderInputMethodCalled() const { return m_higherOrderInputMethodCalled; } + // Bipartite + bool isBipartite() const { return m_bipartiteStartId > 0; } + unsigned int bipartiteStartId() const { return m_bipartiteStartId; } + void setBipartiteStartId(unsigned int value) { m_bipartiteStartId = value; } + + /** + * Write state network to file. + */ + void writeStateNetwork(const std::string& filename) const; + + /** + * Write state network as first-order Pajek network, where + * state nodes are treated as physical nodes. + * For a non-memory input, the state nodes are equivalent to + * physical nodes. + */ + void writePajekNetwork(const std::string& filename, bool printFlow = false) const; + +protected: + std::pair addStateNodeWithAutogeneratedId(unsigned int physId); + + std::pair addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2); +}; + +} // namespace infomap + +#endif // STATE_NETWORK_H_ diff --git a/vendor/infomap/src/core/iterators/InfomapIterator.cpp b/vendor/infomap/src/core/iterators/InfomapIterator.cpp new file mode 100644 index 0000000..433d9ed --- /dev/null +++ b/vendor/infomap/src/core/iterators/InfomapIterator.cpp @@ -0,0 +1,238 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "InfomapIterator.h" +#include "../InfoNode.h" +#include +#include // std::pair + +namespace infomap { + +InfomapIterator& InfomapIterator::operator++() noexcept +{ + const auto root = m_current->getInfomapRoot(); + auto current = root ? root : m_current; + + if (current->firstChild) { + current = current->firstChild; + ++m_depth; + m_path.push_back(1); + } else { + // Current node is a leaf + // Presupposes that the next pointer can't reach out from the current parent. + + bool tryNext = true; + + while (tryNext) { + tryNext = false; + + while (!current->next) { + if (current->owner) { + current = current->owner; + + if (current == m_root) { // Check if back to beginning + m_current = nullptr; + return *this; + } + + tryNext = true; + break; + } + if (current->parent) { + current = current->parent; + --m_depth; + m_path.pop_back(); + + if (current == m_root) { // Check if back to beginning + m_current = nullptr; + return *this; + } + } else { // null also if no children in first place + m_current = nullptr; + return *this; + } + } + } + + current = current->next; + + if (!current->isLeaf() && (static_cast(m_moduleIndexLevel) >= m_depth)) { + ++m_moduleIndex; + } + + ++m_path.back(); + } + + m_current = current; + return *this; +} + +double InfomapIterator::modularCentrality() const noexcept +{ + if (m_current->parent == nullptr) { + // The root node has no modular centrality + return 0.0; + } + + const auto p_m = m_current->parent->data.flow; + const auto p_u = m_current->data.flow; + const auto p_diff = p_m - p_u; + + if (p_diff > 0.0) { + return -p_diff * std::log2(p_diff / p_m); + } + + return 0.0; +} + +// ------------------------------------- +// InfomapModuleIterator +// ------------------------------------- + +InfomapIterator& InfomapModuleIterator::operator++() noexcept +{ + InfomapIterator::operator++(); + while (!isEnd() && m_current->isLeaf()) { + InfomapIterator::operator++(); + } + return *this; +} + +// ------------------------------------- +// InfomapLeafModuleIterator +// ------------------------------------- + +void InfomapLeafModuleIterator::init() noexcept +{ + while (!isEnd() && !m_current->isLeafModule()) { + InfomapIterator::operator++(); + } +} + +InfomapIterator& InfomapLeafModuleIterator::operator++() noexcept +{ + InfomapIterator::operator++(); + while (!isEnd() && !m_current->isLeafModule()) { + InfomapIterator::operator++(); + } + return *this; +} + +// ------------------------------------- +// InfomapLeafIterator +// ------------------------------------- + +void InfomapLeafIterator::init() noexcept +{ + while (!isEnd() && !m_current->isLeaf()) { + InfomapIterator::operator++(); + } +} + +InfomapIterator& InfomapLeafIterator::operator++() noexcept +{ + InfomapIterator::operator++(); + while (!isEnd() && !m_current->isLeaf()) { + InfomapIterator::operator++(); + } + return *this; +} + +// ------------------------------------- +// InfomapIteratorPhysical +// ------------------------------------- + +InfomapIterator& InfomapIteratorPhysical::operator++() noexcept +{ + if (m_physNodes.empty()) { + // Iterate modules + InfomapIterator::operator++(); + if (isEnd()) { + return *this; + } + if (m_current->isLeaf()) { + // Copy current iterator to restart after iterating through the leaf nodes + auto firstLeafIt = *this; + // If on a leaf node, loop through and aggregate to physical nodes + while (!isEnd() && m_current->isLeaf()) { + auto ret = m_physNodes.insert(std::make_pair(m_current->physicalId, InfoNode(*m_current))); + auto& physNode = ret.first->second; + if (ret.second) { + // New physical node, use same parent as the state leaf node + physNode.parent = m_current->parent; + } else { + // Not inserted, add flow to existing physical node + // TODO: If exitFlow should be correct, flow between memory nodes within same physical node should be subtracted. + physNode.data += m_current->data; + } + physNode.stateNodes.push_back(m_current->stateId); + InfomapIterator::operator++(); + } + // Store current iterator to continue with after iterating physical leaf nodes + m_oldIter = *this; + // Reset path/depth/moduleIndex to values for first leaf node + m_path = firstLeafIt.m_path; + m_depth = firstLeafIt.m_depth; + m_moduleIndex = firstLeafIt.m_moduleIndex; + // Set current node to the first physical node + m_physIter = m_physNodes.begin(); + m_current = &m_physIter->second; + } + } else { + // Iterate physical nodes instead of leaf state nodes + ++m_physIter; + ++m_path.back(); + if (m_physIter == m_physNodes.end()) { + // End of leaf nodes + m_physNodes.clear(); + m_path.pop_back(); + // reset iterator to the one after the leaf nodes + *this = m_oldIter; + } else { + // Set iterator node to the currently iterated physical node + m_current = &m_physIter->second; + } + } + return *this; +} + +// ------------------------------------- +// InfomapLeafIteratorPhysical +// ------------------------------------- + +void InfomapLeafIteratorPhysical::init() noexcept +{ + while (!isEnd() && !m_current->isLeaf()) { + InfomapIteratorPhysical::operator++(); + } +} + +InfomapIterator& InfomapLeafIteratorPhysical::operator++() noexcept +{ + InfomapIteratorPhysical::operator++(); + while (!isEnd() && !m_current->isLeaf()) { + InfomapIteratorPhysical::operator++(); + } + return *this; +} + +// ------------------------------------- +// InfomapParentIterator +// ------------------------------------- + +InfomapParentIterator& InfomapParentIterator::operator++() noexcept +{ + m_current = m_current->parent; + if (m_current != nullptr && m_current->owner != nullptr) { + m_current = m_current->owner; + } + return *this; +} + +} // namespace infomap diff --git a/vendor/infomap/src/core/iterators/InfomapIterator.h b/vendor/infomap/src/core/iterators/InfomapIterator.h new file mode 100644 index 0000000..ac7f6d1 --- /dev/null +++ b/vendor/infomap/src/core/iterators/InfomapIterator.h @@ -0,0 +1,341 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_ITERATOR_H_ +#define INFOMAP_ITERATOR_H_ + +#include +#include +#include + +namespace infomap { + +class InfoNode; + +/** + * Pre processing depth first iterator that explores sub-Infomap instances + * Note: + * This iterator presupposes that the next pointer of a node can't reach a node with a different parent. + */ +struct InfomapIterator { +protected: + InfoNode* m_root = nullptr; + InfoNode* m_current = nullptr; + int m_moduleIndexLevel = -1; + unsigned int m_moduleIndex = 0; + std::deque m_path; // The tree path to current node (indexing starting from one!) + unsigned int m_depth = 0; + +public: + InfomapIterator() = default; + + InfomapIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) + : m_root(nodePointer), m_current(nodePointer), m_moduleIndexLevel(moduleIndexLevel) { } + + virtual ~InfomapIterator() = default; + InfomapIterator(const InfomapIterator&) = default; + InfomapIterator& operator=(const InfomapIterator&) = default; + InfomapIterator(InfomapIterator&&) noexcept = default; + InfomapIterator& operator=(InfomapIterator&&) noexcept = default; + + InfoNode* current() noexcept { return m_current; } + + const InfoNode* current() const noexcept { return m_current; } + + InfoNode& operator*() noexcept { return *m_current; } + + const InfoNode& operator*() const noexcept { return *m_current; } + + InfoNode* operator->() noexcept { return m_current; } + + const InfoNode* operator->() const noexcept { return m_current; } + + bool operator==(const InfomapIterator& other) const noexcept { return m_current == other.m_current; } + + bool operator!=(const InfomapIterator& other) const noexcept { return m_current != other.m_current; } + + virtual InfomapIterator& operator++() noexcept; + + virtual InfomapIterator operator++(int) noexcept + { + InfomapIterator copy(*this); + ++(*this); + return copy; + } + + virtual InfomapIterator& stepForward() noexcept + { + ++(*this); + return *this; + } + + const std::deque& path() const noexcept { return m_path; } + + unsigned int moduleIndex() const noexcept { return m_moduleIndex; } + + unsigned int moduleId() const noexcept { return m_moduleIndex + 1; } + + unsigned int childIndex() const noexcept { return m_path.empty() ? 0 : m_path.back() - 1; } + + unsigned int depth() const noexcept { return m_depth; } + + double modularCentrality() const noexcept; + + bool isEnd() const noexcept { return m_current == nullptr; } +}; + +struct InfomapModuleIterator : public InfomapIterator { +public: + InfomapModuleIterator() : InfomapIterator() { } + + InfomapModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) : InfomapIterator(nodePointer, moduleIndexLevel) { } + + ~InfomapModuleIterator() override = default; + InfomapModuleIterator(const InfomapModuleIterator&) = default; + InfomapModuleIterator& operator=(const InfomapModuleIterator&) = default; + InfomapModuleIterator(InfomapModuleIterator&&) = default; + InfomapModuleIterator& operator=(InfomapModuleIterator&&) = default; + + InfomapIterator& operator++() noexcept override; + + InfomapIterator operator++(int) noexcept override + { + InfomapModuleIterator copy(*this); + ++(*this); + return std::move(copy); + } + + using InfomapIterator::childIndex; + using InfomapIterator::current; + using InfomapIterator::depth; + using InfomapIterator::modularCentrality; + using InfomapIterator::path; +}; + +struct InfomapLeafModuleIterator : public InfomapIterator { +public: + InfomapLeafModuleIterator() : InfomapIterator() { } + + InfomapLeafModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) + : InfomapIterator(nodePointer, moduleIndexLevel) { init(); } + + ~InfomapLeafModuleIterator() override = default; + InfomapLeafModuleIterator(const InfomapLeafModuleIterator& other) : InfomapIterator(other) { init(); } + InfomapLeafModuleIterator& operator=(const InfomapLeafModuleIterator&) = default; + InfomapLeafModuleIterator(InfomapLeafModuleIterator&&) = default; + InfomapLeafModuleIterator& operator=(InfomapLeafModuleIterator&&) = default; + + /** + * Iterate to first leaf module + */ + void init() noexcept; + + InfomapIterator& operator++() noexcept override; + + InfomapIterator operator++(int) noexcept override + { + InfomapLeafModuleIterator copy(*this); + ++(*this); + return std::move(copy); + } + + using InfomapIterator::childIndex; + using InfomapIterator::current; + using InfomapIterator::depth; + using InfomapIterator::modularCentrality; + using InfomapIterator::path; +}; + +struct InfomapLeafIterator : public InfomapIterator { +public: + InfomapLeafIterator() : InfomapIterator() { } + + InfomapLeafIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) + : InfomapIterator(nodePointer, moduleIndexLevel) { init(); } + + ~InfomapLeafIterator() override = default; + InfomapLeafIterator(const InfomapLeafIterator& other) : InfomapIterator(other) { init(); } + InfomapLeafIterator& operator=(const InfomapLeafIterator&) = default; + InfomapLeafIterator(InfomapLeafIterator&&) = default; + InfomapLeafIterator& operator=(InfomapLeafIterator&&) = default; + + /** + * Iterate to first leaf node + */ + void init() noexcept; + + InfomapIterator& operator++() noexcept override; + + InfomapIterator operator++(int) noexcept override + { + InfomapLeafIterator copy(*this); + ++(*this); + return std::move(copy); + } + + using InfomapIterator::childIndex; + using InfomapIterator::current; + using InfomapIterator::depth; + using InfomapIterator::modularCentrality; + using InfomapIterator::path; +}; + +/** + * Iterate over the whole tree, collecting physical nodes within same leaf modules + * Note: The physical nodes are created when entering the parent module and removed + * when leaving the module. The tree will not be modified. + */ +struct InfomapIteratorPhysical : public InfomapIterator { +protected: + std::map m_physNodes; + std::map::iterator m_physIter; + InfomapIterator m_oldIter; + +public: + InfomapIteratorPhysical() : InfomapIterator() { } + + InfomapIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1) + : InfomapIterator(nodePointer, moduleIndexLevel) { } + + ~InfomapIteratorPhysical() override = default; + InfomapIteratorPhysical(const InfomapIteratorPhysical&) = default; + InfomapIteratorPhysical(const InfomapIterator& other) : InfomapIterator(other) { } + + InfomapIteratorPhysical(InfomapIteratorPhysical&&) = default; + InfomapIteratorPhysical& operator=(const InfomapIteratorPhysical&) = default; + + // Don't allow moving from this iterator as we use the old iterator in operator++ + InfomapIteratorPhysical& operator=(InfomapIteratorPhysical&&) = delete; + + InfomapIteratorPhysical& operator=(const InfomapIterator& other) + { + InfomapIterator::operator=(other); + return *this; + } + + InfomapIterator& operator++() noexcept override; + + InfomapIterator operator++(int) noexcept override + { + InfomapIteratorPhysical copy(*this); + ++(*this); + return std::move(copy); + } + + using InfomapIterator::childIndex; + using InfomapIterator::current; + using InfomapIterator::depth; + using InfomapIterator::modularCentrality; + using InfomapIterator::path; +}; + +/** + * Iterate over all physical leaf nodes, joining physical nodes within same leaf modules + * Note: The physical nodes are created when entering the parent module and removed + * when leaving the module. The tree will not be modified. + */ +struct InfomapLeafIteratorPhysical : public InfomapIteratorPhysical { +public: + InfomapLeafIteratorPhysical() : InfomapIteratorPhysical() { } + + InfomapLeafIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1) + : InfomapIteratorPhysical(nodePointer, moduleIndexLevel) { init(); } + + InfomapLeafIteratorPhysical(const InfomapLeafIteratorPhysical& other) + : InfomapIteratorPhysical(other) { init(); } + + ~InfomapLeafIteratorPhysical() override = default; + InfomapLeafIteratorPhysical(InfomapLeafIteratorPhysical&&) = default; + InfomapLeafIteratorPhysical& operator=(const InfomapLeafIteratorPhysical&) = default; + + // Don't allow moving from this iterator as we use the old iterator in operator++ + InfomapLeafIteratorPhysical& operator=(InfomapLeafIteratorPhysical&&) = delete; + + /** + * Iterate to first leaf node + */ + void init() noexcept; + + InfomapIterator& operator++() noexcept override; + + InfomapIterator operator++(int) noexcept override + { + InfomapLeafIteratorPhysical copy(*this); + ++(*this); + return std::move(copy); + } + + using InfomapIteratorPhysical::childIndex; + using InfomapIteratorPhysical::current; + using InfomapIteratorPhysical::depth; + using InfomapIteratorPhysical::modularCentrality; + using InfomapIteratorPhysical::path; +}; + +/** + * Iterate parent by parent until it is nullptr, + * moving up through possible sub infomap instances + * on the way + */ +struct InfomapParentIterator { +protected: + InfoNode* m_current = nullptr; + +public: + InfomapParentIterator() = default; + + InfomapParentIterator(InfoNode* nodePointer) : m_current(nodePointer) { } + + ~InfomapParentIterator() = default; + + InfomapParentIterator(const InfomapParentIterator&) = default; + + InfomapParentIterator& operator=(const InfomapParentIterator&) = default; + + InfomapParentIterator(InfomapParentIterator&&) = default; + + InfomapParentIterator& operator=(InfomapParentIterator&&) = default; + + InfoNode* current() noexcept { return m_current; } + + const InfoNode* current() const noexcept { return m_current; } + + InfoNode& operator*() noexcept { return *m_current; } + + const InfoNode& operator*() const noexcept { return *m_current; } + + InfoNode* operator->() noexcept { return m_current; } + + const InfoNode* operator->() const noexcept { return m_current; } + + bool operator==(const InfomapParentIterator& other) const noexcept { return m_current == other.m_current; } + + bool operator!=(const InfomapParentIterator& other) const noexcept { return m_current != other.m_current; } + + InfomapParentIterator& operator++() noexcept; + + InfomapParentIterator operator++(int) noexcept + { + InfomapParentIterator copy(*this); + ++(*this); + return copy; + } + + InfomapParentIterator& stepForward() noexcept + { + ++(*this); + return *this; + } + + bool isEnd() const noexcept { return m_current == nullptr; } +}; + +} // namespace infomap + +#endif // INFOMAP_ITERATOR_H_ diff --git a/vendor/infomap/src/core/iterators/IterWrapper.h b/vendor/infomap/src/core/iterators/IterWrapper.h new file mode 100644 index 0000000..f043eb6 --- /dev/null +++ b/vendor/infomap/src/core/iterators/IterWrapper.h @@ -0,0 +1,32 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef ITER_WRAPPER_H_ +#define ITER_WRAPPER_H_ + +namespace infomap { + +template +class IterWrapper { + Iter m_begin, m_end; + +public: + IterWrapper(Iter begin, Iter end) : m_begin(begin), m_end(end) { } + + template + IterWrapper(Container& container) : m_begin(container.begin()), m_end(container.end()) { } + + Iter begin() noexcept { return m_begin; }; + + Iter end() noexcept { return m_end; }; +}; + +} // namespace infomap + +#endif // ITER_WRAPPER_H_ diff --git a/vendor/infomap/src/core/iterators/infomapIterators.h b/vendor/infomap/src/core/iterators/infomapIterators.h new file mode 100644 index 0000000..2d13ffe --- /dev/null +++ b/vendor/infomap/src/core/iterators/infomapIterators.h @@ -0,0 +1,369 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMAP_ITERATORS_H_ +#define INFOMAP_ITERATORS_H_ + +#include "treeIterators.h" +#include +#include + +namespace infomap { + +/** + * Child iterator. + */ +template // pointer or const pointer +class InfomapChildIterator { + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename iterator_traits::value_type; + using difference_type = typename iterator_traits::difference_type; + using reference = typename iterator_traits::reference; + using pointer = typename iterator_traits::pointer; + +protected: + NodePointerType m_root = nullptr; + NodePointerType m_current = nullptr; + +public: + InfomapChildIterator() = default; + + InfomapChildIterator(const NodePointerType& nodePointer) + : m_root(nodePointer), m_current(nodePointer) { init(); } + + InfomapChildIterator(const InfomapChildIterator& other) + : m_root(other.m_root), m_current(other.m_current) { } + + InfomapChildIterator& operator=(const InfomapChildIterator& other) + { + m_root = other.m_root; + m_current = other.m_current; + return *this; + } + + void init() + { + if (m_root != nullptr) { + NodePointerType infomapRoot = m_root->getInfomapRoot(); + if (infomapRoot != nullptr) { + m_root = infomapRoot; + } + } + m_current = m_root == nullptr ? nullptr : m_root->firstChild; + } + + pointer current() const { return m_current; } + + reference operator*() const { return *m_current; } + + pointer operator->() const { return m_current; } + + bool operator==(const InfomapChildIterator& rhs) const { return m_current == rhs.m_current; } + + bool operator!=(const InfomapChildIterator& rhs) const { return m_current != rhs.m_current; } + + bool isEnd() const { return m_current == nullptr; } + + InfomapChildIterator& operator++() + { + m_current = m_current->next; + if (m_current != nullptr && m_current->parent != m_root) { + m_current = nullptr; + } + return *this; + } + + InfomapChildIterator operator++(int) + { + InfomapChildIterator copy(*this); + ++(*this); + return copy; + } + + InfomapChildIterator& operator--() + { + m_current = m_current->previous; + if (m_current != nullptr && m_current->parent != m_root) { + m_current = nullptr; + } + return *this; + } + + InfomapChildIterator operator--(int) + { + InfomapChildIterator copy(*this); + --(*this); + return copy; + } +}; + +/** + * Pre processing depth first iterator that explores sub-Infomap instances + * Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent. + */ +template +class InfomapClusterIterator : public DepthFirstIteratorBase { +protected: + using Base = DepthFirstIteratorBase; + + unsigned int m_moduleIndex = 0; + int m_moduleIndexLevel = -1; + + using Base::m_current; + using Base::m_depth; + +public: + InfomapClusterIterator() : Base() { } + + InfomapClusterIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1) + : Base(nodePointer), m_moduleIndexLevel(moduleIndexLevel) + { + init(); + } + + InfomapClusterIterator(const InfomapClusterIterator& other) + : Base(other), m_moduleIndex(other.m_moduleIndex), m_moduleIndexLevel(other.m_moduleIndexLevel) { } + + virtual void init() { moveToInfomapRootIfExist(); } + + void moveToInfomapRootIfExist() + { + if (m_current != nullptr) { + NodePointerType infomapRoot = m_current->getInfomapRoot(); + if (infomapRoot != nullptr) { + m_current = infomapRoot; + } + } + } + + InfomapClusterIterator& operator=(const InfomapClusterIterator& other) + { + Base::operator=(other); + m_moduleIndex = other.m_moduleIndex; + m_moduleIndexLevel = other.m_moduleIndexLevel; + return *this; + } + + InfomapClusterIterator& operator++() + { + NodePointerType curr = Base::m_current; + + if (curr->firstChild != nullptr) { + curr = curr->firstChild; + ++m_depth; + } else { + // Current node is a leaf + // Presupposes that the next pointer can't reach out from the current parent. + tryNext: + while (curr->next == nullptr) { + if (curr->parent != nullptr) { + curr = curr->parent; + --m_depth; + if (curr == Base::m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + if (m_moduleIndexLevel < 0) { + if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom + ++m_moduleIndex; + } else if (static_cast(m_moduleIndexLevel) == m_depth) + ++m_moduleIndex; + } else { + NodePointerType infomapOwner = curr->owner; + if (infomapOwner != nullptr) { + curr = infomapOwner; + if (curr == Base::m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + goto tryNext; + } else // null also if no children in first place + { + m_current = nullptr; + return *this; + } + } + } + curr = curr->next; + } + m_current = curr; + moveToInfomapRootIfExist(); + return *this; + } + + InfomapClusterIterator operator++(int) + { + InfomapClusterIterator copy(*this); + ++(*this); + return copy; + } + + InfomapClusterIterator next() + { + InfomapClusterIterator copy(*this); + return ++copy; + } + + InfomapClusterIterator& stepForward() + { + ++(*this); + return *this; + } + + unsigned int moduleIndex() const + { + return m_moduleIndex; + } +}; + +/** + * Pre processing depth first iterator that explores sub-Infomap instances + * Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent. + */ +template +class InfomapDepthFirstIterator : public DepthFirstIteratorBase { +protected: + using Base = DepthFirstIteratorBase; + + std::deque m_path; // The child index path to current node + unsigned int m_moduleIndex = 0; + int m_moduleIndexLevel = -1; + + using Base::m_current; + using Base::m_depth; + +public: + InfomapDepthFirstIterator() : Base() { } + + InfomapDepthFirstIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1) + : Base(nodePointer), + m_moduleIndexLevel(moduleIndexLevel) + { + init(); + } + + InfomapDepthFirstIterator(const InfomapDepthFirstIterator& other) + : Base(other), + m_path(other.m_path), + m_moduleIndex(other.m_moduleIndex), + m_moduleIndexLevel(other.m_moduleIndexLevel) + { + } + + InfomapDepthFirstIterator& operator=(const InfomapDepthFirstIterator& other) + { + Base::operator=(other); + m_path = other.m_path; + m_moduleIndex = other.m_moduleIndex; + m_moduleIndexLevel = other.m_moduleIndexLevel; + return *this; + } + + virtual void init() + { + moveToInfomapRootIfExist(); + } + + void moveToInfomapRootIfExist() + { + if (m_current != nullptr) { + NodePointerType infomapRoot = m_current->getInfomapRoot(); + if (infomapRoot != nullptr) { + m_current = infomapRoot; + } + } + } + + InfomapDepthFirstIterator& operator++() + { + NodePointerType curr = Base::m_current; + + if (curr->firstChild != nullptr) { + curr = curr->firstChild; + ++m_depth; + m_path.push_back(0); + } else { + // Current node is a leaf + // Presupposes that the next pointer can't reach out from the current parent. + tryNext: + while (curr->next == nullptr) { + if (curr->parent != nullptr) { + curr = curr->parent; + --m_depth; + m_path.pop_back(); + if (curr == Base::m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + if (m_moduleIndexLevel < 0) { + if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom + ++m_moduleIndex; + } else if (static_cast(m_moduleIndexLevel) == m_depth) + ++m_moduleIndex; + } else { + NodePointerType infomapOwner = curr->owner; + if (infomapOwner != nullptr) { + curr = infomapOwner; + if (curr == Base::m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + goto tryNext; + } else // null also if no children in first place + { + m_current = nullptr; + return *this; + } + } + } + curr = curr->next; + ++m_path.back(); + } + m_current = curr; + moveToInfomapRootIfExist(); + return *this; + } + + InfomapDepthFirstIterator operator++(int) + { + InfomapDepthFirstIterator copy(*this); + ++(*this); + return copy; + } + + InfomapDepthFirstIterator next() + { + InfomapDepthFirstIterator copy(*this); + return ++copy; + } + + InfomapDepthFirstIterator& stepForward() + { + ++(*this); + return *this; + } + + const std::deque& path() const + { + return m_path; + } + + unsigned int moduleIndex() const + { + return m_moduleIndex; + } +}; + +} // namespace infomap + +#endif // INFOMAP_ITERATORS_H_ diff --git a/vendor/infomap/src/core/iterators/treeIterators.h b/vendor/infomap/src/core/iterators/treeIterators.h new file mode 100644 index 0000000..7717e20 --- /dev/null +++ b/vendor/infomap/src/core/iterators/treeIterators.h @@ -0,0 +1,628 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef TREE_ITERATORS_H_ +#define TREE_ITERATORS_H_ + +#include +#include +#include + +namespace infomap { + +#ifndef ASSERT +#include +#define ASSERT(x) assert(x) +#endif + +using std::iterator_traits; + +/** + * Child iterator. + */ +template // pointer or const pointer +class ChildIterator { + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename iterator_traits::value_type; + using difference_type = typename iterator_traits::difference_type; + using reference = typename iterator_traits::reference; + using pointer = typename iterator_traits::pointer; + +protected: + NodePointerType m_root = nullptr; + NodePointerType m_current = nullptr; + +public: + ChildIterator() = default; + + ChildIterator(const NodePointerType& nodePointer) + : m_root(nodePointer), m_current(nodePointer == nullptr ? nullptr : nodePointer->firstChild) { } + + ChildIterator(const ChildIterator& other) + : m_root(other.m_root), m_current(other.m_current) { } + + ChildIterator& operator=(const ChildIterator& other) + { + m_root = other.m_root; + m_current = other.m_current; + return *this; + } + + pointer current() const { return m_current; } + + reference operator*() const { return *m_current; } + + pointer operator->() const { return m_current; } + + bool operator==(const ChildIterator& rhs) const { return m_current == rhs.m_current; } + + bool operator!=(const ChildIterator& rhs) const { return !(m_current == rhs.m_current); } + + bool isEnd() const { return m_current == nullptr; } + + ChildIterator& operator++() + { + m_current = m_current->next; + if (m_current != nullptr && m_current->parent != m_root) { + m_current = nullptr; + } + return *this; + } + + ChildIterator operator++(int) + { + ChildIterator copy(*this); + ++(*this); + return copy; + } + + ChildIterator& operator--() + { + m_current = m_current->previous; + if (m_current != nullptr && m_current->parent != m_root) { + m_current = nullptr; + } + return *this; + } + + ChildIterator operator--(int) + { + ChildIterator copy(*this); + --(*this); + return copy; + } +}; + +/** + * Tree iterator. + */ +template // pointer or const pointer +class TreeIterator { + using iterator_category = std::forward_iterator_tag; + using value_type = typename iterator_traits::value_type; + using difference_type = typename iterator_traits::difference_type; + using reference = typename iterator_traits::reference; + using pointer = typename iterator_traits::pointer; + +protected: + NodePointerType m_root = nullptr; + NodePointerType m_current = nullptr; + int m_moduleIndexLevel = -1; + unsigned int m_moduleIndex = 0; + std::deque m_path; // The child index path to current node + unsigned int m_depth = 0; + +public: + TreeIterator() = default; + + TreeIterator(NodePointerType nodePointer, int moduleIndexLevel = -1) + : m_root(nodePointer), + m_current(nodePointer), + m_moduleIndexLevel(moduleIndexLevel) { } + + TreeIterator(const TreeIterator& other) + : m_root(other.m_root), + m_current(other.m_current), + m_moduleIndexLevel(other.m_moduleIndexLevel), + m_moduleIndex(other.m_moduleIndex), + m_path(other.m_path), + m_depth(other.m_depth) { } + + virtual ~TreeIterator() = default; + + TreeIterator& operator=(const TreeIterator& other) + { + m_root = other.m_root; + m_current = other.m_current; + m_moduleIndexLevel = other.m_moduleIndexLevel; + m_moduleIndex = other.m_moduleIndex; + m_path = other.m_path; + m_depth = other.m_depth; + return *this; + } + + pointer current() const { return m_current; } + + reference operator*() const { return *m_current; } + + pointer operator->() const { return m_current; } + + bool operator==(const TreeIterator& rhs) const { return m_current == rhs.m_current; } + + bool operator!=(const TreeIterator& rhs) const { return !(m_current == rhs.m_current); } + + const std::deque& path() const { return m_path; } + + unsigned int moduleIndex() const { return m_moduleIndex; } + + unsigned int depth() const { return m_depth; } + + bool isEnd() const { return m_current == nullptr; } + + TreeIterator& operator++() + { + NodePointerType curr = m_current; + NodePointerType infomapRoot = curr->getInfomapRoot(); + if (infomapRoot != nullptr) { + curr = infomapRoot; + } + + if (curr->firstChild != nullptr) { + curr = curr->firstChild; + ++m_depth; + m_path.push_back(0); + } else { + // Current node is a leaf + // Presupposes that the next pointer can't reach out from the current parent. + tryNext: + while (curr->next == nullptr) { + if (curr->parent != nullptr) { + curr = curr->parent; + --m_depth; + m_path.pop_back(); + if (curr == m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + if (m_moduleIndexLevel < 0) { + if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom + ++m_moduleIndex; + } else if (static_cast(m_moduleIndexLevel) == m_depth) + ++m_moduleIndex; + } else { + NodePointerType infomapOwner = curr->owner; + if (infomapOwner != nullptr) { + curr = infomapOwner; + if (curr == m_root) // Check if back to beginning + { + m_current = nullptr; + return *this; + } + goto tryNext; + } else // null also if no children in first place + { + m_current = nullptr; + return *this; + } + } + } + curr = curr->next; + ++m_path.back(); + } + m_current = curr; + return *this; + } + + TreeIterator operator++(int) + { + TreeIterator copy(*this); + ++(*this); + return copy; + } + + TreeIterator& stepForward() + { + ++(*this); + return *this; + } +}; + +/** + * Base node iterator. + */ +template +struct node_iterator_base { + using iterator_category = iterator_tag; + using value_type = typename iterator_traits::value_type; + using difference_type = typename iterator_traits::difference_type; + using reference = typename iterator_traits::reference; + using pointer = typename iterator_traits::pointer; + + node_iterator_base() : m_current(nullptr) { } + + node_iterator_base(const NodePointerType& nodePointer) : m_current(nodePointer) { } + + node_iterator_base(const node_iterator_base& other) : m_current(other.m_current) { } + + node_iterator_base& operator=(const node_iterator_base& other) + { + m_current = other.m_current; + return *this; + } + + virtual ~node_iterator_base() = default; + + pointer base() const { return m_current; } + + reference operator*() const { return *m_current; } + + pointer operator->() const { return m_current; } + + bool operator==(const node_iterator_base& rhs) const { return m_current == rhs.m_current; } + + bool operator!=(const node_iterator_base& rhs) const { return !(m_current == rhs.m_current); } + + bool isEnd() const { return m_current == nullptr; } + +protected: + NodePointerType m_current; +}; + +template +class DepthFirstIteratorBase : public node_iterator_base { + using Base = node_iterator_base; + +public: + DepthFirstIteratorBase() : Base(), m_root(nullptr), m_depth(0) { } + + DepthFirstIteratorBase(const NodePointerType& nodePointer) : Base(nodePointer), m_root(nodePointer), m_depth(0) { } + + DepthFirstIteratorBase(const DepthFirstIteratorBase& other) : Base(other), m_root(other.m_root), m_depth(other.m_depth) { } + + DepthFirstIteratorBase& operator=(const DepthFirstIteratorBase& other) + { + Base::operator=(other); + m_root = other.m_root; + m_depth = other.m_depth; + return *this; + } + + unsigned int depth() const { return m_depth; } + +protected: + NodePointerType m_root; + unsigned int m_depth; + using Base::m_current; +}; + +/** + * Pre processing depth first iterator + * Note: + * This iterator presupposes that the next pointer of a node can't reach a node with a different parent. + */ +template +class DepthFirstIterator : public DepthFirstIteratorBase { + using Base = DepthFirstIteratorBase; + +public: + DepthFirstIterator() : Base() { } + + DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { } + + DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { } + + DepthFirstIterator& operator=(const DepthFirstIterator& other) + { + Base::operator=(other); + return *this; + } + + DepthFirstIterator& operator++() + { + NodePointerType curr = Base::m_current; + if (curr->firstChild != nullptr) { + curr = curr->firstChild; + ++Base::m_depth; + } else { + // Presupposes that the next pointer can't reach out from the current parent. + while (curr->next == nullptr) { + curr = curr->parent; + --Base::m_depth; + if (curr == Base::m_root || curr == nullptr) // 0 if no children in first place + { + Base::m_current = nullptr; + return *this; + } + } + curr = curr->next; + } + Base::m_current = curr; + return *this; + } + + DepthFirstIterator operator++(int) + { + auto copy(*this); + ++(*this); + return copy; + } + + DepthFirstIterator next() + { + auto copy(*this); + return ++copy; + } +}; + +/** + * Post processing depth first iterator + * Note: + * This iterator presupposes that the next pointer of a node can't reach a node with a different parent. + */ +template +class DepthFirstIterator : public DepthFirstIteratorBase { + using Base = DepthFirstIteratorBase; + +public: + DepthFirstIterator() : Base() { } + + DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); } + + DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { } + + DepthFirstIterator& operator=(const DepthFirstIterator& other) + { + Base::operator=(other); + return *this; + } + + void init() + { + if (Base::m_current != nullptr) { + while (Base::m_current->firstChild != nullptr) { + Base::m_current = Base::m_current->firstChild; + ++Base::m_depth; + } + } + } + + DepthFirstIterator& operator++() + { + // The root should be the last node + if (Base::m_current == Base::m_root) { + Base::m_current = nullptr; + return *this; + } + + NodePointerType curr = Base::m_current; + if (curr->next != nullptr) { + curr = curr->next; + while (curr->firstChild != nullptr) { + curr = curr->firstChild; + ++Base::m_depth; + } + } else { + curr = curr->parent; + --Base::m_depth; + } + + Base::m_current = curr; + + return *this; + } + + DepthFirstIterator operator++(int) + { + DepthFirstIterator copy(*this); + ++(*this); + return copy; + } + + DepthFirstIterator + next() + { + DepthFirstIterator copy(*this); + return ++copy; + } +}; + +/** + * Leaf node iterator + */ +template +class LeafNodeIterator : public DepthFirstIteratorBase { + using Base = DepthFirstIteratorBase; + +public: + LeafNodeIterator() : Base() { } + + LeafNodeIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); } + + LeafNodeIterator(const LeafNodeIterator& other) : Base(other) { } + + LeafNodeIterator& operator=(const LeafNodeIterator& other) + { + Base::operator=(other); + return *this; + } + + void init() + { + if (Base::m_current != nullptr) { + while (Base::m_current->firstChild != nullptr) { + Base::m_current = Base::m_current->firstChild; + ++Base::m_depth; + } + } + } + + LeafNodeIterator& operator++() + { + ASSERT(Base::m_current != nullptr); + while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) { + Base::m_current = Base::m_current->parent; + --Base::m_depth; + if (Base::m_current == nullptr) + return *this; + } + + Base::m_current = Base::m_current->next; + + if (Base::m_current != nullptr) { + while (Base::m_current->firstChild != nullptr) { + Base::m_current = Base::m_current->firstChild; + ++Base::m_depth; + } + } + return *this; + } + + LeafNodeIterator operator++(int) + { + LeafNodeIterator copy(*this); + ++(*this); + return copy; + } + + LeafNodeIterator next() + { + LeafNodeIterator copy(*this); + return ++copy; + } +}; + +/** + * Leaf module iterator + */ +template +class LeafModuleIterator : public DepthFirstIteratorBase { + using Base = DepthFirstIteratorBase; + +public: + LeafModuleIterator() : Base() { } + + LeafModuleIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); } + + LeafModuleIterator(const LeafModuleIterator& other) : Base(other) { } + + LeafModuleIterator& operator=(const LeafModuleIterator& other) + { + Base::operator=(other); + init(); + return *this; + } + + void init() + { + if (Base::m_current != nullptr) { + if (Base::m_current->firstChild == nullptr) { + Base::m_current = nullptr; // End directly if no module + } else { + while (Base::m_current->firstChild->firstChild != nullptr) { + Base::m_current = Base::m_current->firstChild; + ++Base::m_depth; + } + } + } + } + + LeafModuleIterator& operator++() + { + ASSERT(Base::m_current != nullptr); + while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) { + Base::m_current = Base::m_current->parent; + --Base::m_depth; + if (Base::m_current == nullptr) + return *this; + } + + Base::m_current = Base::m_current->next; + + if (Base::m_current != nullptr) { + if (Base::m_current->firstChild == nullptr) { + Base::m_current = Base::m_current->parent; + } else { + while (Base::m_current->firstChild->firstChild != nullptr) { + Base::m_current = Base::m_current->firstChild; + ++Base::m_depth; + } + } + } + return *this; + } + + LeafModuleIterator operator++(int) + { + LeafModuleIterator copy(*this); + ++(*this); + return copy; + } + + LeafModuleIterator next() + { + LeafModuleIterator copy(*this); + return ++copy; + } +}; + +/** + * Sibling iterator. + */ +template // pointer or const pointer +class SiblingIterator : public node_iterator_base { + using Base = node_iterator_base; + +public: + using self_type = SiblingIterator; + + SiblingIterator() : Base() { } + + SiblingIterator(const NodePointerType& nodePointer) : Base(nodePointer) { } + + SiblingIterator(const SiblingIterator& other) : Base(other) { } + + SiblingIterator& operator=(const SiblingIterator& other) + { + Base::operator=(other); + return *this; + } + + SiblingIterator& operator++() + { + ASSERT(Base::m_current != nullptr); + Base::m_current = Base::m_current->next; + return *this; + } + + SiblingIterator operator++(int) + { + SiblingIterator copy(*this); + ++(*this); + return copy; + } + + SiblingIterator& operator--() + { + ASSERT(Base::m_current != nullptr); + Base::m_current = Base::m_current->previous; + return *this; + } + + SiblingIterator operator--(int) + { + SiblingIterator copy(*this); + --(*this); + return copy; + } +}; + +} // namespace infomap + +#endif // TREE_ITERATORS_H_ diff --git a/vendor/infomap/src/io/ClusterMap.cpp b/vendor/infomap/src/io/ClusterMap.cpp new file mode 100644 index 0000000..1de468c --- /dev/null +++ b/vendor/infomap/src/io/ClusterMap.cpp @@ -0,0 +1,189 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "ClusterMap.h" +#include "SafeFile.h" +#include "../utils/Log.h" +#include "../utils/FileURI.h" +#include + +namespace infomap { + +void ClusterMap::readClusterData(const std::string& filename, bool includeFlow, const std::map>* layerNodeToStateId) +{ + FileURI file(filename); + m_extension = file.getExtension(); + if (m_extension == "tree" || m_extension == "ftree") { + return readTree(filename, includeFlow, layerNodeToStateId); + } + if (m_extension == "clu") { + return readClu(filename, includeFlow, layerNodeToStateId); + } + throw std::runtime_error(io::Str() << "Input cluster data from file '" << filename << "' is of unknown extension '" << m_extension << "'. Must be 'clu' or 'tree'."); +} + +/** + * Sample from .tree file +# Codelength = 3.46227314 bits. +# path flow name physicalId +1:1:1 0.0384615 "1" 1 +1:1:2 0.025641 "2" 2 +1:1:3 0.0384615 "3" 3 +1:2:1 0.0384615 "4" 4 + */ +void ClusterMap::readTree(const std::string& filename, bool includeFlow, const std::map>* layerNodeToStateId) +{ + bool isMultilayer = layerNodeToStateId != nullptr; + + SafeInFile input(filename); + std::string line; + std::istringstream lineStream; + std::istringstream pathStream; + m_nodePaths.clear(); + + unsigned int lineNr = 0; + + while (!std::getline(input, line).fail()) { + ++lineNr; + if (line.length() == 0) + continue; + if (line[0] == '#') { + continue; + } + if (line[0] == '*') { + break; + } + + lineStream.clear(); + lineStream.str(line); + + std::string pathString; + double flow; + std::string name; + unsigned int stateId; + unsigned int nodeId; + unsigned int layerId; + if (!(lineStream >> pathString)) + throw std::runtime_error(io::Str() << "Couldn't parse tree path from line '" << line << "'"); + if (!(lineStream >> flow)) + throw std::runtime_error(io::Str() << "Couldn't parse node flow from line '" << line << "'"); + // Get the name by extracting the rest of the stream until the first quotation mark and then the last. + if (!getline(lineStream, name, '"')) + throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "')."); + if (!getline(lineStream, name, '"')) + throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "')."); + if (!(lineStream >> stateId)) + throw std::runtime_error(io::Str() << "Couldn't parse node id from line '" << line << "'"); + if (lineStream >> nodeId) { + m_isHigherOrder = true; + } else if (m_isHigherOrder) { + throw std::runtime_error(io::Str() << "Missing state id for node on line '" << line << "'."); + } + if (isMultilayer && !(lineStream >> layerId)) + throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'"); + + bool multilayerNodeFound = false; + + if (isMultilayer) { + // get new state id from map + auto it = layerNodeToStateId->find(layerId); + + if (it != layerNodeToStateId->end()) { + auto nodeIdToStateId = it->second.find(nodeId); + if (nodeIdToStateId != it->second.end()) { + stateId = nodeIdToStateId->second; + multilayerNodeFound = true; + } + } + } + + if (isMultilayer && !multilayerNodeFound) { + continue; + } + + pathStream.clear(); + pathStream.str(pathString); + unsigned int childNumber; + + Path path; + while (pathStream >> childNumber) { + pathStream.get(); // Extract the delimiting character also + if (childNumber == 0) + throw std::runtime_error("There is a '0' in the tree path, lowest allowed integer is 1."); + path.push_back(childNumber); // Keep 1-based indexing in path + } + + m_nodePaths.emplace_back(stateId, path); + + if (includeFlow) + m_flowData[stateId] = flow; + } +} + +void ClusterMap::readClu(const std::string& filename, bool includeFlow, const std::map>* layerNodeToStateId) +{ + auto isMultilayer = layerNodeToStateId != nullptr; + + Log() << "Read initial partition from '" << filename << "'... " << std::flush; + SafeInFile input(filename); + std::string line; + std::istringstream lineStream; + std::map clusterData; + + while (!std::getline(input, line).fail()) { + if (line.length() == 0 || line[0] == '#' || line[0] == '*') + continue; + + lineStream.clear(); + lineStream.str(line); + // # state_id module flow node_id layer_id + + unsigned int stateId; + unsigned int nodeId; + unsigned int moduleId; + unsigned int layerId; + + if (!(lineStream >> stateId >> moduleId)) + throw std::runtime_error(io::Str() << "Couldn't parse node key and cluster id from line '" << line << "'"); + + auto flow = 0.0; + if (lineStream >> flow) { + if (includeFlow) + m_flowData[stateId] = flow; + } + + auto multilayerNodeFound = false; + if (isMultilayer) { + if (!(lineStream >> nodeId)) + throw std::runtime_error(io::Str() << "Couldn't parse node key from line '" << line << "'"); + + if (!(lineStream >> layerId)) + throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'"); + + // get new state id from map + auto it = layerNodeToStateId->find(layerId); + + if (it != layerNodeToStateId->end()) { + auto nodeIdToStateId = it->second.find(nodeId); + if (nodeIdToStateId != it->second.end()) { + stateId = nodeIdToStateId->second; + multilayerNodeFound = true; + } + } + } + + if (isMultilayer && !multilayerNodeFound) { + continue; + } + + m_clusterIds[stateId] = moduleId; + } +} + +} // namespace infomap diff --git a/vendor/infomap/src/io/ClusterMap.h b/vendor/infomap/src/io/ClusterMap.h new file mode 100644 index 0000000..5526663 --- /dev/null +++ b/vendor/infomap/src/io/ClusterMap.h @@ -0,0 +1,52 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef CLUSTER_MAP_H_ +#define CLUSTER_MAP_H_ + +#include +#include +#include +#include + +namespace infomap { + +using Path = std::deque; // 1-based indexing + +using NodePath = std::pair; + +using NodePaths = std::vector; + +class ClusterMap { +public: + void readClusterData(const std::string& filename, bool includeFlow = false, const std::map>* layerNodeToStateId = nullptr); + + const std::map& clusterIds() const noexcept + { + return m_clusterIds; + } + + const NodePaths& nodePaths() const noexcept { return m_nodePaths; } + + const std::string& extension() const noexcept { return m_extension; } + +private: + void readTree(const std::string& filename, bool includeFlow, const std::map>* layerNodeToStateId = nullptr); + void readClu(const std::string& filename, bool includeFlow, const std::map>* layerNodeToStateId = nullptr); + + std::map m_clusterIds; + std::map m_flowData; + NodePaths m_nodePaths; + std::string m_extension; + bool m_isHigherOrder = false; +}; + +} // namespace infomap + +#endif // CLUSTER_MAP_H_ diff --git a/vendor/infomap/src/io/Config.cpp b/vendor/infomap/src/io/Config.cpp new file mode 100644 index 0000000..ce6a80f --- /dev/null +++ b/vendor/infomap/src/io/Config.cpp @@ -0,0 +1,264 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "Config.h" +#include "ProgramInterface.h" +#include "SafeFile.h" +#include "../utils/FileURI.h" +#include "../utils/Log.h" +#include +#include + +namespace infomap { + +constexpr int FlowModel::undirected; +constexpr int FlowModel::directed; +constexpr int FlowModel::undirdir; +constexpr int FlowModel::outdirdir; +constexpr int FlowModel::rawdir; +constexpr int FlowModel::precomputed; + +Config::Config(const std::string& flags, bool isCLI) : isCLI(isCLI) +{ + ProgramInterface api("Infomap", + "Implementation of the Infomap clustering algorithm based on the Map Equation (see www.mapequation.org)", + INFOMAP_VERSION); + + api.setGroups({ "Input", "Algorithm", "Accuracy", "Output" }); + + std::vector optionalOutputDir; // Used if !isCLI + // --------------------- Input options --------------------- + if (isCLI) { + api.addNonOptionArgument(networkFile, "network_file", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", "Input"); + } else { + api.addOptionArgument(networkFile, "input", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", ArgType::path, "Input"); + } + + api.addOptionArgument(skipAdjustBipartiteFlow, "skip-adjust-bipartite-flow", "Skip distributing all flow from the bipartite nodes to the primary nodes.", "Input", true); + + api.addOptionArgument(bipartiteTeleportation, "bipartite-teleportation", "Teleport like the bipartite flow instead of two-step (unipartite) teleportation.", "Input", true); + + api.addOptionArgument(weightThreshold, "weight-threshold", "Limit the number of links to read from the network. Ignore links with less weight than the threshold.", ArgType::number, "Input", 0.0, true); + + bool deprecated_includeSelfLinks = false; + api.addOptionArgument(deprecated_includeSelfLinks, 'k', "include-self-links", "DEPRECATED. Include self links by default now, exclude with --no-self-links.", "Input", true).setHidden(true); + + api.addOptionArgument(noSelfLinks, "no-self-links", "Exclude self links in the input network.", "Input", true); + + api.addOptionArgument(nodeLimit, "node-limit", "Limit the number of nodes to read from the network. Ignore links connected to ignored nodes.", ArgType::integer, "Input", 1u, true); + + api.addOptionArgument(matchableMultilayerIds, "matchable-multilayer-ids", "Construct state ids from node and layer ids that are consistent across networks for the same max number of layers. Set to at least the largest layer id among networks to match.", ArgType::integer, "Input", 1u, true); + + api.addOptionArgument(clusterDataFile, 'c', "cluster-data", "Provide an initial two-level (clu format) or multi-layer (tree format) solution.", ArgType::path, "Input"); + + api.addOptionArgument(assignToNeighbouringModule, "assign-to-neighbouring-module", "Assign nodes without module assignments (from --cluster-data) to the module assignment of a neighbouring node if possible.", "Input", true); + + api.addOptionArgument(metaDataFile, "meta-data", "Provide meta data (clu format) that should be encoded.", ArgType::path, "Input", true); + + api.addOptionArgument(metaDataRate, "meta-data-rate", "Metadata encoding rate. Default is to encode each step.", ArgType::number, "Input", 0.0, true); + + api.addOptionArgument(unweightedMetaData, "meta-data-unweighted", "Don't weight meta data by node flow.", "Input", true); + + api.addOptionArgument(noInfomap, "no-infomap", "Don't run the optimizer. Useful to calculate codelength of provided cluster data or to print non-modular statistics.", "Input"); + + // --------------------- Output options --------------------- + + api.addOptionArgument(outName, "out-name", "Name for the output files, e.g. [output_directory]/[out-name].tree", ArgType::string, "Output", true); + + api.addOptionArgument(noFileOutput, '0', "no-file-output", "Don't write output to file.", "Output", true); + + api.addOptionArgument(printTree, "tree", "Write a tree file with the modular hierarchy. Automatically enabled if no other output is specified.", "Output"); + + api.addOptionArgument(printFlowTree, "ftree", "Write a ftree file with the modular hierarchy including aggregated links between (nested) modules. (Used by Network Navigator)", "Output"); + + api.addOptionArgument(printClu, "clu", "Write a clu file with the top cluster ids for each node.", "Output"); + + api.addOptionArgument(cluLevel, "clu-level", "For clu output, print modules at specified depth from root. Use -1 for bottom level modules.", ArgType::integer, "Output", -1, true); + + api.addOptionArgument(outputFormats, 'o', "output", "Comma-separated output formats without spaces, e.g. -o clu,tree,ftree. Options: clu, tree, ftree, newick, json, csv, network, states, flow.", ArgType::list, "Output", true); + + api.addOptionArgument(hideBipartiteNodes, "hide-bipartite-nodes", "Project bipartite solution to unipartite.", "Output", true); + + api.addOptionArgument(printAllTrials, "print-all-trials", "Print all trials to separate files.", "Output", true); + + // --------------------- Core algorithm options --------------------- + api.addOptionArgument(twoLevel, '2', "two-level", "Optimize a two-level partition of the network. Default is multi-level.", "Algorithm"); + + std::string flowModelArg; + + api.addOptionArgument(flowModelArg, 'f', "flow-model", "Specify flow model. Options: undirected, directed, undirdir, outdirdir, rawdir, precomputed.", ArgType::option, "Algorithm"); + + api.addOptionArgument(directed, 'd', "directed", "Assume directed links. Shorthand for '--flow-model directed'.", "Algorithm"); + + api.addOptionArgument(recordedTeleportation, 'e', "recorded-teleportation", "If teleportation is used to calculate the flow, also record it when minimizing codelength.", "Algorithm", true); + + api.addOptionArgument(useNodeWeightsAsFlow, "use-node-weights-as-flow", "Use node weights (from api or after names in Pajek format) as flow, normalized to sum to 1", "Algorithm", true); + + api.addOptionArgument(teleportToNodes, "to-nodes", "Teleport to nodes instead of to links, assuming uniform node weights if no such input data.", "Algorithm", true); + + api.addOptionArgument(teleportationProbability, 'p', "teleportation-probability", "Probability of teleporting to a random node or link.", ArgType::probability, "Algorithm", 0.0, 1.0, true); + + api.addOptionArgument(regularized, "regularized", "Effectively add a fully connected Bayesian prior network to not overfit due to missing links. Implies recorded teleportation", "Algorithm", true); + + api.addOptionArgument(regularizationStrength, "regularization-strength", "Adjust relative strength of Bayesian prior network with this multiplier.", ArgType::number, "Algorithm", 0.0, true); + + api.addOptionArgument(entropyBiasCorrection, "entropy-corrected", "Correct for negative entropy bias in small samples (many modules).", "Algorithm", true); + + api.addOptionArgument(entropyBiasCorrectionMultiplier, "entropy-correction-strength", "Increase or decrease the default entropy correction with this factor.", ArgType::number, "Algorithm", true); + + api.addOptionArgument(markovTime, "markov-time", "Scale link flow to change the cost of moving between modules. Higher values results in fewer modules.", ArgType::number, "Algorithm", 0.0, true); + + api.addOptionArgument(variableMarkovTime, "variable-markov-time", "Increase Markov time locally to level out link flow. Reduces risk of overpartitioning sparse areas while keeping high resolution in dense areas.", "Algorithm", true); + + api.addOptionArgument(variableMarkovTimeDamping, "variable-markov-damping", "Damping parameter for variable Markov time, to scale with local effective degree (0) or local entropy (1).", ArgType::number, "Algorithm", true); + + api.addOptionArgument(variableMarkovTimeMinLocalScale, "variable-markov-min-scale", "Minimum local scale for nodes with zero entropy to avoid division by zero. Local Markov time is max scale divided by local scale.", ArgType::number, "Algorithm", true); + + // api.addOptionArgument(markovTimeNoSelfLinks, "markov-time-no-self-links", "For testing.", "Algorithm", true); + + api.addOptionArgument(preferredNumberOfModules, "preferred-number-of-modules", "Penalize solutions the more they differ from this number.", ArgType::integer, "Algorithm", 1u, true); + + api.addOptionArgument(multilayerRelaxRate, "multilayer-relax-rate", "Probability to relax the constraint to move only in the current layer.", ArgType::probability, "Algorithm", 0.0, 1.0, true); + + api.addOptionArgument(multilayerRelaxLimit, "multilayer-relax-limit", "Number of neighboring layers in each direction to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true); + + api.addOptionArgument(multilayerRelaxLimitUp, "multilayer-relax-limit-up", "Number of neighboring layers with higher id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true); + + api.addOptionArgument(multilayerRelaxLimitDown, "multilayer-relax-limit-down", "Number of neighboring layers with lower id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true); + + api.addOptionArgument(multilayerRelaxByJensenShannonDivergence, "multilayer-relax-by-jsd", "Relax proportional to the out-link similarity measured by the Jensen-Shannon divergence.", "Algorithm", true); + + // --------------------- Performance and accuracy options --------------------- + // api.addOptionArgument(seedToRandomNumberGenerator, 's', "seed", "A seed (integer) to the random number generator for reproducible results.", ArgType::integer, "Accuracy", 1ul); + + api.addOptionArgument(numTrials, 'N', "num-trials", "Number of outer-most loops to run before picking the best solution.", ArgType::integer, "Accuracy", 1u); + + api.addOptionArgument(coreLoopLimit, 'M', "core-loop-limit", "Limit the number of loops that tries to move each node into the best possible module.", ArgType::integer, "Accuracy", 1u, true); + + api.addOptionArgument(levelAggregationLimit, 'L', "core-level-limit", "Limit the number of times the core loops are reapplied on existing modular network to search bigger structures.", ArgType::integer, "Accuracy", 1u, true); + + api.addOptionArgument(tuneIterationLimit, 'T', "tune-iteration-limit", "Limit the number of main iterations in the two-level partition algorithm. 0 means no limit.", ArgType::integer, "Accuracy", 1u, true); + + api.addOptionArgument(minimumCodelengthImprovement, "core-loop-codelength-threshold", "Minimum codelength threshold for accepting a new solution in core loop.", ArgType::number, "Accuracy", 0.0, true); + + api.addOptionArgument(minimumRelativeTuneIterationImprovement, "tune-iteration-relative-threshold", "Set codelength improvement threshold of each new tune iteration to 'f' times the initial two-level codelength.", ArgType::number, "Accuracy", 0.0, true); + + api.addIncrementalOptionArgument(fastHierarchicalSolution, 'F', "fast-hierarchical-solution", "Find top modules fast. Use -FF to keep all fast levels. Use -FFF to skip recursive part.", "Accuracy", true); + + api.addOptionArgument(preferModularSolution, "prefer-modular-solution", "Prefer modular solutions even if they are worse than putting all nodes in one module.", "Accuracy", true); + + api.addOptionArgument(innerParallelization, "inner-parallelization", "Parallelize the inner-most loop for greater speed. This may give some accuracy tradeoff.", "Accuracy", true); + + api.addOptionalNonOptionArguments(optionalOutputDir, "out_directory", "Directory to write the results to.", "Output"); + + api.addIncrementalOptionArgument(verbosity, 'v', "verbose", "Verbose output on the console. Add additional 'v' flags to increase verbosity up to -vvv.", "Output"); + + api.addOptionArgument(silent, "silent", "No output on the console.", "Output"); + + api.parseArgs(flags); + + if (deprecated_includeSelfLinks) { + throw std::runtime_error("The --include-self-links flag is deprecated to include self links by default. Use --no-self-links to exclude."); + } + + if (!optionalOutputDir.empty()) + outDirectory = optionalOutputDir[0]; + + if (!isCLI && outDirectory.empty()) + noFileOutput = true; + + if (!noFileOutput && outDirectory.empty() && isCLI) { + throw std::runtime_error("Missing out_directory"); + } + + if (flowModelArg == "directed" || directed) { + setFlowModel(FlowModel::directed); + } else if (flowModelArg == "undirected") { + setFlowModel(FlowModel::undirected); + } else if (flowModelArg == "undirdir") { + setFlowModel(FlowModel::undirdir); + } else if (flowModelArg == "outdirdir") { + setFlowModel(FlowModel::outdirdir); + } else if (flowModelArg == "rawdir") { + setFlowModel(FlowModel::rawdir); + } else if (flowModelArg == "precomputed") { + setFlowModel(FlowModel::precomputed); + } else if (!flowModelArg.empty()) { + throw std::runtime_error(io::Str() << "Unrecognized flow model: '" << flowModelArg << "'"); + } + + if (regularized) { + recordedTeleportation = true; + } + + if (*--outDirectory.end() != '/') + outDirectory.append("/"); + + if (haveOutput() && !isDirectoryWritable(outDirectory)) + throw std::runtime_error(io::Str() << "Can't write to directory '" << outDirectory << "'. Check that the directory exists and that you have write permissions."); + + if (outName.empty()) { + outName = !networkFile.empty() ? FileURI(networkFile).getName() : "no-name"; + } + + if (noInfomap) { + numTrials = 1; + } + + parsedString = flags; + parsedOptions = api.getUsedOptionArguments(); + + if (printAllTrials && numTrials < 2) { + printAllTrials = false; + } + + adaptDefaults(); + + Log::init(verbosity, silent, verboseNumberPrecision); +} + +void Config::adaptDefaults() +{ + auto outputs = io::split(outputFormats, ','); + for (std::string& o : outputs) { + if (o == "clu") { + printClu = true; + } else if (o == "tree") { + printTree = true; + } else if (o == "ftree") { + printFlowTree = true; + } else if (o == "newick") { + printNewick = true; + } else if (o == "json") { + printJson = true; + } else if (o == "csv") { + printCsv = true; + } else if (o == "network") { + printPajekNetwork = true; + } else if (o == "flow") { + printFlowNetwork = true; + } else if (o == "states") { + printStateNetwork = true; + } else { + throw std::runtime_error(io::Str() << "Unrecognized output format: '" << o << "'."); + } + } + + // Of no output format specified, use tree as default (if not used as a library). + if (isCLI && !haveModularResultOutput()) { + printTree = true; + } +} + +std::ostream& operator<<(std::ostream& out, FlowModel f) +{ + return out << flowModelToString(f); +} + +} // namespace infomap diff --git a/vendor/infomap/src/io/Config.h b/vendor/infomap/src/io/Config.h new file mode 100644 index 0000000..c7ce611 --- /dev/null +++ b/vendor/infomap/src/io/Config.h @@ -0,0 +1,268 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#include "../utils/Date.h" +#include "../version.h" +#include "ProgramInterface.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace infomap { + +struct FlowModel { + static constexpr int undirected = 0; + static constexpr int directed = 1; + static constexpr int undirdir = 2; + static constexpr int outdirdir = 3; + static constexpr int rawdir = 4; + static constexpr int precomputed = 5; + + int value = 0; + + FlowModel(int val) : value(val) { } + FlowModel& operator=(int val) + { + value = val; + return *this; + } + + operator int&() { return value; } + operator int() const { return value; } +}; + +std::ostream& operator<<(std::ostream& out, FlowModel f); + +inline const char* flowModelToString(const FlowModel& flowModel) +{ + switch (flowModel) { + case FlowModel::directed: + return "directed"; + case FlowModel::undirdir: + return "undirdir"; + case FlowModel::outdirdir: + return "outdirdir"; + case FlowModel::rawdir: + return "rawdir"; + case FlowModel::precomputed: + return "precomputed"; + case FlowModel::undirected: + default: + return "undirected"; + } +} + +struct Config { + // Input + bool isCLI = false; + std::string networkFile; + std::vector additionalInput; + bool stateInput = false; + bool stateOutput = false; + bool multilayerInput = false; + double weightThreshold = 0.0; + bool bipartite = false; + bool skipAdjustBipartiteFlow = false; + bool bipartiteTeleportation = false; + bool noSelfLinks = false; // Replaces includeSelfLinks + unsigned int nodeLimit = 0; + unsigned int matchableMultilayerIds = 0; + std::string clusterDataFile; + std::string metaDataFile; + double metaDataRate = 1.0; + bool unweightedMetaData = false; + unsigned int numMetaDataDimensions = 0; + bool clusterDataIsHard = false; // FIXME Not used + bool assignToNeighbouringModule = false; + bool noInfomap = false; + + FlowModel flowModel = FlowModel::undirected; + bool flowModelIsSet = false; + bool directed = false; + bool useNodeWeightsAsFlow = false; + bool teleportToNodes = false; + double markovTime = 1.0; + bool variableMarkovTime = false; + double variableMarkovTimeDamping = 1.0; // 0 for linear scaling, 1 for log scaled. + double variableMarkovTimeMinLocalScale = 1; // Correspond to two links in undirected unweighted networks. Avoids division by zero. + bool markovTimeNoSelfLinks = false; + double multilayerRelaxRate = 0.15; + int multilayerRelaxLimit = -1; // Amount of layers allowed to jump up or down + int multilayerRelaxLimitUp = -1; // One-sided limit to higher layers + int multilayerRelaxLimitDown = -1; // One-sided limit to lower layers + double multilayerJSRelaxRate = 0.15; + bool multilayerRelaxByJensenShannonDivergence = false; + int multilayerJSRelaxLimit = -1; + + // Clustering + bool twoLevel = false; + bool noCoarseTune = false; + bool recordedTeleportation = false; + bool regularized = false; // Add a Bayesian prior network with recorded teleportation (sets recordedTeleportation and teleportToNodes to true) + double regularizationStrength = 1.0; // Scale Bayesian prior constant ln(N)/N with this factor + double teleportationProbability = 0.15; + unsigned int preferredNumberOfModules = 0; + bool entropyBiasCorrection = false; + double entropyBiasCorrectionMultiplier = 1; + /* unsigned long seedToRandomNumberGenerator = 123; */ + + // Performance and accuracy + unsigned int numTrials = 1; + double minimumCodelengthImprovement = 1e-10; + double minimumSingleNodeCodelengthImprovement = 1e-16; + bool randomizeCoreLoopLimit = false; + unsigned int coreLoopLimit = 10; + unsigned int levelAggregationLimit = 0; + unsigned int tuneIterationLimit = 0; // Iterations of fine-tune/coarse-tune in two-level partition + double minimumRelativeTuneIterationImprovement = 1e-5; + bool onlySuperModules = false; + unsigned int fastHierarchicalSolution = 0; + bool preferModularSolution = false; + bool innerParallelization = false; + + // Output + std::string outDirectory; + std::string outName; + std::string outputFormats; + bool printTree = false; + bool printFlowTree = false; + bool printNewick = false; + bool printJson = false; + bool printCsv = false; + bool printClu = false; + bool printAllTrials = false; + int cluLevel = 1; // Write modules at specified depth from root. 1, 2, ... or -1 for bottom level + bool printFlowNetwork = false; + bool printPajekNetwork = false; + bool printStateNetwork = false; + bool noFileOutput = false; + unsigned int verbosity = 0; + unsigned int verboseNumberPrecision = 9; + bool silent = false; + bool hideBipartiteNodes = false; + + // Other + Date startDate; + std::string version = INFOMAP_VERSION; + std::string parsedString; + std::vector parsedOptions; + infomap::interruptionHandlerFn *interruptionHandler = NULL; + + Config() = default; + + explicit Config(const std::string& flags, bool isCLI = false); + + Config& cloneAsNonMain(const Config& other) + { + isCLI = other.isCLI; + networkFile = other.networkFile; + additionalInput = other.additionalInput; + stateInput = other.stateInput; + stateOutput = other.stateOutput; + multilayerInput = other.multilayerInput; + weightThreshold = other.weightThreshold; + bipartite = other.bipartite; + skipAdjustBipartiteFlow = other.skipAdjustBipartiteFlow; + bipartiteTeleportation = other.bipartiteTeleportation; + noSelfLinks = other.noSelfLinks; + nodeLimit = other.nodeLimit; + matchableMultilayerIds = other.matchableMultilayerIds; + metaDataRate = other.metaDataRate; + unweightedMetaData = other.unweightedMetaData; + numMetaDataDimensions = other.numMetaDataDimensions; + assignToNeighbouringModule = other.assignToNeighbouringModule; + noInfomap = other.noInfomap; + flowModel = other.flowModel; + flowModelIsSet = other.flowModelIsSet; + directed = other.directed; + useNodeWeightsAsFlow = other.useNodeWeightsAsFlow; + teleportToNodes = other.teleportToNodes; + markovTime = other.markovTime; + variableMarkovTime = other.variableMarkovTime; + variableMarkovTimeDamping = other.variableMarkovTimeDamping; + markovTimeNoSelfLinks = other.markovTimeNoSelfLinks; + multilayerRelaxRate = other.multilayerRelaxRate; + multilayerRelaxLimit = other.multilayerRelaxLimit; + multilayerRelaxLimitUp = other.multilayerRelaxLimitUp; + multilayerRelaxLimitDown = other.multilayerRelaxLimitDown; + multilayerJSRelaxRate = other.multilayerJSRelaxRate; + multilayerRelaxByJensenShannonDivergence = other.multilayerRelaxByJensenShannonDivergence; + multilayerJSRelaxLimit = other.multilayerJSRelaxLimit; + twoLevel = other.twoLevel; + noCoarseTune = other.noCoarseTune; + recordedTeleportation = other.recordedTeleportation; + regularized = other.regularized; + regularizationStrength = other.regularizationStrength; + teleportationProbability = other.teleportationProbability; + entropyBiasCorrection = other.entropyBiasCorrection; + entropyBiasCorrectionMultiplier = other.entropyBiasCorrectionMultiplier; + // seedToRandomNumberGenerator = other.seedToRandomNumberGenerator; + minimumCodelengthImprovement = other.minimumCodelengthImprovement; + minimumSingleNodeCodelengthImprovement = other.minimumSingleNodeCodelengthImprovement; + randomizeCoreLoopLimit = other.randomizeCoreLoopLimit; + minimumRelativeTuneIterationImprovement = other.minimumRelativeTuneIterationImprovement; + preferModularSolution = other.preferModularSolution; + innerParallelization = other.innerParallelization; + outDirectory = other.outDirectory; + outName = other.outName; + outputFormats = other.outputFormats; + verbosity = other.verbosity; + verboseNumberPrecision = other.verboseNumberPrecision; + startDate = other.startDate; + version = other.version; + return *this; + } + + void adaptDefaults(); + + void setStateInput() { stateInput = true; } + + void setStateOutput() { stateOutput = true; } + + void setMultilayerInput() { multilayerInput = true; } + + void setFlowModel(FlowModel value) + { + flowModel = value; + flowModelIsSet = true; + } + + bool isUndirectedClustering() const { return flowModel == FlowModel::undirected; } + + bool isUndirectedFlow() const { return flowModel == FlowModel::undirected || flowModel == FlowModel::undirdir; } + + bool printAsUndirected() const { return isUndirectedClustering(); } + + bool isMultilayerNetwork() const { return multilayerInput || !additionalInput.empty(); } + bool isBipartite() const { return bipartite; } + + bool haveMemory() const { return stateInput; } + bool printStates() const { return stateOutput; } + + bool haveMetaData() const { return !metaDataFile.empty() || numMetaDataDimensions != 0; } + + bool haveOutput() const { return !noFileOutput; } + + bool haveModularResultOutput() const + { + return printTree || printFlowTree || printNewick || printJson || printCsv || printClu; + } +}; + +} // namespace infomap + +#endif // CONFIG_H_ diff --git a/vendor/infomap/src/io/Network.cpp b/vendor/infomap/src/io/Network.cpp new file mode 100644 index 0000000..b405fd7 --- /dev/null +++ b/vendor/infomap/src/io/Network.cpp @@ -0,0 +1,990 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "Network.h" +#include "../io/SafeFile.h" +#include "../utils/FileURI.h" +#include "../utils/Log.h" + +#include +#include +#include + +namespace infomap { + +using std::make_pair; + +void Network::init() +{ + initValidHeadings(); + m_multilayerStateIdBitShift = static_cast(std::ceil(std::log2(m_config.matchableMultilayerIds))); +} + +void Network::initValidHeadings() +{ + auto& headingsPajek = m_validHeadings["pajek"]; + headingsPajek.insert("*vertices"); + headingsPajek.insert("*edges"); + headingsPajek.insert("*arcs"); + + auto& headingsLinklist = m_validHeadings["link-list"]; + headingsLinklist.insert("*links"); + headingsLinklist.insert("*edges"); + headingsLinklist.insert("*arcs"); + + auto& headingsBipartite = m_validHeadings["bipartite"]; + headingsBipartite.insert("*vertices"); + headingsBipartite.insert("*bipartite"); + + auto& headingsStates = m_validHeadings["states"]; + headingsStates.insert("*vertices"); + headingsStates.insert("*states"); + headingsStates.insert("*edges"); + headingsStates.insert("*arcs"); + headingsStates.insert("*links"); + headingsStates.insert("*contexts"); + auto& ignoreHeadingsStates = m_ignoreHeadings["states"]; + ignoreHeadingsStates.insert("*edges"); + ignoreHeadingsStates.insert("*contexts"); + + auto& headingsMultilayer = m_validHeadings["multilayer"]; + headingsMultilayer.insert("*vertices"); + headingsMultilayer.insert("*multiplex"); + headingsMultilayer.insert("*multilayer"); + headingsMultilayer.insert("*intra"); + headingsMultilayer.insert("*inter"); + + auto& headingsGeneral = m_validHeadings["general"]; + headingsGeneral.insert("*vertices"); + headingsGeneral.insert("*states"); + headingsGeneral.insert("*multilayer"); + headingsGeneral.insert("*intra"); + headingsGeneral.insert("*inter"); + headingsGeneral.insert("*paths"); + headingsGeneral.insert("*edges"); + headingsGeneral.insert("*arcs"); + headingsGeneral.insert("*links"); + headingsGeneral.insert("*contexts"); + headingsGeneral.insert("*bipartite"); + auto& ignoreHeadingsGeneral = m_ignoreHeadings["general"]; + ignoreHeadingsGeneral.insert("*contexts"); +} + +void Network::clear() +{ + StateNetwork::clear(); + m_networks.clear(); + m_interLinks.clear(); + m_layerNodeToStateId.clear(); + m_sumIntraOutWeight.clear(); + m_layers.clear(); + m_numInterLayerLinks = 0; + m_numIntraLayerLinks = 0; + + // Bipartite + m_bipartiteStartId = 0; + + // Meta data + m_metaData.clear(); + m_numMetaDataColumns = 0; +} + +void Network::readInputData(std::string filename, bool accumulate) +{ + if (!accumulate) { + clear(); + } + if (filename.empty()) + filename = m_config.networkFile; + if (filename.empty()) { + throw std::runtime_error("No input file to read network"); + } + FileURI networkFilename(filename, false); + + parseNetwork(filename); + printSummary(); +} + +void Network::parseNetwork(const std::string& filename) +{ + Log() << "Parsing " << (m_config.isUndirectedFlow() ? "undirected" : "directed") << " network from file '" << filename << "'...\n"; + + parseNetwork(filename, m_validHeadings["general"], m_ignoreHeadings["general"]); +} + +void Network::parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading) +{ + m_haveFileInput = true; + SafeInFile input(filename); + + // Parse standard links by default until possible heading is reached + std::string heading = startHeading.length() > 0 ? startHeading : parseLinks(input); + + while (heading.length() > 0 && heading[0] == '*') { + std::string headingLowerCase = io::tolower(io::firstWord(heading)); + if (validHeadings.count(headingLowerCase) == 0) { + throw std::runtime_error(io::Str() << "Unrecognized heading in network file: '" << headingLowerCase << "'."); + } + if (ignoreHeadings.count(headingLowerCase) > 0) { + heading = ignoreSection(input, headingLowerCase); + } else if (headingLowerCase == "*vertices") { + heading = parseVertices(input, heading); + } else if (headingLowerCase == "*states") { + heading = parseStateNodes(input, heading); + } else if (headingLowerCase == "*edges") { + if (!m_config.isUndirectedFlow()) + Log() << "\n --> Notice: Links marked as undirected but parsed as directed.\n"; + heading = parseLinks(input); + } else if (headingLowerCase == "*arcs") { + if (m_config.isUndirectedFlow()) + Log() << "\n --> Notice: Links marked as directed but parsed as undirected.\n"; + heading = parseLinks(input); + } else if (headingLowerCase == "*links") { + heading = parseLinks(input); + } else if (headingLowerCase == "*multilayer" || headingLowerCase == "*multiplex") { + heading = parseMultilayerLinks(input); + } else if (headingLowerCase == "*intra") { + heading = parseMultilayerIntraLinks(input); + } else if (headingLowerCase == "*inter") { + heading = parseMultilayerInterLinks(input); + } else if (headingLowerCase == "*bipartite") { + heading = parseBipartiteLinks(input, heading); + } else { + heading = ignoreSection(input, headingLowerCase); + } + } + + postProcessInputData(); + Log() << "Done!\n"; +} + +void Network::postProcessInputData() +{ + if (!m_networks.empty()) { + generateStateNetworkFromMultilayer(); + } + + if (!haveMemoryInput()) { + // If no memory input, add physical nodes as state nodes to not miss unconnected nodes + for (auto& it : m_physNodes) { + addNode(it.second.physId, it.second.weight); + } + } +} + +void Network::readMetaData(const std::string& filename) +{ + Log() << "Parsing meta data from '" << filename << "'...\n"; + SafeInFile input(filename); + std::string line; + while (!std::getline(input, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + m_extractor.clear(); + m_extractor.str(line); + + unsigned int nodeId; + if (!(m_extractor >> nodeId)) + throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'"); + + std::vector metaData; + unsigned int metaId; + while (m_extractor >> metaId) { + metaData.push_back(metaId); + } + if (metaData.empty()) + throw std::runtime_error(io::Str() << "Can't parse any meta data from line '" << line << "'"); + + addMetaData(nodeId, metaData); + } + Log() << " -> Parsed " << m_numMetaDataColumns << " columns of meta data for " << m_metaData.size() << " nodes.\n"; +} +////////////////////////////////////////////////////////////////////////////////////////// +// +// HELPER METHODS +// +////////////////////////////////////////////////////////////////////////////////////////// + +std::string Network::parseVertices(std::ifstream& file, const std::string& /*heading*/) +{ + Log() << " Parsing vertices...\n" + << std::flush; + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + m_extractor.clear(); + m_extractor.str(line); + + unsigned int id = 0; + if (!(m_extractor >> id)) + throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'"); + + auto nameStart = line.find_first_of('\"'); + auto nameEnd = line.find_last_of('\"'); + std::string name; + if (nameStart < nameEnd) { + name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd); + line = line.substr(nameEnd + 1); + m_extractor.clear(); + m_extractor.str(line); + } else { + if (!(m_extractor >> name)) + throw std::runtime_error(io::Str() << "Can't parse node name from line '" << line << "'"); + } + double weight = 1.0; + if ((m_extractor >> weight)) { + m_haveNodeWeights = true; + if (weight < 0) + throw std::runtime_error(io::Str() << "Negative node weight (" << weight << ") from line '" << line << "'"); + } + + addPhysicalNode(id, weight, name); + } + Log() << " -> " << m_physNodes.size() << " physical nodes added\n"; + return line; +} + +std::string Network::parseStateNodes(std::ifstream& file, const std::string& /*heading*/) +{ + m_higherOrderInputMethodCalled = true; + Log() << " Parsing state nodes...\n" + << std::flush; + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + StateNode stateNode; + parseStateNode(line, stateNode); + + addStateNode(stateNode); + addPhysicalNode(stateNode.physicalId); + + ++m_numStateNodesFound; + } + Log() << " -> " << m_numStateNodesFound << " state nodes added\n"; + return line; +} + +std::string Network::parseLinks(std::ifstream& file) +{ + // This is the default action, so check for links before printing + bool parsingLinks = false; + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + if (!parsingLinks) { + parsingLinks = true; + Log() << " Parsing links...\n" + << std::flush; + } + + unsigned int n1, n2; + double weight; + parseLink(line, n1, n2, weight); + + addLink(n1, n2, weight); + } + if (parsingLinks) + Log() << " -> " << m_numLinks << " links\n"; + return line; +} + +std::string Network::parseMultilayerLinks(std::ifstream& file) +{ + Log() << " Parsing multilayer links...\n" + << std::flush; + + if (m_config.matchableMultilayerIds > 0) { + Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n"; + } + + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + unsigned int layer1, n1, layer2, n2; + double weight; + parseMultilayerLink(line, layer1, n1, layer2, n2, weight); + + // TODO: This explicit multilayer format can allow undirected but not the inter/intra format, clear? + addMultilayerLink(layer1, n1, layer2, n2, weight); + } + Log() << " -> " << (m_numIntraLayerLinks + m_numInterLayerLinks) << " links in " << m_layers.size() << " layers\n"; + Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n"; + Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n"; + return line; +} + +std::string Network::parseMultilayerIntraLinks(std::ifstream& file) +{ + Log() << " Parsing intra-layer links...\n" + << std::flush; + + if (m_config.matchableMultilayerIds > 0) { + Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n"; + } + + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + unsigned int layer, n1, n2; + double weight; + parseMultilayerIntraLink(line, layer, n1, n2, weight); + + addMultilayerIntraLink(layer, n1, n2, weight); + } + Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n"; + return line; +} + +std::string Network::parseMultilayerInterLinks(std::ifstream& file) +{ + Log() << " Parsing inter-layer links...\n" + << std::flush; + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + unsigned int layer1, n, layer2; + double weight; + parseMultilayerInterLink(line, layer1, n, layer2, weight); + + addMultilayerInterLink(layer1, n, layer2, weight); + } + Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n"; + return line; +} + +std::string Network::parseBipartiteLinks(std::ifstream& file, const std::string& heading) +{ + Log() << " Parsing bipartite links...\n"; + // Extract break point for bipartite links + m_extractor.clear(); + m_extractor.str(heading); + std::string tmp; + if (!(m_extractor >> tmp >> m_bipartiteStartId)) + throw std::runtime_error(io::Str() << "Can't parse bipartite start id from line '" << heading << "'"); + + Log() << " -> Using bipartite start id " << m_bipartiteStartId << "\n"; + m_config.bipartite = true; + std::string line; + while (!std::getline(file, line).fail()) { + if (line.length() == 0 || line[0] == '#') + continue; + + if (line[0] == '*') + break; + + unsigned int n1, n2; + double weight; + parseLink(line, n1, n2, weight); + bool sourceIsFeature = n1 >= m_bipartiteStartId; + bool targetIsFeature = n2 >= m_bipartiteStartId; + if (sourceIsFeature == targetIsFeature) { + throw std::runtime_error(io::Str() << "Bipartite link '" << line << "' must cross bipartite start id " << m_bipartiteStartId << "."); + } + addLink(n1, n2, weight); + } + return line; +} + +std::string Network::ignoreSection(std::ifstream& file, const std::string& heading) +{ + Log() << "(Ignoring section " << heading << ") "; + std::string line; + while (!std::getline(file, line).fail()) { + if (line[0] == '*') + break; + } + return line; +} + +void Network::parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode) +{ + m_extractor.clear(); + m_extractor.str(line); + if (!(m_extractor >> stateNode.id >> stateNode.physicalId)) + throw std::runtime_error(io::Str() << "Can't parse any state node from line '" << line << "'"); + + // Optional name enclosed in double quotes + auto nameStart = line.find_first_of('\"', m_extractor.tellg()); + auto nameEnd = line.find_last_of('\"'); + if (nameStart < nameEnd) { + stateNode.name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd); + m_extractor.seekg(nameEnd + 1); + } + // Optional weight, default to 1.0 + if ((m_extractor >> stateNode.weight)) { + m_haveStateNodeWeights = true; + if (stateNode.weight < 0) + throw std::runtime_error(io::Str() << "Negative state node weight (" << stateNode.weight << ") from line '" << line << "'"); + } +} + +void Network::parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight) +{ + m_extractor.clear(); + m_extractor.str(line); + if (!(m_extractor >> n1 >> n2)) + throw std::runtime_error(io::Str() << "Can't parse link data from line '" << line << "'"); + (m_extractor >> weight) || (weight = 1.0); +} + +void Network::parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight) +{ + m_extractor.clear(); + m_extractor.str(line); + if (!(m_extractor >> layer1 >> n1 >> layer2 >> n2)) + throw std::runtime_error(io::Str() << "Can't parse multilayer link data from line '" << line << "'"); + (m_extractor >> weight) || (weight = 1.0); +} + +void Network::parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight) +{ + m_extractor.clear(); + m_extractor.str(line); + if (!(m_extractor >> layer >> n1 >> n2)) + throw std::runtime_error(io::Str() << "Can't parse intra-multilayer link data from line '" << line << "'"); + (m_extractor >> weight) || (weight = 1.0); +} + +void Network::parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight) +{ + m_extractor.clear(); + m_extractor.str(line); + if (!(m_extractor >> layer1 >> n >> layer2)) + throw std::runtime_error(io::Str() << "Can't parse inter-multilayer link data from line '" << line << "'"); + (m_extractor >> weight) || (weight = 1.0); + if (layer1 == layer2) + throw std::runtime_error(io::Str() << "Inter-layer link from line '" << line << "' doesn't go between different layers."); + // TODO: Same as intra-layer self-link? +} + +void Network::printSummary() +{ + Log() << "-------------------------------------\n"; + if (haveMemoryInput()) { + Log() << " -> " << numNodes() << " state nodes\n"; + Log() << " -> " << numPhysicalNodes() << " physical nodes\n"; + } else { + if (m_bipartiteStartId > 0) + Log() << " -> " << numNodes() << " bipartite nodes\n"; + else + Log() << " -> " << numNodes() << " nodes\n"; + } + Log() << " -> " << numLinks() << " links with total weight " << m_totalLinkWeightAdded << "\n"; + if (m_numLinksIgnoredByWeightThreshold > 0) { + Log() << " -> " << m_numLinksIgnoredByWeightThreshold << " links ignored by weight threshold with total weight " << m_totalLinkWeightIgnored << " (" << io::toPrecision(m_totalLinkWeightIgnored / (m_totalLinkWeightIgnored + m_totalLinkWeightAdded) * 100, 1, true) << "%)\n"; + } +} + +void Network::addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight) +{ + m_higherOrderInputMethodCalled = true; + if (weight < m_config.weightThreshold) { + ++m_numLinksIgnoredByWeightThreshold; + m_totalLinkWeightIgnored += weight; + return; + } + unsigned int stateId1 = addMultilayerNode(layer1, n1); + unsigned int stateId2 = addMultilayerNode(layer2, n2); + + if (stateId1 == stateId2) { + // TODO: Handle self-links? + } + + if (layer1 == layer2) { + ++m_numIntraLayerLinks; + m_sumIntraOutWeight[layer1][n1] += weight; // TODO: Not used? Add on target also if undirected (not inter/intra format)? + } else { + ++m_numInterLayerLinks; + } + + addLink(stateId1, stateId2, weight); +} + +void Network::generateStateNetworkFromMultilayer() +{ + // As inter-layer links is directed to neighbouring nodes in target layer, + // the symmetry is broken so we need directed links for inter-layer flow + m_haveDirectedInput = true; + if (m_config.isUndirectedFlow()) { + // TODO: Don't allow undirdir/outdirdir/rawdir? + // Expand each undirected intra-layer link to two opposite directed links + Log() << " -> Expanding undirected links to directed...\n"; + for (auto& layerIt : m_networks) { + auto& network = layerIt.second; + network.undirectedToDirected(); + } + } + + if (!m_interLinks.empty()) { + generateStateNetworkFromMultilayerWithInterLinks(); + } else { + generateStateNetworkFromMultilayerWithSimulatedInterLinks(); + } + m_networks.clear(); + m_interLinks.clear(); +} + +void Network::generateStateNetworkFromMultilayerWithInterLinks() +{ + Log() << "Generating state network from multilayer networks with inter-layer links...\n" + << std::flush; + // First add intra-layer links + for (auto& layerIt : m_networks) { + unsigned int layer1 = layerIt.first; + auto& network = layerIt.second; + for (auto& linkIt : network.nodeLinkMap()) { + auto& source = linkIt.first; + const auto& subLinks = linkIt.second; + for (auto& subIt : subLinks) { + auto& target = subIt.first; + double linkWeight = subIt.second.weight; + addMultilayerLink(layer1, source.physicalId, layer1, target.physicalId, linkWeight); + } + } + } + + Log() << "Connecting layers...\n"; + // Connect layers with inter-layer links spread out in target layer + for (auto& it : m_interLinks) { + auto& layerNode = it.first; + unsigned int layer1 = layerNode.layer; + unsigned int physId = layerNode.node; + unsigned int stateId1 = addMultilayerNode(layer1, physId); + for (auto& it2 : it.second) { + unsigned int layer2 = it2.first; + double interWeight = it2.second; + auto& targetNetwork = m_networks[layer2]; + + std::map>& targetLinks = targetNetwork.nodeLinkMap(); + auto& outlinks = targetLinks[StateNode(physId)]; + if (outlinks.empty()) { + continue; + } + auto& targetOutWeights = targetNetwork.outWeights(); + double sumIntraOutWeightTargetLayer = targetOutWeights[physId]; + + for (auto& outLink : outlinks) { + auto& targetPhysId = outLink.first.physicalId; + auto& linkData = outLink.second; + double intraWeight = linkData.weight; + unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId); + + double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer; + + addLink(stateId1, stateId2i, weight); + ++m_numInterLayerLinks; // TODO: Count all as one? + } + } + } + if (m_config.isUndirectedFlow()) { + // For undirected inter-layer links, expand and add in other direction too + for (auto& it : m_interLinks) { + auto& layerNode = it.first; + unsigned int layer2 = layerNode.layer; + unsigned int physId = layerNode.node; + auto& targetNetwork = m_networks[layer2]; + std::map>& targetLinks = targetNetwork.nodeLinkMap(); + auto& outlinks = targetLinks[StateNode(physId)]; + if (outlinks.empty()) { + continue; + } + auto& targetOutWeights = targetNetwork.outWeights(); + double sumIntraOutWeightTargetLayer = targetOutWeights[physId]; + for (auto& it2 : it.second) { + unsigned int layer1 = it2.first; + double interWeight = it2.second; + unsigned int stateId1 = addMultilayerNode(layer1, physId); + + for (auto& outLink : outlinks) { + auto& targetPhysId = outLink.first.physicalId; + auto& linkData = outLink.second; + double intraWeight = linkData.weight; + unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId); + + double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer; + + addLink(stateId1, stateId2i, weight); + ++m_numInterLayerLinks; // TODO: Count all as one? + } + } + } + } +} + +void Network::generateStateNetworkFromMultilayerWithSimulatedInterLinks() +{ + Log() << "Generating state network from multilayer networks with simulated inter-layer links...\n" + << std::flush; + double relaxRate = m_config.multilayerRelaxRate; + + int maxRelaxLimit = m_networks.size(); + int relaxLimitSymmetric = m_config.multilayerRelaxLimit < 0 ? maxRelaxLimit : m_config.multilayerRelaxLimit; + int relaxLimitDown = m_config.multilayerRelaxLimitDown < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitDown); + int relaxLimitUp = m_config.multilayerRelaxLimitUp < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitUp); + auto haveUpOrDownLimit = m_config.multilayerRelaxLimitDown >= 0 || m_config.multilayerRelaxLimitUp >= 0; + + Log() << "-> " << m_networks.size() << " networks\n"; + Log() << "-> Relax rate: " << relaxRate << "\n"; + if (haveUpOrDownLimit) { + Log() << "-> Relax limit up: " << relaxLimitUp << (relaxLimitUp == maxRelaxLimit ? " (no limit)\n" : "\n"); + Log() << "-> Relax limit down: " << relaxLimitDown << (relaxLimitDown == maxRelaxLimit ? " (no limit)\n" : "\n"); + } else if (m_config.multilayerRelaxLimit >= 0) { + Log() << "-> Relax limit: " << m_config.multilayerRelaxLimit << "\n"; + } + + auto withinRelaxLimit = [relaxLimitDown, relaxLimitUp](auto& layer1, auto& layer2) { + int diff = layer1 - layer2; + return layer1 >= layer2 ? diff <= relaxLimitDown : -diff <= relaxLimitUp; + }; + + if (m_config.multilayerRelaxByJensenShannonDivergence) { + Log() << "-> Using Jensen-Shannon Divergence\n"; + + for (unsigned int nodeId = 0; nodeId <= m_maxNodeIdInIntraLayerNetworks; ++nodeId) { + unsigned int layer2from = 0; + + // Calculate Jensen-Shannon similarity between all layers such that layer1 >= layer2, + // and then use its symmetry for layer2 > layer1 + std::map> jsRelaxWeights; + std::map jsTotWeight; + + for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) { + unsigned int layer2to = layer1 + 1; + // Limit possible jumps to close by layers + if (m_config.multilayerRelaxLimit >= 0) { + layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit; + } + + auto& layer1LinkMap = m_networks[layer1].nodeLinkMap(); + auto& layer1OutLinks = layer1LinkMap[StateNode(nodeId)]; + // Skip dangling nodes, because they have no information to calculate similarity + if (layer1OutLinks.empty()) + continue; + + double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId]; + + for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) { + auto& layer2LinkMap = m_networks[layer2].nodeLinkMap(); + auto& layer2OutLinks = layer2LinkMap[StateNode(nodeId)]; + if (layer2OutLinks.empty()) + continue; + + double sumOutLinkWeightLayer2 = m_networks[layer2].outWeights()[nodeId]; + + bool intersect; + double div = calculateJensenShannonDivergence(intersect, layer1OutLinks, sumOutLinkWeightLayer1, layer2OutLinks, sumOutLinkWeightLayer2); + double jsWeight = 1.0 - div; + if (intersect && (jsWeight >= m_config.multilayerJSRelaxLimit)) { + jsTotWeight[layer1] += jsWeight; + jsRelaxWeights[layer1][layer2] = jsWeight; + if (layer1 != layer2) { + jsTotWeight[layer2] += jsWeight; + jsRelaxWeights[layer2][layer1] = jsWeight; + } + } + } + } + + // Second loop over all pairs of layers + unsigned int layer2to = m_networks.size(); + + for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) { + // Limit possible jumps to close by layers + if (m_config.multilayerRelaxLimit >= 0) { + layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit; + layer2to = (layer1 + m_config.multilayerRelaxLimit) > m_networks.size() ? m_networks.size() : layer1 + m_config.multilayerRelaxLimit; + } + + double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId]; + + auto jsRelaxWeightsLayer1It = jsRelaxWeights.find(layer1); + auto jsTotWeightIt = jsTotWeight.find(layer1); + + // Create inter-links to the intra-connected nodes in other layers + for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) { + if (jsRelaxWeightsLayer1It != jsRelaxWeights.end()) { + auto jsRelaxWeightsIt = jsRelaxWeightsLayer1It->second.find(layer2); + if (jsRelaxWeightsIt != jsRelaxWeightsLayer1It->second.end()) { + bool isIntra = layer2 == layer1; + + // Create inter-links to the outgoing nodes in the target layer + double linkWeightNormalizationFactor; + if (isIntra) { + linkWeightNormalizationFactor = 1; + } else { + linkWeightNormalizationFactor = jsRelaxWeightsIt->second * relaxRate / (1.0 - relaxRate) * sumOutLinkWeightLayer1 / jsTotWeightIt->second; + } + + auto& targetLinks = m_networks[layer2].nodeLinkMap(); + auto& targetOutlinks = targetLinks[StateNode(nodeId)]; + if (targetOutlinks.empty()) { + continue; + } + for (auto& outLink : targetOutlinks) { + auto& n2 = outLink.first.physicalId; + auto& linkData = outLink.second; + double intraWeight = linkData.weight; + // Add intra link weight as teleport weight to source node + unsigned int stateId1 = addMultilayerNode(layer1, nodeId, intraWeight); + unsigned int stateId2i = addMultilayerNode(layer2, n2, 0.0); + + double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight; + addLink(stateId1, stateId2i, weight); + ++m_numInterLayerLinks; + } + } + } + } + } + } + + return; + } + + for (auto& it1 : m_networks) { + auto layer1 = it1.first; + auto& network1 = it1.second; + + for (auto& n1It : network1.nodes()) { + auto& n1 = n1It.first; + unsigned int stateId1 = addMultilayerNode(layer1, n1); + + double sumOutLinkWeightLayer1 = network1.outWeights()[n1]; + double sumOutWeightAllLayers = 0.0; + + for (auto& it2 : m_networks) { + auto layer2 = it2.first; + if (!withinRelaxLimit(layer1, layer2)) { + continue; + } + auto& network2 = it2.second; + sumOutWeightAllLayers += network2.outWeights()[n1]; + } + + if (sumOutWeightAllLayers <= 0) { + continue; + } + for (auto& it2 : m_networks) { + auto layer2 = it2.first; + if (!withinRelaxLimit(layer1, layer2)) { + continue; + } + auto& network2 = it2.second; + bool isIntra = layer2 == layer1; + + double linkWeightNormalizationFactor = relaxRate / sumOutWeightAllLayers; + if (isIntra) { + linkWeightNormalizationFactor += (1.0 - relaxRate) / sumOutLinkWeightLayer1; + } + + auto& targetLinks = network2.nodeLinkMap(); + auto& targetOutlinks = targetLinks[StateNode(n1)]; + if (targetOutlinks.empty()) { + continue; + } + for (auto& outLink : targetOutlinks) { + auto& n2 = outLink.first.physicalId; + auto& linkData = outLink.second; + double intraWeight = linkData.weight; + unsigned int stateId2i = addMultilayerNode(layer2, n2); + + double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight; + addLink(stateId1, stateId2i, weight); + ++m_numInterLayerLinks; // TODO: Count all as one? + } + } + } + } +} + +double Network::calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2) +{ + intersect = false; + double h1 = 0.0; // The entropy rate of the node in the first layer + double h2 = 0.0; // The entropy rate of the node in the second layer + double h12 = 0.0; // The entropy rate of the lumped node + // The out-link weights of the nodes + double ow1 = sumOutLinkWeightLayer1; + double ow2 = sumOutLinkWeightLayer2; + // Normalized weights over node in layer 1 and 2 + double pi1 = ow1 / (ow1 + ow2); + double pi2 = ow2 / (ow1 + ow2); + + auto layer1OutLinkIt = layer1OutLinks.begin(); + auto layer2OutLinkIt = layer2OutLinks.begin(); + auto layer1OutLinkItEnd = layer1OutLinks.end(); + auto layer2OutLinkItEnd = layer2OutLinks.end(); + while (layer1OutLinkIt != layer1OutLinkItEnd && layer2OutLinkIt != layer2OutLinkItEnd) { + int diff = layer1OutLinkIt->first.id - layer2OutLinkIt->first.id; + if (diff < 0) { + // If the first state node has a link that the second has not + double p1 = layer1OutLinkIt->second.weight / ow1; + h1 -= p1 * log2(p1); + double p12 = pi1 * layer1OutLinkIt->second.weight / ow1; + h12 -= p12 * log2(p12); + layer1OutLinkIt++; + } else if (diff > 0) { + // If the second state node has a link that the second has not + double p2 = layer2OutLinkIt->second.weight / ow2; + h2 -= p2 * log2(p2); + double p12 = pi2 * layer2OutLinkIt->second.weight / ow2; + h12 -= p12 * log2(p12); + layer2OutLinkIt++; + } else { // If both state nodes have the link + intersect = true; + double p1 = layer1OutLinkIt->second.weight / ow1; + h1 -= p1 * log2(p1); + double p2 = layer2OutLinkIt->second.weight / ow2; + h2 -= p2 * log2(p2); + double p12 = pi1 * layer1OutLinkIt->second.weight / ow1 + pi2 * layer2OutLinkIt->second.weight / ow2; + h12 -= p12 * log2(p12); + layer1OutLinkIt++; + layer2OutLinkIt++; + } + } + + while (layer1OutLinkIt != layer1OutLinkItEnd) { + // If the first state node has a link that the second has not + double p1 = layer1OutLinkIt->second.weight / ow1; + h1 -= p1 * log2(p1); + double p12 = pi1 * layer1OutLinkIt->second.weight / ow1; + h12 -= p12 * log2(p12); + layer1OutLinkIt++; + } + + while (layer2OutLinkIt != layer2OutLinkItEnd) { + // If the second state node has a link that the second has not + double p2 = layer2OutLinkIt->second.weight / ow2; + h2 -= p2 * log2(p2); + double p12 = pi2 * layer2OutLinkIt->second.weight / ow2; + h12 -= p12 * log2(p12); + layer2OutLinkIt++; + } + + double div = (pi1 + pi2) * h12 - pi1 * h1 - pi2 * h2; + + // Fix precision problems + if (div < 0.0) + div = 0.0; + else if (div > 1.0) + div = 1.0; + + return div; +} + +void Network::simulateInterLayerLinks() +{ +} + +void Network::addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight) +{ + m_higherOrderInputMethodCalled = true; + bool added = m_networks[layer].addLink(n1, n2, weight); + if (added) { + ++m_numIntraLayerLinks; + m_maxNodeIdInIntraLayerNetworks = std::max(m_maxNodeIdInIntraLayerNetworks, std::max(n1, n2)); + } +} + +void Network::addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight) +{ + if (layer1 == layer2) { + throw std::runtime_error(io::Str() << "Inter-layer link (layer1, node, layer2): " << layer1 << ", " << n << ", " << layer2 << " must have layer1 != layer2"); + } + m_higherOrderInputMethodCalled = true; + + auto& interLinks = m_interLinks[LayerNode(layer1, n)]; + auto it = interLinks.find(layer2); + + if (it == interLinks.end()) { + ++m_numInterLayerLinks; + } + interLinks[layer2] += interWeight; +} + +unsigned int Network::addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight) +{ + m_higherOrderInputMethodCalled = true; + + // Create state node if not already exist, return state node id + auto& layerIt = m_layerNodeToStateId[layerId]; + auto it = layerIt.find(physicalId); + + if (it != layerIt.end()) { + return it->second; + } + + bool matchableMultilayerIds = m_config.matchableMultilayerIds != 0; + + if (matchableMultilayerIds && layerId > m_config.matchableMultilayerIds) { + throw std::runtime_error(io::Str() << "Cannot add node with layer " << layerId << " to network with matchable multilayer ids using largest layer id " << m_config.matchableMultilayerIds); + } + + auto ret = matchableMultilayerIds + ? addStateNodeWithDeterministicId(physicalId, layerId, m_multilayerStateIdBitShift) + : addStateNodeWithAutogeneratedId(physicalId); + auto& stateNode = ret.first->second; + stateNode.layerId = layerId; + stateNode.weight = weight; + m_layerNodeToStateId[layerId][physicalId] = stateNode.id; + m_layers.insert(layerId); + return stateNode.id; +} + +void Network::addMetaData(unsigned int nodeId, int meta) +{ + std::vector metaData(1, meta); + addMetaData(nodeId, metaData); +} + +void Network::addMetaData(unsigned int nodeId, const std::vector& metaData) +{ + m_metaData[nodeId] = metaData; + if (m_numMetaDataColumns == 0) { + m_numMetaDataColumns = metaData.size(); + } else if (metaData.size() != m_numMetaDataColumns) { + throw std::runtime_error(io::Str() << "Must have same number of dimensions in meta data, error trying to add meta data '" << io::stringify(metaData, ",") << "' on node " << nodeId << "."); + } +} + +} // namespace infomap diff --git a/vendor/infomap/src/io/Network.h b/vendor/infomap/src/io/Network.h new file mode 100644 index 0000000..a801db6 --- /dev/null +++ b/vendor/infomap/src/io/Network.h @@ -0,0 +1,214 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef NETWORK_H_ +#define NETWORK_H_ + +#include "Config.h" +#include "../core/StateNetwork.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace infomap { + +struct LayerNode; + +class Network : public StateNetwork { +private: + // Helpers + std::istringstream m_extractor; + + // Multilayer + std::map m_networks; // intra-layer links + std::map> m_interLinks; + // { layer -> { physId -> stateId }} + std::map> m_layerNodeToStateId; + std::map> m_sumIntraOutWeight; + std::set m_layers; + unsigned int m_numInterLayerLinks = 0; + unsigned int m_numIntraLayerLinks = 0; + unsigned int m_maxNodeIdInIntraLayerNetworks = 0; + + unsigned int m_multilayerStateIdBitShift = 0; + + // Meta data + std::map> m_metaData; + unsigned int m_numMetaDataColumns = 0; + + using InsensitiveStringSet = std::set; + + std::map m_ignoreHeadings; + std::map m_validHeadings; // { + // { "pajek", {"*Vertices", "*Edges", "*Arcs"} }, + // { "link-list", {"*Links"} }, + // { "bipartite", {"*Vertices", "*Bipartite"} }, + // { "general", {"*Vertices", "*States", "*Edges", "*Arcs", "*Links", "*Context"} } + // }; + +public: + Network() : StateNetwork() { init(); } + explicit Network(const Config& config) : StateNetwork(config) { init(); } + explicit Network(const std::string& flags) : StateNetwork(Config(flags)) { init(); } + ~Network() override = default; + + Network(const Network&) = delete; + Network& operator=(const Network&) = delete; + Network(Network&&) = delete; + Network& operator=(Network&&) = delete; + + void clear() override; + + /** + * Parse network data from file and generate network + * @param filename input network + * @param accumulate add to possibly existing network data (default), else clear before. + */ + virtual void readInputData(std::string filename = "", bool accumulate = true); + + /** + * Init categorical meta data on all nodes from a file with the following format: + * # nodeId metaData + * 1 1 + * 2 1 + * 3 2 + * 4 2 + * 5 3 + * @param filename input filename for metadata + */ + virtual void readMetaData(const std::string& filename); + + unsigned int numMetaDataColumns() const { return m_numMetaDataColumns; } + const std::map>& metaData() const override { return m_metaData; } + + bool isMultilayerNetwork() const { return !m_layerNodeToStateId.empty(); } + const std::map>& layerNodeToStateId() const { return m_layerNodeToStateId; } + + void postProcessInputData(); + void generateStateNetworkFromMultilayer(); + void generateStateNetworkFromMultilayerWithInterLinks(); + void generateStateNetworkFromMultilayerWithSimulatedInterLinks(); + void simulateInterLayerLinks(); + + /** + * Create state node corresponding to this multilayer node if not already exist + * @return state node id + */ + unsigned int addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight = 1.0); + + void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight); + + /** + * Create an intra-layer link + */ + void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight); + + /** + * Create links between (layer1,n) and (layer2,m) for all m connected to n in layer 2. + * The weight is distributed proportionally. + * TODO: This is done later.. + */ + void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight); + + void addMetaData(unsigned int nodeId, int meta); + + void addMetaData(unsigned int nodeId, const std::vector& metaData); + +private: + void init(); + void initValidHeadings(); + + void parseNetwork(const std::string& filename); + void parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading = ""); + + // Helper methods + + /** + * Parse vertices under the heading + * @return The line after the vertices + */ + std::string parseVertices(std::ifstream& file, const std::string& heading); + std::string parseStateNodes(std::ifstream& file, const std::string& heading); + + std::string parseLinks(std::ifstream& file); + + /** + * Parse multilayer links from a *multilayer section + */ + std::string parseMultilayerLinks(std::ifstream& file); + + /** + * Parse multilayer links from an *intra section + */ + std::string parseMultilayerIntraLinks(std::ifstream& file); + + /** + * Parse multilayer links from an *inter section + */ + std::string parseMultilayerInterLinks(std::ifstream& file); + + std::string parseBipartiteLinks(std::ifstream& file, const std::string& heading); + + static std::string ignoreSection(std::ifstream& file, const std::string& heading); + + void parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode); + + /** + * Parse a string of link data. + * If no weight data can be extracted, the default value 1.0 will be used. + * @throws an error if not both node ids can be extracted. + */ + void parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight); + + /** + * Parse a string of multilayer link data. + * If no weight data can be extracted, the default value 1.0 will be used. + * @throws an error if not both node and layer ids can be extracted. + */ + void parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight); + + /** + * Parse a string of intra-multilayer link data. + * If no weight data can be extracted, the default value 1.0 will be used. + * @throws an error if not both node and layer ids can be extracted. + */ + void parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight); + + /** + * Parse a string of inter-multilayer link data. + * If no weight data can be extracted, the default value 1.0 will be used. + * @throws an error if not both node and layer ids can be extracted. + */ + void parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight); + + static double calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2); + + void printSummary(); +}; + +struct LayerNode { + unsigned int layer, node; + explicit LayerNode(unsigned int layer = 0, unsigned int node = 0) : layer(layer), node(node) { } + + bool operator<(const LayerNode other) const + { + return layer == other.layer ? node < other.node : layer < other.layer; + } +}; + +} // namespace infomap + +#endif // NETWORK_H_ diff --git a/vendor/infomap/src/io/Output.cpp b/vendor/infomap/src/io/Output.cpp new file mode 100644 index 0000000..79f116c --- /dev/null +++ b/vendor/infomap/src/io/Output.cpp @@ -0,0 +1,629 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "Output.h" +#include "../core/InfomapBase.h" +#include "../core/StateNetwork.h" +#include "../io/SafeFile.h" + +namespace infomap { + +std::string getOutputFilename(const InfomapBase& im, const std::string& filename, const std::string& ext, bool states) +{ + if (!filename.empty()) { + return filename; + } + + auto defaultFilename = im.outDirectory + im.outName; + + if (im.haveMemory() && states) { + defaultFilename += "_states"; + } + + return defaultFilename + ext; +} + +std::string getOutputFileHeader(const InfomapBase& im, const StateNetwork& network, bool states) +{ + std::string bipartiteInfo = io::Str() << "\n# bipartite start id " << network.bipartiteStartId(); + return io::Str() << "# v" << INFOMAP_VERSION << "\n" + << "# ./Infomap " << im.parsedString << "\n" + << "# started at " << im.getStartDate() << "\n" + << "# completed in " << im.getElapsedTime().getElapsedTimeInSec() << " s\n" + << "# partitioned into " << im.maxTreeDepth() << " levels with " << im.numTopModules() << " top modules\n" + << "# codelength " << im.codelength() << " bits\n" + << "# relative codelength savings " << im.getRelativeCodelengthSavings() * 100 << "%\n" + << "# flow model " << flowModelToString(im.flowModel) + << (im.haveMemory() ? "\n# higher order" : "") + << (im.haveMemory() ? states ? "\n# state level" : "\n# physical level" : "") + << (network.isBipartite() ? bipartiteInfo : ""); +} + +std::string getNodeName(const std::map& names, const InfoNode& node) +{ + try { + return names.at(node.physicalId); + } catch (...) { + return io::stringify(node.physicalId); + } +} + +std::string writeClu(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, int moduleIndexLevel) +{ + auto outputFilename = getOutputFilename(im, filename, ".clu", states); + SafeOutFile outFile { outputFilename }; + + outFile << std::setprecision(9); + outFile << getOutputFileHeader(im, network, states) << "\n"; + outFile << "# module level " << moduleIndexLevel << "\n"; + outFile << std::resetiosflags(std::ios::floatfield) << std::setprecision(6); + + if (states) { + outFile << "# state_id module flow node_id"; + if (im.isMultilayerNetwork()) + outFile << " layer_id"; + outFile << '\n'; + } else { + outFile << "# node_id module flow\n"; + } + + const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes; + const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0; + + if (im.haveMemory() && !states) { + for (auto it(im.iterTreePhysical(moduleIndexLevel)); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n"; + } + } + } else { + for (auto it(im.iterTree(moduleIndexLevel)); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + if (states) { + outFile << node.stateId << " " << it.moduleId() << " " << node.data.flow << " " << node.physicalId; + if (im.isMultilayerNetwork()) + outFile << " " << node.layerId; + outFile << "\n"; + } else + outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n"; + } + } + } + return outputFilename; +} + +void writeTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states) +{ + auto oldPrecision = outStream.precision(); + outStream << std::setprecision(9); + outStream << getOutputFileHeader(im, network, states) << "\n"; + outStream << std::setprecision(6); + + if (states) { + outStream << "# path flow name state_id node_id"; + if (im.isMultilayerNetwork()) + outStream << " layer_id"; + outStream << '\n'; + } else { + outStream << "# path flow name node_id\n"; + } + + const auto shouldHideBipartiteNodes = !im.printFlowTree && im.isBipartite() && im.hideBipartiteNodes; + const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0; + + // TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here + if (im.haveMemory() && !states) { + for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + auto& path = it.path(); + + outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" " << node.physicalId << '\n'; + } + } + } else { + for (auto it(im.iterTree()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + auto& path = it.path(); + + outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" "; + + if (states) { + outStream << node.stateId << " " << node.physicalId; + if (im.isMultilayerNetwork()) + outStream << " " << node.layerId; + outStream << '\n'; + } else { + outStream << node.physicalId << '\n'; + } + } + } + } + + outStream << std::setprecision(oldPrecision); +} + +using Link = std::pair; +using LinkMap = std::map; + +std::map aggregateModuleLinks(InfomapBase& im, bool states) +{ + // Aggregate links between each module. Rest is aggregated as exit flow + + // Links on nodes within sub infomap instances doesn't have links outside the root + // so iterate over links on main instance and map to infomap tree iterator + bool mergePhysicalNodes = im.haveMemory() && !states; + + // Map state id to parent in infomap tree iterator + std::map stateIdToParent; + std::map stateIdToChildIndex; + + if (mergePhysicalNodes) { + for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) { + if (it->isLeaf()) { + for (auto stateId : it->stateNodes) { + stateIdToParent[stateId] = it->parent; + stateIdToChildIndex[stateId] = it.childIndex(); + } + } else { + // Use stateId to store depth on modules to simplify link aggregation + it->stateId = it.depth(); + it->index = it.childIndex(); + } + } + } else { + for (auto it(im.iterTree()); !it.isEnd(); ++it) { + if (it->isLeaf()) { + stateIdToParent[it->stateId] = it->parent; + stateIdToChildIndex[it->stateId] = it.childIndex(); + } else { + // Use stateId to store depth on modules to simplify link aggregation + it->stateId = it.depth(); + it->index = it.childIndex(); + } + } + } + + std::map moduleLinks; + + for (auto& leaf : im.leafNodes()) { + for (auto& link : leaf->outEdges()) { + double flow = link->data.flow; + InfoNode* sourceParent = stateIdToParent[link->source->stateId]; + InfoNode* targetParent = stateIdToParent[link->target->stateId]; + + auto sourceDepth = sourceParent->calculatePath().size() + 1; + auto targetDepth = targetParent->calculatePath().size() + 1; + + auto sourceChildIndex = stateIdToChildIndex[link->source->stateId]; + auto targetChildIndex = stateIdToChildIndex[link->target->stateId]; + + auto sourceParentIt = InfomapParentIterator(sourceParent); + auto targetParentIt = InfomapParentIterator(targetParent); + + // Iterate to same depth + // First raise target + while (targetDepth > sourceDepth) { + ++targetParentIt; + --targetDepth; + } + + // Raise source to same depth + while (sourceDepth > targetDepth) { + ++sourceParentIt; + --sourceDepth; + } + + auto currentDepth = sourceDepth; + // Add link if same parent + + while (currentDepth > 0) { + if (sourceParentIt == targetParentIt) { + // Skip self-links + if (sourceChildIndex != targetChildIndex) { + auto parentId = io::stringify(sourceParentIt->calculatePath(), ":"); + auto& linkMap = moduleLinks[parentId]; + linkMap[std::make_pair(sourceChildIndex + 1, targetChildIndex + 1)] += flow; + } + } + + sourceChildIndex = sourceParentIt->index; + targetChildIndex = targetParentIt->index; + + ++sourceParentIt; + ++targetParentIt; + + --currentDepth; + } + } + } + + return moduleLinks; +} + +void writeTreeLinks(InfomapBase& im, std::ostream& outStream, bool states) +{ + auto oldPrecision = outStream.precision(); + outStream << std::setprecision(6); + + auto moduleLinks = aggregateModuleLinks(im, states); + + outStream << "*Links " << (im.isUndirectedFlow() ? "undirected" : "directed") << "\n"; + outStream << "#*Links path enterFlow exitFlow numEdges numChildren\n"; + + // Use stateId to store depth on modules to optimize link aggregation + for (auto it(im.iterModules()); !it.isEnd(); ++it) { + auto parentId = io::stringify(it.path(), ":"); + auto& module = *it; + auto& links = moduleLinks[parentId]; + + outStream << "*Links " << (parentId.empty() ? "root" : parentId) << " " << module.data.enterFlow << " " << module.data.exitFlow << " " << links.size() << " " << module.infomapChildDegree() << "\n"; + + for (auto itLink : links) { + unsigned int sourceId = itLink.first.first; + unsigned int targetId = itLink.first.second; + double flow = itLink.second; + outStream << sourceId << " " << targetId << " " << flow << "\n"; + } + } + + outStream << std::setprecision(oldPrecision); +} + +void writeNewickTree(InfomapBase& im, std::ostream& outStream, bool states) +{ + auto oldPrecision = outStream.precision(); + outStream << std::setprecision(6); + + auto isRoot = true; + unsigned int lastDepth = 0; + std::vector flowStack; + + auto writeNewickNode = [&](const InfoNode& node, unsigned int depth) { + if (depth > lastDepth || isRoot) { + outStream << "("; + flowStack.push_back(node.data.flow); + if (node.isLeaf()) + outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow; + } else if (depth == lastDepth) { + outStream << ","; + flowStack[flowStack.size() - 1] = node.data.flow; + if (node.isLeaf()) { + outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow; + } + } else { + // depth < lastDepth + while (flowStack.size() > depth + 1) { + flowStack.pop_back(); + outStream << "):" << flowStack.back(); + } + flowStack[flowStack.size() - 1] = node.data.flow; + outStream << ","; + } + lastDepth = depth; + isRoot = false; + }; + + // TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here + if (im.haveMemory() && !states) { + for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) { + writeNewickNode(*it, it.depth()); + } + } else { + for (auto it(im.iterTree()); !it.isEnd(); ++it) { + writeNewickNode(*it, it.depth()); + } + } + while (flowStack.size() > 1) { + flowStack.pop_back(); + outStream << "):" << flowStack.back(); + } + outStream << ");\n"; + outStream << std::setprecision(oldPrecision); +} + +void writeJsonTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states, bool writeLinks) +{ + auto oldPrecision = outStream.precision(); + + outStream << "{"; + + outStream << "\"version\":\"v" << INFOMAP_VERSION << "\"," + << "\"args\":\"" << im.parsedString << "\"," + << "\"startedAt\":\"" << im.getStartDate() << "\"," + << "\"completedIn\":" << im.getElapsedTime().getElapsedTimeInSec() << "," + << "\"codelength\":" << im.codelength() << "," + << "\"numLevels\":" << im.maxTreeDepth() << "," + << "\"numTopModules\":" << im.numTopModules() << "," + << "\"relativeCodelengthSavings\":" << im.getRelativeCodelengthSavings() << "," + << "\"directed\":" << (im.isUndirectedFlow() ? "false" : "true") << "," + << "\"flowModel\": \"" << flowModelToString(im.flowModel) << "\"," + << "\"higherOrder\":" << (im.haveMemory() ? "true" : "false") << ","; + + if (im.haveMemory()) { + outStream << "\"stateLevel\":" << (states ? "true" : "false") << ","; + } + + if (im.isBipartite()) { + outStream << "\"bipartiteStartId\":" << network.bipartiteStartId() << ","; + } + + outStream << std::setprecision(6); + + outStream << "\"nodes\":["; + + const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes; + const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0; + + auto metaData = network.metaData(); + auto writeMeta = [&metaData](auto& outStream, auto nodeId) { + outStream << "\"metadata\":{"; + auto meta = metaData[nodeId]; + for (unsigned int i = 0; i < meta.size(); ++i) { + outStream << '"' << i << "\":" + << '"' << meta[i] << '"'; // metadata class as string to highlight that this is a categorical variable + if (i < meta.size() - 1) + outStream << ','; + } + outStream << "},"; + }; + + // don't append a comma after the last entry + auto first = true; + + if (im.haveMemory() && !states) { + for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + const auto path = io::stringify(it.path(), ","); + + if (first) { + first = false; + } else { + outStream << ","; + } + + outStream << "{" + << "\"path\":[" << path << "]," + << "\"name\":\"" << getNodeName(network.names(), node) << "\"," + << "\"flow\":" << node.data.flow << "," + << "\"mec\":" << it.modularCentrality() << "," + << "\"id\":" << node.physicalId << "}"; + } + } + } else { + const auto multilevelModules = im.getMultilevelModules(states); + + for (auto it(im.iterTree()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + if (first) { + first = false; + } else { + outStream << ","; + } + + const auto path = io::stringify(it.path(), ", "); + const auto modules = im.haveModules() ? io::stringify(multilevelModules.at(states ? node.stateId : node.physicalId), ", ") : "1"; + + outStream << "{" + << "\"path\":[" << path << "]," + << "\"modules\":[" << modules << "]," + << "\"name\":\"" << getNodeName(network.names(), node) << "\"," + << "\"flow\":" << node.data.flow << "," + << "\"mec\":" << it.modularCentrality() << ","; + + // can't currently use both memory and meta map equation + if (im.haveMetaData() && !states) { + writeMeta(outStream, node.physicalId); + } + + if (states) { + outStream << "\"stateId\":" << node.stateId << ","; + if (im.isMultilayerNetwork()) + outStream << "\"layerId\":" << node.layerId << ","; + } + + outStream << "\"id\":" << node.physicalId << "}"; + } + } + } + + outStream << "],"; // tree + + // ------------- + // Write modules + // ------------- + + // Uses stateId to store depth on modules to optimize link aggregation + auto moduleLinks = aggregateModuleLinks(im, states); + + first = true; + + outStream << "\"modules\":["; + + for (auto it(im.iterModules()); !it.isEnd(); ++it) { + const auto parentId = io::stringify(it.path(), ":"); + const auto& module = *it; + const auto& links = moduleLinks[parentId]; + const auto path = io::stringify(it.path(), ","); + + if (first) { + first = false; + } else { + outStream << ","; + } + + outStream << "{"; + + outStream << "\"path\":[" << (parentId.empty() ? "0" : path) << "]," + << "\"enterFlow\":" << module.data.enterFlow << ',' + << "\"exitFlow\":" << module.data.exitFlow << ',' + << "\"numEdges\":" << links.size() << ',' + << "\"numChildren\":" << module.infomapChildDegree() << ',' + << "\"codelength\":" << module.codelength; + + if (writeLinks) { + outStream << "," + << "\"links\":["; + + auto firstLink = true; + + for (auto itLink : links) { + if (firstLink) { + firstLink = false; + } else { + outStream << ","; + } + + unsigned int sourceId = itLink.first.first; + unsigned int targetId = itLink.first.second; + double flow = itLink.second; + outStream << "{\"source\":" << sourceId << ",\"target\":" << targetId << ",\"flow\":" << flow << "}"; + } + outStream << "]"; // links + } + + outStream << "}"; + } + + outStream << "]"; // modules + + outStream << "}"; + + outStream << std::setprecision(oldPrecision); +} + +void writeCsvTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states) +{ + auto oldPrecision = outStream.precision(); + outStream << std::setprecision(6); + + outStream << "path,flow,name,"; + + if (im.haveMemory() && !states) { + outStream << "node_id\n"; + } else { + if (states) { + outStream << "state_id,"; + if (im.isMultilayerNetwork()) + outStream << "layer_id,"; + } + outStream << "node_id\n"; + } + + const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes; + const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0; + + if (im.haveMemory() && !states) { + for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + const auto path = io::stringify(it.path(), ":"); + outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\"," << node.physicalId << '\n'; + } + } + } else { + for (auto it(im.iterTree()); !it.isEnd(); ++it) { + InfoNode& node = *it; + if (node.isLeaf()) { + if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) { + continue; + } + + const auto path = io::stringify(it.path(), ":"); + outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\","; + + if (states) { + outStream << node.stateId << ','; + if (im.isMultilayerNetwork()) + outStream << node.layerId << ','; + } + + outStream << node.physicalId << '\n'; + } + } + } + + outStream << std::setprecision(oldPrecision); +} + +std::string writeTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states) +{ + auto outputFilename = getOutputFilename(im, filename, ".tree", states); + SafeOutFile outFile { outputFilename }; + writeTree(im, network, outFile, states); + return outputFilename; +} + +std::string writeFlowTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states) +{ + auto outputFilename = getOutputFilename(im, filename, ".ftree", states); + SafeOutFile outFile { outputFilename }; + writeTree(im, network, outFile, states); + writeTreeLinks(im, outFile, states); + return outputFilename; +} + +std::string writeNewickTree(InfomapBase& im, const std::string& filename, bool states) +{ + auto outputFilename = getOutputFilename(im, filename, ".nwk", states); + SafeOutFile outFile { outputFilename }; + writeNewickTree(im, outFile, states); + return outputFilename; +} + +std::string writeJsonTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, bool writeLinks) +{ + auto outputFilename = getOutputFilename(im, filename, ".json", states); + SafeOutFile outFile { outputFilename }; + writeJsonTree(im, network, outFile, states, writeLinks); + return outputFilename; +} + +std::string writeCsvTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states) +{ + auto outputFilename = getOutputFilename(im, filename, ".csv", states); + SafeOutFile outFile { outputFilename }; + writeCsvTree(im, network, outFile, states); + return outputFilename; +} + +} // namespace infomap \ No newline at end of file diff --git a/vendor/infomap/src/io/Output.h b/vendor/infomap/src/io/Output.h new file mode 100644 index 0000000..2b64983 --- /dev/null +++ b/vendor/infomap/src/io/Output.h @@ -0,0 +1,36 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef OUTPUT_H_ +#define OUTPUT_H_ + +#include +#include +#include + +namespace infomap { + +class InfomapBase; +class StateNetwork; + +std::string writeTree(InfomapBase&, const StateNetwork&, const std::string&, bool states); + +std::string writeFlowTree(InfomapBase&, const StateNetwork&, const std::string&, bool states); + +std::string writeNewickTree(InfomapBase&, const std::string&, bool states); + +std::string writeJsonTree(InfomapBase&, const StateNetwork&, const std::string&, bool states, bool writeLinks); + +std::string writeCsvTree(InfomapBase&, const StateNetwork&, const std::string&, bool states); + +std::string writeClu(InfomapBase&, const StateNetwork&, const std::string&, bool states, int moduleIndexLevel); + +} // namespace infomap + +#endif // OUTPUT_H_ diff --git a/vendor/infomap/src/io/ProgramInterface.cpp b/vendor/infomap/src/io/ProgramInterface.cpp new file mode 100644 index 0000000..54d0624 --- /dev/null +++ b/vendor/infomap/src/io/ProgramInterface.cpp @@ -0,0 +1,362 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "ProgramInterface.h" +#include "../utils/Log.h" + +#include "igraph_error.h" + +#include +#include +#include +#include + +namespace infomap { + +const std::string ArgType::integer = "integer"; +const std::string ArgType::number = "number"; +const std::string ArgType::string = "string"; +const std::string ArgType::path = "path"; +const std::string ArgType::probability = "probability"; +const std::string ArgType::option = "option"; +const std::string ArgType::list = "list"; + +const std::unordered_map ArgType::toShort = { + { "integer", 'n' }, + { "number", 'f' }, + { "string", 's' }, + { "path", 'p' }, + { "probability", 'P' }, + { "option", 'o' }, + { "list", 'l' }, +}; + +ProgramInterface::ProgramInterface(std::string name, std::string shortDescription, std::string version) + : m_programName(std::move(name)), + m_shortProgramDescription(std::move(shortDescription)), + m_programVersion(std::move(version)) +{ + addIncrementalOptionArgument(m_displayHelp, 'h', "help", "Prints this help message. Use -hh to show advanced options.", "About"); + addOptionArgument(m_displayVersion, 'V', "version", "Display program version information.", "About"); + addOptionArgument(m_printJsonParameters, "print-json-parameters", "Print Infomap parameters in JSON.", "About").setHidden(true); +} + +/* Modification for igraph: Disable functions that call forbidden + * functions such as exit(). */ +#if 0 +void ProgramInterface::exitWithUsage(bool showAdvanced) const +{ + Log() << "Name:\n"; + Log() << " " << m_programName << " - " << m_shortProgramDescription << '\n'; + Log() << "\nUsage:\n"; + Log() << " " << m_executableName; + for (auto& nonOptionArgument : m_nonOptionArguments) + if (showAdvanced || !nonOptionArgument->isAdvanced) + Log() << " " << nonOptionArgument->variableName; + if (!m_optionArguments.empty()) + Log() << " [options]"; + Log() << '\n'; + + if (!m_programDescription.empty()) + Log() << "\nDescription:\n " << m_programDescription << '\n'; + + for (auto& nonOptionArgument : m_nonOptionArguments) + if (showAdvanced || !nonOptionArgument->isAdvanced) + Log() << "\n[" << nonOptionArgument->variableName << "]\n " << nonOptionArgument->description << '\n'; + + if (!m_optionArguments.empty()) + Log() << "\n[options]\n"; + + // First stringify the options part to get the maximum length + std::deque optionStrings(m_optionArguments.size()); + std::string::size_type maxLength = 0; + for (unsigned int i = 0; i < m_optionArguments.size(); ++i) { + auto& opt = *m_optionArguments[i]; + bool haveShort = opt.shortName != '\0'; + std::string optArgShort = opt.requireArgument ? (io::Str() << "<" << ArgType::toShort.at(opt.argumentName) << ">") : opt.incrementalArgument ? "[+]" + : std::string(3, ' '); + std::string optArgLong = opt.requireArgument ? (io::Str() << "<" << opt.argumentName << ">") : opt.incrementalArgument ? "[+]" + : std::string(3, ' '); + std::string shortOption = haveShort ? (io::Str() << " -" << opt.shortName << optArgShort) : std::string(7, ' '); + optionStrings[i] = io::Str() << shortOption << " --" << opt.longName << " " << optArgLong; + if (optionStrings[i].length() > maxLength) + maxLength = optionStrings[i].length(); + } + + std::vector groups { "About" }; + for (auto& group : m_groups) { + if (group != "About") + groups.push_back(group); + } + if (m_groups.empty()) + groups.emplace_back("All"); + + for (const auto& group : groups) { + if (group != "All") { + Log() << "\n" + << group << "\n"; + Log() << std::string(group.length(), '-') << "\n"; + } + for (unsigned int i = 0; i < m_optionArguments.size(); ++i) { + auto& opt = *m_optionArguments[i]; + if (group == "All" || opt.group == group) { + std::string::size_type numSpaces = maxLength + 3 - optionStrings[i].length(); + if (showAdvanced || !opt.isAdvanced) { + Log() << optionStrings[i] << std::string(numSpaces, ' ') << opt.description; + if (!opt.printNumericValue().empty()) + Log() << " (Default: " << opt.printNumericValue() << ")"; + Log() << "\n"; + } + } + } + } + Log() << '\n'; + std::exit(0); +} + +void ProgramInterface::exitWithVersionInformation() const +{ + Log() << m_programName << " version " << m_programVersion; +#ifdef _OPENMP + Log() << " compiled with OpenMP"; +#endif + Log() << '\n'; + Log() << "See www.mapequation.org for terms of use.\n"; + std::exit(0); +} +#endif + +void ProgramInterface::exitWithError(const std::string& message) const +{ + /* Modification for igraph: This function must never be called + * when using Infomap through igraph. The function is disabled + * to eliminate forbidden references to exit() and std::cerr. */ + IGRAPH_FATALF("Infomap called exitWithError() with message '%s'.", + message.c_str()); +#if 0 + Log() << m_programName << " version " << m_programVersion; +#ifdef _OPENMP + Log() << " compiled with OpenMP"; +#endif + Log() << std::endl; + std::cerr << message << std::endl; + Log() << "Usage: " << m_executableName; + for (auto& nonOptionArgument : m_nonOptionArguments) + if (!nonOptionArgument->isAdvanced) + Log() << " " << nonOptionArgument->variableName; + if (!m_optionArguments.empty()) + Log() << " [options]"; + Log() << ". Run with option '-h' for more information.\n"; + std::exit(1); +#endif +} + +std::string toJson(const std::string& key, const std::string& value) +{ + return io::Str() << '"' << key << "\": \"" << value << '"'; +} + +std::string toJson(const std::string& key, bool value) +{ + return io::Str() << '"' << key << "\": " << (value ? "true" : "false"); +} + +template +std::string toJson(const std::string& key, Value value) +{ + return io::Str() << '"' << key << "\": " << value; +} + +std::string toJson(const Option& opt) +{ + return io::Str() << "{ " + << toJson("long", std::string(io::Str() << "--" << opt.longName)) << ", " + << toJson("short", opt.shortName != '\0' ? std::string(io::Str() << "-" << opt.shortName) : "") << ", " + << toJson("description", opt.description) << ", " + << toJson("group", opt.group) << ", " + << toJson("required", opt.requireArgument) << ", " + << toJson("advanced", opt.isAdvanced) << ", " + << toJson("incremental", opt.incrementalArgument) << ", " + << (opt.requireArgument + ? (io::Str() << toJson("longType", opt.argumentName) << ", " + << toJson("shortType", std::string(1, ArgType::toShort.at(opt.argumentName))) << ", " + << toJson("default", opt.printValue())) + : toJson("default", false)) + << " }"; +} + +/* Modification for igraph: Disable functions that call forbidden + * functions such as exit(). */ +#if 0 +void ProgramInterface::exitWithJsonParameters() const +{ + Log() << "{\n \"parameters\": [\n"; + + for (unsigned int i = 0; i < m_optionArguments.size(); ++i) { + auto& opt = *m_optionArguments[i]; + if (opt.hidden) + continue; + Log() << " " << toJson(opt); + if (i < m_optionArguments.size() - 1) { + Log() << ",\n"; + } else { + Log() << "\n"; + } + } + Log() << " ]\n}"; + + std::exit(0); +} +#endif + +void ProgramInterface::parseArgs(const std::string& args) +{ + // Map the options on short and long name, and check for duplication + std::map shortOptionMap; + std::map longOptionMap; + for (auto& optionArgument : m_optionArguments) { + auto& opt = *optionArgument; + if (opt.shortName != '\0') { + auto it = shortOptionMap.find(opt.shortName); + if (it != shortOptionMap.end()) + throw std::runtime_error(io::Str() << "Duplication of option '" << opt.shortName << "'"); + shortOptionMap.insert(std::make_pair(opt.shortName, &opt)); + } + + auto it = longOptionMap.find(opt.longName); + if (it != longOptionMap.end()) + throw std::runtime_error(io::Str() << "Duplication of option \"" << opt.longName << "\""); + longOptionMap.insert(std::make_pair(opt.longName, &opt)); + } + + // Split the flags on whitespace + std::vector flags; + std::istringstream argStream(args); + + { + std::string arg; + while (!(argStream >> arg).fail()) + flags.push_back(arg); + } + + std::deque nonOpts; + try { + for (unsigned int i = 0; i < flags.size(); ++i) { + bool flagValue = true; + unsigned int numArgsLeft = flags.size() - i - 1; + + const std::string& arg = flags[i]; + if (arg.length() == 0) + throw std::runtime_error("Illegal argument ''"); + + if (arg[0] != '-') { + nonOpts.push_back(arg); + } else { + if (arg.length() < 2) + throw std::runtime_error("Illegal argument '-'"); + + if (arg[1] == '-') { + // Long option + if (arg.length() < 3) + throw std::runtime_error("Illegal argument '--'"); + std::string longOpt = arg.substr(2); + auto it = longOptionMap.find(longOpt); + if (it == longOptionMap.end()) { + // Unrecognized option, check if it negates a recognised option with the '--no-' prefix + if (longOpt.compare(0, 3, "no-") == 0 && longOptionMap.find(std::string(longOpt, 3)) != longOptionMap.end()) { + longOpt = std::string(longOpt, 3); + it = longOptionMap.find(longOpt); + flagValue = false; + } else { + throw std::runtime_error(io::Str() << "Unrecognized option: '--" << longOpt << "'"); + } + } + auto& opt = *it->second; + if (!opt.requireArgument || opt.incrementalArgument) + opt.set(flagValue); + else { + if (numArgsLeft == 0) + throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument"); + ++i; + if (!opt.parse(flags[i])) + throw std::runtime_error(io::Str() << "Cannot parse '" << flags[i] << "' as argument to option '" << opt.longName << "'. "); + } + } else { + // Short option(s) + for (unsigned int j = 1; j < arg.length(); ++j) { + char o = arg[j]; + unsigned int numCharsLeft = arg.length() - j - 1; + auto it = shortOptionMap.find(o); + if (it == shortOptionMap.end()) + throw std::runtime_error(io::Str() << "Unrecognized option: '-" << o << "'"); + auto& opt = *it->second; + if (!opt.requireArgument || opt.incrementalArgument) + opt.set(flagValue); + else { + std::string optArg; + if (numCharsLeft > 0) { + optArg = arg.substr(j + 1); + j = arg.length() - 1; + } else if (numArgsLeft) { + ++i; + optArg = flags[i]; + } else + throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument"); + + if (!opt.parse(optArg)) + throw std::runtime_error(io::Str() << "Cannot parse '" << optArg << "' as argument to option '" << opt.longName << "'. "); + } + } + } + } + /* Modification for igraph: Disable calls to functions that + * needed to be removed because they referenced exit(). */ +#if 0 + if (m_displayHelp > 0) + exitWithUsage(m_displayHelp > 1); + if (m_displayVersion) + exitWithVersionInformation(); + if (m_printJsonParameters) + exitWithJsonParameters(); +#endif + } + } catch (std::exception& e) { + exitWithError(e.what()); + } + + if (nonOpts.size() < numRequiredArguments()) + exitWithError("Missing required arguments."); + + unsigned int i = 0; + unsigned int numVectorArguments = nonOpts.size() - (m_nonOptionArguments.size() - 1); + while (!nonOpts.empty()) { + std::string arg = nonOpts.front(); + nonOpts.pop_front(); + if (m_nonOptionArguments[i]->isOptionalVector && numVectorArguments == 0) + ++i; + if (!m_nonOptionArguments[i]->parse(arg)) + exitWithError("Argument error."); + if (!m_nonOptionArguments[i]->isOptionalVector || --numVectorArguments == 0) + ++i; + } +} + +std::vector ProgramInterface::getUsedOptionArguments() const +{ + std::vector opts; + unsigned int numFlags = m_optionArguments.size(); + for (unsigned int i = 0; i < numFlags; ++i) { + auto& opt = *m_optionArguments[i]; + if (opt.used && opt.longName != "negate-next") + opts.emplace_back(opt); + } + return opts; +} + +} // namespace infomap diff --git a/vendor/infomap/src/io/ProgramInterface.h b/vendor/infomap/src/io/ProgramInterface.h new file mode 100644 index 0000000..21841cb --- /dev/null +++ b/vendor/infomap/src/io/ProgramInterface.h @@ -0,0 +1,423 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef PROGRAM_INTERFACE_H_ +#define PROGRAM_INTERFACE_H_ + +#include "../utils/convert.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace infomap { + +class InterruptException : public std::exception { +public: + InterruptException() {}; +}; + +typedef bool interruptionHandlerFn(void); + +struct ArgType { + static const std::string integer; + static const std::string number; + static const std::string string; + static const std::string path; + static const std::string probability; + static const std::string option; + static const std::string list; + static const std::unordered_map toShort; +}; + +struct Option { + Option(char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, bool requireArgument = false, std::string argName = "") + : shortName(shortName), + longName(std::move(longName)), + description(std::move(desc)), + group(std::move(group)), + isAdvanced(isAdvanced), + requireArgument(requireArgument), + incrementalArgument(false), + argumentName(std::move(argName)) { } + + virtual ~Option() = default; + + Option(const Option&) = default; + Option& operator=(const Option&) = default; + Option(Option&&) = default; + Option& operator=(Option&&) = default; + + virtual bool parse(std::string const&) + { + used = true; + return true; + } + + virtual void set(bool value) + { + used = true; + negated = !value; + }; + + Option& setHidden(bool value) + { + hidden = value; + return *this; + } + + virtual std::ostream& printValue(std::ostream& out) const { return out; } + virtual std::string printValue() const { return ""; } + virtual std::string printNumericValue() const { return ""; } + + friend std::ostream& operator<<(std::ostream& out, const Option& option) + { + out << option.longName; + if (option.requireArgument) { + out << " = "; + option.printValue(out); + } + return out; + } + + char shortName; + std::string longName; + std::string description; + std::string group; + bool isAdvanced; + bool requireArgument; + bool incrementalArgument; + std::string argumentName; + bool hidden = false; + bool used = false; + bool negated = false; +}; + +struct IncrementalOption : Option { + IncrementalOption(unsigned int& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced) + : Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target) + { + incrementalArgument = true; + } + + bool parse(std::string const& value) override + { + Option::parse(value); + return ++target; + } + + void set(bool value) override + { + Option::set(value); + if (value) { + ++target; + } else if (target > 0) { + --target; + } + } + + std::ostream& printValue(std::ostream& out) const override { return out << target; } + std::string printValue() const override { return io::Str() << target; } + + unsigned int& target; +}; + +template +struct ArgumentOption : Option { + ArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName) + : Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, true, std::move(argName)), target(target) { } + + bool parse(std::string const& value) override + { + Option::parse(value); + return io::stringToValue(value, target); + } + + std::string printValue() const override { return io::Str() << target; } + std::ostream& printValue(std::ostream& out) const override { return out << target; } + std::string printNumericValue() const override { return TypeInfo::isNumeric() ? printValue() : ""; } + + T& target; +}; + +template +struct LowerBoundArgumentOption : ArgumentOption { + LowerBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue) + : ArgumentOption(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName)), minValue(minValue) { } + + bool parse(std::string const& value) override + { + auto ok = ArgumentOption::parse(value); + if (ArgumentOption::target < minValue) return false; + return ok; + } + + T minValue; +}; + +template +struct LowerUpperBoundArgumentOption : LowerBoundArgumentOption { + LowerUpperBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue, T maxValue) + : LowerBoundArgumentOption(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName), minValue), maxValue(maxValue) { } + + bool parse(std::string const& value) override + { + auto ok = LowerBoundArgumentOption::parse(value); + if (LowerBoundArgumentOption::target > maxValue) return false; + return ok; + } + + T maxValue; +}; + +template <> +struct ArgumentOption : Option { + ArgumentOption(bool& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced) + : Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target) { } + + bool parse(std::string const& value) override + { + Option::parse(value); + return target = true; + } + + void set(bool value) override + { + Option::set(value); + target = value; + } + + std::ostream& printValue(std::ostream& out) const override { return out << target; } + std::string printValue() const override { return io::Str() << target; } + std::string printNumericValue() const override { return ""; } + + bool& target; +}; + +struct ParsedOption { + explicit ParsedOption(const Option& opt) + : shortName(opt.shortName), + longName(opt.longName), + description(opt.description), + group(opt.group), + isAdvanced(opt.isAdvanced), + requireArgument(opt.requireArgument), + incrementalArgument(opt.incrementalArgument), + argumentName(opt.argumentName), + negated(opt.negated), + value(opt.printValue()) { } + + friend std::ostream& operator<<(std::ostream& out, const ParsedOption& option) + { + if (option.negated) + out << "no "; + out << option.longName; + if (option.requireArgument) + out << " = " << option.value; + return out; + } + + char shortName; + std::string longName; + std::string description; + std::string group; + bool isAdvanced; + bool requireArgument; + bool incrementalArgument; + std::string argumentName; + bool negated; + std::string value; +}; + +struct TargetBase { + TargetBase(std::string variableName, std::string desc, std::string group, bool isAdvanced) + : variableName(std::move(variableName)), description(std::move(desc)), group(std::move(group)), isOptionalVector(false), isAdvanced(isAdvanced) { } + + virtual ~TargetBase() = default; + + TargetBase(const TargetBase&) = default; + TargetBase& operator=(const TargetBase&) = default; + TargetBase(TargetBase&&) = default; + TargetBase& operator=(TargetBase&&) = default; + + virtual bool parse(std::string const& value) = 0; + + std::string variableName; + std::string description; + std::string group; + bool isOptionalVector = false; + bool isAdvanced; +}; + +template +struct Target : TargetBase { + Target(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced) + : TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), target(target) { } + + bool parse(std::string const& value) override + { + return io::stringToValue(value, target); + } + + T& target; +}; + +template +struct OptionalTargets : TargetBase { + OptionalTargets(std::vector& target, std::string variableName, std::string desc, std::string group, bool isAdvanced) + : TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), targets(target) + { + isOptionalVector = true; + } + + bool parse(std::string const& value) override + { + T target; + bool ok = io::stringToValue(value, target); + if (ok) + targets.push_back(target); + return ok; + } + + std::vector& targets; +}; + +class ProgramInterface { +public: + ProgramInterface(std::string name, std::string shortDescription, std::string version); + + void setGroups(std::vector groups) { m_groups = std::move(groups); } + + template + void addNonOptionArgument(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false) + { + m_nonOptionArguments.emplace_back(new Target(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced)); + } + + template + void addOptionalNonOptionArguments(std::vector& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false) + { + if (m_numOptionalNonOptionArguments != 0) + throw std::runtime_error("Can't have two non-option vector arguments"); + ++m_numOptionalNonOptionArguments; + m_nonOptionArguments.emplace_back(new OptionalTargets(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced)); + } + + Option& addOptionArgument(char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false) + { + auto* o = new Option(shortName, std::move(longName), std::move(description), std::move(group), isAdvanced); + m_optionArguments.emplace_back(o); + return *o; + } + + Option& addIncrementalOptionArgument(unsigned int& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false) + { + auto* o = new IncrementalOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced); + m_optionArguments.emplace_back(o); + return *o; + } + + Option& addOptionArgument(bool& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false) + { + auto* o = new ArgumentOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced); + m_optionArguments.emplace_back(o); + return *o; + } + + // Without shortName + Option& addOptionArgument(bool& target, std::string longName, std::string description, std::string group, bool isAdvanced = false) + { + return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(group), isAdvanced); + } + + template + Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false) + { + auto* o = new ArgumentOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName)); + m_optionArguments.emplace_back(o); + return *o; + } + + template + Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false) + { + auto* o = new LowerBoundArgumentOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue); + m_optionArguments.emplace_back(o); + return *o; + } + + template + Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false) + { + auto* o = new LowerUpperBoundArgumentOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue, maxValue); + m_optionArguments.emplace_back(o); + return *o; + } + + // Without shortName + template + Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false) + { + return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), isAdvanced); + } + + template + Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false) + { + return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, isAdvanced); + } + + template + Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false) + { + return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, maxValue, isAdvanced); + } + + void parseArgs(const std::string& args); + + std::vector getUsedOptionArguments() const; + + unsigned int numRequiredArguments() const { return m_nonOptionArguments.size() - m_numOptionalNonOptionArguments; } + +private: +/* Modification for igraph: Disable functions that call forbidden + * functions such as exit(). */ +#if 0 + void exitWithUsage(bool showAdvanced) const; + void exitWithVersionInformation() const; +#endif + void exitWithError(const std::string& message) const; +#if 0 + void exitWithJsonParameters() const; +#endif + + std::deque> m_optionArguments; + std::deque> m_nonOptionArguments; + std::string m_programName = "Infomap"; + std::string m_shortProgramDescription; + std::string m_programVersion; + std::string m_programDescription; + std::vector m_groups; + std::string m_executableName = "Infomap"; + unsigned int m_displayHelp = 0; + bool m_displayVersion = false; + bool m_printJsonParameters = false; + + unsigned int m_numOptionalNonOptionArguments = 0; +}; + +} // namespace infomap + +#endif // PROGRAM_INTERFACE_H_ diff --git a/vendor/infomap/src/io/SafeFile.h b/vendor/infomap/src/io/SafeFile.h new file mode 100644 index 0000000..967cd09 --- /dev/null +++ b/vendor/infomap/src/io/SafeFile.h @@ -0,0 +1,87 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef SAFEFILE_H_ +#define SAFEFILE_H_ + +#include "../utils/convert.h" +#include +#include +#include +#include +#include + +namespace infomap { + +using std::ifstream; +using std::ofstream; + +/** + * A wrapper for the C++ file stream class that automatically closes + * the file stream when the destructor is called. Allocate it on the + * stack to have it automatically closed when going out of scope. + * + * Note: + * In C++, the only code that can be guaranteed to be executed after an + * exception is thrown are the destructors of objects residing on the stack. + * + * You can exploit that fact to avoid resource leaks by tying all resources + * to the lifespan of an object allocated on the stack. This technique is + * called Resource Acquisition Is Initialization (RAII). + * + */ +class SafeInFile : public ifstream { +public: + SafeInFile(const std::string& filename, ios_base::openmode mode = ios_base::in) + : ifstream(filename, mode) + { + if (fail()) + throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the path points to a file and that you have read permissions."); + } + + ~SafeInFile() override + { + if (is_open()) + close(); + } +}; + +class SafeOutFile : public ofstream { +public: + SafeOutFile(const std::string& filename, ios_base::openmode mode = ios_base::out) + : ofstream(filename, mode) + { + if (fail()) + throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the directory you are writing to exists and that you have write permissions."); + } + + ~SafeOutFile() override + { + if (is_open()) + close(); + } +}; + +inline bool isDirectoryWritable(const std::string& dir) +{ + std::string path = io::Str() << dir << "_1nf0m4p_.tmp"; + bool ok = true; + try { + SafeOutFile out(path); + } catch (const std::runtime_error&) { + ok = false; + } + if (ok) + std::remove(path.c_str()); + return ok; +} + +} // namespace infomap + +#endif // SAFEFILE_H_ diff --git a/vendor/infomap/src/utils/Date.h b/vendor/infomap/src/utils/Date.h new file mode 100644 index 0000000..98feced --- /dev/null +++ b/vendor/infomap/src/utils/Date.h @@ -0,0 +1,69 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef DATE_H_ +#define DATE_H_ + +#include +#include +#include + +namespace infomap { + +class ElapsedTime { +public: + ElapsedTime(double elapsedTime = 0.0) : m_elapsedTime(elapsedTime) { } + + double getSeconds() const { return m_elapsedTime; } + + friend std::ostream& operator<<(std::ostream& out, const ElapsedTime& elapsedTime) + { + auto temp = static_cast(std::floor(elapsedTime.getSeconds())); + if (temp > 60) { + if (temp > 3600) { + if (temp > 86400) { + out << temp / 86400 << "d "; + temp %= 86400; + } + out << temp / 3600 << "h "; + temp %= 3600; + } + out << temp / 60 << "m "; + temp %= 60; + out << temp << "s"; + } else { + out << temp << "s"; + } + return out; + } + +private: + double m_elapsedTime; +}; + +class Date { +public: + friend std::ostream& operator<<(std::ostream& out, const Date& date) + { + struct std::tm t = *localtime(&date.m_timeOfCreation); + return out << "" << (t.tm_year + 1900) << (t.tm_mon < 9 ? "-0" : "-") << (t.tm_mon + 1) << (t.tm_mday < 10 ? "-0" : "-") << t.tm_mday << (t.tm_hour < 10 ? " 0" : " ") << t.tm_hour << (t.tm_min < 10 ? ":0" : ":") << t.tm_min << (t.tm_sec < 10 ? ":0" : ":") << t.tm_sec << ""; + } + + ElapsedTime operator-(const Date& date) const + { + return { difftime(m_timeOfCreation, date.m_timeOfCreation) }; + } + +private: + std::time_t m_timeOfCreation = time(nullptr); +}; + +} // namespace infomap + +#endif // DATE_H_ diff --git a/vendor/infomap/src/utils/FileURI.cpp b/vendor/infomap/src/utils/FileURI.cpp new file mode 100644 index 0000000..c466d58 --- /dev/null +++ b/vendor/infomap/src/utils/FileURI.cpp @@ -0,0 +1,50 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "FileURI.h" +#include "convert.h" +#include +#include + +using std::string; + +namespace infomap { + +FileURI::FileURI(string filename, bool requireExtension) + : m_filename(std::move(filename)), m_requireExtension(requireExtension) +{ + auto getErrorMessage = [](const auto& name, auto requireExt) { + string s = io::Str() << "Filename '" << name << "' must match the pattern \"[dir/]name" << (requireExt ? ".extension\"" : "[.extension]\""); + return s; + }; + + auto name = m_filename; + auto pos = m_filename.find_last_of('/'); + if (pos != string::npos) { + if (pos == m_filename.length()) // File could not end with slash + throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension)); + m_directory = m_filename.substr(0, pos + 1); // Include the last slash in the directory + name = m_filename.substr(pos + 1); // No slash in the name + } else { + m_directory = ""; + } + + pos = name.find_last_of('.'); + if (pos == string::npos || pos == 0 || pos == name.length() - 1) { + if (pos != string::npos || m_requireExtension) + throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension)); + m_name = name; + m_extension = ""; + } else { + m_name = name.substr(0, pos); + m_extension = name.substr(pos + 1); + } +} + +} // namespace infomap diff --git a/vendor/infomap/src/utils/FileURI.h b/vendor/infomap/src/utils/FileURI.h new file mode 100644 index 0000000..8dc1511 --- /dev/null +++ b/vendor/infomap/src/utils/FileURI.h @@ -0,0 +1,54 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef FILEURI_H_ +#define FILEURI_H_ + +#include +#include + +namespace infomap { + +/** + * Filename class to simplify handling of parts of a filename. + * If a path is path/to/file.ext, the member methods of this class give these parts: + * getFilename -> "path/to/file.ext" + * getName -> "file" + * getExtension -> "ext" + * Can throw std::invalid_argument on creation. + */ +class FileURI { +public: + FileURI() = default; + + explicit FileURI(std::string filename, bool requireExtension = false); + + const std::string& getFilename() const { return m_filename; } + + const std::string& getName() const { return m_name; } + + const std::string& getExtension() const { return m_extension; } + + friend std::ostream& operator<<(std::ostream& out, const FileURI& file) + { + return out << file.getFilename(); + } + +private: + std::string m_filename; + bool m_requireExtension = false; + + std::string m_directory; + std::string m_name; + std::string m_extension; +}; + +} // namespace infomap + +#endif // FILEURI_H_ diff --git a/vendor/infomap/src/utils/FlowCalculator.cpp b/vendor/infomap/src/utils/FlowCalculator.cpp new file mode 100644 index 0000000..723f167 --- /dev/null +++ b/vendor/infomap/src/utils/FlowCalculator.cpp @@ -0,0 +1,898 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "FlowCalculator.h" +#include "../utils/Log.h" +#include "../utils/infomath.h" +#include "../core/StateNetwork.h" +#include +#include +#include +#include +#include +#include + +namespace infomap { + +template +inline void normalize(std::vector& v, const T sum) noexcept +{ + for (auto& numerator : v) { + numerator /= sum; + } +} + +template +inline void normalize(std::vector& v) noexcept +{ + const auto sum = std::accumulate(cbegin(v), cend(v), T {}); + normalize(v, sum); +} + +FlowCalculator::FlowCalculator(StateNetwork& network, const Config& config) + : numNodes(network.numNodes()) +{ + Log() << "Calculating global network flow using flow model '" << config.flowModel << "'... " << std::flush; + + // Prepare data in sequence containers for fast access of individual elements + // Map to zero-based dense indexing + nodeFlow.assign(numNodes, 0.0); + nodeTeleportWeights.assign(numNodes, 0.0); // Fraction of teleportation flow landing on node i + + nodeOutDegree.assign(numNodes, 0); + sumLinkOutWeight.assign(numNodes, 0.0); + + unsigned int nodeIndex = 0; + const auto& nodeLinkMap = network.nodeLinkMap(); + + if (network.isBipartite()) { + // Preserve node order + for (const auto& node : network.nodes()) { + const auto nodeId = node.second.id; + nodeIndexMap[nodeId] = nodeIndex++; + } + + auto bipartiteStartId = network.bipartiteStartId(); + bipartiteStartIndex = nodeIndexMap[bipartiteStartId]; + + } else { + if (config.flowModel != FlowModel::directed) { + // Preserve node order + for (const auto& node : network.nodes()) { + const auto nodeId = node.second.id; + nodeIndexMap[nodeId] = nodeIndex++; + } + } else { + // Store dangling nodes out-of-order, + // with dangling nodes first to optimize calculation of dangling rank + + for (const auto& node : network.nodes()) { + const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end(); + if (!isDangling) continue; + + const auto& nodeId = node.second.id; + nodeIndexMap[nodeId] = nodeIndex++; + } + + nonDanglingStartIndex = nodeIndex; + + for (const auto& node : network.nodes()) { + const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end(); + if (isDangling) continue; + + const auto& nodeId = node.second.id; + nodeIndexMap[nodeId] = nodeIndex++; + } + } + } + + flowLinks.resize(network.numLinks(), { 0, 0, 0.0 }); + sumLinkWeight = network.sumLinkWeight(); + sumWeightedDegree = network.sumWeightedDegree(); + + if (network.isBipartite()) { + const auto bipartiteStartId = network.bipartiteStartId(); + + for (const auto& node : nodeLinkMap) { + const auto sourceIsFeature = node.first.id >= bipartiteStartId; + if (sourceIsFeature) continue; + bipartiteLinkStartIndex += node.second.size(); + } + } + + unsigned int linkIndex = 0; + unsigned int featureLinkIndex = bipartiteLinkStartIndex; // bipartite case + + for (const auto& node : nodeLinkMap) { + const auto sourceId = node.first.id; + const auto sourceIndex = nodeIndexMap[sourceId]; + + for (const auto& link : node.second) { + const auto targetId = link.first.id; + const auto targetIndex = nodeIndexMap[targetId]; + const auto linkWeight = link.second.weight; + + ++nodeOutDegree[sourceIndex]; + sumLinkOutWeight[sourceIndex] += linkWeight; + nodeFlow[sourceIndex] += linkWeight / sumWeightedDegree; + + if (network.isBipartite() && sourceId >= network.bipartiteStartId()) { + // Link from feature node to ordinary node + flowLinks[featureLinkIndex].source = sourceIndex; + flowLinks[featureLinkIndex].target = targetIndex; + flowLinks[featureLinkIndex].flow = linkWeight; + ++featureLinkIndex; + } else { + // Ordinary link, or unipartite + flowLinks[linkIndex].source = sourceIndex; + flowLinks[linkIndex].target = targetIndex; + flowLinks[linkIndex].flow = linkWeight; + ++linkIndex; + } + + if (sourceIndex != targetIndex) { + if (config.isUndirectedFlow()) { + ++nodeOutDegree[targetIndex]; + sumLinkOutWeight[targetIndex] += linkWeight; + } + if (config.flowModel != FlowModel::outdirdir) { + nodeFlow[targetIndex] += linkWeight / sumWeightedDegree; + } + } + } + } + + bool normalizeNodeFlow = false; + + switch (config.flowModel) { + case FlowModel::undirected: + if (config.regularized) { + calcUndirectedRegularizedFlow(network, config); + } else { + calcUndirectedFlow(); + } + break; + case FlowModel::directed: + if (network.isBipartite() && config.bipartiteTeleportation) { + calcDirectedBipartiteFlow(network, config); + } else { + if (config.regularized) { + calcDirectedRegularizedFlow(network, config); + } else { + calcDirectedFlow(network, config); + } + } + break; + case FlowModel::undirdir: + case FlowModel::outdirdir: + calcDirdirFlow(config); + normalizeNodeFlow = true; + break; + case FlowModel::rawdir: + calcRawdirFlow(); + normalizeNodeFlow = true; + break; + case FlowModel::precomputed: + usePrecomputedFlow(network, config); + normalizeNodeFlow = true; + break; + } + + finalize(network, config, normalizeNodeFlow); +} + +void FlowCalculator::calcUndirectedFlow() noexcept +{ + Log() << "\n -> Using undirected links."; + + // Flow is outgoing transition probability times source node flow + // = w_ij / s_ij * s_ij / sum(s_ij) = w_ij / sum(s_ij) + // Count twice for non-loops to cover flow in both directions + // Assuming convention to treat self-links as directed + + for (auto& link : flowLinks) { + link.flow /= sumWeightedDegree; + if (link.source != link.target) { + link.flow *= 2; + } + } +} + +void FlowCalculator::calcDirdirFlow(const Config& config) noexcept +{ + if (config.flowModel == FlowModel::outdirdir) + Log() << "\n -> Counting only ingoing links."; + else + Log() << "\n -> Using undirected links, switching to directed after steady state."; + + // Take one last power iteration + const std::vector nodeFlowSteadyState(nodeFlow); + nodeFlow.assign(numNodes, 0.0); + + for (const auto& link : flowLinks) { + nodeFlow[link.target] += nodeFlowSteadyState[link.source] * link.flow / sumLinkOutWeight[link.source]; + } + + double sumNodeFlow = std::accumulate(cbegin(nodeFlow), cend(nodeFlow), 0.0); + + // Update link data to represent flow instead of weight + for (auto& link : flowLinks) { + link.flow *= nodeFlowSteadyState[link.source] / sumLinkOutWeight[link.source] / sumNodeFlow; + } +} + +void FlowCalculator::calcRawdirFlow() noexcept +{ + Log() << "\n -> Using directed links with raw flow."; + Log() << "\n -> Total link weight: " << sumLinkWeight << "."; + + // Treat the link weights as flow (after global normalization) and + // do one power iteration to set the node flow + nodeFlow.assign(numNodes, 0.0); + + for (auto& link : flowLinks) { + link.flow /= sumLinkWeight; + nodeFlow[link.target] += link.flow; + } +} + +void FlowCalculator::usePrecomputedFlow(const StateNetwork& network, const Config& config) +{ + Log() << "\n -> Using directed links with precomputed flow from input data."; + Log() << "\n -> Total link flow: " << sumLinkWeight << "."; + + if (network.haveFileInput()) { + if (network.haveMemoryInput() && !network.haveStateNodeWeights()) { + Log() << std::endl; + throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *States section."); + } + if (!network.haveMemoryInput() && !network.haveNodeWeights()) { + Log() << std::endl; + throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *Vertices section."); + } + } + + // Treat the link weights as flow + nodeFlow.assign(numNodes, 0.0); + double sumFlow = 0.0; + + for (const auto& nodeIt : network.nodes()) { + auto& node = nodeIt.second; + nodeFlow[nodeIndexMap[node.id]] = node.weight; + sumFlow += node.weight; + } + Log() << "\n -> Total node flow: " << sumFlow << "."; + + if (infomath::isEqual(sumFlow, 0)) { + throw std::runtime_error("Missing node flow. Set it on the node weight property."); + } + if (!infomath::isEqual(sumFlow, 1)) { + if (infomath::isEqual(sumFlow, numNodes) && infomath::isEqual(nodeFlow[0], 1)) { + Log() << "\n Warning: Node flow sums to the number of nodes, is node flow provided or is default node weights used? Normalizing."; + } else { + Log() << "\n Warning: Node flow sums to " << sumFlow << ", normalizing."; + } + for (unsigned int i = 0; i < numNodes; ++i) { + nodeFlow[i] /= sumFlow; + } + } +} + +struct IterationResult { + double alpha; + double beta; +}; + +template +IterationResult powerIterate(double alpha, Iteration&& iter) +{ + unsigned int iterations = 0; + double beta = 1.0 - alpha; + double err = 0.0; + + do { + double oldErr = err; + err = iter(iterations, alpha, beta); + + // Perturb the system if equilibrium + if (std::abs(err - oldErr) < 1e-17) { + alpha += 1.0e-12; + beta = 1.0 - alpha; + } + + ++iterations; + } while (iterations < 200 && (err > 1.0e-15 || iterations < 50)); + + Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n"; + + return { alpha, beta }; +} + +void FlowCalculator::calcDirectedFlow(const StateNetwork& network, const Config& config) noexcept +{ + Log() << "\n -> Using " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush; + + // Calculate the teleport rate distribution + if (config.teleportToNodes) { + double sumNodeWeights = 0.0; + + for (const auto& nodeIt : network.nodes()) { + auto& node = nodeIt.second; + nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight; + sumNodeWeights += node.weight; + } + + normalize(nodeTeleportWeights, sumNodeWeights); + } else { + // Teleport to links + + // Teleport proportionally to out-degree, or in-degree if recorded teleportation. + for (const auto& link : flowLinks) { + auto toNode = config.recordedTeleportation ? link.target : link.source; + nodeTeleportWeights[toNode] += link.flow / sumLinkWeight; + } + } + + // Normalize link weights with respect to its source nodes total out-link weight; + for (auto& link : flowLinks) { + if (sumLinkOutWeight[link.source] > 0) { + link.flow /= sumLinkOutWeight[link.source]; + } + } + + std::vector nodeFlowTmp(numNodes, 0.0); + double danglingRank; + + // Calculate PageRank + const auto iteration = [&](const auto iter, const double alpha, const double beta) { + danglingRank = std::accumulate(cbegin(nodeFlow), cbegin(nodeFlow) + nonDanglingStartIndex, 0.0); + + // Flow from teleportation + const auto teleportationFlow = alpha + beta * danglingRank; + for (unsigned int i = 0; i < numNodes; ++i) { + nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i]; + } + + // Flow from links + for (const auto& link : flowLinks) { + nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source]; + } + + // Update node flow from the power iteration above and check if converged + double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later + double error = 0.0; + for (unsigned int i = 0; i < numNodes; ++i) { + nodeFlowDiff += nodeFlowTmp[i]; + error += std::abs(nodeFlowTmp[i] - nodeFlow[i]); + } + + nodeFlow = nodeFlowTmp; + + // Normalize if needed + if (std::abs(nodeFlowDiff) > 1.0e-10) { + Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") "; + normalize(nodeFlow, nodeFlowDiff + 1.0); + } + + return error; + }; + + const auto result = powerIterate(config.teleportationProbability, iteration); + + double sumNodeRank = 1.0; + double beta = result.beta; + + if (!config.recordedTeleportation) { + // Take one last power iteration excluding the teleportation + // and normalize node flow + sumNodeRank = 1.0 - danglingRank; + nodeFlow.assign(numNodes, 0.0); + + for (const auto& link : flowLinks) { + nodeFlow[link.target] += link.flow * nodeFlowTmp[link.source] / sumNodeRank; + } + + beta = 1.0; + } + + // Update the links with their global flow from the PageRank values. + // Note: beta is set to 1 if unrecorded teleportation + for (auto& link : flowLinks) { + link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank; + } +} + +void FlowCalculator::calcDirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept +{ + Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush; + + // Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree + unsigned int N = network.numNodes(); + + std::vector k_out(N, 0); + std::vector k_in(N, 0); + std::vector s_out(N, 0); + std::vector s_in(N, 0); + double sum_s = sumWeightedDegree; + unsigned int sum_k = network.sumDegree(); + double average_weight = sum_s / sum_k; + + for (auto& link : flowLinks) { + k_out[link.source] += 1; + s_out[link.source] += link.flow; + k_in[link.target] += 1; + s_in[link.target] += link.flow; + } + + double min_u_out = std::numeric_limits::max(); + double min_u_in = std::numeric_limits::max(); + for (unsigned int i = 0; i < N; ++i) { + if (k_out[i] > 0) { + min_u_out = std::min(min_u_out, s_out[i] / k_out[i]); + } + if (k_in[i] > 0) { + min_u_in = std::min(min_u_in, s_in[i] / k_in[i]); + } + } + + auto u_out = [s_out, k_out, min_u_out](auto i) { return k_out[i] == 0 ? min_u_out : s_out[i] / k_out[i]; }; + auto u_in = [s_in, k_in, min_u_in](auto i) { return k_in[i] == 0 ? min_u_in : s_in[i] / k_in[i]; }; + + unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N; + double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets; + double u_t = average_weight; + + double sum_u_in = 0.0; + for (unsigned int i = 0; i < N; ++i) { + sum_u_in += u_in(i); + } + + for (unsigned int i = 0; i < N; ++i) { + nodeTeleportWeights[i] = u_in(i) / sum_u_in; + } + + std::function t_out_withoutSelfLinks = [lambda, u_t, u_out, u_in, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * (sum_u_in - u_in(i)); }; + std::function t_out_withSelfLinks = [lambda, u_t, u_out, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * sum_u_in; }; + auto t_out = config.noSelfLinks ? t_out_withoutSelfLinks : t_out_withSelfLinks; + + std::vector alpha(N, 0); + for (unsigned int i = 0; i < N; ++i) { + auto t_i = t_out(i); + alpha[i] = t_i / (s_out[i] + t_i); // = 1 for dangling nodes + if (config.noSelfLinks) { + // Inflate to adjust for no self-teleportation + // TODO: Check possible side-effects + alpha[i] /= 1 - nodeTeleportWeights[i]; + } + } + + // Normalize link weights with respect to its source nodes total out-link weight; + for (auto& link : flowLinks) { + if (sumLinkOutWeight[link.source] > 0) { + link.flow /= sumLinkOutWeight[link.source]; + } + } + + std::vector nodeFlowTmp(numNodes, 0.0); + + // Calculate PageRank + const auto iteration = [&](const auto iter) { + double teleTmp = 0.0; + for (unsigned int i = 0; i < N; ++i) { + teleTmp += alpha[i] * nodeFlow[i]; + } + + for (unsigned int i = 0; i < N; ++i) { + nodeFlowTmp[i] = nodeTeleportWeights[i] * (config.noSelfLinks ? (teleTmp - alpha[i] * nodeFlow[i]) : teleTmp); + } + + // Flow from links + for (const auto& link : flowLinks) { + double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1); + nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source]; + } + + // Update node flow from the power iteration above and check if converged + double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later + double error = 0.0; + for (unsigned int i = 0; i < numNodes; ++i) { + nodeFlowDiff += nodeFlowTmp[i]; + error += std::abs(nodeFlowTmp[i] - nodeFlow[i]); + } + + nodeFlow = nodeFlowTmp; + + // Normalize if needed + if (std::abs(nodeFlowDiff) > 1.0e-10) { + Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") "; + normalize(nodeFlow, nodeFlowDiff + 1.0); + } + + return error; + }; + + unsigned int iterations = 0; + double err = 0.0; + + do { + double oldErr = err; + err = iteration(iterations); + + // Perturb the system if equilibrium + if (std::abs(err - oldErr) < 1e-15) { + } + + ++iterations; + } while (iterations < 200 && (err > 1.0e-15 || iterations < 50)); + + Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n"; + + double sumNodeRank = 1.0; + for (auto& link : flowLinks) { + double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1); + link.flow *= beta * nodeFlow[link.source] / sumNodeRank; + } + + nodeTeleportFlow.assign(numNodes, 0.0); + for (unsigned int i = 0; i < N; ++i) { + nodeTeleportFlow[i] = nodeFlow[i] * alpha[i]; + } +} + +void FlowCalculator::calcUndirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept +{ + Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush; + + // Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree + unsigned int N = network.numNodes(); + std::vector k(N, 0); + std::vector s(N, 0); + double sum_s = sumWeightedDegree; + unsigned int sum_k = network.sumDegree(); + double average_weight = sum_s / sum_k; + + for (auto& link : flowLinks) { + k[link.source] += 1; + s[link.source] += link.flow; + if (link.source != link.target) { + k[link.target] += 1; + s[link.target] += link.flow; + } + } + + double min_u = std::numeric_limits::max(); + for (unsigned int i = 0; i < N; ++i) { + if (k[i] > 0) { + min_u = std::min(min_u, s[i] / k[i]); + } + } + + auto u = [s, k, min_u](auto i) { return k[i] == 0 ? min_u : s[i] / k[i]; }; + + unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N; + double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets; + double u_t = average_weight; + + double sum_u = 0.0; + for (unsigned int i = 0; i < N; ++i) { + sum_u += u(i); + } + + // nodeTeleportWeights is the fraction of teleportation flow landing on each node. This is proportional to u_in + for (unsigned int i = 0; i < N; ++i) { + nodeTeleportWeights[i] = u(i) / sum_u; + } + + std::function t_withoutSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * (sum_u - u(i)); }; + std::function t_withSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * sum_u; }; + auto t = config.noSelfLinks ? t_withoutSelfLinks : t_withSelfLinks; + + std::vector alpha(N, 0); + double sum_t = 0.0; + for (unsigned int i = 0; i < N; ++i) { + auto t_i = t(i); + alpha[i] = t_i / (s[i] + t_i); + if (config.noSelfLinks) { + // Inflate to adjust for no self-teleportation + // TODO: No later side effects of cheating here? Need to normalize targets instead? + alpha[i] /= 1 - nodeTeleportWeights[i]; + } + sum_t += t_i; + } + + for (auto& link : flowLinks) { + if (sumLinkOutWeight[link.source] > 0) { + link.flow /= sumLinkOutWeight[link.source]; + } + } + + for (unsigned int i = 0; i < N; ++i) { + nodeFlow[i] = (s[i] + t(i)) / (sum_s + sum_t); + } + + nodeTeleportFlow.assign(numNodes, 0.0); + for (unsigned int i = 0; i < N; ++i) { + nodeTeleportFlow[i] = nodeFlow[i] * alpha[i]; + } + + for (auto& link : flowLinks) { + // TODO: Side effect from inflating alpha, need real alpha here. + double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1); + link.flow *= beta * nodeFlow[link.source] * 2; + } +} + +void FlowCalculator::calcDirectedBipartiteFlow(const StateNetwork& network, const Config& config) noexcept +{ + Log() << "\n -> Using bipartite " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush; + + const auto bipartiteStartId = network.bipartiteStartId(); + + if (config.teleportToNodes) { + for (const auto& nodeIt : network.nodes()) { + auto& node = nodeIt.second; + if (node.id < bipartiteStartId) { + nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight; + } + } + } else { + // Teleport proportionally to out-degree, or in-degree if recorded teleportation. + // Two-step degree: sum of products between incoming and outgoing links from bipartite nodes + + if (config.recordedTeleportation) { + for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) { + // target is an ordinary node + nodeTeleportWeights[link->target] += link->flow; + } + } else { + // Unrecorded teleportation + + for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) { + // source is an ordinary node + nodeTeleportWeights[link->source] += link->flow; + } + } + } + + normalize(nodeTeleportWeights); + + nodeFlow = nodeTeleportWeights; + + // Normalize link weights with respect to its source nodes total out-link weight; + for (auto& link : flowLinks) { + if (sumLinkOutWeight[link.source] > 0) { + link.flow /= sumLinkOutWeight[link.source]; + } + } + + std::vector danglingIndices; + for (size_t i = 0; i < numNodes; ++i) { + if (nodeOutDegree[i] == 0) { + danglingIndices.push_back(i); + } + } + + std::vector nodeFlowTmp(numNodes, 0.0); + double danglingRank; + + // Calculate two-step PageRank + const auto iteration = [&](const auto iter, const double alpha, const double beta) { + danglingRank = 0.0; + for (const auto& i : danglingIndices) { + danglingRank += nodeFlow[i]; + } + + // Flow from teleportation + const auto teleportationFlow = alpha + beta * danglingRank; + for (unsigned int i = 0; i < bipartiteStartIndex; ++i) { + nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i]; + } + + for (unsigned int i = bipartiteStartIndex; i < numNodes; ++i) { + nodeFlowTmp[i] = 0.0; + } + + // Flow from links + // First step + for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) { + nodeFlow[link->target] += beta * link->flow * nodeFlow[link->source]; + } + + // Second step back to primary nodes + for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) { + nodeFlowTmp[link->target] += link->flow * nodeFlow[link->source]; + } + + // Update node flow from the power iteration above and check if converged + double nodeFlowDiff = -1.0; + double error = 0.0; + for (unsigned int i = 0; i < bipartiteStartIndex; ++i) { + nodeFlowDiff += nodeFlowTmp[i]; + error += std::abs(nodeFlowTmp[i] - nodeFlow[i]); + } + + nodeFlow = nodeFlowTmp; + + // Normalize if needed + if (std::abs(nodeFlowDiff) > 1.0e-10) { + Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") "; + normalize(nodeFlow, nodeFlowDiff + 1.0); + } + + return error; + }; + + const auto result = powerIterate(config.teleportationProbability, iteration); + + double sumNodeRank = 1.0; + double beta = result.beta; + + if (!config.recordedTeleportation) { + // Take one last power iteration excluding the teleportation (and normalize node flow to sum 1.0) + sumNodeRank = 1.0 - danglingRank; + nodeFlow.assign(numNodes, 0.0); + + for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) { + nodeFlowTmp[link->target] += link->flow * nodeFlowTmp[link->source]; + } + // Second step back to primary nodes + for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) { + nodeFlow[link->target] += link->flow * nodeFlowTmp[link->source]; + } + + beta = 1.0; + } + + // Update the links with their global flow from the PageRank values. + // Note: beta is set to 1 if unrecorded teleportation + for (auto& link : flowLinks) { + link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank; + } +} + +void FlowCalculator::finalize(StateNetwork& network, const Config& config, bool normalizeNodeFlow) noexcept +{ + unsigned int N = network.numNodes(); + // TODO: Skip bipartite flow adjustment for directed / rawdir / .. ? + if (network.isBipartite()) { + Log() << "\n -> Using bipartite links."; + + if (!config.skipAdjustBipartiteFlow && !config.bipartiteTeleportation) { + // Only links between ordinary nodes and feature nodes in bipartite network + // Don't code feature nodes -> distribute all flow from those to ordinary nodes + for (auto& link : flowLinks) { + auto sourceIsFeature = link.source >= bipartiteStartIndex; + + if (sourceIsFeature) { + nodeFlow[link.target] += link.flow; + nodeFlow[link.source] = 0.0; // Doesn't matter if done multiple times on each node. + } else { + nodeFlow[link.source] += link.flow; + nodeFlow[link.target] = 0.0; // Doesn't matter if done multiple times on each node. + } + // TODO: Should flow double before moving to nodes, does it cancel out in normalization? + + // Markov time 2 on the full network will correspond to markov time 1 between the real nodes. + link.flow *= 2; + } + // TODO: Should flow double before moving to nodes, does it cancel out in normalization? + + normalizeNodeFlow = true; + + } else if (config.bipartiteTeleportation) { + for (auto& link : flowLinks) { + // Markov time 2 on the full network will correspond to markov time 1 between the real nodes. + link.flow *= 2; + } + } + } + + if (config.useNodeWeightsAsFlow) { + Log() << "\n -> Using node weights as flow."; + + for (auto& nodeIt : network.nodes()) { + auto& node = nodeIt.second; + nodeFlow[nodeIndexMap[node.id]] = node.weight; + } + + normalizeNodeFlow = true; + } + + if (normalizeNodeFlow) { + normalize(nodeFlow); + } + + // Write back flow to network + double sumNodeFlow = 0.0; + double sumLinkFlow = 0.0; + unsigned int linkIndex = 0; + auto featureLinkIndex = bipartiteLinkStartIndex; + + for (auto& node : network.m_nodeLinkMap) { + for (auto& link : node.second) { + auto& linkData = link.second; + if (network.isBipartite() && node.first.id >= network.bipartiteStartId()) { + linkData.flow = flowLinks[featureLinkIndex++].flow; + } else { + linkData.flow = flowLinks[linkIndex++].flow; + } + sumLinkFlow += linkData.flow; + } + } + + for (auto& nodeIt : network.m_nodes) { + auto& node = nodeIt.second; + const auto nodeIndex = nodeIndexMap[node.id]; + node.flow = nodeFlow[nodeIndex]; + node.weight = nodeTeleportWeights[nodeIndex]; + node.teleFlow = !nodeTeleportFlow.empty() ? nodeTeleportFlow[nodeIndex] : nodeFlow[nodeIndex] * (nodeOutDegree[nodeIndex] == 0 ? 1 : config.teleportationProbability); + node.enterFlow = node.flow; + node.exitFlow = node.flow; + + if (!config.noSelfLinks) { + // Remove self-teleportation flow + node.enterFlow -= node.teleFlow * node.weight; + node.exitFlow -= node.teleFlow * node.weight; + + // Remove self-link flow + unsigned int norm = config.isUndirectedFlow() ? 2 : 1; + auto& outLinks = network.m_nodeLinkMap[node.id]; + for (auto& link : outLinks) { + auto& linkData = link.second; + if (node.id == link.first.id) { + node.enterFlow -= linkData.flow / norm; + node.exitFlow -= linkData.flow / norm; + break; + } + } + } + + sumNodeFlow += node.flow; + } + + // Enter/exit flow + if (!config.isUndirectedClustering() && !config.regularized) { + enterFlow.assign(N, 0); + exitFlow.assign(N, 0); + double alpha = config.teleportationProbability; + double sumDanglingFlow = 0.0; + for (unsigned int i = 0; i < N; ++i) { + if (nodeOutDegree[i] == 0) { + sumDanglingFlow += nodeFlow[i]; + } + } + for (auto& nodeIt : network.m_nodes) { + auto& node = nodeIt.second; + const auto sourceIndex = nodeIndexMap[node.id]; + auto& outLinks = network.m_nodeLinkMap[node.id]; + double danglingFlow = outLinks.empty() ? node.flow : 0.0; + if (config.recordedTeleportation) { + // Don't let self-teleportation add to the enter/exit flow (i.e. multiply with (1.0 - node.data.teleportWeight)) + exitFlow[sourceIndex] += alpha * node.flow * (1.0 - node.weight); + enterFlow[sourceIndex] += (alpha * (1.0 - node.flow) + (1 - alpha) * (sumDanglingFlow - danglingFlow)) * node.weight; + } + for (auto& link : outLinks) { + auto& linkData = link.second; + const auto targetIndex = nodeIndexMap[link.first.id]; + exitFlow[sourceIndex] += linkData.flow; + enterFlow[targetIndex] += linkData.flow; + } + } + for (auto& nodeIt : network.m_nodes) { + auto& node = nodeIt.second; + const auto nodeIndex = nodeIndexMap[node.id]; + node.enterFlow = enterFlow[nodeIndex]; + node.exitFlow = exitFlow[nodeIndex]; + } + } + + Log() << "\n => Sum node flow: " << sumNodeFlow << ", sum link flow: " << sumLinkFlow << "\n"; +} + +} // namespace infomap diff --git a/vendor/infomap/src/utils/FlowCalculator.h b/vendor/infomap/src/utils/FlowCalculator.h new file mode 100644 index 0000000..88294b3 --- /dev/null +++ b/vendor/infomap/src/utils/FlowCalculator.h @@ -0,0 +1,75 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef FLOW_CALCULATOR_H_ +#define FLOW_CALCULATOR_H_ + +#include +#include + +namespace infomap { + +struct Config; +class StateNetwork; + +namespace detail { + struct FlowLink { + unsigned int source; + unsigned int target; + double flow; + }; +} // namespace detail + +/** + * Calculate flow on network based on different flow models + */ +class FlowCalculator { +public: + FlowCalculator(StateNetwork&, const Config&); + +private: + void calcUndirectedFlow() noexcept; + void calcDirectedFlow(const StateNetwork&, const Config&) noexcept; + void calcUndirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept; + void calcDirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept; + void calcDirectedBipartiteFlow(const StateNetwork&, const Config&) noexcept; + void calcDirdirFlow(const Config&) noexcept; + void calcRawdirFlow() noexcept; + void usePrecomputedFlow(const StateNetwork&, const Config&); + + void finalize(StateNetwork&, const Config&, bool) noexcept; + + unsigned int numNodes; + unsigned int nonDanglingStartIndex = 0; + unsigned int bipartiteStartIndex = 0; + unsigned int bipartiteLinkStartIndex = 0; + + double sumLinkWeight = 0; + double sumWeightedDegree = 0; + + std::map nodeIndexMap; + std::vector nodeFlow; + std::vector nodeTeleportWeights; + std::vector nodeTeleportFlow; + std::vector enterFlow; + std::vector exitFlow; + std::vector sumLinkOutWeight; + std::vector nodeOutDegree; + using FlowLink = detail::FlowLink; + std::vector flowLinks; +}; + +inline void calculateFlow(StateNetwork& network, const Config& config) +{ + FlowCalculator(network, config); +} + +} // namespace infomap + +#endif // FLOW_CALCULATOR_H_ diff --git a/vendor/infomap/src/utils/Log.cpp b/vendor/infomap/src/utils/Log.cpp new file mode 100644 index 0000000..f16f2b0 --- /dev/null +++ b/vendor/infomap/src/utils/Log.cpp @@ -0,0 +1,17 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#include "Log.h" + +namespace infomap { + +unsigned int Log::s_verboseLevel = 0; +bool Log::s_silent = false; + +} // namespace infomap diff --git a/vendor/infomap/src/utils/Log.h b/vendor/infomap/src/utils/Log.h new file mode 100644 index 0000000..412dcd5 --- /dev/null +++ b/vendor/infomap/src/utils/Log.h @@ -0,0 +1,110 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef LOG_H_ +#define LOG_H_ + +#include +#include +#include +#include + +namespace infomap { + +struct hideIf; + +class Log { + using ostreamFuncPtr = std::add_pointer_t; + +public: + /** + * Log when level is below or equal Log::verboseLevel() + * and maxLevel is above or equal Log::verboseLevel() + */ + explicit Log(unsigned int level = 0, unsigned int maxLevel = std::numeric_limits::max()) + : m_level(level), m_maxLevel(maxLevel), m_visible(isVisible(m_level, m_maxLevel)) { } + + bool isVisible() const { return isVisible(m_level, m_maxLevel); } + + void hide(bool value) { m_visible = !value && isVisible(); } + + Log& operator<<(const hideIf&) { return *this; } + + template + Log& operator<<(const T& data) + { +#if 0 + if (m_visible) + m_ostream << data; +#endif + return *this; + } + + Log& operator<<(ostreamFuncPtr f) + { +#if 0 + if (m_visible) + m_ostream << f; +#endif + return *this; + } + + static void init(unsigned int verboseLevel, bool silent, unsigned int numberPrecision) + { + setVerboseLevel(verboseLevel); + setSilent(silent); + Log() << std::setprecision(static_cast(numberPrecision)); + } + + static bool isVisible(unsigned int level, unsigned int maxLevel) + { + return !s_silent && s_verboseLevel >= level && s_verboseLevel <= maxLevel; + } + + static void setVerboseLevel(unsigned int level) { s_verboseLevel = level; } + + static void setSilent(bool silent) { s_silent = silent; } + + static bool isSilent() { return s_silent; } + + /* precision() is patched in igraph to avoid references to std::cout */ + static std::streamsize precision() { return 6; } + + static std::streamsize precision(std::streamsize) + { + return precision(); + } + +private: + unsigned int m_level; + unsigned int m_maxLevel; + bool m_visible; +#if 0 + std::ostream& m_ostream = std::cout; +#endif + + static unsigned int s_verboseLevel; + static bool s_silent; +}; + +struct hideIf { + explicit hideIf(bool value) : hide(value) { } + + friend Log& operator<<(Log& out, const hideIf& manip) + { + out.hide(manip.hide); + return out; + } + + bool hide; +}; + +} // namespace infomap + +#endif // LOG_H_ diff --git a/vendor/infomap/src/utils/MetaCollection.h b/vendor/infomap/src/utils/MetaCollection.h new file mode 100644 index 0000000..67a60d3 --- /dev/null +++ b/vendor/infomap/src/utils/MetaCollection.h @@ -0,0 +1,153 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef META_COLLECTION_H_ +#define META_COLLECTION_H_ + +#include "infomath.h" +#include +#include + +namespace infomap { + +struct FlowCount { + FlowCount() = default; + + explicit FlowCount(double flow) + : flow(flow), count(1) { } + + FlowCount& operator+=(const FlowCount& o) + { + flow += o.flow; + count += o.count; + return *this; + } + + FlowCount& operator+=(double f) + { + flow += f; + ++count; + return *this; + } + + FlowCount& operator-=(const FlowCount& o) + { + flow -= o.flow; + count -= o.count; + return *this; + } + + FlowCount& operator-=(double f) + { + flow -= f; + --count; + return *this; + } + + friend std::ostream& operator<<(std::ostream& out, const FlowCount& o) + { + return out << o.flow << "/" << o.count; + } + + void reset() + { + flow = 0.0; + count = 0; + } + + bool empty() const { return count == 0; } + double flow = 0.0; + unsigned int count = 0; +}; + +using MetaToFlowCount = std::map; // metaId -> (flow,count) + +class MetaCollection { +protected: + FlowCount m_total; + MetaToFlowCount m_metaToFlowCount; + +public: + unsigned int size() const { return m_metaToFlowCount.size(); } + + bool empty() const { return m_metaToFlowCount.empty(); } + + MetaToFlowCount::iterator begin() { return m_metaToFlowCount.begin(); } + MetaToFlowCount::iterator end() { return m_metaToFlowCount.end(); } + MetaToFlowCount::const_iterator begin() const { return m_metaToFlowCount.begin(); } + MetaToFlowCount::const_iterator end() const { return m_metaToFlowCount.end(); } + + void add(unsigned int meta, double flow = 1.0) + { + m_total += flow; + m_metaToFlowCount[meta] += flow; + } + + void add(unsigned int meta, const FlowCount& flow) + { + m_total += flow; + m_metaToFlowCount[meta] += flow; + } + + void add(const MetaCollection& other) + { + for (auto& it : other) { + auto metaId = it.first; + auto& flowCount = it.second; + add(metaId, flowCount); + } + } + + void remove(unsigned int meta, const FlowCount& flow) + { + m_total -= flow; + auto& metaFlowCount = m_metaToFlowCount[meta]; + metaFlowCount -= flow; + if (metaFlowCount.empty()) + m_metaToFlowCount.erase(meta); + } + + void remove(const MetaCollection& other) + { + for (auto& it : other) { + auto metaId = it.first; + auto& flowCount = it.second; + remove(metaId, flowCount); + } + } + + double calculateEntropy() + { + double metaCodelength = 0.0; + for (auto& it : m_metaToFlowCount) { + metaCodelength -= infomath::plogp(it.second.flow / m_total.flow); + } + return m_total.flow * metaCodelength; + } + + void clear() + { + m_total.reset(); + m_metaToFlowCount.clear(); + } + + friend std::ostream& operator<<(std::ostream& out, const MetaCollection& m) + { + out << "<( " << m.m_total << ": "; + for (auto& it : m.m_metaToFlowCount) { + out << "(" << it.first << "," << it.second << ") "; + } + out << ")>"; + return out; + } +}; + +} // namespace infomap + +#endif // META_COLLECTION_H_ diff --git a/vendor/infomap/src/utils/Random.h b/vendor/infomap/src/utils/Random.h new file mode 100644 index 0000000..6a50069 --- /dev/null +++ b/vendor/infomap/src/utils/Random.h @@ -0,0 +1,45 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef RANDOM_H_ +#define RANDOM_H_ + +#include + +#include +#include + +namespace infomap { + +class Random { + igraph_rng_t* m_randGen; + +public: + Random() : m_randGen(igraph_rng_default()) { } + + unsigned int randInt(unsigned int min, unsigned int max) + { + return igraph_rng_get_integer(m_randGen, min, max); + } + + /** + * Get a random permutation of indices of the size of the input vector + */ + void getRandomizedIndexVector(std::vector& randomOrder) + { + unsigned int size = randomOrder.size(); + for (unsigned int i = 0; i < size; ++i) + randomOrder[i] = i; + for (unsigned int i = 0; i < size; ++i) + std::swap(randomOrder[i], randomOrder[i + randInt(0, size - i - 1)]); + } +}; +} // namespace infomap + +#endif // RANDOM_H_ diff --git a/vendor/infomap/src/utils/Stopwatch.h b/vendor/infomap/src/utils/Stopwatch.h new file mode 100644 index 0000000..9ee4210 --- /dev/null +++ b/vendor/infomap/src/utils/Stopwatch.h @@ -0,0 +1,103 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef STOPWATCH_H_ +#define STOPWATCH_H_ + +#include +#include +#include +#include + +namespace infomap { + +class Stopwatch { +public: + using Clock = std::chrono::high_resolution_clock; + using TimeType = std::chrono::time_point; + + explicit Stopwatch(bool startImmediately) + : m_start(now()), m_stop(now()), m_running(false) + { + if (startImmediately) { + start(); + } + } + + void start() + { + m_start = Clock::now(); + m_running = true; + } + + void reset() + { + if (m_running) + m_start = Clock::now(); + } + + void stop() + { + if (m_running) { + m_stop = Clock::now(); + m_running = false; + } + } + + TimeType getCurrentTimePoint() const + { + return m_running ? Clock::now() : m_stop; + } + + double getElapsedTimeInSec() const + { + std::chrono::duration diff = getCurrentTimePoint() - m_start; + return diff.count(); + } + + double getElapsedTimeInMilliSec() const + { + std::chrono::duration diff = getCurrentTimePoint() - m_start; + return diff.count(); + } + + static TimeType now() + { + return Clock::now(); + } + + friend std::ostream& operator<<(std::ostream& out, const Stopwatch& stopwatch) + { + auto temp = static_cast(std::floor(stopwatch.getElapsedTimeInMilliSec())); + if (temp > 60'000) { + if (temp > 3600'000) { + if (temp > 86'400'000) { + out << temp / 86'400'000 << "d "; + temp %= 86'400'000; + } + out << temp / 3600'000 << "h "; + temp %= 3600'000; + } + out << temp / 60'000 << "m "; + temp %= 60'000; + out << temp * 1.0 / 1000 << "s"; + } else { + out << stopwatch.getElapsedTimeInSec() << "s"; + } + return out; + } + +private: + TimeType m_start, m_stop; + bool m_running; +}; + +} // namespace infomap + +#endif // STOPWATCH_H_ diff --git a/vendor/infomap/src/utils/VectorMap.h b/vendor/infomap/src/utils/VectorMap.h new file mode 100644 index 0000000..63994bc --- /dev/null +++ b/vendor/infomap/src/utils/VectorMap.h @@ -0,0 +1,82 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef VECTOR_MAP_H_ +#define VECTOR_MAP_H_ + +#include +#include +#include + +namespace infomap { + +template +class VectorMap { +public: + VectorMap(unsigned int capacity = 0) + : m_capacity(capacity), + m_values(capacity), + m_redirect(capacity, 0), + m_maxOffset(std::numeric_limits::max() - 1 - capacity) { } + + void startRound() + { + if (m_size > 0) { + m_offset += m_capacity; + m_size = 0; + } + if (m_offset > m_maxOffset) { + m_redirect.assign(m_capacity, 0); + m_offset = 1; + } + } + + void add(unsigned int index, T value) + { + if (isSet(index)) { + m_values[m_redirect[index] - m_offset] += value; + } else { + m_redirect[index] = m_offset + m_size; + m_values[m_size] = value; + ++m_size; + } + } + + bool isSet(unsigned int index) + { + return m_redirect[index] >= m_offset; + } + + unsigned int size() + { + return m_size; + } + + T& operator[](unsigned int index) + { + return m_values[m_redirect[index] - m_offset]; + } + + std::vector& values() + { + return m_values; + } + +private: + unsigned int m_capacity = 0; + std::vector m_values; + std::vector m_redirect; + unsigned int m_maxOffset = std::numeric_limits::max() - 1; + unsigned int m_offset = 1; + unsigned int m_size = 0; +}; + +} // namespace infomap + +#endif // VECTOR_MAP_H_ diff --git a/vendor/infomap/src/utils/convert.h b/vendor/infomap/src/utils/convert.h new file mode 100644 index 0000000..f8e5077 --- /dev/null +++ b/vendor/infomap/src/utils/convert.h @@ -0,0 +1,216 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef CONVERT_H_ +#define CONVERT_H_ + +#include +#include +#include +#include +#include // std::locale, std::tolower +#include +#include + +namespace infomap { + +template +struct TypeInfo { + static bool isNumeric() { return false; } +}; +template <> +struct TypeInfo { + static bool isNumeric() { return false; } +}; +template <> +struct TypeInfo { + static bool isNumeric() { return true; } +}; +template <> +struct TypeInfo { + static bool isNumeric() { return true; } +}; +template <> +struct TypeInfo { + static bool isNumeric() { return true; } +}; + +namespace io { + + inline std::string tolower(std::string str) + { + std::locale loc; + for (char& c : str) + c = std::tolower(c, loc); + return str; + } + + template + inline std::string stringify(T& x) + { + std::ostringstream o; + if (!(o << x)) + throw std::runtime_error((o << "stringify(" << x << ")", o.str())); + return o.str(); + } + + template <> + inline std::string stringify(bool& x) + { + return x ? "true" : "false"; + } + + template + inline std::string stringify(const Container& cont, const std::string& delimiter) + { + std::ostringstream o; + if (cont.empty()) + return ""; + unsigned int maxIndex = cont.size() - 1; + for (unsigned int i = 0; i < maxIndex; ++i) { + if (!(o << cont[i])) + throw std::runtime_error((o << "stringify(container[" << i << "])", o.str())); + o << delimiter; + } + if (!(o << cont[maxIndex])) + throw std::runtime_error((o << "stringify(container[" << maxIndex << "])", o.str())); + return o.str(); + } + + struct InsensitiveCompare { + bool operator()(const std::string& a, const std::string& b) const + { + auto lhs = a.begin(); + auto rhs = b.begin(); + + std::locale loc; + for (; lhs != a.end() && rhs != b.end(); ++lhs, ++rhs) { + auto lhs_val = std::tolower(*lhs, loc); + auto rhs_val = std::tolower(*rhs, loc); + + if (lhs_val != rhs_val) + return lhs_val < rhs_val; + } + + return (rhs != b.end()); + } + }; + + inline std::vector& split(const std::string& s, char delim, std::vector& items) + { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + if (item.length() > 0) { + items.push_back(item); + } + } + return items; + } + + inline std::vector split(const std::string& s, char delim) + { + std::vector items; + split(s, delim, items); + return items; + } + + class Str { + public: + Str() = default; + template + Str& operator<<(const T& t) + { + m_oss << stringify(t); + return *this; + } + Str& operator<<(std::ostream& (*f)(std::ostream&)) + { + m_oss << f; + return *this; + } + operator std::string() const + { + return m_oss.str(); + } + + private: + std::ostringstream m_oss; + }; + + template + inline bool stringToValue(std::string const& str, T& value) + { + std::istringstream istream(str); + return !!(istream >> value); + } + + template <> + inline bool stringToValue(std::string const& str, unsigned int& value) + { + std::istringstream istream(str); + int target = 0; + istream >> target; + if (target < 0) return false; + value = target; + return true; + } + + template <> + inline bool stringToValue(std::string const& str, unsigned long& value) + { + std::istringstream istream(str); + int target = 0; + istream >> target; + if (target < 0) return false; + value = target; + return true; + } + + inline std::string firstWord(const std::string& line) + { + std::istringstream ss; + std::string buf; + ss.str(line); + ss >> buf; + return buf; + } + + template + inline std::string padValue(T value, const std::string::size_type size, bool rightAligned = true, const char paddingChar = ' ') + { + std::string valStr = stringify(value); + if (size == valStr.size()) + return valStr; + if (size < valStr.size()) + return valStr.substr(0, size); + + if (!rightAligned) + return valStr.append(size - valStr.size(), paddingChar); + + return std::string(size - valStr.size(), paddingChar).append(valStr); + } + + inline std::string toPrecision(double value, unsigned int precision = 10, bool fixed = false) + { + std::ostringstream o; + if (fixed) + o << std::fixed << std::setprecision(static_cast(precision)); + else + o << std::setprecision(static_cast(precision)); + if (!(o << value)) + throw std::runtime_error((o << "stringify(" << value << ")", o.str())); + return o.str(); + } + +} // namespace io + +} // namespace infomap + +#endif // CONVERT_H_ diff --git a/vendor/infomap/src/utils/infomath.h b/vendor/infomap/src/utils/infomath.h new file mode 100644 index 0000000..7c09add --- /dev/null +++ b/vendor/infomap/src/utils/infomath.h @@ -0,0 +1,57 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef INFOMATH_H_ +#define INFOMATH_H_ + +#include +#include + +namespace infomap { +namespace infomath { + + using std::log2; + + inline double plogp(double p) + { + return p > 0.0 ? p * log2(p) : 0.0; + } + + inline double isEqual(double a, double b, double tol = 1e-8) + { + return std::abs(a - b) <= tol; + } + + /** + * Tsallis entropy S_q of a uniform probability distribution of length n + */ + inline double tsallisEntropyUniform(double n, double q = 1) + { + if (isEqual(q, 1)) { + return std::log2(n); + } + return 1 / (q - 1) * (1 - pow(n, (1 - q))) / std::log(2); + } + + /** + * Interpolate from linear (q = 0) to log (q = 1) + * linlog(k, 0) = k + * linlog(k, 1) = log2(k) + */ + inline double linlog(double k, double q = 1) + { + double baseCorrection = q <= 1 ? (1 - q) * std::log(2) + q : 1; + double offsetCorrection = q <= 1 ? 1 - q : 0; + return tsallisEntropyUniform(k, q) * baseCorrection + offsetCorrection; + } + +} // namespace infomath +} // namespace infomap + +#endif // INFOMATH_H_ diff --git a/vendor/infomap/src/version.h b/vendor/infomap/src/version.h new file mode 100644 index 0000000..0671d20 --- /dev/null +++ b/vendor/infomap/src/version.h @@ -0,0 +1,19 @@ +/******************************************************************************* + Infomap software package for multi-level network clustering + Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall + + This file is part of the Infomap software package. + See file LICENSE_GPLv3.txt for full license details. + For more information, see + ******************************************************************************/ + +#ifndef VERSION_H_ +#define VERSION_H_ + +namespace infomap { + +const char* const INFOMAP_VERSION = "2.8.0"; + +} + +#endif // VERSION_H_ diff --git a/vendor/nanoflann/nanoflann.hpp b/vendor/nanoflann/nanoflann.hpp new file mode 100644 index 0000000..dc297bc --- /dev/null +++ b/vendor/nanoflann/nanoflann.hpp @@ -0,0 +1,2813 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved. + * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved. + * Copyright 2011-2025 Jose Luis Blanco (joseluisblancoc@gmail.com). + * All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +/** \mainpage nanoflann C++ API documentation + * nanoflann is a C++ header-only library for building KD-Trees, mostly + * optimized for 2D or 3D point clouds. + * + * nanoflann does not require compiling or installing, just an + * #include in your code. + * + * Macros that are observed in this file: + * - NANOFLANN_NO_THREADS: If defined, single thread will be enforced. + * - NANOFLANN_FIRST_MATCH: If defined, in case of a tie in distances the item with the smallest + * index will be returned. + * - NANOFLANN_NODE_ALIGNMENT: The memory alignment, in bytes, for kd-tree nodes. Default: 16 + * + * See: + * - [Online README](https://github.com/jlblancoc/nanoflann) + * - [C++ API documentation](https://jlblancoc.github.io/nanoflann/) + */ + +#pragma once + +#include +#include +#include +#include +#include // for abs() +#include +#include // for abs() +#include // std::reference_wrapper +#include +#include +#include // std::numeric_limits +#include +#include +#include +#include +#include + +/** Library version: 0xMmP (M=Major,m=minor,P=patch) */ +#define NANOFLANN_VERSION 0x190 + +// Avoid conflicting declaration of min/max macros in Windows headers +#if !defined(NOMINMAX) && (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64)) +#define NOMINMAX +#ifdef max +#undef max +#undef min +#endif +#endif +// Avoid conflicts with X11 headers +#ifdef None +#undef None +#endif + +// Handle restricted pointers +#if defined(__GNUC__) || defined(__clang__) +#define NANOFLANN_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define NANOFLANN_RESTRICT __restrict +#else +#define NANOFLANN_RESTRICT +#endif + +// Memory alignment of KD-tree nodes: +#ifndef NANOFLANN_NODE_ALIGNMENT +#define NANOFLANN_NODE_ALIGNMENT 16 +#endif + +namespace nanoflann +{ +/** @addtogroup nanoflann_grp nanoflann C++ library for KD-trees + * @{ */ + +/** the PI constant (required to avoid MSVC missing symbols) */ +template +constexpr T pi_const() +{ + return static_cast(3.14159265358979323846); +} + +/** + * Traits if object is resizable and assignable (typically has a resize | assign + * method) + */ +template +struct has_resize : std::false_type +{ +}; + +template +struct has_resize().resize(1), 0)> : std::true_type +{ +}; + +template +struct has_assign : std::false_type +{ +}; + +template +struct has_assign().assign(1, 0), 0)> : std::true_type +{ +}; + +/** + * Free function to resize a resizable object + */ +template +inline typename std::enable_if::value, void>::type resize( + Container& c, const size_t nElements) +{ + c.resize(nElements); +} + +/** + * Free function that has no effects on non resizable containers (e.g. + * std::array) It raises an exception if the expected size does not match + */ +template +inline typename std::enable_if::value, void>::type resize( + Container& c, const size_t nElements) +{ + if (nElements != c.size()) throw std::logic_error("Attempt to resize a fixed size container."); +} + +/** + * Free function to assign to a container + */ +template +inline typename std::enable_if::value, void>::type assign( + Container& c, const size_t nElements, const T& value) +{ + c.assign(nElements, value); +} + +/** + * Free function to assign to a std::array + */ +template +inline typename std::enable_if::value, void>::type assign( + Container& c, const size_t nElements, const T& value) +{ + for (size_t i = 0; i < nElements; i++) c[i] = value; +} + +/** operator "<" for std::sort() */ +struct IndexDist_Sorter +{ + /** PairType will be typically: ResultItem */ + template + bool operator()(const PairType& p1, const PairType& p2) const + { + return p1.second < p2.second; + } +}; + +/** + * Each result element in RadiusResultSet. Note that distances and indices + * are named `first` and `second` to keep backward-compatibility with the + * `std::pair<>` type used in the past. In contrast, this structure is ensured + * to be `std::is_standard_layout` so it can be used in wrappers to other + * languages. + * See: https://github.com/jlblancoc/nanoflann/issues/166 + */ +template +struct ResultItem +{ + ResultItem() = default; + ResultItem(const IndexType index, const DistanceType distance) : first(index), second(distance) + { + } + + IndexType first; //!< Index of the sample in the dataset + DistanceType second; //!< Distance from sample to query point +}; + +/** @addtogroup result_sets_grp Result set classes + * @{ */ + +/** Result set for KNN searches (N-closest neighbors) */ +template +class KNNResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + using CountType = _CountType; + + private: + IndexType* indices; + DistanceType* dists; + CountType capacity; + CountType count; + + public: + explicit KNNResultSet(CountType capacity_) + : indices(nullptr), dists(nullptr), capacity(capacity_), count(0) + { + } + + void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; + } + + CountType size() const noexcept { return count; } + bool empty() const noexcept { return count == 0; } + bool full() const noexcept { return count == capacity; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + CountType i; + for (i = count; i > 0; --i) + { + /** If defined and two points have the same distance, the one with + * the lowest-index will be returned first. */ +#ifdef NANOFLANN_FIRST_MATCH + if ((dists[i - 1] > dist) || ((dist == dists[i - 1]) && (indices[i - 1] > index))) + { +#else + if (dists[i - 1] > dist) + { +#endif + if (i < capacity) + { + dists[i] = dists[i - 1]; + indices[i] = indices[i - 1]; + } + } + else + break; + } + if (i < capacity) + { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; + + // tell caller that the search shall continue + return true; + } + + //! Returns the worst distance among found solutions if the search result is + //! full, or the maximum possible distance, if not full yet. + DistanceType worstDist() const noexcept + { + return (count < capacity || !count) ? std::numeric_limits::max() + : dists[count - 1]; + } + + void sort() + { + // already sorted + } +}; + +/** Result set for RKNN searches (N-closest neighbors with a maximum radius) */ +template +class RKNNResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + using CountType = _CountType; + + private: + IndexType* indices; + DistanceType* dists; + CountType capacity; + CountType count; + DistanceType maximumSearchDistanceSquared; + + public: + explicit RKNNResultSet(CountType capacity_, DistanceType maximumSearchDistanceSquared_) + : indices(nullptr), + dists(nullptr), + capacity(capacity_), + count(0), + maximumSearchDistanceSquared(maximumSearchDistanceSquared_) + { + } + + void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; + if (capacity) dists[capacity - 1] = maximumSearchDistanceSquared; + } + + CountType size() const noexcept { return count; } + bool empty() const noexcept { return count == 0; } + bool full() const noexcept { return count == capacity; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + CountType i; + for (i = count; i > 0; --i) + { + /** If defined and two points have the same distance, the one with + * the lowest-index will be returned first. */ +#ifdef NANOFLANN_FIRST_MATCH + if ((dists[i - 1] > dist) || ((dist == dists[i - 1]) && (indices[i - 1] > index))) + { +#else + if (dists[i - 1] > dist) + { +#endif + if (i < capacity) + { + dists[i] = dists[i - 1]; + indices[i] = indices[i - 1]; + } + } + else + break; + } + if (i < capacity) + { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; + + // tell caller that the search shall continue + return true; + } + + //! Returns the worst distance among found solutions if the search result is + //! full, or the maximum possible distance, if not full yet. + DistanceType worstDist() const noexcept + { + return (count < capacity || !count) ? maximumSearchDistanceSquared : dists[count - 1]; + } + + void sort() + { + // already sorted + } +}; + +/** + * A result-set class used when performing a radius based search. + */ +template +class RadiusResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + + public: + const DistanceType radius; + + std::vector>& m_indices_dists; + + explicit RadiusResultSet( + DistanceType radius_, std::vector>& indices_dists) + : radius(radius_), m_indices_dists(indices_dists) + { + init(); + } + + void init() { clear(); } + void clear() { m_indices_dists.clear(); } + + size_t size() const noexcept { return m_indices_dists.size(); } + bool empty() const noexcept { return m_indices_dists.empty(); } + bool full() const noexcept { return true; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + if (dist < radius) m_indices_dists.emplace_back(index, dist); + return true; + } + + DistanceType worstDist() const noexcept { return radius; } + + /** + * Find the worst result (farthest neighbor) without copying or sorting + * Pre-conditions: size() > 0 + */ + ResultItem worst_item() const + { + if (m_indices_dists.empty()) + throw std::runtime_error( + "Cannot invoke RadiusResultSet::worst_item() on " + "an empty list of results."); + auto it = + std::max_element(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); + return *it; + } + + void sort() { std::sort(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); } +}; + +/** @} */ + +/** @addtogroup loadsave_grp Load/save auxiliary functions + * @{ */ +template +void save_value(std::ostream& stream, const T& value) +{ + stream.write(reinterpret_cast(&value), sizeof(T)); +} + +template +void save_value(std::ostream& stream, const std::vector& value) +{ + size_t size = value.size(); + stream.write(reinterpret_cast(&size), sizeof(size_t)); + stream.write(reinterpret_cast(value.data()), sizeof(T) * size); +} + +template +void load_value(std::istream& stream, T& value) +{ + stream.read(reinterpret_cast(&value), sizeof(T)); +} + +template +void load_value(std::istream& stream, std::vector& value) +{ + size_t size; + stream.read(reinterpret_cast(&size), sizeof(size_t)); + value.resize(size); + stream.read(reinterpret_cast(value.data()), sizeof(T) * size); +} +/** @} */ + +/** @addtogroup metric_grp Metric (distance) classes + * @{ */ + +struct Metric +{ +}; + +/** Manhattan distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L1 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +struct L1_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L1_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric( + const T* NANOFLANN_RESTRICT a, const IndexType b_idx, size_t size, + DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const size_t multof4 = (size >> 2) << 2; // largest multiple of 4, i.e. 1 << 2 + size_t d; + + /* Process 4 items with each loop for efficiency. */ + if (worst_dist <= 0) + { + /* No checks with worst_dist. */ + for (d = 0; d < multof4; d += 4) + { + const DistanceType diff0 = + std::abs(a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0)); + const DistanceType diff1 = + std::abs(a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1)); + const DistanceType diff2 = + std::abs(a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2)); + const DistanceType diff3 = + std::abs(a[d + 3] - data_source.kdtree_get_pt(b_idx, d + 3)); + /* Parentheses break dependency chain: */ + result += (diff0 + diff1) + (diff2 + diff3); + } + } + else + { + /* Check with worst_dist. */ + for (d = 0; d < multof4; d += 4) + { + const DistanceType diff0 = + std::abs(a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0)); + const DistanceType diff1 = + std::abs(a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1)); + const DistanceType diff2 = + std::abs(a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2)); + const DistanceType diff3 = + std::abs(a[d + 3] - data_source.kdtree_get_pt(b_idx, d + 3)); + /* Parentheses break dependency chain: */ + result += (diff0 + diff1) + (diff2 + diff3); + if (result > worst_dist) + { + return result; + } + } + } + /* Process last 0-3 components. Unrolled loop with fall-through switch. + */ + switch (size - multof4) + { + case 3: + result += std::abs(a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2)); + case 2: + result += std::abs(a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1)); + case 1: + result += std::abs(a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0)); + case 0: + break; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const + { + return std::abs(a - b); + } +}; + +/** **Squared** Euclidean distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L2 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +struct L2_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric( + const T* NANOFLANN_RESTRICT a, const IndexType b_idx, size_t size, + DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const size_t multof4 = (size >> 2) << 2; // largest multiple of 4, i.e. 1 << 2 + size_t d; + + /* Process 4 items with each loop for efficiency. */ + if (worst_dist <= 0) + { + /* No checks with worst_dist. */ + for (d = 0; d < multof4; d += 4) + { + const DistanceType diff0 = a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0); + const DistanceType diff1 = a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1); + const DistanceType diff2 = a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2); + const DistanceType diff3 = a[d + 3] - data_source.kdtree_get_pt(b_idx, d + 3); + /* Parentheses break dependency chain: */ + result += (diff0 * diff0 + diff1 * diff1) + (diff2 * diff2 + diff3 * diff3); + } + } + else + { + /* Check with worst_dist. */ + for (d = 0; d < multof4; d += 4) + { + const DistanceType diff0 = a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0); + const DistanceType diff1 = a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1); + const DistanceType diff2 = a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2); + const DistanceType diff3 = a[d + 3] - data_source.kdtree_get_pt(b_idx, d + 3); + /* Parentheses break dependency chain: */ + result += (diff0 * diff0 + diff1 * diff1) + (diff2 * diff2 + diff3 * diff3); + if (result > worst_dist) + { + return result; + } + } + } + /* Process last 0-3 components. Unrolled loop with fall-through switch. + */ + DistanceType diff; + switch (size - multof4) + { + case 3: + diff = a[d + 2] - data_source.kdtree_get_pt(b_idx, d + 2); + result += diff * diff; + case 2: + diff = a[d + 1] - data_source.kdtree_get_pt(b_idx, d + 1); + result += diff * diff; + case 1: + diff = a[d + 0] - data_source.kdtree_get_pt(b_idx, d + 0); + result += diff * diff; + case 0: + break; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const + { + auto diff = a - b; + return diff * diff; + } +}; + +/** **Squared** Euclidean (L2) distance functor (suitable for low-dimensionality + * datasets, like 2D or 3D point clouds) Corresponding distance traits: + * nanoflann::metric_L2_Simple + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +struct L2_Simple_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L2_Simple_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric(const T* a, const IndexType b_idx, size_t size) const + { + DistanceType result = DistanceType(); + for (size_t i = 0; i < size; ++i) + { + const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); + result += diff * diff; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const + { + auto diff = a - b; + return diff * diff; + } +}; + +/** SO2 distance functor + * Corresponding distance traits: nanoflann::metric_SO2 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) orientation is constrained to be in [-pi, pi] + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +struct SO2_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + SO2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric(const T* a, const IndexType b_idx, size_t size) const + { + return accum_dist(a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1), size - 1); + } + + /** Note: this assumes that input angles are already in the range [-pi,pi] + */ + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const + { + DistanceType result = DistanceType(); + DistanceType PI = pi_const(); + result = b - a; + if (result > PI) + result -= 2 * PI; + else if (result < -PI) + result += 2 * PI; + return result; + } +}; + +/** SO3 distance functor (Uses L2_Simple) + * Corresponding distance traits: nanoflann::metric_SO3 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +struct SO3_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + L2_Simple_Adaptor distance_L2_Simple; + + SO3_Adaptor(const DataSource& _data_source) : distance_L2_Simple(_data_source) {} + + inline DistanceType evalMetric(const T* a, const IndexType b_idx, size_t size) const + { + return distance_L2_Simple.evalMetric(a, b_idx, size); + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t idx) const + { + return distance_L2_Simple.accum_dist(a, b, idx); + } +}; + +/** Metaprogramming helper traits class for the L1 (Manhattan) metric */ +struct metric_L1 : public Metric +{ + template + struct traits + { + using distance_t = L1_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the L2 (Euclidean) **squared** + * distance metric */ +struct metric_L2 : public Metric +{ + template + struct traits + { + using distance_t = L2_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the L2_simple (Euclidean) + * **squared** distance metric */ +struct metric_L2_Simple : public Metric +{ + template + struct traits + { + using distance_t = L2_Simple_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO2 : public Metric +{ + template + struct traits + { + using distance_t = SO2_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO3 : public Metric +{ + template + struct traits + { + using distance_t = SO3_Adaptor; + }; +}; + +/** @} */ + +/** @addtogroup param_grp Parameter structs + * @{ */ + +enum class KDTreeSingleIndexAdaptorFlags +{ + None = 0, + SkipInitialBuildIndex = 1 +}; + +inline std::underlying_type::type operator&( + KDTreeSingleIndexAdaptorFlags lhs, KDTreeSingleIndexAdaptorFlags rhs) +{ + using underlying = typename std::underlying_type::type; + return static_cast(lhs) & static_cast(rhs); +} + +/** Parameters (see README.md) */ +struct KDTreeSingleIndexAdaptorParams +{ + KDTreeSingleIndexAdaptorParams( + size_t _leaf_max_size = 10, + KDTreeSingleIndexAdaptorFlags _flags = KDTreeSingleIndexAdaptorFlags::None, + unsigned int _n_thread_build = 1) + : leaf_max_size(_leaf_max_size), flags(_flags), n_thread_build(_n_thread_build) + { + } + + size_t leaf_max_size; + KDTreeSingleIndexAdaptorFlags flags; + unsigned int n_thread_build; +}; + +/** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ +struct SearchParameters +{ + SearchParameters(float eps_ = 0, bool sorted_ = true) : eps(eps_), sorted(sorted_) {} + + float eps; //!< search for eps-approximate neighbours (default: 0) + bool sorted; //!< only for radius search, require neighbours sorted by + //!< distance (default: true) +}; +/** @} */ + +/** @addtogroup memalloc_grp Memory allocation + * @{ */ + +/** + * Pooled storage allocator + * + * The following routines allow for the efficient allocation of storage in + * small chunks from a specified pool. Rather than allowing each structure + * to be freed individually, an entire pool of storage is freed at once. + * This method has two advantages over just using malloc() and free(). First, + * it is far more efficient for allocating small objects, as there is + * no overhead for remembering all the information needed to free each + * object or consolidating fragmented memory. Second, the decision about + * how long to keep an object is made at the time of allocation, and there + * is no need to track down all the objects to free them. + * + */ +class PooledAllocator +{ + static constexpr size_t WORDSIZE = 16; // WORDSIZE must >= 8 + static constexpr size_t BLOCKSIZE = 8192; + + /* We maintain memory alignment to word boundaries by requiring that all + allocations be in multiples of the machine wordsize. */ + /* Size of machine word in bytes. Must be power of 2. */ + /* Minimum number of bytes requested at a time from the system. Must be + * multiple of WORDSIZE. */ + + using Size = size_t; + + Size remaining_ = 0; //!< Number of bytes left in current block of storage + void* base_ = nullptr; //!< Pointer to base of current block of storage + void* loc_ = nullptr; //!< Current location in block to next allocate + + void internal_init() + { + remaining_ = 0; + base_ = nullptr; + usedMemory = 0; + wastedMemory = 0; + } + + public: + Size usedMemory = 0; + Size wastedMemory = 0; + + /** + Default constructor. Initializes a new pool. + */ + PooledAllocator() { internal_init(); } + + /** + * Destructor. Frees all the memory allocated in this pool. + */ + ~PooledAllocator() { free_all(); } + + /** Frees all allocated memory chunks */ + void free_all() + { + while (base_ != nullptr) + { + // Get pointer to prev block + void* prev = *(static_cast(base_)); + ::free(base_); + base_ = prev; + } + internal_init(); + } + + /** + * Returns a pointer to a piece of new memory of the given size in bytes + * allocated from the pool. + */ + void* malloc(const size_t req_size) + { + /* Round size up to a multiple of wordsize. The following expression + only works for WORDSIZE that is a power of 2, by masking last bits + of incremented size to zero. + */ + const Size size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); + + /* Check whether a new block must be allocated. Note that the first + word of a block is reserved for a pointer to the previous block. + */ + if (size > remaining_) + { + wastedMemory += remaining_; + + /* Allocate new storage. */ + const Size blocksize = size > BLOCKSIZE ? size + WORDSIZE : BLOCKSIZE + WORDSIZE; + + // use the standard C malloc to allocate memory + void* m = ::malloc(blocksize); + if (!m) + { + throw std::bad_alloc(); + } + + /* Fill first word of new block with pointer to previous block. */ + static_cast(m)[0] = base_; + base_ = m; + + remaining_ = blocksize - WORDSIZE; + loc_ = static_cast(m) + WORDSIZE; + } + void* rloc = loc_; + loc_ = static_cast(loc_) + size; + remaining_ -= size; + + usedMemory += size; + + return rloc; + } + + /** + * Allocates (using this pool) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + T* allocate(const size_t count = 1) + { + T* mem = static_cast(this->malloc(sizeof(T) * count)); + return mem; + } +}; +/** @} */ + +/** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff + * @{ */ + +/** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors + * when DIM=-1. Fixed size version for a generic DIM: + */ +template +struct array_or_vector +{ + using type = std::array; +}; +/** Dynamic size version */ +template +struct array_or_vector<-1, T> +{ + using type = std::vector; +}; + +/** @} */ + +/** kd-tree base-class + * + * Contains the member functions common to the classes KDTreeSingleIndexAdaptor + * and KDTreeSingleIndexDynamicAdaptor_. + * + * \tparam Derived The name of the class which inherits this class. + * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to + * have a lifetime equal or longer than the instance of this class. + * \tparam Distance The distance metric to use, these are all classes derived + * from nanoflann::Metric + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class Derived, typename Distance, class DatasetAdaptor, int32_t DIM = -1, + typename index_t = uint32_t> +class KDTreeBaseClass +{ + public: + /** Frees the previously-built index. Automatically called within + * buildIndex(). */ + void freeIndex(Derived& obj) + { + obj.pool_.free_all(); + obj.root_node_ = nullptr; + obj.size_at_index_build_ = 0; + } + + using ElementType = typename Distance::ElementType; + using DistanceType = typename Distance::DistanceType; + using IndexType = index_t; + + /** + * Array of indices to vectors in the dataset_. + */ + std::vector vAcc_; + + using Offset = typename decltype(vAcc_)::size_type; + using Size = typename decltype(vAcc_)::size_type; + using Dimension = int32_t; + + /*------------------------------------------------------------------- + * Internal Data Structures + * + * "Node" below can be declared with alignas(N) to improve + * cache friendliness and SIMD load/store performance. + * + * The optimal N depends on the underlying hardware: + * + Intel x86-64: 16 for SSE, 32 for AVX/AVX2 and 64 for AVX-512 + * + NVIDIA Jetson: 16 for ARM + NEON and CUDA float4/ + * To avoid unnecessary padding, the smallest alignment + * compatible with a platform's vector width should be chosen. + * ------------------------------------------------------------------*/ + struct alignas(NANOFLANN_NODE_ALIGNMENT) Node + { + /** Union used because a node can be either a LEAF node or a non-leaf + * node, so both data fields are never used simultaneously */ + union + { + struct leaf + { + Offset left, right; //!< Indices of points in leaf node + } lr; + struct nonleaf + { + Dimension divfeat; //!< Dimension used for subdivision. + /// The values used for subdivision. + DistanceType divlow, divhigh; + } sub; + } node_type; + + /** Child nodes (both=nullptr mean its a leaf node) */ + Node *child1 = nullptr, *child2 = nullptr; + }; + + using NodePtr = Node*; + using NodeConstPtr = const Node*; + + struct Interval + { + ElementType low, high; + }; + + NodePtr root_node_ = nullptr; + + Size leaf_max_size_ = 0; + + /// Number of thread for concurrent tree build + Size n_thread_build_ = 1; + /// Number of current points in the dataset + Size size_ = 0; + /// Number of points in the dataset when the index was built + Size size_at_index_build_ = 0; + Dimension dim_ = 0; //!< Dimensionality of each data point + + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename array_or_vector::type; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename array_or_vector::type; + + /** The KD-tree used to find neighbours */ + BoundingBox root_bbox_; + + /** + * Pooled memory allocator. + * + * Using a pooled memory allocator is more efficient + * than allocating memory directly when there is a large + * number small of memory allocations. + */ + PooledAllocator pool_; + + /** Returns number of points in dataset */ + Size size(const Derived& obj) const { return obj.size_; } + + /** Returns the length of each point in the dataset */ + Size veclen(const Derived& obj) const { return DIM > 0 ? DIM : obj.dim_; } + + /// Helper accessor to the dataset points: + ElementType dataset_get(const Derived& obj, IndexType element, Dimension component) const + { + return obj.dataset_.kdtree_get_pt(element, component); + } + + /** + * Computes the index memory usage + * Returns: memory used by the index + */ + Size usedMemory(const Derived& obj) const + { + return obj.pool_.usedMemory + obj.pool_.wastedMemory + + obj.dataset_.kdtree_get_point_count() * + sizeof(IndexType); // pool memory and vind array memory + } + + /** + * Compute the minimum and maximum element values in the specified dimension + */ + void computeMinMax( + const Derived& obj, Offset ind, Size count, Dimension element, ElementType& min_elem, + ElementType& max_elem) const + { + min_elem = dataset_get(obj, vAcc_[ind], element); + max_elem = min_elem; + for (Offset i = 1; i < count; ++i) + { + ElementType val = dataset_get(obj, vAcc_[ind + i], element); + if (val < min_elem) min_elem = val; + if (val > max_elem) max_elem = val; + } + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] + * to vind[last]. The routine is called recursively on each sublist. + * + * @param left index of the first vector + * @param right index of the last vector + * @param bbox bounding box used as input for splitting and output for + * parent node + */ + NodePtr divideTree(Derived& obj, const Offset left, const Offset right, BoundingBox& bbox) + { + assert(left < obj.dataset_.kdtree_get_point_count()); + + NodePtr node = obj.pool_.template allocate(); // allocate memory + const auto dims = (DIM > 0 ? DIM : obj.dim_); + + /* If too few exemplars remain, then make this a leaf node. */ + if ((right - left) <= static_cast(obj.leaf_max_size_)) + { + node->child1 = node->child2 = nullptr; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = dataset_get(obj, obj.vAcc_[left], i); + bbox[i].high = dataset_get(obj, obj.vAcc_[left], i); + } + for (Offset k = left + 1; k < right; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = dataset_get(obj, obj.vAcc_[k], i); + if (bbox[i].low > val) bbox[i].low = val; + if (bbox[i].high < val) bbox[i].high = val; + } + } + } + else + { + /* Determine the index, dimension and value for split plane */ + Offset idx; + Dimension cutfeat; + DistanceType cutval; + middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + /* Recurse on left */ + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = this->divideTree(obj, left, left + idx, left_bbox); + + /* Recurse on right */ + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + node->child2 = this->divideTree(obj, left + idx, right, right_bbox); + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] to + * vind[last] concurrently. The routine is called recursively on each + * sublist. + * + * @param left index of the first vector + * @param right index of the last vector + * @param bbox bounding box used as input for splitting and output for + * parent node + * @param thread_count count of std::async threads + * @param mutex mutex for mempool allocation + */ + NodePtr divideTreeConcurrent( + Derived& obj, const Offset left, const Offset right, BoundingBox& bbox, + std::atomic& thread_count, std::mutex& mutex) + { + std::unique_lock lock(mutex); + NodePtr node = obj.pool_.template allocate(); // allocate memory + lock.unlock(); + + const auto dims = (DIM > 0 ? DIM : obj.dim_); + + /* If too few exemplars remain, then make this a leaf node. */ + if ((right - left) <= static_cast(obj.leaf_max_size_)) + { + node->child1 = node->child2 = nullptr; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = dataset_get(obj, obj.vAcc_[left], i); + bbox[i].high = dataset_get(obj, obj.vAcc_[left], i); + } + for (Offset k = left + 1; k < right; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = dataset_get(obj, obj.vAcc_[k], i); + if (bbox[i].low > val) bbox[i].low = val; + if (bbox[i].high < val) bbox[i].high = val; + } + } + } + else + { + /* Determine the index, dimension and value for split plane */ + Offset idx; + Dimension cutfeat; + DistanceType cutval; + middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + std::future right_future; + + /* Recurse on right concurrently, if possible */ + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + if (++thread_count < n_thread_build_) + { + /* Concurrent thread for right recursion */ + + right_future = std::async( + std::launch::async, &KDTreeBaseClass::divideTreeConcurrent, this, std::ref(obj), + left + idx, right, std::ref(right_bbox), std::ref(thread_count), + std::ref(mutex)); + } + else + { + --thread_count; + } + + /* Recurse on left in this thread */ + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = + this->divideTreeConcurrent(obj, left, left + idx, left_bbox, thread_count, mutex); + + if (right_future.valid()) + { + /* Block and wait for concurrent right from above */ + + node->child2 = right_future.get(); + --thread_count; + } + else + { + /* Otherwise, recurse on right in this thread */ + + node->child2 = this->divideTreeConcurrent( + obj, left + idx, right, right_bbox, thread_count, mutex); + } + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + void middleSplit_( + const Derived& obj, const Offset ind, const Size count, Offset& index, Dimension& cutfeat, + DistanceType& cutval, const BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : obj.dim_); + const auto EPS = static_cast(0.00001); + + // Pre-compute max_span once + ElementType max_span = bbox[0].high - bbox[0].low; + for (Dimension i = 1; i < dims; ++i) + { + ElementType span = bbox[i].high - bbox[i].low; + if (span > max_span) max_span = span; + } + + // Single-pass min/max computation for candidate dimensions + cutfeat = 0; + ElementType max_spread = -1; + ElementType min_elem = 0, max_elem = 0; + + // Only check dimensions within (1-EPS) of max_span + std::vector candidates; + candidates.reserve(dims); + for (Dimension i = 0; i < dims; ++i) + { + if (bbox[i].high - bbox[i].low >= (1 - EPS) * max_span) + { + candidates.push_back(i); + } + } + + // Vectorized min/max for candidates + for (Dimension dim : candidates) + { + ElementType local_min = dataset_get(obj, vAcc_[ind], dim); + ElementType local_max = local_min; + + // Unrolled loop for better performance + constexpr size_t UNROLL = 4; + Offset k = 1; + for (; k + UNROLL <= count; k += UNROLL) + { + ElementType v0 = dataset_get(obj, vAcc_[ind + k], dim); + ElementType v1 = dataset_get(obj, vAcc_[ind + k + 1], dim); + ElementType v2 = dataset_get(obj, vAcc_[ind + k + 2], dim); + ElementType v3 = dataset_get(obj, vAcc_[ind + k + 3], dim); + + local_min = std::min({local_min, v0, v1, v2, v3}); + local_max = std::max({local_max, v0, v1, v2, v3}); + } + + // Handle remainder + for (; k < count; ++k) + { + ElementType val = dataset_get(obj, vAcc_[ind + k], dim); + local_min = std::min(local_min, val); + local_max = std::max(local_max, val); + } + + ElementType spread = local_max - local_min; + if (spread > max_spread) + { + cutfeat = dim; + max_spread = spread; + min_elem = local_min; + max_elem = local_max; + } + } + + // Median-of-three for better balance + DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; + if (split_val < min_elem) split_val = min_elem; + if (split_val > max_elem) split_val = max_elem; + + cutval = split_val; + + // Optimized partitioning + Offset lim1, lim2; + planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); + + index = (lim1 > count / 2) ? lim1 : (lim2 < count / 2) ? lim2 : count / 2; + } + + /** + * Subdivide the list of points by a plane perpendicular on the axis + * corresponding to the 'cutfeat' dimension at 'cutval' position. + * + * On return: + * dataset[ind[0..lim1-1]][cutfeat] < cutval + * dataset[ind[lim1..lim2-1]][cutfeat] == cutval + * dataset[ind[lim2..count]][cutfeat] > cutval + */ + void planeSplit( + const Derived& obj, const Offset ind, const Size count, const Dimension cutfeat, + const DistanceType& cutval, Offset& lim1, Offset& lim2) + { + // Dutch National Flag algorithm for three-way partitioning + Offset left = 0; + Offset mid = 0; + Offset right = count - 1; + + while (mid <= right) + { + ElementType val = dataset_get(obj, vAcc_[ind + mid], cutfeat); + + if (val < cutval) + { + std::swap(vAcc_[ind + left], vAcc_[ind + mid]); + left++; + mid++; + } + else if (val > cutval) + { + std::swap(vAcc_[ind + mid], vAcc_[ind + right]); + right--; + } + else + { + mid++; + } + } + + lim1 = left; + lim2 = mid; + } + + DistanceType computeInitialDistances( + const Derived& obj, const ElementType* vec, distance_vector_t& dists) const + { + assert(vec); + DistanceType dist = DistanceType(); + + for (Dimension i = 0; i < (DIM > 0 ? DIM : obj.dim_); ++i) + { + if (vec[i] < obj.root_bbox_[i].low) + { + dists[i] = obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].low, i); + dist += dists[i]; + } + if (vec[i] > obj.root_bbox_[i].high) + { + dists[i] = obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].high, i); + dist += dists[i]; + } + } + return dist; + } + + static void save_tree(const Derived& obj, std::ostream& stream, const NodeConstPtr tree) + { + save_value(stream, *tree); + if (tree->child1 != nullptr) + { + save_tree(obj, stream, tree->child1); + } + if (tree->child2 != nullptr) + { + save_tree(obj, stream, tree->child2); + } + } + + static void load_tree(Derived& obj, std::istream& stream, NodePtr& tree) + { + tree = obj.pool_.template allocate(); + load_value(stream, *tree); + if (tree->child1 != nullptr) + { + load_tree(obj, stream, tree->child1); + } + if (tree->child2 != nullptr) + { + load_tree(obj, stream, tree->child2); + } + } + + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(const Derived& obj, std::ostream& stream) const + { + save_value(stream, obj.size_); + save_value(stream, obj.dim_); + save_value(stream, obj.root_bbox_); + save_value(stream, obj.leaf_max_size_); + save_value(stream, obj.vAcc_); + if (obj.root_node_) save_tree(obj, stream, obj.root_node_); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(Derived& obj, std::istream& stream) + { + load_value(stream, obj.size_); + load_value(stream, obj.dim_); + load_value(stream, obj.root_bbox_); + load_value(stream, obj.leaf_max_size_); + load_value(stream, obj.vAcc_); + load_tree(obj, stream, obj.root_node_); + } +}; + +/** @addtogroup kdtrees_grp KD-tree classes and adaptors + * @{ */ + +/** kd-tree static index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * size_t kdtree_get_point_count() const { ... } + * + * + * // Must return the dim'th component of the idx'th point in the class: + * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to + * have a lifetime equal or longer than the instance of this class. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will + * be typically size_t or int + */ +template +class KDTreeSingleIndexAdaptor + : public KDTreeBaseClass< + KDTreeSingleIndexAdaptor, Distance, + DatasetAdaptor, DIM, index_t> +{ + public: + /** Deleted copy constructor*/ + explicit KDTreeSingleIndexAdaptor( + const KDTreeSingleIndexAdaptor&) = delete; + + /** The data source used by this index */ + const DatasetAdaptor& dataset_; + + const KDTreeSingleIndexAdaptorParams indexParams; + + Distance distance_; + + using Base = typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexAdaptor, Distance, + DatasetAdaptor, DIM, index_t>; + + using Offset = typename Base::Offset; + using Size = typename Base::Size; + using Dimension = typename Base::Dimension; + + using ElementType = typename Base::ElementType; + using DistanceType = typename Base::DistanceType; + using IndexType = typename Base::IndexType; + + using Node = typename Base::Node; + using NodePtr = Node*; + + using Interval = typename Base::Interval; + + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename Base::BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename Base::distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + * + * Note that there is a variable number of optional additional parameters + * which will be forwarded to the metric class constructor. Refer to example + * `examples/pointcloud_custom_metric.cpp` for a use case. + * + */ + template + explicit KDTreeSingleIndexAdaptor( + const Dimension dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params, Args&&... args) + : dataset_(inputData), + indexParams(params), + distance_(inputData, std::forward(args)...) + { + init(dimensionality, params); + } + + explicit KDTreeSingleIndexAdaptor( + const Dimension dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params = {}) + : dataset_(inputData), indexParams(params), distance_(inputData) + { + init(dimensionality, params); + } + + private: + void init(const Dimension dimensionality, const KDTreeSingleIndexAdaptorParams& params) + { + Base::size_ = dataset_.kdtree_get_point_count(); + Base::size_at_index_build_ = Base::size_; + Base::dim_ = dimensionality; + if (DIM > 0) Base::dim_ = DIM; + Base::leaf_max_size_ = params.leaf_max_size; + if (params.n_thread_build > 0) + { + Base::n_thread_build_ = params.n_thread_build; + } + else + { + Base::n_thread_build_ = std::max(std::thread::hardware_concurrency(), 1u); + } + + if (!(params.flags & KDTreeSingleIndexAdaptorFlags::SkipInitialBuildIndex)) + { + // Build KD-tree: + buildIndex(); + } + } + + public: + /** + * Builds the index + */ + void buildIndex() + { + Base::size_ = dataset_.kdtree_get_point_count(); + Base::size_at_index_build_ = Base::size_; + init_vind(); + this->freeIndex(*this); + Base::size_at_index_build_ = Base::size_; + if (Base::size_ == 0) return; + computeBoundingBox(Base::root_bbox_); + // construct the tree + if (Base::n_thread_build_ == 1) + { + Base::root_node_ = this->divideTree(*this, 0, Base::size_, Base::root_bbox_); + } + else + { +#ifndef NANOFLANN_NO_THREADS + std::atomic thread_count(0u); + std::mutex mutex; + Base::root_node_ = this->divideTreeConcurrent( + *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex); +#else /* NANOFLANN_NO_THREADS */ + throw std::runtime_error("Multithreading is disabled"); +#endif /* NANOFLANN_NO_THREADS */ + } + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, const SearchParameters& searchParams = {}) const + { + assert(vec); + if (this->size(*this) == 0) return false; + if (!Base::root_node_) + throw std::runtime_error( + "[nanoflann] findNeighbors() called before building the " + "index."); + float epsError = 1 + searchParams.eps; + + // fixed or variable-sized container (depending on DIM) + distance_vector_t dists; + // Fill it with zeros. + auto zero = static_cast(0); + assign(dists, (DIM > 0 ? DIM : Base::dim_), zero); + DistanceType dist = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, Base::root_node_, dist, dists, epsError); + + if (searchParams.sorted) result.sort(); + + return result.full(); + } + + /** + * Find all points contained within the specified bounding box. Their + * indices are stored inside the result object. + * + * Params: + * result = the result object in which the indices of the points + * within the bounding box are stored + * bbox = the bounding box defining the search region + * + * \tparam RESULTSET Should be any ResultSet + * \return Number of points found within the bounding box. + * \sa findNeighbors, knnSearch, radiusSearch + * + * \note The search is inclusive - points on the boundary are included. + */ + template + Size findWithinBox(RESULTSET& result, const BoundingBox& bbox) const + { + if (this->size(*this) == 0) return 0; + if (!Base::root_node_) + throw std::runtime_error( + "[nanoflann] findWithinBox() called before building the " + "index."); + + std::stack stack; + stack.push(Base::root_node_); + + while (!stack.empty()) + { + const NodePtr node = stack.top(); + stack.pop(); + + // If this is a leaf node, then do check and return. + if (!node->child1) // (if one node is nullptr, both are) + { + for (Offset i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) + { + if (contains(bbox, Base::vAcc_[i])) + { + if (!result.addPoint(0, Base::vAcc_[i])) + { + // the resultset doesn't want to receive any more + // points, we're done searching! + return result.size(); + } + } + } + } + else + { + const int idx = node->node_type.sub.divfeat; + const auto low_bound = node->node_type.sub.divlow; + const auto high_bound = node->node_type.sub.divhigh; + + if (bbox[idx].low <= low_bound) stack.push(node->child1); + if (bbox[idx].high >= high_bound) stack.push(node->child2); + } + } + + return result.size(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices and distances are stored in the provided pointers to + * array/vector. + * + * \sa radiusSearch, findNeighbors + * \return Number `N` of valid points in the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return is less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size knnSearch( + const ElementType* query_point, const Size num_closest, IndexType* out_indices, + DistanceType* out_distances) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the + * vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + * + * \note If L2 norms are used, search radius and all returned distances + * are actually squared distances. + */ + Size radiusSearch( + const ElementType* query_point, const DistanceType& radius, + std::vector>& IndicesDists, + const SearchParameters& searchParams = {}) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const Size nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as + * a start point for your own classes. \sa radiusSearch + */ + template + Size radiusSearchCustomCallback( + const ElementType* query_point, SEARCH_CALLBACK& resultSet, + const SearchParameters& searchParams = {}) const + { + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** + * Find the first N neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * \sa radiusSearch, findNeighbors + * \return Number `N` of valid points in the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return is less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size rknnSearch( + const ElementType* query_point, const Size num_closest, IndexType* out_indices, + DistanceType* out_distances, const DistanceType& radius) const + { + nanoflann::RKNNResultSet resultSet(num_closest, radius); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point); + return resultSet.size(); + } + + /** @} */ + + public: + /** Make sure the auxiliary list \a vind has the same size as the + * current dataset, and re-generate if size has changed. */ + void init_vind() + { + // Create a permutable array of indices to the input vectors. + Base::size_ = dataset_.kdtree_get_point_count(); + if (Base::vAcc_.size() != Base::size_) Base::vAcc_.resize(Base::size_); + for (IndexType i = 0; i < static_cast(Base::size_); i++) Base::vAcc_[i] = i; + } + + void computeBoundingBox(BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : Base::dim_); + resize(bbox, dims); + if (dataset_.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const Size N = dataset_.kdtree_get_point_count(); + if (!N) + throw std::runtime_error( + "[nanoflann] computeBoundingBox() called but " + "no data points found."); + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = bbox[i].high = this->dataset_get(*this, Base::vAcc_[0], i); + } + for (Offset k = 1; k < N; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = this->dataset_get(*this, Base::vAcc_[k], i); + if (val < bbox[i].low) bbox[i].low = val; + if (val > bbox[i].high) bbox[i].high = val; + } + } + } + } + + bool contains(const BoundingBox& bbox, IndexType idx) const + { + const auto dims = (DIM > 0 ? DIM : Base::dim_); + for (Dimension i = 0; i < dims; ++i) + { + const auto point = this->dataset_.kdtree_get_pt(idx, i); + if (point < bbox[i].low || point > bbox[i].high) return false; + } + return true; + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + * \return true if the search should be continued, false if the results are + * sufficient + */ + template + bool searchLevel( + RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindist, + distance_vector_t& dists, const float epsError) const + { + // If this is a leaf node, then do check and return. + if (!node->child1) // (if one node is nullptr, both are) + { + for (Offset i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) + { + const IndexType accessor = Base::vAcc_[i]; // reorder... : i; + DistanceType dist = + distance_.evalMetric(vec, accessor, (DIM > 0 ? DIM : Base::dim_)); + if (dist < result_set.worstDist()) + { + if (!result_set.addPoint(dist, Base::vAcc_[i])) + { + // the resultset doesn't want to receive any more + // points, we're done searching! + return false; + } + } + } + return true; + } + + /* Which child branch should be taken first? */ + Dimension idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) + { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance_.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else + { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance_.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + if (!searchLevel(result_set, vec, bestChild, mindist, dists, epsError)) + { + // the resultset doesn't want to receive any more points, we're done + // searching! + return false; + } + + DistanceType dst = dists[idx]; + mindist = mindist + cut_dist - dst; + dists[idx] = cut_dist; + if (mindist * epsError <= result_set.worstDist()) + { + if (!searchLevel(result_set, vec, otherChild, mindist, dists, epsError)) + { + // the resultset doesn't want to receive any more points, we're + // done searching! + return false; + } + } + dists[idx] = dst; + return true; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(std::ostream& stream) const { Base::saveIndex(*this, stream); } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(std::istream& stream) { Base::loadIndex(*this, stream); } + +}; // class KDTree + +/** kd-tree dynamic index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * size_t kdtree_get_point_count() const { ... } + * + * // Must return the dim'th component of the idx'th point in the class: + * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template +class KDTreeSingleIndexDynamicAdaptor_ + : public KDTreeBaseClass< + KDTreeSingleIndexDynamicAdaptor_, Distance, + DatasetAdaptor, DIM, IndexType> +{ + public: + /** + * The dataset used by this index + */ + const DatasetAdaptor& dataset_; //!< The source of our data + + KDTreeSingleIndexAdaptorParams index_params_; + + std::vector& treeIndex_; + + Distance distance_; + + using Base = typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexDynamicAdaptor_, + Distance, DatasetAdaptor, DIM, IndexType>; + + using ElementType = typename Base::ElementType; + using DistanceType = typename Base::DistanceType; + + using Offset = typename Base::Offset; + using Size = typename Base::Size; + using Dimension = typename Base::Dimension; + + using Node = typename Base::Node; + using NodePtr = Node*; + + using Interval = typename Base::Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename Base::BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename Base::distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor_( + const Dimension dimensionality, const DatasetAdaptor& inputData, + std::vector& treeIndex, + const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams()) + : dataset_(inputData), index_params_(params), treeIndex_(treeIndex), distance_(inputData) + { + Base::size_ = 0; + Base::size_at_index_build_ = 0; + for (auto& v : Base::root_bbox_) v = {}; + Base::dim_ = dimensionality; + if (DIM > 0) Base::dim_ = DIM; + Base::leaf_max_size_ = params.leaf_max_size; + if (params.n_thread_build > 0) + { + Base::n_thread_build_ = params.n_thread_build; + } + else + { + Base::n_thread_build_ = std::max(std::thread::hardware_concurrency(), 1u); + } + } + + /** Explicitly default the copy constructor */ + KDTreeSingleIndexDynamicAdaptor_(const KDTreeSingleIndexDynamicAdaptor_& rhs) = default; + + /** Assignment operator definiton */ + KDTreeSingleIndexDynamicAdaptor_ operator=(const KDTreeSingleIndexDynamicAdaptor_& rhs) + { + KDTreeSingleIndexDynamicAdaptor_ tmp(rhs); + std::swap(Base::vAcc_, tmp.Base::vAcc_); + std::swap(Base::leaf_max_size_, tmp.Base::leaf_max_size_); + std::swap(index_params_, tmp.index_params_); + std::swap(treeIndex_, tmp.treeIndex_); + std::swap(Base::size_, tmp.Base::size_); + std::swap(Base::size_at_index_build_, tmp.Base::size_at_index_build_); + std::swap(Base::root_node_, tmp.Base::root_node_); + std::swap(Base::root_bbox_, tmp.Base::root_bbox_); + std::swap(Base::pool_, tmp.Base::pool_); + return *this; + } + + /** + * Builds the index + */ + void buildIndex() + { + Base::size_ = Base::vAcc_.size(); + this->freeIndex(*this); + Base::size_at_index_build_ = Base::size_; + if (Base::size_ == 0) return; + computeBoundingBox(Base::root_bbox_); + // construct the tree + if (Base::n_thread_build_ == 1) + { + Base::root_node_ = this->divideTree(*this, 0, Base::size_, Base::root_bbox_); + } + else + { +#ifndef NANOFLANN_NO_THREADS + std::atomic thread_count(0u); + std::mutex mutex; + Base::root_node_ = this->divideTreeConcurrent( + *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex); +#else /* NANOFLANN_NO_THREADS */ + throw std::runtime_error("Multithreading is disabled"); +#endif /* NANOFLANN_NO_THREADS */ + } + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * This is the core search function, all others are wrappers around this + * one. + * + * \param result The result object in which the indices of the + * nearest-neighbors are stored. + * \param vec The vector of the query point for which to search the + * nearest neighbors. + * \param searchParams Optional parameters for the search. + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * + * \sa knnSearch(), radiusSearch(), radiusSearchCustomCallback() + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, const SearchParameters& searchParams = {}) const + { + assert(vec); + if (this->size(*this) == 0) return false; + if (!Base::root_node_) return false; + float epsError = 1 + searchParams.eps; + + // fixed or variable-sized container (depending on DIM) + distance_vector_t dists; + // Fill it with zeros. + assign( + dists, (DIM > 0 ? DIM : Base::dim_), + static_cast(0)); + DistanceType dist = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, Base::root_node_, dist, dists, epsError); + + if (searchParams.sorted) result.sort(); + + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices are stored inside the result object. \sa radiusSearch, + * findNeighbors + * \return Number `N` of valid points in + * the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return may be less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size knnSearch( + const ElementType* query_point, const Size num_closest, IndexType* out_indices, + DistanceType* out_distances, const SearchParameters& searchParams = {}) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the + * vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + * + * \note If L2 norms are used, search radius and all returned distances + * are actually squared distances. + */ + Size radiusSearch( + const ElementType* query_point, const DistanceType& radius, + std::vector>& IndicesDists, + const SearchParameters& searchParams = {}) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as + * a start point for your own classes. \sa radiusSearch + */ + template + Size radiusSearchCustomCallback( + const ElementType* query_point, SEARCH_CALLBACK& resultSet, + const SearchParameters& searchParams = {}) const + { + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + void computeBoundingBox(BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : Base::dim_); + resize(bbox, dims); + + if (dataset_.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const Size N = Base::size_; + if (!N) + throw std::runtime_error( + "[nanoflann] computeBoundingBox() called but " + "no data points found."); + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = bbox[i].high = this->dataset_get(*this, Base::vAcc_[0], i); + } + for (Offset k = 1; k < N; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = this->dataset_get(*this, Base::vAcc_[k], i); + if (val < bbox[i].low) bbox[i].low = val; + if (val > bbox[i].high) bbox[i].high = val; + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + */ + template + void searchLevel( + RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindist, + distance_vector_t& dists, const float epsError) const + { + // If this is a leaf node, then do check and return. + if (!node->child1) // (if one node is nullptr, both are) + { + for (Offset i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) + { + const IndexType index = Base::vAcc_[i]; // reorder... : i; + if (treeIndex_[index] == -1) continue; + DistanceType dist = distance_.evalMetric(vec, index, (DIM > 0 ? DIM : Base::dim_)); + if (dist < result_set.worstDist()) + { + if (!result_set.addPoint( + static_cast(dist), + static_cast(Base::vAcc_[i]))) + { + // the resultset doesn't want to receive any more + // points, we're done searching! + return; // false; + } + } + } + return; + } + + /* Which child branch should be taken first? */ + Dimension idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) + { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance_.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else + { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance_.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + searchLevel(result_set, vec, bestChild, mindist, dists, epsError); + + DistanceType dst = dists[idx]; + mindist = mindist + cut_dist - dst; + dists[idx] = cut_dist; + if (mindist * epsError <= result_set.worstDist()) + { + searchLevel(result_set, vec, otherChild, mindist, dists, epsError); + } + dists[idx] = dst; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(std::ostream& stream) { saveIndex(*this, stream); } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(std::istream& stream) { loadIndex(*this, stream); } +}; + +/** kd-tree dynaimic index + * + * class to create multiple static index and merge their results to behave as + * single dynamic index as proposed in Logarithmic Approach. + * + * Example of usage: + * examples/dynamic_pointcloud_example.cpp + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType + * Will be typically size_t or int + */ +template +class KDTreeSingleIndexDynamicAdaptor +{ + public: + using ElementType = typename Distance::ElementType; + using DistanceType = typename Distance::DistanceType; + + using Offset = typename KDTreeSingleIndexDynamicAdaptor_::Offset; + using Size = typename KDTreeSingleIndexDynamicAdaptor_::Size; + using Dimension = + typename KDTreeSingleIndexDynamicAdaptor_::Dimension; + + protected: + Size leaf_max_size_; + Size treeCount_; + Size pointCount_; + + /** + * The dataset used by this index + */ + const DatasetAdaptor& dataset_; //!< The source of our data + + /** treeIndex[idx] is the index of tree in which point at idx is stored. + * treeIndex[idx]=-1 means that point has been removed. */ + std::vector treeIndex_; + std::unordered_set removedPoints_; + + KDTreeSingleIndexAdaptorParams index_params_; + + Dimension dim_; //!< Dimensionality of each data point + + using index_container_t = + KDTreeSingleIndexDynamicAdaptor_; + std::vector index_; + + public: + /** Get a const ref to the internal list of indices; the number of indices + * is adapted dynamically as the dataset grows in size. */ + const std::vector& getAllIndices() const { return index_; } + + private: + /** finds position of least significant unset bit */ + int First0Bit(IndexType num) + { + int pos = 0; + while (num & 1) + { + num = num >> 1; + pos++; + } + return pos; + } + + /** Creates multiple empty trees to handle dynamic support */ + void init() + { + using my_kd_tree_t = + KDTreeSingleIndexDynamicAdaptor_; + std::vector index( + treeCount_, my_kd_tree_t(dim_ /*dim*/, dataset_, treeIndex_, index_params_)); + index_ = index; + } + + public: + Distance distance_; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + */ + explicit KDTreeSingleIndexDynamicAdaptor( + const int dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams(), + const size_t maximumPointCount = 1000000000U) + : dataset_(inputData), index_params_(params), distance_(inputData) + { + treeCount_ = static_cast(std::log2(maximumPointCount)) + 1; + pointCount_ = 0U; + dim_ = dimensionality; + treeIndex_.clear(); + if (DIM > 0) dim_ = DIM; + leaf_max_size_ = params.leaf_max_size; + init(); + const size_t num_initial_points = dataset_.kdtree_get_point_count(); + if (num_initial_points > 0) + { + addPoints(0, num_initial_points - 1); + } + } + + /** Deleted copy constructor*/ + explicit KDTreeSingleIndexDynamicAdaptor( + const KDTreeSingleIndexDynamicAdaptor&) = delete; + + /** Add points to the set, Inserts all points from [start, end] */ + void addPoints(IndexType start, IndexType end) + { + const Size count = end - start + 1; + int maxIndex = 0; + treeIndex_.resize(treeIndex_.size() + count); + for (IndexType idx = start; idx <= end; idx++) + { + const int pos = First0Bit(pointCount_); + maxIndex = std::max(pos, maxIndex); + treeIndex_[pointCount_] = pos; + + const auto it = removedPoints_.find(idx); + if (it != removedPoints_.end()) + { + removedPoints_.erase(it); + treeIndex_[idx] = pos; + } + + for (int i = 0; i < pos; i++) + { + for (int j = 0; j < static_cast(index_[i].vAcc_.size()); j++) + { + index_[pos].vAcc_.push_back(index_[i].vAcc_[j]); + if (treeIndex_[index_[i].vAcc_[j]] != -1) treeIndex_[index_[i].vAcc_[j]] = pos; + } + index_[i].vAcc_.clear(); + } + index_[pos].vAcc_.push_back(idx); + pointCount_++; + } + + for (int i = 0; i <= maxIndex; ++i) + { + index_[i].freeIndex(index_[i]); + if (!index_[i].vAcc_.empty()) index_[i].buildIndex(); + } + } + + /** Remove a point from the set (Lazy Deletion) */ + void removePoint(size_t idx) + { + if (idx >= pointCount_) return; + removedPoints_.insert(idx); + treeIndex_[idx] = -1; + } + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, const SearchParameters& searchParams = {}) const + { + for (size_t i = 0; i < treeCount_; i++) + { + index_[i].findNeighbors(result, &vec[0], searchParams); + } + return result.full(); + } +}; + +/** An L2-metric KD-tree adaptor for working with data directly stored in an + * Eigen Matrix, without duplicating the data storage. You can select whether a + * row or column in the matrix represents a point in the state space. + * + * Example of usage: + * \code + * Eigen::Matrix mat; + * + * // Fill out "mat"... + * using my_kd_tree_t = nanoflann::KDTreeEigenMatrixAdaptor< + * Eigen::Matrix>; + * + * const int max_leaf = 10; + * my_kd_tree_t mat_index(mat, max_leaf); + * mat_index.index->... + * \endcode + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality + * for the points in the data set, allowing more compiler optimizations. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam row_major If set to true the rows of the matrix are used as the + * points, if set to false the columns of the matrix are used as the + * points. + */ +template < + class MatrixType, int32_t DIM = -1, class Distance = nanoflann::metric_L2, + bool row_major = true> +struct KDTreeEigenMatrixAdaptor +{ + using self_t = KDTreeEigenMatrixAdaptor; + using num_t = typename MatrixType::Scalar; + using IndexType = typename MatrixType::Index; + using metric_t = typename Distance::template traits::distance_t; + + using index_t = KDTreeSingleIndexAdaptor< + metric_t, self_t, row_major ? MatrixType::ColsAtCompileTime : MatrixType::RowsAtCompileTime, + IndexType>; + + index_t* index_; //! The kd-tree index for the user to call its methods as + //! usual with any other FLANN index. + + using Offset = typename index_t::Offset; + using Size = typename index_t::Size; + using Dimension = typename index_t::Dimension; + + /// Constructor: takes a const ref to the matrix object with the data points + explicit KDTreeEigenMatrixAdaptor( + const Dimension dimensionality, const std::reference_wrapper& mat, + const int leaf_max_size = 10, const unsigned int n_thread_build = 1) + : m_data_matrix(mat) + { + const auto dims = row_major ? mat.get().cols() : mat.get().rows(); + if (static_cast(dims) != dimensionality) + throw std::runtime_error( + "Error: 'dimensionality' must match column count in data " + "matrix"); + if (DIM > 0 && static_cast(dims) != DIM) + throw std::runtime_error( + "Data set dimensionality does not match the 'DIM' template " + "argument"); + index_ = new index_t( + dims, *this /* adaptor */, + nanoflann::KDTreeSingleIndexAdaptorParams( + leaf_max_size, nanoflann::KDTreeSingleIndexAdaptorFlags::None, n_thread_build)); + } + + public: + /** Deleted copy constructor */ + KDTreeEigenMatrixAdaptor(const self_t&) = delete; + + ~KDTreeEigenMatrixAdaptor() { delete index_; } + + const std::reference_wrapper m_data_matrix; + + /** Query for the \a num_closest closest points to a given point (entered as + * query_point[0:dim-1]). Note that this is a short-cut method for + * index->findNeighbors(). The user can also call index->... methods as + * desired. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + void query( + const num_t* query_point, const Size num_closest, IndexType* out_indices, + num_t* out_distances) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + index_->findNeighbors(resultSet, query_point); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + inline const self_t& derived() const noexcept { return *this; } + inline self_t& derived() noexcept { return *this; } + + // Must return the number of data points + inline Size kdtree_get_point_count() const + { + if (row_major) + return m_data_matrix.get().rows(); + else + return m_data_matrix.get().cols(); + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const IndexType idx, size_t dim) const + { + if (row_major) + return m_data_matrix.get().coeff(idx, IndexType(dim)); + else + return m_data_matrix.get().coeff(IndexType(dim), idx); + } + + // Optional bounding-box computation: return false to default to a standard + // bbox computation loop. + // Return true if the BBOX was already computed by the class and returned + // in "bb" so it can be avoided to redo it again. Look at bb.size() to + // find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + inline bool kdtree_get_bbox(BBOX& /*bb*/) const + { + return false; + } + + /** @} */ + +}; // end of KDTreeEigenMatrixAdaptor +/** @} */ + +/** @} */ // end of grouping +} // namespace nanoflann + +#undef NANOFLANN_RESTRICT diff --git a/vendor/pcg/CMakeLists.txt b/vendor/pcg/CMakeLists.txt new file mode 100644 index 0000000..c106f5e --- /dev/null +++ b/vendor/pcg/CMakeLists.txt @@ -0,0 +1,21 @@ +# Declare the files needed to compile our vendored copy of the PCG random +# number generator +add_library( + pcg + OBJECT + EXCLUDE_FROM_ALL + pcg-advance-64.c + pcg-advance-128.c + pcg-output-32.c + pcg-output-64.c + pcg-output-128.c + pcg-rngs-64.c + pcg-rngs-128.c +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET pcg PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +use_all_warnings(pcg) + diff --git a/vendor/pcg/LICENSE.txt b/vendor/pcg/LICENSE.txt new file mode 100644 index 0000000..2315960 --- /dev/null +++ b/vendor/pcg/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/pcg/pcg-advance-128.c b/vendor/pcg/pcg-advance-128.c new file mode 100644 index 0000000..eceb90e --- /dev/null +++ b/vendor/pcg/pcg-advance-128.c @@ -0,0 +1,61 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * Repetative C code is derived using C preprocessor metaprogramming + * techniques. + */ + +#include "pcg_variants.h" + +/* Multi-step advance functions (jump-ahead, jump-back) + * + * The method used here is based on Brown, "Random Number Generation + * with Arbitrary Stride,", Transactions of the American Nuclear + * Society (Nov. 1994). The algorithm is very similar to fast + * exponentiation. + * + * Even though delta is an unsigned integer, we can pass a + * signed integer to go backwards, it just goes "the long way round". + */ + +#if PCG_HAS_128BIT_OPS +pcg128_t pcg_advance_lcg_128(pcg128_t state, pcg128_t delta, pcg128_t cur_mult, + pcg128_t cur_plus) +{ + pcg128_t acc_mult = 1u; + pcg128_t acc_plus = 0u; + while (delta > 0) { + if (delta & 1) { + acc_mult *= cur_mult; + acc_plus = acc_plus * cur_mult + cur_plus; + } + cur_plus = (cur_mult + 1) * cur_plus; + cur_mult *= cur_mult; + delta /= 2; + } + return acc_mult * state + acc_plus; +} +#endif diff --git a/vendor/pcg/pcg-advance-64.c b/vendor/pcg/pcg-advance-64.c new file mode 100644 index 0000000..ecd05de --- /dev/null +++ b/vendor/pcg/pcg-advance-64.c @@ -0,0 +1,59 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * Repetative C code is derived using C preprocessor metaprogramming + * techniques. + */ + +#include "pcg_variants.h" + +/* Multi-step advance functions (jump-ahead, jump-back) + * + * The method used here is based on Brown, "Random Number Generation + * with Arbitrary Stride,", Transactions of the American Nuclear + * Society (Nov. 1994). The algorithm is very similar to fast + * exponentiation. + * + * Even though delta is an unsigned integer, we can pass a + * signed integer to go backwards, it just goes "the long way round". + */ + +uint64_t pcg_advance_lcg_64(uint64_t state, uint64_t delta, uint64_t cur_mult, + uint64_t cur_plus) +{ + uint64_t acc_mult = 1u; + uint64_t acc_plus = 0u; + while (delta > 0) { + if (delta & 1) { + acc_mult *= cur_mult; + acc_plus = acc_plus * cur_mult + cur_plus; + } + cur_plus = (cur_mult + 1) * cur_plus; + cur_mult *= cur_mult; + delta /= 2; + } + return acc_mult * state + acc_plus; +} diff --git a/vendor/pcg/pcg-output-128.c b/vendor/pcg/pcg-output-128.c new file mode 100644 index 0000000..3955499 --- /dev/null +++ b/vendor/pcg/pcg-output-128.c @@ -0,0 +1,63 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * The contents of this file were mechanically derived from pcg_variants.h + * (every inline function defined there gets a generated extern declaration). + */ + +#include "pcg_variants.h" + +/* + * Rotate helper functions. + */ + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t pcg_rotr_128(pcg128_t value, unsigned int rot); +#endif + +/* + * Output functions. These are the core of the PCG generation scheme. + */ + +/* XSH RS */ + +/* XSH RR */ + +/* RXS M XS */ + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t pcg_output_rxs_m_xs_128_128(pcg128_t state); +#endif + +/* RXS M */ + +/* XSL RR (only defined for >= 64 bits) */ + +/* XSL RR RR (only defined for >= 64 bits) */ + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t pcg_output_xsl_rr_rr_128_128(pcg128_t state); +#endif diff --git a/vendor/pcg/pcg-output-32.c b/vendor/pcg/pcg-output-32.c new file mode 100644 index 0000000..dfe0cc5 --- /dev/null +++ b/vendor/pcg/pcg-output-32.c @@ -0,0 +1,63 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * The contents of this file were mechanically derived from pcg_variants.h + * (every inline function defined there gets a generated extern declaration). + */ + +#include "pcg_variants.h" + +/* + * Rotate helper functions. + */ + +extern inline uint32_t pcg_rotr_32(uint32_t value, unsigned int rot); + +/* + * Output functions. These are the core of the PCG generation scheme. + */ + +/* XSH RS */ + +extern inline uint32_t pcg_output_xsh_rs_64_32(uint64_t state); + +/* XSH RR */ + +extern inline uint32_t pcg_output_xsh_rr_64_32(uint64_t state); + +/* RXS M XS */ + +extern inline uint32_t pcg_output_rxs_m_xs_32_32(uint32_t state); + +/* RXS M */ + +extern inline uint32_t pcg_output_rxs_m_64_32(uint64_t state); + +/* XSL RR (only defined for >= 64 bits) */ + +extern inline uint32_t pcg_output_xsl_rr_64_32(uint64_t state); + +/* XSL RR RR (only defined for >= 64 bits) */ diff --git a/vendor/pcg/pcg-output-64.c b/vendor/pcg/pcg-output-64.c new file mode 100644 index 0000000..ba3598d --- /dev/null +++ b/vendor/pcg/pcg-output-64.c @@ -0,0 +1,73 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * The contents of this file were mechanically derived from pcg_variants.h + * (every inline function defined there gets a generated extern declaration). + */ + +#include "pcg_variants.h" + +/* + * Rotate helper functions. + */ + +extern inline uint64_t pcg_rotr_64(uint64_t value, unsigned int rot); + +/* + * Output functions. These are the core of the PCG generation scheme. + */ + +/* XSH RS */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t pcg_output_xsh_rs_128_64(pcg128_t state); +#endif + +/* XSH RR */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t pcg_output_xsh_rr_128_64(pcg128_t state); +#endif + +/* RXS M XS */ + +extern inline uint64_t pcg_output_rxs_m_xs_64_64(uint64_t state); + +/* RXS M */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t pcg_output_rxs_m_128_64(pcg128_t state); +#endif + +/* XSL RR (only defined for >= 64 bits) */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t pcg_output_xsl_rr_128_64(pcg128_t state); +#endif + +/* XSL RR RR (only defined for >= 64 bits) */ + +extern inline uint64_t pcg_output_xsl_rr_rr_64_64(uint64_t state); diff --git a/vendor/pcg/pcg-rngs-128.c b/vendor/pcg/pcg-rngs-128.c new file mode 100644 index 0000000..6f8797f --- /dev/null +++ b/vendor/pcg/pcg-rngs-128.c @@ -0,0 +1,378 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * The contents of this file were mechanically derived from pcg_variants.h + * (every inline function defined there gets a generated extern declaration). + */ + +#include "pcg_variants.h" + +/* Functions to advance the underlying LCG, one version for each size and + * each style. These functions are considered semi-private. There is rarely + * a good reason to call them directly. + */ + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_oneseq_128_step_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_oneseq_128_advance_r(struct pcg_state_128* rng, + pcg128_t delta); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_mcg_128_step_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_mcg_128_advance_r(struct pcg_state_128* rng, + pcg128_t delta); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_unique_128_step_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_unique_128_advance_r(struct pcg_state_128* rng, + pcg128_t delta); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_setseq_128_step_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_setseq_128_advance_r(struct pcg_state_setseq_128* rng, + pcg128_t delta); +#endif + +/* Functions to seed the RNG state, one version for each size and each + * style. Unlike the step functions, regular users can and should call + * these functions. + */ + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_oneseq_128_srandom_r(struct pcg_state_128* rng, + pcg128_t initstate); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_mcg_128_srandom_r(struct pcg_state_128* rng, + pcg128_t initstate); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_unique_128_srandom_r(struct pcg_state_128* rng, + pcg128_t initstate); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline void pcg_setseq_128_srandom_r(struct pcg_state_setseq_128* rng, + pcg128_t initstate, + pcg128_t initseq); +#endif + +/* Now, finally we create each of the individual generators. We provide + * a random_r function that provides a random number of the appropriate + * type (using the full range of the type) and a boundedrand_r version + * that provides + * + * Implementation notes for boundedrand_r: + * + * To avoid bias, we need to make the range of the RNG a multiple of + * bound, which we do by dropping output less than a threshold. + * Let's consider a 32-bit case... A naive scheme to calculate the + * threshold would be to do + * + * uint32_t threshold = 0x100000000ull % bound; + * + * but 64-bit div/mod is slower than 32-bit div/mod (especially on + * 32-bit platforms). In essence, we do + * + * uint32_t threshold = (0x100000000ull-bound) % bound; + * + * because this version will calculate the same modulus, but the LHS + * value is less than 2^32. + * + * (Note that using modulo is only wise for good RNGs, poorer RNGs + * such as raw LCGs do better using a technique based on division.) + * Empirical tests show that division is preferable to modulus for + * reducing the range of an RNG. It's faster, and sometimes it can + * even be statistically prefereable. + */ + +/* Generation functions for XSH RS */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsh_rs_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsh_rs_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsh_rs_64_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsh_rs_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsh_rs_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, uint64_t bound); +#endif + +/* Generation functions for XSH RR */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsh_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsh_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsh_rr_64_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsh_rr_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsh_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, uint64_t bound); +#endif + +/* Generation functions for RXS M XS (no MCG versions because they + * don't make sense when you want to use the entire state) + */ + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_oneseq_128_rxs_m_xs_128_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_oneseq_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_unique_128_rxs_m_xs_128_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_unique_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_setseq_128_rxs_m_xs_128_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_setseq_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_setseq_128* rng, + pcg128_t bound); +#endif + +/* Generation functions for RXS M */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_rxs_m_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_rxs_m_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_rxs_m_64_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_rxs_m_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t pcg_mcg_128_rxs_m_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, uint64_t bound); +#endif + +/* Generation functions for XSL RR (only defined for "large" types) */ + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsl_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_oneseq_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsl_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_unique_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsl_rr_64_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_setseq_128_xsl_rr_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsl_rr_64_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline uint64_t +pcg_mcg_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, uint64_t bound); +#endif + +/* Generation functions for XSL RR RR (only defined for "large" types) */ + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_oneseq_128_xsl_rr_rr_128_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_oneseq_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_unique_128_xsl_rr_rr_128_random_r(struct pcg_state_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_unique_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_setseq_128_xsl_rr_rr_128_random_r(struct pcg_state_setseq_128* rng); +#endif + +#if PCG_HAS_128BIT_OPS +extern inline pcg128_t +pcg_setseq_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_setseq_128* rng, + pcg128_t bound); +#endif diff --git a/vendor/pcg/pcg-rngs-64.c b/vendor/pcg/pcg-rngs-64.c new file mode 100644 index 0000000..a22df31 --- /dev/null +++ b/vendor/pcg/pcg-rngs-64.c @@ -0,0 +1,255 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * The contents of this file were mechanically derived from pcg_variants.h + * (every inline function defined there gets a generated extern declaration). + */ + +#include "pcg_variants.h" + +/* Functions to advance the underlying LCG, one version for each size and + * each style. These functions are considered semi-private. There is rarely + * a good reason to call them directly. + */ + +extern inline void pcg_oneseq_64_step_r(struct pcg_state_64* rng); + +extern inline void pcg_oneseq_64_advance_r(struct pcg_state_64* rng, + uint64_t delta); + +extern inline void pcg_mcg_64_step_r(struct pcg_state_64* rng); + +extern inline void pcg_mcg_64_advance_r(struct pcg_state_64* rng, + uint64_t delta); + +extern inline void pcg_unique_64_step_r(struct pcg_state_64* rng); + +extern inline void pcg_unique_64_advance_r(struct pcg_state_64* rng, + uint64_t delta); + +extern inline void pcg_setseq_64_step_r(struct pcg_state_setseq_64* rng); + +extern inline void pcg_setseq_64_advance_r(struct pcg_state_setseq_64* rng, + uint64_t delta); + +/* Functions to seed the RNG state, one version for each size and each + * style. Unlike the step functions, regular users can and should call + * these functions. + */ + +extern inline void pcg_oneseq_64_srandom_r(struct pcg_state_64* rng, + uint64_t initstate); + +extern inline void pcg_mcg_64_srandom_r(struct pcg_state_64* rng, + uint64_t initstate); + +extern inline void pcg_unique_64_srandom_r(struct pcg_state_64* rng, + uint64_t initstate); + +extern inline void pcg_setseq_64_srandom_r(struct pcg_state_setseq_64* rng, + uint64_t initstate, + uint64_t initseq); + +/* Now, finally we create each of the individual generators. We provide + * a random_r function that provides a random number of the appropriate + * type (using the full range of the type) and a boundedrand_r version + * that provides + * + * Implementation notes for boundedrand_r: + * + * To avoid bias, we need to make the range of the RNG a multiple of + * bound, which we do by dropping output less than a threshold. + * Let's consider a 32-bit case... A naive scheme to calculate the + * threshold would be to do + * + * uint32_t threshold = 0x100000000ull % bound; + * + * but 64-bit div/mod is slower than 32-bit div/mod (especially on + * 32-bit platforms). In essence, we do + * + * uint32_t threshold = (0x100000000ull-bound) % bound; + * + * because this version will calculate the same modulus, but the LHS + * value is less than 2^32. + * + * (Note that using modulo is only wise for good RNGs, poorer RNGs + * such as raw LCGs do better using a technique based on division.) + * Empirical tests show that division is preferable to modulus for + * reducing the range of an RNG. It's faster, and sometimes it can + * even be statistically prefereable. + */ + +/* Generation functions for XSH RS */ + +extern inline uint32_t +pcg_oneseq_64_xsh_rs_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_oneseq_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_unique_64_xsh_rs_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_unique_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_setseq_64_xsh_rs_32_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint32_t +pcg_setseq_64_xsh_rs_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound); + +extern inline uint32_t pcg_mcg_64_xsh_rs_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_mcg_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +/* Generation functions for XSH RR */ + +extern inline uint32_t +pcg_oneseq_64_xsh_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_oneseq_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_unique_64_xsh_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_unique_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_setseq_64_xsh_rr_32_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint32_t +pcg_setseq_64_xsh_rr_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound); + +extern inline uint32_t pcg_mcg_64_xsh_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_mcg_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +/* Generation functions for RXS M XS (no MCG versions because they + * don't make sense when you want to use the entire state) + */ + +extern inline uint64_t +pcg_oneseq_64_rxs_m_xs_64_random_r(struct pcg_state_64* rng); + +extern inline uint64_t +pcg_oneseq_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound); + +extern inline uint64_t +pcg_unique_64_rxs_m_xs_64_random_r(struct pcg_state_64* rng); + +extern inline uint64_t +pcg_unique_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound); + +extern inline uint64_t +pcg_setseq_64_rxs_m_xs_64_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint64_t +pcg_setseq_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_setseq_64* rng, + uint64_t bound); + +/* Generation functions for RXS M */ + +extern inline uint32_t +pcg_oneseq_64_rxs_m_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_oneseq_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_unique_64_rxs_m_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_unique_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_setseq_64_rxs_m_32_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint32_t +pcg_setseq_64_rxs_m_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound); + +extern inline uint32_t pcg_mcg_64_rxs_m_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_mcg_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +/* Generation functions for XSL RR (only defined for "large" types) */ + +extern inline uint32_t +pcg_oneseq_64_xsl_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_oneseq_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_unique_64_xsl_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_unique_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +extern inline uint32_t +pcg_setseq_64_xsl_rr_32_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint32_t +pcg_setseq_64_xsl_rr_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound); + +extern inline uint32_t pcg_mcg_64_xsl_rr_32_random_r(struct pcg_state_64* rng); + +extern inline uint32_t +pcg_mcg_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, uint32_t bound); + +/* Generation functions for XSL RR RR (only defined for "large" types) */ + +extern inline uint64_t +pcg_oneseq_64_xsl_rr_rr_64_random_r(struct pcg_state_64* rng); + +extern inline uint64_t +pcg_oneseq_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound); + +extern inline uint64_t +pcg_unique_64_xsl_rr_rr_64_random_r(struct pcg_state_64* rng); + +extern inline uint64_t +pcg_unique_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound); + +extern inline uint64_t +pcg_setseq_64_xsl_rr_rr_64_random_r(struct pcg_state_setseq_64* rng); + +extern inline uint64_t +pcg_setseq_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_setseq_64* rng, + uint64_t bound); diff --git a/vendor/pcg/pcg_variants.h b/vendor/pcg/pcg_variants.h new file mode 100644 index 0000000..25ca2e7 --- /dev/null +++ b/vendor/pcg/pcg_variants.h @@ -0,0 +1,2557 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014-2019 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + +/* + * This code is derived from the canonical C++ PCG implementation, which + * has many additional features and is preferable if you can use C++ in + * your project. + * + * Much of the derivation was performed mechanically. In particular, the + * output functions were generated by compiling the C++ output functions + * into LLVM bitcode and then transforming that using the LLVM C backend + * (from https://github.com/draperlaboratory/llvm-cbe), and then + * postprocessing and hand editing the output. + * + * Much of the remaining code was generated by C-preprocessor metaprogramming. + */ + +#ifndef PCG_VARIANTS_H_INCLUDED +#define PCG_VARIANTS_H_INCLUDED 1 + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4146) /* "unary minus operator applied to unsigned type, result still unsigned" */ +#endif + +#if __SIZEOF_INT128__ + typedef __uint128_t pcg128_t; + #define PCG_128BIT_CONSTANT(high,low) \ + ((((pcg128_t)high) << 64) + low) + #define PCG_HAS_128BIT_OPS 1 +#endif + +/* Checking for !__GNUC_STDC_INLINE__ is a hack to work around a bug in the + * Intel compiler where it defined both __GNUC_GNU_INLINE__ and __GNUC_STDC_INLINE__ + * to 1 when using -std=gnu99. igraph is always compiled with -std=gnu99. + * + * Tested with icc (ICC) 2021.3.0 20210609 on Linux */ +#if __GNUC_GNU_INLINE__ && !__GNUC_STDC_INLINE__ && !defined(__cplusplus) + #error Nonstandard GNU inlining semantics. Compile with -std=c99 or better. + /* We could instead use macros PCG_INLINE and PCG_EXTERN_INLINE + but better to just reject ancient C code. */ +#endif + +#if __cplusplus +extern "C" { +#endif + +/* + * Rotate helper functions. + */ + +inline uint8_t pcg_rotr_8(uint8_t value, unsigned int rot) +{ +/* Unfortunately, clang is kinda pathetic when it comes to properly + * recognizing idiomatic rotate code, so for clang we actually provide + * assembler directives (enabled with PCG_USE_INLINE_ASM). Boo, hiss. + */ +#if PCG_USE_INLINE_ASM && __clang__ && (__x86_64__ || __i386__) + asm ("rorb %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); + return value; +#else + return (value >> rot) | (value << ((- rot) & 7)); +#endif +} + +inline uint16_t pcg_rotr_16(uint16_t value, unsigned int rot) +{ +#if PCG_USE_INLINE_ASM && __clang__ && (__x86_64__ || __i386__) + asm ("rorw %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); + return value; +#else + return (value >> rot) | (value << ((- rot) & 15)); +#endif +} + +inline uint32_t pcg_rotr_32(uint32_t value, unsigned int rot) +{ +#if PCG_USE_INLINE_ASM && __clang__ && (__x86_64__ || __i386__) + asm ("rorl %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); + return value; +#else + return (value >> rot) | (value << ((- rot) & 31)); +#endif +} + +inline uint64_t pcg_rotr_64(uint64_t value, unsigned int rot) +{ +#if 0 && PCG_USE_INLINE_ASM && __clang__ && __x86_64__ + /* For whatever reason, clang actually *does* generate rotq by + itself, so we don't need this code. */ + asm ("rorq %%cl, %0" : "=r" (value) : "0" (value), "c" (rot)); + return value; +#else + return (value >> rot) | (value << ((- rot) & 63)); +#endif +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_rotr_128(pcg128_t value, unsigned int rot) +{ + return (value >> rot) | (value << ((- rot) & 127)); +} +#endif + +/* + * Output functions. These are the core of the PCG generation scheme. + */ + +/* XSH RS */ + +inline uint8_t pcg_output_xsh_rs_16_8(uint16_t state) +{ + return (uint8_t)(((state >> 7u) ^ state) >> ((state >> 14u) + 3u)); +} + +inline uint16_t pcg_output_xsh_rs_32_16(uint32_t state) +{ + return (uint16_t)(((state >> 11u) ^ state) >> ((state >> 30u) + 11u)); +} + +inline uint32_t pcg_output_xsh_rs_64_32(uint64_t state) +{ + + return (uint32_t)(((state >> 22u) ^ state) >> ((state >> 61u) + 22u)); +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_output_xsh_rs_128_64(pcg128_t state) +{ + return (uint64_t)(((state >> 43u) ^ state) >> ((state >> 124u) + 45u)); +} +#endif + +/* XSH RR */ + +inline uint8_t pcg_output_xsh_rr_16_8(uint16_t state) +{ + return pcg_rotr_8(((state >> 5u) ^ state) >> 5u, state >> 13u); +} + +inline uint16_t pcg_output_xsh_rr_32_16(uint32_t state) +{ + return pcg_rotr_16(((state >> 10u) ^ state) >> 12u, state >> 28u); +} + +inline uint32_t pcg_output_xsh_rr_64_32(uint64_t state) +{ + return pcg_rotr_32(((state >> 18u) ^ state) >> 27u, state >> 59u); +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_output_xsh_rr_128_64(pcg128_t state) +{ + return pcg_rotr_64(((state >> 35u) ^ state) >> 58u, state >> 122u); +} +#endif + +/* RXS M XS */ + +inline uint8_t pcg_output_rxs_m_xs_8_8(uint8_t state) +{ + uint8_t word = ((state >> ((state >> 6u) + 2u)) ^ state) * 217u; + return (word >> 6u) ^ word; +} + +inline uint16_t pcg_output_rxs_m_xs_16_16(uint16_t state) +{ + uint16_t word = ((state >> ((state >> 13u) + 3u)) ^ state) * 62169u; + return (word >> 11u) ^ word; +} + +inline uint32_t pcg_output_rxs_m_xs_32_32(uint32_t state) +{ + uint32_t word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +inline uint64_t pcg_output_rxs_m_xs_64_64(uint64_t state) +{ + uint64_t word = ((state >> ((state >> 59u) + 5u)) ^ state) + * 12605985483714917081ull; + return (word >> 43u) ^ word; +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_output_rxs_m_xs_128_128(pcg128_t state) +{ + pcg128_t word = ((state >> ((state >> 122u) + 6u)) ^ state) + * (PCG_128BIT_CONSTANT(17766728186571221404ULL, + 12605985483714917081ULL)); + /* 327738287884841127335028083622016905945 */ + return (word >> 86u) ^ word; +} +#endif + +/* RXS M */ + +inline uint8_t pcg_output_rxs_m_16_8(uint16_t state) +{ + return (((state >> ((state >> 13u) + 3u)) ^ state) * 62169u) >> 8u; +} + +inline uint16_t pcg_output_rxs_m_32_16(uint32_t state) +{ + return (((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u) >> 16u; +} + +inline uint32_t pcg_output_rxs_m_64_32(uint64_t state) +{ + return (((state >> ((state >> 59u) + 5u)) ^ state) + * 12605985483714917081ull) >> 32u; +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_output_rxs_m_128_64(pcg128_t state) +{ + return (((state >> ((state >> 122u) + 6u)) ^ state) + * (PCG_128BIT_CONSTANT(17766728186571221404ULL, + 12605985483714917081ULL))) >> 64u; + /* 327738287884841127335028083622016905945 */ +} +#endif + +/* XSL RR (only defined for >= 64 bits) */ + +inline uint32_t pcg_output_xsl_rr_64_32(uint64_t state) +{ + return pcg_rotr_32(((uint32_t)(state >> 32u)) ^ (uint32_t)state, + state >> 59u); +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_output_xsl_rr_128_64(pcg128_t state) +{ + return pcg_rotr_64(((uint64_t)(state >> 64u)) ^ (uint64_t)state, + state >> 122u); +} +#endif + +/* XSL RR RR (only defined for >= 64 bits) */ + +inline uint64_t pcg_output_xsl_rr_rr_64_64(uint64_t state) +{ + uint32_t rot1 = (uint32_t)(state >> 59u); + uint32_t high = (uint32_t)(state >> 32u); + uint32_t low = (uint32_t)state; + uint32_t xored = high ^ low; + uint32_t newlow = pcg_rotr_32(xored, rot1); + uint32_t newhigh = pcg_rotr_32(high, newlow & 31u); + return (((uint64_t)newhigh) << 32u) | newlow; +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_output_xsl_rr_rr_128_128(pcg128_t state) +{ + uint32_t rot1 = (uint32_t)(state >> 122u); + uint64_t high = (uint64_t)(state >> 64u); + uint64_t low = (uint64_t)state; + uint64_t xored = high ^ low; + uint64_t newlow = pcg_rotr_64(xored, rot1); + uint64_t newhigh = pcg_rotr_64(high, newlow & 63u); + return (((pcg128_t)newhigh) << 64u) | newlow; +} +#endif + +#define PCG_DEFAULT_MULTIPLIER_8 141U +#define PCG_DEFAULT_MULTIPLIER_16 12829U +#define PCG_DEFAULT_MULTIPLIER_32 747796405U +#define PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL + +#define PCG_DEFAULT_INCREMENT_8 77U +#define PCG_DEFAULT_INCREMENT_16 47989U +#define PCG_DEFAULT_INCREMENT_32 2891336453U +#define PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL + +#if PCG_HAS_128BIT_OPS +#define PCG_DEFAULT_MULTIPLIER_128 \ + PCG_128BIT_CONSTANT(2549297995355413924ULL,4865540595714422341ULL) +#define PCG_DEFAULT_INCREMENT_128 \ + PCG_128BIT_CONSTANT(6364136223846793005ULL,1442695040888963407ULL) +#endif + +/* + * Static initialization constants (if you can't call srandom for some + * bizarre reason). + */ + +#define PCG_STATE_ONESEQ_8_INITIALIZER { 0xd7U } +#define PCG_STATE_ONESEQ_16_INITIALIZER { 0x20dfU } +#define PCG_STATE_ONESEQ_32_INITIALIZER { 0x46b56677U } +#define PCG_STATE_ONESEQ_64_INITIALIZER { 0x4d595df4d0f33173ULL } +#if PCG_HAS_128BIT_OPS +#define PCG_STATE_ONESEQ_128_INITIALIZER \ + { PCG_128BIT_CONSTANT(0xb8dc10e158a92392ULL, 0x98046df007ec0a53ULL) } +#endif + +#define PCG_STATE_UNIQUE_8_INITIALIZER PCG_STATE_ONESEQ_8_INITIALIZER +#define PCG_STATE_UNIQUE_16_INITIALIZER PCG_STATE_ONESEQ_16_INITIALIZER +#define PCG_STATE_UNIQUE_32_INITIALIZER PCG_STATE_ONESEQ_32_INITIALIZER +#define PCG_STATE_UNIQUE_64_INITIALIZER PCG_STATE_ONESEQ_64_INITIALIZER +#if PCG_HAS_128BIT_OPS +#define PCG_STATE_UNIQUE_128_INITIALIZER PCG_STATE_ONESEQ_128_INITIALIZER +#endif + +#define PCG_STATE_MCG_8_INITIALIZER { 0xe5U } +#define PCG_STATE_MCG_16_INITIALIZER { 0xa5e5U } +#define PCG_STATE_MCG_32_INITIALIZER { 0xd15ea5e5U } +#define PCG_STATE_MCG_64_INITIALIZER { 0xcafef00dd15ea5e5ULL } +#if PCG_HAS_128BIT_OPS +#define PCG_STATE_MCG_128_INITIALIZER \ + { PCG_128BIT_CONSTANT(0x0000000000000000ULL, 0xcafef00dd15ea5e5ULL) } +#endif + +#define PCG_STATE_SETSEQ_8_INITIALIZER { 0x9bU, 0xdbU } +#define PCG_STATE_SETSEQ_16_INITIALIZER { 0xe39bU, 0x5bdbU } +#define PCG_STATE_SETSEQ_32_INITIALIZER { 0xec02d89bU, 0x94b95bdbU } +#define PCG_STATE_SETSEQ_64_INITIALIZER \ + { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL } +#if PCG_HAS_128BIT_OPS +#define PCG_STATE_SETSEQ_128_INITIALIZER \ + { PCG_128BIT_CONSTANT(0x979c9a98d8462005ULL, 0x7d3e9cb6cfe0549bULL), \ + PCG_128BIT_CONSTANT(0x0000000000000001ULL, 0xda3e39cb94b95bdbULL) } +#endif + +/* Representations for the oneseq, mcg, and unique variants */ + +struct pcg_state_8 { + uint8_t state; +}; + +struct pcg_state_16 { + uint16_t state; +}; + +struct pcg_state_32 { + uint32_t state; +}; + +struct pcg_state_64 { + uint64_t state; +}; + +#if PCG_HAS_128BIT_OPS +struct pcg_state_128 { + pcg128_t state; +}; +#endif + +/* Representations setseq variants */ + +struct pcg_state_setseq_8 { + uint8_t state; + uint8_t inc; +}; + +struct pcg_state_setseq_16 { + uint16_t state; + uint16_t inc; +}; + +struct pcg_state_setseq_32 { + uint32_t state; + uint32_t inc; +}; + +struct pcg_state_setseq_64 { + uint64_t state; + uint64_t inc; +}; + +#if PCG_HAS_128BIT_OPS +struct pcg_state_setseq_128 { + pcg128_t state; + pcg128_t inc; +}; +#endif + +/* Multi-step advance functions (jump-ahead, jump-back) */ + +extern uint8_t pcg_advance_lcg_8(uint8_t state, uint8_t delta, uint8_t cur_mult, + uint8_t cur_plus); +extern uint16_t pcg_advance_lcg_16(uint16_t state, uint16_t delta, + uint16_t cur_mult, uint16_t cur_plus); +extern uint32_t pcg_advance_lcg_32(uint32_t state, uint32_t delta, + uint32_t cur_mult, uint32_t cur_plus); +extern uint64_t pcg_advance_lcg_64(uint64_t state, uint64_t delta, + uint64_t cur_mult, uint64_t cur_plus); + +#if PCG_HAS_128BIT_OPS +extern pcg128_t pcg_advance_lcg_128(pcg128_t state, pcg128_t delta, + pcg128_t cur_mult, pcg128_t cur_plus); +#endif + +/* Functions to advance the underlying LCG, one version for each size and + * each style. These functions are considered semi-private. There is rarely + * a good reason to call them directly. + */ + +inline void pcg_oneseq_8_step_r(struct pcg_state_8* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_8 + + PCG_DEFAULT_INCREMENT_8; +} + +inline void pcg_oneseq_8_advance_r(struct pcg_state_8* rng, uint8_t delta) +{ + rng->state = pcg_advance_lcg_8(rng->state, delta, PCG_DEFAULT_MULTIPLIER_8, + PCG_DEFAULT_INCREMENT_8); +} + +inline void pcg_mcg_8_step_r(struct pcg_state_8* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_8; +} + +inline void pcg_mcg_8_advance_r(struct pcg_state_8* rng, uint8_t delta) +{ + rng->state + = pcg_advance_lcg_8(rng->state, delta, PCG_DEFAULT_MULTIPLIER_8, 0u); +} + +inline void pcg_unique_8_step_r(struct pcg_state_8* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_8 + + (uint8_t)(((intptr_t)rng) | 1u); +} + +inline void pcg_unique_8_advance_r(struct pcg_state_8* rng, uint8_t delta) +{ + rng->state = pcg_advance_lcg_8(rng->state, delta, PCG_DEFAULT_MULTIPLIER_8, + (uint8_t)(((intptr_t)rng) | 1u)); +} + +inline void pcg_setseq_8_step_r(struct pcg_state_setseq_8* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_8 + rng->inc; +} + +inline void pcg_setseq_8_advance_r(struct pcg_state_setseq_8* rng, + uint8_t delta) +{ + rng->state = pcg_advance_lcg_8(rng->state, delta, PCG_DEFAULT_MULTIPLIER_8, + rng->inc); +} + +inline void pcg_oneseq_16_step_r(struct pcg_state_16* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_16 + + PCG_DEFAULT_INCREMENT_16; +} + +inline void pcg_oneseq_16_advance_r(struct pcg_state_16* rng, uint16_t delta) +{ + rng->state = pcg_advance_lcg_16( + rng->state, delta, PCG_DEFAULT_MULTIPLIER_16, PCG_DEFAULT_INCREMENT_16); +} + +inline void pcg_mcg_16_step_r(struct pcg_state_16* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_16; +} + +inline void pcg_mcg_16_advance_r(struct pcg_state_16* rng, uint16_t delta) +{ + rng->state + = pcg_advance_lcg_16(rng->state, delta, PCG_DEFAULT_MULTIPLIER_16, 0u); +} + +inline void pcg_unique_16_step_r(struct pcg_state_16* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_16 + + (uint16_t)(((intptr_t)rng) | 1u); +} + +inline void pcg_unique_16_advance_r(struct pcg_state_16* rng, uint16_t delta) +{ + rng->state + = pcg_advance_lcg_16(rng->state, delta, PCG_DEFAULT_MULTIPLIER_16, + (uint16_t)(((intptr_t)rng) | 1u)); +} + +inline void pcg_setseq_16_step_r(struct pcg_state_setseq_16* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_16 + rng->inc; +} + +inline void pcg_setseq_16_advance_r(struct pcg_state_setseq_16* rng, + uint16_t delta) +{ + rng->state = pcg_advance_lcg_16(rng->state, delta, + PCG_DEFAULT_MULTIPLIER_16, rng->inc); +} + +inline void pcg_oneseq_32_step_r(struct pcg_state_32* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_32 + + PCG_DEFAULT_INCREMENT_32; +} + +inline void pcg_oneseq_32_advance_r(struct pcg_state_32* rng, uint32_t delta) +{ + rng->state = pcg_advance_lcg_32( + rng->state, delta, PCG_DEFAULT_MULTIPLIER_32, PCG_DEFAULT_INCREMENT_32); +} + +inline void pcg_mcg_32_step_r(struct pcg_state_32* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_32; +} + +inline void pcg_mcg_32_advance_r(struct pcg_state_32* rng, uint32_t delta) +{ + rng->state + = pcg_advance_lcg_32(rng->state, delta, PCG_DEFAULT_MULTIPLIER_32, 0u); +} + +inline void pcg_unique_32_step_r(struct pcg_state_32* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_32 + + (uint32_t)(((intptr_t)rng) | 1u); +} + +inline void pcg_unique_32_advance_r(struct pcg_state_32* rng, uint32_t delta) +{ + rng->state + = pcg_advance_lcg_32(rng->state, delta, PCG_DEFAULT_MULTIPLIER_32, + (uint32_t)(((intptr_t)rng) | 1u)); +} + +inline void pcg_setseq_32_step_r(struct pcg_state_setseq_32* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_32 + rng->inc; +} + +inline void pcg_setseq_32_advance_r(struct pcg_state_setseq_32* rng, + uint32_t delta) +{ + rng->state = pcg_advance_lcg_32(rng->state, delta, + PCG_DEFAULT_MULTIPLIER_32, rng->inc); +} + +inline void pcg_oneseq_64_step_r(struct pcg_state_64* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64 + + PCG_DEFAULT_INCREMENT_64; +} + +inline void pcg_oneseq_64_advance_r(struct pcg_state_64* rng, uint64_t delta) +{ + rng->state = pcg_advance_lcg_64( + rng->state, delta, PCG_DEFAULT_MULTIPLIER_64, PCG_DEFAULT_INCREMENT_64); +} + +inline void pcg_mcg_64_step_r(struct pcg_state_64* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64; +} + +inline void pcg_mcg_64_advance_r(struct pcg_state_64* rng, uint64_t delta) +{ + rng->state + = pcg_advance_lcg_64(rng->state, delta, PCG_DEFAULT_MULTIPLIER_64, 0u); +} + +inline void pcg_unique_64_step_r(struct pcg_state_64* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64 + + (uint64_t)(((intptr_t)rng) | 1u); +} + +inline void pcg_unique_64_advance_r(struct pcg_state_64* rng, uint64_t delta) +{ + rng->state + = pcg_advance_lcg_64(rng->state, delta, PCG_DEFAULT_MULTIPLIER_64, + (uint64_t)(((intptr_t)rng) | 1u)); +} + +inline void pcg_setseq_64_step_r(struct pcg_state_setseq_64* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64 + rng->inc; +} + +inline void pcg_setseq_64_advance_r(struct pcg_state_setseq_64* rng, + uint64_t delta) +{ + rng->state = pcg_advance_lcg_64(rng->state, delta, + PCG_DEFAULT_MULTIPLIER_64, rng->inc); +} + +#if PCG_HAS_128BIT_OPS +inline void pcg_oneseq_128_step_r(struct pcg_state_128* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_128 + + PCG_DEFAULT_INCREMENT_128; +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_oneseq_128_advance_r(struct pcg_state_128* rng, pcg128_t delta) +{ + rng->state + = pcg_advance_lcg_128(rng->state, delta, PCG_DEFAULT_MULTIPLIER_128, + PCG_DEFAULT_INCREMENT_128); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_mcg_128_step_r(struct pcg_state_128* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_128; +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_mcg_128_advance_r(struct pcg_state_128* rng, pcg128_t delta) +{ + rng->state = pcg_advance_lcg_128(rng->state, delta, + PCG_DEFAULT_MULTIPLIER_128, 0u); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_unique_128_step_r(struct pcg_state_128* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_128 + + (pcg128_t)(((intptr_t)rng) | 1u); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_unique_128_advance_r(struct pcg_state_128* rng, pcg128_t delta) +{ + rng->state + = pcg_advance_lcg_128(rng->state, delta, PCG_DEFAULT_MULTIPLIER_128, + (pcg128_t)(((intptr_t)rng) | 1u)); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_setseq_128_step_r(struct pcg_state_setseq_128* rng) +{ + rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_128 + rng->inc; +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_setseq_128_advance_r(struct pcg_state_setseq_128* rng, + pcg128_t delta) +{ + rng->state = pcg_advance_lcg_128(rng->state, delta, + PCG_DEFAULT_MULTIPLIER_128, rng->inc); +} +#endif + +/* Functions to seed the RNG state, one version for each size and each + * style. Unlike the step functions, regular users can and should call + * these functions. + */ + +inline void pcg_oneseq_8_srandom_r(struct pcg_state_8* rng, uint8_t initstate) +{ + rng->state = 0U; + pcg_oneseq_8_step_r(rng); + rng->state += initstate; + pcg_oneseq_8_step_r(rng); +} + +inline void pcg_mcg_8_srandom_r(struct pcg_state_8* rng, uint8_t initstate) +{ + rng->state = initstate | 1u; +} + +inline void pcg_unique_8_srandom_r(struct pcg_state_8* rng, uint8_t initstate) +{ + rng->state = 0U; + pcg_unique_8_step_r(rng); + rng->state += initstate; + pcg_unique_8_step_r(rng); +} + +inline void pcg_setseq_8_srandom_r(struct pcg_state_setseq_8* rng, + uint8_t initstate, uint8_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg_setseq_8_step_r(rng); + rng->state += initstate; + pcg_setseq_8_step_r(rng); +} + +inline void pcg_oneseq_16_srandom_r(struct pcg_state_16* rng, + uint16_t initstate) +{ + rng->state = 0U; + pcg_oneseq_16_step_r(rng); + rng->state += initstate; + pcg_oneseq_16_step_r(rng); +} + +inline void pcg_mcg_16_srandom_r(struct pcg_state_16* rng, uint16_t initstate) +{ + rng->state = initstate | 1u; +} + +inline void pcg_unique_16_srandom_r(struct pcg_state_16* rng, + uint16_t initstate) +{ + rng->state = 0U; + pcg_unique_16_step_r(rng); + rng->state += initstate; + pcg_unique_16_step_r(rng); +} + +inline void pcg_setseq_16_srandom_r(struct pcg_state_setseq_16* rng, + uint16_t initstate, uint16_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg_setseq_16_step_r(rng); + rng->state += initstate; + pcg_setseq_16_step_r(rng); +} + +inline void pcg_oneseq_32_srandom_r(struct pcg_state_32* rng, + uint32_t initstate) +{ + rng->state = 0U; + pcg_oneseq_32_step_r(rng); + rng->state += initstate; + pcg_oneseq_32_step_r(rng); +} + +inline void pcg_mcg_32_srandom_r(struct pcg_state_32* rng, uint32_t initstate) +{ + rng->state = initstate | 1u; +} + +inline void pcg_unique_32_srandom_r(struct pcg_state_32* rng, + uint32_t initstate) +{ + rng->state = 0U; + pcg_unique_32_step_r(rng); + rng->state += initstate; + pcg_unique_32_step_r(rng); +} + +inline void pcg_setseq_32_srandom_r(struct pcg_state_setseq_32* rng, + uint32_t initstate, uint32_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg_setseq_32_step_r(rng); + rng->state += initstate; + pcg_setseq_32_step_r(rng); +} + +inline void pcg_oneseq_64_srandom_r(struct pcg_state_64* rng, + uint64_t initstate) +{ + rng->state = 0U; + pcg_oneseq_64_step_r(rng); + rng->state += initstate; + pcg_oneseq_64_step_r(rng); +} + +inline void pcg_mcg_64_srandom_r(struct pcg_state_64* rng, uint64_t initstate) +{ + rng->state = initstate | 1u; +} + +inline void pcg_unique_64_srandom_r(struct pcg_state_64* rng, + uint64_t initstate) +{ + rng->state = 0U; + pcg_unique_64_step_r(rng); + rng->state += initstate; + pcg_unique_64_step_r(rng); +} + +inline void pcg_setseq_64_srandom_r(struct pcg_state_setseq_64* rng, + uint64_t initstate, uint64_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg_setseq_64_step_r(rng); + rng->state += initstate; + pcg_setseq_64_step_r(rng); +} + +#if PCG_HAS_128BIT_OPS +inline void pcg_oneseq_128_srandom_r(struct pcg_state_128* rng, + pcg128_t initstate) +{ + rng->state = 0U; + pcg_oneseq_128_step_r(rng); + rng->state += initstate; + pcg_oneseq_128_step_r(rng); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_mcg_128_srandom_r(struct pcg_state_128* rng, pcg128_t initstate) +{ + rng->state = initstate | 1u; +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_unique_128_srandom_r(struct pcg_state_128* rng, + pcg128_t initstate) +{ + rng->state = 0U; + pcg_unique_128_step_r(rng); + rng->state += initstate; + pcg_unique_128_step_r(rng); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline void pcg_setseq_128_srandom_r(struct pcg_state_setseq_128* rng, + pcg128_t initstate, pcg128_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg_setseq_128_step_r(rng); + rng->state += initstate; + pcg_setseq_128_step_r(rng); +} +#endif + +/* Now, finally we create each of the individual generators. We provide + * a random_r function that provides a random number of the appropriate + * type (using the full range of the type) and a boundedrand_r version + * that provides + * + * Implementation notes for boundedrand_r: + * + * To avoid bias, we need to make the range of the RNG a multiple of + * bound, which we do by dropping output less than a threshold. + * Let's consider a 32-bit case... A naive scheme to calculate the + * threshold would be to do + * + * uint32_t threshold = 0x100000000ull % bound; + * + * but 64-bit div/mod is slower than 32-bit div/mod (especially on + * 32-bit platforms). In essence, we do + * + * uint32_t threshold = (0x100000000ull-bound) % bound; + * + * because this version will calculate the same modulus, but the LHS + * value is less than 2^32. + * + * (Note that using modulo is only wise for good RNGs, poorer RNGs + * such as raw LCGs do better using a technique based on division.) + * Empricical tests show that division is preferable to modulus for + * reducting the range of an RNG. It's faster, and sometimes it can + * even be statistically prefereable. + */ + +/* Generation functions for XSH RS */ + +inline uint8_t pcg_oneseq_16_xsh_rs_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_oneseq_16_step_r(rng); + return pcg_output_xsh_rs_16_8(oldstate); +} + +inline uint8_t pcg_oneseq_16_xsh_rs_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_oneseq_16_xsh_rs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_oneseq_32_xsh_rs_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_oneseq_32_step_r(rng); + return pcg_output_xsh_rs_32_16(oldstate); +} + +inline uint16_t pcg_oneseq_32_xsh_rs_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_oneseq_32_xsh_rs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_oneseq_64_xsh_rs_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_xsh_rs_64_32(oldstate); +} + +inline uint32_t pcg_oneseq_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_oneseq_64_xsh_rs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_oneseq_128_xsh_rs_64_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_xsh_rs_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_oneseq_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_128_xsh_rs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_unique_16_xsh_rs_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_unique_16_step_r(rng); + return pcg_output_xsh_rs_16_8(oldstate); +} + +inline uint8_t pcg_unique_16_xsh_rs_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_unique_16_xsh_rs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_unique_32_xsh_rs_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_unique_32_step_r(rng); + return pcg_output_xsh_rs_32_16(oldstate); +} + +inline uint16_t pcg_unique_32_xsh_rs_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_unique_32_xsh_rs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_unique_64_xsh_rs_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_xsh_rs_64_32(oldstate); +} + +inline uint32_t pcg_unique_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_unique_64_xsh_rs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_unique_128_xsh_rs_64_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_xsh_rs_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_unique_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_128_xsh_rs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_setseq_16_xsh_rs_8_random_r(struct pcg_state_setseq_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_setseq_16_step_r(rng); + return pcg_output_xsh_rs_16_8(oldstate); +} + +inline uint8_t +pcg_setseq_16_xsh_rs_8_boundedrand_r(struct pcg_state_setseq_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_setseq_16_xsh_rs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t +pcg_setseq_32_xsh_rs_16_random_r(struct pcg_state_setseq_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_setseq_32_step_r(rng); + return pcg_output_xsh_rs_32_16(oldstate); +} + +inline uint16_t +pcg_setseq_32_xsh_rs_16_boundedrand_r(struct pcg_state_setseq_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_setseq_32_xsh_rs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t +pcg_setseq_64_xsh_rs_32_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_xsh_rs_64_32(oldstate); +} + +inline uint32_t +pcg_setseq_64_xsh_rs_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_setseq_64_xsh_rs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsh_rs_64_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_xsh_rs_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsh_rs_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_128_xsh_rs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_mcg_16_xsh_rs_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_mcg_16_step_r(rng); + return pcg_output_xsh_rs_16_8(oldstate); +} + +inline uint8_t pcg_mcg_16_xsh_rs_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_mcg_16_xsh_rs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_mcg_32_xsh_rs_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_mcg_32_step_r(rng); + return pcg_output_xsh_rs_32_16(oldstate); +} + +inline uint16_t pcg_mcg_32_xsh_rs_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_mcg_32_xsh_rs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_mcg_64_xsh_rs_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_mcg_64_step_r(rng); + return pcg_output_xsh_rs_64_32(oldstate); +} + +inline uint32_t pcg_mcg_64_xsh_rs_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_mcg_64_xsh_rs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsh_rs_64_random_r(struct pcg_state_128* rng) +{ + pcg_mcg_128_step_r(rng); + return pcg_output_xsh_rs_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsh_rs_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_mcg_128_xsh_rs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/* Generation functions for XSH RR */ + +inline uint8_t pcg_oneseq_16_xsh_rr_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_oneseq_16_step_r(rng); + return pcg_output_xsh_rr_16_8(oldstate); +} + +inline uint8_t pcg_oneseq_16_xsh_rr_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_oneseq_16_xsh_rr_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_oneseq_32_xsh_rr_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_oneseq_32_step_r(rng); + return pcg_output_xsh_rr_32_16(oldstate); +} + +inline uint16_t pcg_oneseq_32_xsh_rr_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_oneseq_32_xsh_rr_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_oneseq_64_xsh_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_xsh_rr_64_32(oldstate); +} + +inline uint32_t pcg_oneseq_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_oneseq_64_xsh_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_oneseq_128_xsh_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_xsh_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_oneseq_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_128_xsh_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_unique_16_xsh_rr_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_unique_16_step_r(rng); + return pcg_output_xsh_rr_16_8(oldstate); +} + +inline uint8_t pcg_unique_16_xsh_rr_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_unique_16_xsh_rr_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_unique_32_xsh_rr_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_unique_32_step_r(rng); + return pcg_output_xsh_rr_32_16(oldstate); +} + +inline uint16_t pcg_unique_32_xsh_rr_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_unique_32_xsh_rr_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_unique_64_xsh_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_xsh_rr_64_32(oldstate); +} + +inline uint32_t pcg_unique_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_unique_64_xsh_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_unique_128_xsh_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_xsh_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_unique_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_128_xsh_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_setseq_16_xsh_rr_8_random_r(struct pcg_state_setseq_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_setseq_16_step_r(rng); + return pcg_output_xsh_rr_16_8(oldstate); +} + +inline uint8_t +pcg_setseq_16_xsh_rr_8_boundedrand_r(struct pcg_state_setseq_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_setseq_16_xsh_rr_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t +pcg_setseq_32_xsh_rr_16_random_r(struct pcg_state_setseq_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_setseq_32_step_r(rng); + return pcg_output_xsh_rr_32_16(oldstate); +} + +inline uint16_t +pcg_setseq_32_xsh_rr_16_boundedrand_r(struct pcg_state_setseq_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_setseq_32_xsh_rr_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t +pcg_setseq_64_xsh_rr_32_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_xsh_rr_64_32(oldstate); +} + +inline uint32_t +pcg_setseq_64_xsh_rr_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_setseq_64_xsh_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsh_rr_64_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_xsh_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsh_rr_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_128_xsh_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_mcg_16_xsh_rr_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_mcg_16_step_r(rng); + return pcg_output_xsh_rr_16_8(oldstate); +} + +inline uint8_t pcg_mcg_16_xsh_rr_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_mcg_16_xsh_rr_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_mcg_32_xsh_rr_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_mcg_32_step_r(rng); + return pcg_output_xsh_rr_32_16(oldstate); +} + +inline uint16_t pcg_mcg_32_xsh_rr_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_mcg_32_xsh_rr_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_mcg_64_xsh_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_mcg_64_step_r(rng); + return pcg_output_xsh_rr_64_32(oldstate); +} + +inline uint32_t pcg_mcg_64_xsh_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_mcg_64_xsh_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsh_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_mcg_128_step_r(rng); + return pcg_output_xsh_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsh_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_mcg_128_xsh_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/* Generation functions for RXS M XS (no MCG versions because they + * don't make sense when you want to use the entire state) + */ + +inline uint8_t pcg_oneseq_8_rxs_m_xs_8_random_r(struct pcg_state_8* rng) +{ + uint8_t oldstate = rng->state; + pcg_oneseq_8_step_r(rng); + return pcg_output_rxs_m_xs_8_8(oldstate); +} + +inline uint8_t pcg_oneseq_8_rxs_m_xs_8_boundedrand_r(struct pcg_state_8* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_oneseq_8_rxs_m_xs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_oneseq_16_rxs_m_xs_16_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_oneseq_16_step_r(rng); + return pcg_output_rxs_m_xs_16_16(oldstate); +} + +inline uint16_t +pcg_oneseq_16_rxs_m_xs_16_boundedrand_r(struct pcg_state_16* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_oneseq_16_rxs_m_xs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_oneseq_32_rxs_m_xs_32_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_oneseq_32_step_r(rng); + return pcg_output_rxs_m_xs_32_32(oldstate); +} + +inline uint32_t +pcg_oneseq_32_rxs_m_xs_32_boundedrand_r(struct pcg_state_32* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_oneseq_32_rxs_m_xs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint64_t pcg_oneseq_64_rxs_m_xs_64_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_rxs_m_xs_64_64(oldstate); +} + +inline uint64_t +pcg_oneseq_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_64_rxs_m_xs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_oneseq_128_rxs_m_xs_128_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_rxs_m_xs_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_oneseq_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_oneseq_128_rxs_m_xs_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint16_t pcg_unique_16_rxs_m_xs_16_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_unique_16_step_r(rng); + return pcg_output_rxs_m_xs_16_16(oldstate); +} + +inline uint16_t +pcg_unique_16_rxs_m_xs_16_boundedrand_r(struct pcg_state_16* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_unique_16_rxs_m_xs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_unique_32_rxs_m_xs_32_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_unique_32_step_r(rng); + return pcg_output_rxs_m_xs_32_32(oldstate); +} + +inline uint32_t +pcg_unique_32_rxs_m_xs_32_boundedrand_r(struct pcg_state_32* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_unique_32_rxs_m_xs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint64_t pcg_unique_64_rxs_m_xs_64_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_rxs_m_xs_64_64(oldstate); +} + +inline uint64_t +pcg_unique_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_64_rxs_m_xs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_unique_128_rxs_m_xs_128_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_rxs_m_xs_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_unique_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_unique_128_rxs_m_xs_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_setseq_8_rxs_m_xs_8_random_r(struct pcg_state_setseq_8* rng) +{ + uint8_t oldstate = rng->state; + pcg_setseq_8_step_r(rng); + return pcg_output_rxs_m_xs_8_8(oldstate); +} + +inline uint8_t +pcg_setseq_8_rxs_m_xs_8_boundedrand_r(struct pcg_state_setseq_8* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_setseq_8_rxs_m_xs_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t +pcg_setseq_16_rxs_m_xs_16_random_r(struct pcg_state_setseq_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_setseq_16_step_r(rng); + return pcg_output_rxs_m_xs_16_16(oldstate); +} + +inline uint16_t +pcg_setseq_16_rxs_m_xs_16_boundedrand_r(struct pcg_state_setseq_16* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_setseq_16_rxs_m_xs_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t +pcg_setseq_32_rxs_m_xs_32_random_r(struct pcg_state_setseq_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_setseq_32_step_r(rng); + return pcg_output_rxs_m_xs_32_32(oldstate); +} + +inline uint32_t +pcg_setseq_32_rxs_m_xs_32_boundedrand_r(struct pcg_state_setseq_32* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_setseq_32_rxs_m_xs_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint64_t +pcg_setseq_64_rxs_m_xs_64_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_rxs_m_xs_64_64(oldstate); +} + +inline uint64_t +pcg_setseq_64_rxs_m_xs_64_boundedrand_r(struct pcg_state_setseq_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_64_rxs_m_xs_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_setseq_128_rxs_m_xs_128_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_rxs_m_xs_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_setseq_128_rxs_m_xs_128_boundedrand_r(struct pcg_state_setseq_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_setseq_128_rxs_m_xs_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/* Generation functions for RXS M */ + +inline uint8_t pcg_oneseq_16_rxs_m_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_oneseq_16_step_r(rng); + return pcg_output_rxs_m_16_8(oldstate); +} + +inline uint8_t pcg_oneseq_16_rxs_m_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_oneseq_16_rxs_m_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_oneseq_32_rxs_m_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_oneseq_32_step_r(rng); + return pcg_output_rxs_m_32_16(oldstate); +} + +inline uint16_t pcg_oneseq_32_rxs_m_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_oneseq_32_rxs_m_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_oneseq_64_rxs_m_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_rxs_m_64_32(oldstate); +} + +inline uint32_t pcg_oneseq_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_oneseq_64_rxs_m_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_oneseq_128_rxs_m_64_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_rxs_m_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_oneseq_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_128_rxs_m_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_unique_16_rxs_m_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_unique_16_step_r(rng); + return pcg_output_rxs_m_16_8(oldstate); +} + +inline uint8_t pcg_unique_16_rxs_m_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_unique_16_rxs_m_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_unique_32_rxs_m_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_unique_32_step_r(rng); + return pcg_output_rxs_m_32_16(oldstate); +} + +inline uint16_t pcg_unique_32_rxs_m_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_unique_32_rxs_m_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_unique_64_rxs_m_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_rxs_m_64_32(oldstate); +} + +inline uint32_t pcg_unique_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_unique_64_rxs_m_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_unique_128_rxs_m_64_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_rxs_m_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_unique_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_128_rxs_m_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_setseq_16_rxs_m_8_random_r(struct pcg_state_setseq_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_setseq_16_step_r(rng); + return pcg_output_rxs_m_16_8(oldstate); +} + +inline uint8_t +pcg_setseq_16_rxs_m_8_boundedrand_r(struct pcg_state_setseq_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_setseq_16_rxs_m_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_setseq_32_rxs_m_16_random_r(struct pcg_state_setseq_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_setseq_32_step_r(rng); + return pcg_output_rxs_m_32_16(oldstate); +} + +inline uint16_t +pcg_setseq_32_rxs_m_16_boundedrand_r(struct pcg_state_setseq_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_setseq_32_rxs_m_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_setseq_64_rxs_m_32_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_rxs_m_64_32(oldstate); +} + +inline uint32_t +pcg_setseq_64_rxs_m_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_setseq_64_rxs_m_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_rxs_m_64_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_rxs_m_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_rxs_m_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_128_rxs_m_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint8_t pcg_mcg_16_rxs_m_8_random_r(struct pcg_state_16* rng) +{ + uint16_t oldstate = rng->state; + pcg_mcg_16_step_r(rng); + return pcg_output_rxs_m_16_8(oldstate); +} + +inline uint8_t pcg_mcg_16_rxs_m_8_boundedrand_r(struct pcg_state_16* rng, + uint8_t bound) +{ + uint8_t threshold = ((uint8_t)(-bound)) % bound; + for (;;) { + uint8_t r = pcg_mcg_16_rxs_m_8_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint16_t pcg_mcg_32_rxs_m_16_random_r(struct pcg_state_32* rng) +{ + uint32_t oldstate = rng->state; + pcg_mcg_32_step_r(rng); + return pcg_output_rxs_m_32_16(oldstate); +} + +inline uint16_t pcg_mcg_32_rxs_m_16_boundedrand_r(struct pcg_state_32* rng, + uint16_t bound) +{ + uint16_t threshold = ((uint16_t)(-bound)) % bound; + for (;;) { + uint16_t r = pcg_mcg_32_rxs_m_16_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +inline uint32_t pcg_mcg_64_rxs_m_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_mcg_64_step_r(rng); + return pcg_output_rxs_m_64_32(oldstate); +} + +inline uint32_t pcg_mcg_64_rxs_m_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_mcg_64_rxs_m_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_rxs_m_64_random_r(struct pcg_state_128* rng) +{ + pcg_mcg_128_step_r(rng); + return pcg_output_rxs_m_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_rxs_m_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_mcg_128_rxs_m_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/* Generation functions for XSL RR (only defined for "large" types) */ + +inline uint32_t pcg_oneseq_64_xsl_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_xsl_rr_64_32(oldstate); +} + +inline uint32_t pcg_oneseq_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_oneseq_64_xsl_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_oneseq_128_xsl_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_xsl_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_oneseq_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_128_xsl_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint32_t pcg_unique_64_xsl_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_xsl_rr_64_32(oldstate); +} + +inline uint32_t pcg_unique_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_unique_64_xsl_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_unique_128_xsl_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_xsl_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_unique_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_128_xsl_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint32_t +pcg_setseq_64_xsl_rr_32_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_xsl_rr_64_32(oldstate); +} + +inline uint32_t +pcg_setseq_64_xsl_rr_32_boundedrand_r(struct pcg_state_setseq_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_setseq_64_xsl_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsl_rr_64_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_xsl_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t +pcg_setseq_128_xsl_rr_64_boundedrand_r(struct pcg_state_setseq_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_128_xsl_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint32_t pcg_mcg_64_xsl_rr_32_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_mcg_64_step_r(rng); + return pcg_output_xsl_rr_64_32(oldstate); +} + +inline uint32_t pcg_mcg_64_xsl_rr_32_boundedrand_r(struct pcg_state_64* rng, + uint32_t bound) +{ + uint32_t threshold = -bound % bound; + for (;;) { + uint32_t r = pcg_mcg_64_xsl_rr_32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsl_rr_64_random_r(struct pcg_state_128* rng) +{ + pcg_mcg_128_step_r(rng); + return pcg_output_xsl_rr_128_64(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline uint64_t pcg_mcg_128_xsl_rr_64_boundedrand_r(struct pcg_state_128* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_mcg_128_xsl_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/* Generation functions for XSL RR RR (only defined for "large" types) */ + +inline uint64_t pcg_oneseq_64_xsl_rr_rr_64_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_oneseq_64_step_r(rng); + return pcg_output_xsl_rr_rr_64_64(oldstate); +} + +inline uint64_t +pcg_oneseq_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_oneseq_64_xsl_rr_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_oneseq_128_xsl_rr_rr_128_random_r(struct pcg_state_128* rng) +{ + pcg_oneseq_128_step_r(rng); + return pcg_output_xsl_rr_rr_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_oneseq_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_oneseq_128_xsl_rr_rr_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint64_t pcg_unique_64_xsl_rr_rr_64_random_r(struct pcg_state_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_unique_64_step_r(rng); + return pcg_output_xsl_rr_rr_64_64(oldstate); +} + +inline uint64_t +pcg_unique_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_unique_64_xsl_rr_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t pcg_unique_128_xsl_rr_rr_128_random_r(struct pcg_state_128* rng) +{ + pcg_unique_128_step_r(rng); + return pcg_output_xsl_rr_rr_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_unique_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_unique_128_xsl_rr_rr_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +inline uint64_t +pcg_setseq_64_xsl_rr_rr_64_random_r(struct pcg_state_setseq_64* rng) +{ + uint64_t oldstate = rng->state; + pcg_setseq_64_step_r(rng); + return pcg_output_xsl_rr_rr_64_64(oldstate); +} + +inline uint64_t +pcg_setseq_64_xsl_rr_rr_64_boundedrand_r(struct pcg_state_setseq_64* rng, + uint64_t bound) +{ + uint64_t threshold = -bound % bound; + for (;;) { + uint64_t r = pcg_setseq_64_xsl_rr_rr_64_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_setseq_128_xsl_rr_rr_128_random_r(struct pcg_state_setseq_128* rng) +{ + pcg_setseq_128_step_r(rng); + return pcg_output_xsl_rr_rr_128_128(rng->state); +} +#endif + +#if PCG_HAS_128BIT_OPS +inline pcg128_t +pcg_setseq_128_xsl_rr_rr_128_boundedrand_r(struct pcg_state_setseq_128* rng, + pcg128_t bound) +{ + pcg128_t threshold = -bound % bound; + for (;;) { + pcg128_t r = pcg_setseq_128_xsl_rr_rr_128_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif + +/*** Typedefs */ +typedef struct pcg_state_setseq_64 pcg32_random_t; +typedef struct pcg_state_64 pcg32s_random_t; +typedef struct pcg_state_64 pcg32u_random_t; +typedef struct pcg_state_64 pcg32f_random_t; +/*** random_r */ +#define pcg32_random_r pcg_setseq_64_xsh_rr_32_random_r +#define pcg32s_random_r pcg_oneseq_64_xsh_rr_32_random_r +#define pcg32u_random_r pcg_unique_64_xsh_rr_32_random_r +#define pcg32f_random_r pcg_mcg_64_xsh_rs_32_random_r +/*** boundedrand_r */ +#define pcg32_boundedrand_r pcg_setseq_64_xsh_rr_32_boundedrand_r +#define pcg32s_boundedrand_r pcg_oneseq_64_xsh_rr_32_boundedrand_r +#define pcg32u_boundedrand_r pcg_unique_64_xsh_rr_32_boundedrand_r +#define pcg32f_boundedrand_r pcg_mcg_64_xsh_rs_32_boundedrand_r +/*** srandom_r */ +#define pcg32_srandom_r pcg_setseq_64_srandom_r +#define pcg32s_srandom_r pcg_oneseq_64_srandom_r +#define pcg32u_srandom_r pcg_unique_64_srandom_r +#define pcg32f_srandom_r pcg_mcg_64_srandom_r +/*** advance_r */ +#define pcg32_advance_r pcg_setseq_64_advance_r +#define pcg32s_advance_r pcg_oneseq_64_advance_r +#define pcg32u_advance_r pcg_unique_64_advance_r +#define pcg32f_advance_r pcg_mcg_64_advance_r + +#if PCG_HAS_128BIT_OPS +/*** Typedefs */ +typedef struct pcg_state_setseq_128 pcg64_random_t; +typedef struct pcg_state_128 pcg64s_random_t; +typedef struct pcg_state_128 pcg64u_random_t; +typedef struct pcg_state_128 pcg64f_random_t; +/*** random_r */ +#define pcg64_random_r pcg_setseq_128_xsl_rr_64_random_r +#define pcg64s_random_r pcg_oneseq_128_xsl_rr_64_random_r +#define pcg64u_random_r pcg_unique_128_xsl_rr_64_random_r +#define pcg64f_random_r pcg_mcg_128_xsl_rr_64_random_r +/*** boundedrand_r */ +#define pcg64_boundedrand_r pcg_setseq_128_xsl_rr_64_boundedrand_r +#define pcg64s_boundedrand_r pcg_oneseq_128_xsl_rr_64_boundedrand_r +#define pcg64u_boundedrand_r pcg_unique_128_xsl_rr_64_boundedrand_r +#define pcg64f_boundedrand_r pcg_mcg_128_xsl_rr_64_boundedrand_r +/*** srandom_r */ +#define pcg64_srandom_r pcg_setseq_128_srandom_r +#define pcg64s_srandom_r pcg_oneseq_128_srandom_r +#define pcg64u_srandom_r pcg_unique_128_srandom_r +#define pcg64f_srandom_r pcg_mcg_128_srandom_r +/*** advance_r */ +#define pcg64_advance_r pcg_setseq_128_advance_r +#define pcg64s_advance_r pcg_oneseq_128_advance_r +#define pcg64u_advance_r pcg_unique_128_advance_r +#define pcg64f_advance_r pcg_mcg_128_advance_r +#endif + +/*** Typedefs */ +typedef struct pcg_state_8 pcg8si_random_t; +typedef struct pcg_state_16 pcg16si_random_t; +typedef struct pcg_state_32 pcg32si_random_t; +typedef struct pcg_state_64 pcg64si_random_t; +/*** random_r */ +#define pcg8si_random_r pcg_oneseq_8_rxs_m_xs_8_random_r +#define pcg16si_random_r pcg_oneseq_16_rxs_m_xs_16_random_r +#define pcg32si_random_r pcg_oneseq_32_rxs_m_xs_32_random_r +#define pcg64si_random_r pcg_oneseq_64_rxs_m_xs_64_random_r +/*** boundedrand_r */ +#define pcg8si_boundedrand_r pcg_oneseq_8_rxs_m_xs_8_boundedrand_r +#define pcg16si_boundedrand_r pcg_oneseq_16_rxs_m_xs_16_boundedrand_r +#define pcg32si_boundedrand_r pcg_oneseq_32_rxs_m_xs_32_boundedrand_r +#define pcg64si_boundedrand_r pcg_oneseq_64_rxs_m_xs_64_boundedrand_r +/*** srandom_r */ +#define pcg8si_srandom_r pcg_oneseq_8_srandom_r +#define pcg16si_srandom_r pcg_oneseq_16_srandom_r +#define pcg32si_srandom_r pcg_oneseq_32_srandom_r +#define pcg64si_srandom_r pcg_oneseq_64_srandom_r +/*** advance_r */ +#define pcg8si_advance_r pcg_oneseq_8_advance_r +#define pcg16si_advance_r pcg_oneseq_16_advance_r +#define pcg32si_advance_r pcg_oneseq_32_advance_r +#define pcg64si_advance_r pcg_oneseq_64_advance_r + +#if PCG_HAS_128BIT_OPS +typedef struct pcg_state_128 pcg128si_random_t; +#define pcg128si_random_r pcg_oneseq_128_rxs_m_xs_128_random_r +#define pcg128si_boundedrand_r pcg_oneseq_128_rxs_m_xs_128_boundedrand_r +#define pcg128si_srandom_r pcg_oneseq_128_srandom_r +#define pcg128si_advance_r pcg_oneseq_128_advance_r +#endif + +/*** Typedefs */ +typedef struct pcg_state_setseq_8 pcg8i_random_t; +typedef struct pcg_state_setseq_16 pcg16i_random_t; +typedef struct pcg_state_setseq_32 pcg32i_random_t; +typedef struct pcg_state_setseq_64 pcg64i_random_t; +/*** random_r */ +#define pcg8i_random_r pcg_setseq_8_rxs_m_xs_8_random_r +#define pcg16i_random_r pcg_setseq_16_rxs_m_xs_16_random_r +#define pcg32i_random_r pcg_setseq_32_rxs_m_xs_32_random_r +#define pcg64i_random_r pcg_setseq_64_rxs_m_xs_64_random_r +/*** boundedrand_r */ +#define pcg8i_boundedrand_r pcg_setseq_8_rxs_m_xs_8_boundedrand_r +#define pcg16i_boundedrand_r pcg_setseq_16_rxs_m_xs_16_boundedrand_r +#define pcg32i_boundedrand_r pcg_setseq_32_rxs_m_xs_32_boundedrand_r +#define pcg64i_boundedrand_r pcg_setseq_64_rxs_m_xs_64_boundedrand_r +/*** srandom_r */ +#define pcg8i_srandom_r pcg_setseq_8_srandom_r +#define pcg16i_srandom_r pcg_setseq_16_srandom_r +#define pcg32i_srandom_r pcg_setseq_32_srandom_r +#define pcg64i_srandom_r pcg_setseq_64_srandom_r +/*** advance_r */ +#define pcg8i_advance_r pcg_setseq_8_advance_r +#define pcg16i_advance_r pcg_setseq_16_advance_r +#define pcg32i_advance_r pcg_setseq_32_advance_r +#define pcg64i_advance_r pcg_setseq_64_advance_r + +#if PCG_HAS_128BIT_OPS +typedef struct pcg_state_setseq_128 pcg128i_random_t; +#define pcg128i_random_r pcg_setseq_128_rxs_m_xs_128_random_r +#define pcg128i_boundedrand_r pcg_setseq_128_rxs_m_xs_128_boundedrand_r +#define pcg128i_srandom_r pcg_setseq_128_srandom_r +#define pcg128i_advance_r pcg_setseq_128_advance_r +#endif + +extern uint32_t pcg32_random(void); +extern uint32_t pcg32_boundedrand(uint32_t bound); +extern void pcg32_srandom(uint64_t seed, uint64_t seq); +extern void pcg32_advance(uint64_t delta); + +#if PCG_HAS_128BIT_OPS +extern uint64_t pcg64_random(void); +extern uint64_t pcg64_boundedrand(uint64_t bound); +extern void pcg64_srandom(pcg128_t seed, pcg128_t seq); +extern void pcg64_advance(pcg128_t delta); +#endif + +/* + * Static initialization constants (if you can't call srandom for some + * bizarre reason). + */ + +#define PCG32_INITIALIZER PCG_STATE_SETSEQ_64_INITIALIZER +#define PCG32U_INITIALIZER PCG_STATE_UNIQUE_64_INITIALIZER +#define PCG32S_INITIALIZER PCG_STATE_ONESEQ_64_INITIALIZER +#define PCG32F_INITIALIZER PCG_STATE_MCG_64_INITIALIZER + +#if PCG_HAS_128BIT_OPS +#define PCG64_INITIALIZER PCG_STATE_SETSEQ_128_INITIALIZER +#define PCG64U_INITIALIZER PCG_STATE_UNIQUE_128_INITIALIZER +#define PCG64S_INITIALIZER PCG_STATE_ONESEQ_128_INITIALIZER +#define PCG64F_INITIALIZER PCG_STATE_MCG_128_INITIALIZER +#endif + +#define PCG8SI_INITIALIZER PCG_STATE_ONESEQ_8_INITIALIZER +#define PCG16SI_INITIALIZER PCG_STATE_ONESEQ_16_INITIALIZER +#define PCG32SI_INITIALIZER PCG_STATE_ONESEQ_32_INITIALIZER +#define PCG64SI_INITIALIZER PCG_STATE_ONESEQ_64_INITIALIZER +#if PCG_HAS_128BIT_OPS +#define PCG128SI_INITIALIZER PCG_STATE_ONESEQ_128_INITIALIZER +#endif + +#define PCG8I_INITIALIZER PCG_STATE_SETSEQ_8_INITIALIZER +#define PCG16I_INITIALIZER PCG_STATE_SETSEQ_16_INITIALIZER +#define PCG32I_INITIALIZER PCG_STATE_SETSEQ_32_INITIALIZER +#define PCG64I_INITIALIZER PCG_STATE_SETSEQ_64_INITIALIZER +#if PCG_HAS_128BIT_OPS +#define PCG128I_INITIALIZER PCG_STATE_SETSEQ_128_INITIALIZER +#endif + +#if __cplusplus +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* PCG_VARIANTS_H_INCLUDED */ diff --git a/vendor/qhull/CHANGES.md b/vendor/qhull/CHANGES.md new file mode 100644 index 0000000..9172439 --- /dev/null +++ b/vendor/qhull/CHANGES.md @@ -0,0 +1,12 @@ +Modifying from https://github.com/qhull/qhull on commit d1c2fc0caa5f644f3a0f220290d4a868c68ed4f6 + +Changed `userprintf_r.c` to not print when passed a null pointer as file handle. + +Changed `usermem_r.c` to not reference `exit()`. Even though this `exit()` is never called, it is necessary to keep igraph free of any references to it to satisfy CRAN requirements and comply with Debian guidelines. + +Changed the following settings in `user_r.h`: + + - `#define qh_KEEPstatistics 0`, do not incude statistics gathering code + - `#define qh_NOtrace`, do not include tracing code + +Patched with https://github.com/qhull/qhull/pull/165. diff --git a/vendor/qhull/CMakeLists.txt b/vendor/qhull/CMakeLists.txt new file mode 100644 index 0000000..2e7cf5e --- /dev/null +++ b/vendor/qhull/CMakeLists.txt @@ -0,0 +1,48 @@ +# Declare the files needed to compile our vendored plfit copy +add_library( + qhull_vendored + OBJECT + EXCLUDE_FROM_ALL + libqhull_r/accessors_r.c + libqhull_r/geom_r.c + libqhull_r/geom2_r.c + libqhull_r/global_r.c + libqhull_r/io_r.c + libqhull_r/libqhull_r.c + libqhull_r/mem_r.c + libqhull_r/merge_r.c + libqhull_r/poly_r.c + libqhull_r/poly2_r.c + libqhull_r/qset_r.c + libqhull_r/random_r.c + libqhull_r/rboxlib_r.c + libqhull_r/stat_r.c + libqhull_r/user_r.c + libqhull_r/usermem_r.c + libqhull_r/userprintf_r.c + libqhull_r/userprintf_rbox_r.c +) + +target_include_directories( + qhull_vendored + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + #PUBLIC + #${CMAKE_CURRENT_SOURCE_DIR} +) + +if (BUILD_SHARED_LIBS) + set_property(TARGET qhull_vendored PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + +# Since these are included as object files, they should call the +# function as is (without visibility specification) +target_compile_definitions(qhull_vendored PRIVATE IGRAPH_STATIC) + +use_all_warnings(qhull_vendored) + +target_compile_options( + qhull_vendored PRIVATE + $<$:-Wno-unused-variable> +) \ No newline at end of file diff --git a/vendor/qhull/COPYING.txt b/vendor/qhull/COPYING.txt new file mode 100644 index 0000000..122a00a --- /dev/null +++ b/vendor/qhull/COPYING.txt @@ -0,0 +1,39 @@ + Qhull, Copyright (c) 1993-2020 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Files derived from Qhull 1.0 are copyrighted by the Geometry Center. The +remaining files are copyrighted by C.B. Barber. Qhull is free software +and may be obtained via http from www.qhull.org. It may be freely copied, +modified, and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. diff --git a/vendor/qhull/README.txt b/vendor/qhull/README.txt new file mode 100644 index 0000000..baf1b80 --- /dev/null +++ b/vendor/qhull/README.txt @@ -0,0 +1,720 @@ +Name + + qhull, rbox 2020.2 2020/08/31 (8.0.2) + +Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection + + Documentation: + html/index.htm + + + Available from: + + + (git@github.com:qhull/qhull.git) + + News and a paper: + + + + Version 1 (simplicial only): + + +Purpose + + Qhull is a general dimension convex hull program that reads a set + of points from stdin, and outputs the smallest convex set that contains + the points to stdout. It also generates Delaunay triangulations, Voronoi + diagrams, furthest-site Voronoi diagrams, and halfspace intersections + about a point. + + Rbox is a useful tool in generating input for Qhull; it generates + hypercubes, diamonds, cones, circles, simplices, spirals, + lattices, and random points. + + Qhull produces graphical output for Geomview. This helps with + understanding the output. + +Environment requirements + + Qhull and rbox should run on all 32-bit and 64-bit computers. Use + an ANSI C or C++ compiler to compile the program. The software is + self-contained. It comes with examples and test scripts. + + Qhull's C++ interface uses the STL. The C++ test program uses QTestLib + from the Qt Framework. + + Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt + before using or distributing Qhull. + +To cite Qhull, please use + + Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull + algorithm for convex hulls," ACM Trans. on Mathematical Software, + 22(4):469-483, Dec 1996, http://www.qhull.org. + +To modify Qhull, particularly the C++ interface + + Qhull is on GitHub + (http://github.com/qhull/qhull/wiki, git@github.com:qhull/qhull.git) + + For internal documentation, see html/qh-code.htm + +To install Qhull + + Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. + + Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, + .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files + for Qt Creator. It compiles under Windows with mingw. + () + + Install and build instructions follow. + + See the end of this document for a list of distributed files. + +------------------ +Index + +Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT +Installing Qhull on Unix with gcc +Installing Qhull with CMake 2.6 or later +Installing Qhull with Qt +Working with Qhull's C++ interface +Calling Qhull from C programs +Compiling Qhull with Microsoft Visual C++ +Compiling Qhull with Qt Creator +Compiling Qhull with mingw/gcc on Windows +Compiling Qhull with cygwin on Windows +Compiling from Makfile without gcc +Compiling on other machines and compilers +Distributed files +Authors + +------------------ +Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT + + The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, + qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, + and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant + library while the other executables use the non-reentrant library. + + To install Qhull: + - Unzip the files into a directory (e.g., named 'qhull') + - Click on QHULL-GO or open a command window into Qhull's bin directory. + - Test with 'rbox D4 | qhull' + + To uninstall Qhull + - Delete the qhull directory + + To learn about Qhull: + - Execute 'qconvex' for a synopsis and examples. + Or 'qconvex --help' or 'qconvex -?' + - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points. + - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'. + - Browse the documentation: qhull\html\index.htm + - If an error occurs, Windows sends the error to stdout instead of stderr. + Use 'TO xxx' to send normal output to xxx + + To improve the command window + - Double-click the window bar to increase the size of the window + - Right-click the window bar + - Select Properties + - Check QuickEdit Mode + Select text with right-click or Enter + Paste text with right-click + - Change Font to Lucinda Console + - Change Layout to Screen Buffer Height 999, Window Size Height 55 + - Change Colors to Screen Background White, Screen Text Black + - Click OK + - Select 'Modify shortcut that started this window', then OK + + If you regularly use qhull on a Windows host, install a bash shell such as + https://gitforwindows.org/ # based on MSYS2 + https://github.com/git-for-windows/git/wiki + http://www.msys2.org/ + https://github.com/msys2/msys2/wiki + [mar'19] Git for Windows v2.21 requires 'qhull --help' + Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' fails + www.cygwin.com + www.mingw.org/wiki/msys # for Windows XP + Road Bash (www.qhull.org/bash) # based on MSYS + +------------------ +Installing Qhull on Unix with gcc + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - make + - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH + - make test + + 'make install' installs Qhull at '/usr/local/'. It installs pkg-config files + at '/usr/local/lib/pkgconfig'. Change the install directory with DESTDIR and PREFIX. + + To build 32-bit Qhull on a 64-bit host (uses 33% less memory in 4-d) + - make new M32=-m32 + + To build 32-bit Qhull without -fpic (may be faster, but shared library may fail) + - make new M32=-m32 FPIC= + + The Makefiles may be edited for other compilers. + If 'testqset' exits with an error, qhull is broken + + A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. + To build the Qhull executables and libqhullstatic + - Extract Qhull from qhull...tgz or qhull...zip + - cd src/libqhull_r # cd src/libqhull + - make + + +------------------ +Installing Qhull with CMake 2.6 or later + + See CMakeLists.txt for examples and further build instructions + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - cd build + - cmake --help # List build generators + - cmake -G "" .. # e.g., for MINGW-w64 -- cmake -G "MSYS Makefiles" .. + - cmake .. + - make + - ctest + - make install # If MSYS or UNIX, default CMAKE_INSTALL_PREFIX is '/usr/local' + # otherwise if WINDOWS, installs to ../bin, ../include, and ../lib + - make uninstall # Delete the files in install_manifest.txt + + The ".." is important. It refers to the parent directory (i.e., qhull/) + + CMake installs lib/pkgconfig/qhull*.pc for use with pkg-config + + If CMAKE_INSTALL_PREFIX is C:/Program Files/qhull, you may need to give 'Users' "full control" + to qhull's sub-directories: bin, doc, include, lib, and man (folder > Properties > Security > Edit > Users). + + On Windows, CMake's 64-bit generators have a "Win64" tag. Qhull's data structures + are substantial larger as 64-bit code than as 32-bit code. This may slow down Qhull. + + If cmake fails with "No CMAKE_C_COMPILER could be found" + - cmake was not able to find the build environment specified by -G "..." + + If cmake's gcc smoketest fails after a Windows update + - Reinstall MINGW-w64 and delete build/CMakeCache.txt. A Windows update can break gcc process creation for cc1. + +------------------ +Installing Qhull with Qt + + To build Qhull, including its C++ test program (qhulltest) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load src/qhull-all.pro into QtCreator + - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' + If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' + - Build + + - Build qhulltest with a C++11 or later compiler + - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied + into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin + - If qhulltest fails with exit status 127 and no error message, + check for missing Q5Core.dll and Qt5Test.dll + +------------------ +Working with Qhull's C++ interface + + See html/qh-code.htm#cpp for calling Qhull from C++ programs + + Class and method documentation is limited + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Examples of using the C++ interface + user_eg3_r.cpp + qhulltest/*_test.cpp + + Qhull's C++ interface is likely to change. Stay current with GitHub. + + To clone Qhull's next branch from http://github.com/qhull/qhull/wiki + git init + git clone git@github.com:qhull/qhull.git + cd qhull + git checkout next + ... + git pull origin next + + Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries + use the C routines setjmp() and longjmp() for error handling. They must + be compiled with the same compiler. + + Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc + With back-ticks, you can compile your C++ program with the Qhull libraries: + g++ `pkg-config --cflags --libs qhullcpp qhullstatic_r` -o my_app my_app.cpp + or + g++ `pkg-config --cflags --libs qhullcpp qhull_r` -o my_app my_app.cpp + + qhullcpp must be linked before qhull_r, otherwise the linker reports + an error -- "QhullUser ... multiple definition of `qh_fprintf'" + +------------------ +Calling Qhull from C programs + + See html/qh-code.htm#library for calling Qhull from C programs + + Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc + With back-ticks, you can compile your C program with the Qhull library + gcc `pkg-config --cflags --libs qhull_r` -o my_app my_app.c + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Warning: You will need to understand Qhull's data structures and read the + code. Most users will find it easier to call Qhull as an external command. + + The reentrant 'C' code (src/libqhull_r), passes a pointer to qhT + to most Qhull routines. This allows multiple instances of Qhull to run + at the same time. It simplifies the C++ interface. + + The non-reentrant 'C' code (src/libqhull) looks unusual. It refers to + Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). + This allows the same code to use static memory or heap memory. + If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; + otherwise qh_qh is a global static data structure of type qhT. + +------------------ +Compiling Qhull with Microsoft Visual C++ + + To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-32.sln + - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset + File > Save All + - Build target 'Win32' + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + - Copy Qt shared libraries, QtCore.dll and QtTest.dll, into the bin directory + + To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later + - 64-bit Qhull has larger data structures due to 64-bit pointers. This may slow down Qhull. + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-64.sln + - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset + File > Save All + - Build target 'x64' + - If build as 32-bit fails, use solution build/qhull-32.sln + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + + If error -- MSB8020: The build tools for Visual Studio 2012 (Platform Toolset = 'v110') cannot be found. + - 'Project > Retarget solution' for both qhull-32.sln and qhull-64.sln + - 'File > Open' your preferred solution (qhull-32.sln or qhull-64.sln) + - 'Save All' both projects + - DevStudio may need a restart + + To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull.sln + - Build target 'win32' (not 'x64') + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + +------------------ +Compiling Qhull with Qt Creator + + Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh + + Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) + + To compile Qhull with Qt Creator + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Download the Qt SDK + - Start Qt Creator + - Load src/qhull-all.pro + - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' + If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' + - Build + + - Build qhulltest with a C++11 or later compiler + - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied + into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin + - If qhulltest fails with exit status 127 and no error message, + check for missing Q5Core.dll and Qt5Test.dll + +------------------ +Compiling Qhull with mingw/gcc on Windows + + To compile Qhull with MINGW + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install GitForWindows (https://gitforwindows.org/) + or MSYS2 (http://www.msys2.org/) + Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' will not work + - Install MINGW-w64 with gcc (https://mingw-w64.org/) + 1) Goto sourceforge -- https://sourceforge.net/projects/mingw-w64/files/ + 2) in folder -- mingw-w64 + 3) download installer -- MinGW-W64-install.exe + Run the installer + 1) Select i686/posix/dwarf + 2) Install in 'C:\mingw-w64' # Not 'Program Files\...' + Rename /c/mingw-w64/mingw32/bin/mingw32-make.exe to make.exe + Add the 'C:\mingw-w64\mingw32\bin' directory to your $PATH environment variable + Execute 'which make' to check that 'make' is mingw-w64's make + - Compile Qhull from the home directory + make help + make + + Notes + - Mingw is included with Qt SDK in qt/Tools/mingw53_32 + - If you use Windows XP + Install Road Bash (http://www.qhull.org/bash) or MSYS (http://www.mingw.org/wiki/msys) + Install MINGW (http://mingw.org/) + +------------------ +Compiling Qhull with cygwin on Windows + + To compile Qhull with cygwin + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install cygwin (http://www.cygwin.com) + - Include packages for gcc, make, ar, and ln + - make + +------------------ +Compiling from Makfile without gcc + + The file, qhull-src.tgz, contains documentation and source files for + qhull and rbox. + + To unpack the tgz file + - tar zxf qhull-src.tgz + - cd qhull + - Use qhull/Makefile + Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile + + Compiling qhull and rbox with Makefile + - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines + - the defaults are gcc and enscript + - CCOPTS1 should include the ANSI flag. It defines __STDC__ + - in user.h, check the definitions of qh_SECticks and qh_CPUclock. + - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour + - type: make + - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a + - type: make doc + - this prints the man page + - See also qhull/html/index.htm + - if your compiler reports many errors, it is probably not a ANSI C compiler + - you will need to set the -ansi switch or find another compiler + - if your compiler warns about missing prototypes for fprintf() etc. + - this is ok, your compiler should have these in stdio.h + - if your compiler warns about missing prototypes for memset() etc. + - include memory.h in qhull_a.h + - if your compiler reports "global.c: storage size of 'qh_qh' isn't known" + - delete the initializer "={0}" in global.c, stat.c and mem.c + - if your compiler warns about "stat.c: improper initializer" + - this is ok, the initializer is not used + - if you have trouble building libqhull.a with 'ar' + - try 'make -f Makefile.txt qhullx' + - if the code compiles, the qhull test case will automatically execute + - if an error occurs, there's an incompatibility between machines + - If you can, try a different compiler + - You can turn off the Qhull memory manager with qh_NOmem in mem.h + - You can turn off compiler optimization (-O2 in Makefile) + - If you find the source of the problem, please let us know + - to install the programs and their man pages: + - define MANDIR and BINDIR + - type 'make install' + + - if you have Geomview (www.geomview.org) + - try 'rbox 100 | qconvex G >a' and load 'a' into Geomview + - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm) + +------------------ +Compiling on other machines and compilers + + Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. + Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set + the ANSI option in Options:Project:Compiler:Source:Language-compliance. + + Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. + Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with + Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". + Use the same options for Qhull 1.0. [D. Zwick] + + If you have troubles with the memory manager, you can turn it off by + defining qh_NOmem in mem.h. + +------------------ +Distributed files + + README.txt // Instructions for installing Qhull + REGISTER.txt // Qhull registration + COPYING.txt // Copyright notice + QHULL-GO.lnk // Windows icon for eg/qhull-go.bat + Announce.txt // Announcement + CMakeLists.txt // CMake build file (2.6 or later) + File_id.diz // Package descriptor + index.htm // Home page + Makefile // Makefile for gcc and other compilers + qhull*.md5sum // md5sum for all files + + bin/* // Qhull executables and dll (.zip only) + build/CMakeModules/CheckLFS.cmake // enables Large File Support in CMake + build/config.cmake.in // extract target variables + build/qhull.pc.in // pkg-config template for creating lib/pkgconfig/qhull*.pc + build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) + build/*-32.vcxproj + build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) + build/*-64.vcxproj + build/qhull.sln // DevStudio solution and project files (2005 and 2009) + build/*.vcproj + build/qhulltest/ // DevStudio project files for qhulltest (C++ and Qt) + build/README-build.txt // Contents of build/ + eg/* // Test scripts and geomview files from q_eg + html/index.htm // Manual + html/qh-faq.htm // Frequently asked questions + html/qh-get.htm // Download page + html/qhull-cpp.xml // C++ style notes as a Road FAQ (www.qhull.org/road) + src/Changes.txt // Change history for Qhull and rbox + src/qhull-all.pro // Qt project + +eg/ + q_benchmark // shell script for precision and performance benchmark + q_benchmark-ok.txt // reviewed output from q_benchmark + q_eg // shell script for Geomview examples (eg.01.cube) + q_egtest // shell script for Geomview test examples + q_test // shell script to test qhull + q_test.bat // Windows batch test for QHULL-GO.bat + // cd bin; ..\eg\q_test.bat >q_test.x 2>&1 + q_test-ok.txt // reviewed output from q_test + qhulltest-ok.txt // reviewed output from qhulltest (Qt only) + make-qhull_qh.sh // shell script to create non-reentrant qhull_qh from reentrant Qhull + make-vcproj.sh // shell script to create vcproj and vcxprog files + qhull-zip.sh // shell script to create distribution files + qtest.sh // shell script for testing and logging qhull + +rbox consists of (bin, html): + rbox.exe // Win32 executable (.zip only) + rbox.htm // html manual + rbox.man // Unix man page + rbox.txt + +qhull consists of (bin, html): + qconvex.exe // Win32 executables and dlls (.zip download only) + qhull.exe // Built with the reentrant library (about 2% slower) + qdelaunay.exe + qhalf.exe + qvoronoi.exe + qhull_r.dll + qhull-go.bat // command window + qconvex.htm // html manual + qdelaun.htm + qdelau_f.htm + qhalf.htm + qvoronoi.htm + qvoron_f.htm + qh-eg.htm + qh-code.htm + qh-impre.htm + index.htm + qh-opt*.htm + qh-quick.htm + qh--*.gif // images for manual + normal_voronoi_knauss_oesterle.jpg + qh_findbestfacet-drielsma.pdf + qhull.man // Unix man page + qhull.txt + +bin/ + msvcr80.dll // Visual C++ redistributable file (.zip download only) + +src/ + qhull/unix.c // Qhull and rbox applications using non-reentrant libqhullstatic.a + rbox/rbox.c + qconvex/qconvex.c + qhalf/qhalf.c + qdelaunay/qdelaunay.c + qvoronoi/qvoronoi.c + + qhull/unix_r.c // Qhull and rbox applications using reentrant libqhullstatic_r.a + rbox/rbox_r.c + qconvex/qconvex_r.c // Qhull applications built with reentrant libqhull_r/Makefile + qhalf/qhalf_r.c + qdelaunay/qdelaun_r.c + qvoronoi/qvoronoi_r.c + + user_eg/user_eg_r.c // example of using qhull_r.dll from a user program + user_eg2/user_eg2_r.c // example of using libqhullstatic_r.a from a user program + user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a + qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib + qhull-*.pri // Include files for Qt projects + testqset_r/testqset_r.c // Test of reentrant qset_r.c and mem_r.c + testqset/testqset.c // Test of non-rentrant qset.c and mem.c + +src/libqhull + libqhull.pro // Qt project for non-rentrant, shared library (qhull.dll) + index.htm // design documentation for libqhull + qh-*.htm + qhull-exports.def // Export Definition files for Visual C++ + qhull-nomerge-exports.def + qhull_p-exports.def + qhull_p-nomerge-exports.def + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + Mborland // Makefile for Borland C++ 5.0 + + libqhull.h // header file for qhull + user.h // header file of user definable constants + libqhull.c // Quickhull algorithm with partitioning + user.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + + qhull_a.h // include files for libqhull/*.c + geom.c // geometric routines + geom2.c + geom.h + global.c // global variables + io.c // input-output routines + io.h + mem.c // memory routines, this is stand-alone code + mem.h + merge.c // merging of non-convex facets + merge.h + poly.c // polyhedron routines + poly2.c + poly.h + qset.c // set routines, this only depends on mem.c + qset.h + random.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib.c // point set generator for rbox + stat.c // statistics + stat.h + +src/libqhull_r + libqhull_r.pro // Qt project for rentrant, shared library (qhull_r.dll) + index.htm // design documentation for libqhull_r + qh-*_r.htm + qhull_r-exports.def // Export Definition files for Visual C++ + qhull_r-nomerge-exports.def + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + + libqhull_r.h // header file for qhull + user_r.h // header file of user definable constants + libqhull_r.c // Quickhull algorithm wi_r.hpartitioning + user_r.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + qhull_ra.h // include files for libqhull/*_r.c + geom_r.c // geometric routines + geom2.c + geom_r.h + global_r.c // global variables + io_r.c // input-output routines + io_r.h + mem_r.c // memory routines, this is stand-alone code + mem.h + merge_r.c // merging of non-convex facets + merge.h + poly_r.c // polyhedron routines + poly2.c + poly_r.h + qset_r.c // set routines, this only depends on mem_r.c + qset.h + random_r.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib_r.c // point set generator for rbox + stat_r.c // statistics + stat.h + +src/libqhullcpp/ + libqhullcpp.pro // Qt project for renentrant, static C++ library + Qhull.cpp // Calls libqhull_r.c from C++ + Qhull.h + qt-qhull.cpp // Supporting methods for Qt + + Coordinates.cpp // input classes + Coordinates.h + + PointCoordinates.cpp + PointCoordinates.h + RboxPoints.cpp // call rboxlib.c from C++ + RboxPoints.h + + QhullFacet.cpp // data structure classes + QhullFacet.h + QhullHyperplane.cpp + QhullHyperplane.h + QhullPoint.cpp + QhullPoint.h + QhullQh.cpp + QhullRidge.cpp + QhullRidge.h + QhullVertex.cpp + QhullVertex.h + + QhullFacetList.cpp // collection classes + QhullFacetList.h + QhullFacetSet.cpp + QhullFacetSet.h + QhullIterator.h + QhullLinkedList.h + QhullPoints.cpp + QhullPoints.h + QhullPointSet.cpp + QhullPointSet.h + QhullSet.cpp + QhullSet.h + QhullSets.h + QhullVertexSet.cpp + QhullVertexSet.h + + functionObjects.h // supporting classes + QhullError.cpp + QhullError.h + QhullQh.cpp + QhullQh.h + QhullStat.cpp + QhullStat.h + QhullUser.cpp + QhullUser.h + RoadError.cpp // Supporting base classes + RoadError.h + RoadLogEvent.cpp + RoadLogEvent.h + usermem_r-cpp.cpp // Optional override for qh_exit() to throw an error + +src/libqhullstatic/ + libqhullstatic.pro // Qt project for non-reentrant, static library + +src/libqhullstatic_r/ + libqhullstatic_r.pro // Qt project for reentrant, static library + +src/qhulltest/ + qhulltest.pro // Qt project for test of C++ interface + Coordinates_test.cpp // Test of each class + PointCoordinates_test.cpp + Qhull_test.cpp + QhullFacet_test.cpp + QhullFacetList_test.cpp + QhullFacetSet_test.cpp + QhullHyperplane_test.cpp + QhullLinkedList_test.cpp + QhullPoint_test.cpp + QhullPoints_test.cpp + QhullPointSet_test.cpp + QhullRidge_test.cpp + QhullSet_test.cpp + QhullVertex_test.cpp + QhullVertexSet_test.cpp + RboxPoints_test.cpp + RoadTest.cpp // Run multiple test files with QTestLib + RoadTest.h + +------------------ +Authors + + C. Bradford Barber Hannu Huhdanpaa (Version 1.0) + bradb@shore.net hannu@qhull.org + + Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 + and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard + University. If you find Qhull useful, please let us know. diff --git a/vendor/qhull/libqhull_r/accessors_r.c b/vendor/qhull/libqhull_r/accessors_r.c new file mode 100644 index 0000000..5052e08 --- /dev/null +++ b/vendor/qhull/libqhull_r/accessors_r.c @@ -0,0 +1,69 @@ +#include +#include "libqhull_r.h" + +qhT *qh_alloc_qh(FILE *errfile) { + qhT *qh = (qhT*) qh_malloc(sizeof(qhT)); + QHULL_LIB_CHECK + if (qh) qh_zero(qh, errfile); + return qh; +} + +/* Free the qhT pointer. + + Note: qh_freeqhull and qh_memfreeshort should be called before calling qh_free_qh. */ +void qh_free_qh(qhT *qh) { + qh_free(qh); +} + +#define GETTER(TYPE, FIELD) TYPE qh_get_##FIELD(const qhT *qh) { return qh->FIELD; } +#define SETTER(TYPE, FIELD) void qh_set_##FIELD(qhT *qh, TYPE _val_) { qh->FIELD = _val_; } + +/* required to emulate the various FOR* macros */ +GETTER(facetT*, facet_list) +GETTER(pointT*, first_point) +GETTER(int, hull_dim) +GETTER(int, num_facets) +GETTER(int, num_points) +GETTER(int, num_vertices) +GETTER(vertexT*, vertex_list) + +/* suggested by @blegat based on Qhull.jl wrappers */ +GETTER(realT, totarea) +GETTER(realT, totvol) +GETTER(boolT, hasAreaVolume) +SETTER(boolT, hasAreaVolume) +GETTER(boolT, hasTriangulation) +SETTER(boolT, hasTriangulation) + +/* suggested by @JuhaHeiskala based on his DirectQHull.jl wrapper */ +GETTER(int, num_good) +GETTER(setT*, del_vertices) +GETTER(int, input_dim) + +/* other accessors, mimicking those provided by the scipy API: */ +GETTER(boolT, DELAUNAY) +GETTER(boolT, SCALElast) +GETTER(boolT, KEEPcoplanar) +GETTER(boolT, MERGEexact) +GETTER(boolT, NOerrexit) +GETTER(boolT, PROJECTdelaunay) +GETTER(boolT, ATinfinity) +GETTER(boolT, UPPERdelaunay) +GETTER(int, normal_size) +GETTER(int, num_visible) +GETTER(int, center_size) +GETTER(const char *, qhull_command) +GETTER(facetT*, facet_tail) +GETTER(vertexT*, vertex_tail) +GETTER(unsigned int, facet_id) +GETTER(unsigned int, visit_id) +GETTER(unsigned int, vertex_visit) +GETTER(pointT*, input_points) +GETTER(coordT*, feasible_point) +GETTER(realT, last_low) +GETTER(realT, last_high) +GETTER(realT, last_newhigh) +GETTER(realT, max_outside) +GETTER(realT, MINoutside) +GETTER(realT, DISTround) +GETTER(setT*, other_points) diff --git a/vendor/qhull/libqhull_r/geom2_r.c b/vendor/qhull/libqhull_r/geom2_r.c new file mode 100644 index 0000000..7d171e4 --- /dev/null +++ b/vendor/qhull/libqhull_r/geom2_r.c @@ -0,0 +1,2302 @@ +/*
  ---------------------------------
+
+
+   geom2_r.c
+   infrequently used geometric routines of qhull
+
+   see qh-geom_r.htm and geom_r.h
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/geom2_r.c#18 $$Change: 3396 $
+   $DateTime: 2023/01/02 16:59:48 $$Author: bbarber $
+
+   frequently used code goes into geom_r.c
+*/
+
+#include "qhull_ra.h"
+
+/*================== functions in alphabetic order ============*/
+
+/*---------------------------------
+
+  qh_copypoints(qh, points, numpoints, dimension )
+    return qh_malloc'd copy of points
+
+  notes:
+    qh_free the returned points to avoid a memory leak
+*/
+coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension)
+{
+  int size;
+  coordT *newpoints;
+
+  size= numpoints * dimension * (int)sizeof(coordT);
+  if (!(newpoints= (coordT *)qh_malloc((size_t)size))) {
+    qh_fprintf(qh, qh->ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
+        numpoints);
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
+  return newpoints;
+} /* copypoints */
+
+/*---------------------------------
+
+  qh_crossproduct( dim, vecA, vecB, vecC )
+    crossproduct of 2 dim vectors
+    C= A x B
+
+  notes:
+    from Glasner, Graphics Gems I, p. 639
+    only defined for dim==3
+*/
+void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
+
+  if (dim == 3) {
+    vecC[0]=   det2_(vecA[1], vecA[2],
+                     vecB[1], vecB[2]);
+    vecC[1]= - det2_(vecA[0], vecA[2],
+                     vecB[0], vecB[2]);
+    vecC[2]=   det2_(vecA[0], vecA[1],
+                     vecB[0], vecB[1]);
+  }
+} /* vcross */
+
+/*---------------------------------
+
+  qh_determinant(qh, rows, dim, nearzero )
+    compute signed determinant of a square matrix
+    uses qh.NEARzero to test for degenerate matrices
+
+  returns:
+    determinant
+    overwrites rows and the matrix
+    if dim == 2 or 3
+      nearzero iff determinant < qh->NEARzero[dim-1]
+      (!quite correct, not critical)
+    if dim >= 4
+      nearzero iff diagonal[k] < qh->NEARzero[k]
+*/
+realT qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero) {
+  realT det=0;
+  int i;
+  boolT sign= False;
+
+  *nearzero= False;
+  if (dim < 2) {
+    qh_fprintf(qh, qh->ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }else if (dim == 2) {
+    det= det2_(rows[0][0], rows[0][1],
+                 rows[1][0], rows[1][1]);
+    if (fabs_(det) < 10*qh->NEARzero[1])  /* QH11031 FIX: not really correct, what should this be? */
+      *nearzero= True;
+  }else if (dim == 3) {
+    det= det3_(rows[0][0], rows[0][1], rows[0][2],
+                 rows[1][0], rows[1][1], rows[1][2],
+                 rows[2][0], rows[2][1], rows[2][2]);
+    if (fabs_(det) < 10*qh->NEARzero[2])  /* QH11031 FIX: what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63 */
+      *nearzero= True;
+  }else {
+    qh_gausselim(qh, rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok */
+    det= 1.0;
+    for (i=dim; i--; )
+      det *= (rows[i])[i];
+    if (sign)
+      det= -det;
+  }
+  return det;
+} /* determinant */
+
+/*---------------------------------
+
+  qh_detjoggle(qh, points, numpoints, dimension )
+    determine default max joggle for point array
+      as qh_distround * qh_JOGGLEdefault
+
+  returns:
+    initial value for JOGGLEmax from points and REALepsilon
+
+  notes:
+    computes DISTround since qh_maxmin not called yet
+    if qh->SCALElast, last dimension will be scaled later to MAXwidth
+
+    loop duplicated from qh_maxmin
+*/
+realT qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension) {
+  realT abscoord, distround, joggle, maxcoord, mincoord;
+  pointT *point, *pointtemp;
+  realT maxabs= -REALmax;
+  realT sumabs= 0;
+  realT maxwidth= 0;
+  int k;
+
+  if (qh->SETroundoff)
+    distround= qh->DISTround; /* 'En' */
+  else{
+    for (k=0; k < dimension; k++) {
+      if (qh->SCALElast && k == dimension-1)
+        abscoord= maxwidth;
+      else if (qh->DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
+        abscoord= 2 * maxabs * maxabs;  /* may be low by qh->hull_dim/2 */
+      else {
+        maxcoord= -REALmax;
+        mincoord= REALmax;
+        FORALLpoint_(qh, points, numpoints) {
+          maximize_(maxcoord, point[k]);
+          minimize_(mincoord, point[k]);
+        }
+        maximize_(maxwidth, maxcoord-mincoord);
+        abscoord= fmax_(maxcoord, -mincoord);
+      }
+      sumabs += abscoord;
+      maximize_(maxabs, abscoord);
+    } /* for k */
+    distround= qh_distround(qh, qh->hull_dim, maxabs, sumabs);
+  }
+  joggle= distround * qh_JOGGLEdefault;
+  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
+  trace2((qh, qh->ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
+  return joggle;
+} /* detjoggle */
+
+/*---------------------------------
+
+  qh_detmaxoutside(qh);
+    determine qh.MAXoutside target for qh_RATIO... tests of distance
+    updates option '_max-outside'
+
+  notes:
+    called from qh_addpoint and qh_detroundoff
+    accounts for qh.ONEmerge, qh.DISTround, qh.MINoutside ('Wn'), qh.max_outside
+    see qh_maxout for qh.max_outside with qh.DISTround
+*/
+
+void qh_detmaxoutside(qhT *qh) {
+  realT maxoutside;
+
+  maxoutside= fmax_(qh->max_outside, qh->ONEmerge + qh->DISTround);
+  maximize_(maxoutside, qh->MINoutside);
+  qh->MAXoutside= maxoutside;
+  trace3((qh, qh->ferr, 3056, "qh_detmaxoutside: MAXoutside %2.2g from qh.max_outside %2.2g, ONEmerge %2.2g, MINoutside %2.2g, DISTround %2.2g\n",
+      qh->MAXoutside, qh->max_outside, qh->ONEmerge, qh->MINoutside, qh->DISTround));
+} /* detmaxoutside */
+
+/*---------------------------------
+
+  qh_detroundoff(qh)
+    determine maximum roundoff errors from
+      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
+      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
+
+    accounts for qh.SETroundoff, qh.RANDOMdist, qh->MERGEexact
+      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
+      qh.postmerge_centrum, qh.MINoutside,
+      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
+
+  returns:
+    sets qh.DISTround, etc. (see below)
+    appends precision constants to qh.qhull_options
+
+  see:
+    qh_maxmin() for qh.NEARzero
+
+  design:
+    determine qh.DISTround for distance computations
+    determine minimum denominators for qh_divzero
+    determine qh.ANGLEround for angle computations
+    adjust qh.premerge_cos,... for roundoff error
+    determine qh.ONEmerge for maximum error due to a single merge
+    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
+      qh.MINoutside, qh.WIDEfacet
+    initialize qh.max_vertex and qh.minvertex
+*/
+void qh_detroundoff(qhT *qh) {
+
+  qh_option(qh, "_max-width", NULL, &qh->MAXwidth);
+  if (!qh->SETroundoff) {
+    qh->DISTround= qh_distround(qh, qh->hull_dim, qh->MAXabs_coord, qh->MAXsumcoord);
+    qh_option(qh, "Error-roundoff", NULL, &qh->DISTround);
+  }
+  qh->MINdenom= qh->MINdenom_1 * qh->MAXabs_coord;
+  qh->MINdenom_1_2= sqrt(qh->MINdenom_1 * qh->hull_dim) ;  /* if will be normalized */
+  qh->MINdenom_2= qh->MINdenom_1_2 * qh->MAXabs_coord;
+                                              /* for inner product */
+  qh->ANGLEround= 1.01 * qh->hull_dim * REALepsilon;
+  if (qh->RANDOMdist) {
+    qh->ANGLEround += qh->RANDOMfactor;
+    trace4((qh, qh->ferr, 4096, "qh_detroundoff: increase qh.ANGLEround by option 'R%2.2g'\n", qh->RANDOMfactor));
+  }
+  if (qh->premerge_cos < REALmax/2) {
+    qh->premerge_cos -= qh->ANGLEround;
+    if (qh->RANDOMdist)
+      qh_option(qh, "Angle-premerge-with-random", NULL, &qh->premerge_cos);
+  }
+  if (qh->postmerge_cos < REALmax/2) {
+    qh->postmerge_cos -= qh->ANGLEround;
+    if (qh->RANDOMdist)
+      qh_option(qh, "Angle-postmerge-with-random", NULL, &qh->postmerge_cos);
+  }
+  qh->premerge_centrum += 2 * qh->DISTround;    /*2 for centrum and distplane()*/
+  qh->postmerge_centrum += 2 * qh->DISTround;
+  if (qh->RANDOMdist && (qh->MERGEexact || qh->PREmerge))
+    qh_option(qh, "Centrum-premerge-with-random", NULL, &qh->premerge_centrum);
+  if (qh->RANDOMdist && qh->POSTmerge)
+    qh_option(qh, "Centrum-postmerge-with-random", NULL, &qh->postmerge_centrum);
+  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
+    realT maxangle= 1.0, maxrho;
+
+    minimize_(maxangle, qh->premerge_cos);
+    minimize_(maxangle, qh->postmerge_cos);
+    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
+    qh->ONEmerge= sqrt((realT)qh->hull_dim) * qh->MAXwidth *
+      sqrt(1.0 - maxangle * maxangle) + qh->DISTround;
+    maxrho= qh->hull_dim * qh->premerge_centrum + qh->DISTround;
+    maximize_(qh->ONEmerge, maxrho);
+    maxrho= qh->hull_dim * qh->postmerge_centrum + qh->DISTround;
+    maximize_(qh->ONEmerge, maxrho);
+    if (qh->MERGING)
+      qh_option(qh, "_one-merge", NULL, &qh->ONEmerge);
+  }
+  qh->NEARinside= qh->ONEmerge * qh_RATIOnearinside; /* only used if qh->KEEPnearinside */
+  if (qh->JOGGLEmax < REALmax/2 && (qh->KEEPcoplanar || qh->KEEPinside)) {
+    realT maxdist;             /* adjust qh.NEARinside for joggle */
+    qh->KEEPnearinside= True;
+    maxdist= sqrt((realT)qh->hull_dim) * qh->JOGGLEmax + qh->DISTround;
+    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
+    maximize_(qh->NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
+  }
+  if (qh->KEEPnearinside)
+    qh_option(qh, "_near-inside", NULL, &qh->NEARinside);
+  if (qh->JOGGLEmax < qh->DISTround) {
+    qh_fprintf(qh, qh->ferr, 6006, "qhull option error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
+         qh->JOGGLEmax, qh->DISTround);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (qh->MINvisible > REALmax/2) {
+    if (!qh->MERGING)
+      qh->MINvisible= qh->DISTround;
+    else if (qh->hull_dim <= 3)
+      qh->MINvisible= qh->premerge_centrum;
+    else
+      qh->MINvisible= qh_COPLANARratio * qh->premerge_centrum;
+    if (qh->APPROXhull && qh->MINvisible > qh->MINoutside)
+      qh->MINvisible= qh->MINoutside;
+    qh_option(qh, "Visible-distance", NULL, &qh->MINvisible);
+  }
+  if (qh->MAXcoplanar > REALmax/2) {
+    qh->MAXcoplanar= qh->MINvisible;
+    qh_option(qh, "U-max-coplanar", NULL, &qh->MAXcoplanar);
+  }
+  if (!qh->APPROXhull) {             /* user may specify qh->MINoutside */
+    qh->MINoutside= 2 * qh->MINvisible;
+    if (qh->premerge_cos < REALmax/2)
+      maximize_(qh->MINoutside, (1- qh->premerge_cos) * qh->MAXabs_coord);
+    qh_option(qh, "Width-outside", NULL, &qh->MINoutside);
+  }
+  qh->WIDEfacet= qh->MINoutside;
+  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MAXcoplanar);
+  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MINvisible);
+  qh_option(qh, "_wide-facet", NULL, &qh->WIDEfacet);
+  if (qh->MINvisible > qh->MINoutside + 3 * REALepsilon
+  && !qh->BESToutside && !qh->FORCEoutput)
+    qh_fprintf(qh, qh->ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
+             qh->MINvisible, qh->MINoutside);
+  qh->max_vertex= qh->DISTround;
+  qh->min_vertex= -qh->DISTround;
+  /* numeric constants reported in printsummary */
+  qh_detmaxoutside(qh);
+} /* detroundoff */
+
+/*---------------------------------
+
+  qh_detsimplex(qh, apex, points, dim, nearzero )
+    compute determinant of a simplex with point apex and base points
+
+  returns:
+     signed determinant and nearzero from qh_determinant
+
+  notes:
+     called by qh_maxsimplex and qh_initialvertices
+     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
+
+  design:
+    construct qm_matrix by subtracting apex from points
+    compute determinate
+*/
+realT qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero) {
+  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
+  coordT **rows;
+  int k,  i=0;
+  realT det;
+
+  zinc_(Zdetsimplex);
+  gmcoord= qh->gm_matrix;
+  rows= qh->gm_row;
+  FOREACHpoint_(points) {
+    if (i == dim)
+      break;
+    rows[i++]= gmcoord;
+    coordp= point;
+    coorda= apex;
+    for (k=dim; k--; )
+      *(gmcoord++)= *coordp++ - *coorda++;
+  }
+  if (i < dim) {
+    qh_fprintf(qh, qh->ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
+               i, dim);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  det= qh_determinant(qh, rows, dim, nearzero);
+  trace2((qh, qh->ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
+          det, qh_pointid(qh, apex), dim, *nearzero));
+  return det;
+} /* detsimplex */
+
+/*---------------------------------
+
+  qh_distnorm( dim, point, normal, offset )
+    return distance from point to hyperplane at normal/offset
+
+  returns:
+    dist
+
+  notes:
+    dist > 0 if point is outside of hyperplane
+
+  see:
+    qh_distplane in geom_r.c
+*/
+realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
+  coordT *normalp= normal, *coordp= point;
+  realT dist;
+  int k;
+
+  dist= *offsetp;
+  for (k=dim; k--; )
+    dist += *(coordp++) * *(normalp++);
+  return dist;
+} /* distnorm */
+
+/*---------------------------------
+
+  qh_distround(qh, dimension, maxabs, maxsumabs )
+    compute maximum round-off error for a distance computation
+      to a normalized hyperplane
+    maxabs is the maximum absolute value of a coordinate
+    maxsumabs is the maximum possible sum of absolute coordinate values
+    if qh.RANDOMdist ('Qr'), adjusts qh_distround
+
+  returns:
+    max dist round for qh.REALepsilon and qh.RANDOMdist
+
+  notes:
+    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
+    use sqrt(dim) since one vector is normalized
+      or use maxsumabs since one vector is < 1
+*/
+realT qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs) {
+  realT maxdistsum, maxround, delta;
+
+  maxdistsum= sqrt((realT)dimension) * maxabs;
+  minimize_( maxdistsum, maxsumabs);
+  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
+              /* adds maxabs for offset */
+  if (qh->RANDOMdist) {
+    delta= qh->RANDOMfactor * maxabs;
+    maxround += delta;
+    trace4((qh, qh->ferr, 4092, "qh_distround: increase roundoff by random delta %2.2g for option 'R%2.2g'\n", delta, qh->RANDOMfactor));
+  }
+  trace4((qh, qh->ferr, 4008, "qh_distround: %2.2g, maxabs %2.2g, maxsumabs %2.2g, maxdistsum %2.2g\n",
+            maxround, maxabs, maxsumabs, maxdistsum));
+  return maxround;
+} /* distround */
+
+/*---------------------------------
+
+  qh_divzero( numer, denom, mindenom1, zerodiv )
+    divide by a number that's nearly zero
+    mindenom1= minimum denominator for dividing into 1.0
+
+  returns:
+    quotient
+    sets zerodiv and returns 0.0 if it would overflow
+
+  design:
+    if numer is nearly zero and abs(numer) < abs(denom)
+      return numer/denom
+    else if numer is nearly zero
+      return 0 and zerodiv
+    else if denom/numer non-zero
+      return numer/denom
+    else
+      return 0 and zerodiv
+*/
+realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
+  realT temp, numerx, denomx;
+
+
+  if (numer < mindenom1 && numer > -mindenom1) {
+    numerx= fabs_(numer);
+    denomx= fabs_(denom);
+    if (numerx < denomx) {
+      *zerodiv= False;
+      return numer/denom;
+    }else {
+      *zerodiv= True;
+      return 0.0;
+    }
+  }
+  temp= denom/numer;
+  if (temp > mindenom1 || temp < -mindenom1) {
+    *zerodiv= False;
+    return numer/denom;
+  }else {
+    *zerodiv= True;
+    return 0.0;
+  }
+} /* divzero */
+
+
+/*---------------------------------
+
+  qh_facetarea(qh, facet )
+    return area for a facet
+
+  notes:
+    if non-simplicial,
+      uses centrum to triangulate facet and sums the projected areas.
+    if (qh->DELAUNAY),
+      computes projected area instead for last coordinate
+    assumes facet->normal exists
+    projecting tricoplanar facets to the hyperplane does not appear to make a difference
+
+  design:
+    if simplicial
+      compute area
+    else
+      for each ridge
+        compute area from centrum to ridge
+    negate area if upper Delaunay facet
+*/
+realT qh_facetarea(qhT *qh, facetT *facet) {
+  vertexT *apex;
+  pointT *centrum;
+  realT area= 0.0;
+  ridgeT *ridge, **ridgep;
+
+  if (facet->simplicial) {
+    apex= SETfirstt_(facet->vertices, vertexT);
+    area= qh_facetarea_simplex(qh, qh->hull_dim, apex->point, facet->vertices,
+                    apex, facet->toporient, facet->normal, &facet->offset);
+  }else {
+    if (qh->CENTERtype == qh_AScentrum)
+      centrum= facet->center;
+    else
+      centrum= qh_getcentrum(qh, facet);
+    FOREACHridge_(facet->ridges)
+      area += qh_facetarea_simplex(qh, qh->hull_dim, centrum, ridge->vertices,
+                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
+    if (qh->CENTERtype != qh_AScentrum)
+      qh_memfree(qh, centrum, qh->normal_size);
+  }
+  if (facet->upperdelaunay && qh->DELAUNAY)
+    area= -area;  /* the normal should be [0,...,1] */
+  trace4((qh, qh->ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
+  return area;
+} /* facetarea */
+
+/*---------------------------------
+
+  qh_facetarea_simplex(qh, dim, apex, vertices, notvertex, toporient, normal, offset )
+    return area for a simplex defined by
+      an apex, a base of vertices, an orientation, and a unit normal
+    if simplicial or tricoplanar facet,
+      notvertex is defined and it is skipped in vertices
+
+  returns:
+    computes area of simplex projected to plane [normal,offset]
+    returns 0 if vertex too far below plane (qh->WIDEfacet)
+      vertex can't be apex of tricoplanar facet
+
+  notes:
+    if (qh->DELAUNAY),
+      computes projected area instead for last coordinate
+    uses qh->gm_matrix/gm_row and qh->hull_dim
+    helper function for qh_facetarea
+
+  design:
+    if Notvertex
+      translate simplex to apex
+    else
+      project simplex to normal/offset
+      translate simplex to apex
+    if Delaunay
+      set last row/column to 0 with -1 on diagonal
+    else
+      set last row to Normal
+    compute determinate
+    scale and flip sign for area
+*/
+realT qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
+        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
+  pointT *coorda, *coordp, *gmcoord;
+  coordT **rows, *normalp;
+  int k,  i=0;
+  realT area, dist;
+  vertexT *vertex, **vertexp;
+  boolT nearzero;
+
+  gmcoord= qh->gm_matrix;
+  rows= qh->gm_row;
+  FOREACHvertex_(vertices) {
+    if (vertex == notvertex)
+      continue;
+    rows[i++]= gmcoord;
+    coorda= apex;
+    coordp= vertex->point;
+    normalp= normal;
+    if (notvertex) {
+      for (k=dim; k--; )
+        *(gmcoord++)= *coordp++ - *coorda++;
+    }else {
+      dist= *offset;
+      for (k=dim; k--; )
+        dist += *coordp++ * *normalp++;
+      if (dist < -qh->WIDEfacet) {
+        zinc_(Znoarea);
+        return 0.0;
+      }
+      coordp= vertex->point;
+      normalp= normal;
+      for (k=dim; k--; )
+        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
+    }
+  }
+  if (i != dim-1) {
+    qh_fprintf(qh, qh->ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
+               i, dim);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  rows[i]= gmcoord;
+  if (qh->DELAUNAY) {
+    for (i=0; i < dim-1; i++)
+      rows[i][dim-1]= 0.0;
+    for (k=dim; k--; )
+      *(gmcoord++)= 0.0;
+    rows[dim-1][dim-1]= -1.0;
+  }else {
+    normalp= normal;
+    for (k=dim; k--; )
+      *(gmcoord++)= *normalp++;
+  }
+  zinc_(Zdetfacetarea);
+  area= qh_determinant(qh, rows, dim, &nearzero);
+  if (toporient)
+    area= -area;
+  area *= qh->AREAfactor;
+  trace4((qh, qh->ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
+          area, qh_pointid(qh, apex), toporient, nearzero));
+  return area;
+} /* facetarea_simplex */
+
+/*---------------------------------
+
+  qh_facetcenter(qh, vertices )
+    return Voronoi center (Voronoi vertex) for a facet's vertices
+
+  returns:
+    return temporary point equal to the center
+
+  see:
+    qh_voronoi_center()
+*/
+pointT *qh_facetcenter(qhT *qh, setT *vertices) {
+  setT *points= qh_settemp(qh, qh_setsize(qh, vertices));
+  vertexT *vertex, **vertexp;
+  pointT *center;
+
+  FOREACHvertex_(vertices)
+    qh_setappend(qh, &points, vertex->point);
+  center= qh_voronoi_center(qh, qh->hull_dim-1, points);
+  qh_settempfree(qh, &points);
+  return center;
+} /* facetcenter */
+
+/*---------------------------------
+
+  qh_findgooddist(qh, point, facetA, dist, facetlist )
+    find best good facet visible for point from facetA
+    assumes facetA is visible from point
+
+  returns:
+    best facet, i.e., good facet that is furthest from point
+      distance to best facet
+      NULL if none
+
+    moves good, visible facets (and some other visible facets)
+      to end of qh->facet_list
+
+  notes:
+    uses qh->visit_id
+
+  design:
+    initialize bestfacet if facetA is good
+    move facetA to end of facetlist
+    for each facet on facetlist
+      for each unvisited neighbor of facet
+        move visible neighbors to end of facetlist
+        update best good neighbor
+        if no good neighbors, update best facet
+*/
+facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp,
+               facetT **facetlist) {
+  realT bestdist= -REALmax, dist;
+  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
+  boolT goodseen= False;
+
+  if (facetA->good) {
+    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
+    qh_distplane(qh, point, facetA, &bestdist);
+    bestfacet= facetA;
+    goodseen= True;
+  }
+  qh_removefacet(qh, facetA);
+  qh_appendfacet(qh, facetA);
+  *facetlist= facetA;
+  facetA->visitid= ++qh->visit_id;
+  FORALLfacet_(*facetlist) {
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid == qh->visit_id)
+        continue;
+      neighbor->visitid= qh->visit_id;
+      if (goodseen && !neighbor->good)
+        continue;
+      zzinc_(Zcheckpart);
+      qh_distplane(qh, point, neighbor, &dist);
+      if (dist > 0) {
+        qh_removefacet(qh, neighbor);
+        qh_appendfacet(qh, neighbor);
+        if (neighbor->good) {
+          goodseen= True;
+          if (dist > bestdist) {
+            bestdist= dist;
+            bestfacet= neighbor;
+          }
+        }
+      }
+    }
+  }
+  if (bestfacet) {
+    *distp= bestdist;
+    trace2((qh, qh->ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
+      qh_pointid(qh, point), bestdist, bestfacet->id));
+    return bestfacet;
+  }
+  trace4((qh, qh->ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
+      qh_pointid(qh, point), facetA->id));
+  return NULL;
+}  /* findgooddist */
+
+/*---------------------------------
+
+  qh_furthestnewvertex(qh, unvisited, facet, &maxdist )
+    return furthest unvisited, new vertex to a facet
+
+  return:
+    NULL if no vertex is above facet
+    maxdist to facet
+    updates v.visitid
+
+  notes:
+    Ignores vertices in facetB
+    Does not change qh.vertex_visit.  Use in conjunction with qh_furthestvertex
+*/
+vertexT *qh_furthestnewvertex(qhT *qh, unsigned int unvisited, facetT *facet, realT *maxdistp /* qh.newvertex_list */) {
+  vertexT *maxvertex= NULL, *vertex;
+  coordT dist, maxdist= 0.0;
+
+  FORALLvertex_(qh->newvertex_list) {
+    if (vertex->newfacet && vertex->visitid <= unvisited) {
+      vertex->visitid= qh->vertex_visit;
+      qh_distplane(qh, vertex->point, facet, &dist);
+      if (dist > maxdist) {
+        maxdist= dist;
+        maxvertex= vertex;
+      }
+    }
+  }
+  trace4((qh, qh->ferr, 4085, "qh_furthestnewvertex: v%d dist %2.2g is furthest new vertex for f%d\n",
+    getid_(maxvertex), maxdist, facet->id));
+  *maxdistp= maxdist;
+  return maxvertex;
+} /* furthestnewvertex */
+
+/*---------------------------------
+
+  qh_furthestvertex(qh, facetA, facetB, &maxdist, &mindist )
+    return furthest vertex in facetA from facetB, or NULL if none
+
+  return:
+    maxdist and mindist to facetB or 0.0 if none
+    updates qh.vertex_visit
+
+  notes:
+    Ignores vertices in facetB
+*/
+vertexT *qh_furthestvertex(qhT *qh, facetT *facetA, facetT *facetB, realT *maxdistp, realT *mindistp) {
+  vertexT *maxvertex= NULL, *vertex, **vertexp;
+  coordT dist, maxdist= -REALmax, mindist= REALmax;
+
+  qh->vertex_visit++;
+  FOREACHvertex_(facetB->vertices)
+    vertex->visitid= qh->vertex_visit;
+  FOREACHvertex_(facetA->vertices) {
+    if (vertex->visitid != qh->vertex_visit) {
+      vertex->visitid= qh->vertex_visit;
+      zzinc_(Zvertextests);
+      qh_distplane(qh, vertex->point, facetB, &dist);
+      if (!maxvertex) {
+        maxdist= dist;
+        mindist= dist;
+        maxvertex= vertex;
+      }else if (dist > maxdist) {
+        maxdist= dist;
+        maxvertex= vertex;
+      }else if (dist < mindist)
+        mindist= dist;
+    }
+  }
+  if (!maxvertex) {
+    trace3((qh, qh->ferr, 3067, "qh_furthestvertex: all vertices of f%d are in f%d.  Returning 0.0 for max and mindist\n",
+      facetA->id, facetB->id));
+    maxdist= mindist= 0.0;
+  }else {
+    trace4((qh, qh->ferr, 4084, "qh_furthestvertex: v%d dist %2.2g is furthest (mindist %2.2g) of f%d above f%d\n",
+      maxvertex->id, maxdist, mindist, facetA->id, facetB->id));
+  }
+  *maxdistp= maxdist;
+  *mindistp= mindist;
+  return maxvertex;
+} /* furthestvertex */
+
+/*---------------------------------
+
+  qh_getarea(qh, facetlist )
+    set area of all facets in facetlist
+    collect statistics
+    nop if hasAreaVolume
+
+  returns:
+    sets qh->totarea/totvol to total area and volume of convex hull
+    for Delaunay triangulation, computes projected area of the lower or upper hull
+      ignores upper hull if qh->ATinfinity
+
+  notes:
+    could compute outer volume by expanding facet area by rays from interior
+    the following attempt at perpendicular projection underestimated badly:
+      qh.totoutvol += (-dist + facet->maxoutside + qh->DISTround)
+                            * area/ qh->hull_dim;
+  design:
+    for each facet on facetlist
+      compute facet->area
+      update qh.totarea and qh.totvol
+*/
+void qh_getarea(qhT *qh, facetT *facetlist) {
+  realT area;
+  realT dist;
+  facetT *facet;
+
+  if (qh->hasAreaVolume)
+    return;
+  if (qh->REPORTfreq)
+    qh_fprintf(qh, qh->ferr, 8020, "computing area of each facet and volume of the convex hull\n");
+  else
+    trace1((qh, qh->ferr, 1001, "qh_getarea: computing area for each facet and its volume to qh.interior_point (dist*area/dim)\n"));
+  qh->totarea= qh->totvol= 0.0;
+  FORALLfacet_(facetlist) {
+    if (!facet->normal)
+      continue;
+    if (facet->upperdelaunay && qh->ATinfinity)
+      continue;
+    if (!facet->isarea) {
+      facet->f.area= qh_facetarea(qh, facet);
+      facet->isarea= True;
+    }
+    area= facet->f.area;
+    if (qh->DELAUNAY) {
+      if (facet->upperdelaunay == qh->UPPERdelaunay)
+        qh->totarea += area;
+    }else {
+      qh->totarea += area;
+      qh_distplane(qh, qh->interior_point, facet, &dist);
+      qh->totvol += -dist * area/ qh->hull_dim;
+    }
+    if (qh->PRINTstatistics) {
+      wadd_(Wareatot, area);
+      wmax_(Wareamax, area);
+      wmin_(Wareamin, area);
+    }
+  }
+  qh->hasAreaVolume= True;
+} /* getarea */
+
+/*---------------------------------
+
+  qh_gram_schmidt(qh, dim, row )
+    implements Gram-Schmidt orthogonalization by rows
+
+  returns:
+    false if zero norm
+    overwrites rows[dim][dim]
+
+  notes:
+    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
+    overflow due to small divisors not handled
+
+  design:
+    for each row
+      compute norm for row
+      if non-zero, normalize row
+      for each remaining rowA
+        compute inner product of row and rowA
+        reduce rowA by row * inner product
+*/
+boolT qh_gram_schmidt(qhT *qh, int dim, realT **row) {
+  realT *rowi, *rowj, norm;
+  int i, j, k;
+
+  for (i=0; i < dim; i++) {
+    rowi= row[i];
+    for (norm=0.0, k=dim; k--; rowi++)
+      norm += *rowi * *rowi;
+    norm= sqrt(norm);
+    wmin_(Wmindenom, norm);
+    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
+      return False;
+    for (k=dim; k--; )
+      *(--rowi) /= norm;
+    for (j=i+1; j < dim; j++) {
+      rowj= row[j];
+      for (norm=0.0, k=dim; k--; )
+        norm += *rowi++ * *rowj++;
+      for (k=dim; k--; )
+        *(--rowj) -= *(--rowi) * norm;
+    }
+  }
+  return True;
+} /* gram_schmidt */
+
+
+/*---------------------------------
+
+  qh_inthresholds(qh, normal, angle )
+    return True if normal within qh.lower_/upper_threshold
+
+  returns:
+    estimate of angle by summing of threshold diffs
+      angle may be NULL
+      smaller "angle" is better
+
+  notes:
+    invalid if qh.SPLITthresholds
+
+  see:
+    qh.lower_threshold in qh_initbuild()
+    qh_initthresholds()
+
+  design:
+    for each dimension
+      test threshold
+*/
+boolT qh_inthresholds(qhT *qh, coordT *normal, realT *angle) {
+  boolT within= True;
+  int k;
+  realT threshold;
+
+  if (angle)
+    *angle= 0.0;
+  for (k=0; k < qh->hull_dim; k++) {
+    threshold= qh->lower_threshold[k];
+    if (threshold > -REALmax/2) {
+      if (normal[k] < threshold)
+        within= False;
+      if (angle) {
+        threshold -= normal[k];
+        *angle += fabs_(threshold);
+      }
+    }
+    if (qh->upper_threshold[k] < REALmax/2) {
+      threshold= qh->upper_threshold[k];
+      if (normal[k] > threshold)
+        within= False;
+      if (angle) {
+        threshold -= normal[k];
+        *angle += fabs_(threshold);
+      }
+    }
+  }
+  return within;
+} /* inthresholds */
+
+
+/*---------------------------------
+
+  qh_joggleinput(qh)
+    randomly joggle input to Qhull by qh.JOGGLEmax
+    initial input is qh.first_point/qh.num_points of qh.hull_dim
+      repeated calls use qh.input_points/qh.num_points
+
+  returns:
+    joggles points at qh.first_point/qh.num_points
+    copies data to qh.input_points/qh.input_malloc if first time
+    determines qh.JOGGLEmax if it was zero
+    if qh.DELAUNAY
+      computes the Delaunay projection of the joggled points
+
+  notes:
+    if qh.DELAUNAY, unnecessarily joggles the last coordinate
+    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
+
+  design:
+    if qh.DELAUNAY
+      set qh.SCALElast for reduced precision errors
+    if first call
+      initialize qh.input_points to the original input points
+      if qh.JOGGLEmax == 0
+        determine default qh.JOGGLEmax
+    else
+      increase qh.JOGGLEmax according to qh.build_cnt
+    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
+    if qh.DELAUNAY
+      sets the Delaunay projection
+*/
+void qh_joggleinput(qhT *qh) {
+  int i, seed, size;
+  coordT *coordp, *inputp;
+  realT randr, randa, randb;
+
+  if (!qh->input_points) { /* first call */
+    qh->input_points= qh->first_point;
+    qh->input_malloc= qh->POINTSmalloc;
+    size= qh->num_points * qh->hull_dim * (int)sizeof(coordT);
+    if (!(qh->first_point= (coordT *)qh_malloc((size_t)size))) {
+      qh_fprintf(qh, qh->ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
+          qh->num_points);
+      qh_errexit(qh, qh_ERRmem, NULL, NULL);
+    }
+    qh->POINTSmalloc= True;
+    if (qh->JOGGLEmax == 0.0) {
+      qh->JOGGLEmax= qh_detjoggle(qh, qh->input_points, qh->num_points, qh->hull_dim);
+      qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
+    }
+  }else {                 /* repeated call */
+    if (!qh->RERUN && qh->build_cnt > qh_JOGGLEretry) {
+      if (((qh->build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
+        realT maxjoggle= qh->MAXwidth * qh_JOGGLEmaxincrease;
+        if (qh->JOGGLEmax < maxjoggle) {
+          qh->JOGGLEmax *= qh_JOGGLEincrease;
+          minimize_(qh->JOGGLEmax, maxjoggle);
+        }
+      }
+    }
+    qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
+  }
+  if (qh->build_cnt > 1 && qh->JOGGLEmax > fmax_(qh->MAXwidth/4, 0.1)) {
+      qh_fprintf(qh, qh->ferr, 6010, "qhull input error (qh_joggleinput): the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
+                qh->JOGGLEmax);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  /* for some reason, using qh->ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
+  seed= qh_RANDOMint;
+  qh_option(qh, "_joggle-seed", &seed, NULL);
+  trace0((qh, qh->ferr, 6, "qh_joggleinput: joggle input by %4.4g with seed %d\n",
+    qh->JOGGLEmax, seed));
+  inputp= qh->input_points;
+  coordp= qh->first_point;
+  randa= 2.0 * qh->JOGGLEmax/qh_RANDOMmax;
+  randb= -qh->JOGGLEmax;
+  size= qh->num_points * qh->hull_dim;
+  for (i=size; i--; ) {
+    randr= qh_RANDOMint;
+    *(coordp++)= *(inputp++) + (randr * randa + randb);
+  }
+  if (qh->DELAUNAY) {
+    qh->last_low= qh->last_high= qh->last_newhigh= REALmax;
+    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
+  }
+} /* joggleinput */
+
+/*---------------------------------
+
+  qh_maxabsval( normal, dim )
+    return pointer to maximum absolute value of a dim vector
+    returns NULL if dim=0
+*/
+realT *qh_maxabsval(realT *normal, int dim) {
+  realT maxval= -REALmax;
+  realT *maxp= NULL, *colp, absval;
+  int k;
+
+  for (k=dim, colp= normal; k--; colp++) {
+    absval= fabs_(*colp);
+    if (absval > maxval) {
+      maxval= absval;
+      maxp= colp;
+    }
+  }
+  return maxp;
+} /* maxabsval */
+
+
+/*---------------------------------
+
+  qh_maxmin(qh, points, numpoints, dimension )
+    return max/min points for each dimension
+    determine max and min coordinates
+
+  returns:
+    returns a temporary set of max and min points
+      may include duplicate points. Does not include qh.GOODpoint
+    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
+         qh.MAXlastcoord, qh.MINlastcoord
+    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
+
+  notes:
+    loop duplicated in qh_detjoggle()
+
+  design:
+    initialize global precision variables
+    checks definition of REAL...
+    for each dimension
+      for each point
+        collect maximum and minimum point
+      collect maximum of maximums and minimum of minimums
+      determine qh.NEARzero for Gaussian Elimination
+*/
+setT *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension) {
+  int k;
+  realT maxcoord, temp;
+  pointT *minimum, *maximum, *point, *pointtemp;
+  setT *set;
+
+  qh->max_outside= 0.0;
+  qh->MAXabs_coord= 0.0;
+  qh->MAXwidth= -REALmax;
+  qh->MAXsumcoord= 0.0;
+  qh->min_vertex= 0.0;
+  qh->WAScoplanar= False;
+  if (qh->ZEROcentrum)
+    qh->ZEROall_ok= True;
+  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
+  && REALmax > 0.0 && -REALmax < 0.0)
+    ; /* all ok */
+  else {
+    qh_fprintf(qh, qh->ferr, 6011, "qhull error: one or more floating point constants in user_r.h are inconsistent. REALmin %g, -REALmax %g, 0.0, REALepsilon %g, REALmax %g\n",
+          REALmin, -REALmax, REALepsilon, REALmax);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  set= qh_settemp(qh, 2*dimension);
+  trace1((qh, qh->ferr, 8082, "qh_maxmin: dim             min             max           width    nearzero  min-point  max-point\n"));
+  for (k=0; k < dimension; k++) {
+    if (points == qh->GOODpointp)
+      minimum= maximum= points + dimension;
+    else
+      minimum= maximum= points;
+    FORALLpoint_(qh, points, numpoints) {
+      if (point == qh->GOODpointp)
+        continue;
+      if (maximum[k] < point[k])
+        maximum= point;
+      else if (minimum[k] > point[k])
+        minimum= point;
+    }
+    if (k == dimension-1) {
+      qh->MINlastcoord= minimum[k];
+      qh->MAXlastcoord= maximum[k];
+    }
+    if (qh->SCALElast && k == dimension-1)
+      maxcoord= qh->MAXabs_coord;
+    else {
+      maxcoord= fmax_(maximum[k], -minimum[k]);
+      if (qh->GOODpointp) {
+        temp= fmax_(qh->GOODpointp[k], -qh->GOODpointp[k]);
+        maximize_(maxcoord, temp);
+      }
+      temp= maximum[k] - minimum[k];
+      maximize_(qh->MAXwidth, temp);
+    }
+    maximize_(qh->MAXabs_coord, maxcoord);
+    qh->MAXsumcoord += maxcoord;
+    qh_setappend(qh, &set, minimum);
+    qh_setappend(qh, &set, maximum);
+    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
+       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
+       Golub & van Loan say that n^3 can be ignored and 10 be used in
+       place of rho */
+    qh->NEARzero[k]= 80 * qh->MAXsumcoord * REALepsilon;
+    trace1((qh, qh->ferr, 8106, "           %3d % 14.8e % 14.8e % 14.8e  %4.4e  p%-9d p%-d\n",
+            k, minimum[k], maximum[k], maximum[k]-minimum[k], qh->NEARzero[k], qh_pointid(qh, minimum), qh_pointid(qh, maximum)));
+    if (qh->SCALElast && k == dimension-1)
+      trace1((qh, qh->ferr, 8107, "           last coordinate scaled to (%4.4g, %4.4g), width %4.4e for option 'Qbb'\n",
+            qh->MAXabs_coord - qh->MAXwidth, qh->MAXabs_coord, qh->MAXwidth));
+  }
+  if (qh->IStracing >= 1)
+    qh_printpoints(qh, qh->ferr, "qh_maxmin: found the max and min points (by dim):", set);
+  return(set);
+} /* maxmin */
+
+/*---------------------------------
+
+  qh_maxouter(qh)
+    return maximum distance from facet to outer plane
+    normally this is qh.max_outside+qh.DISTround
+    does not include qh.JOGGLEmax
+
+  see:
+    qh_outerinner()
+
+  notes:
+    need to add another qh.DISTround if testing actual point with computation
+    see qh_detmaxoutside for a qh_RATIO... target
+
+  for joggle:
+    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
+    need to use Wnewvertexmax since could have a coplanar point for a high
+      facet that is replaced by a low facet
+    need to add qh.JOGGLEmax if testing input points
+*/
+realT qh_maxouter(qhT *qh) {
+  realT dist;
+
+  dist= fmax_(qh->max_outside, qh->DISTround);
+  dist += qh->DISTround;
+  trace4((qh, qh->ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %4.4g, qh.max_outside is %4.4g\n", dist, qh->max_outside));
+  return dist;
+} /* maxouter */
+
+/*---------------------------------
+
+  qh_maxsimplex(qh, dim, maxpoints, points, numpoints, simplex )
+    determines maximum simplex for a set of points
+    maxpoints is the subset of points with a min or max coordinate
+    may start with points already in simplex
+    skips qh.GOODpointp (assumes that it isn't in maxpoints)
+
+  returns:
+    simplex with dim+1 points
+
+  notes:
+    called by qh_initialvertices, qh_detvnorm, and qh_voronoi_center
+    requires qh.MAXwidth to estimate determinate for each vertex
+    assumes at least needed points in points
+    maximizes determinate for x,y,z,w, etc.
+    uses maxpoints as long as determinate is clearly non-zero
+
+  design:
+    initialize simplex with at least two points
+      (find points with max or min x coordinate)
+    create a simplex of dim+1 vertices as follows
+      add point from maxpoints that maximizes the determinate of the point and the simplex vertices  
+      if last point and maxdet/prevdet < qh_RATIOmaxsimplex (3.0e-2)
+        flag maybe_falsenarrow
+      if no maxpoint or maxnearzero or maybe_falsenarrow
+        search all points for maximum determinate
+        early exit if maybe_falsenarrow and !maxnearzero and maxdet > prevdet
+*/
+void qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
+  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
+  boolT nearzero, maxnearzero= False, maybe_falsenarrow;
+  int i, sizinit;
+  realT maxdet= -1.0, prevdet= -1.0, det, mincoord= REALmax, maxcoord= -REALmax, mindet, ratio, targetdet;
+
+  if (qh->MAXwidth <= 0.0) {
+    qh_fprintf(qh, qh->ferr, 6421, "qhull internal error (qh_maxsimplex): qh.MAXwidth required for qh_maxsimplex.  Used to estimate determinate\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  sizinit= qh_setsize(qh, *simplex);
+  if (sizinit >= 2) {
+    maxdet= pow(qh->MAXwidth, sizinit - 1);
+  }else {
+    if (qh_setsize(qh, maxpoints) >= 2) {
+      FOREACHpoint_(maxpoints) {
+        if (maxcoord < point[0]) {
+          maxcoord= point[0];
+          maxx= point;
+        }
+        if (mincoord > point[0]) {
+          mincoord= point[0];
+          minx= point;
+        }
+      }
+    }else {
+      FORALLpoint_(qh, points, numpoints) {
+        if (point == qh->GOODpointp)
+          continue;
+        if (maxcoord < point[0]) {
+          maxcoord= point[0];
+          maxx= point;
+        }
+        if (mincoord > point[0]) {
+          mincoord= point[0];
+          minx= point;
+        }
+      }
+    }
+    maxdet= maxcoord - mincoord;
+    qh_setunique(qh, simplex, minx);
+    if (qh_setsize(qh, *simplex) < 2)
+      qh_setunique(qh, simplex, maxx);
+    sizinit= qh_setsize(qh, *simplex);
+    if (sizinit < 2) {
+      qh_joggle_restart(qh, "input has same x coordinate");
+      if (zzval_(Zsetplane) > qh->hull_dim+1) {
+        qh_fprintf(qh, qh->ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center): %d points with the same x coordinate %4.4g\n",
+                 qh_setsize(qh, maxpoints)+numpoints, mincoord);
+        qh_errexit(qh, qh_ERRprec, NULL, NULL);
+      }else {
+        qh_fprintf(qh, qh->ferr, 6013, "qhull input error: input is less than %d-dimensional since all points have the same x coordinate %4.4g\n",
+                 qh->hull_dim, mincoord);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }
+    }
+  }
+  for (i=sizinit; i < dim+1; i++) {
+    prevdet= maxdet;
+    maxpoint= NULL;
+    maxdet= -1.0;
+    FOREACHpoint_(maxpoints) {
+      if (!qh_setin(*simplex, point) && point != maxpoint) {
+        det= qh_detsimplex(qh, point, *simplex, i, &nearzero); /* retests maxpoints if duplicate or multiple iterations */
+        if ((det= fabs_(det)) > maxdet) {
+          maxdet= det;
+          maxpoint= point;
+          maxnearzero= nearzero;
+        }
+      }
+    }
+    maybe_falsenarrow= False;
+    ratio= 1.0;
+    targetdet= prevdet * qh->MAXwidth;
+    mindet= 10 * qh_RATIOmaxsimplex * targetdet;
+    if (maxdet > 0.0) {
+      ratio= maxdet / targetdet;
+      if (ratio < qh_RATIOmaxsimplex)
+        maybe_falsenarrow= True;
+    }
+    if (!maxpoint || maxnearzero || maybe_falsenarrow) {
+      zinc_(Zsearchpoints);
+      if (!maxpoint) {
+        trace0((qh, qh->ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex, better than mindet %4.4g, targetdet %4.4g\n",
+                i+1, mindet, targetdet));
+      }else if (qh->ALLpoints) {
+        trace0((qh, qh->ferr, 30, "qh_maxsimplex: searching all points ('Qs') for %d-th initial vertex, better than p%d det %4.4g, targetdet %4.4g, ratio %4.4g\n",
+                i+1, qh_pointid(qh, maxpoint), maxdet, targetdet, ratio));
+      }else if (maybe_falsenarrow) {
+        trace0((qh, qh->ferr, 17, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %4.4g and mindet %4.4g, ratio %4.4g\n",
+                i+1, qh_pointid(qh, maxpoint), maxdet, mindet, ratio));
+      }else {
+        trace0((qh, qh->ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g and mindet %4.4g, targetdet %4.4g\n",
+                i+1, qh_pointid(qh, maxpoint), maxdet, mindet, targetdet));
+      }
+      FORALLpoint_(qh, points, numpoints) {
+        if (point == qh->GOODpointp)
+          continue;
+        if (!qh_setin(maxpoints, point) && !qh_setin(*simplex, point)) {
+          det= qh_detsimplex(qh, point, *simplex, i, &nearzero);
+          if ((det= fabs_(det)) > maxdet) {
+            maxdet= det;
+            maxpoint= point;
+            maxnearzero= nearzero;
+            if (!maxnearzero && !qh->ALLpoints && maxdet > mindet)
+              break;
+          }
+        }
+      }
+    } /* !maxpoint */
+    if (!maxpoint) {
+      qh_fprintf(qh, qh->ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    qh_setappend(qh, simplex, maxpoint);
+    trace1((qh, qh->ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%4.4g, targetdet=%4.4g, mindet=%4.4g\n",
+            qh_pointid(qh, maxpoint), i+1, maxdet, prevdet * qh->MAXwidth, mindet));
+  } /* i */
+} /* maxsimplex */
+
+/*---------------------------------
+
+  qh_minabsval( normal, dim )
+    return minimum absolute value of a dim vector
+*/
+realT qh_minabsval(realT *normal, int dim) {
+  realT minval= 0;
+  realT maxval= 0;
+  realT *colp;
+  int k;
+
+  for (k=dim, colp=normal; k--; colp++) {
+    maximize_(maxval, *colp);
+    minimize_(minval, *colp);
+  }
+  return fmax_(maxval, -minval);
+} /* minabsval */
+
+
+/*---------------------------------
+
+  qh_mindif(qh, vecA, vecB, dim )
+    return index of min abs. difference of two vectors
+*/
+int qh_mindiff(realT *vecA, realT *vecB, int dim) {
+  realT mindiff= REALmax, diff;
+  realT *vecAp= vecA, *vecBp= vecB;
+  int k, mink= 0;
+
+  for (k=0; k < dim; k++) {
+    diff= *vecAp++ - *vecBp++;
+    diff= fabs_(diff);
+    if (diff < mindiff) {
+      mindiff= diff;
+      mink= k;
+    }
+  }
+  return mink;
+} /* mindiff */
+
+
+
+/*---------------------------------
+
+  qh_orientoutside(qh, facet  )
+    make facet outside oriented via qh.interior_point
+
+  returns:
+    True if facet reversed orientation.
+*/
+boolT qh_orientoutside(qhT *qh, facetT *facet) {
+  int k;
+  realT dist;
+
+  qh_distplane(qh, qh->interior_point, facet, &dist);
+  if (dist > 0) {
+    for (k=qh->hull_dim; k--; )
+      facet->normal[k]= -facet->normal[k];
+    facet->offset= -facet->offset;
+    return True;
+  }
+  return False;
+} /* orientoutside */
+
+/*---------------------------------
+
+  qh_outerinner(qh, facet, outerplane, innerplane  )
+    if facet and qh.maxoutdone (i.e., qh_check_maxout)
+      returns outer and inner plane for facet
+    else
+      returns maximum outer and inner plane
+    accounts for qh.JOGGLEmax
+
+  see:
+    qh_maxouter(qh), qh_check_bestdist(), qh_check_points()
+
+  notes:
+    outerplaner or innerplane may be NULL
+    facet is const
+    Does not error (QhullFacet)
+
+    includes qh.DISTround for actual points
+    adds another qh.DISTround if testing with floating point arithmetic
+*/
+void qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
+  realT dist, mindist;
+  vertexT *vertex, **vertexp;
+
+  if (outerplane) {
+    if (!qh_MAXoutside || !facet || !qh->maxoutdone) {
+      *outerplane= qh_maxouter(qh);       /* includes qh.DISTround */
+    }else { /* qh_MAXoutside ... */
+#if qh_MAXoutside
+      *outerplane= facet->maxoutside + qh->DISTround;
+#endif
+
+    }
+    if (qh->JOGGLEmax < REALmax/2)
+      *outerplane += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
+  }
+  if (innerplane) {
+    if (facet) {
+      mindist= REALmax;
+      FOREACHvertex_(facet->vertices) {
+        zinc_(Zdistio);
+        qh_distplane(qh, vertex->point, facet, &dist);
+        minimize_(mindist, dist);
+      }
+      *innerplane= mindist - qh->DISTround;
+    }else
+      *innerplane= qh->min_vertex - qh->DISTround;
+    if (qh->JOGGLEmax < REALmax/2)
+      *innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
+  }
+} /* outerinner */
+
+/*---------------------------------
+
+  qh_pointdist( point1, point2, dim )
+    return distance between two points
+
+  notes:
+    returns distance squared if 'dim' is negative
+*/
+coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
+  coordT dist, diff;
+  int k;
+
+  dist= 0.0;
+  for (k= (dim > 0 ? dim : -dim); k--; ) {
+    diff= *point1++ - *point2++;
+    dist += diff * diff;
+  }
+  if (dim > 0)
+    return(sqrt(dist));
+  return dist;
+} /* pointdist */
+
+
+/*---------------------------------
+
+  qh_printmatrix(qh, fp, string, rows, numrow, numcol )
+    print matrix to fp given by row vectors
+    print string as header
+    qh may be NULL if fp is defined
+
+  notes:
+    print a vector by qh_printmatrix(qh, fp, "", &vect, 1, len)
+*/
+void qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
+  realT *rowp;
+  realT r; /*bug fix*/
+  int i,k;
+
+  qh_fprintf(qh, fp, 9001, "%s\n", string);
+  for (i=0; i < numrow; i++) {
+    rowp= rows[i];
+    for (k=0; k < numcol; k++) {
+      r= *rowp++;
+      qh_fprintf(qh, fp, 9002, "%6.3g ", r);
+    }
+    qh_fprintf(qh, fp, 9003, "\n");
+  }
+} /* printmatrix */
+
+
+/*---------------------------------
+
+  qh_printpoints(qh, fp, string, points )
+    print pointids to fp for a set of points
+    if string, prints string and 'p' point ids
+*/
+void qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points) {
+  pointT *point, **pointp;
+
+  if (string) {
+    qh_fprintf(qh, fp, 9004, "%s", string);
+    FOREACHpoint_(points)
+      qh_fprintf(qh, fp, 9005, " p%d", qh_pointid(qh, point));
+    qh_fprintf(qh, fp, 9006, "\n");
+  }else {
+    FOREACHpoint_(points)
+      qh_fprintf(qh, fp, 9007, " %d", qh_pointid(qh, point));
+    qh_fprintf(qh, fp, 9008, "\n");
+  }
+} /* printpoints */
+
+
+/*---------------------------------
+
+  qh_projectinput(qh)
+    project input points using qh.lower_bound/upper_bound and qh->DELAUNAY
+    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
+      removes dimension k
+    if halfspace intersection
+      removes dimension k from qh.feasible_point
+    input points in qh->first_point, num_points, input_dim
+
+  returns:
+    new point array in qh->first_point of qh->hull_dim coordinates
+    sets qh->POINTSmalloc
+    if qh->DELAUNAY
+      projects points to paraboloid
+      lowbound/highbound is also projected
+    if qh->ATinfinity
+      adds point "at-infinity"
+    if qh->POINTSmalloc
+      frees old point array
+
+  notes:
+    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
+
+
+  design:
+    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
+    determines newdim and newnum for qh->hull_dim and qh->num_points
+    projects points to newpoints
+    projects qh.lower_bound to itself
+    projects qh.upper_bound to itself
+    if qh->DELAUNAY
+      if qh->ATINFINITY
+        projects points to paraboloid
+        computes "infinity" point as vertex average and 10% above all points
+      else
+        uses qh_setdelaunay to project points to paraboloid
+*/
+void qh_projectinput(qhT *qh) {
+  int k,i;
+  int newdim= qh->input_dim, newnum= qh->num_points;
+  signed char *project;
+  int projectsize= (qh->input_dim + 1) * (int)sizeof(*project);
+  pointT *newpoints, *coord, *infinity;
+  realT paraboloid, maxboloid= 0;
+
+  project= (signed char *)qh_memalloc(qh, projectsize);
+  memset((char *)project, 0, (size_t)projectsize);
+  for (k=0; k < qh->input_dim; k++) {   /* skip Delaunay bound */
+    if (qh->lower_bound[k] == 0.0 && qh->upper_bound[k] == 0.0) {
+      project[k]= -1;
+      newdim--;
+    }
+  }
+  if (qh->DELAUNAY) {
+    project[k]= 1;
+    newdim++;
+    if (qh->ATinfinity)
+      newnum++;
+  }
+  if (newdim != qh->hull_dim) {
+    qh_memfree(qh, project, projectsize);
+    qh_fprintf(qh, qh->ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh->hull_dim);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (!(newpoints= qh->temp_malloc= (coordT *)qh_malloc((size_t)(newnum * newdim) * sizeof(coordT)))) {
+    qh_memfree(qh, project, projectsize);
+    qh_fprintf(qh, qh->ferr, 6016, "qhull error: insufficient memory to project %d points\n",
+           qh->num_points);
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  /* qh_projectpoints throws error if mismatched dimensions */
+  qh_projectpoints(qh, project, qh->input_dim+1, qh->first_point,
+                    qh->num_points, qh->input_dim, newpoints, newdim);
+  trace1((qh, qh->ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
+  qh_projectpoints(qh, project, qh->input_dim+1, qh->lower_bound,
+                    1, qh->input_dim+1, qh->lower_bound, newdim+1);
+  qh_projectpoints(qh, project, qh->input_dim+1, qh->upper_bound,
+                    1, qh->input_dim+1, qh->upper_bound, newdim+1);
+  if (qh->HALFspace) {
+    if (!qh->feasible_point) {
+      qh_memfree(qh, project, projectsize);
+      qh_fprintf(qh, qh->ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    qh_projectpoints(qh, project, qh->input_dim, qh->feasible_point,
+                      1, qh->input_dim, qh->feasible_point, newdim);
+  }
+  qh_memfree(qh, project, projectsize);
+  if (qh->POINTSmalloc)
+    qh_free(qh->first_point);
+  qh->first_point= newpoints;
+  qh->POINTSmalloc= True;
+  qh->temp_malloc= NULL;
+  if (qh->DELAUNAY && qh->ATinfinity) {
+    coord= qh->first_point;
+    infinity= qh->first_point + qh->hull_dim * qh->num_points;
+    for (k=qh->hull_dim-1; k--; )
+      infinity[k]= 0.0;
+    for (i=qh->num_points; i--; ) {
+      paraboloid= 0.0;
+      for (k=0; k < qh->hull_dim-1; k++) {
+        paraboloid += *coord * *coord;
+        infinity[k] += *coord;
+        coord++;
+      }
+      *(coord++)= paraboloid;
+      maximize_(maxboloid, paraboloid);
+    }
+    /* coord == infinity */
+    for (k=qh->hull_dim-1; k--; )
+      *(coord++) /= qh->num_points;
+    *(coord++)= maxboloid * 1.1;
+    qh->num_points++;
+    trace0((qh, qh->ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
+  }else if (qh->DELAUNAY)  /* !qh->ATinfinity */
+    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
+} /* projectinput */
+
+
+/*---------------------------------
+
+  qh_projectpoints(qh, project, n, points, numpoints, dim, newpoints, newdim )
+    project points/numpoints/dim to newpoints/newdim
+    if project[k] == -1
+      delete dimension k
+    if project[k] == 1
+      add dimension k by duplicating previous column
+    n is size of project
+
+  notes:
+    newpoints may be points if only adding dimension at end
+
+  design:
+    check that 'project' and 'newdim' agree
+    for each dimension
+      if project == -1
+        skip dimension
+      else
+        determine start of column in newpoints
+        determine start of column in points
+          if project == +1, duplicate previous column
+        copy dimension (column) from points to newpoints
+*/
+void qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
+        int numpoints, int dim, realT *newpoints, int newdim) {
+  int testdim= dim, oldk=0, newk=0, i,j=0,k;
+  realT *newp, *oldp;
+
+  for (k=0; k < n; k++)
+    testdim += project[k];
+  if (testdim != newdim) {
+    qh_fprintf(qh, qh->ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
+      newdim, testdim);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  for (j=0; j= dim)
+          continue;
+        oldp= points+oldk;
+      }else
+        oldp= points+oldk++;
+      for (i=numpoints; i--; ) {
+        *newp= *oldp;
+        newp += newdim;
+        oldp += dim;
+      }
+    }
+    if (oldk >= dim)
+      break;
+  }
+  trace1((qh, qh->ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
+    numpoints, dim, newdim));
+} /* projectpoints */
+
+
+/*---------------------------------
+
+  qh_rotateinput(qh, rows )
+    rotate input using row matrix
+    input points given by qh->first_point, num_points, hull_dim
+    assumes rows[dim] is a scratch buffer
+    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
+
+  returns:
+    rotated input
+    sets qh->POINTSmalloc
+
+  design:
+    see qh_rotatepoints
+*/
+void qh_rotateinput(qhT *qh, realT **rows) {
+
+  if (!qh->POINTSmalloc) {
+    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
+    qh->POINTSmalloc= True;
+  }
+  qh_rotatepoints(qh, qh->first_point, qh->num_points, qh->hull_dim, rows);
+}  /* rotateinput */
+
+/*---------------------------------
+
+  qh_rotatepoints(qh, points, numpoints, dim, row )
+    rotate numpoints points by a d-dim row matrix
+    assumes rows[dim] is a scratch buffer
+
+  returns:
+    rotated points in place
+
+  design:
+    for each point
+      for each coordinate
+        use row[dim] to compute partial inner product
+      for each coordinate
+        rotate by partial inner product
+*/
+void qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **row) {
+  realT *point, *rowi, *coord= NULL, sum, *newval;
+  int i,j,k;
+
+  if (qh->IStracing >= 1)
+    qh_printmatrix(qh, qh->ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
+  for (point=points, j=numpoints; j--; point += dim) {
+    newval= row[dim];
+    for (i=0; i < dim; i++) {
+      rowi= row[i];
+      coord= point;
+      for (sum=0.0, k=dim; k--; )
+        sum += *rowi++ * *coord++;
+      *(newval++)= sum;
+    }
+    for (k=dim; k--; )
+      *(--coord)= *(--newval);
+  }
+} /* rotatepoints */
+
+
+/*---------------------------------
+
+  qh_scaleinput(qh)
+    scale input points using qh->low_bound/high_bound
+    input points given by qh->first_point, num_points, hull_dim
+    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
+
+  returns:
+    scales coordinates of points to low_bound[k], high_bound[k]
+    sets qh->POINTSmalloc
+
+  design:
+    see qh_scalepoints
+*/
+void qh_scaleinput(qhT *qh) {
+
+  if (!qh->POINTSmalloc) {
+    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
+    qh->POINTSmalloc= True;
+  }
+  qh_scalepoints(qh, qh->first_point, qh->num_points, qh->hull_dim,
+       qh->lower_bound, qh->upper_bound);
+}  /* scaleinput */
+
+/*---------------------------------
+
+  qh_scalelast(qh, points, numpoints, dim, low, high, newhigh )
+    scale last coordinate to [0.0, newhigh], for Delaunay triangulation
+    input points given by points, numpoints, dim
+
+  returns:
+    changes scale of last coordinate from [low, high] to [0.0, newhigh]
+    overwrites last coordinate of each point
+    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
+
+  notes:
+    to reduce precision issues, qh_scalelast makes the last coordinate similar to other coordinates
+      the last coordinate for Delaunay triangulation is the sum of squares of input coordinates
+      note that the range [0.0, newwidth] is wrong for narrow distributions with large positive coordinates (e.g., [995933.64, 995963.48])
+
+    when called by qh_setdelaunay, low/high may not match the data passed to qh_setdelaunay
+
+  design:
+    compute scale and shift factors
+    apply to last coordinate of each point
+*/
+void qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
+                   coordT high, coordT newhigh) {
+  realT scale, shift;
+  coordT *coord, newlow;
+  int i;
+  boolT nearzero= False;
+
+  newlow= 0.0;
+  trace4((qh, qh->ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [%2.2g, %2.2g]\n",
+    low, high, newlow, newhigh));
+  qh->last_low= low;
+  qh->last_high= high;
+  qh->last_newhigh= newhigh;
+  scale= qh_divzero(newhigh - newlow, high - low,
+                  qh->MINdenom_1, &nearzero);
+  if (nearzero) {
+    if (qh->DELAUNAY)
+      qh_fprintf(qh, qh->ferr, 6019, "qhull input error (qh_scalelast): can not scale last coordinate to [%4.4g, %4.4g].  Input is cocircular or cospherical.   Use option 'Qz' to add a point at infinity.\n",
+             newlow, newhigh);
+    else
+      qh_fprintf(qh, qh->ferr, 6020, "qhull input error (qh_scalelast): can not scale last coordinate to [%4.4g, %4.4g].  New bounds are too wide for compared to existing bounds [%4.4g, %4.4g] (width %4.4g)\n",
+             newlow, newhigh, low, high, high-low);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  shift= newlow - low * scale;
+  coord= points + dim - 1;
+  for (i=numpoints; i--; coord += dim)
+    *coord= *coord * scale + shift;
+} /* scalelast */
+
+/*---------------------------------
+
+  qh_scalepoints(qh, points, numpoints, dim, newlows, newhighs )
+    scale points to new lowbound and highbound
+    retains old bound when newlow= -REALmax or newhigh= +REALmax
+
+  returns:
+    scaled points
+    overwrites old points
+
+  design:
+    for each coordinate
+      compute current low and high bound
+      compute scale and shift factors
+      scale all points
+      enforce new low and high bound for all points
+*/
+void qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
+        realT *newlows, realT *newhighs) {
+  int i,k;
+  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
+  boolT nearzero= False;
+
+  for (k=0; k < dim; k++) {
+    newhigh= newhighs[k];
+    newlow= newlows[k];
+    if (newhigh > REALmax/2 && newlow < -REALmax/2)
+      continue;
+    low= REALmax;
+    high= -REALmax;
+    for (i=numpoints, coord=points+k; i--; coord += dim) {
+      minimize_(low, *coord);
+      maximize_(high, *coord);
+    }
+    if (newhigh > REALmax/2)
+      newhigh= high;
+    if (newlow < -REALmax/2)
+      newlow= low;
+    if (qh->DELAUNAY && k == dim-1 && newhigh < newlow) {
+      qh_fprintf(qh, qh->ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
+               k, k, newhigh, newlow);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    scale= qh_divzero(newhigh - newlow, high - low,
+                  qh->MINdenom_1, &nearzero);
+    if (nearzero) {
+      qh_fprintf(qh, qh->ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
+              k, newlow, newhigh, low, high);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    shift= (newlow * high - low * newhigh)/(high-low);
+    coord= points+k;
+    for (i=numpoints; i--; coord += dim)
+      *coord= *coord * scale + shift;
+    coord= points+k;
+    if (newlow < newhigh) {
+      mincoord= newlow;
+      maxcoord= newhigh;
+    }else {
+      mincoord= newhigh;
+      maxcoord= newlow;
+    }
+    for (i=numpoints; i--; coord += dim) {
+      minimize_(*coord, maxcoord);  /* because of roundoff error */
+      maximize_(*coord, mincoord);
+    }
+    trace0((qh, qh->ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
+      k, low, high, newlow, newhigh, numpoints, scale, shift));
+  }
+} /* scalepoints */
+
+
+/*---------------------------------
+
+  qh_setdelaunay(qh, dim, count, points )
+    project count points to dim-d paraboloid for Delaunay triangulation
+
+    dim is one more than the dimension of the input set
+    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
+
+    points is a dim*count realT array.  The first dim-1 coordinates
+    are the coordinates of the first input point.  array[dim] is
+    the first coordinate of the second input point.  array[2*dim] is
+    the first coordinate of the third input point.
+
+    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
+      calls qh_scalelast to scale the last coordinate the same as the other points
+
+  returns:
+    for each point
+      sets point[dim-1] to sum of squares of coordinates
+    scale points to 'Qbb' if needed
+
+  notes:
+    to project one point, use
+      qh_setdelaunay(qh, qh->hull_dim, 1, point)
+
+    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
+    the coordinates after the original projection.
+
+*/
+void qh_setdelaunay(qhT *qh, int dim, int count, pointT *points) {
+  int i, k;
+  coordT *coordp, coord;
+  realT paraboloid;
+
+  trace0((qh, qh->ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
+  coordp= points;
+  for (i=0; i < count; i++) {
+    coord= *coordp++;
+    paraboloid= coord*coord;
+    for (k=dim-2; k--; ) {
+      coord= *coordp++;
+      paraboloid += coord*coord;
+    }
+    *coordp++= paraboloid;
+  }
+  if (qh->last_low < REALmax/2)
+    qh_scalelast(qh, points, count, dim, qh->last_low, qh->last_high, qh->last_newhigh);
+} /* setdelaunay */
+
+
+/*---------------------------------
+
+  qh_sethalfspace(qh, dim, coords, nextp, normal, offset, feasible )
+    set point to dual of halfspace relative to feasible point
+    halfspace is normal coefficients and offset.
+
+  returns:
+    false and prints error if feasible point is outside of hull
+    overwrites coordinates for point at dim coords
+    nextp= next point (coords)
+    does not call qh_errexit
+
+  design:
+    compute distance from feasible point to halfspace
+    divide each normal coefficient by -dist
+*/
+boolT qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
+         coordT *normal, coordT *offset, coordT *feasible) {
+  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
+  realT dist;
+  realT r; /*bug fix*/
+  int k;
+  boolT zerodiv;
+
+  dist= *offset;
+  for (k=dim; k--; )
+    dist += *(normp++) * *(feasiblep++);
+  if (dist > 0)
+    goto LABELerroroutside;
+  normp= normal;
+  if (dist < -qh->MINdenom) {
+    for (k=dim; k--; )
+      *(coordp++)= *(normp++) / -dist;
+  }else {
+    for (k=dim; k--; ) {
+      *(coordp++)= qh_divzero(*(normp++), -dist, qh->MINdenom_1, &zerodiv);
+      if (zerodiv)
+        goto LABELerroroutside;
+    }
+  }
+  *nextp= coordp;
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4) {
+    qh_fprintf(qh, qh->ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
+    for (k=dim, coordp=coords; k--; ) {
+      r= *coordp++;
+      qh_fprintf(qh, qh->ferr, 8022, " %6.2g", r);
+    }
+    qh_fprintf(qh, qh->ferr, 8023, "\n");
+  }
+#endif
+  return True;
+LABELerroroutside:
+  feasiblep= feasible;
+  normp= normal;
+  qh_fprintf(qh, qh->ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
+  for (k=dim; k--; )
+    qh_fprintf(qh, qh->ferr, 8024, qh_REAL_1, r=*(feasiblep++));
+  qh_fprintf(qh, qh->ferr, 8025, "\n     halfspace: ");
+  for (k=dim; k--; )
+    qh_fprintf(qh, qh->ferr, 8026, qh_REAL_1, r=*(normp++));
+  qh_fprintf(qh, qh->ferr, 8027, "\n     at offset: ");
+  qh_fprintf(qh, qh->ferr, 8028, qh_REAL_1, *offset);
+  qh_fprintf(qh, qh->ferr, 8029, " and distance: ");
+  qh_fprintf(qh, qh->ferr, 8030, qh_REAL_1, dist);
+  qh_fprintf(qh, qh->ferr, 8031, "\n");
+  return False;
+} /* sethalfspace */
+
+/*---------------------------------
+
+  qh_sethalfspace_all(qh, dim, count, halfspaces, feasible )
+    generate dual for halfspace intersection with feasible point
+    array of count halfspaces
+      each halfspace is normal coefficients followed by offset
+      the origin is inside the halfspace if the offset is negative
+    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
+
+  returns:
+    malloc'd array of count X dim-1 points
+
+  notes:
+    call before qh_init_B or qh_initqhull_globals
+    free memory when done
+    unused/untested code: please email bradb@shore.net if this works ok for you
+    if using option 'Fp', qh.feasible_point must be set (e.g., to 'feasible')
+    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
+
+  design:
+    see qh_sethalfspace
+*/
+coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible) {
+  int i, newdim;
+  pointT *newpoints;
+  coordT *coordp, *normalp, *offsetp;
+
+  trace0((qh, qh->ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
+  newdim= dim - 1;
+  if (!(newpoints= (coordT *)qh_malloc((size_t)(count * newdim) * sizeof(coordT)))){
+    qh_fprintf(qh, qh->ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
+          count);
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  coordp= newpoints;
+  normalp= halfspaces;
+  for (i=0; i < count; i++) {
+    offsetp= normalp + newdim;
+    if (!qh_sethalfspace(qh, newdim, coordp, &coordp, normalp, offsetp, feasible)) {
+      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
+      qh_fprintf(qh, qh->ferr, 8032, "The halfspace was at index %d\n", i);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    normalp= offsetp + 1;
+  }
+  return newpoints;
+} /* sethalfspace_all */
+
+
+/*---------------------------------
+
+  qh_sharpnewfacets(qh)
+
+  returns:
+    true if could be an acute angle (facets in different quadrants)
+
+  notes:
+    for qh_findbest
+
+  design:
+    for all facets on qh.newfacet_list
+      if two facets are in different quadrants
+        set issharp
+*/
+boolT qh_sharpnewfacets(qhT *qh) {
+  facetT *facet;
+  boolT issharp= False;
+  int *quadrant, k;
+
+  quadrant= (int *)qh_memalloc(qh, qh->hull_dim * (int)sizeof(int));
+  FORALLfacet_(qh->newfacet_list) {
+    if (facet == qh->newfacet_list) {
+      for (k=qh->hull_dim; k--; )
+        quadrant[ k]= (facet->normal[ k] > 0);
+    }else {
+      for (k=qh->hull_dim; k--; ) {
+        if (quadrant[ k] != (facet->normal[ k] > 0)) {
+          issharp= True;
+          break;
+        }
+      }
+    }
+    if (issharp)
+      break;
+  }
+  qh_memfree(qh, quadrant, qh->hull_dim * (int)sizeof(int));
+  trace3((qh, qh->ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
+  return issharp;
+} /* sharpnewfacets */
+
+/*---------------------------------
+
+  qh_vertex_bestdist(qh, vertices )
+  qh_vertex_bestdist2(qh, vertices, vertexp, vertexp2 )
+    return nearest distance between vertices
+    optionally returns vertex and vertex2
+
+  notes:
+    called by qh_partitioncoplanar, qh_mergefacet, qh_check_maxout, qh_checkpoint
+*/
+coordT qh_vertex_bestdist(qhT *qh, setT *vertices) {
+  vertexT *vertex, *vertex2;
+
+  return qh_vertex_bestdist2(qh, vertices, &vertex, &vertex2);
+} /* vertex_bestdist */
+
+coordT qh_vertex_bestdist2(qhT *qh, setT *vertices, vertexT **vertexp/*= NULL*/, vertexT **vertexp2/*= NULL*/) {
+  vertexT *vertex, *vertexA, *bestvertex= NULL, *bestvertex2= NULL;
+  coordT dist, bestdist= REALmax;
+  int k, vertex_i, vertex_n;
+
+  FOREACHvertex_i_(qh, vertices) {
+    for (k= vertex_i+1; k < vertex_n; k++) {
+      vertexA= SETelemt_(vertices, k, vertexT);
+      dist= qh_pointdist(vertex->point, vertexA->point, -qh->hull_dim);
+      if (dist < bestdist) {
+        bestdist= dist;
+        bestvertex= vertex;
+        bestvertex2= vertexA;
+      }
+    }
+  }
+  *vertexp= bestvertex;
+  *vertexp2= bestvertex2;
+  return sqrt(bestdist);
+} /* vertex_bestdist */
+
+/*---------------------------------
+
+  qh_voronoi_center(qh, dim, points )
+    return Voronoi center for a set of points
+    dim is the original dimension of the points
+    gh.gm_matrix/qh.gm_row are scratch buffers
+
+  returns:
+    center as a temporary point (qh_memalloc)
+    if non-simplicial,
+      returns center for max simplex of points
+
+  notes:
+    only called by qh_facetcenter
+    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
+
+  design:
+    if non-simplicial
+      determine max simplex for points
+    translate point0 of simplex to origin
+    compute sum of squares of diagonal
+    compute determinate
+    compute Voronoi center (see Bowyer & Woodwark)
+*/
+pointT *qh_voronoi_center(qhT *qh, int dim, setT *points) {
+  pointT *point, **pointp, *point0;
+  pointT *center= (pointT *)qh_memalloc(qh, qh->center_size);
+  setT *simplex;
+  int i, j, k, size= qh_setsize(qh, points);
+  coordT *gmcoord;
+  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
+  boolT nearzero, infinite;
+
+  if (size == dim+1)
+    simplex= points;
+  else if (size < dim+1) {
+    qh_memfree(qh, center, qh->center_size);
+    qh_fprintf(qh, qh->ferr, 6025, "qhull internal error (qh_voronoi_center):  need at least %d points to construct a Voronoi center\n",
+             dim+1);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    simplex= points;  /* never executed -- avoids warning */
+  }else {
+    simplex= qh_settemp(qh, dim+1);
+    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
+  }
+  point0= SETfirstt_(simplex, pointT);
+  gmcoord= qh->gm_matrix;
+  for (k=0; k < dim; k++) {
+    qh->gm_row[k]= gmcoord;
+    FOREACHpoint_(simplex) {
+      if (point != point0)
+        *(gmcoord++)= point[k] - point0[k];
+    }
+  }
+  sum2row= gmcoord;
+  for (i=0; i < dim; i++) {
+    sum2= 0.0;
+    for (k=0; k < dim; k++) {
+      diffp= qh->gm_row[k] + i;
+      sum2 += *diffp * *diffp;
+    }
+    *(gmcoord++)= sum2;
+  }
+  det= qh_determinant(qh, qh->gm_row, dim, &nearzero);
+  factor= qh_divzero(0.5, det, qh->MINdenom, &infinite);
+  if (infinite) {
+    for (k=dim; k--; )
+      center[k]= qh_INFINITE;
+    if (qh->IStracing)
+      qh_printpoints(qh, qh->ferr, "qh_voronoi_center: at infinity for ", simplex);
+  }else {
+    for (i=0; i < dim; i++) {
+      gmcoord= qh->gm_matrix;
+      sum2p= sum2row;
+      for (k=0; k < dim; k++) {
+        qh->gm_row[k]= gmcoord;
+        if (k == i) {
+          for (j=dim; j--; )
+            *(gmcoord++)= *sum2p++;
+        }else {
+          FOREACHpoint_(simplex) {
+            if (point != point0)
+              *(gmcoord++)= point[k] - point0[k];
+          }
+        }
+      }
+      center[i]= qh_determinant(qh, qh->gm_row, dim, &nearzero)*factor + point0[i];
+    }
+#ifndef qh_NOtrace
+    if (qh->IStracing >= 3) {
+      qh_fprintf(qh, qh->ferr, 3061, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
+      qh_printmatrix(qh, qh->ferr, "center:", ¢er, 1, dim);
+      if (qh->IStracing >= 5) {
+        qh_printpoints(qh, qh->ferr, "points", simplex);
+        FOREACHpoint_(simplex)
+          qh_fprintf(qh, qh->ferr, 8034, "p%d dist %.2g, ", qh_pointid(qh, point),
+                   qh_pointdist(point, center, dim));
+        qh_fprintf(qh, qh->ferr, 8035, "\n");
+      }
+    }
+#endif
+  }
+  if (simplex != points)
+    qh_settempfree(qh, &simplex);
+  return center;
+} /* voronoi_center */
+
diff --git a/vendor/qhull/libqhull_r/geom_r.c b/vendor/qhull/libqhull_r/geom_r.c
new file mode 100644
index 0000000..22faead
--- /dev/null
+++ b/vendor/qhull/libqhull_r/geom_r.c
@@ -0,0 +1,1284 @@
+/*
  ---------------------------------
+
+   geom_r.c
+   geometric routines of qhull
+
+   see qh-geom_r.htm and geom_r.h
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/geom_r.c#5 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+
+   infrequent code goes into geom2_r.c
+*/
+
+#include "qhull_ra.h"
+
+/*---------------------------------
+
+  qh_distplane(qh, point, facet, dist )
+    return distance from point to facet
+
+  returns:
+    dist
+    if qh.RANDOMdist, joggles result
+
+  notes:
+    dist > 0 if point is above facet (i.e., outside)
+    does not error (for qh_sortfacets, qh_outerinner)
+    for nearly coplanar points, the returned values may be duplicates
+      for example pairs of nearly incident points, rbox 175 C1,2e-13 t1538759579 | qhull d T4
+      622 qh_distplane: e-014  # count of two or more duplicate values for unique calls
+      258 qh_distplane: e-015
+      38 qh_distplane: e-016
+      40 qh_distplane: e-017
+      6 qh_distplane: e-018
+      5 qh_distplane: -e-018
+      33 qh_distplane: -e-017
+         3153 qh_distplane: -2.775557561562891e-017  # duplicated value for 3153 unique calls
+      42 qh_distplane: -e-016
+      307 qh_distplane: -e-015
+      1271 qh_distplane: -e-014
+      13 qh_distplane: -e-013
+
+  see:
+    qh_distnorm in geom2_r.c
+    qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
+*/
+void qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist) {
+  coordT *normal= facet->normal, *coordp, randr;
+  int k;
+
+  switch (qh->hull_dim){
+  case 2:
+    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
+    break;
+  case 3:
+    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
+    break;
+  case 4:
+    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
+    break;
+  case 5:
+    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
+    break;
+  case 6:
+    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
+    break;
+  case 7:
+    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
+    break;
+  case 8:
+    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
+    break;
+  default:
+    *dist= facet->offset;
+    coordp= point;
+    for (k=qh->hull_dim; k--; )
+      *dist += *coordp++ * *normal++;
+    break;
+  }
+  zzinc_(Zdistplane);
+  if (!qh->RANDOMdist && qh->IStracing < 4)
+    return;
+  if (qh->RANDOMdist) {
+    randr= qh_RANDOMint;
+    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
+      qh->RANDOMfactor * qh->MAXabs_coord;
+  }
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4) {
+    qh_fprintf(qh, qh->ferr, 8001, "qh_distplane: ");
+    qh_fprintf(qh, qh->ferr, 8002, qh_REAL_1, *dist);
+    qh_fprintf(qh, qh->ferr, 8003, "from p%d to f%d\n", qh_pointid(qh, point), facet->id);
+  }
+#endif
+  return;
+} /* distplane */
+
+
+/*---------------------------------
+
+  qh_findbest(qh, point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
+    find facet that is furthest below a point
+    for upperDelaunay facets
+      returns facet only if !qh_NOupper and clearly above
+
+  input:
+    starts search at 'startfacet' (can not be flipped)
+    if !bestoutside(qh_ALL), stops at qh.MINoutside
+
+  returns:
+    best facet (reports error if NULL)
+    early out if isoutside defined and bestdist > qh.MINoutside
+    dist is distance to facet
+    isoutside is true if point is outside of facet
+    numpart counts the number of distance tests
+
+  see also:
+    qh_findbestnew()
+
+  notes:
+    If merging (testhorizon), searches horizon facets of coplanar best facets because
+    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
+      avoid calls to distplane, function calls, and real number operations.
+    caller traces result
+    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
+    Made code more complicated.
+
+  when called by qh_partitionvisible():
+    indicated by qh_ISnewfacets
+    qh.newfacet_list is list of simplicial, new facets
+    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
+    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
+
+  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
+                 qh_check_bestdist(), qh_addpoint()
+    indicated by !qh_ISnewfacets
+    returns best facet in neighborhood of given facet
+      this is best facet overall if dist >= -qh.MAXcoplanar
+        or hull has at least a "spherical" curvature
+
+  design:
+    initialize and test for early exit
+    repeat while there are better facets
+      for each neighbor of facet
+        exit if outside facet found
+        test for better facet
+    if point is inside and partitioning
+      test for new facets with a "sharp" intersection
+      if so, future calls go to qh_findbestnew()
+    test horizon facets
+*/
+facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
+                     boolT bestoutside, boolT isnewfacets, boolT noupper,
+                     realT *dist, boolT *isoutside, int *numpart) {
+  realT bestdist= -REALmax/2 /* avoid underflow */;
+  facetT *facet, *neighbor, **neighborp;
+  facetT *bestfacet= NULL, *lastfacet= NULL;
+  int oldtrace= qh->IStracing;
+  unsigned int visitid= ++qh->visit_id;
+  int numpartnew=0;
+  boolT testhorizon= True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
+
+  zinc_(Zfindbest);
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
+    if (qh->TRACElevel > qh->IStracing)
+      qh->IStracing= qh->TRACElevel;
+    qh_fprintf(qh, qh->ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g,",
+             qh_pointid(qh, point), startfacet->id, isnewfacets, bestoutside, qh->MINoutside);
+    qh_fprintf(qh, qh->ferr, 8005, " testhorizon? %d, noupper? %d,", testhorizon, noupper);
+    qh_fprintf(qh, qh->ferr, 8006, " Last qh_addpoint p%d,", qh->furthest_id);
+    qh_fprintf(qh, qh->ferr, 8007, " Last merge #%d, max_outside %2.2g\n", zzval_(Ztotmerge), qh->max_outside);
+  }
+#endif
+  if (isoutside)
+    *isoutside= True;
+  if (!startfacet->flipped) {  /* test startfacet before testing its neighbors */
+    *numpart= 1;
+    qh_distplane(qh, point, startfacet, dist);  /* this code is duplicated below */
+    if (!bestoutside && *dist >= qh->MINoutside
+    && (!startfacet->upperdelaunay || !noupper)) {
+      bestfacet= startfacet;
+      goto LABELreturn_best;
+    }
+    bestdist= *dist;
+    if (!startfacet->upperdelaunay) {
+      bestfacet= startfacet;
+    }
+  }else
+    *numpart= 0;
+  startfacet->visitid= visitid;
+  facet= startfacet;
+  while (facet) {
+    trace4((qh, qh->ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
+                facet->id, bestdist, getid_(bestfacet)));
+    lastfacet= facet;
+    FOREACHneighbor_(facet) {
+      if (!neighbor->newfacet && isnewfacets)
+        continue;
+      if (neighbor->visitid == visitid)
+        continue;
+      neighbor->visitid= visitid;
+      if (!neighbor->flipped) {  /* code duplicated above */
+        (*numpart)++;
+        qh_distplane(qh, point, neighbor, dist);
+        if (*dist > bestdist) {
+          if (!bestoutside && *dist >= qh->MINoutside
+          && (!neighbor->upperdelaunay || !noupper)) {
+            bestfacet= neighbor;
+            goto LABELreturn_best;
+          }
+          if (!neighbor->upperdelaunay) {
+            bestfacet= neighbor;
+            bestdist= *dist;
+            break; /* switch to neighbor */
+          }else if (!bestfacet) {
+            bestdist= *dist;
+            break; /* switch to neighbor */
+          }
+        } /* end of *dist>bestdist */
+      } /* end of !flipped */
+    } /* end of FOREACHneighbor */
+    facet= neighbor;  /* non-NULL only if *dist>bestdist */
+  } /* end of while facet (directed search) */
+  if (isnewfacets) {
+    if (!bestfacet) { /* startfacet is upperdelaunay (or flipped) w/o !flipped newfacet neighbors */
+      bestdist= -REALmax/2;
+      bestfacet= qh_findbestnew(qh, point, qh->newfacet_list, &bestdist, bestoutside, isoutside, &numpartnew);
+      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
+    }else if (!qh->findbest_notsharp && bestdist < -qh->DISTround) {
+      if (qh_sharpnewfacets(qh)) {
+        /* seldom used, qh_findbestnew will retest all facets */
+        zinc_(Zfindnewsharp);
+        bestfacet= qh_findbestnew(qh, point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
+        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
+        qh->findbestnew= True;
+      }else
+        qh->findbest_notsharp= True;
+    }
+  }
+  if (!bestfacet)
+    bestfacet= qh_findbestlower(qh, lastfacet, point, &bestdist, numpart); /* lastfacet is non-NULL because startfacet is non-NULL */
+  if (testhorizon) /* qh_findbestnew not called */
+    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
+  *dist= bestdist;
+  if (isoutside && bestdist < qh->MINoutside)
+    *isoutside= False;
+LABELreturn_best:
+  zadd_(Zfindbesttot, *numpart);
+  zmax_(Zfindbestmax, *numpart);
+  (*numpart) += numpartnew;
+  qh->IStracing= oldtrace;
+  return bestfacet;
+}  /* findbest */
+
+
+/*---------------------------------
+
+  qh_findbesthorizon(qh, qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
+    search coplanar and better horizon facets from startfacet/bestdist
+    ischeckmax turns off statistics and minsearch update
+    all arguments must be initialized, including *bestdist and *numpart
+    qh.coplanarfacetset used to maintain current search set, reset whenever best facet is substantially better
+  returns(ischeckmax):
+    best facet
+    updates f.maxoutside for neighbors of searched facets (if qh_MAXoutside)
+  returns(!ischeckmax):
+    best facet that is not upperdelaunay or newfacet (qh.first_newfacet)
+    allows upperdelaunay that is clearly outside
+  returns:
+    bestdist is distance to bestfacet
+    numpart -- updates number of distance tests
+
+  notes:
+    called by qh_findbest if point is not outside a facet (directed search)
+    called by qh_findbestnew if point is not outside a new facet
+    called by qh_check_maxout for each point in hull
+    called by qh_check_bestdist for each point in hull (rarely used)
+
+    no early out -- use qh_findbest() or qh_findbestnew()
+    Searches coplanar or better horizon facets
+
+  when called by qh_check_maxout() (qh_IScheckmax)
+    startfacet must be closest to the point
+      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
+      even though other facets are below the point.
+    updates facet->maxoutside for good, visited facets
+    may return NULL
+
+    searchdist is qh.max_outside + 2 * DISTround
+      + max( MINvisible('Vn'), MAXcoplanar('Un'));
+    This setting is a guess.  It must be at least max_outside + 2*DISTround
+    because a facet may have a geometric neighbor across a vertex
+
+  design:
+    for each horizon facet of coplanar best facets
+      continue if clearly inside
+      unless upperdelaunay or clearly outside
+         update best facet
+*/
+facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
+  facetT *bestfacet= startfacet;
+  realT dist;
+  facetT *neighbor, **neighborp, *facet;
+  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
+  int numpartinit= *numpart, coplanarfacetset_size, numcoplanar= 0, numfacet= 0;
+  unsigned int visitid= ++qh->visit_id;
+  boolT newbest= False; /* for tracing */
+  realT minsearch, searchdist;  /* skip facets that are too far from point */
+  boolT is_5x_minsearch;
+
+  if (!ischeckmax) {
+    zinc_(Zfindhorizon);
+  }else {
+#if qh_MAXoutside
+    if ((!qh->ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
+      startfacet->maxoutside= *bestdist;
+#endif
+  }
+  searchdist= qh_SEARCHdist; /* an expression, a multiple of qh.max_outside and precision constants */
+  minsearch= *bestdist - searchdist;
+  if (ischeckmax) {
+    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
+    minimize_(minsearch, -searchdist);
+  }
+  coplanarfacetset_size= 0;
+  startfacet->visitid= visitid;
+  facet= startfacet;
+  while (True) {
+    numfacet++;
+    is_5x_minsearch= (ischeckmax && facet->nummerge > 10 && qh_setsize(qh, facet->neighbors) > 100);  /* QH11033 FIX: qh_findbesthorizon: many tests for facets with many merges and neighbors.  Can hide coplanar facets, e.g., 'rbox 1000 s Z1 G1e-13' with 4400+ neighbors */
+    trace4((qh, qh->ferr, 4002, "qh_findbesthorizon: test neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g is_5x? %d searchdist %2.2g\n",
+                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
+                minsearch, is_5x_minsearch, searchdist));
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid == visitid)
+        continue;
+      neighbor->visitid= visitid;
+      if (!neighbor->flipped) {  /* neighbors of flipped facets always searched via nextfacet */
+        qh_distplane(qh, point, neighbor, &dist); /* duplicate qh_distpane for new facets, they may be coplanar */
+        (*numpart)++;
+        if (dist > *bestdist) {
+          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh->MINoutside)) {
+            if (!ischeckmax) {
+              minsearch= dist - searchdist;
+              if (dist > *bestdist + searchdist) {
+                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
+                coplanarfacetset_size= 0;
+              }
+            }
+            bestfacet= neighbor;
+            *bestdist= dist;
+            newbest= True;
+          }
+        }else if (is_5x_minsearch) {
+          if (dist < 5 * minsearch)
+            continue; /* skip this neighbor, do not set nextfacet.  dist is negative */
+        }else if (dist < minsearch)
+          continue;  /* skip this neighbor, do not set nextfacet.  If ischeckmax, dist can't be positive */
+#if qh_MAXoutside
+        if (ischeckmax && dist > neighbor->maxoutside)
+          neighbor->maxoutside= dist;
+#endif
+      } /* end of !flipped, need to search neighbor */
+      if (nextfacet) {
+        numcoplanar++;
+        if (!coplanarfacetset_size++) {
+          SETfirst_(qh->coplanarfacetset)= nextfacet;
+          SETtruncate_(qh->coplanarfacetset, 1);
+        }else
+          qh_setappend(qh, &qh->coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
+                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
+      }
+      nextfacet= neighbor;
+    } /* end of EACHneighbor */
+    facet= nextfacet;
+    if (facet)
+      nextfacet= NULL;
+    else if (!coplanarfacetset_size)
+      break;
+    else if (!--coplanarfacetset_size) {
+      facet= SETfirstt_(qh->coplanarfacetset, facetT);
+      SETtruncate_(qh->coplanarfacetset, 0);
+    }else
+      facet= (facetT *)qh_setdellast(qh->coplanarfacetset);
+  } /* while True, i.e., "for each facet in qh.coplanarfacetset" */
+  if (!ischeckmax) {
+    zadd_(Zfindhorizontot, *numpart - numpartinit);
+    zmax_(Zfindhorizonmax, *numpart - numpartinit);
+    if (newbest)
+      zinc_(Znewbesthorizon);
+  }
+  trace4((qh, qh->ferr, 4003, "qh_findbesthorizon: p%d, newbest? %d, bestfacet f%d, bestdist %2.2g, numfacet %d, coplanarfacets %d, numdist %d\n",
+    qh_pointid(qh, point), newbest, getid_(bestfacet), *bestdist, numfacet, numcoplanar, *numpart - numpartinit));
+  return bestfacet;
+}  /* findbesthorizon */
+
+/*---------------------------------
+
+  qh_findbestnew(qh, point, startfacet, dist, isoutside, numpart )
+    find best newfacet for point
+    searches all of qh.newfacet_list starting at startfacet
+    searches horizon facets of coplanar best newfacets
+    searches all facets if startfacet == qh.facet_list
+  returns:
+    best new or horizon facet that is not upperdelaunay
+    early out if isoutside and not 'Qf'
+    dist is distance to facet
+    isoutside is true if point is outside of facet
+    numpart is number of distance tests
+
+  notes:
+    Always used for merged new facets (see qh_USEfindbestnew)
+    Avoids upperdelaunay facet unless (isoutside and outside)
+
+    Uses qh.visit_id, qh.coplanarfacetset.
+    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
+
+    If merging (testhorizon), searches horizon facets of coplanar best facets because
+    a point maybe coplanar to the bestfacet, below its horizon facet,
+    and above a horizon facet of a coplanar newfacet.  For example,
+      rbox 1000 s Z1 G1e-13 | qhull
+      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
+
+    qh_findbestnew() used if
+       qh_sharpnewfacets -- newfacets contains a sharp angle
+       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
+
+  see also:
+    qh_partitionall() and qh_findbest()
+
+  design:
+    for each new facet starting from startfacet
+      test distance from point to facet
+      return facet if clearly outside
+      unless upperdelaunay and a lowerdelaunay exists
+         update best facet
+    test horizon facets
+*/
+facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
+           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
+  realT bestdist= -REALmax/2;
+  facetT *bestfacet= NULL, *facet;
+  int oldtrace= qh->IStracing, i;
+  unsigned int visitid= ++qh->visit_id;
+  realT distoutside= 0.0;
+  boolT isdistoutside; /* True if distoutside is defined */
+  boolT testhorizon= True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
+
+  if (!startfacet || !startfacet->next) {
+    if (qh->MERGING) {
+      qh_fprintf(qh, qh->ferr, 6001, "qhull topology error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
+      qh_errexit(qh, qh_ERRtopology, NULL, NULL);
+    }else {
+      qh_fprintf(qh, qh->ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
+              qh->furthest_id);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+  }
+  zinc_(Zfindnew);
+  if (qh->BESToutside || bestoutside)
+    isdistoutside= False;
+  else {
+    isdistoutside= True;
+    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user_r.h */
+  }
+  if (isoutside)
+    *isoutside= True;
+  *numpart= 0;
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
+    if (qh->TRACElevel > qh->IStracing)
+      qh->IStracing= qh->TRACElevel;
+    qh_fprintf(qh, qh->ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g,",
+             qh_pointid(qh, point), startfacet->id, isdistoutside, distoutside);
+    qh_fprintf(qh, qh->ferr, 8009, " Last qh_addpoint p%d, qh.visit_id %d, vertex_visit %d,",  qh->furthest_id, visitid, qh->vertex_visit);
+    qh_fprintf(qh, qh->ferr, 8010, " Last merge #%d\n", zzval_(Ztotmerge));
+  }
+#endif
+  /* visit all new facets starting with startfacet, maybe qh->facet_list */
+  for (i=0, facet=startfacet; i < 2; i++, facet= qh->newfacet_list) {
+    FORALLfacet_(facet) {
+      if (facet == startfacet && i)
+        break;
+      facet->visitid= visitid;
+      if (!facet->flipped) {
+        qh_distplane(qh, point, facet, dist);
+        (*numpart)++;
+        if (*dist > bestdist) {
+          if (!facet->upperdelaunay || *dist >= qh->MINoutside) {
+            bestfacet= facet;
+            if (isdistoutside && *dist >= distoutside)
+              goto LABELreturn_bestnew;
+            bestdist= *dist;
+          }
+        }
+      } /* end of !flipped */
+    } /* FORALLfacet from startfacet or qh->newfacet_list */
+  }
+  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
+    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
+                                        !qh_NOupper, &bestdist, numpart);
+  *dist= bestdist;
+  if (isoutside && *dist < qh->MINoutside)
+    *isoutside= False;
+LABELreturn_bestnew:
+  zadd_(Zfindnewtot, *numpart);
+  zmax_(Zfindnewmax, *numpart);
+  trace4((qh, qh->ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g for p%d f%d bestoutside? %d \n",
+    getid_(bestfacet), *dist, qh_pointid(qh, point), startfacet->id, bestoutside));
+  qh->IStracing= oldtrace;
+  return bestfacet;
+}  /* findbestnew */
+
+/* ============ hyperplane functions -- keep code together [?] ============ */
+
+/*---------------------------------
+
+  qh_backnormal(qh, rows, numrow, numcol, sign, normal, nearzero )
+    given an upper-triangular rows array and a sign,
+    solve for normal equation x using back substitution over rows U
+
+  returns:
+     normal= x
+
+     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
+       if fails on last row
+         this means that the hyperplane intersects [0,..,1]
+         sets last coordinate of normal to sign
+       otherwise
+         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
+         sets nearzero
+
+  notes:
+     assumes numrow == numcol-1
+
+     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
+
+     solves Ux=b where Ax=b and PA=LU
+     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
+     last row of A= [0,...,0,1]
+
+     1) Ly=Pb == y=b since P only permutes the 0's of   b
+
+  design:
+    for each row from end
+      perform back substitution
+      if near zero
+        use qh_divzero for division
+        if zero divide and not last row
+          set tail of normal to 0
+*/
+void qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign,
+        coordT *normal, boolT *nearzero) {
+  int i, j;
+  coordT *normalp, *normal_tail, *ai, *ak;
+  realT diagonal;
+  boolT waszero;
+  int zerocol= -1;
+
+  normalp= normal + numcol - 1;
+  *normalp--= (sign ? -1.0 : 1.0);
+  for (i=numrow; i--; ) {
+    *normalp= 0.0;
+    ai= rows[i] + i + 1;
+    ak= normalp+1;
+    for (j=i+1; j < numcol; j++)
+      *normalp -= *ai++ * *ak++;
+    diagonal= (rows[i])[i];
+    if (fabs_(diagonal) > qh->MINdenom_2)
+      *(normalp--) /= diagonal;
+    else {
+      waszero= False;
+      *normalp= qh_divzero(*normalp, diagonal, qh->MINdenom_1_2, &waszero);
+      if (waszero) {
+        zerocol= i;
+        *(normalp--)= (sign ? -1.0 : 1.0);
+        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
+          *normal_tail= 0.0;
+      }else
+        normalp--;
+    }
+  }
+  if (zerocol != -1) {
+    *nearzero= True;
+    trace4((qh, qh->ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
+    zzinc_(Zback0);
+    qh_joggle_restart(qh, "zero diagonal on back substitution");
+  }
+} /* backnormal */
+
+/*---------------------------------
+
+  qh_gausselim(qh, rows, numrow, numcol, sign )
+    Gaussian elimination with partial pivoting
+
+  returns:
+    rows is upper triangular (includes row exchanges)
+    flips sign for each row exchange
+    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
+
+  notes:
+    if nearzero, the determinant's sign may be incorrect.
+    assumes numrow <= numcol
+
+  design:
+    for each row
+      determine pivot and exchange rows if necessary
+      test for near zero
+      perform gaussian elimination step
+*/
+void qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
+  realT *ai, *ak, *rowp, *pivotrow;
+  realT n, pivot, pivot_abs= 0.0, temp;
+  int i, j, k, pivoti, flip=0;
+
+  *nearzero= False;
+  for (k=0; k < numrow; k++) {
+    pivot_abs= fabs_((rows[k])[k]);
+    pivoti= k;
+    for (i=k+1; i < numrow; i++) {
+      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
+        pivot_abs= temp;
+        pivoti= i;
+      }
+    }
+    if (pivoti != k) {
+      rowp= rows[pivoti];
+      rows[pivoti]= rows[k];
+      rows[k]= rowp;
+      *sign ^= 1;
+      flip ^= 1;
+    }
+    if (pivot_abs <= qh->NEARzero[k]) {
+      *nearzero= True;
+      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
+#ifndef qh_NOtrace
+        if (qh->IStracing >= 4) {
+          qh_fprintf(qh, qh->ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh->DISTround);
+          qh_printmatrix(qh, qh->ferr, "Matrix:", rows, numrow, numcol);
+        }
+#endif
+        zzinc_(Zgauss0);
+        qh_joggle_restart(qh, "zero pivot for Gaussian elimination");
+        goto LABELnextcol;
+      }
+    }
+    pivotrow= rows[k] + k;
+    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
+    for (i=k+1; i < numrow; i++) {
+      ai= rows[i] + k;
+      ak= pivotrow;
+      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
+      for (j= numcol - (k+1); j--; )
+        *ai++ -= n * *ak++;
+    }
+  LABELnextcol:
+    ;
+  }
+  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
+  if (qh->IStracing >= 5)
+    qh_printmatrix(qh, qh->ferr, "qh_gausselem: result", rows, numrow, numcol);
+} /* gausselim */
+
+
+/*---------------------------------
+
+  qh_getangle(qh, vect1, vect2 )
+    returns the dot product of two vectors
+    if qh.RANDOMdist, joggles result
+
+  notes:
+    the angle may be > 1.0 or < -1.0 because of roundoff errors
+
+*/
+realT qh_getangle(qhT *qh, pointT *vect1, pointT *vect2) {
+  realT angle= 0, randr;
+  int k;
+
+  for (k=qh->hull_dim; k--; )
+    angle += *vect1++ * *vect2++;
+  if (qh->RANDOMdist) {
+    randr= qh_RANDOMint;
+    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
+      qh->RANDOMfactor;
+  }
+  trace4((qh, qh->ferr, 4006, "qh_getangle: %4.4g\n", angle));
+  return(angle);
+} /* getangle */
+
+
+/*---------------------------------
+
+  qh_getcenter(qh, vertices )
+    returns arithmetic center of a set of vertices as a new point
+
+  notes:
+    allocates point array for center
+*/
+pointT *qh_getcenter(qhT *qh, setT *vertices) {
+  int k;
+  pointT *center, *coord;
+  vertexT *vertex, **vertexp;
+  int count= qh_setsize(qh, vertices);
+
+  if (count < 2) {
+    qh_fprintf(qh, qh->ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  center= (pointT *)qh_memalloc(qh, qh->normal_size);
+  for (k=0; k < qh->hull_dim; k++) {
+    coord= center+k;
+    *coord= 0.0;
+    FOREACHvertex_(vertices)
+      *coord += vertex->point[k];
+    *coord /= count;  /* count>=2 by QH6003 */
+  }
+  return(center);
+} /* getcenter */
+
+
+/*---------------------------------
+
+  qh_getcentrum(qh, facet )
+    returns the centrum for a facet as a new point
+
+  notes:
+    allocates the centrum
+*/
+pointT *qh_getcentrum(qhT *qh, facetT *facet) {
+  realT dist;
+  pointT *centrum, *point;
+
+  point= qh_getcenter(qh, facet->vertices);
+  zzinc_(Zcentrumtests);
+  qh_distplane(qh, point, facet, &dist);
+  centrum= qh_projectpoint(qh, point, facet, dist);
+  qh_memfree(qh, point, qh->normal_size);
+  trace4((qh, qh->ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
+          facet->id, qh_setsize(qh, facet->vertices), dist));
+  return centrum;
+} /* getcentrum */
+
+
+/*---------------------------------
+
+  qh_getdistance(qh, facet, neighbor, mindist, maxdist )
+    returns the min and max distance to neighbor of non-neighbor vertices in facet
+
+  returns:
+    the max absolute value
+
+  design:
+    for each vertex of facet that is not in neighbor
+      test the distance from vertex to neighbor
+*/
+coordT qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, coordT *mindist, coordT *maxdist) {
+  vertexT *vertex, **vertexp;
+  coordT dist, maxd, mind;
+
+  FOREACHvertex_(facet->vertices)
+    vertex->seen= False;
+  FOREACHvertex_(neighbor->vertices)
+    vertex->seen= True;
+  mind= 0.0;
+  maxd= 0.0;
+  FOREACHvertex_(facet->vertices) {
+    if (!vertex->seen) {
+      zzinc_(Zbestdist);
+      qh_distplane(qh, vertex->point, neighbor, &dist);
+      if (dist < mind)
+        mind= dist;
+      else if (dist > maxd)
+        maxd= dist;
+    }
+  }
+  *mindist= mind;
+  *maxdist= maxd;
+  mind= -mind;
+  if (maxd > mind)
+    return maxd;
+  else
+    return mind;
+} /* getdistance */
+
+
+/*---------------------------------
+
+  qh_normalize(qh, normal, dim, toporient )
+    normalize a vector and report if too small
+    does not use min norm
+
+  see:
+    qh_normalize2
+*/
+void qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient) {
+  qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
+} /* normalize */
+
+/*---------------------------------
+
+  qh_normalize2(qh, normal, dim, toporient, minnorm, ismin )
+    normalize a vector and report if too small
+    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
+
+  returns:
+    normalized vector
+    flips sign if !toporient
+    if minnorm non-NULL,
+      sets ismin if normal < minnorm
+
+  notes:
+    if zero norm
+       sets all elements to sqrt(1.0/dim)
+    if divide by zero (divzero())
+       sets largest element to   +/-1
+       bumps Znearlysingular
+
+  design:
+    computes norm
+    test for minnorm
+    if not near zero
+      normalizes normal
+    else if zero norm
+      sets normal to standard value
+    else
+      uses qh_divzero to normalize
+      if nearzero
+        sets norm to direction of maximum value
+*/
+void qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
+            realT *minnorm, boolT *ismin) {
+  int k;
+  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
+  boolT zerodiv;
+
+  norm1= normal+1;
+  norm2= normal+2;
+  norm3= normal+3;
+  if (dim == 2)
+    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
+  else if (dim == 3)
+    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
+  else if (dim == 4) {
+    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
+               + (*norm3)*(*norm3));
+  }else if (dim > 4) {
+    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
+               + (*norm3)*(*norm3);
+    for (k=dim-4, colp=normal+4; k--; colp++)
+      norm += (*colp) * (*colp);
+    norm= sqrt(norm);
+  }
+  if (minnorm) {
+    if (norm < *minnorm)
+      *ismin= True;
+    else
+      *ismin= False;
+  }
+  wmin_(Wmindenom, norm);
+  if (norm > qh->MINdenom) {
+    if (!toporient)
+      norm= -norm;
+    *normal /= norm;
+    *norm1 /= norm;
+    if (dim == 2)
+      ; /* all done */
+    else if (dim == 3)
+      *norm2 /= norm;
+    else if (dim == 4) {
+      *norm2 /= norm;
+      *norm3 /= norm;
+    }else if (dim >4) {
+      *norm2 /= norm;
+      *norm3 /= norm;
+      for (k=dim-4, colp=normal+4; k--; )
+        *colp++ /= norm;
+    }
+  }else if (norm == 0.0) {
+    temp= sqrt(1.0/dim);
+    for (k=dim, colp=normal; k--; )
+      *colp++= temp;
+  }else {
+    if (!toporient)
+      norm= -norm;
+    for (k=dim, colp=normal; k--; colp++) { /* k used below */
+      temp= qh_divzero(*colp, norm, qh->MINdenom_1, &zerodiv);
+      if (!zerodiv)
+        *colp= temp;
+      else {
+        maxp= qh_maxabsval(normal, dim);
+        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
+        for (k=dim, colp=normal; k--; colp++)
+          *colp= 0.0;
+        *maxp= temp;
+        zzinc_(Znearlysingular);
+        /* qh_joggle_restart ignored for Znearlysingular, normal part of qh_sethyperplane_gauss */
+        trace0((qh, qh->ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
+               norm, qh->furthest_id));
+        return;
+      }
+    }
+  }
+} /* normalize */
+
+
+/*---------------------------------
+
+  qh_projectpoint(qh, point, facet, dist )
+    project point onto a facet by dist
+
+  returns:
+    returns a new point
+
+  notes:
+    if dist= distplane(point,facet)
+      this projects point to hyperplane
+    assumes qh_memfree_() is valid for normal_size
+*/
+pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist) {
+  pointT *newpoint, *np, *normal;
+  int normsize= qh->normal_size;
+  int k;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+
+  qh_memalloc_(qh, normsize, freelistp, newpoint, pointT);
+  np= newpoint;
+  normal= facet->normal;
+  for (k=qh->hull_dim; k--; )
+    *(np++)= *point++ - dist * *normal++;
+  return(newpoint);
+} /* projectpoint */
+
+
+/*---------------------------------
+
+  qh_setfacetplane(qh, facet )
+    sets the hyperplane for a facet
+    if qh.RANDOMdist, joggles hyperplane
+
+  notes:
+    uses global buffers qh.gm_matrix and qh.gm_row
+    overwrites facet->normal if already defined
+    updates Wnewvertex if PRINTstatistics
+    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
+
+  design:
+    copy vertex coordinates to qh.gm_matrix/gm_row
+    compute determinate
+    if nearzero
+      recompute determinate with gaussian elimination
+      if nearzero
+        force outside orientation by testing interior point
+*/
+void qh_setfacetplane(qhT *qh, facetT *facet) {
+  pointT *point;
+  vertexT *vertex, **vertexp;
+  int normsize= qh->normal_size;
+  int k,i, oldtrace= 0;
+  realT dist;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+  coordT *coord, *gmcoord;
+  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
+  boolT nearzero= False;
+
+  zzinc_(Zsetplane);
+  if (!facet->normal)
+    qh_memalloc_(qh, normsize, freelistp, facet->normal, coordT);
+#ifndef qh_NOtrace
+  if (facet == qh->tracefacet) {
+    oldtrace= qh->IStracing;
+    qh->IStracing= 5;
+    qh_fprintf(qh, qh->ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
+    qh_fprintf(qh, qh->ferr, 8013, "  Last point added to hull was p%d.", qh->furthest_id);
+    if (zzval_(Ztotmerge))
+      qh_fprintf(qh, qh->ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
+    qh_fprintf(qh, qh->ferr, 8015, "\n\nCurrent summary is:\n");
+      qh_printsummary(qh, qh->ferr);
+  }
+#endif
+  if (qh->hull_dim <= 4) {
+    i= 0;
+    if (qh->RANDOMdist) {
+      gmcoord= qh->gm_matrix;
+      FOREACHvertex_(facet->vertices) {
+        qh->gm_row[i++]= gmcoord;
+        coord= vertex->point;
+        for (k=qh->hull_dim; k--; )
+          *(gmcoord++)= *coord++ * qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
+      }
+    }else {
+      FOREACHvertex_(facet->vertices)
+       qh->gm_row[i++]= vertex->point;
+    }
+    qh_sethyperplane_det(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
+                facet->normal, &facet->offset, &nearzero);
+  }
+  if (qh->hull_dim > 4 || nearzero) {
+    i= 0;
+    gmcoord= qh->gm_matrix;
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->point != point0) {
+        qh->gm_row[i++]= gmcoord;
+        coord= vertex->point;
+        point= point0;
+        for (k=qh->hull_dim; k--; )
+          *(gmcoord++)= *coord++ - *point++;
+      }
+    }
+    qh->gm_row[i]= gmcoord;  /* for areasimplex */
+    if (qh->RANDOMdist) {
+      gmcoord= qh->gm_matrix;
+      for (i=qh->hull_dim-1; i--; ) {
+        for (k=qh->hull_dim; k--; )
+          *(gmcoord++) *= qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
+      }
+    }
+    qh_sethyperplane_gauss(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
+                facet->normal, &facet->offset, &nearzero);
+    if (nearzero) {
+      if (qh_orientoutside(qh, facet)) {
+        trace0((qh, qh->ferr, 2, "qh_setfacetplane: flipped orientation due to nearzero gauss and interior_point test.  During p%d\n", qh->furthest_id));
+      /* this is part of using Gaussian Elimination.  For example in 5-d
+           1 1 1 1 0
+           1 1 1 1 1
+           0 0 0 1 0
+           0 1 0 0 0
+           1 0 0 0 0
+           norm= 0.38 0.38 -0.76 0.38 0
+         has a determinate of 1, but g.e. after subtracting pt. 0 has
+         0's in the diagonal, even with full pivoting.  It does work
+         if you subtract pt. 4 instead. */
+      }
+    }
+  }
+  facet->upperdelaunay= False;
+  if (qh->DELAUNAY) {
+    if (qh->UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
+      if (facet->normal[qh->hull_dim -1] >= qh->ANGLEround * qh_ZEROdelaunay)
+        facet->upperdelaunay= True;
+    }else {
+      if (facet->normal[qh->hull_dim -1] > -qh->ANGLEround * qh_ZEROdelaunay)
+        facet->upperdelaunay= True;
+    }
+  }
+  if (qh->PRINTstatistics || qh->IStracing || qh->TRACElevel || qh->JOGGLEmax < REALmax) {
+    qh->old_randomdist= qh->RANDOMdist;
+    qh->RANDOMdist= False;
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->point != point0) {
+        boolT istrace= False;
+        zinc_(Zdiststat);
+        qh_distplane(qh, vertex->point, facet, &dist);
+        dist= fabs_(dist);
+        zinc_(Znewvertex);
+        wadd_(Wnewvertex, dist);
+        if (dist > wwval_(Wnewvertexmax)) {
+          wwval_(Wnewvertexmax)= dist;
+          if (dist > qh->max_outside) {
+            qh->max_outside= dist;  /* used by qh_maxouter(qh) */
+            if (dist > qh->TRACEdist)
+              istrace= True;
+          }
+        }else if (-dist > qh->TRACEdist)
+          istrace= True;
+        if (istrace) {
+          qh_fprintf(qh, qh->ferr, 3060, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
+                qh_pointid(qh, vertex->point), vertex->id, dist, facet->id, qh->furthest_id);
+          qh_errprint(qh, "DISTANT", facet, NULL, NULL, NULL);
+        }
+      }
+    }
+    qh->RANDOMdist= qh->old_randomdist;
+  }
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4) {
+    qh_fprintf(qh, qh->ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
+             facet->id, facet->offset);
+    for (k=0; k < qh->hull_dim; k++)
+      qh_fprintf(qh, qh->ferr, 8018, "%2.2g ", facet->normal[k]);
+    qh_fprintf(qh, qh->ferr, 8019, "\n");
+  }
+#endif
+  qh_checkflipped(qh, facet, NULL, qh_ALL);
+  if (facet == qh->tracefacet) {
+    qh->IStracing= oldtrace;
+    qh_printfacet(qh, qh->ferr, facet);
+  }
+} /* setfacetplane */
+
+
+/*---------------------------------
+
+  qh_sethyperplane_det(qh, dim, rows, point0, toporient, normal, offset, nearzero )
+    given dim X dim array indexed by rows[], one row per point,
+        toporient(flips all signs),
+        and point0 (any row)
+    set normalized hyperplane equation from oriented simplex
+
+  returns:
+    normal (normalized)
+    offset (places point0 on the hyperplane)
+    sets nearzero if hyperplane not through points
+
+  notes:
+    only defined for dim == 2..4
+    rows[] is not modified
+    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
+    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
+
+  derivation of 3-d minnorm
+    Goal: all vertices V_i within qh.one_merge of hyperplane
+    Plan: exactly translate the facet so that V_0 is the origin
+          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
+          exactly rotate the effective perturbation to only effect n_0
+             this introduces a factor of sqrt(3)
+    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
+    Let M_d be the max coordinate difference
+    Let M_a be the greater of M_d and the max abs. coordinate
+    Let u be machine roundoff and distround be max error for distance computation
+    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
+    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
+    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
+    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
+
+  derivation of 4-d minnorm
+    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
+     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
+    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
+     [all other terms contain at least two factors nearly zero.]
+    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
+    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
+    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
+*/
+void qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
+          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
+  realT maxround, dist;
+  int i;
+  pointT *point;
+
+
+  if (dim == 2) {
+    normal[0]= dY(1,0);
+    normal[1]= dX(0,1);
+    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
+    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
+    *nearzero= False;  /* since nearzero norm => incident points */
+  }else if (dim == 3) {
+    normal[0]= det2_(dY(2,0), dZ(2,0),
+                     dY(1,0), dZ(1,0));
+    normal[1]= det2_(dX(1,0), dZ(1,0),
+                     dX(2,0), dZ(2,0));
+    normal[2]= det2_(dX(2,0), dY(2,0),
+                     dX(1,0), dY(1,0));
+    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
+    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
+               + point0[2]*normal[2]);
+    maxround= qh->DISTround;
+    for (i=dim; i--; ) {
+      point= rows[i];
+      if (point != point0) {
+        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
+               + point[2]*normal[2]);
+        if (dist > maxround || dist < -maxround) {
+          *nearzero= True;
+          break;
+        }
+      }
+    }
+  }else if (dim == 4) {
+    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
+                        dY(1,0), dZ(1,0), dW(1,0),
+                        dY(3,0), dZ(3,0), dW(3,0));
+    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
+                        dX(1,0), dZ(1,0), dW(1,0),
+                        dX(3,0), dZ(3,0), dW(3,0));
+    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
+                        dX(1,0), dY(1,0), dW(1,0),
+                        dX(3,0), dY(3,0), dW(3,0));
+    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
+                        dX(1,0), dY(1,0), dZ(1,0),
+                        dX(3,0), dY(3,0), dZ(3,0));
+    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
+    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
+               + point0[2]*normal[2] + point0[3]*normal[3]);
+    maxround= qh->DISTround;
+    for (i=dim; i--; ) {
+      point= rows[i];
+      if (point != point0) {
+        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
+               + point[2]*normal[2] + point[3]*normal[3]);
+        if (dist > maxround || dist < -maxround) {
+          *nearzero= True;
+          break;
+        }
+      }
+    }
+  }
+  if (*nearzero) {
+    zzinc_(Zminnorm);
+    /* qh_joggle_restart not needed, will call qh_sethyperplane_gauss instead */
+    trace0((qh, qh->ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d, use qh_sethyperplane_gauss instead.\n", qh->furthest_id));
+  }
+} /* sethyperplane_det */
+
+
+/*---------------------------------
+
+  qh_sethyperplane_gauss(qh, dim, rows, point0, toporient, normal, offset, nearzero )
+    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
+    set normalized hyperplane equation from oriented simplex
+
+  returns:
+    normal (normalized)
+    offset (places point0 on the hyperplane)
+
+  notes:
+    if nearzero
+      orientation may be incorrect because of incorrect sign flips in gausselim
+    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
+        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
+    i.e., N is normal to the hyperplane, and the unnormalized
+        distance to [0 .. 1] is either 1 or   0
+
+  design:
+    perform gaussian elimination
+    flip sign for negative values
+    perform back substitution
+    normalize result
+    compute offset
+*/
+void qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
+                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
+  coordT *pointcoord, *normalcoef;
+  int k;
+  boolT sign= toporient, nearzero2= False;
+
+  qh_gausselim(qh, rows, dim-1, dim, &sign, nearzero);
+  for (k=dim-1; k--; ) {
+    if ((rows[k])[k] < 0)
+      sign ^= 1;
+  }
+  if (*nearzero) {
+    zzinc_(Znearlysingular);
+    /* qh_joggle_restart ignored for Znearlysingular, normal part of qh_sethyperplane_gauss */
+    trace0((qh, qh->ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh->furthest_id));
+    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
+  }else {
+    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
+    if (nearzero2) {
+      zzinc_(Znearlysingular);
+      trace0((qh, qh->ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh->furthest_id));
+    }
+  }
+  if (nearzero2)
+    *nearzero= True;
+  qh_normalize2(qh, normal, dim, True, NULL, NULL);
+  pointcoord= point0;
+  normalcoef= normal;
+  *offset= -(*pointcoord++ * *normalcoef++);
+  for (k=dim-1; k--; )
+    *offset -= *pointcoord++ * *normalcoef++;
+} /* sethyperplane_gauss */
+
+
+
diff --git a/vendor/qhull/libqhull_r/geom_r.h b/vendor/qhull/libqhull_r/geom_r.h
new file mode 100644
index 0000000..f3f8ee8
--- /dev/null
+++ b/vendor/qhull/libqhull_r/geom_r.h
@@ -0,0 +1,189 @@
+/*
  ---------------------------------
+
+  geom_r.h
+    header file for geometric routines
+
+   see qh-geom_r.htm and geom_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/geom_r.h#2 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFgeom
+#define qhDEFgeom 1
+
+#include "libqhull_r.h"
+
+/* ============ -macros- ======================== */
+
+/*----------------------------------
+
+  fabs_(a)
+    returns the absolute value of a
+*/
+#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
+
+/*----------------------------------
+
+  fmax_(a,b)
+    returns the maximum value of a and b
+*/
+#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
+
+/*----------------------------------
+
+  fmin_(a,b)
+    returns the minimum value of a and b
+*/
+#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
+
+/*----------------------------------
+
+  maximize_(maxval, val)
+    set maxval to val if val is greater than maxval
+*/
+#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
+
+/*----------------------------------
+
+  minimize_(minval, val)
+    set minval to val if val is less than minval
+*/
+#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
+
+/*----------------------------------
+
+  det2_(a1, a2,
+        b1, b2)
+
+    compute a 2-d determinate
+*/
+#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
+
+/*----------------------------------
+
+  det3_(a1, a2, a3,
+       b1, b2, b3,
+       c1, c2, c3)
+
+    compute a 3-d determinate
+*/
+#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
+                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
+
+/*----------------------------------
+
+  dX( p1, p2 )
+  dY( p1, p2 )
+  dZ( p1, p2 )
+
+    given two indices into rows[],
+
+    compute the difference between X, Y, or Z coordinates
+*/
+#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
+#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
+#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
+#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
+
+/*============= prototypes in alphabetical order, infrequent at end ======= */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void    qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
+void    qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist);
+facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
+                     boolT bestoutside, boolT isnewfacets, boolT noupper,
+                     realT *dist, boolT *isoutside, int *numpart);
+facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT *point,
+                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
+facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet, realT *dist,
+                     boolT bestoutside, boolT *isoutside, int *numpart);
+void    qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
+realT   qh_getangle(qhT *qh, pointT *vect1, pointT *vect2);
+pointT *qh_getcenter(qhT *qh, setT *vertices);
+pointT *qh_getcentrum(qhT *qh, facetT *facet);
+coordT  qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, coordT *mindist, coordT *maxdist);
+void    qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient);
+void    qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
+            realT *minnorm, boolT *ismin);
+pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist);
+
+void    qh_setfacetplane(qhT *qh, facetT *newfacets);
+void    qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
+              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
+void    qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
+             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
+boolT   qh_sharpnewfacets(qhT *qh);
+
+/*========= infrequently used code in geom2_r.c =============*/
+
+coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension);
+void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
+realT   qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero);
+realT   qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension);
+void    qh_detmaxoutside(qhT *qh);
+void    qh_detroundoff(qhT *qh);
+realT   qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero);
+realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
+realT   qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs);
+realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
+realT   qh_facetarea(qhT *qh, facetT *facet);
+realT   qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
+          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
+pointT *qh_facetcenter(qhT *qh, setT *vertices);
+facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
+vertexT *qh_furthestnewvertex(qhT *qh, unsigned int unvisited, facetT *facet, realT *maxdistp /* qh.newvertex_list */);
+vertexT *qh_furthestvertex(qhT *qh, facetT *facetA, facetT *facetB, realT *maxdistp, realT *mindistp);
+void    qh_getarea(qhT *qh, facetT *facetlist);
+boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
+boolT   qh_inthresholds(qhT *qh, coordT *normal, realT *angle);
+void    qh_joggleinput(qhT *qh);
+realT  *qh_maxabsval(realT *normal, int dim);
+setT   *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension);
+realT   qh_maxouter(qhT *qh);
+void    qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
+realT   qh_minabsval(realT *normal, int dim);
+int     qh_mindiff(realT *vecA, realT *vecB, int dim);
+boolT   qh_orientoutside(qhT *qh, facetT *facet);
+void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
+coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
+void    qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol);
+void    qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points);
+void    qh_projectinput(qhT *qh);
+void    qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
+             int numpoints, int dim, realT *newpoints, int newdim);
+void    qh_rotateinput(qhT *qh, realT **rows);
+void    qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **rows);
+void    qh_scaleinput(qhT *qh);
+void    qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
+                   coordT high, coordT newhigh);
+void    qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
+                realT *newlows, realT *newhighs);
+boolT   qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
+              coordT *normal, coordT *offset, coordT *feasible);
+coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
+coordT  qh_vertex_bestdist(qhT *qh, setT *vertices);
+coordT  qh_vertex_bestdist2(qhT *qh, setT *vertices, vertexT **vertexp, vertexT **vertexp2);
+pointT *qh_voronoi_center(qhT *qh, int dim, setT *points);
+
+#ifdef __cplusplus
+} /* extern "C"*/
+#endif
+
+#endif /* qhDEFgeom */
+
+
+
diff --git a/vendor/qhull/libqhull_r/global_r.c b/vendor/qhull/libqhull_r/global_r.c
new file mode 100644
index 0000000..d8f0f0b
--- /dev/null
+++ b/vendor/qhull/libqhull_r/global_r.c
@@ -0,0 +1,2268 @@
+
+/*
  ---------------------------------
+
+   global_r.c
+   initializes all the globals of the qhull application
+
+   see README
+
+   see libqhull_r.h for qh.globals and function prototypes
+
+   see qhull_ra.h for internal functions
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/global_r.c#21 $$Change: 3664 $
+   $DateTime: 2024/07/22 23:55:01 $$Author: bbarber $
+ */
+
+#include "qhull_ra.h"
+
+/*========= qh->definition -- globals defined in libqhull_r.h =======================*/
+
+/*----------------------------------
+
+  qh_version
+    version string by year and date
+    qh_version2 for Unix users and -V
+
+    the revision increases on code changes only
+
+  notes:
+    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
+                    qhull-news.html, Eudora signatures, CMakeLists.txt
+    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
+    check that CMakeLists.txt @version is the same as qh_version2
+    change year:    Copying.txt
+    check download size
+    recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c
+*/
+
+const char qh_version[]= "2020.2.r 2023/01/02";
+const char qh_version2[]= "qhull_r 8.1-alpha3 (2020.2.r 2023/01/02)";
+
+/*---------------------------------
+
+  qh_appendprint(qh, printFormat )
+    append printFormat to qh.PRINTout unless already defined
+*/
+void qh_appendprint(qhT *qh, qh_PRINT format) {
+  int i;
+
+  for (i=0; i < qh_PRINTEND; i++) {
+    if (qh->PRINTout[i] == format && format != qh_PRINTqhull)
+      break;
+    if (!qh->PRINTout[i]) {
+      qh->PRINTout[i]= format;
+      break;
+    }
+  }
+} /* appendprint */
+
+/*---------------------------------
+
+  qh_checkflags(qh, commandStr, hiddenFlags )
+    errors if commandStr contains hiddenFlags
+    hiddenFlags starts and ends with a space and is space delimited (checked)
+
+  notes:
+    ignores first word (e.g., "qconvex i")
+    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
+
+  see:
+    qh_initflags() initializes Qhull according to commandStr
+*/
+void qh_checkflags(qhT *qh, char *command, char *hiddenflags) {
+  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
+  char key, opt, prevopt;
+  char chkkey[]=  "   ";    /* check one character options ('s') */
+  char chkopt[]=  "    ";   /* check two character options ('Ta') */
+  char chkopt2[]= "     ";  /* check three character options ('Q12') */
+  boolT waserr= False;
+
+  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
+    qh_fprintf(qh, qh->ferr, 6026, "qhull internal error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"\n", hiddenflags);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (strpbrk(hiddenflags, ",\n\r\t")) {
+    qh_fprintf(qh, qh->ferr, 6027, "qhull internal error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"\n", hiddenflags);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  while (*s && !isspace(*s))  /* skip program name */
+    s++;
+  while (*s) {
+    while (*s && isspace(*s))
+      s++;
+    if (*s == '-')
+      s++;
+    if (!*s)
+      break;
+    key= *s++;
+    chkerr= NULL;
+    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
+      s= qh_skipfilename(qh, ++s);
+      continue;
+    }
+    chkkey[1]= key;
+    if (strstr(hiddenflags, chkkey)) {
+      chkerr= chkkey;
+    }else if (isupper(key)) {
+      opt= ' ';
+      prevopt= ' ';
+      chkopt[1]= key;
+      chkopt2[1]= key;
+      while (!chkerr && *s && !isspace(*s)) {
+        opt= *s++;
+        if (isalpha(opt)) {
+          chkopt[2]= opt;
+          if (strstr(hiddenflags, chkopt))
+            chkerr= chkopt;
+          if (prevopt != ' ') {
+            chkopt2[2]= prevopt;
+            chkopt2[3]= opt;
+            if (strstr(hiddenflags, chkopt2))
+              chkerr= chkopt2;
+          }
+        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
+              && (prevopt == ' ' || islower(prevopt))) {
+            if (isdigit(*s)) {  /* Q12 */
+              chkopt2[2]= opt;
+              chkopt2[3]= *s++;
+              if (strstr(hiddenflags, chkopt2))
+                chkerr= chkopt2;
+            }else {
+              chkopt[2]= opt;
+              if (strstr(hiddenflags, chkopt))
+                chkerr= chkopt;
+            }
+        }else {
+          qh_strtod(s-1, &t);
+          if (s < t)
+            s= t;
+        }
+        prevopt= opt;
+      }
+    }
+    if (chkerr) {
+      *chkerr= '\'';
+      chkerr[strlen(chkerr)-1]=  '\'';
+      qh_fprintf(qh, qh->ferr, 6029, "qhull option error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
+      waserr= True;
+    }
+  }
+  if (waserr)
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+} /* checkflags */
+
+/*---------------------------------
+
+  qh_clear_outputflags(qh)
+    Clear output flags for QhullPoints
+*/
+void qh_clear_outputflags(qhT *qh) {
+  int i,k;
+
+  qh->ANNOTATEoutput= False;
+  qh->DOintersections= False;
+  qh->DROPdim= -1;
+  qh->FORCEoutput= False;
+  qh->GETarea= False;
+  qh->GOODpoint= 0;
+  qh->GOODpointp= NULL;
+  qh->GOODthreshold= False;
+  qh->GOODvertex= 0;
+  qh->GOODvertexp= NULL;
+  qh->IStracing= 0;
+  qh->KEEParea= False;
+  qh->KEEPmerge= False;
+  qh->KEEPminArea= REALmax;
+  qh->PRINTcentrums= False;
+  qh->PRINTcoplanar= False;
+  qh->PRINTdots= False;
+  qh->PRINTgood= False;
+  qh->PRINTinner= False;
+  qh->PRINTneighbors= False;
+  qh->PRINTnoplanes= False;
+  qh->PRINToptions1st= False;
+  qh->PRINTouter= False;
+  qh->PRINTprecision= True;
+  qh->PRINTridges= False;
+  qh->PRINTspheres= False;
+  qh->PRINTstatistics= False;
+  qh->PRINTsummary= False;
+  qh->PRINTtransparent= False;
+  qh->SPLITthresholds= False;
+  qh->TRACElevel= 0;
+  qh->TRInormals= False;
+  qh->USEstdout= False;
+  qh->VERIFYoutput= False;
+  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
+    qh->lower_threshold[k]= -REALmax;
+    qh->upper_threshold[k]= REALmax;
+    qh->lower_bound[k]= -REALmax;
+    qh->upper_bound[k]= REALmax;
+  }
+
+  for (i=0; i < qh_PRINTEND; i++) {
+    qh->PRINTout[i]= qh_PRINTnone;
+  }
+
+  if (!qh->qhull_commandsiz2)
+      qh->qhull_commandsiz2= (int)strlen(qh->qhull_command); /* WARN64 */
+  else {
+      qh->qhull_command[qh->qhull_commandsiz2]= '\0';
+  }
+  if (!qh->qhull_optionsiz2)
+    qh->qhull_optionsiz2= (int)strlen(qh->qhull_options);  /* WARN64 */
+  else {
+    qh->qhull_options[qh->qhull_optionsiz2]= '\0';
+    qh->qhull_optionlen= qh_OPTIONline;  /* start a new line */
+  }
+} /* clear_outputflags */
+
+/*---------------------------------
+
+  qh_clock()
+    return user CPU time in 100ths (qh_SECtick)
+    only defined for qh_CLOCKtype == 2
+
+  notes:
+    use first value to determine time 0
+    from Stevens '92 8.15
+*/
+unsigned long qh_clock(qhT *qh) {
+
+#if (qh_CLOCKtype == 2)
+  struct tms time;
+  static long clktck;  /* initialized first call and never updated */
+  double ratio, cpu;
+  unsigned long ticks;
+
+  if (!clktck) {
+    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
+      qh_fprintf(qh, qh->ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user_r.h\n");
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+  }
+  if (times(&time) == -1) {
+    qh_fprintf(qh, qh->ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user_r.h\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  ratio= qh_SECticks / (double)clktck;
+  ticks= time.tms_utime * ratio;
+  return ticks;
+#else
+  qh_fprintf(qh, qh->ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user_r.h\n");
+  qh_errexit(qh, qh_ERRqhull, NULL, NULL); /* never returns */
+  return 0;
+#endif
+} /* clock */
+
+/*---------------------------------
+
+  qh_freebuffers()
+    free up global memory buffers
+
+  notes:
+    must match qh_initbuffers()
+*/
+void qh_freebuffers(qhT *qh) {
+
+  trace5((qh, qh->ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
+  /* allocated by qh_initqhull_buffers */
+  qh_setfree(qh, &qh->other_points);
+  qh_setfree(qh, &qh->del_vertices);
+  qh_setfree(qh, &qh->coplanarfacetset);
+  qh_memfree(qh, qh->NEARzero, qh->hull_dim * (int)sizeof(realT));
+  qh_memfree(qh, qh->lower_threshold, (qh->input_dim+1) * (int)sizeof(realT));
+  qh_memfree(qh, qh->upper_threshold, (qh->input_dim+1) * (int)sizeof(realT));
+  qh_memfree(qh, qh->lower_bound, (qh->input_dim+1) * (int)sizeof(realT));
+  qh_memfree(qh, qh->upper_bound, (qh->input_dim+1) * (int)sizeof(realT));
+  qh_memfree(qh, qh->gm_matrix, (qh->hull_dim+1) * qh->hull_dim * (int)sizeof(coordT));
+  qh_memfree(qh, qh->gm_row, (qh->hull_dim+1) * (int)sizeof(coordT *));
+  qh->NEARzero= qh->lower_threshold= qh->upper_threshold= NULL;
+  qh->lower_bound= qh->upper_bound= NULL;
+  qh->gm_matrix= NULL;
+  qh->gm_row= NULL;
+
+  if (qh->line)                /* allocated by qh_readinput, freed if no error */
+    qh_free(qh->line);
+  if (qh->half_space)
+    qh_free(qh->half_space);
+  if (qh->temp_malloc)
+    qh_free(qh->temp_malloc);
+  if (qh->feasible_point)      /* allocated by qh_readfeasible */
+    qh_free(qh->feasible_point);
+  if (qh->feasible_string)     /* allocated by qh_initflags */
+    qh_free(qh->feasible_string);
+  qh->line= qh->feasible_string= NULL;
+  qh->half_space= qh->feasible_point= qh->temp_malloc= NULL;
+  /* usually allocated by qh_readinput */
+  if (qh->first_point && qh->POINTSmalloc) {
+    qh_free(qh->first_point);
+    qh->first_point= NULL;
+  }
+  if (qh->input_points && qh->input_malloc) { /* set by qh_joggleinput */
+    qh_free(qh->input_points);
+    qh->input_points= NULL;
+  }
+  trace5((qh, qh->ferr, 5002, "qh_freebuffers: finished\n"));
+} /* freebuffers */
+
+
+/*---------------------------------
+
+  qh_freebuild(qh, allmem )
+    free global memory used by qh_initbuild and qh_buildhull
+    if !allmem,
+      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
+
+  design:
+    free centrums
+    free each vertex
+    for each facet
+      free ridges
+      free outside set, coplanar set, neighbor set, ridge set, vertex set
+      free facet
+    free hash table
+    free interior point
+    free merge sets
+    free temporary sets
+*/
+void qh_freebuild(qhT *qh, boolT allmem) {
+  facetT *facet, *previousfacet= NULL;
+  vertexT *vertex, *previousvertex= NULL;
+  ridgeT *ridge, **ridgep, *previousridge= NULL;
+  mergeT *merge, **mergep;
+  int newsize;
+  boolT freeall;
+
+  /* free qhT global sets first, includes references from qh_buildhull */
+  trace5((qh, qh->ferr, 5004, "qh_freebuild: free global sets\n"));
+  FOREACHmerge_(qh->facet_mergeset)  /* usually empty */
+    qh_memfree(qh, merge, (int)sizeof(mergeT));
+  FOREACHmerge_(qh->degen_mergeset)  /* usually empty */
+    qh_memfree(qh, merge, (int)sizeof(mergeT));
+  FOREACHmerge_(qh->vertex_mergeset)  /* usually empty */
+    qh_memfree(qh, merge, (int)sizeof(mergeT));
+  qh->facet_mergeset= NULL;  /* temp set freed by qh_settempfree_all */
+  qh->degen_mergeset= NULL;  /* temp set freed by qh_settempfree_all */
+  qh->vertex_mergeset= NULL;  /* temp set freed by qh_settempfree_all */
+  qh_setfree(qh, &(qh->hash_table));
+  trace5((qh, qh->ferr, 5003, "qh_freebuild: free temporary sets (qh_settempfree_all)\n"));
+  qh_settempfree_all(qh);
+  trace1((qh, qh->ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
+  if (qh->del_vertices)
+    qh_settruncate(qh, qh->del_vertices, 0);
+  if (allmem) {
+    while ((vertex= qh->vertex_list)) {
+      if (vertex->next)
+        qh_delvertex(qh, vertex);
+      else {
+        qh_memfree(qh, vertex, (int)sizeof(vertexT)); /* sentinel */
+        qh->newvertex_list= qh->vertex_list= NULL;
+        break;
+      }
+      previousvertex= vertex; /* in case of memory fault */
+      QHULL_UNUSED(previousvertex)
+    }
+  }else if (qh->VERTEXneighbors) {
+    FORALLvertices
+      qh_setfreelong(qh, &(vertex->neighbors));
+  }
+  qh->VERTEXneighbors= False;
+  qh->GOODclosest= NULL;
+  if (allmem) {
+    FORALLfacets {
+      FOREACHridge_(facet->ridges)
+        ridge->seen= False;
+    }
+    while ((facet= qh->facet_list)) {
+      if (!facet->newfacet || !qh->NEWtentative || qh_setsize(qh, facet->ridges) > 1) { /* skip tentative horizon ridges */
+        trace4((qh, qh->ferr, 4095, "qh_freebuild: delete the previously-seen ridges of f%d\n", facet->id));
+        FOREACHridge_(facet->ridges) {
+          if (ridge->seen)
+            qh_delridge(qh, ridge);
+          else
+            ridge->seen= True;
+          previousridge= ridge; /* in case of memory fault */
+          QHULL_UNUSED(previousridge)
+        }
+      }
+      qh_setfree(qh, &(facet->outsideset));
+      qh_setfree(qh, &(facet->coplanarset));
+      qh_setfree(qh, &(facet->neighbors));
+      qh_setfree(qh, &(facet->ridges));
+      qh_setfree(qh, &(facet->vertices));
+      if (facet->next)
+        qh_delfacet(qh, facet);
+      else {
+        qh_memfree(qh, facet, (int)sizeof(facetT));
+        qh->visible_list= qh->newfacet_list= qh->facet_list= NULL;
+      }
+      previousfacet= facet; /* in case of memory fault */
+      QHULL_UNUSED(previousfacet)
+    }
+  }else {
+    freeall= True;
+    if (qh_setlarger_quick(qh, qh->hull_dim + 1, &newsize))
+      freeall= False;
+    FORALLfacets {
+      qh_setfreelong(qh, &(facet->outsideset));
+      qh_setfreelong(qh, &(facet->coplanarset));
+      if (!facet->simplicial || freeall) {
+        qh_setfreelong(qh, &(facet->neighbors));
+        qh_setfreelong(qh, &(facet->ridges));
+        qh_setfreelong(qh, &(facet->vertices));
+      }
+    }
+  }
+  /* qh internal constants */
+  qh_memfree(qh, qh->interior_point, qh->normal_size);
+  qh->interior_point= NULL;
+} /* freebuild */
+
+/*---------------------------------
+
+  qh_freeqhull(qh, allmem )
+
+  free global memory and set qhT to 0
+  if !allmem,
+    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
+
+notes:
+  sets qh.NOerrexit in case caller forgets to
+  Does not throw errors
+
+see:
+  see qh_initqhull_start2()
+  For libqhull_r, qhstatT is part of qhT
+
+design:
+  free global and temporary memory from qh_initbuild and qh_buildhull
+  free buffers
+*/
+void qh_freeqhull(qhT *qh, boolT allmem) {
+
+  qh->NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
+  trace1((qh, qh->ferr, 1006, "qh_freeqhull: free global memory\n"));
+  qh_freebuild(qh, allmem);
+  qh_freebuffers(qh);
+  trace1((qh, qh->ferr, 1061, "qh_freeqhull: clear qhT except for qh.qhmem and qh.qhstat\n"));
+  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
+  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));
+  qh->NOerrexit= True;
+} /* freeqhull */
+
+/*---------------------------------
+
+  qh_init_A(qh, infile, outfile, errfile, argc, argv )
+    initialize memory and stdio files
+    convert input options to option string (qh.qhull_command)
+
+  notes:
+    infile may be NULL if qh_readpoints() is not called
+
+    errfile should always be defined.  It is used for reporting
+    errors.  outfile is used for output and format options.
+
+    argc/argv may be 0/NULL
+
+    called before error handling initialized
+    qh_errexit() may not be used
+*/
+void qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
+  qh_meminit(qh, errfile);
+  qh_initqhull_start(qh, infile, outfile, errfile);
+  qh_init_qhull_command(qh, argc, argv);
+} /* init_A */
+
+/*---------------------------------
+
+  qh_init_B(qh, points, numpoints, dim, ismalloc )
+    initialize globals for points array
+
+    points has numpoints dim-dimensional points
+      points[0] is the first coordinate of the first point
+      points[1] is the second coordinate of the first point
+      points[dim] is the first coordinate of the second point
+
+    ismalloc=True
+      Qhull will call qh_free(points) on exit or input transformation
+    ismalloc=False
+      Qhull will allocate a new point array if needed for input transformation
+
+    qh.qhull_command
+      is the option string.
+      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
+
+  returns:
+    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
+      projects the input to a new point array
+
+        if qh.DELAUNAY,
+          qh.hull_dim is increased by one
+        if qh.ATinfinity,
+          qh_projectinput adds point-at-infinity for Delaunay tri.
+
+    if qh.SCALEinput
+      changes the upper and lower bounds of the input, see qh_scaleinput
+
+    if qh.ROTATEinput
+      rotates the input by a random rotation, see qh_rotateinput
+      if qh.DELAUNAY
+        rotates about the last coordinate
+
+  notes:
+    called after points are defined
+    qh_errexit() may be used
+*/
+void qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
+  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc);
+  if (qh->qhmem.LASTsize == 0)
+    qh_initqhull_mem(qh);
+  /* mem_r.c and qset_r.c are initialized */
+  qh_initqhull_buffers(qh);
+  qh_initthresholds(qh, qh->qhull_command);
+  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay))
+    qh_projectinput(qh);
+  if (qh->SCALEinput)
+    qh_scaleinput(qh);
+  if (qh->ROTATErandom >= 0) {
+    qh_randommatrix(qh, qh->gm_matrix, qh->hull_dim, qh->gm_row);
+    if (qh->DELAUNAY) {
+      int k, lastk= qh->hull_dim-1;
+      for (k=0; k < lastk; k++) {
+        qh->gm_row[k][lastk]= 0.0;
+        qh->gm_row[lastk][k]= 0.0;
+      }
+      qh->gm_row[lastk][lastk]= 1.0;
+    }
+    qh_gram_schmidt(qh, qh->hull_dim, qh->gm_row);
+    qh_rotateinput(qh, qh->gm_row);
+  }
+} /* init_B */
+
+/*---------------------------------
+
+  qh_init_qhull_command(qh, argc, argv )
+    build qh.qhull_command from argc/argv
+    Calls qh_exit if qhull_command is too short
+
+  returns:
+    a space-delimited string of options (just as typed)
+
+  notes:
+    makes option string easy to input and output
+
+    argc/argv may be 0/NULL
+*/
+void qh_init_qhull_command(qhT *qh, int argc, char *argv[]) {
+
+  if (!qh_argv_to_command(argc, argv, qh->qhull_command, (int)sizeof(qh->qhull_command))){
+    /* Assumes qh.ferr is defined. */
+    qh_fprintf(qh, qh->ferr, 6033, "qhull input error: more than %d characters in command line.\n",
+          (int)sizeof(qh->qhull_command));
+    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
+  }
+} /* init_qhull_command */
+
+/*---------------------------------
+
+  qh_initflags(qh, commandStr )
+    set flags and initialized constants from commandStr
+    calls qh_exit() if qh.NOerrexit
+
+  returns:
+    sets qh.qhull_command to command if needed
+
+  notes:
+    ignores first word (e.g., 'qhull' in "qhull d")
+    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
+
+  see:
+    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
+    'prompt' in unix_r.c for documentation
+
+  design:
+    for each space-delimited option group
+      if top-level option
+        check syntax
+        append appropriate option to option string
+        set appropriate global variable or append printFormat to print options
+      else
+        for each sub-option
+          check syntax
+          append appropriate option to option string
+          set appropriate global variable or append printFormat to print options
+*/
+void qh_initflags(qhT *qh, char *command) {
+  int k, i, lastproject;
+  char *s= command, *t, *prev_s, *start, key, *lastwarning= NULL;
+  boolT isgeom= False, wasproject;
+  realT r;
+
+  if(qh->NOerrexit){
+    qh_fprintf(qh, qh->ferr, 6245, "qhull internal error (qh_initflags): qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.\n");
+    qh_exit(qh_ERRqhull);
+  }
+#ifdef qh_RANDOMdist
+  qh->RANDOMfactor= qh_RANDOMdist;
+  qh_option(qh, "Random-qh_RANDOMdist", NULL, &qh->RANDOMfactor);
+  qh->RANDOMdist= True;
+#endif
+  if (command <= &qh->qhull_command[0] || command > &qh->qhull_command[0] + sizeof(qh->qhull_command)) {
+    if (command != &qh->qhull_command[0]) {
+      *qh->qhull_command= '\0';
+      strncat(qh->qhull_command, command, sizeof(qh->qhull_command)-strlen(qh->qhull_command)-1);
+    }
+    while (*s && !isspace(*s))  /* skip program name */
+      s++;
+  }
+  while (*s) {
+    while (*s && isspace(*s))
+      s++;
+    if (*s == '-')
+      s++;
+    if (!*s)
+      break;
+    prev_s= s;
+    switch (*s++) {
+    case 'd':
+      qh_option(qh, "delaunay", NULL, NULL);
+      qh->DELAUNAY= True;
+      break;
+    case 'f':
+      qh_option(qh, "facets", NULL, NULL);
+      qh_appendprint(qh, qh_PRINTfacets);
+      break;
+    case 'i':
+      qh_option(qh, "incidence", NULL, NULL);
+      qh_appendprint(qh, qh_PRINTincidences);
+      break;
+    case 'm':
+      qh_option(qh, "mathematica", NULL, NULL);
+      qh_appendprint(qh, qh_PRINTmathematica);
+      break;
+    case 'n':
+      qh_option(qh, "normals", NULL, NULL);
+      qh_appendprint(qh, qh_PRINTnormals);
+      break;
+    case 'o':
+      qh_option(qh, "offFile", NULL, NULL);
+      qh_appendprint(qh, qh_PRINToff);
+      break;
+    case 'p':
+      qh_option(qh, "points", NULL, NULL);
+      qh_appendprint(qh, qh_PRINTpoints);
+      break;
+    case 's':
+      qh_option(qh, "summary", NULL, NULL);
+      qh->PRINTsummary= True;
+      break;
+    case 'v':
+      qh_option(qh, "voronoi", NULL, NULL);
+      qh->VORONOI= True;
+      qh->DELAUNAY= True;
+      break;
+    case 'A':
+      if (!isdigit(*s) && *s != '.' && *s != '-') {
+        qh_fprintf(qh, qh->ferr, 7002, "qhull input warning: no maximum cosine angle given for option 'An'.  A1.0 is coplanar\n");
+        lastwarning= s-1;
+      }else {
+        if (*s == '-') {
+          qh->premerge_cos= -qh_strtod(s, &s);
+          qh_option(qh, "Angle-premerge-", NULL, &qh->premerge_cos);
+          qh->PREmerge= True;
+        }else {
+          qh->postmerge_cos= qh_strtod(s, &s);
+          qh_option(qh, "Angle-postmerge", NULL, &qh->postmerge_cos);
+          qh->POSTmerge= True;
+        }
+        qh->MERGING= True;
+      }
+      break;
+    case 'C':
+      if (!isdigit(*s) && *s != '.' && *s != '-') {
+        qh_fprintf(qh, qh->ferr, 7003, "qhull input warning: no centrum radius given for option 'Cn'\n");
+        lastwarning= s-1;
+      }else {
+        if (*s == '-') {
+          qh->premerge_centrum= -qh_strtod(s, &s);
+          qh_option(qh, "Centrum-premerge-", NULL, &qh->premerge_centrum);
+          qh->PREmerge= True;
+        }else {
+          qh->postmerge_centrum= qh_strtod(s, &s);
+          qh_option(qh, "Centrum-postmerge", NULL, &qh->postmerge_centrum);
+          qh->POSTmerge= True;
+        }
+        qh->MERGING= True;
+      }
+      break;
+    case 'E':
+      if (*s == '-') {
+        qh_fprintf(qh, qh->ferr, 6363, "qhull option error: expecting a positive number for maximum roundoff 'En'.  Got '%s'\n", s-1);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }else if (!isdigit(*s)) {
+        qh_fprintf(qh, qh->ferr, 7005, "qhull option warning: no maximum roundoff given for option 'En'\n");
+        lastwarning= s-1;
+      }else {
+        qh->DISTround= qh_strtod(s, &s);
+        qh_option(qh, "Distance-roundoff", NULL, &qh->DISTround);
+        qh->SETroundoff= True;
+      }
+      break;
+    case 'H':
+      start= s;
+      qh->HALFspace= True;
+      qh_strtod(s, &t);
+      while (t > s)  {
+        if (*t && !isspace(*t)) {
+          if (*t == ',')
+            t++;
+          else {
+            qh_fprintf(qh, qh->ferr, 6364, "qhull option error: expecting 'Hn,n,n,...' for feasible point of halfspace intersection. Got '%s'\n", start-1);
+            qh_errexit(qh, qh_ERRinput, NULL, NULL);
+          }
+        }
+        s= t;
+        qh_strtod(s, &t);
+      }
+      if (start < t) {
+        if (!(qh->feasible_string= (char *)calloc((size_t)(t-start+1), (size_t)1))) {
+          qh_fprintf(qh, qh->ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
+          qh_errexit(qh, qh_ERRmem, NULL, NULL);
+        }
+        strncpy(qh->feasible_string, start, (size_t)(t-start));
+        qh_option(qh, "Halfspace-about", NULL, NULL);
+        qh_option(qh, qh->feasible_string, NULL, NULL);
+      }else
+        qh_option(qh, "Halfspace", NULL, NULL);
+      break;
+    case 'R':
+      if (!isdigit(*s)) {
+        qh_fprintf(qh, qh->ferr, 7007, "qhull option warning: missing random perturbation for option 'Rn'\n");
+        lastwarning= s-1;
+      }else {
+        qh->RANDOMfactor= qh_strtod(s, &s);
+        qh_option(qh, "Random-perturb", NULL, &qh->RANDOMfactor);
+        qh->RANDOMdist= True;
+      }
+      break;
+    case 'V':
+      if (!isdigit(*s) && *s != '-') {
+        qh_fprintf(qh, qh->ferr, 7008, "qhull option warning: missing visible distance for option 'Vn'\n");
+        lastwarning= s-1;
+      }else {
+        qh->MINvisible= qh_strtod(s, &s);
+        qh_option(qh, "Visible", NULL, &qh->MINvisible);
+      }
+      break;
+    case 'U':
+      if (!isdigit(*s) && *s != '-') {
+        qh_fprintf(qh, qh->ferr, 7009, "qhull option warning: missing coplanar distance for option 'Un'\n");
+        lastwarning= s-1;
+      }else {
+        qh->MAXcoplanar= qh_strtod(s, &s);
+        qh_option(qh, "U-coplanar", NULL, &qh->MAXcoplanar);
+      }
+      break;
+    case 'W':
+      if (*s == '-') {
+        qh_fprintf(qh, qh->ferr, 6365, "qhull option error: expecting a positive number for outside width 'Wn'.  Got '%s'\n", s-1);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }else if (!isdigit(*s)) {
+        qh_fprintf(qh, qh->ferr, 7011, "qhull option warning: missing outside width for option 'Wn'\n");
+        lastwarning= s-1;
+      }else {
+        qh->MINoutside= qh_strtod(s, &s);
+        qh_option(qh, "W-outside", NULL, &qh->MINoutside);
+        qh->APPROXhull= True;
+      }
+      break;
+    /************  sub menus ***************/
+    case 'F':
+      while (*s && !isspace(*s)) {
+        switch (*s++) {
+        case 'a':
+          qh_option(qh, "Farea", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTarea);
+          qh->GETarea= True;
+          break;
+        case 'A':
+          qh_option(qh, "FArea-total", NULL, NULL);
+          qh->GETarea= True;
+          break;
+        case 'c':
+          qh_option(qh, "Fcoplanars", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTcoplanars);
+          break;
+        case 'C':
+          qh_option(qh, "FCentrums", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTcentrums);
+          break;
+        case 'd':
+          qh_option(qh, "Fd-cdd-in", NULL, NULL);
+          qh->CDDinput= True;
+          break;
+        case 'D':
+          qh_option(qh, "FD-cdd-out", NULL, NULL);
+          qh->CDDoutput= True;
+          break;
+        case 'F':
+          qh_option(qh, "FFacets-xridge", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTfacets_xridge);
+          break;
+        case 'i':
+          qh_option(qh, "Finner", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTinner);
+          break;
+        case 'I':
+          qh_option(qh, "FIDs", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTids);
+          break;
+        case 'm':
+          qh_option(qh, "Fmerges", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTmerges);
+          break;
+        case 'M':
+          qh_option(qh, "FMaple", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTmaple);
+          break;
+        case 'n':
+          qh_option(qh, "Fneighbors", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTneighbors);
+          break;
+        case 'N':
+          qh_option(qh, "FNeighbors-vertex", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTvneighbors);
+          break;
+        case 'o':
+          qh_option(qh, "Fouter", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTouter);
+          break;
+        case 'O':
+          if (qh->PRINToptions1st) {
+            qh_option(qh, "FOptions", NULL, NULL);
+            qh_appendprint(qh, qh_PRINToptions);
+          }else
+            qh->PRINToptions1st= True;
+          break;
+        case 'p':
+          qh_option(qh, "Fpoint-intersect", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTpointintersect);
+          break;
+        case 'P':
+          qh_option(qh, "FPoint-nearest", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTpointnearest);
+          break;
+        case 'Q':
+          qh_option(qh, "FQhull", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTqhull);
+          break;
+        case 's':
+          qh_option(qh, "Fsummary", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTsummary);
+          break;
+        case 'S':
+          qh_option(qh, "FSize", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTsize);
+          qh->GETarea= True;
+          break;
+        case 't':
+          qh_option(qh, "Ftriangles", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTtriangles);
+          break;
+        case 'v':
+          /* option set in qh_initqhull_globals */
+          qh_appendprint(qh, qh_PRINTvertices);
+          break;
+        case 'V':
+          qh_option(qh, "FVertex-average", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTaverage);
+          break;
+        case 'x':
+          qh_option(qh, "Fxtremes", NULL, NULL);
+          qh_appendprint(qh, qh_PRINTextremes);
+          break;
+        default:
+          s--;
+          qh_fprintf(qh, qh->ferr, 7012, "qhull option warning: unknown 'F' output option 'F%c', skip to next space\n", (int)s[0]);
+          lastwarning= s-1;
+          while (*++s && !isspace(*s));
+          break;
+        }
+      }
+      break;
+    case 'G':
+      isgeom= True;
+      qh_appendprint(qh, qh_PRINTgeom);
+      while (*s && !isspace(*s)) {
+        switch (*s++) {
+        case 'a':
+          qh_option(qh, "Gall-points", NULL, NULL);
+          qh->PRINTdots= True;
+          break;
+        case 'c':
+          qh_option(qh, "Gcentrums", NULL, NULL);
+          qh->PRINTcentrums= True;
+          break;
+        case 'h':
+          qh_option(qh, "Gintersections", NULL, NULL);
+          qh->DOintersections= True;
+          break;
+        case 'i':
+          qh_option(qh, "Ginner", NULL, NULL);
+          qh->PRINTinner= True;
+          break;
+        case 'n':
+          qh_option(qh, "Gno-planes", NULL, NULL);
+          qh->PRINTnoplanes= True;
+          break;
+        case 'o':
+          qh_option(qh, "Gouter", NULL, NULL);
+          qh->PRINTouter= True;
+          break;
+        case 'p':
+          qh_option(qh, "Gpoints", NULL, NULL);
+          qh->PRINTcoplanar= True;
+          break;
+        case 'r':
+          qh_option(qh, "Gridges", NULL, NULL);
+          qh->PRINTridges= True;
+          break;
+        case 't':
+          qh_option(qh, "Gtransparent", NULL, NULL);
+          qh->PRINTtransparent= True;
+          break;
+        case 'v':
+          qh_option(qh, "Gvertices", NULL, NULL);
+          qh->PRINTspheres= True;
+          break;
+        case 'D':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7004, "qhull option warning: missing dimension for option 'GDn'\n");
+            lastwarning= s-2;
+          }else {
+            if (qh->DROPdim >= 0) {
+              qh_fprintf(qh, qh->ferr, 7013, "qhull option warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
+                   qh->DROPdim);
+              lastwarning= s-2;
+            }
+            qh->DROPdim= qh_strtol(s, &s);
+            qh_option(qh, "GDrop-dim", &qh->DROPdim, NULL);
+          }
+          break;
+        default:
+          s--;
+          qh_fprintf(qh, qh->ferr, 7014, "qhull option warning: unknown 'G' geomview option 'G%c', skip to next space\n", (int)s[0]);
+          lastwarning= s-1;
+          while (*++s && !isspace(*s));
+          break;
+        }
+      }
+      break;
+    case 'P':
+      while (*s && !isspace(*s)) {
+        switch (*s++) {
+        case 'd': case 'D':  /* see qh_initthresholds() */
+          key= s[-1];
+          i= qh_strtol(s, &s);
+          r= 0;
+          if (*s == ':') {
+            s++;
+            r= qh_strtod(s, &s);
+          }
+          if (key == 'd')
+            qh_option(qh, "Pdrop-facets-dim-less", &i, &r);
+          else
+            qh_option(qh, "PDrop-facets-dim-more", &i, &r);
+          break;
+        case 'g':
+          qh_option(qh, "Pgood-facets", NULL, NULL);
+          qh->PRINTgood= True;
+          break;
+        case 'G':
+          qh_option(qh, "PGood-facet-neighbors", NULL, NULL);
+          qh->PRINTneighbors= True;
+          break;
+        case 'o':
+          qh_option(qh, "Poutput-forced", NULL, NULL);
+          qh->FORCEoutput= True;
+          break;
+        case 'p':
+          qh_option(qh, "Pprecision-ignore", NULL, NULL);
+          qh->PRINTprecision= False;
+          break;
+        case 'A':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7006, "qhull option warning: missing facet count for keep area option 'PAn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->KEEParea= qh_strtol(s, &s);
+            qh_option(qh, "PArea-keep", &qh->KEEParea, NULL);
+            qh->GETarea= True;
+          }
+          break;
+        case 'F':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7010, "qhull option warning: missing facet area for option 'PFn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->KEEPminArea= qh_strtod(s, &s);
+            qh_option(qh, "PFacet-area-keep", NULL, &qh->KEEPminArea);
+            qh->GETarea= True;
+          }
+          break;
+        case 'M':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7090, "qhull option warning: missing merge count for option 'PMn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->KEEPmerge= qh_strtol(s, &s);
+            qh_option(qh, "PMerge-keep", &qh->KEEPmerge, NULL);
+          }
+          break;
+        default:
+          s--;
+          qh_fprintf(qh, qh->ferr, 7015, "qhull option warning: unknown 'P' print option 'P%c', skip to next space\n", (int)s[0]);
+          lastwarning= s-1;
+          while (*++s && !isspace(*s));
+          break;
+        }
+      }
+      break;
+    case 'Q':
+      lastproject= -1;
+      while (*s && !isspace(*s)) {
+        switch (*s++) {
+        case 'a':
+          qh_option(qh, "Qallow-short", NULL, NULL);
+          qh->ALLOWshort= True;
+          break;
+        case 'b': case 'B':  /* handled by qh_initthresholds */
+          key= s[-1];
+          if (key == 'b' && *s == 'B') {
+            s++;
+            r= qh_DEFAULTbox;
+            qh->SCALEinput= True;
+            qh_option(qh, "QbBound-unit-box", NULL, &r);
+            break;
+          }
+          if (key == 'b' && *s == 'b') {
+            s++;
+            qh->SCALElast= True;
+            qh_option(qh, "Qbbound-last", NULL, NULL);
+            break;
+          }
+          k= qh_strtol(s, &s);
+          r= 0.0;
+          wasproject= False;
+          if (*s == ':') {
+            s++;
+            if ((r= qh_strtod(s, &s)) == 0.0) {
+              t= s;            /* need true dimension for memory allocation */
+              while (*t && !isspace(*t)) {
+                if (toupper(*t++) == 'B'
+                 && k == qh_strtol(t, &t)
+                 && *t++ == ':'
+                 && qh_strtod(t, &t) == 0.0) {
+                  qh->PROJECTinput++;
+                  trace2((qh, qh->ferr, 2004, "qh_initflags: project dimension %d\n", k));
+                  qh_option(qh, "Qb-project-dim", &k, NULL);
+                  wasproject= True;
+                  lastproject= k;
+                  break;
+                }
+              }
+            }
+          }
+          if (!wasproject) {
+            if (lastproject == k && r == 0.0)
+              lastproject= -1;  /* doesn't catch all possible sequences */
+            else if (key == 'b') {
+              qh->SCALEinput= True;
+              if (r == 0.0)
+                r= -qh_DEFAULTbox;
+              qh_option(qh, "Qbound-dim-low", &k, &r);
+            }else {
+              qh->SCALEinput= True;
+              if (r == 0.0)
+                r= qh_DEFAULTbox;
+              qh_option(qh, "QBound-dim-high", &k, &r);
+            }
+          }
+          break;
+        case 'c':
+          qh_option(qh, "Qcoplanar-keep", NULL, NULL);
+          qh->KEEPcoplanar= True;
+          break;
+        case 'f':
+          qh_option(qh, "Qfurthest-outside", NULL, NULL);
+          qh->BESToutside= True;
+          break;
+        case 'g':
+          qh_option(qh, "Qgood-facets-only", NULL, NULL);
+          qh->ONLYgood= True;
+          break;
+        case 'i':
+          qh_option(qh, "Qinterior-keep", NULL, NULL);
+          qh->KEEPinside= True;
+          break;
+        case 'm':
+          qh_option(qh, "Qmax-outside-only", NULL, NULL);
+          qh->ONLYmax= True;
+          break;
+        case 'r':
+          qh_option(qh, "Qrandom-outside", NULL, NULL);
+          qh->RANDOMoutside= True;
+          break;
+        case 's':
+          qh_option(qh, "Qsearch-initial-simplex", NULL, NULL);
+          qh->ALLpoints= True;
+          break;
+        case 't':
+          qh_option(qh, "Qtriangulate", NULL, NULL);
+          qh->TRIangulate= True;
+          break;
+        case 'T':
+          qh_option(qh, "QTestPoints", NULL, NULL);
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7091, "qhull option warning: missing number of test points for option 'QTn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->TESTpoints= qh_strtol(s, &s);
+            qh_option(qh, "QTestPoints", &qh->TESTpoints, NULL);
+          }
+          break;
+        case 'u':
+          qh_option(qh, "QupperDelaunay", NULL, NULL);
+          qh->UPPERdelaunay= True;
+          break;
+        case 'v':
+          qh_option(qh, "Qvertex-neighbors-convex", NULL, NULL);
+          qh->TESTvneighbors= True;
+          break;
+        case 'x':
+          qh_option(qh, "Qxact-merge", NULL, NULL);
+          qh->MERGEexact= True;
+          break;
+        case 'z':
+          qh_option(qh, "Qz-infinity-point", NULL, NULL);
+          qh->ATinfinity= True;
+          break;
+        case '0':
+          qh_option(qh, "Q0-no-premerge", NULL, NULL);
+          qh->NOpremerge= True;
+          break;
+        case '1':
+          if (!isdigit(*s)) {
+            qh_option(qh, "Q1-angle-merge", NULL, NULL);
+            qh->ANGLEmerge= True;
+            break;
+          }
+          switch (*s++) {
+          case '0':
+            qh_option(qh, "Q10-no-narrow", NULL, NULL);
+            qh->NOnarrow= True;
+            break;
+          case '1':
+            qh_option(qh, "Q11-trinormals Qtriangulate", NULL, NULL);
+            qh->TRInormals= True;
+            qh->TRIangulate= True;
+            break;
+          case '2':
+            qh_option(qh, "Q12-allow-wide", NULL, NULL);
+            qh->ALLOWwide= True;
+            break;
+          case '4':
+#ifndef qh_NOmerge
+            qh_option(qh, "Q14-merge-pinched-vertices", NULL, NULL);
+            qh->MERGEpinched= True;
+#else
+            /* ignore 'Q14' for q_benchmark testing of difficult cases for Qhull */
+            qh_fprintf(qh, qh->ferr, 7099, "qhull option warning: option 'Q14-merge-pinched' disabled due to qh_NOmerge\n");
+#endif
+            break;
+          case '7':
+            qh_option(qh, "Q17-check-duplicates", NULL, NULL);
+            qh->CHECKduplicates= True;
+            break;
+          default:
+            s--;
+            qh_fprintf(qh, qh->ferr, 7016, "qhull option warning: unknown 'Q' qhull option 'Q1%c', skip to next space\n", (int)s[0]);
+            lastwarning= s-1;
+            while (*++s && !isspace(*s));
+            break;
+          }
+          break;
+        case '2':
+          qh_option(qh, "Q2-no-merge-independent", NULL, NULL);
+          qh->MERGEindependent= False;
+          goto LABELcheckdigit;
+          break; /* no gcc warnings */
+        case '3':
+          qh_option(qh, "Q3-no-merge-vertices", NULL, NULL);
+          qh->MERGEvertices= False;
+        LABELcheckdigit:
+          if (isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7017, "qhull option warning: can not follow '1', '2', or '3' with a digit.  'Q%c%c' skipped\n", *(s-1), *s);
+            lastwarning= s-2;
+            s++;
+          }
+          break;
+        case '4':
+          qh_option(qh, "Q4-avoid-old-into-new", NULL, NULL);
+          qh->AVOIDold= True;
+          break;
+        case '5':
+          qh_option(qh, "Q5-no-check-outer", NULL, NULL);
+          qh->SKIPcheckmax= True;
+          break;
+        case '6':
+          qh_option(qh, "Q6-no-concave-merge", NULL, NULL);
+          qh->SKIPconvex= True;
+          break;
+        case '7':
+          qh_option(qh, "Q7-no-breadth-first", NULL, NULL);
+          qh->VIRTUALmemory= True;
+          break;
+        case '8':
+          qh_option(qh, "Q8-no-near-inside", NULL, NULL);
+          qh->NOnearinside= True;
+          break;
+        case '9':
+          qh_option(qh, "Q9-pick-furthest", NULL, NULL);
+          qh->PICKfurthest= True;
+          break;
+        case 'G':
+          i= qh_strtol(s, &t);
+          if (qh->GOODpoint) {
+            qh_fprintf(qh, qh->ferr, 7018, "qhull option warning: good point already defined for option 'QGn'.  Ignored\n");
+            lastwarning= s-2;
+          }else if (s == t) {
+            qh_fprintf(qh, qh->ferr, 7019, "qhull option warning: missing good point id for option 'QGn'.  Ignored\n");
+            lastwarning= s-2;
+          }else if (i < 0 || *s == '-') {
+            qh->GOODpoint= i-1;
+            qh_option(qh, "QGood-if-dont-see-point", &i, NULL);
+          }else {
+            qh->GOODpoint= i+1;
+            qh_option(qh, "QGood-if-see-point", &i, NULL);
+          }
+          s= t;
+          break;
+        case 'J':
+          if (!isdigit(*s) && *s != '-')
+            qh->JOGGLEmax= 0.0;
+          else {
+            qh->JOGGLEmax= (realT) qh_strtod(s, &s);
+            qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
+          }
+          break;
+        case 'R':
+          if (!isdigit(*s) && *s != '-') {
+            qh_fprintf(qh, qh->ferr, 7020, "qhull option warning: missing random seed for option 'QRn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->ROTATErandom= i= qh_strtol(s, &s);
+            if (i > 0)
+              qh_option(qh, "QRotate-id", &i, NULL );
+            else if (i < -1)
+              qh_option(qh, "QRandom-seed", &i, NULL );
+          }
+          break;
+        case 'V':
+          i= qh_strtol(s, &t);
+          if (qh->GOODvertex) {
+            qh_fprintf(qh, qh->ferr, 7021, "qhull option warning: good vertex already defined for option 'QVn'.  Ignored\n");
+            lastwarning= s-2;
+          }else if (s == t) {
+            qh_fprintf(qh, qh->ferr, 7022, "qhull option warning: no good point id given for option 'QVn'.  Ignored\n");
+            lastwarning= s-2;
+          }else if (i < 0) {
+            qh->GOODvertex= i - 1;
+            qh_option(qh, "QV-good-facets-not-point", &i, NULL);
+          }else {
+            qh_option(qh, "QV-good-facets-point", &i, NULL);
+            qh->GOODvertex= i + 1;
+          }
+          s= t;
+          break;
+        case 'w':
+          qh_option(qh, "Qwarn-allow", NULL, NULL);
+          qh->ALLOWwarning= True;
+          break;
+        default:
+          s--;
+          qh_fprintf(qh, qh->ferr, 7023, "qhull option warning: unknown 'Q' qhull option 'Q%c', skip to next space\n", (int)s[0]);
+          lastwarning= s-1;
+          while (*++s && !isspace(*s));
+          break;
+        }
+      }
+      break;
+    case 'T':
+      while (*s && !isspace(*s)) {
+        if (isdigit(*s) || *s == '-')
+          qh->IStracing= qh_strtol(s, &s);
+        else switch (*s++) {
+        case 'a':
+          qh_option(qh, "Tannotate-output", NULL, NULL);
+          qh->ANNOTATEoutput= True;
+          break;
+        case 'c':
+          qh_option(qh, "Tcheck-frequently", NULL, NULL);
+          qh->CHECKfrequently= True;
+          break;
+        case 'f':
+          qh_option(qh, "Tflush", NULL, NULL);
+          qh->FLUSHprint= True;
+          break;
+        case 's':
+          qh_option(qh, "Tstatistics", NULL, NULL);
+          qh->PRINTstatistics= True;
+          break;
+        case 'v':
+          qh_option(qh, "Tverify", NULL, NULL);
+          qh->VERIFYoutput= True;
+          break;
+        case 'z':
+          if (qh->ferr == qh_FILEstderr) {
+            /* The C++ interface captures the output in qh_fprint_qhull() */
+            qh_option(qh, "Tz-stdout", NULL, NULL);
+            qh->USEstdout= True;
+          }else if (!qh->fout) {
+            qh_fprintf(qh, qh->ferr, 7024, "qhull option warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
+            lastwarning= s-2;
+          }else {
+            qh_option(qh, "Tz-stdout", NULL, NULL);
+            qh->USEstdout= True;
+            qh->ferr= qh->fout;
+            qh->qhmem.ferr= qh->fout;
+          }
+          break;
+        case 'C':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7025, "qhull option warning: missing point id for cone for trace option 'TCn'\n");
+            lastwarning= s-2;
+          }else {
+            i= qh_strtol(s, &s);
+            qh_option(qh, "TCone-stop", &i, NULL);
+            qh->STOPcone= i + 1;
+          }
+          break;
+        case 'F':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7026, "qhull option warning: missing frequency count for trace option 'TFn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->REPORTfreq= qh_strtol(s, &s);
+            qh_option(qh, "TFacet-log", &qh->REPORTfreq, NULL);
+            qh->REPORTfreq2= qh->REPORTfreq/2;  /* for tracemerging() */
+          }
+          break;
+        case 'I':
+          while (isspace(*s))
+            s++;
+          t= qh_skipfilename(qh, s);
+          {
+            char filename[qh_FILENAMElen];
+
+            qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
+            s= t;
+            if (!freopen(filename, "r", stdin)) {
+              qh_fprintf(qh, qh->ferr, 6041, "qhull option error: cannot open 'TI' file \"%s\"\n", filename);
+              qh_errexit(qh, qh_ERRinput, NULL, NULL);
+            }else {
+              qh_option(qh, "TInput-file", NULL, NULL);
+              qh_option(qh, filename, NULL, NULL);
+            }
+          }
+          break;
+        case 'O':
+          while (isspace(*s))
+            s++;
+          t= qh_skipfilename(qh, s);
+          {
+            char filename[qh_FILENAMElen];
+
+            qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
+            if (!qh->fout) {
+              qh_fprintf(qh, qh->ferr, 7092, "qhull option warning: qh.fout was not set by caller of qh_initflags.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
+              lastwarning= s-2;
+            }else if (!freopen(filename, "w", qh->fout)) {
+              qh_fprintf(qh, qh->ferr, 6044, "qhull option error: cannot open file \"%s\" for writing as option 'TO'.  It is already in use or read-only\n", filename);
+              qh_errexit(qh, qh_ERRinput, NULL, NULL);
+            }else {
+              qh_option(qh, "TOutput-file", NULL, NULL);
+              qh_option(qh, filename, NULL, NULL);
+            }
+            s= t;
+          }
+          break;
+        case 'A':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7093, "qhull option warning: missing count of added points for trace option 'TAn'\n");
+            lastwarning= s-2;
+          }else {
+            i= qh_strtol(s, &t);
+            qh->STOPadd= i + 1;
+            qh_option(qh, "TA-stop-add", &i, NULL);
+          }
+          s= t;
+          break;
+        case 'P':
+          if (*s == '-') {
+            if (s[1] == '1' && !isdigit(s[2])) {
+              s += 2;
+              qh->TRACEpoint= qh_IDunknown; /* qh_buildhull done */
+              qh_option(qh, "Trace-point", &qh->TRACEpoint, NULL);
+            }else {
+              qh_fprintf(qh, qh->ferr, 7100, "qhull option warning: negative point id for trace option 'TPn'.  Expecting 'TP-1' for tracing after qh_buildhull and qh_postmerge\n");
+              lastwarning= s-2;
+              while (isdigit(*(++s)))
+                ; /* skip digits */
+            }
+          }else if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7029, "qhull option warning: missing point id or -1 for trace option 'TPn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->TRACEpoint= qh_strtol(s, &s);
+            qh_option(qh, "Trace-point", &qh->TRACEpoint, NULL);
+          }
+          break;
+        case 'M':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7030, "qhull option warning: missing merge id for trace option 'TMn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->TRACEmerge= qh_strtol(s, &s);
+            qh_option(qh, "Trace-merge", &qh->TRACEmerge, NULL);
+          }
+          break;
+        case 'R':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7031, "qhull option warning: missing rerun count for trace option 'TRn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->RERUN= qh_strtol(s, &s);
+            qh_option(qh, "TRerun", &qh->RERUN, NULL);
+          }
+          break;
+        case 'V':
+          i= qh_strtol(s, &t);
+          if (s == t) {
+            qh_fprintf(qh, qh->ferr, 7032, "qhull option warning: missing furthest point id for trace option 'TVn'\n");
+            lastwarning= s-2;
+          }else if (i < 0) {
+            qh->STOPpoint= i - 1;
+            qh_option(qh, "TV-stop-before-point", &i, NULL);
+          }else {
+            qh->STOPpoint= i + 1;
+            qh_option(qh, "TV-stop-after-point", &i, NULL);
+          }
+          s= t;
+          break;
+        case 'W':
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7033, "qhull option warning: missing max width for trace option 'TWn'\n");
+            lastwarning= s-2;
+          }else {
+            qh->TRACEdist= (realT) qh_strtod(s, &s);
+            qh_option(qh, "TWide-trace", NULL, &qh->TRACEdist);
+          }
+          break;
+        default:
+          s--;
+          qh_fprintf(qh, qh->ferr, 7034, "qhull option warning: unknown 'T' trace option 'T%c', skip to next space\n", (int)s[0]);
+          lastwarning= s-2;
+          while (*++s && !isspace(*s));
+          break;
+        }
+      }
+      break;
+    default:
+      qh_fprintf(qh, qh->ferr, 7094, "qhull option warning: unknown option '%c'(%x)\n",
+        (int)s[-1], (int)s[-1]);
+      lastwarning= s-2;
+      break;
+    }
+    if (s-1 == prev_s && *s && !isspace(*s)) {
+      qh_fprintf(qh, qh->ferr, 7036, "qhull option warning: missing space after option '%c'(%x), reserved for sub-options, ignoring '%c' options to next space\n",
+               (int)*prev_s, (int)*prev_s, (int)*prev_s);
+      lastwarning= s-1;
+      while (*s && !isspace(*s))
+        s++;
+    }
+  }
+  if (qh->STOPcone && qh->JOGGLEmax < REALmax/2) {
+    qh_fprintf(qh, qh->ferr, 7078, "qhull option warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
+    lastwarning= command;
+  }
+  if (isgeom && !qh->FORCEoutput && qh->PRINTout[1]) {
+    qh_fprintf(qh, qh->ferr, 7037, "qhull option warning: additional output formats ('Fc',etc.) are not compatible with Geomview ('G').  Use option 'Po' to override\n");
+    lastwarning= command;
+  }
+  if (lastwarning && !qh->ALLOWwarning) {
+    qh_fprintf(qh, qh->ferr, 6035, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n",
+          command, (int)(lastwarning-command));
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  trace4((qh, qh->ferr, 4093, "qh_initflags: option flags initialized\n"));
+  /* set derived values in qh_initqhull_globals */
+} /* initflags */
+
+
+/*---------------------------------
+
+  qh_initqhull_buffers(qh)
+    initialize global memory buffers
+
+  notes:
+    must match qh_freebuffers()
+*/
+void qh_initqhull_buffers(qhT *qh) {
+  int k;
+
+  qh->TEMPsize= (qh->qhmem.LASTsize - SETbasesize)/SETelemsize;
+  if (qh->TEMPsize <= 0 || qh->TEMPsize > qh->qhmem.LASTsize)
+    qh->TEMPsize= 8;  /* e.g., if qh_NOmem */
+  qh->other_points= qh_setnew(qh, qh->TEMPsize);
+  qh->del_vertices= qh_setnew(qh, qh->TEMPsize);
+  qh->coplanarfacetset= qh_setnew(qh, qh->TEMPsize);
+  qh->NEARzero= (realT *)qh_memalloc(qh, qh->hull_dim * (int)sizeof(realT));
+  qh->lower_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * (int)sizeof(realT));
+  qh->upper_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * (int)sizeof(realT));
+  qh->lower_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * (int)sizeof(realT));
+  qh->upper_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * (int)sizeof(realT));
+  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
+    qh->lower_threshold[k]= -REALmax;
+    qh->upper_threshold[k]= REALmax;
+    qh->lower_bound[k]= -REALmax;
+    qh->upper_bound[k]= REALmax;
+  }
+  qh->gm_matrix= (coordT *)qh_memalloc(qh, (qh->hull_dim+1) * qh->hull_dim * (int)sizeof(coordT));
+  qh->gm_row= (coordT **)qh_memalloc(qh, (qh->hull_dim+1) * (int)sizeof(coordT *));
+} /* initqhull_buffers */
+
+/*---------------------------------
+
+  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc )
+    initialize globals
+    if ismalloc
+      points were malloc'd and qhull should free at end
+
+  returns:
+    sets qh.first_point, num_points, input_dim, hull_dim and others
+    seeds random number generator (seed=1 if tracing)
+    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
+    adjust user flags as needed
+    also checks DIM3 dependencies and constants
+
+  notes:
+    do not use qh_point() since an input transformation may move them elsewhere
+    qh_initqhull_start() sets default values for non-zero globals
+    consider duplicate error checks in qh_readpoints.  It is called before qh_initqhull_globals
+
+  design:
+    initialize points array from input arguments
+    test for qh.ZEROcentrum
+      (i.e., use opposite vertex instead of cetrum for convexity testing)
+    initialize qh.CENTERtype, qh.normal_size,
+      qh.center_size, qh.TRACEpoint/level,
+    initialize and test random numbers
+    qh_initqhull_outputflags() -- adjust and test output flags
+*/
+void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
+  int seed, pointsneeded, extra= 0, i, randi, k;
+  realT randr;
+  realT factorial;
+
+  time_t timedata;
+
+  trace0((qh, qh->ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh->rbox_command,
+      qh->qhull_command));
+  if (numpoints < 1 || numpoints > qh_POINTSmax) {
+    qh_fprintf(qh, qh->ferr, 6412, "qhull input error (qh_initqhull_globals): expecting between 1 and %d points.  Got %d %d-d points\n",
+      qh_POINTSmax, numpoints, dim);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    /* same error message in qh_readpoints */
+  }
+  qh->POINTSmalloc= ismalloc;
+  qh->first_point= points;
+  qh->num_points= numpoints;
+  qh->hull_dim= qh->input_dim= dim;
+  if (!qh->NOpremerge && !qh->MERGEexact && !qh->PREmerge && qh->JOGGLEmax > REALmax/2) {
+    qh->MERGING= True;
+    if (qh->hull_dim <= 4) {
+      qh->PREmerge= True;
+      qh_option(qh, "_pre-merge", NULL, NULL);
+    }else {
+      qh->MERGEexact= True;
+      qh_option(qh, "Qxact-merge", NULL, NULL);
+    }
+  }else if (qh->MERGEexact)
+    qh->MERGING= True;
+  if (qh->NOpremerge && (qh->MERGEexact || qh->PREmerge))
+    qh_fprintf(qh, qh->ferr, 7095, "qhull option warning: 'Q0-no-premerge' ignored due to exact merge ('Qx') or pre-merge ('C-n' or 'A-n')\n");
+  if (!qh->NOpremerge && qh->JOGGLEmax > REALmax/2) {
+#ifdef qh_NOmerge
+    qh->JOGGLEmax= 0.0;
+#endif
+  }
+  if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && !qh->PREmerge && !qh->POSTmerge && qh->PRINTprecision)
+    qh_fprintf(qh, qh->ferr, 7038, "qhull option warning: joggle ('QJ') produces simplicial output (i.e., triangles in 2-D).  Unless merging is requested, option 'Qt' has no effect\n");
+  if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) {
+    qh->SCALElast= True;
+    qh_option(qh, "Qbbound-last-qj", NULL, NULL);
+  }
+  if (qh->MERGING && !qh->POSTmerge && qh->premerge_cos > REALmax/2
+  && qh->premerge_centrum == 0.0) {
+    qh->ZEROcentrum= True;
+    qh->ZEROall_ok= True;
+    qh_option(qh, "_zero-centrum", NULL, NULL);
+  }
+  if (qh->JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh->PRINTprecision)
+    qh_fprintf(qh, qh->ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user_r.h).\n",
+          REALepsilon);
+#ifdef qh_NOmerge
+  if (qh->MERGING) {
+    qh_fprintf(qh, qh->ferr, 6045, "qhull option error: merging not installed (qh_NOmerge) for 'Qx', 'Cn' or 'An')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+#endif
+  if (qh->DELAUNAY && qh->KEEPcoplanar && !qh->KEEPinside) {
+    qh->KEEPinside= True;
+    qh_option(qh, "Qinterior-keep", NULL, NULL);
+  }
+  if (qh->VORONOI && !qh->DELAUNAY) {
+    qh_fprintf(qh, qh->ferr, 6038, "qhull internal error (qh_initqhull_globals): if qh.VORONOI is set, qh.DELAUNAY must be set.  Qhull constructs the Delaunay triangulation in order to compute the Voronoi diagram\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (qh->DELAUNAY && qh->HALFspace) {
+    qh_fprintf(qh, qh->ferr, 6046, "qhull option error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    /* same error message in qh_readpoints */
+  }
+  if (!qh->DELAUNAY && (qh->UPPERdelaunay || qh->ATinfinity)) {
+    qh_fprintf(qh, qh->ferr, 6047, "qhull option error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (qh->UPPERdelaunay && qh->ATinfinity) {
+    qh_fprintf(qh, qh->ferr, 6048, "qhull option error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (qh->MERGEpinched && qh->ONLYgood) {
+    qh_fprintf(qh, qh->ferr, 6362, "qhull option error: can not use merge-pinched-vertices ('Q14') with good-facets-only ('Qg')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (qh->MERGEpinched && qh->hull_dim == 2) {
+    trace2((qh, qh->ferr, 2108, "qh_initqhull_globals: disable qh.MERGEpinched for 2-d.  It has no effect"))
+    qh->MERGEpinched= False;
+  }
+  if (qh->SCALElast && !qh->DELAUNAY && qh->PRINTprecision)
+    qh_fprintf(qh, qh->ferr, 7040, "qhull option warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
+  qh->DOcheckmax= (!qh->SKIPcheckmax && (qh->MERGING || qh->APPROXhull));
+  qh->KEEPnearinside= (qh->DOcheckmax && !(qh->KEEPinside && qh->KEEPcoplanar)
+                          && !qh->NOnearinside);
+  if (qh->MERGING)
+    qh->CENTERtype= qh_AScentrum;
+  else if (qh->VORONOI)
+    qh->CENTERtype= qh_ASvoronoi;
+  if (qh->TESTvneighbors && !qh->MERGING) {
+    qh_fprintf(qh, qh->ferr, 6049, "qhull option error: test vertex neighbors('Qv') needs a merge option\n");
+    qh_errexit(qh, qh_ERRinput, NULL ,NULL);
+  }
+  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay)) {
+    qh->hull_dim -= qh->PROJECTinput;
+    if (qh->DELAUNAY) {
+      qh->hull_dim++;
+      if (qh->ATinfinity)
+        extra= 1;
+    }
+  }
+  if (qh->hull_dim <= 1) {
+    qh_fprintf(qh, qh->ferr, 6050, "qhull error: dimension %d must be > 1\n", qh->hull_dim);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  for (k=2, factorial=1.0; k < qh->hull_dim; k++)
+    factorial *= k;
+  qh->AREAfactor= 1.0 / factorial;
+  trace2((qh, qh->ferr, 2005, "qh_initqhull_globals: initialize globals.  input_dim %d, numpoints %d, malloc? %d, projected %d to hull_dim %d\n",
+        qh->input_dim, numpoints, ismalloc, qh->PROJECTinput, qh->hull_dim));
+  qh->normal_size= qh->hull_dim * (int)sizeof(coordT);
+  qh->center_size= qh->normal_size - (int)sizeof(coordT);
+  pointsneeded= qh->hull_dim+1;
+  if (qh->hull_dim > qh_DIMmergeVertex) {
+    qh->MERGEvertices= False;
+    qh_option(qh, "Q3-no-merge-vertices-dim-high", NULL, NULL);
+  }
+  if (qh->GOODpoint)
+    pointsneeded++;
+#ifdef qh_NOtrace
+  if (qh->IStracing || qh->TRACEmerge || qh->TRACEpoint != qh_IDnone || qh->TRACEdist < REALmax/2) {
+      qh_fprintf(qh, qh->ferr, 6051, "qhull option error: tracing is not installed (qh_NOtrace in user_r.h).  Trace options 'Tn', 'TMn', 'TPn' and 'TWn' mostly removed.  Continue with 'Qw' (allow warning)\n");
+      if (!qh->ALLOWwarning)
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+#endif
+  if (qh->RERUN > 1) {
+    qh->TRACElastrun= qh->IStracing; /* qh_build_withrestart duplicates next conditional */
+    if (qh->IStracing && qh->IStracing != -1) {
+      qh_fprintf(qh, qh->ferr, 8162, "qh_initqhull_globals: trace last of TR%d runs at level %d\n", qh->RERUN, qh->IStracing);
+      qh->IStracing= 0;
+    }
+  }else if (qh->TRACEpoint != qh_IDnone || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
+    qh->TRACElevel= (qh->IStracing ? qh->IStracing : 3);
+    qh->IStracing= 0;
+  }
+  if (qh->ROTATErandom == 0 || qh->ROTATErandom == -1) {
+    seed= (int)time(&timedata);
+    if (qh->ROTATErandom  == -1) {
+      seed= -seed;
+      qh_option(qh, "QRandom-seed", &seed, NULL );
+    }else
+      qh_option(qh, "QRotate-random", &seed, NULL);
+    qh->ROTATErandom= seed;
+  }
+  seed= qh->ROTATErandom;
+  if (seed == INT_MIN)    /* default value */
+    seed= 1;
+  else if (seed < 0)
+    seed= -seed;
+  qh_RANDOMseed_(qh, seed);
+  randr= 0.0;
+  for (i=1000; i--; ) {
+    randi= qh_RANDOMint;
+    randr += randi;
+    if (randi > qh_RANDOMmax) {
+      qh_fprintf(qh, qh->ferr, 8036, "\
+qhull configuration error (qh_RANDOMmax in user_r.h): random integer %d > qh_RANDOMmax (%.8g)\n",
+               randi, qh_RANDOMmax);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+  }
+  qh_RANDOMseed_(qh, seed);
+  randr= randr/1000;
+  if (randr < qh_RANDOMmax * 0.1
+  || randr > qh_RANDOMmax * 0.9)
+    qh_fprintf(qh, qh->ferr, 8037, "\
+qhull configuration warning (qh_RANDOMmax in user_r.h): average of 1000 random integers (%.2g) is much different than expected (%.2g).  Is qh_RANDOMmax (%.2g) wrong?\n",
+             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
+  qh->RANDOMa= 2.0 * qh->RANDOMfactor/qh_RANDOMmax;
+  qh->RANDOMb= 1.0 - qh->RANDOMfactor;
+  if (qh_HASHfactor < 1.1) {
+    qh_fprintf(qh, qh->ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
+      qh_HASHfactor);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (numpoints+extra < pointsneeded) {
+    qh_fprintf(qh, qh->ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
+            numpoints, pointsneeded);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  qh_initqhull_outputflags(qh);
+} /* initqhull_globals */
+
+/*---------------------------------
+
+  qh_initqhull_mem(qh )
+    initialize mem_r.c for qhull
+    qh.hull_dim and qh.normal_size determine some of the allocation sizes
+    if qh.MERGING,
+      includes ridgeT
+    calls qh_user_memsizes (user_r.c) to add up to 10 additional sizes for quick allocation
+      (see numsizes below)
+
+  returns:
+    mem_r.c already for qh_memalloc/qh_memfree (errors if called beforehand)
+
+  notes:
+    qh_produceoutput() prints memsizes
+
+*/
+void qh_initqhull_mem(qhT *qh) {
+  int numsizes;
+  int i;
+
+  numsizes= 8+10;
+  qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, numsizes,
+                     qh_MEMbufsize, qh_MEMinitbuf);
+  qh_memsize(qh, (int)sizeof(vertexT));
+  if (qh->MERGING) {
+    qh_memsize(qh, (int)sizeof(ridgeT));
+    qh_memsize(qh, (int)sizeof(mergeT));
+  }
+  qh_memsize(qh, (int)sizeof(facetT));
+  i= SETbasesize + (qh->hull_dim - 1) * SETelemsize;  /* ridge.vertices */
+  qh_memsize(qh, i);
+  qh_memsize(qh, qh->normal_size);        /* normal */
+  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
+  qh_memsize(qh, i);
+  qh_user_memsizes(qh);
+  qh_memsetup(qh);
+} /* initqhull_mem */
+
+/*---------------------------------
+
+  qh_initqhull_outputflags
+    initialize flags concerned with output
+
+  returns:
+    adjust user flags as needed
+
+  see:
+    qh_clear_outputflags() resets the flags
+
+  design:
+    test for qh.PRINTgood (i.e., only print 'good' facets)
+    check for conflicting print output options
+*/
+void qh_initqhull_outputflags(qhT *qh) {
+  boolT printgeom= False, printmath= False, printcoplanar= False;
+  int i;
+
+  trace3((qh, qh->ferr, 3024, "qh_initqhull_outputflags: %s\n", qh->qhull_command));
+  if (!(qh->PRINTgood || qh->PRINTneighbors)) {
+    if (qh->DELAUNAY || qh->KEEParea || qh->KEEPminArea < REALmax/2 || qh->KEEPmerge
+        || (!qh->ONLYgood && (qh->GOODvertex || qh->GOODpoint))) {
+      qh->PRINTgood= True;
+      qh_option(qh, "Pgood", NULL, NULL);
+    }
+  }
+  if (qh->PRINTtransparent) {
+    if (qh->hull_dim != 4 || !qh->DELAUNAY || qh->VORONOI || qh->DROPdim >= 0) {
+      qh_fprintf(qh, qh->ferr, 6215, "qhull option error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    qh->DROPdim= 3;
+    qh->PRINTridges= True;
+  }
+  for (i=qh_PRINTEND; i--; ) {
+    if (qh->PRINTout[i] == qh_PRINTgeom)
+      printgeom= True;
+    else if (qh->PRINTout[i] == qh_PRINTmathematica || qh->PRINTout[i] == qh_PRINTmaple)
+      printmath= True;
+    else if (qh->PRINTout[i] == qh_PRINTcoplanars)
+      printcoplanar= True;
+    else if (qh->PRINTout[i] == qh_PRINTpointnearest)
+      printcoplanar= True;
+    else if (qh->PRINTout[i] == qh_PRINTpointintersect && !qh->HALFspace) {
+      qh_fprintf(qh, qh->ferr, 6053, "qhull option error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }else if (qh->PRINTout[i] == qh_PRINTtriangles && (qh->HALFspace || qh->VORONOI)) {
+      qh_fprintf(qh, qh->ferr, 6054, "qhull option error: option 'Ft' is not available for Voronoi vertices ('v') or halfspace intersection ('H')\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }else if (qh->PRINTout[i] == qh_PRINTcentrums && qh->VORONOI) {
+      qh_fprintf(qh, qh->ferr, 6055, "qhull option error: option 'FC' is not available for Voronoi vertices('v')\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }else if (qh->PRINTout[i] == qh_PRINTvertices) {
+      if (qh->VORONOI)
+        qh_option(qh, "Fvoronoi", NULL, NULL);
+      else
+        qh_option(qh, "Fvertices", NULL, NULL);
+    }
+  }
+  if (printcoplanar && qh->DELAUNAY && qh->JOGGLEmax < REALmax/2) {
+    if (qh->PRINTprecision)
+      qh_fprintf(qh, qh->ferr, 7041, "qhull option warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
+  }
+  if (printmath && (qh->hull_dim > 3 || qh->VORONOI)) {
+    qh_fprintf(qh, qh->ferr, 6056, "qhull option error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (printgeom) {
+    if (qh->hull_dim > 4) {
+      qh_fprintf(qh, qh->ferr, 6057, "qhull option error: Geomview output is only available for 2-d, 3-d and 4-d\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (qh->PRINTnoplanes && !(qh->PRINTcoplanar + qh->PRINTcentrums
+     + qh->PRINTdots + qh->PRINTspheres + qh->DOintersections + qh->PRINTridges)) {
+      qh_fprintf(qh, qh->ferr, 6058, "qhull option error: no output specified for Geomview\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (qh->VORONOI && (qh->hull_dim > 3 || qh->DROPdim >= 0)) {
+      qh_fprintf(qh, qh->ferr, 6059, "qhull option error: Geomview output for Voronoi diagrams only for 2-d\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    /* can not warn about furthest-site Geomview output: no lower_threshold */
+    if (qh->hull_dim == 4 && qh->DROPdim == -1 &&
+        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
+      qh_fprintf(qh, qh->ferr, 7042, "qhull option warning: coplanars, vertices, and centrums output not available for 4-d output(ignored).  Could use 'GDn' instead.\n");
+      qh->PRINTcoplanar= qh->PRINTspheres= qh->PRINTcentrums= False;
+    }
+  }
+  if (!qh->KEEPcoplanar && !qh->KEEPinside && !qh->ONLYgood) {
+    if ((qh->PRINTcoplanar && qh->PRINTspheres) || printcoplanar) {
+      if (qh->QHULLfinished) {
+        qh_fprintf(qh, qh->ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
+      }else {
+        qh->KEEPcoplanar= True;
+        qh_option(qh, "Qcoplanar", NULL, NULL);
+      }
+    }
+  }
+  qh->PRINTdim= qh->hull_dim;
+  if (qh->DROPdim >=0) {    /* after Geomview checks */
+    if (qh->DROPdim < qh->hull_dim) {
+      qh->PRINTdim--;
+      if (!printgeom || qh->hull_dim < 3)
+        qh_fprintf(qh, qh->ferr, 7043, "qhull option warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh->DROPdim);
+    }else
+      qh->DROPdim= -1;
+  }else if (qh->VORONOI) {
+    qh->DROPdim= qh->hull_dim-1;
+    qh->PRINTdim= qh->hull_dim-1;
+  }
+} /* qh_initqhull_outputflags */
+
+/*---------------------------------
+
+  qh_initqhull_start(qh, infile, outfile, errfile )
+    allocate memory if needed and call qh_initqhull_start2()
+*/
+void qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
+
+  qh_initstatistics(qh);
+  qh_initqhull_start2(qh, infile, outfile, errfile);
+} /* initqhull_start */
+
+/*---------------------------------
+
+  qh_initqhull_start2(qh, infile, outfile, errfile )
+    start initialization of qhull
+    initialize statistics, stdio, default values for global variables
+    assumes qh is allocated
+  notes:
+    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
+  see:
+    qh_maxmin() determines the precision constants
+    qh_freeqhull()
+*/
+void qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
+  time_t timedata;
+  int seed;
+
+  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
+  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
+  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));   /* every field is 0, FALSE, NULL */
+  qh->NOerrexit= True;
+  qh->DROPdim= -1;
+  qh->ferr= errfile;
+  qh->fin= infile;
+  qh->fout= outfile;
+  qh->furthest_id= qh_IDunknown;
+#ifndef qh_NOmerge
+  qh->JOGGLEmax= REALmax;
+#else
+  qh->JOGGLEmax= 0.0;  /* Joggle ('QJ') if qh_NOmerge */
+#endif
+  qh->KEEPminArea= REALmax;
+  qh->last_low= REALmax;
+  qh->last_high= REALmax;
+  qh->last_newhigh= REALmax;
+  qh->last_random= 1; /* reentrant only */
+  qh->lastcpu= 0.0;
+  qh->max_outside= 0.0;
+  qh->max_vertex= 0.0;
+  qh->MAXabs_coord= 0.0;
+  qh->MAXsumcoord= 0.0;
+  qh->MAXwidth= -REALmax;
+  qh->MERGEindependent= True;
+  qh->MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
+  qh->MINoutside= 0.0;
+  qh->MINvisible= REALmax;
+  qh->MAXcoplanar= REALmax;
+  qh->outside_err= REALmax;
+  qh->premerge_centrum= 0.0;
+  qh->premerge_cos= REALmax;
+  qh->PRINTprecision= True;
+  qh->PRINTradius= 0.0;
+  qh->postmerge_cos= REALmax;
+  qh->postmerge_centrum= 0.0;
+  qh->ROTATErandom= INT_MIN;
+  qh->MERGEvertices= True;
+  qh->totarea= 0.0;
+  qh->totvol= 0.0;
+  qh->TRACEdist= REALmax;
+  qh->TRACEpoint= qh_IDnone;    /* recompile to trace a point, or use 'TPn' */
+  qh->tracefacet_id= UINT_MAX;  /* recompile to trace a facet, set to UINT_MAX when done, see userprintf_r.c/qh_fprintf */
+  qh->traceridge_id= UINT_MAX;  /* recompile to trace a ridge, set to UINT_MAX when done, see userprintf_r.c/qh_fprintf */
+  qh->tracevertex_id= UINT_MAX; /* recompile to trace a vertex, set to UINT_MAX when done, see userprintf_r.c/qh_fprintf */
+  seed= (int)time(&timedata);
+  qh_RANDOMseed_(qh, seed);
+  qh->run_id= qh_RANDOMint;
+  if(!qh->run_id)
+      qh->run_id++;  /* guarantee non-zero */
+  qh_option(qh, "run-id", &qh->run_id, NULL);
+  strcat(qh->qhull, "qhull");
+} /* initqhull_start2 */
+
+/*---------------------------------
+
+  qh_initthresholds(qh, commandString )
+    set thresholds for printing and scaling from commandString
+
+  returns:
+    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
+
+  see:
+    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
+    qh_inthresholds()
+
+  design:
+    for each 'Pdn' or 'PDn' option
+      check syntax
+      set qh.lower_threshold or qh.upper_threshold
+    set qh.GOODthreshold if an unbounded threshold is used
+    set qh.SPLITthreshold if a bounded threshold is used
+*/
+void qh_initthresholds(qhT *qh, char *command) {
+  realT value;
+  int idx, maxdim, k;
+  char *s= command; /* non-const due to strtol */
+  char *lastoption, *lastwarning= NULL;
+  char key;
+
+  maxdim= qh->input_dim;
+  if (qh->DELAUNAY && (qh->PROJECTdelaunay || qh->PROJECTinput))
+    maxdim++;
+  while (*s) {
+    if (*s == '-')
+      s++;
+    if (*s == 'P') {
+      lastoption= s++;
+      while (*s && !isspace(key= *s++)) {
+        if (key == 'd' || key == 'D') {
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7044, "qhull option warning: no dimension given for Print option 'P%c' at: %s.  Ignored\n",
+                    key, s-1);
+            lastwarning= lastoption;
+            continue;
+          }
+          idx= qh_strtol(s, &s);
+          if (idx >= qh->hull_dim) {
+            qh_fprintf(qh, qh->ferr, 7045, "qhull option warning: dimension %d for Print option 'P%c' is >= %d.  Ignored\n",
+                idx, key, qh->hull_dim);
+            lastwarning= lastoption;
+            continue;
+          }
+          if (*s == ':') {
+            s++;
+            value= qh_strtod(s, &s);
+            if (fabs((double)value) > 1.0) {
+              qh_fprintf(qh, qh->ferr, 7046, "qhull option warning: value %2.4g for Print option 'P%c' is > +1 or < -1.  Ignored\n",
+                      value, key);
+              lastwarning= lastoption;
+              continue;
+            }
+          }else
+            value= 0.0;
+          if (key == 'd')
+            qh->lower_threshold[idx]= value;
+          else
+            qh->upper_threshold[idx]= value;
+        }
+      }
+    }else if (*s == 'Q') {
+      lastoption= s++;
+      while (*s && !isspace(key= *s++)) {
+        if (key == 'b' && *s == 'B') {
+          s++;
+          for (k=maxdim; k--; ) {
+            qh->lower_bound[k]= -qh_DEFAULTbox;
+            qh->upper_bound[k]= qh_DEFAULTbox;
+          }
+        }else if (key == 'b' && *s == 'b')
+          s++;
+        else if (key == 'b' || key == 'B') {
+          if (!isdigit(*s)) {
+            qh_fprintf(qh, qh->ferr, 7047, "qhull option warning: no dimension given for Qhull option 'Q%c'\n",
+                    key);
+            lastwarning= lastoption;
+            continue;
+          }
+          idx= qh_strtol(s, &s);
+          if (idx >= maxdim) {
+            qh_fprintf(qh, qh->ferr, 7048, "qhull option warning: dimension %d for Qhull option 'Q%c' is >= %d.  Ignored\n",
+                idx, key, maxdim);
+            lastwarning= lastoption;
+            continue;
+          }
+          if (*s == ':') {
+            s++;
+            value= qh_strtod(s, &s);
+          }else if (key == 'b')
+            value= -qh_DEFAULTbox;
+          else
+            value= qh_DEFAULTbox;
+          if (key == 'b')
+            qh->lower_bound[idx]= value;
+          else
+            qh->upper_bound[idx]= value;
+        }
+      }
+    }else {
+      while (*s && !isspace(*s))
+        s++;
+    }
+    while (isspace(*s))
+      s++;
+  }
+  for (k=qh->hull_dim; k--; ) {
+    if (qh->lower_threshold[k] > -REALmax/2) {
+      qh->GOODthreshold= True;
+      if (qh->upper_threshold[k] < REALmax/2) {
+        qh->SPLITthresholds= True;
+        qh->GOODthreshold= False;
+        break;
+      }
+    }else if (qh->upper_threshold[k] < REALmax/2)
+      qh->GOODthreshold= True;
+  }
+  if (lastwarning && !qh->ALLOWwarning) {
+    qh_fprintf(qh, qh->ferr, 6036, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n",
+      command, (int)(lastwarning-command));
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+} /* initthresholds */
+
+/*---------------------------------
+
+  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
+    Report error if library does not agree with caller
+
+  notes:
+    NOerrors -- qh_lib_check can not call qh_errexit()
+*/
+void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
+    int last_errcode= qh_ERRnone;
+
+#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
+    /*_CrtSetBreakAlloc(744);*/  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
+    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
+    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
+    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
+    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
+    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
+    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
+    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
+#endif
+
+    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
+      qh_fprintf_stderr(6257, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a static qhT.  Qhull library is reentrant.\n");
+      last_errcode= 6257;
+    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
+      qh_fprintf_stderr(6258, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a dynamic qhT via qh_QHpointer.  Qhull library is reentrant.\n");
+      last_errcode= 6258;
+    }else if (qhullLibraryType != QHULL_REENTRANT) { /* 2 */
+      qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
+      last_errcode= 6262;
+    }
+    if (qhTsize != (int)sizeof(qhT)) {
+      qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for qhull library is %d.\n", qhTsize, (int)sizeof(qhT));
+      last_errcode= 6249;
+    }
+    if (vertexTsize != (int)sizeof(vertexT)) {
+      qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for qhull library is %d.\n", vertexTsize, (int)sizeof(vertexT));
+      last_errcode= 6250;
+    }
+    if (ridgeTsize != (int)sizeof(ridgeT)) {
+      qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for qhull library is %d.\n", ridgeTsize, (int)sizeof(ridgeT));
+      last_errcode= 6251;
+    }
+    if (facetTsize != (int)sizeof(facetT)) {
+      qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for qhull library is %d.\n", facetTsize, (int)sizeof(facetT));
+      last_errcode= 6252;
+    }
+    if (setTsize && setTsize != SETbasesize) {
+      qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for qhull library is %d.\n", setTsize, SETbasesize);
+      last_errcode= 6253;
+    }
+    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
+      qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for qhull library is %d.\n", qhmemTsize, (int)sizeof(qhmemT));
+      last_errcode= 6254;
+    }
+    if (last_errcode) {
+      qh_fprintf_stderr(6259, "qhull internal error (qh_lib_check): Cannot continue due to QH%d.  '%s' is not reentrant (e.g., qhull.so) or out-of-date.  Exit with %d\n",
+            last_errcode, qh_version2, last_errcode - 6200);
+      qh_exit(last_errcode - 6200);  /* can not use qh_errexit(), must be less than 255 */
+    }
+} /* lib_check */
+
+/*---------------------------------
+
+  qh_option(qh, option, intVal, realVal )
+    add an option description to qh.qhull_options
+
+  notes:
+    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
+    will be printed with statistics ('Ts') and errors
+    strlen(option) < 40
+*/
+void qh_option(qhT *qh, const char *option, int *i, realT *r) {
+  char buf[200];
+  int buflen, remainder;
+
+  if (strlen(option) > sizeof(buf)-30-30) {
+    qh_fprintf(qh, qh->ferr, 6408, "qhull internal error (qh_option): option (%d chars) has more than %d chars.  May overflow temporary buffer.  Option '%s'\n",
+        (int)strlen(option), (int)sizeof(buf)-30-30, option);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  buflen = 0;
+  buflen += snprintf(buf, sizeof(buf) / sizeof(buf[0]), "  %s", option);
+  if (i)
+    buflen += snprintf(buf+buflen, sizeof(buf) / sizeof(buf[0]) - buflen, " %d", *i);
+  if (r)
+    buflen += snprintf(buf+buflen, sizeof(buf) / sizeof(buf[0]) - buflen, " %2.2g", *r);
+  qh->qhull_optionlen += buflen;
+  remainder= (int)(sizeof(qh->qhull_options) - strlen(qh->qhull_options)) - 1;    /* WARN64 */
+  maximize_(remainder, 0);
+  if (qh->qhull_optionlen >= qh_OPTIONline && remainder > 0) {
+    strncat(qh->qhull_options, "\n", (unsigned int)remainder);
+    --remainder;
+    qh->qhull_optionlen= buflen;
+  }
+  if (buflen > remainder) {
+    trace1((qh, qh->ferr, 1058, "qh_option: option would overflow qh.qhull_options. Truncated '%s'\n", buf));
+  }
+  strncat(qh->qhull_options, buf, (unsigned int)remainder);
+} /* option */
+
+/*---------------------------------
+
+  qh_zero( qh, errfile )
+    Initialize and zero Qhull's memory for qh_new_qhull()
+
+  notes:
+    Not needed in global_r.c because static variables are initialized to zero
+*/
+void qh_zero(qhT *qh, FILE *errfile) {
+    memset((char *)qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
+    qh->NOerrexit= True;
+    qh_meminit(qh, errfile);
+} /* zero */
+
diff --git a/vendor/qhull/libqhull_r/io_r.c b/vendor/qhull/libqhull_r/io_r.c
new file mode 100644
index 0000000..148d312
--- /dev/null
+++ b/vendor/qhull/libqhull_r/io_r.c
@@ -0,0 +1,4128 @@
+/*
  ---------------------------------
+
+   io_r.c
+   Input/Output routines of qhull application
+
+   see qh-io_r.htm and io_r.h
+
+   see user_r.c for qh_errprint and qh_printfacetlist
+
+   unix_r.c calls qh_readpoints and qh_produce_output
+
+   unix_r.c and user_r.c are the only callers of io_r.c functions
+   This allows the user to avoid loading io_r.o from qhull.a
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/io_r.c#12 $$Change: 2965 $
+   $DateTime: 2020/06/04 15:37:41 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+/*========= -functions in alphabetical order after qh_produce_output(qh)  =====*/
+
+/*---------------------------------
+
+  qh_produce_output(qh )
+  qh_produce_output2(qh )
+    prints out the result of qhull in desired format
+    qh_produce_output2 does not call qh_prepare_output
+      qh_checkpolygon is valid for qh_prepare_output
+    if qh.GETarea
+      computes and prints area and volume
+    qh.PRINTout[] is an array of output formats
+
+  notes:
+    prints output in qh.PRINTout order
+*/
+void qh_produce_output(qhT *qh) {
+    int tempsize= qh_setsize(qh, qh->qhmem.tempstack);
+
+    qh_prepare_output(qh);
+    qh_produce_output2(qh);
+    if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
+        qh_fprintf(qh, qh->ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
+            qh_setsize(qh, qh->qhmem.tempstack));
+        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+} /* produce_output */
+
+
+void qh_produce_output2(qhT *qh) {
+  int i, tempsize= qh_setsize(qh, qh->qhmem.tempstack), d_1;
+
+  fflush(NULL);
+  if (qh->PRINTsummary)
+    qh_printsummary(qh, qh->ferr);
+  else if (qh->PRINTout[0] == qh_PRINTnone)
+    qh_printsummary(qh, qh->fout);
+  for (i=0; i < qh_PRINTEND; i++)
+    qh_printfacets(qh, qh->fout, qh->PRINTout[i], qh->facet_list, NULL, !qh_ALL);
+  fflush(NULL);
+
+  qh_allstatistics(qh);
+  if (qh->PRINTprecision && !qh->MERGING && (qh->JOGGLEmax > REALmax/2 || qh->RERUN))
+    qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
+  if (qh->VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
+    qh_printstats(qh, qh->ferr, qh->qhstat.vridges, NULL);
+  if (qh->PRINTstatistics) {
+    qh_printstatistics(qh, qh->ferr, "");
+    qh_memstatistics(qh, qh->ferr);
+    d_1= SETbasesize + (qh->hull_dim - 1) * SETelemsize;
+    qh_fprintf(qh, qh->ferr, 8040, "\
+    size in bytes: merge %d ridge %d vertex %d facet %d\n\
+         normal %d ridge vertices %d facet vertices or neighbors %d\n",
+            (int)sizeof(mergeT), (int)sizeof(ridgeT),
+            (int)sizeof(vertexT), (int)sizeof(facetT),
+            qh->normal_size, d_1, d_1 + SETelemsize);
+  }
+  if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
+    qh_fprintf(qh, qh->ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
+             qh_setsize(qh, qh->qhmem.tempstack));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+} /* produce_output2 */
+
+/*---------------------------------
+
+  qh_dfacet(qh, id )
+    print facet by id, for debugging
+
+*/
+void qh_dfacet(qhT *qh, unsigned int id) {
+  facetT *facet;
+
+  FORALLfacets {
+    if (facet->id == id) {
+      qh_printfacet(qh, qh->fout, facet);
+      break;
+    }
+  }
+} /* dfacet */
+
+
+/*---------------------------------
+
+  qh_dvertex(qh, id )
+    print vertex by id, for debugging
+*/
+void qh_dvertex(qhT *qh, unsigned int id) {
+  vertexT *vertex;
+
+  FORALLvertices {
+    if (vertex->id == id) {
+      qh_printvertex(qh, qh->fout, vertex);
+      break;
+    }
+  }
+} /* dvertex */
+
+
+/*---------------------------------
+
+  qh_compare_facetarea( p1, p2 )
+    used by qsort() to order facets by area
+*/
+int qh_compare_facetarea(const void *p1, const void *p2) {
+  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
+
+  if (!a->isarea)
+    return -1;
+  if (!b->isarea)
+    return 1;
+  if (a->f.area > b->f.area)
+    return 1;
+  else if (a->f.area == b->f.area)
+    return 0;
+  return -1;
+} /* compare_facetarea */
+
+/*---------------------------------
+
+  qh_compare_facetvisit( p1, p2 )
+    used by qsort() to order facets by visit id or id
+*/
+int qh_compare_facetvisit(const void *p1, const void *p2) {
+  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
+  int i,j;
+
+  if (!(i= (int)a->visitid))
+    i= (int)(0 - a->id); /* sign distinguishes id from visitid */
+  if (!(j= (int)b->visitid))
+    j= (int)(0 - b->id);
+  return(i - j);
+} /* compare_facetvisit */
+
+/*---------------------------------
+
+  qh_compare_nummerge( p1, p2 )
+    used by qsort() to order facets by number of merges
+
+notes:
+    called by qh_markkeep ('PMerge-keep')
+*/
+int qh_compare_nummerge(const void *p1, const void *p2) {
+  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
+
+  return(a->nummerge - b->nummerge);
+} /* compare_nummerge */
+
+/*---------------------------------
+
+  qh_copyfilename(qh, dest, size, source, length )
+    copy filename identified by qh_skipfilename()
+
+  notes:
+    see qh_skipfilename() for syntax
+*/
+void qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length) {
+  char c= *source;
+
+  if (length > size + 1) {
+      qh_fprintf(qh, qh->ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  strncpy(filename, source, (size_t)length);
+  filename[length]= '\0';
+  if (c == '\'' || c == '"') {
+    char *s= filename + 1;
+    char *t= filename;
+    while (*s) {
+      if (*s == c) {
+          if (s[-1] == '\\')
+              t[-1]= c;
+      }else
+          *t++= *s;
+      s++;
+    }
+    *t= '\0';
+  }
+} /* copyfilename */
+
+/*---------------------------------
+
+  qh_countfacets(qh, facetlist, facets, printall,
+          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
+    count good facets for printing and set visitid
+    if allfacets, ignores qh_skipfacet()
+
+  notes:
+    qh_printsummary and qh_countfacets must match counts
+
+  returns:
+    numfacets, numsimplicial, total neighbors, numridges, coplanars
+    each facet with ->visitid indicating 1-relative position
+      ->visitid==0 indicates not good
+
+  notes
+    numfacets >= numsimplicial
+    if qh.NEWfacets,
+      does not count visible facets (matches qh_printafacet)
+
+  design:
+    for all facets on facetlist and in facets set
+      unless facet is skipped or visible (i.e., will be deleted)
+        mark facet->visitid
+        update counts
+*/
+void qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
+    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
+  facetT *facet, **facetp;
+  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
+
+  FORALLfacet_(facetlist) {
+    if ((facet->visible && qh->NEWfacets)
+    || (!printall && qh_skipfacet(qh, facet)))
+      facet->visitid= 0;
+    else {
+      facet->visitid= (unsigned int)(++numfacets);
+      totneighbors += qh_setsize(qh, facet->neighbors);
+      if (facet->simplicial) {
+        numsimplicial++;
+        if (facet->keepcentrum && facet->tricoplanar)
+          numtricoplanars++;
+      }else
+        numridges += qh_setsize(qh, facet->ridges);
+      if (facet->coplanarset)
+        numcoplanars += qh_setsize(qh, facet->coplanarset);
+    }
+  }
+
+  FOREACHfacet_(facets) {
+    if ((facet->visible && qh->NEWfacets)
+    || (!printall && qh_skipfacet(qh, facet)))
+      facet->visitid= 0;
+    else {
+      facet->visitid= (unsigned int)(++numfacets);
+      totneighbors += qh_setsize(qh, facet->neighbors);
+      if (facet->simplicial){
+        numsimplicial++;
+        if (facet->keepcentrum && facet->tricoplanar)
+          numtricoplanars++;
+      }else
+        numridges += qh_setsize(qh, facet->ridges);
+      if (facet->coplanarset)
+        numcoplanars += qh_setsize(qh, facet->coplanarset);
+    }
+  }
+  qh->visit_id += (unsigned int)numfacets + 1;
+  *numfacetsp= numfacets;
+  *numsimplicialp= numsimplicial;
+  *totneighborsp= totneighbors;
+  *numridgesp= numridges;
+  *numcoplanarsp= numcoplanars;
+  *numtricoplanarsp= numtricoplanars;
+} /* countfacets */
+
+/*---------------------------------
+
+  qh_detvnorm(qh, vertex, vertexA, centers, offset )
+    compute separating plane of the Voronoi diagram for a pair of input sites
+    centers= set of facets (i.e., Voronoi vertices)
+      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
+
+  assumes:
+    qh_ASvoronoi and qh_vertexneighbors() already set
+
+  returns:
+    norm
+      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
+      copy the data before reusing qh.gm_matrix
+    offset
+      if 'QVn'
+        sign adjusted so that qh.GOODvertexp is inside
+      else
+        sign adjusted so that vertex is inside
+
+    qh.gm_matrix= simplex of points from centers relative to first center
+
+  notes:
+    in io_r.c so that code for 'v Tv' can be removed by removing io_r.c
+    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
+
+  design:
+    determine midpoint of input sites
+    build points as the set of Voronoi vertices
+    select a simplex from points (if necessary)
+      include midpoint if the Voronoi region is unbounded
+    relocate the first vertex of the simplex to the origin
+    compute the normalized hyperplane through the simplex
+    orient the hyperplane toward 'QVn' or 'vertex'
+    if 'Tv' or 'Ts'
+      if bounded
+        test that hyperplane is the perpendicular bisector of the input sites
+      test that Voronoi vertices not in the simplex are still on the hyperplane
+    free up temporary memory
+*/
+pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
+  facetT *facet, **facetp;
+  int  i, k, pointid, pointidA, point_i, point_n;
+  setT *simplex= NULL;
+  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
+  coordT *coord, *gmcoord, *normalp;
+  setT *points= qh_settemp(qh, qh->TEMPsize);
+  boolT nearzero= False;
+  boolT unbounded= False;
+  int numcenters= 0;
+  int dim= qh->hull_dim - 1;
+  realT dist, offset, angle, zero= 0.0;
+
+  midpoint= qh->gm_matrix + qh->hull_dim * qh->hull_dim;  /* last row */
+  for (k=0; k < dim; k++)
+    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
+  FOREACHfacet_(centers) {
+    numcenters++;
+    if (!facet->visitid)
+      unbounded= True;
+    else {
+      if (!facet->center)
+        facet->center= qh_facetcenter(qh, facet->vertices);
+      qh_setappend(qh, &points, facet->center);
+    }
+  }
+  if (numcenters > dim) {
+    simplex= qh_settemp(qh, qh->TEMPsize);
+    qh_setappend(qh, &simplex, vertex->point);
+    if (unbounded)
+      qh_setappend(qh, &simplex, midpoint);
+    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
+    qh_setdelnth(qh, simplex, 0);
+  }else if (numcenters == dim) {
+    if (unbounded)
+      qh_setappend(qh, &points, midpoint);
+    simplex= points;
+  }else {
+    qh_fprintf(qh, qh->ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  i= 0;
+  gmcoord= qh->gm_matrix;
+  point0= SETfirstt_(simplex, pointT);
+  FOREACHpoint_(simplex) {
+    if (qh->IStracing >= 4)
+      qh_printmatrix(qh, qh->ferr, "qh_detvnorm: Voronoi vertex or midpoint",
+                              &point, 1, dim);
+    if (point != point0) {
+      qh->gm_row[i++]= gmcoord;
+      coord= point0;
+      for (k=dim; k--; )
+        *(gmcoord++)= *point++ - *coord++;
+    }
+  }
+  qh->gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
+  normal= gmcoord;
+  qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True,
+                normal, &offset, &nearzero);
+  /* nearzero is true for axis-parallel hyperplanes (e.g., a bounding box).  Should detect degenerate hyperplanes.  See 'Tv' check following */
+  if (qh->GOODvertexp == vertexA->point)
+    inpoint= vertexA->point;
+  else
+    inpoint= vertex->point;
+  zinc_(Zdistio);
+  dist= qh_distnorm(dim, inpoint, normal, &offset);
+  if (dist > 0) {
+    offset= -offset;
+    normalp= normal;
+    for (k=dim; k--; ) {
+      *normalp= -(*normalp);
+      normalp++;
+    }
+  }
+  if (qh->VERIFYoutput || qh->PRINTstatistics) {
+    pointid= qh_pointid(qh, vertex->point);
+    pointidA= qh_pointid(qh, vertexA->point);
+    if (!unbounded) {
+      zinc_(Zdiststat);
+      dist= qh_distnorm(dim, midpoint, normal, &offset);
+      if (dist < 0)
+        dist= -dist;
+      zzinc_(Zridgemid);
+      wwmax_(Wridgemidmax, dist);
+      wwadd_(Wridgemid, dist);
+      trace4((qh, qh->ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
+                 pointid, pointidA, dist));
+      for (k=0; k < dim; k++)
+        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
+      qh_normalize(qh, midpoint, dim, False);
+      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
+      if (angle < 0.0)
+        angle= angle + 1.0;
+      else
+        angle= angle - 1.0;
+      if (angle < 0.0)
+        angle= -angle;
+      trace4((qh, qh->ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
+                 pointid, pointidA, angle, nearzero));
+      if (nearzero) {
+        zzinc_(Zridge0);
+        wwmax_(Wridge0max, angle);
+        wwadd_(Wridge0, angle);
+      }else {
+        zzinc_(Zridgeok)
+        wwmax_(Wridgeokmax, angle);
+        wwadd_(Wridgeok, angle);
+      }
+    }
+    if (simplex != points) {
+      FOREACHpoint_i_(qh, points) {
+        if (!qh_setin(simplex, point)) {
+          facet= SETelemt_(centers, point_i, facetT);
+          zinc_(Zdiststat);
+          dist= qh_distnorm(dim, point, normal, &offset);
+          if (dist < 0)
+            dist= -dist;
+          zzinc_(Zridge);
+          wwmax_(Wridgemax, dist);
+          wwadd_(Wridge, dist);
+          trace4((qh, qh->ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
+                             pointid, pointidA, facet->visitid, dist));
+        }
+      }
+    }
+  }
+  *offsetp= offset;
+  if (simplex != points)
+    qh_settempfree(qh, &simplex);
+  qh_settempfree(qh, &points);
+  return normal;
+} /* detvnorm */
+
+/*---------------------------------
+
+  qh_detvridge(qh, vertexA )
+    determine Voronoi ridge from 'seen' neighbors of vertexA
+    include one vertex-at-infinite if an !neighbor->visitid
+
+  returns:
+    temporary set of centers (facets, i.e., Voronoi vertices)
+    sorted by center id
+*/
+setT *qh_detvridge(qhT *qh, vertexT *vertex) {
+  setT *centers= qh_settemp(qh, qh->TEMPsize);
+  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
+  facetT *neighbor, **neighborp;
+  boolT firstinf= True;
+
+  FOREACHneighbor_(vertex) {
+    if (neighbor->seen) {
+      if (neighbor->visitid) {
+        if (!neighbor->tricoplanar || qh_setunique(qh, &tricenters, neighbor->center))
+          qh_setappend(qh, ¢ers, neighbor);
+      }else if (firstinf) {
+        firstinf= False;
+        qh_setappend(qh, ¢ers, neighbor);
+      }
+    }
+  }
+  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(qh, centers),
+             sizeof(facetT *), qh_compare_facetvisit);
+  qh_settempfree(qh, &tricenters);
+  return centers;
+} /* detvridge */
+
+/*---------------------------------
+
+  qh_detvridge3(qh, atvertex, vertex )
+    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
+    include one vertex-at-infinite for !neighbor->visitid
+    assumes all facet->seen2= True
+
+  returns:
+    temporary set of centers (facets, i.e., Voronoi vertices)
+    listed in adjacency order (!oriented)
+    all facet->seen2= True
+
+  design:
+    mark all neighbors of atvertex
+    for each adjacent neighbor of both atvertex and vertex
+      if neighbor selected
+        add neighbor to set of Voronoi vertices
+*/
+setT *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex) {
+  setT *centers= qh_settemp(qh, qh->TEMPsize);
+  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
+  facetT *neighbor, **neighborp, *facet= NULL;
+  boolT firstinf= True;
+
+  FOREACHneighbor_(atvertex)
+    neighbor->seen2= False;
+  FOREACHneighbor_(vertex) {
+    if (!neighbor->seen2) {
+      facet= neighbor;
+      break;
+    }
+  }
+  while (facet) {
+    facet->seen2= True;
+    if (neighbor->seen) {
+      if (facet->visitid) {
+        if (!facet->tricoplanar || qh_setunique(qh, &tricenters, facet->center))
+          qh_setappend(qh, ¢ers, facet);
+      }else if (firstinf) {
+        firstinf= False;
+        qh_setappend(qh, ¢ers, facet);
+      }
+    }
+    FOREACHneighbor_(facet) {
+      if (!neighbor->seen2) {
+        if (qh_setin(vertex->neighbors, neighbor))
+          break;
+        else
+          neighbor->seen2= True;
+      }
+    }
+    facet= neighbor;
+  }
+  if (qh->CHECKfrequently) {
+    FOREACHneighbor_(vertex) {
+      if (!neighbor->seen2) {
+          qh_fprintf(qh, qh->ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
+                 qh_pointid(qh, vertex->point), neighbor->id);
+        qh_errexit(qh, qh_ERRqhull, neighbor, NULL);
+      }
+    }
+  }
+  FOREACHneighbor_(atvertex)
+    neighbor->seen2= True;
+  qh_settempfree(qh, &tricenters);
+  return centers;
+} /* detvridge3 */
+
+/*---------------------------------
+
+  qh_eachvoronoi(qh, fp, printvridge, vertex, visitall, innerouter, inorder )
+    if visitall,
+      visit all Voronoi ridges for vertex (i.e., an input site)
+    else
+      visit all unvisited Voronoi ridges for vertex
+      all vertex->seen= False if unvisited
+    assumes
+      all facet->seen= False
+      all facet->seen2= True (for qh_detvridge3)
+      all facet->visitid == 0 if vertex_at_infinity
+                         == index of Voronoi vertex
+                         >= qh.num_facets if ignored
+    innerouter:
+      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
+      qh_RIDGEinner- only inner
+      qh_RIDGEouter- only outer
+
+    if inorder
+      orders vertices for 3-d Voronoi diagrams
+
+  returns:
+    number of visited ridges (does not include previously visited ridges)
+
+    if printvridge,
+      calls printvridge( fp, vertex, vertexA, centers)
+        fp== any pointer (assumes FILE*)
+             fp may be NULL for QhullQh::qh_fprintf which calls appendQhullMessage
+        vertex,vertexA= pair of input sites that define a Voronoi ridge
+        centers= set of facets (i.e., Voronoi vertices)
+                 ->visitid == index or 0 if vertex_at_infinity
+                 ordered for 3-d Voronoi diagram
+  notes:
+    uses qh.vertex_visit
+
+  see:
+    qh_eachvoronoi_all()
+
+  design:
+    mark selected neighbors of atvertex
+    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
+      for each unvisited vertex
+        if atvertex and vertex share more than d-1 neighbors
+          bump totalcount
+          if printvridge defined
+            build the set of shared neighbors (i.e., Voronoi vertices)
+            call printvridge
+*/
+int qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
+  boolT unbounded;
+  int count;
+  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
+  setT *centers;
+  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
+
+  vertexT *vertex, **vertexp;
+  boolT firstinf;
+  unsigned int numfacets= (unsigned int)qh->num_facets;
+  int totridges= 0;
+
+  qh->vertex_visit++;
+  atvertex->seen= True;
+  if (visitall) {
+    FORALLvertices
+      vertex->seen= False;
+  }
+  FOREACHneighbor_(atvertex) {
+    if (neighbor->visitid < numfacets)
+      neighbor->seen= True;
+  }
+  FOREACHneighbor_(atvertex) {
+    if (neighbor->seen) {
+      FOREACHvertex_(neighbor->vertices) {
+        if (vertex->visitid != qh->vertex_visit && !vertex->seen) {
+          vertex->visitid= qh->vertex_visit;
+          count= 0;
+          firstinf= True;
+          qh_settruncate(qh, tricenters, 0);
+          FOREACHneighborA_(vertex) {
+            if (neighborA->seen) {
+              if (neighborA->visitid) {
+                if (!neighborA->tricoplanar || qh_setunique(qh, &tricenters, neighborA->center))
+                  count++;
+              }else if (firstinf) {
+                count++;
+                firstinf= False;
+              }
+            }
+          }
+          if (count >= qh->hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
+            if (firstinf) {
+              if (innerouter == qh_RIDGEouter)
+                continue;
+              unbounded= False;
+            }else {
+              if (innerouter == qh_RIDGEinner)
+                continue;
+              unbounded= True;
+            }
+            totridges++;
+            trace4((qh, qh->ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
+                  count, qh_pointid(qh, atvertex->point), qh_pointid(qh, vertex->point)));
+            if (printvridge) {
+              if (inorder && qh->hull_dim == 3+1) /* 3-d Voronoi diagram */
+                centers= qh_detvridge3(qh, atvertex, vertex);
+              else
+                centers= qh_detvridge(qh, vertex);
+              (*printvridge)(qh, fp, atvertex, vertex, centers, unbounded);
+              qh_settempfree(qh, ¢ers);
+            }
+          }
+        }
+      }
+    }
+  }
+  FOREACHneighbor_(atvertex)
+    neighbor->seen= False;
+  qh_settempfree(qh, &tricenters);
+  return totridges;
+} /* eachvoronoi */
+
+
+/*---------------------------------
+
+  qh_eachvoronoi_all(qh, fp, printvridge, isUpper, innerouter, inorder )
+    visit all Voronoi ridges
+
+    innerouter:
+      see qh_eachvoronoi()
+
+    if inorder
+      orders vertices for 3-d Voronoi diagrams
+
+  returns
+    total number of ridges
+
+    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
+      facet->visitid= Voronoi vertex index(same as 'o' format)
+    else
+      facet->visitid= 0
+
+    if printvridge,
+      calls printvridge( fp, vertex, vertexA, centers)
+      [see qh_eachvoronoi]
+
+  notes:
+    Not used for qhull.exe
+    same effect as qh_printvdiagram but ridges not sorted by point id
+*/
+int qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
+  facetT *facet;
+  vertexT *vertex;
+  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
+  int totridges= 0;
+
+  qh_clearcenters(qh, qh_ASvoronoi);
+  qh_vertexneighbors(qh);
+  maximize_(qh->visit_id, (unsigned int)qh->num_facets);
+  FORALLfacets {
+    facet->visitid= 0;
+    facet->seen= False;
+    facet->seen2= True;
+  }
+  FORALLfacets {
+    if (facet->upperdelaunay == isUpper)
+      facet->visitid= (unsigned int)(numcenters++);
+  }
+  FORALLvertices
+    vertex->seen= False;
+  FORALLvertices {
+    if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
+      continue;
+    totridges += qh_eachvoronoi(qh, fp, printvridge, vertex,
+                   !qh_ALL, innerouter, inorder);
+  }
+  return totridges;
+} /* eachvoronoi_all */
+
+/*---------------------------------
+
+  qh_facet2point(qh, facet, point0, point1, mindist )
+    return two projected temporary vertices for a 2-d facet
+    may be non-simplicial
+
+  returns:
+    point0 and point1 oriented and projected to the facet
+    returns mindist (maximum distance below plane)
+*/
+void qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
+  vertexT *vertex0, *vertex1;
+  realT dist;
+
+  if (facet->toporient ^ qh_ORIENTclock) {
+    vertex0= SETfirstt_(facet->vertices, vertexT);
+    vertex1= SETsecondt_(facet->vertices, vertexT);
+  }else {
+    vertex1= SETfirstt_(facet->vertices, vertexT);
+    vertex0= SETsecondt_(facet->vertices, vertexT);
+  }
+  zadd_(Zdistio, 2);
+  qh_distplane(qh, vertex0->point, facet, &dist);
+  *mindist= dist;
+  *point0= qh_projectpoint(qh, vertex0->point, facet, dist);
+  qh_distplane(qh, vertex1->point, facet, &dist);
+  minimize_(*mindist, dist);
+  *point1= qh_projectpoint(qh, vertex1->point, facet, dist);
+} /* facet2point */
+
+
+/*---------------------------------
+
+  qh_facetvertices(qh, facetlist, facets, allfacets )
+    returns temporary set of vertices in a set and/or list of facets
+    if allfacets, ignores qh_skipfacet()
+
+  returns:
+    vertices with qh.vertex_visit
+
+  notes:
+    optimized for allfacets of facet_list
+
+  design:
+    if allfacets of facet_list
+      create vertex set from vertex_list
+    else
+      for each selected facet in facets or facetlist
+        append unvisited vertices to vertex set
+*/
+setT *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets) {
+  setT *vertices;
+  facetT *facet, **facetp;
+  vertexT *vertex, **vertexp;
+
+  qh->vertex_visit++;
+  if (facetlist == qh->facet_list && allfacets && !facets) {
+    vertices= qh_settemp(qh, qh->num_vertices);
+    FORALLvertices {
+      vertex->visitid= qh->vertex_visit;
+      qh_setappend(qh, &vertices, vertex);
+    }
+  }else {
+    vertices= qh_settemp(qh, qh->TEMPsize);
+    FORALLfacet_(facetlist) {
+      if (!allfacets && qh_skipfacet(qh, facet))
+        continue;
+      FOREACHvertex_(facet->vertices) {
+        if (vertex->visitid != qh->vertex_visit) {
+          vertex->visitid= qh->vertex_visit;
+          qh_setappend(qh, &vertices, vertex);
+        }
+      }
+    }
+  }
+  FOREACHfacet_(facets) {
+    if (!allfacets && qh_skipfacet(qh, facet))
+      continue;
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->visitid != qh->vertex_visit) {
+        vertex->visitid= qh->vertex_visit;
+        qh_setappend(qh, &vertices, vertex);
+      }
+    }
+  }
+  return vertices;
+} /* facetvertices */
+
+/*---------------------------------
+
+  qh_geomplanes(qh, facet, outerplane, innerplane )
+    return outer and inner planes for Geomview
+    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
+
+  notes:
+    assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
+*/
+void qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
+  realT radius;
+
+  if (qh->MERGING || qh->JOGGLEmax < REALmax/2) {
+    qh_outerinner(qh, facet, outerplane, innerplane);
+    radius= qh->PRINTradius;
+    if (qh->JOGGLEmax < REALmax/2)
+      radius -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);  /* already accounted for in qh_outerinner() */
+    *outerplane += radius;
+    *innerplane -= radius;
+    if (qh->PRINTcoplanar || qh->PRINTspheres) {
+      *outerplane += qh->MAXabs_coord * qh_GEOMepsilon;
+      *innerplane -= qh->MAXabs_coord * qh_GEOMepsilon;
+    }
+  }else
+    *innerplane= *outerplane= 0;
+} /* geomplanes */
+
+
+/*---------------------------------
+
+  qh_markkeep(qh, facetlist )
+    restrict good facets for qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
+    ignores visible facets (!part of convex hull)
+
+  returns:
+    may clear facet->good
+    recomputes qh.num_good
+
+  notes:
+    only called by qh_prepare_output after qh_findgood_all
+    does not throw errors except memory/corruption of qset_r.c
+
+  design:
+    get set of good facets
+    if qh.KEEParea
+      sort facets by area
+      clear facet->good for all but n largest facets
+    if qh.KEEPmerge
+      sort facets by merge count
+      clear facet->good for all but n most merged facets
+    if qh.KEEPminarea
+      clear facet->good if area too small
+    update qh.num_good
+*/
+void qh_markkeep(qhT *qh, facetT *facetlist) {
+  facetT *facet, **facetp;
+  setT *facets= qh_settemp(qh, qh->num_facets);
+  int size, count;
+
+  trace2((qh, qh->ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
+          qh->KEEParea, qh->KEEPmerge, qh->KEEPminArea));
+  FORALLfacet_(facetlist) {
+    if (!facet->visible && facet->good)
+      qh_setappend(qh, &facets, facet);
+  }
+  size= qh_setsize(qh, facets);
+  if (qh->KEEParea) {
+    qsort(SETaddr_(facets, facetT), (size_t)size,
+             sizeof(facetT *), qh_compare_facetarea);
+    if ((count= size - qh->KEEParea) > 0) {
+      FOREACHfacet_(facets) {
+        facet->good= False;
+        if (--count == 0)
+          break;
+      }
+    }
+  }
+  if (qh->KEEPmerge) {
+    qsort(SETaddr_(facets, facetT), (size_t)size,
+             sizeof(facetT *), qh_compare_nummerge);
+    if ((count= size - qh->KEEPmerge) > 0) {
+      FOREACHfacet_(facets) {
+        facet->good= False;
+        if (--count == 0)
+          break;
+      }
+    }
+  }
+  if (qh->KEEPminArea < REALmax/2) {
+    FOREACHfacet_(facets) {
+      if (!facet->isarea || facet->f.area < qh->KEEPminArea)
+        facet->good= False;
+    }
+  }
+  qh_settempfree(qh, &facets);
+  count= 0;
+  FORALLfacet_(facetlist) {
+    if (facet->good)
+      count++;
+  }
+  qh->num_good= count;
+} /* markkeep */
+
+
+/*---------------------------------
+
+  qh_markvoronoi(qh, facetlist, facets, printall, isLower, numcenters )
+    mark voronoi vertices for printing by site pairs
+
+  returns:
+    temporary set of vertices indexed by pointid
+    isLower set if printing lower hull (i.e., at least one facet is lower hull)
+    numcenters= total number of Voronoi vertices
+    bumps qh.printoutnum for vertex-at-infinity
+    clears all facet->seen and sets facet->seen2
+
+    if selected
+      facet->visitid= Voronoi vertex id
+    else if upper hull (or 'Qu' and lower hull)
+      facet->visitid= 0
+    else
+      facet->visitid >= qh->num_facets
+
+  notes:
+    ignores qh.ATinfinity, if defined
+*/
+setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
+  int numcenters=0;
+  facetT *facet, **facetp;
+  setT *vertices;
+  boolT isLower= False;
+
+  qh->printoutnum++;
+  qh_clearcenters(qh, qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
+  qh_vertexneighbors(qh);
+  vertices= qh_pointvertex(qh);
+  if (qh->ATinfinity)
+    SETelem_(vertices, qh->num_points-1)= NULL;
+  qh->visit_id++;
+  maximize_(qh->visit_id, (unsigned int)qh->num_facets);
+  FORALLfacet_(facetlist) {
+    if (printall || !qh_skipfacet(qh, facet)) {
+      if (!facet->upperdelaunay) {
+        isLower= True;
+        break;
+      }
+    }
+  }
+  FOREACHfacet_(facets) {
+    if (printall || !qh_skipfacet(qh, facet)) {
+      if (!facet->upperdelaunay) {
+        isLower= True;
+        break;
+      }
+    }
+  }
+  FORALLfacets {
+    if (facet->normal && (facet->upperdelaunay == isLower))
+      facet->visitid= 0;  /* facetlist or facets may overwrite */
+    else
+      facet->visitid= qh->visit_id;
+    facet->seen= False;
+    facet->seen2= True;
+  }
+  numcenters++;  /* qh_INFINITE */
+  FORALLfacet_(facetlist) {
+    if (printall || !qh_skipfacet(qh, facet))
+      facet->visitid= (unsigned int)(numcenters++);
+  }
+  FOREACHfacet_(facets) {
+    if (printall || !qh_skipfacet(qh, facet))
+      facet->visitid= (unsigned int)(numcenters++);
+  }
+  *isLowerp= isLower;
+  *numcentersp= numcenters;
+  trace2((qh, qh->ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
+  return vertices;
+} /* markvoronoi */
+
+/*---------------------------------
+
+  qh_order_vertexneighbors(qh, vertex )
+    order facet neighbors of vertex by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id)
+
+  notes:
+    error if qh_vertexneighbors not called beforehand
+    only 2-d orients the neighbors
+    for 4-d and higher
+      set or clear f.visitid for qh_compare_facetvisit
+      for example, use qh_markvoronoi (e.g., qh_printvornoi) or qh_countfacets (e.g., qh_printvneighbors)
+
+  design (2-d):
+    see qh_printextremes_2d
+  design (3-d):
+    initialize a new neighbor set with the first facet in vertex->neighbors
+    while vertex->neighbors non-empty
+      select next neighbor in the previous facet's neighbor set
+    set vertex->neighbors to the new neighbor set
+  design (n-d):
+    qsort by f.visitid, or f.facetid (qh_compare_facetvisit)
+    facet_id is negated (sorted before visit_id facets)
+*/
+void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) {
+  setT *newset;
+  facetT *facet, *facetA, *facetB, *neighbor, **neighborp;
+  vertexT *vertexA;
+  int numneighbors;
+
+  trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order facet neighbors of v%d by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id)\n", vertex->id));
+  if (!qh->VERTEXneighbors) {
+    qh_fprintf(qh, qh->ferr, 6428, "qhull internal error (qh_order_vertexneighbors): call qh_vertexneighbors before calling qh_order_vertexneighbors\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (qh->hull_dim == 2) {
+    facetA= SETfirstt_(vertex->neighbors, facetT);
+    if (facetA->toporient ^ qh_ORIENTclock)
+      vertexA= SETfirstt_(facetA->vertices, vertexT);
+    else
+      vertexA= SETsecondt_(facetA->vertices, vertexT);
+    if (vertexA!=vertex) {
+      facetB= SETsecondt_(vertex->neighbors, facetT);
+      SETfirst_(vertex->neighbors)= facetB;
+      SETsecond_(vertex->neighbors)= facetA;
+    }
+  }else if (qh->hull_dim == 3) {
+    newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors));
+    facet= (facetT *)qh_setdellast(vertex->neighbors);
+    qh_setappend(qh, &newset, facet);
+    while (qh_setsize(qh, vertex->neighbors)) {
+      FOREACHneighbor_(vertex) {
+        if (qh_setin(facet->neighbors, neighbor)) {
+          qh_setdel(vertex->neighbors, neighbor);
+          qh_setappend(qh, &newset, neighbor);
+          facet= neighbor;
+          break;
+        }
+      }
+      if (!neighbor) {
+        qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
+          vertex->id, facet->id);
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+      }
+    }
+    qh_setfree(qh, &vertex->neighbors);
+    qh_settemppop(qh);
+    vertex->neighbors= newset;
+  }else { /* qh.hull_dim >= 4 */
+    numneighbors= qh_setsize(qh, vertex->neighbors);
+    qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
+        sizeof(facetT *), qh_compare_facetvisit);
+  }
+} /* order_vertexneighbors */
+
+/*---------------------------------
+
+  qh_prepare_output(qh )
+    prepare for qh_produce_output2(qh) according to
+      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
+    does not reset facet->good
+
+  notes
+    called by qh_produce_output, qh_new_qhull, Qhull.outputQhull
+    except for PRINTstatistics, no-op if previously called with same options
+*/
+void qh_prepare_output(qhT *qh) {
+  if (qh->VORONOI) {
+    qh_clearcenters(qh, qh_ASvoronoi);  /* must be before qh_triangulate */
+    qh_vertexneighbors(qh);
+  }
+  if (qh->TRIangulate && !qh->hasTriangulation) {
+    qh_triangulate(qh);
+    if (qh->VERIFYoutput && !qh->CHECKfrequently)
+      qh_checkpolygon(qh, qh->facet_list);
+  }
+  qh_findgood_all(qh, qh->facet_list);
+  if (qh->GETarea)
+    qh_getarea(qh, qh->facet_list);
+  if (qh->KEEParea || qh->KEEPmerge || qh->KEEPminArea < REALmax/2)
+    qh_markkeep(qh, qh->facet_list);
+  if (qh->PRINTstatistics)
+    qh_collectstatistics(qh);
+}
+
+/*---------------------------------
+
+  qh_printafacet(qh, fp, format, facet, printall )
+    print facet to fp in given output format (see qh.PRINTout)
+
+  returns:
+    nop if !printall and qh_skipfacet()
+    nop if visible facet and NEWfacets and format != PRINTfacets
+    must match qh_countfacets
+
+  notes
+    preserves qh.visit_id
+    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
+
+  see
+    qh_printbegin() and qh_printend()
+
+  design:
+    test for printing facet
+    call appropriate routine for format
+    or output results directly
+*/
+void qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
+  realT color[4], offset, dist, outerplane, innerplane;
+  boolT zerodiv;
+  coordT *point, *normp, *coordp, **pointp, *feasiblep;
+  int k;
+  vertexT *vertex, **vertexp;
+  facetT *neighbor, **neighborp;
+
+  if (!printall && qh_skipfacet(qh, facet))
+    return;
+  if (facet->visible && qh->NEWfacets && format != qh_PRINTfacets)
+    return;
+  qh->printoutnum++;
+  switch (format) {
+  case qh_PRINTarea:
+    if (facet->isarea) {
+      qh_fprintf(qh, fp, 9009, qh_REAL_1, facet->f.area);
+      qh_fprintf(qh, fp, 9010, "\n");
+    }else
+      qh_fprintf(qh, fp, 9011, "0\n");
+    break;
+  case qh_PRINTcoplanars:
+    qh_fprintf(qh, fp, 9012, "%d", qh_setsize(qh, facet->coplanarset));
+    FOREACHpoint_(facet->coplanarset)
+      qh_fprintf(qh, fp, 9013, " %d", qh_pointid(qh, point));
+    qh_fprintf(qh, fp, 9014, "\n");
+    break;
+  case qh_PRINTcentrums:
+    qh_printcenter(qh, fp, format, NULL, facet);
+    break;
+  case qh_PRINTfacets:
+    qh_printfacet(qh, fp, facet);
+    break;
+  case qh_PRINTfacets_xridge:
+    qh_printfacetheader(qh, fp, facet);
+    break;
+  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
+    if (!facet->normal)
+      break;
+    for (k=qh->hull_dim; k--; ) {
+      color[k]= (facet->normal[k]+1.0)/2.0;
+      maximize_(color[k], -1.0);
+      minimize_(color[k], +1.0);
+    }
+    qh_projectdim3(qh, color, color);
+    if (qh->PRINTdim != qh->hull_dim)
+      qh_normalize2(qh, color, 3, True, NULL, NULL);
+    if (qh->hull_dim <= 2)
+      qh_printfacet2geom(qh, fp, facet, color);
+    else if (qh->hull_dim == 3) {
+      if (facet->simplicial)
+        qh_printfacet3geom_simplicial(qh, fp, facet, color);
+      else
+        qh_printfacet3geom_nonsimplicial(qh, fp, facet, color);
+    }else {
+      if (facet->simplicial)
+        qh_printfacet4geom_simplicial(qh, fp, facet, color);
+      else
+        qh_printfacet4geom_nonsimplicial(qh, fp, facet, color);
+    }
+    break;
+  case qh_PRINTids:
+    qh_fprintf(qh, fp, 9015, "%d\n", facet->id);
+    break;
+  case qh_PRINTincidences:
+  case qh_PRINToff:
+  case qh_PRINTtriangles:
+    if (qh->hull_dim == 3 && format != qh_PRINTtriangles)
+      qh_printfacet3vertex(qh, fp, facet, format);
+    else if (facet->simplicial || qh->hull_dim == 2 || format == qh_PRINToff)
+      qh_printfacetNvertex_simplicial(qh, fp, facet, format);
+    else
+      qh_printfacetNvertex_nonsimplicial(qh, fp, facet, qh->printoutvar++, format);
+    break;
+  case qh_PRINTinner:
+    qh_outerinner(qh, facet, NULL, &innerplane);
+    offset= facet->offset - innerplane;
+    goto LABELprintnorm;
+    break; /* prevent warning */
+  case qh_PRINTmerges:
+    qh_fprintf(qh, fp, 9016, "%d\n", facet->nummerge);
+    break;
+  case qh_PRINTnormals:
+    offset= facet->offset;
+    goto LABELprintnorm;
+    break; /* prevent warning */
+  case qh_PRINTouter:
+    qh_outerinner(qh, facet, &outerplane, NULL);
+    offset= facet->offset - outerplane;
+  LABELprintnorm:
+    if (!facet->normal) {
+      qh_fprintf(qh, fp, 9017, "no normal for facet f%d\n", facet->id);
+      break;
+    }
+    if (qh->CDDoutput) {
+      qh_fprintf(qh, fp, 9018, qh_REAL_1, -offset);
+      for (k=0; k < qh->hull_dim; k++)
+        qh_fprintf(qh, fp, 9019, qh_REAL_1, -facet->normal[k]);
+    }else {
+      for (k=0; k < qh->hull_dim; k++)
+        qh_fprintf(qh, fp, 9020, qh_REAL_1, facet->normal[k]);
+      qh_fprintf(qh, fp, 9021, qh_REAL_1, offset);
+    }
+    qh_fprintf(qh, fp, 9022, "\n");
+    break;
+  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
+  case qh_PRINTmaple:
+    if (qh->hull_dim == 2)
+      qh_printfacet2math(qh, fp, facet, format, qh->printoutvar++);
+    else
+      qh_printfacet3math(qh, fp, facet, format, qh->printoutvar++);
+    break;
+  case qh_PRINTneighbors:
+    qh_fprintf(qh, fp, 9023, "%d", qh_setsize(qh, facet->neighbors));
+    FOREACHneighbor_(facet)
+      qh_fprintf(qh, fp, 9024, " %d",
+               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
+    qh_fprintf(qh, fp, 9025, "\n");
+    break;
+  case qh_PRINTpointintersect:
+    if (!qh->feasible_point) {
+      qh_fprintf(qh, qh->ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh->feasible_point\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (facet->offset > 0)
+      goto LABELprintinfinite;
+    point= coordp= (coordT *)qh_memalloc(qh, qh->normal_size);
+    normp= facet->normal;
+    feasiblep= qh->feasible_point;
+    if (facet->offset < -qh->MINdenom) {
+      for (k=qh->hull_dim; k--; )
+        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
+    }else {
+      for (k=qh->hull_dim; k--; ) {
+        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh->MINdenom_1,
+                                 &zerodiv) + *(feasiblep++);
+        if (zerodiv) {
+          qh_memfree(qh, point, qh->normal_size);
+          goto LABELprintinfinite;
+        }
+      }
+    }
+    qh_printpoint(qh, fp, NULL, point);
+    qh_memfree(qh, point, qh->normal_size);
+    break;
+  LABELprintinfinite:
+    for (k=qh->hull_dim; k--; )
+      qh_fprintf(qh, fp, 9026, qh_REAL_1, qh_INFINITE);
+    qh_fprintf(qh, fp, 9027, "\n");
+    break;
+  case qh_PRINTpointnearest:
+    FOREACHpoint_(facet->coplanarset) {
+      int id, id2;
+      vertex= qh_nearvertex(qh, facet, point, &dist);
+      id= qh_pointid(qh, vertex->point);
+      id2= qh_pointid(qh, point);
+      qh_fprintf(qh, fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
+    }
+    break;
+  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
+    if (qh->CDDoutput)
+      qh_fprintf(qh, fp, 9029, "1 ");
+    qh_printcenter(qh, fp, format, NULL, facet);
+    break;
+  case qh_PRINTvertices:
+    qh_fprintf(qh, fp, 9030, "%d", qh_setsize(qh, facet->vertices));
+    FOREACHvertex_(facet->vertices)
+      qh_fprintf(qh, fp, 9031, " %d", qh_pointid(qh, vertex->point));
+    qh_fprintf(qh, fp, 9032, "\n");
+    break;
+  default:
+    break;
+  }
+} /* printafacet */
+
+/*---------------------------------
+
+  qh_printbegin(qh )
+    prints header for all output formats
+
+  returns:
+    checks for valid format
+
+  notes:
+    uses qh.visit_id for 3/4off
+    changes qh.interior_point if printing centrums
+    qh_countfacets clears facet->visitid for non-good facets
+
+  see
+    qh_printend() and qh_printafacet()
+
+  design:
+    count facets and related statistics
+    print header for format
+*/
+void qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
+  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
+  int i, num;
+  facetT *facet, **facetp;
+  vertexT *vertex, **vertexp;
+  setT *vertices;
+  pointT *point, **pointp, *pointtemp;
+
+  qh->printoutnum= 0;
+  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
+      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
+  switch (format) {
+  case qh_PRINTnone:
+    break;
+  case qh_PRINTarea:
+    qh_fprintf(qh, fp, 9033, "%d\n", numfacets);
+    break;
+  case qh_PRINTcoplanars:
+    qh_fprintf(qh, fp, 9034, "%d\n", numfacets);
+    break;
+  case qh_PRINTcentrums:
+    if (qh->CENTERtype == qh_ASnone)
+      qh_clearcenters(qh, qh_AScentrum);
+    qh_fprintf(qh, fp, 9035, "%d\n%d\n", qh->hull_dim, numfacets);
+    break;
+  case qh_PRINTfacets:
+  case qh_PRINTfacets_xridge:
+    if (facetlist)
+      qh_printvertexlist(qh, fp, "Vertices and facets:\n", facetlist, facets, printall);
+    break;
+  case qh_PRINTgeom:
+    if (qh->hull_dim > 4)  /* qh_initqhull_globals also checks */
+      goto LABELnoformat;
+    if (qh->VORONOI && qh->hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
+      goto LABELnoformat;
+    if (qh->hull_dim == 2 && (qh->PRINTridges || qh->DOintersections))
+      qh_fprintf(qh, qh->ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
+    if (qh->hull_dim == 4 && (qh->PRINTinner || qh->PRINTouter ||
+                             (qh->PRINTdim == 4 && qh->PRINTcentrums)))
+      qh_fprintf(qh, qh->ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
+    if (qh->PRINTdim == 4 && (qh->PRINTspheres))
+      qh_fprintf(qh, qh->ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
+    if (qh->PRINTdim == 4 && qh->DOintersections && qh->PRINTnoplanes)
+      qh_fprintf(qh, qh->ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
+    if (qh->PRINTdim == 2) {
+      qh_fprintf(qh, fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
+              qh->rbox_command, qh->qhull_command);
+    }else if (qh->PRINTdim == 3) {
+      qh_fprintf(qh, fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
+              qh->rbox_command, qh->qhull_command);
+    }else if (qh->PRINTdim == 4) {
+      qh->visit_id++;
+      num= 0;
+      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
+        qh_printend4geom(qh, NULL, facet, &num, printall);
+      FOREACHfacet_(facets)
+        qh_printend4geom(qh, NULL, facet, &num, printall);
+      qh->ridgeoutnum= num;
+      qh->printoutvar= 0;  /* counts number of ridges in output */
+      qh_fprintf(qh, fp, 9038, "LIST # %s | %s\n", qh->rbox_command, qh->qhull_command);
+    }
+
+    if (qh->PRINTdots) {
+      qh->printoutnum++;
+      num= qh->num_points + qh_setsize(qh, qh->other_points);
+      if (qh->DELAUNAY && qh->ATinfinity)
+        num--;
+      if (qh->PRINTdim == 4)
+        qh_fprintf(qh, fp, 9039, "4VECT %d %d 1\n", num, num);
+      else
+        qh_fprintf(qh, fp, 9040, "VECT %d %d 1\n", num, num);
+
+      for (i=num; i--; ) {
+        if (i % 20 == 0)
+          qh_fprintf(qh, fp, 9041, "\n");
+        qh_fprintf(qh, fp, 9042, "1 ");
+      }
+      qh_fprintf(qh, fp, 9043, "# 1 point per line\n1 ");
+      for (i=num-1; i--; ) { /* num at least 3 for D2 */
+        if (i % 20 == 0)
+          qh_fprintf(qh, fp, 9044, "\n");
+        qh_fprintf(qh, fp, 9045, "0 ");
+      }
+      qh_fprintf(qh, fp, 9046, "# 1 color for all\n");
+      FORALLpoints {
+        if (!qh->DELAUNAY || !qh->ATinfinity || qh_pointid(qh, point) != qh->num_points-1) {
+          if (qh->PRINTdim == 4)
+            qh_printpoint(qh, fp, NULL, point);
+            else
+              qh_printpoint3(qh, fp, point);
+        }
+      }
+      FOREACHpoint_(qh->other_points) {
+        if (qh->PRINTdim == 4)
+          qh_printpoint(qh, fp, NULL, point);
+        else
+          qh_printpoint3(qh, fp, point);
+      }
+      qh_fprintf(qh, fp, 9047, "0 1 1 1  # color of points\n");
+    }
+
+    if (qh->PRINTdim == 4  && !qh->PRINTnoplanes)
+      /* 4dview loads up multiple 4OFF objects slowly */
+      qh_fprintf(qh, fp, 9048, "4OFF %d %d 1\n", 3*qh->ridgeoutnum, qh->ridgeoutnum);
+    qh->PRINTcradius= 2 * qh->DISTround;  /* include test DISTround */
+    if (qh->PREmerge) {
+      maximize_(qh->PRINTcradius, qh->premerge_centrum + qh->DISTround);
+    }else if (qh->POSTmerge)
+      maximize_(qh->PRINTcradius, qh->postmerge_centrum + qh->DISTround);
+    qh->PRINTradius= qh->PRINTcradius;
+    if (qh->PRINTspheres + qh->PRINTcoplanar)
+      maximize_(qh->PRINTradius, qh->MAXabs_coord * qh_MINradius);
+    if (qh->premerge_cos < REALmax/2) {
+      maximize_(qh->PRINTradius, (1- qh->premerge_cos) * qh->MAXabs_coord);
+    }else if (!qh->PREmerge && qh->POSTmerge && qh->postmerge_cos < REALmax/2) {
+      maximize_(qh->PRINTradius, (1- qh->postmerge_cos) * qh->MAXabs_coord);
+    }
+    maximize_(qh->PRINTradius, qh->MINvisible);
+    if (qh->JOGGLEmax < REALmax/2)
+      qh->PRINTradius += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
+    if (qh->PRINTdim != 4 &&
+        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
+      vertices= qh_facetvertices(qh, facetlist, facets, printall);
+      if (qh->PRINTspheres && qh->PRINTdim <= 3)
+        qh_printspheres(qh, fp, vertices, qh->PRINTradius);
+      if (qh->PRINTcoplanar || qh->PRINTcentrums) {
+        qh->firstcentrum= True;
+        if (qh->PRINTcoplanar&& !qh->PRINTspheres) {
+          FOREACHvertex_(vertices)
+            qh_printpointvect2(qh, fp, vertex->point, NULL, qh->interior_point, qh->PRINTradius);
+        }
+        FORALLfacet_(facetlist) {
+          if (!printall && qh_skipfacet(qh, facet))
+            continue;
+          if (!facet->normal)
+            continue;
+          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
+            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
+          if (!qh->PRINTcoplanar)
+            continue;
+          FOREACHpoint_(facet->coplanarset)
+            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
+          FOREACHpoint_(facet->outsideset)
+            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
+        }
+        FOREACHfacet_(facets) {
+          if (!printall && qh_skipfacet(qh, facet))
+            continue;
+          if (!facet->normal)
+            continue;
+          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
+            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
+          if (!qh->PRINTcoplanar)
+            continue;
+          FOREACHpoint_(facet->coplanarset)
+            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
+          FOREACHpoint_(facet->outsideset)
+            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
+        }
+      }
+      qh_settempfree(qh, &vertices);
+    }
+    qh->visit_id++; /* for printing hyperplane intersections */
+    break;
+  case qh_PRINTids:
+    qh_fprintf(qh, fp, 9049, "%d\n", numfacets);
+    break;
+  case qh_PRINTincidences:
+    if (qh->VORONOI && qh->PRINTprecision)
+      qh_fprintf(qh, qh->ferr, 7053, "qhull warning: input sites of Delaunay regions (option 'i').  Use option 'p' or 'o' for Voronoi centers.  Disable warning with option 'Pp'\n");
+    qh->printoutvar= (int)qh->vertex_id;  /* centrum id for 4-d+, non-simplicial facets */
+    if (qh->hull_dim <= 3)
+      qh_fprintf(qh, fp, 9050, "%d\n", numfacets);
+    else
+      qh_fprintf(qh, fp, 9051, "%d\n", numsimplicial+numridges);
+    break;
+  case qh_PRINTinner:
+  case qh_PRINTnormals:
+  case qh_PRINTouter:
+    if (qh->CDDoutput)
+      qh_fprintf(qh, fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh->rbox_command,
+            qh->qhull_command, numfacets, qh->hull_dim+1);
+    else
+      qh_fprintf(qh, fp, 9053, "%d\n%d\n", qh->hull_dim+1, numfacets);
+    break;
+  case qh_PRINTmathematica:
+  case qh_PRINTmaple:
+    if (qh->hull_dim > 3)  /* qh_initbuffers also checks */
+      goto LABELnoformat;
+    if (qh->VORONOI)
+      qh_fprintf(qh, qh->ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
+    if (format == qh_PRINTmaple) {
+      if (qh->hull_dim == 2)
+        qh_fprintf(qh, fp, 9054, "PLOT(CURVES(\n");
+      else
+        qh_fprintf(qh, fp, 9055, "PLOT3D(POLYGONS(\n");
+    }else
+      qh_fprintf(qh, fp, 9056, "{\n");
+    qh->printoutvar= 0;   /* counts number of facets for notfirst */
+    break;
+  case qh_PRINTmerges:
+    qh_fprintf(qh, fp, 9057, "%d\n", numfacets);
+    break;
+  case qh_PRINTpointintersect:
+    qh_fprintf(qh, fp, 9058, "%d\n%d\n", qh->hull_dim, numfacets);
+    break;
+  case qh_PRINTneighbors:
+    qh_fprintf(qh, fp, 9059, "%d\n", numfacets);
+    break;
+  case qh_PRINToff:
+  case qh_PRINTtriangles:
+    if (qh->VORONOI)
+      goto LABELnoformat;
+    num= qh->hull_dim;
+    if (format == qh_PRINToff || qh->hull_dim == 2)
+      qh_fprintf(qh, fp, 9060, "%d\n%d %d %d\n", num,
+        qh->num_points+qh_setsize(qh, qh->other_points), numfacets, totneighbors/2);
+    else { /* qh_PRINTtriangles */
+      qh->printoutvar= qh->num_points+qh_setsize(qh, qh->other_points); /* first centrum */
+      if (qh->DELAUNAY)
+        num--;  /* drop last dimension */
+      qh_fprintf(qh, fp, 9061, "%d\n%d %d %d\n", num, qh->printoutvar
+        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
+    }
+    FORALLpoints
+      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
+    FOREACHpoint_(qh->other_points)
+      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
+    if (format == qh_PRINTtriangles && qh->hull_dim > 2) {
+      FORALLfacets {
+        if (!facet->simplicial && facet->visitid)
+          qh_printcenter(qh, qh->fout, format, NULL, facet);
+      }
+    }
+    break;
+  case qh_PRINTpointnearest:
+    qh_fprintf(qh, fp, 9062, "%d\n", numcoplanars);
+    break;
+  case qh_PRINTpoints:
+    if (!qh->VORONOI)
+      goto LABELnoformat;
+    if (qh->CDDoutput)
+      qh_fprintf(qh, fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
+           qh->qhull_command, numfacets, qh->hull_dim);
+    else
+      qh_fprintf(qh, fp, 9064, "%d\n%d\n", qh->hull_dim-1, numfacets);
+    break;
+  case qh_PRINTvertices:
+    qh_fprintf(qh, fp, 9065, "%d\n", numfacets);
+    break;
+  case qh_PRINTsummary:
+  default:
+  LABELnoformat:
+    qh_fprintf(qh, qh->ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
+         qh->hull_dim);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+} /* printbegin */
+
+/*---------------------------------
+
+  qh_printcenter(qh, fp, string, facet )
+    print facet->center as centrum or Voronoi center
+    string may be NULL.  Don't include '%' codes.
+    nop if qh->CENTERtype neither CENTERvoronoi nor CENTERcentrum
+    if upper envelope of Delaunay triangulation and point at-infinity
+      prints qh_INFINITE instead;
+
+  notes:
+    defines facet->center if needed
+    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
+    Same as QhullFacet::printCenter
+*/
+void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
+  int k, num;
+
+  if (qh->CENTERtype != qh_ASvoronoi && qh->CENTERtype != qh_AScentrum)
+    return;
+  if (string)
+    qh_fprintf(qh, fp, 9066, "%s", string);
+  if (qh->CENTERtype == qh_ASvoronoi) {
+    num= qh->hull_dim-1;
+    if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) {
+      if (!facet->center)
+        facet->center= qh_facetcenter(qh, facet->vertices);
+      for (k=0; k < num; k++)
+        qh_fprintf(qh, fp, 9067, qh_REAL_1, facet->center[k]);
+    }else {
+      for (k=0; k < num; k++)
+        qh_fprintf(qh, fp, 9068, qh_REAL_1, qh_INFINITE);
+    }
+  }else /* qh.CENTERtype == qh_AScentrum */ {
+    num= qh->hull_dim;
+    if (format == qh_PRINTtriangles && qh->DELAUNAY)
+      num--;
+    if (!facet->center)
+      facet->center= qh_getcentrum(qh, facet);
+    for (k=0; k < num; k++)
+      qh_fprintf(qh, fp, 9069, qh_REAL_1, facet->center[k]);
+  }
+  if (format == qh_PRINTgeom && num == 2)
+    qh_fprintf(qh, fp, 9070, " 0\n");
+  else
+    qh_fprintf(qh, fp, 9071, "\n");
+} /* printcenter */
+
+/*---------------------------------
+
+  qh_printcentrum(qh, fp, facet, radius )
+    print centrum for a facet in OOGL format
+    radius defines size of centrum
+    2-d or 3-d only
+
+  returns:
+    defines facet->center if needed
+*/
+void qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius) {
+  pointT *centrum, *projpt;
+  boolT tempcentrum= False;
+  realT xaxis[4], yaxis[4], normal[4], dist;
+  realT green[3]={0, 1, 0};
+  vertexT *apex;
+  int k;
+
+  if (qh->CENTERtype == qh_AScentrum) {
+    if (!facet->center)
+      facet->center= qh_getcentrum(qh, facet);
+    centrum= facet->center;
+  }else {
+    centrum= qh_getcentrum(qh, facet);
+    tempcentrum= True;
+  }
+  qh_fprintf(qh, fp, 9072, "{appearance {-normal -edge normscale 0} ");
+  if (qh->firstcentrum) {
+    qh->firstcentrum= False;
+    qh_fprintf(qh, fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
+-0.3 -0.3 0.0001     0 0 1 1\n\
+ 0.3 -0.3 0.0001     0 0 1 1\n\
+ 0.3  0.3 0.0001     0 0 1 1\n\
+-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
+  }else
+    qh_fprintf(qh, fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
+  apex= SETfirstt_(facet->vertices, vertexT);
+  qh_distplane(qh, apex->point, facet, &dist);
+  projpt= qh_projectpoint(qh, apex->point, facet, dist);
+  for (k=qh->hull_dim; k--; ) {
+    xaxis[k]= projpt[k] - centrum[k];
+    normal[k]= facet->normal[k];
+  }
+  if (qh->hull_dim == 2) {
+    xaxis[2]= 0;
+    normal[2]= 0;
+  }else if (qh->hull_dim == 4) {
+    qh_projectdim3(qh, xaxis, xaxis);
+    qh_projectdim3(qh, normal, normal);
+    qh_normalize2(qh, normal, qh->PRINTdim, True, NULL, NULL);
+  }
+  qh_crossproduct(3, xaxis, normal, yaxis);
+  qh_fprintf(qh, fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
+  qh_fprintf(qh, fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
+  qh_fprintf(qh, fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
+  qh_printpoint3(qh, fp, centrum);
+  qh_fprintf(qh, fp, 9078, "1 }}}\n");
+  qh_memfree(qh, projpt, qh->normal_size);
+  qh_printpointvect(qh, fp, centrum, facet->normal, NULL, radius, green);
+  if (tempcentrum)
+    qh_memfree(qh, centrum, qh->normal_size);
+} /* printcentrum */
+
+/*---------------------------------
+
+  qh_printend(qh, fp, format )
+    prints trailer for all output formats
+
+  see:
+    qh_printbegin() and qh_printafacet()
+
+*/
+void qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
+  int num;
+  facetT *facet, **facetp;
+
+  if (!qh->printoutnum)
+    qh_fprintf(qh, qh->ferr, 7055, "qhull warning: no facets printed\n");
+  switch (format) {
+  case qh_PRINTgeom:
+    if (qh->hull_dim == 4 && qh->DROPdim < 0  && !qh->PRINTnoplanes) {
+      qh->visit_id++;
+      num= 0;
+      FORALLfacet_(facetlist)
+        qh_printend4geom(qh, fp, facet,&num, printall);
+      FOREACHfacet_(facets)
+        qh_printend4geom(qh, fp, facet, &num, printall);
+      if (num != qh->ridgeoutnum || qh->printoutvar != qh->ridgeoutnum) {
+        qh_fprintf(qh, qh->ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh->ridgeoutnum, qh->printoutvar, num);
+        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+      }
+    }else
+      qh_fprintf(qh, fp, 9079, "}\n");
+    break;
+  case qh_PRINTinner:
+  case qh_PRINTnormals:
+  case qh_PRINTouter:
+    if (qh->CDDoutput)
+      qh_fprintf(qh, fp, 9080, "end\n");
+    break;
+  case qh_PRINTmaple:
+    qh_fprintf(qh, fp, 9081, "));\n");
+    break;
+  case qh_PRINTmathematica:
+    qh_fprintf(qh, fp, 9082, "}\n");
+    break;
+  case qh_PRINTpoints:
+    if (qh->CDDoutput)
+      qh_fprintf(qh, fp, 9083, "end\n");
+    break;
+  default:
+    break;
+  }
+} /* printend */
+
+/*---------------------------------
+
+  qh_printend4geom(qh, fp, facet, numridges, printall )
+    helper function for qh_printbegin/printend
+
+  returns:
+    number of printed ridges
+
+  notes:
+    just counts printed ridges if fp=NULL
+    uses facet->visitid
+    must agree with qh_printfacet4geom...
+
+  design:
+    computes color for facet from its normal
+    prints each ridge of facet
+*/
+void qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *nump, boolT printall) {
+  realT color[3];
+  int i, num= *nump;
+  facetT *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+
+  if (!printall && qh_skipfacet(qh, facet))
+    return;
+  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
+    return;
+  if (!facet->normal)
+    return;
+  if (fp) {
+    for (i=0; i < 3; i++) {
+      color[i]= (facet->normal[i]+1.0)/2.0;
+      maximize_(color[i], -1.0);
+      minimize_(color[i], +1.0);
+    }
+  }
+  facet->visitid= qh->visit_id;
+  if (facet->simplicial) {
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid != qh->visit_id) {
+        if (fp)
+          qh_fprintf(qh, fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
+                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
+                 facet->id, neighbor->id);
+        num++;
+      }
+    }
+  }else {
+    FOREACHridge_(facet->ridges) {
+      neighbor= otherfacet_(ridge, facet);
+      if (neighbor->visitid != qh->visit_id) {
+        if (fp)
+          qh_fprintf(qh, fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
+                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
+                 ridge->id, facet->id, neighbor->id);
+        num++;
+      }
+    }
+  }
+  *nump= num;
+} /* printend4geom */
+
+/*---------------------------------
+
+  qh_printextremes(qh, fp, facetlist, facets, printall )
+    print extreme points for convex hulls or halfspace intersections
+
+  notes:
+    #points, followed by ids, one per line
+
+    sorted by id
+    same order as qh_printpoints_out if no coplanar/interior points
+*/
+void qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
+  setT *vertices, *points;
+  pointT *point;
+  vertexT *vertex, **vertexp;
+  int id;
+  int numpoints=0, point_i, point_n;
+  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
+
+  points= qh_settemp(qh, allpoints);
+  qh_setzero(qh, points, 0, allpoints);
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  FOREACHvertex_(vertices) {
+    id= qh_pointid(qh, vertex->point);
+    if (id >= 0) {
+      SETelem_(points, id)= vertex->point;
+      numpoints++;
+    }
+  }
+  qh_settempfree(qh, &vertices);
+  qh_fprintf(qh, fp, 9086, "%d\n", numpoints);
+  FOREACHpoint_i_(qh, points) {
+    if (point)
+      qh_fprintf(qh, fp, 9087, "%d\n", point_i);
+  }
+  qh_settempfree(qh, &points);
+} /* printextremes */
+
+/*---------------------------------
+
+  qh_printextremes_2d(qh, fp, facetlist, facets, printall )
+    prints point ids for facets in qh_ORIENTclock order
+
+  notes:
+    #points, followed by ids, one per line
+    if facetlist/facets are disjoint than the output includes skips
+    errors if facets form a loop
+    does not print coplanar points
+*/
+void qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
+  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
+  setT *vertices;
+  facetT *facet, *startfacet, *nextfacet;
+  vertexT *vertexA, *vertexB;
+
+  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
+      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh->visit_id */
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  qh_fprintf(qh, fp, 9088, "%d\n", qh_setsize(qh, vertices));
+  qh_settempfree(qh, &vertices);
+  if (!numfacets)
+    return;
+  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
+  qh->vertex_visit++;
+  qh->visit_id++;
+  do {
+    if (facet->toporient ^ qh_ORIENTclock) {
+      vertexA= SETfirstt_(facet->vertices, vertexT);
+      vertexB= SETsecondt_(facet->vertices, vertexT);
+      nextfacet= SETfirstt_(facet->neighbors, facetT);
+    }else {
+      vertexA= SETsecondt_(facet->vertices, vertexT);
+      vertexB= SETfirstt_(facet->vertices, vertexT);
+      nextfacet= SETsecondt_(facet->neighbors, facetT);
+    }
+    if (facet->visitid == qh->visit_id) {
+      qh_fprintf(qh, qh->ferr, 6218, "qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
+                 facet->id, nextfacet->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, nextfacet);
+    }
+    if (facet->visitid) {
+      if (vertexA->visitid != qh->vertex_visit) {
+        vertexA->visitid= qh->vertex_visit;
+        qh_fprintf(qh, fp, 9089, "%d\n", qh_pointid(qh, vertexA->point));
+      }
+      if (vertexB->visitid != qh->vertex_visit) {
+        vertexB->visitid= qh->vertex_visit;
+        qh_fprintf(qh, fp, 9090, "%d\n", qh_pointid(qh, vertexB->point));
+      }
+    }
+    facet->visitid= qh->visit_id;
+    facet= nextfacet;
+  }while (facet && facet != startfacet);
+} /* printextremes_2d */
+
+/*---------------------------------
+
+  qh_printextremes_d(qh, fp, facetlist, facets, printall )
+    print extreme points of input sites for Delaunay triangulations
+
+  notes:
+    #points, followed by ids, one per line
+
+    unordered
+*/
+void qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
+  setT *vertices;
+  vertexT *vertex, **vertexp;
+  boolT upperseen, lowerseen;
+  facetT *neighbor, **neighborp;
+  int numpoints=0;
+
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  qh_vertexneighbors(qh);
+  FOREACHvertex_(vertices) {
+    upperseen= lowerseen= False;
+    FOREACHneighbor_(vertex) {
+      if (neighbor->upperdelaunay)
+        upperseen= True;
+      else
+        lowerseen= True;
+    }
+    if (upperseen && lowerseen) {
+      vertex->seen= True;
+      numpoints++;
+    }else
+      vertex->seen= False;
+  }
+  qh_fprintf(qh, fp, 9091, "%d\n", numpoints);
+  FOREACHvertex_(vertices) {
+    if (vertex->seen)
+      qh_fprintf(qh, fp, 9092, "%d\n", qh_pointid(qh, vertex->point));
+  }
+  qh_settempfree(qh, &vertices);
+} /* printextremes_d */
+
+/*---------------------------------
+
+  qh_printfacet(qh, fp, facet )
+    prints all fields of a facet to fp
+
+  notes:
+    ridges printed in neighbor order
+*/
+void qh_printfacet(qhT *qh, FILE *fp, facetT *facet) {
+
+  qh_printfacetheader(qh, fp, facet);
+  if (facet->ridges)
+    qh_printfacetridges(qh, fp, facet);
+} /* printfacet */
+
+
+/*---------------------------------
+
+  qh_printfacet2geom(qh, fp, facet, color )
+    print facet as part of a 2-d VECT for Geomview
+
+    notes:
+      assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
+      mindist is calculated within io_r.c.  maxoutside is calculated elsewhere
+      so a DISTround error may have occurred.
+*/
+void qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
+  pointT *point0, *point1;
+  realT mindist, innerplane, outerplane;
+  int k;
+
+  qh_facet2point(qh, facet, &point0, &point1, &mindist);
+  qh_geomplanes(qh, facet, &outerplane, &innerplane);
+  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
+    qh_printfacet2geom_points(qh, fp, point0, point1, facet, outerplane, color);
+  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
+                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
+    for (k=3; k--; )
+      color[k]= 1.0 - color[k];
+    qh_printfacet2geom_points(qh, fp, point0, point1, facet, innerplane, color);
+  }
+  qh_memfree(qh, point1, qh->normal_size);
+  qh_memfree(qh, point0, qh->normal_size);
+} /* printfacet2geom */
+
+/*---------------------------------
+
+  qh_printfacet2geom_points(qh, fp, point1, point2, facet, offset, color )
+    prints a 2-d facet as a VECT with 2 points at some offset.
+    The points are on the facet's plane.
+*/
+void qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
+                               facetT *facet, realT offset, realT color[3]) {
+  pointT *p1= point1, *p2= point2;
+
+  qh_fprintf(qh, fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
+  if (offset != 0.0) {
+    p1= qh_projectpoint(qh, p1, facet, -offset);
+    p2= qh_projectpoint(qh, p2, facet, -offset);
+  }
+  qh_fprintf(qh, fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
+           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
+  if (offset != 0.0) {
+    qh_memfree(qh, p1, qh->normal_size);
+    qh_memfree(qh, p2, qh->normal_size);
+  }
+  qh_fprintf(qh, fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
+} /* printfacet2geom_points */
+
+
+/*---------------------------------
+
+  qh_printfacet2math(qh, fp, facet, format, notfirst )
+    print 2-d Maple or Mathematica output for a facet
+    may be non-simplicial
+
+  notes:
+    use %16.8f since Mathematica 2.2 does not handle exponential format
+    see qh_printfacet3math
+*/
+void qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
+  pointT *point0, *point1;
+  realT mindist;
+  const char *pointfmt;
+
+  qh_facet2point(qh, facet, &point0, &point1, &mindist);
+  if (notfirst)
+    qh_fprintf(qh, fp, 9096, ",");
+  if (format == qh_PRINTmaple)
+    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
+  else
+    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
+  qh_fprintf(qh, fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
+  qh_memfree(qh, point1, qh->normal_size);
+  qh_memfree(qh, point0, qh->normal_size);
+} /* printfacet2math */
+
+
+/*---------------------------------
+
+  qh_printfacet3geom_nonsimplicial(qh, fp, facet, color )
+    print Geomview OFF for a 3-d nonsimplicial facet.
+    if DOintersections, prints ridges to unvisited neighbors(qh->visit_id)
+
+  notes
+    uses facet->visitid for intersections and ridges
+*/
+void qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
+  ridgeT *ridge, **ridgep;
+  setT *projectedpoints, *vertices;
+  vertexT *vertex, **vertexp, *vertexA, *vertexB;
+  pointT *projpt, *point, **pointp;
+  facetT *neighbor;
+  realT dist, outerplane, innerplane;
+  int cntvertices, k;
+  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
+
+  qh_geomplanes(qh, facet, &outerplane, &innerplane);
+  vertices= qh_facet3vertex(qh, facet); /* oriented */
+  cntvertices= qh_setsize(qh, vertices);
+  projectedpoints= qh_settemp(qh, cntvertices);
+  FOREACHvertex_(vertices) {
+    zinc_(Zdistio);
+    qh_distplane(qh, vertex->point, facet, &dist);
+    projpt= qh_projectpoint(qh, vertex->point, facet, dist);
+    qh_setappend(qh, &projectedpoints, projpt);
+  }
+  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
+    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, outerplane, color);
+  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
+                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
+    for (k=3; k--; )
+      color[k]= 1.0 - color[k];
+    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, innerplane, color);
+  }
+  FOREACHpoint_(projectedpoints)
+    qh_memfree(qh, point, qh->normal_size);
+  qh_settempfree(qh, &projectedpoints);
+  qh_settempfree(qh, &vertices);
+  if ((qh->DOintersections || qh->PRINTridges)
+  && (!facet->visible || !qh->NEWfacets)) {
+    facet->visitid= qh->visit_id;
+    FOREACHridge_(facet->ridges) {
+      neighbor= otherfacet_(ridge, facet);
+      if (neighbor->visitid != qh->visit_id) {
+        if (qh->DOintersections)
+          qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, black);
+        if (qh->PRINTridges) {
+          vertexA= SETfirstt_(ridge->vertices, vertexT);
+          vertexB= SETsecondt_(ridge->vertices, vertexT);
+          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
+        }
+      }
+    }
+  }
+} /* printfacet3geom_nonsimplicial */
+
+/*---------------------------------
+
+  qh_printfacet3geom_points(qh, fp, points, facet, offset )
+    prints a 3-d facet as OFF Geomview object.
+    offset is relative to the facet's hyperplane
+    Facet is determined as a list of points
+*/
+void qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
+  int k, n= qh_setsize(qh, points), i;
+  pointT *point, **pointp;
+  setT *printpoints;
+
+  qh_fprintf(qh, fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
+  if (offset != 0.0) {
+    printpoints= qh_settemp(qh, n);
+    FOREACHpoint_(points)
+      qh_setappend(qh, &printpoints, qh_projectpoint(qh, point, facet, -offset));
+  }else
+    printpoints= points;
+  FOREACHpoint_(printpoints) {
+    for (k=0; k < qh->hull_dim; k++) {
+      if (k == qh->DROPdim)
+        qh_fprintf(qh, fp, 9099, "0 ");
+      else
+        qh_fprintf(qh, fp, 9100, "%8.4g ", point[k]);
+    }
+    if (printpoints != points)
+      qh_memfree(qh, point, qh->normal_size);
+    qh_fprintf(qh, fp, 9101, "\n");
+  }
+  if (printpoints != points)
+    qh_settempfree(qh, &printpoints);
+  qh_fprintf(qh, fp, 9102, "%d ", n);
+  for (i=0; i < n; i++)
+    qh_fprintf(qh, fp, 9103, "%d ", i);
+  qh_fprintf(qh, fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
+} /* printfacet3geom_points */
+
+
+/*---------------------------------
+
+  qh_printfacet3geom_simplicial(qh )
+    print Geomview OFF for a 3-d simplicial facet.
+
+  notes:
+    may flip color
+    uses facet->visitid for intersections and ridges
+
+    assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
+    innerplane may be off by qh->DISTround.  Maxoutside is calculated elsewhere
+    so a DISTround error may have occurred.
+*/
+void qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
+  setT *points, *vertices;
+  vertexT *vertex, **vertexp, *vertexA, *vertexB;
+  facetT *neighbor, **neighborp;
+  realT outerplane, innerplane;
+  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
+  int k;
+
+  qh_geomplanes(qh, facet, &outerplane, &innerplane);
+  vertices= qh_facet3vertex(qh, facet);
+  points= qh_settemp(qh, qh->TEMPsize);
+  FOREACHvertex_(vertices)
+    qh_setappend(qh, &points, vertex->point);
+  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
+    qh_printfacet3geom_points(qh, fp, points, facet, outerplane, color);
+  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
+              outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
+    for (k=3; k--; )
+      color[k]= 1.0 - color[k];
+    qh_printfacet3geom_points(qh, fp, points, facet, innerplane, color);
+  }
+  qh_settempfree(qh, &points);
+  qh_settempfree(qh, &vertices);
+  if ((qh->DOintersections || qh->PRINTridges)
+  && (!facet->visible || !qh->NEWfacets)) {
+    facet->visitid= qh->visit_id;
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid != qh->visit_id) {
+        vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
+                          SETindex_(facet->neighbors, neighbor), 0);
+        if (qh->DOintersections)
+           qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, black);
+        if (qh->PRINTridges) {
+          vertexA= SETfirstt_(vertices, vertexT);
+          vertexB= SETsecondt_(vertices, vertexT);
+          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
+        }
+        qh_setfree(qh, &vertices);
+      }
+    }
+  }
+} /* printfacet3geom_simplicial */
+
+/*---------------------------------
+
+  qh_printfacet3math(qh, fp, facet, notfirst )
+    print 3-d Maple or Mathematica output for a facet
+
+  notes:
+    may be non-simplicial
+    use %16.8f since Mathematica 2.2 does not handle exponential format
+    see qh_printfacet2math
+*/
+void qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
+  vertexT *vertex, **vertexp;
+  setT *points, *vertices;
+  pointT *point, **pointp;
+  boolT firstpoint= True;
+  realT dist;
+  const char *pointfmt, *endfmt;
+
+  if (notfirst)
+    qh_fprintf(qh, fp, 9105, ",\n");
+  vertices= qh_facet3vertex(qh, facet);
+  points= qh_settemp(qh, qh_setsize(qh, vertices));
+  FOREACHvertex_(vertices) {
+    zinc_(Zdistio);
+    qh_distplane(qh, vertex->point, facet, &dist);
+    point= qh_projectpoint(qh, vertex->point, facet, dist);
+    qh_setappend(qh, &points, point);
+  }
+  if (format == qh_PRINTmaple) {
+    qh_fprintf(qh, fp, 9106, "[");
+    pointfmt= "[%16.8f, %16.8f, %16.8f]";
+    endfmt= "]";
+  }else {
+    qh_fprintf(qh, fp, 9107, "Polygon[{");
+    pointfmt= "{%16.8f, %16.8f, %16.8f}";
+    endfmt= "}]";
+  }
+  FOREACHpoint_(points) {
+    if (firstpoint)
+      firstpoint= False;
+    else
+      qh_fprintf(qh, fp, 9108, ",\n");
+    qh_fprintf(qh, fp, 9109, pointfmt, point[0], point[1], point[2]);
+  }
+  FOREACHpoint_(points)
+    qh_memfree(qh, point, qh->normal_size);
+  qh_settempfree(qh, &points);
+  qh_settempfree(qh, &vertices);
+  qh_fprintf(qh, fp, 9110, "%s", endfmt);
+} /* printfacet3math */
+
+
+/*---------------------------------
+
+  qh_printfacet3vertex(qh, fp, facet, format )
+    print vertices in a 3-d facet as point ids
+
+  notes:
+    prints number of vertices first if format == qh_PRINToff
+    the facet may be non-simplicial
+*/
+void qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
+  vertexT *vertex, **vertexp;
+  setT *vertices;
+
+  vertices= qh_facet3vertex(qh, facet);
+  if (format == qh_PRINToff)
+    qh_fprintf(qh, fp, 9111, "%d ", qh_setsize(qh, vertices));
+  FOREACHvertex_(vertices)
+    qh_fprintf(qh, fp, 9112, "%d ", qh_pointid(qh, vertex->point));
+  qh_fprintf(qh, fp, 9113, "\n");
+  qh_settempfree(qh, &vertices);
+} /* printfacet3vertex */
+
+
+/*---------------------------------
+
+  qh_printfacet4geom_nonsimplicial(qh )
+    print Geomview 4OFF file for a 4d nonsimplicial facet
+    prints all ridges to unvisited neighbors (qh.visit_id)
+    if qh.DROPdim
+      prints in OFF format
+
+  notes:
+    must agree with printend4geom()
+*/
+void qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
+  facetT *neighbor;
+  ridgeT *ridge, **ridgep;
+  vertexT *vertex, **vertexp;
+  pointT *point;
+  int k;
+  realT dist;
+
+  facet->visitid= qh->visit_id;
+  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
+    return;
+  FOREACHridge_(facet->ridges) {
+    neighbor= otherfacet_(ridge, facet);
+    if (neighbor->visitid == qh->visit_id)
+      continue;
+    if (qh->PRINTtransparent && !neighbor->good)
+      continue;
+    if (qh->DOintersections)
+      qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, color);
+    else {
+      if (qh->DROPdim >= 0)
+        qh_fprintf(qh, fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
+      else {
+        qh->printoutvar++;
+        qh_fprintf(qh, fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
+      }
+      FOREACHvertex_(ridge->vertices) {
+        zinc_(Zdistio);
+        qh_distplane(qh, vertex->point,facet, &dist);
+        point=qh_projectpoint(qh, vertex->point,facet, dist);
+        for (k=0; k < qh->hull_dim; k++) {
+          if (k != qh->DROPdim)
+            qh_fprintf(qh, fp, 9116, "%8.4g ", point[k]);
+        }
+        qh_fprintf(qh, fp, 9117, "\n");
+        qh_memfree(qh, point, qh->normal_size);
+      }
+      if (qh->DROPdim >= 0)
+        qh_fprintf(qh, fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
+    }
+  }
+} /* printfacet4geom_nonsimplicial */
+
+
+/*---------------------------------
+
+  qh_printfacet4geom_simplicial(qh, fp, facet, color )
+    print Geomview 4OFF file for a 4d simplicial facet
+    prints triangles for unvisited neighbors (qh.visit_id)
+
+  notes:
+    must agree with printend4geom()
+*/
+void qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
+  setT *vertices;
+  facetT *neighbor, **neighborp;
+  vertexT *vertex, **vertexp;
+  int k;
+
+  facet->visitid= qh->visit_id;
+  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
+    return;
+  FOREACHneighbor_(facet) {
+    if (neighbor->visitid == qh->visit_id)
+      continue;
+    if (qh->PRINTtransparent && !neighbor->good)
+      continue;
+    vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
+                          SETindex_(facet->neighbors, neighbor), 0);
+    if (qh->DOintersections)
+      qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, color);
+    else {
+      if (qh->DROPdim >= 0)
+        qh_fprintf(qh, fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
+                facet->id, neighbor->id);
+      else {
+        qh->printoutvar++;
+        qh_fprintf(qh, fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
+      }
+      FOREACHvertex_(vertices) {
+        for (k=0; k < qh->hull_dim; k++) {
+          if (k != qh->DROPdim)
+            qh_fprintf(qh, fp, 9121, "%8.4g ", vertex->point[k]);
+        }
+        qh_fprintf(qh, fp, 9122, "\n");
+      }
+      if (qh->DROPdim >= 0)
+        qh_fprintf(qh, fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
+    }
+    qh_setfree(qh, &vertices);
+  }
+} /* printfacet4geom_simplicial */
+
+
+/*---------------------------------
+
+  qh_printfacetNvertex_nonsimplicial(qh, fp, facet, id, format )
+    print vertices for an N-d non-simplicial facet
+    triangulates each ridge to the id
+*/
+void qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format) {
+  vertexT *vertex, **vertexp;
+  ridgeT *ridge, **ridgep;
+
+  if (facet->visible && qh->NEWfacets)
+    return;
+  FOREACHridge_(facet->ridges) {
+    if (format == qh_PRINTtriangles)
+      qh_fprintf(qh, fp, 9124, "%d ", qh->hull_dim);
+    qh_fprintf(qh, fp, 9125, "%d ", id);
+    if ((ridge->top == facet) ^ qh_ORIENTclock) {
+      FOREACHvertex_(ridge->vertices)
+        qh_fprintf(qh, fp, 9126, "%d ", qh_pointid(qh, vertex->point));
+    }else {
+      FOREACHvertexreverse12_(ridge->vertices)
+        qh_fprintf(qh, fp, 9127, "%d ", qh_pointid(qh, vertex->point));
+    }
+    qh_fprintf(qh, fp, 9128, "\n");
+  }
+} /* printfacetNvertex_nonsimplicial */
+
+
+/*---------------------------------
+
+  qh_printfacetNvertex_simplicial(qh, fp, facet, format )
+    print vertices for an N-d simplicial facet
+    prints vertices for non-simplicial facets
+      2-d facets (orientation preserved by qh_mergefacet2d)
+      PRINToff ('o') for 4-d and higher
+*/
+void qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
+  vertexT *vertex, **vertexp;
+
+  if (format == qh_PRINToff || format == qh_PRINTtriangles)
+    qh_fprintf(qh, fp, 9129, "%d ", qh_setsize(qh, facet->vertices));
+  if ((facet->toporient ^ qh_ORIENTclock)
+  || (qh->hull_dim > 2 && !facet->simplicial)) {
+    FOREACHvertex_(facet->vertices)
+      qh_fprintf(qh, fp, 9130, "%d ", qh_pointid(qh, vertex->point));
+  }else {
+    FOREACHvertexreverse12_(facet->vertices)
+      qh_fprintf(qh, fp, 9131, "%d ", qh_pointid(qh, vertex->point));
+  }
+  qh_fprintf(qh, fp, 9132, "\n");
+} /* printfacetNvertex_simplicial */
+
+
+/*---------------------------------
+
+  qh_printfacetheader(qh, fp, facet )
+    prints header fields of a facet to fp
+
+  notes:
+    for 'f' output and debugging
+    Same as QhullFacet::printHeader()
+*/
+void qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet) {
+  pointT *point, **pointp, *furthest;
+  facetT *neighbor, **neighborp;
+  realT dist;
+
+  if (facet == qh_MERGEridge) {
+    qh_fprintf(qh, fp, 9133, " MERGEridge\n");
+    return;
+  }else if (facet == qh_DUPLICATEridge) {
+    qh_fprintf(qh, fp, 9134, " DUPLICATEridge\n");
+    return;
+  }else if (!facet) {
+    qh_fprintf(qh, fp, 9135, " NULLfacet\n");
+    return;
+  }
+  qh->old_randomdist= qh->RANDOMdist;
+  qh->RANDOMdist= False;
+  qh_fprintf(qh, fp, 9136, "- f%d\n", facet->id);
+  qh_fprintf(qh, fp, 9137, "    - flags:");
+  if (facet->toporient)
+    qh_fprintf(qh, fp, 9138, " top");
+  else
+    qh_fprintf(qh, fp, 9139, " bottom");
+  if (facet->simplicial)
+    qh_fprintf(qh, fp, 9140, " simplicial");
+  if (facet->tricoplanar)
+    qh_fprintf(qh, fp, 9141, " tricoplanar");
+  if (facet->upperdelaunay)
+    qh_fprintf(qh, fp, 9142, " upperDelaunay");
+  if (facet->visible)
+    qh_fprintf(qh, fp, 9143, " visible");
+  if (facet->newfacet)
+    qh_fprintf(qh, fp, 9144, " newfacet");
+  if (facet->tested)
+    qh_fprintf(qh, fp, 9145, " tested");
+  if (!facet->good)
+    qh_fprintf(qh, fp, 9146, " notG");
+  if (facet->seen && qh->IStracing)
+    qh_fprintf(qh, fp, 9147, " seen");
+  if (facet->seen2 && qh->IStracing)
+    qh_fprintf(qh, fp, 9418, " seen2");
+  if (facet->isarea)
+    qh_fprintf(qh, fp, 9419, " isarea");
+  if (facet->coplanarhorizon)
+    qh_fprintf(qh, fp, 9148, " coplanarhorizon");
+  if (facet->mergehorizon)
+    qh_fprintf(qh, fp, 9149, " mergehorizon");
+  if (facet->cycledone)
+    qh_fprintf(qh, fp, 9420, " cycledone");
+  if (facet->keepcentrum)
+    qh_fprintf(qh, fp, 9150, " keepcentrum");
+  if (facet->dupridge)
+    qh_fprintf(qh, fp, 9151, " dupridge");
+  if (facet->mergeridge && !facet->mergeridge2)
+    qh_fprintf(qh, fp, 9152, " mergeridge1");
+  if (facet->mergeridge2)
+    qh_fprintf(qh, fp, 9153, " mergeridge2");
+  if (facet->newmerge)
+    qh_fprintf(qh, fp, 9154, " newmerge");
+  if (facet->flipped)
+    qh_fprintf(qh, fp, 9155, " flipped");
+  if (facet->notfurthest)
+    qh_fprintf(qh, fp, 9156, " notfurthest");
+  if (facet->degenerate)
+    qh_fprintf(qh, fp, 9157, " degenerate");
+  if (facet->redundant)
+    qh_fprintf(qh, fp, 9158, " redundant");
+  qh_fprintf(qh, fp, 9159, "\n");
+  if (facet->isarea)
+    qh_fprintf(qh, fp, 9160, "    - area: %2.2g\n", facet->f.area);
+  else if (qh->NEWfacets && facet->visible && facet->f.replace)
+    qh_fprintf(qh, fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
+  else if (facet->newfacet) {
+    if (facet->f.samecycle && facet->f.samecycle != facet)
+      qh_fprintf(qh, fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
+  }else if (facet->tricoplanar /* !isarea */) {
+    if (facet->f.triowner)
+      qh_fprintf(qh, fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
+  }else if (facet->f.newcycle)
+    qh_fprintf(qh, fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
+  if (facet->nummerge == qh_MAXnummerge)
+    qh_fprintf(qh, fp, 9427, "    - merges: %dmax\n", qh_MAXnummerge);
+  else if (facet->nummerge)
+    qh_fprintf(qh, fp, 9165, "    - merges: %d\n", facet->nummerge);
+  qh_printpointid(qh, fp, "    - normal: ", qh->hull_dim, facet->normal, qh_IDunknown);
+  qh_fprintf(qh, fp, 9166, "    - offset: %10.7g\n", facet->offset);
+  if (qh->CENTERtype == qh_ASvoronoi || facet->center)
+    qh_printcenter(qh, fp, qh_PRINTfacets, "    - center: ", facet);
+#if qh_MAXoutside
+  if (facet->maxoutside > qh->DISTround) /* initial value */
+    qh_fprintf(qh, fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
+#endif
+  if (!SETempty_(facet->outsideset)) {
+    furthest= (pointT *)qh_setlast(facet->outsideset);
+    if (qh_setsize(qh, facet->outsideset) < 6) {
+      qh_fprintf(qh, fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(qh, furthest));
+      FOREACHpoint_(facet->outsideset)
+        qh_printpoint(qh, fp, "     ", point);
+    }else if (qh_setsize(qh, facet->outsideset) < 21) {
+      qh_printpoints(qh, fp, "    - outside set:", facet->outsideset);
+    }else {
+      qh_fprintf(qh, fp, 9169, "    - outside set:  %d points.", qh_setsize(qh, facet->outsideset));
+      qh_printpoint(qh, fp, "  Furthest", furthest);
+    }
+#if !qh_COMPUTEfurthest
+    qh_fprintf(qh, fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
+#endif
+  }
+  if (!SETempty_(facet->coplanarset)) {
+    furthest= (pointT *)qh_setlast(facet->coplanarset);
+    if (qh_setsize(qh, facet->coplanarset) < 6) {
+      qh_fprintf(qh, fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(qh, furthest));
+      FOREACHpoint_(facet->coplanarset)
+        qh_printpoint(qh, fp, "     ", point);
+    }else if (qh_setsize(qh, facet->coplanarset) < 21) {
+      qh_printpoints(qh, fp, "    - coplanar set:", facet->coplanarset);
+    }else {
+      qh_fprintf(qh, fp, 9172, "    - coplanar set:  %d points.", qh_setsize(qh, facet->coplanarset));
+      qh_printpoint(qh, fp, "  Furthest", furthest);
+    }
+    zinc_(Zdistio);
+    qh_distplane(qh, furthest, facet, &dist);
+    qh_fprintf(qh, fp, 9173, "      furthest distance= %2.2g\n", dist);
+  }
+  qh_printvertices(qh, fp, "    - vertices:", facet->vertices);
+  qh_fprintf(qh, fp, 9174, "    - neighboring facets:");
+  FOREACHneighbor_(facet) {
+    if (neighbor == qh_MERGEridge)
+      qh_fprintf(qh, fp, 9175, " MERGEridge");
+    else if (neighbor == qh_DUPLICATEridge)
+      qh_fprintf(qh, fp, 9176, " DUPLICATEridge");
+    else
+      qh_fprintf(qh, fp, 9177, " f%d", neighbor->id);
+  }
+  qh_fprintf(qh, fp, 9178, "\n");
+  qh->RANDOMdist= qh->old_randomdist;
+} /* printfacetheader */
+
+
+/*---------------------------------
+
+  qh_printfacetridges(qh, fp, facet )
+    prints ridges of a facet to fp
+
+  notes:
+    ridges printed in neighbor order
+    assumes the ridges exist
+    for 'f' output
+    same as QhullFacet::printRidges
+*/
+void qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet) {
+  facetT *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+  int numridges= 0;
+  int n;
+
+  if (facet->visible && qh->NEWfacets) {
+    qh_fprintf(qh, fp, 9179, "    - ridges (tentative ids):");
+    FOREACHridge_(facet->ridges)
+      qh_fprintf(qh, fp, 9180, " r%d", ridge->id);
+    qh_fprintf(qh, fp, 9181, "\n");
+  }else {
+    qh_fprintf(qh, fp, 9182, "    - ridges:\n");
+    FOREACHridge_(facet->ridges)
+      ridge->seen= False;
+    if (qh->hull_dim == 3) {
+      ridge= SETfirstt_(facet->ridges, ridgeT);
+      while (ridge && !ridge->seen) {
+        ridge->seen= True;
+        qh_printridge(qh, fp, ridge);
+        numridges++;
+        ridge= qh_nextridge3d(ridge, facet, NULL);
+        }
+    }else {
+      FOREACHneighbor_(facet) {
+        FOREACHridge_(facet->ridges) {
+          if (otherfacet_(ridge, facet) == neighbor && !ridge->seen) {
+            ridge->seen= True;
+            qh_printridge(qh, fp, ridge);
+            numridges++;
+          }
+        }
+      }
+    }
+    n= qh_setsize(qh, facet->ridges);
+    if (n == 1 && facet->newfacet && qh->NEWtentative) {
+      qh_fprintf(qh, fp, 9411, "     - horizon ridge to visible facet\n");
+    }
+    if (numridges != n) {
+      qh_fprintf(qh, fp, 9183, "     - all ridges:");
+      FOREACHridge_(facet->ridges)
+        qh_fprintf(qh, fp, 9184, " r%d", ridge->id);
+      qh_fprintf(qh, fp, 9185, "\n");
+    }
+    /* non-3d ridges w/o non-simplicial neighbors */
+    FOREACHridge_(facet->ridges) {
+      if (!ridge->seen)
+        qh_printridge(qh, fp, ridge);
+    }
+  }
+} /* printfacetridges */
+
+/*---------------------------------
+
+  qh_printfacets(qh, fp, format, facetlist, facets, printall )
+    prints facetlist and/or facet set in output format
+
+  notes:
+    also used for specialized formats ('FO' and summary)
+    turns off 'Rn' option since want actual numbers
+*/
+void qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
+  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
+  facetT *facet, **facetp;
+  setT *vertices;
+  coordT *center;
+  realT outerplane, innerplane;
+
+  qh->old_randomdist= qh->RANDOMdist;
+  qh->RANDOMdist= False;
+  if (qh->CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
+    qh_fprintf(qh, qh->ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
+  if (format == qh_PRINTnone)
+    ; /* print nothing */
+  else if (format == qh_PRINTaverage) {
+    vertices= qh_facetvertices(qh, facetlist, facets, printall);
+    center= qh_getcenter(qh, vertices);
+    qh_fprintf(qh, fp, 9186, "%d 1\n", qh->hull_dim);
+    qh_printpointid(qh, fp, NULL, qh->hull_dim, center, qh_IDunknown);
+    qh_memfree(qh, center, qh->normal_size);
+    qh_settempfree(qh, &vertices);
+  }else if (format == qh_PRINTextremes) {
+    if (qh->DELAUNAY)
+      qh_printextremes_d(qh, fp, facetlist, facets, printall);
+    else if (qh->hull_dim == 2)
+      qh_printextremes_2d(qh, fp, facetlist, facets, printall);
+    else
+      qh_printextremes(qh, fp, facetlist, facets, printall);
+  }else if (format == qh_PRINToptions)
+    qh_fprintf(qh, fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
+  else if (format == qh_PRINTpoints && !qh->VORONOI)
+    qh_printpoints_out(qh, fp, facetlist, facets, printall);
+  else if (format == qh_PRINTqhull)
+    qh_fprintf(qh, fp, 9188, "%s | %s\n", qh->rbox_command, qh->qhull_command);
+  else if (format == qh_PRINTsize) {
+    qh_fprintf(qh, fp, 9189, "0\n2 ");
+    qh_fprintf(qh, fp, 9190, qh_REAL_1, qh->totarea);
+    qh_fprintf(qh, fp, 9191, qh_REAL_1, qh->totvol);
+    qh_fprintf(qh, fp, 9192, "\n");
+  }else if (format == qh_PRINTsummary) {
+    qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
+      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
+    vertices= qh_facetvertices(qh, facetlist, facets, printall);
+    qh_fprintf(qh, fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh->hull_dim,
+                qh->num_points + qh_setsize(qh, qh->other_points),
+                qh->num_vertices, qh->num_facets - qh->num_visible,
+                qh_setsize(qh, vertices), numfacets, numcoplanars,
+                numfacets - numsimplicial, zzval_(Zdelvertextot),
+                numtricoplanars);
+    qh_settempfree(qh, &vertices);
+    qh_outerinner(qh, NULL, &outerplane, &innerplane);
+    qh_fprintf(qh, fp, 9194, qh_REAL_2n, outerplane, innerplane);
+  }else if (format == qh_PRINTvneighbors)
+    qh_printvneighbors(qh, fp, facetlist, facets, printall);
+  else if (qh->VORONOI && format == qh_PRINToff)
+    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
+  else if (qh->VORONOI && format == qh_PRINTgeom) {
+    qh_printbegin(qh, fp, format, facetlist, facets, printall);
+    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
+    qh_printend(qh, fp, format, facetlist, facets, printall);
+  }else if (qh->VORONOI
+  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
+    qh_printvdiagram(qh, fp, format, facetlist, facets, printall);
+  else {
+    qh_printbegin(qh, fp, format, facetlist, facets, printall);
+    FORALLfacet_(facetlist)
+      qh_printafacet(qh, fp, format, facet, printall);
+    FOREACHfacet_(facets)
+      qh_printafacet(qh, fp, format, facet, printall);
+    qh_printend(qh, fp, format, facetlist, facets, printall);
+  }
+  qh->RANDOMdist= qh->old_randomdist;
+} /* printfacets */
+
+
+/*---------------------------------
+
+  qh_printhyperplaneintersection(qh, fp, facet1, facet2, vertices, color )
+    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
+*/
+void qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
+                   setT *vertices, realT color[3]) {
+  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
+  vertexT *vertex, **vertexp;
+  int i, k;
+  boolT nearzero1, nearzero2;
+
+  costheta= qh_getangle(qh, facet1->normal, facet2->normal);
+  denominator= 1 - costheta * costheta;
+  i= qh_setsize(qh, vertices);
+  if (qh->hull_dim == 3)
+    qh_fprintf(qh, fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
+  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
+    qh_fprintf(qh, fp, 9196, "OFF 3 1 1 ");
+  else
+    qh->printoutvar++;
+  qh_fprintf(qh, fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
+  mindenom= 1 / (10.0 * qh->MAXabs_coord);
+  FOREACHvertex_(vertices) {
+    zadd_(Zdistio, 2);
+    qh_distplane(qh, vertex->point, facet1, &dist1);
+    qh_distplane(qh, vertex->point, facet2, &dist2);
+    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
+    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
+    if (nearzero1 || nearzero2)
+      s= t= 0.0;
+    for (k=qh->hull_dim; k--; )
+      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
+    if (qh->PRINTdim <= 3) {
+      qh_projectdim3(qh, p, p);
+      qh_fprintf(qh, fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
+    }else
+      qh_fprintf(qh, fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
+    if (nearzero1+nearzero2)
+      qh_fprintf(qh, fp, 9200, "p%d(coplanar facets)\n", qh_pointid(qh, vertex->point));
+    else
+      qh_fprintf(qh, fp, 9201, "projected p%d\n", qh_pointid(qh, vertex->point));
+  }
+  if (qh->hull_dim == 3)
+    qh_fprintf(qh, fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
+  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
+    qh_fprintf(qh, fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
+} /* printhyperplaneintersection */
+
+/*---------------------------------
+
+  qh_printline3geom(qh, fp, pointA, pointB, color )
+    prints a line as a VECT
+    prints 0's for qh.DROPdim
+
+  notes:
+    if pointA == pointB,
+      it's a 1 point VECT
+*/
+void qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
+  int k;
+  realT pA[4], pB[4];
+
+  qh_projectdim3(qh, pointA, pA);
+  qh_projectdim3(qh, pointB, pB);
+  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
+      (fabs(pA[1] - pB[1]) > 1e-3) ||
+      (fabs(pA[2] - pB[2]) > 1e-3)) {
+    qh_fprintf(qh, fp, 9204, "VECT 1 2 1 2 1\n");
+    for (k=0; k < 3; k++)
+       qh_fprintf(qh, fp, 9205, "%8.4g ", pB[k]);
+    qh_fprintf(qh, fp, 9206, " # p%d\n", qh_pointid(qh, pointB));
+  }else
+    qh_fprintf(qh, fp, 9207, "VECT 1 1 1 1 1\n");
+  for (k=0; k < 3; k++)
+    qh_fprintf(qh, fp, 9208, "%8.4g ", pA[k]);
+  qh_fprintf(qh, fp, 9209, " # p%d\n", qh_pointid(qh, pointA));
+  qh_fprintf(qh, fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
+}
+
+/*---------------------------------
+
+  qh_printneighborhood(qh, fp, format, facetA, facetB, printall )
+    print neighborhood of one or two facets
+
+  notes:
+    calls qh_findgood_all()
+    bumps qh.visit_id
+*/
+void qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
+  facetT *neighbor, **neighborp, *facet;
+  setT *facets;
+
+  if (format == qh_PRINTnone)
+    return;
+  qh_findgood_all(qh, qh->facet_list);
+  if (facetA == facetB)
+    facetB= NULL;
+  facets= qh_settemp(qh, 2*(qh_setsize(qh, facetA->neighbors)+1));
+  qh->visit_id++;
+  for (facet=facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
+    if (facet->visitid != qh->visit_id) {
+      facet->visitid= qh->visit_id;
+      qh_setappend(qh, &facets, facet);
+    }
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid == qh->visit_id)
+        continue;
+      neighbor->visitid= qh->visit_id;
+      if (printall || !qh_skipfacet(qh, neighbor))
+        qh_setappend(qh, &facets, neighbor);
+    }
+  }
+  qh_printfacets(qh, fp, format, NULL, facets, printall);
+  qh_settempfree(qh, &facets);
+} /* printneighborhood */
+
+/*---------------------------------
+
+  qh_printpoint(qh, fp, string, point )
+  qh_printpointid(qh, fp, string, dim, point, id )
+    prints the coordinates of a point
+
+  returns:
+    if string is defined
+      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
+
+  notes:
+    nop if point is NULL
+    Same as QhullPoint's printPoint
+*/
+void qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point) {
+  int id= qh_pointid(qh, point);
+
+  qh_printpointid(qh, fp, string, qh->hull_dim, point, id);
+} /* printpoint */
+
+void qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id) {
+  int k;
+  realT r; /*bug fix*/
+
+  if (!point)
+    return;
+  if (string) {
+    qh_fprintf(qh, fp, 9211, "%s", string);
+    if (id != qh_IDunknown && id != qh_IDnone)
+      qh_fprintf(qh, fp, 9212, " p%d: ", id);
+  }
+  for (k=dim; k--; ) {
+    r= *point++;
+    if (string)
+      qh_fprintf(qh, fp, 9213, " %8.4g", r);
+    else
+      qh_fprintf(qh, fp, 9214, qh_REAL_1, r);
+  }
+  qh_fprintf(qh, fp, 9215, "\n");
+} /* printpointid */
+
+/*---------------------------------
+
+  qh_printpoint3(qh, fp, point )
+    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
+*/
+void qh_printpoint3(qhT *qh, FILE *fp, pointT *point) {
+  int k;
+  realT p[4];
+
+  qh_projectdim3(qh, point, p);
+  for (k=0; k < 3; k++)
+    qh_fprintf(qh, fp, 9216, "%8.4g ", p[k]);
+  qh_fprintf(qh, fp, 9217, " # p%d\n", qh_pointid(qh, point));
+} /* printpoint3 */
+
+/*----------------------------------------
+-printpoints- print pointids for a set of points starting at index
+   see geom_r.c
+*/
+
+/*---------------------------------
+
+  qh_printpoints_out(qh, fp, facetlist, facets, printall )
+    prints vertices, coplanar/inside points, for facets by their point coordinates
+    allows qh.CDDoutput
+
+  notes:
+    same format as qhull input
+    if no coplanar/interior points,
+      same order as qh_printextremes
+*/
+void qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
+  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
+  int numpoints=0, point_i, point_n;
+  setT *vertices, *points;
+  facetT *facet, **facetp;
+  pointT *point, **pointp;
+  vertexT *vertex, **vertexp;
+  int id;
+
+  points= qh_settemp(qh, allpoints);
+  qh_setzero(qh, points, 0, allpoints);
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  FOREACHvertex_(vertices) {
+    id= qh_pointid(qh, vertex->point);
+    if (id >= 0)
+      SETelem_(points, id)= vertex->point;
+  }
+  if (qh->KEEPinside || qh->KEEPcoplanar || qh->KEEPnearinside) {
+    FORALLfacet_(facetlist) {
+      if (!printall && qh_skipfacet(qh, facet))
+        continue;
+      FOREACHpoint_(facet->coplanarset) {
+        id= qh_pointid(qh, point);
+        if (id >= 0)
+          SETelem_(points, id)= point;
+      }
+    }
+    FOREACHfacet_(facets) {
+      if (!printall && qh_skipfacet(qh, facet))
+        continue;
+      FOREACHpoint_(facet->coplanarset) {
+        id= qh_pointid(qh, point);
+        if (id >= 0)
+          SETelem_(points, id)= point;
+      }
+    }
+  }
+  qh_settempfree(qh, &vertices);
+  FOREACHpoint_i_(qh, points) {
+    if (point)
+      numpoints++;
+  }
+  if (qh->CDDoutput)
+    qh_fprintf(qh, fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
+             qh->qhull_command, numpoints, qh->hull_dim + 1);
+  else
+    qh_fprintf(qh, fp, 9219, "%d\n%d\n", qh->hull_dim, numpoints);
+  FOREACHpoint_i_(qh, points) {
+    if (point) {
+      if (qh->CDDoutput)
+        qh_fprintf(qh, fp, 9220, "1 ");
+      qh_printpoint(qh, fp, NULL, point);
+    }
+  }
+  if (qh->CDDoutput)
+    qh_fprintf(qh, fp, 9221, "end\n");
+  qh_settempfree(qh, &points);
+} /* printpoints_out */
+
+
+/*---------------------------------
+
+  qh_printpointvect(qh, fp, point, normal, center, radius, color )
+    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
+*/
+void qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
+  realT diff[4], pointA[4];
+  int k;
+
+  for (k=qh->hull_dim; k--; ) {
+    if (center)
+      diff[k]= point[k]-center[k];
+    else if (normal)
+      diff[k]= normal[k];
+    else
+      diff[k]= 0;
+  }
+  if (center)
+    qh_normalize2(qh, diff, qh->hull_dim, True, NULL, NULL);
+  for (k=qh->hull_dim; k--; )
+    pointA[k]= point[k]+diff[k] * radius;
+  qh_printline3geom(qh, fp, point, pointA, color);
+} /* printpointvect */
+
+/*---------------------------------
+
+  qh_printpointvect2(qh, fp, point, normal, center, radius )
+    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
+*/
+void qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
+  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
+
+  qh_printpointvect(qh, fp, point, normal, center, radius, red);
+  qh_printpointvect(qh, fp, point, normal, center, -radius, yellow);
+} /* printpointvect2 */
+
+/*---------------------------------
+
+  qh_printridge(qh, fp, ridge )
+    prints the information in a ridge
+
+  notes:
+    for qh_printfacetridges()
+    same as operator<< [QhullRidge.cpp]
+*/
+void qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge) {
+
+  qh_fprintf(qh, fp, 9222, "     - r%d", ridge->id);
+  if (ridge->tested)
+    qh_fprintf(qh, fp, 9223, " tested");
+  if (ridge->nonconvex)
+    qh_fprintf(qh, fp, 9224, " nonconvex");
+  if (ridge->mergevertex)
+    qh_fprintf(qh, fp, 9421, " mergevertex");
+  if (ridge->mergevertex2)
+    qh_fprintf(qh, fp, 9422, " mergevertex2");
+  if (ridge->simplicialtop)
+    qh_fprintf(qh, fp, 9425, " simplicialtop");
+  if (ridge->simplicialbot)
+    qh_fprintf(qh, fp, 9423, " simplicialbot");
+  qh_fprintf(qh, fp, 9225, "\n");
+  qh_printvertices(qh, fp, "           vertices:", ridge->vertices);
+  if (ridge->top && ridge->bottom)
+    qh_fprintf(qh, fp, 9226, "           between f%d and f%d\n",
+            ridge->top->id, ridge->bottom->id);
+} /* printridge */
+
+/*---------------------------------
+
+  qh_printspheres(qh, fp, vertices, radius )
+    prints 3-d vertices as OFF spheres
+
+  notes:
+    inflated octahedron from Stuart Levy earth/mksphere2
+*/
+void qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius) {
+  vertexT *vertex, **vertexp;
+
+  qh->printoutnum++;
+  qh_fprintf(qh, fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
+INST geom {define vsphere OFF\n\
+18 32 48\n\
+\n\
+0 0 1\n\
+1 0 0\n\
+0 1 0\n\
+-1 0 0\n\
+0 -1 0\n\
+0 0 -1\n\
+0.707107 0 0.707107\n\
+0 -0.707107 0.707107\n\
+0.707107 -0.707107 0\n\
+-0.707107 0 0.707107\n\
+-0.707107 -0.707107 0\n\
+0 0.707107 0.707107\n\
+-0.707107 0.707107 0\n\
+0.707107 0.707107 0\n\
+0.707107 0 -0.707107\n\
+0 0.707107 -0.707107\n\
+-0.707107 0 -0.707107\n\
+0 -0.707107 -0.707107\n\
+\n\
+3 0 6 11\n\
+3 0 7 6 \n\
+3 0 9 7 \n\
+3 0 11 9\n\
+3 1 6 8 \n\
+3 1 8 14\n\
+3 1 13 6\n\
+3 1 14 13\n\
+3 2 11 13\n\
+3 2 12 11\n\
+3 2 13 15\n\
+3 2 15 12\n\
+3 3 9 12\n\
+3 3 10 9\n\
+3 3 12 16\n\
+3 3 16 10\n\
+3 4 7 10\n\
+3 4 8 7\n\
+3 4 10 17\n\
+3 4 17 8\n\
+3 5 14 17\n\
+3 5 15 14\n\
+3 5 16 15\n\
+3 5 17 16\n\
+3 6 13 11\n\
+3 7 8 6\n\
+3 9 10 7\n\
+3 11 12 9\n\
+3 14 8 17\n\
+3 15 13 14\n\
+3 16 12 15\n\
+3 17 10 16\n} transforms { TLIST\n");
+  FOREACHvertex_(vertices) {
+    qh_fprintf(qh, fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
+      radius, vertex->id, radius, radius);
+    qh_printpoint3(qh, fp, vertex->point);
+    qh_fprintf(qh, fp, 9229, "1\n");
+  }
+  qh_fprintf(qh, fp, 9230, "}}}\n");
+} /* printspheres */
+
+
+/*----------------------------------------------
+-printsummary-
+                see libqhull_r.c
+*/
+
+/*---------------------------------
+
+  qh_printvdiagram(qh, fp, format, facetlist, facets, printall )
+    print voronoi diagram
+      # of pairs of input sites
+      #indices site1 site2 vertex1 ...
+
+    sites indexed by input point id
+      point 0 is the first input point
+    vertices indexed by 'o' and 'p' order
+      vertex 0 is the 'vertex-at-infinity'
+      vertex 1 is the first Voronoi vertex
+
+  see:
+    qh_printvoronoi()
+    qh_eachvoronoi_all()
+
+  notes:
+    if all facets are upperdelaunay,
+      prints upper hull (furthest-site Voronoi diagram)
+*/
+void qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
+  setT *vertices;
+  int totcount, numcenters;
+  boolT isLower;
+  qh_RIDGE innerouter= qh_RIDGEall;
+  printvridgeT printvridge= NULL;
+
+  if (format == qh_PRINTvertices) {
+    innerouter= qh_RIDGEall;
+    printvridge= qh_printvridge;
+  }else if (format == qh_PRINTinner) {
+    innerouter= qh_RIDGEinner;
+    printvridge= qh_printvnorm;
+  }else if (format == qh_PRINTouter) {
+    innerouter= qh_RIDGEouter;
+    printvridge= qh_printvnorm;
+  }else {
+    qh_fprintf(qh, qh->ferr, 6219, "qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
+  totcount= qh_printvdiagram2(qh, NULL, NULL, vertices, innerouter, False);
+  qh_fprintf(qh, fp, 9231, "%d\n", totcount);
+  totcount= qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, True /* inorder*/);
+  qh_settempfree(qh, &vertices);
+#if 0  /* for testing qh_eachvoronoi_all */
+  qh_fprintf(qh, fp, 9232, "\n");
+  totcount= qh_eachvoronoi_all(qh, fp, printvridge, qh->UPPERdelaunay, innerouter, True /* inorder*/);
+  qh_fprintf(qh, fp, 9233, "%d\n", totcount);
+#endif
+} /* printvdiagram */
+
+/*---------------------------------
+
+  qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, inorder )
+    visit all pairs of input sites (vertices) for selected Voronoi vertices
+    vertices may include NULLs
+
+  innerouter:
+    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
+    qh_RIDGEinner print only inner ridges
+    qh_RIDGEouter print only outer ridges
+
+  inorder:
+    print 3-d Voronoi vertices in order
+
+  assumes:
+    qh_markvoronoi marked facet->visitid for Voronoi vertices
+    all facet->seen= False
+    all facet->seen2= True
+
+  returns:
+    total number of Voronoi ridges
+    if printvridge,
+      calls printvridge( fp, vertex, vertexA, centers) for each ridge
+      [see qh_eachvoronoi()]
+
+  see:
+    qh_eachvoronoi_all()
+*/
+int qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
+  int totcount= 0;
+  int vertex_i, vertex_n;
+  vertexT *vertex;
+
+  FORALLvertices
+    vertex->seen= False;
+  FOREACHvertex_i_(qh, vertices) {
+    if (vertex) {
+      if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
+        continue;
+      totcount += qh_eachvoronoi(qh, fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
+    }
+  }
+  return totcount;
+} /* printvdiagram2 */
+
+/*---------------------------------
+
+  qh_printvertex(qh, fp, vertex )
+    prints the information in a vertex
+    Duplicated as operator<< [QhullVertex.cpp]
+*/
+void qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex) {
+  pointT *point;
+  int k, count= 0;
+  facetT *neighbor, **neighborp;
+  realT r; /*bug fix*/
+
+  if (!vertex) {
+    qh_fprintf(qh, fp, 9234, "  NULLvertex\n");
+    return;
+  }
+  qh_fprintf(qh, fp, 9235, "- p%d(v%d):", qh_pointid(qh, vertex->point), vertex->id);
+  point= vertex->point;
+  if (point) {
+    for (k=qh->hull_dim; k--; ) {
+      r= *point++;
+      qh_fprintf(qh, fp, 9236, " %5.2g", r);
+    }
+  }
+  if (vertex->deleted)
+    qh_fprintf(qh, fp, 9237, " deleted");
+  if (vertex->delridge)
+    qh_fprintf(qh, fp, 9238, " delridge");
+  if (vertex->newfacet)
+    qh_fprintf(qh, fp, 9415, " newfacet");
+  if (vertex->seen && qh->IStracing)
+    qh_fprintf(qh, fp, 9416, " seen");
+  if (vertex->seen2 && qh->IStracing)
+    qh_fprintf(qh, fp, 9417, " seen2");
+  qh_fprintf(qh, fp, 9239, "\n");
+  if (vertex->neighbors) {
+    qh_fprintf(qh, fp, 9240, "  neighbors:");
+    FOREACHneighbor_(vertex) {
+      if (++count % 100 == 0)
+        qh_fprintf(qh, fp, 9241, "\n     ");
+      qh_fprintf(qh, fp, 9242, " f%d", neighbor->id);
+    }
+    qh_fprintf(qh, fp, 9243, "\n");
+  }
+} /* printvertex */
+
+
+/*---------------------------------
+
+  qh_printvertexlist(qh, fp, string, facetlist, facets, printall )
+    prints vertices used by a facetlist or facet set
+    tests qh_skipfacet() if !printall
+*/
+void qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
+                         setT *facets, boolT printall) {
+  vertexT *vertex, **vertexp;
+  setT *vertices;
+
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  qh_fprintf(qh, fp, 9244, "%s", string);
+  FOREACHvertex_(vertices)
+    qh_printvertex(qh, fp, vertex);
+  qh_settempfree(qh, &vertices);
+} /* printvertexlist */
+
+
+/*---------------------------------
+
+  qh_printvertices(qh, fp, string, vertices )
+    prints vertices in a set
+    duplicated as printVertexSet [QhullVertex.cpp]
+*/
+void qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices) {
+  vertexT *vertex, **vertexp;
+
+  qh_fprintf(qh, fp, 9245, "%s", string);
+  FOREACHvertex_(vertices)
+    qh_fprintf(qh, fp, 9246, " p%d(v%d)", qh_pointid(qh, vertex->point), vertex->id);
+  qh_fprintf(qh, fp, 9247, "\n");
+} /* printvertices */
+
+/*---------------------------------
+
+  qh_printvneighbors(qh, fp, facetlist, facets, printall )
+    print vertex neighbors of vertices in facetlist and facets ('FN')
+
+  notes:
+    qh_countfacets clears facet->visitid for non-printed facets
+
+  design:
+    collect facet count and related statistics
+    if necessary, build neighbor sets for each vertex
+    collect vertices in facetlist and facets
+    build a point array for point->vertex and point->coplanar facet
+    for each point
+      list vertex neighbors or coplanar facet
+*/
+void qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
+  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
+  setT *vertices, *vertex_points, *coplanar_points;
+  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
+  vertexT *vertex, **vertexp;
+  int vertex_i, vertex_n;
+  facetT *facet, **facetp, *neighbor, **neighborp;
+  pointT *point, **pointp;
+
+  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
+      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
+  qh_fprintf(qh, fp, 9248, "%d\n", numpoints);
+  qh_vertexneighbors(qh);
+  vertices= qh_facetvertices(qh, facetlist, facets, printall);
+  vertex_points= qh_settemp(qh, numpoints);
+  coplanar_points= qh_settemp(qh, numpoints);
+  qh_setzero(qh, vertex_points, 0, numpoints);
+  qh_setzero(qh, coplanar_points, 0, numpoints);
+  FOREACHvertex_(vertices)
+    qh_point_add(qh, vertex_points, vertex->point, vertex);
+  FORALLfacet_(facetlist) {
+    FOREACHpoint_(facet->coplanarset)
+      qh_point_add(qh, coplanar_points, point, facet);
+  }
+  FOREACHfacet_(facets) {
+    FOREACHpoint_(facet->coplanarset)
+      qh_point_add(qh, coplanar_points, point, facet);
+  }
+  FOREACHvertex_i_(qh, vertex_points) {
+    if (vertex) {
+      numneighbors= qh_setsize(qh, vertex->neighbors);
+      qh_fprintf(qh, fp, 9249, "%d", numneighbors);
+      qh_order_vertexneighbors(qh, vertex);
+      FOREACHneighbor_(vertex)
+        qh_fprintf(qh, fp, 9250, " %d",
+                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
+      qh_fprintf(qh, fp, 9251, "\n");
+    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
+      qh_fprintf(qh, fp, 9252, "1 %d\n",
+                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
+    else
+      qh_fprintf(qh, fp, 9253, "0\n");
+  }
+  qh_settempfree(qh, &coplanar_points);
+  qh_settempfree(qh, &vertex_points);
+  qh_settempfree(qh, &vertices);
+} /* printvneighbors */
+
+/*---------------------------------
+
+  qh_printvoronoi(qh, fp, format, facetlist, facets, printall )
+    print voronoi diagram in 'o' or 'G' format
+    for 'o' format
+      prints voronoi centers for each facet and for infinity
+      for each vertex, lists ids of printed facets or infinity
+      assumes facetlist and facets are disjoint
+    for 'G' format
+      prints an OFF object
+      adds a 0 coordinate to center
+      prints infinity but does not list in vertices
+
+  see:
+    qh_printvdiagram()
+
+  notes:
+    if 'o',
+      prints a line for each point except "at-infinity"
+    if all facets are upperdelaunay,
+      reverses lower and upper hull
+*/
+void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
+  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
+  facetT *facet, **facetp, *neighbor, **neighborp;
+  setT *vertices;
+  vertexT *vertex;
+  boolT isLower;
+  unsigned int numfacets= (unsigned int)qh->num_facets;
+
+  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
+  FOREACHvertex_i_(qh, vertices) {
+    if (vertex) {
+      numvertices++;
+      numneighbors= numinf= 0;
+      FOREACHneighbor_(vertex) {
+        if (neighbor->visitid == 0)
+          numinf= 1;
+        else if (neighbor->visitid < numfacets)
+          numneighbors++;
+      }
+      if (numinf && !numneighbors) {
+        SETelem_(vertices, vertex_i)= NULL;
+        numvertices--;
+      }
+    }
+  }
+  if (format == qh_PRINTgeom)
+    qh_fprintf(qh, fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
+                numcenters, numvertices);
+  else
+    qh_fprintf(qh, fp, 9255, "%d\n%d %d 1\n", qh->hull_dim-1, numcenters, qh_setsize(qh, vertices));
+  if (format == qh_PRINTgeom) {
+    for (k=qh->hull_dim-1; k--; )
+      qh_fprintf(qh, fp, 9256, qh_REAL_1, 0.0);
+    qh_fprintf(qh, fp, 9257, " 0 # infinity not used\n");
+  }else {
+    for (k=qh->hull_dim-1; k--; )
+      qh_fprintf(qh, fp, 9258, qh_REAL_1, qh_INFINITE);
+    qh_fprintf(qh, fp, 9259, "\n");
+  }
+  FORALLfacet_(facetlist) {
+    if (facet->visitid && facet->visitid < numfacets) {
+      if (format == qh_PRINTgeom)
+        qh_fprintf(qh, fp, 9260, "# %d f%d\n", vid++, facet->id);
+      qh_printcenter(qh, fp, format, NULL, facet);
+    }
+  }
+  FOREACHfacet_(facets) {
+    if (facet->visitid && facet->visitid < numfacets) {
+      if (format == qh_PRINTgeom)
+        qh_fprintf(qh, fp, 9261, "# %d f%d\n", vid++, facet->id);
+      qh_printcenter(qh, fp, format, NULL, facet);
+    }
+  }
+  FOREACHvertex_i_(qh, vertices) {
+    numneighbors= 0;
+    numinf=0;
+    if (vertex) {
+      qh_order_vertexneighbors(qh, vertex);
+      FOREACHneighbor_(vertex) {
+        if (neighbor->visitid == 0)
+          numinf= 1;
+        else if (neighbor->visitid < numfacets)
+          numneighbors++;
+      }
+    }
+    if (format == qh_PRINTgeom) {
+      if (vertex) {
+        qh_fprintf(qh, fp, 9262, "%d", numneighbors);
+        FOREACHneighbor_(vertex) {
+          if (neighbor->visitid && neighbor->visitid < numfacets)
+            qh_fprintf(qh, fp, 9263, " %d", neighbor->visitid);
+        }
+        qh_fprintf(qh, fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
+      }else
+        qh_fprintf(qh, fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
+    }else {
+      if (numinf)
+        numneighbors++;
+      qh_fprintf(qh, fp, 9266, "%d", numneighbors);
+      if (vertex) {
+        FOREACHneighbor_(vertex) {
+          if (neighbor->visitid == 0) {
+            if (numinf) {
+              numinf= 0;
+              qh_fprintf(qh, fp, 9267, " %d", neighbor->visitid);
+            }
+          }else if (neighbor->visitid < numfacets)
+            qh_fprintf(qh, fp, 9268, " %d", neighbor->visitid);
+        }
+      }
+      qh_fprintf(qh, fp, 9269, "\n");
+    }
+  }
+  if (format == qh_PRINTgeom)
+    qh_fprintf(qh, fp, 9270, "}\n");
+  qh_settempfree(qh, &vertices);
+} /* printvoronoi */
+
+/*---------------------------------
+
+  qh_printvnorm(qh, fp, vertex, vertexA, centers, unbounded )
+    print one separating plane of the Voronoi diagram for a pair of input sites
+    unbounded==True if centers includes vertex-at-infinity
+
+  assumes:
+    qh_ASvoronoi and qh_vertexneighbors() already set
+
+  note:
+    parameter unbounded is UNUSED by this callback
+
+  see:
+    qh_printvdiagram()
+    qh_eachvoronoi()
+*/
+void qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
+  pointT *normal;
+  realT offset;
+  int k;
+  QHULL_UNUSED(unbounded);
+
+  normal= qh_detvnorm(qh, vertex, vertexA, centers, &offset);
+  qh_fprintf(qh, fp, 9271, "%d %d %d ",
+      2+qh->hull_dim, qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
+  for (k=0; k< qh->hull_dim-1; k++)
+    qh_fprintf(qh, fp, 9272, qh_REAL_1, normal[k]);
+  qh_fprintf(qh, fp, 9273, qh_REAL_1, offset);
+  qh_fprintf(qh, fp, 9274, "\n");
+} /* printvnorm */
+
+/*---------------------------------
+
+  qh_printvridge(qh, fp, vertex, vertexA, centers, unbounded )
+    print one ridge of the Voronoi diagram for a pair of input sites
+    unbounded==True if centers includes vertex-at-infinity
+
+  see:
+    qh_printvdiagram()
+
+  notes:
+    the user may use a different function
+    parameter unbounded is UNUSED
+*/
+void qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
+  facetT *facet, **facetp;
+  QHULL_UNUSED(unbounded);
+
+  qh_fprintf(qh, fp, 9275, "%d %d %d", qh_setsize(qh, centers)+2,
+       qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
+  FOREACHfacet_(centers)
+    qh_fprintf(qh, fp, 9276, " %d", facet->visitid);
+  qh_fprintf(qh, fp, 9277, "\n");
+} /* printvridge */
+
+/*---------------------------------
+
+  qh_projectdim3(qh, source, destination )
+    project 2-d 3-d or 4-d point to a 3-d point
+    uses qh.DROPdim and qh.hull_dim
+    source and destination may be the same
+
+  notes:
+    allocate 4 elements to destination just in case
+*/
+void qh_projectdim3(qhT *qh, pointT *source, pointT *destination) {
+  int i,k;
+
+  for (k=0, i=0; k < qh->hull_dim; k++) {
+    if (qh->hull_dim == 4) {
+      if (k != qh->DROPdim)
+        destination[i++]= source[k];
+    }else if (k == qh->DROPdim)
+      destination[i++]= 0;
+    else
+      destination[i++]= source[k];
+  }
+  while (i < 3)
+    destination[i++]= 0.0;
+} /* projectdim3 */
+
+/*---------------------------------
+
+  qh_readfeasible(qh, dim, curline )
+    read feasible point from current line and qh.fin
+
+  returns:
+    number of lines read from qh.fin
+    sets qh.feasible_point with malloc'd coordinates
+
+  notes:
+    checks for qh.HALFspace
+    assumes dim > 1
+
+  see:
+    qh_setfeasible
+*/
+int qh_readfeasible(qhT *qh, int dim, const char *curline) {
+  boolT isfirst= True;
+  int linecount= 0, tokcount= 0;
+  const char *s;
+  char *t, firstline[qh_MAXfirst+1];
+  coordT *coords, value;
+
+  if (!qh->HALFspace) {
+    qh_fprintf(qh, qh->ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (qh->feasible_string)
+    qh_fprintf(qh, qh->ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
+  if (!(qh->feasible_point= (coordT *)qh_malloc((size_t)dim * sizeof(coordT)))) {
+    qh_fprintf(qh, qh->ferr, 6071, "qhull error: insufficient memory for feasible point\n");
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  coords= qh->feasible_point;
+  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh->fin)))) {
+    if (isfirst)
+      isfirst= False;
+    else
+      linecount++;
+    while (*s) {
+      while (isspace(*s))
+        s++;
+      value= qh_strtod(s, &t);
+      if (s == t)
+        break;
+      s= t;
+      *(coords++)= value;
+      if (++tokcount == dim) {
+        while (isspace(*s))
+          s++;
+        qh_strtod(s, &t);
+        if (s != t) {
+          qh_fprintf(qh, qh->ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
+               s);
+          qh_errexit(qh, qh_ERRinput, NULL, NULL);
+        }
+        return linecount;
+      }
+    }
+  }
+  qh_fprintf(qh, qh->ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
+           tokcount, dim);
+  qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  return 0;
+} /* readfeasible */
+
+/*---------------------------------
+
+  qh_readpoints(qh, numpoints, dimension, ismalloc )
+    read points from qh.fin into qh.first_point, qh.num_points
+    qh.fin is lines of coordinates, one per vertex, first line number of points
+    if 'rbox D4',
+      gives message
+    if qh.ATinfinity,
+      adds point-at-infinity for Delaunay triangulations
+
+  returns:
+    number of points, array of point coordinates, dimension, ismalloc True
+    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
+        and clears qh.PROJECTdelaunay
+    if qh.HALFspace, reads optional feasible point, reads halfspaces,
+        converts to dual.
+
+  for feasible point in "cdd format" in 3-d:
+    3 1
+    coordinates
+    comments
+    begin
+    n 4 real/integer
+    ...
+    end
+
+  notes:
+    dimension will change in qh_initqhull_globals if qh.PROJECTinput
+    uses malloc() since qh_mem not initialized
+    QH11012 FIX: qh_readpoints needs rewriting, too long
+*/
+coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc) {
+  coordT *points, *coords, *infinity= NULL;
+  realT paraboloid, maxboloid= -REALmax, value;
+  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
+  char *s= 0, *t, firstline[qh_MAXfirst+1];
+  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
+  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
+  int tokcount= 0, linecount=0, maxcount, coordcount=0;
+  boolT islong, isfirst= True, wasbegin= False;
+  boolT isdelaunay= qh->DELAUNAY && !qh->PROJECTinput;
+
+  if (qh->CDDinput) {
+    while ((s= fgets(firstline, qh_MAXfirst, qh->fin))) {
+      linecount++;
+      if (qh->HALFspace && linecount == 1 && isdigit(*s)) {
+        dimfeasible= qh_strtol(s, &s);
+        while (isspace(*s))
+          s++;
+        if (qh_strtol(s, &s) == 1)
+          linecount += qh_readfeasible(qh, dimfeasible, s);
+        else
+          dimfeasible= 0;
+      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
+        break;
+      else if (!*qh->rbox_command)
+        strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
+    }
+    if (!s) {
+      qh_fprintf(qh, qh->ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+  }
+  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh->fin))) {
+    linecount++;
+    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
+      wasbegin= True;
+    while (*s) {
+      while (isspace(*s))
+        s++;
+      if (!*s)
+        break;
+      if (!isdigit(*s)) {
+        if (!*qh->rbox_command) {
+          strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
+          firsttext= linecount;
+        }
+        break;
+      }
+      if (!diminput)
+        diminput= qh_strtol(s, &s);
+      else {
+        numinput= qh_strtol(s, &s);
+        if (numinput == 1 && diminput >= 2 && qh->HALFspace && !qh->CDDinput) {
+          linecount += qh_readfeasible(qh, diminput, s); /* checks if ok */
+          dimfeasible= diminput;
+          diminput= numinput= 0;
+        }else
+          break;
+      }
+    }
+  }
+  if (!s) {
+    qh_fprintf(qh, qh->ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (diminput > numinput) {
+    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
+    diminput= numinput;
+    numinput= tempi;
+  }
+  if (diminput < 2) {
+    qh_fprintf(qh, qh->ferr, 6220, "qhull input error: dimension %d (first or smaller number) should be at least 2\n",
+            diminput);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (numinput < 1 || numinput > qh_POINTSmax) {
+    qh_fprintf(qh, qh->ferr, 6411, "qhull input error: expecting between 1 and %d points.  Got %d %d-d points\n",
+      qh_POINTSmax, numinput, diminput);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    /* same error message in qh_initqhull_globals */
+  }
+
+  if (isdelaunay && qh->HALFspace) {
+    qh_fprintf(qh, qh->ferr, 6037, "qhull option error (qh_readpoints): can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    /* otherwise corrupted memory allocations, same error message as in qh_initqhull_globals */
+  }else if (isdelaunay) {
+    qh->PROJECTdelaunay= False;
+    if (qh->CDDinput)
+      *dimension= diminput;
+    else
+      *dimension= diminput+1;
+    *numpoints= numinput;
+    if (qh->ATinfinity)
+      (*numpoints)++;
+  }else if (qh->HALFspace) {
+    *dimension= diminput - 1;
+    *numpoints= numinput;
+    if (diminput < 3) {
+      qh_fprintf(qh, qh->ferr, 6221, "qhull input error: dimension %d (first number, includes offset) should be at least 3 for halfspaces\n",
+            diminput);
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (dimfeasible) {
+      if (dimfeasible != *dimension) {
+        qh_fprintf(qh, qh->ferr, 6222, "qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
+          dimfeasible, diminput);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }
+    }else
+      qh_setfeasible(qh, *dimension);
+  }else {
+    if (qh->CDDinput)
+      *dimension= diminput-1;
+    else
+      *dimension= diminput;
+    *numpoints= numinput;
+  }
+  qh->normal_size= *dimension * (int)sizeof(coordT); /* for tracing with qh_printpoint */
+  if (qh->HALFspace) {
+    qh->half_space= coordp= (coordT *)qh_malloc((size_t)qh->normal_size + sizeof(coordT));
+    if (qh->CDDinput) {
+      offsetp= qh->half_space;
+      normalp= offsetp + 1;
+    }else {
+      normalp= qh->half_space;
+      offsetp= normalp + *dimension;
+    }
+  }
+  qh->maxline= diminput * (qh_REALdigits + 5);
+  maximize_(qh->maxline, 500);
+  qh->line= (char *)qh_malloc((size_t)(qh->maxline+1) * sizeof(char));
+  *ismalloc= True;  /* use malloc since memory not setup */
+  coords= points= qh->temp_malloc=  /* numinput and diminput >=2 by QH6220 */
+        (coordT *)qh_malloc((size_t)((*numpoints)*(*dimension))*sizeof(coordT));
+  if (!coords || !qh->line || (qh->HALFspace && !qh->half_space)) {
+    qh_fprintf(qh, qh->ferr, 6076, "qhull error: insufficient memory to read %d points\n",
+            numinput);
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  if (isdelaunay && qh->ATinfinity) {
+    infinity= points + numinput * (*dimension);
+    for (k= (*dimension) - 1; k--; )
+      infinity[k]= 0.0;
+  }
+  maxcount= numinput * diminput;
+  paraboloid= 0.0;
+  while ((s= (isfirst ?  s : fgets(qh->line, qh->maxline, qh->fin)))) {
+    if (!isfirst) {
+      linecount++;
+      if (*s == 'e' || *s == 'E') {
+        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
+          if (qh->CDDinput )
+            break;
+          else if (wasbegin)
+            qh_fprintf(qh, qh->ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
+        }
+      }
+    }
+    islong= False;
+    while (*s) {
+      while (isspace(*s))
+        s++;
+      value= qh_strtod(s, &t);
+      if (s == t) {
+        if (!*qh->rbox_command)
+         strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
+        if (*s && !firsttext)
+          firsttext= linecount;
+        if (!islong && !firstshort && coordcount)
+          firstshort= linecount;
+        break;
+      }
+      if (!firstpoint)
+        firstpoint= linecount;
+      s= t;
+      if (++tokcount > maxcount)
+        continue;
+      if (qh->HALFspace) {
+        if (qh->CDDinput)
+          *(coordp++)= -value; /* both coefficients and offset */
+        else
+          *(coordp++)= value;
+      }else {
+        *(coords++)= value;
+        if (qh->CDDinput && !coordcount) {
+          if (value != 1.0) {
+            qh_fprintf(qh, qh->ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
+                   linecount);
+            qh_errexit(qh, qh_ERRinput, NULL, NULL);
+          }
+          coords--;
+        }else if (isdelaunay) {
+          paraboloid += value * value;
+          if (qh->ATinfinity) {
+            if (qh->CDDinput)
+              infinity[coordcount-1] += value;
+            else
+              infinity[coordcount] += value;
+          }
+        }
+      }
+      if (++coordcount == diminput) {
+        coordcount= 0;
+        if (isdelaunay) {
+          *(coords++)= paraboloid;
+          maximize_(maxboloid, paraboloid);
+          paraboloid= 0.0;
+        }else if (qh->HALFspace) {
+          if (!qh_sethalfspace(qh, *dimension, coords, &coords, normalp, offsetp, qh->feasible_point)) {
+            qh_fprintf(qh, qh->ferr, 8048, "The halfspace was on line %d\n", linecount);
+            if (wasbegin)
+              qh_fprintf(qh, qh->ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
+            qh_errexit(qh, qh_ERRinput, NULL, NULL);
+          }
+          coordp= qh->half_space;
+        }
+        while (isspace(*s))
+          s++;
+        if (*s) {
+          islong= True;
+          if (!firstlong)
+            firstlong= linecount;
+        }
+      }
+    }
+    if (!islong && !firstshort && coordcount)
+      firstshort= linecount;
+    if (!isfirst && s - qh->line >= qh->maxline) {
+      qh_fprintf(qh, qh->ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
+              linecount, (int) (s - qh->line));   /* WARN64 */
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    isfirst= False;
+  }
+  if (qh->rbox_command[0])
+    qh->rbox_command[strlen(qh->rbox_command)-1]= '\0'; /* remove \n, previous qh_errexit's display command as two lines */
+  if (tokcount != maxcount) {
+    newnum= fmin_(numinput, tokcount/diminput);
+    if (qh->ALLOWshort)
+      qh_fprintf(qh, qh->ferr, 7073, "qhull warning: instead of %d points in %d-d, input contains %d points and %d extra coordinates.\n",
+          numinput, diminput, tokcount/diminput, tokcount % diminput);
+    else
+      qh_fprintf(qh, qh->ferr, 6410, "qhull error: instead of %d points in %d-d, input contains %d points and %d extra coordinates.\n",
+          numinput, diminput, tokcount/diminput, tokcount % diminput);
+    if (firsttext)
+      qh_fprintf(qh, qh->ferr, 8051, "    Line %d is the first comment.\n", firsttext);
+    qh_fprintf(qh, qh->ferr, 8033,   "    Line %d is the first point.\n", firstpoint);
+    if (firstshort)
+      qh_fprintf(qh, qh->ferr, 8052, "    Line %d is the first short line.\n", firstshort);
+    if (firstlong)
+      qh_fprintf(qh, qh->ferr, 8053, "    Line %d is the first long line.\n", firstlong);
+    if (qh->ALLOWshort)
+      qh_fprintf(qh, qh->ferr, 8054, "    Continuing with %d points.\n", newnum);
+    else {
+      qh_fprintf(qh, qh->ferr, 8077, "    Override with option 'Qa' (allow-short)\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    numinput= newnum;
+    if (isdelaunay && qh->ATinfinity) {
+      for (k= tokcount % diminput; k--; )
+        infinity[k] -= *(--coords);
+      *numpoints= newnum+1;
+    }else {
+      coords -= tokcount % diminput;
+      *numpoints= newnum;
+    }
+  }
+  if (isdelaunay && qh->ATinfinity) {
+    for (k= (*dimension) - 1; k--; )
+      infinity[k] /= numinput;
+    if (coords == infinity)
+      coords += (*dimension) -1;
+    else {
+      for (k=0; k < (*dimension) - 1; k++)
+        *(coords++)= infinity[k];
+    }
+    *(coords++)= maxboloid * 1.1;
+  }
+  if (!strcmp(qh->rbox_command, "./rbox D4"))
+    qh_fprintf(qh, qh->ferr, 8055, "\n\
+This is the qhull test case.  If any errors or core dumps occur,\n\
+recompile qhull with 'make new'.  If errors still occur, there is\n\
+an incompatibility.  You should try a different compiler.  You can also\n\
+change the choices in user_r.h.  If you discover the source of the problem,\n\
+please send mail to qhull_bug@qhull.org.\n\
+\n\
+Type 'qhull' for a short list of options.\n");
+  qh_free(qh->line);
+  qh->line= NULL;
+  if (qh->half_space) {
+    qh_free(qh->half_space);
+    qh->half_space= NULL;
+  }
+  qh->temp_malloc= NULL;
+  trace1((qh, qh->ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
+          numinput, diminput));
+  return(points);
+} /* readpoints */
+
+
+/*---------------------------------
+
+  qh_setfeasible(qh, dim )
+    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
+
+  notes:
+    "n,n,n" already checked by qh_initflags()
+    see qh_readfeasible()
+    called only once from qh_new_qhull, otherwise leaks memory
+*/
+void qh_setfeasible(qhT *qh, int dim) {
+  int tokcount= 0;
+  char *s;
+  coordT *coords, value;
+
+  if (!(s= qh->feasible_string)) {
+    qh_fprintf(qh, qh->ferr, 6223, "qhull input error: halfspace intersection needs a feasible point.  Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (!(qh->feasible_point= (pointT *)qh_malloc((size_t)dim * sizeof(coordT)))) {
+    qh_fprintf(qh, qh->ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
+    qh_errexit(qh, qh_ERRmem, NULL, NULL);
+  }
+  coords= qh->feasible_point;
+  while (*s) {
+    value= qh_strtod(s, &s);
+    if (++tokcount > dim) {
+      qh_fprintf(qh, qh->ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
+          qh->feasible_string, dim);
+      break;
+    }
+    *(coords++)= value;
+    if (*s)
+      s++;
+  }
+  while (++tokcount <= dim)
+    *(coords++)= 0.0;
+} /* setfeasible */
+
+/*---------------------------------
+
+  qh_skipfacet(qh, facet )
+    returns 'True' if this facet is not to be printed
+
+  notes:
+    based on the user provided slice thresholds and 'good' specifications
+*/
+boolT qh_skipfacet(qhT *qh, facetT *facet) {
+  facetT *neighbor, **neighborp;
+
+  if (qh->PRINTneighbors) {
+    if (facet->good)
+      return !qh->PRINTgood;
+    FOREACHneighbor_(facet) {
+      if (neighbor->good)
+        return False;
+    }
+    return True;
+  }else if (qh->PRINTgood)
+    return !facet->good;
+  else if (!facet->normal)
+    return True;
+  return(!qh_inthresholds(qh, facet->normal, NULL));
+} /* skipfacet */
+
+/*---------------------------------
+
+  qh_skipfilename(qh, string )
+    returns pointer to character after filename
+
+  notes:
+    skips leading spaces
+    ends with spacing or eol
+    if starts with ' or " ends with the same, skipping \' or \"
+    For qhull, qh_argv_to_command() only uses double quotes
+*/
+char *qh_skipfilename(qhT *qh, char *filename) {
+  char *s= filename;  /* non-const due to return */
+  char c;
+
+  while (*s && isspace(*s))
+    s++;
+  c= *s++;
+  if (c == '\0') {
+    qh_fprintf(qh, qh->ferr, 6204, "qhull input error: filename expected, none found.\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  if (c == '\'' || c == '"') {
+    while (*s !=c || s[-1] == '\\') {
+      if (!*s) {
+        qh_fprintf(qh, qh->ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }
+      s++;
+    }
+    s++;
+  }
+  else while (*s && !isspace(*s))
+      s++;
+  return s;
+} /* skipfilename */
+
diff --git a/vendor/qhull/libqhull_r/io_r.h b/vendor/qhull/libqhull_r/io_r.h
new file mode 100644
index 0000000..eb3c751
--- /dev/null
+++ b/vendor/qhull/libqhull_r/io_r.h
@@ -0,0 +1,166 @@
+/*
  ---------------------------------
+
+   io_r.h
+   declarations of Input/Output functions
+
+   see README, libqhull_r.h and io_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/io_r.h#3 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFio
+#define qhDEFio 1
+
+#include "libqhull_r.h"
+
+/*============ constants and flags ==================*/
+
+/*----------------------------------
+
+  qh_MAXfirst
+    maximum length of first two lines of stdin
+*/
+#define qh_MAXfirst  200
+
+/*----------------------------------
+
+  qh_MINradius
+    min radius for Gp and Gv, fraction of maxcoord
+*/
+#define qh_MINradius 0.02
+
+/*----------------------------------
+
+  qh_GEOMepsilon
+    adjust outer planes for 'lines closer' and geomview roundoff.
+    This prevents bleed through.
+*/
+#define qh_GEOMepsilon 2e-3
+
+/*----------------------------------
+
+  qh_WHITESPACE
+    possible values of white space
+*/
+#define qh_WHITESPACE " \n\t\v\r\f"
+
+
+/*----------------------------------
+
+  qh_RIDGE
+    to select which ridges to print in qh_eachvoronoi
+*/
+typedef enum
+{
+    qh_RIDGEall= 0, qh_RIDGEinner, qh_RIDGEouter
+}
+qh_RIDGE;
+
+/*----------------------------------
+
+  printvridgeT
+    prints results of qh_printvdiagram
+
+  see:
+    qh_printvridge for an example
+*/
+typedef void (*printvridgeT)(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
+
+/*============== -prototypes in alphabetical order =========*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void    qh_dfacet(qhT *qh, unsigned int id);
+void    qh_dvertex(qhT *qh, unsigned int id);
+int     qh_compare_facetarea(const void *p1, const void *p2);
+int     qh_compare_facetvisit(const void *p1, const void *p2);
+int     qh_compare_nummerge(const void *p1, const void *p2);
+void    qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length);
+void    qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
+              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
+              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
+pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
+setT   *qh_detvridge(qhT *qh, vertexT *vertex);
+setT   *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex);
+int     qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
+int     qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
+void    qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist);
+setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
+void    qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
+void    qh_markkeep(qhT *qh, facetT *facetlist);
+setT   *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
+void    qh_order_vertexneighbors(qhT *qh, vertexT *vertex);
+void    qh_prepare_output(qhT *qh);
+void    qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
+void    qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet);
+void    qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius);
+void    qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *num, boolT printall);
+void    qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printfacet(qhT *qh, FILE *fp, facetT *facet);
+void    qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
+void    qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
+void    qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
+                               facetT *facet, realT offset, realT color[3]);
+void    qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
+void    qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
+void    qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
+void    qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
+void    qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
+void    qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
+void    qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
+void    qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format);
+void    qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
+void    qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet);
+void    qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet);
+void    qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
+                   setT *vertices, realT color[3]);
+void    qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
+void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
+void    qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point);
+void    qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id);
+void    qh_printpoint3(qhT *qh, FILE *fp, pointT *point);
+void    qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
+void    qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
+void    qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge);
+void    qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius);
+void    qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
+int     qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
+void    qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex);
+void    qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
+                         setT *facets, boolT printall);
+void    qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices);
+void    qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall);
+void    qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
+void    qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
+void    qh_produce_output(qhT *qh);
+void    qh_produce_output2(qhT *qh);
+void    qh_projectdim3(qhT *qh, pointT *source, pointT *destination);
+int     qh_readfeasible(qhT *qh, int dim, const char *curline);
+coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
+void    qh_setfeasible(qhT *qh, int dim);
+boolT   qh_skipfacet(qhT *qh, facetT *facet);
+char   *qh_skipfilename(qhT *qh, char *filename);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFio */
diff --git a/vendor/qhull/libqhull_r/libqhull_r.c b/vendor/qhull/libqhull_r/libqhull_r.c
new file mode 100644
index 0000000..0d41d7b
--- /dev/null
+++ b/vendor/qhull/libqhull_r/libqhull_r.c
@@ -0,0 +1,1754 @@
+/*
  ---------------------------------
+
+   libqhull_r.c
+   Quickhull algorithm for convex hulls
+
+   qhull() and top-level routines
+
+   see qh-qhull_r.htm, libqhull_r.h, unix_r.c
+
+   see qhull_ra.h for internal functions
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.c#17 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+/*============= functions in alphabetic order after qhull() =======*/
+
+/*---------------------------------
+
+  qh_qhull(qh)
+    compute DIM3 convex hull of qh.num_points starting at qh.first_point
+    qh->contains all global options and variables
+
+  returns:
+    returns polyhedron
+      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
+
+    returns global variables
+      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
+
+    returns precision constants
+      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
+
+  notes:
+    unless needed for output
+      qh.max_vertex and qh.min_vertex are max/min due to merges
+
+  see:
+    to add individual points to either qh.num_points
+      use qh_addpoint()
+
+    if qh.GETarea
+      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
+
+  design:
+    record starting time
+    initialize hull and partition points
+    build convex hull
+    unless early termination
+      update facet->maxoutside for vertices, coplanar, and near-inside points
+    error if temporary sets exist
+    record end time
+*/
+
+void qh_qhull(qhT *qh) {
+  int numoutside;
+
+  qh->hulltime= qh_CPUclock;
+  if (qh->RERUN || qh->JOGGLEmax < REALmax/2)
+    qh_build_withrestart(qh);
+  else {
+    qh_initbuild(qh);
+    qh_buildhull(qh);
+  }
+  if (!qh->STOPadd && !qh->STOPcone && !qh->STOPpoint) {
+    if (qh->ZEROall_ok && !qh->TESTvneighbors && qh->MERGEexact)
+      qh_checkzero(qh, qh_ALL);
+    if (qh->ZEROall_ok && !qh->TESTvneighbors && !qh->WAScoplanar) {
+      trace2((qh, qh->ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
+      qh->DOcheckmax= False;
+    }else {
+      qh_initmergesets(qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */);
+      if (qh->MERGEexact || (qh->hull_dim > qh_DIMreduceBuild && qh->PREmerge))
+        qh_postmerge(qh, "First post-merge", qh->premerge_centrum, qh->premerge_cos,
+             (qh->POSTmerge ? False : qh->TESTvneighbors)); /* calls qh_reducevertices */
+      else if (!qh->POSTmerge && qh->TESTvneighbors)
+        qh_postmerge(qh, "For testing vertex neighbors", qh->premerge_centrum,
+             qh->premerge_cos, True);                       /* calls qh_test_vneighbors */
+      if (qh->POSTmerge)
+        qh_postmerge(qh, "For post-merging", qh->postmerge_centrum,
+             qh->postmerge_cos, qh->TESTvneighbors);
+      if (qh->visible_list == qh->facet_list) {            /* qh_postmerge was called */
+        qh->findbestnew= True;
+        qh_partitionvisible(qh, !qh_ALL, &numoutside /* qh.visible_list */);
+        qh->findbestnew= False;
+        qh_deletevisible(qh /* qh.visible_list */);        /* stops at first !f.visible */
+        qh_resetlists(qh, False, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+      }
+      qh_all_vertexmerges(qh, -1, NULL, NULL);
+      qh_freemergesets(qh);
+    }
+    if (qh->TRACEpoint == qh_IDunknown && qh->TRACElevel > qh->IStracing) {
+      qh->IStracing= qh->TRACElevel;
+      qh_fprintf(qh, qh->ferr, 2112, "qh_qhull: finished qh_buildhull and qh_postmerge, start tracing (TP-1)\n");
+    }
+    if (qh->DOcheckmax){
+      if (qh->REPORTfreq) {
+        qh_buildtracing(qh, NULL, NULL);
+        qh_fprintf(qh, qh->ferr, 8115, "\nTesting all coplanar points.\n");
+      }
+      qh_check_maxout(qh);
+    }
+    if (qh->KEEPnearinside && !qh->maxoutdone)
+      qh_nearcoplanar(qh);
+  }
+  if (qh_setsize(qh, qh->qhmem.tempstack) != 0) {
+    qh_fprintf(qh, qh->ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d) at end of Qhull\n",
+             qh_setsize(qh, qh->qhmem.tempstack));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->hulltime= qh_CPUclock - qh->hulltime;
+  qh->QHULLfinished= True;
+  trace1((qh, qh->ferr, 1036, "Qhull: algorithm completed\n"));
+} /* qhull */
+
+/*---------------------------------
+
+  qh_addpoint(qh, furthest, facet, checkdist )
+    add point (usually furthest point) above facet to hull
+    if checkdist,
+      check that point is above facet.
+      if point is not outside of the hull, uses qh_partitioncoplanar()
+      assumes that facet is defined by qh_findbestfacet()
+    else if facet specified,
+      assumes that point is above facet (major damage if below)
+    for Delaunay triangulations,
+      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
+      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
+
+  returns:
+    returns False if user requested an early termination
+      qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
+    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
+    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
+    if unknown point, adds a pointer to qh.other_points
+      do not deallocate the point's coordinates
+
+  notes:
+    called from qh_initbuild, qh_buildhull, and qh_addpoint
+    tail recursive call if merged a pinchedvertex due to a duplicated ridge
+      no more than qh.num_vertices calls (QH6296)
+    assumes point is near its best facet and not at a local minimum of a lens
+      distributions.  Use qh_findbestfacet to avoid this case.
+    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
+    if called from a user application after qh_qhull and 'QJ' (joggle),
+      facet merging for precision problems is disabled by default
+
+  design:
+    exit if qh.STOPadd vertices 'TAn'
+    add point to other_points if needed
+    if checkdist
+      if point not above facet
+        partition coplanar point
+        exit
+    exit if pre STOPpoint requested
+    find horizon and visible facets for point
+    build cone of new facets to the horizon
+    exit if build cone fails due to qh.ONLYgood
+    tail recursive call if build cone fails due to pinched vertices
+    exit if STOPcone requested
+    merge non-convex new facets
+    if merge found, many merges, or 'Qf'
+       use qh_findbestnew() instead of qh_findbest()
+    partition outside points from visible facets
+    delete visible facets
+    check polyhedron if requested
+    exit if post STOPpoint requested
+    reset working lists of facets and vertices
+*/
+boolT qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist) {
+  realT dist, pbalance;
+  facetT *replacefacet, *newfacet;
+  vertexT *apex;
+  boolT isoutside= False;
+  int numpart, numpoints, goodvisible, goodhorizon, apexpointid;
+
+  qh->maxoutdone= False;
+  if (qh_pointid(qh, furthest) == qh_IDunknown)
+    qh_setappend(qh, &qh->other_points, furthest);
+  if (!facet) {
+    qh_fprintf(qh, qh->ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh_detmaxoutside(qh);
+  if (checkdist) {
+    facet= qh_findbest(qh, furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
+                        &dist, &isoutside, &numpart);
+    zzadd_(Zpartition, numpart);
+    if (!isoutside) {
+      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
+      facet->notfurthest= True;
+      qh_partitioncoplanar(qh, furthest, facet, &dist, qh->findbestnew);
+      return True;
+    }
+  }
+  qh_buildtracing(qh, furthest, facet);
+  if (qh->STOPpoint < 0 && qh->furthest_id == -qh->STOPpoint-1) {
+    facet->notfurthest= True;
+    return False;
+  }
+  qh_findhorizon(qh, furthest, facet, &goodvisible, &goodhorizon);
+  if (qh->ONLYgood && !qh->GOODclosest && !(goodvisible+goodhorizon)) {
+    zinc_(Znotgood);
+    facet->notfurthest= True;
+    /* last point of outsideset is no longer furthest.  This is ok
+        since all points of the outside are likely to be bad */
+    qh_resetlists(qh, False, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+    return True;
+  }
+  apex= qh_buildcone(qh, furthest, facet, goodhorizon, &replacefacet);
+  /* qh.newfacet_list, visible_list, newvertex_list */
+  if (!apex) {
+    if (qh->ONLYgood)
+      return True; /* ignore this furthest point, a good new facet was not found */
+    if (replacefacet) {
+      if (qh->retry_addpoint++ >= qh->num_vertices) {
+        qh_fprintf(qh, qh->ferr, 6296, "qhull internal error (qh_addpoint): infinite loop (%d retries) of merging pinched vertices due to dupridge for point p%d, facet f%d, and %d vertices\n",
+          qh->retry_addpoint, qh_pointid(qh, furthest), facet->id, qh->num_vertices);
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+      }
+      /* retry qh_addpoint after resolving a dupridge via qh_merge_pinchedvertices */
+      return qh_addpoint(qh, furthest, replacefacet, True /* checkdisk */);
+    }
+    qh->retry_addpoint= 0;
+    return True; /* ignore this furthest point, resolved a dupridge by making furthest a coplanar point */
+  }
+  if (qh->retry_addpoint) {
+    zinc_(Zretryadd);
+    zadd_(Zretryaddtot, qh->retry_addpoint);
+    zmax_(Zretryaddmax, qh->retry_addpoint);
+    qh->retry_addpoint= 0;
+  }
+  apexpointid= qh_pointid(qh, apex->point);
+  zzinc_(Zprocessed);
+  if (qh->STOPcone && qh->furthest_id == qh->STOPcone-1) {
+    facet->notfurthest= True;
+    return False;  /* visible_list etc. still defined */
+  }
+  qh->findbestnew= False;
+  if (qh->PREmerge || qh->MERGEexact) {
+    qh_initmergesets(qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */);
+    qh_premerge(qh, apexpointid, qh->premerge_centrum, qh->premerge_cos /* qh.newfacet_list */);
+    if (qh_USEfindbestnew)
+      qh->findbestnew= True;
+    else {
+      FORALLnew_facets {
+        if (!newfacet->simplicial) {
+          qh->findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
+          break;
+        }
+      }
+    }
+  }else if (qh->BESToutside)
+    qh->findbestnew= True;
+  if (qh->IStracing >= 4)
+    qh_checkpolygon(qh, qh->visible_list);
+  qh_partitionvisible(qh, !qh_ALL, &numpoints /* qh.visible_list */);
+  qh->findbestnew= False;
+  qh->findbest_notsharp= False;
+  zinc_(Zpbalance);
+  pbalance= numpoints - (realT) qh->hull_dim /* assumes all points extreme */
+                * (qh->num_points - qh->num_vertices)/qh->num_vertices;
+  wadd_(Wpbalance, pbalance);
+  wadd_(Wpbalance2, pbalance * pbalance);
+  qh_deletevisible(qh /* qh.visible_list */);
+  zmax_(Zmaxvertex, qh->num_vertices);
+  qh->NEWfacets= False;
+  if (qh->IStracing >= 4) {
+    if (qh->num_facets < 200)
+      qh_printlists(qh);
+    qh_printfacetlist(qh, qh->newfacet_list, NULL, True);
+    qh_checkpolygon(qh, qh->facet_list);
+  }else if (qh->CHECKfrequently) {
+    if (qh->num_facets < 1000)
+      qh_checkpolygon(qh, qh->facet_list);
+    else
+      qh_checkpolygon(qh, qh->newfacet_list);
+  }
+  if (qh->STOPpoint > 0 && qh->furthest_id == qh->STOPpoint-1 && qh_setsize(qh, qh->vertex_mergeset) > 0)
+    return False;
+  qh_resetlists(qh, True, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+  if (qh->facet_mergeset) {
+    /* vertex merges occur after facet merges (qh_premerge) and qh_resetlists */
+    qh_all_vertexmerges(qh, apexpointid, NULL, NULL);
+    qh_freemergesets(qh);
+  }
+  /* qh_triangulate(qh); to test qh.TRInormals */
+  if (qh->STOPpoint > 0 && qh->furthest_id == qh->STOPpoint-1)
+    return False;
+  trace2((qh, qh->ferr, 2056, "qh_addpoint: added p%d to convex hull with point balance %2.2g\n",
+    qh_pointid(qh, furthest), pbalance));
+  return True;
+} /* addpoint */
+
+/*---------------------------------
+
+  qh_build_withrestart(qh)
+    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
+       qh_errexit always undoes qh_build_withrestart()
+    qh.FIRSTpoint/qh.NUMpoints is point array
+       it may be moved by qh_joggleinput
+*/
+void qh_build_withrestart(qhT *qh) {
+  int restart;
+  vertexT *vertex, **vertexp;
+
+  qh->ALLOWrestart= True;
+  while (True) {
+    restart= setjmp(qh->restartexit); /* simple statement for CRAY J916 */
+    if (restart) {       /* only from qh_joggle_restart() */
+      qh->last_errcode= qh_ERRnone;
+      zzinc_(Zretry);
+      wmax_(Wretrymax, qh->JOGGLEmax);
+      /* QH7078 warns about using 'TCn' with 'QJn' */
+      qh->STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
+      FOREACHvertex_(qh->del_vertices) {
+        if (vertex->point && !vertex->partitioned)
+          vertex->partitioned= True; /* avoid error in qh_freebuild -> qh_delvertex */
+      }
+    }
+    if (!qh->RERUN && qh->JOGGLEmax < REALmax/2) {
+      if (qh->build_cnt > qh_JOGGLEmaxretry) {
+        qh_fprintf(qh, qh->ferr, 6229, "qhull input error: %d attempts to construct a convex hull with joggled input.  Increase joggle above 'QJ%2.2g' or modify qh_JOGGLE... parameters in user_r.h\n",
+           qh->build_cnt, qh->JOGGLEmax);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }
+      if (qh->build_cnt && !restart)
+        break;
+    }else if (qh->build_cnt && qh->build_cnt >= qh->RERUN)
+      break;
+    qh->STOPcone= 0;
+    qh_freebuild(qh, True);  /* first call is a nop */
+    qh->build_cnt++;
+    if (!qh->qhull_optionsiz)
+      qh->qhull_optionsiz= (int)strlen(qh->qhull_options);   /* WARN64 */
+    else {
+      qh->qhull_options[qh->qhull_optionsiz]= '\0';
+      qh->qhull_optionlen= qh_OPTIONline;  /* starts a new line */
+    }
+    qh_option(qh, "_run", &qh->build_cnt, NULL);
+    if (qh->build_cnt == qh->RERUN) {
+      qh->IStracing= qh->TRACElastrun;  /* duplicated from qh_initqhull_globals */
+      if (qh->TRACEpoint != qh_IDnone || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
+        qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
+        qh->IStracing= 0;
+      }
+      qh->qhmem.IStracing= qh->IStracing;
+    }
+    if (qh->JOGGLEmax < REALmax/2)
+      qh_joggleinput(qh);
+    qh_initbuild(qh);
+    qh_buildhull(qh);
+    if (qh->JOGGLEmax < REALmax/2 && !qh->MERGING)
+      qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
+  }
+  qh->ALLOWrestart= False;
+} /* qh_build_withrestart */
+
+/*---------------------------------
+
+  qh_buildcone(qh, furthest, facet, goodhorizon, &replacefacet )
+    build cone of new facets from furthest to the horizon
+    goodhorizon is count of good, horizon facets from qh_find_horizon
+
+  returns:
+    returns apex of cone with qh.newfacet_list and qh.first_newfacet (f.id)
+    returns NULL if qh.ONLYgood and no good facets
+    returns NULL and retryfacet if merging pinched vertices will resolve a dupridge
+      a horizon vertex was nearly adjacent to another vertex
+      will retry qh_addpoint
+    returns NULL if resolve a dupridge by making furthest a coplanar point
+      furthest was nearly adjacent to an existing vertex
+    updates qh.degen_mergeset (MRGridge) if resolve a dupridge by merging facets
+    updates qh.newfacet_list, visible_list, newvertex_list
+    updates qh.facet_list, vertex_list, num_facets, num_vertices
+
+  notes:
+    called by qh_addpoint
+    see qh_triangulate, it triangulates non-simplicial facets in post-processing
+
+  design:
+    make new facets for point to horizon
+    compute balance statistics
+    make hyperplanes for point
+    exit if qh.ONLYgood and not good (qh_buildcone_onlygood)
+    match neighboring new facets
+    if dupridges
+      exit if !qh.IGNOREpinched and dupridge resolved by coplanar furthest
+      retry qh_buildcone if !qh.IGNOREpinched and dupridge resolved by qh_buildcone_mergepinched
+      otherwise dupridges resolved by merging facets
+    update vertex neighbors and delete interior vertices
+*/
+vertexT *qh_buildcone(qhT *qh, pointT *furthest, facetT *facet, int goodhorizon, facetT **retryfacet) {
+  vertexT *apex;
+  realT newbalance;
+  int numnew;
+
+  *retryfacet= NULL;
+  qh->first_newfacet= qh->facet_id;
+  qh->NEWtentative= (qh->MERGEpinched || qh->ONLYgood); /* cleared by qh_attachnewfacets or qh_resetlists */
+  apex= qh_makenewfacets(qh, furthest /* qh.newfacet_list visible_list, attaches new facets if !qh.NEWtentative */);
+  numnew= (int)(qh->facet_id - qh->first_newfacet);
+  newbalance= numnew - (realT)(qh->num_facets - qh->num_visible) * qh->hull_dim / qh->num_vertices;
+  /* newbalance statistics updated below if the new facets are accepted */
+  if (qh->ONLYgood) { /* qh.MERGEpinched is false by QH6362 */
+    if (!qh_buildcone_onlygood(qh, apex, goodhorizon /* qh.newfacet_list */)) {
+      facet->notfurthest= True;
+      return NULL;
+    }
+  }else if(qh->MERGEpinched) {
+#ifndef qh_NOmerge
+    if (qh_buildcone_mergepinched(qh, apex, facet, retryfacet /* qh.newfacet_list */))
+      return NULL;
+#else
+    qh_fprintf(qh, qh->ferr, 6375, "qhull option error (qh_buildcone): option 'Q14' (qh.MERGEpinched) is not available due to qh_NOmerge\n");
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+#endif
+  }else {
+    /* qh_makenewfacets attached new facets to the horizon */
+    qh_matchnewfacets(qh); /* ignore returned value.  qh_forcedmerges will merge dupridges if any */
+    qh_makenewplanes(qh /* qh.newfacet_list */);
+    qh_update_vertexneighbors_cone(qh);
+  }
+  wadd_(Wnewbalance, newbalance);
+  wadd_(Wnewbalance2, newbalance * newbalance);
+  trace2((qh, qh->ferr, 2067, "qh_buildcone: created %d newfacets for p%d(v%d) new facet balance %2.2g\n",
+    numnew, qh_pointid(qh, furthest), apex->id, newbalance));
+  return apex;
+} /* buildcone */
+
+#ifndef qh_NOmerge
+/*---------------------------------
+
+  qh_buildcone_mergepinched(qh, apex, facet, maxdupdist, &retryfacet )
+    build cone of new facets from furthest to the horizon
+    maxdupdist>0.0 for merging dupridges (qh_matchdupridge)
+
+  returns:
+    returns True if merged a pinched vertex and deleted the cone of new facets
+      if retryfacet is set
+        a dupridge was resolved by qh_merge_pinchedvertices
+        retry qh_addpoint
+      otherwise
+        apex/furthest was partitioned as a coplanar point
+        ignore this furthest point
+    returns False if no dupridges or if dupridges will be resolved by MRGridge
+    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
+
+  notes:
+    only called from qh_buildcone with qh.MERGEpinched
+
+  design:
+    match neighboring new facets
+    if matching detected dupridges with a wide merge (qh_RATIOtrypinched)
+      if pinched vertices (i.e., nearly adjacent)
+        delete the cone of new facets
+        delete the apex and reset the facet lists
+        if coplanar, pinched apex
+          partition the apex as a coplanar point
+        else
+           repeatedly merge the nearest pair of pinched vertices and subsequent facet merges
+        return True
+      otherwise
+        MRGridge are better than vertex merge, but may report an error
+    attach new facets
+    make hyperplanes for point
+    update vertex neighbors and delete interior vertices
+*/
+boolT qh_buildcone_mergepinched(qhT *qh, vertexT *apex, facetT *facet, facetT **retryfacet) {
+  facetT *newfacet, *nextfacet;
+  pointT *apexpoint;
+  coordT maxdupdist;
+  int apexpointid;
+  boolT iscoplanar;
+
+  *retryfacet= NULL;
+  maxdupdist= qh_matchnewfacets(qh);
+  if (maxdupdist > qh_RATIOtrypinched * qh->ONEmerge) { /* one or more dupridges with a wide merge */
+    if (qh->IStracing >= 4 && qh->num_facets < 1000)
+      qh_printlists(qh);
+    qh_initmergesets(qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */);
+    if (qh_getpinchedmerges(qh, apex, maxdupdist, &iscoplanar /* qh.newfacet_list, qh.vertex_mergeset */)) {
+      for (newfacet=qh->newfacet_list; newfacet && newfacet->next; newfacet= nextfacet) {
+        nextfacet= newfacet->next;
+        qh_delfacet(qh, newfacet);
+      }
+      apexpoint= apex->point;
+      apexpointid= qh_pointid(qh, apexpoint);
+      qh_delvertex(qh, apex);
+      qh_resetlists(qh, False, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+      if (iscoplanar) {
+        zinc_(Zpinchedapex);
+        facet->notfurthest= True;
+        qh_partitioncoplanar(qh, apexpoint, facet, NULL, qh->findbestnew);
+      }else {
+        qh_all_vertexmerges(qh, apexpointid, facet, retryfacet);
+      }
+      qh_freemergesets(qh); /* errors if not empty */
+      return True;
+    }
+    /* MRGridge are better than vertex merge, but may report an error */
+    qh_freemergesets(qh);
+  }
+  qh_attachnewfacets(qh /* qh.visible_list */);
+  qh_makenewplanes(qh /* qh.newfacet_list */);
+  qh_update_vertexneighbors_cone(qh);
+  return False;
+} /* buildcone_mergepinched */
+#endif /* !qh_NOmerge */
+
+/*---------------------------------
+
+  qh_buildcone_onlygood(qh, apex, goodhorizon )
+    build cone of good, new facets from apex and its qh.newfacet_list to the horizon
+    goodhorizon is count of good, horizon facets from qh_find_horizon
+
+  returns:
+    False if a f.good facet or a qh.GOODclosest facet is not found
+    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
+
+  notes:
+    called from qh_buildcone
+    QH11030 FIX: Review effect of qh.GOODclosest on qh_buildcone_onlygood ('Qg').  qh_findgood preserves old value if didn't find a good facet.  See qh_findgood_all for disabling
+
+  design:
+    make hyperplanes for point
+    if qh_findgood fails to find a f.good facet or a qh.GOODclosest facet
+      delete cone of new facets
+      return NULL (ignores apex)
+    else
+      attach cone to horizon
+      match neighboring new facets
+*/
+boolT qh_buildcone_onlygood(qhT *qh, vertexT *apex, int goodhorizon) {
+  facetT *newfacet, *nextfacet;
+
+  qh_makenewplanes(qh /* qh.newfacet_list */);
+  if(qh_findgood(qh, qh->newfacet_list, goodhorizon) == 0) {
+    if (!qh->GOODclosest) {
+      for (newfacet=qh->newfacet_list; newfacet && newfacet->next; newfacet= nextfacet) {
+        nextfacet= newfacet->next;
+        qh_delfacet(qh, newfacet);
+      }
+      qh_delvertex(qh, apex);
+      qh_resetlists(qh, False /*no stats*/, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+      zinc_(Znotgoodnew);
+      /* !good outside points dropped from hull */
+      return False;
+    }
+  }
+  qh_attachnewfacets(qh /* qh.visible_list */);
+  qh_matchnewfacets(qh); /* ignore returned value.  qh_forcedmerges will merge dupridges if any */
+  qh_update_vertexneighbors_cone(qh);
+  return True;
+} /* buildcone_onlygood */
+
+/*---------------------------------
+
+  qh_buildhull(qh)
+    construct a convex hull by adding outside points one at a time
+
+  returns:
+
+  notes:
+    may be called multiple times
+    checks facet and vertex lists for incorrect flags
+    to recover from STOPcone, call qh_deletevisible and qh_resetlists
+
+  design:
+    check visible facet and newfacet flags
+    check newfacet vertex flags and qh.STOPcone/STOPpoint
+    for each facet with a furthest outside point
+      add point to facet
+      exit if qh.STOPcone or qh.STOPpoint requested
+    if qh.NARROWhull for initial simplex
+      partition remaining outside points to coplanar sets
+*/
+void qh_buildhull(qhT *qh) {
+  facetT *facet;
+  pointT *furthest;
+  vertexT *vertex;
+  int id;
+
+  trace1((qh, qh->ferr, 1037, "qh_buildhull: start build hull\n"));
+  FORALLfacets {
+    if (facet->visible || facet->newfacet) {
+      qh_fprintf(qh, qh->ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
+                   facet->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+  }
+  FORALLvertices {
+    if (vertex->newfacet) {
+      qh_fprintf(qh, qh->ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
+                   vertex->id);
+      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    id= qh_pointid(qh, vertex->point);
+    if ((qh->STOPpoint>0 && id == qh->STOPpoint-1) ||
+        (qh->STOPpoint<0 && id == -qh->STOPpoint-1) ||
+        (qh->STOPcone>0 && id == qh->STOPcone-1)) {
+      trace1((qh, qh->ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
+      return;
+    }
+  }
+  qh->facet_next= qh->facet_list;      /* advance facet when processed */
+  while ((furthest= qh_nextfurthest(qh, &facet))) {
+    qh->num_outside--;  /* if ONLYmax, furthest may not be outside */
+    if (qh->STOPadd>0 && (qh->num_vertices - qh->hull_dim - 1 >= qh->STOPadd - 1)) {
+      trace1((qh, qh->ferr, 1059, "qh_buildhull: stop after adding %d vertices\n", qh->STOPadd-1));
+      return;
+    }
+    if (!qh_addpoint(qh, furthest, facet, qh->ONLYmax))
+      break;
+  }
+  if (qh->NARROWhull) /* move points from outsideset to coplanarset */
+    qh_outcoplanar(qh /* facet_list */ );
+  if (qh->num_outside && !furthest) {
+    qh_fprintf(qh, qh->ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh->num_outside);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  trace1((qh, qh->ferr, 1039, "qh_buildhull: completed the hull construction\n"));
+} /* buildhull */
+
+
+/*---------------------------------
+
+  qh_buildtracing(qh, furthest, facet )
+    trace an iteration of qh_buildhull() for furthest point and facet
+    if !furthest, prints progress message
+
+  returns:
+    tracks progress with qh.lastreport, lastcpu, lastfacets, lastmerges, lastplanes, lastdist
+    updates qh.furthest_id (-3 if furthest is NULL)
+    also resets visit_id, vertext_visit on wrap around
+
+  see:
+    qh_tracemerging()
+
+  design:
+    if !furthest
+      print progress message
+      exit
+    if 'TFn' iteration
+      print progress message
+    else if tracing
+      trace furthest point and facet
+    reset qh.visit_id and qh.vertex_visit if overflow may occur
+    set qh.furthest_id for tracing
+*/
+void qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet) {
+  realT dist= 0;
+  double cpu;
+  int total, furthestid;
+  time_t timedata;
+  struct tm *tp;
+  vertexT *vertex;
+
+  qh->old_randomdist= qh->RANDOMdist;
+  qh->RANDOMdist= False;
+  if (!furthest) {
+    time(&timedata);
+    tp= localtime(&timedata);
+    cpu= (double)qh_CPUclock - (double)qh->hulltime;
+    cpu /= (double)qh_SECticks;
+    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
+    qh_fprintf(qh, qh->ferr, 8118, "\n\
+At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
+ The current hull contains %d facets and %d vertices.  Last point was p%d\n",
+      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
+      total, qh->num_facets, qh->num_vertices, qh->furthest_id);
+    return;
+  }
+  furthestid= qh_pointid(qh, furthest);
+#ifndef qh_NOtrace
+  if (qh->TRACEpoint == furthestid) {
+    trace1((qh, qh->ferr, 1053, "qh_buildtracing: start trace T%d for point TP%d above facet f%d\n", qh->TRACElevel, furthestid, facet->id));
+    qh->IStracing= qh->TRACElevel;
+    qh->qhmem.IStracing= qh->TRACElevel;
+  }else if (qh->TRACEpoint != qh_IDnone && qh->TRACEdist < REALmax/2) {
+    qh->IStracing= 0;
+    qh->qhmem.IStracing= 0;
+  }
+#endif
+  if (qh->REPORTfreq && (qh->facet_id-1 > qh->lastreport + (unsigned int)qh->REPORTfreq)) {
+    qh->lastreport= qh->facet_id-1;
+    time(&timedata);
+    tp= localtime(&timedata);
+    cpu= (double)qh_CPUclock - (double)qh->hulltime;
+    cpu /= (double)qh_SECticks;
+    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
+    zinc_(Zdistio);
+    qh_distplane(qh, furthest, facet, &dist);
+    qh_fprintf(qh, qh->ferr, 8119, "\n\
+At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
+ The current hull contains %d facets and %d vertices.  There are %d\n\
+ outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
+      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
+      total, qh->num_facets, qh->num_vertices, qh->num_outside+1,
+      furthestid, qh->vertex_id, dist, getid_(facet));
+  }else if (qh->IStracing >=1) {
+    cpu= (double)qh_CPUclock - (double)qh->hulltime;
+    cpu /= (double)qh_SECticks;
+    qh_distplane(qh, furthest, facet, &dist);
+    qh_fprintf(qh, qh->ferr, 1049, "qh_addpoint: add p%d(v%d) %2.2g above f%d to hull of %d facets, %d merges, %d outside at %4.4g CPU secs.  Previous p%d(v%d) delta %4.4g CPU, %d facets, %d merges, %d hyperplanes, %d distplanes, %d retries\n",
+      furthestid, qh->vertex_id, dist, getid_(facet), qh->num_facets, zzval_(Ztotmerge), qh->num_outside+1, cpu, qh->furthest_id, qh->vertex_id - 1,
+      cpu - qh->lastcpu, qh->num_facets - qh->lastfacets,  zzval_(Ztotmerge) - qh->lastmerges, zzval_(Zsetplane) - qh->lastplanes, zzval_(Zdistplane) - qh->lastdist, qh->retry_addpoint);
+    qh->lastcpu= cpu;
+    qh->lastfacets= qh->num_facets;
+    qh->lastmerges= zzval_(Ztotmerge);
+    qh->lastplanes= zzval_(Zsetplane);
+    qh->lastdist= zzval_(Zdistplane);
+  }
+  zmax_(Zvisit2max, (int)qh->visit_id/2);
+  if (qh->visit_id > (unsigned int) INT_MAX) { /* 31 bits */
+    zinc_(Zvisit);
+    if (!qh_checklists(qh, qh->facet_list)) {
+      qh_fprintf(qh, qh->ferr, 6370, "qhull internal error: qh_checklists failed on reset of qh.visit_id %u\n", qh->visit_id);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    qh->visit_id= 0;
+    FORALLfacets
+      facet->visitid= 0;
+  }
+  zmax_(Zvvisit2max, (int)qh->vertex_visit/2);
+  if (qh->vertex_visit > (unsigned int) INT_MAX) { /* 31 bits */
+    zinc_(Zvvisit);
+    if (qh->visit_id && !qh_checklists(qh, qh->facet_list)) {
+      qh_fprintf(qh, qh->ferr, 6371, "qhull internal error: qh_checklists failed on reset of qh.vertex_visit %u\n", qh->vertex_visit);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    qh->vertex_visit= 0;
+    FORALLvertices
+      vertex->visitid= 0;
+  }
+  qh->furthest_id= furthestid;
+  qh->RANDOMdist= qh->old_randomdist;
+} /* buildtracing */
+
+/*---------------------------------
+
+  qh_errexit2(qh, exitcode, facet, otherfacet )
+    return exitcode to system after an error
+    report two facets
+
+  returns:
+    assumes exitcode non-zero
+
+  see:
+    normally use qh_errexit() in user_r.c(reports a facet and a ridge)
+*/
+void qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet) {
+  qh->tracefacet= NULL;  /* avoid infinite recursion through qh_fprintf */
+  qh->traceridge= NULL;
+  qh->tracevertex= NULL;
+  qh_errprint(qh, "ERRONEOUS", facet, otherfacet, NULL, NULL);
+  qh_errexit(qh, exitcode, NULL, NULL);
+} /* errexit2 */
+
+
+/*---------------------------------
+
+  qh_findhorizon(qh, point, facet, goodvisible, goodhorizon )
+    given a visible facet, find the point's horizon and visible facets
+    for all facets, !facet-visible
+
+  returns:
+    returns qh.visible_list/num_visible with all visible facets
+      marks visible facets with ->visible
+    updates count of good visible and good horizon facets
+    updates qh.max_outside, qh.max_vertex, facet->maxoutside
+
+  see:
+    similar to qh_delpoint()
+
+  design:
+    move facet to qh.visible_list at end of qh.facet_list
+    for all visible facets
+     for each unvisited neighbor of a visible facet
+       compute distance of point to neighbor
+       if point above neighbor
+         move neighbor to end of qh.visible_list
+       else if point is coplanar with neighbor
+         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
+         mark neighbor coplanar (will create a samecycle later)
+         update horizon statistics
+*/
+void qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
+  facetT *neighbor, **neighborp, *visible;
+  int numhorizon= 0, coplanar= 0;
+  realT dist;
+
+  trace1((qh, qh->ferr, 1040, "qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(qh, point),facet->id));
+  *goodvisible= *goodhorizon= 0;
+  zinc_(Ztotvisible);
+  qh_removefacet(qh, facet);  /* visible_list at end of qh->facet_list */
+  qh_appendfacet(qh, facet);
+  qh->num_visible= 1;
+  if (facet->good)
+    (*goodvisible)++;
+  qh->visible_list= facet;
+  facet->visible= True;
+  facet->f.replace= NULL;
+  if (qh->IStracing >=4)
+    qh_errprint(qh, "visible", facet, NULL, NULL, NULL);
+  qh->visit_id++;
+  FORALLvisible_facets {
+    if (visible->tricoplanar && !qh->TRInormals) {
+      qh_fprintf(qh, qh->ferr, 6230, "qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
+      qh_errexit(qh, qh_ERRqhull, visible, NULL);
+    }
+    if (qh_setsize(qh, visible->neighbors) == 0) {
+      qh_fprintf(qh, qh->ferr, 6295, "qhull internal error (qh_findhorizon): visible facet f%d does not have neighbors\n", visible->id);
+      qh_errexit(qh, qh_ERRqhull, visible, NULL);
+    }
+    visible->visitid= qh->visit_id;
+    FOREACHneighbor_(visible) {
+      if (neighbor->visitid == qh->visit_id)
+        continue;
+      neighbor->visitid= qh->visit_id;
+      zzinc_(Znumvisibility);
+      qh_distplane(qh, point, neighbor, &dist);
+      if (dist > qh->MINvisible) {
+        zinc_(Ztotvisible);
+        qh_removefacet(qh, neighbor);  /* append to end of qh->visible_list */
+        qh_appendfacet(qh, neighbor);
+        neighbor->visible= True;
+        neighbor->f.replace= NULL;
+        qh->num_visible++;
+        if (neighbor->good)
+          (*goodvisible)++;
+        if (qh->IStracing >=4)
+          qh_errprint(qh, "visible", neighbor, NULL, NULL, NULL);
+      }else {
+        if (dist >= -qh->MAXcoplanar) {
+          neighbor->coplanarhorizon= True;
+          zzinc_(Zcoplanarhorizon);
+          qh_joggle_restart(qh, "coplanar horizon");
+          coplanar++;
+          if (qh->MERGING) {
+            if (dist > 0) {
+              maximize_(qh->max_outside, dist);
+              maximize_(qh->max_vertex, dist);
+#if qh_MAXoutside
+              maximize_(neighbor->maxoutside, dist);
+#endif
+            }else
+              minimize_(qh->min_vertex, dist);  /* due to merge later */
+          }
+          trace2((qh, qh->ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh->MINvisible(%2.7g)\n",
+              qh_pointid(qh, point), neighbor->id, dist, qh->MINvisible));
+        }else
+          neighbor->coplanarhorizon= False;
+        zinc_(Ztothorizon);
+        numhorizon++;
+        if (neighbor->good)
+          (*goodhorizon)++;
+        if (qh->IStracing >=4)
+          qh_errprint(qh, "horizon", neighbor, NULL, NULL, NULL);
+      }
+    }
+  }
+  if (!numhorizon) {
+    qh_joggle_restart(qh, "empty horizon");
+    qh_fprintf(qh, qh->ferr, 6168, "qhull topology error (qh_findhorizon): empty horizon for p%d.  It was above all facets.\n", qh_pointid(qh, point));
+    if (qh->num_facets < 100) {
+      qh_printfacetlist(qh, qh->facet_list, NULL, True);
+    }
+    qh_errexit(qh, qh_ERRtopology, NULL, NULL);
+  }
+  trace1((qh, qh->ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
+       numhorizon, *goodhorizon, qh->num_visible, *goodvisible, coplanar));
+  if (qh->IStracing >= 4 && qh->num_facets < 100)
+    qh_printlists(qh);
+} /* findhorizon */
+
+/*---------------------------------
+
+  qh_joggle_restart(qh, reason )
+    if joggle ('QJn') and not merging, restart on precision and topology errors
+*/
+void qh_joggle_restart(qhT *qh, const char *reason) {
+
+  if (qh->JOGGLEmax < REALmax/2) {
+    if (qh->ALLOWrestart && !qh->PREmerge && !qh->MERGEexact) {
+      trace0((qh, qh->ferr, 26, "qh_joggle_restart: qhull restart because of %s\n", reason));
+      /* May be called repeatedly if qh->ALLOWrestart */
+      longjmp(qh->restartexit, qh_ERRprec);
+    }
+  }
+} /* qh_joggle_restart */
+
+/*---------------------------------
+
+  qh_nextfurthest(qh, visible )
+    returns next furthest point and visible facet for qh_addpoint()
+    starts search at qh.facet_next
+
+  returns:
+    removes furthest point from outside set
+    NULL if none available
+    advances qh.facet_next over facets with empty outside sets
+
+  design:
+    for each facet from qh.facet_next
+      if empty outside set
+        advance qh.facet_next
+      else if qh.NARROWhull
+        determine furthest outside point
+        if furthest point is not outside
+          advance qh.facet_next(point will be coplanar)
+    remove furthest point from outside set
+*/
+pointT *qh_nextfurthest(qhT *qh, facetT **visible) {
+  facetT *facet;
+  int size, idx, loopcount= 0;
+  realT randr, dist;
+  pointT *furthest;
+
+  while ((facet= qh->facet_next) != qh->facet_tail) {
+    if (!facet || loopcount++ > qh->num_facets) {
+      qh_fprintf(qh, qh->ferr, 6406, "qhull internal error (qh_nextfurthest): null facet or infinite loop detected for qh.facet_next f%d facet_tail f%d\n",
+        getid_(facet), getid_(qh->facet_tail));
+      qh_printlists(qh);
+      qh_errexit2(qh, qh_ERRqhull, facet, qh->facet_tail);
+    }
+    if (!facet->outsideset) {
+      qh->facet_next= facet->next;
+      continue;
+    }
+    SETreturnsize_(facet->outsideset, size);
+    if (!size) {
+      qh_setfree(qh, &facet->outsideset);
+      qh->facet_next= facet->next;
+      continue;
+    }
+    if (qh->NARROWhull) {
+      if (facet->notfurthest)
+        qh_furthestout(qh, facet);
+      furthest= (pointT *)qh_setlast(facet->outsideset);
+#if qh_COMPUTEfurthest
+      qh_distplane(qh, furthest, facet, &dist);
+      zinc_(Zcomputefurthest);
+#else
+      dist= facet->furthestdist;
+#endif
+      if (dist < qh->MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
+        qh->facet_next= facet->next;
+        continue;
+      }
+    }
+    if (!qh->RANDOMoutside && !qh->VIRTUALmemory) {
+      if (qh->PICKfurthest) {
+        qh_furthestnext(qh /* qh.facet_list */);
+        facet= qh->facet_next;
+      }
+      *visible= facet;
+      return ((pointT *)qh_setdellast(facet->outsideset));
+    }
+    if (qh->RANDOMoutside) {
+      int outcoplanar= 0;
+      if (qh->NARROWhull) {
+        FORALLfacets {
+          if (facet == qh->facet_next)
+            break;
+          if (facet->outsideset)
+            outcoplanar += qh_setsize(qh, facet->outsideset);
+        }
+      }
+      randr= qh_RANDOMint;
+      randr= randr/(qh_RANDOMmax+1);
+      randr= floor((qh->num_outside - outcoplanar) * randr);
+      idx= (int)randr;
+      FORALLfacet_(qh->facet_next) {
+        if (facet->outsideset) {
+          SETreturnsize_(facet->outsideset, size);
+          if (!size)
+            qh_setfree(qh, &facet->outsideset);
+          else if (size > idx) {
+            *visible= facet;
+            return ((pointT *)qh_setdelnth(qh, facet->outsideset, idx));
+          }else
+            idx -= size;
+        }
+      }
+      qh_fprintf(qh, qh->ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
+              qh->num_outside, idx+1, randr);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }else { /* VIRTUALmemory */
+      facet= qh->facet_tail->previous;
+      if (!(furthest= (pointT *)qh_setdellast(facet->outsideset))) {
+        if (facet->outsideset)
+          qh_setfree(qh, &facet->outsideset);
+        qh_removefacet(qh, facet);
+        qh_prependfacet(qh, facet, &qh->facet_list);
+        continue;
+      }
+      *visible= facet;
+      return furthest;
+    }
+  }
+  return NULL;
+} /* nextfurthest */
+
+/*---------------------------------
+
+  qh_partitionall(qh, vertices, points, numpoints )
+    partitions all points in points/numpoints to the outsidesets of facets
+    vertices= vertices in qh.facet_list(!partitioned)
+
+  returns:
+    builds facet->outsideset
+    does not partition qh.GOODpoint
+    if qh.ONLYgood && !qh.MERGING,
+      does not partition qh.GOODvertex
+
+  notes:
+    faster if qh.facet_list sorted by anticipated size of outside set
+
+  design:
+    initialize pointset with all points
+    remove vertices from pointset
+    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
+    for all facets
+      for all remaining points in pointset
+        compute distance from point to facet
+        if point is outside facet
+          remove point from pointset (by not reappending)
+          update bestpoint
+          append point or old bestpoint to facet's outside set
+      append bestpoint to facet's outside set (furthest)
+    for all points remaining in pointset
+      partition point into facets' outside sets and coplanar sets
+*/
+void qh_partitionall(qhT *qh, setT *vertices, pointT *points, int numpoints){
+  setT *pointset;
+  vertexT *vertex, **vertexp;
+  pointT *point, **pointp, *bestpoint;
+  int size, point_i, point_n, point_end, remaining, i, id;
+  facetT *facet;
+  realT bestdist= -REALmax, dist, distoutside;
+
+  trace1((qh, qh->ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
+  pointset= qh_settemp(qh, numpoints);
+  qh->num_outside= 0;
+  pointp= SETaddr_(pointset, pointT);
+  for (i=numpoints, point= points; i--; point += qh->hull_dim)
+    *(pointp++)= point;
+  qh_settruncate(qh, pointset, numpoints);
+  FOREACHvertex_(vertices) {
+    if ((id= qh_pointid(qh, vertex->point)) >= 0)
+      SETelem_(pointset, id)= NULL;
+  }
+  id= qh_pointid(qh, qh->GOODpointp);
+  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
+    SETelem_(pointset, id)= NULL;
+  if (qh->GOODvertexp && qh->ONLYgood && !qh->MERGING) { /* matches qhull()*/
+    if ((id= qh_pointid(qh, qh->GOODvertexp)) >= 0)
+      SETelem_(pointset, id)= NULL;
+  }
+  if (!qh->BESToutside) {  /* matches conditional for qh_partitionpoint below */
+    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user_r.h */
+    zval_(Ztotpartition)= qh->num_points - qh->hull_dim - 1; /*misses GOOD... */
+    remaining= qh->num_facets;
+    point_end= numpoints;
+    FORALLfacets {
+      size= point_end/(remaining--) + 100;
+      facet->outsideset= qh_setnew(qh, size);
+      bestpoint= NULL;
+      point_end= 0;
+      FOREACHpoint_i_(qh, pointset) {
+        if (point) {
+          zzinc_(Zpartitionall);
+          qh_distplane(qh, point, facet, &dist);
+          if (dist < distoutside)
+            SETelem_(pointset, point_end++)= point;
+          else {
+            qh->num_outside++;
+            if (!bestpoint) {
+              bestpoint= point;
+              bestdist= dist;
+            }else if (dist > bestdist) {
+              qh_setappend(qh, &facet->outsideset, bestpoint);
+              bestpoint= point;
+              bestdist= dist;
+            }else
+              qh_setappend(qh, &facet->outsideset, point);
+          }
+        }
+      }
+      if (bestpoint) {
+        qh_setappend(qh, &facet->outsideset, bestpoint);
+#if !qh_COMPUTEfurthest
+        facet->furthestdist= bestdist;
+#endif
+      }else
+        qh_setfree(qh, &facet->outsideset);
+      qh_settruncate(qh, pointset, point_end);
+    }
+  }
+  /* if !qh->BESToutside, pointset contains points not assigned to outsideset */
+  if (qh->BESToutside || qh->MERGING || qh->KEEPcoplanar || qh->KEEPinside || qh->KEEPnearinside) {
+    qh->findbestnew= True;
+    FOREACHpoint_i_(qh, pointset) {
+      if (point)
+        qh_partitionpoint(qh, point, qh->facet_list);
+    }
+    qh->findbestnew= False;
+  }
+  zzadd_(Zpartitionall, zzval_(Zpartition));
+  zzval_(Zpartition)= 0;
+  qh_settempfree(qh, &pointset);
+  if (qh->IStracing >= 4)
+    qh_printfacetlist(qh, qh->facet_list, NULL, True);
+} /* partitionall */
+
+
+/*---------------------------------
+
+  qh_partitioncoplanar(qh, point, facet, dist, allnew )
+    partition coplanar point to a facet
+    dist is distance from point to facet
+    if dist NULL,
+      searches for bestfacet and does nothing if inside
+    if allnew (qh.findbestnew)
+      searches new facets instead of using qh_findbest()
+
+  returns:
+    qh.max_ouside updated
+    if qh.KEEPcoplanar or qh.KEEPinside
+      point assigned to best coplanarset
+    qh.repart_facetid==0 (for detecting infinite recursion via qh_partitionpoint)
+
+  notes:
+    facet->maxoutside is updated at end by qh_check_maxout
+
+  design:
+    if dist undefined
+      find best facet for point
+      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
+        exit
+    if keeping coplanar/nearinside/inside points
+      if point is above furthest coplanar point
+        append point to coplanar set (it is the new furthest)
+        update qh.max_outside
+      else
+        append point one before end of coplanar set
+    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
+    and bestfacet is more than perpendicular to facet
+      repartition the point using qh_findbest() -- it may be put on an outsideset
+    else
+      update qh.max_outside
+*/
+void qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist, boolT allnew) {
+  facetT *bestfacet;
+  pointT *oldfurthest;
+  realT bestdist, angle, nearest, dist2= 0.0;
+  int numpart= 0;
+  boolT isoutside, oldfindbest, repartition= False;
+
+  trace4((qh, qh->ferr, 4090, "qh_partitioncoplanar: partition coplanar point p%d starting with f%d dist? %2.2g, allnew? %d, gh.repart_facetid f%d\n",
+    qh_pointid(qh, point), facet->id, (dist ? *dist : 0.0), allnew, qh->repart_facetid));
+  qh->WAScoplanar= True;
+  if (!dist) {
+    if (allnew)
+      bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
+    else
+      bestfacet= qh_findbest(qh, point, facet, qh_ALL, !qh_ISnewfacets, qh->DELAUNAY,
+                          &bestdist, &isoutside, &numpart);
+    zinc_(Ztotpartcoplanar);
+    zzadd_(Zpartcoplanar, numpart);
+    if (!qh->DELAUNAY && !qh->KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
+      if (qh->KEEPnearinside) {
+        if (bestdist < -qh->NEARinside) {
+          zinc_(Zcoplanarinside);
+          trace4((qh, qh->ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g allnew? %d\n",
+                  qh_pointid(qh, point), bestfacet->id, bestdist, allnew));
+          qh->repart_facetid= 0;
+          return;
+        }
+      }else if (bestdist < -qh->MAXcoplanar) {
+          trace4((qh, qh->ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g allnew? %d\n",
+                  qh_pointid(qh, point), bestfacet->id, bestdist, allnew));
+        zinc_(Zcoplanarinside);
+        qh->repart_facetid= 0;
+        return;
+      }
+    }
+  }else {
+    bestfacet= facet;
+    bestdist= *dist;
+  }
+  if(bestfacet->visible){
+    qh_fprintf(qh, qh->ferr, 6405, "qhull internal error (qh_partitioncoplanar): cannot partition coplanar p%d of f%d into visible facet f%d\n",
+        qh_pointid(qh, point), facet->id, bestfacet->id);
+    qh_errexit2(qh, qh_ERRqhull, facet, bestfacet);
+  }
+  if (bestdist > qh->max_outside) {
+    if (!dist && facet != bestfacet) { /* can't be recursive from qh_partitionpoint since facet != bestfacet */
+      zinc_(Zpartangle);
+      angle= qh_getangle(qh, facet->normal, bestfacet->normal);
+      if (angle < 0) {
+        nearest= qh_vertex_bestdist(qh, bestfacet->vertices);
+        /* typically due to deleted vertex and coplanar facets, e.g.,
+        RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
+        zinc_(Zpartcorner);
+        trace2((qh, qh->ferr, 2058, "qh_partitioncoplanar: repartition coplanar point p%d from f%d as an outside point above corner facet f%d dist %2.2g with angle %2.2g\n",
+          qh_pointid(qh, point), facet->id, bestfacet->id, bestdist, angle));
+        repartition= True;
+      }
+    }
+    if (!repartition) {
+      if (bestdist > qh->MAXoutside * qh_RATIOcoplanaroutside) {
+        nearest= qh_vertex_bestdist(qh, bestfacet->vertices);
+        if (facet->id == bestfacet->id) {
+          if (facet->id == qh->repart_facetid) {
+            qh_fprintf(qh, qh->ferr, 6404, "Qhull internal error (qh_partitioncoplanar): infinite loop due to recursive call to qh_partitionpoint.  Repartition point p%d from f%d as a outside point dist %2.2g nearest vertices %2.2g\n",
+              qh_pointid(qh, point), facet->id, bestdist, nearest);
+            qh_errexit(qh, qh_ERRqhull, facet, NULL);
+          }
+          qh->repart_facetid= facet->id; /* reset after call to qh_partitionpoint */
+        }
+        if (point == qh->coplanar_apex) {
+          /* otherwise may loop indefinitely, the point is well above a facet, yet near a vertex */
+          qh_fprintf(qh, qh->ferr, 6425, "Qhull topology error (qh_partitioncoplanar): can not repartition coplanar point p%d from f%d as outside point above f%d.  It previously failed to form a cone of facets, dist %2.2g, nearest vertices %2.2g\n",
+            qh_pointid(qh, point), facet->id, bestfacet->id, bestdist, nearest);
+          qh_errexit(qh, qh_ERRtopology, facet, NULL);
+        }
+        if (nearest < 2 * qh->MAXoutside * qh_RATIOcoplanaroutside) {
+          zinc_(Zparttwisted);
+          qh_fprintf(qh, qh->ferr, 7085, "Qhull precision warning: repartition coplanar point p%d from f%d as an outside point above twisted facet f%d dist %2.2g nearest vertices %2.2g\n",
+            qh_pointid(qh, point), facet->id, bestfacet->id, bestdist, nearest);
+        }else {
+          zinc_(Zparthidden);
+          qh_fprintf(qh, qh->ferr, 7086, "Qhull precision warning: repartition coplanar point p%d from f%d as an outside point above hidden facet f%d dist %2.2g nearest vertices %2.2g\n",
+            qh_pointid(qh, point), facet->id, bestfacet->id, bestdist, nearest);
+        }
+        repartition= True;
+      }
+    }
+    if (repartition) {
+      oldfindbest= qh->findbestnew;
+      qh->findbestnew= False;
+      qh_partitionpoint(qh, point, bestfacet);
+      qh->findbestnew= oldfindbest;
+      qh->repart_facetid= 0;
+      return;
+    }
+    qh->repart_facetid= 0;
+    qh->max_outside= bestdist;
+    if (bestdist > qh->TRACEdist || qh->IStracing >= 3) {
+      qh_fprintf(qh, qh->ferr, 3041, "qh_partitioncoplanar: == p%d from f%d increases qh.max_outside to %2.2g of f%d last p%d\n",
+                     qh_pointid(qh, point), facet->id, bestdist, bestfacet->id, qh->furthest_id);
+      qh_errprint(qh, "DISTANT", facet, bestfacet, NULL, NULL);
+    }
+  }
+  if (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside) {
+    oldfurthest= (pointT *)qh_setlast(bestfacet->coplanarset);
+    if (oldfurthest) {
+      zinc_(Zcomputefurthest);
+      qh_distplane(qh, oldfurthest, bestfacet, &dist2);
+    }
+    if (!oldfurthest || dist2 < bestdist)
+      qh_setappend(qh, &bestfacet->coplanarset, point);
+    else
+      qh_setappend2ndlast(qh, &bestfacet->coplanarset, point);
+  }
+  trace4((qh, qh->ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d (or inside) dist %2.2g\n",
+          qh_pointid(qh, point), bestfacet->id, bestdist));
+} /* partitioncoplanar */
+
+/*---------------------------------
+
+  qh_partitionpoint(qh, point, facet )
+    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
+    if qh.findbestnew
+      uses qh_findbestnew() to search all new facets
+    else
+      uses qh_findbest()
+
+  notes:
+    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
+
+  design:
+    find best facet for point
+      (either exhaustive search of new facets or directed search from facet)
+    if qh.NARROWhull
+      retain coplanar and nearinside points as outside points
+    if point is outside bestfacet
+      if point above furthest point for bestfacet
+        append point to outside set (it becomes the new furthest)
+        if outside set was empty
+          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
+        update bestfacet->furthestdist
+      else
+        append point one before end of outside set
+    else if point is coplanar to bestfacet
+      if keeping coplanar points or need to update qh.max_outside
+        partition coplanar point into bestfacet
+    else if near-inside point
+      partition as coplanar point into bestfacet
+    else is an inside point
+      if keeping inside points
+        partition as coplanar point into bestfacet
+*/
+void qh_partitionpoint(qhT *qh, pointT *point, facetT *facet) {
+  realT bestdist, previousdist;
+  boolT isoutside, isnewoutside= False;
+  facetT *bestfacet;
+  int numpart;
+
+  if (qh->findbestnew)
+    bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh->BESToutside, &isoutside, &numpart);
+  else
+    bestfacet= qh_findbest(qh, point, facet, qh->BESToutside, qh_ISnewfacets, !qh_NOupper,
+                          &bestdist, &isoutside, &numpart);
+  zinc_(Ztotpartition);
+  zzadd_(Zpartition, numpart);
+  if(bestfacet->visible){
+    qh_fprintf(qh, qh->ferr, 6293, "qhull internal error (qh_partitionpoint): cannot partition p%d of f%d into visible facet f%d\n",
+      qh_pointid(qh, point), facet->id, bestfacet->id);
+    qh_errexit2(qh, qh_ERRqhull, facet, bestfacet);
+  }
+  if (qh->NARROWhull) {
+    if (qh->DELAUNAY && !isoutside && bestdist >= -qh->MAXcoplanar)
+      qh_joggle_restart(qh, "nearly incident point (narrow hull)");
+    if (qh->KEEPnearinside) {
+      if (bestdist >= -qh->NEARinside)
+        isoutside= True;
+    }else if (bestdist >= -qh->MAXcoplanar)
+      isoutside= True;
+  }
+
+  if (isoutside) {
+    if (!bestfacet->outsideset
+    || !qh_setlast(bestfacet->outsideset)) { /* empty outside set */
+      qh_setappend(qh, &(bestfacet->outsideset), point);
+      if (!qh->NARROWhull || bestdist > qh->MINoutside)
+        isnewoutside= True;
+#if !qh_COMPUTEfurthest
+      bestfacet->furthestdist= bestdist;
+#endif
+    }else {
+#if qh_COMPUTEfurthest
+      zinc_(Zcomputefurthest);
+      qh_distplane(qh, oldfurthest, bestfacet, &previousdist);
+      if (previousdist < bestdist)
+        qh_setappend(qh, &(bestfacet->outsideset), point);
+      else
+        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
+#else
+      previousdist= bestfacet->furthestdist;
+      if (previousdist < bestdist) {
+        qh_setappend(qh, &(bestfacet->outsideset), point);
+        bestfacet->furthestdist= bestdist;
+        if (qh->NARROWhull && previousdist < qh->MINoutside && bestdist >= qh->MINoutside)
+          isnewoutside= True;
+      }else
+        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
+#endif
+    }
+    if (isnewoutside && qh->facet_next != bestfacet) {
+      if (bestfacet->newfacet) {
+        if (qh->facet_next->newfacet)
+          qh->facet_next= qh->newfacet_list; /* make sure it's after qh.facet_next */
+      }else {
+        qh_removefacet(qh, bestfacet);  /* make sure it's after qh.facet_next */
+        qh_appendfacet(qh, bestfacet);
+        if(qh->newfacet_list){
+          bestfacet->newfacet= True;
+        }
+      }
+    }
+    qh->num_outside++;
+    trace4((qh, qh->ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d newfacet? %d, newoutside? %d (or narrowhull)\n",
+          qh_pointid(qh, point), bestfacet->id, bestfacet->newfacet, isnewoutside));
+  }else if (qh->DELAUNAY || bestdist >= -qh->MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
+    if (qh->DELAUNAY)
+      qh_joggle_restart(qh, "nearly incident point");
+    /* allow coplanar points with joggle, may be interior */
+    zzinc_(Zcoplanarpart);
+    if ((qh->KEEPcoplanar + qh->KEEPnearinside) || bestdist > qh->max_outside)
+      qh_partitioncoplanar(qh, point, bestfacet, &bestdist, qh->findbestnew);
+    else {
+      trace4((qh, qh->ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
+          qh_pointid(qh, point), bestfacet->id));
+    }
+  }else if (qh->KEEPnearinside && bestdist >= -qh->NEARinside) {
+    zinc_(Zpartnear);
+    qh_partitioncoplanar(qh, point, bestfacet, &bestdist, qh->findbestnew);
+  }else {
+    zinc_(Zpartinside);
+    trace4((qh, qh->ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
+          qh_pointid(qh, point), bestfacet->id, bestdist));
+    if (qh->KEEPinside)
+      qh_partitioncoplanar(qh, point, bestfacet, &bestdist, qh->findbestnew);
+  }
+} /* partitionpoint */
+
+/*---------------------------------
+
+  qh_partitionvisible(qh, allpoints, numoutside )
+    partitions outside points in visible facets (qh.visible_list) to qh.newfacet_list
+    if keeping coplanar/near-inside/inside points
+      partitions coplanar points; repartitions if 'allpoints' (not used)
+    1st neighbor (if any) of visible facets points to a horizon facet or a new facet
+
+  returns:
+    updates outside sets and coplanar sets of qh.newfacet_list
+    updates qh.num_outside (count of outside points)
+    does not truncate f.outsideset, f.coplanarset, or qh.del_vertices (see qh_deletevisible)
+
+  notes:
+    called by qh_qhull, qh_addpoint, and qh_all_vertexmerges
+    qh.findbest_notsharp should be clear (extra work if set)
+
+  design:
+    for all visible facets with outside set or coplanar set
+      select a newfacet for visible facet
+      if outside set
+        partition outside set into new facets
+      if coplanar set and keeping coplanar/near-inside/inside points
+        if allpoints
+          partition coplanar set into new facets, may be assigned outside
+        else
+          partition coplanar set into coplanar sets of new facets
+    for each deleted vertex
+      if allpoints
+        partition vertex into new facets, may be assigned outside
+      else
+        partition vertex into coplanar sets of new facets
+*/
+void qh_partitionvisible(qhT *qh, boolT allpoints, int *numoutside /* qh.visible_list */) {
+  facetT *visible, *newfacet;
+  pointT *point, **pointp;
+  int delsize, coplanar=0, size;
+  vertexT *vertex, **vertexp;
+
+  trace3((qh, qh->ferr, 3042, "qh_partitionvisible: partition outside and coplanar points of visible and merged facets f%d into new facets f%d\n",
+    qh->visible_list->id, qh->newfacet_list->id));
+  if (qh->ONLYmax)
+    maximize_(qh->MINoutside, qh->max_vertex);
+  *numoutside= 0;
+  FORALLvisible_facets {
+    if (!visible->outsideset && !visible->coplanarset)
+      continue;
+    newfacet= qh_getreplacement(qh, visible);
+    if (!newfacet)
+      newfacet= qh->newfacet_list;
+    if (!newfacet->next) {
+      qh_fprintf(qh, qh->ferr, 6170, "qhull topology error (qh_partitionvisible): all new facets deleted as\n       degenerate facets. Can not continue.\n");
+      qh_errexit(qh, qh_ERRtopology, NULL, NULL);
+    }
+    if (visible->outsideset) {
+      size= qh_setsize(qh, visible->outsideset);
+      *numoutside += size;
+      qh->num_outside -= size;
+      FOREACHpoint_(visible->outsideset)
+        qh_partitionpoint(qh, point, newfacet);
+    }
+    if (visible->coplanarset && (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside)) {
+      size= qh_setsize(qh, visible->coplanarset);
+      coplanar += size;
+      FOREACHpoint_(visible->coplanarset) {
+        if (allpoints) /* not used */
+          qh_partitionpoint(qh, point, newfacet);
+        else
+          qh_partitioncoplanar(qh, point, newfacet, NULL, qh->findbestnew);
+      }
+    }
+  }
+  delsize= qh_setsize(qh, qh->del_vertices);
+  if (delsize > 0) {
+    trace3((qh, qh->ferr, 3049, "qh_partitionvisible: partition %d deleted vertices as coplanar? %d points into new facets f%d\n",
+      delsize, !allpoints, qh->newfacet_list->id));
+    FOREACHvertex_(qh->del_vertices) {
+      if (vertex->point && !vertex->partitioned) {
+        if (!qh->newfacet_list || qh->newfacet_list == qh->facet_tail) {
+          qh_fprintf(qh, qh->ferr, 6284, "qhull internal error (qh_partitionvisible): all new facets deleted or none defined.  Can not partition deleted v%d.\n", vertex->id);
+          qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+        }
+        if (allpoints) /* not used */
+          /* [apr'2019] infinite loop if vertex recreates the same facets from the same horizon
+             e.g., qh_partitionpoint if qh.DELAUNAY with qh.MERGEindependent for all mergetype, ../eg/qtest.sh t427764 '1000 s W1e-13 D3' 'd' */
+          qh_partitionpoint(qh, vertex->point, qh->newfacet_list);
+        else
+          qh_partitioncoplanar(qh, vertex->point, qh->newfacet_list, NULL, qh_ALL); /* search all new facets */
+        vertex->partitioned= True;
+      }
+    }
+  }
+  trace1((qh, qh->ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets, %d points from coplanarsets, and %d deleted vertices\n", *numoutside, coplanar, delsize));
+} /* partitionvisible */
+
+/*---------------------------------
+
+  qh_printsummary(qh, fp )
+    prints summary to fp
+
+  notes:
+    not in io_r.c so that user_eg.c can prevent io_r.c from loading
+    qh_printsummary and qh_countfacets must match counts
+    updates qh.facet_visit to detect infinite loop
+
+  design:
+    determine number of points, vertices, and coplanar points
+    print summary
+*/
+void qh_printsummary(qhT *qh, FILE *fp) {
+  realT ratio, outerplane, innerplane;
+  double cpu;
+  int size, id, nummerged, numpinched, numvertices, numcoplanars= 0, nonsimplicial=0, numdelaunay= 0;
+  facetT *facet;
+  const char *s;
+  int numdel= zzval_(Zdelvertextot);
+  int numtricoplanars= 0;
+  boolT goodused;
+
+  size= qh->num_points + qh_setsize(qh, qh->other_points);
+  numvertices= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
+  id= qh_pointid(qh, qh->GOODpointp);
+  if (!qh_checklists(qh, qh->facet_list) && !qh->ERREXITcalled) {
+    qh_fprintf(qh, fp, 6372, "qhull internal error: qh_checklists failed at qh_printsummary\n");
+    if (qh->num_facets < 4000)
+      qh_printlists(qh);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (qh->DELAUNAY && qh->ERREXITcalled) {
+    /* update f.good and determine qh.num_good as in qh_findgood_all */
+    FORALLfacets {
+      if (facet->visible)
+        facet->good= False; /* will be deleted */
+      else if (facet->good) {
+        if (facet->normal && !qh_inthresholds(qh, facet->normal, NULL))
+          facet->good= False;
+        else
+          numdelaunay++;
+      }
+    }
+    qh->num_good= numdelaunay;
+  }
+  FORALLfacets {
+    if (facet->coplanarset)
+      numcoplanars += qh_setsize(qh, facet->coplanarset);
+    if (facet->good) {
+      if (facet->simplicial) {
+        if (facet->keepcentrum && facet->tricoplanar)
+          numtricoplanars++;
+      }else if (qh_setsize(qh, facet->vertices) != qh->hull_dim)
+        nonsimplicial++;
+    }
+  }
+  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
+    size--;
+  if (qh->STOPadd || qh->STOPcone || qh->STOPpoint)
+    qh_fprintf(qh, fp, 9288, "\nEarly exit due to 'TAn', 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
+  goodused= False;
+  if (qh->ERREXITcalled)
+    ; /* qh_findgood_all not called */
+  else if (qh->UPPERdelaunay) {
+    if (qh->GOODvertex || qh->GOODpoint || qh->SPLITthresholds)
+      goodused= True;
+  }else if (qh->DELAUNAY) {
+    if (qh->GOODvertex || qh->GOODpoint || qh->GOODthreshold)
+      goodused= True;
+  }else if (qh->num_good > 0 || qh->GOODthreshold)
+    goodused= True;
+  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
+  if (qh->VORONOI) {
+    if (qh->UPPERdelaunay)
+      qh_fprintf(qh, fp, 9289, "\n\
+Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    else
+      qh_fprintf(qh, fp, 9290, "\n\
+Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    qh_fprintf(qh, fp, 9291, "  Number of Voronoi regions%s: %d\n",
+              qh->ATinfinity ? " and at-infinity" : "", numvertices);
+    if (numdel)
+      qh_fprintf(qh, fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
+    if (numcoplanars - numdel > 0)
+      qh_fprintf(qh, fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
+    else if (size - numvertices - numdel > 0)
+      qh_fprintf(qh, fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
+    qh_fprintf(qh, fp, 9295, "  Number of%s Voronoi vertices: %d\n",
+              goodused ? " 'good'" : "", qh->num_good);
+    if (nonsimplicial)
+      qh_fprintf(qh, fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
+              goodused ? " 'good'" : "", nonsimplicial);
+  }else if (qh->DELAUNAY) {
+    if (qh->UPPERdelaunay)
+      qh_fprintf(qh, fp, 9297, "\n\
+Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    else
+      qh_fprintf(qh, fp, 9298, "\n\
+Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    qh_fprintf(qh, fp, 9299, "  Number of input sites%s: %d\n",
+              qh->ATinfinity ? " and at-infinity" : "", numvertices);
+    if (numdel)
+      qh_fprintf(qh, fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
+    if (numcoplanars - numdel > 0)
+      qh_fprintf(qh, fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
+    else if (size - numvertices - numdel > 0)
+      qh_fprintf(qh, fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
+    qh_fprintf(qh, fp, 9303, "  Number of%s Delaunay regions: %d\n",
+              goodused ? " 'good'" : "", qh->num_good);
+    if (nonsimplicial)
+      qh_fprintf(qh, fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
+              goodused ? " 'good'" : "", nonsimplicial);
+  }else if (qh->HALFspace) {
+    qh_fprintf(qh, fp, 9305, "\n\
+Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    qh_fprintf(qh, fp, 9306, "  Number of halfspaces: %d\n", size);
+    qh_fprintf(qh, fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
+    if (numcoplanars) {
+      if (qh->KEEPinside && qh->KEEPcoplanar)
+        s= "similar and redundant";
+      else if (qh->KEEPinside)
+        s= "redundant";
+      else
+        s= "similar";
+      qh_fprintf(qh, fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
+    }
+    qh_fprintf(qh, fp, 9309, "  Number of intersection points: %d\n", qh->num_facets - qh->num_visible);
+    if (goodused)
+      qh_fprintf(qh, fp, 9310, "  Number of 'good' intersection points: %d\n", qh->num_good);
+    if (nonsimplicial)
+      qh_fprintf(qh, fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
+              goodused ? " 'good'" : "", nonsimplicial);
+  }else {
+    qh_fprintf(qh, fp, 9312, "\n\
+Convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
+    qh_fprintf(qh, fp, 9313, "  Number of vertices: %d\n", numvertices);
+    if (numcoplanars) {
+      if (qh->KEEPinside && qh->KEEPcoplanar)
+        s= "coplanar and interior";
+      else if (qh->KEEPinside)
+        s= "interior";
+      else
+        s= "coplanar";
+      qh_fprintf(qh, fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
+    }
+    qh_fprintf(qh, fp, 9315, "  Number of facets: %d\n", qh->num_facets - qh->num_visible);
+    if (goodused)
+      qh_fprintf(qh, fp, 9316, "  Number of 'good' facets: %d\n", qh->num_good);
+    if (nonsimplicial)
+      qh_fprintf(qh, fp, 9317, "  Number of%s non-simplicial facets: %d\n",
+              goodused ? " 'good'" : "", nonsimplicial);
+  }
+  if (numtricoplanars)
+      qh_fprintf(qh, fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
+  qh_fprintf(qh, fp, 9319, "\nStatistics for: %s | %s",
+                      qh->rbox_command, qh->qhull_command);
+  if (qh->ROTATErandom != INT_MIN)
+    qh_fprintf(qh, fp, 9320, " QR%d\n\n", qh->ROTATErandom);
+  else
+    qh_fprintf(qh, fp, 9321, "\n\n");
+  qh_fprintf(qh, fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
+  qh_fprintf(qh, fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
+  if (qh->DELAUNAY)
+    qh_fprintf(qh, fp, 9324, "  Number of facets in hull: %d\n", qh->num_facets - qh->num_visible);
+  qh_fprintf(qh, fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
+      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
+#if 0  /* NOTE: must print before printstatistics() */
+  {realT stddev, ave;
+  qh_fprintf(qh, fp, 9326, "  average new facet balance: %2.2g\n",
+          wval_(Wnewbalance)/zval_(Zprocessed));
+  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
+                                 wval_(Wnewbalance2), &ave);
+  qh_fprintf(qh, fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
+  qh_fprintf(qh, fp, 9328, "  average partition balance: %2.2g\n",
+          wval_(Wpbalance)/zval_(Zpbalance));
+  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
+                                 wval_(Wpbalance2), &ave);
+  qh_fprintf(qh, fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
+  }
+#endif
+  if (nummerged) {
+    qh_fprintf(qh, fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
+          zzval_(Zcentrumtests)+zzval_(Zvertextests)+zzval_(Zdistcheck)+zzval_(Zdistzero));
+    qh_fprintf(qh, fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart)+zzval_(Zdistconvex));
+    qh_fprintf(qh, fp, 9332,"  Number of merged facets: %d\n", nummerged);
+  }
+  numpinched= zzval_(Zpinchduplicate) + zzval_(Zpinchedvertex);
+  if (numpinched)
+    qh_fprintf(qh, fp, 9375,"  Number of merged pinched vertices: %d\n", numpinched);
+  if (!qh->RANDOMoutside && qh->QHULLfinished) {
+    cpu= (double)qh->hulltime;
+    cpu /= (double)qh_SECticks;
+    wval_(Wcpu)= cpu;
+    qh_fprintf(qh, fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
+  }
+  if (qh->RERUN) {
+    if (!qh->PREmerge && !qh->MERGEexact)
+      qh_fprintf(qh, fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
+           zzval_(Zretry)*100.0/qh->build_cnt);  /* careful of order */
+  }else if (qh->JOGGLEmax < REALmax/2) {
+    if (zzval_(Zretry))
+      qh_fprintf(qh, fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
+         zzval_(Zretry), qh->JOGGLEmax);
+    else
+      qh_fprintf(qh, fp, 9336, "  Input joggled by: %2.2g\n", qh->JOGGLEmax);
+  }
+  if (qh->totarea != 0.0)
+    qh_fprintf(qh, fp, 9337, "  %s facet area:   %2.8g\n",
+            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totarea);
+  if (qh->totvol != 0.0)
+    qh_fprintf(qh, fp, 9338, "  %s volume:       %2.8g\n",
+            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totvol);
+  if (qh->MERGING) {
+    qh_outerinner(qh, NULL, &outerplane, &innerplane);
+    if (outerplane > 2 * qh->DISTround) {
+      qh_fprintf(qh, fp, 9339, "  Maximum distance of point above facet: %2.2g", outerplane);
+      ratio= outerplane/(qh->ONEmerge + qh->DISTround);
+      /* don't report ratio if MINoutside is large */
+      if (ratio > 0.05 && 2* qh->ONEmerge > qh->MINoutside && qh->JOGGLEmax > REALmax/2)
+        qh_fprintf(qh, fp, 9340, " (%.1fx)\n", ratio);
+      else
+        qh_fprintf(qh, fp, 9341, "\n");
+    }
+    if (innerplane < -2 * qh->DISTround) {
+      qh_fprintf(qh, fp, 9342, "  Maximum distance of vertex below facet: %2.2g", innerplane);
+      ratio= -innerplane/(qh->ONEmerge+qh->DISTround);
+      if (ratio > 0.05 && qh->JOGGLEmax > REALmax/2)
+        qh_fprintf(qh, fp, 9343, " (%.1fx)\n", ratio);
+      else
+        qh_fprintf(qh, fp, 9344, "\n");
+    }
+  }
+  qh_fprintf(qh, fp, 9345, "\n");
+} /* printsummary */
+
+
diff --git a/vendor/qhull/libqhull_r/libqhull_r.h b/vendor/qhull/libqhull_r/libqhull_r.h
new file mode 100644
index 0000000..7193e2a
--- /dev/null
+++ b/vendor/qhull/libqhull_r/libqhull_r.h
@@ -0,0 +1,1281 @@
+/*
  ---------------------------------
+
+   libqhull_r.h
+   user-level header file for using qhull.a library
+
+   see qh-qhull_r.htm, qhull_ra.h
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.h#18 $$Change: 3664 $
+   $DateTime: 2024/07/22 23:55:01 $$Author: bbarber $
+
+   includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user_r.c
+
+   use mem_r.h for mem_r.c
+   use qset_r.h for qset_r.c
+
+   see unix_r.c for an example of using libqhull_r.h
+
+   recompile qhull if you change this file
+*/
+
+#ifndef qhDEFlibqhull
+#define qhDEFlibqhull 1
+
+/*=========================== -included files ==============*/
+
+/* user_r.h first for QHULL_CRTDBG */
+#include "user_r.h"      /* user definable constants (e.g., realT). */
+
+#include "mem_r.h"   /* Needed for qhT in libqhull_r.h */
+#include "qset_r.h"   /* Needed for QHULL_LIB_CHECK */
+/* include stat_r.h after defining boolT.  Needed for qhT in libqhull_r.h */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifndef __STDC__
+#ifndef __cplusplus
+#if     !defined(_MSC_VER)
+#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
+#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
+#error  your compiler is a standard C compiler, you can delete this warning from libqhull_r.h
+#endif
+#endif
+#endif
+
+#if defined(__GNUC__)
+/* See https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-format-function-attribute */
+#define QH_PRINTF_LIKE(string_index, first_to_check) __attribute__((format(printf, string_index, first_to_check)))
+#else
+#define QH_PRINTF_LIKE(string_index, first_to_check)
+#endif
+
+/* QH_NORETURN marks as a function as never returning. This is primarily
+   beneficial for aiding static analyzers and reducing compiler warnings.
+   It must come before function declarations, as MSVC only supports this syntax. */
+#if defined(__GNUC__)
+/* Compilers that support the GNU C syntax. Use __noreturn__ instead of 'noreturn' as the latter is a macro in C11. */
+#define QH_NORETURN __attribute__((__noreturn__))
+#elif defined(_MSC_VER)
+/* Compilers that support the MSVC syntax. */
+#define QH_NORETURN __declspec(noreturn)
+#else
+#define QH_NORETURN /* empty */
+#endif
+
+/*============ constants and basic types ====================*/
+
+extern const char qh_version[]; /* defined in global_r.c */
+extern const char qh_version2[]; /* defined in global_r.c */
+
+/*----------------------------------
+
+  coordT
+    coordinates and coefficients are stored as realT (i.e., double)
+
+  notes:
+    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
+
+    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
+      This requires many type casts, and adjusted error bounds.
+      Also C compilers may do expressions in double anyway.
+*/
+#define coordT realT
+
+/*----------------------------------
+
+  pointT
+    a point is an array of coordinates, usually qh.hull_dim
+    qh_pointid returns
+      qh_IDnone if point==0 or qh is undefined
+      qh_IDinterior for qh.interior_point
+      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
+
+  notes:
+    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
+    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
+*/
+#define pointT coordT
+typedef enum
+{
+    qh_IDnone= -3, qh_IDinterior= -2, qh_IDunknown= -1
+}
+qh_pointT;
+
+/*----------------------------------
+
+  flagT
+    Boolean flag as a bit
+*/
+#define flagT unsigned int
+
+/*----------------------------------
+
+  boolT
+    boolean value, either True or False
+
+  notes:
+    needed for portability
+    Use qh_False/qh_True as synonyms
+*/
+#define boolT unsigned int
+#ifdef False
+#undef False
+#endif
+#ifdef True
+#undef True
+#endif
+#define False 0
+#define True 1
+#define qh_False 0
+#define qh_True 1
+
+#include "stat_r.h"  /* needs boolT */
+
+/*----------------------------------
+
+  qh_CENTER
+    to distinguish facet->center
+*/
+typedef enum
+{
+    qh_ASnone= 0,    /* If not MERGING and not VORONOI */
+    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
+    qh_AScentrum     /* If MERGING (assumed during merging) */
+}
+qh_CENTER;
+
+/*----------------------------------
+
+  qh_PRINT
+    output formats for printing (qh.PRINTout).
+    'Fa' 'FV' 'Fc' 'FC'
+
+
+   notes:
+   some of these names are similar to qhT names.  The similar names are only
+   used in switch statements in qh_printbegin() etc.
+*/
+typedef enum {qh_PRINTnone= 0,
+  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
+  qh_PRINTcoplanars, qh_PRINTcentrums,
+  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
+  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
+  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
+  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
+  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
+  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
+  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
+  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
+  qh_PRINTEND} qh_PRINT;
+
+/*----------------------------------
+
+  qh_ALL
+    argument flag for selecting everything
+*/
+#define qh_ALL      True
+#define qh_NOupper  True      /* argument for qh_findbest */
+#define qh_IScheckmax  True   /* argument for qh_findbesthorizon */
+#define qh_ISnewfacets  True  /* argument for qh_findbest */
+#define qh_RESETvisible  True /* argument for qh_resetlists */
+
+/*----------------------------------
+
+  qh_ERR...
+    Qhull exit status codes, for indicating errors
+    See: MSG_ERROR (6000) and MSG_WARNING (7000) [user_r.h]
+*/
+#define qh_ERRnone  0    /* no error occurred during qhull */
+#define qh_ERRinput 1    /* input inconsistency */
+#define qh_ERRsingular 2 /* singular input data, calls qh_printhelp_singular */
+#define qh_ERRprec  3    /* precision error, calls qh_printhelp_degenerate */
+#define qh_ERRmem   4    /* insufficient memory, matches mem_r.h */
+#define qh_ERRqhull 5    /* internal error detected, matches mem_r.h, calls qh_printhelp_internal */
+#define qh_ERRother 6    /* other error detected */
+#define qh_ERRtopology 7 /* topology error, maybe due to nearly adjacent vertices, calls qh_printhelp_topology */
+#define qh_ERRwide 8     /* wide facet error, maybe due to nearly adjacent vertices, calls qh_printhelp_wide */
+#define qh_ERRdebug 9    /* qh_errexit from debugging code */
+
+/*----------------------------------
+
+qh_FILEstderr
+Fake stderr to distinguish error output from normal output
+For C++ interface.  Must redefine qh_fprintf_qhull
+*/
+#define qh_FILEstderr ((FILE *)1)
+
+/* ============ -structures- ====================
+   each of the following structures is defined by a typedef
+   all realT and coordT fields occur at the beginning of a structure
+        (otherwise space may be wasted due to alignment)
+   define all flags together and pack into 32-bit number
+
+   DEFqhT and DEFsetT are likewise defined in mem_r.h, qset_r.h, and stat_r.h
+*/
+
+typedef struct vertexT vertexT;
+typedef struct ridgeT ridgeT;
+typedef struct facetT facetT;
+
+#ifndef DEFqhT
+#define DEFqhT 1
+typedef struct qhT qhT;          /* defined below */
+#endif
+
+#ifndef DEFsetT
+#define DEFsetT 1
+typedef struct setT setT;        /* defined in qset_r.h */
+#endif
+
+/*----------------------------------
+
+  facetT
+    defines a facet
+
+  notes:
+   qhull() generates the hull as a list of facets.
+
+  topological information:
+    f.previous,next     doubly-linked list of facets, next is always defined
+    f.vertices          set of vertices
+    f.ridges            set of ridges
+    f.neighbors         set of neighbors
+    f.toporient         True if facet has top-orientation (else bottom)
+
+  geometric information:
+    f.offset,normal     hyperplane equation
+    f.maxoutside        offset to outer plane -- all points inside
+    f.center            centrum for testing convexity or Voronoi center for output
+    f.simplicial        True if facet is simplicial
+    f.flipped           True if facet does not include qh.interior_point
+
+  for constructing hull:
+    f.visible           True if facet on list of visible facets (will be deleted)
+    f.newfacet          True if facet on list of newly created facets
+    f.coplanarset       set of points coplanar with this facet
+                        (includes near-inside points for later testing)
+    f.outsideset        set of points outside of this facet
+    f.furthestdist      distance to furthest point of outside set
+    f.visitid           marks visited facets during a loop
+    f.replace           replacement facet for to-be-deleted, visible facets
+    f.samecycle,newcycle cycle of facets for merging into horizon facet
+
+  see below for other flags and fields
+*/
+/* QhullFacet.cpp -- Update static initializer list for s_empty_facet if add or remove fields */
+struct facetT {
+#if !qh_COMPUTEfurthest
+  coordT   furthestdist;/* distance to furthest point of outsideset */
+#endif
+#if qh_MAXoutside
+  coordT   maxoutside;  /* max computed distance of point to facet
+                        Before QHULLfinished this is an approximation
+                        since maxdist not always set for qh_mergefacet
+                        Actual outer plane is +DISTround and
+                        computed outer plane is +2*DISTround.
+                        Initial maxoutside is qh.DISTround, otherwise distance tests need to account for DISTround */
+#endif
+  coordT   offset;      /* exact offset of hyperplane from origin */
+  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
+                        /*   if f.tricoplanar, shared with a neighbor */
+  union {               /* in order of testing */
+   realT   area;        /* area of facet, only in io_r.c if  f.isarea */
+   facetT *replace;     /* replacement facet for qh.NEWfacets with f.visible
+                             NULL if qh_mergedegen_redundant, interior, or !NEWfacets */
+   facetT *samecycle;   /* cycle of facets from the same visible/horizon intersection,
+                             if ->newfacet */
+   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
+   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
+   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
+  }f;
+  coordT  *center;      /* set according to qh.CENTERtype */
+                        /*   qh_ASnone:    no center (not MERGING) */
+                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
+                        /*                 assumed qh_AScentrum while merging */
+                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
+                        /* after constructing the hull, it may be changed (qh_clearcenter) */
+                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
+  facetT  *previous;    /* previous facet in the facet_list or NULL, for C++ interface */
+  facetT  *next;        /* next facet in the facet_list or facet_tail */
+  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
+                           if simplicial, 1st vertex was apex/furthest
+                           qh_reduce_vertices removes extraneous vertices via qh_remove_extravertices
+                           if f.visible, vertices may be on qh.del_vertices */
+  setT    *ridges;      /* explicit ridges for nonsimplicial facets or nonsimplicial neighbors.
+                           For simplicial facets, neighbors define the ridges
+                           qh_makeridges() converts simplicial facets by creating ridges prior to merging
+                           If qh.NEWtentative, new facets have horizon ridge, but not vice versa
+                           if f.visible && qh.NEWfacets, ridges is empty */
+  setT    *neighbors;   /* neighbors of the facet.  Neighbors may be f.visible
+                           If simplicial, the kth neighbor is opposite the kth vertex and the
+                           first neighbor is the horizon facet for the first vertex.
+                           dupridges marked by qh_DUPLICATEridge (0x01) and qh_MERGEridge (0x02)
+                           if f.visible && qh.NEWfacets, neighbors is empty */
+  setT    *outsideset;  /* set of points outside this facet
+                           if non-empty, last point is furthest
+                           if NARROWhull, includes coplanars (less than qh.MINoutside) for partitioning*/
+  setT    *coplanarset; /* set of points coplanar with this facet
+                           >= qh.min_vertex and <= facet->max_outside
+                           a point is assigned to the furthest facet
+                           if non-empty, last point is furthest away */
+  unsigned int visitid; /* visit_id, for visiting all neighbors,
+                           all uses are independent */
+  unsigned int id;      /* unique identifier from qh.facet_id, 1..qh.facet_id, 0 is sentinel, printed as 'f%d' */
+  unsigned int nummerge:9; /* number of merges */
+#define qh_MAXnummerge 511 /* 2^9-1 */
+                        /* 23 flags (at most 23 due to nummerge), printed by "flags:" in io_r.c */
+  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
+                          /*   all tricoplanars share the same apex */
+                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
+                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
+                          /*   if ->degenerate, does not span facet (one logical ridge) */
+                          /*   during qh_triangulate, f.trivisible points to original facet */
+  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new/qh.first_newfacet or merged) */
+  flagT    visible:1;   /* True if visible facet (will be deleted) */
+  flagT    toporient:1; /* True if created with top orientation
+                           after merging, use ridge orientation */
+  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
+  flagT    seen:1;      /* used to perform operations only once, like visitid */
+  flagT    seen2:1;     /* used to perform operations only once, like visitid */
+  flagT    flipped:1;   /* True if facet is flipped */
+  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
+  flagT    notfurthest:1; /* True if last point of outsideset is not furthest */
+
+/*-------- flags primarily for output ---------*/
+  flagT    good:1;      /* True if a facet marked good for output */
+  flagT    isarea:1;    /* True if facet->f.area is defined */
+
+/*-------- flags for merging ------------------*/
+  flagT    dupridge:1;  /* True if facet has one or more dupridge in a new facet (qh_matchneighbor),
+                             a dupridge has a subridge shared by more than one new facet */
+  flagT    mergeridge:1; /* True if facet or neighbor has a qh_MERGEridge (qh_mark_dupridges)
+                            ->normal defined for mergeridge and mergeridge2 */
+  flagT    mergeridge2:1; /* True if neighbor has a qh_MERGEridge (qh_mark_dupridges) */
+  flagT    coplanarhorizon:1;  /* True if horizon facet is coplanar at last use */
+  flagT     mergehorizon:1; /* True if will merge into horizon (its first neighbor w/ f.coplanarhorizon). */
+  flagT     cycledone:1;/* True if mergecycle_all already done */
+  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
+  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar
+                             Set by qh_updatetested if more than qh_MAXnewcentrum extra vertices
+                             Set by qh_mergefacet if |maxdist| > qh.WIDEfacet */
+  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
+  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
+  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset)
+                         Maybe merge degenerate and redundant to gain another flag */
+};
+
+
+/*----------------------------------
+
+  ridgeT
+    defines a ridge
+
+  notes:
+  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
+  facets are non-simplicial, there may be more than one ridge between
+  two facets.  E.G. a 4-d hypercube has two triangles between each pair
+  of neighboring facets.
+
+  topological information:
+    vertices            a set of vertices
+    top,bottom          neighboring facets with orientation
+
+  geometric information:
+    tested              True if ridge is clearly convex
+    nonconvex           True if ridge is non-convex
+*/
+/* QhullRidge.cpp -- Update static initializer list for s_empty_ridge if add or remove fields */
+struct ridgeT {
+  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
+                           NULL if a degen ridge (matchsame) */
+  facetT  *top;         /* top facet for this ridge */
+  facetT  *bottom;      /* bottom facet for this ridge
+                        ridge oriented by odd/even vertex order and top/bottom */
+  unsigned int id;      /* unique identifier.  Same size as vertex_id, printed as 'r%d' */
+  flagT    seen:1;      /* used to perform operations only once */
+  flagT    tested:1;    /* True when ridge is tested for convexity by centrum or opposite vertices */
+  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
+                           only one ridge between neighbors may have nonconvex */
+  flagT    mergevertex:1; /* True if pending qh_appendvertexmerge due to
+                             qh_maybe_duplicateridge or qh_maybe_duplicateridges
+                             disables check for duplicate vertices in qh_checkfacet */
+  flagT    mergevertex2:1; /* True if qh_drop_mergevertex of MRGvertices, printed but not used */
+  flagT    simplicialtop:1; /* True if top was simplicial (original vertices) */
+  flagT    simplicialbot:1; /* True if bottom was simplicial (original vertices)
+                             use qh_test_centrum_merge if top and bot, need to retest since centrum may change */
+};
+
+/*----------------------------------
+
+  vertexT
+     defines a vertex
+
+  topological information:
+    next,previous       doubly-linked list of all vertices
+    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
+
+  geometric information:
+    point               array of DIM3 coordinates
+*/
+/* QhullVertex.cpp -- Update static initializer list for s_empty_vertex if add or remove fields */
+struct vertexT {
+  vertexT *next;        /* next vertex in vertex_list or vertex_tail */
+  vertexT *previous;    /* previous vertex in vertex_list or NULL, for C++ interface */
+  pointT  *point;       /* hull_dim coordinates (coordT) */
+  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
+                           initialized in io_r.c or after first merge
+                           qh_update_vertices for qh_addpoint or qh_triangulate
+                           updated by merges
+                           qh_order_vertexneighbors by 2-d (orientation) 3-d (adjacency), n-d (f.visitid,id) */
+  unsigned int id;      /* unique identifier, 1..qh.vertex_id,  0 for sentinel, printed as 'r%d' */
+  unsigned int visitid; /* for use with qh.vertex_visit, size must match */
+  flagT    seen:1;      /* used to perform operations only once */
+  flagT    seen2:1;     /* another seen flag */
+  flagT    deleted:1;   /* vertex will be deleted via qh.del_vertices */
+  flagT    delridge:1;  /* vertex belonged to a deleted ridge, cleared by qh_reducevertices */
+  flagT    newfacet:1;  /* true if vertex is in a new facet
+                           vertex is on qh.newvertex_list and it has a facet on qh.newfacet_list
+                           or vertex is on qh.newvertex_list due to qh_newvertices while merging
+                           cleared by qh_resetlists */
+  flagT    partitioned:1; /* true if deleted vertex has been partitioned */
+};
+
+/*======= -global variables -qh ============================*/
+
+/*----------------------------------
+
+  qhT
+   All global variables for qhull are in qhT.  It includes qhmemT, qhstatT, and rbox globals
+
+   This version of Qhull is reentrant, but it is not thread-safe.
+
+   Do not run separate threads on the same instance of qhT.
+
+   QHULL_LIB_CHECK checks that a program and the corresponding
+   qhull library were built with the same type of header files.
+
+   QHULL_LIB_TYPE is QHULL_NON_REENTRANT, QHULL_QH_POINTER, or QHULL_REENTRANT
+*/
+
+#define QHULL_NON_REENTRANT 0
+#define QHULL_QH_POINTER 1
+#define QHULL_REENTRANT 2
+
+#define QHULL_LIB_TYPE QHULL_REENTRANT
+
+#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), SETbasesize, sizeof(qhmemT));
+#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
+
+struct qhT {
+
+/*----------------------------------
+
+  qh constants
+    configuration flags and constants for Qhull
+
+  notes:
+    The user configures Qhull by defining flags.  They are
+    copied into qh by qh_setflags().  qh-quick_r.htm#options defines the flags.
+*/
+  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
+  boolT ALLOWshort;       /* true 'Qa' allow input with fewer or more points than coordinates */
+  boolT ALLOWwarning;     /* true 'Qw' if allow option warnings */
+  boolT ALLOWwide;        /* true 'Q12' if allow wide facets and wide dupridges, c.f. qh_WIDEmaxoutside */
+  boolT ANGLEmerge;       /* true 'Q1' if sort potential merges by type/angle instead of type/distance  */
+  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
+  realT MINoutside;       /*   Minimum distance for an outside point ('Wn' or 2*qh.MINvisible) */
+  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
+  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
+                             for improving precision in Delaunay triangulations */
+  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
+  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
+  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
+  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
+  boolT CHECKduplicates;  /* true 'Q17' if qh_maybe_duplicateridges after each qh_mergefacet */
+  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
+  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
+  realT postmerge_cos;    /*   'An'    cos_max when post merging */
+  boolT DELAUNAY;         /* true 'd' or 'v' if computing DELAUNAY triangulation */
+  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
+  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
+  boolT FLUSHprint;       /* true 'Tf' if flush after qh_fprintf for segfaults */
+  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
+  int   GOODpoint;        /* 'QGn' or 'QG-n' (n+1, n-1), good facet if visible from point n (or not) */
+  pointT *GOODpointp;     /*   the actual point */
+  boolT GOODthreshold;    /* true 'Pd/PD' if qh.lower_threshold/upper_threshold defined
+                             set if qh.UPPERdelaunay (qh_initbuild)
+                             false if qh.SPLITthreshold */
+  int   GOODvertex;       /* 'QVn' or 'QV-n' (n+1, n-1), good facet if vertex for point n (or not) */
+  pointT *GOODvertexp;     /*   the actual point */
+  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
+  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
+  int   IStracing;        /* 'Tn' trace execution, 0=none, 1=least, 4=most, -1=events */
+  int   KEEParea;         /* 'PAn' number of largest facets to keep */
+  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
+  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
+                              set automatically if 'd Qc' */
+  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
+  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
+  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
+  int   MAXwide;          /* 'QWn' max ratio for wide facet, otherwise error unless Q12-allow-wide */
+  boolT MERGEexact;       /* true 'Qx' if exact merges (concave, degen, dupridge, flipped)
+                             tested by qh_checkzero and qh_test_*_merge */
+  boolT MERGEindependent; /* true if merging independent sets of coplanar facets. 'Q2' disables */
+  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
+  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
+  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
+  boolT MERGEpinched;     /* true 'Q14' if merging pinched vertices due to dupridge */
+  boolT MERGEvertices;    /* true if merging redundant vertices, 'Q3' disables or qh.hull_dim > qh_DIMmergeVertex */
+  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
+  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
+  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning, qh_check_points may fail */
+  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
+  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
+  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
+  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
+  boolT POSTmerge;        /* true if merging after buildhull ('Cn' or 'An') */
+  boolT PREmerge;         /* true if merging during buildhull ('C-n' or 'A-n') */
+                        /* NOTE: some of these names are similar to qh_PRINT names */
+  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
+  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
+  int   PRINTdim;         /* print dimension for Geomview output */
+  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
+  boolT PRINTgood;        /* true 'Pg' if printing good facets
+                             PGood set if 'd', 'PAn', 'PFn', 'PMn', 'QGn', 'QG-n', 'QVn', or 'QV-n' */
+  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
+  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
+  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
+  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
+  boolT PRINTouter;       /* true 'Go' if printing outer planes */
+  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
+  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
+  boolT PRINTridges;      /* true 'Gr' if print ridges */
+  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
+  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
+  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
+  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
+  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
+                             need projectinput() for Delaunay in qh_init_B */
+  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
+  boolT RANDOMdist;       /* true 'Rn' if randomly change distplane and setfacetplane */
+  realT RANDOMfactor;     /*    maximum random perturbation */
+  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
+  realT RANDOMb;
+  boolT RANDOMoutside;    /* true 'Qr' if select a random outside point */
+  int   REPORTfreq;       /* 'TFn' buildtracing reports every n facets */
+  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
+  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
+  int   ROTATErandom;     /* 'QRn' n<-1 random seed, n==-1 time is seed, n==0 random rotation by time, n>0 rotate input */
+  boolT SCALEinput;       /* true 'Qbk' if scaling input */
+  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
+  boolT SETroundoff;      /* true 'En' if qh.DISTround is predefined */
+  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout, qh_check_points may fail */
+  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
+  boolT SPLITthresholds;  /* true 'Pd/PD' if upper_/lower_threshold defines a region
+                               else qh.GOODthresholds
+                               set if qh.DELAUNAY (qh_initbuild)
+                               used only for printing (!for qh.ONLYgood) */
+  int   STOPadd;          /* 'TAn' 1+n for stop after adding n vertices */
+  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
+                          /*       also used by qh_build_withresart for err exit*/
+  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
+                                        adding point n */
+  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
+  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
+  int   TRACElevel;       /* 'Tn' conditional IStracing level */
+  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
+  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex, use qh_IDunknown (-1) after qh_buildhull and qh_postmerge */
+  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
+  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
+  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
+  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
+  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
+  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
+  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
+  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
+  boolT VORONOI;          /* true 'v' if computing Voronoi diagram, also sets qh.DELAUNAY */
+
+  /*--------input constants ---------*/
+  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
+  boolT DOcheckmax;       /* true if calling qh_check_maxout (!qh.SKIPcheckmax && qh.MERGING) */
+  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
+  coordT *feasible_point;  /*    as coordinates, both malloc'd */
+  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io_r.c */
+  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
+  int   hull_dim;         /* dimension of hull, set by initbuffers */
+  int   input_dim;        /* dimension of input, set by initbuffers */
+  int   num_points;       /* number of input points */
+  pointT *first_point;    /* array of input points, see POINTSmalloc */
+  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
+  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
+  boolT input_malloc;     /* true if qh.input_points malloc'd */
+  char  qhull_command[256];/* command line that invoked this program */
+  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
+  char  rbox_command[256]; /* command line that produced the input points */
+  char  qhull_options[512];/* descriptive list of options */
+  int   qhull_optionlen;  /*    length of last line */
+  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
+  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
+  int   run_id;           /* non-zero, random identifier for this instance of qhull */
+  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
+  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx' and not post-merging or 'A-n'.  Sets ZEROall_ok */
+  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
+                             must set either GOODthreshold or SPLITthreshold
+                             if qh.DELAUNAY, default is 0.0 for upper envelope (qh_initbuild) */
+  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
+  realT *upper_bound;     /* scale point[k] to new upper bound */
+  realT *lower_bound;     /* scale point[k] to new lower bound
+                             project if both upper_ and lower_bound == 0 */
+
+/*----------------------------------
+
+  qh precision constants
+    precision constants for Qhull
+
+  notes:
+    qh_detroundoff [geom2_r.c] computes the maximum roundoff error for distance
+    and other computations.  It also sets default values for the
+    qh constants above.
+*/
+  realT ANGLEround;       /* max round off error for angles */
+  realT centrum_radius;   /* max centrum radius for convexity ('Cn' + 2*qh.DISTround) */
+  realT cos_max;          /* max cosine for convexity (roundoff added) */
+  realT DISTround;        /* max round off error for distances, qh.SETroundoff ('En') overrides qh_distround */
+  realT MAXabs_coord;     /* max absolute coordinate */
+  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
+  realT MAXoutside;       /* max target for qh.max_outside/f.maxoutside, base for qh_RATIO...
+                             recomputed at qh_addpoint, unrelated to qh_MAXoutside */
+  realT MAXsumcoord;      /* max sum of coordinates */
+  realT MAXwidth;         /* max rectilinear width of point coordinates */
+  realT MINdenom_1;       /* min. abs. value for 1/x */
+  realT MINdenom;         /*    use divzero if denominator < MINdenom */
+  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
+  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
+  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
+  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
+  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
+  realT ONEmerge;         /* max distance for merging simplicial facets */
+  realT outside_err;      /* application's epsilon for coplanar points
+                             qh_check_bestdist() qh_check_points() reports error if point outside */
+  realT WIDEfacet;        /* size of wide facet for skipping ridge in
+                             area computation and locking centrum */
+  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
+
+/*----------------------------------
+
+  qh internal constants
+    internal constants for Qhull
+*/
+  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
+  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
+  char    jmpXtra[40];    /* extra bytes in case jmp_buf is defined wrong by compiler */
+  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
+  char    jmpXtra2[40];   /* extra bytes in case jmp_buf is defined wrong by compiler*/
+  FILE *  fin;            /* pointer to input file, init by qh_initqhull_start2 */
+  FILE *  fout;           /* pointer to output file */
+  FILE *  ferr;           /* pointer to error file */
+  pointT *interior_point; /* center point of the initial simplex*/
+  int     normal_size;    /* size in bytes for facet normals and point coords */
+  int     center_size;    /* size in bytes for Voronoi centers */
+  int     TEMPsize;       /* size for small, temporary sets (in quick mem) */
+
+/*----------------------------------
+
+  qh facet and vertex lists
+    defines lists of facets, new facets, visible facets, vertices, and
+    new vertices.  Includes counts, next ids, and trace ids.
+  see:
+    qh_resetlists()
+*/
+  facetT *facet_list;     /* first facet */
+  facetT *facet_tail;     /* end of facet_list (dummy facet with id 0 and next==NULL) */
+  facetT *facet_next;     /* next facet for buildhull()
+                             previous facets do not have outside sets
+                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
+  facetT *newfacet_list;  /* list of new facets to end of facet_list
+                             qh_postmerge sets newfacet_list to facet_list */
+  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
+                             end of visible list if !facet->visible, same as newfacet_list
+                             qh_findhorizon sets visible_list at end of facet_list
+                             qh_willdelete prepends to visible_list
+                             qh_triangulate appends mirror facets to visible_list at end of facet_list
+                             qh_postmerge sets visible_list to facet_list
+                             qh_deletevisible deletes the visible facets */
+  int       num_visible;  /* current number of visible facets */
+  unsigned int tracefacet_id; /* set at init, then can print whenever */
+  facetT  *tracefacet;    /*   set in newfacet/mergefacet, undone in delfacet and qh_errexit */
+  unsigned int traceridge_id; /* set at init, then can print whenever */
+  ridgeT  *traceridge;    /*   set in newridge, undone in delridge, errexit, errexit2, makenew_nonsimplicial, mergecycle_ridges */
+  unsigned int tracevertex_id; /* set at buildtracing, can print whenever */
+  vertexT *tracevertex;   /*   set in newvertex, undone in delvertex and qh_errexit */
+  vertexT *vertex_list;   /* list of all vertices, to vertex_tail */
+  vertexT *vertex_tail;   /*      end of vertex_list (dummy vertex with ID 0, next NULL) */
+  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
+                             all vertices have 'newfacet' set */
+  int   num_facets;       /* number of facets in facet_list
+                             includes visible faces (num_visible) */
+  int   num_vertices;     /* number of vertices in facet_list */
+  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
+                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
+  int   num_good;         /* number of good facets (after qh_findgood_all or qh_markkeep) */
+  unsigned int facet_id;  /* ID of next, new facet from newfacet() */
+  unsigned int ridge_id;  /* ID of next, new ridge from newridge() */
+  unsigned int vertex_id; /* ID of next, new vertex from newvertex() */
+  unsigned int first_newfacet; /* ID of first_newfacet for qh_buildcone, or 0 if none */
+
+/*----------------------------------
+
+  qh global variables
+    defines minimum and maximum distances, next visit ids, several flags,
+    and other global variables.
+    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
+*/
+  unsigned long hulltime; /* ignore time to set up input and randomize */
+                          /*   use 'unsigned long' to avoid wrap-around errors */
+  boolT ALLOWrestart;     /* true if qh_joggle_restart can use qh.restartexit */
+  int   build_cnt;        /* number of calls to qh_initbuild */
+  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
+  int   furthest_id;      /* pointid of furthest point, for tracing */
+  int   last_errcode;     /* last errcode from qh_fprintf, reset in qh_build_withrestart */
+  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
+  pointT *coplanar_apex;  /* last apex declared a coplanar point by qh_getpinchedmerges, prevents infinite loop */
+  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
+  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
+  boolT isRenameVertex;   /* true during qh_merge_pinchedvertices, disables duplicate ridge vertices in qh_checkfacet */
+  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input. 'QJ'/'QJ0.0' sets default (qh_detjoggle) */
+  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
+  realT max_outside;      /* maximum distance from a point to a facet,
+                               before roundoff, not simplicial vertices
+                               actual outer plane is +DISTround and
+                               computed outer plane is +2*DISTround */
+  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
+                               before roundoff, due to a merge */
+  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
+                               before roundoff, due to a merge
+                               if qh.JOGGLEmax, qh_makenewplanes sets it
+                               recomputed if qh.DOcheckmax, default -qh.DISTround */
+  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
+                              from qh_makecone/qh_attachnewfacets to qh_resetlists */
+  boolT NEWtentative;     /* true while new facets are tentative due to !qh.IGNOREpinched or qh.ONLYgood
+                              from qh_makecone to qh_attachnewfacets */
+  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
+  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
+  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp.  See qh.ERREXITcalled */
+  realT PRINTcradius;     /* radius for printing centrums */
+  realT PRINTradius;      /* radius for printing vertex spheres and points */
+  boolT POSTmerging;      /* true when post merging */
+  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
+  int   printoutnum;      /* number of facets printed */
+  unsigned int repart_facetid; /* previous facetid to prevent recursive qh_partitioncoplanar+qh_partitionpoint */
+  int   retry_addpoint;   /* number of retries of qh_addpoint due to merging pinched vertices */
+  boolT QHULLfinished;    /* True after qhull() is finished */
+  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
+  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
+  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
+  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
+  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qh_check_maxout) */
+  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
+
+/*----------------------------------
+
+  qh global sets
+    defines sets for merging, initial simplex, hashing, extra input points,
+    and deleted vertices
+*/
+  setT *facet_mergeset;   /* temporary set of merges to be done */
+  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
+  setT *vertex_mergeset;  /* temporary set of vertex merges */
+  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
+                             size is setsize() */
+  setT *other_points;     /* additional points */
+  setT *del_vertices;     /* vertices to partition and delete with visible
+                             facets.  v.deleted is set for checkfacet */
+
+/*----------------------------------
+
+  qh global buffers
+    defines buffers for maxtrix operations, input, and error messages
+*/
+  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom_r.c */
+  coordT **gm_row;        /* array of gm_matrix rows */
+  char* line;             /* malloc'd input line of maxline+1 chars */
+  int maxline;
+  coordT *half_space;     /* malloc'd input array for halfspace (qh.normal_size+coordT) */
+  coordT *temp_malloc;    /* malloc'd input array for points */
+
+/*----------------------------------
+
+  qh static variables
+    defines static variables for individual functions
+
+  notes:
+    do not use 'static' within a function.  Multiple instances of qhull
+    may exist.
+
+    do not assume zero initialization, 'QPn' may cause a restart
+*/
+  boolT ERREXITcalled;    /* true during qh_errexit (prevents duplicate calls).  see qh.NOerrexit */
+  boolT firstcentrum;     /* for qh_printcentrum */
+  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
+  setT *coplanarfacetset; /* set of coplanar facets for searching qh_findbesthorizon() */
+  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
+  realT last_high;
+  realT last_newhigh;
+  realT lastcpu;          /* for qh_buildtracing */
+  int   lastfacets;       /*   last qh.num_facets */
+  int   lastmerges;       /*   last zzval_(Ztotmerge) */
+  int   lastplanes;       /*   last zzval_(Zsetplane) */
+  int   lastdist;         /*   last zzval_(Zdistplane) */
+  unsigned int lastreport; /*  last qh.facet_id */
+  int mergereport;        /* for qh_tracemerging */
+  setT *old_tempstack;    /* for saving qh->qhmem.tempstack in save_qhull */
+  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
+
+/*----------------------------------
+
+  qh memory management, rbox globals, and statistics
+
+  Replaces global data structures defined for libqhull
+*/
+  int     last_random;    /* Last random number from qh_rand (random_r.c) */
+  jmp_buf rbox_errexit;   /* errexit from rboxlib_r.c, defined by qh_rboxpoints() only */
+  char    jmpXtra3[40];   /* extra bytes in case jmp_buf is defined wrong by compiler */
+  int     rbox_isinteger;
+  double  rbox_out_offset;
+  void *  cpp_object;     /* C++ pointer.  Currently used by RboxPoints.qh_fprintf_rbox */
+  void *  cpp_other;      /* C++ pointer.  Reserved for other users */
+  void *  cpp_user;       /* C++ pointer.  Currently used by QhullUser.qh_fprintf */
+
+  /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */
+  qhmemT  qhmem;          /* Qhull managed memory (mem_r.h) */
+  /* After qhmem because its size depends on the number of statistics */
+  qhstatT qhstat;         /* Qhull statistics (stat_r.h) */
+};
+
+/*=========== -macros- =========================*/
+
+/*----------------------------------
+
+  otherfacet_(ridge, facet)
+    return neighboring facet for a ridge in facet
+*/
+#define otherfacet_(ridge, facet) \
+                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
+
+/*----------------------------------
+
+  getid_(p)
+    return int ID for facet, ridge, or vertex
+    return qh_IDunknown(-1) if NULL
+    return 0 if facet_tail or vertex_tail
+*/
+#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
+
+/*============== FORALL macros ===================*/
+
+/*----------------------------------
+
+  FORALLfacets { ... }
+    assign 'facet' to each facet in qh.facet_list
+
+  notes:
+    uses 'facetT *facet;'
+    assumes last facet is a sentinel
+    assumes qh defined
+
+  see:
+    FORALLfacet_( facetlist )
+*/
+#define FORALLfacets for (facet=qh->facet_list;facet && facet->next;facet=facet->next)
+
+/*----------------------------------
+
+  FORALLpoints { ... }
+    assign 'point' to each point in qh.first_point, qh.num_points
+
+  notes:
+    assumes qh defined
+
+  declare:
+    coordT *point, *pointtemp;
+*/
+#define FORALLpoints FORALLpoint_(qh, qh->first_point, qh->num_points)
+
+/*----------------------------------
+
+  FORALLpoint_(qh, points, num) { ... }
+    assign 'point' to each point in points array of num points
+
+  declare:
+    coordT *point, *pointtemp;
+*/
+#define FORALLpoint_(qh, points, num) for (point=(points), \
+      pointtemp= (points)+qh->hull_dim*(num); point < pointtemp; point += qh->hull_dim)
+
+/*----------------------------------
+
+  FORALLvertices { ... }
+    assign 'vertex' to each vertex in qh.vertex_list
+
+  declare:
+    vertexT *vertex;
+
+  notes:
+    assumes qh.vertex_list terminated by NULL or a sentinel (v.next==NULL)
+    assumes qh defined
+*/
+#define FORALLvertices for (vertex=qh->vertex_list;vertex && vertex->next;vertex= vertex->next)
+
+/*----------------------------------
+
+  FOREACHfacet_( facets ) { ... }
+    assign 'facet' to each facet in facets
+
+  declare:
+    facetT *facet, **facetp;
+
+  notes:
+    assumes set is not modified
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
+
+/*----------------------------------
+
+  FOREACHneighbor_( facet ) { ... }
+    assign 'neighbor' to each neighbor in facet->neighbors
+
+  FOREACHneighbor_( vertex ) { ... }
+    assign 'neighbor' to each neighbor in vertex->neighbors
+
+  declare:
+    facetT *neighbor, **neighborp;
+
+  notes:
+    assumes set is not modified
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
+
+/*----------------------------------
+
+  FOREACHpoint_( points ) { ... }
+    assign 'point' to each point in points set
+
+  declare:
+    pointT *point, **pointp;
+
+  notes:
+    assumes set is not modified
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
+
+/*----------------------------------
+
+  FOREACHridge_( ridges ) { ... }
+    assign 'ridge' to each ridge in ridges set
+
+  declare:
+    ridgeT *ridge, **ridgep;
+
+  notes:
+    assumes set is not modified
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
+
+/*----------------------------------
+
+  FOREACHvertex_( vertices ) { ... }
+    assign 'vertex' to each vertex in vertices set
+
+  declare:
+    vertexT *vertex, **vertexp;
+
+  notes:
+    assumes set is not modified
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
+
+/*----------------------------------
+
+  FOREACHfacet_i_(qh, facets ) { ... }
+    assign 'facet' and 'facet_i' for each facet in facets set
+
+  declare:
+    facetT *facet;
+    int     facet_n, facet_i;
+
+  see:
+    FOREACHsetelement_i_
+*/
+#define FOREACHfacet_i_(qh, facets)    FOREACHsetelement_i_(qh, facetT, facets, facet)
+
+/*----------------------------------
+
+  FOREACHneighbor_i_(qh, facet ) { ... }
+    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
+
+  declare:
+    facetT *neighbor;
+    int     neighbor_n, neighbor_i;
+
+  notes:
+    see FOREACHsetelement_i_
+    for facet neighbors of vertex, need to define a new macro
+*/
+#define FOREACHneighbor_i_(qh, facet)  FOREACHsetelement_i_(qh, facetT, facet->neighbors, neighbor)
+
+/*----------------------------------
+
+  FOREACHpoint_i_(qh, points ) { ... }
+    assign 'point' and 'point_i' for each point in points set
+
+  declare:
+    pointT *point;
+    int     point_n, point_i;
+
+  see:
+    FOREACHsetelement_i_
+*/
+#define FOREACHpoint_i_(qh, points)    FOREACHsetelement_i_(qh, pointT, points, point)
+
+/*----------------------------------
+
+  FOREACHridge_i_(qh, ridges ) { ... }
+    assign 'ridge' and 'ridge_i' for each ridge in ridges set
+
+  declare:
+    ridgeT *ridge;
+    int     ridge_n, ridge_i;
+
+  see:
+    FOREACHsetelement_i_
+*/
+#define FOREACHridge_i_(qh, ridges)    FOREACHsetelement_i_(qh, ridgeT, ridges, ridge)
+
+/*----------------------------------
+
+  FOREACHvertex_i_(qh, vertices ) { ... }
+    assign 'vertex' and 'vertex_i' for each vertex in vertices set
+
+  declare:
+    vertexT *vertex;
+    int     vertex_n, vertex_i;
+
+  see:
+    FOREACHsetelement_i_
+*/
+#define FOREACHvertex_i_(qh, vertices) FOREACHsetelement_i_(qh, vertexT, vertices, vertex)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/********* -libqhull_r.c prototypes (duplicated from qhull_ra.h) **********************/
+
+void    qh_qhull(qhT *qh);
+boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
+void    QH_NORETURN qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet);
+void    qh_printsummary(qhT *qh, FILE *fp);
+
+/********* -user_r.c prototypes (alphabetical) **********************/
+
+void    QH_NORETURN qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge);
+void    qh_errprint(qhT *qh, const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
+int     qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
+                char *qhull_cmd, FILE *outfile, FILE *errfile);
+void    qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall);
+void    qh_printhelp_degenerate(qhT *qh, FILE *fp);
+void    qh_printhelp_internal(qhT *qh, FILE *fp);
+void    qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle);
+void    qh_printhelp_singular(qhT *qh, FILE *fp);
+void    qh_printhelp_topology(qhT *qh, FILE *fp);
+void    qh_printhelp_wide(qhT *qh, FILE *fp);
+void    qh_user_memsizes(qhT *qh);
+
+/********* -usermem_r.c prototypes (alphabetical) **********************/
+void    QH_NORETURN qh_exit(int exitcode);
+void    qh_fprintf_stderr(int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(2, 3);
+void    qh_free(void *mem);
+void   *qh_malloc(size_t size);
+
+/********* -userprintf_r.c and userprintf_rbox_r.c prototypes **********************/
+void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(4, 5);
+void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(4, 5);
+
+/***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/
+
+facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
+                     boolT bestoutside, boolT newfacets, boolT noupper,
+                     realT *dist, boolT *isoutside, int *numpart);
+facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
+                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
+boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
+void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
+void    qh_projectinput(qhT *qh);
+void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
+void    qh_rotateinput(qhT *qh, realT **rows);
+void    qh_scaleinput(qhT *qh);
+void    qh_setdelaunay(qhT *qh, int dim, int count, pointT *points);
+coordT  *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
+
+/***** -global_r.c prototypes (alphabetical) ***********************/
+
+unsigned long qh_clock(qhT *qh);
+void    qh_checkflags(qhT *qh, char *command, char *hiddenflags);
+void    qh_clear_outputflags(qhT *qh);
+void    qh_freebuffers(qhT *qh);
+void    qh_freeqhull(qhT *qh, boolT allmem);
+void    qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
+void    qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
+void    qh_init_qhull_command(qhT *qh, int argc, char *argv[]);
+void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
+void    qh_initflags(qhT *qh, char *command);
+void    qh_initqhull_buffers(qhT *qh);
+void    qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
+void    qh_initqhull_mem(qhT *qh);
+void    qh_initqhull_outputflags(qhT *qh);
+void    qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
+void    qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
+void    qh_initthresholds(qhT *qh, char *command);
+void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
+void    qh_option(qhT *qh, const char *option, int *i, realT *r);
+void    qh_zero(qhT *qh, FILE *errfile);
+
+/***** -io_r.c prototypes (duplicated from io_r.h) ***********************/
+
+void    qh_dfacet(qhT *qh, unsigned int id);
+void    qh_dvertex(qhT *qh, unsigned int id);
+void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
+void    qh_produce_output(qhT *qh);
+coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
+
+
+/********* -poly_r.c/poly2_r.c prototypes (duplicated from poly_r.h) **********************/
+
+void    qh_check_output(qhT *qh);
+void    qh_check_points(qhT *qh);
+setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
+facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
+           realT *bestdist, boolT *isoutside);
+vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
+pointT *qh_point(qhT *qh, int id);
+setT   *qh_pointfacet(qhT *qh /* qh.facet_list */);
+int     qh_pointid(qhT *qh, pointT *point);
+setT   *qh_pointvertex(qhT *qh /* qh.facet_list */);
+void    qh_setvoronoi_all(qhT *qh);
+void    qh_triangulate(qhT *qh /* qh.facet_list */);
+
+/********* -rboxlib_r.c prototypes **********************/
+int     qh_rboxpoints(qhT *qh, char* rbox_command);
+void    QH_NORETURN qh_errexit_rbox(qhT *qh, int exitcode);
+
+/************************** accessors.c prototypes ******************************/
+
+#define QH_GETTER(TYPE, FIELD) TYPE qh_get_##FIELD(const qhT *qh)
+#define QH_SETTER(TYPE, FIELD) void qh_set_##FIELD(qhT *qh, TYPE _val_)
+QH_GETTER(facetT*, facet_list);
+QH_GETTER(pointT*, first_point);
+QH_GETTER(int, hull_dim);
+QH_GETTER(int, num_facets);
+QH_GETTER(int, num_points);
+QH_GETTER(int, num_vertices);
+QH_GETTER(vertexT*, vertex_list);
+QH_GETTER(realT, totarea);
+QH_GETTER(realT, totvol);
+QH_GETTER(boolT, hasAreaVolume);
+QH_SETTER(boolT, hasAreaVolume);
+QH_GETTER(boolT, hasTriangulation);
+QH_SETTER(boolT, hasTriangulation);
+QH_GETTER(int, num_good);
+QH_GETTER(setT*, del_vertices);
+QH_GETTER(int, input_dim);
+QH_GETTER(boolT, DELAUNAY);
+QH_GETTER(boolT, SCALElast);
+QH_GETTER(boolT, KEEPcoplanar);
+QH_GETTER(boolT, MERGEexact);
+QH_GETTER(boolT, NOerrexit);
+QH_GETTER(boolT, PROJECTdelaunay);
+QH_GETTER(boolT, ATinfinity);
+QH_GETTER(boolT, UPPERdelaunay);
+QH_GETTER(int, normal_size);
+QH_GETTER(int, num_visible);
+QH_GETTER(int, center_size);
+QH_GETTER(const char *, qhull_command);
+QH_GETTER(facetT*, facet_tail);
+QH_GETTER(vertexT*, vertex_tail);
+QH_GETTER(unsigned int, facet_id);
+QH_GETTER(unsigned int, visit_id);
+QH_GETTER(unsigned int, vertex_visit);
+QH_GETTER(pointT*, input_points);
+QH_GETTER(coordT*, feasible_point);
+QH_GETTER(realT, last_low);
+QH_GETTER(realT, last_high);
+QH_GETTER(realT, last_newhigh);
+QH_GETTER(realT, max_outside);
+QH_GETTER(realT, MINoutside);
+QH_GETTER(realT, DISTround);
+QH_GETTER(setT*, other_points);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFlibqhull */
diff --git a/vendor/qhull/libqhull_r/mem_r.c b/vendor/qhull/libqhull_r/mem_r.c
new file mode 100644
index 0000000..57e80ed
--- /dev/null
+++ b/vendor/qhull/libqhull_r/mem_r.c
@@ -0,0 +1,566 @@
+/*
  ---------------------------------
+
+  mem_r.c
+    memory management routines for qhull
+
+  See libqhull/mem.c for a standalone program.
+
+  To initialize memory:
+
+    qh_meminit(qh, stderr);
+    qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
+    qh_memsize(qh, (int)sizeof(facetT));
+    qh_memsize(qh, (int)sizeof(facetT));
+    ...
+    qh_memsetup(qh);
+
+  To free up all memory buffers:
+    qh_memfreeshort(qh, &curlong, &totlong);
+
+  if qh_NOmem,
+    malloc/free is used instead of mem_r.c
+
+  notes:
+    uses Quickfit algorithm (freelists for commonly allocated sizes)
+    assumes small sizes for freelists (it discards the tail of memory buffers)
+
+  see:
+    qh-mem_r.htm and mem_r.h
+    global_r.c (qh_initbuffers) for an example of using mem_r.c
+
+  Copyright (c) 1993-2020 The Geometry Center.
+  $Id: //main/2019/qhull/src/libqhull_r/mem_r.c#7 $$Change: 2953 $
+  $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#include "libqhull_r.h"  /* includes user_r.h and mem_r.h */
+
+#include 
+#include 
+#include 
+
+#ifndef qh_NOmem
+
+/*============= internal functions ==============*/
+
+static int qh_intcompare(const void *i, const void *j);
+
+/*========== functions in alphabetical order ======== */
+
+/*---------------------------------
+
+  qh_intcompare( i, j )
+    used by qsort and bsearch to compare two integers
+*/
+static int qh_intcompare(const void *i, const void *j) {
+  return(*((const int *)i) - *((const int *)j));
+} /* intcompare */
+
+
+/*----------------------------------
+
+  qh_memalloc(qh, insize )
+    returns object of insize bytes
+    qhmem is the global memory structure
+
+  returns:
+    pointer to allocated memory
+    errors if insufficient memory
+
+  notes:
+    use explicit type conversion to avoid type warnings on some compilers
+    actual object may be larger than insize
+    use qh_memalloc_() for inline code for quick allocations
+    logs allocations if 'T5'
+    caller is responsible for freeing the memory.
+    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
+
+  design:
+    if size < qh->qhmem.LASTsize
+      if qh->qhmem.freelists[size] non-empty
+        return first object on freelist
+      else
+        round up request to size of qh->qhmem.freelists[size]
+        allocate new allocation buffer if necessary
+        allocate object from allocation buffer
+    else
+      allocate object with qh_malloc() in user_r.c
+*/
+void *qh_memalloc(qhT *qh, int insize) {
+  void **freelistp, *newbuffer;
+  int idx, size, n;
+  int outsize, bufsize;
+  void *object;
+
+  if (insize<0) {
+      qh_fprintf(qh, qh->qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
+      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+  }
+  if (insize>=0 && insize <= qh->qhmem.LASTsize) {
+    idx= qh->qhmem.indextable[insize];
+    outsize= qh->qhmem.sizetable[idx];
+    qh->qhmem.totshort += outsize;
+    freelistp= qh->qhmem.freelists+idx;
+    if ((object= *freelistp)) {
+      qh->qhmem.cntquick++;
+      qh->qhmem.totfree -= outsize;
+      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
+#ifdef qh_TRACEshort
+      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
+      if (qh->qhmem.IStracing >= 5)
+          qh_fprintf(qh, qh->qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
+#endif
+      return(object);
+    }else {
+      qh->qhmem.cntshort++;
+      if (outsize > qh->qhmem.freesize) {
+        qh->qhmem.totdropped += qh->qhmem.freesize;
+        if (!qh->qhmem.curbuffer)
+          bufsize= qh->qhmem.BUFinit;
+        else
+          bufsize= qh->qhmem.BUFsize;
+        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
+          qh_fprintf(qh, qh->qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
+          qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+        }
+        *((void **)newbuffer)= qh->qhmem.curbuffer;  /* prepend newbuffer to curbuffer
+                                                    list.  newbuffer!=0 by QH6080 */
+        qh->qhmem.curbuffer= newbuffer;
+        size= ((int)sizeof(void **) + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
+        qh->qhmem.freemem= (void *)((char *)newbuffer+size);
+        qh->qhmem.freesize= bufsize - size;
+        qh->qhmem.totbuffer += bufsize - size; /* easier to check */
+        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
+        n= qh->qhmem.totshort + qh->qhmem.totfree + qh->qhmem.totdropped + qh->qhmem.freesize - outsize;
+        if (qh->qhmem.totbuffer != n) {
+            qh_fprintf(qh, qh->qhmem.ferr, 6212, "qhull internal error (qh_memalloc): short totbuffer %d != totshort+totfree... %d\n", qh->qhmem.totbuffer, n);
+            qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+        }
+      }
+      object= qh->qhmem.freemem;
+      qh->qhmem.freemem= (void *)((char *)qh->qhmem.freemem + outsize);
+      qh->qhmem.freesize -= outsize;
+      qh->qhmem.totunused += outsize - insize;
+#ifdef qh_TRACEshort
+      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
+      if (qh->qhmem.IStracing >= 5)
+          qh_fprintf(qh, qh->qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
+#endif
+      return object;
+    }
+  }else {                     /* long allocation */
+    if (!qh->qhmem.indextable) {
+      qh_fprintf(qh, qh->qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
+      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+    }
+    outsize= insize;
+    qh->qhmem.cntlong++;
+    qh->qhmem.totlong += outsize;
+    if (qh->qhmem.maxlong < qh->qhmem.totlong)
+      qh->qhmem.maxlong= qh->qhmem.totlong;
+    if (!(object= qh_malloc((size_t)outsize))) {
+      qh_fprintf(qh, qh->qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
+      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+    }
+    if (qh->qhmem.IStracing >= 5)
+      qh_fprintf(qh, qh->qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, outsize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
+  }
+  return(object);
+} /* memalloc */
+
+
+/*----------------------------------
+
+  qh_memcheck(qh)
+*/
+void qh_memcheck(qhT *qh) {
+  int i, count, totfree= 0;
+  void *object;
+
+  if (!qh) {
+    qh_fprintf_stderr(6243, "qhull internal error (qh_memcheck): qh is 0.  It does not point to a qhT\n");
+    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
+  }
+  if (qh->qhmem.ferr == 0 || qh->qhmem.IStracing < 0 || qh->qhmem.IStracing > 10 || (((qh->qhmem.ALIGNmask+1) & qh->qhmem.ALIGNmask) != 0)) {
+    qh_fprintf_stderr(6244, "qhull internal error (qh_memcheck): either qh->qhmem is overwritten or qh->qhmem is not initialized.  Call qh_meminit or qh_new_qhull before calling qh_mem routines.  ferr %p, IsTracing %d, ALIGNmask 0x%x\n",
+          (void *) qh->qhmem.ferr, qh->qhmem.IStracing, qh->qhmem.ALIGNmask);
+    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
+  }
+  if (qh->qhmem.IStracing != 0)
+    qh_fprintf(qh, qh->qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qh->qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qh->qhmem\n");
+  for (i=0; i < qh->qhmem.TABLEsize; i++) {
+    count=0;
+    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
+      count++;
+    totfree += qh->qhmem.sizetable[i] * count;
+  }
+  if (totfree != qh->qhmem.totfree) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6211, "qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qh->qhmem.totfree, totfree);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  if (qh->qhmem.IStracing != 0)
+    qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree (%d) is the same as qh->qhmem.totfree\n", totfree);
+} /* memcheck */
+
+/*----------------------------------
+
+  qh_memfree(qh, object, insize )
+    free up an object of size bytes
+    size is insize from qh_memalloc
+
+  notes:
+    object may be NULL
+    type checking warns if using (void **)object
+    use qh_memfree_() for quick free's of small objects
+
+  design:
+    if size <= qh->qhmem.LASTsize
+      append object to corresponding freelist
+    else
+      call qh_free(object)
+*/
+void qh_memfree(qhT *qh, void *object, int insize) {
+  void **freelistp;
+  int idx, outsize;
+
+  if (!object)
+    return;
+  if (insize <= qh->qhmem.LASTsize) {
+    qh->qhmem.freeshort++;
+    idx= qh->qhmem.indextable[insize];
+    outsize= qh->qhmem.sizetable[idx];
+    qh->qhmem.totfree += outsize;
+    qh->qhmem.totshort -= outsize;
+    freelistp= qh->qhmem.freelists + idx;
+    *((void **)object)= *freelistp;
+    *freelistp= object;
+#ifdef qh_TRACEshort
+    idx= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
+    if (qh->qhmem.IStracing >= 5)
+        qh_fprintf(qh, qh->qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
+#endif
+  }else {
+    qh->qhmem.freelong++;
+    qh->qhmem.totlong -= insize;
+    if (qh->qhmem.IStracing >= 5)
+      qh_fprintf(qh, qh->qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
+    qh_free(object);
+  }
+} /* memfree */
+
+
+/*---------------------------------
+
+  qh_memfreeshort(qh, curlong, totlong )
+    frees up all short and qhmem memory allocations
+
+  returns:
+    number and size of current long allocations
+
+  notes:
+    if qh_NOmem (qh_malloc() for all allocations),
+       short objects (e.g., facetT) are not recovered.
+       use qh_freeqhull(qh, qh_ALL) instead.
+
+  see:
+    qh_freeqhull(qh, allMem)
+    qh_memtotal(qh, curlong, totlong, curshort, totshort, maxlong, totbuffer);
+*/
+void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
+  void *buffer, *nextbuffer;
+  FILE *ferr;
+
+  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
+  *totlong= qh->qhmem.totlong;
+  for (buffer=qh->qhmem.curbuffer; buffer; buffer= nextbuffer) {
+    nextbuffer= *((void **) buffer);
+    qh_free(buffer);
+  }
+  qh->qhmem.curbuffer= NULL;
+  if (qh->qhmem.LASTsize) {
+    qh_free(qh->qhmem.indextable);
+    qh_free(qh->qhmem.freelists);
+    qh_free(qh->qhmem.sizetable);
+  }
+  ferr= qh->qhmem.ferr;
+  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
+  qh->qhmem.ferr= ferr;
+} /* memfreeshort */
+
+
+/*----------------------------------
+
+  qh_meminit(qh, ferr )
+    initialize qhmem and test sizeof(void *)
+    Does not throw errors.  qh_exit on failure
+*/
+void qh_meminit(qhT *qh, FILE *ferr) {
+
+  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
+  if (ferr)
+    qh->qhmem.ferr= ferr;
+  else
+    qh->qhmem.ferr= stderr;
+  if (sizeof(void *) < sizeof(int)) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void *) %d < sizeof(int) %d.  qset_r.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
+    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
+  }
+  if (sizeof(void *) > sizeof(ptr_intT)) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void *) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem_r.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
+    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
+  }
+  qh_memcheck(qh);
+} /* meminit */
+
+/*---------------------------------
+
+  qh_meminitbuffers(qh, tracelevel, alignment, numsizes, bufsize, bufinit )
+    initialize qhmem
+    if tracelevel >= 5, trace memory allocations
+    alignment= desired address alignment for memory allocations
+    numsizes= number of freelists
+    bufsize=  size of additional memory buffers for short allocations
+    bufinit=  size of initial memory buffer for short allocations
+*/
+void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
+
+  qh->qhmem.IStracing= tracelevel;
+  qh->qhmem.NUMsizes= numsizes;
+  qh->qhmem.BUFsize= bufsize;
+  qh->qhmem.BUFinit= bufinit;
+  qh->qhmem.ALIGNmask= alignment-1;
+  if (qh->qhmem.ALIGNmask & ~qh->qhmem.ALIGNmask) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  qh->qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
+  qh->qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
+  if (!qh->qhmem.sizetable || !qh->qhmem.freelists) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
+    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+  }
+  if (qh->qhmem.IStracing >= 1)
+    qh_fprintf(qh, qh->qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
+} /* meminitbuffers */
+
+/*---------------------------------
+
+  qh_memsetup(qh)
+    set up memory after running memsize()
+*/
+void qh_memsetup(qhT *qh) {
+  int k,i;
+
+  qsort(qh->qhmem.sizetable, (size_t)qh->qhmem.TABLEsize, sizeof(int), qh_intcompare);
+  qh->qhmem.LASTsize= qh->qhmem.sizetable[qh->qhmem.TABLEsize-1];
+  if (qh->qhmem.LASTsize >= qh->qhmem.BUFsize || qh->qhmem.LASTsize >= qh->qhmem.BUFinit) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
+            qh->qhmem.LASTsize, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
+    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+  }
+  if (!(qh->qhmem.indextable= (int *)qh_malloc((size_t)(qh->qhmem.LASTsize+1) * sizeof(int)))) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
+    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+  }
+  for (k=qh->qhmem.LASTsize+1; k--; )
+    qh->qhmem.indextable[k]= k;
+  i= 0;
+  for (k=0; k <= qh->qhmem.LASTsize; k++) {
+    if (qh->qhmem.indextable[k] <= qh->qhmem.sizetable[i])
+      qh->qhmem.indextable[k]= i;
+    else
+      qh->qhmem.indextable[k]= ++i;
+  }
+} /* memsetup */
+
+/*---------------------------------
+
+  qh_memsize(qh, size )
+    define a free list for this size
+*/
+void qh_memsize(qhT *qh, int size) {
+  int k;
+
+  if (qh->qhmem.LASTsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6089, "qhull internal error (qh_memsize): qh_memsize called after qh_memsetup\n");
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  size= (size + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
+  if (qh->qhmem.IStracing >= 3)
+    qh_fprintf(qh, qh->qhmem.ferr, 3078, "qh_memsize: quick memory of %d bytes\n", size);
+  for (k=qh->qhmem.TABLEsize; k--; ) {
+    if (qh->qhmem.sizetable[k] == size)
+      return;
+  }
+  if (qh->qhmem.TABLEsize < qh->qhmem.NUMsizes)
+    qh->qhmem.sizetable[qh->qhmem.TABLEsize++]= size;
+  else
+    qh_fprintf(qh, qh->qhmem.ferr, 7060, "qhull warning (qh_memsize): free list table has room for only %d sizes\n", qh->qhmem.NUMsizes);
+} /* memsize */
+
+
+/*---------------------------------
+
+  qh_memstatistics(qh, fp )
+    print out memory statistics
+
+    Verifies that qh->qhmem.totfree == sum of freelists
+*/
+void qh_memstatistics(qhT *qh, FILE *fp) {
+  int i;
+  int count;
+  void *object;
+
+  qh_memcheck(qh);
+  qh_fprintf(qh, fp, 9278, "\nmemory statistics:\n\
+%7d quick allocations\n\
+%7d short allocations\n\
+%7d long allocations\n\
+%7d short frees\n\
+%7d long frees\n\
+%7d bytes of short memory in use\n\
+%7d bytes of short memory in freelists\n\
+%7d bytes of dropped short memory\n\
+%7d bytes of unused short memory (estimated)\n\
+%7d bytes of long memory allocated (max, except for input)\n\
+%7d bytes of long memory in use (in %d pieces)\n\
+%7d bytes of short memory buffers (minus links)\n\
+%7d bytes per short memory buffer (initially %d bytes)\n",
+           qh->qhmem.cntquick, qh->qhmem.cntshort, qh->qhmem.cntlong,
+           qh->qhmem.freeshort, qh->qhmem.freelong,
+           qh->qhmem.totshort, qh->qhmem.totfree,
+           qh->qhmem.totdropped + qh->qhmem.freesize, qh->qhmem.totunused,
+           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong,
+           qh->qhmem.totbuffer, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
+  if (qh->qhmem.cntlarger) {
+    qh_fprintf(qh, fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
+           qh->qhmem.cntlarger, ((double)qh->qhmem.totlarger)/(double)qh->qhmem.cntlarger);
+    qh_fprintf(qh, fp, 9280, "  freelists(bytes->count):");
+  }
+  for (i=0; i < qh->qhmem.TABLEsize; i++) {
+    count=0;
+    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
+      count++;
+    qh_fprintf(qh, fp, 9281, " %d->%d", qh->qhmem.sizetable[i], count);
+  }
+  qh_fprintf(qh, fp, 9282, "\n\n");
+} /* memstatistics */
+
+
+/*---------------------------------
+
+  qh_NOmem
+    turn off quick-fit memory allocation
+
+  notes:
+    uses qh_malloc() and qh_free() instead
+*/
+#else /* qh_NOmem */
+
+void *qh_memalloc(qhT *qh, int insize) {
+  void *object;
+
+  if (!(object= qh_malloc((size_t)insize))) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
+    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+  }
+  qh->qhmem.cntlong++;
+  qh->qhmem.totlong += insize;
+  if (qh->qhmem.maxlong < qh->qhmem.totlong)
+      qh->qhmem.maxlong= qh->qhmem.totlong;
+  if (qh->qhmem.IStracing >= 5)
+    qh_fprintf(qh, qh->qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
+  return object;
+}
+
+void qh_memcheck(qhT *qh) {
+}
+
+void qh_memfree(qhT *qh, void *object, int insize) {
+
+  if (!object)
+    return;
+  qh_free(object);
+  qh->qhmem.freelong++;
+  qh->qhmem.totlong -= insize;
+  if (qh->qhmem.IStracing >= 5)
+    qh_fprintf(qh, qh->qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
+}
+
+void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
+  *totlong= qh->qhmem.totlong;
+  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
+  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
+}
+
+void qh_meminit(qhT *qh, FILE *ferr) {
+
+  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
+  if (ferr)
+      qh->qhmem.ferr= ferr;
+  else
+      qh->qhmem.ferr= stderr;
+  if (sizeof(void *) < sizeof(int)) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void *) %d < sizeof(int) %d.  qset_r.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+}
+
+void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
+
+  qh->qhmem.IStracing= tracelevel;
+}
+
+void qh_memsetup(qhT *qh) {
+}
+
+void qh_memsize(qhT *qh, int size) {
+}
+
+void qh_memstatistics(qhT *qh, FILE *fp) {
+
+  qh_fprintf(qh, fp, 9409, "\nmemory statistics:\n\
+%7d long allocations\n\
+%7d long frees\n\
+%7d bytes of long memory allocated (max, except for input)\n\
+%7d bytes of long memory in use (in %d pieces)\n",
+           qh->qhmem.cntlong,
+           qh->qhmem.freelong,
+           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong);
+}
+
+#endif /* qh_NOmem */
+
+/*---------------------------------
+
+  qh_memtotal(qh, totlong, curlong, totshort, curshort, maxlong, totbuffer )
+    Return the total, allocated long and short memory
+
+  returns:
+    Returns the total current bytes of long and short allocations
+    Returns the current count of long and short allocations
+    Returns the maximum long memory and total short buffer (minus one link per buffer)
+    Does not error (for deprecated UsingLibQhull.cpp in libqhullpcpp)
+*/
+void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
+    *totlong= qh->qhmem.totlong;
+    *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
+    *totshort= qh->qhmem.totshort;
+    *curshort= qh->qhmem.cntshort + qh->qhmem.cntquick - qh->qhmem.freeshort;
+    *maxlong= qh->qhmem.maxlong;
+    *totbuffer= qh->qhmem.totbuffer;
+} /* memtotlong */
+
diff --git a/vendor/qhull/libqhull_r/mem_r.h b/vendor/qhull/libqhull_r/mem_r.h
new file mode 100644
index 0000000..aeb761b
--- /dev/null
+++ b/vendor/qhull/libqhull_r/mem_r.h
@@ -0,0 +1,235 @@
+/*
  ---------------------------------
+
+   mem_r.h
+     prototypes for memory management functions
+
+   see qh-mem_r.htm, mem_r.c and qset_r.h
+
+   for error handling, writes message and calls
+     qh_errexit(qhT *qh, qhmem_ERRmem, NULL, NULL) if insufficient memory
+       and
+     qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/mem_r.h#6 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFmem
+#define qhDEFmem 1
+
+#include 
+
+#ifndef DEFsetT
+#define DEFsetT 1
+typedef struct setT setT;          /* defined in qset_r.h */
+#endif
+
+#ifndef DEFqhT
+#define DEFqhT 1
+typedef struct qhT qhT;          /* defined in libqhull_r.h */
+#endif
+
+/*---------------------------------
+
+  qh_NOmem
+    turn off quick-fit memory allocation
+
+  notes:
+    mem_r.c implements Quickfit memory allocation for about 20% time
+    savings.  If it fails on your machine, try to locate the
+    problem, and send the answer to qhull@qhull.org.  If this can
+    not be done, define qh_NOmem to use malloc/free instead.
+
+    #define qh_NOmem
+*/
+
+/*---------------------------------
+
+qh_TRACEshort
+Trace short and quick memory allocations at T5
+
+*/
+#define qh_TRACEshort
+
+/*-------------------------------------------
+    to avoid bus errors, memory allocation must consider alignment requirements.
+    malloc() automatically takes care of alignment.   Since mem_r.c manages
+    its own memory, we need to explicitly specify alignment in
+    qh_meminitbuffers().
+
+    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
+    do not occur in data structures and pointers are the same size.  Be careful
+    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
+    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
+
+   see qh_MEMalign in user_r.h for qhull's alignment
+*/
+
+#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull_r.h */
+#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull_r.h */
+
+/*----------------------------------
+
+  ptr_intT
+    for casting a void * to an integer-type that holds a pointer
+    Used for integer expressions (e.g., computing qh_gethash() in poly_r.c)
+
+  notes:
+    WARN64 -- these notes indicate 64-bit issues
+    On 64-bit machines, a pointer may be larger than an 'int'.
+    qh_meminit()/mem_r.c checks that 'ptr_intT' holds a 'void*'
+    ptr_intT is typically a signed value, but not necessarily so
+    size_t is typically unsigned, but should match the parameter type
+    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
+    This matches Qt convention and is easier to work with.
+*/
+#if (defined(__MINGW64__)) && defined(_WIN64)
+typedef long long ptr_intT;
+#elif defined(_MSC_VER) && defined(_WIN64)
+typedef long long ptr_intT;
+#else
+typedef long ptr_intT;
+#endif
+
+/*----------------------------------
+
+  qhmemT
+    global memory structure for mem_r.c
+
+ notes:
+   users should ignore qhmem except for writing extensions
+   qhmem is allocated in mem_r.c
+
+   qhmem could be swapable like qh and qhstat, but then
+   multiple qh's and qhmem's would need to keep in synch.
+   A swapable qhmem would also waste memory buffers.  As long
+   as memory operations are atomic, there is no problem with
+   multiple qh structures being active at the same time.
+   If you need separate address spaces, you can swap the
+   contents of qh->qhmem.
+*/
+typedef struct qhmemT qhmemT;
+
+struct qhmemT {               /* global memory management variables */
+  int      BUFsize;           /* size of memory allocation buffer */
+  int      BUFinit;           /* initial size of memory allocation buffer */
+  int      TABLEsize;         /* actual number of sizes in free list table */
+  int      NUMsizes;          /* maximum number of sizes in free list table */
+  int      LASTsize;          /* last size in free list table */
+  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
+  void   **freelists;          /* free list table, linked by offset 0 */
+  int     *sizetable;         /* size of each freelist */
+  int     *indextable;        /* size->index table */
+  void    *curbuffer;         /* current buffer, linked by offset 0 */
+  void    *freemem;           /*   free memory in curbuffer */
+  int      freesize;          /*   size of freemem in bytes */
+  setT    *tempstack;         /* stack of temporary memory, managed by users */
+  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
+  int      IStracing;         /* =5 if tracing memory allocations */
+  int      cntquick;          /* count of quick allocations */
+                              /* Note: removing statistics doesn't effect speed */
+  int      cntshort;          /* count of short allocations */
+  int      cntlong;           /* count of long allocations */
+  int      freeshort;         /* count of short memfrees */
+  int      freelong;          /* count of long memfrees */
+  int      totbuffer;         /* total short memory buffers minus buffer links */
+  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
+  int      totfree;           /* total size of free, short memory on freelists */
+  int      totlong;           /* total size of long memory in use */
+  int      maxlong;           /*   maximum totlong */
+  int      totshort;          /* total size of short memory in use */
+  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
+  int      cntlarger;         /* count of setlarger's */
+  int      totlarger;         /* total copied by setlarger */
+};
+
+
+/*==================== -macros ====================*/
+
+/*----------------------------------
+
+  qh_memalloc_(qh, insize, freelistp, object, type)
+    returns object of size bytes
+        assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
+*/
+
+#if defined qh_NOmem
+#define qh_memalloc_(qh, insize, freelistp, object, type) {\
+  (void)freelistp; /* Avoid warnings */ \
+  object= (type *)qh_memalloc(qh, insize); }
+#elif defined qh_TRACEshort
+#define qh_memalloc_(qh, insize, freelistp, object, type) {\
+  (void)freelistp; /* Avoid warnings */ \
+  object= (type *)qh_memalloc(qh, insize); }
+#else /* !qh_NOmem */
+
+#define qh_memalloc_(qh, insize, freelistp, object, type) {\
+  freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
+  if ((object= (type *)*freelistp)) {\
+    qh->qhmem.totshort += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
+    qh->qhmem.totfree -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
+    qh->qhmem.cntquick++;  \
+    *freelistp= *((void **)*freelistp);\
+  }else object= (type *)qh_memalloc(qh, insize);}
+#endif
+
+/*----------------------------------
+
+  qh_memfree_(qh, object, insize, freelistp)
+    free up an object
+
+  notes:
+    object may be NULL
+    assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
+*/
+#if defined qh_NOmem
+#define qh_memfree_(qh, object, insize, freelistp) {\
+  (void)freelistp; /* Avoid warnings */ \
+  qh_memfree(qh, object, insize); }
+#elif defined qh_TRACEshort
+#define qh_memfree_(qh, object, insize, freelistp) {\
+  (void)freelistp; /* Avoid warnings */ \
+  qh_memfree(qh, object, insize); }
+#else /* !qh_NOmem */
+
+#define qh_memfree_(qh, object, insize, freelistp) {\
+  if (object) { \
+    qh->qhmem.freeshort++;\
+    freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
+    qh->qhmem.totshort -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
+    qh->qhmem.totfree += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
+    *((void **)object)= *freelistp;\
+    *freelistp= object;}}
+#endif
+
+/*=============== prototypes in alphabetical order ============*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *qh_memalloc(qhT *qh, int insize);
+void qh_memcheck(qhT *qh);
+void qh_memfree(qhT *qh, void *object, int insize);
+void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
+void qh_meminit(qhT *qh, FILE *ferr);
+void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes,
+                        int bufsize, int bufinit);
+void qh_memsetup(qhT *qh);
+void qh_memsize(qhT *qh, int size);
+void qh_memstatistics(qhT *qh, FILE *fp);
+void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFmem */
diff --git a/vendor/qhull/libqhull_r/merge_r.c b/vendor/qhull/libqhull_r/merge_r.c
new file mode 100644
index 0000000..f820cf8
--- /dev/null
+++ b/vendor/qhull/libqhull_r/merge_r.c
@@ -0,0 +1,5589 @@
+/*
  ---------------------------------
+
+   merge_r.c
+   merges non-convex facets
+
+   see qh-merge_r.htm and merge_r.h
+
+   other modules call qh_premerge() and qh_postmerge()
+
+   the user may call qh_postmerge() to perform additional merges.
+
+   To remove deleted facets and vertices (qhull() in libqhull_r.c):
+     qh_partitionvisible(qh, !qh_ALL, &numoutside);  // visible_list, newfacet_list
+     qh_deletevisible();         // qh.visible_list
+     qh_resetlists(qh, False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
+
+   assumes qh.CENTERtype= centrum
+
+   merges occur in qh_mergefacet and in qh_mergecycle
+   vertex->neighbors not set until the first merge occurs
+
+   Copyright (c) 1993-2020 C.B. Barber.
+   $Id: //main/2019/qhull/src/libqhull_r/merge_r.c#15 $$Change: 3664 $
+   $DateTime: 2024/07/22 23:55:01 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+#ifndef qh_NOmerge
+
+/* MRGnone, etc. */
+const char *mergetypes[]= {
+  "none",
+  "coplanar",
+  "anglecoplanar",
+  "concave",
+  "concavecoplanar",
+  "twisted",
+  "flip",
+  "dupridge",
+  "subridge",
+  "vertices",
+  "degen",
+  "redundant",
+  "mirror",
+  "coplanarhorizon",
+};
+
+/*===== functions(alphabetical after premerge and postmerge) ======*/
+
+/*---------------------------------
+
+  qh_premerge(qh, apexpointid, maxcentrum )
+    pre-merge nonconvex facets in qh.newfacet_list for apexpointid
+    maxcentrum defines coplanar and concave (qh_test_appendmerge)
+
+  returns:
+    deleted facets added to qh.visible_list with facet->visible set
+
+  notes:
+    only called by qh_addpoint
+    uses globals, qh.MERGEexact, qh.PREmerge
+
+  design:
+    mark dupridges in qh.newfacet_list
+    merge facet cycles in qh.newfacet_list
+    merge dupridges and concave facets in qh.newfacet_list
+    check merged facet cycles for degenerate and redundant facets
+    merge degenerate and redundant facets
+    collect coplanar and concave facets
+    merge concave, coplanar, degenerate, and redundant facets
+*/
+void qh_premerge(qhT *qh, int apexpointid, realT maxcentrum, realT maxangle /* qh.newfacet_list */) {
+  boolT othermerge= False;
+
+  if (qh->ZEROcentrum && qh_checkzero(qh, !qh_ALL))
+    return;
+  trace2((qh, qh->ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %4.4g for apex p%d newfacet_list f%d\n",
+            maxcentrum, maxangle, apexpointid, getid_(qh->newfacet_list)));
+  if (qh->IStracing >= 4 && qh->num_facets < 100)
+    qh_printlists(qh);
+  qh->centrum_radius= maxcentrum;
+  qh->cos_max= maxangle;
+  if (qh->hull_dim >=3) {
+    qh_mark_dupridges(qh, qh->newfacet_list, qh_ALL); /* facet_mergeset */
+    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
+    qh_forcedmerges(qh, &othermerge /* qh.facet_mergeset */);
+  }else /* qh.hull_dim == 2 */
+    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
+  qh_flippedmerges(qh, qh->newfacet_list, &othermerge);
+  if (!qh->MERGEexact || zzval_(Ztotmerge)) {
+    zinc_(Zpremergetot);
+    qh->POSTmerging= False;
+    qh_getmergeset_initial(qh, qh->newfacet_list);
+    qh_all_merges(qh, othermerge, False);
+  }
+} /* premerge */
+
+/*---------------------------------
+
+  qh_postmerge(qh, reason, maxcentrum, maxangle, vneighbors )
+    post-merge nonconvex facets as defined by maxcentrum and maxangle
+    'reason' is for reporting progress
+    if vneighbors ('Qv'),
+      calls qh_test_vneighbors at end of qh_all_merge from qh_postmerge
+
+  returns:
+    if first call (qh.visible_list != qh.facet_list),
+      builds qh.facet_newlist, qh.newvertex_list
+    deleted facets added to qh.visible_list with facet->visible
+    qh.visible_list == qh.facet_list
+
+  notes:
+    called by qh_qhull after qh_buildhull
+    called if a merge may be needed due to
+      qh.MERGEexact ('Qx'), qh_DIMreduceBuild, POSTmerge (e.g., 'Cn'), or TESTvneighbors ('Qv')
+    if firstmerge,
+      calls qh_reducevertices before qh_getmergeset
+
+  design:
+    if first call
+      set qh.visible_list and qh.newfacet_list to qh.facet_list
+      add all facets to qh.newfacet_list
+      mark non-simplicial facets, facet->newmerge
+      set qh.newvertext_list to qh.vertex_list
+      add all vertices to qh.newvertex_list
+      if a pre-merge occurred
+        set vertex->delridge {will retest the ridge}
+        if qh.MERGEexact
+          call qh_reducevertices()
+      if no pre-merging
+        merge flipped facets
+    determine non-convex facets
+    merge all non-convex facets
+*/
+void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
+                      boolT vneighbors) {
+  facetT *newfacet;
+  boolT othermerges= False;
+  vertexT *vertex;
+
+  if (qh->REPORTfreq || qh->IStracing) {
+    qh_buildtracing(qh, NULL, NULL);
+    qh_printsummary(qh, qh->ferr);
+    if (qh->PRINTstatistics)
+      qh_printallstatistics(qh, qh->ferr, "reason");
+    qh_fprintf(qh, qh->ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
+        reason, maxcentrum, maxangle);
+  }
+  trace2((qh, qh->ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
+            vneighbors));
+  qh->centrum_radius= maxcentrum;
+  qh->cos_max= maxangle;
+  qh->POSTmerging= True;
+  if (qh->visible_list != qh->facet_list) {  /* first call due to qh_buildhull, multiple calls if qh.POSTmerge */
+    qh->NEWfacets= True;
+    qh->visible_list= qh->newfacet_list= qh->facet_list;
+    FORALLnew_facets {              /* all facets are new facets for qh_postmerge */
+      newfacet->newfacet= True;
+       if (!newfacet->simplicial)
+        newfacet->newmerge= True;   /* test f.vertices for 'delridge'.  'newmerge' was cleared at end of qh_all_merges */
+     zinc_(Zpostfacets);
+    }
+    qh->newvertex_list= qh->vertex_list;
+    FORALLvertices
+      vertex->newfacet= True;
+    if (qh->VERTEXneighbors) {  /* a merge has occurred */
+      if (qh->MERGEexact && qh->hull_dim <= qh_DIMreduceBuild)
+        qh_reducevertices(qh);  /* qh_all_merges did not call qh_reducevertices for v.delridge */
+    }
+    if (!qh->PREmerge && !qh->MERGEexact)
+      qh_flippedmerges(qh, qh->newfacet_list, &othermerges);
+  }
+  qh_getmergeset_initial(qh, qh->newfacet_list);
+  qh_all_merges(qh, False, vneighbors); /* calls qh_reducevertices before exiting */
+  FORALLnew_facets
+    newfacet->newmerge= False;   /* Was True if no vertex in f.vertices was 'delridge' */
+} /* post_merge */
+
+/*---------------------------------
+
+  qh_all_merges(qh, othermerge, vneighbors )
+    merge all non-convex facets
+
+    set othermerge if already merged facets (calls qh_reducevertices)
+    if vneighbors ('Qv' at qh.POSTmerge)
+      tests vertex neighbors for convexity at end (qh_test_vneighbors)
+    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
+    qh.degen_mergeset is defined
+    if qh.MERGEexact && !qh.POSTmerging,
+      does not merge coplanar facets
+
+  returns:
+    deleted facets added to qh.visible_list with facet->visible
+    deleted vertices added qh.delvertex_list with vertex->delvertex
+
+  notes:
+    unless !qh.MERGEindependent,
+      merges facets in independent sets
+    uses qh.newfacet_list as implicit argument since merges call qh_removefacet()
+    [apr'19] restored qh_setdellast in place of qh_next_facetmerge.  Much faster for post-merge
+
+  design:
+    while merges occur
+      for each merge in qh.facet_mergeset
+        unless one of the facets was already merged in this pass
+          merge the facets
+        test merged facets for additional merges
+        add merges to qh.facet_mergeset
+        if qh.POSTmerging
+          periodically call qh_reducevertices to reduce extra vertices and redundant vertices
+      after each pass, if qh.VERTEXneighbors
+        if qh.POSTmerging or was a merge with qh.hull_dim<=5
+          call qh_reducevertices
+          update qh.facet_mergeset if degenredundant merges
+      if 'Qv' and qh.POSTmerging
+        test vertex neighbors for convexity
+*/
+void qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors) {
+  facetT *facet1, *facet2, *newfacet;
+  mergeT *merge;
+  boolT wasmerge= False, isreduce;
+  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
+  vertexT *vertex;
+  realT angle, distance;
+  mergeType mergetype;
+  int numcoplanar=0, numconcave=0, numconcavecoplanar= 0, numdegenredun= 0, numnewmerges= 0, numtwisted= 0;
+
+  trace2((qh, qh->ferr, 2010, "qh_all_merges: starting to merge %d facet and %d degenerate merges for new facets f%d, othermerge? %d\n",
+            qh_setsize(qh, qh->facet_mergeset), qh_setsize(qh, qh->degen_mergeset), getid_(qh->newfacet_list), othermerge));
+
+  while (True) {
+    wasmerge= False;
+    while (qh_setsize(qh, qh->facet_mergeset) > 0 || qh_setsize(qh, qh->degen_mergeset) > 0) {
+      if (qh_setsize(qh, qh->degen_mergeset) > 0) {
+        numdegenredun += qh_merge_degenredundant(qh);
+        wasmerge= True;
+      }
+      while ((merge= (mergeT *)qh_setdellast(qh->facet_mergeset))) {
+        facet1= merge->facet1;
+        facet2= merge->facet2;
+        vertex= merge->vertex1;  /* not used for qh.facet_mergeset*/
+        mergetype= merge->mergetype;
+        angle= merge->angle;
+        distance= merge->distance;
+        qh_memfree_(qh, merge, (int)sizeof(mergeT), freelistp);   /* 'merge' is invalid */
+        if (facet1->visible || facet2->visible) {
+          trace3((qh, qh->ferr, 3045, "qh_all_merges: drop merge of f%d (del? %d) into f%d (del? %d) mergetype %d, dist %4.4g, angle %4.4g.  One or both facets is deleted\n",
+            facet1->id, facet1->visible, facet2->id, facet2->visible, mergetype, distance, angle));
+          continue;
+        }else if (mergetype == MRGcoplanar || mergetype == MRGanglecoplanar) {
+          if (qh->MERGEindependent) {
+            if ((!facet1->tested && facet1->newfacet)
+            || (!facet2->tested && facet2->newfacet)) {
+              trace3((qh, qh->ferr, 3064, "qh_all_merges: drop merge of f%d (tested? %d) into f%d (tested? %d) mergetype %d, dist %2.2g, angle %4.4g.  Merge independent sets of coplanar merges\n",
+                facet1->id, facet1->visible, facet2->id, facet2->visible, mergetype, distance, angle));
+              continue;
+            }
+          }
+        }
+        trace3((qh, qh->ferr, 3047, "qh_all_merges: merge f%d and f%d type %d dist %2.2g angle %4.4g\n",
+          facet1->id, facet2->id, mergetype, distance, angle));
+        if (mergetype == MRGtwisted)
+          qh_merge_twisted(qh, facet1, facet2);
+        else
+          qh_merge_nonconvex(qh, facet1, facet2, mergetype);
+        numnewmerges++;
+        numdegenredun += qh_merge_degenredundant(qh);
+        wasmerge= True;
+        if (mergetype == MRGconcave)
+          numconcave++;
+        else if (mergetype == MRGconcavecoplanar)
+          numconcavecoplanar++;
+        else if (mergetype == MRGtwisted)
+          numtwisted++;
+        else if (mergetype == MRGcoplanar || mergetype == MRGanglecoplanar)
+          numcoplanar++;
+        else {
+          qh_fprintf(qh, qh->ferr, 6394, "qhull internal error (qh_all_merges): expecting concave, coplanar, or twisted merge.  Got merge f%d f%d v%d mergetype %d\n",
+            getid_(facet1), getid_(facet2), getid_(vertex), mergetype);
+          qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+        }
+      } /* while qh_setdellast */
+      if (qh->POSTmerging && qh->hull_dim <= qh_DIMreduceBuild
+      && numnewmerges > qh_MAXnewmerges) {
+        numnewmerges= 0;
+        wasmerge= othermerge= False;
+        qh_reducevertices(qh);  /* otherwise large post merges too slow */
+      }
+      qh_getmergeset(qh, qh->newfacet_list); /* qh.facet_mergeset */
+    } /* while facet_mergeset or degen_mergeset */
+    if (qh->VERTEXneighbors) {  /* at least one merge */
+      isreduce= False;
+      if (qh->POSTmerging && qh->hull_dim >= 4) {
+        isreduce= True;
+      }else if (qh->POSTmerging || !qh->MERGEexact) {
+        if ((wasmerge || othermerge) && qh->hull_dim > 2 && qh->hull_dim <= qh_DIMreduceBuild)
+          isreduce= True;
+      }
+      if (isreduce) {
+        wasmerge= othermerge= False;
+        if (qh_reducevertices(qh)) {
+          qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
+          continue;
+        }
+      }
+    }
+    if (vneighbors && qh_test_vneighbors(qh /* qh.newfacet_list */))
+      continue;
+    break;
+  } /* while (True) */
+  if (wasmerge || othermerge) {
+    trace3((qh, qh->ferr, 3033, "qh_all_merges: skip qh_reducevertices due to post-merging, no qh.VERTEXneighbors (%d), or hull_dim %d ==2 or >%d\n", qh->VERTEXneighbors, qh->hull_dim, qh_DIMreduceBuild))
+    FORALLnew_facets {
+      newfacet->newmerge= False;
+    }
+  }
+  if (qh->CHECKfrequently && !qh->MERGEexact) {
+    qh->old_randomdist= qh->RANDOMdist;
+    qh->RANDOMdist= False;
+    qh_checkconvex(qh, qh->newfacet_list, qh_ALGORITHMfault);
+    /* qh_checkconnect(qh); [this is slow and it changes the facet order] */
+    qh->RANDOMdist= qh->old_randomdist;
+  }
+  trace1((qh, qh->ferr, 1009, "qh_all_merges: merged %d coplanar %d concave %d concavecoplanar %d twisted facets and %d degen or redundant facets.\n",
+    numcoplanar, numconcave, numconcavecoplanar, numtwisted, numdegenredun));
+  if (qh->IStracing >= 4 && qh->num_facets < 500)
+    qh_printlists(qh);
+} /* all_merges */
+
+/*---------------------------------
+
+  qh_all_vertexmerges(qh, apexpointid, facet, &retryfacet )
+    merge vertices in qh.vertex_mergeset and subsequent merges
+
+  returns:
+    returns retryfacet for facet (if defined)
+    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
+    mergesets are empty
+    if merges, resets facet lists
+
+  notes:
+    called from qh_qhull, qh_addpoint, and qh_buildcone_mergepinched
+    vertex merges occur after facet merges and qh_resetlists
+
+  design:
+    while merges in vertex_mergeset (MRGvertices)
+      merge a pair of pinched vertices
+      update vertex neighbors
+      merge non-convex and degenerate facets and check for ridges with duplicate vertices
+      partition outside points of deleted, "visible" facets
+*/
+void qh_all_vertexmerges(qhT *qh, int apexpointid, facetT *facet, facetT **retryfacet) {
+  int numpoints; /* ignore count of partitioned points.  Used by qh_addpoint for Zpbalance */
+
+  if (retryfacet)
+    *retryfacet= facet;
+  while (qh_setsize(qh, qh->vertex_mergeset) > 0) {
+    trace1((qh, qh->ferr, 1057, "qh_all_vertexmerges: starting to merge %d vertex merges for apex p%d facet f%d\n",
+            qh_setsize(qh, qh->vertex_mergeset), apexpointid, getid_(facet)));
+    if (qh->IStracing >= 4  && qh->num_facets < 1000)
+      qh_printlists(qh);
+    qh_merge_pinchedvertices(qh, apexpointid /* qh.vertex_mergeset, visible_list, newvertex_list, newfacet_list */);
+    qh_update_vertexneighbors(qh); /* update neighbors of qh.newvertex_list from qh_newvertices for deleted facets on qh.visible_list */
+                           /* test ridges and merge non-convex facets */
+    qh_getmergeset(qh, qh->newfacet_list);
+    qh_all_merges(qh, True, False); /* calls qh_reducevertices */
+    if (qh->CHECKfrequently)
+      qh_checkpolygon(qh, qh->facet_list);
+    qh_partitionvisible(qh, !qh_ALL, &numpoints /* qh.visible_list qh.del_vertices*/);
+    if (retryfacet)
+      *retryfacet= qh_getreplacement(qh, *retryfacet);
+    qh_deletevisible(qh /* qh.visible_list  qh.del_vertices*/);
+    qh_resetlists(qh, False, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+    if (qh->IStracing >= 4  && qh->num_facets < 1000) {
+      qh_printlists(qh);
+      qh_checkpolygon(qh, qh->facet_list);
+    }
+  }
+} /* all_vertexmerges */
+
+/*---------------------------------
+
+  qh_appendmergeset(qh, facet, vertex, neighbor, mergetype, dist, angle )
+    appends an entry to qh.facet_mergeset or qh.degen_mergeset
+    if 'dist' is unknown, set it to 0.0
+        if 'angle' is unknown, set it to 1.0 (coplanar)
+
+  returns:
+    merge appended to facet_mergeset or degen_mergeset
+      sets ->degenerate or ->redundant if degen_mergeset
+
+  notes:
+    caller collects statistics and/or caller of qh_mergefacet
+    see: qh_test_appendmerge()
+
+  design:
+    allocate merge entry
+    if regular merge
+      append to qh.facet_mergeset
+    else if degenerate merge and qh.facet_mergeset is all degenerate
+      append to qh.degen_mergeset
+    else if degenerate merge
+      prepend to qh.degen_mergeset (merged last)
+    else if redundant merge
+      append to qh.degen_mergeset
+*/
+void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, coordT dist, realT angle) {
+  mergeT *merge, *lastmerge;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+  const char *mergename;
+
+  if ((facet->redundant && mergetype != MRGmirror) || neighbor->redundant) {
+    trace3((qh, qh->ferr, 3051, "qh_appendmergeset: f%d is already redundant (%d) or f%d is already redundant (%d).  Ignore merge f%d and f%d type %d\n",
+      facet->id, facet->redundant, neighbor->id, neighbor->redundant, facet->id, neighbor->id, mergetype));
+    return;
+  }
+  if (facet->degenerate && mergetype == MRGdegen) {
+    trace3((qh, qh->ferr, 3077, "qh_appendmergeset: f%d is already degenerate.  Ignore merge f%d type %d (MRGdegen)\n",
+      facet->id, facet->id, mergetype));
+    return;
+  }
+  if (!qh->facet_mergeset || !qh->degen_mergeset) {
+    qh_fprintf(qh, qh->ferr, 6403, "qhull internal error (qh_appendmergeset): expecting temp set defined for qh.facet_mergeset (%p) and qh.degen_mergeset (%p).  Got NULL\n",
+      (void *) qh->facet_mergeset, (void *) qh->degen_mergeset);
+    /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (neighbor->flipped && !facet->flipped) {
+    if (mergetype != MRGdupridge) {
+      qh_fprintf(qh, qh->ferr, 6355, "qhull internal error (qh_appendmergeset): except for MRGdupridge, cannot merge a non-flipped facet f%d into flipped f%d, mergetype %d, dist %4.4g\n",
+        facet->id, neighbor->id, mergetype, dist);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }else {
+      trace2((qh, qh->ferr, 2106, "qh_appendmergeset: dupridge will merge a non-flipped facet f%d into flipped f%d, dist %4.4g\n",
+        facet->id, neighbor->id, dist));
+    }
+  }
+  qh_memalloc_(qh, (int)sizeof(mergeT), freelistp, merge, mergeT);
+  merge->angle= angle;
+  merge->distance= dist;
+  merge->facet1= facet;
+  merge->facet2= neighbor;
+  merge->vertex1= NULL;
+  merge->vertex2= NULL;
+  merge->ridge1= NULL;
+  merge->ridge2= NULL;
+  merge->mergetype= mergetype;
+  if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *))
+    mergename= mergetypes[mergetype];
+  else
+    mergename= mergetypes[MRGnone];
+  if (mergetype < MRGdegen)
+    qh_setappend(qh, &(qh->facet_mergeset), merge);
+  else if (mergetype == MRGdegen) {
+    facet->degenerate= True;
+    if (!(lastmerge= (mergeT *)qh_setlast(qh->degen_mergeset))
+    || lastmerge->mergetype == MRGdegen)
+      qh_setappend(qh, &(qh->degen_mergeset), merge);
+    else
+      qh_setaddnth(qh, &(qh->degen_mergeset), 0, merge);    /* merged last */
+  }else if (mergetype == MRGredundant) {
+    facet->redundant= True;
+    qh_setappend(qh, &(qh->degen_mergeset), merge);
+  }else /* mergetype == MRGmirror */ {
+    if (facet->redundant || neighbor->redundant) {
+      qh_fprintf(qh, qh->ferr, 6092, "qhull internal error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet (i.e., 'redundant')\n",
+           facet->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+    }
+    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
+      qh_fprintf(qh, qh->ferr, 6093, "qhull internal error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
+           facet->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+    }
+    facet->redundant= True;
+    neighbor->redundant= True;
+    qh_setappend(qh, &(qh->degen_mergeset), merge);
+  }
+  if (merge->mergetype >= MRGdegen) {
+    trace3((qh, qh->ferr, 3044, "qh_appendmergeset: append merge f%d and f%d type %d (%s) to qh.degen_mergeset (size %d)\n",
+      merge->facet1->id, merge->facet2->id, merge->mergetype, mergename, qh_setsize(qh, qh->degen_mergeset)));
+  }else {
+    trace3((qh, qh->ferr, 3027, "qh_appendmergeset: append merge f%d and f%d type %d (%s) dist %2.2g angle %4.4g to qh.facet_mergeset (size %d)\n",
+      merge->facet1->id, merge->facet2->id, merge->mergetype, mergename, merge->distance, merge->angle, qh_setsize(qh, qh->facet_mergeset)));
+  }
+} /* appendmergeset */
+
+
+/*---------------------------------
+
+  qh_appendvertexmerge(qh, vertex, vertex2, mergetype, distance, ridge1, ridge2 )
+    appends a vertex merge to qh.vertex_mergeset
+    MRGsubridge includes two ridges (from MRGdupridge)
+    MRGvertices includes two ridges
+
+  notes:
+    called by qh_getpinchedmerges for MRGsubridge
+    called by qh_maybe_duplicateridge and qh_maybe_duplicateridges for MRGvertices
+    only way to add a vertex merge to qh.vertex_mergeset
+    checked by qh_next_vertexmerge
+*/
+void qh_appendvertexmerge(qhT *qh, vertexT *vertex, vertexT *destination, mergeType mergetype, realT distance, ridgeT *ridge1, ridgeT *ridge2) {
+  mergeT *merge;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+  const char *mergename;
+
+  if (!qh->vertex_mergeset) {
+    qh_fprintf(qh, qh->ferr, 6387, "qhull internal error (qh_appendvertexmerge): expecting temp set defined for qh.vertex_mergeset.  Got NULL\n");
+    /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh_memalloc_(qh, (int)sizeof(mergeT), freelistp, merge, mergeT);
+  merge->angle= qh_ANGLEnone;
+  merge->distance= distance;
+  merge->facet1= NULL;
+  merge->facet2= NULL;
+  merge->vertex1= vertex;
+  merge->vertex2= destination;
+  merge->ridge1= ridge1;
+  merge->ridge2= ridge2;
+  merge->mergetype= mergetype;
+  if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *))
+    mergename= mergetypes[mergetype];
+  else
+    mergename= mergetypes[MRGnone];
+  if (mergetype == MRGvertices) {
+    if (!ridge1 || !ridge2 || ridge1 == ridge2) {
+      qh_fprintf(qh, qh->ferr, 6106, "qhull internal error (qh_appendvertexmerge): expecting two distinct ridges for MRGvertices.  Got r%d r%d\n",
+        getid_(ridge1), getid_(ridge2));
+      qh_errexit(qh, qh_ERRqhull, NULL, ridge1);
+    }
+  }
+  qh_setappend(qh, &(qh->vertex_mergeset), merge);
+  trace3((qh, qh->ferr, 3034, "qh_appendvertexmerge: append merge v%d into v%d r%d r%d dist %2.2g type %d (%s)\n",
+    vertex->id, destination->id, getid_(ridge1), getid_(ridge2), distance, merge->mergetype, mergename));
+} /* appendvertexmerge */
+
+
+/*---------------------------------
+
+  qh_basevertices(qh, samecycle )
+    return temporary set of base vertices for samecycle
+    samecycle is first facet in the cycle
+    assumes apex is SETfirst_( samecycle->vertices )
+
+  returns:
+    vertices(settemp)
+    all ->seen are cleared
+
+  notes:
+    uses qh_vertex_visit;
+
+  design:
+    for each facet in samecycle
+      for each unseen vertex in facet->vertices
+        append to result
+*/
+setT *qh_basevertices(qhT *qh, facetT *samecycle) {
+  facetT *same;
+  vertexT *apex, *vertex, **vertexp;
+  setT *vertices= qh_settemp(qh, qh->TEMPsize);
+
+  apex= SETfirstt_(samecycle->vertices, vertexT);
+  apex->visitid= ++qh->vertex_visit;
+  FORALLsame_cycle_(samecycle) {
+    if (same->mergeridge)
+      continue;
+    FOREACHvertex_(same->vertices) {
+      if (vertex->visitid != qh->vertex_visit) {
+        qh_setappend(qh, &vertices, vertex);
+        vertex->visitid= qh->vertex_visit;
+        vertex->seen= False;
+      }
+    }
+  }
+  trace4((qh, qh->ferr, 4019, "qh_basevertices: found %d vertices\n",
+         qh_setsize(qh, vertices)));
+  return vertices;
+} /* basevertices */
+
+/*---------------------------------
+
+  qh_check_dupridge(qh, facet1, dist1, facet2, dist2 )
+    Check dupridge between facet1 and facet2 for wide merge
+    dist1 is the maximum distance of facet1's vertices to facet2
+    dist2 is the maximum distance of facet2's vertices to facet1
+
+  returns
+    Level 1 log of the dupridge with the minimum distance between vertices
+    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
+
+  notes:
+    only called from qh_forcedmerges
+*/
+void qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
+  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
+  realT dist, innerplane, mergedist, outerplane, prevdist, ratio, vertexratio;
+  realT minvertex= REALmax;
+
+  mergedist= fmin_(dist1, dist2);
+  qh_outerinner(qh, NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
+  FOREACHvertex_(facet1->vertices) {     /* The dupridge is between facet1 and facet2, so either facet can be tested */
+    FOREACHvertexA_(facet1->vertices) {
+      if (vertex > vertexA){   /* Test each pair once */
+        dist= qh_pointdist(vertex->point, vertexA->point, qh->hull_dim);
+        minimize_(minvertex, dist);
+        /* Not quite correct.  A facet may have a dupridge and another pair of nearly adjacent vertices. */
+      }
+    }
+  }
+  prevdist= fmax_(outerplane, innerplane);
+  maximize_(prevdist, qh->ONEmerge + qh->DISTround);
+  maximize_(prevdist, qh->MINoutside + qh->DISTround);
+  ratio= mergedist/prevdist;
+  vertexratio= minvertex/prevdist;
+  trace0((qh, qh->ferr, 16, "qh_check_dupridge: dupridge between f%d and f%d (vertex dist %2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
+        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh->furthest_id));
+  if (ratio > qh_WIDEduplicate) {
+    qh_fprintf(qh, qh->ferr, 6271, "qhull topology error (qh_check_dupridge): wide merge (%.1fx wider) due to dupridge between f%d and f%d (vertex dist %2.2g), merge dist %2.2g, while processing p%d\n- Allow error with option 'Q12'\n",
+      ratio, facet1->id, facet2->id, minvertex, mergedist, qh->furthest_id);
+    if (vertexratio < qh_WIDEpinched)
+      qh_fprintf(qh, qh->ferr, 8145, "- Experimental option merge-pinched-vertices ('Q14') may avoid this error.  It merges nearly adjacent vertices.\n");
+    if (qh->DELAUNAY)
+      qh_fprintf(qh, qh->ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
+    if (!qh->ALLOWwide)
+      qh_errexit2(qh, qh_ERRwide, facet1, facet2);
+  }
+} /* check_dupridge */
+
+/*---------------------------------
+
+  qh_checkconnect(qh)
+    check that new facets are connected
+    new facets are on qh.newfacet_list
+
+  notes:
+    this is slow and it changes the order of the facets
+    uses qh.visit_id
+
+  design:
+    move first new facet to end of qh.facet_list
+    for all newly appended facets
+      append unvisited neighbors to end of qh.facet_list
+    for all new facets
+      report error if unvisited
+*/
+void qh_checkconnect(qhT *qh /* qh.newfacet_list */) {
+  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
+
+  facet= qh->newfacet_list;
+  qh_removefacet(qh, facet);
+  qh_appendfacet(qh, facet);
+  facet->visitid= ++qh->visit_id;
+  FORALLfacet_(facet) {
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid != qh->visit_id) {
+        qh_removefacet(qh, neighbor);
+        qh_appendfacet(qh, neighbor);
+        neighbor->visitid= qh->visit_id;
+      }
+    }
+  }
+  FORALLnew_facets {
+    if (newfacet->visitid == qh->visit_id)
+      break;
+    qh_fprintf(qh, qh->ferr, 6094, "qhull internal error (qh_checkconnect): f%d is not attached to the new facets\n",
+         newfacet->id);
+    errfacet= newfacet;
+  }
+  if (errfacet)
+    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
+} /* checkconnect */
+
+/*---------------------------------
+
+  qh_checkdelfacet(qh, facet, mergeset )
+    check that mergeset does not reference facet
+
+*/
+void qh_checkdelfacet(qhT *qh, facetT *facet, setT *mergeset) {
+  mergeT *merge, **mergep;
+
+  FOREACHmerge_(mergeset) {
+    if (merge->facet1 == facet || merge->facet2 == facet) {
+      qh_fprintf(qh, qh->ferr, 6390, "qhull internal error (qh_checkdelfacet): cannot delete f%d.  It is referenced by merge f%d f%d mergetype %d\n",
+        facet->id, merge->facet1->id, getid_(merge->facet2), merge->mergetype);
+      qh_errexit2(qh, qh_ERRqhull, merge->facet1, merge->facet2);
+    }
+  }
+} /* checkdelfacet */
+
+/*---------------------------------
+
+  qh_checkdelridge(qh)
+    check that qh_delridge_merge is not needed for deleted ridges
+
+    notes:
+      called from qh_mergecycle, qh_makenewfacets, qh_attachnewfacets
+      errors if qh.vertex_mergeset is non-empty
+      errors if any visible or new facet has a ridge with r.nonconvex set
+      assumes that vertex.delfacet is not needed
+*/
+void qh_checkdelridge(qhT *qh /* qh.visible_facets, vertex_mergeset */) {
+  facetT *newfacet, *visible;
+  ridgeT *ridge, **ridgep;
+
+  if (!SETempty_(qh->vertex_mergeset)) {
+    qh_fprintf(qh, qh->ferr, 6382, "qhull internal error (qh_checkdelridge): expecting empty qh.vertex_mergeset in order to avoid calling qh_delridge_merge.  Got %d merges\n", qh_setsize(qh, qh->vertex_mergeset));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+
+  FORALLnew_facets {
+    FOREACHridge_(newfacet->ridges) {
+      if (ridge->nonconvex) {
+        qh_fprintf(qh, qh->ferr, 6313, "qhull internal error (qh_checkdelridge): unexpected 'nonconvex' flag for ridge r%d in newfacet f%d.  Otherwise need to call qh_delridge_merge\n",
+           ridge->id, newfacet->id);
+        qh_errexit(qh, qh_ERRqhull, newfacet, ridge);
+      }
+    }
+  }
+
+  FORALLvisible_facets {
+    FOREACHridge_(visible->ridges) {
+      if (ridge->nonconvex) {
+        qh_fprintf(qh, qh->ferr, 6385, "qhull internal error (qh_checkdelridge): unexpected 'nonconvex' flag for ridge r%d in visible facet f%d.  Otherwise need to call qh_delridge_merge\n",
+          ridge->id, visible->id);
+        qh_errexit(qh, qh_ERRqhull, visible, ridge);
+      }
+    }
+  }
+} /* checkdelridge */
+
+
+/*---------------------------------
+
+  qh_checkzero(qh, testall )
+    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
+
+    if testall,
+      test all facets for qh.MERGEexact post-merging
+    else
+      test qh.newfacet_list
+
+    if qh.MERGEexact,
+      allows coplanar ridges
+      skips convexity test while qh.ZEROall_ok
+
+  returns:
+    True if all facets !flipped, !dupridge, normal
+         if all horizon facets are simplicial
+         if all vertices are clearly below neighbor
+         if all opposite vertices of horizon are below
+    clears qh.ZEROall_ok if any problems or coplanar facets
+
+  notes:
+    called by qh_premerge (qh.CHECKzero, 'C-0') and qh_qhull ('Qx')
+    uses qh.vertex_visit
+    horizon facets may define multiple new facets
+
+  design:
+    for all facets in qh.newfacet_list or qh.facet_list
+      check for flagged faults (flipped, etc.)
+    for all facets in qh.newfacet_list or qh.facet_list
+      for each neighbor of facet
+        skip horizon facets for qh.newfacet_list
+        test the opposite vertex
+      if qh.newfacet_list
+        test the other vertices in the facet's horizon facet
+*/
+boolT qh_checkzero(qhT *qh, boolT testall) {
+  facetT *facet, *neighbor;
+  facetT *horizon, *facetlist;
+  int neighbor_i, neighbor_n;
+  vertexT *vertex, **vertexp;
+  realT dist;
+
+  if (testall)
+    facetlist= qh->facet_list;
+  else {
+    facetlist= qh->newfacet_list;
+    FORALLfacet_(facetlist) {
+      horizon= SETfirstt_(facet->neighbors, facetT);
+      if (!horizon->simplicial)
+        goto LABELproblem;
+      if (facet->flipped || facet->dupridge || !facet->normal)
+        goto LABELproblem;
+    }
+    if (qh->MERGEexact && qh->ZEROall_ok) {
+      trace2((qh, qh->ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
+      return True;
+    }
+  }
+  FORALLfacet_(facetlist) {
+    qh->vertex_visit++;
+    horizon= NULL;
+    FOREACHneighbor_i_(qh, facet) {
+      if (!neighbor_i && !testall) {
+        horizon= neighbor;
+        continue; /* horizon facet tested in qh_findhorizon */
+      }
+      vertex= SETelemt_(facet->vertices, neighbor_i, vertexT);
+      vertex->visitid= qh->vertex_visit;
+      zzinc_(Zdistzero);
+      qh_distplane(qh, vertex->point, neighbor, &dist);
+      if (dist >= -2 * qh->DISTround) {  /* need 2x for qh_distround and 'Rn' for qh_checkconvex, same as qh.premerge_centrum */
+        qh->ZEROall_ok= False;
+        if (!qh->MERGEexact || testall || dist > qh->DISTround)
+          goto LABELnonconvex;
+      }
+    }
+    if (!testall && horizon) {
+      FOREACHvertex_(horizon->vertices) {
+        if (vertex->visitid != qh->vertex_visit) {
+          zzinc_(Zdistzero);
+          qh_distplane(qh, vertex->point, facet, &dist);
+          if (dist >= -2 * qh->DISTround) {
+            qh->ZEROall_ok= False;
+            if (!qh->MERGEexact || dist > qh->DISTround)
+              goto LABELnonconvexhorizon;
+          }
+          break;
+        }
+      }
+    }
+  }
+  trace2((qh, qh->ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
+        (qh->MERGEexact && !testall) ?
+           "not concave, flipped, or dupridge" : "clearly convex"));
+  return True;
+
+ LABELproblem:
+  qh->ZEROall_ok= False;
+  trace2((qh, qh->ferr, 2013, "qh_checkzero: qh_premerge is needed.  New facet f%d or its horizon f%d is non-simplicial, flipped, dupridge, or mergehorizon\n",
+       facet->id, horizon->id));
+  return False;
+
+ LABELnonconvex:
+  trace2((qh, qh->ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
+         facet->id, neighbor->id, vertex->id, dist));
+  return False;
+
+ LABELnonconvexhorizon:
+  trace2((qh, qh->ferr, 2060, "qh_checkzero: facet f%d and horizon f%d are not clearly convex.  v%d dist %.2g\n",
+      facet->id, horizon->id, vertex->id, dist));
+  return False;
+} /* checkzero */
+
+/*---------------------------------
+
+  qh_compare_anglemerge( mergeA, mergeB )
+    used by qsort() to order qh.facet_mergeset by mergetype and angle (qh.ANGLEmerge, 'Q1')
+    lower numbered mergetypes done first (MRGcoplanar before MRGconcave)
+
+  notes:
+    qh_all_merges processes qh.facet_mergeset by qh_setdellast
+    [mar'19] evaluated various options with eg/q_benchmark and merging of pinched vertices (Q14)
+*/
+int qh_compare_anglemerge(const void *p1, const void *p2) {
+  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
+
+  if (a->mergetype != b->mergetype)
+    return (a->mergetype < b->mergetype ? 1 : -1); /* select MRGcoplanar (1) before MRGconcave (3) */
+  else
+    return (a->angle > b->angle ? 1 : -1);         /* select coplanar merge (1.0) before sharp merge (-0.5) */
+} /* compare_anglemerge */
+
+/*---------------------------------
+
+  qh_compare_facetmerge( mergeA, mergeB )
+    used by qsort() to order merges by mergetype, first merge, first
+    lower numbered mergetypes done first (MRGcoplanar before MRGconcave)
+    if same merge type, flat merges are first
+
+  notes:
+    qh_all_merges processes qh.facet_mergeset by qh_setdellast
+    [mar'19] evaluated various options with eg/q_benchmark and merging of pinched vertices (Q14)
+*/
+int qh_compare_facetmerge(const void *p1, const void *p2) {
+  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
+
+  if (a->mergetype != b->mergetype)
+    return (a->mergetype < b->mergetype ? 1 : -1); /* select MRGcoplanar (1) before MRGconcave (3) */
+  else if (a->mergetype == MRGanglecoplanar)
+    return (a->angle > b->angle ? 1 : -1);         /* if MRGanglecoplanar, select coplanar merge (1.0) before sharp merge (-0.5) */
+  else
+    return (a->distance < b->distance ? 1 : -1);   /* select flat (0.0) merge before wide (1e-10) merge */
+} /* compare_facetmerge */
+
+/*---------------------------------
+
+  qh_comparevisit( vertexA, vertexB )
+    used by qsort() to order vertices by their visitid
+
+  notes:
+    only called by qh_find_newvertex
+*/
+int qh_comparevisit(const void *p1, const void *p2) {
+  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
+
+  if (a->visitid > b->visitid)
+    return 1;
+  return -1;
+} /* comparevisit */
+
+/*---------------------------------
+
+  qh_copynonconvex(qh, atridge )
+    set non-convex flag on other ridges (if any) between same neighbors
+
+  notes:
+    may be faster if use smaller ridge set
+
+  design:
+    for each ridge of atridge's top facet
+      if ridge shares the same neighbor
+        set nonconvex flag
+*/
+void qh_copynonconvex(qhT *qh, ridgeT *atridge) {
+  facetT *facet, *otherfacet;
+  ridgeT *ridge, **ridgep;
+
+  facet= atridge->top;
+  otherfacet= atridge->bottom;
+  atridge->nonconvex= False;
+  FOREACHridge_(facet->ridges) {
+    if (otherfacet == ridge->top || otherfacet == ridge->bottom) {
+      if (ridge != atridge) {
+        ridge->nonconvex= True;
+        trace4((qh, qh->ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d between f%d and f%d\n",
+                atridge->id, ridge->id, facet->id, otherfacet->id));
+        break;
+      }
+    }
+  }
+} /* copynonconvex */
+
+/*---------------------------------
+
+  qh_degen_redundant_facet(qh, facet )
+    check for a degenerate (too few neighbors) or redundant (subset of vertices) facet
+
+  notes:
+    called at end of qh_mergefacet, qh_renamevertex, and qh_reducevertices
+    bumps vertex_visit
+    called if a facet was redundant but no longer is (qh_merge_degenredundant)
+    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
+    see: qh_test_redundant_neighbors, qh_maydropneighbor
+
+  design:
+    test for redundant neighbor
+    test for degenerate facet
+*/
+void qh_degen_redundant_facet(qhT *qh, facetT *facet) {
+  vertexT *vertex, **vertexp;
+  facetT *neighbor, **neighborp;
+
+  trace3((qh, qh->ferr, 3028, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
+          facet->id));
+  if (facet->flipped) {
+    trace2((qh, qh->ferr, 3074, "qh_degen_redundant_facet: f%d is flipped, will merge later\n", facet->id));
+    return;
+  }
+  FOREACHneighbor_(facet) {
+    if (neighbor->flipped) /* disallow merge of non-flipped into flipped, neighbor will be merged later */
+      continue;
+    if (neighbor->visible) {
+      qh_fprintf(qh, qh->ferr, 6357, "qhull internal error (qh_degen_redundant_facet): facet f%d has deleted neighbor f%d (qh.visible_list)\n",
+        facet->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+    }
+    qh->vertex_visit++;
+    FOREACHvertex_(neighbor->vertices)
+      vertex->visitid= qh->vertex_visit;
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->visitid != qh->vertex_visit)
+        break;
+    }
+    if (!vertex) {
+      trace2((qh, qh->ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
+      qh_appendmergeset(qh, facet, neighbor, MRGredundant, 0.0, 1.0);
+      return;
+    }
+  }
+  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
+    qh_appendmergeset(qh, facet, facet, MRGdegen, 0.0, 1.0);
+    trace2((qh, qh->ferr, 2016, "qh_degen_redundant_facet: f%d is degenerate.\n", facet->id));
+  }
+} /* degen_redundant_facet */
+
+
+/*---------------------------------
+
+  qh_delridge_merge(qh, ridge )
+    delete ridge due to a merge
+
+  notes:
+    only called by merge_r.c (qh_mergeridges, qh_renameridgevertex)
+    ridges also freed in qh_freeqhull and qh_mergecycle_ridges
+
+  design:
+    if needed, moves ridge.nonconvex to another ridge
+    sets vertex.delridge for qh_reducevertices
+    deletes ridge from qh.vertex_mergeset
+    deletes ridge from its neighboring facets
+    frees up its memory
+*/
+void qh_delridge_merge(qhT *qh, ridgeT *ridge) {
+  vertexT *vertex, **vertexp;
+  mergeT *merge;
+  int merge_i, merge_n;
+
+  trace3((qh, qh->ferr, 3036, "qh_delridge_merge: delete ridge r%d between f%d and f%d\n",
+    ridge->id, ridge->top->id, ridge->bottom->id));
+  if (ridge->nonconvex)
+    qh_copynonconvex(qh, ridge);
+  FOREACHvertex_(ridge->vertices)
+    vertex->delridge= True;
+  FOREACHmerge_i_(qh, qh->vertex_mergeset) {
+    if (merge->ridge1 == ridge || merge->ridge2 == ridge) {
+      trace3((qh, qh->ferr, 3029, "qh_delridge_merge: drop merge of v%d into v%d (dist %2.2g r%d r%d) due to deleted, duplicated ridge r%d\n",
+        merge->vertex1->id, merge->vertex2->id, merge->distance, merge->ridge1->id, merge->ridge2->id, ridge->id));
+      if (merge->ridge1 == ridge)
+        merge->ridge2->mergevertex= False;
+      else
+        merge->ridge1->mergevertex= False;
+      qh_setdelnth(qh, qh->vertex_mergeset, merge_i);
+      merge_i--; merge_n--; /* next merge after deleted */
+    }
+  }
+  qh_setdel(ridge->top->ridges, ridge);
+  qh_setdel(ridge->bottom->ridges, ridge);
+  qh_delridge(qh, ridge);
+} /* delridge_merge */
+
+
+/*---------------------------------
+
+  qh_drop_mergevertex(qh, merge )
+
+  clear mergevertex flags for ridges of a vertex merge
+*/
+void qh_drop_mergevertex(qhT *qh, mergeT *merge)
+{
+  if (merge->mergetype == MRGvertices) {
+    merge->ridge1->mergevertex= False;
+    merge->ridge1->mergevertex2= True;
+    merge->ridge2->mergevertex= False;
+    merge->ridge2->mergevertex2= True;
+    trace3((qh, qh->ferr, 3032, "qh_drop_mergevertex: unset mergevertex for r%d and r%d due to dropped vertex merge v%d to v%d.  Sets mergevertex2\n",
+      merge->ridge1->id, merge->ridge2->id, merge->vertex1->id, merge->vertex2->id));
+  }
+} /* drop_mergevertex */
+
+/*---------------------------------
+
+  qh_find_newvertex(qh, oldvertex, vertices, ridges )
+    locate new vertex for renaming old vertex
+    vertices is a set of possible new vertices
+      vertices sorted by number of deleted ridges
+
+  returns:
+    newvertex or NULL
+      each ridge includes both newvertex and oldvertex
+    vertices without oldvertex sorted by number of deleted ridges
+    qh.vertex_visit updated
+    sets v.seen
+
+  notes:
+    called by qh_redundant_vertex due to vertex->delridge and qh_rename_sharedvertex
+    sets vertex->visitid to 0..setsize() for vertices
+    new vertex is in one of the ridges
+    renaming will not cause a duplicate ridge
+    renaming will minimize the number of deleted ridges
+    newvertex may not be adjacent in the dual (though unlikely)
+
+  design:
+    for each vertex in vertices
+      set vertex->visitid to number of ridges
+    remove unvisited vertices
+    set qh.vertex_visit above all possible values
+    sort vertices by number of ridges (minimize ridges that need renaming
+    add each ridge to qh.hash_table
+    for each vertex in vertices
+      find the first vertex that would not cause a duplicate ridge after a rename
+*/
+vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges) {
+  vertexT *vertex, **vertexp;
+  setT *newridges;
+  ridgeT *ridge, **ridgep;
+  int size, hashsize;
+  int hash;
+  unsigned int maxvisit;
+
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 4) {
+    qh_fprintf(qh, qh->ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
+             oldvertex->id);
+    FOREACHvertex_(vertices)
+      qh_fprintf(qh, qh->ferr, 8064, "v%d ", vertex->id);
+    FOREACHridge_(ridges)
+      qh_fprintf(qh, qh->ferr, 8065, "r%d ", ridge->id);
+    qh_fprintf(qh, qh->ferr, 8066, "\n");
+  }
+#endif
+  FOREACHridge_(ridges) {
+    FOREACHvertex_(ridge->vertices)
+      vertex->seen= False;
+  }
+  FOREACHvertex_(vertices) {
+    vertex->visitid= 0;  /* v.visitid will be number of ridges */
+    vertex->seen= True;
+  }
+  FOREACHridge_(ridges) {
+    FOREACHvertex_(ridge->vertices) {
+      if (vertex->seen)
+        vertex->visitid++;
+    }
+  }
+  FOREACHvertex_(vertices) {
+    if (!vertex->visitid) {
+      qh_setdelnth(qh, vertices, SETindex_(vertices,vertex));
+      vertexp--; /* repeat since deleted this vertex */
+    }
+  }
+  maxvisit= (unsigned int)qh_setsize(qh, ridges);
+  maximize_(qh->vertex_visit, maxvisit);
+  if (!qh_setsize(qh, vertices)) {
+    trace4((qh, qh->ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
+            oldvertex->id));
+    return NULL;
+  }
+  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(qh, vertices),
+                sizeof(vertexT *), qh_comparevisit);
+  /* can now use qh->vertex_visit */
+  if (qh->PRINTstatistics) {
+    size= qh_setsize(qh, vertices);
+    zinc_(Zintersect);
+    zadd_(Zintersecttot, size);
+    zmax_(Zintersectmax, size);
+  }
+  hashsize= qh_newhashtable(qh, qh_setsize(qh, ridges));
+  FOREACHridge_(ridges)
+    qh_hashridge(qh, qh->hash_table, hashsize, ridge, oldvertex);
+  FOREACHvertex_(vertices) {
+    newridges= qh_vertexridges(qh, vertex, !qh_ALL);
+    FOREACHridge_(newridges) {
+      if (qh_hashridge_find(qh, qh->hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
+        zinc_(Zvertexridge);
+        break;
+      }
+    }
+    qh_settempfree(qh, &newridges);
+    if (!ridge)
+      break;  /* found a rename */
+  }
+  if (vertex) {
+    /* counted in qh_renamevertex */
+    trace2((qh, qh->ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
+      vertex->id, oldvertex->id, qh_setsize(qh, vertices), qh_setsize(qh, ridges)));
+  }else {
+    zinc_(Zfindfail);
+    trace0((qh, qh->ferr, 14, "qh_find_newvertex: no vertex for renaming v%d (all duplicated ridges) during p%d\n",
+      oldvertex->id, qh->furthest_id));
+  }
+  qh_setfree(qh, &qh->hash_table);
+  return vertex;
+} /* find_newvertex */
+
+/*---------------------------------
+
+  qh_findbest_pinchedvertex(qh, merge, apex, nearestp, distp )
+    Determine the best pinched vertex to rename as its nearest neighboring vertex
+    Renaming will remove a duplicate MRGdupridge in newfacet_list
+
+  returns:
+    pinched vertex (either apex or subridge), nearest vertex (subridge or neighbor vertex), and the distance between them
+
+  notes:
+    only called by qh_getpinchedmerges
+    assumes qh.VERTEXneighbors
+    see qh_findbest_ridgevertex
+
+  design:
+    if the facets have the same vertices
+      return the nearest vertex pair
+    else
+      the subridge is the intersection of the two new facets minus the apex
+      the subridge consists of qh.hull_dim-2 horizon vertices
+      the subridge is also a matched ridge for the new facets (its duplicate)
+      determine the nearest vertex to the apex
+      determine the nearest pair of subridge vertices
+      for each vertex in the subridge
+        determine the nearest neighbor vertex (not in the subridge)
+*/
+vertexT *qh_findbest_pinchedvertex(qhT *qh, mergeT *merge, vertexT *apex, vertexT **nearestp, coordT *distp /* qh.newfacet_list */) {
+  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
+  vertexT *bestvertex= NULL, *bestpinched= NULL;
+  setT *subridge, *maybepinched;
+  coordT dist, bestdist= REALmax;
+  coordT pincheddist= (qh->ONEmerge+qh->DISTround)*qh_RATIOpinchedsubridge;
+
+  if (!merge->facet1->simplicial || !merge->facet2->simplicial) {
+    qh_fprintf(qh, qh->ferr, 6351, "qhull internal error (qh_findbest_pinchedvertex): expecting merge of adjacent, simplicial new facets.  f%d or f%d is not simplicial\n",
+      merge->facet1->id, merge->facet2->id);
+    qh_errexit2(qh, qh_ERRqhull, merge->facet1, merge->facet2);
+  }
+  subridge= qh_vertexintersect_new(qh, merge->facet1->vertices, merge->facet2->vertices); /* new setT.  No error_exit() */
+  if (qh_setsize(qh, subridge) == qh->hull_dim) { /* duplicate vertices */
+    bestdist= qh_vertex_bestdist2(qh, subridge, &bestvertex, &bestpinched);
+    if(bestvertex == apex) {
+      bestvertex= bestpinched;
+      bestpinched= apex;
+    }
+  }else {
+    qh_setdel(subridge, apex);
+    if (qh_setsize(qh, subridge) != qh->hull_dim - 2) {
+      qh_fprintf(qh, qh->ferr, 6409, "qhull internal error (qh_findbest_pinchedvertex): expecting subridge of qh.hull_dim-2 vertices for the intersection of new facets f%d and f%d minus their apex.  Got %d vertices\n",
+          merge->facet1->id, merge->facet2->id, qh_setsize(qh, subridge));
+      qh_errexit2(qh, qh_ERRqhull, merge->facet1, merge->facet2);
+    }
+    FOREACHvertex_(subridge) {
+      dist= qh_pointdist(vertex->point, apex->point, qh->hull_dim);
+      if (dist < bestdist) {
+        bestpinched= apex;
+        bestvertex= vertex;
+        bestdist= dist;
+      }
+    }
+    if (bestdist > pincheddist) {
+      FOREACHvertex_(subridge) {
+        FOREACHvertexA_(subridge) {
+          if (vertexA->id > vertex->id) { /* once per vertex pair, do not compare addresses */
+            dist= qh_pointdist(vertexA->point, vertex->point, qh->hull_dim);
+            if (dist < bestdist) {
+              bestpinched= vertexA;
+              bestvertex= vertex;
+              bestdist= dist;
+            }
+          }
+        }
+      }
+    }
+    if (bestdist > pincheddist) {
+      FOREACHvertexA_(subridge) {
+        maybepinched= qh_neighbor_vertices(qh, vertexA, subridge); /* subridge and apex tested above */
+        FOREACHvertex_(maybepinched) {
+          dist= qh_pointdist(vertex->point, vertexA->point, qh->hull_dim);
+          if (dist < bestdist) {
+            bestvertex= vertex;
+            bestpinched= vertexA;
+            bestdist= dist;
+          }
+        }
+        qh_settempfree(qh, &maybepinched);
+      }
+    }
+  }
+  *distp= bestdist;
+  qh_setfree(qh, &subridge); /* qh_err_exit not called since allocated */
+  if (!bestvertex) {  /* should never happen if qh.hull_dim > 2 */
+    qh_fprintf(qh, qh->ferr, 6274, "qhull internal error (qh_findbest_pinchedvertex): did not find best vertex for subridge of dupridge between f%d and f%d, while processing p%d\n", merge->facet1->id, merge->facet2->id, qh->furthest_id);
+    qh_errexit2(qh, qh_ERRqhull, merge->facet1, merge->facet2);
+  }
+  *nearestp= bestvertex;
+  trace2((qh, qh->ferr, 2061, "qh_findbest_pinchedvertex: best pinched p%d(v%d) and vertex p%d(v%d) are closest (%2.2g) for duplicate subridge between f%d and f%d\n",
+      qh_pointid(qh, bestpinched->point), bestpinched->id, qh_pointid(qh, bestvertex->point), bestvertex->id, bestdist, merge->facet1->id, merge->facet2->id));
+  return bestpinched;
+} /* findbest_pinchedvertex */
+
+/*---------------------------------
+
+  qh_findbest_ridgevertex(qh, ridge, pinchedp, distp )
+    Determine the best vertex/pinched-vertex to merge for ridges with the same vertices
+
+  returns:
+    vertex, pinched vertex, and the distance between them
+
+  notes:
+    assumes qh.hull_dim>=3
+    see qh_findbest_pinchedvertex
+
+*/
+vertexT *qh_findbest_ridgevertex(qhT *qh, ridgeT *ridge, vertexT **pinchedp, coordT *distp) {
+  vertexT *bestvertex;
+
+  *distp= qh_vertex_bestdist2(qh, ridge->vertices, &bestvertex, pinchedp);
+  trace4((qh, qh->ferr, 4069, "qh_findbest_ridgevertex: best pinched p%d(v%d) and vertex p%d(v%d) are closest (%2.2g) for duplicated ridge r%d (same vertices) between f%d and f%d\n",
+      qh_pointid(qh, (*pinchedp)->point), (*pinchedp)->id, qh_pointid(qh, bestvertex->point), bestvertex->id, *distp, ridge->id, ridge->top->id, ridge->bottom->id));
+  return bestvertex;
+} /* findbest_ridgevertex */
+
+/*---------------------------------
+
+  qh_findbest_test(qh, testcentrum, facet, neighbor, &bestfacet, &dist, &mindist, &maxdist )
+    test neighbor of facet for qh_findbestneighbor()
+    if testcentrum,
+      tests centrum (assumes it is defined)
+    else
+      tests vertices
+    initially *bestfacet==NULL and *dist==REALmax
+
+  returns:
+    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
+      updates bestfacet, dist, mindist, and maxdist
+
+  notes:
+    called by qh_findbestneighbor
+    ignores pairs of flipped facets, unless that's all there is
+*/
+void qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
+      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
+  realT dist, mindist, maxdist;
+
+  if (facet->flipped && neighbor->flipped && *bestfacet && !(*bestfacet)->flipped)
+    return; /* do not merge flipped into flipped facets */
+  if (testcentrum) {
+    zzinc_(Zbestdist);
+    qh_distplane(qh, facet->center, neighbor, &dist);
+    dist *= qh->hull_dim; /* estimate furthest vertex */
+    if (dist < 0) {
+      maxdist= 0;
+      mindist= dist;
+      dist= -dist;
+    }else {
+      mindist= 0;
+      maxdist= dist;
+    }
+  }else
+    dist= qh_getdistance(qh, facet, neighbor, &mindist, &maxdist);
+  if (dist < *distp) {
+    *bestfacet= neighbor;
+    *mindistp= mindist;
+    *maxdistp= maxdist;
+    *distp= dist;
+  }
+} /* findbest_test */
+
+/*---------------------------------
+
+  qh_findbestneighbor(qh, facet, dist, mindist, maxdist )
+    finds best neighbor (least dist) of a facet for merging
+
+  returns:
+    returns min and max distances and their max absolute value
+
+  notes:
+    error if qh_ASvoronoi
+    avoids merging old into new
+    assumes ridge->nonconvex only set on one ridge between a pair of facets
+    could use an early out predicate but not worth it
+
+  design:
+    if a large facet
+      will test centrum
+    else
+      will test vertices
+    if a large facet
+      test nonconvex neighbors for best merge
+    else
+      test all neighbors for the best merge
+    if testing centrum
+      get distance information
+*/
+facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
+  facetT *neighbor, **neighborp, *bestfacet= NULL;
+  ridgeT *ridge, **ridgep;
+  boolT nonconvex= True, testcentrum= False;
+  int size= qh_setsize(qh, facet->vertices);
+
+  if(qh->CENTERtype==qh_ASvoronoi){
+    qh_fprintf(qh, qh->ferr, 6272, "qhull internal error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  *distp= REALmax;
+  if (size > qh_BESTcentrum2 * qh->hull_dim + qh_BESTcentrum) {
+    testcentrum= True;
+    zinc_(Zbestcentrum);
+    if (!facet->center)
+       facet->center= qh_getcentrum(qh, facet);
+  }
+  if (size > qh->hull_dim + qh_BESTnonconvex) {
+    FOREACHridge_(facet->ridges) {
+      if (ridge->nonconvex) {
+        neighbor= otherfacet_(ridge, facet);
+        qh_findbest_test(qh, testcentrum, facet, neighbor,
+                          &bestfacet, distp, mindistp, maxdistp);
+      }
+    }
+  }
+  if (!bestfacet) {
+    nonconvex= False;
+    FOREACHneighbor_(facet)
+      qh_findbest_test(qh, testcentrum, facet, neighbor,
+                        &bestfacet, distp, mindistp, maxdistp);
+  }
+  if (!bestfacet) {
+    qh_fprintf(qh, qh->ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  if (testcentrum)
+    qh_getdistance(qh, facet, bestfacet, mindistp, maxdistp);
+  trace3((qh, qh->ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
+     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
+  return(bestfacet);
+} /* findbestneighbor */
+
+
+/*---------------------------------
+
+  qh_flippedmerges(qh, facetlist, wasmerge )
+    merge flipped facets into best neighbor
+    assumes qh.facet_mergeset at top of temporary stack
+
+  returns:
+    no flipped facets on facetlist
+    sets wasmerge if merge occurred
+    degen/redundant merges passed through
+
+  notes:
+    othermerges not needed since qh.facet_mergeset is empty before & after
+      keep it in case of change
+
+  design:
+    append flipped facets to qh.facetmergeset
+    for each flipped merge
+      find best neighbor
+      merge facet into neighbor
+      merge degenerate and redundant facets
+    remove flipped merges from qh.facet_mergeset
+*/
+void qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge) {
+  facetT *facet, *neighbor, *facet1;
+  realT dist, mindist, maxdist;
+  mergeT *merge, **mergep;
+  setT *othermerges;
+  int nummerge= 0, numdegen= 0;
+
+  trace4((qh, qh->ferr, 4024, "qh_flippedmerges: begin\n"));
+  FORALLfacet_(facetlist) {
+    if (facet->flipped && !facet->visible)
+      qh_appendmergeset(qh, facet, facet, MRGflip, 0.0, 1.0);
+  }
+  othermerges= qh_settemppop(qh);
+  if(othermerges != qh->facet_mergeset) {
+    qh_fprintf(qh, qh->ferr, 6392, "qhull internal error (qh_flippedmerges): facet_mergeset (%d merges) not at top of tempstack (%d merges)\n",
+        qh_setsize(qh, qh->facet_mergeset), qh_setsize(qh, othermerges));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
+  qh_settemppush(qh, othermerges);
+  FOREACHmerge_(othermerges) {
+    facet1= merge->facet1;
+    if (merge->mergetype != MRGflip || facet1->visible)
+      continue;
+    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+    neighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
+    trace0((qh, qh->ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
+      facet1->id, neighbor->id, dist, qh->furthest_id));
+    qh_mergefacet(qh, facet1, neighbor, merge->mergetype, &mindist, &maxdist, !qh_MERGEapex);
+    nummerge++;
+    if (qh->PRINTstatistics) {
+      zinc_(Zflipped);
+      wadd_(Wflippedtot, dist);
+      wmax_(Wflippedmax, dist);
+    }
+  }
+  FOREACHmerge_(othermerges) {
+    if (merge->facet1->visible || merge->facet2->visible)
+      qh_memfree(qh, merge, (int)sizeof(mergeT)); /* invalidates merge and othermerges */
+    else
+      qh_setappend(qh, &qh->facet_mergeset, merge);
+  }
+  qh_settempfree(qh, &othermerges);
+  numdegen += qh_merge_degenredundant(qh); /* somewhat better here than after each flipped merge -- qtest.sh 10 '500 C1,2e-13 D4' 'd Qbb' */
+  if (nummerge)
+    *wasmerge= True;
+  trace1((qh, qh->ferr, 1010, "qh_flippedmerges: merged %d flipped and %d degenredundant facets into a good neighbor\n",
+    nummerge, numdegen));
+} /* flippedmerges */
+
+
+/*---------------------------------
+
+  qh_forcedmerges(qh, wasmerge )
+    merge dupridges
+    calls qh_check_dupridge to report an error on wide merges
+    assumes qh_settemppop is qh.facet_mergeset
+
+  returns:
+    removes all dupridges on facet_mergeset
+    wasmerge set if merge
+    qh.facet_mergeset may include non-forced merges(none for now)
+    qh.degen_mergeset includes degen/redun merges
+
+  notes:
+    called by qh_premerge
+    dupridges occur when the horizon is pinched,
+        i.e. a subridge occurs in more than two horizon ridges.
+     could rename vertices that pinch the horizon
+    assumes qh_merge_degenredundant() has not be called
+    othermerges isn't needed since facet_mergeset is empty afterwards
+      keep it in case of change
+
+  design:
+    for each dupridge
+      find current facets by chasing f.replace links
+      check for wide merge due to dupridge
+      determine best direction for facet
+      merge one facet into the other
+      remove dupridges from qh.facet_mergeset
+*/
+void qh_forcedmerges(qhT *qh, boolT *wasmerge) {
+  facetT *facet1, *facet2, *merging, *merged, *newfacet;
+  mergeT *merge, **mergep;
+  realT dist, mindist, maxdist, dist2, mindist2, maxdist2;
+  setT *othermerges;
+  int nummerge=0, numflip=0, numdegen= 0;
+  boolT wasdupridge= False;
+
+  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+  trace3((qh, qh->ferr, 3054, "qh_forcedmerges: merge dupridges\n"));
+  othermerges= qh_settemppop(qh); /* was facet_mergeset */
+  if (qh->facet_mergeset != othermerges ) {
+      qh_fprintf(qh, qh->ferr, 6279, "qhull internal error (qh_forcedmerges): qh_settemppop (size %d) is not qh->facet_mergeset (size %d)\n",
+          qh_setsize(qh, othermerges), qh_setsize(qh, qh->facet_mergeset));
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
+  qh_settemppush(qh, othermerges);
+  FOREACHmerge_(othermerges) {
+    if (merge->mergetype != MRGdupridge)
+        continue;
+    wasdupridge= True;
+    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+        qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+    facet1= qh_getreplacement(qh, merge->facet1);  /* must exist, no qh_merge_degenredunant */
+    facet2= qh_getreplacement(qh, merge->facet2);  /* previously merged facet, if any */
+    if (facet1 == facet2)
+      continue;
+    if (!qh_setin(facet2->neighbors, facet1)) {
+      qh_fprintf(qh, qh->ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a dupridge but as f%d and f%d they are no longer neighbors\n",
+               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
+      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+    }
+    dist= qh_getdistance(qh, facet1, facet2, &mindist, &maxdist);
+    dist2= qh_getdistance(qh, facet2, facet1, &mindist2, &maxdist2);
+    qh_check_dupridge(qh, facet1, dist, facet2, dist2);
+    if (dist < dist2) {
+      if (facet2->flipped && !facet1->flipped && dist2 < qh_WIDEdupridge*(qh->ONEmerge+qh->DISTround)) { /* prefer merge of flipped facet */
+        merging= facet2;
+        merged= facet1;
+        dist= dist2;
+        mindist= mindist2;
+        maxdist= maxdist2;
+      }else {
+        merging= facet1;
+        merged= facet2;
+      }
+    }else {
+      if (facet1->flipped && !facet2->flipped && dist < qh_WIDEdupridge*(qh->ONEmerge+qh->DISTround)) { /* prefer merge of flipped facet */
+        merging= facet1;
+        merged= facet2;
+      }else {
+        merging= facet2;
+        merged= facet1;
+        dist= dist2;
+        mindist= mindist2;
+        maxdist= maxdist2;
+      }
+    }
+    qh_mergefacet(qh, merging, merged, merge->mergetype, &mindist, &maxdist, !qh_MERGEapex);
+    numdegen += qh_merge_degenredundant(qh); /* better here than at end -- qtest.sh 10 '500 C1,2e-13 D4' 'd Qbb' */
+    if (facet1->flipped) {
+      zinc_(Zmergeflipdup);
+      numflip++;
+    }else
+      nummerge++;
+    if (qh->PRINTstatistics) {
+      zinc_(Zduplicate);
+      wadd_(Wduplicatetot, dist);
+      wmax_(Wduplicatemax, dist);
+    }
+  }
+  FOREACHmerge_(othermerges) {
+    if (merge->mergetype == MRGdupridge)
+      qh_memfree(qh, merge, (int)sizeof(mergeT)); /* invalidates merge and othermerges */
+    else
+      qh_setappend(qh, &qh->facet_mergeset, merge);
+  }
+  qh_settempfree(qh, &othermerges);
+  if (wasdupridge) {
+    FORALLnew_facets {
+      if (newfacet->dupridge) {
+        newfacet->dupridge= False;
+        newfacet->mergeridge= False;
+        newfacet->mergeridge2= False;
+        if (qh_setsize(qh, newfacet->neighbors) < qh->hull_dim) { /* not tested for MRGdupridge */
+          qh_appendmergeset(qh, newfacet, newfacet, MRGdegen, 0.0, 1.0);
+          trace2((qh, qh->ferr, 2107, "qh_forcedmerges: dupridge f%d is degenerate with fewer than %d neighbors\n",
+                      newfacet->id, qh->hull_dim));
+        }
+      }
+    }
+    numdegen += qh_merge_degenredundant(qh);
+  }
+  if (nummerge || numflip) {
+    *wasmerge= True;
+    trace1((qh, qh->ferr, 1011, "qh_forcedmerges: merged %d facets, %d flipped facets, and %d degenredundant facets across dupridges\n",
+                  nummerge, numflip, numdegen));
+  }
+} /* forcedmerges */
+
+
+/*---------------------------------
+
+  qh_freemergesets(qh )
+    free the merge sets
+
+  notes:
+    matches qh_initmergesets
+*/
+void qh_freemergesets(qhT *qh) {
+
+  if (!qh->facet_mergeset || !qh->degen_mergeset || !qh->vertex_mergeset) {
+    qh_fprintf(qh, qh->ferr, 6388, "qhull internal error (qh_freemergesets): expecting mergesets.  Got a NULL mergeset, qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n",
+      (void *) qh->facet_mergeset, (void *) qh->degen_mergeset, (void *) qh->vertex_mergeset);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (!SETempty_(qh->facet_mergeset) || !SETempty_(qh->degen_mergeset) || !SETempty_(qh->vertex_mergeset)) {
+    qh_fprintf(qh, qh->ferr, 6389, "qhull internal error (qh_freemergesets): expecting empty mergesets.  Got qh.facet_mergeset (%d merges), qh.degen_mergeset (%d merges), qh.vertex_mergeset (%d merges)\n",
+      qh_setsize(qh, qh->facet_mergeset), qh_setsize(qh, qh->degen_mergeset), qh_setsize(qh, qh->vertex_mergeset));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh_settempfree(qh, &qh->facet_mergeset);
+  qh_settempfree(qh, &qh->vertex_mergeset);
+  qh_settempfree(qh, &qh->degen_mergeset);
+} /* freemergesets */
+
+/*---------------------------------
+
+  qh_getmergeset(qh, facetlist )
+    determines nonconvex facets on facetlist
+    tests !tested ridges and nonconvex ridges of !tested facets
+
+  returns:
+    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
+    all ridges tested
+
+  notes:
+    facetlist is qh.facet_newlist, use qh_getmergeset_initial for all facets
+    assumes no nonconvex ridges with both facets tested
+    uses facet->tested/ridge->tested to prevent duplicate tests
+    can not limit tests to modified ridges since the centrum changed
+    uses qh.visit_id
+
+  design:
+    for each facet on facetlist
+      for each ridge of facet
+        if untested ridge
+          test ridge for convexity
+          if non-convex
+            append ridge to qh.facet_mergeset
+    sort qh.facet_mergeset by mergetype and angle or distance
+*/
+void qh_getmergeset(qhT *qh, facetT *facetlist) {
+  facetT *facet, *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+  int nummerges;
+  boolT simplicial;
+
+  nummerges= qh_setsize(qh, qh->facet_mergeset);
+  trace4((qh, qh->ferr, 4026, "qh_getmergeset: started.\n"));
+  qh->visit_id++;
+  FORALLfacet_(facetlist) {
+    if (facet->tested)
+      continue;
+    facet->visitid= qh->visit_id;
+    FOREACHneighbor_(facet)
+      neighbor->seen= False;
+    /* facet must be non-simplicial due to merge to qh.facet_newlist */
+    FOREACHridge_(facet->ridges) {
+      if (ridge->tested && !ridge->nonconvex)
+        continue;
+      /* if r.tested & r.nonconvex, need to retest and append merge */
+      neighbor= otherfacet_(ridge, facet);
+      if (neighbor->seen) { /* another ridge for this facet-neighbor pair was already tested in this loop */
+        ridge->tested= True;
+        ridge->nonconvex= False;   /* only one ridge is marked nonconvex per facet-neighbor pair */
+      }else if (neighbor->visitid != qh->visit_id) {
+        neighbor->seen= True;
+        ridge->nonconvex= False;
+        simplicial= False;
+        if (ridge->simplicialbot && ridge->simplicialtop)
+          simplicial= True;
+        if (qh_test_appendmerge(qh, facet, neighbor, simplicial))
+          ridge->nonconvex= True;
+        ridge->tested= True;
+      }
+    }
+    facet->tested= True;
+  }
+  nummerges= qh_setsize(qh, qh->facet_mergeset);
+  if (qh->ANGLEmerge)
+    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compare_anglemerge);
+  else
+    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compare_facetmerge);
+  nummerges += qh_setsize(qh, qh->degen_mergeset);
+  if (qh->POSTmerging) {
+    zadd_(Zmergesettot2, nummerges);
+  }else {
+    zadd_(Zmergesettot, nummerges);
+    zmax_(Zmergesetmax, nummerges);
+  }
+  trace2((qh, qh->ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
+} /* getmergeset */
+
+
+/*---------------------------------
+
+  qh_getmergeset_initial(qh, facetlist )
+    determine initial qh.facet_mergeset for facets
+    tests all facet/neighbor pairs on facetlist
+
+  returns:
+    sorted qh.facet_mergeset with nonconvex ridges
+    sets facet->tested, ridge->tested, and ridge->nonconvex
+
+  notes:
+    uses visit_id, assumes ridge->nonconvex is False
+    see qh_getmergeset
+
+  design:
+    for each facet on facetlist
+      for each untested neighbor of facet
+        test facet and neighbor for convexity
+        if non-convex
+          append merge to qh.facet_mergeset
+          mark one of the ridges as nonconvex
+    sort qh.facet_mergeset by mergetype and angle or distance
+*/
+void qh_getmergeset_initial(qhT *qh, facetT *facetlist) {
+  facetT *facet, *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+  int nummerges;
+  boolT simplicial;
+
+  qh->visit_id++;
+  FORALLfacet_(facetlist) {
+    facet->visitid= qh->visit_id;
+    FOREACHneighbor_(facet) {
+      if (neighbor->visitid != qh->visit_id) {
+        simplicial= False; /* ignores r.simplicialtop/simplicialbot.  Need to test horizon facets */
+        if (facet->simplicial && neighbor->simplicial)
+          simplicial= True;
+        if (qh_test_appendmerge(qh, facet, neighbor, simplicial)) {
+          FOREACHridge_(neighbor->ridges) {
+            if (facet == otherfacet_(ridge, neighbor)) {
+              ridge->nonconvex= True;
+              break;    /* only one ridge is marked nonconvex */
+            }
+          }
+        }
+      }
+    }
+    facet->tested= True;
+    FOREACHridge_(facet->ridges)
+      ridge->tested= True;
+  }
+  nummerges= qh_setsize(qh, qh->facet_mergeset);
+  if (qh->ANGLEmerge)
+    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compare_anglemerge);
+  else
+    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compare_facetmerge);
+  nummerges += qh_setsize(qh, qh->degen_mergeset);
+  if (qh->POSTmerging) {
+    zadd_(Zmergeinittot2, nummerges);
+  }else {
+    zadd_(Zmergeinittot, nummerges);
+    zmax_(Zmergeinitmax, nummerges);
+  }
+  trace2((qh, qh->ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
+} /* getmergeset_initial */
+
+/*---------------------------------
+
+  qh_getpinchedmerges(qh, apex, maxdist, iscoplanar )
+    get pinched merges for dupridges in qh.facet_mergeset
+    qh.NEWtentative==True
+      qh.newfacet_list with apex
+      qh.horizon_list is attached to qh.visible_list instead of qh.newfacet_list
+      maxdist for vertex-facet of a dupridge
+    qh.facet_mergeset is empty
+    qh.vertex_mergeset is a temporary set
+
+  returns:
+    False if nearest vertex would increase facet width by more than maxdist or qh_WIDEpinched
+    True and iscoplanar, if the pinched vertex is the apex (i.e., make the apex a coplanar point)
+    True and !iscoplanar, if should merge a pinched vertex of a dupridge
+      qh.vertex_mergeset contains one or more MRGsubridge with a pinched vertex and a nearby, neighboring vertex
+    qh.facet_mergeset is empty
+
+  notes:
+    called by qh_buildcone_mergepinched
+    hull_dim >= 3
+    a pinched vertex is in a dupridge and the horizon
+    selects the pinched vertex that is closest to its neighbor
+
+  design:
+    for each dupridge
+        determine the best pinched vertex to be merged into a neighboring vertex
+        if merging the pinched vertex would produce a wide merge (qh_WIDEpinched)
+           ignore pinched vertex with a warning, and use qh_merge_degenredundant instead
+        else
+           append the pinched vertex to vertex_mergeset for merging
+*/
+boolT qh_getpinchedmerges(qhT *qh, vertexT *apex, coordT maxdupdist, boolT *iscoplanar /* qh.newfacet_list, qh.vertex_mergeset */) {
+  mergeT *merge, **mergep, *bestmerge= NULL;
+  vertexT *nearest, *pinched, *bestvertex= NULL, *bestpinched= NULL;
+  boolT result;
+  coordT dist, prevdist, bestdist= REALmax/(qh_RATIOcoplanarapex+1.0); /* allow *3.0 */
+  realT ratio;
+
+  trace2((qh, qh->ferr, 2062, "qh_getpinchedmerges: try to merge pinched vertices for dupridges in new facets with apex p%d(v%d) max dupdist %2.2g\n",
+      qh_pointid(qh, apex->point), apex->id, maxdupdist));
+  *iscoplanar= False;
+  prevdist= fmax_(qh->ONEmerge + qh->DISTround, qh->MINoutside + qh->DISTround);
+  maximize_(prevdist, qh->max_outside);
+  maximize_(prevdist, -qh->min_vertex);
+  qh_mark_dupridges(qh, qh->newfacet_list, !qh_ALL); /* qh.facet_mergeset, creates ridges */
+  /* qh_mark_dupridges is called a second time in qh_premerge */
+  FOREACHmerge_(qh->facet_mergeset) {  /* read-only */
+    if (merge->mergetype != MRGdupridge) {
+      qh_fprintf(qh, qh->ferr, 6393, "qhull internal error (qh_getpinchedmerges): expecting MRGdupridge from qh_mark_dupridges.  Got merge f%d f%d type %d\n",
+        getid_(merge->facet1), getid_(merge->facet2), merge->mergetype);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    /* dist is distance between vertices */
+    pinched= qh_findbest_pinchedvertex(qh, merge, apex, &nearest, &dist /* qh.newfacet_list */);
+    if (pinched == apex && dist < qh_RATIOcoplanarapex*bestdist) { /* prefer coplanar apex since it always works */
+      bestdist= dist/qh_RATIOcoplanarapex;
+      bestmerge= merge;
+      bestpinched= pinched;
+      bestvertex= nearest;
+    }else if (dist < bestdist) {
+      bestdist= dist;
+      bestmerge= merge;
+      bestpinched= pinched;
+      bestvertex= nearest;
+    }
+  }
+  result= False;
+  if (bestmerge && bestdist < maxdupdist) {
+    ratio= bestdist / prevdist;
+    if (ratio > qh_WIDEpinched) {
+      if (bestmerge->facet1->mergehorizon || bestmerge->facet2->mergehorizon) { /* e.g., rbox 175 C3,2e-13 t1539182828 | qhull d */
+        trace1((qh, qh->ferr, 1051, "qh_getpinchedmerges: dupridge (MRGdupridge) of coplanar horizon would produce a wide merge (%.0fx) due to pinched vertices v%d and v%d (dist %2.2g) for f%d and f%d.  qh_mergecycle_all will merge one or both facets\n",
+          ratio, bestpinched->id, bestvertex->id, bestdist, bestmerge->facet1->id, bestmerge->facet2->id));
+      }else {
+        qh_fprintf(qh, qh->ferr, 7081, "qhull precision warning (qh_getpinchedmerges): pinched vertices v%d and v%d (dist %2.2g, %.0fx) would produce a wide merge for f%d and f%d.  Will merge dupridge instead\n",
+          bestpinched->id, bestvertex->id, bestdist, ratio, bestmerge->facet1->id, bestmerge->facet2->id);
+      }
+    }else {
+      if (bestpinched == apex) {
+        trace2((qh, qh->ferr, 2063, "qh_getpinchedmerges: will make the apex a coplanar point.  apex p%d(v%d) is the nearest vertex to v%d on dupridge.  Dist %2.2g\n",
+          qh_pointid(qh, apex->point), apex->id, bestvertex->id, bestdist*qh_RATIOcoplanarapex));
+        qh->coplanar_apex= apex->point;
+        *iscoplanar= True;
+        result= True;
+      }else if (qh_setin(bestmerge->facet1->vertices, bestpinched) != qh_setin(bestmerge->facet2->vertices, bestpinched)) { /* pinched in one facet but not the other facet */
+        trace2((qh, qh->ferr, 2064, "qh_getpinchedmerges: will merge new facets to resolve dupridge between f%d and f%d with pinched v%d and v%d\n",
+          bestmerge->facet1->id, bestmerge->facet2->id, bestpinched->id, bestvertex->id));
+        qh_appendvertexmerge(qh, bestpinched, bestvertex, MRGsubridge, bestdist, NULL, NULL);
+        result= True;
+      }else {
+        trace2((qh, qh->ferr, 2065, "qh_getpinchedmerges: will merge pinched v%d into v%d to resolve dupridge between f%d and f%d\n",
+          bestpinched->id, bestvertex->id, bestmerge->facet1->id, bestmerge->facet2->id));
+        qh_appendvertexmerge(qh, bestpinched, bestvertex, MRGsubridge, bestdist, NULL, NULL);
+        result= True;
+      }
+    }
+  }
+  /* delete MRGdupridge, qh_mark_dupridges is called a second time in qh_premerge */
+  while ((merge= (mergeT *)qh_setdellast(qh->facet_mergeset)))
+    qh_memfree(qh, merge, (int)sizeof(mergeT));
+  return result;
+}/* getpinchedmerges */
+
+/*---------------------------------
+
+  qh_hasmerge( mergeset, mergetype, facetA, facetB )
+    True if mergeset has mergetype for facetA and facetB
+*/
+boolT   qh_hasmerge(setT *mergeset, mergeType type, facetT *facetA, facetT *facetB) {
+  mergeT *merge, **mergep;
+
+  FOREACHmerge_(mergeset) {
+    if (merge->mergetype == type) {
+      if (merge->facet1 == facetA && merge->facet2 == facetB)
+        return True;
+      if (merge->facet1 == facetB && merge->facet2 == facetA)
+        return True;
+    }
+  }
+  return False;
+}/* hasmerge */
+
+/*---------------------------------
+
+  qh_hashridge(qh, hashtable, hashsize, ridge, oldvertex )
+    add ridge to hashtable without oldvertex
+
+  notes:
+    assumes hashtable is large enough
+
+  design:
+    determine hash value for ridge without oldvertex
+    find next empty slot for ridge
+*/
+void qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
+  int hash;
+  ridgeT *ridgeA;
+
+  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, oldvertex);
+  while (True) {
+    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
+      SETelem_(hashtable, hash)= ridge;
+      break;
+    }else if (ridgeA == ridge)
+      break;
+    if (++hash == hashsize)
+      hash= 0;
+  }
+} /* hashridge */
+
+
+/*---------------------------------
+
+  qh_hashridge_find(qh, hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
+    returns matching ridge without oldvertex in hashtable
+      for ridge without vertex
+    if oldvertex is NULL
+      matches with any one skip
+
+  returns:
+    matching ridge or NULL
+    if no match,
+      if ridge already in   table
+        hashslot= -1
+      else
+        hashslot= next NULL index
+
+  notes:
+    assumes hashtable is large enough
+    can't match ridge to itself
+
+  design:
+    get hash value for ridge without vertex
+    for each hashslot
+      return match if ridge matches ridgeA without oldvertex
+*/
+ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
+              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
+  int hash;
+  ridgeT *ridgeA;
+
+  *hashslot= 0;
+  zinc_(Zhashridge);
+  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, vertex);
+  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
+    if (ridgeA == ridge)
+      *hashslot= -1;
+    else {
+      zinc_(Zhashridgetest);
+      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
+        return ridgeA;
+    }
+    if (++hash == hashsize)
+      hash= 0;
+  }
+  if (!*hashslot)
+    *hashslot= hash;
+  return NULL;
+} /* hashridge_find */
+
+
+/*---------------------------------
+
+  qh_initmergesets(qh )
+    initialize the merge sets
+    if 'all', include qh.degen_mergeset
+
+  notes:
+    matches qh_freemergesets
+*/
+void qh_initmergesets(qhT *qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */) {
+
+  if (qh->facet_mergeset || qh->degen_mergeset || qh->vertex_mergeset) {
+    qh_fprintf(qh, qh->ferr, 6386, "qhull internal error (qh_initmergesets): expecting NULL mergesets.  Got qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n",
+      (void *) qh->facet_mergeset, (void *) qh->degen_mergeset, (void *) qh->vertex_mergeset);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
+  qh->vertex_mergeset= qh_settemp(qh, qh->TEMPsize);
+  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize); /* last temporary set for qh_forcedmerges */
+} /* initmergesets */
+
+/*---------------------------------
+
+  qh_makeridges(qh, facet )
+    creates explicit ridges between simplicial facets
+
+  returns:
+    facet with ridges and without qh_MERGEridge
+    ->simplicial is False
+    if facet was tested, new ridges are tested
+
+  notes:
+    allows qh_MERGEridge flag
+    uses existing ridges
+    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
+
+  see:
+    qh_mergecycle_ridges()
+    qh_rename_adjacentvertex for qh_merge_pinchedvertices
+
+  design:
+    look for qh_MERGEridge neighbors
+    mark neighbors that already have ridges
+    for each unprocessed neighbor of facet
+      create a ridge for neighbor and facet
+    if any qh_MERGEridge neighbors
+      delete qh_MERGEridge flags (previously processed by qh_mark_dupridges)
+*/
+void qh_makeridges(qhT *qh, facetT *facet) {
+  facetT *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+  int neighbor_i, neighbor_n;
+  boolT toporient, mergeridge= False;
+
+  if (!facet->simplicial)
+    return;
+  trace4((qh, qh->ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
+  facet->simplicial= False;
+  FOREACHneighbor_(facet) {
+    if (neighbor == qh_MERGEridge)
+      mergeridge= True;
+    else
+      neighbor->seen= False;
+  }
+  FOREACHridge_(facet->ridges)
+    otherfacet_(ridge, facet)->seen= True;
+  FOREACHneighbor_i_(qh, facet) {
+    if (neighbor == qh_MERGEridge)
+      continue;  /* fixed by qh_mark_dupridges */
+    else if (!neighbor->seen) {  /* no current ridges */
+      ridge= qh_newridge(qh);
+      ridge->vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
+                                                          neighbor_i, 0);
+      toporient= (boolT)(facet->toporient ^ (neighbor_i & 0x1));
+      if (toporient) {
+        ridge->top= facet;
+        ridge->bottom= neighbor;
+        ridge->simplicialtop= True;
+        ridge->simplicialbot= neighbor->simplicial;
+      }else {
+        ridge->top= neighbor;
+        ridge->bottom= facet;
+        ridge->simplicialtop= neighbor->simplicial;
+        ridge->simplicialbot= True;
+      }
+      if (facet->tested && !mergeridge)
+        ridge->tested= True;
+#if 0 /* this also works */
+      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
+      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
+        ridge->top= neighbor;
+        ridge->bottom= facet;
+        ridge->simplicialtop= True;
+        ridge->simplicialbot= neighbor->simplicial;
+      }else {
+        ridge->top= facet;
+        ridge->bottom= neighbor;
+        ridge->simplicialtop= neighbor->simplicial;
+        ridge->simplicialbot= True;
+      }
+#endif
+      qh_setappend(qh, &(facet->ridges), ridge);
+      trace5((qh, qh->ferr, 5005, "makeridges: appended r%d to ridges for f%d.  Next is ridges for neighbor f%d\n",
+            ridge->id, facet->id, neighbor->id));
+      qh_setappend(qh, &(neighbor->ridges), ridge);
+      if (qh->ridge_id == qh->traceridge_id)
+        qh->traceridge= ridge;
+    }
+  }
+  if (mergeridge) {
+    while (qh_setdel(facet->neighbors, qh_MERGEridge))
+      ; /* delete each one */
+  }
+} /* makeridges */
+
+
+/*---------------------------------
+
+  qh_mark_dupridges(qh, facetlist, allmerges )
+    add duplicated ridges to qh.facet_mergeset
+    facet-dupridge is true if it contains a subridge shared by more than one new facet
+    for each such facet, one has a neighbor marked qh_MERGEridge
+    allmerges is true if merging dupridges
+    allmerges is false if merging pinched vertices followed by retry addpoint
+      qh_mark_dupridges will be called again if pinched vertices not found
+
+  returns:
+    dupridges on qh.facet_mergeset (MRGdupridge)
+    f.mergeridge and f.mergeridge2 set for facet
+    f.mergeridge set for neighbor
+    if allmerges is true
+      make ridges for facets with dupridges as marked by qh_MERGEridge and both sides facet->dupridge
+      removes qh_MERGEridge from neighbor sets
+
+  notes:
+    called by qh_premerge and qh_getpinchedmerges
+    dupridges are due to duplicate subridges
+        i.e. a subridge occurs in more than two horizon ridges.
+        i.e., a ridge has more than two neighboring facets
+    dupridges occur in at least two cases
+    1) a pinched horizon with nearly adjacent vertices -> merge the vertices (qh_getpinchedmerges)
+    2) more than one newfacet for a horizon face -> merge coplanar facets (qh_premerge)
+    qh_matchdupridge previously identified the furthest apart pair of facets to retain
+       they must have a matching subridge and the same orientation
+    only way to set facet->mergeridge and mergeridge2
+    uses qh.visit_id
+
+  design:
+    for all facets on facetlist
+      if facet contains a dupridge
+        for each neighbor of facet
+          if neighbor marked qh_MERGEridge (one side of the merge)
+            set facet->mergeridge
+          else
+            if neighbor contains a dupridge
+            and the back link is qh_MERGEridge
+              append dupridge to qh.facet_mergeset
+   exit if !allmerges for repeating qh_mark_dupridges later
+   for each dupridge
+     make ridge sets in preparation for merging
+     remove qh_MERGEridge from neighbor set
+   for each dupridge
+     restore the missing neighbor from the neighbor set that was qh_MERGEridge
+     add the missing ridge for this neighbor
+*/
+void qh_mark_dupridges(qhT *qh, facetT *facetlist, boolT allmerges) {
+  facetT *facet, *neighbor, **neighborp;
+  int nummerge=0;
+  mergeT *merge, **mergep;
+
+  trace4((qh, qh->ferr, 4028, "qh_mark_dupridges: identify dupridges in facetlist f%d, allmerges? %d\n",
+    facetlist->id, allmerges));
+  FORALLfacet_(facetlist) {  /* not necessary for first call */
+    facet->mergeridge2= False;
+    facet->mergeridge= False;
+  }
+  FORALLfacet_(facetlist) {
+    if (facet->dupridge) {
+      FOREACHneighbor_(facet) {
+        if (neighbor == qh_MERGEridge) {
+          facet->mergeridge= True;
+          continue;
+        }
+        if (neighbor->dupridge) {
+          if (!qh_setin(neighbor->neighbors, facet)) { /* i.e., it is qh_MERGEridge, neighbors are distinct */
+            qh_appendmergeset(qh, facet, neighbor, MRGdupridge, 0.0, 1.0);
+            facet->mergeridge2= True;
+            facet->mergeridge= True;
+            nummerge++;
+          }else if (qh_setequal(facet->vertices, neighbor->vertices)) { /* neighbors are the same except for horizon and qh_MERGEridge, see QH7085 */
+            trace3((qh, qh->ferr, 3043, "qh_mark_dupridges): dupridge due to duplicate vertices for subridges f%d and f%d\n",
+                 facet->id, neighbor->id));
+            qh_appendmergeset(qh, facet, neighbor, MRGdupridge, 0.0, 1.0);
+            facet->mergeridge2= True;
+            facet->mergeridge= True;
+            nummerge++;
+            break; /* same for all neighbors */
+          }
+        }
+      }
+    }
+  }
+  if (!nummerge)
+    return;
+  if (!allmerges) {
+    trace1((qh, qh->ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges (MRGdupridge) for qh_getpinchedmerges\n", nummerge));
+    return;
+  }
+  trace1((qh, qh->ferr, 1048, "qh_mark_dupridges: found %d duplicated ridges (MRGdupridge) for qh_premerge.  Prepare facets for merging\n", nummerge));
+  /* make ridges in preparation for merging */
+  FORALLfacet_(facetlist) {
+    if (facet->mergeridge && !facet->mergeridge2)
+      qh_makeridges(qh, facet);
+  }
+  trace3((qh, qh->ferr, 3075, "qh_mark_dupridges: restore missing neighbors and ridges due to qh_MERGEridge\n"));
+  FOREACHmerge_(qh->facet_mergeset) {   /* restore the missing neighbors */
+    if (merge->mergetype == MRGdupridge) { /* only between simplicial facets */
+      if (merge->facet2->mergeridge2 && qh_setin(merge->facet2->neighbors, merge->facet1)) {
+        /* Due to duplicate or multiple subridges, e.g., ../eg/qtest.sh t712682 '200 s W1e-13  C1,1e-13 D5' 'd'
+            merge->facet1:    - neighboring facets: f27779 f59186 f59186 f59186 MERGEridge f59186
+            merge->facet2:    - neighboring facets: f27779 f59100 f59100 f59100 f59100 f59100
+           or, ../eg/qtest.sh 100 '500 s W1e-13 C1,1e-13 D4' 'd'
+           both facets will be degenerate after merge, consider for special case handling
+        */
+        qh_fprintf(qh, qh->ferr, 6361, "qhull topological error (qh_mark_dupridges): multiple dupridges for f%d and f%d, including reverse\n",
+          merge->facet1->id, merge->facet2->id);
+        qh_errexit2(qh, qh_ERRtopology, merge->facet1, merge->facet2);
+      }else
+        qh_setappend(qh, &merge->facet2->neighbors, merge->facet1);
+      qh_makeridges(qh, merge->facet1);   /* and the missing ridges */
+    }
+  }
+} /* mark_dupridges */
+
+/*---------------------------------
+
+  qh_maybe_duplicateridge(qh, ridge )
+    add MRGvertices if neighboring facet has another ridge with the same vertices
+
+  returns:
+    adds rename requests to qh.vertex_mergeset
+
+  notes:
+    called by qh_renamevertex
+    nop if 2-D
+    expensive test
+    Duplicate ridges may lead to new facets with same vertex set (QH7084), will try merging vertices
+    same as qh_maybe_duplicateridges
+
+  design:
+    for the two neighbors
+      if non-simplicial
+        for each ridge with the same first and last vertices (max id and min id)
+          if the remaining vertices are the same
+            get the closest pair of vertices
+            add to vertex_mergeset for merging
+*/
+void qh_maybe_duplicateridge(qhT *qh, ridgeT *ridgeA) {
+  ridgeT *ridge, **ridgep;
+  vertexT *vertex, *pinched;
+  facetT *neighbor;
+  coordT dist;
+  int i, k, last= qh->hull_dim-2;
+
+  if (qh->hull_dim < 3 )
+    return;
+
+  for (neighbor= ridgeA->top, i=0; i<2; neighbor= ridgeA->bottom, i++) {
+    if (!neighbor->simplicial && neighbor->nummerge > 0) { /* skip degenerate neighbors with both new and old vertices that will be merged */
+      FOREACHridge_(neighbor->ridges) {
+        if (ridge != ridgeA && SETfirst_(ridge->vertices) == SETfirst_(ridgeA->vertices)) {
+          if (SETelem_(ridge->vertices, last) == SETelem_(ridgeA->vertices, last)) {
+            for (k=1; kvertices, k) != SETelem_(ridgeA->vertices, k))
+                break;
+            }
+            if (k == last) {
+              vertex= qh_findbest_ridgevertex(qh, ridge, &pinched, &dist);
+              trace2((qh, qh->ferr, 2069, "qh_maybe_duplicateridge: will merge v%d into v%d (dist %2.2g) due to duplicate ridges r%d/r%d with the same vertices.  mergevertex set\n",
+                pinched->id, vertex->id, dist, ridgeA->id, ridge->id));
+              qh_appendvertexmerge(qh, pinched, vertex, MRGvertices, dist, ridgeA, ridge);
+              ridge->mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */
+              ridgeA->mergevertex= True;
+            }
+          }
+        }
+      }
+    }
+  }
+} /* maybe_duplicateridge */
+
+/*---------------------------------
+
+  qh_maybe_duplicateridges(qh, facet )
+    if Q17, add MRGvertices if facet has ridges with the same vertices
+
+  returns:
+    adds rename requests to qh.vertex_mergeset
+
+  notes:
+    called at end of qh_mergefacet and qh_mergecycle_all
+    only enabled if qh.CHECKduplicates ('Q17') and 3-D or more
+    expensive test, not worth it
+    same as qh_maybe_duplicateridge
+
+  design:
+    for all ridge pairs in facet
+        if the same first and last vertices (max id and min id)
+          if the remaining vertices are the same
+            get the closest pair of vertices
+            add to vertex_mergeset for merging
+*/
+void qh_maybe_duplicateridges(qhT *qh, facetT *facet) {
+  facetT *otherfacet;
+  ridgeT *ridge, *ridge2;
+  vertexT *vertex, *pinched;
+  coordT dist;
+  int ridge_i, ridge_n, i, k, last_v= qh->hull_dim-2;
+
+  if (qh->hull_dim < 3 || !qh->CHECKduplicates)
+    return;
+
+  FOREACHridge_i_(qh, facet->ridges) {
+    otherfacet= otherfacet_(ridge, facet);
+    if (otherfacet->degenerate || otherfacet->redundant || otherfacet->dupridge || otherfacet->flipped) /* will merge */
+      continue;
+    for (i=ridge_i+1; i < ridge_n; i++) {
+      ridge2= SETelemt_(facet->ridges, i, ridgeT);
+      otherfacet= otherfacet_(ridge2, facet);
+      if (otherfacet->degenerate || otherfacet->redundant || otherfacet->dupridge || otherfacet->flipped) /* will merge */
+        continue;
+      /* optimize qh_setequal(ridge->vertices, ridge2->vertices) */
+      if (SETelem_(ridge->vertices, last_v) == SETelem_(ridge2->vertices, last_v)) { /* SETfirst is likely to be the same */
+        if (SETfirst_(ridge->vertices) == SETfirst_(ridge2->vertices)) {
+          for (k=1; kvertices, k) != SETelem_(ridge2->vertices, k))
+              break;
+          }
+          if (k == last_v) {
+            vertex= qh_findbest_ridgevertex(qh, ridge, &pinched, &dist);
+            if (ridge->top == ridge2->bottom && ridge->bottom == ridge2->top) {
+              /* proof that ridges may have opposite orientation */
+              trace2((qh, qh->ferr, 2088, "qh_maybe_duplicateridges: will merge v%d into v%d (dist %2.2g) due to opposite oriented ridges r%d/r%d for f%d and f%d\n",
+                pinched->id, vertex->id, dist, ridge->id, ridge2->id, ridge->top->id, ridge->bottom->id));
+            }else {
+              trace2((qh, qh->ferr, 2083, "qh_maybe_duplicateridges: will merge v%d into v%d (dist %2.2g) due to duplicate ridges with the same vertices r%d/r%d in merged facet f%d\n",
+                pinched->id, vertex->id, dist, ridge->id, ridge2->id, facet->id));
+            }
+            qh_appendvertexmerge(qh, pinched, vertex, MRGvertices, dist, ridge, ridge2);
+            ridge->mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */
+            ridge2->mergevertex= True;
+          }
+        }
+      }
+    }
+  }
+} /* maybe_duplicateridges */
+
+/*---------------------------------
+
+  qh_maydropneighbor(qh, facet )
+    drop neighbor relationship if ridge was deleted between a non-simplicial facet and its neighbors
+
+  returns:
+    for deleted ridges
+      ridges made for simplicial neighbors
+      neighbor sets updated
+      appends degenerate facets to qh.facet_mergeset
+
+  notes:
+    called by qh_renamevertex
+    assumes neighbors do not include qh_MERGEridge (qh_makeridges)
+    won't cause redundant facets since vertex inclusion is the same
+    may drop vertex and neighbor if no ridge
+    uses qh.visit_id
+
+  design:
+    visit all neighbors with ridges
+    for each unvisited neighbor of facet
+      delete neighbor and facet from the non-simplicial neighbor sets
+      if neighbor becomes degenerate
+        append neighbor to qh.degen_mergeset
+    if facet is degenerate
+      append facet to qh.degen_mergeset
+*/
+void qh_maydropneighbor(qhT *qh, facetT *facet) {
+  ridgeT *ridge, **ridgep;
+  facetT *neighbor, **neighborp;
+
+  qh->visit_id++;
+  trace4((qh, qh->ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
+          facet->id));
+  if (facet->simplicial) {
+    qh_fprintf(qh, qh->ferr, 6278, "qhull internal error (qh_maydropneighbor): not valid for simplicial f%d while adding furthest p%d\n",
+      facet->id, qh->furthest_id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  FOREACHridge_(facet->ridges) {
+    ridge->top->visitid= qh->visit_id;
+    ridge->bottom->visitid= qh->visit_id;
+  }
+  FOREACHneighbor_(facet) {
+    if (neighbor->visible) {
+      qh_fprintf(qh, qh->ferr, 6358, "qhull internal error (qh_maydropneighbor): facet f%d has deleted neighbor f%d (qh.visible_list)\n",
+            facet->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+    }
+    if (neighbor->visitid != qh->visit_id) {
+      trace2((qh, qh->ferr, 2104, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors while adding furthest p%d\n",
+            facet->id, neighbor->id, qh->furthest_id));
+      if (neighbor->simplicial) {
+        qh_fprintf(qh, qh->ferr, 6280, "qhull internal error (qh_maydropneighbor): not valid for simplicial neighbor f%d of f%d while adding furthest p%d\n",
+            neighbor->id, facet->id, qh->furthest_id);
+        qh_errexit2(qh, qh_ERRqhull, neighbor, facet);
+      }
+      zinc_(Zdropneighbor);
+      qh_setdel(neighbor->neighbors, facet);
+      if (qh_setsize(qh, neighbor->neighbors) < qh->hull_dim) {
+        zinc_(Zdropdegen);
+        qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, 0.0, qh_ANGLEnone);
+        trace2((qh, qh->ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
+      }
+      qh_setdel(facet->neighbors, neighbor);
+      neighborp--;  /* repeat, deleted a neighbor */
+    }
+  }
+  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
+    zinc_(Zdropdegen);
+    qh_appendmergeset(qh, facet, facet, MRGdegen, 0.0, qh_ANGLEnone);
+    trace2((qh, qh->ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
+  }
+} /* maydropneighbor */
+
+
+/*---------------------------------
+
+  qh_merge_degenredundant(qh)
+    merge all degenerate and redundant facets
+    qh.degen_mergeset contains merges from  qh_test_degen_neighbors, qh_test_redundant_neighbors, and qh_degen_redundant_facet
+
+  returns:
+    number of merges performed
+    resets facet->degenerate/redundant
+    if deleted (visible) facet has no neighbors
+      sets ->f.replace to NULL
+
+  notes:
+    redundant merges happen before degenerate ones
+    merging and renaming vertices can result in degen/redundant facets
+    check for coplanar and convex neighbors afterwards
+
+  design:
+    for each merge on qh.degen_mergeset
+      if redundant merge
+        if non-redundant facet merged into redundant facet
+          recheck facet for redundancy
+        else
+          merge redundant facet into other facet
+*/
+int qh_merge_degenredundant(qhT *qh) {
+  int size;
+  mergeT *merge;
+  facetT *bestneighbor, *facet1, *facet2, *facet3;
+  realT dist, mindist, maxdist;
+  vertexT *vertex, **vertexp;
+  int nummerges= 0;
+  mergeType mergetype;
+  setT *mergedfacets;
+
+  trace2((qh, qh->ferr, 2095, "qh_merge_degenredundant: merge %d degenerate, redundant, and mirror facets\n",
+    qh_setsize(qh, qh->degen_mergeset)));
+  mergedfacets= qh_settemp(qh, qh->TEMPsize);
+  while ((merge= (mergeT *)qh_setdellast(qh->degen_mergeset))) {
+    facet1= merge->facet1;
+    facet2= merge->facet2;
+    mergetype= merge->mergetype;
+    qh_memfree(qh, merge, (int)sizeof(mergeT)); /* 'merge' is invalidated */
+    if (facet1->visible)
+      continue;
+    facet1->degenerate= False;
+    facet1->redundant= False;
+    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+    if (mergetype == MRGredundant) {
+      zinc_(Zredundant);
+      facet3= qh_getreplacement(qh, facet2); /* the same facet if !facet2.visible */
+      if (!facet3) {
+          qh_fprintf(qh, qh->ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d is redundant but visible f%d has no replacement\n",
+               facet1->id, getid_(facet2));
+          qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+      }
+      qh_setunique(qh, &mergedfacets, facet3);
+      if (facet1 == facet3) {
+        continue;
+      }
+      trace2((qh, qh->ferr, 2025, "qh_merge_degenredundant: merge redundant f%d into f%d (arg f%d)\n",
+            facet1->id, facet3->id, facet2->id));
+      qh_mergefacet(qh, facet1, facet3, mergetype, NULL, NULL, !qh_MERGEapex);
+      /* merge distance is already accounted for */
+      nummerges++;
+    }else {  /* mergetype == MRGdegen or MRGmirror, other merges may have fixed */
+      if (!(size= qh_setsize(qh, facet1->neighbors))) {
+        zinc_(Zdelfacetdup);
+        trace2((qh, qh->ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
+        qh_willdelete(qh, facet1, NULL);
+        FOREACHvertex_(facet1->vertices) {
+          qh_setdel(vertex->neighbors, facet1);
+          if (!SETfirst_(vertex->neighbors)) {
+            zinc_(Zdegenvertex);
+            trace2((qh, qh->ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
+                 vertex->id, facet1->id));
+            vertex->deleted= True;
+            qh_setappend(qh, &qh->del_vertices, vertex);
+          }
+        }
+        nummerges++;
+      }else if (size < qh->hull_dim) {
+        bestneighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
+        trace2((qh, qh->ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
+              facet1->id, size, bestneighbor->id, dist));
+        qh_mergefacet(qh, facet1, bestneighbor, mergetype, &mindist, &maxdist, !qh_MERGEapex);
+        nummerges++;
+        if (qh->PRINTstatistics) {
+          zinc_(Zdegen);
+          wadd_(Wdegentot, dist);
+          wmax_(Wdegenmax, dist);
+        }
+      } /* else, another merge fixed the degeneracy and redundancy tested */
+    }
+  }
+  qh_settempfree(qh, &mergedfacets);
+  return nummerges;
+} /* merge_degenredundant */
+
+/*---------------------------------
+
+  qh_merge_nonconvex(qh, facet1, facet2, mergetype )
+    remove non-convex ridge between facet1 into facet2
+    mergetype gives why the facet's are non-convex
+
+  returns:
+    merges one of the facets into the best neighbor
+
+  notes:
+    mergetype is MRGcoplanar..MRGconvex
+
+  design:
+    if one of the facets is a new facet
+      prefer merging new facet into old facet
+    find best neighbors for both facets
+    merge the nearest facet into its best neighbor
+    update the statistics
+*/
+void qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) {
+  facetT *bestfacet, *bestneighbor, *neighbor, *merging, *merged;
+  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
+
+  if (mergetype < MRGcoplanar || mergetype > MRGconcavecoplanar) {
+    qh_fprintf(qh, qh->ferr, 6398, "qhull internal error (qh_merge_nonconvex): expecting mergetype MRGcoplanar..MRGconcavecoplanar.  Got merge f%d and f%d type %d\n",
+      facet1->id, facet2->id, mergetype);
+    qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+  }
+  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+  trace3((qh, qh->ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
+      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
+  /* concave or coplanar */
+  if (!facet1->newfacet) {
+    bestfacet= facet2;   /* avoid merging old facet if new is ok */
+    facet2= facet1;
+    facet1= bestfacet;
+  }else
+    bestfacet= facet1;
+  bestneighbor= qh_findbestneighbor(qh, bestfacet, &dist, &mindist, &maxdist);
+  neighbor= qh_findbestneighbor(qh, facet2, &dist2, &mindist2, &maxdist2);
+  if (dist < dist2) {
+    merging= bestfacet;
+    merged= bestneighbor;
+  }else if (qh->AVOIDold && !facet2->newfacet
+  && ((mindist >= -qh->MAXcoplanar && maxdist <= qh->max_outside)
+       || dist * 1.5 < dist2)) {
+    zinc_(Zavoidold);
+    wadd_(Wavoidoldtot, dist);
+    wmax_(Wavoidoldmax, dist);
+    trace2((qh, qh->ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
+           facet2->id, dist2, facet1->id, dist2));
+    merging= bestfacet;
+    merged= bestneighbor;
+  }else {
+    merging= facet2;
+    merged= neighbor;
+    dist= dist2;
+    mindist= mindist2;
+    maxdist= maxdist2;
+  }
+  qh_mergefacet(qh, merging, merged, mergetype, &mindist, &maxdist, !qh_MERGEapex);
+  /* caller merges qh_degenredundant */
+  if (qh->PRINTstatistics) {
+    if (mergetype == MRGanglecoplanar) {
+      zinc_(Zacoplanar);
+      wadd_(Wacoplanartot, dist);
+      wmax_(Wacoplanarmax, dist);
+    }else if (mergetype == MRGconcave) {
+      zinc_(Zconcave);
+      wadd_(Wconcavetot, dist);
+      wmax_(Wconcavemax, dist);
+    }else if (mergetype == MRGconcavecoplanar) {
+      zinc_(Zconcavecoplanar);
+      wadd_(Wconcavecoplanartot, dist);
+      wmax_(Wconcavecoplanarmax, dist);
+    }else { /* MRGcoplanar */
+      zinc_(Zcoplanar);
+      wadd_(Wcoplanartot, dist);
+      wmax_(Wcoplanarmax, dist);
+    }
+  }
+} /* merge_nonconvex */
+
+/*---------------------------------
+
+  qh_merge_pinchedvertices(qh, apex )
+    merge pinched vertices in qh.vertex_mergeset to avoid qh_forcedmerges of dupridges
+
+  notes:
+    only called by qh_all_vertexmerges
+    hull_dim >= 3
+
+  design:
+    make vertex neighbors if necessary
+    for each pinched vertex
+      determine the ridges for the pinched vertex (make ridges as needed)
+      merge the pinched vertex into the horizon vertex
+      merge the degenerate and redundant facets that result
+    check and resolve new dupridges
+*/
+void qh_merge_pinchedvertices(qhT *qh, int apexpointid /* qh.newfacet_list */) {
+  mergeT *merge, *mergeA, **mergeAp;
+  vertexT *vertex, *vertex2;
+  realT dist;
+  boolT firstmerge= True;
+
+  qh_vertexneighbors(qh);
+  if (qh->visible_list || qh->newfacet_list || qh->newvertex_list) {
+    qh_fprintf(qh, qh->ferr, 6402, "qhull internal error (qh_merge_pinchedvertices): qh.visible_list (f%d), newfacet_list (f%d), or newvertex_list (v%d) not empty\n",
+      getid_(qh->visible_list), getid_(qh->newfacet_list), getid_(qh->newvertex_list));
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->visible_list= qh->newfacet_list= qh->facet_tail;
+  qh->newvertex_list= qh->vertex_tail;
+  qh->isRenameVertex= True; /* disable duplicate ridge vertices check in qh_checkfacet */
+  while ((merge= qh_next_vertexmerge(qh /* qh.vertex_mergeset */))) { /* only one at a time from qh_getpinchedmerges */
+    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+    if (merge->mergetype == MRGsubridge) {
+      zzinc_(Zpinchedvertex);
+      trace1((qh, qh->ferr, 1050, "qh_merge_pinchedvertices: merge one of %d pinched vertices before adding apex p%d.  Try to resolve duplicate ridges in newfacets\n",
+        qh_setsize(qh, qh->vertex_mergeset)+1, apexpointid));
+      qh_remove_mergetype(qh, qh->vertex_mergeset, MRGsubridge);
+    }else {
+      zzinc_(Zpinchduplicate);
+      if (firstmerge)
+        trace1((qh, qh->ferr, 1056, "qh_merge_pinchedvertices: merge %d pinched vertices from dupridges in merged facets, apex p%d\n",
+           qh_setsize(qh, qh->vertex_mergeset)+1, apexpointid));
+      firstmerge= False;
+    }
+    vertex= merge->vertex1;
+    vertex2= merge->vertex2;
+    dist= merge->distance;
+    qh_memfree(qh, merge, (int)sizeof(mergeT)); /* merge is invalidated */
+    qh_rename_adjacentvertex(qh, vertex, vertex2, dist);
+#ifndef qh_NOtrace
+    if (qh->IStracing >= 2) {
+      FOREACHmergeA_(qh->degen_mergeset) {
+        if (mergeA->mergetype== MRGdegen) {
+          qh_fprintf(qh, qh->ferr, 2072, "qh_merge_pinchedvertices: merge degenerate f%d into an adjacent facet\n", mergeA->facet1->id);
+        }else {
+          qh_fprintf(qh, qh->ferr, 2084, "qh_merge_pinchedvertices: merge f%d into f%d mergeType %d\n", mergeA->facet1->id, mergeA->facet2->id, mergeA->mergetype);
+        }
+      }
+    }
+#endif
+    qh_merge_degenredundant(qh); /* simplicial facets with both old and new vertices */
+  }
+  qh->isRenameVertex= False;
+}/* merge_pinchedvertices */
+
+/*---------------------------------
+
+  qh_merge_twisted(qh, facet1, facet2 )
+    remove twisted ridge between facet1 into facet2 or report error
+
+  returns:
+    merges one of the facets into the best neighbor
+
+  notes:
+    a twisted ridge has opposite vertices that are convex and concave
+
+  design:
+    find best neighbors for both facets
+    error if wide merge
+    merge the nearest facet into its best neighbor
+    update statistics
+*/
+void qh_merge_twisted(qhT *qh, facetT *facet1, facetT *facet2) {
+  facetT *neighbor2, *neighbor, *merging, *merged;
+  vertexT *bestvertex, *bestpinched;
+  realT dist, dist2, mindist, mindist2, maxdist, maxdist2, mintwisted, bestdist;
+
+  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+  trace3((qh, qh->ferr, 3050, "qh_merge_twisted: merge #%d for twisted f%d and f%d\n",
+      zzval_(Ztotmerge) + 1, facet1->id, facet2->id));
+  /* twisted */
+  neighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
+  neighbor2= qh_findbestneighbor(qh, facet2, &dist2, &mindist2, &maxdist2);
+  mintwisted= qh_RATIOtwisted * qh->ONEmerge;
+  maximize_(mintwisted, facet1->maxoutside);
+  maximize_(mintwisted, facet2->maxoutside);
+  if (dist > mintwisted && dist2 > mintwisted) {
+    bestdist= qh_vertex_bestdist2(qh, facet1->vertices, &bestvertex, &bestpinched);
+    if (bestdist > mintwisted) {
+      qh_fprintf(qh, qh->ferr, 6417, "qhull precision error (qh_merge_twisted): twisted facet f%d does not contain pinched vertices.  Too wide to merge into neighbor.  mindist %2.2g maxdist %2.2g vertexdist %2.2g maxpinched %2.2g neighbor f%d mindist %2.2g maxdist %2.2g\n",
+        facet1->id, mindist, maxdist, bestdist, mintwisted, facet2->id, mindist2, maxdist2);
+    }else {
+      qh_fprintf(qh, qh->ferr, 6418, "qhull precision error (qh_merge_twisted): twisted facet f%d with pinched vertices.  Could merge vertices, but too wide to merge into neighbor.   mindist %2.2g maxdist %2.2g vertexdist %2.2g neighbor f%d mindist %2.2g maxdist %2.2g\n",
+        facet1->id, mindist, maxdist, bestdist, facet2->id, mindist2, maxdist2);
+    }
+    qh_errexit2(qh, qh_ERRwide, facet1, facet2);
+  }
+  if (dist < dist2) {
+    merging= facet1;
+    merged= neighbor;
+  }else {
+    /* ignores qh.AVOIDold ('Q4') */
+    merging= facet2;
+    merged= neighbor2;
+    dist= dist2;
+    mindist= mindist2;
+    maxdist= maxdist2;
+  }
+  qh_mergefacet(qh, merging, merged, MRGtwisted, &mindist, &maxdist, !qh_MERGEapex);
+  /* caller merges qh_degenredundant */
+  zinc_(Ztwisted);
+  wadd_(Wtwistedtot, dist);
+  wmax_(Wtwistedmax, dist);
+} /* merge_twisted */
+
+/*---------------------------------
+
+  qh_mergecycle(qh, samecycle, newfacet )
+    merge a cycle of facets starting at samecycle into a newfacet
+    newfacet is a horizon facet with ->normal
+    samecycle facets are simplicial from an apex
+
+  returns:
+    initializes vertex neighbors on first merge
+    samecycle deleted (placed on qh.visible_list)
+    newfacet at end of qh.facet_list
+    deleted vertices on qh.del_vertices
+
+  notes:
+    only called by qh_mergecycle_all for multiple, same cycle facets
+    see qh_mergefacet
+
+  design:
+    make vertex neighbors if necessary
+    make ridges for newfacet
+    merge neighbor sets of samecycle into newfacet
+    merge ridges of samecycle into newfacet
+    merge vertex neighbors of samecycle into newfacet
+    make apex of samecycle the apex of newfacet
+    if newfacet wasn't a new facet
+      add its vertices to qh.newvertex_list
+    delete samecycle facets a make newfacet a newfacet
+*/
+void qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet) {
+  int traceonce= False, tracerestore= 0;
+  vertexT *apex;
+#ifndef qh_NOtrace
+  facetT *same;
+#endif
+
+  zzinc_(Ztotmerge);
+  if (qh->REPORTfreq2 && qh->POSTmerging) {
+    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
+      qh_tracemerging(qh);
+  }
+#ifndef qh_NOtrace
+  if (qh->TRACEmerge == zzval_(Ztotmerge))
+    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+  trace2((qh, qh->ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
+        zzval_(Ztotmerge), samecycle->id, newfacet->id));
+  if (newfacet == qh->tracefacet) {
+    tracerestore= qh->IStracing;
+    qh->IStracing= 4;
+    qh_fprintf(qh, qh->ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
+               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh->furthest_id);
+    traceonce= True;
+  }
+  if (qh->IStracing >=4) {
+    qh_fprintf(qh, qh->ferr, 8069, "  same cycle:");
+    FORALLsame_cycle_(samecycle)
+      qh_fprintf(qh, qh->ferr, 8070, " f%d", same->id);
+    qh_fprintf(qh, qh->ferr, 8071, "\n");
+  }
+  if (qh->IStracing >=4)
+    qh_errprint(qh, "MERGING CYCLE", samecycle, newfacet, NULL, NULL);
+#endif /* !qh_NOtrace */
+  if (newfacet->tricoplanar) {
+    if (!qh->TRInormals) {
+      qh_fprintf(qh, qh->ferr, 6224, "qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
+      qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
+    }
+    newfacet->tricoplanar= False;
+    newfacet->keepcentrum= False;
+  }
+  if (qh->CHECKfrequently)
+    qh_checkdelridge(qh);
+  if (!qh->VERTEXneighbors)
+    qh_vertexneighbors(qh);
+  apex= SETfirstt_(samecycle->vertices, vertexT);
+  qh_makeridges(qh, newfacet);
+  qh_mergecycle_neighbors(qh, samecycle, newfacet);
+  qh_mergecycle_ridges(qh, samecycle, newfacet);
+  qh_mergecycle_vneighbors(qh, samecycle, newfacet);
+  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
+    qh_setaddnth(qh, &newfacet->vertices, 0, apex);  /* apex has last id */
+  if (!newfacet->newfacet)
+    qh_newvertices(qh, newfacet->vertices);
+  qh_mergecycle_facets(qh, samecycle, newfacet);
+  qh_tracemerge(qh, samecycle, newfacet, MRGcoplanarhorizon);
+  /* check for degen_redundant_neighbors after qh_forcedmerges() */
+  if (traceonce) {
+    qh_fprintf(qh, qh->ferr, 8072, "qh_mergecycle: end of trace facet\n");
+    qh->IStracing= tracerestore;
+  }
+} /* mergecycle */
+
+/*---------------------------------
+
+  qh_mergecycle_all(qh, facetlist, wasmerge )
+    merge all samecycles of coplanar facets into horizon
+    don't merge facets with ->mergeridge (these already have ->normal)
+    all facets are simplicial from apex
+    all facet->cycledone == False
+
+  returns:
+    all newfacets merged into coplanar horizon facets
+    deleted vertices on  qh.del_vertices
+    sets wasmerge if any merge
+
+  notes:
+    called by qh_premerge
+    calls qh_mergecycle for multiple, same cycle facets
+
+  design:
+    for each facet on facetlist
+      skip facets with dupridges and normals
+      check that facet is in a samecycle (->mergehorizon)
+      if facet only member of samecycle
+        sets vertex->delridge for all vertices except apex
+        merge facet into horizon
+      else
+        mark all facets in samecycle
+        remove facets with dupridges from samecycle
+        merge samecycle into horizon (deletes facets from facetlist)
+*/
+void qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge) {
+  facetT *facet, *same, *prev, *horizon, *newfacet;
+  facetT *samecycle= NULL, *nextfacet, *nextsame;
+  vertexT *apex, *vertex, **vertexp;
+  int cycles=0, total=0, facets, nummerge, numdegen= 0;
+
+  trace2((qh, qh->ferr, 2031, "qh_mergecycle_all: merge new facets into coplanar horizon facets.  Bulk merge a cycle of facets with the same horizon facet\n"));
+  for (facet=facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
+    if (facet->normal)
+      continue;
+    if (!facet->mergehorizon) {
+      qh_fprintf(qh, qh->ferr, 6225, "qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    horizon= SETfirstt_(facet->neighbors, facetT);
+    if (facet->f.samecycle == facet) {
+      if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
+        qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
+      zinc_(Zonehorizon);
+      /* merge distance done in qh_findhorizon */
+      apex= SETfirstt_(facet->vertices, vertexT);
+      FOREACHvertex_(facet->vertices) {
+        if (vertex != apex)
+          vertex->delridge= True;
+      }
+      horizon->f.newcycle= NULL;
+      qh_mergefacet(qh, facet, horizon, MRGcoplanarhorizon, NULL, NULL, qh_MERGEapex);
+    }else {
+      samecycle= facet;
+      facets= 0;
+      prev= facet;
+      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
+           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
+        nextsame= same->f.samecycle;
+        if (same->cycledone || same->visible)
+          qh_infiniteloop(qh, same);
+        same->cycledone= True;
+        if (same->normal) {
+          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
+          same->f.samecycle= NULL;
+        }else {
+          prev= same;
+          facets++;
+        }
+      }
+      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
+        nextfacet= nextfacet->next;
+      horizon->f.newcycle= NULL;
+      qh_mergecycle(qh, samecycle, horizon);
+      nummerge= horizon->nummerge + facets;
+      if (nummerge > qh_MAXnummerge)
+        horizon->nummerge= qh_MAXnummerge;
+      else
+        horizon->nummerge= (short unsigned int)nummerge; /* limited to 9 bits by qh_MAXnummerge, -Wconversion */
+      zzinc_(Zcyclehorizon);
+      total += facets;
+      zzadd_(Zcyclefacettot, facets);
+      zmax_(Zcyclefacetmax, facets);
+    }
+    cycles++;
+  }
+  if (cycles) {
+    FORALLnew_facets {
+      /* qh_maybe_duplicateridges postponed since qh_mergecycle_ridges deletes ridges without calling qh_delridge_merge */
+      if (newfacet->coplanarhorizon) {
+        qh_test_redundant_neighbors(qh, newfacet);
+        qh_maybe_duplicateridges(qh, newfacet);
+        newfacet->coplanarhorizon= False;
+      }
+    }
+    numdegen += qh_merge_degenredundant(qh);
+    *wasmerge= True;
+    trace1((qh, qh->ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons and %d degenredundant facets\n",
+      cycles, numdegen));
+  }
+} /* mergecycle_all */
+
+/*---------------------------------
+
+  qh_mergecycle_facets(qh, samecycle, newfacet )
+    finish merge of samecycle into newfacet
+
+  returns:
+    samecycle prepended to visible_list for later deletion and partitioning
+      each facet->f.replace == newfacet
+
+    newfacet moved to end of qh.facet_list
+      makes newfacet a newfacet (get's facet1->id if it was old)
+      sets newfacet->newmerge
+      clears newfacet->center (unless merging into a large facet)
+      clears newfacet->tested and ridge->tested for facet1
+
+    adds neighboring facets to facet_mergeset if redundant or degenerate
+
+  design:
+    make newfacet a new facet and set its flags
+    move samecycle facets to qh.visible_list for later deletion
+    unless newfacet is large
+      remove its centrum
+*/
+void qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet) {
+  facetT *same, *next;
+
+  trace4((qh, qh->ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
+  qh_removefacet(qh, newfacet);  /* append as a newfacet to end of qh->facet_list */
+  qh_appendfacet(qh, newfacet);
+  newfacet->newfacet= True;
+  newfacet->simplicial= False;
+  newfacet->newmerge= True;
+
+  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
+    next= same->f.samecycle;  /* reused by willdelete */
+    qh_willdelete(qh, same, newfacet);
+  }
+  if (newfacet->center
+      && qh_setsize(qh, newfacet->vertices) <= qh->hull_dim + qh_MAXnewcentrum) {
+    qh_memfree(qh, newfacet->center, qh->normal_size);
+    newfacet->center= NULL;
+  }
+  trace3((qh, qh->ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
+             samecycle->id, newfacet->id));
+} /* mergecycle_facets */
+
+/*---------------------------------
+
+  qh_mergecycle_neighbors(qh, samecycle, newfacet )
+    add neighbors for samecycle facets to newfacet
+
+  returns:
+    newfacet with updated neighbors and vice-versa
+    newfacet has ridges
+    all neighbors of newfacet marked with qh.visit_id
+    samecycle facets marked with qh.visit_id-1
+    ridges updated for simplicial neighbors of samecycle with a ridge
+
+  notes:
+    assumes newfacet not in samecycle
+    usually, samecycle facets are new, simplicial facets without internal ridges
+      not so if horizon facet is coplanar to two different samecycles
+
+  see:
+    qh_mergeneighbors()
+
+  design:
+    check samecycle
+    delete neighbors from newfacet that are also in samecycle
+    for each neighbor of a facet in samecycle
+      if neighbor is simplicial
+        if first visit
+          move the neighbor relation to newfacet
+          update facet links for its ridges
+        else
+          make ridges for neighbor
+          remove samecycle reference
+      else
+        update neighbor sets
+*/
+void qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
+  facetT *same, *neighbor, **neighborp;
+  int delneighbors= 0, newneighbors= 0;
+  unsigned int samevisitid;
+  ridgeT *ridge, **ridgep;
+
+  samevisitid= ++qh->visit_id;
+  FORALLsame_cycle_(samecycle) {
+    if (same->visitid == samevisitid || same->visible)
+      qh_infiniteloop(qh, samecycle);
+    same->visitid= samevisitid;
+  }
+  newfacet->visitid= ++qh->visit_id;
+  trace4((qh, qh->ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
+  FOREACHneighbor_(newfacet) {
+    if (neighbor->visitid == samevisitid) {
+      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
+      delneighbors++;
+    }else
+      neighbor->visitid= qh->visit_id;
+  }
+  qh_setcompact(qh, newfacet->neighbors);
+
+  trace4((qh, qh->ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
+  FORALLsame_cycle_(samecycle) {
+    FOREACHneighbor_(same) {
+      if (neighbor->visitid == samevisitid)
+        continue;
+      if (neighbor->simplicial) {
+        if (neighbor->visitid != qh->visit_id) {
+          qh_setappend(qh, &newfacet->neighbors, neighbor);
+          qh_setreplace(qh, neighbor->neighbors, same, newfacet);
+          newneighbors++;
+          neighbor->visitid= qh->visit_id;
+          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
+            if (ridge->top == same) {
+              ridge->top= newfacet;
+              break;
+            }else if (ridge->bottom == same) {
+              ridge->bottom= newfacet;
+              break;
+            }
+          }
+        }else {
+          qh_makeridges(qh, neighbor);
+          qh_setdel(neighbor->neighbors, same);
+          /* same can't be horizon facet for neighbor */
+        }
+      }else { /* non-simplicial neighbor */
+        qh_setdel(neighbor->neighbors, same);
+        if (neighbor->visitid != qh->visit_id) {
+          qh_setappend(qh, &neighbor->neighbors, newfacet);
+          qh_setappend(qh, &newfacet->neighbors, neighbor);
+          neighbor->visitid= qh->visit_id;
+          newneighbors++;
+        }
+      }
+    }
+  }
+  trace2((qh, qh->ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
+             delneighbors, newneighbors));
+} /* mergecycle_neighbors */
+
+/*---------------------------------
+
+  qh_mergecycle_ridges(qh, samecycle, newfacet )
+    add ridges/neighbors for facets in samecycle to newfacet
+    all new/old neighbors of newfacet marked with qh.visit_id
+    facets in samecycle marked with qh.visit_id-1
+    newfacet marked with qh.visit_id
+
+  returns:
+    newfacet has merged ridges
+
+  notes:
+    ridge already updated for simplicial neighbors of samecycle with a ridge
+    qh_checkdelridge called by qh_mergecycle
+
+  see:
+    qh_mergeridges()
+    qh_makeridges()
+
+  design:
+    remove ridges between newfacet and samecycle
+    for each facet in samecycle
+      for each ridge in facet
+        update facet pointers in ridge
+        skip ridges processed in qh_mergecycle_neighors
+        free ridges between newfacet and samecycle
+        free ridges between facets of samecycle (on 2nd visit)
+        append remaining ridges to newfacet
+      if simplicial facet
+        for each neighbor of facet
+          if simplicial facet
+          and not samecycle facet or newfacet
+            make ridge between neighbor and newfacet
+*/
+void qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet) {
+  facetT *same, *neighbor= NULL;
+  int numold=0, numnew=0;
+  int neighbor_i, neighbor_n;
+  unsigned int samevisitid;
+  ridgeT *ridge, **ridgep;
+  boolT toporient;
+  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
+
+  trace4((qh, qh->ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
+  samevisitid= qh->visit_id -1;
+  FOREACHridge_(newfacet->ridges) {
+    neighbor= otherfacet_(ridge, newfacet);
+    if (neighbor->visitid == samevisitid)
+      SETref_(ridge)= NULL; /* ridge free'd below */
+  }
+  qh_setcompact(qh, newfacet->ridges);
+
+  trace4((qh, qh->ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
+  FORALLsame_cycle_(samecycle) {
+    FOREACHridge_(same->ridges) {
+      if (ridge->top == same) {
+        ridge->top= newfacet;
+        neighbor= ridge->bottom;
+      }else if (ridge->bottom == same) {
+        ridge->bottom= newfacet;
+        neighbor= ridge->top;
+      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
+        qh_setappend(qh, &newfacet->ridges, ridge);
+        numold++;  /* already set by qh_mergecycle_neighbors */
+        continue;
+      }else {
+        qh_fprintf(qh, qh->ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
+        qh_errexit(qh, qh_ERRqhull, NULL, ridge);
+      }
+      if (neighbor == newfacet) {
+        if (qh->traceridge == ridge)
+          qh->traceridge= NULL;
+        qh_setfree(qh, &(ridge->vertices));
+        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
+        numold++;
+      }else if (neighbor->visitid == samevisitid) {
+        qh_setdel(neighbor->ridges, ridge);
+        if (qh->traceridge == ridge)
+          qh->traceridge= NULL;
+        qh_setfree(qh, &(ridge->vertices));
+        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
+        numold++;
+      }else {
+        qh_setappend(qh, &newfacet->ridges, ridge);
+        numold++;
+      }
+    }
+    if (same->ridges)
+      qh_settruncate(qh, same->ridges, 0);
+    if (!same->simplicial)
+      continue;
+    FOREACHneighbor_i_(qh, same) {       /* note: !newfact->simplicial */
+      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
+        ridge= qh_newridge(qh);
+        ridge->vertices= qh_setnew_delnthsorted(qh, same->vertices, qh->hull_dim,
+                                                          neighbor_i, 0);
+        toporient= (boolT)(same->toporient ^ (neighbor_i & 0x1));
+        if (toporient) {
+          ridge->top= newfacet;
+          ridge->bottom= neighbor;
+          ridge->simplicialbot= True;
+        }else {
+          ridge->top= neighbor;
+          ridge->bottom= newfacet;
+          ridge->simplicialtop= True;
+        }
+        qh_setappend(qh, &(newfacet->ridges), ridge);
+        qh_setappend(qh, &(neighbor->ridges), ridge);
+        if (qh->ridge_id == qh->traceridge_id)
+          qh->traceridge= ridge;
+        numnew++;
+      }
+    }
+  }
+
+  trace2((qh, qh->ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
+             numold, numnew));
+} /* mergecycle_ridges */
+
+/*---------------------------------
+
+  qh_mergecycle_vneighbors(qh, samecycle, newfacet )
+    create vertex neighbors for newfacet from vertices of facets in samecycle
+    samecycle marked with visitid == qh.visit_id - 1
+
+  returns:
+    newfacet vertices with updated neighbors
+    marks newfacet with qh.visit_id-1
+    deletes vertices that are merged away
+    sets delridge on all vertices (faster here than in mergecycle_ridges)
+
+  see:
+    qh_mergevertex_neighbors()
+
+  design:
+    for each vertex of samecycle facet
+      set vertex->delridge
+      delete samecycle facets from vertex neighbors
+      append newfacet to vertex neighbors
+      if vertex only in newfacet
+        delete it from newfacet
+        add it to qh.del_vertices for later deletion
+*/
+void qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
+  facetT *neighbor, **neighborp;
+  unsigned int mergeid;
+  vertexT *vertex, **vertexp, *apex;
+  setT *vertices;
+
+  trace4((qh, qh->ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
+  mergeid= qh->visit_id - 1;
+  newfacet->visitid= mergeid;
+  vertices= qh_basevertices(qh, samecycle); /* temp */
+  apex= SETfirstt_(samecycle->vertices, vertexT);
+  qh_setappend(qh, &vertices, apex);
+  FOREACHvertex_(vertices) {
+    vertex->delridge= True;
+    FOREACHneighbor_(vertex) {
+      if (neighbor->visitid == mergeid)
+        SETref_(neighbor)= NULL;
+    }
+    qh_setcompact(qh, vertex->neighbors);
+    qh_setappend(qh, &vertex->neighbors, newfacet);
+    if (!SETsecond_(vertex->neighbors)) {
+      zinc_(Zcyclevertex);
+      trace2((qh, qh->ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
+        vertex->id, samecycle->id, newfacet->id));
+      qh_setdelsorted(newfacet->vertices, vertex);
+      vertex->deleted= True;
+      qh_setappend(qh, &qh->del_vertices, vertex);
+    }
+  }
+  qh_settempfree(qh, &vertices);
+  trace3((qh, qh->ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
+             samecycle->id, newfacet->id));
+} /* mergecycle_vneighbors */
+
+/*---------------------------------
+
+  qh_mergefacet(qh, facet1, facet2, mergetype, mindist, maxdist, mergeapex )
+    merges facet1 into facet2
+    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon (optimizes qh_mergesimplex)
+
+  returns:
+    qh.max_outside and qh.min_vertex updated
+    initializes vertex neighbors on first merge
+
+  note:
+    mergetype only used for logging and error reporting
+
+  returns:
+    facet2 contains facet1's vertices, neighbors, and ridges
+      facet2 moved to end of qh.facet_list
+      makes facet2 a newfacet
+      sets facet2->newmerge set
+      clears facet2->center (unless merging into a large facet)
+      clears facet2->tested and ridge->tested for facet1
+
+    facet1 prepended to visible_list for later deletion and partitioning
+      facet1->f.replace == facet2
+
+    adds neighboring facets to facet_mergeset if redundant or degenerate
+
+  notes:
+    when done, tests facet1 and facet2 for degenerate or redundant neighbors and dupridges
+    mindist/maxdist may be NULL (only if both NULL)
+    traces merge if fmax_(maxdist,-mindist) > TRACEdist
+
+  see:
+    qh_mergecycle()
+
+  design:
+    trace merge and check for degenerate simplex
+    make ridges for both facets
+    update qh.max_outside, qh.max_vertex, qh.min_vertex
+    update facet2->maxoutside and keepcentrum
+    update facet2->nummerge
+    update tested flags for facet2
+    if facet1 is simplicial
+      merge facet1 into facet2
+    else
+      merge facet1's neighbors into facet2
+      merge facet1's ridges into facet2
+      merge facet1's vertices into facet2
+      merge facet1's vertex neighbors into facet2
+      add facet2's vertices to qh.new_vertexlist
+    move facet2 to end of qh.newfacet_list
+    unless MRGcoplanarhorizon
+      test facet2 for redundant neighbors
+      test facet1 for degenerate neighbors
+      test for redundant facet2
+      maybe test for duplicate ridges ('Q17')
+    move facet1 to qh.visible_list for later deletion
+*/
+void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype, realT *mindist, realT *maxdist, boolT mergeapex) {
+  boolT traceonce= False;
+  vertexT *vertex, **vertexp;
+  realT mintwisted, vertexdist;
+  realT onemerge;
+  int tracerestore=0, nummerge;
+  const char *mergename;
+
+  if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *))
+    mergename= mergetypes[mergetype];
+  else
+    mergename= mergetypes[MRGnone];
+  if (facet1->tricoplanar || facet2->tricoplanar) {
+    if (!qh->TRInormals) {
+      qh_fprintf(qh, qh->ferr, 6226, "qhull internal error (qh_mergefacet): merge f%d into f%d for mergetype %d (%s) does not work for tricoplanar facets.  Use option 'Q11'\n",
+        facet1->id, facet2->id, mergetype, mergename);
+      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+    }
+    if (facet2->tricoplanar) {
+      facet2->tricoplanar= False;
+      facet2->keepcentrum= False;
+    }
+  }
+  zzinc_(Ztotmerge);
+  if (qh->REPORTfreq2 && qh->POSTmerging) {
+    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
+      qh_tracemerging(qh);
+  }
+#ifndef qh_NOtrace
+  if (qh->build_cnt >= qh->RERUN) {
+    if (mindist && (-*mindist > qh->TRACEdist || *maxdist > qh->TRACEdist)) {
+      tracerestore= 0;
+      qh->IStracing= qh->TRACElevel;
+      traceonce= True;
+      qh_fprintf(qh, qh->ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d for mergetype %d (%s), last point was p%d\n",
+          zzval_(Ztotmerge), fmax_(-*mindist, *maxdist), facet1->id, facet2->id, mergetype, mergename, qh->furthest_id);
+    }else if (facet1 == qh->tracefacet || facet2 == qh->tracefacet) {
+      tracerestore= qh->IStracing;
+      qh->IStracing= 4;
+      traceonce= True;
+      qh_fprintf(qh, qh->ferr, 8076, "qh_mergefacet: ========= trace merge #%d for f%d into f%d for mergetype %d (%s), furthest is p%d\n",
+                 zzval_(Ztotmerge), facet1->id, facet2->id, mergetype, mergename, qh->furthest_id);
+    }
+  }
+  if (qh->IStracing >= 2) {
+    realT mergemin= -2;
+    realT mergemax= -2;
+
+    if (mindist) {
+      mergemin= *mindist;
+      mergemax= *maxdist;
+    }
+    qh_fprintf(qh, qh->ferr, 2081, "qh_mergefacet: #%d merge f%d into f%d for merge for mergetype %d (%s), mindist= %2.2g, maxdist= %2.2g, max_outside %2.2g\n",
+    zzval_(Ztotmerge), facet1->id, facet2->id, mergetype, mergename, mergemin, mergemax, qh->max_outside);
+  }
+#endif /* !qh_NOtrace */
+  if(!qh->ALLOWwide && mindist) {
+    mintwisted= qh_WIDEmaxoutside * qh->ONEmerge;  /* same as qh_merge_twisted and qh_check_maxout (poly2) */
+    maximize_(mintwisted, facet1->maxoutside);
+    maximize_(mintwisted, facet2->maxoutside);
+    if (*maxdist > mintwisted || -*mindist > mintwisted) {
+      vertexdist= qh_vertex_bestdist(qh, facet1->vertices);
+      onemerge= qh->ONEmerge + qh->DISTround;
+      if (vertexdist > mintwisted) {
+        qh_fprintf(qh, qh->ferr, 6347, "qhull precision error (qh_mergefacet): wide merge for facet f%d into f%d for mergetype %d (%s).  maxdist %2.2g (%.1fx) mindist %2.2g (%.1fx) vertexdist %2.2g  Allow with 'Q12' (allow-wide)\n",
+          facet1->id, facet2->id, mergetype, mergename, *maxdist, *maxdist/onemerge, *mindist, -*mindist/onemerge, vertexdist);
+      }else {
+        qh_fprintf(qh, qh->ferr, 6348, "qhull precision error (qh_mergefacet): wide merge for pinched facet f%d into f%d for mergetype %d (%s).  maxdist %2.2g (%.fx) mindist %2.2g (%.1fx) vertexdist %2.2g  Allow with 'Q12' (allow-wide)\n",
+          facet1->id, facet2->id, mergetype, mergename, *maxdist, *maxdist/onemerge, *mindist, -*mindist/onemerge, vertexdist);
+      }
+      qh_errexit2(qh, qh_ERRwide, facet1, facet2);
+    }
+  }
+  if (facet1 == facet2 || facet1->visible || facet2->visible) {
+    qh_fprintf(qh, qh->ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet, mergetype %d (%s)\n",
+             facet1->id, facet2->id, mergetype, mergename);
+    qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
+  }
+  if (qh->num_facets - qh->num_visible <= qh->hull_dim + 1) {
+    qh_fprintf(qh, qh->ferr, 6227, "qhull topology error: Only %d facets remain.  The input is too degenerate or the convexity constraints are too strong.\n",
+          qh->hull_dim+1);
+    if (qh->hull_dim >= 5 && !qh->MERGEexact)
+      qh_fprintf(qh, qh->ferr, 8079, "    Option 'Qx' may avoid this problem.\n");
+    qh_errexit(qh, qh_ERRtopology, NULL, NULL);
+  }
+  if (!qh->VERTEXneighbors)
+    qh_vertexneighbors(qh);
+  qh_makeridges(qh, facet1);
+  qh_makeridges(qh, facet2);
+  if (qh->IStracing >=4)
+    qh_errprint(qh, "MERGING", facet1, facet2, NULL, NULL);
+  if (mindist) {
+    maximize_(qh->max_outside, *maxdist);
+    maximize_(qh->max_vertex, *maxdist);
+#if qh_MAXoutside
+    maximize_(facet2->maxoutside, *maxdist);
+#endif
+    minimize_(qh->min_vertex, *mindist);
+    if (!facet2->keepcentrum
+    && (*maxdist > qh->WIDEfacet || *mindist < -qh->WIDEfacet)) {
+      facet2->keepcentrum= True;
+      zinc_(Zwidefacet);
+    }
+  }
+  nummerge= facet1->nummerge + facet2->nummerge + 1;
+  if (nummerge >= qh_MAXnummerge)
+    facet2->nummerge= qh_MAXnummerge;
+  else
+    facet2->nummerge= (short unsigned int)nummerge; /* limited to 9 bits by qh_MAXnummerge, -Wconversion */
+  facet2->newmerge= True;
+  facet2->dupridge= False;
+  qh_updatetested(qh, facet1, facet2);
+  if (qh->hull_dim > 2 && qh_setsize(qh, facet1->vertices) == qh->hull_dim)
+    qh_mergesimplex(qh, facet1, facet2, mergeapex);
+  else {
+    qh->vertex_visit++;
+    FOREACHvertex_(facet2->vertices)
+      vertex->visitid= qh->vertex_visit;
+    if (qh->hull_dim == 2)
+      qh_mergefacet2d(qh, facet1, facet2);
+    else {
+      qh_mergeneighbors(qh, facet1, facet2);
+      qh_mergevertices(qh, facet1->vertices, &facet2->vertices);
+    }
+    qh_mergeridges(qh, facet1, facet2);
+    qh_mergevertex_neighbors(qh, facet1, facet2);
+    if (!facet2->newfacet)
+      qh_newvertices(qh, facet2->vertices);
+  }
+  if (facet2->coplanarhorizon) {
+    zinc_(Zmergeintocoplanar);
+  }else if (!facet2->newfacet) {
+    zinc_(Zmergeintohorizon);
+  }else if (!facet1->newfacet && facet2->newfacet) {
+    zinc_(Zmergehorizon);
+  }else {
+    zinc_(Zmergenew);
+  }
+  qh_removefacet(qh, facet2);  /* append as a newfacet to end of qh->facet_list */
+  qh_appendfacet(qh, facet2);
+  facet2->newfacet= True;
+  facet2->tested= False;
+  qh_tracemerge(qh, facet1, facet2, mergetype);
+  if (traceonce) {
+    qh_fprintf(qh, qh->ferr, 8080, "qh_mergefacet: end of wide tracing\n");
+    qh->IStracing= tracerestore;
+  }
+  if (mergetype != MRGcoplanarhorizon) {
+    trace3((qh, qh->ferr, 3076, "qh_mergefacet: check f%d and f%d for redundant and degenerate neighbors\n",
+        facet1->id, facet2->id));
+    qh_test_redundant_neighbors(qh, facet2);
+    qh_test_degen_neighbors(qh, facet1);  /* after qh_test_redundant_neighbors since MRGdegen more difficult than MRGredundant
+                                             and before qh_willdelete which clears facet1.neighbors */
+    qh_degen_redundant_facet(qh, facet2); /* may occur in qh_merge_pinchedvertices, e.g., rbox 175 C3,2e-13 D4 t1545228104 | qhull d */
+    qh_maybe_duplicateridges(qh, facet2);
+  }
+  qh_willdelete(qh, facet1, facet2);
+} /* mergefacet */
+
+
+/*---------------------------------
+
+  qh_mergefacet2d(qh, facet1, facet2 )
+    in 2d, merges neighbors and vertices of facet1 into facet2
+
+  returns:
+    build ridges for neighbors if necessary
+    facet2 looks like a simplicial facet except for centrum, ridges
+      neighbors are opposite the corresponding vertex
+      maintains orientation of facet2
+
+  notes:
+    qh_mergefacet() retains non-simplicial structures
+      they are not needed in 2d, but later routines may use them
+    preserves qh.vertex_visit for qh_mergevertex_neighbors()
+
+  design:
+    get vertices and neighbors
+    determine new vertices and neighbors
+    set new vertices and neighbors and adjust orientation
+    make ridges for new neighbor if needed
+*/
+void qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2) {
+  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
+  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
+
+  vertex1A= SETfirstt_(facet1->vertices, vertexT);
+  vertex1B= SETsecondt_(facet1->vertices, vertexT);
+  vertex2A= SETfirstt_(facet2->vertices, vertexT);
+  vertex2B= SETsecondt_(facet2->vertices, vertexT);
+  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
+  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
+  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
+  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
+  if (vertex1A == vertex2A) {
+    vertexA= vertex1B;
+    vertexB= vertex2B;
+    neighborA= neighbor2A;
+    neighborB= neighbor1A;
+  }else if (vertex1A == vertex2B) {
+    vertexA= vertex1B;
+    vertexB= vertex2A;
+    neighborA= neighbor2B;
+    neighborB= neighbor1A;
+  }else if (vertex1B == vertex2A) {
+    vertexA= vertex1A;
+    vertexB= vertex2B;
+    neighborA= neighbor2A;
+    neighborB= neighbor1B;
+  }else { /* 1B == 2B */
+    vertexA= vertex1A;
+    vertexB= vertex2A;
+    neighborA= neighbor2B;
+    neighborB= neighbor1B;
+  }
+  /* vertexB always from facet2, neighborB always from facet1 */
+  if (vertexA->id > vertexB->id) {
+    SETfirst_(facet2->vertices)= vertexA;
+    SETsecond_(facet2->vertices)= vertexB;
+    if (vertexB == vertex2A)
+      facet2->toporient= !facet2->toporient;
+    SETfirst_(facet2->neighbors)= neighborA;
+    SETsecond_(facet2->neighbors)= neighborB;
+  }else {
+    SETfirst_(facet2->vertices)= vertexB;
+    SETsecond_(facet2->vertices)= vertexA;
+    if (vertexB == vertex2B)
+      facet2->toporient= !facet2->toporient;
+    SETfirst_(facet2->neighbors)= neighborB;
+    SETsecond_(facet2->neighbors)= neighborA;
+  }
+  /* qh_makeridges not needed since neighborB is not degenerate */
+  qh_setreplace(qh, neighborB->neighbors, facet1, facet2);
+  trace4((qh, qh->ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
+       vertexA->id, neighborB->id, facet1->id, facet2->id));
+} /* mergefacet2d */
+
+
+/*---------------------------------
+
+  qh_mergeneighbors(qh, facet1, facet2 )
+    merges the neighbors of facet1 into facet2
+
+  notes:
+    only called by qh_mergefacet
+    qh.hull_dim >= 3
+    see qh_mergecycle_neighbors
+
+  design:
+    for each neighbor of facet1
+      if neighbor is also a neighbor of facet2
+        if neighbor is simplicial
+          make ridges for later deletion as a degenerate facet
+        update its neighbor set
+      else
+        move the neighbor relation to facet2
+    remove the neighbor relation for facet1 and facet2
+*/
+void qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2) {
+  facetT *neighbor, **neighborp;
+
+  trace4((qh, qh->ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
+          facet1->id, facet2->id));
+  qh->visit_id++;
+  FOREACHneighbor_(facet2) {
+    neighbor->visitid= qh->visit_id;
+  }
+  FOREACHneighbor_(facet1) {
+    if (neighbor->visitid == qh->visit_id) {
+      if (neighbor->simplicial)    /* is degen, needs ridges */
+        qh_makeridges(qh, neighbor);
+      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
+        qh_setdel(neighbor->neighbors, facet1);
+      else {
+        qh_setdel(neighbor->neighbors, facet2);
+        qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
+      }
+    }else if (neighbor != facet2) {
+      qh_setappend(qh, &(facet2->neighbors), neighbor);
+      qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
+    }
+  }
+  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
+  qh_setdel(facet2->neighbors, facet1);
+} /* mergeneighbors */
+
+
+/*---------------------------------
+
+  qh_mergeridges(qh, facet1, facet2 )
+    merges the ridge set of facet1 into facet2
+
+  returns:
+    may delete all ridges for a vertex
+    sets vertex->delridge on deleted ridges
+
+  see:
+    qh_mergecycle_ridges()
+
+  design:
+    delete ridges between facet1 and facet2
+      mark (delridge) vertices on these ridges for later testing
+    for each remaining ridge
+      rename facet1 to facet2
+*/
+void qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2) {
+  ridgeT *ridge, **ridgep;
+
+  trace4((qh, qh->ferr, 4038, "qh_mergeridges: merge ridges of f%d into f%d\n",
+          facet1->id, facet2->id));
+  FOREACHridge_(facet2->ridges) {
+    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
+      /* ridge.nonconvex is irrelevant due to merge */
+      qh_delridge_merge(qh, ridge);  /* expensive in high-d, could rebuild */
+      ridgep--; /* deleted this ridge, repeat with next ridge*/
+    }
+  }
+  FOREACHridge_(facet1->ridges) {
+    if (ridge->top == facet1) {
+      ridge->top= facet2;
+      ridge->simplicialtop= False;
+    }else { /* ridge.bottom is facet1 */
+      ridge->bottom= facet2;
+      ridge->simplicialbot= False;
+    }
+    qh_setappend(qh, &(facet2->ridges), ridge);
+  }
+} /* mergeridges */
+
+
+/*---------------------------------
+
+  qh_mergesimplex(qh, facet1, facet2, mergeapex )
+    merge simplicial facet1 into facet2
+    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
+      vertex id is latest (most recently created)
+    facet1 may be contained in facet2
+    ridges exist for both facets
+
+  returns:
+    facet2 with updated vertices, ridges, neighbors
+    updated neighbors for facet1's vertices
+    facet1 not deleted
+    sets vertex->delridge on deleted ridges
+
+  notes:
+    special case code since this is the most common merge
+    called from qh_mergefacet()
+
+  design:
+    if qh_MERGEapex
+      add vertices of facet2 to qh.new_vertexlist if necessary
+      add apex to facet2
+    else
+      for each ridge between facet1 and facet2
+        set vertex->delridge
+      determine the apex for facet1 (i.e., vertex to be merged)
+      unless apex already in facet2
+        insert apex into vertices for facet2
+      add vertices of facet2 to qh.new_vertexlist if necessary
+      add apex to qh.new_vertexlist if necessary
+      for each vertex of facet1
+        if apex
+          rename facet1 to facet2 in its vertex neighbors
+        else
+          delete facet1 from vertex neighbors
+          if only in facet2
+            add vertex to qh.del_vertices for later deletion
+      for each ridge of facet1
+        delete ridges between facet1 and facet2
+        append other ridges to facet2 after renaming facet to facet2
+*/
+void qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex) {
+  vertexT *vertex, **vertexp, *opposite;
+  ridgeT *ridge, **ridgep;
+  boolT isnew= False;
+  facetT *neighbor, **neighborp, *otherfacet;
+
+  if (mergeapex) {
+    opposite= SETfirstt_(facet1->vertices, vertexT); /* apex is opposite facet2.  It has the last vertex id */
+    trace4((qh, qh->ferr, 4086, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
+      opposite->id, facet1->id, facet2->id));
+    if (!facet2->newfacet)
+      qh_newvertices(qh, facet2->vertices);  /* apex, the first vertex, is already new */
+    if (SETfirstt_(facet2->vertices, vertexT) != opposite) {
+      qh_setaddnth(qh, &facet2->vertices, 0, opposite);
+      isnew= True;
+    }
+  }else {
+    zinc_(Zmergesimplex);
+    FOREACHvertex_(facet1->vertices)
+      vertex->seen= False;
+    FOREACHridge_(facet1->ridges) {
+      if (otherfacet_(ridge, facet1) == facet2) {
+        FOREACHvertex_(ridge->vertices) {
+          vertex->seen= True;
+          vertex->delridge= True;
+        }
+        break;
+      }
+    }
+    FOREACHvertex_(facet1->vertices) {
+      if (!vertex->seen)
+        break;  /* must occur */
+    }
+    opposite= vertex;
+    trace4((qh, qh->ferr, 4039, "qh_mergesimplex: merge opposite v%d of f%d into facet f%d\n",
+          opposite->id, facet1->id, facet2->id));
+    isnew= qh_addfacetvertex(qh, facet2, opposite);
+    if (!facet2->newfacet)
+      qh_newvertices(qh, facet2->vertices);
+    else if (!opposite->newfacet) {
+      qh_removevertex(qh, opposite);
+      qh_appendvertex(qh, opposite);
+    }
+  }
+  trace4((qh, qh->ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
+          facet1->id));
+  FOREACHvertex_(facet1->vertices) {
+    if (vertex == opposite && isnew)
+      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
+    else {
+      qh_setdel(vertex->neighbors, facet1);
+      if (!SETsecond_(vertex->neighbors))
+        qh_mergevertex_del(qh, vertex, facet1, facet2);
+    }
+  }
+  trace4((qh, qh->ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
+          facet1->id, facet2->id));
+  qh->visit_id++;
+  FOREACHneighbor_(facet2)
+    neighbor->visitid= qh->visit_id;
+  FOREACHridge_(facet1->ridges) {
+    otherfacet= otherfacet_(ridge, facet1);
+    if (otherfacet == facet2) {
+      /* ridge.nonconvex is irrelevant due to merge */
+      qh_delridge_merge(qh, ridge);  /* expensive in high-d, could rebuild */
+      ridgep--; /* deleted this ridge, repeat with next ridge*/
+      qh_setdel(facet2->neighbors, facet1); /* a simplicial facet may have duplicate neighbors, need to delete each one */
+    }else if (otherfacet->dupridge && !qh_setin(otherfacet->neighbors, facet1)) {
+      qh_fprintf(qh, qh->ferr, 6356, "qhull topology error (qh_mergesimplex): f%d is a dupridge of f%d, cannot merge f%d into f%d\n",
+        facet1->id, otherfacet->id, facet1->id, facet2->id);
+      qh_errexit2(qh, qh_ERRqhull, facet1, otherfacet);
+    }else {
+      trace4((qh, qh->ferr, 4059, "qh_mergesimplex: move r%d with f%d to f%d, new neighbor? %d, maybe horizon? %d\n",
+        ridge->id, otherfacet->id, facet2->id, (otherfacet->visitid != qh->visit_id), (SETfirstt_(otherfacet->neighbors, facetT) == facet1)));
+      qh_setappend(qh, &facet2->ridges, ridge);
+      if (otherfacet->visitid != qh->visit_id) {
+        qh_setappend(qh, &facet2->neighbors, otherfacet);
+        qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
+        otherfacet->visitid= qh->visit_id;
+      }else {
+        if (otherfacet->simplicial)    /* is degen, needs ridges */
+          qh_makeridges(qh, otherfacet);
+        if (SETfirstt_(otherfacet->neighbors, facetT) == facet1) {
+          /* keep new, otherfacet->neighbors->horizon */
+          qh_setdel(otherfacet->neighbors, facet2);
+          qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
+        }else {
+          /* facet2 is already a neighbor of otherfacet, by f.visitid */
+          qh_setdel(otherfacet->neighbors, facet1);
+        }
+      }
+      if (ridge->top == facet1) { /* wait until after qh_makeridges */
+        ridge->top= facet2;
+        ridge->simplicialtop= False;
+      }else {
+        ridge->bottom= facet2;
+        ridge->simplicialbot= False;
+      }
+    }
+  }
+  trace3((qh, qh->ferr, 3006, "qh_mergesimplex: merged simplex f%d v%d into facet f%d\n",
+          facet1->id, opposite->id, facet2->id));
+} /* mergesimplex */
+
+/*---------------------------------
+
+  qh_mergevertex_del(qh, vertex, facet1, facet2 )
+    delete a vertex because of merging facet1 into facet2
+
+  returns:
+    deletes vertex from facet2
+    adds vertex to qh.del_vertices for later deletion
+*/
+void qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2) {
+
+  zinc_(Zmergevertex);
+  trace2((qh, qh->ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
+          vertex->id, facet1->id, facet2->id));
+  qh_setdelsorted(facet2->vertices, vertex);
+  vertex->deleted= True;
+  qh_setappend(qh, &qh->del_vertices, vertex);
+} /* mergevertex_del */
+
+/*---------------------------------
+
+  qh_mergevertex_neighbors(qh, facet1, facet2 )
+    merge the vertex neighbors of facet1 to facet2
+
+  returns:
+    if vertex is current qh.vertex_visit
+      deletes facet1 from vertex->neighbors
+    else
+      renames facet1 to facet2 in vertex->neighbors
+    deletes vertices if only one neighbor
+
+  notes:
+    assumes vertex neighbor sets are good
+*/
+void qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2) {
+  vertexT *vertex, **vertexp;
+
+  trace4((qh, qh->ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighborset for f%d into f%d\n",
+          facet1->id, facet2->id));
+  if (qh->tracevertex) {
+    qh_fprintf(qh, qh->ferr, 8081, "qh_mergevertex_neighbors: of f%d into f%d at furthest p%d f0= %p\n",
+             facet1->id, facet2->id, qh->furthest_id, (void *) qh->tracevertex->neighbors->e[0].p);
+    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
+  }
+  FOREACHvertex_(facet1->vertices) {
+    if (vertex->visitid != qh->vertex_visit)
+      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
+    else {
+      qh_setdel(vertex->neighbors, facet1);
+      if (!SETsecond_(vertex->neighbors))
+        qh_mergevertex_del(qh, vertex, facet1, facet2);
+    }
+  }
+  if (qh->tracevertex)
+    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
+} /* mergevertex_neighbors */
+
+
+/*---------------------------------
+
+  qh_mergevertices(qh, vertices1, vertices2 )
+    merges the vertex set of facet1 into facet2
+
+  returns:
+    replaces vertices2 with merged set
+    preserves vertex_visit for qh_mergevertex_neighbors
+    updates qh.newvertex_list
+
+  design:
+    create a merged set of both vertices (in inverse id order)
+*/
+void qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices2) {
+  int newsize= qh_setsize(qh, vertices1)+qh_setsize(qh, *vertices2) - qh->hull_dim + 1;
+  setT *mergedvertices;
+  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
+
+  mergedvertices= qh_settemp(qh, newsize);
+  FOREACHvertex_(vertices1) {
+    if (!*vertex2 || vertex->id > (*vertex2)->id)
+      qh_setappend(qh, &mergedvertices, vertex);
+    else {
+      while (*vertex2 && (*vertex2)->id > vertex->id)
+        qh_setappend(qh, &mergedvertices, *vertex2++);
+      if (!*vertex2 || (*vertex2)->id < vertex->id)
+        qh_setappend(qh, &mergedvertices, vertex);
+      else
+        qh_setappend(qh, &mergedvertices, *vertex2++);
+    }
+  }
+  while (*vertex2)
+    qh_setappend(qh, &mergedvertices, *vertex2++);
+  if (newsize < qh_setsize(qh, mergedvertices)) {
+    qh_fprintf(qh, qh->ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh_setfree(qh, vertices2);
+  *vertices2= mergedvertices;
+  qh_settemppop(qh);
+} /* mergevertices */
+
+
+/*---------------------------------
+
+  qh_neighbor_intersections(qh, vertex )
+    return intersection of all vertices in vertex->neighbors except for vertex
+
+  returns:
+    returns temporary set of vertices
+    does not include vertex
+    NULL if a neighbor is simplicial
+    NULL if empty set
+
+  notes:
+    only called by qh_redundant_vertex for qh_reducevertices
+      so f.vertices does not contain extraneous vertices that are not in f.ridges
+    used for renaming vertices
+
+  design:
+    initialize the intersection set with vertices of the first two neighbors
+    delete vertex from the intersection
+    for each remaining neighbor
+      intersect its vertex set with the intersection set
+      return NULL if empty
+    return the intersection set
+*/
+setT *qh_neighbor_intersections(qhT *qh, vertexT *vertex) {
+  facetT *neighbor, **neighborp, *neighborA, *neighborB;
+  setT *intersect;
+  int neighbor_i, neighbor_n;
+
+  FOREACHneighbor_(vertex) {
+    if (neighbor->simplicial)
+      return NULL;
+  }
+  neighborA= SETfirstt_(vertex->neighbors, facetT);
+  neighborB= SETsecondt_(vertex->neighbors, facetT);
+  zinc_(Zintersectnum);
+  if (!neighborA)
+    return NULL;
+  if (!neighborB)
+    intersect= qh_setcopy(qh, neighborA->vertices, 0);
+  else
+    intersect= qh_vertexintersect_new(qh, neighborA->vertices, neighborB->vertices);
+  qh_settemppush(qh, intersect);
+  qh_setdelsorted(intersect, vertex);
+  FOREACHneighbor_i_(qh, vertex) {
+    if (neighbor_i >= 2) {
+      zinc_(Zintersectnum);
+      qh_vertexintersect(qh, &intersect, neighbor->vertices);
+      if (!SETfirst_(intersect)) {
+        zinc_(Zintersectfail);
+        qh_settempfree(qh, &intersect);
+        return NULL;
+      }
+    }
+  }
+  trace3((qh, qh->ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
+          qh_setsize(qh, intersect), vertex->id));
+  return intersect;
+} /* neighbor_intersections */
+
+/*---------------------------------
+
+  qh_neighbor_vertices(qh, vertex )
+    return neighboring vertices for a vertex (not in subridge)
+    assumes vertices have full vertex->neighbors
+
+  returns:
+    temporary set of vertices
+
+  notes:
+    updates qh.visit_id and qh.vertex_visit
+    similar to qh_vertexridges
+
+*/
+setT *qh_neighbor_vertices(qhT *qh, vertexT *vertexA, setT *subridge) {
+  facetT *neighbor, **neighborp;
+  vertexT *vertex, **vertexp;
+  setT *vertices= qh_settemp(qh, qh->TEMPsize);
+
+  qh->visit_id++;
+  FOREACHneighbor_(vertexA)
+    neighbor->visitid= qh->visit_id;
+  qh->vertex_visit++;
+  vertexA->visitid= qh->vertex_visit;
+  FOREACHvertex_(subridge) {
+    vertex->visitid= qh->vertex_visit;
+  }
+  FOREACHneighbor_(vertexA) {
+    if (*neighborp)   /* no new ridges in last neighbor */
+      qh_neighbor_vertices_facet(qh, vertexA, neighbor, &vertices);
+  }
+  trace3((qh, qh->ferr, 3035, "qh_neighbor_vertices: %d non-subridge, vertex neighbors for v%d\n",
+    qh_setsize(qh, vertices), vertexA->id));
+  return vertices;
+} /* neighbor_vertices */
+
+/*---------------------------------
+
+  qh_neighbor_vertices_facet(qh, vertex, facet, vertices )
+    add neighboring vertices on ridges for vertex in facet
+    neighbor->visitid==qh.visit_id if it hasn't been visited
+    v.visitid==qh.vertex_visit if it is already in vertices
+
+  returns:
+    vertices updated
+    sets facet->visitid to qh.visit_id-1
+
+  notes:
+    only called by qh_neighbor_vertices
+    similar to qh_vertexridges_facet
+
+  design:
+    for each ridge of facet
+      if ridge of visited neighbor (i.e., unprocessed)
+        if vertex in ridge
+          append unprocessed vertices of ridge
+    mark facet processed
+*/
+void qh_neighbor_vertices_facet(qhT *qh, vertexT *vertexA, facetT *facet, setT **vertices) {
+  ridgeT *ridge, **ridgep;
+  facetT *neighbor;
+  vertexT *second, *last, *vertex, **vertexp;
+  int last_i= qh->hull_dim-2, count= 0;
+  boolT isridge;
+
+  if (facet->simplicial) {
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->visitid != qh->vertex_visit) {
+        vertex->visitid= qh->vertex_visit;
+        qh_setappend(qh, vertices, vertex);
+        count++;
+      }
+    }
+  }else {
+    FOREACHridge_(facet->ridges) {
+      neighbor= otherfacet_(ridge, facet);
+      if (neighbor->visitid == qh->visit_id) {
+        isridge= False;
+        if (SETfirst_(ridge->vertices) == vertexA) {
+          isridge= True;
+        }else if (last_i > 2) {
+          second= SETsecondt_(ridge->vertices, vertexT);
+          last= SETelemt_(ridge->vertices, last_i, vertexT);
+          if (second->id >= vertexA->id && last->id <= vertexA->id) { /* vertices inverse sorted by id */
+            if (second == vertexA || last == vertexA)
+              isridge= True;
+            else if (qh_setin(ridge->vertices, vertexA))
+              isridge= True;
+          }
+        }else if (SETelem_(ridge->vertices, last_i) == vertexA) {
+          isridge= True;
+        }else if (last_i > 1 && SETsecond_(ridge->vertices) == vertexA) {
+          isridge= True;
+        }
+        if (isridge) {
+          FOREACHvertex_(ridge->vertices) {
+            if (vertex->visitid != qh->vertex_visit) {
+              vertex->visitid= qh->vertex_visit;
+              qh_setappend(qh, vertices, vertex);
+              count++;
+            }
+          }
+        }
+      }
+    }
+  }
+  facet->visitid= qh->visit_id-1;
+  if (count) {
+    trace4((qh, qh->ferr, 4079, "qh_neighbor_vertices_facet: found %d vertex neighbors for v%d in f%d (simplicial? %d)\n",
+      count, vertexA->id, facet->id, facet->simplicial));
+  }
+} /* neighbor_vertices_facet */
+
+
+/*---------------------------------
+
+  qh_newvertices(qh, vertices )
+    add vertices to end of qh.vertex_list (marks as new vertices)
+
+  returns:
+    vertices on qh.newvertex_list
+    vertex->newfacet set
+*/
+void qh_newvertices(qhT *qh, setT *vertices) {
+  vertexT *vertex, **vertexp;
+
+  FOREACHvertex_(vertices) {
+    if (!vertex->newfacet) {
+      qh_removevertex(qh, vertex);
+      qh_appendvertex(qh, vertex);
+    }
+  }
+} /* newvertices */
+
+/*---------------------------------
+
+  qh_next_vertexmerge(qh )
+    return next vertex merge from qh.vertex_mergeset
+
+  returns:
+    vertex merge either MRGvertices or MRGsubridge
+    drops merges of deleted vertices
+
+  notes:
+    called from qh_merge_pinchedvertices
+*/
+mergeT *qh_next_vertexmerge(qhT *qh /* qh.vertex_mergeset */) {
+  mergeT *merge;
+  int merge_i, merge_n, best_i= -1;
+  realT bestdist= REALmax;
+
+  FOREACHmerge_i_(qh, qh->vertex_mergeset) {
+    if (!merge->vertex1 || !merge->vertex2) {
+      qh_fprintf(qh, qh->ferr, 6299, "qhull internal error (qh_next_vertexmerge): expecting two vertices for vertex merge.  Got v%d v%d and optional f%d\n",
+        getid_(merge->vertex1), getid_(merge->vertex2), getid_(merge->facet1));
+      qh_errexit(qh, qh_ERRqhull, merge->facet1, NULL);
+    }
+    if (merge->vertex1->deleted || merge->vertex2->deleted) {
+      trace3((qh, qh->ferr, 3030, "qh_next_vertexmerge: drop merge of v%d (del? %d) into v%d (del? %d) due to deleted vertex of r%d and r%d\n",
+        merge->vertex1->id, merge->vertex1->deleted, merge->vertex2->id, merge->vertex2->deleted, getid_(merge->ridge1), getid_(merge->ridge2)));
+      qh_drop_mergevertex(qh, merge);
+      qh_setdelnth(qh, qh->vertex_mergeset, merge_i);
+      merge_i--; merge_n--;
+      qh_memfree(qh, merge, (int)sizeof(mergeT));
+    }else if (merge->distance < bestdist) {
+      bestdist= merge->distance;
+      best_i= merge_i;
+    }
+  }
+  merge= NULL;
+  if (best_i >= 0) {
+    merge= SETelemt_(qh->vertex_mergeset, best_i, mergeT);
+    if (bestdist/qh->ONEmerge > qh_WIDEpinched) {
+      if (merge->mergetype==MRGvertices) {
+        if (merge->ridge1->top == merge->ridge2->bottom && merge->ridge1->bottom == merge->ridge2->top)
+          qh_fprintf(qh, qh->ferr, 6391, "qhull topology error (qh_next_vertexmerge): no nearly adjacent vertices to resolve opposite oriented ridges r%d and r%d in f%d and f%d.  Nearest v%d and v%d dist %2.2g (%.1fx)\n",
+            merge->ridge1->id, merge->ridge2->id, merge->ridge1->top->id, merge->ridge1->bottom->id, merge->vertex1->id, merge->vertex2->id, bestdist, bestdist/qh->ONEmerge);
+        else
+          qh_fprintf(qh, qh->ferr, 6381, "qhull topology error (qh_next_vertexmerge): no nearly adjacent vertices to resolve duplicate ridges r%d and r%d.  Nearest v%d and v%d dist %2.2g (%.1fx)\n",
+            merge->ridge1->id, merge->ridge2->id, merge->vertex1->id, merge->vertex2->id, bestdist, bestdist/qh->ONEmerge);
+      }else {
+        qh_fprintf(qh, qh->ferr, 6208, "qhull topology error (qh_next_vertexmerge): no nearly adjacent vertices to resolve dupridge.  Nearest v%d and v%d dist %2.2g (%.1fx)\n",
+          merge->vertex1->id, merge->vertex2->id, bestdist, bestdist/qh->ONEmerge);
+      }
+      /* it may be possible to find a different vertex, after other vertex merges have occurred */
+      qh_errexit(qh, qh_ERRtopology, NULL, merge->ridge1);
+    }
+    qh_setdelnth(qh, qh->vertex_mergeset, best_i);
+  }
+  return merge;
+} /* next_vertexmerge */
+
+/*---------------------------------
+
+  qh_opposite_horizonfacet(qh, merge, opposite )
+    return horizon facet for one of the merge facets, and its opposite vertex across the ridge
+    assumes either facet1 or facet2 of merge is 'mergehorizon'
+    assumes both facets are simplicial facets on qh.new_facetlist
+
+  returns:
+    horizon facet and opposite vertex
+
+  notes:
+    called by qh_getpinchedmerges
+*/
+facetT *qh_opposite_horizonfacet(qhT *qh, mergeT *merge, vertexT **opposite) {
+  facetT *facet, *horizon, *otherfacet;
+  int neighbor_i;
+
+  if (!merge->facet1->simplicial || !merge->facet2->simplicial || (!merge->facet1->mergehorizon && !merge->facet2->mergehorizon)) {
+    qh_fprintf(qh, qh->ferr, 6273, "qhull internal error (qh_opposite_horizonfacet): expecting merge of simplicial facets, at least one of which is mergehorizon.  Either simplicial or mergehorizon is wrong\n");
+    qh_errexit2(qh, qh_ERRqhull, merge->facet1, merge->facet2);
+  }
+  if (merge->facet1->mergehorizon) {
+    facet= merge->facet1;
+    otherfacet= merge->facet2;
+  }else {
+    facet= merge->facet2;
+    otherfacet= merge->facet1;
+  }
+  horizon= SETfirstt_(facet->neighbors, facetT);
+  neighbor_i= qh_setindex(otherfacet->neighbors, facet);
+  if (neighbor_i==-1)
+    neighbor_i= qh_setindex(otherfacet->neighbors, qh_MERGEridge);
+  if (neighbor_i==-1) {
+    qh_fprintf(qh, qh->ferr, 6238, "qhull internal error (qh_opposite_horizonfacet): merge facet f%d not connected to mergehorizon f%d\n",
+      otherfacet->id, facet->id);
+    qh_errexit2(qh, qh_ERRqhull, otherfacet, facet);
+  }
+  *opposite= SETelemt_(otherfacet->vertices, neighbor_i, vertexT);
+  return horizon;
+} /* opposite_horizonfacet */
+
+
+/*---------------------------------
+
+  qh_reducevertices(qh)
+    reduce extra vertices, shared vertices, and redundant vertices
+    facet->newmerge is set if merged since last call
+    vertex->delridge is set if vertex was on a deleted ridge
+    if !qh.MERGEvertices, only removes extra vertices
+
+  returns:
+    True if also merged degen_redundant facets
+    vertices are renamed if possible
+    clears facet->newmerge and vertex->delridge
+
+  notes:
+    called by qh_all_merges and qh_postmerge
+    ignored if 2-d
+
+  design:
+    merge any degenerate or redundant facets
+    repeat until no more degenerate or redundant facets
+      for each newly merged facet
+        remove extra vertices
+      if qh.MERGEvertices
+        for each newly merged facet
+          for each vertex
+            if vertex was on a deleted ridge
+              rename vertex if it is shared
+        for each new, undeleted vertex
+          remove delridge flag
+          if vertex is redundant
+            merge degenerate or redundant facets
+*/
+boolT qh_reducevertices(qhT *qh) {
+  int numshare=0, numrename= 0;
+  boolT degenredun= False;
+  facetT *newfacet;
+  vertexT *vertex, **vertexp;
+
+  if (qh->hull_dim == 2)
+    return False;
+  trace2((qh, qh->ferr, 2101, "qh_reducevertices: reduce extra vertices, shared vertices, and redundant vertices\n"));
+  if (qh_merge_degenredundant(qh))
+    degenredun= True;
+LABELrestart:
+  FORALLnew_facets {
+    if (newfacet->newmerge) {
+      if (!qh->MERGEvertices)
+        newfacet->newmerge= False;
+      if (qh_remove_extravertices(qh, newfacet)) {
+        qh_degen_redundant_facet(qh, newfacet);
+        if (qh_merge_degenredundant(qh)) {
+          degenredun= True;
+          goto LABELrestart;
+        }
+      }
+    }
+  }
+  if (!qh->MERGEvertices)
+    return False;
+  FORALLnew_facets {
+    if (newfacet->newmerge) {
+      newfacet->newmerge= False;
+      FOREACHvertex_(newfacet->vertices) {
+        if (vertex->delridge) {
+          if (qh_rename_sharedvertex(qh, vertex, newfacet)) {
+            numshare++;
+            if (qh_merge_degenredundant(qh)) {
+              degenredun= True;
+              goto LABELrestart;
+            }
+            vertexp--; /* repeat since deleted vertex */
+          }
+        }
+      }
+    }
+  }
+  FORALLvertex_(qh->newvertex_list) {
+    if (vertex->delridge && !vertex->deleted) {
+      vertex->delridge= False;
+      if (qh->hull_dim >= 4 && qh_redundant_vertex(qh, vertex)) {
+        numrename++;
+        if (qh_merge_degenredundant(qh)) {
+          degenredun= True;
+          goto LABELrestart;
+        }
+      }
+    }
+  }
+  trace1((qh, qh->ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
+          numshare, numrename, degenredun));
+  return degenredun;
+} /* reducevertices */
+
+/*---------------------------------
+
+  qh_redundant_vertex(qh, vertex )
+    rename a redundant vertex if qh_find_newvertex succeeds
+    assumes vertices have full vertex->neighbors
+
+  returns:
+    if find a replacement vertex
+      returns new vertex
+      qh_renamevertex sets vertex->deleted for redundant vertex
+
+  notes:
+    only called by qh_reducevertices for vertex->delridge and hull_dim >= 4
+    may add degenerate facets to qh.facet_mergeset
+    doesn't change vertex->neighbors or create redundant facets
+
+  design:
+    intersect vertices of all facet neighbors of vertex
+    determine ridges for these vertices
+    if find a new vertex for vertex among these ridges and vertices
+      rename vertex to the new vertex
+*/
+vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex) {
+  vertexT *newvertex= NULL;
+  setT *vertices, *ridges;
+
+  trace3((qh, qh->ferr, 3008, "qh_redundant_vertex: check if v%d from a deleted ridge can be renamed\n", vertex->id));
+  if ((vertices= qh_neighbor_intersections(qh, vertex))) {
+    ridges= qh_vertexridges(qh, vertex, !qh_ALL);
+    if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges))) {
+      zinc_(Zrenameall);
+      qh_renamevertex(qh, vertex, newvertex, ridges, NULL, NULL); /* ridges invalidated */
+    }
+    qh_settempfree(qh, &ridges);
+    qh_settempfree(qh, &vertices);
+  }
+  return newvertex;
+} /* redundant_vertex */
+
+/*---------------------------------
+
+  qh_remove_extravertices(qh, facet )
+    remove extra vertices from non-simplicial facets
+
+  returns:
+    returns True if it finds them
+      deletes facet from vertex neighbors
+      facet may be redundant (test with qh_degen_redundant)
+
+  notes:
+    called by qh_renamevertex and qh_reducevertices
+    a merge (qh_reducevertices) or qh_renamevertex may drop all ridges for a vertex in a facet
+
+  design:
+    for each vertex in facet
+      if vertex not in a ridge (i.e., no longer used)
+        delete vertex from facet
+        delete facet from vertex's neighbors
+        unless vertex in another facet
+          add vertex to qh.del_vertices for later deletion
+*/
+boolT qh_remove_extravertices(qhT *qh, facetT *facet) {
+  ridgeT *ridge, **ridgep;
+  vertexT *vertex, **vertexp;
+  boolT foundrem= False;
+
+  if (facet->simplicial) {
+    return False;
+  }
+  trace4((qh, qh->ferr, 4043, "qh_remove_extravertices: test non-simplicial f%d for extra vertices\n",
+          facet->id));
+  FOREACHvertex_(facet->vertices)
+    vertex->seen= False;
+  FOREACHridge_(facet->ridges) {
+    FOREACHvertex_(ridge->vertices)
+      vertex->seen= True;
+  }
+  FOREACHvertex_(facet->vertices) {
+    if (!vertex->seen) {
+      foundrem= True;
+      zinc_(Zremvertex);
+      qh_setdelsorted(facet->vertices, vertex);
+      qh_setdel(vertex->neighbors, facet);
+      if (!qh_setsize(qh, vertex->neighbors)) {
+        vertex->deleted= True;
+        qh_setappend(qh, &qh->del_vertices, vertex);
+        zinc_(Zremvertexdel);
+        trace2((qh, qh->ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
+      }else
+        trace3((qh, qh->ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
+      vertexp--; /*repeat*/
+    }
+  }
+  return foundrem;
+} /* remove_extravertices */
+
+/*---------------------------------
+
+  qh_remove_mergetype(qh, mergeset, mergetype )
+    Remove mergetype merges from mergeset
+
+  notes:
+    Does not preserve order
+*/
+void qh_remove_mergetype(qhT *qh, setT *mergeset, mergeType type) {
+  mergeT *merge;
+  int merge_i, merge_n;
+
+  FOREACHmerge_i_(qh, mergeset) {
+    if (merge->mergetype == type) {
+        trace3((qh, qh->ferr, 3037, "qh_remove_mergetype: remove merge f%d f%d v%d v%d r%d r%d dist %2.2g type %d",
+            getid_(merge->facet1), getid_(merge->facet2), getid_(merge->vertex1), getid_(merge->vertex2), getid_(merge->ridge1), getid_(merge->ridge2), merge->distance, type));
+        qh_setdelnth(qh, mergeset, merge_i);
+        merge_i--; merge_n--;  /* repeat with next merge */
+    }
+  }
+} /* remove_mergetype */
+
+/*---------------------------------
+
+  qh_rename_adjacentvertex(qh, oldvertex, newvertex )
+    renames oldvertex as newvertex.  Must be adjacent (i.e., in the same subridge)
+    no-op if either vertex is deleted
+
+  notes:
+    called from qh_merge_pinchedvertices
+
+  design:
+    for all neighbors of oldvertex
+      if simplicial, rename oldvertex to newvertex and drop if degenerate
+      if needed, add oldvertex neighbor to newvertex
+    determine ridges for oldvertex
+    rename oldvertex as newvertex in ridges (qh_renamevertex)
+*/
+void qh_rename_adjacentvertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, realT dist) {
+  setT *ridges;
+  facetT *neighbor, **neighborp, *maxfacet= NULL;
+  ridgeT *ridge, **ridgep;
+  boolT istrace= False;
+  int oldsize= qh_setsize(qh, oldvertex->neighbors);
+  int newsize= qh_setsize(qh, newvertex->neighbors);
+  coordT maxdist2= -REALmax, dist2;
+
+  if (qh->IStracing >= 4 || oldvertex->id == qh->tracevertex_id || newvertex->id == qh->tracevertex_id) {
+    istrace= True;
+  }
+  zzinc_(Ztotmerge);
+  if (istrace) {
+    qh_fprintf(qh, qh->ferr, 2071, "qh_rename_adjacentvertex: merge #%d rename v%d (%d neighbors) to v%d (%d neighbors) dist %2.2g\n",
+      zzval_(Ztotmerge), oldvertex->id, oldsize, newvertex->id, newsize, dist);
+  }
+  if (oldvertex->deleted || newvertex->deleted) {
+    if (istrace || qh->IStracing >= 2) {
+      qh_fprintf(qh, qh->ferr, 2072, "qh_rename_adjacentvertex: ignore rename.  Either v%d (%d) or v%d (%d) is deleted\n",
+        oldvertex->id, oldvertex->deleted, newvertex->id, newvertex->deleted);
+    }
+    return;
+  }
+  if (oldsize == 0 || newsize == 0) {
+    qh_fprintf(qh, qh->ferr, 2072, "qhull internal error (qh_rename_adjacentvertex): expecting neighbor facets for v%d and v%d.  Got %d and %d neighbors resp.\n",
+      oldvertex->id, newvertex->id, oldsize, newsize);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  FOREACHneighbor_(oldvertex) {
+    if (neighbor->simplicial) {
+      if (qh_setin(neighbor->vertices, newvertex)) {
+        if (istrace || qh->IStracing >= 2) {
+          qh_fprintf(qh, qh->ferr, 2070, "qh_rename_adjacentvertex: simplicial f%d contains old v%d and new v%d.  Will be marked degenerate by qh_renamevertex\n",
+            neighbor->id, oldvertex->id, newvertex->id);
+        }
+        qh_makeridges(qh, neighbor); /* no longer simplicial, nummerge==0, skipped by qh_maybe_duplicateridge */
+      }else {
+        qh_replacefacetvertex(qh, neighbor, oldvertex, newvertex);
+        qh_setunique(qh, &newvertex->neighbors, neighbor);
+        qh_newvertices(qh, neighbor->vertices);  /* for qh_update_vertexneighbors of vertex neighbors */
+      }
+    }
+  }
+  ridges= qh_vertexridges(qh, oldvertex, qh_ALL);
+  if (istrace) {
+    FOREACHridge_(ridges) {
+      qh_printridge(qh, qh->ferr, ridge);
+    }
+  }
+  FOREACHneighbor_(oldvertex) {
+    if (!neighbor->simplicial){
+      qh_addfacetvertex(qh, neighbor, newvertex);
+      qh_setunique(qh, &newvertex->neighbors, neighbor);
+      qh_newvertices(qh, neighbor->vertices);  /* for qh_update_vertexneighbors of vertex neighbors */
+      if (qh->newfacet_list == qh->facet_tail) {
+        qh_removefacet(qh, neighbor);  /* add a neighbor to newfacet_list so that qh_partitionvisible has a newfacet */
+        qh_appendfacet(qh, neighbor);
+        neighbor->newfacet= True;
+      }
+    }
+  }
+  qh_renamevertex(qh, oldvertex, newvertex, ridges, NULL, NULL);  /* ridges invalidated */
+  if (oldvertex->deleted && !oldvertex->partitioned) {
+    FOREACHneighbor_(newvertex) {
+      if (!neighbor->visible) {
+        qh_distplane(qh, oldvertex->point, neighbor, &dist2);
+        if (dist2>maxdist2) {
+          maxdist2= dist2;
+          maxfacet= neighbor;
+        }
+      }
+    }
+    trace2((qh, qh->ferr, 2096, "qh_rename_adjacentvertex: partition old p%d(v%d) as a coplanar point for furthest f%d dist %2.2g.  Maybe repartition later (QH0031)\n",
+      qh_pointid(qh, oldvertex->point), oldvertex->id, maxfacet->id, maxdist2))
+    qh_partitioncoplanar(qh, oldvertex->point, maxfacet, NULL, !qh_ALL);  /* faster with maxdist2, otherwise duplicates distance tests from maxdist2/dist2 */
+    oldvertex->partitioned= True;
+  }
+  qh_settempfree(qh, &ridges);
+} /* rename_adjacentvertex */
+
+/*---------------------------------
+
+  qh_rename_sharedvertex(qh, vertex, facet )
+    detect and rename if shared vertex in facet
+    vertices have full ->neighbors
+
+  returns:
+    newvertex or NULL
+    the vertex may still exist in other facets (i.e., a neighbor was pinched)
+    does not change facet->neighbors
+    updates vertex->neighbors
+
+  notes:
+    only called by qh_reducevertices after qh_remove_extravertices
+       so f.vertices does not contain extraneous vertices
+    a shared vertex for a facet is only in ridges to one neighbor
+    this may undo a pinched facet
+
+    it does not catch pinches involving multiple facets.  These appear
+      to be difficult to detect, since an exhaustive search is too expensive.
+
+  design:
+    if vertex only has two neighbors
+      determine the ridges that contain the vertex
+      determine the vertices shared by both neighbors
+      if can find a new vertex in this set
+        rename the vertex to the new vertex
+*/
+vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet) {
+  facetT *neighbor, **neighborp, *neighborA= NULL;
+  setT *vertices, *ridges;
+  vertexT *newvertex= NULL;
+
+  if (qh_setsize(qh, vertex->neighbors) == 2) {
+    neighborA= SETfirstt_(vertex->neighbors, facetT);
+    if (neighborA == facet)
+      neighborA= SETsecondt_(vertex->neighbors, facetT);
+  }else if (qh->hull_dim == 3)
+    return NULL;
+  else {
+    qh->visit_id++;
+    FOREACHneighbor_(facet)
+      neighbor->visitid= qh->visit_id;
+    FOREACHneighbor_(vertex) {
+      if (neighbor->visitid == qh->visit_id) {
+        if (neighborA)
+          return NULL;
+        neighborA= neighbor;
+      }
+    }
+  }
+  if (!neighborA) {
+    qh_fprintf(qh, qh->ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
+        vertex->id, facet->id);
+    qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, vertex);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (neighborA) { /* avoid warning */
+    /* the vertex is shared by facet and neighborA */
+    ridges= qh_settemp(qh, qh->TEMPsize);
+    neighborA->visitid= ++qh->visit_id;
+    qh_vertexridges_facet(qh, vertex, facet, &ridges);
+    trace2((qh, qh->ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
+      qh_pointid(qh, vertex->point), vertex->id, facet->id, qh_setsize(qh, ridges), neighborA->id));
+    zinc_(Zintersectnum);
+    vertices= qh_vertexintersect_new(qh, facet->vertices, neighborA->vertices);
+    qh_setdel(vertices, vertex);
+    qh_settemppush(qh, vertices);
+    if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
+      qh_renamevertex(qh, vertex, newvertex, ridges, facet, neighborA);  /* ridges invalidated */
+    qh_settempfree(qh, &vertices);
+    qh_settempfree(qh, &ridges);
+  }
+  return newvertex;
+} /* rename_sharedvertex */
+
+/*---------------------------------
+
+  qh_renameridgevertex(qh, ridge, oldvertex, newvertex )
+    renames oldvertex as newvertex in ridge
+
+  returns:
+    True if renames oldvertex
+    False if deleted the ridge
+
+  notes:
+    called by qh_renamevertex
+    caller sets newvertex->delridge for deleted ridges (qh_reducevertices)
+
+  design:
+    delete oldvertex from ridge
+    if newvertex already in ridge
+      copy ridge->noconvex to another ridge if possible
+      delete the ridge
+    else
+      insert newvertex into the ridge
+      adjust the ridge's orientation
+*/
+boolT qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
+  int nth= 0, oldnth;
+  facetT *temp;
+  vertexT *vertex, **vertexp;
+
+  oldnth= qh_setindex(ridge->vertices, oldvertex);
+  if (oldnth < 0) {
+    qh_fprintf(qh, qh->ferr, 6424, "qhull internal error (qh_renameridgevertex): oldvertex v%d not found in r%d.  Cannot rename to v%d\n",
+        oldvertex->id, ridge->id, newvertex->id);
+    qh_errexit(qh, qh_ERRqhull, NULL, ridge);
+  }
+  qh_setdelnthsorted(qh, ridge->vertices, oldnth);
+  FOREACHvertex_(ridge->vertices) {
+    if (vertex == newvertex) {
+      zinc_(Zdelridge);
+      if (ridge->nonconvex) /* only one ridge has nonconvex set */
+        qh_copynonconvex(qh, ridge);
+      trace2((qh, qh->ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
+        ridge->id, oldvertex->id, newvertex->id));
+      qh_delridge_merge(qh, ridge); /* ridge.vertices deleted */
+      return False;
+    }
+    if (vertex->id < newvertex->id)
+      break;
+    nth++;
+  }
+  qh_setaddnth(qh, &ridge->vertices, nth, newvertex);
+  ridge->simplicialtop= False;
+  ridge->simplicialbot= False;
+  if (abs(oldnth - nth)%2) {
+    trace3((qh, qh->ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
+            ridge->id));
+    temp= ridge->top;
+    ridge->top= ridge->bottom;
+    ridge->bottom= temp;
+  }
+  return True;
+} /* renameridgevertex */
+
+
+/*---------------------------------
+
+  qh_renamevertex(qh, oldvertex, newvertex, ridges, oldfacet, neighborA )
+    renames oldvertex as newvertex in ridges of non-simplicial neighbors
+    set oldfacet/neighborA if oldvertex is shared between two facets (qh_rename_sharedvertex)
+    otherwise qh_redundant_vertex or qh_rename_adjacentvertex
+
+  returns:
+    if oldfacet and multiple neighbors, oldvertex may still exist afterwards
+    otherwise sets oldvertex->deleted for later deletion
+    one or more ridges maybe deleted
+    ridges is invalidated
+    merges may be added to degen_mergeset via qh_maydropneighbor or qh_degen_redundant_facet
+
+  notes:
+    qh_rename_sharedvertex can not change neighbors of newvertex (since it's a subset)
+    qh_redundant_vertex due to vertex->delridge for qh_reducevertices
+    qh_rename_adjacentvertex for complete renames
+
+  design:
+    for each ridge in ridges
+      rename oldvertex to newvertex and delete degenerate ridges
+    if oldfacet not defined
+      for each non-simplicial neighbor of oldvertex
+        delete oldvertex from neighbor's vertices
+        remove extra vertices from neighbor
+      add oldvertex to qh.del_vertices
+    else if oldvertex only between oldfacet and neighborA
+      delete oldvertex from oldfacet and neighborA
+      add oldvertex to qh.del_vertices
+    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
+      delete oldvertex from oldfacet
+      delete oldfacet from old vertex's neighbors
+      remove extra vertices (e.g., oldvertex) from neighborA
+*/
+void qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
+  facetT *neighbor, **neighborp;
+  ridgeT *ridge, **ridgep;
+  int topsize, bottomsize;
+  boolT istrace= False;
+
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 2 || oldvertex->id == qh->tracevertex_id ||
+        newvertex->id == qh->tracevertex_id) {
+    istrace= True;
+    qh_fprintf(qh, qh->ferr, 2086, "qh_renamevertex: rename v%d to v%d in %d ridges with old f%d and neighbor f%d\n",
+      oldvertex->id, newvertex->id, qh_setsize(qh, ridges), getid_(oldfacet), getid_(neighborA));
+  }
+#endif
+  FOREACHridge_(ridges) {
+    if (qh_renameridgevertex(qh, ridge, oldvertex, newvertex)) { /* ridge is deleted if False, invalidating ridges */
+      topsize= qh_setsize(qh, ridge->top->vertices);
+      bottomsize= qh_setsize(qh, ridge->bottom->vertices);
+      if (topsize < qh->hull_dim || (topsize == qh->hull_dim && !ridge->top->simplicial && qh_setin(ridge->top->vertices, newvertex))) {
+        trace4((qh, qh->ferr, 4070, "qh_renamevertex: ignore duplicate check for r%d.  top f%d (size %d) will be degenerate after rename v%d to v%d\n",
+          ridge->id, ridge->top->id, topsize, oldvertex->id, newvertex->id));
+      }else if (bottomsize < qh->hull_dim || (bottomsize == qh->hull_dim && !ridge->bottom->simplicial && qh_setin(ridge->bottom->vertices, newvertex))) {
+        trace4((qh, qh->ferr, 4071, "qh_renamevertex: ignore duplicate check for r%d.  bottom f%d (size %d) will be degenerate after rename v%d to v%d\n",
+          ridge->id, ridge->bottom->id, bottomsize, oldvertex->id, newvertex->id));
+      }else
+        qh_maybe_duplicateridge(qh, ridge);
+    }
+  }
+  if (!oldfacet) {
+    /* stat Zrenameall or Zpinchduplicate */
+    if (istrace)
+      qh_fprintf(qh, qh->ferr, 2087, "qh_renamevertex: renaming v%d to v%d in several facets for qh_redundant_vertex or MRGsubridge\n",
+               oldvertex->id, newvertex->id);
+    FOREACHneighbor_(oldvertex) {
+      if (neighbor->simplicial) {
+        qh_degen_redundant_facet(qh, neighbor); /* e.g., rbox 175 C3,2e-13 D4 t1545235541 | qhull d */
+      }else {
+        if (istrace)
+          qh_fprintf(qh, qh->ferr, 4080, "qh_renamevertex: rename vertices in non-simplicial neighbor f%d of v%d\n", neighbor->id, oldvertex->id);
+        qh_maydropneighbor(qh, neighbor);
+        qh_setdelsorted(neighbor->vertices, oldvertex); /* if degenerate, qh_degen_redundant_facet will add to mergeset */
+        if (qh_remove_extravertices(qh, neighbor))
+          neighborp--; /* neighbor deleted from oldvertex neighborset */
+        qh_degen_redundant_facet(qh, neighbor); /* either direction may be redundant, faster if combine? */
+        qh_test_redundant_neighbors(qh, neighbor);
+        qh_test_degen_neighbors(qh, neighbor);
+      }
+    }
+    if (!oldvertex->deleted) {
+      oldvertex->deleted= True;
+      qh_setappend(qh, &qh->del_vertices, oldvertex);
+    }
+  }else if (qh_setsize(qh, oldvertex->neighbors) == 2) {
+    zinc_(Zrenameshare);
+    if (istrace)
+      qh_fprintf(qh, qh->ferr, 3039, "qh_renamevertex: renaming v%d to v%d in oldfacet f%d for qh_rename_sharedvertex\n",
+               oldvertex->id, newvertex->id, oldfacet->id);
+    FOREACHneighbor_(oldvertex) {
+      qh_setdelsorted(neighbor->vertices, oldvertex);
+      qh_degen_redundant_facet(qh, neighbor);
+    }
+    oldvertex->deleted= True;
+    qh_setappend(qh, &qh->del_vertices, oldvertex);
+  }else {
+    zinc_(Zrenamepinch);
+    if (istrace || qh->IStracing >= 1)
+      qh_fprintf(qh, qh->ferr, 3040, "qh_renamevertex: renaming pinched v%d to v%d between f%d and f%d\n",
+               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
+    qh_setdelsorted(oldfacet->vertices, oldvertex);
+    qh_setdel(oldvertex->neighbors, oldfacet);
+    if (qh_remove_extravertices(qh, neighborA))
+      qh_degen_redundant_facet(qh, neighborA);
+  }
+  if (oldfacet)
+    qh_degen_redundant_facet(qh, oldfacet);
+} /* renamevertex */
+
+/*---------------------------------
+
+  qh_test_appendmerge(qh, facet, neighbor, simplicial )
+    test convexity and append to qh.facet_mergeset if non-convex
+    if pre-merging,
+      no-op if qh.SKIPconvex, or qh.MERGEexact and coplanar
+    if simplicial, assumes centrum test is valid (e.g., adjacent, simplicial new facets)
+
+  returns:
+    true if appends facet/neighbor to qh.facet_mergeset
+    sets facet->center as needed
+    does not change facet->seen
+
+  notes:
+    called from qh_getmergeset_initial, qh_getmergeset, and qh_test_vneighbors
+    must be at least as strong as qh_checkconvex (poly2_r.c)
+    assumes !f.flipped
+
+  design:
+    exit if qh.SKIPconvex ('Q0') and !qh.POSTmerging
+    if qh.cos_max ('An') is defined and merging coplanars
+      if the angle between facet normals is too shallow
+        append an angle-coplanar merge to qh.mergeset
+        return True
+    test convexity of facet and neighbor
+*/
+boolT qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor, boolT simplicial) {
+  realT angle= -REALmax;
+  boolT okangle= False;
+
+  if (qh->SKIPconvex && !qh->POSTmerging)
+    return False;
+  if (qh->cos_max < REALmax/2 && (!qh->MERGEexact || qh->POSTmerging)) {
+    angle= qh_getangle(qh, facet->normal, neighbor->normal);
+    okangle= True;
+    zinc_(Zangletests);
+    if (angle > qh->cos_max) {
+      zinc_(Zcoplanarangle);
+      qh_appendmergeset(qh, facet, neighbor, MRGanglecoplanar, 0.0, angle);
+      trace2((qh, qh->ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
+         angle, facet->id, neighbor->id));
+      return True;
+    }
+  }
+  if (simplicial || qh->hull_dim <= 3)
+    return qh_test_centrum_merge(qh, facet, neighbor, angle, okangle);
+  else
+    return qh_test_nonsimplicial_merge(qh, facet, neighbor, angle, okangle);
+} /* test_appendmerge */
+
+/*---------------------------------
+
+  qh_test_centrum_merge(qh, facet, neighbor, angle, okangle )
+    test centrum convexity and append non-convex facets to qh.facet_mergeset
+    'angle' is angle between facets if okangle is true, otherwise use 0.0
+
+  returns:
+    true if append facet/neighbor to qh.facet_mergeset
+    sets facet->center as needed
+    does not change facet->seen
+
+  notes:
+    called from test_appendmerge if adjacent simplicial facets or 2-d/3-d
+    at least as strict as qh_checkconvex, including qh.DISTround ('En' and 'Rn')
+
+  design:
+    make facet's centrum if needed
+    if facet's centrum is above the neighbor (qh.centrum_radius)
+      set isconcave
+
+    if facet's centrum is not below the neighbor (-qh.centrum_radius)
+      set iscoplanar
+    make neighbor's centrum if needed
+    if neighbor's centrum is above the facet
+      set isconcave
+    else if neighbor's centrum is not below the facet
+      set iscoplanar
+    if isconcave or iscoplanar and merging coplanars
+      get angle if needed (qh.ANGLEmerge 'An')
+      append concave-coplanar, concave ,or coplanar merge to qh.mergeset
+*/
+boolT qh_test_centrum_merge(qhT *qh, facetT *facet, facetT *neighbor, realT angle, boolT okangle) {
+  coordT dist, dist2, mergedist;
+  boolT isconcave= False, iscoplanar= False;
+
+  if (!facet->center)
+    facet->center= qh_getcentrum(qh, facet);
+  zzinc_(Zcentrumtests);
+  qh_distplane(qh, facet->center, neighbor, &dist);
+  if (dist > qh->centrum_radius)
+    isconcave= True;
+  else if (dist >= -qh->centrum_radius)
+    iscoplanar= True;
+  if (!neighbor->center)
+    neighbor->center= qh_getcentrum(qh, neighbor);
+  zzinc_(Zcentrumtests);
+  qh_distplane(qh, neighbor->center, facet, &dist2);
+  if (dist2 > qh->centrum_radius)
+    isconcave= True;
+  else if (!iscoplanar && dist2 >= -qh->centrum_radius)
+    iscoplanar= True;
+  if (!isconcave && (!iscoplanar || (qh->MERGEexact && !qh->POSTmerging)))
+    return False;
+  if (!okangle && qh->ANGLEmerge) {
+    angle= qh_getangle(qh, facet->normal, neighbor->normal);
+    zinc_(Zangletests);
+  }
+  if (isconcave && iscoplanar) {
+    zinc_(Zconcavecoplanarridge);
+    if (dist > dist2)
+      qh_appendmergeset(qh, facet, neighbor, MRGconcavecoplanar, dist, angle);
+    else
+      qh_appendmergeset(qh, neighbor, facet, MRGconcavecoplanar, dist2, angle);
+    trace0((qh, qh->ferr, 36, "qh_test_centrum_merge: concave f%d to coplanar f%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+           facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
+  }else if (isconcave) {
+    mergedist= fmax_(dist, dist2);
+    zinc_(Zconcaveridge);
+    qh_appendmergeset(qh, facet, neighbor, MRGconcave, mergedist, angle);
+    trace0((qh, qh->ferr, 37, "qh_test_centrum_merge: concave f%d to f%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+      facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
+  }else /* iscoplanar */ {
+    mergedist= fmin_(fabs_(dist), fabs_(dist2));
+    zinc_(Zcoplanarcentrum);
+    qh_appendmergeset(qh, facet, neighbor, MRGcoplanar, mergedist, angle);
+    trace2((qh, qh->ferr, 2097, "qh_test_centrum_merge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
+              facet->id, neighbor->id, dist, dist2, angle));
+  }
+  return True;
+} /* test_centrum_merge */
+
+/*---------------------------------
+
+  qh_test_degen_neighbors(qh, facet )
+    append degenerate neighbors to qh.degen_mergeset
+
+  notes:
+  called at end of qh_mergefacet() and qh_renamevertex()
+  call after test_redundant_facet() since MRGredundant is less expensive then MRGdegen
+    a degenerate facet has fewer than hull_dim neighbors
+    see: qh_merge_degenredundant()
+
+*/
+void qh_test_degen_neighbors(qhT *qh, facetT *facet) {
+  facetT *neighbor, **neighborp;
+  int size;
+
+  trace4((qh, qh->ferr, 4073, "qh_test_degen_neighbors: test for degenerate neighbors of f%d\n", facet->id));
+  FOREACHneighbor_(facet) {
+    if (neighbor->visible) {
+      qh_fprintf(qh, qh->ferr, 6359, "qhull internal error (qh_test_degen_neighbors): facet f%d has deleted neighbor f%d (qh.visible_list)\n",
+        facet->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+    }
+    if (neighbor->degenerate || neighbor->redundant || neighbor->dupridge) /* will merge or delete */
+      continue;
+    /* merge flipped-degenerate facet before flipped facets */
+    if ((size= qh_setsize(qh, neighbor->neighbors)) < qh->hull_dim) {
+      qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, 0.0, 1.0);
+      trace2((qh, qh->ferr, 2019, "qh_test_degen_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
+    }
+  }
+} /* test_degen_neighbors */
+
+
+/*---------------------------------
+
+  qh_test_nonsimplicial_merge(qh, facet, neighbor, angle, okangle )
+    test centrum and vertex convexity and append non-convex or redundant facets to qh.facet_mergeset
+    'angle' is angle between facets if okangle is true, otherwise use 0.0
+    skips coplanar merges if pre-merging with qh.MERGEexact ('Qx')
+
+  returns:
+    true if appends facet/neighbor to qh.facet_mergeset
+    sets facet->center as needed
+    does not change facet->seen
+
+  notes:
+    only called from test_appendmerge if a non-simplicial facet and at least 4-d
+    at least as strict as qh_checkconvex, including qh.DISTround ('En' and 'Rn')
+      centrums must be < -qh.centrum_radius
+    tests vertices as well as centrums since a facet may be twisted relative to its neighbor
+
+  design:
+    set precision constants for maxoutside, clearlyconcave, minvertex, and coplanarcentrum
+      use maxoutside for coplanarcentrum if premerging with 'Qx' and qh_MAXcoplanarcentrum merges
+      otherwise use qh.centrum_radious for coplanarcentrum
+    make facet and neighbor centrums if needed
+    isconcave if a centrum is above neighbor (coplanarcentrum)
+    iscoplanar if a centrum is not below neighbor (-qh.centrum_radius)
+    maybeconvex if a centrum is clearly below neighbor (-clearyconvex)
+    return False if both centrums clearly below neighbor (-clearyconvex)
+    return MRGconcave if isconcave
+
+    facets are neither clearly convex nor clearly concave
+    test vertices as well as centrums
+    if maybeconvex
+      determine mindist and maxdist for vertices of the other facet
+      maybe MRGredundant
+    otherwise
+      determine mindist and maxdist for vertices of either facet
+      maybe MRGredundant
+      maybeconvex if a vertex is clearly below neighbor (-clearconvex)
+
+    vertices are concave if dist > clearlyconcave
+    vertices are twisted if dist > maxoutside (isconcave and maybeconvex)
+    return False if not concave and pre-merge of 'Qx' (qh.MERGEexact)
+    vertices are coplanar if dist in -minvertex..maxoutside
+    if !isconcave, vertices are coplanar if dist >= -qh.MAXcoplanar (n*qh.premerge_centrum)
+
+    return False if neither concave nor coplanar
+    return MRGtwisted if isconcave and maybeconvex
+    return MRGconcavecoplanar if isconcave and isconvex
+    return MRGconcave if isconcave
+    return MRGcoplanar if iscoplanar
+*/
+boolT qh_test_nonsimplicial_merge(qhT *qh, facetT *facet, facetT *neighbor, realT angle, boolT okangle) {
+  coordT dist, mindist, maxdist, mindist2, maxdist2, dist2, maxoutside, clearlyconcave, minvertex, clearlyconvex, mergedist, coplanarcentrum;
+  boolT isconcave= False, iscoplanar= False, maybeconvex= False, isredundant= False;
+  vertexT *maxvertex= NULL, *maxvertex2= NULL;
+
+  maxoutside= fmax_(neighbor->maxoutside, qh->ONEmerge + qh->DISTround);
+  maxoutside= fmax_(maxoutside, facet->maxoutside);
+  clearlyconcave= qh_RATIOconcavehorizon * maxoutside;
+  minvertex= fmax_(-qh->min_vertex, qh->MAXcoplanar); /* non-negative, not available per facet, not used for iscoplanar */
+  clearlyconvex= qh_RATIOconvexmerge * minvertex; /* must be convex for MRGtwisted */
+  if (qh->MERGEexact && !qh->POSTmerging && (facet->nummerge > qh_MAXcoplanarcentrum || neighbor->nummerge > qh_MAXcoplanarcentrum))
+    coplanarcentrum= maxoutside;
+  else
+    coplanarcentrum= qh->centrum_radius;
+
+  if (!facet->center)
+    facet->center= qh_getcentrum(qh, facet);
+  zzinc_(Zcentrumtests);
+  qh_distplane(qh, facet->center, neighbor, &dist);
+  if (dist > coplanarcentrum)
+    isconcave= True;
+  else if (dist >= -qh->centrum_radius)
+    iscoplanar= True;
+  else if (dist < -clearlyconvex)
+    maybeconvex= True;
+  if (!neighbor->center)
+    neighbor->center= qh_getcentrum(qh, neighbor);
+  zzinc_(Zcentrumtests);
+  qh_distplane(qh, neighbor->center, facet, &dist2);
+  if (dist2 > coplanarcentrum)
+    isconcave= True;
+  else if (dist2 >= -qh->centrum_radius)
+    iscoplanar= True;
+  else if (dist2 < -clearlyconvex) {
+    if (maybeconvex)
+      return False; /* both centrums clearly convex */
+    maybeconvex= True;
+  }
+  if (isconcave) {
+    if (!okangle && qh->ANGLEmerge) {
+      angle= qh_getangle(qh, facet->normal, neighbor->normal);
+      zinc_(Zangletests);
+    }
+    mergedist= fmax_(dist, dist2);
+    zinc_(Zconcaveridge);
+    qh_appendmergeset(qh, facet, neighbor, MRGconcave, mergedist, angle);
+    trace0((qh, qh->ferr, 18, "qh_test_nonsimplicial_merge: concave centrum for f%d or f%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+      facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
+    return True;
+  }
+  /* neither clearly convex nor clearly concave, test vertices as well as centrums */
+  if (maybeconvex) {
+    if (dist < -clearlyconvex) {
+      maxdist= dist;  /* facet centrum clearly convex, no need to test its vertex distance */
+      mindist= dist;
+      maxvertex2= qh_furthestvertex(qh, neighbor, facet, &maxdist2, &mindist2);
+      if (!maxvertex2) {
+        qh_appendmergeset(qh, neighbor, facet, MRGredundant, maxdist2, qh_ANGLEnone);
+        isredundant= True;
+      }
+    }else { /* dist2 < -clearlyconvex */
+      maxdist2= dist2;   /* neighbor centrum clearly convex, no need to test its vertex distance */
+      mindist2= dist2;
+      maxvertex= qh_furthestvertex(qh, facet, neighbor, &maxdist, &mindist);
+      if (!maxvertex) {
+        qh_appendmergeset(qh, facet, neighbor, MRGredundant, maxdist, qh_ANGLEnone);
+        isredundant= True;
+      }
+    }
+  }else {
+    maxvertex= qh_furthestvertex(qh, facet, neighbor, &maxdist, &mindist);
+    if (maxvertex) {
+      maxvertex2= qh_furthestvertex(qh, neighbor, facet, &maxdist2, &mindist2);
+      if (!maxvertex2) {
+        qh_appendmergeset(qh, neighbor, facet, MRGredundant, maxdist2, qh_ANGLEnone);
+        isredundant= True;
+      }else if (mindist < -clearlyconvex || mindist2 < -clearlyconvex)
+        maybeconvex= True;
+    }else { /* !maxvertex */
+      qh_appendmergeset(qh, facet, neighbor, MRGredundant, maxdist, qh_ANGLEnone);
+      isredundant= True;
+    }
+  }
+  if (isredundant) {
+    zinc_(Zredundantmerge);
+    return True;
+  }
+
+  if (maxdist > clearlyconcave || maxdist2 > clearlyconcave)
+    isconcave= True;
+  else if (maybeconvex) {
+    if (maxdist > maxoutside || maxdist2 > maxoutside)
+      isconcave= True;  /* MRGtwisted */
+  }
+  if (!isconcave && qh->MERGEexact && !qh->POSTmerging)
+    return False;
+  if (isconcave && !iscoplanar) {
+    if (maxdist < maxoutside && (-qh->MAXcoplanar || (maxdist2 < maxoutside && mindist2 >= -qh->MAXcoplanar)))
+      iscoplanar= True; /* MRGconcavecoplanar */
+  }else if (!iscoplanar) {
+    if (mindist >= -qh->MAXcoplanar || mindist2 >= -qh->MAXcoplanar)
+      iscoplanar= True;  /* MRGcoplanar */
+  }
+  if (!isconcave && !iscoplanar)
+    return False;
+  if (!okangle && qh->ANGLEmerge) {
+    angle= qh_getangle(qh, facet->normal, neighbor->normal);
+    zinc_(Zangletests);
+  }
+  if (isconcave && maybeconvex) {
+    zinc_(Ztwistedridge);
+    if (maxdist > maxdist2)
+      qh_appendmergeset(qh, facet, neighbor, MRGtwisted, maxdist, angle);
+    else
+      qh_appendmergeset(qh, neighbor, facet, MRGtwisted, maxdist2, angle);
+    trace0((qh, qh->ferr, 27, "qh_test_nonsimplicial_merge: twisted concave f%d v%d to f%d v%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+           facet->id, getid_(maxvertex), neighbor->id, getid_(maxvertex2), maxdist, maxdist2, angle, qh->furthest_id));
+  }else if (isconcave && iscoplanar) {
+    zinc_(Zconcavecoplanarridge);
+    if (maxdist > maxdist2)
+      qh_appendmergeset(qh, facet, neighbor, MRGconcavecoplanar, maxdist, angle);
+    else
+      qh_appendmergeset(qh, neighbor, facet, MRGconcavecoplanar, maxdist2, angle);
+    trace0((qh, qh->ferr, 28, "qh_test_nonsimplicial_merge: concave coplanar f%d v%d to f%d v%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+      facet->id, getid_(maxvertex), neighbor->id, getid_(maxvertex2), maxdist, maxdist2, angle, qh->furthest_id));
+  }else if (isconcave) {
+    mergedist= fmax_(maxdist, maxdist2);
+    zinc_(Zconcaveridge);
+    qh_appendmergeset(qh, facet, neighbor, MRGconcave, mergedist, angle);
+    trace0((qh, qh->ferr, 29, "qh_test_nonsimplicial_merge: concave f%d v%d to f%d v%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+      facet->id, getid_(maxvertex), neighbor->id, getid_(maxvertex2), maxdist, maxdist2, angle, qh->furthest_id));
+  }else /* iscoplanar */ {
+    mergedist= fmax_(fmax_(maxdist, maxdist2), fmax_(-mindist, -mindist2));
+    zinc_(Zcoplanarcentrum);
+    qh_appendmergeset(qh, facet, neighbor, MRGcoplanar, mergedist, angle);
+    trace2((qh, qh->ferr, 2099, "qh_test_nonsimplicial_merge: coplanar f%d v%d to f%d v%d, dist %4.4g and reverse dist %4.4g, angle %4.4g during p%d\n",
+      facet->id, getid_(maxvertex), neighbor->id, getid_(maxvertex2), maxdist, maxdist2, angle, qh->furthest_id));
+  }
+  return True;
+} /* test_nonsimplicial_merge */
+
+/*---------------------------------
+
+  qh_test_redundant_neighbors(qh, facet )
+    append degenerate facet or its redundant neighbors to qh.degen_mergeset
+
+  returns:
+    bumps vertex_visit
+
+  notes:
+    called at end of qh_mergefacet(), qh_mergecycle_all(), and qh_renamevertex
+    call before qh_test_degen_neighbors (MRGdegen are more likely to cause problems)
+    a redundant neighbor's vertices is a subset of the facet's vertices
+    with pinched and flipped facets, a redundant neighbor may have a wildly different normal
+
+    see qh_merge_degenredundant() and qh_-_facet()
+
+  design:
+    if facet is degenerate
+       appends facet to degen_mergeset
+    else
+       appends redundant neighbors of facet to degen_mergeset
+*/
+void qh_test_redundant_neighbors(qhT *qh, facetT *facet) {
+  vertexT *vertex, **vertexp;
+  facetT *neighbor, **neighborp;
+  int size;
+
+  trace4((qh, qh->ferr, 4022, "qh_test_redundant_neighbors: test neighbors of f%d vertex_visit %d\n",
+          facet->id, qh->vertex_visit+1));
+  if ((size= qh_setsize(qh, facet->neighbors)) < qh->hull_dim) {
+    qh_appendmergeset(qh, facet, facet, MRGdegen, 0.0, 1.0);
+    trace2((qh, qh->ferr, 2017, "qh_test_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
+  }else {
+    qh->vertex_visit++;
+    FOREACHvertex_(facet->vertices)
+      vertex->visitid= qh->vertex_visit;
+    FOREACHneighbor_(facet) {
+      if (neighbor->visible) {
+        qh_fprintf(qh, qh->ferr, 6360, "qhull internal error (qh_test_redundant_neighbors): facet f%d has deleted neighbor f%d (qh.visible_list)\n",
+          facet->id, neighbor->id);
+        qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
+      }
+      if (neighbor->degenerate || neighbor->redundant || neighbor->dupridge) /* will merge or delete */
+        continue;
+      if (facet->flipped && !neighbor->flipped) /* do not merge non-flipped into flipped */
+        continue;
+      /* merge redundant-flipped facet first */
+      /* uses early out instead of checking vertex count */
+      FOREACHvertex_(neighbor->vertices) {
+        if (vertex->visitid != qh->vertex_visit)
+          break;
+      }
+      if (!vertex) {
+        qh_appendmergeset(qh, neighbor, facet, MRGredundant, 0.0, 1.0);
+        trace2((qh, qh->ferr, 2018, "qh_test_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
+      }
+    }
+  }
+} /* test_redundant_neighbors */
+
+/*---------------------------------
+
+  qh_test_vneighbors(qh)
+    test vertex neighbors for convexity
+    tests all facets on qh.newfacet_list
+
+  returns:
+    true if non-convex vneighbors appended to qh.facet_mergeset
+    initializes vertex neighbors if needed
+
+  notes:
+    called by qh_all_merges from qh_postmerge if qh.TESTvneighbors ('Qv')
+    assumes all facet neighbors have been tested
+    this can be expensive
+    this does not guarantee that a centrum is below all facets
+      but it is unlikely
+    uses qh.visit_id
+
+  design:
+    build vertex neighbors if necessary
+    for all new facets
+      for all vertices
+        for each unvisited facet neighbor of the vertex
+          test new facet and neighbor for convexity
+*/
+boolT qh_test_vneighbors(qhT *qh /* qh.newfacet_list */) {
+  facetT *newfacet, *neighbor, **neighborp;
+  vertexT *vertex, **vertexp;
+  int nummerges= 0;
+
+  trace1((qh, qh->ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
+  if (!qh->VERTEXneighbors)
+    qh_vertexneighbors(qh);
+  FORALLnew_facets
+    newfacet->seen= False;
+  FORALLnew_facets {
+    newfacet->seen= True;
+    newfacet->visitid= qh->visit_id++;
+    FOREACHneighbor_(newfacet)
+      newfacet->visitid= qh->visit_id;
+    FOREACHvertex_(newfacet->vertices) {
+      FOREACHneighbor_(vertex) {
+        if (neighbor->seen || neighbor->visitid == qh->visit_id)
+          continue;
+        if (qh_test_appendmerge(qh, newfacet, neighbor, False)) /* ignores optimization for simplicial ridges */
+          nummerges++;
+      }
+    }
+  }
+  zadd_(Ztestvneighbor, nummerges);
+  trace1((qh, qh->ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
+           nummerges));
+  return (nummerges > 0);
+} /* test_vneighbors */
+
+/*---------------------------------
+
+  qh_tracemerge(qh, facet1, facet2 )
+    print trace message after merge
+*/
+void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) {
+  boolT waserror= False;
+  const char *mergename;
+
+#ifndef qh_NOtrace
+  if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *))
+    mergename= mergetypes[mergetype];
+  else
+    mergename= mergetypes[MRGnone];
+  if (qh->IStracing >= 4)
+    qh_errprint(qh, "MERGED", facet2, NULL, NULL, NULL);
+  if (facet2 == qh->tracefacet || (qh->tracevertex && qh->tracevertex->newfacet)) {
+    qh_fprintf(qh, qh->ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d into f%d type %d (%s), furthest p%d\n",
+      facet1->id, facet2->id, mergetype, mergename, qh->furthest_id);
+    if (facet2 != qh->tracefacet)
+      qh_errprint(qh, "TRACE", qh->tracefacet,
+        (qh->tracevertex && qh->tracevertex->neighbors) ?
+           SETfirstt_(qh->tracevertex->neighbors, facetT) : NULL,
+        NULL, qh->tracevertex);
+  }
+  if (qh->tracevertex) {
+    if (qh->tracevertex->deleted)
+      qh_fprintf(qh, qh->ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
+            qh->furthest_id);
+    else
+      qh_checkvertex(qh, qh->tracevertex, qh_ALL, &waserror);
+  }
+  if (qh->tracefacet && qh->tracefacet->normal && !qh->tracefacet->visible)
+    qh_checkfacet(qh, qh->tracefacet, True /* newmerge */, &waserror);
+#endif /* !qh_NOtrace */
+  if (qh->CHECKfrequently || qh->IStracing >= 4) { /* can't check polygon here */
+    if (qh->IStracing >= 4 && qh->num_facets < 500) {
+      qh_printlists(qh);
+    }
+    qh_checkfacet(qh, facet2, True /* newmerge */, &waserror);
+  }
+  if (waserror)
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL); /* erroneous facet logged by qh_checkfacet */
+} /* tracemerge */
+
+/*---------------------------------
+
+  qh_tracemerging(qh)
+    print trace message during POSTmerging
+
+  returns:
+    updates qh.mergereport
+
+  notes:
+    called from qh_mergecycle() and qh_mergefacet()
+
+  see:
+    qh_buildtracing()
+*/
+void qh_tracemerging(qhT *qh) {
+  realT cpu;
+  int total;
+  time_t timedata;
+  struct tm *tp;
+
+  qh->mergereport= zzval_(Ztotmerge);
+  time(&timedata);
+  tp= localtime(&timedata);
+  cpu= qh_CPUclock;
+  cpu /= qh_SECticks;
+  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
+  qh_fprintf(qh, qh->ferr, 8087, "\n\
+At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets with max_outside %2.2g, min_vertex %2.2g.\n\
+  The hull contains %d facets and %d vertices.\n",
+      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, total, qh->max_outside, qh->min_vertex,
+      qh->num_facets - qh->num_visible,
+      qh->num_vertices-qh_setsize(qh, qh->del_vertices));
+} /* tracemerging */
+
+/*---------------------------------
+
+  qh_updatetested(qh, facet1, facet2 )
+    clear facet2->tested and facet1->ridge->tested for merge
+
+  returns:
+    deletes facet2->center unless it's already large
+      if so, clears facet2->ridge->tested
+
+  notes:
+    only called by qh_mergefacet
+
+  design:
+    clear facet2->tested
+    clear ridge->tested for facet1's ridges
+    if facet2 has a centrum
+      if facet2 is large
+        set facet2->keepcentrum
+      else if facet2 has 3 vertices due to many merges, or not large and post merging
+        clear facet2->keepcentrum
+      unless facet2->keepcentrum
+        clear facet2->center to recompute centrum later
+        clear ridge->tested for facet2's ridges
+*/
+void qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2) {
+  ridgeT *ridge, **ridgep;
+  int size;
+
+  facet2->tested= False;
+  FOREACHridge_(facet1->ridges)
+    ridge->tested= False;
+  if (!facet2->center)
+    return;
+  size= qh_setsize(qh, facet2->vertices);
+  if (!facet2->keepcentrum) {
+    if (size > qh->hull_dim + qh_MAXnewcentrum) {
+      facet2->keepcentrum= True;
+      zinc_(Zwidevertices);
+    }
+  }else if (size <= qh->hull_dim + qh_MAXnewcentrum) {
+    /* center and keepcentrum was set */
+    if (size == qh->hull_dim || qh->POSTmerging)
+      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
+  }
+  if (!facet2->keepcentrum) {
+    qh_memfree(qh, facet2->center, qh->normal_size);
+    facet2->center= NULL;
+    FOREACHridge_(facet2->ridges)
+      ridge->tested= False;
+  }
+} /* updatetested */
+
+/*---------------------------------
+
+  qh_vertexridges(qh, vertex, allneighbors )
+    return temporary set of ridges adjacent to a vertex
+    vertex->neighbors defined (qh_vertexneighbors)
+
+  notes:
+    uses qh.visit_id
+    does not include implicit ridges for simplicial facets
+    skips last neighbor, unless allneighbors.  For new facets, the last neighbor shares ridges with adjacent neighbors
+    if the last neighbor is not simplicial, it will have ridges for its simplicial neighbors
+    Use allneighbors when a new cone is attached to an existing convex hull
+    similar to qh_neighbor_vertices
+
+  design:
+    for each neighbor of vertex
+      add ridges that include the vertex to ridges
+*/
+setT *qh_vertexridges(qhT *qh, vertexT *vertex, boolT allneighbors) {
+  facetT *neighbor, **neighborp;
+  setT *ridges= qh_settemp(qh, qh->TEMPsize);
+  int size;
+
+  qh->visit_id += 2;  /* visit_id for vertex neighbors, visit_id-1 for facets of visited ridges */
+  FOREACHneighbor_(vertex)
+    neighbor->visitid= qh->visit_id;
+  FOREACHneighbor_(vertex) {
+    if (*neighborp || allneighbors)   /* no new ridges in last neighbor */
+      qh_vertexridges_facet(qh, vertex, neighbor, &ridges);
+  }
+  if (qh->PRINTstatistics || qh->IStracing) {
+    size= qh_setsize(qh, ridges);
+    zinc_(Zvertexridge);
+    zadd_(Zvertexridgetot, size);
+    zmax_(Zvertexridgemax, size);
+    trace3((qh, qh->ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
+             size, vertex->id));
+  }
+  return ridges;
+} /* vertexridges */
+
+/*---------------------------------
+
+  qh_vertexridges_facet(qh, vertex, facet, ridges )
+    add adjacent ridges for vertex in facet
+    neighbor->visitid==qh.visit_id if it hasn't been visited
+
+  returns:
+    ridges updated
+    sets facet->visitid to qh.visit_id-1
+
+  design:
+    for each ridge of facet
+      if ridge of visited neighbor (i.e., unprocessed)
+        if vertex in ridge
+          append ridge
+    mark facet processed
+*/
+void qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges) {
+  ridgeT *ridge, **ridgep;
+  facetT *neighbor;
+  int last_i= qh->hull_dim-2;
+  vertexT *second, *last;
+
+  FOREACHridge_(facet->ridges) {
+    neighbor= otherfacet_(ridge, facet);
+    if (neighbor->visitid == qh->visit_id) {
+      if (SETfirst_(ridge->vertices) == vertex) {
+        qh_setappend(qh, ridges, ridge);
+      }else if (last_i > 2) {
+        second= SETsecondt_(ridge->vertices, vertexT);
+        last= SETelemt_(ridge->vertices, last_i, vertexT);
+        if (second->id >= vertex->id && last->id <= vertex->id) { /* vertices inverse sorted by id */
+          if (second == vertex || last == vertex)
+            qh_setappend(qh, ridges, ridge);
+          else if (qh_setin(ridge->vertices, vertex))
+            qh_setappend(qh, ridges, ridge);
+        }
+      }else if (SETelem_(ridge->vertices, last_i) == vertex
+          || (last_i > 1 && SETsecond_(ridge->vertices) == vertex)) {
+        qh_setappend(qh, ridges, ridge);
+      }
+    }
+  }
+  facet->visitid= qh->visit_id-1;
+} /* vertexridges_facet */
+
+/*---------------------------------
+
+  qh_willdelete(qh, facet, replace )
+    moves facet to visible list for qh_deletevisible
+    sets facet->f.replace to replace (may be NULL)
+    clears f.ridges and f.neighbors -- no longer valid
+
+  returns:
+    bumps qh.num_visible
+*/
+void qh_willdelete(qhT *qh, facetT *facet, facetT *replace) {
+
+  trace4((qh, qh->ferr, 4081, "qh_willdelete: move f%d to visible list, set its replacement as f%d, and clear f.neighbors and f.ridges\n", facet->id, getid_(replace)));
+  if (!qh->visible_list && qh->newfacet_list) {
+    qh_fprintf(qh, qh->ferr, 6378, "qhull internal error (qh_willdelete): expecting qh.visible_list at before qh.newfacet_list f%d.   Got NULL\n",
+        qh->newfacet_list->id);
+    qh_errexit2(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh_removefacet(qh, facet);
+  qh_prependfacet(qh, facet, &qh->visible_list);
+  qh->num_visible++;
+  facet->visible= True;
+  facet->f.replace= replace;
+  if (facet->ridges)
+    SETfirst_(facet->ridges)= NULL;
+  if (facet->neighbors)
+    SETfirst_(facet->neighbors)= NULL;
+} /* willdelete */
+
+#else /* qh_NOmerge */
+
+void qh_all_vertexmerges(qhT *qh, int apexpointid, facetT *facet, facetT **retryfacet) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(apexpointid)
+  QHULL_UNUSED(facet)
+  QHULL_UNUSED(retryfacet)
+}
+void qh_premerge(qhT *qh, int apexpointid, realT maxcentrum, realT maxangle) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(apexpointid)
+  QHULL_UNUSED(maxcentrum)
+  QHULL_UNUSED(maxangle)
+}
+void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
+                      boolT vneighbors) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(reason)
+  QHULL_UNUSED(maxcentrum)
+  QHULL_UNUSED(maxangle)
+  QHULL_UNUSED(vneighbors)
+}
+void qh_checkdelfacet(qhT *qh, facetT *facet, setT *mergeset) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(facet)
+  QHULL_UNUSED(mergeset)
+}
+void qh_checkdelridge(qhT *qh /* qh.visible_facets, vertex_mergeset */) {
+  QHULL_UNUSED(qh)
+}
+boolT qh_checkzero(qhT *qh, boolT testall) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(testall)
+
+  return True;
+}
+void qh_freemergesets(qhT *qh) {
+  QHULL_UNUSED(qh)
+}
+void qh_initmergesets(qhT *qh) {
+  QHULL_UNUSED(qh)
+}
+void qh_merge_pinchedvertices(qhT *qh, int apexpointid /* qh.newfacet_list */) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(apexpointid)
+}
+#endif /* qh_NOmerge */
+
diff --git a/vendor/qhull/libqhull_r/merge_r.h b/vendor/qhull/libqhull_r/merge_r.h
new file mode 100644
index 0000000..a0d4091
--- /dev/null
+++ b/vendor/qhull/libqhull_r/merge_r.h
@@ -0,0 +1,238 @@
+/*
  ---------------------------------
+
+   merge_r.h
+   header file for merge_r.c
+
+   see qh-merge_r.htm and merge_r.c
+
+   Copyright (c) 1993-2020 C.B. Barber.
+   $Id: //main/2019/qhull/src/libqhull_r/merge_r.h#2 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFmerge
+#define qhDEFmerge 1
+
+#include "libqhull_r.h"
+
+
+/*============ -constants- ==============*/
+
+/*----------------------------------
+
+  qh_ANGLEnone
+    indicates missing angle for mergeT->angle
+*/
+#define qh_ANGLEnone 2.0
+
+/*----------------------------------
+
+  MRG... (mergeType)
+    indicates the type of a merge (mergeT->type)
+    MRGcoplanar...MRGtwisted set by qh_test_centrum_merge, qh_test_nonsimplicial_merge
+*/
+typedef enum {  /* must match mergetypes[] */
+  MRGnone= 0,
+                  /* MRGcoplanar..MRGtwisted go into qh.facet_mergeset for qh_all_merges 
+                     qh_compare_facetmerge selects lower mergetypes for merging first */
+  MRGcoplanar,          /* (1) centrum coplanar if centrum ('Cn') or vertex not clearly above or below neighbor */
+  MRGanglecoplanar,     /* (2) angle coplanar if angle ('An') is coplanar */
+  MRGconcave,           /* (3) concave ridge */
+  MRGconcavecoplanar,   /* (4) concave and coplanar ridge, one side concave, other side coplanar */
+  MRGtwisted,           /* (5) twisted ridge, both concave and convex, facet1 is wider */
+                  /* MRGflip go into qh.facet_mergeset for qh_flipped_merges */
+  MRGflip,              /* (6) flipped facet if qh.interior_point is above facet, w/ facet1 == facet2 */
+                  /* MRGdupridge go into qh.facet_mergeset for qh_forcedmerges */
+  MRGdupridge,          /* (7) dupridge if more than two neighbors.  Set by qh_mark_dupridges for qh_MERGEridge */
+                  /* MRGsubridge and MRGvertices go into vertex_mergeset */
+  MRGsubridge,          /* (8) merge pinched vertex to remove the subridge of a MRGdupridge */
+  MRGvertices,          /* (9) merge pinched vertex to remove a facet's ridges with the same vertices */
+                  /* MRGdegen, MRGredundant, and MRGmirror go into qh.degen_mergeset */
+  MRGdegen,             /* (10) degenerate facet (!enough neighbors) facet1 == facet2 */
+  MRGredundant,         /* (11) redundant facet (vertex subset) */
+                        /* merge_degenredundant assumes degen < redundant */
+  MRGmirror,            /* (12) mirror facets: same vertices due to null facets in qh_triangulate 
+                           f.redundant for both facets*/
+                  /* MRGcoplanarhorizon for qh_mergecycle_all only */
+  MRGcoplanarhorizon,   /* (13) new facet coplanar with the horizon (qh_mergecycle_all) */
+  ENDmrg
+} mergeType;
+
+/*----------------------------------
+
+  qh_MERGEapex
+    flag for qh_mergefacet() to indicate an apex merge
+*/
+#define qh_MERGEapex     True
+
+/*============ -structures- ====================*/
+
+/*----------------------------------
+
+  mergeT
+    structure used to merge facets
+*/
+
+typedef struct mergeT mergeT;
+struct mergeT {         /* initialize in qh_appendmergeset */
+  realT   angle;        /* cosine of angle between normals of facet1 and facet2, 
+                           null value and right angle is 0.0, coplanar is 1.0, narrow is -1.0 */
+  realT   distance;     /* absolute value of distance between vertices, centrum and facet, or vertex and facet */
+  facetT *facet1;       /* will merge facet1 into facet2 */
+  facetT *facet2;
+  vertexT *vertex1;     /* will merge vertext1 into vertex2 for MRGsubridge or MRGvertices */
+  vertexT *vertex2;
+  ridgeT  *ridge1;      /* the duplicate ridges resolved by MRGvertices */
+  ridgeT  *ridge2;      /* merge is deleted if either ridge is deleted (qh_delridge) */
+  mergeType mergetype;
+};
+
+
+/*=========== -macros- =========================*/
+
+/*----------------------------------
+
+  FOREACHmerge_( merges ) {...}
+    assign 'merge' to each merge in merges
+
+  notes:
+    uses 'mergeT *merge, **mergep;'
+    if qh_mergefacet(),
+      restart or use qh_setdellast() since qh.facet_mergeset may change
+    see FOREACHsetelement_
+*/
+#define FOREACHmerge_(merges) FOREACHsetelement_(mergeT, merges, merge)
+
+/*----------------------------------
+
+  FOREACHmergeA_( vertices ) { ... }
+    assign 'mergeA' to each merge in merges
+
+  notes:
+    uses 'mergeT *mergeA, *mergeAp;'
+    see FOREACHsetelement_
+*/
+#define FOREACHmergeA_(merges) FOREACHsetelement_(mergeT, merges, mergeA)
+
+/*----------------------------------
+
+  FOREACHmerge_i_(qh, vertices ) { ... }
+    assign 'merge' and 'merge_i' for each merge in mergeset
+
+  declare:
+    mergeT *merge;
+    int     merge_n, merge_i;
+
+  see:
+    FOREACHsetelement_i_
+*/
+#define FOREACHmerge_i_(qh, mergeset) FOREACHsetelement_i_(qh, mergeT, mergeset, merge)
+
+/*============ prototypes in alphabetical order after pre/postmerge =======*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void    qh_premerge(qhT *qh, int apexpointid, realT maxcentrum, realT maxangle);
+void    qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
+             boolT vneighbors);
+void    qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors);
+void    qh_all_vertexmerges(qhT *qh, int apexpointid, facetT *facet, facetT **retryfacet);
+void    qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, coordT dist, realT angle);
+void    qh_appendvertexmerge(qhT *qh, vertexT *vertex, vertexT *destination, mergeType mergetype, realT distance, ridgeT *ridge1, ridgeT *ridge2);
+setT   *qh_basevertices(qhT *qh, facetT *samecycle);
+void    qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2);
+void    qh_checkconnect(qhT *qh /* qh.new_facets */);
+void    qh_checkdelfacet(qhT *qh, facetT *facet, setT *mergeset);
+void    qh_checkdelridge(qhT *qh /* qh.visible_facets, vertex_mergeset */);
+boolT   qh_checkzero(qhT *qh, boolT testall);
+int     qh_compare_anglemerge(const void *p1, const void *p2);
+int     qh_compare_facetmerge(const void *p1, const void *p2);
+int     qh_comparevisit(const void *p1, const void *p2);
+void    qh_copynonconvex(qhT *qh, ridgeT *atridge);
+void    qh_degen_redundant_facet(qhT *qh, facetT *facet);
+void    qh_drop_mergevertex(qhT *qh, mergeT *merge);
+void    qh_delridge_merge(qhT *qh, ridgeT *ridge);
+vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges);
+vertexT *qh_findbest_pinchedvertex(qhT *qh, mergeT *merge, vertexT *apex, vertexT **pinchedp, realT *distp /* qh.newfacet_list */);
+vertexT *qh_findbest_ridgevertex(qhT *qh, ridgeT *ridge, vertexT **pinchedp, coordT *distp);
+void    qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
+           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
+facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
+void    qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge);
+void    qh_forcedmerges(qhT *qh, boolT *wasmerge);
+void    qh_freemergesets(qhT *qh);
+void    qh_getmergeset(qhT *qh, facetT *facetlist);
+void    qh_getmergeset_initial(qhT *qh, facetT *facetlist);
+boolT   qh_getpinchedmerges(qhT *qh, vertexT *apex, coordT maxdupdist, boolT *iscoplanar /* qh.newfacet_list, vertex_mergeset */);
+boolT   qh_hasmerge(setT *mergeset, mergeType type, facetT *facetA, facetT *facetB);
+void    qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
+ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
+              vertexT *vertex, vertexT *oldvertex, int *hashslot);
+void    qh_initmergesets(qhT *qh);
+void    qh_makeridges(qhT *qh, facetT *facet);
+void    qh_mark_dupridges(qhT *qh, facetT *facetlist, boolT allmerges);
+void    qh_maybe_duplicateridge(qhT *qh, ridgeT *ridge);
+void    qh_maybe_duplicateridges(qhT *qh, facetT *facet);
+void    qh_maydropneighbor(qhT *qh, facetT *facet);
+int     qh_merge_degenredundant(qhT *qh);
+void    qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype);
+void    qh_merge_pinchedvertices(qhT *qh, int apexpointid /* qh.newfacet_list */);
+void    qh_merge_twisted(qhT *qh, facetT *facet1, facetT *facet2);
+void    qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet);
+void    qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge);
+void    qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet);
+void    qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
+void    qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet);
+void    qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
+void    qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype, realT *mindist, realT *maxdist, boolT mergeapex);
+void    qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2);
+void    qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2);
+void    qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2);
+void    qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex);
+void    qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2);
+void    qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2);
+void    qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices);
+setT   *qh_neighbor_intersections(qhT *qh, vertexT *vertex);
+setT   *qh_neighbor_vertices(qhT *qh, vertexT *vertex, setT *subridge);
+void    qh_neighbor_vertices_facet(qhT *qh, vertexT *vertexA, facetT *facet, setT **vertices);
+void    qh_newvertices(qhT *qh, setT *vertices);
+mergeT *qh_next_vertexmerge(qhT *qh);
+facetT *qh_opposite_horizonfacet(qhT *qh, mergeT *merge, vertexT **vertex);
+boolT   qh_reducevertices(qhT *qh);
+vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex);
+boolT   qh_remove_extravertices(qhT *qh, facetT *facet);
+void    qh_remove_mergetype(qhT *qh, setT *mergeset, mergeType type);
+void    qh_rename_adjacentvertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, realT dist);
+vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet);
+boolT   qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
+void    qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges,
+                        facetT *oldfacet, facetT *neighborA);
+boolT   qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor, boolT simplicial);
+void    qh_test_degen_neighbors(qhT *qh, facetT *facet);
+boolT   qh_test_centrum_merge(qhT *qh, facetT *facet, facetT *neighbor, realT angle, boolT okangle);
+boolT   qh_test_nonsimplicial_merge(qhT *qh, facetT *facet, facetT *neighbor, realT angle, boolT okangle);
+void    qh_test_redundant_neighbors(qhT *qh, facetT *facet);
+boolT   qh_test_vneighbors(qhT *qh /* qh.newfacet_list */);
+void    qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype);
+void    qh_tracemerging(qhT *qh);
+void    qh_undo_newfacets(qhT *qh);
+void    qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2);
+setT   *qh_vertexridges(qhT *qh, vertexT *vertex, boolT allneighbors);
+void    qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges);
+void    qh_willdelete(qhT *qh, facetT *facet, facetT *replace);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFmerge */
diff --git a/vendor/qhull/libqhull_r/poly2_r.c b/vendor/qhull/libqhull_r/poly2_r.c
new file mode 100644
index 0000000..aed77e0
--- /dev/null
+++ b/vendor/qhull/libqhull_r/poly2_r.c
@@ -0,0 +1,3959 @@
+/*
  ---------------------------------
+
+   poly2_r.c
+   implements polygons and simplicies
+
+   see qh-poly_r.htm, poly_r.h and libqhull_r.h
+
+   frequently used code is in poly_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/poly2_r.c#22 $$Change: 3978 $
+   $DateTime: 2025/08/24 21:38:45 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+/*======== functions in alphabetical order ==========*/
+
+/*---------------------------------
+
+  qh_addfacetvertex(qh, facet, newvertex )
+    add newvertex to facet.vertices if not already there
+    vertices are inverse sorted by vertex->id
+
+  returns:
+    True if new vertex for facet
+
+  notes:
+    see qh_replacefacetvertex
+*/
+boolT qh_addfacetvertex(qhT *qh, facetT *facet, vertexT *newvertex) {
+  vertexT *vertex;
+  int vertex_i= 0, vertex_n;
+  boolT isnew= True;
+
+  FOREACHvertex_i_(qh, facet->vertices) {
+    if (vertex->id < newvertex->id) {
+      break;
+    }else if (vertex->id == newvertex->id) {
+      isnew= False;
+      break;
+    }
+  }
+  if (isnew)
+    qh_setaddnth(qh, &facet->vertices, vertex_i, newvertex);
+  return isnew;
+} /* addfacetvertex */
+
+/*---------------------------------
+
+  qh_addhash( newelem, hashtable, hashsize, hash )
+    add newelem to linear hash table at hash if not already there
+*/
+void qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash) {
+  int scan;
+  void *elem;
+
+  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
+       scan= (++scan >= hashsize ? 0 : scan)) {
+    if (elem == newelem)
+      break;
+  }
+  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
+  if (!elem)
+    SETelem_(hashtable, scan)= newelem;
+} /* addhash */
+
+/*---------------------------------
+
+  qh_check_bestdist(qh)
+    check that all points are within max_outside of the nearest facet
+    if qh.ONLYgood,
+      ignores !good facets
+
+  see:
+    qh_check_maxout(), qh_outerinner()
+
+  notes:
+    only called from qh_check_points()
+      seldom used since qh.MERGING is almost always set
+    if notverified>0 at end of routine
+      some points were well inside the hull.  If the hull contains
+      a lens-shaped component, these points were not verified.  Use
+      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
+
+  design:
+    determine facet for each point (if any)
+    for each point
+      start with the assigned facet or with the first facet
+      find the best facet for the point and check all coplanar facets
+      error if point is outside of facet
+*/
+void qh_check_bestdist(qhT *qh) {
+  boolT waserror= False, unassigned;
+  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
+  facetT *facetlist;
+  realT dist, maxoutside, maxdist= -REALmax;
+  pointT *point;
+  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
+  setT *facets;
+
+  trace1((qh, qh->ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
+      qh->facet_list->id));
+  maxoutside= qh_maxouter(qh);
+  maxoutside += qh->DISTround;
+  /* one more qh.DISTround for check computation */
+  trace1((qh, qh->ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
+  facets= qh_pointfacet(qh /* qh.facet_list */);
+  if (!qh_QUICKhelp && qh->PRINTprecision)
+    qh_fprintf(qh, qh->ferr, 8091, "\n\
+qhull output completed.  Verifying that %d points are\n\
+below %2.2g of the nearest %sfacet.\n",
+             qh_setsize(qh, facets), maxoutside, (qh->ONLYgood ?  "good " : ""));
+  FOREACHfacet_i_(qh, facets) {  /* for each point with facet assignment */
+    if (facet)
+      unassigned= False;
+    else {
+      unassigned= True;
+      facet= qh->facet_list;
+    }
+    point= qh_point(qh, facet_i);
+    if (point == qh->GOODpointp)
+      continue;
+    qh_distplane(qh, point, facet, &dist);
+    numpart++;
+    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
+    /* occurs after statistics reported */
+    maximize_(maxdist, dist);
+    if (dist > maxoutside) {
+      if (qh->ONLYgood && !bestfacet->good
+      && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
+      && dist > maxoutside))
+        notgood++;
+      else {
+        waserror= True;
+        qh_fprintf(qh, qh->ferr, 6109, "qhull precision error (qh_check_bestdist): point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
+                facet_i, bestfacet->id, dist, maxoutside);
+        if (errfacet1 != bestfacet) {
+          errfacet2= errfacet1;
+          errfacet1= bestfacet;
+        }
+      }
+    }else if (unassigned && dist < -qh->MAXcoplanar)
+      notverified++;
+  }
+  qh_settempfree(qh, &facets);
+  if (notverified && !qh->DELAUNAY && !qh_QUICKhelp && qh->PRINTprecision)
+    qh_fprintf(qh, qh->ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
+a lens-shaped component, these points were not verified.  Use\n\
+options 'Qci Tv' to verify all points.\n", notverified);
+  if (maxdist > qh->outside_err) {
+    qh_fprintf(qh, qh->ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value is qh.outside_err (%6.2g)\n",
+              maxdist, qh->outside_err);
+    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
+  }else if (waserror && qh->outside_err > REALmax/2)
+    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
+  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
+  trace0((qh, qh->ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
+} /* check_bestdist */
+
+#ifndef qh_NOmerge
+/*---------------------------------
+
+  qh_check_maxout(qh)
+    updates qh.max_outside by checking all points against bestfacet
+    if qh.ONLYgood, ignores !good facets
+
+  returns:
+    updates facet->maxoutside via qh_findbesthorizon()
+    sets qh.maxoutdone
+    if printing qh.min_vertex (qh_outerinner),
+      it is updated to the current vertices
+    removes inside/coplanar points from coplanarset as needed
+
+  notes:
+    defines coplanar as qh.min_vertex instead of qh.MAXcoplanar
+    may not need to check near-inside points because of qh.MAXcoplanar
+      and qh.KEEPnearinside (before it was -qh.DISTround)
+
+  see also:
+    qh_check_bestdist()
+
+  design:
+    if qh.min_vertex is needed
+      for all neighbors of all vertices
+        test distance from vertex to neighbor
+    determine facet for each point (if any)
+    for each point with an assigned facet
+      find the best facet for the point and check all coplanar facets
+        (updates outer planes)
+    remove near-inside points from coplanar sets
+*/
+void qh_check_maxout(qhT *qh) {
+  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist, *maxbestfacet= NULL, *minfacet, *maxfacet, *maxpointfacet;
+  realT dist, maxoutside, mindist, nearest;
+  realT maxoutside_base, minvertex_base;
+  pointT *point, *maxpoint= NULL;
+  int numpart= 0, facet_i, facet_n, notgood= 0;
+  setT *facets, *vertices;
+  vertexT *vertex, *minvertex;
+
+  trace1((qh, qh->ferr, 1022, "qh_check_maxout: check and update qh.min_vertex %2.2g and qh.max_outside %2.2g\n", qh->min_vertex, qh->max_outside));
+  minvertex_base= fmin_(qh->min_vertex, -(qh->ONEmerge+qh->DISTround));
+  maxoutside= mindist= 0.0;
+  minvertex= qh->vertex_list;
+  maxfacet= minfacet= maxpointfacet= qh->facet_list;
+  if (qh->VERTEXneighbors
+  && (qh->PRINTsummary || qh->KEEPinside || qh->KEEPcoplanar
+        || qh->TRACElevel || qh->PRINTstatistics || qh->VERIFYoutput || qh->CHECKfrequently
+        || qh->PRINTout[0] == qh_PRINTsummary || qh->PRINTout[0] == qh_PRINTnone)) {
+    trace1((qh, qh->ferr, 1023, "qh_check_maxout: determine actual minvertex\n"));
+    vertices= qh_pointvertex(qh /* qh.facet_list */);
+    FORALLvertices {
+      FOREACHneighbor_(vertex) {
+        zinc_(Zdistvertex);  /* distance also computed by main loop below */
+        qh_distplane(qh, vertex->point, neighbor, &dist);
+        if (dist < mindist) {
+          if (qh->min_vertex/minvertex_base > qh_WIDEmaxoutside && (qh->PRINTprecision || !qh->ALLOWwide)) {
+            nearest= qh_vertex_bestdist(qh, neighbor->vertices);
+            /* should be caught in qh_mergefacet */
+            qh_fprintf(qh, qh->ferr, 7083, "Qhull precision warning: in post-processing (qh_check_maxout) p%d(v%d) is %2.2g below f%d nearest vertices %2.2g\n",
+              qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id, nearest);
+          }
+          mindist= dist;
+          minvertex= vertex;
+          minfacet= neighbor;
+        }
+#ifndef qh_NOtrace
+        if (-dist > qh->TRACEdist || dist > qh->TRACEdist
+        || neighbor == qh->tracefacet || vertex == qh->tracevertex) {
+          nearest= qh_vertex_bestdist(qh, neighbor->vertices);
+          qh_fprintf(qh, qh->ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d nearest vertices %2.2g\n",
+                    qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id, nearest);
+        }
+#endif
+      }
+    }
+    if (qh->MERGING) {
+      wmin_(Wminvertex, qh->min_vertex);
+    }
+    qh->min_vertex= mindist;
+    qh_settempfree(qh, &vertices);
+  }
+  trace1((qh, qh->ferr, 1055, "qh_check_maxout: determine actual maxoutside\n"));
+  maxoutside_base= fmax_(qh->max_outside, qh->ONEmerge+qh->DISTround);
+  /* maxoutside_base is same as qh.MAXoutside without qh.MINoutside (qh_detmaxoutside) */
+  facets= qh_pointfacet(qh /* qh.facet_list */);
+  FOREACHfacet_i_(qh, facets) {     /* for each point with facet assignment */
+    if (facet) {
+      point= qh_point(qh, facet_i);
+      if (point == qh->GOODpointp)
+        continue;
+      zzinc_(Ztotcheck);
+      qh_distplane(qh, point, facet, &dist);
+      numpart++;
+      bestfacet= qh_findbesthorizon(qh, qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
+      if (bestfacet && dist >= maxoutside) {
+        if (qh->ONLYgood && !bestfacet->good
+        && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
+        && dist > maxoutside)) {
+          notgood++;
+        }else if (dist/maxoutside_base > qh_WIDEmaxoutside && (qh->PRINTprecision || !qh->ALLOWwide)) {
+          nearest= qh_vertex_bestdist(qh, bestfacet->vertices);
+          if (nearest < fmax_(qh->ONEmerge, qh->max_outside) * qh_RATIOcoplanaroutside * 2) {
+            qh_fprintf(qh, qh->ferr, 7087, "Qhull precision warning: in post-processing (qh_check_maxout) p%d for f%d is %2.2g above twisted facet f%d nearest vertices %2.2g\n",
+              qh_pointid(qh, point), facet->id, dist, bestfacet->id, nearest);
+          }else {
+            qh_fprintf(qh, qh->ferr, 7088, "Qhull precision warning: in post-processing (qh_check_maxout) p%d for f%d is %2.2g above hidden facet f%d nearest vertices %2.2g\n",
+              qh_pointid(qh, point), facet->id, dist, bestfacet->id, nearest);
+          }
+          maxbestfacet= bestfacet;
+        }
+        maxoutside= dist;
+        maxfacet= bestfacet;
+        maxpoint= point;
+        maxpointfacet= facet;
+      }
+      if (dist > qh->TRACEdist || (bestfacet && bestfacet == qh->tracefacet))
+        qh_fprintf(qh, qh->ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
+              qh_pointid(qh, point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
+    }
+  }
+  zzadd_(Zcheckpart, numpart);
+  qh_settempfree(qh, &facets);
+  wval_(Wmaxout)= maxoutside - qh->max_outside;
+  wmax_(Wmaxoutside, qh->max_outside);
+  if (!qh->APPROXhull && maxoutside > qh->DISTround) { /* initial value for f.maxoutside */
+    FORALLfacets {
+      if (maxoutside < facet->maxoutside) {
+        if (!qh->KEEPcoplanar) {
+          maxoutside= facet->maxoutside;
+        }else if (maxoutside + qh->DISTround < facet->maxoutside) { /* maxoutside is computed distance, e.g., rbox 100 s D3 t1547136913 | qhull R1e-3 Tcv Qc */
+          qh_fprintf(qh, qh->ferr, 7082, "Qhull precision warning (qh_check_maxout): f%d.maxoutside (%4.4g) is greater than computed qh.max_outside (%2.2g) + qh.DISTround (%2.2g).  It should be less than or equal\n",
+            facet->id, facet->maxoutside, maxoutside, qh->DISTround);
+        }
+      }
+    }
+  }
+  qh->max_outside= maxoutside;
+  qh_nearcoplanar(qh /* qh.facet_list */);
+  qh->maxoutdone= True;
+  trace1((qh, qh->ferr, 1024, "qh_check_maxout:  p%d(v%d) is qh.min_vertex %2.2g below facet f%d.  Point p%d for f%d is qh.max_outside %2.2g above f%d.  %d points are outside of not-good facets\n",
+    qh_pointid(qh, minvertex->point), minvertex->id, qh->min_vertex, minfacet->id, qh_pointid(qh, maxpoint), maxpointfacet->id, qh->max_outside, maxfacet->id, notgood));
+  if(!qh->ALLOWwide) {
+    if (maxoutside/maxoutside_base > qh_WIDEmaxoutside) {
+      qh_fprintf(qh, qh->ferr, 6297, "Qhull precision error (qh_check_maxout): large increase in qh.max_outside during post-processing dist %2.2g (%.1fx).  See warning QH0032/QH0033.  Allow with 'Q12' (allow-wide) and 'Pp'\n",
+        maxoutside, maxoutside/maxoutside_base);
+      qh_errexit(qh, qh_ERRwide, maxbestfacet, NULL);
+    }else if (!qh->APPROXhull && maxoutside_base > (qh->ONEmerge * qh_WIDEmaxoutside2)) {
+      if (maxoutside > (qh->ONEmerge * qh_WIDEmaxoutside2)) {  /* wide facets may have been deleted */
+        qh_fprintf(qh, qh->ferr, 6298, "Qhull precision error (qh_check_maxout): a facet merge, vertex merge, vertex, or coplanar point produced a wide facet %2.2g (%.1fx). Trace with option 'TWn' to identify the merge.   Allow with 'Q12' (allow-wide)\n",
+          maxoutside_base, maxoutside_base/(qh->ONEmerge + qh->DISTround));
+        qh_errexit(qh, qh_ERRwide, maxbestfacet, NULL);
+      }
+    }else if (qh->min_vertex/minvertex_base > qh_WIDEmaxoutside) {
+      qh_fprintf(qh, qh->ferr, 6354, "Qhull precision error (qh_check_maxout): large increase in qh.min_vertex during post-processing dist %2.2g (%.1fx).  See warning QH7083.  Allow with 'Q12' (allow-wide) and 'Pp'\n",
+        qh->min_vertex, qh->min_vertex/minvertex_base);
+      qh_errexit(qh, qh_ERRwide, minfacet, NULL);
+    }else if (minvertex_base < -(qh->ONEmerge * qh_WIDEmaxoutside2)) {
+      if (qh->min_vertex < -(qh->ONEmerge * qh_WIDEmaxoutside2)) {  /* wide facets may have been deleted */
+        qh_fprintf(qh, qh->ferr, 6380, "Qhull precision error (qh_check_maxout): a facet or vertex merge produced a wide facet: v%d below f%d distance %2.2g (%.1fx). Trace with option 'TWn' to identify the merge.  Allow with 'Q12' (allow-wide)\n",
+          minvertex->id, minfacet->id, mindist, -qh->min_vertex/(qh->ONEmerge + qh->DISTround));
+        qh_errexit(qh, qh_ERRwide, minfacet, NULL);
+      }
+    }
+  }
+} /* check_maxout */
+#else /* qh_NOmerge */
+void qh_check_maxout(qhT *qh) {
+  QHULL_UNUSED(qh)
+}
+#endif
+
+/*---------------------------------
+
+  qh_check_output(qh)
+    performs the checks at the end of qhull algorithm
+    Maybe called after Voronoi output.  If so, it recomputes centrums since they are Voronoi centers instead.
+*/
+void qh_check_output(qhT *qh) {
+  int i;
+
+  if (qh->STOPcone)
+    return;
+  if (qh->VERIFYoutput || qh->IStracing || qh->CHECKfrequently) {
+    qh_checkpolygon(qh, qh->facet_list);
+    qh_checkflipped_all(qh, qh->facet_list);
+    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
+  }else if (!qh->MERGING && qh_newstats(qh, qh->qhstat.precision, &i)) {
+    qh_checkflipped_all(qh, qh->facet_list);
+    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
+  }
+} /* check_output */
+
+
+
+/*---------------------------------
+
+  qh_check_point(qh, point, facet, maxoutside, maxdist, errfacet1, errfacet2, errcount )
+    check that point is less than maxoutside from facet
+
+  notes:
+    only called from qh_checkpoints
+    reports up to qh_MAXcheckpoint-1 errors per facet
+*/
+void qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2, int *errcount) {
+  realT dist, nearest;
+
+  /* occurs after statistics reported */
+  qh_distplane(qh, point, facet, &dist);
+  maximize_(*maxdist, dist);
+  if (dist > *maxoutside) {
+    (*errcount)++;
+    if (*errfacet1 != facet) {
+      *errfacet2= *errfacet1;
+      *errfacet1= facet;
+    }
+    if (*errcount < qh_MAXcheckpoint) {
+      nearest= qh_vertex_bestdist(qh, facet->vertices);
+      qh_fprintf(qh, qh->ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g nearest vertices %2.2g\n",
+                qh_pointid(qh, point), facet->id, dist, *maxoutside, nearest);
+    }
+  }
+} /* qh_check_point */
+
+
+/*---------------------------------
+
+  qh_check_points(qh)
+    checks that all points are inside all facets
+
+  notes:
+    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
+       calls qh_findbesthorizon via qh_check_bestdist (seldom done).
+    ignores flipped facets
+    maxoutside includes 2 qh.DISTrounds
+      one qh.DISTround for the computed distances in qh_check_points
+    qh_printafacet and qh_printsummary needs only one qh.DISTround
+    the computation for qh.VERIFYdirect does not account for qh.other_points
+
+  design:
+    if many points
+      use qh_check_bestdist()
+    else
+      for all facets
+        for all points
+          check that point is inside facet
+*/
+void qh_check_points(qhT *qh) {
+  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
+  realT total, maxoutside, maxdist= -REALmax;
+  pointT *point, **pointp, *pointtemp;
+  int errcount;
+  boolT testouter;
+
+  maxoutside= qh_maxouter(qh);
+  maxoutside += qh->DISTround;
+  /* one more qh.DISTround for check computation */
+  trace1((qh, qh->ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
+          maxoutside));
+  if (qh->num_good)   /* miss counts other_points and !good facets */
+     total= (realT)qh->num_good * (realT)qh->num_points;
+  else
+     total= (realT)qh->num_facets * (realT)qh->num_points;
+  if (total >= qh_VERIFYdirect && !qh->maxoutdone) {
+    if (!qh_QUICKhelp && qh->SKIPcheckmax && qh->MERGING)
+      qh_fprintf(qh, qh->ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').  Verify may report that a point is outside of a facet.\n");
+    qh_check_bestdist(qh);
+  }else {
+    if (qh_MAXoutside && qh->maxoutdone)
+      testouter= True;
+    else
+      testouter= False;
+    if (!qh_QUICKhelp) {
+      if (qh->MERGEexact)
+        qh_fprintf(qh, qh->ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point is outside of a facet.  See qh-optq.htm#Qx\n");
+      else if (qh->SKIPcheckmax || qh->NOnearinside)
+        qh_fprintf(qh, qh->ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of near-inside points ('Q8').  Verify may report that a point is outside of a facet.\n");
+    }
+    if (qh->PRINTprecision) {
+      if (testouter)
+        qh_fprintf(qh, qh->ferr, 8098, "\n\
+Output completed.  Verifying that all points are below outer planes of\n\
+all %sfacets.  Will make %2.0f distance computations.\n",
+              (qh->ONLYgood ?  "good " : ""), total);
+      else
+        qh_fprintf(qh, qh->ferr, 8099, "\n\
+Output completed.  Verifying that all points are below %2.2g of\n\
+all %sfacets.  Will make %2.0f distance computations.\n",
+              maxoutside, (qh->ONLYgood ?  "good " : ""), total);
+    }
+    FORALLfacets {
+      if (!facet->good && qh->ONLYgood)
+        continue;
+      if (facet->flipped)
+        continue;
+      if (!facet->normal) {
+        qh_fprintf(qh, qh->ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
+        if (!errfacet1)
+          errfacet1= facet;
+        continue;
+      }
+      if (testouter) {
+#if qh_MAXoutside
+        maxoutside= facet->maxoutside + 2 * qh->DISTround;
+        /* one DISTround to actual point and another to computed point */
+#endif
+      }
+      errcount= 0;
+      FORALLpoints {
+        if (point != qh->GOODpointp)
+          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2, &errcount);
+      }
+      FOREACHpoint_(qh->other_points) {
+        if (point != qh->GOODpointp)
+          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2, &errcount);
+      }
+      if (errcount >= qh_MAXcheckpoint) {
+        qh_fprintf(qh, qh->ferr, 6422, "qhull precision error (qh_check_points): %d additional points outside facet f%d, maxdist= %6.8g\n",
+             errcount-qh_MAXcheckpoint+1, facet->id, maxdist);
+      }
+    }
+    if (maxdist > qh->outside_err) {
+      qh_fprintf(qh, qh->ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
+                maxdist, qh->outside_err );
+      qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
+    }else if (errfacet1 && qh->outside_err > REALmax/2)
+        qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
+    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
+    trace0((qh, qh->ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
+  }
+} /* check_points */
+
+
+/*---------------------------------
+
+  qh_checkconvex(qh, facetlist, fault )
+    check that each ridge in facetlist is convex
+    fault = qh_DATAfault if reporting errors from qh_initialhull with qh.ZEROcentrum
+          = qh_ALGORITHMfault otherwise
+
+  returns:
+    counts Zconcaveridges and Zcoplanarridges
+    errors if !qh.FORCEoutput ('Fo') and concaveridge or if merging a coplanar ridge
+    overwrites Voronoi centers if set by qh_setvoronoi_all/qh_ASvoronoi
+
+  notes:
+    called by qh_initial_hull, qh_check_output, qh_all_merges ('Tc'), qh_build_withrestart ('QJ')
+    does not test f.tricoplanar facets (qh_triangulate)
+    must be no stronger than qh_test_appendmerge
+    if not merging,
+      tests vertices for neighboring simplicial facets < -qh.DISTround
+    else if ZEROcentrum and simplicial facet,
+      tests vertices for neighboring simplicial facets < 0.0
+      tests centrums of neighboring nonsimplicial facets < 0.0
+    else if ZEROcentrum
+      tests centrums of neighboring facets < 0.0
+    else
+      tests centrums of neighboring facets < -qh.DISTround ('En' 'Rn')
+    Does not test against -qh.centrum_radius since repeated computations may have different round-off errors (e.g., 'Rn')
+
+  design:
+    for all facets
+      report flipped facets
+      if ZEROcentrum and simplicial neighbors
+        test vertices against neighbor
+      else
+        test centrum against neighbor
+*/
+void qh_checkconvex(qhT *qh, facetT *facetlist, int fault) {
+  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
+  vertexT *vertex;
+  realT dist;
+  pointT *centrum;
+  boolT waserror= False, centrum_warning= False, tempcentrum= False, first_nonsimplicial= False, tested_simplicial, allsimplicial;
+  int neighbor_i, neighbor_n;
+
+  if (qh->ZEROcentrum) {
+    trace1((qh, qh->ferr, 1064, "qh_checkconvex: check that facets are not-flipped and for qh.ZEROcentrum that simplicial vertices are below their neighbor (dist<0.0)\n"));
+    first_nonsimplicial= True;
+  }else if (!qh->MERGING) {
+    trace1((qh, qh->ferr, 1026, "qh_checkconvex: check that facets are not-flipped and that simplicial vertices are convex by qh.DISTround ('En', 'Rn')\n"));
+    first_nonsimplicial= True;
+  }else
+    trace1((qh, qh->ferr, 1062, "qh_checkconvex: check that facets are not-flipped and that their centrums are convex by qh.DISTround ('En', 'Rn') \n"));
+  if (!qh->RERUN) {
+    zzval_(Zconcaveridges)= 0;
+    zzval_(Zcoplanarridges)= 0;
+  }
+  FORALLfacet_(facetlist) {
+    if (facet->flipped) {
+      qh_joggle_restart(qh, "flipped facet"); /* also tested by qh_checkflipped */
+      qh_fprintf(qh, qh->ferr, 6113, "qhull precision error: f%d is flipped (interior point is outside)\n",
+               facet->id);
+      errfacet1= facet;
+      waserror= True;
+      continue;
+    }
+    if (facet->tricoplanar)
+      continue;
+    if (qh->MERGING && (!qh->ZEROcentrum || !facet->simplicial)) {
+      allsimplicial= False;
+      tested_simplicial= False;
+    }else {
+      allsimplicial= True;
+      tested_simplicial= True;
+      FOREACHneighbor_i_(qh, facet) {
+        if (neighbor->tricoplanar)
+          continue;
+        if (!neighbor->simplicial) {
+          allsimplicial= False;
+          continue;
+        }
+        vertex= SETelemt_(facet->vertices, neighbor_i, vertexT);
+        qh_distplane(qh, vertex->point, neighbor, &dist);
+        if (dist >= -qh->DISTround) {
+          if (fault == qh_DATAfault) {
+            qh_joggle_restart(qh, "non-convex initial simplex");
+            if (dist > qh->DISTround)
+              qh_fprintf(qh, qh->ferr, 6114, "qhull precision error: initial simplex is not convex, since p%d(v%d) is %6.4g above opposite f%d\n",
+                  qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
+            else
+              qh_fprintf(qh, qh->ferr, 6379, "qhull precision error: initial simplex is not convex, since p%d(v%d) is within roundoff of opposite facet f%d (dist %6.4g)\n",
+                  qh_pointid(qh, vertex->point), vertex->id, neighbor->id, dist);
+            qh_errexit(qh, qh_ERRsingular, neighbor, NULL);
+          }
+          if (dist > qh->DISTround) {
+            zzinc_(Zconcaveridges);
+            qh_joggle_restart(qh, "concave ridge");
+            qh_fprintf(qh, qh->ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above f%d\n",
+              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
+            errfacet1= facet;
+            errfacet2= neighbor;
+            waserror= True;
+          }else if (qh->ZEROcentrum) {
+            if (dist > 0.0) {     /* qh_checkzero checked convex (dist < (- 2*qh->DISTround)), computation may differ e.g. 'Rn' */
+              zzinc_(Zcoplanarridges);
+              qh_joggle_restart(qh, "coplanar ridge");
+              qh_fprintf(qh, qh->ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above or coplanar with f%d with qh.ZEROcentrum\n",
+                facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
+              errfacet1= facet;
+              errfacet2= neighbor;
+              waserror= True;
+            }
+          }else {
+            zzinc_(Zcoplanarridges);
+            qh_joggle_restart(qh, "coplanar ridge");
+            trace0((qh, qh->ferr, 22, "qhull precision error: f%d is coplanar to f%d, since p%d(v%d) is within %6.4g of f%d, during p%d\n",
+              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id, qh->furthest_id));
+          }
+        }
+      }
+    }
+    if (!allsimplicial) {
+      if (first_nonsimplicial) {
+        trace1((qh, qh->ferr, 1063, "qh_checkconvex: starting with f%d, also check that centrums of non-simplicial ridges are below their neighbors (dist<0.0)\n",
+             facet->id));
+        first_nonsimplicial= False;
+      }
+      if (qh->CENTERtype == qh_AScentrum) {
+        if (!facet->center)
+          facet->center= qh_getcentrum(qh, facet);
+        centrum= facet->center;
+      }else {
+        if (!centrum_warning && !facet->simplicial) {  /* recomputed centrum correct for simplicial facets */
+           centrum_warning= True;
+           qh_fprintf(qh, qh->ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
+        }
+        centrum= qh_getcentrum(qh, facet);
+        tempcentrum= True;
+      }
+      FOREACHneighbor_(facet) {
+        if (neighbor->simplicial && tested_simplicial) /* tested above since f.simplicial */
+          continue;
+        if (neighbor->tricoplanar)
+          continue;
+        zzinc_(Zdistconvex);
+        qh_distplane(qh, centrum, neighbor, &dist);
+        if (dist > qh->DISTround) {
+          zzinc_(Zconcaveridges);
+          qh_joggle_restart(qh, "concave ridge");
+          qh_fprintf(qh, qh->ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
+            facet->id, neighbor->id, facet->id, dist, neighbor->id);
+          errfacet1= facet;
+          errfacet2= neighbor;
+          waserror= True;
+        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
+                                     can test against centrum radius instead */
+          zzinc_(Zcoplanarridges);
+          qh_joggle_restart(qh, "coplanar ridge");
+          qh_fprintf(qh, qh->ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
+            facet->id, neighbor->id, facet->id, dist, neighbor->id);
+          errfacet1= facet;
+          errfacet2= neighbor;
+          waserror= True;
+        }
+      }
+      if (tempcentrum)
+        qh_memfree(qh, centrum, qh->normal_size);
+    }
+  }
+  if (waserror && !qh->FORCEoutput)
+    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
+} /* checkconvex */
+
+
+/*---------------------------------
+
+  qh_checkfacet(qh, facet, newmerge, waserror )
+    checks for consistency errors in facet
+    newmerge set if from merge_r.c
+
+  returns:
+    sets waserror if any error occurs
+
+  checks:
+    vertex ids are inverse sorted
+    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
+    if non-simplicial, at least as many ridges as neighbors
+    neighbors are not duplicated
+    ridges are not duplicated
+    in 3-d, ridges=verticies
+    (qh.hull_dim-1) ridge vertices
+    neighbors are reciprocated
+    ridge neighbors are facet neighbors and a ridge for every neighbor
+    simplicial neighbors match facetintersect
+    vertex intersection matches vertices of common ridges
+    vertex neighbors and facet vertices agree
+    all ridges have distinct vertex sets
+
+  notes:
+    called by qh_tracemerge and qh_checkpolygon
+    uses neighbor->seen
+
+  design:
+    check sets
+    check vertices
+    check sizes of neighbors and vertices
+    check for qh_MERGEridge and qh_DUPLICATEridge flags
+    check neighbor set
+    check ridge set
+    check ridges, neighbors, and vertices
+*/
+void qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp) {
+  facetT *neighbor, **neighborp, *errother=NULL;
+  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
+  vertexT *vertex, **vertexp;
+  unsigned int previousid= INT_MAX;
+  int numneighbors, numvertices, numridges=0, numRvertices=0;
+  boolT waserror= False;
+  int skipA, skipB, ridge_i, ridge_n, i, last_v= qh->hull_dim-2;
+  setT *intersection;
+
+  trace4((qh, qh->ferr, 4088, "qh_checkfacet: check f%d newmerge? %d\n", facet->id, newmerge));
+  if (facet->id >= qh->facet_id) {
+    qh_fprintf(qh, qh->ferr, 6414, "qhull internal error (qh_checkfacet): unknown facet id f%d >= qh.facet_id (%d)\n", facet->id, qh->facet_id);
+    waserror= True;
+  }
+  if (facet->visitid > qh->visit_id) {
+    qh_fprintf(qh, qh->ferr, 6415, "qhull internal error (qh_checkfacet): expecting f%d.visitid <= qh.visit_id (%d).  Got visitid %d\n", facet->id, qh->visit_id, facet->visitid);
+    waserror= True;
+  }
+  if (facet->visible && !qh->NEWtentative) {
+    qh_fprintf(qh, qh->ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on qh.visible_list\n",
+      facet->id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  if (facet->redundant && !facet->visible && qh_setsize(qh, qh->degen_mergeset)==0) {
+    qh_fprintf(qh, qh->ferr, 6399, "qhull internal error (qh_checkfacet): redundant facet f%d not on qh.visible_list\n",
+      facet->id);
+    waserror= True;
+  }
+  if (facet->degenerate && !facet->visible && qh_setsize(qh, qh->degen_mergeset)==0) {
+    qh_fprintf(qh, qh->ferr, 6400, "qhull internal error (qh_checkfacet): degenerate facet f%d is not on qh.visible_list and qh.degen_mergeset is empty\n",
+      facet->id);
+    waserror= True;
+  }
+  if (!facet->normal) {
+    qh_fprintf(qh, qh->ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have a normal\n",
+      facet->id);
+    waserror= True;
+  }
+  if (!facet->newfacet) {
+    if (facet->dupridge) {
+      qh_fprintf(qh, qh->ferr, 6349, "qhull internal error (qh_checkfacet): f%d is 'dupridge' but it is not a newfacet on qh.newfacet_list f%d\n",
+        facet->id, getid_(qh->newfacet_list));
+      waserror= True;
+    }
+    if (facet->newmerge) {
+      qh_fprintf(qh, qh->ferr, 6383, "qhull internal error (qh_checkfacet): f%d is 'newmerge' but it is not a newfacet on qh.newfacet_list f%d.  Missing call to qh_reducevertices\n",
+        facet->id, getid_(qh->newfacet_list));
+      waserror= True;
+    }
+  }
+  qh_setcheck(qh, facet->vertices, "vertices for f", facet->id);
+  qh_setcheck(qh, facet->ridges, "ridges for f", facet->id);
+  qh_setcheck(qh, facet->outsideset, "outsideset for f", facet->id);
+  qh_setcheck(qh, facet->coplanarset, "coplanarset for f", facet->id);
+  qh_setcheck(qh, facet->neighbors, "neighbors for f", facet->id);
+  FOREACHvertex_(facet->vertices) {
+    if (vertex->deleted) {
+      qh_fprintf(qh, qh->ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
+      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
+      waserror= True;
+    }
+    if (vertex->id >= previousid) {
+      qh_fprintf(qh, qh->ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
+      waserror= True;
+      break;
+    }
+    previousid= vertex->id;
+  }
+  numneighbors= qh_setsize(qh, facet->neighbors);
+  numvertices= qh_setsize(qh, facet->vertices);
+  numridges= qh_setsize(qh, facet->ridges);
+  if (facet->simplicial) {
+    if (numvertices+numneighbors != 2*qh->hull_dim
+    && !facet->degenerate && !facet->redundant) {
+      qh_fprintf(qh, qh->ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh->hull_dim\n",
+                facet->id, numvertices, numneighbors);
+      qh_setprint(qh, qh->ferr, "", facet->neighbors);
+      waserror= True;
+    }
+  }else { /* non-simplicial */
+    if (!newmerge
+    &&(numvertices < qh->hull_dim || numneighbors < qh->hull_dim)
+    && !facet->degenerate && !facet->redundant) {
+      qh_fprintf(qh, qh->ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh->hull_dim\n",
+         facet->id, numvertices, numneighbors);
+       waserror= True;
+    }
+    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
+    if (numridges < numneighbors
+    ||(qh->hull_dim == 3 && numvertices > numridges && !qh->NEWfacets)
+    ||(qh->hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
+      if (!facet->degenerate && !facet->redundant) {
+        qh_fprintf(qh, qh->ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
+            facet->id, numridges, numneighbors, numvertices);
+        waserror= True;
+      }
+    }
+  }
+  FOREACHneighbor_(facet) {
+    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
+      qh_fprintf(qh, qh->ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGEridge or DUPLICATEridge neighbor\n", facet->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    if (neighbor->visible) {
+      qh_fprintf(qh, qh->ferr, 6401, "qhull internal error (qh_checkfacet): facet f%d has deleted neighbor f%d (qh.visible_list)\n",
+        facet->id, neighbor->id);
+      errother= neighbor;
+      waserror= True;
+    }
+    neighbor->seen= True;
+  }
+  FOREACHneighbor_(facet) {
+    if (!qh_setin(neighbor->neighbors, facet)) {
+      qh_fprintf(qh, qh->ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
+              facet->id, neighbor->id, neighbor->id, facet->id);
+      errother= neighbor;
+      waserror= True;
+    }
+    if (!neighbor->seen) {
+      qh_fprintf(qh, qh->ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
+              facet->id, neighbor->id);
+      errother= neighbor;
+      waserror= True;
+    }
+    neighbor->seen= False;
+  }
+  FOREACHridge_(facet->ridges) {
+    qh_setcheck(qh, ridge->vertices, "vertices for r", ridge->id);
+    ridge->seen= False;
+  }
+  FOREACHridge_(facet->ridges) {
+    if (ridge->seen) {
+      qh_fprintf(qh, qh->ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
+              facet->id, ridge->id);
+      errridge= ridge;
+      waserror= True;
+    }
+    ridge->seen= True;
+    numRvertices= qh_setsize(qh, ridge->vertices);
+    if (numRvertices != qh->hull_dim - 1) {
+      qh_fprintf(qh, qh->ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
+                ridge->top->id, ridge->bottom->id, numRvertices);
+      errridge= ridge;
+      waserror= True;
+    }
+    neighbor= otherfacet_(ridge, facet);
+    neighbor->seen= True;
+    if (!qh_setin(facet->neighbors, neighbor)) {
+      qh_fprintf(qh, qh->ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
+           facet->id, neighbor->id, ridge->id);
+      errridge= ridge;
+      waserror= True;
+    }
+    if (!facet->newfacet && !neighbor->newfacet) {
+      if ((!ridge->tested) | ridge->nonconvex | ridge->mergevertex) {
+        qh_fprintf(qh, qh->ferr, 6384, "qhull internal error (qh_checkfacet): ridge r%d is nonconvex (%d), mergevertex (%d) or not tested (%d) for facet f%d, neighbor f%d\n",
+          ridge->id, ridge->nonconvex, ridge->mergevertex, ridge->tested, facet->id, neighbor->id);
+        errridge= ridge;
+        waserror= True;
+      }
+    }
+  }
+  if (!facet->simplicial) {
+    FOREACHneighbor_(facet) {
+      if (!neighbor->seen) {
+        qh_fprintf(qh, qh->ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
+              facet->id, neighbor->id);
+        errother= neighbor;
+        waserror= True;
+      }
+      intersection= qh_vertexintersect_new(qh, facet->vertices, neighbor->vertices);
+      qh_settemppush(qh, intersection);
+      FOREACHvertex_(facet->vertices) {
+        vertex->seen= False;
+        vertex->seen2= False;
+      }
+      FOREACHvertex_(intersection)
+        vertex->seen= True;
+      FOREACHridge_(facet->ridges) {
+        if (neighbor != otherfacet_(ridge, facet))
+            continue;
+        FOREACHvertex_(ridge->vertices) {
+          if (!vertex->seen) {
+            qh_fprintf(qh, qh->ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
+                  vertex->id, ridge->id, facet->id, neighbor->id);
+            qh_errexit(qh, qh_ERRqhull, facet, ridge);
+          }
+          vertex->seen2= True;
+        }
+      }
+      if (!newmerge) {
+        FOREACHvertex_(intersection) {
+          if (!vertex->seen2) {
+            if (!qh->MERGING) {
+              qh_fprintf(qh, qh->ferr, 6420, "qhull topology error (qh_checkfacet): vertex v%d in f%d intersect f%d but not in a ridge.  Last point was p%d\n",
+                     vertex->id, facet->id, neighbor->id, qh->furthest_id);
+              if (!qh->FORCEoutput) {
+                qh_errprint(qh, "ERRONEOUS", facet, neighbor, NULL, vertex);
+                qh_errexit(qh, qh_ERRtopology, NULL, NULL);
+              }
+            }else {
+              trace4((qh, qh->ferr, 4025, "qh_checkfacet: vertex v%d in f%d intersect f%d but not in a ridge.  Repaired by qh_remove_extravertices in qh_reducevertices\n",
+                vertex->id, facet->id, neighbor->id));
+            }
+          }
+        }
+      }
+      qh_settempfree(qh, &intersection);
+    }
+  }else { /* simplicial */
+    FOREACHneighbor_(facet) {
+      if (neighbor->simplicial && !facet->degenerate && !neighbor->degenerate) {
+        skipA= SETindex_(facet->neighbors, neighbor);
+        skipB= qh_setindex(neighbor->neighbors, facet);
+        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
+          qh_fprintf(qh, qh->ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
+                   facet->id, skipA, neighbor->id, skipB);
+          errother= neighbor;
+          waserror= True;
+        }
+      }
+    }
+  }
+  if (!newmerge && qh->CHECKduplicates && qh->hull_dim < 5 && (qh->IStracing > 2 || qh->CHECKfrequently)) {
+    FOREACHridge_i_(qh, facet->ridges) {           /* expensive, if was merge and qh_maybe_duplicateridges hasn't been called yet */
+      if (!ridge->mergevertex) {
+        for (i=ridge_i+1; i < ridge_n; i++) {
+          ridge2= SETelemt_(facet->ridges, i, ridgeT);
+          if (SETelem_(ridge->vertices, last_v) == SETelem_(ridge2->vertices, last_v)) { /* SETfirst is likely to be the same */
+            if (SETfirst_(ridge->vertices) == SETfirst_(ridge2->vertices)) {
+              if (qh_setequal(ridge->vertices, ridge2->vertices)) {
+                qh_fprintf(qh, qh->ferr, 6294, "qhull internal error (qh_checkfacet): ridges r%d and r%d (f%d) have the same vertices\n", /* same as duplicate ridge */
+                    ridge->id, ridge2->id, facet->id);
+                errridge= ridge;
+                waserror= True;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  if (waserror) {
+    qh_errprint(qh, "ERRONEOUS", facet, errother, errridge, NULL);
+    *waserrorp= True;
+  }
+} /* checkfacet */
+
+/*---------------------------------
+
+  qh_checkflipped_all(qh, facetlist )
+    checks orientation of facets in list against interior point
+
+  notes:
+    called by qh_checkoutput
+*/
+void qh_checkflipped_all(qhT *qh, facetT *facetlist) {
+  facetT *facet;
+  boolT waserror= False;
+  realT dist;
+
+  if (facetlist == qh->facet_list)
+    zzval_(Zflippedfacets)= 0;
+  FORALLfacet_(facetlist) {
+    if (facet->normal && !qh_checkflipped(qh, facet, &dist, !qh_ALL)) {
+      qh_fprintf(qh, qh->ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
+              facet->id, dist);
+      if (!qh->FORCEoutput) {
+        qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, NULL);
+        waserror= True;
+      }
+    }
+  }
+  if (waserror) {
+    qh_fprintf(qh, qh->ferr, 8101, "\n\
+A flipped facet occurs when its distance to the interior point is\n\
+greater than or equal to %2.2g, the maximum roundoff error.\n", -qh->DISTround);
+    qh_errexit(qh, qh_ERRprec, NULL, NULL);
+  }
+} /* checkflipped_all */
+
+/*---------------------------------
+
+  qh_checklists(qh, facetlist )
+    Check and repair facetlist and qh.vertex_list for infinite loops or overwritten facets
+    Checks that qh.newvertex_list is on qh.vertex_list
+    if facetlist is qh.facet_list
+      Checks that qh.visible_list and qh.newfacet_list are on qh.facet_list
+    Updates qh.facetvisit and qh.vertexvisit
+
+  returns:
+    True if no errors found
+    If false, repairs erroneous lists to prevent infinite loops by FORALL macros
+
+  notes:
+    called by qh_buildtracing, qh_checkpolygon, qh_collectstatistics, qh_printfacetlist, qh_printsummary
+    not called by qh_printlists
+
+  design:
+    if facetlist
+      check qh.facet_tail
+      for each facet
+        check for infinite loop or overwritten facet
+        check previous facet
+      if facetlist is qh.facet_list
+        check qh.next_facet, qh.visible_list and qh.newfacet_list
+    if vertexlist
+      check qh.vertex_tail
+      for each vertex
+        check for infinite loop or overwritten vertex
+        check previous vertex
+      check qh.newvertex_list
+*/
+boolT qh_checklists(qhT *qh, facetT *facetlist) {
+  facetT *facet, *errorfacet= NULL, *errorfacet2= NULL, *previousfacet;
+  vertexT *vertex, *vertexlist, *previousvertex, *errorvertex= NULL;
+  boolT waserror= False, newseen= False, nextseen= False, newvertexseen= False, visibleseen= False;
+
+  if (facetlist == qh->newfacet_list || facetlist == qh->visible_list) {
+    vertexlist= qh->vertex_list;
+    previousvertex= NULL;
+    trace2((qh, qh->ferr, 2110, "qh_checklists: check qh.%s_list f%d and qh.vertex_list v%d\n",
+        (facetlist == qh->newfacet_list ? "newfacet" : "visible"), facetlist->id, getid_(vertexlist)));
+  }else {
+    vertexlist= qh->vertex_list;
+    previousvertex= NULL;
+    trace2((qh, qh->ferr, 2111, "qh_checklists: check %slist f%d and qh.vertex_list v%d\n",
+        (facetlist == qh->facet_list ? "qh.facet_" : "facet"), getid_(facetlist), getid_(vertexlist)));
+  }
+  if (facetlist) {
+    if (qh->facet_tail == NULL || qh->facet_tail->id != 0 || qh->facet_tail->next != NULL) {
+      qh_fprintf(qh, qh->ferr, 6397, "qhull internal error (qh_checklists): either qh.facet_tail f%d is NULL, or its id is not 0, or its next is not NULL\n",
+          getid_(qh->facet_tail));
+      qh_errexit(qh, qh_ERRqhull, qh->facet_tail, NULL);
+    }
+    previousfacet= (facetlist == qh->facet_list ? NULL : facetlist->previous);
+    qh->visit_id++;
+    FORALLfacet_(facetlist) {
+      if (facet->visitid >= qh->visit_id || facet->id >= qh->facet_id) {
+        waserror= True;
+        errorfacet= facet;
+        errorfacet2= previousfacet;
+        if (facet->visitid == qh->visit_id)
+          qh_fprintf(qh, qh->ferr, 6039, "qhull internal error (qh_checklists): f%d already in facetlist causing an infinite loop ... f%d > f%d ... > f%d > f%d.  Truncate facetlist at f%d\n",
+            facet->id, facet->id, facet->next->id, getid_(previousfacet), facet->id, getid_(previousfacet));
+        else
+          qh_fprintf(qh, qh->ferr, 6350, "qhull internal error (qh_checklists): unknown or overwritten facet f%d, either id >= qh.facet_id (%d) or f.visitid %u > qh.visit_id %u.  Facetlist terminated at previous facet f%d\n",
+              facet->id, qh->facet_id, facet->visitid, qh->visit_id, getid_(previousfacet));
+        if (previousfacet)
+          previousfacet->next= qh->facet_tail;
+        else
+          facetlist= qh->facet_tail;
+        break;
+      }
+      facet->visitid= qh->visit_id;
+      if (facet->previous != previousfacet) {
+        qh_fprintf(qh, qh->ferr, 6416, "qhull internal error (qh_checklists): expecting f%d.previous == f%d.  Got f%d\n",
+          facet->id, getid_(previousfacet), getid_(facet->previous));
+        waserror= True;
+        errorfacet= facet;
+        errorfacet2= facet->previous;
+      }
+      previousfacet= facet;
+      if (facetlist == qh->facet_list) {
+        if (facet == qh->visible_list) {
+          if(newseen){
+            qh_fprintf(qh, qh->ferr, 6285, "qhull internal error (qh_checklists): qh.visible_list f%d is after qh.newfacet_list f%d.  It should be at, before, or NULL\n",
+              facet->id, getid_(qh->newfacet_list));
+            waserror= True;
+            errorfacet= facet;
+            errorfacet2= qh->newfacet_list;
+          }
+          visibleseen= True;
+        }
+        if (facet == qh->newfacet_list)
+          newseen= True;
+        if (facet == qh->facet_next)
+          nextseen= True;
+      }
+    }
+    if (facetlist == qh->facet_list) {
+      if (!nextseen && qh->facet_next && qh->facet_next->next) {
+        qh_fprintf(qh, qh->ferr, 6369, "qhull internal error (qh_checklists): qh.facet_next f%d for qh_addpoint is not on qh.facet_list f%d\n",
+          qh->facet_next->id, facetlist->id);
+        waserror= True;
+        errorfacet= qh->facet_next;
+        errorfacet2= facetlist;
+      }
+      if (!newseen && qh->newfacet_list && qh->newfacet_list->next) {
+        qh_fprintf(qh, qh->ferr, 6286, "qhull internal error (qh_checklists): qh.newfacet_list f%d is not on qh.facet_list f%d\n",
+          qh->newfacet_list->id, facetlist->id);
+        waserror= True;
+        errorfacet= qh->newfacet_list;
+        errorfacet2= facetlist;
+      }
+      if (!visibleseen && qh->visible_list && qh->visible_list->next) {
+        qh_fprintf(qh, qh->ferr, 6138, "qhull internal error (qh_checklists): qh.visible_list f%d is not on qh.facet_list f%d\n",
+          qh->visible_list->id, facetlist->id);
+        waserror= True;
+        errorfacet= qh->visible_list;
+        errorfacet2= facetlist;
+      }
+    }
+  }
+  if (vertexlist) {
+    if (qh->vertex_tail == NULL || qh->vertex_tail->id != 0 || qh->vertex_tail->next != NULL) {
+      qh_fprintf(qh, qh->ferr, 6366, "qhull internal error (qh_checklists): either qh.vertex_tail v%d is NULL, or its id is not 0, or its next is not NULL\n",
+           getid_(qh->vertex_tail));
+      qh_errprint(qh, "ERRONEOUS", errorfacet, errorfacet2, NULL, qh->vertex_tail);
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+    }
+    qh->vertex_visit++;
+    FORALLvertex_(vertexlist) {
+      if (vertex->visitid >= qh->vertex_visit || vertex->id >= qh->vertex_id) {
+        waserror= True;
+        errorvertex= vertex;
+        if (vertex->visitid == qh->visit_id)
+          qh_fprintf(qh, qh->ferr, 6367, "qhull internal error (qh_checklists): v%d already in vertexlist causing an infinite loop ... v%d > v%d ... > v%d > v%d.  Truncate vertexlist at v%d\n",
+            vertex->id, vertex->id, vertex->next->id, getid_(previousvertex), vertex->id, getid_(previousvertex));
+        else
+          qh_fprintf(qh, qh->ferr, 6368, "qhull internal error (qh_checklists): unknown or overwritten vertex v%d, either id >= qh.vertex_id (%d) or v.visitid %u > qh.visit_id %u.  vertexlist terminated at previous vertex v%d\n",
+            vertex->id, qh->vertex_id, vertex->visitid, qh->visit_id, getid_(previousvertex));
+        if (previousvertex)
+          previousvertex->next= qh->vertex_tail;
+        else
+          vertexlist= qh->vertex_tail;
+        break;
+      }
+      vertex->visitid= qh->vertex_visit;
+      if (vertex->previous != previousvertex) {
+        qh_fprintf(qh, qh->ferr, 6427, "qhull internal error (qh_checklists): expecting v%d.previous == v%d.  Got v%d\n",
+              vertex->id, getid_(previousvertex), getid_(vertex->previous));
+        waserror= True;
+        errorvertex= vertex;
+      }
+      previousvertex= vertex;
+      if(vertex == qh->newvertex_list)
+        newvertexseen= True;
+    }
+    if(!newvertexseen && qh->newvertex_list && qh->newvertex_list->next) {
+      qh_fprintf(qh, qh->ferr, 6287, "qhull internal error (qh_checklists): new vertex list v%d is not on vertex list\n", qh->newvertex_list->id);
+      waserror= True;
+      errorvertex= qh->newvertex_list;
+    }
+  }
+  if (waserror) {
+    qh_errprint(qh, "ERRONEOUS", errorfacet, errorfacet2, NULL, errorvertex);
+    return False;
+  }
+  return True;
+} /* checklists */
+
+/*---------------------------------
+
+  qh_checkpolygon(qh, facetlist )
+    checks the correctness of the structure
+
+  notes:
+    called by qh_addpoint, qh_all_vertexmerge, qh_check_output, qh_initialhull, qh_prepare_output, qh_triangulate
+    call with qh.facet_list or qh.newfacet_list or another list
+    checks num_facets and num_vertices if qh.facet_list
+
+  design:
+    check and repair lists for infinite loop
+    for each facet
+      check f.newfacet and f.visible
+      check facet and outside set if qh.NEWtentative and not f.newfacet, or not f.visible
+    initializes vertexlist for qh.facet_list or qh.newfacet_list
+    for each vertex
+      check vertex
+      check v.newfacet
+    for each facet
+      count f.ridges
+      check and count f.vertices
+    if checking qh.facet_list
+      check facet count
+      if qh.VERTEXneighbors
+        check and count v.neighbors for all vertices
+        check v.neighbors count and report possible causes of mismatch
+        check that facets are in their v.neighbors
+      check vertex count
+*/
+void qh_checkpolygon(qhT *qh, facetT *facetlist) {
+  facetT *facet, *neighbor, **neighborp;
+  facetT *errorfacet= NULL, *errorfacet2= NULL;
+  vertexT *vertex, **vertexp, *vertexlist;
+  int numfacets= 0, numvertices= 0, numridges= 0;
+  int totvneighbors= 0, totfacetvertices= 0;
+  boolT waserror= False, newseen= False, newvertexseen= False, nextseen= False, visibleseen= False;
+  boolT checkfacet;
+
+  trace1((qh, qh->ferr, 1027, "qh_checkpolygon: check all facets from f%d, qh.NEWtentative? %d\n", facetlist->id, qh->NEWtentative));
+  if (!qh_checklists(qh, facetlist)) {
+    waserror= True;
+    qh_fprintf(qh, qh->ferr, 6374, "qhull internal error: qh_checklists failed in qh_checkpolygon\n");
+    if (qh->num_facets < 4000)
+      qh_printlists(qh);
+  }
+  if (facetlist != qh->facet_list || qh->ONLYgood)
+    nextseen= True; /* allow f.outsideset */
+  FORALLfacet_(facetlist) {
+    if (facet == qh->visible_list)
+      visibleseen= True;
+    if (facet == qh->newfacet_list)
+      newseen= True;
+    if (facet->newfacet && !newseen && !visibleseen) {
+        qh_fprintf(qh, qh->ferr, 6289, "qhull internal error (qh_checkpolygon): f%d is 'newfacet' but it is not on qh.newfacet_list f%d or visible_list f%d\n",  facet->id, getid_(qh->newfacet_list), getid_(qh->visible_list));
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    if (!facet->newfacet && newseen) {
+        qh_fprintf(qh, qh->ferr, 6292, "qhull internal error (qh_checkpolygon): f%d is on qh.newfacet_list f%d but it is not 'newfacet'\n",  facet->id, getid_(qh->newfacet_list));
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    if (facet->visible != (visibleseen & !newseen)) {
+      if(facet->visible)
+        qh_fprintf(qh, qh->ferr, 6290, "qhull internal error (qh_checkpolygon): f%d is 'visible' but it is not on qh.visible_list f%d\n", facet->id, getid_(qh->visible_list));
+      else
+        qh_fprintf(qh, qh->ferr, 6291, "qhull internal error (qh_checkpolygon): f%d is on qh.visible_list f%d but it is not 'visible'\n", facet->id, qh->newfacet_list->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    if (qh->NEWtentative) {
+      checkfacet= !facet->newfacet;
+    }else {
+      checkfacet= !facet->visible;
+    }
+    if(checkfacet) {
+      if (!nextseen) {
+        if (facet == qh->facet_next)  /* previous facets do not have outsideset */
+          nextseen= True;
+        else if (qh_setsize(qh, facet->outsideset)) {
+          if (!qh->NARROWhull
+#if !qh_COMPUTEfurthest
+          || facet->furthestdist >= qh->MINoutside
+#endif
+          ) {
+            qh_fprintf(qh, qh->ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh.facet_next f%d\n",
+                     facet->id, getid_(qh->facet_next));
+            qh_errexit2(qh, qh_ERRqhull, facet, qh->facet_next);
+          }
+        }
+      }
+      numfacets++;
+      qh_checkfacet(qh, facet, False, &waserror);
+    }else if (facet->visible && qh->NEWfacets) {
+      if (!SETempty_(facet->neighbors) || !SETempty_(facet->ridges)) {
+        qh_fprintf(qh, qh->ferr, 6376, "qhull internal error (qh_checkpolygon): expecting empty f.neighbors and f.ridges for visible facet f%d.  Got %d neighbors and %d ridges\n",
+          facet->id, qh_setsize(qh, facet->neighbors), qh_setsize(qh, facet->ridges));
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+      }
+    }
+  }
+  if (facetlist == qh->facet_list) {
+    vertexlist= qh->vertex_list;
+  }else if (facetlist == qh->newfacet_list) {
+    vertexlist= qh->newvertex_list;
+  }else {
+    vertexlist= NULL;
+  }
+  FORALLvertex_(vertexlist) {
+    qh_checkvertex(qh, vertex, !qh_ALL, &waserror);
+    if(vertex == qh->newvertex_list)
+      newvertexseen= True;
+    vertex->seen= False;
+    vertex->visitid= 0;
+    if(vertex->newfacet && !newvertexseen && !vertex->deleted) {
+      qh_fprintf(qh, qh->ferr, 6288, "qhull internal error (qh_checkpolygon): v%d is 'newfacet' but it is not on new vertex list v%d\n", vertex->id, getid_(qh->newvertex_list));
+      qh_errexit(qh, qh_ERRqhull, qh->visible_list, NULL);
+    }
+  }
+  FORALLfacet_(facetlist) {
+    if (facet->visible)
+      continue;
+    if (facet->simplicial)
+      numridges += qh->hull_dim;
+    else
+      numridges += qh_setsize(qh, facet->ridges);
+    FOREACHvertex_(facet->vertices) {
+      vertex->visitid++;
+      if (!vertex->seen) {
+        vertex->seen= True;
+        numvertices++;
+        if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
+          qh_fprintf(qh, qh->ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
+                   (void *) vertex->point, vertex->id, (void *) qh->first_point);
+          waserror= True;
+        }
+      }
+    }
+  }
+  qh->vertex_visit += (unsigned int)numfacets;
+  if (facetlist == qh->facet_list) {
+    if (numfacets != qh->num_facets - qh->num_visible) {
+      qh_fprintf(qh, qh->ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
+              numfacets, qh->num_facets, qh->num_visible);
+      waserror= True;
+    }
+    qh->vertex_visit++;
+    if (qh->VERTEXneighbors) {
+      FORALLvertices {
+        if (!vertex->neighbors) {
+          qh_fprintf(qh, qh->ferr, 6407, "qhull internal error (qh_checkpolygon): missing vertex neighbors for v%d\n", vertex->id);
+          waserror= True;
+        }
+        qh_setcheck(qh, vertex->neighbors, "neighbors for v", vertex->id);
+        if (vertex->deleted)
+          continue;
+        totvneighbors += qh_setsize(qh, vertex->neighbors);
+      }
+      FORALLfacet_(facetlist) {
+        if (!facet->visible)
+          totfacetvertices += qh_setsize(qh, facet->vertices);
+      }
+      if (totvneighbors != totfacetvertices) {
+        qh_fprintf(qh, qh->ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent (tot_vneighbors %d != tot_facetvertices %d).  Maybe duplicate or missing vertex\n",
+                totvneighbors, totfacetvertices);
+        waserror= True;
+        FORALLvertices {
+          if (vertex->deleted)
+            continue;
+          qh->visit_id++;
+          FOREACHneighbor_(vertex) {
+            if (neighbor->visitid==qh->visit_id) {
+              qh_fprintf(qh, qh->ferr, 6275, "qhull internal error (qh_checkpolygon): facet f%d occurs twice in neighbors of vertex v%d\n",
+                  neighbor->id, vertex->id);
+              errorfacet2= errorfacet;
+              errorfacet= neighbor;
+            }
+            neighbor->visitid= qh->visit_id;
+            if (!qh_setin(neighbor->vertices, vertex)) {
+              qh_fprintf(qh, qh->ferr, 6276, "qhull internal error (qh_checkpolygon): facet f%d is a neighbor of vertex v%d but v%d is not a vertex of f%d\n",
+                  neighbor->id, vertex->id, vertex->id, neighbor->id);
+              errorfacet2= errorfacet;
+              errorfacet= neighbor;
+            }
+          }
+        }
+        FORALLfacet_(facetlist){
+          if (!facet->visible) {
+            /* vertices are inverse sorted and are unlikely to be duplicated */
+            FOREACHvertex_(facet->vertices){
+              if (!qh_setin(vertex->neighbors, facet)) {
+                qh_fprintf(qh, qh->ferr, 6277, "qhull internal error (qh_checkpolygon): v%d is a vertex of facet f%d but f%d is not a neighbor of v%d\n",
+                  vertex->id, facet->id, facet->id, vertex->id);
+                errorfacet2= errorfacet;
+                errorfacet= facet;
+              }
+            }
+          }
+        }
+      }
+    }
+    if (numvertices != qh->num_vertices - qh_setsize(qh, qh->del_vertices)) {
+      qh_fprintf(qh, qh->ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
+              numvertices, qh->num_vertices - qh_setsize(qh, qh->del_vertices));
+      waserror= True;
+    }
+    if (qh->hull_dim == 2 && numvertices != numfacets) {
+      qh_fprintf(qh, qh->ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
+        numvertices, numfacets);
+      waserror= True;
+    }
+    if (qh->hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
+      qh_fprintf(qh, qh->ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2.  A vertex appears twice in a edge list.  May occur during merging.\n",
+          numvertices, numfacets, numridges/2);
+      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
+    }
+  }
+  if (waserror)
+    qh_errexit2(qh, qh_ERRqhull, errorfacet, errorfacet2);
+} /* checkpolygon */
+
+
+/*---------------------------------
+
+  qh_checkvertex(qh, vertex, allchecks, &waserror )
+    check vertex for consistency
+    if allchecks, checks vertex->neighbors
+
+  returns:
+    sets waserror if any error occurs
+
+  notes:
+    called by qh_tracemerge and qh_checkpolygon
+    neighbors checked efficiently in qh_checkpolygon
+*/
+void qh_checkvertex(qhT *qh, vertexT *vertex, boolT allchecks, boolT *waserrorp) {
+  boolT waserror= False;
+  facetT *neighbor, **neighborp, *errfacet=NULL;
+
+  if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
+    qh_fprintf(qh, qh->ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", (void *) vertex->point);
+    waserror= True;
+  }
+  if (vertex->id >= qh->vertex_id) {
+    qh_fprintf(qh, qh->ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id v%d >= qh.vertex_id (%d)\n", vertex->id, qh->vertex_id);
+    waserror= True;
+  }
+  if (vertex->visitid > qh->vertex_visit) {
+    qh_fprintf(qh, qh->ferr, 6413, "qhull internal error (qh_checkvertex): expecting v%d.visitid <= qh.vertex_visit (%d).  Got visitid %d\n", vertex->id, qh->vertex_visit, vertex->visitid);
+    waserror= True;
+  }
+  if (allchecks && !waserror && !vertex->deleted) {
+    if (qh_setsize(qh, vertex->neighbors)) {
+      FOREACHneighbor_(vertex) {
+        if (!qh_setin(neighbor->vertices, vertex)) {
+          qh_fprintf(qh, qh->ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
+          errfacet= neighbor;
+          waserror= True;
+        }
+      }
+    }
+  }
+  if (waserror) {
+    qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
+    if (errfacet)
+      qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
+    *waserrorp= True;
+  }
+} /* checkvertex */
+
+/*---------------------------------
+
+  qh_clearcenters(qh, type )
+    clear old data from facet->center
+
+  notes:
+    sets new centertype
+    nop if CENTERtype is the same
+*/
+void qh_clearcenters(qhT *qh, qh_CENTER type) {
+  facetT *facet;
+
+  if (qh->CENTERtype != type) {
+    FORALLfacets {
+      if (facet->tricoplanar && !facet->keepcentrum)
+          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
+      else if (qh->CENTERtype == qh_ASvoronoi){
+        if (facet->center) {
+          qh_memfree(qh, facet->center, qh->center_size);
+          facet->center= NULL;
+        }
+      }else /* qh.CENTERtype == qh_AScentrum */ {
+        if (facet->center) {
+          qh_memfree(qh, facet->center, qh->normal_size);
+          facet->center= NULL;
+        }
+      }
+    }
+    qh->CENTERtype= type;
+  }
+  trace2((qh, qh->ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
+} /* clearcenters */
+
+/*---------------------------------
+
+  qh_createsimplex(qh, vertices )
+    creates a simplex from a set of vertices
+
+  returns:
+    initializes qh.facet_list to the simplex
+
+  notes:
+    only called by qh_initialhull
+
+  design:
+    for each vertex
+      create a new facet
+    for each new facet
+      create its neighbor set
+*/
+void qh_createsimplex(qhT *qh, setT *vertices /* qh.facet_list */) {
+  facetT *facet= NULL, *newfacet;
+  boolT toporient= True;
+  int vertex_i, vertex_n, nth;
+  setT *newfacets= qh_settemp(qh, qh->hull_dim+1);
+  vertexT *vertex;
+
+  FOREACHvertex_i_(qh, vertices) {
+    newfacet= qh_newfacet(qh);
+    newfacet->vertices= qh_setnew_delnthsorted(qh, vertices, vertex_n, vertex_i, 0);
+    if (toporient)
+      newfacet->toporient= True;
+    qh_appendfacet(qh, newfacet);
+    newfacet->newfacet= True;
+    qh_appendvertex(qh, vertex);
+    qh_setappend(qh, &newfacets, newfacet);
+    toporient ^= True;
+  }
+  FORALLnew_facets {
+    nth= 0;
+    FORALLfacet_(qh->newfacet_list) {
+      if (facet != newfacet)
+        SETelem_(newfacet->neighbors, nth++)= facet;
+    }
+    qh_settruncate(qh, newfacet->neighbors, qh->hull_dim);
+  }
+  qh_settempfree(qh, &newfacets);
+  trace1((qh, qh->ferr, 1028, "qh_createsimplex: created simplex\n"));
+} /* createsimplex */
+
+/*---------------------------------
+
+  qh_delridge(qh, ridge )
+    delete a ridge's vertices and frees its memory
+
+  notes:
+    assumes r.top->ridges and r.bottom->ridges have been updated
+*/
+void qh_delridge(qhT *qh, ridgeT *ridge) {
+
+  if (ridge == qh->traceridge)
+    qh->traceridge= NULL;
+  qh_setfree(qh, &(ridge->vertices));
+  qh_memfree(qh, ridge, (int)sizeof(ridgeT));
+} /* delridge */
+
+/*---------------------------------
+
+  qh_delvertex(qh, vertex )
+    deletes a vertex and frees its memory
+
+  notes:
+    assumes vertex->adjacencies have been updated if needed
+    unlinks from vertex_list
+*/
+void qh_delvertex(qhT *qh, vertexT *vertex) {
+
+  if (vertex->deleted && !vertex->partitioned && !qh->NOerrexit) {
+    qh_fprintf(qh, qh->ferr, 6395, "qhull internal error (qh_delvertex): vertex v%d was deleted but it was not partitioned as a coplanar point\n",
+      vertex->id);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  if (vertex == qh->tracevertex)
+    qh->tracevertex= NULL;
+  qh_removevertex(qh, vertex);
+  qh_setfree(qh, &vertex->neighbors);
+  qh_memfree(qh, vertex, (int)sizeof(vertexT));
+} /* delvertex */
+
+
+/*---------------------------------
+
+  qh_facet3vertex(qh )
+    return temporary set of 3-d vertices in qh_ORIENTclock order
+
+  design:
+    if simplicial facet
+      build set from facet->vertices with facet->toporient
+    else
+      for each ridge in order
+        build set from ridge's vertices
+*/
+setT *qh_facet3vertex(qhT *qh, facetT *facet) {
+  ridgeT *ridge, *firstridge;
+  vertexT *vertex;
+  int cntvertices, cntprojected=0;
+  setT *vertices;
+
+  cntvertices= qh_setsize(qh, facet->vertices);
+  vertices= qh_settemp(qh, cntvertices);
+  if (facet->simplicial) {
+    if (cntvertices != 3) {
+      qh_fprintf(qh, qh->ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
+                  cntvertices, facet->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    qh_setappend(qh, &vertices, SETfirst_(facet->vertices));
+    if (facet->toporient ^ qh_ORIENTclock)
+      qh_setappend(qh, &vertices, SETsecond_(facet->vertices));
+    else
+      qh_setaddnth(qh, &vertices, 0, SETsecond_(facet->vertices));
+    qh_setappend(qh, &vertices, SETelem_(facet->vertices, 2));
+  }else {
+    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
+    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
+      qh_setappend(qh, &vertices, vertex);
+      if (++cntprojected > cntvertices || ridge == firstridge)
+        break;
+    }
+    if (!ridge || cntprojected != cntvertices) {
+      qh_fprintf(qh, qh->ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
+                  facet->id, cntprojected);
+      qh_errexit(qh, qh_ERRqhull, facet, ridge);
+    }
+  }
+  return vertices;
+} /* facet3vertex */
+
+/*---------------------------------
+
+  qh_findbestfacet(qh, point, bestoutside, bestdist, isoutside )
+    find facet that is furthest below a point
+
+    for Delaunay triangulations,
+      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
+      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
+
+  returns:
+    if bestoutside is set (e.g., qh_ALL)
+      returns best facet that is not upperdelaunay
+      if Delaunay and inside, point is outside circumsphere of bestfacet
+    else
+      returns first facet below point
+      if point is inside, returns nearest, !upperdelaunay facet
+    distance to facet
+    isoutside set if outside of facet
+
+  notes:
+    Distance is measured by distance to the facet's hyperplane.  For
+    Delaunay facets, this is not the same as the containing facet.  It may
+    be an adjacent facet or a different tricoplanar facet.  See
+    locate a facet with qh_findbestfacet()
+
+    For tricoplanar facets, this finds one of the tricoplanar facets closest
+    to the point.
+
+    If inside, qh_findbestfacet performs an exhaustive search
+       this may be too conservative.  Sometimes it is clearly required.
+
+    qh_findbestfacet is not used by qhull.
+    uses qh.visit_id and qh.coplanarset
+
+  see:
+    qh_findbest
+*/
+facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
+           realT *bestdist, boolT *isoutside) {
+  facetT *bestfacet= NULL;
+  int numpart, totpart= 0;
+
+  bestfacet= qh_findbest(qh, point, qh->facet_list,
+                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
+                            bestdist, isoutside, &totpart);
+  if (*bestdist < -qh->DISTround) {
+    bestfacet= qh_findfacet_all(qh, point, !qh_NOupper, bestdist, isoutside, &numpart);
+    totpart += numpart;
+    if ((isoutside && *isoutside && bestoutside)
+    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
+      bestfacet= qh_findbest(qh, point, bestfacet,
+                            bestoutside, False, bestoutside,
+                            bestdist, isoutside, &totpart);
+      totpart += numpart;
+    }
+  }
+  trace3((qh, qh->ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
+          bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
+  return bestfacet;
+} /* findbestfacet */
+
+/*---------------------------------
+
+  qh_findbestlower(qh, facet, point, bestdist, numpart )
+    returns best non-upper, non-flipped neighbor of facet for point
+    if needed, searches vertex neighbors
+
+  returns:
+    returns bestdist and updates numpart
+
+  notes:
+    called by qh_findbest() for points above an upperdelaunay facet
+    if Delaunay and inside, point is outside of circumsphere of bestfacet
+
+*/
+facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
+  facetT *neighbor, **neighborp, *bestfacet= NULL;
+  realT bestdist= -REALmax/2 /* avoid underflow */;
+  realT dist;
+  vertexT *vertex;
+  boolT isoutside= False;  /* not used */
+
+  zinc_(Zbestlower);
+  FOREACHneighbor_(upperfacet) {
+    if (neighbor->upperdelaunay || neighbor->flipped)
+      continue;
+    (*numpart)++;
+    qh_distplane(qh, point, neighbor, &dist);
+    if (dist > bestdist) {
+      bestfacet= neighbor;
+      bestdist= dist;
+    }
+  }
+  if (!bestfacet) {
+    zinc_(Zbestlowerv);
+    /* rarely called, numpart does not count nearvertex computations */
+    vertex= qh_nearvertex(qh, upperfacet, point, &dist);
+    qh_vertexneighbors(qh);
+    FOREACHneighbor_(vertex) {
+      if (neighbor->upperdelaunay || neighbor->flipped)
+        continue;
+      (*numpart)++;
+      qh_distplane(qh, point, neighbor, &dist);
+      if (dist > bestdist) {
+        bestfacet= neighbor;
+        bestdist= dist;
+      }
+    }
+  }
+  if (!bestfacet) {
+    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
+    zmax_(Zbestloweralln, qh->num_facets);
+    /* [dec'15] Previously reported as QH6228 */
+    trace3((qh, qh->ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
+        upperfacet->id));
+    /* rarely called */
+    bestfacet= qh_findfacet_all(qh, point, qh_NOupper, &bestdist, &isoutside, numpart);
+  }
+  *bestdistp= bestdist;
+  trace3((qh, qh->ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
+          bestfacet->id, bestdist, upperfacet->id, qh_pointid(qh, point)));
+  return bestfacet;
+} /* findbestlower */
+
+/*---------------------------------
+
+  qh_findfacet_all(qh, point, noupper, bestdist, isoutside, numpart )
+    exhaustive search for facet below a point
+    ignore flipped and visible facets, f.normal==NULL, and if noupper, f.upperdelaunay facets
+
+    for Delaunay triangulations,
+      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
+      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
+
+  returns:
+    returns first facet below point
+    if point is inside,
+      returns nearest facet
+    distance to facet
+    isoutside if point is outside of the hull
+    number of distance tests
+
+  notes:
+    called by qh_findbestlower if all neighbors are flipped or upper Delaunay (QH3025)
+    primarily for library users (qh_findbestfacet), rarely used by Qhull
+*/
+facetT *qh_findfacet_all(qhT *qh, pointT *point, boolT noupper, realT *bestdist, boolT *isoutside,
+                          int *numpart) {
+  facetT *bestfacet= NULL, *facet;
+  realT dist;
+  int totpart= 0;
+
+  *bestdist= -REALmax;
+  *isoutside= False;
+  FORALLfacets {
+    if (facet->flipped || !facet->normal || facet->visible)
+      continue;
+    if (noupper && facet->upperdelaunay)
+      continue;
+    totpart++;
+    qh_distplane(qh, point, facet, &dist);
+    if (dist > *bestdist) {
+      *bestdist= dist;
+      bestfacet= facet;
+      if (dist > qh->MINoutside) {
+        *isoutside= True;
+        break;
+      }
+    }
+  }
+  *numpart= totpart;
+  trace3((qh, qh->ferr, 3016, "qh_findfacet_all: p%d, noupper? %d, f%d, dist %2.2g, isoutside %d, totpart %d\n",
+      qh_pointid(qh, point), noupper, getid_(bestfacet), *bestdist, *isoutside, totpart));
+  return bestfacet;
+} /* findfacet_all */
+
+/*---------------------------------
+
+  qh_findgood(qh, facetlist, goodhorizon )
+    identify good facets for qh.PRINTgood and qh_buildcone_onlygood
+    goodhorizon is count of good, horizon facets from qh_find_horizon, otherwise 0 from qh_findgood_all
+    if not qh.MERGING and qh.GOODvertex>0
+      facet includes point as vertex
+      if !match, returns goodhorizon
+    if qh.GOODpoint
+      facet is visible or coplanar (>0) or not visible (<0)
+    if qh.GOODthreshold
+      facet->normal matches threshold
+    if !goodhorizon and !match,
+      selects facet with closest angle to thresholds
+      sets GOODclosest
+
+  returns:
+    number of new, good facets found
+    determines facet->good
+    may update qh.GOODclosest
+
+  notes:
+    called from qh_initbuild, qh_buildcone_onlygood, and qh_findgood_all
+    qh_findgood_all (called from qh_prepare_output) further reduces the good region
+
+  design:
+    count good facets
+    if not merging, clear good facets that fail qh.GOODvertex ('QVn', but not 'QV-n')
+    clear good facets that fail qh.GOODpoint ('QGn' or 'QG-n')
+    clear good facets that fail qh.GOODthreshold
+    if !goodhorizon and !find f.good,
+      sets GOODclosest to facet with closest angle to thresholds
+*/
+int qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon) {
+  facetT *facet, *bestfacet= NULL;
+  realT angle, bestangle= REALmax, dist;
+  int  numgood=0;
+
+  FORALLfacet_(facetlist) {
+    if (facet->good)
+      numgood++;
+  }
+  if (qh->GOODvertex>0 && !qh->MERGING) {
+    FORALLfacet_(facetlist) {
+      if (facet->good && !qh_isvertex(qh->GOODvertexp, facet->vertices)) {
+        facet->good= False;
+        numgood--;
+      }
+    }
+  }
+  if (qh->GOODpoint && numgood) {
+    FORALLfacet_(facetlist) {
+      if (facet->good && facet->normal) {
+        zinc_(Zdistgood);
+        qh_distplane(qh, qh->GOODpointp, facet, &dist);
+        if ((qh->GOODpoint > 0) ^ (dist > 0.0)) {
+          facet->good= False;
+          numgood--;
+        }
+      }
+    }
+  }
+  if (qh->GOODthreshold && (numgood || goodhorizon || qh->GOODclosest)) {
+    FORALLfacet_(facetlist) {
+      if (facet->good && facet->normal) {
+        if (!qh_inthresholds(qh, facet->normal, &angle)) {
+          facet->good= False;
+          numgood--;
+          if (angle < bestangle) {
+            bestangle= angle;
+            bestfacet= facet;
+          }
+        }
+      }
+    }
+    if (numgood == 0 && (goodhorizon == 0 || qh->GOODclosest)) {
+      if (qh->GOODclosest) {
+        if (qh->GOODclosest->visible)
+          qh->GOODclosest= NULL;
+        else {
+          qh_inthresholds(qh, qh->GOODclosest->normal, &angle);
+          if (angle < bestangle)
+            bestfacet= qh->GOODclosest;
+        }
+      }
+      if (bestfacet && bestfacet != qh->GOODclosest) {   /* numgood == 0 */
+        if (qh->GOODclosest)
+          qh->GOODclosest->good= False;
+        qh->GOODclosest= bestfacet;
+        bestfacet->good= True;
+        numgood++;
+        trace2((qh, qh->ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
+           bestfacet->id, bestangle));
+        return numgood;
+      }
+    }else if (qh->GOODclosest) { /* numgood > 0 */
+      qh->GOODclosest->good= False;
+      qh->GOODclosest= NULL;
+    }
+  }
+  zadd_(Zgoodfacet, numgood);
+  trace2((qh, qh->ferr, 2045, "qh_findgood: found %d good facets with %d good horizon and qh.GOODclosest f%d\n",
+               numgood, goodhorizon, getid_(qh->GOODclosest)));
+  if (!numgood && qh->GOODvertex>0 && !qh->MERGING)
+    return goodhorizon;
+  return numgood;
+} /* findgood */
+
+/*---------------------------------
+
+  qh_findgood_all(qh, facetlist )
+    apply other constraints for good facets (used by qh.PRINTgood)
+    if qh.GOODvertex
+      facet includes (>0) or doesn't include (<0) point as vertex
+      if last good facet and ONLYgood, prints warning and continues
+    if qh.SPLITthresholds (e.g., qh.DELAUNAY)
+      facet->normal matches threshold, or if none, the closest one
+    calls qh_findgood
+    nop if good not used
+
+  returns:
+    clears facet->good if not good
+    sets qh.num_good
+
+  notes:
+    called by qh_prepare_output and qh_printneighborhood
+    unless qh.ONLYgood, calls qh_findgood first
+
+  design:
+    uses qh_findgood to mark good facets
+    clear f.good for failed qh.GOODvertex
+    clear f.good for failed qh.SPLITthreholds
+       if no more good facets, select best of qh.SPLITthresholds
+*/
+void qh_findgood_all(qhT *qh, facetT *facetlist) {
+  facetT *facet, *bestfacet=NULL;
+  realT angle, bestangle= REALmax;
+  int  numgood=0, startgood;
+
+  if (!qh->GOODvertex && !qh->GOODthreshold && !qh->GOODpoint
+  && !qh->SPLITthresholds)
+    return;
+  if (!qh->ONLYgood)
+    qh_findgood(qh, qh->facet_list, 0);
+  FORALLfacet_(facetlist) {
+    if (facet->good)
+      numgood++;
+  }
+  if (qh->GOODvertex <0 || (qh->GOODvertex > 0 && qh->MERGING)) {
+    FORALLfacet_(facetlist) {
+      if (facet->good && ((qh->GOODvertex > 0) ^ !!qh_isvertex(qh->GOODvertexp, facet->vertices))) { /* convert to bool */
+        if (!--numgood) {
+          if (qh->ONLYgood) {
+            qh_fprintf(qh, qh->ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
+               qh_pointid(qh, qh->GOODvertexp), facet->id);
+            return;
+          }else if (qh->GOODvertex > 0)
+            qh_fprintf(qh, qh->ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
+                qh->GOODvertex-1, qh->GOODvertex-1);
+          else
+            qh_fprintf(qh, qh->ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
+                -qh->GOODvertex - 1, -qh->GOODvertex - 1);
+        }
+        facet->good= False;
+      }
+    }
+  }
+  startgood= numgood;
+  if (qh->SPLITthresholds) {
+    FORALLfacet_(facetlist) {
+      if (facet->good) {
+        if (!qh_inthresholds(qh, facet->normal, &angle)) {
+          facet->good= False;
+          numgood--;
+          if (angle < bestangle) {
+            bestangle= angle;
+            bestfacet= facet;
+          }
+        }
+      }
+    }
+    if (!numgood && bestfacet) {
+      bestfacet->good= True;
+      numgood++;
+      trace0((qh, qh->ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to split thresholds\n",
+           bestfacet->id, bestangle));
+      return;
+    }
+  }
+  if (numgood == 1 && !qh->PRINTgood && qh->GOODclosest && qh->GOODclosest->good) {
+    trace2((qh, qh->ferr, 2109, "qh_findgood_all: undo selection of qh.GOODclosest f%d since it would fail qh_inthresholds in qh_skipfacet\n",
+      qh->GOODclosest->id));
+    qh->GOODclosest->good= False;
+    numgood= 0;
+  }
+  qh->num_good= numgood;
+  trace0((qh, qh->ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
+        numgood, startgood));
+} /* findgood_all */
+
+/*---------------------------------
+
+  qh_furthestnext()
+    set qh.facet_next to facet with furthest of all furthest points
+    searches all facets on qh.facet_list
+
+  notes:
+    this may help avoid precision problems
+*/
+void qh_furthestnext(qhT *qh /* qh.facet_list */) {
+  facetT *facet, *bestfacet= NULL;
+  realT dist, bestdist= -REALmax;
+
+  FORALLfacets {
+    if (facet->outsideset) {
+#if qh_COMPUTEfurthest
+      pointT *furthest;
+      furthest= (pointT *)qh_setlast(facet->outsideset);
+      zinc_(Zcomputefurthest);
+      qh_distplane(qh, furthest, facet, &dist);
+#else
+      dist= facet->furthestdist;
+#endif
+      if (dist > bestdist) {
+        bestfacet= facet;
+        bestdist= dist;
+      }
+    }
+  }
+  if (bestfacet) {
+    qh_removefacet(qh, bestfacet);
+    qh_prependfacet(qh, bestfacet, &qh->facet_next);
+    trace1((qh, qh->ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
+            bestfacet->id, bestdist));
+  }
+} /* furthestnext */
+
+/*---------------------------------
+
+  qh_furthestout(qh, facet )
+    make furthest outside point the last point of outsideset
+
+  returns:
+    updates facet->outsideset
+    clears facet->notfurthest
+    sets facet->furthestdist
+
+  design:
+    determine best point of outsideset
+    make it the last point of outsideset
+*/
+void qh_furthestout(qhT *qh, facetT *facet) {
+  pointT *point, **pointp, *bestpoint= NULL;
+  realT dist, bestdist= -REALmax;
+
+  FOREACHpoint_(facet->outsideset) {
+    qh_distplane(qh, point, facet, &dist);
+    zinc_(Zcomputefurthest);
+    if (dist > bestdist) {
+      bestpoint= point;
+      bestdist= dist;
+    }
+  }
+  if (bestpoint) {
+    qh_setdel(facet->outsideset, point);
+    qh_setappend(qh, &facet->outsideset, point);
+#if !qh_COMPUTEfurthest
+    facet->furthestdist= bestdist;
+#endif
+  }
+  facet->notfurthest= False;
+  trace3((qh, qh->ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
+          qh_pointid(qh, point), facet->id));
+} /* furthestout */
+
+
+/*---------------------------------
+
+  qh_infiniteloop(qh, facet )
+    report infinite loop error due to facet
+*/
+void qh_infiniteloop(qhT *qh, facetT *facet) {
+
+  qh_fprintf(qh, qh->ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected.  If visible, f.replace. If newfacet, f.samecycle\n");
+  qh_errexit(qh, qh_ERRqhull, facet, NULL);
+} /* qh_infiniteloop */
+
+/*---------------------------------
+
+  qh_initbuild()
+    initialize hull and outside sets with point array
+    qh.FIRSTpoint/qh.NUMpoints is point array
+    if qh.GOODpoint
+      adds qh.GOODpoint to initial hull
+
+  returns:
+    qh_facetlist with initial hull
+    points partitioned into outside sets, coplanar sets, or inside
+    initializes qh.GOODpointp, qh.GOODvertexp,
+
+  design:
+    initialize global variables used during qh_buildhull
+    determine precision constants and points with max/min coordinate values
+      if qh.SCALElast, scale last coordinate(for 'd')
+    initialize qh.newfacet_list, qh.facet_tail
+    initialize qh.vertex_list, qh.newvertex_list, qh.vertex_tail
+    determine initial vertices
+    build initial simplex
+    partition input points into facets of initial simplex
+    set up lists
+    if qh.ONLYgood
+      check consistency
+      add qh.GOODvertex if defined
+*/
+void qh_initbuild(qhT *qh) {
+  setT *maxpoints, *vertices;
+  facetT *facet;
+  int i, numpart;
+  realT dist;
+  boolT isoutside;
+
+  if (qh->PRINTstatistics) {
+    qh_fprintf(qh, qh->ferr, 9350, "qhull %s Statistics: %s | %s\n",
+      qh_version, qh->rbox_command, qh->qhull_command);
+    fflush(NULL);
+  }
+  qh->furthest_id= qh_IDunknown;
+  qh->lastreport= 0;
+  qh->lastfacets= 0;
+  qh->lastmerges= 0;
+  qh->lastplanes= 0;
+  qh->lastdist= 0;
+  qh->facet_id= qh->vertex_id= qh->ridge_id= 0;
+  qh->visit_id= qh->vertex_visit= 0;
+  qh->maxoutdone= False;
+
+  if (qh->GOODpoint > 0)
+    qh->GOODpointp= qh_point(qh, qh->GOODpoint-1);
+  else if (qh->GOODpoint < 0)
+    qh->GOODpointp= qh_point(qh, -qh->GOODpoint-1);
+  if (qh->GOODvertex > 0)
+    qh->GOODvertexp= qh_point(qh, qh->GOODvertex-1);
+  else if (qh->GOODvertex < 0)
+    qh->GOODvertexp= qh_point(qh, -qh->GOODvertex-1);
+  if ((qh->GOODpoint
+       && (qh->GOODpointp < qh->first_point  /* also catches !GOODpointp */
+           || qh->GOODpointp > qh_point(qh, qh->num_points-1)))
+  || (qh->GOODvertex
+       && (qh->GOODvertexp < qh->first_point  /* also catches !GOODvertexp */
+           || qh->GOODvertexp > qh_point(qh, qh->num_points-1)))) {
+    qh_fprintf(qh, qh->ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
+             qh->num_points-1);
+    qh_errexit(qh, qh_ERRinput, NULL, NULL);
+  }
+  maxpoints= qh_maxmin(qh, qh->first_point, qh->num_points, qh->hull_dim);
+  if (qh->SCALElast)
+    qh_scalelast(qh, qh->first_point, qh->num_points, qh->hull_dim, qh->MINlastcoord, qh->MAXlastcoord, qh->MAXabs_coord);
+  qh_detroundoff(qh);
+  if (qh->DELAUNAY && qh->upper_threshold[qh->hull_dim-1] > REALmax/2
+                  && qh->lower_threshold[qh->hull_dim-1] < -REALmax/2) {
+    for (i=qh_PRINTEND; i--; ) {
+      if (qh->PRINTout[i] == qh_PRINTgeom && qh->DROPdim < 0
+          && !qh->GOODthreshold && !qh->SPLITthresholds)
+        break;  /* in this case, don't set upper_threshold */
+    }
+    if (i < 0) {
+      if (qh->UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
+        qh->lower_threshold[qh->hull_dim-1]= qh->ANGLEround * qh_ZEROdelaunay;
+        qh->GOODthreshold= True;
+      }else {
+        qh->upper_threshold[qh->hull_dim-1]= -qh->ANGLEround * qh_ZEROdelaunay;
+        if (!qh->GOODthreshold)
+          qh->SPLITthresholds= True; /* build upper-convex hull even if Qg */
+          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
+      }
+    }
+  }
+  trace4((qh, qh->ferr, 4091, "qh_initbuild: create sentinels for qh.facet_tail and qh.vertex_tail\n"));
+  qh->facet_list= qh->newfacet_list= qh->facet_tail= qh_newfacet(qh);
+  qh->num_facets= qh->num_vertices= qh->num_visible= 0;
+  qh->vertex_list= qh->newvertex_list= qh->vertex_tail= qh_newvertex(qh, NULL);
+  vertices= qh_initialvertices(qh, qh->hull_dim, maxpoints, qh->first_point, qh->num_points);
+  qh_initialhull(qh, vertices);  /* initial qh->facet_list */
+  qh_partitionall(qh, vertices, qh->first_point, qh->num_points);
+  if (qh->PRINToptions1st || qh->TRACElevel || qh->IStracing) {
+    if (qh->TRACElevel || qh->IStracing)
+      qh_fprintf(qh, qh->ferr, 8103, "\nTrace level T%d, IStracing %d, point TP%d, merge TM%d, dist TW%2.2g, qh.tracefacet_id %d, traceridge_id %d, tracevertex_id %d, last qh.RERUN %d, %s | %s\n",
+         qh->TRACElevel, qh->IStracing, qh->TRACEpoint, qh->TRACEmerge, qh->TRACEdist, qh->tracefacet_id, qh->traceridge_id, qh->tracevertex_id, qh->TRACElastrun, qh->rbox_command, qh->qhull_command);
+    qh_fprintf(qh, qh->ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
+  }
+  qh_resetlists(qh, False, qh_RESETvisible /* qh.visible_list newvertex_list qh.newfacet_list */);
+  qh->facet_next= qh->facet_list;
+  qh_furthestnext(qh /* qh.facet_list */);
+  if (qh->PREmerge) {
+    qh->cos_max= qh->premerge_cos;
+    qh->centrum_radius= qh->premerge_centrum; /* overwritten by qh_premerge */
+  }
+  if (qh->ONLYgood) {
+    if (qh->GOODvertex > 0 && qh->MERGING) {
+      qh_fprintf(qh, qh->ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (!(qh->GOODthreshold || qh->GOODpoint
+         || (!qh->MERGEexact && !qh->PREmerge && qh->GOODvertexp))) {
+      qh_fprintf(qh, qh->ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
+      qh_errexit(qh, qh_ERRinput, NULL, NULL);
+    }
+    if (qh->GOODvertex > 0  && !qh->MERGING  /* matches qh_partitionall */
+    && !qh_isvertex(qh->GOODvertexp, vertices)) {
+      facet= qh_findbestnew(qh, qh->GOODvertexp, qh->facet_list,
+                          &dist, !qh_ALL, &isoutside, &numpart);
+      zadd_(Zdistgood, numpart);
+      if (!isoutside) {
+        qh_fprintf(qh, qh->ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
+               qh_pointid(qh, qh->GOODvertexp));
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }
+      if (!qh_addpoint(qh, qh->GOODvertexp, facet, False)) {
+        qh_settempfree(qh, &vertices);
+        qh_settempfree(qh, &maxpoints);
+        return;
+      }
+    }
+    qh_findgood(qh, qh->facet_list, 0);
+  }
+  qh_settempfree(qh, &vertices);
+  qh_settempfree(qh, &maxpoints);
+  trace1((qh, qh->ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
+} /* initbuild */
+
+/*---------------------------------
+
+  qh_initialhull(qh, vertices )
+    constructs the initial hull as a DIM3 simplex of vertices
+
+  notes:
+    only called by qh_initbuild
+
+  design:
+    creates a simplex (initializes lists)
+    determines orientation of simplex
+    sets hyperplanes for facets
+    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
+    checks for flipped facets and qh.NARROWhull
+    checks the result
+*/
+void qh_initialhull(qhT *qh, setT *vertices) {
+  facetT *facet, *firstfacet, *neighbor, **neighborp;
+  realT angle, minangle= REALmax, dist;
+
+  qh_createsimplex(qh, vertices /* qh.facet_list */);
+  qh_resetlists(qh, False, qh_RESETvisible);
+  qh->facet_next= qh->facet_list;      /* advance facet when processed */
+  qh->interior_point= qh_getcenter(qh, vertices);
+  if (qh->IStracing) {
+    qh_fprintf(qh, qh->ferr, 8105, "qh_initialhull: ");
+    qh_printpoint(qh, qh->ferr, "qh.interior_point", qh->interior_point);
+  }
+  firstfacet= qh->facet_list;
+  qh_setfacetplane(qh, firstfacet);   /* qh_joggle_restart if flipped */
+  if (firstfacet->flipped) {
+    trace1((qh, qh->ferr, 1065, "qh_initialhull: ignore f%d flipped.  Test qh.interior_point (p-2) for clearly flipped\n", firstfacet->id));
+    firstfacet->flipped= False;
+  }
+  zzinc_(Zdistcheck);
+  qh_distplane(qh, qh->interior_point, firstfacet, &dist);
+  if (dist > qh->DISTround) {  /* clearly flipped */
+    trace1((qh, qh->ferr, 1060, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d.  Reversing orientation of all facets\n",
+          dist, firstfacet->id));
+    FORALLfacets
+      facet->toporient ^= (unsigned char)True;
+    qh_setfacetplane(qh, firstfacet);
+  }
+  FORALLfacets {
+    if (facet != firstfacet)
+      qh_setfacetplane(qh, facet);    /* qh_joggle_restart if flipped */
+  }
+  FORALLfacets {
+    if (facet->flipped) {
+      trace1((qh, qh->ferr, 1066, "qh_initialhull: ignore f%d flipped.  Test qh.interior_point (p-2) for clearly flipped\n", facet->id));
+      facet->flipped= False;
+    }
+    zzinc_(Zdistcheck);
+    qh_distplane(qh, qh->interior_point, facet, &dist);  /* duplicates qh_setfacetplane */
+    if (dist > qh->DISTround) {  /* clearly flipped, due to axis-parallel facet or coplanar firstfacet */
+      trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d.  Either axis-parallel facet or coplanar firstfacet f%d.  Force outside orientation of all facets\n",
+        dist, facet->id, firstfacet->id));
+      FORALLfacets { /* reuse facet, then 'break' */
+        facet->flipped= False;
+        facet->toporient ^= (unsigned char)True;
+        qh_orientoutside(qh, facet);  /* force outside orientation for f.normal */
+      }
+      break;
+    }
+  }
+  FORALLfacets {
+    if (!qh_checkflipped(qh, facet, NULL, qh_ALL)) {
+      if (qh->DELAUNAY && ! qh->ATinfinity) {
+        qh_joggle_restart(qh, "initial Delaunay cocircular or cospherical");
+        if (qh->UPPERdelaunay)
+          qh_fprintf(qh, qh->ferr, 6240, "Qhull precision error: initial Delaunay input sites are cocircular or cospherical.  Option 'Qs' searches all points.  Use option 'QJ' to joggle the input, otherwise cannot compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
+        else
+          qh_fprintf(qh, qh->ferr, 6239, "Qhull precision error: initial Delaunay input sites are cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points; it adds a point \"at infinity\".  Alternatively use option 'QJ' to joggle the input.  Use option 'Qs' to search all points for the initial simplex.\n");
+        qh_printvertexlist(qh, qh->ferr, "\ninput sites with last coordinate projected to a paraboloid\n", qh->facet_list, NULL, qh_ALL);
+        qh_errexit(qh, qh_ERRinput, NULL, NULL);
+      }else {
+        qh_joggle_restart(qh, "initial simplex is flat");
+        qh_fprintf(qh, qh->ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
+                   facet->id);
+        qh_errexit(qh, qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
+      }
+    }
+    FOREACHneighbor_(facet) {
+      angle= qh_getangle(qh, facet->normal, neighbor->normal);
+      minimize_( minangle, angle);
+    }
+  }
+  if (minangle < qh_MAXnarrow && !qh->NOnarrow) {
+    realT diff= 1.0 + minangle;
+
+    qh->NARROWhull= True;
+    qh_option(qh, "_narrow-hull", NULL, &diff);
+    if (minangle < qh_WARNnarrow && !qh->RERUN && qh->PRINTprecision)
+      qh_printhelp_narrowhull(qh, qh->ferr, minangle);
+  }
+  zzval_(Zprocessed)= qh->hull_dim+1;
+  qh_checkpolygon(qh, qh->facet_list);
+  qh_checkconvex(qh, qh->facet_list, qh_DATAfault);
+  if (qh->IStracing >= 1) {
+    qh_fprintf(qh, qh->ferr, 8105, "qh_initialhull: simplex constructed\n");
+  }
+} /* initialhull */
+
+/*---------------------------------
+
+  qh_initialvertices(qh, dim, maxpoints, points, numpoints )
+    determines a non-singular set of initial vertices
+    maxpoints may include duplicate points
+
+  returns:
+    temporary set of dim+1 vertices in descending order by vertex id
+    if qh.RANDOMoutside && !qh.ALLpoints
+      picks random points
+    if dim >= qh_INITIALmax,
+      uses min/max x and max points with non-zero determinants
+
+  notes:
+    unless qh.ALLpoints,
+      uses maxpoints as long as determinate is non-zero
+*/
+setT *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints) {
+  pointT *point, **pointp;
+  setT *vertices, *simplex, *tested;
+  realT randr;
+  int idx, point_i, point_n, k;
+  boolT nearzero= False;
+
+  vertices= qh_settemp(qh, dim + 1);
+  simplex= qh_settemp(qh, dim + 1);
+  if (qh->ALLpoints)
+    qh_maxsimplex(qh, dim, NULL, points, numpoints, &simplex);
+  else if (qh->RANDOMoutside) {
+    while (qh_setsize(qh, simplex) != dim+1) {
+      randr= qh_RANDOMint;
+      randr= randr/(qh_RANDOMmax+1);
+      randr= floor(qh->num_points * randr);
+      idx= (int)randr;
+      while (qh_setin(simplex, qh_point(qh, idx))) {
+        idx++; /* in case qh_RANDOMint always returns the same value */
+        idx= idx < qh->num_points ? idx : 0;
+      }
+      qh_setappend(qh, &simplex, qh_point(qh, idx));
+    }
+  }else if (qh->hull_dim >= qh_INITIALmax) {
+    tested= qh_settemp(qh, dim+1);
+    qh_setappend(qh, &simplex, SETfirst_(maxpoints));   /* max and min X coord */
+    qh_setappend(qh, &simplex, SETsecond_(maxpoints));
+    qh_maxsimplex(qh, fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
+    k= qh_setsize(qh, simplex);
+    FOREACHpoint_i_(qh, maxpoints) {
+      if (k >= dim)  /* qh_maxsimplex for last point */
+        break;
+      if (point_i & 0x1) {     /* first try up to dim, max. coord. points */
+        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
+          qh_detsimplex(qh, point, simplex, k, &nearzero);
+          if (nearzero)
+            qh_setappend(qh, &tested, point);
+          else {
+            qh_setappend(qh, &simplex, point);
+            k++;
+          }
+        }
+      }
+    }
+    FOREACHpoint_i_(qh, maxpoints) {
+      if (k >= dim)  /* qh_maxsimplex for last point */
+        break;
+      if ((point_i & 0x1) == 0) {  /* then test min. coord points */
+        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
+          qh_detsimplex(qh, point, simplex, k, &nearzero);
+          if (nearzero)
+            qh_setappend(qh, &tested, point);
+          else {
+            qh_setappend(qh, &simplex, point);
+            k++;
+          }
+        }
+      }
+    }
+    /* remove tested points from maxpoints */
+    FOREACHpoint_i_(qh, maxpoints) {
+      if (qh_setin(simplex, point) || qh_setin(tested, point))
+        SETelem_(maxpoints, point_i)= NULL;
+    }
+    qh_setcompact(qh, maxpoints);
+    idx= 0;
+    while (k < dim && (point= qh_point(qh, idx++))) {
+      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
+        qh_detsimplex(qh, point, simplex, k, &nearzero);
+        if (!nearzero){
+          qh_setappend(qh, &simplex, point);
+          k++;
+        }
+      }
+    }
+    qh_settempfree(qh, &tested);
+    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
+  }else /* qh.hull_dim < qh_INITIALmax */
+    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
+  FOREACHpoint_(simplex)
+    qh_setaddnth(qh, &vertices, 0, qh_newvertex(qh, point)); /* descending order */
+  qh_settempfree(qh, &simplex);
+  return vertices;
+} /* initialvertices */
+
+
+/*---------------------------------
+
+  qh_isvertex( point, vertices )
+    returns vertex if point is in vertex set, else returns NULL
+
+  notes:
+    for qh.GOODvertex
+*/
+vertexT *qh_isvertex(pointT *point, setT *vertices) {
+  vertexT *vertex, **vertexp;
+
+  FOREACHvertex_(vertices) {
+    if (vertex->point == point)
+      return vertex;
+  }
+  return NULL;
+} /* isvertex */
+
+/*---------------------------------
+
+  qh_makenewfacets(qh, point )
+    make new facets from point and qh.visible_list
+
+  returns:
+    apex (point) of the new facets
+    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
+    qh.newvertex_list= list of vertices in new facets with ->newfacet set
+
+    if (qh.NEWtentative)
+      newfacets reference horizon facets, but not vice versa
+      ridges reference non-simplicial horizon ridges, but not vice versa
+      does not change existing facets
+    else
+      sets qh.NEWfacets
+      new facets attached to horizon facets and ridges
+      for visible facets,
+        visible->r.replace is corresponding new facet
+
+  see also:
+    qh_makenewplanes() -- make hyperplanes for facets
+    qh_attachnewfacets() -- attachnewfacets if not done here qh->NEWtentative
+    qh_matchnewfacets() -- match up neighbors
+    qh_update_vertexneighbors() -- update vertex neighbors and delvertices
+    qh_deletevisible() -- delete visible facets
+    qh_checkpolygon() --check the result
+    qh_triangulate() -- triangulate a non-simplicial facet
+
+  design:
+    for each visible facet
+      make new facets to its horizon facets
+      update its f.replace
+      clear its neighbor set
+*/
+vertexT *qh_makenewfacets(qhT *qh, pointT *point /* qh.visible_list */) {
+  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
+  vertexT *apex;
+  int numnew=0;
+
+  if (qh->CHECKfrequently) {
+    qh_checkdelridge(qh);
+  }
+  qh->newfacet_list= qh->facet_tail;
+  qh->newvertex_list= qh->vertex_tail;
+  apex= qh_newvertex(qh, point);
+  qh_appendvertex(qh, apex);
+  qh->visit_id++;
+  FORALLvisible_facets {
+    FOREACHneighbor_(visible)
+      neighbor->seen= False;
+    if (visible->ridges) {
+      visible->visitid= qh->visit_id;
+      newfacet2= qh_makenew_nonsimplicial(qh, visible, apex, &numnew);
+    }
+    if (visible->simplicial)
+      newfacet= qh_makenew_simplicial(qh, visible, apex, &numnew);
+    if (!qh->NEWtentative) {
+      if (newfacet2)  /* newfacet is null if all ridges defined */
+        newfacet= newfacet2;
+      if (newfacet)
+        visible->f.replace= newfacet;
+      else
+        zinc_(Zinsidevisible);
+      if (visible->ridges)      /* ridges and neighbors are no longer valid for visible facet */
+        SETfirst_(visible->ridges)= NULL;
+      SETfirst_(visible->neighbors)= NULL;
+    }
+  }
+  if (!qh->NEWtentative)
+    qh->NEWfacets= True;
+  trace1((qh, qh->ferr, 1032, "qh_makenewfacets: created %d new facets f%d..f%d from point p%d to horizon\n",
+    numnew, qh->first_newfacet, qh->facet_id-1, qh_pointid(qh, point)));
+  if (qh->IStracing >= 4)
+    qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
+  return apex;
+} /* makenewfacets */
+
+#ifndef qh_NOmerge
+/*---------------------------------
+
+  qh_matchdupridge(qh, atfacet, atskip, hashsize, hashcount )
+    match duplicate ridges in qh.hash_table for atfacet@atskip
+    duplicates marked with ->dupridge and qh_DUPLICATEridge
+
+  returns:
+    vertex-facet distance (>0.0) for qh_MERGEridge ridge
+    updates hashcount
+    set newfacet, facet, matchfacet's hyperplane (removes from mergecycle of coplanarhorizon facets)
+
+  see also:
+    qh_matchneighbor
+
+  notes:
+    only called by qh_matchnewfacets for qh_buildcone and qh_triangulate_facet
+    assumes atfacet is simplicial
+    assumes atfacet->neighbors @ atskip == qh_DUPLICATEridge
+    usually keeps ridge with the widest merge
+    both MRGdupridge and MRGflipped are required merges -- rbox 100 C1,2e-13 D4 t1 | qhull d Qbb
+      can merge flipped f11842 skip 3 into f11862 skip 2 and vice versa (forced by goodmatch/goodmatch2)
+         blocks -- cannot merge f11862 skip 2 and f11863 skip2 (the widest merge)
+         must block -- can merge f11843 skip 3 into f11842 flipped skip 3, but not vice versa
+      can merge f11843 skip 3 into f11863 skip 2, but not vice versa
+    working/unused.h: [jan'19] Dropped qh_matchdupridge_coplanarhorizon, it was the same or slightly worse.  Complex addition, rarely occurs
+
+  design:
+    compute hash value for atfacet and atskip
+    repeat twice -- once to make best matches, once to match the rest
+      for each possible facet in qh.hash_table
+        if it is a matching facet with the same orientation and pass 2
+          make match
+          unless tricoplanar, mark match for merging (qh_MERGEridge)
+          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
+        if it is a matching facet with the same orientation and pass 1
+          test if this is a better match
+      if pass 1,
+        make best match (it will not be merged)
+        set newfacet, facet, matchfacet's hyperplane (removes from mergecycle of coplanarhorizon facets)
+
+*/
+coordT qh_matchdupridge(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
+  boolT same, ismatch, isduplicate= False;
+  int hash, scan;
+  facetT *facet, *newfacet, *nextfacet;
+  facetT *maxmatch= NULL, *maxmatch2= NULL, *goodmatch= NULL, *goodmatch2= NULL;
+  int skip, newskip, nextskip= 0, makematch;
+  int maxskip= 0, maxskip2= 0, goodskip= 0, goodskip2= 0;
+  coordT maxdist= -REALmax, maxdist2= 0.0, dupdist, dupdist2, low, high, maxgood, gooddist= 0.0;
+
+  maxgood= qh_WIDEdupridge * (qh->ONEmerge + qh->DISTround);
+  hash= qh_gethash(qh, hashsize, atfacet->vertices, qh->hull_dim, 1,
+                     SETelem_(atfacet->vertices, atskip));
+  trace2((qh, qh->ferr, 2046, "qh_matchdupridge: find dupridge matches for f%d skip %d hash %d hashcount %d\n",
+          atfacet->id, atskip, hash, *hashcount));
+  for (makematch=0; makematch < 2; makematch++) { /* makematch is false on the first pass and 1 on the second */
+    qh->visit_id++;
+    for (newfacet=atfacet, newskip=atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
+      zinc_(Zhashlookup);
+      nextfacet= NULL; /* exit when ismatch found */
+      newfacet->visitid= qh->visit_id;
+      for (scan=hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
+           scan= (++scan >= hashsize ? 0 : scan)) {
+        if (!facet->dupridge || facet->visitid == qh->visit_id)
+          continue;
+        zinc_(Zhashtests);
+        if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
+          if (SETelem_(newfacet->vertices, newskip) == SETelem_(facet->vertices, skip)) {
+            trace3((qh, qh->ferr, 3053, "qh_matchdupridge: duplicate ridge due to duplicate facets (f%d skip %d and f%d skip %d) previously reported as QH7084.  Maximize dupdist to force vertex merge\n",
+              newfacet->id, newskip, facet->id, skip));
+            isduplicate= True;
+          }
+          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
+          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
+            if (!makematch) {  /* occurs if many merges, e.g., rbox 100 W0 C2,1e-13 D6 t1546872462 | qhull C0 Qt Tcv */
+              qh_fprintf(qh, qh->ferr, 6155, "qhull topology error (qh_matchdupridge): missing qh_DUPLICATEridge at f%d skip %d for new f%d skip %d hash %d ismatch %d.  Set by qh_matchneighbor\n",
+                facet->id, skip, newfacet->id, newskip, hash, ismatch);
+              qh_errexit2(qh, qh_ERRtopology, facet, newfacet);
+            }
+          }else if (!ismatch) {
+            nextfacet= facet;
+            nextskip= skip;
+          }else if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
+            if (makematch) {
+              if (newfacet->tricoplanar) {
+                SETelem_(facet->neighbors, skip)= newfacet;
+                SETelem_(newfacet->neighbors, newskip)= facet;
+                *hashcount -= 2; /* removed two unmatched facets */
+                trace2((qh, qh->ferr, 2075, "qh_matchdupridge: allow tricoplanar dupridge for new f%d skip %d and f%d skip %d\n",
+                    newfacet->id, newskip, facet->id, skip));
+              }else if (goodmatch && goodmatch2) {
+                SETelem_(goodmatch2->neighbors, goodskip2)= qh_MERGEridge;  /* undo selection of goodmatch */
+                SETelem_(facet->neighbors, skip)= newfacet;
+                SETelem_(newfacet->neighbors, newskip)= facet;
+                *hashcount -= 2; /* removed two unmatched facets */
+                trace2((qh, qh->ferr, 2105, "qh_matchdupridge: make good forced merge of dupridge f%d skip %d into f%d skip %d, keep new f%d skip %d and f%d skip %d, dist %4.4g\n",
+                  goodmatch->id, goodskip, goodmatch2->id, goodskip2, newfacet->id, newskip, facet->id, skip, gooddist));
+                goodmatch2= NULL;
+              }else {
+                SETelem_(facet->neighbors, skip)= newfacet;
+                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;  /* resolved by qh_mark_dupridges */
+                *hashcount -= 2; /* removed two unmatched facets */
+                trace3((qh, qh->ferr, 3073, "qh_matchdupridge: make forced merge of dupridge for new f%d skip %d and f%d skip %d, maxdist %4.4g in qh_forcedmerges\n",
+                  newfacet->id, newskip, facet->id, skip, maxdist2));
+              }
+            }else { /* !makematch */
+              if (!facet->normal)
+                qh_setfacetplane(qh, facet); /* qh_mergecycle will ignore 'mergehorizon' facets with normals, too many cases otherwise */
+              if (!newfacet->normal)
+                qh_setfacetplane(qh, newfacet);
+              dupdist= qh_getdistance(qh, facet, newfacet, &low, &high); /* ignore low/high */
+              dupdist2= qh_getdistance(qh, newfacet, facet, &low, &high);
+              if (isduplicate) {
+                goodmatch= NULL;
+                minimize_(dupdist, dupdist2);
+                maxdist= dupdist;
+                maxdist2= REALmax/2;
+                maxmatch= facet;
+                maxskip= skip;
+                maxmatch2= newfacet;
+                maxskip2= newskip;
+                break; /* force maxmatch */
+              }else if (facet->flipped && !newfacet->flipped && dupdist < maxgood) {
+                if (!goodmatch || !goodmatch->flipped || dupdist < gooddist) {
+                  goodmatch= facet;
+                  goodskip= skip;
+                  goodmatch2= newfacet;
+                  goodskip2= newskip;
+                  gooddist= dupdist;
+                  trace3((qh, qh->ferr, 3070, "qh_matchdupridge: try good dupridge flipped f%d skip %d into new f%d skip %d at dist %2.2g otherdist %2.2g\n",
+                    goodmatch->id, goodskip, goodmatch2->id, goodskip2, gooddist, dupdist2));
+                }
+              }else if (newfacet->flipped && !facet->flipped && dupdist2 < maxgood) {
+                if (!goodmatch || !goodmatch->flipped || dupdist2 < gooddist) {
+                  goodmatch= newfacet;
+                  goodskip= newskip;
+                  goodmatch2= facet;
+                  goodskip2= skip;
+                  gooddist= dupdist2;
+                  trace3((qh, qh->ferr, 3071, "qh_matchdupridge: try good dupridge flipped new f%d skip %d into f%d skip %d at dist %2.2g otherdist %2.2g\n",
+                    goodmatch->id, goodskip, goodmatch2->id, goodskip2, gooddist, dupdist));
+                }
+              }else if (dupdist < maxgood && (!newfacet->flipped || facet->flipped)) { /* disallow not-flipped->flipped */
+                if (!goodmatch || (!goodmatch->flipped && dupdist < gooddist)) {
+                  goodmatch= facet;
+                  goodskip= skip;
+                  goodmatch2= newfacet;
+                  goodskip2= newskip;
+                  gooddist= dupdist;
+                  trace3((qh, qh->ferr, 3072, "qh_matchdupridge: try good dupridge f%d skip %d into new f%d skip %d at dist %2.2g otherdist %2.2g\n",
+                    goodmatch->id, goodskip, goodmatch2->id, goodskip2, gooddist, dupdist2));
+                }
+              }else if (dupdist2 < maxgood && (!facet->flipped || newfacet->flipped)) { /* disallow not-flipped->flipped */
+                if (!goodmatch || (!goodmatch->flipped && dupdist2 < gooddist)) {
+                  goodmatch= newfacet;
+                  goodskip= newskip;
+                  goodmatch2= facet;
+                  goodskip2= skip;
+                  gooddist= dupdist2;
+                  trace3((qh, qh->ferr, 3018, "qh_matchdupridge: try good dupridge new f%d skip %d into f%d skip %d at dist %2.2g otherdist %2.2g\n",
+                    goodmatch->id, goodskip, goodmatch2->id, goodskip2, gooddist, dupdist));
+                }
+              }else if (!goodmatch) { /* otherwise match the furthest apart facets */
+                if (!newfacet->flipped || facet->flipped) {
+                  minimize_(dupdist, dupdist2);
+                }
+                if (dupdist > maxdist) { /* could keep !flipped->flipped, but probably lost anyway */
+                  maxdist2= maxdist;
+                  maxdist= dupdist;
+                  maxmatch= facet;
+                  maxskip= skip;
+                  maxmatch2= newfacet;
+                  maxskip2= newskip;
+                  trace3((qh, qh->ferr, 3055, "qh_matchdupridge: try furthest dupridge f%d skip %d new f%d skip %d at dist %2.2g\n",
+                    maxmatch->id, maxskip, maxmatch2->id, maxskip2, maxdist));
+                }else if (dupdist > maxdist2)
+                  maxdist2= dupdist;
+              }
+            }
+          }
+        }
+      } /* end of foreach entry in qh.hash_table starting at 'hash' */
+      if (makematch && SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
+        qh_fprintf(qh, qh->ferr, 6156, "qhull internal error (qh_matchdupridge): no MERGEridge match for dupridge new f%d skip %d at hash %d..%d\n",
+                    newfacet->id, newskip, hash, scan);
+        qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
+      }
+    } /* end of foreach newfacet at 'hash' */
+    if (!makematch) {
+      if (!maxmatch && !goodmatch) {
+        qh_fprintf(qh, qh->ferr, 6157, "qhull internal error (qh_matchdupridge): no maximum or good match for dupridge new f%d skip %d at hash %d..%d\n",
+          atfacet->id, atskip, hash, scan);
+        qh_errexit(qh, qh_ERRqhull, atfacet, NULL);
+      }
+      if (goodmatch) {
+        SETelem_(goodmatch->neighbors, goodskip)= goodmatch2;
+        SETelem_(goodmatch2->neighbors, goodskip2)= goodmatch;
+        *hashcount -= 2; /* removed two unmatched facets */
+        if (goodmatch->flipped) {
+          if (!goodmatch2->flipped) {
+            zzinc_(Zflipridge);
+          }else {
+            zzinc_(Zflipridge2);
+            /* qh_joggle_restart called by qh_matchneighbor if qh_DUPLICATEridge */
+          }
+        }
+        /* previously traced */
+      }else {
+        SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=NULL by QH6157 */
+        SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
+        *hashcount -= 2; /* removed two unmatched facets */
+        zzinc_(Zmultiridge);
+        /* qh_joggle_restart called by qh_matchneighbor if qh_DUPLICATEridge */
+        trace0((qh, qh->ferr, 25, "qh_matchdupridge: keep dupridge f%d skip %d and f%d skip %d, dist %4.4g\n",
+          maxmatch2->id, maxskip2, maxmatch->id, maxskip, maxdist));
+      }
+    }
+  }
+  if (goodmatch)
+    return gooddist;
+  return maxdist2;
+} /* matchdupridge */
+
+#else /* qh_NOmerge */
+coordT qh_matchdupridge(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(atfacet)
+  QHULL_UNUSED(atskip)
+  QHULL_UNUSED(hashsize)
+  QHULL_UNUSED(hashcount)
+
+  return 0.0;
+}
+#endif /* qh_NOmerge */
+
+/*---------------------------------
+
+  qh_nearcoplanar()
+    for all facets, remove near-inside points from facet->coplanarset
+    coplanar points defined by innerplane from qh_outerinner()
+
+  returns:
+    if qh->KEEPcoplanar && !qh->KEEPinside
+      facet->coplanarset only contains coplanar points
+    if qh.JOGGLEmax
+      drops inner plane by another qh.JOGGLEmax diagonal since a
+        vertex could shift out while a coplanar point shifts in
+
+  notes:
+    used for qh.PREmerge and qh.JOGGLEmax
+    must agree with computation of qh.NEARcoplanar in qh_detroundoff
+
+  design:
+    if not keeping coplanar or inside points
+      free all coplanar sets
+    else if not keeping both coplanar and inside points
+      remove !coplanar or !inside points from coplanar sets
+*/
+void qh_nearcoplanar(qhT *qh /* qh.facet_list */) {
+  facetT *facet;
+  pointT *point, **pointp;
+  int numpart;
+  realT dist, innerplane;
+
+  if (!qh->KEEPcoplanar && !qh->KEEPinside) {
+    FORALLfacets {
+      if (facet->coplanarset)
+        qh_setfree(qh, &facet->coplanarset);
+    }
+  }else if (!qh->KEEPcoplanar || !qh->KEEPinside) {
+    qh_outerinner(qh, NULL, NULL, &innerplane);
+    if (qh->JOGGLEmax < REALmax/2)
+      innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
+    numpart= 0;
+    FORALLfacets {
+      if (facet->coplanarset) {
+        FOREACHpoint_(facet->coplanarset) {
+          numpart++;
+          qh_distplane(qh, point, facet, &dist);
+          if (dist < innerplane) {
+            if (!qh->KEEPinside)
+              SETref_(point)= NULL;
+          }else if (!qh->KEEPcoplanar)
+            SETref_(point)= NULL;
+        }
+        qh_setcompact(qh, facet->coplanarset);
+      }
+    }
+    zzadd_(Zcheckpart, numpart);
+  }
+} /* nearcoplanar */
+
+/*---------------------------------
+
+  qh_nearvertex(qh, facet, point, bestdist )
+    return nearest vertex in facet to point
+
+  returns:
+    vertex and its distance
+
+  notes:
+    if qh.DELAUNAY
+      distance is measured in the input set
+    searches neighboring tricoplanar facets (requires vertexneighbors)
+      Slow implementation.  Recomputes vertex set for each point.
+    The vertex set could be stored in the qh.keepcentrum facet.
+*/
+vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp) {
+  realT bestdist= REALmax, dist;
+  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
+  coordT *center;
+  facetT *neighbor, **neighborp;
+  setT *vertices;
+  int dim= qh->hull_dim;
+
+  if (qh->DELAUNAY)
+    dim--;
+  if (facet->tricoplanar) {
+    if (!qh->VERTEXneighbors || !facet->center) {
+      qh_fprintf(qh, qh->ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    vertices= qh_settemp(qh, qh->TEMPsize);
+    apex= SETfirstt_(facet->vertices, vertexT);
+    center= facet->center;
+    FOREACHneighbor_(apex) {
+      if (neighbor->center == center) {
+        FOREACHvertex_(neighbor->vertices)
+          qh_setappend(qh, &vertices, vertex);
+      }
+    }
+  }else
+    vertices= facet->vertices;
+  FOREACHvertex_(vertices) {
+    dist= qh_pointdist(vertex->point, point, -dim);
+    if (dist < bestdist) {
+      bestdist= dist;
+      bestvertex= vertex;
+    }
+  }
+  if (facet->tricoplanar)
+    qh_settempfree(qh, &vertices);
+  *bestdistp= sqrt(bestdist);
+  if (!bestvertex) {
+      qh_fprintf(qh, qh->ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(qh, point));
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  trace3((qh, qh->ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
+        bestvertex->id, *bestdistp, facet->id, qh_pointid(qh, point))); /* bestvertex!=0 by QH2161 */
+  return bestvertex;
+} /* nearvertex */
+
+/*---------------------------------
+
+  qh_newhashtable(qh, newsize )
+    returns size of qh.hash_table of at least newsize slots
+
+  notes:
+    assumes qh.hash_table is NULL
+    qh_HASHfactor determines the number of extra slots
+    size is not divisible by 2, 3, or 5
+*/
+int qh_newhashtable(qhT *qh, int newsize) {
+  int size;
+
+  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
+  while (True) {
+    if (newsize<0 || size<0) {
+        qh_fprintf(qh, qh->qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
+        qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
+    }
+    if ((size%3) && (size%5))
+      break;
+    size += 2;
+    /* loop terminates because there is an infinite number of primes */
+  }
+  qh->hash_table= qh_setnew(qh, size);
+  qh_setzero(qh, qh->hash_table, 0, size);
+  return size;
+} /* newhashtable */
+
+/*---------------------------------
+
+  qh_newvertex(qh, point )
+    returns a new vertex for point
+*/
+vertexT *qh_newvertex(qhT *qh, pointT *point) {
+  vertexT *vertex;
+
+  zinc_(Ztotvertices);
+  vertex= (vertexT *)qh_memalloc(qh, (int)sizeof(vertexT));
+  memset((char *) vertex, (size_t)0, sizeof(vertexT));
+  if (qh->vertex_id == UINT_MAX) {
+    qh_memfree(qh, vertex, (int)sizeof(vertexT));
+    qh_fprintf(qh, qh->ferr, 6159, "qhull error: 2^32 or more vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
+    qh_errexit(qh, qh_ERRother, NULL, NULL);
+  }
+  if (qh->vertex_id == qh->tracevertex_id)
+    qh->tracevertex= vertex;
+  vertex->id= qh->vertex_id++;
+  vertex->point= point;
+  trace4((qh, qh->ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(qh, vertex->point),
+          vertex->id));
+  return(vertex);
+} /* newvertex */
+
+/*---------------------------------
+
+  qh_nextfacet2d( facet, &nextvertex )
+    return next facet and vertex for a 2d facet in qh_ORIENTclock order
+    returns NULL on error
+
+  notes:
+    in qh_ORIENTclock order (default counter-clockwise)
+    nextvertex is in between the two facets
+    does not use qhT or qh_errexit [QhullFacet.cpp]
+
+  design:
+    see io_r.c/qh_printextremes_2d
+*/
+facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp) {
+  facetT *nextfacet;
+
+  if (facet->toporient ^ qh_ORIENTclock) {
+    *nextvertexp= SETfirstt_(facet->vertices, vertexT);
+    nextfacet= SETfirstt_(facet->neighbors, facetT);
+  }else {
+    *nextvertexp= SETsecondt_(facet->vertices, vertexT);
+    nextfacet= SETsecondt_(facet->neighbors, facetT);
+  }
+  return nextfacet;
+} /* nextfacet2d */
+
+/*---------------------------------
+
+  qh_nextridge3d( atridge, facet, &vertex )
+    return next ridge and vertex for a 3d facet
+    returns NULL on error
+    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT.
+
+  notes:
+    in qh_ORIENTclock order
+    this is a O(n^2) implementation to trace all ridges
+    be sure to stop on any 2nd visit
+    same as QhullRidge::nextRidge3d
+    does not use qhT or qh_errexit [QhullFacet.cpp]
+
+  design:
+    for each ridge
+      exit if it is the ridge after atridge
+*/
+ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
+  vertexT *atvertex, *vertex, *othervertex;
+  ridgeT *ridge, **ridgep;
+
+  if ((atridge->top == facet) ^ qh_ORIENTclock)
+    atvertex= SETsecondt_(atridge->vertices, vertexT);
+  else
+    atvertex= SETfirstt_(atridge->vertices, vertexT);
+  FOREACHridge_(facet->ridges) {
+    if (ridge == atridge)
+      continue;
+    if ((ridge->top == facet) ^ qh_ORIENTclock) {
+      othervertex= SETsecondt_(ridge->vertices, vertexT);
+      vertex= SETfirstt_(ridge->vertices, vertexT);
+    }else {
+      vertex= SETsecondt_(ridge->vertices, vertexT);
+      othervertex= SETfirstt_(ridge->vertices, vertexT);
+    }
+    if (vertex == atvertex) {
+      if (vertexp)
+        *vertexp= othervertex;
+      return ridge;
+    }
+  }
+  return NULL;
+} /* nextridge3d */
+
+/*---------------------------------
+
+  qh_opposite_vertex(qh, facetA, neighbor )
+    return the opposite vertex in facetA to neighbor
+
+*/
+vertexT *qh_opposite_vertex(qhT *qh, facetT *facetA,  facetT *neighbor) {
+    vertexT *opposite= NULL;
+    facetT *facet;
+    int facet_i, facet_n;
+
+    if (facetA->simplicial) {
+      FOREACHfacet_i_(qh, facetA->neighbors) {
+        if (facet == neighbor) {
+          opposite= SETelemt_(facetA->vertices, facet_i, vertexT);
+          break;
+        }
+      }
+    }
+    if (!opposite) {
+      qh_fprintf(qh, qh->ferr, 6396, "qhull internal error (qh_opposite_vertex): opposite vertex in facet f%d to neighbor f%d is not defined.  Either is facet is not simplicial or neighbor not found\n",
+        facetA->id, neighbor->id);
+      qh_errexit2(qh, qh_ERRqhull, facetA, neighbor);
+    }
+    return opposite;
+} /* opposite_vertex */
+
+/*---------------------------------
+
+  qh_outcoplanar()
+    move points from all facets' outsidesets to their coplanarsets
+
+  notes:
+    for post-processing under qh.NARROWhull
+
+  design:
+    for each facet
+      for each outside point for facet
+        partition point into coplanar set
+*/
+void qh_outcoplanar(qhT *qh /* facet_list */) {
+  pointT *point, **pointp;
+  facetT *facet;
+  realT dist;
+
+  trace1((qh, qh->ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh->NARROWhull\n"));
+  FORALLfacets {
+    FOREACHpoint_(facet->outsideset) {
+      qh->num_outside--;
+      if (qh->KEEPcoplanar || qh->KEEPnearinside) {
+        qh_distplane(qh, point, facet, &dist);
+        zinc_(Zpartition);
+        qh_partitioncoplanar(qh, point, facet, &dist, qh->findbestnew);
+      }
+    }
+    qh_setfree(qh, &facet->outsideset);
+  }
+} /* outcoplanar */
+
+/*---------------------------------
+
+  qh_point(qh, id )
+    return point for a point id, or NULL if unknown
+
+  alternative code:
+    return((pointT *)((unsigned long)qh.first_point
+           + (unsigned long)((id)*qh.normal_size)));
+*/
+pointT *qh_point(qhT *qh, int id) {
+
+  if (id < 0)
+    return NULL;
+  if (id < qh->num_points)
+    return qh->first_point + id * qh->hull_dim;
+  id -= qh->num_points;
+  if (id < qh_setsize(qh, qh->other_points))
+    return SETelemt_(qh->other_points, id, pointT);
+  return NULL;
+} /* point */
+
+/*---------------------------------
+
+  qh_point_add(qh, set, point, elem )
+    stores elem at set[point.id]
+
+  returns:
+    access function for qh_pointfacet and qh_pointvertex
+
+  notes:
+    checks point.id
+*/
+void qh_point_add(qhT *qh, setT *set, pointT *point, void *elem) {
+  int id, size;
+
+  SETreturnsize_(set, size);
+  if ((id= qh_pointid(qh, point)) < 0)
+    qh_fprintf(qh, qh->ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
+      (void *) point, id);
+  else if (id >= size) {
+    qh_fprintf(qh, qh->ferr, 6160, "qhull internal error (point_add): point p%d is out of bounds(%d)\n",
+             id, size);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }else
+    SETelem_(set, id)= elem;
+} /* point_add */
+
+
+/*---------------------------------
+
+  qh_pointfacet()
+    return temporary set of facet for each point
+    the set is indexed by point id
+    at most one facet per point, arbitrary selection
+
+  notes:
+    each point is assigned to at most one of vertices, coplanarset, or outsideset
+    unassigned points are interior points or
+    vertices assigned to one of its facets
+    coplanarset assigned to the facet
+    outside set assigned to the facet
+    NULL if no facet for point (inside)
+      includes qh.GOODpointp
+
+  access:
+    FOREACHfacet_i_(qh, facets) { ... }
+    SETelem_(facets, i)
+
+  design:
+    for each facet
+      add each vertex
+      add each coplanar point
+      add each outside point
+*/
+setT *qh_pointfacet(qhT *qh /* qh.facet_list */) {
+  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
+  setT *facets;
+  facetT *facet;
+  vertexT *vertex, **vertexp;
+  pointT *point, **pointp;
+
+  facets= qh_settemp(qh, numpoints);
+  qh_setzero(qh, facets, 0, numpoints);
+  qh->vertex_visit++;
+  FORALLfacets {
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->visitid != qh->vertex_visit) {
+        vertex->visitid= qh->vertex_visit;
+        qh_point_add(qh, facets, vertex->point, facet);
+      }
+    }
+    FOREACHpoint_(facet->coplanarset)
+      qh_point_add(qh, facets, point, facet);
+    FOREACHpoint_(facet->outsideset)
+      qh_point_add(qh, facets, point, facet);
+  }
+  return facets;
+} /* pointfacet */
+
+/*---------------------------------
+
+  qh_pointvertex(qh )
+    return temporary set of vertices indexed by point id
+    entry is NULL if no vertex for a point
+      this will include qh.GOODpointp
+
+  access:
+    FOREACHvertex_i_(qh, vertices) { ... }
+    SETelem_(vertices, i)
+*/
+setT *qh_pointvertex(qhT *qh /* qh.facet_list */) {
+  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
+  setT *vertices;
+  vertexT *vertex;
+
+  vertices= qh_settemp(qh, numpoints);
+  qh_setzero(qh, vertices, 0, numpoints);
+  FORALLvertices
+    qh_point_add(qh, vertices, vertex->point, vertex);
+  return vertices;
+} /* pointvertex */
+
+
+/*---------------------------------
+
+  qh_prependfacet(qh, facet, facetlist )
+    prepend facet to the start of a facetlist
+
+  returns:
+    increments qh.numfacets
+    updates facetlist, qh.facet_list, facet_next
+
+  notes:
+    be careful of prepending since it can lose a pointer.
+      e.g., can lose _next by deleting and then prepending before _next
+*/
+void qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist) {
+  facetT *prevfacet, *list;
+
+  trace4((qh, qh->ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
+          facet->id, getid_(*facetlist)));
+  if (!*facetlist)
+    (*facetlist)= qh->facet_tail;
+  list= *facetlist;
+  prevfacet= list->previous;
+  facet->previous= prevfacet;
+  if (prevfacet)
+    prevfacet->next= facet;
+  list->previous= facet;
+  facet->next= *facetlist;
+  if (qh->facet_list == list)  /* this may change *facetlist */
+    qh->facet_list= facet;
+  if (qh->facet_next == list)
+    qh->facet_next= facet;
+  *facetlist= facet;
+  qh->num_facets++;
+} /* prependfacet */
+
+
+/*---------------------------------
+
+  qh_printhashtable(qh, fp )
+    print hash table to fp
+
+  notes:
+    not in I/O to avoid bringing io_r.c in
+
+  design:
+    for each hash entry
+      if defined
+        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
+          print entry and neighbors
+*/
+void qh_printhashtable(qhT *qh, FILE *fp) {
+  facetT *facet, *neighbor;
+  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
+  vertexT *vertex, **vertexp;
+
+  FOREACHfacet_i_(qh, qh->hash_table) {
+    if (facet) {
+      FOREACHneighbor_i_(qh, facet) {
+        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
+          break;
+      }
+      if (neighbor_i == neighbor_n)
+        continue;
+      qh_fprintf(qh, fp, 9283, "hash %d f%d ", facet_i, facet->id);
+      FOREACHvertex_(facet->vertices)
+        qh_fprintf(qh, fp, 9284, "v%d ", vertex->id);
+      qh_fprintf(qh, fp, 9285, "\n neighbors:");
+      FOREACHneighbor_i_(qh, facet) {
+        if (neighbor == qh_MERGEridge)
+          id= -3;
+        else if (neighbor == qh_DUPLICATEridge)
+          id= -2;
+        else
+          id= getid_(neighbor);
+        qh_fprintf(qh, fp, 9286, " %d", id);
+      }
+      qh_fprintf(qh, fp, 9287, "\n");
+    }
+  }
+} /* printhashtable */
+
+/*---------------------------------
+
+  qh_printlists(qh)
+    print out facet and vertex lists for debugging (without 'f/v' tags)
+
+  notes:
+    not in I/O to avoid bringing io_r.c in
+*/
+void qh_printlists(qhT *qh) {
+  facetT *facet;
+  vertexT *vertex;
+  int count= 0;
+
+  qh_fprintf(qh, qh->ferr, 3062, "qh_printlists: max_outside %2.2g all facets:", qh->max_outside);
+  FORALLfacets{
+    if (++count % 100 == 0)
+      qh_fprintf(qh, qh->ferr, 8109, "\n     ");
+    qh_fprintf(qh, qh->ferr, 8110, " %d", facet->id);
+  }
+    qh_fprintf(qh, qh->ferr, 8111, "\n  qh.visible_list f%d, newfacet_list f%d, facet_next f%d for qh_addpoint\n  qh.newvertex_list v%d all vertices:",
+      getid_(qh->visible_list), getid_(qh->newfacet_list), getid_(qh->facet_next), getid_(qh->newvertex_list));
+  count= 0;
+  FORALLvertices{
+    if (++count % 100 == 0)
+      qh_fprintf(qh, qh->ferr, 8112, "\n     ");
+    qh_fprintf(qh, qh->ferr, 8113, " %d", vertex->id);
+  }
+  qh_fprintf(qh, qh->ferr, 8114, "\n");
+} /* printlists */
+
+/*---------------------------------
+
+  qh_replacefacetvertex(qh, facet, oldvertex, newvertex )
+    replace oldvertex with newvertex in f.vertices
+    vertices are inverse sorted by vertex->id
+
+  returns:
+    toporient is flipped if an odd parity, position change
+
+  notes:
+    for simplicial facets in qh_rename_adjacentvertex
+    see qh_addfacetvertex
+*/
+void qh_replacefacetvertex(qhT *qh, facetT *facet, vertexT *oldvertex, vertexT *newvertex) {
+  vertexT *vertex;
+  facetT *neighbor;
+  int vertex_i, vertex_n= 0;
+  int old_i= -1, new_i= -1;
+
+  trace3((qh, qh->ferr, 3038, "qh_replacefacetvertex: replace v%d with v%d in f%d\n", oldvertex->id, newvertex->id, facet->id));
+  if (!facet->simplicial) {
+    qh_fprintf(qh, qh->ferr, 6283, "qhull internal error (qh_replacefacetvertex): f%d is not simplicial\n", facet->id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  FOREACHvertex_i_(qh, facet->vertices) {
+    if (new_i == -1 && vertex->id < newvertex->id) {
+      new_i= vertex_i;
+    }else if (vertex->id == newvertex->id) {
+      qh_fprintf(qh, qh->ferr, 6281, "qhull internal error (qh_replacefacetvertex): f%d already contains new v%d\n", facet->id, newvertex->id);
+      qh_errexit(qh, qh_ERRqhull, facet, NULL);
+    }
+    if (vertex->id == oldvertex->id) {
+      old_i= vertex_i;
+    }
+  }
+  if (old_i == -1) {
+    qh_fprintf(qh, qh->ferr, 6282, "qhull internal error (qh_replacefacetvertex): f%d does not contain old v%d\n", facet->id, oldvertex->id);
+    qh_errexit(qh, qh_ERRqhull, facet, NULL);
+  }
+  if (new_i == -1) {
+    new_i= vertex_n;
+  }
+  if (old_i < new_i)
+    new_i--;
+  if ((old_i & 0x1) != (new_i & 0x1))
+    facet->toporient ^= 1;
+  qh_setdelnthsorted(qh, facet->vertices, old_i);
+  qh_setaddnth(qh, &facet->vertices, new_i, newvertex);
+  neighbor= SETelemt_(facet->neighbors, old_i, facetT);
+  qh_setdelnthsorted(qh, facet->neighbors, old_i);
+  qh_setaddnth(qh, &facet->neighbors, new_i, neighbor);
+} /* replacefacetvertex */
+
+/*---------------------------------
+
+  qh_resetlists(qh, stats, qh_RESETvisible )
+    reset newvertex_list, newfacet_list, visible_list, NEWfacets, NEWtentative
+    if stats,
+      maintains statistics
+    if resetVisible,
+      visible_list is restored to facet_list
+      otherwise, f.visible/f.replace is retained
+
+  returns:
+    newvertex_list, newfacet_list, visible_list are NULL
+
+  notes:
+    To delete visible facets, call qh_deletevisible before qh_resetlists
+*/
+void qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /* qh.newvertex_list newfacet_list visible_list */) {
+  vertexT *vertex;
+  facetT *newfacet, *visible;
+  int totnew=0, totver=0;
+
+  trace2((qh, qh->ferr, 2066, "qh_resetlists: reset newvertex_list v%d, newfacet_list f%d, visible_list f%d, facet_list f%d next f%d vertex_list v%d -- NEWfacets? %d, NEWtentative? %d, stats? %d\n",
+    getid_(qh->newvertex_list), getid_(qh->newfacet_list), getid_(qh->visible_list), getid_(qh->facet_list), getid_(qh->facet_next), getid_(qh->vertex_list), qh->NEWfacets, qh->NEWtentative, stats));
+  if (stats) {
+    FORALLvertex_(qh->newvertex_list)
+      totver++;
+    FORALLnew_facets
+      totnew++;
+    zadd_(Zvisvertextot, totver);
+    zmax_(Zvisvertexmax, totver);
+    zadd_(Znewfacettot, totnew);
+    zmax_(Znewfacetmax, totnew);
+  }
+  FORALLvertex_(qh->newvertex_list)
+    vertex->newfacet= False;
+  qh->newvertex_list= NULL;
+  qh->first_newfacet= 0;
+  FORALLnew_facets {
+    newfacet->newfacet= False;
+    newfacet->dupridge= False;
+  }
+  qh->newfacet_list= NULL;
+  if (resetVisible) {
+    FORALLvisible_facets {
+      visible->f.replace= NULL;
+      visible->visible= False;
+    }
+    qh->num_visible= 0;
+  }
+  qh->visible_list= NULL;
+  qh->NEWfacets= False;
+  qh->NEWtentative= False;
+} /* resetlists */
+
+/*---------------------------------
+
+  qh_setvoronoi_all(qh)
+    compute Voronoi centers for all facets
+    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
+
+  returns:
+    facet->center is the Voronoi center
+
+  notes:
+    unused/untested code: please email bradb@shore.net if this works ok for you
+
+  use:
+    FORALLvertices {...} to locate the vertex for a point.
+    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
+*/
+void qh_setvoronoi_all(qhT *qh) {
+  facetT *facet;
+
+  qh_clearcenters(qh, qh_ASvoronoi);
+  qh_vertexneighbors(qh);
+
+  FORALLfacets {
+    if (!facet->normal || !facet->upperdelaunay || qh->UPPERdelaunay) {
+      if (!facet->center)
+        facet->center= qh_facetcenter(qh, facet->vertices);
+    }
+  }
+} /* setvoronoi_all */
+
+#ifndef qh_NOmerge
+/*---------------------------------
+
+  qh_triangulate()
+    triangulate non-simplicial facets on qh.facet_list,
+    if qh->VORONOI, sets Voronoi centers of non-simplicial facets
+    nop if hasTriangulation
+
+  returns:
+    all facets simplicial
+    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
+    resets qh.newfacet_list and visible_list
+
+  notes:
+    called by qh_prepare_output and user_eg2_r.c
+    call after qh_check_output since may switch to Voronoi centers, and qh_checkconvex skips f.tricoplanar facets
+    Output may overwrite ->f.triowner with ->f.area
+    while running, 'triangulated_facet_list' is a list of
+       one non-simplicial facet followed by its 'f.tricoplanar' triangulated facets
+    See qh_buildcone
+*/
+void qh_triangulate(qhT *qh /* qh.facet_list */) {
+  facetT *facet, *nextfacet, *owner;
+  facetT *neighbor, *visible= NULL, *facet1, *facet2, *triangulated_facet_list= NULL;
+  facetT *orig_neighbor= NULL, *otherfacet;
+  vertexT *triangulated_vertex_list= NULL;
+  mergeT *merge;
+  mergeType mergetype;
+  int neighbor_i, neighbor_n;
+  boolT onlygood= qh->ONLYgood;
+
+  if (qh->hasTriangulation)
+      return;
+  trace1((qh, qh->ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
+  if (qh->hull_dim == 2)
+    return;
+  if (qh->VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
+    qh_clearcenters(qh, qh_ASvoronoi);
+    qh_vertexneighbors(qh);
+  }
+  qh->ONLYgood= False; /* for makenew_nonsimplicial */
+  qh->visit_id++;
+  qh_initmergesets(qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */);
+  qh->newvertex_list= qh->vertex_tail;
+  for (facet=qh->facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
+    nextfacet= facet->next;
+    if (facet->visible || facet->simplicial)
+      continue;
+    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
+    if (!triangulated_facet_list)
+      triangulated_facet_list= facet;  /* will be first triangulated facet */
+    qh_triangulate_facet(qh, facet, &triangulated_vertex_list); /* qh_resetlists ! */
+  }
+  /* qh_checkpolygon invalid due to f.visible without qh.visible_list */
+  trace2((qh, qh->ferr, 2047, "qh_triangulate: delete null facets from facetlist f%d.  A null facet has the same first (apex) and second vertices\n", getid_(triangulated_facet_list)));
+  for (facet=triangulated_facet_list; facet && facet->next; facet= nextfacet) {
+    nextfacet= facet->next;
+    if (facet->visible)
+      continue;
+    if (facet->ridges) {
+      if (qh_setsize(qh, facet->ridges) > 0) {
+        qh_fprintf(qh, qh->ferr, 6161, "qhull internal error (qh_triangulate): ridges still defined for f%d\n", facet->id);
+        qh_errexit(qh, qh_ERRqhull, facet, NULL);
+      }
+      qh_setfree(qh, &facet->ridges);
+    }
+    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
+      zinc_(Ztrinull);
+      qh_triangulate_null(qh, facet); /* will delete facet */
+    }
+  }
+  trace2((qh, qh->ferr, 2048, "qh_triangulate: delete %d or more mirrored facets.  Mirrored facets have the same vertices due to a null facet\n", qh_setsize(qh, qh->degen_mergeset)));
+  qh->visible_list= qh->facet_tail;
+  while ((merge= (mergeT *)qh_setdellast(qh->degen_mergeset))) {
+    facet1= merge->facet1;
+    facet2= merge->facet2;
+    mergetype= merge->mergetype;
+    qh_memfree(qh, merge, (int)sizeof(mergeT));
+    if (mergetype == MRGmirror) {
+      zinc_(Ztrimirror);
+      qh_triangulate_mirror(qh, facet1, facet2);  /* will delete both facets */
+    }
+  }
+  qh_freemergesets(qh);
+  trace2((qh, qh->ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(triangulated_vertex_list)));
+  qh->newvertex_list= triangulated_vertex_list;  /* all vertices of triangulated facets */
+  qh->visible_list= NULL;
+  qh_update_vertexneighbors(qh /* qh.newvertex_list, empty newfacet_list and visible_list */);
+  qh_resetlists(qh, False, !qh_RESETvisible /* qh.newvertex_list, empty newfacet_list and visible_list */);
+
+  trace2((qh, qh->ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(triangulated_facet_list)));
+  trace2((qh, qh->ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
+  FORALLfacet_(triangulated_facet_list) {
+    if (facet->tricoplanar && !facet->visible) {
+      FOREACHneighbor_i_(qh, facet) {
+        if (neighbor_i == 0) {  /* first iteration */
+          if (neighbor->tricoplanar)
+            orig_neighbor= neighbor->f.triowner;
+          else
+            orig_neighbor= neighbor;
+        }else {
+          if (neighbor->tricoplanar)
+            otherfacet= neighbor->f.triowner;
+          else
+            otherfacet= neighbor;
+          if (orig_neighbor == otherfacet) {
+            zinc_(Ztridegen);
+            facet->degenerate= True;
+            break;
+          }
+        }
+      }
+    }
+  }
+  if (qh->IStracing >= 4)
+    qh_printlists(qh);
+  trace2((qh, qh->ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
+  owner= NULL;
+  visible= NULL;
+  for (facet=triangulated_facet_list; facet && facet->next; facet= nextfacet) {
+    /* deleting facets, triangulated_facet_list is no longer valid */
+    nextfacet= facet->next;
+    if (facet->visible) {
+      if (facet->tricoplanar) { /* a null or mirrored facet */
+        qh_delfacet(qh, facet);
+        qh->num_visible--;
+      }else {  /* a non-simplicial facet followed by its tricoplanars */
+        if (visible && !owner) {
+          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
+          trace2((qh, qh->ferr, 2053, "qh_triangulate: delete f%d.  All tricoplanar facets degenerate for non-simplicial facet\n",
+                       visible->id));
+          qh_delfacet(qh, visible);
+          qh->num_visible--;
+        }
+        visible= facet;
+        owner= NULL;
+      }
+    }else if (facet->tricoplanar) {
+      if (facet->f.triowner != visible || visible==NULL) {
+        qh_fprintf(qh, qh->ferr, 6162, "qhull internal error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
+        qh_errexit2(qh, qh_ERRqhull, facet, visible);
+      }
+      if (owner)
+        facet->f.triowner= owner;
+      else if (!facet->degenerate) {
+        owner= facet;
+        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
+        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
+        facet->coplanarset= visible->coplanarset;
+        facet->outsideset= visible->outsideset;
+        visible->coplanarset= NULL;
+        visible->outsideset= NULL;
+        if (!qh->TRInormals) { /* center and normal copied to tricoplanar facets */
+          visible->center= NULL;
+          visible->normal= NULL;
+        }
+        qh_delfacet(qh, visible);
+        qh->num_visible--;
+      }
+    }
+    facet->degenerate= False; /* reset f.degenerate set by qh_triangulate*/
+  }
+  if (visible && !owner) {
+    trace2((qh, qh->ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
+                 visible->id));
+    qh_delfacet(qh, visible);
+    qh->num_visible--;
+  }
+  qh->ONLYgood= onlygood; /* restore value */
+  if (qh->CHECKfrequently)
+    qh_checkpolygon(qh, qh->facet_list);
+  qh->hasTriangulation= True;
+} /* triangulate */
+
+
+/*---------------------------------
+
+  qh_triangulate_facet(qh, facetA, &firstVertex )
+    triangulate a non-simplicial facet
+      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
+  returns:
+    qh.newfacet_list == simplicial facets
+      facet->tricoplanar set and ->keepcentrum false
+      facet->degenerate set if duplicated apex
+      facet->f.trivisible set to facetA
+      facet->center copied from facetA (created if qh_ASvoronoi)
+        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
+      facet->normal,offset,maxoutside copied from facetA
+
+  notes:
+      only called by qh_triangulate
+      qh_makenew_nonsimplicial uses neighbor->seen for the same
+      if qh.TRInormals, newfacet->normal will need qh_free
+        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
+        keepcentrum is also set on Zwidefacet in qh_mergefacet
+        freed by qh_clearcenters
+
+  see also:
+      qh_addpoint() -- add a point
+      qh_makenewfacets() -- construct a cone of facets for a new vertex
+
+  design:
+      if qh_ASvoronoi,
+         compute Voronoi center (facet->center)
+      select first vertex (highest ID to preserve ID ordering of ->vertices)
+      triangulate from vertex to ridges
+      copy facet->center, normal, offset
+      update vertex neighbors
+*/
+void qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex) {
+  facetT *newfacet;
+  facetT *neighbor, **neighborp;
+  vertexT *apex;
+  int numnew=0;
+
+  trace3((qh, qh->ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
+
+  qh->first_newfacet= qh->facet_id;
+  if (qh->IStracing >= 4)
+    qh_printfacet(qh, qh->ferr, facetA);
+  FOREACHneighbor_(facetA) {
+    neighbor->seen= False;
+    neighbor->coplanarhorizon= False;
+  }
+  if (qh->CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
+  && fabs_(facetA->normal[qh->hull_dim -1]) >= qh->ANGLEround * qh_ZEROdelaunay) {
+    facetA->center= qh_facetcenter(qh, facetA->vertices);
+  }
+  qh->visible_list= qh->newfacet_list= qh->facet_tail;
+  facetA->visitid= qh->visit_id;
+  apex= SETfirstt_(facetA->vertices, vertexT);
+  qh_makenew_nonsimplicial(qh, facetA, apex, &numnew);
+  qh_willdelete(qh, facetA, NULL);
+  FORALLnew_facets {
+    newfacet->tricoplanar= True;
+    newfacet->f.trivisible= facetA;
+    newfacet->degenerate= False;
+    newfacet->upperdelaunay= facetA->upperdelaunay;
+    newfacet->good= facetA->good;
+    if (qh->TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
+      newfacet->keepcentrum= True;
+      if(facetA->normal){
+        newfacet->normal= (coordT *)qh_memalloc(qh, qh->normal_size);
+        memcpy((char *)newfacet->normal, facetA->normal, (size_t)qh->normal_size);
+      }
+      if (qh->CENTERtype == qh_AScentrum)
+        newfacet->center= qh_getcentrum(qh, newfacet);
+      else if (qh->CENTERtype == qh_ASvoronoi && facetA->center){
+        newfacet->center= (coordT *)qh_memalloc(qh, qh->center_size);
+        memcpy((char *)newfacet->center, facetA->center, (size_t)qh->center_size);
+      }
+    }else {
+      newfacet->keepcentrum= False;
+      /* one facet will have keepcentrum=True at end of qh_triangulate */
+      newfacet->normal= facetA->normal;
+      newfacet->center= facetA->center;
+    }
+    newfacet->offset= facetA->offset;
+#if qh_MAXoutside
+    newfacet->maxoutside= facetA->maxoutside;
+#endif
+  }
+  qh_matchnewfacets(qh /* qh.newfacet_list */); /* ignore returned value, maxdupdist */
+  zinc_(Ztricoplanar);
+  zadd_(Ztricoplanartot, numnew);
+  zmax_(Ztricoplanarmax, numnew);
+  if (!(*first_vertex))
+    (*first_vertex)= qh->newvertex_list;
+  qh->newvertex_list= NULL;
+  qh->visible_list= NULL;
+  /* only update v.neighbors for qh.newfacet_list.  qh.visible_list and qh.newvertex_list are NULL */
+  qh_update_vertexneighbors(qh /* qh.newfacet_list */);
+  qh_resetlists(qh, False, !qh_RESETvisible /* qh.newfacet_list */);
+} /* triangulate_facet */
+
+/*---------------------------------
+
+  qh_triangulate_link(qh, oldfacetA, facetA, oldfacetB, facetB)
+    relink facetA to facetB via null oldfacetA or mirrored oldfacetA and oldfacetB
+  returns:
+    if neighbors are already linked, will merge as MRGmirror (qh.degen_mergeset, 4-d and up)
+*/
+void qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
+  int errmirror= False;
+
+  if (oldfacetA == oldfacetB) {
+    trace3((qh, qh->ferr, 3052, "qh_triangulate_link: relink neighbors f%d and f%d of null facet f%d\n",
+      facetA->id, facetB->id, oldfacetA->id));
+  }else {
+    trace3((qh, qh->ferr, 3021, "qh_triangulate_link: relink neighbors f%d and f%d of mirrored facets f%d and f%d\n",
+      facetA->id, facetB->id, oldfacetA->id, oldfacetB->id));
+  }
+  if (qh_setin(facetA->neighbors, facetB)) {
+    if (!qh_setin(facetB->neighbors, facetA))
+      errmirror= True;
+    else if (!facetA->redundant || !facetB->redundant || !qh_hasmerge(qh->degen_mergeset, MRGmirror, facetA, facetB))
+      qh_appendmergeset(qh, facetA, facetB, MRGmirror, 0.0, 1.0);
+  }else if (qh_setin(facetB->neighbors, facetA))
+    errmirror= True;
+  if (errmirror) {
+    qh_fprintf(qh, qh->ferr, 6163, "qhull internal error (qh_triangulate_link): neighbors f%d and f%d do not match for null facet or mirrored facets f%d and f%d\n",
+       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
+    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
+  }
+  qh_setreplace(qh, facetB->neighbors, oldfacetB, facetA);
+  qh_setreplace(qh, facetA->neighbors, oldfacetA, facetB);
+} /* triangulate_link */
+
+/*---------------------------------
+
+  qh_triangulate_mirror(qh, facetA, facetB)
+    delete two mirrored facets identified by qh_triangulate_null() and itself
+      a mirrored facet shares the same vertices of a logical ridge
+  design:
+    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
+    if they are already neighbors, the opposing neighbors become MRGmirror facets
+*/
+void qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB) {
+  facetT *neighbor, *neighborB;
+  int neighbor_i, neighbor_n;
+
+  trace3((qh, qh->ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d and link their neighbors\n",
+         facetA->id, facetB->id));
+  FOREACHneighbor_i_(qh, facetA) {
+    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
+    if (neighbor == facetB && neighborB == facetA)
+      continue; /* occurs twice */
+    else if (neighbor->redundant && neighborB->redundant) { /* also mirrored facets (D5+) */
+      if (qh_hasmerge(qh->degen_mergeset, MRGmirror, neighbor, neighborB))
+        continue;
+    }
+    if (neighbor->visible && neighborB->visible) /* previously deleted as mirrored facets */
+      continue;
+    qh_triangulate_link(qh, facetA, neighbor, facetB, neighborB);
+  }
+  qh_willdelete(qh, facetA, NULL);
+  qh_willdelete(qh, facetB, NULL);
+} /* triangulate_mirror */
+
+/*---------------------------------
+
+  qh_triangulate_null(qh, facetA)
+    remove null facetA from qh_triangulate_facet()
+      a null facet has vertex #1 (apex) == vertex #2
+  returns:
+    adds facetA to ->visible for deletion after qh_update_vertexneighbors
+    qh->degen_mergeset contains mirror facets (4-d and up only)
+  design:
+    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
+    if they are already neighbors, the opposing neighbors will be merged (MRGmirror)
+*/
+void qh_triangulate_null(qhT *qh, facetT *facetA) {
+  facetT *neighbor, *otherfacet;
+
+  trace3((qh, qh->ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
+  neighbor= SETfirstt_(facetA->neighbors, facetT);
+  otherfacet= SETsecondt_(facetA->neighbors, facetT);
+  qh_triangulate_link(qh, facetA, neighbor, facetA, otherfacet);
+  qh_willdelete(qh, facetA, NULL);
+} /* triangulate_null */
+
+#else /* qh_NOmerge */
+void qh_triangulate(qhT *qh) {
+  QHULL_UNUSED(qh)
+}
+#endif /* qh_NOmerge */
+
+/*---------------------------------
+
+  qh_vertexintersect(qh, verticesA, verticesB )
+    intersects two vertex sets (inverse id ordered)
+    vertexsetA is a temporary set at the top of qh->qhmem.tempstack
+
+  returns:
+    replaces vertexsetA with the intersection
+
+  notes:
+    only called by qh_neighbor_intersections
+    if !qh.QHULLfinished, non-simplicial facets may have f.vertices with extraneous vertices
+      cleaned by qh_remove_extravertices in qh_reduce_vertices
+    could optimize by overwriting vertexsetA
+*/
+void qh_vertexintersect(qhT *qh, setT **vertexsetA, setT *vertexsetB) {
+  setT *intersection;
+
+  intersection= qh_vertexintersect_new(qh, *vertexsetA, vertexsetB);
+  qh_settempfree(qh, vertexsetA);
+  *vertexsetA= intersection;
+  qh_settemppush(qh, intersection);
+} /* vertexintersect */
+
+/*---------------------------------
+
+  qh_vertexintersect_new(qh, verticesA, verticesB )
+    intersects two vertex sets (inverse id ordered)
+
+  returns:
+    a new set
+
+  notes:
+    called by qh_checkfacet, qh_vertexintersect, qh_rename_sharedvertex, qh_findbest_pinchedvertex, qh_neighbor_intersections
+    if !qh.QHULLfinished, non-simplicial facets may have f.vertices with extraneous vertices
+       cleaned by qh_remove_extravertices in qh_reduce_vertices
+*/
+setT *qh_vertexintersect_new(qhT *qh, setT *vertexsetA, setT *vertexsetB) {
+  setT *intersection= qh_setnew(qh, qh->hull_dim - 1);
+  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
+  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
+
+  while (*vertexA && *vertexB) {
+    if (*vertexA  == *vertexB) {
+      qh_setappend(qh, &intersection, *vertexA);
+      vertexA++; vertexB++;
+    }else {
+      if ((*vertexA)->id > (*vertexB)->id)
+        vertexA++;
+      else
+        vertexB++;
+    }
+  }
+  return intersection;
+} /* vertexintersect_new */
+
+/*---------------------------------
+
+  qh_vertexneighbors(qh)
+    for each vertex in qh.facet_list,
+      determine its neighboring facets
+
+  returns:
+    sets qh.VERTEXneighbors
+      nop if qh.VERTEXneighbors already set
+      qh_addpoint() will maintain them
+
+  notes:
+    assumes all vertex->neighbors are NULL
+
+  design:
+    for each facet
+      for each vertex
+        append facet to vertex->neighbors
+*/
+void qh_vertexneighbors(qhT *qh /* qh.facet_list */) {
+  facetT *facet;
+  vertexT *vertex, **vertexp;
+
+  if (qh->VERTEXneighbors)
+    return;
+  trace1((qh, qh->ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
+  qh->vertex_visit++;
+  FORALLfacets {
+    if (facet->visible)
+      continue;
+    FOREACHvertex_(facet->vertices) {
+      if (vertex->visitid != qh->vertex_visit) {
+        vertex->visitid= qh->vertex_visit;
+        vertex->neighbors= qh_setnew(qh, qh->hull_dim);
+      }
+      qh_setappend(qh, &vertex->neighbors, facet);
+    }
+  }
+  qh->VERTEXneighbors= True;
+} /* vertexneighbors */
+
+/*---------------------------------
+
+  qh_vertexsubset( vertexsetA, vertexsetB )
+    returns True if vertexsetA is a subset of vertexsetB
+    assumes vertexsets are sorted
+
+  note:
+    empty set is a subset of any other set
+*/
+boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
+  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
+  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
+
+  while (True) {
+    if (!*vertexA)
+      return True;
+    if (!*vertexB)
+      return False;
+    if ((*vertexA)->id > (*vertexB)->id)
+      return False;
+    if (*vertexA  == *vertexB)
+      vertexA++;
+    vertexB++;
+  }
+  return False; /* avoid warnings */
+} /* vertexsubset */
diff --git a/vendor/qhull/libqhull_r/poly_r.c b/vendor/qhull/libqhull_r/poly_r.c
new file mode 100644
index 0000000..d6a5e7a
--- /dev/null
+++ b/vendor/qhull/libqhull_r/poly_r.c
@@ -0,0 +1,1448 @@
+/*
  ---------------------------------
+
+   poly_r.c
+   implements polygons and simplices
+
+   see qh-poly_r.htm, poly_r.h and libqhull_r.h
+
+   infrequent code is in poly2_r.c
+   (all but top 50 and their callers 12/3/95)
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/poly_r.c#8 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+/*======== functions in alphabetical order ==========*/
+
+/*---------------------------------
+
+  qh_appendfacet(qh, facet )
+    appends facet to end of qh.facet_list,
+
+  returns:
+    updates qh.newfacet_list, facet_next, facet_list
+    increments qh.numfacets
+
+  notes:
+    assumes qh.facet_list/facet_tail is defined (createsimplex)
+
+  see:
+    qh_removefacet()
+
+*/
+void qh_appendfacet(qhT *qh, facetT *facet) {
+  facetT *tail= qh->facet_tail;
+
+  if (tail == qh->newfacet_list) {
+    qh->newfacet_list= facet;
+    if (tail == qh->visible_list) /* visible_list is at or before newfacet_list */
+      qh->visible_list= facet;
+  }
+  if (tail == qh->facet_next)
+    qh->facet_next= facet;
+  facet->previous= tail->previous;
+  facet->next= tail;
+  if (tail->previous)
+    tail->previous->next= facet;
+  else
+    qh->facet_list= facet;
+  tail->previous= facet;
+  qh->num_facets++;
+  trace4((qh, qh->ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
+} /* appendfacet */
+
+
+/*---------------------------------
+
+  qh_appendvertex(qh, vertex )
+    appends vertex to end of qh.vertex_list,
+
+  returns:
+    sets vertex->newfacet
+    updates qh.vertex_list, newvertex_list
+    increments qh.num_vertices
+
+  notes:
+    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
+
+*/
+void qh_appendvertex(qhT *qh, vertexT *vertex) {
+  vertexT *tail= qh->vertex_tail;
+
+  if (tail == qh->newvertex_list)
+    qh->newvertex_list= vertex;
+  vertex->newfacet= True;
+  vertex->previous= tail->previous;
+  vertex->next= tail;
+  if (tail->previous)
+    tail->previous->next= vertex;
+  else
+    qh->vertex_list= vertex;
+  tail->previous= vertex;
+  qh->num_vertices++;
+  trace4((qh, qh->ferr, 4045, "qh_appendvertex: append v%d to qh.newvertex_list and set v.newfacet\n", vertex->id));
+} /* appendvertex */
+
+
+/*---------------------------------
+
+  qh_attachnewfacets(qh)
+    attach horizon facets to new facets in qh.newfacet_list
+    newfacets have neighbor and ridge links to horizon but not vice versa
+
+  returns:
+    clears qh.NEWtentative
+    set qh.NEWfacets
+    horizon facets linked to new facets
+      ridges changed from visible facets to new facets
+      simplicial ridges deleted
+    qh.visible_list, no ridges valid
+    facet->f.replace is a newfacet (if any)
+
+  notes:
+    used for qh.NEWtentative, otherwise see qh_makenew_nonsimplicial and qh_makenew_simplicial
+    qh_delridge_merge not needed (as tested by qh_checkdelridge)
+
+  design:
+    delete interior ridges and neighbor sets by
+      for each visible, non-simplicial facet
+        for each ridge
+          if last visit or if neighbor is simplicial
+            if horizon neighbor
+              delete ridge for horizon's ridge set
+            delete ridge
+        erase neighbor set
+    attach horizon facets and new facets by
+      for all new facets
+        if corresponding horizon facet is simplicial
+          locate corresponding visible facet {may be more than one}
+          link visible facet to new facet
+          replace visible facet with new facet in horizon
+        else it is non-simplicial
+          for all visible neighbors of the horizon facet
+            link visible neighbor to new facet
+            delete visible neighbor from horizon facet
+          append new facet to horizon's neighbors
+          the first ridge of the new facet is the horizon ridge
+          link the new facet into the horizon ridge
+*/
+void qh_attachnewfacets(qhT *qh /* qh.visible_list, qh.newfacet_list */) {
+  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
+  ridgeT *ridge, **ridgep;
+
+  trace3((qh, qh->ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
+  if (qh->CHECKfrequently) {
+    qh_checkdelridge(qh);
+  }
+  qh->visit_id++;
+  FORALLvisible_facets {
+    visible->visitid= qh->visit_id;
+    if (visible->ridges) {
+      FOREACHridge_(visible->ridges) {
+        neighbor= otherfacet_(ridge, visible);
+        if (neighbor->visitid == qh->visit_id
+            || (!neighbor->visible && neighbor->simplicial)) {
+          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
+            qh_setdel(neighbor->ridges, ridge);
+          qh_delridge(qh, ridge); /* delete on second visit */
+        }
+      }
+    }
+  }
+  trace1((qh, qh->ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
+  FORALLnew_facets {
+    horizon= SETfirstt_(newfacet->neighbors, facetT);
+    if (horizon->simplicial) {
+      visible= NULL;
+      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
+        if (neighbor->visible) {
+          if (visible) {
+            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
+                                  SETindex_(horizon->neighbors, neighbor))) {
+              visible= neighbor;
+              break;
+            }
+          }else
+            visible= neighbor;
+        }
+      }
+      if (visible) {
+        visible->f.replace= newfacet;
+        qh_setreplace(qh, horizon->neighbors, visible, newfacet);
+      }else {
+        qh_fprintf(qh, qh->ferr, 6102, "qhull internal error (qh_attachnewfacets): could not find visible facet for horizon f%d of newfacet f%d\n",
+                 horizon->id, newfacet->id);
+        qh_errexit2(qh, qh_ERRqhull, horizon, newfacet);
+      }
+    }else { /* non-simplicial, with a ridge for newfacet */
+      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
+        if (neighbor->visible) {
+          neighbor->f.replace= newfacet;
+          qh_setdelnth(qh, horizon->neighbors, SETindex_(horizon->neighbors, neighbor));
+          neighborp--; /* repeat */
+        }
+      }
+      qh_setappend(qh, &horizon->neighbors, newfacet);
+      ridge= SETfirstt_(newfacet->ridges, ridgeT);
+      if (ridge->top == horizon) {
+        ridge->bottom= newfacet;
+        ridge->simplicialbot= True;
+      }else {
+        ridge->top= newfacet;
+        ridge->simplicialtop= True;
+      }
+    }
+  } /* newfacets */
+  trace4((qh, qh->ferr, 4094, "qh_attachnewfacets: clear f.ridges and f.neighbors for visible facets, may become invalid before qh_deletevisible\n"));
+  FORALLvisible_facets {
+    if (visible->ridges)
+      SETfirst_(visible->ridges)= NULL; 
+    SETfirst_(visible->neighbors)= NULL;
+  }
+  qh->NEWtentative= False;
+  qh->NEWfacets= True;
+  if (qh->PRINTstatistics) {
+    FORALLvisible_facets {
+      if (!visible->f.replace)
+        zinc_(Zinsidevisible);
+    }
+  }
+} /* attachnewfacets */
+
+/*---------------------------------
+
+  qh_checkflipped(qh, facet, dist, allerror )
+    checks facet orientation to interior point
+
+    if allerror set,
+      tests against -qh.DISTround
+    else
+      tests against 0.0 since tested against -qh.DISTround before
+
+  returns:
+    False if it flipped orientation (sets facet->flipped)
+    distance if non-NULL
+
+  notes:
+    called by qh_setfacetplane, qh_initialhull, and qh_checkflipped_all
+*/
+boolT qh_checkflipped(qhT *qh, facetT *facet, realT *distp, boolT allerror) {
+  realT dist;
+
+  if (facet->flipped && !distp)
+    return False;
+  zzinc_(Zdistcheck);
+  qh_distplane(qh, qh->interior_point, facet, &dist);
+  if (distp)
+    *distp= dist;
+  if ((allerror && dist >= -qh->DISTround) || (!allerror && dist > 0.0)) {
+    facet->flipped= True;
+    trace0((qh, qh->ferr, 19, "qh_checkflipped: facet f%d flipped, allerror? %d, distance= %6.12g during p%d\n",
+              facet->id, allerror, dist, qh->furthest_id));
+    if (qh->num_facets > qh->hull_dim+1) { /* qh_initialhull reverses orientation if !qh_checkflipped */
+      zzinc_(Zflippedfacets);
+      qh_joggle_restart(qh, "flipped facet");
+    }
+    return False;
+  }
+  return True;
+} /* checkflipped */
+
+/*---------------------------------
+
+  qh_delfacet(qh, facet )
+    removes facet from facet_list and frees up its memory
+
+  notes:
+    assumes vertices and ridges already freed or referenced elsewhere
+*/
+void qh_delfacet(qhT *qh, facetT *facet) {
+  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
+
+  trace3((qh, qh->ferr, 3057, "qh_delfacet: delete f%d\n", facet->id));
+  if (qh->CHECKfrequently || qh->VERIFYoutput) { 
+    if (!qh->NOerrexit) {
+      qh_checkdelfacet(qh, facet, qh->facet_mergeset);
+      qh_checkdelfacet(qh, facet, qh->degen_mergeset);
+      qh_checkdelfacet(qh, facet, qh->vertex_mergeset);
+    }
+  }
+  if (facet == qh->tracefacet)
+    qh->tracefacet= NULL;
+  if (facet == qh->GOODclosest)
+    qh->GOODclosest= NULL;
+  qh_removefacet(qh, facet);
+  if (!facet->tricoplanar || facet->keepcentrum) {
+    qh_memfree_(qh, facet->normal, qh->normal_size, freelistp);
+    if (qh->CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
+      qh_memfree_(qh, facet->center, qh->center_size, freelistp);
+    }else /* AScentrum */ {
+      qh_memfree_(qh, facet->center, qh->normal_size, freelistp);
+    }
+  }
+  qh_setfree(qh, &(facet->neighbors));
+  if (facet->ridges)
+    qh_setfree(qh, &(facet->ridges));
+  qh_setfree(qh, &(facet->vertices));
+  if (facet->outsideset)
+    qh_setfree(qh, &(facet->outsideset));
+  if (facet->coplanarset)
+    qh_setfree(qh, &(facet->coplanarset));
+  qh_memfree_(qh, facet, (int)sizeof(facetT), freelistp);
+} /* delfacet */
+
+
+/*---------------------------------
+
+  qh_deletevisible()
+    delete visible facets and vertices
+
+  returns:
+    deletes each facet and removes from facetlist
+    deletes vertices on qh.del_vertices and ridges in qh.del_ridges
+    at exit, qh.visible_list empty (== qh.newfacet_list)
+
+  notes:
+    called by qh_all_vertexmerges, qh_addpoint, and qh_qhull
+    ridges already deleted or moved elsewhere
+    deleted vertices on qh.del_vertices
+    horizon facets do not reference facets on qh.visible_list
+    new facets in qh.newfacet_list
+    uses   qh.visit_id;
+*/
+void qh_deletevisible(qhT *qh /* qh.visible_list */) {
+  facetT *visible, *nextfacet;
+  vertexT *vertex, **vertexp;
+  int numvisible= 0, numdel= qh_setsize(qh, qh->del_vertices);
+
+  trace1((qh, qh->ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
+         qh->num_visible, numdel));
+  for (visible=qh->visible_list; visible && visible->visible;
+                visible= nextfacet) { /* deleting current */
+    nextfacet= visible->next;
+    numvisible++;
+    qh_delfacet(qh, visible);  /* f.ridges deleted or moved elsewhere, deleted f.vertices on qh.del_vertices */
+  }
+  if (numvisible != qh->num_visible) {
+    qh_fprintf(qh, qh->ferr, 6103, "qhull internal error (qh_deletevisible): qh->num_visible %d is not number of visible facets %d\n",
+             qh->num_visible, numvisible);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  qh->num_visible= 0;
+  zadd_(Zvisfacettot, numvisible);
+  zmax_(Zvisfacetmax, numvisible);
+  zzadd_(Zdelvertextot, numdel);
+  zmax_(Zdelvertexmax, numdel);
+  FOREACHvertex_(qh->del_vertices)
+    qh_delvertex(qh, vertex);
+  qh_settruncate(qh, qh->del_vertices, 0);
+} /* deletevisible */
+
+/*---------------------------------
+
+  qh_facetintersect(qh, facetA, facetB, skipa, skipB, prepend )
+    return vertices for intersection of two simplicial facets
+    may include 1 prepended entry (if more, need to settemppush)
+
+  returns:
+    returns set of qh.hull_dim-1 + prepend vertices
+    returns skipped index for each test and checks for exactly one
+
+  notes:
+    does not need settemp since set in quick memory
+
+  see also:
+    qh_vertexintersect and qh_vertexintersect_new
+    use qh_setnew_delnthsorted to get nth ridge (no skip information)
+
+  design:
+    locate skipped vertex by scanning facet A's neighbors
+    locate skipped vertex by scanning facet B's neighbors
+    intersect the vertex sets
+*/
+setT *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB,
+                         int *skipA,int *skipB, int prepend) {
+  setT *intersect;
+  int dim= qh->hull_dim, i, j;
+  facetT **neighborsA, **neighborsB;
+
+  neighborsA= SETaddr_(facetA->neighbors, facetT);
+  neighborsB= SETaddr_(facetB->neighbors, facetT);
+  i= j= 0;
+  if (facetB == *neighborsA++)
+    *skipA= 0;
+  else if (facetB == *neighborsA++)
+    *skipA= 1;
+  else if (facetB == *neighborsA++)
+    *skipA= 2;
+  else {
+    for (i=3; i < dim; i++) {
+      if (facetB == *neighborsA++) {
+        *skipA= i;
+        break;
+      }
+    }
+  }
+  if (facetA == *neighborsB++)
+    *skipB= 0;
+  else if (facetA == *neighborsB++)
+    *skipB= 1;
+  else if (facetA == *neighborsB++)
+    *skipB= 2;
+  else {
+    for (j=3; j < dim; j++) {
+      if (facetA == *neighborsB++) {
+        *skipB= j;
+        break;
+      }
+    }
+  }
+  if (i >= dim || j >= dim) {
+    qh_fprintf(qh, qh->ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in other's neighbors\n",
+            facetA->id, facetB->id);
+    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
+  }
+  intersect= qh_setnew_delnthsorted(qh, facetA->vertices, qh->hull_dim, *skipA, prepend);
+  trace4((qh, qh->ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
+          facetA->id, *skipA, facetB->id, *skipB));
+  return(intersect);
+} /* facetintersect */
+
+/*---------------------------------
+
+  qh_gethash(qh, hashsize, set, size, firstindex, skipelem )
+    return hashvalue for a set with firstindex and skipelem
+
+  notes:
+    returned hash is in [0,hashsize)
+    assumes at least firstindex+1 elements
+    assumes skipelem is NULL, in set, or part of hash
+
+    hashes memory addresses which may change over different runs of the same data
+    using sum for hash does badly in high d
+*/
+int qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem) {
+  void **elemp= SETelemaddr_(set, firstindex, void);
+  ptr_intT hash= 0, elem;
+  unsigned int uresult;
+  int i;
+#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
+#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
+#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
+#endif
+
+  switch (size-firstindex) {
+  case 1:
+    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
+    break;
+  case 2:
+    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
+    break;
+  case 3:
+    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
+      - (ptr_intT) skipelem;
+    break;
+  case 4:
+    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
+      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
+    break;
+  case 5:
+    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
+      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
+    break;
+  case 6:
+    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
+      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
+      - (ptr_intT) skipelem;
+    break;
+  default:
+    hash= 0;
+    i= 3;
+    do {     /* this is about 10% in 10-d */
+      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
+        hash ^= (elem << i) + (elem >> (32-i));
+        i += 3;
+        if (i >= 32)
+          i -= 32;
+      }
+    }while (*elemp);
+    break;
+  }
+  if (hashsize<0) {
+    qh_fprintf(qh, qh->ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly_r.c]\n", hashsize);
+    qh_errexit2(qh, qh_ERRqhull, NULL, NULL);
+  }
+  uresult= (unsigned int)hash;
+  uresult %= (unsigned int)hashsize;
+  /* result= 0; for debugging */
+  return (int)uresult;
+#ifdef _MSC_VER
+#pragma warning( pop)
+#endif
+} /* gethash */
+
+/*---------------------------------
+
+  qh_getreplacement(qh, visible )
+    get replacement for visible facet
+
+  returns:
+    valid facet from visible.replace (may be chained)
+*/
+facetT *qh_getreplacement(qhT *qh, facetT *visible) {
+  unsigned int count= 0;
+
+  facetT *result= visible;
+  while (result && result->visible) {
+    result= result->f.replace;
+    if (count++ > qh->facet_id)
+      qh_infiniteloop(qh, visible);
+  }
+  return result;
+}
+
+/*---------------------------------
+
+  qh_makenewfacet(qh, vertices, toporient, horizon )
+    creates a toporient? facet from vertices
+
+  returns:
+    returns newfacet
+      adds newfacet to qh.facet_list
+      newfacet->vertices= vertices
+      if horizon
+        newfacet->neighbor= horizon, but not vice versa
+    newvertex_list updated with vertices
+*/
+facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient, facetT *horizon) {
+  facetT *newfacet;
+  vertexT *vertex, **vertexp;
+
+  FOREACHvertex_(vertices) {
+    if (!vertex->newfacet) {
+      qh_removevertex(qh, vertex);
+      qh_appendvertex(qh, vertex);
+    }
+  }
+  newfacet= qh_newfacet(qh);
+  newfacet->vertices= vertices;
+  if (toporient)
+    newfacet->toporient= True;
+  if (horizon)
+    qh_setappend(qh, &(newfacet->neighbors), horizon);
+  qh_appendfacet(qh, newfacet);
+  return(newfacet);
+} /* makenewfacet */
+
+
+/*---------------------------------
+
+  qh_makenewplanes()
+    make new hyperplanes for facets on qh.newfacet_list
+
+  returns:
+    all facets have hyperplanes or are marked for   merging
+    doesn't create hyperplane if horizon is coplanar (will merge)
+    updates qh.min_vertex if qh.JOGGLEmax
+
+  notes:
+    facet->f.samecycle is defined for facet->mergehorizon facets
+*/
+void qh_makenewplanes(qhT *qh /* qh.newfacet_list */) {
+  facetT *newfacet;
+
+  trace4((qh, qh->ferr, 4074, "qh_makenewplanes: make new hyperplanes for facets on qh.newfacet_list f%d\n",
+    qh->newfacet_list->id));
+  FORALLnew_facets {
+    if (!newfacet->mergehorizon)
+      qh_setfacetplane(qh, newfacet); /* updates Wnewvertexmax */
+  }
+  if (qh->JOGGLEmax < REALmax/2)
+    minimize_(qh->min_vertex, -wwval_(Wnewvertexmax));
+} /* makenewplanes */
+
+#ifndef qh_NOmerge
+/*---------------------------------
+
+  qh_makenew_nonsimplicial(qh, visible, apex, numnew )
+    make new facets for ridges of a visible facet
+
+  returns:
+    first newfacet, bumps numnew as needed
+    attaches new facets if !qh->NEWtentative
+    marks ridge neighbors for simplicial visible
+    if (qh.NEWtentative)
+      ridges on newfacet, horizon, and visible
+    else
+      ridge and neighbors between newfacet and horizon
+      visible facet's ridges are deleted
+      visible facet's f.neighbors is empty
+
+  notes:
+    called by qh_makenewfacets and qh_triangulatefacet
+    qh.visit_id if visible has already been processed
+    sets neighbor->seen for building f.samecycle
+      assumes all 'seen' flags initially false
+    qh_delridge_merge not needed (as tested by qh_checkdelridge in qh_makenewfacets)
+
+  design:
+    for each ridge of visible facet
+      get neighbor of visible facet
+      if neighbor was already processed
+        delete the ridge (will delete all visible facets later)
+      if neighbor is a horizon facet
+        create a new facet
+        if neighbor coplanar
+          adds newfacet to f.samecycle for later merging
+        else
+          updates neighbor's neighbor set
+          (checks for non-simplicial facet with multiple ridges to visible facet)
+        updates neighbor's ridge set
+        (checks for simplicial neighbor to non-simplicial visible facet)
+        (deletes ridge if neighbor is simplicial)
+
+*/
+facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
+  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
+  ridgeT *ridge, **ridgep;
+  facetT *neighbor, *newfacet= NULL, *samecycle;
+  setT *vertices;
+  boolT toporient;
+  unsigned int ridgeid;
+
+  FOREACHridge_(visible->ridges) {
+    ridgeid= ridge->id;
+    neighbor= otherfacet_(ridge, visible);
+    if (neighbor->visible) {
+      if (!qh->NEWtentative) {
+        if (neighbor->visitid == qh->visit_id) {
+          if (qh->traceridge == ridge)
+            qh->traceridge= NULL;
+          qh_setfree(qh, &(ridge->vertices));  /* delete on 2nd visit */
+          qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
+        }
+      }
+    }else {  /* neighbor is an horizon facet */
+      toporient= (ridge->top == visible);
+      vertices= qh_setnew(qh, qh->hull_dim); /* makes sure this is quick */
+      qh_setappend(qh, &vertices, apex);
+      qh_setappend_set(qh, &vertices, ridge->vertices);
+      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
+      (*numnew)++;
+      if (neighbor->coplanarhorizon) {
+        newfacet->mergehorizon= True;
+        if (!neighbor->seen) {
+          newfacet->f.samecycle= newfacet;
+          neighbor->f.newcycle= newfacet;
+        }else {
+          samecycle= neighbor->f.newcycle;
+          newfacet->f.samecycle= samecycle->f.samecycle;
+          samecycle->f.samecycle= newfacet;
+        }
+      }
+      if (qh->NEWtentative) {
+        if (!neighbor->simplicial)
+          qh_setappend(qh, &(newfacet->ridges), ridge);
+      }else {  /* qh_attachnewfacets */
+        if (neighbor->seen) {
+          if (neighbor->simplicial) {
+            qh_fprintf(qh, qh->ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
+                   neighbor->id, visible->id);
+            qh_errexit2(qh, qh_ERRqhull, neighbor, visible);
+          }
+          qh_setappend(qh, &(neighbor->neighbors), newfacet);
+        }else
+          qh_setreplace(qh, neighbor->neighbors, visible, newfacet);
+        if (neighbor->simplicial) {
+          qh_setdel(neighbor->ridges, ridge);
+          qh_delridge(qh, ridge);
+        }else {
+          qh_setappend(qh, &(newfacet->ridges), ridge);
+          if (toporient) {
+            ridge->top= newfacet;
+            ridge->simplicialtop= True;
+          }else {
+            ridge->bottom= newfacet;
+            ridge->simplicialbot= True;
+          }
+        }
+      }
+      trace4((qh, qh->ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
+          newfacet->id, apex->id, ridgeid, neighbor->id));
+    }
+    neighbor->seen= True;
+  } /* for each ridge */
+  return newfacet;
+} /* makenew_nonsimplicial */
+
+#else /* qh_NOmerge */
+facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(visible)
+  QHULL_UNUSED(apex)
+  QHULL_UNUSED(numnew)
+
+  return NULL;
+}
+#endif /* qh_NOmerge */
+
+/*---------------------------------
+
+  qh_makenew_simplicial(qh, visible, apex, numnew )
+    make new facets for simplicial visible facet and apex
+
+  returns:
+    attaches new facets if !qh.NEWtentative
+      neighbors between newfacet and horizon
+
+  notes:
+    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
+
+  design:
+    locate neighboring horizon facet for visible facet
+    determine vertices and orientation
+    create new facet
+    if coplanar,
+      add new facet to f.samecycle
+    update horizon facet's neighbor list
+*/
+facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
+  facetT *neighbor, **neighborp, *newfacet= NULL;
+  setT *vertices;
+  boolT flip, toporient;
+  int horizonskip= 0, visibleskip= 0;
+
+  FOREACHneighbor_(visible) {
+    if (!neighbor->seen && !neighbor->visible) {
+      vertices= qh_facetintersect(qh, neighbor,visible, &horizonskip, &visibleskip, 1);
+      SETfirst_(vertices)= apex;
+      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
+      if (neighbor->toporient)
+        toporient= horizonskip & 0x1;
+      else
+        toporient= (horizonskip & 0x1) ^ 0x1;
+      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
+      (*numnew)++;
+      if (neighbor->coplanarhorizon && (qh->PREmerge || qh->MERGEexact)) {
+#ifndef qh_NOmerge
+        newfacet->f.samecycle= newfacet;
+        newfacet->mergehorizon= True;
+#endif
+      }
+      if (!qh->NEWtentative)
+        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
+      trace4((qh, qh->ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
+            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
+              neighbor->toporient, visible->id, visibleskip, flip));
+    }
+  }
+  return newfacet;
+} /* makenew_simplicial */
+
+/*---------------------------------
+
+  qh_matchneighbor(qh, newfacet, newskip, hashsize, hashcount )
+    either match subridge of newfacet with neighbor or add to hash_table
+
+  returns:
+    matched ridges of newfacet, except for duplicate ridges
+    duplicate ridges marked by qh_DUPLICATEridge for qh_matchdupridge
+
+  notes:
+    called by qh_matchnewfacets
+    assumes newfacet is simplicial
+    ridge is newfacet->vertices w/o newskip vertex
+    do not allocate memory (need to free hash_table cleanly)
+    uses linear hash chains
+    see qh_matchdupridge (poly2_r.c)
+
+  design:
+    for each possible matching facet in qh.hash_table
+      if vertices match
+        set ismatch, if facets have opposite orientation
+        if ismatch and matching facet doesn't have a match
+          match the facets by updating their neighbor sets
+        else
+          note: dupridge detected when a match 'f&d skip %d' has already been seen 
+                need to mark all of the dupridges for qh_matchdupridge
+          indicate a duplicate ridge by qh_DUPLICATEridge and f.dupridge
+          add facet to hashtable
+          unless the other facet was already a duplicate ridge
+            mark both facets with a duplicate ridge
+            add other facet (if defined) to hash table
+
+  state at "indicate a duplicate ridge":
+    newfacet@newskip= the argument
+    facet= the hashed facet@skip that has the same vertices as newfacet@newskip
+    same= true if matched vertices have the same orientation
+    matchfacet= neighbor at facet@skip
+    matchfacet=qh_DUPLICATEridge, matchfacet was previously detected as a dupridge of facet@skip
+    ismatch if 'vertex orientation (same) matches facet/newfacet orientation (toporient)
+    unknown facet will match later
+
+  details at "indicate a duplicate ridge":
+    if !ismatch and matchfacet,
+      dupridge is between hashed facet@skip/matchfacet@matchskip and arg newfacet@newskip/unknown 
+      set newfacet@newskip, facet@skip, and matchfacet@matchskip to qh_DUPLICATEridge
+      add newfacet and matchfacet to hash_table
+      if ismatch and matchfacet, 
+        same as !ismatch and matchfacet -- it matches facet instead of matchfacet
+      if !ismatch and !matchfacet
+        dupridge between hashed facet@skip/unknown and arg newfacet@newskip/unknown 
+        set newfacet@newskip and facet@skip to qh_DUPLICATEridge
+        add newfacet to hash_table
+      if ismatch and matchfacet==qh_DUPLICATEridge
+        dupridge with already duplicated hashed facet@skip and arg newfacet@newskip/unknown
+        set newfacet@newskip to qh_DUPLICATEridge
+        add newfacet to hash_table
+        facet's hyperplane already set
+*/
+void qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize, int *hashcount) {
+  boolT newfound= False;   /* True, if new facet is already in hash chain */
+  boolT same, ismatch;
+  int hash, scan;
+  facetT *facet, *matchfacet;
+  int skip, matchskip;
+
+  hash= qh_gethash(qh, hashsize, newfacet->vertices, qh->hull_dim, 1,
+                     SETelem_(newfacet->vertices, newskip));
+  trace4((qh, qh->ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
+          newfacet->id, newskip, hash, *hashcount));
+  zinc_(Zhashlookup);
+  for (scan=hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
+       scan= (++scan >= hashsize ? 0 : scan)) {
+    if (facet == newfacet) {
+      newfound= True;
+      continue;
+    }
+    zinc_(Zhashtests);
+    if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
+      if (SETelem_(newfacet->vertices, newskip) == SETelem_(facet->vertices, skip)) {
+        qh_joggle_restart(qh, "two new facets with the same vertices");
+        /* duplicated for multiple skips, not easily avoided */
+        qh_fprintf(qh, qh->ferr, 7084, "qhull topology warning (qh_matchneighbor): will merge vertices to undo new facets -- f%d and f%d have the same vertices (skip %d, skip %d) and same horizon ridges to f%d and f%d\n",
+          facet->id, newfacet->id, skip, newskip, SETfirstt_(facet->neighbors, facetT)->id, SETfirstt_(newfacet->neighbors, facetT)->id);
+        /* will rename a vertex (QH3053).  The fault was duplicate ridges (same vertices) in different facets due to a previous rename.  Expensive to detect beforehand */
+      }
+      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
+      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
+      if (ismatch && !matchfacet) {
+        SETelem_(facet->neighbors, skip)= newfacet;
+        SETelem_(newfacet->neighbors, newskip)= facet;
+        (*hashcount)--;
+        trace4((qh, qh->ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
+           facet->id, skip, newfacet->id, newskip));
+        return;
+      }
+      if (!qh->PREmerge && !qh->MERGEexact) {
+        qh_joggle_restart(qh, "a ridge with more than two neighbors");
+        qh_fprintf(qh, qh->ferr, 6107, "qhull topology error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue due to no qh.PREmerge and no 'Qx' (MERGEexact)\n",
+                 facet->id, newfacet->id, getid_(matchfacet));
+        qh_errexit2(qh, qh_ERRtopology, facet, newfacet);
+      }
+      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
+      newfacet->dupridge= True;
+      qh_addhash(newfacet, qh->hash_table, hashsize, hash);
+      (*hashcount)++;
+      if (matchfacet != qh_DUPLICATEridge) {
+        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
+        facet->dupridge= True;
+        if (matchfacet) {
+          matchskip= qh_setindex(matchfacet->neighbors, facet);
+          if (matchskip<0) {
+              qh_fprintf(qh, qh->ferr, 6260, "qhull topology error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
+                  matchfacet->id, facet->id);
+              qh_errexit2(qh, qh_ERRtopology, matchfacet, facet);
+          }
+          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
+          matchfacet->dupridge= True;
+          qh_addhash(matchfacet, qh->hash_table, hashsize, hash);
+          *hashcount += 2;
+        }
+      }
+      trace4((qh, qh->ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
+           newfacet->id, newskip, facet->id, skip,
+           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
+           ismatch, hash));
+      return; /* end of duplicate ridge */
+    }
+  }
+  if (!newfound)
+    SETelem_(qh->hash_table, scan)= newfacet;  /* same as qh_addhash */
+  (*hashcount)++;
+  trace4((qh, qh->ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
+           newfacet->id, newskip, hash));
+} /* matchneighbor */
+
+
+/*---------------------------------
+
+  qh_matchnewfacets(qh )
+    match new facets in qh.newfacet_list to their newfacet neighbors
+    all facets are simplicial
+
+  returns:
+    if dupridges and merging 
+      returns maxdupdist (>=0.0) from vertex to opposite facet
+      sets facet->dupridge
+      missing neighbor links identify dupridges to be merged (qh_DUPLICATEridge)
+    else  
+      qh.newfacet_list with full neighbor sets
+        vertices for the nth neighbor match all but the nth vertex
+    if not merging and qh.FORCEoutput
+      for facets with normals (i.e., with dupridges)
+      sets facet->flippped for flipped normals, also prevents point partitioning
+
+  notes:
+    called by qh_buildcone* and qh_triangulate_facet
+    neighbor[0] of new facets is the horizon facet
+    if NEWtentative, new facets not attached to the horizon
+    assumes qh.hash_table is NULL
+    vertex->neighbors has not been updated yet
+    do not allocate memory after qh.hash_table (need to free it cleanly)
+    
+  design:
+    truncate neighbor sets to horizon facet for all new facets
+    initialize a hash table
+    for all new facets
+      match facet with neighbors
+    if unmatched facets (due to duplicate ridges)
+      for each new facet with a duplicate ridge
+        try to match facets with the same coplanar horizon
+    if not all matched
+      for each new facet with a duplicate ridge
+        match it with a coplanar facet, or identify a pinched vertex
+    if not merging and qh.FORCEoutput
+      check for flipped facets
+*/
+coordT qh_matchnewfacets(qhT *qh /* qh.newfacet_list */) {
+  int numnew=0, hashcount=0, newskip;
+  facetT *newfacet, *neighbor;
+  coordT maxdupdist= 0.0, maxdist2;
+  int dim= qh->hull_dim, hashsize, neighbor_i, neighbor_n;
+  setT *neighbors;
+#ifndef qh_NOtrace
+  int facet_i, facet_n, numunused= 0;
+  facetT *facet;
+#endif
+
+  trace1((qh, qh->ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
+  FORALLnew_facets {
+    numnew++;
+    {  /* inline qh_setzero(qh, newfacet->neighbors, 1, qh->hull_dim); */
+      neighbors= newfacet->neighbors;
+      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
+      memset((char *)SETelemaddr_(neighbors, 1, void), 0, (size_t)(dim * SETelemsize));
+    }
+  }
+
+  qh_newhashtable(qh, numnew*(qh->hull_dim-1)); /* twice what is normally needed,
+                                     but every ridge could be DUPLICATEridge */
+  hashsize= qh_setsize(qh, qh->hash_table);
+  FORALLnew_facets {
+    if (!newfacet->simplicial) {
+      qh_fprintf(qh, qh->ferr, 6377, "qhull internal error (qh_matchnewfacets): expecting simplicial facets on qh.newfacet_list f%d for qh_matchneighbors, qh_matchneighbor, and qh_matchdupridge.  Got non-simplicial f%d\n",
+        qh->newfacet_list->id, newfacet->id);
+      qh_errexit2(qh, qh_ERRqhull, newfacet, qh->newfacet_list);
+    }
+    for (newskip=1; newskiphull_dim; newskip++) /* furthest/horizon already matched */
+      /* hashsize>0 because hull_dim>1 and numnew>0 */
+      qh_matchneighbor(qh, newfacet, newskip, hashsize, &hashcount);
+#if 0   /* use the following to trap hashcount errors */
+    {
+      int count= 0, k;
+      facetT *facet, *neighbor;
+
+      count= 0;
+      FORALLfacet_(qh->newfacet_list) {  /* newfacet already in use */
+        for (k=1; k < qh->hull_dim; k++) {
+          neighbor= SETelemt_(facet->neighbors, k, facetT);
+          if (!neighbor || neighbor == qh_DUPLICATEridge)
+            count++;
+        }
+        if (facet == newfacet)
+          break;
+      }
+      if (count != hashcount) {
+        qh_fprintf(qh, qh->ferr, 6266, "qhull error (qh_matchnewfacets): after adding facet %d, hashcount %d != count %d\n",
+                 newfacet->id, hashcount, count);
+        qh_errexit(qh, qh_ERRdebug, newfacet, NULL);
+      }
+    }
+#endif  /* end of trap code */
+  } /* end FORALLnew_facets */
+  if (hashcount) { /* all neighbors matched, except for qh_DUPLICATEridge neighbors */
+    qh_joggle_restart(qh, "ridge with multiple neighbors");
+    if (hashcount) {
+      FORALLnew_facets {
+        if (newfacet->dupridge) {
+          FOREACHneighbor_i_(qh, newfacet) {
+            if (neighbor == qh_DUPLICATEridge) {
+              maxdist2= qh_matchdupridge(qh, newfacet, neighbor_i, hashsize, &hashcount);
+              maximize_(maxdupdist, maxdist2);
+            }
+          }
+        }
+      }
+    }
+  }
+  if (hashcount) {
+    qh_fprintf(qh, qh->ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
+        hashcount);
+    qh_printhashtable(qh, qh->ferr);
+    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+#ifndef qh_NOtrace
+  if (qh->IStracing >= 3) {
+    FOREACHfacet_i_(qh, qh->hash_table) {
+      if (!facet)
+        numunused++;
+    }
+    qh_fprintf(qh, qh->ferr, 3063, "qh_matchnewfacets: maxdupdist %2.2g, new facets %d, unused hash entries %d, hashsize %d\n",
+             maxdupdist, numnew, numunused, qh_setsize(qh, qh->hash_table));
+  }
+#endif /* !qh_NOtrace */
+  qh_setfree(qh, &qh->hash_table);
+  if (qh->PREmerge || qh->MERGEexact) {
+    if (qh->IStracing >= 4)
+      qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
+  }
+  return maxdupdist;
+} /* matchnewfacets */
+
+
+/*---------------------------------
+
+  qh_matchvertices(qh, firstindex, verticesA, skipA, verticesB, skipB, same )
+    tests whether vertices match with a single skip
+    starts match at firstindex since all new facets have a common vertex
+
+  returns:
+    true if matched vertices
+    skip index for skipB
+    sets same iff vertices have the same orientation
+
+  notes:
+    called by qh_matchneighbor and qh_matchdupridge
+    assumes skipA is in A and both sets are the same size
+
+  design:
+    set up pointers
+    scan both sets checking for a match
+    test orientation
+*/
+boolT qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
+       setT *verticesB, int *skipB, boolT *same) {
+  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
+
+  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
+  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
+  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
+  do if (elemAp != skipAp) {
+    while (*elemAp != *elemBp++) {
+      if (skipBp)
+        return False;
+      skipBp= elemBp;  /* one extra like FOREACH */
+    }
+  }while (*(++elemAp));
+  if (!skipBp)
+    skipBp= ++elemBp;
+  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB
+                                       verticesA and verticesB are the same size, otherwise trace4 may segfault */
+  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
+  trace4((qh, qh->ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
+          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
+  return(True);
+} /* matchvertices */
+
+/*---------------------------------
+
+  qh_newfacet(qh)
+    return a new facet
+
+  returns:
+    all fields initialized or cleared   (NULL)
+    preallocates neighbors set
+*/
+facetT *qh_newfacet(qhT *qh) {
+  facetT *facet;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+
+  qh_memalloc_(qh, (int)sizeof(facetT), freelistp, facet, facetT);
+  memset((char *)facet, (size_t)0, sizeof(facetT));
+  if (qh->facet_id == qh->tracefacet_id)
+    qh->tracefacet= facet;
+  facet->id= qh->facet_id++;
+  facet->neighbors= qh_setnew(qh, qh->hull_dim);
+#if !qh_COMPUTEfurthest
+  facet->furthestdist= 0.0;
+#endif
+#if qh_MAXoutside
+  if (qh->FORCEoutput && qh->APPROXhull)
+    facet->maxoutside= qh->MINoutside;
+  else
+    facet->maxoutside= qh->DISTround; /* same value as test for QH7082 */
+#endif
+  facet->simplicial= True;
+  facet->good= True;
+  facet->newfacet= True;
+  trace4((qh, qh->ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
+  return(facet);
+} /* newfacet */
+
+
+/*---------------------------------
+
+  qh_newridge()
+    return a new ridge
+  notes:
+    caller sets qh.traceridge
+*/
+ridgeT *qh_newridge(qhT *qh) {
+  ridgeT *ridge;
+  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
+
+  qh_memalloc_(qh, (int)sizeof(ridgeT), freelistp, ridge, ridgeT);
+  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
+  zinc_(Ztotridges);
+  if (qh->ridge_id == UINT_MAX) {
+    qh_fprintf(qh, qh->ferr, 7074, "qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
+  }
+  ridge->id= qh->ridge_id++;
+  trace4((qh, qh->ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
+  return(ridge);
+} /* newridge */
+
+
+/*---------------------------------
+
+  qh_pointid(qh, point )
+    return id for a point,
+    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
+
+  alternative code if point is in qh.first_point...
+    unsigned long id;
+    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
+
+  notes:
+    Valid points are non-negative
+    WARN64 -- id truncated to 32-bits, at most 2G points
+    NOerrors returned (QhullPoint::id)
+    if point not in point array
+      the code does a comparison of unrelated pointers.
+*/
+int qh_pointid(qhT *qh, pointT *point) {
+  ptr_intT offset, id;
+
+  if (!point || !qh)
+    return qh_IDnone;
+  else if (point == qh->interior_point)
+    return qh_IDinterior;
+  else if (point >= qh->first_point
+  && point < qh->first_point + qh->num_points * qh->hull_dim) {
+    offset= (ptr_intT)(point - qh->first_point);
+    id= offset / qh->hull_dim;
+  }else if ((id= qh_setindex(qh->other_points, point)) != -1)
+    id += qh->num_points;
+  else
+    return qh_IDunknown;
+  return (int)id;
+} /* pointid */
+
+/*---------------------------------
+
+  qh_removefacet(qh, facet )
+    unlinks facet from qh.facet_list,
+
+  returns:
+    updates qh.facet_list .newfacet_list .facet_next visible_list
+    decrements qh.num_facets
+
+  see:
+    qh_appendfacet
+*/
+void qh_removefacet(qhT *qh, facetT *facet) {
+  facetT *next= facet->next, *previous= facet->previous; /* next is always defined */
+
+  if (facet == qh->newfacet_list)
+    qh->newfacet_list= next;
+  if (facet == qh->facet_next)
+    qh->facet_next= next;
+  if (facet == qh->visible_list)
+    qh->visible_list= next;
+  if (previous) {
+    previous->next= next;
+    next->previous= previous;
+  }else {  /* 1st facet in qh->facet_list */
+    qh->facet_list= next;
+    qh->facet_list->previous= NULL;
+  }
+  qh->num_facets--;
+  trace4((qh, qh->ferr, 4057, "qh_removefacet: removed f%d from facet_list, newfacet_list, and visible_list\n", facet->id));
+} /* removefacet */
+
+
+/*---------------------------------
+
+  qh_removevertex(qh, vertex )
+    unlinks vertex from qh.vertex_list,
+
+  returns:
+    updates qh.vertex_list .newvertex_list
+    decrements qh.num_vertices
+*/
+void qh_removevertex(qhT *qh, vertexT *vertex) {
+  vertexT *next= vertex->next, *previous= vertex->previous; /* next is always defined */
+
+  trace4((qh, qh->ferr, 4058, "qh_removevertex: remove v%d from qh.vertex_list\n", vertex->id));
+  if (vertex == qh->newvertex_list)
+    qh->newvertex_list= next;
+  if (previous) {
+    previous->next= next;
+    next->previous= previous;
+  }else {  /* 1st vertex in qh->vertex_list */
+    qh->vertex_list= next;
+    qh->vertex_list->previous= NULL;
+  }
+  qh->num_vertices--;
+} /* removevertex */
+
+
+/*---------------------------------
+
+  qh_update_vertexneighbors(qh )
+    update vertex neighbors and delete interior vertices
+
+  returns:
+    if qh.VERTEXneighbors, 
+      if qh.newvertex_list,
+         removes visible neighbors from vertex neighbors
+      if qh.newfacet_list
+         adds new facets to vertex neighbors
+      if qh.visible_list
+         interior vertices added to qh.del_vertices for later partitioning as coplanar points
+    if not qh.VERTEXneighbors (not merging)
+      interior vertices of visible facets added to qh.del_vertices for later partitioning as coplanar points
+  
+  notes
+    [jan'19] split off qh_update_vertexneighbors_cone.  Optimize the remaining cases in a future release
+    called by qh_triangulate_facet after triangulating a non-simplicial facet, followed by reset_lists
+    called by qh_triangulate after triangulating null and mirror facets
+    called by qh_all_vertexmerges after calling qh_merge_pinchedvertices
+
+  design:
+    if qh.VERTEXneighbors
+      for each vertex on newvertex_list (i.e., new vertices and vertices of new facets)
+        delete visible facets from vertex neighbors
+      for each new facet on newfacet_list
+        for each vertex of facet
+          append facet to vertex neighbors
+      for each visible facet on qh.visible_list
+        for each vertex of facet
+          if the vertex is not on a new facet and not itself deleted
+            if the vertex has a not-visible neighbor (due to merging)
+               remove the visible facet from the vertex's neighbors
+            otherwise
+               add the vertex to qh.del_vertices for later deletion
+
+    if not qh.VERTEXneighbors (not merging)
+      for each vertex of a visible facet
+        if the vertex is not on a new facet and not itself deleted
+           add the vertex to qh.del_vertices for later deletion
+*/
+void qh_update_vertexneighbors(qhT *qh /* qh.newvertex_list, newfacet_list, visible_list */) {
+  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
+  vertexT *vertex, **vertexp;
+  int neighborcount= 0;
+
+  if (qh->VERTEXneighbors) {
+    trace3((qh, qh->ferr, 3013, "qh_update_vertexneighbors: update v.neighbors for qh.newvertex_list (v%d) and qh.newfacet_list (f%d)\n",
+         getid_(qh->newvertex_list), getid_(qh->newfacet_list)));
+    FORALLvertex_(qh->newvertex_list) {
+      neighborcount= 0;
+      FOREACHneighbor_(vertex) {
+        if (neighbor->visible) {
+          neighborcount++;
+          SETref_(neighbor)= NULL;
+        }
+      }
+      if (neighborcount) {
+        trace4((qh, qh->ferr, 4046, "qh_update_vertexneighbors: delete %d of %d vertex neighbors for v%d.  Removes to-be-deleted, visible facets\n",
+          neighborcount, qh_setsize(qh, vertex->neighbors), vertex->id));
+        qh_setcompact(qh, vertex->neighbors);
+      }
+    }
+    FORALLnew_facets {
+      if (qh->first_newfacet && newfacet->id >= qh->first_newfacet) {
+        FOREACHvertex_(newfacet->vertices)
+          qh_setappend(qh, &vertex->neighbors, newfacet);
+      }else {  /* called after qh_merge_pinchedvertices.  In 7-D, many more neighbors than new facets.  qh_setin is expensive */
+        FOREACHvertex_(newfacet->vertices)
+          qh_setunique(qh, &vertex->neighbors, newfacet); 
+      }
+    }
+    trace3((qh, qh->ferr, 3058, "qh_update_vertexneighbors: delete interior vertices for qh.visible_list (f%d)\n",
+        getid_(qh->visible_list)));
+    FORALLvisible_facets {
+      FOREACHvertex_(visible->vertices) {
+        if (!vertex->newfacet && !vertex->deleted) {
+          FOREACHneighbor_(vertex) { /* this can happen under merging */
+            if (!neighbor->visible)
+              break;
+          }
+          if (neighbor)
+            qh_setdel(vertex->neighbors, visible);
+          else {
+            vertex->deleted= True;
+            qh_setappend(qh, &qh->del_vertices, vertex);
+            trace2((qh, qh->ferr, 2041, "qh_update_vertexneighbors: delete interior vertex p%d(v%d) of visible f%d\n",
+                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
+          }
+        }
+      }
+    }
+  }else {  /* !VERTEXneighbors */
+    trace3((qh, qh->ferr, 3058, "qh_update_vertexneighbors: delete old vertices for qh.visible_list (f%d)\n",
+      getid_(qh->visible_list)));
+    FORALLvisible_facets {
+      FOREACHvertex_(visible->vertices) {
+        if (!vertex->newfacet && !vertex->deleted) {
+          vertex->deleted= True;
+          qh_setappend(qh, &qh->del_vertices, vertex);
+          trace2((qh, qh->ferr, 2042, "qh_update_vertexneighbors: will delete interior vertex p%d(v%d) of visible f%d\n",
+                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
+        }
+      }
+    }
+  }
+} /* update_vertexneighbors */
+
+/*---------------------------------
+
+  qh_update_vertexneighbors_cone(qh )
+    update vertex neighbors for a cone of new facets and delete interior vertices
+
+  returns:
+    if qh.VERTEXneighbors, 
+      if qh.newvertex_list,
+         removes visible neighbors from vertex neighbors
+      if qh.newfacet_list
+         adds new facets to vertex neighbors
+      if qh.visible_list
+         interior vertices added to qh.del_vertices for later partitioning as coplanar points
+    if not qh.VERTEXneighbors (not merging)
+      interior vertices of visible facets added to qh.del_vertices for later partitioning as coplanar points
+  
+  notes
+    called by qh_addpoint after create cone and before premerge
+
+  design:
+    if qh.VERTEXneighbors
+      for each vertex on newvertex_list (i.e., new vertices and vertices of new facets)
+        delete visible facets from vertex neighbors
+      for each new facet on newfacet_list
+        for each vertex of facet
+          append facet to vertex neighbors
+      for each visible facet on qh.visible_list
+        for each vertex of facet
+          if the vertex is not on a new facet and not itself deleted
+            if the vertex has a not-visible neighbor (due to merging)
+               remove the visible facet from the vertex's neighbors
+            otherwise
+               add the vertex to qh.del_vertices for later deletion
+
+    if not qh.VERTEXneighbors (not merging)
+      for each vertex of a visible facet
+        if the vertex is not on a new facet and not itself deleted
+           add the vertex to qh.del_vertices for later deletion
+
+*/
+void qh_update_vertexneighbors_cone(qhT *qh /* qh.newvertex_list, newfacet_list, visible_list */) {
+  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
+  vertexT *vertex, **vertexp;
+  int delcount= 0;
+
+  if (qh->VERTEXneighbors) {
+    trace3((qh, qh->ferr, 3059, "qh_update_vertexneighbors_cone: update v.neighbors for qh.newvertex_list (v%d) and qh.newfacet_list (f%d)\n",
+         getid_(qh->newvertex_list), getid_(qh->newfacet_list)));
+    FORALLvertex_(qh->newvertex_list) {
+      delcount= 0;
+      FOREACHneighbor_(vertex) {
+        if (neighbor->visible) { /* alternative design is a loop over visible facets, but needs qh_setdel() */
+          delcount++;
+          qh_setdelnth(qh, vertex->neighbors, SETindex_(vertex->neighbors, neighbor));
+          neighborp--; /* repeat */
+        }
+      }
+      if (delcount) {
+        trace4((qh, qh->ferr, 4021, "qh_update_vertexneighbors_cone: deleted %d visible vertexneighbors of v%d\n",
+          delcount, vertex->id));
+      }
+    }
+    FORALLnew_facets {
+      FOREACHvertex_(newfacet->vertices)
+        qh_setappend(qh, &vertex->neighbors, newfacet);
+    }
+    trace3((qh, qh->ferr, 3065, "qh_update_vertexneighbors_cone: delete interior vertices, if any, for qh.visible_list (f%d)\n",
+        getid_(qh->visible_list)));
+    FORALLvisible_facets {
+      FOREACHvertex_(visible->vertices) {
+        if (!vertex->newfacet && !vertex->deleted) {
+          FOREACHneighbor_(vertex) { /* this can happen under merging, qh_checkfacet QH4025 */
+            if (!neighbor->visible)
+              break;
+          }
+          if (neighbor)
+            qh_setdel(vertex->neighbors, visible);
+          else {
+            vertex->deleted= True;
+            qh_setappend(qh, &qh->del_vertices, vertex);
+            trace2((qh, qh->ferr, 2102, "qh_update_vertexneighbors_cone: will delete interior vertex p%d(v%d) of visible f%d\n",
+              qh_pointid(qh, vertex->point), vertex->id, visible->id));
+          }
+        }
+      }
+    }
+  }else {  /* !VERTEXneighbors */
+    trace3((qh, qh->ferr, 3066, "qh_update_vertexneighbors_cone: delete interior vertices for qh.visible_list (f%d)\n",
+      getid_(qh->visible_list)));
+    FORALLvisible_facets {
+      FOREACHvertex_(visible->vertices) {
+        if (!vertex->newfacet && !vertex->deleted) {
+          vertex->deleted= True;
+          qh_setappend(qh, &qh->del_vertices, vertex);
+          trace2((qh, qh->ferr, 2059, "qh_update_vertexneighbors_cone: will delete interior vertex p%d(v%d) of visible f%d\n",
+                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
+        }
+      }
+    }
+  }
+} /* update_vertexneighbors_cone */
+
diff --git a/vendor/qhull/libqhull_r/poly_r.h b/vendor/qhull/libqhull_r/poly_r.h
new file mode 100644
index 0000000..83c5914
--- /dev/null
+++ b/vendor/qhull/libqhull_r/poly_r.h
@@ -0,0 +1,310 @@
+/*
  ---------------------------------
+
+   poly_r.h
+   header file for poly_r.c and poly2_r.c
+
+   see qh-poly_r.htm, libqhull_r.h and poly_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2963 $
+   $DateTime: 2020/06/03 19:31:01 $$Author: bbarber $
+*/
+
+#ifndef qhDEFpoly
+#define qhDEFpoly 1
+
+#include "libqhull_r.h"
+
+/*===============   constants ========================== */
+
+/*----------------------------------
+
+  qh_ALGORITHMfault
+    use as argument to checkconvex() to report errors during buildhull
+*/
+#define qh_ALGORITHMfault 0
+
+/*----------------------------------
+
+  qh_DATAfault
+    use as argument to checkconvex() to report errors during initialhull
+*/
+#define qh_DATAfault 1
+
+/*----------------------------------
+
+  qh_DUPLICATEridge
+    special value for facet->neighbor to indicate a duplicate ridge
+
+  notes:
+    set by qh_matchneighbor for qh_matchdupridge
+*/
+#define qh_DUPLICATEridge (facetT *)1L
+
+/*----------------------------------
+
+  qh_MERGEridge       flag in facet
+    special value for facet->neighbor to indicate a duplicate ridge that needs merging
+
+  notes:
+    set by qh_matchnewfacets..qh_matchdupridge from qh_DUPLICATEridge
+    used by qh_mark_dupridges to set facet->mergeridge, facet->mergeridge2 from facet->dupridge
+*/
+#define qh_MERGEridge (facetT *)2L
+
+
+/*============ -structures- ====================*/
+
+/*=========== -macros- =========================*/
+
+/*----------------------------------
+
+  FORALLfacet_( facetlist ) { ... }
+    assign 'facet' to each facet in facetlist
+
+  notes:
+    uses 'facetT *facet;'
+    assumes last facet is a sentinel
+
+  see:
+    FORALLfacets
+*/
+#define FORALLfacet_( facetlist ) if (facetlist) for ( facet=(facetlist); facet && facet->next; facet= facet->next )
+
+/*----------------------------------
+
+  FORALLnew_facets { ... }
+    assign 'newfacet' to each facet in qh.newfacet_list
+
+  notes:
+    uses 'facetT *newfacet;'
+    at exit, newfacet==NULL
+*/
+#define FORALLnew_facets for ( newfacet=qh->newfacet_list; newfacet && newfacet->next; newfacet=newfacet->next )
+
+/*----------------------------------
+
+  FORALLvertex_( vertexlist ) { ... }
+    assign 'vertex' to each vertex in vertexlist
+
+  notes:
+    uses 'vertexT *vertex;'
+    at exit, vertex==NULL
+*/
+#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
+
+/*----------------------------------
+
+  FORALLvisible_facets { ... }
+    assign 'visible' to each visible facet in qh.visible_list
+
+  notes:
+    uses 'vacetT *visible;'
+    at exit, visible==NULL
+*/
+#define FORALLvisible_facets for (visible=qh->visible_list; visible && visible->visible; visible= visible->next)
+
+/*----------------------------------
+
+  FORALLsame_( newfacet ) { ... }
+    assign 'same' to each facet in newfacet->f.samecycle
+
+  notes:
+    uses 'facetT *same;'
+    stops when it returns to newfacet
+*/
+#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
+
+/*----------------------------------
+
+  FORALLsame_cycle_( newfacet ) { ... }
+    assign 'same' to each facet in newfacet->f.samecycle
+
+  notes:
+    uses 'facetT *same;'
+    at exit, same == NULL
+*/
+#define FORALLsame_cycle_(newfacet) \
+     for (same= newfacet->f.samecycle; \
+         same; same= (same == newfacet ?  NULL : same->f.samecycle))
+
+/*----------------------------------
+
+  FOREACHneighborA_( facet ) { ... }
+    assign 'neighborA' to each neighbor in facet->neighbors
+
+  FOREACHneighborA_( vertex ) { ... }
+    assign 'neighborA' to each neighbor in vertex->neighbors
+
+  declare:
+    facetT *neighborA, **neighborAp;
+
+  see:
+    FOREACHsetelement_
+*/
+#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
+
+/*----------------------------------
+
+  FOREACHvisible_( facets ) { ... }
+    assign 'visible' to each facet in facets
+
+  notes:
+    uses 'facetT *facet, *facetp;'
+    see FOREACHsetelement_
+*/
+#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
+
+/*----------------------------------
+
+  FOREACHnewfacet_( facets ) { ... }
+    assign 'newfacet' to each facet in facets
+
+  notes:
+    uses 'facetT *newfacet, *newfacetp;'
+    see FOREACHsetelement_
+*/
+#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
+
+/*----------------------------------
+
+  FOREACHvertexA_( vertices ) { ... }
+    assign 'vertexA' to each vertex in vertices
+
+  notes:
+    uses 'vertexT *vertexA, *vertexAp;'
+    see FOREACHsetelement_
+*/
+#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
+
+/*----------------------------------
+
+  FOREACHvertexreverse12_( vertices ) { ... }
+    assign 'vertex' to each vertex in vertices
+    reverse order of first two vertices
+
+  notes:
+    uses 'vertexT *vertex, *vertexp;'
+    see FOREACHsetelement_
+*/
+#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
+
+
+/*=============== prototypes poly_r.c in alphabetical order ================*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void    qh_appendfacet(qhT *qh, facetT *facet);
+void    qh_appendvertex(qhT *qh, vertexT *vertex);
+void    qh_attachnewfacets(qhT *qh /* qh.visible_list, qh.newfacet_list */);
+boolT   qh_checkflipped(qhT *qh, facetT *facet, realT *dist, boolT allerror);
+void    qh_delfacet(qhT *qh, facetT *facet);
+void    qh_deletevisible(qhT *qh /* qh.visible_list, qh.horizon_list */);
+setT   *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
+int     qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem);
+facetT *qh_getreplacement(qhT *qh, facetT *visible);
+facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient, facetT *facet);
+void    qh_makenewplanes(qhT *qh /* qh.newfacet_list */);
+facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
+facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
+void    qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize,
+                          int *hashcount);
+coordT  qh_matchnewfacets(qhT *qh);
+boolT   qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
+                          setT *verticesB, int *skipB, boolT *same);
+facetT *qh_newfacet(qhT *qh);
+ridgeT *qh_newridge(qhT *qh);
+int     qh_pointid(qhT *qh, pointT *point);
+void    qh_removefacet(qhT *qh, facetT *facet);
+void    qh_removevertex(qhT *qh, vertexT *vertex);
+void    qh_update_vertexneighbors(qhT *qh);
+void    qh_update_vertexneighbors_cone(qhT *qh);
+
+
+/*========== -prototypes poly2_r.c in alphabetical order ===========*/
+
+boolT   qh_addfacetvertex(qhT *qh, facetT *facet, vertexT *newvertex);
+void    qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash);
+void    qh_check_bestdist(qhT *qh);
+void    qh_check_maxout(qhT *qh);
+void    qh_check_output(qhT *qh);
+void    qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2, int *errcount);
+void    qh_check_points(qhT *qh);
+void    qh_checkconvex(qhT *qh, facetT *facetlist, int fault);
+void    qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp);
+void    qh_checkflipped_all(qhT *qh, facetT *facetlist);
+boolT   qh_checklists(qhT *qh, facetT *facetlist);
+void    qh_checkpolygon(qhT *qh, facetT *facetlist);
+void    qh_checkvertex(qhT *qh, vertexT *vertex, boolT allchecks, boolT *waserrorp);
+void    qh_clearcenters(qhT *qh, qh_CENTER type);
+void    qh_createsimplex(qhT *qh, setT *vertices);
+void    qh_delridge(qhT *qh, ridgeT *ridge);
+void    qh_delvertex(qhT *qh, vertexT *vertex);
+setT   *qh_facet3vertex(qhT *qh, facetT *facet);
+facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
+           realT *bestdist, boolT *isoutside);
+facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
+facetT *qh_findfacet_all(qhT *qh, pointT *point, boolT noupper, realT *bestdist, boolT *isoutside,
+                          int *numpart);
+int     qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon);
+void    qh_findgood_all(qhT *qh, facetT *facetlist);
+void    qh_furthestnext(qhT *qh /* qh.facet_list */);
+void    qh_furthestout(qhT *qh, facetT *facet);
+void    qh_infiniteloop(qhT *qh, facetT *facet);
+void    qh_initbuild(qhT *qh);
+void    qh_initialhull(qhT *qh, setT *vertices);
+setT   *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints);
+vertexT *qh_isvertex(pointT *point, setT *vertices);
+vertexT *qh_makenewfacets(qhT *qh, pointT *point /* qh.horizon_list, visible_list */);
+coordT  qh_matchdupridge(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount);
+void    qh_nearcoplanar(qhT *qh /* qh.facet_list */);
+vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
+int     qh_newhashtable(qhT *qh, int newsize);
+vertexT *qh_newvertex(qhT *qh, pointT *point);
+facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp);
+ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
+vertexT *qh_opposite_vertex(qhT *qh, facetT *facetA,  facetT *neighbor);
+void    qh_outcoplanar(qhT *qh /* qh.facet_list */);
+pointT *qh_point(qhT *qh, int id);
+void    qh_point_add(qhT *qh, setT *set, pointT *point, void *elem);
+setT   *qh_pointfacet(qhT *qh /* qh.facet_list */);
+setT   *qh_pointvertex(qhT *qh /* qh.facet_list */);
+void    qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist);
+void    qh_printhashtable(qhT *qh, FILE *fp);
+void    qh_printlists(qhT *qh);
+void    qh_replacefacetvertex(qhT *qh, facetT *facet, vertexT *oldvertex, vertexT *newvertex);
+void    qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /* qh.newvertex_list qh.newfacet_list qh.visible_list */);
+void    qh_setvoronoi_all(qhT *qh);
+void    qh_triangulate(qhT *qh /* qh.facet_list */);
+void    qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex);
+void    qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
+void    qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB);
+void    qh_triangulate_null(qhT *qh, facetT *facetA);
+void    qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB);
+setT   *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB);
+void    qh_vertexneighbors(qhT *qh /* qh.facet_list */);
+boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFpoly */
diff --git a/vendor/qhull/libqhull_r/qhull_ra.h b/vendor/qhull/libqhull_r/qhull_ra.h
new file mode 100644
index 0000000..52ccd85
--- /dev/null
+++ b/vendor/qhull/libqhull_r/qhull_ra.h
@@ -0,0 +1,161 @@
+/*
  ---------------------------------
+
+   qhull_ra.h
+   all header files for compiling qhull with reentrant code
+   included before C++ headers for user_r.h:QHULL_CRTDBG
+
+   see qh-qhull.htm
+
+   see libqhull_r.h for user-level definitions
+
+   see user_r.h for user-definable constants
+
+   defines internal functions for libqhull_r.c global_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/qhull_ra.h#2 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+
+   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
+           full parens around (x?y:z)
+           use '#include "libqhull_r/qhull_ra.h"' to avoid name clashes
+*/
+
+#ifndef qhDEFqhulla
+#define qhDEFqhulla 1
+
+#include "libqhull_r.h"  /* Includes user_r.h and data types */
+
+#include "stat_r.h"
+#include "random_r.h"
+#include "mem_r.h"
+#include "qset_r.h"
+#include "geom_r.h"
+#include "merge_r.h"
+#include "poly_r.h"
+#include "io_r.h"
+
+#include 
+#include 
+#include 
+#include     /* some compilers will not need float.h */
+#include 
+#include 
+#include 
+#include 
+#include 
+/*** uncomment here and qset_r.c
+     if string.h does not define memcpy()
+#include 
+*/
+
+#if qh_CLOCKtype == 2  /* defined in user_r.h from libqhull_r.h */
+#include 
+#include 
+#include 
+#endif
+
+#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
+#pragma warning( disable : 4100)  /* unreferenced formal parameter */
+#pragma warning( disable : 4127)  /* conditional expression is constant */
+#pragma warning( disable : 4706)  /* assignment within conditional function */
+#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
+#endif
+
+/* ======= -macros- =========== */
+
+/*----------------------------------
+
+  traceN((qh, qh->ferr, 0Nnnn, "format\n", vars));
+    calls qh_fprintf if qh.IStracing >= N
+
+    Add debugging traps to the end of qh_fprintf
+
+  notes:
+    removing tracing reduces code size but doesn't change execution speed
+*/
+#ifndef qh_NOtrace
+#define trace0(args) {if (qh->IStracing) qh_fprintf args;}
+#define trace1(args) {if (qh->IStracing >= 1) qh_fprintf args;}
+#define trace2(args) {if (qh->IStracing >= 2) qh_fprintf args;}
+#define trace3(args) {if (qh->IStracing >= 3) qh_fprintf args;}
+#define trace4(args) {if (qh->IStracing >= 4) qh_fprintf args;}
+#define trace5(args) {if (qh->IStracing >= 5) qh_fprintf args;}
+#else /* qh_NOtrace */
+#define trace0(args) {}
+#define trace1(args) {}
+#define trace2(args) {}
+#define trace3(args) {}
+#define trace4(args) {}
+#define trace5(args) {}
+#endif /* qh_NOtrace */
+
+/*----------------------------------
+
+  Define an unused variable to avoid compiler warnings
+
+  Derived from Qt's corelib/global/qglobal.h
+
+*/
+
+#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
+template 
+inline void qhullUnused(T &x) { (void)x; }
+#  define QHULL_UNUSED(x) qhullUnused(x);
+#else
+#  define QHULL_UNUSED(x) (void)x;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/***** -libqhull_r.c prototypes (alphabetical after qhull) ********************/
+
+void    qh_qhull(qhT *qh);
+boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
+void    qh_build_withrestart(qhT *qh);
+vertexT *qh_buildcone(qhT *qh, pointT *furthest, facetT *facet, int goodhorizon, facetT **retryfacet);
+boolT   qh_buildcone_mergepinched(qhT *qh, vertexT *apex, facetT *facet, facetT **retryfacet);
+boolT   qh_buildcone_onlygood(qhT *qh, vertexT *apex, int goodhorizon);
+void    qh_buildhull(qhT *qh);
+void    qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet);
+void    qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet);
+void    qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
+pointT *qh_nextfurthest(qhT *qh, facetT **visible);
+void    qh_partitionall(qhT *qh, setT *vertices, pointT *points,int npoints);
+void    qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist, boolT allnew);
+void    qh_partitionpoint(qhT *qh, pointT *point, facetT *facet);
+void    qh_partitionvisible(qhT *qh, boolT allpoints, int *numpoints);
+void    qh_joggle_restart(qhT *qh, const char *reason);
+void    qh_printsummary(qhT *qh, FILE *fp);
+
+/***** -global_r.c internal prototypes (alphabetical) ***********************/
+
+void    qh_appendprint(qhT *qh, qh_PRINT format);
+void    qh_freebuild(qhT *qh, boolT allmem);
+void    qh_freebuffers(qhT *qh);
+void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
+
+/***** -stat_r.c internal prototypes (alphabetical) ***********************/
+
+void    qh_allstatA(qhT *qh);
+void    qh_allstatB(qhT *qh);
+void    qh_allstatC(qhT *qh);
+void    qh_allstatD(qhT *qh);
+void    qh_allstatE(qhT *qh);
+void    qh_allstatE2(qhT *qh);
+void    qh_allstatF(qhT *qh);
+void    qh_allstatG(qhT *qh);
+void    qh_allstatH(qhT *qh);
+void    qh_freebuffers(qhT *qh);
+void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFqhulla */
diff --git a/vendor/qhull/libqhull_r/qset_r.c b/vendor/qhull/libqhull_r/qset_r.c
new file mode 100644
index 0000000..50cdb21
--- /dev/null
+++ b/vendor/qhull/libqhull_r/qset_r.c
@@ -0,0 +1,1383 @@
+/*
  ---------------------------------
+
+   qset_r.c
+   implements set manipulations needed for quickhull
+
+   see qh-set_r.htm and qset_r.h
+
+   Be careful of strict aliasing (two pointers of different types
+   that reference the same location).  The last slot of a set is
+   either the actual size of the set plus 1, or the NULL terminator
+   of the set (i.e., setelemT).
+
+   Only reference qh for qhmem or qhstat.  Otherwise the matching code in qset.c will bring in qhT
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/qset_r.c#8 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */
+#include "qset_r.h"
+#include "mem_r.h"
+#include 
+#include 
+/*** uncomment here and qhull_ra.h
+     if string.h does not define memcpy()
+#include 
+*/
+
+#ifndef qhDEFlibqhull
+typedef struct ridgeT ridgeT;
+typedef struct facetT facetT;
+void    qh_errexit(qhT *qh, int exitcode, facetT *, ridgeT *);
+void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
+#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
+#  pragma warning( disable : 4127)  /* conditional expression is constant */
+#  pragma warning( disable : 4706)  /* assignment within conditional function */
+#  endif
+#endif
+
+/*=============== internal macros ===========================*/
+
+/*============ functions in alphabetical order ===================*/
+
+/*----------------------------------
+
+  qh_setaddnth(qh, setp, nth, newelem )
+    adds newelem as n'th element of sorted or unsorted *setp
+
+  notes:
+    *setp and newelem must be defined
+    *setp may be a temp set
+    nth=0 is first element
+    errors if nth is out of bounds
+
+  design:
+    expand *setp if empty or full
+    move tail of *setp up one
+    insert newelem
+*/
+void qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem) {
+  int oldsize, i;
+  setelemT *sizep;          /* avoid strict aliasing */
+  setelemT *oldp, *newp;
+
+  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
+    qh_setlarger(qh, setp);
+    sizep= SETsizeaddr_(*setp);
+  }
+  oldsize= sizep->i - 1;
+  if (nth < 0 || nth > oldsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
+    qh_setprint(qh, qh->qhmem.ferr, "", *setp);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  sizep->i++;
+  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
+  newp= oldp+1;
+  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
+    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
+  newp->p= newelem;
+} /* setaddnth */
+
+
+/*----------------------------------
+
+  setaddsorted( setp, newelem )
+    adds an newelem into sorted *setp
+
+  notes:
+    *setp and newelem must be defined
+    *setp may be a temp set
+    nop if newelem already in set
+
+  design:
+    find newelem's position in *setp
+    insert newelem
+*/
+void qh_setaddsorted(qhT *qh, setT **setp, void *newelem) {
+  int newindex=0;
+  void *elem, **elemp;
+
+  FOREACHelem_(*setp) {          /* could use binary search instead */
+    if (elem < newelem)
+      newindex++;
+    else if (elem == newelem)
+      return;
+    else
+      break;
+  }
+  qh_setaddnth(qh, setp, newindex, newelem);
+} /* setaddsorted */
+
+
+/*---------------------------------
+
+  qh_setappend(qh, setp, newelem )
+    append newelem to *setp
+
+  notes:
+    *setp may be a temp set
+    *setp and newelem may be NULL
+
+  design:
+    expand *setp if empty or full
+    append newelem to *setp
+
+*/
+void qh_setappend(qhT *qh, setT **setp, void *newelem) {
+  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
+  setelemT *endp;
+  int count;
+
+  if (!newelem)
+    return;
+  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
+    qh_setlarger(qh, setp);
+    sizep= SETsizeaddr_(*setp);
+  }
+  count= (sizep->i)++ - 1;
+  endp= (setelemT *)SETelemaddr_(*setp, count, void);
+  (endp++)->p= newelem;
+  endp->p= NULL;
+} /* setappend */
+
+/*---------------------------------
+
+  qh_setappend_set(qh, setp, setA )
+    appends setA to *setp
+
+  notes:
+    *setp can not be a temp set
+    *setp and setA may be NULL
+
+  design:
+    setup for copy
+    expand *setp if it is too small
+    append all elements of setA to *setp
+*/
+void qh_setappend_set(qhT *qh, setT **setp, setT *setA) {
+  int sizeA, size;
+  setT *oldset;
+  setelemT *sizep;
+
+  if (!setA)
+    return;
+  SETreturnsize_(setA, sizeA);
+  if (!*setp)
+    *setp= qh_setnew(qh, sizeA);
+  sizep= SETsizeaddr_(*setp);
+  if (!(size= sizep->i))
+    size= (*setp)->maxsize;
+  else
+    size--;
+  if (size + sizeA > (*setp)->maxsize) {
+    oldset= *setp;
+    *setp= qh_setcopy(qh, oldset, sizeA);
+    qh_setfree(qh, &oldset);
+    sizep= SETsizeaddr_(*setp);
+  }
+  if (sizeA > 0) {
+    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
+    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
+  }
+} /* setappend_set */
+
+
+/*---------------------------------
+
+  qh_setappend2ndlast(qh, setp, newelem )
+    makes newelem the next to the last element in *setp
+
+  notes:
+    *setp must have at least one element
+    newelem must be defined
+    *setp may be a temp set
+
+  design:
+    expand *setp if empty or full
+    move last element of *setp up one
+    insert newelem
+*/
+void qh_setappend2ndlast(qhT *qh, setT **setp, void *newelem) {
+    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
+    setelemT *endp, *lastp;
+    int count;
+
+    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
+        qh_setlarger(qh, setp);
+        sizep= SETsizeaddr_(*setp);
+    }
+    count= (sizep->i)++ - 1;
+    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
+    lastp= endp-1;
+    *(endp++)= *lastp;
+    endp->p= NULL;    /* may overwrite *sizep */
+    lastp->p= newelem;
+} /* setappend2ndlast */
+
+/*---------------------------------
+
+  qh_setcheck(qh, set, typename, id )
+    check set for validity
+    report errors with typename and id
+
+  design:
+    checks that maxsize, actual size, and NULL terminator agree
+*/
+void qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned int id) {
+  int maxsize, size;
+  int waserr= 0;
+
+  if (!set)
+    return;
+  SETreturnsize_(set, size);
+  maxsize= set->maxsize;
+  if (size > maxsize || !maxsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
+             size, tname, id, maxsize);
+    waserr= 1;
+  }else if (set->e[size].p) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
+             tname, id, size-1, maxsize);
+    waserr= 1;
+  }
+  if (waserr) {
+    qh_setprint(qh, qh->qhmem.ferr, "ERRONEOUS", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+} /* setcheck */
+
+
+/*---------------------------------
+
+  qh_setcompact(qh, set )
+    remove internal NULLs from an unsorted set
+
+  returns:
+    updated set
+
+  notes:
+    set may be NULL
+    it would be faster to swap tail of set into holes, like qh_setdel
+
+  design:
+    setup pointers into set
+    skip NULLs while copying elements to start of set
+    update the actual size
+*/
+void qh_setcompact(qhT *qh, setT *set) {
+  int size;
+  void **destp, **elemp, **endp, **firstp;
+
+  if (!set)
+    return;
+  SETreturnsize_(set, size);
+  destp= elemp= firstp= SETaddr_(set, void);
+  endp= destp + size;
+  while (1) {
+    if (!(*destp++= *elemp++)) {
+      destp--;
+      if (elemp > endp)
+        break;
+    }
+  }
+  qh_settruncate(qh, set, (int)(destp-firstp));   /* WARN64 */
+} /* setcompact */
+
+
+/*---------------------------------
+
+  qh_setcopy(qh, set, extra )
+    make a copy of a sorted or unsorted set with extra slots
+
+  returns:
+    new set
+
+  design:
+    create a newset with extra slots
+    copy the elements to the newset
+
+*/
+setT *qh_setcopy(qhT *qh, setT *set, int extra) {
+  setT *newset;
+  int size;
+
+  if (extra < 0)
+    extra= 0;
+  SETreturnsize_(set, size);
+  newset= qh_setnew(qh, size+extra);
+  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
+  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
+  return(newset);
+} /* setcopy */
+
+
+/*---------------------------------
+
+  qh_setdel(set, oldelem )
+    delete oldelem from an unsorted set
+
+  returns:
+    returns oldelem if found
+    returns NULL otherwise
+
+  notes:
+    set may be NULL
+    oldelem must not be NULL;
+    only deletes one copy of oldelem in set
+
+  design:
+    locate oldelem
+    update actual size if it was full
+    move the last element to the oldelem's location
+*/
+void *qh_setdel(setT *set, void *oldelem) {
+  setelemT *sizep;
+  setelemT *elemp;
+  setelemT *lastp;
+
+  if (!set)
+    return NULL;
+  elemp= (setelemT *)SETaddr_(set, void);
+  while (elemp->p != oldelem && elemp->p)
+    elemp++;
+  if (elemp->p) {
+    sizep= SETsizeaddr_(set);
+    if (!(sizep->i)--)         /*  if was a full set */
+      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
+    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
+    elemp->p= lastp->p;      /* may overwrite itself */
+    lastp->p= NULL;
+    return oldelem;
+  }
+  return NULL;
+} /* setdel */
+
+
+/*---------------------------------
+
+  qh_setdellast( set )
+    return last element of set or NULL
+
+  notes:
+    deletes element from set
+    set may be NULL
+
+  design:
+    return NULL if empty
+    if full set
+      delete last element and set actual size
+    else
+      delete last element and update actual size
+*/
+void *qh_setdellast(setT *set) {
+  int setsize;  /* actually, actual_size + 1 */
+  int maxsize;
+  setelemT *sizep;
+  void *returnvalue;
+
+  if (!set || !(set->e[0].p))
+    return NULL;
+  sizep= SETsizeaddr_(set);
+  if ((setsize= sizep->i)) {
+    returnvalue= set->e[setsize - 2].p;
+    set->e[setsize - 2].p= NULL;
+    sizep->i--;
+  }else {
+    maxsize= set->maxsize;
+    returnvalue= set->e[maxsize - 1].p;
+    set->e[maxsize - 1].p= NULL;
+    sizep->i= maxsize;
+  }
+  return returnvalue;
+} /* setdellast */
+
+
+/*---------------------------------
+
+  qh_setdelnth(qh, set, nth )
+    deletes nth element from unsorted set
+    0 is first element
+
+  returns:
+    returns the element (needs type conversion)
+
+  notes:
+    errors if nth invalid
+
+  design:
+    setup points and check nth
+    delete nth element and overwrite with last element
+*/
+void *qh_setdelnth(qhT *qh, setT *set, int nth) {
+  void *elem;
+  setelemT *sizep;
+  setelemT *elemp, *lastp;
+
+  sizep= SETsizeaddr_(set);
+  if ((sizep->i--)==0)         /*  if was a full set */
+    sizep->i= set->maxsize;    /*    *sizep= (maxsize-1)+ 1 */
+  if (nth < 0 || nth >= sizep->i) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
+  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
+  elem= elemp->p;
+  elemp->p= lastp->p;      /* may overwrite itself */
+  lastp->p= NULL;
+  return elem;
+} /* setdelnth */
+
+/*---------------------------------
+
+  qh_setdelnthsorted(qh, set, nth )
+    deletes nth element from sorted set
+
+  returns:
+    returns the element (use type conversion)
+
+  notes:
+    errors if nth invalid
+
+  see also:
+    setnew_delnthsorted
+
+  design:
+    setup points and check nth
+    copy remaining elements down one
+    update actual size
+*/
+void *qh_setdelnthsorted(qhT *qh, setT *set, int nth) {
+  void *elem;
+  setelemT *sizep;
+  setelemT *newp, *oldp;
+
+  sizep= SETsizeaddr_(set);
+  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  newp= (setelemT *)SETelemaddr_(set, nth, void);
+  elem= newp->p;
+  oldp= newp+1;
+  while (((newp++)->p= (oldp++)->p))
+    ; /* copy remaining elements and NULL */
+  if ((sizep->i--)==0)         /*  if was a full set */
+    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
+  return elem;
+} /* setdelnthsorted */
+
+
+/*---------------------------------
+
+  qh_setdelsorted( set, oldelem )
+    deletes oldelem from sorted set
+
+  returns:
+    returns oldelem if it was deleted
+
+  notes:
+    set may be NULL
+
+  design:
+    locate oldelem in set
+    copy remaining elements down one
+    update actual size
+*/
+void *qh_setdelsorted(setT *set, void *oldelem) {
+  setelemT *sizep;
+  setelemT *newp, *oldp;
+
+  if (!set)
+    return NULL;
+  newp= (setelemT *)SETaddr_(set, void);
+  while(newp->p != oldelem && newp->p)
+    newp++;
+  if (newp->p) {
+    oldp= newp+1;
+    while (((newp++)->p= (oldp++)->p))
+      ; /* copy remaining elements */
+    sizep= SETsizeaddr_(set);
+    if ((sizep->i--)==0)    /*  if was a full set */
+      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
+    return oldelem;
+  }
+  return NULL;
+} /* setdelsorted */
+
+
+/*---------------------------------
+
+  qh_setduplicate(qh, set, elemsize )
+    duplicate a set of elemsize elements
+
+  notes:
+    use setcopy if retaining old elements
+
+  design:
+    create a new set
+    for each elem of the old set
+      create a newelem
+      append newelem to newset
+*/
+setT *qh_setduplicate(qhT *qh, setT *set, int elemsize) {
+  void          *elem, **elemp, *newElem;
+  setT          *newSet;
+  int           size;
+
+  if (!(size= qh_setsize(qh, set)))
+    return NULL;
+  newSet= qh_setnew(qh, size);
+  FOREACHelem_(set) {
+    newElem= qh_memalloc(qh, elemsize);
+    memcpy(newElem, elem, (size_t)elemsize);
+    qh_setappend(qh, &newSet, newElem);
+  }
+  return newSet;
+} /* setduplicate */
+
+
+/*---------------------------------
+
+  qh_setendpointer( set )
+    Returns pointer to NULL terminator of a set's elements
+    set can not be NULL
+
+*/
+void **qh_setendpointer(setT *set) {
+
+  setelemT *sizep= SETsizeaddr_(set);
+  int n= sizep->i;
+  return (n ? &set->e[n-1].p : &sizep->p);
+} /* qh_setendpointer */
+
+/*---------------------------------
+
+  qh_setequal( setA, setB )
+    returns 1 if two sorted sets are equal, otherwise returns 0
+
+  notes:
+    either set may be NULL
+
+  design:
+    check size of each set
+    setup pointers
+    compare elements of each set
+*/
+int qh_setequal(setT *setA, setT *setB) {
+  void **elemAp, **elemBp;
+  int sizeA= 0, sizeB= 0;
+
+  if (setA) {
+    SETreturnsize_(setA, sizeA);
+  }
+  if (setB) {
+    SETreturnsize_(setB, sizeB);
+  }
+  if (sizeA != sizeB)
+    return 0;
+  if (!sizeA)
+    return 1;
+  elemAp= SETaddr_(setA, void);
+  elemBp= SETaddr_(setB, void);
+  if (!memcmp((char *)elemAp, (char *)elemBp, (size_t)(sizeA * SETelemsize)))
+    return 1;
+  return 0;
+} /* setequal */
+
+
+/*---------------------------------
+
+  qh_setequal_except( setA, skipelemA, setB, skipelemB )
+    returns 1 if sorted setA and setB are equal except for skipelemA & B
+
+  returns:
+    false if either skipelemA or skipelemB are missing
+
+  notes:
+    neither set may be NULL
+
+    if skipelemB is NULL,
+      can skip any one element of setB
+
+  design:
+    setup pointers
+    search for skipelemA, skipelemB, and mismatches
+    check results
+*/
+int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
+  void **elemA, **elemB;
+  int skip=0;
+
+  elemA= SETaddr_(setA, void);
+  elemB= SETaddr_(setB, void);
+  while (1) {
+    if (*elemA == skipelemA) {
+      skip++;
+      elemA++;
+    }
+    if (skipelemB) {
+      if (*elemB == skipelemB) {
+        skip++;
+        elemB++;
+      }
+    }else if (*elemA != *elemB) {
+      skip++;
+      if (!(skipelemB= *elemB++))
+        return 0;
+    }
+    if (!*elemA)
+      break;
+    if (*elemA++ != *elemB++)
+      return 0;
+  }
+  if (skip != 2 || *elemB)
+    return 0;
+  return 1;
+} /* setequal_except */
+
+
+/*---------------------------------
+
+  qh_setequal_skip( setA, skipA, setB, skipB )
+    returns 1 if sorted setA and setB are equal except for elements skipA & B
+
+  returns:
+    false if different size
+
+  notes:
+    neither set may be NULL
+
+  design:
+    setup pointers
+    search for mismatches while skipping skipA and skipB
+*/
+int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
+  void **elemA, **elemB, **skipAp, **skipBp;
+
+  elemA= SETaddr_(setA, void);
+  elemB= SETaddr_(setB, void);
+  skipAp= SETelemaddr_(setA, skipA, void);
+  skipBp= SETelemaddr_(setB, skipB, void);
+  while (1) {
+    if (elemA == skipAp)
+      elemA++;
+    if (elemB == skipBp)
+      elemB++;
+    if (!*elemA)
+      break;
+    if (*elemA++ != *elemB++)
+      return 0;
+  }
+  if (*elemB)
+    return 0;
+  return 1;
+} /* setequal_skip */
+
+
+/*---------------------------------
+
+  qh_setfree(qh, setp )
+    frees the space occupied by a sorted or unsorted set
+
+  returns:
+    sets setp to NULL
+
+  notes:
+    set may be NULL
+
+  design:
+    free array
+    free set
+*/
+void qh_setfree(qhT *qh, setT **setp) {
+  int size;
+  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
+
+  if (*setp) {
+    size= SETbasesize + ((*setp)->maxsize)*SETelemsize;
+    if (size <= qh->qhmem.LASTsize) {
+      qh_memfree_(qh, *setp, size, freelistp);
+    }else
+      qh_memfree(qh, *setp, size);
+    *setp= NULL;
+  }
+} /* setfree */
+
+
+/*---------------------------------
+
+  qh_setfree2(qh, setp, elemsize )
+    frees the space occupied by a set and its elements
+
+  notes:
+    set may be NULL
+
+  design:
+    free each element
+    free set
+*/
+void qh_setfree2(qhT *qh, setT **setp, int elemsize) {
+  void          *elem, **elemp;
+
+  FOREACHelem_(*setp)
+    qh_memfree(qh, elem, elemsize);
+  qh_setfree(qh, setp);
+} /* setfree2 */
+
+
+
+/*---------------------------------
+
+  qh_setfreelong(qh, setp )
+    frees a set only if it's in long memory
+
+  returns:
+    sets setp to NULL if it is freed
+
+  notes:
+    set may be NULL
+
+  design:
+    if set is large
+      free it
+*/
+void qh_setfreelong(qhT *qh, setT **setp) {
+  int size;
+
+  if (*setp) {
+    size= SETbasesize + ((*setp)->maxsize)*SETelemsize;
+    if (size > qh->qhmem.LASTsize) {
+      qh_memfree(qh, *setp, size);
+      *setp= NULL;
+    }
+  }
+} /* setfreelong */
+
+
+/*---------------------------------
+
+  qh_setin( set, setelem )
+    returns 1 if setelem is in a set, 0 otherwise
+
+  notes:
+    set may be NULL or unsorted
+
+  design:
+    scans set for setelem
+*/
+int qh_setin(setT *set, void *setelem) {
+  void *elem, **elemp;
+
+  FOREACHelem_(set) {
+    if (elem == setelem)
+      return 1;
+  }
+  return 0;
+} /* setin */
+
+
+/*---------------------------------
+
+  qh_setindex(set, atelem )
+    returns the index of atelem in set.
+    returns -1, if not in set or maxsize wrong
+
+  notes:
+    set may be NULL and may contain nulls.
+    NOerrors returned (qh_pointid, QhullPoint::id)
+
+  design:
+    checks maxsize
+    scans set for atelem
+*/
+int qh_setindex(setT *set, void *atelem) {
+  void **elem;
+  int size, i;
+
+  if (!set)
+    return -1;
+  SETreturnsize_(set, size);
+  if (size > set->maxsize)
+    return -1;
+  elem= SETaddr_(set, void);
+  for (i=0; i < size; i++) {
+    if (*elem++ == atelem)
+      return i;
+  }
+  return -1;
+} /* setindex */
+
+
+/*---------------------------------
+
+  qh_setlarger(qh, oldsetp )
+    returns a larger set that contains all elements of *oldsetp
+
+  notes:
+    if long memory,
+      the new set is 2x larger
+    if qhmem.LASTsize is between 1.5x and 2x
+      the new set is qhmem.LASTsize
+    otherwise use quick memory,
+      the new set is 2x larger, rounded up to next qh_memsize
+       
+    if temp set, updates qh->qhmem.tempstack
+
+  design:
+    creates a new set
+    copies the old set to the new set
+    updates pointers in tempstack
+    deletes the old set
+*/
+void qh_setlarger(qhT *qh, setT **oldsetp) {
+  int setsize= 1, newsize;
+  setT *newset, *set, **setp, *oldset;
+  setelemT *sizep;
+  setelemT *newp, *oldp;
+
+  if (*oldsetp) {
+    oldset= *oldsetp;
+    SETreturnsize_(oldset, setsize);
+    qh->qhmem.cntlarger++;
+    qh->qhmem.totlarger += setsize+1;
+    qh_setlarger_quick(qh, setsize, &newsize);
+    newset= qh_setnew(qh, newsize);
+    oldp= (setelemT *)SETaddr_(oldset, void);
+    newp= (setelemT *)SETaddr_(newset, void);
+    memcpy((char *)newp, (char *)oldp, (size_t)(setsize+1) * SETelemsize);
+    sizep= SETsizeaddr_(newset);
+    sizep->i= setsize+1;
+    FOREACHset_((setT *)qh->qhmem.tempstack) {
+      if (set == oldset)
+        *(setp-1)= newset;
+    }
+    qh_setfree(qh, oldsetp);
+  }else
+    newset= qh_setnew(qh, 3);
+  *oldsetp= newset;
+} /* setlarger */
+
+
+/*---------------------------------
+
+  qh_setlarger_quick(qh, setsize, newsize )
+    determine newsize for setsize
+    returns True if newsize fits in quick memory
+
+  design:
+    if 2x fits into quick memory
+      return True, 2x
+    if x+4 does not fit into quick memory
+      return False, 2x
+    if x+x/3 fits into quick memory
+      return True, the last quick set
+    otherwise
+      return False, 2x
+*/
+int qh_setlarger_quick(qhT *qh, int setsize, int *newsize) {
+    int lastquickset;
+
+    *newsize= 2 * setsize;
+    lastquickset= (qh->qhmem.LASTsize - SETbasesize) / SETelemsize; /* matches size computation in qh_setnew */
+    if (*newsize <= lastquickset)
+      return 1;
+    if (setsize + 4 > lastquickset)
+      return 0;
+    if (setsize + setsize/3 <= lastquickset) {
+      *newsize= lastquickset;
+      return 1;
+    }
+    return 0;
+} /* setlarger_quick */
+
+/*---------------------------------
+
+  qh_setlast( set )
+    return last element of set or NULL (use type conversion)
+
+  notes:
+    set may be NULL
+
+  design:
+    return last element
+*/
+void *qh_setlast(setT *set) {
+  int size;
+
+  if (set) {
+    size= SETsizeaddr_(set)->i;
+    if (!size)
+      return SETelem_(set, set->maxsize - 1);
+    else if (size > 1)
+      return SETelem_(set, size - 2);
+  }
+  return NULL;
+} /* setlast */
+
+
+/*---------------------------------
+
+  qh_setnew(qh, setsize )
+    creates and allocates space for a set
+
+  notes:
+    setsize means the number of elements (!including the NULL terminator)
+    use qh_settemp/qh_setfreetemp if set is temporary
+
+  design:
+    allocate memory for set
+    roundup memory if small set
+    initialize as empty set
+*/
+setT *qh_setnew(qhT *qh, int setsize) {
+  setT *set;
+  int sizereceived; /* used if !qh_NOmem */
+  int size;
+  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
+
+  if (!setsize)
+    setsize++;
+  size= SETbasesize + setsize * SETelemsize; /* setT includes NULL terminator, see qh.LASTquickset */
+  if (size>0 && size <= qh->qhmem.LASTsize) {
+    qh_memalloc_(qh, size, freelistp, set, setT);
+#ifndef qh_NOmem
+    sizereceived= qh->qhmem.sizetable[ qh->qhmem.indextable[size]];
+    if (sizereceived > size)
+      setsize += (sizereceived - size)/SETelemsize;
+#endif
+  }else
+    set= (setT *)qh_memalloc(qh, size);
+  set->maxsize= setsize;
+  set->e[setsize].i= 1;
+  set->e[0].p= NULL;
+  return(set);
+} /* setnew */
+
+
+/*---------------------------------
+
+  qh_setnew_delnthsorted(qh, set, size, nth, prepend )
+    creates a sorted set not containing nth element
+    if prepend, the first prepend elements are undefined
+
+  notes:
+    set must be defined
+    checks nth
+    see also: setdelnthsorted
+
+  design:
+    create new set
+    setup pointers and allocate room for prepend'ed entries
+    append head of old set to new set
+    append tail of old set to new set
+*/
+setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend) {
+  setT *newset;
+  void **oldp, **newp;
+  int tailsize= size - nth -1, newsize;
+
+  if (tailsize < 0) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  newsize= size-1 + prepend;
+  newset= qh_setnew(qh, newsize);
+  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
+  oldp= SETaddr_(set, void);
+  newp= SETaddr_(newset, void) + prepend;
+  switch (nth) {
+  case 0:
+    break;
+  case 1:
+    *(newp++)= *oldp++;
+    break;
+  case 2:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  case 3:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  case 4:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  default:
+    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
+    newp += nth;
+    oldp += nth;
+    break;
+  }
+  oldp++;
+  switch (tailsize) {
+  case 0:
+    break;
+  case 1:
+    *(newp++)= *oldp++;
+    break;
+  case 2:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  case 3:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  case 4:
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    *(newp++)= *oldp++;
+    break;
+  default:
+    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
+    newp += tailsize;
+  }
+  *newp= NULL;
+  return(newset);
+} /* setnew_delnthsorted */
+
+
+/*---------------------------------
+
+  qh_setprint(qh, fp, string, set )
+    print set elements to fp with identifying string
+
+  notes:
+    never errors
+*/
+void qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set) {
+  int size, k;
+
+  if (!set)
+    qh_fprintf(qh, fp, 9346, "%s set is null\n", string);
+  else {
+    SETreturnsize_(set, size);
+    qh_fprintf(qh, fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
+             string, (void *) set, set->maxsize, size);
+    if (size > set->maxsize)
+      size= set->maxsize+1;
+    for (k=0; k < size; k++)
+      qh_fprintf(qh, fp, 9348, " %p", (void *) set->e[k].p);
+    qh_fprintf(qh, fp, 9349, "\n");
+  }
+} /* setprint */
+
+/*---------------------------------
+
+  qh_setreplace(qh, set, oldelem, newelem )
+    replaces oldelem in set with newelem
+
+  notes:
+    errors if oldelem not in the set
+    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
+
+  design:
+    find oldelem
+    replace with newelem
+*/
+void qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem) {
+  void **elemp;
+
+  elemp= SETaddr_(set, void);
+  while (*elemp != oldelem && *elemp)
+    elemp++;
+  if (*elemp)
+    *elemp= newelem;
+  else {
+    qh_fprintf(qh, qh->qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
+       oldelem);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+} /* setreplace */
+
+
+/*---------------------------------
+
+  qh_setsize(qh, set )
+    returns the size of a set
+
+  notes:
+    errors if set's maxsize is incorrect
+    same as SETreturnsize_(set)
+    same code for qh_setsize [qset_r.c] and QhullSetBase::count
+    if first element is NULL, SETempty_() is True but qh_setsize may be greater than 0
+
+  design:
+    determine actual size of set from maxsize
+*/
+int qh_setsize(qhT *qh, setT *set) {
+  int size;
+  setelemT *sizep;
+
+  if (!set)
+    return(0);
+  sizep= SETsizeaddr_(set);
+  if ((size= sizep->i)) {
+    size--;
+    if (size > set->maxsize) {
+      qh_fprintf(qh, qh->qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
+               size, set->maxsize);
+      qh_setprint(qh, qh->qhmem.ferr, "set: ", set);
+      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+    }
+  }else
+    size= set->maxsize;
+  return size;
+} /* setsize */
+
+/*---------------------------------
+
+  qh_settemp(qh, setsize )
+    return a stacked, temporary set of up to setsize elements
+
+  notes:
+    use settempfree or settempfree_all to release from qh->qhmem.tempstack
+    see also qh_setnew
+
+  design:
+    allocate set
+    append to qh->qhmem.tempstack
+
+*/
+setT *qh_settemp(qhT *qh, int setsize) {
+  setT *newset;
+
+  newset= qh_setnew(qh, setsize);
+  qh_setappend(qh, &qh->qhmem.tempstack, newset);
+  if (qh->qhmem.IStracing >= 5)
+    qh_fprintf(qh, qh->qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
+       (void *) newset, newset->maxsize, qh_setsize(qh, qh->qhmem.tempstack));
+  return newset;
+} /* settemp */
+
+/*---------------------------------
+
+  qh_settempfree(qh, set )
+    free temporary set at top of qh->qhmem.tempstack
+
+  notes:
+    nop if set is NULL
+    errors if set not from previous   qh_settemp
+
+  to locate errors:
+    use 'T2' to find source and then find mis-matching qh_settemp
+
+  design:
+    check top of qh->qhmem.tempstack
+    free it
+*/
+void qh_settempfree(qhT *qh, setT **set) {
+  setT *stackedset;
+
+  if (!*set)
+    return;
+  stackedset= qh_settemppop(qh);
+  if (stackedset != *set) {
+    qh_settemppush(qh, stackedset);
+    qh_fprintf(qh, qh->qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
+             (void *) *set, qh_setsize(qh, *set), qh_setsize(qh, qh->qhmem.tempstack)+1,
+             (void *) stackedset, qh_setsize(qh, stackedset));
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  qh_setfree(qh, set);
+} /* settempfree */
+
+/*---------------------------------
+
+  qh_settempfree_all(qh)
+    free all temporary sets in qh->qhmem.tempstack
+
+  design:
+    for each set in tempstack
+      free set
+    free qh->qhmem.tempstack
+*/
+void qh_settempfree_all(qhT *qh) {
+  setT *set, **setp;
+
+  FOREACHset_(qh->qhmem.tempstack)
+    qh_setfree(qh, &set);
+  qh_setfree(qh, &qh->qhmem.tempstack);
+} /* settempfree_all */
+
+/*---------------------------------
+
+  qh_settemppop(qh)
+    pop and return temporary set from qh->qhmem.tempstack
+
+  notes:
+    the returned set is permanent
+
+  design:
+    pop and check top of qh->qhmem.tempstack
+*/
+setT *qh_settemppop(qhT *qh) {
+  setT *stackedset;
+
+  stackedset= (setT *)qh_setdellast(qh->qhmem.tempstack);
+  if (!stackedset) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  if (qh->qhmem.IStracing >= 5)
+    qh_fprintf(qh, qh->qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
+       qh_setsize(qh, qh->qhmem.tempstack)+1, (void *) stackedset, qh_setsize(qh, stackedset));
+  return stackedset;
+} /* settemppop */
+
+/*---------------------------------
+
+  qh_settemppush(qh, set )
+    push temporary set unto qh->qhmem.tempstack (makes it temporary)
+
+  notes:
+    duplicates settemp() for tracing
+
+  design:
+    append set to tempstack
+*/
+void qh_settemppush(qhT *qh, setT *set) {
+  if (!set) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  qh_setappend(qh, &qh->qhmem.tempstack, set);
+  if (qh->qhmem.IStracing >= 5)
+    qh_fprintf(qh, qh->qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
+      qh_setsize(qh, qh->qhmem.tempstack), (void *) set, qh_setsize(qh, set));
+} /* settemppush */
+
+
+/*---------------------------------
+
+  qh_settruncate(qh, set, size )
+    truncate set to size elements
+
+  notes:
+    set must be defined
+
+  see:
+    SETtruncate_
+
+  design:
+    check size
+    update actual size of set
+*/
+void qh_settruncate(qhT *qh, setT *set, int size) {
+
+  if (size < 0 || size > set->maxsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
+  set->e[size].p= NULL;
+} /* settruncate */
+
+/*---------------------------------
+
+  qh_setunique(qh, set, elem )
+    add elem to unsorted set unless it is already in set
+
+  notes:
+    returns 1 if it is appended
+
+  design:
+    if elem not in set
+      append elem to set
+*/
+int qh_setunique(qhT *qh, setT **set, void *elem) {
+
+  if (!qh_setin(*set, elem)) {
+    qh_setappend(qh, set, elem);
+    return 1;
+  }
+  return 0;
+} /* setunique */
+
+/*---------------------------------
+
+  qh_setzero(qh, set, index, size )
+    zero elements from index on
+    set actual size of set to size
+
+  notes:
+    set must be defined
+    the set becomes an indexed set (can not use FOREACH...)
+
+  see also:
+    qh_settruncate
+
+  design:
+    check index and size
+    update actual size
+    zero elements starting at e[index]
+*/
+void qh_setzero(qhT *qh, setT *set, int idx, int size) {
+  int count;
+
+  if (idx < 0 || idx >= size || size > set->maxsize) {
+    qh_fprintf(qh, qh->qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
+    qh_setprint(qh, qh->qhmem.ferr, "", set);
+    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
+  }
+  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
+  count= size - idx + 1;   /* +1 for NULL terminator */
+  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
+} /* setzero */
+
+
diff --git a/vendor/qhull/libqhull_r/qset_r.h b/vendor/qhull/libqhull_r/qset_r.h
new file mode 100644
index 0000000..bc0298e
--- /dev/null
+++ b/vendor/qhull/libqhull_r/qset_r.h
@@ -0,0 +1,515 @@
+/*
  ---------------------------------
+
+   qset_r.h
+     header file for qset_r.c that implements set
+
+   see qh-set_r.htm and qset_r.c
+
+   only uses mem_r.c, malloc/free
+
+   for error handling, writes message and calls
+      qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL);
+
+   set operations satisfy the following properties:
+    - sets have a max size, the actual size (if different) is stored at the end
+    - every set is NULL terminated
+    - sets may be sorted or unsorted, the caller must distinguish this
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFset
+#define qhDEFset 1
+
+#include 
+
+/*================= -structures- ===============*/
+
+#ifndef DEFsetT
+#define DEFsetT 1
+typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
+#endif
+
+#ifndef DEFqhT
+#define DEFqhT 1
+typedef struct qhT qhT;          /* defined in libqhull_r.h */
+#endif
+
+/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
+
+/*------------------------------------------
+
+setT
+  a set or list of pointers with maximum size and actual size.
+
+variations:
+  unsorted, unique   -- a list of unique pointers with NULL terminator
+                           user guarantees uniqueness
+  sorted             -- a sorted list of unique pointers with NULL terminator
+                           qset_r.c guarantees uniqueness
+  unsorted           -- a list of pointers terminated with NULL
+  indexed            -- an array of pointers with NULL elements
+
+structure for set of n elements:
+
+        --------------
+        |  maxsize
+        --------------
+        |  e[0] - a pointer, may be NULL for indexed sets
+        --------------
+        |  e[1]
+
+        --------------
+        |  ...
+        --------------
+        |  e[n-1]
+        --------------
+        |  e[n] = NULL
+        --------------
+        |  ...
+        --------------
+        |  e[maxsize] - n+1 or NULL (determines actual size of set)
+        --------------
+
+*/
+
+/*-- setelemT -- internal type to allow both pointers and indices
+*/
+typedef union setelemT setelemT;
+union setelemT {
+  void    *p;
+  int   i;         /* integer used for e[maxSize] */
+};
+
+struct setT {
+  int maxsize;          /* maximum number of elements (except NULL) */
+  setelemT e[];        /* array of pointers, tail is NULL */
+                        /* last slot (unless NULL) is actual size+1
+                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
+                        /* this may generate a warning since e[] contains
+                           maxsize elements */
+};
+
+/*=========== -constants- =========================*/
+
+/*-------------------------------------
+
+  SETelemsize
+    size of a set element in bytes
+*/
+#define SETelemsize ((int)sizeof(setelemT))
+
+/*
+   SETbasesize - size of setT with a single element
+   in e[]. Before C99, setT has been declared with e[1], so
+   sizeof(setT) included already space for one element, but with e[]
+   (C99), it does not. All instances of (int)sizeof(setT) have been
+   replaced with SETbasesize throughout the code -- Tomas Kalibera,
+   2025-01-09 */
+#define SETbasesize ((int)sizeof(setT) + SETelemsize)
+
+
+/*=========== -macros- =========================*/
+
+/*-------------------------------------
+
+   FOREACHsetelement_(type, set, variable)
+     define FOREACH iterator
+
+   declare:
+     assumes *variable and **variablep are declared
+     no space in "variable)" [DEC Alpha cc compiler]
+
+   each iteration:
+     variable is set element
+     variablep is one beyond variable.
+
+   to repeat an element:
+     variablep--; / *repeat* /
+
+   at exit:
+     variable is NULL at end of loop
+
+   example:
+     #define FOREACHfacet_(facets) FOREACHsetelement_(facetT, facets, facet)
+
+   notes:
+     use FOREACHsetelement_i_() if need index or include NULLs
+     assumes set is not modified
+
+   WARNING:
+     nested loops can't use the same variable (define another FOREACH)
+
+     needs braces if nested inside another FOREACH
+     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
+*/
+#define FOREACHsetelement_(type, set, variable) \
+        if (((variable= NULL), set)) for (\
+          variable##p= (type **)&((set)->e[0].p); \
+          (variable= *variable##p++);)
+
+/*------------------------------------------
+
+   FOREACHsetelement_i_(qh, type, set, variable)
+     define indexed FOREACH iterator
+
+   declare:
+     type *variable, variable_n, variable_i;
+
+   each iteration:
+     variable is set element, may be NULL
+     variable_i is index, variable_n is qh_setsize()
+
+   to repeat an element:
+     variable_i--; variable_n-- repeats for deleted element
+
+   at exit:
+     variable==NULL and variable_i==variable_n
+
+   example:
+     #define FOREACHfacet_i_(qh, facets) FOREACHsetelement_i_(qh, facetT, facets, facet)
+
+   WARNING:
+     nested loops can't use the same variable (define another FOREACH)
+
+     needs braces if nested inside another FOREACH
+     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
+*/
+#define FOREACHsetelement_i_(qh, type, set, variable) \
+        if (((variable= NULL), set)) for (\
+          variable##_i= 0, variable= (type *)((set)->e[0].p), \
+                   variable##_n= qh_setsize(qh, set);\
+          variable##_i < variable##_n;\
+          variable= (type *)((set)->e[++variable##_i].p) )
+
+/*----------------------------------------
+
+   FOREACHsetelementreverse_(qh, type, set, variable)-
+     define FOREACH iterator in reverse order
+
+   declare:
+     assumes *variable and **variablep are declared
+     also declare 'int variabletemp'
+
+   each iteration:
+     variable is set element
+
+   to repeat an element:
+     variabletemp++; / *repeat* /
+
+   at exit:
+     variable is NULL
+
+   example:
+     #define FOREACHvertexreverse_(vertices) FOREACHsetelementreverse_(vertexT, vertices, vertex)
+
+   notes:
+     use FOREACHsetelementreverse12_() to reverse first two elements
+     WARNING: needs braces if nested inside another FOREACH
+*/
+#define FOREACHsetelementreverse_(qh, type, set, variable) \
+        if (((variable= NULL), set)) for (\
+           variable##temp= qh_setsize(qh, set)-1, variable= qh_setlast(qh, set);\
+           variable; variable= \
+           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
+
+/*-------------------------------------
+
+   FOREACHsetelementreverse12_(type, set, variable)-
+     define FOREACH iterator with e[1] and e[0] reversed
+
+   declare:
+     assumes *variable and **variablep are declared
+
+   each iteration:
+     variable is set element
+     variablep is one after variable.
+
+   to repeat an element:
+     variablep--; / *repeat* /
+
+   at exit:
+     variable is NULL at end of loop
+
+   example
+     #define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
+
+   notes:
+     WARNING: needs braces if nested inside another FOREACH
+*/
+#define FOREACHsetelementreverse12_(type, set, variable) \
+        if (((variable= NULL), set)) for (\
+          variable##p= (type **)&((set)->e[1].p); \
+          (variable= *variable##p); \
+          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
+              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
+
+/*-------------------------------------
+
+   FOREACHelem_( set )-
+     iterate elements in a set
+
+   declare:
+     void *elem, *elemp;
+
+   each iteration:
+     elem is set element
+     elemp is one beyond
+
+   to repeat an element:
+     elemp--; / *repeat* /
+
+   at exit:
+     elem == NULL at end of loop
+
+   example:
+     FOREACHelem_(set) {
+
+   notes:
+     assumes set is not modified
+     WARNING: needs braces if nested inside another FOREACH
+*/
+#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
+
+/*-------------------------------------
+
+   FOREACHset_( set )-
+     iterate a set of sets
+
+   declare:
+     setT *set, **setp;
+
+   each iteration:
+     set is set element
+     setp is one beyond
+
+   to repeat an element:
+     setp--; / *repeat* /
+
+   at exit:
+     set == NULL at end of loop
+
+   example
+     FOREACHset_(sets) {
+
+   notes:
+     WARNING: needs braces if nested inside another FOREACH
+*/
+#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
+
+/*-------------------------------------------
+
+   SETindex_( set, elem )
+     return index of elem in set
+
+   notes:
+     for use with FOREACH iteration
+     WARN64 -- Maximum set size is 2G
+
+   example:
+     i= SETindex_(ridges, ridge)
+*/
+#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
+
+/*-----------------------------------------
+
+   SETref_( elem )
+     l.h.s. for modifying the current element in a FOREACH iteration
+
+   example:
+     SETref_(ridge)= anotherridge;
+*/
+#define SETref_(elem) (elem##p[-1])
+
+/*-----------------------------------------
+
+   SETelem_(set, n)
+     return the n'th element of set
+
+   notes:
+      assumes that n is valid [0..size] and that set is defined
+      use SETelemt_() for type cast
+*/
+#define SETelem_(set, n)           ((set)->e[n].p)
+
+/*-----------------------------------------
+
+   SETelemt_(set, n, type)
+     return the n'th element of set as a type
+
+   notes:
+      assumes that n is valid [0..size] and that set is defined
+*/
+#define SETelemt_(set, n, type)    ((type *)((set)->e[n].p))
+
+/*-----------------------------------------
+
+   SETelemaddr_(set, n, type)
+     return address of the n'th element of a set
+
+   notes:
+      assumes that n is valid [0..size] and set is defined
+*/
+#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
+
+/*-----------------------------------------
+
+   SETfirst_(set)
+     return first element of set
+
+*/
+#define SETfirst_(set)             ((set)->e[0].p)
+
+/*-----------------------------------------
+
+   SETfirstt_(set, type)
+     return first element of set as a type
+
+*/
+#define SETfirstt_(set, type)      ((type *)((set)->e[0].p))
+
+/*-----------------------------------------
+
+   SETsecond_(set)
+     return second element of set
+
+*/
+#define SETsecond_(set)            ((set)->e[1].p)
+
+/*-----------------------------------------
+
+   SETsecondt_(set, type)
+     return second element of set as a type
+*/
+#define SETsecondt_(set, type)     ((type *)((set)->e[1].p))
+
+/*-----------------------------------------
+
+   SETaddr_(set, type)
+       return address of set's elements
+*/
+#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
+
+/*-----------------------------------------
+
+   SETreturnsize_(set, size)
+     return size of a set
+
+   notes:
+      set must be defined
+      use qh_setsize(qhT *qh, set) unless speed is critical
+*/
+#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
+
+/*-----------------------------------------
+
+   SETempty_(set)
+     return true(1) if set is empty (i.e., FOREACHsetelement_ is empty)
+
+   notes:
+      set may be NULL
+      qh_setsize may be non-zero if first element is NULL
+*/
+#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
+
+/*---------------------------------
+
+  SETsizeaddr_(set)
+    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
+    Its type is setelemT* for strict aliasing
+    All SETelemaddr_ must be cast to setelemT
+
+
+  notes:
+    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
+*/
+#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
+
+/*-----------------------------------------
+
+   SETtruncate_(set, size)
+     truncate set to size
+
+   see:
+     qh_settruncate()
+
+*/
+#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
+      set->e[size].p= NULL;}
+
+/*======= prototypes in alphabetical order ============*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void  qh_setaddsorted(qhT *qh, setT **setp, void *elem);
+void  qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem);
+void  qh_setappend(qhT *qh, setT **setp, void *elem);
+void  qh_setappend_set(qhT *qh, setT **setp, setT *setA);
+void  qh_setappend2ndlast(qhT *qh, setT **setp, void *elem);
+void  qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned int id);
+void  qh_setcompact(qhT *qh, setT *set);
+setT *qh_setcopy(qhT *qh, setT *set, int extra);
+void *qh_setdel(setT *set, void *elem);
+void *qh_setdellast(setT *set);
+void *qh_setdelnth(qhT *qh, setT *set, int nth);
+void *qh_setdelnthsorted(qhT *qh, setT *set, int nth);
+void *qh_setdelsorted(setT *set, void *newelem);
+setT *qh_setduplicate(qhT *qh, setT *set, int elemsize);
+void **qh_setendpointer(setT *set);
+int   qh_setequal(setT *setA, setT *setB);
+int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
+int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
+void  qh_setfree(qhT *qh, setT **set);
+void  qh_setfree2(qhT *qh, setT **setp, int elemsize);
+void  qh_setfreelong(qhT *qh, setT **set);
+int   qh_setin(setT *set, void *setelem);
+int   qh_setindex(setT *set, void *setelem);
+void  qh_setlarger(qhT *qh, setT **setp);
+int   qh_setlarger_quick(qhT *qh, int setsize, int *newsize);
+void *qh_setlast(setT *set);
+setT *qh_setnew(qhT *qh, int size);
+setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend);
+void  qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set);
+void  qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem);
+int   qh_setsize(qhT *qh, setT *set);
+setT *qh_settemp(qhT *qh, int setsize);
+void  qh_settempfree(qhT *qh, setT **set);
+void  qh_settempfree_all(qhT *qh);
+setT *qh_settemppop(qhT *qh);
+void  qh_settemppush(qhT *qh, setT *set);
+void  qh_settruncate(qhT *qh, setT *set, int size);
+int   qh_setunique(qhT *qh, setT **set, void *elem);
+void  qh_setzero(qhT *qh, setT *set, int idx, int size);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFset */
diff --git a/vendor/qhull/libqhull_r/random_r.c b/vendor/qhull/libqhull_r/random_r.c
new file mode 100644
index 0000000..7eecd30
--- /dev/null
+++ b/vendor/qhull/libqhull_r/random_r.c
@@ -0,0 +1,249 @@
+/*
  ---------------------------------
+
+   random_r.c and utilities
+     Park & Miller's minimimal standard random number generator
+     argc/argv conversion
+
+     Used by rbox.  Do not use 'qh' 
+*/
+
+#include "libqhull_r.h"
+#include "random_r.h"
+
+#include 
+#include 
+#include 
+
+#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
+#pragma warning( disable : 4706)  /* assignment within conditional function */
+#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
+#endif
+
+/*---------------------------------
+
+  qh_argv_to_command( argc, argv, command, max_size )
+
+    build command from argc/argv
+    max_size is at least
+
+  returns:
+    a space-delimited string of options (just as typed)
+    returns false if max_size is too short
+
+  notes:
+    silently removes
+    makes option string easy to input and output
+    matches qh_argv_to_command_size
+    argc may be 0
+*/
+int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
+  int i, remaining;
+  char *s;
+  *command= '\0';  /* max_size > 0 */
+
+  if (argc) {
+    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
+    || (s= strrchr( argv[0], '/')))
+        s++;
+    else
+        s= argv[0];
+    if ((int)strlen(s) < max_size)   /* WARN64 */
+        strcpy(command, s);
+    else
+        goto error_argv;
+    if ((s= strstr(command, ".EXE"))
+    ||  (s= strstr(command, ".exe")))
+        *s= '\0';
+  }
+  for (i=1; i < argc; i++) {
+    s= argv[i];
+    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
+    if (!*s || strchr(s, ' ')) {
+      char *t= command + strlen(command);
+      remaining -= 2;
+      if (remaining < 0) {
+        goto error_argv;
+      }
+      *t++= ' ';
+      *t++= '"';
+      while (*s) {
+        if (*s == '"') {
+          if (--remaining < 0)
+            goto error_argv;
+          *t++= '\\';
+        }
+        *t++= *s++;
+      }
+      *t++= '"';
+      *t= '\0';
+    }else if (remaining < 0) {
+      goto error_argv;
+    }else {
+      strcat(command, " ");
+      strcat(command, s);
+    }
+  }
+  return 1;
+
+error_argv:
+  return 0;
+} /* argv_to_command */
+
+/*---------------------------------
+
+  qh_argv_to_command_size( argc, argv )
+
+    return size to allocate for qh_argv_to_command()
+
+  notes:
+    only called from rbox with qh_errexit not enabled
+    caller should report error if returned size is less than 1
+    argc may be 0
+    actual size is usually shorter
+*/
+int qh_argv_to_command_size(int argc, char *argv[]) {
+    int count= 1; /* null-terminator if argc==0 */
+    int i;
+    char *s;
+
+    for (i=0; i0 && strchr(argv[i], ' ')) {
+        count += 2;  /* quote delimiters */
+        for (s=argv[i]; *s; s++) {
+          if (*s == '"') {
+            count++;
+          }
+        }
+      }
+    }
+    return count;
+} /* argv_to_command_size */
+
+/*---------------------------------
+
+  qh_rand()
+  qh_srand(qh, seed )
+    generate pseudo-random number between 1 and 2^31 -2
+
+  notes:
+    For qhull and rbox, called from qh_RANDOMint(),etc. [user_r.h]
+
+    From Park & Miller's minimal standard random number generator
+      Communications of the ACM, 31:1192-1201, 1988.
+    Does not use 0 or 2^31 -1
+      this is silently enforced by qh_srand()
+    Can make 'Rn' much faster by moving qh_rand to qh_distplane
+*/
+
+/* Global variables and constants */
+
+#define qh_rand_a 16807
+#define qh_rand_m 2147483647
+#define qh_rand_q 127773  /* m div a */
+#define qh_rand_r 2836    /* m mod a */
+
+int qh_rand(qhT *qh) {
+    int lo, hi, test;
+    int seed= qh->last_random;
+
+    hi= seed / qh_rand_q;  /* seed div q */
+    lo= seed % qh_rand_q;  /* seed mod q */
+    test= qh_rand_a * lo - qh_rand_r * hi;
+    if (test > 0)
+        seed= test;
+    else
+        seed= test + qh_rand_m;
+    qh->last_random= seed;
+    /* seed= seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
+    /* seed= qh_RANDOMmax;  for testing */
+    return seed;
+} /* rand */
+
+void qh_srand(qhT *qh, int seed) {
+    if (seed < 1)
+        qh->last_random= 1;
+    else if (seed >= qh_rand_m)
+        qh->last_random= qh_rand_m - 1;
+    else
+        qh->last_random= seed;
+} /* qh_srand */
+
+/*---------------------------------
+
+qh_randomfactor(qh, scale, offset )
+  return a random factor r * scale + offset
+
+notes:
+  qh.RANDOMa/b are defined in global_r.c
+  qh_RANDOMint requires 'qh'
+*/
+realT qh_randomfactor(qhT *qh, realT scale, realT offset) {
+    realT randr;
+
+    randr= qh_RANDOMint;
+    return randr * scale + offset;
+} /* randomfactor */
+
+/*---------------------------------
+
+  qh_randommatrix(qh, buffer, dim, rows )
+    generate a random dim X dim matrix in range [-1,1]
+    assumes buffer is [dim+1, dim]
+
+  returns:
+    sets buffer to random numbers
+    sets rows to rows of buffer
+    sets row[dim] as scratch row
+
+  notes:
+    qh_RANDOMint requires 'qh'
+*/
+void qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **rows) {
+    int i, k;
+    realT **rowi, *coord, realr;
+
+    coord= buffer;
+    rowi= rows;
+    for (i=0; i < dim; i++) {
+        *(rowi++)= coord;
+        for (k=0; k < dim; k++) {
+            realr= qh_RANDOMint;
+            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
+        }
+    }
+    *rowi= coord;
+} /* randommatrix */
+
+/*---------------------------------
+
+  qh_strtol( s, endp) qh_strtod( s, endp)
+    internal versions of strtol() and strtod()
+    does not skip trailing spaces
+  notes:
+    some implementations of strtol()/strtod() skip trailing spaces
+*/
+double qh_strtod(const char *s, char **endp) {
+  double result;
+
+  result= strtod(s, endp);
+  if (s < (*endp) && (*endp)[-1] == ' ')
+    (*endp)--;
+  return result;
+} /* strtod */
+
+int qh_strtol(const char *s, char **endp) {
+  int result;
+
+  result= (int) strtol(s, endp, 10);     /* WARN64 */
+  if (s< (*endp) && (*endp)[-1] == ' ')
+    (*endp)--;
+  return result;
+} /* strtol */
diff --git a/vendor/qhull/libqhull_r/random_r.h b/vendor/qhull/libqhull_r/random_r.h
new file mode 100644
index 0000000..a17549d
--- /dev/null
+++ b/vendor/qhull/libqhull_r/random_r.h
@@ -0,0 +1,41 @@
+/*
  ---------------------------------
+
+  random_r.h
+    header file for random and utility routines
+
+   see qh-geom_r.htm and random_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/random_r.h#3 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+*/
+
+#ifndef qhDEFrandom
+#define qhDEFrandom 1
+
+#include "libqhull_r.h"
+
+/*============= prototypes in alphabetical order ======= */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
+int     qh_argv_to_command_size(int argc, char *argv[]);
+int     qh_rand(qhT *qh);
+void    qh_srand(qhT *qh, int seed);
+realT   qh_randomfactor(qhT *qh, realT scale, realT offset);
+void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
+int     qh_strtol(const char *s, char **endp);
+double  qh_strtod(const char *s, char **endp);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* qhDEFrandom */
+
+
+
diff --git a/vendor/qhull/libqhull_r/rboxlib_r.c b/vendor/qhull/libqhull_r/rboxlib_r.c
new file mode 100644
index 0000000..67e3998
--- /dev/null
+++ b/vendor/qhull/libqhull_r/rboxlib_r.c
@@ -0,0 +1,854 @@
+/*
  ---------------------------------
+
+   rboxlib_r.c
+     Generate input points
+
+   notes:
+     For documentation, see prompt[] of rbox_r.c
+     50 points generated for 'rbox D4'
+
+   WARNING:
+     incorrect range if qh_RANDOMmax is defined wrong (user_r.h)
+*/
+
+#include "libqhull_r.h"  /* First for user_r.h */
+#include "random_r.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef _MSC_VER  /* Microsoft Visual C++ */
+#pragma warning( disable : 4706)  /* assignment within conditional expression. */
+#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
+#endif
+
+#define MAXdim 200
+#define PI 3.1415926535897932384
+
+/* ------------------------------ prototypes ----------------*/
+int qh_roundi(qhT *qh, double a);
+void qh_out1(qhT *qh, double a);
+void qh_out2n(qhT *qh, double a, double b);
+void qh_out3n(qhT *qh, double a, double b, double c);
+void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim);
+void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim);
+void qh_rboxpoints2(qhT *qh, char* rbox_command, double **simplex);
+
+void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
+void    qh_free(void *mem);
+void   *qh_malloc(size_t size);
+int     qh_rand(qhT *qh);
+void    qh_srand(qhT *qh, int seed);
+
+/*---------------------------------
+
+  qh_rboxpoints(qh, rbox_command )
+    Generate points to qh.fout according to rbox options
+    Report errors on qh.ferr
+
+  returns:
+    0 (qh_ERRnone) on success
+    1 (qh_ERRinput) on input error
+    4 (qh_ERRmem) on memory error
+    5 (qh_ERRqhull) on internal error
+
+  notes:
+    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user_r.c)
+    Split out qh_rboxpoints2() to avoid -Wclobbered
+
+  design:
+    Straight line code (consider defining a struct and functions):
+
+    Parse arguments into variables
+    Determine the number of points
+    Generate the points
+*/
+int qh_rboxpoints(qhT *qh, char* rbox_command) {
+  int exitcode;
+  double *simplex;
+
+  simplex= NULL;
+  exitcode= setjmp(qh->rbox_errexit);
+  if (exitcode) {
+    /* same code for error exit and normal return.  qh.NOerrexit is set */
+    if (simplex)
+      qh_free(simplex);
+    return exitcode;
+  }
+  qh_rboxpoints2(qh, rbox_command, &simplex);
+  /* same code for error exit and normal return */
+  if (simplex)
+    qh_free(simplex);
+  return qh_ERRnone;
+} /* rboxpoints */
+
+void qh_rboxpoints2(qhT *qh, char* rbox_command, double **simplex) {
+  int i,j,k;
+  int gendim;
+  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
+  int cubesize, diamondsize, seed=0, count, apex;
+  int dim=3, numpoints=0, totpoints, addpoints=0;
+  int issphere=0, isaxis=0,  iscdd=0, islens=0, isregular=0, iswidth=0, addcube=0;
+  int isgap=0, isspiral=0, NOcommand=0, adddiamond=0;
+  int israndom=0, istime=0;
+  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
+  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
+  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
+  double *coordp, *simplexp;
+  int nthroot, mult[MAXdim];
+  double norm, factor, randr, rangap, tempr, lensangle=0, lensbase=1;
+  double anglediff, angle, x, y, cube=0.0, diamond=0.0;
+  double box= qh_DEFAULTbox; /* scale all numbers before output */
+  double randmax= qh_RANDOMmax;
+  char command[250], seedbuf[50];
+  char *s=command, *t, *first_point=NULL;
+  time_t timedata;
+
+  *command= '\0';
+  strncat(command, rbox_command, sizeof(command)-sizeof(seedbuf)-strlen(command)-1);
+
+  while (*s && !isspace(*s))  /* skip program name */
+    s++;
+  while (*s) {
+    while (*s && isspace(*s))
+      s++;
+    if (*s == '-')
+      s++;
+    if (!*s)
+      break;
+    if (isdigit(*s)) {
+      numpoints= qh_strtol(s, &s);
+      continue;
+    }
+    /* ============= read flags =============== */
+    switch (*s++) {
+    case 'c':
+      addcube= 1;
+      t= s;
+      while (isspace(*t))
+        t++;
+      if (*t == 'G')
+        cube= qh_strtod(++t, &s);
+      break;
+    case 'd':
+      adddiamond= 1;
+      t= s;
+      while (isspace(*t))
+        t++;
+      if (*t == 'G')
+        diamond= qh_strtod(++t, &s);
+      break;
+    case 'h':
+      iscdd= 1;
+      break;
+    case 'l':
+      isspiral= 1;
+      break;
+    case 'n':
+      NOcommand= 1;
+      break;
+    case 'r':
+      isregular= 1;
+      break;
+    case 's':
+      issphere= 1;
+      break;
+    case 't':
+      istime= 1;
+      if (isdigit(*s)) {
+        seed= qh_strtol(s, &s);
+        israndom= 0;
+      }else
+        israndom= 1;
+      break;
+    case 'x':
+      issimplex= 1;
+      break;
+    case 'y':
+      issimplex2= 1;
+      break;
+    case 'z':
+      qh->rbox_isinteger= 1;
+      break;
+    case 'B':
+      box= qh_strtod(s, &s);
+      isbox= 1;
+      break;
+    case 'C':
+      if (*s)
+        coincidentpoints=  qh_strtol(s, &s);
+      if (*s == ',') {
+        ++s;
+        coincidentradius=  qh_strtod(s, &s);
+      }
+      if (*s == ',') {
+        ++s;
+        coincidenttotal=  qh_strtol(s, &s);
+      }
+      if (*s && !isspace(*s)) {
+        qh_fprintf_rbox(qh, qh->ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
+        qh_errexit_rbox(qh, qh_ERRinput);
+      }
+      if (coincidentpoints==0){
+        qh_fprintf_rbox(qh, qh->ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
+        qh_errexit_rbox(qh, qh_ERRinput);
+      }
+      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
+        qh_fprintf_rbox(qh, qh->ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
+        qh_errexit_rbox(qh, qh_ERRinput);
+      }
+      break;
+    case 'D':
+      dim= qh_strtol(s, &s);
+      if (dim < 1
+      || dim > MAXdim) {
+        qh_fprintf_rbox(qh, qh->ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)\n", dim, MAXdim);
+        qh_errexit_rbox(qh, qh_ERRinput);
+      }
+      break;
+    case 'G':
+      if (isdigit(*s))
+        gap= qh_strtod(s, &s);
+      else
+        gap= 0.5;
+      isgap= 1;
+      break;
+    case 'L':
+      if (isdigit(*s))
+        radius= qh_strtod(s, &s);
+      else
+        radius= 10;
+      islens= 1;
+      break;
+    case 'M':
+      ismesh= 1;
+      if (*s)
+        meshn= qh_strtod(s, &s);
+      if (*s == ',') {
+        ++s;
+        meshm= qh_strtod(s, &s);
+      }else
+        meshm= 0.0;
+      if (*s == ',') {
+        ++s;
+        meshr= qh_strtod(s, &s);
+      }else
+        meshr= sqrt(meshn*meshn + meshm*meshm);
+      if (*s && !isspace(*s)) {
+        qh_fprintf_rbox(qh, qh->ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
+        meshn= 3.0, meshm=4.0, meshr=5.0;
+      }
+      break;
+    case 'O':
+      qh->rbox_out_offset= qh_strtod(s, &s);
+      break;
+    case 'P':
+      if (!first_point)
+        first_point= s - 1;
+      addpoints++;
+      while (*s && !isspace(*s))   /* read points later */
+        s++;
+      break;
+    case 'W':
+      width= qh_strtod(s, &s);
+      iswidth= 1;
+      break;
+    case 'Z':
+      if (isdigit(*s))
+        radius= qh_strtod(s, &s);
+      else
+        radius= 1.0;
+      isaxis= 1;
+      break;
+    default:
+      qh_fprintf_rbox(qh, qh->ferr, 6352, "rbox error: unknown flag at '%s'.\nExecute 'rbox' without arguments for documentation.\n", s - 1);
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    if (*s && !isspace(*s)) {
+      qh_fprintf_rbox(qh, qh->ferr, 6353, "rbox error: missing space between flags at %s.\n", s);
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+  }
+
+  /* ============= defaults, constants, and sizes =============== */
+  if (qh->rbox_isinteger && !isbox)
+    box= qh_DEFAULTzbox;
+  if (addcube) {
+    tempr= floor(ldexp(1.0,dim)+0.5);
+    cubesize= (int)tempr;
+    if (cube == 0.0)
+      cube= box;
+  }else
+    cubesize= 0;
+  if (adddiamond) {
+    diamondsize= 2*dim;
+    if (diamond == 0.0)
+      diamond= box;
+  }else
+    diamondsize= 0;
+  if (islens) {
+    if (isaxis) {
+        qh_fprintf_rbox(qh, qh->ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
+        qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    if (radius <= 1.0) {
+        qh_fprintf_rbox(qh, qh->ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
+               radius);
+        qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    lensangle= asin(1.0/radius);
+    lensbase= radius * cos(lensangle);
+  }
+
+  if (!numpoints) {
+    if (issimplex2)
+        ; /* ok */
+    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
+        qh_fprintf_rbox(qh, qh->ferr, 6192, "rbox error: missing count\n");
+        qh_errexit_rbox(qh, qh_ERRinput);
+    }else if (adddiamond + addcube + addpoints)
+        ; /* ok */
+    else {
+        numpoints= 50;  /* ./rbox D4 is the test case */
+        issphere= 1;
+    }
+  }
+  if ((issimplex + islens + isspiral + ismesh > 1)
+  || (issimplex + issphere + isspiral + ismesh > 1)) {
+    qh_fprintf_rbox(qh, qh->ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
+    qh_errexit_rbox(qh, qh_ERRinput);
+  }
+  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
+    qh_fprintf_rbox(qh, qh->ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
+    qh_errexit_rbox(qh, qh_ERRinput);
+  }
+  if (coincidentpoints > 0 && isregular) {
+    qh_fprintf_rbox(qh, qh->ferr, 6423, "rbox error: 'Cn,r,m' is not implemented for regular points ('r')\n");
+    qh_errexit_rbox(qh, qh_ERRinput);
+  }
+
+  if (coincidenttotal == 0)
+    coincidenttotal= numpoints;
+
+  /* ============= print header with total points =============== */
+  if (issimplex || ismesh)
+    totpoints= numpoints;
+  else if (issimplex2)
+    totpoints= numpoints+dim+1;
+  else if (isregular) {
+    totpoints= numpoints;
+    if (dim == 2) {
+        if (islens)
+          totpoints += numpoints - 2;
+    }else if (dim == 3) {
+        if (islens)
+          totpoints += 2 * numpoints;
+      else if (isgap)
+        totpoints += 1 + numpoints;
+      else
+        totpoints += 2;
+    }
+  }else
+    totpoints= numpoints + isaxis;
+  totpoints += cubesize + diamondsize + addpoints;
+  totpoints += coincidentpoints*coincidenttotal;
+
+  /* ============= seed randoms =============== */
+  if (istime == 0) {
+    for (s=command; *s; s++) {
+      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
+        i= 'x';
+      else
+        i= *s;
+      seed= 11*seed + i;
+    }
+  }else if (israndom) {
+    seed= (int)time(&timedata);
+    snprintf(seedbuf, sizeof(seedbuf) / sizeof(seedbuf[0]), " t%d", seed);  /* appends an extra t, not worth removing */
+    strncat(command, seedbuf, sizeof(command) - strlen(command) - 1);
+    t= strstr(command, " t ");
+    if (t)
+      strcpy(t+1, t+3); /* remove " t " */
+  } /* else, seed explicitly set to n */
+  qh_RANDOMseed_(qh, seed);
+
+  /* ============= print header =============== */
+
+  if (iscdd)
+      qh_fprintf_rbox(qh, qh->fout, 9391, "%s\nbegin\n        %d %d %s\n",
+      NOcommand ? "" : command,
+      totpoints, dim+1,
+      qh->rbox_isinteger ? "integer" : "real");
+  else if (NOcommand)
+      qh_fprintf_rbox(qh, qh->fout, 9392, "%d\n%d\n", dim, totpoints);
+  else
+      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
+      qh_fprintf_rbox(qh, qh->fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
+
+  /* ============= explicit points =============== */
+  if ((s= first_point)) {
+    while (s && *s) { /* 'P' */
+      count= 0;
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      while (*++s) {
+        qh_out1(qh, qh_strtod(s, &s));
+        count++;
+        if (isspace(*s) || !*s)
+          break;
+        if (*s != ',') {
+          qh_fprintf_rbox(qh, qh->ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
+          qh_errexit_rbox(qh, qh_ERRinput);
+        }
+      }
+      if (count < dim) {
+        for (k=dim-count; k--; )
+          qh_out1(qh, 0.0);
+      }else if (count > dim) {
+        qh_fprintf_rbox(qh, qh->ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
+                  count, dim, s);
+        qh_errexit_rbox(qh, qh_ERRinput);
+      }
+      qh_fprintf_rbox(qh, qh->fout, 9394, "\n");
+      while ((s= strchr(s, 'P'))) {
+        if (isspace(s[-1]))
+          break;
+      }
+    }
+  }
+
+  /* ============= simplex distribution =============== */
+  if (issimplex+issimplex2) {
+    if (!(*simplex= (double *)qh_malloc( (size_t)(dim * (dim+1)) * sizeof(double)))) {
+      qh_fprintf_rbox(qh, qh->ferr, 6196, "rbox error: insufficient memory for simplex\n");
+      qh_errexit_rbox(qh, qh_ERRmem); /* qh_ERRmem */
+    }
+    simplexp= *simplex;
+    if (isregular) {
+      for (i=0; ifout, 9395, "\n");
+      }
+    }
+    for (j=0; jferr, 6197, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    if (!isaxis || radius == 0.0) {
+      isaxis= 1;
+      radius= 1.0;
+    }
+    if (dim == 3) {
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      qh_out3n(qh, 0.0, 0.0, -box);
+      if (!isgap) {
+        if (iscdd)
+          qh_out1(qh, 1.0);
+        qh_out3n(qh, 0.0, 0.0, box);
+      }
+    }
+    angle= 0.0;
+    anglediff= 2.0 * PI/numpoints;
+    for (i=0; i < numpoints; i++) {
+      angle += anglediff;
+      x= radius * cos(angle);
+      y= radius * sin(angle);
+      if (dim == 2) {
+        if (iscdd)
+          qh_out1(qh, 1.0);
+        qh_out2n(qh, x*box, y*box);
+      }else {
+        norm= sqrt(1.0 + x*x + y*y);
+        if (iscdd)
+          qh_out1(qh, 1.0);
+        qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
+        if (isgap) {
+          x *= 1-gap;
+          y *= 1-gap;
+          norm= sqrt(1.0 + x*x + y*y);
+          if (iscdd)
+            qh_out1(qh, 1.0);
+          qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
+        }
+      }
+    }
+  }
+  /* ============= regular points for 'r Ln D2' =============== */
+  else if (isregular && islens && dim == 2) {
+    double cos_0;
+
+    angle= lensangle;
+    anglediff= 2 * lensangle/(numpoints - 1);
+    cos_0= cos(lensangle);
+    for (i=0; i < numpoints; i++, angle -= anglediff) {
+      x= radius * sin(angle);
+      y= radius * (cos(angle) - cos_0);
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      qh_out2n(qh, x*box, y*box);
+      if (i != 0 && i != numpoints - 1) {
+        if (iscdd)
+          qh_out1(qh, 1.0);
+        qh_out2n(qh, x*box, -y*box);
+      }
+    }
+  }
+  /* ============= regular points for 'r Ln D3' =============== */
+  else if (isregular && islens && dim != 2) {
+    if (dim != 3) {
+      qh_fprintf_rbox(qh, qh->ferr, 6198, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    angle= 0.0;
+    anglediff= 2* PI/numpoints;
+    if (!isgap) {
+      isgap= 1;
+      gap= 0.5;
+    }
+    offset= sqrt(radius * radius - (1-gap)*(1-gap)) - lensbase;
+    for (i=0; i < numpoints; i++, angle += anglediff) {
+      x= cos(angle);
+      y= sin(angle);
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      qh_out3n(qh, box*x, box*y, 0.0);
+      x *= 1-gap;
+      y *= 1-gap;
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      qh_out3n(qh, box*x, box*y, box * offset);
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      qh_out3n(qh, box*x, box*y, -box * offset);
+    }
+  }
+  /* ============= apex of 'Zn' distribution + gendim =============== */
+  else {
+    if (isaxis) {
+      gendim= dim-1;
+      if (iscdd)
+        qh_out1(qh, 1.0);
+      for (j=0; j < gendim; j++)
+        qh_out1(qh, 0.0);
+      qh_out1(qh, -box);
+      qh_fprintf_rbox(qh, qh->fout, 9398, "\n");
+    }else if (islens)
+      gendim= dim-1;
+    else
+      gendim= dim;
+    /* ============= generate random point in unit cube =============== */
+    for (i=0; i < numpoints; i++) {
+      norm= 0.0;
+      for (j=0; j < gendim; j++) {
+        randr= qh_RANDOMint;
+        coord[j]= 2.0 * randr/randmax - 1.0;
+        norm += coord[j] * coord[j];
+      }
+      norm= sqrt(norm);
+      /* ============= dim-1 point of 'Zn' distribution ========== */
+      if (isaxis) {
+        if (!isgap) {
+          isgap= 1;
+          gap= 1.0;
+        }
+        randr= qh_RANDOMint;
+        rangap= 1.0 - gap * randr/randmax;
+        factor= radius * rangap / norm;
+        for (j=0; jferr, 6199, "rbox error: spiral distribution is available only in 3d\n\n");
+          qh_errexit_rbox(qh, qh_ERRinput);
+        }
+        coord[0]= cos(2*PI*i/(numpoints - 1));
+        coord[1]= sin(2*PI*i/(numpoints - 1));
+        coord[2]= 2.0*(double)i/(double)(numpoints - 1) - 1.0;
+      /* ============= point of 's' distribution =============== */
+      }else if (issphere) {
+        factor= 1.0/norm;
+        if (iswidth) {
+          randr= qh_RANDOMint;
+          factor *= 1.0 - width * randr/randmax;
+        }
+        for (j=0; j randmax/2)
+          coord[dim-1]= -coord[dim-1];
+      /* ============= project 'Wn' point toward boundary =============== */
+      }else if (iswidth && !issphere) {
+        j= qh_RANDOMint % gendim;
+        if (coord[j] < 0)
+          coord[j]= -1.0 - coord[j] * width;
+        else
+          coord[j]= 1.0 - coord[j] * width;
+      }
+      /* ============= scale point to box =============== */
+      for (k=0; k=0; k--) {
+        if (j & ( 1 << k))
+          qh_out1(qh, cube);
+        else
+          qh_out1(qh, -cube);
+      }
+      qh_fprintf_rbox(qh, qh->fout, 9400, "\n");
+    }
+  }
+
+  /* ============= write diamond vertices =============== */
+  if (adddiamond) {
+    for (j=0; j=0; k--) {
+        if (j/2 != k)
+          qh_out1(qh, 0.0);
+        else if (j & 0x1)
+          qh_out1(qh, diamond);
+        else
+          qh_out1(qh, -diamond);
+      }
+      qh_fprintf_rbox(qh, qh->fout, 9401, "\n");
+    }
+  }
+
+  if (iscdd)
+    qh_fprintf_rbox(qh, qh->fout, 9402, "end\nhull\n");
+} /* rboxpoints2 */
+
+/*------------------------------------------------
+outxxx - output functions for qh_rboxpoints
+*/
+int qh_roundi(qhT *qh, double a) {
+  if (a < 0.0) {
+    if (a - 0.5 < INT_MIN) {
+      qh_fprintf_rbox(qh, qh->ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    return (int)(a - 0.5);
+  }else {
+    if (a + 0.5 > INT_MAX) {
+      qh_fprintf_rbox(qh, qh->ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
+      qh_errexit_rbox(qh, qh_ERRinput);
+    }
+    return (int)(a + 0.5);
+  }
+} /* qh_roundi */
+
+void qh_out1(qhT *qh, double a) {
+
+  if (qh->rbox_isinteger)
+    qh_fprintf_rbox(qh, qh->fout, 9403, "%d ", qh_roundi(qh, a+qh->rbox_out_offset));
+  else
+    qh_fprintf_rbox(qh, qh->fout, 9404, qh_REAL_1, a+qh->rbox_out_offset);
+} /* qh_out1 */
+
+void qh_out2n(qhT *qh, double a, double b) {
+
+  if (qh->rbox_isinteger)
+    qh_fprintf_rbox(qh, qh->fout, 9405, "%d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset));
+  else
+    qh_fprintf_rbox(qh, qh->fout, 9406, qh_REAL_2n, a+qh->rbox_out_offset, b+qh->rbox_out_offset);
+} /* qh_out2n */
+
+void qh_out3n(qhT *qh, double a, double b, double c) {
+
+  if (qh->rbox_isinteger)
+    qh_fprintf_rbox(qh, qh->fout, 9407, "%d %d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset), qh_roundi(qh, c+qh->rbox_out_offset));
+  else
+    qh_fprintf_rbox(qh, qh->fout, 9408, qh_REAL_3n, a+qh->rbox_out_offset, b+qh->rbox_out_offset, c+qh->rbox_out_offset);
+} /* qh_out3n */
+
+void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim) {
+    double *p= coord;
+    int k;
+
+    if (iscdd)
+      qh_out1(qh, 1.0);
+    for (k=0; k < dim; k++)
+      qh_out1(qh, *(p++));
+    qh_fprintf_rbox(qh, qh->fout, 9396, "\n");
+} /* qh_outcoord */
+
+void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
+  double *p;
+  double randr, delta;
+  int i,k;
+  double randmax= qh_RANDOMmax;
+
+  for (i=0; ifout, 9410, "\n");
+  }
+} /* qh_outcoincident */
+
+/*------------------------------------------------
+   Only called from qh_rboxpoints2 or qh_fprintf_rbox
+   qh_fprintf_rbox is only called from qh_rboxpoints2
+   The largest exitcode is '255' for compatibility with exit()
+*/
+void qh_errexit_rbox(qhT *qh, int exitcode)
+{
+    longjmp(qh->rbox_errexit, exitcode);
+} /* qh_errexit_rbox */
+
diff --git a/vendor/qhull/libqhull_r/stat_r.c b/vendor/qhull/libqhull_r/stat_r.c
new file mode 100644
index 0000000..edc9c2f
--- /dev/null
+++ b/vendor/qhull/libqhull_r/stat_r.c
@@ -0,0 +1,727 @@
+/*
  ---------------------------------
+
+   stat_r.c
+   contains all statistics that are collected for qhull
+
+   see qh-stat_r.htm and stat_r.h
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/stat_r.c#9 $$Change: 3037 $
+   $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $
+*/
+
+#include "qhull_ra.h"
+
+/*========== functions in alphabetic order ================*/
+
+/*---------------------------------
+
+  qh_allstatA()
+    define statistics in groups of 20
+
+  notes:
+    (otherwise, 'gcc -O2' uses too much memory)
+    uses qhstat.next
+*/
+void qh_allstatA(qhT *qh) {
+
+   /* zdef_(type,name,doc,average) */
+  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
+  zdef_(zinc, Znewvertex, NULL, -1);
+  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet", Znewvertex);
+  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
+  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
+  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
+  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
+
+  qh->qhstat.precision= qh->qhstat.next;  /* usually call qh_joggle_restart, printed if Q0 or QJn */
+  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
+  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
+  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
+  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
+  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
+  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
+  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
+  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
+  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
+  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
+  zzdef_(zinc, Zmultiridge, "dupridges with multiple neighbors", -1);
+  zzdef_(zinc, Zflipridge, "dupridges with flip facet into good neighbor", -1);
+  zzdef_(zinc, Zflipridge2, "dupridges with flip facet into good flip neighbor", -1);
+}
+void qh_allstatB(qhT *qh) {
+  zzdef_(zdoc, Zdoc1, "summary information", -1);
+  zdef_(zinc, Zvertices, "number of vertices in output", -1);
+  zdef_(zinc, Znumfacets, "number of facets in output", -1);
+  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
+  zdef_(zinc, Znowsimplicial, "simplicial facets that were non-simplicial", -1);
+  zdef_(zinc, Znumridges, "number of ridges in output", -1);
+  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
+  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
+  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
+  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
+  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
+  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
+  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
+  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
+  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
+  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
+  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
+  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
+  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
+  zdef_(zadd, Znummergetot, "average merges per facet (at most 511)", Znumfacets);
+  zdef_(zmax, Znummergemax, "  maximum merges for a facet (at most 511)", -1);
+  zdef_(zinc, Zangle, NULL, -1);
+  zdef_(wadd, Wangle, "average cosine (angle) of facet normals for all ridges", Zangle);
+  zdef_(wmax, Wanglemax, "  maximum cosine of facet normals (flatest) across a ridge", -1);
+  zdef_(wmin, Wanglemin, "  minimum cosine of facet normals (sharpest) across a ridge", -1);
+  zdef_(wadd, Wareatot, "total area of facets", -1);
+  zdef_(wmax, Wareamax, "  maximum facet area", -1);
+  zdef_(wmin, Wareamin, "  minimum facet area", -1);
+}
+void qh_allstatC(qhT *qh) {
+  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
+  zzdef_(zinc, Zprocessed, "points processed", -1);
+  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
+  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
+  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
+  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
+  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
+  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
+  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
+  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
+  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
+  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
+  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
+  zdef_(zmax, Znewfacetmax,  "    maximum (includes initial simplex)", -1);
+  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
+  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
+  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
+  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
+  zdef_(zinc, Zpbalance,  "  count", -1);
+  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
+  zdef_(zinc, Zdetfacetarea, "determinants for facet area", -1);
+  zdef_(zinc, Znoarea, "  determinants not computed because vertex too low", -1);
+  zdef_(zinc, Zdetsimplex, "determinants for initial hull or voronoi vertices", -1);
+  zdef_(zinc, Znotmax, "points ignored (!above max_outside)", -1);
+  zdef_(zinc, Zpinchedapex, "points ignored (pinched apex)", -1);
+  zdef_(zinc, Znotgood, "points ignored (!above a good facet)", -1);
+  zdef_(zinc, Znotgoodnew, "points ignored (didn't create a good new facet)", -1);
+  zdef_(zinc, Zgoodfacet, "good facets found", -1);
+  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
+  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
+  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
+  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
+}
+void qh_allstatD(qhT *qh) {
+  zdef_(zinc, Zvisit, "resets of visit_id", -1);
+  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
+  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
+  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
+
+  zdef_(zdoc, Zdoc4, "partitioning statistics (see previous for outer planes)", -1);
+  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
+  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
+  zdef_(zinc, Zfindbest, "calls to findbest", -1);
+  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
+  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
+  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
+  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
+  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
+  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
+  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
+  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
+  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
+  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
+  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
+  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
+  zdef_(zinc, Znewbesthorizon, " new bestfacets during qh_findbesthorizon", -1);
+  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
+  zdef_(zinc, Zpartcorner, "  repartitioned coplanar points above a corner facet", -1);
+  zdef_(zinc, Zparthidden, "  repartitioned coplanar points above a hidden facet", -1);
+  zdef_(zinc, Zparttwisted, "  repartitioned coplanar points above a twisted facet", -1);
+}
+void qh_allstatE(qhT *qh) {
+  zdef_(zinc, Zpartinside, "inside points", -1);
+  zdef_(zinc, Zpartnear, "  near inside points kept with a facet", -1);
+  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
+  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
+  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
+  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
+  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
+  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
+  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
+  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
+  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
+  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
+  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
+  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
+  zdef_(zinc, Zdistio, "distance tests for output", -1);
+  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
+  zzdef_(zinc, Zdistplane, "total number of distance tests", -1);
+  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
+  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
+  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
+}
+void qh_allstatE2(qhT *qh) {
+  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
+  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
+  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
+  zdef_(zinc, Zhashridge, "total lookups of subridges (duplicates and boundary)", -1);
+  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
+  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
+  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
+
+  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
+  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
+  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
+  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
+  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
+  zzdef_(zinc, Zvertextests, "distance tests for vertex convexity", -1);
+  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
+  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
+  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums or vertices in getmergeset", -1);
+  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
+  zdef_(zinc, Zconcavecoplanarridge, "concave-coplanar ridges in getmergeset", -1);
+  zdef_(zinc, Ztwistedridge, "twisted ridges in getmergeset", -1);
+}
+void qh_allstatF(qhT *qh) {
+  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
+  zdef_(zinc, Zpremergetot, "merge iterations", -1);
+  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
+  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
+  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
+  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
+  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
+  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
+  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet (w/roundoff)", -1);
+  zdef_(wmin, Wminvertex, "max distance of vertex below facet (or roundoff)", -1);
+  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
+  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
+  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
+  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
+  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
+  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
+  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
+  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
+  zdef_(zinc, Zmergeintocoplanar, "new facets merged into coplanar horizon", -1);
+  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
+  zdef_(zinc, Zmergenew, "new facets merged", -1);
+  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
+  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
+  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
+  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
+  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
+  zdef_(zinc, Zredundant, "merges due to redundant neighbors", -1);
+  zdef_(zinc, Zredundantmerge, "  detected by qh_test_nonsimplicial_merge instead of qh_test_redundant_neighbors", -1);
+  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
+}
+void qh_allstatG(qhT *qh) {
+  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
+  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
+  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
+  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
+  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
+  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
+  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
+  zdef_(zinc, Zconcavecoplanar, "merges due to concave-coplanar facets", -1);
+  zdef_(wadd, Wconcavecoplanartot, "  average merge distance", Zconcavecoplanar);
+  zdef_(wmax, Wconcavecoplanarmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
+  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
+  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
+  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
+  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
+  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
+  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
+  zdef_(zinc, Zduplicate, "merges due to dupridges", -1);
+  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
+  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
+  zdef_(zinc, Ztwisted, "merges due to twisted facets", -1);
+  zdef_(wadd, Wtwistedtot, "  average merge distance", Ztwisted);
+  zdef_(wmax, Wtwistedmax, "  maximum merge distance", -1);
+}
+void qh_allstatH(qhT *qh) {
+  zdef_(zdoc, Zdoc8, "statistics for vertex merges", -1);
+  zzdef_(zinc, Zpinchduplicate, "merge pinched vertices for a duplicate ridge", -1);
+  zzdef_(zinc, Zpinchedvertex, "merge pinched vertices for a dupridge", -1);
+  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
+  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
+  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
+  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
+  zdef_(zinc, Znewvertexridge, "  found new vertex in ridge", -1);
+  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
+  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
+  zdef_(zinc, Zdropdegen, "merge degenerate facets due to dropped neighbors", -1);
+  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
+  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
+  zdef_(zinc, Zremvertexdel, "  deleted", -1);
+  zdef_(zinc, Zretryadd, "retry qh_addpoint after merge pinched vertex", -1);
+  zdef_(zadd, Zretryaddtot, "  tot. merge pinched vertex due to dupridge", -1);
+  zdef_(zmax, Zretryaddmax, "  max. merge pinched vertex for a qh_addpoint", -1);
+  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
+  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
+  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
+  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
+  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
+  zdef_(zinc, Zvertexridge, NULL, -1);
+  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
+  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
+
+  zdef_(zdoc, Zdoc10, "memory usage statistics (in bytes)", -1);
+  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
+  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
+  zdef_(zadd, Zmempoints, "for input points, outside and coplanar sets, and qhT",-1);
+  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
+} /* allstat */
+
+void qh_allstatI(qhT *qh) {
+  qh->qhstat.vridges= qh->qhstat.next; /* printed in qh_produce_output2 if non-zero Zridge or Zridgemid */
+  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
+  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
+  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
+  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
+  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
+  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
+  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
+  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
+  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
+  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
+  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
+  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
+  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
+
+  zdef_(zdoc, Zdoc12, "Triangulation statistics ('Qt')", -1);
+  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
+  zdef_(zadd, Ztricoplanartot, "  ave. new facets created (may be deleted)", Ztricoplanar);
+  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
+  zdef_(zinc, Ztrinull, "null new facets deleted (duplicated vertex)", -1);
+  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted (same vertices)", -1);
+  zdef_(zinc, Ztridegen, "degenerate new facets in output (same ridge)", -1);
+} /* allstat */
+
+/*---------------------------------
+
+  qh_allstatistics()
+    reset printed flag for all statistics
+*/
+void qh_allstatistics(qhT *qh) {
+  int i;
+
+  for(i=ZEND; i--; )
+    qh->qhstat.printed[i]= False;
+} /* allstatistics */
+
+#if qh_KEEPstatistics
+/*---------------------------------
+
+  qh_collectstatistics()
+    collect statistics for qh.facet_list
+
+*/
+void qh_collectstatistics(qhT *qh) {
+  facetT *facet, *neighbor, **neighborp;
+  vertexT *vertex, **vertexp;
+  realT dotproduct, dist;
+  int sizneighbors, sizridges, sizvertices, i;
+
+  qh->old_randomdist= qh->RANDOMdist;
+  qh->RANDOMdist= False;
+  zval_(Zmempoints)= qh->num_points * qh->normal_size + (int)sizeof(qhT);
+  zval_(Zmemfacets)= 0;
+  zval_(Zmemridges)= 0;
+  zval_(Zmemvertices)= 0;
+  zval_(Zangle)= 0;
+  wval_(Wangle)= 0.0;
+  zval_(Znumridges)= 0;
+  zval_(Znumfacets)= 0;
+  zval_(Znumneighbors)= 0;
+  zval_(Znumvertices)= 0;
+  zval_(Znumvneighbors)= 0;
+  zval_(Znummergetot)= 0;
+  zval_(Znummergemax)= 0;
+  zval_(Zvertices)= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
+  if (qh->MERGING || qh->APPROXhull || qh->JOGGLEmax < REALmax/2)
+    wmax_(Wmaxoutside, qh->max_outside);
+  if (qh->MERGING)
+    wmin_(Wminvertex, qh->min_vertex);
+  if (!qh_checklists(qh, qh->facet_list)) {
+    qh_fprintf(qh, qh->ferr, 6373, "qhull internal error: qh_checklists failed on qh_collectstatistics\n");
+    if (!qh->ERREXITcalled)
+      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
+  }
+  FORALLfacets
+    facet->seen= False;
+  if (qh->DELAUNAY) {
+    FORALLfacets {
+      if (facet->upperdelaunay != qh->UPPERdelaunay)
+        facet->seen= True; /* remove from angle statistics */
+    }
+  }
+  FORALLfacets {
+    if (facet->visible && qh->NEWfacets)
+      continue;
+    sizvertices= qh_setsize(qh, facet->vertices);
+    sizneighbors= qh_setsize(qh, facet->neighbors);
+    sizridges= qh_setsize(qh, facet->ridges);
+    zinc_(Znumfacets);
+    zadd_(Znumvertices, sizvertices);
+    zmax_(Zmaxvertices, sizvertices);
+    zadd_(Znumneighbors, sizneighbors);
+    zmax_(Zmaxneighbors, sizneighbors);
+    zadd_(Znummergetot, facet->nummerge);
+    i= facet->nummerge; /* avoid warnings */
+    zmax_(Znummergemax, i);
+    if (!facet->simplicial) {
+      if (sizvertices == qh->hull_dim) {
+        zinc_(Znowsimplicial);
+      }else {
+        zinc_(Znonsimplicial);
+      }
+    }
+    if (sizridges) {
+      zadd_(Znumridges, sizridges);
+      zmax_(Zmaxridges, sizridges);
+    }
+    zadd_(Zmemfacets, (int)sizeof(facetT) + qh->normal_size + 2*SETbasesize
+       + SETelemsize * (sizneighbors + sizvertices));
+    if (facet->ridges) {
+      zadd_(Zmemridges,
+        SETbasesize + SETelemsize * sizridges + sizridges *
+         ((int)sizeof(ridgeT) + SETbasesize + SETelemsize * (qh->hull_dim-1))/2);
+    }
+    if (facet->outsideset)
+      zadd_(Zmempoints, SETbasesize + SETelemsize * qh_setsize(qh, facet->outsideset));
+    if (facet->coplanarset)
+      zadd_(Zmempoints, SETbasesize + SETelemsize * qh_setsize(qh, facet->coplanarset));
+    if (facet->seen) /* Delaunay upper envelope */
+      continue;
+    facet->seen= True;
+    FOREACHneighbor_(facet) {
+      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
+          || neighbor->seen || !facet->normal || !neighbor->normal)
+        continue;
+      dotproduct= qh_getangle(qh, facet->normal, neighbor->normal);
+      zinc_(Zangle);
+      wadd_(Wangle, dotproduct);
+      wmax_(Wanglemax, dotproduct)
+      wmin_(Wanglemin, dotproduct)
+    }
+    if (facet->normal) {
+      FOREACHvertex_(facet->vertices) {
+        zinc_(Zdiststat);
+        qh_distplane(qh, vertex->point, facet, &dist);
+        wmax_(Wvertexmax, dist);
+        wmin_(Wvertexmin, dist);
+      }
+    }
+  }
+  FORALLvertices {
+    if (vertex->deleted)
+      continue;
+    zadd_(Zmemvertices, (int)sizeof(vertexT));
+    if (vertex->neighbors) {
+      sizneighbors= qh_setsize(qh, vertex->neighbors);
+      zadd_(Znumvneighbors, sizneighbors);
+      zmax_(Zmaxvneighbors, sizneighbors);
+      zadd_(Zmemvertices, (int)sizeof(vertexT) + SETelemsize * sizneighbors);
+    }
+  }
+  qh->RANDOMdist= qh->old_randomdist;
+} /* collectstatistics */
+#endif /* qh_KEEPstatistics */
+
+/*---------------------------------
+
+  qh_initstatistics(qh)
+    initialize statistics
+
+  notes:
+    NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
+    On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
+    Also invoked by QhullQh().
+*/
+void qh_initstatistics(qhT *qh) {
+  int i;
+  realT realx;
+  int intx;
+
+  qh_allstatistics(qh);
+  qh->qhstat.next= 0;
+  qh_allstatA(qh);
+  qh_allstatB(qh);
+  qh_allstatC(qh);
+  qh_allstatD(qh);
+  qh_allstatE(qh);
+  qh_allstatE2(qh);
+  qh_allstatF(qh);
+  qh_allstatG(qh);
+  qh_allstatH(qh);
+  qh_allstatI(qh);
+  if (qh->qhstat.next > (int)sizeof(qh->qhstat.id)) {
+    qh_fprintf_stderr(6184, "qhull internal error (qh_initstatistics): increase size of qhstat.id[].  qhstat.next %d should be <= sizeof(qh->qhstat.id) %d\n", 
+          qh->qhstat.next, (int)sizeof(qh->qhstat.id));
+#if 0 /* for locating error, Znumridges should be duplicated */
+    for(i=0; i < ZEND; i++) {
+      int j;
+      for(j=i+1; j < ZEND; j++) {
+        if (qh->qhstat.id[i] == qh->qhstat.id[j]) {
+          qh_fprintf_stderr(6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
+              qh->qhstat.id[i], i, j);
+        }
+      }
+    }
+#endif
+    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
+  }
+  qh->qhstat.init[zinc].i= 0;
+  qh->qhstat.init[zadd].i= 0;
+  qh->qhstat.init[zmin].i= INT_MAX;
+  qh->qhstat.init[zmax].i= INT_MIN;
+  qh->qhstat.init[wadd].r= 0;
+  qh->qhstat.init[wmin].r= REALmax;
+  qh->qhstat.init[wmax].r= -REALmax;
+  for(i=0; i < ZEND; i++) {
+    if (qh->qhstat.type[i] > ZTYPEreal) {
+      realx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r;
+      qh->qhstat.stats[i].r= realx;
+    }else if (qh->qhstat.type[i] != zdoc) {
+      intx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i;
+      qh->qhstat.stats[i].i= intx;
+    }
+  }
+} /* initstatistics */
+
+/*---------------------------------
+
+  qh_newstats(qh )
+    returns True if statistics for zdoc
+
+  returns:
+    next zdoc
+*/
+boolT qh_newstats(qhT *qh, int idx, int *nextindex) {
+  boolT isnew= False;
+  int start, i;
+
+  if (qh->qhstat.type[qh->qhstat.id[idx]] == zdoc)
+    start= idx+1;
+  else
+    start= idx;
+  for(i= start; i < qh->qhstat.next && qh->qhstat.type[qh->qhstat.id[i]] != zdoc; i++) {
+    if (!qh_nostatistic(qh, qh->qhstat.id[i]) && !qh->qhstat.printed[qh->qhstat.id[i]])
+        isnew= True;
+  }
+  *nextindex= i;
+  return isnew;
+} /* newstats */
+
+/*---------------------------------
+
+  qh_nostatistic(qh, index )
+    true if no statistic to print
+*/
+boolT qh_nostatistic(qhT *qh, int i) {
+
+  if ((qh->qhstat.type[i] > ZTYPEreal
+       &&qh->qhstat.stats[i].r == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r)
+      || (qh->qhstat.type[i] < ZTYPEreal
+          &&qh->qhstat.stats[i].i == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i))
+    return True;
+  return False;
+} /* nostatistic */
+
+#if qh_KEEPstatistics
+/*---------------------------------
+
+  qh_printallstatistics(qh, fp, string )
+    print all statistics with header 'string'
+*/
+void qh_printallstatistics(qhT *qh, FILE *fp, const char *string) {
+
+  qh_allstatistics(qh);
+  qh_collectstatistics(qh);
+  qh_printstatistics(qh, fp, string);
+  qh_memstatistics(qh, fp);
+}
+
+
+/*---------------------------------
+
+  qh_printstatistics(qh, fp, string )
+    print statistics to a file with header 'string'
+    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
+
+  see:
+    qh_printallstatistics()
+*/
+void qh_printstatistics(qhT *qh, FILE *fp, const char *string) {
+  int i, k;
+  realT ave; /* ignored */
+
+  if (qh->num_points != qh->num_vertices || zval_(Zpbalance) == 0) {
+    wval_(Wpbalance)= 0.0;
+    wval_(Wpbalance2)= 0.0;
+  }else
+    wval_(Wpbalance2)= qh_stddev(qh, zval_(Zpbalance), wval_(Wpbalance),
+                                 wval_(Wpbalance2), &ave);
+  if (zval_(Zprocessed) == 0)
+    wval_(Wnewbalance2)= 0.0;
+  else
+    wval_(Wnewbalance2)= qh_stddev(qh, zval_(Zprocessed), wval_(Wnewbalance),
+                                 wval_(Wnewbalance2), &ave);
+  qh_fprintf(qh, fp, 9350, "\n\
+%s\n\
+qhull invoked by: %s | %s\n  %s with options:\n%s\n", 
+    string, qh->rbox_command, qh->qhull_command, qh_version, qh->qhull_options);
+
+  qh_fprintf(qh, fp, 9351, "\nprecision constants:\n\
+ %6.2g max. abs. coordinate in the (transformed) input ('Qbd:n')\n\
+ %6.2g max. roundoff error for distance computation ('En')\n\
+ %6.2g max. roundoff error for angle computations\n\
+ %6.2g min. distance for outside points ('Wn')\n\
+ %6.2g min. distance for visible facets ('Vn')\n\
+ %6.2g max. distance for coplanar facets ('Un')\n\
+ %6.2g max. facet width for recomputing centrum and area\n\
+",
+  qh->MAXabs_coord, qh->DISTround, qh->ANGLEround, qh->MINoutside,
+        qh->MINvisible, qh->MAXcoplanar, qh->WIDEfacet);
+  if (qh->KEEPnearinside)
+    qh_fprintf(qh, fp, 9352, "\
+ %6.2g max. distance for near-inside points\n", qh->NEARinside);
+  if (qh->premerge_cos < REALmax/2) qh_fprintf(qh, fp, 9353, "\
+ %6.2g max. cosine for pre-merge angle\n", qh->premerge_cos);
+  if (qh->PREmerge) qh_fprintf(qh, fp, 9354, "\
+ %6.2g radius of pre-merge centrum\n", qh->premerge_centrum);
+  if (qh->postmerge_cos < REALmax/2) qh_fprintf(qh, fp, 9355, "\
+ %6.2g max. cosine for post-merge angle\n", qh->postmerge_cos);
+  if (qh->POSTmerge) qh_fprintf(qh, fp, 9356, "\
+ %6.2g radius of post-merge centrum\n", qh->postmerge_centrum);
+  qh_fprintf(qh, fp, 9357, "\
+ %6.2g max. distance for merging two simplicial facets\n\
+ %6.2g max. roundoff error for arithmetic operations\n\
+ %6.2g min. denominator for division\n\
+  zero diagonal for Gauss: ", qh->ONEmerge, REALepsilon, qh->MINdenom);
+  for(k=0; k < qh->hull_dim; k++)
+    qh_fprintf(qh, fp, 9358, "%6.2e ", qh->NEARzero[k]);
+  qh_fprintf(qh, fp, 9359, "\n\n");
+  for(i=0 ; i < qh->qhstat.next; )
+    qh_printstats(qh, fp, i, &i);
+} /* printstatistics */
+#endif /* qh_KEEPstatistics */
+
+/*---------------------------------
+
+  qh_printstatlevel(qh, fp, id )
+    print level information for a statistic
+
+  notes:
+    nop if id >= ZEND, printed, or same as initial value
+*/
+void qh_printstatlevel(qhT *qh, FILE *fp, int id) {
+
+  if (id >= ZEND || qh->qhstat.printed[id])
+    return;
+  if (qh->qhstat.type[id] == zdoc) {
+    qh_fprintf(qh, fp, 9360, "%s\n", qh->qhstat.doc[id]);
+    return;
+  }
+  if (qh_nostatistic(qh, id) || !qh->qhstat.doc[id])
+    return;
+  qh->qhstat.printed[id]= True;
+  if (qh->qhstat.count[id] != -1
+      && qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i == 0)
+    qh_fprintf(qh, fp, 9361, " *0 cnt*");
+  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] == -1)
+    qh_fprintf(qh, fp, 9362, "%7.2g", qh->qhstat.stats[id].r);
+  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] != -1)
+    qh_fprintf(qh, fp, 9363, "%7.2g", qh->qhstat.stats[id].r/ qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
+  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] == -1)
+    qh_fprintf(qh, fp, 9364, "%7d", qh->qhstat.stats[id].i);
+  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] != -1)
+    qh_fprintf(qh, fp, 9365, "%7.3g", (realT) qh->qhstat.stats[id].i / qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
+  qh_fprintf(qh, fp, 9366, " %s\n", qh->qhstat.doc[id]);
+} /* printstatlevel */
+
+
+/*---------------------------------
+
+  qh_printstats(qh, fp, index, nextindex )
+    print statistics for a zdoc group
+
+  returns:
+    next zdoc if non-null
+*/
+void qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex) {
+  int j, nexti;
+
+  if (qh_newstats(qh, idx, &nexti)) {
+    qh_fprintf(qh, fp, 9367, "\n");
+    for (j=idx; jqhstat.id[j]);
+  }
+  if (nextindex)
+    *nextindex= nexti;
+} /* printstats */
+
+#if qh_KEEPstatistics
+
+/*---------------------------------
+
+  qh_stddev(qh, num, tot, tot2, ave )
+    compute the standard deviation and average from statistics
+
+    tot2 is the sum of the squares
+  notes:
+    computes r.m.s.:
+      (x-ave)^2
+      == x^2 - 2x tot/num +   (tot/num)^2
+      == tot2 - 2 tot tot/num + tot tot/num
+      == tot2 - tot ave
+*/
+realT qh_stddev(qhT *qh, int num, realT tot, realT tot2, realT *ave) {
+  realT stddev;
+
+  if (num <= 0) {
+    qh_fprintf(qh, qh->ferr, 7101, "qhull warning (qh_stddev): expecting num > 0.  Got num %d, tot %4.4g, tot2 %4.4g.  Returning 0.0\n",
+      num, tot, tot2);
+    return 0.0;
+  }
+  *ave= tot/num;
+  stddev= sqrt(fabs(tot2/num - *ave * *ave));
+  return stddev;
+} /* stddev */
+#else
+realT qh_stddev(qhT *qh, int num, realT tot, realT tot2, realT *ave) { /* for qhull_r-exports.def */
+  QHULL_UNUSED(qh)
+  QHULL_UNUSED(num)
+  QHULL_UNUSED(tot)
+  QHULL_UNUSED(tot2)
+  QHULL_UNUSED(ave)
+
+  return 0.0;
+}
+#endif /* qh_KEEPstatistics */
+
+#if !qh_KEEPstatistics
+void    qh_collectstatistics(qhT *qh) {}
+void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string) {}
+void    qh_printstatistics(qhT *qh, FILE *fp, const char *string) {}
+#endif
+
diff --git a/vendor/qhull/libqhull_r/stat_r.h b/vendor/qhull/libqhull_r/stat_r.h
new file mode 100644
index 0000000..41b6e51
--- /dev/null
+++ b/vendor/qhull/libqhull_r/stat_r.h
@@ -0,0 +1,563 @@
+/*
  ---------------------------------
+
+   stat_r.h
+     contains all statistics that are collected for qhull
+
+   see qh-stat_r.htm and stat_r.c
+
+   Copyright (c) 1993-2020 The Geometry Center.
+   $Id: //main/2019/qhull/src/libqhull_r/stat_r.h#4 $$Change: 2953 $
+   $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $
+
+   recompile qhull if you change this file
+
+   Integer statistics are Z* while real statistics are W*.
+
+   define MAYdebugx to call a routine at every statistic event
+
+*/
+
+#ifndef qhDEFstat
+#define qhDEFstat 1
+
+/* Depends on realT.  Do not include "libqhull_r" to avoid circular dependency */
+
+#ifndef DEFqhT
+#define DEFqhT 1
+typedef struct qhT qhT;         /* Defined by libqhull_r.h */
+#endif
+
+#ifndef DEFqhstatT
+#define DEFqhstatT 1
+typedef struct qhstatT qhstatT; /* Defined here */
+#endif
+
+/*---------------------------------
+
+  qh_KEEPstatistics
+    0 turns off statistic reporting and gathering (except zzdef/zzinc/zzadd/zzval/wwval)
+
+  set qh_KEEPstatistics in user_r.h to 0 to turn off statistics
+*/
+#ifndef qh_KEEPstatistics
+#define qh_KEEPstatistics 1
+#endif
+
+/*---------------------------------
+
+  Zxxx for integers, Wxxx for reals
+
+  notes:
+    be sure that all statistics are defined in stat_r.c
+      otherwise initialization may core dump
+    can pick up all statistics by:
+      grep '[zw].*_[(][ZW]' *.c >z.x
+    remove trailers with query">-
+    remove leaders with  query-replace-regexp [ ^I]+  (
+*/
+#if qh_KEEPstatistics
+enum qh_statistics {     /* alphabetical after Z/W */
+    Zacoplanar,
+    Wacoplanarmax,
+    Wacoplanartot,
+    Zangle,
+    Wangle,
+    Wanglemax,
+    Wanglemin,
+    Zangletests,
+    Wareatot,
+    Wareamax,
+    Wareamin,
+    Zavoidold,
+    Wavoidoldmax,
+    Wavoidoldtot,
+    Zback0,
+    Zbestcentrum,
+    Zbestdist,
+    Zbestlower,
+    Zbestlowerall,
+    Zbestloweralln,
+    Zbestlowerv,
+    Zcentrumtests,
+    Zcheckpart,
+    Zcomputefurthest,
+    Zconcave,
+    Wconcavemax,
+    Wconcavetot,
+    Zconcavecoplanar,
+    Wconcavecoplanarmax,
+    Wconcavecoplanartot,
+    Zconcavecoplanarridge,
+    Zconcaveridge,
+    Zconcaveridges,
+    Zcoplanar,
+    Wcoplanarmax,
+    Wcoplanartot,
+    Zcoplanarangle,
+    Zcoplanarcentrum,
+    Zcoplanarhorizon,
+    Zcoplanarinside,
+    Zcoplanarpart,
+    Zcoplanarridges,
+    Wcpu,
+    Zcyclefacetmax,
+    Zcyclefacettot,
+    Zcyclehorizon,
+    Zcyclevertex,
+    Zdegen,
+    Wdegenmax,
+    Wdegentot,
+    Zdegenvertex,
+    Zdelfacetdup,
+    Zdelridge,
+    Zdelvertextot,
+    Zdelvertexmax,
+    Zdetfacetarea,
+    Zdetsimplex,
+    Zdistcheck,
+    Zdistconvex,
+    Zdistgood,
+    Zdistio,
+    Zdistplane,
+    Zdiststat,
+    Zdistvertex,
+    Zdistzero,
+    Zdoc1,
+    Zdoc2,
+    Zdoc3,
+    Zdoc4,
+    Zdoc5,
+    Zdoc6,
+    Zdoc7,
+    Zdoc8,
+    Zdoc9,
+    Zdoc10,
+    Zdoc11,
+    Zdoc12,
+    Zdropdegen,
+    Zdropneighbor,
+    Zdupflip,
+    Zduplicate,
+    Wduplicatemax,
+    Wduplicatetot,
+    Zdupsame,
+    Zflipped,
+    Wflippedmax,
+    Wflippedtot,
+    Zflippedfacets,
+    Zflipridge,
+    Zflipridge2,
+    Zfindbest,
+    Zfindbestmax,
+    Zfindbesttot,
+    Zfindcoplanar,
+    Zfindfail,
+    Zfindhorizon,
+    Zfindhorizonmax,
+    Zfindhorizontot,
+    Zfindjump,
+    Zfindnew,
+    Zfindnewmax,
+    Zfindnewtot,
+    Zfindnewjump,
+    Zfindnewsharp,
+    Zgauss0,
+    Zgoodfacet,
+    Zhashlookup,
+    Zhashridge,
+    Zhashridgetest,
+    Zhashtests,
+    Zinsidevisible,
+    Zintersect,
+    Zintersectfail,
+    Zintersectmax,
+    Zintersectnum,
+    Zintersecttot,
+    Zmaxneighbors,
+    Wmaxout,
+    Wmaxoutside,
+    Zmaxridges,
+    Zmaxvertex,
+    Zmaxvertices,
+    Zmaxvneighbors,
+    Zmemfacets,
+    Zmempoints,
+    Zmemridges,
+    Zmemvertices,
+    Zmergeflipdup,
+    Zmergehorizon,
+    Zmergeinittot,
+    Zmergeinitmax,
+    Zmergeinittot2,
+    Zmergeintocoplanar,
+    Zmergeintohorizon,
+    Zmergenew,
+    Zmergesettot,
+    Zmergesetmax,
+    Zmergesettot2,
+    Zmergesimplex,
+    Zmergevertex,
+    Wmindenom,
+    Wminvertex,
+    Zminnorm,
+    Zmultiridge,
+    Znearlysingular,
+    Zredundant,
+    Wnewbalance,
+    Wnewbalance2,
+    Znewbesthorizon,
+    Znewfacettot,
+    Znewfacetmax,
+    Znewvertex,
+    Wnewvertex,
+    Wnewvertexmax,
+    Znewvertexridge,
+    Znoarea,
+    Znonsimplicial,
+    Znowsimplicial,
+    Znotgood,
+    Znotgoodnew,
+    Znotmax,
+    Znumfacets,
+    Znummergemax,
+    Znummergetot,
+    Znumneighbors,
+    Znumridges,
+    Znumvertices,
+    Znumvisibility,
+    Znumvneighbors,
+    Zonehorizon,
+    Zpartangle,
+    Zpartcoplanar,
+    Zpartcorner,
+    Zparthidden,
+    Zpartinside,
+    Zpartition,
+    Zpartitionall,
+    Zpartnear,
+    Zparttwisted,
+    Zpbalance,
+    Wpbalance,
+    Wpbalance2,
+    Zpinchduplicate,
+    Zpinchedapex,
+    Zpinchedvertex,
+    Zpostfacets,
+    Zpremergetot,
+    Zprocessed,
+    Zremvertex,
+    Zremvertexdel,
+    Zredundantmerge,
+    Zrenameall,
+    Zrenamepinch,
+    Zrenameshare,
+    Zretry,
+    Wretrymax,
+    Zretryadd,
+    Zretryaddmax,
+    Zretryaddtot,
+    Zridge,
+    Wridge,
+    Wridgemax,
+    Zridge0,
+    Wridge0,
+    Wridge0max,
+    Zridgemid,
+    Wridgemid,
+    Wridgemidmax,
+    Zridgeok,
+    Wridgeok,
+    Wridgeokmax,
+    Zsearchpoints,
+    Zsetplane,
+    Ztestvneighbor,
+    Ztotcheck,
+    Ztothorizon,
+    Ztotmerge,
+    Ztotpartcoplanar,
+    Ztotpartition,
+    Ztotridges,
+    Ztotvertices,
+    Ztotvisible,
+    Ztricoplanar,
+    Ztricoplanarmax,
+    Ztricoplanartot,
+    Ztridegen,
+    Ztrimirror,
+    Ztrinull,
+    Ztwisted,
+    Wtwistedtot,
+    Wtwistedmax,
+    Ztwistedridge,
+    Zvertextests,
+    Wvertexmax,
+    Wvertexmin,
+    Zvertexridge,
+    Zvertexridgetot,
+    Zvertexridgemax,
+    Zvertices,
+    Zvisfacettot,
+    Zvisfacetmax,
+    Zvisit,
+    Zvisit2max,
+    Zvisvertextot,
+    Zvisvertexmax,
+    Zvvisit,
+    Zvvisit2max,
+    Zwidefacet,
+    Zwidevertices,
+    ZEND};
+
+/*---------------------------------
+
+  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
+
+  notes:
+    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
+*/
+#else
+enum qh_statistics {     /* for zzdef etc. macros */
+  Zback0,
+  Zbestdist,
+  Zcentrumtests,
+  Zcheckpart,
+  Zconcaveridges,
+  Zcoplanarhorizon,
+  Zcoplanarpart,
+  Zcoplanarridges,
+  Zcyclefacettot,
+  Zcyclehorizon,
+  Zdelvertextot,
+  Zdistcheck,
+  Zdistconvex,
+  Zdistplane,
+  Zdistzero,
+  Zdoc1,
+  Zdoc2,
+  Zdoc3,
+  Zdoc11,
+  Zflippedfacets,
+  Zflipridge,
+  Zflipridge2,
+  Zgauss0,
+  Zminnorm,
+  Zmultiridge,
+  Znearlysingular,
+  Wnewvertexmax,
+  Znumvisibility,
+  Zpartcoplanar,
+  Zpartition,
+  Zpartitionall,
+  Zpinchduplicate,
+  Zpinchedvertex,
+  Zprocessed,
+  Zretry,
+  Zridge,
+  Wridge,
+  Wridgemax,
+  Zridge0,
+  Wridge0,
+  Wridge0max,
+  Zridgemid,
+  Wridgemid,
+  Wridgemidmax,
+  Zridgeok,
+  Wridgeok,
+  Wridgeokmax,
+  Zsetplane,
+  Ztotcheck,
+  Ztotmerge,
+  Zvertextests,
+  ZEND};
+#endif
+
+/*---------------------------------
+
+  ztype
+    the type of a statistic sets its initial value.
+
+  notes:
+    The type should be the same as the macro for collecting the statistic
+*/
+enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
+
+/*========== macros and constants =============*/
+
+/*----------------------------------
+
+  MAYdebugx
+    define as maydebug() to be called frequently for error trapping
+*/
+#define MAYdebugx
+
+/*----------------------------------
+
+  zzdef_, zdef_( type, name, doc, -1)
+    define a statistic (assumes 'qhstat.next= 0;')
+
+  zdef_( type, name, doc, count)
+    define an averaged statistic
+    printed as name/count
+*/
+#define zzdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
+   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
+#if qh_KEEPstatistics
+#define zdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
+   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
+#else
+#define zdef_(type,name,doc,count)
+#endif
+
+/*----------------------------------
+
+  zzinc_( name ), zinc_( name)
+    increment an integer statistic
+*/
+#define zzinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
+#if qh_KEEPstatistics
+#define zinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
+#else
+#define zinc_(id) {}
+#endif
+
+/*----------------------------------
+
+  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
+    add value to an integer or real statistic
+*/
+#define zzadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
+#define wwadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
+#if qh_KEEPstatistics
+#define zadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
+#define wadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
+#else
+#define zadd_(id, val) {}
+#define wadd_(id, val) {}
+#endif
+
+/*----------------------------------
+
+  zzval_( name ), zval_( name ), wwval_( name )
+    set or return value of a statistic
+*/
+#define zzval_(id) ((qh->qhstat.stats[id]).i)
+#define wwval_(id) ((qh->qhstat.stats[id]).r)
+#if qh_KEEPstatistics
+#define zval_(id) ((qh->qhstat.stats[id]).i)
+#define wval_(id) ((qh->qhstat.stats[id]).r)
+#else
+#define zval_(id) qh->qhstat.tempi
+#define wval_(id) qh->qhstat.tempr
+#endif
+
+/*----------------------------------
+
+  zmax_( id, val ), wmax_( id, value )
+    maximize id with val
+*/
+#define wwmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
+#if qh_KEEPstatistics
+#define zmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].i,(val));}
+#define wmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
+#else
+#define zmax_(id, val) {}
+#define wmax_(id, val) {}
+#endif
+
+/*----------------------------------
+
+  zmin_( id, val ), wmin_( id, value )
+    minimize id with val
+*/
+#if qh_KEEPstatistics
+#define zmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].i,(val));}
+#define wmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].r,(val));}
+#else
+#define zmin_(id, val) {}
+#define wmin_(id, val) {}
+#endif
+
+/*================== stat_r.h types ==============*/
+
+
+/*----------------------------------
+
+  intrealT
+    union of integer and real, used for statistics
+*/
+typedef union intrealT intrealT;    /* union of int and realT */
+union intrealT {
+    int i;
+    realT r;
+};
+
+/*----------------------------------
+
+  qhstat
+    Data structure for statistics, similar to qh and qhrbox
+
+    Allocated as part of qhT (libqhull_r.h)
+*/
+
+struct qhstatT {
+  intrealT   stats[ZEND];     /* integer and real statistics */
+  unsigned char id[ZEND+10];  /* id's in print order */
+  const char *doc[ZEND];      /* array of documentation strings */
+  short int  count[ZEND];     /* -1 if none, else index of count to use */
+  char       type[ZEND];      /* type, see ztypes above */
+  char       printed[ZEND];   /* true, if statistic has been printed */
+  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
+
+  int        next;            /* next index for zdef_ */
+  int        precision;       /* index for precision problems, printed on qh_errexit and qh_produce_output2/Q0/QJn */
+  int        vridges;         /* index for Voronoi ridges, printed on qh_produce_output2 */
+  int        tempi;
+  realT      tempr;
+};
+
+/*========== function prototypes ===========*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void    qh_allstatA(qhT *qh);
+void    qh_allstatB(qhT *qh);
+void    qh_allstatC(qhT *qh);
+void    qh_allstatD(qhT *qh);
+void    qh_allstatE(qhT *qh);
+void    qh_allstatE2(qhT *qh);
+void    qh_allstatF(qhT *qh);
+void    qh_allstatG(qhT *qh);
+void    qh_allstatH(qhT *qh);
+void    qh_allstatI(qhT *qh);
+void    qh_allstatistics(qhT *qh);
+void    qh_collectstatistics(qhT *qh);
+void    qh_initstatistics(qhT *qh);
+boolT   qh_newstats(qhT *qh, int idx, int *nextindex);
+boolT   qh_nostatistic(qhT *qh, int i);
+void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
+void    qh_printstatistics(qhT *qh, FILE *fp, const char *string);
+void    qh_printstatlevel(qhT *qh, FILE *fp, int id);
+void    qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex);
+realT   qh_stddev(qhT *qh, int num, realT tot, realT tot2, realT *ave);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif   /* qhDEFstat */
diff --git a/vendor/qhull/libqhull_r/user_r.c b/vendor/qhull/libqhull_r/user_r.c
new file mode 100644
index 0000000..70eab8b
--- /dev/null
+++ b/vendor/qhull/libqhull_r/user_r.c
@@ -0,0 +1,617 @@
+/*
  ---------------------------------
+
+   user_r.c
+   user redefinable functions
+
+   see user2_r.c for qh_fprintf, qh_malloc, qh_free
+
+   see README.txt  see COPYING.txt for copyright information.
+
+   see libqhull_r.h for data structures, macros, and user-callable functions.
+
+   see user_eg_r.c, user_eg2_r.c, and unix_r.c for examples.
+
+   see user_r.h for user-definable constants
+
+      use qh_NOmem in mem_r.h to turn off memory management
+      use qh_NOmerge in user_r.h to turn off facet merging
+      set qh_KEEPstatistics in user_r.h to 0 to turn off statistics
+
+   This is unsupported software.  You're welcome to make changes,
+   but you're on your own if something goes wrong.  Use 'Tc' to
+   check frequently.  Usually qhull will report an error if
+   a data structure becomes inconsistent.  If so, it also reports
+   the last point added to the hull, e.g., 102.  You can then trace
+   the execution of qhull with "T4P102".
+
+   Please report any errors that you fix to qhull@qhull.org
+
+   Qhull-template is a template for calling qhull from within your application
+
+   if you recompile and load this module, then user.o will not be loaded
+   from qhull.a
+
+   you can add additional quick allocation sizes in qh_user_memsizes
+
+   if the other functions here are redefined to not use qh_print...,
+   then io.o will not be loaded from qhull.a.  See user_eg_r.c for an
+   example.  We recommend keeping io.o for the extra debugging
+   information it supplies.
+*/
+
+#include "qhull_ra.h"
+
+#include 
+
+/*---------------------------------
+
+  Qhull-template
+    Template for calling qhull from inside your program
+
+  returns:
+    exit code(see qh_ERR... in libqhull_r.h)
+    all memory freed
+
+  notes:
+    This can be called any number of times.
+*/
+#if 0
+{
+  int dim;                  /* dimension of points */
+  int numpoints;            /* number of points */
+  coordT *points;           /* array of coordinates for each point */
+  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
+  char flags[]= "qhull Tv"; /* option flags for qhull, see html/qh-quick.htm */
+  FILE *outfile= stdout;    /* output from qh_produce_output
+                               use NULL to skip qh_produce_output */
+  FILE *errfile= stderr;    /* error messages from qhull code */
+  int exitcode;             /* 0 if no error from qhull */
+  facetT *facet;            /* set by FORALLfacets */
+  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
+
+  qhT qh_qh;                /* Qhull's data structure.  First argument of most calls */
+  qhT *qh= &qh_qh;          /* Alternatively -- qhT *qh= (qhT *)malloc(sizeof(qhT)) */
+
+  QHULL_LIB_CHECK /* Check for compatible library */
+
+  qh_zero(qh, errfile);
+
+  /* initialize dim, numpoints, points[], ismalloc here */
+  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
+                      flags, outfile, errfile);
+  if (!exitcode) {                  /* if no error */
+    /* 'qh->facet_list' contains the convex hull */
+    FORALLfacets {
+       /* ... your code ... */
+    }
+  }
+  qh_freeqhull(qh, !qh_ALL);
+  qh_memfreeshort(qh, &curlong, &totlong);
+  if (curlong || totlong)
+    qh_fprintf(qh, errfile, 7079, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
+}
+#endif
+
+/*---------------------------------
+
+  qh_new_qhull(qh, dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
+    Run qhull
+    Before first call, either call qh_zero(qh, errfile), or set qh to all zero.
+
+  returns:
+    results in qh
+    exitcode (0 if no errors).
+
+  notes:
+    do not modify points until finished with results.
+      The qhull data structure contains pointers into the points array.
+    do not call qhull functions before qh_new_qhull().
+      The qhull data structure is not initialized until qh_new_qhull().
+    do not call qh_init_A (global_r.c)
+
+    Default errfile is stderr, outfile may be null
+    qhull_cmd must start with "qhull "
+    projects points to a new point array for Delaunay triangulations ('d' and 'v')
+    transforms points into a new point array for halfspace intersection ('H')
+
+  see:
+    Qhull-template at the beginning of this file.
+    An example of using qh_new_qhull is user_eg_r.c
+*/
+int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
+                char *qhull_cmd, FILE *outfile, FILE *errfile) {
+  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
+     These parameters are not referenced after a longjmp() and hence not clobbered.
+     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
+  int exitcode, hulldim;
+  boolT new_ismalloc;
+  coordT *new_points;
+
+  if(!errfile){
+    errfile= stderr;
+  }
+  if (!qh->qhmem.ferr) {
+    qh_meminit(qh, errfile);
+  } else {
+    qh_memcheck(qh);
+  }
+  if (strncmp(qhull_cmd, "qhull ", (size_t)6) && strcmp(qhull_cmd, "qhull") != 0) {
+    qh_fprintf(qh, errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \" or set to \"qhull\"\n");
+    return qh_ERRinput;
+  }
+  qh_initqhull_start(qh, NULL, outfile, errfile);
+  if(numpoints==0 && points==NULL){
+      trace1((qh, qh->ferr, 1047, "qh_new_qhull: initialize Qhull\n"));
+      return 0;
+  }
+  trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
+  exitcode= setjmp(qh->errexit);
+  if (!exitcode) {
+    qh->NOerrexit= False;
+    qh_initflags(qh, qhull_cmd);
+    if (qh->DELAUNAY)
+      qh->PROJECTdelaunay= True;
+    if (qh->HALFspace) {
+      /* points is an array of halfspaces,
+         the last coordinate of each halfspace is its offset */
+      hulldim= dim-1;
+      qh_setfeasible(qh, hulldim);
+      new_points= qh_sethalfspace_all(qh, dim, numpoints, points, qh->feasible_point);
+      new_ismalloc= True;
+      if (ismalloc)
+        qh_free(points);
+    }else {
+      hulldim= dim;
+      new_points= points;
+      new_ismalloc= ismalloc;
+    }
+    qh_init_B(qh, new_points, numpoints, hulldim, new_ismalloc);
+    qh_qhull(qh);
+    qh_check_output(qh);
+    if (outfile) {
+      qh_produce_output(qh);
+    }else {
+      qh_prepare_output(qh);
+    }
+    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPadd && !qh->STOPcone && !qh->STOPpoint)
+      qh_check_points(qh);
+  }
+  qh->NOerrexit= True;
+  return exitcode;
+} /* new_qhull */
+
+/*---------------------------------
+
+  qh_errexit(qh, exitcode, facet, ridge )
+    report and exit from an error
+    report facet and ridge if non-NULL
+    reports useful information such as last point processed
+    set qh.FORCEoutput to print neighborhood of facet
+
+  see:
+    qh_errexit2() in libqhull_r.c for printing 2 facets
+
+  design:
+    check for error within error processing
+    compute qh.hulltime
+    print facet and ridge (if any)
+    report commandString, options, qh.furthest_id
+    print summary and statistics (including precision statistics)
+    if qh_ERRsingular
+      print help text for singular data set
+    exit program via long jump (if defined) or exit()
+*/
+void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
+
+  qh->tracefacet= NULL;  /* avoid infinite recursion through qh_fprintf */
+  qh->traceridge= NULL;
+  qh->tracevertex= NULL;
+  if (qh->ERREXITcalled) {
+    qh_fprintf(qh, qh->ferr, 8126, "\nqhull error while handling previous error in qh_errexit.  Exit program\n");
+    qh_exit(qh_ERRother);
+  }
+  qh->ERREXITcalled= True;
+  if (!qh->QHULLfinished)
+    qh->hulltime= qh_CPUclock - qh->hulltime;
+  qh_errprint(qh, "ERRONEOUS", facet, NULL, ridge, NULL);
+  qh_option(qh, "_maxoutside", NULL, &qh->MAXoutside);
+  qh_fprintf(qh, qh->ferr, 8127, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
+  qh_fprintf(qh, qh->ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
+  if (qh->furthest_id >= 0) {
+    qh_fprintf(qh, qh->ferr, 8129, "Last point added to hull was p%d.", qh->furthest_id);
+    if (zzval_(Ztotmerge))
+      qh_fprintf(qh, qh->ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
+    if (qh->QHULLfinished)
+      qh_fprintf(qh, qh->ferr, 8131, "\nQhull has finished constructing the hull.");
+    else if (qh->POSTmerging)
+      qh_fprintf(qh, qh->ferr, 8132, "\nQhull has started post-merging.");
+    qh_fprintf(qh, qh->ferr, 8133, "\n");
+  }
+  if (qh->FORCEoutput && (qh->QHULLfinished || (!facet && !ridge)))
+    qh_produce_output(qh);
+  else if (exitcode != qh_ERRinput) {
+    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh->hull_dim+1) {
+      qh_fprintf(qh, qh->ferr, 8134, "\nAt error exit:\n");
+      qh_printsummary(qh, qh->ferr);
+      if (qh->PRINTstatistics) {
+        qh_collectstatistics(qh);
+        qh_allstatistics(qh);
+        qh_printstatistics(qh, qh->ferr, "at error exit");
+        qh_memstatistics(qh, qh->ferr);
+      }
+    }
+    if (qh->PRINTprecision)
+      qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
+  }
+  if (!exitcode)
+    exitcode= qh_ERRother;
+  else if (exitcode == qh_ERRprec && !qh->PREmerge)
+    qh_printhelp_degenerate(qh, qh->ferr);
+  else if (exitcode == qh_ERRqhull)
+    qh_printhelp_internal(qh, qh->ferr);
+  else if (exitcode == qh_ERRsingular)
+    qh_printhelp_singular(qh, qh->ferr);
+  else if (exitcode == qh_ERRdebug)
+    qh_fprintf(qh, qh->ferr, 8016, "qhull exit due to qh_ERRdebug\n");
+  else if (exitcode == qh_ERRtopology || exitcode == qh_ERRwide || exitcode == qh_ERRprec) {
+    if (qh->NOpremerge && !qh->MERGING)
+      qh_printhelp_degenerate(qh, qh->ferr);
+    else if (exitcode == qh_ERRtopology)
+      qh_printhelp_topology(qh, qh->ferr);
+    else if (exitcode == qh_ERRwide)
+      qh_printhelp_wide(qh, qh->ferr);
+  }else if (exitcode > 255) {
+    qh_fprintf(qh, qh->ferr, 6426, "qhull internal error (qh_errexit): exit code %d is greater than 255.  Invalid argument for exit().  Replaced with 255\n", exitcode);
+    exitcode= 255;
+  }
+  if (qh->NOerrexit) {
+    qh_fprintf(qh, qh->ferr, 6187, "qhull internal error (qh_errexit): either error while reporting error QH%d, or qh.NOerrexit not cleared after setjmp(). Exit program with error status %d\n",
+         qh->last_errcode, exitcode);
+    qh_exit(exitcode);
+  }
+  qh->ERREXITcalled= False;
+  qh->NOerrexit= True;
+  qh->ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
+  longjmp(qh->errexit, exitcode);
+} /* errexit */
+
+/*---------------------------------
+
+  qh_errprint(qh, fp, string, atfacet, otherfacet, atridge, atvertex )
+    prints out the information of facets and ridges to fp
+    also prints neighbors and geomview output
+
+  notes:
+    except for string, any parameter may be NULL
+*/
+void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
+  int i;
+
+  if (atvertex) {
+    qh_fprintf(qh, qh->ferr, 8138, "%s VERTEX:\n", string);
+    qh_printvertex(qh, qh->ferr, atvertex);
+  }
+  if (atridge) {
+    qh_fprintf(qh, qh->ferr, 8137, "%s RIDGE:\n", string);
+    qh_printridge(qh, qh->ferr, atridge);
+    if (!atfacet)
+      atfacet= atridge->top;
+    if (!otherfacet)
+      otherfacet= otherfacet_(atridge, atfacet);
+    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
+      qh_printfacet(qh, qh->ferr, atridge->top);
+    if (atridge->bottom && atridge->bottom != atfacet && atridge->bottom != otherfacet)
+      qh_printfacet(qh, qh->ferr, atridge->bottom);
+  }
+  if (atfacet) {
+    qh_fprintf(qh, qh->ferr, 8135, "%s FACET:\n", string);
+    qh_printfacet(qh, qh->ferr, atfacet);
+  }
+  if (otherfacet) {
+    qh_fprintf(qh, qh->ferr, 8136, "%s OTHER FACET:\n", string);
+    qh_printfacet(qh, qh->ferr, otherfacet);
+  }
+  if (qh->fout && qh->FORCEoutput && atfacet && !qh->QHULLfinished && !qh->IStracing) {
+    qh_fprintf(qh, qh->ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
+    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
+      qh_printneighborhood(qh, qh->fout, qh->PRINTout[i], atfacet, otherfacet,
+                            !qh_ALL);
+  }
+} /* errprint */
+
+
+/*---------------------------------
+
+  qh_printfacetlist(qh, fp, facetlist, facets, printall )
+    print all fields for a facet list and/or set of facets to fp
+    if !printall,
+      only prints good facets
+
+  notes:
+    also prints all vertices
+*/
+void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
+  facetT *facet, **facetp;
+
+  if (facetlist)
+    qh_checklists(qh, facetlist);
+  qh_fprintf(qh, qh->ferr, 9424, "printfacetlist: vertices\n");
+  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
+  if (facetlist) {
+    qh_fprintf(qh, qh->ferr, 9413, "printfacetlist: facetlist\n");
+    FORALLfacet_(facetlist)
+      qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
+  }
+  if (facets) {
+    qh_fprintf(qh, qh->ferr, 9414, "printfacetlist: %d facets\n", qh_setsize(qh, facets));
+    FOREACHfacet_(facets)
+      qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
+  }
+  qh_fprintf(qh, qh->ferr, 9412, "printfacetlist: end\n");
+  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
+} /* printfacetlist */
+
+
+/*---------------------------------
+
+  qh_printhelp_degenerate(qh, fp )
+    prints descriptive message for precision error with qh_ERRprec
+
+  notes:
+    no message if qh_QUICKhelp
+*/
+void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
+
+  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
+    qh_fprintf(qh, fp, 9368, "\n\
+A Qhull error has occurred.  Qhull should have corrected the above\n\
+precision error.  Please send the input and all of the output to\n\
+qhull_bug@qhull.org\n");
+  else if (!qh_QUICKhelp) {
+    qh_fprintf(qh, fp, 9369, "\n\
+Precision problems were detected during construction of the convex hull.\n\
+This occurs because convex hull algorithms assume that calculations are\n\
+exact, but floating-point arithmetic has roundoff errors.\n\
+\n\
+To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
+selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
+Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
+in Qhull\" (qh-impre.htm).\n\
+\n\
+If you use 'Q0', the output may include\n\
+coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
+Qhull may produce a ridge with four neighbors or two facets with the same \n\
+vertices.  Qhull reports these events when they occur.  It stops when a\n\
+concave ridge, flipped facet, or duplicate facet occurs.\n");
+#if REALfloat
+    qh_fprintf(qh, fp, 9370, "\
+\n\
+Qhull is currently using single precision arithmetic.  The following\n\
+will probably remove the precision problems:\n\
+  - recompile qhull for realT precision(#define REALfloat 0 in user_r.h).\n");
+#endif
+    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
+      qh_fprintf(qh, fp, 9371, "\
+\n\
+When computing the Delaunay triangulation of coordinates > 1.0,\n\
+  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
+    if (qh->DELAUNAY && !qh->ATinfinity)
+      qh_fprintf(qh, fp, 9372, "\
+When computing the Delaunay triangulation:\n\
+  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
+
+    qh_fprintf(qh, fp, 9373, "\
+\n\
+If you need triangular output:\n\
+  - use option 'Qt' to triangulate the output\n\
+  - use option 'QJ' to joggle the input points and remove precision errors\n\
+  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
+\n\
+If you must use 'Q0',\n\
+try one or more of the following options.  They can not guarantee an output.\n\
+  - use 'QbB' to scale the input to a cube.\n\
+  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
+  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
+  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
+  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
+               qh->DISTround);
+    qh_fprintf(qh, fp, 9374, "\
+\n\
+To guarantee simplicial output:\n\
+  - use option 'Qt' to triangulate the output\n\
+  - use option 'QJ' to joggle the input points and remove precision errors\n\
+  - use option 'Ft' to triangulate the output by adding points\n\
+  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
+");
+  }
+} /* printhelp_degenerate */
+
+/*---------------------------------
+
+  qh_printhelp_internal(qh, fp )
+    prints descriptive message for qhull internal error with qh_ERRqhull
+
+  notes:
+    no message if qh_QUICKhelp
+*/
+void qh_printhelp_internal(qhT *qh, FILE *fp) {
+
+  if (!qh_QUICKhelp) {
+    qh_fprintf(qh, fp, 9426, "\n\
+A Qhull internal error has occurred.  Please send the input and output to\n\
+qhull_bug@qhull.org. If you can duplicate the error with logging ('T4z'), please\n\
+include the log file.\n");
+  }
+} /* printhelp_internal */
+
+/*---------------------------------
+
+  qh_printhelp_narrowhull(qh, minangle )
+    Warn about a narrow hull
+
+  notes:
+    Alternatively, reduce qh_WARNnarrow in user_r.h
+
+*/
+void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
+
+    qh_fprintf(qh, fp, 7089, "qhull precision warning: The initial hull is narrow.  Is the input lower\n\
+dimensional (e.g., a square in 3-d instead of a cube)?  Cosine of the minimum\n\
+angle is %.16f.  If so, Qhull may produce a wide facet.\n\
+Options 'Qs' (search all points), 'Qbb' (scale last coordinate), or\n\
+'QbB' (scale to unit box) may remove this warning.\n\
+See 'Limitations' in qh-impre.htm.  Use 'Pp' to skip this warning.\n",
+          -minangle);   /* convert from angle between normals to angle between facets */
+} /* printhelp_narrowhull */
+
+/*---------------------------------
+
+  qh_printhelp_singular(qh, fp )
+    prints descriptive message for singular input
+*/
+void qh_printhelp_singular(qhT *qh, FILE *fp) {
+  facetT *facet;
+  vertexT *vertex, **vertexp;
+  realT min, max, *coord, dist;
+  int i,k;
+
+  qh_fprintf(qh, fp, 9376, "\n\
+The input to qhull appears to be less than %d dimensional, or a\n\
+computation has overflowed.\n\n\
+Qhull could not construct a clearly convex simplex from points:\n",
+           qh->hull_dim);
+  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
+  if (!qh_QUICKhelp)
+    qh_fprintf(qh, fp, 9377, "\n\
+The center point is coplanar with a facet, or a vertex is coplanar\n\
+with a neighboring facet.  The maximum round off error for\n\
+computing distances is %2.2g.  The center point, facets and distances\n\
+to the center point are as follows:\n\n", qh->DISTround);
+  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, qh_IDunknown);
+  qh_fprintf(qh, fp, 9378, "\n");
+  FORALLfacets {
+    qh_fprintf(qh, fp, 9379, "facet");
+    FOREACHvertex_(facet->vertices)
+      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
+    zinc_(Zdistio);
+    qh_distplane(qh, qh->interior_point, facet, &dist);
+    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
+  }
+  if (!qh_QUICKhelp) {
+    if (qh->HALFspace)
+      qh_fprintf(qh, fp, 9382, "\n\
+These points are the dual of the given halfspaces.  They indicate that\n\
+the intersection is degenerate.\n");
+    qh_fprintf(qh, fp, 9383,"\n\
+These points either have a maximum or minimum x-coordinate, or\n\
+they maximize the determinant for k coordinates.  Trial points\n\
+are first selected from points that maximize a coordinate.\n");
+    if (qh->hull_dim >= qh_INITIALmax)
+      qh_fprintf(qh, fp, 9384, "\n\
+Because of the high dimension, the min x-coordinate and max-coordinate\n\
+points are used if the determinant is non-zero.  Option 'Qs' will\n\
+do a better, though much slower, job.  Instead of 'Qs', you can change\n\
+the points by randomly rotating the input with 'QR0'.\n");
+  }
+  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
+  for (k=0; k < qh->hull_dim; k++) {
+    min= REALmax;
+    max= -REALmax;
+    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
+      maximize_(max, *coord);
+      minimize_(min, *coord);
+    }
+    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
+  }
+  if (!qh_QUICKhelp) {
+    qh_fprintf(qh, fp, 9387, "\n\
+If the input should be full dimensional, you have several options that\n\
+may determine an initial simplex:\n\
+  - use 'QJ'  to joggle the input and make it full dimensional\n\
+  - use 'QbB' to scale the points to the unit cube\n\
+  - use 'QR0' to randomly rotate the input for different maximum points\n\
+  - use 'Qs'  to search all points for the initial simplex\n\
+  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
+  - trace execution with 'T3' to see the determinant for each point.\n",
+                     qh->DISTround);
+#if REALfloat
+    qh_fprintf(qh, fp, 9388, "\
+  - recompile qhull for realT precision(#define REALfloat 0 in libqhull_r.h).\n");
+#endif
+    qh_fprintf(qh, fp, 9389, "\n\
+If the input is lower dimensional:\n\
+  - use 'QJ' to joggle the input and make it full dimensional\n\
+  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
+    pick the coordinate with the least range.  The hull will have the\n\
+    correct topology.\n\
+  - determine the flat containing the points, rotate the points\n\
+    into a coordinate plane, and delete the other coordinates.\n\
+  - add one or more points to make the input full dimensional.\n\
+");
+  }
+} /* printhelp_singular */
+
+/*---------------------------------
+
+  qh_printhelp_topology(qh, fp )
+    prints descriptive message for qhull topology error with qh_ERRtopology
+
+  notes:
+    no message if qh_QUICKhelp
+*/
+void qh_printhelp_topology(qhT *qh, FILE *fp) {
+
+  if (!qh_QUICKhelp) {
+    qh_fprintf(qh, fp, 9427, "\n\
+A Qhull topology error has occurred.  Qhull did not recover from facet merges and vertex merges.\n\
+This usually occurs when the input is nearly degenerate and substantial merging has occurred.\n\
+See http://www.qhull.org/html/qh-impre.htm#limit\n");
+  }
+} /* printhelp_topology */
+
+/*---------------------------------
+
+  qh_printhelp_wide(qh, fp )
+    prints descriptive message for qhull wide facet with qh_ERRwide
+
+  notes:
+    no message if qh_QUICKhelp
+*/
+void qh_printhelp_wide(qhT *qh, FILE *fp) {
+
+  if (!qh_QUICKhelp) {
+    qh_fprintf(qh, fp, 9428, "\n\
+A wide merge error has occurred.  Qhull has produced a wide facet due to facet merges and vertex merges.\n\
+This usually occurs when the input is nearly degenerate and substantial merging has occurred.\n\
+See http://www.qhull.org/html/qh-impre.htm#limit\n");
+  }
+} /* printhelp_wide */
+
+/*---------------------------------
+
+  qh_user_memsizes(qh)
+    allocate up to 10 additional, quick allocation sizes
+
+  notes:
+    increase maximum number of allocations in qh_initqhull_mem()
+*/
+void qh_user_memsizes(qhT *qh) {
+
+  QHULL_UNUSED(qh)
+  /* qh_memsize(qh, size); */
+} /* user_memsizes */
+
+
diff --git a/vendor/qhull/libqhull_r/user_r.h b/vendor/qhull/libqhull_r/user_r.h
new file mode 100644
index 0000000..523c179
--- /dev/null
+++ b/vendor/qhull/libqhull_r/user_r.h
@@ -0,0 +1,1061 @@
+/*
  ---------------------------------
+
+   user_r.h
+   user redefinable constants
+
+   for each source file, user_r.h is included first
+
+   see qh-user_r.htm.  see COPYING for copyright information.
+
+   See user_r.c for sample code.
+
+   before reading any code, review libqhull_r.h for data structure definitions
+
+Sections:
+   ============= qhull library constants ======================
+   ============= data types and configuration macros ==========
+   ============= performance related constants ================
+   ============= memory constants =============================
+   ============= joggle constants =============================
+   ============= conditional compilation ======================
+   ============= merge constants ==============================
+   ============= Microsoft DevStudio ==========================
+
+Code flags --
+  NOerrors -- the code does not call qh_errexit()
+  WARN64 -- the code may be incompatible with 64-bit pointers
+
+*/
+
+#include 
+#include 
+#include 
+
+#ifndef qhDEFuser
+#define qhDEFuser 1
+
+/* Derived from Qt's corelib/global/qglobal.h */
+#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
+#   define QHULL_OS_WIN
+#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
+#   define QHULL_OS_WIN
+#endif
+
+/*============================================================*/
+/*============= qhull library constants ======================*/
+/*============================================================*/
+
+/*----------------------------------
+
+  FILENAMElen -- max length for TI and TO filenames
+
+*/
+
+#define qh_FILENAMElen 500
+
+/*----------------------------------
+
+  msgcode -- Unique message codes for qh_fprintf
+
+  If add new messages, assign these values and increment in user.h and user_r.h
+  See QhullError.h for 10000 error codes.
+  Cannot use '0031' since it would be octal
+
+  def counters =  [31/32/33/38, 1067, 2113, 3079, 4097, 5006,
+     6429, 7027/7028/7035/7068/7070/7102, 8163, 9428, 10000, 11034]
+
+  See: qh_ERR* [libqhull_r.h]
+*/
+
+#define MSG_TRACE0     0   /* always include if logging ('Tn') */
+#define MSG_TRACE1  1000
+#define MSG_TRACE2  2000
+#define MSG_TRACE3  3000
+#define MSG_TRACE4  4000
+#define MSG_TRACE5  5000
+#define MSG_ERROR   6000   /* errors written to qh.ferr */
+#define MSG_WARNING 7000
+#define MSG_STDERR  8000   /* log messages Written to qh.ferr */
+#define MSG_OUTPUT  9000
+#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
+#define MSG_FIX    11000   /* Document as 'QH11... FIX: ...' */
+#define MSG_MAXLEN  3000   /* qh_printhelp_degenerate() in user_r.c */
+
+
+/*----------------------------------
+
+  qh_OPTIONline -- max length of an option line 'FO'
+*/
+#define qh_OPTIONline 80
+
+/*============================================================*/
+/*============= data types and configuration macros ==========*/
+/*============================================================*/
+
+/*----------------------------------
+
+  realT
+    set the size of floating point numbers
+
+  qh_REALdigits
+    maximum number of significant digits
+
+  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
+    format strings for printf
+
+  REALmax, REALmin
+    maximum and minimum (near zero) values
+
+  REALepsilon
+    machine roundoff.  Maximum roundoff error for addition and multiplication.
+
+  notes:
+   Select whether to store floating point numbers in single precision (float)
+   or double precision (double).
+
+   Use 'float' to save about 8% in time and 25% in space.  This is particularly
+   helpful if high-d where convex hulls are space limited.  Using 'float' also
+   reduces the printed size of Qhull's output since numbers have 8 digits of
+   precision.
+
+   Use 'double' when greater arithmetic precision is needed.  This is needed
+   for Delaunay triangulations and Voronoi diagrams when you are not merging
+   facets.
+
+   If 'double' gives insufficient precision, your data probably includes
+   degeneracies.  If so you should use facet merging (done by default)
+   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
+   You may also use option 'Po' to force output despite precision errors.
+
+   You may use 'long double', but many format statements need to be changed
+   and you may need a 'long double' square root routine.  S. Grundmann
+   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
+   much slower with little gain in precision.
+
+   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
+      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
+
+   REALfloat =   1      all numbers are 'float' type
+             =   0      all numbers are 'double' type
+*/
+#define REALfloat 0
+
+#if (REALfloat == 1)
+#define realT float
+#define REALmax FLT_MAX
+#define REALmin FLT_MIN
+#define REALepsilon FLT_EPSILON
+#define qh_REALdigits 8   /* maximum number of significant digits */
+#define qh_REAL_1 "%6.8g "
+#define qh_REAL_2n "%6.8g %6.8g\n"
+#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
+
+#elif (REALfloat == 0)
+#define realT double
+#define REALmax DBL_MAX
+#define REALmin DBL_MIN
+#define REALepsilon DBL_EPSILON
+#define qh_REALdigits 16    /* maximum number of significant digits */
+#define qh_REAL_1 "%6.16g "
+#define qh_REAL_2n "%6.16g %6.16g\n"
+#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
+
+#else
+#error unknown float option
+#endif
+
+/*----------------------------------
+
+  countT
+    The type for counts and identifiers (e.g., the number of points, vertex identifiers)
+    Currently used by C++ code-only.  Decided against using it for setT because most sets are small.
+
+    Defined as 'int' for C-code compatibility and QH11026
+
+    QH11026 FIX: countT may be defined as a 'unsigned int', but several code issues need to be solved first.  See countT in Changes.txt
+*/
+
+#ifndef DEFcountT
+#define DEFcountT 1
+typedef int countT;
+#endif
+#define COUNTmax INT_MAX
+
+/*----------------------------------
+
+  qh_POINTSmax
+    Maximum number of points for qh.num_points and point allocation in qh_readpoints
+*/
+#define qh_POINTSmax (INT_MAX-16)
+
+/*----------------------------------
+
+  qh_CPUclock
+    define the clock() function for reporting the total time spent by Qhull
+    returns CPU ticks as a 'long int'
+    qh_CPUclock is only used for reporting the total time spent by Qhull
+
+  qh_SECticks
+    the number of clock ticks per second
+
+  notes:
+    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
+    to define a custom clock, set qh_CLOCKtype to 0
+
+    if your system does not use clock() to return CPU ticks, replace
+    qh_CPUclock with the corresponding function.  It is converted
+    to 'unsigned long' to prevent wrap-around during long runs.  By default,
+     defines clock_t as 'long'
+
+   Set qh_CLOCKtype to
+
+     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
+                Note:  may fail if more than 1 hour elapsed time
+
+     2          use qh_clock() with POSIX times() (see global_r.c)
+*/
+#define qh_CLOCKtype 1  /* change to the desired number */
+
+#if (qh_CLOCKtype == 1)
+
+#if defined(CLOCKS_PER_SECOND)
+#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock, may be converted to approximate double */
+#define qh_SECticks CLOCKS_PER_SECOND
+
+#elif defined(CLOCKS_PER_SEC)
+#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock, may be converted to approximate double */
+#define qh_SECticks CLOCKS_PER_SEC
+
+#elif defined(CLK_TCK)
+#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock, may be converted to approximate double */
+#define qh_SECticks CLK_TCK
+
+#else
+#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock, may be converted to approximate double */
+#define qh_SECticks 1E6
+#endif
+
+#elif (qh_CLOCKtype == 2)
+#define qh_CPUclock    qh_clock()  /* return CPU clock, may be converted to approximate double */
+#define qh_SECticks 100
+
+#else /* qh_CLOCKtype == ? */
+#error unknown clock option
+#endif
+
+/*----------------------------------
+
+  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
+    define random number generator
+
+    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
+    qh_RANDOMseed sets the random number seed for qh_RANDOMint
+
+  Set qh_RANDOMtype (default 5) to:
+    1       for random() with 31 bits (UCB)
+    2       for rand() with RAND_MAX or 15 bits (system 5)
+    3       for rand() with 31 bits (Sun)
+    4       for lrand48() with 31 bits (Solaris)
+    5       for qh_rand(qh) with 31 bits (included with Qhull, requires 'qh')
+
+  notes:
+    Random numbers are used by rbox to generate point sets.  Random
+    numbers are used by Qhull to rotate the input ('QRn' option),
+    simulate a randomized algorithm ('Qr' option), and to simulate
+    roundoff errors ('Rn' option).
+
+    Random number generators differ between systems.  Most systems provide
+    rand() but the period varies.  The period of rand() is not critical
+    since qhull does not normally use random numbers.
+
+    The default generator is Park & Miller's minimal standard random
+    number generator [CACM 31:1195 '88].  It is included with Qhull.
+
+    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
+    output will likely be invisible.
+*/
+#define qh_RANDOMtype 5   /* *** change to the desired number *** */
+
+#if (qh_RANDOMtype == 1)
+#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
+#define qh_RANDOMint random()
+#define qh_RANDOMseed_(qh, seed) srandom(seed);
+
+#elif (qh_RANDOMtype == 2)
+#ifdef RAND_MAX
+#define qh_RANDOMmax ((realT)RAND_MAX)
+#else
+#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
+#endif
+#define qh_RANDOMint  rand()
+#define qh_RANDOMseed_(qh, seed) srand((unsigned int)seed);
+
+#elif (qh_RANDOMtype == 3)
+#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
+#define qh_RANDOMint  rand()
+#define qh_RANDOMseed_(qh, seed) srand((unsigned int)seed);
+
+#elif (qh_RANDOMtype == 4)
+#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
+#define qh_RANDOMint lrand48()
+#define qh_RANDOMseed_(qh, seed) srand48(seed);
+
+#elif (qh_RANDOMtype == 5)  /* 'qh' is an implicit parameter */
+#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
+#define qh_RANDOMint qh_rand(qh)
+#define qh_RANDOMseed_(qh, seed) qh_srand(qh, seed);
+/* unlike rand(), never returns 0 */
+
+#else
+#error: unknown random option
+#endif
+
+/*----------------------------------
+
+  qh_ORIENTclock
+    0 for inward pointing normals by Geomview convention
+*/
+#define qh_ORIENTclock 0
+
+/*----------------------------------
+
+  qh_RANDOMdist
+    define for random perturbation of qh_distplane and qh_setfacetplane (qh.RANDOMdist, 'QRn')
+
+  For testing qh.DISTround.  Qhull should not depend on computations always producing the same roundoff error
+
+  #define qh_RANDOMdist 1e-13
+*/
+
+/*============================================================*/
+/*============= joggle constants =============================*/
+/*============================================================*/
+
+/*----------------------------------
+
+  qh_JOGGLEdefault
+    default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
+
+  notes:
+    rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
+    rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
+    rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
+    rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
+    rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
+    rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
+    rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
+    rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
+    rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
+    the later have about 20 points per facet, each of which may interfere
+
+    pick a value large enough to avoid retries on most inputs
+*/
+#define qh_JOGGLEdefault 30000.0
+
+/*----------------------------------
+
+  qh_JOGGLEincrease
+    factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
+*/
+#define qh_JOGGLEincrease 10.0
+
+/*----------------------------------
+
+  qh_JOGGLEretry
+    if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
+
+notes:
+try twice at the original value in case of bad luck the first time
+*/
+#define qh_JOGGLEretry 2
+
+/*----------------------------------
+
+  qh_JOGGLEagain
+    every following qh_JOGGLEagain, increase qh.JOGGLEmax
+
+  notes:
+    1 is OK since it's already failed qh_JOGGLEretry times
+*/
+#define qh_JOGGLEagain 1
+
+/*----------------------------------
+
+  qh_JOGGLEmaxincrease
+    maximum qh.JOGGLEmax due to qh_JOGGLEincrease
+    relative to qh.MAXwidth
+
+  notes:
+    qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
+*/
+#define qh_JOGGLEmaxincrease 1e-2
+
+/*----------------------------------
+
+  qh_JOGGLEmaxretry
+    stop after qh_JOGGLEmaxretry attempts
+*/
+#define qh_JOGGLEmaxretry 50
+
+/*============================================================*/
+/*============= performance related constants ================*/
+/*============================================================*/
+
+/*----------------------------------
+
+  qh_HASHfactor
+    total hash slots / used hash slots.  Must be at least 1.1.
+
+  notes:
+    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
+*/
+#define qh_HASHfactor 2
+
+/*----------------------------------
+
+  qh_VERIFYdirect
+    with 'Tv' verify all points against all facets if op count is smaller
+
+  notes:
+    if greater, calls qh_check_bestdist() instead
+*/
+#define qh_VERIFYdirect 1000000
+
+/*----------------------------------
+
+  qh_INITIALsearch
+     if qh_INITIALmax, search points up to this dimension
+*/
+#define qh_INITIALsearch 6
+
+/*----------------------------------
+
+  qh_INITIALmax
+    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
+
+  notes:
+    from points with non-zero determinants
+    use option 'Qs' to override (much slower)
+*/
+#define qh_INITIALmax 8
+
+/*============================================================*/
+/*============= memory constants =============================*/
+/*============================================================*/
+
+/*----------------------------------
+
+  qh_MEMalign
+    memory alignment for qh_meminitbuffers() in global_r.c
+
+  notes:
+    to avoid bus errors, memory allocation must consider alignment requirements.
+    malloc() automatically takes care of alignment.   Since mem_r.c manages
+    its own memory, we need to explicitly specify alignment in
+    qh_meminitbuffers().
+
+    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
+    do not occur in data structures and pointers are the same size.  Be careful
+    of machines (e.g., DEC Alpha) with large pointers.
+
+    If using gcc, best alignment is [fmax_() is defined in geom_r.h]
+              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
+*/
+#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
+
+/*----------------------------------
+
+  qh_MEMbufsize
+    size of additional memory buffers
+
+  notes:
+    used for qh_meminitbuffers() in global_r.c
+*/
+#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
+
+/*----------------------------------
+
+  qh_MEMinitbuf
+    size of initial memory buffer
+
+  notes:
+    use for qh_meminitbuffers() in global_r.c
+*/
+#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
+
+/*----------------------------------
+
+  qh_INFINITE
+    on output, indicates Voronoi center at infinity
+*/
+#define qh_INFINITE  -10.101
+
+/*----------------------------------
+
+  qh_DEFAULTbox
+    default box size (Geomview expects 0.5)
+
+  qh_DEFAULTbox
+    default box size for integer coorindate (rbox only)
+*/
+#define qh_DEFAULTbox 0.5
+#define qh_DEFAULTzbox 1e6
+
+/*============================================================*/
+/*============= conditional compilation ======================*/
+/*============================================================*/
+
+/*----------------------------------
+
+  __cplusplus
+    defined by C++ compilers
+
+  __MSC_VER
+    defined by Microsoft Visual C++
+
+  __MWERKS__ && __INTEL__
+    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
+
+  __MWERKS__ && __POWERPC__
+    defined by Metrowerks when compiling for PowerPC-based Macintosh
+
+  __STDC__
+    defined for strict ANSI C
+*/
+
+/*----------------------------------
+
+  qh_COMPUTEfurthest
+    compute furthest distance to an outside point instead of storing it with the facet
+    =1 to compute furthest
+
+  notes:
+    computing furthest saves memory but costs time
+      about 40% more distance tests for partitioning
+      removes facet->furthestdist
+*/
+#define qh_COMPUTEfurthest 0
+
+/*----------------------------------
+
+  qh_KEEPstatistics
+    =0 removes most of statistic gathering and reporting
+
+  notes:
+    if 0, code size is reduced by about 4%.
+*/
+#define qh_KEEPstatistics 0
+
+/*----------------------------------
+
+  qh_MAXoutside
+    record outer plane for each facet
+    =1 to record facet->maxoutside
+
+  notes:
+    this takes a realT per facet and slightly slows down qhull
+    it produces better outer planes for geomview output
+*/
+#define qh_MAXoutside 1
+
+/*----------------------------------
+
+  qh_NOmerge
+    disables facet merging if defined
+    For MSVC compiles, use qhull_r-exports-nomerge.def instead of qhull_r-exports.def
+
+  notes:
+    This saves about 25% space, 30% space in combination with qh_NOtrace,
+    and 36% with qh_NOtrace and qh_KEEPstatistics 0
+
+    Unless option 'Q0' is used
+      qh_NOmerge sets 'QJ' to avoid precision errors
+
+  see:
+    qh_NOmem in mem_r.h
+
+    see user_r.c/user_eg.c for removing io_r.o
+
+  #define qh_NOmerge
+*/
+
+/*----------------------------------
+
+  qh_NOtrace
+    no tracing if defined
+    disables 'Tn', 'TMn', 'TPn' and 'TWn'
+    override with 'Qw' for qh_addpoint tracing and various other items
+
+  notes:
+    This saves about 15% space.
+    Removes all traceN((...)) code and substantial sections of qh.IStracing code
+
+  #define qh_NOtrace
+*/
+#define qh_NOtrace
+
+#if 0  /* sample code */
+    exitcode= qh_new_qhull(qhT *qh, dim, numpoints, points, ismalloc,
+                      flags, outfile, errfile);
+    qh_freeqhull(qhT *qh, !qh_ALL); /* frees long memory used by second call */
+    qh_memfreeshort(qhT *qh, &curlong, &totlong);  /* frees short memory and memory allocator */
+#endif
+
+/*----------------------------------
+
+  qh_QUICKhelp
+    =1 to use abbreviated help messages, e.g., for degenerate inputs
+*/
+#define qh_QUICKhelp    0
+
+/*============================================================*/
+/*============= merge constants ==============================*/
+/*============================================================*/
+/*
+   These constants effect facet merging.  You probably will not need
+   to modify them.  They effect the performance of facet merging.
+*/
+
+/*----------------------------------
+
+  qh_BESTcentrum
+     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
+     else, qh_findbestneighbor() tests all vertices (much better merges)
+
+  qh_BESTcentrum2
+     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
+*/
+#define qh_BESTcentrum 20
+#define qh_BESTcentrum2 2
+
+/*----------------------------------
+
+  qh_BESTnonconvex
+    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
+
+  notes:
+    It is needed because qh_findbestneighbor is slow for large facets
+*/
+#define qh_BESTnonconvex 15
+
+/*----------------------------------
+
+  qh_COPLANARratio
+    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
+
+  notes:
+    for non-merging, it's DISTround
+*/
+#define qh_COPLANARratio 3
+
+/*----------------------------------
+
+  qh_DIMmergeVertex
+    max dimension for vertex merging (it is not effective in high-d)
+*/
+#define qh_DIMmergeVertex 6
+
+/*----------------------------------
+
+  qh_DIMreduceBuild
+     max dimension for vertex reduction during build (slow in high-d)
+*/
+#define qh_DIMreduceBuild 5
+
+/*----------------------------------
+
+  qh_DISToutside
+    When is a point clearly outside of a facet?
+    Stops search in qh_findbestnew or qh_partitionall
+    qh_findbest uses qh.MINoutside since since it is only called if no merges.
+
+  notes:
+    'Qf' always searches for best facet
+    if !qh.MERGING, same as qh.MINoutside.
+    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
+      [Note: Zdelvertextot occurs normally with interior points]
+            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
+    When there is a sharp edge, need to move points to a
+    clearly good facet; otherwise may be lost in another partitioning.
+    if too big then O(n^2) behavior for partitioning in cone
+    if very small then important points not processed
+    Needed in qh_partitionall for
+      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
+    Needed in qh_findbestnew for many instances of
+      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
+
+  See:
+    qh_DISToutside -- when is a point clearly outside of a facet
+    qh_SEARCHdist -- when is facet coplanar with the best facet?
+    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
+*/
+#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
+     fmax_((qh->MERGING ? 2 : 1)*qh->MINoutside, qh->max_outside))
+
+/*----------------------------------
+
+  qh_MAXcheckpoint
+    Report up to qh_MAXcheckpoint errors per facet in qh_check_point ('Tv')
+*/
+#define qh_MAXcheckpoint 10
+
+/*----------------------------------
+
+  qh_MAXcoplanarcentrum
+    if pre-merging with qh.MERGEexact ('Qx') and f.nummerge > qh_MAXcoplanarcentrum
+      use f.maxoutside instead of qh.centrum_radius for coplanarity testing
+
+  notes:
+    see qh_test_nonsimplicial_merges
+    with qh.MERGEexact, a coplanar ridge is ignored until post-merging
+    otherwise a large facet with many merges may take all the facets
+*/
+#define qh_MAXcoplanarcentrum 10
+
+/*----------------------------------
+
+  qh_MAXnewcentrum
+    if <= dim+n vertices (n approximates the number of merges),
+      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
+
+  notes:
+    needed to reduce cost and because centrums may move too much if
+    many vertices in high-d
+*/
+#define qh_MAXnewcentrum 5
+
+/*----------------------------------
+
+  qh_MAXnewmerges
+    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
+
+  notes:
+    It is needed because postmerge can merge many facets at once
+*/
+#define qh_MAXnewmerges 2
+
+/*----------------------------------
+
+  qh_RATIOconcavehorizon
+    ratio of horizon vertex distance to max_outside for concave, twisted new facets in qh_test_nonsimplicial_merge
+    if too small, end up with vertices far below merged facets
+*/
+#define qh_RATIOconcavehorizon 20.0
+
+/*----------------------------------
+
+  qh_RATIOconvexmerge
+    ratio of vertex distance to qh.min_vertex for clearly convex new facets in qh_test_nonsimplicial_merge
+
+  notes:
+    must be convex for MRGtwisted
+*/
+#define qh_RATIOconvexmerge 10.0
+
+/*----------------------------------
+
+  qh_RATIOcoplanarapex
+    ratio of best distance for coplanar apex vs. vertex merge in qh_getpinchedmerges
+
+  notes:
+    A coplanar apex always works, while a vertex merge may fail
+*/
+#define qh_RATIOcoplanarapex 3.0
+
+/*----------------------------------
+
+  qh_RATIOcoplanaroutside
+    qh.MAXoutside ratio to repartition a coplanar point in qh_partitioncoplanar and qh_check_maxout
+
+  notes:
+    combines several tests, see qh_partitioncoplanar
+
+*/
+#define qh_RATIOcoplanaroutside 30.0
+
+/*----------------------------------
+
+  qh_RATIOmaxsimplex
+    ratio of max determinate to estimated determinate for searching all points in qh_maxsimplex
+
+  notes:
+    As each point is added to the simplex, the max determinate is should approximate the previous determinate * qh.MAXwidth
+    If maxdet is significantly less, the simplex may not be full-dimensional.
+    If so, all points are searched, stopping at 10 times qh_RATIOmaxsimplex
+*/
+#define qh_RATIOmaxsimplex 1.0e-3
+
+/*----------------------------------
+
+  qh_RATIOnearinside
+    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
+    qh_check_maxout().
+
+  notes:
+    This is overkill since do not know the correct value.
+    It effects whether 'Qc' reports all coplanar points
+    Not used for 'd' since non-extreme points are coplanar, nearly incident points
+*/
+#define qh_RATIOnearinside 5
+
+/*----------------------------------
+
+  qh_RATIOpinchedsubridge
+    ratio to qh.ONEmerge to accept vertices in qh_findbest_pinchedvertex
+    skips search of neighboring vertices
+    facet width may increase by this ratio
+*/
+#define qh_RATIOpinchedsubridge 10.0
+
+/*----------------------------------
+
+  qh_RATIOtrypinched
+    ratio to qh.ONEmerge to try qh_getpinchedmerges in qh_buildcone_mergepinched
+    otherwise a duplicate ridge will increase facet width by this amount
+*/
+#define qh_RATIOtrypinched 4.0
+
+/*----------------------------------
+
+  qh_RATIOtwisted
+    maximum ratio to qh.ONEmerge to merge twisted facets in qh_merge_twisted
+*/
+#define qh_RATIOtwisted 20.0
+
+/*----------------------------------
+
+  qh_SEARCHdist
+    When is a facet coplanar with the best facet?
+    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
+        increases minsearch if ischeckmax and more than 100 neighbors (is_5x_minsearch)
+  See:
+    qh_DISToutside -- when is a point clearly outside of a facet
+    qh_SEARCHdist -- when is facet coplanar with the best facet?
+    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
+*/
+#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
+      (qh->max_outside + 2 * qh->DISTround + fmax_( qh->MINvisible, qh->MAXcoplanar)));
+
+/*----------------------------------
+
+  qh_USEfindbestnew
+     Always use qh_findbestnew for qh_partitionpoint, otherwise use
+     qh_findbestnew if merged new facet or sharpnewfacets.
+
+  See:
+    qh_DISToutside -- when is a point clearly outside of a facet
+    qh_SEARCHdist -- when is facet coplanar with the best facet?
+    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
+*/
+#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
+
+/*----------------------------------
+
+  qh_MAXnarrow
+    max. cosine in initial hull that sets qh.NARROWhull
+
+  notes:
+    If qh.NARROWhull, the initial partition does not make
+    coplanar points.  If narrow, a coplanar point can be
+    coplanar to two facets of opposite orientations and
+    distant from the exact convex hull.
+
+    Conservative estimate.  Don't actually see problems until it is -1.0
+*/
+#define qh_MAXnarrow -0.99999999
+
+/*----------------------------------
+
+  qh_WARNnarrow
+    max. cosine in initial hull to warn about qh.NARROWhull
+
+  notes:
+    this is a conservative estimate.
+    Don't actually see problems until it is -1.0.  See qh-impre.htm
+*/
+#define qh_WARNnarrow -0.999999999999999
+
+/*----------------------------------
+
+  qh_WIDEcoplanar
+    n*MAXcoplanar or n*MINvisible for a WIDEfacet
+
+    if vertex is further than qh.WIDEfacet from the hyperplane
+    then its ridges are not counted in computing the area, and
+    the facet's centrum is frozen.
+
+  notes:
+    qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
+    qh_WIDEcoplanar * qh.MINvisible);
+*/
+#define qh_WIDEcoplanar 6
+
+/*----------------------------------
+
+  qh_WIDEduplicate
+    merge ratio for errexit from qh_forcedmerges due to duplicate ridge
+    Override with option Q12-allow-wide
+
+  Notes:
+    Merging a duplicate ridge can lead to very wide facets.
+*/
+#define qh_WIDEduplicate 100
+
+/*----------------------------------
+
+  qh_WIDEdupridge
+    Merge ratio for selecting a forced dupridge merge
+
+  Notes:
+    Merging a dupridge can lead to very wide facets.
+*/
+#define qh_WIDEdupridge 50
+
+/*----------------------------------
+
+  qh_WIDEmaxoutside
+    Precision ratio for maximum increase for qh.max_outside in qh_check_maxout
+    Precision errors while constructing the hull, may lead to very wide facets when checked in qh_check_maxout
+    Nearly incident points in 4-d and higher is the most likely culprit
+    Skip qh_check_maxout with 'Q5' (no-check-outer)
+    Do not error with option 'Q12' (allow-wide)
+    Do not warn with options 'Q12 Pp'
+*/
+#define qh_WIDEmaxoutside 100
+
+/*----------------------------------
+
+  qh_WIDEmaxoutside2
+    Precision ratio for maximum qh.max_outside in qh_check_maxout
+    Skip qh_check_maxout with 'Q5' no-check-outer
+    Do not error with option 'Q12' allow-wide
+*/
+#define qh_WIDEmaxoutside2 (10*qh_WIDEmaxoutside)
+
+
+/*----------------------------------
+
+  qh_WIDEpinched
+    Merge ratio for distance between pinched vertices compared to current facet width for qh_getpinchedmerges and qh_next_vertexmerge
+    Reports warning and merges duplicate ridges instead
+    Enable these attempts with option Q14 merge-pinched-vertices
+
+  notes:
+    Merging pinched vertices should prevent duplicate ridges (see qh_WIDEduplicate)
+    Merging the duplicate ridges may be better than merging the pinched vertices
+    Found up to 45x ratio for qh_pointdist -- for ((i=1; i<20; i++)); do rbox 175 C1,6e-13 t | qhull d T4 2>&1 | tee x.1 | grep  -E 'QH|non-simplicial|Statis|pinched'; done
+    Actual distance to facets is a third to a tenth of the qh_pointdist (T1)
+*/
+#define qh_WIDEpinched 100
+
+/*----------------------------------
+
+  qh_ZEROdelaunay
+    a zero Delaunay facet occurs for input sites coplanar with their convex hull
+    the last normal coefficient of a zero Delaunay facet is within
+        qh_ZEROdelaunay * qh.ANGLEround of 0
+
+  notes:
+    qh_ZEROdelaunay does not allow for joggled input ('QJ').
+
+    You can avoid zero Delaunay facets by surrounding the input with a box.
+
+    Use option 'PDk:-n' to explicitly define zero Delaunay facets
+      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
+      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
+*/
+#define qh_ZEROdelaunay 2
+
+/*============================================================*/
+/*============= Microsoft DevStudio ==========================*/
+/*============================================================*/
+
+/*
+   Finding Memory Leaks Using the CRT Library
+   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
+
+   Reports enabled in qh_lib_check for Debug window and stderr
+
+   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
+
+   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
+   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
+
+   Examples
+     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
+     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
+*/
+#if 0   /* off (0) by default for QHULL_CRTDBG */
+#define QHULL_CRTDBG
+#endif
+
+#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
+#define _CRTDBG_MAP_ALLOC
+#include 
+#include 
+#endif
+
+#endif /* qh_DEFuser */
diff --git a/vendor/qhull/libqhull_r/usermem_r.c b/vendor/qhull/libqhull_r/usermem_r.c
new file mode 100644
index 0000000..27563cf
--- /dev/null
+++ b/vendor/qhull/libqhull_r/usermem_r.c
@@ -0,0 +1,102 @@
+/*
  ---------------------------------
+
+   usermem_r.c
+   user redefinable functions -- qh_exit, qh_free, and qh_malloc
+
+   See README.txt.
+
+   If you redefine one of these functions you must redefine all of them.
+   If you recompile and load this file, then usermem.o will not be loaded
+   from qhull.a or qhull.lib
+
+   See libqhull_r.h for data structures, macros, and user-callable functions.
+   See user_r.c for qhull-related, redefinable functions
+   see user_r.h for user-definable constants
+   See userprintf_r.c for qh_fprintf and userprintf_rbox_r.c for qh_fprintf_rbox
+
+   Please report any errors that you fix to qhull@qhull.org
+*/
+
+#include "libqhull_r.h"
+
+#include "igraph_error.h"
+
+#include 
+#include 
+
+/*---------------------------------
+
+  qh_exit( exitcode )
+    exit program
+    the exitcode must be 255 or less.  Zero indicates success.
+    Note: Exit status ('$?') in bash reports 256 as 0
+
+  notes:
+    qh_exit() is called when qh_errexit() and longjmp() are not available.
+
+    This is the only use of exit() in Qhull
+    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
+*/
+void qh_exit(int exitcode) {
+    /* exit(exitcode); */
+    /* This code is not reachable. We comment out exit() to ensure
+     * that the igraph shared library does not reference it. */
+    IGRAPH_FATALF("Qhull called exit() with exit code %d.", exitcode);
+} /* exit */
+
+/*---------------------------------
+
+  qh_fprintf_stderr( msgcode, format, list of args )
+    fprintf to stderr with msgcode (non-zero)
+
+  notes:
+    qh_fprintf_stderr() is called when qh.ferr is not defined, usually due to an initialization error
+    if msgcode is a MSG_ERROR (6000), caller should set qh.last_errcode (like qh_fprintf) or variable 'last_errcode'
+    
+    It is typically followed by qh_errexit().
+
+    Redefine this function to avoid using stderr
+
+    Use qh_fprintf [userprintf_r.c] for normal printing
+*/
+void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
+    va_list args;
+
+    va_start(args, fmt);
+    if(msgcode)
+      fprintf(stderr, "QH%.4d ", msgcode);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+} /* fprintf_stderr */
+
+/*---------------------------------
+
+  qh_free(qh, mem )
+    free memory
+
+  notes:
+    same as free()
+    No calls to qh_errexit() 
+*/
+void qh_free(void *mem) {
+    free(mem);
+} /* free */
+
+/*---------------------------------
+
+    qh_malloc( mem )
+      allocate memory
+
+    notes:
+      same as malloc()
+*/
+void *qh_malloc(size_t size) {
+    return malloc(size);
+} /* malloc */
+
+
diff --git a/vendor/qhull/libqhull_r/userprintf_r.c b/vendor/qhull/libqhull_r/userprintf_r.c
new file mode 100644
index 0000000..7eb3600
--- /dev/null
+++ b/vendor/qhull/libqhull_r/userprintf_r.c
@@ -0,0 +1,68 @@
+/*
  ---------------------------------
+
+  userprintf_r.c
+  user redefinable function -- qh_fprintf
+
+  see README.txt  see COPYING.txt for copyright information.
+
+  If you recompile and load this file, then userprintf_r.o will not be loaded
+  from qhull_r.a or qhull_r.lib
+
+  See libqhull_r.h for data structures, macros, and user-callable functions.
+  See user_r.c for qhull-related, redefinable functions
+  see user_r.h for user-definable constants
+  See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
+  see Qhull.cpp and RboxPoints.cpp for examples.
+
+  qh_printf is a good location for debugging traps, checked on each log line
+
+  Please report any errors that you fix to qhull@qhull.org
+*/
+
+#include "libqhull_r.h"
+#include "poly_r.h" /* for qh.tracefacet */
+
+#include 
+#include 
+#include 
+
+/*---------------------------------
+
+  qh_fprintf(qh, fp, msgcode, format, list of args )
+    print arguments to *fp according to format
+    Use qh_fprintf_rbox() for rboxlib_r.c
+
+  notes:
+    sets qh.last_errcode if msgcode is error 6000..6999
+    same as fprintf()
+    fgets() is not trapped like fprintf()
+    exit qh_fprintf via qh_errexit()
+    may be called for errors in qh_initstatistics and qh_meminit
+*/
+
+void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
+    va_list args;
+    if (qh) {
+        if (msgcode >= MSG_ERROR && msgcode < MSG_WARNING) {
+            qh->last_errcode = msgcode;
+        }
+        if (qh->FLUSHprint) {
+            fflush(fp);
+        }
+    }
+    if (!fp) {
+        return;
+    }
+    if ((qh && qh->ANNOTATEoutput) || msgcode < MSG_TRACE4) {
+        fprintf(fp, "[QH%.4d]", msgcode);
+    } else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
+        fprintf(fp, "QH%.4d ", msgcode);
+    }
+    va_start(args, fmt);
+    vfprintf(fp, fmt, args);
+    va_end(args);
+
+} /* qh_fprintf */
+
diff --git a/vendor/qhull/libqhull_r/userprintf_rbox_r.c b/vendor/qhull/libqhull_r/userprintf_rbox_r.c
new file mode 100644
index 0000000..64c16cb
--- /dev/null
+++ b/vendor/qhull/libqhull_r/userprintf_rbox_r.c
@@ -0,0 +1,53 @@
+/*
  ---------------------------------
+
+   userprintf_rbox_r.c
+   user redefinable function -- qh_fprintf_rbox
+
+   see README.txt  see COPYING.txt for copyright information.
+
+   If you recompile and load this file, then userprintf_rbox_r.o will not be loaded
+   from qhull.a or qhull.lib
+
+   See libqhull_r.h for data structures, macros, and user-callable functions.
+   See user_r.c for qhull-related, redefinable functions
+   see user_r.h for user-definable constants
+   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
+   see Qhull.cpp and RboxPoints.cpp for examples.
+
+   Please report any errors that you fix to qhull@qhull.org
+*/
+
+#include "libqhull_r.h"
+
+#include 
+#include 
+#include 
+
+/*---------------------------------
+
+   qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
+     print arguments to *fp according to format
+     Use qh_fprintf_rbox() for rboxlib_r.c
+
+   notes:
+     same as fprintf()
+     fgets() is not trapped like fprintf()
+     exit qh_fprintf_rbox via qh_errexit_rbox()
+*/
+
+void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
+    va_list args;
+
+    if (!fp) {
+      qh_fprintf_stderr(6231, "qhull internal error (userprintf_rbox_r.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
+      qh_errexit_rbox(qh, qh_ERRqhull);
+    }
+    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
+      fprintf(fp, "QH%.4d ", msgcode);
+    va_start(args, fmt);
+    vfprintf(fp, fmt, args);
+    va_end(args);
+} /* qh_fprintf_rbox */
+
-- 
2.30.2